HN 日本語サマリー

← 一覧へ戻る
AI・機械学習

Geminiに9ドルで自身の後継機をトレーニングさせた

I had Gemini train its own replacement for $9 (petervijeh.com)

80 pointsby p-s-v34 コメント

要約

この記事は、Redditのナイフ愛好家フォーラムからブランド、モデル、素材名を抽出するタスクにおいて、高価なGemini APIの代わりに、より安価でオープンソースのGLiNERモデルをファインチューニングするプロジェクトについて詳述しています。Geminiによる9ドルのラベル付けデータセットを用いてGLiNERをトレーニングした結果、Geminiの精度(F1スコア約0.65)に対し、GLiNERは0.83のF1スコアを達成し、コストを大幅に削減しつつ、ローカルでの高速処理を実現しました。

全文翻訳

私は料理が好きで、いつの間にか高級シェフナイフへの執着に変わりました。そこで、人々がそれについて議論しているRedditのスレッドをスクレイピングし、ブランド、モデル、鋼材をすべて抜き出して、何が買われ、議論されているのかを見ています。 テキストから製品名を抽出する仕事は、固有表現認識(named-entity recognition)と呼ばれ、小さなモデルなら10年前からできています。私はGemini 3.1 Proを使って、コメントごとに1回の有料API呼び出しでこれを実行していました。やりすぎですが、うまくいきました。「白い#2のMazakiを手に入れた、古いFibroxよりずっと良い」というコメントから、Mazakiをブランド、Fibroxをモデル、白い#2を鋼材として抽出し、それ以外は何も返しませんでした。しかし、スクレイパーは新しいコメントをすべてプルするため、投稿量に応じて請求額が増加し、上限を設定する唯一の方法はコメントをスキップすることでした。 明らかに代替となる、GLiNERというオープンNERモデルをゼロショットで実行すると、コストはゼロになり、精度は約0.65 F1(推定)に低下しました。このギャップが、残りの記事の主題です。Geminiに4,290件のコメントを一度だけラベル付けさせ、GLiNERにそのギャップを埋めるように教えることはできるでしょうか? 何をしたか: Gemini 3.1 Proが一度書いたラベルで、ブランド、モデル、素材をタグ付けするために、GLiNER large v2.5 (459M)をファインチューニングしました。 なぜか: ゼロショットGLiNERは、約0.65 F1(推定)を記録しました。Geminiはうまく機能しましたが、スクレイパーが実行されている間、すべてのコメントに請求しました。 アプローチ: Geminiにオフセットではなく文字列を要求しました。コードでオフセットを計算しました。製品を含まないコメントをネガティブとして追加しました。2番目の実行の前に225件のコメントの検証セットをロックしました。 問題: 10回の実行のうち5回は、使用可能なモデルを生成しませんでした。3回は設定で失敗しました。2回は、注意マスク(attention mask)と同じように埋めたwords_maskというテンソルで失敗しました。 結果: Tesla T4で24分間実行し、Geminiのラベルに対して0.83 F1を達成しました。ラベルに9ドル、GPU時間に約2.50ドル、デバッグに数日かかりました。 私がやろうとしたこと 計画は3つのステップでした。Geminiに数千件のRedditコメントを一度だけラベル付けさせ、すべてのブランド、モデル、鋼材にマークを付けさせます。そのラベルでGLiNERをトレーニングします。その後、それ以降のすべてのコメントを自分のマシンでGLiNERを実行し、Geminiへの呼び出しを停止します。 Geminiはコメントあたり0.0021ドル、つまり9ドルで4,290件のコメントにラベルを付けました。これは、後続のコメントが同じくらいの長さで、私がすでに所有しているGPUで実行される限り、トレーニング済みモデルは約4,291件目のコメントで元が取れることを意味します。成功のテストは単純でした。モデルが一度も見たことのない225件のコメントで、Geminiがタグ付けしたのと同じ単語をモデルがタグ付けした頻度はどれくらいか?この記事のすべてのスコアで覚えておくべき1つの注意点があります。Geminiのラベルを手作業でチェックした人はいなかったので、モデルは真実に対してではなく、Geminiに対して採点されています。Geminiが間違っていた場合、モデルは間違いをコピーしたことで正解となり、それを修正したことで不正解となります。 アプローチ GeminiはOpenRouterを介して、温度0で25分かけてコメントにラベルを付けました。最も重要だったプロンプトの決定は、モデルに文字オフセットを要求しないことでした。文字のカウントが悪く、スパンが2〜3文字ずれて返されます。プロンプトは正確な部分文字列とラベルを要求し、TypeScriptがオフセットを見つけます。文字列がコメントにない場合、エンティティはドロップされ、ログに記録されます。 // モデルは文字列を返します。コードがオフセットを計算します。 { "entities": [ { "text": "Benchmade", "label": "knife brand" }, { "text": "940", "label": "knife model" }, { "text": "S30V", "label": "knife steel" } ] } 製品名には句読点が多く含まれており、一般的なトークナイザーはそれを分割しますが、正規表現はVG-10、CPM-154、1.4116をそのまま保持し、それ以外の空白以外の文字を個別のトークンとして出力します。まだトークン境界を外れているスパンは、推測するのではなくドロップされます。トレーニングセットの約30%は、製品を含まず、既知の偽陽性トリガー(gyuto、carbon、handle、patina)を含むコメントで、空としてラベル付けされています。2回目の実行の前に、225件のコメントを検証セットとして確保し、それ以降は一切触れませんでした。トレーニングはTesla T4でModalとHF Trainerを使用して実行されました。 per_device_train_batch_size = 2 gradient_accumulation_steps = 8 learning_rate = 1e-5 threshold = 0.45 モデルがトレーニングされたもの モデルがトレーニングされたもの 4,290件のフォーラムコメントをLLMが9ドルでアノテーション 1,575件のポジティブ(69.6%) 675件のネガティブ 2,250件のトレーニング例 2,029件のトレーニング 225件の検証(検証セットは実行10からロック済み) 3,907件のエンティティスパン ブランド 1,720件 製品モデル 1,345件 素材仕様 842件 例の約30%は意図的にエンティティを全く含んでいません。 何がうまくいかなかったか 5回の実行では、モデルは何も学習しませんでした。最初の3回は設定で失敗し、GLiNERでHF Trainerを使用する人は誰でも午後中に遭遇するでしょう。 実行 何がうまくいかなかったか 修正 1 GLiNERのデフォルトmax_steps=10000がnum_train_epochs=3を上書き; 39エポックトレーニング max_stepsを明示的に設定 2 load_best_model_at_end が eval_strategy なしで例外をスロー eval_strategy="steps" を設定 3 Trainerが状態辞書のキーを "model." 接頭辞なしで保存 接頭辞を保存に戻す 4 ner_labels がネガティブ例で欠落 すべての例にラベルリストを設定 4–5 words_mask がバイナリとして構築; ロスが70–130でフラット 増分単語インデックスを出力 実行4と5は高価なものでした。GLiNERのtokenize_inputsは壊れたReddit絵文字でクラッシュしたため、パッチを当てましたが、そのパッチはwords_maskというテンソルを埋める必要がありました。これはattention_maskの隣にあり、同じ形状で、私がこれまでに構築したすべてのattention maskは、実際のトークンに対して1、パディングに対して0でした。私はそのように構築しました。名前や形状から、それがattention mask以外の何ものでもないということは何も示唆されていませんでした。トレーニングは完了しました。ロスは130前後から始まり、約70まで低下し、そこに留まりました。クラッシュも警告もNaNもなく、通常のサイズの勾配、スケジュール通りのチェックポイント保存、評価F1はゼロ近くでした。最初にラベルリストを疑いました。なぜなら、実行4でもラベルが設定されていないネガティブがあったからです。それを修正して再実行しても、同じフラットなロスが得られました。実行5で唯一間違っていたのはwords_maskでした。これは私が一度も見たことのないテンソルでした。 10回のトレーニング実行 10回のトレーニング実行 最初の5回は、モデリングなしの配管のみで、使用可能なモデルを生成しませんでした 0.0 0.2 0.4 0.6 0.8 1.0 失敗 本番で最高のF1を記録 ✕ R1 ステップキャップ ✕ R2 設定 ✕ R3 チェックポイント ✕ R4 単語マスク ✕ R5 単語マスク スコアなし R6 初めて成功 0.800 R7 チューニング 209M 0.879 R8 459M 0.799 R9 ネガティブが多すぎる 0.832 R10 検証セットロック 実行1〜5は完全に失敗しました。破線は、単語マスク修正後の全体的なF1を示しています。 修正方法 ドキュメントではなく、GLiNERのトレーニングループを読みました。words_maskはマスクではありません。それは単語インデックスです:特殊トークン、プロンプトトークン、パディングトークンは0、その後、各実際の単語の最初のサブトークンに対して1、2、3となります。スパンスコアリングヘッドは、これを使用してサブトークンを単語にプールします。1で埋めると、コメント全体が1つの単語であるとみなされ、モデルは1つの巨大なトークン内でブランドと素材のスパンを見つけるように求められます。それはできないため、ロスはフラットなままで、フラットなロスはどの入力が間違っているかを示しません。 # 私が書いたもの # GLiNERが期待するもの words_mask = [1,1,1,1,1] words_mask = [0,1,2,2,3] # [CLS] Mazaki wh ##ite #2 インデックスを修正すると、実行6は最初の試みで学習しました。残りはロックされたセットに対してチューニングすることでした。209Mの中モデルは0.800に達し、T4にグラデーションアキュムレーションでしか収まらない459Mの大モデルは0.83に達しました。10倍の敵対的ネガティブ(510対51)はF1を0.799に低下させたため、実行10は51に戻りました。グローバルなカットオフではなく、クラスごとの1つのしきい値は、素材の再現率を0.787から0.911に向上させました。なぜなら、MagnaCut、S35VN、HAP40のような鋼材名は、ブランドよりも低い信頼度でスコアリングされ、単一のカットオフがそれらをドロップしたからです。すべての大きな実行はエポック2で底を打ち、それ以降は過学習します。2,000の例では、それはデータセットサイズの問題であり、早期停止が解決策です。 エンティティクラスごとのF1 エンティティクラスごとのF1 ゼロショットベースライン対ファインチューニング済みチェックポイント、同じ保持データ ゼロショット(推定) ファインチューニング済み 209M ファインチューニング済み 459M 0.0 0.2 0.4 0.6 0.8 1.0 ~0.65 0.800 0.879 全体 n/a 0.858 0.904 ブランド n/a 0.775 0.877 製品 n/a 0.712 0.829 仕様 より大きなエンコーダーは、語彙が完全にドメイン固有である場合に最も役立ちます。 学んだこと うまくいきました。モデルはローカルで実行され、見たことのないコメントでGeminiのラベルに0.83 F1で一致し、「カーボン鋼」がカテゴリであり、単なる