TensorFlow Eager 教程

TensorFlow Eager 教程

来源:madalinabuzau/tensorflow-eager-tutorials

译者:飞龙

协议:CC BY-NC-SA 4.0

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

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

教程步骤:

使用的版本: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()

第四步:构建单隐层神经网络(线性 -> 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)

二、在 Eager 模式中使用指标

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

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

教程步骤:

我选择了三个案例:

多分类

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

不平衡的二分类

当我们处理不平衡的数据集时,模型的准确率不是可靠的度量。 因此,我们将使用 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()

好的,所以这些类看起来很容易分开。 顺便说一句,我实际上在特征标准化之前尝试使用 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);

混淆矩阵

在训练完算法后展示混淆矩阵是一种很好的方式,可以全面了解网络表现。 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]])

我们将使用相同的神经网络架构。 我们只需用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);

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

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

我们的输入数据集仅包含一个特征,以便使绘图保持简单。 标签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);


# 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);

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

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

教程的流程图

导入有用的库

# 导入 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。 我知道很多人都卡在输入处理流水线,尤其是当你开始着手自己的个人项目时。 所以我真的希望它对你们任何人都有用!

教程的流程图

虚拟的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,因为它让我的生活变得更加容易。

教程的流程图

本教程将包含以下部分:

  • 创建一个函数,读取原始图像并将其转换为 TFRecords 的。
  • 创建一个函数,将 TFRecords 解析为 TF 张量。

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

导入有用的库

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 批量读取数据

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

教程的流程图:

我们将研究两种情况:

  • 可变序列长度的输入数据 - 在这种情况下,我们将填充批次到最大序列长度。
  • 图像数据

两种情况的数据都存储为 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将是:

  • [None] -> 将序列填充到最大维度,还不知道,因此是None
  • [] -> 标签没有填充。
  • [] -> 序列长度没有填充。
# 选取批量大小
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()])

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

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

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

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

教程步骤

  • 下载并处理 Kaggle 上提供的 FER2013 数据集。
  • 整个数据集上的探索性数据分析。
  • 将数据集拆分为训练和开发数据集。
  • 标准化图像。
  • 使用tf.data.Dataset API 遍历训练和开发数据集。
  • 在 Eager 模式下为 CNN 创建一个类。
  • 能够保存模型或从先前的检查点恢复。
  • 创建一个损失函数,一个优化器和一个梯度计算函数。
  • 用梯度下降训练模型。
  • 从头开始或者从预训练模型开始。
  • 在训练期间可视化表现并计算准确率。
  • 使用集成梯度可视化样本图像上的 CNN 归属。
  • 使用 OpenCV 和 Haar 级联算法在新图像上测试 CNN。

导入有用的库

# 导入 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…
1 0 151 150 147 155 148 133 111 140 170 174 182 15…
2 2 231 212 156 164 174 138 161 173 182 200 106 38…
3 4 24 32 36 30 32 23 19 20 30 41 21 22 32 34 21 1…
4 6 4 0 0 0 0 0 0 0 0 0 0 0 3 15 23 28 48 50 58 84…
# 获取每个表情的含义
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
1 Neutral
2 Sad
3 Fear
4 Angry
5 Surprise
6 Disgust

如你所见,数据集非常不平衡。 特别是对于情绪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

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


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);

计算准确率

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)

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

网络摄像头测试

最后,你可以在任何新的图像或视频集上测试 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 的展开输出,从磁盘批量读取数据)。 我希望能够让你看看,在你可能感兴趣的任何领域中,如何继续建立你自己的模型,不管它们有多复杂。

教程步骤

  • 下载原始数据并将其转换为 TFRecords( TensorFlow 默认文件格式)。
  • 准备一个数据集迭代器,它从磁盘中批量读取数据,并自动将可变长度的输入数据填充到批量中的最大大小。
  • 使用 LSTM 和 UGRNN 单元构建单词级 RNN 模型。
  • 在测试数据集上比较两个单元的性能。
  • 保存/恢复训练模型
  • 在新评论上测试网络
  • 可视化 RNN 激活

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

导入实用的库

# 导入函数来编写和解析 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)

猜你喜欢

转载自blog.csdn.net/wizardforcel/article/details/81211571