感谢上一期能够进入csdn“每日推荐看”,那必然带着热情写下第二期《从n-gram到TFIDF》,这里引入一本《Speach and Language Processing》第三版翻译版本(语音与语言处理(SLP)),前半部分写的很好!里面连编辑距离(海明距离)都讲了,所以算很详细的了。那本期末尾留一个坑,利用编辑距离计算文本相似度!

上一节精彩回顾 原文链接

  1. 我们学习了词袋模型,并且仅使用了jieba库就完成了两段文本的相似度的比较

上一节todo

  1. 做jieba的详细介绍。目标:能制作自己需要的词典,让jieba能根据自身需求进行“改变”。

第二节 n-gram

1. 基本概念

【如何用大语言模型快速深度学习系列】从n-gram到TFIDF-LMLPHPN是一个具体的数字,将长文本处理成一个个的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】

显然,在以两个词为切片的情况下,“看电影”与“好看的电影”从有一半相似,变成了完全不同的两个内容,思考一下,这合理吗?

  1. 从语法层面,一个是动词词组,一个是名词词组,分成两个完全不同的内容是合理的”。
  2. 从语义层,“看电影”与“好看的电影”仿佛又有千丝万缕的关系。

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 jieba

text = "今天天气很好,适合出去散步"
tokens = list(jieba.cut(text))  # 使用jieba的分词工具对文本进行分词,并将结果转换为列表
trigrams = zip(tokens, tokens[1:], tokens[2:])  # 使用zip函数将列表中三个连续元素组成一个三元组
for trigram in trigrams:
    print(trigram)

4. n-gram相似度

【如何用大语言模型快速深度学习系列】从n-gram到TFIDF-LMLPHP

输入1:文本1
输入2:文本2
输出:文本的n-gram相似度

5. n-gram的应用场景

【如何用大语言模型快速深度学习系列】从n-gram到TFIDF-LMLPHP

6. 项目案例(自创easy项目,埋下伏笔,提高兴趣,没兴趣不看也不影响后续阅读)

鲁迅风格的一句话补全。

准备:一个txt文件,jieba库的使用,2-gram基/3-gram础知识,马尔科夫链(一句话:下一个状态只与当前状态有关。)

参考:
输入:世上本没有路,走的人多了
输出:世上本没有路,走的人多了也便成了路。

代码思路:
① 通过一个txt文件,生成2-gram/3-gram的概率,即(“1”,“2”,(“3”),出现次数/出现概率),按频次或概率排序。② 给出新的文本内容,jieba得到最后一个词,然后依次排列到文本最后(限定长度)。

#todo 下午写

大模型的参考写法(只给了参考内容和一些描述)

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  # 导入分词库jieba
import 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.0
transition_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拓展一下。

总结:

  1. n-gram基本概念,但它实际上将词语词之间构建了联系,初步拥有了上下文,这实际上是文本匹配在语义层面迈出的重要一步!
  2. n-gram相似度的实际应用,个人感觉这是最基础的部分,只有有了n-gram的概念,才能做出词典,传统方式中的文本才能向量化。

第三节 tfidf

1. 概念

【如何用大语言模型快速深度学习系列】从n-gram到TFIDF-LMLPHP

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)的情况:①调用别人的词典,按别人词典顺序对系列文档进行统计;② 抽样几篇文档做成词典,再应用到所有数据集等;)
【如何用大语言模型快速深度学习系列】从n-gram到TFIDF-LMLPHP

以上述三篇文章为例,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都没有见到,别着急,别着急,马上就要开始啦!

07-04 06:35