(一)从全连接到卷积神经网络
首先我们用全连接层的思维去思考我们该怎么分类猫和狗的图片。
假设,一个手机的像素是12M,那么开通RGB三个通道后,一张图片的大小为36M
假设使用但隐藏层的MLP,那么模型的参数就变成了3.6B(存储空间约为14GB)个参数,这将比世界上所有的猫和狗的数量都多。(狗900M只,猫600M只)
在图片中识别出一个东西的模式是什么呢?它遵循什么规律?
(1)平移不变性原则
- 识别的标准是一样的,不管是哪张图片什么位置我都能认出来
(2)局部性原则 - 识别一个东西,只需要一些关键的东西就嫩判断,而不是整张图片的所有像素。
(1)从全连接层到卷积
- 将输入和输出变形为矩阵(宽度和高度)
- 将权重变为4-D的张量
- 让权重不会随着x的改变而改变
- 如图所示:V就是以前的权重w。这便是平移不变性原则。
然后,我们还认为,V只需要对关键的X做周围的观察,这就是局部性原则,这就得到了卷积核。
(2)卷积层
输入和卷积核里的元素对应相乘,然后相加。这个过程就叫做卷积。
没算完一次,卷积核向右移动一格,继续进行卷积操作,直到将整个输入的像素点都扫到。
说完卷积,我们来看一下一个卷积层里的参数。
不同的卷积核对图片作用后有不同的效果。或许我们可以理解为,神经网络做的事情就是通过卷积核的不断操作来提取图中的特征信息,从而进行判断。
(二)代码实现
import torch
from torch import nn
from d2l import torch as d2l
def corr2d(X,K):
"""计算二维卷积的操作,步长默认为1"""
h,w = K.shape
Y = torch.zeros((X.shape[0]-h+1,X.shape[1]-w+1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i][j] = (X[i:i+h, j:j+w] * K).sum()
return Y
X = torch.arange(9,dtype=torch.float32).reshape((3,3))
K = torch.arange(4,dtype=torch.float32).reshape((2,2))
print(corr2d(X,K))
# 自定义卷积层,默认为一个卷积核的情况
class Conv2D(nn.Module):
def __init__(self, kernal_size) -> None:
super().__init__()
self.weight = nn.Parameter(torch.randn(kernal_size))
self.bias = nn.Parameter(torch.zeros(1))
def forward(self,X):
return corr2d(X,self.weight) + self.bias
layer = Conv2D((2,2))
print(layer(X))
# 卷积层的一个简单应用:检测图片中不同颜色的边缘
# 只能检测垂直的边缘
X = torch.ones((6,8))
X[:,2:6] = 0
print(X)
K = torch.tensor([[1.,-1.]])
Y = corr2d(X,K)
print(Y)
# 让机器自动学习卷积核的内容,学习由X,生成Y的卷积核
X = torch.ones((6,8))
X[:,2:6] = 0
K = torch.tensor([[1.,-1.]])
Y = corr2d(X,K)
conv2d = nn.Conv2d(1,1,kernel_size=(1,2),bias=False)
# 增加两个维度,第一个为通道;第二个为批量大小
# (c,b,h,w)
X = X.reshape((1,1,6,8))
Y = Y.reshape((1,1,6,7))
for i in range(25):
y_hat = conv2d(X)
l = (y_hat - Y)**2
conv2d.zero_grad()
l.sum().backward()
# 这里手动实现了
conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad
if (i+2) % 2 == 0:
print(f"epoch:{i}, loss:{l.sum():.3f}")
print(conv2d.weight.data)
网友评论