金と緑のエントロピー

ヴォイニッチ手稿,投資,農作について,データ分析手法を使って不真面目に研究する場所です.

ヴォイニッチ手稿の文字をWord2Vecで(Char2Vecで)解読する

はじめに

面白い記事を見かけたので,久々にヴォイニッチ手稿の解析です.

alpha.mixi.co.jp
Word2Vecと呼ばれる単語のベクトル化手法を,単語の代わりに文字に適用してベクトル化してみたというものです.記事のポイントは数字と漢数字をベクトル化すると加減算の答えがあっており,数字という文字の意味を捉えられている,という話です.


今回はヴォイニッチ手稿の文字をChar2Vecでベクトル変換して,類似した文字の可視化とクラスタリングをしてみようと思います.
 

前置き

Word Embedding自体は単語をベクトルにする手法全てを指しますが,有名なのはCBoWとSkip-gramの2種です.今回用いるのはpython向けのWord2Vecの実装であるgensimにある,Skip-gramになります(だよね?).Skip-gramの理解はこちらの記事がわかりやすく,参考になりました.
Word2Vec のニューラルネットワーク学習過程を理解する · けんごのお屋敷


なお,今回Word2Vecに加えてPCA,階層的クラスタリングと,専門的な手法を幾つか使っているのですが,どういうものかは一切説明しておりません.そのため,各手法を知らない人には理解しがたい文書になっているかもしれません.いずれ説明しようかと思いますので,今回は結果だけ速報ということで許していただきたいです.


また,gensimというpython向けの言語処理ライブラリを筆頭にscipy,scilit-learn,pandas,pyplotを使いますが,これらは全てインストールされていることを前提に進めています.
これらは解説記事が非常に多くありますので,別サイトをご参考いただくということでご容赦ください.


データの成形と入力

まずは入力データをChar2Vecにできるように,データを1文字ごとに半角スペースで区切ったデータにします. 

def char2vec(filename,outname):
    infile = open(filename,"r")
    outfile = open(outname,"w")
    for line in infile:
        if line[0]=="#":
            continue
        outfile.write(" ".join(list(line)))
    infile.close()
    outfile.close()

データを文字区切りにした後,一度ファイル出力しています.
データは以前と同様にFSG.txtを利用しています.


その次に,データを読み込んでSkip-gramを学習します.

filename = "FSG.txtのパス"
outname = "文字区切りデータの中間出力先ファイル"

char2vec(filename,outname)

from gensim.models import word2vec
sentences = word2vec.LineSentence(outname)
model = word2vec.Word2Vec(sentences,sg=1,size=30,min_count=1,window=2,hs=1,negative=0)

ベクトルの次元はとりあえず30次元としています.窓サイズ(学習時に相関を見る周辺の大きさ)はあまり大きくすると文全体を見てしまいそうなので,近くの文字だけ見るように2としました.データを一度出力してもいいですが,学習データが小さくそれほど計算時間がかからないので,そのまま分析まで行なっています.


単語分布図を描く

まずは,学習したデータを主成分分析(PCA)でベクトル次元数を2次元に落とし,単語の分布図を書いてみました.文字分布図の表示はこちらを参考にさせていただきました.
qiita.comqiita.com
文字の種類数が少ないので,全ての文字を表示しているところが違いです.

from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

def draw_word_scatter():
    """ 単語の分布図を描く """

    words = [x[0] for x in sorted(model.most_similar("-", topn=100))]
    words.append(word)

    vecs = [model[word] for word in words]

    # 分布図
    draw_scatter_plot(vecs, words)

def draw_scatter_plot(vecs, tags, clusters=None):
    """ 入力されたベクトルに基づき散布図(ラベル付き)を描くためのメソッド """

    # Scikit-learnのPCAによる次元削減とその可視化
    pca = PCA(n_components=2)
    coords = pca.fit_transform(vecs)

    # matplotlibによる可視化
    fig, ax = plt.subplots()
    x = [v[0] for v in coords]
    y = [v[1] for v in coords]

    # 各点のクラスターが設定されていればクラスタを考慮
    # エラーハンドリングは適当
    if clusters:
        ax.scatter(x, y, c=clusters)
    else:
        ax.scatter(x, y)

    for i, txt in enumerate(tags):
        ax.annotate(txt, (coords[i][0], coords[i][1]))
    plt.show()

ヴォイニッチ手稿の文字のクラスタリング結果がこちら.
f:id:shounenA:20171021232419p:plain 

今回は文字も区切り文字も全部含めて分析しました.単純に分析が楽なのもありますが,単語間の空白や行末が意味を持つものかどうかがかわからなかったためです.
「-」は文の終わり,「=」は文書の終わりを示すのですが,近い位置にあります.これはつまり,2つの文字がベクトルとしては近いということであり,つまり同じような単語と使われている傾向が高いということです.区切り用に表現した文字をきちんと同じものだと認識しており,Char2Vecで特性を解析できていると期待が持てます.


他にも,なんとはなくですが,文字が幾つかのまとまりを持っているように見受けられます.近くにある文字は同じような意味や役割を持つ文字群として,クラスタリングできるかもしれません.


ヴォイニッチ手稿の文字をクラスタリングしてみる

ということで,文字のクラスタリングをしてみました.上記サイトを再び参考にさせていただき,階層的クラスタリングでベクトル化した文字をクラスタリングしました.


結果がこちら.
f:id:shounenA:20171021232433p:plain
非常に面白い特性を捉えられました.

「e」「t」「i」は,他の文字と異なるベクトルを示し,特殊な意味を持つ文字だと考えられる.

階層的クラスタリングを行なった結果のデンドログラムでは,
「e」「t」「i」などの文字は右側のクラスたに集められ,他の文字と比べて距離が遠いことを示唆しています.
このことから,「e」「t」「i」は他の文字とは出現位置が異なる,特殊な文字であると予測することができそうです.

「C」「D」「G」は他の文字に比べて独立しており,単独で意味を持つように見受けられる.

 「C」「D」「G」の3文字は,他の文字たちとクラスタを形成せず,3文字だけ独立しています.
こちらの文字たちも,「e」「t」「i」と同様に意味を持ちそうです.
ただ,これらの文字はそれぞれが他の単語と比べてやや類似性が低く,特殊な意味を持つかもしれません.
例えば,過去/現在/未来の時制を示すなど.どうでしょうか?

文字はいくつかの類似した文字群に分けられそうである.

階層的クラスタリングの結果は,類似した文字の集合を形成した後に,各集合をまとめるように層が作られています.
明らかに,幾つかの文字のクラスタに分かれています.
もっとも,これがクラスタリングの手法による結果であるなど,バイアスがかかった結果である可能性は大いにあります.
しかしながら,合計で7つほどのクラスタを形成する様子は,各文字が品詞に対応するかもしれない,など,様々な期待をさせてくれます.
このあたりは,次回に是非解析していきたいと思います!


最後に

今回は,ヴォイニッチ手稿のデータをWord2Vecにかけて,非常に面白い結果が得られました.
次回はこれを更に発展させて,文字や文字Bi-gramでの結果を深掘りしていこうと思います.