質問に答える店のアプリ
自転車の貸出店が、アプリを作ります。客が質問を打つと、アプリは店の答えの中からいちばん合う段落を探し、言語モデル(文章を作るプログラム)に渡して、返事の文章にしてもらいます。
アプリが違う段落を見つけると、モデルは、とても丁寧に、違う答えを書きます。このレッスンでは、探す部分を試します。ふつうのコードなので、モデルを呼ばずに採点できます。
このレッスンのゴール
メモ
質問に答えるアプリを、少数の質問と、答えに含まれるべき事実で試せる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 検索 | 質問に答えるのに役立つ文章を見つけること | |
| 期待する事実 | よい答えに必ず含まれるはずの短い言葉 | 1200円 |
| 当たり率 | 正しい文章が見つかった質問の割合 | |
| ありふれた2文字 | どの文章にもよく出て、手がかりにならない2文字の組 | |
| はずれ | 文章は見つかったが、期待する事実がない | |
| 答えなし | 何も合わなかったので、アプリが分からないと言う |
学生が入力するセルは、このプレビューでは空白です。
2つの段階。試しやすいのは片方だけ
質問に答えるアプリの多くは、2段階です。まず検索で、質問に合う段落を選びます。次に、言語モデルが、その段落から返事を書きます。
モデルの返事は、実行のたびに変わるので、採点しにくいです。検索は毎回同じ結果なので、採点しやすいです。検索が間違えば、そのあとに正しいものはありません。
2文字ずつ区切って、共通を数える
日本語は、単語の間に空白がありません。そこで、文章を2文字ずつ区切ります。「お店は9時」なら、「お店」「店は」「は9」「9時」です。
質問の2文字の組のうち、段落にもあるものを数えます。数がいちばん多い段落が勝ちです。コードを Run して、数を読みましょう。
1つの質問の、共通する2文字を数える
def pairs(text):
for mark in "、。?! ":
text = text.replace(mark, "")
found = []
for n in range(len(text) - 1):
found.append(text[n:n + 2])
return found
docs = [
"お店は9時に開いて、18時に閉まります。月曜日から土曜日までです。",
"自転車は1日1200円で、1時間は500円です。",
"自転車は、町の3つの駐輪場のどこでも返せます。",
"自転車がこわれたら、フレームの番号に電話してください。すぐ直します。",
"ヘルメットは無料です。受付で声をかけてください。",
]
question = "1日いくらで借りられますか?"
for text in docs:
shared = []
for pair in pairs(question):
if pair in pairs(text):
shared.append(pair)
print(len(shared), shared, "<-", text[:10])
# 質問を "会員になれますか?" に変えて、もう一度 Run しましょう。
テストの集まり: 質問と事実
アプリを採点するには、小さなテストの集まりが要ります。客が聞きそうな質問を並べ、それぞれに、正しい段落にある期待する事実を付けます。
見つかった段落に事実があれば当たりです。段落は見つかったのに事実がなければはずれです。何も合わなければ答えなしです。
店の5つの答えを書く
docker exec jupyter sh -c "printf '%s\n' 'd1|お店は9時に開いて、18時に閉まります。月曜日から土曜日までです。' 'd2|自転車は1日1200円で、1時間は500円です。' 'd3|自転車は、町の3つの駐輪場のどこでも返せます。' 'd4|自転車がこわれたら、フレームの番号に電話してください。すぐ直します。' 'd5|ヘルメットは無料です。受付で声をかけてください。' > faq.txt"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
6つのテスト質問を書く
docker exec jupyter sh -c "printf '%s\n' 'お店は何時に開きますか?|9時' '1日いくらで借りられますか?|1200円' '自転車はどこで返せますか?|3つの駐輪場' '自転車がこわれたらどうしますか?|番号に電話' '帽子は借りられますか?|無料' '会員になれますか?|無料' > questions.txt"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
採点のスクリプトを書く
docker exec jupyter sh -c "printf '%s\n' 'import sys' '' '' 'def pairs(text):' ' for mark in \"、。?! \":' ' text = text.replace(mark, \"\")' ' found = []' ' for n in range(len(text) - 1):' ' found.append(text[n:n + 2])' ' return found' '' '' 'docs = []' 'for line in open(\"faq.txt\", encoding=\"utf-8\"):' ' docs.append(line.strip().split(\"|\")[1])' 'skip = []' 'if sys.argv[1] == \"skip\":' ' seen = {}' ' for text in docs:' ' done = []' ' for pair in pairs(text):' ' if pair not in done:' ' done.append(pair)' ' if pair in seen:' ' seen[pair] = seen[pair] + 1' ' else:' ' seen[pair] = 1' ' for pair in seen:' ' if seen[pair] >= 2:' ' skip.append(pair)' ' print(\"無視する2文字:\", len(skip), \"個\")' > steps/ask.py"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
採点のスクリプトの続きを書く
docker exec jupyter sh -c "printf '%s\n' 'hit = 0' 'wrong = 0' 'none = 0' 'for line in open(\"questions.txt\", encoding=\"utf-8\"):' ' parts = line.strip().split(\"|\")' ' question = parts[0]' ' fact = parts[1]' ' asked = []' ' for pair in pairs(question):' ' if pair not in skip:' ' asked.append(pair)' ' best = \"\"' ' best_shared = 0' ' for text in docs:' ' shared = 0' ' for pair in asked:' ' if pair in pairs(text):' ' shared = shared + 1' ' if shared > best_shared:' ' best = text' ' best_shared = shared' ' if best == \"\":' ' none = none + 1' ' print(\"なし:\", question)' ' elif fact in best:' ' hit = hit + 1' ' print(\"当たり:\", question)' ' else:' ' wrong = wrong + 1' ' print(\"はずれ:\", question)' 'print(\"当たり\", hit, \"はずれ\", wrong, \"なし\", none, \"率\", round(hit / (hit + wrong + none), 2))' >> steps/ask.py"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
単純な一致を採点する
docker exec jupyter python3 steps/ask.py plainここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
1つ直して、もう一度採点する
「ます」「です」のような、どの文章にも出る2文字のせいで、質問が違う段落へ行きました。2つ以上の文章に出る2文字をありふれた2文字として除き、もう一度採点します。
直すのは、一度に1つだけです。3つ同時に変えたら、どれが効いたか分かりません。
ありふれた2文字を除いて、採点する
docker exec jupyter python3 steps/ask.py skipここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
当たり率が 0.5 から 0.67 に上がったので、アプリがよくなったと思う人が多いです。ほかの数も見ます。3つのはずれは、1つが当たりに、2つが答えなしになりました。これはよいことですが、2つの質問はまだ何も見つけません。これほど小さなテストは、どこを見るかを教えます。アプリの良さまでは、教えません。
例: 2回の実行を並べる
| 実行 | 当たり | はずれ | 答えなし | 当たり率 |
|---|---|---|---|---|
| 何も除かない | 3 | 3 | 0 | 0.5 |
| ありふれた2文字を除く | 4 | 0 | 2 | 0.67 |
学生が入力するセルは、このプレビューでは空白です。
さらに: 自分の質問を足す
テストのファイルに、事実付きの質問を4つ足します。店の5つの答えでは答えられない質問も1つ入れます。
2回の実行を、もう一度採点します。店の文章に答えがない質問の、正しい結果は何でしょう。アプリはそうなりますか。