base-LLM(三)

张开发
2026/4/16 9:44:18 15 分钟阅读

分享文章

base-LLM(三)
LDA与文档主题挖掘主题模型如 LDA能从大量文档中自动发现隐藏的、无监督的主题它的输入同样是词典和 BoW 语料库import jieba from gensim import corpora,models # 准备语料 headlines [ 央行降息刺激股市反弹, 球队赢得总决赛冠军球员表现出色, 国家队公布最新一期足球集训名单, A股市场持续震荡投资者需谨慎, 篮球巨星刷新历史得分记录, 理财产品收益率创下新高 ] tokenized_headlines [jieba.lcut(title) for title in headlines] # 创建词典与 BOW 语料库 dict corpora.Dictionary(tokenized_headlines) corpus_bow [dictionary.doc2bow(doc) for doc in tokenized_headlines] # 训练 LDA 模型假设要发现2个主题 lda_model models.LdaModel(corpuscorpus_bow, id2worddictionary, num_topics2, random_state100) # 查看模型发现的主题 print(模型发现的2个主题及其关键词:) for topic in lda_model.print_topics(): print(topic) # 推断新文档的主题分布 new_headline 巨星詹姆斯获得常规赛MVP new_headline_bow dictionary.doc2bow(jieba.lcut(new_headline)) topic_distribution lda_model[new_headline_bow] print(f\n新标题 {new_headline} 的主题分布:) print(topic_distribution)通过 LDA不仅可以将一篇文档表示为一个主题概率分布Gensim 默认以稀疏列表返回例如 90% 体育、10% 财经还能清晰地看到每个主题由哪些核心词构成若新文本在词典中几乎无重叠词doc2bow 为空推断出的主题分布可能接近均匀例如 2 个主题时约为 0.5/0.5Word2Vec 模型实战与前两者不同的是Word2Vec 的输入是分词后的句子列表它的目标不是加权或寻找主题而是根据上下文学习每个词语本身内在的稠密的语义向量Word2Vec 的最终目标是获得一个高质量的词向量查询表存储在model.wv 属性中模型训练与核心参数Word2Vec 主要的难点是如何根据语料特点调整超参数如窗口大小最小词频等这些参数决定了词向量的质量import jieba from gensim.models import Word2Vec # 准备语料 headlines [ # 财经 央行降息刺激股市反弹, A股市场持续震荡投资者需谨慎, 理财产品收益率创下新高, 证监会发布新规规范市场交易, 创业板指数上涨科技股领涨大盘, 房价调控政策出台房地产市场降温, 全球股市动荡影响资本市场信心, 分析师认为当前股市风险与机遇并存市场情绪复杂, # 体育 球队赢得总决赛冠军球员表现出色, 国家队公布最新一期足球集训名单, 篮球巨星刷新历史得分记录, 奥运会开幕中国代表团旗手确定, 马拉松比赛圆满结束选手创造佳绩, 电子竞技联赛吸引大量年轻观众, 这支球队的每位球员都表现出色, 球员转会市场活跃多支球队积极引援 ] tokenized_headlines [jieba.lcut(title) for title in headlines] # 训练 Word2Vec 模型 model Word2Vec(tokenized_headlines, vector_size50, window3, min_count1, sg1)训练完成后所有词向量都存储在 model.wv 对象中这是一个 KeyedVectors 实例介绍一下模型训练时我们设置的参数sentences输入的语料库对应代码中的 tokenized_headlines 必须是 list[list[str]] 格式vector_size词向量的维度维度越高表达的语义信息越丰富计算量越大通常在 50--300 之间选择windows 上下文窗口大小min_count最小词频过滤任何语料库中出现次数低于此值的词将直接被忽略可以过滤掉大量噪声错别字罕见词等sg选择训练算法0 表示 CBOW 根据上下文预测中心词1 表示 Skip-gram根据中心词预测上下文hs选择优化策略0 表示使用Negative Sampling(负采样)1 表示使用Hierarchical Softmaxnegative当 hs 0 使用负采样时为每个正样本随机选择多少个负样本通常在 5-20 之间sample高频次二次重采样阈值这是一个控制高频词如是的等被随机跳过的机制目的是减少他们对模型训练的过多影响加快训练速度使用词向量模型训练完成后所有的操作都围绕 model.wv 展开用于探索词语间的语义关系小语料示例下相似度数值通常较低且不稳定仅作演示参考# 寻找最相似的词 similar_to_market model.wv.most_similar(股市) print(f与‘股市’最相似的词{similar_to_market}) # 计算两个词的余弦相似度 similarity model.wv.similarity(球队,球员) print(f‘球队’和‘球员’的相似度{similarity:.4f}) # 获取一个词的向量 market_vector model.wv[市场] print(f‘市场’的向量维度{market_vector.shape})模型的持久化在实际项目中会把训练好的向量保存下来如果后期不再进行训练就只保存 KeyedVectors 对象 model.wv 因为完整的模型对象包含了哈夫曼树梯度累积量等仅在训练阶段需要的中间状态from gensim.models import KeyedVectors # 保存词向量文件到文件 model.wv.save(news_vectors.kv) # 从文件加载词向量 loaded_wv KeyedVectors.load(news_vectors.kv) # 加载后可以执行同样的操作 print(f加载后‘球员’和‘球队’的相似度{loaded_wv.similarity(球队,球员):.4f})通过 Gensim我们就可以非常方便地训练自己的 Word2Vec 模型并利用它的语义捕捉能力进行相似度计算、语义类比等 NLP 任务循环神经网络上述过程我们学会了如何将文本分词并通过词嵌入技术将每个独立的词元转换成一个静态的稠密的词向量这解决了模型输入的第一个问题即文本数值化接下来的问题就是如何从一个词向量序列中有效的提取整个序列的特征简单方法的局限性针对将词向量序列融合成一个定长的文本向量这一需求早期的解决方案主要是对此项链那个的简单组合一种方法是词袋法将所有词向量加权或取平均但这种方法完全忽略了语序信息它将所有词语看做是同等重要的还有一种方法是使用全连接网络FCNFully Connected Network来处理这些词向量可以先FCN后求和或者先求和后FCN无论哪种方法在每个词元的位置上每个时间步使用的全连接层都必须共享同一套权重参数这样的话就导致在计算任意一个词的输出时模型仅仅使用了该词的词向量模型还是无法理解词元之间的顺序关系和上下文依赖在处理序列数据时模型按顺序逐个处理序列中的元素时间步就是这个处理过程中的一个离散步骤在文本处理的上下文中一个时间步通常对应处理一个词元因此我们将在图像处理中有显著成就的 CNN 用于文本通过使用一维卷积核窗口划过整个词向量序列CNN 能够捕捉到词语的局部依赖关系如 n-grams但是 CNN 的缺陷在于它的感受野是固定的一个大小为3的卷积核只能看到附近3个词的关系虽然可以通过堆叠多层CNN来扩大感受野但对于句子开头和结尾的长距离依赖CNN 仍然难以有效捕捉无法预先设定一个适用于所有句子的最佳窗口大小引入记忆的 RNN循环神经网络Recurrent Neural Network, RNN不仅接收当前时间步还会接受一个来自上一步的记忆网络会将这两部分信息融合生成当前步的输出作为记忆传递给下一步这种最基础的 RNN 结构通常被称为简单循环神经网络Simple Recurrent NetworkSRN在所有的时间步中权重矩阵U输入到隐藏层对当前时间步的输入做处理和 W隐藏层到隐藏层对上一个时间步的记忆做处理是共享的这样可以减少模型参数使 RNN 能处理任意序列的长度RNN 工作原理解析RNN 解决了静态词向量的局限性实现了从 Type 到 Token 的跨越静态的 Type词嵌入体现在 WordVec 等模型中他们学习到的是词在词典中的静态含义相比之下RNN 的隐藏状态则实现了动态的 Token 表示即具体的与上下文紧密相关的实例RNN 代码实现上述公式中采用的是数学通用的列向量表示法输入是列向量后续的代码实现为了体现计算机内存布局的优势采用行向量表示# 数据准备 import numpy as np # (B, T, E, H) 分别表示 批次/序列长度/输入维度/隐藏维度 BE,H 11283 def prepare_inputs(): np.random.seed(42) vocab {播放:0,周杰伦:1,的:2,《稻香》:3} # 构造最小词表 tokens [播放,周杰伦,的,《稻香》] ids [vocab[t] for t in tokens] # 词向量表V,E V len(vocab) emb_table np.random.randn(V,E),astype(np.float32) # 取出序列词向量并加上 batch 维度B,T,E x_np emb_table[ids][None] return tokens,x_np基于 numpy 实现 RNN初始化首先创建一个全零的初始隐藏状态 h_prev 作为空白记忆逐帧处理使用循环遍历序列中的每一个时间步词元核心计算在循环内部实现对于输入和记忆的线性转换将二者相加后通过 tanh 激活函数得到融合了上下文状态的新隐藏状态状态更新将当前计算得到的隐藏状态赋值给 h_prev 作为下一个时间步的输入实现循环传递结果保存将每一步计算出的隐藏状态保存下来最终组合成一个包含所有时间步输出的张量def manual_rnn_numpy(x_np,U_np,W_np): B_local,T_local,_ x_np.shape h_prev np.zeros((B_Local,H),dtypenp.float32) steps [] # 按时间步来循环 for t in range(T_local): x_t x_np[:,t,:] h_t np.tanh(x_t U_np h_prev W_np) steps.append(h_t) h_prev h_t # 更新状态 return np.stack(steps,axis1),h_prevPytorch 的 nn.RNN 实现Pytorch 提供了高度封装的 nn.RNN 模块它内部完成了与我们手写版本相同的循环计算但经过了优化效率更高def pytorch_rnn_forward(x,U,W): rnn nn.RNN( input_size E, # 输入特征的维度在NLP中通常是指词嵌入的维度embedding_dim hidden_size H, num_layers 1, # RNN 的层数默认为1如果大于1会构成一个足够的 RNN nonlinearity tahn, bias False, batch_first True, # 一个非常重要的参数维度顺序默认为 false此时输入张量的形状为(T,B,E)设置为True的话是更符合直觉的(B,T,E) bidirectional False, # 是否构造一个端点 RNN默认为 false ) with torch.no_grad(): # Pytorch 内部存放的是转置后的权重 rnn.weight_ih_l0.copy_(U,T) rnn.weight_hh_l0.copy_(W,T) y,h_n rnn(x) return y,h_n.squeeze(0)数值对齐验证验证用 Numpy 手写的 RNN 和 Pytorch 官方的 nn.RNN 模块在给定相同输入和相同权重时其输出是否完全相同# 将 numpy 结果转回 Pytorch 张量 out_manual torch.from_numpy(out_manual_np) # 使用 allclose 进行浮点数精度下的严格比较 print(逐步输出一致,torch.allclose(out_manual,out_torch,atol1e-6)) # 输出 True双向循环神经网络在前面的讨论中RNN 在处理序列时信息是单向流动的就是在计算任意 t 时刻的状态时只利用了 t 时刻及以前的信息但在很多自然语言处理任务中一个词的含义往往不仅依赖于它前面的词还与它后面的词密切相关为了利用未来的信息早期的尝试是引入时间延迟即在预测 t 时刻的输出时允许模型看到 tM 时刻的输入延迟窗口 M 是一个需要人工调整的超参数太大模型难以聚焦于局部太小未来信息不足因此我们提出双向循环神经网络Bidirectional RNN, BiRNNBiRNN 是由两个完全独立的 RNN 构成并将他们叠加在一起处理信息BiRNN 包含一个正向的 RNN它按照从左到右的顺序读取输入序列从 x1 到 xn计算出一系列正向隐藏状态同时包含一个反向的 RNN它按照从右到左的顺序读取输入序列计算出一系列反向隐藏状态在任意时间步BiRNN 的最终输出是将该时间步对应的正向隐藏状态和反向隐藏状态进行拼接得到的

更多文章