深度可分离卷积(Depthwise separable convolution)是由DepthWise和PointWise两个部分组成,来提取特征,像google提出的mobilenet就是使用的深度可分离卷积。该类网络模型具有轻量级的特点,但是熊掌和鱼不可兼得,其轻量级的特点也是导致其精度降低的重要原因。
1. 常规的卷积操作
常规的卷积操作,一个卷积核的大小是由卷积核 宽 * 高 * 输入图像的通道数 三部分组成,那么我们将输入图像与该卷积核做卷积操作时,是将输入图像的各个通道与卷积核各个通道做卷积操作,然后三个通道结果进行相加求和,再通过非线性函数得到最终的feature map。
那么这里的参数数量为:
那么我们得到的常规卷积的计算量(偏置加法不考虑,因为加法计算量相对于乘法可忽略不计)为:
其计算量计算过程如下:
表示卷积核的大小,
表示输入图像的通道数,则单个卷积核对应单个输出像素点的计算量为
;
又因为表示输出feature map的大小,则对应的单通道的输出feature map的计算量为
那么N个通道的feature map对应的计算量为
2. 深度可分离卷积
深度可分离卷积由DepthWise深度卷积和PintWise点卷积两部分组成
2.1 DepthWise卷积
如上图所示,深度卷积的卷积核只负责一个通道,一个通道也只会被一个卷积核卷积;那么卷积核的数量取决于输入图像的通道数,不像常规的卷积核(通道数与输入图像的通道数要保持一致)。
那么DepthWise卷积的参数数量为(从代码summary中打印出的模型layer结构看无偏置bias):
那么DepthWise卷积的计算量(同样不考虑加法的计算量):
其计算量的计算过程如下:
这里的对于各通道的卷积核的计算量为,又因为输入图像的通道数为
,那么DepthWise卷积的计算量为
,这里我们也就得到了feature map的通道数为
,这里的
也将作为PointWise的输入图像的通道数。
Depthwise Convolution完成后的Feature map数量与输入层的通道数相同,无法扩展Feature map。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的feature信息。因此需要Pointwise Convolution来将这些Feature map进行组合生成新的Feature map。
2.2 PointWise卷积
点卷积与常规的卷积操作相同,只不过其卷积核的尺寸为 1*1*输入图像通道数。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个卷积核就有几个输出Feature map。
前面的博文中有说过1*1卷积的卷积有 增加非线性和改变feature map通道数两个作用
那么PointWise的参数量(因为这里的使用的只不过是1*1卷积,这里应该是包含了bias)为:
那么PointWise的计算量为
其计算量计算过程如下:
根据前面的DepthWise卷积我们得到,这里PointWise卷积的输入图像的通道数为,则对应的单个点卷积核的计算量为
,那么对于N通道的feature map其总的计算量大小为
2.3 深度可分离卷积总的参数和计算量
深度可分离卷积总的参数量为:
+
========>
对于常规卷积的计算量为:
我们可以将两者参数求出一个比例,我们待会可以在代码中打印的参数代入反过来验证这个比例是否正确:
深度可分离卷积总的计算量大小为:
+
========>
。
将该计算量除以常规卷积的计算量,得到深度可分离卷积的优化比例为:
由此我们可以得出,深度可分离卷积使得卷积神经网络计算量大大减少,从而使得卷积神经网络可以在移动设备等计算资源有限的设备上运行。
3. 深度可分离卷积在tensorflow2上的实现
深度可分离卷积的接口已经在tf.keras中被实现,封装成一个专门的接口,我们在模型定义部分直接调用即可。
-
import matplotlib
as mpl
#画图用的库
-
import matplotlib.pyplot
as plt
-
#下面这一句是为了可以在notebook中画图
-
%matplotlib inline
-
import numpy
as np
-
import sklearn
#机器学习算法库
-
import pandas
as pd
#处理数据的库
-
import os
-
import sys
-
import time
-
import tensorflow
as tf
-
-
from tensorflow
import keras
#使用tensorflow中的keras
-
#import keras #单纯的使用keras
-
-
print(tf.__version__)
-
print(sys.version_info)
-
for module
in mpl, np, sklearn, pd, tf, keras:
-
print(module.__name__, module.__version__)
-
-
-
-
2.0
.0
-
sys.version_info(major=
3, minor=
6, micro=
9, releaselevel=
'final', serial=
0)
-
matplotlib
3.1
.2
-
numpy
1.18
.0
-
sklearn
0.21
.3
-
pandas
0.25
.3
-
tensorflow
2.0
.0
-
tensorflow_core.keras
2.2
.4-tf
-
physical_devices = tf.config.experimental.list_physical_devices(
'GPU')
-
assert len(physical_devices) >
0,
"Not enough GPU hardware devices available"
-
tf.config.experimental.set_memory_growth(physical_devices[
0],
True)
-
fashion_mnist = keras.datasets.fashion_mnist
# 该数据集是黑白服装数据集
-
#拆分训练集和测试集
-
(x_train_all, y_train_all), (x_test, y_test) = fashion_mnist.load_data()
-
#将训练集拆分为训练集和验证集
-
#训练集共6万张图片,我们将前5000张作为验证集,后面所有的做训练集
-
x_valid, x_train = x_train_all[:
5000], x_train_all[
5000:]
-
y_valid, y_train = y_train_all[:
5000], y_train_all[
5000:]
-
-
print(x_train[
0].dtype)
-
print(x_train[
0])
# 是一个数据矩阵 28*28, 矩阵中的每一个数值都是uint8类型
-
print(y_train[
0])
#这里的y值均为数字编码,非向量,所以后面定义模型损失函数为 sparse_categorical_crossentropy
-
print(x_train.shape, y_train.shape)
-
print(x_valid.shape, y_valid.shape)
-
print(x_test.shape, y_test.shape)
-
-
-
-
-
uint8
-
[[
0
0
0
0
0
0
0
0
0
0
0
0
0
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
44
127
182
185
161
120
55
-
0
0
0
0
0
0
0
0
0
0]
-
[
0
0
0
0
0
0
0
0
0
42
198
251
255
251
249
247
255
252
-
214
100
0
0
0
0
0
0
0
0]
-
[
0
0
0
0
0
0
2
0
0
233
252
237
239
234
237
235
237
237
-
254
227
0
0
0
0
1
0
0
0]
-
[
0
0
0
0
0
2
0
0
16
210
225
215
175
217
216
193
196
226
-
221
209
50
0
0
2
0
0
0
0]
-
[
0
0
0
0
2
0
0
199
229
232
230
245
204
219
253
245
207
194
-
223
231
236
235
0
0
3
0
0
0]
-
[
0
0
0
0
1
0
137
235
204
209
201
209
234
190
234
218
215
238
-
239
204
189
224
154
0
0
0
0
0]
-
[
0
0
0
0
0
0
194
201
200
209
202
193
205
194
183
218
231
197
-
172
181
193
205
199
0
0
0
0
0]
-
[
0
0
0
0
0
3
212
203
188
189
196
198
198
201
196
217
179
167
-
183
217
197
202
219
30
0
0
0
0]
-
[
0
0
0
0
0
34
225
200
194
190
188
192
196
192
170
202
190
201
-
195
200
201
209
227
50
0
0
0
0]
-
[
0
0
0
0
0
68
225
210
211
198
192
196
204
196
181
212
197
195
-
192
206
220
210
229
93
0
0
0
0]
-
[
0
0
0
0
0
111
223
227
253
209
196
204
211
206
183
216
206
210
-
203
215
244
224
227
150
0
0
0
0]
-
[
0
0
0
0
0
139
225
224
255
202
206
212
209
211
190
213
202
207
-
206
222
255
230
220
190
0
0
0
0]
-
[
0
0
0
0
0
180
226
224
255
199
204
207
214
214
190
216
206
203
-
205
219
243
224
214
234
0
0
0
0]
-
[
0
0
0
0
0
225
223
228
254
209
206
208
213
210
191
215
207
204
-
208
211
249
226
214
255
38
0
0
0]
-
[
0
0
0
0
0
250
232
240
239
211
203
209
205
211
197
215
208
208
-
214
213
239
231
219
255
81
0
0
0]
-
[
0
0
0
0
0
248
236
247
240
203
200
208
206
214
193
213
212
208
-
212
211
243
242
225
254
66
0
0
0]
-
[
0
0
0
0
0
247
230
252
226
199
211
202
211
213
182
213
212
206
-
202
219
207
247
222
237
104
0
0
0]
-
[
0
0
0
0
10
244
219
250
205
199
209
202
209
211
189
214
206
210
-
200
212
154
240
208
219
140
0
0
0]
-
[
0
0
0
0
21
255
222
238
184
210
192
206
209
210
189
213
211
209
-
192
228
155
226
238
241
166
0
0
0]
-
[
0
0
0
0
37
245
226
241
150
197
189
204
209
210
183
213
213
201
-
184
215
146
216
236
225
154
0
0
0]
-
[
0
0
0
0
58
239
227
255
158
193
195
204
209
213
180
207
217
199
-
194
211
158
219
236
216
151
0
0
0]
-
[
0
0
0
0
68
233
226
243
139
200
193
205
210
208
180
205
212
203
-
196
216
157
179
255
216
155
0
0
0]
-
[
0
0
0
0
81
225
224
211
138
219
185
201
213
207
197
226
212
200
-
190
215
183
90
255
211
147
0
0
0]
-
[
0
0
0
0
91
210
230
158
114
205
187
208
209
206
193
210
211
204
-
195
204
181
23
255
213
158
0
0
0]
-
[
0
0
0
0
87
205
232
109
164
255
214
224
222
210
197
214
225
222
-
211
220
217
0
234
216
169
0
0
0]
-
[
0
0
0
0
92
213
232
146
5
134
151
162
170
183
182
164
166
178
-
162
156
98
0
240
225
210
0
0
0]
-
[
0
0
0
0
43
164
206
141
0
0
0
0
0
0
0
0
0
0
-
0
0
0
0
127
125
76
0
0
0]]
-
4
-
(
55000,
28,
28) (
55000,)
-
(
5000,
28,
28) (
5000,)
-
(
10000,
28,
28) (
10000,)
-
#在图像分类领域我们提升准确率的手段 归一化:
-
# 1.对训练数据进行归一化 2. 批归一化
-
-
# x = (x - u)/std u为均值,std为方差
-
from sklearn.preprocessing
import StandardScaler
#使用sklearn中的StandardScaler实现训练数据归一化
-
-
scaler = StandardScaler()
-
-
#fit_transform:得到方差、均值、最大最小值然后数据进行归一化操作
-
#https://blog.csdn.net/youhuakongzhi/article/details/90519801
-
#x_train:先转为float32用于做除法,x_train本身为三维矩阵[None,28,28],因为fit_transform要求二维数据所以需要转换为[None, 784],再转回四维矩阵
-
x_train_scaled = scaler.fit_transform(x_train.astype(np.float32).reshape(
-1,
1)).reshape(
-1,
28,
28,
1)
-
#是因为在trainData的时候,已经使用fit()得到了整体的指标(均值,方差等)并被保存起来了后面验证集测试集可以使用,所以在测试集上直接transform(),使用之前的指标,
-
#如果在测试集上再进行fit(),由于两次的数据不一样,导致得到不同的指标,会使预测发生偏差,因为模型是针对之前的数据fit()出来
-
#的标准来训练的,而现在的数据是新的标准,会导致预测的不准确
-
x_valid_scaled = scaler.transform(x_valid.astype(np.float32).reshape(
-1,
1)).reshape(
-1,
28,
28,
1)
-
x_test_scaled = scaler.transform(x_test.astype(np.float32).reshape(
-1,
1)).reshape(
-1,
28,
28,
1)
-
#reshape(-1,1)表示(任意行,1列),这里个人认为设置里面什么参数影响不大,只要是转换为二维即可,反正最终要转换为三/四 维使用
-
#tf.keras.models.Sequential()
-
-
model = keras.models.Sequential()
-
-
'''
-
#使用深度卷积网络实现
-
-
model.add(keras.layers.Flatten(input_shape=[28,28]))
-
for _ in range(20):
-
model.add(keras.layers.Dense(100,activation="selu"))# 激活函数selu自带数据归一化功能,在一定程度上也能缓解梯度消失问题
-
-
'''
-
#使用卷积神经网络实现
-
#激活函数这里使用了自带批归一化的selu函数来代替使用relu激活函数
-
-
#SeparableConv2D 深度可分离卷积的接口
-
model.add(keras.layers.Conv2D(filters=
32,kernel_size=
3,padding=
'same',activation=
"selu",input_shape=(
28,
28,
1)))
-
model.add(keras.layers.SeparableConv2D(filters=
32,kernel_size=
3,padding=
'same',activation=
"selu"))
-
model.add(keras.layers.MaxPool2D(pool_size=
2))
-
-
model.add(keras.layers.SeparableConv2D(filters=
64,kernel_size=
3,padding=
'same',activation=
"selu"))
-
model.add(keras.layers.SeparableConv2D(filters=
64,kernel_size=
3,padding=
'same',activation=
"selu"))
-
model.add(keras.layers.MaxPool2D(pool_size=
2))
-
-
model.add(keras.layers.SeparableConv2D(filters=
128,kernel_size=
3,padding=
'same',activation=
"selu"))
-
model.add(keras.layers.SeparableConv2D(filters=
128,kernel_size=
3,padding=
'same',activation=
"selu"))
-
model.add(keras.layers.MaxPool2D(pool_size=
2))
-
-
model.add(keras.layers.Flatten())
-
model.add(keras.layers.Dense(
128, activation=
"selu"))
-
-
-
#softmax层输出
-
model.add(keras.layers.Dense(
10,activation=
"softmax"))
-
-
model.compile(loss=
"sparse_categorical_crossentropy",
-
optimizer=
"adam",
#optimizer="sgd", 优化算法一般来说我们无脑用adam即可
-
metrics=[
"accuracy"])
-
-
#查看上面建立的模型架构信息
-
model.summary()
-
-
-
-
Model:
"sequential"
-
_________________________________________________________________
-
Layer (type) Output Shape Param
#
-
=================================================================
-
conv2d (Conv2D) (
None,
28,
28,
32)
320==(
3*
3+
1)*
32
-
_________________________________________________________________
-
separable_conv2d (SeparableC (
None,
28,
28,
32)
1344==
3*
3*
32+(
1*
1*
32+
1)*
32
-
_________________________________________________________________
-
max_pooling2d (MaxPooling2D) (
None,
14,
14,
32)
0
-
_________________________________________________________________
-
separable_conv2d_1 (Separabl (
None,
14,
14,
64)
2400==
3*
3*
32+(
1*
1*
32+
1)*
64
-
_________________________________________________________________
-
separable_conv2d_2 (Separabl (
None,
14,
14,
64)
4736==
3*
3*
64+(
1*
1*
64+
1)*
64
-
_________________________________________________________________
-
max_pooling2d_1 (MaxPooling2 (
None,
7,
7,
64)
0
-
_________________________________________________________________
-
separable_conv2d_3 (Separabl (
None,
7,
7,
128)
8896==
3*
3*
64+(
1*
1*
64+
1)*
128
-
_________________________________________________________________
-
separable_conv2d_4 (Separabl (
None,
7,
7,
128)
17664==
3*
3*
128+(
1*
1*
128+
1)*
128
-
_________________________________________________________________
-
max_pooling2d_2 (MaxPooling2 (
None,
3,
3,
128)
0
-
_________________________________________________________________
-
flatten (Flatten) (
None,
1152)
0
-
_________________________________________________________________
-
dense (Dense) (
None,
128)
147584==
1152*
128+
128
-
_________________________________________________________________
-
dense_1 (Dense) (
None,
10)
1290==
128*
10+
10
-
=================================================================
-
Total params:
184,
234
-
Trainable params:
184,
234
-
Non-trainable params:
0
从模型的结构和打印的参数我们可以看到,这里的卷积层的参数并不多,主要参数都是在全连接层
-
#Tensorflow中的callback用于模型训练过程中的一些监听操作,常用的callback类型如下三类:
-
#Tensorboard 可视化Tensorboard
-
#earlystopping 当loss函数不能再优化时停止训练,这样可以截取到最优的模型参数
-
#ModelCheckpoint 每次epoch之后就保存模型
-
-
#当前目录下新建一个callbacks文件夹并在里面创建一个h5模型文件
-
import shutil
-
logdir=
'./callbacks_separable_cnn'
-
-
if os.path.exists(logdir):
-
shutil.rmtree(logdir)
#先强制删除该文件夹,后面再新建
-
os.mkdir(logdir)
-
-
output_model_file=os.path.join(logdir,
"fashion_mnist_model.h5")
#在logdir中创建一个模型文件.h5
-
-
#定义一个callbacks数组
-
callbacks = [
-
keras.callbacks.TensorBoard(logdir),
-
keras.callbacks.ModelCheckpoint(output_model_file,save_best_only=
True),
#这里第二个参数表示仅保存最好的那个模型
-
keras.callbacks.EarlyStopping(patience=
5,min_delta=
1e-3)
-
]
-
-
'''
-
#在未做数据集归一化时这里直接将x_train三维矩阵转换为四维
-
x_train = x_train.reshape(-1,28,28,1)
-
x_valid = x_valid.reshape(-1,28,28,1)
-
x_test = x_test.reshape(-1,28,28,1)
-
'''
-
-
#fit用于训练
-
history=model.fit(x_train_scaled, y_train, epochs=
10,
#epochs用于遍历训练集次数
-
validation_data=(x_valid_scaled,y_valid),
#加入验证集,每隔一段时间就对验证集进行验证
-
callbacks=callbacks)
-
'''
-
history=model.fit(x_train, y_train, epochs=10, #epochs用于遍历训练集次数
-
validation_data=(x_valid,y_valid),#加入验证集,每隔一段时间就对验证集进行验证
-
callbacks=callbacks)
-
'''
-
-
-
-
Train on
55000 samples, validate on
5000 samples
-
Epoch
1/
10
-
55000/
55000 [==============================] -
16s
290us/sample - loss:
0.5224 - accuracy:
0.8074 - val_loss:
0.3760 - val_accuracy:
0.8624
-
Epoch
2/
10
-
55000/
55000 [==============================] -
12s
224us/sample - loss:
0.3129 - accuracy:
0.8863 - val_loss:
0.2872 - val_accuracy:
0.8974
-
Epoch
3/
10
-
。。。
-
Epoch
10/
10
-
55000/
55000 [==============================] -
14s
262us/sample - loss:
0.1238 - accuracy:
0.9537 - val_loss:
0.2709 - val_accuracy:
0.9100
-
#将上面history中的数据指标用一张图来表示
-
def plot_learning_curves(history):
-
pd.DataFrame(history.history).plot(figsize=(
8,
5))
#设置图的大小
-
plt.grid(
True)
#显示网格
-
plt.gca().set_ylim(
0,
1)
#设置y轴范围
-
plt.show()
-
plot_learning_curves(history)
-
#测试集上进行测试评估一下
-
model.evaluate(x_test_scaled,y_test)
-
-
-
-
10000/
1 [================。。。=================================] -
1s
147us/sample - loss:
0.3051 - accuracy:
0.9109
-
[
0.2942018260926008,
0.9109]
转载:https://blog.csdn.net/zz531987464/article/details/105803764