PyTorch实现CNN

原理及步骤

数据处理

       PyTorch中对于数据集的处理有三个非常重要的类:DatasetDataloaderSampler,它们均是 torch.utils.data 包下的模块(类)。它们的关系可以这样理解:

       Dataset是数据集的类,主要用于定义数据集

       Sampler是采样器的类,用于定义从数据集中选出数据的规则,比如是随机取数据还是按照顺序取等等

       Dataloader是数据的加载类,它是对于Dataset和Sampler的进一步包装,即其实Dataset和Sampler会作为参数传递给Dataloader,用于实际读取数据,可以理解为它是这个工作的真正实践者,而Dataset和Sampler则负责定义。我们训练、测试所获得的数据也是Dataloader直接给我们的。

Dataset

       Dataset 位于 torch.utils.data 下,我们通过定义继承自这个类的子类来自定义数据集。它有两个最重要的方法需要重写,实际上它们都是类的特殊方法:

       __getitem__(self, index):传入参数index为下标,返回数据集中对应下标的数据组(数据和标签)
       __len__(self):返回数据集的大小

       Dataset的目标是根据你输入的索引输出对应的image和label,而且这个功能是要在__getitem__()函数中完成的,所以当你自定义数据集的时候,首先要继承Dataset类,还要复写__getitem__()函数。

Dataloader

       Dataloader对Dataset(和Sampler等)打包,完成最后对数据的读取的执行工作,一般不需要自己定义或者重写一个Dataloader的类(或子类),直接使用即可,通过传入参数定制Dataloader,定制化的功能应该在Dataset(和Sampler等)中完成了。

Sampler

       Sampler类是一个很抽象的父类,其主要用于设置从一个序列中返回样本的规则,即采样的规则。Sampler是一个可迭代对象,使用step方法可以返回下一个迭代后的结果,因此其主要的类方法就是 __iter__ 方法,定义了迭代后返回的内容。

SequentialSampler

       SequentialSampler就是一个按照顺序进行采样的采样器,接收一个数据集做参数(实际上任何可迭代对象都可),按照顺序对其进行采样。

RandmSampler

       RandomSampler 即一个随机采样器,返回随机采样的值,第一个参数依然是一个数据集(或可迭代对象)。还有一组参数如下:

       replacement:bool值,默认是False,设置为True时表示可以采出重复的样本
       num_samples:只有在replacement设置为True的时候才能设置此参数,表示要采出样本的个数,默认为数据集的总长度。有时候由于replacement置True的原因导致重复数据被采样,导致有些数据被采不到,所以往往会设置一个比较大的值.

SubsetRandomSampler

       SubsetRandomSampler可以设置子集的随机采样,多用于将数据集分成多个集合,比如训练集和验证集的时候使用:

WeightedRandomSampler

       WeightedRandomSampler和RandomSampler的参数一致,但是不在传入一个dataset,第一个参数变成了weights,只接收一个一定长度的list作为 weights 参数,表示采样的权重,采样时会根据权重随机从 list(range(len(weights))) 中采样,即WeightedRandomSampler并不需要传入样本集,而是只在一个根据weights长度创建的数组中采样,所以采样的结果可能需要进一步处理才能使用。weights的所有元素之和不需要为1。

BatchSampler

       以上的四个Sampler在每次迭代都只返回一个索引,而BatchSampler的作用是对上述这类返回一个索引的采样器进行包装,按照设定的batch size返回一组索引,因其他的参数和上述的有些不同:
       sampler:一个Sampler对象(或者一个可迭代对象)
       batch_size:batch的大小
       drop_last:是否丢弃最后一个可能不足batch size大小的数据

collate_fn

       Dataloader其实还有一个比较重要的参数是 collate_fn,它接收一个 callable 对象,比如一个函数,它的作用是将每次迭代出来的数据打包成batch。
       举个例子,如果我们在Dataloader中设置了batch_size为8,实际上,从Dataloader所读取的数据集Dataset中取出数据时得到的是单独的数据,比如我们的例子中,每次采样得到一个tuple:(image, label),因此collate_fn 的作用就有了,它负责包装batch,即每从数据集中抽出8个这样的tuple,它负责把8个(image, label)包装成一个list: [images, labels],这个list有两个元素,每一个是一个tensor,比如第一个元素,实际上是一个 8×size(image) 的tensor,即给原来的数据增加了一维,也就是最前边的batch的维度,labels也同理。
       有时候我们可能会需要实现自己的包装逻辑,所以需要自定义一个函数来完成定制化的如上的内容,只要将该函数名传递给collate_fn参数即可。

pytorch两个基本对象:Tensor(张量)和Variable(变量)

其中,tensor不能反向传播,variable可以反向传播。 

tensor的算术运算和选取操作与numpy一样,一次你numpy相似的运算操作都可以迁移过来。

variable是一种可以不断变化的变量,符合反向传播,参数更新的属性。pytorch的variable是一个存放会变化值的地理位置,里面的值会不停变化,像装糖果(糖果就是数据,即tensor)的盒子,糖果的数量不断变化。pytorch都是由tensor计算的,而tensor里面的参数是variable形式。

代码及注释

import torch # 需要的各种包
import torch.nn as nn
from torch.autograd import Variable
import torch.utils.data as data
import matplotlib.pyplot as plt
import torchvision # 数据库模块


# 数据预处理
# 将training data转化成torch能够使用的DataLoader,这样可以方便使用batch进行训练
torch.manual_seed(1) # reproducible 将随机数生成器的种子设置为固定值,这样,当调用时torch.rand(x),结果将可重现

# Hyper Parameters
EPOCH = 1 # 训练迭代次数
BATCH_SIZE = 50 # 分块送入训练器
LR = 0.001 # 学习率 learning rate

train_data = torchvision.datasets.MNIST(
    root='./mnist/', # 保存位置 若没有就新建
    train=True, # training set
    transform=torchvision.transforms.ToTensor(), # 
    # converts a PIL.Image or numpy.ndarray to torch.FloatTensor(C*H*W) in range(0.0,1.0)
    download=True
)

test_data = torchvision.datasets.MNIST(root='./MNIST/')

# 如果是普通的Tensor数据,想使用 torch_dataset = data.TensorDataset(data_tensor=x, target_tensor=y)
# 将Tensor转换成torch能识别的dataset
# 批训练, 50 samples, 1 channel, 28*28, (50, 1, 28 ,28)
train_loader = data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)

test_x = Variable(torch.unsqueeze(test_data.test_data, dim=1), volatile=True).type(torch.FloatTensor)[:2000]/255.
# torch.unsqueeze 返回一个新的张量,对输入的既定位置插入维度 1

test_y = test_data.test_lables[:2000]
# 数据预处理


# 定义网络结构
# 1)class CNN需要·继承·Module 
# 2)需要·调用·父类的构造方法:super(CNN, self).__init__()
# 3)在Pytorch中激活函数Relu也算是一层layer
# 4)需要·实现·forward()方法,用于网络的前向传播,而反向传播只需要·调用·Variable.backward()即可。
# 输入的四维张量[N, C, H, W]
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # nn.Sequential一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行,
        # 同时以神经网络模块为元素的有序字典也可以作为传入参数
        # nn.Conv2d 二维卷积 先实例化再使用 在Pytorch的nn模块中,它是不需要你手动定义网络层的权重和偏置的
        self.conv1 = nn.Sequential( #input shape (1,28,28)
            nn.Conv2d(in_channels=1, #input height 必须手动提供 输入张量的channels数
                      out_channels=16, #n_filter 必须手动提供 输出张量的channels数
                      kernel_size=5, #filter size 必须手动提供 卷积核的大小 
                      # 如果左右两个数不同,比如3x5的卷积核,那么写作kernel_size = (3, 5),注意需要写一个tuple,而不能写一个列表(list)
                      stride=1, #filter step 卷积核在图像窗口上每次平移的间隔,即所谓的步长
                      padding=2 #con2d出来的图片大小不变 Pytorch与Tensorflow在卷积层实现上最大的差别就在于padding上
            ), # output shape (16,28,28) 输出图像尺寸计算公式是唯一的 # O = (I - K + 2P)/ S +1
            nn.ReLU(), # 分段线性函数,把所有的负值都变为0,而正值不变,即单侧抑制
            nn.MaxPool2d(kernel_size=2) #2x2采样,28/2=14,output shape (16,14,14) maxpooling有局部不变性而且可以提取显著特征的同时降低模型的参数,从而降低模型的过拟合
        ) 
        self.conv2 = nn.Sequential(nn.Conv2d(16, 32, 5, 1, 2), #output shape (32,7,7)
                                  nn.ReLU(),
                                  nn.MaxPool2d(2))
        # 因上述几层网络处理后的output为[32,7,7]的tensor,展开即为7*7*32的一维向量,接上一层全连接层,最终output_size应为10,即识别出来的数字总类别数
        # 在二维图像处理的任务中,全连接层的输入与输出一般都设置为二维张量,形状通常为[batch_size, size]
        self.out = nn.Linear(32*7*7, 10) # 全连接层 7*7*32, num_classes
        
    def forward(self, x):
        x = self.conv1(x) # 卷一次
        x = self.conv2(x) # 卷两次
        x = x.view(x.size(0), -1) #flat (batch_size, 32*7*7) 
        # 将前面多维度的tensor展平成一维 x.size(0)指batchsize的值
        # view()函数的功能根reshape类似,用来转换size大小
        output = self.out(x) # fc out全连接层 分类器
        return output
# 定义网络结构


# 查看网络结构
cnn = CNN()
print(cnn) # 使用print(cnn)可以看到网络的结构详细信息,可以看到ReLU()也是一层layer
# 查看网络结构


# 训练 需要特别指出的是记得每次反向传播前都要清空上一次的梯度,optimizer.zero_grad()
# optimizer 可以指定程序优化特定的选项,例如学习速率,权重衰减等
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR) # torch.optim是一个实现了多种优化算法的包

# loss_fun CrossEntropyLoss 交叉熵损失
# 信息量:它是用来衡量一个事件的不确定性的;一个事件发生的概率越大,不确定性越小,则它所携带的信息量就越小。
# 熵:它是用来衡量一个系统的混乱程度的,代表一个系统中信息量的总和;信息量总和越大,表明这个系统不确定性就越大。
# 交叉熵:它主要刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近
loss_func = nn.CrossEntropyLoss() # 该损失函数结合了nn.LogSoftmax()和nn.NLLLoss()两个函数 适用于分类

# training loop
for epoch in range(EPOCH):
    for i, (x, y) in enumerate(train_loader):
        batch_x = Variable(x)
        batch_y = Variable(y)
        output = cnn(batch_x) # 输入训练数据
        loss = loss_func(output, batch_y) # 计算误差 # 实际输出, 期望输出
        optimizer.zero_grad() # 清空上一次梯度
        loss.backward() # 误差反向传递 只需要调用.backward()即可
        optimizer.step() # cnn的优化器参数更新
# 训练


# 预测结果
# cnn.eval()
test_output = cnn(test_x[:10])
pred_y = torch.max(test_output, 1)[1].data.numpy().squeeze()
# torch.max(input, dim)函数  
# torch.max(test_output, 1)[1]  取出来indices 每行最大值的索引
# 输入 input是softmax函数输出的一个tensor  
# 输入 dim是max函数索引的维度0/1,0是每列的最大值,1是每行的最大值
# 输出 函数会返回两个tensor,第一个tensor是每行的最大值;第二个tensor是每行最大值的索引。
# squeeze()函数的功能是:从矩阵shape中,去掉维度为1的。例如一个矩阵是的shape是(5, 1),使用过这个函数后,结果为(5,)。
print(pred_y, 'prediction number')
print(test_y[:10], 'real number')
# 预测结果

参考文章:

Pytorch实现卷积神经网络CNN - Peter-Young - 博客园

使用PyTorch实现CNN_dongyangY的博客-CSDN博客_cnn pytorch

Pytorch实现简单CNN模型 - 知乎

CNN简单理解+PyTorch示例实现_「已注销」的博客-CSDN博客_pytorch实现cnn代码讲解

其他文章内容已被汇总至代码注释中,忘记出处,抱歉

你可能感兴趣的:(CV-计算机视觉,AI-人工智能,求职,pytorch,cnn,深度学习)