You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Python从零实现Word2Vec时Loss居高不下的求助

Word2Vec从零实现训练Loss居高不下问题排查

我正在学习Word2Vec模型,尝试基于Python从零实现它。沿用找到的教程代码,仅将训练数据替换为《爱丽丝梦游仙境》第一章内容,但训练时Loss始终维持在30000左右,无法降至1以下。我认为前向传播、反向传播、损失函数定义均无问题,已尝试调整词嵌入维度n、窗口大小、学习率等参数,均未达预期,恳请帮忙排查问题。

以下是我的Jupyter Notebook代码:

#--- IMPORT DEPENDENCIES ------------------------------------------------------+

import numpy as np
import re
from collections import defaultdict

#---Import for graph-----------------------------------------------------------+

import matplotlib.pyplot as plt
from IPython.display import clear_output

#--- CONSTANTS ----------------------------------------------------------------+


class word2vec():
    def __init__ (self):
        self.n = settings['n']
        self.eta = settings['learning_rate']
        self.epochs = settings['epochs']
        self.window = settings['window_size']
        self.losses = [] # added
        pass
    
    
    # GENERATE TRAINING DATA
    def generate_training_data(self, settings, corpus):

        # GENERATE WORD COUNTS
        word_counts = defaultdict(int)
        for row in corpus:
            for word in row:
                word_counts[word] += 1

        self.v_count = len(word_counts.keys())

        # GENERATE LOOKUP DICTIONARIES
        self.words_list = sorted(list(word_counts.keys()),reverse=False)
        self.word_index = dict((word, i) for i, word in enumerate(self.words_list))
        self.index_word = dict((i, word) for i, word in enumerate(self.words_list))

        training_data = []
        # CYCLE THROUGH EACH SENTENCE IN CORPUS
        for sentence in corpus:
            sent_len = len(sentence)

            # CYCLE THROUGH EACH WORD IN SENTENCE
            for i, word in enumerate(sentence):
                
                #w_target  = sentence[i]
                w_target = self.word2onehot(sentence[i])

                # CYCLE THROUGH CONTEXT WINDOW
                w_context = []
                for j in range(i-self.window, i+self.window+1):
                    if j!=i and j<=sent_len-1 and j>=0:
                        w_context.append(self.word2onehot(sentence[j]))
                training_data.append([w_target, w_context])
        return np.array(training_data)


    # SOFTMAX ACTIVATION FUNCTION
    def softmax(self, x):
        e_x = np.exp(x - np.max(x))
        return e_x / e_x.sum(axis=0)


    # CONVERT WORD TO ONE HOT ENCODING
    def word2onehot(self, word):
        word_vec = [0 for i in range(0, self.v_count)]
        word_index = self.word_index[word]
        word_vec[word_index] = 1
        return word_vec


    # FORWARD PASS
    def forward_pass(self, x):
        h = np.dot(self.w1.T, x)
        u = np.dot(self.w2.T, h)
        y_c = self.softmax(u)
        return y_c, h, u
                

    # BACKPROPAGATION
    def backprop(self, e, h, x):
        dl_dw2 = np.outer(h, e)  
        dl_dw1 = np.outer(x, np.dot(self.w2, e.T))

        # UPDATE WEIGHTS
        self.w1 = self.w1 - (self.eta * dl_dw1)
        self.w2 = self.w2 - (self.eta * dl_dw2)
        pass


    # TRAIN W2V model
    def train(self, training_data):
        # INITIALIZE WEIGHT MATRICES
        self.w1 = np.random.uniform(-0.8, 0.8, (self.v_count, self.n))     # embedding matrix
        self.w2 = np.random.uniform(-0.8, 0.8, (self.n, self.v_count))     # context matrix
        
        # CYCLE THROUGH EACH EPOCH
        for i in range(0, self.epochs):

            self.loss = 0

            # CYCLE THROUGH EACH TRAINING SAMPLE
            for w_t, w_c in training_data:

                # FORWARD PASS
                y_pred, h, u = self.forward_pass(w_t)
                
                # CALCULATE ERROR
                EI = np.sum([np.subtract(y_pred, word) for word in w_c], axis=0)

                # BACKPROPAGATION
                self.backprop(EI, h, w_t)

                # CALCULATE LOSS
                self.loss += -np.sum([u[word.index(1)] for word in w_c]) + len(w_c) * np.log(np.sum(np.exp(u)))
                #self.loss += -2*np.log(len(w_c)) -np.sum([u[word.index(1)] for word in w_c]) + (len(w_c) * np.log(np.sum(np.exp(u))))
                
            print('EPOCH:',i, 'LOSS:', self.loss)
            self.losses.append(self.loss)
            self.plot_losses()
        pass
    
    # Add this method
    def plot_losses(self):
        clear_output(wait=True)
        plt.plot(self.losses)
        plt.ylabel('Loss')
        plt.xlabel('Epoch')
        plt.show()
    
    # input a word, returns a vector (if available)
    def word_vec(self, word):
        w_index = self.word_index[word]
        v_w = self.w1[w_index]
        return v_w


    # input a vector, returns nearest word(s)
    def vec_sim(self, vec, top_n):

        # CYCLE THROUGH VOCAB
        word_sim = {}
        for i in range(self.v_count):
            v_w2 = self.w1[i]
            theta_num = np.dot(vec, v_w2)
            theta_den = np.linalg.norm(vec) * np.linalg.norm(v_w2)
            theta = theta_num / theta_den

            word = self.index_word[i]
            word_sim[word] = theta

        words_sorted = sorted(word_sim.items(), key=lambda word_sim: word_sim[1], reverse=True)

        for word, sim in words_sorted[:top_n]:
            print(word, sim)
            
        pass

    # input word, returns top [n] most similar words
    def word_sim(self, word, top_n):
        
        w1_index = self.word_index[word]
        v_w1 = self.w1[w1_index]

        # CYCLE THROUGH VOCAB
        word_sim = {}
        for i in range(self.v_count):
            v_w2 = self.w1[i]
            theta_num = np.dot(v_w1, v_w2)
            theta_den = np.linalg.norm(v_w1) * np.linalg.norm(v_w2)
            theta = theta_num / theta_den

            word = self.index_word[i]
            word_sim[word] = theta

        words_sorted = sorted(word_sim.items(), key=lambda word_sim: word_sim[1], reverse=True)

        for word, sim in words_sorted[:top_n]:
            print(word, sim)
            
        pass

import string
import nltk
nltk.download('punkt')

# 파일 읽기 및 전처리 함수
def preprocess(file_name):
    with open(file_name, 'r') as f:
        text = f.read().lower()
        text = text.translate(str.maketrans('', '', string.punctuation))  # punctuation 제거
        tokenized_text = nltk.word_tokenize(text)  # tokenizing
        return tokenized_text

# 학습시키기 위한 텍스트 파일 불러오기
file_name = 'alice_wonderland_small.txt'  # 이곳에 사용하려는 텍스트 파일 이름 입력
corpus = [preprocess(file_name)]  # 전처리 후 corpus로 사용

settings = {
    'n': 100,  # dimension of word embeddings
    'window_size': 2,  # context window +/- center word
    'min_count': 5,  # minimum word count
    'epochs': 100,  # number of training epochs
    'neg_samp': 5,  # number of negative words to use during training
    'learning_rate': 0.01  # learning rate
}
np.random.seed(0)

w2v = word2vec()

# generate training data
training_data = w2v.generate_training_data(settings, corpus)

# train word2vec model
w2v.train(training_data)

问题排查与优化建议:

  • 训练数据量不足:仅用单章文本的语料规模太小,Word2Vec需要大量上下文关联来学习有效嵌入。建议扩充至整本书或更大规模语料,让模型有足够样本学习词汇间的语义关系。
  • 权重初始化范围过大:当前用[-0.8, 0.8]初始化权重,范围过大导致初始Loss极高,优化难度大。建议缩小范围至[-0.01, 0.01],让模型从更接近最优解的初始状态开始训练。
  • 低频词未过滤:配置了min_count:5但代码未实现过滤逻辑,稀有词会增加模型优化难度且无法有效学习嵌入。在generate_training_data中添加逻辑:仅保留出现次数≥min_count的词汇,缩减词汇表规模。
  • 学习率与训练轮数调整:当前0.01的学习率对于小规模数据可能收敛过慢,可尝试初始用0.1,随训练轮数逐步衰减(如每轮乘以0.95);同时增加训练轮数至200-500轮,观察Loss下降趋势。
  • 关注Loss趋势而非绝对值:当前Loss是样本损失的累加值,小规模数据下绝对值本身会偏高,更应该看每轮Loss是否持续下降。可以修改Loss计算为平均Loss(总Loss除以训练样本数),更直观判断收敛情况。

内容的提问来源于stack exchange,提问作者James Jang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:15:55