Press "Enter" to skip to content

Pytorch —– 卷积神经网络 CNN –基础部分(卷积层,池化层 下采样) 附代码解读+实现~~学习笔记

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

全连接网络:每一层串起来了,每一个输出节点都参与了下一层输入节点的权重计算。

在前一节。直接使用全连接网络将上面这个图变成一维向量。

 

显然损失了空间状态。

 

现在使用卷积神经网络保留空间状态结构。

 

通过卷积层之后,通道,宽,长都可能变,也可能不变。像下面这样:

然后使用下采样,变成了4 * 12 * 12

 

使用下采样,通道数不会变,但宽和高会变。

 

下采样目的就是为了减小数据量,方便运算。

然后再通过一次卷积 和一次下采样,得到 8 * 4 * 4。

 

然后直接展开成一维向量,之后全链接层映射到结果分类。

其中 不断的卷积,采样的过程叫做特征提取器。

 

后面的变成向量,全链接映射分类那块(最后两步)。叫分类器.

卷积过程如下图:

图片读取进来就是RGB形式。通道有三层。左上角坐标为原点。

 

会有一个小块的扫描器从头到尾扫描整个图像,小块中得到的计算是原来块中全部数据特征经过权重得到的,然后计算出来新的通道 和 W,H,全部扫描完再进行融合得到卷积结果。

 

单通道卷积 执行过程 数值实例:

 

这里步长设置为1,即每次仅向右或者向下移动1个单位。

全是数据为 1

5 5 卷积核 (kernel) 为 1 3 * 3

3 的去输入的数据 5*5中扫描。

然后做对位数乘,得到的结果放到结果集的第一个位置。

 

这里是做数乘,不是矩阵乘。

 

然后再将卷积核往后移,继续扫描。

 

三通道卷积 的情况:

 

每一个通道配一个卷积核,然后算出来三个卷积结果。

 

再对三个卷积结果进行相加,还是对位相加然后得到一个总的结果。

 

实际上就是 3 * 3 * 3 张量输入数据和 3 * 3 * 3的卷积核对位相乘得到 3* 3 *3的张量,然后再求和的结果。

.

卷积输入数据的通道数必须和所配备的卷积核通道数相同,w和h可以不一样。经过卷积之后的通道数为1。

 

如果想最后得到的通道数为M M>1。则可以让输入数据分别经过M个卷积核,得到M个通道为1的结果,再将这M个结果进行拼接,就可以得到M通道的卷积结果。

 

像下面这样:

 

卷积输入数据的通道数必须和所配备的卷积核通道数相同
卷积核的个数和输出的通道数 数值一致。

所以显然,如果现在我们有输入 n * w * h 通过卷积得到 m * w * h 则中间需要配备的卷积核为 m * n * w * h 为四维张量。

 

所以我们要配备一个四维张量的权重。

 

数据准备部分代码:

 

输入通道数为5,输出通道数位10.

 

w和h都是100.

 

卷积核大小为3 将默认成 3 * 3 也可以写成元组(x,x)或者写成长方形的 (x,y),但一般都用正方形,所以可以只写一个数。 。

 

batch为1。

下图,输入数据 参数多了一个batch,表示当前是第几个batch,后面参数依次 通道数 ,w 和 h。

卷积层设置:

 

下图 输入通道数量,输出通道数量。卷积核大小。

然后 下图 将输入数据放到卷子层得到输出的卷积结果:

 

参数解读 [1,5,100,100] 第一个batch 通道数5 。w * h 为100 *100。(输入为 100 * 100 卷积核 3 * 3所以 输出为 100-2=98 * 98)

 

最后哪一行输出为卷积层权重的形状:

 

[10,5,3,3] 输出通道为 10 输入通道为 5 ,卷积核 3 * 3。

因为卷积核扫描的遍历原数据,所以卷积核的大小和原来数据的大小 即 w和h是不关心的,多扫描一点而已。仅仅关心两者的通道数,跟前面说的那样,必须一样,

 

在前面的例子中使用了下图的数据:

即输入 5 * 5 卷积核 3 * 3 得到结果为 3 * 3.

 

现在希望 输入和卷积核的数据不变 得到的结果变成5 5的,可以将输入数据的外围添加0,添加一圈变成 7 7的数据维度(一圈嘛 上下左右都多出来一行,实际上就是横向多了两行,竖向多了两行。),这样和3 3卷积则可以变成 5 5的数据了。

 

这个过程叫 padding =1 即添加了一圈,添加的一般为0,也可以是别的。

 

小公式: 如果要得到输入维度和卷积后的输出维度一样,则可以直接拿卷积核 的 大小 整除 2。

 

比如 5 * 5 的输入 卷积核 3 * 3 要得到 5 * 5 则padding = 3 / 2 = 1圈即可。

 

添加后可以得到 下图:

 

使用代码实现上面的过程:

 

import torch
input = [
    3, 4, 6, 5, 7,
    2, 4, 6, 8, 2,
    1, 6, 7, 8, 4,
    9, 7, 4, 6, 2,
    3, 7, 5, 4, 1
]
# 输入数据设置
input = torch.Tensor(input).view(1, 1, 5, 5)  # batch  通道数, w 和 h
# 卷积层设置
conv_layer = torch.nn.Conv2d(1, 1, kernel_size=3, padding=1, bias=False)  # 输入通道,输出通道
# 卷积核设置
kernel = torch.Tensor([1, 2, 3, 4, 5, 6, 7, 8, 9]).view(1, 1, 3, 3)  # 输出通道数,输入通道数  w和h
# 做出来的张量给卷积层的权重   即初始化权重
conv_layer.weight.data = kernel.data
# 将输入值给卷积层去计算得到结果
out = conv_layer(input)
print(out)
得到输出:
tensor([[[[ 91., 168., 224., 215., 127.],
          [114., 211., 295., 262., 149.],
          [192., 259., 282., 214., 122.],
          [194., 251., 253., 169.,  86.],
          [ 96., 112., 110.,  68.,  31.]]]], grad_fn=<ThnnConv2DBackward0>)

 

设置步长的话:

 

conv_layer = torch.nn.Conv2d(1, 1, kernel_size=3,padding=1,stride=2, bias=False)  # 输入通道,输出通道 padding 步长  是否设置偏置量。

 

下采样:

 

用的比较多的叫 MaxPooling 也叫最大池化层。

最大池化层没有权重, 默认 stride = 2 即步长为2。

 

将原图分成 2 * 2 一组 分成4组 :

在每组中找最大值 平成 2* 2 的矩阵得到结果:

在做 MaxPooling时 通道数量不变。图像的w和h缩为原来的一半。

 

整个的流程:

初始值为 (batch,1,28,28) 即 batch样本数 ,1通道 28*28像素。

经过第一层卷积层 卷积核为 (5*5 输出为10通道),则输出的w和h 变为 28-5+1 = 24 即第一层卷积输出(batch,1,24,24)
经过第一层池化层 batch和通道数不变,w和h折半,所以输出(batch,1,12,12)
再次经过第二层卷积层,卷积核(5*5 输入10通道,输出20通道),则输出为(batch,20,8,8)
再次经过第二层池化层,w和h折半,所以输出(batch,20,4,4)
最后拿去线性层进行操作 此时的输入数为 4 * 4 *20 = 320个元素,最后变成 (bitch,10)。

之前用全连接训练的图像改成卷积训练。

 

流程图:

这里 由于池化层没有权重,所以可以只用一个变量,而每一次的卷积层是有权重的,所以每次都要用新的变量。

 

最后一次池化层到线性层那一块,要注意形状的改变,由 (batch,20,4,4) 变成 (batch,320)。然后扔到交叉熵里面训练,看损失。所以最后一层不用relu激活,因为交叉熵里面自动激活。

 

CPU版本:
完整代码:

 

import torch
from torch import optim
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
batch_size = 64
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)
# 模型类设计
class DiabetesDataset(torch.nn.Module):
    def __init__(self):
        super(DiabetesDataset, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
        self.pooling = torch.nn.MaxPool2d(2)
        self.fc = torch.nn.Linear(320, 10)
    def forward(self, x):
        batch_size = x.size(0)
        x = F.relu(self.pooling(self.conv1(x)))  # 第一轮卷积池化激活
        x = F.relu(self.pooling(self.conv2(x)))  # 第二轮卷积池化激活
        x = x.view(batch_size, -1)  # 吧数据变成线性层接受的
        x = self.fc(x)  # 去线性层
        return x
model = DiabetesDataset()
# 损失函数
criterion = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
def train(epoch):
    runing_loss = 0.0
    for i, data in enumerate(train_loader):
        x, y = data
        # 清零 正向传播  损失函数  反向传播 更新
        optimizer.zero_grad()
        y_pre = model(x)
        loss = criterion(y_pre, y)
        loss.backward()
        optimizer.step()
        runing_loss += loss.item()
    # 每轮训练一共训练1W个样本,这里的runing_loss是1W个样本的总损失值,要看每一个样本的平均损失值, 记得除10000
    print("这是第 %d轮训练,当前损失值 %.5f" % (epoch + 1, runing_loss / 10000))
def test(epoch):
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            x, y = data
            pre_y = model(x)
            # 这里拿到的预测值 每一行都对应10个分类,这10个分类都有对应的概率,
            # 我们要拿到最大的那个概率和其对应的下标。
            j, pre_y = torch.max(pre_y.data, dim=1)  # dim = 1 列是第0个维度,行是第1个维度
            total += y.size(0)  # 统计方向0上的元素个数 即样本个数
            correct += (pre_y == y).sum().item()  # 张量之间的比较运算
    print("第%d轮测试结束,当前正确率:%d %%" % (epoch + 1, correct / total * 100))
if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test(epoch)
结论:
这是第 1轮训练,当前损失值 0.03040
第1轮测试结束,当前正确率:96 %
这是第 2轮训练,当前损失值 0.00937
第2轮测试结束,当前正确率:97 %
这是第 3轮训练,当前损失值 0.00690
第3轮测试结束,当前正确率:98 %
这是第 4轮训练,当前损失值 0.00574
第4轮测试结束,当前正确率:98 %
这是第 5轮训练,当前损失值 0.00503
第5轮测试结束,当前正确率:98 %
这是第 6轮训练,当前损失值 0.00456
第6轮测试结束,当前正确率:98 %
这是第 7轮训练,当前损失值 0.00417
第7轮测试结束,当前正确率:98 %
这是第 8轮训练,当前损失值 0.00383
第8轮测试结束,当前正确率:98 %
这是第 9轮训练,当前损失值 0.00360
第9轮测试结束,当前正确率:98 %
这是第 10轮训练,当前损失值 0.00335
第10轮测试结束,当前正确率:98 %

 

昨天用线性模型跑这个数据,正确率最高也就97% 。

 

今天用刚学的卷积池化层加入,正确率可以达到98% 。

 

GPU版本
完整代码:

 

import torch
from torch import optim
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F
batch_size = 64
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)
# 模型类设计
class DiabetesDataset(torch.nn.Module):
    def __init__(self):
        super(DiabetesDataset, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
        self.pooling = torch.nn.MaxPool2d(2)
        self.fc = torch.nn.Linear(320, 10)
    def forward(self, x):
        batch_size = x.size(0)
        x = F.relu(self.pooling(self.conv1(x)))  # 第一轮卷积池化激活
        x = F.relu(self.pooling(self.conv2(x)))  # 第二轮卷积池化激活
        x = x.view(batch_size, -1)  # 吧数据变成线性层接受的
        x = self.fc(x)  # 去线性层
        return x
model = DiabetesDataset()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)  # 将训练模型放到GPU
# 损失函数
criterion = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
def train(epoch):
    runing_loss = 0.0
    for i, data in enumerate(train_loader):
        x, y = data
        x, y = x.to(device), y.to(device)
        # 清零 正向传播  损失函数  反向传播 更新
        optimizer.zero_grad()
        y_pre = model(x)
        loss = criterion(y_pre, y)
        loss.backward()
        optimizer.step()
        runing_loss += loss.item()
    # 每轮训练一共训练1W个样本,这里的runing_loss是1W个样本的总损失值,要看每一个样本的平均损失值, 记得除10000
    print("这是第 %d轮训练,当前损失值 %.5f" % (epoch + 1, runing_loss / 10000))
def test(epoch):
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            x, y = data
            x, y = x.to(device), y.to(device)
            pre_y = model(x)
            # 这里拿到的预测值 每一行都对应10个分类,这10个分类都有对应的概率,
            # 我们要拿到最大的那个概率和其对应的下标。
            j, pre_y = torch.max(pre_y.data, dim=1)  # dim = 1 列是第0个维度,行是第1个维度
            total += y.size(0)  # 统计方向0上的元素个数 即样本个数
            correct += (pre_y == y).sum().item()  # 张量之间的比较运算
    print("第%d轮测试结束,当前正确率:%d %%" % (epoch + 1, correct / total * 100))
if __name__ == '__main__':
    for epoch in range(10):
        train(epoch)
        test(epoch)

 

其实就多了两三行代码,先选择GPU,然后吧训练模型放GPU上,再吧数据放GPU上就ok了~。

Be First to Comment

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注