卷积神经网络初步(二):细胞、组织和器官

引言

这一章节我们稍缓一下,讲讲每一层的原理、实现和参数设置。目前能够实现深度学习的语言有很多,其中Python相对拥有比较完善的环境,因此使用的也较多。

Tensorflow、Keras、PyTorch是目前使用较多的三种环境。由于博主比较熟悉PyTorch,就用它来讲解了。本文以RSOD目标检测数据集为例,进行图像分类任务。

开局先放几个引用在这里。

import torch
from torch import optim
import torch.nn.functional as F

0_细胞:层

我们首先看看pytorch中对几个关键功能的定义

卷积层

如上文所述,卷积操作指的是通过使用卷积核计算更新矩阵的值,以达成平滑或者锐化的目的。
对于卷积神经网络来说,由于我们的目的是找寻特征,所以我们所使用的卷积层所达成的锐化的目的。pytorch按照输入数据的维度对卷积层做出了如下的分类

#一维卷积
class torch.nn.Conv1d(in_channels, out_channels, 
				kernel_size, stride=1, padding=0, 
				dilation=1, groups=1, bias=True)
#二维卷积
class torch.nn.Conv2d(in_channels, out_channels, 
				kernel_size, stride=1, padding=0, 
				dilation=1, groups=1, bias=True)
#三维卷积
class torch.nn.Conv3d(in_channels, out_channels, 
				kernel_size, stride=1, padding=0, 
				dilation=1, groups=1, bias=True)

参数说明:

in_channels:输入矩阵的通道数
out_channels:输出矩阵的通道数
kernel_size:卷积核尺寸
stride:卷积核处理步长
padding:为将输出与输入的尺寸相统一,在输入矩阵外围补充0值的行列数
dilation:卷积核元素之间的间距(空洞卷积运算时使用)
groups:对输入按维度进行分组,分组进行卷积
bias:添加偏置
  • Tips:
  • 1.卷积核的尺寸选择并没有一个统一的标准。但一般来说,小一点的卷积核能够降低参数量和计算难度,反正模型里会有多层的卷积操作,与其想要贪心地用较大的卷积核,不如交给多个卷积核的叠加。
  • 2.输出矩阵的通道数指的是卷积核的数量。若有8x256x256的矩阵输入卷积层,输出矩阵的通道数设置为64,则卷积层会自动生成64个不同卷积核,使用每一个卷积对输入进行卷积,则最后的输入尺寸变为8x256x256x64
  • 3.空洞卷积可以在扩大感受野的同时不降低特征尺寸,但对于图像分类任务没有什么作用。
  • 4.分组是为了方便多卡训练的设置。pytorch有整体的多卡训练模式,无需具体到每一层上。

激活层

Sigmoid激活函数在pytorch的实现如下:

class torch.nn.sigmoid()

池化层

池化层的目的是降低矩阵尺寸,放大特征在矩阵中的占比。在分类任务中,池化层通常跟在卷积层+激活层之后。

#最大池化: 取最大值的方式
nn.MaxPool2d(kernel_size, stride=None, 
			padding=0, dilation=1, 
			return_indices=False, ceil_mode=False)
#平均池化:取平均值的方式
torch.nn.AvgPool2d(kernel_size, stride=None, 
					padding=0, ceil_mode=False, 
					count_include_pad=True, divisor_override=None)

参数说明:

kernel_size:池化核尺寸
stride:步长,通常与 kernel_size 一致
padding:填充宽度,主要是为了调整输出的特征图大小,一般把 padding 设置合适的值后,保持输入和输出的图像尺寸不变。
dilation:池化间隔大小,默认为 1。常用于图像分割任务中,主要是为了提升感受野
ceil_mode:默认为 False,尺寸向下取整。为 True 时,尺寸向上取整
return_indices:为 True 时,返回最大池化所使用的像素的索引,这些记录的索引通常在反最大池化时使用,把小的特征图反池化到大的特征图时,每一个像素放在哪个位置。
count_include_pad:在计算平均值时,是否把填充值考虑在内计算
divisor_override:除法因子。在计算平均值时,分子是像素值的总和,分母默认是像素值的个数。如果设置了 divisor_override,把分母改为 divisor_override。

全连接层

全连接层即感知机中的线性神经元。在图像分类任务中,全连接层用于将输入变为nx1的矩阵。

class torch.nn.Linear(input_size,output_size)

Softmax

Softmax将nx1的矩阵映射为kx1的矩阵,且输出的矩阵要素之和为1。

class torch.nn.softmax(dim=i)

1_组织:Block

前面我们讲过,卷积神经网络相对于多层感知机机制的差异,如感受野机制,部分连接和特征。但其实还有一点是多层感知机无论如何也比不了,就是卷积神经网络在网络扩展方面的便捷性。

block = torch.nn.Sequential(torch.nn.Conv2d(in_size, out_size, kernel_size, stride, padding),
                            torch.nn.sigmoid(inplace=True),
                            torch.nn.MaxPool2d(kernel_size, stride)
                            torch.nn.Conv2d(out_size, out_size, kernel_size, stride, padding),
                            torch.nn.sigmoid(inplace=True)
                            torch.nn.MaxPool2d(kernel_size, stride)
                                     )

如图是一个包含了两次和池化操作的block。pytorch中torch.nn.Sequential()是专为类似使用场景构建的一个有序容器。当发生调用时,容器内填入的层会自动按照填入的顺序依次处理。

2_器官:LeNet-5实现分类

上一篇文章中,我们已经讲解了LeNet模型的结构,今天我们用pytorch来实现它。LeNet-5是LeNet模型专为MNist手写识别任务搭建的构型,5的意思是共有五层工作结构。本文将其转移至RSOD分类任务中。

第一步,读取并分割数据集

import os
import random

dir = r'E:\RSOD'

读取文件目录中所有的jpg文件并打乱顺序

def search_file(dir_path, filters):
	#迭代查找符合条件的文件,并输出文件名列表
    print("search files begin in dir {}".format(dir_path))
    w = []
    for cur_dir, sub_dir, files in os.walk(dir_path) :
        for file in files :
            if os.path.splitext(file)[1] in filters :
                file_abs_path = os.path.join(cur_dir, file)
                w.append(file_abs_path)
    print("search end.")
    return w

file_list = search_file(dir, filters=['.jpg'])
random.shuffle(file_list) #输入list随机化,并覆盖原list

建立类别与数值的对应关系(多分类)

classes = {'playground':1,'overpass':2,'aircraft':3,'oiltank':4}

按8:2的比例将数据集划分为train、test,并保存为txt。

from sklearn.model_selection import train_test_split

train_files, test_files = train_test_split(file_list, test_size=0.2, random_state=0, shuffle=True)

#写入train
with open(osp.join(dir, 'train.txt'), 'w') as f:
    for name in train:
        name = str(name)
        f.write(name)
# 写入test
with open(osp.join(dir, 'test.txt'), 'w') as f:
    for name in test:
        name = str(name)
        f.write(name)

建立LeNet网络结构。
请注意网络的搭建方式。

import torch
import torch.nn as nn


class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Sequential(     #input_size=(1*1082*922)
            nn.Conv2d(3, 6, 5, 1, 2), #padding=2保证输入输出尺寸相同
            nn.sigmoid(),      #input_size=(6*1082*922)
            nn.MaxPool2d(kernel_size=2, stride=2),#output_size=(6*541*461)
        )
        self.conv2 = nn.Sequential( #input_size=(6*541*461)
            nn.Conv2d(3, 6, 16, 5),#(16*537*457)
            nn.sigmoid(),      #input_size=(16*537*457)
            nn.MaxPool2d(2, 2, 1)  #output_size=(16*269*229)
        )
        self.fc1 = nn.Sequential(#input_size=(16*269*269)
            nn.Linear(16*269*269, 120),
            nn.sigmoid()
        )
        self.fc2 = nn.Sequential(
            nn.Linear(120, 84),
            nn.sigmoid()
            nn.Linear(84, 4),
            nn.sigmoid()
        )
        self.fc3 = nn.softmax(dim=1)

    # 定义前向传播过程,输入为x
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        # nn.Linear()的输入输出都是维度为一的值,所以要把多维度的tensor展平成一维
        x = x.view(x.size()[0], -1)
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x

读取数据,准备填入网络中。
Pytorch提供了Dataset类,通常我们会以继承该类的方式获得其功能,并在类中添加注入缩放、旋转、裁剪、归一化等操作。
Pytorch还提供了dataloader类以调用dataset的输出。我们会在train中加入。

from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image

class ds(Dataset):
    def __init__(self, target_files, transform = None):
        super(ds, self).__init__()
        self.img_list = target_files
        self.transform = transform

    def __len__(self):
        return len(self.img_list)

    def __getitem__(self, index):
        img = self.img_list[index]
        img_label = classes[img.split('\\')[-1].split('.')[0].split('_')[0]]
        img = Image.open(img)
        
        if self.transform:
            img = self.transform(img)
            img = normalize(img)
		else:
			img = img.ToTensor()
			img = mormalize(img)
		
        return img, torch.Tensor(img_label)

	def transform(image):
		transforms.Resize(864, interpolation=2)
		transforms.CenterCrop(864)
		transforms.ToTensor()
        )
	def normalize(image):
		img = np.array(img)
		for i in range(img.shape[0]):#对影像进行处理
            img = img.numpy().astype(np.float64)
            mean = np.mean(img[i, :, :]) #求取每个波段的均值
            img[i, :, :] -= mean #标准化
            img[i, :, :] = (img[i, :, :] - np.min(img[i, :, :])) * 1 / (np.max(img[i, :, :]) - np.min(img[i, :, :])) + 0  
            #对每一个波段进行归一化	
            img = torch.from_numpy(img)
            return img

下面我们构建训练器。

class Trainer():
	def __init__(self, file_list, batch_size, workers, epoch):
		self.batch_size = batch_size
		self.workers = workers
		self.epoch = epoch
		self.dataset = ds(train_files, transform = True)
		self.train_loader = Dataloader(self.dataset, 		
										batch_size=self.batch_size, 
										shuffle=True
										)
		self.model = LeNet()
		self.criterion = nn.CrossEntropyLoss(size_average=False)
		self.optimizer = torch.optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.99))
		
	def weight_init(m):
		if isinstance(m, nn.Conv2d):
	        n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
	        m.weight.data.normal_(0, math.sqrt(2. / n))
		elif isinstance(m, nn.BatchNorm2d):
		     m.weigth.data.fill_(1)
		     m.bias.data.zero_()
	
	def train(epoch):
		self.model.train() #此处并非指的是开始训练,我们尚未实例化model。此处的含义是将model调整为train模式
		tbar = tqdm(self.train_loader) #tbar是可见化进度条
		for i, agg in enumerate(tbar):
				image = agg[0]
				label = agg[-1]
		        self.optimizer.zero_grad() #初始化时,要清空梯度
		        output = self.model(data)
		        loss = self.criterion(output, label)
		        loss.backward()
		        self.optimizer.step() #相当于更新权重值
		        if i % 100 == 0:
		        	print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
		               epoch, batch_idx * len(data), len(train_loader.dataset),
		               100. * batch_idx / len(train_loader), loss.data[0]))
		self.model.apply(weight_init)
	
	def main(self.epoch)
		for epoch in range(1,self.epoch+1):
			train(epoch)

trainer = Trainer(file_list=train_files, batch_size=6, workers=6, epoch=100)
trainer.main()

测试器也是大同小异。

class Evaluator():
	self.__init__(self,)
def test():
    model.eval() #转换模型为eval也即测试模式
    test_loss = 0
    correct = 0
    for agg in test_loader:
		image = agg[0]
		label = agg[-1]
        output = model(image)
        #计算总的损失
        test_loss += c riterion(output, target).data[0]
        pred = output.data.max(1, keepdim=True)[1] #获得得分最高的类别
        correct += pred.eq(target.data.view_as(pred)).cpu().sum()
    
    test_loss /= len(test_loader.dataset)
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
       test_loss, correct, len(test_loader.dataset),
       100. * correct / len(test_loader.dataset)))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值