「どこにいた」を崩して入力しても同じ証言に届くか。747 問の metamorphic testing
Does a reworded "where were you" reach the same testimony? 747 cases of metamorphic testing
D20-3-T39-metamorphic-paraphrase・技術・日本語入力でプレイする仕組み
港で佐伯に「どこにいた」と入力すると、夜の居場所の証言が返ります。
では「どこいた」なら。「どにこいた」なら。
正しい返事を 1 問ずつ書かなくても、「言い方を崩しても、着地する証言は変わらないはず」という関係だけで検査できます。
これに名前があります。
この回の位置と、前後の回#
区切り: T34〜T49 確かめ方(16 回のうち 6 回目)
先に読むとよい回: T04(文字 n-gram と tf–idf)・T05(文の埋め込み)
次に読む回: T40(データを壊したら赤くなるか)
名前 — metamorphic testing#
- 正式名
- metamorphic testing
- 略称
- MT
- 日本語
- メタモルフィックテスト(変成テストと訳す資料もあります)
- 中心の概念
- metamorphic relation(MR。複数の実行の入力と出力のあいだに成り立つべき関係)
- 初出
- T. Y. Chen, S. C. Cheung, S. M. Yiu, "Metamorphic Testing: A New Approach for Generating Next Test Cases", Technical Report HKUST-CS98-01, 香港科技大学, 1998(2020 年に arXiv:2002.12543 として公開)
- 概説
- Segura ほか "A Survey on Metamorphic Testing"(IEEE TSE, 2016)、Chen ほか "Metamorphic Testing: A Review of Challenges and Opportunities"(ACM Computing Surveys, 2018)
metamorphic testing とは#
metamorphic testing(MT)とは、1 つの出力の正しさではなく、元の入力と決まった規則で変えた入力について、出力どうしのあいだに成り立つべき関係(metamorphic relation)を確かめるテストです。
テストには、出力が正しいかを決めるもの(test oracle)が要ります。
ところが、正しい出力を誰も書けない対象があります。
数値計算、検索エンジン、コンパイラの最適化、機械学習のモデル。
これを test oracle problem と呼びます。
Chen らの考えは、「1 つの出力の正しさ」ではなく「複数の出力の関係」を確かめる、です。
教科書の例は sin です。
sin(1.234) の正しい値を知らなくても、sin(x) = sin(π − x) は成り立つはずです。
x で 1 回、π − x でもう 1 回走らせ、二つの出力が一致しなければ、どちらかが間違っています。
用語を並べます。
- source test case … 元の入力(多くは、すでに通った検査)
- follow-up test case … 元の入力を決まった規則で変えたもの
- metamorphic relation … 元と変形の入力・出力が満たすべき関係
検索なら「検索語を足して絞ったら、結果は元の結果の部分集合になる」。
分類器なら「意味を変えない書き換えをしても、ラベルは変わらない」。
自然言語処理のモデルを振る舞いで検査する CheckList(Ribeiro ほか、ACL 2020)のような枠組みも、この系統にあります。
よく知られた弱点は二つです。
- MR が破れても、どちらの実行が間違っているかは分からない。分かるのは「少なくとも一方が間違い」だけです。
- MR そのものが正しくないことがある。「意味を変えない書き換え」のつもりが、実は意味を変えていた、という場合です。そのときの破れは欠陥ではありません。
うちの変形#
adv-sim mutate が、検査の入力から 6 種類の変形を機械的に作ります。
main.cpp から写します。
// v2 助詞をひとつ落とす(口語の省略)
static std::string MutDropParticle(const std::string& In)
{
std::vector<char32_t> S = DecodeUtf8ToU32(In);
for (size_t i = S.size(); i-- > 1;)
{
if (S[i] == U'を' || S[i] == U'が' || S[i] == U'は' || S[i] == U'に')
{
S.erase(S.begin() + i);
break;
}
}
return EncodeUtf8(S);
}
// v5 隣接文字の入れ替え(入力の間違い)
static std::string MutTypo(const std::string& In)
{
std::vector<char32_t> S = DecodeUtf8ToU32(In);
if (S.size() >= 4)
{
const size_t i = S.size() / 2;
std::swap(S[i], S[i - 1]);
}
return EncodeUtf8(S);
}
// v6 前半だけ残す(言いかけ・短文化)
static std::string MutTruncate(const std::string& In)
{
std::vector<char32_t> S = DecodeUtf8ToU32(In);
const size_t Keep = std::max<size_t>(3, (S.size() * 3) / 5);
if (Keep < S.size()) { S.resize(Keep); }
return EncodeUtf8(S);
}
6 種類をまとめます。
- v1 体言止め … 文末の丁寧な言い回し(「でしょうか」「ませんか」など、語の表は tooling.json)を 1 つ落とす
- v2 助詞落ち … 後ろから見て最初の「を・が・は・に」を 1 字消す
- v3 常体 … 「です」「ます」などを全部消す
- v4 記号なし … 「?」「、」「。」「…」「・」「」」などを消す
- v5 入力の間違い … 真ん中の 2 字を入れ替える(4 字以上のときだけ)
- v6 言いかけ … 前から 5 分の 3(最低 3 字)だけ残す
作らない場合が二つあります。
if (C.Expect == "none") { continue; } // 負例は変異させない(意図が変わりうる)
const std::string Mutated = V.Fn(C.Input);
if (Mutated == C.Input || DecodeUtf8ToU32(Mutated).size() < 2) { continue; }
「進んではいけない」入力は変形しません。
変形の結果が元と同じか、1 字以下になったら捨てます。
だから変形ごとに件数が違います。
出力は元と同じ JSONL の形で、state と expect は元のまま写します。
つまり、MR は「元と同じ期待(同じ意図、段まで書いてあれば同じ段)に着地する」です。
例(第一章・港で佐伯に)#
今日の版で 1 つずつ入力しました。
どこにいた → 夜の居場所の証言に採用(0.700)
どこいた(v2)→ 聞き返し(0.284)
どにこいた(v5)→ 受け流し(0.147)
あの夜は → 採用(0.569)
あ夜のは(v5)→ 受け流し(0.218)
助詞を 1 字落とすと点が 0.70 から 0.28 まで下がり、採用線 0.33 を割って聞き返しになります。
「はい」と答えれば届くので、損は 1 手です。
真ん中の 2 字を入れ替えると、受け流しまで落ちます。
文字の 2-gram で照合している部分(T04)では、5 字の入力の 2 字を入れ替えると、2-gram 4 個のうち 3 個が壊れます。
埋め込みの側(T05)も、この程度の字の入れ替えでは元の点に戻せていません。
今日の値(10/29) — metamorphic testing#
元にするのは、自然な文(probe-natural・67 問)、人物像を変えた文(probe-persona・120 問)、短い文(probe-short・74 問)の 3 つです。
3 つ × 6 変形で、18 集合 747 問になりました。
18 集合を 1 つずつ adv-sim golden に流した合計です。
- v1 体言止め … 13/16
- v2 助詞落ち … 120/141
- v3 常体 … 31/38
- v4 記号なし … 62/68
- v5 入力の間違い … 165/242
- v6 言いかけ … 198/242
- 合計 … 589/747(78.8%)
元の 3 集合そのものは、自然な文 100.0%、人物像 83.3%、短い文 95.9% です(T09 の回と同じ値)。
これだけでは、MR を確かめたことになりません。
元の入力でもともと外れていた問いは、変形でも外れて当然だからです。
そこで、元と変形を 1 組ずつ対にして数え直しました。
- 元が正解だった組 668 … 変形も正解 581、変形で外れた 87(13.0%)
- 元が不正解だった組 79 … 変形で正解になった 8
- 着地した先(聞き返し・誘導・受け流しも 1 つの着地として)が元と変形で同じ組 … 637(85.3%)
変形ごとに見ると、外れの大半は入力の間違いと言いかけです。
短い文の入力の間違いは 56 問中 24 問しか届きません。
3〜5 字の入力で 2 字を入れ替えると、ほとんど別の文字列だからです。
進行に要る入力の組(probe-critical・244 問)からも 705 問を作りました。
こちらは 457/705(64.8%)で、入力の間違い 113/222、言いかけ 148/222 です。
合否には入れていない#
この 747 問は、ループの合否に入っていません。
派生を読むのは、文書に載せる数を出す adv-sim metrics --full だけです。
落ちる問いの中に、変形が意味を壊したもの(下に書きます)が混じっているので、線を引けないからです。
もう一つ、この worktree の派生(Game/adv/probe-mut/)は git の管理外で、8 月 4 日に作られたままでした。
今日作り直したものと中身は同じでした。
元の 3 集合が 8 月 4 日から変わっていないからです。
元を変えたら作り直さないと、古い派生を測ることになります。
名前どおりでない所 — metamorphic testing#
一つ目、比べている相手が違います。
Chen らの MR は「元の実行の出力」と「変形の実行の出力」の関係です。
正しい出力を知らなくてよいのが、この方法の意味でした。
うちの golden は、変形の出力を「元に人が付けた正解のラベル」と比べています。
元の実行が間違っていても、変形が正解なら「通った」になります(8 組ありました)。
これは、正解ラベルを持ったまま入力を揺らす頑健性の評価に近い形です。
本来の MR にあたる「元と同じ所に着地したか」は、今日別に数えた 637/747 の方です。
二つ目、変形が意味を保つとは限りません。
v6 の言いかけは、問いの肝の語を切り落とすことがあります。
v3 で「です」「ます」を消すと、「ありますか」が「ありか」のような文になります。
v5 の入れ替えで、別の語ができることもあります。
こういう問いが外れても、照合の欠陥とは言えません。
MR が成り立たない入力が混ざっている、というのが二つ目の弱点の実例です。
三つ目、入力の間違いの作り方が、日本語入力の実際の入力の間違いと違います。
IME を使う人の間違いは、ローマ字入力の誤りで別の仮名になる、変換で別の漢字になる、のどちらかが多いはずです。
隣り合う仮名が入れ替わる間違いは、変換を確定した文ではあまり起きません。
v5 の 165/242 は、実際の入力の頑健さとしては悲観的すぎる数字だと見ています。
四つ目、名前の混乱がありました。
mutate というコマンド名のとおり、以前はこれを「変異テスト」と呼んでいました。
変異テスト(mutation testing)は、被検査物の方を壊して、検査が赤くなるかを見る別の方法です(次の T40)。
入力を変える方が metamorphic、被検査物を変える方が mutation、と呼び分けるよう直しました。
コマンド名は mutate のままです。
弱点と、採らなかった案 — metamorphic testing#
- 変形が 6 種類しかありません。語順の入れ替え、同義語への置き換え、方言、漢字と仮名の書き換えは作っていません。
- 変形は一度に 1 種類だけです。助詞落ちと言いかけが同時に起きる入力は作りません。
- 元の 3 集合は 8 月から変わっていないので、10 月に増えた意図は入っていません。
やっていないことも書きます。
- 外れた変形を言い換えの辞書に足すこと。足せば 747 問の率は上がります。ただ、それは検査の文を答えに写すことで、率が上がっても何も分かりません(Goodhart の法則と、検査の文の写し込み。T43・T50 で書きます)。凍結した組(golden)と、runs/ に置いた探りの台本の文が言い換えの辞書に写っていないかは、lint(L4・L18)が文字の 3-gram の Jaccard 係数で見ています。probe-* の JSONL と、この派生は、その対象に入っていません。
- ループの合否に入れること。上に書いたとおり、意味を壊した変形が混ざるので線が引けません。入れるなら、まず変形ごとに「意味を保つ」ことを人が確かめた組を作る必要があります。
検索語#
metamorphic testing
metamorphic relation
test oracle problem
Chen Cheung Yiu 1998
言い方を崩して入力しても、佐伯が聞き返してくれる帯があります。どこまで崩すと黙るのか、『霧走町 流氷に眠る』の第一章をブラウザで試してみてください。プロフィールのリンクから遊べます。