Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

言語処理100本ノック2020(Rev1)をやってみました。

Posted at

第7章: 単語ベクトル

この章もGoogle Colaboratory上で行いました。

単語の意味を実ベクトルで表現する単語ベクトル(単語埋め込み)に関して,以下の処理を行うプログラムを作成せよ.

60. 単語ベクトルの読み込みと表示

Google Newsデータセット(約1,000億単語)での学習済み単語ベクトル(300万単語・フレーズ,300次元)をダウンロードし,”United States”の単語ベクトルを表示せよ.ただし,”United States”は内部的には”United_States”と表現されていることに注意せよ.

本当はwgetで直接学習済み単語ベクトルのURLを指定したかったのですが、うまくいかなかったので、直接ダウンロードし、自分のGoogleドライブにアップロードしました。

#Googleドライブをマウント
from google.colab import drive
drive.mount('/content/drive')

なお、このファイルから単語ベクトルとして読み取るには、
gensimのKeyedVectors.load_word2vec_format()を使用します。

from gensim.models import KeyedVectors

model = KeyedVectors.load_word2vec_format('/content/drive/My Drive/NL100/data/GoogleNews-vectors-negative300.bin.gz', binary=True)
model['United_States']

<出力>

array([-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.74804688e-01, -7.71484375e-02,  2.58789062e-02, -7.66601562e-02,
       -3.80859375e-02,  1.35742188e-01,  3.75976562e-02, -4.19921875e-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],
      dtype=float32)

61. 単語の類似度

“United States”と”U.S.”のコサイン類似度を計算せよ.

コサイン類似度とは、単語ベクトル同士が内積空間でどれだけ類似しているかの尺度です。
1に近いほど類似しています。
KeyedVectorsではsimilarity()を使用して、2つの単語間のコサイン類似度を求めることができます。

model.similarity("United_States", "U.S.")

<出力>

/usr/local/lib/python3.6/dist-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
  if np.issubdtype(vec.dtype, np.int):
0.73107743

類似度は0.73と出ました。
"United_States"と"U.S."は比較的強い関連があることがわかります。

62. 類似度の高い単語10件

“United States”とコサイン類似度が高い10語と,その類似度を出力せよ.

KeyedVectorsではmost_similar()を使用して、指定した単語に対してコサイン類似度の高い単語を降順に表示できます。

model.most_similar("United_States")

<出力>

/usr/local/lib/python3.6/dist-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
  if np.issubdtype(vec.dtype, np.int):
[('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),
 ('Canada', 0.6019070148468018)]

直感的にみて、「United States」から連想される単語が出力され、
妥当な結果だといえます。

63. 加法構成性によるアナロジー

“Spain”の単語ベクトルから”Madrid”のベクトルを引き,”Athens”のベクトルを足したベクトルを計算し,そのベクトルと類似度の高い10語とその類似度を出力せよ.

most_similarには2つのパラメータ(positive, negative)があります。
positiveには、足し合わせたい単語、negativeには、差し引きたい単語をリストで指定します。

model.most_similar(positive=["Spain","Athens"], negative=["Madrid"])

<出力>

/usr/local/lib/python3.6/dist-packages/gensim/matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int64 == np.dtype(int).type`.
  if np.issubdtype(vec.dtype, np.int):
[('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)]

スペインからマドリードを引いてアテネを足したら、ギリシャやその周辺国の国名、ギリシャのスポーツ選手の名前が出てきました。
まあ、分からなくもない結果でしょうか。

64. アナロジーデータでの実験

単語アナロジーの評価データをダウンロードし,vec(2列目の単語) - vec(1列目の単語) + vec(3列目の単語)を計算し,そのベクトルと類似度が最も高い単語と,その類似度を求めよ.求めた単語と類似度は,各事例の末尾に追記せよ.

まずはデータをダウンロード

%cd "/content/drive/My Drive/NL100/data/"
!wget "http://download.tensorflow.org/data/questions-words.txt"
%cat "questions-words.txt" | wc -l
%cat "questions-words.txt" | head -n 3

<出力>

・・・(省略)・・・
19558
: capital-common-countries
Athens Greece Baghdad Iraq
Athens Greece Bangkok Thailand

このテキストファイルは、類推に使用する4つの単語のセットで構成されています。
ここで、ファイルのフォーマットに気をつける必要があります。
単純なスペース区切りのデータの羅列かと思いきや、
カテゴリ名の行があり、(上記出力の": capital-common-countries")
カテゴリごとにデータが区分けされています。

この辺、@yamaruさんのサイトを参考にさせていただきました。

このデータには14個のカテゴリがあります。

  1. capital-common-countries
  2. capital-world
  3. currency
  4. city-in-state
  5. family
  6. gram1-adjective-to-adverb
  7. gram2-opposite
  8. gram3-comparative
  9. gram4-superlative
  10. gram5-present-participle
  11. gram6-nationality-adjective
  12. gram7-past-tense
  13. gram8-plural
  14. gram9-plural-verbs

問題64では特にカテゴリは関係ないのですが、
問題65で、「意味的アナロジー」「文法的アナロジー」というのが出てきます。
この「意味的」データと「文法的」データのどちらかを識別するのにカテゴリを使用します。
そのため、問題には明記されていませんが、出力するデータにカテゴリの情報を持たせる必要があります。

import numpy as np
import pandas as pd

# データをDataFrameに読み込む。1行目はカテゴリ行なので読み飛ばす。
df = pd.read_csv("questions-words.txt", header=None, sep=" ",error_bad_lines =False, warn_bad_lines=True, skiprows=1)

category = ""
df["CATEGORY"] = "" # カテゴリ列追加
df["POP_WORD"] = "" # 類似度が一番高い単語列追加
df["RATE"] = ""     # コサイン類似度列追加
categoryIdx = df[df.iloc[:,0]==":"].index # カテゴリ行のインデックスリスト

df.loc[0:categoryIdx[0], "CATEGORY"] = 0 # 1つ目のカテゴリに属する行のカテゴリは0をセット
df.loc[categoryIdx[12]:,"CATEGORY"] = 13 # 14番目のカテゴリに属する行のカテゴリは13をセット
for i in np.arange(1,13): # 2~13番目のカテゴリに属する行のカテゴリをセット
  df.loc[categoryIdx[i-1]:categoryIdx[i],"CATEGORY"] = i

df=df.drop(categoryIdx, axis=0) # カテゴリ行は不要なので削除
df = df.reset_index(drop=True) # インデックスを振りなおす

for i in range(len(df)): # 1行ごとに類似度を計算
  simvec = model.most_similar(positive=[df.iloc[i,1],df.iloc[i,2]], negative=[df.iloc[i,0]])
  df.loc[i,"POP_WORD"] = simvec[0][0]
  df.loc[i,"RATE"] = str(simvec[0][1])
  if i%100==0:
    print("-------%d-------" % i) # 実行にかなり時間がかかるため、進捗表示

# テキストファイルに書き出し
df.to_csv("questions-words2.txt",sep=" ", header=False, index=False)

%cat "questions-words2.txt" | wc -l # 行数を表示
import pandas as pd

df = pd.read_csv("questions-words2.txt",sep=" ", header=None)
df.head()

<出力>
image.png

65. アナロジータスクでの正解率

64の実行結果を用い,意味的アナロジー(semantic analogy)と文法的アナロジー(syntactic analogy)の正解率を測定せよ.

先ほど64に書きましたが、意味的アナロジー/文法的アナロジーの違いは、単語の類推のタイプの違いです。
データにカテゴリがありましたが、以下の5つは、単語の意味から類推したデータです。(例:国名→首都)

  1. capital-common-countries
  2. capital-world
  3. currency
  4. city-in-state
  5. family

以下の7つは、文法的に類推したデータです。(例:原型→複数形)
6. gram1-adjective-to-adverb
7. gram2-opposite
8. gram3-comparative
9. gram4-superlative
10. gram5-present-participle
11. gram6-nationality-adjective
12. gram7-past-tense
13. gram8-plural
14. gram9-plural-verbs

import pandas as pd

df = pd.read_csv("questions-words2.txt",sep=" ", header=None)
df.head()

df1 = df[df[4] < 5]   # カテゴリ0~4までのデータ(意味的アナロ ジー)
df2 = df[df[4] >= 5]  # カテゴリ5~13までのデータ(文法的アナロジー)

# 正解ラベルと類似度の高い単語を比較し、正解率を出す
ACC1 = len(df1[df1[3]==df1[5]]) / len(df1)
ACC2 = len(df2[df2[3]==df2[5]]) / len(df2)

print("accuracy of semantic analogy: %.3f" % ACC1)
print("accuracy of syntactic analogy : %.3f" % ACC2)

<出力>

accuracy of semantic analogy: 0.731
accuracy of syntactic analogy : 0.740

66. WordSimilarity-353での評価

The WordSimilarity-353 Test Collectionの評価データをダウンロードし,単語ベクトルにより計算される類似度のランキングと,人間の類似度判定のランキングの間のスピアマン相関係数を計算せよ.

この評価データは、アンケートを行い、2つの単語の関係性(類似度)を、0~10の範囲で採点してもらった集計結果です。
ここでは、人間が判断した類似度と、Word2Vecで算出した類似度の相関を調べます。

スピアマン相関係数とは、wikipediaによると、「統計学において順位データから求められる相関の指標」だそうです。

スピアマン相関係数:$\gamma$ $-1 \leqq \gamma \leqq 1$

0のときは相関なし
1のときは正の相関が強い
-1のときは負の相関が強い

評価データはzipで複数ファイルが圧縮されているため、直接ダウンロードして解凍します。

!wget "http://www.gabrilovich.com/resources/data/wordsim353/wordsim353.zip"
!unzip "wordsim353.zip"

解凍したファイルの中で、使用するのは、combined.csvです。

!head combined.csv

<出力>

Word 1,Word 2,Human (mean)
love,sex,6.77
tiger,cat,7.35
tiger,tiger,10.00
book,paper,7.46
computer,keyboard,7.62
computer,internet,7.58
plane,car,5.77
train,car,6.31
telephone,communication,7.50

実装ですが、スピアマン相関係数は、scipy.statsのspearmanrを使用します。

from scipy.stats import spearmanr
import numpy as np
import pandas as pd

df = pd.read_csv("combined.csv")

df["W2V"] = 0
for i in range(len(df)):  
  # Word2Vecを用いて2単語の類似度を求める。
  df.loc[i,"W2V"] = model.similarity(df.loc[i, "Word 1"], df.loc[i, "Word 2"])

print(df.head())

a, b = spearmanr(df.loc[:,"Human (mean)"], df.loc[:, "W2V"])
print("スピアマン相関係数:%3f" % a)

<出力>

     Word 1    Word 2  Human (mean)       W2V
0      love       sex          6.77  0.263938
1     tiger       cat          7.35  0.517296
2     tiger     tiger         10.00  1.000000
3      book     paper          7.46  0.363463
4  computer  keyboard          7.62  0.396392
スピアマン相関係数:0.700017

67. k-meansクラスタリング

国名に関する単語ベクトルを抽出し,k-meansクラスタリングをクラスタ数k=5として実行せよ.

国名一覧は、外務省のサイトに
国連加盟国一覧があったので、これをもとにアルファベット国名のテキストファイルを作成しました。(nations.txt)

K-meansはscikit-learnのKMeansを使用します。

from sklearn.cluster import KMeans

vecs = []
nations=[]
with open("nations.txt", 'r') as f:
  for line in f:
    try:
      vec = model.word_vec(line.strip())
      nations.append(line.strip()) # 国名リスト
      vecs.append(vec)             # 単語ベクトルリスト
      #continue
    except KeyError:
      print("error : {}".format(line)) # modelに存在しない国名はエラー
# 国名の単語ベクトルを5つにクラスタリング
pred = KMeans(n_clusters=5).fit_predict(vecs) 

for j in range(5):
  print("****** GROUP %d ******" % j)
  for i, name in enumerate(nations):
    #print("{} : {}".format(name, pred[i]))
    if pred[i]==j: # predには、国名リストのサイズ分の分類カテゴリが入っている。
      print(str(pred[i] )+ " " +name)

<出力>

error : American_Samoa

error : Antigua_and_Barbuda

error : Central_Africa

error : Chinese_Taipei

error : Commonwealth_of_Dominica

error : Congo,_Republic_of

error : Congo,_Democratic_Republic_of

error : Côte_d'Ivoire

error : Democratic_People's_Republic_of_Korea

error : Democratic_Republic_of_the_Congo

error : Eswatini

error : Korea,_Democratic_People's_Republic_of

error : Korea,_Republic_of

error : New_Caledonia

error : North_Macedonia

error : Papua_New_Guinea

error : Republic_of_China

error : Republic_of_Congo

error : Republic_of_Korea

error : Sao_Tome_and_Principe

error : South_Sudan

error : St.Christopher_and_Nevis

error : St.Kitts_and_Nevis

error : St.Vincent_and_the_Grenadines

error : Timor-Leste

error : Trinidad_and_Tobago

error : United_States_of_America

error : Virgin_Is.

error : West_Bank

****** GROUP 0 ******
0 Algeria
0 Angola
0 Benin
0 Botswana
0 Burkina_Faso
0 Burundi
0 Cameroon
0 Cape_Verde
0 Comoros
0 Djibouti
0 Equatorial_Guinea
0 Eritrea
0 Ethiopia
0 Gabon
0 Gambia
0 Ghana
0 Guinea
0 Guinea_Bissau
0 Ivory_Coast
0 Kenya
0 Lesotho
0 Liberia
0 Madagascar
0 Malawi
0 Mali
0 Mauritania
0 Mozambique
0 Namibia
0 Niger
0 Nigeria
0 Rwanda
0 Senegal
0 Sierra_Leone
0 South_Africa
0 Sudan
0 Swaziland
0 Tanzania
0 Togo
0 Tunisia
0 Uganda
0 Zaire
0 Zambia
0 Zimbabwe
****** GROUP 1 ******
1 Afghanistan
1 Bahrain
1 Bangladesh
1 Bhutan
1 Burma
1 Cambodia
1 Chad
1 China
1 Egypt
1 Gaza_Strip
1 Holy_See
1 India
1 Iran
1 Iraq
1 Israel
1 Jordan
1 Kosovo
1 Kuwait
1 Kyrgyz
1 Laos
1 Lebanon
1 Libya
1 Mongolia
1 Morocco
1 Myanmar
1 Nepal
1 North_Korea
1 Oman
1 Pakistan
1 Palestine
1 Qatar
1 Saudi_Arabia
1 Somalia
1 South_Korea
1 Syria
1 Tajikistan
1 Turkey
1 Turkmenistan
1 United_Arab_Emirates
1 Uzbekistan
1 Vatican
1 Vietnam
1 Western_Sahara
1 Yemen
****** GROUP 2 ******
2 Argentina
2 Aruba
2 Bahamas
2 Barbados
2 Belize
2 Bermuda
2 Bolivia
2 Brazil
2 Canada
2 Canary_Islands
2 Chile
2 Colombia
2 Costa_Rica
2 Cuba
2 Curacao
2 Dominica
2 Dominican_Republic
2 Ecuador
2 El_Salvador
2 French_Guiana
2 Gibraltar
2 Guadeloupe
2 Guatemala
2 Guiana
2 Guyana
2 Grenada
2 Haiti
2 Honduras
2 Jamaica
2 Martinique
2 Mexico
2 Netherlands_Antilles
2 Nicaragua
2 Panama
2 Paraguay
2 Peru
2 Puerto_Rico
2 Sint_Maarten
2 Spain
2 Suriname
2 Uruguay
2 Venezuela
****** GROUP 3 ******
3 Antarctica
3 Australia
3 Brunei
3 Cook_Islands
3 East_Timor
3 Falkland_Islands
3 Fiji
3 French_Polynesia
3 Greenland
3 Guam
3 Holand
3 Hong_Kong
3 Indonesia
3 Japan
3 Kiribati
3 Macau
3 Malaysia
3 Maldives
3 Marshall_Islands
3 Mauritius
3 Micronesia
3 Nauru
3 New_Zealand
3 Niue
3 Northern_Marianas
3 Palau
3 Philippines
3 Polynesia
3 Reunion
3 Saint_Lucia
3 Samoa
3 Seychelles
3 Singapore
3 Solomon_Islands
3 Sri_Lanka
3 Taiwan
3 Thailand
3 Tonga
3 Tuvalu
3 United_Kingdom
3 Vanuatu
****** GROUP 4 ******
4 Albania
4 Andorra
4 Armenia
4 Austria
4 Azerbaijan
4 Belarus
4 Belgium
4 Bosnia_Herzegovina
4 Bulgaria
4 Croatia
4 Cyprus
4 Czech_Republic
4 Denmark
4 England
4 Estonia
4 Faroe_Islands
4 Finland
4 France
4 Georgia
4 Germany
4 Great_Britain
4 Greece
4 Hellenic_Republic
4 Hungary
4 Iceland
4 Ireland
4 Italy
4 Kazakhstan
4 Latvia
4 Liechtenstein
4 Lithuania
4 Luxembourg
4 Macedonia
4 Malta
4 Moldova
4 Monaco
4 Montenegro
4 Netherlands
4 Norway
4 Poland
4 Portugal
4 Romania
4 Russia
4 San_Marino
4 Serbia
4 Slovakia
4 Slovenia
4 Sweden
4 Switzerland
4 Ukraine
4 Yugoslavia

結果を見ると、大体以下のように分かれている。
グループ0:アフリカ
グループ1:アジア
グループ2:アメリカ
グループ3:オセアニア
グループ4:ヨーロッパ

68. Ward法によるクラスタリング

国名に関する単語ベクトルに対し,Ward法による階層型クラスタリングを実行せよ.さらに,クラスタリング結果をデンドログラムとして可視化せよ.

Word法に関しては、こちらのサイトに分かりやすく書かれています。
ウォード法によるクラスタリングのやり方

クラスタリングのアルゴリズムの一つで、はじめに各点がそれぞれ別のクラスタだとみなし、最寄りの2クラスタとマージしていきます。最後、希望するクラスタ数になるまで処理を続ける方法です。
この処理には、scipy.cluster.hierarchyのlinkageを使用します。

また、デンドログラムとは樹形図のことで、同じくscipy.cluster.hierarchyにdendrogramが用意されているので、こちらを使用します。

import matplotlib.pyplot as plt
%matplotlib inline

from scipy.cluster.hierarchy import linkage, dendrogram
result1 = linkage(vecs, method='ward', metric = 'euclidean')
dendrogram(result1)
plt.title("Dedrogram")
plt.ylabel("Threshold")
plt.show()

<出力>

image.png

69. t-SNEによる可視化

ベクトル空間上の国名に関する単語ベクトルをt-SNEで可視化せよ.

t-SNEとは、高次元データの次元を圧縮するアルゴリズムで、データの可視化に使用されます。

この実装には、sklearn.manifoldのTSNEを使用しました。

import pickle
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
import pandas as pd

# 問題67の結果をDataFrameに格納
df = pd.DataFrame()
df["nations"]=np.array(nations)
df["group"]=np.array(pred)


#t-SNEで次元削減
tsne = TSNE(n_components=2, random_state = 0, perplexity = 30, n_iter = 1000)
# 問題67の国名単語ベクトルリストを次元削減
vecs_embedded = tsne.fit_transform(vecs)

# 国名・グループのDataFrameと2次元に次元削減したベクトル座標値を結合
ddf = pd.concat([df, pd.DataFrame(vecs_embedded, columns = ['col1', 'col2'])], axis = 1)

nation_list = ddf["group"].unique() # 国のグループリスト

colors =  ["r", "g", "b", "c", "m", "y", "k", "orange","pink"]
plt.figure(figsize = (30, 30))

# グループごとに抽出し、散布図にプロットする。
for i , v in enumerate(nation_list):
    tmp_df = ddf[ddf["group"] == v]
    plt.scatter(tmp_df['col1'],  
                tmp_df['col2'],
                label = v,
                color = colors[i%9])

plt.legend(bbox_to_anchor=(0, -0.1), loc='upper left', borderaxespad=0,fontsize = 20)

<出力>

image.png

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?