NNDL 作业5:卷积

作业1

编程实现

 

1. 图1使用卷积核\begin{pmatrix} 1 & -1 \end{pmatrix},输出特征图

2. 图1使用卷积核\begin{pmatrix} 1\\ -1\\ \end{pmatrix},输出特征图

import torch
from torch import nn
import matplotlib.pyplot as plt
import numpy as np
def corr2d(X, K):  #@save
    """计算二维互相关运算"""
    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.tensor([[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],
                  ])
K = torch.tensor([[1,-1]])
Y = corr2d(X,K)
print(Y)
J = torch.tensor([[1],[-1]])
Z = corr2d(X,J)
print(Z)
plt.figure()
plt.subplot(121)
plt.imshow(Y,cmap='gray')
plt.subplot(122)
plt.imshow(Z)
plt.show()

得到以下结果:

tensor([[   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.]])
tensor([[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.]])

 

3. 图2使用卷积核\begin{pmatrix} 1 & -1 \end{pmatrix},输出特征图

4. 图2使用卷积核\begin{pmatrix} 1\\ -1\\ \end{pmatrix},输出特征图 

import torch
from torch import nn
import matplotlib.pyplot as plt
import numpy as np
def corr2d(X, K):  #@save
    """计算二维互相关运算"""
    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.tensor([[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],[0,0,0,255,255,255],
                  [255,255,255,0,0,0],[255,255,255,0,0,0],[255,255,255,0,0,0],[255,255,255,0,0,0],[255,255,255,0,0,0],[255,255,255,0,0,0],[255,255,255,0,0,0]])
K = torch.tensor([[1,-1]])
Y = corr2d(X,K)
print(Y)
J = torch.tensor([[1],[-1]])
Z = corr2d(X,J)
print(Z)
plt.figure()
plt.subplot(121)
plt.imshow(Y,cmap='gray')
plt.subplot(122)
plt.imshow(Z,cmap='gray')
plt.show()

得到以下结果: 

tensor([[   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0., -255.,    0.,    0.],
        [   0.,    0.,  255.,    0.,    0.],
        [   0.,    0.,  255.,    0.,    0.],
        [   0.,    0.,  255.,    0.,    0.],
        [   0.,    0.,  255.,    0.,    0.],
        [   0.,    0.,  255.,    0.,    0.],
        [   0.,    0.,  255.,    0.,    0.],
        [   0.,    0.,  255.,    0.,    0.]])
tensor([[   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.],
        [-255., -255., -255.,  255.,  255.,  255.],
        [   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.]])

 

5. 图3使用卷积核\begin{pmatrix} 1 & -1 \end{pmatrix}\begin{pmatrix} 1\\ -1\\ \end{pmatrix}\begin{pmatrix} 1 &-1 \\ -1&1 \end{pmatrix} ,输出特征图 

import torch
from torch import nn
import matplotlib.pyplot as plt
import numpy as np
def corr2d(X, K):  #@save
    """计算二维互相关运算"""
    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.tensor([[0,0,0,0,0,0,0,0,0],[0,255,0,0,0,0,0,255,0],
                  [0,0,255,0,0,0,255,0,0],[0,0,0,255,0,255,0,0,0],
                  [0,0,0,0,255,0,0,0,0],[0,0,0,255,0,255,0,0,0],
                  [0,0,255,0,0,0,255,0,0],[0,255,0,0,0,0,0,255,0],[0,0,0,0,0,0,0,0,0]])
K = torch.tensor([[1,-1]])
Y = corr2d(X,K)
print(Y)
J = torch.tensor([[1],[-1]])
Z = corr2d(X,J)
print(Z)
Q = torch.tensor([[1,-1],[-1,1]])
M = corr2d(X,Q)
print(M)
plt.figure()
plt.subplot(221)
plt.imshow(Y,cmap='gray')
plt.subplot(222)
plt.imshow(Z,cmap='gray')
plt.subplot(223)
plt.imshow(Q,cmap='gray')
plt.show()

得到以下结果:

tensor([[   0.,    0.,    0.,    0.,    0.,    0.,    0.,    0.],
        [-255.,  255.,    0.,    0.,    0.,    0., -255.,  255.],
        [   0., -255.,  255.,    0.,    0., -255.,  255.,    0.],
        [   0.,    0., -255.,  255., -255.,  255.,    0.,    0.],
        [   0.,    0.,    0., -255.,  255.,    0.,    0.,    0.],
        [   0.,    0., -255.,  255., -255.,  255.,    0.,    0.],
        [   0., -255.,  255.,    0.,    0., -255.,  255.,    0.],
        [-255.,  255.,    0.,    0.,    0.,    0., -255.,  255.],
        [   0.,    0.,    0.,    0.,    0.,    0.,    0.,    0.]])
tensor([[   0., -255.,    0.,    0.,    0.,    0.,    0., -255.,    0.],
        [   0.,  255., -255.,    0.,    0.,    0., -255.,  255.,    0.],
        [   0.,    0.,  255., -255.,    0., -255.,  255.,    0.,    0.],
        [   0.,    0.,    0.,  255., -255.,  255.,    0.,    0.,    0.],
        [   0.,    0.,    0., -255.,  255., -255.,    0.,    0.,    0.],
        [   0.,    0., -255.,  255.,    0.,  255., -255.,    0.,    0.],
        [   0., -255.,  255.,    0.,    0.,    0.,  255., -255.,    0.],
        [   0.,  255.,    0.,    0.,    0.,    0.,    0.,  255.,    0.]])
tensor([[ 255., -255.,    0.,    0.,    0.,    0.,  255., -255.],
        [-255.,  510., -255.,    0.,    0.,  255., -510.,  255.],
        [   0., -255.,  510., -255.,  255., -510.,  255.,    0.],
        [   0.,    0., -255.,  510., -510.,  255.,    0.,    0.],
        [   0.,    0.,  255., -510.,  510., -255.,    0.,    0.],
        [   0.,  255., -510.,  255., -255.,  510., -255.,    0.],
        [ 255., -510.,  255.,    0.,    0., -255.,  510., -255.],
        [-255.,  255.,    0.,    0.,    0.,    0., -255.,  255.]])

 

 

作业2

 一、概念

自己的语言描述“卷积、卷积核、特征图、特征选择、步长、填充、感受野”。

卷积:

将此时深色区域内每个元素分别与对应的权值相乘然后再相加,所得到的值作为输出矩阵的第二个元素;重复上述“求点积–滑动窗口"操作,直至得到输出矩阵所有值。卷积核在2维输入数据上“滑动”,对当前输入部分的元素进行矩阵乘法,然后将结果汇为单个输出像素值,重复这个过程直到遍历整张图像,这个过程就叫作卷积。

卷积核:

 卷积过程中那个权值矩阵就是卷积核。

特征图:

卷积操作结束后得到的就是特征图。

 特征选择:

 特征选择想要做的是:选择尽可能少的子特征,模型的效果不会显著下降,并且结果的类别分布尽可能的接近真实的类别分别。

特征选择的本质就是对一个给定特征子集的优良性通过一个特定的评价标准(evaluation criterion)进行衡量.通过特征选择,原始特征集合中的冗余(redundant)特征和不相关(irrelevant)特征被除去。

    因此,进行特征选择的主要目的:

   1、降维
   2、降低学习任务的难度
   3、提升模型的效率

步长:

可以理解为卷积过程中一次滑动的距离。

填充:

在前面的卷积中可以发现输入图像与卷积核进行卷积后的结果中损失了部分值,输入图像的边缘被“修剪”掉了(边缘处只检测了部分像素点,丢失了图片边界处的众多信息)。这是因为边缘上的像素永远不会位于卷积核中心,而卷积核也没法扩展到边缘区域以外。

这个结果我们是不能接受的,有时我们还希望输入和输出的大小应该保持一致。为解决这个问题,可以在进行卷积操作前,对原矩阵进行边界填充(Padding),也就是在矩阵的边界上填充一些值,以增加矩阵的大小,通常都用“0”来进行填充的。

感受野:

卷积神经网络每一层输出的特征图(feature map)上的像素点在输入图片上映射的区域大小。再通俗点的解释是,特征图上的一个点对应输入图上的区域。就是这个特征图是谁卷积出来的谁就是这个特征图的感受野。

 比如这个例子,进行三层3*3的卷积核卷积操作之后的感受野是7*7。

CNN基础知识——卷积(Convolution)、填充(Padding)、步长(Stride) - 知乎 (zhihu.com)

知乎盐选 | 深度学习卷积神经网络研究概述 (zhihu.com)

(51条消息) 卷积神经网络中感受野的详细介绍_Microstrong0305的博客-CSDN博客_卷积神经网络局部感受野

二、探究不同卷积核的作用

000
010
000

因为只有中心点的值是1。邻域点的权值都是0,对滤波后的取值没有任何影响

bulr模糊

 buttom sobel底部轮廓检测

 emboss浮雕

left sobel 左侧轮廓检测

 outline 边缘检测

right sobel 右侧轮廓检测

 sharpen 锐化

top sobel 上部轮廓检测

 

三、编程实现

      1、实现灰度图的边缘检测、锐化、模糊。 

import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
from PIL import Image
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号 #有中文出现的情况,需要u'内容
# https://blog.csdn.net/weixin_40123108/article/details/83510592
file_path = 'cameraman.tif'
im = Image.open(file_path).convert('L')  # 读入一张灰度图的图片
im = np.array(im, dtype='float32')  # 将其转换为一个矩阵
print(im.shape[0], im.shape[1])
plt.subplot(221)
plt.imshow(im.astype('uint8'), cmap='gray')  # 可视化图片
plt.title('原图')

im = torch.from_numpy(im.reshape((1, 1, im.shape[0], im.shape[1])))
conv1 = nn.Conv2d(1, 1, 3, bias=False)  # 定义卷积
conv2 = nn.Conv2d(1, 1, 3, bias=False)  # 定义卷积
conv3 = nn.Conv2d(1, 1, 3, bias=False)  # 定义卷积



sobel_kernel = np.array([[-1, -1, -1],
                         [-1, 8, -1],
                         [-1, -1, -1]], dtype='float32')  # 定义轮廓检测算子
sobel_kernel = sobel_kernel.reshape((1, 1, 3, 3))  # 适配卷积的输入输出
conv1.weight.data = torch.from_numpy(sobel_kernel)  # 给卷积的 kernel 赋值
edge1 = conv1(Variable(im))  # 作用在图片上
x1 = edge1.data.squeeze().numpy()

sobel_kernel = np.array([[0, -1, 0],
                         [-1, 5, -1],
                         [0, -1, 0]], dtype='float32')  # 定义锐化算子
sobel_kernel = sobel_kernel.reshape((1, 1, 3, 3))  # 适配卷积的输入输出
conv2.weight.data = torch.from_numpy(sobel_kernel)  # 给卷积的 kernel 赋值
edge2 = conv2(Variable(im))  # 作用在图片上
x2 = edge2.data.squeeze().numpy()

sobel_kernel = np.array([[0.0625, 0.125, 0.0625],
                 [0.125, 0.25, 0.125],
                 [0.0625, 0.125, 0.0625]], dtype='float32')  # 定义模糊算子
sobel_kernel = sobel_kernel.reshape((1, 1, 3, 3))  # 适配卷积的输入输出
conv3.weight.data = torch.from_numpy(sobel_kernel)  # 给卷积的 kernel 赋值
edge3 = conv3(Variable(im))  # 作用在图片上
x3 = edge3.data.squeeze().numpy()

print(x1.shape)  # 输出大小
print(x2.shape)
print(x3.shape)


plt.subplot(222)
plt.imshow(x1, cmap='gray')
plt.title('边缘检测')
plt.subplot(223)
plt.imshow(x2, cmap='gray')
plt.title('锐化')
plt.subplot(224)
plt.imshow(x3, cmap='gray')
plt.title('模糊')
plt.show()

得到以下结果: 

 果然,我也遇到了锐化图像变灰的问题,虽然老师和同学们在博客中说了是因为数值超出了[0,255]这个范围的问题,不过纸上得来终觉浅,还是要实践一下,这里先看一下锐化后的图片是什么样的

 通过图中不难发现有很多的数值已经超出了[0,255]的范围,那么就要再探索一下是不是这个原因造成的图片变灰,所以加上这串代码

for i in range(x2.shape[0]):
    for j in range(x2.shape[1]):
        if x2[i][j]>255:
            x2[i][j]=255
        if x2[i][j]<0:
            x2[i][j]=0

 

 果然变灰的问题消失了。不过我又发现了个问题

 

这个是我在查询x2也就是锐化的时候碰巧发现的,边缘检测的图像也是有很多小于0的数值的,那么他是不是也是灰色的呢? 

 果然,正常的边缘检测应该是上图这样的,而不是最开始那样的。

但是总不能每次都把他循环一遍吧,有时候没有标准的答案我们也不能每次都看一下数组内的具体数值有没有超过范围吧,那么有没有一种方法可以确保这个值在[0,255]之间呢?

答案肯定是有的,我这里参考了老师的博客,也去matplotlib的官网查了一下。他这里说只接受[0,1]之间的浮点和[0,255]

 那么超出[0,255]范围的就可以设置参数vmin=0,vmax=255

那么我们就把代码改一下

plt.subplot(222)
plt.imshow(x1, cmap='gray',vmax=255,vmin=0)
plt.title('边缘检测')
plt.subplot(223)
plt.imshow(x2, cmap='gray',vmax=255,vmin=0)
plt.title('锐化')
plt.subplot(224)
plt.imshow(x3, cmap='gray',vmax=255,vmin=0)
plt.title('模糊')
plt.show()

 

 

 如果我们用循环的时间

用时 0.6873586177825928

而设置参数的方法

用时 0.23428702354431152

 可以明显看出来要比我们用循环的笨方法快很多,而且我选择的还只是一个256*256的图片,对于现在的高精度图片来说已经是非常非常小的了。

2、调整卷积核参数,测试并总结。

我先是调了一下卷积核的初始权值:

 可以得到结论是初始权值中间的那个数越大,图片越亮,也可以说是卷积后得到的值越大,而中间的数越小图片越暗,是什么样的操作是跟卷积核边上的数值有关。

然后我又在官网上找到了Conv2d的参数设置

可以看到默认的填充padding=0

那我们就先设置padding=1: 

卷积核=3*3的时候padding应该设为1,因为卷积后的图像大小应该是原图的边长-卷积核的边长+1而padding=1就是这样的

 在原图的一周加上一圈0那也就是边长+2,所以这个时候卷积后得到的特征图跟原图大小就是一样的了

256 256
(256, 256)
(256, 256)
(256, 256)

然后再设置一下padding=2,5,10 

发现填充的越多边上的黑边越大,这也很好理解,因为填充的都是0那0乘任何数都是0,所以边上的数值进行互相关运算后得到的数值也是0,那么卷积后得到的图像最边上的像素点都是0,所以填充的越多,边上的0越多,黑边也就越大。

默认的步长stride=1,那我们就把步长设置为stride=2,5,10 

 发现步长越大越模糊,这也很好理解,步长越大,忽略的像素点也就越多,像素点少了自然就模糊了。

在查询资料的时候我发现个很好玩的参数dilation——扩张卷积。它的道理从下面这个图可以很好理解这个是扩张率为2的时候,也就是dilation=2。

 在这里插入图片描述

 那我们也可以看到官方文档里给的扩张率默认为1,所以我们还是把它设为2,5,10 

 为什么说他有意思呢,因为我本人高度近视加散光200,这个模糊处理加dilation=10得到的结果特别像是我不带眼镜的时候看到的图像,是这种略微带点重影的。

这个dilation跟stride有点像,  这在某些情况下与0-dilated滤波器一起使用非常有用,因为它允许你用更少的层来更积极地合并整个输入的空间信息。例如,如果你把两个3 x 3的CONV层叠在一起,那么可以说,第二层的神经元是一个5x5的输入补丁的函数(我们会说这些神经元的有效感受野是5x5)。如果我们使用dilation的卷积,那么这个有效感受野会增长得更快。

3、使用不同尺寸图片,测试总结

找不同尺寸的图片那肯定又要用到matlab了,不得不说matlab里带的图片是真的方便,不仅有彩色的还有黑白的,而且尺寸有大有小。

 这是一个2215*2956的图片,我们可以发现这个时候除了边缘检测,锐化和模糊好像都没有太大的改变,这是很正常的,因为图片大了,所以本身含有的像素点就多,和卷积核进行运算的像素点多,所以提取出的特征清晰。而且这张图我们也能看出来边缘不是很明显,而我做的这个还是把权值调大了,不然看到的就是一片黑。

我们再找一个512*512的图像

用这个和上面的256*256的摄影师对比就很明显了,锐化和模糊的效果都变弱了,而边缘提取没有什么太大变换。 

4、探索更多类型卷积核。(选做)

我们这里就用上面提到过的卷积核来进行一下实验:

 当然还是摄影师更明显一点:

 

5、尝试彩色图片边缘检测。(选做)

        把彩色图片变成灰度图再边缘检测。

import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
from PIL import Image
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号 #有中文出现的情况,需要u'内容
img = plt.imread('football.jpg')
im = np.dot(img, [0.299, 0.587, 0.114])
print("加权平均灰度化:", im.shape)
plt.subplot(121)
plt.imshow(im, cmap='gray')
plt.title("加权平均灰度化")

im = np.array(im, dtype='float32')  # 将其转换为一个矩阵

im = torch.from_numpy(im.reshape((1, 1, im.shape[0], im.shape[1])))
conv1 = nn.Conv2d(1, 1, 3, bias=False)  # 定义卷积

sobel_kernel = np.array([[-1, -1, -1],
                         [-1, 8, -1],
                         [-1, -1, -1]], dtype='float32')  # 定义轮廓检测算子
sobel_kernel = sobel_kernel.reshape((1, 1, 3, 3))  # 适配卷积的输入输出
conv1.weight.data = torch.from_numpy(sobel_kernel)  # 给卷积的 kernel 赋值
edge1 = conv1(Variable(im))  # 作用在图片上
x1 = edge1.data.squeeze().numpy()



print(x1.shape)  # 输出大小


plt.subplot(122)
plt.imshow(x1, cmap='gray',vmax=255,vmin=0)
plt.title('边缘检测')
plt.show()

 大量实现表明,Grey = 0.299R + 0.587G + 0.114B 这个比例更符合人的视觉特点,当然你想改一下也是可以的,但是记得RGB的三个参数加起来要等于1哦。

参考链接(51条消息) Python实现图片灰度化_陈壮实的搬砖生活的博客-CSDN博客_python 灰度化

 

 总结:

最开始对一些简单的图片进行卷积的时候用的是手写的卷积公式,这个可以说没有什么难度,但是也帮助我对卷积原理的认识更加透彻。但是手写的毕竟不如Conv2d的功能全面,可以说他只能进行简单的卷积,所以后面就用了Conv2d。这次实验收获最大的就是对Conv2d函数里参数的了解,还有锐化和边缘检测变灰的处理办法,但是这次的作业也只写了卷积,对于后面的卷积神经网络来说只是开头的一部分,后面还有池化层和全连接层,虽然说上学期机器学习里面已经学过了卷积神经网络,不过这次再学一遍我可以很清晰的感觉学的更深了,收获更大。期待后面的实验带来的收获

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HBU_fangerfang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值