1.数据的读入
由于我本人也是第一次接触Pytorch,所以就用之前帮妹妹写作业熟悉过的Jupyter Notebook进行这次的学习(Jovian官方教程也使用的Jupyter Notebook,这样就更加方便了)
# Imports
import torch
import torchvision
from torchvision.datasets import MNIST
importPytorch,没什么好说的。
Pytorch官方文档https://pytorch.org/docs/stable/index.html
MNIST是著名的数据库,存有手写的数字。将其下载下来,并命名为dataset。http://yann.lecun.com/exdb/mnist/
dataset = MNIST(root='data/', download=True)
这样就把所有的来自MNIST数据库的数据存入了根目录下的data文件夹下。
瞅一眼数据量
60000个,够用了。
而这60000个数据,应该有10000个用来验证结果。MNIST官方已经帮我们打好标签了,使用test_dataset = MNIST(root=’data/’, train=False)就可以将train值为False的数据存入test_dataset中。
如果我们取出一个,看看这个数据是什么样子。
看不懂具体是什么意思没关系,我也不懂:) 但有一些参数是能够看出来的,这是一个PIL.Image.Image类的对象,其大小为28×28个像素。
下一步使用pyplot将其画出来,看看是什么样
带有%前缀的指令叫做魔法指令,会对Jupyter Notebook本身做出一些修改,这里是为了让画的图直接在行内显示。
得到第一个数据如图,这里的image,label=dataset[0]让我困惑了一小下,不知道这是个什么种声明的方式,经过回看上文可以发现,dataset本身就是一个有两个字段的数据,前一部分是图片数据,后一部分为label,即这个数字是几。这里也放一下pyplot的官方文档的链接。
https://matplotlib.org/stable/api/pyplot_summary.html
官方文档中是这样写的。
这里我们要使用的图像是灰阶图像,因此参数选择’gray’
至此,我们已经完成图像的导入
图像的数据化
这里的图像格式为一个我们不熟悉的类,Pytorch应当使用一个tensor(译为张量对其进行处理),因此需要转换,这里Pytorch依然为我们准备好了转换的工具——torchvision.transforms
将 torchvision.transforms 导入,命名为transforms
现在再来看dataset的情况。
dataset已经被成功转换为tensor格式。
打印img_tensor的通道0(因为是灰阶图像,只有0),从图片的第十行第十列输出到第15行第15列。 输出值越大,这里越接近白色。 下面的max和min也告诉了我们最大值与最小值是多少
调用熟悉的plt.imshow指令,就可以看到这一区域的图像是什么样的了。
数据预处理与读入
通常来讲,机器学习的数据集可以分为三部分(由于没学过中文课程,可能翻译有误):
1.训练集
2.验证集
3.测试集
对于MNIST数据库而言,有60000个训练图片与10000个测试图片,但没有预设的验证集,我们需要手动将60000个训练图片打散,从而得到训练集与验证集。(至于为什么训练集和测试集要分开还不得而知,看看能否通过后续的学习理解)
这里打散的方式Pytorch也给出了(所以说是黑猩猩都可以懂的学习过程,Pytorch实在太过完善,所有所需的功能基本都已给出,照着官方文档去翻看调用即可)
导入torch.utils.data将其命名为random_split 将分散的数据存入train_ds(命名意义为:用于训练的dataset)和val_ds中 查看其长度,分别为50000与10000
DataLoader是用来载入数据的,即一次读取多少数据来训练模型
这里将batch_size设置为128 在train_loader中 将shuffle设置为True,保证每一个epoch(也许是迭代?)取出的数据都是不同的这里有一个疑问,为什么验证数据不用shullfe,可能也得通过之后的学习解答了
建立模型
这里就可以到我们熟悉的(x)神经网络环节了。
这里使用线性回归模型,其具体含义之后有机会在讲,能看到这里的同学默认都会了吧。放上Jovian官方的说明。
其模块封装于torch.nn内,导入即可
这里的Linear后的两个参数代表了这个模型所要处理的输入输出的个数 在这里,需要对有728个维度的数据进行处理(28×28=728个像素点,即728维的数据) 而输出为10维,因为要将这些数据与各个数字的可能性输出
首先来看一下model的形状如何
可以看到这是一个10*784的矩阵 而requires_grad=True是tensor的一个属性,意为我们需要对它求导。这样的标记是有意义的,但我也仅仅是知道有意义而已,具体是应用于什么样的情况还不得而知。
bias矩阵是一个1*10的矩阵
这里直接对train_loader中的数据进行处理,看看情况如何
得到了这样的结果
发生RuntimeError,因为矩阵不可相乘
这是因为我们当前的数据还是28*28维的矩阵,再与128相乘后得到了一个奇形怪状的东西:(,函数根本不会处理它
其实解决的方法很简单,将28*28的矩阵变成1*784的向量即可。
使用reshape将images变为128*784的矩阵
这里教程中新写了一个类MnistModel(继承自nn.Module)来实现我们所需模型的功能,代码如下。
这里在构造函数(也可能不叫这个名字?)中,增加了self的属性linear,linear初始化为nn.Linear提供的矩阵,input_size和num_classes均已给出 forward函数简便的提供了输入数据的tensor化功能,这里(-1,784)中的第一个参数说明函数可以自己根据输入数量决定tensor的第一维维数。 现在model进行了再次的初始化,初始化为MnistModel类的一个对象,注意此时model不再具有.weight和.bias这两个属性(attributes)
现在查看一下model对象的linear属性
之前,属于model类的.weight和.bias转移到(或者说重新初始化为)model.linear门下。
这里的parameters()函数应该来自于Pytorch本身,因为我们的MnistModel类继承自nn.Module,自然也就有他的各种功能
这回再来将数据读入,进行处理。得到如下结果。
使用softmax函数对输出进行处理
可以看到,在上文,我们使用model对images进行处理后,每张图片输出了一个十维向量,但现在的输出是有正有负很不直观,因此要用softmax函数对其处理,使这十个输出值变为正,并且和为1。思路很简单,将得到的数值变为指数函数的指数,再除以总的数值即可,看图片可能更直观一些。
先通过指数函数的映射使各个值变为正
再使用softmax函数将输出值归一化
作为傻瓜式教程,softmax函数当然也被Pytorch提供了,直接导入torch.nn.functional模块即可
将torch.nn.functional模块导入,命名为F
查看经过处理过的output数据,发现其均为正,和为1,达到目的
得到预测结果
现在就可以得到我们的神经网络的预测结果了,当然这个预测肯定是不准确的,这里通过选择可能性最高的(即softmax函数输出的最高值认为它就代表数字几)
这可以使用torch.max函数实现
预测得到的结果
那么与数据库提供的标签对比如何呢?查看一下数据库提供的标签数据。
可以看到大部分数据都预测错误了,但没关系,毕竟这只是初始化的网络,还没有开始学习
评价指标与损失函数
Accuracy函数
神经网络的基本概念我们应该都知道,如果预测的结果错误,我们应当给他一个改动,然后继续训练,直到预测的准确度达到我们的要求为止,那么在这个过程中,就需要一个参数来评估,这就是接下来要用的accuracy指标。
accuracy指标的定义也十分简单,用预测对的结果数量比上总的结果数量即可,新定义accuracy函数进行计算。
这里的下划线是表示函数并不关心前者的取值,回看之前使用max函数的例子,我们会发现假如下划线是一个变量的话,它会被赋值为最大可能性的值,这里我们只需要预测值和标签值相比,因此使用了这样一种语句
调用后得到如下结果。
准确率0.0625,很低
Loss function 损失函数
accuracy函数是一种很好的评价的指标,但并不能作为损失函数。原因有二:
1.accuracy函数不可微,在accuracy函数中有‘torch.max’和’==’两个 运算,这两个都不是线性运算,因此无法使用accruracy函数计算weights和biases矩阵的改变量。
2.accuracy并没有“真正地”将预测的可能性(即我们得到的probability)带入计算中,只是单纯地比较了预测值与标签值的大小,因此也就无法对我们的模型改进做出指导。
因此,一般使用corss-entropy(交叉熵),公式如下
观察这个函数的形式,它会使预测概率更接近1的越来越接近1,预测概率接近0的越来越接近0
多分类情况
这里使用log函数的图片会更直观
假如我们得到的预测的概率为[0.1,x,0.2,……]而标签为1,也就是预测正确的可能性为x,那么忽略其他元素,只看x的话,假如这个值x很大,接近于1,那么logx接近于0,也就是损失很小,如果x很小,即预测正确的可能性很小,那么logx是一个很大的负数,我们再给它加一个负号,再取各个图片(输入)的平均值,就可以得到一次输入的平均损失值,越大越不好
再次扣题,PyTorch已经为我们准备好了交叉熵损失函数的计算,并且自带softmax函数,作为只有大猩猩智商的我,直接进行一个调的用。
得到损失函数的值为2.3418
训练模型
现在,我们得到了模型(weight与bias矩阵),得到了评估模型的方法(accuracy函数与以corss-entropy为基础的损失函数),以及各种读取、输入数据的小工具,我们还拥有MNIST数据库中得到的验证集,那么就可以正式开始训练模型了。
训练模型的伪代码应如下所示:
#给入迭代次数num_epochs
for epoch in range(num_epochs):
# Training phase 训练模型
# 对于train_loader读入的一批(batch)数据 执行如下操作
for batch in train_loader:
# Generate predictions 生成预测值
# Calculate loss 评估损失
# Compute gradients 计算梯度
# Update weights 更新权重
# Reset gradients 重置梯度
# Validation phase 验证环节
# 对于val_loader读入的一批数据 执行如下操作
for batch in val_loader:
# Generate predictions 生成预测值
# Calculate loss 计算损失
# Calculate metrics ( accuracy etc.) 计算accuracy函数 评估准确度
# Calculate average validation loss & metrics 计算平均损失与准确度
# Log epoch, loss & metrics for inspection 记录这次迭代,损失函数,准且率指标并进行检查
其真正代码如下:
def fit(epochs, lr, model, train_loader, val_loader, opt_func=torch.optim.SGD):
optimizer = opt_func(model.parameters(), lr)
history = [] # for recording epoch-wise results
for epoch in range(epochs):
# Training Phase
for batch in train_loader:
loss = model.training_step(batch)
loss.backward()
optimizer.step()
optimizer.zero_grad()
# Validation phase
result = evaluate(model, val_loader)
model.epoch_end(epoch, result)
history.append(result)
return history
这个名为fit的函数,将各参数和数据读入后,返回一个history的值,用来记录损失函数和指标的变化。
这里还有一些我们没见过的东西,比如lr代表学习率(learning rate),opt_func=torch.optim.SGD是一种随机下降方法,可以避免学习过程中的一些问题。现阶段还不需要学那么深入(毕竟我也还不会),只要明白这段代码实现了和上面伪代码一样的功能即可。
但上面的其他东西仍然需要我们自己去定义,比如需要再次重写一遍MinistModel类,从而加入raining_step、validation_step、validation_epoch_end、 epoch_end 等类函数。代码如下
class MnistModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(input_size, num_classes)
def forward(self, xb):
xb = xb.reshape(-1, 784)
out = self.linear(xb)
return out
def training_step(self, batch):
images, labels = batch
out = self(images) # Generate predictions
loss = F.cross_entropy(out, labels) # Calculate loss
return loss
def validation_step(self, batch):
images, labels = batch
out = self(images) # Generate predictions
loss = F.cross_entropy(out, labels) # Calculate loss
acc = accuracy(out, labels) # Calculate accuracy
return {'val_loss': loss, 'val_acc': acc}
def validation_epoch_end(self, outputs):
batch_losses = [x['val_loss'] for x in outputs]
epoch_loss = torch.stack(batch_losses).mean() # Combine losses
batch_accs = [x['val_acc'] for x in outputs]
epoch_acc = torch.stack(batch_accs).mean() # Combine accuracies
return {'val_loss': epoch_loss.item(), 'val_acc': epoch_acc.item()}
def epoch_end(self, epoch, result):
print("Epoch [{}], val_loss: {:.4f}, val_acc: {:.4f}".format(epoch, result['val_loss'], result['val_acc']))
model = MnistModel()
定义evaluate函数用来评估
在开始训练之前,用评估函数来看一下目前默认模型的准确率与损失函数的情况
正式开始训练
至此,我们已经准备妥当,直接开始训练模型即可,对model进行训练,共训练4次,每一次训练进行5次迭代,学习率设为0.001,结果如下
将默认模型和这四次训练的准确度进行合并,并在图中画出来,得到如下图像,显示了在每一次迭代后,准确率的变化情况
可以看到,仅仅经过20次迭代,模型的准确度就已经达到了85%以上
但可以看到的是,从图上的趋势而言,这个模型即使经过非常久的训练,也很难超过90%准确度的阈值。
一种可能的解释是:学习率过高 , 该模型的参数可能在以最低损失为目标的最优参数集上“跳跃” 。降低学习率也许会有一些帮助。
但更有可能的解释是:模型本身就不够强大 。毕竟从本质上而言,我们的人工神经网络也只是一个有几百个维度的矩阵而已!对于这次的模型,我们一开始给出这样的假设:输出与输入之间是线性关系。但也许,输入和输出之间根本就不存在这样理想的线性关系(对于本模型来说,就是像素点的密度和灰度,也许和这个数字是几之间毫无关系,但我们知道并不是这样,所以我们的模型还是可以达到较高的准确度,但换一个场景,比如我们训练一个模型让它去寻找今天天气和我午饭要吃几碗米饭之间的关系,它就无法做出准确的判断了,因为二者之间并不存在明显的线性相关关系)。
但至少我们得到了一个结论:我们需要更加复杂的模型来对输入输出进行处理来捕捉二者之间的非线性相关关系,这样才能够对更加复杂的物体识别、图像识别的任务进行实现,但那就是下一次文章的内容了,希望我不要鸽太久!