深度学习04-09

04数据操作

矩阵的维度,
创建数组,
访问元素(和matlab类似):

  1. matrix[i:j, :] matrix的第i行到第j-1行(包左不包右)
  2. Matrix[::3,::2]所有行每三行一跳,所有列每两列一跳,即第0行的第0个,第2个,第4个…,第3行的第0个,第2个,第4个…,第6行的第0个,第2个,第4个…,
    (a: b :c的意思是区间[a, b),步长为c。)
    Matrix[-1]:最后一行
  • 数据操作实现
import torch
# from torch import nn
# 生成一个元素0~9的一维矩阵
x = torch.arange(12)
print(x.shape)# 矩阵的形状
print(x.numel())# 矩阵的元素数量
x = x.reshape(3,4)# 改变矩阵的形状(一定要赋值给x)
print(torch.zeros((2,3,4)))# 生成形状为2,3,4的0矩阵,也就是2个3*4的零矩阵
torch.ones((2,3,4))# 全1矩阵
torch.tensor([[1,2,3,4],[2,1,3,4], [4,3,2,1]])# 直接给每个元素赋值
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y ##按元素加减乘除、求幂
torch.exp(x)# 按元素求指数

## 把多个张量连结在一起
x = torch.arange(12, dtype = torch.float32).reshape((3,4))
y = torch.tensor([[2.0, 1, 4, 2], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((x, y), dim = 0)# 以行连结,两个3*4的张量变成一个6*4的
torch.cat((x, y), dim= 1)# 以列连结,两个3*4变成一个3*8

# 逻辑符号构建张量
x == y # 3*4张量,按元素判断x、y是否相等
x.sum()# 所有元素求和

## 广播机制
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a + b# [0,0;1,1;2,2] + [0,1;0,1;0,1]

## 区域赋值
x[0:2, :] = 12 #x的第0、1行全部赋值为12

## 一些操作会给新结果分配内存
before = id(y)
y = y + x
id(y) == before #False
# 原地操作
z = torch.zeros_like(y) # 生成一个与y形状一样的全0矩阵z
print('id(z)', id(z))
z[:] = x + y
print('id(z)', id(z))# 前后两次id相同
before = id(x)
x += y
id(x) == before # True

## NumPy
A = x.numpy() #torch转为numpy
B = torch.tensor(A) # numpy转为torch
type(A) # numpy.ndarray
type(B) # torch.Tensor
a = torch.tensor([3.4])
a, a.item(), float(a), int(a)#大小为1的张量转为Python标量
  • 数据预处理实现
    在已经有了数据的情况下,对数据的读写等处理
    python一般默认64位float,对深度学习来说较慢,深度学习一般用32位float
import torch
import os
import pandas as pd

os.makedirs(os.path.join('E:\study\Code\python\code', 'data'), exist_ok = True)
data_file = os.path.join('E:\study\Code\python\code', 'data', 'house_tiny.csv')#csv:每行数据按逗号分隔
with open(data_file, 'w') as f:
    f.write('NumRooms,Alley,Price\n') #列名
    f.write('NA,Pave,123400\n') #每行表示一个数据样本
    f.write('2,NA,10600\n')
    f.write('4,NA,178200\n')
    f.write('NA,NA,140000\n')

data = pd.read_csv(data_file)
print(data)
inputs, outputs = data.iloc[:, 0: 2], data.iloc[:, 2]# 把数据分为输入(前2列)和输出(最后1列)
inputs = inputs.fillna(inputs.mean())# 填充数值域的NaN
print(inputs)
# 把非数值类转换为数值类,一种数字对应一种特征
# get_dummies是pandas库中的一个函数,用于将分类变量转换为虚拟/指示变量。
# 这个函数会为数据集中的每一个唯一的类别值创建一个新的列,如果原始数据中的类别值为该类别,
# 则新列的值为1,否则为0。
inputs = pd.get_dummies(inputs, dummy_na = True)
print(inputs)
# 将以上信息转换为张量的形式
x, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
print(x)
print(y)

输出为:

   NumRooms Alley   Price
0       NaN  Pave  123400
1       2.0   NaN   10600
2       4.0   NaN  178200
3       NaN   NaN  140000
   NumRooms Alley
0       3.0  Pave
1       2.0   NaN
2       4.0   NaN
3       3.0   NaN
   NumRooms  Alley_Pave  Alley_nan
0       3.0           1          0
1       2.0           0          1
2       4.0           0          1
3       3.0           0          1
tensor([[3., 1., 0.],
        [2., 0., 1.],
        [4., 0., 1.],
        [3., 0., 1.]], dtype=torch.float64)
tensor([123400,  10600, 178200, 140000])

05线性代数

import torch
import os
import pandas as pd

x = torch.arange(4)
print(len(x))# 一维张量的长度
A = torch.arange(20).reshape(5,4)
A.T# 矩阵的转置
A = torch.arange(20, dtype=torch.float32).reshape(5,4)
B = A.clone()#分配新内存,将A的数值分配给B
A * B# Hadamard积
A = torch.arange(40, dtype=torch.float32).reshape(2,5,4)
print(A.sum(axis=[0]))#按指定维度求和
print(A.sum(axis=[0,2]))
print(A.sum(axis=[0,1,2]))
print(A.mean(axis=[0]))#按指定维度求均值
print(A.mean(axis=[0,2]))
print(A.mean(axis=[0,1,2]))
# 求和,但保持原来的维度,方便后续的计算
sumA = A.sum(axis=0, keepdims=True)
print(sumA.shape)#A是三维的,sumA也是3维的
A / sumA#都是三维的,可利用广播机制计算
# 某个轴计算A的累积总和
A = torch.arange(20, dtype=torch.float32).reshape(5,4)
print(A.cumsum(axis=0))# 第i行第j列的元素是第0~i-1行,第j列元素的和
# 点积
y = torch.ones(4, dtype=torch.float32)
x = torch.tensor([1.0,2,3,4])
print(torch.dot(x, y))#只能相同维度的张量(一维和一维,二维和二维)
torch.sum(x*y)# 通过按元素乘再求和计算点积
print(torch.mv(A,x))# 矩阵点乘向量
B = torch.ones(4,3)# 矩阵点乘
print(torch.mm(A, B))# A:5*4, B:4*3 结果:5*3
# 范数
u = torch.tensor([3.0, -4])
torch.norm(u)# L2范数
torch.abs(u).sum()# L1范数
torch.norm(A)# 矩阵的F范数

结果:

tensor([[20., 22., 24., 26.],
        [28., 30., 32., 34.],
        [36., 38., 40., 42.],
        [44., 46., 48., 50.],
        [52., 54., 56., 58.]])
tensor([ 92., 124., 156., 188., 220.])
tensor(780.)
tensor([[10., 11., 12., 13.],
        [14., 15., 16., 17.],
        [18., 19., 20., 21.],
        [22., 23., 24., 25.],
        [26., 27., 28., 29.]])
tensor([11.5000, 15.5000, 19.5000, 23.5000, 27.5000])
tensor(19.5000)
torch.Size([1, 5, 4])
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  6.,  8., 10.],
        [12., 15., 18., 21.],
        [24., 28., 32., 36.],
        [40., 45., 50., 55.]])
tensor(10.)
tensor([ 20.,  60., 100., 140., 180.])
tensor([[ 6.,  6.,  6.],
        [22., 22., 22.],
        [38., 38., 38.],
        [54., 54., 54.],
        [70., 70., 70.]])

06矩阵计算

理论

07自动求导

在这里插入图片描述
正向是符号求解的过程,反向是数值求解的过程
正向的计算复杂度O(n),空间复杂度O(1)
反向的计算复杂度O(n),空间复杂度O(n)

  • 实现
import torch
import os
import pandas as pd

x = torch.arange(4.0)
x.requires_grad_(True)
x.grad #用来存x的梯度的地方
y = 2 * torch.dot(x, x)
print(y)
y.backward()# 反向计算
print(x.grad)# 打印y对x的导数
x.grad == 4 * x #结果: [True, True, True, True]
# pytorch默认会累积梯度,计算新的梯度时需要清楚之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad # 结果:[1., 1., 1., 1.]

x.grad.zero_()
y = x * x# tensor([0., 1., 4., 9.], grad_fn=<MulBackward0>)
u = y.detach() # tensor([0., 1., 4., 9.])
z = u * x# tensor([ 0.,  1.,  8., 27.], grad_fn=<MulBackward0>)
z.sum().backward()
x.grad == u # 结果:[True, True, True, True]

def f(a):
    b = a * 2
    while b.norm() < 1000:
        b = b* 2
    if b.sum() > 0:
        c = b
    else:
        c = 100 * b
    return c

a = torch.randn(size=(), requires_grad=True) #生成一个随机标量,并设置其梯度计算属性
d = f(a)
d.backward()
print(a.grad == d / a) # True

08线性回归+基础优化

  • 线性回归模型:
    n维输入: x = [ x 1 , x 2 , . . . , x n ] T \bm x = [x_1, x_2, ..., x_n]^T x=[x1,x2,...,xn]T
    模型权重: w = [ w 1 , w 2 , . . . . , w n ] T , b \bm w = [w_1, w_2, ...., w_n]^T, b w=[w1,w2,....,wn]T,b
    输出是输入的加权和: y = w 1 x 1 + w 2 x 2 + . . . + x n x n + b y=w_1x_1+w_2x_2+...+x_nx_n+b y=w1x1+w2x2+...+xnxn+b
    向量形式: y = < w , x > + b y=<\bm w, \bm x>+b y=<w,x>+b

  • 衡量模型质量(预测质量)
    平方损失: e = ( y − y ^ ) 2 / 2 e=(y-\hat y)^2/2 e=(yy^)2/2

  • 参数学习
    真实值: y = [ y 1 , y 2 , . . . , y m ] T \bm y=[y_1,y_2,...,y_m]^T y=[y1,y2,...,ym]T
    m组输入: X = [ x 1 , x 2 , . . . , x m ] \bm X=[\bm x_1, \bm x_2,...,\bm x_m] X=[x1,x2,...,xm]
    l ( X , y , w , b ) = 1 2 n ∣ ∣ y − X w − b ∣ ∣ 2 \mathcal{l} (\bm X, \bm y,\bm w, b)=\dfrac{1}{2n}||\bm y-\bm X \bm w-b||^2 l(X,y,w,b)=2n1∣∣yXwb2
    w ∗ , b ∗ = a r g min ⁡ w , b l ( X , y , w , b ) \bm w^*, b^* = arg \mathop {\min }\limits_{\bm w, b} {\mathcal{l} (\bm X, \bm y,\bm w, b)} w,b=argw,bminl(X,y,w,b)

  • 优化方法
    (1)梯度下降
    初始权重: w 0 \bm w_0 w0
    迭代: w t = w t − 1 − η ∂ l ∂ w t − 1 \bm w_t=\bm w_{t-1}-\eta \frac{\partial l}{\partial \bm w_{t-1}} wt=wt1ηwt1l
    η \eta η学习率(步长的超参数,认为指定),沿着负梯度方向,可减少loss
    η \eta η太小,迭代次数太多,成本高;太大,会来回摆动
    (2)小批量随机梯度下降(深度学习常用)
    随机选择s个样本来近似loss,可减小计算量(批量大小s偏大->近似度高但计算量大)

  • 实现

# %matplotlib inline # Jupyter Notebook用的
import torch
import os
import random
from d2l import torch as d2l
import matplotlib.pyplot as plt

def synthetic_data(w, b, num_examples):# 权重、偏差、样本数
    # 生成y = Xw + b + 噪声
    X = torch.normal(0,1,(num_examples, len(w)))
    # 生成均值为0,方差为1的num_examples*len(w)的张量
    y = torch.matmul(X, w) + b
    # torch.mm仅支持二维张量的矩阵乘法,而torch.matmul支持更多维度的张量
    y += torch.normal(0, 0.01, y.shape)# 加噪声
    return X, y.reshape((-1,1))
# 将张量 y 的形状从未知的初始形状更改为两个维度的新形状,
# 其中第一个维度的大小由张量 y 中的元素总数自动计算得出,第二个维度的大小固定为1。

# 真实值w, b
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 100)
print('features:', features[0], '\nlabel:', labels[0])
# 画图
d2l.set_figsize()
d2l.plt.scatter(features[:,1].detach().numpy(),labels.detach().numpy(), 1)
# detach()分离出数值,供numpy()用

# plt.figure(figsize=(4, 3))
# plt.scatter(features[:, 0], labels, 1)
plt.show()# 这样才能显示出图来,但不知道为啥,上面两行是另一种画图的方法
# 每次读取一个小批量的函数
# 输入:批量大小,特征、标签
def data_iter(batch_size, features, labels):
    num_examples = len(features) #样本数
    indices = list(range(num_examples)) #range(n)生成从[0,n)的序列,步长为1,list(range(...))转换为列表
    random.shuffle(indices) # 打乱列表中的数的顺序
    for i in range(0, num_examples, batch_size):#i从0开始到num_examples,步长为batch_size
        batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)])
        # 每个批量的所有序号,若最后一个批量不足batch_size个样本,则到最后一个样本为止
        yield features[batch_indices], labels[batch_indices]
        # 生成每个批量的样本和标签
        # 生成器通过使用yield关键字来产生值。当生成器的函数执行到yield语句时,
        # 它会暂停并保存当前所有的运行信息,返回yield的值。在下一次调用next()方法
        # 或再次发送新的值给生成器时,它会从上次暂停的yield处继续执行。

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break# 这里打印一次就推出,不然数据太多了
# 使用for循环会自动调用next()方法,直到生成器没有更多的值可以产生。

# 定义初始化参数模型
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
# 定义loss
def squared_loss(y_hat, y):#预测值,真实值
    return (y_hat - y.reshape(y_hat.shape))**2 / 2#保证y和y_hat的形状一样
# 定义优化算法(小批量随机梯度下降)
def sgd(params, lr, batch_size):# 所有参数(w,b),学习率,批量大小
    with torch.no_grad():# 更新的时候不要参与梯度计算
        # with torch.no_grad()是PyTorch中的一个上下文管理器,用于在不计算梯度的情况下执行代码块
        # 告诉PyTorch不要构建计算图
        for param in params:
            param -= lr * param.grad / batch_size # 梯度下降
            param.grad.zero_()# 梯度设为0,否则会累积
# 训练过程
lr = 0.03 # 学习率
num_epochs = 3 # 对所有样本扫描num_epochs次
net = linreg # 重新命名模型和loss的名称,方便以后换成其他模型、loss
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() #结果存在了param.grad
        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}')

输出:

features: tensor([-0.6639, -0.2955]) 
label: tensor([3.8862])
tensor([[-0.2816, -0.7242],
        [-2.1460, -1.4689],
        [ 0.2681, -0.0632],
        [ 1.5474,  0.5062],
        [ 0.2974, -1.1747],
        [-0.1228, -1.1603],
        [-0.9479,  1.5074],
        [-0.2452,  0.5163],
        [-0.1720, -0.2860],
        [ 0.0665, -0.0946]])
 tensor([[ 6.1077],
        [ 4.9030],
        [ 4.9552],
        [ 5.5848],
        [ 8.7874],
        [ 7.9042],
        [-2.8086],
        [ 1.9495],
        [ 4.8153],
        [ 4.6611]])
epoch 1, loss 8.458451
epoch 2, loss 4.990515
epoch 3, loss 2.949248
  • 简洁实现
import numpy as np
import torch
from torch.utils import data
import os
import random
from d2l import torch as d2l
from torch import nn #神经网络

import matplotlib.pyplot as plt

def synthetic_data(w, b, num_examples):# 权重、偏差、样本数
    # 生成y = Xw + b + 噪声
    X = torch.normal(0,1,(num_examples, len(w)))
    # 生成均值为0,方差为1的num_examples*len(w)的张量
    y = torch.matmul(X, w) + b
    # torch.mm仅支持二维张量的矩阵乘法,而torch.matmul支持更多维度的张量
    y += torch.normal(0, 0.01, y.shape)# 加噪声
    return X, y.reshape((-1,1))
# 将张量 y 的形状从未知的初始形状更改为两个维度的新形状,
# 其中第一个维度的大小由张量 y 中的元素总数自动计算得出(即y中元素总数/第二维度),第二个维度的大小固定为1。

# 真实值w, b
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 100)

# 调用框架中现有的API读取数据
def load_array(data_arrays, batch_size, is_train = True):# 是否为训练模式,默认为True
    dataset = data.TensorDataset(*data_arrays)
    # 使用data.TensorDataset将data_arrays中的数据转换为一个张量数据集
    return data.DataLoader(dataset, batch_size, shuffle=is_train)
# 返回一个data.DataLoader对象,该对象将数据集分批次加载,并根据is_train参数决定是否打乱数据顺序。

batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter))

# 使用框架的预定义好的层
net = nn.Sequential(nn.Linear(2,1))#输入的维度2,输出的维度1
# 初始化参数
net[0].weight.data.normal_(0, 0.01) #权重的均值为0,方差为0.01
net[0].bias.data.fill_(0) #偏差b
#已有的均方误差
loss = nn.MSELoss()
# 已有的随机梯度下降法
trainer = torch.optim.SGD(net.parameters(), lr=0.06)
# 训练过程
num_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}')
    # 设置输出的l为浮点型
    #  f-string 中,可以使用大括号 {} 来包围表达式,使用冒号:来指定格式,设置小数点后的位数、对齐方式等。

09 Softmax回归,损失函数 图片分类数据集

  • Softmax回归解决的是多分类问题。
    分类的输出通常有多个,输出i是预测为第i类的置信度,将置信度最大的结果作为预测结果。判断标准:正确预测的置信度远远大于其他预测的置信度
    评价指标;
    交叉熵:是一种衡量两个概率分布之间差异的方法,深度学习中用作损失函数
    定义损失为 l ( y , y ^ ) = − ∑ i y i l o g y ^ i l(\bm y, \hat{\bm y})=-\sum_{i} y_ilog{{\hat y_i}} l(y,y^)=iyilogy^i
    梯度是真实概率和预测概率的区别: ∂ o i l ( y , y ^ ) = s o f t m a x ( o ) i − y i {\partial _{o_i}}l(\bm y, \hat{\bm y})=softmax(\bm o)_i-y_i oil(y,y^)=softmax(o)iyi
  • 损失函数选择可导的
    损失函数的梯度为常数->每次优化的程度固定
    损失函数随预测值和真实值的差而减小->当预测值接近真实值时,优化变缓,更平滑地靠近真实值
  • 图像分类数据集
import torch
import torchvision #对计算机视觉实现的库
from torch.utils import data 
from torchvision import transforms # 对数据进行操作
from d2l import torch as d2l # 将一些函数的实现导入d2l
import matplotlib.pyplot as plt

d2l.use_svg_display() # 使用svg来显示图片,清晰度高

# 把图片转成pytorch的tensor形式
trans = transforms.ToTensor()
# 把图片集FashionMNIST拿到,下载到下级目录的data里,train=True下载的是训练数据集,
# transform参数表示拿到的是tensor而不是图片,download默认从网上下载(事先下载好就不用指定download
# mnist_train = torchvision.datasets.FashionMNIST(root="E:\study\Code\python\code\data",
#                                                  train=True, transform=trans, download=True)
# # 测试集下载
# mnist_test = torchvision.datasets.FashionMNIST(root="E:\study\Code\python\code\data",
#                                                train=False, transform=trans, download=True)

mnist_train = torchvision.datasets.FashionMNIST(root="E:\study\Code\python\code\data",
                                                 train=True, transform=trans)
# 测试集下载
mnist_test = torchvision.datasets.FashionMNIST(root="E:\study\Code\python\code\data",
                                               train=False, transform=trans)
print(len(mnist_train))
print(len(mnist_test))
print(mnist_train[0][0].shape) #黑白图片,channel数为1

# 定义图片标签
def get_fashion_mnist_labels(labels):
    text_labels = ['t-shirt', 'trouser', 'pollover', '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)):
    # zip(axes, imgs)将axes和imgs中的元素按照顺序配对,enumerate()函数对这个迭代器进行遍历,
    # 并为每个元素分配一个从0开始的索引。最后,enumerate(zip(axes, imgs))返回一个包含索引和元素配对的迭代器。
        if torch.is_tensor(img): # 如果图像是一个张量,将其转换为NumPy数组并显示在子图上
            ax.imshow(img.numpy())
        else:
            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

X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
show_images(X.reshape(18,28,28), 2, 9, titles=get_fashion_mnist_labels(y))
plt.show()
  • softmax回归的实现
import torch
import torchvision #对计算机视觉实现的库
from IPython import display
from d2l import torch as d2l
from torchvision import transforms # 对数据进行操作
import matplotlib.pyplot as plt

trans = transforms.ToTensor()

mnist_train = torchvision.datasets.FashionMNIST(root="E:\study\Code\python\code\data",
                                                 train=True, transform=trans)
# 测试集下载
mnist_test = torchvision.datasets.FashionMNIST(root="E:\study\Code\python\code\data",
                                               train=False, transform=trans)

batch_size = 256 # 批量大小
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)# 返回训练集和测试集的迭代器 

num_inputs = 784 # 28*28,因为softmax的输入是向量,所以需要把矩阵拉成一个向量
num_outputs = 10 # 标签有10个,所以输出的长度为10
# 初始化权重和偏差
W = torch.normal(0,0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
print(b)
# 定义softmax操作
def softmax(X):#输入为样本数*图片向量长度 的矩阵
    X_exp = torch.exp(X) # 指数操作保证每个元素大于0
    partition = X_exp.sum(1, keepdim=True)
    return X_exp/partition # 每个元素/每行的和,保证每行的和为1
# 定义模型
def net(X):
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
#输出为batch_size * num_outputs

# 定义损失函数 交叉熵
def cross_entropy(y_hat, y):
    return -torch.log(y_hat[range(len(y_hat)), y])
# y_hat[[0,1,2,...], y],y_hat是m*n,y是m*1,
# 返回的是y_hat[0][y[0]],y_hat[1][y[1]],...
# 即正确分类对应的概率

# 分类精度
def accuracy(y_hat, y):
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        #如果y_hat的维度大于1,且列数大于1(说明不止1个分类
        y_hat = y_hat.argmax(axis=1)#每一行的最大值
    cmp = y_hat.type(y.dtype) == y #将y-hat转为和y一个类型,同一样本,分类一样的为true,否则false
    return float(cmp.type(y.dtype).sum())# 返回分类正确的样本数

def 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()) #y.numel()是y中元素的总个数
    return metric.data[0] / metric.data[1] # 正确预测数/样本总数


class Accumulator:
    #self表示实例本身
    def __init__(self, n):
        self.data = [0.0]*n
        # 初始化方法,接收一个参数n,用于创建一个长度为n的列表
        # data是类的属性,不需要像java单独定义

    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]
        # 接收可变数量的参数,将这些参数与data列表中的元素逐个相加,并将结果存储回data列表
        # a是self.data中的数,b是args中的,a+b就是将b的值累加到self.data中
        # *args表示args的数量可变

    def reset(self):
        self.data = [0.0] * len(self.data)
    
    def _getitem_(self, idx):
        return self.data[idx]

# 训练
def train_epoch_ch3(net, train_iter, loss, updater):#ch3:第三章
    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.data[0] / metric.data[2], metric.data[1] / metric.data[2]# 返回训练损失和训练精度
# 定义一个在动画中绘制数据的实用程序类Animator, 它能够简化本书其余部分的代码
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=[]
        d2l.use_svg_display()
        # d2l.use_svg_display()是Dive into Deep Learning(深度学习)这本书中的一个函数,
        # 用于在Jupyter Notebook中显示矢量图形。它的作用是将matplotlib的默认输出格式从PNG更改
        # 为SVG,以便更好地支持矢量图形的绘制和交互
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:# 如果只有一行一列,则将坐标轴放入一个列表中。这样可以方便后续操作
            self.axes = [self.axes, ]
        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__"):
            # hasattr()函数在Python中用于检查对象是否具有给定的属性。
            # 它接受两个参数:对象和属性名(字符串形式)。如果对象具有该属性,则返回True,否则返回False。
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:# 如果X或Y为空,则初始化它们。这样可以确保后续操作时不会出错
            self.X = [[] for _ in range(n)]
            # 创建一个包含n个空列表的列表,并将这个列表赋值给变量self.X。
            # 这里的range(n)表示从0到n-1的整数序列,for _ in range(n)表示遍历这个整数序列,
            # 对于每个整数,都创建一个空列表。最后将这些空列表组成一个列表,即得到一个包含n个空列表的列表。
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x,y)):
            # 遍历x和y的元素,将非空元素添加到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()# 调用config_axes()方法设置坐标轴属性
        plt.draw()# 不用jupyter,要加了这两行才能显示
        plt.pause(0.001)
        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 = 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, print(train_loss)
    assert train_acc <= 1 and train_acc > 0.7, print(train_acc)
    assert train_acc <= 1 and test_acc > 0.7, print(test_acc)
    plt.show()
    # 如果这些结果在范围内,则打印
    # assert关键字用于断言某个条件为真。如果条件为假,则会触发AssertionError异常。
    # assert语句通常用于调试目的,以确保代码在特定条件下的行为符合预期

lr = 0.1
def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)     

num_epochs = 5
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])
    plt.show()# 不用jupyter,要加了这个才能显示

predict_ch3(net, test_iter)

  • 简洁实现(使用PyTorch的nn模型提供的数据预处理的模块来实现)
import torch
from d2l import torch as d2l
from torch.utils import data
from torch import nn

true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 500)

def load_array(data_arrays, batch_size, is_train=True):
    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)

next(iter(data_iter))

net = nn.Sequential(nn.Linear(2, 1)) 
#指定线性神经网络的输入维度为2,输出维度为1
# 为了后续处理的方便,把数据放到Sequential容器,可理解为list of layers, 把参数按顺序放在一起

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
print(net[0].weight.data)
# loss使用均方误差
loss = nn.MSELoss()
# 
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
# 训练
num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X), y)# net自己带了模型参数,不需要像之前传入w,b
        trainer.zero_grad()# 梯度清零
        l.backward() #pytorch已经求了sum(loss函数中有sum),不需要手动求
        trainer.step() # 模型更新
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')

QA:
(1)batch_size小,有利于随机梯度下降的收敛。因为batch_size越小,噪音越大,一定的噪音可以让深度神经网络不会走偏
(2)softlabel:理想的情况下,多分类预测希望正确的预测类的可能性为1,其他为0(0-1分布),如果用softmax来训练这样的模型,需要正确的预测类的输出为无穷大时,才能达到0-1分布,这在现实中就很难实现的。softlabel就是将正确分类的可能性设为0.9,其他类0.1,即可认为模型有效,且在现实中有实现的可能性。

  • 31
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值