豪鬼メモ

一瞬千撃

英語力年齢診断・改

英単語の意味を知っているかどうかの質問に答えていくと語彙力が診断できるサービスを作った。英単語の獲得年齢データベースを使って、ネイティブ英語話者の何歳程度の語彙力に相当するかを検証するものだ。まずは、実際のサービスを使ってみて欲しい。
f:id:fridaynight:20210209013826p:plain


質問は全部で22問あり、回答し終わると、その成否に応じた実力診断の結果が表示される。2分くらいで終わるようになっている。Twitterで呟かれた皆様の結果はこんな感じ。#語彙力年齢診断

f:id:fridaynight:20210209013840p:plain

データとアルゴリズムについて説明する。獲得年齢データベースの収録語数は3万語ほどなのだが、その他の語も出現頻度から推定して獲得年齢を割り振っている。8割方の英語ネイティブ話者は「mama」を3.18歳までに覚え、「teacher」を5.69歳までに覚え、「draconian」を16.97歳までに覚えるといったデータが得られている。全部で7万語の単語および熟語が検査の対象にできるようになっている。和訳は例によってWordNetWiktionaryの統合辞書から引っ張っている。

当然ながら、7万語の全てについて知っているか検査するわけにはいかない。楽しく実施できる分量の限界として、1回あたり22語の検査で済ませることにした。たった22語で7万語を代表させるのだから、どういう風に検査対象の語を選択するかが重要になる。もし個々の語の語彙力判定能力が完全であるとしたら、難易度順に並べて単純な二分探索をすれば良い。つまり、「teacher」を知っていれば、5.69歳以上の語彙力があり、そうでなければそれ未満の語彙力しかないと言い切れるならば、話は簡単だ。しかし、実際はそうではない。どの語をいつ覚えるかには個人差があるからだ。「teacher」は知らないけど「professor」は知っているという逆転は普通に起こりうる。なので、二分探索の考え方に基づきつつも、適当なヒューリスティクスを組み合わせる必要がある。

事前準備として、検査対象になりうる全ての語を獲得年齢で並べて、順位を割り振る。順位をキーとして、語の文字列と獲得年齢と品詞と訳語のリストを属性として持たせたレコードをデータベースに入れる。以下のような感じだ。

00000   momma   noun  2.27    おかあちゃん,ママ,お母さん,おっ母,おっ母さん
00001   potty   noun  3.11    幼児用便器,甘い,トイレ,夢中になった,WC,お手洗い,W C,おまる
00002   water   noun  3.13    水,ウォーター,水道,給水,注ぐ,海域,水体,潅漑する
00003   mama    noun  3.18    ママ,お母,お母さん,おっ母,おっ母さん,おかあさん
00004   spoon   noun  3.29    スプーン,匙,いちゃつく,スプーンで掬う,スプーンで釣る,キスする,愚か者,愛撫する
00005   nap     noun  3.30    昼寝,仮眠,昼寝する,うたた寝,毛羽,まどろむ,仮眠する,うたた寝する
00006   mom     noun  3.44    お母さん,ママ,おかあさん,母,母さん,おかあちゃん,かあちゃん
00007   hug     noun  3.54    抱擁,抱っこ,ハグ,抱きしめる,抱擁する,沿って進む,抱き締める,抱く
00008   count   noun  3.59    カウント,数える,計数,計算する,伯爵,勘定に入れる,数に入れる,計算

データベースの形式はスキップリスト(TkrzwのSkipDBM)とする。スキップリストは順序付きのデータベースなので、範囲検索ができる。よって、1000位から2000位までのレコードを取り出すといったことが可能になる。この時点では順位だけを基準にして考え、獲得年齢の分布について深く考えないということが要点だ。順位だけを考えて検査を通した上で、最後にその結果を獲得年齢に変換する。

検査は、簡単そうな語から始めるのが妥当だろう。いきなり難しい問題を出すと、モチベーションが保てないからだ。かといって毎回「mama」の意味を尋ねられてもむかつくので、あまり簡単すぎてはいけない。よって、500位付近の語から始める。複数回の試行をする利用者には毎回異なる語で検査すべきなので、各回の対象は乱数で揺らがせる。つまり、500位を中心として、周辺1000の範囲、すなわち1位から1000位までを初回の対象にする。結果としていきなり「mama」が対象になる可能性もあるが、確率的にはそんなに高くないので気にしない。

22語の検査をするのだが、それぞれの語の検査を「イニング」と呼ぶことにする。各イニングにおいて、中心となる順位の前999位と後1000位までの2000語を「候補」とする。候補の中で無作為に1語を選び、それを「答え」とする。その他にも4語を無作為に選び、それを「ダミー」とする。答えの英語を表示し、ダミーを含めた5つの訳語の中から対応するものを選ばせることで、検査が成立する。中心となる順位が1000位より低い場合(少なくとも初回がそうだ)、前は1位からその順位までになり、また後の取得数も前に合わせて減らす。そうすることで、低い順位の難易度が下がる。

各イニングで正解が入力された場合、その利用者は、より賢いと期待されるので、難易度を上げるべきだ。不正解が入力された場合、その利用者はより無知であると期待されるので、難易度を下げるべきだ。その移動量をどう算定するか。全体が22問であることを考えると、正解の場合には、現在の着目点を全体の語数から引いた値を8で割った量だけ、着目点を上に移動させるのがよいだろう。不正解の場合、現在の着目点を8で割った値だけ、着目点を下に移動させる。つまり、正解した問題が簡単すぎる場合には、より多く上に動かし、不正解した問題が難しすぎる場合には、より多く下に動かす。しかし、この方法だと収束しないので、移動量を「レコード数 / イニング数」でキャップする。言い換えれば、前半の検査でだいたいの実力を測って適切な難易度の問題を出せるようにして、後半の検査で最終的な判定を追い込むということだ。21イニングと22イニングの間では上にも下にも3200位しか移動しない。

ここで、重要な知見がある。獲得年齢が低い簡単な語の数は、獲得年齢が高い難しい語の数よりも少ないということだ。にもかかわらず、簡単な語を知っているかを確認する方が、難しい語を知っているかを確認するよりも重要だ。pyelitis(腎盂炎)とかconstitutionalism(立憲主義)とかの学術用語を10個知らないよりも、thwack(叩く)とかgarner(集める)とかの日常語を1個知らない方がまずい。順位だけを見て着目点を移動させると、難しい語の検査に偏る可能性が高いので、何らかの調整をすべきだ。そこで、着目点に対して、1未満のガンマ補正をかける。具体的に言うと、0.6のガンマ補正をかける。例えば、レコード数が70000で着目点が30000位の場合、( ( 30000 / ( 70000 - 1 ) ) ^ ( 1 / 0.6 ) ) * 70000 = 17053 を実際の着目点にする。こうすることで、変域を変えることなく、低順位の語を重点的に調べることができる。

答えとダミーの選択は無作為に行われ、またそれは毎回の検査ごとに異なるものが選ばれることが望ましい。当然、乱数を使ってそれをなすのだが、ページをリロードする度に異なる問題が出るというのは困る。よって、検査を開始する際に乱数種の親種を真の乱数で決定し、一連の問題の間では同じ親種に何らかの決定的な値を加えたものを擬似乱数の乱数種として利用する。ここで言う決定的とは、リロードしても変わらないということだ。とりあえずは現在のイニング数で良いだろう。

最終的な語彙力を年齢として出すためには、何らかの基準が必要となる。ある獲得年齢を基準値とした場合、それ以上の難易度の問題を3回以上解けたならば、その基準の語彙力を持っているとみなすことにする。つまり、正解した問題を順位で降順に並べて、3番目のレコードの獲得年齢を最終的な実力とみなす。逆の考え方もある。ある難易度の問題を3回以上間違ったならば、その基準の語彙力を持っていないとみなす。つまり、不正解した問題を順位で昇順に並べて、3番目のレコードの獲得年齢を最終的な実力とみなす。今回はその双方の指標の算術平均を最終的な結果として提示する。このチューニングに何ら科学的な根拠はない。単に試行錯誤で編み出した調整だ。上記のアルゴリズムで7万語を検査すると、最低値である3歳から最高値である20歳までうまいこと分布するのだ。「3回達成すれば免許皆伝、3回失敗したら失格」というのは利用者としても合点がいきやすい。

和訳を表示する際にもちょっと工夫をした。カタカナ語が和訳に出てしまうと、答えがすぐに分かってしまうという問題があるのだ。「"nail" の意味は?」という問題の選択肢に「ネイル」って書いてあったら問題として成り立たない。よって、片仮名が含まれる訳語をすべて無視するという作戦が考えられる。しかし、そうすると「bread」の和訳から「パン」を削ることになってしまう。そうすると「生計」などの迂遠な訳語が残るのだが、それを選ばせるのは直感に反する。よって、片仮名語の訳語を全て削るのではなく、音写(transliteration)だと思われる訳語だけを削るのが理想的である。そのためには、英単語を音写して片仮名を得る関数が欲しくなるが、実装が面倒そうだ。よって、英単語の最初の文字だけ調べてブラックリストを作る簡便法でお茶を濁すことにした。

top_letter = word[:1].lower()
if top_letter == "a":
  if regex.search(r"^[アエ]", tran): continue
elif top_letter == "b":
  if regex.search(r"^[バビブベボ]", tran): continue
elif top_letter == "c":
  if regex.search(r"^[カキクケコサシスセソ]", tran): continue
elif top_letter == "d":
  if regex.search(r"^[ダヂヅデド]", tran): continue
elif top_letter == "e":
  if regex.search(r"^[エイ]", tran): continue
...

選択肢の中に同じ訳語が複数個含まれていると曖昧になるので、一度出た訳語を含む候補は捨てる。さらに、問題の語の意味を知らなくても品詞は推測できることが多いので、ダミーも同じ品詞に揃えることにした。言い換えれば、問題の語と品詞が異なる候補は捨てる。実際には、全部捨てるとかえって不自然なので、品詞が異なる候補は2/3の確率で捨てる。結果として、80/81の確率でダミー候補に少なくとも一つの同一品詞の語が存在することになる。

22問の検査の間の状態遷移に関わる内部状態をどうやって保持するかも面倒な問題だった。少なくとも、ユーザ名、乱数親種、過去に出した問題とその成否のリストを持ち回さねばならない。22問程度の情報量であればURLのquery stringに全部入れられるのだが、最終出力をTwitterで呟けるようにすると考えると、あまり長いURLにはしたくない。したがって、途中経過のデータは全て転送フォームのhiddenフィールドで持ち回して、最後に結果を出力する直前にサーバ側で全22問の回答状況をファイルに保存することにした。そのファイルのIDをURLに含めることで、過去の結果を参照することができる。Twitterに書き込むURLもそうすれば良い。

英語力年齢診断は以前にも実装したことがあるのだが、今回のにはいくつか改良点がある。検査対象の語数が前回の3万語から7万語に拡大した。単語だけでなく複数語からなる熟語も扱う。訳語を集める際に日本語WordNetだけでなくWiktionaryなども使って拡充している。前回の実装だと、何度もやっていると同じ問題が出てきてつまらないという問題があったが、今回のではそれはほぼ解消されている。熟語の難易度がちょっと低すぎる気もしているので、そのあたりのチューニングは課題かも。


まとめ。英和辞書と獲得年齢データベースがあれば、英語の語彙力診断テストを作成することができる。たった22問の問題に答えるだけで、そこそこそれっぽい実力判定ができる。私個人の結果は17歳か18歳になることがほとんどだ。ところで、知らない言葉でも接頭辞とか接尾辞で大体の意味や品詞を推定できるので、「わからない」という選択肢を積極的に選ばずにそれっぽい語を選択していけば、それなりの結果が得られる。このような推論は実際にネイティブ話者も日常でやっていることなので、別にズルではない。いずれにせよ、全問正解すると20歳になるのだが、普通の日本人がそれを達成するのは相当難しいだろう。一方で、高等教育を受けたネイティブ英語話者(かつ一定の日本語を習得したバイリンガル)であれば容易にそれを達成するだろう。もしそうであれば、私の目論見通りだ。

Twitterで呟かれた結果を見ていると、皆さん勉強しているんだなぁ、としみじみ思う。アーリーアダプタ的に試してくれたITエンジニアっぽい人々は普通に17歳から20歳まで叩き出していて、やっぱり生き残っている人々にはそれなりの理由があるのだなと感心する。私も地道にやっている自負があって何とか17歳くらいは出せるけれども、彼らの中では「出来る」って誇れるレベルではないのだなぁと、遠い目にならざるを得ない。まあ、それでも地道に生きるしかない。

それにしても、改めて思うのは、非ネイティブがネイティブ並の語彙力を身につけるなんてのは本当に難しい話だ。「shigellosis」(赤痢)とか、「heterosis」(雑種強勢)とか、18歳なら知っているべき言葉らしいが、日本語であれば理解できるので、英語圏で同じ概念の言葉が知られているのは合点がいく。しかし、習慣的に英字新聞を読んでいるくらいでは、それらの言葉にはなかなか出会わない。7万語を全部覚えるのは無理だ。検査対象が7万語なだけで、同レベルの語を全て覚えるとしたらその何倍もが巷で使われているはずだ。非ネイティブとして育った以上、カバー率をネイティブ並に引き上げるのはおそらく一生かかっても不可能なので、どこかで線を引いて割り切るのが肝心なのだろう。とはいえ14歳くらいのレベルには至っていないと新聞を読むのも厳しいので、割り切った上で目標の語彙力を獲得および維持するのは重要だ。このツールがその一助になれば幸いである。

追記:5択だと推測による回答が簡単すぎるとの声が多かったので、6択に増やした。当てずっぽうを阻止するという意味では10択とか20択にしても良いのだが、そうすると検査にかかる時間が増えてモチベーションを下げてしまうので悩ましい。推測力も含めて語彙力だとは思うので、とりあえずはこんなもんでいいかな。