《Pytorch深度学习和图神经网络(卷 1)》学习笔记——第七章

news/2024/11/30 6:00:03/

这一章内容有点丰富,多用了一些时间,实例就有四五个。
这章内容是真多啊!(学完之后又回到开头感叹)

大脑分级处理机制:

将图像从基础像素到局部信息再到整体信息
即将图片由低级特征到高级特征进行逐级计算,逐级累计。

视觉神经网络中的离散积分

计算机中对图片的处理可以理解为离散微积分的过程。
利用卷积操作对图片的局部信息处理,生成低级特征
对低级特征进行多次卷积操作,生成中级特征、高级特征
将多个局部信息的高级特征组合到一起,生成最终的解释结果
这就是卷积神经网络

比全连接权重更少,对数据进行基于区域小规模运算,改善了难收敛,提高了泛化能力。

可以用全连接网络为参照,卷积神经网络更像是多个全连接片段的组合。

卷积网络输出的每个节点,都是原数据中局部区域节点经过神经元计算后得到的结果。
全连接网络输出的每个节点,都是原数据中全部节点经过神经元计算后得到的结果。
因此卷积神经网络包含的局部信息更为明显,在计算机视觉领域被广泛应用。

1D卷积常用来处理文本或特征数值类数据
2D卷积常用来处理平面图片类数据
3D卷积常用来处理立体图像或视频类数据

实例分析:Sobel算子的原理
Sobel算子其实是卷积操作中的一个典型例子,手动配置好权重的卷积核,实现图片的边缘检测。
a为水平 b为垂直方向

a=[[-1,0,1],[-2,0,2],[-1,0,1]]
b=[[1,2,1],[0,0,0],[-1,-2,1]]

新生成的像素值不一定在0到256之间,要进行归一化然后乘256。

sobel算子原理

第一行(-1,0,-1)的卷积核进行1D卷积时,本质上是计算相隔像素之间的距离。
1256788卷积后44221,少了2位因为核的缘故。
如果将像素差值数据用图片的方式显示出来,就是轮廓图片。
第二行(-2,0,2)的同上,只不过将距离放大2倍,起到增强效果。
思想是,对卷积核3行像素差值再做加权处理,以第二行像素差值为中心,距离中心点近影响越大的原理,对第二行加强,在结果中产生主要影响。
第二行与第一行相同也可以产生轮廓,OpenCV中有scharr函数…实sobel变了权重。
垂直其实就是水平算子的转置。

深层神经网络中的卷积核

这时的卷积核是经过大量样本训练之后计算出来的,若生成了若干个有特定功能的卷积核,有的计算像素差值,提取轮廓特征;有的计算平均值,提取纹理特征。

卷积分

y = 3 x + 2. y = 3x+2. y=3x+2.
y = 2 x 2 + 3 x − 1. y = 2x^{2}+3x-1. y=2x2+3x1.
代数的角度理解是相乘
y = 6 x 3 + 13 x 2 + 3 x − 2. y = 6x^{3}+13x^{2}+3x-2. y=6x3+13x2+3x2.

卷积神经网络的实现

卷积的操作类型:
窄卷积、同卷积、全卷积。
计算规则:
H o u t = H i n + 2 × p a d d i n g [ 0 ] − d i l a t i o n [ 0 ] × ( k e r n e l s i z e [ 0 ] − 1 ) − 1 s t r i d e [ 0 ] + 1. H_{out} = \frac{H_{in}+2\times padding[0]-dilation[0]\times (kernel_size[0]-1)-1}{stride[0]}+1. Hout=stride[0]Hin+2×padding[0]dilation[0]×(kernelsize[0]1)1+1.
W o u t = W i n + 2 × p a d d i n g [ 1 ] − d i l a t i o n [ 1 ] × ( k e r n e l s i z e [ 1 ] − 1 ) − 1 s t r i d e [ 1 ] + 1. W_{out} = \frac{W_{in}+2\times padding[1]-dilation[1]\times (kernel_size[1]-1)-1}{stride[1]}+1. Wout=stride[1]Win+2×padding[1]dilation[1]×(kernelsize[1]1)1+1.

实例6:卷积函数应用

观察卷积核个数和图像通道数对卷积核维度的影响。

import torch
# [batch, in_channels, in_height, in_width] [训练时一个batch的图片数量, 图像通道数, 图片高度, 图片宽度]
input1 = torch.ones([1, 1, 5, 5])
input2 = torch.ones([1, 2, 5, 5])
input3 = torch.ones([1, 1, 4, 4])
# [ out_channels, in_channels,filter_height, filter_width] [卷积核个数,图像通道数,卷积核的高度,卷积核的宽度]
filter1 =  torch.tensor([-1.0,0,0,-1]).reshape([1, 1, 2, 2])
filter2 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1]).reshape([2,1,2, 2])
filter3 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1,-1.0,0,0,-1]).reshape([3,1,2, 2])
filter4 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1,-1.0,0,0,-1,-1.0,0,0,-1]).reshape([2, 2, 2, 2])
filter5 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1]).reshape([1,2, 2, 2])print(filter1)
print(filter2)
print(filter3)
print(filter4)
print(filter5)
#tensor([[[[-1.,  0.],
#          [ 0., -1.]]]])
卷积核个数X图像通道数=1,共一个卷积核单通道
#tensor([[[[-1.,  0.],
#          [ 0., -1.]]],
#
#        [[[-1.,  0.],
#          [ 0., -1.]]]])
2X1=2,两个卷积核单通道
#tensor([[[[-1.,  0.],
#          [ 0., -1.]]],
#
#        [[[-1.,  0.],
#          [ 0., -1.]]],
#
#        [[[-1.,  0.],
#          [ 0., -1.]]]])
3X1=3,三个卷积核,单通道
#tensor([[[[-1.,  0.],
#          [ 0., -1.]],
#         [[-1.,  0.],
#          [ 0., -1.]]],
#
#        [[[-1.,  0.],
#          [ 0., -1.]],
#         [[-1.,  0.],
#          [ 0., -1.]]]])
2X2=4,两个卷积核且两个通道
#tensor([[[[-1.,  0.],
#          [ 0., -1.]],
#         [[-1.,  0.],
#          [ 0., -1.]]]])
1X2=2,一个卷积核且两个通道
[ ]里为一个通道,[[]]里为一个卷积核

观察padding填充,padding=(1,2)是(上,下)填充,padding=1,为对周围填充一圈。

#验证padding补0的规则 ——上下左右都补0
padding1 = torch.nn.functional.conv2d(input1, torch.ones([1,1,1,1]), stride=1, padding=1)
print(padding1)
#tensor([[[[0., 0., 0., 0., 0., 0., 0.],   
#          [0., 1., 1., 1., 1., 1., 0.],   
#          [0., 1., 1., 1., 1., 1., 0.],   
#          [0., 1., 1., 1., 1., 1., 0.],   
#          [0., 1., 1., 1., 1., 1., 0.],   
#          [0., 1., 1., 1., 1., 1., 0.],   
#          [0., 0., 0., 0., 0., 0., 0.]]]])
padding2 = torch.nn.functional.conv2d(input1, torch.ones([1,1,1,1]), stride=1, padding=(1,2))
print(padding2)
#tensor([[[[0., 0., 0., 0., 0., 0., 0., 0., 0.],
#          [0., 0., 1., 1., 1., 1., 1., 0., 0.],
#          [0., 0., 1., 1., 1., 1., 1., 0., 0.],
#          [0., 0., 1., 1., 1., 1., 1., 0., 0.],
#          [0., 0., 1., 1., 1., 1., 1., 0., 0.],
#          [0., 0., 1., 1., 1., 1., 1., 0., 0.],
#          [0., 0., 0., 0., 0., 0., 0., 0., 0.]]]])

几个卷积核生成几个特征图,几个通道数相加为一个通道图

op1:单卷积核单通道,生成1个feature maptensor([[[[-1., -1., -1.],[-1., -2., -2.],[-1., -2., -2.]]]])
op2:双卷积核单通道,生成2个feature maptensor([[[[-1., -1., -1.],[-1., -2., -2.],[-1., -2., -2.]],[[-1., -1., -1.],[-1., -2., -2.],[-1., -2., -2.]]]])
op3:三卷积核单通道,生成3个feature maptensor([[[[-1., -1., -1.],[-1., -2., -2.],[-1., -2., -2.]],[[-1., -1., -1.],[-1., -2., -2.],[-1., -2., -2.]],[[-1., -1., -1.],[-1., -2., -2.],[-1., -2., -2.]]]])
op4:双卷积核双通道,生成2个feature maptensor([[[[-2., -2., -2.],[-2., -4., -4.],[-2., -4., -4.]],[[-2., -2., -2.],[-2., -4., -4.],[-2., -4., -4.]]]]) 
op5:单卷积核双通道,生成1个feature maptensor([[[[-2., -2., -2.],[-2., -4., -4.],[-2., -4., -4.]]]]) 
---------------------------------------
op1:tensor([[[[-1., -1., -1.],[-1., -2., -2.],[-1., -2., -2.]]]]) 
op6:不加paddingtensor([[[[-2., -2.],[-2., -2.]]]]) 

自行设计了单卷积核三通道,证明上述分析。

# [batch, in_channels, in_height, in_width] [训练时一个batch的图片数量, 图像通道数, 图片高度, 图片宽度]5])
input8 = torch.ones([1, 3, 5, 5])
op8 = torch.nn.functional.conv2d(input8, filter8, stride=2, padding=1) 
# [ out_channels, in_channels,filter_height, filter_width] [卷积核个数,图像通道数,卷积核的高度,卷积核的宽度]
filter8= torch.tensor([[[[-1.,  0.],[ 0., -1.]],[[-1.,  0.],[ 0., -1.]],[[-1.,  0.],[ 0., -1.]]]]).reshape([1,3, 2, 2])
print("op8:\n",op8,filter8)
tensor([[[[-3., -3., -3.],[-3., -6., -6.],[-3., -6., -6.]]]]) 

完整代码:

import torch
# [batch, in_channels, in_height, in_width] [训练时一个batch的图片数量, 图像通道数, 图片高度, 图片宽度]
input1 = torch.ones([1, 1, 5, 5])
input2 = torch.ones([1, 2, 5, 5])
input3 = torch.ones([1, 1, 4, 4])
# [ out_channels, in_channels,filter_height, filter_width] [卷积核个数,图像通道数,卷积核的高度,卷积核的宽度]
filter1 =  torch.tensor([-1.0,0,0,-1]).reshape([2, 2, 1, 1])
filter2 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1]).reshape([2,1,2, 2])
filter3 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1,-1.0,0,0,-1]).reshape([3,1,2, 2])
filter4 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1,-1.0,0,0,-1,-1.0,0,0,-1]).reshape([2, 2, 2, 2])
filter5 =  torch.tensor([-1.0,0,0,-1,-1.0,0,0,-1]).reshape([1,2, 2, 2])#class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
#condv = torch.nn.Conv2d(1,1,kernel_size=1,padding=1, bias=False)
#condv.weight = torch.nn.Parameter(torch.ones([1,1,1,1]))
#padding1 = condv(input1)
#print(padding1)#验证padding补0的规则 ——上下左右都补0
padding1 = torch.nn.functional.conv2d(input1, torch.ones([1,1,1,1]), stride=1, padding=1)
print(padding1)padding2 = torch.nn.functional.conv2d(input1, torch.ones([1,1,1,1]), stride=1, padding=(1,2))
print(padding2)##1个通道输入,生成1个feature map
#filter1 =  torch.tensor([-1.0,0,0,-1]).reshape([1, 1, 2, 2])
#op1 = torch.nn.functional.conv2d(input1, filter1, stride=2, padding=1)
#print('\n')
#print(padding1)
#print(filter1)
#print(op1)#torch.nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
#torch.nn.functional.conv1d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1)
#torch.nn.functional.conv2d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1)op1 = torch.nn.functional.conv2d(input1, filter1, stride=2, padding=1) #1个通道输入,生成1个feature map
op2 = torch.nn.functional.conv2d(input1, filter2, stride=2, padding=1) #1个通道输入,生成2个feature map
op3 = torch.nn.functional.conv2d(input1, filter3, stride=2, padding=1) #1个通道输入,生成3个feature mapop4 = torch.nn.functional.conv2d(input2, filter4, stride=2, padding=1) # 2个通道输入,生成2个feature
op5 = torch.nn.functional.conv2d(input2, filter5, stride=2, padding=1) # 2个通道输入,生成一个feature mapop6 = torch.nn.functional.conv2d(input1, filter1, stride=2, padding=0) # 5*5 对于pading不同而不同print("op1:\n",op1,filter1)#1-1  后面补0
print("------------------")print("op2:\n",op2,filter2) #1-2多卷积核 按列取
print("op3:\n",op3,filter3) #1-3
print("------------------")print("op4:\n",op4,filter4)#2-2    通道叠加
print("op5:\n",op5,filter5)#2-1
print("------------------")print("op1:\n",op1,filter1)#1-1
print("op6:\n",op6,filter1)

实例7:使用卷积提取图片的轮廓

shape为(3264,2448,3)
transforms.ToTensor类能将图片转化为Pytorch所支持的形状(【通道数,高,宽】),同时将图片数值归一化为0到1的小数
sobelfilter = torch.tensor([[-1.0,0,1], [-2,0,2], [-1.0,0,1.0]]*3).reshape([1,3,3, 3])
三通道就乘三

import matplotlib.pyplot as plt # plt 用于显示图片
import matplotlib.image as mpimg # mpimg 用于读取图片
import torch
import torchvision.transforms as transformsmyimg = mpimg.imread('pytorch\chapter7\img.jpg') # 读取和代码处于同一目录下的图片
plt.imshow(myimg) # 显示图片
plt.axis('off') # 不显示坐标轴
plt.show()
print(myimg.shape)
(3264, 2448, 3)pil2tensor = transforms.ToTensor()
rgb_image = pil2tensor(myimg)
print(rgb_image[0][0])
tensor([0.8471, 0.8471, 0.8471,  ..., 0.6824, 0.6824, 0.6824])
print(rgb_image.shape)
torch.Size([3, 3264, 2448])sobelfilter =  torch.tensor([[-1.0,0,1],  [-2,0,2],  [-1.0,0,1.0]]*3).reshape([1,3,3, 3])
print(sobelfilter)
tensor([[[[-1.,  0.,  1.],[-2.,  0.,  2.],[-1.,  0.,  1.]],[[-1.,  0.,  1.],[-2.,  0.,  2.],[-1.,  0.,  1.]],[[-1.,  0.,  1.],[-2.,  0.,  2.],[-1.,  0.,  1.]]]])op =torch.nn.functional.conv2d(rgb_image.unsqueeze(0), sobelfilter, stride=3,padding = 1) #3个通道输入,生成1个feature map#对卷积结果进行处理,数据不能保证在0到255内,必须归一化再乘255
ret = (op - op.min()).div(op.max() - op.min())
ret =ret.clamp(0., 1.).mul(255).int()
print(ret)
tensor([[[[193,  99,  99,  ...,  99,  99,  99],[225,  99,  99,  ...,  99, 100,  99],[224,  99,  99,  ...,  99, 100,  99],...,[177,  97, 100,  ...,  95, 100, 100],[178, 100, 100,  ..., 100,  98,  97],[177,  99,  98,  ...,  96, 100,  98]]]], dtype=torch.int32)plt.imshow(ret.squeeze(),cmap='Greys_r') # 显示图片
plt.axis('off') # 不显示坐标轴
plt.show()op=torch.nn.functional.max_pool2d(op,kernel_size =5, stride=5)
op = op.transpose(1,3).transpose(1,2)
print(op.shape)
torch.Size([1, 217, 163, 1])

在这里插入图片描述
在这里插入图片描述

关于灰度图,对3个通道的图片取平均值,或计算图片0维上的平均值。

r_image=rgb_image[0]
g_image=rgb_image[0]
b_image=rgb_image[0]
grayscale_image=(r_image=rgb_image[0]+g_image=rgb_image[0]+b_image=rgb_image[0]).div(3.0)
plt.imshow(grayscale_image,cmap='Greys_r') # 显示图片
plt.axis('off') # 不显示坐标轴
plt.show()
或者
plt.imshow(rgb_image.mean(),cmap='Greys_r') # 显示图片
plt.axis('off') # 不显示坐标轴
plt.show()

深层卷积神经网络

是多个卷积层和若干其他的神经网络叠加在一起的,原始的主要是输入、卷积、池化、全连接(或全局平均池化层)等部分组成。
输入层:将每个像素作为一个特征节点输入网络
卷积层:多个滤波器组合而成
池化层:将卷积结果降维
全局平均池化层:对生成的特征图取全局平均值,该层也可以用全连接网络代替
输出层:网络需要将数据分成几类,就输出几个节点。

卷积神经网络的反向传播

卷积操作反向求导时,要将特征图做一次padding再与转置后的卷积核做一次卷积操作,即可得到输入端的误差,实现误差的反向传播。

池化操作

主要目的是降维,在保持原有特征的基础上最大限度的将数组的维度变小。
池化只关心滤波器的尺寸,不考虑内部的值,算法是将滤波器映射区域内的像素点取平均值或最大值。
有均值池化(对背景信息更敏感)和最大池化(对纹理特征信息更敏感)。
也有两种实现方式,函数调用和类的方式

实例8:池化函数的应用

手动生成一个4x4的矩阵来模拟图片,两个通道,定义一个2x2的滤波器
pooling3是常用的操作手法,也称全局池化法,与输入数据经两次平均值计算结果数值一致,只有形状不同。

import torchimg=torch.tensor([ [ [0.,0.,0.,0.],[1.,1.,1.,1.],[2.,2.,2.,2.],[3.,3.,3.,3.] ],[ [4.,4.,4.,4.],[5.,5.,5.,5.],[6.,6.,6.,6.],[7.,7.,7.,7.] ]]).reshape([1,2,4,4])
print(img)
#两个通道
tensor([[[[0., 0., 0., 0.],[1., 1., 1., 1.],[2., 2., 2., 2.],[3., 3., 3., 3.]],[[4., 4., 4., 4.],[5., 5., 5., 5.],[6., 6., 6., 6.],[7., 7., 7., 7.]]]])
print(img[0][0])
tensor([[0., 0., 0., 0.],[1., 1., 1., 1.],[2., 2., 2., 2.],[3., 3., 3., 3.]])
print(img[0][1])
tensor([[4., 4., 4., 4.],[5., 5., 5., 5.],[6., 6., 6., 6.],[7., 7., 7., 7.]])
#torch.nn.functional.avg_pool2d(input, kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True)
pooling=torch.nn.functional.max_pool2d(img,kernel_size =2)
print("pooling:\n",pooling)
pooling:tensor([[[[1., 1.],[3., 3.]],[[5., 5.],[7., 7.]]]])
pooling1=torch.nn.functional.max_pool2d(img,kernel_size =2,stride=1)
print("pooling1:\n",pooling1)
pooling1:tensor([[[[1., 1., 1.],[2., 2., 2.],[3., 3., 3.]],[[5., 5., 5.],[6., 6., 6.],[7., 7., 7.]]]])pooling2=torch.nn.functional.avg_pool2d(img,kernel_size =4,stride=1,padding=1)
print("pooling2:\n",pooling2)
pooling2:tensor([[[[0.5625, 0.7500, 0.5625],[1.1250, 1.5000, 1.1250],[1.1250, 1.5000, 1.1250]],[[2.8125, 3.7500, 2.8125],[4.1250, 5.5000, 4.1250],[3.3750, 4.5000, 3.3750]]]])
pooling3=torch.nn.functional.avg_pool2d(img,kernel_size =4)
print("pooling3:\n",pooling3)
pooling3:tensor([[[[1.5000]],[[5.5000]]]])
m1 = img.mean(3)
print("第1次平均值结果:\n",m1)1次平均值结果:tensor([[[0., 1., 2., 3.],[4., 5., 6., 7.]]])
print("第2次平均值结果:\n",m1.mean(2))2次平均值结果:tensor([[1.5000, 5.5000]])

上述可以修改,结果等价

img=torch.tensor( [ [0.,0.,0.,0.],[1.,1.,1.,1.],[2.,2.,2.,2.],[3.,3.,3.,3.] ,[4.,4.,4.,4.],[5.,5.,5.,5.],[6.,6.,6.,6.],[7.,7.,7.,7.] ]).reshape([2,4,4])
m1 = img.mean(2)
print("第2次平均值结果:\n",m1.mean(1))

实例9:搭建卷积神经网络

对第六章实例5进行修改,将2个全连接变为全局平均池化层。
将最后3个全连接层,改为1个卷积层和1个全局平均池化层,卷积核由5改为3
替换一下网络类定义就行了

class myConNet(torch.nn.Module):def __init__(self):super(myConNet, self).__init__()#定义卷积层self.conv1 = torch.nn.Conv2d(in_channels=1, out_channels=6, kernel_size=3)self.conv2 = torch.nn.Conv2d(in_channels=6, out_channels=12, kernel_size=3)self.conv3 = torch.nn.Conv2d(in_channels=12, out_channels=10, kernel_size=3)def forward(self, t):#搭建正向结构#第一层卷积和池化处理t = self.conv1(t)t = F.relu(t)t = F.max_pool2d(t, kernel_size=2, stride=2)#第二层卷积和池化处理t = self.conv2(t)t = F.relu(t)t = F.max_pool2d(t, kernel_size=2, stride=2)#第三层卷积和池化处理t = self.conv3(t)t = F.avg_pool2d(t, kernel_size=t.shape[-2:], stride=t.shape[-2:])return t.reshape(t.shape[:2])#训练完记得torch.save(network.state_dict(),'./CNNFashionMNIST2.pth')#保存模型network.load_state_dict(torch.load( './CNNFashionMNIST2.pth'))#加载模型

测试结果:
Accuracy of T-shirt : 66 %
Accuracy of Trouser : 92 %
Accuracy of Pullover : 72 %
Accuracy of Dress : 80 %
Accuracy of Coat : 68 %
Accuracy of Sandal : 94 %
Accuracy of Shirt : 58 %
Accuracy of Sneaker : 91 %
Accuracy of Bag : 95 %
Accuracy of Ankle_Boot : 96 %
Accuracy of all : 81 %
对比之前的模型在某些类别上提升很明显:
Accuracy of Trouser : 90 %
Accuracy of Pullover : 49 %
Accuracy of Dress : 85 %
Accuracy of Coat : 81 %
Accuracy of Sandal : 92 %
Accuracy of Shirt : 42 %
Accuracy of Sneaker : 91 %
Accuracy of Bag : 94 %
Accuracy of Ankle_Boot : 94 %
Accuracy of all : 80 %

循环神经网络(Recurrent Neural Network ,RNN)

是一个具有记忆功能的网络,它可以发现样本彼此间的相互关系,它多用于处理带有序列特征的样本数据。
人的记忆原理。
婴儿虽然说话能表达意思,但有时会很奇怪,要零食的时候说把“我要”说成“要我”,大脑对这两个字是有先后顺序的。
当获得“我来找你玩游”的时候,大脑语言模型会自动匹配“戏”,而不是游泳、游乐。
用下列伪代码表示逻辑:
(input我+empty-input)→output我
(input来+output我)→output来
(input找+output来)→output找
(input你+output找)→output你
如让小孩背三字经,名俱扬下一句很容易说,问上一句是什么,小孩从头背了一遍。
对于序列化的特征任务,如情感分析、关键字提取、语音识别、机器翻译、股票分析等等适合循环神经网络来解决。

基本结构是,将全连接网络的输出节点复制一份,传回到输入节点,与输入数据一起进行下一次计算。

实例10: 简单循环神经网络实现——设计一个退位减法器

定义基本函数,手写sigmoid及其导数(用于反向传播)

import copy, numpy as np
np.random.seed(0) #随机数生成器的种子,可以每次得到一样的值
# compute sigmoid nonlinearity
def sigmoid(x): #激活函数output = 1/(1+np.exp(-x))return output
# convert output of sigmoid function to its derivative
def sigmoid_output_to_derivative(output):#激活函数的导数return output*(1-output)

建立二进制映射,将减法允许最大值设置为255,即8位二级制,定义int与二进制之间的映射组int2binary。

int2binary = {} #整数到其二进制表示的映射
binary_dim = 8 #暂时制作256以内的减法
## 计算0-256的二进制表示
largest_number = pow(2,binary_dim)
binary = np.unpackbits(np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
for i in range(largest_number):int2binary[i] = binary[i]
print(int2binary)
{0: array([0, 0, 0, 0, 0, 0, 0, 0], dtype=uint8), 1: array([0, 0, 0, 0, 0, 0, 0, 1], dtype=uint8), 2: array([0, 0, 0, 0, 0, 0, 
1, 0], dtype=uint8), 3: array([0, 0, 0, 0, 0, 0, 1, 1], dtype=uint8), 4: array([0, 0, 0, 0, 0, 1, 0, 0], dtype=uint8), 5: array([0, 0, 0, 0, 0, 1, 0, 1], dtype=uint8)..........}

定义参数
隐藏层的权重为synapse_0,循环节点的权重为synapse_h(输入16节点、输出16节点),输出层的权重为synapse_1(输入16节点输出1节点)。
synapse_0_update在前面很少见,是因为它被隐藏在优化器里了,这里是自动手写,需要定义一组变量来存放反向优化参数时需要调整的值。对于前面3个权重synapse_0到3。

# input variables
alpha = 0.9 #学习速率
input_dim = 2 #输入的维度是2
hidden_dim = 16 
output_dim = 1 #输出维度为1# initialize neural network weights
synapse_0 = (2*np.random.random((input_dim,hidden_dim)) - 1)*0.05 #维度为2*16, 2是输入维度,16是隐藏层维度
synapse_1 = (2*np.random.random((hidden_dim,output_dim)) - 1)*0.05
synapse_h = (2*np.random.random((hidden_dim,hidden_dim)) - 1)*0.05
# => [-0.05, 0.05),# 用于存放反向传播的权重更新值
synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)

准备样本数据
建立循环生成样本数据,先生成两个数a和b。如果a小于b,就交换位置,保证被减数大。计算出相减结果c,将3个数转化为二进制,为模型计算做准备。
模型初始化
初始化输出值为0,初始化总误差为0,定义layer_2_deltas为存储反向传播过程中的循环层的误差,layer_1_values为隐藏层的输出值。由于第一个数据输入时,没有前面隐藏层输出值来作为本次的输入,因此需要定义一个初始值,这里初始化为0.1。
正向传播
future_layer_1_delta = np.zeros(hidden_dim)是为了反向传播准备的初始化,反向传播是从正向传播的最后一次计算开始反向计算误差,它没有后一次的输出,因此要初始化一个值作为其后一次的输入,这里初始化为0。
反向传播
开始从高位往回遍历,一次对每一位的所有层计算误差,并对权重求偏导,得到调整值,最后将每一位算出的各层权重的调整值加在一起乘以学习率来更新各层参数。每次更新完后中间变量会清零。
输出结果
每运行800次输出一次结果。

# training 
for j in range(10000):#生成一个数字aa_int = np.random.randint(largest_number) #生成一个数字b,b的最大值取的是largest_number/2,作为被减数,让它小一点。b_int = np.random.randint(largest_number/2) #如果生成的b大了,那么交换一下if a_int<b_int:tt = a_intb_int = a_inta_int=tta = int2binary[a_int] # binary encodingb = int2binary[b_int] # binary encoding    # true answerc_int = a_int - b_intc = int2binary[c_int]# 存储神经网络的预测值d = np.zeros_like(c)overallError = 0 #每次把总误差清零layer_2_deltas = list() #存储每个时间点输出层的误差layer_1_values = list() #存储每个时间点隐藏层的值layer_1_values.append(np.ones(hidden_dim)*0.1) # 一开始没有隐藏层,所以初始化一下原始值为0.1# moving along the positions in the binary encodingfor position in range(binary_dim):#循环遍历每一个二进制位# generate input and outputX = np.array([[a[binary_dim - position - 1],b[binary_dim - position - 1]]])#从右到左,每次去两个输入数字的一个bit位y = np.array([[c[binary_dim - position - 1]]]).T#正确答案# hidden layer (input ~+ prev_hidden)layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))#(输入层 + 之前的隐藏层) -> 新的隐藏层,这是体现循环神经网络的最核心的地方!!!# output layer (new binary representation)layer_2 = sigmoid(np.dot(layer_1,synapse_1)) #隐藏层 * 隐藏层到输出层的转化矩阵synapse_1 -> 输出层layer_2_error = y - layer_2 #预测误差layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2)) #把每一个时间点的误差导数都记录下来overallError += np.abs(layer_2_error[0])#总误差d[binary_dim - position - 1] = np.round(layer_2[0][0]) #记录下每一个预测bit位# store hidden layer so we can use it in the next timesteplayer_1_values.append(copy.deepcopy(layer_1))#记录下隐藏层的值,在下一个时间点用future_layer_1_delta = np.zeros(hidden_dim)#反向传播,从最后一个时间点到第一个时间点for position in range(binary_dim):X = np.array([[a[position],b[position]]]) #最后一次的两个输入layer_1 = layer_1_values[-position-1] #当前时间点的隐藏层prev_layer_1 = layer_1_values[-position-2] #前一个时间点的隐藏层# error at output layerlayer_2_delta = layer_2_deltas[-position-1] #当前时间点输出层导数# error at hidden layer# 通过后一个时间点(因为是反向传播)的隐藏层误差和当前时间点的输出层误差,计算当前时间点的隐藏层误差layer_1_delta = (future_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(synapse_1.T)) * sigmoid_output_to_derivative(layer_1)# 等到完成了所有反向传播误差计算, 才会更新权重矩阵,先暂时把更新矩阵存起来。synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)synapse_0_update += X.T.dot(layer_1_delta)future_layer_1_delta = layer_1_delta# 完成所有反向传播之后,更新权重矩阵。并把矩阵变量清零synapse_0 += synapse_0_update * alphasynapse_1 += synapse_1_update * alphasynapse_h += synapse_h_update * alphasynapse_0_update *= 0synapse_1_update *= 0synapse_h_update *= 0# print out progressif(j % 800 == 0):#print(synapse_0,synapse_h,synapse_1)print("总误差:" + str(overallError))print("Pred:" + str(d))print("True:" + str(c))out = 0for index,x in enumerate(reversed(d)):out += x*pow(2,index)print(str(a_int) + " - " + str(b_int) + " = " + str(out))print("------------")
总误差:[3.97242498]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 0 0 0 0 0]
9 - 9 = 0
------------
总误差:[2.1721182]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 1 0 0 0 1]
17 - 0 = 0
------------
总误差:[1.1082385]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 0 0 0 0 0]
59 - 59 = 0
------------
总误差:[0.18727913]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 0 0 0 0 0]
19 - 19 = 0
------------
总误差:[0.21914293]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 0 0 0 0 0]
71 - 71 = 0
------------
总误差:[0.26861004]
Pred:[0 0 1 1 1 1 0 0]
True:[0 0 1 1 1 1 0 0]
71 - 11 = 60
------------
总误差:[0.11815367]
Pred:[1 0 0 0 0 0 0 0]
True:[1 0 0 0 0 0 0 0]
230 - 102 = 128
------------
总误差:[0.2927243]
Pred:[0 1 1 1 0 0 0 1]
True:[0 1 1 1 0 0 0 1]
160 - 47 = 113
------------
总误差:[0.04298749]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 0 0 0 0 0]
3 - 3 = 0
------------
总误差:[0.04243453]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 0 0 0 0 0]
17 - 17 = 0
------------
总误差:[0.04588656]
Pred:[1 0 0 1 0 1 1 0]
True:[1 0 0 1 0 1 1 0]
167 - 17 = 150
------------
总误差:[0.08098026]
Pred:[1 0 0 1 1 0 0 0]
True:[1 0 0 1 1 0 0 0]
204 - 52 = 152
------------
总误差:[0.03262333]
Pred:[1 1 0 0 0 0 0 0]
True:[1 1 0 0 0 0 0 0]
209 - 17 = 192
------------

从训练结果可以看出,一开始不准确,多次迭代后就精准了。

完整代码:

import copy, numpy as np
np.random.seed(0) #随机数生成器的种子,可以每次得到一样的值
# compute sigmoid nonlinearity
def sigmoid(x): #激活函数output = 1/(1+np.exp(-x))return output
# convert output of sigmoid function to its derivative
def sigmoid_output_to_derivative(output):#激活函数的导数return output*(1-output)int2binary = {} #整数到其二进制表示的映射
binary_dim = 8 #暂时制作256以内的减法
## 计算0-256的二进制表示
largest_number = pow(2,binary_dim)
binary = np.unpackbits(np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
for i in range(largest_number):int2binary[i] = binary[i]# input variables
alpha = 0.9 #学习速率
input_dim = 2 #输入的维度是2
hidden_dim = 16 
output_dim = 1 #输出维度为1# initialize neural network weights
synapse_0 = (2*np.random.random((input_dim,hidden_dim)) - 1)*0.05 #维度为2*16, 2是输入维度,16是隐藏层维度
synapse_1 = (2*np.random.random((hidden_dim,output_dim)) - 1)*0.05
synapse_h = (2*np.random.random((hidden_dim,hidden_dim)) - 1)*0.05
# => [-0.05, 0.05),# 用于存放反向传播的权重更新值
synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)# training 
for j in range(10000):#生成一个数字aa_int = np.random.randint(largest_number) #生成一个数字b,b的最大值取的是largest_number/2,作为被减数,让它小一点。b_int = np.random.randint(largest_number/2) #如果生成的b大了,那么交换一下if a_int<b_int:tt = a_intb_int = a_inta_int=tta = int2binary[a_int] # binary encodingb = int2binary[b_int] # binary encoding    # true answerc_int = a_int - b_intc = int2binary[c_int]# 存储神经网络的预测值d = np.zeros_like(c)overallError = 0 #每次把总误差清零layer_2_deltas = list() #存储每个时间点输出层的误差layer_1_values = list() #存储每个时间点隐藏层的值layer_1_values.append(np.ones(hidden_dim)*0.1) # 一开始没有隐藏层,所以初始化一下原始值为0.1# moving along the positions in the binary encodingfor position in range(binary_dim):#循环遍历每一个二进制位# generate input and outputX = np.array([[a[binary_dim - position - 1],b[binary_dim - position - 1]]])#从右到左,每次去两个输入数字的一个bit位y = np.array([[c[binary_dim - position - 1]]]).T#正确答案# hidden layer (input ~+ prev_hidden)layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))#(输入层 + 之前的隐藏层) -> 新的隐藏层,这是体现循环神经网络的最核心的地方!!!# output layer (new binary representation)layer_2 = sigmoid(np.dot(layer_1,synapse_1)) #隐藏层 * 隐藏层到输出层的转化矩阵synapse_1 -> 输出层layer_2_error = y - layer_2 #预测误差layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2)) #把每一个时间点的误差导数都记录下来overallError += np.abs(layer_2_error[0])#总误差d[binary_dim - position - 1] = np.round(layer_2[0][0]) #记录下每一个预测bit位# store hidden layer so we can use it in the next timesteplayer_1_values.append(copy.deepcopy(layer_1))#记录下隐藏层的值,在下一个时间点用future_layer_1_delta = np.zeros(hidden_dim)#反向传播,从最后一个时间点到第一个时间点for position in range(binary_dim):X = np.array([[a[position],b[position]]]) #最后一次的两个输入layer_1 = layer_1_values[-position-1] #当前时间点的隐藏层prev_layer_1 = layer_1_values[-position-2] #前一个时间点的隐藏层# error at output layerlayer_2_delta = layer_2_deltas[-position-1] #当前时间点输出层导数# error at hidden layer# 通过后一个时间点(因为是反向传播)的隐藏层误差和当前时间点的输出层误差,计算当前时间点的隐藏层误差layer_1_delta = (future_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(synapse_1.T)) * sigmoid_output_to_derivative(layer_1)# 等到完成了所有反向传播误差计算, 才会更新权重矩阵,先暂时把更新矩阵存起来。synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)synapse_0_update += X.T.dot(layer_1_delta)future_layer_1_delta = layer_1_delta# 完成所有反向传播之后,更新权重矩阵。并把矩阵变量清零synapse_0 += synapse_0_update * alphasynapse_1 += synapse_1_update * alphasynapse_h += synapse_h_update * alphasynapse_0_update *= 0synapse_1_update *= 0synapse_h_update *= 0# print out progressif(j % 800 == 0):#print(synapse_0,synapse_h,synapse_1)print("总误差:" + str(overallError))print("Pred:" + str(d))print("True:" + str(c))out = 0for index,x in enumerate(reversed(d)):out += x*pow(2,index)print(str(a_int) + " - " + str(b_int) + " = " + str(out))print("------------")

常见的循环神经网络单元及结构

上述实例仅限于简单的逻辑和样本,对于相对复杂的问题有缺陷,原因在激活函数上。
通常像Sigmoid、tanh这类激活函数在神经网络里最多只能有6层左右,因为反向误差的传播会导致随着层数增加传递的误差值越小。RNN中,误差传递不光在层之间,还在每层的样本序列之间,因此其无法学习太长的序列特征。
在深层网络结构中,会将简单的RNN模型从两个角度进行改造:
1.使用结构更复杂的RNN模型的基本单元,使其在单层网络上提取更好的记忆特征。
2.将多个基本单元结合起来,组成不同的结构(多层RNN,双向RNN等),有时还会配合全连接网络、卷积网络等多种模型结构,一起组成拟合能力更强的网络模型。

长短记忆(LSTM)单元

一种使用了类似搭桥术结构的RNN单元,可以学习长期序列信息,是RNN网络中最常用的Cell之一。
看起来比较复杂其实是一个带有tanh激活函数的简单RNN,原理是引入一个成为细胞状态的连接,用来存放想要记忆的东西,(对应于简单RNN中的h,只不过不再只是保存上一次的状态了,而是通过网络学习存放那些有用的状态),同时在里面加入3个门,忘记门、输入门、输出门。

门控制循环单元(GRU)

几乎是与LSTM功能一样的常用网络结构,它将忘记门和输入门合成了一个单一的更新门,同时又将细胞状态和隐藏状态进行混合,以及一些其他的改动,最终的模型比LSTM模型要简单,少一个状态输出,但效果几乎一样,可以让代码更简单。

只有忘记门的LSTM(JANET)单元

只有忘记门时,性能居然优于标志LSTM,该优化方式也可以用在GRU。

独立循环单元(IndRNN)单元

效果和速度都优于LSTM单元,不但能解决传统RNN模型存在的梯度消失和梯度爆炸问题,而且可以更好学习样本中长期依赖的关系。
在搭建模型时:
可以用堆叠、残差、全连接的方式使用IndRNN单元,搭建更深的网络结构;
将IndRNN单元配合ReLU等非饱和激活函数一起使用,会使模型表现出很好的鲁棒性。

IndRNN与LSTM单元相比,使用了更简单的结构,比其快10倍,更像一个原始的RNN模型结构(只将神经元的输出复制到节点之中),其在循环层部分做了特殊处理。
通过公式来详细介绍…

双向RNN结构

又称Bi-RNN,采用了两个方向的RNN模型。正反结合比单向的循环网络有更高的拟合度。例如预测一个语句中缺失的词语,需要根据上下文来预测。
略…

实例11:用循环神经网络训练语言模型

还涉及自然语言处理(NLP)领域的相关知识,语言模型包括文法语言模型和统计语言模型,一般指统计语言模型。略…只做了解。
PyTorch中,有两个封装好的RNN类,LSTM和GRU。
解码要改成utf-8
labels =labels+label.decode(‘utf-8’)

import torch
import torch.nn.functional as F
import time
import random
import numpy as np
from collections import Counterimport sys# print(sys.getdefaultencoding()) RANDOM_SEED = 123
torch.manual_seed(RANDOM_SEED)DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')def elapsed(sec):if sec<60:return str(sec) + " sec"elif sec<(60*60):return str(sec/60) + " min"else:return str(sec/(60*60)) + " hr"training_file = 'wordstest.txt'#中文多文件
def readalltxt(txt_files):labels = []for txt_file in txt_files:target = get_ch_lable(txt_file)labels.append(target)  return labels#中文字
def get_ch_lable(txt_file):  labels= ""with open(txt_file, 'rb') as f:for label in f: #labels =label.decode('utf-8')labels =labels+label.decode('utf-8')return  labels#优先转文件里的字符到向量
def get_ch_lable_v(txt_file,word_num_map,txt_label=None):words_size = len(word_num_map)   to_num = lambda word: word_num_map.get(word, words_size) if txt_file!= None:txt_label = get_ch_lable(txt_file)labels_vector = list(map(to_num, txt_label)) return labels_vector  training_data =get_ch_lable(training_file)print("Loaded training data...")print('样本长度:',len(training_data))
counter = Counter(training_data)  
words = sorted(counter)
words_size= len(words)
word_num_map = dict(zip(words, range(words_size))) print('字表大小:', words_size)     
wordlabel = get_ch_lable_v(training_file,word_num_map)class GRURNN(torch.nn.Module):def __init__(self, word_size, embed_dim,hidden_dim, output_size, num_layers):super(GRURNN, self).__init__()self.num_layers = num_layersself.hidden_dim = hidden_dimself.embed = torch.nn.Embedding(word_size, embed_dim)self.gru = torch.nn.GRU(input_size=embed_dim,hidden_size=hidden_dim,num_layers=num_layers,bidirectional=True)self.fc = torch.nn.Linear(hidden_dim*2, output_size)def forward(self, features, hidden):embedded = self.embed(features.view(1, -1))output, hidden = self.gru(embedded.view(1, 1, -1), hidden)output = self.fc(output.view(1, -1))return output, hiddendef init_zero_state(self):init_hidden = torch.zeros(self.num_layers*2, 1, self.hidden_dim).to(DEVICE)return init_hiddenEMBEDDING_DIM = 10
HIDDEN_DIM = 20
NUM_LAYERS = 1model = GRURNN(words_size, EMBEDDING_DIM, HIDDEN_DIM, words_size, NUM_LAYERS)
model = model.to(DEVICE)
optimizer = torch.optim.Adam(model.parameters(), lr=0.005)def evaluate(model, prime_str, predict_len, temperature=0.8):hidden = model.init_zero_state().to(DEVICE)predicted = ''#处理输入语义for p in range(len(prime_str) - 1):_, hidden = model(prime_str[p], hidden)predicted +=words[prime_str[p]]inp = prime_str[-1]predicted +=words[inp]for p in range(predict_len):output, hidden = model(inp, hidden)#从多项式分布中采样output_dist = output.data.view(-1).div(temperature).exp()inp = torch.multinomial(output_dist, 1)[0]predicted += words[inp]return predicted#定义参数训练模型
training_iters = 5000
display_step = 1000
n_input = 4
step = 0
offset = random.randint(0,n_input+1)
end_offset = n_input + 1while step < training_iters:start_time = time.time()# 随机取一个位置偏移if offset > (len(training_data)-end_offset):offset = random.randint(0, n_input+1)inwords =wordlabel[offset:offset+n_input]inwords = np.reshape(np.array(inwords), [n_input, -1,  1])out_onehot = wordlabel[offset+1:offset+n_input+1]hidden = model.init_zero_state()optimizer.zero_grad()loss = 0.inputs, targets = torch.LongTensor(inwords).to(DEVICE), torch.LongTensor(out_onehot).to(DEVICE)for c in range(n_input):outputs, hidden = model(inputs[c], hidden)loss += F.cross_entropy(outputs, targets[c].view(1))loss /= n_inputloss.backward()optimizer.step()#输出日志with torch.set_grad_enabled(False):if (step+1) % display_step == 0:print(f'Time elapsed: {(time.time() - start_time)/60:.4f} min')print(f'step {step+1} | Loss {loss.item():.2f}\n\n')with torch.no_grad():print(evaluate(model, inputs, 32), '\n')print(50*'=')step += 1offset += (n_input+1)#中间隔了一个,作为预测print("Finished!")while True:prompt = "请输入几个字,最好是%s个: " % n_inputsentence = input(prompt)inputword = sentence.strip()try:inputword = get_ch_lable_v(None,word_num_map,inputword)keys = np.reshape(np.array(inputword), [ len(inputword),-1, 1])model.eval()with torch.no_grad():sentence =evaluate(model, torch.LongTensor(keys).to(DEVICE), 32)print(sentence)except:print("该字我还没学会")

过拟合问题及优化技巧

介绍一下神经网络在训练过程中的一些常用技巧。

实例12:训练具有过拟合问题的模型

可以用下面引用其他写的example1.py,不知道为什么会运行一遍原来文件的函数。
可以看图看出过拟合。

import sys
sys.path.append('pytorch\chapter3')
from example1 import LogicNet,moving_average,predict,plot_decision_boundary
import sklearn.datasets     #引入数据集
import torch
import numpy as np
import matplotlib.pyplot as plt
import sys
sys.path.append('pytorch\chapter3')
from example1 import LogicNet,moving_average,predict,plot_decision_boundarynp.random.seed(0)           #设置随机数种子
X, Y = sklearn.datasets.make_moons(40,noise=0.2) #生成2组半圆形数据arg = np.squeeze(np.argwhere(Y==0),axis = 1)     #获取第1组数据索引
arg2 = np.squeeze(np.argwhere(Y==1),axis = 1)#获取第2组数据索引plt.title("train moons data")
plt.scatter(X[arg,0], X[arg,1], s=100,c='b',marker='+',label='data1')
plt.scatter(X[arg2,0], X[arg2,1],s=40, c='r',marker='o',label='data2')
plt.legend()
plt.show()model = LogicNet(inputdim=2,hiddendim=500,outputdim=2)#初始化模型
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)#定义优化器xt = torch.from_numpy(X).type(torch.FloatTensor)#将Numpy数据转化为张量
yt = torch.from_numpy(Y).type(torch.LongTensor)
epochs = 1000#定义迭代次数
losses = []#定义列表,用于接收每一步的损失值
for i in range(epochs):loss = model.getloss(xt,yt)losses.append(loss.item())optimizer.zero_grad()#清空之前的梯度loss.backward()#反向传播损失值optimizer.step()#更新参数avgloss= moving_average(losses) #获得损失值的移动平均值
plt.figure(1)
plt.subplot(211)
plt.plot(range(len(avgloss)), avgloss, 'b--')
plt.xlabel('step number')
plt.ylabel('Training loss')
plt.title('step number vs. Training loss')
plt.show()plot_decision_boundary(lambda x : predict(model,x) ,X, Y)
from sklearn.metrics import accuracy_score
print("训练时的准确率:",accuracy_score(model.predict(xt),yt))Xtest, Ytest = sklearn.datasets.make_moons(80,noise=0.2) #生成2组半圆形数据
plot_decision_boundary(lambda x : predict(model,x) ,Xtest, Ytest)
Xtest_t = torch.from_numpy(Xtest).type(torch.FloatTensor)#将Numpy数据转化为张量
Ytest_t = torch.from_numpy(Ytest).type(torch.LongTensor)
print("测试时的准确率:",accuracy_score(model.predict(Xtest_t),Ytest_t))

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
训练时的准确率: 1.0
在这里插入图片描述
测试时的准确率: 0.9375

改善模型过拟合的方法

如early stopping、数据集扩增、正则化、Dropout。
early stopping:在数据过拟合之前结束,不好把控。
数据集扩增:让模型看到更多的情况,最大化满足全样本,但在实际中为未来事件预测力不从心。
正则化:通过引入范数概念,增强泛化能力,有L1正则化、L2正则化。
Dropout:每次训练舍去一些节点来增强泛化能力

了解正则化

所谓正则化,就是在神经网络计算损失过程中,在损失后面加一项来干扰,实现模型无法和与样本完全拟合,从而抑制过拟合。

正则化的分类和公式

干扰项一定要有以下特征
当欠拟合时,希望它对模型误差影响尽量小,让模型快速拟合实际。
过拟合时,希望影响大,让模型不要产生过拟合的情况。
于是引入了两个范数——L1、L2
L1:所有学习参数的w的绝对值的和
L2:所有学习参数的w的平方和,然后求平方根
实际应用中L2最常用

L2正则化的实现

直接的方式是用优化器自带的weight_decay参数指定权重值衰减率,相当于L2正则化中的λ。默认对w和b都处理,实际上只需要对w,如果对b可能会欠拟合。

实例13:用L2正则改善模型的过拟合情况

在实例12上添加正则化处理,重新进行训练。

#添加正则化处理
weight_p, bias_p = [],[]
for name, p in model.named_parameters():if 'bias' in name:bias_p += [p]else:weight_p += [p]
optimizer = torch.optim.Adam([{'params': weight_p, 'weight_decay':0.001},{'params': bias_p, 'weight_decay':0}],lr=0.01)

在这里插入图片描述

训练时的准确率由1到0.975,是由于L2正则化干扰项
测试时的准确率由0.9375到0.9875,表明L2改善了过拟合
在这里插入图片描述
在这里插入图片描述

观察训练和测试图片没有了闭合区间,更接近原始的数据分布。

实例14:通过增大数据集改善模型的过拟合状况

不再生成一次,而是循环生成40次,修改每次训练都加入新的数据集。
在迭代中加入

    X, Y = sklearn.datasets.make_moons(40,noise=0.2) #生成2组半圆形数据xt = torch.from_numpy(X).type(torch.FloatTensor)#将Numpy数据转化为张量yt = torch.from_numpy(Y).type(torch.LongTensor)

可以看出loss曲线有明显的抖动,是由于新数据对上一次模型的拟合能力冲突较大,经过多次迭代就可以不断修正错误,达到合理的拟合能力。
在这里插入图片描述
与之前对比
训练时的准确率由1到0.95,是由于训练了新的数据
测试时的准确率由0.9375到0.975,表明增大数据集的方法改进了过拟合情况。
在这里插入图片描述
在这里插入图片描述
观察图片没有了闭合区间,更接近原始的数据分布。

Dropout方法

原理:每次随机选择一部分节点不去学习,是因为过拟合是把一些异常数据当成规律来学习,但其量非常少,利用上述特性,每次训练忽略一些节点,将小概率的异常数据获得学习的机会变得更低。但不是丢弃越多越好,会降低拟合速度。它改变了网络结构,只能训练的时候用,测试时候要改成False。使用类的方式时候,没有training参数,因为它会根据调用方式自己调节。

实例15:通过Dropout方法改善模型的过拟合状况

为了简化代码,之间继承模型类,然后重写前向结构。

#继承LogicNet类,构建网络模型
class Logic_Dropout_Net(LogicNet):def __init__(self,inputdim,hiddendim,outputdim):#初始化网络结构super(Logic_Dropout_Net,self).__init__(inputdim,hiddendim,outputdim)def forward(self,x): #搭建用两层全连接组成的网络模型x = self.Linear1(x)#将输入数据传入第1层x = torch.tanh(x)#对第一层的结果进行非线性变换x = nn.functional.dropout(x, p=0.07, training=self.training)x = self.Linear2(x)#再将数据传入第2层return x

在这里插入图片描述

与之前对比
训练时的准确率由1到0.925
测试时的准确率由0.9375到0.95
测试准确率同样没有低于训练准确率,说明Dropout方式有效改善过拟合
在这里插入图片描述
在这里插入图片描述

观察图片没有了闭合区间,更接近原始的数据分布。

全连接网络的深浅与泛化能力的联系

浅层网络有更好的拟合能力,但泛化能力弱,深层反之。
实际过程要考虑二者平衡,如wide_deep模型,就是单层线性模型(浅层全连接网络模型)和深度的全连接模型(深层全连接网络模型)。

了解批量归一化(BN)算法

一般用在全连接神经网络和卷积神经网络中,它的问世让整个神经网络的识别准确率上升了一个台阶。
权重值差太大,会让网络无法计算产生梯度爆炸,原因是网络内部协变量的转移,即正向传播时的不同层的参数会将反向训练计算时所参照的数据样本分布改变。
引入批量归一化的作用:最大限度的保证每次正向传播输出在同一分布上,这样反向计算时参照的数据样本分布就会与正向一样了。保证了分布统一,对权重的调整才会更有意义。
算法实现就是将每一层运算出来的数据归一化成均值为0、方差为1的标准高斯分布,这样就会在保留样本的分布特征的同时,又消除了层与层之间的分布差异。实际上加两个参数通过训练获得。

实例16:手动实现批量归一化的计算方法

data为2个样本,2个通道高宽为2和1。
BatchNorm2d接口为数据的每个通道创建一套自适应参数,实际计算中根据每个通道的数据进行批量归一化计算的。
经过批量归一化后,只改变了值没有改变形状。
最后手动计算第1通道中第一个数据的BN,结果与接口一致。

import torch
import torch.nn as nn
data=torch.randn(2,2,2,1)
print(data)
tensor([[[[-0.3322],  [ 0.2331]], [[ 0.0162],  [ 1.0788]]],[[[ 0.6592],[ 1.3542]],[[-0.0912],[ 0.9763]]]])
obn=nn.BatchNorm2d(2,affine=True) #实例化自适应BN对象
output=obn(data)
print(obn.weight)
print(obn.bias)
print(obn.eps)
print(output,output.size())
#自适应参数
Parameter containing:
tensor([1., 1.], requires_grad=True)
Parameter containing:
tensor([0., 0.], requires_grad=True)
1e-05
tensor([[[[-1.3166],[-0.3986]],[[-0.8948],[ 1.0910]]],[[[ 0.2933],[ 1.4218]],[[-1.0955],[ 0.8994]]]], grad_fn=<NativeBatchNormBackward0>) torch.Size([2, 2, 2, 1])print("第1通道的数据:",data[:,0])#计算第1通道数据的均值和方差
Mean=torch.Tensor.mean(data[:,0])
Var=torch.Tensor.var(data[:,0],False)   #false表示贝塞尔校正不会被使用
print(Mean)
print(Var)#计算第1通道中第一个数据的BN
batchnorm=((data[0][0][0][0]-Mean)/(torch.pow(Var,0.5)+obn.eps))\*obn.weight[0]+obn.bias[0]
print(batchnorm)1通道的数据: tensor([[[-0.3322],[ 0.2331]],[[ 0.6592],[ 1.3542]]])
tensor(0.4786)
tensor(0.3793)
tensor(-1.3166, grad_fn=<AddBackward0>)

实例17:通过批量归一化方法改善模型形状

继承模型后进行BN处理

#继承LogicNet类,构建网络模型
class Logic_BN_Net(LogicNet):def __init__(self,inputdim,hiddendim,outputdim):#初始化网络结构super(Logic_BN_Net,self).__init__(inputdim,hiddendim,outputdim)self.BN = nn.BatchNorm1d(hiddendim) #定义BN层def forward(self,x): #搭建用两层全连接组成的网络模型x = self.Linear1(x)#将输入数据传入第1层x = torch.tanh(x)#对第一层的结果进行非线性变换x = self.BN(x)#将第一层的数据做BN处理x = self.Linear2(x)#再将数据传入第2层return x

在这里插入图片描述
与之前对比
训练时的准确率由1到0.975
测试时的准确率由0.9375到0.925
说明BN有效改善过拟合
在这里插入图片描述
在这里插入图片描述

注意力机制

让神经网络忽略不重要的特征向量,重点计算有用的,抛弃无用特征对拟合效果的干扰同时能提高运算速度。
通过注意力分数来实现,是一个0到1的值,可以用在任何网络中。
可以作用在RNN模型中的每个序列上,在模型输出的特征向量上。
有两种模式,软模式(所有数据都注意)、硬模式(会舍弃一部分不符合条件的注意力权值为0)。
多头注意力机制…自注意力机制…

实例18:利用注意力循环神经网络对图片分类

import torchvision
import torchvision.transforms as tranforms
data_dir = './fashion_mnist/'
tranform = tranforms.Compose([tranforms.ToTensor()])
train_dataset = torchvision.datasets.FashionMNIST(data_dir, train=True, transform=tranform,download=True)print("训练数据集条数",len(train_dataset))
val_dataset  = torchvision.datasets.FashionMNIST(root=data_dir, train=False, transform=tranform)
print("测试数据集条数",len(val_dataset))
import pylab
im = train_dataset[0][0]
im = im.reshape(-1,28)
pylab.imshow(im)
pylab.show()
print("该图片的标签为:",train_dataset[0][1])############数据集的制作
import torch
batch_size = 10
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(val_dataset, batch_size=batch_size, shuffle=False)from matplotlib import pyplot as plt
import numpy as np
def imshow(img):print("图片形状:",np.shape(img))npimg = img.numpy()plt.axis('off')plt.imshow(np.transpose(npimg, (1, 2, 0)))classes = ('T-shirt', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle_Boot')
sample = iter(train_loader)
images, labels = sample.next()
print('样本形状:',np.shape(images))
print('样本标签:',labels)
imshow(torchvision.utils.make_grid(images,nrow=batch_size))
print(','.join('%5s' % classes[labels[j]] for j in range(len(images))))######################################################################################################################################定义myLSTMNet模型类,该模型包括 2个RNN层和1个全连接层
class myLSTMNet(torch.nn.Module):def __init__(self,in_dim, hidden_dim, n_layer, n_class):super(myLSTMNet, self).__init__()#定义循环神经网络层self.lstm = torch.nn.LSTM(in_dim, hidden_dim, n_layer,batch_first=True)self.Linear = torch.nn.Linear(hidden_dim*28, n_class)#定义全连接层self.attention = AttentionSeq(hidden_dim,hard=0.03)def forward(self, t):    #搭建正向结构t, _ = self.lstm(t)  #进行RNN处理t = self.attention(t)t=t.reshape(t.shape[0],-1)
#        t = t[:, -1, :]      #获取RNN网络的最后一个序列数据out = self.Linear(t) #进行全连接处理return out
class AttentionSeq(torch.nn.Module):def __init__(self, hidden_dim,hard= 0):super(AttentionSeq, self).__init__()self.hidden_dim = hidden_dimself.dense = torch.nn.Linear(hidden_dim, hidden_dim)self.hard = harddef forward(self, features, mean=False):#[batch,seq,dim]batch_size, time_step, hidden_dim = features.size()weight = torch.nn.Tanh()(self.dense(features))# mask给负无穷使得权重为0mask_idx = torch.sign(torch.abs(features).sum(dim=-1))
#        mask_idx = mask_idx.unsqueeze(-1).expand(batch_size, time_step, hidden_dim)mask_idx = mask_idx.unsqueeze(-1).repeat(1, 1, hidden_dim)weight = torch.where(mask_idx== 1, weight,torch.full_like(mask_idx,(-2 ** 32 + 1)))weight = weight.transpose(2, 1)weight = torch.nn.Softmax(dim=2)(weight)if self.hard!=0: #hard modeweight = torch.where(weight>self.hard, weight, torch.full_like(weight,0))if mean:weight = weight.mean(dim=1)weight = weight.unsqueeze(1)weight = weight.repeat(1, hidden_dim, 1)weight = weight.transpose(2, 1)features_attention = weight * featuresreturn features_attention
#实例化模型对象
network = myLSTMNet(28, 128, 2, 10)  # 图片大小是28x28
#指定设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
network.to(device)
print(network)#打印网络criterion = torch.nn.CrossEntropyLoss()  #实例化损失函数类
optimizer = torch.optim.Adam(network.parameters(), lr=.01)for epoch in range(2): #数据集迭代2次running_loss = 0.0for i, data in enumerate(train_loader, 0): #循环取出批次数据inputs, labels = datainputs = inputs.squeeze(1)inputs, labels = inputs.to(device), labels.to(device) #optimizer.zero_grad()#清空之前的梯度outputs = network(inputs)loss = criterion(outputs, labels)#计算损失loss.backward()  #反向传播optimizer.step() #更新参数running_loss += loss.item()if i % 1000 == 999:print('[%d, %5d] loss: %.3f' %(epoch + 1, i + 1, running_loss / 2000))running_loss = 0.0print('Finished Training')#使用模型
dataiter = iter(test_loader)
images, labels = dataiter.next()inputs, labels = images.to(device), labels.to(device)imshow(torchvision.utils.make_grid(images,nrow=batch_size))
print('真实标签: ', ' '.join('%5s' % classes[labels[j]] for j in range(len(images))))
inputs = inputs.squeeze(1)
outputs = network(inputs)
_, predicted = torch.max(outputs, 1)print('预测结果: ', ' '.join('%5s' % classes[predicted[j]]for j in range(len(images))))#测试模型
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():for data in test_loader:images, labels = dataimages = images.squeeze(1)inputs, labels = images.to(device), labels.to(device)outputs = network(inputs)_, predicted = torch.max(outputs, 1)predicted = predicted.to(device)c = (predicted == labels).squeeze()for i in range(10):label = labels[i]class_correct[label] += c[i].item()class_total[label] += 1sumacc = 0
for i in range(10):Accuracy = 100 * class_correct[i] / class_total[i]print('Accuracy of %5s : %2d %%' % (classes[i], Accuracy ))sumacc =sumacc+Accuracy
print('Accuracy of all : %2d %%' % ( sumacc/10. ))

输出结果如下:
训练数据集条数 60000
测试数据集条数 10000
libpng warning: iCCP: cHRM chunk does not match sRGB
该图片的标签为: 9
样本形状: torch.Size([10, 1, 28, 28])
样本标签: tensor([2, 2, 5, 3, 7, 1, 7, 9, 2, 9])
图片形状: torch.Size([3, 32, 302])
Pullover,Pullover,Sandal,Dress,Sneaker,Trouser,Sneaker,Ankle_Boot,Pullover,Ankle_Boot
cpu
myLSTMNet(
(lstm): LSTM(28, 128, num_layers=2, batch_first=True)
(Linear): Linear(in_features=3584, out_features=10, bias=True)
(attention): AttentionSeq(
(dense): Linear(in_features=128, out_features=128, bias=True)
)
)
[1, 1000] loss: 0.381
[1, 2000] loss: 0.261
[1, 3000] loss: 0.240
[1, 4000] loss: 0.216
[1, 5000] loss: 0.205
[1, 6000] loss: 0.207
[2, 1000] loss: 0.231
[2, 2000] loss: 0.281
[2, 3000] loss: 0.282
[2, 4000] loss: 0.265
[2, 5000] loss: 0.238
[2, 6000] loss: 0.230
Finished Training
图片形状: torch.Size([3, 32, 302])
真实标签: Ankle_Boot Pullover Trouser Trouser Shirt Trouser Coat Shirt Sandal Sneaker
预测结果: Ankle_Boot Pullover Trouser Trouser Shirt Trouser Pullover Shirt Sandal Sneaker
Accuracy of T-shirt : 78 %
Accuracy of Trouser : 95 %
Accuracy of Pullover : 81 %
Accuracy of Dress : 75 %
Accuracy of Coat : 65 %
Accuracy of Sandal : 89 %
Accuracy of Shirt : 54 %
Accuracy of Sneaker : 91 %
Accuracy of Bag : 95 %
Accuracy of Ankle_Boot : 95 %
Accuracy of all : 82 %

这一章内容也太多了,都有点烦了。。。


http://www.ppmy.cn/news/708331.html

相关文章

痞子衡单片机排行榜(2022Q4)

痞子衡单片机排行榜(2022Q4) 继2020年开办的《痞子衡嵌入式半月刊》之后&#xff0c;从2023年1月份开始痞子衡将为大家带来新项目《痞子衡单片机排行榜》(一年分四季&#xff0c;每个季度发布一期)&#xff0c;以MCU主频和Coremark跑分为基础(后期会加入更多指标)&#xff0c;搜…

王思聪砸百万组装服务器,跑分全球第4

本文转载自GitHub技术社区 【导读】最近王校长砸钱装了台服务器&#xff0c;跑分世界第四、亚洲第一&#xff01;他和 GEEK 的差别可能就只有金钱了。现在&#xff0c;一起来站在有钱人的肩膀上看看都有什么「高级」的配置吧…… 王校长随随便便就跑了个世界第四&#xff01; …

学计算机测试用什么电脑,电脑跑分软件哪个好?好用的电脑跑分软件盘点

想要更加直观的了解自己的电脑&#xff0c;一款好用的电脑跑分软件无疑是必不可少的&#xff0c;毕竟其能够将测试结果用跑分的形式计算出来。那么&#xff0c;电脑跑分软件哪个比较好呢&#xff1f;下面是小编分享的好用的电脑跑分软件盘点&#xff0c;小伙伴们可不要错过了。…

迷迷糊糊?似懂非懂?一文让你从此对SPI了如指掌

迷迷糊糊&#xff1f;似懂非懂&#xff1f;一文让你从此对SPI了如指掌 前言一、SPI 与 API1. SPI 在生活中的类比2. SPI 在代码上的例子3. API 与 SPI 的关系 二、JAVA 的 SPI 机制1. JAVA 中的 SPI 例子2. SPI 机制的四大组件3. SPI 机制的实现4. JAVA SPI的不足 前言 你是不…

架构设计第十讲:架构之高并发:缓存

架构设计第十讲:架构之高并发:缓存 高并发实现的三板斧:缓存,限流和降级。缓存在高并发系统中有者极其广阔的应用,需要重点掌握,本文是架构设计第10讲,重点介绍下缓存及其实现 文章目录 架构设计第十讲:架构之高并发:缓存1、缓存简介1.1、关键词-命中率1.2、缓存介质1…

深入了解 SOCKS5 协议及其应用

本文将深入介绍 SOCKS5 协议&#xff0c;探讨其在网络安全、爬虫和 HTTP 编程中的应用。我们将详细解释 SOCKS5 协议的工作原理和特点&#xff0c;并提供一些实际案例&#xff0c;展示如何使用 SOCKS5 代理进行网络访问、保护隐私和提高性能。此外&#xff0c;我们还将介绍如何…

美国户外品牌北面推出新科技防水透气面料

北面&#xff08;The North Face&#xff09;于拉斯维加斯宣布推出FUTURELIGHT™全新防水透气面料&#xff0c;革新科技面料未来&#xff0c;为可持续发展树立了新标准。品牌旨在以负责的态度&#xff0c;使用再生布料减少生产过程中的化学消耗&#xff0c;以生产出三层结构的衣…

什么是高防IP,以及那些行业会用的多一点。

高防IP是针对互联网服务器在遭受大流量的DDoS攻击后导致服务不可用的情况下&#xff0c;将攻击流量引流到高防IP&#xff0c;确保源站的稳定可靠。&#xff08;无需转移数据&#xff0c;理论上任何主机都可以使用高防IP来防护DDOS攻击。&#xff09;  高防ip是指高防机房所提…