Vertex AI (PaLM API)の chat-bison を使って日本語版の TinyStories を作ってみた話
この記事は、Google Cloud Champion Innovators Advent Calendar 2023 の2日目の記事です。2日目にして GCP 色薄めですが、 PaLM API を使って TinyStories データセット の日本語版を作ってみた話をしようと思います。
なお、本プロジェクトを進めるにあたって、Google ML Developer Programs team に GCP クレジットの支援をいただきました(Google ML Developer Programs team supported this work by providing Google Cloud Credit)。大変感謝しています。
3行要約
- TinyStoriesという研究目的で非常に有用な英語のデータセットの日本語版を構築しました[1]
- 生成される物語の質やコストの観点から VertexAI の PaLM API を利用することにしました
- 構築したデータセットで小規模言語モデル(SLM)を訓練すると、ある程度流暢な日本語を生成できることがわかりました[2]
例えば、構築したデータセットで訓練した42Mのモデルで物語を生成すると、以下のような出力が得られます(太字は入力で、それ以降が生成文)
暑い夏のある日、 ジョンは友達と公園で遊んでいました。彼らはサッカーをしていて、とても楽しんでいました。ジョンはボールを蹴って、友達がそれを追いかけました。ボールは木の枝に当たって、木の上に落ちました。ジョンは木に登ってボールを取りに行きました。
ジョンは木の上に登り、ボールを見つけました。彼はボールを友達に返しました。友達はジョンにありがとうと言いました。ジョンは友達と遊んでとても楽しかったです。
ジョンは友達と遊んだ後、家に帰りました。彼は母親に今日のことを話しました。母親はジョンが楽しかったことを聞いてとても嬉しかったです。
ジョンは明日も友達と遊びに行くのが楽しみでした。彼はまた暑い日に友達と遊ぶのが楽しみでした。
...
TinyStories データセットとは
TinyStories データセットデータセットは、ChatGPTを用いて生成された「3-4歳児でも容易に理解できる簡単な短い物語」のデータセットです。物語数は約150万話[3]で、以下の論文で紹介されています。
TinyStories に含まれる物語は、例えば以下のようなものです。
One day, a little girl named Lily found a needle in her room. She knew it was difficult to play with it because it was sharp. Lily wanted to share the needle with her mom, so she could sew a button on her shirt. Lily went to her mom and said, "Mom, I found this needle. Can you share it with me and sew my shirt?" Her mom smiled and said, "Yes, Lily, we can share the needle and fix your shirt." Together, they shared the needle and sewed the button on Lily's shirt. It was not difficult for them because they were sharing and helping each other. After they finished, Lily thanked her mom for sharing the needle and fixing her shirt. They both felt happy because they had shared and worked together.
近年小規模な言語モデルでは一貫性のある文章を生成するのが難しく大量のデータを使って大規模な言語モデルを訓練する必要があると思われていましたが、ChatGPTを使って生成したノイズのすくないデータセットで訓練することで、小規模な言語モデル(パラメーター数<100M程度)であっても一貫性のある文章を生成できることが示されています[3:1]。
パラメーター数<100Mの小規模な言語モデルであれば、事前学習含め 1GPU で行うことも可能になるので、趣味で言語モデルで遊んでいる自分としてはとても魅力的です。ただ、オリジナルの TinyStories データセットは、全て英語で日本語が含まれていません。そこで、日本語版の TinyStories 作りたい!と考えました。
日本語 TinyStories の構築方針
TinyStories が非常に魅力的なので、これの日本語版が欲しいなと思いました。そこで、日本語 TinyStories の構築方針を3つ検討しました。
- APIを用いた翻訳
- 翻訳モデルを持ちいた翻訳
- OSS の LLM を使った生成
- ChatGPT もしくは PaLM API を使った生成
APIを用いた翻訳
まず考えたのが、DeepLや Google Translate API を用いて、オリジナルの TinyStories データセットを日本語に翻訳することでしたが、これは費用の観点から断念しました。
例えば Google Translate API の場合、100万文字あたり $20 です。 TinyStories データセットは約200万話なので、ざっくり1話500文字として1,000,000,000文字です。単純計算すると $20,000(約300万円)かかることになります。データセットのサイズを100万話に絞ったとしても$10,000(約150万円)ほどかかるので、断念しました。
翻訳モデルを用いた翻訳
次に考えたのは、翻訳モデルを用いて翻訳したらどうか、ということでした。フリーで使える翻訳モデルの中では、知名度が高く、性能も良さそうな FuguMT を用いて実験しました。
2〜3話手動で試してみて良さそうだったので、10万件ほど翻訳しました。結果を見てみると意味はとれるものの「ですます調」と「だ・である調」が混じってしまう点や代名詞の扱いが日本語的でない点が気になりました。
もう少しやり方を工夫するといい感じになりそうと思いつつも、今回はできるだけ綺麗なデータセットを作りたいと考え、英語の物語を翻訳するのではなく日本語の物語を直接生成する方針を検討することにしました。
Once upon a time, there was a little girl named Lily. She had a teddy bear named Teddy that she loved very much. Teddy was special to her because he was unique and no one else had a teddy bear like him.
One day, Lily went to the park with her mom and she accidentally left Teddy on the bench. When she realized that Teddy was missing, she started to worry and cry. But her mom told her not to worry and that they would go back to the park to find him.
Lily trusted her mom and they went back to the park. Luckily, Teddy was still on the bench where Lily had left him. She was so happy to have him back and hugged him tightly. From that day on, Lily made sure to always keep Teddy close so she wouldn't lose him again.
昔々、リリーという少女がいました。 彼女はテディという名前のテディベアを持っていて、とても愛していました。 テディはユニークで、他の誰も彼のようなテディベアを持っていなかったので、彼女にとって特別でした。 ある日、リリーはお母さんと一緒に公園に行き、誤ってテディをベンチに置き去りにした。 彼女がテディが行方不明であることを知ったとき、彼女は心配して泣き始めました。 しかし、彼女の母親は心配しないで、彼らは彼を見つけるために公園に戻るだろうと彼女に言った。リリーは母親を信頼し、彼らは公園に戻った。 幸いにも、テディはまだリリーが彼から離れていたベンチにいた。 彼女は彼を戻して、しっかり抱きしめてくれてとてもうれしかった。 その日から、リリーはテディが二度と失わないように、いつもテディを近づけておくようにした。
OSS の LLM を使った生成
次に考えたのが、OSS の LLM をローカルで動かして、物語を生成することです。日本語の精度が高いと評判の elyza/ELYZA-japanese-Llama-2-7b をはじめ、公開されている日本語特化型の言語モデルはモデルサイズが 7B もしくは 13B 程度のものばかりだったので、個人所有のGPU(RTX-3090)でも利用でき、相性がよさそうと判断しました。
ただ、これも以下の2つの理由から結局断念しました。
- 生成にかかる時間
個人所有の RTX-3090 では、7B のモデルで物語を生成するのにだいたい3秒ほどかかりました。100万話生成するのに、300万秒、1日が8万秒なので、だいたい40日ほど動かし続ける必要があります。不可能ではないですが、少し辛いです - 生成される物語の質
生成される物語の質にも問題がありそうでした。以下の図は ELYZA-japanese-Llama-2-7b-fast のデモ環境 で、物語を生成してみた例です。日本語としては成立していますが、物語としてはすこし微妙な気がしました。他の言語モデルも、物語の生成は苦手なようでした。
ChatGPT もしくは PaLM API を使った生成
最後に、有償の API (ChatGPT や PaLM API)を使って生成することを考えました。こちらは、翻訳や OSS の LLM を使った生成に比べて、日本語として自然な物語を生成することができていました。
ChatGPT(gpt-3.5-turbo)では、以下のような出力が得られました。依頼通りの物語が生成されています。
ある日、ねずみのミッキーは、友達のモルガンと一緒に冒険に出発しました。ミッキーは白い帽子をかぶり、モルガンは白い靴をはいていました。
森の中で、ふしぎな冒険が待っていました。ミッキーが大きな木の下で休んでいると、白いうさぎがやってきて言いました。「こんにちは!どこへ行くの?」と。
ミッキーは嬉しそうに答えました。「冒険に出発して、たくさんの楽しいことを見つけるんだよ!」
うさぎは笑顔で言いました。「それは素敵だね!いってらっしゃい!」ミッキーとモルガンは、うさぎの元気な声を受けて、ますます元気になりました。そして、新しい冒険が続くのでした。
一方、こちらは PaLM API の chat-bison を用いて生成した結果です。依頼内容は一部無視[4]されているものの、こちらも非常に自然な日本語になっています。
雪が降り始めました。雪は白くて、とてもきれいでした。雪は地面を覆い、木々を覆いました。雪は空に舞い、風に吹かれて飛んでいました。
雪が降り始めると、子供たちが外に出てきて雪遊びを始めました。彼らは雪だるまを作ったり、雪合戦をしたりしました。彼らは雪の中でとても楽しそうでした。
ある日、雪が降り始めると、白い犬が外に出てきました。犬は雪の中を走り回り、楽しそうに遊んでいました。犬は雪を掘ったり、雪の中に飛び込んだりしました。犬は雪の中でとても楽しそうでした。
犬は雪の中で遊んでいるうちに、疲れて家に帰りました。犬はソファに座って、火のそばで暖まりました。犬は暖かい火のそばで眠りに落ちました。犬は夢の中で、雪の中で遊んでいることを夢見ていました。犬は雪だるまを作ったり、雪合戦をしたりしていました。犬は雪の中でとても楽しそうでした。
犬が夢から目覚めると、まだ雪が降っていました。犬は外に出て、雪遊びを続けました。犬は雪の中でとても楽しそうでした。
API 利用料の比較
API を使えばうまくいきそう[5]ということがわかったので、1コールあたり500文字程度と想定してコストを見積もりました。
- chat-gpt-turbo の場合、1000トークンあたり $0.002
日本語の場合はだいたい文字数の2/3くらいのトークン数になることを考慮に入れると、200万話で $1333(〜20万円)になります[6] - chat-bison の場合、1000文字あたり $0.0005
200万話で $500(〜7.5万円)となります
個人の出費として20万円はつらい。chat-bisonを使って、かつデータ量を半分にすれば 3.75万円なので、なんとかなるか...ということで、chat-bison を用いることにしました[7]
補足: 実際のAPI利用料
100万話の生成にかかった API 利用料は 4,8883円でした。
データセットの作成手順
データセットの作成は、前述の TinyStories の論文と、解説動画を参考に、以下の要領で行いました.
- 物語に含めるべき簡単な単語リストを準備する
「簡単な単語」の定義が難しかったので、今回は日本語能力試験の N4、N5 相当の単語約1500語を抽出して利用することにしました。幼児が知っている単語ばかりではないですが、日常的に使われる単語ばかりなので、採用しました - 物語の特徴(悲劇、道徳的など)のリストを用意する
こちらは、解説動画の中で、特徴の一覧とそれぞれの出現確率が紹介されていたので、それを日本語に翻訳したものを使いました - chat-bison の出力結果を見て微修正を行う
どうしても物語以外の文言(かしこまりましたといった返答や、物語に関する解説が含まれるなど)が出力されてしまうので、以下のように prompt の最後に「## 物語」と付けることにしました。これにより、物語と物語以外が分割されやすくなり、後処理の工数を削減しました。3〜5段落の、短くて簡単な物語を書いてください。5歳児でも簡単に理解できるような簡単な単語のみを使ってください。物語は「ですます調」で書いてください。
その物語では、動詞の「受ける」、名詞の「出発」、形容詞もしくは形容動詞の「白い」という単語を使ってください。その物語には、少なくとも1つ会話を含めてください。くれぐれも簡単な言葉しか使わないようにしてください。
## 物語
- API のクォータをあげてもらう
デフォルトでは PaLM API は 60 call/min の制限がかかっているので、コンソール上から申請して 240 call/min に上げてもらいました - Pythonスクリプトの実装とAPIコール
前述の「単語」や「特徴」をランダムに選択してプロンプトを生成し、APIに投げるプログラムを書きます。初めは1プロセスでやっていましたが、1話あたり4〜5秒かかっていたので、12並列くらいで走らせました。並列処理自体はスクリプトに含めず、Task Spooler を使うことで柔軟性を確保しました - 後処理
以下のような後処理を施しました- 連続する改行の削除
- 物語本文以外と想定される箇所の削除
- 生成に失敗していそうなものを削除
実際に生成された物語たち
悲劇です
小さな女の子が林の中に住んでいました。彼女は林の中の木々や花をとても愛していました。しかし、ある日、林に火がついてしまいました。女の子は火から逃げようとしましたが、木が燃えて道がふさがれてしまいました。女の子は逃げることができず、火で焼かれて死んでしまいました。
女の子の死はとても悲しいことでした。彼女は林をとても愛していましたが、火に焼かれて死んでしまいました。女の子の死を悼んで、林の中の木々はみんな黒く染まってしまいました。
女の子の死は、林にとっても大きな悲劇でした。林は女の子の死を悼んで、みんな黒く染まってしまいました。林は女の子の死を忘れることはないでしょう。
TinyStories の解説動画に合わせて、指示の3割に「その物語は、悲劇で終わるようにしてください。」という文言を含めているため、悲劇が非常に多いです。
昔々、あるところに、とても仲の良い男の子と女の子がいました。男の子の名字は「田中」、女の子の名字は「鈴木」でした。
ある日、田中くんと鈴木さんが森で遊んでいると、大きな熊に出会いました。熊は二人に襲いかかってきました。
田中くんは鈴木さんを守るために、熊に立ち向かいました。田中くんは必死に戦いましたが、熊にはかなわず、殺されてしまいました。
鈴木さんは田中くんの死を悲しみ、泣き続けました。熊は鈴木さんの泣き声を聞いて、逃げてしまいました。
鈴木さんは田中くんの死を悲しみ続け、やがて死んでしまいました。
田中くんと鈴木さんは、天国で仲良く暮らしています。
「その物語には、少なくとも1つ会話を含めてください。」という指示を含めると、会話を含めてくれます
ジョンは庭に小さな木を植えました。
「この木が大きくなると、どんなにきれいだろうね」とジョンは思いました。
ジョンは木に水をあげ、毎日日当たりの良い場所に置きました。
木はどんどん大きくなり、葉を茂らせました。
ある日、ジョンは木の下に虫を見つけました。
「この虫が木を傷つけてしまうかもしれない」とジョンは思いました。
ジョンは虫を捕まえようとしましたが、虫はとても速くて捕まえることができませんでした。
ジョンは困ってしまいました。
「どうしよう」とジョンは思いました。
ジョンは知恵を絞って考えました。
「そうだ!」とジョンは思いました。
ジョンは虫を捕まえるために罠を作りました。
罠はうまくいき、ジョンは虫を捕まえることができました。
ジョンは虫を木から遠ざけました。
木は虫に傷つけられることなく、元気に成長しました。
ジョンは木をとても大切にしました。
ジョンは木が大きくなるのを楽しみにしていました。
時々ものがたりではないものも出力されます
砂糖は硬い食べ物です。それをどうやって食べたらいいか、考えました。砂糖を噛んで食べてもいいですが、硬くて痛いかもしれません。砂糖を溶かして飲むのもいいですが、砂糖が残って口が甘くなるかもしれません。
砂糖をどうやって食べたらいいか、いろいろ考えました。結局、砂糖を溶かして飲むことにしました。砂糖を溶かすと甘いお茶になります。お茶を飲むと甘くておいしいです。
砂糖は硬い食べ物ですが、いろいろな食べ方があります。砂糖をどうやって食べたらいいか、考えてみるのも楽しいです。
作ったデータセットで小規模言語モデルを構築できるか確認する
データセットの有効性を確認するため、今回構築したデータセットのみを使って小規模言語モデル構築の実験を行いました。詳細は省略しますが、パラメーター数は1.3Mから62Mで10エポック訓練。手持ちの RTX-3090 で5時間〜30時間ほどかかりました。
以下の例は42Mのモデルの出力例です(太字が入力、それ以降が生成文)。見ると分かる通り、コンテクストを見て生成する文を変えてくれているようで面白いです
暑かったので、コートを脱ぎました。彼はコートを木の枝にかけ...
寒かったので、コートを着ました。コートは暖かくて、ジョンは寒く...
寒かったけれど、コートを着ていれば寒くないよ」と言いました。
また、冒頭であげた例もこのモデルで生成したものです。日本語でも42M程度のモデルで、流暢な物語を生成できそうです[8]。
まとめ
日本語版の TinyStories を作ってみました。あくまで個人の趣味でおこなっていることなので、主にコストの観点から PaLM API の chat-bison を選択しました。生成された物語を読むだけでも面白いのですが、日本語でも、たかだか数十Mの言語モデルで流暢な物語を生成できることがわかったり、個人的に色々な発見があって面白かったです。
-
PaLM API をつかって PaLM API や MakerSuiteに競合するモデルを構築するためにAPIを利用することは利用規約で禁止されており、データセットを公開する場合の適切なライセンスが不明なため、現時点では公開を考えていません。
https://developers.generativeai.google/terms ↩︎ -
厳密な評価をしたわけではなく、あくまで生成される物語を目視でパラパラながめているだけです ↩︎
-
軽量にも関わらず高性能と言われているMicrosoftのphi-1.5などは、まさにこの考え方を踏襲して構築されたモデルです。 ↩︎ ↩︎
-
プロンプトによっては ChatGPT も普通に依頼内容を無視します ↩︎
-
悩ましいところですが、商用 API の場合は、その利用規約で競合するモデルの構築が禁止されていることがほとんどです。そのため、完全に自由に使えるデータセットを作ることはできません。 ↩︎
-
正確には入力と出力で料金が違うので、これよりも少し安くなります ↩︎
-
結局 Google にお願いしてクーポンを発行してもらうことになったので、この費用は発生していません。 ↩︎
-
現時点では、生成結果について、きちんと評価しているわけではありません。例えば、単にパターンマッチを行っているだけの可能性もあります。 ↩︎
Discussion