論文メモ: GloVe: Global Vectors for Word Representation

前々回の投稿でGloVeで単語ベクトルを計算しましたが、今回の投稿ではその提案論文を整理したいと思います。

nlp.stanford.edu

ohke.hateblo.jp

GloVe: Global Vectors for Word Representation

@inproceedings{pennington2014glove,
  author = {Jeffrey Pennington and Richard Socher and Christopher D. Manning},
  booktitle = {Empirical Methods in Natural Language Processing (EMNLP)},
  title = {GloVe: Global Vectors for Word Representation},
  year = {2014},
  pages = {1532--1543},
  url = {http://www.aclweb.org/anthology/D14-1162},
}

Abstract

グローバルなMatrix Factorizationとローカルなコンテキストウィンドウを組み合わせることでいいとこ取りを目指してます。

1 Introduction

これまで単語のベクトル表現を獲得する方法は大きく2つで、それぞれ固有の問題を持っています。

  • 文書全体の単語の共起行列からMatrix Factorizationすることで獲得する、カウントベースな方法
    • ここで使う行列には、単語-文書 (ex.LSA) と 単語-単語 (ex. Hyperspace Analogue to Language: HLA) の2パターンがある
    • "the"や"a"などの頻出単語の影響を除去するために、エントロピや相関係数で正規化する
  • 単語の前後 (ウィンドウ) の出現単語を予測することで獲得する、予測ベースな方法 (ex. skip-gram word2vec)

グローバルにlogbilinearを適用することでいいとこ取りができないだろうか?
グローバルな単語-単語の共起を、重み付き最小2乗で学習するモデルを提案する。

3 The GloVe Model

Global Vectorsで新しいベクトル表現を作ります。

  • 単語-単語の共起カウントを行列  X とする
    •  X_{ij} は単語iの文脈で何回単語jが現れたのかを表す (非対称行列)
    •  X_{i}=\sum_{k}X_{ik} で、単語iの文脈で現れた総単語数
  •  P_{ij}=P(j|i)=X_{ij}/X_{i} は、単語iの文脈で単語j現れる確率を表す

i="ice", j="stream"で例示しながら、提案モデルの原理となる共起確率の比について説明していきます。

  • 文脈に現れる単語k="solid" なら P(k|ice) > P(k|steam) となる → P(k|ice)/P(k|steam) >> 1.0
    • つまり、"solid"が現れやすいのは、"steam"よりも"ice"の文脈
    • 逆に k="gas" なら P(k|ice) < P(k|steam) → P(k|ice)/P(k|steam) << 1.0
    • もしkが"water"や"fashion"のような一般的な言葉であれば、P(k|ice)とP(k|steam)の差は小さくなる → P(k|ice)/P(k|steam) ≒1.0
  • こうみると、P(k|ice)/P(k|steam) が1から遠いほど関連が強く、1に近いほど関連が弱い、という関係が表されている
    • 2単語の共起確率そのものではなく、別のある単語 (k) を介したときの共起確率の比が重要ではないか

この2つの単語 (i, j) および文脈単語 (k) を使って定式化を行う。

  •  w \in \mathbb{R}^{d} は、単語iと単語jのベクトル
  •  \tilde{w} \in \mathbb{R}^{d} は、文脈単語k (複数) のベクトル (4.2で議論する?)


F(w_{i},w_{j},\tilde{w_{k}})=\frac{P_{ik}}{P_{jk}}

上の関数Fをモデル化していきます。

まずはパラメータを単純にします。
単語ベクトルは線形構造なのでベクトル間の差として表現できます。加えて、転置・内積によって左辺のパラメータを1つにまとめることで、シンプルになります。


F((w_{i}-w_{j})^{T}\tilde{w_{k}})=\frac{P_{ik}}{P_{jk}}

Fについて  (\mathbb{R}, +)  (\mathbb{R_{>0}}, \times) が準同型 (Wikipedia) を仮定すると、以下のように変形できます。


F((w_{i}-w_{j})^{T}\tilde{w_{k}})=\frac{F(w_{i}^{T}\tilde{w_{k}})}{F(w_{j}^{T}\tilde{w_{k}})}
\Leftrightarrow F(w_{i}^{T}\tilde{w_{k}})=P_{ik}=\frac{X_{ik}}{X_{i}}

F=expとおくと以下の形になります。


w_{i}^{T}\tilde{w_{k}}=log(P_{ik})=log(X_{ik})-log(X_{i})

で、最後に  log(X_{i})  w_{i} のバイアス  b_{i}  \tilde{w_{k}} のバイアス  \tilde{b_{k}} にします。

  • 共起行列の対数を因数分解する発想はLSAに近い


w_{i}^{T}\tilde{w_{k}}+b_{i}+\tilde{b_{k}}=log(X_{ik})

上で得られた式を、最小二乗法で単語ベクトルを求めるための損失関数として定義すれば、学習タスクに落とし込めます。

  • Vはボキャブラリサイズ (単語の種類数)


J=\sum_{i,j=1}^V f(X_{ij})(w_i^T\tilde{w_j} + b_i + \tilde{b_j} - logX_{ij})^2

関数  f は重みをつけるためのもので、コーパス全体で頻出するワード (助詞や指示語など) の重みを低くします。

  •  x_{max}  \alpha をハイパーパラメータとして選択する必要がある
    •  \alpha は、1よりも3/4の方が性能は良かった

f:id:ohke:20181101230625p:plain

4 Experiments

単語類推タスク

"a is to b as c is to ?"を類推するタスクです。word2vecのときと同じですね。
タスクは意味的な類推 (例えば"Athens is to Greece as Berlin is to
?"で、表中ではSem.) と構文的な類推 (例えば"dance is to dancing as fly is to __?"で、表中ではSyn.) の2種類があります。
タスクでは  w_{b}-w_{a}+w_{c} のベクトルと、最もコサイン類似度が近い単語ベクトルを計算することによって導出されます。

GloVeと他のモデルを比較した表が以下です (Table 2抜粋) 。いずれもGloVeが最も高い精度をマークしています。

  • word2vec (SG) もやっぱり強くて、従来の他の方法よりもかなり良い精度を示している
  • GloVeなら420億単語の巨大なコーパスでも学習でき、かつ、相応の精度向上が実現できている
    • 単純なSVD-L (対数のSVD) ではトークンサイズが増えると性能劣化が見られるため (56.6%→38.4%) 、GloVeで追加した重み関数fが効いていると思われる

f:id:ohke:20181102161844p:plain

その他、類似単語や名前エンティティ認識のタスクにおいても、他のモデルよりも概ね一貫して高い精度をマークしました。

ベクトル次元数とコンテキストサイズ

ベクトル次元数とコンテキストサイズを変更した場合の、単語類推タスク (トークン数60億) の精度を下表 (Figure 2抜粋) を示します。

  • ベクトルの次元数は200くらいで頭打ちになる (下表a)
  • 構文類推サブタスク (Syntactic) では、単語の左側のみを文脈単語とするAsymmetricな方法の方が精度は高かった
    • またウィンドウサイズを広げすぎると、精度が落ちるという傾向も見られる
    • 構文は単語の順序と強く依存している、という直感と一致する結果と言える
  • 意味類推サブタスク (Semantic) では、ウィンドウサイズを広げれば、精度が上がり続ける傾向が見られる
    • 意味的に関連する単語は、前後に広く分散しているため

f:id:ohke:20181102180325p:plain

コーパスのサイズ

サイズ (トークン数) が異なる4つのコーパスで、単語類推タスクを行ったときの結果が下表です (Figure 3抜粋) 。

  • コーパスサイズが大きくなれば、構文類推サブタスクの精度はコーパスが大きくなるほど良くなり続ける傾向
  • 意味類推サブタスクの精度は、コーパスのサイズと単純に比例していない (Wiki2014よりもGigaword5の方が精度は低い)
    • 意味類推サブタスクは、先の例のように地域 (市や国) を使ったテストが多く、Wikipediaの方がこのテストセットには適していた
    • Gigawordは更新が止まったコーパスなので、古い・誤ったデータによって結果が悪くなった

f:id:ohke:20181102182317p:plain

学習速度

学習も速く、類推タスクにおいて、同じ学習時間でCBOW・Skip-Gramよりも高い精度をマークしてます (Figure 4抜粋) 。

  • CBOWはnegative sample数が10を超えると精度を落としていく
    • negative sampleで近似に使っている分布が、実際と離れている可能性がある

f:id:ohke:20181102183827p:plain

まとめ

GloVeを実際に利用するにあたって、検討しないといけなさそうな点を最後にまとめておきます。

  • コーパスが精度に大きな影響を与えるので、自分の適用しようとしているタスクに適しているかどうかは確かめる必要がある
    • こういう時にどうやって適切かどうかを可視化すべきなのか?がわかっていない
  • 意味的な関連性が重要なら、ウィンドウサイズは大きめに設定する (順序は気にしなくて良い)
  • ベクトルサイズは学習時間とのトレードオフだが、長めに設定する

論文メモ: Distributed Representations of Words and Phrases and their Compositionality

前回の投稿で紹介したGloVeの論文を読もうと思ったのですが、先発のword2vecの論文をまだ読んでなかったので、先にそっちを読んだメモです。

なお、gensimのword2vecの実装を使った例を以前投稿してます。

ohke.hateblo.jp

Distributed Representations of Words and Phrases and their Compositionality

word2vecを提案したThomas Mikolovの論文はいくつかありますが、肝となるSkip-gramやNegative Samplingの説明が一番詳細そうだったので、今回はDistributed Representations of Words and Phrases and their Compositionality (arXiv) を読みます。

@article{DBLP:journals/corr/MikolovSCCD13,
  author    = {Tomas Mikolov and
               Ilya Sutskever and
               Kai Chen and
               Greg Corrado and
               Jeffrey Dean},
  title     = {Distributed Representations of Words and Phrases and their Compositionality},
  journal   = {CoRR},
  volume    = {abs/1310.4546},
  year      = {2013},
  url       = {http://arxiv.org/abs/1310.4546},
  archivePrefix = {arXiv},
  eprint    = {1310.4546},
  timestamp = {Mon, 13 Aug 2018 16:47:09 +0200},
  biburl    = {https://dblp.org/rec/bib/journals/corr/MikolovSCCD13},
  bibsource = {dblp computer science bibliography, https://dblp.org}
}

Abstract

単語の分散表現の精度と学習速度の両方を改善するNegative samplingについて提案します。

1 Introduction

大量のテキストから高速に単語ベクトルを得られるSkip-gramを、Efficient estimation of word representations in vector space (arXiv) で提案しました。

Skip-gramでは、単語の周辺単語を予測するというタスクのもと、ニューラルネットワーク (NN) を学習させることで単語ベクトルを獲得します (Figure 1抜粋) 。

  • 巨大な行列積をなくすことで、100,000,000,000単語の文書からでも、1日で単語ベクトル計算できます

f:id:ohke:20181027103154p:plain

本稿ではSkip-gramモデルを拡張します。

  • 頻出単語をサブサンプリングすることで、2〜10倍の高速化し、また、低頻度の単語のベクトル表現の精度を改善
  • 階層ソフトマックスに代わって、Noise Constrastive Estimationの簡易版を使うことで、高速化、および、頻出単語のベクトル表現の精度を改善

2 The Skip-gram Model

Skip-gramモデルは、文または文書内の周辺の単語を予測することで、単語ベクトルを得ます。

文内で  w_1,w_2,...,w_T の順番で単語が現れるとき、Skip-gramは以下の式 (対数確率の項) を最大化するベクトルを学習で見つけようとします。

  •  p(w_{t+j}|w_{t})  w_{t} の周辺t-c番目からt+c番目内に単語が現れる確率を表します
  • cを大きくすることで、計算時間と引き換えに精度は高くなる


\frac{1}{T}\sum_{t=1}^{T}\sum_{-c \leq j \leq c, j \neq 0} log p(w_{t+j}|w_{t})

 p(w_{t+j}|w_{t}) は以下のソフトマックスによって計算しています。

  •  v_{w}  v_{w}^{\prime} はそれぞれ単語  w の入力・出力のベクトル表現 (W次元)
    • NNの隠れ層のノードと出力層のノードに相当します
    • 入力ベクトル  v_{w} が今回必要な単語ベクトル
    • 出力ベクトル  v_{w}^{\prime} は、周辺単語の出現確率
  • Wはボキャブラリ数 (=単語の種類数) で、100,000〜10,000,000くらい


p(w_{O}|w_{I}) = \frac{exp(v_{w_{O}}^{\prime T} v_{w_{I}})}{\sum_{w=1}^{W}exp(v_{w}^{\prime T}v_{w_{I}})}

2.1 Hierarchical Softmax

現実的な計算量にするため、全結合ソフトマックス (入力層のノードが全ての出力層のノードに結びつく) の近似である階層ソフトマックスを使います。

  • 全結合ソフトマックスでは、上の式の分母の計算量が大きすぎるためです

階層ソフトマックスでは、二分木で表現され、出力層 (W個) はその葉に対応します。これにより、Wではなく  log_{2}(W) ノードの計算のみで済みます。

加えて、2分ハフマン木によって頻出単語には短いコードを割り当てて近似することで、さらに学習を高速化しました。

2.2 Negative Sampling

階層ソフトマックスに代わって、Noise Constrastive Estimation (NCE, ソフトマックスを近似する手法) を単純化したNegative sampling (NEG) を定義します。

 P_{n}(w) の雑音分布からk個サンプリングして、そのサンプルのみで計算することでさらに粗く近似します。


log \sigma(v_{w_{O}}^{\prime T}v_{w_{I}})+\sum_{i=1}^{k}\E_{w_{i} \sim P_{n}(w)} [log \sigma(-v_{w_{I}}^{\prime T}v_{w_{I}}) ]

kのサイズは、小さなデータセットであれば5〜20、大きなデータセットであれば更に小さく2〜5で、実用上十分です。元々ボキャブラリ全て (100,000〜10,000,000) だった計算が、5〜20程度に収まるので、計算量は大幅に削減できます。

  • 雑音分布  P_{n}(w) はユニグラム分布 (単語の出現頻度分布) を3/4乗で正規化したものが、ユニグラム分布そのものや一様分布と比較して良かった

2.3 Subsampling of Frequent Words

頻出単語 ("in", "the", "a"など) は低頻度の単語と比較すると大きな情報量を持っていないので、頻出単語の学習頻度を以下式で下げます。

  •  f(w_{i}) が単語の出現回数
  • tは閾値で  10^{-5} が良さそう


P(w_{i})=1-\sqrt{\frac{t}{f(w_{i})}}

3 Empirical Results

階層ソフトマックス (HS) 、NCS、NEG (kは5と15) で比較実験を行います。

  • 2パターンの類推タスクの精度を比較
    • 意味的な類推タスク
      • ex.1 "Germany":"Berlin" :: "France" : ? (正解は"Paris")
    • 統語論的な類推タスク
      • ex.2 "quick":"quickly" :: "slow" : ? (正解は"slowly")
    • いずれもベクトル加算で計算し、コサイン距離で最も近い単語で予測する

結果 (Table 1抜粋) を見ると、精度はNEGが最も良く、kを少なくしたりサブサンプリングを行うことで学習速度が向上することが確認できます。

  • kが大きいほど精度が良くなるが、学習に時間はかかるというトレードオフが伺える
  • NEG-5の場合、サブサンプリングによって精度が改善されているのが面白い

f:id:ohke:20181027170209p:plain

4 Learning Phrases

複数の単語からなるフレーズに拡張して、3と同じタスクを行った。
(3と類似のため、省略)

5 Additive Compositionality

Skip-gramで得られた単語ベクトルは単純な線形構造なので、ベクトル演算だけで意味の類推が可能となります (Table 5抜粋) 。

f:id:ohke:20181027215832p:plain

それぞれの単語ベクトルは周辺単語が現れる確率 (対数和) を最大にするように学習されているので、単語ベクトル同士の和はAND検索のように2つの単語と一緒に現れた単語のベクトルに近くなります。"Volga River"は、"Russian"や"river"といった単語と一緒に現れるので、"Russian"と"river"の和は"Volga River"の単語ベクトルと類似するようになるのです。

6 Comparison to Published Word Representations

下表 (Table 6抜粋) のとおり既存の他の手法と比較しても、妥当な単語ベクトルがリーズナブルに獲得できることがわかります。データが巨大化しても計算量が爆発しないことが、本手法のポイントです。

f:id:ohke:20181027215226p:plain

7 Conclusion

Skip-gramモデルのアーキテクチャの改善によって、もっと大量のデータでも効率的に学習できるようになりました。

  • 頻出単語のサブサンプリングによって、学習の高速化と、低頻度の単語の単語ベクトルの精度向上
  • Negative samplingは、頻出単語の単語ベクトルの精度向上に寄与

適用にあたって考えないといけないのはモデルアーキテクチャとハイパパラメータ。

  • モデルアーキテクチャは、階層ソフトマックスとNegative samplingのどちらかを選ぶ必要がある
  • ハイパパラメータで重要なのは、ベクトル長、サブサンプリングの割合、ウィンドウサイズ

Skip-gramで得られた単語ベクトル同士を単純に足し算するだけでも面白い表現が得られ、Skip-gramが柔軟であることが伺えます。

参考にさせていただいた文献

GloVeで単語ベクトルを得る

単語ベクトル化モデルの一つであるGloVeを試してみます。

GloVe

GloVeは単語のベクトル表現を得る手法の一つで、Word2Vecの後発となります。論文はこちらです。

nlp.stanford.edu

Word2Vec (skip-gram with negative sampling: SGNS) では各単語から周辺単語を予測するというタスクをニューラルネットワークで解くことによって単語ベクトルを得ますが、GloVeではコーパス全体から得られる単語間の共起行列を持ち込んだ最適化関数 (重み付き最小二乗法) で学習します。

  • 単語iと単語jの共起行列が  logX_{ij}
  •  w_i^T\tilde{w_j} は、次元削減された単語ベクトル (factorization matrix)
  •  b_i,\tilde{b_j} は、それぞれ  w_i, \tilde{w_j} のバイアス
  • 関数  f は重みをつけるためのもので、コーパス全体で頻出するワード (助詞や指示語など) の重みを低くします


J=\sum_{i,j=1}^V f(X_{ij})(w_i^T\tilde{w_j} + b_i + \tilde{b_j} - logX_{ij})^2

理論面はきちんと勉強できていませんので、改めて整理します。

GloVeでベクトル化する

それでは実際にGloVeの実装を使って、単語をベクトル化していきます。

github.com

GloVeのダウンロードとビルド

READMEの手順に従って、ビルドを行います。./glove/buildに実行形式ファイルがいくつか生成されますので、あとで使います。

$ git clone http://github.com/stanfordnlp/glove
$ cd glove && make

コーパスの作成

最初にコーパスを作ります。日本語へ適用した場合の性質を見てみたいので、ここではサイズが小さくてかつ青空文庫でダウンロードできるデカルトの省察からコーパスを作ります。

import re
import urllib.request
import zipfile

# zipファイルのダウンロード
urllib.request.urlretrieve('https://www.aozora.gr.jp/cards/001029/files/43291_ruby_20925.zip', '43291_ruby_20925.zip')
with zipfile.ZipFile('./43291_ruby_20925.zip') as zip_file:
    zip_file.extractall('.')
    
# テキストファイルから文字列を抽出
with open('./meditationes.txt', 'r', encoding='shift_jis') as f:
    text = f.read()

# 本文を抽出 (ヘッダとフッタを削除)
text = ''.join(text.split('\n')[23:346]).replace(' ', '')
# 注釈 ([]) とルビ (《》) を削除
text = re.sub(r'([.*?])|(《.*?》)', '', text)
# 神の存在、及び人間の霊魂と肉体との区別を論証する、第一哲学についての省察 ...

さらにJanomeで形態素解析することで、単語を抽出します。名詞・動詞・副詞・形容詞の原型に限定しました。

# pip install Janome
from janome.analyzer import Analyzer
from janome.charfilter import UnicodeNormalizeCharFilter
from janome.tokenizer import Tokenizer
from janome.tokenfilter import POSKeepFilter, ExtractAttributeFilter

analyzer = Analyzer(
    [UnicodeNormalizeCharFilter()],
    Tokenizer(),
    [POSKeepFilter(['名詞', '動詞', '形容詞', '副詞']), ExtractAttributeFilter('base_form')]
)

tokens = [token for token in analyzer.analyze(text)]
# ['神', '存在', '人間', '霊魂', '肉体', ... ]

単語ごとに' 'で区切った1行の文書として./input.txtに保存します。これをコーパスとして使います。

with open('./input.txt', 'w') as f:
    f.write(' '.join(tokens))

GloVeでベクトル化

先程得られた実行ファイルを使って、コーパスから単語ベクトルを作ります。

必要なのは4つのコマンドです。

  • vocab_countで辞書を生成 (vocab.txt)
    • -min-countで、出現頻度の低い単語を足切り (ここでは2未満の単語はベクトル化しない)
  • cooccurで共起行列を生成 (cooccurrence)
    • -window-sizeで、いくつの周辺単語の共起をカウントするか指定する (ここでは前後15単語)
  • shuffle (cooccurrence_shuffleを出力、論文内でも記述が無く、おそらく実装都合のもの)
  • gloveでベクトル化 (vectors.txt, vectors.bin)
    • -vector-sizeで、ベクトルの次元数を指定
    • -iterで、イテレーション回数を指定
$ ./glove/build/vocab_count -min-count 2 -verbose 2 < input.txt > vocab.txt
$ ./glove/build/cooccur -memory 4 -vocab-file vocab.txt -verbose 2 -window-size 15 < input.txt > cooccurrence
$ ./glove/build/shuffle -memory 4 -verbose 2 < cooccurrence.txt > cooccurrence_shuffle
$ ./glove/build/glove -save-file vectors -threads 2 -input-file cooccurrence_shuffle -x-max 10 -iter 10 -vector-size 50 -binary 2 -vocab-file vocab.txt -verbose 2

vectors.txtは以下のように、1列目が単語ラベル、2列目以降がベクトルとなっています。

する 0.821036 -0.767963 0.173905 -1.520309 0.974474 0.516971 -0.905228 0.454891 0.759307 0.265375 -0.075784 0.535552 0.288605 -1.316993 -0.012559 -1.067366 -0.840541 0.042517 1.565274 -0.208433 -1.157825 1.280726 -0.874233 0.239113 -0.047998 0.300433 -0.785599 0.228769 -0.067241 -1.164181 -1.075916 -0.410294 -0.525001 0.309162 -0.522551 -1.094749 0.352643 0.157778 -0.395058 0.489368 0.144088 -1.184835 0.090272 0.048608 1.166587 -1.310016 -0.179411 0.451254 0.048985 0.206744
私 1.077732 -0.197421 0.158596 -1.215650 0.603018 -0.416900 -0.240335 -0.229747 0.497587 0.726050 -0.685833 0.304997 0.025257 -1.270019 0.117946 -0.820217 -1.074720 -0.251358 1.141868 -0.266699 -1.171293 0.236756 -0.899527 0.295187 -0.390448 0.432460 0.517610 0.523386 -0.012453 -0.166734 -1.654589 0.189975 -0.705832 0.434905 0.071865 -0.490482 0.746462 0.633038 -0.828486 0.306500 -0.047694 -0.760142 -0.170331 0.489952 0.957421 -0.903183 -0.070720 0.450352 0.165593 0.084121
こと 0.803995 -0.283790 -0.806350 -1.189181 0.367095 0.138987 0.039391 -0.141035 0.746949 0.676415 -0.684094 0.234107 0.723286 -0.853208 -0.421323 -0.547982 -0.630406 0.520100 1.511528 0.477472 -1.140159 0.218199 -0.874279 -0.031568 0.008137 0.625755 0.165821 0.212576 -0.022433 -0.677801 -1.592565 -0.456095 -0.199207 0.648881 0.391887 -0.248479 0.334491 0.716378 -0.922525 -0.100026 0.312525 -0.417347 -0.103911 0.243988 0.601623 -0.585152 -0.542268 0.113903 0.301077 -0.147555
...

ベクトルを読み込む

作られたベクトルファイル vectors.txt をpandas/numpyで読み込みます。

import pandas as pd
import numpy as np

# 単語ラベルをインデックスにしてDataFrameで読み込む
vectors = pd.read_csv('./vectors.txt', delimiter=' ', index_col=0, header=None)

vector = vectors.loc['人間',:].values
# array([ 0.118479, -0.161301, -0.11967 , -0.386339, -0.151804,  0.092074,
#         0.027781,  0.07296 ,  0.114962,  0.184768, -0.151259, -0.009416,
#         0.072511, -0.330517,  0.118942, -0.015616, -0.304021, -0.052112,
#         0.307647, -0.084063, -0.330387,  0.043164, -0.280033,  0.178405,
#        -0.118228,  0.144567, -0.001227,  0.122085,  0.016187, -0.025795,
#        -0.275177, -0.086059, -0.093451,  0.104058,  0.222139, -0.093288,
#         0.106345,  0.239498, -0.283105, -0.015229,  0.209854, -0.270562,
#        -0.115171, -0.011286,  0.242488, -0.124451, -0.064112,  0.008188,
#         0.125374, -0.065054])

gensimで読み込む

numpyで表現されていますのでコサイン類似度などの計算はできますが、gensimのKeyedVectorsにロードして、提供されているAPIを使って楽に取り扱えるようにします。

上で出力された vectors.txt をそのままKeyedVectorsでロードできません。1行目に単語数 次元数の記述が必要です。
そこで、1行目に単語数 次元数を追加したファイル (gensim_vectors.txt) を新たに準備します。

with open('./vectors.txt', 'r') as original, open('./gensim_vectors.txt', 'w') as transformed:
    vocab_count = vectors.shape[0]  # 単語数
    size = vectors.shape[1]  # 次元数
    
    transformed.write(f'{vocab_count} {size}\n')
    transformed.write(original.read())  # 2行目以降はそのまま出力

後はload_word2vec_formatメソッドでロードできます。

from gensim.models import KeyedVectors

glove_vectors = KeyedVectors.load_word2vec_format('./gensim_vectors.txt', binary=False)

例えば"人間"と最もベクトルが近い単語は以下で計算できます。

glove_vectors.most_similar('人間')
# [('精神', 0.9610371589660645),
#  ('身体', 0.9233092069625854),
#  ('見る', 0.913536548614502),
#  ('最後', 0.9134587049484253),
#  ('いま', 0.9112191200256348),
#  ('言う', 0.9044301509857178),
#  ('かよう', 0.902915358543396),
#  ('かえって', 0.9026569128036499),
#  ('できる', 0.9021652936935425),
#  ('よう', 0.9006799459457397)]