CNN原理及Keras实现
1 前言
从本篇博客开始介绍一系列经典的神经网络模型,比如CNN,RNN等等,大概对自己的要求初步就是理解每个模型的原理,然后能用keras实现即可!后续等kera较为熟练之后,再上手新的框架,框架不重要,最核心的其实还是理解这个模型本身。
今天主要介绍下卷积神经网络!
2 什么叫卷积神经网络?
卷积神经网络的英文名称叫:Convolutional Neural Network(CNN) ,其中 Convolutional表示卷积的含义,为什么有这个奇怪的东东呢?那是因为卷积神经网络相比于普通的多层神经网络加入了2个新的结构:
- 卷积层
- 池化层
正是由于有了这个卷积层才有了卷积神经网络这个称谓!
那相信大家肯定会问了,什么叫卷积和池化呢?
回答这个问题之前,我们首先来看下CNN整体的一个架构,看是如何运作的,具体见下图:
有了上图直观的认识之后,下面分几个问题来了解一下CNN的原理。
2.1 应用场景
CNN的应用场景主要是在图像处理上,这时候很简单的一个问题来了:
1、为什么图像处理要用CNN 而不是之前普通的多层神经网络呢?
要回答上述问题,首先就需要了解图像是如何存储在计算机当中的!
- 图像是由一个个像素点构成,每个像素点有三个通道,分别代表RGB颜色
- 那么,如果一个图像的尺寸是(28,28,1),即代表这个图像的是一个长宽均为28,channel为1的图像(channel也叫depth,此处1代表灰色图像,0代表黑色图像)。
- 如果使用全连接的网络结构,即,网络中的神经与与相邻层上的每个神经元均连接,那就意味着我们的网络有28 * 28 =784个神经元,hidden层采用了15个神经元,那么简单计算一下,我们需要的参数个数(w和b)就有:7841510+15+10=117625个,这个参数太多了,随便进行一次反向传播计算量都是巨大的,从计算资源和调参的角度都不建议用传统的神经网络。
为什么参数是117625呢?
- 图片是由像素点组成的,用矩阵表示的,2828的矩阵,肯定是没法直接放到神经元里的,我们得把它“拍平”,变成一个2828=784 的一列向量,这一列向量和隐含层的15个神经元连接,就有784×15=11760个权重w,隐含层和最后的输出层的10个神经元连接,就有11760*10=117600个权重w,再加上隐含层的偏置项15个和输出层的偏置项10个,就是:117625个参数了。
图解见下方:
所以通过上述的解释,普通的多层神经网络无法用在图像处理上!那下一个问题:卷积神经网络CNN又是如何胜任这一项工作的呢?而这就需要从CNN的网络结构说起了!
2.2 CNN的网络结构
CNN的网络结构通过博客一开始的图片就可以看到大概有三种不同的结构层:
- 卷积层
- 池化层
- Flatten层和最后的全连接层Fully Connected Layer
那么这三种结构分别代表什么意思呢?继续往下。
2.2.1 卷积层
首先说下最重要的卷积层,毕竟从CNN这个名称就可以看出来卷积层在CNN中的地位举足轻重了!
先上两个牛逼的动图:
上述很炫酷的操作展示的就是卷积的过程!
关于上述卷积首先就有这个问题了:
1、小滑窗是啥?为啥扫来扫去的?起到的目的是什么?
- 首先明确背景下的矩阵就是我们一张张的图片背后的像素矩阵!如果矩阵是28×28的,就说明图片的像素是28×28
- 其次小滑窗的学名叫卷积核filter,听着是不是高大上?
- 卷积核扫描的过程表面上是进行矩阵的内积运算,本质上是在提取图片的特征,获取图片的局部特性!
在上述通俗的解释之下,下面我们来看看一些学术名词和解释,卷积的过程大概涉及到3个参数:
- 深度depth:即卷积核filter的个数!它决定输出的depth厚度,卷积核有几个,输出的depth厚度就有多深。比如说一张图片的像素是: 28 x 28 x 1,经过25个 3 x 3 的卷积核 步长为1 扫描之后 也就形成了 25个 26 x 26 的矩阵! 即 25 x 26 x 26的结构!如果卷积核个数越多,那么输出的depth也就越深!
- 步长stride:表示每次滑动多少步
- 填充值zero-padding:在外围边缘补充若干圈0。如果补0,输出结构和原来的保持一致,否则就会减小!
2、为什么卷积核能起到效果?卷积核的权重是共享的吗?
上面的过程我们了解了大概卷积的操作,大家肯定会问,诶,为什么卷积会起到效果呢?
其实这个问题可以这么理解,如果对于设计不同的卷积核,比如有的是斜对角线全为1,那么这样扫描下来得到的结果中,如果哪一块斜着的元素值比较大就说明原始图片有着斜对角的相关特征,其余分析类似!所以卷积就是通过卷积核扫描矩阵自动提取特征而起到效果的!
故我们只需要把图片数据灌进去,设计好卷积核的尺寸、数量和滑动的步长就可以让自动提取出图片的某些特征,从而达到分类的效果!
小tips:一般情况下,根据实验得到的经验来看,会在越靠近输入层的卷积层设定少量的卷积核,越往后,卷积层设定的卷积核数目就越多
另外权重是共享的!上面动图的扫描过程就可以看到!
3、最右边得到的结果是怎么算的?
这个计算结果注意是矩阵的内积运算,而不是矩阵相乘运算即可,最后注意加上偏置项!
具体来看一个实例:为什么下图的卷积操作会得到1呢?
计算公式就是:
1
×
1
+
1
×
(
−
1
)
+
1
=
1
1×1+1×(-1)+1=1
1×1+1×(−1)+1=1 注意,任一相乘元素为0已经省略,计算就是各自两个矩阵内积,然后相加,最后加上偏置即可!
这样最复杂的卷积就讲完了!
2.2.2 池化层
其实关于池化层主要有两个问题:
1、什么叫池化层?分类是?
关于定义,具体见下图:
所以池化说白了就是一个降维的过程,如何降维呢?有两种方式:
- 最大池化。上图展示的就是最大池化,即将卷积之后的结果进行分区域,每个区域只保留最大的值!
- 平均池化。原理和上图类似,只是这时候不是保留最大,而是平均这个区域内的值!
2、为什么要搞这个池化层?
可以结合卷积核操作的本质来看!
- 由于每一个卷积核可以看做一个特征提取器,不同的卷积核负责提取不同的特征,比如让一个卷积核能够提取出“垂直”方向的特征,第二个卷积核能够提取出“水平”方向的特征
- 那么我们对其进行Max Pooling操作后,提取出的是真正能够识别特征的数值,其余被舍弃的数值,对于我提取特定的特征并没有特别大的帮助。那么在进行后续计算使,减小了feature map的尺寸,从而减少参数,达到减小计算量,缺不损失效果的情况。
一句话总结就是:池化操作保留下了卷积后有用的信息,同时还达到简化计算的目的!
2.2.3 Flatten层 & Fully Connected Layer
在CNN中,做完了卷积和池化之后,接下来干吗呢?能直接把结果丢出来吗?做预测吗?显然是不行的,因为上述操作ok之后数据还是矩阵的形式,最后需要的是预测某一类的概率!所以我们怎么办呢?
- 将矩阵给铺平!也就是Flatten!将二维的矩阵Flatten为一维的!
- 然后呢?丢在FC中,即全连接层,然后如果是多分类的问题,采用softmax对其进行分类,二分类就是sigmoid。
这样就将CNN的三层结构给讲清楚了!
2.3 其余的问题
除了上述基本的问题之外,还有几个小的问题值得注意:
1、卷积核的尺寸必须为正方形吗?可以为长方形吗?如果是长方形应该怎么计算?
不是必须的。可以为长方形。
2、卷积核的个数如何确定?每一层的卷积核的个数都是相同的吗?
由经验确定。不一定,而且一开始设少一点,后面多一点效果会更好!为什么呢?如手写数字识别中第一层我们设定卷积核个数为5个,一般是找出诸如"横线"、“竖线”、“斜线”等共性特征,我们称之为basic feature,经过max pooling后,在第二层卷积层,设定卷积核个数为20个,可以找出一些相对复杂的特征,如“横折”、“左半圆”、“右半圆”等特征,越往后,卷积核设定的数目越多,越能体现label的特征就越细致,就越容易分类出来
3、步长的向右和向下移动的幅度必须是一样的吗?
- 不是的,可以设置不一样。有stride_w和stride_h,后者表示的就是上下步长。
- 如果用stride,则表示stride_h=stride_w=stride。
3 keras简单介绍及实现简单三层神经网络
keras是深度学习的框架之一,非常好用,和Python语法非常相似,下面就来看下如何用
keras实现简单的三层神经网络。
3.1 导入相关的库
import keras
from keras.models import Sequential
from keras.layers import Dense
import numpy as np
3.2 读入数据
x = np.array([[0, 1, 0], [0, 0, 1], [1, 3, 2], [3, 2, 1]])
y = np.array([0, 0, 1, 1]).T
x
array([[0, 1, 0],
[0, 0, 1],
[1, 3, 2],
[3, 2, 1]])
y
array([0, 0, 1, 1])
3.3 搭建网络
结构
[外链图片转存失败(img-AIuPfvjt-1562771378758)(attachment:image.png)]
下面的例子就是上面的神经元个数可以依次换成:4,4,4,1
'''构建了一个三层的神经网络
- 第一个隐层有5个神经元 为relu激活函数
- 第二个隐层有4个 为relu激活函数
- 均为全连接层
- 然后输出 使用sigmoid激活函数
'''
simple_model = Sequential()
simple_model.add(Dense(5, input_shape=(x.shape[1],), activation='relu', name='layer1'))
simple_model.add(Dense(4, activation='relu', name='layer2'))
simple_model.add(Dense(1, activation='sigmoid', name='layer3'))
3.4 编译
- 使用sgd优化器
- 使用均方误差损失
simple_model.compile(optimizer='sgd', loss='mean_squared_error')
3.5 拟合模型
simple_model.fit(x, y, epochs=20000) # 训练20000次模型
3.6 绘制损失函数曲线图
import matplotlib.pyplot as plt
plt.plot(simple_model.history.history['loss']) # 训练集的acc
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()
3.7 预测
simple_model.predict(x[0:1]) # 预测第一个结果看和实际是否相符 相符!
array([[0.02791087]], dtype=float32)
4 keras实现CNN-Lenet实现
CNN演变历史
- 最早是由Lecun提出来的Lenet成为cnn的鼻祖
- 他的学生Alex提出了层数更深的Alexnet
- 2013年又提出了VGGnet,有16层和19层两种
- google提出了inception net在网络结构上实现了创新,提出了一种inception的机构
- facebook ai 实验室又提出了resnet,残差网络,实现了150层的网络结构可训练化
4.1 载入数据
import keras
from keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
print(x_train.shape, y_train.shape, x_test.shape, y_test.shape)
(60000, 28, 28) (60000,) (10000, 28, 28) (10000,)
x_train
array([[[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, 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, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]]], dtype=uint8)
y_train
array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)
(60000, 28, 28)解释:
- 60000表示图片的张数
- 28x28代表的是像素点,说白了也就是图片的大小
- 好像应该有一个表示RGB的?但是好像手写数字这个都是黑白的图片 所以就省略了?
训练集有6万张图片,测试集有1万张图片
4.2 数据转换
x_train = x_train.reshape(-1, 28, 28, 1)
x_test = x_test.reshape(-1, 28, 28, 1)
# 除以255是因为图片的最大像素就是255 进行归一化处理
x_train = x_train / 255.
x_test = x_test / 255.
y_train = keras.utils.to_categorical(y_train) # label做一个one-hot encoding处理
y_test = keras.utils.to_categorical(y_test)
x_train.shape # 要保证以这种形式输入到神经网络中
'''1表示图片: black/white 黑白!, 3表示图片: RGB,即彩色的'''
(60000, 28, 28, 1)
y_train # one-hot编码处理
array([[0., 0., 0., ..., 0., 0., 0.],
[1., 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., 1., 0.]], dtype=float32)
y_train.shape
(60000, 10)
4.3 搭建模型
- padding=‘same’ :边界会使用0填充,所以维数不变
- padding=‘valid’ :就不会填充了,所以维数在变化
from keras.layers import Conv2D, MaxPool2D, Dense, Flatten
from keras.models import Sequential
lenet = Sequential()
# 表示我们的网络将学习6个滤波器 每个滤波器的大小都是3×3,步长为1
lenet.add(Conv2D(6, kernel_size=3, strides=1, padding='same', input_shape=(28, 28, 1)))
# 2×2的最大池化层 步长为2
lenet.add(MaxPool2D(pool_size=2, strides=2))
# 表示我们的网络将学习16个滤波器 每个滤波器的大小都是5×5,步长为1
lenet.add(Conv2D(16, kernel_size=5, strides=1, padding='valid'))
# 2×2的最大池化层 步长为2
lenet.add(MaxPool2D(pool_size=2, strides=2))
# 展开
lenet.add(Flatten())
# 接下来相当于有两层full-connected网络
# 120个神经元 全连接网络
lenet.add(Dense(120))
# 84个神经元 全连接网络
lenet.add(Dense(84))
lenet.add(Dense(10, activation='softmax')) # 10个类别的softmax分类器
疑问:上述代码没有加激活函数为啥也ok呢?
4.4 编译
- 由于是分类,采用交叉熵作为损失函数
- 优化器为sgd
lenet.compile('sgd', loss='categorical_crossentropy', metrics=['accuracy'])
4.5 训练模型
50次迭代
lenet.fit(x_train, y_train, batch_size=64, epochs=50, validation_split=0.2) # 即训练集再拿出0.2作为验证集
# 一开始测测试集用来做最后的模型评估!
Train on 48000 samples, validate on 12000 samples
Epoch 1/50
48000/48000 [==============================] - 19s 390us/step - loss: 0.0660 - acc: 0.9804 - val_loss: 0.0602 - val_acc: 0.9808
Epoch 2/50
48000/48000 [==============================] - 20s 409us/step - loss: 0.0625 - acc: 0.9813 - val_loss: 0.0604 - val_acc: 0.9802
Epoch 3/50
48000/48000 [==============================] - 20s 416us/step - loss: 0.0591 - acc: 0.9823 - val_loss: 0.0621 - val_acc: 0.9803
Epoch 4/50
48000/48000 [==============================] - 21s 437us/step - loss: 0.0565 - acc: 0.9829 - val_loss: 0.0631 - val_acc: 0.9802
......
Epoch 45/50
48000/48000 [==============================] - 21s 441us/step - loss: 0.0164 - acc: 0.9954 - val_loss: 0.0616 - val_acc: 0.9834
Epoch 46/50
48000/48000 [==============================] - 14s 293us/step - loss: 0.0164 - acc: 0.9950 - val_loss: 0.0652 - val_acc: 0.9832
Epoch 47/50
48000/48000 [==============================] - 12s 259us/step - loss: 0.0162 - acc: 0.9952 - val_loss: 0.0640 - val_acc: 0.9833
Epoch 48/50
48000/48000 [==============================] - 13s 274us/step - loss: 0.0151 - acc: 0.9956 - val_loss: 0.0687 - val_acc: 0.9818
Epoch 49/50
48000/48000 [==============================] - 14s 302us/step - loss: 0.0148 - acc: 0.9957 - val_loss: 0.0654 - val_acc: 0.9832
Epoch 50/50
48000/48000 [==============================] - 13s 268us/step - loss: 0.0142 - acc: 0.9958 - val_loss: 0.0670 - val_acc: 0.9828
<keras.callbacks.History at 0x1289d4e10>
4.6 模型评估
score = lenet.evaluate(x_test, y_test)
print('test score:', score[0])
print('test accuracy:', score[1])
10000/10000 [==============================] - 2s 188us/step
test score: 0.05805485805499775
test accuracy: 0.9844
4.7 绘制损失函数曲线图
4.7.1 训练集VS验证集 loss
首先希望看到训练集和验证集上面损失函数同步变化的情况
import matplotlib.pyplot as plt
plt.plot(lenet.history.history['loss'])
plt.plot(lenet.history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'valid'], loc = 'lower left')
plt.show()
4.7.2 训练集VS验证集 accuracy
import matplotlib.pyplot as plt
plt.plot(lenet.history.history['acc'])
plt.plot(lenet.history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'valid'], loc = 'upper left')
plt.show()
5 重新搭建模型来run
更新的点:
- 使用dropout
- 使用adam
- 加入激活函数
from keras.layers import Conv2D, MaxPool2D, Dense, Flatten, Dropout
from keras.models import Sequential
from keras.layers.core import Activation
from keras.optimizers import SGD, Adam, RMSprop
'''搭建模型'''
lenet = Sequential()
# 表示我们的网络将学习6个滤波器 每个滤波器的大小都是3×3,步长为1
lenet.add(Conv2D(6, kernel_size=3, strides=1, padding='same', input_shape=(28, 28, 1)))
# 2×2的最大池化层 步长为2
lenet.add(Activation("relu"))
lenet.add(MaxPool2D(pool_size=2, strides=2))
# 表示我们的网络将学习16个滤波器 每个滤波器的大小都是5×5,步长为1
lenet.add(Conv2D(16, kernel_size=5, strides=1, padding='valid'))
lenet.add(Activation("relu"))
# 2×2的最大池化层 步长为2
lenet.add(MaxPool2D(pool_size=2, strides=2))
# 展开
lenet.add(Flatten())
# 接下来相当于有两层full-connected网络
# 120个神经元 全连接网络
lenet.add(Dense(120))
lenet.add(Activation("relu"))
lenet.add(Dropout(0.25))
# 84个神经元 全连接网络
lenet.add(Dense(84))
lenet.add(Activation("relu"))
lenet.add(Dense(10, activation='softmax')) # 10个类别的softmax分类器
'''编译'''
lenet.compile('Adam', loss='categorical_crossentropy', metrics=['accuracy'])
'''训练模型'''
lenet.fit(x_train, y_train, batch_size=64, epochs=50, validation_split=0.2) # 即训练集再拿出0.2作为验证集
# 一开始测测试集用来做最后的模型评估!
Train on 48000 samples, validate on 12000 samples
Epoch 1/50
48000/48000 [==============================] - 27s 569us/step - loss: 0.3039 - acc: 0.9047 - val_loss: 0.0934 - val_acc: 0.9722
Epoch 2/50
48000/48000 [==============================] - 26s 552us/step - loss: 0.0953 - acc: 0.9702 - val_loss: 0.0723 - val_acc: 0.9768
Epoch 3/50
48000/48000 [==============================] - 16s 333us/step - loss: 0.0718 - acc: 0.9777 - val_loss: 0.0540 - val_acc: 0.9824
Epoch 4/50
48000/48000 [==============================] - 15s 311us/step - loss: 0.0589 - acc: 0.9820 - val_loss: 0.0502 - val_acc: 0.9842
......
Epoch 46/50
48000/48000 [==============================] - 15s 316us/step - loss: 0.0065 - acc: 0.9978 - val_loss: 0.0551 - val_acc: 0.9903
Epoch 47/50
48000/48000 [==============================] - 15s 309us/step - loss: 0.0054 - acc: 0.9982 - val_loss: 0.0636 - val_acc: 0.9890
Epoch 48/50
48000/48000 [==============================] - 15s 310us/step - loss: 0.0067 - acc: 0.9978 - val_loss: 0.0609 - val_acc: 0.9890
Epoch 49/50
48000/48000 [==============================] - 15s 309us/step - loss: 0.0057 - acc: 0.9980 - val_loss: 0.0653 - val_acc: 0.9895
Epoch 50/50
48000/48000 [==============================] - 15s 309us/step - loss: 0.0059 - acc: 0.9980 - val_loss: 0.0603 - val_acc: 0.9872
<keras.callbacks.History at 0x15d843048>
'''评估'''
score = lenet.evaluate(x_test, y_test)
print('test score:', score[0])
print('test accuracy:', score[1])
10000/10000 [==============================] - 1s 148us/step
test score: 0.06008354149081474
test accuracy: 0.9885
import matplotlib.pyplot as plt
plt.plot(lenet.history.history['loss'])
plt.plot(lenet.history.history['val_loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train', 'valid'], loc = 'upper right')
plt.show()
import matplotlib.pyplot as plt
plt.plot(lenet.history.history['acc'])
plt.plot(lenet.history.history['val_acc'])
plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train', 'valid'], loc = 'lower right')
plt.show()
6 知识点补充:softmax和sigmoid
6.1 softmax
import numpy as np
np.set_printoptions(linewidth=100) # 设定print输出每行最大长度为100个字符防止自动换行
m = np.random.randn(5,5) * 10 + 1000
print('Origin:\n', m, '\n')
m_row_max = m.max(axis = 1)
print('Row max value:\n', m_row_max.reshape(5, 1), '\n')
m = m - m_row_max.reshape(5,1) # 每行元素减去本行的最大值(防止出现 INF)
m_exp = np.exp(m) # 求指数
m_exp_row_sum = m_exp.sum(axis=1).reshape(5,1)
softmax = m_exp / m_exp_row_sum
print('Softmax:\n', softmax, '\n') # 校验softmax数组是否正确的方法是查看每一行的和是否是1
print('Verification:\n', softmax.sum(axis=1).reshape(5, 1))
Origin:
[[1004.52758237 995.19160091 1014.47409723 994.09021834 1016.73181843]
[1026.35621051 995.54729275 995.78077014 1011.75856544 989.43945877]
[1004.76643052 989.01982099 1001.7131216 988.0155224 993.99620217]
[1015.41230066 980.8775022 1004.08270454 983.0149173 1004.06092301]
[ 992.30890863 1001.69547553 1004.56735877 1015.58083086 1006.31579688]]
Row max value:
[[1016.73181843]
[1026.35621051]
[1004.76643052]
[1015.41230066]
[1015.58083086]]
Softmax:
[[4.53487284e-06 3.99944642e-10 9.46850978e-02 1.32946073e-10 9.05310367e-01]
[9.99999543e-01 4.16731973e-14 5.26325929e-14 4.57428369e-07 9.27381078e-17]
[9.54905823e-01 1.38450623e-07 4.50739196e-02 5.07146673e-08 2.00682639e-05]
[9.99976235e-01 1.00396200e-15 1.20118141e-05 8.51108363e-15 1.17530073e-05]
[7.81778848e-11 9.32434044e-07 1.64763560e-05 9.99887924e-01 9.46669077e-05]]
Verification:
[[1.]
[1.]
[1.]
[1.]
[1.]]
所以哪一列取值最大就属于哪一类!
6.2 Sigmoid
import numpy as np
def logistic(x):
return 1/(1+np.exp(-x))
print(logistic(6.666666))
0.9987289828906512
所以sigmoid仅仅是二分类的问题,softmax可以解决多分类的问题!
6.3 总结
- 本质上来说,Softmax 属于离散概率分布而 Sigmoid 是非线性映射。
- 分类其实就是设定一个阈值,然后我们将想要分类的对象与这个阈值进行比较,根据比较结果来决定分类。
- Softmax 函数能够将一个K维实值向量归一化,所以它主要被用于多分类任务;
- Sigmoid 能够将一个实数归一化,因此它一般用于二分类任务。
- 特别地,当 Softmax 的维数 K=2 时,Softmax 会退化为 Sigmoid 函数。
7 参考
- 李宏毅老师课件:http://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2016/Lecture/CNN (v2).pdf
- CNN讲的特别好的:https://www.cnblogs.com/charlotte77/p/7759802.html
- 卷积过程讲的很好的:https://blog.csdn.net/v_JULY_v/article/details/51812459
- https://zhuanlan.zhihu.com/p/26647196
- softmax和sigmoid对比:https://www.lolimay.cn/2019/01/14/快速理解-Softmax-和-Sigmoid/
- 小白学CNN以及Keras的速成(3):https://zhuanlan.zhihu.com/p/26648813