本文为对《GPT 图解 - 大模型是怎样构建的》一书的学习笔记,所有的例子和代码均来源于本书。
基本介绍 N-Gram 模型为语言模型的雏形。
基本思想为:一个词出现的概率仅依赖其前面的 N-1 个词。即通过有限的 N-1 个词来预测第 N 个词。
以 “我爱吃肉” 举例,分词为 [”我“, “爱”, “吃”, “肉”]。
当 N=1 时,对应的序列为[”我“, “爱”, “吃”, “肉”],又成为 Unigram。
当 N=2 时,对应的序列为[”我爱“, “爱吃”, “吃肉”],又成为 Bigram。
当 N=3 时,对应的序列为[”我爱吃“, “爱吃肉”],又成为 Trigram。
2-Gram 的构建过程 将语料拆分为分词 将给定的语料库,拆分为以一个的分词。在真实的场景中需要使用分词函数,这里简单起见,使用了一个汉字一个分词的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 corpus = [ "我喜欢吃苹果" , "我喜欢吃香蕉" , "她喜欢吃葡萄" , "他不喜欢吃香蕉" , "他喜欢吃苹果" , "她喜欢吃草莓" ] def tokenize (text ): return [char for char in text] print ("单字列表:" ) for text in corpus: tokens = tokenize(text) print (tokens)
得到如下的结果:
1 2 3 4 5 6 7 单字列表: ['我', '喜', '欢', '吃', '苹', '果'] ['我', '喜', '欢', '吃', '香', '蕉'] ['她', '喜', '欢', '吃', '葡', '萄'] ['他', '不', '喜', '欢', '吃', '香', '蕉'] ['他', '喜', '欢', '吃', '苹', '果'] ['她', '喜', '欢', '吃', '草', '莓']
计算每个 2-Gram(BiGram) 在语料库中的词频 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from collections import defaultdict, Counter def count_ngrams (corpus, n ): ngrams_count = defaultdict(Counter) for text in corpus: tokens = tokenize(text) for i in range (len (tokens) - n + 1 ): prefix = ngram[:-1 ] token = ngram[-1 ] ngrams_count[prefix][token] += 1 return ngrams_count bigram_counts = count_ngrams(corpus, 2 ) print ("bigram 词频:" ) for prefix, counts in bigram_counts.items(): print ("{}: {}" .format ("" .join(prefix), dict (counts)))
计算获取到如下的结果:
1 2 3 4 5 6 7 8 9 10 11 12 bigram 词频: 我: {'喜': 2} 喜: {'欢': 6} 欢: {'吃': 6} 吃: {'苹': 2, '香': 2, '葡': 1, '草': 1} 苹: {'果': 2} 香: {'蕉': 2} 她: {'喜': 2} 葡: {'萄': 1} 他: {'不': 1, '喜': 1} 不: {'喜': 1} 草: {'莓': 1}
即当第一个词为 ”我“,第二个词为”喜“在整个语料库中出现了 2 次。
如果为 3-Gram(TriGram),此时输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 bigram 词频: 我喜: {'欢': 2} 喜欢: {'吃': 6} 欢吃: {'苹': 2, '香': 2, '葡': 1, '草': 1} 吃苹: {'果': 2} 吃香: {'蕉': 2} 她喜: {'欢': 2} 吃葡: {'萄': 1} 他不: {'喜': 1} 不喜: {'欢': 1} 他喜: {'欢': 1} 吃草: {'莓': 1}
如果为 1-Gram(UniGram),此时输出结果如下:
1 2 bigram 词频: : {'我': 2, '喜': 6, '欢': 6, '吃': 6, '苹': 2, '果': 2, '香': 2, '蕉': 2, '她': 2, '葡': 1, '萄': 1, '他': 2, '不': 1, '草': 1, '莓': 1}
可以看到已经退化为了每个单词在整个语料库中出现的次数。
计算每个 2-Gram 出现的概率 即给定前一个词,计算下一个词出现的概率。
1 2 3 4 5 6 7 8 9 10 11 def ngram_probabilities (ngram_counts ): ngram_probs = defaultdict(Counter) for prefix, tokens_count in ngram_counts.items(): total_count = sum (tokens_count.values()) for token, count in tokens_count.items(): return ngram_probs bigram_probs = ngram_probabilities(bigram_counts) print ("\nbigram 出现的概率 :" ) for prefix, probs in bigram_probs.items(): print ("{}: {}" .format ("" .join(prefix), dict (probs)))
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 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}
给定一个前缀,输出连续的文本 根据前面学习的语料信息,给定一个前缀,即可生成对应的文本内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def generate_next_token (prefix, ngram_probs ): if not prefix in ngram_probs: 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: break tokens.append(next_token) return "" .join(tokens) generated_text = generate_text("我" , bigram_probs, 2 ) print ("\n 生成的文本:" , generated_text)
给定了文本”我“,可以生成出如下结果:
总结 N-Gram 为非常简单的语言模型,可以根据给定的词来生成句子。
缺点:无法捕捉到距离较远的词之间的关系。