言語処理100本ノック2020(python)備忘録60-69

60

import gensim

PATH = 'GoogleNews-vectors-negative300.bin'

model = gensim.models.KeyedVectors.load_word2vec_format(PATH, binary=True)

print(model['United_States'])

出力は

[-3.61328125e-02 -4.83398438e-02  2.35351562e-01  1.74804688e-01
 -1.46484375e-01 -7.42187500e-02 -1.01562500e-01 -7.71484375e-02
  1.09375000e-01 -5.71289062e-02 -1.48437500e-01 -6.00585938e-02
(中略)
  1.69921875e-01 -2.80761719e-02  3.03649902e-03  9.32617188e-02
 -8.49609375e-02  1.57470703e-02  7.03125000e-02  1.62353516e-02
 -2.27050781e-02  3.51562500e-02  2.47070312e-01 -2.67333984e-02]

GoogleNews-vectors-negative300.binは非常に大きなファイルであるため、読み込みに時間がかかる。
また、modelgensim.models.KeyedVectors.load_word2vec_formatという非常に長い呼び方をしている。
但し一度modelに入れた後は、単語名だけでw2vを入手できて便利。

61

import gensim
import matplotlib.pyplot as plt
import numpy as np

def cos_sim(v1, v2):
    if v1 is None or v2 is None:
        print('Error. v1 or(and) v2 is None')
        return None
    if len(v1) != len(v2):
        print('Error, length must be equal')
        return None
    return np.dot(v1,v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))

PATH = 'GoogleNews-vectors-negative300.bin'

model = gensim.models.KeyedVectors.load_word2vec_format(PATH, binary=True)

United_States_vec = model['United_States']
US_vec = model['U.S.']

print(cos_sim(United_States_vec, US_vec))
print(model.similarity('U.S.','United_States')) #上記自分の実装と比べると微妙に違う...

出力は

0.7310775
0.73107743

コサイン類似度を実装してみたが、model.similarity()という便利なものの存在に後で気がつく。
そして出力が少し異なっている(浮動小数点演算の誤差だろうか?)。

62

import gensim
import numpy as np

PATH = 'GoogleNews-vectors-negative300.bin'

model = gensim.models.KeyedVectors.load_word2vec_format(PATH, binary=True)

US_like_word = model.wv.most_similar('United_States')[:10]
for i in range(10):
    print(US_like_word[i])

出力は

62.py:8: DeprecationWarning: Call to deprecated `wv` (Attribute will be removed in 4.0.0, use self instead).
  US_like_word = model.wv.most_similar('United_States')
('Unites_States', 0.7877248525619507)
('Untied_States', 0.7541370391845703)
('United_Sates', 0.74007248878479)
('U.S.', 0.7310774326324463)
('theUnited_States', 0.6404393911361694)
('America', 0.6178410053253174)
('UnitedStates', 0.6167312264442444)
('Europe', 0.6132988929748535)
('countries', 0.6044804453849792)

model.wv.most_similar(単語)でその単語に類似する単語とその類似度のタプルを返す。
オプションtopnのデフォルトの値が10であるため、書いてないが、
参考:gensim.models.Word2Vec.most_similar
によれば
Word2Vec.most_similar(positive=, negative=, topn=10, restrict_vocab=None, indexer=None)
であり、
model.wv.most_similar(positive=[似ていてほしい単語列]n negative=[似ていてほしくない単語列], topn=何位まで出すか, restrict_vocab=何単語目まで語彙を計算するか)
といった感じだろう。単語列は算術平均で加減算されるのか?
ちなみに、

62.py:8: DeprecationWarning: Call to deprecated `wv` (Attribute will be removed in 4.0.0, use self instead).
  US_like_word = model.wv.most_similar('United_States')

という出力は正しく書いているのにもかかわらずでている模様。邪魔。

63

import gensim
import numpy as np
from pprint import pprint

PATH = 'GoogleNews-vectors-negative300.bin'

model = gensim.models.KeyedVectors.load_word2vec_format(PATH, binary=True)

vec = model['Spain'] - model['Madrid'] + model['Athens']
word = model.wv.most_similar([vec],[],10)
word2 = model.wv.most_similar(positive=['Spain', 'Athens'],negative=['Madrid'],topn=10)
pprint(word)
pprint(word2)

出力は

63.py:11: DeprecationWarning: Call to deprecated `wv` (Attribute will be removed in 4.0.0, use self instead).
  word = model.wv.most_similar(vec)
63.py:12: DeprecationWarning: Call to deprecated `wv` (Attribute will be removed in 4.0.0, use self instead).
  word2 = model.wv.most_similar(positive=['Spain', 'Athens'],negative=['Madrid'],topn=10)
[('Athens', 0.7528455853462219),
 ('Greece', 0.6685472726821899),
 ('Aristeidis_Grigoriadis', 0.5495778322219849),
 ('Ioannis_Drymonakos', 0.5361456871032715),
 ('Greeks', 0.5351786613464355),
 ('Ioannis_Christou', 0.5330226421356201),
 ('Hrysopiyi_Devetzi', 0.5088489055633545),
 ('Iraklion', 0.5059264898300171),
 ('Greek', 0.5040615797042847),
 ('Athens_Greece', 0.5034109354019165)]
[('Greece', 0.6898481249809265),
 ('Aristeidis_Grigoriadis', 0.5606848001480103),
 ('Ioannis_Drymonakos', 0.5552908778190613),
 ('Greeks', 0.545068621635437),
 ('Ioannis_Christou', 0.5400862693786621),
 ('Hrysopiyi_Devetzi', 0.5248444676399231),
 ('Heraklio', 0.5207759737968445),
 ('Athens_Greece', 0.516880989074707),
 ('Lithuania', 0.5166866183280945),
 ('Iraklion', 0.5146791934967041)]

vec = model['Spain'] - model['Madrid'] + model['Athens']
とベクトルで計算した後にwv.most_similar(vec)で計算した場合と、
.wv.most_similar(positive=['Spain', 'Athens'],negative=['Madrid'],topn=10)
の計算が異なる。
単なる算術平均を行っていると思ったが、異なる結果を出した。
それぞれのベクトルを足したものと、算術平均では大きさの差はあれど方向は同じであり、cos類似度は同じであると思っていた。
なぜなのかは不明。題意に従うのは前者だと思う。

64

import gensim
import numpy as np

PATH_w2v = 'GoogleNews-vectors-negative300.bin'
model = gensim.models.KeyedVectors.load_word2vec_format(PATH_w2v, binary=True)

PATH_qw = 'questions-words.txt'
with open(PATH_qw, 'r') as f_qw:
    raw_qw = f_qw.read()
lines_qw = raw_qw.split('\n')
PATH_qw64 = '64_questions-words.txt'
with open(PATH_qw64, 'w') as w:
    for line in lines_qw:
        words_qw = line.split(' ') 
        if words_qw[0] == ':': # 凡例の場合
            continue
        query = model[words_qw[1]] - model[words_qw[0]] + model[words_qw[2]]
        query_ms = model.wv.most_similar([query], topn=1)
        query_out = words_qw + [query_ms[0][0]] + [str(query_ms[0][1])]
        query_out.append('\n')
        w.write(' '.join(query_out))

非常に長い時間がかかるため注意。
出力は

: capital-common-countries
Athens Greece Baghdad Iraq Baghdad 0.7489826679229736 
Athens Greece Bangkok Thailand Bangkok 0.7431141138076782 
Athens Greece Beijing China China 0.7186591625213623 
...
write writes vanish vanishes vanish 0.7304517030715942 
write writes walk walks walk 0.609993577003479 
write writes work works writes 0.5275965929031372 
Athens Greece Bagdad Iraq

のようなクエリ以外にも、

: capital-common-countries

のような見出しもあるため、words_qw[0]が「:」の場合その行はそのまま出力

65

from sklearn.metrics import accuracy_score

with open('64_questions-words.txt', 'r') as r:
    raw_qw64 = r.read()
lines_qw = raw_qw64.splitlines()
label_true = []
label_pred = []
for line in lines_qw:
    words_qw = line.split(' ') 
    if words_qw[0] == ':': #見出しの場合
        continue
    label_true.append(words_qw[3])
    label_pred.append(words_qw[4])

print(accuracy_score(label_true, label_pred))

出力は

0.2018522308636922

top1単語で当てなければいけない中で20%はまあまあ高いのではないか?
もしかしたら何か間違っているかも

66

import gensim
import scipy.stats

# 人間のデータ
with open('combined.csv', 'r') as r:
    raw_com = r.read()
lines_com = raw_com.splitlines()

# w2vのデータ
PATH_w2v = 'GoogleNews-vectors-negative300.bin'
model = gensim.models.KeyedVectors.load_word2vec_format(PATH_w2v, binary=True)

# 類似度判定
human_sim = []
w2vec_sim = []
for line in lines_com:
    w1, w2, sim = line.split(',')
    if w1 == 'Word 1':
        continue # 凡例なので処理しない
    sim = float(sim)
    human_sim.append(sim)
    w2vec_sim.append(model.wv.similarity(w1, w2))

print(scipy.stats.spearmanr(human_sim, w2vec_sim))

出力は

SpearmanrResult(correlation=0.7000166486272194, pvalue=2.86866666051422e-53)

scipy.stats.spearmanr(data1, data2)でスピアマン相関係数が計算できる。
人間が答えた類似度と、w2vが答えた類似度をそれぞれfloatで用意するだけで後はすべて任せている。

67

import gensim
import numpy as np
from sklearn.cluster import KMeans

# w2vのデータ
PATH_w2v = 'GoogleNews-vectors-negative300.bin'
w2v = gensim.models.KeyedVectors.load_word2vec_format(PATH_w2v, binary=True)

with open('questions-words.txt') as r:
    raw_qw = r.read()

# 国名一覧を取得
countries = set()
for line in raw_qw.splitlines():
    word_qw = line.split(' ')
    if word_qw[0] == ':': # 凡例の場合
        #国名を2番目に含まなくなるので、currency以降は除外。
        if word_qw[1] == 'currency':
            break
        continue
    countries.add(word_qw[1])
countries = list(countries)

# 国名とベクトルの組を生成
country_vec = w2v[countries]

# k-means
kmeans_model = KMeans(n_clusters=5, random_state=0)
kmeans_model.fit(country_vec)

predicted_list = list(kmeans_model.predict(w2v[countries]))

for i in range(5):
    print(i, ': ')
    ith_country = sorted([countries[num] for num in range(len(predicted_list)) if predicted_list[num] == i])
    print(' '.join(ith_country))

出力はしなくていいが、上記だと

0 : 
Albania Armenia Azerbaijan Belarus Bulgaria Croatia Cyprus Estonia Georgia Greece Hungary Kazakhstan Kyrgyzstan Latvia Lithuania Macedonia Moldova Montenegro Poland Romania Russia Serbia Slovakia Slovenia Tajikistan Turkey Turkmenistan Ukraine Uzbekistan
1 : 
Algeria Angola Botswana Burundi Eritrea Gabon Gambia Ghana Guinea Kenya Liberia Madagascar Malawi Mali Mauritania Mozambique Namibia Niger Nigeria Rwanda Senegal Sudan Tunisia Uganda Zambia Zimbabwe
2 : 
Australia Austria Belgium Canada Denmark England Finland France Germany Greenland Ireland Italy Japan Liechtenstein Malta Norway Portugal Spain Sweden Switzerland
3 : 
Bahamas Belize Chile Cuba Dominica Ecuador Fiji Guyana Honduras Jamaica Nicaragua Peru Philippines Samoa Suriname Taiwan Tuvalu Uruguay Venezuela
4 : 
Afghanistan Bahrain Bangladesh Bhutan China Egypt Indonesia Iran Iraq Jordan Laos Lebanon Libya Morocco Nepal Oman Pakistan Qatar Somalia Syria Thailand Vietnam

KMeansのrandom_state=0としてシードを与えたつもりだが常に同じ結果を出さなかった。未解決問題。

predicted_listの中身は

2
1
1

のように、クラスタの数字しか入っていない。

68

import gensim
import numpy as np
import matplotlib.pyplot as plt

from pprint import pprint
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster import hierarchy

# w2vのデータ
PATH_w2v = 'GoogleNews-vectors-negative300.bin'
w2v = gensim.models.KeyedVectors.load_word2vec_format(PATH_w2v, binary=True)

with open('questions-words.txt') as r:
    raw_qw = r.read()

# 国名一覧を取得
countries = set()
for line in raw_qw.splitlines():
    word_qw = line.split(' ')
    if word_qw[0] == ':': # 凡例の場合
        #国名を2番目に含まなくなるので、currency以降は除外。
        if word_qw[1] == 'currency':
            break
        continue
    countries.add(word_qw[1])
countries = list(countries)
# 国名とベクトルの組を生成
country_vec = w2v[countries]

# ward
ward_model = AgglomerativeClustering(n_clusters=5)
ward_model.fit(country_vec)

linked_array = hierarchy.ward(country_vec)

fig = plt.figure()
hierarchy.dendrogram(linked_array, labels=countries)
ax = plt.gca()
bounds = ax.get_xbound()
ax.plot(bounds, [7.25,7.25], '--', c='gray')
plt.ylabel('Cluster distance', fontsize=12)
plt.title('Dendrogram for Ward method', fontsize=14)
plt.show()
fig.savefig('68_dendrogram.pdf')

出力は
f:id:D_PLIUS:20200521171820j:plain

from scipy.cluster import hierarchy
linked_array = hierarchy.ward(country_vec)
hierarchy.dendrogram(linked_array, labels=countries)

によってこのデンドログラムが書ける。
灰色の点線部分がで着れば、5クラスタに分かれる。
見にくいが、黄色の部分にあるJapanは、Taiwanと Chitaが合わさったクラスタに最初に合流する。
位置的に近いので妥当(ちなみに上記データベースにKoreaはなぜかなかった)。

69

import gensim
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
from pprint import pprint

# w2vのデータ
PATH_w2v = 'GoogleNews-vectors-negative300.bin'
w2v = gensim.models.KeyedVectors.load_word2vec_format(PATH_w2v, binary=True)

PATH_qw = 'questions-words.txt'
with open(PATH_qw) as r:
    raw_qw = r.read()

# 国名一覧を取得
countries = set()
for line in raw_qw.splitlines():
    word_qw = line.split(' ')
    if word_qw[0] == ':': # 凡例の場合
        #国名を2番目に含まなくなるので、currency以降は除外。
        if word_qw[1] == 'currency':
            break
        continue
    countries.add(word_qw[1])
countries = list(countries)
# 国名とベクトルの組を生成
country_vec = w2v[countries]

tsne_model = TSNE(n_components=2)
country_vec_embedded = tsne_model.fit_transform(country_vec)
x_fig = [elem[0] for elem in country_vec_embedded]
y_fig = [elem[1] for elem in country_vec_embedded]

fig = plt.figure()
plt.scatter(x_fig, y_fig)
ax = plt.gca()

for i, country in enumerate(countries):
    ax.annotate(country, (x_fig[i], y_fig[i]))
plt.show()
fig.savefig('69_tsne.pdf')

出力は
f:id:D_PLIUS:20200521172614j:plain
tsne_model = TSNE(n_components=2)
で2次元のTSNEモデルを作り、
tsne_model.fit_transform(country_vec)
でfitさせると2次元の配列が返ってくるため、それぞれをx_figとy_figに代入して描画。