【ゼロから作るDeepLearning②】カウントベース手法で単語の類義語を取得する

自然言語処理

ぱらげです。
コーパス(テキストデータ)を使って、「ゼロから作るDeepLearning②」を参考にしながらカウントベースで指定した単語の類義語を取得してみたいと思います。

コーパスの前処理方法については以下の記事をご覧ください。

今回は「青空文庫」の公開著作からダウンロードした「相対性理論」のテキストデータを使用します。以下のように前処理が済んでいる状態のテキストデータが変数「result」に格納されていることを前提とします。

スペースで分割された単語を次のようにリスト化します。

words = result.split(' ')
print(words)

さらに操作しやすくするために、作成したリスト内の単語にIDを振ってディクショナリにします。

import numpy as np

def preprocess(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
corpus, word_to_id, id_to_word = preprocess(result)
print(word_to_id)

続いて、周囲の単語をカウントし、どのような単語がどれぐらい現れるかを集計した共起行列を作成します。

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 - i
            right_idx = idx + i

            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
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
print(C)

ベクトルを表せたので、次はベクトル間の類似度を計測するためコサイン類似度計測部分を作成します。(ベクトルを正規化して内積をとる)

def cos_similarity(x, y, eps=1e-8):

    nx = x / (np.sqrt(np.sum(x ** 2)) + eps)
    ny = y / (np.sqrt(np.sum(y ** 2)) + eps)
    return np.dot(nx, ny)

上記のコサイン類似度を利用して、類似単語のランキング表示をします。

def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):

    if query not in word_to_id:
        print('%s is not found' % query)
        return

    print('\n[query] ' + query)
    query_id = word_to_id[query]
    query_vec = word_matrix[query_id]

    vocab_size = len(id_to_word)

    similarity = np.zeros(vocab_size)
    for i in range(vocab_size):
        similarity[i] = cos_similarity(word_matrix[i], query_vec)

    count = 0
    for i in (-1 * similarity).argsort():
        if id_to_word[i] == query:
            continue
        print(' %s: %s' % (id_to_word[i], similarity[i]))

        count += 1
        if count >= top:
            return

次に関連性の強弱を調整するため「相互情報量(PMI)」を定義する関数を作成します。

def ppmi(C, verbose=False, eps = 1e-8):

    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 + 1) == 0:
                    print('%.1f%% done' % (100*cnt/total))
    return M

最後にSVDによる次元削減をしつつ、処理を実行してみます。

window_size = 2
wordvec_size = 200

corpus, word_to_id, id_to_word = preprocess(result)
vocab_size = len(word_to_id)
C = create_co_matrix(corpus, vocab_size)
W = ppmi(C, verbose=True)

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

word_vecs = U[:, :wordvec_size]

querys = ['自然', '物理', '法則', '質量']
for query in querys:
    most_similar(query, word_to_id, id_to_word, word_vecs, top=5)

指定した単語に対し、類似度の高い単語が上位から5つ表示されました。
所々ズレていると感じる単語もちらほらありますが、ニュアンス的には大体合っているかなという印象です。以上カウントベース手法での類義語取得でした。

コメント