Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

単語分散表現

単語分散表現とは、単語の意味を低次元の実数値ベクトルで表現することです。

機械学習・深層学習モデルは、ベクトル(数値の配列)を入力として受け取ります。テキストを扱う際、最初に決めなければならないのは、文字列を機械学習モデルに入力する前に、数値に変換する(あるいはテキストを「ベクトル化」する)ための戦略です。

単語の持つ性質や意味をよく反映するベクトル表現を獲得することは、機械学習・深層学習を自然言語処理で活用するために重要なプロセスです。

  • 類似性: ある概念を表現する際に、ほかの概念との共通点や類似性と紐づけながら、ベクトル空間上に表現します。

  • 単語類推: 分散表現では異なる概念を表現するベクトル同士での計算が可能です。

単語分散表現による単語の意味の類似性を表現することができます: 意味的に近い単語(例:cat と kitten)は近い位置に配置されます
  • 単語分散表現による単語の意味の類似性を表現することができます: 意味的に近い単語(例:cat と kitten)は近い位置に配置されます

  • 単語分散表現による単語類推を行うことができます: man と woman、king と queen のように意味的に対応関係を持つ単語はベクトル差が似た方向を持つため、アナロジー推論(king - man + woman ≈ queen)が可能になります

単語分散表現による多次元ベクトル空間において意味や関係を捉えます
  • 単語分散表現による多次元ベクトル空間において意味や関係を捉えます

単語ベクトルへの変換には様々なアプローチが存在します。最初に、(1)統計情報から単語を表現する手法(カウントベースの手法)について説明します。この方法は、言語のモデル化を理解することに役立つと考えられます。そして、(2)ニューラルネットワークを用いた手法(具体的には、word2vecと呼ばれる手法)を扱います。

  1. カウントベースの手法

    • 統計情報から単語を表現する手法

  2. 推論ベースの手法

    • ニューラルネットワークを用いた手法

1シソーラスによる手法

「単語の意味」を表すためには、人の手によって単語の意味を定義することが考えられます。

シソーラス(thesaurus)と呼ばれるタイプの辞書は、単語間の関係を異表記・類義語・上位下位といった関係性を用いて、単語間の関連を定義できます。

car=auto automobile machine motorcarcar = auto \ automobile \ machine \ motorcar

この「単語ネットワーク」を利用することで、コンピュータに単語間の関連性を伝えることができます。しかし、この手法には大きな欠点が存在します。

  • 人の作業コストが高い

  • 時代の変化に対応するのが困難

    • 言語は常に進化しており、新しい単語や意味が生まれては消えていくので、シソーラスを最新の状態に保つのは難しいです。

  • 単語の些細なニュアンスを表現できない

    • 単語が持つ複数の意味を区別することは難しい

    • 単語間の関連性はシソーラスでは静的なものであり、動的な文脈や知識の流れを反映しきれないことがあります

2カウントベースの手法

コーパスには、自然言語に対する人の「知識」(文章の書き方、単語の選び方、単語の意味)がふんだんに含まれています。カウントベースの手法の目標は、そのような人の知識を、それが詰まったコーパスから自動的に抽出することにあります。

2.1コーパスの前処理

コーパスに対して、テキストデータを単語に分割し、その分割した単語をID化することが必要です。

単語のID化とは、テキストデータを機械学習モデルなどで処理する際に、単語を一意の整数値(ID)に変換するプロセスを指します。これは、テキストデータをベクトルや行列の形でモデルに入力するための前処理として行われます。

例として、簡単なテキストを用意します。

you say goodbye and i say hello.
you say goodbye and i say hello .
['you', 'say', 'goodbye', 'and', 'i', 'say', 'hello', '.']

これで、元の文章を単語リストとして利用できるようになりました。これに基づいて、分割した単語と、単語ごとに通し番号を割り振ったIDを2つのディクショナリに格納します。

{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
hello
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
5

最後に、単語リストから単語IDリストに変換します。

[0 1 2 3 4 1 5 6]

以上の処理をpreprocess()という関数として、まとめて実装することにします。

{0: 'you', 1: 'say', 2: 'goodbye', 3: 'and', 4: 'i', 5: 'hello', 6: '.'}
{'you': 0, 'say': 1, 'goodbye': 2, 'and': 3, 'i': 4, 'hello': 5, '.': 6}
[0, 1, 2, 3, 4, 1, 5, 6]

2.2分布仮説

分布仮説(Distributional Hypothesis)は、言語学や自然言語処理の分野で重要な考え方で、単語の意味は、周囲の単語(コンテキスト)によって形成されるというものです。

  • 単語は、その単語が出現する文脈の集合によって意味が形成されるとされます。同じ文脈で出現する単語は、意味が似ていると考えられます。

  • 単語Aと単語Bが多くの共通の文脈で使用される場合、これらの単語は意味的に関連があると見なされます。

この仮説は、単語の意味を捉えるためのモデルを作成する際に基本的な原則となっています。

2.3共起行列

分布仮説に基づいた単語ベクトル化の方法を考える際、一番素直な方法は、周囲の単語を"カウント"することです。つまり、ある単語に着目した場合、その周囲にどのような単語がどれだけ現れるのかをカウントし、それを集計するのです。

ここでは、"You say goodbye and I say hello."という文章について、ウィンドウサイズを1とする場合、そのコンテキストに含まれる単語の頻度をカウントしてみます。

各単語について、そのコンテキストに含まれる単語の頻度

各単語について、そのコンテキストに含まれる単語の頻度

  • 「You」の周辺単語は「say」のみであり、「say」にのみコンテキストの目印として共起した回数の1をカウントします

  • 「say」については文字列中に2回現れていることに注意すると、[1,0,1,0,1,1,0][1, 0, 1, 0, 1, 1, 0]とベクトル表記できます

全ての単語に対して、共起する単語をまとめたものを共起行列と呼びます。

単語の種類数: 7
総単語数: 8
[[0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]]
(7, 7)

コーパスの7語目の単語「hello」に注目してみます。

5
hello
5
7
1
say
[[0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 1 0 0 0 0 0]
 [0 0 0 0 0 0 0]]
6
.
[[0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0]
 [0 1 0 0 0 0 1]
 [0 0 0 0 0 0 0]]
[0 1 0 0 0 0 1]

処理を共起行列を作成する関数として実装します。

2.4ベクトル間の類似度

2.4.1コサイン類似度

共起行列によって単語をベクトルで表すことができました。単語の意味を「計算」する手法として、ベクトル間の類似度を計測する方法について見ていきます。

様々な方法がありますが、単語のベクトル表現の類似度に関して、コサイン類似度がよく用いられます。

コサイン類似度とは、2つのベクトルをx=(x1,x2,⋯ ,xn),y=(y1,y2,⋯ ,yn)\mathbf{x} = (x_1, x_2, \cdots, x_n), \mathbf{y} = (y_1, y_2, \cdots, y_n)として、次の式で定義されます。

similarity(x,y)=x⋅y∥x∥∥y∥=x1y1+x2y2+⋯+xnynx12+x22+⋯+xn2y12+y22+⋯+yn2=∑nxnyn∑nxn2∑nyn2\begin{align} \mathrm{similarity}(\mathbf{x}, \mathbf{y}) &= \frac{ \mathbf{x} \cdot \mathbf{y} }{ \|\mathbf{x}\| \|\mathbf{y}\| } \\ &= \frac{ x_1 y_1 + x_2 y_2 + \cdots + x_n y_n }{ \sqrt{x_1^2 + x_2^2 + \cdots + x_n^2} \sqrt{y_1^2 + y_2^2 + \cdots + y_n^2} } \\ &= \frac{ \sum_{n} x_n y_n }{ \sqrt{\sum_{n} x_n^2} \sqrt{\sum_{n} y_n^2} } \end{align}
  • 分子はベクトルの内積

  • 分母は各ベクトルの「ノルム」(ベクトルの大きさ)の積です。

実装した関数を使って、ベクトルの値とコサイン類似度の値との関係を見ましょう。

<Figure size 640x480 with 1 Axes>
Notebook Cell
<Figure size 1200x800 with 6 Axes>

2.4.2単語間の類似度

前項で作成した各単語ベクトルを用いて、2つの単語の類似度を測りましょう。

比較したい2つの単語を指定して、単語ベクトルからコサイン類似度を計算します。

[0 1 0 0 0 0 0]
[0 1 0 1 0 0 0]
0.7071067691154799

2.4.3類似度のランキングを表示

単語の「意味」を分析する際には、ある単語に対して類似した単語を探すことがよく挙げられます。ここでは、指定した単語との類似度が高い単語を調べる関数を実装します。

指定した単語のID:0
指定した単語のベクトル: [0 1 0 0 0 0 0]
類似度の結果:
you:1.0
say:0.0
goodbye:0.70711
and:0.0
i:0.70711
hello:0.70711
.:0.0
Notebook Cell
[ 0 20 10 40 30]
[0 2 1 4 3]
[  0 -20 -10 -40 -30]
[3 4 1 2 0]
 goodbye: 0.7071067691154799
 i: 0.7071067691154799
 hello: 0.7071067691154799
 say: 0.0
 and: 0.0

[query] you
 goodbye: 0.7071067691154799
 i: 0.7071067691154799
 hello: 0.7071067691154799
 say: 0.0
 and: 0.0

3カウントベースの手法の改善

3.1相互情報量

ここまでは単語がテキストに出現する頻度そのままを扱いましたが、このやり方によって単語分散を計測する際、高頻度単語によるバイアスが生じる可能性があります。

例えば、日本語において、「の」は非常に一般的な助詞で、さまざまな文脈で使用されます。単語の共起行列を作成するとき、「の」は非常に頻繁に出現し、多くの異なる単語とペアを形成するため、単純な共起頻度はその単語の意味のある関連性を捉えるのには不十分です。

そのような問題を解決するために、相互情報量(Pointwise Mutual Information)と呼ばれる指標が使われます。

2つの単語xx,yyの出現確率をそれぞれP(x)P(x),P(y)P(y)とします。xxの出現確率は

P(x)=C(x)NP(x) = \frac{C(x)}{N}

で計算します。ここでC(x)C(x)は単語xxが(テキストではなく)コーパス(共起行列)にカウントされた回数、NNはコーパスの総単語数とします。

また単語xx,yyが共起(ウィンドウサイズ内で続けて出現)した回数をC(x,y)C(x,y)とします。

P(x,y)=C(x,y)NP(x, y) = \frac{C(x, y)}{N}

これを用いて、単語xx,yyの相互情報量(PMI)は次のように定義されます。

PMI(x,y)=log⁡2P(x,y)P(x)P(y)\mathrm{PMI}(x, y) = \log_2 \frac{ P(x, y) }{ P(x) P(y) }

またこの式は、次のように変形することで

PMI(x,y)=log⁡2(P(x,y)1P(x)1P(y))=log⁡2(C(x,y)NNC(x)NC(y))=log⁡2C(x,y)NC(x)C(y)\begin{align} \mathrm{PMI}(x, y) &= \log_2 \left( P(x, y) \frac{1}{P(x)} \frac{1}{P(y)} \right) \\ &= \log_2 \left( \frac{C(x, y)}{N} \frac{N}{C(x)} \frac{N}{C(y)} \right) \\ &= \log_2 \frac{ C(x, y) N }{ C(x) C(y) } \end{align}

出現回数と総単語数から直接計算できることが分かります。

ただし出現回数が0のときに不都合が生じるため、次の正の相互情報量(PPMI)を用います。

PPMI(x,y)=max⁡(0,PMI(x,y))\mathrm{PPMI}(x, y) = \max(0, \mathrm{PMI}(x, y))
[[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]]
Loading...
[[0.   1.81 0.   0.   0.   0.   0.  ]
 [1.81 0.   0.81 0.   0.81 0.81 0.  ]
 [0.   0.81 0.   1.81 0.   0.   0.  ]
 [0.   0.   1.81 0.   1.81 0.   0.  ]
 [0.   0.81 0.   1.81 0.   0.   0.  ]
 [0.   0.81 0.   0.   0.   0.   2.81]
 [0.   0.   0.   0.   0.   2.81 0.  ]]

3.2次元削減

次元削減は、ベクトルの次元を重要な情報を保持しながら削減する手法を指します。

3.2.1SVD

次元削減を行う方法はいくつかあります。ここでは特異値分解(Singular Value Decomposition:SVD)を使って次元削減を行います。

SVDは、任意の行列を3つの行列の積へ分解します。

A=UΣVTA = U\Sigma V^T

ここで、AAはn×dn \times dの行列、UUはn×nn \times n、VVはd×dd \times dの直交行列です。

  • UUは何らかの空間の基底を形成しています。ここは、UUという行列を「単語空間」として扱うことができます。

  • Σ\Sigmaは対角行列で、この対角成分には、「特異値」というものが大きい順で並んでいます。ここで、「特異値」は対応する軸の重要度とみなすことができます。次元削減は、この情報によって重要でない要素を削除することが考えられます。

SVDによって、疎なベクトルが密なベクトルへ変換されています。この密なベクトルから、先頭の二つの要素を取り出すことで、元のベクトルの「情報」をできるだけ保持しながら次元を削減することができます。

[0 1 0 0 0 0 0]
[0.        1.8073549 0.        0.        0.        0.        0.       ]
[ 0.0000000e+00 -3.4094876e-01 -3.8857806e-16  1.2051624e-01
  9.3232495e-01 -1.1102230e-16  3.2068993e-16]

4カウントベースの手法による単語分散の作成

[' 友人代表のスピーチ、独女はどうこなしている?', 'もうすぐジューン・ブライドと呼ばれる6月。独女の中には自分の式はまだなのに呼ばれてばかり……という「お祝い貧乏」状態の人も多いのではないだろうか?さらに出席回数を重ねていくと、こんなお願いごとをされることも少なくない。', '「お願いがあるんだけど……友人代表のスピーチ、やってくれないかな?」', 'さてそんなとき、独女はどう対応したらいいか?', '最近だとインターネット等で検索すれば友人代表スピーチ用の例文サイトがたくさん出てくるので、それらを参考にすれば、無難なものは誰でも作成できる。しかし由利さん(33歳)はネットを参考にして作成したものの「これで本当にいいのか不安でした。一人暮らしなので聞かせて感想をいってくれる人もいないし、かといって他の友人にわざわざ聞かせるのもどうかと思うし……」ということで活用したのが、なんとインターネットの悩み相談サイトに。そこに作成したスピーチ文を掲載し「これで大丈夫か添削してください」とメッセージを送ったというのである。']
<>:8: SyntaxWarning: invalid escape sequence '\+'
<>:8: SyntaxWarning: invalid escape sequence '\+'
/var/folders/0s/grknnvt93bs7w2rbrt894wkc0000gn/T/ipykernel_13865/2217006374.py:8: SyntaxWarning: invalid escape sequence '\+'
  sentence = re.sub("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\+[0-9]{4}","", sentence) # 注3)
['友人', '代表', 'の', 'スピーチ', '、', '独女', 'は', 'どう', 'こなし', 'て']
id_to_word[0]: 友人
id_to_word[1]: 代表
id_to_word[2]: の

word_to_id['女']: 3163
word_to_id['結婚']: 148
word_to_id['夫']: 1426
25547

[query] 女
 デブ: 0.7226879000663757
 頼りがい: 0.7069697380065918
 技: 0.7065211534500122
 結ば: 0.6929681897163391
 市民: 0.683417797088623

[query] 結婚
 縁組: 0.7332433462142944
 正子: 0.6813077926635742
 切迫: 0.6812717318534851
 久仁子: 0.6402035355567932
 後押し: 0.6352586150169373

[query] 彼女
 土地: 0.8677912950515747
 つかわ: 0.8450607657432556
 ソテー: 0.8422375917434692
 強がり: 0.8376142382621765
 まね: 0.8207266330718994

[query] 秋
 館: 0.514491081237793
 おわ: 0.5090659856796265
 切らさ: 0.4935395121574402
 食後: 0.4698116183280945
 交渉: 0.4658271074295044

4.1おまけ:PyTorchでの実装


[query] 女
 男: 0.7777467966079712
 男子: 0.5937835574150085
 独女: 0.5688649415969849
 男性: 0.5374248027801514
 女性: 0.5247462391853333

[query] 結婚
 離婚: 0.6396859288215637
 交際: 0.5854832530021667
 迎え: 0.5492004156112671
 入籍: 0.5393035411834717
 出産: 0.5391035676002502

[query] 彼女
 彼: 0.7317190170288086
 娘: 0.610599935054779
 夫: 0.6025635600090027
 友人: 0.6021445989608765
 母親: 0.5997404456138611

[query] 秋
 春: 0.7465042471885681
 夏: 0.7199810743331909
 冬: 0.6601986289024353
 フェス: 0.659843921661377
 季節: 0.6529552936553955