「ゼロから作る Deep Learning 2 自然言語処理編」の読書まとめ。後で思い出すためのメモです。コードは以下。図は転記しません。本を片手に読む感じ。
1章 ニューラルネットワークの復習
第1巻の復習の章です。第1巻を読んでいれば基本飛ばしてもよい章です。
計算グラフ
足し算、掛け算に関しては前巻でも載っていましたが、分岐ノードとリピートノード、Sumノードに関しては今回初めて明示的に出てきました。また使っていたけど明示的に今回MatMulノードも説明されています。
分岐ノード
順伝播に関しては同じ値が2つに分岐するだけ、逆伝播に関してはそれぞれの勾配の加算を行います。コピーノードとも。計算による説明はありませんでしたが、直感的にはわかります。
リピートノード
分岐が複数になっただけ。むしろ分岐を一般化したもの。
x = np.random.randn(1, D) # 仮入力 y = np.repeat(x, N, axis=0) # 順伝播 dy = np.random.randn(N, D) # 仮勾配 dx = np.sum(dy, axis=0) # 逆伝播
Sumノード
リピートの順序が逆になったイメージ。加算ノードを複数束ねたもの。これもむしろ加算ノードを一般化したものとみることができそうです。なので逆伝播も加算と同様に同じ値が分岐するだけです。
x = np.random.randn(N, D) # 仮入力 y = np.sum(x, axis=0) # 順伝播 dy = np.random.randn(1, D) # 仮勾配 dx = np.repeat(dy, N, axis=0) # 逆伝播
分岐の逆伝播がSumで、Sumの逆伝播が分岐。という関係になります。
MutMulノード
行列の掛け算のノード。前巻でも複数バッチ処理のところで自然と使っていたノードです。行列なので各要素の掛け算と足し算がいっぺんに行われていると読めばそれほど難しくないです。順伝播は普通に行列の掛け算。ここでは\(x_i\)に注目して、以下の式で微分を説明しています。
$$ \frac{\partial L}{\partial x_i}=\sum_j\frac{\partial L}{\partial y_j}\frac{\partial y_j}{\partial x_i} $$
順伝播で\(x_i\)がたくさんの係数\(y_j\)と掛け算され、和をとっているのでその偏微分も複数の\(y_j\)が登場するのも直感的に理解できます。そしてその\\frac{\partial y_i}{\partial x_i}\)が\(y=ax\)になってるだけなので、微分結果が重み\(W_{ij}\)になっているということが理解できれば式
$$ \frac{\partial L}{\partial x_i}=\sum_j\frac{\partial L}{\partial y_j}\frac{\partial y_j}{\partial x_i}=\sum_j\frac{\partial L}{\partial y_j}W_{ij} $$
の意味もなんとなく分かります。結果通常の乗算ノードと同じような計算グラフになっています。行列表現の便利なところですね。
W = np.random.randn(D, H) # 仮重み x = np.random.randn(N, D) # 仮入力(Nはバッチ数かな) y = np.dot(x, W) # 順伝播 dy = np.random.randn(N, H) # 仮勾配 dx = np.dot(dy, W.T) # 逆伝播 dW = np.dot(x.T, dy) # 逆伝播
2章 自然言語と単語の分散表現
本題となるword2vecに入る前に知識としてのカウントベースの手法と分散表現に関して、その手法、効果と課題の説明されています。この章ではまだニューラルネットワークは出てきません。
自然言語処理とは
我々が普段使っている言語をコンピュータに理解させるための処理です。曖昧さを含む「柔らかい」言語をコンピュータが扱える数字で表現していきます。
狙いとしては、単語の意味を理解させる作業です。
シソーラス
言葉の意味を人の手によって分類した辞書です。いろいろ意味づけもされており、「同義語、類義語」だけでなく、「上位と下位」「全体と部分」のような関連付けもされています。carとautomobileは同義語、motor vehicleの下位にcarといった感じ。有名なものでは「WordNet」というものがあり、付録にPythonによる使い方が載っています。
ただこの辞書式には欠点がいくつかあります。
①時代によって言葉が変わる。
②人作業なのでコストが高い。
③単語の細かいニュアンスが表現できない。(例えば「ヴィンテージ」と「レトロ」)
など。
カウントベースの手法
この手法はシソーラスの欠点を克服すべく、大量のテキストデータから自動的に単語の意味を抽出する手法です。画像の分類で人手による特徴量抽出がディープラーニングに置き換わったように、自然言語処理でもこれに近い状況になっています。
この学習に使う大量のテキストデータのことをコーパスと呼びます。この人手によって書かれた文章から自動的に、効率よくエッセンスを抽出することが目標になります。
コーパスも単純な文章だけでなく、品詞のラベルを付けるなどの前処理が施されている場合もあります。が今回の例では純粋な文章で実装が進みます。
前処理
大文字を小文字にしたり、ピリオドを分けたり、スペースを区切り文字として単語に分けたり。そんな処理です。そのうえで各単語にIDを付け、IDから単語、単語からIDをそれぞれ引けるような辞書を作っておきます。
You say goodbye and I say hello.
おしゃれな例文です。sayが2回出てきているのでいいサンプルになったのだと思います。
0:”you” 1:”say” 2:”goodbye” 3:”and” 4:”i” 5:”hello” 6:”.”
こんな辞書です。すると例文は
[0,1,2,3,4,1,5,6]
と変換されます。
単語の分散表現
単語をベクトル表現するための表現のことを分散表現と呼びます。色をR,G,Bであらわすようなものです。深緋がどんな色かわからなくても(R,G,B) =(201.23.30)と言われればなんとなくどんな感じの色かわかるように、単語もこのようなベクトルに変換します。このベクトルは固定長で各要素密な表現(非ゼロが多い)が望ましいです。
分布仮説
「単語の意味は周囲の単語によって形成される」直感的には理解できます。I drink beer. We drink wine.確かにdrinkのあとには飲み物が来やすい。そしてI guzzle beer. We guzzle wine.であれば意味は分からなくてもguzzleはdrinkと意味が似てそうなこともわかる。
注目する単語の周囲にある単語を「コンテキスト」と呼びます。そしてこの周囲の範囲を「ウィンドウサイズ」と呼びます。いったん定義です。
共起行列
周囲の単語をカウントすることで得られる行列のことです。左右1単語となりにある単語に1をを立てていくようなそんな操作をした結果できる行列です。例を見た方がわかりやすいです。先ほどの例文で考えます。7つの単語からなるこの例文の場合
you | say | goodby | and | i | hello | . | |
you | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
say | 1 | 0 | 1 | 0 | 1 | 1 | 0 |
goodbye | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
and | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
i | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
hello | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
. | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
配列で表現すると、
np.array([ [0,1,0,0,0,0,0], [1,0,1,0,1,1,0], [0,1,0,1,0,0,0], [0,0,1,0,1,0,0], [0,1,0,1,0,0,0], [0,1,0,0,0,0,1], [0,0,0,0,0,1,0], ])
0行目のベクトルがyouを表すベクトルというわけです。
ベクトル間の類似度
2つのベクトルがどれだけ似ているか?を測るための物差しで、よく使われるのがコサイン類似度となります。
$$ similarity(x,y) = \frac{x_1y_1+…+x_ny_n}{\sqrt{x_1^2+…x_n^2}\sqrt{y_1^2+…y_n^2}} $$
分子にベクトルの内積、分母がL2ノルム。式がややこしいですが、概念としては二つのベクトルがなす角度をコサインで表したもので、1であれば角度ゼロなので完全に同じ方向を向いているのでよく似ている。-1であれば真逆を向いている。といった解釈ができます。分母は1~-1へ正規化するための計算。
実装もシンプル
nx = x / (np.sqrt(np.sum(x**2)) + eps) ny = y / (np.sqrt(np.sum(y**2)) + eps) return np.dot(nx, ny) # 内積
epsはゼロ除算回避のための微小な値です。この計算をすることで、youとiが似ていることが確認できます。ただこのコーパスがとても小さいのでまだあまり意味を成してはいません。
カウントベースの手法改善
大きく2つの改善を行い、PTBのデータセットで確認します。
相互情報量
単語の登場回数をカウントしただけの共起行列はあまり素性の良いものではないです。というのも単語の出現頻度を考慮していないからです。theなんてカウントされまくってしまうので、carにくっつくことでcarはdriveよりもtheの方と仲良しと判断されてしまいます。
この改善の手法が相互情報量(PMI)と呼ばれる手法です。定義式は以下(P(x)はxが起こる確率、P(x,y)はx,yが同時に起こる確率)
$$ PMI(x,y) = \log_2\frac{P(x, y)}{P(x)P(y)} $$
なぜ底が2の対数を使うのかはよくわかりませんが、出現確率で割り算している点で出現頻度が高いものの値を下げています。10000単語のうちtheが1000回、carが20回、driveが10回、the-carが10回、car-driveが5回出てきたとして計算すると、回数だけ見るとtheとcarが近いですが、この計算をするとtheの1000回登場によりthe-carの値は大きく減り、carとdriveの関係の方が強い結果になります。
ちなみにlog0が-∞なので、通常は0でクリッピングして正の値だけで計算するようです。
次元削減
この行列は語彙数が多くなればなるほど巨大な行列になってしまいます。ただ情報量を見るとゼロが多くスッカスカな行列です。こういった疎な行列から重要な情報だけを残して次元を落とす代表的な方法として特異値分解(SVD)があります。だいぶ難しい話になるので概念だけの理解にとどめます。というかついていけない。SVDとは、
疎な行列を意味を保ったまま次元を落とした密な行列に変換する方法
ただし計算量が莫大
という特徴だけ理解しときます。手を抜く工夫として、Truncated SVDってのもあって、値が小さいものは切り捨てながら計算するもの。とこちらもざっくりとした理解にとどめます。
PTBデータセット
Penn Treebankという有名なコーパスだそうです。前処理として、レアな単語にはunknownを意味する具体的な数にはNなど置き換えが行われています。こいつを使ってカウントベース+PMI+SVDでベクトル化するサンプルが書かれています。この処理により単語の関連性(コサイン類似度)のランクを見ていくと、例えばyouであればweやiなどの人称代名詞が上位に来る。という結果が得られています。
コメント