- GPT图解:大模型是怎样构建的
- 黄佳
- 2258字
- 2024-12-11 17:23:09
1.3 创建一个Bigram字符预测模型
这个Bigram字符预测模型程序的整体结构如下图所示。
Bigram字符预测模型程序结构
第1步 构建实验语料库
首先建一个非常简单的数据集。
In
# 构建一个玩具数据集 corpus = [ "我喜欢吃苹果", "我喜欢吃香蕉", "她喜欢吃葡萄", "他不喜欢吃香蕉", "他喜欢吃苹果", "她喜欢吃草莓"]
这个玩具数据集,你可以把它想象成中文的一个简单缩影,抑或文明曙光初现时我们祖先发明的第一批语言和文字,人们每天反反复复就只说这么几句话。这就是我们的实验语料库。
第2步 把句子分成N个“Gram”(分词)
定义一个分词函数,用它将文本分割成单个汉字字符,针对字符来计算Bigram词频。
In
# 定义一个分词函数,将文本转换为单个字符的列表 def tokenize(text): return [char for char in text] # 将文本拆分为字符列表
第3步 计算每个Bigram在语料库中的词频
定义计算N-Gram词频的函数,并在数据集上应用这个函数,指定参数n为2,以生成Bigram,然后把所有的词频都显示出来。
In
# 定义计算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)))
Out
Bigram词频: 我: {'喜': 2} 喜: {'欢': 6} 欢: {'吃': 6} 吃: {'苹': 2, '香': 2, '葡': 1, '草': 1} 苹: {'果': 2} 香: {'蕉': 2} 她: {'喜': 2} 葡: {'萄': 1} 他: {'不': 1, '喜': 1} 不: {'喜': 1} 草: {'莓': 1}
从输出中可以看到,每一个二元组在整个语料库中出现的次数都被统计得清清楚楚,这就是我们所构造的模型的基础信息。比如说,“吃苹”这个Bigram,在语料库中出现了2次;而“吃葡”则只出现过1次。
第4步 计算每个Bigram的出现概率
根据词频计算每一个Bigram出现的概率。也就是计算给定前一个词时,下一个词出现的可能性,这是通过计算某个Bigram词频与前缀词频之比得到的。
In
# 定义计算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)))
Out
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}
这样,我们拥有了全部Bigram出现的概率,也就拥有了一个N-Gram模型。可以用这个模型来进行文本生成。也就是说,你给出一个字,它可以为你预测下一个字,方法就是直接选择出现概率最高的一个词进行生成。
第5步 根据Bigram出现的概率,定义生成下一个词的函数
定义生成下一个词的函数,基于N-Gram出现的概率计算特定前缀出现后的下一个词。
In
# 定义生成下一个词的函数 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
这段代码接收一个词序列(称为前缀)和一个包含各种可能的下一个词及其对应概率的词典。首先,检查前缀是否在词典中。如果前缀不存在于词典中,那么函数返回None,表示无法生成下一个词。如果前缀存在于词典中,该函数就会从词典中取出这个前缀对应的下一个词的概率。接着,函数会在其中找到概率最大的词,然后将这个词作为下一个词返回。
有了这个函数,给定一个前缀词之后,我们就可以调用它,生成下一个词。
第6步 输入一个前缀,生成连续文本
先定义一个生成连续文本的函数。
In
# 定义生成连续文本的函数 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) # 将字符列表连接成字符串
这个函数首先将前缀字符串转化为字符列表tokens,以便后续操作。然后进入一个循环,循环的次数等于生成文本的目标长度length减去前缀的长度。循环的目的是生成足够长度的文本。在循环中,函数会调用之前定义的generate_next_token函数,以获取下一个词。
这个函数会考虑到当前的n-1个词(也就是前缀的最后n-1个词),以及所有可能的下一个词及其对应的概率。如果generate_next_token函数返回的下一个词是None(也就是没有找到合适的下一个词),那么循环会提前结束,不再生成新的词。如果函数成功找到了下一个词,那么这个词会被添加到字符列表tokens的尾部。当循环结束时,函数将使用Python的join方法,将字符列表连接成一个字符串,也就是函数生成的一段连续文本。
有了这个函数,我们就可以调用它,生成连续文本。一个简单的语言模型就做好了!
小冰问道:咖哥,那我们能否用这个模型生成句子呢?
咖哥回答:当然可以,加入一个前缀,模型立刻就能生成一个句子。
In
# 输入一个前缀,生成文本 generated_text = generate_text("我", bigram_probs, 2) print("\n生成的文本:", generated_text) # 打印生成的文本
Out
生成的文本: 我喜欢吃苹果
这个Bigram字符预测模型,也就是简单的N-Gram模型虽然有局限性,但它对后来许多更加强大的自然语言处理技术都有很大的启发意义。你看,我们只是通过一个玩具数据集和二元组模型,就能生成简单的句子。
在N-Gram模型中,我们预测一个词出现的概率,只需考虑它前面的N-1个词。这样做的优点是计算简单,但缺点也很明显:它无法捕捉到距离较远的词之间的关系。而下面我给你介绍的这个和N-Gram差不多同时出现的早期语言模型——Bag-of-Words模型(也称“词袋模型”),则并不考虑哪个词和哪个词临近,而是通过把词看作一袋子元素的方式来把文本转换为能统计的特征。