Skip-Gram#
[発展課題]skip_gram.py#
(簡易的な)Skip-Gramを実装し,max_epochs=100, minibatch_size=512として訓練し,「サッカー」,「日本」,「女王」,「機械学習」について類似単語を類似度の高い順に上位5個表示するプログラムを作成してください.
cbow.pyを参考にしてください.
学習にはja.text8を利用してください.
雛形:
class SkipGram(nn.Module):
def __init__(self):
super().__init__()
...
def forward(self,x):
...
実装#
実行結果#
Usage#
argparserのdescriptionやhelpに説明を書き込んで,--help
オプションで使い方が表示できるようにしてください.
(datasci) mriki@RikinoMac _prml % python skipgram.py -h
usage: skipgram.py [-h] [--learning_rate LEARNING_RATE] [--batch_size BATCH_SIZE] [--embedding_dim EMBEDDING_DIM] [--seed SEED] [--max_epochs MAX_EPOCHS] [--char_limit CHAR_LIMIT] [--device DEVICE] [--data_path DATA_PATH] [--save_path SAVE_PATH] [--window_size WINDOW_SIZE] [--query QUERY] [--topn TOPN]
Skip-Gramの訓練をja.text8で行う
options:
-h, --help show this help message and exit
--learning_rate LEARNING_RATE
--batch_size BATCH_SIZE
--embedding_dim EMBEDDING_DIM
--seed SEED
--max_epochs MAX_EPOCHS
--char_limit CHAR_LIMIT
ja.text8の先頭から何文字を利用するか.Noneの場合は全てを使う. ex. 1_000_000
--device DEVICE
--data_path DATA_PATH
訓練用コーパスの保存場所
--save_path SAVE_PATH
学習済みモデルのファイル名.すでに存在していた場合はそれを読み込んで利用する
--window_size WINDOW_SIZE
--query QUERY 文字列を渡すと類似する単語をtopn個検索する
--topn TOPN 検索単語数
実行#
初回学習時:
(datasci) mriki@RikinoMac _prml % python skipgram.py --char_limit 1000000 --seed 7012 --save_path ./skipgram.pkl --max_epochs 2
全文書の文字数が46507793あり,その内1000000だけを利用します.
前処理...
363003it [00:16, 21819.80it/s]
100%|███████████████████████| 80264/80264 [00:00<00:00, 900528.08it/s]
contextsのshape: (80264, 17871)
訓練開始...
epoch train_loss train_ppl valid_loss valid_ppl dur
------- ------------ ----------- ------------ ----------- -------
1 9.8043 18111.2683 10.1251 24961.8040 14.5564
2 8.4743 4789.8352 10.6640 42786.7327 14.5319
学習済みの場合:
(datasci) mriki@RikinoMac _prml % python skipgram.py --char_limit 1000000 --seed 7012 --save_path ./skipgram.pkl --max_epochs 2
./skipgram.pklから学習済みモデルを読み込みます...
学習済みでクエリを検索する場合:
(datasci) mriki@RikinoMac _prml % python skipgram.py --save_path ./skipgram.pkl --query 日本
./skipgram.pklから学習済みモデルを読み込みます...
>>> 日本
1:古代 0.9472917318344116
2:文明 0.9328379034996033
3:社会 0.931919515132904
4:文化 0.9224883317947388
5:天皇 0.9139895439147949
(datasci) mriki@RikinoMac _prml % python skipgram.py --save_path ./skipgram.pkl --query ロボット
./skipgram.pklから学習済みモデルを読み込みます...
>>> ロボット
1:ロボティックス 0.8361095190048218
2:ステーション 0.8090811967849731
3:ぼう 0.8085721135139465
4:ロケット 0.7877843976020813
5:地球 0.7545166611671448
(簡易的な)Skip-Gram実装のヒント#
Note
このノートは Skip-Gram実装課題 のヒントになるように書かれています.
CBoWと遂になる単語埋め込みベクトル作成手法である Skip-Gram を実装します.ここでは,Negative Samplingのような技術を使わず,出力層でsoftmax関数を利用することで実装を簡単にしています.そのため計算コストが膨大になる傾向があり,大規模なコーパスに適用することはお勧めしません.
Skip-Gramの計算コストの大きさには出力層のSoftmax活性化関数が大きな影響を与えます.そのため,高速化を行うためにはSoftmaxを Negative Sampling と呼ばれるアルゴリズムで代用することになります.これについてはこのブログが実装の助けになります.また,直接Skip-Gramを紹介しているわけではないのですが,CBoWの説明の中でこれを説明しているゼロから作るDeep Learning ❷ ―自然言語処理編も非常に参考になるでしょう.
# packageのimport
import re
import math
from typing import Any
from tqdm.std import trange,tqdm
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.sparse import lil_matrix
# pytorch関連のimport
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import skorch
from skorch import NeuralNetClassifier, NeuralNetRegressor
from skorch.callbacks import Callback, EpochScoring
from torch.utils.data import Dataset
from janome.tokenizer import Tokenizer
データの読み込み#
コーパスにはja.text8のサブセットを利用します.発展課題に取り組む場合も,指定されているハイパーパラメータやコーパスのサイズが実行困難である場合は適宜修正してください.ただし,その場合はskip_gram.pyの先頭行に,docstringを用意してその旨を書いてください.あるいはCLIのオプションにしてもいいかもしれません.
with open("./input/ja.text8") as f:
text8 = f.read()
print(text8[:200])
#LIMIT = math.floor(len(text8)*0.1)
LIMIT = 100_0000
print(f"{LIMIT}/ {len(text8)}")
text8 = text8[:LIMIT]
ちょん 掛け ( ちょん がけ 、 丁 斧 掛け ・ 手斧 掛け と も 表記 ) と は 、 相撲 の 決まり 手 の ひとつ で ある 。 自分 の 右 ( 左 ) 足 の 踵 を 相手 の 右 ( 左 ) 足 の 踵 に 掛け 、 後方 に 捻っ て 倒す 技 。 手斧 ( ちょう な ) を かける 仕草 に 似 て いる こと から 、 ちょう な が 訛っ て ちょん 掛け と なっ
1000000/ 46507793
形態素解析#
コーパス内の単語(トークン)全てを利用すると語彙が多くなりすぎるので,ここでは名詞(それも一般名詞と固有名詞)のみを利用します.そのために形態素解析を行う必要があるので,python製の形態素解析器であるjanomeを利用しています.形態素解析器はこれ以外にもMecabなどが有名です.
形態素解析器 janome#
ja.text8の一部に品詞分解を行なった結果を以下に示します.
t = Tokenizer()
sample_text = "".join(text8[:50].split())
for token in t.tokenize(sample_text):
print(token.surface, "\t", token.part_of_speech.split(","))
ちょん ['名詞', '一般', '*', '*']
掛け ['名詞', '接尾', '一般', '*']
( ['記号', '括弧開', '*', '*']
ちょん ['名詞', '一般', '*', '*']
がけ ['名詞', '接尾', '一般', '*']
、 ['記号', '読点', '*', '*']
丁 ['名詞', '固有名詞', '人名', '姓']
斧 ['名詞', '一般', '*', '*']
掛け ['名詞', '接尾', '一般', '*']
・ ['記号', '一般', '*', '*']
手斧 ['名詞', '一般', '*', '*']
掛け ['名詞', '接尾', '一般', '*']
と ['助詞', '格助詞', '引用', '*']
も ['助詞', '係助詞', '*', '*']
表記 ['名詞', 'サ変接続', '*', '*']
) ['記号', '括弧閉', '*', '*']
と ['助詞', '格助詞', '引用', '*']
は ['助詞', '係助詞', '*', '*']
、 ['記号', '読点', '*', '*']
相撲 ['名詞', '一般', '*', '*']
janomeを使った語彙辞書作成#
活用する語彙をまとめた辞書(word2id, id2word)を作成します.この実装はダーティなので,実際に自然言語処理を行う場合は参考にしないでください.
def my_analyzer(text):
#text = code_regex.sub('', text)
#tokens = text.split()
#tokens = filter(lambda token: re.search(r'[ぁ-ん]+|[ァ-ヴー]+|[一-龠]+', token), tokens)
tokens = []
for token in tqdm(t.tokenize(text)):
pos = token.part_of_speech.split(",")
if "名詞" == pos[0]:
if "一般" == pos[1] or "固有名詞" == pos[1]:
tokens.append(token.surface)
tokens = filter(lambda token: re.search(r'[ぁ-ん]+|[ァ-ヴー]+|[一-龠]+', token), tokens)
return tokens
def build_contexts_and_target(corpus, window_size:int=5)->tuple[np.ndarray,np.ndarray]:
contexts = []
target = []
vocab = set()
_window_size = window_size//2
# 文ごとに分割
preprocessed_corpus = corpus.replace(" ","")
# posを見て単語ごとに分割
tokens = list(my_analyzer(preprocessed_corpus))
# 新しい語彙を追加
vocab = vocab | set(tokens)
# スライディングウィンドウ
for i in trange(_window_size, len(tokens)-_window_size):
# ウィンドウの真ん中をtargetにする
target.append(tokens[i])
# 真ん中以外の単語をcontextsへ
tmp = tokens[i-_window_size:i]
tmp += tokens[i+1:i+1+_window_size]
contexts.append(tmp)
# 辞書作成
id2word = list(vocab)
word2id = {word:id for id,word in enumerate(id2word)}
vocab_size = len(word2id)
# contextsとtargetを単語id配列へ置き換え
contexts_id_list = [[word2id[word] for word in doc] for doc in contexts]
target_id_list = [word2id[word] for word in target]
contexts = lil_matrix((len(contexts_id_list), vocab_size),dtype=np.float32)
for index, _contexts_id_list in enumerate(contexts_id_list):
#tmp = np.eye(vocab_size)[np.array(_contexts_id_list)]
for word_id in _contexts_id_list:
contexts[index, word_id] +=1.
target = np.array(target_id_list)
return contexts.tocsr().astype(np.float32), target, word2id, id2word
WINDOW_SIZE = 11
contexts, target, word2id, id2word = build_contexts_and_target(text8, window_size=WINDOW_SIZE)
print(f"contextsのshape: {contexts.shape}")
Show code cell output
0it [00:00, ?it/s]
2067it [00:00, 16788.10it/s]
6114it [00:00, 28602.12it/s]
9054it [00:00, 27513.13it/s]
13070it [00:00, 31596.71it/s]
16274it [00:00, 29259.19it/s]
20240it [00:00, 31985.91it/s]
23487it [00:00, 29714.19it/s]
26975it [00:00, 30285.42it/s]
30041it [00:01, 24175.57it/s]
33531it [00:01, 26601.94it/s]
36383it [00:01, 25186.02it/s]
40080it [00:01, 28102.24it/s]
43038it [00:01, 27185.48it/s]
47181it [00:01, 30772.49it/s]
50371it [00:01, 28219.07it/s]
54610it [00:01, 31350.79it/s]
57850it [00:02, 27924.15it/s]
60768it [00:02, 27947.62it/s]
64296it [00:02, 26403.68it/s]
68418it [00:02, 29675.01it/s]
71494it [00:02, 28336.15it/s]
74629it [00:02, 28300.20it/s]
77557it [00:02, 28215.65it/s]
80415it [00:02, 24705.54it/s]
83979it [00:03, 26913.42it/s]
86758it [00:03, 21413.99it/s]
89659it [00:03, 22960.89it/s]
92782it [00:03, 23040.60it/s]
96758it [00:03, 26982.49it/s]
99808it [00:03, 24583.65it/s]
103248it [00:03, 26746.48it/s]
106075it [00:03, 26002.27it/s]
108779it [00:04, 23805.48it/s]
112098it [00:04, 25576.92it/s]
114741it [00:04, 23626.72it/s]
117534it [00:04, 24421.70it/s]
120039it [00:04, 22662.13it/s]
122905it [00:04, 23829.15it/s]
125894it [00:04, 24982.43it/s]
128438it [00:04, 22667.08it/s]
131015it [00:05, 23264.20it/s]
133783it [00:05, 21041.74it/s]
137202it [00:05, 23884.01it/s]
140052it [00:05, 24930.75it/s]
142628it [00:05, 23719.98it/s]
146774it [00:05, 27978.05it/s]
149650it [00:05, 25320.49it/s]
153314it [00:05, 27742.60it/s]
156177it [00:05, 24780.47it/s]
159299it [00:06, 26038.11it/s]
161994it [00:06, 24371.58it/s]
165754it [00:06, 27730.18it/s]
168631it [00:06, 27250.74it/s]
172467it [00:06, 29755.64it/s]
175506it [00:06, 28448.40it/s]
179451it [00:06, 30959.44it/s]
182596it [00:06, 30136.05it/s]
187017it [00:06, 33470.57it/s]
190400it [00:07, 26255.65it/s]
193278it [00:07, 26309.55it/s]
196302it [00:07, 23741.03it/s]
200093it [00:07, 26879.51it/s]
203555it [00:07, 26480.24it/s]
207394it [00:07, 29410.45it/s]
210487it [00:07, 29166.32it/s]
213508it [00:08, 26895.29it/s]
217925it [00:08, 26725.94it/s]
221964it [00:08, 29842.69it/s]
225310it [00:08, 28545.33it/s]
229410it [00:08, 31307.23it/s]
232644it [00:08, 27619.69it/s]
235745it [00:08, 28328.65it/s]
239002it [00:08, 28759.53it/s]
241955it [00:09, 25132.97it/s]
245702it [00:09, 27826.15it/s]
248611it [00:09, 25966.52it/s]
252677it [00:09, 29245.89it/s]
255712it [00:09, 26169.03it/s]
258896it [00:09, 27460.17it/s]
261750it [00:09, 24834.35it/s]
265072it [00:09, 26905.70it/s]
267876it [00:10, 23138.73it/s]
271067it [00:10, 25050.50it/s]
274540it [00:10, 26974.20it/s]
277361it [00:10, 25489.65it/s]
281061it [00:10, 28046.84it/s]
283957it [00:10, 26710.19it/s]
288063it [00:10, 30090.33it/s]
291150it [00:10, 28279.04it/s]
294920it [00:10, 30672.90it/s]
298061it [00:11, 24372.09it/s]
300857it [00:11, 25179.12it/s]
303564it [00:11, 22856.82it/s]
306920it [00:11, 24987.33it/s]
309564it [00:11, 23933.32it/s]
312976it [00:11, 26180.60it/s]
315885it [00:11, 22397.85it/s]
318766it [00:12, 23857.26it/s]
322056it [00:12, 26019.05it/s]
324795it [00:12, 24036.28it/s]
328838it [00:12, 28206.75it/s]
331808it [00:12, 25551.70it/s]
335425it [00:12, 27766.25it/s]
338326it [00:12, 25687.79it/s]
342551it [00:12, 29755.42it/s]
345662it [00:13, 23158.86it/s]
348279it [00:13, 23823.99it/s]
350893it [00:13, 23223.21it/s]
355084it [00:13, 27424.62it/s]
357999it [00:13, 27282.78it/s]
361440it [00:13, 29060.77it/s]
363003it [00:13, 26577.71it/s]
0%| | 0/80264 [00:00<?, ?it/s]
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 80264/80264 [00:00<00:00, 1031989.90it/s]
contextsのshape: (80264, 17871)
クラスの作成#
Skip-gramをnn.Moduleのサブクラスとして実装します.
クラスの実装には上のskip-gramアーキテクチャ図を参考にしてください.高速化のテクニックなどは不要です.(もちろん実装できる人は実装してもOK)
class SkipGram(nn.Module):
def __init__(self, vocab_size:int, embedding_dim:int)->None:
super().__init__()
...
def forward(self, input:torch.Tensor)->torch.Tensor:
...
損失関数の作成#
Skip-Gramはクラス分類の体裁をとっているので,損失関数にはCross Entropyを用います.ただしPyTorchで用意されているnn.CrossEntropyを用いることは(おそらく)できないので,自作しましょう.
Hint
条件:
batch_size=128, vocab_size=11342のとき,以下が損失関数に入力されると仮定して実装してください.
SkipGramがforwardメソッドから出力するtensor.shapeは「torch.Size([128, 11342])」,
正解データとして利用するtensor.shapeは「torch.Size([128, 11342])」
callbackにおいて,ここで実装したcross entropyを使ってperplexityを計算します.
class BowCrossEntropy(nn.Module):
def forward(self, input, target):
"""
inputはSkip-gramの出力です.
targetは予測したいcontextsです.
"""
...
trainerの準備と訓練#
ここまでの実装が終わったら,あとは訓練用のプログラムを書くだけです.この解説ではskorchを利用して楽をします.Skip-Gramはクラス分類の体裁を取っていると言いましたが,出力はcategoricalではなくmultinomialです.つまり 一つのデータに対して正解ラベルが複数あります .これはskorchのNeuralNetClassifier
では上手く扱えないので,NeuralNetRegressor
を使っています.
Note
NeuralNetClassifierは主に1データ1ラベルの場合に利用します.今回の例でも使えないわけではないのですが,標準で設定された「正答率を表示するコールバック」が動作してしまうので利用を見送りました.
EpochScoring(lambda net,X=None,y=None: np.exp(net.history_[-1, "valid_loss"]), name="valid_ppl"),
はエポックの終わりに呼び出されるコールバック関数の雛形であるEpochScoring
を利用して,Perplexityを計算します.targetもcontextsもnp.ndarrayのままでfitに渡します.
trainerが中でdatasetやdataloaderを用意してくれます.
contextsはscipy.sparse.lil_matrix or scipy.sparse.csr_matrixになっているので,
toarray
メソッドでnp.ndarrayに戻しています.
trainer = NeuralNetRegressor(
SkipGram(len(word2id), 50),
optimizer=optim.Adam,
criterion=BowCrossEntropy,
max_epochs=20,
batch_size=128,
lr=0.01,
callbacks=[
EpochScoring(lambda net,X=None,y=None: np.exp(net.history_[-1, "valid_loss"]), name="valid_ppl"),
EpochScoring(lambda net,X=None,y=None: np.exp(net.history_[-1, "train_loss"]), name="train_ppl", on_train=True,)
],
device="cpu", # 適宜変更
)
trainer.fit(target, contexts.toarray())
Show code cell output
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[7], line 15
1 trainer = NeuralNetRegressor(
2 SkipGram(len(word2id), 50),
3 optimizer=optim.Adam,
(...)
12 device="cpu", # 適宜変更
13 )
---> 15 trainer.fit(target, contexts.toarray())
File ~/workspace/prpy/.venv/lib/python3.11/site-packages/skorch/regressor.py:82, in NeuralNetRegressor.fit(self, X, y, **fit_params)
71 """See ``NeuralNet.fit``.
72
73 In contrast to ``NeuralNet.fit``, ``y`` is non-optional to
(...)
77
78 """
79 # pylint: disable=useless-super-delegation
80 # this is actually a pylint bug:
81 # https://github.com/PyCQA/pylint/issues/1085
---> 82 return super(NeuralNetRegressor, self).fit(X, y, **fit_params)
File ~/workspace/prpy/.venv/lib/python3.11/site-packages/skorch/net.py:1317, in NeuralNet.fit(self, X, y, **fit_params)
1285 """Initialize and fit the module.
1286
1287 If the module was already initialized, by calling fit, the
(...)
1314
1315 """
1316 if not self.warm_start or not self.initialized_:
-> 1317 self.initialize()
1319 self.partial_fit(X, y, **fit_params)
1320 return self
File ~/workspace/prpy/.venv/lib/python3.11/site-packages/skorch/net.py:905, in NeuralNet.initialize(self)
903 self._initialize_module()
904 self._initialize_criterion()
--> 905 self._initialize_optimizer()
906 self._initialize_history()
908 self._validate_params()
File ~/workspace/prpy/.venv/lib/python3.11/site-packages/skorch/net.py:876, in NeuralNet._initialize_optimizer(self, reason)
873 msg = self._format_reinit_msg("optimizer", triggered_directly=False)
874 print(msg)
--> 876 self.initialize_optimizer()
878 # register the virtual params for all optimizers
879 for name in self._optimizers:
File ~/workspace/prpy/.venv/lib/python3.11/site-packages/skorch/net.py:642, in NeuralNet.initialize_optimizer(self, triggered_directly)
638 args, kwargs = self.get_params_for_optimizer(
639 'optimizer', named_parameters)
641 # pylint: disable=attribute-defined-outside-init
--> 642 self.optimizer_ = self.optimizer(*args, **kwargs)
643 return self
File ~/workspace/prpy/.venv/lib/python3.11/site-packages/torch/optim/adam.py:45, in Adam.__init__(self, params, lr, betas, eps, weight_decay, amsgrad, foreach, maximize, capturable, differentiable, fused)
39 raise ValueError(f"Invalid weight_decay value: {weight_decay}")
41 defaults = dict(lr=lr, betas=betas, eps=eps,
42 weight_decay=weight_decay, amsgrad=amsgrad,
43 maximize=maximize, foreach=foreach, capturable=capturable,
44 differentiable=differentiable, fused=fused)
---> 45 super().__init__(params, defaults)
47 if fused:
48 if differentiable:
File ~/workspace/prpy/.venv/lib/python3.11/site-packages/torch/optim/optimizer.py:279, in Optimizer.__init__(self, params, defaults)
277 param_groups = list(params)
278 if len(param_groups) == 0:
--> 279 raise ValueError("optimizer got an empty parameter list")
280 if not isinstance(param_groups[0], dict):
281 param_groups = [{'params': param_groups}]
ValueError: optimizer got an empty parameter list
類似単語検索#
cbowと同様に単語埋め込みベクトルを使って,類似単語の検索を行います.
def get_similar_words(query, word_embeddings, topn=5, word2id=word2id, ):
"""単語埋め込みベクトルを使って似た単語を検索する
Args:
query (str): 類似単語を検索したい単語
topn (int, optional): 検索結果の表示個数. Defaults to 5.
word2id (dict[str,int], optional): 単語→単語idの辞書. Defaults to word2id.
word_embeddings (np.ndarray, optional): 単語埋め込み行列.必ず(語彙数x埋め込み次元数)の行列であること. Defaults to word_embeddings.
"""
id=word2id[query]
E = (word_embeddings.T / np.linalg.norm(word_embeddings,ord=2, axis=1)).T # {(V,L).T / (V)}.T = (V,L)
target_vector = E[id]
cossim = E @ target_vector # (V,L)@(L)=(V)
sorted_index = np.argsort(cossim)[::-1][1:topn+1] # 最も似たベクトルは自分自身なので先頭を除外
print(f">>> {query}")
_id2word = list(word2id.keys())
for rank, i in enumerate(sorted_index):
print(f"{rank+1}:{_id2word[i]} \t{cossim[i]}")
word_embeddings = trainer.module_.embedding.weight.detach().cpu().numpy()
get_similar_words("ロボット", word_embeddings, )
>>> ロボット
1:ユニバーサル 0.898608386516571
2:ポルト 0.7893995642662048
3:ロボティックス 0.763614296913147
4:テラ 0.742680013179779
5:関節 0.7259170413017273
get_similar_words("サッカー", word_embeddings, )
get_similar_words("日本", word_embeddings, )
get_similar_words("女王", word_embeddings, )
get_similar_words("機械学習", word_embeddings, )
>>> サッカー
1:リーグ 0.734089195728302
2:専業 0.7245967388153076
3:ヴァンフォーレ 0.6850863695144653
4:選手 0.6845436692237854
5:アルビレックス 0.6741206645965576
>>> 日本
1:ほん 0.6705817580223083
2:米国 0.6255179047584534
3:王者 0.6063108444213867
4:社団 0.5765134692192078
5:蓄音機 0.5684884786605835
>>> 女王
1:ヴィクトリアシリーズ 0.6750556826591492
2:後塵 0.649889349937439
3:ティアラカップ 0.641579806804657
4:ボウラー 0.6231715083122253
5:シェクター 0.6060587763786316
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Cell In[11], line 4
2 get_similar_words("日本", word_embeddings, )
3 get_similar_words("女王", word_embeddings, )
----> 4 get_similar_words("機械学習", word_embeddings, )
Cell In[9], line 10, in get_similar_words(query, word_embeddings, topn, word2id)
1 def get_similar_words(query, word_embeddings, topn=5, word2id=word2id, ):
2 """単語埋め込みベクトルを使って似た単語を検索する
3
4 Args:
(...)
8 word_embeddings (np.ndarray, optional): 単語埋め込み行列.必ず(語彙数x埋め込み次元数)の行列であること. Defaults to word_embeddings.
9 """
---> 10 id=word2id[query]
11 E = (word_embeddings.T / np.linalg.norm(word_embeddings,ord=2, axis=1)).T # {(V,L).T / (V)}.T = (V,L)
12 target_vector = E[id]
KeyError: '機械学習'
今回の解説ではja.text8のサブセットを利用しているせいで,この単語埋め込みがカバーしている語彙に「機械学習」は含まれていないようです.