1 深度学习及Tensorflow框架简介
为什么要有框架,深度学习框架目的是让更多的人使用同一个规范来实现一些深度学习的方法,这样便于其他人阅读和学习。
深度学习框架目前有很多,我们要清楚,框架是让我们快速实现问题的,所以不要觉得这个框架直接用API很简单就觉得比较low。
目前比较有名气的使以下深度框架:
(1)Caffe2:早期出的一个框架,比较底层,难使用
(3)Paddlepaddle:百度出的:很好用,但是没有占领市场
(4)mxnet:也是中国人出的,很好用,但是也是没有占领市场。
(5)pytorch:学术界主流的框架
(6)Tensorflow:工业街主流的框架
(7)keras:keras现在已经和Tensorflow合并,使用的时候相当于封装了Tensorflow更高级的API,使用起来非常简单。
深度学习的每一个应用的使用,其实很简单,简单来说,都是要经历三个过程,第一个是定义网络,第二个是定义损失函数,最后优化网络。
本文章主要使用的是Tensorflow来对各种算法进行实现,因为学习了它的相关API之后,对于其他的框架比如pytorch都可以非常轻松快速的上手,区别不是很大。
Tensorflow时Google大脑小组开发的,采用数据流图,支持多中平台-GPU、CPU、移动设备;有以下特性:
(1)高度的灵活性
(2)自动求微分(基本每个深度框架必须解决的问题,会方便解决很多问题)
(3)真正的可移植性
(4)多语言支持
(5)产品和科研结合
(6)性能最优化
Tensorflow的版本发展:
(1)2015.11 Tensorflow宣布开源
(2)2015.12 支持GPU,支持python3.3
(3)2016.4 支持分布式的Tensorflow
(4)2016.11 支持Windows
(5)2017.2 性能改进,API稳定性
(6)2017.4 Keras集成
(7)2017.8 更高级的API发行,预估器,支持TPU
(8)2017.11 Eager execution模式,Tensorflow Lite
(9)2018.3 推出TF Hub,Tensorflow.js,Tensorflow Extended
(10)2018.5 Cloud TPU模块与管道
(11)2018.6 新的分步式策略API发行,概率编程工具
(12)2018.8 Cloud Big Table集成
(13)2018.10 可重用性的API改进
(14)2019.3 Tensorflow2.0 Alpha
(15)2019.10 Tensorflow2.0正式版本
(16)2020.1 发布Tensorflow2.1 不在区分GPU和CPU版本,会自动查看
Tensorflow1.0架构,主要由Keras,Estimator,Datasets,layers,Distribution engine构成如下:
Tensorflow1.0主要是提成了训练速度,而且可以在移动设备上运行,另外,还引入了更高级别的API,如tf.laryers
,tf.metrics
,tf.losses
,tf.keras
。最后还有Tensorflow的调试器,支持dorcker镜像,引入了tensorflow serving服务。
Tensorflow2 架构,主要由两层,第一层是训练层,另一个是部署层。如下图
Tensorflow2 架构的特性
(1)使用tf.data加载数据
(2)使用tf.keras构建模型,也可以使用premade estimator来验证模型,使用tensorflow hub进行迁移学习
(3)使用eager mode进行运行和调试
(4)使用分发策略来进行分布式训练
(5)导出到SaveModel
(6)使用Tensorflow Server、TensorFlow Lite、TensorFlow.js部署模型
(7)强大的跨平台能力,Tensorflow2服务直接通过HTTP/REST或者GRPC/协议缓冲区实现,TensorFlow Lite可以直接部署在Android、IOS和嵌入式系统上,TensorFlow.js在javascript中部署模型
(8)Tf.keras功能API和子类API,允许创建负责的拓扑结构
(9)自定义训练逻辑,使用tf.GradientTape和tf.custom_gradient进行更细粒度的控制
(10)底层API可以与高层结合使用,完全的可定制
(11)高级扩展:Ragged Tensors、Tensor2Tensor
下面本文用一个简单的应用来讲以下如何使用Tensorflow框架,
2 tf.keras核心概念
2.1 tf.keras简介
Keras 是一个高级的 (high-level) 深度学习框架,作者是 Francois Chollet。Keras 可以以两种方法运行:
(1)以 TensorFlow, CNTK, 或者 Theano 作为后端 (backend) 运行
(2)在 TensorFlow 里面直接运行,使用语句 tf.keras
,本文使用这个
2.2 tf.完整流程
很多学习过Scikit-learn的框架,而Keras的完整流程和它也差不多,大致流程如图所示(左图为Scikit-learn流程,右图为Keras流程):
代码实例:
# 导入包
# 导入基础包
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
# 导入tf
import tensorflow as tf
print(tf.__version__)
print(tf.keras.__version__)
fashion_minist = tf.keras.datasets.fashion_mnist
(x_train_all,y_train_all),(x_test,y_test) = fashion_minist.load_data()
print(x_train_all[0]) # 第一条数据,有很多个特征,每一个特征由一个一维向量表示
x_train_all = x_train_all / 255.0 # 由于数据分散,所以要进行归一化,让每个特征的各个向量都差异不要太大
x_test = x_test / 255.0
print(x_train_all[0])
#将训练集合划分为数据集和校验集
x_train , x_valid = x_train_all[5000:],x_train_all[:5000]
y_train , y_valid = y_train_all[5000:],y_train_all[:5000]
print(x_train.shape, y_train.shape)
print(x_valid.shape, y_valid.shape)
print(x_test.shape, y_test.shape)
# 展示一张图片
def show_image(img_arr):
plt.imshow(img_arr, cmap='binary')
plt.show()
show_image(x_train[0])
print(y_train[0])
show_image(x_valid[0])
print(y_valid[0])
show_image(x_test[0])
print(y_test[0])
# 构建模型
model = tf.keras.models.Sequential() # 相当于一个容器
model.add(tf.keras.layers.Flatten(input_shape=[28,28]))
model.add(tf.keras.layers.Dense(300,activation=tf.keras.activations.relu))
model.add(tf.keras.layers.Dense(100,activation=tf.keras.activations.relu))
model.add(tf.keras.layers.Dense(10,activation=tf.keras.activations.softmax))
# 模型编译
model.compile(
loss = tf.keras.losses.sparse_categorical_crossentropy,
optimizer=tf.keras.optimizers.SGD(lr=0.001),
metrics=['accuracy']
)
history = model.fit(x_train, y_train, epochs = 5, validation_data=(x_valid, y_valid))
print(type(history))
print(type(history.history))
print(history.history)
def plot_learing_show(history):
pd.DataFrame(history.history).plot(figsize=(8, 5))
plt.grid(True)
plt.gca().set_ylim(0,1)
plt.show()
plot_learing_show(history)
test_history = model.evaluate(x_test,y_test)
print(test_history)
print(y_test[9])
test_class_list = model.predict_classes(x_test)
print(test_class_list[9])
show_image(x_test[9])
2.3 神经网络中的数据格式(重要)
我们一定要非常清楚我们输入到模型的数据是什么样子的,并不是说直接就把比如视频或者文本文件直接输入到模型中,而是要转化为模型所能处理的样式才可以。
现实生活中一般会遇到的类型有:
(1)结构性的数据:(数据表,如班级名单,股票预测等)
(2)非结构性的数据:(序列(文本),图片,视频)
神经网络中可以使用的数据
(1)数据表-2D数据 形状=(样本数,特征数)
。一般称为2D张量,其实就是举证,一般可以用csv或者excel来存储。
(2)序列类-3D数据 形状=(样本数,步长(每段文字的长度),特征数(每个字的特征数))
,一般称为3D张量,通常用来表示文本。(假设,收集一百万条新闻,那么整个数据集的形状为(10000,280,128))
(3)图像类-4D数据 形状=(样本数,宽,高,通道数)
。一般称为4D张量,通常用来表示图像。
(4)视频类-5D数据 形状=(样本数,帧数,宽,高,通道数)
。一般称为5D张量,通常用来表示视频。
2.4 tf.keras中的神经网络
现实中的神经由很多的神经元组成,这些神经元有两种状态,一个是激发状态,一个则是
抑制状态。人工神经网络主要由大量的神经元以及它们之间的有向链接构成。
神经网络主要考虑三方面的事情,一个是神经元的激活规则(使用激活函数):主要是指神经元输入到输出之间的映射关系,一般称为非线性函数。另一个是网络的拓扑结构,就是指整个神经元是如何链接的。第三个是学习算法,就是通过训练数据来学习神经网络的参数,让每个神经元到哪种程度才会触发做一个调整。
深度学习的网络结构有三种类型,前馈网络,记忆网络和图网络,但是大多数网络都是复合型的结构,包含了多种网络。
在tf.keras中神经网络通常由以下四个方面组成:
(1)层(layers)和 模型(models)
(2)输入(input)和输出(output)
(3)损失函数(loss)
(4)优化器(optimizer)
通常神经网络里面的基本数据类型是层,而 tf.keras
中 layers
是层的集合;不同数据格式或不同数据处理类型需要用到不同的层,比如:
(1)形状为 (样本数,特征数) 的 2D 数据,通常用全连接层,对应 tf.keras
里面的 Dense
层;
(2)形状为 (样本数,步长,特征数) 的 3D 序列数据,通常用循环层,对应tf.keras
里面的 RNN, GRU
或 LSTM
;
(3)形状为 (样本数,宽,高,通道数) 的 4D 图像数据,通常用二维卷积层,对应tf.keras
里面的 Conv2D
。
对于Dense层,经常会做Flatten;
损失函数:对于分类、回归、序列预测等常见问题,你可以遵循一些简单的指导原则来选择正确的损失函数。
(1)对于二分类问题,用二元交叉熵(binary crossentropy)损失函数
(2)对于多分类问题,用分类交叉熵(categorical crossentropy)损失函数
(3)对于回归问题,用均方误差(mean-squared error)损失函数
(4)对于序列学习问题,用联结主义时序分类(CTC,connectionist temporal classification)损失函数
(5)有时在面对真正全新的问题时,你还需要自主的设计损失函数。
而优化器常用在tf.keras中有:
(1)Adagrad
(2)Adadelta
(3)RMSprop
(4)Adam
(5)AdaMax
(6)Nadam
(7)AMSGrad
2.5 tf.keras中模型的三种构建方式
三种模型的构建方式分别称为序列式构建,函数式构建和子类化构建。
序列式
构建如下图所示:
# 序列式构建第一种方式(如果模型中有分支就不好处理了)
model = keras.models.Sequential() #相当于一个容器
model.add(keras.layers.Flatten(input_shape=[28,28])) #拉直
model.add(keras.layers.Dense(256, activation='relu'))
model.add(keras.layers.Dense(100, activation='relu'))
model.add(keras.layers.Dense(10, activation='softmax'))
# 序列式构建第二种方式
model2 = keras.models.Sequential(
[
keras.layers.Flatten(input_shape=[28,28]),
keras.layers.Dense(256, activation=keras.activations.relu),
keras.layers.Dense(100, activation=keras.activations.relu),
keras.layers.Dense(10, activation=keras.activations.softmax)
]
)
函数式
构建如下图所示:
# 函数式构建 记住一句话:把层当做函数来使用
input = Input(shape=[28,28]) #输入
x = Flatten()(input) #把Flatten()当成函数f, 简化一下呢 x = f(input)
x = Dense(256, activation='relu')(x) #把Dense(256, activation='relu') 当成函数g 简化一下呢 x = g(x)
x = Dense(100, activation='relu')(x) #把Dense(100, activation='relu') 当成函数p 简化一下呢 p = p(x)
output = Dense(10, activation='softmax')(x) #把Dense(10, activation='softmax') 当成函数h 简化一下呢 h = h(x)
model3 = keras.Model(inputs = [input], outputs = [output]) #最后用keras.Model将input和output建立关系
序列式
和函数式
都是声明式编程
(declarative programming),它描述目标的性质,让计算机明白目标,而非流程。具体来说,它们都是声明哪些层应该按什么顺序来添加,层与层以什么样的方式连接,所有声明完成之后再给模型喂数据开始训练,这种方法有好有坏:
(1)好处:模型很容易保存、复制和分享,模型结构也容易展示和分析,因此调试起来比较容易;
(2)坏处:是个静态模型,很多情况模型有循环(loops)和条件分支(conditional branching),这时我们更需要命令式编程(imperative programming)了。
子类化
构建:(首先引入必要的包,然后构建一个类集成Model,然后重写__init__()
方法和call()
方法即可)
class MnistModel(keras.Model):
def __init__(self, **kwargs): # 类似序列式定义各个层次
super().__init__(**kwargs)
self.hidden_layer1 = Dense(units=256, activation='relu')
self.hidden_layer2 = Dense(units=100, activation='relu')
self.output_layer = Dense(units=10, activation='softmax')
def call(self, input): # 类似函数式,将每次之间的关系进行关联
x = Flatten()(input)
hidden_layer1 = self.hidden_layer1(x)
hidden_layer2 = self.hidden_layer2(hidden_layer1)
output = self.output_layer(hidden_layer2)
return output
model4 = MnistModel()
model4.build(input_shape=(None, 784))
构造函数负责创建不同的层,在本例中创建了一个隐藏层 self.hidden 和一个输出层self.main_output
;
call()
函数负责各种计算,注意到该函数有个参数是 input
3 实战1
3.1 电影评论情感分类
下面引入一个电影评论情感分类的一个应用来进一步掌握整个深度学习的过程。
整个流程如下:(大部分应用都是这个步骤)
(1)导入数据
(2)数据预处理
(3)构建模型定义损失函数和优化器
(4)模型训练
(5)评估
代码实例:
# 导包
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import os
# 导入tf
import tensorflow as tf
from tensorflow import keras
print(tf.__version__)
print(keras.__version__)
# 加载数据集
# num_words:只取10000个词作为词表
imdb = keras.datasets.imdb
(train_x_all, train_y_all),(test_x, test_y)=imdb.load_data(num_words=10000)
# 查看数据样本数量
print("Train entries: {}, labels: {}".format(len(train_x_all), len(train_y_all)))
print("Train entries: {}, labels: {}".format(len(test_x), len(test_y)))
print(train_x_all[0]) # 查看第一个样本数据的内容
print(len(train_x_all[0])) # 查看第一个和第二个训练样本的长度,不一致
print(len(train_x_all[1]))
# 构建字典 两个方法,一个是id映射到字,一个是字映射到id
word_index = imdb.get_word_index()
word2id = { k:(v+3) for k, v in word_index.items()}
word2id['<PAD>'] = 0
word2id['START'] = 1
word2id['<UNK>'] = 2
word2id['UNUSED'] = 3
id2word = {v:k for k, v in word2id.items()}
def get_words(sent_ids):
return ' '.join([id2word.get(i, '?') for i in sent_ids])
sent = get_words(train_x_all[0])
print(sent)
# 句子末尾进行填充
train_x_all = keras.preprocessing.sequence.pad_sequences(
train_x_all,
value=word2id['<PAD>'],
padding='post', #pre表示在句子前面填充, post表示在句子末尾填充
maxlen=256
)
test_x = keras.preprocessing.sequence.pad_sequences(
test_x,
value=word2id['<PAD>'],
padding='post',
maxlen=256
)
print(train_x_all[0])
print(len(train_x_all[0]))
print(len(train_x_all[1]))
#模型编写
vocab_size = 10000
model = keras.Sequential()
model.add(keras.layers.Embedding(vocab_size, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation='relu'))
model.add(keras.layers.Dense(1, activation='sigmoid'))
model.summary()
model.compile(optimizer='adam', loss=keras.losses.binary_crossentropy, metrics=['accuracy'])
train_x, valid_x = train_x_all[10000:], train_x_all[:10000]
train_y, valid_y = train_y_all[10000:], train_y_all[:10000]
# callbacks Tensorboard, earlystoping, ModelCheckpoint
# 创建一个文件夹,用于放置日志文件
logdir = os.path.join("callbacks")
if not os.path.exists(logdir):
os.mkdir(logdir)
output_model_file = os.path.join(logdir, "imdb_model.h5")
# 当训练模型到什么程度的时候,就停止执行 也可以直接不用,然后直接训练
callbacks = [
# 保存的路径(使用TensorBoard就可以用命令,tensorboard --logdir callbacks 来分析结果)
keras.callbacks.TensorBoard(logdir),
# 保存最好的模型
keras.callbacks.ModelCheckpoint(filepath=output_model_file, save_best_only=True),
# 当精度连续5次都在1乘以10的-1次方之后停止训练
keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3)
]
history = model.fit(
train_x, train_y,
epochs=40,
batch_size=512,
validation_data=(valid_x, valid_y),
callbacks = callbacks,
verbose=1 # 设置为1就会打印日志到控制台,0就不打印
)
def plot_learing_show(history):
pd.DataFrame(history.history).plot(figsize=(8,5))
plt.grid(True)
plt.gca().set_ylim(0,1)
plt.show()
plot_learing_show(history)
result = model.evaluate(test_x, test_y)
print(result)
test_classes_list = model.predict_classes(test_x)
print(test_classes_list[1][0])
print(test_y[1])
3.2 earlystoping的使用
在模型训练过程中,可能会出现过拟合的情况,所以可以使用earlystoping函数来让模型提前结束训练。具体使用方法如下:简单总结如下
(1)定义一个callbacks列表,加载存在的模型,加载EarlyStopping。
(2)在模型训练的使用,把参数callbacks加上上面的列表即可。
(3)如果想用tensorboard打开一个图形化界面来分析训练过程,那么还可以在callbacks外面加入一个TensorBorard。
# callbacks Tensorboard, earlystoping, ModelCheckpoint
# 创建一个文件夹,用于放置日志文件
logdir = os.path.join("callbacks")
if not os.path.exists(logdir):
os.mkdir(logdir)
output_model_file = os.path.join(logdir, "imdb_model.h5")
# 当训练模型到什么程度的时候,就停止执行 也可以直接不用,然后直接训练
callbacks = [
# 保存的路径(使用TensorBoard就可以用命令,tensorboard --logdir callbacks 来分析结果)
keras.callbacks.TensorBoard(logdir),
# 保存最好的模型
keras.callbacks.ModelCheckpoint(filepath=output_model_file, save_best_only=True),
# 当精度连续5次都在1乘以10的-1次方之后停止训练
keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3)
]
history = model.fit(
train_x, train_y,
epochs=40,
batch_size=512,
validation_data=(valid_x, valid_y),
callbacks = callbacks,
verbose=1 # 设置为1就会打印日志到控制台,0就不打印
)
4 实战2
4.1 回归问题预测汽车燃油效率
回归问题
在实际中很常见,如预测房屋价格、⽓温、销售额等连续值的问题。与回归问题不同,分类问题中模型的最终输出是⼀个离散值。我们所说的图像分类、垃圾邮件识别、疾病检测等输出为离散值的问题都属于分类问题的范畴。
预测汽车燃油效率:使用经典的 Auto MPG 数据集,构建了一个用来预测70年代末到80年代初汽车燃油效率的模型。为了做到这一点,本文将为该模型提供许多那个时期的汽车描述。这个描述包含:气缸数,排量,马力以及重量
MPG数据集:该数据集取自卡内基梅隆大学维护的StatLib库。该数据集用于1983年美国统计协会博览会。数据涉及以每加仑英里为单位的城市循环燃料消耗,将根据3个多值离散值和5个连续属性进行预测。
代码实例:
# 导包
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import os
import pathlib
import seaborn as sns
# 导入tf
import tensorflow as tf
from tensorflow import keras
print(tf.__version__)
print(keras.__version__)
# 加载数据集
dataset_path = keras.utils.get_file('auto-mpg.data',
"http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
print(dataset_path)
# 使用pandas导入数据集
column_names = ['MPG', 'Cylinders', 'Displacement', 'Horsepower', 'Weight', 'Acceleration', 'Model Year', 'Origin']
raw_dataset = pd.read_csv(dataset_path, names=column_names, na_values='?', comment='\t',
sep=' ', skipinitialspace=True)
dataset = raw_dataset.copy()
print(dataset.tail())
# 数据清洗
print(dataset.isna().sum())
dataset = dataset.dropna()
print(dataset.isna().sum())
# 将origin转换成one-hot编码
origin = dataset.pop('Origin')
dataset['USA'] = (origin == 1) * 1.0
dataset['Europe'] = (origin == 2) * 1.0
dataset['Japan'] = (origin == 3) * 1.0
print(dataset.tail())
# 拆分数据集 拆分成训练集和测试集
train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)
# 总体数据统计
train_stats = train_dataset.describe()
train_stats.pop("MPG")
train_stats = train_stats.transpose()
print(train_stats)
# 从标签中分类特征
train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')
print(train_labels[0])
# 数据规范化
def norm(x):
return (x - train_stats['mean']) / train_stats['std']
norm_train_data = norm(train_dataset)
norm_test_data = norm(test_dataset)
# 构建模型
def build_model():
model = keras.Sequential([
keras.layers.Dense(512, activation='relu', input_shape=[len(train_dataset.keys())]),
keras.layers.Dense(256, activation='relu'),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dense(1)
]
)
optimizer = keras.optimizers.RMSprop(0.001)
model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse'])
return model
# 构建防止过拟合的模型,加入正则项L1和L2
def build_model2():
model = keras.Sequential([
keras.layers.Dense(512, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001),
input_shape=[len(train_dataset.keys())]),
keras.layers.Dense(256, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001)),
keras.layers.Dense(128, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001)),
keras.layers.Dense(64, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001)),
keras.layers.Dense(1)
]
)
optimizer = keras.optimizers.RMSprop(0.001)
model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse'])
return model
# 构建防止过拟合的模型,加入正则项L1
def build_model3():
model = keras.Sequential([
keras.layers.Dense(512, activation='relu', kernel_regularizer=keras.regularizers.l1(0.001),
input_shape=[len(train_dataset.keys())]),
keras.layers.Dense(256, activation='relu', kernel_regularizer=keras.regularizers.l1(0.001)),
keras.layers.Dense(128, activation='relu', kernel_regularizer=keras.regularizers.l1(0.001)),
keras.layers.Dense(64, activation='relu', kernel_regularizer=keras.regularizers.l1(0.001)),
keras.layers.Dense(1)
]
)
optimizer = keras.optimizers.RMSprop(0.001)
model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse'])
return model
# 构建防止过拟合的模型,加入正则项L2
def build_model4():
model = keras.Sequential([
keras.layers.Dense(512, activation='relu', kernel_regularizer=keras.regularizers.l2(0.001),
input_shape=[len(train_dataset.keys())]),
keras.layers.Dense(256, activation='relu', kernel_regularizer=keras.regularizers.l2(0.001)),
keras.layers.Dense(128, activation='relu', kernel_regularizer=keras.regularizers.l2(0.001)),
keras.layers.Dense(64, activation='relu', kernel_regularizer=keras.regularizers.l2(0.001)),
keras.layers.Dense(1)
]
)
optimizer = keras.optimizers.RMSprop(0.001)
model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse'])
return model
# 构建模型 使用dropout来防止过拟合
def build_model5():
model = keras.Sequential([
keras.layers.Dense(512, activation='relu', input_shape=[len(train_dataset.keys())]),
keras.layers.Dropout(0.5),
keras.layers.Dense(256, activation='relu'),
keras.layers.Dropout(0.5),
keras.layers.Dense(128, activation='relu'),
keras.layers.Dropout(0.5),
keras.layers.Dense(64, activation='relu'),
keras.layers.Dropout(0.5),
keras.layers.Dense(1)
]
)
optimizer = keras.optimizers.RMSprop(0.001)
model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse'])
return model
# 构建模型 使用正则化L1和L2以及dropout来预测
def build_model6():
model = keras.Sequential([
keras.layers.Dense(512, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001),
input_shape=[len(train_dataset.keys())]),
keras.layers.Dropout(0.5),
keras.layers.Dense(256, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001)),
keras.layers.Dropout(0.5),
keras.layers.Dense(128, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001)),
keras.layers.Dropout(0.5),
keras.layers.Dense(64, activation='relu', kernel_regularizer=keras.regularizers.l1_l2(0.001)),
keras.layers.Dropout(0.5),
keras.layers.Dense(1)
]
)
optimizer = keras.optimizers.RMSprop(0.001)
model.compile(loss='mse', optimizer=optimizer, metrics=['mae', 'mse'])
return model
model = bulid_model()
model.summary()
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=200)
# 模型训练
history = model.fit(
norm_train_data, train_labels, epochs=1000, validation_split=0.2, verbose=0, callback=[early_stop]
)
def plot_history(history):
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Abs ERROR [PMG]')
plt.plot(hist['epoch'], hist['mae'], label='Train Error')
plt.plot(hist['epoch'], hist['val_mae'], label='Val Error')
plt.ylim([0, 5])
plt.legend()
plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Squaree ERROR [PMG]')
plt.plot(hist['epoch'], hist['mse'], label='Train Error')
plt.plot(hist['epoch'], hist['val_mse'], label='Val Error')
plt.ylim([0, 20])
plt.legend()
plt.show()
plot_history(history)
# 看下测试集合的效果
loss, mae, mse = model.evaluate(norm_test_data, test_labels, verbose=2)
print(loss)
print(mae)
print(mse)
# 做预测
test_preditions = model.predict(norm_test_data)
test_preditions = test_preditions.flatten()
plt.scatter(test_labels, test_preditions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictios [MPG]')
plt.axis('equal')
plt.axis('square')
plt.xlim([0, plt.xlim()[1]])
plt.ylim([0, plt.ylim()[1]])
_ = plt.plot([-100, 100], [-100, 100])
# 看一下误差分布
error = test_preditions - test_labels
plt.hist(error, bins=25)
plt.xlabel("Prediction Error [MPG]")
_ = plt.ylabel('Count')
4.2 回归问题的评价指标
也就是我们说的用于回归问题的损失函数使用什么的问题。一般我们常用的三个函数是:均方误差MSE,军方根误差RMSE,和平均相对误差MAE
(1)均方误差:MSE(Mean Squared Error)
M
S
E
=
1
m
∑
i
=
1
m
(
y
i
−
y
^
i
)
2
MSE = \frac{1}{m}\sum\limits_{i=1}^m(y_i-\hat{y}_i)^2
MSE=m1i=1∑m(yi−y^i)2
(2)均方根误差:RMSE(Root Mean Squard Error)
M
S
E
=
1
m
∑
i
=
1
m
(
y
i
−
y
^
i
)
2
MSE =\sqrt{ \frac{1}{m}\sum\limits_{i=1}^m(y_i-\hat{y}_i)^2}
MSE=m1i=1∑m(yi−y^i)2
(3)平均绝对误差:MAE(Mean Absolute Error)
M
S
E
=
1
m
∑
i
=
1
m
∣
y
i
−
y
^
i
∣
MSE = \frac{1}{m}\sum\limits_{i=1}^m|y_i-\hat{y}_i|
MSE=m1i=1∑m∣yi−y^i∣
4.2 过拟合和欠拟合
过拟合
是指模型在训练集上表现很好,但是在训练集外的数据集上表现一般。产生原因如下:
(1)训练数据过少或存在噪音,无法对整个数据的分布进行估计
(2)特征维度过多,求解模型中没有那么多的特征值得重用
(3)在对模型进行过度训练(overtraining)时,常常会导致过拟合
常见的防止过拟合
的方法有:
(1)数据清洗
(2)增加数据集
(3)early stopping
(4)数据集扩增(Data augmentation)
(5)正则化(Regularization)
(6)Dropout(神经网络)
欠拟合
指的是模型没有很好地捕捉到数据特征,导致拟合的函数在训练集上表现效果差,
预测的准确率低。产生原因如下:
(1)模型复杂度过低
(2)特征量过少
常见的防止欠拟合
的方法有:
(1)添加其他特征项(e.g.,“组合”、“泛化”、“相关性”),模型出现欠拟合的时候是因为特征项不够导致的,可以添加其他特征项来很好地解决。
(2)添加多项式特征,例如将线性模型通过添加二次项或者三次项使模型泛化能力更强
(3)减少正则化参数,正则化的目的是用来防止过拟合的,当模型出现了欠拟合,则需要减少正则化参数。
4.2.1 正则化
所谓的使用正则化来防止过拟合,就是在原来 Loss Function 的基础上
,加了一些正则化项
,或者叫做模型复杂度惩罚项
。
4.2.1.1 L1正则化
在原始的损失函数后面加上一个L1正则化项,即全部权重 w 的绝对值的和
,再乘以λ/n。
则损失函数变为:
- C = C 0 + λ n ∑ i ∣ w i ∣ C=C_0+\frac{\lambda}{n}\sum\limits_i|w_i| C=C0+nλi∑∣wi∣
则对应的导数为:
- ∂ C ∂ w = ∂ C 0 ∂ w + λ n s g n ( w ) \frac{\partial{C}}{\partial{w}} = \frac{\partial{C_0}}{\partial{w}}+\frac{\lambda}{n}sgn(w) ∂w∂C=∂w∂C0+nλsgn(w)
其中 sgn(w)只是简单地取 w 各个元素地正负号。
- s g n ( w ) = { 1 , w > 0 0 , w = 0 − 1 , w < 0 sgn(w)=\begin{cases}1,\quad w\gt0 \\ 0,\quad w=0\\ -1,\quad w\lt0 \end{cases} sgn(w)=⎩⎪⎨⎪⎧1,w>00,w=0−1,w<0
梯度下降时权重 w更新变为:
- w − − > w ′ = w − η λ n s g n ( w ) − η ∂ C 0 ∂ w w --> w' = w-\frac{\eta{\lambda}}{n}sgn(w)-\eta\frac{\partial{C_0}}{\partial{w}} w−−>w′=w−nηλsgn(w)−η∂w∂C0
(1)当w=0时,|w|是不可导的。所以我们仅仅能依照原始的未经正则化的方法去更新。
(2)当w>0时,sgn(w)>0,则梯度下降时更新后的w变小。
(3)当w<0时,sgn(w)>0,则梯度下降时更新后的w变大。换句话说,L1正则化使得权重w往0靠,使网络中的权重尽可能为0,也就相当于减小了网络复杂度,防止过拟合。
这也就是L1正则化会产生更稀疏(sparse)的解的原因。此处稀疏性指的是最优值中的一些参数为0,L1正则化的稀疏性质已经被广泛地应用于特征选择机制,从可用的特征子集中选择出有意义的特征。
4.2.1.2 L2正则化
L2正则化通常被称为权重衰减
(weight decay),就是在原始的损失函数后面再加上一个L2正则化项,即全部权重w的平方和
,再乘以λ/2n。
则损失函数变为:
- C = C 0 + λ 2 n ∑ i w i 2 C=C_0+\frac{\lambda}{2n}\sum\limits_iw_i^2 C=C0+2nλi∑wi2
对应的梯度为:
-
∂ C ∂ w = ∂ C 0 ∂ w + λ n w \frac{\partial{C}}{\partial{w}} = \frac{\partial{C_0}}{\partial{w}}+\frac{\lambda}{n}w ∂w∂C=∂w∂C0+nλw
-
∂ C ∂ b = ∂ C 0 ∂ b \frac{\partial{C}}{\partial{b}} = \frac{\partial{C_0}}{\partial{b}} ∂b∂C=∂b∂C0
能够发现L2正则化项对偏置 b 的更新没有影响,可是对于权重w的更新有影响:
- w − − > w ′ = w − η ∂ C 0 ∂ w − η λ n w = ( 1 − η λ n ) w − η ∂ C 0 ∂ w w --> w' = w-\eta\frac{\partial{C0}}{\partial{w}}-\frac{\eta\lambda}{n}w=(1-\frac{\eta\lambda}{n})w-\eta\frac{\partial{C_0}}{\partial{w}} w−−>w′=w−η∂w∂C0−nηλw=(1−nηλ)w−η∂w∂C0
能够发现L2正则化项对偏置 b 的更新没有影响,可是对于权重w的更新有影响,这里的
η
、
n
、
λ
\eta、n、\lambda
η、n、λ都是大于0的,所以
1
−
f
r
a
c
η
λ
n
1-frac{\eta\lambda}{n}
1−fracηλn小于1。因此在梯度下降过程中,权重w将逐渐减小,趋向于0但不等于0.这也就是权重衰减
(weight decay)的由来。
为什么L2正则化可以使得权重参数w变小的效果,为什么能防止过拟合呢?因为更小的权重参数w以为着模型的复杂度更低,对训练数据的拟合刚刚好,不会过分拟合训练数据,从而提高模型的泛化能力。
4.2.2 Dropout
正则是通过在代价函数后面加上正则项来防止模型过拟合的。而在神经网络中,有一种方法是通过修改
神经网络本身结构
来实现的,其名为Dropout。该方法是在对网络进行训练时用一种技巧(trick),对于如下所示的三层人工神经网络:
对于上图所示的网络,在训练开始时,随机得删除一些
(可以设定为一半,也可以为1/3,1/4等)隐藏层
神经元,即认为这些神经元不存在
,同时保持输入层与输出层神经元的个数不变,这样便得到如下的神经网络:
然后按照BP学习算法对神经网络中的参数进行学习更新
(虚线连接的单元不更新,因为认为这些神经元被临时删除了)。这样一次迭代更新便完成了。下一次迭代中,同样随机删除一些神经元,与上次不一样,做随机选择。这样一直进行下去,直至训练结束。Dropout方法是通过修改
神经网络中隐藏层的神经元个数
来防止神经网络的过拟合。