Amazon Web Services ブログ

日本語大規模言語モデル OpenCALM の知識でクイズ王に挑戦する

数十億を超えるパラメーターを持つ大規模言語モデルは、追加学習なしに人間も驚く知識を披露します。その知識で、クイズ王に輝くことはできるのでしょうか? 本記事では、株式会社サイバーエージェント様が公開した OpenCALM を用いてクイズを題材にした日本語 QA データセット JAQKET にどこまで正確できるか検証します。クイズに回答するのは、問い合わせ窓口での質問回答業務に近いタスクです。本記事の内容は、お客様対応等の業務を続々とオープンソースで公開される大規模言語モデルを利用してどのようにコスト効率良く改善するのかの検討にも活用頂けます。

OpenCALM は、株式会社サイバーエージェント様から 2023 年 5 月 11 日に公開された日本語大規模言語モデルです。Wikipedia と CommonCrawl から日本語記事を抽出し、 EleutherAI が開発した分散学習に適した GPT の実装 GPT-NeoX で学習しています。注目すべき点として、ライセンスが CC BY-SA 4.0 であり商用利用が可能です。 small, medium, large, 1B, 3B, 7B とパラメーター数の異なるモデルが 6 つ公開されています。

Hugging Face のモデル Model Card より引用した図

JAQKET はクイズを題材とした質問応答データセットです。このデータセットを用いた「 AI 王 〜クイズ AI 日本一決定戦〜」が開催されています。直近の第 3 回は、東北大学の鈴木 潤先生が実行委員長をされています。データセットには次のようなクイズが収録されています。

質問 : 童謡『たなばたさま』の歌詞で、「さらさら」と歌われる植物は何の葉?
回答 : ササ

学習データは過去の早押しクイズ大会の問題を参考に、開発 ( 評価 ) ・テストデータはクイズ作家協力のもと新たに作成されています。開発データは CC BY-SA 4.0 ですが、学習データのうち abc/EQIDEN 実行委員会 が著作権を持つデータは研究目的の再配布が許可されています。データセットの回答は必ず Wikipedia の記事名になるよう整えられています。同様の Q&A 形式のデータセットには日本語の言語理解ベンチマークである JGLUE に含まれる JSQuAD や JCommonsenseQA がありますが、 JSQuAD は質問文に関連するパラグラフ文、 JCommonsenseQA は複数の回答候補が提供されるため言語モデルらしい回答生成の性能よりも抽出、分類性能を評価しているといえます。 JAQKET ではヒントとなるパラグラフ、回答候補がなく回答を自ら生成する必要があるため、言語モデルの生成性能の評価に適していると考えています。

さて、現時点でクイズ AI はどのような作戦でクイズに回答しているのでしょうか ?

AI王 第 3 回コンペティションのリーダーボードによれば、最高精度のモデルは NEC データサイエンス研究所のチーム「レヴォ」が記録した精度 0.924です。最終報告会の資料を参照すると、優勝したレヴォに限らず、ほとんどのチームは Retriever / (Ranker) / Reader の構成を取っています。流れとしては、「 Retriever 」で記事を検索してから、場合によって「 Ranker 」で検索順を調整した後「 Reader 」で回答を生成します。次の図に構成を示します。このような構成は、 Retrieval-Augmented Generation (RAG) と呼ばれます。 RAG という言葉の初出は 2020 年に NeurIPS で発表された “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks” と思われ、大規模言語モデルが全盛となる前から活用されていた技術です。

「 Retriever 」があるということは、外部知識を参照するということです。人間がテスト中に検索エンジンを使ったら怒られると思いますが、 AI には現時点で許容されているということです。外部知識を参照しない GPT を利用したベースラインの実装では精度が 0.344 となっており、これが現時点で「自らの知識のみで」回答する AI の精度といえます。 Retriever を使用したベースラインである AIO-FiD-baseline の精度は 0.597 なので、外部知識を参照することで 25 ポイント以上精度が上がることになります。

大規模言語モデルは、外部知識を参照する AI のベースライン 0.597 にどこまで迫れるか ? に本記事は迫ります。

大規模言語モデルにクイズを解かせる方法

大規模言語モデルにクイズを解かせる方法は 2 つあります。

  1. 追加学習なしで、問題のみ与えクイズに回答させる
  2. 追加学習 (Fine Turning) をしてから回答させる

1 点目は問題に対し答えるよう指示 ( プロンプト ) を与えて回答を得ます。具体的には、以下のようなプロンプトを与え続く文を回答として得ます。問題文の与え方はベンチマークである AIO3_GPT_baseline を参考にしました。

{question}
答えは「

具体的には次のようにプロンプトを与えます。

童謡『たなばたさま』の歌詞で、「さらさら」と歌われる植物は何の葉?
答えは「

一般的な言語モデルはひたすら次の単語 ( 厳密には単語とは異なるトークンという単位 ) を生成するよう訓練されており、回答を端的に答えるよう訓練されていません。そのため、提供されている学習データで追加学習 (Fine Tuning) を行い回答の様式を学習させる 2 点目のケースでも検証します。

2 点目は、提供されている学習データを用いクイズの回答形式 (Instruction) を学習させた後に回答させます。実際の例を見てみましょう。まず、 JAQKET では次の JSON 形式でクイズが収録されています。question が問題文で、answers が回答です。回答が配列となっていますが、学習データでは 1 つのみで、開発データ・テストデータでは別解があることがあり回答がどれかに一致すれば正解となります。

{
    "qid": "QA20QBIK-0002",
    "competition": "第1回AI王",
    "timestamp": "2020/01/27",
    "section": "開発データ問題 (dev1)",
    "number": "2",
    "original_question": "童謡『たなばたさま』の歌詞で、「さらさら」と歌われる植物は何の葉?",
    "original_answer": "ササ",
    "original_additional_info": "",
    "question": "童謡『たなばたさま』の歌詞で、「さらさら」と歌われる植物は何の葉?",
    "answers" :["ササ"]
}

追加学習用に加工すると次のデータ ( 文書 ) になります。 question がプロンプトの形式に加工され、 answers が結合しています。これにより、プロンプトとして赤字のパートが渡された後に回答と括弧閉じを生成するよう学習させます。

童謡『たなばたさま』の歌詞で、「さらさら」と歌われる植物は何の葉?
答えは「ササ」

検証には第 2 回・第 3 回コンペティション向けに作成されたデータセットを使用します。学習用データは 22,335 問、開発中の評価に使用する開発用データは 1000 問で構成されます。今回はこの 1000 問で性能を評価します。大規模言語モデルとして、 OpenCALM の、 1B 、 3B 、 7B の3種類で検証します。比較対象として、 OpenAI API から使用することのできる GPT-3.5 のモデルである text-davinci-003 、 gpt-3.5-turbo (ChatGPT 3.5) 、また Fine Tuning 機能で追加学習したモデルで性能を計測します。 text-davinci-003 と gpt-3.5-turbo は同程度の性能と言及されていますが (Chat Completions vs. Completions: “For reference, gpt-3.5-turbo performs at a similar capability level to text-davinci-003”) 、前者は OpenCALM のような素の言語モデルに近く、後者は user/system 間の対話形式を学習させています。呼び出す API も前者は Completions API、後者は Chat Completions API とエンドポイントが異なります。なお、 Fine Tuning 機能では Completions API で使用されているモデルに対してしか行うことができません。

本節では大規模言語モデルにクイズを解かせる 2 つの方法を整理し、回答に使うモデルとして OpenCALM の 1B 、 3B 、 7B 、比較用として OpenAI API の text-davinci-003 、 gpt-3.5-turbo 、Fine Tuning 機能で追加学習したモデルの計 6 つを使うことを明示しました。では、実際にクイズを解かせてみましょう!

検証① : 問題を与え回答させる

検証①では、素直に問題文を与え出力された文字列を回答として評価をします。 OpenCALM から回答を得る流れ、 OpenAI API から回答を得る流れをそれぞれ見ていきます。

OpenCALM から回答を得るため Amazon SageMaker で推論用のエンドポイントを立てます。エンドポイントの実体は Docker コンテナが動いているサーバーで、コンテナのイメージはあらかじめ Deep Learning Containers (DLC) として提供されており、 TensorFlow や PyTorch など使っているフレームワークに合わせて選ぶことができます。そのため、推論用のスクリプトとスクリプトを動かすための依存ライブラリの定義ファイル (requirements.txt) を用意すればすぐに推論のエンドポイントを立てることができます。

サンプルの OpenCALM SageMaker Inference for JAQKET dataset がどのように推論用のエンドポイントを立てているか解説します。 Notebook 上の次のコードでは、 PyTorchModel 、すなわち PyTorch のコンテナを使用することを宣言しています。モデルや推論用のコード一式を収めた scripts フォルダを直前のセルで tar.gz でひとまとめにして、 Amazon Simple Storage Service (Amazon S3) に送っています。送り先の S3 パスを model_data の引数に渡しています。環境変数 (env) として、base_modelcyberagent/open-calm-7bprompt_template として simple_qa_ja などを指定しています。prompt_template の中身は scripts/code/templates で見ることができます。 その後は、 deploy のメソッドでインスタンスなどを指定しデプロイしています。ml.g5.2xlarge は NVIDIA A10G を搭載した GPU メモリ 24GB のインスタンスです。

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

endpoint_name = "OpenCALM"

huggingface_model = PyTorchModel(
    model_data=model_path,
    framework_version="1.13",
    py_version="py39",
    role=role,
    name=endpoint_name,
    env={
        "model_params": json.dumps(
            {
                "base_model": model_name,
                "peft": False,
                "load_8bit": True,
                "prompt_template": "simple_qa_ja",
            }
        )
    },
)

# 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(),
)

scripts/code/inference.py が推論用のスクリプトです。 DLC で稼働させるため、 model_fn 関数でモデルをロード、 predict_fn で推論を行うなどあらかじめ関数と役割が決められており、中身の処理を実装していく形になります。詳細は Use PyTorch with the SageMaker Python SDK を参照してください。

実際推論してみましょう。 Notebook 上で推論の呼び出しは次の関数で行っています。

def inference(instruction):
    data = {
        "instruction": instruction,
        "max_new_tokens": 32,
        "temperature": 0.1,
        "do_sample": False,
        "top_k": 500,
        "top_p": 0.95,
        "num_beams": 5,
        "pad_token_id": 1,
        "bos_token_id": 0,
        "eos_token_is": 0,
        "stop_ids": [1, 0],
    }
    response = predictor_client.predict(data=data)
    return response

predict でエンドポイントに送信したデータは scripts/code/inference.pyinput_fn 関数を通り、最終的に Prompter クラスの generate_prompt 関数により prompt_template に従い「大規模言語モデルにクイズを解かせる方法」で言及したプロンプトの形式に変換されます。

ハイパーパラメーター、特に top_k, top_p, num_beams は精度に影響を与えます。生成を行う際、 次のトークンとして確率が高い top_k ( 下記の場合 500 ) かつ top_p( 0.95 ) を超えるものを候補にし、個別単語でなく生成文全体として確率が高くなるよう num_beams ( 下記の場合 5 ) の文候補から選ぶというような意味なります。性能以外に再現性を高めるため、今回 temperature は 0 に近い値、 do_samplesFalse としています。 Hugging Face では temperature として 0 より大きい値でなければいけないため 0.1 としています。

OpenAI の Completions APIChat Completions API では直接プロンプトを与えて回答を得ます。サンプルの OpenAI API Completions Inference for JAQKET dataset では answer メソッドで回答を得ています。 OpenAI は多言語のモデルなので、日本語で回答するよう最初に指示を入れています。

import re

def answer(model: str, question: str) -> str:
    openai.organization = OPENAI_ORGANIZATION
    openai.api_key = OPENAI_API_KEY

    template = "日本語のクイズに答えてください。\n{instruction}\n答えは「"
    prompt = template.format(instruction=question)

    response = openai.Completion.create(
        model=model,
        prompt=prompt,
        max_tokens=64,
        temperature=0,
        top_p=1,
        n=1,
        stop="\n",
    )

    _answer = response["choices"][0]["text"]
    _answer = re.findall("「(.*?)」", prompt + _answer)[-1]
    return _answer

OpenAI のドキュメント Chat Completions vs. Completions にある通り、双方の API はごくわずかな書き換えで呼び出すことができるため、 Chat Completions API を呼び出すサンプル OpenAI API Chat Completions Inference for JAQKET dataset との差分はわずかです。 ただし、 Chat Completions の方は頻繁に Rate Limit にかかることがあり time.sleep 等の処理を入れる必要がありました。

OpenCALM 、OpenAI API 双方での推論の仕方を解説しました。推論の結果は次のようになりました。

種別 パラメーター数 Accuracy
( 検証① )
Baseline GPT 1B 0.344
AIO-FiD-baseline 0.597
OpenCALM 1B 1B 0.211
3B 3B 0.207
7B 7B 0.318
OpenAI GPT3.5
(text-davinci-003)
非公開 0.216
GPT3.5
(gpt-3.5-turbo)
非公開 0.458

結果は、 gpt-3.5-turbo のスコアが 0.458 となりベンチマークの GPT の精度 0.344 を超えました。 OpenCALM はベンチマークの GPT に及ばなかったものの、 175B 以上と言われる text-davinci-003 よりも 25 ~ 175 倍少ないパラメーター数で同程度の正解率が得られています (GPT 3.5 のパラメーター数は非公開のため、厳密な差異は分からない点にご留意ください )。 1B と 3B ではあまり違いがありませんでしたが、 7B でぐっと精度が上がっています。 GPT-4 は金額の問題で今回検証していませんが、精度が増す代わりにパラメーターサイズのギャップは大きくなるはずです。 Training language models to follow instructions with human feedback によれば、 text-davinci-003 で使われている InstructGPT は対話形式で扱えるようにするために教師有り学習と人間の回答の嗜好データに基づく強化学習を行っています。 OpenCALM がそのような追加学習を行わずさらに少ないパラメーターで同等の精度を達成していると考えると、非常にコスト効率良いモデルといえると思います。 gpt-3.5-turbo (ChatGPT) を作るためにどのような学習をしているか詳細は明かされていませんが、仮に OpenCALM で同等の学習をした場合それ以上の精度が期待できると考えられます。

なお、評価にあたり実質的に回答と同じ場合でも文字列が一致しない場合不正解としています。たとえば正解は「げっ歯類」で生成された回答が「齧歯類」の場合、実質同じですが文字列が異なるため誤りとしています。このような回答はだいたい 100 件中 3 ~ 6 件ほど含まれるので、揺らぎを加味した場合 0.03 ~ 0.06 程度は上がる可能性があります。

検証② : Fine Tuning をした後回答させる

クイズの回答形式を学んでもらうため、 Instruction tuning と呼ばれる追加学習を行います。素の言語モデルはクイズを与えられてもクイズの続きを考えればいいのか回答すればいいのかわからないので、こちらの意図した生成結果が得られる確率が低いです。そのため、回答形式を教えることで出題者の意図に沿った生成を行えるようにします。自然言語による指示に基づきタスクが解けるよう学習させることを Instruction tuning と呼びます。 Instruction tuning の初出は Fine-tuned Language Models Are Zero-Shot Learners で、通常の教師有り学習による追加学習 (Fine Tuning) が目的に特化したデータと学習を行うのに対し、 Instruction tuning は複数のタスクを表す指示 (Instruction) をもとに指示されたタスクを解く方法を学びます。この定義において今回はクイズという単一のタスクを解くため Fine Tuning と同義ですが、 Instruction Tuning の形式に沿い学習させます。

大規模言語モデルは文字通り大規模であり、できれば 1 つのモデルで複数の問題を解かせたいところです。そうでなければ、解きたいタスクごと追加学習した大規模なモデルを何個も抱えることになるためです。そこで、大規模言語モデルのパラメーターは固定し、チューニング用のパラメーターを別途用意して学習させる LoRA(LoRA: Low-Rank Adaptation of Large Language Models) という手法が開発されました。 LoRA で学習したパラメーターを大規模言語モデルにつけ外しすることで、学習先のドメインやタスクに特化させることができます。Hugging Face から peft と呼ばれるライブラリが公開されており、これを利用することで簡単に LoRA での学習を行うことができます。

では、 OpenCALM のモデルを LoRA の手法で Instruction Tuning してみましょう。はじめに、クイズの問題を Instruction Tuning 用の形式に変換します。

{
   "instruction":"会社を設立した2人の名前から社名をとった、「hp」というロゴで知られるパソコンメーカーはどこでしょう?",
   "output":"ヒューレット・パッカード」",
   "input":""
}

この Instruction Tuning 用のデータは、最終的には次の文に成型されます。この文書により、クイズとして赤字のプロンプトが出題されたら回答して括弧を閉じるよう学習させます。

会社を設立した2人の名前から社名をとった、「hp」というロゴで知られるパソコンメーカーはどこでしょう?
答えは「ヒューレット・パッカード」

次に、変換したデータで LoRA による学習を行います。サンプルの OpenCALM_LoRA_jaqket.ipynb では、次のコードから学習を起動しています。学習の実体は scripts/code/finetune.py です。

huggingface_estimator = HuggingFace(
    base_job_name=model_name_base,
    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})

finetune.py の次の箇所で 「チューニング用のパラメーターを別途用意した」モデルが得られます。 target_modules はチューニング用パラメーターを用意するモデルの箇所、 r は低いほどパラメーター数が少なくなります。

    config = LoraConfig(
        r=lora_r,
        lora_alpha=lora_alpha,
        target_modules=lora_target_modules,
        lora_dropout=lora_dropout,
        bias="none",
        task_type="CAUSAL_LM",
    )
    model = get_peft_model(model, config)

モデルの学習は通常のモデルと同様 transformers.Trainer で学習することができます。次の画像は OpenCALM 7B のモデルを学習させたときの train_loss と eval_loss の状況を CloudWatch のメトリクスで表示した図です。双方きちんと下がっていることがわかります。

OpenAI の GPT でも Instruction Tuning を行ってみましょう。 Instruction Tuning は Fine Tuning の機能を使って行いますが、学習させることができるのは Completions のモデルにのみです。なお、どのような手法で Fine Tuning が行われるのかは明らかにされていません。与えるデータ項目は promptcompletion と決まっています。サンプルの OpenAI API Fine Tuning for JAQKET dataset では次のコードで JAQKET のデータをこの形式に揃えています。

PROMPT_TEMPLATE = "日本語のクイズに答えてください。\n{instruction}\n答えは「"


def convert_for_fine_tune(df: pd.DataFrame) -> pd.DataFrame:
    df_fine_tune = df[["question", "answers"]].rename(
        columns={"question": "prompt", "answers": "completion"}
    )
    df_fine_tune.prompt = df_fine_tune.prompt.map(
        lambda p: PROMPT_TEMPLATE.format(instruction=p)
    )
    df_fine_tune.completion = df_fine_tune.completion.map(lambda c: f"{c[0]}」")
    return df_fine_tune


fine_tune_file_name = "jaqket_fine_tune.jsonl"
df_fine_tune = convert_for_fine_tune(df_train)
df_fine_tune = df_fine_tune.drop_duplicates("prompt")
df_fine_tune.head(3)

加工した結果は次のようになります。

OpenAI のエンドポイントに送り学習します。デフォルトでは、学習させるベースのモデルは curie となります。 curie のパラメーターは非公開ですが、 On the Sizes of OpenAI API Models ではベンチマーク性能から GPT-3 の 6.7B 程度のモデルと推察しています。

!openai api fine_tunes.create -t data/{fine_tune_file_name}

text-davinci-003 で使われている davincicurie の価格の 10 倍で、金額の問題で今回は検証していません。仮にお財布に余裕があっても、 OpenAI API は月 $120 までしか使うことができず必要な実験を慎重に見極める必要があります。コストの比較についても後程行います。

OpenCALM と OpenAI API の Completions のモデルを Instruction Tuning する方法を見てきました。では、結果を見てみましょう。検証①に、追加学習を行った検証②の結果を加えた表が下表です。

種別 パラメーター数 Accuracy
( 検証① )
Accuracy
( 検証② : 1000 件 )
Accuracy
( 検証② : 全件 )
Baseline GPT 1B 0.344
AIO-FiD-baseline 0.597
OpenCALM 1B 1B 0.211 0.274 0.390
3B 3B 0.207 0.432 0.431
7B 7B 0.318 0.400 0.511
OpenAI GPT3.5
(text-davinci-003)
非公開 0.216
GPT-3
(curie)
非公開 0.074
GPT3.5
(gpt-3.5-turbo)
非公開 0.458 (不可) (不可)

OpenCALM 7B は、追加学習を行うことで gpt-3.5-turbo の精度を超えることができました。外部知識を参照する AI のベースライン 0.597 にあともう少しで、回答の揺らぎを考慮したり、ハイパーパラメーターを調整することで到達できそうな範囲に見えます。 OpenAI は Fine Tuning することで逆に精度が下がる傾向が見られました。 curiedavinci に比べモデルが一世代前でパラメーター数もおそらく少ないことが影響したと考えられます。とはいえ OpenCALM 7B と同程度のパラメーターサイズと見込まれることを考えると、精度的には物足りない結果となりました。 LIMA: Less Is More for Alignment では 1000 件程度の厳選されたサンプルによる Instruction Tuning で十分効果が得られるとしており、今回ランダムに抽出した 1000 件のみでの学習も検証しました。全体 22,335 問を学習した場合の約 70% が得られる結果になっています (3B では 1000 件で同等 ) 。1000 件、 2000 件、 4000 件、と増やした場合で確認したところ線形に精度は上がりますが、最初の 1000 件で高いところまで精度を上げることができています。

OpenCALM と OpenAI とで、クイズの回答結果から一部を抜粋し比較した表を掲載します。上 4 つは双方正解したクイズ、下 4 つはいずれも不正解だったクイズを列挙しています。 OpenCALM と OpenAI とでクイズの得意 / 不得意を調査したいところですが、クイズのカテゴリが定義されておらず統計的な比較は行えていません。ざっと見たところ、特に大きな違いはありませんでした。

回答 問題文 回答 OpenCALM (7B) OpenAI (turbo)
正解 大腸の運動や分泌機能の異常で起こる「過敏腸性症候群」のことをアルファベット3文字の略称で何という? IBS IBS IBS
正解 「Trp」という略号で表される、チーズや牛乳などに多く含まれ、睡眠を促す効果があるとされる必須アミノ酸の一種は何? トリプトファン トリプトファン トリプトファン
正解 企業買収の際などに行われる「株式公開買付」のことをアルファベット3文字で何という? TOB TOB TOB
正解 オーストラリアの公用語は英語ですが、オーストリアの公用語は何語? ドイツ語 ドイツ語 ドイツ語
不正解 氷った海に穴を開けて漁をすることから、漢字で「氷の下の魚」と書くタラ科の魚は何? コマイ スケトウダラ アイナメ
不正解 作家の森鴎外、斎藤茂吉、安部公房は、現在の東京大学の何学部出身? 医学部 文学部 法学部
不正解 椋鳩十の童話『大造じいさんとガン』に登場する、ガンの群れの頭領の名前は何? 残雪 大造じいさん クロネコ
不正解 1969年にアポロ11号が初めて月面着陸した場所は何の海? 静かの海 マーシャル諸島 トランキリティ海

推論と学習のコストの検証

推論、学習のコスト面について検証を行います。はじめに、推論にかかったコストを一覧で示します。 1000 件の開発用データにかかったコストを比較します。インスタンスは時間単位、 OpenAI API はトークン単位で課金されるため列を分けています。

種別 実験環境 単価 (※1) 開発データの
推論時間 (時) ※2
開発データの
トークン数
推論総額 ($)
OpenCALM 1B g5.2xlarge (A10G) $1.212 / 時 0.075 0.091
3B g5.2xlarge (A10G) $1.212 / 時 0.094 0.114
7B g5.2xlarge (A10G) $1.212 / 時 0.133 0.161
OpenAI GPT3.5
(text-davinci-003)
非公開 (175B?) $0.0200 / 1K tokens 73,458 1.469
GPT3.5
(gpt-3.5-turbo)
非公開 (175B?) input: $0.0015 / 1K tokens
output: $0.002 / 1K tokens
input: 52380
output: 5925
0.090

※1 : 価格は 2023 年 6 月 15 日時点のものです

gpt-3.5-turbo は性能が追加学習した OpenCALM 7B のモデルと同程度でありつつ価格は 1B クラスのモデルを A10G で推論した場合と同額でありコスト効率が良いと言えます。ただ、実際に使うと頻繁に Rate Limit が発生しリトライ処理が不可欠でした。また、上記の表は OpenCALM 側に推論最適化とインスタンス稼働率の点で改善の余地があります。 OpenCALM の推論について、 1) バッチ推論化することで大幅にスループットを向上 2) C++ 実装の CTranslate2 で 8bit 化して推論することでさらにスループットを向上できること (1B については 8bit にした場合精度の低下を確認したため FP32 で推論 ) 、 3) g5.xlarge で同等の推論速度が達成できることを確認しています。 CTranslate2 を使用した推論は OpenCALM SageMaker Inference with CTranslate2 をご参照ください。 3点の改善を加えた場合、 gpt-3.5-turbo に比べ 1B (FP32) は約 35 倍、 3B は約 37 倍、 7B は約 20 倍低コストになりました。効率化を行うことで、推論コスト比はパラメーターの比に近づきます。

種別 実験環境 単価 (※1) 開発データの
推論時間 (秒)
推論総額 ($) 推論総額 ($)
(gpt-3.5-turbo)
推論コスト比
OpenCALM 1B g5.xlarge (A10G) $1.006 / 時 9.28 0.0026 0.0904 34.868
3B g5.xlarge (A10G) $1.006 / 時 8.75 0.0025 0.0904 36.979
7B g5.xlarge (A10G) $1.006 / 時 16.1 0.0045 0.0904 20.098

次に、追加学習のコストを見てみましょう。 OpenCALM の価格は通常の単価で計算していますが、スポットインスタンスを使うことでよりコストを下げられます。 OpenAI の Fine Tuning は、単純に単価と学習トークン量を掛けると $5 程度なのですが、実際には $28 ぐらい請求が来ました。請求額は、 openai api fine_tunes.get -i (fine tuning のid) で参照することができます。 Epoch 数が 4 になっているので、少なくともデータセット 1 週当たりのコスト x 4 倍にはなっていると推察されます。具体的な計算方法を探すことができなかったのですが、 GPU インスタンスで学習するよりコスト効率はかなり悪くなります。

種別 実験環境 単価 学習データの
学習時間 (時) ※2
学習データの
トークン数
学習総額 ($)
OpenCALM 1B g5.2xlarge (A10G) $1.212 / 時 0.107 0.130
3B g5.2xlarge (A10G) $1.212 / 時 0.152 0.184
7B g5.2xlarge (A10G) $1.212 / 時 0.350 0.424
OpenAI GPT-3
(curie)
非公開 $0.003 / 1K tokens 1,673,892 27.970

Key takeaways

検証結果から得られた示唆をまとめます。

  1. 回答のみから推論した場合の精度として、 OpenCALM は GPT3.5(text-davinci-003) の 精度 0.216 と同程度の精度を 1B のパラメーターのモデルで出すことができる。 GPT-3.5 のパラメーター数は各種記事によれば 175B とも言われ、この場合最大 175 倍小さいモデルで同等の性能が出せていることになる。また、 GPT-3.5 が教師有り学習と強化学習で Instruction Tuning 済みであることを考えるとモデル構築にかかるコストの面でも優位である。
  2. 追加学習してから推論した場合の精度として、 OpenCALM の 3B と 7B のモデルは gpt-3.5-turbo (ChatGPT 3.5) の精度 0.458 と同程度以上の精度 0.431 、 0.511 に達することができる。さらに、 1000 件のデータで全データで学習した時の 70% 程度の精度を得ることができる。
  3. 推論価格について、 gpt-3.5-turbo は 1B の OpenCALM を A10G のインスタンス (g5.2xlarge) で推論した時と同程度であり、精度が追加学習後の 7B のモデル以上であることを考えると破格といえる。しかし、 Rate Limit により安定性に欠ける面がある。 OpenCALM は g5.xlarge へ切り替えること、 CTranslate2 を利用すること、 バッチ推論を行うことで gpt-3.5-turbo に比べ 20 倍 ~ 37 倍低価格で推論でき、コスト効率良くかつ安定的に推論する場合適している。
  4. 追加学習のコストについて、パラメーター数が 6.7B と推察される curie  をベースにすると精度が 0.074 と検証したモデル中最も低くなりコストも OpenCALM をインスタンスで追加学習させるのに比べ 67 倍以上高くなった。精度・コスト的に優位な gpt-3.5-turbo は追加学習できない点には留意する必要がある。

総じて、サービスを提供する際に Rate Limit が影響を与えない、かつプロンプトの与え方を変えることで十分目的を達成することができる場合は gpt-3.5-turbo が良い選択肢になるといえます。ただ、プロンプトの量がかさむほど料金や出力結果のパースが困難になることは留意してください。一方、サービスとして安定的に推論する必要があり、将来独自のデータによる追加学習で精度面や機能面で差別化を図りたい場合、 OpenCALM をはじめとするオープンソースのモデルを使うことが推奨できます。 2 つの選択肢は二律背反ではなく、最初は OpenAI を利用しつつもオープンソースのモデルに切り替えていくこともできます。 プロダクトの成長をリードする生成系 AI の活用戦略 では OpenAI API など使いやすい API を利用した短期の迅速な検証からデータを蓄積し長期的なプロダクト価値の向上に至るためのロードマップの作り方を解説しているので参考にしてください。

なお、上記の結論にはいくつか制約があります。

まず、今回は複数のプロンプトで実験を行っていません。質問応答のプロンプトには Stanford 大学が公開する HELM ベンチマークの評価用スクリプトにあるよう、コロンと改行で区切る方式もあります。こちらの方式でも少し試していますが、精度はそれほど大きく変化しませんでした。

質問:
童謡『たなばたさま』の歌詞で、「さらさら」と歌われる植物は何の葉?

回答:
ササ

次に、ハイパーパラメーターのチューニングをほとんど行っていません。今回は外部知識を参照する AI のベースライン 0.597 にあと一歩及びませんでしたが、プロンプトやハイパーパラメーターのチューニングにより差が縮まる可能性があります。

最後に、クイズのカテゴリが明確でないため OpenCALM / OpenAI が得意・不得意とするクイズは明らかでありません。言語モデルによって得意不得意の性質の差があれば、用途に応じ使い分ける必然性がより明らかになります。

クイズの応用

今回はクイズのデータセットを使用しましたが、すでに QA のデータが手元にあれば同じ方法でクイズ王を作成することができます。追加学習に使用したサンプルの、データを作成する箇所を書き換えることで独自のデータで追加学習できます。

import pandas as pd

# data/aio_02_train.jsonl を独自データに変更し、 instruction / output の形式に合わせる
df = pd.read_json("data/aio_02_train.jsonl", orient="records", lines=True)
df = df.rename(columns={"question": "instruction", "answers": "output"})
df = df[["instruction", "output"]]
df["output"] = df["output"].apply(lambda x: f"{x[0]}」")
df["input"] = ""
print(df.shape)
# data/aio_02_train_formatted.jsonl を独自データのファイル名に変更
df.to_json(
    "data/aio_02_train_formatted.jsonl", orient="records", force_ascii=False, lines=True
)

ハイパーパラメータの data_path をアップロードしたファイル名に変更します。

# data/aio_02_train_formatted.jsonl を独自データのファイル名に変更
hyperparameters={
...
  'data_path': '/opt/ml/input/data/train/aio_02_train_formatted.jsonl'
...
}

ぜひ、コードを活用し実際の問題を解いてみてください!

おわりに

本記事では、日本語の大規模言語モデル OpenCALM をつかって日本語クイズの問題がどの程度解けるのかをモデルの大きさ、学習させるデータセットのサイズを変えながら検証しました。JAQKET は第 4 回の大会が開催決定しているので、そこでどのようなモデルが登場するのかも楽しみですね。本記事の結果を、ぜひ業務負荷の軽減にも役立てて頂ければ幸いです!

著者プロフィール

久保 隆宏 (Takahiro Kubo) は AWS Japan の機械学習領域のデベロッパーリレーションを担当しており、「機械学習をするなら AWS 」と感じて頂くべくコンテンツの作成とフィードバックの収集による AWS サービスの改善を行っています。
前川 泰毅 (Taiki Maekawa) は AWS Japan のソリューションアーキテクトでメディア領域のお客様中心にアーキテクチャ設計や構築を支援しています。機械学習領域を得意としておりソリューションやサンプルコードの作成を行っています。