私はブルーアーカイブというゲームをよくプレイしているのだが、その中でも特に好きなキャラクターがいる。
彼女の名は黒舘ハルナ。一言で表するなら顔の良いテロリストである。
以下は公式Twitterによる彼女の紹介文章を引用したものだ。
「常識すら丸ごと食べ尽くした」と言われる美食研究会、その会長さんです!
ブルーアーカイブ公式X
お金持ちのお嬢様なのですが、美食の為なら戦闘行為も躊躇しないお人です…
「〜ですわ」といったお嬢様言葉で丁寧に話す一方で食へのこだわりは非常に強く、美食を求めるためなら手段を選ばない。
ゲームにおいても値段相当の価値を見出せないお店を爆破する1、水族館で展示されている魚を強奪するなどの犯罪行為を平然と行なう。
お店へのテロ行為については彼女曰く、「誰も使ってない流し台で水が出しっぱなしになっていたら、蛇口を閉めたい衝動に駆られる」とのこと。一見理が通っていそうで、冷静に考えると意味が分からない。
上の発言から分かる通り彼女なりの信念に基づいた行動であり、上記の行動はいずれも理性的に行われている。
ストーリーでの出番も割と多く、主人公にもかなり好意的なことも特徴だ。
どんな時でも自らの理念を曲げず、信念を貫きとおす彼女に私はとても惹かれている。
そんなハルナの魅力を伝えるには本記事は狭すぎるため、詳細は割愛させていただく。
今回は彼女の丁寧なお嬢様風の言葉遣いと、理解が難しい強いこだわりが伺える言動をもっと見てみたいという私の欲を満たすため、マルコフ連鎖による文章生成を行おう。
実行環境は以下の通り。
今回はPythonで実装していくにあたり、形態素解析エンジンであるMecab、PythonでMecabを使うためのパッケージmecab-python3、マルコフ連鎖を簡単に実装できるパッケージmarkovifyを利用する。それぞれ事前にインストールしておくこと。
環境
MacBook Air (M1,2020)
macOS Monterey 12.0.1
python 3.10.6
mecab-python3 1.0.8
markovify 0.9.4
mecab-ipadic-neologdのインストール方法/詳細はこちらを参照
https://github.com/neologd/mecab-ipadic-neologd/blob/master/README.ja.md
markovify
https://github.com/jsvine/markovify
マルコフ連鎖とは
マルコフ連鎖とは、未来は現在の状態によってのみ予測される(=過去の状態に依存しない)とする確率過程だ。
日本統計学会が出版している統計学実践ワークブックでは以下のように記載されている。
実数値確率変数の列\(X = (X_n)_{n=0,1,2,…}\)と集合\(B \subset \mathbb{R} \)に対して、
$$P(X_{n+1}\in B |X_n,X_{n-1},\ldots,X_1,X_0) = P(X_{n+1}\in B |X_n) $$
となるとき、これを確率変数列\(X\)のマルコフ性(Markov property)といい、このような性質を持つ\(X\)をマルコフ連鎖(Markov chain)という。
日本統計学会 編, 統計学実践ワークブック, 学術図書出版社, 2020, P1082
このマルコフ連鎖の特性は文章生成に利用できる。
「祇園精舎の」とあれば「鐘の声」と続きそうだとか、「透き通るような」という文字列の次は「世界観」が入るかな、というように現在までの情報から未来を予測するというのは日常的に我々人間もやっているだろう。
厳密には異なるが、マルコフ連鎖での文章生成のイメージはこれに近い。
詳細は別の機会に解説するため、ここでは文章を作るために便利なモデルということと、名前がカッコいいということだけ覚えておけばよい。
データセットを用意して、Mecabで分かち書きした結果を新しいtxtファイルに格納する
まずはwikiに記載されたハルナ(通常)の台詞62種3をtxtファイルに保存して、マルコフ連鎖を使うためのデータセットを作成する。
import MeCab
mecab = MeCab.Tagger('-Owakati -d /opt/homebrew/lib/mecab/dic/mecab-ipadic-neologd')
# 生データのパス
input_file_path = '/Users/username/haruna.txt'
# 加工データのパス
output_file_path = '/Users/username/haruna_mecab.txt'
with open(output_file_path, 'w') as output_file:
with open(input_file_path, 'r') as input_file:
# 生データの文章を1行ずつ処理
for line in input_file:
# 行末の改行文字を除去
line = line.rstrip('\n')
# 形態素解析を行なった結果をharuna_mecab.txtに保存
mecab_result = mecab.parse(line)
output_file.write(mecab_result)
ここでは分かち書きという形態素(語や文節)ごとに区切っていく加工を施している。
具体的にデータを見てもらった方が早いだろう。
生データ | 分かち書き |
美食研究会へようこそ。究極の味を求めて、共に冒険に出かけましょうか? | 美食 研究会 へ ようこそ 。 究極 の 味 を 求め て 、 共に 冒険 に 出かけ ましょ う か ? |
たい焼きですの?私の一番お気に入りの食べ物ですわ。うふふ。 | たい焼き です の ? 私 の 一番 お気に入り の 食べ物 です わ 。 うふふ 。 |
ここで気になるのは、分かち書きで「美食研究会」が「美食」「研究会」と分けられてしまった点だ。
比較的新しい言葉に強い辞書である「mecab-ipadic-neologd」を使っているものの4、流石に難しかったか。
キヴォトスで有名なテロリスト集団の名に、まだ現代が追いついていないようだ。
対処法はあるが、今回はベンチマークとなるモデルを作ることができれば良いため、このまま進めていく。
マルコフ連鎖で文章生成
文章を形態素ごとに区切ったデータを用意できたことで、マルコフ連鎖による文章生成ができるようになった。
今回はひとつ前と現在の形態素から次の状態を予測する2階マルコフ連鎖を用いる5。
イメージとしては、ひとつ前の「究極」、現在「の」まで分かった状態で、次の形態素が「味」なのか、「美食」なのか予測するようなものだ。
markovifyのgithub6を参考に、以下のコードを実行する。
改行されたテキストを取り込むためmarkovify.Text
ではなくmarkovify.NewlineText
を使う点に注意しよう。
with open("/Users/username/haruna_mecab.txt") as t:
text = t.read()
#モデル作成
text_model = markovify.NewlineText(text)
#上記のモデルを使って文章を生成する。
for i in range(5):
print(text_model.make_sentence().replace(' ', ''))
日本語のデータ特有のエラーは発生しなかったが7、データが少ない故の幸運か。
それでは結果を確認してみよう。
もう少し一緒に頂く食事ですわ。ただ、私が一番好きな食べ物を頂く。これぞ正しく…。
たい焼きですの?私の一番お気に入りの食べ物ですわ。新鮮な生クリームを乗せても飽きないお顔をされてますわよ?好意的な意味なのかしら?
では、先生が持っていらっしゃるのかしら?
たまにはこういうのも悪くありませんわ。…私にくださる?
あら……これで私もまた少し、前に進んで行くこと。うふふっ……感謝いたしますわ。
うーん、悪くはないけど良くもない。
「新鮮な生クリームを乗せても飽きないお顔をされてますわよ?」はなかなかのパワーワードだが、全体的に日本語が怪しい。
ハルナの上品さと意味の分からなさが合わさった、日本語として成立する文章を作るには時間がかかりそうだ。
しばらくこのモデルで遊んでみて、面白かった文章を3つ記載する。
先生と一緒でしたら、どんなものでも美味しそうですわね。あら?褒め言葉ですわね。盛大にお出迎えして差し上げましょうか?楽しみにして差し上げましょうか? |
お誕生日おめでとうございます、先生が持っていらっしゃる飴、ですって?いいえ…私にくださる? |
スコヴィル値1000万級の激辛ですわ。…いいえ。あーん、でなくては。 |
まとめ
マルコフ連鎖と形態素解析により、ハルナっぽい文章を作ることができた。
モデルの都合上どうしても意味不明な文章ができてしまうが、そういうところも彼女らしいと言えなくもない。
今後はデータセットを増やす、ユーザー辞書を編集してブルアカの世界に合わせていくなど、より彼女のような突き抜けたモデルを目指していこうと思う。
- 爆破されても仕方のない程度に酷いお店も登場している。 ↩︎
- 日本統計学会 編, 統計学実践ワークブック, 学術図書出版社, 2020 ↩︎
- https://bluearchive.wikiru.jp/?%E3%83%8F%E3%83%AB%E3%83%8A ↩︎
- 人気漫画のタイトルやキャラクター名を一つの形態素として認識できる。 ↩︎
markovify
のデフォルト設定。変更する場合はstate_size=NとすればN階マルコフ連鎖になる。 ↩︎- https://github.com/jsvine/markovify?tab=readme-ov-file#basic-usage ↩︎
markovify.NewlineText
にwell_formed=True,reject_reg="[飛ばしたい文字]"
とすることで、不正となる文字を無視することができる。過去にgithubに本手法は記載されているものの私が読み飛ばしており、他のデータで利用する際にかなり詰まったことがあるので、反省を兼ねて記載する。 ↩︎
コメント