感谢上一期能够进入csdn“每日推荐看”,那必然带着热情写下第二期《从n-gram到TFIDF》,这里引入一本《Speach and Language Processing》第三版翻译版本(语音与语言处理(SLP)),前半部分写的很好!里面连编辑距离(海明距离)都讲了,所以算很详细的了。那本期末尾留一个坑,利用编辑距离计算文本相似度!
上一节精彩回顾 原文链接
- 我们学习了词袋模型,并且仅使用了jieba库就完成了两段文本的相似度的比较
上一节todo
- 做jieba的详细介绍。目标:能制作自己需要的词典,让jieba能根据自身需求进行“改变”。
第二节 n-gram
基本概念
后者的举例确实生动形象一些。
理解
将我的理解通俗易懂的讲解一波:理解n-gram,那必然得从1-gram,2-gram,3-gram开始入手,1-gram就是指one by one,一个词一个词的统计,我们接着用上一节的举例:
1-gram
- 我喜欢看电影
用单词出现次数来表示:【“我”:1.“喜欢”:1,“看”:1,“电影”,1】 - 我喜欢看电影尤其好看的电影(为了方便演示,我去了标点)
用单词出现次数来表示:【“我”:1,“喜欢”:1,“看”:1,“电影”:2,“尤其”:1,“好看的”:1】
对于中文,其实这个词的概念是模糊的,英文中,for 循环就能轻易做到,而我们恰恰多出来的,就是前文中用jieba库让它分成词(特指:自然语言处理的最小单位)
2-gram(Bigram)
- 我喜欢看电影。
用2-词片段出现次数来表示:【“我喜欢”:1.“喜欢看”:1,“看电影”:1】 - 我喜欢看电影尤其好看的电影。
用2-词片段出现次数来表示:【“我喜欢”:1,“喜欢看”:1,“看电影”:1,“电影尤其”:1,”尤其好看的”:1,“好看的电影”:1】
显然,在以两个词为切片的情况下,“看电影”与“好看的电影”从有一半相似,变成了完全不同的两个内容,思考一下,这合理吗?,
- 从语法层面,一个是动词词组,一个是名词词组,分成两个完全不同的内容是合理的”。
- 从语义层,“看电影”与“好看的电影”仿佛又有千丝万缕的关系。
n-gram练习
input 一段文本
input n
output n-gram切片后的文本
#todo
大模型给的参考案例:
import jieba # 导入jieba库
def generate_2grams(words): # 定义一个名为generate_2grams的函数,用于生成2-gram序列
ngrams = [] # 初始化一个空列表ngrams,用于存储2-gram序列
for i in range(len(words) - 1): # 对于单词列表中的每个单词,执行以下操作
ngrams.append((words[i], words[i+1])) # 将当前单词和下一个单词组成一个元组,并将其添加到ngrams列表中
return ngrams # 返回生成的2-gram序列
text = "我爱自然语言处理技术" # 定义要进行分词的文本
words = jieba.lcut(text) # 使用jieba库对文本进行分词,并将结果存储在变量words中
ngrams = generate_2grams(words) # 调用generate_2grams函数生成2-gram序列,并将结果存储在变量ngrams中
print(list(ngrams)) # 将2-gram序列转换为列表类型并打印出来
inscode给的案例
import jieba
text = "今天天气很好,适合出去散步"
tokens = list(jieba.cut(text)) # 使用jieba的分词工具对文本进行分词,并将结果转换为列表
trigrams = zip(tokens, tokens[1:], tokens[2:]) # 使用zip函数将列表中三个连续元素组成一个三元组
for trigram in trigrams:
print(trigram)
应用
easy项目案例(哈哈哈哈,自创easy项目)
鲁迅风格的一句话补全。
准备:一个txt文件,n-gram基础知识(上文)
参考:
输入:世上本没有路
输出:世上本没有路,走的人多了也便成了路。
#todo 下午写
大模型给的代码案例,可以读一个文件夹中的所有txt文件等,等于把easy项目做成了easy大项目咯!
import jieba
from collections import Counter
import os
# 定义一个函数,用于从文件夹中获取文件列表
def get_file_list(path):
file_list = []
for root, dirs, files in os.walk(path):
for file in files:
if file.endswith('.txt'): # 只获取以。txt结尾的文件
file_list.append(os.path.join(root, file))
return file_list
# 定义一个函数,用于从文件中提取文本内容
def extract_text(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
text = f.read()
return text
# 定义一个函数,用于生成n-gram序列并统计每个n-gram出现的次数
def generate_ngrams(text, n):
ngram_sequences = list(ngrams(text.split(), n))
ngram_counts = Counter(ngram_sequences)
return ngram_counts
# 定义一个函数,用于找到出现次数最多的n-gram序列并补全缺失的单词
def complete_ngram(ngram, text):
ngram_list = list(ngram)
for i in range(len(ngram_list)-1):
if ngram_list[i] not in text:
ngram_list[i] = '*' # 用星号代替缺失的单词
return tuple(ngram_list)
# 定义一个函数,用于将补全后的句子输出到控制台或文件中
def print_result(result, output_file=None):
if output_file is not None:
with open(output_file, 'w', encoding='utf-8') as f:
f.write("补全后的句子: " + str(result) + "\n")
print("补全后的句子已保存到文件中")
else:
print("补全后的句子:", result)
# 定义一个函数,用于读取鲁迅全集并生成文本内容和n-gram序列
def read_luxun_books():
luxun_books_path = "path/to/luxun/books" # 请替换为实际路径
ngrams = {} # 将所有书籍的文本内容和n-gram序列存储在一个字典中,方便后续处理和比较
for book in os.listdir(luxun_books_path): # 对于每个书籍文件进行处理
book_path = os.path.join(luxun_books_path, book) # 获取书籍文件的完整路径
if book.endswith('.txt'): # 只获取以。txt结尾的文件
text = extract_text(book_path) # 从文件中提取文本内容
ngram_counts = generate_ngrams(text, n=3) # 生成3-gram序列并统计每个序列出现的次数
ngrams[book] = (tuple(ngram), ngram_counts) # 将当前书籍的文本内容和3-gram序列存储在字典中,方便后续处理和比较
max_count = max([v[1]['*'] for v in ngrams.values()]) # 找到出现次数最多的单词出现次数,即缺失单词最多的情况
max_book = [k for k, v in ngrams.items() if v[1]['*'] == max_count][0] # 找到出现次数最多的单词所在的书籍名称,即缺失单词最多的书籍名称
max_ngram = [v[0] for v in ngrams[max_book]] # 将缺失单词最多的书籍的所有3-gram序列存储在一个列表中,方便进行补全操作
max_ngram.sort() # 对3-gram序列进行排序,以便后面的处理操作可以按照一定的顺序进行
max_ngram = max([v for v in max_ngram if v != '*']) # 将所有用星号代替的单词删除,只保留需要补全的单词列表中的单词
max_result = complete_ngram(tuple('*'.join(max_ngram)), text) # 将缺失单词列表中的单词按照原来的顺序组合成一个新的3-gram序列,然后调用complete_ngram函数进行补全操作,得到最终结果
print("鲁迅全集中缺失单词最多的情况是:") # 在控制台输出提示信息
print("缺失单词最多的书籍名称是:", max_book) # 在控制台输出缺失单词最多的书籍名称和对应的3-gram序列列表
print("缺失单词最多的情况补全后的结果是:", max_result) # 在控制台输出补全后的结果
print("将补全后的句子输出到控制台或文件中") # 在控制台输出提示信息,告诉用户可以将结果输出到控制台或文件中进行查看或保存操作
InsCode 提供的案例,深得我心!
import jieba
import random
# 读取文本内容
with open("input.txt", "r", encoding="utf-8") as f:
corpus = f.read()
# 使用jieba分词对文本进行处理
tokens = list(jieba.cut(corpus))
# 定义n-gram模型的n值
n = 3
# 构建n-gram模型
ngrams = []
for i in range(len(tokens) - n + 1):
ngrams.append("".join(tokens[i:i+n]))
# 定义隐马尔可夫模型的参数
states = ["路", "人"]
observations = list(set(ngrams))
start_probability = {"路": 1.0, "人": 0.0}
transition_probability = {
"路": {"路": 0.5, "人": 0.5},
"人": {"路": 0.5, "人": 0.5}
}
emission_probability = {}
for token in observations:
probs = {
"路": 1.0 if token.startswith("路") else 0.0,
"人": 1.0 if token.startswith("人") else 0.0
}
emission_probability[token] = probs
# 生成鲁迅风格的一句话
result = "世上本没有路,走的人多了,"
while len(result) < 30:
# 从当前状态出发,根据隐马尔可夫模型的参数随机生成下一个状态
current_state = result[-1]
next_state = random.choices(states, [transition_probability[current_state][s] for s in states])[0]
# 从下一个状态出发,根据隐马尔可夫模型的参数随机生成下一个观测值
next_token = random.choices(observations, [emission_probability[o][next_state] for o in observations])[0]
# 将生成的观测值添加到结果中
result += next_token[1:]
print(result)
第三节 tfidf
下午写