我正在使用 Python 实现 Word2Vec 的 Skip-Gram 模型。然而,正如生成的嵌入及其可视化所示,我的模型似乎无法正常工作。这是嵌入的 3D 图的示例,它显示单词聚集在一起并重叠,因此很难区分它们:
我怀疑问题在于我的实现而不是绘图函数。
import numpy as np
from nltk.corpus import stopwords
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
import re
np.random.seed(10)
def softmax(x):
'''
(xi - max{x1,x2,...,xv})
e
xi = --------------
(xj - max{x1,x2,...,xv})
∑j e
'''
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
class SkipGram:
def __init__(self,ws=2,dim=8) -> None:
self.X = []
self.N = dim
self.Y = []
self.window_size = ws
self.alpha = 0.1
self.vocab = {}
self.vocab_size = 0
def __create_vocabulary(self,corpus):
stop_words = set(stopwords.words("english"))
filtered_corpus = []
self.vocab_size = 0
for i,sentence in enumerate(corpus):
if isinstance(sentence,str) :
corpus[i] = sentence.split()
filtered_corpus.append([])
j = 0
for word in corpus[i]:
w = re.sub(r'[^a-z]+','',word.lower())
if w != '' and w not in stop_words:
corpus[i][j] = w
filtered_corpus[i].append(w)
else:
continue
if corpus[i][j].lower() not in self.vocab:
self.vocab[corpus[i][j].lower()] = self.vocab_size
self.vocab_size += 1
j += 1
return filtered_corpus
def __create_context_and_center_words(self,processed_corpus):
for sentence in processed_corpus:
for i,word in enumerate(sentence):
center_word = np.zeros((self.vocab_size,1))
center_word[self.vocab[word]][0] = 1
context = np.zeros((self.vocab_size,1))
for j in range(i-self.window_size,i + self.window_size + 1):
if j != i and j >= 0 and j < len(sentence):
context[self.vocab[sentence[j]]][0] += 1
self.X.append(center_word)
self.Y.append(context)
self.X = np.array(self.X)
self.Y = np.array(self.Y)
def initialize(self,corpus):
corpus = self.__create_vocabulary(corpus)
self.__create_context_and_center_words(corpus)
self.W1 = np.random.rand(self.vocab_size,self.N)
self.W2 = np.random.rand(self.N,self.vocab_size)
def feed_forward(self,x):
h = np.dot(self.W1.T,x) # N V . V 1 -> N 1
u = np.dot(self.W2.T,h) # V N . N 1 -> V 1
y = softmax(u)
return h,u,y
def backpropagate(self,x,y_actual,y_result,h):
e = y_result - y_actual # V 1
dw2 = np.dot(h,e.T) # N 1 . 1 V -> N V
eh = np.dot(self.W2,e) # N x V . V x 1 -> N x 1
dw1 = np.dot(x,eh.T) # V x 1 . 1 x N -> V x N
return dw1,dw2
def train(self,epochs):
for i in range(epochs):
loss = 0
dw1,dw2 = np.zeros_like(self.W1),np.zeros_like(self.W2)
for j in range(len(self.X)):
h,_,y = self.feed_forward(self.X[j])
a,b = self.backpropagate(self.X[j],self.Y[j],y,h)
dw1 += a
dw2 += b
loss -= np.sum(self.Y[j] * np.log(y+1e-08))
loss /= len(self.X)
[dw1,dw2] = [dw1/len(self.X), dw2/len(self.X)]
self.W1 -= self.alpha * dw1
self.W2 -= self.alpha * dw2
print(f'Epoch : {i+1}, Loss = {loss}')
def get_similar_words(self,word,n):
if word in self.vocab:
x = np.zeros((self.vocab_size,))
x[self.vocab[word]] = 1
_,_,y = self.feed_forward(x)
output = {}
for i in range(self.vocab_size):
output[y[i]] = i
words = {i:word for i,word in enumerate(self.vocab.keys())}
context = []
for k in sorted(output,reverse=True):
context.append(words[output[k]])
if len(context) == n:
break
return context
else:
print("Given Word not found")
def get_vector(self,word):
return self.W1[self.vocab[word]]
def plot(self):
tsne = TSNE(n_components=3,random_state=0,perplexity=self.vocab_size-1)
vectors_3d = tsne.fit_transform(self.W1)
fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(111,projection='3d')
ax.scatter(vectors_3d[:,0],vectors_3d[:,1],vectors_3d[:,2],marker='o',edgecolors='k')
for word,i in self.vocab.items():
ax.text(vectors_3d[i,0],vectors_3d[i,1],vectors_3d[i,2],word)
ax.set_title('Word2Vec Word Embeddings')
ax.set_xlabel('Dimension 1')
ax.set_ylabel('Dimension 2')
ax.set_zlabel('Dimension 3')
plt.show()
#main.py
from nltk.corpus import gutenberg
corpus = gutenberg.sents()[:40]
w2v = SkipGram(3,20)
w2v.initialize(corpus)
w2v.train(200)
w2v.plot()
I已尝试调整学习率并使用不同的值初始化权重,但问题仍然存在。
任何人都可以帮助我确定我的实现可能出现的问题吗?
查看了用于生成词汇和上下文单词的代码。 检查权重初始化和学习率设置。
代码中有几个地方可能会导致 Skip-gram 实现出现问题,从而导致嵌入聚集在一起,并且性能不佳:
- Softmax 函数: 的 softmax 实现虽然在技术上是正确的,但可能会遇到数值稳定性问题。指数项会导致非常大的数字,从而导致上溢。为了解决这个问题,请从指数中减去输入向量的最大值。以下是更新后的 softmax 函数:
def softmax(x):
"""计算 softmax 以提高数值稳定性"""
exp_x = np.exp(x - np.max(x))
return exp_x / exp_x.sum(axis=0)
-
反向传播中的维度:
需要仔细检查反向传播步骤中矩阵和向量的维度,尤其是在计算
dw2
和eh
时。确保维度在这些计算中对齐,并且正在更新正确的权重。当前,的代码似乎正在转置应该转置的内容。请尝试以下更改:
def backpropagate(self,x,y_actual,y_result,h):
e = y_result - y_actual
dw2 = np.dot(h,e.T)
eh = np.dot(self.W2,e)
dw1 = np.dot(x,eh.T)
return dw1,dw2
-
学习率: 虽然提到过调整学习率,但它仍然可能是罪魁祸首。过高的学习率会导致模型在最佳值附近波动,而过低的学习率会导致收敛速度太慢。尝试使用不同的学习率值(例如,在 0.1 到 0.001 之间)进行实验,以找到最适合数据集的学习率。还可以考虑使用随时间衰减的学习率。
-
训练数据: 此代码示例仅使用古登堡语料库中的一小部分句子(40 个句子)进行训练。对于 Word2Vec 和 Skip-gram 模型,通常需要更大的数据集才能学习有意义的词嵌入。尝试增加训练数据量,看看是否可以提高性能。
-
嵌入维度: 使用的是大小为 8 的嵌入。虽然这对于较小的数据集可能足够了,但增加嵌入维度通常可以提高性能,因为它允许模型捕获更多单词之间的关系。尝试将维度增加到 50 或 100。
-
窗口大小: 窗口大小确定在预测目标词时要考虑的上下文词数。虽然窗口大小为 3 是一个合理的默认值,但也可以尝试不同的值,以查看是否可以提高性能。
-
TSNE 可视化: 虽然 TSNE 是可视化高维数据的有用工具,但它并不总是嵌入质量的完美表示。如果在解决上述问题后嵌入仍然聚集在一起,则可能需要尝试使用不同的可视化技术,例如 PCA 或使用更大的数据集。
其他提示:
- 考虑在训练过程中监控验证集上的损失或指标,以了解模型是否过拟合训练数据。
-
探索使用
gensim
或TensorFlow
等库中可用的 Word2Vec 实现。这些实现通常经过优化,并且包含诸如负采样等功能,可以提高性能。
通过解决这些问题并尝试不同的超参数,应该能够改进 Skip-gram 实现并生成更有意义的词嵌入。
标签:python,word2vec From: 78824197