给定 维输入
需要确定一个 维权重 和一个标量偏差
输出一个预测值
平方损失:,其中 是真实值, 是估计值
然后我们有训练数据:
有损失函数
可以把 给合并进 中,方法是给 附加一列元素全为 的列向量,然后给行向量 的最后加一个元素 。然后就可以合并成:
然后选取最合适的 和 来使损失函数最小
挑选一个初始值 ,然后重复迭代参数 ,。其中 为学习率,是一个需要认为设置的参数,不能太大也不能太小。
在整个训练集上计算梯度耗时很长,我们可以随机采样 个样本 来近似损失:
python12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970import random
import torch
from matplotlib import pyplot as plt
from matplotlib_inline import backend_inline
# 生成噪声数据
def synthetic_data(w, b, count):
X = torch.normal(0, 1, (count, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.1, 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, 10000)
# plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1)
# plt.show()
# 随机取样
def data_iter(batch_size, features, labels):
count = len(features)
indices = list(range(count))
random.shuffle(indices)
for i in range(0, count, batch_size):
batch_indices = torch.tensor(indices[i: min(i + batch_size, count)])
yield features[batch_indices], labels[batch_indices]
batch_size = 10
# 设置初始预测值
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_()
# 训练
lr = 0.03
num_epochs = 10 # 训练次数
net = linreg # 选用神经网络
loss = squared_loss # 选用损失函数
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
# 使用 linreg 进行预测,然后 squared_loss 来计算损失
l = loss(net(X, w, b), y)
# 目前得到的 l 是一个 (batch_size, 1) 形状的向量,使用 sum 将其转为标量,然后就可以求梯度
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}, loss: {float(train_l.mean()):f}')
print(f'w_hat: {w}')
print(f'b_hat: {b}')
print(f'delta w: {true_w - w.reshape(true_w.shape)}')
print(f'delta b: {true_b - b}')
使用 pytorch 定义好的工具。先生成数据集:
python123true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 10000)
使用框架自带的随机采样读取器:
python1234567def 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)
然后定义神经网络,其中 nn.Sequential
把神经网络中的每一层连接,nn.Linear(2, 1)
相当于是一个全连接层,表示有 个因素影响 个输出结果(相当于是 的矩阵)
python123456from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
# 设置初始权重和偏置
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
计算均方误差使用的是 MMSELoss
类
python
1loss = nn.MSELoss()
然后设置优化算法,即梯度下降
python1trainer = torch.optim.SGD(net.parameters(), lr=0.03)
然后是训练过程:
python123456789num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
优点:离准确值比较远的时候梯度会很大,下降更快
缺点:有时候可能不希望下降特别快
优点:永远能以一个固定的速率下降
缺点:原点处不可导,且预测值和准确值比较靠近的时候会发生剧烈抖动
结合上面两个损失函数,损失较大的时候以恒定速度下降,损失较小的时候下降速度比较平滑
由多个因素和多个权重,线性确定出多个类的比例
我们的预测概率为
其中 ,也就是把多个类比例用自然指数函数转为一个非负数之后再分百分比
而精确值
其中:
也就是精确值只有正确分类的权重为 ,其他都为
我们一般使用交叉熵来衡量两个概率的区别
那么损失函数:
准备训练数据集:
python1234567891011121314151617def get_dataloader_workers():
"""使用4个进程来读取数据"""
return 4
def load_data_fashion_mnist(batch_size, resize=None):
"""下载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()))
设置迭代器
python12batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
定义权重和偏置:
python123
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
其中 为 的矩阵。我们这里图片像素为 ,由于图片颜色只有一个通道,所以每个像素的亮度就相当于是一个决定图片类型的因子,共有 个因子,然后每个因子由分别决定 个类别
然后有 softmax
函数,其传入一个矩阵。函数将对每个项求幂,然后对每一行进行求和,得到每一行的规范化常数,然后每一行除以规范化常数,使得每一行的加和为
python1234def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
然后定义神经网络,其中 是样本数据,这里 reshape
以确保样本数据中每一行都是一个样本(行数设为 -1
以自动推导 batch_size
),每一列都是该样本的像素信息(把二维像素信息拍平成一维)
python12def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
然后样本数据与我们当前预测的权重矩阵相乘,就得到了每个样本对于每个分类的比例,然后再通过 softmax
将比例规范化,同时加上偏置
通过神经网络就能得到我们的 y_hat
了
然后我们定义交叉熵损失函数:
python12def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
我们这里的 y
是一个向量, 表示第 个样本属于哪类。y_hat[range(len(y_hat)), y]
中,range(len(y_hat))
指明了我们要取 y_hat
的所有行(len
取的是行数),y
是一个 list,指明了每行取哪一列,算是 python 的语法糖。然后按照交叉熵损失的定义来求
为了统计精度数据,我们还需要 accuracy
函数来计算我们这个预测结果在这个样本数据上正确了多少个:
python123456def 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())
accuracy(y_hat, y) / len(y)
即为正确率
然后定义实用类 Accumulator
作为累加器:
python12345678910111213class Accumulator:
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
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]
定义 evaluate_acccuracy
来计算在整个数据集上的精确度:
python123456789def evaluate_accuracy(net, data_iter):
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
定义一个 Animator
,以便后续动态观察我们的训练情况
python123456789101112131415161718192021222324252627282930313233343536373839404142434445from matplotlib_inline import backend_inline
def use_svg_display():
"""Use the svg format to display a plot in Jupyter.
Defined in :numref:`sec_calculus`"""
backend_inline.set_matplotlib_formats('svg')
class Animator:
"""在动画中绘制数据"""
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 = []
use_svg_display()
self.fig, self.axes = 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)
定义训练函数 train_epoch
python1234567891011121314151617181920212223def train_epoch(net, train_iter, loss, updater):
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
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.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]
定义 updater
python12def updater(batch_size):
return sgd([W, b], lr, batch_size)
总训练函数 train
python123456789101112def train(net, train_iter, test_iter, loss, num_epochs, updater):
"""训练模型(定义见第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(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
设置学习率和训练次数后即可训练:
python123lr = 0.1
num_epochs = 10
train(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
一样的准备数据集
python12batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
然后定义神经网络:
python12345678910# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
# nn.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
python1loss = nn.CrossEntropyLoss(reduction='none')
设置优化算法
python1trainer = torch.optim.SGD(net.parameters(), lr=0.1)
然后训练:
python12num_epochs = 10
train(net, train_iter, test_iter, loss, num_epochs, trainer)