人工神经网络(artificial neural network,缩写ANN),简称神经网络(neural network,缩写NN)或类神经网络,是一种模仿生物神经网络(动物的中枢神经系统,特别是大脑)的结构和功能的数学模型或计算模型,用于对函数进行估计或近似。
神经网络主要由:输入层,隐藏层,输出层构成。当隐藏层只有一层时,该网络为两层神经网络,由于输入层未做任何变换,可以不看做单独的一层。实际中,网络输入层的每个神经元代表了一个特征,输出层个数代表了分类标签的个数(在做二分类时,如果采用sigmoid分类器,输出层的神经元个数为1个;如果采用softmax分类器,输出层神经元个数为2个;如果是多分类问题,即输出类别>=3时,输出层神经元为类别个数),而隐藏层层数以及隐藏层神经元是由人工设定。一个基本的两层神经网络可见下图(注意:说神经网络多少层数的时候一般不包括输入层。 在神经网络中的激活主要讲的是梯度的更新的激活):
神经网络的最基本的构成元素是神经元(Neuron),也就是Kohonen的定义中的简单单元。学过生物的都知道,人的大脑中有上亿个神经元构成神经网络,生物神经网络中各个网络之间相互连接,通过神经递质相互传递信息。如果某个神经元接收了足够多的神经递质(乙酰胆碱),那么其点位变会积累地足够高,从而超过某个阈值(Threshold)。超过这个阈值之后,这个神经元变会被激活,达到兴奋的状态,而后发送神经递质给其他的神经元。
1943年,McCulloch和Pitts将生物神经网络工作的原理抽象成了一个简单的机器学习模型——MP神经元模型。
感知机(Perceptron)是由两层神经元所构成。
如图所示,输入层接收输入信号之后传输给输出层,输出层即阈值逻辑单元(MP神经元)。通过感知机可以轻易地实现逻辑与、或、非运算。
首先,神经网络应用在分类问题中效果很好。 工业界中分类问题居多。LR 或者 linear SVM 更适用线性分类。如果数据非线性可分(现实生活中多是非线性的),LR 通常需要靠特征工程做特征映射,增加高斯项或者组合项;SVM需要选择核。 而增加高斯项、组合项会产生很多没有用的维度,增加计算量。GBDT 可以使用弱的线性分类器组合成强分类器,但维度很高时效果可能并不好。而神经网络在三层及以上时,能够很好地进行非线性可分。
这里将以MINST数据集为例,实现手写数字的识别。
网络的结构如下,输入层一共28*28=784个神经元,隐藏层共15个神经元(这里可以自由设置),输出层一个十个神经元,分别代表十个数字(0~9)。
对于上图中的神经网络结构,假设我们随机初始化各个神经元的权值,并且将手写数字图片”9“传入到网络中,可以预见网络大概率并不会得到正确答案,但是我们可以通过不断调整权值来让它输出正确的结果。对于简单的模型也许这样做是可行的,因为毕竟可以调整的权重数据很少,但是对于我们定义的网络,它其中包含了784x15+15x10+15+10 = 11935个参数,手动修改就很不现实。
因此我们希望能够设计出一种学习算法,帮助我们自动调整感知器中的权重和偏置,以至于网络的输出能够拟合所有的训练输入。因此我们定义一个损失函数:
a代表的是神经网络的输出,y(x)代表的是输入x的标签。(前面的1/2是为了损失函数求导方便)
我们的目的,就是要最小化代价函数,代价函数越接近0,识别的效果就越好。
梯度下降的目的就是最小化代价函数。梯度表示的是各点处的函数值减小最多的方向,每次朝着梯度的方向,能够最大限度的减少函数的值。
因此,在寻找函数的最小值(或者尽可能小的值)的位置的任务中,要以梯度的信息为线索,决定前进的方向。在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进。像这样,通过不断地沿梯度方向前进, 逐渐减小函数值的过程就是梯度法(gradient method)。
因此我们就有了下面这个更新w,b的式子。
η为学习率。它的主要功能是用来控制每次变化的多少。如果太大,可能就会错过最低点;如果太小,每次变化的又太少,则需要花费很多的时间。因此选择一个合适的值非常重要。
反向传播(Backpropagation)是神经网络中一种用于训练模型的常用算法。它通过计算模型输出与真实值之间的误差,并将误差从输出层向输入层进行传播,以更新模型的权重和偏置。
下面是反向传播算法的简要步骤:
初始化:随机初始化神经网络的权重和偏置。
前向传播:将输入样本通过神经网络,按照顺序进行计算,直到获得输出。每个神经元接收来自上一层的输入,并通过激活函数计算输出。
计算误差:将神经网络的输出与真实值进行比较,计算误差(通常使用损失函数来度量误差)。
反向传播:从输出层开始,将误差从后一层传递到前一层。通过链式法则,计算每个神经元对误差的贡献,并将误差传递到上一层。
权重更新:使用梯度下降法则,根据每个权重对误差的贡献来更新神经网络的权重。梯度下降使用误差对权重的偏导数来确定每个权重的调整方向和大小。
重复步骤2-5:重复执行前向传播、误差计算、反向传播和权重更新的步骤,直到达到停止条件(例如,达到最大迭代次数或误差低于某个阈值)。
通过多次迭代上述步骤,神经网络可以逐渐优化权重和偏置,使其能够更好地拟合训练数据,并在新的输入上进行准确的预测。
反向传播是一种高效的训练神经网络的方法,它使得神经网络能够学习复杂的非线性关系。然而,反向传播算法也存在一些挑战,如梯度消失和过拟合等问题,针对这些问题,研究者们提出了一些改进的方法和技术。
导入相关包
import torch
import torchvision
import torch.nn as nn
import torchvision.transforms as transforms
配置训练参数
#超参数
num_classes = 10 #类别个数
num_epochs = 10 #迭代次数/训练次数
learning_rate = 0.01 #学习率,方法更新的速率
batch_size = 64 #批次,批量大小,每次传输图片的个数
下载数据集
#训练集
train_dataset = torchvision.datasets.MNIST(root = './data',
train = True,
download = True,
transform = transforms.Compose([
transforms.Resize((28,28)),transforms.ToTensor()])
)
#测试集
test_dataset = torchvision.datasets.MNIST(root = './data',
train = False,
download = True,
transform = transforms.Compose([
transforms.Resize((28,28)),transforms.ToTensor()])
)
加载训练与测试数据,对图片进行分组、打乱顺序
#加载训练数据,对图片进行分组、打乱顺序
train_loader = torch.utils.data.DataLoader(dataset = train_dataset,batch_size = batch_size,shuffle = True)
#加载测试数据,对图片进行分组、打乱顺序
test_loader = torch.utils.data.DataLoader(dataset = test_dataset,batch_size = batch_size,shuffle = True)
查看数据集和测试集的内容数量
定义网络结构
# 定义网络结构
class network(nn.Module):
def __init__(self,num_classes):
super(network, self).__init__()
#继承父类,即此处的nn.Module
self.fc1 = nn.Linear(784,64)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(64,num_classes)
#前向传播
def forward(self, x):
x = x.view(x.size()[0], -1)
#x.view(x.size()[0], -1)将前面多维度的tensor展平成一维
x = self.fc1(x)
x = self.fc2(x)
return x
配置优化器
model = network(num_classes).cpu() #让网络模型在cpu上运行
loss = nn.CrossEntropyLoss() #交叉熵损失函数,返回值是误差,用以表示模型算法模型对图片识别的结果与图片本身的标签之间的偏差
optimizer = torch.optim.Adam(model.parameters(),learning_rate)
#优化器:优化参数,包括w和b,更新w和吧,使得算法的输出结果更贴近正确结果
进行模型训练
#训练
group = len(train_loader) #获取图片组数
for epoch in range(num_epochs): #num_epochs = 10,epoch是0-9,range(10)是0到9的数字序列
# i = 0
for i,(images,labels) in enumerate(train_loader):
images = images.cpu()
labels = labels.cpu()
#forward前向传播
output = model(images) #获取输出
cost = loss(output,labels) #获取误差
#反向传播
optimizer.zero_grad() #清空梯度存储空间
cost.backward() #对loss进行求导(求梯度)
optimizer.step() # w = w - learning_rate * 偏w , b = b - learning_rate * 偏b
if (i + 1) == 400:
print("epoch:[{}/{}],step:[{}/{}],loss:{:.4f}".format(epoch+1, num_epochs, i+1, group, cost.item()))
# i += 1
训练时过程如下
数据测试
#测试
with torch.no_grad(): #测试阶段不需要梯度更新(参数更新),即:w和b不需要求导求梯度,因此不需要grad
correct = 0 # 存储识别正确的图片张数
for images,labels in test_loader:
images = images.cpu()
labels = labels.cpu()
#前向传播
output = model(images) #output仅仅是模型的输出,形式是10个张量类型的数据tensor([])
_,predict = torch.max(output,1) # predict是确切的某一个类,预测结果
#torch.max(x,1) 返回的是两个结果,第一个是x里的最大值,第二个是最大值所在的位置(索引)
#最大值所在的位置(索引)才是预测的类
#比如图片0,torch.max(output,1)结果是tensor([22,13,10,11,5,19,14,0,4,1]),最大值是22,22所在位置是0,则predict = 0
correct += (predict == labels).sum().item() # (predict == labels).sum().item()的结果是每一组图片中预测正确的图片数(识别正确的数量)
accruacy = correct / len(test_dataset)
print("Accuracy:{}%".format(accruacy * 100))
对于手写体识别数据集,经过三层全连接神经网络,经过10次的迭代周期之后,最终达到了98.76%的识别正确率
之后我们借用此网络但将数据集换为cifar10,对于此数据集,训练效果不佳,简单更改网络结构和训练参数后,仅使用全连接神经网络的训练效果仅可达到52%
针对上一节末尾提出的问题,下面将加入卷积神经网络,对cifar10数据集的训练进行优化
卷积神经网络(Convolutional Neural Network,简称CNN)是一种主要用于图像处理和模式识别任务的深度学习模型。它通过模拟人类视觉系统的工作原理,能够自动学习图像中的特征和模式。
CNN主要由以下几个组件构成:
卷积层(Convolutional Layer):卷积层是CNN的核心组件。它通过使用一系列可学习的滤波器(也称为卷积核或特征检测器),在输入图像上进行滑动窗口式的卷积操作。每个滤波器会提取出图像中的不同特征,例如边缘、纹理等。卷积操作可以有效地捕捉局部特征,并保持平移不变性。
激活函数(Activation Function):卷积层的输出经过激活函数进行非线性映射,以引入非线性关系。常用的激活函数包括ReLU(Rectified Linear Unit)、Sigmoid和Tanh等。ReLU是最常用的激活函数,它能够保留正数输入并将负数输入归零。
池化层(Pooling Layer):池化层用于减少特征图的空间尺寸,并降低计算量。常用的池化操作是最大池化(Max Pooling),它在每个池化窗口中选择最大值作为输出。最大池化能够保留主要特征并提高空间不变性。
全连接层(Fully Connected Layer):在经过一系列卷积层和池化层之后,通常会添加全连接层。全连接层将前一层的所有神经元与当前层的每个神经元相连接,用于对特征进行分类和预测。
CNN的训练过程通常采用反向传播算法。通过将输入样本传递给网络,并与标签进行比较,计算预测值与真实值之间的误差。然后,通过反向传播算法逐层更新网络中的权重和偏置,以减小误差,并提高模型的准确性。
卷积神经网络在计算机视觉领域取得了巨大的成功。它能够自动学习图像的特征表示,从而实现图像分类、目标检测、图像分割等任务。此外,CNN还具有参数共享和稀疏连接等特性,使得它能够处理大规模图像数据并具有较好的泛化能力。
我们仍以手写体识别的代码进行改进
修改数据下载部分的代码,改完使用cifar10数据集
#训练集
train_dataset = torchvision.datasets.CIFAR10(root = './data',#存储位置
train = True,#是否是训练操作
download = True,#是否下载训练集
transform =transforms.ToTensor()
)
test_dataset = torchvision.datasets.CIFAR10(root = './data',
train = False,
download = True,
transform = transforms.ToTensor())
通过查看此数据集的简介,发现这个数据集的数据大小均一致,所以不必对图像大小进行处理
训练集与测试集数据数量如下
# 定义网络结构
class network(nn.Module):
def __init__(self,num_classes):
super(network, self).__init__()
#继承父类,即此处的nn.Module
self.conv1 = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2)
)
# 第一个卷积层后的大小:int((32-3+2*1)/1+1) = 32,第一个最大池化结果:int((32-2)/2+1) = 16
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2)
)
# 第二个卷积层后的大小:int((16-3+2*1)/1+1) = 16,第二个最大池化结果:int((16-2)/2+1) = 8
self.conv3 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2, 2)
)
# 第三个卷积层后的大小:int((8-3+2*1)/1+1) = 8,第三个最大池化结果:int((8-2)/2+1) = 4
self.fc1 = nn.Linear(64 * 4 * 4, 256)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, num_classes)
#前向传播
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = x.view(x.size(0), -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
return x
添加三个卷积层作为网络结构
配置优化器
model = network(num_classes).cpu() #让网络模型在cpu上运行
loss = nn.CrossEntropyLoss() #交叉熵损失函数,返回值是误差,用以表示模型算法模型对图片识别的结果与图片本身的标签之间的偏差
optimizer = torch.optim.Adam(model.parameters(),learning_rate)
#优化器:优化参数,包括w和b,更新w和吧,使得算法的输出结果更贴近正确结果
进行模型训练
#训练
group = len(train_loader) #获取图片组数
epoch_arr = []
loss_arr = []
for epoch in range(num_epochs): #num_epochs = 10,epoch是0-9,range(10)是0到9的数字序列
# i = 0
epoch_arr.append(epoch)
for i,(images,labels) in enumerate(train_loader):
images = images.cpu()
labels = labels.cpu()
#forward前向传播
output = model(images) #获取输出
cost = loss(output,labels) #获取误差
#反向传播
optimizer.zero_grad() #清空梯度存储空间b
cost.backward() #对loss进行求偏导(求梯度) ,偏w,偏b
optimizer.step() # w = w - learning_rate * 偏w , b = b - learning_rate * 偏b
if (i + 1) == 400:
loss_arr.append(cost.item())
print("epoch:[{}/{}],step:[{}/{}],loss:{:.4f}".format(epoch+1, num_epochs,i + 1, group,cost.item()))
# i += 1
# cost返回结果:tensor()
模型测试结果如下,
与之前使用全连接神经网络的对比,发现卷积神经网络对于cifar10数据集的训练,准确度有明显的提升。
在本博客中,我们初步了解了神经网络,并对全连接神经网络(FCN)和卷积神经网络(CNN)进行了介绍和简单实现。
我们了解了全连接神经网络的基本结构。全连接神经网络由多个神经元组成,每个神经元都与前一层的所有神经元相连接。我们了解了前向传播、反向传播以及权重更新的基本步骤,这是训练全连接神经网络的关键。
接下来,我们学习了卷积神经网络的特点和应用。卷积神经网络通过使用卷积层、激活函数和池化层,能够更好地捕捉图像中的局部特征并保持空间不变性。我们了解了卷积操作的原理以及最大池化的作用,这些都对于图像处理任务非常重要。
初步认识了神经网络,探讨了全连接神经网络和卷积神经网络的原理和应用。神经网络作为一种强大的机器学习模型,具有广泛的应用领域,特别是在计算机视觉任务中取得了巨大的成功。通过进一步学习和实践,我们可以深入了解神经网络的更高级概念和技术,进一步提升我们在这个领域的能力和应用水平。