6 文の類似度

  • 単語を単位として分の類似度を算出します。

6.0.1 関数とメソッド

In [1]:
# 関数
# 小数点以下を切り捨てる関数は
a = 2.5
round(a)

# 命令文と考えたら、roundが命令(動詞)で()内に目的語を書く感じ。
# 「aの小数点以下を切り捨てろ」みたいな。
# ()内に「目的語」の後ろに「どうやって」を表す副詞みたいな表現が入るときもある。
Out[1]:
2
In [ ]:
# メソッド
# 関数と同じ命令だけど、「文法」が少し違う」
# 大文字を小文字にするメソッドは

a = "I LOVE YOU MORE THAN ANYTHING IN THE WORLD."
a.lower()

# .(ドット)の前が「目的語」でその後ろに命令(動詞)を書く。
# ()の中に「どうやって」を表す副詞みたいな表現が入るときもある。

6.0.2 パッケージ

  • 何もimportすることなく使用できるlen()などは組み込み(ビルトイン)関数と呼ばれ、
  • デフォルトで使えるものでした。
  • デフォルトではない関数を呼び出して使えるようにするには以下のように書きます。
  • また、関数が集まったものをパッケージと呼びます。
In [2]:
# numpyをnpという名前でimportする(これでnumpy以下の関数はすべて使える)
import numpy as np

# とするとnp.xxでnumpyの関数が使えるようになる
np.sqrt(2)
Out[2]:
1.4142135623730951
In [3]:
# 特定の関数をimportするとき
from numpy.random import *

# 乱数の発生
L = rand(100) 
# import numpy as npと書いてたら
# L = np.random.rand(100)

6.0.3 関数の定義

  • 自分で関数を定義することができます。
  • ファイルに保存した関数をモジュールと呼びます。
In [4]:
# 関数の書き方
def func1(x):
    return x +12
In [6]:
# 関数の呼び出し
func1(111)
Out[6]:
123
In [ ]:
# この関数をmodule1.pyとして保存すると

from module1 import func_plus

func_plus(2)

6.0.4 クラス

In [7]:
class myfirstclass:
    def __init__(self):
        self.name = ""
   
    def getName(self):
        return self.name
    
    def setName(self,name):
        self.name = name

classA = myfirstclass() #インスタンスの生成と言います。これでこのクラスのメソッドが使用できるようになります。
classA.setName("Saionji") #setName()を呼び出して引数をセット
classA.getName() # getName()を呼び出す
Out[7]:
'Saionji'
  • ここでは自身でクラスを書くのではなく、
  • クラスの使い方を学んで
  • 使えるようになることが目的です。
  • クラスは関数の設計図のようなもので、
  • インスタンスを生成することで
  • そのクラスのメソッドが使えるようになります。
  • クラスの中で定義された関数は生成されたインスタンスのメソッドとして使用できます。

6.1 編集距離

  • 単語列Aを単語列Bにする場合に必要とされる「削除、追加、置換」の回数。
  • "I have a pen"と"I have a dog"は"pen"を"dog"で置換してるから編集距離は1。
  • "can you hear me"と"you can hear me"は、"can"を"you"に"you"を"can"に置換してるから編集距離は2。
In [1]:
# 例文
a = "I walk to school"
b = "I walked to school"
c = "Yesterday I walked to school"
d = "I walked to school yesterday"
In [2]:
# 文を単語のリストへ
from nltk import word_tokenize
aw = word_tokenize(a)
bw = word_tokenize(b)
cw = word_tokenize(c)
dw = word_tokenize(d)
In [3]:
# 編集距離の算出
from nltk.metrics.distance import edit_distance
edit_distance(aw,bw)
Out[3]:
1
In [4]:
L = [aw,bw,cw,dw]
P = []
for i in L:
    M = []
    for j in L:
        M.append(edit_distance(i,j))
    P.append(M)
print(P)
[[0, 1, 2, 2], [1, 0, 1, 1], [2, 1, 0, 2], [2, 1, 2, 0]]
In [5]:
# データフレームにしてみた。
import pandas as pd
df1 = pd.DataFrame({"a":P[0],"b":P[1],"c":P[2],"d":P[3]},index=["a","b","c","d"])
df1
Out[5]:
a b c d
a 0 1 2 2
b 1 0 1 1
c 2 1 0 2
d 2 1 2 0

6.2 編集距離の問題点

  • 意味的にはほぼ同じ"Yesterday I walked to school"と"I walked to school yesterday"の編集距離が2に対して、
  • 意味的に異なる"I walked to school"と"I walk to school"の編集距離が1となり、
  • 期待する結果が得られない。編集距離は語順に強い影響を受ける。

6.3 Bag-of-words

  • 編集距離より頑健な手法として、bag-of-wordsというアプローチがある。
  • 単語の出現回数を数えてベクトルとして扱う("I"は全ての文にあるのでカウントされない。)。

表1

単語 文a 文b 文c 文d
school 1 1 1 1
to 1 1 1 1
walk 1 0 0 0
walked 0 1 1 1
yesterday 0 0 1 1
  • このように文をベクトルとして表現するとユークリッド距離やコサイン類似度を計算できる。
  • 2つのベクトルの距離を測るのがユークリッド距離
  • 2つのベクトルの角度を測るのがコサイン類似度 PBC13_1
In [6]:
# 対象となる文をリストに格納する。
L = [a,b,c,d]

# sklearnのクラスのインポート
from sklearn.feature_extraction.text import CountVectorizer

# インスタンスの作成
vectorizer = CountVectorizer(min_df=1)
In [7]:
# カウントする単語を登録
vectorizer.fit(L)

# 文のベクトル表現を算出 (Compressed Sparse Row)
X = vectorizer.transform(L)

# 行列の出力(表とは行と列が入れ替わっている)
X.toarray()
Out[7]:
array([[1, 1, 1, 0, 0],
       [1, 1, 0, 1, 0],
       [1, 1, 0, 1, 1],
       [1, 1, 0, 1, 1]])
In [8]:
# 表1と同じにしたかったら
X.toarray().transpose()
Out[8]:
array([[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 0, 0, 0],
       [0, 1, 1, 1],
       [0, 0, 1, 1]])
In [9]:
# 新たな文を同じ方法でベクトル化する
M = ["I yesterday walked to school"]

X2 = vectorizer.transform(M).toarray()
X2
Out[9]:
array([[1, 1, 0, 1, 1]])
  • 単語のみのBag of wordsだと単語の出現順序を捉えることができない。
  • "He stole a car, and I bought a banana."と"I stole a car, and he bought a banana."は同じになってしまう。

6.4 N-gram

  • 隣り合うN個のかたまりのこと
  • "This is a pen"の2-gramは["this is", "is a", "a pen"]
  • 1-gramはuni-gram、2-gramはbigram、3-gramはtri-gram、それ以降はfour-gramみたいに読む。
  • N-gramを使うと多少語順を考慮できる。
In [11]:
L = [a,b,c,d]

bigram_vectorizer = CountVectorizer(min_df=1,ngram_range=(2,2))

bigram_vectorizer.fit(L)

X_b = bigram_vectorizer.transform(L).toarray()

X_b
Out[11]:
array([[0, 1, 1, 0, 0],
       [0, 1, 0, 1, 0],
       [0, 1, 0, 1, 1],
       [1, 1, 0, 1, 0]])
In [12]:
# bigramのリスト
bigram_vectorizer.get_feature_names()
Out[12]:
['school yesterday', 'to school', 'walk to', 'walked to', 'yesterday walked']

6.5 ユークリッド距離

  • 「通常の」2点間の距離
  • 点pと点qの距離は以下の式で求められる。 $$ d(p,q) = \sqrt{(q_1-p_1)^2 + (q_2-p_2)^2 + ... + (q_n -p_n)^2} \\ = \sqrt{\sum_{i=1}^n(q_i - p_i)^2} $$

  • 表1の場合、文が5つの単語で表現されているので、

  • 5次元空間におけるユークリッド距離を求める。
In [10]:
# X.toarray()を代入
X_a = X.toarray()

# numpyのインポート
import numpy as np

# ユークリッド距離を求める
np.linalg.norm(X_a[2] - X_a[3])
Out[10]:
0.0
In [11]:
X_euc = []

for i in X_a:
    x = []
    for j in X_a:
        x.append(np.linalg.norm(i - j))
    X_euc.append(x)
X_euc
Out[11]:
[[0.0, 1.4142135623730951, 1.7320508075688772, 1.7320508075688772],
 [1.4142135623730951, 0.0, 1.0, 1.0],
 [1.7320508075688772, 1.0, 0.0, 0.0],
 [1.7320508075688772, 1.0, 0.0, 0.0]]
In [12]:
# 2桁のみ表示
pd.options.display.precision = 2
# データフレームしてみた。
df2 = pd.DataFrame({"a":X_euc[0],"b":X_euc[1],"c":X_euc[2],"d":X_euc[3]},index=["a","b","c","d"])
df2
Out[12]:
a b c d
a 0.00 1.41 1.73 1.73
b 1.41 0.00 1.00 1.00
c 1.73 1.00 0.00 0.00
d 1.73 1.00 0.00 0.00

6.6 コサイン類似度

  • ベクトル同士の成す角度の近さを表す。
  • 1に近ければ類似度が高く、0に近ければ類似度が低い。
  • 2つのベクトル$\vec{p}=(p_1,p_2,...p_n)$および$\vec{q}=(q_1,q_2,...q_n)$に対して
  • 以下の式で求められる。 $$ cos(\vec{p},\vec{q}) = \frac{p_1q_1 + p_2q_2 ... p_nq_n}{\sqrt{p_1^2+p_2^2+ ... p_n^2}\sqrt{q_1^2+q_2^2+...q_n^2}} \\ = \frac{\sum_{i=0}^np_nq_n}{\sqrt{\sum_{i=0}^np^2}\sqrt{\sum_{i=0}^nq^2}}$$
In [13]:
# scipyのインポート
import scipy.spatial.distance as dis
# コサイン類似度
dis.cosine(X_a[2],X_a[3])
Out[13]:
0.0
In [14]:
# すべての文のコサイン類似度
X_cos = []

for i in X_a:
    x = []
    for j in X_a:
        x.append(dis.cosine(i,j))
    X_cos.append(x)
X_cos
Out[14]:
[[-2.2204460492503131e-16,
  0.33333333333333326,
  0.42264973081037416,
  0.42264973081037416],
 [0.33333333333333326,
  -2.2204460492503131e-16,
  0.13397459621556129,
  0.13397459621556129],
 [0.42264973081037416, 0.13397459621556129, 0.0, 0.0],
 [0.42264973081037416, 0.13397459621556129, 0.0, 0.0]]
In [15]:
# データフレームにしてみた。
df3 = pd.DataFrame({"a":X_cos[0],"b":X_cos[1],"c":X_cos[2],"d":X_cos[3]},index=["a","b","c","d"])
df3
Out[15]:
a b c d
a -2.22e-16 3.33e-01 0.42 0.42
b 3.33e-01 -2.22e-16 0.13 0.13
c 4.23e-01 1.34e-01 0.00 0.00
d 4.23e-01 1.34e-01 0.00 0.00

練習問題

  • 日本人英語学習者が以下の問題に回答しました。

When you want to break into a conversation and add something, what would you say?

  • 評定者によって1 (Good)と採点されたデータは、../DATA02/TALL06_1.txtに、0 (Poor)と採点されたデータは../DATA02/TALL06_0.txtに保存されています。
  • 以下の発話はGoodとPoorのどちらと判定されるべきでしょうか。
  • ユークリッド距離あるいはコサイン類似度を算出してみましょう。
  1. can i join your conversation
  2. join me a conversation
  3. let me talk
  4. please listen to me