💬

Amazon Bedrock カスタムモデルを試してみる

2024/12/09に公開

こんにちは、初めましての方は初めまして。株式会社 Fusic の瓦です。寒さは体を動かせばなんとかなると自分に言い聞かせて(12 月とは思えないほど暖かいというのもあって)何とかまだ暖房を付けずに済んでいます。

この記事は「Amazon Bedrock Advent Calendar 2024」の八日目の記事で、Amazon Bedrock のカスタムモデルを試してみたものとなっています。最初に断っておきますが、精度についてはあまりよくなっていません。 データをもうちょっとちゃんと整形しないといけないのか、パラメータをもっと調整しないといけないのか、そもそもこのデータセットでは難しいのか、今後調査していきたいと思います。

Amazon Bedrock カスタムモデル

Amazon Bedrock のカスタムモデルは、Amazon Titan や Meta の Llama、Anthropic の Claude モデルなどを独自のデータでさらに学習出来るサービスです。 学習方法も「継続的な事前学習」や「ファインチューニング」、つい最近プレビューが発表された「蒸留」の三種類から選べます。例えば自社独自のドキュメントを使って継続的な事前学習をしたり、RAG のサービスで収集したデータを使ってファインチューニングしたりすることで、基盤モデルの言語能力を維持したまま自社の知識について出力させることができるようになります。

以下では実際に、ファインチューニングと蒸留を試していきたいと思います。(蒸留については内部でのエラーが解消できなかったため、訓練を開始するまでを記載しています、すみません)

実際に試してみる

以下では Huggingface で公開されている izumi-lab/llm-japanese-dataset-vanilla[1] を使用していきます。このデータセットはチャット用のデータセットであり、日本特有の知識も多く含まれているためこのデータセットを選択しました。

ファインチューニング

ファインチューニングに対してはドキュメントに記述してあるように、以下のフォーマットの Json Line ファイルを用意します。

{"prompt": "<prompt1>", "completion": "<expected generated text>"}
{"prompt": "<prompt2>", "completion": "<expected generated text>"}
{"prompt": "<prompt3>", "completion": "<expected generated text>"}

データセットを用意するためのコードを以下に記載します。訓練と検証データセットの件数の合計が 10000 件以下、かつ検証データセットの件数の合計は 1000 件以下しか使用できないため、下のように分割しています(本当はシャッフルしてランダムに取ってきた方が偏りがなくなるので良いですが、今回は簡単に検証するため最初の 10000 件を取ってきています)

from datasets import load_dataset
import json

dataset = load_dataset("izumi-lab/llm-japanese-dataset-vanilla", split="train")
sub_train_datasets = dataset[:10000]

with open("train-finetune.jsonl", "w") as f:
    for inst, output in zip(
        sub_train_datasets["instruction"][:9000], sub_train_datasets["output"]
    ):
        json_data = {"prompt": inst, "completion": output}
        f.write(json.dumps(json_data, ensure_ascii=False) + "\n")

with open("valid-finetune.jsonl", "w") as f:
    for inst, output in zip(
        sub_train_datasets["instruction"][9000:], sub_train_datasets["output"]
    ):
        json_data = {"prompt": inst, "completion": output}
        f.write(json.dumps(json_data, ensure_ascii=False) + "\n")

あとはこれを S3 にアップロードし、どのモデルを使用するか、入力データやエポック数などのハイパーパラメータを指定して学習を開始します。

訓練が終了すると、訓練のログが S3 の指定したパス以下に出力されます。実際に出力されたログは以下のようになっていました。

step_wise_training_metrics.csv
step_number,epoch_number,training_loss
1,0.3333333333333333,4.5913
2,0.6666666666666666,4.538
3,1.0,4.5709
4,1.3333333333333333,4.518
5,1.6666666666666665,4.5861
6,2.0,4.4236
7,2.3333333333333335,4.5227
8,2.6666666666666665,4.3372
9,3.0,4.3676
10,3.3333333333333335,4.3308
11,3.6666666666666665,4.3193
12,4.0,4.3143
13,4.333333333333333,4.3578
14,4.666666666666667,4.2234
15,5.0,4.2672
validation_metrics.csv
step_number,epoch_number,validation_loss
3,1.0,5.5887370109558105
6,2.0,5.522740840911865
9,3.0,5.447824954986572
12,4.0,5.405906677246094
15,5.0,5.3969502449035645

訓練データや検証データのロスを見ると、エポックが進むごとに値が減少しており、学習がうまく進んでいることが分かります。この値が大きくなっていったりしている場合は学習がうまくいっていないので、データセットや学習率などを確認すると良いでしょう。

実際に学習が終わったら、プロビジョンドスループットを購入してデプロイします。プロビジョンドスループットには「期間なし」「1ヵ月」「6ヵ月」の三つの選択肢がありますが、とりあえず試したい場合は「期間なし」を選択すると良いと思います。[2]

デプロイが終わったらデフォルトで使用できる LLM と同じように使用できます。以下に boto3 を使用した場合のコードを記載しておきます。以下のコードでは実際の出力だけを取り出していますが、トークン数や出力が終了した理由も含まれているので、それらも使用して開発出来ます。

boto3で叩く例.py
def get_prediction(query):
    res = client.converse(
        modelId="プロビジョンドのARN",
        messages=[{"role": "user", "content": [{"text": query}]}],
        system=[{"text": "You are a helpful assistant."}],
    )
    return res["output"]["message"]["content"][0]["text"]

一応以下に学習する前とした後の結果を載せておきます。冒頭にも書いた通り精度は上がらなかったので、興味がある方だけ開いてみてください。

実際の回答精度結果

以下に回答精度を比較した結果を記述します。回答精度の計算には上述したデータセットの前から 1000 件を使用しました。これは訓練データと被っていますが、そもそも訓練が出来ているかどうかを確かめるためであり、本来精度を確かめたい場合は訓練、検証データセット以外から選ぶべきではあります。

以下に精度を測るためのコードを記載します。LLM からの出力には最初に改行が含まれていたり「~です。」とつけられていたため、それを取り除いています。その後、回答が正解に含まれている場合に限り合っているものとして計算しています。これは「○○(△△も可)」というような正解もあったためです。

correct = 0
with open("llama-8b-base.jsonl") as fin:
    for line in fin:
        data = json.loads(line)
        answer = data["answer"]
        pred = data["prediction"]
        pred = pred.strip()
        if pred.endswith("です。"):
            pred = pred[:-3]
        
        if pred in answer:
            correct += 1

print(f"Acc: {correct / 1000}")

実際に精度を確かめた結果が以下になります。

精度
ファインチューニング前 0.337
ファインチューニング後 0.318

ファインチューニング後の方が回答精度が低下しています。ロス自体は下がっていたのでおそらくハイパーパラメータに問題はなく、今回はデータセットの作り方があまり良くなかったのかもしれません。これについては今後調査していきたいと思います。

蒸留モデル

蒸留モデルについてはこの記事を見ると良いと思います。データセットは自分たちで用意したものと実際の出力ログを用いたものの二種類のどちらかを使用できます。[3]

自分で用意する場合は、以下のフォーマットを一行のデータセットとした Json Line にします。[4]

{
    "schemaVersion": "bedrock-conversation-2024",
    "system": [
        {
            "text": モデルへの指示など
        }
    ],
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "text": ユーザーからの入力
                }
            ]
        },
        {
            "role": "assistant",
            "content": [
               {
                   "text": モデルの出力
               }
            ]
        }
    ]
}

データセットを用意して S3 にアップロードしたら、親となるモデル、子となるモデルを指定して学習を開始します。ただし、自分が試したところ Encountered an internal error when processing the request. というエラーが出力されてうまく学習が終わらなかったため現在原因を調査中です。うまくいくようになったら精度検証をしてこの記事に追記しようと思います。

まとめ

この記事では Bedrock のカスタムモデルを試してみました。ファインチューニングについては学習は出来ているものの、精度は出ていないという結果になりました。これについては今後調査をしていきたいと思います。蒸留は小さいモデルで大きいモデルに匹敵する精度が期待できる手法であり、Bedrock で手軽に出来るようなアップデートが来たことはかなり嬉しく思います。今はまだエラーで試せていませんが、今後精度やスピードに関して検証していきたいと思います。

最後に宣伝になりますが、機械学習でビジネスの成長を加速するために、Fusic の機械学習チームがお手伝いたします。機械学習のPoCから運用まで、すべての場面でサポートした実績があります。もし、困っている方がいましたら、ぜひ Fusic にご相談ください。お問い合わせからでも気軽にご連絡いただけます。また Twitter の DM でのメッセージも大歓迎です。

脚注
  1. https://huggingface.co/datasets/izumi-lab/llm-japanese-dataset-vanilla ↩︎

  2. ただし、デプロイされている間ずっと料金がかかり、料金も安くはない(自分が試した時点では 24$/h でした)ため、試し終わったら消すのを絶対に忘れないようにしましょう。私は消し忘れて 200$ 近く払うことになりました。 ↩︎

  3. 自分たちの用意したもので学習させるならファインチューニングと同じでは…?と思っているのですが、Bedrock の蒸留の裏側を知らないので何とも言えません ↩︎

  4. Amazon の記事に書いてあるように、Anthropic や Meta のモデルは一問一答形式のチャットしか出来ないので、"user" のプロンプトは一つだけしか入れられません。 ↩︎

GitHubで編集を提案
Fusic 技術ブログ

Discussion