第二周作业:多层感知机

一.线性回归从0开始实现:

  1.

   (1)生成数据集

%matplotlib inline
import random
import torch
!pip install d2l
from d2l import torch as d2l

def synthetic_data(w, b, num_examples):  
    #"""生成 y = Xw + b + 噪声。"""
    X = torch.normal(0, 1, (num_examples, len(w))) #生成X,其均值为0,方差为1,大小为样本数*w长度
    y = torch.matmul(X, w) + b
    y += torch.normal(0, 0.01, y.shape) #加入随机噪音
    print(X,y)
    return X, y.reshape((-1, 1)) #当参数为-1时,reshape将自动根据另一个参数计算行数

true_w = torch.tensor([2, -3.4]) #初始化W的值
true_b = 4.2#初始化b的值
features, labels = synthetic_data(true_w, true_b, 1000)

print('features:', features[0],'\nlabel:', labels[0])

d2l.set_figsize()
d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1);

 

  (2)读取数据集,每次读取小批量:

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    #生成list,并随即打乱
    indices = list(range(num_examples))
    random.shuffle(indices)
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)]) #最后一个批量注意最小值
        #返回随机顺序的特征及其对应label
        yield features[batch_indices], labels[batch_indices]

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

 

 

  (3)初始化随机参数w,b;定义模型;定义损失函数;小批量梯度下降模型 

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

#定义模型
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(): for param in params: param -= lr * param.grad / batch_size param.grad.zero_()

  (5).进行计算

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):
        l = loss(net(X, w, b), y)  
        l.sum().backward()
        sgd([w, b], lr, batch_size)  
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

   (6).与真实预测值区别:    

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

 

 

二:Softmax回归

 

1.分类和回归区别:

  回归:估计一个连续值

  分类:预测一个连续类别。Softmax回归实际是分类问题

2.从回归到分类:(使用均方损失)

  分类通常有多个输出,使用softmax操作子使得输出i是预测第i类的置信度

  使用交叉熵来衡量预测和标号的区别,作为损失函数

3.损失函数

  (1)均方损失:

      L2 Loss:l(y,y')=1/2(y-y')^2

  (2)绝对值损失函数:

      L1 Loss:l(y,y')=|y-y'|    这样权重更新比较稳定,但0处不可导

   (3)Huber's Robust Loss

     

 

    比较平滑

 

三.图像分类数据集

%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
!pip install d2l
from d2l import torch as d2l

d2l.use_svg_display()

1.使用内置函数将Fashion-MNIST数据集下载并读入内存

# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
#train=true表示下载训练数据集,tansform=trans表示下载tensor。
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)

 

2.数据集的可视化:  

#获取数字标签
def get_fashion_mnist_labels(labels):  
    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 = d2l.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

X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y));

 

 

3.读取小批量数据:

batch_size = 256

def get_dataloader_workers(): 
    #使用2个进程来读取数据。
    return 2

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

 

4.对以上代码进行整合:

def load_data_fashion_mnist(batch_size, resize=None): 
    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()))
train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
    print(X.shape, X.dtype, y.shape, y.dtype)
    break

 

 

四.softma从零开始:

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

 

 1.原图片长和宽均为28,通道数为1,但对于softmax回归来说,输入需要是向量。

#28*28
num_inputs = 784
#十类
num_outputs = 10

#行数为输入个数,列数为输出个数
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

2.softmax操作:

  

 

 

 对于一个矩阵来说,按每一行做softmax

def softmax(X):
    X_exp = torch.exp(X)
    #
    partition = X_exp.sum(1, keepdim=True)
    #使用广播机制
    return X_exp / partition

 

 3.定义模型

def net(X):
    #需要一个批量大小*输入维度的矩阵
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

 

4.损失函数:  

def cross_entropy(y_hat, y):
    #y_hat 表示拿出每一行对应真实表好的预测值     
    return - torch.log(y_hat[range(len(y_hat)), y])

 

5.判断预测类别和真实类别是否相同:

def accuracy(y_hat, y):  
#取出概率最大的 if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: y_hat = y_hat.argmax(axis=1) cmp = y_hat.type(y.dtype) == y return float(cmp.type(y.dtype).sum())

def evaluate_accuracy(net, data_iter):  
    if isinstance(net, torch.nn.Module):
        net.eval()  # 将模型设置为评估模式
    metric = Accumulator(2)  # 正确预测数、预测总数
    for X, y in data_iter:
        metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]

6.训练:

def train_epoch_ch3(net, train_iter, loss, updater): 
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):
        net.train()
    # 训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)
    for X, y in train_iter:
        # 计算梯度并更新参数
        y_hat = net(X)
        l = loss(y_hat, y)
        if isinstance(updater, torch.optim.Optimizer):
            # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad()
            l.backward()
            updater.step()
            metric.add(float(l) * len(y), accuracy(y_hat, y),
                       y.size().numel())
        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):  
    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   

7.开始训练及结果

lr = 0.1
def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

def predict_ch3(net, test_iter, n=6):
    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)

1.初始化模型参数:

# PyTorch自动地调整输入的形状。因此,
# 我们在线性层前用flatten来调整网络输入的形状,将其变为2D的tensor
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);

2.实现

loss = nn.CrossEntropyLoss()
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

 

 六.多层感知机

1.单层感知机是一个二分类模型,其求解方法等价于使用批量大小为1的梯度下降,但其不能拟合XOR函数。

2.常见激活函数:

  Sigmoid函数:

  

 

  Tanh函数:

 

 

 

ReLU函数:

 

 

 

 

代码实现:

1.初始化参数

import torch
from torch import nn
from d2l import torch as d2l


batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

num_inputs, num_outputs, num_hiddens = 784, 10, 256
#定义参数
W1 = nn.Parameter(torch.randn(
    num_inputs, num_hiddens, requires_grad=True) * 0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
W2 = nn.Parameter(torch.randn(
    num_hiddens, num_outputs, requires_grad=True) * 0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))

params = [W1, b1, W2, b2]

2.ReLU激活函数

def relu(X):
    a = torch.zeros_like(X)
    return torch.max(X, a)

3.定义模型

def net(X):
    X = X.reshape((-1, num_inputs))
  #@代表矩阵乘法 H
= relu(X@W1 + b1) return (H@W2 + b2)

4.损失函数及训练

loss = nn.CrossEntropyLoss()
num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)
d2l.predict_ch3(net, test_iter)

 

 

 

 

简洁实现:

net = nn.Sequential(nn.Flatten(),
                    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
loss = nn.CrossEntropyLoss()
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)

 

 

 

七:模型选择:

1.训练误差:模型在训练数据上的误差

泛化误差:模型在新数据集的误差(重点关心)

 

2.注意不要混用训练数据集和验证数据集

 

3.K-则交叉验证:

  在没有足够的数据时,将训练数据分成K块,做K次这样的运算,每一次将第i块作为验证数据集,其余作为训练数据集,报告K个验证集误差的平均。常用K=5或10.

 

八.过拟合和欠拟合

1.模型容量:拟合各种函数的能力,低容量的模型难以拟合训练数据,高容量的模型可以记住所有的训练数据。

                                             

 

2.给定一个模型种类,将有两个主要因素:参数的个数,参数值的参考范围

 

3.数据复杂度:样本个数,每个样本的元素个数,时间空间结构、多样性

模型容量需要匹配数据复杂度

 

 

九.权重衰退

权重衰退是通过L2正则项使得模型参数不会过大,从而控制模型复杂度,正则项权重是控制模型复杂度的超参数。

 

1.使用均方范数作为柔性限制

对于每个 θ,都可以找到λ使得目标函数等价于下面:

 

超参数λ控制了正则项的重要程度,λ=0:无作用,λ趋于无穷,最优解趋向于0。

2.

 

通常nλ<1

 

3.代码实现:

  生成数据:

n_train, n_test, num_inputs, batch_size = 20, 100, 200, 5
true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.05
train_data = d2l.synthetic_data(true_w, true_b, n_train)
train_iter = d2l.load_array(train_data, batch_size)
test_data = d2l.synthetic_data(true_w, true_b, n_test)
test_iter = d2l.load_array(test_data, batch_size, is_train=False)

  初始化w,b 并定义L2范数惩罚

def init_params():
    w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)
    b = torch.zeros(1, requires_grad=True)
    return [w, b]

定义L2范数权重衰退
def l2_penalty(w):
    return torch.sum(w.pow(2)) / 2

  定义训练函数

def train(lambd):
    w, b = init_params()
    net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss
    num_epochs, lr = 100, 0.003
    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:
            with torch.enable_grad():
                # 增加了L2范数惩罚项,广播机制使l2_penalty(w)成为一个长度为`batch_size`的向量。
                l = loss(net(X), y) + lambd * l2_penalty(w)
            l.sum().backward()
            d2l.sgd([w, b], lr, batch_size)
        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范数是:', torch.norm(w).item())

  训练结果:

原本结果:                                                                                                      加入权重衰退

            

 

 

发现当没有采用权重衰退方法时虽然训练集下降很快,但测试集loss近乎不动,过拟合十分严重。之后的两次实验中,分别设置了不同的权重衰退参数,发现训练结果有所提升。

 

十.丢弃法
在层之间加入噪音

 

2.无偏差的加入噪音

这里期望:对x加入噪音得到x',希望E[x']=x

丢弃法对每个元素进行如下扰动:

 

在概率p中,输入变为0,在剩下的概率里,变大

这里注意Xi'的期望不会发生改变

 

3.使用地点:

  通常将丢弃法作用在隐藏全连阶层的输出上,丢弃的概率是控制模型复杂度的超参数

4.代码实现:

  对丢弃法定义函数:

def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    # 在本情况中,所有元素都被丢弃。
    if dropout == 1:
        return torch.zeros_like(X)
    # 在本情况中,所有元素都被保留。
    if dropout == 0:
        return X
    mask = (torch.Tensor(X.shape).uniform_(0, 1) > dropout).float()
    return mask * X / (1.0 - dropout)

  定义网络:

num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

net = nn.Sequential(nn.Flatten(),
        nn.Linear(784, 256),
        nn.ReLU(),
        # 在第一个全连接层之后添加一个dropout层
        nn.Dropout(dropout1),
        nn.Linear(256, 256),
        nn.ReLU(),
        # 在第二个全连接层之后添加一个dropout层
        nn.Dropout(dropout2),
        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);

  结果:

 num_epochs, lr, batch_size = 10, 0.5, 256
 loss = nn.CrossEntropyLoss()
 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

 

 

在训练中可以将隐藏层稍微整多一点,同时dropout也设置大一点,可能训练效果会提升一点

 

十一.数值稳定性:

1.常见的两个问题:梯度爆炸、梯度消失

2.梯度爆炸:超出值域

对学习率敏感:

  学习率太大->参数值大->梯度更大

  学习率小->训练无进展

3.梯度消失:

  梯度值变为0、训练无进展、顶部层训练结果较好,但底层效果差

4.让每一层的方差是一个常数:

  将每层的输出和梯度都看作是随机变量,让它们的均值和方差保持一致

  权重初始化:

    在合理的值区间里随机初始参数

    训练开始的时候更容易出现数值不稳定(一般来说越远越抖)

    使用N(0,0.01)来初始对小网络没问题,但对很深的网络不能保证

 

5.Xavier初始

通常,Xavier初始化从均值为零,方差𝜎^2=2/(nin+𝑛out)的高斯分布中采样权重也可以利用Xavier的直觉来选择从均匀分布中抽取权重时的方差。

Xavier初始化表明,对于每一层,输出的方差不受输入数量的影响,任何梯度的方差不受输出数量的影响。

6.随即初始化是保证在进行优化前打破对称性的关键。

7.ReLU激活函数缓解了梯度消失问题,这样可以加速收敛。

 

问题及感受:

  1.暂时还未进行Kaggle比赛实战,准备下周进行练习。

  2.对数值稳定性一节的许多数学公式并未完全听懂,准备下周找时间认真推导。

  3.最后三节许多复杂实现代码只是大概了解了功能,下周准备利用闲散时间来学习了解一下具体用法。

本周学习内容较上周多了不少,由于没利用好一周开始的时间,所以后几节学习比较仓促,下周开始一定注意。

 

 

 

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

    

  

posted @ 2021-09-12 20:47  glysw  阅读(389)  评论(1)    收藏  举报