week05_nlp大模型训练·词向量&文本向量

1、词向量训练

1.1 CBOW(两边预测中间)

一、CBOW 基本概念

CBOW 是一种用于生成词向量的方法,属于神经网络语言模型的一种。其核心思想是根据上下文来预测中心词。在 CBOW 中,输入是目标词的上下文词汇,输出是该目标词。

二、CBOW 的网络结构

1、输入层

  • 对于给定的一个窗口大小 (通常是一个奇数,例如n=5 ),考虑目标词wt及其前后各(n-1)/2个词作为上下文。假设我们的词汇表大小为V,每个词都可以表示为一个V维的 one-hot 向量。
  • 对于上下文词(其中 ),它们的 one-hot 向量被输入到网络中。

2、投影层

  • 输入层的多个 one-hot 向量会被映射到一个投影层。
  • 对于每个输入的 one-hot 向量,它会激活隐藏层中的一个神经元,而隐藏层的权重矩阵W(维度为V x N ,其中N是词向量的维度)将这些输入进行加权求和,得到一个N维的向量。

从数学上看,如果xi是第i个上下文词的 one-hot 向量,那么投影层的向量h可以表示为:

其中C = n-1是上下文词的数量。

3、隐藏层

  • 投影层的结果作为隐藏层的输入,隐藏层通常不进行非线性变换,直接将结果传递给输出层。

4、输出层

  • 输出层是一个 softmax 层,其神经元的数量等于词汇表的大小V 。

  • 输出层的权重矩阵为 (维度为 N x V),使用 softmax 函数将隐藏层的输出转换为概率分布:

    其中

三、训练过程

  1. 损失函数

    • 通常使用交叉熵损失函数:

    • 其中 yj是真实目标词的 one-hot 向量, p(wj)是预测词的概率。

  2. 优化算法

    • 常用的优化算法是随机梯度下降(SGD)或其变种,如 Adagrad、Adadelta 等。
    • 在训练过程中,通过反向传播算法更新权重矩阵 和 ,以最小化损失函数。

四、CBOW 的优点

  1. 考虑上下文信息

    • CBOW 利用了上下文信息来预测中心词,能够捕捉到词与词之间的语义关系。
  2. 计算效率

    • 对于每个训练样本,由于使用了上下文词的平均作为输入,CBOW 比 Skip-gram 在训练时计算量相对较小,尤其是在处理大规模语料库时,CBOW 可以更快地训练出较为不错的词向量。

五、CBOW 的缺点

  1. 对低频词不敏感

CBOW 侧重于根据上下文预测中心词,对于低频词,由于它们在语料库中出现的频率低,在训练过程中得到的学习机会相对较少,因此生成的词向量可能不能很好地表示低频词的语义信息。

六、应用场景

  1. 词向量初始化

    • CBOW 可以为下游的 NLP 任务提供预训练的词向量 ,如文本分类、情感分析、命名实体识别等。将文本中的词替换为其对应的 CBOW 词向量,可以将文本表示为一个向量序列,为后续任务提供良好的输入表示。
  2. 词相似度计算

    • 训练好的词向量可以计算词与词之间的相似度,例如使用余弦相似度:

七、与 Skip-gram 的对比

  • Skip-gram 与 CBOW 的区别在于,Skip-gram 是根据中心词预测上下文词,而 CBOW 是根据上下文词预测中心词。Skip-gram 更适合处理少量数据和低频词,因为它为每个中心词 - 上下文词对都进行单独的训练而 CBOW 更适合处理大规模数据,因为它在计算上更高效。

八、示例代码(使用 gensim)

python 复制代码
from gensim.models import Word2Vec
sentences = [["I", "love", "natural", "language", "processing"],
            ["Word", "embeddings", "are", "useful"],
            ["This", "is", "an", "example", "sentence"]]
# CBOW 模型训练,window 是窗口大小,min_count 是最小词频,sg=0 表示 CBOW 算法
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, sg=0)
# 获取词向量
vector = model.wv['love']
print(vector)
python 复制代码
#coding:utf8

import torch
import torch.nn as nn
import numpy as np


"""
基于pytorch的词向量CBOW
模型部分
"""

class CBOW(nn.Module):
    def __init__(self, vocab_size, embedding_size, window_length):
        super(CBOW, self).__init__()
        self.word_vectors = nn.Embedding(vocab_size, embedding_size)
        self.pooling = nn.AvgPool1d(window_length)
        self.projection_layer = nn.Linear(embedding_size, vocab_size)

    def forward(self, context):
        context_embedding = self.word_vectors(context)  #batch_size * max_length * embedding size  1*4*4
        #transpose: batch_size * embedding size * max_length -> pool: batch_size * embedding_size * 1 -> squeeze:batch_size * embeddig_size
        context_embedding = self.pooling(context_embedding.transpose(1, 2)).squeeze()
        #batch_size * embeddig_size -> batch_size * vocab_size
        pred = self.projection_layer(context_embedding)
        return pred

vocab_size = 8  #词表大小
embedding_size = 4  #人为指定的向量维度
window_length = 4  #窗口长度
model = CBOW(vocab_size, embedding_size, window_length)
#假如选取一个词窗口【1,2,3,4,5】· 
context = torch.LongTensor([[1,2,4,5]]) #输入1,2,4,5, 预期输出3, 两边预测中间
pred = model(context)
print("预测值:", pred)



# print("词向量矩阵")
# print(model.state_dict()["word_vectors.weight"])

1.2 简单词向量模型(自主选择 CBOW or SkipGram 方法)

python 复制代码
import json
import jieba
import numpy as np
import gensim
from gensim.models import Word2Vec
from collections import defaultdict

'''
词向量模型的简单实现
'''

#训练模型
#corpus: [["cat", "say", "meow"], ["dog", "say", "woof"]]
#corpus: [["今天", "天气", "不错"], ["你", "好", "吗"]]
#dim指定词向量的维度,如100
def train_word2vec_model(corpus, dim):
    model = Word2Vec(corpus, vector_size=dim, sg=1)
    model.save("model.w2v")
    return model

#输入模型文件路径
#加载训练好的模型
def load_word2vec_model(path):
    model = Word2Vec.load(path)
    return model

def main():
    sentences = []
    with open("corpus.txt", encoding="utf8") as f:
        for line in f:
            sentences.append(jieba.lcut(line))
    model = train_word2vec_model(sentences, 128)
    return model

if __name__ == "__main__":
    # model = main()  #训练
    model = load_word2vec_model("model.w2v")  #加载
 
    print(model.wv.most_similar(positive=["男人", "母亲"], negative=["女人"])) #类比

    while True:  #找相似
        string = input("input:")
        try:
            print(model.wv.most_similar(string))
        except KeyError:
            print("输入词不存在")

函数部分

train_word2vec_model 函数:

  • 功能:使用 Word2Vec 类训练一个词向量模型。
  • 参数:
    • corpus:输入的语料库,应该是一个由词汇列表组成的列表,例如 [["cat", "say", "meow"], ["dog", "say", "woof"]]
    • dim:词向量的维度,例如 128
  • 实现细节:
    • model = Word2Vec(corpus, vector_size=dim, sg=1):创建一个 Word2Vec 模型,其中 vector_size 表示词向量的维度,sg=1 表示使用 Skip-gram 算法进行训练(sg=0 表示使用 CBOW 算法)。
    • model.save("model.w2v"):将训练好的模型保存到文件 model.w2v 中。

load_word2vec_model 函数:

  • 功能:从文件中加载已经训练好的 Word2Vec 模型。
  • 参数:
    • path:存储 Word2Vec 模型的文件路径,例如 "model.w2v"
  • 实现细节:
    • model = Word2Vec.load(path):从指定的文件路径加载 Word2Vec 模型。

main 函数:

  • 实现细节:
    • sentences = []:初始化一个空列表用于存储分词后的句子。
    • with open("corpus.txt", encoding="utf8") as f:以 UTF-8 编码打开文件 corpus.txt
    • for line in f: sentences.append(jieba.lcut(line)):逐行读取文件,并使用 jieba.lcut 对每行进行分词,将分词结果添加到 sentences 列表中。
    • model = train_word2vec_model(sentences, 128):调用 train_word2vec_model 函数,使用分词后的 sentences 作为语料库,维度为 128 训练词向量模型。

1.3 基于pytorch的语言模型

核心算法:

复制代码
y = Wx + Utanh(hx+d) + b
python 复制代码
#coding:utf8

import torch
import torch.nn as nn
import numpy as np


"""
基于pytorch的语言模型
与基于窗口的词向量训练本质上非常接近
只是输入输出的预期不同
不使用向量的加和平均,而是直接拼接起来
"""

class LanguageModel(nn.Module):
    def __init__(self, vocab_size, max_len, embedding_size, hidden_size):
        super(LanguageModel, self).__init__()
        self.word_vectors = nn.Embedding(vocab_size, embedding_size)
        self.inner_projection_layer = nn.Linear(embedding_size * max_len, hidden_size)
        self.outter_projection_layer = nn.Linear(hidden_size, hidden_size)
        self.x_projection_layer = nn.Linear(embedding_size * max_len, hidden_size)
        self.projection_layer = nn.Linear(hidden_size, vocab_size)

    def forward(self, context):
        #context shape = batch_size, max_length
        context_embedding = self.word_vectors(context)  #output shape = batch_size, max_length, embedding_size
        #总体计算 y = b+Wx+Utanh(d+Hx), 其中x为每个词向量的拼接
        #词向量的拼接
        x = context_embedding.view(context_embedding.shape[0], -1) #shape = batch_size, max_length*embedding_size
        #hx + d
        inner_projection = self.inner_projection_layer(x)  #shape = batch_size, hidden_size
        #tanh(hx+d)
        inner_projection = torch.tanh(inner_projection)    #shape = batch_size, hidden_size
        #U * tanh(hx+d) + b
        outter_project = self.outter_projection_layer(inner_projection)  # shape = batch_size, hidden_size
        #Wx
        x_projection = self.x_projection_layer(x)    #shape = batch_size, hidden_size
        #y = Wx + Utanh(hx+d) + b
        y = x_projection + outter_project  #shape = batch_size, hidden_size
        #softmax后输出预测概率, 训练的目标是让y_pred对应到字表中某个字
        y_pred = torch.softmax(y, dim=-1)  #shape = batch_size, hidden_size
        return y_pred

vocab_size = 8  #词表大小
embedding_size = 5  #人为指定的向量维度
max_len = 4 #输入长度
hidden_size = vocab_size  #由于最终的输出维度应当是字表大小的,所以这里hidden_size = vocab_size
model = LanguageModel(vocab_size, max_len, embedding_size, hidden_size)
#假如选取一个文本窗口"天王盖地虎"
#输入:"天王盖地" ---> 输出:"虎"
#假设词表embedding中, 天王盖地虎 对应位置 12345
context = torch.LongTensor([[1,2,3,4]])  #shape = 1, 4  batch_size = 1, max_length = 4
pred = model(context)
print("预测值:", pred)
print("loss可以使用交叉熵计算:", nn.functional.cross_entropy(pred, torch.LongTensor([5])))


print("词向量矩阵")
matrix = model.state_dict()["word_vectors.weight"]

print(matrix.shape)  #vocab_size, embedding_size
print(matrix)

2、KMeans(词向量的应用------聚类)

1)将一句话或一段文本分成若干个词

2)找到每个词对应的词向量

3)所有词向量加和求平均或通过各种网络模型,得到文本向量

4)使用文本向量计算相似度或进行聚类

KMeans

随机选择k个点作为初始质心

repeat

将每个点指派到最近的质心,形成k个簇

重新计算每个簇的质心

until

质心不发生变化

KMeans优点

1.速度很快,可以支持很大量的数据

2.样本均匀特征明显的情况下,效果不错

KMeans缺点

1.人为设定聚类数量

2.初始化中心影响效果,导致结果不稳定

3.对于个别特殊样本敏感,会大幅影响聚类中心位置

4.不适合多分类或样本较为离散的数据

KMeans一些使用技巧:

1.先设定较多的聚类类别

2.聚类结束后计算类内平均距离

3.排序后,舍弃类内平均距离较长的类别

4.计算距离时可以尝试欧式距离、余弦距离或其他距离

5.短文本的聚类记得先去重,以及其他预处理

代码实例

python 复制代码
import numpy as np
import random
import sys
'''
Kmeans算法实现
原文链接:https://blog.csdn.net/qingchedeyongqi/article/details/116806277
'''

class KMeansClusterer:  # k均值聚类
    def __init__(self, ndarray, cluster_num):
        self.ndarray = ndarray
        self.cluster_num = cluster_num
        self.points = self.__pick_start_point(ndarray, cluster_num)

    def cluster(self):
        result = []
        for i in range(self.cluster_num):
            result.append([])
        for item in self.ndarray:
            distance_min = sys.maxsize
            index = -1
            for i in range(len(self.points)):
                distance = self.__distance(item, self.points[i])
                if distance < distance_min:
                    distance_min = distance
                    index = i
            result[index] = result[index] + [item.tolist()]
        new_center = []
        for item in result:
            new_center.append(self.__center(item).tolist())
        # 中心点未改变,说明达到稳态,结束递归
        if (self.points == new_center).all():
            sum = self.__sumdis(result)
            return result, self.points, sum
        self.points = np.array(new_center)
        return self.cluster()

    def __sumdis(self,result):
        #计算总距离和
        sum=0
        for i in range(len(self.points)):
            for j in range(len(result[i])):
                sum+=self.__distance(result[i][j],self.points[i])
        return sum

    def __center(self, list):
        # 计算每一列的平均值
        return np.array(list).mean(axis=0)

    def __distance(self, p1, p2):
        #计算两点间距
        tmp = 0
        for i in range(len(p1)):
            tmp += pow(p1[i] - p2[i], 2)
        return pow(tmp, 0.5)

    def __pick_start_point(self, ndarray, cluster_num):
        if cluster_num < 0 or cluster_num > ndarray.shape[0]:
            raise Exception("簇数设置有误")
        # 取点的下标
        indexes = random.sample(np.arange(0, ndarray.shape[0], step=1).tolist(), cluster_num)
        points = []
        for index in indexes:
            points.append(ndarray[index].tolist())
        return np.array(points)

x = np.random.rand(100, 8)
kmeans = KMeansClusterer(x, 10)
result, centers, distances = kmeans.cluster()
print('result:', result)
print('centers:', centers)
print('distances:', distances)
python 复制代码
#!/usr/bin/env python3  
#coding: utf-8

#基于训练好的词向量模型进行聚类
#聚类采用Kmeans算法
import math
import re
import json
import jieba
import numpy as np
from gensim.models import Word2Vec
from sklearn.cluster import KMeans
from collections import defaultdict

#输入模型文件路径
#加载训练好的模型
def load_word2vec_model(path):
    model = Word2Vec.load(path)
    return model

def load_sentence(path):
    sentences = set()
    with open(path, encoding="utf8") as f:
        for line in f:
            sentence = line.strip()
            sentences.add(" ".join(jieba.cut(sentence)))
    print("获取句子数量:", len(sentences))
    return sentences

#将文本向量化
def sentences_to_vectors(sentences, model):
    vectors = []
    for sentence in sentences:
        words = sentence.split()  #sentence是分好词的,空格分开
        vector = np.zeros(model.vector_size)
        #所有词的向量相加求平均,作为句子向量
        for word in words:
            try:
                vector += model.wv[word]
            except KeyError:
                #部分词在训练中未出现,用全0向量代替
                vector += np.zeros(model.vector_size)
        vectors.append(vector / len(words))
    return np.array(vectors)


def main():
    model = load_word2vec_model(r"model.w2v") #加载词向量模型
    sentences = load_sentence("titles.txt")  #加载所有标题
    vectors = sentences_to_vectors(sentences, model)   #将所有标题向量化

    n_clusters = int(math.sqrt(len(sentences)))  #指定聚类数量
    print("指定聚类数量:", n_clusters)
    kmeans = KMeans(n_clusters)  #定义一个kmeans计算类
    kmeans.fit(vectors)          #进行聚类计算

    sentence_label_dict = defaultdict(list)
    for sentence, label in zip(sentences, kmeans.labels_):  #取出句子和标签
        sentence_label_dict[label].append(sentence)         #同标签的放到一起
    for label, sentences in sentence_label_dict.items():
        print("cluster %s :" % label)
        for i in range(min(10, len(sentences))):  #随便打印几个,太多了看不过来
            print(sentences[i].replace(" ", ""))
        print("---------")

if __name__ == "__main__":
    main()
python 复制代码
#coding: utf-8

#基于训练好的词向量模型进行聚类
#聚类采用Kmeans算法
#Kmeans基础上实现按照类内距离排序
import math
import re
import json
import jieba
import numpy as np
from gensim.models import Word2Vec
from sklearn.cluster import KMeans
from collections import defaultdict

#输入模型文件路径
#加载训练好的模型
def load_word2vec_model(path):
    model = Word2Vec.load(path)
    return model

def load_sentence(path):
    sentences = set()
    with open(path, encoding="utf8") as f:
        for line in f:
            sentence = line.strip()
            sentences.add(" ".join(jieba.cut(sentence)))
    print("获取句子数量:", len(sentences))
    return sentences

#将文本向量化
def sentences_to_vectors(sentences, model):
    vectors = []
    for sentence in sentences:
        words = sentence.split()  #sentence是分好词的,空格分开
        vector = np.zeros(model.vector_size)
        #所有词的向量相加求平均,作为句子向量
        for word in words:
            try:
                vector += model.wv[word]
            except KeyError:
                #部分词在训练中未出现,用全0向量代替
                vector += np.zeros(model.vector_size)
        vectors.append(vector / len(words))
    return np.array(vectors)


def main():
    model = load_word2vec_model("model.w2v") #加载词向量模型
    sentences = load_sentence("titles.txt")  #加载所有标题
    vectors = sentences_to_vectors(sentences, model)   #将所有标题向量化

    n_clusters = int(math.sqrt(len(sentences)))  #指定聚类数量
    print("指定聚类数量:", n_clusters)
    kmeans = KMeans(n_clusters)  #定义一个kmeans计算类
    kmeans.fit(vectors)          #进行聚类计算

    sentence_label_dict = defaultdict(list)
    for sentence, label in zip(sentences, kmeans.labels_):  #取出句子和标签
        sentence_label_dict[label].append(sentence)         #同标签的放到一起

    #计算类内距离
    density_dict = defaultdict(list)
    for vector_index, label in enumerate(kmeans.labels_):
        vector = vectors[vector_index]   #某句话的向量
        center = kmeans.cluster_centers_[label]  #对应的类别中心向量
        distance = cosine_distance(vector, center)  #计算距离
        density_dict[label].append(distance)    #保存下来
    for label, distance_list in density_dict.items():
        density_dict[label] = np.mean(distance_list)   #对于每一类,将类内所有文本到中心的向量余弦值取平均
    density_order = sorted(density_dict.items(), key=lambda x: x[1], reverse=True)  #按照平均距离排序,向量夹角余弦值越接近1,距离越小

    #按照余弦距离顺序输出
    for label, avg_distance in density_order:
        print("cluster %s , avg distance %s: " % (label, avg_distance))
        sentences = sentence_label_dict[label]
        for i in range(min(10, len(sentences))):  #随便打印几个,太多了看不过来
            print(sentences[i].replace(" ", ""))
        print("---------")

#向量余弦距离
def cosine_distance(vec1, vec2):
    vec1 = vec1 / np.sqrt(np.sum(np.square(vec1)))  #A/|A|
    vec2 = vec2 / np.sqrt(np.sum(np.square(vec2)))  #B/|B|
    return np.sum(vec1 * vec2)

#欧式距离
def eculid_distance(vec1, vec2):
    return np.sqrt((np.sum(np.square(vec1 - vec2))))

if __name__ == "__main__":
    main()

3、 词向量训练总结

一、根据词与词之间关系的某种假设,制定训练目标

二、设计模型,以词向量为输入

三、随机初始化词向量,开始训练

四、训练过程中词向量作为参数不断调整,获取一定的语义信息

五、使用训练好的词向量做下游任务

词向量总结:

1.质变:将离散的字符 转化为连续的数值

2.通过向量的相似度代表语义的相似度

3.词向量的训练基于很多不完全正确的假设,但是据此训练的词向量是有意义的

4.使用无标注的文本的一种好方法

词向量存在的问题:

1)词向量是"静态"的每个词使用固定向量,没有考虑前后文

2)一词多义的情况。西瓜 - 苹果 - 华为

3)影响效果的因素非常多

维度选择、随机初始化、skip-gram/cbow/glove、分词质量、词频截断、未登录词、窗口大小、迭代轮数、停止条件、语料质量等

4)没有好的直接评价指标。常需要用下游任务来评价

相关推荐
WBingJ25 分钟前
机器学习基础-机器学习的常用学习方法
深度学习·神经网络·机器学习
小李学AI35 分钟前
基于YOLOv8的恶劣天气目标检测系统
人工智能·深度学习·神经网络·yolo·目标检测·机器学习·计算机视觉
有Li1 小时前
对比式机器学习揭示了跨物种共享和特异性的脑功能结构|文献速递-视觉大模型医疗图像应用
人工智能·机器学习
GIS数据转换器1 小时前
低空经济新动力:无人机航测技术的普及与应用
大数据·人工智能·物联网·无人机·智慧城市
wcyd1 小时前
如何使用Python生成词云图:结合`wordcloud`、`imageio`、`collections`和`jieba`分词模块
开发语言·python·信息可视化
ONE_PUNCH_Ge1 小时前
Python3 正则表达式
python
一水鉴天1 小时前
智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之6
人工智能
Apache Flink1 小时前
Paimon 1.0: Unified Lake Format for Data + AI
人工智能
大雄野比2 小时前
静态库封装之ComFile类
运维·服务器·算法
小鸡毛程序员2 小时前
B3842 [GESP202306 三级] 春游
数据结构·算法