到目前为止,Keras已经与TensorFlow完全整合。Keras团队不再更新或维护Keras的独立版本。所以现在所讨论的Keras,是一个集成在Tensor Flow中的API,而不是一个单独的独立库。
在准备数据时,我们首先需要了解数据的格式,希望数据的格式能够传递给神经网络模型。Sequential模型在训练期间接收数据,这发生在我们对模型调用fit()函数时(训练)。因此,我们需要检查这个函数期望的数据类型。
fit()函数接收的参数如下:
fit(
x=None,
y=None,
batch_size=None,
epochs=1,
verbose='auto',
callbacks=None,
validation_split=0.0,
validation_data=None,
shuffle=True,
class_weight=None,
sample_weight=None,
initial_epoch=0,
steps_per_epoch=None,
validation_steps=None,
validation_batch_size=None,
validation_freq=1,
max_queue_size=10,
workers=1,
use_multiprocessing=False
)
对其中一些常见的参数进行解释:
更多的细节可以查看:https://tensorflow.google.cn/api_docs/python/tf/keras/Sequential#fit
除了格式化数据以使其符合模型所需的格式之外,格式化或处理数据的另一个原因是以这样的方式对其进行转换,从而使网络更容易、更快或更高效地学习。
深度学习的数据处理将因我们处理的数据类型和我们将使用网络执行的任务类型而有很大不同。
我们将从一个非常简单的分类任务开始,使用一个简单的数字数据集。
我们首先需要导入我们将要使用的库。接下来,我们创建两个空列表。一个保存输入数据,另一个保存目标数据。
import numpy as np
from random import randint
from sklearn.utils import shuffle
from sklearn.preprocessing import MinMaxScaler
train_labels = []
train_samples = []
创建数据集:一个简单的数据集,用于一个简单的任务
我们假设在一项临床试验中,一种实验药物在13岁到100岁的人身上进行了测试。该试验有2100名参与者。一半参与者年龄在65岁以下,另一半参与者年龄在65岁或以上。该试验显示,约95%的65岁或以上患者出现药物副作用,约95%的65岁以下患者没有副作用,这通常表明老年人更有可能出现副作用。
最终,我们想要建立一个模型,告诉我们患者是否会仅仅根据患者的年龄而经历副作用。模型的判断将基于训练数据。
年龄 | 数量 | 情况 |
---|---|---|
13-64 | 1050 | 95%没有副作用,5%出现副作用 |
65及以上 | 1050 | 95%出现副作用,5%没有副作用 |
该代码创建2100个样本,并在train_samples列表中存储个体的年龄,并在train_labels列表中存储个体是否经历过副作用。
for i in range(50):
# The ~5% of younger individuals who did experience side effects
random_younger = randint(13,64)
train_samples.append(random_younger)
train_labels.append(1)
# The ~5% of older individuals who did not experience side effects
random_older = randint(65,100)
train_samples.append(random_older)
train_labels.append(0)
for i in range(1000):
# The ~95% of younger individuals who did not experience side effects
random_younger = randint(13,64)
train_samples.append(random_younger)
train_labels.append(0)
# The ~95% of older individuals who did experience side effects
random_older = randint(65,100)
train_samples.append(random_older)
train_labels.append(1)
由于fit()函数的预测,我们现在将这两个列表转换为numpy数组,然后对数组进行shuffle,以删除创建过程中强加给数据的任何顺序。
train_labels = np.array(train_labels)
train_samples = np.array(train_samples)
train_labels, train_samples = shuffle(train_labels, train_samples)
在这种形式下,我们现在能够将数据传递给模型,因为它现在是所需的格式,然而,在这样做之前,我们首先将数据缩小到0到1的范围。
我们将使用scikit learn的MinMaxScaler类将所有数据从13到100的范围缩小到0到1的范围。
scaler = MinMaxScaler(feature_range=(0,1))
scaled_train_samples = scaler.fit_transform(train_samples.reshape(-1,1))
上面的reshape是因为fit_transform()不接受一维数据,所以进行维度的扩增。
先导入TensorFlow相关的包
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Activation, Dense
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.metrics import categorical_crossentropy
接下来是通过Sequential()创建模型的各层次结构,model是Sequential对象的一个实例,tf.kreas.Sequential模型是一个层的线性堆栈,它接受一个列表,列表中的每一元素都是一个层
model = Sequential([
Dense(units=16, input_shape=(1,), activation='relu'),
Dense(units=32, activation='relu'),
Dense(units=2, activation='softmax')
])
我们的第一层是Dense层。这种类型的层是我们标准的完全连接的神经网络层。Dense层需要的第一个参数是该层拥有的神经元或单位的数量,我们任意将其设置为16。
此外,模型需要知道输入数据的形状(也就是输入特征)。因此,我们在模型的第一个隐藏层中指定输入数据的形状(仅此层),其他的层会自动从上一层获得输入数据的形状。名为input_shape的参数就是我们指定它的方式。
如前所述,我们将根据我们在上一集中生成和处理的数据来训练我们的网络,train_samples是一维的。input_shape参数需要一个匹配输入数据形状的整数元组,因此我们相应地指定(1,)作为一维数据的input_shape。
你可以把我们在这里指定input_shape的方式想象成一个隐式输入层。神经网络的输入层是底层原始数据本身,因此我们不创建明确的输入层。我们现在处理的第一个Dense层实际上是第一个隐藏层。
最后,我们将为密集层设置的可选参数是在该层之后使用的激活函数。我们将使用流行的relu选项。注意,如果没有明确设置激活函数,那么Keras将使用linear激活函数。
我们的下一层也将是Dense层,这一层将有32个节点。这个节点有多少神经元的选择也是任意的,因为其想法是创建一个简单的模型,然后用它进行测试和实验。如果我们注意到这是不够的,那么在那个时候,我们可以解决这个问题,并开始尝试改变参数,如层数、节点数等。这个Dense层还将使用relu作为其激活函数。
最后,我们指定输出层。这一层也是一个Dense层,它将有2个神经元。这是因为我们有两种可能的输出:**要么患者出现副作用,要么患者没有出现副作用。**这一次,我们将使用的激活函数是softmax,它将为我们提供可能输出之间的概率分布。
我们可以在模型上调用summary()来快速可视化该模型。
model.summary()
首先我们需要将模型需要的数据准备好,compile()函数就是将模型训练时所需要的所有数据都准备好,等着模型进行训练即可。
model.compile(optimizer=Adam(learning_rate=0.0001), loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(x=scaled_train_samples, y=train_labels, batch_size=10, epochs=30, verbose=2)
回想一下,我们之前构建了一个训练集,在此基础上训练我们的模型。随着我们的模型被训练的每一个阶段,模型将继续学习这个训练集中数据的特征和特征。我们的希望是,以后我们可以利用这个模型,将其应用到新数据中,并让该模型仅根据从训练集中学到的知识,准确地预测以前从未见过的数据。
这就是验证集的作用。在训练开始之前,我们可以选择删除培训集的一部分,并将其放入验证集中。然后,在训练期间,模型将只在训练集中训练,并通过评估验证集中的数据进行验证。本质上,该模型是在训练集中学习数据的特征,从这些数据中学习到什么,然后在验证集上进行预测。在每个阶段,我们不仅会看到训练集的损失和准确性结果,还会看到验证集的损失和准确性结果。这让我们能够看到模型在未经训练的数据上的泛化程度,因为回想起来,验证数据不应该是训练数据的一部分。
这也有助于我们了解模型是否过度拟合。当模型只了解训练数据的细节,无法很好地概括未训练的数据时,就会发生过度拟合。
通常情况下,我们的训练集准确率如果大于验证集的准确率,表示发生了过拟合
可以和创建数据集那样创建符合要求的验证集数据,valid_set = (x_val, y_val),然后直接将该验证集数据传入fit中进行训练即可。
model.fit(
x=scaled_train_samples
, y=train_labels
, validation_data=valid_set
, batch_size=10
, epochs=30
, verbose=2
)
直接使用 validation_split参数在训练集中进行分割。
指定此参数后,Keras将分离训练数据的一部分(本例中为10%)用作验证数据。该模型将分离训练数据的这一部分,不在其上进行训练,并将在每个epoch结束时评估该数据的损失和任何模型度量。
请注意,默认情况下,fit()函数会在每个epoch之前洗牌数据。但是,在指定validation_split参数时,会从洗牌前x和y数据中的最后一个样本中选择验证数据。因此,在我们以这种方式使用validation_split来创建验证数据的情况下,我们需要确保我们的数据已经提前被shuffle。
model.fit(
x=scaled_train_samples
, y=train_labels
, validation_split=0.1
, batch_size=10
, epochs=30
, verbose=2
)
不仅可以看到训练数据集的准确性信息,还可以看到验证集中的信息。
首先需要创建与训练数据类似的测试数据,该数据是模型从没有见过的,因此可以测试模型的训练结果。
创建测试集的方法与训练集的创建很类似。
test_labels = []
test_samples = []
for i in range(10):
# The 5% of younger individuals who did experience side effects
random_younger = randint(13,64)
test_samples.append(random_younger)
test_labels.append(1)
# The 5% of older individuals who did not experience side effects
random_older = randint(65,100)
test_samples.append(random_older)
test_labels.append(0)
for i in range(200):
# The 95% of younger individuals who did not experience side effects
random_younger = randint(13,64)
test_samples.append(random_younger)
test_labels.append(0)
# The 95% of older individuals who did experience side effects
random_older = randint(65,100)
test_samples.append(random_older)
test_labels.append(1)
test_labels = np.array(test_labels)
test_samples = np.array(test_samples)
test_labels, test_samples = shuffle(test_labels, test_samples)
scaled_test_samples = scaler.fit_transform(test_samples.reshape(-1,1))
最后利用model.predict()进行预测。在进行测试的时候,是不需要对数据的label值进行输入的,只是为了测试数据的结果,最后我们通过绘制一个混淆矩阵进行效果的分析。
对于这个函数,我们传入测试样本x,指定批处理大小,并指定在预测生成期间希望日志消息的详细程度。预测的输出与我们无关,因此我们将verbose设置为0表示无输出。请注意,与训练集和验证集不同,我们在推理阶段不会将测试集的标签传递给模型。
首先看一下打印的结果,可以知道是一个数组,每一个元素表示的是预测为0,和1的概率是多少。概率大的即为我们预测的结果。
for i in predictions:
print(i)
我们通过绘制混淆矩阵对最后的预测结果进行分析。
首先我们将预测的概率值转化为我们最后的结果,也就是是否有副作用,用0和1表示。
rounded_predictions = np.argmax(predictions, axis=-1) #返回预测结果更大的那个所在下标,即为我们所需要的预测结果
#引入混淆矩阵需要的包
%matplotlib inline
from sklearn.metrics import confusion_matrix
import itertools
import matplotlib.pyplot as plt
#构造混淆矩阵
cm = confusion_matrix(y_true=test_labels, y_pred=rounded_predictions)
# 绘制混淆矩阵的函数
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
"""
This function prints and plots the confusion matrix.
Normalization can be applied by setting `normalize=True`.
"""
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
print("Normalized confusion matrix")
else:
print('Confusion matrix, without normalization')
print(cm)
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, cm[i, j],
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
#进行绘制
cm_plot_labels = ['no_side_effects','had_side_effects']
plot_confusion_matrix(cm=cm, classes=cm_plot_labels, title='Confusion Matrix')
#将模型保存至在当前目录下的model/下。
model.save('models/medical_trial_model.h5')
模型的加载需要导入TensorFlow中的load_model
from tensorflow.keras.models import load_model
new_model = load_model('models/medical_trial_model.h5')
最后获得的新模型new_model 与原来的模型一样的结构。还包含模型中使用的优化器,模型的损失函数,以及模型的权值。这种保存模型的方式是保存信息最全面的一种方式。
仅保存模型的结构信息
我们只保存模型的架构。这不会保存模型权重、配置、优化器、损失或其他任何内容。这只会保存模型的体系结构。我们可以通过调用model.to_json()来实现这一点。这将把模型的架构保存为JSON字符串。如果我们把字符串打印出来,我们就能确切地看到它是什么样子。
json_string = model.to_json()
json_string
现在我们已经保存了它,我们可以从中创建一个新模型。首先,我们将从model_from_json函数导入所需的模型,然后我们可以加载模型架构。
from tensorflow.keras.models import model_from_json
model_architecture = model_from_json(json_string)
保存模型结构的另外一种方法是使用to_yaml()函数,然后通过model_from_yaml()加载保存到饿模型。使用的方式和上面的方法一样。
保存模型的权重
model.save_weights('models/my_model_weights.h5')
如果要加载模型的权值,就需要我新建立的模型与原来的模型的结构一样。
model2 = Sequential([
Dense(units=16, input_shape=(1,), activation='relu'),
Dense(units=32, activation='relu'),
Dense(units=2, activation='softmax')
])
model2.load_weights('models/my_model_weights.h5')