GPT图解:大模型是怎样构建的
上QQ阅读APP看书,第一时间看更新

1.5 用词袋模型计算文本相似度

下面的程序将用词袋模型比较一些文本的相似度,这个程序的结构如下。

用词袋模型比较文本相似度的程序结构

第1步 构建实验语料库

定义一个简单的数据集,作为我们的实验语料库。

  In  

# 构建一个玩具数据集
corpus = ["我特别特别喜欢看电影",
        "这部电影真的是很好看的电影",
        "今天天气真好是难得的好天气",
        "我今天去看了一部电影",
        "电影院的电影都很好看"]

第2步 给句子分词

用jieba包对这些句子进行分词。和前面N-Gram的示例略有不同,这个例子中以词为单位处理语料,而不是“字”。

  In  

# 对句子进行分词
import jieba # 导入jieba包
# 使用jieba.cut进行分词,并将结果转换为列表,存储在corpus_tokenized中
corpus_tokenized = [list(jieba.cut(sentence)) for sentence in corpus]

第3步 创建词汇表

根据分词结果,为语料库创建一个完整的词汇表,并显示这个词汇表。

  In  

# 创建词汇表
word_dict = {} # 初始化词汇表
# 遍历分词后的语料库
for sentence in corpus_tokenized:
    for word in sentence:
        # 如果词汇表中没有该词,则将其添加到词汇表中
        if word not in word_dict:
            word_dict[word] = len(word_dict) #分配当前词汇表索引
print("词汇表:", word_dict) # 打印词汇表

 Out 

词汇表: {'我': 0, '特别': 1, '喜欢': 2, '看': 3, '电影': 4, '这部': 5, '真的': 6, '是': 7, '很': 8, '好看': 9, '的': 10, '今天天气': 11, '真好': 12, '难得': 13, '好': 14, '天气': 15, '今天': 16, '去': 17, '了': 18, '一部': 19, '电影院': 20, '都': 21}

第4步 生成词袋表示

根据这个词汇表将句子转换为词袋表示。

  In  

# 根据词汇表将句子转换为词袋表示
bow_vectors = [] # 初始化词袋表示
# 遍历分词后的语料库
for sentence in corpus_tokenized:
    # 初始化一个全0向量,其长度等于词汇表大小
    sentence_vector = [0] * len(word_dict)
    for word in sentence:
        # 给对应词索引位置上的数加1,表示该词在当前句子中出现了一次
        sentence_vector[word_dict[word]] += 1
    # 将当前句子的词袋向量添加到向量列表中
    bow_vectors.append(sentence_vector)
print("词袋表示:", bow_vectors) # 打印词袋表示

 Out 

词袋表示: [[1, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], 
[1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0], 
[0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]

这里,我们得到了5个Python列表,分别对应语料库中的5句话。这5个列表,就是词袋表示向量,向量中的每个元素表示对应词在文本中出现的次数。向量的长度等于词汇表中的词的数量,这里我们一共有22个词。我们可以看到,词袋表示忽略了文本中词的顺序信息,仅关注词的出现频率。

例如,我们的词汇表为{'我': 0, '特别': 1, '喜欢': 2, '看': 3, '电影': 4},那么句子“我特别特别喜欢看电影”的词袋表示为[1, 2, 1, 1, 1],即“我”“喜欢”“看”和“电影”这些词在句子中各出现了一次,而“特别”则出现了两次。

咖哥发言

我们经常听说的One-Hot编码也可以看作一种特殊的词袋表示。在One-Hot编码中,每个词都对应一个只包含一个1,其他元素全为0的向量,1的位置与该词在词汇表中的索引对应。在单词独立成句的情况下,词袋表示就成了One-Hot编码。比如上面的语料库中,“我”这个单词如果独立成句,则该句子的词袋表示为[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],这完全等价于“我”在当前词汇表中的One-Hot编码。

第5步 计算余弦相似度

计算余弦相似度(Cosine Similarity),衡量两个文本向量的相似性。

余弦相似度可用来衡量两个向量的相似程度。它的值在-1到1之间,值越接近1,表示两个向量越相似;值越接近-1,表示两个向量越不相似;当值接近0时,表示两个向量之间没有明显的相似性。

余弦相似度的计算公式如下:

cosine_similarity(A, B) = (A·B) / (||A|| * ||B||)

其中,(A·B)表示向量A和向量B的点积,||A||和||B||分别表示向量A和向量B的范数(长度)。在文本处理中,我们通常使用余弦相似度来衡量两个文本在语义上的相似程度。对于词袋表示的文本向量,使用余弦相似度计算文本之间的相似程度可以减少句子长度差异带来的影响。

咖哥发言

余弦相似度和向量距离(Vector Distance)都可以衡量两个向量之间的相似性。余弦相似度关注向量之间的角度,而不是它们之间的距离,其取值范围在 -1(完全相反) 到 1(完全相同) 之间。向量距离关注向量之间的实际距离,通常使用欧几里得距离(Euclidean Distance)来计算。两个向量越接近,它们的距离越小。

如果要衡量两个向量的相似性,而不关心它们的大小,那么余弦相似度会更合适。因此,余弦相似度通常用于衡量文本、图像等高维数据的相似性,因为在这些场景下,关注向量的方向关系通常比关注距离更有意义。而在一些需要计算实际距离的应用场景,如聚类分析、推荐系统等,向量距离会更合适。

本例中,我们编写一个函数来计算余弦相似度,然后计算每两个句子之间的余弦相似度。

  In  

# 导入numpy库,用于计算余弦相似度
import numpy as np 
# 定义余弦相似度函数
def cosine_similarity(vec1, vec2):
    dot_product = np.dot(vec1, vec2) # 计算向量vec1和vec2的点积
    norm_a = np.linalg.norm(vec1) # 计算向量vec1的范数
    norm_b = np.linalg.norm(vec2) # 计算向量vec2的范数   
    return dot_product / (norm_a * norm_b) # 返回余弦相似度
# 初始化一个全0矩阵,用于存储余弦相似度
similarity_matrix = np.zeros((len(corpus), len(corpus)))
# 计算每两个句子之间的余弦相似度
for i in range(len(corpus)):
    for j in range(len(corpus)):
        similarity_matrix[i][j] = cosine_similarity(bow_vectors[i], 
                                                    bow_vectors[j])

第6步 可视化余弦相似度

使用matplotlib可视化句子和句子之间的相似度。

  In  

# 导入matplotlib库,用于可视化余弦相似度矩阵
import matplotlib.pyplot as plt
plt.rcParams["font.family"]=['SimHei'] # 用来设定字体样式
plt.rcParams['font.sans-serif']=['SimHei'] # 用来设定无衬线字体样式
plt.rcParams['axes.unicode_minus']=False # 用来正常显示负号
fig, ax = plt.subplots() # 创建一个绘图对象
# 使用matshow函数绘制余弦相似度矩阵,颜色使用蓝色调
cax = ax.matshow(similarity_matrix, cmap=plt.cm.Blues)
fig.colorbar(cax) # 条形图颜色映射
ax.set_xticks(range(len(corpus))) # x轴刻度
ax.set_yticks(range(len(corpus))) # y轴刻度
ax.set_xticklabels(corpus, rotation=45, ha='left') # 刻度标签 
ax.set_yticklabels(corpus) # 刻度标签为原始句子
plt.show() # 显示图形

 Out 

矩阵图中每个单元格表示两个句子之间的余弦相似度,颜色越深,句子在语义上越相似。例如,“这部电影真的是很好看的电影”和“电影院的电影都很好看”交叉处的单元格颜色相对较深,说明它们具有较高的余弦相似度,这意味着它们在语义上较为相似。

小冰:词袋模型的确不错!不过,咖哥,如果不出意外的话,你现在一定会告诉我这个模型有哪些不足之处,后续的研究人员又是如何改进的吧?

咖哥:没错,小冰。词袋模型是早期的一种模型,相对简单,存在两个主要问题:第一,它使用高维稀疏向量来表示文本,每个单词对应词汇表中的一个维度。这导致模型更适用于高维空间,而且计算效率低。第二,词袋模型在表示单词时忽略了它们在文本中的上下文信息。该模型无法捕捉单词之间的语义关系,因为单词在向量空间中的相对位置没有意义。

所以你看,词袋模型并没有像N-Gram那样,将连续的几个单词放在一起考虑,因此也就缺乏相邻上下文的语言信息。

因此,NLP领域的科学家们逐渐从回归N-Gram的思路入手,去研究如何使用低维密集向量表示单词,同时尽量通过 N-Gram这种子序列来捕捉单词和单词直接的上下文关系。这就是下节课中我要给你讲的词向量。