"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

テキストを「'0000'から'FFFF'の出現頻度」で符号化する自然言語前処理/Neural Network Console応用編

日本語文章を「Neural Network Console」で学習・評価可能なように「'0000'から'FFFF'」のコードの出現頻度を使って符号化する前処理がテーマです。

f:id:arakan_no_boku:20190326212937j:plain

 

前提

 

日本語文章を「Neural Network Console」で学習・評価可能なように、列固定の数値データに変換するテーマでは一度記事を書いてます。

arakan-pgm-ai.hatenablog.com

上記記事では、'00'から'FF'の256列のコードの出現頻度で数値化してます。

 

ただ、この時の256列というのは、これがベストだから・・という理由ではなく、EXCELのグラフで生成したデータを目視確認したり、処理時間を短くするなどの諸々の妥協の産物でデータを大きくしないようにしただけです。

なので。

日本語のようなマルチバイト文字を、シングルバイトで処理するのは、少々無理があるのでは?というご指摘の声については「まったく、その通りですが・・うんぬん」と言い訳をし続けることになってます。

でも、よく考えてみれば。

マルチバイト('0000'から'FFFF'の65536列)でやった方が本当にベストに近づくのか?というのは試してないわけです。 

 

Pythonのクラスを'0000'~’FFFF'のマルチバイト対応に変更

 

ベースは上記の記事のクラスです。

それを、マルチバイト('0000'から'FFFF'の65536列)に変更します。

変更する部分は2か所。

  • 初期化する部分を、'00'~'FF'ではなく、'0000'~'FFFF'にする
  • 文章を文字コードに変換する部分をマルチバイト対応にする

 

初期化する部分を、'00'~'FF'ではなく、'0000'~'FFFF'にする 

 

これはシンプルに'0'~'F'を4列つなぎ合わせて行けばいいですね。

# 初期化 0000からFFFFをひとつずつおく。位置を固定し、かつ、0エラーを回避する
l_line = []
l_seed = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']
for i1 in range(16):
    for i2 in range(16):
        for i3 in range(16):
            for i4 in range(16):
                l_line.append(l_seed[i1] + l_seed[i2] + l_seed[i3] + l_seed[i4])

 

文章を文字コードに変換する部分をマルチバイト対応にする 

 

こっちは単に取得する範囲を広げるだけ。

なのですが、1バイト文字の時は'00'を前につけるなどの工夫がいります。

ちなみに、[2:6]のようにスライスしているのは、16進数に変換した文字列には不要な「0x」という文字列が頭についている(0x0D0Fみたいに)ので、それを除去しています。

for i,l_pt in enumerate(l_text):
    # 不要な空白などを除去
    l_p = re.sub(self.__sub_pat,"",l_pt)
    # 空行は無視する
    if l_p:
        # 元テキストも出力しておく
        tout_f.write(l_p + "\r\n")
        # 1行分の単語を分割
        o_malist = o_t.tokenize(l_p)
        for n in o_malist:            
            # 記号以外を対象にする
                if not (o_reg.match(n.part_of_speech)):
                    # 分割した単語を1行分リストに加えていく                    
                    for i in range(len(n.surface)):                    
                        h = str(hex(ord(n.surface[i]))).upper()
                        if(len(h) ==4):
                            l01 = '00' + h[2:4]
                            l_line.append(l01)
                        if(len(h) >=6):
                            l02 = h[2:6]
                            l_line.append(l02) 

他の部分はそのまま使えます。 

 

同じニュースコーパスを処理してみる

 

上記の部分だけを修正して、マルチバイト('0000'~'FFFF')の出現頻度でカウントする方法でNNC用データを作り直しました。

65536列のデータになります。

 

ニュースコーパスを加工してみた

 

元にしたテキストデータは、「使い方30」と同じく以下の記事で紹介している「livedoorのニュースコーパス」です。

arakan-pgm-ai.hatenablog.com

256列版と異なり、データ作成にも相当(自分の古いPCだと1時間近く)かかります。

しかも、できあがったデータをチェックしてみたら、ほとんどがカウント1の同じデータという実に筋の良くないデータになってました。

実世界で例えるなら・・観客が1000人位しか入ってない東京ドームの観客席・・みたいにスカスカです。

たぶん、マルチバイトにするには、記事データが小さすぎるのですね。

うーーん。

嫌な予感を感じます。

でも、せっかく作ったので学習と評価をやってみます。

 

比較用に、同じネットワークモデルで学習・評価する

 

'00'~'FF'でやった時との比較をしたいので、同じネットワークモデルを使います。

arakan-pgm-ai.hatenablog.com

変更点は、inputのサイズだけです。

前回は「1,256」ですけど、今回は「1,65536」です。

そのため、続くAffineのOUTPUTサイズもちょっと変更してます。

f:id:arakan_no_boku:20180902203638j:plain

それ以外は一切触らずに学習をしました。

 

学習の時間は画像なみにかかるし結果グラフも不安な感じ

 

256列('00'~'FF')の時は、学習はものの数分で終わってました。

しかし、さすがに65536列になると・・・時間がかかります。

自分のCPUのみの環境だと、ざっと2時間位ですかね、

相当、精度があがってくれないと元はとれない位の差があります。

学習結果のグラフはこんな感じです。

f:id:arakan_no_boku:20180902215446j:plain

なんじゃ?これは。

TrainingでもValidationでも、ボコッと大量にエラーがでたりでなかったり、ミニバッチ単位で結果のバラつきがひどすぎる感じがあります。

うーーーん。

ますます、不安がつのります。

これは期待できないかも・・・。

そう思いながら、評価を実行してみました。

 

評価結果は無茶苦茶改善されたんだな・・これが

 

評価の結果は以下の通りです。

f:id:arakan_no_boku:20180902215911j:plain

99.76%!

どうなってんの?というような数字です。

学習結果を見た時の予想に反して、256列('00'~'FF')パターン時から、約2%近く改善しています。

それにしても、この改善率はすさまじいですね。

 

まとめ:まあ、使えそうな気はする

 

正直、過学習じゃないかと思って、データを見直しました。

異常はないです。

念のため、他のニュースのテキストを持ってきてバリデーションデータを差し替えてみても、ちゃんと精度がでています。

過学習ではないですね。

なんなんでしょうね。

一体。

正直、データの感じからして、もっと長いテキスト(小説とか)だと、こういう結果になりそうな気はしてました。

でも、今回のこの結果は予想してませんでした(笑)

でも・・まあ。

マルチバイト版('0000'~'FFFF')のアプローチは、データ作成にも学習にも時間はかかるけど、それに見合う位の精度向上の可能性は持ってる。

そう考えても・・いいのかな?

f:id:arakan_no_boku:20171115215731j:plain