NLP基石双雄:从N-Gram到BoW的终极实战指南

NLP基石双雄:从N-Gram到BoW的终极实战指南


一、N-Gram模型

1.1创建⼀个Bigram字符预测模型

image-20250220151120531

# 构建一个玩具数据集
corpus = [ "我喜欢吃苹果",
        "我喜欢吃香蕉",
        "她喜欢吃葡萄",
        "他不喜欢吃香蕉",
        "他喜欢吃苹果",
        "她喜欢吃草莓"]
# 定义一个分词函数,将文本转换为单个字符的列表
def tokenize(text):
 return [char for char in text] # 将文本拆分为字符列表
# 对每个文本进行分词,并打印出对应的单字列表
print("单字列表:") 
for text in corpus:
    tokens = tokenize(text)
    print(tokens)
单字列表:
['我', '喜', '欢', '吃', '苹', '果']
['我', '喜', '欢', '吃', '香', '蕉']
['她', '喜', '欢', '吃', '葡', '萄']
['他', '不', '喜', '欢', '吃', '香', '蕉']
['他', '喜', '欢', '吃', '苹', '果']
['她', '喜', '欢', '吃', '草', '莓']
# 定义计算 N-Gram 词频的函数
from collections import defaultdict, Counter # 导入所需库
def count_ngrams(corpus, n):
    ngrams_count = defaultdict(Counter)  # 创建一个字典,存储 N-Gram 计数
    for text in corpus:  # 遍历语料库中的每个文本
        tokens = tokenize(text)  # 对文本进行分词
        for i in range(len(tokens) - n + 1):  # 遍历分词结果,生成 N-Gram
            ngram = tuple(tokens[i:i+n])  # 创建一个 N-Gram 元组
            prefix = ngram[:-1]  # 获取 N-Gram 的前缀
            token = ngram[-1]  # 获取 N-Gram 的目标单字
            ngrams_count[prefix][token] += 1  # 更新 N-Gram 计数
    return ngrams_count
bigram_counts = count_ngrams(corpus, 2) # 计算 bigram 词频
print("bigram 词频:") # 打印 bigram 词频
for prefix, counts in bigram_counts.items():
    print("{}: {}".format("".join(prefix), dict(counts))) 
bigram 词频:
我: {'喜': 2}
喜: {'欢': 6}
欢: {'吃': 6}
吃: {'苹': 2, '香': 2, '葡': 1, '草': 1}
苹: {'果': 2}
香: {'蕉': 2}
她: {'喜': 2}
葡: {'萄': 1}
他: {'不': 1, '喜': 1}
不: {'喜': 1}
草: {'莓': 1}
# 定义计算 N-Gram 出现概率的函数
def ngram_probabilities(ngram_counts):
 ngram_probs = defaultdict(Counter) # 创建一个字典,存储 N-Gram 出现的概率
 for prefix, tokens_count in ngram_counts.items(): # 遍历 N-Gram 前缀
     total_count = sum(tokens_count.values()) # 计算当前前缀的 N-Gram 计数
     for token, count in tokens_count.items(): # 遍历每个前缀的 N-Gram
         ngram_probs[prefix][token] = count / total_count # 计算每个 N-Gram 出现的概率
 return ngram_probs
bigram_probs = ngram_probabilities(bigram_counts) # 计算 bigram 出现的概率
print("\nbigram 出现的概率 :") # 打印 bigram 概率
for prefix, probs in bigram_probs.items():
 print("{}: {}".format("".join(prefix), dict(probs)))
bigram 出现的概率 :
我: {'喜': 1.0}
喜: {'欢': 1.0}
欢: {'吃': 1.0}
吃: {'苹': 0.3333333333333333, '香': 0.3333333333333333, '葡': 0.16666666666666666, '草': 0.16666666666666666}
苹: {'果': 1.0}
香: {'蕉': 1.0}
她: {'喜': 1.0}
葡: {'萄': 1.0}
他: {'不': 0.5, '喜': 0.5}
不: {'喜': 1.0}
草: {'莓': 1.0}
# 定义生成下一个词的函数
def generate_next_token(prefix, ngram_probs):
 if not prefix in ngram_probs: # 如果前缀不在 N-Gram 中,返回 None
    return None
 next_token_probs = ngram_probs[prefix] # 获取当前前缀的下一个词的概率
 next_token = max(next_token_probs, 
                    key=next_token_probs.get) # 选择概率最大的词作为下一个词
 return next_token
# 定义生成连续文本的函数
def generate_text(prefix, ngram_probs, n, length=6):
 tokens = list(prefix) # 将前缀转换为字符列表
 for _ in range(length - len(prefix)): # 根据指定长度生成文本 
     # 获取当前前缀的下一个词
     next_token = generate_next_token(tuple(tokens[-(n-1):]), ngram_probs) 
     if not next_token: # 如果下一个词为 None,跳出循环
         break
     tokens.append(next_token) # 将下一个词添加到生成的文本中
 return "".join(tokens) # 将字符列表连接成字符串
# 输入一个前缀,生成文本
generated_text = generate_text("我", bigram_probs, 2)
print("\n 生成的文本:", generated_text) # 打印生成的文本
生成的文本: 我喜欢吃苹果

在N-Gram模型中,我们预测⼀个词出现的概率,只需考虑它前⾯的N-1个词。这样做的优点是计算简单,但缺点也很明显:它⽆法捕捉到距离较远的词之间的关系。

⽽Bag-of-Words模型(也称“词袋模型”),不考虑哪个词和哪个词临近,⽽是通过把词看作⼀袋⼦元素的⽅式来把⽂本转换为能统计的特征。

二、词袋模型

词袋模型将⽂本中的词看作⼀个个独⽴的个体,不考虑它们在句⼦中的顺序,只关⼼每个词出现的频次。

image-20250220152229499

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

image-20250220152348041

2.1.1 构建实验语料库
# 构建一个玩具数据集
corpus = ["我特别特别喜欢看电影",
        "这部电影真的是很好看的电影",
        "今天天气真好是难得的好天气",
        "我今天去看了一部电影",
        "电影院的电影都很好看"]
2.1.2 给句子分词
# 对句子进行分词
import jieba # 导入 jieba 包
# 使用 jieba.cut 进行分词,并将结果转换为列表,存储在 corpus_tokenized 中
corpus_tokenized = [list(jieba.cut(sentence)) for sentence in corpus]
2.1.3 创建词汇表
# 创建词汇表
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) # 打印词汇表
 词汇表: {'我': 0, '特别': 1, '喜欢': 2, '看': 3, '电影': 4, '这部': 5, '真的': 6, '是': 7, '很': 8, '好看': 9, '的': 10, '今天天气': 11, '真好': 12, '难得': 13, '好': 14, '天气': 15, '今天': 16, '去': 17, '了': 18, '一部': 19, '电影院': 20, '都': 21}
2.1.4 生成词袋表示
# 根据词汇表将句子转换为词袋表示
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) # 打印词袋表示
 词袋表示: 
 [[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个列表,就是词袋表示向量,向量中的每个元素表示对应词在⽂本中出现的次数。可以看到,词袋表示忽略了⽂本中词的顺序信息,仅关注词的出现频率。

2.1.5 计算余弦相似度

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

余弦相似度可⽤来衡量两个向量的相似程度。

它的值在-1到1之间,值越接近1,表示两个向量越相似;

值越接近-1,表示两个向量越不相似;

当值接近0时,表示两个向量之间没有明显的相似性。
c o s i n e _ s i m i l a r i t y ( A , B ) = ( A ⋅ B ) / ( ∣ ∣ A ∣ ∣ ⋆ ∣ ∣ B ∣ ∣ ) cosine\_similarity(A,B)=(A\cdotp B)/(||A||^{\star}||B||) cosine_similarity(A,B)=(AB)/(∣∣A∣∣B∣∣)

# 导入 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])
2.1.6 可视化余弦相似度
# 导入 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() # 显示图形

image-20250220153410155

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

y江江江江

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值