GloVe: Global Vectors for Word Representation 読んだ

論文について

タイトル

GloVe: Global Vectors for Word Representation

著者

Jeffrey Pennington, Richard Socher, Christopher D. Manning

所属

Stanford NLP

3行まとめ

“global matrix factorization methods”と”local context window methods”のいいとこ取りをしていい感じのword embeddingを獲得する手法を提案するよ。GloVeは単語-単語のスパースな共起行列全体や超巨大なコーパス上の個々のlocal context windowを使うことなく、共起行列の非ゼロな要素だけを使って効率的に学習できるよ。

補足:前者の”global matrix factorization”はいわゆるカウントベースの手法で、単語-単語の共起行列をmatrxi factorizeするモデルで、後者の”local contet windows methods”というのは対象語(target)を周辺語(local context)から予測するというモデル(e.g. CBOWやその他言語モデル)である。

関連研究

  1. global matrix factorization methods

    • このような手法は、統計情報を効率的に活用しているが、アナロジータスクに弱い。
    • LSA、HAL
    • 最も出現回数の高い語は不均衡な影響を与える。
    • COALS [Rohde et al., 2006]
    • ↑の問題に対応するためにエントロピーあるいは相関に基づくnormalizationにより共起行列の変換を行っている。
    • positive pointwise mutual information (PPMI)に基づく変換
    • Hellinger PCA (HPCA) [Lebret and Collobert, 2014]
    • 平方根型の変換
  2. local context window methods

    • Skip-gram、CBOW
    • このような手法は、アナロジータスクに強いが、統計情報は(コーパス全体の共起行列ではなくlocalなcontext windowを使っているため)活用できていない。

手法

まず、単語-単語の共起のカウントを格納した行列(共起行列)をXXと表す。このとき、

XijX_{ij}

とは、文脈語iiの文脈で単語jjの出現回数を表す。さらに、

Xi=kXikX_i = \sum_{k} X_{ik}

iiが文脈語として出現した合計回数になる。また、

Pij=P(ji)=XijXiP_{ij} = P(j | i) = \frac{X_{ij}}{X_i}

は単語jjが文脈語iiの文脈で出現する確率とする。

table1

熱力学を考えたときにi=icei=\text{ice}j=steamj=\text{steam}で考えてみる。

k=solidk=\text{solid}のとき、solidはsteamではなく、iceに関連があるためPikPjk\frac{P_{ik}}{P_{jk}}の値は大きくなると考えられる。
逆にk=gask=\text{gas}のとき、gasはiceではなく、steamに関連があるためPikPjk\frac{P_{ik}}{P_{jk}}の値は小さくなると考えられる。
k=water or fashionk = \text{water or fashion}のときは、kkはiceとsteamの両方に関係しているか、どちらにも関係していないので、PikPjk\frac{P_{ik}}{P_{jk}}の値は1に近づくはず。

上記の議論は、単語ベクトル学習のための適切な出発点は、確率それ自体よりもむしろ共起確率の比率にあるべきであることを示唆している。これを定式化すると、

F(wi,wj,w~k)=PikPjk       (1)F \left( w _ { i } , w _ { j } , \tilde { w } _ { k } \right) = \frac { P _ { i k } } { P _ { j k } } ~ ~ ~ ~ ~ ~ ~ (1)

ここで、wRdw \in \mathbb{R}^dは単語ベクトル、w~Rd\tilde{w} \in \mathbb{R}^dは文脈単語ベクトルである。
最初に、単語ベクトル空間においてPikPjk\frac{P_{ik}}{P_{jk}}を表す情報に変換するFFを考える。ベクトル空間は線形構造なので、ベクトルの差を使って

F(wiwj,w~k)=PikPjk.       (2)F \left( w _ { i } - w _ { j } , \tilde { w } _ { k } \right) = \frac { P _ { i k } } { P _ { j k } }. ~ ~ ~ ~ ~ ~ ~ (2)

左辺の引数はベクトル、右辺はスカラである。 FFは、例えばニューラルネットワークによってパラメータ化された複雑な関数であると見なすことができるが、そうしてしまうと前提にある線形構造をobfuscateしてしまうので、FFの引数同士のdot productとしてしてあげると、

F((wiwj)Tw~k)=PikPjk.       (3)F \left( \left(w _ { i } - w _ { j }\right)^{\mathrm{T}}\tilde { w } _ { k } \right) = \frac { P _ { i k } } { P _ { j k } }. ~ ~ ~ ~ ~ ~ ~ (3)

次に、単語-単語の共起行列の場合、単語と文脈語の区別は任意であり、2つの役割を自由に交換できます。つまり、ww~w \leftrightarrow \tilde { w }だけでなく、XXTX \leftrightarrow X ^ { T }も交換可能でないといけないが、(3)(3)は満たしていない。

まずはじめに、FFについて(R,+)( \mathbb { R } , + )(R>0,×)\left( \mathbb { R } _ { > 0 } , \times \right)の間で準同型(homomorphism)を仮定すると、

F((wiwj)Tw~k)=F(wiTw~k)F(wjTw~k).       (4)F \left( \left( w _ { i } - w _ { j } \right) ^ { T } \tilde { w } _ { k } \right) = \frac { F \left( w _ { i } ^ { T } \tilde { w } _ { k } \right) } { F \left( w _ { j } ^ { T } \tilde { w } _ { k } \right) }. ~ ~ ~ ~ ~ ~ ~ (4)

補足:FFについて加法群(R,+)(\mathbb{R, +})から乗法群(R>0,×)\left( \mathbb { R } _ { > 0 } , \times \right)への準同型であるとは、

F(a+b)=F(a)F(b)F(a+b) = F(a)F(b)

を満たすことを言う。

(3)(4)(3)(4)より、

F(wiTw~k)=Pik=XikXi.       (5)F \left( w _ { i } ^ { T } \tilde { w } _ { k } \right) = P _ { i k } = \frac { X _ { i k } } { X _ { i } }. ~ ~ ~ ~ ~ ~ ~ (5)

F=expF = \expとおくと、

wiTw~k=log(Pik)=log(Xik)log(Xi).       (6)w _ { i } ^ { T } \tilde { w } _ { k } = \log \left( P _ { i k } \right) = \log \left( X _ { i k } \right) - \log \left( X _ { i } \right) . ~ ~ ~ ~ ~ ~ ~ (6)

(6)(6)のlog(Xi)\log \left( X _ { i } \right)がなければ、交換対称性があるのに。。。。

補足:ww~w \leftrightarrow \tilde { w }XXTX \leftrightarrow X ^ { T }としたときに(6)は

w~iTwk=log(XikT)log(XiT)       ()\tilde{w} _ { i } ^ { T } w _ { k } = \log \left( X ^ {\mathrm{T}} _ { i k } \right) - \log \left( X ^ {\mathrm{T}} _ { i } \right) ~ ~ ~ ~ ~ ~ ~ (\ast)

となる。一方iki\leftrightarrow kとしたときに、

wkw~iT=log(Xki)log(Xk) w _ { k } \tilde{w} _ { i } ^ { T } = \log \left( X _ { ki } \right) - \log \left( X _ { k } \right) w~iTwk=log(XikT)log(Xk)       ()\Leftrightarrow \tilde{w} _ { i } ^ { T } w _ { k } = \log \left( X ^ {\mathrm{T}} _ { i k } \right) - \log \left( X _ { k } \right) ~ ~ ~ ~ ~ ~ ~ (\ast \ast)

となり、()(\ast)()(\ast \ast)を比較したときに矛盾する。(log(XiT)log(Xk)\because \log \left(X_i^\mathrm{T} \right) \neq \log \left(X_k \right))

そこで、(6)(6)について、この項はkkとは関係がないので(wiw_iに対する)バイアスbib_iで置き換えることを考え、さらに(w~k\tilde{w}_kに対する)バイアスb~k\tilde{b}_kを付け加えることで、対称性を持った

wiTw~k+bi+b~k=log(Xik)w _ { i } ^ { T } \tilde { w } _ { k } + b _ { i } + \tilde { b } _ { k } = \log \left( X _ { i k } \right)

を得る。これの2乗誤差に重み関数ffで重み付けを行ったものを損失関数とする。

J=i,j=1Vf(Xij)(wiTw~j+bi+b~jlogXij)2J = \sum _ { i , j = 1 } ^ { V } f \left( X _ { i j } \right) \left( w _ { i } ^ { T } \tilde { w } _ { j } + b _ { i } + \tilde { b } _ { j } - \log X _ { i j } \right) ^ { 2 }

ここでVVは語彙数、重み関数ff

f(x)={(x/xmax)α if x<xmax1 otherwise f ( x ) = \left\{ \begin{array} { c c } { \left( x / x _ { \max } \right) ^ { \alpha } } & { \text { if } x < x _ { \max } } \\ { 1 } & { \text { otherwise } } \end{array} \right. f

xmaxx _ { \max }100100α\alpha3/43/4のときに良い結果が得られた。negative samplingのときと同じ値だ。。。 f(0)=0f(0) = 0なのでそもそもXXの非ゼロな要素だけ見れば良いので嬉しい。(語彙やコーパスサイズにもよるが、XXの75–95%はゼロな要素らしい。。。)

実験

タスク

  • Word analogies

    • aa is to bb as cc is to ___ ?
    • wbwa+wcw _ { b } - w _ { a } + w _ { c }にコサイン類似が近い語を探してくるタスク。
    • 意味的なタスク
      e.g. Athens is to Greece as Berlin is to __ ?
    • 文法的なタスク
      e.g. dance is to dancing as fly is to __ ?
  • Word similarity

    • データセット
    • WordSim-353
    • MC
    • RG
    • SCWS
    • RW
  • Named entity recognition (NER)

    • 単語ベクトルを入力とし、CRF(Conditional Random Fields)で学習する。
    • 訓練データセット
    • CoNLL-2003 training data
    • テストデータセット
    • ConLL-03 testing data
    • ACE Phase2 (2001-02)
    • MUC7 Formal Run test set

学習につかったコーパス

  1. 2010 Wikipedia dump with 1 billion tokens
  2. 2014 Wikipedia dump with 1.6 billion tokens
  3. Gigaword 5 which has 4.3 billion tokens
  4. 2と3の足し合わせ 6 billion tokens
  5. 42 billion tokens of web data, from Common Crawl

Stanford tokenizerでトークナイズ、 最頻出の40万語を語彙として使用。
※5.については200万語を語彙として使用。

比較手法

  • Skip-gram
  • CBOW
  • SVD (最頻出の1万語のみで共起行列XtruncX_{\text{trunc}}を作る)

    • SVD-S(Xtrunc\sqrt{X_{\text{trunc}}})
    • SVD-L(log(1+Xtrunc)\log \left(1+X_{\text{trunc}}\right))

結果

  • Word analogies
    概して、GloVeが良かった。特に、42Bものバカでかいコーパスでも簡単に学習することができた。SVD-Lはコーパスサイズが大きくなると逆に精度が悪くなったが、GloVeは良くなった。(GloVeは重み関数ffが効いている?)
table2
  • Word similarity
    概してGloVeが良い。SVD-Lとの比較は先程と同じだが、より大きいコーパスで学習したCBOWよりもGloVeの方が良かった。
table3
  • NER
    概してGloVeが良い。SVD-Lとの比較は先程と同じだが、より大きいコーパスで学習したCBOWよりもGloVeの方が良かった。
table4

考察

Embeddingの次元と文脈の長さについて

  • (a) だいたい200次元くらいでaccuracyが頭打ちになる。
  • (b)(c) Syntacticについては語順に強く依存するためAsynmmetric context(contextが注目語の左のみ)の方が良く、window sizeを大きくすると悪化する。Semanticについてはlocalではなく、window sizeが大きければ大きいほどよい結果となった。
figure2

コーパスサイズについて

Syntacticについてはコーパスのサイズが大きければ大きいほど良い傾向にあるが、Semnticについては必ずしもその限りではなく、コーパスの性質によって変わってくる。例えばアナロジータスクでは地名に関する設問があったが、Wiki2014の方ではそれをカバーできてきたと考えれる。

figure3

学習時間について

40万語彙、60億語のコーパスでWindow size = 10で共起行列XXをつくるのに85分かかったらしい。学習には32コアすべてを使って1イテレーション14分しかかからなかった。(Figure 4参照)

Skip-gram、CBOWとの比較

単純な比較はパラメータが多すぎるのでむずかしい。学習時間で比較したい。GloVeの学習時間がiterationで表されるのに対し、Skip-gram、CBOWはepochである。しかし、残念ながら現状の(Skip-gram、CBOWの)コードがsingle epochしか学習しないように実装されているため、ひとまずnegative samplingの個数が訓練に使いデータ量に関連があるので、これと比較する。CBOWに至ってはnegative samplingの個数を増やすとかえって精度が悪くなった。これは、negative samplingが対象となる確率分布をうまく近似できていないからなのかも知れない。

figure4

NNablaでの実装

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF

x_central = nn.Variable((batch_size, ))
x_context = nn.Variable((batch_size, ))

with nn.parameter_scope('central_embedding'):
    central_embedding = PF.embed(x_central, vocab_size, embedding_size)
with nn.parameter_scope('context_embedding'):
    context_embedding = PF.embed(x_context, vocab_size, embedding_size)

with nn.parameter_scope('central_bias'):
    central_bias = PF.embed(x_central, vocab_size, 1)
with nn.parameter_scope('context_bias'):
    context_bias = PF.embed(x_context, vocab_size, 1)

dot_product = F.reshape(
    F.batch_matmul(
        F.reshape(central_embedding, shape=(batch_size, 1, embedding_size)),
        F.reshape(context_embedding, shape=(batch_size, embedding_size, 1))
    ),
    shape=(batch_size, 1)
)

prediction = dot_product + central_bias + context_bias

t = nn.Variable((batch_size, 1))
zero = F.constant(0, shape=(batch_size, 1))
one = F.constant(1, shape=(batch_size, 1))
weight = F.clip_by_value(t / 100, zero, one) ** 0.75
loss = F.sum(weight * ((prediction - F.log(t+1)) ** 2))

Written by@Minato Sato
Senior Software Engineer - Embedded AI

GitHubTwitterFacebookLinkedIn

© 2023 Minato Sato. All Rights Reserved