文章目录
-
- 首先要考虑解决什么问题
- 1 获取和读取数据
-
- 获取数据
- 读取数据
- 2 构建模型
-
- 模型参数的初始化
- 定义模型和损失函数
- 3 定义优化算法
- 4 训练模型
-
- 定义训练函数中注意的要素
- 定义步骤
- 5 评估模型
- 完整版代码(不需要安装d2l)
一些简单的深度学习模型从零开始看教程似乎很简单,敲代码过去了,但仍然做出自己的初步总结。思维过程适用于未来的任何问题,包括实现的基本步骤、如何反映代码的模型概念、定义培训过程中需要的参数等。
本文以Softmax以零实现为例,总结一个深度学习模型的实现过程,每一步都附上自己的总结思路。
首先要考虑解决什么问题
比如我们想用Softmax回归模型,实现图片分类问题。 后面是目的,前面是手段。 然后脑海中需要出现几个解决问题的基本步骤:
- 获取和读取数据
- 定义模型:①根据模型初始化模型参数,②定义模型和损失函数
- 定义优化算法(如SGD)用于学习参数
- 训练模型
- 评估模型
1 获取和读取数据
获取数据
- 首先,我们需要创建一个数据集对象Dataset()
对于获取数据,一般来说,初学者些获取数据的人通常会使用数据pytorch一些相关库的方法。 例如,我们想读 FashionMNIST 用于通过的数据集Softmax模型解决数据集的分类问题(或评估)Softmax模型的分类性能,Whatever,不同的表达)。
我们通常使用它torchvision.datasets
下一类创建数据集,有很多公共数据集可以直接下载获取,如果我们需要的话FashionMNIST
数据集:
# 对于机器学习问题,通常会生成训练集和测试集 mnist_train = torchvision.datasets.FashionMNIST( root='~/Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor()) mnist_test = torchvision.datasets.FashionMNIST( root='~/Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
注意其中的transform数据转换器(位于参数中torchvision.transforms下),常用的有ToTensor()
该方法将图像数据转换为尺寸(C x H x W)大小位于[0.0, 1.0]的float32数据类型的Tensor。 创建的数据集对象属于torch.utils.data.Dataset
的子类。
读取数据
- 然后创建数据集对象Dataset()进入数据加载器Dataloader()
我们创造一个Dataset一般后直接传入torch.utils.data.Dataloader() 生成一个Dataloader对象。
batch_size = 256 train_iter = torch.utils.data.DataLoader( mnist_train, batch_size=batch_size, shuffle=True, num_workers=0) test_iter = torch.utils.data.DataLoader( mnist_test, batch_size
=batch_size
, shuffle
=
False
, num_workers
=
0
)
# 这里的num_workers表示数据读取的线程数,Windows系统一般默认设0,这里更多信息可以自行搜索学习。
生成Dataloader后,就可以使用for循环读取批量数据了,一次循环是一个batch_size数量的数据。 batch_size是一个重要的超参数,横贯机器学习过程的始终,不仅方便计算机按批次读取数据减少内存开销,并且在计算梯度时,使用一个batch_size的数据进行迭代更新,大大减少计算量。
我们也可以使用next(iter(dataloader))
手工读取一个批次的数据。
Dataloader是一个可迭代对象,它通过生成迭代器,来读取批量数据。 对于一些数据,我们还可以构建生成器来读取批量数据,这部分扩展阅读可以参考我这篇文章。
2 构建模型
- 将模型的数学表达转换成代码表达
我们选择Softmax回归模型时,心中需要构思好这个模型的数学表达,见下。 需要注意的是,softmax回归本身是一个单层神经网络,并且和线性回归一样是全连接的: 这里提示我们要有一个思维:把一个数学模型用深度神经网络去构建解释。
我们的Softmax回归数学模型是(以4像素图片,3分类标签为例): o ( i ) = x ( i ) W + b y ^ ( i ) = softmax ( o ( i ) ) p = arg max p y ^ p \begin{aligned} \boldsymbol{o}^{(i)} &=\boldsymbol{x}^{(i)} \boldsymbol{W}+\boldsymbol{b} \\ \hat{\boldsymbol{y}}^{(i)} &=\operatorname{softmax}\left(\boldsymbol{o}^{(i)}\right) \end{aligned} \\ p = \underset{p}{\argmax } \hat{y}_{p} o(i)y^(i)=x(i)W+b=softmax(o(i))p=pargmaxy^p x ( i ) = [ x 1 ( i ) x 2 ( i ) x 3 ( i ) x 4 ( i ) ] , o ( i ) = [ o 1 ( i ) o 2 ( i ) o 3 ( i ) ] , y ^ ( i ) = [ y ^ 1 ( i ) y ^ 2 ( i ) y ^ 3 ( i ) ] \boldsymbol{x}^{(i)}=\left[x_{1}^{(i)} \quad x_{2}^{(i)} \quad x_{3}^{(i)} \quad x_{4}^{(i)}\right], \boldsymbol{o}^{(i)}=\left[\begin{array}{lll} o_{1}^{(i)} & o_{2}^{(i)} & o_{3}^{(i)} \end{array}\right], \\ \hat{\boldsymbol{y}}^{(i)}=\left[\begin{array}{lll} \hat{y}_{1}^{(i)} & \hat{y}_{2}^{(i)} & \hat{y}_{3}^{(i)} \end{array}\right] x(i)=[x1(i)x2(i)x3(i)x4(i)],o(i)=[o1(i)o2(i)o3(i)],y^(i)=[y^1(i)y^2(i)y^3(i)] 参数: W = [ w 11 w 12 w 13 w 21 w 22 w 23 w 31 w 32 w 33 w 41 w 42 w 43 ] , b = [ b 1 b 2 b 3 ] \boldsymbol{W}=\left[\begin{array}{lll}w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} \\ w_{31} & w_{32} & w_{33} \\ w_{41} & w_{42} & w_{43}\end{array}\right], \quad \boldsymbol{b}=\left[\begin{array}{lll}b_{1} & b_{2} & b_{3}\end{array}\right] W=⎣⎢⎢⎡w11w21w31w41w12w22w32w42w13w23w33w43⎦⎥⎥⎤,b=[b1b2b3]
初始化模型参数
本例中,我们需要学习的模型系数是 矩阵和偏倚项。 我们将28281大小的图片拉伸为28×28=784长度的向量(输入的是一个batch的,即X形状为256×784),输出的是10分类,因此的形状为:784×10 偏倚项的形状为10×1
然后,我们一般使用(0,0.01)的正态分布去初始化参数的数值:
num_inputs = 784
num_outputs = 10
W = torch.tensor(
np.random.normal(loc=0, scale=0.01, size=(num_inputs, num_outputs)),
dtype=torch.float)
b = torch.zeros(num_outputs , dtype=torch.float)
最后,最重要的一步,W和b,因为我们需要学习这个参数!
# 设上梯度
W.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
定义模型、损失函数
先定义softmax:
def softmax(X):
X_exp = X.exp()
partition = X_exp.sum(dim=1, keepdim=True)
return X_exp / partition # 这里使用了广播机制
:
def net(X):
return softmax(torch.mm(X.view((-1, num_inputs)), W) + b)
:
def cross_entropy(y_hat, y):
return -torch.log(y_hat.gather(dim=1, index=y.view(-1,1)))
# y就是待传入的那个批量的label数据
(定义交叉熵损失函数的细节可以参考我这篇文章)
我们在定义模型和损失函数时,在心中对模型的最好有个大致的把握。 我们可以先看看本模型损失函数的梯度节点可视化: (可视化可参考我的这篇文章) 可以看到在这个损失函数的计算图里,我们要求的是顶端的两个参数W,b,整个损失函数是关于参数的函数。DivBackward0节点及以上部分是模型计算结果y_hat,节点以下部分流向损失函数的计算。
3 定义优化算法
- 定义优化器以优化参数
本例中我们依然可使用随机梯度下降法(SGD)作为Optimizer,届时在训练时每个batch数据计算完后梯度后,利用当前梯度迭代更新一次参数。 优化器除了需要传入的待学习参数(本例为W,b)外,还需要传入一些超参数。
# lr是学习率。作为超参数。
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
4 训练模型
接下去就是重头戏的训练环节了,我们一般定义一个训练函数train()作为一套训练的过程的打包。
定义训练函数注意的要素
- :lr(学习率),num_epochs(训练轮数),batch_size 超参数一般根据经验设置,比如本例设lr=0.1,num_epochs=5 在深度学习模型中,一般需要训练多轮epoch才有比较好的效果
- : 训练集的Dataloader:train_iter 测试集的Dataloader:test_iter(视情况非必须) 模型名net:net 损失函数loss:cross_entropy 训练参数params:[W, b] 优化器Optimizer:None(我们直接在函数中封入SGD作为默认优化器,就可以不用再手动传)
定义步骤
- 从训练集Dataloader获得一个X,y批次; 通过模型net算出预测值y_hat; 通过损失函数loss算出损失值l。
记得回顾上面那张计算图,只有待学习的参数W,b带梯度,是届时需要传入优化器更新的。 同时注意损失函数定义时一般返回的是一个batch_size长度的向量,对齐求sum()转换成标量以方便求导(见下方代码)。
- 对参数W,b梯度清零
l.backward()
针对这一批量的数据结果,反向传播,求出当前梯度- 之后W,b中就带有了梯度信息,这时传入优化器sgd对参数进行一次迭代更新:
W . d a t a = W . d a t a − l r ∗ W . g r a d / b a t c h s i z e W.data = W.data -lr * W.grad / batchsize W.data= 标签: 5w33kr电阻5w33r精密电阻