由于很多知识都不了解,所以,我先用python写一写代码,了解一些CNN的内部原理。
我参考的博客是这篇:卷积神经网络的简单可视化
用的编译环境是:python 3.9.1,pycharm community
开始敲代码。。。
import cv2
import matplotlib.pyplot as plt
然后一天就过去了……
总结:被matplotlib给玩弄了。。。好多依赖库没有下载成功导致无法运行。一整天都在进行下载库的操作。最后还是把自己以前能够运行的代码里的库包的文件夹一个一个复制过来,才最终解决了下载库的问题。。。吐了。。
终于搞定了BUG,开始敲代码
import cv2
import matplotlib.pyplot as plt
img_path = './img/michi_1.jpg' # 图片的路径
bgr_img = cv2.imread(img_path) # 读取图片的BGR三个通道(注意顺序B\G\R),返回值是np.array()
gray_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY) # 将BGR格式转换成灰度图片,转换的公式为 GRAY = B×0.114 + G×0.587 + R×0.299
gray_img = gray_img.astype("float32")/255 # 使用plt绘制热图,需要二维数组的值标准化[0, 1]
plt.imshow(gray_img, cmap='gray') # 形成了灰色渐变的热图。注意区分plt.imshow()和cv2.imshow()
plt.show()
接着敲代码
import numpy as np
filter_vals = np.array([[-1, -1, 1, 1], [-1, -1, 1, 1], [-1, -1, 1, 1], [-1, -1, 1, 1]])
# 变化产生更丰富的过滤器
filter_1 = filter_vals
filter_2 = -filter_1
filter_3 = filter_1.T
filter_4 = -filter_3
filters = np.array([filter_1, filter_2, filter_3, filter_4])
fig = plt.figure(figsize=(10, 5))
for i in range(4):
ax = fig.add_subplot(1, 4, i+1, xticks=[], yticks=[]) # 设置 1行4列 ,一列一个图像
ax.imshow(filters[i], cmap='gray')
ax.set_title('Filter %s' % str(i+1)) # 设置图片标题
width, height = filters[i].shape
for x in range(width):
for y in range(height):
ax.annotate(str(filters[i][x][y]), xy=(y,x),
horizontalalignment='center',
verticalalignment='center',
color='white' if filters[i][x][y] < 0 else 'black')
plt.show()
参考博客:【深度学习入门】——亲手实现图像卷积操作
RGB 模式中,数值的取值范围是 0 ~ 255,如果超出这个范围就应该截断,所以我们需要优化下程序
小于 0 时,像素值取 0,大于 255 时取 255,其它情况保持现值。
开始变难了。。。
敲代码,一开始又是被导入库的问题绊住了。。
# ======= 定义简单卷积神经网络 ========
import torch
import torch.nn as nn
import torch.nn.functional as F
"""
在定义自已的网络的时候,需要继承nn.Module类,
并重新实现构造函数__init__ 和 forward 这两个方法
"""
class Net(nn.Module): # 自定义网络层,继承nn.Module类
def __init__(self, weight): # 申明各个层的定义,weight是卷积核(4,1,4,4)
"""
创建一个卷积神经网络
:param in_channels: 输入通道数量
:param out_channels: 输出通道数量
:param kernel_size: 卷积核的大小,类型为int或者元组
:param bias: 是否将一个 学习到的 bias 增加输出中,默认是 True。
"""
super(Net, self).__init__() # 调用父类的构造函数
k_height, k_width = weight.shape[2:] # 获得卷积核的高、宽
self.conv = nn.Conv2d(in_channels=1, out_channels=4, kernel_size=(k_height, k_width), bias=False)
self.conv.weight = torch.nn.Parameter(weight) # 简而言之,可以把这个函数理解为类型转换函数,将一个不可训练的类型Tensor转换成可以训练的类型parameter,并将这个parameter绑定到这个module里面
self.pool = nn.MaxPool2d(kernel_size=4, stride=4) # 表示最大池化的窗口大小为4×4的正方形,步长是每次向右或者向下移动4步
def forward(self, x): # 实现层之间的连接关系,实际上就是前向传播的过程
"""
前向传播函数
:param x: 输入,tensor 类型
:return: 返回结果
"""
conv_x = self.conv(x) # 上面的nn.Conv2d,进行卷积操作后,conv_x -> (N, C, H, W)
activated_x = F.relu(conv_x) # relu: f(x) = max(0, x)
pooled_x = self.pool(activated_x) # 池化
return conv_x, activated_x, pooled_x
weight = torch.from_numpy(filters).unsqueeze(1).type(torch.FloatTensor) # filters 的大小为 4 4 4, weight 的大小被增加为 4 1 4 4, 1的维度是针对输入的一个通道
model = Net(weight)
# 为 gray img 添加 1 个 batch 维度,以及 1 个 channel 维度,并转化为 tensor
gray_img_tensor = torch.from_numpy(gray_img).unsqueeze(0).unsqueeze(1)
conv_layer, activated_layer, pooled_layer = model(gray_img_tensor)
接着是卷积可视化
# ======= 可视化卷积输出 ========
def viz_layer(layer, n_filters=4):
fig = plt.figure(figsize=(20, 20))
for i in range(n_filters):
ax = fig.add_subplot(1, n_filters, i + 1, xticks=[], yticks=[])
ax.imshow(np.squeeze(layer[0, i].data.numpy()), cmap='gray')
ax.set_title('Output %s' % str(i + 1))
# 输出原图
plt.imshow(gray_img, cmap='gray')
# 可视化卷积输出
viz_layer(conv_layer)
# 可视化卷积后激活函数后的输出
viz_layer(activated_layer)
plt.show()
img = cv.imread(filename[, flags])
功能:这是opencv中的处理图片的函数,使用时需 import cv2。读取的是图像的真实数据,读取通道的顺序为BGR(即使图片是RGBA四通道,cv2.imread()方法仍然读取的是BGR三通道),可以操作图像每个元素。
filename:需要打开图片的路径,可以是绝对路径或者相对路径,路径中不能出现中文。
flag:图像的通道和色彩信息(默认值为1)
flag = -1, 8位深度,原通道
flag = 0, 8位深度,1通道
flag = 1, 8位深度,3通道
flag = 2, 原深度, 1通道
flag = 3, 原深度, 3通道
flag = 4, 8位深度,3通道
cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道
cv2.IMREAD_GRAYSCALE:读入灰度图片
cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道
返回值img:数据类型是np.array()类型。
参考博客:Python cv2.imread()基本参数介绍、图像读取的Image.open()和cv2.imread()的区别
cv2.imread()接口读图像,读进来直接是BGR 格式,数据格式在 0~255,由于不是我们最常见的RGB格式,颜色肯定有区别。
cv2.cvtColor(p1,p2) 是颜色空间转换函数,p1是需要转换的图片,p2是转换成何种格式。
cv2.COLOR_BGR2RGB 将BGR格式转换成RGB格式
cv2.COLOR_BGR2GRAY 将BGR格式转换成灰度图片
参考博客:cv2.imread()和cv2.cvtColor() 的使用
具体转换的算法参考OpenCV文档
第一个参数是显示图像的窗口的名字;
第二个参数是要显示的图像(imread读入的图像),窗口大小自动调整为图片大小
在matplotlib中,imshow方法首先将二维数组的值标准化为0到1之间的值,然后根据指定的渐变色依次赋予每个单元格对应的颜色,就形成了热图。
imshow方法常用的几个参数如下
1. cmap
cmap是colormap的简称,用于指定渐变色,默认的值为viridis, 在matplotlib中,内置了一系列的渐变色,用法如下
plt.imshow(data, cmap='Greens')
参考博客:matplotlib基础绘图命令之imshow
使用Module类来自定义网络层,在__init__构造函数中申明各个层的定义,在forward中实现层之间的连接关系,实际上就是前向传播的过程。
①我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__和forward这两个方法。
(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数_init_()中,当然我也可以吧不具有参数的层也放在里面;
(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
参考博客:pytorch教程之nn.Module类详解——使用Module类来自定义网络层、pytorch教程之nn.Module类详解——使用Module类来自定义模型
①用法
②参数
in_channels:输入的通道数目 【必选】
out_channels: 输出的通道数目 【必选】
kernel_size:卷积核的大小,类型为int 或者元组,当卷积是方形的时候,只需要一个整数边长即可,卷积不是方形,要输入一个元组表示 高和宽。【必选】
stride: 卷积每次滑动的步长为多少,默认是 1 【可选】
padding: 设置在所有边界增加 值为 0 的边距的大小(也就是在feature map 外围增加几圈 0 ),例如当 padding =1 的时候,如果原来大小为 3 × 3 ,那么之后的大小为 5 × 5 。即在外围加了一圈 0 。【可选】
dilation:控制卷积核之间的间距【可选】
groups:控制输入和输出之间的连接。(不常用)【可选】
bias: 是否将一个 学习到的 bias 增加输出中,默认是 True 。【可选】
padding_mode : 字符串类型,接收的字符串只有 “zeros” 和 “circular”。【可选】
注意:参数 kernel_size,stride,padding,dilation 都可以是一个整数或者是一个元组,一个值的情况将会同时作用于高和宽 两个维度,两个值的元组情况代表分别作用于 高 和 宽 维度。
参考博客:torch.nn.Conv2d() 用法讲解、pytorch之torch.nn.Conv2d()函数详解
③计算原理
(N, Cin, H, W)表示N(batch size)张图片,Cin个输入通道,H行,W列。
四个参数分别表示 (batch_size, C_in, H_in, W_in)
分别对应,批处理大小,输入通道数,图像高度(像素),图像宽度(像素)
(N, Cout, Hout, Wout)中Cout等于卷积核的个数
具体的卷积操作的计算示意图:
当卷积核的输入通道数为3时,卷积的计算方式:
当有两个这样的卷积核时,输出的通道数为2(要和卷积核的个数保持一致):
理解1:torch.nn.Parameter是继承自torch.Tensor的子类,其主要作用是作为nn.Module中的可训练参数使用。它与torch.Tensor的区别就是nn.Parameter会自动被认为是module的可训练参数,即加入到parameter()这个迭代器中去;而module中非nn.Parameter()的普通tensor是不在parameter中的。
注意到,nn.Parameter的对象的requires_grad属性的默认值是True,即是可被训练的,这与torth.Tensor对象的默认值相反。
在nn.Module类中,pytorch也是使用nn.Parameter来对每一个module的参数进行初始化的。
理解2:首先可以把这个函数理解为类型转换函数,将一个不可训练的类型Tensor
转换成可以训练的类型parameter
并将这个parameter
绑定到这个module
里面(net.parameter()
中就有这个绑定的parameter
,所以在参数优化的时候可以进行优化的),所以经过类型转换这个self.v
变成了模型的一部分,成为了模型中根据训练可以改动的参数了。使用这个函数的目的也是想让某些变量在学习的过程中不断的修改其值以达到最优化。
参考博客:torch.nn.Parameter理解、PyTorch里面的torch.nn.Parameter()
①用法
torch.nn.MaxPool2d
(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
②参数
参考博客:torch.nn.MaxPool2d详解、MAXPOOL2D
参考博客:激活函数的作用
1.为何引入非线性的激活函数?
如果不用激活函数,在这种情况下每一层输出都是上层输入的线性函数。容易验证,无论神经网络有多少层,输出都是输入的线性组合,与没有隐藏层效果相当,这种情况就是最原始的感知机(Perceptron)了。因此引入非线性函数作为激活函数,这样深层神经网络就有意义了(不再是输入的线性组合,可以逼近任意函数)。最早的想法是sigmoid函数或者tanh函数,输出有界,很容易充当下一层输入。
2.引入ReLu的原因
第一,采用sigmoid等函数,算激活函数时(指数运算),计算量大,反向传播求误差梯度时,求导涉及除法,计算量相对大,而采用Relu激活函数,整个过程的计算量节省很多。
第二,对于深层网络,sigmoid函数反向传播时,很容易就会出现 梯度消失 的情况(在sigmoid接近饱和区时,变换太缓慢,导数趋于0,这种情况会造成信息丢失),从而无法完成深层网络的训练。
第三,ReLu会使一部分神经元的输出为0,这样就造成了 网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
3.ReLU激活函数的形式,如下图:
公式:f(x) = max(0, x)
划重点:
torch.from_numpy()用来将numpy.ndarray转换成Tensor,
但Tensor中并不存储数据,而是存储有对应Numpy数组的数据指针的拷贝,即共享内存。
参考博客:torch.Size理解
x = torch.tensor([1,2,3,4]) ````# x.size() = [4]
y = torch.tensor([[[1,2,3,4],[1,2,3,4],[1,2,3,4]],
[[1,2,3,4],[1,2,3,4],[1,2,3,4]]]) # y.size() = [3,3,4]
import cv2
import matplotlib.pyplot as plt
from PIL import Image
解决教程搬运:
没有找到cv2
应该安装opencv-python
pip install opencv-python -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
pip install matplotlib
# 以下都是matplotlib的依赖库
pip install pillow
pip install image
pip install cycler
pip install kiwisolver
……
pip install torch
pip install protocol
参考博客-import cv2安装opencv-python
但下载失败,所以我又进入了如下探索,比如发现没有用管理员身份打开cmd,或者是开了代理,又或者是没有用镜像等原因。
WARNING:Retrying(Retry(total=4,connect=None,read=None,redirect=None,status=None))
解决方法:关闭代理
设置-网络和Internet-代理-使用代理服务器(选择关闭)
关闭代理后,重新安装opencv-python,成功!
Traceback (most recent call last):
File “c:\……\cnn\venv\lib\site-packages\pip_v
endor\urllib3\response.py”, line 438, in _error_catcher
yield
分析:用管理员身份打开cmd,使用pip install matplotlib却发现,已经安装了matplotlib(或者用pip list查看)。接下来,我想直接把下载好的库导入到pycharm中。
操作就是,根据目录找到两个文件夹,如下图所示
粘贴后,matplotlib成功导入到pycharm!(参考博客:Python:pip下载库后导入Pycharm的方法)
注意:其他的库如果在pycharm中均下载失败,都可以用管理员身份打开cmd,然后pip install xxx,下载好之后,在按照以上方法,搬运到pycharm中。
参考博客:%matplotlib inline报错
分析:在使用jupyter notebook 或者 jupyter qtconsole的时候,才会经常用到%matplotlib,而我的代码并没有用到以上两个东西。
解决方案:删除这一行代码,然后在所有画图的语言下面加上plt.show()就可以了。