何か見つけたが、正しかったのか
検索 (リトリーバル) は、質問に答える断片を見つけることです。測るとは、答えを知っている質問をして、正しい断片が何回返ってくるかを数えることです。
図書館のサイトは、いつも3件の結果を出します。職員は、よく動いていると感じています。でも、誰も数えていません。数えなければ、変更がよくなったのか悪くなったのか、分かりません。
このレッスンのゴール
メモ
質問に答える断片を見つけ、どれだけうまくいったかを言える。
キーワード
| ことば | いみ | れい |
|---|---|---|
| リトリーバー | 質問に合う断片を見つけるコード | |
| 命中 | 正しい断片が返ってきたこと | |
| 命中率 | 命中した質問の割合 | 80% |
| テストケース | 出典の断片が分かっている質問 | |
| 最低スコア | 返す価値のある、最も低い点数 | 0.3 |
学生が入力するセルは、このプレビューでは空白です。
テストケースは出典が分かる質問
質問を書き、それぞれに、答えを含む断片の id を添えます。リトリーバーを全部の質問で動かします。返ってきた断片の中に正しい id があれば、命中です。
命中率は、命中数を質問の数で割ったものです。5問のうち4問が命中なら、命中率は80%です。
答えのない質問もある
利用者は、文書にないことも尋ねます。その場合、正しい結果は「なし」です。リトリーバーが、いちばん悪くない断片を返すと、あとの手順が、間違った文章から答えを作るかもしれません。
最低スコアが役に立ちます。点数が最低スコア以上のときだけ断片を返し、そうでなければ空のリストを返します。
例: 5つのテストケース
| 質問の内容 | 期待 | 見つかった | 結果 |
|---|---|---|---|
| 開館時間 | hours | hours | 命中 |
| 延滞料 | fines | rooms | はずれ |
| 館長 | なし | なし | 命中 |
| 駐車場 | なし | rooms | はずれ |
| 学習室の予約 | rooms | rooms | 命中 |
学生が入力するセルは、このプレビューでは空白です。
スクリプトでリトリーバーを採点する
次のスクリプトは、レッスン3の図書館のストアを、同じ数えるベクトルで作り直します。最低スコア0.3を足し、返す断片は多くても1件にします。
そのあと、5つのテストケース (1つは答えなし) を動かし、各結果と命中率を表示します。
採点のスクリプトを書く(1)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const vocabulary = [\"開き\", \"閉まり\", \"土曜\", \"日曜\", \"借り\", \"本\", \"延滞\", \"延長\", \"学習室\", \"図書館\", \"平日\", \"時間\"];' 'const chunks = [' ' { id: \"hours-weekday\", text: \"つばめ図書館は平日、午前9時に開き、午後6時に閉まります。\" },' ' { id: \"hours-weekend\", text: \"土曜日は午前10時に開き、午後2時に閉まります。日曜日は休館です。\" },' ' { id: \"borrowing\", text: \"本は8冊まで3週間借りられます。インターネットで2回まで延長できます。\" },' ' { id: \"fines\", text: \"返却が遅れた本は1日20円の延滞料がかかります。上限は1000円です。\" },' ' { id: \"rooms\", text: \"学習室は6人まで使えます。受付かインターネットで予約し、1回2時間です。\" },' '];' > score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
採点のスクリプトを書く(2)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function vectorOf(text) {' ' return vocabulary.map(function (term) { return text.split(term).length - 1; });' '}' 'function cosine(a, b) {' ' let dot = 0, sa = 0, sb = 0;' ' for (let i = 0; i < a.length; i++) { dot += a[i] * b[i]; sa += a[i] * a[i]; sb += b[i] * b[i]; }' ' return sa === 0 || sb === 0 ? 0 : dot / Math.sqrt(sa * sb);' '}' 'const store = chunks.map(function (c) { return { id: c.id, vector: vectorOf(c.text) }; });' 'const MINIMUM = 0.3;' >> score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
採点のスクリプトを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function retrieve(question) {' ' const query = vectorOf(question);' ' let best = { id: \"\", score: 0 };' ' store.forEach(function (item) {' ' const score = cosine(query, item.vector);' ' if (score > best.score) best = { id: item.id, score: score };' ' });' ' return best.score >= MINIMUM ? [best.id] : [];' '}' >> score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
採点のスクリプトを書く(4)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const cases = [' ' { question: \"土曜日は何時に開きますか\", expected: \"hours-weekend\" },' ' { question: \"本は何冊借りられますか\", expected: \"borrowing\" },' ' { question: \"本が遅れたら延滞料はいくらですか\", expected: \"fines\" },' ' { question: \"学習室を予約できますか\", expected: \"rooms\" },' ' { question: \"館長はだれですか\", expected: null },' '];' 'let hits = 0;' 'cases.forEach(function (one) {' ' const found = retrieve(one.question);' ' const hit = one.expected === null ? found.length === 0 : found.indexOf(one.expected) >= 0;' ' if (hit) hits = hits + 1;' ' console.log((hit ? \"命中 \" : \"はずれ \") + one.question + \" -> \" + (found.join(\" \") || \"なし\"));' '});' 'console.log(\"命中率:\", cases.length, \"問中\", hits, \"問\");' >> score.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
実行して命中率を読む
docker exec -w /home/node/project agent node score.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
表示される主な行
| 行 | 意味 |
|---|---|
| 命中 土曜日は何時に開きますか -> hours-weekend | 正しい断片 |
| 命中 館長はだれですか -> なし | 何も見つからず、それが正しい |
| 命中率: 5 問中 5 問 | すべてのケースが命中 |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
何かを必ず返せば、検索は動いている、と考える人が多くいます。文書では答えられない質問に、何かを返すのは、はずれです。そうした質問も数え、リトリーバーが「なし」と言えるようにします。