LoginSignup
6
6

More than 3 years have passed since last update.

『ゼロから作るDeep Learning❷』2.4 カウントベースの手法の改善 資料

Last updated at Posted at 2019-05-14
1 / 21

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

本節の概要

  • 分散表現を改善する
    • 正の相互情報量(PPMI)を使う(難易度:中)
    • 次元削減のために特異値分解(SVD)を使う(難易度:高)
  • もっと大きなデータに適用してみる(計算時間:大)

分散表現の改善

  • そもそも、なんで共起行列を作ったか?→単語の分散表現のため
  • その意味では、単語の共起行列は「単語の(ある種の)分散表現」といえる
  • とはいえ、ただの共起行列では微妙なので、改良する

2.4.1 相互情報量

  • 微妙な点その1:高頻度な単語を適切に扱えない
  • 「the」「car」「drive」では、「car」と「drive」の関連性が高そうだが、「the」と「car」の関連の方が強く出る
    • そもそも「the」はたくさん出現するため共起することが多い
    • でも、出現回数が小さいのに共起する方が重要では?
  • それを考慮する指標として、「相互情報量」を使う

自己相互情報量

  • 相互情報量(Pointwise Mutual Information)
    • 「自己相互情報量」「点相互情報量」という訳語も使われる。
    • Wikipediaの https://ja.wikipedia.org/wiki/相互情報量 は意味が微妙に違うのに注意。
      • こちらの方は情報理論の方で出てくる用語っぽい。
      • 自然言語処理では自己相互情報量のことを「相互情報量」と呼ぶ(らしい)ので要注意。PMIと呼べばよい?

PMIの定義

\begin{align}
PMI(x,y) &= log_2 \frac{P(x\cdot y)}{P(x)P(y)} \\
 &= log_2 \frac{\frac{C(x,y)}{N}}{\frac{C(x)}{N}\cdot\frac{C(y)}{N}} = log_2 \frac{C(x,y)\cdot N}{C(x)\cdot C(y)}
\end{align}

$P(x)$ : xが起こる確率
$P(y)$ : yが起こる確率
$P(x\cdot y)$ : xとyが同時に起こる確率
$C(x)$ : xの出現回数
$N$ : 全語彙数

$ y = log_2(x)$ のグラフ

PMI.png


PPMIの定義

  • xとyが共起しない場合、$P(x,y)=0$ なので、 $log_2(0)=-\infty$となり、こまる
  • そのため、正の相互情報量(Positive PMI)を使う
\begin{align}
PPMI(x,y) &= max(0, PMI(x,y)) \\
 &= max(0, log_2 \frac{C(x,y)\cdot N}{C(x)\cdot C(y)})
\end{align}

$y=max(0, log_2(x))$ のグラフ

PPMI.png


式2.6のバグ(?)

total < 100の場合、Verbose=trueにすると落ちるのでは??

import sys
sys.path.append('..')
import numpy as np
from common.util import preprocess, create_co_matrix, cos_similarity, ppmi

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
W = ppmi(C, True)

実行結果:

$ python3 ppmi2.py 
Traceback (most recent call last):
  File "ppmi2.py", line 11, in <module>
    W = ppmi(C, True)
  File "../common/util.py", line 144, in ppmi
    if cnt % (total//100) == 0:
ZeroDivisionError: integer division or modulo by zero

普通に実行

ppmi()の定義

def ppmi(C, verbose=False, eps = 1e-8):
    '''PPMI(正の相互情報量)の作成

    :param C: 共起行列
    :param verbose: 進行状況を出力するかどうか
    :return:
    '''
    M = np.zeros_like(C, dtype=np.float32)
    N = np.sum(C)
    S = np.sum(C, axis=0)
    total = C.shape[0] * C.shape[1]
    cnt = 0

    for i in range(C.shape[0]):
        for j in range(C.shape[1]):
            pmi = np.log2(C[i, j] * N / (S[j]*S[i]) + eps)
            M[i, j] = max(0, pmi)

            if verbose:
                cnt += 1
                if cnt % (total//100) == 0:
                    print('%.1f%% done' % (100*cnt/total))
    return M

ポイント: 小さい数 $eps$ を加えることで、$log_2(0)$になるのを防いでいる


普通に実行

# coding: utf-8
import sys
sys.path.append('..')
import numpy as np
from common.util import preprocess, create_co_matrix, cos_similarity, ppmi

text = 'You say goodbye and I say hello.'
corpus, word_to_id, id_to_word = preprocess(text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
W = ppmi(C)

np.set_printoptions(precision=3)  # 有効桁3桁で表示
print('covariance matrix')
print(C)
print('-'*50)
print('PPMI')
print(W)

実行結果:

$ python3 ppmi.py 
covariance matrix
[[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]]
--------------------------------------------------
PPMI
[[0.    1.807 0.    0.    0.    0.    0.   ]
 [1.807 0.    0.807 0.    0.807 0.807 0.   ]
 [0.    0.807 0.    1.807 0.    0.    0.   ]
 [0.    0.    1.807 0.    1.807 0.    0.   ]
 [0.    0.807 0.    1.807 0.    0.    0.   ]
 [0.    0.807 0.    0.    0.    0.    2.807]
 [0.    0.    0.    0.    0.    2.807 0.   ]]

2.4.3 次元削減

現状の問題点(微妙な点その2)

  • この方法だと、語彙数が増えると次元も増える
  • 10万語だと10万次元?!
    • そんなに次元があってもうれしくない(計算資源の問題)
    • 0が多いのは無駄(疎な行列,sparse)
    • 本質的に近いものも別々に表現されてしまう
  • そこで次元削減

次元削減(dimension reduction)

  • 次元削減: 次元を減らすこと(そのまま)
  • 本書では特異値分解を使うが、他にも主成分分析とかいろいろある(らしい)
  • 「本質的な情報はあまり減らさないようにする」ことが重要

特異値分解

(この辺は行列用語というか数学用語が頻出してつらい…)

行列の変形。M x Nの行列Cは以下のように分解できる。

$$C = U\Sigma V^T$$

$U$はM x M、$V$はN x Nの正方行列(=行と列のサイズが同じ行列)。

$U$ と $V$ は「直交行列」になる。直交行列とは、転置行列 $A^T$と逆行列$A^{-1}$が等しくなる行列(転置行列は行と列をひっくり返した行列)。

\begin{align}
U^T \cdot U &= U \cdot U^T = I \\
V^T \cdot V &= V \cdot V^T = I
\end{align}

($I$ は単位行列)

直感的には、直交行列は回転とか反転とかのような、距離を変えない変換と考えると良さそう。「本質的な情報」を分かりやすく$\Sigma$で表せられるようにする。

$\Sigma$ は対角行列成分のみ、大きい順に並んでいて、それ以外は0。むしろ、そういう並びに $\Sigma$ が並ぶように、$U$と$V$を決めるイメージ、で良さそう。与えられた$C$に対して$\Sigma$は一意に決まるが、$U$と$V$は一意ではない(らしい)。


特異値分解の Σ の例

\left(
\begin{matrix}
\sigma_1 & 0 & 0 \\
0 & \sigma_2 & 0 \\
0 & 0 & \sigma_3 \\
0 & 0 & 0 \\
0 & 0 & 0 
\end{matrix}
\right)

ちなみに、特異値をそれぞれ2乗した値の合計(=特異値の2乗和)は、元の行列の全要素の2乗和に等しくなる(らしい)。これをフロベニウスノルムと呼ぶ(Wikipedia:行列ノルム#フロベニウスノルム)。


特異値分解を使った次元削減

ここまでの特異値分解は値を変えない(=等価な)変換。ここからは、(等価ではない)近似した行列 $C' ( \fallingdotseq C)$ を求める。

$\Sigma$ は左上から右下へ大きい順に数が並んでいるため、下の方から削る(=0にする)と次元が削減できる。べんり! というか、この便利さのために特異値分解を使ってるという理解。

直感的には、値が小さいやつを0にするので影響が小さい(らしい)。


2.4.3 SVDによる次元削減

Pythonで実行する。numpyのlinalg.svdを使う。

W = ppmi(C)

# SVD
U, S, V = np.linalg.svd(W)

高速化のため、Truncated SVDという手法もある


2.4.4 PTBデータセット

本格的なデータとして、PTBを扱ってみる。

  • Penn Treebank (PTB) https://web.archive.org/web/20131109202842/http://www.cis.upenn.edu/~treebank/
  • Treebank: コーパスの一種で、構文的・意味的なアノテーションが付与されているもの。Seed bank(種子銀行)やBlood bankのアナロジーだそう(Wikipediaより)。
  • コーパス: 言語学・自然言語処理などで使われる、大規模かつ構造化されたテキストデータセット。

ここのPTBコーパスはタグや構文解析木のデータがすっぱり落とされた、ただのテキストになっている。Treeとは一体…。

オリジナルのPTBはNLTK Corporaよりダウンロードできるぽい(元サイトが消滅しているのでほんとにオリジナルかどうかは不明)。


Penn Treebank(NLTK Corporaより)

rawデータ

Rudolph Agnew, 55 years old and former chairman of Consolidated Gold Fields PLC, was named a nonexecutive director of this British industrial conglomerate. 

parsedデータ

wsj_0002.prd
( (S (NP-SBJ-1 (NP Rudolph Agnew)
               ,
               (UCP (ADJP (NP 55 years)
              old)
            and
            (NP (NP former chairman)
            (PP of
                (NP Consolidated Gold Fields PLC))))
               ,)
     (VP was
         (VP named
             (S (NP-SBJ *-1)
        (NP-PRD (NP a nonexecutive director)
            (PP of
                (NP this British industrial conglomerate))))))
     .))

taggedデータ

wsj_0002.pos
[ Rudolph/NNP Agnew/NNP ]
,/, 
[ 55/CD years/NNS ]
old/JJ and/CC 
[ former/JJ chairman/NN ]
of/IN 
[ Consolidated/NNP Gold/NNP Fields/NNP PLC/NNP ]
,/, was/VBD named/VBN 
[ a/DT nonexecutive/JJ director/NN ]
of/IN 
[ this/DT British/JJ industrial/JJ conglomerate/NN ]
./. 

2.4.5 PTBデータセットでの評価

SVDの高速化のため、sklearnのrandomized_svd()を使う。

print('calculating SVD ...')
try:
    # truncated SVD (fast!)
    from sklearn.utils.extmath import randomized_svd
    U, S, V = randomized_svd(W, n_components=wordvec_size, n_iter=5,
                             random_state=None)
except ImportError:
    # SVD (slow)
    U, S, V = np.linalg.svd(W)
$ cd ch02
$ python3 count_method_big.py
counting  co-occurrence ...
calculating PPMI ...
1.0% done
2.0% done
3.0% done

92.0% done
93.0% done
94.0% done
95.0% done
96.0% done
97.0% done
98.0% done
99.0% done
100.0% done
calculating SVD ...

[query] you
 i: 0.700317919254303
 we: 0.6367185115814209
 anybody: 0.565764307975769
 do: 0.563567042350769
 'll: 0.5127798318862915

[query] year
 month: 0.6961644291877747
 quarter: 0.6884941458702087
 earlier: 0.6663320660591125
 last: 0.6281364560127258
 next: 0.6175755858421326

[query] car
 luxury: 0.6728832125663757
 auto: 0.6452109813690186
 vehicle: 0.6097723245620728
 cars: 0.6032834053039551
 corsica: 0.5698372721672058

[query] toyota
 motor: 0.7585658431053162
 nissan: 0.7148030996322632
 motors: 0.6926157474517822
 lexus: 0.6583304405212402
 honda: 0.6350275278091431

分布表現(distributional representation)と分散表現(distributed representation)

使い分ける場合があるぽい


参考文献

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6