MENU

[ゲーム統計]マルコフ連鎖で黒舘ハルナのような美食的文章を作りたい(part2:markovifyによる複数モデルの合成)

今回はmarkovifyによる複数モデルの合成方法について記載する。

前回はブルーアーカイブの黒舘ハルナの台詞62種を使ったマルコフ連鎖を行ない、彼女っぽい文章を生成した。
今回はデータを増やすべく、彼女の別衣装(正月84種、体操服48種)のデータを取り入れていく。

ハルナ(正月)
ハルナ(体操服)

単純に同じファイルへデータを格納してもできるが、今回はmarkovifyのモデル合成を使っていこう。

前回の記事でやったように「データセットを用意して、Mecabで分かち書きした結果を新しい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)

環境
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

目次

3つのmarkovifyモデルを作成

ハルナ(通常)、ハルナ(正月)、ハルナ(体操服)の分かち書きファイルをそれぞれharuna_mecab.txt、haruna_newyear_mecab.txt、haruna_sportswear_mecab.txtとする。

まずはこのデータを取り込んで、それぞれのモデルを作ろう。
ファイル名をリストにして、まとめてtext_(ファイル名)といった形式で取り込み、model_(ファイル名)というmarkovifyモデルを作る。

import os
import markovify

#ファイル名のリスト
filenames = ["haruna", "haruna_newyear", "haruna_sportswear"]
#それぞれのファイルを読み込み、モデルを作成
for filename in filenames:
    with open(os.path.join("/Users/username/", filename  + "_mecab.txt")) as t:
        text_variable_name = "text_" + filename
        model_variable_name = "model_" + filename
        locals()[text_variable_name] = t.read()
        locals()[model_variable_name] = markovify.NewlineText(locals()[text_variable_name],well_formed=True,reject_reg="[?!♡]")
        print("*****" + model_variable_name + "のテストですわ"+ "*****")
        for i in range(5):
            print(locals()[model_variable_name].make_short_sentence(280).replace(' ', ''))

#text_haruna_mecab:ハルナ(通常)
#text_haruna_newyear_mecab:ハルナ(正月)
#text_haruna_sportswear_mecab:ハルナ(体操服)
#実行結果
*****model_harunaのテストですわ*****
あら、私の一番お気に入りの食べ物ですわね。できればご一緒に。
もう少し一緒に頂く食事ですわ!
メリークリスマス。こんな日にはこういうのも悪くありませんわ。
美食への道のりはやはりケーキですわね。先生、私が一番好きな食べ物を頂く。これぞ正しく…。
明けましておめでとうございます、先生が持っていらっしゃるのかしら?ご存じないですの?私の出番ですわね。

*****model_haruna_newyearのテストですわ*****
ほら……どのような食事でも美食にたどり着けていますのね!大吉を引いた先生には、うれしいような料理が見つかれば良いのですね。
さぁ、参りましょう。ですが…汚れてしまいますわ、先生。
今年もどのような目に遭うのですが、私と先生の毎日が、私達を待っている点にあります。
私にこのような…?そ、それ相応の罰を受ける覚悟が必要なのです。それと同じくらい、シチュエーションもなくても大丈夫ですわね。せっかくですので、振袖に着替えて参りましたか?
先生が……嬉しいです。

*****model_haruna_sportswearのテストですわ*****
さあ、美食の祭典…。このように目をかけてくださる、先生がいらっしゃるからこそ、私は輝けるのです。
先生…少しだけ、一緒に歩きませんか?…「パン食い競争」に。
先生、楽しんでおりますわ。
うふっ。今日は、先生。新たな美食の探検と洒落込みましょう。この絶好の機会、堪能するより他ありませんか?私は輝けるのですわ。
ふふっ。し、美味しいものを口にする…。この服装のおかげかしら?それとも先生の事でしたら…私、いくらでも待てますわ。

全体的に意味不明であるのは前回同様である。

モデル合成

markovify.combine([モデルA,モデルB,...],[モデルAへの重み,モデルBへの重み,...]) を用いることで、複数のmarkovifyモデルを合成することができる
またこの合成の際に、どのモデルに重点を置くかどうかも指定可能。

#モデルのリスト
models = [model_haruna, model_haruna_newyear, model_haruna_sportswear]
#重みのリスト
weights_list = [[1, 1, 3], [1, 3, 1], [3, 1, 1]]
#それぞれの重みでモデルを結合して、文章を生成
for weights in weights_list:
    model_combo = markovify.combine(models, weights)
    print("*****通常:正月:体操服=" + ":".join(map(str, weights)) + "の重みですわ!"+ "*****")
    for i in range(5):
        print(model_combo.make_short_sentence(280).replace(' ', ''))
#実行結果
*****通常:正月:体操服=3:1:1の重みですわ!*****
心躍る、美味しい競技でしたか?楽しみにしております。
あら……恥ずかしいのですの?私にとっては最高に贅沢な瞬間ですわね…。そのようなことまでしていただいて、共に歩める方ができたらと願っておりますわ。
私の出番ですわね。盛大にお任せください。
美食研究会へようこそ。究極というものをいただきたいですわね。あら?ありがとうございますか?…「パン食い競争」に。
お相手して……恥ずかしいのです。一緒に頂けるのでしょう?

*****通常:正月:体操服=1:3:1の重みですわ!*****
今年もどのようなことがありましたか?
先生には、揺るぎない覚悟がお有りなのです。……あ~んしてくださいね。
身体が軽くなったような美食が、きっと。
ええ、分かりましたわ。私も同じ、ですわ!流石先生、運も持っていますのね。私の成長して…とても新鮮でした。もし、よろしければ一緒に探しに行きましょう。
前進いたします。ですが、やって参りましたわ。なんだか楽しいですわね。先生は振袖姿がお有りなのですね?

*****通常:正月:体操服=1:1:3の重みですわ!*****
たしかに振袖は、後に吉とは、小さな積み重ねが大切と申しますもの。それが、私と先生が…。実に、完璧な循環ですわ。ふふ。
体操服、似合っている点にありますわね。
運動後のたい焼きは格別ですわ。新鮮なフルーツを乗せても?
先生…少しだけ、一緒に歩きませんか?楽しみにしておりますわね。
こういうのも新鮮ですね、先生。誕生日には、すでに頂いておりますわ、先生のお口に合うお店を予約いたしますわ。

ハルナ推しとしては何となく分かるけど、他の人から見ても違いが分かりにくいか。

走れメロスとハルナ(3種)を合成する

ということで全く異なる文章同士を合成してみよう。
青空文庫で公開されている太宰治 著の「走れメロス」を利用させていただく。
なおメロスの文章をスクレイピングで取る方法については別記事に記載しているため、具体的な方法は省略する。

#import os
#ファイル名のリスト
filenames = ["melos"]
#それぞれのファイルを読み込み、モデルを作成
for filename in filenames:
    with open(os.path.join("/Users/username/", filename  + "_mecab.txt")) as t:
        text_variable_name = "text_" + filename
        model_variable_name = "model_" + filename
        locals()[text_variable_name] = t.read()
        locals()[model_variable_name] = markovify.NewlineText(locals()[text_variable_name],well_formed=True,reject_reg="[?!♡]")
        print("*****" + model_variable_name + "のテストですわ"+ "*****")
        for i in range(5):
            print(locals()[model_variable_name].make_short_sentence(280).replace(' ', ''))
#実行結果
*****model_melosのテストですわ*****
「セリヌンティウス。よくも私を信じたばかりに輝いている。ぜいぜい荒い呼吸をしている。
「いや、まだまだ大丈夫、これから王に、必死の闘争を開始した。
「待て。その頃には、単純な男であったか。どうでも、早かったなら!」
妹は、どよめいた。セリヌンティウスよ、私は精一ぱいに鳴り響くほど音高くメロスの右頬を殴った。
「たくさんの人を、まじまじと見つめていた。「もう、駄目でございます。きょうは是非とも、あの方をお助けになるところです。このごろは、翌る日の薄明の頃である。

ここで「ハルナ(通常/正月/体操服)」と「走れメロス」を4:1で合成すると、次のようになる。

#ハルナモデルを結合
haruna_model_combo = markovify.combine([model_haruna,model_haruna_newyear,model_haruna_sportswear], [ 1,1,1 ])

#ハルナモデルとメロスモデルを結合
model_combo = markovify.combine([haruna_model_combo,model_melos], [ 4,1])
for i in range(10):
    print(model_combo.make_short_sentence(280).replace(' ', ''))
#実行結果
あけましておめでとうございます、先生がお有りなのだ。間に合う、間に合わぬものではありませんが。
こういうのも、美食の邪魔をするから、ですからね。
「晄輪大祭」の屋台へご案内いたしますわ。
私を村から追い出すような料理が見つかれば良いのですから、それからすぐに出発すれば、なおのこと。
食事とは、小さな積み重ねが大切と申します。」
美食研究会へようこそ。究極の味わいが、喉がつぶれて嗄れた声で低く笑った。村にはこういうのも新鮮ですね。福を呼び込むには、わが身に鞭打ち、ついに、がくりと膝を折った。私も同じ、ですって?いいえ、暖かいですわ。
何から何も無い。妹たちは、料理と相手……はい、こちらをどうぞ。
さて次は?ふふっ、ありがとうございます。」
「ああ、究極の味にたどり着くためにここまでしておりますか?えっ、よく似合っていた村人たちにも私たちを止める事などできません。
恐縮ですわね。

所々強そうな物言いが目立つが、ハルナを主として所々にメロスの言い回しが使われた文章の生成ができた。

まとめ

マルコフ連鎖でとりあえずの文章を生成することはできた。
一方で鉤括弧などの処理などが不十分で、完璧なものとは程遠い。
文章に「ハルナらしさ」を出すには、まだまだ改善が必要だ。

参考
ハルナ(正月)
ハルナ(体操服)
https://github.com/jsvine/markovify
前回:https://data-umikaze.com/markovify_haruna1
走れメロス:https://www.aozora.gr.jp/cards/000035/files/1567_14913.html

この記事を書いた人

サークル「データのうみかぜ」代表。趣味でRTAや旅行の動画を公開しています。

目次