- 全连接网络:网络中所使用的全都是线性层,并且串行器来,就跟上一章的那个模型一样的样子:
线性层中输入\出层的任意两个节点之间都有权重,即所有输入节点都要参与到下一层输出的计算中去。我们就把这样的线性层叫做全连接层full connected(FC);
但是做成全连接层也有一些不足:
以上图为例,将一个数字转换为1维的长串时,可能会丢失一些空间信息,即在长串中隔得很远的两个点可能在原来的数字图像中是相邻的。这说明:做成全连接之后会丧失一些原有的空间信息;
相应的,CNN则是把图像按照原始空间结构进行保存,从而保留住了许多空间信息。
卷积计算:就是像上面这样,通过卷积核来对原来的图像进行压缩;
不难看出,在进行下采样时,通道数不变,但是宽度和高度则是按照采样规格进行缩减;
下采样:为了减少数据量,从而降低运算需求;
最终将n1的输出映射到10维的n2去,这样就可以接上 交叉熵损失 从而计算其分布,这样就可以解决分类问题了;
通过上面的图,可以得出一套工作流程:
通过这些层,来进行维度/尺寸上的大小变化,最终将它映射到想要的输出空间中去;
特征提取器:也就是卷积,下采样等,表示通过卷积运算来找到某种特征(此阶段直接对图像进行卷积运算从而转为向量);
分类器,就是把经过特征提取后的向量放到全连接层中去做分类;
可以通过扩展来提升像素值:
将原来2* 2的图像扩展为一个3* 3的,空出来的部分用平均值来进行填充即可
注意,经过卷积操作后的通道数量也是可能发生变化的,因为原来有三个通道,现在卷积核有5个通道,这五个通道把你原来三个通道都遍历一遍(多少个卷积核就会得到不同的特征图),卷积核个数是下一层通道数
将上面的过程整合一下就是:
不难看出,无论中间那个卷积核是多少层的,最后输出的结果一定是一个单通道的结果,如果想要一个多通道的结果就需要很多个卷积层,如下图所示;
这里的cat是指拼接的意思;
从这张图我们不难发现:
- 每个卷积核的通道数量要求和输入通道一样;
- 最终输出的通道数就是卷积核的数量;
如此一来就可以推出以下结论:
看看代码的计算过程:
import torch
#分别是n和m
in_channels, out_channels = 5,10
#这里的宽和高是图像的大小
w,h = 100, 100
#kernel_size表示卷积核的数量,输入一个3就是3*3,也可以直接给个数组
kernel_size = 3
#这里是指图片的数量
batch_size = 1
#生成一个输入图像
input = torch.rand(batch_size
,in_channels #这个最需要关注,一定要和卷积的in一样
,w
,h
)
#Conv2d:对由多个输入平面组成的输入信号进行二维卷积
conv_layer = torch.nn.Conv2d(in_channels
,out_channels
,kernel_size = kernel_size
)
#用上面的那个卷积层来对input进行卷积并生成output
output = conv_layer(input)
print(input.shape)
#torch.Size([1, 5, 100, 100])
print(output.shape)
#torch.Size([1, 10, 98, 98])
print(conv_layer.weight.shape) #卷积层权重的形状
#torch.Size([10, 5, 3, 3])
卷积层中其他常见的参数:
- padding:通过这个参数来改变输出矩阵的规格;
在外围加了一圈格子,从而使其在经过卷积核后的结果为5* 5 ()的,一般都是按实际需求选择是否填充。填充的圈数就是卷积核的大小/2后取整的值;
最常见的padding就是填充0,如下图所示:
而这一步也可以通过代码实现
import torch
#原来的矩阵
input = [3,4,5,6,7
,2,4,6,8,2
,1,6,7,8,4
,9,7,6,4,2
,3,7,5,4,1]
#按要求进行转换,view的四个数字分别是:batch, channel, w, h
input = torch.Tensor(input).view(1, 1, 5, 5)
#bias是加偏执量的参数,这里不需要加
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)
#通过赋值来进行卷积层权重的初始化
conv_layer.weight.data = kernel.data
output = conv_layer(input)
print(output)
- stride:步长,表示每次卷积之后框移动的距离;
import torch
input = [3,4,5,6,7
,2,4,6,8,2
,1,6,7,8,4
,9,7,6,4,2
,3,7,5,4,1]
input = torch.Tensor(input).view(1, 1, 5, 5)
conv_layer = torch.nn.Conv2d(1, 1, kernel_size = 3, stride=2, bias=False)
kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1, 1, 3, 3)
conv_layer.weight.data = kernel.data
output = conv_layer(input)
print(output)
#tensor([[[[208., 263.],
# [263., 167.]]]], grad_fn=<ConvolutionBackward0>)
下采样方法介绍:
-
MaxPooling:按宽和高进行分组,然后在每组内找到最大值来作为那一组的代表;
默认stride=2。这里只能在同一个通道内进行MaxPooling,不能在不同通道之间进行;
import torch
input = [3,4,5,6
,2,4,6,8
,1,6,7,8
,9,7,6,4]
input = torch.Tensor(input).view(1,1,4,4)
#当kernel_size设为2时,默认步长也会变成2
maxpooling_layer = torch.nn.MaxPool2d(kernel_size=2)
output = maxpooling_layer(input)
print(output)
接下来做一个简单的CNN:
第一个卷积层:5* 5的卷积核,输入通道为1,输出通道为5;
第一个池化层:batch和10都不变,宽和高的24都减小一半;
以此类推。。。
全连接层:把结果映射成10个输出向量来作为预测值;
最后的那个分类器最在乎每个样本的元素个数,因此要看看前面的输出有多少个。但是在实操时可以先不写最后这个分类器--,定义到最后一个batch就停,然后看看它的输出是咋样的,然后根据输出来编写全连接层即可;
在放进FC层之前要进行一次view,也就是把2044变成320再丢给FC;
import torch.nn.functional as F
class Net(torch.nn.Module):
def __init__(self):
super(Net, 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 = self.pooling(F.relu(self.conv1(x)))
x = self.pooling(F.relu(self.conv2(x)))
x = x.view(batch_size, -1)
x = self.fc(x)
return x
model = Net()
#用显卡来算,就是把模型迁移到GPU上去
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#通过这句话改跑模型的设备
model.to(device)
def train(epoch):
running_loss = 0.0
for batch_idx, data in enumerate(train_loader, 0):
inputs, target = data
#把他俩都送去device上,一定要放到同一块显卡上
inputs, target = inputs.to(device), target.to(device)
optimizer.zero_grad()
# forward + backward + update
outputs = model(inputs)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
if batch_idx % 300 == 299:
print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 2000))
running_loss = 0.0
def test():
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
inputs, target = data
#测试这里也要放到显卡上
inputs, target = inputs.to(device), target.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs.data, dim=1)
total += target.size(0)
correct += (predicted == target).sum().item()
print('Accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))
- 作业:
class Net1(torch.nn.Module):
def __init__(self):
super(Net1, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=4)#10,13,13
self.conv2 = torch.nn.Conv2d(10,20,kernel_size=4) #20,6,6
self.conv3 = torch.nn.Conv2d(20,40,kernel_size=4) #40,2,2
self.pooling = torch.nn.MaxPool2d(2)
self.fc1 = torch.nn.Linear(160,80)
self.fc2 = torch.nn.Linear(80,40)
self.fc3 = torch.nn.Linear(40,10)
def forward(self, x):
batch_size = x.size(0)
x = self.pooling(F.relu(self.conv1(x)))
x = self.pooling(F.relu(self.conv2(x)))
x = self.pooling(F.relu(self.conv3(x)))
x = x.view(batch_size, -1)
x = self.fc1(x)
x = self.fc2(x)
x = self.fc3(x)
return x
不知道对不对,应该是这样?