ずるをせずに、事例を作る
合成した評価データとは、プログラムが作った事例のことです。千件の事例を手で書くのは大変なので、型から作るチームが多くいます。
作った事例は、重なったり、自分の答えを漏らしたりします。このレッスンでは、種から事例を作り、悪いものを取り除きます。
このレッスンのゴール
メモ
種から新しい事例を作り、それが写しでないと言える。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 型 | 空所を埋めて使う文 | {item}を取り替えたいです |
| 種 | 生成を毎回同じにする、始めの数 | |
| 近い重複 | ほかの事例とほとんど同じ事例 | |
| 漏れ | 自分の答えや、学習用の文章を漏らす事例 | |
| 似ている度合い | 二つの文が共通して持つ文字の組の割合 |
学生が入力するセルは、このプレビューでは空白です。
型と種が、事例を作る
型とは、空所のある文です。たとえば「{item}を取り替えたいです」です。空所は、品物の一覧から埋めます。型ごとに、良い機能が出すべき答え、ラベルを付けておきます。
種とは、数え役を動かし始める数です。同じ種からは、いつも同じ型と品物が選ばれるので、事例は繰り返せます。
例: 種のある生成
| 手順 | 何が起こるか |
|---|---|
| 始める | 種が最初の数になる |
| 引く | 16807 を掛け、2147483647 で割った余りを取る |
| 選ぶ | その数を一覧の大きさで割った余りで選ぶ |
| 埋める | 選んだ品物を空所に入れる |
| くり返す | 同じ種なら、毎回同じ事例ができる |
学生が入力するセルは、このプレビューでは空白です。
近い重複は、集まりを大きく見せる
一語しか違わない二つの事例からは、新しいことを学べません。日本語は 2 文字ずつの組にして、共通する組を数えます。それを、二つの文の組を合わせた種類の数で割ります。重なった組は一度だけ数えます。
80 パーセント以上なら、近い重複とします。最初の事例を残し、あとの事例をはねます。
よくある間違い
注意
作った事例が多ければ、いつも良いテストになると考える人が多いです。多くが写しなら、集まりは見た目より小さいのです。近い重複でない事例だけを数えましょう。
二つの漏れが、テストをだめにする
答えの漏れとは、事例が自分のラベルを含むことです。ラベルが返金の事例に「返金してください」と書くと、機能は、その言葉を見つけるだけで通れます。
学習用の文章の漏れとは、機能がすでに見た文章と事例が合うことです。それで点数が高くても、力ではなく、記憶を示すだけです。
助っ人 model.js を書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// 応答のトークン数。なければ null。' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
助っ人 model.js を書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' const data = await res.json();' ' const text = data.choices[0].message.content;' ' return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"記録済みサンプル: モデルが答えませんでした (\" + error.message + \")。\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
生成のプログラムを書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const templates = [' ' [\"{item}が{fault}届きました。お金を返してほしいです\", \"返金\"],' ' [\"{item}を返金してください。{fault}届きました\", \"返金\"],' ' [\"{item}はどこにありますか。まだ届いていません\", \"配送\"],' ' [\"{item}を色違いのものと取り替えたいです\", \"交換\"],' ' [\"{item}が{fault}しまい、代金を戻してほしいです\", \"返金\"],' ' [\"{item}の注文の状況を教えてください\", \"配送\"],' '];' 'const items = [\"やかん\", \"ランプ\", \"マフラー\", \"マグカップ\"];' 'const faults = [\"壊れて\", \"傷ついて\", \"割れて\"];' 'const train = [\"やかんはどこにありますか。まだ届いていません\", \"ランプを色違いのものと取り替えたいです\"];' > synth.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
生成のプログラムを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'let s = 23;' 'function next(size) { s = (s * 16807) % 2147483647; return s % size; }' 'function generate(n) {' ' const out = [];' ' for (let i = 1; i <= n; i++) {' ' const t = templates[next(templates.length)];' ' let text = t[0];' ' if (text.indexOf(\"{item}\") >= 0) text = text.replace(\"{item}\", items[next(items.length)]);' ' if (text.indexOf(\"{fault}\") >= 0) text = text.replace(\"{fault}\", faults[next(faults.length)]);' ' out.push({ id: i, text: text, label: t[1] });' ' }' ' return out;' '}' >> synth.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
生成のプログラムを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function pairs(text) {' ' const t = text.toLowerCase().replace(/[、。?! ,.?]/g, \"\");' ' const out = [];' ' for (let i = 0; i + 1 < t.length; i++) {' ' const p = t.slice(i, i + 2);' ' if (out.indexOf(p) < 0) out.push(p);' ' }' ' return out;' '}' 'function similarity(a, b) {' ' const wa = pairs(a);' ' const wb = pairs(b);' ' const shared = wa.filter(function (w) { return wb.indexOf(w) >= 0; }).length;' ' return Math.floor(shared * 100 / (wa.length + wb.length - shared));' '}' 'function near(text, list) {' ' return list.some(function (t) { return similarity(text, t) >= 80; });' '}' >> synth.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
生成のプログラムを書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const kept = [];' ' const reasons = {};' ' generate(12).forEach(function (c) {' ' let why = \"\";' ' if (c.text.indexOf(c.label) >= 0) why = \"答えが漏れている\";' ' else if (near(c.text, train)) why = \"学習用の文章が漏れている\";' ' else if (near(c.text, kept.map(function (k) { return k.text; }))) why = \"重複\";' ' if (why === \"\") kept.push(c);' ' else reasons[why] = (reasons[why] || 0) + 1;' ' });' >> synth.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
生成のプログラムを書く(5)
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' console.log(\"採用 \" + kept.length + \" 件 / 12 件\");' ' console.log(\"却下: \" + JSON.stringify(reasons));' ' console.log(\"最初に採用: \" + kept[0].text + \" -> \" + kept[0].label);' ' const sample = \"ラベルの言葉が、文章の中に入らないように、型を直します。\";' ' const q = \"一文で短く答えてください。作った事例が、自分の答えを漏らさないようにするには、どうしますか。\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 80);' ' console.log(\"モデル: \" + reply.text);' '}' 'main();' >> synth.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
生成を動かす
docker exec -w /home/node/project agent node synth.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
Run の出力
| 行 | 意味 |
|---|---|
| 採用 8 件 / 12 件 | 確認のあとに残った事例 |
| 却下: {...} | 何件はねられ、理由は何か |
| 最初に採用: ... -> ... | きれいな事例一つと、そのラベル |
| モデル: ... | ラボからの一文。答えがなければ記録済みサンプル |
学生が入力するセルは、このプレビューでは空白です。