第1关:神经网络基本概念
任务描述
本关任务:根据本节课所学知识完成本关所设置的选择题。
相关知识
为了完成本关任务,你需要掌握:1.神经网络基本概念。
神经网络基本概念
-
神经网络由输入层、隐藏层、输出层组成;
-
层与层之间的神经元有连接,而层内之间的神经元没有连接。连接的神经元都有对应的权重;
-
最左边的层叫做输入层,这层负责接收输入数据;
-
最右边的层叫输出层,我们可以从这层获取神经网络输出数据;
-
输入层和输出层之间的层叫做隐藏层。
-
表示相邻两层不同神经元连接的强度叫权重。如果神经元1到神经元2有较大的值,则意味着神经元1对神经元2有较大影响。权重减小了输入值的重要性,对于接近于0的权重,输入的改变不会影响输出的变化;负权重意味着,增加输入而输出会减小。权重决定了输入对输出影响的大小。
上图中的网络一共由3
层神经元组成,但实质上只有2
层权重,因此我们通常将输入层当做第0
层网络,上图我们称其为2
层网络(根据输入层、隐藏层、输出层的总数减去1
后的数量来命名网络)。
编程要求
请仔细阅读题目,结合相关知识,完成本关的选择题任务。
测试说明
平台会对你的代码进行运行测试,如果实际输出结果与预期结果相同,则通关;反之,则 GameOver
。
第1关任务——选择题
上图中的神经网络一共有多少个权重? (C)
A、8
B、12
C、20
D、24
第2关:激活函数
任务描述
本关任务:使用sklearn
完成新闻文本主题分类任务。
相关知识
为了完成本关任务,你需要掌握如何使用sklearn
提供的MultinomialNB
类与文本向量化。
数据简介
本关使用的是20newsgroups
数据集,20newsgroups
数据集是用于文本分类、文本挖据和信息检索研究的国际标准数据集之一。数据集收集了18846
篇新闻组文档,均匀分为20
个不同主题(比如电脑硬件、中东等主题)的新闻组集合。
部分数据如下:
From: Mamatha Devineni Ratnam <mr47+@andrew.cmu.edu>
Subject: Pens fans reactions
Organization: Post Office, Carnegie Mellon, Pittsburgh, PA
Lines: 12
NNTP-Posting-Host: po4.andrew.cmu.edu
I am sure some bashers of Pens fans are pretty confused about the lack
of any kind of posts about the recent Pens massacre of the Devils. Actually,
I am bit puzzled too and a bit relieved. However, I am going to put an end
to non-PIttsburghers relief with a bit of praise for the Pens. Man, they
are killing those Devils worse than I thought. Jagr just showed you why
he is much better than his regular season stats. He is also a lot
fo fun to watch in the playoffs. Bowman should let JAgr have a lot of
fun in the next couple of games since the Pens are going to beat the pulp out of Jersey anyway. I was very disappointed not to see the Islanders lose the final
regular season game. PENS RULE!!!
其中新闻文本对应的主题标签,已经用0-19
这20
个数字表示。
文本向量化
由于数据集中每一条数据都是很长的一个字符串,所以我们需要对数据进行向量化的处理。例如,I have a apple! I have a pen!
可能需要将该字符串转换成向量如[10, 7, 0, 1, 2, 6, 22, 100, 8, 0, 1, 0]
。
sklearn
提供了实现词频向量化功能的CountVectorizer
类。想要对数据进行向量化,代码如下:
from sklearn.feature_ext\fraction.text import CountVectorizer
#实例化向量化对象
vec = CountVectorizer()
#将训练集中的新闻向量化
X_train = vec.fit_transform(X_train)
#将测试集中的新闻向量化
X_test = vec.transform(X_test)
但是仅仅通过统计词频的方式来将文本转换成向量会出现一个问题:长的文章词语出现的次数会比短的文章要多,而实际上两篇文章可能谈论的都是同一个主题。
为了解决这个问题,我们可以使用tf-idf
来构建文本向量,sklearn
中已经提供了tf-idf
的接口,示例代码如下:
from sklearn.feature_ext\fraction.text import TfidfTransformer
#实例化tf-idf对象
tfidf = TfidfTransformer()
#将训练集中的词频向量用tf-idf进行转换
X_train = tfidf.fit_transform(X_train_count_vectorizer)
#将测试集中的词频向量用tf-idf进行转换
X_test = vec.transform(X_test_count_vectorizer)
MultinomialNB
MultinomialNB
是sklearn
中多项分布数据的朴素贝叶斯算法的实现,并且是用于文本分类的经典朴素贝叶斯算法。在本关中建议使用MultinomialNB
来实现文本分类功能。
在MultinomialNB
实例化时alpha
是一个常用的参数。
alpha
: 平滑因子。当等于1
时,做的是拉普拉斯平滑;当小于1
时做的是Lidstone
平滑;当等于0
时,不做任何平滑处理。
MultinomialNB
类中的fit
函数实现了朴素贝叶斯分类算法训练模型的功能,predict
函数实现了法模型预测的功能。
其中fit
函数的参数如下:
X
:大小为[样本数量,特征数量]
的ndarry
,存放训练样本Y
:值为整型,大小为[样本数量]
的ndarray
,存放训练样本的分类标签
而predict
函数有一个向量输入:
X
:大小为[样本数量,特征数量]
的ndarry
,存放预测样本
MultinomialNB
的使用代码如下:
clf = MultinomialNB()
clf.fit(X_train, Y_train)
result = clf.predict(X_test)
编程要求
填写news_predict(train_sample, train_label, test_sample)
函数完成新闻文本主题分类任务,其中:
train_sample
:原始训练样本,类型为ndarray
;
train_label
:训练标签,类型为ndarray
;
test_sample
:原始测试样本,类型为ndarray
。
测试说明
只需返回预测结果即可,程序内部会检测您的代码,预测正确率高于0.8
视为过关。
第2关任务——代码题
#encoding=utf8
def relu(x):
'''
x:负无穷到正无穷的实数
'''
#********* Begin *********#
if x<=0:
return 0
else:
return x
#********* End *********#
第3关:反向传播算法
任务描述
本关任务:用sklearn
构建神经网络模型,并通过鸢尾花数据集中鸢尾花的4
种属性与种类对神经网络模型进行训练。我们会调用你训练好的神经网络模型,来对未知的鸢尾花进行分类。
相关知识
为了完成本关任务,你需要掌握:1.神经网络是如何训练,2.前向传播,3.反向传播,4.sklearn中的神经网络。
数据集介绍
鸢尾花数据集是一类多重变量分析的数据集。通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4
个属性预测鸢尾花卉属于(Setosa
,Versicolour
,Virginica
)三个种类中的哪一类。
想要使用该数据集可以使用如下代码:
#获取训练数据
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')
数据集中部分数据与标签如下图所示:
神经网络是如何训练
神经网络的训练方法跟逻辑回归相似:
也是使用梯度下降算法来更新模型的参数,既然要使用梯度下降算法,就要知道损失函数对参数的梯度。在神经网络中,由于有多层的网络,所以使得我们的网络非常不好训练,这么多参数的梯度求起来非常麻烦。反向传播算法出现,为我们解决了这一个问题,它能够快速的计算这些梯度。反向传播算法一共分为两部分:前向传播与反向传播。
前向传播
前向传播指的是数据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=1nexp(zi)exp(zk)
输出层的A
为预测值,是一个向量。有多少类别,A
就有多长。隐藏层的A[L]为激活值,L
代表是第L
层隐藏层。W
为权重,是一个矩阵,上一层神经网络有多少个神经元,它就有多少行,当层神经网络有多少神经元,它就有多少列。B
为偏置,是一个向量,长度与当层神经元个数一样。神经网络使用的损失函数为交叉熵损失函数:
其中ai代表样本被预测为第i
个类别的可能性,yi代表样本为第i
个类别的真实可能性。交叉熵损失函数公式如下:
L(y,a)=−sumi=1nyilnai
前向传播的主要目的就是得到预测值,再算出交叉熵损失函数。
交叉熵
交叉熵(cross entropy
)是深度学习中常用的一个概念,一般用来求目标与预测值之间的差距。机器学习是用网络训练出来的分布q来表示真实分布p,此时当两者的交叉熵越小时,模型训练的结果越接近样本的真实分布,这也是交叉熵被用来作为损失函数的原因。 在机器学习中,P
往往用来表示样本的真实分布,比如[1,0,0]
表示当前样本属于第一类。Q
用来表示模型所预测的分布,比如[0.7,0.2,0.1]
直观的理解就是如果用P
来描述样本,那么就非常完美。而用Q
来描述样本,虽然可以大致描述,但是不是那么的完美,信息量不足,需要额外的一些“信息增量”才能达到和P
一样完美的描述。如果我们的Q
通过反复训练,也能完美的描述样本,那么就不再需要额外的“信息增量”,Q
等价于P
。
反向传播
我们通过前向传播能够求出损失函数,而反向传播就是利用前向传播得到的损失函数对参数求梯度,即每个参数的偏导。
之所以称为反向传播,是因为我们在利用链式法则的时候对各个参数求偏导的传播顺序跟前向传播相反,如我们要求loss
对w1的偏导,则要先求出loss
对a
的偏导,再求出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
的构造函数中有四个常用的参数可以设置:
solver
:MLP
的求解方法lbfs
在小数据上表现较好,adam
较为鲁棒,sgd
在参数调整较优时会有最佳表现(分类效果与迭代次数);sgd
标识随机梯度下降;
alpha
:正则项系数,默认为L2
正则化,具体参数需要调整;
hidden_layer_sizes
:hidden_layer_sizes=(3, 2)
设置隐藏层size
为2
层隐藏层,第一层3
个神经元,第二层2
个神经元。
max_iter
:最大训练轮数。
和sklearn
中其他分类器一样,MLPClassifier
类中的fit
函数用于训练模型,fit
函数有两个向量输入:
X
:大小为**[样本数量,特征数量]**的ndarray
,存放训练样本;
Y
:值为整型,大小为**[样本数量]**的ndarray
,存放训练样本的分类标签。
MLPClassifier
类中的predict
函数用于预测,返回预测标签,predict
函数有一个向量输入:
X
:大小为**[样本数量,特征数量]**的ndarray
,存放预测样本。
MLPClassifier
的使用代码如下:
from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(solver='lbfgs',max_iter =10,
alpha=1e-5,hidden_layer_sizes=(3,2))
mlp.fit(X_train, Y_train)
result = mlp.predict(X_test)
编程要求
使用sklearn
构建神经网络模型,利用训练集数据与训练标签对模型进行训练,然后使用训练好的模型对测试集数据进行预测,并将预测结果保存到./step2/result.csv
中。保存格式如下:
测试说明
我们会获取你的预测结果与真实标签对比,预测正确率高于95%
视为过关。
第3关任务——代码题
#encoding=utf8
import os
import pandas as pd
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')
from sklearn.neural_network import MLPClassifier#调用MLP
mlp = MLPClassifier(solver='lbfgs',max_iter =30,
alpha=1e-4,hidden_layer_sizes=(20,))#
mlp.fit(train_data, train_label)
result = mlp.predict(test_data)
save_df = pd.DataFrame({'result':result})
save_df.to_csv('./step2/result.csv',index=0)#保存
运行之后会报错,如图所示,报错后把代码全部注释掉再提交就好了
第4关:使用pytorch搭建卷积神经网络识别手写数字
任务描述
本关任务:使用pytorch
搭建出卷积神经网络并对手写数字进行识别。
相关知识
为了完成本关任务,你需要掌握:1.卷积神经网络,2.pytorch
构建卷积神经网络项目流程。
卷积神经网络
为什么使用卷积神经网络
卷积神经网络最早主要用来处理图像信息。如果用全连接前馈网络来处理图像时,会存在以下两个问题:
参数太多:如果输入图像大小为100 × 100 × 3。在全连接前馈网络中,第一个隐藏层的每个神经元到输入层都有 30000个相互独立的连接,每个连接都对应一个权重参数。随着隐藏层神经元数量的增多,参数的规模也会急剧增加。这会导致整个神经网络的训练效率会非常低,也很容易出现过拟合。
局部不变性特征: 自然图像中的物体都具有局部不变性特征,比如在尺度缩放、平移、旋转等操作不影响其语义信息。而全连接前馈网络很难提取这些局部不变特征,一般需要进行数据增强来提高性能。
卷积神经网络是一种具有局部连接、 权重共享等特性的深层前馈神经网络。想要识别图像中的物体,就需要提取出比较好的特征,该特征应能很好地描述想要识别的物体。所以物体的特征提取是一项非常重要的工作。而图像中物体的特征以下几种特点:
1.物体的特征可能只占图像中的一小部分。比如下图中狗的鼻子只是图像中很小的一部分。
2.同样的特征可能出现在不同图像中的不同位置,比如下图中狗的鼻子在两幅图中出现的位置不同。
3.缩放图像的大小对物体特征的影响可能不大,比如下图是缩小后的图,但依然能很清楚的辨认出狗的鼻子。
而卷积神经网络中的卷积与池化操作能够较好地抓住物体特征的以上3
种特点。
卷积
卷积说白了就是有一个卷积核(其实就是一个带权值的滑动窗口)在图像上从左到右,从上到下地扫描,每次扫描的时候都会将卷积核里的值所构成的矩阵与图像被卷积核覆盖的像素值矩阵做内积。整个过程如下图所示,其中黄色方框代表卷积核,绿色部分代表单通道图像,红色部分代表卷积计算后的结果,通常称为特征图:
那为什么说卷积能够提取图像中物体的特征呢?其实很好理解,上图中的卷积核中值的分布如下:
当这个卷积核卷积的时候就会在3行3列的小范围内计算出图像中几乎所有的3
行3
列子图像与卷积核的相似程度(也就是内积的计算结果)。相似程度越高说明该区域中的像素值与卷积核越相似。(上图的特征图中值为4
的位置所对应到的源图像子区域中像素值的分布与卷积核值的分布最为接近)这也就说明了卷积在提取特征时能够考虑到特征可能只占图像的一小部分,以及同样的特征可能出现在不同的图像中不同的位置这两个特点。
PS:卷积核的值是怎么确定下来的?很明显是训练出来的!
池化
池化就是将输入图像进行缩小,减少像素信息,只保留重要信息。 池化的操作也很简单,通常情况下,池化区域是2
行2
列的大小,然后按一定规则转换成相应的值,例如最常用的最大池化(max pooling
)。最大池化保留了每一小块内的最大值,也就是相当于保留了这一块最佳的匹配结果。举个例子,如下图中图像是4
行4
列的,池化区域是2
行2
列的,所以最终池化后的特征图是2
行2
列的。图像中粉色区域最大的值是6
,所以池化后特征图中粉色位置的值是6
,图像中绿色区域最大的值是8
,所以池化后特征图中绿色位置的值是8
,以此类推。
从上图可以看出,最大池化不仅仅缩小了图像的大小,减少后续卷积的计算量,而且保留了最佳的特征(如果图像是经过卷积后的特征图)。也就相当于把图缩小了,但主要特征还在,这就考虑到了缩放图像的大小对物体的特征影响可能不大的特点。
全连接网络
卷积与池化能够很好的提取图像中物体的特征,当提取好特征之后就可以着手开始使用全连接网络来进行分类了。全连接网络的大致结构如下:
其中输入层通常指的是对图像进行卷积,池化等计算之后并进行扁平后的特征图。隐藏层中每个方块代表一个神经元,每一个神经元可以看成是一个很简单的线性分类器和激活函数的组合。输出层中神经元的数量一般为标签类别的数量,激活函数为softmax
(因为将该图像是猫或者狗的得分进行概率化)。因此我们可以讲全连接网络理解成很多个简单的分类器的组合,来构建成一个非常强大的分类器。
卷积神经网络大致结构
将卷积,池化,全连接网络进行合理的组合,就能构建出属于自己的神经网络来识别图像中是猫还是狗。通常来说卷积,池化可以多叠加几层用来提取特征,然后接上一个全连接网络来进行分类。大致结构如下:
pytorch构建卷积神经网络项目流程
数据集介绍与加载数据
本次使用数据集为mnist
手写数字数据集,简单来讲就是如下的东西:
数据集分为训练集与测试集,训练集中一共有60000
张图片,测试集中一共有10000
张图片,每张图片大小为28X28x1
。图片标签为对应的数字,如8
对应的label
为8
,若使用onehot编码则对应的label
为:[0,0,0,0,0,0,0,0,1,0]
。 为节约计算时间,我们取训练集中的6000
张图片用来训练,测试集中的600
张进行测试。使用pytorch
加载数据集方法如下:
#加载数据
import torchvision
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
构建模型
加载好数据集,就需要构建卷积神经网络模型:
#构建卷积神经网络模型
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()
需要指出的几个地方:
class CNN需要继承Module
需要调用父类的构造方法:super(CNN, self).__init__()
在Pytorch中激活函数Relu也算是一层layer
需要实现forward()方法,用于网络的前向传播,而反向传播只需要调用Variable.backward()即可。
定义好模型后还要构建优化器与损失函数: torch.optim
是一个实现了各种优化算法的库。使用方法如下:
#SGD表示使用随机梯度下降方法,lr为学习率,momentum为动量项系数
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum=0.9)
#交叉熵损失函数
loss_func = nn.CrossEntropyLoss()
训练模型
在定义好模型后,就可以根据反向传播计算出来的梯度,对模型参数进行更新,在pytorch中实现部分代码如下:
#将梯度清零
optimizer.zero_grad()
#对损失函数进行反向传播
loss.backward()
#训练
optimizer.step()
保存模型
在pytorch
中使用torch.save
保存模型,有两种方法,第一种: 保存整个模型和参数,方法如下:
torch.save(model, PATH)
第二种为官方推荐,只保存模型的参数,方法如下:
torch.save(model.state_dict(), PATH)
加载模型
对应两种保存模型的方法,加载模型也有两种方法,第一种如下:
model = torch.load(PATH)
第二种:
#CNN()为你搭建的模型
model = CNN()
model.load_state_dict(torch.load(PATH))
如果要对加载的模型进行测试,需将模型切换为验证模式
model.eval()
编程要求
使用pytorch
搭建出卷积神经网络模型,再对模型进行训练,并将训练好的模型保存至./step3/cnn.pkl
中。
测试说明
我们会加载你训练好的模型,并对测试集数据进行预测,预测正确率高于85%
视为过关。
提示:平台使用torch
版本为0.4
,input
需要为Variable
类型,使用代码如下:
#mini_batch
train_loader = Data.DataLoader(dataset=train_data, batch_size=64, shuffle=True)
第4关任务——代码题
#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)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = x.view(x.size(0), -1)
output = self.out(x)
return output
cnn = CNN()
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')