5000件から近い3件を探す
ベクトルストアは、断片とそのベクトルを持ち、質問に最も近いものを見つけます。検索は、速く、最もよい数件だけを返してこそ役に立つので、これが大切です。
大学の図書館に、案内や規則の断片が5000件あります。学生が質問を打ちます。プログラムは、質問をすべての断片と比べ、近い3件を返さなければなりません。
このレッスンのゴール
メモ
ベクトルを保存し、最も近いものを見つけられる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| ベクトルストア | 断片とそのベクトルのリスト | |
| クエリ | ベクトルにした質問 | |
| スコア | 断片がクエリにどれだけ近いか | 0.82 |
| 上位k件 | スコアが最もよい k 件の断片 | k = 3 |
| インデックス | リストのほとんどを飛ばす仕組み |
学生が入力するセルは、このプレビューでは空白です。
ベクトルストアは点数付きのリスト
いちばん簡単なストアは、リストです。各要素が、id とベクトルを持ちます。探すときは、質問をベクトルにし、各要素をコサインで採点し、点数を並べ替えて、先頭の k 件を残します。
これが「最近傍探索」の意味のすべてです。最もよい結果は、点数がいちばん高い要素です。
例: クエリ [1, 0] と3つの要素
| 要素 | ベクトル | コサイン | 順位 |
|---|---|---|---|
| a | [1, 0] | 1.00 | 1 |
| b | [1, 1] | 0.71 | 2 |
| c | [0, 1] | 0.00 | 3 |
学生が入力するセルは、このプレビューでは空白です。
数えるベクトルは同じ語しか合わない
このラボには埋め込みのモデルがないので、次のスクリプトは自分でベクトルを作ります。決めた語のリストについて、各語が断片に何回出るかを数えます。日本語には空白がないので、語を文の中から探して数えます。
これは代用品です。本物の埋め込みは「借りる」と「貸し出す」も近づけます。数えるベクトルは、同じ語しか見えないので、できません。
ストアのスクリプトを書く(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時間です。\" },' '];' > store.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 (chunk) {' ' return { id: chunk.id, vector: vectorOf(chunk.text) };' '});' >> store.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
ストアのスクリプトを書く(3)
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function search(question, k) {' ' const query = vectorOf(question);' ' return store' ' .map(function (item) { return { id: item.id, score: cosine(query, item.vector) }; })' ' .sort(function (a, b) { return b.score - a.score; })' ' .slice(0, k);' '}' 'const question = \"土曜日は何時に開きますか\";' 'console.log(\"質問:\", question);' 'search(question, 3).forEach(function (hit) {' ' console.log(hit.score.toFixed(2), hit.id);' '});' >> store.js"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
実行して点数を読む
docker exec -w /home/node/project agent node store.jsここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
表示される内容
| 行 | 意味 |
|---|---|
| 質問: 土曜日は何時に開きますか | クエリ |
| 0.71 hours-weekend | 最もよい断片。土曜が入っている |
| 0.35 hours-weekday | 次の断片。開きが入っている |
| 0.00 borrowing | 3番目。共通の語がない |
学生が入力するセルは、このプレビューでは空白です。
インデックスがある理由
すべての要素を採点するのは、5件でも5000件でも問題ありません。5000万件では遅くなります。検索のたびに、同じ作業を全部やり直すからです。
インデックスは、近いベクトルをまとめ、検索がリストのほとんどを飛ばせるようにします。多くのインデックスは近似です。ずっと速い代わりに、本当の最近傍を、ときどき逃すことがあります。
よくある間違い
注意
先頭の結果には、いつも答えがある、と考える人が多くいます。先頭は、いちばん近い断片にすぎません。ストアに近いものがなければ、先頭は、いちばん悪くないものです。信じる前に、スコアを見ます。