検索拡張生成 RAG

大規模言語モデル(LLM)の登場により、検索の世界が変わりつつあります。これまではキーワードをいくつか入力し、それに関連するドキュメントを検索するような使い方でしたが、LLMによって自然文での検索や自由度の高い検索が可能になりました。

しかしながら、大規模言語モデルが学習しているデータは概ね公知となっている一般的な常識に基づいたものが多いです。したがって、一般的な質問であれば真っ当な回答が得られますが、特定の社内情報など学習されていない秘密情報等については当然ながら回答できないです。

そこで、LLMリクエストする質問のプロンプトを上手く利用して、特定の情報への質問も可能なようにする技術が検索拡張生成 RAG(Retrieval Augumented Generation)です。ここでは、RAGの概要とちょっとした実装例を紹介いたします。

RAGの概要

RAGとは、上記のような特定の情報の中から必要な情報を検索したり、内容を質問して回答を生成してもらう(Q&A)を可能にする仕組みです。簡単にやるのであれば、プロンプトに全ての情報を詰め込み、最後に検索や質問をつけてLLMにリクエストすることでできなくは無いです。ですが、プロンプトのサイズには制限がありますし、不要な情報まで詰め込むことで処理速度も下がり、コストもかかります。ですので、検索や質問に関連しそうな部分だけをプロンプトに詰め込んで送るというのがRAGの基本的な仕組みとなります。

RAGのサーベイ論文の図を引用しますと、まずプロンプトエンジニアリングというものがベースとしてあり、RAGは特定の外部情報などを必要とする場合に拡張するもの、一方ファインチューニングはモデル自体をチューニングするため、ドメイン適応などに適していると説明されています。

もちろん、RAGとファインチューニングは排他的では無いので両方を組み合わせることで両方の良い部分を享受できるようになります。

RAGの処理フロー

RAGの基本的な処理フローを書きます。LLMのモデルにが学習されていない外部情報となるドキュメントがあり、それに対してユーザが質問や検索をして、回答文を生成するというシナリオの処理フローになります。

まず外部情報となるドキュメントを分割して、文章単位にします。この文章をベクトル化し、ベクトルの形式で保存しておきます。次にユーザが質問や検索を自然文で入力します。このユーザの質問文もベクトル化します。このユーザの質問文のベクトルと、外部情報として保存してある各文章のベクトルをベクトル検索で比較してベクトルが近いものをピックアップします。ピックアップしたベクトルの文章とユーザの質問文をプロンプトにいれてLLMにリクエストするプロンプトを生成します。このレスポンスが回答文となります。

この一連の処理フローの中でキーとなる技術を列挙しますと

  • ドキュメントの分割
  • 文章のベクトル化(Embedding)
  • ベクトル検索
  • プロンプト生成

となります。これら全てを提供してくれるフレームワークとして、LangChainやLlamaIndex等があります。特に重要となる部分が文章のベクトル化で、ここは大抵はSBERT(Sentence BERT)というものが利用されます。

文章をベクトル化できてしまえば、ベクトル検索の部分はベクトルのコサイン類似度等を計算すれば類似度比較ができますので、やはりベクトル化の部分が重要になってきます。

SBERT (Sentence BERT)

SBERTとは、事前学習されたBERTモデルを使い、類似する文章のベクトルが近くなるようにファインチューニングされたもので、文章の類似度を比較する文章ベクトルを生成するのに適しております。

上記の論文の図を引用しますが、あらかじめ類似する文章のペアを(類似しない文章のペアも)ファインチューニングデータとして使い、2つのBERTモデルにそれぞれを入力して出力されたベクトルを演算し、Softmaxの分類器で類似・非類似のラベルを出力するモデルを学習させます。

この学習過程で、各BERTモデルは類似度計算に適した文章ベクトルを出力できるようにチューニングされていきます。このファインチューニングされたBERTモデルをSBERTと呼び、さまざまな文章タスク(類似度、文書分類など)に利用できます。

Sentence Transformer

SBERTを簡単に試せる実装として、sentence-transformerがあります。ここではsentence-transformerを使って文章をベクトル化し、類似度を計算する例を紹介します。

pythonを使います。sentence-transformerのインストールは簡単です。ベクトル

# pip install sentence-transformers

文章をベクトル化して比較するサンプルプログラムをこちらのhuggingfaceのサイトを参考に掲載します。

from sentence_transformers import SentenceTransformer, util

docs = ["外部情報1を入れます。", "外部情報2を入れます。"]
query = "ユーザの質問文を入れます。"

#Load the model
model = SentenceTransformer('sentence-transformers/multi-qa-MiniLM-L6-cos-v1')

#Encode query and documents
query_emb = model.encode(query)
doc_emb = model.encode(docs)

#Compute dot score between query and all document embeddings
scores = util.dot_score(query_emb, doc_emb)[0].cpu().tolist()

print(scores)

docsには、検索対象となるドキュメントの文章をリストで格納します。queryには質問となる文章を入れます。modelはいろいろありますが、マルチリンガルでそれほど大きく無いモデルを利用します。modelのencode関数で文章をベクトル化でき、384次元のベクトル値に変換されます。dot_scoreという関数を使えば、これらのベクトルの類似度を計算してスコア化してくれます。

コサイン類似度

コサイン類似度とは、2つのベクトルの角度が近いか正反対かを示すもので、ベクトルの類似度に利用されます。角度が一致するものが1、正反対のものが-1となります。計算方法は、2つのベクトルの内積を距離の積で割ることで算出できます。

上記はpythonで実装しているので、ベクトル同士の類似度計算(コサイン類似度)も用意されていますが、もし実装言語でそういった便利なものがない場合は下記のように計算できます。フロントで実装することを考慮して javasciptで記載します。

function cosine_similarity(vec1, vec2){
    var dot = 0;
    var dist1 = 0;
    var dist2 = 0;
    for(i = 0; i < vec1.length; i++){
        dot += (vec1[i] * vec2[i]);
        dist1 += (vec1[i] * vec1[i]);
        dist2 += (vec2[i] * vec2[i]);	
    }
    dist1 = Math.sqrt(dist1);
    dist2 = Math.sqrt(dist2);    
    var similarity = dot / (dist1 * dist2)
    return similarity;
}

使い方の例も書きます。

    let a = [1.2, -1.3];
    let b = [1.3, -1.2];
    let c = [-1.1, 1.1];    

    let sim;
    sim = cosine_similarity(a, b);
    console.log(sim);
    sim = cosine_similarity(a, c);
    console.log(sim);    

aとbは似ているので0.99ぐらいの値が、aとcは真逆なので -0.99ぐらいの値がでてきます。

もし、ベクトル値が文字列で保存されていたり、受け取ったりした場合は下記のように変換すれば上記と同様に計算できます。

    let a = ['1.2', '-1.3']; 
    a = a.map(str => parseFloat(str));

Transformer

ひと昔前、ニューラル機械翻訳では時系列的な入力を扱えるRNN (Recurrent Neural Network)ベースのsequence-to-sequenceが用いられていました。この時もAttentionというメカニズムは使われていましたが、2017年にGoogleから発表された論文 “Attention is All you need”では、AttentionだけでかつてのRNNの性能を大きく上回る機械翻訳精度を達成し、今では自然言語処理分野の技術は、ほぼAttentionベースの技術(Transformer, BERT, GPT等)が主流となっております。

このGoogleから発表されたAttentionベースのニューラル機械翻訳技術がTransformerです。BERTやGPTも、このTransformerの仕組みと共通しているので、このTransformerの理解が自然言語処理の分野では最重要だと思います。

RNNのAttention

では、まず Transformerで重要となる Attentionについて説明します。このAttentionは、従来のRNNベースの機械翻訳でも使われているため、まずRNNベースで、Attentionとは何なのかを説明します。

RNNの内部状態だけだと、最初の方で入力した単語の情報ほど情報が薄くなっていってしまいます。Attentionとは、デコーダで出力する単語が、エンコーダの入力単語のどの単語を注目すれば良いかを示す指標になります。これは、デコーダ側で所定の位置の単語の内部状態において、エンコーダ側の各入力における内部状態との相関(類似度)を計算し、各入力単語の内部状態に重みをつけて合算し、Context Vectorをいうものを作ります。このContext Vectorをデコーダの出力層に入力し、出力単語の尤度計算に影響させることで、Attentionを考慮したsequence-to-sequenceが可能になります。

このAttentionメカニズムは、query, key, valueに分けて説明されることが多いです。上のRNNの場合、queryはTarget側のベクトルになります。keyとvalueはSource側のベクトルになります。

まず、queryと各keyの内積をとってSoftmaxで正規化したものがAlignment Weightというベクトルになります。これはTarget側の所定の位置の単語が、どの入力単語と相関が高いかを示し指標です。

次に、そのAlignment Weightと各valueを掛けたものが、Context Vectorとなり、デコーダの出力層に影響を与えます。相関が高い単語ほど大きく影響を与えることができるようになります。

TransformerのAttention

では、Transformerの説明に入ります。TransformerはRNNのように前の入力の内部状態を引き継いでいく構造では無いです。すべてAttentionでの計算になります。エンコーダ側もしくはデコーダ側だけでAttentionを計算するものを Self Attentionといいます。また、RNNの時と同じように、デコーダの所定の位置の計算にエンコーダのAttentionを計算するものを Source Target Attentionといいます。

Self Attentionは、上の図だと、ある層において、各ポジションごとに他の入力とのAttention Weightを計算し、上の層の内部状態を計算するようになっています。

Source-Target Attentionは、基本的にエンコーダの最後の層(一番上)の内部状態を使います。こちらはRNNと同じようにAttentionを計算し、デコーダの次の層(上の層)を計算していきます。デコーダの最終層(一番上)から、出力単語を推定します。

RNNを使わないと、もはや語順とか関係ないんじゃないかと思うかもしれませんが、入力の部分にポジションエンコーディングという機構があり、この単語は全体の何番目に入力された単語かを示すベクトルが付与されています。

Transformerの全体の構成を論文から引用します。

左側がエンコーダで右側がデコーダになってます。それぞれN層積んで構成されます。この中で新たにでてくるのが、Masked Attentionと、Multi-Head Attentionかと思います。

Masked Attentionとは、デコーダでは未来の単語がわからない状態で推定しなければいけないため、未来の単語へのAttentionは使わないという意味でMaskします。

Multi-Head Attentionですが、TransformerはAttentionしか使わないので、Attentionを全体で行うと入力文章の意味をあまり理解せずに、単にエンコーダの単語からカンニングするような翻訳がでてしまう可能性があるため、エンコーダの単語群をいくつかのグループにわけて、いろんな視点からAttentionを計算することにより、単なるカンニングよりも深い翻訳ができるようにしているイメージです。

ここまでがTransformerの概要ですが、最近、大規模言語モデルとして注目されているBERTやGPTも、このTransformerがわかると理解できます。(というかTransformerの一種という位置付けです)BERTは、Transformerのエンコーダ部分で構成されており、文書分類、ラベリング等、さまざまな言語処理が可能です。GPTは、Transformerのデコーダ部分だけで構成されており、デコーダの先頭に文章を入れることで、その続きを生成してくれます。この後は、ChatGPTで注目され始めたGPTについても説明します。

GPT (Generative Pre-trained Transformer)

GPTとは、OpenAIなどが研究開発している Generative Pre-trained Transformerという大規模な生成AIの言語モデルで、タスクに特化した学習が必要なく自然な文章を生成できることが特徴です。

オリジナルのGPTモデルは、2018年にリリースされて、1億1700万のパラメータを持つ巨大なモデルでした。その後、GPT-2、GPT-3とスケールアップし、GPT-2では10倍の15億のパラメータ、GPT-3ではさらにその100倍の1750億のパラメータの超巨大な言語モデルとなっています。GPT-3.5で、ChatGPTがリリースされてテレビのニュースなどでもよく取り上げられるようになりました。

GPT-3の論文はこちらです。 Language Models are Few-Shot Learners

GPTですが、基本的にはTransformerのDecoderだけを使います。このDecoderのプレフィックスとして、タスクやFew shot Learningのサンプルを入れ、最後に本題となる質問を入れると、その後を生成してくれるイメージです。下にイメージ図を書きます。

これはTransformerのDecoderだけの構成なのですが、Few shot Learningでは、いくつかのサンプルを学習させる際にモデルの再学習やファインチューニングをするわけではなく、デコーダのプレフィックスとしてタスクやサンプルを入れます。そして本題の質問を入れると、これまでの入力の流れから推測し自然に続く文章を生成する過程で、質問の回答を生成してくれるというようなイメージです。

動画や論文などで紹介されている例を示します。

Translate English to French:   (Task Description)
sea otter =>  loutre de mar    (Example 1)
peppermint =>  menthe poivree  (Example 2)
plush giraffe =>  giraffe peluche  (Example 3)
cheese =>             (Question)

この例では、Task Descriptionとして、”Translate English to French:” としています。英語をフランス語に訳すというタスクを頼むという宣言です。

そしてその後に、サンプルとして3つほど、英語からフランス語に訳した例を入力します。

最後に cheeseは?という質問を入力しています。プロンプトの ”=>”で終わると、これまでの経験から cheeseのフランス語を答えればいいのだなとわかってくれます。そして、fromage(フランス語のチーズ)という単語を生成してくれます。文章の場合は、この後も1単語ずつTransformerのデコーダと同じように生成していきます。

さて、GPT-3を使ってみた感想ですが、文章だけみると本当に人間が書いたような文章だと思うぐらい自然な文章を生成してくれます。内容に関してはまだ弱い部分もあるようですが、自然な文章を生成する能力においてはかなり人間レベルに近いのではないかと思いました。

さらにChatGPTになると、Instruct-GPTという人手の採点による強化学習も組み込まれており、人が生成するにふさわしい文章がでてくるようになっております。言語能力としての汎用性能も上がり、Few Shot Learningしなくても、プロンプトに好きな指令を自然な文章で書けば、それに対応した文章を生成してくれます。内容も人間を超えてると思えるものがでてきたりします。誰でも使える魔法のようなチャットボットが誕生したわけです。

さらには、GPT-4もでてきました。より難しい依頼にも対応できるようになっていたり、言語としての美しさに磨きがかかっております。

今後、人間に求められるのは、創造性やオリジナリティの部分なのかなと思いました。

M系列符号で自己同期

人類の全ての活動がリモートで可能かどうか試されている今、リモートで再生される音声を同期させたいということもあるかと思います。リモート演奏等。

そこでM系列符号で自己同期を取る方法を思い出しましたので、簡単にご紹介します。

M系列符号とは

M系列符号とは、nビットのシフトレジスタから生成される周期が2^n-1の符号列なのですが、とても便利な特徴があります。

  • 自己相関のピークが1周期に1回(ぴったり合った時)だけあり、それ以外は-1(相関ほぼ無し)になる。
  • 0と1の発生確率がほぼ等しい。(擬似ノイズとしても使える)

この自己相関の特徴が、同期を取るのに大変便利なので、同期処理や測位など幅広く使われています。

自己相関のイメージ図を書きます。3ビットのシフトレジスタから生成された7ビットのM系列符号の例です。

7ビットのM系列符号を1ビットずつずらし、各ビットをXOR計算して加算したものが自己相関です。(XORで0の場合に-1を加算します。)上の図のようにぴったり合ってる場所だけが自己相関値が7になり、その他は-1(3勝4敗)になります。ほぼ無相関です。これを使えば、どこが始まりかわからない信号でも先頭を検出できます。

M系列符号生成のサンプルプログラム

M系列符号は、生成多項式から構成されるシフトレジスタの出力で作れるのですが、下記のようなプログラムで生成できます。生成多項式x^8 + x^4 + x^3 + x^2 + 1で生成する255ビットのM系列符号の例です。

#define SpreadingRate  (255)  
#define Generator    (11101)
#define MaxBit       (0x0100)
#define XorBit       (0x011d)

char * code_generator()
{
  int i;
  char *code;
  unsigned int shift = Generator;

  code = (char *)calloc(SpreadingRate, sizeof(char));

  for(i = 0; i < SpreadingRate; i++){
    if(shift & 0x01){
      code[i] = 1;
    }else{
      code[i] = 0;
    }
    shift = shift << 1;
    if(shift & MaxBit){
      shift = shift ^ XorBit;
    }
  }
  return(code);
}

SpreadingRateは符号ビット長になり、生成多項式の次元数nの2^n-1になります。Generatorは、シフトレジスタの初期値なので何でも良いです。変えると符号ビット列が巡回します。MaxBitは最高次のみ1とするビット列で、XorBitは最高次を含めた各係数のビット列です。

M系列符号の自己同期で遊んでみました

M系列符号を音声信号にしてスピーカから送信し、マイクで録音して自己同期をとるという遊びをしてみました。図に書きます。

encoderは、M系列符号を音響信号にしてWaveファイルを生成します。このWaveファイルをスピーカで再生し、マイクで録音してWaveファイルにします。この録音Waveファイルをdecoderで自己相関計算し、ピーク(同期ポイント)が検出できるかをやってみました。

こちらが1サンプルずつずらして自己相関を計算した相関値です。あるポイントだけ鋭いピークが立っているので、ここがM系列符号の始まりだということがわかります。反射波などがあれば、他の場所にも少しピークが立ちます。つまり音響経路のインパルス応答と見なせます。

ちなみにM系列符号は、下図の左側のようにそのまま矩形波で送っても良いですし、右側のように位相変調して送っても同じ信号で相関を取ればよいのでどちらでも可能です。

この遊びのサンプルプログラムをこちらにおきますので、何かお役に立てそうでしたらお使いください。

Msequence-sample.zip

ReactをApacheで動かす

ブラウザのアプリ開発もSPA (Single Page Application)が主流となりつつあります。Slackとかブラウザで動作させていても快適ですね。

このSPAのアプリを開発するJavascriptのフレームワークとして、Angular、React、Vue.js等があります。今回は、このReactをApacheで動かすまでのお話です。

Reactとは

まずReact等のSPA用のライブラリがなぜ便利なのかですが、基本的にはコンポーネントを分離して開発しやすいのが大きなメリットだと思います。まず前提としてはページに何か更新があった時には、ページ全体を更新(DOMの再構築)するのではなく、更新した部分だけを変更します。

ページはヘッダ部やサイド部などいろいろなコンポーネントに分かれていると思いますが、例えばサイド部のボタンを押して何か更新したときに、ヘッダ部やコンテンツ部にも変更がおよぶことがあると思います。これがとても面倒なことだということは何となくわかるかと思います。

Reactライブラリを使った場合、ボタンを押した結果を再描画する場合は、Reactライブラリに「全体再描画」という依頼をするだけで、差分のある部分だけを更新してくれます。これにより、各コンポーネントを独立して開発しやすくなります。

前準備

では、実際にReactを使うための説明に入ります。Reactの開発をするためには、node.jsやnpmコマンドが必要になってきますので、これらをインストールします。

Linuxの場合 (Ubuntu)

# apt install nodejs npm

Mac OSの場合 (まずnodebrewというnpmやnode.jsを管理するプログラムを入れます)

# brew install nodebrew
# nodebrew install-binary latest

でいけるかと思います。

Create ReactAppのインストール

Create ReactAppは、新しいReactアプリを作成するのに便利なツールで、Reactを学習するための快適な環境があります。まず、これをインストールします。

# npm install -g create-react-app

Reactのサンプルアプリの作成

ReactのHelloWorld的なサンプルアプリは、上記でインストールしたCreate ReactAppを使って簡単に作成できます。

# npx create-react-app react-sample

これで、カレントディレクトリにreact-sampleをというフォルダができて、その下に初期プログラムができています。試しにこれが動くかどうかを確認してみましょう。react-sampleというフォルダに移動して、下記のコマンドを実行します。

# npm run start

3000番ポートでWebサーバが立ち上がり、Reactのアプリが閲覧できるようになります。自動でブラウザが立ち上がると思いますが、http://localhost:3000/ にアクセスすると閲覧できると思います。

こんなページがでたら成功です。

Apacheから閲覧できるようにする

先ほどのテストは独自のWebサーバを3000番ポートで立ち上げて確認できましたが、実際にreactアプリを運用するにあたっては、HTTP(80番ポート)でアクセスできるようにしたいです。ですが大抵の場合WebサーバはApacheやnginxがWebサーバとして動いているので、reactだけの専用サーバを80番ポートで立ち上げられない場合が多いです。

ここでは、Apacheで上記のreactアプリを動かす手順を書きます。

まず、先ほどのreactアプリをbuildする必要があるのですが、デプロイするURLを設定する必要があります。これは、package.jsonという設定ファイルに追記します。

{
  "name": "react-sample",
  "version": "0.1.0",

このような記載で始まっていると思いますが、nameの下にでもデプロイするURLを下記のように追記します。

{
  "name": "react-sample",
  "homepage": "http://localhost/react-sample/",
  "version": "0.1.0",

そして、buildします。

# npm run build

そうすると、上記のURLにデプロイする用にbuildされ、buildフォルダが作成されます。そのbuildフォルダを Apacheのドキュメントルートから参照できる場所にコピーすれば Apacheから閲覧できようになります。Apacheのドキュメントルートが /usr/local/htdocs の場合は、

# cp -R build /usr/local/htdocs/react-sample

これで、http://localhost/react-sample のURLで閲覧できるようになります。

Hello World的なものを作ってみる

ここまでだと、何のファイルがどういう役割で表示されているのかわからないです。src/ の下にあるファイルを編集して、buildし、buildフォルダをコピーすれば変更が反映されますが、どこを編集すれば良いかを説明します。

まず、index.jsからスタートします。

ReactDOM.render(
  <react.strictmode>
    <app>
  </app></react.strictmode>,
  document.getElementById('root')
);

このようなスクリプトが書いてあると思います。これはこのままで良いのですが、ここで <App />というタグがあり、Appが実行されます。Appは App.jsというファイルに書かれていまして、それをimportしております。

App.jsを見ると、下記のようなスクリプトがあり、divで囲まれた中が描画されます。サンプルは画像を表示するものでしたが、ここを書き換えてHello Wolrdを表示してみます。

function App(){
    return (
        <div classname="App">
           <h1> Hello World </h1>
           ここに書いたものが描画されます。
        </div>
    );
}

divタグの中にHTML的なものを書いていけば、それが表示されます。これだと普通にHTMLを書くのと変わりないので、RouterやBrowserRouterという機能を使って、コンポーネントを組み合わせてページを作るところまで説明します。

まず、react-router-domというものをインストールする必要があると思います。

# npm install react-router-dom

これでインストールできると思います。そして、ページをHeader部、Content部、Footer部にわけて表示してみます。 App.jsを下記のように書いて、Header, Content, Footerを読み込むようにします。各コンポーネントは、Header.js, Content.js, Footer.js に記載します。

import { Route ,BrowserRouter} from 'react-router-dom';
import Header from './Header';
import Content from './Content';
import Footer from './Footer';

function App(){
    return (
        <div classname="App">
         <h1>Hello World</h1>
         <browserrouter>
           <route path="/" component="{Header}/">
           <route path="/" component="{Content}/">
           <route path="/" component="{Footer}/">
         </browserrouter>
        </div>
    );
}
export default App;

Header.js、Content.js、Footer.jsは下記のように書いておきます。(Header.jsだけの例)

import React, { Component } from 'react';

class header extends Component{
    render(){
        return(
            <div>
              ここはヘッダ部です。
            </div>
        );
    } 
}
export default header;

こんな感じでContent.jsもFooter.jsも書いて buildすると、各コンポーネントを読み込んだHTMLを描画してくれます。

SPAを作るためにRouter機能は必要となってくるので、ここまでイメージができていると Reactに入っていきやすくなります。

GPU対応のDockerコンテナ作成

サーバ開発で当たり前のように使われるコンテナ技術(Docker等)ですが、GPUが絡んでくると最初の設定でつまづくことが多々あります。ここでは、こうやったらできたというのをメモとして残しておきたいと思います。

ホスト側のGPU設定

コンテナを作る前に、そのコンテナが動くホストのGPUが使える状態になっていないと、当然コンテナでも使えません。なので、まず初めにホスト側のGPUが使えるかの確認です。ホストOSはubuntuを前提に書きます。

GPUが使えるかどうかは、nvidia-smiコマンドで確認できます。これで、GPUの使用率などが表示されたら使える状態です。nvidia-smiコマンドが見つからない場合は、/usr/local/cuda等を探してみましょう。それでも入ってなさそうな場合は、cudaをインストールする必要があります。

CUDAのインストールは、ubuntu20.04の場合、

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get -y install cuda

と、1行ずつ実行すればインストールできます。

nvidia-smiコマンドもインストールされていると思います。nvidia-smiコマンドでGPUの情報が見られればOKです。

AWSのGPUインスタンスを使う場合は、AWSから提供されているDeepLearning Base AMI (ubuntu20.04)を使うと楽です。

GPUのDockerコンテナ作成

GPU対応のDockerコンテナを作成する場合、元となるイメージも普通のubuntuイメージではなく、nvidia/cudaのイメージを使う必要があります。

こちらのDocker Hubで、nvidia/cudaのイメージリストがありますので、この中から選んで docker pullすることでイメージをダウンロードでき、イメージからコンテナを作成できます。

作成したコンテナの中に入り、GPUが使えるように設定していきます。

まず、NVIDIA Container Toolkitをインストールします。

これはLinuxのDistributionごとにURLが変わるので、まずコンテナ内のLinuxのDistributionを見なければいけないです。

$ cat /etc/os-release

とすれば、OS情報が見れます。この中のIDとVERSION_IDをくっつけたものがDistribution名になります。

bashであれば、下記のコマンドで distributionという変数にDistribution名を格納できます。

$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)

そしてこの distribution変数を使って、curlでapt用のパッケージリストをとってきます。

$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - 
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
$ sudo apt update && sudo apt install -y nvidia-container-toolkit 

これで、dockerコンテナ起動時に、–-gpus all オプションをつければ、コンテナ内でGPUが利用できるようになっています。

$ docker run -it -d --gpus all --name [container_name] docker_image_name:tag
$ docker exec -it [container_name] sh

コンテナ内で nvidia-smiコマンドを実行して、GPU情報がでてくれば大丈夫です。

docker-composeでGPUコンテナを起動

docker-composeでコンテナを作成する場合は、–-gpusオプションと同等のオプションが無いため、もう少しやることがあります。

まず、nvidia-container-runtimeをインストールします。

$ sudo apt install nvidia-container-runtime

設定ファイルを /etc/docker/daemon.jsonに作成します。

{ "runtimes": 
  { "nvidia": 
    { "path": "nvidia-container-runtime", 
      "runtimeArgs": [] 
    } 
  } 
}

これで、docker-composeの設定ファイルで runtime: nvidia ができるようになります。

docker-compose.ymlの例を記載します。

version: '2.3'
services:
  [service_name]:
    image: [image_name]:[tag_name]
    container_name: [container_name]
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
    tty: true
    ports:
      - 80:80
      - 443:443
    volumes:
    - /var/host:/var/docker

このdocker-compose.ymlファイルがある場所で、docker-compose up -d でコンテナを立ち上げて、コンテナ内で nvidia-smiを実行し、GPU情報が見れればコンテナ内でもGPUが使えます。

おまけ(コンテナ内の環境設定)

作りたてのコンテナでは、GPU以外にもいろいろとインストール・設定することがあると思います。これもメモですが、だいたいこのあたりは使うだろうというものを書いておきます。

コンテナ内がubuntuの例で書きます。

$ apt-get update

これはすでに上記のGPU設定でやってますが、一応書きます。これでいろいろ必要なものをインストールできるようになります。

あと、gccとかmakeとかも入ってないのでこのあたりの開発コマンドを入れます。

$ apt update 
$ apt install build-essential

pythonも使うなら、

$ apt-get install python3-distutils 
$ apt-get install python3-dev

pipのインストールは

$ apt-get install wget 
$ wget https://bootstrap.pypa.io/get-pip.py 
$ python3 get-pip.py

ここまでやれば、AWSでubuntuのEC2インスタンスを立ち上げたぐらいの状態になってると思います。

ただひとつ困ったことがありました。dockerのシェル上で、Ctrl-pが使えないのです。(dockerのdetachのコマンドのため) Ctrl-pは、ひとつ前のコマンドを実行したいときに多用するので、これが使えないと困ります。一応2回押せばできるんですが、そんなのは無理です。 上矢印キーでもできるんですが、手をホームポジションから動かすのは嫌です。

ですので、Ctrl-pを使えるようにします。

~/.docker/config.json の設定ファイルの中に

"detachKeys": "ctrl-\\"

と書いて、detach key をCtrl-pとは違うキーに割り当てます。これで、Ctrl-pが使えるようになります。

これでubuntuで遊べるようになりました。せっかくなので、この状態をイメージに保存しておきたいと思います。イメージとして保存しておけば、この状態からコンテナを作成できるようになります。

$ docker commit [container_name] [image_name]:[tag_name]

これで、このコンテナが、[image_name]で保存されて、[tag_name]というタグが付いています。

WordPressのインストール

ブログを始めようと思った時に、アメブロやFC2ブログなど、無料でお手軽に始められるブログサービスがたくさんありますが、やっぱり独自の城を築きたいと思う方にはWordPressがおすすめです。環境構築など、少しのITリテラシーと少しのサーバ費用が必要ですが、SEO対策などが施されており上位表示されやすく、ドメインも独自ドメインにできるためかっこがつきます。

ここではWordPressの導入をわかりやすく説明していきたいと思います。

WordPressの前提知識

WordPressを導入するためには、レンタルサーバやAWS等を利用して自分のサーバを持つ必要があります。月に数百円程度の出費でブログには十分な自分専用のサーバが利用できるようになります。ここでは、そのサーバでWordPressがどう動くのかを説明します。

まず、WordPressをダウンロードしてみましょう。こちらの公式サイトからダウンロードできます。

ダウンロードしたzipファイルを解凍すると、wordpressというフォルダができて、その下にフォルダやファイルがたくさんあります。

それでは、全体像を説明します。

WordPressのインストール概要

まず、ダウンロードしたwordpressのファイル達をWebサーバにアップロードする必要があります。Webサーバのどこに置けばよいかというと、ブラウザからWebサーバにアクセスした時に見えるドキュメントルートというフォルダに置く必要があります。レンタルサーバの多くの場合、wwwというフォルダです。自分でApacheなどのWebサーバを導入した場合は、/var/www/htmlがデフォルトだと思います。

とにかくここに、先程ダウンロードしたwordpressのファイル達をごっそりコピーすれば良いです。(レンタルサーバによって様々ですが、FTP、SCP、ファイルマネージャーなどを駆使してコピーします)

このWebサーバ以外に、データベースというものを把握しておく必要があります。これはブログの記事やコメントなどを保存しておく場所です。レンタルサーバではたいていデータベースもセットで提供してくれるので、あまり気にする必要はありませんが、後ででてくるWordPressの設定でデータベースの設定も必要になってくるので、把握しておいた方が良いです。

データベースもひとつのサーバとして動いています。なのでサーバのホスト名なるものが存在します。その中にデータベースというものを複数作れます。WordPress用にひとつのデータベースを使います。このデータベースにも名前があり、データベース名と言います。

さらにデータベースの中にテーブルというものが複数作成されます。こちらはWordPressが自動的に作ってくれるので気にする必要は全くありませんが、ここに固定ページ用のテーブルや投稿ページ用のテーブルなどがあり、保存されていきます。

データベースサーバには、ユーザ名とパスワードでアクセスできるようになっているので、このデータベースサーバにアクセスできるユーザ名とパスワードがWordPressの設定に必要になってきます。

まとめると、以下の情報がWordPressの設定に必要になってきます。

  • データベースのホスト名
  • データベース名
  • データベースにアクセスできるユーザ名とパスワード

これらはレンタルサーバの場合、最初から提供されていたりします。

一番つまづきそうな点は、やはりWordPressのサーバへのインストールなのですが、これもレンタルサーバによっては自動でできる場合があります。

さくらインターネットの場合は、コントロールパネルでこんな感じのものがあり、WordPressのインストールもボタンひとつでできてしまいます。

今までの説明はなんだったのかと思うかもしれませんが、たとえボタンぽちっでインストールできたとしても上記の概念的なことはわかっておいて損はないと思います。

WordPressの設定

つぎに、いよいよWordPressの設定なのですが、これは先程のWebサーバにブラウザから普通にアクセスすれば始まるようになっています。

どこにアクセスすればよいかというと、それはWebサーバのドメイン名で、

http://Webサーバのドメイン名/

にアクセスすれば良いです。Webサーバのドメイン名ですが、デフォルトだとレンタルサーバが提供するサーバ名(ドメイン)がついてると思います。

でも、せっかくなのでブログの内容がわかるような独自ドメインをつけたいですね。それについてはまた別記事で触れたいと思います。ひとまず、WordPressの設定を続けます。

このような画面がでてきたら、WordPressが正常にインストールされていて動いています。この後、データベースの設定をしていきます。「さあ、始めましょう!」ボタンを押しましょう。

ここに先ほど説明したデータベースの各種情報を入力していきます。最後のテーブル接頭辞はWordPressがテーブルを作る際の命名規則になりますが、複数のサイトを同じデータベースで運用しない限りはこれを変える必要はありません。そのままにして「送信」ボタンを押しましょう。

このような画面がでてきたら、入力したデータが正しく設定されてデータベースにも接続できております。「インストール実行」を押しましょう。

サイトの内容を設定していきます。タイトルはブログのタイトルですね。ユーザ名とパスワードは、サイトの編集モードにログインするためのユーザ名・パスワードになります。メールアドレスは、当然ですが自分が読めるメールアドレスを入れましょう。そして「WordPressをインストール」を押します。

この後、WordPressが自動的にテーブルなどを作成して完了します。

完了したらログイン画面がでてくると思いますので、上記で設定したユーザ名・パスワードでログインしましょう。WordPressのダッシュボードがでてきたら、WordPressライフの始まりです。

おすすめのプラグイン

WordPressには便利なプラグインがたくさんあり、自分好みのプラグインをインストールして機能を好きに拡張していけます。その中でも必須と思われる機能として、バックアップとSEO対策の2つをご紹介します。

どちらも All-in-One XXX という名前なので、プラグイン追加画面で、All in Oneで検索すれば両方でてきます。

バックアップの方は右側の、All-in-One WP Migrationというプラグインで、サイト全体のデータをエクスポートして、zipファイルとして保存できます。復元はこのzipファイルをインポートするだけです。とても簡単にバックアップができます。

SEO対策の方は左側の、All in One SEO というプラグインで、SEO対策の定番となっております。サイトマップの作成から、SEO観点で記事の良し悪しやサイト全体を採点してくれたりするので、SEO的に良い記事や良いサイトを作りやすいです。

他にも目次を作ってくれるプラグインなど便利なものがたくさんありますので、バックアップをとりながらいろんな機能を試してみるのも良いと思います。

PHPのライブラリ管理ツール Composer をインストール

Composerとは、PHPのライブラリを容易にインストールしてくれるツールで、ライブラリの依存関係も自動的に判断して必要なライブラリも自動的にインストールしてくれる便利なツールです。

PHPでWebアプリを実装する場合、JWTトークンの検証や決済プロバイダのSDKなど、さまざまなライブラリがあります。ここでは、それらをインストールして使えるようにする方法を紹介します。

Composerのインストール

では早速Composerのインストールですが、Composerの公式サイトにダウンロード・インストール方法が記載されているので、そのまま行うだけです。ですが、英語で書かれているので、日本語にしながら転載します。(Macならbrew install composerでもインストール可能です)

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

この4行を1行ずつ実行するだけです。PHPがインストールされていてコマンドで実行できる必要があります。

実行した場所に、composer.phar という実行ファイルができていると思います。これが管理ツールのコマンドとなります。試しに実行してみましょう。

./composer.phar

Composerのテキストロゴ、バージョン、オプションの説明が表示されたらOKです。

このコマンドをどこでも利用できるように /usr/local/bin などにコピーしましょう。名前もpharを取ってcomposerにしましょう。

sudo mv composer.phar /usr/local/bin/composer

これでどこでもcomposerが使えるようになりました。

ライブラリのインストール

次にcomposerを使って、PHPのライブラリをインストールする方法です。実行した場所にvendorというフォルダができて、この下にライブラリが保存されます。PHPファイルから読み込みやすいように、PHPファイルを置く場所で実行すると良いです。

たとえば、認証等で使うJWTトークンを検証するためのライブラリをインストールする場合は、

composer require firebase/php-jwt
composer require codercat/jwk-to-pem

を1行ずつ実行します。vendor/firebaseやvender/codercatというフォルダができてここにインストールされています。

もうひとつ、クレジットカード決済を行う決済プロバイダ StripeのSDKをインストールする場合は、

composer require stripe/stripe-php

を実行します。verdor/stripeというフォルダにインストールされます。

vendor/autoload.phpというファイルがありますが、プログラムからはこれを読み込むだけでライブラリを使えるようになります。

ライブラリの使用

では、PHPプログラムからライブラリをどう使うかですが、これも簡単で冒頭に2~3行書くだけです。

例えば、php-jwtを使う場合は、

<?php
  require __DIR__ . '/vendor/autoload.php';
  use Firebase\JWT\JWT;
  use CoderCat\JWKToPEM\JWKConverter;

と書きます。requireでライブラリのある場所を指定し、useでJWTのライブラリを使うことを宣言しています。この後、JWT::encode() や JWT::decode() などのように関数を利用できます。

もうひとつ、StripeのSDKを利用する場合は、

<?php
  require __DIR__ . '/vendor/autoload.php';
  \Stripe\Stripe::setApiKey($secret_key);

のようにuseを使わずにライブラリを使うことも可能です。useは名前空間に別名をつけるだけですので、フルパスで指定すれば直接使えます。この例では、StripeのAPIキーを設定しています。($secret_keyはシークレットキーです)

(おすすめのライブラリ).envファイルを使いましょう

シークレット情報や環境設定情報などを別のファイルに分けておきたい場合に便利なのが .envファイルです。こちらもcomposerでライブラリをインストールすれば使えます。

composer require vlucas/phpdotenv

これで、vender/vlucasにphpdotenvがインストールされて使えるようになります。使い方は上記と同様に、

<?php
  require __DIR__ . '/vendor/autoload.php';
  Dotenv\Dotenv::createImmutable(__DIR__)->load();

で利用できて、同フォルダの.envファイルを読み込めます。.envの書き方は、

SECRET_KEY="secret"
PUBLIC_KEY="public"

のような書き方で、PHPファイルの中で$_ENVの連想配列として参照できます。

<?php
  require __DIR__ . '/vendor/autoload.php';
  Dotenv\Dotenv::createImmutable(__DIR__)->load();

  $secret_key = $_ENV['SECRET_KEY']
  $public_key = $_ENV['PUBLIC_KEY']

気をつけたいのは、.envファイルにはAPIキーやパスワードを書いたりするため、公開してはいけません。公開github等にpushしてしまわないように、.gitignoreで除外する等しましょう。

また、Webサーバにデプロイした場合にも、.envを直接参照できてしまうと情報がダダ漏れになってしまいますので、.envファイルはWebサーバからアクセスできないところに置く方が賢明です。

.envを別の場所に置いた場合は、上記の __DIR__ 部分を書き換えて、.envを置いたフォルダパスを書けば読み込めます。

ブログ

このブログでは、日常の開発業務などから、ちょっと苦労したことなどをメモがわりに記している記事を書いています。自分がちょっと行き詰まった点で「こうやったらできた」というものが他の人にもお役に立てればと思って書いています。

また、AI技術の急速な進化についても取り上げており、テクノロジーやAIに興味のある方にも楽しんでいただけると思います。

日々のちょっとした学びから独自の洞察まで、読者の皆さんに価値ある情報と共感をお届けできるよう心掛けています。ぜひ気軽に読んでみてください。