今天再读zh.d2l书(4.2. 多层感知机的从零开始实现 --- 动手学深度学习 2.0.0 documentation),
看了关于多层感知机的从零实现与softmax的从零实现
目录
[- `loss`:](#- loss
:)
[反向传播 张量.backward()](#反向传播 张量.backward())
mlp从零实现,
点击"paddle"的代码
里面用到paddle库,可以方便的实现一些神经网络的东西
从paddle引入nn模块,
python
import warnings
from d2l import paddle as d2l
warnings.filterwarnings("ignore")
import paddle
from paddle import nn
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
比如nn.linear(),可以构造线性层
nn.CrossEntropyLoss(),可以快速构造交叉熵损失函数
python
#初始化参数
num_inputs, num_outputs, num_hiddens = 784, 10, 256
W1 = paddle.randn([num_inputs, num_hiddens]) * 0.01
W1.stop_gradient = False
b1 = paddle.zeros([num_hiddens])
b1.stop_gradient = False
W2 = paddle.randn([num_hiddens, num_outputs]) * 0.01
W2.stop_gradient = False
b2 = paddle.zeros([num_outputs])
b2.stop_gradient = False
params = [W1, b1, W2, b2]
#损失函数
loss = nn.CrossEntropyLoss(reduction='none')
(其实是没注意点错了才发现)
点击"torch"的代码
就是是从torch引入nn
python
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)
训练
python
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)
参数解释
-
`num_epochs`: 这个变量表示神经网络训练的轮数(或称为"纪元")。每一轮遍历整个数据集一次就叫做一个epoch。例如,设置`num_epochs = 10`意味着网络将会被训练10次完整的迭代。
-
`lr`: 这是学习率(Learning Rate)的缩写。它决定了在每一步更新时权重更新的幅度。较小的学习率会导致更慢的收敛速度,但可能找到更好的局部最小值;较大的学习率可能会导致错过最优解或者在最优解附近震荡。这里设置了两个不同的学习率:`0.1` 和 `0.01`,可能是为了在不同阶段进行超参数调整。
-
`updater`: 这是一个函数或对象,用于执行权重的更新操作。
在这个例子中,它是通过调用`torch.optim.SGD(params, lr=lr)`创建的SGD优化器实例。
这里的`params`指的是需要优化的模型的参数集合,
python
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]
而`lr`则是之前定义的学习率。
- `d21.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)`: 这行代码调用了某个模块(假设为`d21`)中的一个方法`train_ch3`来开始训练过程。这个方法的参数包括:
- `net`: 需要训练的网络模型。
python
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(X@W1 + b1) # 这里"@"代表矩阵乘法
return (H@W2 + b2)
-
`train_iter`: 训练数据的迭代器,用于逐批次地获取训练样本。
-
`test_iter`: 测试数据的迭代器,用于评估模型在未见过的数据上的性能。
这里从库里自动调的,batch size 为256 的迭代器
python
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
- `loss`:
用于计算损失值的函数或类,通常用来衡量预测与真实标签之间的差距。
这里从torch.nn直接取的交叉熵损失,没有传入具体参数,只是设置none
python
loss = nn.CrossEntropyLoss(reduction='none')
参数说明:
在PyTorch中,nn.CrossEntropyLoss
是一个损失函数,用于测量分类任务的预测值与真实值之间的差距。
-
reduction
: 这个参数决定了如何处理输出损失张量。它有三个可选的值:'none'
: 不应用任何缩减。返回的损失与每个输入元素相对应。'mean'
: 返回损失的平均值。'sum'
: 返回所有损失的求和。
loss = nn.CrossEntropyLoss(reduction='none')
,reduction='none'
表示损失函数会返回一个与输入同样大小的损失张量,而不是将所有损失值缩减为一个单一的数值。这意味着每个输入样本都会有一个对应的损失值,这对于需要单独处理每个样本的损失(例如在自定义的损失计算中)非常有用。
例如,如果有一个批量大小为N
的输入和一个对应的标签,nn.CrossEntropyLoss(reduction='none')
将会返回一个长度为N
的损失张量,其中每个元素都是对应输入样本的交叉熵损失。这样, 可以根据需要进一步处理这些损失值,例如进行加权平均或者选择性地更新模型参数。
d2l提供的接口------
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)
train_iter, test_iter,loss,updater以及其内的params都是torch.nn调用的
其余是自定义的,net函数以及一些数字变量
softmax从零实现
一些工具
torch的矩阵乘法matmul
就是矩阵乘法AB
张量.numel返回张量元素数
在PyTorch中,`.numel()` 是一个Tensor(张量)的方法,用于返回该张量的元素数量。这里的 "y" 应该是一个Tensor对象,它可能代表了一个标签或类别的向量。
具体来说,`.numel()` 方法会计算张量中所有元素的个数,不考虑张量的形状或维度。例如,对于一个形状为 `(3, 4)` 的二维张量,`.numel()` 会返回 \(3 \times 4 = 12\),表示这个张量总共有12个元素。
在你的代码片段中,`y.numel()` 可能被用来获取类别总数,这在某些情况下是需要的,比如计算某个统计量时需要知道总的样本数。
zip
python
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
zipped = zip(list1, list2)
print(list(zipped)) # 输出: [(1, 'a'), (2, 'b'), (3, 'c')]
isinstance(a,b)判断a是否是b类
在Python中,isinstance()
是一个内置函数,用于检查一个对象是否是一个特定类型的实例,或者是其子类的实例。它的基本语法如下:
python
isinstance(object, classinfo)
其中:
object
是要检查的对象;classinfo
可以是一个类,也可以是一个类型(tuple),比如(int, str)
。
isinstance()
会根据 classinfo
参数来判断 object
是否为指定的数据类型。如果是,则返回 True
;否则返回 False
。
反向传播 张量.backward()
Q:反向传播函数需要自己写吗?还是每次只要.backward就行了?
A:在PyTorch中,通常不需要自己手动编写反向传播函数。PyTorch的自动微分(autograd)系统已经为大多数常见的数学运算提供了内置的反向传播函数。当你使用PyTorch的张量(tensor)进行操作时,这些操作会被记录在一个计算图中,PyTorch能够自动地计算并应用链式法则来执行反向传播。
因此,对于大多数情况,你只需要在计算完损失函数之后调用 .backward()
方法,PyTorch就会自动处理反向传播过程。
例
python
import torch
# 假设有一些输入数据和标签
x = torch.randn(5, requires_grad=True)
y = torch.randn(5)
# 定义一个线性模型
linear_model = torch.nn.Linear(5, 1)
# 计算模型的输出
predictions = linear_model(x)
# 定义损失函数
loss = torch.nn.functional.mse_loss(predictions, y)
# 反向传播
loss.backward()
# 现在可以访问模型的梯度
print(linear_model.weight.grad)
print(linear_model.bias.grad)
backward()看似只是一行代码,没有任何赋值,实际上是update的迭代更新需要访问梯度,backward求出了梯度的值存储在哪里以便直接访问了 。
.backward()
方法被调用来计算损失函数关于模型参数的梯度。这些梯度是自动累积在参数的 .grad
属性中的。然后,优化器(updater)使用这些梯度来更新模型的参数。
下面是详细的过程:
-
l.mean().backward()
或l.sum().backward()
:这行代码计算了损失函数l
关于模型参数的梯度,并将这些梯度保存在每个参数的.grad
属性中。 -
updater.zero_grad()
:在调用.backward()
之前,这行代码清除了过往的梯度。这是必须的,因为默认情况下,梯度是累积的(即每次调用.backward()
时,梯度会加到之前的梯度上)。 -
updater.step()
:这行代码使用计算出的梯度来更新模型的参数。这是优化器的一个方法,它执行了实际的参数更新步骤。
在 updater.step()
调用之后,模型的参数就被更新了,这是 .backward()
的结果被使用的地方。这个过程在训练循环的每次迭代中都会发生,因此模型的参数会在每个批次的数据处理后逐渐改进。
总结一下,.backward()
的结果是用来更新模型参数的,这个更新是通过优化器的 step()
方法实现的。在代码中没有直接显示使用 .backward()
的结果,因为这是通过优化器内部机制隐式完成的。
绘制动画
这里先不详细看,使用了animate类
python
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)
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
损失函数的定义
python
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y)
就是一个有着两个输入的函数
(预测值,真实值)
定义accuracy
python
def accuracy(y_hat, y): #@save
"""计算预测正确的数量"""
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())
就是一个有着两个输入的函数
(预测值,真实值)
定义精度(net模型,data_iter数据集)
同样,对于任意数据迭代器data_iter
可访问的数据集, 我们可以评估在任意模型net
的精度。
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
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]
data_iter是二元元祖(输入特征X,输出类别y)构成的可迭代列表,每个元祖是一个样本。
定义的Accumulator(2)
这里定义一个实用程序类Accumulator,用于对多个变量进行累加。 在上面的evaluate_accuracy函数中, 我们在Accumulator实例中创建了2个变量, 分别用于存储正确预测的数量和预测的总数量。 当我们遍历数据集时,两者都将随着时间的推移而累加。
class Accumulator: #@save
"""在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]
解释:
metric.add(accuracy(net(X), y), y.numel())这里accuracy(net(X), y), y.numel()都是args
不妨设某次迭代前metric的值为(x1,x2)
迭代的传入的args为(acc,y.numel)
则经过metric.add(accuracy(net(X), y), y.numel())之后,metric变成了(x1+acc,x2+y.numel)
而metric初值为(0,0)
所以最后metric为(所有样本acc之和,所有样本的特征数之和(特征数*样本数))
定义net
python
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
输入是torch张量,输出也是
定义训练函数
python
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:
# 计算梯度并更新参数
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_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一个迭代周期(定义见第3章)"""
-
定义一个名为
train_epoch_ch3
的函数,它接受四个参数:net
(神经网络模型)、train_iter
(训练数据迭代器)、loss
(损失函数)和updater
(优化器或自定义更新规则)。 -
使用
#@save
注释标记该函数,这可能是一个特殊的标记,用于保存或记录函数定义。# 将模型设置为训练模式 if isinstance(net, torch.nn.Module): net.train()
-
检查
net
是否是torch.nn.Module
类的一个实例(即是否是一个PyTorch模型)。 -
如果是,调用
net.train()
方法将模型设置为训练模式。在训练模式下,某些层(如Dropout和BatchNorm)的行为会有所不同。# 训练损失总和、训练准确度总和、样本数 metric = Accumulator(3)
-
初始化一个名为
metric
的Accumulator
实例,用于累积训练损失总和、训练准确度总和和样本数。Accumulator
可能是一个自定义的类,用于简化统计数据的累积。for X, y in train_iter:
-
遍历
train_iter
迭代器,每次迭代获取一个批量数据X
和对应的标签y
。# 计算梯度并更新参数 y_hat = net(X)
-
使用模型
net
对输入数据X
进行前向传播,得到预测结果y_hat
。l = loss(y_hat, y)
-
使用损失函数
loss
计算y_hat
和真实标签y
之间的损失l
。if isinstance(updater, torch.optim.Optimizer):
-
检查
updater
是否是torch.optim.Optimizer
类的一个实例,即是否是一个PyTorch内置的优化器。# 使用PyTorch内置的优化器和损失函数 updater.zero_grad()
-
如果
updater
是内置优化器,调用updater.zero_grad()
清除模型参数的梯度。l.mean().backward()
-
计算损失
l
的平均值,并调用 .backward() 方法进行反向传播,计算梯度。updater.step()
-
调用 updater.step() 更新模型的参数。
else: # 使用定制的优化器和损失函数 l.sum().backward()
-
如果
updater
不是内置优化器,则计算损失l
的总和,并调用 .backward() 方法进行反向传播。updater(X.shape[0])
-
调用自定义的 updater 函数,可能传入批量大小的参数
X.shape[0](有多少个样本就是几)
,用于更新模型参数。metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
-
使用
metric.add()
方法累积当前批量的损失总和、准确度和样本数。accuracy(y_hat, y)
可能是一个计算准确度的自定义函数,而y.numel()
返回标签张量中的元素数量。# 返回训练损失和训练精度 return metric[0] / metric[2], metric[1] / metric[2]
-
计算并返回整个迭代周期的平均训练损失(总损失除以样本数)和平均训练精度(总准确度除以样本数)。
定义updater()
python
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
这里是使用了d2l现成的()
训练
python
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
使用训好的net预测
python
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)
解释
def predict_ch3(net, test_iter, n=6): #@save
"""预测标签(定义见第3章)"""
-
定义一个名为
predict_ch3
的函数,它接受三个参数:net
(神经网络模型)、test_iter
(测试数据迭代器)和n
(默认值为6,表示要显示的图像数量)。for X, y in test_iter: break
-
遍历
test_iter
迭代器以获取测试数据集的第一个批次的数据X
(图像)和标签y
,然后立即退出循环。
这里是想展示一下第一个X,y的预测效果,所以就拿了第一批的样本(第一批,也有很多,就像后面的图上。)
trues = d2l.get_fashion_mnist_labels(y)
-
调用
d2l.get_fashion_mnist_labels
函数(假设这是一个自定义函数,用于将标签索引转换为可读的标签字符串),并将真实标签y
作为输入,获取真实标签的字符串表示。preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
-
使用神经网络
net
对输入数据X
进行预测,得到预测结果。.argmax(axis=1)
用于获取每个样本预测结果中的最大值索引(即预测的类别)。然后,使用d2l.get_fashion_mnist_labels
函数将预测的类别索引转换为可读的标签字符串。titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
-
创建一个标题列表
titles
,其中每个标题由真实标签和预测标签组成,中间用换行符分隔。d2l.show_images( X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])
-
调用
d2l.show_images
函数(假设这也是一个自定义函数,用于显示图像)来显示前n
张图像。X[0:n].reshape((n, 28, 28))
将前n
张图像的数据重塑为适合显示的形状(每张图像是28x28像素)。1
表示图像的行数,n
表示列数,titles=titles[0:n]
将之前创建的标题与图像一起显示。
(就是画图和显示)
predict_ch3(net, test_iter)
- 调用
predict_ch3
函数,传入模型net
和测试数据迭代器test_iter
,执行预测并显示结果。
这个函数的作用是使用给定的神经网络模型对测试数据集的前 n
张图像进行预测,并将预测结果与真实标签一起显示出来。
画的图------
(2024年10月27日终于知道为什么当时从CNN开始看看不懂了,基础的都在这里讲了。。。)