読者です 読者をやめる 読者になる 読者になる

プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

機械学習でよくわからないまま文章を作らせてみる その2

機械学習

f:id:puni-o:20160324162504j:plain

前回環境を作れたので遊んでみます。 目標はでっかく

某料理レシピサイトのレシピを学習させて面白レシピを作る

The Watson Dinner Party | Howcookingworks

すでにもっとすごいのがありました。

上記リンクのような真面目なレシピではなく、既存のレシピを日本語の入力として文章を生成させるだけですので安心してください。

ということでめちゃくちゃな日本語でニヤニヤするのが目標です。

データを準備する

準備しました

試す

前回動かしたサンプル

github.com

のパラメーターを変えるだけで試せます

すでにあるdataフォルダにcookフォルダを作ってその中にinput.txtを準備します。 input.txtの中には準備したレシピのタイトルだけを2000行入れておきました。

まずは学習させます。

python train.py --data_dir data/cook

デフォルトでGPUを使うようになっているのでCPUの負荷はそんなにありません。 処理が終わるまで待ちます。

f:id:puni-o:20160324170129p:plain

train_lossというのが小さいほどいいらしい(よくわかりません)のですが、3.6はちょっと大きすぎるような気がします。

続いて文章を生成させてみます。

python sample.py --vocabulary data/cook/vocab.bin --model cv/latest.chainermodel

vocab.binlatest.chainermodelはtrain.pyでできたファイルです。 これで2000回の文字結合(?)結果をコンソールに出力してくれます。

結果の一部がこんな感じ

魚ちんちま豆
ラレーン餅
おせちき
濃厚イイパャチップ
アトジのとほさったな味煮♪

これでもまともな奴を選びました。

学習データを増やしてみる

意味不明過ぎなのでもうちょっと学習するデータ数を増やしてみます。

10000件のタイトルで学習させました。

f:id:puni-o:20160324171134p:plain

増やしたらtrain_lossが2.2くらいになりました。 学習データはやっぱり多い方がいいみたいですね。

生成結果の一部はこんな感じ

純焼きうどん
コーンスープで簡単豆腐ーロール
お弁当に!実肉のタコゴール風ピュ
鮭と舞茸のグラタン♬
とっても食パンのポテト
卵と焼きの卵焼き♡
じゃこと母のクリームパスタ

ピュ

「鮭と舞茸のグラタン♬」なんて本当にありそうです。 学習用文字列の中にはもちろんこんなタイトルありません。 すごい!

ソースをちょっと見てみる

サンプルのtrain.pyを見てみます

・・・
def load_data(args):
    vocab = {}
    print ('%s/input.txt'% args.data_dir)
    words = codecs.open('%s/input.txt' % args.data_dir, 'r', 'utf-8').read()
    words = list(words)
    dataset = np.ndarray((len(words),), dtype=np.int32)
    for i, word in enumerate(words):
        if word not in vocab:
            vocab[word] = len(vocab)
        dataset[i] = vocab[word]
・・・

頑張って解読したところ、一文字ずつ学習しているみたいですね。(上はその準備)

同様にsample.pyを見てみると

・・・
for i in xrange(args.length):
    state, prob = model.forward_one_step(prev_char, prev_char, state, train=False)

    if args.sample > 0:
        probability = cuda.to_cpu(prob.data)[0].astype(np.float64)
        probability /= np.sum(probability)
        index = np.random.choice(range(len(probability)), p=probability)
    else:
        index = np.argmax(cuda.to_cpu(prob.data))
    sys.stdout.write(ivocab[index])
・・・

文章生成の部分は単純に一文字出力を2000回(args.length)実行しているだけのようです。

ある文字の後に来る確率が高い文字を出力していく、って感じでしょうか。(よくわかりません)

ということでtrain.py中で改行コードを消して学習とかしちゃうと2000文字の謎文章ができます。(できました)

欲を出してみる

元のサンプルは英語の文章生成なので一文字ずつ連結させてもかなりの確率でちゃんとした単語になるような気がします。

これと同じことを日本語(ひらがな+カタカナ+漢字)でやるのはちょっと無理(たくさん学習させないといけない)な気がします。

先ほどのソースを見た感じだと文字の前後を見てつなげているだけな感じなので、学習時に文字ではなく単語を渡したらいい感じになるんじゃないか、とよくわからないので思いました。

ということでMeCabを使って単語単位で学習してもらうように、がんばってtrain.pyを編集してみました。 (PythonMeCabを使う方法はググったら出てきたので割愛)

・・・
def load_data(args):
    vocab = {}
    print ('%s/input.txt'% args.data_dir)
    words = codecs.open('%s/input.txt' % args.data_dir, 'r', 'utf-8').read()
    list = []
    m = MeCab.Tagger ('-Ochasen')
    print m.parse(words.encode('utf-8'))
    lines = words.encode('utf-8').split("\n")
    for l in lines:
        print l
        mm = m.parseToNode(l)
        while mm:
            list.append(mm.surface)
            print mm.surface
            mm = mm.next
        list.append("\n")
    i = 0
    dataset = np.ndarray((len(list),), dtype=np.int32)
    for word in list:
        if word not in vocab:
            vocab[word] = len(vocab)
        dataset[i] = vocab[word]
        i = i + 1
・・・

申し訳ないですが本当にPython初心者なので適当です。結果がよさそうなのでこれで動かしてますが正しい動きなのかさえワカリマセン。

1行ずつMeCabで解析して単語単位でDictionaryに追加、1行毎の改行の追加を忘れずに。というつもりで書いています。 MeCabの辞書はデフォルトのままなのでちゃんと区切ってくれているのか分かりません。(MeCabに辞書を追加できなかったので・・・)

これで学習させたあと文字コードの問題(?)に苦しめられつつsample.pyで文章を生成してみました。

大根とかぶのオイスターソースあん焼き
ブリのマリネ
プロの香ばしクッキー
もやしと白菜のサトイモ炒め
胡桃ユッケ★
ほうれん草とゆで野菜のバター巻き☆
ほうれん草とわかめのふり煮
大根入り∞みかんの野菜・蒸し煮
ストロベリーカスタードタルト

やばい、普通になりすぎた気がする。 そもそも単語単位で学習させていいのか悪いのか分かっていません。全然わかりません。ごめんなさい。