全连接网络:每一层串起来了,每一个输出节点都参与了下一层输入节点的权重计算。
在前一节。直接使用全连接网络将上面这个图变成一维向量。
显然损失了空间状态。
现在使用卷积神经网络保留空间状态结构。
通过卷积层之后,通道,宽,长都可能变,也可能不变。像下面这样:
然后使用下采样,变成了4 * 12 * 12
使用下采样,通道数不会变,但宽和高会变。
下采样目的就是为了减小数据量,方便运算。
然后再通过一次卷积 和一次下采样,得到 8 * 4 * 4。
然后直接展开成一维向量,之后全链接层映射到结果分类。
其中 不断的卷积,采样的过程叫做特征提取器。
后面的变成向量,全链接映射分类那块(最后两步)。叫分类器.
卷积过程如下图:
图片读取进来就是RGB形式。通道有三层。左上角坐标为原点。
会有一个小块的扫描器从头到尾扫描整个图像,小块中得到的计算是原来块中全部数据特征经过权重得到的,然后计算出来新的通道 和 W,H,全部扫描完再进行融合得到卷积结果。
单通道卷积 执行过程 数值实例:
这里步长设置为1,即每次仅向右或者向下移动1个单位。
全是数据为 1 5 5 卷积核 (kernel) 为 1 3 * 3
这里就是拿卷积核 33 的去输入的数据 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.
现在希望 输入和卷积核的数据不变 得到的结果变成55的,可以将输入数据的外围添加0,添加一圈变成 77的数据维度(一圈嘛 上下左右都多出来一行,实际上就是横向多了两行,竖向多了两行。),这样和33卷积则可以变成 55的数据了。
这个过程叫 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像素。
之前用全连接训练的图像改成卷积训练。
流程图:
这里 由于池化层没有权重,所以可以只用一个变量,而每一次的卷积层是有权重的,所以每次都要用新的变量。
最后一次池化层到线性层那一块,要注意形状的改变,由 (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了~。