感谢上一期能够进入csdn“每日推荐看”,那必然带着热情写下第二期《从n-gram到TFIDF》,这里引入一本《Speach and Language Processing》第三版翻译版本(语音与语言处理(SLP)),前半部分写的很好!里面连编辑距离(海明距离)都讲了,所以算很详细的了。那本期末尾留一个坑,利用编辑距离计算文本相似度!
上一节精彩回顾 原文链接
我们学习了词袋模型,并且仅使用了jieba库就完成了两段文本的相似度的比较上一节todo
做jieba的详细介绍。目标:能制作自己需要的词典,让jieba能根据自身需求进行“改变”。第二节 n-gram
1. 基本概念
N是一个具体的数字,将长文本处理成一个个的N个字节的片段序列,然后对出现频度进行统计。
2. 举例理解
将我的理解通俗易懂的讲解一波:理解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】
显然,在以两个词为切片的情况下,“看电影”与“好看的电影”从有一半相似,变成了完全不同的两个内容,思考一下,这合理吗?,
从语法层面,一个是动词词组,一个是名词词组,分成两个完全不同的内容是合理的”。从语义层,“看电影”与“好看的电影”仿佛又有千丝万缕的关系。
3. 动手理解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 jiebatext = "今天天气很好,适合出去散步"tokens = list(jieba.cut(text)) # 使用jieba的分词工具对文本进行分词,并将结果转换为列表trigrams = zip(tokens, tokens[1:], tokens[2:]) # 使用zip函数将列表中三个连续元素组成一个三元组for trigram in trigrams:print(trigram)
4. n-gram相似度
输入1:文本1
输入2:文本2
输出:文本的n-gram相似度
5. n-gram的应用场景
6. 项目案例(自创easy项目,埋下伏笔,提高兴趣,没兴趣不看也不影响后续阅读)
鲁迅风格的一句话补全。
准备:一个txt文件,jieba库的使用,2-gram基/3-gram础知识,马尔科夫链(一句话:下一个状态只与当前状态有关。)
参考:
输入:世上本没有路,走的人多了
输出:世上本没有路,走的人多了也便成了路。
代码思路:
① 通过一个txt文件,生成2-gram/3-gram的概率,即(“1”,“2”,(“3”),出现次数/出现概率),按频次或概率排序。② 给出新的文本内容,jieba得到最后一个词,然后依次排列到文本最后(限定长度)。
#todo 下午写
大模型的参考写法(只给了参考内容和一些描述)
import jiebafrom collections import Counterimport 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 # 导入分词库jiebaimport random # 导入随机数生成库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)) # 所有可能的观测值集合,即所有可能的n-gram序列start_probability = {"路": 1.0, "人": 0.0} # “路”和“人”两种状态的初始概率,其中“路”的概率为1.0,“人”的概率为0.0transition_probability = {"路": {"路": 0.5, "人": 0.5}, # 从“路”状态转移到“路”状态的概率为0.5,从“路”状态转移到“人”状态的概率为0.5"人": {"路": 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;否则概率为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)
上文提到的马尔科夫模型和隐马尔科夫模型回头会进行深入的了解,我们暂且先使用最简单的理解方式——代码实现,来应用它,弱化其理论细节。后续会在《第二章 文本生成》进行详细讲解。欢迎关注,这里就是将n-gram拓展一下。
总结:
n-gram基本概念,但它实际上将词语词之间构建了联系,初步拥有了上下文,这实际上是文本匹配在语义层面迈出的重要一步!n-gram相似度的实际应用,个人感觉这是最基础的部分,只有有了n-gram的概念,才能做出词典,传统方式中的文本才能向量化。第三节 tfidf
1. 概念
2.理解
2.1 TF的理解
我们的面前有很多篇文章,是“计算机组成原理”、“计算机视觉”、“计算机网络”三个领域的文章若干,并没有事先告知我们每篇文章的类别,依托我们的经验,我们明白在“组成原理“”中,单词频次较高的有:存储器、微指令等;以此类推,计算机视觉中有卷积神经网络;计算机网络中有协议等。
我们如何让计算机能分辨出来这三类文章呢?
我们通过词出现频次统计:
第一篇高频词:计算机:20次,微指令:20次;
第二篇高频词:计算机:20次,卷积神经网络:20次;
第三篇高频词:标准:20次,TCP协议:20次;
…
TF 就是上述每一篇文章中出现这个词的次数,这个也是第一节词袋模型与第二节n-gram是一脉相承的知识内容,可以是单个词的统计,也是可以2-gram的统计…
当然也有很多改进方法,例如:① 该单词出现次数/这篇文章的总词数 ② 该单词出现次数/这篇文章出现频次最多的词出现的次数,这里的词包括后续都是切片后的n-gram词。
2.2 IDF的理解
如果依靠“计算机”来划分这篇文章属于哪一类,是显然不合理的。那我们自然而然会想到找到只在这一类文章中频繁出现,在其他文章中很少出现的词作为判断依据就好了。,由于其他文章是没有提前告知的,我们转换为在所有文章中的出现频率较低替代它。
可以理解为某词xxx在100篇文档里中出现在了25篇文档里,那么该词出现的文档频率为25%
考虑到TF是高频词,肯定是越大越相关,在文档频率中,少部分文档里,高频出现才是好的,因此我们取逆,即用 文档总数÷出现该词的文档数,这样也是让IDF值越大越相关了。
再考虑计算方便,就使用log,同时,为了避免分母为0 (出现0的情况,即log(100/0)的情况:①调用别人的词典,按别人词典顺序对系列文档进行统计;② 抽样几篇文档做成词典,再应用到所有数据集等;)
以上述三篇文章为例,IDF计算
第一篇各个词的IDF:计算机:log(3/3),微指令 :log(3/2)
第二篇各个词的IDF:计算机:log(3/3),卷积神经网络:log(3/2);
第三篇各个词的IDF:标准:log(3/2),TCP协议:log(3/2);
不难发现,计算机的idf已经为0了,确实达到了出现的文档数越少,则IDF越大的效果!
TFIDF就是TF*IDF得到的结果,越相关,值越大。
2.3 停用词的理解
揭秘jieba的时候到啦!即将填坑!!
上文提到计算IDF时,有常常出现的词,比如计算机,还有我们日常生活中使用的,“在”,“这”,“那”,“和”等,还有一些词,这些词最大的特性就是IDF值很低,也就是 log(总文章数)/(出现数+1)很低,即出现次数很多!
而停用词,我们第一次遇见就是在讲到jieba的时候
2. 代码填空
制作文本数据集并输出停用词
step1:将几篇文档(合计超过一万字)作为输入数据
step2:利用jieba库将文档加工成1-gram词典,保存为dictionary.csv(“序号”,“”)
step3:计算所有词的IDF值,并从小到大排序,保存为idf.csv(“序号”,“词”,“idf值”)
step4:输出排名前100的词以 及 idf值>log(100/51)过半的词
# todo 明天吧!今天先溜啦!
大模型参考
3. 总结
可以发现,我们已经逐步从词,走到句子相似,再到文章相关。但依然停留在基础部分,甚至最常听见的RNN、LSTM都没有见到,别着急,别着急,马上就要开始啦!
如果觉得《【如何用大语言模型快速深度学习系列】从n-gram到TFIDF》对你有帮助,请点赞、收藏,并留下你的观点哦!