前回 物語自動生成プログラム『ジェネジェネちゃん』の作り方 基礎編
■出力するサンプルの作風の決定
今回、この物語自動生成プログラム『ジェネジェネちゃん』を
作るにあたって、以下のような内容のサンプルを出力するという
目標を立てました。
■パートの書き分け
バトルモノとはいえ、常に戦闘シーン(パート)ばかりではなく、
日常パートもあるのが一般的なので、そのパートの書き分けが
必要です。それに、混じりすぎると文章がグチャグチャになりますしね。
まず、小説1冊分を4章構成と考え、さらに1章は4節に
分かれるとし、1節は15ページほど出力すると決め、
その各節は「戦闘パート」または「日常パート」のどちらかに
なるようにしたいと考えました。
しかし、前回の記事までの手法では、多少の偏りは生まれても、
戦闘・日常パートの違いまで分けてくれるほどの知性(?)は
有していません。
■文の判定
そこで、前段階の文分割の直後の処理として、文の判定を
おこなうことにしました。
まず、戦闘パート・日常パートそれぞれでしかほぼ登場しない
であろう単語のリストを手動で作成します。
戦闘パート用を一部列挙すると、
こんな感じです。
それらの文字列が含まれるかどうかで、その1文が戦闘パート
なのかを判定します。
ちなみに、形態素解析した後の形態素で判別する方が
確実のように思われるかもしれませんが、ライトノベルスタイルの
元のテキストデータでは、特に戦闘パートだと特殊な用語の
オンパレードで、しかもその作品にしか登場しない用語もあり、
ユーザー辞書を作成してもうまく形態素解析できない場合があるため、
単純な文字列検索で判断することにしました。
このあたりは別途改めて研究したいところです。
日常パート用も同様に、
などの単語リストを作成しておきます。
ただ、たとえば戦闘パート用の「衝撃」などは、「衝撃を受けた」という
日常的な表現にも含まれる場合があり、どこまで登録するかの匙加減は必要です。
■文へのスコア割り当て
その1文が戦闘パートまたは日常パートと判定された場合、
その文に対してスコアを割り当てます。
という文章なら、
と、なり、
■出力するサンプルの作風の決定
今回、この物語自動生成プログラム『ジェネジェネちゃん』を
作るにあたって、以下のような内容のサンプルを出力するという
目標を立てました。
・ライトノベル風
・1万冊分出力する
・現代とファンタジーが入り交じった世界観
・学園が舞台
・登場人物は基本的に学生のみ
・学生達が能力を使って戦うバトルモノ
■パートの書き分け
バトルモノとはいえ、常に戦闘シーン(パート)ばかりではなく、
日常パートもあるのが一般的なので、そのパートの書き分けが
必要です。それに、混じりすぎると文章がグチャグチャになりますしね。
まず、小説1冊分を4章構成と考え、さらに1章は4節に
分かれるとし、1節は15ページほど出力すると決め、
その各節は「戦闘パート」または「日常パート」のどちらかに
なるようにしたいと考えました。
しかし、前回の記事までの手法では、多少の偏りは生まれても、
戦闘・日常パートの違いまで分けてくれるほどの知性(?)は
有していません。
■文の判定
そこで、前段階の文分割の直後の処理として、文の判定を
おこなうことにしました。
まず、戦闘パート・日常パートそれぞれでしかほぼ登場しない
であろう単語のリストを手動で作成します。
戦闘パート用を一部列挙すると、
衝撃, 爆発, 爆散, 炸裂, 一撃, 秘技, 増幅, 突進, 撃破, 咆哮, 唱え, 距離をと, 切り裂, 回避, 剣, 結界, 芒星, 発動
こんな感じです。
それらの文字列が含まれるかどうかで、その1文が戦闘パート
なのかを判定します。
ちなみに、形態素解析した後の形態素で判別する方が
確実のように思われるかもしれませんが、ライトノベルスタイルの
元のテキストデータでは、特に戦闘パートだと特殊な用語の
オンパレードで、しかもその作品にしか登場しない用語もあり、
ユーザー辞書を作成してもうまく形態素解析できない場合があるため、
単純な文字列検索で判断することにしました。
このあたりは別途改めて研究したいところです。
日常パート用も同様に、
洗濯, 調理, テレビ, スマホ, ぬいぐるみ, 焼肉, 宿題, ネコ, ただいま, あひゃひゃ
などの単語リストを作成しておきます。
ただ、たとえば戦闘パート用の「衝撃」などは、「衝撃を受けた」という
日常的な表現にも含まれる場合があり、どこまで登録するかの匙加減は必要です。
■文へのスコア割り当て
その1文が戦闘パートまたは日常パートと判定された場合、
その文に対してスコアを割り当てます。
「後方で爆発が生じた」
という文章なら、
戦闘スコア=1.0、日常スコア=0.0
と、なり、
「彼女はぬいぐるみを抱きかかえた」
なら、
戦闘スコア=0.0、日常スコア=1.0
です。
そして、元のそれらの文章の前後数行に対してもスコアを与えます。
0.3 「その時だった」
0.8 「何者かの気配を感じた」
1.0 「後方で爆発が生じた」
0.8 「受け身を取る暇もなく吹き飛ばされた」
0.3 「息が詰まる」
どのような値を与えるかは、ガウス関数でも線形でも何でもいいかと
思います。行数の範囲もですね。
そして、そのスコアは加算していきます。なので、戦闘パート用の
単語が含まれている2つの文に挟まれた文は、たとえ類似の単語が
含まれていなくても、
1.0+0.3=1.3 「魔術の発動を捉えた」
0.8+0.8=1.6 「その時だった」
0.3+1.0=1.3 「後方で爆発が生じた」
このように高いスコアとなります。
■文の出力生成時の処理
そして、出力生成時の各節で戦闘・日常パートどちらを出力するかを
決めたら、元の文に割り当てられているスコアを参照し、
閾値(たとえば0.5とか)以上ならそのパートだと判断して出力し、
満たしていなければ別の文をランダムで選択して再度判断する処理を
おこなうことで、パートの書き分けが可能となります。
また、戦闘スコアから日常スコアを減算するなどの計算式の調整も
有効だと思います。
■まとめ
基本的にはこのような処理ですが、『ジェネジェネちゃん』では
「食事パート」や「お色気パート」の判断をおこなっていたり、
細かな調整はおこなっています。
同様の方法で、「青空」や「満月」といった単語で時間帯を判断したり、
「教室」「コンビニ」などで場所を判断することもある程度は
出来ると思いますが、「あ、教室にカバンを忘れた」などの文章を
誤認識する可能性もあるため、形態素解析の後に意味解析を
おこなわないのであれば、参考程度の利用にとどめておくのが
好ましいかもしれません(形態素解析が十分に正しくおこなわれているのが
前提ですが)。
もしくは、文章生成時に単語置き換えによって強制的に
整合性を取るのがよさそうですが、その辺りは今後研究して
いきたい部分ではあります。