• 动手学深度学习—序列数据与语言模型


    动手学深度学习—序列数据与语言模型

    序列模型

    时序模型中,当前数据跟之前观察到的数据相关

    在这里插入图片描述

    统计工具

    处理序列数据需要统计工具和新的深度神经网络架构:

    在这里插入图片描述
    问题的提出:围绕着如何有效估计 :

    P ( x t ∣ x t − 1 , … , x 1 ) P\left(x_{t} \mid x_{t-1}, \ldots, x_{1}\right) P(xtxt1,,x1)
    展开。 简单地说,它归结为以下两种策略。

    1. 第一种策略,假设在现实情况下相当长的序列 Xt-1…X1可能是不必要的, 因此我们只需要满足某个长度为t的时间跨度, 即使用观测序列Xt-1…Xt-T。 当下获得的最直接的好处就是参数的数量总是不变的, 至少在
      t>T时如此,这就使我们能够训练一个上面提及的深度网络。 这种模型被称为自回归模型(autoregressive models), 因为它们是对自己执行回归。

    2. 第二种策略,如图所示,是保留一些对过去观测的总结ht,并且同时更新预测xt和总结ht。这就产生了基于
      xt=P(xt|ht)估计xt,以及公式ht=g(ht-1,Xt-1)更新的模型。由于ht从未被观测到,这类模型也被称为隐变量自回归模型(latent autoregressivemodels)

    在这里插入图片描述

    p ( x ) = p ( x 1 ) ⋅ p ( x 2 ∣ x 1 ) ⋅ p ( x 3 ∣ x 1 , x 2 ) ⋅ … p ( x T ∣ x 1 , … x T − 1 ) p(\mathbf{x})=p\left(x_{1}\right) \cdot p\left(x_{2} \mid x_{1}\right) \cdot p\left(x_{3} \mid x_{1}, x_{2}\right) \cdot \ldots p\left(x_{T} \mid x_{1}, \ldots x_{T-1}\right) p(x)=p(x1)p(x2x1)p(x3x1,x2)p(xTx1,xT1)

    p ( x ) = p ( x T ) ⋅ p ( x T − 1 ∣ x T ) ⋅ p ( x T − 2 ∣ x T − 1 , x T ) ⋅ … p ( x 1 ∣ x 2 , … x T ) p(\mathbf{x})=p\left(x_{T}\right) \cdot p\left(x_{T-1} \mid x_{T}\right) \cdot p\left(x_{T-2} \mid x_{T-1}, x_{T}\right) \cdot \ldots p\left(x_{1} \mid x_{2}, \ldots x_{T}\right) p(x)=p(xT)p(xT1xT)p(xT2xT1,xT)p(x1x2,xT)

    对条件概率建模:对见过的数据建模,也称自回归模型。
    p ( x t ∣ x 1 , … x t − 1 ) = p ( x t ∣ f ( x 1 , … x t − 1 ) ) p\left(x_{t} \mid x_{1}, \ldots x_{t-1}\right)=p\left(x_{t} \mid f\left(x_{1}, \ldots x_{t-1}\right)\right) p(xtx1,xt1)=p(xtf(x1,xt1))

    自回归模型:自己进行回归使用之前的数据对下一个状态进行回归。

    方案一:马尔可夫假设

    假设当前当前数据只跟T个过去数据点相关

    p ( x t ∣ x 1 , … x t − 1 ) = p ( x t ∣ x t − τ , … x t − 1 ) = p ( x t ∣ f ( x t − τ , … x t − 1 ) ) p\left(x_{t} \mid x_{1}, \ldots x_{t-1}\right)=p\left(x_{t} \mid x_{t-\tau}, \ldots x_{t-1}\right)=p\left(x_{t} \mid f\left(x_{t-\tau}, \ldots x_{t-1}\right)\right) p(xtx1,xt1)=p(xtxtτ,xt1)=p(xtf(xtτ,xt1))

    方案二:潜变量模型

    引入潜变量ht,来表示过去信息:
    h t = f ( x 1 , … x t − 1 ) h_{t}=f\left(x_{1}, \ldots x_{t-1}\right) ht=f(x1,xt1)
    这样则会有:
    x t = p ( x t ∣ h t ) x_{t}=p\left(x_{t} \mid h_{t}\right) xt=p(xtht)

    在这里插入图片描述

    总结:

    • 自回归模型使用自身过去数据来预测未来
    • 马尔科夫模型假设当前只跟最近少数数据相关,从而简化模型
    • 潜变量模型使用潜变量来概括历史信息

    P ( x 1 , … , x T ) = ∏ t = 1 T P ( x t ∣ x t − 1 , … , x 1 ) P\left(x_{1}, \ldots, x_{T}\right)=\prod_{t=1}^{T} P\left(x_{t} \mid x_{t-1}, \ldots, x_{1}\right) P(x1,,xT)=t=1TP(xtxt1,,x1)

    实验使用马尔可夫假设训练MLP进行预测

    实验目的:给定一个时间点预测接下来的数据会是什么样子(正弦函数加噪音)

    一阶马尔可夫的计算公式:

    P ( x 1 , … , x T ) = ∏ t = 1 T P ( x t ∣ x t − 1 )  当  P ( x 1 ∣ x 0 ) = P ( x 1 ) .  P\left(x_{1}, \ldots, x_{T}\right)=\prod_{t=1}^{T} P\left(x_{t} \mid x_{t-1}\right) \text { 当 } P\left(x_{1} \mid x_{0}\right)=P\left(x_{1}\right) \text {. } P(x1,,xT)=t=1TP(xtxt1)  P(x1x0)=P(x1)

    P ( x t + 1 ∣ x t − 1 ) = ∑ x t P ( x t + 1 , x t , x t − 1 ) P ( x t − 1 ) = ∑ x t P ( x t + 1 ∣ x t , x t − 1 ) P ( x t , x t − 1 ) P ( x t − 1 ) = ∑ x t P ( x t + 1 ∣ x t ) P ( x t ∣ x t − 1 )

    P(xt+1xt1)=xtP(xt+1,xt,xt1)P(xt1)=xtP(xt+1xt,xt1)P(xt,xt1)P(xt1)=xtP(xt+1xt)P(xtxt1)" role="presentation">P(xt+1xt1)=xtP(xt+1,xt,xt1)P(xt1)=xtP(xt+1xt,xt1)P(xt,xt1)P(xt1)=xtP(xt+1xt)P(xtxt1)
    P(xt+1xt1)=P(xt1)xtP(xt+1,xt,xt1)=P(xt1)xtP(xt+1xt,xt1)P(xt,xt1)=xtP(xt+1xt)P(xtxt1)

    实验代码上机

    %matplotlib inline
    import torch
    from torch import nn
    from d2l import torch as d2l
    
    T = 1000  # 总共产生1000个点
    time = torch.arange(1, T + 1, dtype=torch.float32)
    x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))
    d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))
    

    在这里插入图片描述

    接下来,我们将这个序列转换为模型的特征-标签(feature-label)

     对  y t = x t  和  x t = [ x t − τ , … , x t − 1 ] 。  \text { 对 } y_{t}=x_{t} \text { 和 } \mathbf{x}_{t}=\left[x_{t-\tau}, \ldots, x_{t-1}\right]_{\text {。 }}   yt=xt  xt=[xtτ,,xt1] 

    实验使用代码构造出feature并进行输出测试

    tau = 4
    features = torch.zeros((T - tau, tau))
    features # 结构为996 x 4的结构
    

    在这里插入图片描述

    for i in range(tau):
        features[:, i] = x[i: T - tau + i]
    labels = x[tau:].reshape((-1, 1))
    
    batch_size, n_train = 16, 600
    # 只有前n_train个样本用于训练
    train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                                batch_size, is_train=True)
    

    在这里插入图片描述
    从而得到了要求输出的向量Xt的结构

    label标签的值即为5-1000的x的取值
    在这里插入图片描述

    构建网络MLP模型并进行训练

    # 初始化网络权重的函数
    def init_weights(m):
        if type(m) == nn.Linear:
            nn.init.xavier_uniform_(m.weight)
    
    # 一个简单的多层感知机
    def get_net():
        net = nn.Sequential(nn.Linear(4, 10),
                            nn.ReLU(),
                            nn.Linear(10, 1))
        net.apply(init_weights)
        return net
    
    # 平方损失。注意:MSELoss计算平方误差时不带系数1/2
    loss = nn.MSELoss(reduction='none')
    
    def train(net, train_iter, loss, epochs, lr):
        trainer = torch.optim.Adam(net.parameters(), lr)
        for epoch in range(epochs):
            for X, y in train_iter:
                trainer.zero_grad()
                l = loss(net(X), y)
                l.sum().backward()
                #调用trainer.step()方法来更新模型参数
                trainer.step()
            print(f'epoch {epoch + 1}, '
                  f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')
    
    net = get_net()
    train(net, train_iter, loss, 5, 0.01)
    

    之后我们进行预测确定训练的效果:

    由于训练损失很小,因此我们期望模型能有很好的工作效果。 让我们看看这在实践中意味着什么。 首先是检查模型预测下一个时间步的能力, 也就是单步预测(one-step-ahead prediction)。

    实际上看的是600之后的一个训练的效果。

    onestep_preds = net(features)
    onestep_preds.detach().numpy()
    

    在这里插入图片描述

    d2l.plot([time, time[tau:]],
             [x.detach().numpy(), onestep_preds.detach().numpy()], 'time',
             'x', legend=['data', '1-step preds'], xlim=[1, 1000],
             figsize=(6, 3))
    

    在这里插入图片描述

    正如我们所料,单步预测效果不错。 即使这些预测的时间步超过了 600+4(n_train + tau), 其结果看起来仍然是可信的。 然而有一个小问题:如果数据观察序列的时间步只到, 我们需要一步一步地向前迈进:

    x ^ 605 = f ( x 601 , x 602 , x 603 , x 604 ) , x ^ 606 = f ( x 602 , x 603 , x 604 , x ^ 605 ) , x ^ 607 = f ( x 603 , x 604 , x ^ 605 , x ^ 606 ) , x ^ 608 = f ( x 604 , x ^ 605 , x ^ 606 , x ^ 607 ) x ^ 609 = f ( x ^ 605 , x ^ 606 , x ^ 607 , x ^ 608 ) ,

    x^605=f(x601,x602,x603,x604),x^606=f(x602,x603,x604,x^605),x^607=f(x603,x604,x^605,x^606),x^608=f(x604,x^605,x^606,x^607)x^609=f(x^605,x^606,x^607,x^608)," role="presentation">x^605=f(x601,x602,x603,x604),x^606=f(x602,x603,x604,x^605),x^607=f(x603,x604,x^605,x^606),x^608=f(x604,x^605,x^606,x^607)x^609=f(x^605,x^606,x^607,x^608),
    x^605=f(x601,x602,x603,x604),x^606=f(x602,x603,x604,x^605),x^607=f(x603,x604,x^605,x^606),x^608=f(x604,x^605,x^606,x^607)x^609=f(x^605,x^606,x^607,x^608),

    通常,对于直到xt的观测序列,其在时间步t+k处的预测输出Xt+k 称为k步预测(k-step-ahead-prediction)。由于我们的观察已经到了X604,它的k步预测是X604+k。换句话说,我们必须使用我们自己的预测(而不是原始数据)来进行多步预测。让我们看看效果如何。

    首先先将前600的数进行赋值操作,后面的不给出预测全部写为0

    multistep_preds = torch.zeros(T)
    multistep_preds[: n_train + tau] = x[: n_train + tau]
    multistep_preds
    

    在这里插入图片描述
    带入模型得到预测结果

    for i in range(n_train + tau, T):
        multistep_preds[i] = net(
            multistep_preds[i - tau:i].reshape((1, -1)))
    

    在这里插入图片描述
    基于k=1,4,16,64预测

    max_steps = 64
    
    features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
    # 列i(i
    for i in range(tau):
        features[:, i] = x[i: i + T - tau - max_steps + 1]
    
    # 列i(i>=tau)是来自(i-tau+1)步的预测,其时间步从(i)到(i+T-tau-max_steps+1)
    for i in range(tau, tau + max_steps):
        features[:, i] = net(features[:, i - tau:i]).reshape(-1)
    
    steps = (1, 4, 16, 64)
    d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
             [features[:, (tau + i - 1)].detach().numpy() for i in steps], 'time', 'x',
             legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000],
             figsize=(6, 3))
    

    文本预处理

    整个NLP的基础就是文本预处理,类似的是在卷积神经网络处理图像分类问题的时候,对图像数据集的处理过程。

    1. 将文本作为字符串加载到内存中。

    2. 将字符串拆分为词元(如单词和字符)。

    3. 建立一个词表,将拆分的词元映射到数字索引。

    4. 将文本转换为数字索引序列,方便模型操作。

    数据集

    读取数据集

    import collections
    import re
    from d2l import torch as d2l
    
    #@save
    d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt',
                                    '090b5e7e70c295757f55df93cb0a180b9691891a')
    
    def read_time_machine():  #@save
        """将时间机器数据集加载到文本行的列表中"""
        with open(d2l.download('time_machine'), 'r') as f:
            lines = f.readlines()
        return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]
    
    lines = read_time_machine()
    print(f'# 文本总行数: {len(lines)}')
    print(lines[0])
    print(lines[10])
    

    在这里插入图片描述

    词元化

    下面的tokenize函数将文本行列表(lines)作为输入, 列表中的每个元素是一个文本序列(如一条文本行)。 每个文本序列又被拆分成一个词元列表,词元(token)是文本的基本单位。 最后,返回一个由词元列表组成的列表,其中的每个词元都是一个字符串(string)。

    def tokenize(lines, token='word'):  #@save
        """将文本行拆分为单词或字符词元"""
        if token == 'word':
            return [line.split() for line in lines]
        elif token == 'char':
            return [list(line) for line in lines]
        else:
            print('错误:未知词元类型:' + token)
    
    tokens = tokenize(lines)
    for i in range(11):
        print(tokens[i])
    

    词表化

    词元的类型是字符串,而模型需要的输入是数字,因此这种类型不方便模型使用。现在,让我们构建一个字典,通常也叫做词表(vocabulary),用来将字符串类型的词元映射到从0开始的数字索引中。我们先将训练集中的所有文档合并在一起,对它们的唯一词元进行统计,得到的统计结果称之为语料(corpus)
    然后根据每个唯一词元的出现频率,为其分配一个数字索引。很少出现的词元通常被移除,这可以降低复杂性。另外,语料库中不存在或已删除的任何词元都将映射到一个特定的未知词元""。我们可以选择增加一个列表,用于保存那些被保留的词元。

    class Vocab:  #@save
        """文本词表"""
        def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
            if tokens is None:
                tokens = []
            if reserved_tokens is None:
                reserved_tokens = []
            # 按出现频率排序
            counter = count_corpus(tokens)
            self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
                                       reverse=True)
            # 未知词元的索引为0
            self.idx_to_token = [''] + reserved_tokens
            self.token_to_idx = {token: idx
                                 for idx, token in enumerate(self.idx_to_token)}
            for token, freq in self._token_freqs:
                if freq < min_freq:
                    break
                if token not in self.token_to_idx:
                    self.idx_to_token.append(token)
                    self.token_to_idx[token] = len(self.idx_to_token) - 1
    
        def __len__(self):
            return len(self.idx_to_token)
    
        def __getitem__(self, tokens):
            if not isinstance(tokens, (list, tuple)):
                return self.token_to_idx.get(tokens, self.unk)
            return [self.__getitem__(token) for token in tokens]
    
        def to_tokens(self, indices):
            if not isinstance(indices, (list, tuple)):
                return self.idx_to_token[indices]
            return [self.idx_to_token[index] for index in indices]
    
        @property
        def unk(self):  # 未知词元的索引为0
            return 0
    
        @property
        def token_freqs(self):
            return self._token_freqs
    
    def count_corpus(tokens):  #@save
        """统计词元的频率"""
        # 这里的tokens是1D列表或2D列表
        if len(tokens) == 0 or isinstance(tokens[0], list):
            # 将词元列表展平成一个列表
            tokens = [token for line in tokens for token in line]
        return collections.Counter(tokens)
    
    vocab = Vocab(tokens)
    

    实际上是对词进行一个编号的处理过程。

    在这里插入图片描述

    整合数据模块

    def load_corpus_time_machine(max_tokens=-1):  #@save
        """返回时光机器数据集的词元索引列表和词表"""
        lines = read_time_machine()
        tokens = tokenize(lines, 'char')
        vocab = Vocab(tokens)
        # 因为时光机器数据集中的每个文本行不一定是一个句子或一个段落,
        # 所以将所有文本行展平到一个列表中
        corpus = [vocab[token] for line in tokens for token in line]
        if max_tokens > 0:
            corpus = corpus[:max_tokens]
        return corpus, vocab
    
    corpus, vocab = load_corpus_time_machine()
    len(corpus), len(vocab)
    

    语言模型

    给定文本序列x1,….,xr,语言模型的目标是估计联合概率p(x1,….,xr)

    学习语言模型—使用计数来建模

    假设序列长度为2,我们预测:(这里n是总词数,n(x),n(x,x’)是单个单词和连续单词对的出现次数)

    p ( x , x ′ ) = p ( x ) p ( x ′ ∣ x ) = n ( x ) n n ( x , x ′ ) n ( x ) p\left(x, x^{\prime}\right)=p(x) p\left(x^{\prime} \mid x\right)=\frac{n(x)}{n} \frac{n\left(x, x^{\prime}\right)}{n(x)} p(x,x)=p(x)p(xx)=nn(x)n(x)n(x,x)

    p ( x , x ′ , x ′ ′ ) = p ( x ) p ( x ′ ∣ x ) p ( x ′ ′ ∣ x , x ′ ) = n ( x ) n n ( x , x ′ ) n ( x ) n ( x , x ′ , x ′ ′ ) n ( x , x ′ ) p\left(x, x^{\prime}, x^{\prime \prime}\right)=p(x) p\left(x^{\prime} \mid x\right) p\left(x^{\prime \prime} \mid x, x^{\prime}\right)=\frac{n(x)}{n} \frac{n\left(x, x^{\prime}\right)}{n(x)} \frac{n\left(x, x^{\prime}, x^{\prime \prime}\right)}{n\left(x, x^{\prime}\right)} p(x,x,x′′)=p(x)p(xx)p(x′′x,x)=nn(x)n(x)n(x,x)n(x,x)n(x,x,x′′)

    在这里插入图片描述

    马尔可夫模型与n元语法

    阶数越高,对应的依赖关系就越长。 这种性质推导出了许多可以应用于序列建模的近似公式:

    P ( x 1 , x 2 , x 3 , x 4 ) = P ( x 1 ) P ( x 2 ) P ( x 3 ) P ( x 4 ) , P ( x 1 , x 2 , x 3 , x 4 ) = P ( x 1 ) P ( x 2 ∣ x 1 ) P ( x 3 ∣ x 2 ) P ( x 4 ∣ x 3 ) , P ( x 1 , x 2 , x 3 , x 4 ) = P ( x 1 ) P ( x 2 ∣ x 1 ) P ( x 3 ∣ x 1 , x 2 ) P ( x 4 ∣ x 2 , x 3 ) .

    P(x1,x2,x3,x4)=P(x1)P(x2)P(x3)P(x4),P(x1,x2,x3,x4)=P(x1)P(x2x1)P(x3x2)P(x4x3),P(x1,x2,x3,x4)=P(x1)P(x2x1)P(x3x1,x2)P(x4x2,x3)." role="presentation">P(x1,x2,x3,x4)=P(x1)P(x2)P(x3)P(x4),P(x1,x2,x3,x4)=P(x1)P(x2x1)P(x3x2)P(x4x3),P(x1,x2,x3,x4)=P(x1)P(x2x1)P(x3x1,x2)P(x4x2,x3).
    P(x1,x2,x3,x4)=P(x1)P(x2)P(x3)P(x4),P(x1,x2,x3,x4)=P(x1)P(x2x1)P(x3x2)P(x4x3),P(x1,x2,x3,x4)=P(x1)P(x2x1)P(x3x1,x2)P(x4x2,x3).

    通常,涉及一个、两个和三个变量的概率公式分别被称为 一元语法(unigram)、二元语法(bigram)和三元语法(trigram)模型

  • 相关阅读:
    3dmax中格式批量互转obj批量转fbx等等
    LeetCode 337. 打家劫舍 III(C++)*
    物流企业的“海外战事”
    [数据结构] 串与KMP算法详解
    基于Android的香格里拉美食分享APP/美食分享平台/基于android的美食平台
    【安卓】在安卓中使用HTTP协议的最佳实践
    子类继承了什么、多态、 向上转型
    网络协议之:redis protocol 详解
    十五、【VUE-CLI】插槽 slot
    选择探针台时需要注意哪些事项
  • 原文地址:https://blog.csdn.net/weixin_46167190/article/details/139591600