[2022-09-26]神经网络与深度学习第3章-前馈神经网络(part1)

前馈神经网络(part1)

写在开头

经过前面对于线性模型的回归与分类问题的学习,相信大家对于神经网络框架已经较为熟悉,本章我们将使用类似生物学中神经元的结构,通过前馈神经网络,进行问题的求解。

神经元

神经元大家肯定是都知道的,在生物学意义上本人做出不准确定义:神经元有若干树突用于接收来自其他神经元的电信号,再通过细胞体的一系列处理,通过轴突发出结果信号。抽象到计算机上,神经元结构通过对一组输入给予不同权重,进行求和后加入激活函数用于得到该神经元最终的输出。
为了好看插张图

净活性值

这个概念看起来非常牛逼,其实就是在神经元的值进入激活函数前的那个值。我们定义一个神经元有d个输入 x 1 … x d x_1 \dots x_d x1xd,每个输入有权重 w i , i ∈ [ 1 , d ] 且 i ∈ N w_i, i∈[1,d]且i∈ N wi,i[1,d]iN,偏置值 b b b,可得净活性值:
z = ∑ i = 1 d w i x i + b z=\sum_{i=1}^{d}w_ix_i+b z=i=1dwixi+b
净活性值经过非线性函数 f ( ⋅ ) f(·) f()后得到活性值 a a a
对于计算净活性值,我们这边依然用到了torch.nn.Linear,因为净活性值的计算与线性函数计算相同。

x = torch.tensor([1,1,4,5,1,4],dtype=torch.float)
lin = torch.nn.Linear(6,1)
print('weights : {}'.format(lin.weight.data))
print('bia : {}'.format(lin.bias.data))
print('out : {}'.format(lin(x).detach()))

在这里插入图片描述

  • 思考:加权求和与仿射变换之间有什么区别和联系?
    答:联系:加权求和可以看作就是仿射变换,都有对每个输入加权求和的过程。区别:仿射变换在计算时对计算矩阵额外增加一行[0,0,…,0,1],对列向量底部增加一个1。可以看作是笛卡尔坐标系向齐次坐标系转换后进行计算。

激活函数

得到了净活性值后,我们需要像真的神经元有阈值一样,为该值增加一个激活函数用于控制输出。激活函数通常为非线性函数,可以增强神经网络的表示能力和学习能力。

以下所有的激活函数,在文中并未放置其公式,具体函数参见网页链接

Sigmoid(S)型函数

S型函数主要特征为函数图像呈现S的形状,其有Logistic函数和Tanh函数。可视化代码和效果如下:

x = torch.linspace(-10.,10.)

fig, (ax0,ax1) = plt.subplots(2,1,figsize=(10,10))
ax0.set_title('Logistic')
ax1.set_title('Tanh')
ax0.set_yticks((-1,0,1))
ax1.set_yticks((-1,0,1))
ax0.plot(x,torch.sigmoid(x))
ax1.plot(x,torch.tanh(x))

在这里插入图片描述

ReLU型函数

ReLU函数常见的为ReLU、LeakyReLU。这两个函数区别为当输入小于0时,LeakyReLU会有一个非常小的值(泄露就是在这儿),他们在大于等于0的地方函数处处相同。

x = torch.linspace(-10.,2.)

fig, (ax0,ax1) = plt.subplots(2,1,figsize=(10,10))
ax0.set_title('ReLU')
ax1.set_title('LeakyReLU')
ax0.plot(x,torch.relu(x))
ax1.plot(x,torch.nn.LeakyReLU()(x))

在这里插入图片描述

其他函数

书上几乎所有的函数,在torch中均有函数封装,如果要自己造轮子的话,可以按照书上的公式敲一遍代码,这边仅使用torch内置函数进行绘制,这边只放部分,造轮子内容在github。

x = torch.linspace(-10.,10.)
fig,*axs=plt.subplots(4,2,figsize=(10,15))
axs[0][0][0].plot(x,torch.nn.Hardsigmoid()(x))
axs[0][0][0].set_title('Hard-Logistic')

axs[0][0][1].plot(x,torch.nn.Hardtanh()(x))
axs[0][0][1].set_title('Hard-Tanh')

axs[0][1][0].plot(x,torch.nn.ELU()(x))
axs[0][1][0].set_title('ELU')

axs[0][1][1].plot(x,torch.nn.Softplus()(x))
axs[0][1][1].set_title('Softplus')

axs[0][2][0].plot(x,torch.nn.CELU()(x))
axs[0][2][0].set_title('CELU')

axs[0][2][1].plot(x,torch.nn.SiLU()(x))
axs[0][2][1].set_title('Swish(SiLU)')

axs[0][3][0].plot(x,torch.nn.Hardswish()(x))
axs[0][3][0].set_title('Hard-swish')

axs[0][3][1].plot(x,torch.nn.Mish()(x))
axs[0][3][1].set_title('Mish')

在这里插入图片描述

基于前馈神经网络的二分类任务

数据集构建

数据集使用第3.1.1节中构建的二分类数据集:Moon1000数据集,其中训练集640条、验证集160条、测试集200条。该数据集的数据是从两个带噪音的弯月形状数据分布中采样得到,每个样本包含2个特征。
本章不再重复该部分代码。结果如下:
在这里插入图片描述

模型构建

实例化一个两层的前馈网络,令其输入层维度为5,隐藏层维度为10,输出层维度为1。
并随机生成一条长度为5的数据输入两层神经网络,观察输出结果。
根据题意,很容易得到模型构建的代码。这边直接使用torch框架:

class Model(torch.nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.inputs = torch.nn.Linear(2,5)
        self.activ1 = torch.nn.Sigmoid()
        self.hidden = torch.nn.Linear(5,10)
        self.activ2 = torch.nn.Sigmoid()
        self.output = torch.nn.Linear(10,1)
        self.activ3 = torch.nn.Sigmoid()
    def forward(self,x):
        x = self.inputs(x)
        x = self.activ1(x)
        x = self.hidden(x)
        x = self.activ2(x)
        x = self.output(x)
        return self.activ3(x)

model = Model()

我们同时也可以使用torch.nn.Sequential来进行模型构建。

损失函数

这次的损失函数使用和前面Logistic回归实验内容相同,这边不再赘述。

crite = torch.nn.BCELoss()

模型优化

这边的神经网络模型,已经拥有不止一层的计算,这样的模型结构类似下图,每个线上面都有一个权值,因此对于误差的反向传播,我们要将所有值更新一遍,之前线性分类所使用的简单求导显然没法照搬使用。
在这里插入图片描述

反向传播算法

我们在反向传播时,需要一层一层地对参数进行更新,由此我们定义优化的类SGD如下:

class SGD():
    def __init__(self, lr=0.01, momentum=0):
        self.lr = lr 
        self.momentum = momentum
        self.w_ = None

    def update(self, w, grad):
        if self.w_ is None:
            self.w_ = np.zeros(np.shape(w))
        self.w_ = self.momentum * self.w_ + (1 - self.momentum) * grad
        return w - self.lr * self.w_

其中的梯度值grad逆着网络进行反向传播,然后每层都记录下梯度,更新参数并反向不断更新。
我们当然也可以直接使用torch的SGD优化器,该优化器本身就支持神经网络:

optim = torch.optim.SGD(model.parameters(),0.01)

上述求导方式如同链条,因此我们称其为链式求导法则,该参数更新过程被称为反向传播。具体内容和实例将更新至hw3中。

损失函数

交叉熵损失函数根据其函数对于输出的偏导,可得求导函数代码如下:

def backward(self, y, p):
    p = np.clip(p, 1e-15, 1 - 1e-15)
    return - (y / p) + (1 - y) / (1 - p)
Logistic算子

Logistic函数输出对于输入的求导 f ′ ( x ) = f ( x ) ( 1 − f ( x ) ) f'(x)=f(x)(1-f(x)) f(x)=f(x)(1f(x)),非常清楚简单,其反向代码如下:

def backward(self, x):
    return self.__call__(x) * (1 - self.__call__(x))
线性层

计算线性层的梯度,通过计算其后一层对于每个权重的偏导数可得。
其代码如下:

def grad(self, next_back_grad):
    W = self.W
    grad_w = self.layer_input.T.dot(next_back_grad)
    grad_w0 = np.sum(next_back_grad, axis=0, keepdims=True)
    self.W = self.W_opt.update(self.W, grad_w)
    self.w0 = self.w0_opt.update(self.w0, grad_w0)
    next_back_grad = next_back_grad.dot(W.T)
    return next_back_grad # 记录累计的梯度
整个网络

整个网络的构建由前向的结果输出和反向的链式求导参数更新组成,我们这边以前面构建的torch模型为例,前向计算为将每一层按照全连接的方式连接,并且按照顺序进入各个神经元并输出,一层神经元的输出又作为下一层的输入重复过程,最终得到输出结果。

优化器

优化器的轮子已经在前面的反向传播算法内容中由SGD类构建完成,在此不再赘述。

完善Runner类

在自造轮子的Runner类中,我们增加按层进行反向传播的函数:

def backward(self, loss_grad):
    for layer in reversed(self.layers):
        loss_grad = layer.backward(loss_grad)

其中layer为前面所有算子、线性函数类的父类,其中包含反向传播的backward函数,reversed用于将层倒过来(毕竟是反向传播)。
保存参数我们选择将每一层的内容当作一个字典,按照如下类似json的格式进行存储:

numpy.array([
{'name':'layer1',
 'type':'该层种类',
 'specific':{
     //各种属于种类的特殊属性,如输入,输出,偏置,权重等
     }
}],
[//layer2],
...,
])

模型训练

使用训练集和验证集进行模型训练,共训练2000个epoch。评价指标为accuracy。
这边代码和以前别无二异,依然直接使用Runner类,这边不再赘述,直接给出训练过程和结果。
在这里插入图片描述

在这里插入图片描述

性能评价

性能评价的代码和以前也是一样滴uvu:


out = model(dataset_test[:,:-1]).squeeze()
acc = accuracy(out,dataset_test[:,-1])
for i in range(out.shape[0]):
    plt.scatter(dataset_test[i,0],dataset_test[i,1],c=['red','green'][torch.round(out[i]) == torch.round(dataset_test[i,-1])])
loss = crite(dataset_test[:,-1],out)
print('acc = {}, loss = {}'.format(acc,loss))

结果:
在这里插入图片描述

对比思考

  • 3.1 基于Logistic回归的二分类任务 4.2 基于前馈神经网络的二分类任务,谈谈自己的看法。
    共同点:
    首先:Logistic回归和本次基于前馈神经网络的二分类都是二分类任务(送一点分😔);
    其次:使用的最终激活函数、误差计算函数和优化方法本质上相同。
    不同点:
    一:Logistic回归比较适合线性可分的样本,对于异或问题这类无法解决,而前馈神经网络则能够很好地解决这个问题(能以任意精度拟合任意复杂函数);
    二:Logistic回归相当于单层的多输入单输出的全连接神经网络,而反之则是多个Logistic回归(如果用Logistic回归)的层叠复合。

写在最后

本次实验我们实现了功能更强、结构更复杂、计算参数更多的前馈神经网络,了解了其基本性质和很牛的计算方法,理解本次实验内容有助于我们理解神经网络的一般结构和原理,以及优化的计算和应用。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值