atama plus株式会社のアルゴリズムチームでインターンをした話

# はじめに
この記事は[atama plus Advent Calendar 2023](https://qiita.com/advent-calendar/2023/atamaplus) の9日目の記事です

はじめまして。今年の4月からatama plus株式会社に新卒入社した@kiyoです。
入社後して8ヶ月が経ち、業務にも慣れてきたため、私が内定者インターン時代にしていたことを記事にしようと思います。

当時(2022年6月)の私は工学系研究科の修士2年生で、大学では機械学習とコンピュータプロセッサに関する研究をしていました。
atama plus株式会社に内定をもらった私は、どうしてもインターンに参加したいと思い、アルゴリズムチームにインターン枠を用意してもらいました。
アルゴリズムチームとは、atama plusのプロダクトの根幹を担う、レコメンドエンジンの改善や、学習体験の改善のための研究開発を行っているチームです。

# 課題「ケアレスミス判定機能の精度改善」
インターン開始当時、私が配属されたアルゴリズムチームは「ケアレスミス判定機能」のプロトタイプの開発に取り組んでいました。私に渡された課題は、この判定機能の精度を上げることでした。

atama plusのプロダクトで英語を学習する際に、穴埋め問題が出題されることがあります。そこで問題になっていたのが、文頭を大文字にし忘れる、三単現のsをつけ忘れるなどの、いわゆるケアレスミスで不正解になってしまうことでした。これにより、本来解く必要のない問題がレコメンドされたり、理解しているはずの単元が未達成と表示されたりして、生徒のやる気が削がれていました。これでは生徒は本来学ぶべき文法事項に集中できず、学習にかかる時間も伸びてしまいます。

この問題に対する解決策としてアルゴリズムチームで考案されたのが、ケアレスミス判定機能でした。

ケアレスミス判定機能とは、穴埋め問題の回答にケアレスミスが含まれているかどうか判定し、ケアレスミスが含まれている可能性が高ければ見直しを促すポップアップを出すという機能です。
![スクリーンショット 2023-05-15 14.57.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3358304/b1f8a655-c93f-cc56-ea27-2b820c3be3e3.png)
機能の実用化のためには高い精度でケアレスミスの判定を行う必要がありますが、開発中のプロトタイプの精度(F値)は十分ではなく、私の役目はこの精度を上げることでした。

# データセットと評価指標
改めて、ケアレスミスの判定問題を整理すると次のようになります。

* 英語穴埋め問題について生徒の回答(誤答)と模範解答が与えられる
* 生徒の回答に対して文頭大文字、3単元のsなど複数パターンの誤答ラベルを付与する(検知する)

使用できるデータとして、ラベルをつけた生徒の回答データをコンテンツチームの方が用意してくれました。このデータを正解データとし、評価にもこのデータを使用することにしました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3358304/e862a598-a88a-2b0d-66ca-0ab22dded22a.png" width=80%>

適切な評価指標の設定は、メンターの方と相談しながら行いました。最終的には全データに対するF値と、誤答パターンごとのPrecision、 Recallを用いることにしました。ここでは、F値は総合的な精度、誤答パターンごとのPrecisionはその誤答パターンに対する判定の正確さ、と捉えてもらえれば十分です。補足1に評価指標の解説を記載したので、そちらも読んでみてください。

この課題に対して、
　(1) データ観察とルールベースロジックの改善
　(2-1) 機械学習によるアプローチ
　(2-2) ルールベースと機械学習の組み合わせ
という順番で精度改善へのアプローチを行いました。

以下でこれらのアプローチを順番に説明します。

# アプローチ
### 1. データ観察とルールベースロジックの改善
最初に行ったのは、プロトタイプのルールベースロジックでは、どんな誤答パターンで正しくケアレスミスを検知できていないのかを調べることでした。調査結果をまとめた以下のグラフでは、青が正しく分類できたデータ数、オレンジが間違えて分類したデータ数を示しています。グラフに示されている通り、誤答パターンの中で「日英訳ミス」のPrecisionがとても低いことが分かりました。日英訳ミスとは、例えば「I will <INS>sell</INS> this book」と答えるべきところを「I will <INS>buy</INS> this book」のように、文法的には間違えていないが、別の訳語を答えてしまっているパターンと定義しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3358304/6afb48c7-7199-eada-132b-589b225d5009.png" width=60%>

現状のロジックでは、日英訳ミスには当てはまらないデータを日英訳ミスと判定してしまう場合が多いということですね。

さらにデータを眺めると、日英訳ミスの中でも、特に助動詞のdo、haveとbe動詞を間違えているデータでの誤検知が多いことが分かりました。例えば以下のような例です。
* 正しい回答：The computers <INS>are</INS> <INS>used</INS> every day. 
* 生徒の回答：The computers <INS>have</INS> <INS>used</INS> every day. (下線部を穴埋め)

上記の”are”と”have”はともに助動詞であり、動詞の”sell”と”buy”を間違えた時とミスの構造は同様です。しかし英語学習においては、受動態の理解、現在完了の理解を確認する必要があるため、これらの間違いをケアレスミスと扱うわけにはいきません。そこでルールベースロジックに例外処理を挟むことによって、この誤検知を解決しました。その結果「日英訳ミス」のPrecisionは6%向上し、全体のF値は4.2%向上しました。

### 2-1. 機械学習モデルを使用
次に私が行ったのは、機械学習によって検知を行うことでした。狙いとしては、
* プロトタイプのルールベースロジックをベンチマークとして、どれくらいの優位性があるのかを定量的に評価する
* ロジックの改善では拾いきれないケースを修正する
の2つがありました。

最終的に構築した機械学習モデルの概念図を貼っておきます。
モデル構築の詳細な過程は補足2に記載しておりますので、興味のある方はこちらも読んでみてください。

![スクリーンショット 2023-12-06 19.10.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3358304/754248e6-6edc-5fc3-9686-1888880a8f06.png)

推論結果は残念ながら大きく向上することはなく、全体の判定精度はルールベースの方がやや高く、多くの誤答パターンでルールベースのPrecision、 Recallを下回っていました。一方で誤答パターンの種類ごとに見るとルールベースロジックよりも高い精度を出しているものもあり、ルールベースとの部分的な組み合わせにより、全体の精度を向上できる可能性が見えました。

### 2-2. ルールベースと機械学習の組み合わせ
最後に、ルールベースロジックと機械学習を部分的に組み合わせる（アンサンブル）ことで精度の向上を図りました。ルールベースロジックの課題は引き続き「日英訳ミス」の誤検知が多いことでした。そこで「日英訳ミス」の判定に関して、ルールベースロジックでは検知されるが、機械学習モデルでは検知されなかった場合は、検知として判定しないというアンサンブルを行うことで、誤検知率を大幅に減らすことができると考えました。

実際に、このアンサンブルを行った結果、「日英訳ミス」の誤検知が大幅に減少し、Precisionはさらに29%向上しました。

![スクリーンショット 2023-12-06 19.11.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3358304/9c43feb2-ef1a-38db-948b-2cac84f712f7.png)

# 結果と考察
いくつかのロジックの改善と機械学習とのアンサンブルを行った結果、初期プロトタイプと比較して最終的に全体のF値は11.7%向上しました。
特に機械学習で「日英訳ミス」の誤検知を防げるようになったデータを個別で観察すると、

* 正しい回答：Who <INS>reads</INS> this book ?
* 生徒の回答：Who <INS>is</INS> this book ?
<html></html>

* 正しい回答：Who <INS>wants</INS> an apple cake ?
* 生徒の回答：Who <INS>went</INS> an apple cake ?

以上のようなケースが見られ、1つ目の例はbe動詞と一般動詞を間違えているケース、2つ目の例はスペルミスの可能性が高いが、”went”が既存単語であるためスペルミスではなく日英訳ミスに判定されたケースと捉えることができます。

また、機械学習とのアンサンブルを取ることで「日英訳ミス」に正しく判定できなくなったデータの例として、

* 正しい回答：<INS>one</INS> <INS>thousand</INS> (文章ではなく単語のみ回答)
* 生徒の回答：<INS>ten</INS> <INS>hundred</INS>

のようなケースが見られました。ルールベースでは検知しやすいが、周辺情報がないことや、類題の少なさから機械学習での判定が難しいのかもしれません。

今回のモデルは一定の改善を示しましたが、実装や運用等のコストを考慮し、最終的にはルールベースロジックを改善する方針になりました。そして、今回得られた知見をもとにロジックを改善した結果、アンサンブル手法と同程度の精度改善を実現しました。
このプロジェクトのインターンとしての稼働はここで終了しました。今回の改善を反映したロジックが、インターン終了後の2月から本番プロダクトに導入されたそうです🎉

# インターンの感想と学び
本noteでは、私がインターンで行ったプロジェクトについて紹介しました。

インターンでは様々なことを学びましたが、特にメンターの方から聞いた「**数字だけを見ていないで、定性的な評価をしよう**」という教えが一番印象的でした。
今回のような検知精度(数値)を改善する課題では、調査や開発を行うときも、チームに発表するときも、数値が良くなったことにフォーカスしがちになってしまうのですが、それでは方針が明後日の方向を向いてしまったり、発表を聞いている人に納得感を持って貰えないことも多いそうです。

これを踏まえて、今回のプロジェクトでは改善を行うたびに個別データを確認し「**何ができるようになったのか**」「**できなくなったことはないか**」を明らかにしていました。(「検知率が○%上がった」ではなく「be動詞と一般動詞を間違えているケース」を誤検知しなくなった、など)
これにより、インターン最終発表ではチームメンバーに大変分かりやすかったとの評価を貰い、機械学習での分析結果をルールベースに還元する際もスムーズに進みました。

何より、プロジェクトの内容だけでなく、仕事の進め方に関するところまで気にかけていただいたのが、とても嬉しかったです。

今後はインターンでの学びをいかしつつ、社員としてチームメンバーを巻き込んだ仕事を行っていきたいです。

明日は10日目。@tatsuya4559 の「Zappaやめた話」です！
明日もお楽しみに！


# 補足
### 補足1: 評価指標の設定
機械学習の観点で捉えると、今回の課題は多クラスかつ多ラベルの分類問題と言えます。
評価指標の設定は少し苦労したのですが、[こちらの記事](https://qiita.com/jyori112/items/110596b4f04e4e1a3c9b)などを参考に、最終的には
　a. 誤答パターンごとのPrecision、Recall、 F値
　b. 全データに対するPrecision、Recall、F値
を使用することにしました。

aの誤答パターンごとのPrecision、Recall、F値では、課題を誤答パターンごとに2値分類問題として扱います。
すなわち、誤答パターンごとのPrecisionとは、その誤答ラベルを付与したデータのうち、正しく検知できた割合のことであり、Recallとは、その誤答ラベルを付与するべき全データのうち、検知できた割合のことです。
課題によってはPrecisionやRecallのどちらかだけが重要な場合もありますが、今回の課題ではPrecisionとRecallの両方を向上させる必要がありました。Recallが低ければ生徒のケアレスミスを十分に拾うことができず、Precisionが低ければ、ケアレスミスをしていないのにポップアップが出るケースが増え、生徒の学習の邪魔になるからです。
そこで、PrecisionとRecallの両方の大きさを評価できる指標としてF値があります。このF値を向上目標とする評価指標に据えました。F値はPrecisionとRecallの調和平均であり、式で表すと以下の通りです。

```math
F_{measure}= \frac{2*Recall*Precision}{Recall+Precision}
```

bの全データに対するPrecision、Recall、F値では、誤答ラベルを1つのデータとして捉えて計算をします。全データに対するPrecisionは、全ての付与された誤答ラベルのなかで正解データに含まれる割合を示し、全データに対するRecallは正解データに含まれる全ての誤答ラベルのうち、検知できたものの割合を示します。全データに対するF値はこれらのPrecisionとRecallから計算します。

また、過学習を防ぐために、学習データと評価データは半分に分割し、評価データは数値評価にのみ使用し、個別のデータを眺める際には、学習データのみを確認するようにしました。

### 補足2: 機械学習モデルの構築
今回の課題では機械学習を単純に適用することができず、いくつかの問題がありました。
一番の問題は、正解ラベルの形状に様々なパターンがあり得ることでした。本課題で予測したい正解ラベルは
* 誤答の種類
* その種類の誤答が該当する単語の位置（2単語以上にまたがることも）

さらに1つのデータに対して2つ以上のラベルが付くこともあります（多ラベル）。
従って、機械学習モデルからの出力をどのような形状にするのかという点に苦労しました。

結論としては、誤答単語の位置を予測することは諦め、複数の誤答パターンそれぞれに対して1つの機械学習モデルを作成し、入力データにその種の誤答が含まれるかどうかを個別に判定する手法で多ラベル分類を扱うことにしました。この手法はOne vs Restと呼ばれるそうです。
モデルに入力するデータのデータ形式も、複数種類を試したのち、正しい回答の文章と生徒の回答の文章を単純に結合した形式を採用しました。

正解文章と生徒が回答したテキストデータを特徴量に変更するためには、ベクトル化する必要があります。自然言語処理にあまり詳しくなかった当時の私は、最も扱いやすそうなword2vecを使って、正解文章と生徒回答文章の各単語をベクトル化し単純に結合するという手法を取りました。
機械学習モデルも何が適切なのか分からず、色々調べた結果、[こちらの記事](https://upura.hatenablog.com/entry/2019/10/29/184617)などを参考にして、最終的にLightGBMを使用することにしました。LightGBMはkaggleなどでよく使われており、初手での利用がおすすめされている、とても扱いやすいモデルだそうです。