🦜

【Paperspace】AI BunChoに使用されるモデルで小説を書く

2023/10/12に公開2

はじめに

AI BunChoというAI小説作成支援サービスがあります。
今回は、AI BunChoに使用されているモデルをPaperspace上で試してみました。

Google Colab上で試している記事がとても参考になりました。

https://note.com/npaka/n/n4bbc4a9f612e

今回は、ChatGPTと会話しながら、メタ情報をインプットさせてから物語を生成するようにコードを作成します。

ライブラリのインストール

ジュピターノートブックにコードを追加して、以下のコマンド実行します。

#必要なライブラリをインストール
!pip install transformers sentencepiece accelerate

モデルのロード

from transformers import GPTJForCausalLM, AlbertTokenizer
import torch

#モデルのロード
tokenizer = AlbertTokenizer.from_pretrained('AIBunCho/japanese-novel-gpt-j-6b', keep_accents=True, remove_space=False)
model = GPTJForCausalLM.from_pretrained("AIBunCho/japanese-novel-gpt-j-6b", torch_dtype=torch.float16, low_cpu_mem_usage=True)
model.half()
model.eval()
if torch.cuda.is_available():
    model = model.to("cuda") 

物語の生成

コードは以下の記事を参考にしました。

https://note.com/shi3zblog/n/n0eb77bae5a8a

charaに登場人物名、story(" ")に冒頭の文章を入力します。

def b(prompt, seed_value):
    torch.manual_seed(seed_value)  
    input_ids = tokenizer.encode(
        prompt,
        add_special_tokens=False,
        return_tensors="pt"
    ).cuda()
    tokens = model.generate(
        input_ids.to(device=model.device),
        max_new_tokens=320,
        temperature=0.8,
        top_p=0.9,
        repetition_penalty=1.5,
        do_sample=True,
        pad_token_id=tokenizer.pad_token_id,
        bos_token_id=tokenizer.bos_token_id,
        eos_token_id=tokenizer.eos_token_id
    )
    out = tokenizer.decode(tokens[0], skip_special_tokens=True)
    print(out)
    return out

def story(begin):
    t=begin
    chara = ["ネーラ","セリオ"]
    for i in range(10):
        s = b(t, i)
        t = f"{s}\n{chara[i%2]}"
        if i%3==0:
            t+="「"
        else:
            t+="は"
    print(s)
    return s
story("混沌とした世界。燃え盛る村々。")

上記のコマンドを実行すると、10回ループを繰り返しながら少しずつ文章が生成されます。
文章が長くなりますので最後の出力結果が以下になります。

# 出力結果
混沌とした世界。燃え盛る村々。泣き叫ぶ人々、そして人とは思えない奇形と化した怪物達...... ネーラ「パパ! セリオは!?」 ネーラは助けを呼ぶために家を出たが焼け焦げて崩れた家の中で瓦礫に潰された父親を発見した セリオ「俺はもう助からない......」 ネーラは泣きながらそう言うも父親が覆い被さって守る セリオはそんな2人をただ見ていた ネーラ「なんで!? セリオは助かるよね!」 私は必死に助けを求めようとするけれども喉が詰まって上手く声が出せない ネーラはそれでも叫ぼうとするけれど『ドーン!』という音と共に崩れ落ちてきた建物によって私は意識を失った

シード値が固定されていますので、再度コマンドを実行しても同じ文章が生成されます。

コード内にある各パラメータについてChatGPTに聞いてみました。

  • max_new_tokens: 生成するトークンの最大数。
  • temperature: 生成の際のランダム性を制御。値を上げることで、出力の多様性を増加。
  • top_p: 値を下げることで、より一貫性のある出力を得る。
  • repetition_penalty: 繰り返しを減少させるためのペナルティを適用。
  • do_sample=True: サンプリングモードでテキストを生成。
  • pad_token_id, bos_token_id, eos_token_id: それぞれ、パディング、開始、終了のトークンIDを指定します。

文章の出力を変化させたい場合は、temperature、top_p、repetition_penaltyを調整していきましょう。

メタ情報を記憶させて出力させる

先ほどのコードですと、物語の背景や前提条件が無いまま出力されますので意図しない出力結果になることがあります。

今度はシード値をランダムにして、初期のメタ情報(事前の設定)を入れて出力するようにコードを書き替えてみました。(追加でシード値もランダムになるように修正しています)

initial_promptに、メタ情報を入力します。(こちらは出力されません)

continued_promptに、出力させたい文章の冒頭部分を記入します。

def b(prompt, seed_value):
    torch.manual_seed(seed_value)  # シードの変更
    input_ids = tokenizer.encode(
        prompt,
        add_special_tokens=False,
        return_tensors="pt"
    ).cuda()
    tokens = model.generate(
        input_ids.to(device=model.device),
        max_new_tokens=32,
        temperature=0.6,
        top_p=1.5,
        repetition_penalty=1.8,
        do_sample=True,
        pad_token_id=tokenizer.pad_token_id,
        bos_token_id=tokenizer.bos_token_id,
        eos_token_id=tokenizer.eos_token_id
    )
    out = tokenizer.decode(tokens[0], skip_special_tokens=True)
    return out

def story3(begin, is_meta=False):
    t = begin
    story_output = ""  # 出力用の変数を初期化
    chara = ["ネーラ", "セリオ"]
    quote_open = False  # 引用符が開かれているかどうかを追跡
    for i in range(10):
        s = b(t, i)
        if not is_meta:  # メタ情報の場合は出力に追加しない
            story_output += s  # 出力用の変数に追加
        t = f"\n{chara[i%2]}"
        if i % 3 == 0:
            if quote_open:  # 既に引用符が開かれている場合は閉じる
                t += "」"
                quote_open = False
            else:  # 引用符が開かれていない場合は開く
                t += "「"
                quote_open = True
        else:
            # t+="は" この行を削除またはコメントアウトする
            pass  # 何もしない
    if quote_open:  # 最後のループが終了した後でも引用符が開かれている場合は閉じる
        t += "」"
    return story_output

# メタ情報としてstory_output1を生成
initial_prompt = "異世界の大陸、魔法と異種族が存在する中世ヨーロッパ風の世界。"
story_output1 = story3(initial_prompt, is_meta=True)

# story_output2を生成
continued_prompt = "混沌とした世界。燃え盛る村々。"
story_output2 = story3(continued_prompt)

# 出力文を結合
final_output = story_output2
print(final_output)

メタ情報を記憶?させて実行した結果がこちら。

# 出力結果
混沌とした世界。燃え盛る村々。泣き叫ぶ人々、そして人とは思えない形相で襲い掛かる化け物達ネーラ「でも、それは......っ」セリオに、そしてこの村に住む人々の全てが。ネーラとの接触を図った。セリオ」ネーラは、気まずそうに顔を背けた。セリオは、そのままの勢いで地面を蹴って飛び上がる。ネーラ「!」セリオが、その光の中から出てきた。ネーラの首領、アシド・バール。......いや「ディケイダー」がその姿を現した

ディケイダーって誰だよ…

先ほどの出力結果と比べると、文章が異なっていますね。
所々、セリフの引用符が閉じられていなかったりしますが、ちゃんと生成されました。

まとめ

Paperspace上でAI BunChoに使用されているモデルを試してみました。
シード値の変更、パラメータの調整やコードの修正をすることで、さらに面白い文章を出力してくれる可能性があります。

先ほどのコードを使用して出力した文章をChatGPTに編集してもらった小説は以下に投稿していますので、よかったらチェックしてみてください。

https://www.aipictors.com/works/142407/

Discussion

yKesamaruyKesamaru

素晴らしい記事をありがとうございます。

ディケイダーでフキました🤣💦
夢がありますね。

toki_mwctoki_mwc

ありがとうございます!面白い生成が出るのも楽しみの一つですね😆