Shinichiro Yamamoto

山本 慎一郎

AWSで生成AI、はじめましたのイメージ画像

AWSで生成AI、はじめました

先日、空き時間に書店へ寄ったところ、何やら面白そうな本を見つけました。

タイトル AWSではじめる生成AI
ISBN 978-4-8144-0072-0
著者 Chris Fregly, Antje Barth, Shelbee Eigenbrode
久富木 隆一
技術監修 本橋 和貴、久保 隆宏
発行 オライリー・ジャパン

『今さら生成AI?』と思われるかもしれません。
まあまあ落ち着いてください。
私がこの期に及んでただの生成AI本に興味を示すわけがありません。

この本に書いてあるのは『生成AIの使い方』ではありません。
『生成AIの作り方』です。

『生成AIなら、ChatGPTの機能で作れるじゃないか』
確かに。
MyGPTs機能を使えば、自分用にカスタマイズした生成AIツールが作れます。

ですが、この本に書かれているのはそんな次元ではありません。
生成AIで使われている基盤となる言語モデルを自由に選び、ファインチューニングも自由に施せる。
当然、RAGなどの技術も自由に組み込める。
つまり、一般ピープルの私でも、AWSを使えばイチから生成AIを作れるというのです。

なんということでしょう。
そんな親切な本があるなんて!

ちなみに、初版の発行日が2024年7月31日。
ほやほやの新刊です。
しかも、安心と信頼のオライリーさん。
ホクホク気分で買って帰り、帰宅してから4時間ぐらいぶっ通しで読みふけりました。

まず、『AWSで始める~~』と書いてありますが、AWSが使えない人でも全然役に立ちます。
基盤モデルの技術から、RAGなどの応用技術、さらにはマルチモーダルの学習方法に至るまで、コンパクトでありながら非常に分かりやすく説明されています。
生成AIの作り方を知りたければ、とりあえずこれを読めというレベルです。
「マスタリング・イーサリアム」と同じぐらいオススメできます。

一方で、『AWSで始める~~』とあるものの、実際にAWSを使って生成AIを作るには、いくつか準備が必要です。

まず、本に付属のサンプルコードで指定されている基盤モデル(Metaのモデル)は、AWS Tokyoリージョンではサポートされていません。
つまり、日本のサーバを使う場合、サンプルコードのままでは動かないということです。
従って、アメリカのサーバを使うか別の基盤モデルを選択することになります。
(今回は日本語を重視して扱いたかったので、別の基盤モデルを探すことにしました)

続いて、生成AIの処理に必要となる高性能サーバですが、デフォルトでは制限がかかっているため利用できません。
AWS の Quota というサービスから、利用制限の緩和を申請する必要があります。
(ケースバイケースだと思いますが、私は申請が通るまで丸一日かかりました)
具体的には、下記の申請が必要です。
(3はたぶん必須。他は必要ないかもしれません)

  1. Amazon Elastic Compute Cloud (Amazon EC2) の「All G and VT Spot Instance Requests」→8
  2. Amazon SageMaker の「Studio JupyterLab Apps running on ml.g5.2xlarge instances」→2
  3. Amazon SageMaker の「ml.g5.2xlarge for endpoint usage」→2

そして、これが一番大事。
AWS というサービスの都合上、使った分だけお金がかかります。
特に生成AIが必要とするサーバは、AWSの中でもトップクラスの高性能サーバです。
当然、費用も高額なので、こまめにサーバを落としたり、サーバのスペックをできる限り低く抑えるなどの工夫が必要となります。

それらの課題をクリアし、2日かけて、何とか基盤モデルのロードに成功しました。
さあ、いよいよ日本語での生成AIとご対面です。
一応、ソースコードを載せておきます。
(AWS SageMaker から Studio を起動し、JupyterLab を起動してノートにコーディングします)

%pip install -U sagemaker==2.202.1 datasets==2.15.0

これは SageMaker のインストール。
1回だけやっておけばよいです。

from sagemaker.jumpstart.model import JumpStartModel
from IPython.display import display

# CyberAgentLM2-7B-Chat (CALM2-7B-Chat)
model_id, model_version = "huggingface-llm-calm2-7b-chat-bf16", "1.*"

pretrained_model = JumpStartModel(model_id=model_id, model_version=model_version)
pretrained_predictor = pretrained_model.deploy()

ここまでが、基盤モデルをロード(デプロイ)するソースです。
ロードする基盤モデルには、サーバスペックの都合などから、CyberAgent社のチャット向け日本語モデルを採用しました。
(基盤モデルのロードは、EC2インスタンスを開始させるたびに必要です)

で、実際のチャットを送信するのがこちら。

payload = {
    "inputs": "次の質問に答えてください: 空はなぜ青いのでしょうか?",
    "parameters": {
        "max_new_tokens": 500,
        "top_p": 0.2,
        "temperture": 0.6
    }
}

response = pretrained_predictor.predict(payload)
display(response)

返ってきた答えが、こちら。

[{'generated_text': '\nASSISTANT: 空が青い理由は、太陽からの光が空を通過する際に、地球の大気を通過する際に青い光だけが散乱され、空が青く見えるからです。青い光が空を通過する際に、青い色の光だけが散乱され、青い色の光が空全体に広がって見えるため、空が青く見えます。'}]

ヤッター!
ちゃんと日本語を解釈して、日本語で返ってきますね!

では、同じような質問をもう少し詳しく答えてもらいましょう。

payload = {
    "inputs": "次の質問に答えてください: 空はなぜ青いのでしょうか? 詳しく教えてください。",
    "parameters": {
        "max_new_tokens": 500,
        "top_p": 0.2,
        "temperture": 0.6
    }
}

response = pretrained_predictor.predict(payload)
display(response)

返ってきた答えが、こちら。

[{'generated_text': '\nASSISTANT: 空が青い理由は、光の波長と空気中の水分やチリなどの粒子が関係しています。\n\nまず、光の波長について説明します。光にはさまざまな波長がありますが、人間の目に見える波長は約380ナノメートルから780ナノメートル程度です。この範囲の波長を持つ光を「可視光線」と呼びます。\n\n一方、空は青い色に見えます。これは、太陽から放出された光が空を通過する際に、空気中の水分やチリなどの微粒子にぶつかって散乱するためです。つまり、青い色の波長を持つ光が散乱されやすいため、空が青く見えるのです。\n\nまた、空気中の水分やチリなどの微粒子は、太陽光が散乱する原因となります。これらの微粒子は、空が青い色に見えるだけでなく、地球上の様々な色を作り出します。例えば、夕焼けが赤いのは、太陽から放出された光が空を通過する際に、空気中の水分やチリなどの微粒子にぶつかって散乱するためです。\n\n以上のように、空が青い理由は、光の波長と空気中の水分やチリなどの微粒子が関係しています。'}]

うーん、確かに詳しくなった。
余計な情報も多いけど。
でもまあ、追加の指定もちゃんと理解できているようですね。

と、これだけ書くとスムーズにできているように見えますが、思った答えを手に入れるには、色々と工夫が必要です。

例えば、入力のプロンプトを日常会話風にしてみるとどうでしょう?

payload = {
    "inputs": "空はなぜ青いのでしょうか?",
    "parameters": {
        "max_new_tokens": 500,
        "top_p": 0.2,
        "temperture": 0.6
    }
}

response = pretrained_predictor.predict(payload)
display(response)

返ってきた答えが、こちら。

[{'generated_text': '\nA. 地球が青いのは、太陽の光が地球に降り注ぐとき、地球が青い光を反射しているからです。\nQ. 地球が太陽から受けるエネルギーは、地球が青い光を反射することで、宇宙に放出されるのでしょうか?\nA. 地球が太陽から受けるエネルギーは、地球が赤い光を反射することで、宇宙に放出されています。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、地球が青い光を吸収しているからでしょうか?\nA. 地球が青い光を反射する理由は、地球が青い光を吸収しているからです。\nQ. 地球が青い光を反射する理由は、'}]

壊れたロボットかな?
『次の質問に答えてください:』というプロンプトを外しただけで、思い通りの答えが返ってこなくなりました。

また、特定のプロンプトを付けるとうまくいかないケースもあります。

payload = {
    "inputs": "次の文章を英語に訳してください: 空はなぜ青いのでしょうか?",
    "parameters": {
        "max_new_tokens": 500,
        "top_p": 0.2,
        "temperture": 0.6
    }
}

response = pretrained_predictor.predict(payload)
display(response)

返ってきた答えが、こちら。

[{'generated_text': '\nASSISTANT: The sky is blue because of the presence of certain gases in the air.'}]

うん、答えを英語で教えてほしいんじゃなくて、質問の文章を英語にしてほしかったんだなぁ……。

ただ、そうかと思えば、意外とアバウトでもいい動きをしたりします。

payload = {
    "inputs": "次の文章に答えてください: 空はなぜ青いのでしょうか?",
    "parameters": {
        "max_new_tokens": 500,
        "top_p": 0.2,
        "temperture": 0.6
    }
}

response = pretrained_predictor.predict(payload)
display(response)

返ってきた答えが、こちら。

[{'generated_text': '\nASSISTANT: 空が青い理由は、太陽からの光が空を通過する際に、地球の大気を通過する際に青い光だけが散乱され、空を照らすからです。また、地球の大気は、青い光を散乱しやすい性質を持っています。'}]

『答える』=『質問に回答する』と解釈して動いてくれました。

では、質問ではない文章にしてみたらどうでしょうか?

payload = {
    "inputs": "次の文章に答えてください: 空は青くてキレイですね",
    "parameters": {
        "max_new_tokens": 500,
        "top_p": 0.2,
        "temperture": 0.6
    }
}

response = pretrained_predictor.predict(payload)
display(response)

返ってきた答えが、こちら。

[{'generated_text': '。\nASSISTANT: はい、空は青くてキレイですね。'}]

おお、なんかAIっぽい返しが来ました。

でもこれ、単なるオウム返しなんじゃないですか?
確かめさせてもらいましょう。

payload = {
    "inputs": "次の文章に答えてください: 青い空は汚いですか?",
    "parameters": {
        "max_new_tokens": 500,
        "top_p": 0.2,
        "temperture": 0.6
    }
}

response = pretrained_predictor.predict(payload)
display(response)

返ってきた答えが、こちら。

[{'generated_text': '\nASSISTANT: 青い空は汚いとは思いません。青い空は美しい自然現象であり、汚いとは感じません。'}]

ほう、ちゃんと一般的な感覚を識別できているようですね。

ここまでを見る限り、学習自体はちゃんとできているようなので、プロンプティングの問題ですかね。
まだファインチューニングの段階まで行っていないんですが、プロンプティングで挙動を知るところから始める必要がありそうです。

とまあ、こんな感じで、生成AIをイチから組み立てることができます。
また、書籍の説明によれば、基盤モデルに手を加えることもできるらしいです。
(費用が怖くて手を出していませんが……)

個人的には、ファインチューニングやRAGの工夫程度で、何か楽しいオモチャでもできないかなと考えています。
費用がちょっと怖いですが、かといって何十万円も出してグラボを買う気にもならないので、AWS という選択肢は割とアリかなと思っています。
もう少し弄ってみて、まとまりそうなら資料に整理してみるのもいいかもしれませんね。

それでは、生成AIを作り始めた、山本慎一郎でした。

お問い合わせ→