入门GPT(一)| N-Gram带你了解自然语言处理(2)代码实现

科技   2025-01-05 10:46   山西  

-推荐关注-

-正文-

理解N-Gram模型不仅是学习概率语言模型的基础,更是深入理解现代语言模型(如GPT)的重要前奏。GPT的核心思想正是对N-Gram思想的深度拓展和优化。因此,学习N-Gram模型是迈向GPT的第一步

我们用代码实现一下N-Gram模型

  • 1.构建语料库
  • 2.创建分词函数:把句子分成n个gram
  • 3.创建词频函数:计算每个Bigram在语料库的词频
  • 4.创建频率函数:计算每个Bigram的出现频率
  • 5.创建预测函数:根据Bigram出现的频率,生成下一个词
  • 6.输入一个字,生成连续文本

-- 领取学习资料大礼包,见文末

研究者尝试用统计方法来预测文本中的词序列概率,这种为了预测词汇出现的概率而使用的统计方法就是自然语言处理中的概率模型。它的基本思想是,给定一个词序列,计算下一个词出现的概率。

所以我们假设第 i 个字会跟第 1 个字到 i-1 个字有关,这时你会发现如果这个句子很长,该概率计算量将会变得非常大,因此我们可以利用 马尔可夫 假设,也就是当前的这个字仅仅与前几个有限的字相关,如此一来,将不必追溯至第 1 个字,可以减少该概率的计算量,这就是N-Gram 模型,他是一种基于概率的语言模型:

在n=2时,句子的概率表示为(公式比较长,可以左右滑动)


对于“我喜欢吃肉”的概率近似:


可以通过训练语料中的频率统计近似得到  估计公式:

见上文:入门GPT(一)| N-Gram 带你了解自然语言处理(1)

N-Gram 模型的构建过程:

(1) 将给定的文本分割成连续的N个词的组合(N-Gram)

(2) 统计每个 2-Gram 在文本中出现的次数,也就是词频。

(3) 为了得到一个词在给定上下文中出现的概率,我们可以利用条件概率公式计算。

(4) 可以使用这些概率来预测文本中下一个词出现的可能性。


我们用代码实现给定文字预测下一个词

代码分为以下几个部分:


1.构建语料库 

# 构建一个语料库
corpus = ["我喜欢吃苹果",
        "我喜欢吃香蕉",
        "她喜欢吃葡萄",
        "他不喜欢吃香蕉",
        "他喜欢吃苹果",
        "她喜欢吃草莓"]

2.创建分词函数:把句子分成n个gram 

# 定义一个分词函数,将文本转换为单个字符的列表
def tokenize(text):
    """
    将输入文本拆分为单个字符的列表。

    参数:
    text (str): 输入的文本字符串。

    返回:
    list: 包含文本中每个字符的列表。
    """

    return [char for char in text]  # 将文本拆分为字符列表


# 对每个文本进行分词,并打印出对应的单字列表
print("单字列表:")
for text in corpus:
    tokens = tokenize(text)
    print(tokens)
单字列表:
['我''喜''欢''吃''苹''果']
['我''喜''欢''吃''香''蕉']
['她''喜''欢''吃''葡''萄']
['他''不''喜''欢''吃''香''蕉']
['他''喜''欢''吃''苹''果']
['她''喜''欢''吃''草''莓']

3.创建词频函数:计算每个Bigram在语料库的词频 

# 定义计算 N-Gram 词频的函数
from collections import defaultdict, Counter  # 导入所需库


def count_ngrams(corpus, n):
    """
    计算给定语料库中 N-Gram 的频率。

    参数:
    corpus (list): 包含多个文本字符串的列表。
    n (int): N-Gram 的大小。

    返回:
    defaultdict: 一个字典,键为 N-Gram 的前缀,值为一个计数器,表示每个目标单字的频率。
    """

    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 元组
            print(ngram)
            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)))
bigram 词频:
我: {'喜'2}
喜: {'欢'6}
欢: {'吃'6}
吃: {'苹'2'香'2'葡'1'草'1}
苹: {'果'2}
香: {'蕉'2}
她: {'喜'2}
葡: {'萄'1}
他: {'不'1'喜'1}
不: {'喜'1}
草: {'莓'1}

4.创建频率函数:计算每个Bigram的出现频率 

# 定义计算 N-Gram 出现概率的函数
def ngram_probabilities(ngram_counts):
    """
       计算给定 N-Gram 计数的出现概率。

       参数:
       ngram_counts (defaultdict): 包含 N-Gram 计数的字典。

       返回:
       defaultdict: 一个字典,键为 N-Gram 的前缀,值为一个计数器,表示每个目标单字的出现概率。
    """

    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)))
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}

5.创建预测函数:根据Bigram出现的频率,生成下一个词 

# 定义生成下一个词的函数
def generate_next_token(prefix, ngram_probs):
    """
       根据给定的前缀生成下一个词。

       参数:
       prefix (tuple): N-Gram 的前缀。
       ngram_probs (defaultdict): 包含 N-Gram 概率的字典。

       返回:
       str或None: 生成的下一个词,如果前缀不在 N-Gram 中则返回 None。
    """

    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

6.输入一个字,生成连续文本 

# 定义生成连续文本的函数
def generate_text(prefix, ngram_probs, n, length=6):
    """
       根据给定的前缀生成指定长度的连续文本。

       参数:
       prefix (str): 生成文本的前缀。
       ngram_probs (defaultdict): 包含 N-Gram 概率的字典。
       n (int): N-Gram 的大小。
       length (int): 生成文本的总长度。

       返回:
       str: 生成的文本字符串。
    """

    tokens = list(prefix)  # 将前缀转换为字符列表
    for _ in range(length - len(prefix)):  # 根据指定长度生成文本
        # 获取当前前缀的下一个词
        next_token = generate_next_token(tuple(tokens[-(n - 1):]), ngram_probs)
        ifnot next_token:  # 如果下一个词为 None,跳出循环
            break
        tokens.append(next_token)  # 将下一个词添加到生成的文本中
    return"".join(tokens)  # 将字符列表连接成字符串
    
# 输入一个前缀,生成文本
generated_text = generate_text("我", bigram_probs, 2)
print("\n 生成的文本:", generated_text)  # 打印生成的文本
生成的文本: 我喜欢吃苹果


在 N-Gram 模型中,我们预测一个词出现的概率,只需考虑它前面的 N-1 个词,这样做的优点是计算简单,但缺点也很明显:它无法捕捉到距离较远的词之间的关系


参考:
1. 入门GPT(一)| N-Gram 带你了解自然语言处理(1)
2.《GPT图解大模型是怎么构建的》


往日文章:


有需要的,在公众号「AI取经路」发消息「学习资料」即可获取。

--END--

点亮“赞”“在看”“分享”好友一起看

AI取经路
踏上取经路,比抵达灵山更重要! AI技术、 AI知识 、 AI应用 、 人工智能 、 大语言模型
 最新文章