相互情報量のpython実装におけるコーパス単語数計算の問題
解決したいこと
「ゼロから作るDeep Learning 2 自然言語処理編」で勉強しております。
カウントベースによる自然言語処理の章のうち、相互情報量に関する紹介があり、そこで提示されたpythonのサンプルコードがありますが、コードの中で、コーパスの単語数を計算するところが理解できず、皆様の知恵をお借りできれば幸いです。
本で提示されたpythonサンプルコード
def ppmi(C, verbose=False, eps=1e-8):
# PPMI行列の受け皿
M = np.zeros_like(C, dtype=np.float32)
# PPMIに用いる値
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]):
# PPMIを計算
pmi = np.log2(C[i, j] * N / (S[j] * S[i]) + eps) # 式(2.3)
M[i, j] = max(0, pmi) # 式(2.6)
# 進行状況を表示
if verbose:
cnt += 1 # カウントアップ
if cnt % (total // 100 + 1) == 0:
print('%.1f%% done' % (100 * cnt / total))
return M
引数の内容は以下になります。
C:入力コーパス(文章)の共起行列
verbose:進行状況を出力するかどうかを決めるフラグ
eps:微小な値
これは、本書で提示された以下の相互情報量の公式に基づいた実装と認識しております。
PMI(x, y) = log2\frac{C(x,y)*N}{C(x)C(y)}
PMI(x, y): xとyの相互情報量
C(x, y): xとyの共起回数
C(x)/C(y): x/yの出現回数
N: コーパス中の単語数
理解できないところ
# PPMIに用いる値
N = np.sum(C) # コーパス中の単語数
S = np.sum(C, axis=0) # 各単語の出現回数
公式自体への認識は問題ありませんが、pythonサンプルコードで用いられた、N(コーパス中の単語数)と各単語の出現回数の計算が理解できませんでした。
本書で例として挙げている文章で言うと、以下通りになるではないかと思います。
text = "You say goodbye and I say Hello."
# 総単語数(N)は「7個」
# 各単語の出現回数は「1, 2, 1, 1, 1, 1」
# (順番は重複単語を除き文章先頭からです)
しかし、サンプルコードを適用すると以下になります。
text = "You say goodbye and I say Hello."
# 「C」はtextの共起行列
C = 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]])
print(C)
# コーパス中の単語数
N = np.sum(C)
print(N)
# 各単語の出現回数
S = np.sum(C, axis=0)
print(S)
[[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]]
14
[1 4 2 2 2 2 1]
このように、コーパスの合計単語数は7なのに、出力が14になっています。また、各単語の出現回数は「1 2 1 1 1 1」にもかかわらず、出力が[1 4 2 2 2 2 1]になっています。
公式では「コーパスにおける」単語数や出現回数を計算しているのに対し、サンプルコードは「共起行列」に対して計算を行っているように見受けられます。
おそらくどこかで勘違いしてしまっているかと思いますが、どうしてこのようなコードになっているのか、ご教示いただければ幸いです。
(補足:本書における共起行列の作成コード)
上記の共起行列(C)は以下のコードから出力されたものです。
# コーパスの前処理
def prepropess(text):
text = text.lower() # 文字を全部小文字にする
text = text.replace(".", " .")
words = text.split(" ") # スペースを区切り文字で分割を行う
word_to_id = {}
id_to_word = {}
for word in words:
if word not in word_to_id:
new_id = len(word_to_id)
word_to_id[word] = new_id
id_to_word[new_id] = word
corpus = np.array([word_to_id[w] for w in words])
return corpus, word_to_id, id_to_word
# 共起行列の作成
def create_co_matrix(corpus, vocab_size, window_size=1):
corpus_size = len(corpus)
co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)
for idx, word_id in enumerate(corpus):
for i in range(1, window_size + 1):
left_idx = idx - 1
right_idx = idx + 1
if left_idx >= 0:
left_word_id = corpus[left_idx]
co_matrix[word_id, left_word_id] += 1
if right_idx < corpus_size:
right_word_id = corpus[right_idx]
co_matrix[word_id, right_word_id] += 1
return co_matrix
text = "You say goodbye and I say Hello."
corpus, word_to_id, id_to_word = prepropess(long_text)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
print(C)
[[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