この記事は、連載「追わない技術」の第 2 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。

今回は、どのモデルに渡すかを決める時点の話です。前回は「いつ止めるか」でしたが、今回は「誰に渡すか」になります。先に、題を否定しておきます。安いモデルは、使うべきです。私も使っています。やめたのは、一番安いのを「既定」にすることのほうです。値段は単価の比 × 量の比で決まり、量の比のほうは、測らないと分かりません。

もう 1 つ断っておきます。この回にもモデルの名前も、価格も、性能の数字も出てきません。半年で古くなるものは、この連載では書かないと決めています。比べるものは「高い側」と「安い側」とだけ呼びます。そのかわり、倍率は全部出します。手元の単価を掛ければ、そのまま判断に使えるはずです。

そして白状します。私はこの検証で 2 度、「差が無い」と読み間違えました。どちらも数字は正しく、読み方が間違っていました。2 度とも、指摘を受けて初めて気づきました。そのままなら「杞憂でした」と書いていたはずです。今回はその 2 回も含めて書きます。

一つ思い出してみてください。

安いモデルに任せた仕事は、本当に安く終わりましたか。

やり直しの回数まで、数えたうえで言えますか。

CC BY 4.0

予測から始めました —— 「安いほうは、見逃しが増えるはず」

最初に立てた予測はこうです。

安いモデルにテストを書かせると、テストは書けるが、見逃しが増える。

題材にテストを選んだのには理由があります。テストは、失敗が合格に隠れる唯一の成果物だからです。実装のバグはいつかどこかで落ちますが、テストの見逃しは何も言いません。「テストを書かせた」は合格で完了に見え、請求は後から来ます。

物差しは、過去に手元のコードへ入れて、手元の自動テストが実際に落とした変異 8 種を当て直したものにしました。この実験のために変異を書いてはいけません。書いた瞬間、見逃し率は書き方ひとつで 0% にも 100% にもなります。

比べる相手も置きました。同じ対象に、人(私)が書いた既存のテストです。そちらは 8 種のうち 6 種しか落としません。天井は満点ではありません。

条件は 2 つだけです。モデル(高い側 / 安い側)と、範囲(call 1 メソッドに絞る / 「必要だと思うテストを書いて」だけ)。観点は 1 つも渡していません。

1 回目 —— 安い側は、半分が走りませんでした

会話を引き継がない作業者を 12 体そろえて書かせ、変異を入れる前に、まず素のまま走らせました。

走った体走った体だけの検出(8 種のうち何種を落としたか。⚠️ 多いほうが良い)
高い側6 / 63.33 / 8
⚠️ 安い側2 / 63.00 / 8

走らなかった 4 体の中身は、存在しないアサーションを呼ぶ・辞書の行を重複させるといった壊れ方でした。どれも、走らせた瞬間に分かります。

そこで私は、走らなかった 4 体を「見逃しとは別枠」として、検出の集計から外しました。理屈も一応ありました —— 走らないテストは、その場で気づけて、高いほうへ切り替えられます。

これが 1 つ目の間違いです。

間違い 1 —— 走らなかったテストを、母数から落としていました

外した結果、残った安い側は 2 体だけです。その 2 体の平均を「安い側の実力」として、高い側 6 体と並べていました。

⚠️⚠️⚠️ 走らなかったものを母数から落とすと、いちばん危ない側が消えます。

直し方は簡単でした。現実と同じように差し戻したのです。エラーの出力だけを返して「直してください」と送る。何が悪いかは言いません(言えば、それは物差しを渡すことになります)。

差し戻しを重ねて、12 体とも合格にしました。そのうえで数え直すと、こうなりました。

範囲を絞る絞らない
高い側3.33 / 83.33 / 8
安い側3.33 / 82.33 / 8

範囲を絞れば、安い側は高い側と同点です。私はここで、こう書こうとしました。「安いモデルで増えるのは、その場で気づける失敗のほうです。見逃しは増えません。」

これも間違いでした。

間違い 2 —— 物差しに、天井がありました

8 項目のうち 3 項目は、12 体とも 0 でした。人が書いた既存のテストは、その 3 つを落とします。

3 つを並べてみると、共通点がありました。どれも「材料か測り方を、先に自分で用意しないと書けない」項目です。

  • 表の中でいちばん長い見出し語を、自分で辞書に入れる
  • 同じ表記に読みを 2 通り持たせる
  • 戻り値ではなく、問い合わせの本数を数える

課題は、そんなことを一言も言っていませんでした。「単体テストを書いてください」としか書いていない。

つまり 12 体とも 0 だったのは、書けなかったからではなく、課題がそこを指していなかったからかもしれない。

そこで、課題に 1 段落だけ足しました。

setup で作る辞書に現象が入っていなければ、そのテストは何も見ていません。 見たい振る舞いが出せるように、辞書のほうを組み立ててください。 戻り値を見るだけでは分からない振る舞いもあります。そういうものは、測り方のほうを用意してください。

8 項目はどれも名指ししていません。言っているのはやり方だけです。

そして両方の条件に、1 バイトも同じだけ渡しました(条件の差を増やしてしまうと、測っているものが変わります)。

天井が外れたら、結論がひっくり返りました

もう一度 12 体に書かせました。3 項目とも動きました。

1 回目(天井あり)⭐ 2 回目(天井なし)
高い側3.33 / 85.00 / 8
安い側2.83 / 82.83 / 8
高い側・範囲を絞る3.33 / 85.00 / 8
⚠️⚠️ 安い側・範囲を絞る3.33 / 8(同点)3.00 / 8(同点ではない)

天井を外すと、高い側だけが伸びました。安い側は 2.83 のまま動きません。

⚠️⚠️ 「絞れば同点」は、両方が簡単な項目しか取れず、そこで並んでいただけでした。

ついでに、範囲の絞り方は見逃しには効きませんでした(高い側は 5.00 と 5.00 で差 0、安い側は 3.00 と 2.67)。1 回目で範囲が効いていたのは「そもそも走るか」のほうで、見逃しの側ではありません。

最初の予測は当たっていました。ただし、当たっていると分かるまでに、母数の落とし方と物差しの天井を 2 つとも直す必要がありました。

差し戻しは、テストを消させます

差し戻しの回数は、高い側が 2 回の実験とも 0 回、安い側は 1 回目が 6 回・2 回目が 10 回でした。ここまでは想像どおりです。

想像していなかったのは、直し方のほうです。「実装は正しい。テストを直してください」と送ると、返ってきた直しのうち 3 件は、失敗しているテストを消したものでした。

  • 「上限ちょうどは通す」という 1 件を、削除
  • 全角の正規化を見る 1 件を、削除
  • 重複した辞書の行を消さず、例外だけを飲む書き方に変更

どれも合格になります。そして、消えた 1 件はそのまま見逃しになります。

⚠️⚠️⚠️ 「実装は正しい」と言って差し戻すと、直すより消すほうへ倒れます。

これはモデルの話ではなく、差し戻し方の話です。

ただし、差し戻しが要ったのは安い側だけでした。だから結果としては、モデルの側に効いています。

原理: 安いかどうかは「単価の比 × 量の比」で決まります

ここまでの数を、判断に使える形へ畳みます。1 回の応答の値段ではなく、1 つの仕事が終わるまでの総額で見ます。

同じ課題で、安い側が高い側の何倍を使ったかです。

数えたもの範囲を絞る絞らない全体
初めて読ませた量1.64 倍1.24 倍1.48 倍
⭐ 使い回せた量9.71 倍2.62 倍5.29 倍
書いた量0.26 倍0.38 倍0.31 倍
手番(差し戻し込み)3.33 倍2.00 倍2.67 倍

安い側は、単価が安いだけではありません。量そのものが多いのです。やり直すたびに会話が伸び、伸びたぶんが次の手番でもう一度流れます。

使い回した部分は安く課金されますが、ゼロではありません。ここを 0 と見なすと、リトライは「ほぼタダ」に見えます。5.29 倍のものを 0 と数えれば、どんな結論でも作れます。

書いた量だけは安い側のほうが少ない(0.31 倍)ので、出力の重い仕事なら向きが変わります。

⭐⭐⭐ 値段は「単価の比 × 量の比」。単価の比はカタログに書いてありますが、量の比は測らないと分かりません。

だから「一番安いから既定」は、片方だけを見た判断です。同じ理由で「一番高いから既定」も片方だけです。既定を置かず、用途ごとに 2 つとも数えます。

前作で、私は翻訳の用途では安い側を使うと書きました。その判断はいまも変えていません。あの用途では、安い側のほうが手数まで少なかったからです。今回の結果は、それを打ち消しません。打ち消さないことこそが要点で、用途が変われば向きも変わるというだけです。

検証手順: 単価の比と、量の比を、別々に数える

前提

  • 同じ課題を、2 つのモデルへ同じ材料で渡せること。材料が 1 バイトでも違うと、モデルの差ではなく材料の差を測ります
  • 合否がプログラムで出る物差しがあること。人が読んで採点すると、測る回数が費用になって続きません
  • 使ったトークンが、新しく読ませた量・使い回した量・書いた量に分けて残ること。分かれていないなら、この手順の後半は使えません

所要時間: 半日(物差しが既にある場合)

手順

  1. 物差しにする変異を、過去に手元のコードへ入れて、手元の自動テストが落としたものから採ります。この実験のために書いてはいけません
  2. 人が書いた既存のテストを、同じ物差しに掛けます。これが天井です。満点なら物差しが緩いので、変異を足してください
  3. 同じ課題を 2 つのモデル × 2 つの範囲 × 3 回渡し、書かせます。走らせるのはこちらで、書き手には走らせません
  4. 変異を入れない状態で全部を走らせます。失敗した体を「集計から外す」のは禁止です。エラーの出力だけを返して差し戻し、全部が合格になるまで繰り返します
  5. 合格になったものへ、変異を 1 種ずつ当てて走らせます。変異 1 種につき全員ぶんを 1 回で回せます
  6. 差し戻しの回数と、新しく読ませた量・使い回した量・書いた量を、モデルごとに合計します
  7. 手元の単価表を掛けて、完了までの総額を出します

合格条件(すべて満たすこと)

  • 手順 2 で、人の書いたテストが落とせない変異が 1 つ以上ある(満点なら天井です)
  • 手順 4 のあと、全部の体が合格である(1 体でも失敗したまま数えると、いちばん危ない側が母数から消えます)
  • 手順 5 で、全員が 0 の項目が、全体の半分未満である。半分以上あるなら、課題がそこを指していません(材料と測り方を自分で用意する、と課題に書き足してください)
  • 手順 6 で、使い回した量が 0 でない(0 なら分けて取れていません)

合格しなかったとき

  • 人のテストが満点 —— 物差しが緩いか、手元の自動テストが落とした変異を採っていません
  • 失敗した体が合格にならない —— そこが「高いほうへ切り替える」判断点です。回数を費用として記録してから切り替えます
  • 全員が 0 の項目が半分以上 —— 天井です。課題の骨に 1 段落足して、両方の条件へ同じだけ渡し、測り直します
  • 使い回した量が 0 —— 記録が分かれていません。分けて取れないなら、量の比は出さずに手番だけで判断します

後始末

  • 一時的に置いたテストを消します。記録(トークンの内訳)は残します
  • 結果は日付つきで残します。記録は増え続けるので、同じスクリプトを明日走らせると数が動きます

注意: この数字が言えないこと

1 つの枡が 3 体しかありません。

向きだけを読んでください。

5.00 と 2.83 の差は、順位としては読めますが、倍率として持ち出せる精度ではありません。

比べたのは 2 点だけです。高い側と安い側を 1 つずつ選んだので、「安いほど落ちる」という直線は、ここからは引けません。

8 項目のうち 1 つは、天井を外したあとに 12 体とも 0 になりました(⭐ 天井があったときは、12 体のうち 1 体が落としていた項目です)。人が書いたテストも、それは落とせません。

つまりまだ天井が残っています。「どこまで差があるか」は、今回の物差しでは測り切れていません。

差し戻しは「実装が正しい」という前提で送っています。その前提そのものが、テストを消させました。

実務では実装が間違っていることもあるので、この差し戻し方をそのまま真似しないでください。

書き手にテストを走らせていません。走らせられれば、安い側の失敗は自分で気づけたはずです。

「走らせない丸投げ」の数字だと思ってください。

最後に、題材のコードは、コメントがかなり厚いものでした。壊したときに何が起きるかまで、実装自身が書いています。コメントの薄いコードでは、両方とももっと落ちるはずです。そのまま持ち出せる数ではありません。

CC BY 4.0 はここまで

既定にしなくなったもの

  • 「安いほうで足りるだろう」で渡すのをやめました。それは予測です。単価の比はカタログにありますが、量の比は測らないと出ません
  • 走らなかった結果を、集計から外すのをやめました。外した瞬間、いちばん危ない側が母数から消えます。差し戻して、全部を合格にしてから数えます
  • 「差が無い」と書く前に、物差しが動くかを見るようにしました。全員が 0 の項目が半分あるなら、それは差が無いのではなく、測れていないだけです
  • 「実装は正しい。テストを直せ」とだけ言って差し戻すのをやめました。消して合格にする道が、そこに開いています
  • やめていないこともあります —— 用途ごとに安い側を選ぶことです。前作で選んだ用途は、いまも安い側のままです

安いモデルは安い、という感じは、たいてい正しく見えます。請求のうち、1 回ぶんの単価しか見ていないからです。見えないのは、やり直した回数と、そのたびに読み直された会話のほうでした。


次回は「最新モデルを使わない技術」。私は、新しいモデルが出た日に乗り換えるのをやめました。強いモデルでないと通らなかった仕事が、モデルを変えずに通るようになったからです。