总结
以_结尾的方法,好像是原位替换(即 原地修改,就地修改变量)如 fill_()
感恩的心:(沐神的直播环境)
08-线性回归+基础优化算法
引言(如何在美国买房)
根据现在行情预测房价
线性回归(简化模型)、线性模型、神经网络
b为偏差
扩展到一般化
线性模型
每个箭头代表一个权重
当层单层神经网络原因:不看输出层,将权重层和input放一起 带权重的层只有一层
【书中】
衡量预估质量
1/2是为了求导时把2消去
线性回归(求最优解)
一般的模型都没有显示解,因为有显示解的模型都过于简单(显示解:个人感觉就是 能直接用数学方法手动解出来的解)
训练数据
当每个xi是一个列向量样本时, X即一个大矩阵,该矩阵每一行对应一个样本
y是一个列向量,每个yi是一个实数数值,对应预测的房价
参数学习、显示解
损失函数l:模型在每一个数据上的损失 求均值就得到损失函数
目标是求 最小的损失函数
这里X是一个矩阵,x_{i}的意思是第i条数据,每一条数据都包含了(房间个数,居住面积)等决策信息
argmin是指让后面公式取得最小值的参数大小, min是指公式本身的最小值.
因为线性模型,因此有显示解。下面求:
为了写起来方便,将偏差b也写进来,
此时为了能进行矩阵计算X·W,为 矩阵X加入一列全一的特征(增广矩阵?)
最优解是 导数为0的,即
此处是唯一一个有最优解的模型,以后学的都不会有了
具体计算过程:
优化算法(初步体验)
后面有专门的章节将优化方法
梯度下降法(含超参数:学习率)
【实际场景】很少直接使用梯度下降,一般都用小批量随机梯度下降
当一个模型没有显示解时,我们会:
下图画的是等高线
梯度是使其增加最快的方向,负梯度就是下降最快的方向
学习率:此处的yita,是超参数,是需要人为指定的(超参数不需要数据来驱动,而是在训练前或者训练中人为的进行调整的参数)
以w0到w1为例:yita相当于每一步走多远(即w0到w1),偏导数(即梯度)相当于 确定向哪个方向走(沿负梯度方向就是沿下降最快的方向)
上图中橙框乘红框就相当于 w0到w1、w1到w2的每段向量, 则上述等式相当于 起点+ yita乘梯度(即 要运动的向量)=最终的向量
选太小:每次走的步长有限,需要计算很多次梯度,那么 时间和空间成本都太大
后续有教程教选学习率
【常用】小批量随机梯度下降法(Stochastic Gradient Descent)(含超参数:批量大小batch)
每次走一步都计算所有的梯度成本太大。
代码实现
线性回归(从0实现,不用框架)
从零实现整个方法,包括 数据流水线、模型、损失函数、小批量随机梯度下降优化器
生成数据集
标准差:standard deviation
torch.normal() 返回一个张量,包含从给定参数means,std的离散正态分布中抽取随机数。 均值means是一个张量,包含每个输出元素相关的正态分布的均值
torch.matmul:两个张量矩阵相乘
reshape中-1表示自动计算,1表示固定,即列向量为1
弹幕说:
这个函数返回的特征与标签,相当于分别把真实的房屋‘关键因素’和对应的‘房价’列出来。这一页的信息,相当于去市场调研收集真实的房屋数据。
features中每一行都包含一个二维数据样本,labels中的每一行都包含一维标签值(一个标签)。
绘制数据集
此处打印看一下 第0个样本的样子 以及 可视化一下所有样本
使用.detach()方法可以将一个张量从当前的计算图中分离出来,使其变成一个不再需要梯度追踪的普通张量,即使它是由需要梯度的操作创建的
从图中也看出 横纵轴变量是有线性相关性的
读取小批量
定义一个data_iter函数,接收批量大小、特征矩阵、标签向量作为输入,生成大小为batch_size的小批量
yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。
%matplotlib inline
import random
import torch
from d2l import torch as d2l
def synthetic_data(w,b,num_exaples):
"""生成 y = Xw + b + 噪声"""
X = torch.normal(0,1,(num_exaples,len(w)))
y = torch.matmul(X,w) + b
y += torch.normal(0,0.01,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, 1000)
print('features:',features[0],'\nlabel:',labels[0])
d2l.set_figsize()
d2l.plt.scatter(features[:,(1)].detach().numpy(),labels.detach().numpy(),1)
# data_iter:把所有样本分成很多个batch,每个batch中的样本数量为batch_size(函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量)
def data_iter(batch_size,features,labels):
num_examples = len(features) # 样本个数
indices = list(range(num_examples)) # 样本索引(从0- num_examples-1)
# 这些样本是随即读取的,没有特定的顺序
random.shuffle(indices) # 把索引随机打乱
for i in range(0, num_examples, batch_size): # 遍历所有样本,每次步长为batch_size
# 每batch_size个样本为一组,拿到该组样本的索引。注:当i+batch_size超出时,取num_examples
# batch_indices = torch.tensor(indices[i:min(i+batch_size,num_examples)])
batch_indices = indices[i:min(i+batch_size,num_examples)] # 这么写也行,只是为了拿到索引嘛, 索引用不用tensor感觉均可
yield features[batch_indices], labels[batch_indices] # 基于样本索引获得对应的特征,及对应的标签
batch_size = 10
for X,y in data_iter(batch_size, features, labels):
print(X, '\n', y) # 取一个批次后,就break跳出了
break
定义模型、损失函数、优化算法
说明:
1.关于 l.sum().backward():
%matplotlib inline
import random
import torch
from d2l import torch as d2l
def synthetic_data(w,b,num_exaples):
"""生成 y = Xw + b + 噪声"""
X = torch.normal(0,1,(num_exaples,len(w)))
y = torch.matmul(X,w) + b
y += torch.normal(0,0.01,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, 1000)
print('features:',features[0],'\nlabel:',labels[0])
d2l.set_figsize()
d2l.plt.scatter(features[:,(1)].detach().numpy(),labels.detach().numpy(),1)
def data_iter(batch_size,features,labels):
num_examples = len(features) # 样本个数
indices = list(range(num_examples)) # 样本索引
# 这些样本是随即读取的,没有特定的顺序
random.shuffle(indices) # 把索引随即打乱
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i:min(i+batch_size,num_examples)]) # 当i+batch_size超出时,取num_examples
yield features[batch_indices], labels[batch_indices] # 获得随即顺序的特征,及对应的标签
batch_size = 10
for X,y in data_iter(batch_size, features, labels):
print(X, '\n', y) # 取一个批次后,就break跳出了
break
# 定义初始化模型参数
w = torch.normal(0,0.01,size=(2,1),requires_grad=True) # 初始化w:w是长为2的向量,均值为0,标准差为0.01
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 # 将y统一成与y_hat一样的尺寸
# 定义优化算法
def sgd(params,lr,batch_size): # params:参数list,如[w、b]
"""小批量随即梯度下降"""
with torch.no_grad(): # 设置不更新梯度
for param in params: # 每个参数进行遍历
# 每个参数进行更新:因为之前的损失函数没有求均值,所以这里除以 batch_size 求了均值。由于乘法的线性关系,这里除以放在loss的除以是等价的。
param -= lr * param.grad / batch_size
param.grad.zero_() # 每个参数的梯度清零(以避免影响下次梯度计算)
# 训练过程
lr = 0.03
num_epochs = 3
net = linreg # 这里用线性模型,这样写是很方便net赋予其他模型,只需要改一处,不需要下面所有网络模型名称都改
loss = squared_loss
# 训练过程(都是套路,大同小异,两次for loop)
for epoch in range(num_epochs): # 第一层for:对数据扫一遍
for X,y in data_iter(batch_size,features,labels): # 第二层for:每次拿出batch_size大小的一个batch
l = loss(net(X,w,b),y) # x和y的小批量损失: 输入参数给模型net得到预测值;用预测值和真实值y做损失。此时损失l就是长度为batch_size的向量
# 因为l是形状是(batch_size,1),而不是一个标量。l中所有元素被加到一起,并以此计算关于[w,b]的梯度
l.sum().backward() # 求和算梯度不懂什么意思的可以回看上一节自动求导,求和后算梯度是求每一个y对每一个x的梯度,不求和直接算梯度结果是一个矩阵!不是我们要的结果
sgd([w,b],lr,batch_size) #使用参数的梯度更新参数(此处可能存在细节问题,但只是为了说明整体上的训练过程,因为 最后一个batch可能样本数量不足batch_size个)
# 扫完一个epoch后,评价一下(这部分无需更新梯度)
with torch.no_grad():
train_l = loss(net(features,w,b),labels)
print(f'epoch{epoch+1},loss{float(train_l.mean()):f}')
# 比较真实参数和通过训练学到的参数来评估训练的成功程度
print(f'w的估计误差:{true_w-w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b-b}')
打印结果:
体验lr太大或太小时的效果:(注意 改变lr时要 重新初始化一下w和b)
当lr很小时
此时可以考虑增加epoch,但loss还不是特别小
lr太大时,loss为nan,因为可能 求导时会除以0,超出傅里叶的计算范围了
线性回归(使用框架)
python中的* (拆包、解包):在Python中,号表示解包操作,它可以将一个包含多个元素的元组、列表、集合等数据结构解压为多个独立的元素。 表示接受任意多个参数并将其放在一个元组中
下图中 左侧为本节图片,等价于右图(即上节手动实现的方式)
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
from torch import nn
# 0.基本数据准备
true_w = torch.tensor([2,-3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w,true_b,1000) # 库函数生成人工数据集
# 1.【与不用框架实现的区别之一】调用框架现有的API来读取数据(把已有的训练数据(features,labels)传入TensorDataset)
def load_array(data_arrays,batch_size,is_train=True):
"""构造一个Pytorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays) # dataset相当于Pytorch的Dataset。一个星号*,表示对list解开入参(是python中的拆包符号)。
return data.DataLoader(dataset,batch_size,shuffle=is_train) # 返回的是从dataset中随机挑选出batch_size个样本出来
batch_size = 10
data_iter = load_array((features,labels),batch_size) # 返回的数据的迭代器
print(next(iter(data_iter))) # iter(data_iter) 是一个迭代器对象,next是取迭代器里面的元素
# 2.使用框架的预定义好的层
# nn是神经网络的缩写
net = nn.Sequential(nn.Linear(2,1))
# 3.初始化模型参数
# 以单下划线_结尾的方法都是原位替换
net[0].weight.data.normal_(0,0.01) # 使用正态分布替换掉weight变量里面的数据值。 其中net[0]就是 神经网络net的第一层(即 nn.Linear(2,1))。
net[0].bias.data.fill_(0) # 偏差bias变量里面的值设置为0。normal_ :以单下划线_结尾的方法都是原位替换
print(net[0])
# 4.计算均方误差使用的是MSELoss类,也称为平方L2范数
loss = nn.MSELoss() #L1是算术差,L2是平方差
# 4.实例化SGD实例
trainer = torch.optim.SGD(net.parameters(),lr=0.03) # 传入网络中所有的参数(此处指w和b)
# 5.训练过程代码与从零开始时所做的非常相似
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter: # 从DataLoader里面一次一次把所有数据拿出来
# print("X:",X)
# print("y:",y)
l = loss(net(X),y) # net(X) 为计算出来的线性回归的预测值
trainer.zero_grad() # 梯度清零(避免影响其他次计算)
l.backward() # pytorch已经帮我们做了 向量.sum() (即转成了标量),无需手动写了(如上节的l.sum().backward())
trainer.step() # SGD优化器优化模型:调用step()进行模型更新
l = loss(net(features),labels) # 当前epoch的loss
print(f'epoch{epoch+1},loss{l:f}') # 这行都是l 而不是1
打印结果:
QA
1.可以,直接 上面pip就行
2.后面会讲一下二者区别,但是区别不大。
绝对差值在0点时可能不能求导。
3.也可以不求。求不求本质上没区别,不求平均 梯度数值会比较大。如果损失不除以n,那么学习率 除以n也行, 但是 只不过除了之后 更好调(除n主要就是用来忽略样本规模的)
- 是的
6.除以谁都没关系,不影响 本质, 最后求的都是min Loss
7.经验。后续也会讲
8.其实batch_size越小越好,对收敛越好但是大了不行。
采样样本越小,噪音越大,但是噪音对于神经网络是好事情, 更利用训练出 泛化性强的网络
9.batch_size只要不是特别特别大都行
10.是的。 梯度是线性的, 和的均值等于均值的和
11.不是。批量大小是一样的即batch_size,随机只每次在样本中 随机采集batch_size大小个元素
12.需要。 但是一般损失函数和正则会分开, 而且正则有很多方法。
13.首先二阶导数不一定能计算,很难算。 有时一阶导是向量,二阶导就变成了矩阵
为什么不用牛顿法:
【二阶导不一定能计算,或者方便计算】此处有两个问题,一个是统计模型(损失函数长什么样),一个是优化模型(用什么算法求解),但其实这俩都是错的, 因为 统计模型是错的,优化模型肯定也求不对, 应该是指我们很难找到 完全正确、十分精准的损失函数,一般机器学习都求不到最优解(面对实际的问题我们不可能拿到精确的模型,真实的损失函数是非常复杂的)。
因此求出一个统计模型的最优解的意义并不大, 因为它是一个错误的模型,因此收敛快不快并不care,我们关心损失函数收敛到那个地方,用牛顿法虽然快,但 求的结果可能并不平坦。 不一定 很快得到的结果就是好的结果(可能泛化性差)
【牛顿法求出的结果不一定比随机梯度求得的结果好,收敛不一定快, 即便收敛快、结果也不一定好】
弹幕说:收敛更准确比更快更重要
14.如果损失函数L中没除以n,那么 学习率yita除以n
15.从计算图分离:要转换成numpy数据类型,就需要先将其从求梯度的计算图中分离出来(但是具体也看pytorch版本)
16.detach就是pytorch中的
17.是的,如果数据量特别大会爆掉。 但是整本书中dataset都不大, 如果真实情况下 GPU足够大,load全部也没问题。
实际场景下,数据都存储在硬盘上的
18.弹幕说也可以
19.相当于洗牌之后再把所有的牌抽起来看一遍
20.不用return时,每次需要数据时run一遍即可,现用现生成,节省内存;
用iter就是python的写法,python的习惯
21、22.三种做法:(代码中只是刚好整除了)
(1)【常用】代码中:每次取的范围是: [ i , min(i+batch_size,num_examples)]
(2)丢掉最后不足的样本
(3)从下一个epoch中补一些过来 使最后一个batch也满足 batch_size个样本数
24.lr不做衰减也问题不大,此处先不讲这个
24-2.
(1)可以判断相邻两次epoch的loss的变化范围不大(如1%时)
(2)用 交叉验证集,当其精度不增加时
(3)算力允许的情况下,epoch多点也没关系
25.是的。除了线性回归模型外,都没有显示解。能求出显示解的模型都太简单了
NPC(np-complete)问题即NP完全问题, 任何一个NP问题都可以在多项式时间内归约为某个NP问题,那么这个问题就成为NPC问题
【NP问题】
26.初始时也可以用同样的值,偷懒就用了随机的
27.求导时会涉及除法,后面也会讲数据稳定性问题
28.不一定, 只是此处手动设了,后续就不会了
29.是的
30.就是为了print,同时我们只run了forward,没run backward,即没计算梯度,无需清0
31.因为pytorch不自动帮你清零,如果 不人为清零,则 后面梯度计算时会在前面的梯度计算的结果上 累加
32.后面会单独讲
09-softmax回归+损失函数+图片分类数据集
Softmax回归(具体细节也可看吴恩达)
【softmax算子(操作子)的作用】将所有的输入都 拉到[0,1]区间内,且令这些输入 经由softmax的输出 之和为1(刚好符合概率的感觉)
回归vs分类
下图补充:
回归:
输出的区间是 单个自然区间
分类:
多输出,输出的个数等于类别的个数
【分类问题举例】Kaggle上的分类问题
从回归到多类分类
如何从回归问题过渡到分类问题:
【编码和初步计算】均方损失
1.编码
如果有n个类别,则 构造一个 长度为n的向量y。(one-hot编码)
如果该东西真实类别是i,则yi=1,其他yi=0
下图中yhat 是 使得oi取最大值对应的那个类别标号i。
oi:中o为置信度,o是神经网络的输出
y是真实值,yhat是预测值
【分析】无校验比例
【我们关心的问题1】对于分类问题,我们不关心 物体实际的值,而是 关心 分类正确物体的置信度是不是特别大
下图中yhat 是 使得oi取最大值对应的那个类别标号i
我们目的是 要使得对正确类别y的置信度 oy能远远大于 其他类别的置信度oi, 即 二者的差大于等于一个阈值(如 derta)
这样能保证我们的模型能够将 物体真正的类别和其他类别拉开距离
【softmax定义】【基于指数运算进行映射】校验比例(看书效果更好)
【我们关心的问题2】
同时接问题1,我们还希望 分类的预测输出是一个概率,暂时现在我们的输出是一个向量( o1,…,on)
此时我们引入一个操作子 softmax,作用于o,得到yhat,它是一个长度为n的向量(y1hat,…,ynhat),该向量的每个元素yi都非负、且这些元素之和为1(每个元素就是 该物体被预测为当前索引类别的概率)
其中yhati
【个人总结softmax作用】【使用exp(即指数)、exp(oi)/xigema exp(ok) 的目的】
(1)通俗版本:将所有的输入都 拉到[0,1]区间内,且令这些输入 经由softmax的输出 之和为1(刚好符合概率的感觉)
(2)将 每个元素都映射到非负区间上(这才符合概率)且 最后的yhat i更符合概率的特点, 值属于[0,1]且 之和为1
(因为真实的向量y也是 所有元素之和为1,即只有一个1, 也可以将其视为概率)
【书上soffmax作用】softmax函数能够将未规范化的预测变换为⾮负数(范围在 0-1)并且总和为1,同时让模型保持可导的性质
【为什么不能直接将oi作为输出】
我们能否将未规范化的预测o直接视作我们感兴趣的输出呢?答案是否定的。因为将线性层的输出直接
视为概率时存在⼀些问题:⼀⽅⾯,我们没有限制这些输出数字的总和为1。另⼀⽅⾯,根据输⼊的不同,它
们可以为负值。
【o与y的关系(这也就是softmax的 主要计算原理)】
下图中的softmax的输入O是一个(行)向量
下图中softmax的输入是一个矩阵(可见softmax从0实现一节)
Softmax和交叉熵(Cross Entropy)损失
p和q是两个概率,H表示交叉熵,i为第i个元素,共n个元素
向量y(即加粗的黑体y)是真实值的向量(共n个元素, 只有一个元素为1,其余为0),向量yhat是预测得到的向量,
标量y是 物体真实类别,标量yhati是物体被预测为 i类别的概率
解释下图第一个公式的推导(如绿框):由于y是⼀个⻓度为n的独热编码向量,所以除了⼀个项以外的所有项j都消失了。
yhaty:对真实类别y的预测的概率 为yhat。 即 对于分类问题,我们只关心对正确类别的预测值有多大,而不关心对于非正确类别的预测情况
上图中第三个公式的推导
损失函数
【作用】用于衡量真实值和预测值之间的区别
下面:
y:真实值
y’:预测值
除以2:求导后 2和1/2抵消
L2 loss
横轴是y’
④ 【变化规律】当预测值y’跟真实值y隔的比较远的时候,(真实值y为0,预测值就是下面的曲线里的x轴),梯度比较大,所以参数更新比较多。
⑤ 随着预测值靠近真实值是,梯度越来越小,参数的更新越来越小。
下图中的更新指参数权重的更新
L1 loss
L2也不一定很好,因为 当y‘离原点很远时(此例中取真实值y=0),也不一定希望更新的很快,因此看下L1
① 相对L2 loss,L1 loss的梯度就是距离原点时,梯度也不是特别大,权重的更新也不是特别大。会带来很多稳定性的好处。
② 他的缺点是在零点处不可导,并在零点处左右有±1的变化,这个不平滑性导致预测值与真实值靠的比较近的时候,优化到末期的时候,可能会不那么稳定。
Huber’s Robust Loss(鲁棒损失)
Huber’s Robust Loss 结合了L1和L2 loss的优点:
当预测值和真实值的差距不同时,Loss计算方式不同:即 预测值和真实值差的比较大时,梯度通过比较均匀的力度调整参数;预测值和真实值差的比较小时(即优化末期),梯度绝对值越来越小,以保证 整个参数调整过程是比较平滑的
图像分类数据集(Fashion-MNIST)
0.概述和课件
此处是先初步介绍一下后面要常用到的一个数据集Fashion-MNIST
① MINIST数据集是图像分类中广泛使用的数据集之一,但作为基准数据集过于简单。
② 下面将使用类似但更复杂的Fashion-MNIST数据集。
【代码】
1.导入包
2.准备数据
3.单进程读取batch并可视化的函数
4.多进程读取一个小batch,大小为batch_size
一般要保证读取数据的速度大于训练的速度
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display()
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=True,transform=trans,download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=False,transform=trans,download=True)
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):
"""Plot a list of images."""
figsize = (num_cols * scale, num_rows * scale) # 传进来的图像尺寸,scale 为放缩比例因子
_, axes = d2l.plt.subplots(num_rows,num_cols,figsize=figsize)
print(_)
print(axes) # axes 为构建的两行九列的画布
axes = axes.flatten()
print(axes) # axes 变成一维数据
for i,(ax,img) in enumerate(zip(axes,imgs)):
if torch.is_tensor(img):
# 图片张量
ax.imshow(img.numpy())
ax.set_title(titles[i])
else:
# PIL图片
ax.imshow(img)
X, y = next(iter(data.DataLoader(mnist_train,batch_size=18))) # X,y 为仅抽取一次的18个样本的图片、以及对应的标签值
show_images(X.reshape(18,28,28),2,9,titles=get_fashion_mnist_labels(y))
batch_size = 256
def get_dataloader_workers(): # 一般数据都在硬盘上,不能一次读取完,此时使用4个进程来读
"""使用4个进程来读取的数据"""
return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
timer = d2l.Timer() # 计时器对象实例化,开始计时
for X,y in train_iter: # 遍历一个batch_size数据的时间
continue
f'{timer.stop():.2f}sec' # 计时器停止时,停止与开始的时间间隔事件
5.【完整代码】整合-上述内容到一个函数中(load_data_fashion_mnist)
本节整合代码并实现函数load_data_fashion_mnist 以供后面使用 Fashion-MNIST数据集
额外声明resize参数,以便后续 如果模型需要不同size的图片时可以 基于此调整
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
d2l.use_svg_display()
# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=True,transform=trans,download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=False,transform=trans,download=True)
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):
"""Plot a list of images."""
figsize = (num_cols * scale, num_rows * scale) # 传进来的图像尺寸,scale 为放缩比例因子
_, axes = d2l.plt.subplots(num_rows,num_cols,figsize=figsize)
print(_)
print(axes) # axes 为构建的两行九列的画布
axes = axes.flatten()
print(axes) # axes 变成一维数据
for i,(ax,img) in enumerate(zip(axes,imgs)):
if torch.is_tensor(img):
# 图片张量
ax.imshow(img.numpy())
ax.set_title(titles[i])
else:
# PIL图片
ax.imshow(img)
X, y = next(iter(data.DataLoader(mnist_train,batch_size=18))) # X,y 为仅抽取一次的18个样本的图片、以及对应的标签值
show_images(X.reshape(18,28,28),2,9,titles=get_fashion_mnist_labels(y))
batch_size = 256
def get_dataloader_workers():
"""使用4个进程来读取的数据"""
return 4
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
f'{timer.stop():.2f}sec' # 扫一边数据集的事件
def load_data_fashion_mnist(batch_size, resize=None): # 额外声明resize参数,以便后续 如果模型需要不同size的图片时可以 基于此调整
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0,transforms.Resize(resize)) # 如果有Resize参数传进来,就进行resize操作
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=True,transform=trans,download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=False,transform=trans,download=True)
return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()),
data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()))
Softmax-从零实现
1.训练集、测试集抽取
基于上节实现的函数读取数据集的数据
2.初始化参数w、b
因为对于softmax回归来说,input需要是一个向量,因此 784=28*28,将图片展平(但这样会丢失图片的空间信息,后续用卷积神经网络来实现)
size是关键:个人理解
X是的shape是(1,784),为了XW能计算,且输出为10类,则 W.shape为(784,10)
3.定义Softmax
回顾一下:矩阵可以多不同axis上求和
正文:
此处softmax的对象是一个矩阵,对矩阵做softmax就相当于对其每一行做softmax
分别计算公式中的分子和分母
验证一下上面写的是否正确:符合softmax的预期:每个元素都为非负且 在0,1之间,每一行所有元素之和也为1
W.shape为(784,10) (第一步就分析了)
4.交叉熵损失
先补充个细节:如何 从所有预测值中根据 标号(即类别label)拿到其对应的预测值(即 最终预测的概率)
(弹幕说)此处内容涉及python的高级索引
最终拿到的是yhat[0,0], [1,2]
正文:
上面说明的内容就是为了 实现 获取下图中的 yhaty:即 预测结果中 某东西的真实类别(即老师口中的标号)的预测值
5.计算预测正确的数量
因为是分类问题,将预测类别与真实y元素进行比较。
【过程总结】
1.找出yhat中每行(每行代表 对一个样本类别的 属于各个类别的概率预测值)中 预测值最大的元素对应的索引,即 预测该样本的类别;
2.将yhat与y中每个元素逐个比较并 将 每个元素的比较结果保存到cmp中;其中cmp中每个元素都是布尔类型
3.将 cmp转换为y中元素的数据类型(即 int)并求和,得到了 所有预测类别正确的样本的数量
4. 预测正确样本数/总样本数 =准确率
【补充】变量1.type(变量2):将 变量1的类型转换为变量2的类型
【补充2】QA中的问题:为什么不在accuracy中 直接除以len(y)
因为我们读取batch时 最后一个batch中样本数量可能不足 batch_size,
那么此时 accuracy/len(y) 是不对的
6.任意模型的准确率(或称精度)
evaluate_accuracy(net,data_iter)作用:基于给定模型net和数据迭代器data_iter,可以计算该模型在该数据迭代器上的精度
Accumulator类的作用:该类可生成一个累加迭代器
弹幕说:如报Runtime的错误,把自己的Batchsize和进程数改小就可以了,毕竟cpu太拉
%matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
def 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)) # 如果有Resize参数传进来,就进行resize操作
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=True,transform=trans,download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=False,transform=trans,download=True)
return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()),
data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()))
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size) # 返回训练集、测试集的迭代器
num_inputs = 784
num_outputs = 10
w = torch.normal(0,0.01,size=(num_inputs,num_outputs),requires_grad=True)
b = torch.zeros(num_outputs,requires_grad=True)
def softmax(X):
X_exp = torch.exp(X) # 每个都进行指数运算
partition = X_exp.sum(1,keepdim=True)
return X_exp / partition # 这里应用了广播机制
# 实现softmax回归模型
def net(X):
return softmax(torch.matmul(X.reshape((-1,w.shape[0])),w)+b) # -1为默认的批量大小,表示有多少个图片,每个图片用一维的784列个元素表示
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y_hat)),y]) # y_hat[range(len(y_hat)),y]为把y的标号列表对应的值拿出来。传入的y要是最大概率的标号
def accuracy(y_hat,y):
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: # y_hat.shape[1]>1表示不止一个类别,每个类别有各自的概率
y_hat = y_hat.argmax(axis=1) # y_hat.argmax(axis=1)为求行最大值的索引
cmp = y_hat.type(y.dtype) == y # 先判断逻辑运算符==,再赋值给cmp,cmp为布尔类型的数据
return float(cmp.type(y.dtype).sum()) # 获得y.dtype的类型作为传入参数,将cmp的类型转为y的类型(int型),然后再求和
# 【基于给定模型net和数据迭代器data_iter,可以计算该模型在该数据迭代器上的精度】可以评估在任意模型net的准确率
def evaluate_accuracy(net,data_iter): # 当传入一个模型net和数据迭代器data_iter
"""计算在指定数据集上模型的精度"""
#如果net模型是torch.nn.Module实现的神经网络的话,将它变成评估模式(评估模式下不计算梯度,即只做forward不做backward)
if isinstance(net,torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数,metric为累加器的实例化对象,里面存了两个数
for X, y in data_iter: # 对于迭代器中每次拿到的一个batch中的X和y
metric.add(accuracy(net(X),y),y.numel()) # net(X)将X输入模型,获得预测值。y.numel()为y中元素总个数
return metric[0] / metric[1] # 分类正确的样本数 / 总样本数
# 【该类可生成一个累加迭代器】Accumulator实例中创建了2个变量,用于分别存储正确预测的数量和预测的总数量
class 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)] # zip函数把两个列表第一个位置元素打包、第二个位置元素打包....
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self,idx):
return self.data[idx]
print(evaluate_accuracy(net, test_iter)) # 看一下随机出来的模型和测试迭代器 的效果(因为共10个类别,因此正确率应在10%左右)
【补充】pytorch中的tensor.numel()方法
7.训练函数
train_epoch_ch3:对整个数据集数据迭代一次(即一个epoch)的实现方式
8.动画绘制
自定义一个小动画,可视化训练过程
9.多epoch总训练函数
ch3指在第三章的训练函数,后面会不断完善这个训练函数
metrics:指标
我们一般关注测试的精度test acc
可视化效果:
10.预测数据
从测试集中拿出6个样本预测一下
【完整代码】
个别局部部分的解释可以看具体对应章节的注释
##### %matplotlib inline
import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
from IPython import display
def get_dataloader_workers():
"""使用4个进程来读取的数据"""
return 0
def load_data_fashion_mnist(batch_size, resize=None):
"""下载Fashion-MNIST数据集,然后将其加载到内存中"""
trans = [transforms.ToTensor()]
if resize:
trans.insert(0,transforms.Resize(resize)) # 如果有Resize参数传进来,就进行resize操作
trans = transforms.Compose(trans)
mnist_train = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=True,transform=trans,download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="01_data/01_DataSet_FashionMNIST",train=False,transform=trans,download=True)
return (data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()),
data.DataLoader(mnist_train, batch_size, shuffle=True, num_workers=get_dataloader_workers()))
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size) # 返回训练集、测试集的迭代器
# 2.
num_inputs = 784
num_outputs = 10
w = torch.normal(0,0.01,size=(num_inputs,num_outputs),requires_grad=True) # 重点是size
b = torch.zeros(num_outputs,requires_grad=True)
def softmax(X): # 此处softmax的对象是一个矩阵。对矩阵做softmax就相当于对其每一行做softmax
X_exp = torch.exp(X) # 每个元素都进行指数运算(即softmax公式中的分子)
partition = X_exp.sum(1,keepdim=True) # 按axis=1进行求和,并仍保持原维度(即二维矩阵)(即softmax公式中的分母)
return X_exp / partition # 这里应用了广播机制(即softmax公式中的分子/分母)
# 实现softmax回归模型
def net(X):
return softmax(torch.matmul(X.reshape((-1,w.shape[0])),w)+b) # -1表示自动计算一下(为默认的批量大小),表示有多少个图片,每个图片用一维的784列个元素表示
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y_hat)),y]) # y_hat[range(len(y_hat)),y]为把y的标号列表对应的值拿出来。传入的y要是最大概率的标号
def accuracy(y_hat,y):
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: # y_hat.shape[1]>1表示不止一个类别,每个类别有各自的概率
y_hat = y_hat.argmax(axis=1) # y_hat.argmax(axis=1)为求行最大值的索引
cmp = y_hat.type(y.dtype) == y # 先判断逻辑运算符==,再赋值给cmp,cmp为布尔类型的数据
return float(cmp.type(y.dtype).sum()) # 获得y.dtype的类型作为传入参数,将cmp的类型转为y的类型(int型),然后再求和
# 可以评估在任意模型net的准确率
def evaluate_accuracy(net,data_iter):
"""计算在指定数据集上模型的精度"""
if isinstance(net,torch.nn.Module): # 如果net模型是torch.nn.Module实现的神经网络的话,将它变成评估模式
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数,metric为累加器的实例化对象,里面存了两个数
for X, y in data_iter:
metric.add(accuracy(net(X),y),y.numel()) # net(X)将X输入模型,获得预测值。y.numel()为样本总数
return metric[0] / metric[1] # 分类正确的样本数 / 总样本数
# Accumulator实例中创建了2个变量,用于分别存储正确预测的数量和预测的总数量
class 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)] # zip函数把两个列表第一个位置元素打包、第二个位置元素打包....
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):
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): # 如果updater是pytorch的优化器的话
updater.zero_grad()
l.mean().backward() # 这里对loss取了平均值出来
updater.step()
metric.add(float(l)*len(y),accuracy(y_hat,y),y.size().numel()) # 总的训练损失、样本正确数、样本总数
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] # 所有loss累加除以样本总数,总的正确个数除以样本总数
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()
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__"):
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):
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): # 变量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_metrics+(test_acc,) 仅将两个值的正确率相加,
train_loss, train_acc = train_metrics
# 【实现SGD】小批量随即梯度下降来优化模型的损失函数
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)
预测数据:
def predict_ch3(net,test_iter,n=6):
for X, y in test_iter:
break # 仅拿出一批六个数据
trues = d2l.get_fashion_mnist_labels(y) # 真实label
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1)) # 预测label
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)
Softmax-基于框架实现(nn.Module)
1.拿到数据和迭代器
2.构造softmax回归模型:
nn.Flatten():将任何维度的tensor转换为2Dtensor(即保留其第0维度,其他维度均展平为一个向量)
3.损失函数与优化算法
后面的课程会不断在该数据集上应用更深、更复杂的模型
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)
# Softmax回归的输出是一个全连接层
# PyTorch不会隐式地调整输入的形状
# 因此,我们定义了展平层(flatten)在线性层前调整网络输入的形状
net = nn.Sequential(nn.Flatten(),nn.Linear(784,10))
def init_weights(m): # 该函数会对网络的每一层都 call一次,其中m就是当前的layer
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01) # 如果当前层是线性层,就将其权重初始化为 均值为0、方差为0.01的随机值
net.apply(init_weights) # net网络权重初始化:应用init_weights函数到net网络的每一层
print(net.apply(init_weights)) # net网络的参数用的是init_weights初始化参数
# 在交叉熵损失函数中传递未归一化的预测,并同时计算softmax及其对数
loss = nn.CrossEntropyLoss()
# 使用学习率为0.1的小批量随即梯度下降作为优化算法
trainer = torch.optim.SGD(net.parameters(),lr=0.1)
num_epochs = 10
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,trainer)
QA
1.softlabel是图像分类问题的实际场景中的一个常用技巧,大概意思就是 softmax理论上的效果在实际中可能不能完全实现?
刚刚讲的是 softmax+one-hot编码:将 n类 变成长度为n的向量,只有类别正确的那一类为1,其余均为0 ,然后用softmax去逼近这个 纯0、1的分布。
但其存在的问题是 (回想softmax公式)很难用 指数去逼近1, 即 要求输出几乎接近无穷大(分子)、分母的其他项都很小,这样 最后结果才是1,很难实现
因此提出了改进方案softlabel,将正确类记为0.9,错误类记为0.1(而不是像one-hot这样纯0、1),除以n? 这样就使得 用softmax去拟合 那些很小的数 是有可能实现的
2、3.逻辑回归就是 当类别n=2是的softmax回归
4.互信息不好算, 交叉熵就够用、好用了
5.也不是说我们不关注不正确的类,只是one-hot编码 将不正确的类的概率变为0了,所以计算时可以忽略掉不正确的类 。
如果是用softlabel,即类比one-hot用 0.9和0.1,那么计算时就不能忽略掉不正确的类了
6.会。但其实只要每个类都有足够多的样本时, 还是比较平衡的
7.每次走的步长取决于两点:梯度大小和lr,假设lr固定,那么步长只受梯度(如下图中橙色线)影响,因此对于下图,不管梯度是+1or-1,梯度的绝对值是一样的,因此每步移动的距离都是平均的
而下图则是,当 离原点较远时,梯度较大,因此每步步长就较大
8、9.似然函数属于统计学中的概念,因为深度学习后面的模型和统计没有太大关系,因此没有细讲这里
【最小化损失就等价于最大化似然函数】
似然函数简单理解:当给定一个模型(即 有了确定的权重)和数据,这个模型权重出现的概率有多大。因此我们要找到最大似然, 使得 这个权重更合理
10.前面讲过了(回想 权重更新公式即可 w1 = w0 - lr * 梯度)
11.可以用
13、14.是的。开多个python进程
课程中单独定义一个设置num_workers的原因是 老版本 中 windows不支持设置 多进程,因此需要在函数中判断一下是不是win系统,如果是就不用多进程
现在支持了,直接写死num也行,不用函数定义
15.有讲究,此处只是为了当前教学,后面会展开讲方差
17.不是。无论batch_size设为几,计算量不变。
只是说能不能想办法增加计算的并行度,以提高效率。
如果增大减小batch_size的没区别,可能说明 你在CPU上或模型很小
18.python
19.因为我们读取batch时 最后一个batch中样本数量可能不足 batch_size,
那么此时 accuracy/len(y) 是不对的,而是应该像evaluate_accuracy中这样将所有正、负例 累加起来再除
20.后面基于GPU会再讲
- 设一下是好习惯,即 设置后不用计算并更新梯度了,性能会好
- 就是传参穿进去的, 如手动实现时 手动放进去和 基于框架实现时用 net.parameters放进去
23.可能是。 后面会通过 微调学习率或加入正则项来解决过拟合
24.后面
25.后面
26.自己比一下。
27.可以看下pytorch文档,如将每个类别单独放一个文件夹
29.建议看下统计学习
弹幕说:softmax往前回溯的话应该是模式识别中的最大后验分类