《动手学深度学习》-53LanguageModel简约实现

一、语言模型

  1. 核心目标:估计联合概率

语言模型的本质是计算一个句子(单词序列)出现的概率,或者根据上文预测下一个词。

  • 目标公式

    复制代码
    P(S)=P(w1,w2,...,wm)P(S)=P(w1​,w2​,...,wm​)
  • 意义:判断一句话在自然语言中是否通顺(概率越高,越自然),或在生成任务中选择概率最大的下一个词。

  1. 实现路径:链式法则 (Chain Rule)

直接计算联合概率极其困难(数据稀疏),因此利用概率链式法则将其分解为条件概率的乘积:

复制代码
P(w1,w2,...,wm)=P(w1)⋅P(w2∣w1)⋅P(w3∣w1,w2)⋅...⋅P(wm∣w1...wm−1)P(w1​,w2​,...,wm​)=P(w1​)⋅P(w2​∣w1​)⋅P(w3​∣w1​,w2​)⋅...⋅P(wm​∣w1​...wm−1​)
  1. 简化方案:N-gram 模型

为了解决长序列参数过多的问题,引入马尔可夫假设 (Markov Assumption)假设当前词出现的概率只与前面 的tau个词有关

  • N-gram 通用公式

    复制代码
    P(wi∣w1...wi−1)≈P(wi∣wi−N+1...wi−1)P(wi​∣w1​...wi−1​)≈P(wi​∣wi−N+1​...wi−1​)
  • 常见类型

    • Unigram (N=1):词与词独立。

      复制代码
      P(w1,w2)≈P(w1)P(w2)P(w1​,w2​)≈P(w1​)P(w2​)
    • Bigram (N=2):只看前1个词。

      复制代码
      P(wi∣wi−1)P(wi​∣wi−1​)
    • Trigram (N=3):只看前;2个词。

      复制代码
      P(wi∣wi−2,wi−1)P(wi​∣wi−2​,wi−1​)
  1. 参数估计:通过统计语料库中的词频来估算概率。

二、代码

复制代码
tokens=test52text_process.tokenize(test52text_process.read_time_machine())
corpus=[token for line in tokens for token in line]
vocab=test52text_process.Vocab(corpus)
print(vocab.token_freqs[:10])#查看高频词
freq=[freq for token,freq in vocab.token_freqs]
plt.figure(figsize=(10,6))
plt.xlabel('token')
plt.ylabel('frequency n(x)')
plt.xscale('log')
plt.yscale('log')
plt.grid(True)
plt.plot(freq)
plt.show()

频率高于80%掌握在20%词汇里(二八定律),可以从频率图确定min_freq

复制代码
gram_tokens=[pair for pair in zip(corpus[:-1],corpus[1:])]#生成二元列表
gram_vocab=test52text_process.Vocab(gram_tokens)
print(gram_vocab.token_freqs[:10])#二元很多是stopwords
复制代码
trigram_tokens=[pair for pair in zip(corpus[:-2],corpus[1:-1],corpus[2:])]#生成三元列表
trigram_vocab=test52text_process.Vocab(trigram_tokens)
print(trigram_vocab.token_freqs[:10])#三元可以反映文章趋势,很少有3个停用词连在一起
复制代码
bigram_token=[freq for token,freq in gram_vocab.token_freqs]
trigram_token=[freq for token,freq in trigram_vocab.token_freqs]
plt.figure(figsize=(10,6))
plt.xlabel('token')
plt.ylabel('frequency n(x)')
plt.xscale('log')
plt.yscale('log')
plt.grid(True)
plt.plot(freq)
plt.plot(bigram_token)
plt.plot(trigram_token)
plt.legend(['1-gram','bi-gram','tri-gram'])
plt.show()
复制代码
#随机取小批量,方法:每次在长序列最前面随即丢弃一定数量的值,从头来是切
def seq_data_iter_random(corpus,batch_size,num_steps):#num_steps小片段的数据长度
    corpus=corpus[random.randint(0,num_steps-1):]
    num_subseqs=(len(corpus)-1)//num_steps#防止切到最后一个片段,导致没有标签y,提前预留,一共可以产生num_subseqs个小片段
    initial_indices=list(range(0,num_subseqs*num_steps,num_steps))
    random.shuffle(initial_indices)
    def data(pos):#给定起点 pos,就切一段长度 num_steps 的子序列
        return corpus[pos:pos+num_steps]
    num_batches=num_subseqs//batch_size#计算一共能组成多少个 batch
    for i in range(0,batch_size*num_batches,batch_size):#划分n个batch
        initial_indices_per_batch=initial_indices[i:i+batch_size]#逐个取batch
        X=[data(j) for j in initial_indices_per_batch]
        Y=[data(j+1) for j in initial_indices_per_batch]
        yield torch.tensor(X), torch.tensor(Y)
my_seq=list(range(35))
for X,Y in seq_data_iter_random(my_seq,2,5):
    print('X',X,'y',Y)

保证相邻2个小批量的子序列在原始序列是相邻的

复制代码
def seq_data_iter_sequential(corpus,batch_size,num_steps):
    offset=random.randint(0,num_steps)
    num_tokens=((len(corpus)-offset-1)//batch_size)*batch_size
    Xs=torch.tensor(corpus[offset:offset+num_tokens])
    Ys=torch.tensor(corpus[offset+1:offset+num_tokens+1])
    Xs,Ys=Xs.reshape(batch_size,-1),Ys.reshape(batch_size,-1)
    num_batches=Xs.shape[1]//num_steps#获取一个batch里有几个minibatch
    for i in range(0,num_steps*num_batches,num_steps):
        X=Xs[:,i:i+num_steps]
        Y=Ys[:,i:i+num_steps]
        yield X,Y
for X,Y in seq_data_iter_sequential(my_seq,2,5):
    print('X',X,'\n','y',Y)
复制代码
class SeqDataLoader:#合并在一起
    def __init__(self,batch_size,num_steps,use_random_iter,max_tokens):
        if use_random_iter:
            self.data_iter_fn=seq_data_iter_random
        else:
            self.data_iter_fn=seq_data_iter_sequential
        self.corpus,self.vocab=test52text_process.load_crops_time_machine(max_tokens)
        self.batch_size=batch_size
        self.num_steps=num_steps
    def __iter__(self):
        return self.data_iter_fn(self.corpus,self.batch_size,self.num_steps)
def load_data_time_machine(batch_size,num_steps,use_random_iter=False,max_tokens=10000):#定义函数,同时返回数据迭代器和词汇表
    data_iter=SeqDataLoader(batch_size,num_steps,use_random_iter,max_tokens)
    return data_iter,data_iter.vocab
相关推荐
o561路6o623o710 小时前
陈,CPP条件位置偏爱系统
深度学习
ShyanZh10 小时前
【skill】Agent-Browser:AI代理的浏览器自动化实战指南
运维·人工智能·自动化·skill·agent-browser
Hello:CodeWorld10 小时前
AI Agent:从核心原理、架构框架到工程实战,大模型时代的自主智能革命
大数据·人工智能·python·架构
mowei10 小时前
MCP 配了 20 分钟,CLI 一句话:我给 Agent 选工具的真实取舍
人工智能
Chengbei1110 小时前
CTF & 红队专用 AI 求解AI 引擎 Cairn 系统,化轻量化部署,红队、CTF、漏洞研究一站式解决方案
java·人工智能·安全·web安全·网络安全·系统安全
Lucy_CL10 小时前
AI 写代码写到一半跑偏?我用这套工作流解决了
人工智能
davidrevo10 小时前
Harness Engineering(驭缰工程)- 大模型加速器
人工智能
王莎莎10 小时前
从 OCR 到 Context Engineering:用 MinerU 搭一个可复现文档解析评测
人工智能
漫途科技10 小时前
精准感知,智护安全|MTB46-4-2A 4G数字信号采集仪赋能结构安全监测
人工智能
装不满的克莱因瓶10 小时前
掌握空间注意力 STN 模型结构——让神经网络学会自动“看准位置”
人工智能·python·深度学习·神经网络·机器学习·ai