北野坂備忘録

主にインストールやプログラミングのメモを載せています。

言語処理100本ノック 2015年版 (90-95)

 前回90番のプログラムを載せ忘れていたのでまずはそこから。

#!/usr/bin/env python

from gensim.models import word2vec

data = word2vec.Text8Corpus('80.txt')
model = word2vec.Word2Vec(data, size=300)
voc=model.vocab.keys()

if __name__ == "__main__":

  for x in voc:
    print(x,end=" ")
    for y in model[x]:
      print(y,end=" ")
    print("")

 word2vecに任せるだけなのでプログラムは超カンタンです。

91. アナロジーデータの準備

単語アナロジーの評価データをダウンロードせよ.このデータ中で": "で始まる行はセクション名を表す.例えば,": capital-common-countries"という行は,"capital-common-countries"というセクションの開始を表している.ダウンロードした評価データの中で,"family"というセクションに含まれる評価事例を抜き出してファイルに保存せよ.

単語アナロジーとは何か。例を見てみましょう。

: currency
Algeria dinar Angola kwanza

「Algeriaの通貨はdinar。ではAngolaの通貨は?」「kwanza」
 というようなデータです。クイズとその回答のようなものですね。

#!/usr/bin/env python

import codecs
import re

fin = codecs.open('questions-words.txt', 'r', 'utf_8')

if __name__ == "__main__":

  for line in fin: #読み込み
    string = re.split(" ",line[:-1])
    if string[0] == ":":
      if string[1] == "family":
        family = True
      else:
        family = False
    if family == True:
      print(line[:-1])

 もうちょっとマシな切り出しかたもあろうとは思いますが。もっと行数が多ければ「:family」でなくなった時点で終わらせる処理を入れたほうがいいと思います。

結果
: family
boy girl brother sister
boy girl brothers sisters
boy girl dad mom
boy girl father mother
(略)
uncle aunt son daughter
uncle aunt sons daughters
uncle aunt stepbrother stepsister
uncle aunt stepfather stepmother
uncle aunt stepson stepdaughter

92. アナロジーデータへの適用

91で作成した評価データの各事例に対して,vec(2列目の単語) - vec(1列目の単語) + vec(3列目の単語)を計算し,そのベクトルと類似度が最も高い単語と,その類似度を求めよ.求めた単語と類似度は,各事例の末尾に追記せよ.このプログラムを85で作成した単語ベクトル,90で作成した単語ベクトルに対して適用せよ.

残念ながら91で作成した単語の中には85や90で作成した単語ベクトルの中に入っていないものがあります。
それは諦めるようにプログラムを作成。
いちいちファイルを読み込んで比較していくと遅いので単語ベクトルをメモリに持たせました。ベクトルの量が多ければこれは動かなくなると思います。

#!/usr/bin/env python

import codecs
import re
import numpy as np
import copy

def cos_sim(x, y): #コサイン類似度計算
    return np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))

def getallvec(): #全ベクトル読み込み
  vecdict = {}
  fin = codecs.open('85logvec.txt', 'r', 'utf_8')
  for line in fin: #読み込み
    string1 = re.split(" ",line[:-1])
    a = [float(x) for x in string1[1:301] ]
    vecdict[string1[0]] = copy.deepcopy(np.array(a))
  return vecdict

def getsim(v,worddict): #類似度計算
  string = ""
  cos_sim1 = 0
  for k,v1 in worddict.items():
    npe = v1
    cos_sim2=cos_sim(v,npe)
    if cos_sim2 > cos_sim1: #類似度が大きいものに取り替え
      cos_sim1 = cos_sim2
      string = k
  print(string,end=" ")
  print("{0:f}".format(cos_sim1))

if __name__ == "__main__":

  worddict = getallvec()

  f91 = codecs.open('91.txt', 'r', 'utf_8')
  for line in f91: #読み込み
    string = re.split(" ",line[:-1])  

    if not string[1] in worddict:
      print(string[1]," not exist.")
      continue
    else:
      npa = worddict[string[1]]

    if not string[0] in worddict:
      print(string[0]," not exist.")
      continue
    else:
      npb = worddict[string[0]]

    if not string[2] in worddict:
      print(string[2]," not exist.")
      continue
    else:
      npc = worddict[string[2]]
      
    npd = npa - npb + npc
    print(string[1],"-",string[0],"+",string[2],end=" ")
    getsim(npd,worddict)
結果

85単語ベクトル

girl - boy + brother brother 0.969367
girl - boy + brothers brothers 0.896437
girl - boy + dad boyfriend 0.873149
girl - boy + father father 0.963852
girl - boy + grandfather grandfather 0.871043
grandpa  not exist.
girl - boy + grandson grandson 0.778333
girl - boy + groom seemingly 0.622918
girl - boy + he he 0.996097
girl - boy + his his 0.998158
(略)

90単語ベクトル

girl - boy + brother brother 0.946068
girl - boy + brothers brothers 0.905701
girl - boy + dad girl 0.750535
girl - boy + father father 0.946162
girl - boy + grandfather grandmother 0.844906
grandpa  not exist.
girl - boy + grandson granddaughter 0.816291
girl - boy + groom girl 0.746943
girl - boy + he he 0.967500
girl - boy + his his 0.959634
(略)

あまりうまくいっていません。90単語ベクトルの方が「grandmother」や「granddaughter」をうまく類推できています。

93. アナロジータスクの正解率の計算

92で作ったデータを用い,各モデルのアナロジータスクの正解率を求めよ.

正解率出すんだったら最初っから4列目の単語も入れておきたかったな~。
そもそも単語が存在しなかったケースはどう処理すべきか。
今回は分母を減らすのではなくて、不正解として扱います。

#!/usr/bin/env python

import codecs
import re

f85vec = codecs.open('9285vec.txt', 'r', 'utf_8')
f91 = codecs.open('91.txt', 'r', 'utf_8')
list1 = []
list2 = []
list3 = []

if __name__ == "__main__":

  for line in f91: #読み込み
    string = re.split(" ",line[:-1])
    list1.append(string[3])

  for line in f85vec: #読み込み
    string = re.split(" ",line[:-1])  
    if len(string) > 5:
      list2.append(string[5])
    else:
      list2.append("")

  n = 0
  x = 0
  while n < len(list1):
    list3.append([list1[n],list2[n]])
    if list1[n] == list2[n]:
      x += 1
    n += 1

  print(x)
  print(x/len(list1))
結果
85vec: 0.025691699604743084 (506件中13件正解)
90vec: 0.08695652173913043  (506件中44件正解)

思ったより90vecも低い。

94. WordSimilarity-353での類似度計算

The WordSimilarity-353 Test Collectionの評価データを入力とし,1列目と2列目の単語の類似度を計算し,各行の末尾に類似度の値を追加するプログラムを作成せよ.このプログラムを85で作成した単語ベクトル,90で作成した単語ベクトルに対して適用せよ.

類似度はコサイン類似度でいいのかな?
set1とset2の2つのセットが存在するので、2セット*2ベクトル=4セットのデータが生成されます。

#!/usr/bin/env python

import codecs
import re
import numpy as np
import copy

def cos_sim(x, y): #コサイン類似度計算
    return np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))

def getallvec(): #全ベクトル読み込み
  a = []
  npxdict = {}
  fin = codecs.open('85logvec.txt', 'r', 'utf_8')
  for line in fin: #読み込み
    string1 = re.split(" ",line[:-1])
    a = [float(x) for x in string1[1:301] ]
    npxdict[string1[0]] = copy.deepcopy(np.array(a))
  return npxdict

if __name__ == "__main__":

  worddict = getallvec()

  n = 0
  fset = codecs.open('set2.tab', 'r', 'utf_8')
  for line in fset: #読み込み
    if n == 0: #タイトル行を飛ばす
      n += 1
      continue
    string = re.split("\t",line[:-2]) 
    if not string[0] in worddict:
      print(string[0]," not exist.")
      continue
    else:
      npa = worddict[string[0]]
    if not string[1] in worddict:
      print(string[1]," not exist.")
      continue
    else:
      npb = worddict[string[1]]
    print(string[1],string[0],end=" ")
    print("{0:f}".format(cos_sim(npa,npb)))
    n += 1
結果(set1を85で作成した単語ベクトルに適用したもの)
sex   love 0.225515
cat   tiger 0.823653
tiger   tiger 1.000000
paper   book 0.531330
keyboard   computer 0.121840
internet   computer 0.301095
car   plane 0.456288
car   train 0.314277
communication   telephone 0.137384
radio   television 0.724248
(略)
結果(set1を90で作成した単語ベクトルに適用したもの)
sex   love 0.559486
cat   tiger 0.790749
tiger   tiger 1.000000
paper   book 0.557259
keyboard   computer 0.668568
internet   computer 0.634529
car   plane 0.604410
car   train 0.601514
communication   telephone 0.543191
radio   television 0.783467
(略)

85vecはコンピューターやキーボードに対する類似度が90vecよりかなり低い。

95. WordSimilarity-353での評価

94で作ったデータを用い,各モデルが出力する類似度のランキングと,人間の類似度判定のランキングの間のスピアマン相関係数を計算せよ.

単語がベクトル内に存在しなくて計算できなかった行があるんですよねー。
欠損値は飛ばすか。
スピアマン相関係数かぁ……R使いたいなあ……(この前からこれしか言ってないような)。
まずは順位を計算するプログラムを作って……。
スピアマンの順位相関係数の計算式は以下のとおり。
1-6×Σ(x-y)^2/(n×(n^2-1))

#!/usr/bin/env python

import codecs
import re

f94 = codecs.open('9490set2.txt', 'r', 'utf_8')
fset = codecs.open('set2.tab', 'r', 'utf_8')
list1 = []
list2 = []

if __name__ == "__main__":

  n=0
  for line in f94: #94データ読み込み
    string = re.split(" ",line[:-1]) 
    if len(string) > 4:
      list1.append([n,string[0],string[3],string[4]])
    else:
      list1.append([n,""])
    n += 1

  n = 0
  for line in fset: #set読み込み
    if n ==0:
      n += 1
      continue
    string = re.split("\t",line[:-2]) 
    list1[n-1].append(string[2])
    n += 1
 
  for x in list1: #欠損値削除
    if not x[1] == "":
      list2.append(x)
#並べ替え(cos類似度)
  list1_2 = sorted(list2, key=lambda sim: sim[3], reverse=True)
#順位設定(1から)
  n=1
  for x in list1_2:
    list1_2[n-1].append(n)
    n += 1

#並べ替え(人間判定類似度)
  list1_3 = sorted(list1_2, key=lambda sim: sim[4], reverse=True)
#順位設定(1から)
  n=1
  for x in list1_3:
    list1_3[n-1].append(n)
    n += 1

#スピアマン相関係数計算
  num = len(list1_3)

  dis=0
  for x in list1_3:
    dis += (int(x[5])-int(x[6]))**2

  spearman = 1 - ( 6 * dis / ( num * (num**2 - 1) ))

  print(spearman)
結果
85vec set1 0.26828305255447715
90vec set1 0.47424152184541535
85vec set2 0.3813785991508144
90vec set2 0.5302377559438987

んー。やっぱりword2vecの精度は高い……