ユーザー画面操作によるデータ収集の仕組み
先生がドラッグ&ドロップによって操作を行う学習コンテンツは、それ自体でラベリングツールでもありました。 正解データの形は操作方法によって決まるということと、収集したデータの仕組み上見られない軸というものが存在することが分かりました。この記事は、そのような観点ついての記録です。
発音矯正サービスのフィードバックカードは、学習者の録音からピッチ(声の高さ)を抽出して曲線を描き、その上に音節とモーラ(日本語で一拍として数える音の単位)を時間軸に配置します。自動配置は下書きで、ネイティブの先生がドラッグで補正したものが完成版です。
この補正アルゴリズムをチューニングする度に良くなったかを判定する正解データが必要でしたが、別途集める必要はありませんでした。先生が日々行っている補正こそ、ネイティブの耳で判定されたものだったからです。保存時の自動下書き(draft)と人の補正版(final)をペアで記録するようにしたところ正解41件が貯まり、その上にms単位の採点ハーネスを載せました。
人が自動結果を直す画面があるなら、その画面はすでにラベリングツールです。ただし、ラベルが使える形で残るかどうかは操作方法が決めます。ドラッグをどう実装するかがデータ品質を決める作業になるので、以下の三つはUIの使い勝手ではなくデータのための選択でした。
時間軸の補正でまず思いつくのは境界をつかんで引く方法です。音節の開始位置を右に押すとその音節は短くなり、前の音節は長くなります。問題はここで終わりません。前の音節が長くなればその中の配置が再計算され、その結果がさらに前へ波及します。
データの側から見るとこちらの方が厄介です。先生が一度判断しただけなのに複数ユニットの値が一緒に変わると、何を判定したのかが記録からぼやけます。連鎖でずれた値まで正解として扱えば、採点の基準が汚染されます。
そこで境界ではなく区間を動かす方法を選びました。ドラッグでユニットの新しい中心を決め、移動量だけ開始と終了に同じく足します。幅が変わらないので隣を分割し直す理由がなく、保存される記録にはユニットひとつの移動量だけが残ります。判断ひとつがラベルひとつに対応します。
const shift = nextCenter - centerOf(unit)
const moved = {
...unit,
tStart: unit.tStart + shift,
tEnd: unit.tEnd + shift,
}代償はあります。幅が固定されるので「この音節は実際にはもっと長い」という類の補正はこの操作ではできません。レビューで実際に要望されたのは大半が長さではなく位置のずれで、長さの問題は自動アライメント側を直すべき事案として残しました。
区間を自由に動かせるようにすると、三番目の音節が二番目より前に来る状態が作られます。このようなデータはチャートを壊し、正解セットに混ざれば採点結果まで台無しにします。
バリデーションで弾く代わりに、作れないようにしました。新しい中心を隣接ユニットの中心の間にクランプし、最小間隔を設けて中心が重なることも防ぎます。
const MIN_GAP_SEC = 0.02
const lowerBound = prevUnit ? centerOf(prevUnit) + MIN_GAP_SEC : spanStart
const upperBound = nextUnit ? centerOf(nextUnit) - MIN_GAP_SEC : spanEnd
const nextCenter = clamp(draggedCenter, lowerBound, upperBound)ドラッグがどんな座標で入ってきてもユニットの順序は保たれます。整合性チェックを別に置かなくてよいのがこの方式の利点です。
補正を自動値に対するオフセットとして保存する手もありましたが、そうすると自動値が変わるたびにオフセットをどこに足すべきかが再びあいまいになります。補正された時刻そのものを絶対値で保存し、そのユニットに人が触ったという印を残しました。
type Unit = {
text: string
tStart: number
tEnd: number
pitchSemitone: number
/** 人がドラッグで補正したユニット。再解析時に自動値で上書きしない */
edited?: boolean
}再解析は全体を上書きせずマージするようになりました。自動計算の結果を受け取りつつ、人が触った箇所には保存された値をそのまま残します。
const merged = aligned.map((auto, i) => (units[i]?.edited ? units[i] : auto))この一行が二つのことを同時にやります。アルゴリズムを直して再解析を回しても、先生がすでに判定した箇所は動きません。そして正解セットの中で、ラベルのあるユニットと誰も手をつけていないユニットを区別できるようになります。この区別が次の話の前提です。
正解41件で文頭の精度を採点しようとしたところ、41件すべてで最初のユニットの開始位置の移動量が0msでした。誰もその地点をドラッグしなかったからです。
この結果を「文頭はすでに正確だ」と読むことはできません。先生がカードを見るとき、最初の文字の開始が少しずれていても目に留まらず、文中の文字が音とずれていればすぐ気づきます。直す理由がなかったのではなく、直す理由が目に入らなかっただけです。
人が直す画面から正解を得る方式には、この偏りが構造的についてきます。ラベルは人が不便を感じた箇所にだけ生まれ、不便が目に入らない軸はデータに何の痕跡も残しません。その軸を改善できたかはこのハーネスでは判定できないので、レビューラウンドの指摘件数で別に判定する必要がありました。機械採点と人の採点を両方維持している理由がここにあります。
- 幅の補正は依然として不可能です。長さが実際に異なる場合は自動アライメント側を直す必要があります。
- ユニット数が変わる再解析では、インデックス基準のマージが成立しません。テキストが修正されたカードは補正を引き継がず初期化するようにしています。
- 再生位置の表示が音とずれる問題は、この補正とは無関係な再生同期側の事案として残っています。
正解データを集めようと計画したことはありません。補正画面を作り、その結果を捨てなかったことが結果的に正解生成器になりました。その代わり、そのデータがどこを見られないのかは別に突き止める必要がありました。