第二周作业:多层感知机
一.线性回归从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
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);
结果:
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.最后三节许多复杂实现代码只是大概了解了功能,下周准备利用闲散时间来学习了解一下具体用法。
本周学习内容较上周多了不少,由于没利用好一周开始的时间,所以后几节学习比较仓促,下周开始一定注意。