ishigenの技術ブログ

卓球×テクノロジー、機械学習

ツイートからテキスト生成①

今回はおーばさんのツイートからディープラーニングでテキストを生成します。なぜおーばさんなのかというと、自分と異なるテイストのツイートをする人でやってみたくて。おーばさんのツイートは良くも悪くも独特のテイストがあるなあと思い選びました。


結果

5件分のツイートを生成するとこんな感じでした。

してないないから!!!!!!たし笑
わ笑結局結局試合したけど、でだろうけど
あるところってって笑大学のバイト
ちゃったんでんで誰か?だったけど、女子も面白い笑
回もええ、そなねぇ。、、、、、、そうな人がいた。


ちょっと何言ってるかわかりませんよね。(AIにしても女子が出てくるんかい!って思うけど。)ってことで、このテキストがどのくらいましなのかを検討するために、完全にランダムに単語を選んで連結したテキストと比較してみます。

ツイッターで
しまった11マネキンこらフルボッコほど
・∇・)やっぱりhalationノーヒットかわし桐一点
すぎるw用紙段差撮りプラス待った福留しもた人文句くれる
開学場合西武さや軽減かけて川島


完全にランダムに単語を連結した場合はさらに意味がわからないテキストになっていて、ディープラーニングで生成したほうが多少ましかなというレベルです。


データ

ツイートデータはtwitterAPIを叩いて取得した直近3200件を使います。

手法

まず、どうやってディープラーニングのモデルがテキストを生成するかというと、

  1. 最初の 文字列をランダムに選ぶ
  2. 予測:ランダムに選ばれた文字列から、次に当てはまる確率が高い文字列を推定
  3. 連結:予測した文字列を連結する
  4. 以下2と3をループ処理


するのが基本的な流れになります。予測する際に次の単語を予測する方法と次の一文字を予測する方法があります。日本語だと漢字もあり、一文字ずつの予測だと候補数がかなり多くなってしまうので、難しくなります。英語だとアルファベットと記号のみの候補から選ぶことになるので、比較的簡単です。


今回は次に当てはまりそうな単語を予測する方法を採用しています。単語の分割にはmecabを利用し、mecab-ipadic-NEologdを辞書にしています。

モデル

今回はkerasで公開されているテキスト生成モデルを単語ごとに処理するように手を加えて使います。LSTM層と全結合層のみのシンプルな構成になっています。


単語ごとのone-hotエンコーディングを適用させる前処理。

f = open('ohba.txt')
textall = f.readlines()
tagger = MeCab.Tagger("-Owakati")
text = []
for sentence in textall:
    str_output = tagger.parse(sentence)
    list_output = str_output.split(' ')
    list_output = list_output[0:-1]
    if len(list_output) == 0:
        x = 1
    else:
        text.extend(list_output)
f.close()

print('corpus length:', len(text))

chars = sorted(list(set(text)))
print('total words:', len(chars))
word_indices = dict((c, i) for i, c in enumerate(chars))
indices_word = dict((i, c) for i, c in enumerate(chars))

maxlen = 2
step = 3
sentences = []
next_word = []
for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_word.append(text[i + maxlen])

print('Vectorization...')
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        x[i, t, word_indices[char]] = 1
    y[i, word_indices[next_word[i]]] = 1


モデルの構築。

model = Sequential()
model.add(LSTM(128, input_shape=(maxlen, len(chars))))
model.add(Dense(len(chars), activation='softmax'))

optimizer = RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)
model.summary()

f:id:ishigentech:20180906205632p:plain


このモデルをサンプルにあるコールバック処理を削除して、60エポック学習させてみました。

model.fit(x, y,
          batch_size=128,
          epochs=60)


モデルの予測結果はただの数字の配列であり、その配列を入力してランダム性も考慮して文字を選ぶ関数を定義。

def sample(preds, temperature=1.0):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)


テキストを生成するコードは以下の通り。

for i in range(5):
    start_index = random.randint(0, len(text) - maxlen - 1)
    generated_text = text[start_index: start_index + maxlen]
    alltext = generated_text
    wrodlength = random.randrange(20)
    for i in range(wrodlength):
        sampled = np.zeros((1, maxlen, len(word_indices)))
        for t, char in enumerate(generated_text):
            sampled[0, t, word_indices[char]] = 1.
        preds = model.predict(sampled, verbose=0)[0]
        next_index = sample(preds, 0.5)
        next_word = indices_word[next_index]
        generated_text.append(next_word)
        generated_text = generated_text[1:]
        alltext.append(next_word)
    print(''.join(alltext))

課題

  1. モデルが単純すぎる
  2. ツイートのような細切れのテキストへの対応
  3. データ数


1.論文やら他の方法を参考にしつつ、もう少し複雑なモデルが必要じゃないかと。

2.ツイート特有の問題でもありますが、3200ツイートを一つの長いテキストを分析するためのモデルに突っ込んでいるので、ツイートの終わりと始まりが考慮できていません。

3.一人のツイートにこだわるのであれば、3200が今のところ限界っぽい。類似単語に置き換えて、似たような文章を作成する方法もありますが、雰囲気が変わってしまいそうで悩みどころ。


本当はこのモデルでツイート生成して、自分の代わりにツイートしてもらおうと企んでいましたが、結果がよろしくなかったので今はやりません。
これらの課題をもう少し改善しつつ、またリベンジします。