ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 二、在 Eager 模式中使用指标 大家好! 在本教程中,我们将学习如何使用各种指标来评估在 TensorFlow 中使用 Eager 模式时神经网络的表现。 我玩了很久 TensorFlow Eager 模式,我喜欢它。对我来说,与使用声明模式相比,API 看起来非常直观,现在一切看起来都更容易构建。 我现在发现的主要不便之处(我使用的是 1.7 版)是使用 Eager 模式时,`tf.metrics`还不兼容。 尽管如此,我已经构建了几个函数,可以帮助你评估网络的表现,同时仍然享受凭空构建网络的强大之处。 教程步骤: ![](https://img.kancloud.cn/86/e1/86e1a28ce30d917cd72d07d45efe8f21_1248x576.png) 我选择了三个案例: 多分类 对于此任务,我们将使用准确率,混淆矩阵和平均精度以及召回率,来评估我们模型的表现。 不平衡的二分类 当我们处理不平衡的数据集时,模型的准确率不是可靠的度量。 因此,我们将使用 ROC-AUC 分数,这似乎是一个更适合不平衡问题的指标。 回归 为了评估我们的回归模型的性能,我们将使用 R ^ 2 分数(确定系数)。 我相信这些案例的多样性足以帮助你进一步学习任何机器学习项目。 如果你希望我添加下面未遇到的任何额外指标,请告知我们,我会尽力在以后添加它们。 那么,让我们开始吧! TensorFlow 版本 - 1.7 ## 导入重要的库并开启 Eager 模式 ```py # 导入 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() ``` ## 第一部分:用于多分类的的数据集 ```py 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 ``` ### 特征标准化 每个特征的比例变化很大,如下面的单元格所示。 为了加快训练速度,我们将每个特征标准化为零均值和单位标准差。 这个过程称为标准化,它对神经网络的收敛非常有帮助。 ```py # 数据集标准化 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 空间中表示。 ```py 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://img.kancloud.cn/c2/ca/c2ca23f9efc4b508c59bc33bd1b24b6d_393x285.png) 好的,所以这些类看起来很容易分开。 顺便说一句,我实际上在特征标准化之前尝试使用 PCA,粉色和黄色类重叠。 通过在降维之前标准化特征,我们设法在它们之间获得了清晰的界限。 ### 让我们使用 TensorFlow Eager API 构建双层神经网络 你可能已经注意到,使用 TensorFlow Eager 构建模型的最方便方法是使用类。 我认为,为模型使用类可以更容易地组织和添加新组件。 你只需定义初始化期间要使用的层,然后在预测期间使用它们。 它使得在预测阶段更容易阅读模型的架构。 ```py 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`类。 在批量训练模型时,此指标非常有用,因为它会在每次调用时计算批量的平均精度。 当我们在每个步骤中使用整个数据集训练模型时,我们将重置此指标,因为我们不希望它跟踪运行中的平均值。 ```py # 创建输入特征和标签。将数据从 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://img.kancloud.cn/55/bf/55bfe1e8059771f08908eedf7f50bdc8_400x285.png) ### 混淆矩阵 在训练完算法后展示混淆矩阵是一种很好的方式,可以全面了解网络表现。 TensorFlow 具有内置函数来计算混淆矩阵,幸运的是它与 Eager 模式兼容。 因此,让我们可视化此数据集的混淆矩阵。 ```py # 获得整个数据集上的预测 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]] ''' ``` 对角矩阵显示真正例,而矩阵的其它地方显示假正例。 ### 精准率得分 上面计算的混淆矩阵使得计算平均精确率非常容易。 我将在下面实现一个函数,它会自动为你计算。 你还可以指定每个类的权重。 例如,由于某些原因,第二类的精确率可能对你来说更重要。 ```py 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 ``` ### 召回率得分 平均召回率的计算与精确率非常相似。 我们不是对列进行求和,而是对行进行求和,来获得真正例和假负例的总数。 ```py 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 得分。 那么,让我们构建我们的不平衡数据集并开始研究它! ```py 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://img.kancloud.cn/c8/d4/c8d4da11f855398e05cd3d9654091172_393x285.png) 我们将使用相同的神经网络架构。 我们只需用`num_classes = 2`初始化模型,因为我们正在处理二分类问题。 ```py # Numpy 数组变为张量 X = tf.constant(X) y = tf.constant(y) ``` 让我们将模型只训练几个迭代,来避免过拟合。 ```py # 定义优化器 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 曲线下的面积,我们将使用黎曼和和梯形规则。 如果你想了解更多信息,请点击[此处](https://www.khanacademy.org/math/ap-calculus-ab/ab-accumulation-riemann-sums/ab-midpoint-trapezoid/a/understanding-the-trapezoid-rule)。 ### ROC-AUC 函数 ```py 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 曲线 ```py # 阈值更多意味着曲线下的近似面积的粒度更高 # 随意尝试阈值的数量 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://img.kancloud.cn/8e/69/8e6907057dec20ad7bc4e0c7d3217f0b_394x283.png) ## 第三部分:用于回归的数据集 我们最终的数据集为简单的回归任务而创建。 在前两个问题中,网络的输出表示样本所属的类。这里网络的输出是连续的,是一个实数。 我们的输入数据集仅包含一个特征,以便使绘图保持简单。 标签`y`是实数向量。 让我们创建我们的玩具数据集! ```py X, y = make_regression(n_samples=100, n_features=1, n_informative=1, noise=30, random_state=2018) ``` ### 展示输入特征和标签 为了更好地了解我们正在处理的问题,让我们绘制标签和输入特征。 ```py pltplt..scatterscatter((XX,, yy);); pltplt..xlabelxlabel(('Input''Input',, fontsizefontsize=15); plt.ylabel('Target', fontsize=15); plt.title('Toy regression problem', fontsize=15); ``` ![](https://img.kancloud.cn/4b/5e/4b5ebe6835594a6013f760847a31a7b9_405x285.png) ```py # Numpy 数组转为张量 X = tf.constant(X) y = tf.constant(y) y = tf.reshape(y, [-1,1]) # 从行向量变为列向量 ``` ### 用于回归任务的神经网络 我们可以重复使用上面创建的双层神经网络。 由于我们只需要预测一个实数,因此网络的输出大小为 1。 我们必须重新定义我们的损失函数,因为我们无法继续使用`softmax`交叉熵损失。 相反,我们将使用均方误差损失函数。 我们还将定义一个新的优化器,其学习速率比前一个更小。 随意调整迭代的数量。 ```py # 定义优化器 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 模式下运行时,我在下面的单元格中为它创建了一个小函数。 ```py # 计算 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_min`和`X_max`之间的线性空间。 ```py # 创建 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://img.kancloud.cn/c9/54/c95448d42d6e316401341bd3fec49e3c_405x285.png)