今天练了很多代码,绝大部分都是跟着书上练的,没有写注释,这些代码就不贴出来了。把学习到的一些知识概括讲讲,只在必要出给出代码。
在最后面记录了一下深度学习的本质,个人觉得这部分很重要,所以在理解书上写的以后又重新整理了一遍思路。深度学习的本质是泛化、泛化的本质则用流形假说来解释。
这是TensorFlow中用来自动计算梯度的API,只要创建一个GradientTape作用域,对一个或多个张量继续计算,就可以检索计算结果相对于输入的梯度,即GradientTape可以实现自动计算梯度。除了计算一阶梯度,还有计算二阶梯度,非常方便。(标量属于0阶张量,也可以计算梯度。)
计算一阶梯度的方法:
input_var=tf.Variable(initial_value=3.) # 一个可修改的张量
with tf.GradientTape() as tape:
result=tf.square(input_var) # 求平方运算,result=input_var²
# 由于上面的运算是在tape这个GradientTape类型的域中完成的,所以可以调用tape中的函数求出梯度。
gradient=tape.gradient(result,input_var) # 求result相对于input_var的梯度,可以看做result=f(input_var),result是因变量,input_var是自变量
二阶梯度也能计算,以自由落体的位移-时间关系为例(x=g*t²/2):
time=tf.Variable(0.) # 0阶张量,即标量
with tf.GradientTape() as outer_tape: # 外梯度
with tf.GradientTape() as inner_tape: # 内梯度
#两层梯度的嵌套,就可以求出二阶梯度
position=4.9 * time **2 # 位移时间关系
speed=inner_tape.gradient(position,time) # 速度等于位移对之间求导,由于位移和时间是在内梯度inner_tape这个域内,所以使用的是inner_tape
acceleration=outer_tape(speed,time) # 加速度等于速度对时间求导
# 答案是4.9*2=9.8
二分类的时候使用sigmoid或者softmax作为输出层的激活函数在数学上的形式是相等的,但是要注意和它们搭配的损失函数是不一样的:
其中binary_crossentropy与sigmoid联合使用
而categorical_crossentropy与softmax联合使用
keras中有categorical_crossentropy和sparse_categorical_crossentropy两种多元交叉熵损失函数,它们在数学上的形式也是一样的,前者用于处理标签为独热编码的情形;后者用于处理标签为整数的情形。如果类别数量很多,可以使用后者节省内存。只需要损失函数和标签的输入形式相同即可,模型的其他任何部分都不需要改变,包括模型的输出。(比如10输出模型,标签为一个0-9的整数,这是没问题的)
当数据集很小的时候,验证集出现偶然误差的概率很大,不同的随机种子选出来的验证集对评估效果的影响很大,因而我们不能客观的评估模型的性能。而K折交叉验证可以缓解这个问题。下面是手动处理进行k这交叉验证的例子,该模型完成的任务是预测波士顿房价:
import tensorflow as tf
import numpy as np
from keras.layers import *
import keras
from tensorflow.keras.datasets import boston_housing
(train_data,train_targets),(test_data,test_targets)=boston_housing.load_data()
mean=train_data.mean(axis=0) # 将train给标准化
std=train_data.std(axis=0)
train_data-=mean
train_data/=std
test_data-=mean # test标准化使用的是train的mean和std
test_data/=std
def build_model():
model=keras.Sequential([
Dense(64,'relu'),
Dense(64,'relu'),
Dense(1)
])
model.compile(loss='mse',metrics=['mae'])
return model
k=4 # k折
num_val_samples=len(train_data)//k # 验证集的大小
num_epochs=130 # 训练的轮数
all_scores=[] # 所有的得分
test_scores=[]
for i in range(k): # 对于每一折
print(f'Processing fold #{i}')
val_data=train_data[i*num_val_samples:(i+1)*num_val_samples] # 准备验证集数据
val_targets=train_targets[i*num_val_samples:(i+1)*num_val_samples]
partial_train_data=np.concatenate(
[train_data[:i*num_val_samples],
train_data[(i+1)*num_val_samples:]],
axis=0
) # 剩余的数据为训练集数据
partial_train_targets=np.concatenate(
[train_targets[:i*num_val_samples],
train_targets[(i+1)*num_val_samples:]],
axis=0
)
model=build_model() # 获取已编译模型
model.fit(partial_train_data,partial_train_targets,epochs=num_epochs,batch_size=16,verbose=0)
val_mse,val_mae=model.evaluate(val_data,val_targets,verbose=0)
all_scores.append(val_mae)
test_mse,test_mae=model.evaluate(test_data, test_targets,verbose=0)
test_scores.append(test_mae)
print(all_scores)
print(np.mean(all_scores))
print(test_scores)
print(np.mean(test_scores))
模型在测试集上的效果比在验证集上的效果差一些:
# 验证集上的mae
[2.0420572757720947, 2.4130470752716064, 2.4782347679138184, 2.3443572521209717]
2.319424092769623
# 测试集上的mae
[2.723472833633423, 2.7305524349212646, 2.6380789279937744, 2.9562792778015137]
2.762095868587494
推测可能是因为验证集标准化使用的均值和方差是和训练集一起计算出来的,而测试集标准化使用的均值和方差是验证集和训练集的均值和方差,因此存在误差,导致预测效果下降。
将k设置为10以后,由于用于训练的样本数量增多了,所以误差减小了,但测试集误差仍然大于训练集:
[1.4529666900634766, 1.361604928970337, 2.390073776245117, 2.459419012069702, 2.0816545486450195, 2.328268051147461, 2.4580376148223877, 2.953895092010498, 2.5058557987213135, 2.2366909980773926]
2.2228466510772704
[2.457235097885132, 2.5080862045288086, 2.7396130561828613, 2.490891695022583, 2.6309916973114014, 2.4788293838500977, 2.494255542755127, 2.5442817211151123, 2.5079901218414307, 2.5117759704589844]
2.536395049095154
MNIST数据集中,使用Dense层来拟合,选取3个实验组。
原始数据:60000*28*28的图像,扁平化以后为60000*784
加白噪声的数据:在每个图片后面增加784维大小为0-1的白噪声,变成60000*1568
加全零通道的数据:在每个图片后面增强784为的全0通道,变成60000*1568
代码:
from tensorflow.keras.datasets import mnist
import numpy as np
import tensorflow.keras as keras
from keras.layers import *
import matplotlib.pyplot as plt
(train_images,train_labels),_=mnist.load_data()
train_images=train_images.reshape((-1,28*28)).astype('float32')/255.0
train_images_with_noise=np.concatenate([train_images,np.random.random((len(train_images),784))],axis=1)
train_images_without_noise=np.concatenate([train_images,np.zeros((len(train_images),784))],axis=1)
def get_model():
model=keras.Sequential([
Dense(512,'relu'),
Dense(10,'softmax')
])
model.compile(loss=keras.losses.sparse_categorical_crossentropy,metrics=['accuracy'])
return model
model=get_model()
history_noise=model.fit(train_images_with_noise,train_labels,batch_size=128,epochs=20,validation_split=0.2,verbose=2)
model=get_model()
history_zeros=model.fit(train_images_without_noise,train_labels,batch_size=128,epochs=20,validation_split=0.2,verbose=2)
model=get_model()
history=model.fit(train_images,train_labels,batch_size=128,epochs=20,validation_split=0.2,verbose=2)
val_noise=history_noise.history['val_accuracy']
val_zeros=history_zeros.history['val_accuracy']
val=history.history['val_accuracy']
epochs=range(1,21)
plt.plot(epochs,val_noise,label='noise')
plt.plot(epochs,val_zeros,label='zeros')
plt.plot(epochs,val,label='none')
plt.xlabel('epochs')
plt.ylabel('acc')
plt.legend() # 添加角标
plt.savefig('噪声影响.jpg',dpi=1000)
plt.show()
实验结果:
可见加了784个全0通道的数据和原始数据的训练效果基本一致,而加了噪声的数据训练效果要差很多。说明噪声会对神经网络的预测效果造成影响,应当避免噪声。
这是个非常有趣的话题,很少有书本讲到这个深度,keras之父终归是keras之父,将深度学习起作用的本质讲得比较清楚了。
首先需要理解的一点是:只要模型有足够的表示能力(我觉得也可以理解为参数足够多、足够复杂等)深度学习模型就能拟合任何数据。例如:随机生成一些噪声和标签,只要模型的参数足够多,模型也可以将这些数据拟合。当然模型只会记住特定的输入和标签之间的映射关系,就和Python字典一样。但这样的模型是没有用的,深度学习模型能起作用的本质是模型的泛化能力,模型不是简单的记住了输入和标签之间的关系,还能够对新输入起作用。
但是深度学习泛化的本质和深度学习模型本身关系不大,而是与现实世界中信息结构密切相关!
现实世界的信息只占整个信息域的一小部分,比如说MNIST中的每张图片是一个0~255整数的28*28大小的数组,可取的值为256的784次幂,比宇宙中所有的原子数还要大得多!但是在这些信息域中只有少数是有效的MNIST样本。也就是说,在所有可能的28×28的unit8数组中,真实的样本只占了很小的子空间。这个子空间不是随机分布在父空间中,而是高度结构化的。这被称为:手写数字在28×28 unit8数字的可能性空间中构成了一个流形。
流形指的是某个父空间中的低维子空间,它局部近似一个线性空间(欧几里得空间)。比如平面上的光滑曲线就是二维空间中的一维流形;三维空间中的光滑表面是一个二维流形,以此类推。
流形假说认为,所有自然数据都位于高维空间中的一个低维流形中,这个高维空间是数据编码空间。这是关于宇宙信息结构的一个非常有力的表述,这也是深度学习有效的原因。
理解了流形的概念后,再理解流形中的插值:将流形中的点与其相近的点联系起来,就可以理解前所未见的点,可以仅用空间的一个样本来理解空间的整体。在一个流形中,总是可以在两个输入之间进行插值,也就是说,通过一条连续路径将一个输入变形成另外一个输入,这条路径上的所有点都位于流形中。
深度学习为什么有效:深度学习的本质是一条高维曲线——一条光滑连续的曲线,因为它是可微的。通过梯度下降,这条曲线平滑、渐进的对数据点进行拟合。就其本质而言,深度学习就是取一条大而复杂的曲线并逐步调节其参数,知道曲线拟合了一些训练数据点。这条曲线包含足够多的参数,可以拟合任何数据。事实上,如果对模型训练足够长的时间,那么它最终会仅仅记住训练数据,根本没有泛化能力。然而,你要拟合的并不是稀疏分布于底层空间的孤立点组成的。你的数据在输入空间中形成一个高度结构化的低维流形,这就是流形假说。随着梯度逐渐下降,模型曲线会平滑地拟合这些数据。因此,在训练过程中会有一个中间点,此时模型大致接近数据的自然流形。
上面两段话可能有些难懂,总结一下就是:现实空间中的输入是高维空间下的一个流形,因为流形近似一个欧几里得空间,所以可以用一条高维曲线(也就是深度学习模型)将这个流形中的数据点分开,由于现实世界中的信息结构,流形中相邻的点基本上是属于同一个类别的,所以当输入新的数据时,模型也能够大致完成分类的工作。
根据流形假说,要改进深度学习模型有如下方法:
条高维曲线(也就是深度学习模型)将这个流形中的数据点分开,由于现实世界中的信息结构,流形中相邻的点基本上是属于同一个类别的,所以当输入新的数据时,模型也能够大致完成分类的工作。
根据流形假说,要改进深度学习模型有如下方法: