二维互相关运算
这里先补充一下之前的一个误区,关于pytorch中矩阵乘法的问题。之前我一直以为tensortensor与torch.mm (tensor, tensor)的效果是一样的。实则不然,前者表示的实际上是按元素相乘求和的运算,而后者表示的才是矩阵乘法运算。tensortensor要求两个张量维度完全相同,这也是之前即使两个矩阵是ab与bc维度,使用*相乘时仍然报错的问题。
X = torch.tensor([[0, 1], [3, 4]])Y = torch.tensor([[0, 1], [3, 4]])print(X*Y, torch.mm(X, Y))结果:tensor([[ 0, 1],[ 9, 16]])tensor([[ 3, 4],[12, 19]])
关于pytorch中矩阵剪裁,比方说一个提取一个33的矩阵左上角22的矩阵,可以通过定义下标访问范围达到该目的:
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])print(X[0: 2, 0: 2])输出:tensor([[0, 1],[3, 4]])
所以,我们可以很容易定义出一个矩阵与一个卷积核之间的互相关操作:
def corr2d(x, k):h, w = k.shapetarget = torch.zeros(x.shape[0] - h + 1, x.shape[1] - w + 1)for i in range(target.shape[0]):for j in range(target.shape[1]):target[i][j] = (x[i: i + h, j: j + w] * k).sum()return targetX = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])Y = torch.tensor([[0, 1], [2, 3]])print(corr2d(X, Y))结果:tensor([[19., 25.],[37., 43.]])
二维卷积层
定义了互相关运算之后,我们就可以手动定义一个卷积层放入网络进行卷积操作了。注意初始化卷积核以及偏置项时不要忘记加上nn.Parameter,否则模型不知道这两者也是该模型的参数之一。
class Conv2D(nn.Module):def __init__(self, kernel_size):super(Conv2D, self).__init__()self.kernel = nn.Parameter(torch.randn(kernel_size))self.bias = nn.Parameter(torch.randn(1))def forward(self, x):target = corr2d(x, self.kernel) + self.biasreturn target
边缘检测
之前吴恩达机器学习课程中讲述的其实非常清楚了,不同的卷积核具有不同的功能,比如图像边缘检测等。此处我们可以初始化一幅灰度图,其中图像左右两端灰度为1,中间部分灰度为0:
pic = torch.ones(6, 8)pic[:, 2:6] = 0print(pic)输出:tensor([[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.],[1., 1., 0., 0., 0., 0., 1., 1.]])
再将该图像进行一次卷积运算:
kernel = torch.tensor([[1, -1]])pic = corr2d(pic, kernel)print(pic)结果:tensor([[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.],[ 0., 1., 0., 0., 0., -1., 0.]])
卷积核参数学习
先前我们之所以能够检测出一幅图像的边缘,是因为我们将卷积核设置为了[1, -1]。那么我们如果已知一幅图像以及通过卷积核计算后的结果,能不能学习出卷积核的参数呢?答案是可以的。
我们可以定义如下的函数进行训练,要注意由于进行梯度下降的过程中我们不希望梯度被追踪到,因此我们需要对kernel与bias这两个参数的data进行运算操作,这样既可以修改参数的值,又可以不被计算图追踪:
def train(net, step, lr, x, y):for i in range(step):y_hat = net(x)l = ((y - y_hat) ** 2).sum()l.backward()net.kernel.data -= lr * net.kernel.gradnet.bias.data -= lr * net.bias.gradconv2d.kernel.grad.fill_(0)conv2d.bias.grad.fill_(0)if (i + 1) % 5 == 0:print('Step %d, loss %.3f' % (i + 1, l.item()))if __name__ == "__main__":print(pic)kernel = torch.tensor([[1, -1]])label = corr2d(pic, kernel)conv2d = Conv2D(kernel_size=(1, 2))train(conv2d, 20, 0.01, pic, label)输出:Step 5, loss 2.141Step 10, loss 0.295Step 15, loss 0.048Step 20, loss 0.010
梯度清零还可以用如下操作完成,效果是一样的:
net.kernel.grad.data.zero_()net.bias.grad.data.zero_()
