16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

この記事は、富士通ソーシアルサイエンスラボラトリ Advent Calendar 24日目の記事です。

はじめに

AI・機械学習ばやりの昨今、追いかけておきたい技術は多数あります。
しかし複数のニューラルネットワークの組み合わせや多層化などで、なかなか簡単に試せない技術も増えてきました。
最新とは言えませんが、この記事では比較的気軽に試せる自然言語処理向けの Doc2Vec を使って機械学習の実行の流れを見てみたいと思います。

Doc2Vec の考え方

Doc2Vec は単語のベクトル表現を学習する Word2Vec を任意の長さの文に使用できるように拡張したものです。

単語のベクトル表現というとピンとこない感じですが、 Word2Doc で良く使われるベクトルを用いた計算の例は以下です。

「パリ」 - 「フランス」 + 「日本」 = 「東京」

各単語の要素を数値で持つことで、上記のように単語の計算が可能になります。
この数値は例えば以下のような中身になります。

パリ フランス 日本 東京
地名 0.9 0.1 0.05 0.9
国名 0.06 0.96 0.98 0.09
中心 0.95 0.23 0.3 0.92
フランス 0.92 0.99 0.12 0.08
日本 0.01 0.02 0.95 0.97

この数値がうまく単語の特徴を表すような値になっていると、パリからフランスの要素を引いて、日本の要素を足すと東京になる、といった計算が可能になります。
Word2Vec/Doc2Vec ではニューラルネットワークを使って、文章の塊から上記の数値を学習していきます。

仕組みについてに興味がある方は、以下の記事を参照してみてください。
https://deepage.net/bigdata/machine_learning/2016/09/02/word2vec_power_of_word_vector.html
https://deepage.net/machine_learning/2017/01/08/doc2vec.html

試してみる内容

富士通ソーシアルサイエンスラボラトリ Advent Calendar 2018の各記事を学習データとして使用し、記事間の類似度などを見てみたいと思います。

できるだけ直前の記事まで使用しますので、結果は現時点(12/16)で予想つきません。
たぶんオチもつきません。

学習データとしてはテキストの量が不足すると思いますので、本格的に試されたい方は Wikipedia や青空文庫のデータで試してみてください。

実行環境

Google Colaboratory 上の Notebook 環境を使用しました。
Google Colaboratory は Web 上でコード・機械学習を実行できる Google のプラットフォームで、無料で使用できます。IPython/Jupyter Notebook をご存知の方は、そのクラウド版と考えてください。
詳しく知りたい場合は下記記事が参考になります。
https://www.codexa.net/how-to-use-google-colaboratory/

python 3.6, mecab, gensim を使用します。gensim は自然言語処理を行うライブラリで、 Word2Vec/Doc2Vec の実装も含まれています。今回の学習はそれほど時間はかからず、ローカルマシンでもこれらを揃えれば十分実行可能です。
mecab はテキストデータを単語単位に分割するために使用しますが、後述する課題があります。

Notebook のファイルと学習用データは以下に置いてあります。
https://github.com/go5025/doc2vec-cal-201812

学習データ

富士通ソーシアルサイエンスラボラトリ Advent Calendar 2018 の 12/1〜12/22 までの記事を使用させていただきました。
"end_of_text" を記事の区切りとして、22の記事を連結して一つのテキストファイルを作成しました。文章の部分はブラウザー上で手でコピーしています。

コードと実行結果

Google Colaboratory/Jupyter Notebook に合わせて記載します。
! で始まる箇所は、環境作成で使用している OSコマンドを実行するマジックコマンドです。

環境設定

Google Colab で新規ページを作成すると、gensim はすでに入っているので、mecab 関連のパッケージを追加すれば OK です。お手元の Ubuntu などで実行する場合は gensim もインストールしてください。

!apt install mecab libmecab-dev mecab-ipadic-utf8 git make curl xz-utils file -y

だったのですが、なんか学習結果がイマイチです。
今回 mecab をテキストデータを単語単位に分割するために使用しているのですが、どうもその分割結果が変です。

sample
import MeCab

text = "私はバナナが好きです。"

mecabTagger = MeCab.Tagger("mecabrc")
mecabTagger.parse("")
node = mecabTagger.parseToNode(text)
while node:
    word = node.surface
    print(word+": "+ node.feature)
    node = node.next

としたときに期待される結果は以下です。

正しい結果
: BOS/EOS,*,*,*,*,*,*,*,*
: 名詞,代名詞,一般,*,*,*,,ワタシ,ワタシ
: 助詞,係助詞,*,*,*,*,,,
バナナ: 名詞,一般,*,*,*,*,バナナ,バナナ,バナナ
: 助詞,格助詞,一般,*,*,*,,,
好き: 名詞,形容動詞語幹,*,*,*,*,好き,スキ,スキ
です: 助動詞,*,*,*,特殊デス,基本形,です,デス,デス
: 記号,句点,*,*,*,*,,,
: BOS/EOS,*,*,*,*,*,*,*,*

しかし、単に mecab の最新版を入れた時は以下のように出力されます。

mecab-python3 0.996.1の結果
私はバナナが好きです: BOS/EOS,*,*,*,*,*,*,*,*
私はバナナが好きです: 名詞,代名詞,一般,*,*,*,,ワタシ,ワタシ
はバナナが好きです: 助詞,係助詞,*,*,*,*,,,
バナナが好きです: 名詞,一般,*,*,*,*,バナナ,バナナ,バナナ
が好きです: 助詞,格助詞,一般,*,*,*,,,
好きです: 名詞,形容動詞語幹,*,*,*,*,好き,スキ,スキ
です: 助動詞,*,*,*,特殊デス,基本形,です,デス,デス
: 記号,句点,*,*,*,*,,,
: BOS/EOS,*,*,*,*,*,*,*,*

調べたところ、2018-12-16時点最新の mecab-python3 0.996.1 は以下のバグの対応が完全ではなさそうです。
https://github.com/SamuraiT/mecab-python3/issues/19

仕方ないので mecab-python の旧バージョンを入れて暫定対応としておきます。

!pip install mecab-python3==0.7

学習の実行

学習データをテキストファイルから読み込み学習します。

import MeCab
from gensim.models.doc2vec import Doc2Vec
from gensim.models.doc2vec import TaggedDocument

text = open('simuraly-data.txt', 'r').read()
documents = text.replace("\n", "").split("end_of_text")

def words(text):
    # 文章を単語に分割する
    word_list = []
    tagger = MeCab.Tagger('-Ochasen')
    tagger.parse('')
    node = tagger.parseToNode(text)

    file = open('words.txt', 'a')
    while node:
        word_type = node.feature.split(",")[0]
        word = node.surface
        if word == '*' and node.feature.split(",")[6].isalpha():
            word = node.feature.split(",")[6]

        if len(word) > 1 and word.isalpha() and word_type in ["名詞"]:
            word_list.append(word.lower())
            file.write(str(word + "\n"))
            
        node = node.next
    
    file.close()
    return word_list

# テキストごとに単語分割して学習データを作成する
train_data = []
file = open('documents.txt', 'w')

for i, document in enumerate(documents):
    train_data.append(TaggedDocument(words=words(document), tags=['text' + str(i + 1)]))
    file.write("*****" + str(words(document)) + "\n")

file.close()
    

# doc2vec の学習を実行
model = Doc2Vec(documents=train_data, min_count=1, alpha=0.005, epochs=40, sample=20, dm=1)

学習実行の本体は最後の一文です。先頭から、ファイル読み込み、単語分割の関数、学習データの作成を行って、学習を実行する流れです。
本格的に実行する場合は評価方法を決めてパラメータ等調整を行いますが、今回は結果を見ながら手でざっくりと調整しました。

学習結果の確認

各カレンダー記事間の類似度を表示してみます。
これは機械学習の推論と呼ばれる計算で、学習をして、推論するというステップが機械学習の典型的な流れです。

for i, document in enumerate(documents):
    print('text' + str(i + 1) + "\t" + document.replace("\n", "")[0:50])
    for items in model.docvecs.most_similar('text' + str(i + 1), topn=2):
        print("\t" + str(items[0]) + " : \t"+ str(items[1]) + "\t")

各カレンダー記事 text 1-22 に対する類似記事とその類似度を出力します。
マイコンに関する記事 1, 2, 5, 7、python のログに関する記事 6, 19、proxy に関する記事 8, 18、が類似となっているあたりは感覚に近い結果のようです。すべて良い結果というレベルではありませんが、学習はひとまずできています。

  • text1 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(発光ダイオード編)
    text7 : 0.9987452030181885
    text5 : 0.9986720085144043

  • text2 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(温度センサ編)Ru
    text7 : 0.9979823231697083
    text1 : 0.9978688955307007

  • text3 フロントエンド初心者が、ReactでHelloWorldしてみた(ブラウザ上編)初心者ブラウザHel
    text21 : 0.9979885220527649
    text20 : 0.9979159235954285

  • text4 フロントエンド初心者が、Vue.jsでHelloWorldしてみた(ブラウザ上編)初心者ブラウザHe
    text20 : 0.9967581033706665
    text21 : 0.9964686036109924

  • text5 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(サーボモータ編)R
    text1 : 0.9986720085144043
    text7 : 0.9985793232917786

  • text6 最初に覚えて欲しいPythonのロギングPython21はじめに簡単な使い捨てのコードならprint
    text19 : 0.9987303018569946
    text7 : 0.9971414804458618

  • text7 マイコンボードを中継器にして PC に電子部品を接続し、Ruby で制御してみた(人感センサ編)Ru
    text20 : 0.9989395141601562
    text1 : 0.9987452626228333

  • text8 proxy が存在するネットワークから、インターネット上の何かにアクセスするproxy5この記事は、
    text18 : 0.8662922382354736
    text16 : 0.8357861638069153

  • text9 Adobe XD CCでモックアップをサクサク作ろうAdobeXDモックアップ1今更ですが、Adob
    text17 : 0.9987900853157043
    text20 : 0.9986022710800171

  • text10 蠟引紙(ろうびきし)でアドベントツリー作ってみたクリスマスデザイン,UI蝋3< 序章 >先日、妻に急
    text16 : 0.9987809658050537
    text20 : 0.9987393617630005

  • text11 RaspberryPi で人感カメラを作ってみたPythonRaspberryPiIoT4開始IoT
    text16 : 0.9988595247268677
    text20 : 0.9988433122634888

  • text12 Python in RedHat Enterprise Linux 8PythonLinuxRHEL
    text20 : 0.9978363513946533
    text17 : 0.9974120855331421

  • text13 手抜きのjavascript -var編-JavaScript1富士通ソーシアルサイエンスラボラトリ
    text20 : 0.9978794455528259
    text7 : 0.9976962804794312

  • text14 なんか作るPythoncurses2なんか作ってみるPythonと何かでcursesを使って見た目動
    text20 : 0.9948053359985352
    text7 : 0.9946098327636719

  • text15 Excelの入力規則でつくる食卓カレンダーExcel,入力規則2この記事は、富士通ソーシアルサイエン
    text20 : 0.9979256391525269
    text21 : 0.9978295564651489

  • text16 CodiMDによるドキュメント作成のすすめMarkdownHackMDCodiMD4たまたま弊社のア
    text20 : 0.9992533326148987
    text17 : 0.9991676807403564

  • text17 Webで動くピタゴラスイッチ風のおもちゃJavaScriptTypeScript物理演算Matter
    text20 : 0.9992940425872803
    text16 : 0.9991676211357117

  • text18 認証プロキシをFiddlerで超えるHTTPFiddler3はじめに行く手を阻む認証プロキシを、Fi
    text16 : 0.9972044825553894
    text10 : 0.9966017007827759

  • text19 PythonのloggingにTRACEレベルを増やすPython4はじめに前回「最初に覚えて欲しい
    text6 : 0.9987303018569946
    text7 : 0.9975227117538452

  • text20 何も買わずに試すディープラーニングディープラーニングKerasTensorFlow4はじめに 相変わ
    text17 : 0.9992940425872803
    text16 : 0.9992532730102539

  • text21 Cytoscape.jsを用いてデータを可視化するgraphcytoscape.js5Cytosca
    text20 : 0.9991275072097778
    text17 : 0.9991148114204407

  • text22 ルーラーを表示して文字位置を揃えるofficeWordPowerPointドキュメント2はじめにルー
    text20 : 0.99702388048172
    text21 : 0.9970059394836426

単語間の演算を行う

単語の類似度も出力してみます。コマンドの単語に類似している単語は何でしょうか?

for result in model.most_similar(positive="コマンド", topn=10):
    print(str(result))

それなりに関連のありそうな単語が並びました。

('設定', 0.9952303171157837)
('以下', 0.9942916631698608)
('情報', 0.9937410950660706)
('ファイル', 0.9932984113693237)
('fiddler', 0.9932637214660645)
('作成', 0.99315345287323)
('環境', 0.99250328540802)
('記事', 0.9917477369308472)
('id', 0.9915721416473389)
('必要', 0.9906970262527466)

それでは user を加えてみます。

for result in model.most_similar(positive=["コマンド", "user"], topn=10):
    print(str(result))

するとネットワークに関連する単語が出力されます。

('https', 0.991931676864624)
('ネットワーク', 0.9820094108581543)
('存在', 0.9805724024772644)
('情報', 0.978228747844696)
('ファイル', 0.977412223815918)
('設定', 0.973896861076355)
('git', 0.9735790491104126)
('fiddler', 0.9706029295921326)
('インターネット', 0.9678129553794861)
('作成', 0.9671698808670044)

ここから http を引いてみます。

for result in model.most_similar(positive=["コマンド", "user"], negative=["http"], topn=10):
    print(str(result))

ネットワーク関連の単語が無くなりました。

('python', 0.9798836708068848)
('pc', 0.9794877767562866)
('arduino', 0.9788479804992676)
('pip', 0.978778600692749)
('name', 0.9787502288818359)
('self', 0.9783775806427002)
('制御', 0.9783043265342712)
('インストール', 0.9781792759895325)
('keras', 0.9780073165893555)
('logger', 0.9779103994369507)

パリ + フランス〜 ほど面白いところまでは行きませんでしたが、演算はできているようです。

おわりに

Doc2Vec を使うと文書間の類似度などを簡単に計算することができます。
単語の演算なども行うことができますが、精度を求める場合にはデータの整備、パラメーターの調整などが必要になります。
データ量が少なければ学習も短時間で試せますので、Doc2Vec で機械学習入門してみてはいかがでしょう。

それでは良いお年を。

16
11
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
16
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?