NNDL 实验六 卷积神经网络(3)LeNet实现MNIST 手动算子部分
手写体数字识别是计算机视觉中最常用的图像分类任务,让计算机识别出给定图片中的手写体数字(0-9共10个数字)。由于手写体风格差异很大,因此手写体数字识别是具有一定难度的任务。
我们采用常用的手写数字识别数据集:MNIST数据集。
MNIST数据集是计算机视觉领域的经典入门数据集,包含了60,000个训练样本和10,000个测试样本。
这些数字已经过尺寸标准化并位于图像中心,图像是固定大小(28×28像素)。
5.3 基于LeNet实现手写体数字识别实验
LeNet-5虽然提出的时间比较早,但它是一个非常成功的神经网络模型。
基于LeNet-5的手写数字识别系统在20世纪90年代被美国很多银行使用,用来识别支票上面的手写数字。
5.3.2 模型构建
这里的LeNet-5和原始版本有4点不同:
C3层没有使用连接表来减少卷积数量。
汇聚层使用了简单的平均汇聚,没有引入权重和偏置参数以及非线性激活函数。
卷积层的激活函数使用ReLU函数。
最后的输出层为一个全连接线性层。
网络共有7层,包含3个卷积层、2个汇聚层以及2个全连接层的简单卷积神经网络接,受输入图像大小为32×32=1024,输出对应10个类别的得分。
1.测试LeNet-5模型,构造一个形状为 [1,1,32,32]的输入数据送入网络,观察每一层特征图的形状变化。
2.使用自定义算子,构建LeNet-5模型
自定义的Conv2D和Pool2D算子中包含多个for循环,所以运算速度比较慢。
飞桨框架中,针对卷积层算子和汇聚层算子进行了速度上的优化,这里基于paddle.nn.Conv2D、paddle.nn.MaxPool2D和paddle.nn.AvgPool2D构建LeNet-5模型,对比与上边实现的模型的运算速度。
使用pytorch中的相应算子,构建LeNet-5模型
torch.nn.Conv2d();torch.nn.MaxPool2d();torch.nn.avg_pool2d()
3.测试两个网络的运算速度。
4.令两个网络加载同样的权重,测试一下两个网络的输出结果是否一致。
5.统计LeNet-5模型的参数量和计算量。
在飞桨中,还可以使用paddle.flopsAPI自动统计计算量。pytorch可以么?
5.3.3 模型训练
使用交叉熵损失函数,并用随机梯度下降法作为优化器来训练LeNet-5网络。
用RunnerV3在训练集上训练5个epoch,并保存准确率最高的模型作为最佳模型。
5.3.4 模型评价
使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失变化情况。
5.3.5 模型预测
同样地,我们也可以使用保存好的模型,对测试集中的某一个数据进行模型预测,观察模型效果。
实验过程:
首先为了能实现反向传播,将之前基于numpy的卷积类中的参数从array数组转换为tensor对象。
别让我从最后一层一步步链式求导到第一层,那实在是太麻烦、太复杂了,正常人都不会这么干。这也体现出了自动求导的优势。
#coding:utf-8
import json
import math
import numpy as np
import torch
import torchvision
from matplotlib import pyplot as plt
from torchvision import transformsclass myMultipassageConv():def __init__(self,in_chanels,out_chanels,kernel_size,padding,stride=(1,1),padding_str='zeros',use_bias=True,use_redundancy=False):''':param in_chanels: 输入通道数:param out_chanels: 输出通道数:param kernel_size: 卷积核尺寸 tuple类型:param padding: 边缘填充数:param stride: 步长 tuple类型 (可选)默认为(1,1):param padding_str: 边缘填充方式 str类型(可选):param use_bias: 是否使用偏置 bool类型(可选)use_redundancy:是否使用冗余值 bool 默认舍弃'''self.kernel_size=kernel_sizeself.in_chanels=in_chanelsself.out_chanels=out_chanelsself.padding=paddingself.stride=stride(self.kernel_hight,self.kernel_width)=kernel_sizeself.weights,self.bias=self.__parameters()self.padding_str=padding_strself.use_bias=use_biasself.use_redundancy=use_redundancydef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights=torch.randn(self.out_chanels,self.in_chanels,self.kernel_hight,self.kernel_width,requires_grad=True)'''偏置尺寸为out_kernels'''bias=torch.randn(self.out_chanels,1,requires_grad=True)return weights,biasdef forward(self,x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output =[](batch_size, chanels, hight, width)=x.shapefor batch_num in range(batch_size):img_chanel=x[batch_num,:,:,:]print('img_chanel:\n',img_chanel)out_chanel=[]for out_kernel_num in range(self.weights.shape[0]):'''各个输出通道对应的权重'''kernels=self.weights[out_kernel_num,:,:,:]for_sum = []for chanel_img_num in range(chanels):'''对应项进行卷积'''kernel = kernels[chanel_img_num, :, :]_img=img_chanel[chanel_img_num,:,:]print('current input_img:\n', _img)print('current kernel:\n',kernel)if self.padding>0:_img = self.__paddingzeros(_img)#边缘填充print('padded img:\n',_img)out_img,out_shape=self.__myConv(_img,kernel)for_sum.append(out_img.tolist())print('unsumed out img:\n',for_sum)sumed_out_img=np.array(for_sum).sum(axis=0)print('sumed out_img:\n',sumed_out_img)if self.use_bias:sumed_out_img+=self.bias[out_kernel_num].detach().numpy()out_chanel.append(sumed_out_img.tolist())print('out chanel:',out_chanel)output.append(out_chanel)'''计算输出图像的尺寸 ''''''img=x[0,0,:,:]if self.padding>0:img = self.__paddingzeros(img) # 边缘填充kernel=self.weights[0,0,:,:]_,out_shape=self.__myConv(img,kernel)'''output=torch.Tensor(np.array(output).astype(np.float32))print('final output:\n', output)return outputdef __paddingzeros(self,img):'''内部函数,默认 边缘0填充'''if self.padding_str=='zeros':out=torch.zeros((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelif self.padding_str=='ones':out=torch.ones((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelse:raise ValueError('Value error: unexcepted key value of \'padding\'. excepted key values:zeros,ones')def __myConv(self,img, kernel):#单层单核卷积'''内部参数,用于计算单图像单核卷积'''if (self.stride==(1,1))&(self.kernel_size==(3,3)):k1 = int((kernel.shape[0] - 1) / 2)k2 = int((kernel.shape[1] - 1) / 2)out_img = []for i in range(k1, img.shape[0] - k1):for j in range(k2, img.shape[1] - k2):sight = img[i - 1:i + 2, j - 1:j + 2]out_img.append(np.sum(np.multiply(np.array(sight), np.array(kernel))))return torch.from_numpy(np.array(out_img).astype(np.float32).reshape((img.shape[0] - 2 * k1, img.shape[1] - 2 * k2))),(img.shape[0] - 2 * k1, img.shape[1] - 2 * k2)else:k1 = int((kernel.shape[0] - 1) / 2)k2 = int((kernel.shape[1] - 1) / 2)#print('k1,k2=',k1,k2)#print('stride:',self.stride)out_img = []right_line=[]bottom_line=[]botton_right=[]#print('img.shape:',img.shape)for x_index,i in enumerate(range(k1, img.shape[0] - k1)):if x_index%self.stride[1]==0:#纵向移动for y_index,j in enumerate(range(k2, img.shape[1] - k2)):if y_index%self.stride[0]==0:#横向移动#print('current i j', i, j)sight = img[i - k1:i + k2+1, j - k1:j + k2+1]#print(sight)#print(kernel)out_img.append(np.sum(np.multiply(np.array(sight), kernel.detach().numpy())))#print('out img:',out_img)''' 这里考虑到步长大于1时卷积到右侧边缘或下侧会有边缘不足的情况,因此尝试对边界的溢出进行检测与处理 ''''''处理边界冗余值'''if (j+self.stride[0]+k1)>(img.shape[1]-1):#print('列索引',(j+self.stride[0]+k1),'>',(img.shape[1]-k1))#print('检测到达右边界,向右移动并填充后几列')_j=j+self.stride[0]redundancy=img[i - k1:i + k2+1,_j - k1:]split_kernel = kernel[:, :np.array(redundancy).shape[1]]#print(redundancy)#print(split_kernel)right_line.append(np.sum(np.multiply(np.array(redundancy), split_kernel.detach().numpy())))#print( 'right line',right_line)if (i+self.stride[1]+k2)>(img.shape[0]-1):#print('行索引',(i+self.stride[1]),'>',(img.shape[0]-1))#print('检测到达下边界,向下移动并填充下几行')'''达下边界,向下移动并分别获取冗余值以及分割的卷积核,在对两者计算点积和,其他几种也是这种方法'''_i=i+self.stride[1]redundancy=img[_i - k2:, j - k1:j + k2+1]split_kernel=kernel[:np.array(redundancy).shape[0], :]#print(redundancy)#print(split_kernel)bottom_line.append(np.sum(np.multiply(np.array(redundancy), split_kernel.detach().numpy())))#print('bottopn line:',bottom_line)if ((j+self.stride[0]+k1)>(img.shape[1]-1))&((i+self.stride[1]+k2)>(img.shape[0]-1)):#print('达右下角,向右下移动并填充')_i+=self.stride[0]_j+=self.stride[1]redundancy = img[_i - k2:, _j - k1:]split_kernel = kernel[:np.array(redundancy).shape[0], :np.array(redundancy).shape[1]]#print(redundancy)#print(split_kernel)botton_right.append(np.sum(np.multiply(np.array(redundancy),split_kernel.detach().numpy())))out_shape=[0,0]for i in range( img.shape[0] - 2*k1):if i%self.stride[1]==0:out_shape[0]+=1for i in range( img.shape[1] - 2*k2):if i %self.stride[0]==0:out_shape[1]+=1#print(out_shape)if self.use_redundancy:base_img=np.array(out_img).reshape(out_shape)#print(base_img)#print(right_line)#print(bottom_line)#print(botton_right)return self.__Conbine(base_img,right_line,bottom_line,botton_right),self.__Conbine(base_img,right_line,bottom_line,botton_right).shapeelse:return torch.Tensor(np.array(out_img).astype(np.float32).reshape(out_shape)), out_shapedef __Conbine(self,baseimg,rightline,bottomline,rightbottom):'''内置函数,用于将冗余值信息与基本信息组合到一起'''br=np.append(np.array(baseimg),np.array(rightline).reshape(len(rightline),1),axis=1)#print(br)bbr=np.append(np.array(bottomline),np.array(rightbottom))bbrr=bbr.reshape(1,len(bbr))#print(bbr)#print(bbrr)return torch.from_numpy(np.append(br,bbrr,axis=0).astype(np.float32))def get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights, self.bias=weights,biasdef set_weights(self,weights):self.weights=weightsdef set_bias(self,bias):self.bias=biasdef show_img(img):plt.figure()plt.imshow(img, cmap='gray')plt.show()def crossentropy_loss():return torch.nn.CrossEntropyLoss()if __name__=='__main__':# 数据预处理transform = transforms.Compose([transforms.ToTensor()])# 下载mnist库数据并定义读取器train_dataset = torchvision.datasets.MNIST(root='./mnist', train=True, transform=transform, download=True)train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=5000, shuffle=True, num_workers=6)test_dataset = torchvision.datasets.MNIST(root='./mnist', train=False, transform=transform, download=True)test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=10000, shuffle=True, num_workers=0)img = np.array([[[[0, 1, 1, 0, 2],[2, 2, 2, 2, 1],[1, 0, 0, 2, 0],[0, 1, 1, 0, 0],[1, 2, 0, 0, 2]],[[1, 0, 2, 2, 0],[0, 0, 0, 2, 0],[1, 2, 1, 2, 1],[1, 0, 0, 0, 0],[1, 2, 1, 1, 1]],[[2, 1, 2, 0, 0],[1, 0, 0, 1, 0],[0, 2, 1, 0, 1],[0, 1, 2, 2, 2],[2, 1, 0, 0, 1]]]])kernel_weights = torch.Tensor([[[[-1, 1, 0],[0, 1, 0],[0, 1, 1]],[[-1, -1, 0],[0, 0, 0],[0, -1, 0]],[[0, 0, -1],[0, 1, 0],[1, -1, -1]]],[[[1, 1, -1],[-1, -1, 1],[0, -1, 1]],[[0, 1, 0],[-1, 0, -1],[-1, 1, 0]],[[-1, 0, 0],[-1, 0, 1],[-1, 0, 0]]]])bias = torch.Tensor([[1], [0]])target_feature = torch.Tensor([[[[3., 7., 5.],[4., -1., -1.],[2., -6., 4.]],[[2., -7., -8.],[1., -2., -4.],[9., -7., -5.]]]])kernel_weights.requires_grad=Truebias.requires_grad=Trueprint('input img shape:',img.shape)print('kernel shape',kernel_weights.shape)myconv = myMultipassageConv(3, 2, kernel_size=(3,3), padding=1, stride=(2,2), use_bias=True,use_redundancy=False,padding_str='zeros')myconv.set_weights(kernel_weights)myconv.set_bias(bias)output=myconv.forward(torch.Tensor(img.astype(np.float32)))print('output shape:\n',output.shape)loss=crossentropy_loss()l=loss(target_feature,output)l.requires_grad=Truel.backward()print('output shape:\n',output.shape)print(kernel_weights.grad)
将参数设置requires_grad=True,调用backward(),但是发现,输出的grad结果为none,原因可能是因为自定义卷积类中的很多关键地方的计算还是靠numpy来实现的,接下来将其改掉,全用tensor来实现。
#coding:utf-8
import json
import math
import numpy as np
import torch
import torchvision
from matplotlib import pyplot as plt
from torchvision import transformsclass myMultipassageConv():def __init__(self,in_chanels,out_chanels,kernel_size,padding,stride=(1,1),padding_str='zeros',use_bias=True,use_redundancy=False):''':param in_chanels: 输入通道数:param out_chanels: 输出通道数:param kernel_size: 卷积核尺寸 tuple类型:param padding: 边缘填充数:param stride: 步长 tuple类型 (可选)默认为(1,1):param padding_str: 边缘填充方式 str类型(可选):param use_bias: 是否使用偏置 bool类型(可选)use_redundancy:是否使用冗余值 bool 默认舍弃'''self.kernel_size=kernel_sizeself.in_chanels=in_chanelsself.out_chanels=out_chanelsself.padding=paddingself.stride=stride(self.kernel_hight,self.kernel_width)=kernel_sizeself.weights,self.bias=self.__parameters()self.padding_str=padding_strself.use_bias=use_biasself.use_redundancy=use_redundancydef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights=torch.randn(self.out_chanels,self.in_chanels,self.kernel_hight,self.kernel_width,requires_grad=True)'''偏置尺寸为out_kernels'''bias=torch.randn(self.out_chanels,1,requires_grad=True)return weights,biasdef forward(self,x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output =[](batch_size, chanels, hight, width)=x.shapefor batch_num in range(batch_size):img_chanel=x[batch_num,:,:,:]print('img_chanel:\n',img_chanel)out_chanel=[]for out_kernel_num in range(self.weights.shape[0]):'''各个输出通道对应的权重'''kernels=self.weights[out_kernel_num,:,:,:]for_sum = []for chanel_img_num in range(chanels):'''对应项进行卷积'''kernel = kernels[chanel_img_num, :, :]_img=img_chanel[chanel_img_num,:,:]print('current input_img:\n', _img)print('current kernel:\n',kernel)if self.padding>0:_img = self.__paddingzeros(_img)#边缘填充print('padded img:\n',_img)out_img,out_shape=self.__myConv(_img,kernel)for_sum.append(out_img)print('unsumed out img:\n',for_sum)for_sum=torch.stack(for_sum,0)sumed_out_img=torch.sum(for_sum,dim=0)print('sumed out_img:\n',sumed_out_img)if self.use_bias:sumed_out_img+=self.bias[out_kernel_num]out_chanel.append(sumed_out_img)print('out chanel:',out_chanel)out_chanel=torch.stack(out_chanel)output.append(out_chanel)output=torch.stack(output,0)'''计算输出图像的尺寸 ''''''img=x[0,0,:,:]if self.padding>0:img = self.__paddingzeros(img) # 边缘填充kernel=self.weights[0,0,:,:]_,out_shape=self.__myConv(img,kernel)'''print('final output:\n', output)return outputdef __paddingzeros(self,img):'''内部函数,默认 边缘0填充'''if self.padding_str=='zeros':out=torch.zeros((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelif self.padding_str=='ones':out=torch.ones((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelse:raise ValueError('Value error: unexcepted key value of \'padding\'. excepted key values:zeros,ones')def __myConv(self,img, kernel):#单层单核卷积'''内部参数,用于计算单图像单核卷积'''k1 = int((kernel.shape[0] - 1) / 2)k2 = int((kernel.shape[1] - 1) / 2)#print('k1,k2=',k1,k2)#print('stride:',self.stride)out_img = []right_line=[]bottom_line=[]botton_right=[]#print('img.shape:',img.shape)for x_index,i in enumerate(range(k1, img.shape[0] - k1)):if x_index%self.stride[1]==0:#纵向移动for y_index,j in enumerate(range(k2, img.shape[1] - k2)):if y_index%self.stride[0]==0:#横向移动#print('current i j', i, j)sight = img[i - k1:i + k2+1, j - k1:j + k2+1]#print(sight)#print(kernel)out_img.append(torch.sum(torch.mul(torch.Tensor(sight), kernel)))#print('out img:',out_img)''' 这里考虑到步长大于1时卷积到右侧边缘或下侧会有边缘不足的情况,因此尝试对边界的溢出进行检测与处理 ''''''处理边界冗余值'''if (j+self.stride[0]+k1)>(img.shape[1]-1):#print('列索引',(j+self.stride[0]+k1),'>',(img.shape[1]-k1))#print('检测到达右边界,向右移动并填充后几列')_j=j+self.stride[0]redundancy=img[i - k1:i + k2+1,_j - k1:]split_kernel = kernel[:, :np.array(redundancy).shape[1]]#print(redundancy)#print(split_kernel)right_line.append(torch.sum(torch.mul(torch.Tensor(redundancy), split_kernel)))#print( 'right line',right_line)if (i+self.stride[1]+k2)>(img.shape[0]-1):#print('行索引',(i+self.stride[1]),'>',(img.shape[0]-1))#print('检测到达下边界,向下移动并填充下几行')'''达下边界,向下移动并分别获取冗余值以及分割的卷积核,在对两者计算点积和,其他几种也是这种方法'''_i=i+self.stride[1]redundancy=img[_i - k2:, j - k1:j + k2+1]split_kernel=kernel[:np.array(redundancy).shape[0], :]#print(redundancy)#print(split_kernel)bottom_line.append(torch.sum(torch.mul(torch.Tensor(redundancy), split_kernel)))#print('bottopn line:',bottom_line)if ((j+self.stride[0]+k1)>(img.shape[1]-1))&((i+self.stride[1]+k2)>(img.shape[0]-1)):#print('达右下角,向右下移动并填充')_i+=self.stride[0]_j+=self.stride[1]redundancy = img[_i - k2:, _j - k1:]split_kernel = kernel[:np.array(redundancy).shape[0], :np.array(redundancy).shape[1]]#print(redundancy)#print(split_kernel)botton_right.append(torch.sum(torch.mul(torch.Tensor(redundancy), split_kernel)))out_shape=[0,0]for i in range( img.shape[0] - 2*k1):if i%self.stride[1]==0:out_shape[0]+=1for i in range( img.shape[1] - 2*k2):if i %self.stride[0]==0:out_shape[1]+=1#print(out_shape)if self.use_redundancy:base_img=np.array(out_img).reshape(out_shape)#print(base_img)#print(right_line)#print(bottom_line)#print(botton_right)return self.__Conbine(base_img,right_line,bottom_line,botton_right),self.__Conbine(base_img,right_line,bottom_line,botton_right).shapeelse:return torch.stack(out_img).reshape(out_shape), out_shapedef __Conbine(self,baseimg,rightline,bottomline,rightbottom):'''内置函数,用于将冗余值信息与基本信息组合到一起'''br=np.append(np.array(baseimg),np.array(rightline).reshape(len(rightline),1),axis=1)#print(br)bbr=np.append(np.array(bottomline),np.array(rightbottom))bbrr=bbr.reshape(1,len(bbr))#print(bbr)#print(bbrr)return torch.from_numpy(np.append(br,bbrr,axis=0).astype(np.float32))def get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights, self.bias=weights,biasdef set_weights(self,weights):self.weights=weightsdef set_bias(self,bias):self.bias=biasdef show_img(img):plt.figure()plt.imshow(img, cmap='gray')plt.show()if __name__=='__main__':# 数据预处理transform = transforms.Compose([transforms.ToTensor()])# 下载mnist库数据并定义读取器train_dataset = torchvision.datasets.MNIST(root='./mnist', train=True, transform=transform, download=True)train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=5000, shuffle=True, num_workers=6)test_dataset = torchvision.datasets.MNIST(root='./mnist', train=False, transform=transform, download=True)test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=10000, shuffle=True, num_workers=0)img = np.array([[[[0, 1, 1, 0, 2],[2, 2, 2, 2, 1],[1, 0, 0, 2, 0],[0, 1, 1, 0, 0],[1, 2, 0, 0, 2]],[[1, 0, 2, 2, 0],[0, 0, 0, 2, 0],[1, 2, 1, 2, 1],[1, 0, 0, 0, 0],[1, 2, 1, 1, 1]],[[2, 1, 2, 0, 0],[1, 0, 0, 1, 0],[0, 2, 1, 0, 1],[0, 1, 2, 2, 2],[2, 1, 0, 0, 1]]]])kernel_weights = torch.Tensor([[[[-1, 1, 0],[0, 1, 0],[0, 1, 1]],[[-1, -1, 0],[0, 0, 0],[0, -1, 0]],[[0, 0, -1],[0, 1, 0],[1, -1, -1]]],[[[1, 1, -1],[-1, -1, 1],[0, -1, 1]],[[0, 1, 0],[-1, 0, -1],[-1, 1, 0]],[[-1, 0, 0],[-1, 0, 1],[-1, 0, 0]]]])bias = torch.Tensor([[1], [0]])target_feature = torch.Tensor([[[[3., 7., 5.],[4., -1., -1.],[2., -6., 4.]],[[2., -7., -8.],[1., -2., -4.],[9., -7., -5.]]]])kernel_weights.requires_grad=Truebias.requires_grad=Trueprint('input img shape:',img.shape)print('kernel shape',kernel_weights.shape)myconv = myMultipassageConv(3, 2, kernel_size=(3,3), padding=1, stride=(2,2), use_bias=True,use_redundancy=False,padding_str='zeros')myconv.set_weights(kernel_weights)myconv.set_bias(bias)output=myconv.forward(torch.Tensor(img.astype(np.float32)))print('output shape:\n',output.shape)loss=torch.nn.CrossEntropyLoss()l=loss(target_feature,output)l.backward()print('output shape:\n',output.shape)print(myconv.weights.grad)
花了一些时间,终于全部改好了。最后输出的是交叉熵损失loss对myconv的kernel_weights的导数。(use_redundancy还没改,先用着)
有时间我会整理一下numpy和torch的代码实现的对应关系。
LeNet模型中还用到了池化层,因此还需要将numpy的池化层改成tensor的。
import matplotlib.pyplot as plt
import numpy as np
import torchclass myPooling():def __init__(self,size,pooling_mode,stride):self.h,self.w=sizeself.pool_mode=pooling_modeself.stride=stridedef __mypool(self, img):output=[]for x_index, i in enumerate(range(img.shape[0] )):if x_index % self.stride == 0: # 纵向移动for y_index, j in enumerate(range(img.shape[1])):if y_index % self.stride== 0: # 横向移动_img=img[i:i+self.h,j:j+self.w]print(_img)if self.pool_mode == 'ave':output.append(torch.mean(_img))elif self.pool_mode == 'max':output.append(torch.max(_img))elif self.pool_mode == 'min':output.append(torch.min(_img))else:raise ValueError('Value error: unexcepted key value of \'pooling_mode\'. excepted key values:ave,max,min')out_shape = [0, 0]for i in range(img.shape[0]):if i % self.stride == 0:out_shape[0] += 1for i in range(img.shape[1]):if i % self.stride == 0:out_shape[1] += 1print(out_shape)return torch.stack(output).reshape(out_shape), out_shapedef forward(self, x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output = [](batch_size, chanels, hight, width) = x.shapefor batch_num in range(batch_size):img = x[batch_num, :, :, :]chanel=[]for chanel_num in range(chanels):_img = img[chanel_num, :, :]out_img,out_shape=self.__mypool(_img)chanel.append(out_img)chanel=torch.stack(chanel)output.append(chanel)output=torch.stack(output)return outputif __name__=='__main__':img=torch.Tensor([[[[1,0,0,0,0],[0,1,0,0,0],[0,0,1,0,0],[0,0,0,1,0],[0,0,0,0,1]]]])img.requires_grad=Truemypool=myPooling(size=(2,2),stride=1,pooling_mode='ave')out_img=mypool.forward(img)print(out_img)
由于池化层没有权重,不需要参数更新,因此只需要检查是否能对requires_grad=True的输入特征图进行处理,并输出requires_grad=True的特征图即可。
可以看到是可以的,那么基本上是改好了,至少用于实现LeNet应该是没问题。下面再来回顾一下LeNet神经网络:
它一共有三次卷积,两次池化,最后还有2层全连接层。(C5的输出是 120 ∗ 1 ∗ 1 120*1*1 120∗1∗1被平铺到120后输入到全连接层)
池化层和卷积层都有了,现在还需要全连接层,直接用torch写的,就不用再改了,因为之前都没写过基于numpy的全连接层的类,所以也没有可改的,那就现在临时写了一个。我记得当时前馈神经网络的时候就是用numpy写了几个函数,也没有写过封装好的类。为了体现更好的逻辑关系,把他们封装好会更好一些,同时实例化时也更简洁美观一些,当时写卷积类时也是这么想的。
简单写了一个。
线性层:
#coding:utf-8
import torchclass mylinear():def __init__(self,in_size,out_size,use_bias=True):self.in_size=in_sizeself.out_size=out_sizeself.weights,self.bias=self.__parameters()self.use_bias=use_biasdef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为outsize,insize'''weights = torch.randn(self.out_size, self.in_size, requires_grad=True)'''偏置尺寸为outsize 1'''bias = torch.randn(self.out_size, 1, requires_grad=True)return weights, biasdef forward(self,X):'''前向计算'''output=[]for i in range(self.out_size):w=self.weights[i,:]b=self.bias[i]if self.use_bias:y=torch.mul(w,X)+belse:y=torch.mul(w,X)output.append(y)output=torch.stack(output)return outputdef get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights=weightsself.bias=biasif __name__=='__main__':X=torch.Tensor([1,2,3,4,5,6])w=torch.Tensor([[0.1,0.2,0.3 ,0.4, 0.5,0.6],[0.1, 0.2, 0.3, 0.4, 0.5, 0.6],[0.1,0.2,0.3 ,0.4, 0.5,0.6],[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]])b=torch.Tensor([[0.1],[0.2],[0.3],[0.4]])linear=mylinear(in_size=6,out_size=4)output=linear.forward(X)print(linear.get_parameters())print(output)
还得试一下全连接类能否自动求梯度。
#coding:utf-8
import torchclass mylinear():def __init__(self,in_size,out_size,use_bias=True):self.in_size=in_sizeself.out_size=out_sizeself.weights,self.bias=self.__parameters()self.use_bias=use_biasdef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights = torch.randn(self.out_size, self.in_size, requires_grad=True)'''偏置尺寸为out_kernels'''bias = torch.randn(self.out_size, 1, requires_grad=True)return weights, biasdef forward(self,X):'''前向计算'''output=[]for i in range(self.out_size):w=self.weights[i,:]b=self.bias[i]if self.use_bias:y=torch.mul(w,X)+belse:y=torch.mul(w,X)output.append(y)output=torch.stack(output)return outputdef get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights=weightsself.bias=biasif __name__=='__main__':X=torch.Tensor([1,2,3,4,5,6])target=torch.Tensor([[0.1,0.2,0.3 ,0.4, 0.5,0.6],[0.1, 0.2, 0.3, 0.4, 0.5, 0.6],[0.1,0.2,0.3 ,0.4, 0.5,0.6],[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]])b=torch.Tensor([[0.1],[0.2],[0.3],[0.4]])linear=mylinear(in_size=6,out_size=4)output=linear.forward(X)loss=torch.nn.CrossEntropyLoss()l=loss(target,output)l.backward()print(linear.get_parameters())print(output)print(linear.weights.grad)print(linear.bias.grad)
最后两项是loss对linear 的weights、bias的梯度。
大功告成,接下来实例化组建LeNet网络就行了。
Lenet-5
#coding:utf-8
import json
import math
import numpy as np
import torch
import torchvision
from matplotlib import pyplot as plt
from torchvision import transformsclass myMultipassageConv():def __init__(self,in_chanels,out_chanels,kernel_size,padding,stride=(1,1),padding_str='zeros',use_bias=True,use_redundancy=False):''':param in_chanels: 输入通道数:param out_chanels: 输出通道数:param kernel_size: 卷积核尺寸 tuple类型:param padding: 边缘填充数:param stride: 步长 tuple类型 (可选)默认为(1,1):param padding_str: 边缘填充方式 str类型(可选):param use_bias: 是否使用偏置 bool类型(可选)use_redundancy:是否使用冗余值 bool 默认舍弃'''self.kernel_size=kernel_sizeself.in_chanels=in_chanelsself.out_chanels=out_chanelsself.padding=paddingself.stride=stride(self.kernel_hight,self.kernel_width)=kernel_sizeself.weights,self.bias=self.__parameters()self.padding_str=padding_strself.use_bias=use_biasself.use_redundancy=use_redundancydef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights=torch.randn(self.out_chanels,self.in_chanels,self.kernel_hight,self.kernel_width,requires_grad=True)'''偏置尺寸为out_kernels'''bias=torch.randn(self.out_chanels,1,requires_grad=True)return weights,biasdef forward(self,x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output =[](batch_size, chanels, hight, width)=x.shapefor batch_num in range(batch_size):img_chanel=x[batch_num,:,:,:]#print('img_chanel:\n',img_chanel)out_chanel=[]for out_kernel_num in range(self.weights.shape[0]):'''各个输出通道对应的权重'''kernels=self.weights[out_kernel_num,:,:,:]for_sum = []for chanel_img_num in range(chanels):'''对应项进行卷积'''kernel = kernels[chanel_img_num, :, :]_img=img_chanel[chanel_img_num,:,:]#print('current input_img:\n', _img)#print('current kernel:\n',kernel)if self.padding>0:_img = self.__paddingzeros(_img)#边缘填充#print('padded img:\n',_img)out_img,out_shape=self.__myConv(_img,kernel)for_sum.append(out_img)#print('unsumed out img:\n',for_sum)for_sum=torch.stack(for_sum,0)sumed_out_img=torch.sum(for_sum,dim=0)#print('sumed out_img:\n',sumed_out_img)if self.use_bias:sumed_out_img+=self.bias[out_kernel_num]out_chanel.append(sumed_out_img)#print('out chanel:',out_chanel)out_chanel=torch.stack(out_chanel)output.append(out_chanel)output=torch.stack(output,0)'''计算输出图像的尺寸 ''''''img=x[0,0,:,:]if self.padding>0:img = self.__paddingzeros(img) # 边缘填充kernel=self.weights[0,0,:,:]_,out_shape=self.__myConv(img,kernel)'''#print('final output:\n', output)return outputdef __paddingzeros(self,img):'''内部函数,默认 边缘0填充'''if self.padding_str=='zeros':out=torch.zeros((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelif self.padding_str=='ones':out=torch.ones((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelse:raise ValueError('Value error: unexcepted key value of \'padding\'. excepted key values:zeros,ones')def __myConv(self,img, kernel):#单层单核卷积'''内部参数,用于计算单图像单核卷积'''k1 = int((kernel.shape[0] - 1) / 2)k2 = int((kernel.shape[1] - 1) / 2)#print('k1,k2=',k1,k2)#print('stride:',self.stride)out_img = []right_line=[]bottom_line=[]botton_right=[]#print('img.shape:',img.shape)for x_index,i in enumerate(range(k1, img.shape[0] - k1)):if x_index%self.stride[1]==0:#纵向移动for y_index,j in enumerate(range(k2, img.shape[1] - k2)):if y_index%self.stride[0]==0:#横向移动#print('current i j', i, j)sight = img[i - k1:i + k2+1, j - k1:j + k2+1]#print(sight)#print(kernel)out_img.append(torch.sum(torch.mul(torch.Tensor(sight), kernel)))#print('out img:',out_img)''' 这里考虑到步长大于1时卷积到右侧边缘或下侧会有边缘不足的情况,因此尝试对边界的溢出进行检测与处理 ''''''处理边界冗余值''''''if (j+self.stride[0]+k1)>(img.shape[1]-1):#print('列索引',(j+self.stride[0]+k1),'>',(img.shape[1]-k1))#print('检测到达右边界,向右移动并填充后几列')_j=j+self.stride[0]redundancy=img[i - k1:i + k2+1,_j - k1:]split_kernel = kernel[:, :np.array(redundancy).shape[1]]#print(redundancy)#print(split_kernel)right_line.append(torch.sum(torch.mul(torch.Tensor(redundancy), split_kernel)))#print( 'right line',right_line)if (i+self.stride[1]+k2)>(img.shape[0]-1):#print('行索引',(i+self.stride[1]),'>',(img.shape[0]-1))#print('检测到达下边界,向下移动并填充下几行')''''''达下边界,向下移动并分别获取冗余值以及分割的卷积核,在对两者计算点积和,其他几种也是这种方法''''''_i=i+self.stride[1]redundancy=img[_i - k2:, j - k1:j + k2+1]split_kernel=kernel[:np.array(redundancy).shape[0], :]#print(redundancy)#print(split_kernel)bottom_line.append(torch.sum(torch.mul(torch.Tensor(redundancy), split_kernel)))#print('bottopn line:',bottom_line)if ((j+self.stride[0]+k1)>(img.shape[1]-1))&((i+self.stride[1]+k2)>(img.shape[0]-1)):#print('达右下角,向右下移动并填充')_i+=self.stride[0]_j+=self.stride[1]redundancy = img[_i - k2:, _j - k1:]split_kernel = kernel[:np.array(redundancy).shape[0], :np.array(redundancy).shape[1]]#print(redundancy)#print(split_kernel)botton_right.append(torch.sum(torch.mul(torch.Tensor(redundancy), split_kernel)))'''out_shape=[0,0]for i in range( img.shape[0] - 2*k1):if i%self.stride[1]==0:out_shape[0]+=1for i in range( img.shape[1] - 2*k2):if i %self.stride[0]==0:out_shape[1]+=1#print(out_shape)if self.use_redundancy:base_img=np.array(out_img).reshape(out_shape)#print(base_img)#print(right_line)#print(bottom_line)#print(botton_right)return self.__Conbine(base_img,right_line,bottom_line,botton_right),self.__Conbine(base_img,right_line,bottom_line,botton_right).shapeelse:return torch.stack(out_img).reshape(out_shape), out_shapedef __Conbine(self,baseimg,rightline,bottomline,rightbottom):'''内置函数,用于将冗余值信息与基本信息组合到一起'''br=np.append(np.array(baseimg),np.array(rightline).reshape(len(rightline),1),axis=1)#print(br)bbr=np.append(np.array(bottomline),np.array(rightbottom))bbrr=bbr.reshape(1,len(bbr))#print(bbr)#print(bbrr)return torch.from_numpy(np.append(br,bbrr,axis=0).astype(np.float32))def get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights, self.bias=weights,biasdef set_weights(self,weights):self.weights=weightsdef set_bias(self,bias):self.bias=biasdef show_img(img):plt.figure()plt.imshow(img, cmap='gray')plt.show()class mylinear():def __init__(self,in_size,out_size,use_bias=True):self.in_size=in_sizeself.out_size=out_sizeself.weights,self.bias=self.__parameters()self.use_bias=use_biasdef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights = torch.randn(self.out_size, self.in_size, requires_grad=True)'''偏置尺寸为out_kernels'''bias = torch.randn(self.out_size, 1, requires_grad=True)return weights, biasdef forward(self,X):'''前向计算'''output=[]for i in range(self.out_size):w=self.weights[i,:]b=self.bias[i]if self.use_bias:y=torch.mul(w,X)+belse:y=torch.mul(w,X)output.append(y)output=torch.stack(output)return outputdef get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights=weightsself.bias=biasclass myPooling():def __init__(self,size,pooling_mode,stride):self.h,self.w=sizeself.pool_mode=pooling_modeself.stride=stridedef __mypool(self, img):output=[]for x_index, i in enumerate(range(img.shape[0] )):if x_index % self.stride == 0: # 纵向移动for y_index, j in enumerate(range(img.shape[1])):if y_index % self.stride== 0: # 横向移动_img=img[i:i+self.h,j:j+self.w]print(_img)if self.pool_mode == 'ave':output.append(torch.mean(_img))elif self.pool_mode == 'max':output.append(torch.max(_img))elif self.pool_mode == 'min':output.append(torch.min(_img))else:raise ValueError('Value error: unexcepted key value of \'pooling_mode\'. excepted key values:ave,max,min')out_shape = [0, 0]for i in range(img.shape[0]):if i % self.stride == 0:out_shape[0] += 1for i in range(img.shape[1]):if i % self.stride == 0:out_shape[1] += 1#print(out_shape)return torch.stack(output).reshape(out_shape), out_shapedef forward(self, x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output = [](batch_size, chanels, hight, width) = x.shapefor batch_num in range(batch_size):img = x[batch_num, :, :, :]chanel=[]for chanel_num in range(chanels):_img = img[chanel_num, :, :]out_img,out_shape=self.__mypool(_img)chanel.append(out_img)chanel=torch.stack(chanel)output.append(chanel)output=torch.stack(output)return outputclass LENet():def __init__(self):self.conv1=myMultipassageConv(in_chanels=1,out_chanels=6,kernel_size=(5,5),padding=0)#stride默认为(1,1)self.conv2=myMultipassageConv(in_chanels=6,out_chanels=16,kernel_size=(5,5),padding=0)self.conv3=myMultipassageConv(in_chanels=16,out_chanels=120,kernel_size=(5,5),padding=0)self.pool1=myPooling(size=(2,2),pooling_mode='ave',stride=(2,2))self.pool2=myPooling(size=(2,2),pooling_mode='ave',stride=(2,2))self.linear1=mylinear(in_size=120,out_size=84)self.linear2=mylinear(in_size=84,out_size=10)def forward(self,x):x1=self.pool1.forward(self.conv1.forward(x))x2=self.pool2.forward(self.conv2.forward(x1))x3=self.conv3.forward(x2)x4=self.linear1.forward(x3)x5=self.linear2.forward(x4)return x5if __name__=='__main__':# 数据预处理transform = transforms.Compose([transforms.ToTensor()])# 下载mnist库数据并定义读取器train_dataset = torchvision.datasets.MNIST(root='./mnist', train=True, transform=transform, download=True)train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=5000, shuffle=True, num_workers=6)test_dataset = torchvision.datasets.MNIST(root='./mnist', train=False, transform=transform, download=True)test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=10000, shuffle=True, num_workers=0)for X,target in train_dataloader:test_X=X[0]test_target=target[0]print(X)print(target)breaklenet=LENet()output=lenet.forward(X)print(output)
一开始还好好的,打印计算过程十分顺畅,但是迟迟没有输出结果,然后就把打印过程注释掉了,觉得会运行的快一点,但是万万没想到。
这一运行电脑都要炸了,而且最后报错
查了一下,是内存已满的意思,内存已满,说明程序设计的不好,因为自己设计的代码有大量的遍历、迭代还有临时列表等占内存的东西,所以最终导致内存炸了。我是真的累了。
那就到此为止吧,再写就不礼貌了。
我在想用内存在大一点的电脑能不能跑下来。
…………………………………………………………以上为day1……………………………………………………………
第一天做的有点急了,没有改那个数据集加载那块的代码,直接粘的上次的,上次为了加快训练速度,把batchsize设为了5000和10000.难怪内存崩了。。。。
所以就改了一下。除此之外还忘记了对最后一层卷积的结果进行展平。。。
因为要支持多通道,所以这里的展平是这样的。否则会将所有通道都铺到一起。这里的reshape效果和flatten是一样的。
对应的线性层也修正了一下,现在真正支持多通道了。
这一次batchsize只有10,运行没有问题。
利用了上次老师使用数据集的方法,迭代一次就break掉,这样就拿到了第一次的dataset,真神奇。我就直接用了这个了,没有自建一个(1,1,32,32)尺寸的图像。用这个顺便还能测试一下多通道能不能正常运行。
还忽略了一点就是输入尺寸的问题,minist的数据集尺寸为 28 ∗ 28 28*28 28∗28,LeNet的输入要求为 32 ∗ 32 32*32 32∗32,所以实际上需要对输入补两层零。
除此之外,还将之前卷积层那个烦人的冗余值相关的代码删去了,因为这里也用不到。代码简洁了许多。(不过我还没打算放弃它)
#coding:utf-8
import json
import math
import numpy as np
import torch
import torchvision
from matplotlib import pyplot as plt
from torchvision import transformsclass myMultipassageConv():def __init__(self,in_chanels,out_chanels,kernel_size,padding,stride=(1,1),padding_str='zeros',use_bias=True,use_redundancy=False):''':param in_chanels: 输入通道数:param out_chanels: 输出通道数:param kernel_size: 卷积核尺寸 tuple类型:param padding: 边缘填充数:param stride: 步长 tuple类型 (可选)默认为(1,1):param padding_str: 边缘填充方式 str类型(可选):param use_bias: 是否使用偏置 bool类型(可选)use_redundancy:是否使用冗余值 bool 默认舍弃'''self.kernel_size=kernel_sizeself.in_chanels=in_chanelsself.out_chanels=out_chanelsself.padding=paddingself.stride=stride(self.kernel_hight,self.kernel_width)=kernel_sizeself.weights,self.bias=self.__parameters()self.padding_str=padding_strself.use_bias=use_biasself.use_redundancy=use_redundancydef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights=torch.randn(self.out_chanels,self.in_chanels,self.kernel_hight,self.kernel_width,requires_grad=True)'''偏置尺寸为out_kernels'''bias=torch.randn(self.out_chanels,1,requires_grad=True)return weights,biasdef forward(self,x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output =[](batch_size, chanels, hight, width)=x.shapefor batch_num in range(batch_size):img_chanel=x[batch_num,:,:,:]#print('img_chanel:\n',img_chanel)out_chanel=[]for out_kernel_num in range(self.weights.shape[0]):'''各个输出通道对应的权重'''kernels=self.weights[out_kernel_num,:,:,:]for_sum = []for chanel_img_num in range(chanels):'''对应项进行卷积'''kernel = kernels[chanel_img_num, :, :]_img=img_chanel[chanel_img_num,:,:]#print('current input_img:\n', _img)#print('current kernel:\n',kernel)if self.padding>0:_img = self.__paddingzeros(_img)#边缘填充#print('padded img:\n',_img)out_img,out_shape=self.__myConv(_img,kernel)for_sum.append(out_img)#print('unsumed out img:\n',for_sum)for_sum=torch.stack(for_sum,0)sumed_out_img=torch.sum(for_sum,dim=0)#print('sumed out_img:\n',sumed_out_img)if self.use_bias:sumed_out_img+=self.bias[out_kernel_num]out_chanel.append(sumed_out_img)#print('out chanel:',out_chanel)out_chanel=torch.stack(out_chanel)output.append(out_chanel)output=torch.stack(output,0)#print('final output:\n', output)return outputdef __paddingzeros(self,img):'''内部函数,默认 边缘0填充'''if self.padding_str=='zeros':out=torch.zeros((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelif self.padding_str=='ones':out=torch.ones((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelse:raise ValueError('Value error: unexcepted key value of \'padding\'. excepted key values:zeros,ones')def __myConv(self,img, kernel):#单层单核卷积'''内部参数,用于计算单图像单核卷积'''k1 = int((kernel.shape[0] - 1) / 2)k2 = int((kernel.shape[1] - 1) / 2)#print('k1,k2=',k1,k2)#print('stride:',self.stride)out_img = []right_line=[]bottom_line=[]botton_right=[]#print('img.shape:',img.shape)for x_index,i in enumerate(range(k1, img.shape[0] - k1)):if x_index%self.stride[1]==0:#纵向移动for y_index,j in enumerate(range(k2, img.shape[1] - k2)):if y_index%self.stride[0]==0:#横向移动#print('current i j', i, j)sight = img[i - k1:i + k2+1, j - k1:j + k2+1]#print(sight)#print(kernel)out_img.append(torch.sum(torch.mul(torch.Tensor(sight), kernel)))#print('out img:',out_img)out_shape=[0,0]for i in range( img.shape[0] - 2*k1):if i%self.stride[1]==0:out_shape[0]+=1for i in range( img.shape[1] - 2*k2):if i %self.stride[0]==0:out_shape[1]+=1#print(out_shape)return torch.stack(out_img).reshape(out_shape), out_shapedef get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights, self.bias=weights,biasdef set_weights(self,weights):self.weights=weightsdef set_bias(self,bias):self.bias=biasclass mylinear():def __init__(self,in_size,out_size,use_bias=True):self.in_size=in_sizeself.out_size=out_sizeself.weights,self.bias=self.__parameters()self.use_bias=use_biasdef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights = torch.randn(self.out_size, self.in_size, requires_grad=True)'''偏置尺寸为out_kernels'''bias = torch.randn(self.out_size, 1, requires_grad=True)return weights, biasdef forward(self,X):'''前向计算''''''X.shape batchsize,insize'''output=[]for batchnum in range(X.shape[0]):x=X[batchnum,:]batch_output=[]for i in range(self.out_size):w=self.weights[i,:]b=self.bias[i]if self.use_bias:y=torch.sum(torch.mul(w,x))+belse:y=torch.sum(torch.mul(w,x))batch_output.append(y)batch_output=torch.stack(batch_output,dim=1)output.append(batch_output.squeeze())output=torch.stack(output)return outputdef get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights=weightsself.bias=biasclass myPooling():def __init__(self,size,pooling_mode,stride):self.h,self.w=sizeself.pool_mode=pooling_modeself.stride=stridedef __mypool(self, img):output=[]for x_index, i in enumerate(range(img.shape[0] )):if x_index % self.stride[1] == 0: # 纵向移动for y_index, j in enumerate(range(img.shape[1])):if y_index % self.stride[0]== 0: # 横向移动_img=img[i:i+self.h,j:j+self.w]#print(_img)if self.pool_mode == 'ave':output.append(torch.mean(_img))elif self.pool_mode == 'max':output.append(torch.max(_img))elif self.pool_mode == 'min':output.append(torch.min(_img))else:raise ValueError('Value error: unexcepted key value of \'pooling_mode\'. excepted key values:ave,max,min')out_shape = [0, 0]for i in range(img.shape[0]):if i % self.stride[0] == 0:out_shape[0] += 1for i in range(img.shape[1]):if i % self.stride[1] == 0:out_shape[1] += 1#print(out_shape)return torch.stack(output).reshape(out_shape), out_shapedef forward(self, x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output = [](batch_size, chanels, hight, width) = x.shapefor batch_num in range(batch_size):img = x[batch_num, :, :, :]chanel=[]for chanel_num in range(chanels):_img = img[chanel_num, :, :]out_img,out_shape=self.__mypool(_img)chanel.append(out_img)chanel=torch.stack(chanel)output.append(chanel)output=torch.stack(output)return outputclass LENet():def __init__(self):self.conv1=myMultipassageConv(in_chanels=1,out_chanels=6,kernel_size=(5,5),padding=2)#stride默认为(1,1)self.conv2=myMultipassageConv(in_chanels=6,out_chanels=16,kernel_size=(5,5),padding=0)self.conv3=myMultipassageConv(in_chanels=16,out_chanels=120,kernel_size=(5,5),padding=0)self.pool1=myPooling(size=(2,2),pooling_mode='ave',stride=(2,2))self.pool2=myPooling(size=(2,2),pooling_mode='ave',stride=(2,2))self.linear1=mylinear(in_size=120,out_size=84)self.linear2=mylinear(in_size=84,out_size=10)self.relu=torch.nn.ReLU()def forward(self,x):#print('x:\n',x)print('x.shape:\n',x.shape)x1=self.relu(self.conv1.forward(x))#print('X1:\n',x1)print('x1.shape:\n',x1.shape)x2=self.pool1.forward(x1)#print('x2:\n',x2)print('x2.shape:\n',x2.shape)x3=self.relu(self.conv2.forward(x2))#print('x3:\n',x3)print('x3.shape:\n',x3.shape)x4=self.pool2.forward(x3)#print('x4:\n',x4)print('x4.shape:\n',x4.shape)x5=self.conv3.forward(x4)#print('x5:\n',x5)print('x5.shape:\n',x5.shape)x5=x5.reshape(x.shape[0],-1)#print('flatten x5:\n', x5)print('flatten x5.shape:\n',x5.shape)x6=self.linear1.forward(x5)#print('x6:\n',x6)print('x6.shape:\n',x6.shape)x7=self.linear2.forward(x6)#print('x7:\n',x7)print('x7.shape:\n',x7.shape)return x7if __name__=='__main__':# 数据预处理transform = transforms.Compose([transforms.ToTensor()])# 下载mnist库数据并定义读取器train_dataset = torchvision.datasets.MNIST(root='./mnist', train=True, transform=transform, download=True)train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=10, shuffle=True, num_workers=6)test_dataset = torchvision.datasets.MNIST(root='./mnist', train=False, transform=transform, download=True)test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=10, shuffle=True, num_workers=0)for X,target in train_dataloader:test_X=Xtest_target=targetbreaklenet=LENet()output=lenet.forward(test_X)#print(output)
E:\anaconda\envs\pytorch\pythonw.exe "C:/Users/lenovo/PycharmProjects/pythonProject1/deep_learning/实验六 卷积/LeNET for MINIST/LeNet for mnist.py"
x.shape:torch.Size([10, 1, 28, 28])
x1.shape:torch.Size([10, 6, 28, 28])
x2.shape:torch.Size([10, 6, 14, 14])
x3.shape:torch.Size([10, 16, 10, 10])
x4.shape:torch.Size([10, 16, 5, 5])
x5.shape:torch.Size([10, 120, 1, 1])
flatten x5.shape:torch.Size([10, 120])
x6.shape:torch.Size([10, 84])
x7.shape:torch.Size([10, 10])Process finished with exit code 0
附:测试了捆绑数的极限,batchsize为200的时候就很吃力了。运行了大概一两分钟。
你以为完事了?还没有,还需要参数更新。
总共有三个卷积层需要更新,两个全连接层需要更新。
此外dataset中的labels只是分类的标签,而网络的输出是一维的10个元素 ,因此要把标签规范到相同的形式。
for X,target in train_dataloader:test_X=Xtest_target=targetprint(test_X)print(test_target)target_list=[]for target in test_target:if target==1:target_list.append(torch.Tensor([1,0,0,0,0,0,0,0,0,0]))elif target==2:target_list.append(torch.Tensor([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]))elif target == 3:target_list.append(torch.Tensor([0, 0, 1, 0, 0, 0, 0, 0, 0, 0]))elif target == 4:target_list.append(torch.Tensor([0, 0, 0, 1, 0, 0, 0, 0, 0, 0]))elif target == 5:target_list.append(torch.Tensor([0, 0, 0, 0, 1, 0, 0, 0, 0, 0]))elif target == 6:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 1, 0, 0, 0, 0]))elif target == 7:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 1, 0, 0, 0]))elif target == 8:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 0, 1, 0, 0]))elif target == 9:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]))else:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]))test_target=torch.stack(target_list)print(test_target)break
tensor([1, 5, 3, 9, 5, 3, 0, 2, 1, 8])
tensor([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0., 0., 0., 1., 0.],[0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],[0., 0., 0., 0., 0., 0., 0., 1., 0., 0.]])
更新参数选用随机梯度下降法,把参数更新直接写入创建的LeNet类中了,因为就是针对LeNet的参数更新,所以不对其他网络具有普适性。
为LeNet类添加更新参数和偏导置零两个函数。训练时直接调用就行了。
def SGD_step_grad(self,lr=0.01):'''用于更新参数'''self.conv1.weights.data-=self.conv1.weights.grad.data*lrself.conv1.bias.data-=self.conv1.bias.grad.data*lrself.conv2.weights.data-=self.conv2.weights.grad.data*lrself.conv2.bias.data-=self.conv2.bias.grad.data*lrself.conv3.weights.data-=self.conv3.weights.grad.data*lrself.conv3.bias.data-=self.conv3.bias.grad.data*lrself.linear1.weights.data-=self.linear1.weights.grad.data*lrself.linear1.bias.data-=self.linear1.bias.grad.data*lrself.linear2.weights.data-=self.linear2.weights.grad.data*lrself.linear2.bias.data-=self.linear2.bias.grad.data*lrdef zero_grad(self):self.conv1.weights.grad.data.zero_()self.conv2.weights.grad.data.zero_()self.conv3.weights.grad.data.zero_()self.linear1.weights.grad.data.zero_()self.linear2.weights.grad.data.zero_()self.conv1.bias.grad.data.zero_()self.conv2.bias.grad.data.zero_()self.conv3.bias.grad.data.zero_()self.linear1.bias.grad.data.zero_()self.linear2.bias.grad.data.zero_()
LeNet的网络参数是很多的,全部打印出来看的眼花。所以就检查第一个卷积层和最后一个线性层的参数是否更新,因为这两个的参数比较少。发现还行。
#coding:utf-8
import json
import math
import numpy as np
import torch
import torchvision
from matplotlib import pyplot as plt
from torchvision import transformsclass myMultipassageConv():def __init__(self,in_chanels,out_chanels,kernel_size,padding,stride=(1,1),padding_str='zeros',use_bias=True,use_redundancy=False):''':param in_chanels: 输入通道数:param out_chanels: 输出通道数:param kernel_size: 卷积核尺寸 tuple类型:param padding: 边缘填充数:param stride: 步长 tuple类型 (可选)默认为(1,1):param padding_str: 边缘填充方式 str类型(可选):param use_bias: 是否使用偏置 bool类型(可选)use_redundancy:是否使用冗余值 bool 默认舍弃'''self.kernel_size=kernel_sizeself.in_chanels=in_chanelsself.out_chanels=out_chanelsself.padding=paddingself.stride=stride(self.kernel_hight,self.kernel_width)=kernel_sizeself.weights,self.bias=self.__parameters()self.padding_str=padding_strself.use_bias=use_biasself.use_redundancy=use_redundancydef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights=torch.randn(self.out_chanels,self.in_chanels,self.kernel_hight,self.kernel_width,requires_grad=True)'''偏置尺寸为out_kernels'''bias=torch.randn(self.out_chanels,1,requires_grad=True)return weights,biasdef forward(self,x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output =[](batch_size, chanels, hight, width)=x.shapefor batch_num in range(batch_size):img_chanel=x[batch_num,:,:,:]#print('img_chanel:\n',img_chanel)out_chanel=[]for out_kernel_num in range(self.weights.shape[0]):'''各个输出通道对应的权重'''kernels=self.weights[out_kernel_num,:,:,:]for_sum = []for chanel_img_num in range(chanels):'''对应项进行卷积'''kernel = kernels[chanel_img_num, :, :]_img=img_chanel[chanel_img_num,:,:]#print('current input_img:\n', _img)#print('current kernel:\n',kernel)if self.padding>0:_img = self.__paddingzeros(_img)#边缘填充#print('padded img:\n',_img)out_img,out_shape=self.__myConv(_img,kernel)for_sum.append(out_img)#print('unsumed out img:\n',for_sum)for_sum=torch.stack(for_sum,0)sumed_out_img=torch.sum(for_sum,dim=0)#print('sumed out_img:\n',sumed_out_img)if self.use_bias:sumed_out_img+=self.bias[out_kernel_num]out_chanel.append(sumed_out_img)#print('out chanel:',out_chanel)out_chanel=torch.stack(out_chanel)output.append(out_chanel)output=torch.stack(output,0)#print('final output:\n', output)return outputdef __paddingzeros(self,img):'''内部函数,默认 边缘0填充'''if self.padding_str=='zeros':out=torch.zeros((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelif self.padding_str=='ones':out=torch.ones((img.shape[0]+self.padding*2,img.shape[1]+self.padding*2))out[self.padding:-self.padding,self.padding:-self.padding]=imgreturn outelse:raise ValueError('Value error: unexcepted key value of \'padding\'. excepted key values:zeros,ones')def __myConv(self,img, kernel):#单层单核卷积'''内部参数,用于计算单图像单核卷积'''k1 = int((kernel.shape[0] - 1) / 2)k2 = int((kernel.shape[1] - 1) / 2)#print('k1,k2=',k1,k2)#print('stride:',self.stride)out_img = []right_line=[]bottom_line=[]botton_right=[]#print('img.shape:',img.shape)for x_index,i in enumerate(range(k1, img.shape[0] - k1)):if x_index%self.stride[1]==0:#纵向移动for y_index,j in enumerate(range(k2, img.shape[1] - k2)):if y_index%self.stride[0]==0:#横向移动#print('current i j', i, j)sight = img[i - k1:i + k2+1, j - k1:j + k2+1]#print(sight)#print(kernel)out_img.append(torch.sum(torch.mul(torch.Tensor(sight), kernel)))#print('out img:',out_img)out_shape=[0,0]for i in range( img.shape[0] - 2*k1):if i%self.stride[1]==0:out_shape[0]+=1for i in range( img.shape[1] - 2*k2):if i %self.stride[0]==0:out_shape[1]+=1#print(out_shape)return torch.stack(out_img).reshape(out_shape), out_shapedef get_parameters(self):return self.weights,self.biasdef get_weights(self):return self.weightsdef get_bias(self):return self.biasdef set_parameters(self,weights,bias):self.weights, self.bias=weights,biasdef set_weights(self,weights):self.weights=weightsdef set_bias(self,bias):self.bias=biasclass mylinear():def __init__(self,in_size,out_size,use_bias=True):self.in_size=in_sizeself.out_size=out_sizeself.weights,self.bias=self.__parameters()self.use_bias=use_biasdef __parameters(self):'''内部函数,默认标准正态分布初始化参数''''''权重尺寸为in_chanels*out_chanels*kernelsize'''weights = torch.randn(self.out_size, self.in_size, requires_grad=True)'''偏置尺寸为out_kernels'''bias = torch.randn(self.out_size, 1, requires_grad=True)return weights, biasdef forward(self,X):'''前向计算''''''X.shape batchsize,insize'''output=[]for batchnum in range(X.shape[0]):x=X[batchnum,:]batch_output=[]for i in range(self.out_size):w=self.weights[i,:]b=self.bias[i]if self.use_bias:y=torch.sum(torch.mul(w,x))+belse:y=torch.sum(torch.mul(w,x))batch_output.append(y)batch_output=torch.stack(batch_output,dim=1)output.append(batch_output.squeeze())output=torch.stack(output)return outputdef get_parameters(self):return self.weights,self.biasdef set_parameters(self,weights,bias):self.weights=weightsself.bias=biasdef get_weights(self):return self.weightsdef get_bias(self):return self.biasclass myPooling():def __init__(self,size,pooling_mode,stride):''':param size::param pooling_mode::param stride:'''self.h,self.w=sizeself.pool_mode=pooling_modeself.stride=stridedef __mypool(self, img):output=[]for x_index, i in enumerate(range(img.shape[0] )):if x_index % self.stride[1] == 0: # 纵向移动for y_index, j in enumerate(range(img.shape[1])):if y_index % self.stride[0]== 0: # 横向移动_img=img[i:i+self.h,j:j+self.w]#print(_img)if self.pool_mode == 'ave':output.append(torch.mean(_img))elif self.pool_mode == 'max':output.append(torch.max(_img))elif self.pool_mode == 'min':output.append(torch.min(_img))else:raise ValueError('Value error: unexcepted key value of \'pooling_mode\'. excepted key values:ave,max,min')out_shape = [0, 0]for i in range(img.shape[0]):if i % self.stride[0] == 0:out_shape[0] += 1for i in range(img.shape[1]):if i % self.stride[1] == 0:out_shape[1] += 1#print(out_shape)return torch.stack(output).reshape(out_shape), out_shapedef forward(self, x):'''前向计算''''''输入格式(batch_size,chanels,hight,width)'''output = [](batch_size, chanels, hight, width) = x.shapefor batch_num in range(batch_size):img = x[batch_num, :, :, :]chanel=[]for chanel_num in range(chanels):_img = img[chanel_num, :, :]out_img,out_shape=self.__mypool(_img)chanel.append(out_img)chanel=torch.stack(chanel)output.append(chanel)output=torch.stack(output)return outputclass LENet():def __init__(self):self.conv1=myMultipassageConv(in_chanels=1,out_chanels=6,kernel_size=(5,5),padding=2)#stride默认为(1,1)self.conv2=myMultipassageConv(in_chanels=6,out_chanels=16,kernel_size=(5,5),padding=0)self.conv3=myMultipassageConv(in_chanels=16,out_chanels=120,kernel_size=(5,5),padding=0)self.pool1=myPooling(size=(2,2),pooling_mode='ave',stride=(2,2))self.pool2=myPooling(size=(2,2),pooling_mode='ave',stride=(2,2))self.linear1=mylinear(in_size=120,out_size=84)self.linear2=mylinear(in_size=84,out_size=10)self.sigmoid=torch.nn.Sigmoid()self.softmax=torch.nn.Softmax(dim=1)self.relu=torch.nn.ReLU()def forward(self,x):#print('x:\n',x)#print('x.shape:\n',x.shape)x1=self.relu(self.conv1.forward(x))#print('X1:\n',x1)#print('x1.shape:\n',x1.shape)x2=self.pool1.forward(x1)#print('x2:\n',x2)#print('x2.shape:\n',x2.shape)x3=self.relu(self.conv2.forward(x2))#print('x3:\n',x3)#print('x3.shape:\n',x3.shape)x4=self.pool2.forward(x3)#print('x4:\n',x4)#print('x4.shape:\n',x4.shape)x5=self.conv3.forward(x4)#print('x5:\n',x5)#print('x5.shape:\n',x5.shape)x5=x5.reshape(x.shape[0],-1)#print('flatten x5:\n', x5)#print('flatten x5.shape:\n',x5.shape)x6=self.relu(self.linear1.forward(x5))#print('x6:\n',x6)#print('x6.shape:\n',x6.shape)x7=self.relu(self.linear2.forward(x6))#print('x7:\n',x7)#print('x7.shape:\n',x7.shape)return x7def parameters(self):return self.conv1.get_parameters(),self.conv2.get_parameters(),\self.conv3.get_parameters(),self.linear1.get_parameters(),\self.linear2.get_parameters()def SGD_step_grad(self,lr=0.01):'''用于更新参数'''self.conv1.weights.data-=self.conv1.weights.grad.data*lrself.conv1.bias.data-=self.conv1.bias.grad.data*lrself.conv2.weights.data-=self.conv2.weights.grad.data*lrself.conv2.bias.data-=self.conv2.bias.grad.data*lrself.conv3.weights.data-=self.conv3.weights.grad.data*lrself.conv3.bias.data-=self.conv3.bias.grad.data*lrself.linear1.weights.data-=self.linear1.weights.grad.data*lrself.linear1.bias.data-=self.linear1.bias.grad.data*lrself.linear2.weights.data-=self.linear2.weights.grad.data*lrself.linear2.bias.data-=self.linear2.bias.grad.data*lrdef zero_grad(self):self.conv1.weights.grad.data.zero_()self.conv2.weights.grad.data.zero_()self.conv3.weights.grad.data.zero_()self.linear1.weights.grad.data.zero_()self.linear2.weights.grad.data.zero_()self.conv1.bias.grad.data.zero_()self.conv2.bias.grad.data.zero_()self.conv3.bias.grad.data.zero_()self.linear1.bias.grad.data.zero_()self.linear2.bias.grad.data.zero_()def save_model(self,filepath):passif __name__=='__main__':# 数据预处理transform = transforms.Compose([transforms.ToTensor()])# 下载mnist库数据并定义读取器batchsize=10train_dataset = torchvision.datasets.MNIST(root='./mnist', train=True, transform=transform, download=True)train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batchsize, shuffle=True, num_workers=6)test_dataset = torchvision.datasets.MNIST(root='./mnist', train=False, transform=transform, download=True)test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=batchsize, shuffle=True, num_workers=0)lenet=LENet()proces=0epoches=1for epoch in range(epoches):finall_process = int(60000 / batchsize)epoches = 1for X,target in train_dataloader:proces+=1test_X=Xtest_target=target#print(test_X)#print(test_target)target_list=[]for target in test_target:if target==1:target_list.append(torch.Tensor([1,0,0,0,0,0,0,0,0,0]))elif target==2:target_list.append(torch.Tensor([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]))elif target == 3:target_list.append(torch.Tensor([0, 0, 1, 0, 0, 0, 0, 0, 0, 0]))elif target == 4:target_list.append(torch.Tensor([0, 0, 0, 1, 0, 0, 0, 0, 0, 0]))elif target == 5:target_list.append(torch.Tensor([0, 0, 0, 0, 1, 0, 0, 0, 0, 0]))elif target == 6:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 1, 0, 0, 0, 0]))elif target == 7:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 1, 0, 0, 0]))elif target == 8:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 0, 1, 0, 0]))elif target == 9:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]))else:target_list.append(torch.Tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]))test_target=torch.stack(target_list)#print(test_target)output = lenet.forward(test_X)loss = torch.nn.CrossEntropyLoss()l = loss(test_target, output) * 0.001#损失比较大,会导致梯度比较大,因此给他乘小一点print('[epoches:{}],procress:[{}/{}],current loss: {}'.format(epoch,proces,finall_process,l.item()))l.backward()lenet.SGD_step_grad(lr=0.01)lenet.zero_grad()
就这样实现了半全手搓的lenet神经网络,每一层都是自建层,没有继承torch.nn.Module类,我觉得应该还是挺有借鉴意义的。自动求导用到了pytorch,还有数据预处理用到了torch。速度的话平均8到9秒打印一次,一次代表训练了10个图片,还是十分缓慢的。
因为每层网络都是遍历迭遍历,有这样的速度在正常不过了,而且batchsize再大一点都运行不了了,训练到一半就会内存已满,可见循环套循环占用的空间很大。
到这里我对LeNet网络的掌握已经差不多了,手搓部分就到此为止了。
接下来全部用pytorch训练一下
内容有点多了,会写在下一篇文章中。
NNDL 实验六 卷积神经网络(3)LeNet实现MNIST 手动算子部分相关推荐
- NNDL 实验六 卷积神经网络(3)LeNet实现MNIST
目录 5.3 基于LeNet实现手写体数字识别实验 5.3.1 数据 5.3.2 模型构建 5.3.3 模型训练 5.3.4 模型评价 5.3.5 模型预测 使用前馈神经网络实现MNIST识别,与Le ...
- NNDL 实验六 卷积神经网络(3) LeNet实现MNIST
目录 5.3 基于LeNet实现手写体数字识别实验 5.3.1 数据 5.3.1.1 数据预处理 5.3.2 模型构建 1.测试LeNet-5模型,构造一个形状为 [1,1,32,32]的输入数据送入 ...
- NNDL 实验六 卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类
目录 5.5实践:基于ResNet18网络完成图像分类任务 5.5.1数据处理 5.5.1.1数据集介绍 5.5.1.2 数据读取 5.5.1.2 数据集划分 5.5.2模型构建 5.5.2.1使用R ...
- NNDL 实验六 卷积神经网络(1)卷积
文章目录 前言 第5章 卷积神经网络 一.5.1 卷积 5.1.1 二维卷积运算 5.1.2 二维卷积算子 5.1.3 二维卷积的参数量和计算量 5.1.4 感受野 5.1.5 卷积的变种 5.1.5 ...
- NNDL 实验六 卷积神经网络(1)卷积 边缘检测之传统边缘检测算子和基于pytorch的Canny边缘检测
文章目录 卷积神经网络(Convolutional Neural Network,CNN) 5.1 卷积 5.1.1 二维卷积运算 5.1.2 二维卷积算子 5.1.3 二维卷积的参数量和计算量 5. ...
- 深度学习 实验六 卷积神经网络(1)卷积 torch.nn
目录 第5章 卷积神经网络 5.1 卷积 5.1.1 二维卷积运算 5.1.2 二维卷积算子 5.1.3 二维卷积的参数量和计算量 5.1.4 感受野 5.1.5 卷积的变种 5.1.5.1 步长(S ...
- HBU-NNDL 实验六 卷积神经网络(2)基础算子
目录 5.2 卷积神经网络的基础算子 5.2.1 卷积算子 5.2.1.1 多通道卷积 5.2.1.2 多通道卷积层算子 5.2.1.3 卷积算子的参数量和计算量 5.2.2 汇聚层算子 使用pyto ...
- 【人工智能实验】卷积神经网络CNN框架的实现与应用-手写数字识别
目录 实验六 卷积神经网络CNN框架的实现与应用 一.实验目的 二.实验原理 三.实验结果 1.调整学习率.epochs以及bacth_size这三个参数,分别观察参数的变化对于实验结果的影响. 2. ...
- PyTorch实战福利从入门到精通之七——卷积神经网络(LeNet)
卷积神经网络就是含卷积层的网络.介绍一个早期用来识别手写数字图像的卷积神经网络:LeNet [1].这个名字来源于LeNet论文的第一作者Yann LeCun.LeNet展示了通过梯度下降训练卷积神经 ...
最新文章
- 写在马哥教育第八期开始之前
- NanoPi NEO Air使用十三:使用自带的fbtft驱动点亮SPI接口TFT屏幕,ST7789V,模块加载的方式
- update和saveOrUpdate详解
- C#创建windows服务并定时执行
- OpenGL之控制渲染图形的移动
- 《敏捷软件开发-原则、方法与实践》-Robert C. Martin
- 下列php语法描述错误的是,下列关于对象替换语法规则的描述中,错误的是( )。...
- Linux 命令tar的简单用法
- 2022.7.20 JETSON NANO 2GB 跑Swin-Transformer-Object-Detection。(配置:UBUNTU18/PYTHON3.6/PYTORCH1.6.0...)
- netbeans配置JAVA环境_NetBeans IDE 8.0.1 安装说明
- 皮卡丘为什么不进化_为什么皮卡丘不会变身?原因让人想哭
- java深克隆 浅克隆_通过Java中深克隆与浅克隆来理解克隆
- springboot项目制作漂亮的banner
- 背代码可以学好编程吗?下面的回答看的我一脸懵逼!
- 便签内容如何从旧手机转到新手机?
- 劳资蜀道山!6个高质量免费电子书网站!我看谁还不知道
- oracle 修改lsnrctl,ORACLE LSNRCTL密码及忘记密码处理
- 10.1 黑马Vue电商后台管理系统之完善订单管理模块--加入修改订单模块
- 程序员应该避开的20个低级不良用户体验
- webix icon 图标
热门文章
- 总离差平方和公式_在多元线性回归模型中,回归平方和与总离差平方和的比值称为( )_学小易找答案...
- 区块链2.0以太坊漫谈(4)
- 笔记本虚幻引擎帧率低的解决方法
- nmi_watchdog功能测试及解析
- 假如Google真的回来了
- django之商品最近浏览记录实现
- hadoop2 namenode HA的问题
- PostgreSQL 计算逾期率
- [凡鸽鸽]《OC小白鸽01》初学OC的部分要点及输出“Hello Word”
- 【JS面向对象编程常用方法】