时隔很久,我又回来了。。。。。。把以前不是非常懂的都记录在这篇文章中。
1、单个卷积核
看一下基本的卷积操作,如下图。用一个kernel在原图像上面滑动,通过相乘并累加的运算计算出featureMap上面对应点的像素值。
可以看下面形象的动态图。
卷积神经网络一个kernel在滑动的时候权值是共享的,看下面的图也很好理解,不像全连接层参数巨多,CNN每层始终只保持了NxN(N为卷积核的大小,卷积核的尺度一般都是奇数)的参数,加载权值的时候可以节省很多内存。
2、多Channel的卷积
当然上面说的都是单个通道的情况,真正的RGB三通道的卷积操作是需要三个卷积核来负责的,相当于每个卷积核对应输入图像的一个Channel,再将结果累加起来形成图像上的像素点。
非常简单。图片上的(x,y)坐标上面都对应一种颜色,RGB三通道就会有一个三元组表示颜色。喂入卷积神经网络的一般都是RGB三通道的图片,所以每个通道进行卷积,最后将结果累加起来就是了。
3、损失函数及优化器
神经网络可以说每层对数据的操作都保存在权重里面,每层都是权重来初始化的。每次训练也就是先前向传播进行相关计算,再反向传播计算梯度并更新权重。
具体参数怎么更新梯度怎么计算,人家框架都给你封装好了,用一个叫做优化器的进行相关的计算。
框架封装的optimizer(优化器)中,可以设置学习率,动量及梯度下降算法等,优化器功能就是根据设置的参数执行某种梯度下降算法。
这里说一下动量,这也是以前的盲点。比较好理解,相当于是从下图的最左边开始梯度下降以及更新权值,快到局部最小值点的时候,因为有动量的存在,水平方向还会有向右的一个类似惯性的分量,如下图向上的箭头,合理设置好这个动量参数可以冲破局部最小值。
4、卷积神经网络训练的goal
首先要搞明白最终目标是什么,使用到卷积神经网络个人感觉无非是图像分类,那再往深层考虑,最后目的还是为了将训练好的模型用于测试的时候能获得很高的准确率。
那好,再搞清楚一件事情,训练时候用于优化模型的指标是什么?之前机器学习里面的线性回归什么的是拟合,指标就是最小化均方误差等;而CNN和逻辑回归有点像,只不过逻辑回归是二分类的,所以CNN指标是交叉熵。
5、香农与熵
以前一直以为均方误差这种损失函数也可以用于分类任务,到今天我才发现我彻底错了额,以前的自己就是个菜鸡。这时候有一位信息论的奠基人,香农大师。
正好周一的时候,上科技论文写作的时候,老师也提到了香农大师,他说:“那个年代审稿人没怎么看香农的论文就随便写几个理由给拒绝了。”数学大师的科研之路尚且曲折,冲。香农大师首次给熵这个东西下了定义。
熵的意义可以简单的理解为:熵越大不确定性越高。
交叉熵公式:
交叉熵其实就是熵的变形,有两个变量,可以想象成引入了预测值和真实值,用这个来评估分类任务的优化指标准确率是很高的,网上都说比回归时候的均方误差什么的准确率都要高。而且交叉熵的意义也和熵的意义是相同的,值越高也表示不确定性越高。
对于二分类的交叉熵,我看了一下之前自己写的逻辑回归的博客,其实就是上面写的经过极大似然法推导的(吴恩达机器学习的课程里面抄的)。
6、维度问题
手写自己的CNN网络的时候要弄清楚输入图片的格式,再来就是一次卷积核之后输出图片维度的计算。
pytorch框架中每次卷积后输出图片维度计算公式(no padding,步长为1) — b c w h:
batch_size x kernel_num x (input_width - kernel_size +1) x (input_height - kernel_size +1)
池化(下采样)输出维度比较简单,反正下采样不改变图片的通道数,就是根据入参改变图片的width和height。
7、pytorch实现最简单的LeNet
class LeNet5(nn.Module):
def __init__(self):
super(LeNet5, self).__init__()
self.conv_unit = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5, stride=1),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1),
nn.AvgPool2d(kernel_size=2, stride=2)
)
self.fc_unit = nn.Sequential(
# 这个维度可以自己初始化一个张量喂到卷积单元里面测出来
nn.Linear(16 * 5 * 5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, 10)
)
temp = torch.rand(2, 3, 32, 32)
print(self.conv_unit(temp).shape)
def forward(self, x):
x = self.conv_unit(x)
# 输入到全连接层的时候需要将featureMap展平为一维张量
x = x.view(x.size(0), -1)
x = self.fc_unit(x)
return x
8、pytorch的one-hot编码
主要就是理解out.scatter_(dim=1, index=idx, value=1)
函数的作用,非常简单。就是如果一个标签直接是索引并不是一维张量的话,就可以调用这个函数,先用zero初始化好这个矩阵,然后在指定的维度上面根据索引填充1就好了,那必然未被填充到的肯定是默认值,就是为0。
def one_hot(label, class_num):
out = torch.zeros(label.size(0), class_num)
# 将标量转化为1x1的张量
idx = torch.LongTensor(label).view(-1, 1)
# 将1填充到out张量对应dim=1上指定index的位置
out.scatter_(dim=1, index=idx, value=1)
return out