1
2
3
4
5
#We should know about the detail of softmax, so we use Fashion-Mnist dataset in 3.5 chapter, and we set the
#batch_size 256
import torch
from IPython import display
from d2l import torch as d2l
1
2
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=None)#用到了我们3.5节定义的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#we know each image is 28x28, so we can regard them as vectors with length 784.
#we recall y_j is the probability of class j with positive number and normalized by 1.
#Because we have 10 classes, the output dimension will be 10, weights will form a 784x10 matrix
#bias will construct a 1x10 matrix.
num_inputs = 784
num_outputs = 10
#也就是说我们的输入x_i代表每个图片,为向量1x784
#所以整体的输入向量也就是nx784,一共有n张图片
#此外我们的权重为784x10,对于每一个输入图片的行向量来说,每一个像素,乘上其对应的行,行中的每一列都代表不同类给的权重。
#最后每一个图片的行向量经过权重运算,都得到一个行向量1x10,再加上偏置1x10,经过exp和归一化运算,就会得到这个图片属于每一个类的概率了。
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
#这里我们初始化权重为正态分布矩阵,均值0,方差0.01
#偏置初始为0

3.6.2 Define softmax operation

1
2
3
4
# we use sum function to sum along specific dimension
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.sum(0, keepdim = True), X.sum(1, keepdim = True)
#0得到行向量,1得到列向量
(tensor([[5., 7., 9.]]),
 tensor([[ 6.],
         [15.]]))
1
2
3
4
5
6
7
8
9
10
11
12
#Recall three steps of softmax:
#1. exp
#2. sum with every row(each row represents a sample)
#3. Divided by normalized constant, ensuring the result to be 1.
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(1, keepdim = True)
return X_exp / partition#广播机制#每一行除以每一行的和

X = torch.normal(0, 1, (2, 5))
X_prob = softmax(X)
X_prob, X_prob.sum(1, keepdim=True)#现在每一行的概率加和应该为1,如果不用keepdim = True将会写成行向量。
(tensor([[0.0625, 0.1222, 0.1543, 0.2536, 0.4074],
         [0.1057, 0.0316, 0.3033, 0.4390, 0.1204]]),
 tensor([[1.],
         [1.]]))

3.6.3 Define the model

1
2
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

3.6.4 Define the loss function

1
2
3
4
#引入交叉熵损失函数cross-entropy
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]#也就是选取第一行第一列和第二行第三列,y代表着真实归类
tensor([0.1000, 0.5000])
1
2
3
4
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])

cross_entropy(y_hat, y)#也可以再求平均
tensor([2.3026, 0.6931])

3.6.5 classfication accuracy

1
2
3
4
5
6
7
8
9
#The classification accuracy is the ratio between correct prediction number and the total prediction number
#we use argmax to get the max value in each row of y_hat, assigning the index with the max value as predicted class
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
#here transferring number type is to change 0.4, 0.5 into 1, and 0 to 0(false), then we compare this with y, to get a cmp matrix
return float(cmp.type(y.dtype).sum())#calculate number of 1
1
accuracy(y_hat, y) / len(y)
0.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Accumulator: #@save
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n#初始化,根据上面,这里时两个元素,两个0,其实就是一个正确预测数,一个预测总数
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]#zip([0。0, 0.0], [accuracy, y.numel()])也就是每个元素对应一个参数,所以最后算出来也就是[正确预测数的和,总数],随着我们遍历数据集而得到最终结果
def reset(self):
self.data = [0.0] * len(self.data)#重置数据数组
def __getitem__(self, idx):
return self.data[idx]#当我们以self.data[]的方式访问对象时,就会触发。

#对于data_iter可以访问的数据集,我们可以评估任意模型net上的精度
#下面eval是Module也就是模型函数集合里面的一个函数
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
#if isinstance(net, torch.nn.Module):#如果net是Module的子类或者实例,比如net = nn.Sequential(nn.Linear(2, 1))的时候
# net = torch.nn.Sequential(torch.nn.Softmax())
# net.eval()#设置为评估模式,评估模式下,模型不会进行梯度计算和参数更新
metric = Accumulator(2)#创建一个累加器,存储正确预测数,预测总数
with torch.no_grad():#不积累梯度,torch.no_grad() 可以进一步优化评估过程,避免不必要的计算
if data_iter:
for X, y in data_iter:#每次读取不同批次
metric.add(accuracy(net(X), y), y.numel())#numel返回元素总数
else:
print("data_iter is empty")
print("下面是数据集预测中所有正确预测数和预测总数", metric[0], metric[1])#输出非常正确,测试集中有10000张图像
return metric[0] / metric[1]
1
2
train_iter, test_iter = d2l.load_data_fashion_mnist(256)
evaluate_accuracy(net, test_iter)
下面是数据集预测中所有正确预测数和预测总数 1045.0 10000.0





0.1045

3.6.6 training

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#notice: updater is a common function to update model parameters
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""训练模型一轮"""
#将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
#training loss sum, accuracy sum and number of samples
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()#更新参数,比如Gradient descent梯度下降。比如之前定义过的sgd函数,输入为batch_size,也就是下面的
#X.shape[0]
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]#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#we define a practical programming class Animator to present training function
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#fmts为设置线的参数!
def add(self, x, y):
#向图表中添加多个数据点
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n#将x和y换成一个长度的列表,使得x对应y,能够一一对应
if not self.X:
self.X = [[] for _ in range(n)]#构造一个数组里面的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)#使用plot将点画出来
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
#Then we realize a training function, which will train a model "net" in the train_iter dataset.
#The function will run for many times(assigned by num_epochs). After each training, we evaluate the model by
#test_iter dataset.
# Finally Animator will be used to display the procession.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""训练模型"""
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)#测试训练精度,因为上面net.eval()设置为评估模式哈哈
animator.add(epoch + 1, train_metrics + (test_acc, ))#因为train_metrics是一个元组,这是一个元组拼接操作,后面括起来
# 括起来保证被当成元组拼接,而不是数字加和。
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
#确保损失和精度范围,不正确则报误
1
2
3
4
#定义损失函数和学习率,也就是那个递降过程中的系数
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
1
2
3
#现在我们训练模型10轮,所以轮数和学习率都是可以调整的,叫超参数
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)#Oh my god it's beautiful!

svg

3.6.7 预测

1
2
3
4
5
6
7
8
9
10
11
12
13
from d2l import torch as d2l
def predict_ch3(net, test_iter, n = 6): #@save
"""预测标签"""
for X, y in test_iter:
break#推出循环,只获取第一个批次用来预测!
trues = d2l.get_fashion_mnist_labels(y)#得到图像标签,转换为文本标签trues
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))#把横向加一遍,出一个列的结果,当然这里不是加法,是求最大值。#因为net是softmax函数,所以返回结果y是每一个样本的概率向量,而这里也就是找每一行的最大值,得到一个列的结果作为预测概率结果,这里返回的是最大概率对应的类别索引!也就是预测的分类!
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]#n=6也就是前六个样本!
)
predict_ch3(net, test_iter)
#也就是给定一系列图像,我们将比较它的实际标签(第一行)和模型预测(第二行)

svg