気がついたら小説の設定を自動生成してくれるコードを書いていたので、大まかなやり方を残してみる
個人利用以外でサービスとしては公開できないところが多いので、スクラップとして残してみることにしました。
まずはどんな感じのファイルが出力されるのかという完成形から。
ネタになりそうなトピックとしては
・キーワードとそこからのanalogyを出力 ... mecab, BERT
・キャラクター ... OCR
あたり。コードは全部Pythonで書いてます。
まずはキーワードの抽出について。
MeCabの生の辞書(mecabの中で使われている単語の表データ)から名詞のみをピックアップする。
参考になるのは
この記事あたり。
pandasかvaexあたりで名詞とされているものを抽出すれば完了
次にキーワードのアナロジーについて。
これは元ネタとなる記事があってそれが以下です。
BERTの学習過程において、「文章の中の単語をUNKNOWN的なものでマスクし、そのマスクの中身に入るのが何かというのを推定する」というタスクが存在する。
よって、そのタスクのアイデアを流用しようというのがメイン。
上記の記事をもとに
・扱いやすいようにクラス化
・マスクの数を可変に
・〇〇といえば××だ、の『××だ』の部分を推定する
ようにした。
なお、CPUでも動作は遅くない。(MBP, 2015モデル)
import torch
from transformers import BertForMaskedLM, BertJapaneseTokenizer
from dataclasses import dataclass
# ここだけ各自で. 初回起動時はcl-tohokuから始まるリモートのパスを入力し、save_pretrainedにMODEL_DIRを指定して保存しておく
from consts import MODEL_DIR
@dataclass
class InferenceModel:
model = BertForMaskedLM.from_pretrained(MODEL_DIR)
tokenizer = BertJapaneseTokenizer.from_pretrained(MODEL_DIR)
def get_analogy_words(self, word, topk=30, num_mask=1):
analogy_words = [] # 〇〇といえばのやつ
mask_tokens = ''.join(
[self.tokenizer.mask_token for _ in range(num_mask)])
text = f'{word}といえば{mask_tokens}だ。'
# teach 'token is where?' for inference
input_ids = self.tokenizer.encode(text, return_tensors='pt')
mask_token_indices = torch.where(
input_ids == self.tokenizer.mask_token_id)[1].tolist()
# inference masked content and pick up topk
output = self.model(input_ids)
pred_ids = output[0][:, mask_token_indices].topk(
topk).indices.tolist()[0]
# print decoded text
for i in range(len(pred_ids[0])):
_ids = []
for pred_id in pred_ids:
_ids.append(pred_id[i])
analogy_words.append(self.tokenizer.decode(_ids))
return analogy_words
続いてOCRについて。
上記までは多分サービスとして公開しても大丈夫なのだけど、
この部分が私が認識する限りでは公開はダメなのでやるなら各自でやっていただければと。
このシリーズの本を購入し、Tesseract OCRを使ってOCRをして、データを構造化しました。
構造化の部分については文字列を扱うだけなので、難しいタスクではないと思います。
また、OCRのソフトウェアの使用方法については以下の記事などを参照してください。
ここまでくれば大体終わりで、あとはそれをもとにゴリゴリにPythonを書いていくだけです。
ほぼほぼランダムで何回もやって思いつくのがあればという感じなので、実行は5秒以内くらいに収まるように調整しています。
また、プロットについては以下の著書で述べられている7つの中からランダムというふうにしています。
lines = []
# (中略)
emotion_before = self.get_emotion()
emotion_after = self.get_emotion()
pos_side = self.get_character_side()
neg_side = self.get_character_side(False)
lines.append(f'created at: {date_str}')
lines.append('')
lines.append('## Summary')
plot = random.choice(PLOTS)
lines.append(plot['title'])
lines.append(plot['desc'])
lines.append('')
lines.append('## Keywords Candidates\n')
if inf_analogy:
for nouns in [self.s_nouns, self.c_nouns]:
_nouns = random.choices(nouns, k=3)
for _noun in _nouns:
words = ','.join(self.inference_analogy(_noun))
lines.append(f'- {_noun} ... {words}')
else:
for nouns in [self.s_nouns, self.c_nouns]:
_nouns = random.choices(nouns, k=10)
lines.append(', '.join(_nouns))
lines.append('')
# 主人公
lines.append('## 主人公')
lines.append('### キャラクター')
lines.append('')
# (中略)
# create file
d_path = f'./short/{date_str}'
os.makedirs(d_path, exist_ok=True)
with open(f'{d_path}/profile.md', 'w') as f:
f.write('\n'.join(lines))