1.什么是卷积?
卷积是一个数学概念,数学上把以上的两类运算称之为卷积。
上面一个公式是研究连续情况时的公式写法,下面这个则是研究离散数据时的公式写法。
卷积的主要运算方法以及其特点都可以概括为一点:
翻转叠加
我们接下来来代入实际的情况来研究这种运算的方法及其意义。
2.如何进行卷积?
在这里我给出两个函数,一个函数代表你一天以内的进食状况。
另一个函数给出你胃中食物经过一定时间消化之后所剩下的量。
同时提出一个问题:在这一天之内,14点时,你胃中还剩多少量的食物?
在解决这个问题之前我们先做一个引入:13时摄入的食物,在14时还剩下多少,即r(13)的值是多少?
很容易得出,r(13)的计算是先得到13时摄入的食物的量,再乘上经过一定时间消化之后所剩下的量。即
r(13)= 0.5 * 0.7 = 0.35
我们如果写成公式的形式就是
r(13)= E(13) * D(14 – 13) = 0.35
如果是12时、11时,或者任意时刻呢?我们可以得出一个通项公式
r(t)= E(t) * D(14 – t)
那么原来的问题就很容易解决了。14时中胃中所剩食物的量就是这之前所有时刻所摄入的食物经过消化后中在胃中所剩的所有量。
也就是将之前的式子进行从1时到14时的累加(实际上有效的部分最多只有8点到14点)。
这种运算方法就被称作卷积。
那么这样的运算方式实际上是如何体现翻转叠加的呢?
我们来对原式子进行一个简单的变换。
可以见得实际上E(t)函数没做任何变换,而D(t)函数则是先向右移动14个单位,而后再相对于t = 14进行翻转。
这一操作就是翻转叠加中的翻转
观察一下变换后的两个函数
可以发现消化情况函数和进食状况函数在我们需要研究的有效时间段(即经过消化后依旧有食物剩余的时间段)相重叠,我们将重叠部分进行相乘再累加,这一操作就是叠加。所得输出就是我们需要研究的14时的时候胃中所剩下食物量。
这只是14时的情况,如果我们需要研究13时、12时,又或者是任意时刻呢?
我们只需要将14替换成变量T即可。对应在翻转叠加运算中,则是在开始的移动单位的不同,实际运算都是我们反转过后的卷积函数(卷积核)与重叠部分的叠加。
在这里我们提到了一个新的概念,卷积核,卷积核实际上就是为了运算方便而经过反转之后的卷积函数。
简单阐释一下为什么:
在这里x对于x-1的位置在1,对应的卷积函数的值在G(1);
在这里x对于x的位置在0,对应的卷积函数的值在G(0);
在这里x对于x+1的位置在-1,对应的卷积函数的值在G(-1);
在我们的抽象运算中需要翻转过来代入,十分麻烦,所以一般运算时我们采用的是翻转后再叠加的方法,这样就可以实现需要运算对象与其对应的卷积函数中的函数值一一对应继而只需将重叠部分相乘在累加就可以输出了。
采用这样的运算方法,我们可以调用torch.nn.functional里的conv1d函数。
import torch
import torchvision
from matplotlib import pyplot as plt
kernel1 = torch.Tensor([0.0, 0.12, 0.32, 0.43, 0.55, 0.7, 1.0]).float()
kernel1 = torch.reshape(kernel1, [1, 1, 7])
target = torch.Tensor([0, 0, 0, 0, 0, 2.1, 4.2, 6.9, 0, 2.1, 0, 9.5, 0.5, 2.6, 0, 2.3, 12.6, 1.4, 0, 0, 4.1, 0, 0, 0]).float()
target = torch.reshape(target, [1, 1, 24])
output=torch.nn.functional.conv1d(target,kernel1,padding=6)
print(output)
plt.imshow(output[0])
plt.show()
经过这种翻转叠加的运算,我们将14时以前以后的所有时刻摄入的食物都进行了整合再作为新值填入我们用来表示胃中所剩食物的函数。那么是不是就可以说卷积是一种描述周围的点对某一研究对象所产生的影响的一种运算呢。答案是是的,而这种影响方式决定于D(t)。D(t)也被称作是卷积运算中的卷积函数。
一维卷积
让我们进一步来验证一下这种影响方式即卷积的意义。
import torch
import matplotlib.pyplot as plt
import torch.nn.functional as F
import numpy as np
tensor = torch.tensor([1, 2, 2, 1, 9, 4, 1, 2, 2, 1]).float()
kernel1 = torch.tensor([0.1, 2, 0.1])
kernel2 = torch.tensor([0.3, 0.3, 0.3])
tensor = torch.reshape(tensor, [1, 1, 10])
kernel1 = torch.reshape(kernel1, [1, 1, 3])
kernel2 = torch.reshape(kernel2, [1, 1, 3])
output1 = F.conv1d(tensor, kernel1, stride=1, padding=1)
output2 = F.conv1d(tensor, kernel2, stride=1, padding=1)
plt.imshow(output2[0], 'gray')
plt.show()
print(output1)
print(output2)
这里我给出一个一维张量,这是他的灰度图。
然后给出两个卷积核kernel1与kernel2。
kernel1 = torch.tensor([0.1, 2, 0.1])
简单推理一下kernel1,如果在峰值时,中间的峰值会放大,而两边的影响会以小占比影响输出的值;如果在峰值以外时,由于重叠部分的值相对较小,所产生的值也会较小。显然,它可以突出峰值特征。
kernel2 = torch.tensor([0.3, 0.3, 0.3])
再看一下kernel2,他将其周围所有的值进行取平均值再填入,它可以做到将数据进行平滑化。
我们来验证一下。
D:\ANACONDA\envs\PY38\python.exe D:\PRO11_8\conv1dTest.py
tensor([[[ 2.2000, 4.3000, 4.3000, 3.1000, 18.5000, 9.0000, 2.6000, 4.3000, 4.3000, 2.2000]]])
tensor([[[0.6000, 1.0000, 1.0000, 2.4000, 2.8000, 2.8000, 1.4000, 1.0000, 1.0000, 0.6000]]])
可见输出output1虽然在灰度图中不是很明显,但在数值上的确将峰值放大了。
output2则无论在灰度图还是在数值上都很明显的变得平缓。
二维卷积
我们提到过卷积的运算方式是翻转叠加,如果我们将将这种翻转叠加的运算运用到二维数据中,就是二维卷积。
以下是动画演示:
tensor = torch.tensor([
[0, 0, 0, 0, 100.0, 100.0, 100.0, 100.0, 100.0],
[0, 0, 0, 0, 100.0, 100.0, 100.0, 100.0, 100.0],
[0, 0, 0, 0, 100.0, 100.0, 100.0, 100.0, 100.0],
[0, 0, 0, 0, 100.0, 100.0, 100.0, 100.0, 100.0],
[0, 0, 0, 0, 100.0, 100.0, 100.0, 100.0, 100.0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]]).float()
kernel2 = torch.tensor([[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1]]).float()
tensor = torch.reshape(tensor, [1, 1, 9, 9])
kernel2 = torch.reshape(kernel2, [1, 1, 3, 3])
output2 = F.conv2d(tensor, kernel2, stride=1, padding=1)
outputImg = output2.numpy()
outputShow = np.squeeze(outputImg[0], 0)
plt.imshow(outputShow)plt.show()
这里的二维卷积我们选用的依旧是对周围的像素点取平均值填入的卷积核。
可见输出结果和预期是一致的,二维张量中的边界变得更为平缓。
图像卷积操作
而关于图像的描述我们可以将其视作一个三维矩阵,他的维度表示成(h,w,c)分别代表矩阵的高、宽、还有通道数
因此我们可以将二维卷积的运算推广到图片处理当中。
以下是利用二维平滑化卷积核处理过后的图片。
但这仍然和我们所知晓的神经网络中卷积有些不同,我们所知晓的卷积是一种对图像处理的方法,是一种特征提取的工具。
这是由于我们取用的卷积核,目前我们只是取用平滑化的卷积核和具有特定处理意义的卷积核。而卷积核还有一种称作过滤器的卷积核。
过滤器
import numpy as np
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
tensor = torch.tensor([[0, 0, 0, 0, 100, 100, 100, 100, 100],
[0, 0, 0, 0, 100, 100, 100, 100, 100],
[0, 0, 0, 0, 100, 100, 100, 100, 100],
[0, 0, 0, 0, 100, 100, 100, 100, 100],
[0, 0, 0, 0, 100, 100, 100, 100, 100],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]])
kernel1 = torch.tensor([[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]])
kernel2 = torch.tensor([[-1, -1, -1],
[0, 0, 0],
[1, 1, 1]])
tensor = torch.reshape(tensor, [1, 1, 9, 9])
kernel1 = torch.reshape(kernel1, [1, 1, 3, 3])
kernel2 = torch.reshape(kernel2, [1, 1, 3, 3])
output1 = F.conv2d(tensor, kernel1, stride=1, padding=1)
output2 = F.conv2d(tensor, kernel2, stride=1, padding=1)
# tensorImg = tensor.numpy()
# tensorShow = np.squeeze(tensorImg[0], 0)
# plt.imshow(tensorShow)# plt.show()
#
# outputImg = output1.numpy()
# outputShow = np.squeeze(outputImg[0], 0)
# plt.imshow(outputShow)
# plt.show()
outputImg = output2.numpy()
outputShow = np.squeeze(outputImg[0], 0)
plt.imshow(outputShow)
plt.show()
print(tensor)
print(output2)
这里给出的两个卷积核kernel1和kernel2
简单推理一下,kernel1是将重叠部分的右边减去左边。
设想一下,如果在同一片数据区域内部时的卷积,由于内部区域差异较小,产生的值较小。但是如果在边界部分,左右两边的值差异较大,因此产生的值也较大。可以得出,该卷积核是突出竖直边界的卷积核,而kernel2同理,则是突出水平边界的卷积核。
这两个卷积核的作用就是过滤出原图片的边界部分并保留其位置。观察输出。
运用到图片的效果也十分明显:
import PIL.Image
import matplotlib.pyplot as plt
import torch
from PIL.Image import Image
from torch import nn
from torchvision.transforms import transforms
img_path = 'data/img/img_2.png'
image = PIL.Image.open(img_path)
if image.mode != 'L':
image = image.convert('L')
kernel = torch.tensor([[-1, 0, 1],
[-1, 0, 1],
[-1, 0, 1]]).view(1, 1, 3, 3)
image_tensor2 = transforms.ToTensor()(image)
image_tensor = transforms.ToTensor()(image).unsqueeze(0)
print(image_tensor2.shape)
print(image_tensor.shape)
conv_layer = nn.Conv2d(in_channels=image_tensor.shape[1], out_channels=1, kernel_size=3, stride=1, padding=1, bias=False)
conv_layer.weight.data = kernel
output = conv_layer(image_tensor)
output_array = output.squeeze().detach().numpy()
plt.figure(figsize=(8, 4))
plt.subplot(1, 2, 1)
plt.title("Original Image")
plt.imshow(image)
plt.axis('off')
plt.subplot(1, 2, 2)
plt.title("Convolved Image")
plt.imshow(output_array, cmap='gray')
plt.axis('off')
plt.show()