Introduction
こんにちは、ソリューションアーキテクトの大前です。子供の頃、漫画やアニメを好きでよく見ていた方は多いと思います。その中で、「漫画を好きなだけ読めれば良いのに」「物語の続きが読みたい」「もし主人公が違う行動をしたらどうなるのだろう」など思ったことはないでしょうか。そこで、本記事では生成 AI の力を使って、物語生成に挑戦しました。
生成 AI はテキストや画像を自動的に生成するような AI を指します。生成 AI は、基盤モデルと呼ばれる大規模なデータを学習した機械学習モデルを用いて実現されます。AWS では、生成 AI を使ったアプリケーション開発を支援するサービスとして、Amazon Bedrock を提供しています。Amazon Bedrock では Amazon および先進的な AI スタートアップの開発した基盤モデルを使うことができ、テキスト生成や画像生成、埋め込み表現生成といったタスクを行うことができます。
今回は物語作成のために、Anthropic 社が提供する Claude 2.1 というテキスト生成モデルを、挿絵生成のために、Stability AI 社が提供する Stable Diffusion XL 1.0 (SDXL 1.0) という画像生成モデルを使います。また、ナレーションは Amazon Polly という テキスト読み上げのための AWS サービスを使って作成します。これらの AI モデルを組み合わせて絵物語を作っていきたいと思います。
アップデート情報
2024/03/04 より、Claude モデルの最新版である Claude3 Sonnet が Amazon Bedrock 上で利用可能となりました。より精度の高い文章生成が可能となっていますので、ぜひ合わせてお使いいただければと思います。
完成品のイメージ
ご注意
本記事で紹介する AWS サービスを起動する際には、AWS アカウントのサインアップ が必要です。また、料金がかかるため、builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。
builders.flash メールメンバー登録
1. 環境構築
2. プロンプトエンジニアリング
3. プロンプトの探索
4. 生成 AI システムの構築
ここまでで、プロンプトを作ることができました。では実際に一連の流れを JupyterLab 上に実装します。
4-1. ライブラリのインストール
動画を生成するためのライブラリとして、Moviepy をインストールします。
以下のセルを作り、実行します。
Moviepy をインストール
コマンド
!pip install ffmpeg moviepy
4-2. Amazon Bedrock を呼び出す処理の実装
Claude 2.1 モデルや SDXL モデルを何度も呼び出すので、関数として実装します。 Notebook 上で次のようなセルを作り実行します。
import json
import io
import base64
import boto3
from PIL import Image
bedrock_runtime = boto3.client(service_name='bedrock-runtime')
# Claude 2.1 モデルを呼び出す関数
def invoke_claude(prompt: str, max_tokens_to_sample:int=3000) -> str:
# モデルに渡すパラメータを設定する
body = json.dumps({
"prompt": prompt,
"max_tokens_to_sample": max_tokens_to_sample,
"temperature": 0,
"top_k": 250,
"top_p": 1,
})
modelId = 'anthropic.claude-v2:1'
accept = 'application/json'
contentType = 'application/json'
# Bedrock で Claude v2.1 モデルを呼び出す
response = bedrock_runtime.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType)
# Bedrock で生成されたテキストを返す
response_body = json.loads(response.get('body').read())
text = response_body.get('completion')
return text
# StableDiffusion を呼び出す関数
def invoke_sdxl(prompt: str, seed:int=0) -> Image:
# モデルに渡すパラメータを設定する
body = json.dumps({
"text_prompts": [{"text": prompt}],
"cfg_scale": 10,
"seed": seed,
"steps": 50
})
modelId = "stability.stable-diffusion-xl-v1"
accept = "application/json"
contentType = "application/json"
# Bedrock で Stable Diffusion モデルを呼び出す
response = bedrock_runtime.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType)
# Bedrock で生成された画像を PIL.Image 形式で返す
response_body = json.loads(response.get("body").read())
base_64_img_str = response_body.get("artifacts")[0].get("base64")
image = Image.open(io.BytesIO(base64.decodebytes(bytes(base_64_img_str, "utf-8"))))
return image
4-3. 物語のあらすじを作成する処理の実装
以下のようにして、物語のあらすじを作成する処理をセル上に実装し、実行します。
プロンプトを定義する箇所
実行コード
prompt_make_plot = '''
あなたはセンスに溢れた気鋭に溢れる情熱的な作家です。Humanがテーマを与えるので、膨らませて、物語のあらすじを考えてください。
あらすじは物語を起承転結のシーンごとに分け、情景や描写を詳しく書くようにしてください。
起の部分では、登場人物の人となりを説明し、読者が主人公たちに親近感を抱くような情景を描写してください。
承の部分では、主人公は誤ちを犯しますが、途中でそのことに気づき、新たな視点で物事に取り組むようになります。
転の部分では、承で培った経験をもとに、困難を乗り越えます。
結の部分では、主人公がテーマを達成した様子を描写します。
以上のような形であらすじを考案してください。シーンは最低でも8個以上生成してください。
また、出来上がったあらすじはjson形式でexampleタグに示すような形式で、plot要素の中で各シーンをリスト形式で出力してください。また、json以外の文字列は出力しないでください。
<example>
{{
'plot': [
'森の中でクマさんにであう',
'クマさんが追いかけてくる',
'実はクマさんは落とし物を拾っていた',
'笑顔で一緒に踊る'
]
}}
</example>
Human: {instruction}
Assistant: {{
'''
instruction = '昔話の桃太郎を題材に、鬼と人間が種族を超えて手を取り合うことの大切さを描写してください。'
プロンプトを実行するセル
コード
invoke_result = invoke_claude(prompt_make_plot.format(instruction=instruction))
plot = json.loads('{' + invoke_result)
# 動作確認用に出力する
print(plot)
> {'plot': ['桃太郎は鬼ヶ島に向かう途中、仲間たちと出会う', '鬼ヶ島に着くと大きな鬼が現れる', '桃太郎は鬼と戦おうとするが、鬼は人間を攻撃しようとしない', '桃太郎は鬼が人間を食べない理由を尋ねる', '鬼は以前人間と戦争をしていたが、戦争の惨さを知り、人間と仲良くしたいと思うようになったと言う', '桃太郎は鬼の気持ちを理解し、手を取って踊り始める', '鬼と人間は笑顔で踊り、種族の壁を超えて仲良くなる', '桃太郎は鬼ヶ島からの帰り道、人間と鬼が仲良く暮らせる方法を考え始める']}
4-4. 物語の各場面を記述する処理の実装
あらすじをもとに、各場面ごとの物語を生成していきます。前後の表現の一貫性を保つために、チャットのように、前の出力結果を再度入力として使いつつ次の出力を生成させていきます。入力プロンプトはこの図のように、前段の出力が積み重なるような形になります。
実装として、最初のシステムプロンプトと、毎回の更新部分のプロンプトを別で定義し、ループの中で更新部分を追加していくという形で行います。
動作イメージ
物語を作成してくださいHuman: 桃太郎は鬼ヶ島に向かう途中、仲間たちと出会うAssistant: ある日のこと、桃太郎は鬼ヶ島に向かう旅に出かけました...
Human: 鬼ヶ島に着くと大きな鬼が現れるAssistant:
物語を生成
物語を記述させる処理は以下のようになります。セルにコピーペーストし、実行します。
# システムプロンプトの定義
prompt_make_story = '''
あなたはセンスに溢れた気鋭に溢れる情熱的な作家です。子ども向けに読み聞かせできるような物語を描いてください。
Humanが物語のテーマを<Theme></Theme>タグの中で、物語の流れを端的に示したあらすじを<Plot></Plot>タグの中で与えます。全体あらすじの情報を踏まえつつ、<Scene></Scene>タグで示すシーンのあらすじをもとに、情景や背景、登場人物の心情やセリフを記述することで、読み手に伝わりやすいような臨場感のある文章を考えてください。ただし、物語の文章のみを出力し、その他の文字列は出力しないでください。また、指定されたシーンの部分のみ描画してください。文章はですます調で出力してください。
また、出来上がった文章はjson形式で、Scene要素の中にダブルクォーテーションで囲んで出力してください。
<Example>タグに生成したい文章の例を示します。
<Example>
{{
"Scene": "むかしむかし、あるところにちっちゃな、かわいい女の子がおりました。その子は、ちょっと見ただけで、どんな人でもかわいくなってしまうような子でしたが、だれよりもいちばんかわいがっていたのは、この子のおばあさんでした。おばあさんは、この子の顔を見ると、なんでもやりたくなってしまって、いったいなにをやったらいいのか、わからなくなってしまうほどでした。
あるとき、おばあさんはこの子に、赤いビロードでかわいいずきんをこしらえてやりました。すると、それがまたこの子にとってもよくにあいましたので、それからは、もうほかのものはちっともかぶらなくなってしまいました。それで、この子は、みんなに「赤ずきんちゃん」「赤ずきんちゃん」とよばれるようになりました。"
}}
</Example>
<Example>
{{
"Scene": "そこで、寝床のところへいって、カーテンをあけてみました。すると、そこにはおばあさんが横になっていましたが、ずきんをすっぽりと顔までかぶっていて、いつもとちがった、へんなかっこうをしています。
「ああら、おばあさん、おばあさんのお耳は大きいのねえ。」
「おまえのいうことが、よくきこえるようにさ。」
「ああら、おばあさん、おばあさんのお目めは大きいのねえ。」
「おまえがよく見えるようにさ。」
「ああら、おばあさん、おばあさんのお手ては大きいのねえ。」
「おまえがよくつかめるようにさ。」
「でも、おばあさん、おばあさんのお口はこわいほど大きいのねえ。」
「おまえがよく食べられるようにさ。」
オオカミはこういいおわるかおわらないうちに、いきなり寝床からとびだして、かわいそうな赤ずきんちゃんを、ぱっくりとひとのみにしてしまいました。"
}}
</Example>
Human:
<Theme>
{instruction}
</Theme>
<Plot>
{plot}
</Plot>
<Scene>
{scene}
</Scene>
Assistant: {{
'''
# 更新部分のプロンプト定義
prompt_update = '''
{prev_output}
Human:
<Scene>
{scene}
</Scene>
Assistant: {{
'''
# 各あらすじごとにプロンプトを更新して場面を生成させる
scenes = []
prompt_next = ''
invoke_result = ''
for scene in plot['plot']:
# 1つ目のあらすじの際はシステムプロンプトのみを入力する
if prompt_next == '':
prompt_next = prompt_make_story.format(instruction=instruction, plot=str(plot), scene=scene)
# 2つ目以降は前回の出力を使ってプロンプトを更新する
else:
prompt_next += prompt_update.format(prev_output=invoke_result, scene=scene)
# Claude モデルを呼び出す
invoke_result = invoke_claude(prompt_next)
# 呼び出し結果をscenesリストに保存
result = json.loads('{' + invoke_result.replace('\n', ''))
scenes.append(result['Scene'])
print(scenes)
実行中エラーが出た場合
実行中、乱用検知機能などでエラーが出る場合があります。その場合は再度実行してください。
4-5. イラストを生成する処理の実装
各シーンごとに SDXL モデルを使って挿絵を生成します。雰囲気を統一するため、スタイルを色鉛筆画風として指定します。そのために、‘a color pencil painting' という記述をプロンプトの前に挿入するようにします。また、後ほど生成した画像を使いたいため、連番画像で保存します。
イラストの生成
処理としては以下のようになります。新しいセルを作成し、実行します。
# StableDiffusion に指示を出すためのプロンプト
prompt_draw_image = '''
絵本の挿絵を作りたいです。そのために画像生成モデル向けのプロンプトを考える必要があります。
挿絵として、色鉛筆を使った絵を採用したいと思います。
Humanが物語全体のあらすじを<Plot>タグで、該当のシーンを<Scene>タグで明示します。与えられたシーンでの情景を表すような文章を簡潔に英語で1文程度で生成してください。プロンプトを作る上で、以下の点に注意してください。
* 写っている物体や人物を明確に指示する。
* 各物体がどのような形なのか明確に提示する。
* 人物はどのような服を着ていて何をしようとしているのか提示する。
* 以下の中から、関連しそうな Finishing Touch をピックアップして追加する。Highly-detailed, surrealism, trending on artstation, triadic color scheme, smooth, sharp focus, matte, elegant, illustration, digital paint, dark, gloomy, octane render, 8k, 4k, washed-out colors, sharp, dramatic lighting, beautiful, post-processing, picture of the day, ambient lighting, epic composition
* コンプライアンスに注意する
また、出力するプロンプトはjson形式で、prompt要素内に書き出すようにしてください。
Exampleタグで適切な出力の例を示します。
<Example>
{{
"prompt": "a panda by Leonardo da Vinci and Frederic Edwin Church, highly-detailed, dramatic lighting"
}}
</Example>
<Example>
{{
"prompt": "A professional color photograph of a bearded man on the sidewalk"
}}
</Example>
Human:
<Plot>
{plot}
</Plot>
<Scene>
{scene}
</Scene>
Assistant: {{
'''
# イラストのスタイルを指定するプロンプト
style_image = 'a color pencil painting'
for index, scene in enumerate(scenes):
prompt = prompt_draw_image.format(plot=str(plot), scene=scene)
# StableDiffusion 向けのプロンプトを生成する
claude_result = invoke_claude(prompt)
# 鉛筆画を生成するため、プロンプトを追加した上で SDXL を呼び出す
prompt_for_sdxl = json.loads('{' + claude_result.replace('\n', ''))['prompt']
image = invoke_sdxl(f'{style_image}, {prompt_for_sdxl}')
# 生成した画像を表示し、保存する。
image.show()
print(scene)
image.save(f'{index}.png')
4-6. ナレーションを生成して動画として保存する
最後に、作った物語と挿絵を組み合わせて、読み聞かせ動画を作成します。
テキストから音声を生成するためには、Amazon Polly という AI サービスを使います。Polly ではテキストを API で受け渡し、AI で読み上げ、音声ファイルを生成することができます。
動画の作成には、Moviepy という Python ライブラリを使います。各シーンごとに音声を作成し、Moviepy を使って画像と組み合わせて、読み聞かせ動画を作成します。
ナレーションを生成
まずはナレーションを生成します。以下のコードをコピーして実行してください。
# polly の boto3クライアントを作成します
polly_client = boto3.client('polly')
# 連番で音声ナレーションを生成します
for index, scene in enumerate(scenes):
result = polly_client.synthesize_speech(
Engine="neural",
VoiceId='Kazuha',
OutputFormat='mp3',
Text = scene)
with open(f'{index}.mp3', 'wb') as f:
f.write(result['AudioStream'].read())
動画を生成
次に、動画を生成します。各シーンごとのイラストとナレーションファイルを統合します。以下のコードをセルにコピーして実行してください。
from moviepy.editor import *
video_clips = []
for index in range(len(scenes)):
image = ImageClip(f'{index}.png')
audio = AudioFileClip(f'{index}.mp3')
video = image.set_duration(audio.duration+1)
video = video.set_audio(audio)
video_clips.append(video)
final_video = concatenate_videoclips(video_clips)
# output.mp4 へ出力する
final_video.write_videofile("output.mp4", fps=24)
ファイルをダウンロード
上記のセルを実行すると output.mp4 ファイルに生成結果が出力されます。再生のためにはファイルをダウンロードする必要があります。
左側のファイル一覧ペーンから「output.mp4」を右クリックし、「Download」を選択します。
VLC Media Player などの mp4 ファイルを再生できるプレイヤーを使い、動画を再生します。
※プレイヤーによっては音声が再生されない場合があります。その際は、他のプレイヤーでの再生をお試しください。
このようにしてナレーション付きの絵芝居を作ることができました。

5. 環境の削除
意図しない課金を防ぐため、Notebook インスタンス を削除します。
6. まとめ
本記事では、Amazon Bedrock の各種モデルと Amazon Polly を組み合わせて、絵芝居を自動生成させるような生成 AI アプリケーションを構築する方法をご紹介しました。生成される画像はやや荒削りな部分もございますが、プロンプトを工夫したり画像生成モデルをファインチューニングしたりすることで、より良い結果を出力するように工夫することもできます (ファインチューニングで猫の画像を生成するモデルを構築するサンプル)。 ぜひさまざまな方式をお試しください。また、冒頭でご紹介した Claude 3 など、異なる文章生成モデルを使うことで、より良い文章が生成できる可能性もございます。ぜひお試しください。
このように、各種 AWS サービスを組み合わせることにより、本格的な生成 AI アプリケーションを簡単に構築することができます。今回は Prompt chaining のために Jupyter Notebook を使いましたが、実運用の際には、AWS Lambda や Amazon Step Functions、Agent for Amazon Bedrock といったサービスを活用いただくのが便利です。構成の支援をいたしますので、アイデアを思いついた方は、ぜひ AWS のソリューションアーキテクトにご相談ください。
筆者プロフィール
アマゾン ウェブ サービス ジャパン合同会社
ソリューションアーキテクト
主に製造業のお客様に対して技術支援を行なっています。趣味はバイクで、好きな AWS サービスはAmazon SageMaker, Amazon Bedrock です。

Did you find what you were looking for today?
Let us know so we can improve the quality of the content on our pages