(部分项目代码源自《python深度学习》,吴茂贵等著,机械工业出版社。代码头有标注;部分测试代码来自pytorch官方文档,代码头有标注;部分概念图来源于github,图片下方有标注)
其他参考资料:
《An overview of gradient descent optimization algorithms》,https://ruder.io/
《7 Types of Neural Network Activation Functions: How to Choose?》,https://missinglink.ai
《Neural networks and back-propagation explained in a simple way》,https://medium.com/)

《Pytorch master documentation》,https://pytorch.org
《A Gentle Introduction to the Rectified Linear Unit (ReLU)》,https://machinelearningmastery.com
https://stackoverflow.com

机器学习的核心组件包括:层(相当于转移函数)、模型(层网络)、损失函数(衡量某模型参数选择(或其本身)好坏的函数)、优化器(使损失函数最小)。
简单的手写数字识别使用mnist数据集,其中70000张手写数字图片,60000张用于训练,10000张用于检验。(test (validation) data和training data应该分开,并且不能根据testing data的结果自己折回去调参。)
使用pytorch构建最简单的神经网络,在此之前应确保环境安装了python、pytorch(一般独立显卡用GPU加速)、编译环境(jupyter notebook或pycharm),最好用Linux系统(Windows也可以)。使用以下库,若无通过pip或conda下载。
预处理

import numpy as np
import torch
import torchvision
from time import time
import matplotlib.pyplot as plt
from torchvision import datasets, transforms
from torch import nn, optim

下载数据集之前,使用torchvision.transform,以统一传入的图像维度等属性。

#download preprocess data
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5],[0.5])])

在这里,compose将转换函数组合在一起,ToTensor将图像数据转换成张量。

试一试totensor:

from PIL import Image
img_path="D:\微信图片_20200412115703.jpg"
image = Image.open(img_path)
image = ToTensor()(image).unsqueeze(0) # unsqueeze to add artificial first dimension
image

比如,原图

变成

transforms.Normalize([0.5],[0.5])对张量归一化,两参数分别表示归一化的全局平均值和方差。例如,对以上数组继续操作:

C, H, W = image.size()
output_tensor = output_tensor.view(C, H, W)
Normalize = transforms.Compose([transforms.Normalize(mean=[-0.5,-0.5,-0.5], std=[1,1,1]) ]) # Subtract BGRoutput_tensor = Normalize(output_tensor)
output_tensor

得到

下载数据集,随机排序,载入DataLoader,方便对数据进行采样和多进程迭代,同时节省内存。dataloader支持调整
1.数据集映射/迭代方式,
2.数据加载顺序,
3.单\多进程,
4.存储器锁定,等。
具体参见pytorch官方文档–dataloader,在这里,需要将一次训练(准确是修改权重)选择的样本数(默认是1)改为64。迭代器乱序读取。迭代形式的数据集,无法调整sampler。

trainset = datasets.MNIST('PATH_TO_STORE_TRAINSET', download=True, train=True, transform=transform)
valset = datasets.MNIST('PATH_TO_STORE_TESTSET', download=True, train=False, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
valloader = torch.utils.data.DataLoader(valset, batch_size=64, shuffle=True)

可视化数据集
查看数据集图片和对应的张量

dataiter = iter(trainloader)
images, labels = dataiter.next()print(images.shape)
print(labels.shape)

查看数据集结构,得出torch.size([64,1,28,28]),意思是每批有64个图像(上面加载数据的时候设的),图像维度是28*28像素。查看标签结构,得出torch.size([64]),对应一批次中64个图像各自的标签。

查看数据集中一个图像:(灰度图)

查看多个图像:

###########此代码源于书中##############
import matplotlib.pyplot as plt
%matplotlib inline#构造内联对象
examples = enumerate(valloader)
batch_idx,(example_data,example_targets)=next(examples)fig=plt.figure()
for i in range(6):plt.subplot(2,3,i+1)#画成2*3plt.tight_layout()#自动调节图片参数以适应表格尺寸plt.imshow(example_data[i])[0],cmap='gray',interpolation='none')plt.title("实际数据是:{}".format(example_targets[i]))plt.xticks([])plt.yticks([])


再输出一组:

figure = plt.figure()
num_of_images = 60
for index in range(1, num_of_images + 1):plt.subplot(6, 10, index)plt.axis('off')plt.imshow(images[index].numpy().squeeze(), cmap='gray_r')


构建神经网络
构建一个简单(多类单标签)分类器的神经网络,方法如下:

input_size = 784   #28*28
hidden_sizes = [128, 64]
output_size = 10model = nn.Sequential(nn.Linear(input_size, hidden_sizes[0]),nn.ReLU(),nn.Linear(hidden_sizes[0], hidden_sizes[1]),nn.ReLU(),nn.Linear(hidden_sizes[1], output_size),nn.LogSoftmax(dim=1))
print(model)


Sequential用于搭建网络层。上图看出,第一层输入是每个像素点(展平的),输出是128维张量,经过激活函数送入第二层,第二层输入128维输出64维,再经过第二层激活函数处理送入第三层,由64维映射到10维(0到9)。这三个依次排列的系统都具有偏移量bias;最后的激活函数添加了Softmax层。

涉及的基本概念:

激活函数
激活函数的职责在决定神经网络的输出。这个函数附属在每个神经元(节点),根据每个节点的输入与模型预测的相关性决定是否激活此节点。激活函数也能对神经元的输出归一化(0 ~ 1 \ -1 ~ 1)。由于运算次数超多、梯度函数反向传播(backpropagation)也增加了激活函数负担,它必须是高效易计算的。(模型中的Relu就是为此设计的激活函数。)激活函数是介于输入和输出之间的数据门,可以激活或关闭输出,或者建立输入到输出的映射。这样看,一个神经元的作用是:对输入加权、加偏移量(bias),将结果注入激活函数,将激活函数的输出输入下个神经元。
初级的激活函数有二进制阶梯函数(缺点:无法将不同输入分类)、线性激活函数(缺点:导数是常数、无法进行反向传播,且无论多少层都能等效为一层)。使用线性激活函数的只能叫线性回归(linear regression)。
现在普遍使用非线性激活函数。它建立了复杂的映射关系。
最常用的激活函数:
(基本的激活函数高中已涉及。)
Sigmoid (Logistic)S(t)=11+e−t.{\displaystyle S(t)={\frac {1}{1+e^{-t}}}.} S(t)=1+e−t1​.优点:明确的上下界、平滑的曲线
缺点:产生梯度消失(Vanishing gradient),计算代价高
TanH (Hyperbolic Tangent)sinhx=ex−e−x2sinh ~x = \frac{e^x-e^{-x}}{2}sinh x=2ex−e−x​coshx=ex+e−x2cosh~ x = \frac{e^x+e^{-x}}{2}cosh x=2ex+e−x​tanhx=sinhxcoshxtanh~ x =\frac{sinh ~x}{cosh~ x}tanh x=cosh xsinh x​
优缺点与Sigmoid函数相似。再有,图像中心为原点,可以对极端(大)的数据建模(这类函数输入极端数据影响不算大,若像Sigmoind一样偏离中心将很不方便)
Relu(Rectified Linear Unit)f(x)=max(0,x)f(x)=max(0,x)f(x)=max(0,x)经过线性整流激活函数的神经元会输出max(0,wTx+b)max(0,\boldsymbol {w^Tx}+b)max(0,wTx+b)优点:计算速度快、允许反向传播
缺点:输入近0或负值时,神经网络无法学习。

def rectified(x):return max(0.0, x)

稍稍测试一下这个函数:

from matplotlib import pyplot# rectified linear function
def rectified(x):return max(0.0, x)
# define a series of inputs
series_in = [x for x in range(-10, 11)]
# calculate outputs for our inputs
series_out = [rectified(x) for x in series_in]
# line plot of raw inputs to rectified outputs
pyplot.plot(series_in, series_out)
pyplot.show()


Leaky ReLuf(x)=xifx>0otherwise=λxf(x)=x ~~~if ~x>0 ~~~~otherwise = \lambda xf(x)=x   if x>0    otherwise=λx优点:负值也允许反向传播

至于选择哪个(些)激活函数更好,参见research: learning combinations of activation functions

在此例中前三层用了ReLu模型。鉴于这是一个分类器,输出层用了LogSoftmax模型。

softmax functionajL=ezjL∑kezkLa_j^L=\frac{e^{z_j^L}}{\sum_ke^{z_k^L}}ajL​=∑k​ezkL​ezjL​​显然,∑kakL=∑kezkL∑kezkL=1\sum_ka_k^L=\frac{\sum_ke^{z_k^L}}{\sum_ke^{z_k^L}}=1k∑​akL​=∑k​ezkL​∑k​ezkL​​=1从上看出,softmax的输出是一种概率分布,输出层输出之和为1.其中,ajLa_j^LajL​对应最终分类结果为j的概率。


(上图源于github.io)上图是在图片分类中使用Softmax。以中间方格块左上角的5为例e5+e4+e2e5=0.71\frac{e^5+e^4+e^2}{e^5}=0.71e5e5+e4+e2​=0.71
而logsoftmax函数表示为log⁡σ(xi)=log⁡exp⁡(xi)∑jexp⁡(xj)=xi−log⁡(∑jexp⁡(xj))\log\sigma(x_i)=\log \frac{\exp(x_i)}{\sum_j \exp(x_j)}=x_i-\log(\sum_j \exp(x_j))logσ(xi​)=log∑j​exp(xj​)exp(xi​)​=xi​−log(j∑​exp(xj​)) 输入是n维张量,输出相同。源代码参见pytorch_softmax

定义完logsoftmax后,定义负对数似然损失函数(negative log-likelihood loss)NLLLoss,与前者共同作为交叉熵损失函数(cross-entropy loss)使用。

这是接续的代码示例,定义NLLLoss

criterion = nn.NLLLoss()
images, labels = next(iter(trainloader))
images = images.view(images.shape[0], -1)logps = model(images) #log probabilities
loss = criterion(logps, labels) #calculate the NLL loss

其中在第二行中,展平高维数组。image指训练集的数据,label是数据对应的标签。使用迭代器遍历训练集中的数据。logps指向训练之后的模型(参数等);最后计算NLL损失函数。

附1:交叉熵用于度量两个概率分布之间的差异程度,离散的定义式H(p,q)=∑xp(x)⋅log∣1q(x)∣H(p,q)=\sum_xp(x)\cdot log|\frac{1}{q(x)}|H(p,q)=x∑​p(x)⋅log∣q(x)1​∣对于神经网络,分类问题常用交叉熵,回归问题常用均方误差(Mean Squared Error)

附2:手写数字识别是多分类、单标签问题、使用了softmax层,需要使用NLLLoss。该函数默认传参规则是torch.nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction=‘mean’)

例如,对于某些输入得到如下概率:[0.2,0.3,0.5,0.1],而正确结果是[0,0,0,1](第四类),则当前模型得出(正确)结果的概率是0.1,NLL就是-ln(0.1)=2.3。而如果正确结果是第三个,算得NLL=-ln(0.5)=0.69。看出,模型的预测效果越好,NLL的值越低。如下图,y=-log(x)的图像进行理解,当x值很小时,NLL趋近很高,说明很不可靠(放大它的不可靠程度),而趋近很大时,-log(x)一直都很小(放大它的可靠程度)。


下图继续使用识别狗、马、猫图片的示例说明问题。取出每个图片对应的模型分类概率差取-log,就是NLL的过程。
(源自github.io)
下面是测试NLL属性的一段代码,以供参考。

###############此NLLLoss测试代码来源pytorch文档###############
m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
# input is of size N x C = 3 x 5
input = torch.randn(3, 5, requires_grad=True)
#each element in target has to have 0 <= value < C
target = torch.tensor([1, 0, 4])
output = loss(m(input), target)
output.backward()
# 2D loss example (used, for example, with image inputs)
N, C = 5, 4
loss = nn.NLLLoss()
# input is of size N x C x height x width
data = torch.randn(N, 16, 10, 10)
conv = nn.Conv2d(16, C, (3, 3))
m = nn.LogSoftmax(dim=1)
# each element in target has to have 0 <= value < C
target = torch.empty(N, 8, 8, dtype=torch.long).random_(0, C)
output = loss(m(conv(data)), target)
output.backward()

训练模型
一个神经网路是在庞大数据集上迭代特别多次而被训练的。它通过调整神经网络的权重、使损失函数最小化来“学习”。反向传播(求导)之前,模型的权重被设置为空(开始前要先把它们变成0);调用backward()之后,pytorch自动求导,实现梯度方向传播。

print('Before backward pass: \n', model[0].weight.grad)
loss.backward()
print('After backward pass: \n', model[0].weight.grad)

口口声声梯度反向传播,怎么传播看一看:

在多数情况下,写出层层之间连在一起的复合函数十分困难,而且训练要求不断求精确的导数。所以,需要分层分析,分层就需要导数用反向传播实现。
已经知道损失函数(错误观测点)、知道如何计算损失函数的导数、知道复合函数中每一个元函数的导数,就可以从观测点到起点反向传导这个delta(模型与实际的差异)。
假设有这样一个模型:输入→3⋅x→2⋅x→输出输入\rightarrow 3\cdot x\rightarrow 2\cdot x \rightarrow 输出输入→3⋅x→2⋅x→输出输入有0.001的改变,第一层输出改变0.003,输出层改变0.006。反过来,输出0.006的变动,可推知输入0.001的变动。如果我们知道如何前向传播(直接应用某层函数)和反向传播(求每层函数的导数),就能构建任何复杂的神经网络。只要在每层函数调用时保留它们和它们代入的参数,就可以在返回的过程中求导(pytorch提供自动求导功能)。由于在神经网络中任何层又可以传递给很多其他层,所以最终得到的(模型输出)是每个目标层的增量和。这样来,反向传播中线性计算堆栈很可能形成一个十分复杂的图结构。
前向传播和反向传播的流程如下图。

下图给出了更为直观的解释。上标表示这是第几层处理后的结果。从左上方沿上方向右看,是将X=A[0]X=A^{[0]}X=A[0]代入输入层,将各层得到的数代入下一层,沿各层正向传播。在输出层得到A[L]A^{[L]}A[L]便是初始化的模型的预测值。与实际值比对,通过LOSS FUNCTION得到Δ,下图表示成dA[L]dA^{[L]}dA[L](都是矩阵形式)。

(续上文)dA[L]dA^{[L]}dA[L]得出后,根据dAL−1≈fL′L⋅dALd\textbf A^{L-1}\approx \boldsymbol {f_L'}^{L}\cdot dA^LdAL−1≈fL′​L⋅dAL反推出dAL−1dA^{L-1}dAL−1,以此类推反推到头。这只是固定了f变化AL-1,固定A还能得到dWL和dbL(b,bias,偏移量)。
在现实问题中,通常不会用如此大的步长更新参数。实际问题中通常有许多非线性模型,weight的大幅变化影响是巨大的,得不偿失。通常有
Newweight=oldweight—Derivative∗learningrateNew weight = old weight — Derivative * learning rateNewweight=oldweight—Derivative∗learningrate学习率在初级阶段设为常数即可。
有很多种更新权重的方法,这些方法通常叫优化器(optimizer)。传统方法通常是在数据集全局中最小化loss function,而现在通常以N个批量块为一组训练数据(在载入数据被打散的基础上)。这叫小批量梯度下降(mini-batch gradient descent),正如这里刚开始设定Batch_size = 64一样。如果设为1,就叫随机梯度下降法(stochastic gradient descent)。

上图是不同优化器的优化性能比较。以下代码是对上述理论的实现。其中,SGD是优化器的一种(这里就是对每一批次间随机梯度下降)、learning rate一般设得很小;momentum是SGD改良版的动量参数。在反向传播之前,要把梯度值初始化成0,对应optimizer.zero_grad();调用上面建立的三层model,对输出调用上面建好的NLLLoss得出最终的损失函数值(把得出的结论与数字的原始标签比对、求对数的相反数);下一步(重点)是刚刚描述的梯度反向传播;反向传播后更新各层函数的权重,记录损失函数值。

optimizer = optim.SGD(model.parameters(), lr=0.003, momentum=0.9)
time0 = time()
epochs = 15
for e in range(epochs):running_loss = 0for images, labels in trainloader:# Flatten MNIST images into a 784 long vectorimages = images.view(images.shape[0], -1)# Training passoptimizer.zero_grad()output = model(images)loss = criterion(output, labels)#This is where the model learns by backpropagatingloss.backward()#And optimizes its weights hereoptimizer.step()running_loss += loss.item()else:print("Epoch {} - Training loss: {}".format(e, running_loss/len(trainloader)))
print("\nTraining Time (in minutes) =",(time()-time0)/60)

个人电脑直接运行,输出如下所示。epoch表示第几次遍历数据集。后面归一化的长串数字表示错误率。可以看出从第一次遍历向下错误率逐渐降低的过程。

自此机器已经完成了学习。首先输出几组图片和机器的决策看看学得怎么样:

images, labels = next(iter(valloader))
####### 未找到helper,直接把view-classify摘在此。此函数代码摘自pytorch####
def view_classify(img, ps, version="MNIST"):''' Function for viewing an image and it's predicted classes.'''ps = ps.data.numpy().squeeze()fig, (ax1, ax2) = plt.subplots(figsize=(6,9), ncols=2)ax1.imshow(img.resize_(1, 28, 28).numpy().squeeze())ax1.axis('off')ax2.barh(np.arange(10), ps)ax2.set_aspect(0.1)ax2.set_yticks(np.arange(10))if version == "MNIST":ax2.set_yticklabels(np.arange(10))elif version == "Fashion":ax2.set_yticklabels(['T-shirt/top','Trouser','Pullover','Dress','Coat','Sandal','Shirt','Sneaker','Bag','Ankle Boot'], size='small');ax2.set_title('Class Probability')ax2.set_xlim(0, 1.1)plt.tight_layout()
img = images[0].view(1, 784)
with torch.no_grad():logps = model(img)ps = torch.exp(logps)
probab = list(ps.numpy()[0])
print("Predicted Digit =", probab.index(max(probab)))
view_classify(img.view(1, 28, 28), ps)





最后,我们到检验集(test/validation set)来检测模拟真实的表现。调用模型的过程与上相同,通过循环验证模型决策与实际是否匹配,得出匹配率。

correct_count, all_count = 0, 0
for images,labels in valloader:for i in range(len(labels)):img = images[i].view(1, 784)with torch.no_grad():logps = model(img)ps = torch.exp(logps)probab = list(ps.numpy()[0])pred_label = probab.index(max(probab))true_label = labels.numpy()[i]if(true_label == pred_label):correct_count += 1all_count += 1print("Number Of Images Tested =", all_count)
print("\nModel Accuracy =", (correct_count/all_count))


可见,匹配了达到97.52%,最初级的方法效果还是挺好的。如果采用cnn等模型,效果可能更好。

机器学习入门(1)---以手写数字识别为例相关推荐

  1. tensorflow入门之MINIST手写数字识别

    最近在学tensorflow,看了很多资料以及相关视频,有没有大佬推荐一下比较好的教程之类的,谢谢.最后还是到了官方网站去,还好有官方文档中文版,今天就结合官方文档以及之前看的教程写一篇关于MINIS ...

  2. 机器学习之算法案例手写数字识别

    算法案例手写数字识别 MNIST数据集是机器学习领域中非常经典的一个数据集,由60000个 训练样本和10000个测试样本组成,每个样本都是一张28 * 28像素的灰度 手写数字图片. 选择算法,并保 ...

  3. python手写数字识别实验报告_机器学习python实战之手写数字识别

    看了上一篇内容之后,相信对K近邻算法有了一个清晰的认识,今天的内容--手写数字识别是对上一篇内容的延续,这里也是为了自己能更熟练的掌握k-NN算法. 我们有大约2000个训练样本和1000个左右测试样 ...

  4. 在OpenCV里使用机器学习库sklearn 实现手写数字识别1

    前面学习过KNN的方式来实现手写数字识别,不过效果一般,那么有没有别的方法来试一试,或许可以改进一点呢.在本文里将要介绍使用SVM和HOG的方式来实现手写数字识别,比如最终结果如下图: 在这个例子里与 ...

  5. 开根号的笔算算法图解_机器学习KNN算法之手写数字识别

    1.算法简介 手写数字识别是KNN算法一个特别经典的实例,其数据源获取方式有两种,一种是来自MNIST数据集,另一种是从UCI欧文大学机器学习存储库中下载,本文基于后者讲解该例. 基本思想就是利用KN ...

  6. 经典实战案例:用机器学习 KNN 算法实现手写数字识别 | 原力计划

    作者 | 奶糖猫 来源 | CSDN 博客,责编 | 夕颜 头图 | CSDN 下载自视觉中国 出品 | CSDN(ID:CSDNnews) 算法简介 手写数字识别是KNN算法一个特别经典的实例,其数 ...

  7. 入门学习MNIST手写数字识别

    一.MNIST数据集 1.MNIST数据集简介 MNIST数据集是一个公开的数据集,相当于深度学习的hello world,用来检验一个模型/库/框架是否有效的一个评价指标. MNIST数据集是由0〜 ...

  8. 【机器学习/人工智能】 大作业:手写数字识别系统

    写在前面 参考的是https://zh.d2l.ai/index.html 一.大作业设计目的与要求 (1)利用所学习的聚类算法完成简单的图像分割系统. (2)编程并利用相关软件完成大作业测试,得到实 ...

  9. python 手写数字识别 封装GUI,手写板获取鼠标写字轨迹信息

    python 手写数字识别知识不用多说,本文用深度学习Python库Keras实现深度学习入门教程mnist手写数字识别.mnist手写数字识别是机器学习和深度学习领域的"hello wor ...

最新文章

  1. 网络爬虫框架cetty的实现
  2. 对凸优化(Convex Optimization)的一些浅显理解
  3. 力扣(LeetCode) 35. 搜索插入位置
  4. python opencv3 检测人
  5. armbian nginx 部署博客_通过Git将Hexo博客部署到服务器
  6. (数据库系统概论|王珊)第九章关系查询处理和关系优化-第二节:查询优化
  7. OpenCV--常见图片格式转换与深浅拷贝
  8. 尚硅谷 谷粒学院 毕业设计 在线教育 部署文档
  9. 基于Linux利用PPP实现4G模块联网
  10. Kubuntu 安装fcitx 5
  11. 问题 K: [入门OJ]开会时间(初中生请多多指教)
  12. microchip-01 MPLAB IDE安装
  13. python移动文件但不覆盖_怎么做到Python file重复写入之前的内容不被后写入的覆盖...
  14. 下载kaggle数据集出现的一系列问题
  15. SDL2源码分析之OpenGL ES在windows上的渲染过程
  16. 游戏辅助制作核心--植物大战僵尸逆向之阳光修改(一)
  17. 谷歌浏览器的页面保存为图片
  18. SciTE 常见问题及解决方法集锦
  19. 常用网络广告类型:CPC,CPA,CPS,CPM,CPT,PPC详解
  20. 软件工程-网上商城分析设计(小组项目)

热门文章

  1. php输入文字不显示,ps写了文字为什么不显示 ps里输入文字不显示的四个原因及解决方法...
  2. 如何优雅地通过绿X科技的Web安全漏洞扫描
  3. 【CSS3】设置超链接样式
  4. 中国企业流动性市场趋势报告、技术动态创新及市场预测
  5. 企业流程管理体系建设的关键
  6. RISC-V CPU设计(一)---RV32I指令集介绍
  7. 如何解决Adobe Photoshop CS4"产品许可证已过期的问题
  8. Windows 10 FCU已经完全提供,微软叫大家都打开更新吧
  9. FFmpeg —— 录屏,保存为.yuv文件(附代码)
  10. 【答辩问题】计算机专业本科毕业答辩问题及回答