機械学習を用いたAAとセリフの自動分割

はじめに

前回の記事 ではAAとセリフ等のテキストを分割することでスマートフォンでも閲覧するようにやる夫スレを再編集する方法について記述しました.しかし,AAとセリフを人手で分離しようとするとかなりの労力とコストが掛かってしまいます.
そこでこの記事では機械学習を用いて,特にやる夫スレをターゲットにしたAAとセリフの分割をおこないます.

抽出の方法

AAスレからセリフとAAを分離するなんと事がそんなに簡単にできるかというと意外に簡単にできてしまいます. 簡単のために以下のようなデータを考えます.

⊂二二二( ^ω^)二⊃  ブーン

こういったデータが入力された時,領域認識では考えたい領域にラベリングすることを考えます.以下のようなデータが出力されることを期待するわけです

入力:⊂二二二( ^ω^)二⊃  ブーン
出力:AAAA AAAAAAAAOOSSS

ここでAというラベルはAA(今回は顔文字ですが)を,Sはセリフを,Oは領域外(Outside)を表します. こうして入力に対して期待する出力は作ることができました.ではどうやってこういった出力を出す機械学習モデルを定義しましょうか? これもまたコロンブスの卵というか,考えた人は頭がいいなあと思うのですが,実はこの問題は分類モデルを逐次的に適用するだけで解けてしまう問題なのです. 例えば,ωがAなのか,Sなのか,Oなのかを考える時は,この文字と周囲4文字をあわせた窓幅5の文字を素性d={ , ^, ω, ^, )}とする分類問題を解きます.

y* = argmax_y Score(y|d)

ここでyはラベルです.よく見ると文書分類のモデルと何ら変わらない式がでてきました.このように問題を定式化できたのであとはSVMでもパーセプトロンでも対数線形モデルでも何でもいいので分類モデルで最初の文字から最後の文字まで解いていけばいいだけです.実際には上のようなBoWではなく文字の登場位置を考慮した素性テンプレートを作成し,形態素単位でラベリングされることも多いです.

しかし,このモデルにはまだ改善点があります.例えばωの2文字前は「 」(全角スペース)なわけですが,普通空白はAAの一部ではなくAAと台詞の間の空白な感じがするのでOとラベリングされてしまう可能性が高くなります.しかし,1つ前の文字も1つあとの文字もAAなのですから,この空白もAAなのですから,としてラベリングしてほしいと考えます.

そこで考慮するのが隣接コストです.直感的には,単にラベルだけを眺めた時AAの文字が連続することは往々にしてあることですが,AAとOが繰り返し登場するというのは考えにくいことです.ここからAAからAAからへの隣接コストは低いが,AAからOへの隣接コストは少し高めという設定をし,全体でコストが低くなるようなラベルシーケンスを出力することを目標にするわけです.隣接コストは何で書いてもいいのですが,普通は行列で表されます

A_{i,j}

ラベルiからjへの隣接コストはA_{i,j} と表現されます.ここではラベルが1つ前のラベルのみに依存する構造を仮定しましたが,これは2つ前のラベルでも構いません.しかし,2つ前のラベルにも依存させるようにすると計算量が爆発します(経験則です,計算量出したわけじゃないけど).

特に,対数線形モデルに隣接コストを取り入れて系列データに適用させたモデルを条件付き確率場(CRF)といいます

こういった形で入力シーケンスに対して,出力ラベルシーケンスを予測するようなモデルを考えることができました.今回はこのモデルを使ってAAとセリフの分割を予測します.

データセット

今回実験に利用したやる夫スレは以下のものです.

データはVIPやパー速等の過去ログ倉庫から引っ張ってきました.唯一,やる夫が銀行員になるようですの第3話だけはやる夫板で投下が行われたため,yyカキコの閉鎖に伴い過去ログを手に入れることができなかったので,まとめブログ様からデータをお借りしました.

これらのテキストから事前に人手でAAの領域とセリフなどのテキストの領域にアノテーションを行いました.アノテーション作業は最初はbratでやっていましたが,途中からAAが扱えないbratに嫌気が差したので自分でアノテーションツール作ってやりました.作成したアノテーションデータは以下のようになっています

AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, O, O, O, O, O, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, O, O, O, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu, Serifu

f:id:ikazame:20190531215739p:plain
図1. ラベルと対応するAA

このアノテーションデータは図1の3,4行目に対応しています.やる夫の顔の部分はAAとしてラベリングし,セリフの部分にはSerifuとラベリングしています.また,AAと台詞の間の空白はO(Outside)としました.

ラベルの種類

抽出対象のラベルは前回の記事での分析をもとに以下の7種類を定義しました.

表1. ラベルの定義

ラベル名 意味
O ラベリング対象でない文字
AA アスキーアート
Gion ドンッ!キリッ!などの擬音
Jinobun キャラクタのセリフでない地の文
Res_Header 名前欄などのレスヘッダ
Serifu キャラクタのセリフ
Text_in_AA AA内に含まれるテキスト(看板,吹き出しなど)

評価実験

実験の設定

まずデータセットの19の文書の内,2割を開発セットとして分離します.採用したスレの種類と数がちょうどよかったので,各スレの第1話を開発セットとしました.開発セットはパラメータチューニングに利用します.残りの文書を用いて交差検定を行い,モデルの性能を確認します.

実験スクリプトpythonを用いて作成し,CRFモデルはpycrfsuiteを利用しました. 考慮する素性は,表層系と文字種であり,窓幅は5としました.

本実験は15の文書を1個抜き検定して評価しました(15-Fold交差検定とも言える).

パラメータチューニング

開発セットのデータを使ってパラメータチューニングを行います.チューニング対象はCRFの(対数線形モデルの)L1正則化項の係数c1とL2正則化項の係数c2です.この辺の話はCRFSuiteのHPに乗っています.パラメータチューニングはGrid Searchで行い,sklearn.model_selectionのGridSearchCVを利用しました.

ちなみに余談ですが,対数線形モデルは正則化項がないと学習できません.正則化項がないとあるラベルのみに出現する素性への重みが無限になってしまうからです.詳しくは髙村本読んで下さい.

c1={0.01, 0.1, 0, 1, 10}, c2={0.1, 0, 1, 10, 100}の直積をGrid Searchして最適なパラメータを探しました.結果は図2のようになりました.

f:id:ikazame:20190531221048p:plain
図2. グリッドサーチの結果

0の位置がおかしいせいで数値がちゃんと並んでませんがこれは私がアフォだったからです.みなさんはこんなことしないようにしましょう.

しかし,髙村本に書いてあった通り正則化項の係数が0だった時に学習性能が大きく落ちることを確認できたのは意義深い発見でした.

とにかく{'c1': 10, 'c2': 0.1}が最適なパラメータだとわかったので,実験にはこの値を用います.

(これは後で気づいたんですが,グリッドサーチの結果がこれだけはしっこだったのなら,c1=10, c2=0.1を中心としてもう一回グリッドサーチするべきでした.もう後の祭りなのでこの経験は次回に活かします)

評価指標

実験の評価指標には適合率(Precision),再現率(Recall),F1値を用いました.定義は以下のとおりです

 Precision = \frac{抽出した正解ラベル数}{抽出したラベル数}

 Recall = \frac{抽出した正解ラベル数}{正解ラベル数}

 F1 = \frac{2 \times Precision \times Recall}{Precision +  Recall}

また,実験結果の評価は文字単位での性能とチャンク単位での性能の2種類で行いました.これはAAとセリフの分割において,AAは端っこの1文字ぐらいの抽出に失敗しても大域的な影響は小さいため,AAは文字単位での抽出精度を上げることが目的になるからです.対してセリフは端っこの1文字を取り逃してしまうとそのせいで話の辻褄が合わなくなるなど大きな影響が出る可能性があります.そのため,セリフはチャンク単位での抽出性能に重きをおいて確認します.

実験結果の評価にはconllevalを用いました.

結果

実験結果は以下のようになりました.

表2. 抽出結果(文字単位)

ラベル名 precision recall FB1 Support
AA 98.79 99.15 98.97 850898
Gion 58.32 47.59 52.41 811
Jinobun 82.23 68.54 74.76 23639
Res_Header 99.56 99.85 99.70 202281
Serifu 92.04 96.11 94.03 115857
Text_in_AA 20.36 6.13 9.43 393
All 97.88 98.11 97.99 1191090

表3. 抽出結果(チャンク単位)

ラベル名 precision recall FB1 Support
AA 66.82 60.33 63.41 7870
Gion 62.50 38.92 47.97 104
Jinobun 18.35 9.70 12.69 436
Res_Header 89.62 91.19 90.40 3247
Serifu 90.61 93.02 91.80 6868
Text_in_AA 23.53 4.65 7.77 34
All 78.37 73.60 75.9 19762

まず,表1に文字単位での抽出性能を示します.文字単位で抽出はCRFでも大変良い結果が出ることが分かりました.特に,学習データも十分な量が準備できたAA, Res_Header, Serifuの3ラベルについての性能がかなり高い事がわかります.対して,そもそも抽出が難しいと考えられるGion, Text_in_AAについては結果は振るわないものとないっています.また,Serifu同様学習データが十分に確保できていると考えられるJinobunのRecallが少々低いことが気になります.これは,JinobunとSerifuの区別が注目トークンの周辺文脈だけでは曖昧なため,JinobunとラベリングするべきところをSerifuとラベリングしてしまう事が多かったためと考えられます.SerifuのPrecisionが下がっている原因同様です.

続いて,チャンク単位での抽出結果を表2に示します.こちらは文字単位での抽出結果よりいからか性能が低くなる結果になりました.学習したモデルの予測は,文字ごとに捉えると正しいラベルがついているものが多いが,一連のセリフやAA全体で正しいラベルを付ける能力があるかというとそうではないということが分かります.しかし,それでもSerifuの性能は文字単位のときと変化が少ないため,一度認識できたセリフはその1連の塊で間違えることがないということが分かります.

また,チャンク単位での誤りは人間が後処理で正しいラベルをつけ直さなければならない数を示します.人間がいかにらくできるかという指標がチャンク単位の結果なので,もう少し底上げの必要があるでしょう.

結果のビジュアライズ

ここまでの話はきっとピンとこない人が多いと思うので,実験結果をビジュアライズしたものを以下に示します.

まずうまく行った例から,図3はやる夫が銀行員になるようです 第2話の冒頭のレスです.図4は今回作ったモデルに図3のレスを入力し,得られた出力ラベルシーケンスをもとにAAの文字に色を付けた状態を表に示します.このように,AAスレの一部を入力することで,その文字列に含まれるAAとセリフと地の文とレスヘッダを自動的に抽出できるようになりました.

f:id:ikazame:20190531220213p:plain
図3. やる夫が銀行員になるようです第2話の冒頭レス
f:id:ikazame:20190531220219p:plain
図4. 図3のラベリング結果

今度はうまく抽出できなかった例を示します.図5は同様に銀行員スレからお借りしたレスです.ここでは文字列「チョリース」はSerifuとして,その直前の空白列はO(Outside)として認識して欲しいデータですが,ここでは両者ともをAAとして認識していまいました.これはカタカナの連続はAAになることが多いために起こった誤りだろうと考えられます.確かに薄目で見ればチョリースがAAに見えるかも……?

f:id:ikazame:20190531220337p:plain
図5. チョリース

また図6について,ここでは吹き出しの中の文字列「いらっしゃいませ!」「ありがとうございました!」などはすべてText_in_AAとして認識してほしいのですが,ここでは一部をSerifuとして誤認識しています.Serifu扱いしてこれらの文字列をAAから抜き出してしまうとAAが崩れてしまう原因になります.

f:id:ikazame:20190531220358p:plain
図6. 吹き出しのAA

まとめと今後

本記事ではAAスレからAAやセリフ,地の文などを自動的に認識し,ラベリングする実験を行い,その実験方法と結果の報告を行いました.結果はそこそこ使い物になりそうなモデルが完成しました. また,このモデルを使うことで前回の記事で示した,「AAと文章を分離させる」ことを自動的に達成できるようになりました(他にもいくつか問題点は残っていますが,そのうちご紹介します)

あと,今後というかすでにやったことが1つ.本記事の機械学習モデルを利用して,AAとセリフなどの自動認識を行って両者を分離することでやる夫スレをスマホで読みやすい形式に変換するツールを作成し,それを用いたやる夫スレのまとめサイトを作りました. 以下のサイトです.

やる夫空間

今の所,今回学習データに使用したスレとほか少ししかスレをまとめることができていません.というか見て分かる通りガッツリ開発中のサイトなので,この先どんな追加・更新していくかは不明です.まとめてほしいスレがあったらここに書いてもらえれば追加するかも知れせん(まだ本腰入れて作るかどうか決めてないので,追加しなかったらすいません).

また,今回作成したモデルは近いうちにどこかで公開したいと思います. それでは.