ltmemo

機械学習及び自然言語処理に入門する

tags: 機械学習
2023-07-01

ToC

何を学ぶか

ChatGPTなど大規模言語モデルの隆盛に伴い、自然言語処理及び機械学習の基礎的な知識の理解が必要になったと感じている
ここでは機械学習及び自然言語処理の基礎の獲得に向けてもメモ書き

ただし、基礎硬めをずっとやっていると、結果的に何もできるようになっていない、という経験を得たため、今回は以下のロードマップに従う

  • BERTによる自然言語処理入門を読む
  • この過程でわからなかった知識や掘り下げた知識をメモとして残す

先にまとめ

  • BERTは自然言語処理向けの学習手法の一つ
    • Transformerのencodingレイヤを改善して精度を上げたもの
  • Transformerはモデル
    • Attentionという仕組みを用いて、RNNなど既存のモデルより学習が早く精度も高い、らしい
  • TransformersはHuggingFaceの作ったライブラリ
    • 名前がややこしい
    • BERTで作られたモデルをこのライブラリで簡単に読み込んでFineTuningなどができる
    • この本では文章の分類や固有表現の抽出を学んだ

BERTによる自然言語処理入門、2章までのまとめ

機械学習とNLPの概観を掴む
トークンの定義から、ニューラルネットワークを用いた学習でトークンの分散表現が得られると理解した
分散表現は語の周辺の語から求められる
分散表現はコンテキストを考慮して、他の単語との関連を示している

言語モデルは、文章の出現しやすさを学習している

LSTMやword2vecを通して、周りの文脈から単語の出現確率を学習しているという理解を得た。
LSTMでは、計算を重ねるごとに情報が薄まらないように、メモリセルという内部状態を保持している

3章のまとめ

BERTのモデル構造について学んだ。この知識だけでモデルを組める気はしないので雰囲気だけ理解、という気持ち
勉強に際して、この理解が無いと進まないな、というときが来たら学習する方針にする(今理解しようと頑張っても得られるものが少なそうという判断)

4章まとめ

BERTを動かすために、transformersというライブラリを使用する
ざっくり

  • transformersからimportできるモデルを使う
  • モデルからtokenizerを作れて、このtokenizerで文字列のトークンへの分割や、encodingが行える
  • tokenizerには複数文字列を渡せる。この時文字列長(系列長と呼ぶらしい)を統一する必要があるが、最大に合わせるなどのoptionが存在している
  • GPUに乗せる、CPUに戻す、といった操作がある
  • torch.no_grad()で囲む操作、デフォルトだと逆伝播用の勾配を計算する処理があるようで、それを無効にできるという話らしい

5章まとめ

文章の穴埋めの方法を理解する。BERTで穴埋めを実装する

  • BerfForMaskedLMの出力は3次元配列(バッチサイズ、系列長、語彙)
    • 実装詳細
      • BertModelから得る最終レイヤーの出力に対して、線形変換->GELU関数->線形変換を適用して分類スコアを計算している(実際は正規化や正則化のレイヤがあるけど割愛)
  • logitsってなんだ
    • softmaxに渡す前のニューラルネットワークの出力値
    • こいつをsoftmaxに食わせると確率が出る
    • これはbertのmodelのforwardで推論した戻り値に入っている
  • [MASK]の要素を穴埋めする方法を学んだ。貪欲法とビームサーチがある
  • bert_mlmを作ってtokenizerでencodeしたidsを投げ込むと、結果が取れる
    • cpu().numpy()のような呼び出しはちょっと慣れないと厳しそう
    • topkという関数を覚えた
    • idとtokenの相互変換はtokenizerで行う。bertのインスタンスでは、tokenのidsをもとに、スコアを求めるだけのようだ

6章まとめ

文章分類をやる。学びたいことに近い予感がする
livedoorニュースコーパスというデータセットがあるので、文章を与えられたカテゴリーに分類する「文章分類」を行う
ここでファインチューニングのやり方を覚えるぞ。今回は9種類らしい

BertForSequenceClassificationというクラスが文章分類に使える。BERTでは推論も学習もできる。今回は学習から行う

推論時に符号化した文章を入力して、分類スコアを出力
学習時に、符号化した文章とラベル(カテゴリ)を入力して、損失を計算

ファインチューニングしたモデルはsaveもloadも可能

BertForSequenceClassificationの実装内容

BertModelの最終層の出力のうち、最初のトークンである[CLS]に対応する出力に対して

  • 線形変換
  • tanh関数
  • 線形変換 が適用されて分類スコアが計算される

7章まとめ

マルチラベル分類 をやる。6章はいずれかのカテゴリに属する、というものだったけど、複数に属する場合には対応できない。
このような複数のカテゴリーを選んだり、いずれにも該当しない場合に何も選ばないような分類を「マルチラベル分類」と呼ぶ

活性化関数にはtanhではなくsigmoidを使う
選ぶかどうかを表すので、バイナリクロスエントロピー損失で損失を計算する
sigmoidと組み合わせて使われることが多いらしい。予測の確率が50を超えたら選ぶ、という対応関係のようだ

もう一歩踏み込む

理解に自信がわかないので、自分の言葉で実行する流れを記述する

  • マルチラベル分類はmulti-hotなデータでラベルを表す。これは一致するカテゴリだけ1になっているベクトルのイメージで良い
  • 文章に対して、各カテゴリにどれぐらい当てはまるかが推論結果として得られるイメージ
  • 確率50%以上でカテゴリを選択する、とするためsigmoid関数を活性化関数に使う
  • 選ぶ、選ばない、という二択の事象のため、評価関数はバイナリクロスエントロピー関数で良い

8章まとめ

固有表現抽出を学ぶ
固有表現はトークンとタグを用いて表される。そのために文章と固有表現からタグ列を作成したり、BERTが作成したタグ列から、文章中の固有表現を得る関数を実装する必要がある
関数としては以下を実装する

  • 文章と固有表現が与えられたときに、文章の符号化とタグ列の作成を行い、モデルに入力できる形式にする学習用関数
  • 文章を符号化するとともに、各トークンの文章中の位置を特定する推論関数
  • 文章とタグ列と各トークンの文章中の位置が与えられたときに、文章中に含まれる固有表現に対応する、文字列や位置を特定する推論関数

9章まとめ

文章校正。を学ぶ。トークンがサブワードを含む場合とそうでない場合があって、誤字になっている文字列と噛み合わない時にもうまく行くように工夫が必要

正しい文字列をencodingしたときのinput_idsを学習のlabelに使っていてなるほどな〜ってなった

10章まとめ

トークンの分散表現を文章全体で集約することで、文章をベクトルとして特徴づけることができる

この章では文章ベクトルの概念の理解、可視化をlivedoorニュースコーパスを用いて学ぶ

文章ベクトルの取得の方法は以下の2通りで、

  • BERTから出力されたベクトルのうち、トークン列の先頭の特殊トークン[CLS]に対応するベクトル
  • BERTから出力されたベクトルの平均 今回は後者を使う

語彙

  • コーパス
    • 文章を収集して、検索・分析などをできるようにしたデータベース
    • 品詞などの情報が付与されているものは、「注釈付きコーパス」と呼ばれる
  • トークン化
    • 文章を適当な単位に分割すること
    • 日本語の単語の分割ではMeCabやSudachiといった形態素解析ツールが広く利用されている
  • encoding
    • トークナイザーが行う
    • 文字列がidの配列に変換されるイメージ。idとトークンは紐付いていて、idから文字がひける
  • サブワード分割
    • 単語をさらに部分文字列に分割する方法
    • 例えば「東京大学」で「東京」と「大学」に分割
      • 少ない語彙で、より豊かな表現が可能
    • 未知語(語彙にない単語)に強い。東京と##大学、##タワーがあれば、東京大学と東京タワーもカバーできる
      • ##は単語の途中に現れる要素を示す
    • 効果的な分割を行うためには、語彙を適切に作成する必要がある
  • ニューラル言語モデル
    • ニューラルネットワークでもって表現される言語モデル
  • 言語モデル
    • 文章の出現しやすさを確率によってモデル化したもの
    • ある文脈下で、出現するトークンを予測するモデル
    • 条件付き確率
      • p(w2 | w1)とあるとき、w2がw1に続くという条件の上で、w2が出現する確率
      • 文章の出現確率はΠ p(wi|ci)で求められる
  • ニューラルネットワーク
    • なんらかの変換を行う層の組み合わせによって構成される
      • 線形な変換と、活性化関数と呼ばれる非線形変換のを組み合わせる
  • ハイパーパラメータ
    • 学習中に変更されない、入出力値の次元数など
  • 損失関数
    • パラメータが良かったかどうかを定量的に判断する関数
  • 多クラス分類
    • 入力データを与えられた複数のカテゴリに分類する
  • 埋め込み層(embedding layer)
    • トークンを意味のある入力とするために、それぞれを密なベクトル表現に変換する
    • このベクトルを分散表現または埋め込み表現と呼ぶ
      • このベクトルは、トークンの言語的特徴がベクトルの各次元に現れることを期待する
        • 単語の意味や特徴、関係性をベクトル空間上で捉えることができる
        • BERTも分散表現の一つっぽい?
    • 語彙の大きさN分散表現として割当、ベクトルの次元数をDとすると、埋め込み層はN☓Dの行列Eで特徴づけられる
      • i行目のベクトルに、i番目のトークンの分散表現がある
  • 分散表現
    • 単語に任意の次元数のベクトルを対応させた表現。同じような単語は同じようなベクトルを持つ
    • この値は周囲のコンテキストを考慮して算出される
    • 言語モデルの学習の最中に出力されるこの分散表現が、単語の特性を表しているとか
  • 分散意味仮説
    • 語の意味はその周辺に出現する語によって表される
    • 類似度を表す方法としては、コサイン類似度を用いるのが一般的
  • word2vec
    • 文脈に依存せず一意な表現を与えるモデルの一つ
    • 文脈費依存の分散表現を与えることを 単語埋め込み(word embedding) と呼ぶ
      • v(日本) - v(東京) ≒ v(フランス) - v(パリ) のような性質がある
    • 問題点として、多義語を扱う場合に問題になる。文脈非依存なのでかみてとじょうずの区別がない
      • 文脈に応じた意味の変化に対応できないってわけね
  • 加法構成性
    • 単語の意味的な演算と数値の演算の結果が一致するような性質のこと
  • CBOW (Continuous Bag-Of-Words)
    • 文章Sが与えられた時、i番目に位置する単語w_iをその周りの単語C_iから予測するモデル
      • 前後どれだけのContext(C_n)を考慮するかは固定で、これをウインドウサイズと呼ぶ
    • 文脈$C_t$において、j番目の単語$S_j$の出現しやすさは$v(C_i) \cdot v_t(s_j)$の内積で求められる
  • Skip-Gram
    • CBOWの逆のようなモデルで、$w_i$が与えられた時、その文脈$C_i$に出現する単語wの確率分布$p(w|w_i)$をモデル化する
    • $w_i$が与えられた時、$s_j$にその単語が出現する確率は〜という感じ
  • ELMo
    • 文脈に応じた表現を与えるモデルの一つ。word2vecと対比した表現になってる
    • 文脈に応じた分散表現を付与することを文脈化単語埋め込みと呼ぶ
    • 多層LSTMと逆方向の多層LSTMを組合せた厚生で、双方向LSTMと呼ぶらしい
      • 後ろの文脈からの確率と前の文脈からの確率でもって損失の計算を行い学習する
      • 予測対象以外すべての文脈から学習しているとも考えられる
  • 再帰型ニューラルネットワーク(Recurrent Neural Network)
    • RNNとも呼ばれる
    • 適当な時系列データ$(x_1,x_2,...,x_n)$を考える。時刻iにおけるRNNの出力$h_i$は、同時刻の$x_iとh_i-1$で次のように表現できる
      • $h_i=\phi (Ax_i+Bh_{i-1} + b) (i=1,2,...,n)$
      • A,Bは行列、bはベクトル、 $\phi$は活性化関数
      • ここでhを展開すると再帰的な式になる。これを$h_0$まで繰り返すことで、時刻iまでのすべての文脈を考慮して出力を計算できる
    • RNNの短所として、入力された情報を長期間保持できないことが指摘されている。計算のたびに$Bh_{i-1}に対してAx_i$が加算されるので、内容が薄まっていく
  • LSTM(Long Short-Term Memory)
    • RNNの欠点である、入力された情報を長時間保持することができないRNNの抱える構造上の問題を改善するために提案されたモデル
    • $h_i,c_i$の2つの内部状態を持つ。この内後者をメモリセルと呼ぶ
    • ゲートという概念があり、3つのゲートが対応する入出力値に対する重みを算出して、考慮・忘却する割合を決める
      • InputGate: RNNの出力$y_i$に乗算することで、現在時刻で入力された情報を考慮する割合を動的に調整する
      • ForgetGate: 前時刻のメモリセルc_{i-1}に対して乗算することで、過去に入力された情報を考慮する割合を調整する
      • OutputGate: 現在時刻の出力値$h_i$を計算する際に、最終段階に乗算することで現時刻の情報が次時刻で考慮される割合を動的に調整する
  • BERT
    • RNNの問題点、処理を続けると前方または後方のトークンの情報が失われる、また並列計算できない、という問題点を解決したモデルがBERT
    • トークンを処理する際に、他のトークンの情報を直接参照する。この時、どのトークンに注意を払うかを決める仕組みをAttention(注意機構)と呼ぶ
    • BERTはTransformerというモデルで提案されたTransformer EncoderというAttentionを用いたニューラルネットワークを用いている
  • Scaled Dot-Product Attention
    • n個のトークンで構成される文章で、一つ前のレイヤでの時刻iの出力を$x_i$で与えられるとした時、これに3つの行列で線形変換を行い、$q_i,k_i,v_i$というd次元のベクトルを得る。
      • ここでquery、key、valueの頭文字がそれぞれのベクトル
      • トークンはこの3つのベクトルから出力される。出力される$a_i$は$v_i$の重み付き平均で与えられる
        • $a_i = \sum^n_{j=1}a_{i,j}v_j$
        • ここでi番目のトークンのクエリとj番目のトークンの関連度を何かしらの方法で評価する
        • Scaled Dot-Productは、クエリとキーのスコアの評価方法のこと
    • この計算はベクトルを行列にまとめることで効率よく行うことができる
  • Multi-Head Attention
    • Scaled Dot-Product Attentionの、q,k,vを複数用意してそれぞれを適用し、最後に一つの出力に集約するような方法
  • Next Sentence Prediction
    • 事前学習時にはBERTには常に2つの文のペアが入力されている。この2つの文が連続したものかどうかを判定するタスクで、BERTを学習する
      • 特殊トークン[CLS]に対応するBERTの出力を分類機に入力して、2つの文が連続しているかどうかを判定する
  • transformers
    • Hugginface者の提供しているオープンソースのライブラリ
      • model版githubみたいな感じだとか
      • 日本語モデルとして、cl-tohoku/bert-base-japanese-whole-word-maskingを使う
    • transformersを用いた処理は、典型的には以下のような2つのステップに分かれる
      • トークナイザを用いて、文書をトークン化して、BERTに入力できるような形にする
      • 上記処理を行ったデータをBERTに投げて出力を得る
  • Mecab
    • 形態素解析を行うライブラリ
  • WordPiece
    • 単語をトークンに分割する手法の一つで、サブワードに分割を行う。
      • WordPieceの語彙に含まれない文字列の場合、[UNK]で表示される。アンノウンの先頭3文字
      • [CLS]という先頭のトークン、[SEP]という区切りのトークンがあるのを理解しておく
    • tokenizer.encodeで、トークンに割り当てたIDが帰ってくる
    • 複数の文章をまとめて処理する場合、系列長を同じに揃える必要がある
      • 足りないところには[PAD]が入るようだ
  • 系列長
    • トークン列の長さ
  • ipadic
    • IPA辞書のこと。UniDicというのもある
  • ビームサーチ
    • 探索アルゴリズムの一つ
    • ビームサーチでは、[MASK]を含む文章が与えられたときに、1つ目を埋めて10の文章を作って2つ目を埋めて10の文章を作って、その結果からスコアの高い10の文章を選んで...というふうに繰り返す処理
      • 一つ穴埋めするたびに合計スコアの高い10の文章を候補に残す
    • 貪欲法と同じく、MASKが多すぎると自然な文章は生成されない
  • 活性化関数
    • 非線形な変換を行うことで、モデルが複雑な関数も学習できるようにしている。また、sigmoidは0~1、tanhは-1~1に値を丸めるが、これにより勾配の値に制約を課して、勾配消失の問題を軽減する
      • 線形変換だけでは、ネットワーク全体も線形変換になる
  • データローダー
    • ファインチューニングでは指定された数(バッチサイズ)のデータ(符号化された文章)とラベルをデータセットから抜き出し、ミニバッチと呼ばれるデータ処理の単位を作る
    • データローダは、データセットからミニバッチを取り出すためのもの。この本ではTorchのものを使う
  • ミニバッチ
    • ミニバッチは各データと同じキーを持つ辞書。バッチサイズがlのTensorの時、バッチサイズmのデータローダは、(m, n1,n2,...,nl)のサイズのTensorを返す
    • 入力が辞書である必要は無いらしい
  • PyTorch上のTensors
    • 配列や行列に似たデータ構造
    • numpyの配列とメモリを共有できるので、変換にはデータコピーが不要
      • 自動微分に最適化されているらしい
    • テンソルはデフォルトでCPU上で作られるので、明示的に.to('cuda')のように呼び出してデバイス間を移動させる必要がある
  • 学習率
    • ハイパーパラメータの一つ。各学習ステップでパラメータを更新する際のステップの大きさを制御するために使用される
      • 小さいと学習がおそくなって、大きすぎると不安定になる
    • AdaGradやAdamのような自動調整する最適化手法もある
  • One-hotベクトル
    • どれかが1のベクトル
  • Multi-hotベクトル
    • 0または1からなるベクトル
  • 固有表現抽出
    • 人名や組織名、日付などの表現を抽出する
    • 何を固有表現とするかは、カテゴリーによる。金額とか地名とかもある
    • 固有表現の情報を以てタグ付け、みたいなこともできる
  • IO法
    • トークンにタグを当てるための簡易的な方法。BIO法というのがより性能がいいが複雑らしい
    • トークンが固有表現の一部なら、I-(TYPE) とする。TYPEは固有表現のタイプを表す文字列。I-(人名)みたいな感じ
    • 固有表現の一部じゃなかったらタグはO
    • IはInside、OはOutsideの意味
    • トークン列とタグ列がある時、O以外のタグが連続している部分トークン列を結合して固有表現とする
    • この場合、日米間ほにゃらら、みたいなときに「日米」を固有表現として扱ってしまう
  • BIO法
    • IO法の、同じタイプの固有表現が連続すると連結してしまう問題に対応できている
    • 先頭のときにB-(TYPE)のタグを付与する。それ以外はI-(TYPE)
      • なので一つのカテゴリにB-タグとI-タグの2種類が割り当てられる
    • IタグはBタグの後ろにしか来ないので、そのようなルールに即したスコアを算出しなければならない。具体的にはルールに従わない場合ペナルティを課すらしい
  • 適合率(Precision)
    • モデルが抽出した固有表現のうち、実際に固有表現だったものの割合
      • 2つ出力して片方正解なら0.5
      • 予測の正確性を示す
  • 再現率(Recall)
    • 文章中に含まれる固有表現のうち、モデルが正しく抽出できた固有表現の割合
      • 文章が10個固有表現を含んでいて、そのうち2つだけ正解したならば、再現率は0.2
      • 予測の網羅性を図る
  • F値(F-measure)
    • 適合率と再現率の調和平均
      • 調和平均は2x適合率x再現率 / {(適合率)+(再現率)}
        • 上の例だと、20.50.2 / 0.5+0.2
      • これが高いと適合率も再現率も高いとされる
  • t-SNE
    • t-distributed Stochastic Neighbor Embedding
      • Stochasticは確率論的、という意味
    • 次元削減のアルゴリズム。PCA(主成分分析)とかの仲間っぽい
      • 次元削減すると、高次元データの可視化に使える
  • Dropout
    • 計算の過程で不活性な関数を人為的に作り出す仕組み。過学習防止に使われるらしい

数学の語彙

softmax関数

ベクトルの中身を0~1の間で正規化した確率を返す関数
expをかけたyの全要素の平均で割っているので、0~1に正規化できてる
expを通すので負の値がないそうな

$$ p_i = \frac{exp(y_i)}{\displaystyle\sum^{N}_{j=1} = exp(y_j)} $$

ここでpの全要素の合計は1になる

クロスエントロピー損失の算出

上記$P_n$がある時、データがl番目のカテゴリに属する時、 $$ L(y, l) = -\log p_i $$ を分類問題の損失とする

CBOWの計算

CBOWは文脈中の単語をベクトルに変換する埋め込み層$V_c$と、
予測する単語をベクトルに変換する埋め込み層$V_t$の2つを用いる

$V_c$による単語wのベクトルは$v_c(w)$,$V_t$の場合$v_t(w)$とする

ここで確率分布 $p(w_i|C_i)$ は、文脈中の各単語$w_j$に対応するベクトル$v_c(w_j)$を平均する。
ここで $C_i$は周囲の単語$(w_i-c, ..., w_i-1,w_i+1,...,w_i+c)$から求まる

$$ v(C_i) = \frac{1}{2c}\sum_{w_j \in C_i} v_c(w_j) $$

2cはコンテキストの数。で、前後i個の要素があるので2cっぽい?

RNNの計算

$h_i=\phi (Ax_i+Bh_{i-1} + b) (i=1,2,...,n)$

簡略化して、$h_i = \phi (x_i,h_{i-1})$とする
$h_{i-1}$は時刻iより前の文章の単語列の関数$h_{i-1} = h_{i-1}(w_1,w_2,...,w_{i-1})$である

語彙にあるすべての単語の出現確率を割り当てるために、RNNの出力$h_{i-1}$に適当な線形変換を用いて、語彙と同じサイズのベクトル$h^{\prime}_{i-1}$に変換する

$w_i$に語彙中のj番目の単語$s_j$が現れる確率は、ベクトル$h^{\prime}{i-1}$のj番目の要素を$h^{\prime}{i-1},j$とすると、

$$ p(w_i=s_j|w_1,w_2,...,w_{i-1}) = \frac{exp(h^\prime_{i-1,j})}{\sum^N_{k=1} exp(h^\prime_{i-1,k})} $$

一つ前の文脈の中で、各単語の出現確率の中からjが出現する確率を表している。softmaxの計算になっているということに注目

ある位置より後ろにある単語から予測することも可能

tanh関数

双曲線正接関数とも呼ばれる。ハイパボリックタンジェント $$ tanh(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x)) $$ 入力の値を-1~1にマッピングする。0に近いほど0に近くて、大きいほど1か-1に近づく

sigmoidだと勾配が小さくなりがちで学習に時間がかかるらしく、代わりに使われるようになったとか。

行列の対角化

  • i==jの対角線の成分以外が0の行列のことを対角行列という
    • 必ずできるわけじゃない
  • $A = PΛP^-1$
    • このときのΛを対角行列と呼ぶ、らしい
    • 固有値と呼ぶこともあるとか
  • $A_pi = λ_i P_i$
    • $λ_i$は固有値
    • $p_iにAをかけると、p_iがλ_i倍になる,ということ$
    • $p_i$のことを固有ベクトル(eigen vector)と呼ぶとのこと
  • 何が嬉しいの?
    • 行列はベクトルをベクトルに変換する物体として捉える
    • したらばAはどんなベクトルをどう変換するの?
      • Aは$e_iをa_i$にするよ
        • $e_i$はワンホットベクトル
      • Aは$p_iをλ_ip_iにするよ$
      • 固有ベクトルはこの変換をわかりやすくしたもの。なぜなら固有値倍するだけだから簡単だよね
        • 固有値倍にくくり出せるので、長さを簡単に変えれるみたいな話を聞いたような記憶

分位数

分位数、または分位点、分位値とは、統計の代表値の一種
データを順に並べたとき、データをいくつかの等しい部分に分けるのに使う

でもってこの区切った時の値のこと。例えば四分位数は四等分にするので、値が四つ

この四つに分けたときの50パーセンタイルは中央値と同じ。

使ったライブラリ

  • transformers
    • huggingfaceが作ってるライブラリ。モデルの導入や推論が楽
  • fugashi
    • Mecabのラッパー。形態素解析で使う
  • ipadic
    • ipaの辞書
  • pytorch-lightning
    • ファインチューニングに際して使用した
  • tqdm
    • progressバーを表示してくれるiteratorを返すライブラリ。進捗表示で使う
  • PyTorch Lightning
    • ファインチューニングで使用した。xxx_stepの3つの関数とoptimizer指定するだけでできる
    • lossの保存の仕方がキモい。hyperparametersでinitに渡した値を自動で保存するのもキモい。なんでこういう怪しい書き方したがるんだ?
    • testの指標は自分で定義しないといけない

コードのtips

  • argmax関数は、通常最大値のindexを返すけど、-1を入れると最大値を返す
    • なんだってそんな使い方...
  • Tensor == Tensor で、boolの配列が得られる。それぞれの要素を比較してその結果が入るようだ
  • Tensorオブジェクトでは、intのようなprimitiveな値を取得するのにitem関数を使う
  • tokenizerのencodeで使う、paddingとtruncationという概念がある
    • 同じサイズのテンソルで扱えないので、長さを揃えるロジックがあるというわけ
  • unsqueeze
    • テンソルの形状の変更に使われる。指定した次元に1を追加する
      • ここで-1を指定すると、最後の次元が一個増える
  • all(-1)
    • 一番内側の配列の中身の論理積を取る
    • 同様にsum(-1)とすると、一番内側の配列の合計を取る
  • unicodedata
    • 正規化で使う。unicodedata.normalize("NFKC",文字列)みたいな感じ
    • NFKCは正規化方法の一つ。Normalization Form KC
    • 互換文字を対応する基底文字に変換する。例えば半角->全角のような変換が対象
  • attention_mask
    • この値は符号化のときのトークンで0のときは[PAD]で、1のときはそれ以外なので、[PAD]を除くようにしたい場合、attention_maskと対象のtensorの次元数をunsqueezeなどで揃えて*演算すれば良い

ライブラリの棲み分け再確認

  • pytorch,pytorch.lightning
    • 学習の実行に使う。自動微分など学習に適した機能がある
  • BERT
    • 自然言語処理向けに作成された事前学習済みのモデルの一つ
      • このモデルと上記のpytorchで学習する
  • transformers
    • BERTなどをひっくるめて自然言語処理のための機能群を提供してくれるライブラリ
    • こいつからBertModelやtokenizerをimportして使う。日本語の学習済みモデルもあってお手軽

Torchのチュートリアルをやる

このチュートリアルやって、雰囲気もうちょい掴んだら帰ってくる

基礎

  • numpyのarrayとtorchのtensorは相互に変換可能
  • tensorからtensorを作るときは、上書きしない限り形状とデータ型を維持する
  • tensorのshapeは、後ろの値ほど内側
    • shapeから、onesやzerosでtensorを作れる
  • tensorは保存されているデバイスを保持している
    • デフォルトはcpuで、to('cuda')とするとGPUに載せられる。サイズがでかいと重い。そらそう
  • catでtensorを結合できる。類似演算にtorch.stackもある
    • dimを指定すると、その次元の要素が連結される。
    • 例えば[ [1,2,3] ]がある時、dim0だと、[ [1,2,3], [1,2,3] ]になるだけだけど、dim=1だと[ [1,2,3,1,2,3 ] ]になる
  • @がmatmulのシンタックスシュガー
  • 1要素のテンソルはsumとかやると生まれる。.item()で中身を引き出せる
  • インプレースな更新は関数に_がつく。add_みたいな感じ
    • メモリを節約できるが、演算履歴が失われるので微分を計算するときには使っちゃ駄目

Dataloader, Datasets

  • torch.utils.data.{Dataset, DataLoader}という感じに生えてる
    • これらは予め用意したデータセットや自分で作成したデータを使用できる
    • Datasetにはサンプルと対応するラベルがあり、DataLoaderにはいてレート処理が可能なデータがある
      • DataLoaderがDatasetをラップしてる感じ
  • datasets.FashionMNISTみたいなそのまんまな名前が入ってたりする
    • transformでラベルの変換、trainというflagで訓練化どうかを切り替える
      • ラベルにToTensorを渡すと、tensorに変換してくれるって仕組みっぽいな
  • Datasetクラスのカスタマイズには、init,len,__getitem__の3つの実装が必要
  • データは基本的にlabel情報のセットなんだな
    • 教師あり学習ではそらそうじゃ

Transforms

名前がtransformersとややこしいが、こっちはデータの前処理を行い学習に適した形に変えるもの
lambdaはコールバックの関数みたいな扱いのようだ
ToTensorはPIL形式の画像もしくはNumpyのndarrayをFloatTensorに変換するんですと。画像の場合はpixelの値を0~1に変換する

torchで作るNN

torch.nnにレイヤー(モジュールとも)のクラスや関数が用意されている。
PyTorchすべてのモジュールはnn.Moduleを継承している

forwardは実装はするけど、model.forward()のような呼び出し方はしない

  • nn.Flatten
    • 2次元配列を1次元に展開する
    • ミニバッチを投げる時、0次元目はサンプル番号なので1次元目以降が変更される
  • nn.Linear
    • 線形変換
    • 重みとバイアスを保持している
  • nn.ReLU
    • 活性化関数の一つ
  • nn.Sequential
    • layer(module)を束ねたもの
  • nn.Softmax
    • 損失関数
    • linear layerの出力が最終的に[-∞,∞]の範囲のlogitsを返すので、これをsoftmaxに入れて0~1に丸める
      • dimが次元数で、dim=1にすると確率の総和で1になる

nn.Moduleは内部でパラメータを保持していて、parametersやnamed_parametersで各レイヤのパラメタにアクセス可能

自動微分

with句にtorch.no_gradを投げたり、requires_gradをFalseにしたり、detachを呼び出したりなど、
勾配計算を無効にする設定がいくつかある

無効にしたいシチュエーションはネットワークの一部を固定したい場合。ファインチューニングでよくある
順伝播の計算を高速化したい場合。これは推論時

torchはDefine-by-run形式で、実行時に計算グラフを作るのでifとかの制御構文が使いやすいらしい

最適化

損失関数は回帰だったらMeanSquareError、分類だったらNegativeLogLikelihoodのようによく使われるものがある。

  • MeanSquareError
    • 予測値と実測値の差異の2乗の平均
  • NegativeLogLikelihood(負の対数尤度)。NNL
    • 確率分布Pにおける実測値yの対数尤度を負にしたものですって
    • $NLL = -log(P(y))$

最適化器をoptimizerと呼ぶ。これが各ステップでモデルの誤差を小さくするようにモデルパラメタを調整するプロセス

SGDとかADAMとかが該当する

訓練ループでの最適化は以下のステップで行われる

  • optimizer.zero_gradで勾配をリセット
  • loss.backwardsでバックプロパゲーションを実行
  • optimizer.stepでパラメタの勾配からパラメタの値を調整

モデルの保存と読み込み

.pth形式のファイルで保存する。モデルごと保存する場合と、パラメタを保存するstate_dictを保存する場合がある

TransformerとTorchTextを使ってseq2seqやる

seq2seqは、入力の文章から次に来る単語を予測するモデル。nn.Transformerというモジュールがある
これはAttention is All You Needを元に実装されている

nn.TransformerEncoderは、複数のnn.TransformerEncoderLayerで構成される。
単語を文章から推論するタスクは、入力シーケンスとともにattention_maskが必要
Self-Attention層では、シーケンス内で前方のトークンのみに着目しなければならないので、後ろのトークンはマスクする
これは普段の会話で後ろの文脈がわからんのと同じ
torch.triu関数、対角要素の左下にある要素をすべて0にするそのあとこねくり回すんだけど、要はこの行列で以て未来の情報にアクセスできない状況というのを表現している

位置エンコーディング層って? -> 単語の順序情報が埋め込まれたベクトルらしい(peはposition encodingってことみたい)
こいつをモデルに考慮させるのが大事なんやな

Attentionが単語の関連の強さを表していて、このときに位置情報が無いと文脈がおかしくても同じ関連の強さになってしまうので、位置を考慮しよう、という話のようだ

まずtransformerについてもうちょい掘り下げる必要がありそう

transformerとMultiHeadAttention

  • BERTやGPT、DALL-Eなどいろんなタスクに使える
  • RNNは使わず、Attentionのみ
    • RNNと違って並行で学習が行える

レイヤー

  • MultiHeadAttention
    • どの情報に注目すべきか判断して情報を処理する
    • queryとkeyの類似度を内積で計算し、それに応じた重みでvalueを足している
      • 重みはsoftmaxで出す。類似度が高いものだけ大きくなってほかは小さくなる
    • queryは複数ある
    • key,valueの学習が重要で、これをうまくやるのがMultiHeadAttention
    • 入力xがq,k,vに渡されるが、Xにw^q_iやw^k_iの行列を掛けて、xのどこを処理するのか、またいろんな向きのxを作るためにkで回転させているイメージらしい
      • kはx同士をどう注目するか、見る角度を変える演算
  • Add&LayerNormalization
    • 後者は学習高速化の正規化らしい
  • FeedForwardNetwork
    • ReLU(xW_1,+b_1)W_2+b_2

torchのapiメモ

  • torch.arange

    • 連番の配列を作成する
  • tensor.view

    • 形状を変更する
  • tensor.narrow

    • tensorから指定の範囲を切り出す
  • torch.stack

    • 新しい軸を追加してtensorを連結する
      • dimで指定した次元が増えるイメージ
      • [ 1,2 ]と[ 3,4 ]をdim=0でstackすると、[[ 1,2 ], [ 3,4 ]]になる
  • contiguous

    • メモリ上で連続した配置に置き換えるんだそう
  • trainの流れ

    • batchをトレーニングデータから抜き出し
    • optimizerを初期化
    • 損失を計算
    • 損失のbackward実行
    • 勾配爆発防止のためにparametersを調整(clip_grad_norm_)を使う
    • optimizer.step()でlrを更新
    • lossを計上

モデルの準備、optimizerの準備、テストデータの準備がそれぞれ必要
できたらあとは投げ込むだけ

LangChain触る

  • LangChainのモジュール
    • LLM
      • LLM呼び出しの共通I/F
    • プロンプトテンプレート
      • まんまテンプレート。中身の一部をユーザ入力に置き換えてぽん
    • チェーン
      • 複数のLLMやプロンプトの入出力をつなげる
      • PDFを5分割してそれぞれプロンプトに投げて結果をプロンプトに投げて〜、みたいなのができる
    • エージェント
      • ユーザの要求に応じてどの機能をどういう順番で実行するかを決定する
    • ツール
      • エージェントが実行する特定の機能
    • メモリ
      • チェーンやエージェントの記憶を保持
  • LLMのやり取りの途中経過をSQLiteとかに永続化できるみたい
    • これが熱そう
      • ファインチューニングでもいいんだけど、外部のstaticなデータ(==事実)を参照して結果を返すというのがいい
        • ユーザに根拠を示せるのが熱い
  • MemoryというChainsやAgentsの内部の状態を保持する仕組みがある
  • OutputParsers
    • 出力のデータ形式を指定するための機能
  • langchainとchatGPTプラグインの違いって何?
    • langchainはライブラリで、ChatGPTプラグインは、ChatGPT自体に生えている拡張機能
  • DocumentLoadersといってPDFやCSVなどのデータを読み込む機能がある
    • これって要するに構造化データを作れたらばGPTに参照させられるってことだよな〜
    • LlamaIndexもこれ使ってそう
    • S3のファイルやBigQueryにも対応しているらしい
      • GitもGoogleDriveもある
  • Retrievers
    • ドキュメントを検索するための機能
  • PromptTemplateを使うと、promptのtemplate文字列を渡せる
    • chanisにSimpleSequentialChainというChainを連結できるクラスがある
    • 入力できる変数名でもって、chainのinput/outputを連結しているというわけ
  • IndexChainがllama_index相当の機能らしい
  • TextSplitter
    • OpenAIのAPIはtokenの上限数がある
    • その他にも特定のサイズのチャンクに分けて処理が必要な場合があるので、そういうときに使う
  • RetrievalQAは、ソース付きの質疑応答を行える。FAISSにメタデータを指定できる

ツール

  • エージェントの処理の流れの行動で実行するもの
  • pythonのrequestsが使えるので、既存のAPIから情報を取得できる

VectorStorage

類似する文章の検索ができる

  • Chroma
    • pythonだとこっち
  • Faiss
    • デファクト?
    • nodeで動かせる。in-memoryやnodeのアプリケーションはこっちが良いらしい
    • といいつつpythonのライブラリもあったな
    • OpenAIのembeddingを使った結果でindexを貼れる
  • その他
    • なんかいっぱいある
      • HNSWLib
      • LanceDB
      • MemoryVectorStore
      • Pinecone
        • 有料だけどproduction-ready

LlamaIndex

LLMと外部データを接続するためのライブラリ。LLMに独自のデータを扱わせるには、
ファインチューニングするか、入力プロンプトにコンテキストを埋め込むかの2択で、LlamaIndexは後者をサポートしている

特にStorageが面白くて、チャンクに分けた分散表現とそのメタデータや元データへの参照を管理できる
これを使うと根拠を示しつつサジェストや解答を行うチャットが作れる

ServiceContextの登場人物

  • NodeParser
    • textバラすやつ
  • Embeddings
    • 文字列を分散表現ベクトルに変換するやつ
  • LLMPredictor
    • LLMを処理するクラス
  • PromptHelper
    • トークン数の制限に合わせてテキストを分割する
  • CallbackManager
    • 処理の前処理後処理
  • LlamaLogger
    • LLMへのクエリのログを取得するのに使う

最終更新: 2023-07-30