Open6

気がついたら小説の設定を自動生成してくれるコードを書いていたので、大まかなやり方を残してみる

yasudeyasude

個人利用以外でサービスとしては公開できないところが多いので、スクラップとして残してみることにしました。

まずはどんな感じのファイルが出力されるのかという完成形から。

yasudeyasude

ネタになりそうなトピックとしては

・キーワードとそこからのanalogyを出力 ... mecab, BERT
・キャラクター ... OCR

あたり。コードは全部Pythonで書いてます。

yasudeyasude

まずはキーワードの抽出について。

MeCabの生の辞書(mecabの中で使われている単語の表データ)から名詞のみをピックアップする。
参考になるのは
https://qiita.com/hiro0217/items/cfcf801023c0b5e8b1c6
この記事あたり。
pandasかvaexあたりで名詞とされているものを抽出すれば完了

yasudeyasude

次にキーワードのアナロジーについて。
これは元ネタとなる記事があってそれが以下です。
https://qiita.com/mitchy3yos/items/16ece3127252df772eb1

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

yasudeyasude

続いてOCRについて。
上記までは多分サービスとして公開しても大丈夫なのだけど、
この部分が私が認識する限りでは公開はダメなのでやるなら各自でやっていただければと。

https://www.amazon.co.jp/gp/product/B01MPWD2TT

このシリーズの本を購入し、Tesseract OCRを使ってOCRをして、データを構造化しました。
構造化の部分については文字列を扱うだけなので、難しいタスクではないと思います。

また、OCRのソフトウェアの使用方法については以下の記事などを参照してください。
https://qiita.com/henjiganai/items/7a5e871f652b32b41a18

yasudeyasude

ここまでくれば大体終わりで、あとはそれをもとにゴリゴリにPythonを書いていくだけです。
ほぼほぼランダムで何回もやって思いつくのがあればという感じなので、実行は5秒以内くらいに収まるように調整しています。

また、プロットについては以下の著書で述べられている7つの中からランダムというふうにしています。
https://www.amazon.co.jp/dp/B06XRBQV6F

        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))