机器学习 --- 神经网络

第1关:神经网络基本概念

任务描述

本关任务:根据本节课所学知识完成本关所设置的选择题。

相关知识

为了完成本关任务,你需要掌握:1.神经网络基本概念。

神经网络基本概念

  • 神经网络由输入层、隐藏层、输出层组成;

  • 层与层之间的神经元有连接,而层内之间的神经元没有连接。连接的神经元都有对应的权重;

  • 最左边的层叫做输入层,这层负责接收输入数据;

  • 最右边的层叫输出层,我们可以从这层获取神经网络输出数据;

  • 输入层和输出层之间的层叫做隐藏层。

  • 表示相邻两层不同神经元连接的强度叫权重。如果神经元1到神经元2有较大的值,则意味着神经元1对神经元2有较大影响。权重减小了输入值的重要性,对于接近于0的权重,输入的改变不会影响输出的变化;负权重意味着,增加输入而输出会减小。权重决定了输入对输出影响的大小。

上图中的网络一共由3层神经元组成,但实质上只有2层权重,因此我们通常将输入层当做第0层网络,上图我们称其为2层网络(根据输入层、隐藏层、输出层的总数减去1后的数量来命名网络)。

编程要求

请仔细阅读题目,结合相关知识,完成本关的选择题任务。

测试说明

平台会对你的代码进行运行测试,如果实际输出结果与预期结果相同,则通关;反之,则 GameOver

 C

第2关:激活函数

任务描述

本关任务:用Python实现常用激活函数。

相关知识

为了完成本关任务,你需要掌握:1.什么是激活函数,2.激活函数作用,3.常用激活函数。

什么是激活函数

我们已经知道神经网络是由一个个神经元也就是感知机组成,感知机数学模型如下:

f(x)=sign(w1​x1​+w2​x2​+...+wn​xn​+b)

sign={−1+1​x<0xgeq0​

其中的sign函数被称为阶跃函数,是用来引入非线性因素的。而在神经网络里,我们使用一些其它的函数来代替,并称其为激活函数

激活函数作用

如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机。只能对线性数据进行分类:

如果使用的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中,对非线性数据进行分类:

常用激活函数

最开始,神经网络中经常使用的一个激活函数就是sigmoid函数,函数公式如下:

sign={−1+1​x<0xgeq0​

函数图像如下图所示:

sigmoid函数是一条平滑的曲线,输出随着输入发生连续的变化。而阶跃函数以0为界,输出发生急剧兴的变化。sigmoid函数的平滑性对神经网络的学习具有重要意义。

sigmoid函数在x过大或过小时,函数变化非常小,即梯度非常接近0,随着神经网络的加深,在使用梯度下降方法的时候,由于梯度接近0,参数更新接近0,网络开始学不到东西,即梯度消失。所以现在常使用relu激活函数,函数公式如下:

relu(x)={0x​x<0xgeq0​

函数图像如下图所示:

relu函数在x大于等于0时,输出的是x本身,函数变化率恒为1,这样就避免了梯度消失的情况。当x小于0时,输出为0,即神经元不被激活,这样也符合人脑内并不是所有神经元同时被激活的情况。并且,relu函数计算成本非常低,所以在神经网络过深时常使用relu函数作为激活函数。

编程要求

根据提示,在右侧编辑器补充Python代码,实现relu激活函数,底层代码会调用您实现的relu激活函数来进行测试。

测试说明

测试用例:

输入:9

预期输出:9

输入:-1

预期输出:0

#encoding=utf8

def relu(x):
    '''
    x:负无穷到正无穷的实数
    '''
    #********* Begin *********#
    if x>= 0:
        return x
    else:
        return 0
    #********* End *********#

第3关:反向传播算法

任务描述

本关任务:用sklearn构建神经网络模型,并通过鸢尾花数据集中鸢尾花的4种属性与种类对神经网络模型进行训练。我们会调用你训练好的神经网络模型,来对未知的鸢尾花进行分类。

相关知识

为了完成本关任务,你需要掌握:1.神经网络是如何训练,2.前向传播,3.反向传播,4.sklearn中的神经网络。

数据集介绍

鸢尾花数据集是一类多重变量分析的数据集。通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(SetosaVersicolourVirginica)三个种类中的哪一类。

想要使用该数据集可以使用如下代码:

 
  1. #获取训练数据
  2. train_data = pd.read_csv('./step2/train_data.csv')
  3. #获取训练标签
  4. train_label = pd.read_csv('./step2/train_label.csv')
  5. train_label = train_label['target']
  6. #获取测试数据
  7. test_data = pd.read_csv('./step2/test_data.csv')

数据集中部分数据与标签如下图所示:

神经网络是如何训练

神经网络的训练方法跟逻辑回归相似:

也是使用梯度下降算法来更新模型的参数,既然要使用梯度下降算法,就要知道损失函数对参数的梯度。在神经网络中,由于有多层的网络,所以使得我们的网络非常不好训练,这么多参数的梯度求起来非常麻烦。反向传播算法出现,为我们解决了这一个问题,它能够快速的计算这些梯度。反向传播算法一共分为两部分:前向传播与反向传播。

前向传播

前向传播指的是数据x从神经网络输入层,与当层的权重相乘,再加上当层的偏置,所得到的值经过激活函数激活后,再输入到下一层。最后,在输出层所得到的值经过softmax函数转化为网络对数据的预测。

Z[1]=XW[1]+B[1]

A[1]=f(Z[1])

Z[2]=A[1]W[2]+B[2]

A=softmax(Z[2])

其中,输出层的Z值要经过softmax函数,转化为预测值。softmax函数公式如下:

ak​=sumi=1n​exp(zi​)exp(zk​)​

输出层的A为预测值,是一个向量。有多少类别,A就有多长。隐藏层的A[L]为激活值,L代表是第L层隐藏层。W为权重,是一个矩阵,上一层神经网络有多少个神经元,它就有多少行,当层神经网络有多少神经元,它就有多少列。B为偏置,是一个向量,长度与当层神经元个数一样。神经网络使用的损失函数为交叉熵损失函数:

其中ai​代表样本被预测为第i个类别的可能性,yi​代表样本为第i个类别的真实可能性。交叉熵损失函数公式如下:

L(y,a)=−sumi=1n​yi​lnai​

前向传播的主要目的就是得到预测值,再算出交叉熵损失函数。

交叉熵

交叉熵(cross entropy)是深度学习中常用的一个概念,一般用来求目标与预测值之间的差距。机器学习是用网络训练出来的分布q来表示真实分布p,此时当两者的交叉熵越小时,模型训练的结果越接近样本的真实分布,这也是交叉熵被用来作为损失函数的原因。

在机器学习中,P往往用来表示样本的真实分布,比如[1,0,0]表示当前样本属于第一类。Q用来表示模型所预测的分布,比如[0.7,0.2,0.1]直观的理解就是如果用P来描述样本,那么就非常完美。而用Q来描述样本,虽然可以大致描述,但是不是那么的完美,信息量不足,需要额外的一些“信息增量”才能达到和P一样完美的描述。如果我们的Q通过反复训练,也能完美的描述样本,那么就不再需要额外的“信息增量”,Q等价于P

反向传播

我们通过前向传播能够求出损失函数,而反向传播就是利用前向传播得到的损失函数对参数求梯度,即每个参数的偏导。

之所以称为反向传播,是因为我们在利用链式法则的时候对各个参数求偏导的传播顺序跟前向传播相反,如我们要求loss对w1​的偏导,则要先求出lossa的偏导,再求出a对z2的偏导,再求出z2对a1的偏导,再求出a1对z1的偏导,再求出z1对w1的偏导,然后全部相乘就得到loss对w1​的偏导了。公式如下:

partialw[1]partialloss​=partiala[2]partialloss​.partialz[2]partiala[2]​.partiala[1]partialz[2]​.partialz[1]partiala[1]​.partialw[1]partialz[1]​

所以反向传播的目的就是求出损失函数对各个参数的梯度。最后我们就可以用梯度下降算法来训练我们的神经网络模型了。

sklearn中的神经网络

MLPClassifier的构造函数中有四个常用的参数可以设置:

  • solverMLP的求解方法lbfs在小数据上表现较好,adam较为鲁棒,sgd在参数调整较优时会有最佳表现(分类效果与迭代次数);sgd标识随机梯度下降;

  • alpha:正则项系数,默认为L2正则化,具体参数需要调整;

  • hidden_layer_sizeshidden_layer_sizes=(3, 2)设置隐藏层size2层隐藏层,第一层3个神经元,第二层2个神经元。

  • max_iter:最大训练轮数。

sklearn中其他分类器一样,MLPClassifier类中的fit函数用于训练模型,fit函数有两个向量输入:

  • X:大小为**[样本数量,特征数量]**的ndarray,存放训练样本;

  • Y:值为整型,大小为**[样本数量]**的ndarray,存放训练样本的分类标签。

MLPClassifier类中的predict函数用于预测,返回预测标签,predict函数有一个向量输入:

  • X:大小为**[样本数量,特征数量]**的ndarray,存放预测样本。

MLPClassifier的使用代码如下:

 
  1. from sklearn.neural_network import MLPClassifier
  2. mlp = MLPClassifier(solver='lbfgs',max_iter =10,
  3. alpha=1e-5,hidden_layer_sizes=(3,2))
  4. mlp.fit(X_train, Y_train)
  5. result = mlp.predict(X_test)

编程要求

使用sklearn构建神经网络模型,利用训练集数据与训练标签对模型进行训练,然后使用训练好的模型对测试集数据进行预测,并将预测结果保存到./step2/result.csv中。保存格式如下:

测试说明

我们会获取你的预测结果与真实标签对比,预测正确率高于95%视为过关。

#encoding=utf8
import os
import pandas as pd
from sklearn.neural_network import MLPClassifier

if os.path.exists('./step2/result.csv'):
    os.remove('./step2/result.csv')
    
#********* Begin *********#
train_data = pd.read_csv('./step2/train_data.csv')

train_label = pd.read_csv('./step2/train_label.csv')
train_label = train_label['target']

test_data = pd.read_csv('./step2/test_data.csv')
mlp = MLPClassifier(solver='lbfgs',max_iter=30, alpha=1e-4,hidden_layer_sizes=(20, ))

mlp.fit(train_data, train_label)
predict = mlp.predict(test_data)
df = pd.DataFrame({'result':predict}) 
df.to_csv('./step2/result.csv', index=False)
#********* End *********#

第4关:使用pytorch搭建卷积神经网络识别手写数字

任务描述

本关任务:使用pytorch搭建出卷积神经网络并对手写数字进行识别。

相关知识

为了完成本关任务,你需要掌握:1.卷积神经网络,2.pytorch构建卷积神经网络项目流程。

卷积神经网络

为什么使用卷积神经网络

卷积神经网络最早主要用来处理图像信息。如果用全连接前馈网络来处理图像时,会存在以下两个问题:

 
  1. 参数太多:如果输入图像大小为100 × 100 × 3。在全连接前馈网络中,第一个隐藏层的每个神经元到输入层都有 30000个相互独立的连接,每个连接都对应一个权重参数。随着隐藏层神经元数量的增多,参数的规模也会急剧增加。这会导致整个神经网络的训练效率会非常低,也很容易出现过拟合。
  2. 局部不变性特征: 自然图像中的物体都具有局部不变性特征,比如在尺度缩放、平移、旋转等操作不影响其语义信息。而全连接前馈网络很难提取这些局部不变特征,一般需要进行数据增强来提高性能。

卷积神经网络是一种具有局部连接、 权重共享等特性的深层前馈神经网络。想要识别图像中的物体,就需要提取出比较好的特征,该特征应能很好地描述想要识别的物体。所以物体的特征提取是一项非常重要的工作。而图像中物体的特征以下几种特点:

1.物体的特征可能只占图像中的一小部分。比如下图中狗的鼻子只是图像中很小的一部分。

2.同样的特征可能出现在不同图像中的不同位置,比如下图中狗的鼻子在两幅图中出现的位置不同。

3.缩放图像的大小对物体特征的影响可能不大,比如下图是缩小后的图,但依然能很清楚的辨认出狗的鼻子。

而卷积神经网络中的卷积池化操作能够较好地抓住物体特征的以上3种特点。

卷积

卷积说白了就是有一个卷积核(其实就是一个带权值的滑动窗口)在图像上从左到右,从上到下地扫描,每次扫描的时候都会将卷积核里的值所构成的矩阵与图像被卷积核覆盖的像素值矩阵做内积。整个过程如下图所示,其中黄色方框代表卷积核,绿色部分代表单通道图像,红色部分代表卷积计算后的结果,通常称为特征图:

那为什么说卷积能够提取图像中物体的特征呢?其实很好理解,上图中的卷积核中值的分布如下:

当这个卷积核卷积的时候就会在3行3列的小范围内计算出图像中几乎所有的33列子图像与卷积核的相似程度(也就是内积的计算结果)。相似程度越高说明该区域中的像素值与卷积核越相似。(上图的特征图中值为4的位置所对应到的源图像子区域中像素值的分布与卷积核值的分布最为接近)这也就说明了卷积在提取特征时能够考虑到特征可能只占图像的一小部分,以及同样的特征可能出现在不同的图像中不同的位置这两个特点。

PS:卷积核的值是怎么确定下来的?很明显是训练出来的!

池化

池化就是将输入图像进行缩小,减少像素信息,只保留重要信息。 池化的操作也很简单,通常情况下,池化区域是22列的大小,然后按一定规则转换成相应的值,例如最常用的最大池化(max pooling)。最大池化保留了每一小块内的最大值,也就是相当于保留了这一块最佳的匹配结果。举个例子,如下图中图像是44列的,池化区域是22列的,所以最终池化后的特征图是22列的。图像中粉色区域最大的值是6,所以池化后特征图中粉色位置的值是6,图像中绿色区域最大的值是8,所以池化后特征图中绿色位置的值是8,以此类推。

从上图可以看出,最大池化不仅仅缩小了图像的大小,减少后续卷积的计算量,而且保留了最佳的特征(如果图像是经过卷积后的特征图)。也就相当于把图缩小了,但主要特征还在,这就考虑到了缩放图像的大小对物体的特征影响可能不大的特点。

全连接网络

卷积与池化能够很好的提取图像中物体的特征,当提取好特征之后就可以着手开始使用全连接网络来进行分类了。全连接网络的大致结构如下:

其中输入层通常指的是对图像进行卷积,池化等计算之后并进行扁平后的特征图。隐藏层中每个方块代表一个神经元,每一个神经元可以看成是一个很简单的线性分类器和激活函数的组合。输出层中神经元的数量一般为标签类别的数量,激活函数为softmax(因为将该图像是猫或者狗的得分进行概率化)。因此我们可以讲全连接网络理解成很多个简单的分类器的组合,来构建成一个非常强大的分类器。

卷积神经网络大致结构

将卷积,池化,全连接网络进行合理的组合,就能构建出属于自己的神经网络来识别图像中是猫还是狗。通常来说卷积,池化可以多叠加几层用来提取特征,然后接上一个全连接网络来进行分类。大致结构如下:

pytorch构建卷积神经网络项目流程

数据集介绍与加载数据

本次使用数据集为mnist手写数字数据集,简单来讲就是如下的东西:

数据集分为训练集与测试集,训练集中一共有60000张图片,测试集中一共有10000张图片,每张图片大小为28X28x1。图片标签为对应的数字,如8对应的label8,若使用onehot编码则对应的label为:[0,0,0,0,0,0,0,0,1,0]。 为节约计算时间,我们取训练集中的6000张图片用来训练,测试集中的600张进行测试。使用pytorch加载数据集方法如下:

 
  1. #加载数据
  2. import torchvision
  3. train_data = torchvision.datasets.MNIST(
  4. root='./step3/mnist/',
  5. train=True, # this is training data
  6. transform=torchvision.transforms.ToTensor(), # Converts a PIL.Image or numpy.ndarray to
  7. download=False,
  8. )
  9. #取6000个样本为训练集
  10. train_data_tiny = []
  11. for i in range(6000):
  12. train_data_tiny.append(train_data[i])
  13. train_data = train_data_tiny

构建模型

加载好数据集,就需要构建卷积神经网络模型:

 
  1. #构建卷积神经网络模型
  2. class CNN(nn.Module):
  3. def __init__(self):
  4. super(CNN, self).__init__()
  5. self.conv1 = nn.Sequential( # input shape (1, 28, 28)
  6. nn.Conv2d(
  7. in_channels=1, # input height
  8. out_channels=16, # n_filters
  9. kernel_size=5, # filter size
  10. stride=1, # filter movement/step
  11. padding=2, # if want same width and length of this image after con2d, padding=(kernel_size-1)/2 if stride=1
  12. ), # output shape (16, 28, 28)
  13. nn.ReLU(), # activation
  14. nn.MaxPool2d(kernel_size=2), # choose max value in 2x2 area, output shape (16, 14, 14)
  15. )
  16. self.conv2 = nn.Sequential( # input shape (16, 14, 14)
  17. nn.Conv2d(16, 32, 5, 1, 2), # output shape (32, 14, 14)
  18. nn.ReLU(), # activation
  19. nn.MaxPool2d(2), # output shape (32, 7, 7)
  20. )
  21. self.out = nn.Linear(32 * 7 * 7, 10) # fully connected layer, output 10 classes
  22. def forward(self, x):
  23. x = self.conv1(x)
  24. x = self.conv2(x)
  25. x = x.view(x.size(0), -1) # flatten the output of conv2 to (batch_size, 32 * 7 * 7)
  26. output = self.out(x)
  27. return output
  28. cnn = CNN()

需要指出的几个地方:

 
  1. class CNN需要继承Module
  2. 需要调用父类的构造方法:super(CNN, self).__init__()
  3. 在Pytorch中激活函数Relu也算是一层layer
  4. 需要实现forward()方法,用于网络的前向传播,而反向传播只需要调用Variable.backward()即可。

定义好模型后还要构建优化器与损失函数: torch.optim是一个实现了各种优化算法的库。使用方法如下:

 
  1. #SGD表示使用随机梯度下降方法,lr为学习率,momentum为动量项系数
  2. optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)
  3. #交叉熵损失函数
  4. loss_func = nn.CrossEntropyLoss()

训练模型

在定义好模型后,就可以根据反向传播计算出来的梯度,对模型参数进行更新,在pytorch中实现部分代码如下:

 
  1. #将梯度清零
  2. optimizer.zero_grad()
  3. #对损失函数进行反向传播
  4. loss.backward()
  5. #训练
  6. optimizer.step()

保存模型

pytorch中使用torch.save保存模型,有两种方法,第一种: 保存整个模型和参数,方法如下:

 
  1. torch.save(model, PATH)

第二种为官方推荐,只保存模型的参数,方法如下:

 
  1. torch.save(model.state_dict(), PATH)

加载模型

对应两种保存模型的方法,加载模型也有两种方法,第一种如下:

 
  1. model = torch.load(PATH)

第二种:

 
  1. #CNN()为你搭建的模型
  2. model = CNN()
  3. model.load_state_dict(torch.load(PATH))

如果要对加载的模型进行测试,需将模型切换为验证模式

 
  1. model.eval()

编程要求

使用pytorch搭建出卷积神经网络模型,再对模型进行训练,并将训练好的模型保存至./step3/cnn.pkl中。

测试说明

我们会加载你训练好的模型,并对测试集数据进行预测,预测正确率高于85%视为过关。

提示:平台使用torch版本为0.4input需要为Variable类型,使用代码如下:

 
  1. #mini_batch
  2. train_loader = Data.DataLoader(dataset=train_data, batch_size=64, shuffle=True)
  3. #encoding=utf8
    import torch
    import torch.nn as nn
    from torch.autograd import Variable
    import torch.utils.data as Data
    import torchvision
    import os
    if os.path.exists('./step3/cnn.pkl'):
        os.remove('./step3/cnn.pkl')
        
    #加载数据             
    train_data = torchvision.datasets.MNIST(
        root='./step3/mnist/',
        train=True,                                     # this is training data
        transform=torchvision.transforms.ToTensor(),    # Converts a PIL.Image or numpy.ndarray to                                                    
        download=False,
    )
    #取6000个样本为训练集
    train_data_tiny = []
    
    for i in range(6000):
        train_data_tiny.append(train_data[i])
    
    train_data = train_data_tiny
    
    #********* Begin *********#
    train_loader = Data.DataLoader(
        dataset=train_data,
        batch_size=64,
        num_workers=2,
        shuffle=True
    )
    
    
    # 构建卷积神经网络模型
    class CNN(nn.Module):
        def __init__(self):
            super(CNN, self).__init__()
            self.conv1 = nn.Sequential(  # input shape (1, 28, 28)
                nn.Conv2d(
                    in_channels=1,  # input height
                    out_channels=16,  # n_filters
                    kernel_size=5,  # filter size
                    stride=1,  # filter movement/step
                    padding=2,
                    # if want same width and length of this image after con2d, padding=(kernel_size-1)/2 if stride=1
                ),  # output shape (16, 28, 28)
                nn.ReLU(),  # activation
                nn.MaxPool2d(kernel_size=2),  # choose max value in 2x2 area, output shape (16, 14, 14)
            )
            self.conv2 = nn.Sequential(  # input shape (16, 14, 14)
                nn.Conv2d(16, 32, 5, 1, 2),  # output shape (32, 14, 14)
                nn.ReLU(),  # activation
                nn.MaxPool2d(2),  # output shape (32, 7, 7)
            )
            self.out = nn.Linear(32 * 7 * 7, 10)  # fully connected layer, output 10 classes
    
        def forward(self, x):
            x = self.conv1(x)
            x = self.conv2(x)
            x = x.view(x.size(0), -1)  # flatten the output of conv2 to (batch_size, 32 * 7 * 7)
            output = self.out(x)
            return output
    
    
    cnn = CNN()
    
    # SGD表示使用随机梯度下降方法,lr为学习率,momentum为动量项系数
    optimizer = torch.optim.SGD(cnn.parameters(), lr=0.01, momentum=0.9)
    # 交叉熵损失函数
    loss_func = nn.CrossEntropyLoss()
    
    EPOCH = 3
    for e in range(EPOCH):
        for x, y in train_loader:
            batch_x = Variable(x)
            batch_y = Variable(y)
    
            outputs = cnn(batch_x)
    
            loss = loss_func(outputs, batch_y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()         
               
    #********* End *********#
    #保存模型
    torch.save(cnn.state_dict(), './step3/cnn.pkl')
    
    
    
    
    

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值