动手学深度学习8 Softmax回归+损失函数+图片分类

1. Softmax回归–分类问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
真实y只有一个为1,其他为0,也是概率。
在这里插入图片描述
p q假设为离散概率。真实y是有一个类别概率为1,其他为0。

在这里插入图片描述
在这里插入图片描述

2. 三个常用损失函数

衡量预测值和真实值之间的区别。
在这里插入图片描述

1. 均方损失 L2 Loss

定义:真实值减去预测值的平方再除以2.【除以2为了求导是方便抵消掉系数】
蓝色曲线:二次函数,当y=0, 变化预测值的函数
绿色曲线:似然函数,高斯分布 e − l e^{-l} el ?
橙色曲线:损失函数的梯度,是穿过原点的。
梯度下降时,根据负梯度的方向更新参数,导数决定如何更新参数的。当预测值和真实值相差比较大,梯度比较大,参数变化比较大、多;当预测值和真实值相差比较小–靠近原点,梯度越来越小,参数变化的幅度也越来越小。
在这里插入图片描述
当离原点比较远的时候,不一定想要很大的梯度来更新参数,可以考虑绝对值损失函数。

2. 绝对值损失函数 L1 Loss

定义:真实值减去预测值的绝对值。
蓝色曲线:损失函数–绝对值函数,当y=0
绿色曲线:似然函数,在原点处有一个很尖的点
橙色曲线:导数–梯度,在大于0,导数为常数1,小于0,导数-1,在0点不可导,导数在(-1,1)之间瞬间变化
当预测值和真实值离得比较远的时候,梯度为常数,权重更新不大,会带来很多稳定性的好处【不管多远,梯度以同样的力度向中间扯】。不好的地方,0点处不可导,从-1到+1的剧烈变化,平滑性差,当预测值和真实值靠的近,优化到了末期,这里可能变得不那么稳定。
在这里插入图片描述

3. 两个结合 HUber’s Robust Loss

鲁棒损失
定义:当预测值和真实值差距较大,绝对值大于1的时候,损失函数是绝对值误差,减去1/2是为了和曲线连接起来。
当预测值和真实值差距较小,绝对值小于等于1的时候,是平方误差。
蓝色曲线:损失函数,在正负一之间是平滑的二次函数,之外是直线
绿色曲线:似然函数,很像高斯分布
橙色曲线:在正负一之间,是渐变的;意外是常数。

当预测值和真实值差距较大,梯度用均匀的力度往回拉。
当预测值和真实值差距较小,到了优化末期,梯度会越来越小,保证优化是平滑的,避免出现数值的问题。
在这里插入图片描述

分析损失函数特性::通过函数形状,梯度形状,分析预测值和真实值差别大 差别小的时候的函数特性。

3. 图片分类数据集

读取多类分类数据集。

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

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

print(len(mnist_train), len(mnist_test))
# torch.Size([1, 28, 28])  每个输入图像的高度和宽度均为28像素。 数据集由灰度图像组成,其通道数为1
print(mnist_train[0][0].shape)
# 数据格式:[(训练特征x,真实类别y),(),(),......]
print(type(mnist_train), type(mnist_train[0]), len(mnist_train[0]))

# def use_svg_display():
#     """使用svg格式在Jupyter中显示绘图"""
#     backend_inline.set_matplotlib_formats('svg')

# def set_figsize(figsize=(3.5, 2.5)):
#     """设置matplotlib的图表大小"""
#     use_svg_display()
#     d2l.plt.rcParams['figure.figsize'] = figsize

def get_fashion_mnist_labels(labels):
  """返回Fashion-MNIST数据集的文本标签"""
  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)
  print(figsize)
  # d2l.plt.subplots(行数,列数,切成第几个小图)
  # subplots()函数返回值的类型为元组,元组中包含两个元素:第一个元素表示一个画布,第二个是元素表示所包含多个子图像的array数组对象。
  _, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
  # 表示把axes数组降到一维,默认为按行的方向降 返回一个一维数组 子图像对象
  axes = axes.flatten()
  # print(axes)
  # enumerate 输出数值顺序和值 (0,data)
  for i, (ax, img) in enumerate(zip(axes, imgs)):
    # print(i, ax, 'img')
    # 判断图片是什么类型数据表示的
    if torch.is_tensor(img):
      # 图片张量
      ax.imshow(img.numpy()) # 绘图张量数据要转成numpy数组
    else:
      # PIL图片 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]) # 设置坐标系的title
  return axes # 返回画好图的坐标系

X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
# 根据y的索引值,获取y的标签
show_images(X.reshape(18,28,28), 2, 9, titles=get_fashion_mnist_labels(y))

batch_size = 256  # 8.72 sec 速度快14-15倍左右。
# 减少batch_size(如减少到1)会影响读取性能
# batch_size = 1   # 129.88 sec 读取执行了两分钟
def get_dataloader_workers():
  """使用4个进程来读取数据"""
  return 4

# windows环境num_workers只能设置为0
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers())

timer = d2l.Timer()
for X, y in train_iter:
  continue
print(f'{timer.stop():.2f} sec')
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to ./data/FashionMNIST/raw/train-images-idx3-ubyte.gz
100%|██████████| 26421880/26421880 [00:01<00:00, 14501428.66it/s]
Extracting ./data/FashionMNIST/raw/train-images-idx3-ubyte.gz to ./data/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
100%|██████████| 29515/29515 [00:00<00:00, 255620.33it/s]
Extracting ./data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to ./data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
100%|██████████| 4422102/4422102 [00:00<00:00, 4882204.17it/s]
Extracting ./data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to ./data/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
100%|██████████| 5148/5148 [00:00<00:00, 12860200.71it/s]Extracting ./data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/FashionMNIST/raw
60000 10000
torch.Size([1, 28, 28])
<class 'torchvision.datasets.mnist.FashionMNIST'> <class 'tuple'> 2
(13.5, 3.0)
8.72 sec

在这里插入图片描述

# 如果模型需要更大输入,可以用resize让图片更大一些
def load_data_fashion_mnist(batch_size, resize=None):
  """下载Fashion-MNIST数据集,然后将其加载到内存中"""
  trans = [transforms.ToTensor()] # 为什么加中括号,放在列表里 方便后面如果有加resize操作,给trans操作能排序且同时执行。
  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())
      )
  
# 为什么要resize64
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
torch.Size([32, 1, 64, 64]) torch.float32 torch.Size([32]) torch.int64

常见的性能瓶颈:读取训练数据的速度,一般要比训练速度要快,快很多对训练来说是个好事情。

4. Softmax回归从零开始实现

注意:

1. raise RuntimeError(‘DataLoader worker (pid(s) {}) exited unexpectedly’.format(pids_str)) from e

RuntimeError: DataLoader worker (pid(s) 90412, 47140, 98280, 126560) exited unexpectedly 报错怎么解决
windows环境DataLoader加载数据的时候,num_wokers参数应该设置为0,否则就会报上面的错误
在这里插入图片描述

2. pycharm执行代码,display无法显示图像

下面的代码在pycharm中执行的时候,显示结果<Figure size 350x250 with 1 Axes>,无法显示图像。display是需要在ipython环境下才能显示图片的,比如colab、jupyter-notebook。

code

import torch
from IPython import display  # display通常用于在 IPython 环境下显示丰富的输出,比如图像、HTML、音频等。使用这个命令可以在 IPython 环境下更加方便地展示各种类型的内容
from d2l import torch as d2l

batch_size = 256  # 每批次读256个图片
# win环境将d2l加载数据的num-workers改成0
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

# 初始化参数模型
# softmax回归输入需要是一个向量,1*28*28拉长成一个向量,损失掉很多空间信息,留给卷积网络处理
num_inputs = 784  # softmax网络输入
num_outputs = 10
# torch.normal(均值,方差,形状,是否需要梯度)
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True) # 权重
b = torch.zeros(num_outputs, requires_grad=True)  # 偏移 对每一个输出都要有一个偏移,所以b是长度为10的向量

# 测试数据的不降维求和
# batch_size=2 两个测试数据
# 回顾:对于矩阵,可以按照行或列指定轴求和
X = torch.tensor([[1.0, 2., 3.],
                  [4., 5., 6.]])
print(X.sum(0, keepdim=True))
print(X.sum(1, keepdim=True))

# # 定义Softmax操作
# 实现softmax由三个步骤组成:
# 1.对每个项求幂(使用exp);
# 2.对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;
# 3.将每一行除以其规范化常数,确保结果的和为1。
# X 矩阵 对矩阵来讲,是对每一行做softmax
def softmax(X):
    X_exp = torch.exp(X)  # 对每个元素做指数计算 保证每个元素非负
    partition = X_exp.sum(1, keepdim=True) # 按照维度1求和 对每一行求和
    return X_exp/partition # 应用了广播机制  每一行的元素都除以每一行的和 保证和为1

X = torch.normal(0, 1, (2,5)) # 创建正态分布两行五列的矩阵
print(X)
X_prop = softmax(X) # 每个元素值非负 且每行值和为1
print(X_prop)
print(X_prop.sum(1, keepdim=True))  # 验证每行和是否为1

# 定义模型
def net(X):
    # W.shape[0] 784 输入维度,所以-1自动计算出来的是batch-size的大小
    # softmax输出元素值大于0,行和为1的矩阵
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

# 细节:怎么在预测值矩阵里面,根据标号把对应的预测值取出来。
y = torch.tensor([0, 2])  # 两个真实的标号
y_hat = torch.tensor([[0.1, 0.3, 0.6],[0.3, 0.2, 0.5]]) # 预测值 两个样本
print(y_hat[[0,1], y]) # 对第0样本,拿出y[0]标号对应的输出,对第1样本,拿出y[1]标号对应的输出
print(y_hat[[0,1], [0,2]])  # 第一个数组是样本标号,第二个数组是真实值的标号,拿到每个样本真实标号的预测值
print(y_hat[range(len(y_hat)), y])
# 高级索引 y_hat[[0,1], [0,2]] 选择了 y_hat 中位置为 (0, 0) 和 (1, 2) 的两个元素。
#  第一个数组对应行索引,第二个数组对应列索引。
# 定义损失函数--交叉熵损失函数
def cross_entropy(y_hat, y):
    # range(len(y_hat)) 预测值的样本标号数组 y真实值的标号--类别
    # torch.log 计算输入张量的自然对数, 对张量的每个元素取对数
    # 负的真实类别标号的预测值取对数--回想定义 -log(h_hat_y)
    return -torch.log(y_hat[range(len(y_hat)), y])
print(cross_entropy(y_hat, y))

# 分类精度--判断预测的类别是否是正确的
def accuracy(y_hat, y):
    """计算预测正确的数量"""
    # 如果预测值是二维矩阵,而且每一行列数大于1
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        # 每一行元素预测值最大的下标存到y_hat中 argmax返回的是索引下标--获取概率最高类别的索引
        y_hat = y_hat.argmax(axis=1)
    # print(y_hat.dtype, y_hat.type(torch.float).dtype)
    # y_hat.type(y.dtype) 设置元素数值类型和y保持一致
    cmp = y_hat.type(y.dtype) == y
    return float(cmp.type(y.dtype).sum())  # 预测类型成功的个数
print(accuracy(y_hat, y)/len(y))  # 预测成功率

class Accumulator:
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        # print(self.data, args)
        self.data = [a + float(b) for a,b in zip(self.data, args)]
        # print(self.data, args)
    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

# 评估模型net的准确率
def evaluate_accuracy(net, data_iter):
    """计算在指定数据集上模型的精度"""
    if isinstance(net, torch.nn.Module):
        net.eval()  # 将模型设置为评估模式
        # net.eval() 作用1. 网络结构如果有Batch Normalization 和 Dropout 层的,做模型评测必须加上这个命令以防止报错
        # 作用2: 关闭梯度计算
    # Accumulator 累加器 不断迭代数据X y 不断累加评测结果
    # Accumulator 初始化传入数值2 表示有数组有两个变量,第一个位置存正确测试数,第二个位置存样本总数,根据批次遍历依次累加
    metric = Accumulator(2) # 正确预测数、预测总数
    with torch.no_grad():
        for X, y in data_iter: # 迭代一次 一次批量
            # metric.add(该批次的预测类别正确的样本数,批次的总样本数)
            # y.numel() pytorch中用于返回张量的元素总个数
            metric.add(accuracy(net(X), y), y.numel())

    # 返回值=分类正确样本数/样本总数=精度
    return metric[0] / metric[1]

print(evaluate_accuracy(net, test_iter)) # 0.1409  模型随机初始化,总共十个类别,初始概率应该是0.1左右
# raise RuntimeError('DataLoader worker (pid(s) {}) exited unexpectedly'.format(pids_str)) from e
# RuntimeError: DataLoader worker (pid(s) 90412, 47140, 98280, 126560) exited unexpectedly
# 在加载数据的时候data.DataLoader(num_workers) win环境下num_workers要设置为0

# 训练 对训练数据迭代一次
def trian_epoch_ch3(net, train_iter, loss, updater):
    """训练模型一个迭代周期(定义见第三章)"""
    # 如果模型是用nn模组定义的
    if isinstance(net, torch.nn.Module):
        net.train()  # 将模型设置为训练模式 告诉pytorch要计算梯度
    # 训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)  # 三个参数需要累加的迭代器
    for X, y in train_iter:
        # 计算梯度并更新参数
        y_hat = net(X)
        l = loss(y_hat, y) # 计算损失函数
        # 如果优化器是pytorch内置的优化器
        # 下面两个加的结果有什么区别
        # print(float(l)*len(y), accuracy(y_hat,y), y.size().numel(),
        #       float(l.sum()), accuracy(y_hat, y), y.numel())
        if isinstance(updater, torch.optim.Optimizer):
            # 使用pytorch内置的优化器和损失函数
            updater.zero_grad() # 1.梯度先置零
            l.mean().backward() # 2.计算梯度
            updater.step()      # 3.调用step参数进行一次更新
            metric.add(float(l)*len(y), accuracy(y_hat,y), y.size().numel())
        else:
            # 使用定制的优化器和损失函数
            # 自己实现的l是一个向量
            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]

# 实现一个动画展示训练进程
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, nclos=1, figsize=(3.5, 2.5)):

        # 增量的绘制多条线
        if legend is None:
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = d2l.plt.subplots(nrows, nclos, figsize=figsize)
        if nrows * nclos == 1:
            self.axes = [self.axes, ]
        # 使用lamda函数捕获参数
        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)  # n=3 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):
    """训练模型"""
    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 = trian_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

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)

# from IPython.display import Image
# # Image(filename='C:\\工作文档\\learn_pytorch\\tudui_learing\hymenoptera_data\\train\\ants\\0013035.jpg')
# Image(filename='C:\\工作文档\\learn_pytorch\\tudui_learing\\hymenoptera_data\\train\\ants\\6240329_72c01e663e.jpg')
# from IPython.display import HTML
# HTML('<iframe src="https://www.baidu.com" width="800" height="600"></iframe>')
# import pandas as pd
# from IPython.display import display
# data = {'Name': ['John', 'Anna', 'Peter'],
#         'Age': [25, 30, 35]}
# df = pd.DataFrame(data)
# display(df)
tensor([[5., 7., 9.]])
tensor([[ 6.],
        [15.]])
tensor([[ 0.3351,  1.7154, -0.6454, -2.1380, -1.1757],
        [ 0.4918, -0.0947,  0.6408,  0.4666, -0.8963]])
tensor([[0.1768, 0.7030, 0.0663, 0.0149, 0.0390],
        [0.2537, 0.1411, 0.2945, 0.2474, 0.0633]])
tensor([[1.0000],
        [1.0000]])
tensor([0.1000, 0.5000])
tensor([0.1000, 0.5000])
tensor([0.1000, 0.5000])
tensor([2.3026, 0.6931])
0.5
0.2123

在这里插入图片描述

# 把trian_epoch_ch3的累加求和,全部改成下面这条命令
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())

在这里插入图片描述

这段代码定义了一个名为 `Animator` 的类,其目的是在动画中绘制数据。让我们逐行解释它的功能和每行代码的含义:
1. `class Animator:`:定义了一个名为 `Animator` 的类。
2. `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)):`:定义了类的初始化方法 `__init__()`,接受一系列参数用于设置绘图的参数。这些参数包括 x 轴标签、y 轴标签、图例、x 轴范围、y 轴范围、x 轴缩放、y 轴缩放、线条格式、行数、列数和图形尺寸。
3. `if legend is None: legend = []`:如果传入的图例参数为 None,则将其设置为空列表。
4. `d2l.use_svg_display()`:调用了 `d2l` 模块中的 `use_svg_display()` 函数,该函数用于指定使用 SVG 格式进行显示。
5. `self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)`:使用 `d2l` 模块中的 `plt.subplots()` 函数创建了一个或多个子图,返回的是图形对象和子图对象的元组。
6. `if nrows * ncols == 1: self.axes = [self.axes, ]`:如果只有一个子图,则将 `self.axes` 转换为包含一个元素的列表。
7. `self.config_axes = lambda: d2l.set_axes(self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)`:定义了一个名为 `config_axes` 的匿名函数,用于配置子图的属性,例如标签、范围和缩放。
8. `self.X, self.Y, self.fmts = None, None, fmts`:初始化了 `X`、`Y` 和 `fmts` 这三个变量,用于存储数据和线条格式。
9. `def add(self, x, y):`:定义了一个名为 `add` 的方法,用于向图表中添加数据点。
10. `if not hasattr(y, "__len__"): y = [y]`:如果 `y` 不是可迭代对象,则将其转换为包含一个元素的列表。
11. `n = len(y)`:获取 `y` 中元素的数量。
12. `if not hasattr(x, "__len__"): x = [x] * n`:如果 `x` 不是可迭代对象,则将其转换为包含 `n` 个元素的列表。
13. `if not self.X: self.X = [[] for _ in range(n)]` 和 `if not self.Y: self.Y = [[] for _ in range(n)]`:如果 `X` 和 `Y` 还未初始化,则初始化它们为包含 `n` 个空列表的列表。
14. `for i, (a,b) in enumerate(zip(x, y)):`:使用 `zip()` 函数遍历 `x` 和 `y` 中的每个元素,并使用 `enumerate()` 函数获取它们的索引。
15. `if a is not None and b is not None:`:如果 `a` 和 `b` 均不为 None,则执行下面的操作。
16. `self.X[i].append(a)` 和 `self.Y[i].append(b)`:向 `X` 和 `Y` 中对应的列表中添加 `x` 和 `y`。
17. `self.axes[0].cla()`:清除子图的当前内容。
18. `for x, y, fmt in zip(self.X, self.Y, self.fmts):`:使用 `zip()` 函数遍历 `X`、`Y` 和 `fmts` 中的每个元素。
19. `self.axes[0].plot(x, y, fmt)`:在子图中绘制线条,使用给定的格式。
20. `self.config_axes()`:调用 `config_axes` 方法配置子图的属性。
21. `display.display(self.fig)` 和 `display.clear_output(wait=True)`:显示图形,并清除输出区域。
这段代码的作用是创建一个动画对象,可以向其中添加数据点,并在动画中实时绘制出这些数据点的曲线。
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3,0.9],
                        legend=['train-loss', 'train-acc', 'test-acc'])
# 代码解释
这行命令是用于创建一个动画对象的实例,其参数含义如下:
1. `xlabel='epoch'`: 这是横坐标轴的标签,通常表示迭代的次数或周期。
2. `xlim=[1, num_epochs]`: 这是横坐标轴的范围,用于设置横坐标轴的最小值和最大值。在这里,`num_epochs`应该是一个代表总迭代次数或周期数的变量。
3. `ylim=[0.3, 0.9]`: 这是纵坐标轴的范围,用于设置纵坐标轴的最小值和最大值。在这里,它设置了纵坐标轴的范围从0.3到0.9。
4. `legend=['train-loss', 'train-acc', 'test-acc']`: 这是图例标签的列表,用于标识动画中各个曲线的含义。在这里,`train-loss`代表训练损失,`train-acc`代表训练精度,`test-acc`代表测试精度。
self.fig, self.axes = d2l.plt.subplots(nrows, nclos, figsize=figsize)
这行命令是使用matplotlib库创建一个包含多个子图的图形,并返回创建的图形对象和子图对象。其参数含义如下:
1. `nrows`: 行数,指定子图的行数。
2. `ncols`: 列数,指定子图的列数。
3. `figsize`: 图形的尺寸,指定图形的宽度和高度,以英寸为单位。它通常是一个包含两个元素的元组,例如`(width, height)`。
这行命令传入了三个参数:行数、列数以及图形尺寸。它返回了两个变量:
1. `self.fig`: 这是图形对象,是整个图的容器,可以对其进行全局设置,如标题、图例等。
2. `self.axes`: 这是包含多个子图的数组对象,其形状由行数和列数决定。可以通过索引来访问特定位置的子图,例如`self.axes[i, j]`表示第i行第j列的子图。
self.config_axes = lambda: d2l.set_axes(self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
这行命令是定义了一个匿名函数 `self.config_axes`,用于配置子图的坐标轴属性,并返回配置后的子图对象。其参数含义如下:
1. `d2l.set_axes`: 这是一个函数,用于设置子图的坐标轴属性。
2. `self.axes[0]`: 这是指定要设置坐标轴属性的子图对象,通常是第一个子图。
3. `xlabel`: 这是横坐标轴的标签,表示水平方向上的变量。
4. `ylabel`: 这是纵坐标轴的标签,表示垂直方向上的变量。
5. `xlim`: 这是横坐标轴的范围,用于设置横坐标轴的最小值和最大值。
6. `ylim`: 这是纵坐标轴的范围,用于设置纵坐标轴的最小值和最大值。
7. `xscale`: 这是横坐标轴的缩放类型,可以是线性('linear')或对数('log')。
8. `yscale`: 这是纵坐标轴的缩放类型,可以是线性('linear')或对数('log')。
9. `legend`: 这是图例标签的列表,用于标识子图中的曲线或数据的含义。
这行命令传入了这些参数,返回的是配置后的子图对象。

5. Softmax回归简洁实现

import torch
from torch import nn
from d2l import torch as d2l
from IPython import display

# 分类精度--判断预测的类别是否是正确的
def accuracy(y_hat, y):
    """计算预测正确的数量"""
    # 如果预测值是二维矩阵,而且每一行列数大于1
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        # 每一行元素预测值最大的下标存到y_hat中 argmax返回的是索引下标--获取概率最高类别的索引
        y_hat = y_hat.argmax(axis=1)
    # print(y_hat.dtype, y_hat.type(torch.float).dtype)
    # y_hat.type(y.dtype) 设置元素数值类型和y保持一致
    cmp = y_hat.type(y.dtype) == y
    return float(cmp.type(y.dtype).sum())  # 预测类型成功的个数
# print(accuracy(y_hat, y)/len(y))  # 预测成功率

def evaluate_accuracy(net, data_iter):
    """计算在指定数据集上模型的精度"""
    if isinstance(net, torch.nn.Module):
        net.eval()  # 将模型设置为评估模式
        # net.eval() 作用1. 网络结构如果有Batch Normalization 和 Dropout 层的,做模型评测必须加上这个命令以防止报错
        # 作用2: 关闭梯度计算
    # Accumulator 累加器 不断迭代数据X y 不断累加评测结果
    # Accumulator 初始化传入数值2 表示有数组有两个变量,第一个位置存正确测试数,第二个位置存样本总数,根据批次遍历依次累加
    metric = Accumulator(2) # 正确预测数、预测总数
    with torch.no_grad():
        for X, y in data_iter: # 迭代一次 一次批量
            # metric.add(该批次的预测类别正确的样本数,批次的总样本数)
            # y.numel() pytorch中用于返回张量的元素总个数
            metric.add(accuracy(net(X), y), y.numel())

    # 返回值=分类正确样本数/样本总数=精度
    return metric[0] / metric[1]

class Accumulator:
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        # print(self.data, args)
        self.data = [a + float(b) for a,b in zip(self.data, args)]
        # print(self.data, args)
    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]

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, nclos=1, figsize=(3.5, 2.5)):

        # 增量的绘制多条线
        if legend is None:
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = d2l.plt.subplots(nrows, nclos, figsize=figsize)
        if nrows * nclos == 1:
            self.axes = [self.axes, ]
        # 使用lamda函数捕获参数
        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)
        print('n------',n)
        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 trian_epoch_ch3(net, train_iter, loss, updater):
    """训练模型一个迭代周期(定义见第三章)"""
    # 如果模型是用nn模组定义的
    if isinstance(net, torch.nn.Module):
        net.train()  # 将模型设置为训练模式 告诉pytorch要计算梯度
    # 训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)  # 三个参数需要累加的迭代器
    for X, y in train_iter:
        # 计算梯度并更新参数
        y_hat = net(X)
        l = loss(y_hat, y) # 计算损失函数
        # 如果优化器是pytorch内置的优化器
        # 下面两个加的结果有什么区别
        # print(float(l)*len(y), accuracy(y_hat,y), y.size().numel(),
        #       float(l.sum()), accuracy(y_hat, y), y.numel())
        if isinstance(updater, torch.optim.Optimizer):
            # 使用pytorch内置的优化器和损失函数
            updater.zero_grad() # 1.梯度先置零
            l.mean().backward() # 2.计算梯度
            updater.step()      # 3.调用step参数进行一次更新
            # metric.add(float(l)*len(y), accuracy(y_hat,y), y.size().numel())
            # 报错 ValueError: only one element tensors can be converted to Python scalars
        else:
            # 使用定制的优化器和损失函数
            # 自己实现的l是一个向量
            l.sum().backward()
            updater(X.shape[0])
            # metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
        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 = trian_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

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

# pytorch不会隐式的调整输入的shape,所以要手动对模型输入shape做resize处理
# 在线性层前面定义使用展平层flatten,来调整网络输入的形状
# Softmax 回归的输出层是一个全连接层
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

# init_weights 是一个函数,接受一个参数(网络的参数),并根据一定的策略对参数进行初始化。例如,可以使用正态分布、均匀分布等随机初始化方法,或者使用预训练模型的参数进行初始化。
def init_weights(m): # m 网络
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, std=0.01)

# apply 应用特定的初始化函数 以确保网络的参数以一种合适的方式进行初始化,有助于提高训练效果
net.apply(init_weights)

# 我们没有将softmax概率传递到损失函数中, 而是在交叉熵损失函数中传递未规范化的预测,并同时计算softmax及其对数, 这是一种类似“LogSumExp技巧”的聪明方式。 softmax的exp指数操作封装到了交叉熵损失里面
# https://en.wikipedia.org/wiki/LogSumExp
# 在交叉熵损失函数中传递未归一化的预测,并同时计算softmax及其对数
loss = nn.CrossEntropyLoss(reduction='none') # reduction='none': 这是指定了损失的计算方式。在这里,设置为 'none' 表示不进行汇总,而是返回每个样本的损失值 none mean sum

# 使用学习率为0.1的小批量随机梯度下降作为优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)

num_epochs = 10
# 调用 之前 定义的训练函数来训练模型
train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

在这里插入图片描述

体验相同数据集不同模型,在计算复杂度、数据原理和性能、是否容易训练上不同的感受。

注意:

  1. softmax回归的输出层是一个全连接层, linear线性层是全连接层
  2. 在交叉熵损失函数中传递未归一化的预测,并同时计算softmax及其对数。 指数函数是单调增的,不影响最大值的索引。accuracy里面取的是y_hat里面最大的数,exp是单调递增的,所以在简明这里实际上是比的ex里面的x大小,最大的进行softmax肯定也是最大的

6. QA

  1. softlabel训练策略为什么有效?
    https://wenku.baidu.com/view/e619dc09bad528ea81c758f5f61fb7360b4c2b8e.html?wkts=1709101574662&bdQuery=softlabel+%E8%AE%AD%E7%BB%83%E7%AD%96%E7%95%A5&needWelcomeRecommand=1
    用softmax指数很难逼近0 1 极端值,所以把两端值改为【0.1,0.9】,再去逼近会很容易,是softlabel技巧

  2. softmax回归和logistics回归一样吗?哪里不同?
    可以认为一样,logistics回归最常用二分类,softmax预测二分类,可以预测一个类别的概率,另一个类别用1-得概率值
    https://baike.baidu.com/item/logistic%E5%9B%9E%E5%BD%92/2981575?fr=ge_ala

  3. 为什么用交叉熵,而不用相对熵、互信息等其他基于信息量的度量?
    交叉熵计算简单,互信息不好算。相对熵具有对称性。

  4. y*logy^为什么只关心正确类,不关心不正确类呢,关心不正确类效果会更好吗?
    因为one-hot的编码把不正确的类的值弄成0了,在计算上忽略了,softlabel会关注。

  5. 这样的n分类,对每个类别来说,可以认为只有1个正类,n-1个负类,会类别不均衡吗?
    只关心当前类,关心某一类是否有足够多的样本

  6. MSE的最大似然估计?

  7. 似然函数曲线是怎么得出的,有什么参考意义。其他次优解的似然值会也很高吗?
    统计的概念。 深度学习后面的模型和统计没什么关系,但是数据结构是线性代数的。
    最小化损失=最大化似然函数

  8. 不同损失函数的梯度下降的速度和学习率有什么关系?步长和速度有什么关系?
    步长=负的梯度下降的速度【值】*学习率
    假设学习率固定,步长取决于梯度下降的大小,步长大小表示以多大的速度从差别很大的解拉回差别很小的解。不同的损失函数,梯度的值不一样,步长不一样。

  9. rank regression问题,当成 softmax问题,效果会很差吗?
    ranking regression排序问题,A在B的前面,不关心概率,只关心相对值。可以用softmax处理,有一点区别,但不很大。

  10. w的方差0.01的使用?

  11. pytorch训练,batchsize设置1或任意值测试总时间差不多,设置batchsize大一些速度会更快吗?
    batchsize不管等于几,计算量是不会变化的,只有计算的并行度增加,才会加快训练速度。当模型很大,在gpu上会有明显区别。

  12. 为什么accuracy函数中不除以len(y), cmp.type(y.dtype)有必要做吗?
    最后一个batch可能是小于batchsize的,所以是没有除以len(y), 是要求整个样本的精确度而不是求某一个batch的精确度。

  13. DataLoader的num_workers是并行的吗?
    设置为5,pytorch会给开4个进程。

  14. 计算梯度时为什么需要设置net.eval()评估模式
    一个好的习惯,设置eval()评估模式会不再计算梯度,但不设也没关系

  15. w b 怎么从模型中抽出来,放进updater的?
    在trainer = torch.optim.SGD(net.parameters(), lr=0.1)中,net.parameters()是将模型的所有w b参数都放入到updater里面了。

  16. 多次迭代后测试精度先上升再下降时过拟合了吗?要提前终止吗?
    一直在下降,很可能过拟合了。有其他微调可以避免这些事情。

  17. cnn网络中到底学到的是什么信息?纹理?轮廓?所有内容的综合?
    当前理论认为学到的主要是纹理,轮廓不太注意。计算机看纹理很强。

  18. 是否对全连接层的输出特征进行L2归一化,对最后的损失和精度有什么影响吗?
    通过归一化控制过拟合。

  19. 自己实现的softmax和pytorch调用api的softmax,哪个速度快
    一般是pytorch调用的比较稳定,可以实际代码对比一下。

  20. 如果是自己的图片数据集,需要怎么处理才能用于训练?本地的数据集怎么创建迭代器。
    把猫狗等单独创建一个标签名文件夹,同类图片放到同一个文件夹里面,把路径告诉pytorch,是可以自动扫描所有文件的,可以看pytorch官方文档。

  21. softmax学到了什么?他的解释性怎么理解,有什么指标可以衡量神经网络的解释性。
    统计学习有讲解。

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值