生成 AI を使えば寂しくない !? 

Amazon SageMaker で 生成 AI を Fine Tuneして連歌を嗜んで寂しさを紛らわせてみた

2023-08-02
日常で楽しむクラウドテクノロジー

呉 和仁

Builder の皆様こんにちは ! 機械学習ソリューションアーキテクトの呉です。
 
みんな気分を紛らわしたい時どうしていますか !?
いやいや、気分を紛らわしたい時なんかないよ、みたいな方もいらっしゃるかもしれませんが、私は仕事中であっても何かしらのストレス解消を求めてます。ストレス解消には人と話すのが良い、と聞いたこともありますが、 私はあまり友人がいないぼっちな上、数少ない同年代の友人も子育てで手一杯だったり、と、なかなか人と話す機会を作れないものです。
 
そんなことを考えていたら、「そうだ、AI と会話すればいいじゃん !」と技術者らしい狂気を帯びてきました。せっかくなので AI と会話で雅に遊ぼうと思い、 連歌 (誰かが上の句を詠んだら他の誰かが下の句を詠む遊び) に挑戦してみました。

このクラウドレシピ (ハンズオン記事) を無料でお試しいただけます »

毎月提供されるクラウドレシピのアップデート情報とともに、クレジットコードを受け取ることができます。 


1. 出来上がったものはこちら

成果物はこちらです。
※音声が出ますので、音の再生が許される環境であれば、ぜひ音もお楽しみください。

私がダイエット中の夜の辛さを上の句として詠い、古典はよくわからないですが AI が今夜は月も見てみぬふりをしてくれる (から食べてもぃぃょ)、と雅に下の句を詠って甘やかしてくれています。

昨今生成 AI というのが流行っています。生成 AI は Text2Text (テキストからテキストを生成) や Text2Image (テキストから画像を生成) がメジャーどころです。私もこのビッグウェーブに乗り遅れたくなかったので、今回は Text2Text の生成 AI をカスタマイズして、AI と連歌を楽しみました。


2. Text2Text の生成 AI とは ?

Text2Text の生成 AI とは、テキストを渡すことでテキストを生成させるタスクを実行できる AI のことです。GPT(Generative Pre-trained Transformer) と呼ばれるシリーズの AI が有名ですね。先程の動画でも GPT 系の AI を使用しています。

さて、GPT は Generative Pre-trained Transformer というだけあって、事前学習したモデルですが、なにを事前に学習しているのでしょうか。よく “AI” と、もやっとした言葉を使ってますが、GPT は機械学習であり、他の機械学習モデルと同じ用に input された情報から予測結果を output しています。

何を予測するように学習したかというと、文章の次に出てくるトークン (≒単語) を予測するように学習します。このようなイメージです。

input output
吾輩
吾輩は
吾輩は猫
我輩は猫で ある

このように次にどんな単語が来るのか(※)を学習していきます。

※もちろん、「吾輩は」の後に「犬」や「人」が来る可能性もありますが、世界にたくさんある文章の中から一番確率が高いものは「猫」であるため、猫を出力するように学習されているモデルが多いです。学習自体はインターネットのテキストを使って学習することが多いため (japanese-gpt-neox-3.6b の場合は日本語 wikipedia などを用いて事前学習)、将来、冬目漱石さんが「吾輩は犬だった」というベストセラーを出したら、「犬」を出力する日がくるかもしれませんね。

このように大量のデータを学習したモデルを Base Model と言います。ただし、Base Model をそのままアプリケーションや業務に組み込むのは難しいです。なぜなら、例えば今回取り扱っている GPT 系のモデルであれば、次のトークンを予測し続ける(= 与えた文章の続きを書く) ことしかできないので、よく使われるチャットのような回答をすることができません。

チャットのように人間が命令を出して、それに対して適切な回答をするようにするには、Base Model に追加で命令文と回答 (正解データ) をセットで学習し、命令文から回答を予測できるようにチューニングします。この学習過程を Instruction Tuning と言い、出来上がったモデルを Base Model に対して Instruction Model と言います。

備考ですが、機械学習の文脈ではBase Model で行ったトレーニングを、与えたデータだけを学ぶという意味で自己教師あり学習(Self-Supervised Learning)と言い、Instruction Model で行ったトレーニングはよく聞く教師あり学習 (Supervised Learning) と言います。自己教師あり学習は略して SSL と言ったりしますが、通信の暗号化以外の SSL もぜひ覚えてくださいね !

具体的に Instruction Tuning で与えるデータは以下のようなイメージです。

ユーザー: 以下の日本語の文章を英語に翻訳してください。

吾輩は猫である。名前はまだない。

システム: I am a cat. As yet I have no name.

そして、I am a cat. As yet I have no name. の部分を予測するように学習を行い、ユーザーの命令に対して適切な回答ができるようになっていきます。

とはいえ、皆様全員がこのような学習を行うことはなく、商用利用可能な Base Model も Instruction Model も公開されていますので、まずはそれらの使い方だけ覚えれば OK です。


3. 日本語対応のモデルを利用してみる

さて、公開されているモデルを早速使いましょう。日本語を使用できるモデルはいくつかあります。特に 2023 年 5 ~ 6 月は熱い月で、CyberAgent 社が Open-CALM を、rinna 社が japanese-gpt-neox-3.6b を、mosaicml 社が mpt-7b と mpt-30b を、そして TII 社が Falcon-40b を出しました (Base モデルだけでなく Instruction Model も出していることが多いです)。さらに、mosaicml 社が Dolly というデータセットやモデルを出している Databricks 社に買収されたりと、界隈は目まぐるしく動いています。

ちなみにモデルに Xb という表記がありますが、この Xb というのはモデルのパラメータ数を表しており、7b であれば 7 billion (70 億) のパラメータを持つ、という意味です。決して鉛筆の芯の濃さではありません。一般にパラメータは大きければ大きいほど精度は上がる一方、必要なコンピューティングリソースも大きくなっていきます。

今回は rinna 社の japanese-gpt-neox-3.6b-instruction-ppo というモデルを使ってみます。文字通り 36 億パラメータを持つ Instruction Model です。

モデルの使用方法ですが、なにも難しいことはありません。AWS で公開している公式のノートブックが GitHub にあります。Amazon SageMaker Studio で実行しましょう。まずはリポジトリを Amazon SageMaker Studio で Clone します。

なお、Amazon SageMaker Studio の環境準備については Amazon SageMaker Immersion Day の 「12. 以下のような新しいWebタブにリダイレクトされます。」まで実行すれば環境準備ができます。

ターミナルから以下のコマンドを打ち込みます。

git clone https://github.com/aws-samples/aws-ml-jp.git

clone が終わったら以下のパスのノートブックを開きます。

aws-ml-jp/tasks/generative-ai/text-to-text/inference/deploy-endpoint/Transformers/rinna-3.6b-instruction-ppo_Inference.ipynb

そして、こちらのスクリーンショットの部分まで上から順にセルを実行すればモデルがデプロイ (コンピューティングリソースが立ち上がってモデルが利用可能な状態になる) されます。

さて、早速モデルを使ってみましょう。モデルに入力するテキストのことをプロンプトと言います。昨今プロンプトエンジニアリングなんて言葉を聞くようになりましたが、そのプロンプトです。プロンプトエンジニアリングは、このプロンプトにどのようなテキストを入れればモデルから欲しい結果を得られるかの技術です。先程まで「吾輩は猫である」のネタを使ってきたので、デプロイしたモデルの使い方の紹介がてら「吾輩は猫である。名前はまだ」の続きを出させてみましょう。

セルに以下のコードを入力して実行してみます。

prompt = '''吾輩は猫である。名前はまだ'''
request = {
    'prompt' : prompt,
    'max_new_tokens' : 16,
    'do_sample' : True,
    'temperature' : 0.01,
}
output = predictor.predict(request)['result']
print(f'出力結果 : {output}')
出力結果 : ない。猫は、猫の飼い主が猫に名前をつけることになっている

使い方はこのように prompt に入力するテキストを格納して、predict メソッドで出力結果を得ます。また、AI に渡すパラメータはいくつかあるのですが、max_new_token が AI に出力させるテキストの長さです (他はこの記事では扱いませんが、どれだけ AI が出力するテキストのバラエティに関わるパラメータです)。出力結果を見ると、「ない。」までは合ってますが、その後は小説というよりは一般論を生成してますね。

さて、モデルから良い結果を引き出すためにプロンプトエンジニアリングを試してみます。ここでは以下の 3 つのやり方を試します。

  1. モデルのトレーニング時のテンプレートに則ったプロンプトを試す
  2. 検索結果で外部の情報を使ったプロンプトを試す(RAG: Retrieval Augmented Generation)
  3. いくつかの例示を与えたプロンプトを試す(Few-Shot)

3-1. プロンプトエンジニアリングその 1 : テンプレートに則った書き方をする

プロンプトエンジニアリングの中の Basic なテンプレートに則った書き方を試してみましょう。japanese-gpt-neox-3.6b-instruct-ppo では、以下のようにユーザーとシステムが対話する形式 (また、改行は <NL> という記号を使う) で学習しているので、この記法に則って続きを書かせてみましょう。

I/O Format より引用
(クリックすると拡大します)

prompt = '''ユーザー: 以下は夏目漱石の小説「吾輩は猫である」の書き出しである。続きを書いてください。
吾輩は
システム:'''.replace('\n','<NL>')
request = {
    'prompt' : prompt,
    'max_new_tokens' : 32,
    'do_sample' : True,
    'temperature' : 0.01,
}
output = predictor.predict(request)['result']
print(f'出力結果 : {output}')
出力結果 : 猫である。名前はまだない。今日は寒い。猫は毛を逆立てて、暖かい場所を探している。暖かい場所は、家の

「名前はまだない。」まで正しく出力しました。続きはオリジナルのものとは違いますが、少し小説っぽい書きっぷりになりました。このようにプロンプトを工夫することで欲しい結果を得やすくなります。さて、本題の短歌に戻ります。まず、japanese-gpt-neox-3.6b-instruct-ppo が短歌を理解しているかを確認しましょう。


prompt = '''ユーザー: 短歌って知ってますか?
システム:'''.replace('\n','<NL>')
request = {
    'prompt' : prompt,
    'max_new_tokens' : 1024,
    'do_sample' : True,
    'temperature' : 0.01,
}
output = predictor.predict(request)['result']
print(f'出力結果 : {output}')
出力結果 : はい、もちろんです。短歌とは、日本の伝統的な詩の一種です。日本の文化や伝統において重要な役割を果たしています。短歌は、日本の文学や文化の中心的な存在であり、日本の文化や伝統の重要な部分を占めています。</s>

なんかそれっぽいものを返してきましたね。でもどこか知ったかぶりをしている気がします。今までの会話の履歴も加えて、もう少し詳しく短歌のルールについて聞いてみましょう。

prompt = '''ユーザー: 短歌って知ってますか?
システム: はい、もちろんです。短歌とは、日本の伝統的な詩の一種です。日本の文化や伝統において重要な役割を果たしています。短歌は、日本の文学や文化の中心的な存在であり、日本の文化や伝統の重要な部分を占めています。
ユーザー: 短歌には文字数制限があると思うのですが、詠み方のルールを教えてください。
システム: '''.replace('\n','<NL>')
print(prompt)
request = {
    'prompt' : prompt,
    'max_new_tokens' : 128,
    'do_sample' : True,
    'temperature' : 0.01,
}
output = predictor.predict(request)['result']
print(f'出力結果 : {output}')
はい、短歌は、五・七・五の音節で作られる詩の一種です。五・七・五の音節は、俳句や連歌などの日本の伝統の形式に非常に似ています。</s>

それ俳句ゥゥゥゥッ ! もしくは川柳ゥゥゥゥッ ! その後の七・七が大切ゥゥゥゥゥッ ! などと石仮面をかぶっちゃったパンの枚数を数えられない人みたいな絶叫をしてしまいましたが、少し怪しげな知識は持っていることがわかりました。

3-2. プロンプトエンジニアリングその 2 : RAG を用いてみる

次のプロンプトエンジニアリングですが、AI に前提となる知識を渡して得たい結果を出力させる RAG (Retrieval Augmented Generation) という方法もあります。

それでは例として、Wikipedia から短歌の情報を取得して、AI に渡してみましょう

※RAG については 高精度な生成系 AI アプリケーションを Amazon Kendra、LangChain、大規模言語モデルを使って作る が参考になります。

!pip install wikipedia # wikipedia の情報を取得できるライブラリのインストール
import wikipedia
wikipedia.set_lang("ja")
wp = wikipedia.page("短歌")
description = wp.summary
print(f'Wikipedia のサマリー: {description}')
Wikipedia のサマリー: 短歌とは、和歌の一形式で五・七・五・七・七の五句体の歌体のこと。ただし短歌は古より詠まれている歌体であるが、この項目では明治以降の短歌すなわち近代短歌と現代の短歌について取り上げる。つまりは五・七・五・七・七とは限らない。

Wikipedia から欲しい情報が得られましたのでその情報をそのままモデルに入れてしまいましょう。

prompt = f'''ユーザー: 短歌のルールを教えてください。
ただし、以下の情報を使ってください。
{description}
システム: '''.replace('\n','<NL>')
request = {
    'prompt' : prompt,
    'max_new_tokens' : 24,
    'do_sample' : True,
    'temperature' : 0.01,
}
output = predictor.predict(request)['result']
print(f'出力結果 : {output}')
出力結果 : はい、五・七・五・七・七の五句体の歌体が短歌の正式な形式である

少し偉そうなのが気になりますが、 wikipedia の情報を使うことで、求めていた五・七・五・七・七という結果を得ることができました。

今回は RAG をすべて自前で実装しましたが、一般的には LangChain というライブラリを用いて行うことが多いです。というのも LangChain の機能の 1 つにテンプレートの管理というのがあり、今回の 「以下の情報を使ってください」のようなWikipedia の検索結果を後から埋め込むプレースホルダー機能などを持っているためです。今回は触れませんが、SageMaker のエンドポイントにも対応しているので、ぜひお試し下さい。以下が参考資料です。

LangChain の参考資料とコードはこちらがオススメです。

3-3. プロンプトエンジニアリングその 3 : Few-shot

さて、Few-shot の話の前に試しておきたいことがあります。素のモデルの短歌の知識は怪しいですが、短歌の下の句を読んで連歌を楽しむ能力はあるかもしれないので試してみましょう。藤原道長が書いた句の下の句を詠んでもらいましょう。一応正解の下の句は「欠けたることも なしと思へば」ですが、つながる範囲で自由に詠んでもらいたいものです。

prompt = '''ユーザー: 以下は短歌の上の句です。下の句を詠んでください。
この世をば わが世と思ふ 望月の
システム: '''.replace('\n','<NL>')
request = {
    'prompt' : prompt,
    'max_new_tokens' : 1024,
    'do_sample' : True,
    'temperature' : 0.01,
}
output = predictor.predict(request)['result']
print(f'出力結果 : {output}')
望月の 満ち欠けの 影を 見つつ わが身は ありけり。</s>

うーん、惜しい ! という結果ですね。

ここで最後に Few-shot というプロンプトエンジニアリングを試してみましょう。いくつか例示を与えると結果がよくなることがあります。ちなみに、例示なしだと Zero-shot, 1 つだと One-shot, 複数だと Few-shot と言います。

ここでは小倉百人一首から例を取って Few-shot してみます。

prompt = '''ユーザー: 以下の 2 つは短歌の上の句と下の句の例です。

* (上の句)秋の田の かりほの庵の 苫を荒み (下の句)わがころも手は 露に濡れつつ
* (上の句)春すぎて 夏来にけらし 白たへの (下の句)ころもほすてふ あまの香具山

次の上の句に続く下の句を詠んでください。

(上の句)この世をば わが世と思ふ 望月の

システム:
'''.replace('\n','<NL>')
request = {
    'prompt' : prompt,
    'max_new_tokens' : 128,
    'do_sample' : True,
    'temperature' : 0.9,
}
output = predictor.predict(request)['result']
print(f'出力結果 : {output}')
出力結果 : 秋の田の刈り株に 露が垂れて 秋の風が吹くと、衣ずれの音がする。このような美しい瞬間が、この世をわが家のように思う気持ちをもたらしてくれる。</s>

Few-shot で与えた句に影響された自由律俳句のような感じで下の句を返してきましたね (違う、そうじゃない) !


4. Fine Tune で短歌をモデルに教え込む

これまでは、今あるモデルに対してプロンプトエンジニアリングで欲しい結果を得ようとするアプローチをしてきました。しかし、それでは限界(※)があったので、モデルそのものを改変するアプローチを試します。

※他の試していないプロンプトで結果を得られる可能性もあるので、単純に私の呪文力 (プロンプトエンジニアリング力) が不足している可能性もあります。

モデルが短歌を知らないのであれば、短歌を教え込めばいいわけですが、もう一度最初からモデルを学習しなおすと大変です。ここでは今あるモデルに対して少ないデータセットで短期間学習して求める結果を得ようとする Fine-Tune という手法を取ります。Fine-Tune もいくつかの方法がありますが、ここでは LoRA (Low-Rank Adaption)と呼ばれる手法を使います。

4-1. 短歌データセットを作成する

Fine-Tune するにも、まずは短歌を教え込むためのデータセットが必要です。今回は連歌を詠む (下の句を詠ませる) ことに特化したデータセットを作成します。1000 年程度前の短歌であれば著作権が消失していますので、権利を気にすることなく使えます。今回は wikisource (ライセンス:CC BY-SA 4.0) から三大和歌集のうちの 2 つ(※)である、古今和歌集新古今和歌集 を集めてきました。
※残りの万葉集はデータ形式が違って実装が複雑になったので今回は諦めました 。

長くなるので処理の解説は割愛いたしますが、make_dataset.ipynb を実行(※)すると dataset.json が出来上がります。

※実行場所は任意の場所で構いませんが、以下の処理は、aws-ml-jp/tasks/generative-ai/text-to-text/inference/deploy-endpoint/Transformers/ で実行した想定で以下のコードを記載しています。

dataset.json を覗いてみます。

!head -n15 dataset.json
[
  {
    "index": 0,
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "としのうちに春はきにけりひととせを",
    "output": "こそとやいはむことしとやいはむ",
    "category": "open_qa"
  },
  {
    "index": 1,
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "袖ひちてむすひし水のこほれるを",
    "output": "春立つけふの風やとくらむ",
    "category": "open_qa"
  },

このように、instruction に AI に対して上の句をもとに下の句を読めという命令を、input に上の句を、output にその正解である下の句を入れています。古今和歌集と新古今和歌集の 3089 もの短歌のデータセットが出来上がりました。

また、dataset.json の最後を確認します。

!tail -n29 dataset.json
{
    "index": 3089,
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "父の日にこれ手紙だよ手渡され",
    "output": "読んだら息子の欲しいものリスト",
    "category": "open_qa"
  },
  {
    "index": 3090,
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "事務事故の再発防止禁じ手は",
    "output": "気をつけますとダブルチェック",
    "category": "open_qa"
  },
  {
    "index": 3091,
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "寝かしつけ全く寝ない子供たち",
    "output": "その無駄時間俺寝たかった",
    "category": "open_qa"
  },
  {
    "index": 3092,
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "データあるうまい言葉に騙される",
    "output": "実態いつもガベージコレクション",
    "category": "open_qa"
  }
]

現代の和歌が 4 句格納されています。ある人間の苦悩を詠っています (字余りがひどいですね)。意図としては、このままだと古めかしい唄 (平安時代と鎌倉時代) ばかりを詠む AI ができそうだったので、少し混ぜてみたという感じです。このデータセットで FineTune して、

  • 3000 ちょっとの和歌を学習すれば、連歌を詠めるようになるのか
  • 連歌ができるようになったとして、4 句だけで現代的な表現も獲得するのか

のテストを行います。

4-2. Fine Tune する

Fine Tune を行いましょう。Fine Tune の実態は機械学習のトレーニングなので、 Amazon SageMaker Training で行います。

Amazon SageMaker Training の使い方や仕組みについてはぜひ以下の動画を参照ください。

さて、Fine Tune を行うためのコードは?というところですが、aws-ml-jp に text2text の各種モデルの Fine Tune を SageMaker Training で行うためのノートブックが公開されています。もちろん japanese-gpt-neox-3.6b のノートブックも入っております。このノートブックは Databricks 社が公開している databricks-dolly-15k というデータセットを日本語訳した kunishou さんの databricks-dolly-15k-ja というデータセットを使って Fine Tune するコードを公開しています。今回は databricks-dolly-15k-ja を先程作った dataset.json に差し替えて Fine Tune します。(なので先程作成した dataset.jsondatabricks-dolly-15k-ja と同じ形式にデータを事前に整えています。)

まずは、aws-ml-jp/tasks/generative-ai/text-to-text/fine-tuning/instruction-tuning/Transformers/Rinna_Neox_LoRA_ja.ipynb を開きます。

上からセルを実行していきますが、先程作成したデータを使用するので少し改変が必要です。
以下のセルは実行しません。(先程紹介した databricks-dolly-16k-ja というデータセットをダウンロードして確認するコードです)

# コメントアウト
# !curl -L https://huggingface.co/datasets/kunishou/databricks-dolly-15k-ja/resolve/main/databricks-dolly-15k-ja.json --create-dirs -o ./data/databricks-dolly-15k-ja.json
# コメントアウト
# !head ./data/databricks-dolly-15k-ja.json

次にトレーニングに使用するデータを差し替えます。詳細は先程紹介した Amazon SageMaker Training の Black Belt の動画 を確認して頂きたいのですが、SageMaker Training では学習データを S3 にアップロードします。以下を差し替えます。

input_train = sess.upload_data(
    # 変更前
    # path="./data/databricks-dolly-15k-ja.json",
    # key_prefix="Dolly"
    # 変更後
    path="../../../inference/deploy-endpoint/Transformers/dataset.json",
    key_prefix="renga"
)
input_train

引数の path は make_dataset.json を実行した場所に依存するので、違う場所で実行した場合は適宜修正をしてください。

次に fine-tuning を行う設定を修正します。もともと databricks-dolly-15k-ja.json を使用するようになっていたところを、dataset.json に差し替えるだけです。

base_job_name="Rinna"
hyperparameters={
    'base_model':'rinna/japanese-gpt-neox-3.6b',
    # 'load_in_8bit': True,
    'load_in_4bit': True,
    # 修正
    # 'data_path': '/opt/ml/input/data/train/databricks-dolly-15k-ja.json',
    'data_path': '/opt/ml/input/data/train/dataset.json',
    'num_epochs': 3, # default 3
    'cutoff_len': 512,
    'group_by_length': False,
    'output_dir': '/opt/ml/model',
    # 'resume_from_checkpoint': '/opt/ml/checkpoints',
    'lora_target_modules': '[query_key_value]',
    'lora_r': 16,
    'batch_size': 8,
    'micro_batch_size': 8,
    'prompt_template_name': 'alpaca',
}

'base_model':'rinna/japanese-gpt-neox-3.6b' とあるとおり、先程は japanese-gpt-neox-3.6b-instruct-ppo という instruction model を使用していましたが、fine tuning なので、base model である、japanese-gpt-neox-3.6b に対して fine-tune しているところにご注意ください。もちろん japanese-gpt-neox-3.6b-instruct-ppo を Fine Tune することもできますが、今回は連歌特化のモデルにするため、普通の instruct は使えないモデルにしています。

最後にトレーニングの実行を行います。

huggingface_estimator = HuggingFace(
    base_job_name=base_job_name,
    role=role,
    entry_point='finetune.py',
    source_dir='./scripts/code',
    instance_type='ml.g5.2xlarge',
    instance_count=1,
    volume_size=200,
    transformers_version='4.26',
    pytorch_version='1.13',
    py_version='py39',
    use_spot_instances=True,
    max_wait=86400,
    hyperparameters=hyperparameters,
    metric_definitions=[{'Name': 'eval_loss', 'Regex': "'eval_loss': (\d\.\d+)"},
                        {'Name': 'train_loss', 'Regex': "'loss': (\d\.\d+)"}],
    # checkpoint_s3_uri=f"s3://{bucket}/{base_job_name}/checkpoint/",
)
huggingface_estimator.fit({'train': input_train})

最後に以下のような出力が出れば完了です。トレーニング自体は 5 分程度で完了します。

…
2023-07-01 07:31:17 Uploading - Uploading generated training model
2023-07-01 07:31:43 Completed - Training job completed
Training seconds: 1021
Billable seconds: 305
Managed Spot Training savings: 70.1%

ちなみにデフォルトだと ml.g5.2xlarge という NVIDIA A10G Tensor Core GPU を積んだインスタンスが立ち上がります。場合によってはクオータの緩和が必要なので、 Service Quotas から緩和申請を出してください。

5 分程度でトレーニングは完了しました。

4-3. Fine Tune したモデルをデプロイする

トレーニングがおわったので、Fine Tune したモデルをデプロイして実際に使ってみます。以下のセルまで実行します。

参考までにやっていることをまとめると、Fine Tune したモデルを S3 からダウンロードし、推論コードと共に tar.gz にかためて、S3 に再度アップロードしたあと、SageMaker でデプロイしています。

from sagemaker.async_inference import AsyncInferenceConfig
from sagemaker.serializers import JSONSerializer

endpoint_name = "Rinna-LoRA"

huggingface_model = PyTorchModel(
    model_data=model_path,
    framework_version="2.0",
    py_version='py310',
    role=role,
    name=endpoint_name,
    env={
        "model_params": json.dumps({
            "base_model": "rinna/japanese-gpt-neox-3.6b",
            "lora_weights": "model", # path relative to model package
            "peft": True,
            "load_4bit": False,
            "use_deepspeed": True,
            "use_optimum": True,
            "prompt_template": "alpaca",
        }),
        "SAGEMAKER_MODEL_SERVER_TIMEOUT": "3600"
    }
)

# deploy model to SageMaker Inference
predictor = huggingface_model.deploy(
    initial_instance_count=1,
    instance_type='ml.g5.2xlarge',
    endpoint_name=endpoint_name,
    serializer=JSONSerializer(),
    # async_inference_config=AsyncInferenceConfig()
)

以下の出力が出ればデプロイ完了です。

--------! # <- 最後に ! が出ます

4-4. AI に下の句を詠ませる

モデルがデプロイできたので、下の句を詠ませてみましょう。まずは学習データを使用してちゃんと詠む能力があるかを確認します。さきほど head コマンドで確認した詩を利用してみます。元々のセルにあるコードを改変します。

SageMaker SDK と AWS SDK for Python (Boto3) を使ってる推論コードがありますが、先程は SageMaker SDK だったので紹介を兼ねて今度は Boto3 を使用します。Boto3 を利用するメリットとして、SageMaker のエンドポイントを呼び出す際に AWS Lambda などを使うことが多いですが、Lambda などの AWS のサービスは予め Boto3 が入っているので別途インストールが不要といったメリットがあります。

# With Boto3

import boto3
import json

endpoint_name = "Rinna-LoRA"
sagemaker_client = boto3.client('sagemaker-runtime')

data = {
    # 修正前
    # "instruction": "ヴァージン・オーストラリアはいつから運航を開始したのですか?",
    # "input": """ヴァージン・オーストラリア航空(Virgin Australia Airlines Pty Ltd)の商号で、オーストラリアを拠点とする航空会社です。ヴァージン・ブランドを使用する航空会社の中で、保有機材数では最大の航空会社である。2000年8月31日にヴァージン・ブルーとして、2機の航空機で単一路線で運航を開始した[3]。2001年9月のアンセット・オーストラリアの破綻後、突然オーストラリア国内市場の大手航空会社としての地位を確立した。その後、ブリスベン、メルボルン、シドニーをハブとして、オーストラリア国内の32都市に直接乗り入れるまでに成長した[4]。""",
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "としのうちに春はきにけりひととせを",
    "max_new_tokens": 128,
    "temperature": 0.1, # 0.3 -> 0.1
    "do_sample": True,
    "pad_token_id": 0,
    "bos_token_id": 2,
    "eos_token_id": 3,
}

response = sagemaker_client.invoke_endpoint(
    EndpointName=endpoint_name,
    ContentType='application/json',
    Accept='application/json',
    Body=json.dumps(data)
)

result = json.loads(response['Body'].read())
# 修正前
# print(result)
print(f'出力結果 : {result}')# With Boto3

import boto3
import json

endpoint_name = "Rinna-LoRA"
sagemaker_client = boto3.client('sagemaker-runtime')

data = {
    # 修正前
    # "instruction": "ヴァージン・オーストラリアはいつから運航を開始したのですか?",
    # "input": """ヴァージン・オーストラリア航空(Virgin Australia Airlines Pty Ltd)の商号で、オーストラリアを拠点とする航空会社です。ヴァージン・ブランドを使用する航空会社の中で、保有機材数では最大の航空会社である。2000年8月31日にヴァージン・ブルーとして、2機の航空機で単一路線で運航を開始した[3]。2001年9月のアンセット・オーストラリアの破綻後、突然オーストラリア国内市場の大手航空会社としての地位を確立した。その後、ブリスベン、メルボルン、シドニーをハブとして、オーストラリア国内の32都市に直接乗り入れるまでに成長した[4]。""",
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "としのうちに春はきにけりひととせを",
    "max_new_tokens": 128,
    "temperature": 0.1, # 0.3 -> 0.1
    "do_sample": True,
    "pad_token_id": 0,
    "bos_token_id": 2,
    "eos_token_id": 3,
}

response = sagemaker_client.invoke_endpoint(
    EndpointName=endpoint_name,
    ContentType='application/json',
    Accept='application/json',
    Body=json.dumps(data)
)

result = json.loads(response['Body'].read())
# 修正前
# print(result)
print(f'出力結果 : {result}')
出力結果 : けふはこひしきものをとふ人もなし

今日は恋しき 物を問ふ人もなし

私に古典の素養がないのでわかりませんが (そして若干字余りしてますが)、短歌っぽくなりました。
japanese-gpt-neox-3.6b-instruct-ppo では、下の句を全く詠めなかったのに対し、5 分程度の学習で下の句の表現を学べたと言って良いでしょう。

4-5. 現代の日本語表現の獲得を確認する

学習データに 4 句ほど現代の詩を入れてました。その表現を獲得できたか確認してみましょう。上の句はとある人間が家に帰ってただいまと言ったのに息子がタブレットに夢中でなにも返事がなかった時の哀しさを詠っています。

data = {
    "instruction": "input は短歌の上の句です。下の句を詠んでください。",
    "input": "ただいまと虚しく響く玄関に",
    "max_new_tokens": 128,
    "temperature": 0.1,
    "do_sample": True,
    "pad_token_id": 0,
    "bos_token_id": 2,
    "eos_token_id": 3,
}

response = sagemaker_client.invoke_endpoint(
    EndpointName=endpoint_name,
    ContentType='application/json',
    Accept='application/json',
    Body=json.dumps(data)
)

result = json.loads(response['Body'].read())
print(f'出力結果 : {result}')
出力結果 : 月の光をこひつつねるかな

月の光を乞いつつ寝るかな

少し現代文っぽい出力で、不貞腐れてる感があっていとおかしですね。ただ旧仮名遣いが出ていて、たった 4 データだと現代語の獲得を仕切れたかというと怪しいところです。

4-6. せっかくなので音声をつけてみる

連歌 (れんが) は、日本 の古来に普及した伝統的な詩形の一種。5・7・5の発句と7・7の脇句の,長短句を交互に複数人で連ねて詠んで一つの歌にしていく ( 引用: https://ja.wikipedia.org/wiki/%E9%80%A3%E6%AD%8C)

連歌は書いたのではなく詠んだとありますね。発声させましょう。併せて class にしてコードをキレイにします。

まず使用する ライブラリを整理インストールします。
pip install mutagen

mutagen は mp3 ファイルの長さを取得するのに使用します。続いて class を作成します。

import boto3
import json
import IPython.display
from mutagen.mp3 import MP3
from time import sleep
import threading

class 短歌:
    def __init__(self, 上の句):
        self.ぽりー= boto3.client('polly')
        self.せーじめーかーらんたいむ = boto3.client('sagemaker-runtime')
        self.詠み人 = "Rinna-LoRA"
        self.上の句 = 上の句
        self.詩の音声ファイル = '詩.mp3'
        self.下の句 = ''
        self.音声形式 = 'mp3'
        self.言語符号 = 'ja-JP'
        self.上の句の詩い手 = 'Takumi'
        self.下の句の詩い手 = 'Kazuha'
        self.詠みの長さ = 0
        self.上の句を詠み終えた = False
    def 詩を詠む(self, 詩:str, 上):
        詩 = f'<speak><prosody rate="80%">{詩}</prosody></speak>'
        詩の音声 = self.ぽりー.synthesize_speech(
            Engine='neural',
            OutputFormat=self.音声形式,
            LanguageCode=self.言語符号,
            Text=詩,
            TextType='ssml',
            VoiceId=self.上の句の詩い手 if 上 else self.下の句の詩い手,
        )["AudioStream"].read()
        with open(self.詩の音声ファイル,"wb") as f:
            f.write(詩の音声)
        return IPython.display.display(IPython.display.Audio(詩の音声, autoplay=True))

    def 上の句を詠む(self):
        self.詩を詠む(self.上の句, True)
        sleep(MP3(self.詩の音声ファイル).info.length)
        self.上の句を詠み終えた = True
        
    def 下の句を生成する(self):
        data = {
            "instruction": "input は短歌の上の句です。下の句を詠んでください。",
            "input": self.上の句,
            "max_new_tokens": 128,
            "temperature": 0.1,
            "do_sample": True,
            "pad_token_id": 0,
            "bos_token_id": 2,
            "eos_token_id": 3,
        }
        response = self.せーじめーかーらんたいむ.invoke_endpoint(
            EndpointName=self.詠み人,
            ContentType='application/json',
            Accept='application/json',
            Body=json.dumps(data)
        )
        self.下の句 = json.loads(response['Body'].read())    
        
    def 下の句を詠む(self):
        self.下の句を生成する()
        while True:
            if self.上の句を詠み終えた:
                self.詩を詠む(self.下の句, False)
                break
            else:
                sleep(0.1)

    def 始め(self):
        上の句を詠む = threading.Thread(target=self.上の句を詠む)
        下の句を詠む = threading.Thread(target=self.下の句を詠む)
        上の句を詠む.start()
        下の句を詠む.start()
        上の句を詠む.join()
        下の句を詠む.join()

今回は和歌を扱っているため、日本語のクラス、メソッド、プロパティで用意しました。すごく書きづらかったですが、どこか雅を感じますね。使い方は以下のように使います。ダイエット中の辛さを詩にしました。

短歌('ダイエット、腹減る夜中、耐え難き').始め()

すると、以下のように和歌を Amazon Polly が詠みあげてくれます。(冒頭の動画と一緒です)

今夜は月も見て見ぬふりをしてくれるからちょっとなら食べてもぃぃょ。って甘やかしてくれました。

処理は以下の通りです。

  1. 上の句を使って短歌クラスからインスタンスを生成して、開始メソッドを呼び出す
  2. マルチスレッドで2つのスレッドが動く
    1. スレッド 1
      1. 上の句を詠んだ音声ファイルを作成する
      2. 上の句の音声ファイルを再生する
    2. スレッド 2
      1. 上の句を AI に入力して、下の句を取得する
      2. スレッド 1 で再生が終わっているか確認し、終わっていたら次へ、終わっていなかったら 0.1 秒待機して再度確認する
      3. 下の句の音声ファイルを作成する
      4. 下の句の音声ファイルを再生する

このようにして、上の句を詠まれている間に下の句を考えて詠む、という古来からの伝統に則った実装をしました。やはりしきたりは守りたいですからね。

また詩い手がそれぞれいるように、上の句は Takumi , 下の句は Kazuha を利用していたり、詠み上げの速度を遅くして和歌っぽさを出しているのもいとおかしですね。

あとは、短歌クラスを呼び出すときに、上の句を引数にいれて開始メソッドを呼び出せば動く、という仕組みです。

今回は Jupyter の環境で完結するような実装をしましたが、サービスとして Web の UI を構築してみるのも良いでしょう。ぜひ皆様も挑戦されましたら #AWSウェブマガジン で投稿お待ちしております。


5. まとめ

今回やってきたことを最後に3行でまとめておきましょう。

  • SageMaker を使えば 生成 AI をすぐに使えます !
  • 3000 首程度の詩で連歌ができるようになりました !
  • 4 首では現代語の下の句の表現の獲得は難しかったので、もうちょっと必要です !

実は、記事では割愛しましたが、古今和歌集の 1100 首程度でも十分連歌を楽しめました。ですので、現代での下の句は多くても 1100 首程度あればいけると思います (ただし今から 1100 首詠むのは苦痛なので諦めました)。

さて、ここまで大半 (全部 ?) はおふざけでしたが、実際の業務での利用を考えてみましょう。

再掲ですが、2023 年 5-6 月に日本語の生成 AI モデル (今回使用した rinna 社の japanese-gpt-neox-3.6b の他、CyberAgent 社が open-calm-7b) や、日本語が使える生成 AI モデル (MosaicML 社の mpt-7b, mpt-30b や、TII 社の falcon-40b など) がたくさん出てきました。これらのモデルはそのまま業務に使えるかもしれませんし、使えないかもしれません。いくつかのモデルを試して使えるか確認しましょう。そのとき、すべてのモデルでイマイチな結果しか得られないかもしれません。そこで使えないと諦めるのではなく、データを集めて Fine Tune すれば使えるかもしれません。Fine Tune に必要なのは今回お見せしたとおり、きれいに整形されたデータです。

きれいなデータを集める仕組みというのがこれまで以上に大切になってきます。

例えば、コールセンターの業務であれば、問い合わせに対して回答というのを必ず誰かが作成します。問い合わせと回答をセットでシステムに記録すれば、それは立派に整形された正解データであり、Instruction Tuning にそのまま使用できます。あるいは FAQ なども良質な質問と回答です。それが数千数万と揃えば Fine Tune 可能で、業務で使えるモデルになるかもしれません。

ぜひ皆様の業務においても、データ作成が進む仕組みの構築をしていただければ幸いです、と最後くらい真面目に締め、空腹に耐えながら書いたこの記事の筆を擱く (おく) こととします。

タイトルでは寂しさを紛らわせていましたが、空腹と格闘する時間のほうが長かったのを最後にお伝えいたします。ではまた次の記事でお会いしましょう !


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

呉 和仁 (Go Kazuhito / @kazuneet
アマゾン ウェブ サービス ジャパン合同会社
機械学習ソリューションアーキテクト。

IoT の DWH 開発、データサイエンティスト兼業務コンサルタントを経て現職。
プログラマの三大美徳である怠惰だけを極めてしまい、モデル構築を怠けられる AWS の AI サービスをこよなく愛す。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する