引子
超参数的定义是在开始学习过程之前设置值的参数,而不是通过训练得到的参数,包括模型结构,各种激活函数,学习率等等。
如果模型的超参数合适, 即使用简单的模型,网络也能够学到有趣的东西.
这个实验的初心是什么? 就是用尽量简洁的模型, 学习到尽量多的信息, 尤其可以视觉化呈现学习到的东西.
cnn模型可以大幅简化
看一个tensorflow官方的cnn模型 : 卷积神经网络(Convolutional Neural Network, CNN) | TensorFlow Core
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 30, 30, 32) 896 max_pooling2d (MaxPooling2D) (None, 15, 15, 32) 0 conv2d_1 (Conv2D) (None, 13, 13, 64) 18496 max_pooling2d_1 (MaxPooling2D) (None, 6, 6, 64) 0 conv2d_2 (Conv2D) (None, 4, 4, 64) 36928 flatten (None, 1024) 0 dense (Dense) (None, 64) 65600 dense_1 (Dense) (None, 10) 650
这个模型有3个卷积层和2个全连接层, 用来学习mnist 有些大材小用. 如果简化去掉一些层,只剩下一个卷积层和一个展平层 (Flatten)加一个全连接层, 甚至仅仅用一个全连接层都能学到不少东西,达到95%以上的验证集正确率.
对cnn模型进行一些简化
1, 下图是超大卷积核的模型结构:
一个卷积层+ 一个展平层 (Flatten)+ 一个全连接层
用100x 100的卷积核去卷28x 28的照片, 并且padding设置为same,卷积核在照片上滑动.
训练结果, 视觉化展示唯一的卷积核:
2, 与图片等大的16个 卷积核(28x 28 ). 模型结构:
一个卷积层+ 一个展平层 (Flatten)+ 一个全连接层
一般来说卷积核要小于图片, 卷积核在图片上滑动来提取信息,相当于拍摄了多张照片. 如果采用等大卷积核,并且padding设置为valid ,这个超大卷积核就不再滑动, 相当于仅仅拍摄一张照片.
下图是在mnist 数据集上训练结果, 视觉化展示卷积核, 在第一个epoch 就学到了类似数字的图形, 但是有些混乱, 是数字叠加的影像.
上图最右面, 可以看到从mnist中学习到了东西。但是叠加的影像,还不够清晰。
当时猜测原因有两个:
第一个是要将 sparse categorical cross entropy转换到categorical cross entropy,后来证明这个原因猜错了.
第二个是要去除展平层 (Flatten)和全连接层(FC), 每类数据不要交互, 这个猜对了. 果然学到了!!
从上图可以看到,一些数字形状比如"0,1,2,5,7 "很好学习, 但是经过反复实验其它数字学不到.
然后猜测应该更换损失函数, 从relu 换到sigmoid, 成功 !!
在fashion_mnist数据集上也是成功的!
(支线任务,用小一点的卷积核,可以减少模型的参数)
下图 是23x 23 卷积核,将有少量的滑动,比不滑动效果更好:
下图是 25x 25 卷积核, 有更少量的滑动,笔画似乎更细:
(支线任务,膨胀卷积核,可以获得更少的参数)
如果看不清就咪咪眼
突然想起来, 这样用卷积学到的分类别图像, 有点像英国科学家做的人种平均脸图像
# 更换模型的输入标签从spars cross entropy 到 cross entropy , # 更换最后一层,不要全连接
# 卷积 然后 展平, 再多对多, 完全打乱了信息, 不能够得到预先设想的 10个数字
# todo 要是希望用10个超大卷积核对应10个数字, 算法结构中应该只有少量的 平移卷积, 没有多对多的全连接层
# todo 测试 数组转图片,然后画图的输入范围,是否能自动map数值的上下限,是否对负数自动变为正数
# todo 用2层卷积核,拟合数据, 然后在训练完成并训练结果较好的时候, 将2层卷积核相乘,看看每一种可能性 ,看看能不能看到 1-9-0 十个数字
# todo 加入部件 输出gif 或者mp4
# todo 加入部件,看卷积核与fc层连接的权重
# todo 卷积核的初始化很重要, 看到20x20的卷积核虽然测试及表现良好,但是学习的花纹有些看不懂,也许学习的很正确,但是人类看不懂. 希望构造部件学习到类似条纹的卷积核 另外,看到20个epochs 后,花纹基本不变, 不过从初始化到第一步看不到. todo 加入部件,看最初的初始化.
# todo 如何解包, 而不是将一堆小文件存储在文件夹里 --切片??
# 终于提取到卷积核的数据, 并且用图片形式表示出来(每个epoch 只画一个卷积核 ) , 并且画出全部卷积核
# https://blog.csdn.net/gaotihong/article/details/80983937 ---Python-matplotlib画图
# 控制 logs: [ p.terminate() for p in processes if p.is_alive()]
# 在model.Sequential. 里面 找卷积核数值
# xxxx 卷积核的位置从 print(model.summary()) 可以获知, 每次运行 卷积核的序列号加2
# 可以用命名的方式,指定每层的名称。 from https://www.tensorflow.org/guide/keras/sequential_model
# (因为每个层都是一个类,所以返回的层本质上是一个类)
#导入模块
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras.callbacks import LambdaCallback
from PIL import Image
################################################################
# 没有gpu的计算机不需要本段
# 这一段的作用是在用gpu计算时,debug gpu内存报错 UnknownError: Failed to get convolution algorithm.
# This is probably because cuDNN failed to initialize, so try looking to see if a warning log message was printed above.
# [[node sequential/cnn_layer/Conv2D (defined at tmp/ipykernel_13733/2736373417.py:104) ]] [Op:__inference_distributed_function_799]
from tensorflow.compat.v1.keras.backend import set_session
config=tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
sess=tf.compat.v1.Session(config=config)
set_session(sess)
tf.keras.backend.clear_session() #清理session
###############################################################
#导入数据集
mnist = tf.keras.datasets.mnist
#mnist = tf.keras.datasets.fashion_mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
#数据预处理
#Reshape
x_train4D = x_train.reshape(x_train.shape[0],28,28,1).astype('float32')
x_test4D = x_test.reshape(x_test.shape[0],28,28,1).astype('float32')
#像素标准化
x_train, x_test = x_train4D / 255.0, x_test4D / 255.0
#模型搭建
model = tf.keras.models.Sequential([
# tf.keras.layers.Conv2D(filters=16 , kernel_size=(20,20), padding='VALID',input_shape=(28,28,1), activation='relu',name="cnn_layer"), # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<卷积核大小 数量
#tf.keras.layers.Conv2D(filters=16 , kernel_size=(28,28), padding='VALID',input_shape=(28,28,1), activation='sigmoid',name="cnn_layer"), # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<卷积核大小 数量
tf.keras.layers.Conv2D(filters=16 , kernel_size=(25,25), padding='VALID',input_shape=(28,28,1), dilation_rate=1, activation='sigmoid',name="cnn_layer"), # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<卷积核大小 数量
# VALID valid
tf.keras.layers.Flatten(), # 卷积层输出二维数据,而全连接层接收一维数据,faltten降维数据
#tf.keras.layers.Dense(10,activation='softmax')
#tf.keras.layers.Dense(10,activation='sigmoid') # relu不行, 学不到东西. sigmoid , softmax 能够学到东西
])
#打印模型
print(model.summary()) # print 模型
#训练配置
model.compile(loss='sparse_categorical_crossentropy',optimizer='adam', metrics=['accuracy'])
#开始训练
class plot_kernel(keras.callbacks.Callback): # 定义class
"""
def on_train_begin(self, logs={}):
self.losses = []
"""
def on_epoch_end(self, batch, logs={}):
#img = Image.fromarray(model.get_layer(name="cnn_layer").kernel.numpy ()[:, :, :, :])
plt.figure(figsize=(6, 6)) #设置窗口大小
plt.suptitle('cnn_layer') # 图片名称
for i in range(0,16 ): #<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 绘图 ,设定卷积核个数
img = Image.fromarray(model.get_layer(name="cnn_layer").kernel.numpy ()[:,:,0,i] *500+200 )
plt.subplot(4,4, (i+1) )
plt.title(' ')
plt.imshow(img)
#plt.axis('off') # plt.imshow(gray,cmap='gray'), plt.axis('off') #这里显示灰度图要加cmap
plt.xticks([]) # 去除x轴刻度标记 ,可以用 plt.xticks(fontsize=0) 去除x轴刻度数字
plt.yticks([]) # plt.yticks(fontsize=0)
# plt.axes.get_xaxis().set_visible(False) #??
# plt..axes.get_yaxis().set_visible(False) #??
plt.show()
pltKernel = plot_kernel() # class 实例化
model.fit(
x=x_train, y=y_train,
callbacks=[pltKernel],
validation_split=0.2,
epochs=50 , batch_size=300, verbose=2) # verbose = 2 为每个epoch输出一行记录 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<,<<< epochs
#print("model.get_layer(index=0).output_shape============", model.get_layer(index=0).output_shape) # 这个是第一层(cnn)的输出图片.维度
print("model.cnn_layer.kernel=============", model.get_layer(name="cnn_layer").kernel)
print("model.cnn_layer.kernel.shape=========", model.get_layer(name="cnn_layer").kernel.shape) # 这个是第一层 卷积核图片.维度
# callbacks