李沐深度学习

1、常用包 组件库 函数

import collections
import hashlib
import math
import os
import random
import re
import shutil
import sys
import tarfile
import time
import zipfile
from collections import defaultdict
import pandas as pd
import requests
from IPython import display
from matplotlib import pyplot as plt
from matplotlib_inline import backend_inline

%matplotlib inline
import math
import time
import numpy as np
import torch

可视化函数曲线函数

def use_svg_display():  
    """使用svg格式在Jupyter中显示绘图"""
    backend_inline.set_matplotlib_formats('svg')
    
def set_figsize(figsize=(3.5, 2.5)):  #@save
    """设置matplotlib的图表大小"""
    use_svg_display()
    plt.rcParams['figure.figsize'] = figsize
    
def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
    """设置matplotlib的轴"""
    axes.set_xlabel(xlabel)
    axes.set_ylabel(ylabel)
    axes.set_xscale(xscale)
    axes.set_yscale(yscale)
    axes.set_xlim(xlim)
    axes.set_ylim(ylim)
    if legend:
        axes.legend(legend)
    axes.grid()
    
def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None,
         ylim=None, xscale='linear', yscale='linear',
         fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None):
    """绘制数据点"""
    if legend is None:
        legend = []

    set_figsize(figsize)
    axes = axes if axes else d2l.plt.gca()

    # 如果X有一个轴,输出True
    def has_one_axis(X):
        return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list)
                and not hasattr(X[0], "__len__"))

    if has_one_axis(X):
        X = [X]
    if Y is None:
        X, Y = [[]] * len(X), X
    elif has_one_axis(Y):
        Y = [Y]
    if len(X) != len(Y):
        X = X * len(Y)
    axes.cla()
    for x, y, fmt in zip(X, Y, fmts):
        if len(x):
            axes.plot(x, y, fmt)
        else:
            axes.plot(y, fmt)
    set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)

计时器 判断运行效率

class Timer:  #@save
    """记录多次运行时间"""
    def __init__(self):
        self.times = []
        self.start()

    def start(self):
        """启动计时器"""
        self.tik = time.time()

    def stop(self):
        """停止计时器并将时间记录在列表中"""
        self.times.append(time.time() - self.tik)
        return self.times[-1]

    def avg(self):
        """返回平均时间"""
        return sum(self.times) / len(self.times)

    def sum(self):
        """返回时间总和"""
        return sum(self.times)

    def cumsum(self):
        """返回累计时间"""
        return np.array(self.times).cumsum().tolist()

2、线性神经网络

1、线性回归

从0开始

生成随机数据集

def synthetic_data(w, b, num_examples): 
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape)#加入噪声 干扰数据
    return X, y.reshape((-1, 1))

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
#生成数据集X 并根据自己预设的w和b计算得到y数据集
#接下来用X y的数据集反过来预测w b

读取数据集 打乱数据 分批次获取

def data_iter(batch_size, features, labels):
  num_examples = len(features)
  indices = list(range(num_examples))
  random.shuffle(indices)#打乱数据
  for i in range(0,num_examples,batch_size):
    batch_sizes = torch.tensor(indices[i:min(i+batch_size,num_examples)])
    yield features[batch_sizes],labels[batch_sizes]#每次返回固定大小的数据

初始化数据 接下来更新他们

w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

定义模型 损失函数 优化算法

损失函数基于公式
l i ( w 1 , w 2 , . . w k , b ) = 1 2 ( y i ^ − y i ) 2 = 1 2 ( w 1 x 1 i + w 2 x 2 i + . . . + w k x k i + b − y i ) 2 l^i(w_1,w_2,..w_k,b) = \frac{1}{2}(\hat{y^i} - yi)^2=\frac{1}{2}(w_1x_1^i+w_2x_2^i+...+w_kx_k^i+b-y^i)^2 li(w1,w2,..wk,b)=21(yi^yi)2=21(w1x1i+w2x2i+...+wkxki+byi)2
优化算法基于公式(以只有两个参数为例)
w 1 = w 1 − η ∣ β ∣ ∑ i ∈ β ∂ l i ( w 1 , w 2 , b ) ∂ w 1 = w 1 − η ∣ β ∣ ∑ i ∈ β 1 2 ( x 1 2 w 1 2 + 2 x 1 x 2 w 1 w 2 + 2 x 1 w 1 b − 2 x 1 w 1 y ) ∂ w 1 = w 1 − η ∣ β ∣ ∑ i ∈ β x 1 ( x 1 w 1 + x 2 w 2 + b − y ) w_1 = w_1 - \frac{\eta}{|\beta|}\sum_{i\in\beta} \frac{\partial l^i(w_1,w_2,b)}{\partial w_1}=w_1-\frac{\eta}{|\beta|}\sum_{i\in\beta} \frac{\frac{1}{2}(x_1^2w_1^2+2x_1x_2w_1w_2+2x_1w_1b-2x_1w_1y)}{\partial w_1}=w_1-\frac{\eta}{|\beta|}\sum_{i\in\beta}x_1(x_1w_1+x_2w_2+b-y) w1=w1βηiβw1li(w1,w2,b)=w1βηiβw121(x12w12+2x1x2w1w2+2x1w1b2x1w1y)=w1βηiβx1(x1w1+x2w2+by)

def linreg(X, w, b): 
    """线性回归模型"""
    return torch.matmul(X, w) + b
    
def squared_loss(y_hat, y):  
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
    
    
def sgd(params, lr, batch_size): 
    """小批量随机梯度下降"""
    with torch.no_grad():#这里不需要调用Tensor.backward()时可以用torch.no_grad来屏蔽梯度计算
        for param in params:
            param -= lr * param.grad / batch_size#引用的方式
            param.grad.zero_()#梯度清零
            
          #return params  

训练

lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):#小批量获取X,y
        l = loss(net(X, w, b), y)  # X和y的小批量损失
        # 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
        # 并以此计算关于[w,b]的梯度
        l.sum().backward()#backward用标量或矩阵才进行反向求导 矩阵对应为雅可比矩阵如l.backward(torch.ones_like(l))
        sgd([w, b], lr, batch_size)  # 使用参数的梯度更新参数
        #[t_w,t_b] = sgd([w, b], lr, batch_size)
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)#用更新后的w、b来验证整个数据
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
        #print(f'train_w {t_w},train_b {float(b):f}')
线性回归的简洁实现

生成数据集同上

读取数据集

def load_array(data_arrays, batch_size, is_train=True):  
    """构造一个PyTorch数据迭代器"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
data_iter = load_array((features, labels), batch_size)

iter构造Python迭代器,并使用next从迭代器中获取第一项。作为一个迭代器

next(iter(data_iter))

定义模型

# nn是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))
#第一个参数指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
#PyTorch的nn.Linear()是用于设置网络中的全连接层的,需要注意在二维图像处理的任务中,全连接层的输入与输出一般都设置为二维张量,形状通常为[batch_size, size],不同于卷积层要求输入输出是四维张量。
# in_features指的是输入的二维张量的大小,即输入的[batch_size, size]中的size。
#  out_features指的是输出的二维张量的大小,即输出的二维张量的形状为[batch_size,output_size],当然,它也代表了该全连接层的神经元个数。
#  从输入输出的张量的shape角度来理解,相当于一个输入为[batch_size, in_features]的张量变换成了[batch_size, out_features]的输出张量。

初始化模型

通过net[0]选择网络中的第一个图层, 然后使用weight.databias.data方法访问参数。 我们还可以使用替换方法normal_fill_来重写参数值。

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

定义损失函数

loss = nn.MSELoss()#调用均方损失函数

定义优化算法

小批量随机梯度下降算法是一种优化神经网络的标准工具, PyTorch在optim模块中实现了该算法的许多变种。 当我们实例化一个SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得)以及优化算法所需的超参数字典。 小批量随机梯度下降只需要设置lr值,这里设置为0.03。

常用优化器:SGD(随机梯度下降)、Adam、RMSProp

trainer = torch.optim.SGD(net.parameters(), lr=0.03)

训练

num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:#小批量获取X,y
        l = loss(net(X) ,y)
        trainer.zero_grad()
        l.backward()
        trainer.step()#不断修正w b
    l = loss(net(features), labels)#测试整个数据
    print(f'epoch {epoch + 1}, loss {l:f}')

2、softmax回归

表示分类数据的简单方法:独热编码(one-hot encoding)

独热编码是一个向量,它的分量和类别一样多。 类别对应的分量设置为1,其他所有分量设置为0。 在我们的例子中,标签y将是一个三维向量, 其中(1,0,0)对应于“猫”、(0,1,0)对应于“鸡”、(0,0,1)对应于“狗”

交叉熵 这里只关注对真实类别的预测值yi取 0或1 真实值为1 其它类别为0

H ( p , q ) = − ∑ y i l o g y ^ i = − l o g y ^ i H(p,q)= -\sum{y_ilog \hat y_i}=-log\hat y_i H(p,q)=yilogy^i=logy^i

从0开始

初始化模型参数

num_inputs = 784#将1×28×28的图片 拉成长为784的向量
num_outputs = 10#10个类别

W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

定义softmax操作

def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True)
    #在横向上相加 partition为tensor patition.shape[0] == X.shape[0]
    #keepdim=True维度不变 维度不一致 无法广播
    return X_exp / partition  # 这里应用了广播机制

定义模型

def net(X):#注意,将数据传递到模型之前,我们使用reshape函数将每张原始图像展平为向量。
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

定义损失函数

利用数组标号 来实现交叉熵

例子

#下面,我们创建一个数据样本y_hat,其中包含2个样本在3个类别的预测概率, 以及它们对应的标签y。 有了y,我们知道在第一个样本中,第一类是正确的预测; 而在第二个样本中,第三类是正确的预测。 然后使用y作为y_hat中概率的索引, 我们选择第一个样本中第一个类的概率和第二个样本中第三个类的概率。
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]

#输出为tensor([0.1000, 0.5000])

交叉熵函数

def cross_entropy(y_hat, y):
    return - torch.log(y_hat[range(len(y_hat)), y])

分类精度

作为一个衡量标准使用 精度 = 正确分类的样本数/样本数

def accuracy(y_hat, y):  #@save
    """计算预测正确的数量"""
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        #y_hat维度至少为2维即至少有2个样本数 列数至少为2列即分类数至少为两类
        y_hat = y_hat.argmax(axis=1)#在一个样本的概率中取最大值 y_hat为tensor
    cmp = y_hat.type(y.dtype) == y
    #type() 返回数据结构类型(list、dict、numpy.ndarray 等)
    #type用法该方法的功能是:当不指定dtype时,返回类型.当指定dtype时,返回类型转换后的数据,
    #如果类型已经符合要求,那么不做额外的复制,返回原对象.
    #dtype 返回数据元素的数据类型(int、float等)
    return float(cmp.type(y.dtype).sum())#true为1. false为0.0 返回所有正确预测的样本数

accuracy(y_hat, y) / len(y)#为正确率

同样,对于任意数据迭代器data_iter可访问的数据集, 我们可以评估在任意模型net的精度。

model.eval(),不启用 BatchNormalization 和 Dropout

model.eval() 负责改变batchnorm、dropout的工作方式,如在eval()模式下,dropout是不工作的 节省时间

net此时为y_hat为预测值 data_iter为真实值

def evaluate_accuracy(net, data_iter):  #@save
    """计算在指定数据集上模型的精度"""
    if isinstance(net, torch.nn.Module):#isinstance判断类型是否一致 即实例是否与类一致
        net.eval()  # 将模型设置为评估模式
    metric = Accumulator(2)  # 正确预测数、预测总数
    with torch.no_grad():
        for X, y in data_iter:
            metric.add(accuracy(net(X), y), y.numel())#numel统计张量中元素个数 即为list[正确个数,总元素个数]
    return metric[0] / metric[1]
#这里定义一个实用程序类Accumulator,用于对多个变量进行累加。 在上面的evaluate_accuracy函数中, 我们在Accumulator实例中创建了2个变量, 分别用于存储正确预测的数量和预测的总数量。 当我们遍历数据集时,两者都将随着时间的推移而累加。
class Accumulator:  #@save
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n#将合并成一个大的list [0.0,0.0,0.0,....]
        
#*args表示动态传入的参数,也就是参数传入的个数是可变的,可以是一个或者多个,在不需要的时候也可以传入不用给它传入任何值
#args会将所有传入的参数作为一个元组进行统一处理
    def add(self, *args):
        #zip() 函数来可以把 2 个或多个列表合并,并创建一个元组对的列表,元组对的数量以合并列表的最短长度为准。
      	#zip将list连接如[(metric[0],正确的个数),(metric[1],总个数)]
        #a代表为起始值或前一个状态值 b表示新的个数需将其累加
    	self.data = [a + float(b) for a, b in zip(self.data, args)]
      

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]#实现_getitem_()方法 能够像list一样访问实例 参考定制类

训练

训练一个epoch

model.train() :启用 BatchNormalization 和 Dropout。 在模型测试阶段使用model.train() 让model变成训练模式,此时 dropout和batch normalization的操作在训练q起到防止网络过拟合的问题。

def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    """训练模型一个迭代周期(定义见第3章)"""
    if isinstance(net, torch.nn.Module):
        net.train()# 将模型设置为训练模式
    # 训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)
    for X, y in train_iter:#features、labels
        # 计算梯度并更新参数
        y_hat = net(X)
        l = loss(y_hat, y)
        if isinstance(updater, torch.optim.Optimizer):
            # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad()
            l.mean().backward()
            updater.step()
        else:
            # 使用定制的优化器和损失函数
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练精度
    return metric[0] / metric[2], metric[1] / metric[2]

训练所有数据

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  #@save
    """训练模型(定义见第3章)"""
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        animator.add(epoch + 1, train_metrics + (test_acc,))
    train_loss, train_acc = train_metrics
    assert train_loss < 0.5, train_loss
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc

优化器

lr = 0.1

def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)

绘图程序

class Animator:  #@save
    """在动画中绘制数据"""
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
                 figsize=(3.5, 2.5)):
        # 增量地绘制多条线
        if legend is None:
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes, ]
        # 使用lambda函数捕获参数
        self.config_axes = lambda: d2l.set_axes(
            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):
        # 向图表中添加多个数据点
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)):
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla()
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)

训练

num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

预测

def predict_ch3(net, test_iter, n=6):  #@save
    """预测标签(定义见第3章)"""
    for X, y in test_iter:#这里选择拿出一个
        break
    trues = d2l.get_fashion_mnist_labels(y)
    preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
    titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
    d2l.show_images(
        X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

predict_ch3(net, test_iter)
softmax简洁实现
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights);

loss = nn.CrossEntropyLoss(reduction='none')

trainer = torch.optim.SGD(net.parameters(), lr=0.1)

num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

3、Fashion-MNIST数据集

下载数据

# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(
    root="../data", train=True, transform=trans, download=True)#如果已经下载好则不需下载
mnist_test = torchvision.datasets.FashionMNIST(
    root="../data", train=False, transform=trans, download=True)

用于在数字标签索引及其文本名称之间进行转换函数

def get_fashion_mnist_labels(labels): 
    """返回Fashion-MNIST数据集的文本标签"""
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
                   'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[int(i)] for i in labels]

可视化样本

def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): 
    """绘制图像列表"""
    figsize = (num_cols * scale, num_rows * scale)
    _, axes = plt.subplots(num_rows, num_cols, figsize=figsize)
    axes = axes.flatten()
    for i, (ax, img) in enumerate(zip(axes, imgs)):
        if torch.is_tensor(img):
            # 图片张量
            ax.imshow(img.numpy())
        else:
            # PIL图片
            ax.imshow(img)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
        if titles:
            ax.set_title(titles[i])
    return axes

读取小批量

为了使我们在读取训练集和测试集时更容易,我们使用内置的数据迭代器,而不是从零开始创建。 回顾一下,在每次迭代中,数据加载器每次都会读取一小批量数据,大小为batch_size。 通过内置数据迭代器,我们可以随机打乱了所有样本,从而无偏见地读取小批量。

batch_size = 256

def get_dataloader_workers():  #@save
    """使用4个进程来读取数据"""
    return 4

train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
                             num_workers=get_dataloader_workers())

整合组件

def load_data_fashion_mnist(batch_size, resize=None):  #@save
    """下载Fashion-MNIST数据集,然后将其加载到内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(
        root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans, download=True)
    return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                            num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=get_dataloader_workers()))

3、多层感知机(MLP)

1.感知机

本质是一个二分类模型 XOR函数不能拟合导致了AI第一次寒冬 用多层感知机解决了上述问题

激活函数大多数为非线性的函数 如果不使用激活函数则仍为线性模型 等价于一个单层的感知机

激活函数避免层数塌陷 一般最后一层输出不用激活函数

常用激活函数
R e l u ( x ) = m a x ( x , 0 ) Relu(x)=max(x,0) Relu(x)=max(x,0)
当输入小于0时 relu容易造成梯度消失

针对上述问题的Relu变种如pRelu该变体为ReLU添加了一个线性项,因此即使参数是负的,某些信息仍然可以通过:
p R e l u ( x ) = m a x ( x , 0 ) + α m i n ( 0 , x )   ( α 为可学习参数 ) pRelu(x)=max(x,0)+\alpha min(0,x)\space(\alpha为可学习参数) pRelu(x)=max(x,0)+αmin(0,x) (α为可学习参数)
Leaky ReLU
R e l u ( x ) = m a x ( 0.1 x , x ) Relu(x)=max(0.1x,x) Relu(x)=max(0.1x,x)

s i g m o i d ( x ) = 1 1 + e − x sigmoid(x)=\frac{1}{1+e^{-x}} sigmoid(x)=1+ex1
sigmoid常用技巧 适用于反向传播中
s i g m o i d ′ ( x ) = e − x ( 1 + e − x ) 2 = s i g m o i d ( x ) ∗ ( 1 − s i g m o i d ( x ) ) sigmoid'(x)=\frac{e^{-x}}{(1+e^{-x})^2}=sigmoid(x)*(1-sigmoid(x)) sigmoid(x)=(1+ex)2ex=sigmoid(x)(1sigmoid(x))

t a n h ( x ) = 1 − e − 2 x 1 + e − 2 x tanh(x)=\frac{1-e^{-2x}}{1+e^{-2x}} tanh(x)=1+e2x1e2x

对于sigmoid 和tanh等激活函数易知当输入过大过小容易造成梯度消失 链式传递下去会使得梯度越来越小

简洁实现

#定义网络
net = nn.Sequential(nn.Flatten(),#将图片展开成1×28×28的平滑向量
                    nn.Linear(784, 256),
                    nn.ReLU(),
                    nn.Linear(256, 10))

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)#正态分布

net.apply(init_weights);

batch_size, lr, num_epochs = 256, 0.1, 10
#直接使用高级API中的内置函数来计算softmax和交叉熵损失。
loss = nn.CrossEntropyLoss(reduction='none')
trainer = torch.optim.SGD(net.parameters(), lr=lr)

train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

2.过拟合、欠拟合

训练误差与泛化误差(永远不能准确地计算出泛化误差。 这是因为无限多的数据样本是一个虚构的对象。 在实际中,我们只能通过将模型应用于一个独立的测试集来估计泛化误差, 该测试集由随机选取的、未曾在训练集中出现的数据样本构成。)

原则上,在我们确定所有的超参数之前,我们不希望用到测试集。 如果我们在模型选择过程中使用测试数据,可能会有过拟合测试数据的风险 这里将我们的数据分成三份, 除了训练和测试数据集之外,还增加一个验证数据集

K折交叉验证:在没有足够多数据时常用 将训练数据分割成K份 依次使用第i块为验证集其余做训练集 最后取误差均值作为K折交叉验证的误差

估计模型容量 两个主要因素 参数个数 参数值范围

参数个数估计 一层线性(d + 1 d为w数 和1个bias)参数为m的一层隐藏层(m(d + 1) + k(m + 1) k为输出)

模型复杂性:一般来说高阶多项式函数比低阶多项式函数复杂得多。 高阶多项式的参数较多,模型函数的选择范围较广。 因此在固定训练数据集的情况下, 高阶多项式函数相对于低阶多项式的训练误差应该始终更低(最坏也是相等)。 事实上,当数据样本包含了x的不同值时, 函数阶数等于数据样本数量的多项式函数可以完美拟合训练集。

权重衰退 加入一个正则项就可简单理解为降低维次

数据集大小的影响训练数据集中的样本越少,我们就越有可能(且更严重地)过拟合。 随着训练数据量的增加,泛化误差通常会减小。

拟合一个d阶多项式

利用自己的理解写的一个拟合过程 仍类似于一个线性回归

import matplotlib.pyplot as plt # 增加此行
import random
import math
import torch
import numpy as np
import torchvision
from torch.utils import data
from torchvision import transforms
from torch import nn
from d2l import torch as d2l

def evaluate_loss(w,data_iter,loss):
    metric = d2l.Accumulator(2)
    for X,y in data_iter:
        l = loss(net(X,w),y)
        metric.add(l.sum(),l.numel())
    return metric[0] / metric[1]

def sgd(params, lr, batch_size):
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size 
            param.grad.zero_()  
def net(X,w):
    return torch.matmul(X,w)

def get_data(w,num):
    pow_x = torch.arange(4)#0,1,2,3
    X = torch.normal(0,1,size = (num,1))
    power_x = torch.pow(X,pow_x)#广播机制
    for i in range(4):
        power_x[:,i] /= math.gamma(i + 1)
    y = torch.matmul(power_x,w)
    y += torch.normal(0,0.01,y.shape)
    return power_x,y

true_w = torch.tensor([[5.],[1.2],[-3.4],[5.6]])
features,lables = get_data(true_w,200)#create 200 data
train_iter = d2l.load_array((features[:100],lables[:100]),10,True)#100 train
test_iter = d2l.load_array((features[100:],lables[100:]),10,True)#100 test
w = torch.normal(0, 0.1, size=(4,1), requires_grad=True)

animator = d2l.Animator(xlabel='epoch', ylabel='loss', yscale='log',
                            xlim=[1, 200], ylim=[1e-5, 1e2],
                            legend=['train', 'test'])
for epoch in range(200):
    for X, y in train_iter:
        l = d2l.squared_loss(net(X, w), y)
        l.sum().backward()
        sgd([w], 0.08, 10)  # 使用参数的梯度更新参数
    with torch.no_grad():
        animator.add(epoch + 1, (evaluate_loss(w,train_iter, d2l.squared_loss),
                                 evaluate_loss(w,test_iter, d2l.squared_loss)))
print(w)
plt.show() 

loss还不错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwuZO5Wk-1666785491391)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221001224032881.png)]

控制只拟合w的前两个权重时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KvHGqcah-1666785491394)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221001230134856.png)]

扩充w的权重个数时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOyxoZGY-1666785491395)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221001230825816.png)]

3.权重衰退

控制模型容量方法:控制参数大小、控制参数范围(值的选择 :权重衰退)

使用均方范数作为硬性限制 目的让w值控制在一定范围 通常不限制b
l ( w , b ) ( l o s s f u n c )    s u b j e c t   t o   ∣ ∣ w ∣ ∣ 2 ≤ θ l(w,b)(loss func)\space \space subject\space to\space ||w||^2 \leq \theta l(w,b)(lossfunc)  subject to ∣∣w2θ

一般做法
l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 ( λ → 0 无作用  λ → ∞   w → 0 ) l(w,b)+\frac{\lambda}{2}||w||^2(\lambda\to0无作用\space \lambda\to\infty\space w\to0) l(w,b)+2λ∣∣w2(λ0无作用 λ w0)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BxN4N8l9-1666785491396)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221001232252729.png)]

对w进行更新

w = w − η ∂ l ( w , b ) + λ 2 ∣ ∣ w ∣ ∣ 2 ∂ w = w − η ( λ w + ∂ l ( w , b ) ∂ w ) = ( 1 − η λ ) w − η ∂ l ( w , b ) ∂ w ( η λ < 1 ) 被称为权重衰退每一次更新都缩小 w 在调用函数时直接传入此参数即可 w=w-\eta\frac{\partial l(w,b)+\frac{\lambda}{2}||w||^2}{\partial w}=w-\eta(\lambda w+\frac{\partial l(w,b)}{\partial w})=(1-\eta\lambda)w-\eta\frac{\partial l(w,b)}{\partial w}\\(\eta\lambda<1)被称为权重衰退每一次更新都缩小w在调用函数时直接传入此参数即可 w=wηwl(w,b)+2λ∣∣w2=wη(λw+wl(w,b))=(1ηλ)wηwl(w,b)(ηλ<1)被称为权重衰退每一次更新都缩小w在调用函数时直接传入此参数即可
简洁实现

def train_concise(wd):
    net = nn.Sequential(nn.Linear(num_inputs, 1))
    for param in net.parameters():
        param.data.normal_()
    loss = nn.MSELoss(reduction='none')
    num_epochs, lr = 100, 0.003
    # 偏置参数没有衰减
    trainer = torch.optim.SGD([#直接传入衰退参数即可
        {"params":net[0].weight,'weight_decay': wd},
        {"params":net[0].bias}], lr=lr)
    animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log',
                            xlim=[5, num_epochs], legend=['train', 'test'])
    for epoch in range(num_epochs):
        for X, y in train_iter:
            trainer.zero_grad()
            l = loss(net(X), y)
            l.mean().backward()
            trainer.step()
        if (epoch + 1) % 5 == 0:
            animator.add(epoch + 1,
                         (d2l.evaluate_loss(net, train_iter, loss),
                          d2l.evaluate_loss(net, test_iter, loss)))
    print('w的L2范数:', net[0].weight.norm().item())

4.丢弃法

动机:一个好的模型需要对输入数据的扰动具有一定的鲁棒性故无偏差的加入噪音

5.前向传播与反向传播

(结合了cs231n 具体公式计算参照教材)

*为举矩阵乘法 R为正则化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eobZLroz-1666785491398)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20220928231741744.png)]

示例如下:

起初为1在于df/df = 1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wmaU6nQ5-1666785491399)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20220928232203876.png)]

sigmoid函数的特性 sigmoid(x) = 0.73 又dsigmoid(x)/dx = (1 - sigmoid(x))sigmoid(x) = (1 - 0.73)×0.73 = 0.2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fsoXqoBg-1666785491400)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20220928233046108.png)]

6.数值稳定性与初始化

优化数值初始化的目的:不好的输入数据可能导致梯度消失、梯度爆炸 一点扰动就可能导致后果 即让训练更稳定

梯度消失: 参数更新过小,在每次更新时几乎不会移动,导致模型无法学习

梯度爆炸:参数更新过大,破坏了模型的稳定收敛

方法:将乘法变加法(Resnet、LSTM)、归一化(梯度归一化、梯度裁剪)、合理的权重初始化和激活函数

目标:让梯度值在合理的范围内,如[1e-6,1e3] 将每层的输出和梯度都看做随机变量 让它们的均值和方差都保持一致

对于浅的网络 使用N(0,0.01)初始化没问题 但对于深的网络可能就有问题

Xavier方式参数优化

假设参数之间独立同分布、权重之间独立同分布,即
E ( x ) = 0   σ ( x ) = σ 2   E ( w ) = 0   σ ( w ) = γ 2 E(x)=0\space \sigma(x)=\sigma^2\space E(w)=0\space\sigma(w)=\gamma^2 E(x)=0 σ(x)=σ2 E(w)=0 σ(w)=γ2
由于参数之间、权重之间独立同分布 i代表第i层

正向传播上的推算:
E ( o u t i ) = E ( ∑ w i j x j ) = ∑ E ( w i j ) E ( x j ) = 0 σ ( o u t i ) = E ( o u t i 2 ) − E ( o u t i ) 2 = E ( ∑ w i j 2 x i 2 + ∑ w i j w i k x j x k ) − 0 = ∑ E ( w i j 2 x i 2 ) + 0 = ∑ E ( w i j 2 ) E ( x i 2 ) = n i n γ 2 σ 2 E(out_i)=E(\sum w_{ij}x_j)=\sum E(w_{ij})E(x_j)=0 \\ \sigma(out_i)=E(out_i^2)-E(out_i)^2=E(\sum w_{ij}^2x_i^2 + \sum w_{ij}w_{ik}x_jx_k )-0=\sum E(w_{ij}^2x_i^2)+0=\sum E(w_{ij}^2)E(x_i^2)=n_{in}\gamma^2\sigma^2 E(outi)=E(wijxj)=E(wij)E(xj)=0σ(outi)=E(outi2)E(outi)2=E(wij2xi2+wijwikxjxk)0=E(wij2xi2)+0=E(wij2)E(xi2)=ninγ2σ2
为使后面所有层的参数都分布一致即同期望、方差 则令
n i n γ 2 = 1 n_{in}\gamma^2=1 ninγ2=1
反向传播上的推算:
∂ l ∂ x t − 1 = ∂ l ∂ x t W t E ( ∂ l ∂ x t − 1 ) = 0 σ ( ∂ l ∂ x t − 1 ) = n o u t γ 2 v a r ( ∂ l ∂ x t ) \frac{\partial l}{\partial x_{t-1}}=\frac{\partial l}{\partial x_t}W_t\\E(\frac{\partial l}{\partial x_{t-1}})=0\\ \sigma(\frac{\partial l}{\partial x_{t-1}})=n_{out}\gamma^2var(\frac{\partial l}{\partial x_t}) xt1l=xtlWtE(xt1l)=0σ(xt1l)=noutγ2var(xtl)
为使后面所有层的梯度都分布一致即同期望、方差 则令
n o u t γ 2 = 1 n_{out}\gamma^2=1 noutγ2=1
显然不能同时满足则取折中
γ 2 n i n + n o u t 2 = 1     γ 2 = 2 n i n + n o u t \gamma^2 \frac{n_{in}+n_{out}}{2}=1\space\space\space \gamma^2=\frac{2}{n_{in}+n_{out}} γ22nin+nout=1   γ2=nin+nout2
故初始化权重时可考虑正态分布、均匀分布如下:
N ( 0 , 2 / ( n t − 1 + n t ) ) U [ − 6 / ( n t − 1 + n t ) , 6 / ( n t − 1 + n t ) ] N(0,\sqrt{2/(n_{t-1}+n_t)})\\U[-\sqrt{6/(n_{t-1}+n_t)},\sqrt{6/(n_{t-1}+n_t)}] N(0,2/(nt1+nt) )U[6/(nt1+nt) ,6/(nt1+nt) ]
对于如果含有激活函数 假设激活函数是一个线性函数
a c t i v a t e   f u n c t i o n = f ( x ) = α x + β E ( h t ) = E ( f ( x ) ) = β V a r ( h t ) = E ( h t 2 ) − E ( h t ) 2 = α 2 V a r ( h t − 1 ) activate\space function=f(x) = \alpha x+\beta\\E(h_t)=E(f(x))=\beta\\Var(h_t)=E(h_t^2)-E(h_t)^2=\alpha^2Var(h_{t-1}) activate function=f(x)=αx+βE(ht)=E(f(x))=βVar(ht)=E(ht2)E(ht)2=α2Var(ht1)
若想使下一层仍保持一样的方差、期望则
α = 1   β = 0 \alpha=1\space \beta=0 α=1 β=0
调整现有激活函数(用泰勒展开)
s i g m o i d ( x ) = 1 2 + x 4 − x 3 48 + O ( x 5 ) t a n h ( x ) = 0 + x − x 3 3 + O ( x 5 ) r e l u ( x ) = 0 + x sigmoid(x)=\frac{1}{2}+\frac{x}{4}-\frac{x^3}{48}+O(x^5)\\ tanh(x)=0+x-\frac{x^3}{3}+O(x^5)\\ relu(x)=0 +x sigmoid(x)=21+4x48x3+O(x5)tanh(x)=0+x3x3+O(x5)relu(x)=0+x
调整sigmoid:4×sigmoid-2

4、深度学习计算

1.层和块

2.参数管理

3.延后初始化

4.自定义层

5.读写文件

6.GPU

5、卷积神经网络

卷积层

MLP处理图像的难点:每张照片具有百万级像素,这意味着网络的每次输入都有一百万个维度。 即使将隐藏层维度降低到1000,这个全连接层也将有106×103=109个参数。 想要训练这个模型将不可实现,因为需要有大量的GPU、分布式优化训练。

图像分类器的原则:平移不变性(不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应),局部性(神经网络的前面几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系)

互相关运算:基本公式 Input * kernel = Output 默认kernel也是三维channel值==Input的channel(黑白图片通道为1,彩色为3)

也可用于一维(文本、语言、时序序列)、三维(视频)

互相关运算

def corr2d(X, K):  
    """计算二维互相关运算"""
    h, w = K.shape
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
    return Y

特征映射:输出的卷积层有时被称为特征映射(feature map),因为它可以被视为一个输入映射到下一层的空间维度的转换器

感受野(receptive field):卷积神经网络中,对于某一层的任意元素x,其感受野是指在前向传播期间可能影响x计算的所有元素(来自所有先前层)。感受野可能大于输入的实际大小。

填充和步幅

两种控制输出大小的超参数

有时,在应用了连续的卷积之后,我们最终得到的输出远小于输入大小。这是由于卷积核的宽度和高度通常大于1所导致的。比如,一个240×240像素的图像,经过10层5×5的卷积后,将减少到200×200像素。如此一来,原始图像的边界丢失了许多有用信息。而填充是解决此问题最有效的方法。 有时,我们可能希望大幅降低图像的宽度和高度。例如,如果我们发现原始的输入分辨率十分冗余。步幅则可以在这类情况下提供帮助。

添加ph行填充(大约一半在顶部,一半在底部)和pw列填充(左侧大约一半,右侧一半),则输出形状将为
( n h − k h + p h + 1 ) ∗ ( n w − k w + p w + 1 ) (n_h-k_h+p_h+1)*(n_w-k_w+p_w+1) (nhkh+ph+1)(nwkw+pw+1)
在许多情况下,我们需要设置ph=kh-1和pw=kw−1,使输入和输出具有相同的高度和宽度。 这样可以在构建网络时更容易地预测每个图层的输出形状。假设kh是奇数,我们将在高度的两侧填充ph/2行。 如果kh是偶数,则一种可能性是在输入顶部填充⌈ph/2⌉行,在底部填充⌊ph/2⌋行。同理,我们填充宽度的两侧。

当垂直步幅为sh、水平步幅为sw时,输出形状为
[ ( n h − k h + p h + s h + 1 ) / s h ] ∗ [ ( n w − k w + p w + s w + 1 ) / s w ] [(n_h-k_h+p_h+s_h+1)/s_h]*[(n_w-k_w+p_w+s_w+1)/s_w] [(nhkh+ph+sh+1)/sh][(nwkw+pw+sw+1)/sw]
如果我们设置了ph=kh−1和pw=kw−1,则输出形状将简化为⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋。 更进一步,如果输入的高度和宽度可以被垂直和水平步幅整除,则输出形状将为(nh/sh)×(nw/sw)。

简洁使用填充和步幅:

conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
#torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
#in_channels:输入图像通道数;out_channels:卷积产生的通道数

多输入多输出通道

彩色图像有RGB三个通道

对于一个3channel图片 对应的卷积核默认为3channel的kernel 一个kernel的参数个数为kc×kh×kw 输出为单通道矩阵 运算法则为每一个卷积核的通道在对应的图片上滑动然后相加 故得到单输出通道的矩阵 如果有n个kernel则会得到n个channel的输出通道的矩阵

1×1卷积核作用:融合不同通道的信息 调整网络层的通道数量和控制模型复杂性(可以调整参数个数) 比如可以将一个3通道的矩阵转换成其他通道数的矩阵

池化层(Pooling)

减少卷积层对位置的敏感性,实验可参考前面的垂直边缘检测(转置后没法检测可考虑最大池化层)

最大汇聚层:每个窗口最强的模式信号

平均汇聚层:平均的信号强度,比较柔和

填充和步幅:没有可学习的参数只有超参数

多通道:在处理多通道输入数据时,汇聚层在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。 这意味着汇聚层的输出通道数与输入通道数相同。

简洁实现:

pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1))

LeNet

架构图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJitNSQR-1666785491402)(https://zh.d2l.ai/_images/lenet.svg)]

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),#当时还没有Relu
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Flatten(),#展开
    nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.Sigmoid(),
    nn.Linear(84, 10))#识别0~9的数字

AlexNet

相比于LeNet更深更大,丢弃法,Relu(梯度更大),MaxPooling 。使用了更多的卷积层和更多的参数来拟合大规模的ImageNet数据集

AlexNet的最大贡献是直接通过CNN学习提取特征没有像以前通过人工提取特征再用机器学习算法(SVM)

架构:

net = nn.Sequential(
    # 这里,我们使用一个11*11的更大窗口来捕捉对象。
    # 同时,步幅为4,以减少输出的高度和宽度。
    # 另外,输出通道的数目远大于LeNet
    nn.Conv2d(1, 96, kernel_size=11, stride=4, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
    nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    # 使用三个连续的卷积层和较小的卷积窗口。
    # 除了最后的卷积层,输出通道的数量进一步增加。
    # 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度
    nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(),
    nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Flatten(),
    # 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合
    nn.Linear(6400, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    nn.Linear(4096, 4096), nn.ReLU(),
    nn.Dropout(p=0.5),
    # 最后是输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
    nn.Linear(4096, 10))

VGG

带有3×3卷积核、填充为1(保持高度和宽度)的卷积层,和带有2×2汇聚窗口、步幅为2(每个块后的分辨率减半)的最大汇聚层为块。不同次数的重复块得到不同的架构。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-37InIifa-1666785491404)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221007215721699.png)]

NiN

NiN块以一个普通卷积层开始,后面是两个1×1的卷积层。这两个1×1卷积层充当带有ReLU激活函数的逐像素全连接层。 第一层的卷积窗口形状通常由用户设置。 随后的卷积窗口形状固定为1×1。

NiN和AlexNet之间的一个显著区别是NiN完全取消了全连接层。 相反,NiN使用一个NiN块,其输出通道数等于标签类别的数量。最后放一个全局平均汇聚层(global average pooling layer),生成一个对数几率 (logits)。NiN设计的一个优点是,它显著减少了模型所需参数的数量。(一般如果最后是FC参数量会很大)

GoogleNet

建议参考cs231n 巧妙运用1×1块调整网络

Resnet

批量归一化

6、计算机视觉

图像增广

增强泛化性 语音:可以通过加入噪音 图片:裁剪(减少对位置的依赖)、调整亮度、颜色、形状等因素来降低模型对颜色的敏感度

辅助函数apply

def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
    Y = [aug(img) for _ in range(num_rows * num_cols)]#循环8次并存进Y
    d2l.show_images(Y, num_rows, num_cols, scale=scale)

随机翻转各有50%

apply(img, torchvision.transforms.RandomHorizontalFlip())#左右翻转
apply(img, torchvision.transforms.RandomVerticalFlip())#上下翻转

随机裁剪

#随机裁剪一个面积为原始面积10%到100%的区域,该区域的宽高比从0.5到2之间随机取值。 然后,区域的宽度和高度都被缩放到200像素
shape_aug = torchvision.transforms.RandomResizedCrop(
    (200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)

随机改变颜色

#可以改变图像颜色的四个方面:亮度、对比度、饱和度和色调
#这里只改变了亮度为±50%上下均匀随机
apply(img, torchvision.transforms.ColorJitter(
    brightness=0.5, contrast=0, saturation=0, hue=0))

用compose函数结合多种增广方法

augs = torchvision.transforms.Compose([
    torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug])
apply(img, augs)

微调

迁移学习的常见技巧

目标检测和边界框

目标检测不仅可以识别图像中所有感兴趣的物体,还能识别它们的位置,该位置通常由矩形边界框表示。

边界框:一个边缘框可以通过四个数字定义左上角x,y坐标 右下标x,y坐标或者一个角的x,y坐标和长宽

两种边界框表示法

def box_corner_to_center(boxes):
    """从(左上,右下)转换到(中间,宽度,高度)"""
    x1, y1, x2, y2 = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    cx = (x1 + x2) / 2
    cy = (y1 + y2) / 2
    w = x2 - x1
    h = y2 - y1
    boxes = torch.stack((cx, cy, w, h), axis=-1)
    return boxes

def box_center_to_corner(boxes):
    """从(中间,宽度,高度)转换到(左上,右下)"""
    cx, cy, w, h = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
    x1 = cx - 0.5 * w
    y1 = cy - 0.5 * h
    x2 = cx + 0.5 * w
    y2 = cy + 0.5 * h
    boxes = torch.stack((x1, y1, x2, y2), axis=-1)
    return boxes

手动框图

dog_bbox, cat_bbox = [60.0, 45.0, 378.0, 516.0], [400.0, 112.0, 655.0, 493.0]

def bbox_to_rect(bbox, color):
    # 将边界框(左上x,左上y,右下x,右下y)格式转换成matplotlib格式:
    # ((左上x,左上y),宽,高)
    return d2l.plt.Rectangle(
        xy=(bbox[0], bbox[1]), width=bbox[2]-bbox[0], height=bbox[3]-bbox[1],
        fill=False, edgecolor=color, linewidth=2)
#xy: 2元组 矩形左下角xy坐标;width:矩形的宽度;height:矩形的高度;angle: float, 可选,矩形相对于x轴逆时针旋转角度,默认0
#fill: bool, 可选,是否填充矩形
fig = d2l.plt.imshow(img)
fig.axes.add_patch(bbox_to_rect(dog_bbox, 'blue'))
fig.axes.add_patch(bbox_to_rect(cat_bbox, 'red'));

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I94gB6bX-1666785491405)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221008220708964.png)]

锚框

生成多个锚框

交并比:评价好坏

7、循环神经网络

嵌入层(Embedding)

序列模型

在时间t观察到 xt 不同的x为不独立的值
x t ∼ P ( x t ∣ x 1 , . . . x t − 1 ) x_t\sim P(x_t\mid x_1,...x_{t - 1}) xtP(xtx1,...xt1)
使用条件概率乘法公式展开
P ( x 1 , . . x t ) = P ( x t ∣ x 1 , . . x t − 1 ) ⋅ P ( x t − 1 ∣ x 1 , . . x t − 2 ) ⋅ . . . P ( x 3 ∣ x 1 , x 2 ) ⋅ P ( x 2 ∣ x 1 ) ⋅ P ( x 1 ) P(x_1,..x_t)=P(x_t\mid x_1,..x_{t-1})\cdot P(x_{t-1}\mid x_1,..x_{t-2})\cdot ...P(x_3\mid x_1,x_2)\cdot P(x_2\mid x_1)\cdot P(x_1) P(x1,..xt)=P(xtx1,..xt1)P(xt1x1,..xt2)...P(x3x1,x2)P(x2x1)P(x1)
也可以逆推表示xt与x1之间的关系 根据现在发生的事件推测前面的事 但在物理上有时不一定成立 比如当前面存在才能有后面的时候

然而,在许多情况下,数据存在一个自然的方向,即在时间上是前进的。 很明显,未来的事件不能影响过去。 因此,如果我们改变xt,可能会影响未来发生的事情xt+1,但不能反过来。
P ( x 1 , . . x t ) = P ( x 1 ∣ x 2 , . . x t − 1 ) . . . ⋅ P ( x t − 1 ∣ x t ) ⋅ P ( x t ) P(x_1,..x_t)=P(x_1\mid x_2,..x_{t-1})...\cdot P(x_{t-1}\mid x_t)\cdot P(x_t) P(x1,..xt)=P(x1x2,..xt1)...P(xt1xt)P(xt)
自回归模型 比如在一个回归模型中用到的标签就是自己本身称自回归 不同于图片 图片与标签是两类

对自回归建模
P ( x t ∣ x 1 , . . x t − 1 ) = P ( x t ∣ f ( x 1 , . . x t − 1 ) ) P(x_t\mid x_1,..x_{t-1})=P(x_t\mid f(x_1,..x_{t-1})) P(xtx1,..xt1)=P(xtf(x1,..xt1))
马尔可夫模型 当前数据只和过去的m个数据有关
P ( x t ∣ x 1 , . . x t − 1 ) = P ( x t ∣ x t − m , . . x t − 1 ) = P ( x t ∣ f ( x 1 , . . x t − 1 ) ) P(x_t\mid x_1,..x_{t-1})=P(x_t\mid x_{t-m},..x_{t-1})=P(x_t\mid f(x_1,..x_{t-1})) P(xtx1,..xt1)=P(xtxtm,..xt1)=P(xtf(x1,..xt1))
用 0995,1996,2997,3998,训练4~1000

def init_weight(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)#xavier方式初始化 防止梯度消失 梯度爆炸等

def get_net():
    net = nn.Sequential(nn.Linear(4,10),
                        nn.ReLU(),
                        nn.Linear(10,1))
    net.apply(init_weight)
    return net
loss = nn.MSELoss()

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()
        print(f'epoch {epoch + 1}, '
              f'loss: {d2l.evaluate_loss(net, train_iter, loss):f}')

T = 1000  # 总共产生1000个点
time = torch.arange(1, T + 1, dtype=torch.float32)
x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))

tau = 4
features = torch.zeros(T - tau,tau)
for i in range(tau):
    features[:,i] = x[i:T - tau + i]

labels = x[tau:].reshape(-1,1)
batch_size,n_train = 16,600
train_iter = d2l.load_array((features[:600],labels[:600]),batch_size,is_train=True)

net = get_net()
train(net, train_iter, loss, 5, 0.01)

预测

#单步预测
onestep_preds = net(features)
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))

多步预测结果很差表明:马尔可夫的预测误差来源每次都会有误差 导致下一次的误差 一直会将误差拉大 如果是长时间的预测将误差过大

潜变量模型

引入潜变量ht 来表示过去的信息
h t = f ( x 1 , . . , x t − 1 )         x t = p ( x t ∣ h t ) h_t =f(x_1,..,x_{t-1})\space\space\space\space\space\space\space x_t=p(x_t\mid h_t) ht=f(x1,..,xt1)       xt=p(xtht)

文本预处理

文本的常见预处理步骤:

  1. 将文本作为字符串加载到内存中。
  2. 将字符串拆分为词元(如单词和字符)。
  3. 建立一个词表,将拆分的词元映射到数字索引。
  4. 将文本转换为数字索引序列,方便模型操作。

读取数据集(使用较小的语料库大约30000个单词)

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(lines[0])the time machine by h g wells
#原始数据:The Time Machine, by H. G. Wells [1898]

词元化:依据需求拆分成单词或单个字母

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)

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

#更换词语的索引从次数到高低的排序如1、2、3、..1为最高的词频的单词
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)
        #counter.items()列表套着元组 从字典到可遍历的元组数组 以出现次数降次排序
        self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
                                   reverse=True)
        # 未知词元的idx值为0
        self.idx_to_token = ['<unk>'] + reserved_tokens
        #赋予索引词频最高的idx = 1
        self.token_to_idx = {token: idx
                             for idx, token in enumerate(self.idx_to_token)}# enumerate遍历时同时返回idx、value
        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)#统计单词个数计数并返回一个字典(dict类型)

整合功能 :我们在这里所做的改变是:

  1. 为了简化后面章节中的训练,我们使用字符(而不是单词)实现文本词元化;
  2. 时光机器数据集中的每个文本行不一定是一个句子或一个段落,还可能是一个单词,因此返回的corpus仅处理为单个列表,而不是使用多词元列表构成的一个列表。
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)

语言模型和数据集

马尔可夫N元语法:一元语法(各单词互相独立)、二元语法、三元语法(后两者使用较多)

使用time-machine数据集验证齐普夫定律

tokens = d2l.tokenize(d2l.read_time_machine())
#一元语法
vocab = d2l.Vocab(tokens)
vocab.token_freqs[:10]

corpus = [token for line in tokens for token in line]
#二元
bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])]
bigram_vocab = d2l.Vocab(bigram_tokens)
bigram_vocab.token_freqs[:10]
#三元
trigram_tokens = [triple for triple in zip(corpus[:-2], corpus[1:-1], corpus[2:])]
trigram_vocab = d2l.Vocab(trigram_tokens)
trigram_vocab.token_freqs[:10]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vhE5CLaZ-1666785491406)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221011153829990.png)]

发现一元语法词频衰减最快

读取长序列数据(如何从一个很长的原始资料生成批量数据):随机采样、顺序分区

随机采样:让所有数据只使用一次

在随机采样中,每个样本都是在原始的长序列上任意捕获的长为num_step子序列,并让其后面的词元为label y(即错一位且长度也为num_step)。 在迭代过程中,来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻。 对于语言建模,目标是基于到目前为止我们看到的词元来预测下一个词元, 因此标签是移位了一个词元的原始序列。

具体做法:随机地从0~T随机选择k然后以k为开始每段长为num_step划分成(len - k)//num_step

def seq_data_iter_random(corpus, batch_size, num_steps):  #num_steps为取num_step长度为样本
    """使用随机抽样生成一个小批量子序列"""
    # 从随机偏移量开始对序列进行分区,随机范围包括num_steps-1
    corpus = corpus[random.randint(0, num_steps - 1):]
    # 减去1,是因为我们需要考虑标签
    num_subseqs = (len(corpus) - 1) // num_steps#必须减1 如果不减且能刚好整除则最后个样本没有label y
    # 长度为num_steps的子序列的起始索引
    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
    for i in range(0, batch_size * num_batches, batch_size):
        # 在这里,initial_indices包含子序列的随机起始索引
        initial_indices_per_batch = initial_indices[i: i + batch_size]
        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)

顺序分区:保证两个相邻的小批量中的子序列在原始序列上也是相邻的。 这种策略在基于小批量的迭代过程中保留了拆分的子序列的顺序,因此称为顺序分区。

def seq_data_iter_sequential(corpus, batch_size, num_steps):  #@save
    """使用顺序分区生成一个小批量子序列"""
    # 从随机偏移量开始划分序列
    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 + 1 + num_tokens])
    Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)
    num_batches = Xs.shape[1] // num_steps
    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

循环神经网络

背景:对于某个时间之前的那些单词, 如果我们想将其可能产生的影响合并到xt上, 需要增加n,然而模型参数的数量也会随之呈指数增长。因此考虑使用隐变量,用一个值ht-1代替x1…xt-1的影响。

其中ht−1是隐状态(hidden state), 也称为隐藏变量(hidden variable), 它存储了到时间步t−1的序列信息。并且可以基于当前输入xt和先前隐状态ht−1 来计算时间步t处的任何时间的隐状态:ht = f(xt,ht-1)

KaTeX parse error: Invalid size: 'batch\_size,num\_input' at position 117: …m\_hiddens可调\\ [̲b̲a̲t̲c̲h̲\̲_̲s̲i̲z̲e̲,̲n̲u̲m̲\̲_̲i̲n̲p̲u̲t̲]̲*[num\_input,nu…
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wBsquuEP-1666785491408)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221021235450886.png)]

困惑值:评估语言模型
e x p ( − 1 n ∑ t = 1 n l o g P ( x t ∣ x t − 1 , . . . x 1 ) ) exp(-\frac{1}{n}\sum_{t=1}^n logP(x_t|x_{t-1},...x_1)) exp(n1t=1nlogP(xtxt1,...x1))
困惑度的最好的理解是“下一个词元的实际选择数的调和平均数”。案例如下:

  • 在最好的情况下,模型总是完美地估计标签词元的概率为1。 在这种情况下,模型的困惑度为1。
  • 在最坏的情况下,模型总是预测标签词元的概率为0。 在这种情况下,困惑度是正无穷大。
  • 在基线上,该模型的预测是词表的所有可用词元上的均匀分布。 在这种情况下,困惑度等于词表中唯一词元的数量。 事实上,如果我们在没有任何压缩的情况下存储序列, 这将是我们能做的最好的编码方式。 因此,这种方式提供了一个重要的上限, 而任何实际模型都必须超越这个上限。
从0开始

torch.nn.functional.one-hot函数:将每个词元表示为一个特征向量。假设词表中不同词元的数目为N(即len(vocab)), 词元索引的范围为0到N−1。 如果词元的索引是整数i, 那么我们将创建一个长度为N的全0向量, 并将第i处的元素设置为1。 此向量是原始词元的一个独热向量。

初始化参数:Wxh,Whh,Whq,b_h,b_q

def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size#最后做softmax 分类任务

    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01

    # 隐藏层参数
    W_xh = normal((num_inputs, num_hiddens))#词表大小 * 隐状态大小
    W_hh = normal((num_hiddens, num_hiddens))
    b_h = torch.zeros(num_hiddens, device=device)
    # 输出层参数
    W_hq = normal((num_hiddens, num_outputs))
    b_q = torch.zeros(num_outputs, device=device)
    # 附加梯度
    params = [W_xh, W_hh, b_h, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)
    return params

定于初始隐状态H0,这里以0填充

def init_rnn_state(batch_size, num_hiddens, device):
    #将隐藏状态放进一个tuple里
    #实际上RNN此处只需一个元素 但是LSTM处会看到需要了两个元素
    return (torch.zeros((batch_size, num_hiddens), device=device), )

定义计算隐状态和输出函数:

def rnn(inputs, state, params):
    # inputs的形状:(时间步数量,批量大小,词表大小)
    W_xh, W_hh, b_h, W_hq, b_q = params
    H, = state#state为tuple 取第一个元素
    outputs = []
    # X的形状:(批量大小,词表大小)
    #由于需要从前依次往后计算H 所以如果输入的是[时间步,词表]则没法用矩阵乘法计算
    #因此这里使用[批量大小,词表大小]即每次把一个批量且在这一个时间段里的X计算
    for X in inputs:
        H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)#前一个时间的H来更新H
        Y = torch.mm(H, W_hq) + b_q#用当前的H
        outputs.append(Y)
    #拼接后变为batch_size * time_step个行数,列为词表长度的矩阵
    return torch.cat(outputs, dim=0), (H,)#输出output Y以及当前隐状态H

整合以上函数为RNN模块:

class RNNModelScratch: 
    """从零开始实现的循环神经网络模型"""
    def __init__(self, vocab_size, num_hiddens, device,
                 get_params, init_state, forward_fn):
        self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
        self.params = get_params(vocab_size, num_hiddens, device)
        self.init_state, self.forward_fn = init_state, forward_fn

    def __call__(self, X, state):
        X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
        return self.forward_fn(X, state, self.params)

    def begin_state(self, batch_size, device):
        return self.init_state(batch_size, self.num_hiddens, device)

实例如下:

X = torch.arange(10).reshape((2, 5))
F.one_hot(X.T, 28).shape#时间步为5,批量数为2,单词表长度为28
#for t in X: t为如: [[1,0,0,...0],[0,0,0,0,1,...0]]
num_hiddens = 512#设置为512
net = RNNModelScratch(len(vocab), num_hiddens, d2l.try_gpu(), get_params,
                      init_rnn_state, rnn)
state = net.begin_state(X.shape[0], d2l.try_gpu())
Y, new_state = net(X.to(d2l.try_gpu()), state)#_call_函数
Y.shape, len(new_state), new_state[0].shape
#输出结果为(torch.Size([10, 28]), 1, torch.Size(2, 512]))

梯度裁剪:

对于长度为T的序列,我们在迭代中计算这T个时间步上的梯度, 将会在反向传播过程中产生长度为O(T)的矩阵乘法链,当T较大时,它可能导致数值不稳定, 例如:梯度爆炸或梯度消失。 因此,循环神经网络模型往往需要额外的方式来支持稳定训练。
g ← m i n ( 1 , θ ∣ ∣ g ∣ ∣ ) g g\leftarrow min(1,\frac{\theta}{||g||})g gmin(1,∣∣g∣∣θ)g
通过这样做,我们知道梯度范数永远不会超过θ, 并且更新后的梯度完全与g的原始方向对齐。 它还有一个值得拥有的副作用, 即限制任何给定的小批量数据(以及其中任何给定的样本)对参数向量的影响, 这赋予了模型一定程度的稳定性。 梯度裁剪提供了一个快速修复梯度爆炸的方法, 虽然它并不能完全解决问题,但它是众多有效的技术之一。

自定义梯度裁剪函数:

def grad_clipping(net, theta):  
    """裁剪梯度"""
    if isinstance(net, nn.Module):
        params = [p for p in net.parameters() if p.requires_grad]
    else:
        params = net.params
    norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))
    if norm > theta:
        for param in params:
            param.grad[:] *= theta / norm

预测函数:首先定义预测函数来生成prefix之后的新字符, 其中的prefix是一个用户提供的包含多个字符的字符串。 在循环遍历prefix中的开始字符时, 我们不断地将隐状态传递到下一个时间步,但是不生成任何输出。 这被称为预热(warm-up)期, 因为在此期间模型会自我更新(例如,更新隐状态), 但不会进行预测。 预热期结束后,隐状态的值通常比刚开始的初始值更适合预测, 从而预测字符并输出它们。

def predict_ch8(prefix, num_preds, net, vocab, device):  #num_preds需要预测的个数
    """在prefix后面生成新字符"""
    state = net.begin_state(batch_size=1, device=device)
    outputs = [vocab[prefix[0]]]
    get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))
    for y in prefix[1:]:  # 预热期
        _, state = net(get_input(), state)
        outputs.append(vocab[y])
    for _ in range(num_preds):  # 预测num_preds步
        y, state = net(get_input(), state)
        outputs.append(int(y.argmax(dim=1).reshape(1)))
    return ''.join([vocab.idx_to_token[i] for i in outputs])

训练:

简洁实现
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)
#初始化初始隐状态
state = torch.zeros((1, batch_size, num_hiddens))
X = torch.rand(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)#Y在RNN这里没有连接cat
#@save
class RNNModel(nn.Module):
    """循环神经网络模型"""
    def __init__(self, rnn_layer, vocab_size, **kwargs):
        super(RNNModel, self).__init__(**kwargs)
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.num_hiddens = self.rnn.hidden_size
        # 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1
        if not self.rnn.bidirectional:#双向
            self.num_directions = 1
            self.linear = nn.Linear(self.num_hiddens, self.vocab_size)#构建自己的
        else:
            self.num_directions = 2
            self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)

    def forward(self, inputs, state):
        X = F.one_hot(inputs.T.long(), self.vocab_size)
        X = X.to(torch.float32)
        Y, state = self.rnn(X, state)
        # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)
        # 它的输出形状是(时间步数*批量大小,词表大小)。
        output = self.linear(Y.reshape((-1, Y.shape[-1])))
        return output, state

    def begin_state(self, device, batch_size=1):
        if not isinstance(self.rnn, nn.LSTM):#判断隐状态使用的是GRU或LSTM
            # nn.GRU以张量作为隐状态
            return  torch.zeros((self.num_directions * self.rnn.num_layers,
                                 batch_size, self.num_hiddens),
                                device=device)
        else:
            # nn.LSTM以元组作为隐状态
            return (torch.zeros((
                self.num_directions * self.rnn.num_layers,
                batch_size, self.num_hiddens), device=device),
                    torch.zeros((
                        self.num_directions * self.rnn.num_layers,
                        batch_size, self.num_hiddens), device=device))

通过时间反向传播

门控循环单元GRU

对于一个序列不是每一个观察点很重要而又对于某些点需要着重强调,使用更新门、重置门(对于有些不重要的直接忽视)

重置门(update gate):控制“可能还想记住”的过去状态的数量,有助于捕获序列中的短期依赖关系

更新门(reset gate):控制新状态中有多少是旧状态的副本,有助于捕获序列中的长期依赖关系。(避免梯度爆炸,梯度消失等问题)
R t = σ ( X t W x r + H t − 1 W h r + b r ) Z t = σ ( X t W x z + H t − 1 W h z + b z ) ( σ = s i g m o i d ) H t − 1 为前一时间步的隐状态 X t 为当前时间步的输入 其他参数为可学习参数 R_t=\sigma(X_tW_{xr}+H_{t-1}W_{hr}+b_r)\\ Z_t=\sigma(X_tW_{xz}+H_{t-1}W_{hz}+b_z)\\ (\sigma=sigmoid)\\ H_{t-1}为前一时间步的隐状态X_t为当前时间步的输入\space其他参数为可学习参数 Rt=σ(XtWxr+Ht1Whr+br)Zt=σ(XtWxz+Ht1Whz+bz)(σ=sigmoid)Ht1为前一时间步的隐状态Xt为当前时间步的输入 其他参数为可学习参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G2VQpQjV-1666785491409)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221014200736907.png)]

候选隐状态
H t ~ = t a n h ( X t W x h + ( R t ⊙ H t − 1 ) W h h + b h ) R t 接近 0 时 , 候选隐状态是以 X t 作为输入的多层感知机的结果任何预先存在的隐状态都会被重置为默认值 \tilde{H_t}=tanh(X_tW_{xh}+(R_t\odot H_{t-1})W_{hh}+b_h)\\ R_t接近0时,候选隐状态是以X_t作为输入的多层感知机的结果任何预先存在的隐状态都会被重置为默认值 Ht~=tanh(XtWxh+(RtHt1)Whh+bh)Rt接近0,候选隐状态是以Xt作为输入的多层感知机的结果任何预先存在的隐状态都会被重置为默认值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52YzfJOO-1666785491410)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221014203917242.png)]

隐状态
H t = Z t ⊙ H t − 1 + ( 1 − Z t ) ⊙ H t ~ Z t 接近 1 时,模型保留旧状态 Z t 接近 0 时,新的隐状态接近于候选状态 H_t=Z_t\odot H_{t-1}+(1-Z_t)\odot \tilde{H_t}\\ Z_t接近1时,模型保留旧状态\\ Z_t接近0时,新的隐状态接近于候选状态 Ht=ZtHt1+(1Zt)Ht~Zt接近1时,模型保留旧状态Zt接近0时,新的隐状态接近于候选状态

这些设计可以帮助我们处理循环神经网络中的梯度消失问题, 并更好地捕获时间步距离很长的序列的依赖关系。 例如,如果整个子序列的所有时间步的更新门都接近于1, 则无论序列的长度如何,在序列起始时间步的旧隐状态都将很容易保留并传递到序列结束。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6dBBtU5r-1666785491412)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221014204137174.png)]

简洁实现:

长短期记忆网络LSTM

与GRU均为控制方式 门控单元GRU与LSTM相比(GRU通常能够提供同等的效果, 并且计算的速度明显更快)

门控记忆元:输入门、记忆门、输出门

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zpoxe7H-1666785491413)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221014204928178.png)]

候选记忆元

记忆元

隐状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tbusyUoJ-1666785491414)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221014205238591.png)]

简洁实现:

Encoder与Decoder

机器翻译是序列转换模型的一个核心问题, 其输入和输出都是长度可变的序列。 为了处理这种类型的输入和输出, 我们可以设计一个包含两个主要组件的架构: 第一个组件是一个编码器(encoder 将输入编程成中间表达形式): 它接受一个长度可变的序列作为输入, 并将其转换为具有固定形状的编码状态。 第二个组件是解码器(decoder 将中间表示解码成输出): 它将固定形状的编码状态映射到长度可变的序列。 这被称为编码器-解码器。

encoder-decoder架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y8MsrFKy-1666785491415)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221003105328894.png)]

encoder编码器基类

class Encoder(nn.Module):
    """编码器-解码器架构的基本编码器接口"""
    def __init__(self, **kwargs):#**kwargs为key-value对
        super(Encoder, self).__init__(**kwargs)
        #在子类中可以通过super()方法来调用父类的方法
        # Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx 

    def forward(self, X, *args):
        raise NotImplementedError#由继承它的子类去实现 否则调用forward报错

decoder解码器基类

class Decoder(nn.Module):
    """编码器-解码器架构的基本解码器接口"""
    def __init__(self, **kwargs):
        super(Decoder, self).__init__(**kwargs)

    def init_state(self, enc_outputs, *args):#用于将编码器的输出转换为编码后的状态再加上可能的输入
        raise NotImplementedError

    def forward(self, X, state):
        raise NotImplementedError

合并编码器解码器

#@save
class EncoderDecoder(nn.Module):
    """编码器-解码器架构的基类"""
    def __init__(self, encoder, decoder, **kwargs):
        super(EncoderDecoder, self).__init__(**kwargs)
        self.encoder = encoder
        self.decoder = decoder

    def forward(self, enc_X, dec_X, *args):
        enc_outputs = self.encoder(enc_X, *args)#enc_X编码器的输入
        dec_state = self.decoder.init_state(enc_outputs, *args)#编码器的输出表示解码器的状态
        return self.decoder(dec_X, dec_state)#解码器的状态+额外输入得到输出

Seq2Seq

考虑到机器翻译中的输入序列和输出序列都是长度可变的。 为了解决这类问题,我们将使用两个循环神经网络的编码器和解码器, 并将其应用于序列到序列(sequence to sequence,seq2seq)类的学习任务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7bMmNhVT-1666785491417)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221026195538044.png)]

表示序列结束词元。 一旦输出序列生成此词元,模型就会停止预测。表示序列开始词元,它是解码器的输入序列的第一个词元。使用循环神经网络编码器最终的隐状态来初始化解码器的隐状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mKtuNQHf-1666785491418)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221025222958303.png)]

衡量生成序列的好坏标准BLEU:BLEU越大越好

pn 为n-gram连续匹配度。 具体地说,给定标签序列A、B、C、D、E、F 和预测序列A、B、B、C、D, 我们有p1=4/5、p2=3/4、p3=1/3和p4=0。这里考虑到pn < 1所以当n越大时,pn1/2n越大,即当连续匹配程度越高,给予奖励越大。

当预测长度较小时,导致exp的幂为负数,防止当预测过少时分数给高。
e x p ( m i n ( 0 , 1 − l e n l a b e l l e n p r e d ) ) ∏ n = 1 k p n 1 2 n exp(min(0,1-\frac{len_{label}}{len_{pred}}))\prod_{n=1}^k p_n^{\frac{1}{2^n}} exp(min(0,1lenpredlenlabel))n=1kpn2n1
编码器:可以使用双向RNN,也可以单向用来读取输入句子。编码器最后时间步的隐状态用作解码器的初始隐状态。

解码器:不能使用双向的RNN。

束搜索

8、注意力机制

结合了李宏毅的课程

注意力提示与汇聚

非注意力池化层

给定若干键值对(xi , yi ) i = 1,2,3,… 查找某个x对应的y 非注意力方式 选择所有的y
f ( x ) = 1 n ∑ y i f(x)=\frac{1}{n}\sum{y_i} f(x)=n1yi
更好的方式Nadaraya-Watson 核回归
f ( x ) = ∑ i = 1 n K ( x − x i ) ∑ j = 1 n K ( x − x j ) y i f(x)=\sum_{i=1}^n \frac{K(x-x_i)}{\sum_{j=1}^nK(x-x_j)}y_i f(x)=i=1nj=1nK(xxj)K(xxi)yi
其中函数k类似于求距离函数 整体类似于softmax回归 当与x越远越不重要给yi 更小的权重

使用高斯核
K ( u ) = 1 2 π e − u 2 2 K(u)=\frac{1}{\sqrt{2\pi}}e^{-\frac{u^2}{2}} K(u)=2π 1e2u2
带入源式得
f ( x ) = ∑ i = 1 n e − 1 2 ( x − x i ) 2 ∑ j = 1 n e 1 2 ( x − x j ) 2 y i = ∑ i = 1 n s o f t m a x ( − 1 2 ( x − x i ) 2 ) y i f(x)=\sum_{i=1}^n \frac{e^{-\frac{1}{2}(x-x_i)^2}}{\sum_{j=1}^ne^{\frac{1}{2}(x-x_j)^2}}y_i=\sum_{i=1}^n softmax(-\frac{1}{2}(x-x_i)^2)y_i f(x)=i=1nj=1ne21(xxj)2e21(xxi)2yi=i=1nsoftmax(21(xxi)2)yi
这里可以引入可学习的w

∑ i = 1 n s o f t m a x ( − 1 2 ( ( x − x i ) w ) 2 ) y i \sum_{i=1}^n softmax(-\frac{1}{2}((x-x_i)w)^2)y_i i=1nsoftmax(21((xxi)w)2)yi

定义Nadaraya-Watson核回归的带参数版本(这里不细讲用的少???????????????????????????)

注意力评分函数

我们可以将高斯核指数部分视为注意力评分函数(attention scoring function), 简称评分函数(scoring function), 然后把这个函数的输出结果输入到softmax函数中进行运算。 通过上述步骤,我们将得到与键对应的值的概率分布(即注意力权重)。 最后,注意力汇聚的输出就是基于这些注意力权重的值的加权和。(最后为alpha(alpha由注意力函数a经过softmax得到)乘上value)

下图说明了 如何将注意力汇聚的输出计算成为值的加权和, 其中alpha表示注意力评分函数。 由于注意力权重是概率分布, 因此加权和其本质上是加权平均值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0MfOTp2-1666785491419)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221026163327346.png)]

数学语言的描述
f ( q , ( k 1 , v 1 ) , . . . ( k m , v m ) ) = ∑ i = 1 m α ( q , k i ) v i α ( q , k i ) = s o f t m a x ( a ( q , k i ) ) f(q,(k_1,v_1),...(k_m,v_m)) = \sum_{i = 1}^m \alpha(q,k_i)v_i\\ \alpha(q,k_i)=softmax(a(q,k_i)) f(q,(k1,v1),...(km,vm))=i=1mα(q,ki)viα(q,ki)=softmax(a(q,ki))

掩蔽softmax操作:

softmax dim参数,这里以三维矩阵举例:dim = 0时为在每个维度中的同一位置进行运算;dim = 1时为在相同维度中的同一列运算;dim = 2为在相同维度中的同一行运算。考虑到以后的矩阵运算可能是更高维的运算,于是dim = -1同dim = 2时,为在同一维度中的同一行运算。

不同的注意力函数a:

加性注意力(additive attention):

缩放点积注意力(scaled dot-product attention):

Bahdanau注意力

多头注意力

自注意力和位置编码

结合李宏毅

背景:对于一个Seq如果想要考虑整个Seq则不太可能,尤其是当一个seq很长时。

目的:能够很好的考虑到整个seq。直接考虑整个seq。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nucp2oVV-1666785491420)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016160825821.png)]

计算ai与aj之间的关联程度值alpha的方法:两种计算方式。《Attention is all your need》使用左边点积的方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HkcOiqp9-1666785491421)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016161352691.png)]

alphai,j为注意力分数,指以i为查询,j为键值的relevant值。alphai,i表示自己的注意力分数。softmax可以替换成其他的激活函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbdMaauu-1666785491423)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016161659952.png)]

然后根据处理好的关联性的值,抽取seq中重要的信息。当alpha‘i,j较大时表示i,j关联性大,越影响bi的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-likbNHjS-1666785491424)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016162413286.png)]

以上均为可同时计算的,故并行性高。

以矩阵的形式看计算方式:

矩阵I的colum为ai

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7YfvoMy-1666785491425)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016162852473.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M8NJnkRF-1666785491426)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016163125565.png)]

可能会做softmax、normalization等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVyBlM3V-1666785491427)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016163142050.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERHOkrB1-1666785491428)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016163325611.png)]

仅仅只有WqWkWv的矩阵需要学习

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NlFazhpQ-1666785491429)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016163447140.png)]

multi-head:想用不同的形式来表示相关性。N个head就有N个不同的Q,K,V的矩阵

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MB1plvax-1666785491430)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016163727523.png)]

然后用矩阵将几者连接起来即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m86UKC2Z-1666785491431)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016163834120.png)]

如果想要想要考虑位置信息则加入positional即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmFEkA0k-1666785491433)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016164108405.png)]

在语音层面,不可能一直保存长度,使用truncated self-attention(只需要看一部分即可)

如何运用到image 上将image 看成3个vector 如GAN、DETR

self-attention与CNN。可以认为CNN是特殊的self-attention,而后者更灵活。当数据量少时,CNN更好于self-attention。数据量足够大时,self-attention更好。

比RNN的好处 RNN右边必须需要得到所有左边的没办法并行。

self-attention for Graph:GNN

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpftqkwV-1666785491434)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016165610482.png)]

不同self-attention之间的变形:运算速度与效果之间的比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-um4VXony-1666785491435)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016165750744.png)]

Transfomer

目的:seq2seq。(语音转文字,翻译、语音转任意的文字)

是一个seq2seq seq2seq应用:语法剖析(一个树状结构)、多标签分类(一个对象可能有多个标签)、object detection。NLP问题可看作QA。任何QA问题,都可以转换为seq2seq。(question,context——>seq2seq——>answer)

Seq2Seq基本model为encoder-decoder架构。Encoder(Input和Output长度一样,可以用self-attention、RNN、CNN)

一个Block包含多个layer(Self-attention),这里也使用了残差连接。

layer norm(同一个feature里 不同dimension) 和 batch norm(同一个dimension里不同feature)(参考李沐的论文讲解)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5VbBwvdG-1666785491436)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016183900628.png)]

现在的encoder架构可以多种多样:如果更换一下Layer norm的顺序效果更好

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VnSV2dsz-1666785491437)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016184003660.png)]

decoder:

最后用softmax来赋予分数,最高的作为输出结果。

masked-self attention(在decoder的输入中时)只考虑左边不考虑右边。

如何停止,准备end字符。

自回归(AT)vs非自回归(NAT,平行化更好)。

从Encoder到Decoder。Decoder处产生跟一个query矩阵。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aoNMGb7Q-1666785491438)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016210452093.png)]

不同的encoder-decoder的连接方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbTqpGvH-1666785491439)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016211039392.png)]

training:训练的时候是直接把所有的结果拿去做交叉熵。Decoder在训练时是直接输入正确答案。测试时是用自己的输入。甚至可以考虑在训练时故意输入一些错误的值,来增强模型的泛化能力。(比如当测试时可能造成步步错的严重后果)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gn4cjndu-1666785491440)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221016211417440.png)]

copy mechanism:pointer network(解决seq2seq)。用于比如人名之间需要完整不变的复制。

BLEU score作评判最终结果的标准。训练时考虑交叉熵,但挑选Model用BLEU指标。

具体代码实现:

9、自然语言处理:预训练

词嵌入word2vec

词袋模型 词嵌入 独热

词向量是用于表示单词意义的向量, 并且还可以被认为是单词的特征向量或表示。 将单词映射到实向量的技术称为词嵌入。

独热编码(one-hot)的劣势:本质为N个单词创建出N维向量,将第i个位置赋值为1,其他为0,相当于是一些0~N-1的索引。但是不能准确表达不同词之间的相似度,比如我们经常使用的“余弦相似度”。对于向量x,y∈Rd,它们的余弦相似度是它们之间角度的余弦。两个不同的单词的词向量做余弦恒为0。

自监督的word2vec:能很好地解决上述问题。

跳元模型(Skip-Gram):

NLP里的迁移学习:使用预训练好的模型来抽取词、句子的特征例如word2vec或语言模型。不更新预训练好的模型(不会微调word2vec,一般是一个基础底层的当作一个嵌入层)。需要构建新的网络来抓取新任务需要的信息(word2vec忽略了时序信息,语言模型只看了一个方向)。

BERT的动机:基于微调的NLP模型。预训练的模型抽取了足够多的信息。新的任务只需增加一个简单的输出层。类似于CV的迁移学习,只更改最后的层(因为预训练已经用了足够多的数据,抽取了足够的底层,只需增加输出层即可)。

BERT架构:只有编码器的Transformer。两个版本Base:block = 12,hidden size = 768,heads = 12,parameters = 110M;Large:blocks = 24,hidden szie = 1024,head = 16,parameters = 340M。大规模数据训练。

自监督学习

自监督式学习:没有label的数据集。

BERT简介:是一个编码器(输入多长,输出多长)。

预训练:比如把产生BERT的过程叫预训练,然后来微调。

GPT系列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2TO9L9vh-1666785491441)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221017002816372.png)]

更多自监督学习模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLPDAxPd-1666785491442)(C:\Users\wcx\AppData\Roaming\Typora\typora-user-images\image-20221017003005701.png)]

10、计算性能

11、优化算

ransfomer

目的:seq2seq。(语音转文字,翻译、语音转任意的文字)

是一个seq2seq seq2seq应用:语法剖析(一个树状结构)、多标签分类(一个对象可能有多个标签)、object detection。NLP问题可看作QA。任何QA问题,都可以转换为seq2seq。(question,context——>seq2seq——>answer)

Seq2Seq基本model为encoder-decoder架构。Encoder(Input和Output长度一样,可以用self-attention、RNN、CNN)

一个Block包含多个layer(Self-attention),这里也使用了残差连接。

layer norm(同一个feature里 不同dimension) 和 batch norm(同一个dimension里不同feature)(参考李沐的论文讲解)

[外链图片转存中…(img-5VbBwvdG-1666785491436)]

现在的encoder架构可以多种多样:如果更换一下Layer norm的顺序效果更好

[外链图片转存中…(img-VnSV2dsz-1666785491437)]

decoder:

最后用softmax来赋予分数,最高的作为输出结果。

masked-self attention(在decoder的输入中时)只考虑左边不考虑右边。

如何停止,准备end字符。

自回归(AT)vs非自回归(NAT,平行化更好)。

从Encoder到Decoder。Decoder处产生跟一个query矩阵。

[外链图片转存中…(img-aoNMGb7Q-1666785491438)]

不同的encoder-decoder的连接方式:

[外链图片转存中…(img-VbTqpGvH-1666785491439)]

training:训练的时候是直接把所有的结果拿去做交叉熵。Decoder在训练时是直接输入正确答案。测试时是用自己的输入。甚至可以考虑在训练时故意输入一些错误的值,来增强模型的泛化能力。(比如当测试时可能造成步步错的严重后果)

[外链图片转存中…(img-gn4cjndu-1666785491440)]

copy mechanism:pointer network(解决seq2seq)。用于比如人名之间需要完整不变的复制。

BLEU score作评判最终结果的标准。训练时考虑交叉熵,但挑选Model用BLEU指标。

具体代码实现:

9、自然语言处理:预训练

词嵌入word2vec

词袋模型 词嵌入 独热

词向量是用于表示单词意义的向量, 并且还可以被认为是单词的特征向量或表示。 将单词映射到实向量的技术称为词嵌入。

独热编码(one-hot)的劣势:本质为N个单词创建出N维向量,将第i个位置赋值为1,其他为0,相当于是一些0~N-1的索引。但是不能准确表达不同词之间的相似度,比如我们经常使用的“余弦相似度”。对于向量x,y∈Rd,它们的余弦相似度是它们之间角度的余弦。两个不同的单词的词向量做余弦恒为0。

自监督的word2vec:能很好地解决上述问题。

跳元模型(Skip-Gram):

NLP里的迁移学习:使用预训练好的模型来抽取词、句子的特征例如word2vec或语言模型。不更新预训练好的模型(不会微调word2vec,一般是一个基础底层的当作一个嵌入层)。需要构建新的网络来抓取新任务需要的信息(word2vec忽略了时序信息,语言模型只看了一个方向)。

BERT的动机:基于微调的NLP模型。预训练的模型抽取了足够多的信息。新的任务只需增加一个简单的输出层。类似于CV的迁移学习,只更改最后的层(因为预训练已经用了足够多的数据,抽取了足够的底层,只需增加输出层即可)。

BERT架构:只有编码器的Transformer。两个版本Base:block = 12,hidden size = 768,heads = 12,parameters = 110M;Large:blocks = 24,hidden szie = 1024,head = 16,parameters = 340M。大规模数据训练。

自监督学习

自监督式学习:没有label的数据集。

BERT简介:是一个编码器(输入多长,输出多长)。

预训练:比如把产生BERT的过程叫预训练,然后来微调。

GPT系列

[外链图片转存中…(img-2TO9L9vh-1666785491441)]

更多自监督学习模型

[外链图片转存中…(img-xLPDAxPd-1666785491442)]

10、计算性能

11、优化算

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值