3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

情報検索・検索技術Advent Calendar 2024

Day 10

annoyとknnで英文の難しさ推定

Posted at

自己紹介&やること

普段は教育AIや自然言語処理の研究をしており、IRコミュニティには基本的にいません。英単語等の学習支援システムが専門です。本当に久しぶりにQiitaを使います。10年ぶりぐらいでしょうか…

埋め込みベクトルを使う事があったので、annoyとk-nearest neighbor(K近傍)を使って英文の難しさ推定をやってみます。せっかくなので公開してみる事にしました。要するに、英文を入力すると難しさを数値で出します。
実験環境にはGoogle Colaboratoryを使います(課金しています)。

手法

テキストを入力として、意味的に近い文同士の距離が近くなるようなベクトルを出力するのが「埋め込みベクトル」の技術です。今回はOPenAIのtext-embedding-ada-002を使います。これはOpenAI APIを通じて文を投げると、1500次元でノルム(ベクトルの長さ)が1.0になるように正規化されたベクトルを返してくれます。

英文の文単位の難しさを英語教師等の先生に読んでもらって、4段階で難しさを評価してもらったデータセットというのはここで手に入ります。難しさの段階の基準にはCEFRという基準があり、A1,A2,B1,B2があり、後の方が難しいです。ここでは、単純に番号にします。

このようなデータセットを使う場合には、「引用してください」(citation)と書かれている論文を引用することが必要です。論文はここです。

ダウンロードしてきたデータセットを適切なフォルダにおきましょう。文の評価者は2名いますが、今回は2名の評価が一致しているデータだけを使います。

def read_dscefr(dscefr):
  with open('/content/CEFR-SP-main/CEFR-SP/SCoRE/CEFR-SP_SCoRE_train.txt', 'r') as f:
    for line in f:
      ary = line.strip().split('\t')
      if ary[-1]!=ary[-2]:
        continue
      dscefr[ary[0]]=int(ary[1])
dscefr = {}
read_dscefr(dscefr)

文をキー、値をtorchのtext-embedding-ada-02とする辞書を用意します。最初の10文をテストに使いたいので10文以降で作ります。

sdvecs = dict(zip(sents_orig[10:],sents_embeddings[10:]))
from annoy import AnnoyIndex
def get_annoyidx(sdvecs):
  lsdvecskeys = sorted(list(sdvecs.keys()))
  annoyidx = AnnoyIndex(sdvecs[lsdvecskeys[0]].shape[0], 'angular')
  ds2sid = {k:i for i,k in enumerate(lsdvecskeys)}
  rev_ds2sid = {v:k for k,v in ds2sid.items()}
  for k in sdvecs.keys():
    annoyidx.add_item(ds2sid[k], sdvecs[k])
  annoyidx.build(10)
  return annoyidx, ds2sid, rev_ds2sid
annoyidx, ds2sid_annoy, rev_ds2sid_annoy = get_annoyidx(sdvecs)

ベクトルを入力すると3-nearest neighborを検索してきて、その10例の平均値を難しさとして推定するコードを書きます。

import numpy as np
def get_annoydiff(v):
  myids, vs =  annoyidx.get_nns_by_vector(v,3,include_distances=True)
  return (np.mean([dscefr[rev_ds2sid_annoy[myid]] for myid in myids]))

最後に10文で実験してみます。文例をここに直接載せると問題になるかもしれないので(ちゃんとした論文での引用ではないので)、載せません。正解ラベルと比較します。

for i in range(10):
  print('estimated:', get_annoydiff(sdvecs[list(sdvecs.keys())[i]]),  'true label:' ,dscefr[sents_orig[i]])

結果はこんな感じです。難しさではなくて文の意味的類似度で見ているので、簡単な文が軒並み間違っています。難しさ2の時のみ大体当たっているように見えます。

estimated: 2.3333333333333335 true label: 1
estimated: 2.0 true label: 1
estimated: 2.0 true label: 1
estimated: 2.0 true label: 1
estimated: 2.3333333333333335 true label: 1
estimated: 2.0 true label: 1
estimated: 2.0 true label: 2
estimated: 2.0 true label: 2
estimated: 2.3333333333333335 true label: 2
estimated: 2.3333333333333335 true label: 2

今回はやりませんでしたが、一般の好きな文例を入力してもその埋め込みベクトルを計算して同じようにすれば、annoyで英文の難しさ推定気が一応作れるはずです。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?