文學ラボ@東京

(文学をなにかと履き違えている)社会人サークルです。第22回文学フリマ東京では、ケ-21で参加します。一緒に本を作りたい方はsoycurd1あっとgmail.comかtwitter:@boonlab999まで(絶賛人員募集中)。

content

TensorFlowを用いた舞城王太郎の正体の類推

(これは第22回文学フリマ東京の原稿用に作成した記事です)

舞城王太郎は現代作家の中でも特異な地位を築いている。デビューは2001年、推理小説文学賞であるメフィスト賞だが、2003年には純文学の賞である三島由紀夫賞を受賞している。文章は独自のスピード感のある文体で、デビュー作の『煙か土か食い物』では、アメリカ帰りの主人公の心情描写を、スラングを多用した文章により上手く表現していた。そんな舞城王太郎であるが、現在まで本名・性別等が不明の覆面作家であり、その正体を知る方法は現段階で存在しない。そこで本記事では、Google製の機械学習フレームワークであるTensorFlowを用い、舞城王太郎の正体をその文章から類推していく。

TensorFlow

TensorFlowはGoogleが開発した機械学習フレームワークで、特にディープラーニングをターゲットにしたものになっている。ディープラーニングとはニューラルネットワークの一形態であり、その分類タスクにおける識別率の高さから、近年注目を集めている。

まずはざっくりとニューラルネットとはなにかを紹介するため、以下のPythonコードで一層のニューラルネットの例を示す。

import math

# 入力層: 単語に任意のインデックスをつけたもの
# 入力A
# 私 の 好き な もの は 寿司 だ 。
A = [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 入力B
# 私 の 好き な もの は ドッグフード だ 。
B = [1, 2, 3, 4, 5, 6, 10, 8, 9]

# 出力: ノード数2
A_OUT = [1, 0]
B_OUT = [0, 1]


def softmax(xs):
    # ソフトマックス関数を定義
    # 0から1までの間の連続値を得られる
    e = [math.exp(x) for x in xs]
    sum_e = sum(e)
    return [x/sum_e for x in xs]


def hoge(inputs):
    weights = [0.1, -0.2]  # 更新される適当な値
    bias = 0.001  # 更新される適当な値
    ret = []
    for w in weights:
        # それぞれの入力値に重みをかけた値を加算
        h = sum([input * w for input in inputs]) + bias
        ret.append(h)

    return softmax(ret)


y1 = hoge(A)  # -> これをA_OUTに近づけたい!
y2 = hoge(B)  # -> これをB_OUTに近づけたい!

ここで、関数hogeは入力値と重みとの計算を担っている。この関数に任意の長さのベクトルを入力することで、それぞれの入力値に対して重みをかけた和が計算され、例えば[0.91, 0.09]のような出力が得られる。このように、多数の入力に対して制限された出力が得られることが、実際の神経細胞の挙動とのアナロジーになっている。この際に、Aを入力した場合は出力A_OUT [1, 0]、Bを入力した場合は出力B_OUT[0, 1]となるように、重みwを設定すれば、入力値を分類することができる。(上のコードでは重みwの値を適当な値に設定している)。その重みwを決定する手法については様々なものが存在する。適切な結果を得るためには、その中から最も解決したいタスクにあてはまる損失関数や最適化手法を選び出し、結果を比較して、......と煩雑な手順を追う必要がある。そこで、それらの関数がパッケージングされたTensorFlowを用いることで、その手間を省くことができる。それでは、TensorFlowを用いた場合のコードを以下に抜粋する。

    ph_x = tf.placeholder(tf.float32, [None, width])
    ph_y = tf.placeholder(tf.float32, [None, NUM_CLASSES])

    # 初期化
    output_W = tf.Variable(tf.random_uniform([width, NUM_CLASSES], -1.0, 1.0))
    output_b = tf.Variable(tf.zeros([NUM_CLASSES]))

    # 推定値の計算 --- 上記の式は実質この部分のみ!
    predicted_y = tf.nn.softmax(tf.matmul(ph_x, output_W) + output_b)

    # 交差エントロピーの計算
    x_entropy = tf.nn.softmax_cross_entropy_with_logits(predicted_y, ph_y)  # [Dimension(None), Dimension(4)]
    cross_entropy = tf.reduce_mean(x_entropy)

    # 急降下勾配法
    learning_rate = 0.1
    optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cross_entropy)

    # 精度の計算
    correct_prediction = tf.equal(tf.argmax(predicted_y, 1), tf.argmax(ph_y, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

    num_epochs = 1000
    batch_size = 100

    init = tf.initialize_all_variables()
    sess = tf.Session()
    sess.run(init)
    for i in range(num_epochs):
        log("Start {0} epoch.".format(i))
        random.shuffle(train_data)
        _train_x, _train_y = zip(*train_data[: batch_size])
        sess.run(optimizer, feed_dict={ph_x: _train_x, ph_y: _train_y})
        print(sess.run(accuracy, feed_dict={ph_x: test_x, ph_y: test_y}))

以上のように、TensorFlow組み込みの関数を用いることで、自分の手で最適化関数等を実装することなく、重みwの値を計算することができる。ところで、以上のコードを実行しても、残念ながら高い精度を得ることは出来ない。これは、テキストデータが、[4, 33, 2, 198, 67, 32, 1, 0, 0, 0, 0, 0, ... , 0]と隣り合った値同士の間が滑らかに変化しない(スパース)ことが原因のひとつである。この問題を解決する方法として、単語の埋め込み表現を用いる手法がある。これについては(http://tkengo.github.io/blog/2016/03/14/text-classification-by-cnn/)に詳しい。以下、引用先の作者が公開している関数を改変したコードにて、実際に小説の分類を行っていく。

舞城王太郎小説の分類

ここでは青空文庫から適当に取得した "坂口安吾"、"江戸川乱歩"、"折口信夫", "夢野久作"の4作家の作品から、いずれの作家の文章が舞城王太郎の文章に近いか、分類を行う。データについてはそれぞれ、青空文庫からスクレイピングしたものを用いた。それらのテキストについて、各作品毎に一文単位で分割を行い、それらを分かち書きしたものを使用してベクトルデータを作成した。それらのデータについて前述の分類器を作成した結果、0.5程度の精度を得ることができた(単純にランダムだと1/4 = 0.25程度の値になるはずだ)。以下にその学習過程をTensorBoardに出力したものを示す。

f:id:soy-curd:20160427012110p:plain

縦軸が精度で横軸がステップ数だが、15回付近からは精度の上昇が見られない。実際これ以上学習を続けても精度は0.4 ~ 0.6の間に収まるようだった。

ひとまずこの分類器を用いて、舞城王太郎『世界は密室でできている』を分類する。テキストはOCRを用いて抽出し、変換に失敗している箇所については目視で修正した。それを上記のような前処理を行い、ベクトル化した。そのデータについてランダムに100個分類器に入力し、出力値を平均した。すると、

# ["坂口安吾"、"江戸川乱歩"、"折口信夫", "夢野久作"]
 [ 0.63965601  0.14899535  0.12311091  0.08823783]

という値が得られた。この結果はビットが立った(1に近い)ラベルに対して分類されていることを示すので、少なくともこの四人の作家の中では、舞城王太郎の文章は坂口安吾の文章に近いことが推定される。実際に『世界は密室でできている』と坂口安吾の『織田信長』の出だしを見比べてみると、

舞城王太郎

何とかと煙は高いところが好きと人は言うようだし父も母もルンパパも僕に向かっ てそう言うのでどうやら僕は煙であるようだった。

坂口安吾

立入左京亮たてりさきょうのすけが綸旨二通と女房奉書をたずさえて信長をたずねてきたとき、信長は鷹狩に出ていた。

そうでもないような気もするが、安吾推理小説を書いていたことだし、妥当な気もする。つまり、舞城王太郎の正体はなんと、坂口安吾であった(完)。

おわりに

以上、TensorFlowを用いて舞城王太郎の正体の類推を行った。今回は入手性の良い青空文庫の小説データを用いたが、現代作家の小説データを用いることによって、別の面白い結果が得られるかもしれない。小説に対する新しい切り口が欲しい、あるいはプログラミングや機械学習に興味の有る方は、これを機にこれらのツールを利用してみることをお勧めしたい。

世界は密室でできている。 (講談社文庫)

世界は密室でできている。 (講談社文庫)

不連続殺人事件 (角川文庫)

不連続殺人事件 (角川文庫)

深層学習 (機械学習プロフェッショナルシリーズ)

深層学習 (機械学習プロフェッショナルシリーズ)

Python機械学習プログラミング 達人データサイエンティストによる理論と実践 (impress top gear)

Python機械学習プログラミング 達人データサイエンティストによる理論と実践 (impress top gear)