数据科学/机器学习/人工智能我爱编程tensorflow

TensorFlow Eager 教程

2018-07-25  本文已影响87人  布客飞龙

TensorFlow Eager 教程

来源:madalinabuzau/tensorflow-eager-tutorials

译者:飞龙

协议:CC BY-NC-SA 4.0

一、如何使用 TensorFlow Eager 构建简单的神经网络

大家好! 在本教程中,我们将使用 TensorFlow 的命令模式构建一个简单的前馈神经网络。 希望你会发现它很有用! 如果你对如何改进代码有任何建议,请告诉我。

教程步骤:

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/1-1.png

使用的版本:TensorFlow 1.7

第一步:导入有用的库并启用 Eager 模式

# 导入 TensorFlow 和 TensorFlow Eager
import tensorflow as tf
import tensorflow.contrib.eager as tfe

# 导入函数来生成玩具分类问题
from sklearn.datasets import make_moons
import numpy as np

# 导入绘图库
import matplotlib.pyplot as plt
%matplotlib inline

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution()

第二步:为二分类生成玩具数据集

我们将生成一个玩具数据集,来训练我们的网络。 我从sklearn中选择了make_moons函数。 我相信它对我们的任务来说是完美的,因为类不是线性可分的,因此神经网络将非常有用。

# 为分类生成玩具数据集
# X 是 n_samples x n_features 的矩阵,表示输入特征
# y 是 长度为 n_samples 的向量,表示我们的标签
X, y = make_moons(n_samples=100, noise=0.1, random_state=2018)

第三步:展示生成的数据集

plt.scatter(X[:,0], X[:,1], c=y, cmap=plt.cm.autumn)
plt.xlabel('First feature')
plt.ylabel('Second feature')
plt.title('Toy classification problem')
plt.show()

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/1-2.png

第四步:构建单隐层神经网络(线性 -> ReLU -> 线性输出)

我们的第一个试验是一个简单的神经网络,只有一个隐层。 使用 TensorFlow Eager 构建神经网络模型的最简单方法是使用类。 在初始化期间,你可以定义执行模型正向传播所需的层。

由于这是一个分类问题,我们将使用softmax交叉熵损失。 通常,我们必须对标签进行单热编码。 为避免这种情况,我们将使用稀疏softmax损失,它以原始标签作为输入。 无需进一步处理!

class simple_nn(tf.keras.Model):
    def __init__(self):
        super(simple_nn, self).__init__()
        """ 在这里定义正向传播期间
            使用的神经网络层
        """   
        # 隐层
        self.dense_layer = tf.layers.Dense(10, activation=tf.nn.relu)
        # 输出层,无激活函数
        self.output_layer = tf.layers.Dense(2, activation=None)
    
    def predict(self, input_data):
        """ 在神经网络上执行正向传播
            Args:
                input_data: 2D tensor of shape (n_samples, n_features).   
            Returns:
                logits: unnormalized predictions.
        """
        hidden_activations = self.dense_layer(input_data)
        logits = self.output_layer(hidden_activations)
        return logits
    
    def loss_fn(self, input_data, target):
        """ 定义训练期间使用的损失函数
        """
        logits = self.predict(input_data)
        loss = tf.losses.sparse_softmax_cross_entropy(labels=target, logits=logits)
        return loss
    
    def grads_fn(self, input_data, target):
        """ 在每个正向步骤中,
            动态计算损失值对模型参数的梯度
        """
        with tfe.GradientTape() as tape:
            loss = self.loss_fn(input_data, target)
        return tape.gradient(loss, self.variables)
    
    def fit(self, input_data, target, optimizer, num_epochs=500, verbose=50):
        """ 用于训练模型的函数,
            使用所选的优化器,执行所需数量的迭代
        """
        for i in range(num_epochs):
            grads = self.grads_fn(input_data, target)

第五步:使用梯度下降训练模型

使用反向传播来训练我们模型的变量。 随意玩玩学习率和迭代数。

X_tensor = tf.constant(X)
y_tensor = tf.constant(y)

optimizer = tf.train.GradientDescentOptimizer(5e-1)
model = simple_nn()
model.fit(X_tensor, y_tensor, optimizer, num_epochs=500, verbose=50)

optimizer.apply_gradients(zip(grads, self.variables))
if (i==0) | ((i+1)%verbose==0):
    print('Loss at epoch %d: %f' %(i+1, self.loss_fn(input_data, target).numpy()))

'''

Loss at epoch 1: 0.653288
Loss at epoch 50: 0.283921
Loss at epoch 100: 0.260529
Loss at epoch 150: 0.244092
Loss at epoch 200: 0.221653
Loss at epoch 250: 0.186211
Loss at epoch 300: 0.139418
Loss at epoch 350: 0.103654
Loss at epoch 400: 0.078874
Loss at epoch 450: 0.062550
Loss at epoch 500: 0.051096
'''

第六步:绘制决策边界

用于绘制模型决策边界的代码受到本教程的启发。

# 创建 mesh ,在其中绘制
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01),
                     np.arange(y_min, y_max, 0.01))

# 为每个样本 xx, yy 预测标签
Z = np.argmax(model.predict(tf.constant(np.c_[xx.ravel(), yy.ravel()])).numpy(), axis=1)

# 将结果放进彩色绘图
Z = Z.reshape(xx.shape)
fig = plt.figure()
plt.contourf(xx, yy, Z, cmap=plt.cm.autumn, alpha=0.8)

# 绘制我们的训练样本
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.autumn, edgecolors='k')
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.xlabel('First feature', fontsize=15)
plt.ylabel('Second feature', fontsize=15)
plt.title('Toy classification problem', fontsize=15)

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/1-3.png

二、在 Eager 模式中使用指标

大家好! 在本教程中,我们将学习如何使用各种指标来评估在 TensorFlow 中使用 Eager 模式时神经网络的表现。

我玩了很久 TensorFlow Eager 模式,我喜欢它。对我来说,与使用声明模式相比,API 看起来非常直观,现在一切看起来都更容易构建。 我现在发现的主要不便之处(我使用的是 1.7 版)是使用 Eager 模式时,tf.metrics还不兼容。 尽管如此,我已经构建了几个函数,可以帮助你评估网络的表现,同时仍然享受凭空构建网络的强大之处。

教程步骤:

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/1-1.png

我选择了三个案例:

多分类

对于此任务,我们将使用准确率,混淆矩阵和平均精度以及召回率,来评估我们模型的表现。

不平衡的二分类

当我们处理不平衡的数据集时,模型的准确率不是可靠的度量。 因此,我们将使用 ROC-AUC 分数,这似乎是一个更适合不平衡问题的指标。

回归

为了评估我们的回归模型的性能,我们将使用 R ^ 2 分数(确定系数)。

我相信这些案例的多样性足以帮助你进一步学习任何机器学习项目。 如果你希望我添加下面未遇到的任何额外指标,请告知我们,我会尽力在以后添加它们。 那么,让我们开始吧!

TensorFlow 版本 - 1.7

导入重要的库并开启 Eager 模式

# 导入 TensorFlow 和 TensorFlow Eager
import tensorflow as tf
import tensorflow.contrib.eager as tfe

# 导入函数来生成玩具分类问题
from sklearn.datasets import load_wine
from sklearn.datasets import make_classification
from sklearn.datasets import make_regression

# 为数据预处理导入 numpy
import numpy as np

# 导入绘图库
import matplotlib.pyplot as plt
%matplotlib inline

# 为降维导入 PCA
from sklearn.decomposition import PCA

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution()

第一部分:用于多分类的的数据集

wine_data = load_wine()

print('Type of data in the wine_data dictionary: ', list(wine_data.keys()))
'''
Type of data in the wine_data dictionary:  ['data', 'target', 'target_names', 'DESCR', 'feature_names']
'''

print('Number of classes: ', len(np.unique(wine_data.target)))
# Number of classes:  3

print('Distribution of our targets: ', np.unique(wine_data.target, return_counts=True)[1])
# Distribution of our targets:  [59 71 48]

print('Number of features in the dataset: ', wine_data.data.shape[1])
# Number of features in the dataset:  13

特征标准化

每个特征的比例变化很大,如下面的单元格所示。 为了加快训练速度,我们将每个特征标准化为零均值和单位标准差。 这个过程称为标准化,它对神经网络的收敛非常有帮助。

# 数据集标准化
wine_data.data = (wine_data.data - np.mean(wine_data.data, axis=0))/np.std(wine_data.data, axis=0)

print('Standard deviation of each feature after standardization: ', np.std(wine_data.data, axis=0))
# Standard deviation of each feature after standardization:  [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

数据可视化:使用 PCA 降到二维

我们将使用 PCA,仅用于可视化目的。 我们将使用所有 13 个特征来训练我们的神经网络。

让我们看看这三个类如何在 2D 空间中表示。

X_pca = PCA(n_components=2, random_state=2018).fit_transform(wine_data.data)

plt.scatter(X_pca[:,0], X_pca[:,1], c=wine_data.target, cmap=plt.cm.spring)
plt.xlabel('First PCA component', fontsize=15)
plt.ylabel('Second PCA component', fontsize=15)
plt.title('Multi-classification problem', fontsize=15)
plt.show()

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/2-1.png

好的,所以这些类看起来很容易分开。 顺便说一句,我实际上在特征标准化之前尝试使用 PCA,粉色和黄色类重叠。 通过在降维之前标准化特征,我们设法在它们之间获得了清晰的界限。

让我们使用 TensorFlow Eager API 构建双层神经网络

你可能已经注意到,使用 TensorFlow Eager 构建模型的最方便方法是使用类。 我认为,为模型使用类可以更容易地组织和添加新组件。 你只需定义初始化期间要使用的层,然后在预测期间使用它们。 它使得在预测阶段更容易阅读模型的架构。

class two_layer_nn(tf.keras.Model):
    def __init__(self, output_size=2, loss_type='cross-entropy'):
        super(two_layer_nn, self).__init__()
        """ 在这里定义正向传播期间
            使用的神经网络层     
            Args:
                output_size: int (default=2). 
                loss_type: string, 'cross-entropy' or 'regression' (default='cross-entropy')
        """   
        # 第一个隐层
        self.dense_1 = tf.layers.Dense(20, activation=tf.nn.relu)
        # 第二个隐层
        self.dense_2 = tf.layers.Dense(10, activation=tf.nn.relu)
        # 输出层,未缩放的对数概率
        self.dense_out = tf.layers.Dense(output_size, activation=None)     
        # 初始化损失类型
        self.loss_type = loss_type
    
    def predict(self, input_data):
        """ 在神经网络上执行正向传播     
            Args:
                input_data: 2D tensor of shape (n_samples, n_features).   
            Returns:
                logits: unnormalized predictions.
        """
        layer_1 = self.dense_1(input_data)
        layer_2 = self.dense_2(layer_1)
        logits = self.dense_out(layer_2)
        return logits
    
    def loss_fn(self, input_data, target):
        """ 定义训练期间使用的损失函数
        """
        preds = self.predict(input_data)
        if self.loss_type=='cross-entropy':
            loss = tf.losses.sparse_softmax_cross_entropy(labels=target, logits=preds)
        else:
            loss = tf.losses.mean_squared_error(target, preds)
        return loss
    
    def grads_fn(self, input_data, target):
        """ 在每个正向步骤中,
            动态计算损失值对模型参数的梯度
        """
        with tfe.GradientTape() as tape:
            loss = self.loss_fn(input_data, target)
        return tape.gradient(loss, self.variables)
    
    def fit(self, input_data, target, optimizer, num_epochs=500, 
            verbose=50, track_accuracy=True):
        """ 用于训练模型的函数,
            使用所选的优化器,执行所需数量的迭代
        """   
        
        if track_accuracy:
            # Initialize list to store the accuracy of the model
            self.hist_accuracy = []     
            # Initialize class to compute the accuracy metric
            accuracy = tfe.metrics.Accuracy()

        for i in range(num_epochs):
            # Take a step of gradient descent
            grads = self.grads_fn(input_data, target)
            optimizer.apply_gradients(zip(grads, self.variables))
            if track_accuracy:
                # Predict targets after taking a step of gradient descent
                logits = self.predict(X)
                preds = tf.argmax(logits, axis=1)
                # Compute the accuracy
                accuracy(preds, target)
                # Get the actual result and add it to our list
                self.hist_accuracy.append(accuracy.result())
                # Reset accuracy value (we don't want to track the running mean accuracy)
                accuracy.init_variables()

准确率指标

为了使用准确率指标评估模型的表现,我们将使用tfe.metrics.Accuracy类。 在批量训练模型时,此指标非常有用,因为它会在每次调用时计算批量的平均精度。 当我们在每个步骤中使用整个数据集训练模型时,我们将重置此指标,因为我们不希望它跟踪运行中的平均值。

# 创建输入特征和标签。将数据从 numpy 转换为张量
X = tf.constant(wine_data.data)
y = tf.constant(wine_data.target)

# 定义优化器
optimizer = tf.train.GradientDescentOptimizer(5e-1)

# 初始化模型
model = two_layer_nn(output_size=3)

# 在这里选择迭代数量
num_epochs = 5

# 使用梯度下降训练模型
model.fit(X, y, optimizer, num_epochs=num_epochs)

plt.plot(range(num_epochs), model.hist_accuracy);
plt.xlabel('Epoch number', fontsize=15);
plt.ylabel('Accuracy', fontsize=15);
plt.title('Training accuracy history', fontsize=15);

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/2-2.png

混淆矩阵

在训练完算法后展示混淆矩阵是一种很好的方式,可以全面了解网络表现。 TensorFlow 具有内置函数来计算混淆矩阵,幸运的是它与 Eager 模式兼容。 因此,让我们可视化此数据集的混淆矩阵。

# 获得整个数据集上的预测
logits = model.predict(X)
preds = tf.argmax(logits, axis=1)

# 打印混淆矩阵
conf_matrix = tf.confusion_matrix(y, preds, num_classes=3)
print('Confusion matrix: \n', conf_matrix.numpy())
'''
Confusion matrix: 
 [[56  3  0]
 [ 2 66  3]
 [ 0  1 47]]
'''

对角矩阵显示真正例,而矩阵的其它地方显示假正例。

精准率得分

上面计算的混淆矩阵使得计算平均精确率非常容易。 我将在下面实现一个函数,它会自动为你计算。 你还可以指定每个类的权重。 例如,由于某些原因,第二类的精确率可能对你来说更重要。

def precision(labels, predictions, weights=None):
    conf_matrix = tf.confusion_matrix(labels, predictions, num_classes=3)
    tp_and_fp = tf.reduce_sum(conf_matrix, axis=0)
    tp = tf.diag_part(conf_matrix)
    precision_scores = tp/(tp_and_fp)
    if weights:
        precision_score = tf.multiply(precision_scores, weights)/tf.reduce_sum(weights)
    else:
        precision_score = tf.reduce_mean(precision_scores)        
    return precision_score
    
precision_score = precision(y, preds, weights=None)
print('Average precision: ', precision_score.numpy())
# Average precision:  0.9494581280788177

召回率得分

平均召回率的计算与精确率非常相似。 我们不是对列进行求和,而是对行进行求和,来获得真正例和假负例的总数。

def recall(labels, predictions, weights=None):
    conf_matrix = tf.confusion_matrix(labels, predictions, num_classes=3)
    tp_and_fn = tf.reduce_sum(conf_matrix, axis=1)
    tp = tf.diag_part(conf_matrix)
    recall_scores = tp/(tp_and_fn)
    if weights:
        recall_score = tf.multiply(recall_scores, weights)/tf.reduce_sum(weights)
    else:
        recall_score = tf.reduce_mean(recall_scores)        
    return recall_score

recall_score = recall(y, preds, weights=None)
print('Average precision: ', recall_score.numpy())
# Average precision:  0.9526322246094269

第二部分:不平衡二分类

当你开始使用真实数据集时,你会很快发现大多数问题都是不平衡的。 例如,考虑到异常样本与正常样本的比例,异常检测问题严重不平衡。 在这些情况下,评估网络性能的更合适的指标是 ROC-AUC 得分。 那么,让我们构建我们的不平衡数据集并开始研究它!


XX,,  yy  ==  make_classificationmake_cla (n_samples=1000, n_features=2, n_informative=2, 
                           n_redundant=0, n_classes=2, n_clusters_per_class=1,
                           flip_y=0.1, class_sep=4, hypercube=False, 
                           shift=0.0, scale=1.0, random_state=2018)

# 减少标签为 1 的样本数
X = np.vstack([X[y==0], X[y==1][:50]])
y = np.hstack([y[y==0], y[y==1][:50]])

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/2-3.png

我们将使用相同的神经网络架构。 我们只需用num_classes = 2初始化模型,因为我们正在处理二分类问题。

# Numpy 数组变为张量
X = tf.constant(X)
y = tf.constant(y)

让我们将模型只训练几个迭代,来避免过拟合。

# 定义优化器
optimizer = tf.train.GradientDescentOptimizer(5e-1)

# 初始化模型
model = two_layer_nn(output_size=2)

# 在这里选择迭代数量
num_epochs = 5

# 使用梯度下降训练模型
model.fit(X, y, optimizer, num_epochs=num_epochs)

如何计算 ROC-AUC 得分

为了计算 ROC-AUC 得分,我们将使用tf.metric.auc的相同方法。 对于每个概率阈值,我们将计算真正例,真负例,假正例和假负例的数量。 在计算这些统计数据后,我们可以计算每个概率阈值的真正例率和真负例率。

为了近似 ROC 曲线下的面积,我们将使用黎曼和和梯形规则。 如果你想了解更多信息,请点击此处

ROC-AUC 函数

def roc_auc(labels, predictions, thresholds, get_fpr_tpr=True):
    tpr = []
    fpr = []
    for th in thresholds:    
        # 计算真正例数量
        tp_cases = tf.where((tf.greater_equal(predictions, th)) & 
                            (tf.equal(labels, 1)))
        tp = tf.size(tp_cases)
        
        # 计算真负例数量
        tn_cases = tf.where((tf.less(predictions, th)) & 
                            (tf.equal(labels, 0)))
        tn = tf.size(tn_cases)
        
        # 计算假正例数量
        fp_cases = tf.where((tf.greater_equal(predictions, th)) & 
                            (tf.equal(labels,0)))
        fp = tf.size(fp_cases)
        
        # 计算假负例数量
        fn_cases = tf.where((tf.less(predictions, th)) & 
                            (tf.equal(labels,1)))
        fn = tf.size(fn_cases)
        
        # 计算该阈值的真正例率
        tpr_th = tp/(tp + fn)
        
        # 计算该阈值的假正例率
        fpr_th = fp/(fp + tn)
        
        # 附加到整个真正例率列表
        tpr.append(tpr_th)
        
        # 附加到整个假正例率列表
        fpr.append(fpr_th)
        
    # 使用黎曼和和梯形法则,计算曲线下的近似面积
    auc_score = 0
    for i in range(0, len(thresholds)-1):
        height_step = tf.abs(fpr[i+1]-fpr[i])
        b1 = tpr[i]
        b2 = tpr[i+1]
        step_area = height_step*(b1+b2)/2
        auc_score += step_area
    return auc_score, fpr, tpr

为我们训练的模型计算 ROC-AUC 得分并绘制 ROC 曲线

# 阈值更多意味着曲线下的近似面积的粒度更高
# 随意尝试阈值的数量
num_thresholds = 1000 
thresholds = tf.lin_space(0.0, 1.0, num_thresholds).numpy()

# 将Softmax应用于我们的预测,因为模型的输出是非标准化的
# 选择我们的正类的预测(样本较少的类)
preds = tf.nn.softmax(model.predict(X))[:,1] 

# 计算 ROC-AUC 得分并获得每个阈值的 TPR 和 FPR
auc_score, fpr_list, tpr_list = roc_auc(y, preds, thresholds)

print('ROC-AUC score of the model: ', auc_score.numpy())
# ROC-AUC score of the model:  0.93493986

plt.plot(fpr_list, tpr_list, label='AUC score: %.2f' %auc_score);
plt.xlabel('False Positive Rate', fontsize=15);
plt.ylabel('True Positive Rate', fontsize=15);
plt.title('ROC curve');
plt.legend(fontsize=15);

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/2-4.png

第三部分:用于回归的数据集

我们最终的数据集为简单的回归任务而创建。 在前两个问题中,网络的输出表示样本所属的类。这里网络的输出是连续的,是一个实数。

我们的输入数据集仅包含一个特征,以便使绘图保持简单。 标签y是实数向量。

让我们创建我们的玩具数据集!

X, y = make_regression(n_samples=100, n_features=1, n_informative=1, noise=30, 
                       random_state=2018)

展示输入特征和标签

为了更好地了解我们正在处理的问题,让我们绘制标签和输入特征。


pltplt..scatterscatter((XX,,  yy););
 pltplt..xlabelxlabel(('Input''Input',,  fontsizefontsize=15);
plt.ylabel('Target', fontsize=15);
plt.title('Toy regression problem', fontsize=15);

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/2-5.png


# Numpy 数组转为张量
X = tf.constant(X)
y = tf.constant(y)
y = tf.reshape(y, [-1,1]) # 从行向量变为列向量

用于回归任务的神经网络

我们可以重复使用上面创建的双层神经网络。 由于我们只需要预测一个实数,因此网络的输出大小为 1。

我们必须重新定义我们的损失函数,因为我们无法继续使用softmax交叉熵损失。 相反,我们将使用均方误差损失函数。 我们还将定义一个新的优化器,其学习速率比前一个更小。

随意调整迭代的数量。

# 定义优化器
optimizer = tf.train.GradientDescentOptimizer(1e-4)

# 初始化模型
model = two_layer_nn(output_size=1, loss_type='regression')

# 选择迭代数量
num_epochs = 300

# 使用梯度下降训练模型
model.fit(X, y, optimizer, num_epochs=num_epochs, 
          track_accuracy=False)

计算 R^2 得分(决定系数)

如果你曾经处理过回归问题,那么你可能已经听说过这个得分。

这个指标计算输入特征与目标之间的变异百分率,由我们的模型解释。R^2 得分的值范围介于 0 和 1 之间。R^2 得分为 1 意味着该模型可以进行完美的预测。 始终预测目标y的平均值,R^2 得分为 0。

R^2 可能为的负值。 在这种情况下,这意味着比起总是预测目标变量的平均值的模型,我们的模型做出更糟糕的预测。

由于此度量标准在 TensorFlow 1.5 中不易获得,因此在 Eager 模式下运行时,我在下面的单元格中为它创建了一个小函数。

# 计算 R^2 得分
def r2(labels, predictions):
    mean_labels = tf.reduce_mean(labels)
    total_sum_squares = tf.reduce_sum((labels-mean_labels)**2)
    residual_sum_squares = tf.reduce_sum((labels-predictions)**2)
    r2_score = 1 - residual_sum_squares/total_sum_squares
    return r2_score

preds = model.predict(X)
r2_score = r2(y, preds)
print('R2 score: ', r2_score.numpy())
# R2 score:  0.8249999999348803

展示最佳拟合直线

为了可视化我们的神经网络的最佳拟合直线,我们简单地选取X_minX_max之间的线性空间。

# 创建 X_min 和 X_max 之间的数据点来显示最佳拟合直线
X_best_fit = np.arange(X.numpy().min(), X.numpy().max(), 0.001)[:,None]

# X_best_fit 的预测
preds_best_fit = model.predict(X_best_fit)

plt.scatter(X.numpy(), y.numpy()); # 原始数据点
plt.plot(X_best_fit, preds_best_fit.numpy(), color='k',
         linewidth=6, label='$R^2$ score: %.2f' %r2_score) # Our predictions
plt.xlabel('Input', fontsize=15);
plt.ylabel('Target', fontsize=15);
plt.title('Toy regression problem', fontsize=15);
plt.legend(fontsize=15);

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/2-6.png

三、如何保存和恢复训练模型

滚动浏览reddit.com/r/learnmachinelearning的帖子后,我意识到机器学习项目的主要瓶颈,出现于数据输入流水线和模型的最后阶段,你必须保存模型和 对新数据做出预测。 所以我认为制作一个简单直接的教程,向你展示如何保存和恢复使用 Tensorflow Eager 构建的模型会很有用。

教程的流程图

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/3-1.png

导入有用的库

# 导入 TensorFlow 和 TensorFlow Eager
import tensorflow as tf
import tensorflow.contrib.eager as tfe

# 导入函数来生成玩具分类问题
from sklearn.datasets import make_moons

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution()

第一部分:为二分类构建简单的神经网络

class simple_nn(tf.keras.Model):
    def __init__(self):
        super(simple_nn, self).__init__()
        """ 在这里定义正向传播期间
            使用的神经网络层
        """   
        # 隐层
        self.dense_layer = tf.layers.Dense(10, activation=tf.nn.relu)
        # 输出层,无激活
        self.output_layer = tf.layers.Dense(2, activation=None)
    
    def predict(self, input_data):
        """ 在神经网络上执行正向传播
            Args:
                input_data: 2D tensor of shape (n_samples, n_features).   
            Returns:
                logits: unnormalized predictions.
        """
        hidden_activations = self.dense_layer(input_data)
        logits = self.output_layer(hidden_activations)
        return logits
    
    def loss_fn(self, input_data, target):
        """ 定义训练期间使用的损失函数
        """
        logits = self.predict(input_data)
        loss = tf.losses.sparse_softmax_cross_entropy(labels=target, logits=logits)
        return loss
    
    def grads_fn(self, input_data, target):
        """ 在每个正向步骤中,
            动态计算损失值对模型参数的梯度
        """
        with tfe.GradientTape() as tape:
            loss = self.loss_fn(input_data, target)
        return tape.gradient(loss, self.variables)
    
    def fit(self, input_data, target, optimizer, num_epochs=500, verbose=50):
        """ 用于训练模型的函数,
            使用所选的优化器,执行所需数量的迭代
        """
        for i in range(num_epochs):
            grads = self.grads_fn(input_data, target)
            optimizer.apply_gradients(zip(grads, self.variables))
            if (i==0) | ((i+1)%verbose==0):
                print('Loss at epoch %d: %f' %(i+1, self.loss_fn(input_data, target).numpy()))

第二部分:训练模型

# 为分类生成玩具数据集
# X 是 n_samples x n_features 的矩阵,表示输入特征
# y 是 长度为 n_samples 的向量,表示我们的标签
X, y = make_moons(n_samples=100, noise=0.1, random_state=2018)
X_train, y_train = tf.constant(X[:80,:]), tf.constant(y[:80])
X_test, y_test = tf.constant(X[80:,:]), tf.constant(y[80:])

optimizer = tf.train.GradientDescentOptimizer(5e-1)
model = simple_nn()
model.fit(X_train, y_train, optimizer, num_epochs=500, verbose=50)
'''
Loss at epoch 1: 0.658276
Loss at epoch 50: 0.302146
Loss at epoch 100: 0.268594
Loss at epoch 150: 0.247425
Loss at epoch 200: 0.229143
Loss at epoch 250: 0.197839
Loss at epoch 300: 0.143365
Loss at epoch 350: 0.098039
Loss at epoch 400: 0.070781
Loss at epoch 450: 0.053753
Loss at epoch 500: 0.042401
'''

第三部分:保存训练模型

# 指定检查点目录
checkpoint_directory = 'models_checkpoints/SimpleNN/'
# 创建模型检查点
checkpoint = tfe.Checkpoint(optimizer=optimizer,
                            model=model,
                            optimizer_step=tf.train.get_or_create_global_step())

# 保存训练模型
checkpoint.save(file_prefix=checkpoint_directory)
# 'models_checkpoints/SimpleNN/-1'

第四部分:恢复训练模型

# 重新初始化模型实例
model = simple_nn()
optimizer = tf.train.GradientDescentOptimizer(5e-1)

# 指定检查点目录
checkpoint_directory = 'models_checkpoints/SimpleNN/'
# 创建模型检查点
checkpoint = tfe.Checkpoint(optimizer=optimizer,
                            model=model,
                            optimizer_step=tf.train.get_or_create_global_step())

# 从最近的检查点恢复模型
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_directory))
# <tensorflow.contrib.eager.python.checkpointable_utils.CheckpointLoadStatus at 0x7fcfd47d2048>

第五部分:检查模型是否正确恢复

model.fit(X_train, y_train, optimizer, num_epochs=1)
# Loss at epoch 1: 0.042220

损失似乎与我们在之前训练的最后一个迭代中获得的损失一致!

第六部分:对新数据做预测

logits_test = model.predict(X_test)

print(logits_test)
'''
tf.Tensor(
[[ 1.54352813 -0.83117302]
 [-1.60523365  2.82397487]
 [ 2.87589525 -1.36463485]
 [-1.39461001  2.62404279]
 [ 0.82305161 -0.55651397]
 [ 3.53674391 -2.55593046]
 [-2.97344627  3.46589599]
 [-1.69372442  2.95660466]
 [-1.43226137  2.65357974]
 [ 3.11479995 -1.31765645]
 [-0.65841567  1.60468631]
 [-2.27454367  3.60553595]
 [-1.50170912  2.74410115]
 [ 0.76261479 -0.44574208]
 [ 2.34516959 -1.6859307 ]
 [ 1.92181942 -1.63766352]
 [ 4.06047684 -3.03988941]
 [ 1.00252324 -0.78900484]
 [ 2.79802993 -2.2139734 ]
 [-1.43933035  2.68037059]], shape=(20, 2), dtype=float64)
'''

四、文本序列到 TFRecords

大家好! 在本教程中,我将向你展示如何将原始文本数据解析为 TFRecords。 我知道很多人都卡在输入处理流水线,尤其是当你开始着手自己的个人项目时。 所以我真的希望它对你们任何人都有用!

教程的流程图

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/4-1.png

虚拟的IMDB文本数据

在实践中,我从斯坦福大学提供的大型电影评论数据集中选择了一些数据样本。

在这里导入有用的库

from nltk.tokenize import word_tokenize
import tensorflow as tf
import pandas as pd
import pickle
import random
import glob
import nltk
import re

try:
    nltk.data.find('tokenizers/punkt')
except LookupError:
    nltk.download('punkt')

将数据解析为 TFRecords

def imdb2tfrecords(path_data='datasets/dummy_text/', min_word_frequency=5,
                   max_words_review=700):
    '''
    这个脚本处理数据
    并将其保存为默认的 TensorFlow 文件格式:tfrecords。
    
    Args:
        path_data: the path where the imdb data is stored.
        min_word_frequency: the minimum frequency of a word, to keep it
                            in the vocabulary.
        max_words_review: the maximum number of words allowed in a review.
    '''
    # 获取正面/负面评论的文件名 
    pos_files = glob.glob(path_data + 'pos/*')
    neg_files = glob.glob(path_data + 'neg/*')

    # 连接正负评论的文件名
    filenames = pos_files + neg_files
    
    # 列出数据集中的所有评论
    reviews = [open(filenames[i],'r').read() for i in range(len(filenames))]
    
    # 移除 HTML 标签
    reviews = [re.sub(r'<[^>]+>', ' ', review) for review in reviews]
        
    # 将每个评论分词
    reviews = [word_tokenize(review) for review in reviews]
    
    # 计算每个评论的的长度
    len_reviews = [len(review) for review in reviews]

    # 展开嵌套列表
    reviews = [word for review in reviews for word in review]
    
    # 计算每个单词的频率
    word_frequency = pd.value_counts(reviews)
    
    # 仅仅保留频率高于最小值的单词
    vocabulary = word_frequency[word_frequency>=min_word_frequency].index.tolist()
    
    # 添加未知,起始和终止记号
    extra_tokens = ['Unknown_token', 'End_token']
    vocabulary += extra_tokens
    
    # 创建 word2idx 词典
    word2idx = {vocabulary[i]: i for i in range(len(vocabulary))}
    
    # 将单词的词汇表写到磁盘
    pickle.dump(word2idx, open(path_data + 'word2idx.pkl', 'wb'))
        
    def text2tfrecords(filenames, writer, vocabulary, word2idx,
                       max_words_review):
        '''
        用于将每个评论解析为部分,并作为 tfrecord 写入磁盘的函数。
        
        Args:
            filenames: the paths of the review files.
            writer: the writer object for tfrecords.
            vocabulary: list with all the words included in the vocabulary.
            word2idx: dictionary of words and their corresponding indexes.
        '''
        # 打乱 filenames
        random.shuffle(filenames)
        for filename in filenames:
            review = open(filename, 'r').read()
            review = re.sub(r'<[^>]+>', ' ', review)
            review = word_tokenize(review)
            # 将 review 归约为最大单词
            review = review[-max_words_review:]
            # 将单词替换为来自 word2idx 的等效索引
            review = [word2idx[word] if word in vocabulary else 
                      word2idx['Unknown_token'] for word in review]
            indexed_review = review + [word2idx['End_token']]
            sequence_length = len(indexed_review)
            target = 1 if filename.split('/')[-2]=='pos' else 0
            # Create a Sequence Example to store our data in
            ex = tf.train.SequenceExample()
            # 向我们的示例添加非顺序特性
            ex.context.feature['sequence_length'].int64_list.value.append(sequence_length)
            ex.context.feature['target'].int64_list.value.append(target)
            # 添加顺序特征
            token_indexes = ex.feature_lists.feature_list['token_indexes']
            for token_index in indexed_review:
                token_indexes.feature.add().int64_list.value.append(token_index)
            writer.write(ex.SerializeToString())
    
    ##########################################################################     
    # Write data to tfrecords.This might take a while.
    ##########################################################################
    writer = tf.python_io.TFRecordWriter(path_data + 'dummy.tfrecords')
    text2tfrecords(filenames, writer, vocabulary, word2idx, 
                   max_words_review)

imdb2tfrecords(path_data='datasets/dummy_text/')

将 TFRecords 解析为 TF 张量

def parse_imdb_sequence(record):
    '''
    解析 imdb tfrecords 的脚本
    
    Returns:
        token_indexes: sequence of token indexes present in the review.
        target: the target of the movie review.
        sequence_length: the length of the sequence.
    '''
    context_features = {
        'sequence_length': tf.FixedLenFeature([], dtype=tf.int64),
        'target': tf.FixedLenFeature([], dtype=tf.int64),
        }
    sequence_features = {
        'token_indexes': tf.FixedLenSequenceFeature([], dtype=tf.int64),
        }
    context_parsed, sequence_parsed = tf.parse_single_sequence_example(record, 
        context_features=context_features, sequence_features=sequence_features)
        
    return (sequence_parsed['token_indexes'], context_parsed['target'],
            context_parsed['sequence_length'])

如果你希望我在本教程中添加任何内容,请告诉我,我将很乐意进一步改善它。

五、如何将原始图片数据转换为 TFRecords

大家好! 与前一个教程一样,本教程的重点是自动化数据输入流水线。

大多数情况下,我们的数据集太大而无法读取到内存,因此我们必须准备一个流水线,用于从硬盘批量读取数据。 我总是将我的原始数据(文本,图像,表格)处理为 TFRecords,因为它让我的生活变得更加容易。

教程的流程图

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/5-1.png

本教程将包含以下部分:

所以废话不多说,让我们开始吧。

导入有用的库

import tensorflow as tf
import tensorflow.contrib.eager as tfe
import glob

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution()

将原始数据转换为 TFRecords

对于此任务,我们将使用 FER2013 数据集中的一些图像,你可以在datasets/dummy_images文件夹中找到这些图像。 情感标签可以在图像的文件名中找到。 例如,图片id7_3.jpg情感标签为 3,其对应于状态'Happy'(快乐),如下面的字典中所示。

# 获取每个情感的下标的含义
emotion_cat = {0:'Angry', 1:'Disgust', 2:'Fear', 3:'Happy', 4:'Sad', 5:'Surprise', 6:'Neutral'}

def img2tfrecords(path_data='datasets/dummy_images/', image_format='jpeg'):
    ''' 用于将原始图像以及它们标签转换为 TFRecords 的函数
        辅助函数的原始的源代码:https://goo.gl/jEhp2B
        
        Args:
            path_data: the location of the raw images
            image_format: the format of the raw images (e.g. 'png', 'jpeg')
    '''
    
    def _int64_feature(value):
        '''辅助函数'''
        return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
    
    def _bytes_feature(value):
        '''辅助函数'''
        return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
    
    # 获取目录中每个图像的文件名
    filenames = glob.glob(path_data + '*' + image_format)
    
    # 创建 TFRecordWriter
    writer = tf.python_io.TFRecordWriter(path_data + 'dummy.tfrecords')
    
    # 遍历每个图像,并将其写到 TFrecords 文件中
    for filename in filenames:
        # 读取原始图像
        img = tf.read_file(filename).numpy()
        # 从文件名中解析它的标签
        label = int(filename.split('_')[-1].split('.')[0])
        # 创建样本(图像,标签)
        example = tf.train.Example(features=tf.train.Features(feature={
            'label': _int64_feature(label),
            'image': _bytes_feature(img)}))
        # 向 TFRecords 写出序列化样本
        writer.write(example.SerializeToString())

# 将原始数据转换为 TFRecords
img2tfrecords()

将 TFRecords 解析为 TF 张量

def parser(record):
    '''解析 TFRecords 样本的函数'''
    
    # 定义你想要解析的特征
    features = {'image': tf.FixedLenFeature((), tf.string),
                'label': tf.FixedLenFeature((), tf.int64)}
    
    # 解析样本
    parsed = tf.parse_single_example(record, features)

    # 解码图像
    img = tf.image.decode_image(parsed['image'])
   
    return img, parsed['label']

如果你希望我在本教程中添加任何内容,请告诉我,我将很乐意进一步改善它。

六、如何使用 TensorFlow Eager 从 TFRecords 批量读取数据

大家好,本教程再次关注输入流水线。 这很简单,但我记得当我第一次开始批量读取数据时,我陷入了相当多的细节,所以我想我可能会在这里分享我的方法。 我真的希望它对你们中的一些人有用。

教程的流程图:

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/6-1.png

我们将研究两种情况:

两种情况的数据都存储为 TFRecords。 你可以查看教程的第四和第五章,了解如何将原始数转换为 TFRecords。

那么,让我们直接开始编程!

导入有用的库

# 导入数据可视化库
import matplotlib.pyplot as plt

# 使绘图内嵌在笔记本中
%matplotlib inline

# 导入 TensorFlow 和 TensorFlow Eager
import tensorflow as tf
import tensorflow.contrib.eager as tfe

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution()

第一部分:读取可变序列长度的数据

本教程的第一部分向你介绍如何读取不同长度的输入数据。 在我们的例子中,我们使用了大型电影数据库中的虚拟 IMDB 评论。 你可以想象,每个评论都有不同的单词数。 因此,当我们读取一批数据时,我们将序列填充到批次中的最大序列长度。

为了了解我如何获得单词索引序列,以及标签和序列长度,请参阅第四章。

创建函数来解析每个 TFRecord

def parse_imdb_sequence(record):
    '''
    用于解析 imdb tfrecords 的脚本
    
    Returns:
        token_indexes: sequence of token indexes present in the review.
        target: the target of the movie review.
        sequence_length: the length of the sequence.
    '''
    context_features = {
        'sequence_length': tf.FixedLenFeature([], dtype=tf.int64),
        'target': tf.FixedLenFeature([], dtype=tf.int64),
        }
    sequence_features = {
        'token_indexes': tf.FixedLenSequenceFeature([], dtype=tf.int64),
        }
    context_parsed, sequence_parsed = tf.parse_single_sequence_example(record, 
        context_features=context_features, sequence_features=sequence_features)
        
    return (sequence_parsed['token_indexes'], context_parsed['target'],
            context_parsed['sequence_length'])

创建数据集迭代器

正如你在上面的函数中所看到的,在解析每个记录之后,我们返回一系列单词索引,评论标签和序列长度。 在padded_batch方法中,我们只填充记录的第一个元素:单词索引的序列。 在每个示例中,标签和序列长度不需要填充,因为它们只是单个数字。 因此,padded_shapes将是:

# 选取批量大小
batch_size = 2

# 从 TFRecords 创建数据集
dataset = tf.data.TFRecordDataset('datasets/dummy_text/dummy.tfrecords')
dataset = dataset.map(parse_imdb_sequence).shuffle(buffer_size=10000)
dataset = dataset.padded_batch(batch_size, padded_shapes=([None],[],[]))

遍历数据一次

for review, target, sequence_length in tfe.Iterator(dataset):
    print(target)
'''
tf.Tensor([0 1], shape=(2,), dtype=int64)
tf.Tensor([1 0], shape=(2,), dtype=int64)
tf.Tensor([0 1], shape=(2,), dtype=int64)
'''

for review, target, sequence_length in tfe.Iterator(dataset):
    print(review.shape)
'''
(2, 145)
(2, 139)
(2, 171)
'''

for review, target, sequence_length in tfe.Iterator(dataset):
    print(sequence_length)
'''
tf.Tensor([137 151], shape=(2,), dtype=int64)
tf.Tensor([139 171], shape=(2,), dtype=int64)
tf.Tensor([145 124], shape=(2,), dtype=int64)
'''

第二部分:批量读取图像(以及它们的标签)

在本教程的第二部分中,我们将通过批量读取图像,将存储为 TFRecords 的图像可视化。 这些图像是 FER2013 数据集中的一个小型子样本。

创建函数来解析每个记录并解码图片

def parser(record):
    '''
    解析 TFRecords 样本的函数
    
    Returns:
        img: decoded image.
        label: the corresponding label of the image.         
    '''
    
    # 定义你想要解析的特征
    features = {'image': tf.FixedLenFeature((), tf.string),
                'label': tf.FixedLenFeature((), tf.int64)}
    
    # 解析样本
    parsed = tf.parse_single_example(record, features)

    # 解码图像
    img = tf.image.decode_image(parsed['image'])
   
    return img, parsed['label']

创建数据集迭代器

# 选取批量大小
batch_size = 5

# 从 TFRecords 创建数据集
dataset = tf.data.TFRecordDataset('datasets/dummy_images/dummy.tfrecords')
dataset = dataset.map(parser).shuffle(buffer_size=10000)
dataset = dataset.batch(batch_size)

遍历数据集一次。展示图像。

# Dictionary that stores the correspondence between integer labels and the emotions
emotion_cat = {0:'Angry', 1:'Disgust', 2:'Fear', 3:'Happy', 4:'Sad', 5:'Surprise', 6:'Neutral'}

# 遍历数据集一次
for image, label in tfe.Iterator(dataset):
    # 为每个图像批量创建子图
    f, axarr = plt.subplots(1, int(image.shape[0]), figsize=(14, 6))
    # 绘制图像
    for i in range(image.shape[0]):
        axarr[i].imshow(image[i,:,:,0], cmap='gray')
        axarr[i].set_title('Emotion: %s' %emotion_cat[label[i].numpy()])

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/6-2.png

如果你希望我在本教程中添加任何内容,请与我们联系。 我会尽力添加它!

七、使用 TensorFlow Eager 构建用于情感识别的卷积神经网络(CNN)

对于深度学习,我最喜欢的部分之一就是我可以解决一些问题,其中我自己可以测试神经网络。 到目前为止,我建立的最有趣的神经网络是用于情感识别的 CNN。 我已经设法通过网络传递我的网络摄像头视频,并实时预测了我的情绪(使用 GTX-1070)。 相当容易上瘾!

因此,如果你想将工作与乐趣结合起来,那么你一定要仔细阅读本教程。 另外,这是熟悉 Eager API 的好方法!

教程步骤

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/7-1.png

导入有用的库

# 导入 TensorFlow 和 TensorFlow Eager
import tensorflow as tf
import tensorflow.contrib.eager as tfe

# 导入函数来生成玩具分类问题
from sklearn.datasets import make_moons
import numpy as np

# 导入绘图库
import matplotlib.pyplot as plt
%matplotlib inline

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution()

下载数据集

为了训练我们的 CNN,我们将使用 Kaggle 上提供的 FER2013 数据集。 你必须在他们的平台上自己下载数据集,遗憾的是我无法公开分享数据。 尽管如此,数据集只有 96.4 MB,因此你应该能够立即下载它。 你可以在这里下载。

下载完数据后,将其解压缩并放入名为datasets的文件夹中,这样你就不必对下面的代码进行任何修改。

好的,让我们开始探索性数据分析!

探索性数据分析

在构建任何机器学习模型之前,建议对数据集进行探索性数据分析。 这使你有机会发现数据集中的任何缺陷,如类之间的强烈不平衡,低质量图像等。

我发现机器学习项目中出现的大多数错误,都是由于数据处理不正确造成的。 如果你在发现模型没有用后才开始调查数据集,那么找到这些错误会更加困难。

所以,我给你的建议是:在构建任何模型之前总是分析数据。

# 读取输入数据。假设已经解压了数据集,并放入名为 data 的文件夹中。
path_data = 'datasets/fer2013/fer2013.csv'
data = pd.read_csv(path_data)

print('Number of samples in the dataset: ', data.shape[0])
# Number of samples in the dataset:  35887

# 查看前五行
data.head(5)
emotion pixels Usage
0 0 70 80 82 72 58 58 60 63 54 58 60 48 89 115 121... Training
1 0 151 150 147 155 148 133 111 140 170 174 182 15... Training
2 2 231 212 156 164 174 138 161 173 182 200 106 38... Training
3 4 24 32 36 30 32 23 19 20 30 41 21 22 32 34 21 1... Training
4 6 4 0 0 0 0 0 0 0 0 0 0 0 3 15 23 28 48 50 58 84... Training
# 获取每个表情的含义
emotion_cat = {0:'Angry', 1:'Disgust', 2:'Fear', 3:'Happy', 4:'Sad', 5:'Surprise', 6:'Neutral'}

# 查看标签分布(检查不平衡)
target_counts = data['emotion'].value_counts().reset_index(drop=False)
target_counts.columns = ['emotion', 'number_samples']
target_counts['emotion'] = target_counts['emotion'].map(emotion_cat)
target_counts
emotion number_samples
0 Happy 8989
1 Neutral 6198
2 Sad 6077
3 Fear 5121
4 Angry 4953
5 Surprise 4002
6 Disgust 547

如你所见,数据集非常不平衡。 特别是对于情绪Disgust。 这将使这个类的训练更加困难,因为网络将有更少的机会来学习这种表情的表示。

在我们训练网络之后,稍后我们会看到这是否会严重影响我们网络的训练。

我们来看看一些图片!

图像当前表示为整数的字符串,每个整数表示一个像素的强度。 我们将处理字符串。将其表示为整数列表。

# 将图像从字符串换换位整数列表
data['pixels'] = data['pixels'].apply(lambda x: [int(pixel) for pixel in x.split()])

# 修改这里的种子来查看其它图像
random_seed = 2

# 随机选择十个图像
data_sample = data.sample(10, random_state=random_seed)

# 为图像创建子图
f, axarr = plt.subplots(2, 5, figsize=(20, 10))

# 绘制图像
i, j = 0, 0
for idx, row in data_sample.iterrows():
    img = np.array(row['pixels']).reshape(48,48)
    axarr[i,j].imshow(img, cmap='gray')
    axarr[i,j].set_title(emotion_cat[row['emotion']])
    if j==4:
        i += 1
        j = 0
    else:
        j += 1

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/7-2.png

将数据集拆分为训练/开发,并按最大值标准化图像


data_traindata_tra  = data[data['Usage']=='Training']
size_train = data_train.shape[0]
print('Number samples in the training dataset: ', size_train)

data_dev = data[data['Usage']!='Training']
size_dev = data_dev.shape[0]
print('Number samples in the development dataset: ', size_dev)
'''
Number samples in the training dataset:  28709
Number samples in the development dataset:  7178
'''

# 获取训练输入和标签
X_train, y_train = data_train['pixels'].tolist(), data_train['emotion'].as_matrix()
# 将图像形状修改为 4D(样本数,宽,高,通道数)
X_train = np.array(X_train, dtype='float32').reshape(-1,48,48,1)
# 使用最大值标准化图像(最大像素密度为 255)
X_train = X_train/255.0

# 获取开发输入和标签
X_dev, y_dev = data_dev['pixels'].tolist(), data_dev['emotion'].as_matrix()
# 将图像形状修改为 4D(样本数,宽,高,通道数)
X_dev = np.array(X_dev, dtype='float32').reshape(-1,48,48,1)
# 使用最大值标准化图像
X_dev = X_dev/255.0

使用tf.data.Dataset API

为了准备我们的数据集用作 CNN 的输入,我们将使用tf.data.Dataset API,将我们刚刚创建的 numpy 数组转换为 TF 张量。 由于此数据集比以前教程中的数据集大得多,因此我们实际上必须将数据批量提供给模型。

通常,为了提高计算效率,你可以选择与内存一样大的批量。 但是,根据我的经验,如果我在训练期间使用较小的批量,我会在测试数据上获得更好的结果。 随意调整批量大小,看看你是否得到了与我相同的结论。

# 随意调整批量大小
# 通常较小的批量大小在测试集上获取更好的结果
batch_size = 64
training_data = tf.data.Dataset.from_tensor_slices((X_train, y_train[:,None])).batch(batch_size)
eval_data = tf.data.Dataset.from_tensor_slices((X_dev, y_dev[:,None])).batch(batch_size)

在 Eager 模式下创建 CNN 模型

CNN 架构在下面的单元格中创建。 如你所见,EmotionRecognitionCNN类继承自tf.keras.Model类,因为我们想要跟踪包含任何可训练参数的层(例如卷积的权重,批量标准化层的平均值)。 这使我们易于保存这些变量,然后在我们想要继续训练网络时将其恢复。

这个 CNN 的原始架构可以在这里找到(使用 keras 构建)。 我认为如果你开始使用比 ResNet 更简单的架构,那将非常有用。 对于这个网络规模,它的效果非常好。

你可以使用它,添加更多的层,增加层的数量,过滤器等。看看你是否可以获得更好的结果。

有一点可以肯定的是,dropout 越高,网络效果越好。

class EmotionRecognitionCNN(tf.keras.Model):
    
    def __init__(self, num_classes, device='cpu:0', checkpoint_directory=None):
        ''' 定义在正向传播期间使用的参数化层,你要在它上面运行计算的设备,以及检查点目录。
            
            Args:
                num_classes: the number of labels in the network.
                device: string, 'cpu:n' or 'gpu:n' (n can vary). Default, 'cpu:0'.
                checkpoint_directory: the directory where you would like to save or 
                                      restore a model.
        ''' 
        super(EmotionRecognitionCNN, self).__init__()
        
        # 初始化层
        self.conv1 = tf.layers.Conv2D(16, 5, padding='same', activation=None)
        self.batch1 = tf.layers.BatchNormalization()
        self.conv2 = tf.layers.Conv2D(16, 5, 2, padding='same', activation=None)
        self.batch2 = tf.layers.BatchNormalization()
        self.conv3 = tf.layers.Conv2D(32, 5, padding='same', activation=None)
        self.batch3 = tf.layers.BatchNormalization()
        self.conv4 = tf.layers.Conv2D(32, 5, 2, padding='same', activation=None)
        self.batch4 = tf.layers.BatchNormalization()
        self.conv5 = tf.layers.Conv2D(64, 3, padding='same', activation=None)
        self.batch5 = tf.layers.BatchNormalization()
        self.conv6 = tf.layers.Conv2D(64, 3, 2, padding='same', activation=None)
        self.batch6 = tf.layers.BatchNormalization()
        self.conv7 = tf.layers.Conv2D(64, 1, padding='same', activation=None)
        self.batch7 = tf.layers.BatchNormalization()
        self.conv8 = tf.layers.Conv2D(128, 3, 2, padding='same', activation=None)
        self.batch8 = tf.keras.layers.BatchNormalization()
        self.conv9 = tf.layers.Conv2D(256, 1, padding='same', activation=None)
        self.batch9 = tf.keras.layers.BatchNormalization()
        self.conv10 = tf.layers.Conv2D(128, 3, 2, padding='same', activation=None)
        self.conv11 = tf.layers.Conv2D(256, 1, padding='same', activation=None)
        self.batch11 = tf.layers.BatchNormalization()
        self.conv12 = tf.layers.Conv2D(num_classes, 3, 2, padding='same', activation=None)
        
        # 定义设备
        self.device = device
        
        # 定义检查点目录
        self.checkpoint_directory = checkpoint_directory
       
    def predict(self, images, training):
        """ 根据输入样本预测每个类的概率。
            
            Args:
                images: 4D tensor. Either an image or a batch of images.
                training: Boolean. Either the network is predicting in
                          training mode or not.
        """
        x = self.conv1(images)
        x = self.batch1(x, training=training)
        x = self.conv2(x)
        x = self.batch2(x, training=training)
        x = tf.nn.relu(x)
        x = tf.layers.dropout(x, rate=0.4, training=training)
        x = self.conv3(x)
        x = self.batch3(x, training=training)
        x = self.conv4(x)
        x = self.batch4(x, training=training)
        x = tf.nn.relu(x)
        x = tf.layers.dropout(x, rate=0.3, training=training)
        x = self.conv5(x)
        x = self.batch5(x, training=training)
        x = self.conv6(x)
        x = self.batch6(x, training=training)
        x = tf.nn.relu(x)
        x = tf.layers.dropout(x, rate=0.3, training=training)
        x = self.conv7(x)
        x = self.batch7(x, training=training)
        x = self.conv8(x)
        x = self.batch8(x, training=training)
        x = tf.nn.relu(x)
        x = tf.layers.dropout(x, rate=0.3, training=training)
        x = self.conv9(x)
        x = self.batch9(x, training=training)
        x = self.conv10(x)
        x = self.conv11(x)
        x = self.batch11(x, training=training)
        x = self.conv12(x)
        return tf.layers.flatten(x)
    
    def loss_fn(self, images, target, training):
        """ 定义训练期间使用的损失函数。
        """
        preds = self.predict(images, training)
        loss = tf.losses.sparse_softmax_cross_entropy(labels=target, logits=preds)
        return loss
    
    def grads_fn(self, images, target, training):
        """ 在每个正向步骤中,
            动态计算损失值对模型参数的梯度
        """
        with tfe.GradientTape() as tape:
            loss = self.loss_fn(images, target, training)
        return tape.gradient(loss, self.variables)
    
    def restore_model(self):
        """ 用于恢复已训练模型的函数
        """
        with tf.device(self.device):
            # Run the model once to initialize variables
            dummy_input = tf.constant(tf.zeros((1,48,48,1)))
            dummy_pred = self.predict(dummy_input, training=False)
            # Restore the variables of the model
            saver = tfe.Saver(self.variables)
            saver.restore(tf.train.latest_checkpoint
                          (self.checkpoint_directory))
    
    def save_model(self, global_step=0):
        """ 用于保存已训练模型的函数
        """
        tfe.Saver(self.variables).save(self.checkpoint_directory, 
                                       global_step=global_step)   
    
    def compute_accuracy(self, input_data):
        """ 在输入数据上计算准确率
        """
        with tf.device(self.device):
            acc = tfe.metrics.Accuracy()
            for images, targets in tfe.Iterator(input_data):
                # Predict the probability of each class
                logits = self.predict(images, training=False)
                # Select the class with the highest probability
                preds = tf.argmax(logits, axis=1)
                # Compute the accuracy
                acc(tf.reshape(targets, [-1,]), preds)
        return acc
        
    def fit(self, training_data, eval_data, optimizer, num_epochs=500, 
            early_stopping_rounds=10, verbose=10, train_from_scratch=False):
        """ 使用所选优化器和所需数量的迭代来训练模型。 你可以从头开始训练或加载最后训练的模型。 提前停止用于降低过拟合网络的风险。
            
            Args:
                training_data: the data you would like to train the model on.
                                Must be in the tf.data.Dataset format.
                eval_data: the data you would like to evaluate the model on.
                            Must be in the tf.data.Dataset format.
                optimizer: the optimizer used during training.
                num_epochs: the maximum number of iterations you would like to 
                            train the model.
                early_stopping_rounds: stop training if the loss on the eval 
                                       dataset does not decrease after n epochs.
                verbose: int. Specify how often to print the loss value of the network.
                train_from_scratch: boolean. Whether to initialize variables of the
                                    the last trained model or initialize them
                                    randomly.
        """ 
    
        if train_from_scratch==False:
            self.restore_model()
        
        # 初始化最佳损失。 此变量存储评估数据集上的最低损失。
        best_loss = 999
        
        # 初始化类来更新训练和评估的平均损失
        train_loss = tfe.metrics.Mean('train_loss')
        eval_loss = tfe.metrics.Mean('eval_loss')
        
        # 初始化字典来存储损失的历史记录
        self.history = {}
        self.history['train_loss'] = []
        self.history['eval_loss'] = []
        
        # 开始训练
        with tf.device(self.device):
            for i in range(num_epochs):
                # 使用梯度下降来训练
                for images, target in tfe.Iterator(training_data):
                    grads = self.grads_fn(images, target, True)
                    optimizer.apply_gradients(zip(grads, self.variables))
                    
                # 计算一个迭代后的训练数据的损失
                for images, target in tfe.Iterator(training_data):
                    loss = self.loss_fn(images, target, False)
                    train_loss(loss)
                self.history['train_loss'].append(train_loss.result().numpy())
                # 重置指标
                train_loss.init_variables()
                
                # 计算一个迭代后的评估数据的损失
                for images, target in tfe.Iterator(eval_data):
                    loss = self.loss_fn(images, target, False)
                    eval_loss(loss)
                self.history['eval_loss'].append(eval_loss.result().numpy())
                # 重置指标
                eval_loss.init_variables()
                
                # 打印训练和评估损失
                if (i==0) | ((i+1)%verbose==0):
                    print('Train loss at epoch %d: ' %(i+1), self.history['train_loss'][-1])
                    print('Eval loss at epoch %d: ' %(i+1), self.history['eval_loss'][-1])

                # 为提前停止而检查
                if self.history['eval_loss'][-1]<best_loss:
                    best_loss = self.history['eval_loss'][-1]
                    count = early_stopping_rounds
                else:
                    count -= 1
                if count==0:
                    break

使用梯度下降和提前停止来训练模型

我在训练网络 35 个迭代后保存了权重。 你可以在更多的几个迭代中恢复和微调它们。 如果你的计算机上没有 GPU,那么进一步调整模型将比从头开始训练模型容易得多。

如果在n个时期之后开发数据集上的损失没有减少,则可以使用提前停止来停止训练网络(可以使用变量early_stopping_rounds设置n的数量)。


# 指定你打算保存/恢复已训练变量的路径
checkpoint_directory = 'models_checkpoints/EmotionCNN/'

# 如果可用,则使用 GPU
device = 'gpu:0' if tfe.num_gpus()>0 else 'cpu:0'

# 定义优化器
optimizer = tf.train.AdamOptimizer()

# 实例化模型。这不会实例化变量
model = EmotionRecognitionCNN(num_classes=7, device=device, 
                              checkpoint_directory=checkpoint_directory)
                              

# 训练模型  
model.fit(training_data, eval_data, optimizer, num_epochs=500, 
          early_stopping_rounds=5, verbose=10, train_from_scratch=False)
          
'''
Train loss at epoch 1:  1.5994938561539342
Eval loss at epoch 1:  1.6061641948413006
Train loss at epoch 10:  1.1655063030448947
Eval loss at epoch 10:  1.2517835698296538
Train loss at epoch 20:  1.007327914901725
Eval loss at epoch 20:  1.1543473274306912
Train loss at epoch 30:  0.9942544895184863
Eval loss at epoch 30:  1.1808805191411382
'''

# 保存已训练模型
model.save_model()

在训练期间展示表现


pltplt..plotplot((rangerange((lenlen((modelmodel..historyhistory[['train_loss''train_l ])), model.history['train_loss'],
         color='b', label='Train loss');
plt.plot(range(len(model.history['eval_loss'])), model.history['eval_loss'], 
         color='r', label='Dev loss');
plt.title('Model performance during training', fontsize=15)
plt.xlabel('Number of epochs', fontsize=15);
plt.ylabel('Loss', fontsize=15);
plt.legend(fontsize=15);

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/7-3.png

计算准确率

train_acc = model.compute_accuracy(training_data)
eval_acc = model.compute_accuracy(eval_data)

print('Train accuracy: ', train_acc.result().numpy())
print('Eval accuracy: ', eval_acc.result().numpy())
'''
Train accuracy:  0.6615347103695706
Eval accuracy:  0.5728615213151296
'''

使用集成梯度展示神经网络归属

所以现在我们已经训练了我们的 CNN 模型,让我们看看我们是否可以使用集成梯度来理解它的推理。本文详细解释了这种方法,称为深度网络的 Axiomatic 归属。

通常,你首先尝试理解,模型的预测是直接计算输出类对图像的导数。这可以为你提供提示,图像的哪个部分激活网络。但是,这种技术对图像伪影很敏感。

为了避免这种缺陷,我们将使用集成梯度来计算特定图像的网络归属。该技术简单地采用原始图像,将像素强度缩放到不同的度数(从1/mm,其中m是步数)并且计算对每个缩放图像的梯度。为了获得该归属,对所有缩放图像的梯度进行平均并与原始图像相乘。

以下是使用 TensorFlow Eager 实现此操作的示例:

def get_prob_class(X, idx_class):
    """ 获取所选图像的 softmax 概率
        
        Args:
            X: 4D tensor image.
            
        Returns:
            prob_class: the probability of the selected class.  
    """
    logits = model.predict(X, False)
    prob_class = logits[0, idx_class]
    return prob_class

def integrated_gradients(X, m=200):
    """ 为一个图像样本计算集成梯度
        
        Args:
            X: 4D tensor of the image sample.
            m: number of steps, more steps leads to a better approximation.
               
        Returns:
            g: integrated gradients.
    """
    perc = (np.arange(1,m+1)/m).reshape(m,1,1,1)
    perc = tf.constant(perc, dtype=tf.float32)
    idx_class = tf.argmax(model.predict(X, False), axis=1).numpy()[0]
    X_tiled = tf.tile(X, [m,1,1,1])
    X_scaled = tf.multiply(X_tiled, perc)
    grad_fn = tfe.gradients_function(get_prob_class, params=[0])
    g = grad_fn(X_scaled, idx_class)
    g = tf.reduce_mean(g, axis=[1])
    g = tf.multiply(X, g)
    return g, idx_class
    
def visualize_attributions(X, g, idx_class):
    """ 使用集成渐变绘制原始图像以及 CNN 归属。
        
        Args:
            X: 4D tensor image.
            g: integrated gradients.
            idx_class: the index of the predicted label.
    """
    img_attributions = X*tf.abs(g)
    f, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
    ax1.imshow(X[0,:,:,0], cmap='gray')
    ax1.set_title('Predicted emotion: %s' %emotion_cat[idx_class], fontsize=15)
    ax2.imshow(img_attributions[0,:,:,0], cmap='gray')
    ax2.set_title('Integrated gradients', fontsize=15)
    plt.tight_layout()
    
with tf.device(device):
    idx_img = 1000 # modify here to change the image
    X = tf.constant(X_train[idx_img,:].reshape(1,48,48,1))
    g, idx_class = integrated_gradients(X, m=200)
    visualize_attributions(X, g, idx_class)

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/7-4.png

集成梯度图像的较亮部分对预测标签的影响最大。

网络摄像头测试

最后,你可以在任何新的图像或视频集上测试 CNN 的性能。 在下面的单元格中,我将向你展示如何使用网络摄像头捕获图像帧并对其进行预测。

为此,你必须安装opencv-python库。 你可以通过在终端输入这些来轻松完成此操作:

pip install opencv-python

正如你在笔记本开头看到的那样,FER2013 数据集中的图像已经裁剪了面部。 为了裁剪新图像/视频中的人脸,我们将使用 OpenCV 库中预先训练的 Haar-Cascade 算法。

那么,让我们开始吧!

如果要在实时网络摄像头镜头上运行模型,请使用:

cap = cv2.VideoCapture(0)

如果你有想要测试的预先录制的视频,可以使用:

cap = cv2.VideoCapture(path_video)

自己随意尝试网络! 我保证这会很有趣。


# 导入OpenCV
import cv2

# 创建字符来将文本添加到图像
font = cv2.FONT_HERSHEY_SIMPLEX

# 导入与训练的 Haar 级联算法
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")

网络摄像头捕获的代码受到本教程的启发。

# Open video capture
cap = cv2.VideoCapture(0)

# Uncomment if you want to save the video along with its predictions
# fourcc = cv2.VideoWriter_fourcc(*'mp4v') 
# out = cv2.VideoWriter('test_cnn.mp4', fourcc, 20.0, (720,480))

while(True):
    # 逐帧捕获
    ret, frame = cap.read()
   
    # 从 RGB 帧转换为灰度
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 检测帧中的所有人脸
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)
    
    # 遍历发现的每个人脸
    for (x,y,w,h) in faces:
        # 剪裁灰度帧中的人脸
        face_gray = gray[y:y+h, x:x+w]    
        # 将图像大小改为 48x48 像素
        face_res = cv2.resize(face_gray, (48,48)) 
        face_res = face_res.reshape(1,48,48,1)
        # 按最大值标准化图像
        face_norm = face_res/255.0
        # 模型上的正向传播
        with tf.device(device):
            X = tf.constant(face_norm)
            X = tf.cast(X, tf.float32)
            logits = model.predict(X, False)
            probs = tf.nn.softmax(logits)
            ordered_classes = np.argsort(probs[0])[::-1]
            ordered_probs = np.sort(probs[0])[::-1]
            k = 0
            # 为每个预测绘制帧上的概率
            for cl, prob in zip(ordered_classes, ordered_probs):
                # 添加矩形,宽度与其概率成比例
                cv2.rectangle(frame, (20,100+k),(20+int(prob*100),130+k),(170,145,82),-1)
                # 向绘制的矩形添加表情标签
                cv2.putText(frame,emotion_cat[cl],(20,120+k),font,1,(0,0,0),1,cv2.LINE_AA)
                k += 40
    
    # 如果你希望将视频写到磁盘,就取消注释
    #out.write(frame)
    
    # 展示所得帧
    cv2.imshow('frame',frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
        
# 一切都完成后,解除捕获
cap.release()
cv2.destroyAllWindows()

八、用于 TensorFlow Eager 序列分类的动态循坏神经网络

大家好! 在本教程中,我们将构建一个循环神经网络,用于对 IMDB 电影评论进行情感分析。 我选择了这个数据集,因为它很小,很容易被任何人下载,所以数据采集没有瓶颈。

本教程的主要目的不是教你如何构建一个简单的 RNN,而是如何构建一个 RNN,为你提供模型开发的更大灵活性(例如,使用目前在 Keras 中不可用的新 RNN 单元,更容易访问 RNN 的展开输出,从磁盘批量读取数据)。 我希望能够让你看看,在你可能感兴趣的任何领域中,如何继续建立你自己的模型,不管它们有多复杂。

教程步骤

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/8-1.png

如果你想在本教程中添加任何内容,请告诉我们。 此外,我很高兴听到你的任何改进建议。

导入实用的库

# 导入函数来编写和解析 TFRecords
from data_utils import imdb2tfrecords
from data_utils import parse_imdb_sequence

# 导入 TensorFlow 和 TensorFlow Eager
import tensorflow as tf
import tensorflow.contrib.eager as tfe

# 为数据处理导入 pandas,为数据读取导入  pickle
import pandas as pd
import pickle

# 导入绘图库
import matplotlib.pyplot as plt
%matplotlib inline

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution(device_policy=tfe.DEVICE_PLACEMENT_SILENT)

下载数据并转换为 TFRecords

或者,如果你克隆这个仓库,你将自动将下载的数据解析为 TFRecords,因此请随意跳过此步骤。

大型电影评论数据集是“二元情感分类的数据集,包含比以前的基准数据集更多的数据”(来源)。 它由 25000 个评论的训练数据集(12500 个正面和 125000 个负面)和 25000 个评论的测试数据集(12500 个正面和 125000 个负面)组成。

以下是正面评论的示例:

Rented the movie as a joke. My friends and I had so much fun laughing at it that I went and found a used copy and bought it for myself. Now when all my friends are looking for a funny movie I give them Sasquatch Hunters. It needs to be said though there is a rule that was made that made the movie that much better. No talking is allowed while the movie is on unless the words are Sasquatch repeated in a chant. I loved the credit at the end of the movie as well. "Thanks for the Jeep, Tom!" Whoever Tom is I say thank you because without your Jeep the movie may not have been made. In short a great movie if you are looking for something to laugh at. If you want a good movie maybe look for something else but if you don't mind a laugh at the expense of a man in a monkey suit grab yourself a copy.

以下是负面评论的示例:

The Good: I liked this movie because it was the first horror movie I've seen in a long time that actually scared me. The acting wasn't too bad, and the "Cupid" killer was believable and disturbing. The Bad: The story line and plot of this movie is incredibly weak. There just wasn't much to it. The ways the killer killed his victims was very horrifying and disgusting. I do not recommend this movie to anyone who can not handle gore. Overall: A good scare, but a bad story.

为了下载数据集,只需在终端中执行:

chmod o+x datasets/get_imdb_dataset.sh
datasets/get_imdb_dataset.sh

让我们来看看评论长度的分布(单词/评论):

length_reviews = pickle.load(open('datasets/aclImdb/length_reviews.pkl', 'rb'))
pd.DataFrame(length_reviews, columns=['Length reviews']).hist(bins=50, color='orange');
plt.grid(False);

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/8-2.png

似乎大多数评论都有大约 250 个单词。 然而,由于一些相当长的评论,分布似乎有一个很长的尾巴。 由于我们必须将可变长度输入序列填充到批量中的最大序列,因此保留这些评论将是非常低效的。

因此,我在imdb2tfrecords函数中添加了一个参数,你可以在其中指定评论中允许的最大字数。 这将简单地采用评论中的最后一个max_words来提高训练效率,并避免在很多时间步长内展开神经网络,这可能导致内存问题。

下载数据集后,只需运行imdb2tfrecords函数即可。 此函数将每个评论解析为单词索引列表。

# 用于将原始数据转换为 TFRecords 的函数,将评论中每个单词转换为整数索引
#imdb2tfrecords(path_data='datasets/aclImdb/', min_word_frequency=5, max_words_review=700)

处理结束时,每个tfrecord将由以下内容组成:

TFrecord 描述
'token_indexes' 评论中出现的单词索引的序列
'target' 负面情感为 0,正面情感为 1
'sequence_length' 评论的序列长度

如果你想使用新数据集测试此 RNN 网络,请查看data_utils.py脚本中的imdb2tfrecordsparse_imdb_sequence,来了解如何将新数据解析为 TFRecords。 我真的建议使用这种文件格式,因为它非常容易处理非常大的数据集,而不受 RAM 容量的限制。

创建训练和测试迭代器

train_dataset = tf.data.TFRecordDataset('datasets/aclImdb/train.tfrecords')
train_dataset = train_dataset.map(parse_imdb_sequence).shuffle(buffer_size=10000)
train_dataset = train_dataset.padded_batch(512, padded_shapes=([None],[],[]))

test_dataset = tf.data.TFRecordDataset('datasets/aclImdb/test.tfrecords')
test_dataset = test_dataset.map(parse_imdb_sequence).shuffle(buffer_size=10000)
test_dataset = test_dataset.padded_batch(512, padded_shapes=([None],[],[]))

# 读取词汇表
word2idx = pickle.load(open('datasets/aclImdb/word2idx.pkl', 'rb'))

用于序列分类的 RNN 模型,兼容 Eager API

在下面的单元格中,你可以找到我为 RNN 模型创建的类。 API 与我在上一个教程中创建的 API 非常相似,只是现在我们跟踪模型的准确率而不是损失。

网络的想法非常简单。 我们只需在评论中选择每个单词,选择相应的单词嵌入(在开头随机初始化),然后将其传递给 RNN 单元。 然后,我们在序列的末尾获取 RNN 单元的输出并将其传递通过密集层(具有 ReL U激活)来获得最终预测。

通常,网络继承自tf.keras.Model,以便跟踪所有变量并轻松保存/恢复它们。

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/8-3.png

class RNNModel(tf.keras.Model):
    def __init__(self, embedding_size=100, cell_size=64, dense_size=128, 
                 num_classes=2, vocabulary_size=None, rnn_cell='lstm',
                 device='cpu:0', checkpoint_directory=None):
        ''' 定义在正向传播期间使用的参数化层,你要在其上运行计算的设备,以及检查点目录。另外,你还可以修改网络的默认大小。
            
            Args:
                embedding_size: the size of the word embedding.
                cell_size: RNN cell size.
                dense_size: the size of the dense layer.
                num_classes: the number of labels in the network.
                vocabulary_size: the size of the word vocabulary.
                rnn_cell: string, either 'lstm' or 'ugrnn'.
                device: string, 'cpu:n' or 'gpu:n' (n can vary). Default, 'cpu:0'.
                checkpoint_directory: the directory where you would like to save or 
                                      restore a model.
        '''
        super(RNNModel, self).__init__()
        
        # 权重初始化函数
        w_initializer = tf.contrib.layers.xavier_initializer()
    
        # 偏置初始化函数
        b_initializer = tf.zeros_initializer()
        
        # 为单词嵌入初始化权重
        self.embeddings = tf.keras.layers.Embedding(vocabulary_size, embedding_size, 
                                                    embeddings_initializer=w_initializer)
        
        # 密集层的初始化
        self.dense_layer = tf.keras.layers.Dense(dense_size, activation=tf.nn.relu, 
                                                 kernel_initializer=w_initializer, 
                                                 bias_initializer=b_initializer)
        
        # 预测层的初始化
        self.pred_layer = tf.keras.layers.Dense(num_classes, activation=None, 
                                                kernel_initializer=w_initializer, 
                                                bias_initializer=b_initializer)
        
        # 基本的 LSTM 单元
        if rnn_cell=='lstm':
            self.rnn_cell = tf.nn.rnn_cell.BasicLSTMCell(cell_size)
        # 否则是 UGRNN 单元
        else:
            self.rnn_cell = tf.contrib.rnn.UGRNNCell(cell_size)
            
        # 定义设备 
        self.device = device
        
        # 定义检查点目录
        self.checkpoint_directory = checkpoint_directory
        
    def predict(self, X, seq_length, is_training):
        '''
        基于输入样本,预测每个类的概率

        Args:
            X: 2D tensor of shape (batch_size, time_steps).
            seq_length: the length of each sequence in the batch.
            is_training: Boolean. Either the network is predicting in
                         training mode or not.
        '''
        
        # 获取批量中的样本量
        num_samples = tf.shape(X)[0]

        # 初始化 LSTM 单元状态为零
        state = self.rnn_cell.zero_state(num_samples, dtype=tf.float32)
        
        # 获取序列中每个词的嵌入
        embedded_words = self.embeddings(X)
        
        # 分割嵌入
        unstacked_embeddings = tf.unstack(embedded_words, axis=1)
        
        # 遍历每个时间步长并附加预测
        outputs = []
        for input_step in unstacked_embeddings:
            output, state = self.rnn_cell(input_step, state)
            outputs.append(output)
            
        # 将输出堆叠为(批量大小,时间步长,单元数)
        outputs = tf.stack(outputs, axis=1)
        
        # 对于每个样本,提取最后一个时间步骤的输出
        idxs_last_output = tf.stack([tf.range(num_samples), 
                                     tf.cast(seq_length-1, tf.int32)], axis=1)
        final_output = tf.gather_nd(outputs, idxs_last_output)
        
        # 为正则化添加 dropout
        dropped_output = tf.layers.dropout(final_output, rate=0.3, training=is_training)
        
        # 向密集层(ReLU 激活)传入最后单元的状态
        dense = self.dense_layer(dropped_output)
        
        # 计算非标准化的对数概率
        logits = self.pred_layer(dense)
        return logits
    
    def loss_fn(self, X, y, seq_length, is_training):
        """ 定义训练期间使用的损失函数        
        """
        preds = self.predict(X, seq_length, is_training)
        loss = tf.losses.sparse_softmax_cross_entropy(labels=y, logits=preds)
        return loss
    
    def grads_fn(self, X, y, seq_length, is_training):
        """ 在每个正向步骤中,
            动态计算损失值对模型参数的梯度
        """
        with tfe.GradientTape() as tape:
            loss = self.loss_fn(X, y, seq_length, is_training)
        return tape.gradient(loss, self.variables)
    
    def restore_model(self):
        """ 用于恢复训练模型的函数
        """
        with tf.device(self.device):
            # 运行模型一次来初始变量
            dummy_input = tf.constant(tf.zeros((1,1)))
            dummy_length = tf.constant(1, shape=(1,))
            dummy_pred = self.predict(dummy_input, dummy_length, False)
            # 恢复模型变量
            saver = tfe.Saver(self.variables)
            saver.restore(tf.train.latest_checkpoint
                          (self.checkpoint_directory))
    
    def save_model(self, global_step=0):
        """ 用于保存训练模型的函数
        """
        tfe.Saver(self.variables).save(self.checkpoint_directory, 
                                       global_step=global_step)   
        
    def fit(self, training_data, eval_data, optimizer, num_epochs=500, 
            early_stopping_rounds=10, verbose=10, train_from_scratch=False):
        """ 用于训练模型的函数,
            使用所选的优化器,执行所需数量的迭代
            你可以从零开始训练,或者加载最后训练的模型
            使用了提前停止来降低网络的过拟合风险
            
            Args:
                training_data: the data you would like to train the model on.
                                Must be in the tf.data.Dataset format.
                eval_data: the data you would like to evaluate the model on.
                            Must be in the tf.data.Dataset format.
                optimizer: the optimizer used during training.
                num_epochs: the maximum number of iterations you would like to 
                            train the model.
                early_stopping_rounds: stop training if the accuracy on the eval 
                                       dataset does not increase after n epochs.
                verbose: int. Specify how often to print the loss value of the network.
                train_from_scratch: boolean. Whether to initialize variables of the
                                    the last trained model or initialize them
                                    randomly.
        """ 
    
        if train_from_scratch==False:
            self.restore_model()
        
        # 初始化 best_acc。这个变量储存最高的准确率。
        # on the eval dataset.
        best_acc = 0
        
        # 初始化类别来更新训练和评估的平均准确率
        train_acc = tfe.metrics.Accuracy('train_acc')
        eval_acc = tfe.metrics.Accuracy('eval_acc')
        
        # 初始化字典来存储准确率历史
        self.history = {}
        self.history['train_acc'] = []
        self.history['eval_acc'] = []
        
        # 开始训练
        with tf.device(self.device):
            for i in range(num_epochs):
                # 使用梯度下降来训练
                for X, y, seq_length in tfe.Iterator(training_data):
                    grads = self.grads_fn(X, y, seq_length, True)
                    optimizer.apply_gradients(zip(grads, self.variables))
                    
                # 检查训练集的准确率
                for X, y, seq_length in tfe.Iterator(training_data):
                    logits = self.predict(X, seq_length, False)
                    preds = tf.argmax(logits, axis=1)
                    train_acc(preds, y)
                self.history['train_acc'].append(train_acc.result().numpy())
                # 重置指标
                train_acc.init_variables()

                # 检查评估集的准确率
                for X, y, seq_length in tfe.Iterator(eval_data):
                    logits = self.predict(X, seq_length, False)
                    preds = tf.argmax(logits, axis=1)
                    eval_acc(preds, y)
                self.history['eval_acc'].append(eval_acc.result().numpy())
                # 重置指标
                eval_acc.init_variables()
                
                # 打印训练和评估准确率
                if (i==0) | ((i+1)%verbose==0):
                    print('Train accuracy at epoch %d: ' %(i+1), self.history['train_acc'][-1])
                    print('Eval accuracy at epoch %d: ' %(i+1), self.history['eval_acc'][-1])

                # 为提前停止而检查
                if self.history['eval_acc'][-1]>best_acc:
                    best_acc = self.history['eval_acc'][-1]
                    count = early_stopping_rounds
                else:
                    count -= 1
                if count==0:
                    break

使用梯度下降和提前停止来训练模型

使用简单的 LSTM 单元来训练模型

# 指定你打算存储/恢复训练变量的路径
checkpoint_directory = 'models_checkpoints/ImdbRNN/'

# 如果可用,则使用 GPU
device = 'gpu:0' if tfe.num_gpus()>0 else 'cpu:0'

# 定义优化器
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4)

# 初始化模型,还没有初始化变量
lstm_model = RNNModel(vocabulary_size=len(word2idx), device=device, 
                      checkpoint_directory=checkpoint_directory)
                      
# 训练模型
lstm_model.fit(train_dataset, test_dataset, optimizer, num_epochs=500, 
                early_stopping_rounds=5, verbose=1, train_from_scratch=True)
'''
Train accuracy at epoch 1:  0.72308
Eval accuracy at epoch 1:  0.68372
Train accuracy at epoch 2:  0.77708
Eval accuracy at epoch 2:  0.75472
Train accuracy at epoch 3:  0.875
Eval accuracy at epoch 3:  0.82036
Train accuracy at epoch 4:  0.91728
Eval accuracy at epoch 4:  0.8542
Train accuracy at epoch 5:  0.94728
Eval accuracy at epoch 5:  0.87464
Train accuracy at epoch 6:  0.96312
Eval accuracy at epoch 6:  0.88228
Train accuracy at epoch 7:  0.97476
Eval accuracy at epoch 7:  0.88624
Train accuracy at epoch 8:  0.9828
Eval accuracy at epoch 8:  0.88344
Train accuracy at epoch 9:  0.98692
Eval accuracy at epoch 9:  0.87036
Train accuracy at epoch 10:  0.99052
Eval accuracy at epoch 10:  0.86724
Train accuracy at epoch 11:  0.9944
Eval accuracy at epoch 11:  0.87088
Train accuracy at epoch 12:  0.99568
Eval accuracy at epoch 12:  0.86068
'''

# 保存模型
lstm_model.save_model()

使用 UGRNN 单元训练模型

# 定义优化器
optimizer = tf.train.AdamOptimizer(learning_rate=1e-4)

# 初始化模型,还没有初始化变量
ugrnn_model = RNNModel(vocabulary_size=len(word2idx), rnn_cell='ugrnn', 
                       device=device, checkpoint_directory=checkpoint_directory)

# 训练模型
ugrnn_model.fit(train_dataset, test_dataset, optimizer, num_epochs=500, 
                early_stopping_rounds=5, verbose=1, train_from_scratch=True)
'''
Train accuracy at epoch 1:  0.71092
Eval accuracy at epoch 1:  0.67688
Train accuracy at epoch 2:  0.82512
Eval accuracy at epoch 2:  0.7982
Train accuracy at epoch 3:  0.88792
Eval accuracy at epoch 3:  0.84116
Train accuracy at epoch 4:  0.92156
Eval accuracy at epoch 4:  0.85076
Train accuracy at epoch 5:  0.94592
Eval accuracy at epoch 5:  0.86476
Train accuracy at epoch 6:  0.95984
Eval accuracy at epoch 6:  0.87104
Train accuracy at epoch 7:  0.9708
Eval accuracy at epoch 7:  0.87188
Train accuracy at epoch 8:  0.9786
Eval accuracy at epoch 8:  0.8748
Train accuracy at epoch 9:  0.98412
Eval accuracy at epoch 9:  0.86452
Train accuracy at epoch 10:  0.9882
Eval accuracy at epoch 10:  0.86172
Train accuracy at epoch 11:  0.9938
Eval accuracy at epoch 11:  0.86808
Train accuracy at epoch 12:  0.9956
Eval accuracy at epoch 12:  0.8596
Train accuracy at epoch 13:  0.997
Eval accuracy at epoch 13:  0.86368
'''

表现比较

f, (ax1, ax2) = plt.subplots(1, 2, sharey=True, figsize=(10, 4))
ax1.plot(range(len(lstm_model.history['train_acc'])), lstm_model.history['train_acc'], 
         label='LSTM Train Accuracy');
ax1.plot(range(len(lstm_model.history['eval_acc'])), lstm_model.history['eval_acc'], 
         label='LSTM Test Accuracy');
ax2.plot(range(len(ugrnn_model.history['train_acc'])), ugrnn_model.history['train_acc'],
         label='UGRNN Train Accuracy');
ax2.plot(range(len(ugrnn_model.history['eval_acc'])), ugrnn_model.history['eval_acc'],
         label='UGRNN Test Accuracy');
ax1.legend();
ax2.legend();

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/8-4.png

在新评论上测试网络

我认为最近在 IMDb 上发布的新评论测试网络会很不错。 我选择了 2018 年 2 月为电影 Bad Apples 发布的三条评论。 随意试试新的评论或自己发布一个新的评论! 网络能在所有三种情况下正确识别情感,所以我印象非常深刻。

################################################################
# 恢复训练模型
################################################################
tf.reset_default_graph()
checkpoint_directory = 'models_checkpoints/ImdbRNN/'
device = 'gpu:0' if tfe.num_gpus()>0 else 'cpu:0'
lstm_model = RNNModel(vocabulary_size=len(word2idx), device=device, 
                      checkpoint_directory=checkpoint_directory)
lstm_model.restore_model()
# INFO:tensorflow:Restoring parameters from models_checkpoints/ImdbRNN/-0

###############################################################
# 导入/下载必要的库来处理新的序列
###############################################################
import nltk
try:
    nltk.data.find('tokenizers/punkt')
except LookupError:
    nltk.download('punkt')
from nltk.tokenize import word_tokenize
import re

def process_new_review(review):
    '''用于处理新评论的函数
       Args:
           review: original text review, string.
       Returns:
           indexed_review: sequence of integers, words correspondence 
                           from word2idx.
           seq_length: the length of the review.
    '''
    indexed_review = re.sub(r'<[^>]+>', ' ', review)
    indexed_review = word_tokenize(indexed_review)
    indexed_review = [word2idx[word] if word in list(word2idx.keys()) else 
                      word2idx['Unknown_token'] for word in indexed_review]
    indexed_review = indexed_review + [word2idx['End_token']]
    seq_length = len(indexed_review)    
    return indexed_review, seq_length
    
sent_dict = {0: 'negative', 1: 'positive'}

review_score_10 = "I think Bad Apples is a great time and I recommend! I enjoyed the opening, which gave way for the rest of the movie to occur. The main couple was very likable and I believed all of their interactions. They had great onscreen chemistry and made me laugh quite a few times! Keeping the girls in the masks but seeing them in action was something I loved. It kept a mystery to them throughout. I think the dialogue was great. The kills were fun. And the special surprise gore effect at the end was AWESOME!! I won't spoil that part ;) I also enjoyed how the movie wrapped up. It gave a very urban legends type feel of \"did you ever hear the story...\". Plus is leaves the door open for another film which I wouldn't mind at all. Long story short, I think if you take the film for what it is; a fun little horror flick, then you won't be disappointed! HaPpY eArLy HaLLoWeEn!"

review_score_4 = "A young couple comes to a small town, where the husband get a job working in a hospital. The wife which you instantly hate or dislike works home, at the same time a horrible murders takes place in this small town by two masked killers. Bad Apples is just your tipical B-horror movie with average acting (I give them that. Altough you may get the idea that some of the actors are crazy-convervative Christians), but the script is just bad, and that's what destroys the film."

review_score_1 = "When you first start watching this movie, you can tell its going to be a painful ride. the audio is poor...the attacks by the \"girls\" are like going back in time, to watching the old rocky films, were blows never touched. the editing is poor with it aswell, example the actress in is the bath when her husband comes home, clearly you see her wearing a flesh coloured bra in the bath. no hints or spoilers, just wait till you find it in a bargain basket of cheap dvds in a couple of weeks"

new_reviews = [review_score_10, review_score_4, review_score_1]
scores = [10, 4, 1]

with tf.device(device):
    for original_review, score in zip(new_reviews, scores):
        indexed_review, seq_length = process_new_review(original_review)
        indexed_review = tf.reshape(tf.constant(indexed_review), (1,-1))
        seq_length = tf.reshape(tf.constant(seq_length), (1,))
        logits = lstm_model.predict(indexed_review, seq_length, False)
        pred = tf.argmax(logits, axis=1).numpy()[0]
        print('The sentiment for the review with score %d was found to be %s'
              %(score, sent_dict[pred]))
'''

The sentiment for the review with score 10 was found to be positive
The sentiment for the review with score 4 was found to be negative
The sentiment for the review with score 1 was found to be negative
'''

展示 RNN 单元的激活

本教程的部分内容受到了 Karpathy 在《可视化和理解循坏神经网络》中的工作的启发。

我们将使用seaborn库绘制热图。 你可以通过在终端输入这个来获取它:

pip install seaborn
# 导入用于 RNN 可视化的库
import seaborn as sns

def VisualizeRNN(model, X):
    ''' 返回每个时间步骤的单元状态的 tanh 的函数
        
        Args:
            model: trained RNN model.
            X: indexed review of shape (1, sequence_length).
            
        Returns:
            tanh(cell_states): the tanh of the memory cell at each timestep.       
    '''
    
    # 初始化 LSTM 单元状态为零
    state = model.rnn_cell.zero_state(1, dtype=tf.float32)
    
    # 获取序列中每个词的嵌入
    embedded_words = model.embeddings(X)

    # 分割嵌入
    unstacked_embeddings = tf.unstack(embedded_words, axis=1)

    # 遍历每个时间步骤,附加它的单元状态
    cell_states = []
    for input_step in unstacked_embeddings:
        _, state = model.rnn_cell(input_step, state)
        cell_states.append(state[0])
        
    # 将 cell_states 堆叠为(批量大小,时间步长,单元数量)
    cell_states = tf.stack(cell_states, axis=1)
    return tf.tanh(cell_states)
    
# 随意修改输入 
dummy_review = "When you first start watching this movie, you can tell its going to be a painful ride. the audio is poor..."


# 处理新的评论
indexed_review, seq_length = process_new_review(dummy_review)
indexed_review = tf.reshape(tf.constant(indexed_review), (1,-1))

# 获取单元状态
cell_states = VisualizeRNN(lstm_model, indexed_review)


# 绘制单元中前 10 个单元的激活(总共有 64 个单元)
plt.figure(figsize = (16,5))
sns.heatmap(cell_states.numpy()[0,:,:10].T, 
            xticklabels=word_tokenize(dummy_review)+['<END>'],
            cmap='RdBu');

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/8-5.png

九、用于 TensorFlow Eager 时间序列回归的递归神经网络

大家好! 在本教程中,我们将探讨如何为时间序列回归构建循环神经网络。 由于我的背景在电力系统中,我认为最好包括该领域相关的教程。 因此,在本教程中,我们将构建一个用于能源需求预测的 RNN。

为了预测第二天的能源需求,我们将使用 ENTSO-E 提供的每小时能耗数据。 我选择使用来自西班牙的数据,因为我目前住在这里。 然而,相同的分析可以应用于来自任何国家的能耗数据。

教程步骤

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/9-1.png

如果你想在本教程中添加任何内容,请告诉我们。 此外,我很高兴听到你的任何改进建议。

导入有用的库


# 导入 TensorFlow 和 TensorFlow Eager
import tensorflow.contrib.eager as tfe
import tensorflow as tf

# 导入用于数据处理的库
from sklearn.preprocessing import StandardScaler
from datetime import datetime as dt
import pandas as pd
import numpy as np

# 导入绘图库
import matplotlib.pyplot as plt
%matplotlib inline

# 开启 Eager 模式。一旦开启不能撤销!只执行一次。
tfe.enable_eager_execution(device_policy=tfe.DEVICE_PLACEMENT_SILENT)

探索性数据分析

可以在文件夹datasets / load_forecasting中找到本教程中使用的数据集。 我们读取它,来看看吧!

energy_df = pd.read_csv('datasets/load_forecasting/spain_hourly_entsoe.csv')
energy_df.tail(2)

| | Time (CET) | Day-ahead Total Load Forecast [MW] - BZN|ES | Actual Total Load [MW] - BZN|ES |
| --- | --- | --- | --- |
| 29230 | 02.05.2018 22:00 - 02.05.2018 23:00 | 30187.0 | 30344.0 |
| 29231 | 02.05.2018 23:00 - 03.05.2018 00:00 | 27618.0 | 27598.0 |

# 重命名行
energy_df.columns = ['time', 'forecasted_load', 'actual_load']

# 从 'Time (CET)' 中提取日期和时间
energy_df['date'] = energy_df['time'].apply(lambda x: dt.strptime(x.split('-')[0].strip(), '%d.%m.%Y %H:%M'))

如你所见,数据集附带每个测量的时间戳,特定小时的实际能量需求,以及预测值。 对于我们的任务,我们只使用Actual Total Load [MW]列。


printprint(('The date of the first measurement: ''The da , energy_df.loc[0, 'time'])
# The date of the first measurement:  01.01.2015 00:00 - 01.01.2015 01:00

print('The date of the last measurement: ', energy_df.loc[len(energy_df)-1, 'time'])
# The date of the last measurement:  02.05.2018 23:00 - 03.05.2018 00:00

该数据集包括从 2015 年 1 月 1 日到 2018 年 5 月 2 日的每小时能耗数据。最近的数据!

从时间序列数据创建输入和标签样本

将数据分割为训练/测试数据集

数据处理的第一步是从原始数据创建训练和测试数据集。 我选择将 80% 的数据保留在训练集中,20% 保留在测试集中。 通过修改train_size变量,随意调整此参数。

# 将数据分割为训练和测试数据集
train_size = 0.8
end_train = int(len(energy_df)*train_size/24)*24
train_energy_df = energy_df.iloc[:end_train,:]
test_energy_df = energy_df.iloc[end_train:,:]

# 绘制训练集 
plt.figure(figsize=(14,6))
plt.plot(train_energy_df['date'], train_energy_df['actual_load'], color='cornflowerblue');
plt.title('Train dataset', fontsize=17);
plt.ylabel('Energy Demand [MW]');

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/9-2.png


# 绘制测试集
plt.figure(figsize=(14,6))
plt.plot(test_energy_df['date'], test_energy_df['actual_load'], color='gold');
plt.title('Test dataset', fontsize=17);
plt.ylabel('Energy Demand [MW]');

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/9-3.png

缩放数据集

数据被标准化后,神经网络工作得更好,收敛速度更快。 对于此任务,我选择使用零均值和单位方差对数据进行标准化,因为我发现这对 LSTM 更有效。 你还可以尝试使用MinMaxScaler对数据进行标准化,看看是否可以获得更好的结果。

# 为缺失的度量进行差值
train_energy_df = train_energy_df.interpolate(limit_direction='both')
test_energy_df = test_energy_df.interpolate(limit_direction='both')

scaler = StandardScaler().fit(train_energy_df['actual_load'][:,None])
train_energy_df['actual_load'] = scaler.transform(train_energy_df['actual_load'][:,None])
test_energy_df['actual_load'] = scaler.transform(test_energy_df['actual_load'][:,None])

创建滑动窗口样本

你可以在我创建的函数下面的代码单元格中找到,从时间序列数据生成输入和标签样本。 每个 RNN 单元中的输入是一天的每小时数据。 时间步数由look_back变量定义,该变量指定要回顾的天数。 默认值为 5 天,这意味着 RNN 将展开 5 个步骤。

你还可以指定预测的天数,默认为一天。

输入和标签样本是基于look_backpredict_ahead变量创建的,如下图所示。

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/9-4.png

def moving_window_samples(timeseries, look_back=5, predict_ahead=1):
    '''
    用于从时间序列创建输入和标签样本的函数,延迟为一天。
    
    Args:
        timeseries: timeseries dataset.
        look_back: the size of the input. Specifies how many days to look back.
        predict_ahead: size of the output. Specifies how many days to predict ahead.
    
    Returns:
        input_samples: the input samples createad from the timeseries, 
                       using a window shift of one day.
        target_samples: the target corresponding to each input sample.
    '''
    
    n_strides = int((len(timeseries)- predict_ahead*24 - look_back*24 + 24)/24)
    input_samples = np.zeros((n_strides, look_back*24))
    target_samples = np.zeros((n_strides, predict_ahead*24))
    for i in range(n_strides):
        end_input = i*24 + look_back*24
        input_samples[i,:] = timeseries[i*24:end_input]
        target_samples[i,:] = timeseries[end_input:(end_input + predict_ahead*24)]  
        
    # 将输入形状修改为(样本数,时间步长,输入维度)
    input_samples = input_samples.reshape((-1, look_back, 24))
    return input_samples.astype('float32'), target_samples.astype('float32')

train_input_samples, train_target_samples = moving_window_samples(train_energy_df['actual_load'],
                                                                  look_back=5, predict_ahead=1)
                                                                  
test_input_samples, test_target_samples = moving_window_samples(test_energy_df['actual_load'],
                                                                look_back=5, predict_ahead=1)

使用tf.data.Dataset创建训练和测试数据集

通常,我们使用tf.data.Dataset API 将数据传输到张量。 我选择了 64 的批量大小,但随意调整它。 在训练网络时,我们可以使用tfe.Iterator函数非常轻松地遍历这些数据集。

# 随意修改批量大小
# 通常较小的批量大小在测试集上获得更好的结果
batch_size = 64
train_dataset = (tf.data.Dataset.from_tensor_slices(
    (train_input_samples, train_target_samples)).batch(batch_size))
test_dataset = (tf.data.Dataset.from_tensor_slices(
    (test_input_samples, test_target_samples)).batch(batch_size))

兼容 Eager API 的 RNN 回归模型

RNN 模型的类与以前的模型非常相似。 在初始化期间,我们定义了正向传播中所需的所有层。 我们还可以指定要在其上执行计算的设备,以及我们希望恢复或保存模型变量的文件路径。 该模型继承自tf.keras.Model,以便跟踪所有变量。

该模型的架构非常简单。 我们只需获取最后look_back天的每小时数据,然后将其传给 RNN。 最终输出传给了带有 ReLU 激活的密集层。 输出层大小由predict_ahead变量定义。 在我们的例子中,输出大小是 24 个单元,因为我们预测第二天的能源需求。

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/9-5.png

class RegressionRNN(tf.keras.Model):
    def __init__(self, cell_size=64, dense_size=128, predict_ahead=1, 
                 device='cpu:0', checkpoint_directory=None):
        ''' 定义在正向传播期间使用的参数化层,你要在上面运行计算的设备以及检查点目录。 另外,你还可以修改网络的默认大小。
            
            Args:
                cell_size: RNN cell size.
                dense_size: the size of the dense layer.
                predict_ahead: the number of days you would like to predict ahead.
                device: string, 'cpu:n' or 'gpu:n' (n can vary). Default, 'cpu:0'.
                checkpoint_directory: the directory where you would like to 
                                      save/restore a model.
        '''
        super(RegressionRNN, self).__init__()
        
        # 权重初始化函数
        w_initializer = tf.contrib.layers.xavier_initializer()
    
        # 偏置初始化函数
        b_initializer = tf.zeros_initializer()
        
        # 密集层初始化
        self.dense_layer = tf.keras.layers.Dense(dense_size, activation=tf.nn.relu, 
                                                 kernel_initializer=w_initializer, 
                                                 bias_initializer=b_initializer)
        
        # 预测层初始化
        self.pred_layer = tf.keras.layers.Dense(predict_ahead*24, activation=None, 
                                                kernel_initializer=w_initializer, 
                                                bias_initializer=b_initializer)
        
        # 基本的 LSTM 单元
        self.rnn_cell = tf.nn.rnn_cell.BasicLSTMCell(cell_size)
        
        # 定义设备
        self.device = device
        
        # 定义检查点目录
        self.checkpoint_directory = checkpoint_directory
        
    def predict(self, X):
        '''
        在网络上执行正向传播

        Args:
            X: 3D tensor of shape (batch_size, timesteps, input_dimension).
        
        Returns:
            preds: the final predictions of the network.
        '''
        
        # 获取一个批量的样本数量
        num_samples = tf.shape(X)[0]

        # 初始化 LSTM 单元状态为零
        state = self.rnn_cell.zero_state(num_samples, dtype=tf.float32)
        
        # 分割输入
        unstacked_input = tf.unstack(X, axis=1)
        
        # 遍历每个时间步骤
        for input_step in unstacked_input:
            output, state = self.rnn_cell(input_step, state)
        
        # 将最后一个单元状态传给密集层(ReLU 激活)
        dense = self.dense_layer(output)
        
        # 计算最终的预测
        preds = self.pred_layer(dense)
        return preds
    
    def loss_fn(self, X, y):
        """ 定义训练期间使用的损失函数
        """
        preds = self.predict(X)
        loss = tf.losses.mean_squared_error(y, preds)
        return loss
    
    def grads_fn(self, X, y):
        """ 在每个正向步骤中,
            动态计算损失值对模型参数的梯度
        """
        with tfe.GradientTape() as tape:
            loss = self.loss_fn(X, y)
        return tape.gradient(loss, self.variables)
    
    def restore_model(self):
        """ 用于恢复训练模型的函数
        """
        with tf.device(self.device):
            # 运行模型一次来初始化变量
            dummy_input = tf.constant(tf.zeros((1, 5, 24)))
            dummy_pred = self.predict(dummy_input)
            # 恢复模型变量
            saver = tfe.Saver(self.variables)
            saver.restore(tf.train.latest_checkpoint
                          (self.checkpoint_directory))
    
    def save_model(self, global_step=0):
        """ 用于保存训练模型的函数
        """
        tfe.Saver(self.variables).save(self.checkpoint_directory, 
                                       global_step=global_step)   
        
    def fit(self, training_data, eval_data, optimizer, num_epochs=500, 
            early_stopping_rounds=10, verbose=10, train_from_scratch=False):
        """ 用于训练模型的函数,
            使用所选的优化器,执行所需数量的迭代
            你可以从零开始训练,或者加载最后训练的模型
            使用了提前停止来降低网络的过拟合风险
            
            Args:
                training_data: the data you would like to train the model on.
                                Must be in the tf.data.Dataset format.
                eval_data: the data you would like to evaluate the model on.
                            Must be in the tf.data.Dataset format.
                optimizer: the optimizer used during training.
                num_epochs: the maximum number of iterations you would like to 
                            train the model.
                early_stopping_rounds: stop training if the loss on the eval 
                                       dataset does not decrease after n epochs.
                verbose: int. Specify how often to print the loss value of the network.
                train_from_scratch: boolean. Whether to initialize variables of the
                                    the last trained model or initialize them
                                    randomly.
        """ 
    
        if train_from_scratch==False:
            self.restore_model()
        
        # 初始化最佳损失。这个遍历储存评估数据集上的最低损失
        best_loss = 999
        
        # 初始化类别来更新训练和评估平均损失
        train_loss = tfe.metrics.Mean('train_loss')
        eval_loss = tfe.metrics.Mean('eval_loss')
        
        # 初始化目录来储存损失历史
        self.history = {}
        self.history['train_loss'] = []
        self.history['eval_loss'] = []
        
        # 开始训练
        with tf.device(self.device):
            for i in range(num_epochs):
                # 使用梯度下降来训练
                for X, y in tfe.Iterator(training_data):
                    grads = self.grads_fn(X, y)
                    optimizer.apply_gradients(zip(grads, self.variables))
                    
                # 计算一个迭代后训练数据上的损失
                for X, y in tfe.Iterator(training_data):
                    loss = self.loss_fn(X, y)
                    train_loss(loss)
                self.history['train_loss'].append(train_loss.result().numpy())
                # 重置指标
                train_loss.init_variables()
                
                # 计算一个迭代后评估数据上的损失
                for X, y in tfe.Iterator(eval_data):
                    loss = self.loss_fn(X, y)
                    eval_loss(loss)
                self.history['eval_loss'].append(eval_loss.result().numpy())
                # 重置指标
                eval_loss.init_variables()
                
                # 打印训练和评估损失
                if (i==0) | ((i+1)%verbose==0):
                    print('Train loss at epoch %d: ' %(i+1), self.history['train_loss'][-1])
                    print('Eval loss at epoch %d: ' %(i+1), self.history['eval_loss'][-1])

                # 为提前停止而检查
                if self.history['eval_loss'][-1]<best_loss:
                    best_loss = self.history['eval_loss'][-1]
                    count = early_stopping_rounds
                else:
                    count -= 1
                if count==0:
                    break

使用梯度下降来训练模型

# 指定你打算保存/恢复训练变量的路径
checkpoint_directory = 'models_checkpoints/DemandRNN/'

# 如果可用,则使用 GPU
device = 'gpu:0' if tfe.num_gpus()>0 else 'cpu:0'

# 定义优化器
optimizer = tf.train.AdamOptimizer(learning_rate=1e-2)

# 实例化模型。这并不会实例化变量。
model = RegressionRNN(cell_size=16, dense_size=16, predict_ahead=1, 
                      device=device, checkpoint_directory=checkpoint_directory)

# 训练模型
model.fit(train_dataset, test_dataset, optimizer, num_epochs=500, 
          early_stopping_rounds=5, verbose=50, train_from_scratch=True)
'''
Train loss at epoch 1:  0.5420932229608297
Eval loss at epoch 1:  0.609554298222065
Train loss at epoch 50:  0.024180740118026733
Eval loss at epoch 50:  0.049175919964909554
'''

# 保存模型
model.save_model()

在训练期间展示表现

我们可以很容易地看到在模型拟合过程中自动跟踪的损失历史。

plt.figure(figsize=(10, 6))
plt.plot(range(len(model.history['train_loss'])), model.history['train_loss'],
         color='cornflowerblue', label='Train loss');
plt.plot(range(len(model.history['eval_loss'])), model.history['eval_loss'], 
         color='gold', label='Test loss');
plt.title('Model performance during training', fontsize=15)
plt.xlabel('Number of epochs', fontsize=15);
plt.ylabel('Loss', fontsize=15);
plt.legend(fontsize=15);

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/9-6.png

展示测试集上的预测

这是本教程的最后一部分。 在对网络进行训练后,我们可以可展示测试数据集进行的预测。

如果你已经跳过训练,我添加了一个单元格,你可以轻松恢复已经训练过的模型。

##################################################
# 恢复之前训练过的模型
##################################################
tf.reset_default_graph()
checkpoint_directory = 'models_checkpoints/DemandRNN/'
model = RegressionRNN(cell_size=16, dense_size=16, predict_ahead=1, 
                      device=device, checkpoint_directory=checkpoint_directory)
model.restore_model()
# 
INFO:tensorflow:Restoring parameters from models_checkpoints/DemandRNN/-0

###################################################
# 展示实际值和预测值
###################################################
with tf.device(device):
    # 创建输入和标签样本的迭代器
    X_test, y_test = tfe.Iterator(test_dataset).next()
    # 预测测试批量
    preds = model.predict(X_test).numpy()
    y = y_test.numpy()
    # 为一般的批量样本(32)创建子图
    f, axarr = plt.subplots(16, 2, figsize=(12, 40))
    f.tight_layout()
    # 绘制预测
    i, j = 0, 0
    for idx in range(32):
        axarr[i,j].plot(range(24), preds[idx,:], label='Predicted demand', 
                        color='cornflowerblue')
        axarr[i,j].plot(range(24), y[idx,:], label='Actual demand',
                        color='gold')
        axarr[i,j].legend()
        axarr[i,j].set_title('Test sample %d' %idx)
        if j==1:
            i += 1
            j = 0
        else:
            j += 1

https://raw.githubusercontent.com/wizardforcel/data-science-notebook/master/tf/tf-eager-tut/img/9-7.png

上一篇下一篇

猜你喜欢

热点阅读