TensorFlow 深度学习第二版:1~5(1)https://developer.aliyun.com/article/1426791
三、实现前馈神经网络
自动识别手写数字是一个重要的问题,可以在许多实际应用中找到。在本节中,我们将实现一个前馈网络来解决这个问题。
图 3:从 MNIST 数据库中提取的数据示例
为了训练和测试已实现的模型,我们将使用一个名为 MNIST 的手写数字最着名的数据集。 MNIST 数据集是一个包含 60,000 个示例的训练集和一个包含 10,000 个示例的测试集。存储在示例文件中的数据示例如上图所示。
源图像最初是黑白的。之后,为了将它们标准化为20×20
像素的大小,由于抗混叠滤波器用于调整大小的效果,引入了中间亮度级别。随后,在28×28
像素的区域中将图像聚焦在像素的质心中,以便改善学习过程。整个数据库存储在四个文件中:
train-images-idx3-ubyte.gz
:训练集图像(9912422 字节)train-labels-idx1-ubyte.gz
:训练集标签(28881 字节)t10k-images-idx3-ubyte.gz
:测试集图像(1648877 字节)t10k-labels-idx1-ubyte.gz
:测试集标签(4542 字节)
每个数据库包含两个文件的 ;第一个包含图像,而第二个包含相应的标签。
探索 MNIST 数据集
让我们看一下如何访问 MNIST 数据的简短示例,以及如何显示所选图像。为此,只需执行Explore_MNIST.py
脚本。首先,我们必须导入 numpy,因为我们必须进行一些图像处理:
import numpy as np
Matplotlib 中的pyplot
函数用于绘制图像:
import matplotlib.pyplot as plt
我们将使用tensorflow.examples.tutorials.mnist
中的input_data
类,它允许我们下载 MNIST 数据库并构建数据集:
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data
然后我们使用read_data_sets
方法加载数据集:
import os dataPath = "temp/" if not os.path.exists(dataPath): os.makedirs(dataPath) input = input_data.read_data_sets(dataPath, one_hot=True)
图像将保存在temp/
目录中。现在让我们看看图像和标签的形状:
print(input.train.images.shape) print(input.train.labels.shape) print(input.test.images.shape) print(input.test.labels.shape)
以下是上述代码的输出:
>>> (55000, 784) (55000, 10) (10000, 784) (10000, 10)
使用 Python 库matplotlib
,我们想要可视化一个数字:
image_0 = input.train.images[0] image_0 = np.resize(image_0,(28,28)) label_0 = input.train.labels[0] print(label_0)
以下是上述代码的输出:
>>> [ 0\. 0\. 0\. 0\. 0\. 0\. 0\. 1\. 0\. 0.]
数字1
是数组的第八个位置。这意味着我们图像的数字是数字 7。最后,我们必须验证数字是否真的是 7。我们可以使用导入的plt
函数来绘制image_0
张量:
plt.imshow(image_0, cmap='Greys_r') plt.show()
图 4:从 MNIST 数据集中提取的图像
Softmax 分类器
在上一节中,我们展示了如何访问和操作 MNIST 数据集。在本节中,我们将看到如何使用前面的数据集来解决 TensorFlow 手写数字的分类问题。我们将应用所学的概念来构建更多神经网络模型,以便评估和比较所采用的不同方法的结果。
将要实现的第一个前馈网络架构如下图所示:
图 5:softmax 神经网络架构
我们将构建一个五层网络:第一层到第四层是 Sigmoid 结构,第五层是 softmax 激活函数。请记住,定义此网络是为了激活它是一组正值,总和等于 1。这意味着输出的第j
个值是与网络输入对应的类j
的概率。让我们看看如何实现我们的神经网络模型。
为了确定网络的适当大小(即,层中的神经元或单元的数量),即隐藏层的数量和每层神经元的数量,通常我们依赖于一般的经验标准,个人经验或适当的测试。这些是需要调整的一些超参数。在本章的后面,我们将看到一些超参数优化的例子。
下表总结了已实现的网络架构。它显示了每层神经元的数量,以及相应的激活函数:
层 | 神经元数量 | 激活函数 |
1 | L = 200 |
Sigmoid |
2 | M = 100 |
Sigmoid |
3 | N = 60 |
Sigmoid |
4 | O = 30 |
Sigmoid |
5 | 10 |
Softmax |
前四层的激活函数是 Sigmoid 函数。激活函数的最后一层始终是 softmax,因为网络的输出必须表示输入数字的概率。通常,中间层的数量和大小会极大地影响的网络表现:
- 以积极的方式,因为在这些层上是基于网络推广的能力,并检测输入的特殊特征
- 以负面的方式,因为如果网络是冗余的,那么它会不必要地减轻学习阶段的负担
为此,只需执行five_layers_sigmoid.py
脚本。首先,我们将通过导入以下库来开始实现网络:
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data import math from tensorflow.python.framework import ops import random import os
接下来,我们将设置以下配置参数:
logs_path = 'log_sigmoid/' # logging path batch_size = 100 # batch size while performing training learning_rate = 0.003 # Learning rate training_epochs = 10 # training epoch display_epoch = 1
然后,我们将下载图像和标签,并准备数据集:
dataPath = "temp/" if not os.path.exists(dataPath): os.makedirs(dataPath) mnist = input_data.read_data_sets(dataPath, one_hot=True) # MNIST to be downloaded
从输入层开始,我们现在将看看如何构建网络架构。输入层现在是形状[1×784]
的张量 - 即[1,28 * 28]
,它代表要分类的图像:
X = tf.placeholder(tf.float32, [None, 784], name='InputData') # image shape 28*28=784 XX = tf.reshape(X, [-1, 784]) # reshape input Y_ = tf.placeholder(tf.float32, [None, 10], name='LabelData') # 0-9 digits => 10 classes
第一层接收要分类的输入图像的像素,与W1
权重连接组合,并添加到B1
偏差张量的相应值:
W1 = tf.Variable(tf.truncated_normal([784, L], stddev=0.1)) # Initialize random weights for the hidden layer 1 B1 = tf.Variable(tf.zeros([L])) # Bias vector for layer 1
第一层通过 sigmoid 激活函数将其输出发送到第二层:
Y1 = tf.nn.sigmoid(tf.matmul(XX, W1) + B1) # Output from layer 1
第二层从第一层接收Y1
输出,将其与W2
权重连接组合,并将其添加到B2
偏差张量的相应值:
W2 = tf.Variable(tf.truncated_normal([L, M], stddev=0.1)) # Initialize random weights for the hidden layer 2 B2 = tf.Variable(tf.ones([M])) # Bias vector for layer 2
第二层通过 sigmoid 激活函数将其输出发送到第三层:
Y2 = tf.nn.sigmoid(tf.matmul(Y1, W2) + B2) # Output from layer 2
第三层接收来自第二层的Y2
输出,将其与W3
权重连接组合,并将其添加到B3
偏差张量的相应值:
W3 = tf.Variable(tf.truncated_normal([M, N], stddev=0.1)) # Initialize random weights for the hidden layer 3 B3 = tf.Variable(tf.ones([N])) # Bias vector for layer 3
第三层通过 sigmoid 激活函数将其输出发送到第四层:
Y3 = tf.nn.sigmoid(tf.matmul(Y2, W3) + B3) # Output from layer 3
第四层接收来自第三层的Y3
输出,将其与W4
权重连接组合,并将其添加到B4
偏差张量的相应值:
W4 = tf.Variable(tf.truncated_normal([N, O], stddev=0.1)) # Initialize random weights for the hidden layer 4 B4 = tf.Variable(tf.ones([O])) # Bias vector for layer 4
然后通过 Sigmoid 激活函数将第四层的输出传播到第五层:
Y4 = tf.nn.sigmoid(tf.matmul(Y3, W4) + B4) # Output from layer 4
第五层将在输入中接收来自第四层的激活O = 30
,该激活将通过softmax
激活函数,转换为每个数字的相应概率类别:
W5 = tf.Variable(tf.truncated_normal([O, 10], stddev=0.1)) # Initialize random weights for the hidden layer 5 B5 = tf.Variable(tf.ones([10])) # Bias vector for layer 5 Ylogits = tf.matmul(Y4, W5) + B5 # computing the logits Y = tf.nn.softmax(Ylogits)# output from layer 5
这里,我们的损失函数是目标和softmax
激活函数之间的交叉熵,应用于模型的预测:
cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=Ylogits, labels=Y) # final outcome using softmax cross entropy cost_op = tf.reduce_mean(cross_entropy)*100
另外,我们定义correct_prediction
和模型的准确率:
correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(Y_, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
现在我们需要使用优化器来减少训练误差。与简单的GradientDescentOptimizer
相比,AdamOptimizer
具有几个优点。实际上,它使用更大的有效步长,算法将收敛到此步长而不进行微调:
# Optimization op (backprop) train_op = tf.train.AdamOptimizer(learning_rate).minimize(cost_op)
Optimizer
基类提供了计算损失梯度的方法,并将梯度应用于变量。子类集合实现了经典的优化算法,例如GradientDescent
和Adagrad
。在 TensorFlow 中训练 NN 模型时,我们从不实例化Optimizer
类本身,而是实例化以下子类之一
tf.train.Optimizer
tf.train.GradientDescentOptimizer
tf.train.AdadeltaOptimizer
tf.train.AdagradOptimizer
tf.train.AdagradDAOptimizer
tf.train.MomentumOptimizer
tf.train.AdamOptimizer
tf.train.FtrlOptimizer
tf.train.ProximalGradientDescentOptimizer
tf.train.ProximalAdagradOptimizer
tf.train.RMSPropOptimizer
见此链接和tf.contrib.opt
用于更多优化器。
然后让我们构建一个将所有操作封装到范围中的模型,使 TensorBoard 的图可视化更加方便:
# Create a summary to monitor cost tensor tf.summary.scalar("cost", cost_op) # Create a summary to monitor accuracy tensor tf.summary.scalar("accuracy", accuracy) # Merge all summaries into a single op summary_op = tf.summary.merge_all()
最后,我们将开始训练:
with tf.Session() as sess: # Run the initializer sess.run(init_op) # op to write logs to TensorBoard writer = tf.summary.FileWriter(logs_path, graph=tf.get_default_graph()) for epoch in range(training_epochs): batch_count = int(mnist.train.num_examples/batch_size) for i in range(batch_count): batch_x, batch_y = mnist.train.next_batch(batch_size) _,summary = sess.run([train_op, summary_op], feed_dict={X: batch_x, Y_: batch_y}) writer.add_summary(summary, epoch * batch_count + i) print("Epoch: ", epoch) print("Optimization Finished!") print("Accuracy: ", accuracy.eval(feed_dict={X: mnist.test.images, Y_: mnist.test.labels}))
定义摘要和会话运行的源代码几乎与前一个相同。我们可以直接转向评估实现的模型。运行模型时,我们有以下输出:
运行此代码后的最终测试设置准确率应约为 97%:
Extracting temp/train-images-idx3-ubyte.gz Extracting temp/train-labels-idx1-ubyte.gz Extracting temp/t10k-images-idx3-ubyte.gz Extracting temp/t10k-labels-idx1-ubyte.gz Epoch: 0 Epoch: 1 Epoch: 2 Epoch: 3 Epoch: 4 Epoch: 5 Epoch: 6 Epoch: 7 Epoch: 8 Epoch: 9 Optimization Finished! Accuracy: 0.9 715
现在我们可以通过在运行文件夹中打开终端然后执行以下命令来移动到 TensorBoard:
$> tensorboard --logdir='log_sigmoid/' # if required, provide absolute path
然后我们在localhost
上打开浏览器。在下图中,我们显示了成本函数的趋势,作为示例数量的函数,在训练集上,以及测试集的准确率:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJptXo1w-1681565849696)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-tf-2e-zh/img/B09698_03_06.jpg)]
图 6:测试集上的准确率函数,以及训练集上的成本函数
成本函数随着迭代次数的增加而减少。如果没有发生这种情况,则意味着出现了问题。在最好的情况下,这可能只是因为某些参数未正确设置。在最坏的情况下,构建的数据集中可能存在问题,例如,信息太少或图像质量差。如果发生这种情况,我们必须直接修复数据集。
到目前为止,我们已经看到了 FFNN 的实现。但是,使用真实数据集探索更有用的 FFNN 实现会很棒。我们将从 MLP 开始。
实现多层感知器(MLP)
感知器单层 LTU 组成,每个神经元连接到所有输入。这些连接通常使用称为输入神经元的特殊传递神经元来表示:它们只输出它们被输入的任何输入。此外,通常添加额外的偏置特征(x0 = 1
)。
这种偏置特征通常使用称为偏置神经元的特殊类型的神经元来表示,一直输出 1。具有两个输入和三个输出的感知器在图 7 中表示。该感知器可以同时将实例分类为三个不同的二元类,这使其成为多输出分类器:
图 7:具有两个输入和三个输出的感知器
由于每个输出神经元的决策边界是线性的,因此感知器无法学习复杂模式。然而,如果训练实例是线性可分的,研究表明该算法将收敛于称为“感知器收敛定理”的解决方案。
MLP 是 FFNN,这意味着它是来自不同层的神经元之间的唯一连接。更具体地,MLP 由一个(通过)输入层,一个或多个 LTU 层(称为隐藏层)和一个称为输出层的 LTU 的最后一层组成。除输出层外,每一层都包含一个偏置神经元,并作为完全连接的二分图连接到下一层:
图 8:MLP 由一个输入层,一个隐藏层和一个输出层组成
训练 MLP
MLP 在 1986 年首次使用反向传播训练算法成功训练。然而,现在这种算法的优化版本被称为梯度下降。在训练阶段期间,对于每个训练实例,算法将其馈送到网络并计算每个连续层中的每个神经元的输出。
训练算法测量网络的输出误差(即,期望输出和网络的实际输出之间的差异),并计算最后隐藏层中每个神经元对每个输出神经元误差的贡献程度。然后,它继续测量这些误差贡献中有多少来自先前隐藏层中的每个神经元,依此类推,直到算法到达输入层。通过在网络中向后传播误差梯度,该反向传递有效地测量网络中所有连接权重的误差梯度。
在技术上,通过反向传播方法计算每层的成本函数的梯度。梯度下降的想法是有一个成本函数,显示某些神经网络的预测输出与实际输出之间的差异:
图 9:用于无监督学习的 ANN 的示例实现
有几种已知类型的代价函数,例如平方误差函数和对数似然函数。该成本函数的选择可以基于许多因素。梯度下降法通过最小化此成本函数来优化网络的权重。步骤如下:
- 权重初始化
- 计算神经网络的预测输出,通常称为转发传播步骤
- 计算成本/损失函数。一些常见的成本/损失函数包括对数似然函数和平方误差函数
- 计算成本/损失函数的梯度。对于大多数 DNN 架构,最常见的方法是反向传播
- 基于当前权重的权重更新,以及成本/损失函数的梯度
- 步骤 2 到 5 的迭代,直到成本函数,达到某个阈值或经过一定量的迭代
图 9 中可以看到梯度下降的图示。该图显示了基于网络权重的神经网络的成本函数。在梯度下降的第一次迭代中,我们将成本函数应用于一些随机初始权重。对于每次迭代,我们在梯度方向上更新权重,这对应于图 9 中的箭头。重复更新直到一定次数的迭代或直到成本函数达到某个阈值。
使用 MLP
多层感知器通常用于以监督方式解决分类和回归问题。尽管 CNN 逐渐取代了它们在图像和视频数据中的实现,但仍然可以有效地使用低维和数字特征 MLP:可以解决二元和多类分类问题。
图 10:用于分类的现代 MLP(包括 ReLU 和 softmax)
然而,对于多类分类任务和训练,通常通过用共享 softmax 函数替换各个激活函数来修改输出层。每个神经元的输出对应于相应类的估计概率。请注意,信号仅在一个方向上从输入流向输出,因此该架构是 FFNN 的示例。
作为案例研究,我们将使用银行营销数据集。这些数据与葡萄牙银行机构的直接营销活动有关。营销活动基于电话。通常,不止一次联系同一个客户,以评估产品(银行定期存款)是(是)还是不(否)订阅。 目标是使用 MLP 来预测客户是否会订阅定期存款(变量 y),即二元分类问题。
数据集描述
我想在此承认有两个来源。这个数据集被用于 Moro 和其他人发表的一篇研究论文中:一种数据驱动的方法来预测银行电话营销的成功,决策支持系统,Elsevier,2014 年 6 月。后来,它被捐赠给了 UCI 机器学习库,可以从此链接下载。
根据数据集描述,有四个数据集:
bank-additional-full.csv
:这包括所有示例(41,188)和 20 个输入,按日期排序(从 2008 年 5 月到 2010 年 11 月)。这些数据非常接近 Moro 和其他人分析的数据bank-additional.csv
:这包括 10% 的例子(4119),从 1 和 20 个输入中随机选择bank-full.csv
:这包括按日期排序的所有示例和 17 个输入(此数据集的较旧版本,输入较少)bank.csv
:这包括 10% 的示例和 17 个输入,从 3 中随机选择(此数据集的较旧版本,输入较少)
数据集中有 21 个属性。独立变量可以进一步分类为与客户相关的数据(属性 1 到 7),与当前活动的最后一次联系(属性 8 到 11)相关。其他属性(属性 12 至 15)以及社会和经济背景属性(属性 16 至 20)被分类。因变量由 y 指定,最后一个属性(21):
ID | 属性 | 说明 |
1 | age |
年龄数字。 |
2 | job |
这是类别格式的职业类型,具有可能的值:admin ,blue-collar ,entrepreneur ,housemaid ,management ,retired ,self-employed ,services ,student ,technician ,unemployed 和unknown 。 |
3 | marital |
这是类别格式的婚姻状态,具有可能的值:divorced (或widowed ),married ,single 和unknown 。 |
4 | education |
这是类别格式的教育背景,具有如下可能的值:basic.4y ,basic.6y ,basic.9y ,high.school ,illiterate ,professional.course ,university.degree 和unknown 。 |
五 | default |
这是类别格式的信用,默认情况下可能包含:no ,yes 和unknown 。 |
6 | housing |
客户是否有住房贷款? |
7 | loan |
类别格式的个人贷款,具有可能的值:no ,yes 和unknown 。 |
8 | contact |
这是类别格式的通信类型,具有可能的值:cellular 或telephone 。 |
9 | month |
这是类别格式的一年中最后一个通话月份,具有可能的值:jan ,feb ,mar ,… nov 和dec 。 |
10 | day_of_week |
这是类别格式的一周中的最后一个通话日,具有可能的值:mon ,tue ,wed ,thu 和fri 。 |
11 | duration |
这是以秒为单位的最后一次通话持续时间(数值)。此属性高度影响输出目标(例如,如果duration = 0 ,则y = no )。然而,在通话之前不知道持续时间。另外,在通话结束后,y 显然是已知的。因此,此输入仅应包括在基准目的中,如果打算采用现实的预测模型,则应将其丢弃。 |
12 | campaign |
这是活动期间此客户的通话数量。 |
13 | pdays |
这是上一个广告系列和客户的上次通话之后经过的天数(数字 -999 表示之前未联系过客户)。 |
14 | previous |
这是之前此广告系列和此客户的通话数量(数字)。 |
15 | poutcome |
上一次营销活动的结果(类别:failure ,nonexistent 和success )。 |
16 | emp.var.rate |
这是就业变化率的季度指标(数字)。 |
17 | cons.price.idx |
这是消费者价格指数的月度指标(数字)。 |
18 | cons.conf.idx |
这是消费者信心指数的月度指标(数字)。 |
19 | euribor3m |
这是 3 个月的 euribor 费率的每日指标(数字)。 |
20 | nr.employed |
这是员工数的季度指标(数字)。 |
21 | y |
表示客户是否拥有定期存款,可能值是二元:yes 和no 。 |
预处理
您可以看到数据集尚未准备好直接输入 MLP 或 DBN 分类器,因为该特征与数值和分类值混合在一起。此外,结果变量具有分类值。因此,我们需要将分类值转换为数值,以便特征和结果变量以数字形式。下一步显示了此过程。有关此预处理,请参阅preprocessing_b.py
文件。
首先,我们必须加载预处理所需的所需包和库:
import pandas as pd import numpy as np from sklearn import preprocessing
然后从上述 URL 下载数据文件并将其放在方便的位置 - 比如说input
:
然后,我们加载并解析数据集:
data = pd.read_csv('input/bank-additional-full.csv', sep = ";")
接下来,我们将提取变量名称:
var_names = data.columns.tolist()
现在,基于表 1 中的数据集描述,我们将提取分类变量:
categs = ['job','marital','education','default','housing','loan','contact','month','day_of_week','duration','poutcome','y']
然后,我们将提取定量变量:
# Quantitative vars quantit = [i for i in var_names if i not in categs]
然后让我们得到分类变量的虚拟变量:
job = pd.get_dummies(data['job']) marital = pd.get_dummies(data['marital']) education = pd.get_dummies(data['education']) default = pd.get_dummies(data['default']) housing = pd.get_dummies(data['housing']) loan = pd.get_dummies(data['loan']) contact = pd.get_dummies(data['contact']) month = pd.get_dummies(data['month']) day = pd.get_dummies(data['day_of_week']) duration = pd.get_dummies(data['duration']) poutcome = pd.get_dummies(data['poutcome'])
现在,是时候映射变量来预测:
dict_map = dict() y_map = {'yes':1,'no':0} dict_map['y'] = y_map data = data.replace(dict_map) label = data['y'] df_numerical = data[quantit] df_names = df_numerical .keys().tolist()
一旦我们将分类变量转换为数值变量,下一个任务就是正则化数值变量。因此,使用归一化,我们将单个样本缩放为具有单元规范。如果您计划使用二次形式(如点积或任何其他内核)来量化任何样本对的相似性,则此过程非常有用。该假设是在文本分类和聚类上下文中经常使用的向量空间模型的基础。
那么,让我们来衡量量化变量:
min_max_scaler = preprocessing.MinMaxScaler() x_scaled = min_max_scaler.fit_transform(df_numerical) df_temp = pd.DataFrame(x_scaled) df_temp.columns = df_names
现在我们有(原始)数值变量的临时数据帧,下一个任务是将所有数据帧组合在一起并生成正则化数据帧。我们将使用熊猫:
normalized_df = pd.concat([df_temp, job, marital, education, default, housing, loan, contact, month, day, poutcome, duration, label], axis=1)
最后,我们需要将结果数据帧保存在 CSV 文件中,如下所示:
normalized_df.to_csv('bank_normalized.csv', index = False)
用于客户订阅评估的 TensorFlow 中的 MLP 实现
对于这个例子,我们将使用我们在前面的例子中正则化的银行营销数据集。有几个步骤可以遵循。首先,我们需要导入 TensorFlow,以及其他必要的包和模块:
import tensorflow as tf import pandas as pd import numpy as np import os from sklearn.cross_validation import train_test_split # for random split of train/test
现在,我们需要加载正则化的银行营销数据集,其中所有特征和标签都是数字。为此,我们使用 pandas 库中的read_csv()
方法:
FILE_PATH = 'bank_normalized.csv' # Path to .csv dataset raw_data = pd.read_csv(FILE_PATH) # Open raw .csv print("Raw data loaded successfully...\n")
以下是上述代码的输出:
>>> Raw data loaded successfully...
如前一节所述,调整 DNN 的超参数并不简单。但是,它通常取决于您正在处理的数据集。对于某些数据集,可能的解决方法是根据与数据集相关的统计信息设置这些值,例如,训练实例的数量,输入大小和类的数量。
DNN 不适用于小型和低维数据集。在这些情况下,更好的选择是使用线性模型。首先,让我们放置一个指向标签列本身的指针,计算实例数和类数,并定义训练/测试分流比,如下所示:
Y_LABEL = 'y' # Name of the variable to be predicted KEYS = [i for i in raw_data.keys().tolist() if i != Y_LABEL]# Name of predictors N_INSTANCES = raw_data.shape[0] # Number of instances N_INPUT = raw_data.shape[1] - 1 # Input size N_CLASSES = raw_data[Y_LABEL].unique().shape[0] # Number of classes TEST_SIZE = 0.25 # Test set size (% of dataset) TRAIN_SIZE = int(N_INSTANCES * (1 - TEST_SIZE)) # Train size
现在,让我们看一下我们将用于训练 MLP 模型的数据集的统计数据:
print("Variables loaded successfully...\n") print("Number of predictors \t%s" %(N_INPUT)) print("Number of classes \t%s" %(N_CLASSES)) print("Number of instances \t%s" %(N_INSTANCES)) print("\n")
以下是上述代码的输出:
>>> Variables loaded successfully... Number of predictors 1606 Number of classes 2 Number of instances 41188
下一个任务是定义其他参数,例如学习率,训练周期,批量大小和权重的标准偏差。通常,较低的训练率会帮助您的 DNN 学习更慢,但需要集中精力。请注意,我们需要定义更多参数,例如隐藏层数和激活函数。
LEARNING_RATE = 0.001 # learning rate TRAINING_EPOCHS = 1000 # number of training epoch for the forward pass BATCH_SIZE = 100 # batch size to be used during training DISPLAY_STEP = 20 # print the error etc. at each 20 step HIDDEN_SIZE = 256 # number of neurons in each hidden layer # We use tanh as the activation function, but you can try using ReLU as well ACTIVATION_FUNCTION_OUT = tf.nn.tanh STDDEV = 0.1 # Standard Deviations RANDOM_STATE = 100
前面的初始化是基于反复试验设置的 。因此,根据您的用例和数据类型,明智地设置它们,但我们将在本章后面提供一些指导。此外,对于前面的代码,RANDOM_STATE
用于表示训练的随机状态和测试分割。首先,我们将原始特征和标签分开:
data = raw_data[KEYS].get_values() # X data labels = raw_data[Y_LABEL].get_values() # y data
现在我们有标签,他们必须编码:
labels_ = np.zeros((N_INSTANCES, N_CLASSES)) labels_[np.arange(N_INSTANCES), labels] = 1
最后,我们必须拆分训练和测试集。如前所述,我们将保留 75% 的训练输入,剩下的 25% 用于测试集:
data_train, data_test, labels_train, labels_test = train_test_split(data,labels_,test_size = TEST_SIZE,random_state = RANDOM_STATE) print("Data loaded and splitted successfully...\n")
以下是上述代码的输出:
>>> Data loaded and splitted successfully
由于这是一个监督分类问题,我们应该有特征和标签的占位符:
如前所述,MLP 由一个输入层,几个隐藏层和一个称为输出层的最终 LTU 层组成。对于这个例子,我将把训练与四个隐藏层结合起来。因此,我们将分类器称为深度前馈 MLP。请注意,我们还需要在每个层中使用权重(输入层除外),以及每个层中的偏差(输出层除外)。通常,每个隐藏层包括偏置神经元,并且作为从一个隐藏层到另一个隐藏层的完全连接的二分图(前馈)完全连接到下一层。那么,让我们定义隐藏层的大小:
n_input = N_INPUT # input n labels n_hidden_1 = HIDDEN_SIZE # 1st layer n_hidden_2 = HIDDEN_SIZE # 2nd layer n_hidden_3 = HIDDEN_SIZE # 3rd layer n_hidden_4 = HIDDEN_SIZE # 4th layer n_classes = N_CLASSES # output m classes
由于这是一个监督分类问题,我们应该有特征和标签的占位符:
# input shape is None * number of input X = tf.placeholder(tf.float32, [None, n_input])
占位符的第一个维度是None
,这意味着我们可以有任意数量的行。第二个维度固定在多个特征上,这意味着每行需要具有该列数量的特征。
# label shape is None * number of classes y = tf.placeholder(tf.float32, [None, n_classes])
另外,我们需要另一个占位符用于丢弃,这是通过仅以某种可能性保持神经元活动(例如p < 1.0
,或者将其设置为零来实现)来实现的。请注意,这也是要调整的超参数和训练时间,而不是测试时间:
dropout_keep_prob = tf.placeholder(tf.float32)
使用此处给出的缩放,可以将相同的网络用于训练(使用dropout_keep_prob < 1.0
)和评估(使用dropout_keep_prob == 1.0
)。现在,我们可以定义一个实现 MLP 分类器的方法。为此,我们将提供四个参数,如输入,权重,偏置和丢弃概率,如下所示:
def DeepMLPClassifier(_X, _weights, _biases, dropout_keep_prob): layer1 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(_X, _weights['h1']), _biases['b1'])), dropout_keep_prob) layer2 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(layer1, _weights['h2']), _biases['b2'])), dropout_keep_prob) layer3 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(layer2, _weights['h3']), _biases['b3'])), dropout_keep_prob) layer4 = tf.nn.dropout(tf.nn.tanh(tf.add(tf.matmul(layer3, _weights['h4']), _biases['b4'])), dropout_keep_prob) out = ACTIVATION_FUNCTION_OUT(tf.add(tf.matmul(layer4, _weights['out']), _biases['out'])) return out
上述方法的返回值是激活函数的输出。前面的方法是一个存根实现,它没有告诉任何关于权重和偏置的具体内容,所以在我们开始训练之前,我们应该定义它们:
weights = { 'w1': tf.Variable(tf.random_normal([n_input, n_hidden_1],stddev=STDDEV)), 'w2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2],stddev=STDDEV)), 'w3': tf.Variable(tf.random_normal([n_hidden_2, n_hidden_3],stddev=STDDEV)), 'w4': tf.Variable(tf.random_normal([n_hidden_3, n_hidden_4],stddev=STDDEV)), 'out': tf.Variable(tf.random_normal([n_hidden_4, n_classes],stddev=STDDEV)), } biases = { 'b1': tf.Variable(tf.random_normal([n_hidden_1])), 'b2': tf.Variable(tf.random_normal([n_hidden_2])), 'b3': tf.Variable(tf.random_normal([n_hidden_3])), 'b4': tf.Variable(tf.random_normal([n_hidden_4])), 'out': tf.Variable(tf.random_normal([n_classes])) }
现在我们可以使用真实参数(输入层,权重,偏置和退出)调用前面的 MLP 实现,保持概率如下:
pred = DeepMLPClassifier(X, weights, biases, dropout_keep_prob)
我们建立了 MLP 模型,是时候训练网络了。首先,我们需要定义成本操作,然后我们将使用 Adam 优化器,它将慢慢学习并尝试尽可能减少训练损失:
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=pred, labels=y)) # Optimization op (backprop) optimizer = tf.train.AdamOptimizer(learning_rate = LEARNING_RATE).minimize(cost_op)
接下来,我们需要定义用于计算分类准确率的其他参数:
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) print("Deep MLP networks has been built successfully...") print("Starting training...")
之后,我们需要在启动 TensorFlow 会话之前初始化所有变量和占位符:
init_op = tf.global_variables_initializer()
现在,我们非常接近开始训练,但在此之前,最后一步是创建 TensorFlow 会话并按如下方式启动它:
sess = tf.Session() sess.run(init_op)
最后,我们准备开始在训练集上训练我们的 MLP。我们遍历所有批次并使用批量数据拟合以计算平均训练成本。然而,显示每个周期的训练成本和准确率会很棒:
for epoch in range(TRAINING_EPOCHS): avg_cost = 0.0 total_batch = int(data_train.shape[0] / BATCH_SIZE) # Loop over all batches for i in range(total_batch): randidx = np.random.randint(int(TRAIN_SIZE), size = BATCH_SIZE) batch_xs = data_train[randidx, :] batch_ys = labels_train[randidx, :] # Fit using batched data sess.run(optimizer, feed_dict={X: batch_xs, y: batch_ys, dropout_keep_prob: 0.9}) # Calculate average cost avg_cost += sess.run(cost, feed_dict={X: batch_xs, y: batch_ys, dropout_keep_prob:1.})/total_batch # Display progress if epoch % DISPLAY_STEP == 0: print("Epoch: %3d/%3d cost: %.9f" % (epoch, TRAINING_EPOCHS, avg_cost)) train_acc = sess.run(accuracy, feed_dict={X: batch_xs, y: batch_ys, dropout_keep_prob:1.}) print("Training accuracy: %.3f" % (train_acc)) print("Your MLP model has been trained successfully.")
以下是上述代码的输出:
>>> Starting training... Epoch: 0/1000 cost: 0.356494816 Training accuracy: 0.920 … Epoch: 180/1000 cost: 0.350044933 Training accuracy: 0.860 …. Epoch: 980/1000 cost: 0.358226758 Training accuracy: 0.910
干得好,我们的 MLP 模型已经成功训练!现在,如果我们以图形方式看到成本和准确率怎么办?我们来试试吧:
# Plot loss over time plt.subplot(221) plt.plot(i_data, cost_list, 'k--', label='Training loss', linewidth=1.0) plt.title('Cross entropy loss per iteration') plt.xlabel('Iteration') plt.ylabel('Cross entropy loss') plt.legend(loc='upper right') plt.grid(True)
以下是上述代码的输出 :
>>>
图 11:训练阶段每次迭代的交叉熵损失
上图显示交叉熵损失在 0.34 和 0.36 之间或多或少稳定,但波动很小。现在,让我们看看这对整体训练准确率有何影响:
# Plot train and test accuracy plt.subplot(222) plt.plot(i_data, acc_list, 'r--', label='Accuracy on the training set', linewidth=1.0) plt.title('Accuracy on the training set') plt.xlabel('Iteration') plt.ylabel('Accuracy') plt.legend(loc='upper right') plt.grid(True) plt.show()
以下是前面代码的输出:
>>>
图 12:每次迭代时训练集的准确率
我们可以看到训练准确率在 79% 和 96% 之间波动,但不会均匀地增加或减少。解决此问题的一种可能方法是添加更多隐藏层并使用不同的优化器,例如本章前面讨论的梯度下降。我们将丢弃概率提高到 100%,即 1.0。原因是也有相同的网络用于测试:
print("Evaluating MLP on the test set...") test_acc = sess.run(accuracy, feed_dict={X: data_test, y: labels_test, dropout_keep_prob:1.}) print ("Prediction/classification accuracy: %.3f" % (test_acc))
以下是上述代码的输出:
>>> Evaluating MLP on the test set... Prediction/classification accuracy: 0.889 Session closed!
因此,分类准确率约为 89%。一点也不差!现在,如果需要更高的精度,我们可以使用称为深度信任网络(DBN)的另一种 DNN 架构,可以以有监督或无监督的方式进行训练。
这是在其应用中作为分类器观察 DBN 的最简单方法。如果我们有一个 DBN 分类器,那么预训练方法是以类似于自编码器的无监督方式完成的,这将在第 5 章中描述,优化 TensorFlow 自编码器,分类器以受监督的方式训练(微调),就像 MLP 中的那样。
深度信念网络(DBNs)
为了克服 MLP 中的过拟合问题,我们建立了一个 DBN,进行无监督的预训练,为输入获得一组不错的特征表示,然后对训练集进行微调以获得预测。网络。
虽然 MLP 的权重是随机初始化的,但 DBN 使用贪婪的逐层预训练算法通过概率生成模型初始化网络权重。这些模型由可见层和多层随机潜在变量组成,这些变量称为隐藏单元或特征检测器。
堆叠 DBN 中的 RBM,形成无向概率图模型,类似于马尔可夫随机场(MRF):两层由可见神经元和隐藏神经元组成。
堆叠 RBM 中的顶部两层在它们之间具有无向的对称连接并形成关联存储器,而较低层从上面的层接收自上而下的定向连接:
图 13:RBM 作为构建块的 DBN 的高级视图
顶部两层在它们之间具有无向的对称连接并形成关联存储器,而较低层从前面的层接收自上而下的定向连接。几个 RBM 一个接一个地堆叠以形成 DBN。
受限玻尔兹曼机(RBMs)
RBM 是无向概率图模型,称为马尔科夫随机场。它由两层组成。第一层由可见神经元组成,第二层由隐藏神经元组成。图 14 显示了简单 RBM 的结构。可见单元接受输入,隐藏单元是非线性特征检测器。每个可见神经元都连接到所有隐藏的神经元,但同一层中的神经元之间没有内部连接:
图 14:简单 RBM 的结构
图 14 中的 RBM 由m
个可见单元组成,V = (v[1] ... v[m])
和n
个隐藏单元,H = (h[1] ... h[n])
。可见单元接受 0 到 1 之间的值,隐藏单元的生成值介于 0 和 1 之间。模型的联合概率是由以下等式给出的能量函数:
在前面的等式中,i = 1 ... m
,j = 1 ... n
,bi
和cj
分别是可见和隐藏单元的偏差,并且w[ij]
是v[i]
和h[j]
之间的权重。模型分配给可见向量v
的概率由下式给出:
在第二个等式中,Z
是分区函数,定义如下:
权重的学习可以通过以下等式获得:
在等式 4 中,学习率由eps
定义。通常,较小的eps
值可确保训练更加密集。但是,如果您希望网络快速学习,可以将此值设置得更高。
由于同一层中的单元之间没有连接,因此很容易计算第一项。p(h | v)
和p(v | h)
的条件分布是阶乘的,并且由以下等式中的逻辑函数给出:
因此,样本v[i] h[j]
是无偏的。然而,计算第二项的对数似然的计算成本是指数级的。虽然有可能得到第二项的无偏样本,但使用马尔可夫链蒙特卡罗(MCMC)的吉布斯采样,这个过程也不具有成本效益。相反,RBM 使用称为对比发散的有效近似方法。
通常,MCMC 需要许多采样步骤才能达到静止的收敛。运行吉布斯采样几步(通常是一步)足以训练一个模型,这称为对比分歧学习。对比分歧的第一步是用训练向量初始化可见单元。
下一步是使用等式 5 计算所有隐藏单元,同时使用可见单元,然后使用等式 4 从隐藏单元重建可见单元。最后,隐藏单元用重建的可见单元更新。因此,代替方程式 4,我们最终得到以下的权重学习模型:
简而言之,该过程试图减少输入数据和重建数据之间的重建误差。算法收敛需要多次参数更新迭代。迭代称为周期。输入数据被分成小批量,并且在每个小批量之后更新参数,具有参数的平均值。
最后,如前所述,RBM 最大化可见单元p(v)
的概率,其由模式和整体训练数据定义。它相当于最小化模型分布和经验数据分布之间的 KL 散度。
对比分歧只是这个目标函数的粗略近似,但它在实践中非常有效。虽然方便,但重建误差实际上是衡量学习进度的一个非常差的指标。考虑到这些方面,RBM 需要一些时间来收敛,但是如果你看到重建是不错的,那么你的算法效果很好。
构建一个简单的 DBN
单个隐藏层 RBM 无法从输入数据中提取所有特征,因为它无法对变量之间的关系进行建模。因此,一层接一个地使用多层 RBM 来提取非线性特征。在 DBN 中,首先使用输入数据训练 RBM,并且隐藏层以贪婪学习方法表示学习的特征。
第一 RBM 的这些学习特征用作第二 RBM 的输入,作为 DBN 中的另一层,如图 15 所示。类似地,第二层的学习特征用作另一层的输入。
这样,DBN 可以从输入数据中提取深度和非线性特征。最后一个 RBM 的隐藏层代表整个网络的学习特征。前面针对所有 RBM 层描述的学习特征的过程称为预训练。
无监督的预训练
假设您要处理复杂任务,您没有多少标记的训练数据。很难找到合适的 DNN 实现或架构来进行训练并用于预测分析。然而,如果您有大量未标记的训练数据,您可以尝试逐层训练层,从最低层开始,然后使用无监督的特征检测器算法向上移动。这就是 RBM(图 15)或自编码器(图 16)的工作原理。
图 15:使用自编码器在 DBN 中进行无监督的预训练
当您有一个复杂的任务需要解决时,无监督的预训练仍然是一个不错的选择,没有类似的模型可以重复使用,并且标记很少的训练数据,但是大量未标记的训练数据。目前的趋势是使用自编码器而不是 RBM;但是,对于下一节中的示例,RBM 将用于简化。读者也可以尝试使用自编码器而不是 RBM。
预训练是一种无监督的学习过程。在预训练之后,通过在最后一个 RBM 层的顶部添加标记层来执行网络的微调。此步骤是受监督的学习过程。无监督的预训练步骤尝试查找网络权重:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mfg130wX-1681565849700)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-tf-2e-zh/img/B09698_03_16.jpg)]
图 16:通过构建具有 RBM 栈的简单 DBN 在 DBN 中进行无监督预训练
监督的微调
在监督学习阶段(也称为监督微调)中,不是随机初始化网络权重,而是使用在预训练步骤中计算的权重来初始化它们。这样,当使用监督梯度下降时,DBN 可以避免收敛到局部最小值。
如前所述,使用 RBM 栈,DBN 可以构造如下:
- 使用参数
W[1]
训练底部 RBM(第一个 RBM) - 将第二层权重初始化为
W[2] = W[1]^T
,这可确保 DBN 至少与我们的基础 RBM 一样好
因此,将这些步骤放在一起,图 17 显示了由三个 RBM 组成的简单 DBN 的构造:
图 17:使用多个 RBM 构建简单的 DBN
现在,当调整 DBN 以获得更好的预测准确率时,我们应该调整几个超参数,以便 DBN 通过解开和改进W[2]
来拟合训练数据。综上所述,我们有了创建基于 DBN 的分类器或回归器的概念工作流程。
现在我们已经有足够的理论背景来介绍如何使用几个 RBM 构建 DBN,现在是时候将我们的理论应用于实践中了。在下一节中,我们将了解如何开发用于预测分析的监督 DBN 分类器。
用于客户订阅评估的 TensorFlow 中的 DBN 实现
在银行营销数据集的前一个示例中,我们使用 MLP 观察到大约 89% 的分类准确率。我们还将原始数据集标准化,然后将其提供给 MLP。在本节中,我们将了解如何为基于 DBN 的预测模型使用相同的数据集。
我们将使用 Md.Rezaul Karim 最近出版的书籍 Predictive Analytics with TensorFlow 的 DBN 实现,可以从 GitHub 下载。
前面提到的实现是基于 RBM 的简单,干净,快速的 DBN 实现,并且基于 NumPy 和 TensorFlow 库,以便利用 GPU 计算。该库基于以下两篇研究论文实现:
- Geoffrey E. Hinton,Simon Osindero 和 Yee-Whye Teh 的深度信念网快速学习算法。 Neural Computation 18.7(2006):1527-1554。
- 训练受限制的玻尔兹曼机:简介,Asja Fischer 和 Christian Igel。模式识别 47.1(2014):25-39。
我们将看到如何以无监督的方式训练 RBM,然后我们将以有监督的方式训练网络。简而言之,有几个步骤需要遵循。主分类器是classification_demo.py
。
提示
虽然在以监督和无监督的方式训练 DBN 时数据集不是那么大或高维度,但是在训练时间中会有如此多的计算,这需要巨大的资源。然而,RBM 需要大量时间来收敛。因此,我建议读者在 GPU 上进行训练,至少拥有 32 GB 的 RAM 和一个 Corei7 处理器。
我们将从加载所需的模块和库开始:
import numpy as np import pandas as pd from sklearn.datasets import load_digits from sklearn.model_selection import train_test_split from sklearn.metrics.classification import accuracy_score from sklearn.metrics import precision_recall_fscore_support from sklearn.metrics import confusion_matrix import itertools from tf_models import SupervisedDBNClassification import matplotlib.pyplot as plt
然后,我们加载前一个 MLP 示例中使用的已经正则化的数据集:
FILE_PATH = '../input/bank_normalized.csv' raw_data = pd.read_csv(FILE_PATH)
在前面的代码中,我们使用了 pandas read_csv()
方法并创建了一个DataFrame
。现在,下一个任务是按如下方式扩展特征和标签:
Y_LABEL = 'y' KEYS = [i for i in raw_data.keys().tolist() if i != Y_LABEL] X = raw_data[KEYS].get_values() Y = raw_data[Y_LABEL].get_values() class_names = list(raw_data.columns.values) print(class_names)
在前面的行中,我们已经分离了特征和标签。这些特征存储在X
中,标签位于Y
中。接下来的任务是将它们分成训练(75%)和测试集(25%),如下所示:
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.25, random_state=100)
现在我们已经有了训练和测试集,我们可以直接进入 DBN 训练步骤。但是,首先我们需要实例化 DBN。我们将以受监督的方式进行分类,但我们需要为此 DNN 架构提供超参数:
classifier = SupervisedDBNClassification(hidden_layers_structure=[64, 64],learning_rate_rbm=0.05,learning_rate=0.01,n_epochs_rbm=10,n_iter_backprop=100,batch_size=32,activation_function='relu',dropout_p=0.2)
在前面的代码段中,n_epochs_rbm
是预训练(无监督)和n_iter_backprop
用于监督微调的周期数。尽管如此,我们已经为这两个阶段定义了两个单独的学习率,分别使用learning_rate_rbm
和learning_rate
。
不过,我们将在本节后面描述SupervisedDBNClassification
的这个类实现。
该库具有支持 sigmoid,ReLU 和 tanh 激活函数的实现。此外,它利用 l2 正则化来避免过拟合。我们将按如下方式进行实际拟合:
classifier.fit(X_train, Y_train)
如果一切顺利,你应该在控制台上观察到以下进展:
[START] Pre-training step: >> Epoch 1 finished RBM Reconstruction error 1.681226 …. >> Epoch 3 finished RBM Reconstruction error 4.926415 >> Epoch 5 finished RBM Reconstruction error 7.185334 … >> Epoch 7 finished RBM Reconstruction error 37.734962 >> Epoch 8 finished RBM Reconstruction error 467.182892 …. >> Epoch 10 finished RBM Reconstruction error 938.583801 [END] Pre-training step [START] Fine tuning step: >> Epoch 0 finished ANN training loss 0.316619 >> Epoch 1 finished ANN training loss 0.311203 >> Epoch 2 finished ANN training loss 0.308707 …. >> Epoch 98 finished ANN training loss 0.288299 >> Epoch 99 finished ANN training loss 0.288900
由于 RBM 的权重是随机初始化的,因此重建和原始输入之间的差异通常很大。
从技术上讲,我们可以将重建误差视为重建值与输入值之间的差异。然后,在迭代学习过程中,将该误差反向传播 RBM 的权重几次,直到达到最小误差。
然而,在我们的情况下,重建达到 938,这不是那么大(即,不是无限),所以我们仍然可以期望良好的准确率。无论如何,经过 100 次迭代后,显示每个周期训练光泽的微调图如下:
图 18:每次迭代的 SGD 微调损失(仅 100 次迭代)
然而,当我重复前面的训练并微调 1000 个周期时,我没有看到训练损失有任何显着改善:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zHpigbKd-1681565849700)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-tf-2e-zh/img/B09698_03_19.jpg)]
图 19:每次迭代的 SGD 微调损失(1000 次迭代)
这是监督的 DBN 分类器的实现。此类为分类问题实现 DBN。它将网络输出转换为原始标签。在对标签映射执行索引之后,它还需要网络参数并返回列表。
然后,该类预测给定数据中每个样本的类的概率分布,并返回字典列表(每个样本一个)。最后,它附加了 softmax 线性分类器作为输出层:
class SupervisedDBNClassification(TensorFlowAbstractSupervisedDBN, ClassifierMixin): def _build_model(self, weights=None): super(SupervisedDBNClassification, self)._build_model(weights) self.output = tf.nn.softmax(self.y) self.cost_function = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=self.y, labels=self.y_)) self.train_step = self.optimizer.minimize(self.cost_function) @classmethod def _get_param_names(cls): return super(SupervisedDBNClassification, cls)._get_param_names() + ['label_to_idx_map', 'idx_to_label_map'] @classmethod def from_dict(cls, dct_to_load): label_to_idx_map = dct_to_load.pop('label_to_idx_map') idx_to_label_map = dct_to_load.pop('idx_to_label_map') instance = super(SupervisedDBNClassification, cls).from_dict(dct_to_load) setattr(instance, 'label_to_idx_map', label_to_idx_map) setattr(instance, 'idx_to_label_map', idx_to_label_map) return instance def _transform_labels_to_network_format(self, labels): """ Converts network output to original labels. :param indexes: array-like, shape = (n_samples, ) :return: """ new_labels, label_to_idx_map, idx_to_label_map = to_categorical(labels, self.num_classes) self.label_to_idx_map = label_to_idx_map self.idx_to_label_map = idx_to_label_map return new_labels def _transform_network_format_to_labels(self, indexes): return list(map(lambda idx: self.idx_to_label_map[idx], indexes)) def predict(self, X): probs = self.predict_proba(X) indexes = np.argmax(probs, axis=1) return self._transform_network_format_to_labels(indexes) def predict_proba(self, X): """ Predicts probability distribution of classes for each sample in the given data. :param X: array-like, shape = (n_samples, n_features) :return: """ return super(SupervisedDBNClassification, self)._compute_output_units_matrix(X) def predict_proba_dict(self, X): """ Predicts probability distribution of classes for each sample in the given data. Returns a list of dictionaries, one per sample. Each dict contains {label_1: prob_1, ..., label_j: prob_j} :param X: array-like, shape = (n_samples, n_features) :return: """ if len(X.shape) == 1: # It is a single sample X = np.expand_dims(X, 0) predicted_probs = self.predict_proba(X) result = [] num_of_data, num_of_labels = predicted_probs.shape for i in range(num_of_data): # key : label # value : predicted probability dict_prob = {} for j in range(num_of_labels): dict_prob[self.idx_to_label_map[j]] = predicted_probs[i][j] result.append(dict_prob) return result def _determine_num_output_neurons(self, labels): return len(np.unique(labels))
正如我们在前面的例子和运行部分中提到的那样,微调神经网络的参数是一个棘手的过程。有很多不同的方法,但我的知识并没有一刀切的方法。然而,通过前面的组合,我收到了更好的分类结果。另一个要选择的重要参数是学习率。根据您的模型调整学习率是一种可以采取的方法,以减少训练时间,同时避免局部最小化。在这里,我想讨论一些能够帮助我提高预测准确率的技巧,不仅适用于此应用,也适用于其他应用。
现在我们已经建立了模型,现在是评估其表现的时候了。为了评估分类准确率,我们将使用几个表现指标,如precision
,recall
和f1 score
。此外,我们将绘制混淆矩阵,以观察与真实标签相对应的预测标签。首先,让我们计算预测精度如下:
Y_pred = classifier.predict(X_test) print('Accuracy: %f' % accuracy_score(Y_test, Y_pred))
接下来,我们需要计算分类的precision
,recall
和f1 score
:
p, r, f, s = precision_recall_fscore_support(Y_test, Y_pred, average='weighted') print('Precision:', p) print('Recall:', r) print('F1-score:', f)
以下是上述代码的输出:
>>> Accuracy: 0.900554 Precision: 0.8824140209830381 Recall: 0.9005535592891133 F1-score: 0.8767190584424599
太棒了!使用我们的 DBN 实现,我们解决了与使用 MLP 相同的分类问题。尽管如此,与 MLP 相比,我们设法获得了稍微更好的准确率。
现在,如果要解决回归问题,要预测的标签是连续的,则必须使用SupervisedDBNRegression()
函数进行此实现。 DBN 文件夹中的回归脚本(即regression_demo.py
)也可用于执行回归操作。
但是,使用专门为回归 y 准备的另一个数据集将是更好的主意。您需要做的就是准备数据集,以便基于 TensorFlow 的 DBN 可以使用它。因此,为了最小化演示,我使用房价:高级回归技术数据集来预测房价。
调整超参数和高级 FFNN
神经网络的灵活性也是它们的主要缺点之一:有很多超参数可以调整。即使在简单的 MLP 中,您也可以更改层数,每层神经元的数量以及每层中使用的激活函数的类型。您还可以更改权重初始化逻辑,退出保持概率等。
另外,FFNN 中的一些常见问题,例如梯度消失问题,以及选择最合适的激活函数,学习率和优化器,是最重要的。
调整 FFNN 超参数
超参数是不在估计器中直接学习的参数。有可能并建议您在超参数空间中搜索最佳交叉验证得分。在构造估计器时提供的任何参数可以以这种方式优化。现在,问题是:您如何知道超参数的哪种组合最适合您的任务?当然,您可以使用网格搜索和交叉验证来为线性机器学习模型找到正确的超参数。
但是,对于 DNN,有许多超参数可供调整。由于在大型数据集上训练神经网络需要花费大量时间,因此您只能在合理的时间内探索超参数空间的一小部分。以下是一些可以遵循的见解。
此外,当然,正如我所说,您可以使用网格搜索或随机搜索,通过交叉验证,为线性机器学习模型找到正确的超参数。我们将在本节后面看到一些可能的详尽和随机网格搜索和交叉验证方法。
隐藏层数
对于许多问题,你可以从一个或两个隐藏层开始,这个设置可以很好地使用两个隐藏层,具有相同的神经元总数(见下文以了解一些神经元),训练时间大致相同。现在让我们看一些关于设置隐藏层数的朴素估计:
- 0:仅能表示线性可分离函数或决策
- 1:可以近似包含从一个有限空间到另一个有限空间的连续映射的任何函数
- 2:可以用任意精度表示任意决策边界,具有合理的激活函数,并且可以近似任何平滑映射到任何精度
但是,对于更复杂的问题,您可以逐渐增加隐藏层的数量,直到您开始过拟合训练集。非常复杂的任务,例如大图像分类或语音识别,通常需要具有数十层的网络,并且它们需要大量的训练数据。
不过,您可以尝试逐渐增加神经元的数量,直到网络开始过拟合。这意味着不会导致过拟合的隐藏神经元数量的上限是:
在上面的等式中:
N[i]
为输入神经元的数量
N[o]
为输出神经元的数量
N[s]
为训练数据集中的样本数
α
为任意比例因子,通常为 2-10。
请注意,上述等式不是来自任何研究,而是来自我的个人工作经验。但是,对于自动程序,您将以 2 的α
值开始,即训练数据的自由度是模型的两倍,如果训练数据的误差明显小于 10,则可以达到 10。用于交叉验证数据集。
每个隐藏层的神经元数量
显然,输入和输出层中神经元的数量取决于您的任务所需的输入和输出类型。例如,如果您的数据集的形状为28x28
,则它应该具有大小为 784 的输入神经元,并且输出神经元应该等于要预测的类的数量。
我们将在下一个例子中看到它如何在实践中工作,使用 MLP,其中将有四个具有 256 个神经元的隐藏层(只有一个超参数可以调整,而不是每层一个)。就像层数一样,您可以尝试逐渐增加神经元的数量,直到网络开始过拟合。
有一些经验导出的经验法则,其中最常用的是:“隐藏层的最佳大小通常在输入的大小和输出层的大小之间。”
总之,对于大多数问题,通过仅使用两个规则设置隐藏层配置,您可能可以获得不错的表现(即使没有第二个优化步骤):
- 隐藏层的数量等于一
- 该层中的神经元数量是输入和输出层中神经元的平均值
然而,就像层数一样,你可以尝试逐渐增加神经元的数量,直到网络开始过拟合。
权重和偏置初始化
正如我们将在下一个示例中看到的那样,初始化权重和偏置,隐藏层是一个重要的超参数,需要注意:
- 不要做所有零初始化:一个听起来合理的想法可能是将所有初始权重设置为零,但它在实践中不起作用。这是因为如果网络中的每个神经元计算相同的输出,如果它们的权重被初始化为相同,则神经元之间将不存在不对称的来源。
- 小随机数:也可以将神经元的权重初始化为小数,但不能相同为零。或者,可以使用从均匀分布中抽取的小数字。
- 初始化偏差:通常将偏差初始化为零,因为权重中的小随机数提供不对称性破坏。将偏差设置为一个小的常量值,例如所有偏差的 0.01,确保所有 ReLU 单元都可以传播一些梯度。但是,它既没有表现良好,也没有表现出持续改进因此,建议坚持使用零。
选择最合适的优化器
因为在 FFNN 中, 目标函数之一是最小化评估成本,我们必须定义一个优化器。我们已经看到了如何使用tf.train.AdamOptimizer
。Tensorflow tf.train
提供了一组有助于训练模型的类和函数。就个人而言,我发现 Adam 优化器在实践中对我很有效,而不必考虑学习率等等。
对于大多数情况,我们可以利用 Adam,但有时我们可以采用实现的RMSPropOptimizer
函数,这是梯度下降的高级形式。RMSPropOptimizer
函数实现RMSProp
算法。
RMSPropOptimizer
函数还将学习率除以指数衰减的平方梯度平均值。衰减参数的建议设置值为0.9
,而学习率的良好默认值为0.001
:
optimizer = tf.train.RMSPropOptimizer(0.001, 0.9).minimize(cost_op)
使用最常见的优化器 SGD,学习率必须随1/T
缩放才能获得收敛,其中T
是迭代次数。RMSProp
尝试通过调整步长来自动克服此限制,以使步长与梯度的比例相同。
因此,如果您正在训练神经网络,但计算梯度是必需的,使用tf.train.RMSPropOptimizer()
将是在小批量设置中学习的更快方式。研究人员还建议在训练 CNN 等深层网络时使用动量优化器。
最后,如果您想通过设置这些优化器来玩游戏,您只需要更改一行。由于时间限制,我没有尝试过所有这些。然而,根据 Sebastian Ruder 最近的一篇研究论文(见此链接),自适应学习率方法的优化者,Adagrad
,Adadelta
,RMSprop
和Adam
是最合适的,并为这些情况提供最佳收敛。
网格搜索和随机搜索的超参数调整
采样搜索的两种通用候选方法在其他基于 Python 的机器学习库(如 Scikit-learn)中提供。对于给定值,GridSearchCV
详尽考虑所有参数组合,而RandomizedSearchCV
可以从具有指定分布的参数空间中对给定数量的候选进行采样。
GridSearchCV
是自动测试和优化超参数的好方法。我经常在 Scikit-learn 中使用它。然而,TensorFlowEstimator
优化learning_rate
,batch_size
等等还不是那么简单。而且,正如我所说,我们经常调整这么多超参数以获得最佳结果。不过,我发现这篇文章对于学习如何调整上述超参数非常有用。
随机搜索和网格搜索探索完全相同的参数空间。参数设置的结果非常相似,而随机搜索的运行时间则大大降低。
一些基准测试(例如此链接)已经报告了随机搜索的表现略差,尽管这很可能是一种噪音效应并且不会延续到坚持不懈的测试集。
正则化
有几种方法可以控制 DNN 的训练,以防止在训练阶段过拟合,例如,L2/L1 正则化,最大范数约束和退出:
- L2 正则化:这可能是最常见的正则化形式。使用梯度下降参数更新,L2 正则化表示每个权重将线性地向零衰减。
- L1 正则化:对于每个权重 w,我们将项
λ|w|
添加到目标。然而, 也可以组合 L1 和 L2 正则化以实现弹性网络正则化。 - 最大范数约束:这强制了每个隐藏层神经元的权重向量的大小的绝对上限。可以进一步使用投影的梯度下降来强制约束。
消失梯度问题出现在非常深的神经网络(通常是 RNN,它将有一个专门的章节),它使用激活函数,其梯度往往很小(在 0 到 1 的范围内)。
由于这些小梯度在反向传播期间进一步增加,因此它们倾向于在整个层中“消失”,从而阻止网络学习远程依赖性。解决这个问题的常用方法是使用激活函数,如线性单元(又名 ReLU),它不会受到小梯度的影响。我们将看到一种改进的 RNN 变体,称为长短期记忆 (又名 LSTM),它可以解决这个问题。我们将在第 5 章,优化 TensorFlow 自编码器中看到关于该主题的更详细讨论。
尽管如此,我们已经看到最后的架构更改提高了模型的准确率,但我们可以通过使用 ReLU 更改 sigmoid 激活函数来做得更好,如下所示:
图 20:ReLU 函数
ReLU 单元计算函数f(x) = max(0, x)
。 ReLU 计算速度快,因为它不需要任何指数计算,例如 sigmoid 或 tanh 激活所需的计算。此外,与 sigmoid/tanh 函数相比,发现它大大加速了随机梯度下降的收敛。要使用 ReLU 函数,我们只需在先前实现的模型中更改前四个层的以下定义:
第一层输出:
Y1 = tf.nn.relu(tf.matmul(XX, W1) + B1) # Output from layer 1
第二层输出:
Y2 = tf.nn.relu(tf.matmul(Y1, W2) + B2) # Output from layer 2
第三层输出:
Y3 = tf.nn.relu(tf.matmul(Y2, W3) + B3) # Output from layer 3
第四层输出:
Y4 = tf.nn.relu(tf.matmul(Y3, W4) + B4) # Output from layer 4
输出层:
Ylogits = tf.matmul(Y4, W5) + B5 # computing the logits Y = tf.nn.softmax(Ylogits) # output from layer 5
当然,tf.nn.relu
是 TensorFlow 的 ReLU 实现。模型的准确率几乎达到 98%,您可以看到运行网络:
>>> Loading data/train-images-idx3-ubyte.mnist Loading data/train-labels-idx1-ubyte.mnist Loading data/t10k-images-idx3-ubyte.mnist Loading data/t10k-labels-idx1-ubyte.mnist Epoch: 0 Epoch: 1 Epoch: 2 Epoch: 3 Epoch: 4 Epoch: 5 Epoch: 6 Epoch: 7 Epoch: 8 Epoch: 9 Accuracy:0.9789 done >>>
关注 TensorBoard 分析,从源文件执行的文件夹中,您应该数字:
$> Tensorboard --logdir = 'log_relu' # Don't put space before or after '='
然后在localhost
上打开浏览器以显示 TensorBoard 的起始页面。在下图中,我们显示了趋势对训练集示例数量的准确率:
图 21:训练集上的准确率函数
在大约 1000 个示例之后,您可以很容易地看到在不良的初始趋势之后,准确率如何开始快速渐进式改进。
丢弃优化
在使用 DNN 时,我们需要另一个占位符用于丢弃,这是一个需要调整的超参数。它仅通过以某种概率保持神经元活动(比如p > 1.0
)或者将其设置为零来实现。这个想法是在测试时使用单个神经网络而不会丢弃。该网络的权重是训练权重的缩小版本。如果在训练期间使用dropout_keep_prob < 1.0
保留单元,则在测试时将该单元的输出权重乘以p
。
在学习阶段,与下一层的连接可以限于神经元的子集,以减少要更新的权重。这种学习优化技术称为丢弃。因此,丢弃是一种用于减少具有许多层和/或神经元的网络中的过拟合的技术。通常,丢弃层位于具有大量可训练神经元的层之后。
该技术允许将前一层的一定百分比的神经元设置为 0,然后排除激活。神经元激活被设置为 0 的概率由层内的丢弃率参数通过 0 和 1 之间的数字表示。实际上,神经元的激活保持等于丢弃率的概率;否则,它被丢弃,即设置为 0。
图 22:丢弃表示
通过这种方式,对于每个输入,网络拥有与前一个略有不同的架构。即使这些架构具有相同的权重,一些连接也是有效的,有些连接不是每次都以不同的方式。上图显示了丢弃的工作原理:每个隐藏单元都是从网络中随机省略的,概率为p
。
但需要注意的是,每个训练实例的选定丢弃单元不同;这就是为什么这更像是一个训练问题。丢弃可以被视为在大量不同的神经网络中执行模型平均的有效方式,其中可以以比架构问题低得多的计算成本来避免过拟合。丢弃降低了神经元依赖于其他神经元存在的可能性。通过这种方式,它被迫更多地了解强大的特征,并且它们与其他不同神经元的联系非常有用。
允许构建丢弃层的 TensorFlow 函数是tf.nn.dropout
。此函数的输入是前一层的输出,并且丢弃参数tf.nn.dropout
返回与输入张量相同大小的输出张量。该模型的实现遵循与五层网络相同的规则。在这种情况下,我们必须在一层和另一层之间插入丢弃函数:
pkeep = tf.placeholder(tf.float32) Y1 = tf.nn.relu(tf.matmul(XX, W1) + B1) # Output from layer 1 Y1d = tf.nn.dropout(Y1, pkeep) Y2 = tf.nn.relu(tf.matmul(Y1, W2) + B2) # Output from layer 2 Y2d = tf.nn.dropout(Y2, pkeep) Y3 = tf.nn.relu(tf.matmul(Y2, W3) + B3) # Output from layer 3 Y3d = tf.nn.dropout(Y3, pkeep) Y4 = tf.nn.relu(tf.matmul(Y3, W4) + B4) # Output from layer 4 Y4d = tf.nn.dropout(Y4, pkeep) Ylogits = tf.matmul(Y4d, W5) + B5 # computing the logits Y = tf.nn.softmax(Ylogits) # output from layer 5
退出优化产生以下结果:
>>> Loading data/train-images-idx3-ubyte.mnist Loading data/train-labels-idx1-ubyte.mnist Loading data/t10k-images-idx3-ubyte.mnist Loading data/t10k-labels-idx1-ubyte.mnist Epoch: 0 Epoch: 1 Epoch: 2 Epoch: 3 Epoch: 4 Epoch: 5 Epoch: 6 Epoch: 7 Epoch: 8 Epoch: 9 Accuracy: 0.9666 done >>>
尽管有这种实现, 之前的 ReLU 网络仍然更好,但您可以尝试更改网络参数以提高模型的准确率。此外,由于这是一个很小的网络,我们处理的是小规模的数据集,当您处理具有更复杂网络的大规模高维数据集时,您会发现丢弃可能非常重要。我们将在下一章中看到一些动手实例。
现在,要了解丢弃优化的效果,让我们开始 TensorBoard 分析。只需键入以下内容:
$> Tensorboard --logdir=' log_softmax_relu_dropout/'
下图显示了作为训练示例函数的精度成本函数:
图 23:a)丢弃优化的准确率,b)训练集的成本函数
在上图中,我们显示成本函数作为训练样例的函数。这两种趋势都是我们所期望的:随着训练样例的增加,准确率会提高,而成本函数会随着迭代次数的增加而减
总结
我们已经了解了如何实现 FFNN 架构,其特征在于一组输入单元,一组输出单元以及一个或多个连接该输出的输入级别的隐藏单元。我们已经看到如何组织网络层,以便级别之间的连接是完全的并且在单个方向上:每个单元从前一层的所有单元接收信号并发送其输出值,适当地权衡到所有单元的下一层。
我们还看到了如何为每个层定义激活函数(例如,sigmoid,ReLU,tanh 和 softmax),其中激活函数的选择取决于架构和要解决的问题。
然后,我们实现了四种不同的 FFNN 模型。第一个模型有一个隐藏层,具有 softmax 激活函数。其他三个更复杂的模型总共有五个隐藏层,但具有不同的激活函数。我们还看到了如何使用 TensorFlow 实现深度 MLP 和 DBN,以解决分类任务。使用这些实现,我们设法达到了 90% 以上的准确率。最后,我们讨论了如何调整 DNN 的超参数以获得更好和更优化的表现。
虽然常规的 FFNN(例如 MLP)适用于小图像(例如,MNIST 或 CIFAR-10),但由于需要大量参数,它会因较大的图像而分解。例如,100×100 图像具有 10,000 个像素,并且如果第一层仅具有 1,000 个神经元(其已经严格限制传输到下一层的信息量),则这意味着 1000 万个连接。另外,这仅适用于第一层。
重要的是,DNN 不知道像素的组织方式,因此不知道附近的像素是否接近。 CNN 的架构嵌入了这种先验知识。较低层通常识别图像的单元域中的特征,而较高层将较低层特征组合成较大特征。这适用于大多数自然图像,与 DNN 相比,CNN 具有决定性的先机性。
在下一章中,我们将进一步探讨神经网络模型的复杂性,引入 CNN,这可能对深度学习技术产生重大影响。我们将研究主要功能并查看一些实现示例。
四、CNN 实战
以前面提到的5×5
输入矩阵为例,CNN 的输入层由 25 个神经元(5×5
)组成,其任务是获取与每个像素对应的输入值并将其转移到下一层。
在多层网络中,输入层中所有神经元的输出将连接到隐藏层(完全连接层)中的每个神经元。然而,在 CNN 网络中,上面定义的连接方案和我们要描述的卷积层是显着不同的。正如您可能猜到的,这是层的主要类型:在 CNN 中使用这些层中的一个或多个是必不可少的。
在卷积层中,每个神经元连接到输入区域的某个区域,称为感受野。例如,使用3×3
内核滤波器,每个神经元将具有偏置并且 9 个权重(3×3
)连接到单个感受野。为了有效地识别图像,我们需要将各种不同的内核过滤器应用于相同的感受野,因为每个过滤器应该识别来自图像的不同特征。识别相同特征的神经元集定义了单个特征映射。
下图显示了运行中的 CNN 架构:28×28
输入图像将由一个由28x28x32
特征映射组成的卷积层进行分析。该图还显示了一个感受野和一个3×3
内核过滤器:
图 5:CNN 正在运行中
CNN 可以由级联连接的若干卷积层组成。每个卷积层的输出是一组特征映射(每个都由单个内核过滤器生成)。这些矩阵中的每一个都定义了将由下一层使用的新输入。
通常,在 CNN 中,每个神经元产生高达激活阈值的输出,该激活阈值与输入成比例并且不受限制。
CNN 还使用位于卷积层之后的池化层。池化层将卷积区域划分为子区域。然后,池化层选择单个代表值(最大池化或平均池化)以减少后续层的计算时间并增加特征相对于其空间位置的稳健性。卷积网络的最后一层通常是完全连接的网络,具有用于输出层的 softmax 激活函数。在接下来的几节中,将详细分析最重要的 CNN 的架构。
LeNet5
LeNet5 CNN 架构由 Yann LeCun 于 1998 年发明,是第一个 CNN。它是一个多层前馈网络,专门用于对手写数字进行分类。它被用于 LeCun 的实验,由七层组成,包含可训练的权重。 LeNet5 架构如下所示:
图 6:LeNet5 网络
LeNet5 架构由三个卷积层和两个交替序列池化层组成。最后两层对应于传统的完全连接的神经网络,即完全连接的层,后面是输出层。输出层的主要功能是计算输入向量和参数向量之间的欧几里德距离。输出函数识别输入模式和我们模型的测量值之间的差异。输出保持最小,以实现最佳模型。因此,完全连接的层被配置为使得输入模式和我们的模型的测量之间的差异最小化。虽然它在 MNIST 数据集上表现良好,但是在具有更高分辨率和更多类别的更多图像的数据集上表现下降。
注意
有关 LeNet 系列模型的基本参考,请参见此链接。
逐步实现 LeNet-5
在本节中,我们将学习如何构建 LeNet-5 架构来对 MNIST 数据集中的图像进行分类。下图显示了数据如何在前两个卷积层中流动:使用滤波器权重在第一个卷积层中处理输入图像。这导致 32 个新图像,一个用于卷积层中的每个滤波器。图像也通过合并操作进行下采样,因此图像分辨率从28×28
降低到14×14
。然后在第二卷积层中处理这 32 个较小的图像。我们需要为这 32 个图像中的每一个再次使用滤波器权重,并且我们需要该层的每个输出通道的滤波器权重。通过合并操作再次对图像进行下采样,使得图像分辨率从14×14
减小到7×7
。此卷积层的特征总数为 64。
图 7:前两个卷积层的数据流
通过(3×3
)第三卷积层再次过滤 64 个结果图像。没有对该层应用池操作。第三卷积层的输出是128×7×7
像素图像。然后将这些图像展平为单个向量,长度为4×4×128 = 2048
,其用作完全连接层的输入。
LeNet-5 的输出层由 625 个神经元作为输入(即完全连接层的输出)和 10 个神经元作为输出,用于确定图像的类别,该数字在图片。
图 8:最后三个卷积层的数据流
卷积滤波器最初是随机选择的。输入图像的预测类和实际类之间的差异被称为成本函数,并且这使我们的网络超出训练数据。然后,优化器会自动通过 CNN 传播此成本函数,并更新过滤器权重以改善分类误差。这反复进行数千次,直到分类误差足够低。
现在让我们详细看看如何编写我们的第一个 CNN。让我们首先导入我们实现所需的 TensorFlow 库:
import tensorflow as tf import numpy as np from tensorflow.examples.tutorials.mnist import input_data
设置以下参数。它们表示在训练阶段(128
)和测试阶段(256
)使用的样本数量:
batch_size = 128 test_size = 256
当我们定义以下参数时,该值为28
,因为 MNIST 图像的高度和宽度为28
像素:
img_size = 28
对于类的数量,值10
意味着我们将为每个 0 到 9 位数设置一个类:
num_classes = 10
为输入图像定义占位符变量X
。该张量的数据类型设置为float32
,形状设置为[None, img_size, img_size, 1]
,其中None
表示张量可以保存任意数量的图像:
X = tf.placeholder("float", [None, img_size, img_size, 1])
然后我们为占位符变量X
中的输入图像正确关联的标签设置另一个占位符变量Y
。此占位符变量的形状为[None, num_classes]
,这意味着它可以包含任意数量的标签。每个标签都是长度为num_classes
的向量,在这种情况下为10
:
Y = tf.placeholder("float", [None, num_classes])
我们收集MNIST
数据,这些数据将被复制到数据文件夹中:
mnist = input_data.read_data_sets("MNIST-data", one_hot=True)
我们构建训练数据集(trX
,trY
)和测试网络(teX
,teY)
:
trX, trY, teX, teY = mnist.train.images, \ mnist.train.labels, \ mnist.test.images, \ mnist.test.labels
必须重新整形trX
和teX
图像集以匹配输入形状:
trX = trX.reshape(-1, img_size, img_size, 1) teX = teX.reshape(-1, img_size, img_size, 1)
我们现在开始定义网络的weights
。
init_weights
函数在提供的形状中构建新变量,并使用随机值初始化网络权重:
def init_weights(shape): return tf.Variable(tf.random_normal(shape, stddev=0.01))
第一卷积层的每个神经元被卷积为输入张量的小子集,尺寸为3×3×1
。值32
只是我们为第一层考虑的特征图的数量。然后定义权重w
:
w = init_weights([3, 3, 1, 32])
然后输入的数量增加到32
,这意味着第二卷积层中的每个神经元被卷积到第一卷积层的3×3×32
个神经元。w2
权重如下:
w2 = init_weights([3, 3, 32, 64])
值64
表示获得的输出特征的数量。第三个卷积层被卷积为前一层的3x3x64
个神经元,而128
是结果特征。
w3 = init_weights([3, 3, 64, 128])
第四层完全连接并接收128x4x4
输入,而输出等于625
:
w4 = init_weights([128 * 4 * 4, 625])
输出层接收625
输入,输出是类的数量:
w_o = init_weights([625, num_classes])
请注意,这些初始化实际上并未在此时完成。它们仅在 TensorFlow 图中定义。
p_keep_conv = tf.placeholder("float") p_keep_hidden = tf.placeholder("float")
是时候定义网络模型了。就像网络的权重定义一样,它将是一个函数。它接收X
张量,权重张量和丢弃参数作为卷积和完全连接层的输入:
def model(X, w, w2, w3, w4, w_o, p_keep_conv, p_keep_hidden):
tf.nn.conv2d()
执行 TensorFlow 操作进行卷积。请注意,所有尺寸的strides
都设置为 1。实际上,第一步和最后一步必须始终为 1,因为第一步是图像编号,最后一步是输入通道。padding
参数设置为'SAME'
,这意味着输入图像用零填充,因此输出的大小相同:
conv1 = tf.nn.conv2d(X, w,strides=[1, 1, 1, 1],\ padding='SAME')
然后我们将conv1
层传递给 ReLU 层。它为每个输入像素x
计算max(x, 0)
函数,为公式添加一些非线性,并允许我们学习更复杂的函数:
conv1_a = tf.nn.relu(conv1)
然后由tf.nn.max_pool
运算符合并生成的层:
conv1 = tf.nn.max_pool(conv1_a, ksize=[1, 2, 2, 1]\ ,strides=[1, 2, 2, 1],\ padding='SAME')
这是一个2×2
最大池,这意味着我们正在检查2×2
窗口并在每个窗口中选择最大值。然后我们将 2 个像素移动到下一个窗口。我们尝试通过tf.nn.dropout()
函数减少过拟合,我们传递conv1
层和p_keep_conv
概率值:
conv1 = tf.nn.dropout(conv1, p_keep_conv)
如您所见,接下来的两个卷积层conv2
和conv3
的定义方式与conv1
相同:
conv2 = tf.nn.conv2d(conv1, w2,\ strides=[1, 1, 1, 1],\ padding='SAME') conv2_a = tf.nn.relu(conv2) conv2 = tf.nn.max_pool(conv2, ksize=[1, 2, 2, 1],\ strides=[1, 2, 2, 1],\ padding='SAME') conv2 = tf.nn.dropout(conv2, p_keep_conv) conv3=tf.nn.conv2d(conv2, w3,\ strides=[1, 1, 1, 1]\ ,padding='SAME') conv3 = tf.nn.relu(conv3)
完全连接的层将添加到网络中。第一个FC_layer
的输入是前一个卷积的卷积层:
FC_layer = tf.nn.max_pool(conv3, ksize=[1, 2, 2, 1],\ strides=[1, 2, 2, 1],\ padding='SAME') FC_layer = tf.reshape(FC_layer,\ [-1, w4.get_shape().as_list()[0]])
dropout
函数再次用于减少过拟合:
FC_layer = tf.nn.dropout(FC_layer, p_keep_conv)
输出层接收FC_layer
和w4
权重张量作为输入。应用 ReLU 和丢弃运算符:
output_layer = tf.nn.relu(tf.matmul(FC_layer, w4)) output_layer = tf.nn.dropout(output_layer, p_keep_hidden)
结果是一个长度为 10 的向量。这用于确定图像所属的 10 个输入类中的哪一个:
result = tf.matmul(output_layer, w_o) return result
交叉熵是我们在此分类器中使用的表现指标。交叉熵是一个连续的函数,它总是正的,如果预测的输出与期望的输出完全匹配,则等于零。因此,这种优化的目标是通过改变网络层中的变量来最小化交叉熵,使其尽可能接近零。 TensorFlow 具有用于计算交叉熵的内置函数。请注意,该函数在内部计算 softmax,因此我们必须直接使用py_x
的输出:
py_x = model(X, w, w2, w3, w4, w_o, p_keep_conv, p_keep_hidden) Y_ = tf.nn.softmax_cross_entropy_with_logits_v2\ (labels=Y,logits=py_x)
现在我们已经为每个分类图像定义了交叉熵,我们可以衡量模型在每个图像上的表现。我们需要一个标量值来使用交叉熵来优化网络变量,因此我们只需要对所有分类图像求平均交叉熵:
cost = tf.reduce_mean(Y_)
为了最小化评估的cost
,我们必须定义一个优化器。在这种情况下,我们将使用RMSPropOptimizer
,它是 GD 的高级形式。RMSPropOptimizer
实现了 RMSProp 算法,这是一种未发表的自适应学习率方法,由 Geoff Hinton 在他的 Coursera 课程的第 6 讲中提出。
注意
您可以在此链接找到 Geoff Hinton 的课程。
RMSPropOptimizer
还将学习率除以梯度平方的指数衰减均值。 Hinton 建议将衰减参数设置为0.9
,而学习率的良好默认值为0.001
:
optimizer = tf.train.RMSPropOptimizer(0.001, 0.9).minimize(cost)
基本上,通用 SGD 算法存在一个问题,即学习率必须以1 / T
(其中T
是迭代次数)进行缩放以实现收敛。 RMSProp 尝试通过自动调整步长来解决这个问题,以使步长与梯度相同。随着平均梯度变小,SGD 更新中的系数变得更大以进行补偿。
注意
最后,我们定义predict_op
,它是模式输出中尺寸最大值的索引:
predict_op = tf.argmax(py_x, 1)
请注意,此时不执行优化。什么都没有计算,因为我们只是将优化器对象添加到 TensorFlow 图中以便以后执行。
我们现在来定义网络的运行会话。训练集中有 55,000 个图像,因此使用所有这些图像计算模型的梯度需要很长时间。因此,我们将在优化器的每次迭代中使用一小批图像。如果您的计算机崩溃或由于 RAM 耗尽而变得非常慢,那么您可以减少此数量,但您可能需要执行更多优化迭代。
现在我们可以继续实现 TensorFlow 会话:
with tf.Session() as sess: tf.global_variables_initializer().run() for i in range(100):
我们得到了一批训练样例,training_batch
张量现在包含图像的子集和相应的标签:
training_batch = zip(range(0, len(trX), batch_size),\ range(batch_size, \ len(trX)+1, \ batch_size))
将批量放入feed_dict
中,并在图中为占位符变量指定相应的名称。我们现在可以使用这批训练数据运行优化器。 TensorFlow 将馈送中的变量分配给占位符变量,然后运行优化程序:
for start, end in training_batch: sess.run(optimizer, feed_dict={X: trX[start:end],\ Y: trY[start:end],\ p_keep_conv: 0.8,\ p_keep_hidden: 0.5})
同时,我们得到了打乱的一批测试样本:
test_indices = np.arange(len(teX)) np.random.shuffle(test_indices) test_indices = test_indices[0:test_size]
对于每次迭代,我们显示批次的评估accuracy
:
print(i, np.mean(np.argmax(teY[test_indices], axis=1) ==\ sess.run\ (predict_op,\ feed_dict={X: teX[test_indices],\ Y: teY[test_indices], \ p_keep_conv: 1.0,\ p_keep_hidden: 1.0})))
根据所使用的计算资源,训练网络可能需要几个小时。我机器上的结果如下:
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes. Successfully extracted to train-images-idx3-ubyte.mnist 9912422 bytes. Loading ata/train-images-idx3-ubyte.mnist Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes. Successfully extracted to train-labels-idx1-ubyte.mnist 28881 bytes. Loading ata/train-labels-idx1-ubyte.mnist Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes. Successfully extracted to t10k-images-idx3-ubyte.mnist 1648877 bytes. Loading ata/t10k-images-idx3-ubyte.mnist Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes. Successfully extracted to t10k-labels-idx1-ubyte.mnist 4542 bytes. Loading ata/t10k-labels-idx1-ubyte.mnist (0, 0.95703125) (1, 0.98046875) (2, 0.9921875) (3, 0.99609375) (4, 0.99609375) (5, 0.98828125) (6, 0.99609375) (7, 0.99609375) (8, 0.98828125) (9, 0.98046875) (10, 0.99609375) . . . .. . (90, 1.0) (91, 0.9921875) (92, 0.9921875) (93, 0.99609375) (94, 1.0) (95, 0.98828125) (96, 0.98828125) (97, 0.99609375) (98, 1.0) (99, 0.99609375)
经过 10,000 次迭代后, 模型的准确率为 99.60%,这还不错!
AlexNet
AlexNet 神经网络是首批实现巨大成功的 CNN 之一。作为 2012 年 ILSVRC 的获胜者,这个神经网络是第一个使用 LeNet-5 网络之前定义的神经网络的标准结构在 ImageNet 等非常复杂的数据集上获得良好结果。
注意
ImageNet 项目是一个大型视觉数据库,设计用于视觉对象识别软件研究。截至 2016 年,ImageNet 手工标注了超过一千万个图像 URL,以指示图像中的对象。在至少一百万个图像中,还提供了边界框。第三方图像 URL 的标注数据库可直接从 ImageNet 免费获得。
AlexNet 的架构如下图所示:
图 9:AlexNet 网络
在 AlexNet 架构中,有八层具有可训练参数:一系列五个连续卷积层,后面是三个完全连接的层。每个卷积层之后是 ReLU 层,并且可选地还有最大池层,尤其是在网络的开始处,以便减少网络占用的空间量。
所有池层都有3x3
扩展区域,步长率为 2:这意味着您始终使用重叠池。这是因为与没有重叠的普通池相比,这种类型的池提供了稍好的网络表现。在网络的开始,在池化层和下一个卷积层之间,总是使用几个 LRN 标准化层:经过一些测试,可以看出它们倾向于减少网络误差。
前两个完全连接的层拥有 4,096 个神经元,而最后一个拥有 1,000 个单元,对应于 ImageNet 数据集中的类数。考虑到完全连接层中的大量连接,在每对完全连接的层之间添加了比率为 0.5 的丢弃层,即,每次忽略一半的神经元激活。在这种情况下已经注意到,使用丢弃技术不仅加速了单次迭代的处理,而且还很好地防止了过拟合。没有丢弃层,网络制造商声称原始网络过拟合。
迁移学习
迁移学习包括建立已经构建的网络,并对各个层的参数进行适当的更改,以便它可以适应另一个数据集。例如,您可以在大型数据集(如 ImageNet)上使用预先测试的网络,并在较小的数据集上再次训练它。如果我们的数据集在内容上与原始数据集没有明显不同,那么预先训练的模型已经具有与我们自己的分类问题相关的学习特征。
如果我们的数据集与预训练模型训练的数据集没有太大差异,我们可以使用微调技术。 已在大型不同数据集上进行预训练的模型可能会捕捉到早期层中的曲线和边缘等通用特征,这些特征在大多数分类问题中都是相关且有用的。但是,如果我们的数据集来自一个非常特定的域,并且找不到该域中预先训练好的网络,我们应该考虑从头开始训练网络。
预训练的 AlexNet
我们会对预先训练好的 AlexNet 进行微调,以区分狗和猫。 AlexNet 在 ImageNet 数据集上经过预先训练。
要执行这个例子,你还需要安装 scipy(参见此链接)和 PIL(Pillow),这是 scipy 使用的读取图像:pip install Pillow
或pip3 install Pillow
。
然后,您需要下载以下文件:
myalexnet_forward.py
:2017 版 TensorFlow 的 AlexNet 实现和测试代码(Python 3.5)bvlc_alexnet.npy
:权重,需要在工作目录中caffe_classes.py
:类,与网络输出的顺序相同poodle.png
,laska.png
,dog.png
,dog2.png
,quail227.JPEG
:测试图像(图像应为227×227×3
)
从此链接下载这些文件,或从本书的代码库中下载。
首先,我们将在之前下载的图像上测试网络。为此,只需从 Python GUI 运行myalexnet_forward.py
即可。
通过简单地检查源代码可以看到(参见下面的代码片段),将调用预先训练好的网络对以下两个图像进行分类,laska.png
和poodle.png
,这些图像之前已下载过:
im1 = (imread("laska.png")[:,:,:3]).astype(float32) im1 = im1 - mean(im1) im1[:, :, 0], im1[:, :, 2] = im1[:, :, 2], im1[:, :, 0] im2 = (imread("poodle.png")[:,:,:3]).astype(float32) im2[:, :, 0], im2[:, :, 2] = im2[:, :, 2], im2[:, :, 0]
图 10:要分类的图像
的权重和偏置由以下语句加载:
net_data = load(open("bvlc_alexnet.npy", "rb"), encoding="latin1").item()
网络是一组卷积和池化层,后面是三个完全连接的状态。该模型的输出是 softmax 函数:
prob = tf.nn.softmax(fc8)
softmax 函数的输出是分类等级,因为它们表示网络认为输入图像属于caffe_classes.py
文件中定义的类的强度。
如果我们运行代码,我们应该得到以下结果:
Image 0 weasel 0.503177 black-footed ferret, ferret, Mustela nigripes 0.263265 polecat, fitch, foulmart, foumart, Mustela putorius 0.147746 mink 0.0649517 otter 0.00771955 Image 1 clumber, clumber spaniel 0.258953 komondor 0.165846 miniature poodle 0.149518 toy poodle 0.0984719 kuvasz 0.0848062 0.40007972717285156 >>>
在前面的例子中,AlexNet 给鼬鼠的分数约为 50%。这意味着该模型非常有信心图像显示黄鼠狼,其余分数可视为噪音。
数据集准备
我们的任务是建立一个区分狗和猫的图像分类器。我们从 Kaggle 那里得到一些帮助,我们可以从中轻松下载数据集。
在此数据集中,训练集包含 20,000 个标记图像,测试和验证集包含 2,500 个图像。
要使用数据集,必须将每个图像重新整形为227×227×3
。为此,您可以使用prep_images.py
中的 Python 代码。否则,您可以使用本书仓库中的trainDir.rar
和testDir.rar
文件。它们包含 6,000 个用于训练的犬和猫的重塑图像,以及 100 个重新成形的图像用于测试。
以下部分中描述的以下微调实现在alexnet_finetune.py
中实现,可以在本书的代码库中下载。
微调的实现
我们的分类任务包含两个类别,因此网络的新 softmax 层将包含 2 个类别而不是 1,000 个类别。这是输入张量,它是一个227×227×3
图像,以及等级 2 的输出张量:
n_classes = 2 train_x = zeros((1, 227,227,3)).astype(float32) train_y = zeros((1, n_classes))
微调实现包括截断预训练网络的最后一层(softmax 层),并将其替换为与我们的问题相关的新 softmax 层。
例如,ImageNet 上预先训练好的网络带有一个包含 1,000 个类别的 softmax 层。
以下代码片段定义了新的 softmax 层fc8
:
fc8W = tf.Variable(tf.random_normal\ ([4096, n_classes]),\ trainable=True, name="fc8w") fc8b = tf.Variable(tf.random_normal\ ([n_classes]),\ trainable=True, name="fc8b") fc8 = tf.nn.xw_plus_b(fc7, fc8W, fc8b) prob = tf.nn.softmax(fc8)
损失是用于分类的表现指标。它是一个始终为正的连续函数,如果模型的预测输出与期望的输出完全匹配,则交叉熵等于零。因此,优化的目标是通过改变模型的权重和偏置来最小化交叉熵,因此它尽可能接近零。
TensorFlow 具有用于计算交叉熵的内置函数。为了使用交叉熵来优化模型的变量,我们需要一个标量值,因此我们只需要对所有图像分类采用交叉熵的平均值:
loss = tf.reduce_mean\ (tf.nn.softmax_cross_entropy_with_logits_v2\ (logits =prob, labels=y)) opt_vars = [v for v in tf.trainable_variables()\ if (v.name.startswith("fc8"))]
既然我们必须最小化成本度量,那么我们可以创建optimizer
:
optimizer = tf.train.AdamOptimizer\ (learning_rate=learning_rate).minimize\ (loss, var_list = opt_vars) correct_pred = tf.equal(tf.argmax(prob, 1), tf.argmax(y, 1)) accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
在这种情况下,我们使用步长为0.5
的AdamOptimizer
。请注意,此时不执行优化。事实上,根本没有计算任何东西,我们只需将优化器对象添加到 TensorFlow 图中以便以后执行。然后我们在网络上运行反向传播以微调预训练的权重:
batch_size = 100 training_iters = 6000 display_step = 1 dropout = 0.85 # Dropout, probability to keep units init = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init) step = 1
继续训练,直到达到最大迭代次数:
while step * batch_size < training_iters: batch_x, batch_y = \ next(next_batch(batch_size)) #.next()
运行优化操作(反向传播):
sess.run(optimizer, \ feed_dict={x: batch_x, \ y: batch_y, \ keep_prob: dropout}) if step % display_step == 0:
计算批次损失和准确率:
cost, acc = sess.run([loss, accuracy],\ feed_dict={x: batch_x, \ y: batch_y, \ keep_prob: 1.}) print ("Iter " + str(step*batch_size) \ + ", Minibatch Loss= " + \ "{:.6f}".format(cost) + \ ", Training Accuracy= " + \ "{:.5f}".format(acc)) step += 1 print ("Optimization Finished!")
网络训练产生以下结果:
Iter 100, Minibatch Loss= 0.555294, Training Accuracy= 0.76000 Iter 200, Minibatch Loss= 0.584999, Training Accuracy= 0.73000 Iter 300, Minibatch Loss= 0.582527, Training Accuracy= 0.73000 Iter 400, Minibatch Loss= 0.610702, Training Accuracy= 0.70000 Iter 500, Minibatch Loss= 0.583640, Training Accuracy= 0.73000 Iter 600, Minibatch Loss= 0.583523, Training Accuracy= 0.73000 ………………………………………………………………… ………………………………………………………………… Iter 5400, Minibatch Loss= 0.361158, Training Accuracy= 0.95000 Iter 5500, Minibatch Loss= 0.403371, Training Accuracy= 0.91000 Iter 5600, Minibatch Loss= 0.404287, Training Accuracy= 0.91000 Iter 5700, Minibatch Loss= 0.413305, Training Accuracy= 0.90000 Iter 5800, Minibatch Loss= 0.413816, Training Accuracy= 0.89000 Iter 5900, Minibatch Loss= 0.413476, Training Accuracy= 0.90000 Optimization Finished!
要测试我们的模型,我们将预测与标签集(cat = 0
,dog = 1
)进行比较:
output = sess.run(prob, feed_dict = {x:imlist, keep_prob: 1.}) result = np.argmax(output,1) testResult = [1,1,1,1,0,0,0,0,0,0,\ 0,1,0,0,0,0,1,1,0,0,\ 1,0,1,1,0,1,1,0,0,1,\ 1,1,1,0,0,0,0,0,1,0,\ 1,1,1,1,0,1,0,1,1,0,\ 1,0,0,1,0,0,1,1,1,0,\ 1,1,1,1,1,0,0,0,0,0,\ 0,1,1,1,0,1,1,1,1,0,\ 0,0,1,0,1,1,1,1,0,0,\ 0,0,0,1,1,0,1,1,0,0] count = 0 for i in range(0,99): if result[i] == testResult[i]: count=count+1 print("Testing Accuracy = " + str(count) +"%")
最后,我们有我们模型的准确率:
Testing Accuracy = 82%
VGG
VGG 是在 2014 年 ILSVRC 期间发明神经网络的人的名字。我们谈论的是复数网络,因为创建了同一网络的多个版本,每个拥有不同数量的层。根据层数n
,这些网络中的一个具有的权重,它们中的每一个通常称为 VGG-n。所有这些网络都比 AlexNet 更深。这意味着它们由多个层组成,其参数比 AlexNet 更多,在这种情况下,总共有 11 到 19 个训练层。通常,只考虑可行的层,因为它们会影响模型的处理和大小,如前一段所示。然而,整体结构仍然非常相似:总是有一系列初始卷积层和最后一系列完全连接的层,后者与 AlexNet 完全相同。因此,使用的卷积层的数量,当然还有它们的参数有什么变化。下表显示了 VGG 团队构建的所有变体。
每一列,从左侧开始,向右侧,显示一个特定的 VGG 网络,从最深到最浅。粗体项表示与先前版本相比,每个版本中添加的内容。 ReLU 层未在表中显示,但在网络中它存在于每个卷积层之后。所有卷积层使用 1 的步幅:
表:VGG 网络架构
请注意, AlexNet 没有具有相当大的感受野的卷积层:这里,所有感受野都是3×3
,除了 VGG-16 中有几个具有1×1
感受野的卷积层。回想一下,具有 1 步梯度的凸层不会改变输入空间大小,同时修改深度值,该深度值与使用的内核数量相同。因此,VGG 卷积层不会影响输入体积的宽度和高度;只有池化层才能这样做。使用具有较小感受野的一系列卷积层的想法最终总体上模拟具有较大感受野的单个卷积层,这是由于这样的事实,即以这种方式使用多个 ReLU 层而不是单独使用一个,从而增加激活函数的非线性,从而使其更具区别性。它还用于减少使用的参数数量。这些网络被认为是 AlexNet 的演变,因为总体而言,使用相同的数据集,它们的表现优于 AlexNet。 VGG 网络演示的主要概念是拥塞神经网络越来越深刻,其表现也越来越高。但是, 必须拥有越来越强大的硬件,否则网络训练就会成为问题。
对于 VGG,使用了四个 NVIDIA Titan Blacks,每个都有 6 GB 的内存。因此,VGG 具有更好的表现,但需要大量的硬件用于训练,并且还使用大量参数:例如,VGG-19 模型大约为 550MB(是 AlexNet 的两倍)。较小的 VGG 网络仍然具有大约 507MB 的模型。
用 VGG-19 学习艺术风格
在这个项目中,我们使用预训练的 VGG-19 来学习艺术家创建的样式和模式,并将它们转移到图像中(项目文件是style_transfer.py
,在本书的 GitHub 仓库中)。这种技术被称为artistic style learning
(参见 Gatys 等人的文章 A Art Algorithm of Artistic Style)。根据学术文献,艺术风格学习定义如下:给定两个图像作为输入,合成具有第一图像的语义内容和第二图像的纹理/风格的第三图像。
为了使其正常工作,我们需要训练一个深度卷积神经网络来构建以下内容:
- 用于确定图像 A 内容的内容提取器
- 用于确定图像 B 样式的样式提取器
- 合并器将一些任意内容与另一个任意样式合并,以获得最终结果
图 11:艺术风格学习操作模式
输入图像
输入图像,每个都是478×478
像素,是您在本书的代码库中也可以找到的以下图像(cat.jpg
和mosaic.jpg
):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8RNpZcU-1681565849704)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/dl-tf-2e-zh/img/B09698_04_13.jpg)]
图 12:艺术风格学习中的输入图像
为了通过 VGG 模型分析 ,需要对这些图像进行预处理:
- 添加额外的维度
- 从输入图像中减去
MEAN_VALUES
:
MEAN_VALUES = np.array([123.68, 116.779, 103.939]).reshape((1,1,1,3)) content_image = preprocess('cat.jpg') style_image = preprocess('mosaic.jpg') def preprocess(path): image = plt.imread(path) image = image[np.newaxis] image = image - MEAN_VALUES return image
内容提取器和损失
为了隔离图像的语义内容,我们使用预先训练好的 VGG-19 神经网络,对权重进行了一些微调,以适应这个问题,然后使用其中一个隐藏层的输出作为内容提取器。下图显示了用于此问题的 CNN:
图 13:用于艺术风格学习的 VGG-19
使用以下代码加载预训练的 VGG:
import scipy.io vgg = scipy.io.loadmat('imagenet-vgg-verydeep-19.mat')
imagenet-vgg-verydeep-19.mat
模型应从此链接下载。
该模型有 43 层,其中 19 层是卷积层。其余的是最大池/激活/完全连接的层。
我们可以检查每个卷积层的形状:
[print (vgg_layers[0][i][0][0][2][0][0].shape,\ vgg_layers[0][i][0][0][0][0]) for i in range(43) if 'conv' in vgg_layers[0][i][0][0][0][0] \ or 'fc' in vgg_layers[0][i][0][0][0][0]]
上述代码的结果如下:
(3, 3, 3, 64) conv1_1 (3, 3, 64, 64) conv1_2 (3, 3, 64, 128) conv2_1 (3, 3, 128, 128) conv2_2 (3, 3, 128, 256) conv3_1 (3, 3, 256, 256) conv3_2 (3, 3, 256, 256) conv3_3 (3, 3, 256, 256) conv3_4 (3, 3, 256, 512) conv4_1 (3, 3, 512, 512) conv4_2 (3, 3, 512, 512) conv4_3 (3, 3, 512, 512) conv4_4 (3, 3, 512, 512) conv5_1 (3, 3, 512, 512) conv5_2 (3, 3, 512, 512) conv5_3 (3, 3, 512, 512) conv5_4 (7, 7, 512, 4096) fc6 (1, 1, 4096, 4096) fc7 (1, 1, 4096, 1000) fc8
每种形状以下列方式表示:[kernel height, kernel width, number of input channels, number of output channels]
。
第一层有 3 个输入通道,因为输入是 RGB 图像,而卷积层的输出通道数从 64 到 512,所有内核都是3x3
矩阵。
然后我们应用转移学习技术,以使 VGG-19 网络适应我们的问题:
- 不需要完全连接的层,因为它们用于对象识别。
- 最大池层代替平均池层,以获得更好的结果。平均池层的工作方式与卷积层中的内核相同。
IMAGE_WIDTH = 478 IMAGE_HEIGHT = 478 INPUT_CHANNELS = 3 model = {} model['input'] = tf.Variable(np.zeros((1, IMAGE_HEIGHT,\ IMAGE_WIDTH,\ INPUT_CHANNELS)),\ dtype = 'float32') model['conv1_1'] = conv2d_relu(model['input'], 0, 'conv1_1') model['conv1_2'] = conv2d_relu(model['conv1_1'], 2, 'conv1_2') model['avgpool1'] = avgpool(model['conv1_2']) model['conv2_1'] = conv2d_relu(model['avgpool1'], 5, 'conv2_1') model['conv2_2'] = conv2d_relu(model['conv2_1'], 7, 'conv2_2') model['avgpool2'] = avgpool(model['conv2_2']) model['conv3_1'] = conv2d_relu(model['avgpool2'], 10, 'conv3_1') model['conv3_2'] = conv2d_relu(model['conv3_1'], 12, 'conv3_2') model['conv3_3'] = conv2d_relu(model['conv3_2'], 14, 'conv3_3') model['conv3_4'] = conv2d_relu(model['conv3_3'], 16, 'conv3_4') model['avgpool3'] = avgpool(model['conv3_4']) model['conv4_1'] = conv2d_relu(model['avgpool3'], 19,'conv4_1') model['conv4_2'] = conv2d_relu(model['conv4_1'], 21, 'conv4_2') model['conv4_3'] = conv2d_relu(model['conv4_2'], 23, 'conv4_3') model['conv4_4'] = conv2d_relu(model['conv4_3'], 25,'conv4_4') model['avgpool4'] = avgpool(model['conv4_4']) model['conv5_1'] = conv2d_relu(model['avgpool4'], 28, 'conv5_1') model['conv5_2'] = conv2d_relu(model['conv5_1'], 30, 'conv5_2') model['conv5_3'] = conv2d_relu(model['conv5_2'], 32, 'conv5_3') model['conv5_4'] = conv2d_relu(model['conv5_3'], 34, 'conv5_4') model['avgpool5'] = avgpool(model['conv5_4'])
这里我们定义了contentloss
函数来测量两个图像p
和x
之间的内容差异:
def contentloss(p, x): size = np.prod(p.shape[1:]) loss = (1./(2*size)) * tf.reduce_sum(tf.pow((x - p),2)) return loss
当输入图像在内容方面彼此非常接近并且随着其内容偏离而增长时,该函数倾向于为 0。
我们将在conv5_4
层上使用contentloss
。这是输出层,其输出将是预测,因此我们需要使用contentloss
函数将此预测与实际预测进行比较:
content_loss = contentloss\ (sess.run(model['conv5_4']), model['conv5_4'])
最小化content_loss
意味着混合图像在给定层中具有与内容图像的激活非常相似的特征激活。
样式提取器和损失
样式提取器使用过滤器的 Gram 矩阵作为给定的隐藏层。简单来说,使用这个矩阵,我们可以破坏图像的语义,保留其基本组件并使其成为一个好的纹理提取器:
def gram_matrix(F, N, M): Ft = tf.reshape(F, (M, N)) return tf.matmul(tf.transpose(Ft), Ft)
style_loss
测量两个图像彼此之间的接近程度。此函数是样式图像和输入noise_image
生成的 Gram 矩阵元素的平方差的总和:
noise_image = np.random.uniform\ (-20, 20,\ (1, IMAGE_HEIGHT, \ IMAGE_WIDTH,\ INPUT_CHANNELS)).astype('float32') def style_loss(a, x): N = a.shape[3] M = a.shape[1] * a.shape[2] A = gram_matrix(a, N, M) G = gram_matrix(x, N, M) result = (1/(4 * N**2 * M**2))* tf.reduce_sum(tf.pow(G-A,2)) return result
style_loss
生长 ,因为它的两个输入图像(a
和x
)倾向于偏离风格。
合并和总损失
我们可以合并内容和样式损失,以便训练输入noise_image
来输出(在层中)与样式图像类似的样式,其特征相似于内容图像:
alpha = 1 beta = 100 total_loss = alpha * content_loss + beta * styleloss
训练
最小化网络中的损失,以便样式损失(输出图像的样式和样式图像的样式之间的损失),内容损失(内容图像和输出图像之间的损失),以及总变异损失尽可能低:
train_step = tf.train.AdamOptimizer(1.5).minimize(total_loss)
从这样的网络生成的输出图像应该类似于输入图像并且具有样式图像的造型师属性。
最后,我们可以准备网络进行训练:
sess.run(tf.global_variables_initializer()) sess.run(model['input'].assign(input_noise)) for it in range(2001): sess.run(train_step) if it%100 == 0: mixed_image = sess.run(model['input']) print('iteration:',it,'cost: ', sess.run(total_loss)) filename = 'out2/%d.png' % (it) deprocess(filename, mixed_image)
训练时间可能非常耗时,但结果可能非常有趣:
iteration: 0 cost: 8.14037e+11 iteration: 100 cost: 1.65584e+10 iteration: 200 cost: 5.22747e+09 iteration: 300 cost: 2.72995e+09 iteration: 400 cost: 1.8309e+09 iteration: 500 cost: 1.36818e+09 iteration: 600 cost: 1.0804e+09 iteration: 700 cost: 8.83103e+08 iteration: 800 cost: 7.38783e+08 iteration: 900 cost: 6.28652e+08 iteration: 1000 cost: 5.41755e+08
经过 1000 次迭代后,我们创建了一个新的拼接:
图 14:艺术风格学习中的输出图像
真是太棒了!你终于可以训练你的神经网络像毕加索一样画画…玩得开心!
Inception-v3
Szegedy 和其他人在 2014 年的论文“Going Deeper with Convolutions”中首次介绍了 Inception 微架构:
图 15:GoogLeNet 中使用的 Original Inception 模块
初始模块的目标是通过在网络的同一模块内计算1×1
,3×3
和5×5
卷积来充当多级特征提取器 - 这些滤波器的输出然后,在被馈送到网络中的下一层之前,沿着信道维度堆叠。这种架构的原始版本称为 GoogLeNet,但后续形式简称为 InceptionVN,其中 N 表示 Google 推出的版本号。
您可能想知道为什么我们在同一输入上使用不同类型的卷积。答案是,只要仔细研究了它的参数,就不可能总是获得足够的有用特征来用单个卷积进行精确分类。事实上,使用一些输入它可以更好地使用卷积小内核,而其他输入可以使用其他类型的内核获得更好的结果。可能由于这个原因,GoogLeNet 团队想要在他们自己的网络中考虑一些替代方案。如前所述,为此目的,GoogLeNet 在同一网络级别(即它们并行)使用三种类型的卷积层:1×1
层,3×3
层和5×5
层。
这个 3 层并行局部结构的结果是它们所有输出值的组合,链接到单个向量输出,它将是下一层的输入。这是通过使用连接层完成的。除了三个并行卷积层之外,在相同的本地结构中还添加了一个池化层,因为池化操作对于 CNN 的成功至关重要。
使用 TensorFlow 探索初始化
从此链接,你应该能够下载相应的模型库。
然后键入以下命令:
cd models/tutorials/image/imagenet python classify_image.py
当程序第一次运行时,classify_image.py
从 tensorflow.org 下载经过训练的模型。您的硬盘上需要大约 200MB 的可用空间。
上面的命令将对提供的熊猫图像进行分类。如果模型正确运行,脚本将生成以下输出:
giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca (score = 0.88493) indri, indris, Indri indri, Indri brevicaudatus (score = 0.00878) lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00317) custard apple (score = 0.00149) earthstar (score = 0.00127)
如果您想提供其他 JPEG 图像,可以通过编辑来完成:
image_file argument: python classify_image.py --image=image.jpg
您可以通过从互联网下载图像并查看其产生的结果来测试初始阶段。
例如,您可以尝试从此链接获取以下图像(我们将其重命名为inception_image.jpg
):
图 16:使用 Inception-v3 进行分类的输入图像
结果如下:
python classify_image.py --image=inception_example.jpg strawberry (score = 0.91541) crayfish, crawfish, crawdad, crawdaddy (score = 0.01208) chocolate sauce, chocolate syrup (score = 0.00628) cockroach, roach (score = 0.00572) grocery store, grocery, food market, market (score = 0.00264)
听起来不错!
CNN 的情感识别
深度学习中难以解决的一个问题与神经网络无关:它是以正确格式获取正确数据。但是,Kaggle 平台提供了新的问题,并且需要研究新的数据集。
Kaggle 成立于 2010 年,作为预测建模和分析竞赛的平台,公司和研究人员发布他们的数据,来自世界各地的统计人员和数据挖掘者竞争生产最佳模型。在本节中,我们将展示如何使用面部图像制作 CNN 以进行情感检测。此示例的训练和测试集可以从此链接下载。
图 17:Kaggle 比赛页面
训练组由 3,761 个灰度图像组成,尺寸为48×48
像素,3,761 个标签,每个图像有 7 个元素。
每个元素编码一个情感,0:愤怒,1:厌恶,2:恐惧,3:幸福,4:悲伤,5:惊讶,6:中立。
在经典 Kaggle 比赛中,必须由平台评估从测试集获得的标签集。在这个例子中,我们将训练一个来自训练组的神经网络,之后我们将在单个图像上评估模型。
在开始 CNN 实现之前,我们将通过实现一个简单的过程(文件download_and_display_images.py
)来查看下载的数据。
导入库:
import tensorflow as tf import numpy as np from matplotlib import pyplot as plt import EmotionUtils
read_data
函数允许构建所有数据集,从下载的数据开始,您可以在本书的代码库中的EmotionUtils
库中找到它们:
FLAGS = tf.flags.FLAGS tf.flags.DEFINE_string("data_dir",\ "EmotionDetector/",\ "Path to data files") images = [] images = EmotionUtils.read_data(FLAGS.data_dir) train_images = images[0] train_labels = images[1] valid_images = images[2] valid_labels = images[3] test_images = images[4]
然后打印训练的形状并测试图像:
print ("train images shape = ",train_images.shape) print ("test labels shape = ",test_images.shape)
显示训练组的第一个图像及其正确的标签:
image_0 = train_images[0] label_0 = train_labels[0] print ("image_0 shape = ",image_0.shape) print ("label set = ",label_0) image_0 = np.resize(image_0,(48,48)) plt.imshow(image_0, cmap='Greys_r') plt.show()
有 3,761 个48×48
像素的灰度图像:
train images shape = (3761, 48, 48, 1)
有 3,761 个类标签,每个类包含七个元素:
train labels shape = (3761, 7)
测试集由 1,312 个48x48
像素灰度图像组成:
test labels shape = (1312, 48, 48, 1)
单个图像具有以下形状:
image_0 shape = (48, 48, 1)
第一张图片的标签设置如下:
label set = [ 0\. 0\. 0\. 1\. 0\. 0\. 0.]
此标签对应于快乐,图像在以下 matplot 图中可视化:
图 18:来自情感检测面部数据集的第一图像
我们现在转向 CNN 架构。
下图显示了数据将如何在 CNN 中流动:
图 19:实现的 CNN 的前两个卷积层
该网络具有两个卷积层,两个完全连接的层,最后是 softmax 分类层。使用5×5
卷积核在第一卷积层中处理输入图像(48×48
像素)。这导致 32 个图像,每个使用的滤波器一个。通过最大合并操作对图像进行下采样,以将图像从48×48
减小到24×24
像素。然后,这些 32 个较小的图像由第二卷积层处理;这导致 64 个新图像(见上图)。通过第二次池化操作,将得到的图像再次下采样到12×12
像素。
第二合并层的输出是64×12×12
像素的图像。然后将它们展平为长度为12×12×64 = 9,126
的单个向量,其用作具有 256 个神经元的完全连接层的输入。这将进入另一个具有 10 个神经元的完全连接的层,每个类对应一个类,用于确定图像的类别,即图像中描绘的情感。
图 20:实现的 CNN 的最后两层
让我们继续讨论权重和偏置定义。以下数据结构表示网络权重的定义,并总结了到目前为止我们所描述的内容:
weights = { 'wc1': weight_variable([5, 5, 1, 32], name="W_conv1"), 'wc2': weight_variable([3, 3, 32, 64],name="W_conv2"), 'wf1': weight_variable([(IMAGE_SIZE // 4) * (IMAGE_SIZE // 4) \* 64,256],name="W_fc1"), 'wf2': weight_variable([256, NUM_LABELS], name="W_fc2") }
注意卷积滤波器是随机初始化的,所以分类是随机完成的:
def weight_variable(shape, stddev=0.02, name=None): initial = tf.truncated_normal(shape, stddev=stddev) if name is None: return tf.Variable(initial) else: return tf.get_variable(name, initializer=initial)
以相似方式,我们已经定义了偏差:
biases = { 'bc1': bias_variable([32], name="b_conv1"), 'bc2': bias_variable([64], name="b_conv2"), 'bf1': bias_variable([256], name="b_fc1"), 'bf2': bias_variable([NUM_LABELS], name="b_fc2") } def bias_variable(shape, name=None): initial = tf.constant(0.0, shape=shape) if name is None: return tf.Variable(initial) else: return tf.get_variable(name, initializer=initial)
优化器必须使用区分链规则通过 CNN 传播误差,并更新过滤器权重以改善分类误差。输入图像的预测类和真实类之间的差异由loss
函数测量。它将pred
模型的预测输出和所需输出label
作为输入:
def loss(pred, label): cross_entropy_loss =\ tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2\ (logits=pred, labels=label)) tf.summary.scalar('Entropy', cross_entropy_loss) reg_losses = tf.add_n(tf.get_collection("losses")) tf.summary.scalar('Reg_loss', reg_losses) return cross_entropy_loss + REGULARIZATION * reg_losses
tf.nn.softmax_cross_entropy_with_logits_v2(pred, label)
函数在应用 softmax 函数后计算结果的cross_entropy_loss
(但它以数学上仔细的方式一起完成)。这就像是以下结果:
a = tf.nn.softmax(x) b = cross_entropy(a)
我们计算每个分类图像的cross_entropy_loss
,因此我们将测量模型在每个图像上的单独表现。
我们计算分类图像的交叉熵平均值:
cross_entropy_loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2 (logits=pred, labels=label))
为了防止过拟合,我们将使用 L2 正则化,其中包括向cross_entropy_loss
插入一个附加项:
reg_losses = tf.add_n(tf.get_collection("losses")) return cross_entropy_loss + REGULARIZATION * reg_losses
哪里:
def add_to_regularization_loss(W, b): tf.add_to_collection("losses", tf.nn.l2_loss(W)) tf.add_to_collection("losses", tf.nn.l2_loss(b))
注意
有关详细信息,请参阅此链接。
我们已经构建了网络的权重和偏置以及优化过程。但是,与所有已实现的网络一样,我们必须通过导入所有必需的库来启动实现:
import tensorflow as tf import numpy as np from datetime import datetime import EmotionUtils import os, sys, inspect from tensorflow.python.framework import ops import warnings warnings.filterwarnings("ignore") os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' ops.reset_default_graph()
然后,我们在您的计算机上设置存储数据集的路径,以及网络参数:
FLAGS = tf.flags.FLAGS tf.flags.DEFINE_string("data_dir",\ "EmotionDetector/",\ "Path to data files") tf.flags.DEFINE_string("logs_dir",\ "logs/EmotionDetector_logs/",\ "Path to where log files are to be saved") tf.flags.DEFINE_string("mode",\ "train",\ "mode: train (Default)/ test") BATCH_SIZE = 128 LEARNING_RATE = 1e-3 MAX_ITERATIONS = 1001 REGULARIZATION = 1e-2 IMAGE_SIZE = 48 NUM_LABELS = 7 VALIDATION_PERCENT = 0.1
emotion_cnn
函数实现我们的模型:
def emotion_cnn(dataset): with tf.name_scope("conv1") as scope: tf.summary.histogram("W_conv1", weights['wc1']) tf.summary.histogram("b_conv1", biases['bc1']) conv_1 = tf.nn.conv2d(dataset, weights['wc1'],\ strides=[1, 1, 1, 1],\ padding="SAME") h_conv1 = tf.nn.bias_add(conv_1, biases['bc1']) h_1 = tf.nn.relu(h_conv1) h_pool1 = max_pool_2x2(h_1) add_to_regularization_loss(weights['wc1'], biases['bc1']) with tf.name_scope("conv2") as scope: tf.summary.histogram("W_conv2", weights['wc2']) tf.summary.histogram("b_conv2", biases['bc2']) conv_2 = tf.nn.conv2d(h_pool1, weights['wc2'],\ strides=[1, 1, 1, 1], \ padding="SAME") h_conv2 = tf.nn.bias_add(conv_2, biases['bc2']) h_2 = tf.nn.relu(h_conv2) h_pool2 = max_pool_2x2(h_2) add_to_regularization_loss(weights['wc2'], biases['bc2']) with tf.name_scope("fc_1") as scope: prob=0.5 image_size = IMAGE_SIZE // 4 h_flat = tf.reshape(h_pool2,[-1,image_size*image_size*64]) tf.summary.histogram("W_fc1", weights['wf1']) tf.summary.histogram("b_fc1", biases['bf1']) h_fc1 = tf.nn.relu(tf.matmul\ (h_flat, weights['wf1']) + biases['bf1']) h_fc1_dropout = tf.nn.dropout(h_fc1, prob) with tf.name_scope("fc_2") as scope: tf.summary.histogram("W_fc2", weights['wf2']) tf.summary.histogram("b_fc2", biases['bf2']) pred = tf.matmul(h_fc1_dropout, weights['wf2']) +\ biases['bf2'] return pred
然后定义一个main
函数,我们将在其中定义数据集,输入和输出占位符变量以及主会话,以便启动训练过程:
def main(argv=None):
此函数中的第一个操作是加载数据集以进行训练和验证。我们将使用训练集来教授分类器识别待预测的标签,我们将使用验证集来评估分类器的表现:
train_images,\ train_labels,\ valid_images,\ valid_labels,\ test_images=EmotionUtils.read_data(FLAGS.data_dir) print("Train size: %s" % train_images.shape[0]) print('Validation size: %s' % valid_images.shape[0]) print("Test size: %s" % test_images.shape[0])
我们为输入图像定义占位符变量。这允许我们更改输入到 TensorFlow 图的图像。数据类型设置为float32
,形状设置为[None, img_size, img_size, 1]
(其中None
表示张量可以保存任意数量的图像,每个图像为img_size
像素高和img_size
像素宽),和1
是颜色通道的数量:
input_dataset = tf.placeholder(tf.float32, \ [None, \ IMAGE_SIZE, \ IMAGE_SIZE, 1],name="input")
接下来,我们为与占位符变量input_dataset
中输入的图像正确关联的标签提供占位符变量。这个占位符变量的形状是[None, NUM_LABELS]
,这意味着它可以包含任意数量的标签,每个标签是长度为NUM_LABELS
的向量,在这种情况下为 7:
input_labels = tf.placeholder(tf.float32,\ [None, NUM_LABELS])
global_step
保持跟踪到目前为止执行的优化迭代数量。我们希望在检查点中使用所有其他 TensorFlow 变量保存此变量。请注意trainable=False
,这意味着 TensorFlow 不会尝试优化此变量:
global_step = tf.Variable(0, trainable=False)
跟随变量dropout_prob
,用于丢弃优化:
dropout_prob = tf.placeholder(tf.float32)
现在为测试阶段创建神经网络。emotion_cnn()
函数返回input_dataset
的预测类标签pred
:
pred = emotion_cnn(input_dataset)
output_pred
是测试和验证的预测,我们将在运行会话中计算:
output_pred = tf.nn.softmax(pred,name="output")
loss_val
包含输入图像的预测类(pred
)与实际类别(input_labels
)之间的差异:
loss_val = loss(pred, input_labels)
train_op
定义用于最小化成本函数的优化器。在这种情况下,我们再次使用AdamOptimizer
:
train_op = tf.train.AdamOptimizer\ (LEARNING_RATE).minimize\ (loss_val, global_step)
summary_op
是用于 TensorBoard 可视化的 :
summary_op = tf.summary.merge_all()
创建图后,我们必须创建一个 TensorFlow 会话,用于执行图:
with tf.Session() as sess: sess.run(tf.global_variables_initializer()) summary_writer = tf.summary.FileWriter(FLAGS.logs_dir, sess.graph)
我们定义saver
来恢复模型:
saver = tf.train.Saver() ckpt = tf.train.get_checkpoint_state(FLAGS.logs_dir) if ckpt and ckpt.model_checkpoint_path: saver.restore(sess, ckpt.model_checkpoint_path) print ("Model Restored!")
接下来我们需要获得一批训练示例。batch_image
现在拥有一批图像,batch_label
包含这些图像的正确标签:
for step in xrange(MAX_ITERATIONS): batch_image, batch_label = get_next_batch(train_images,\ train_labels,\ step)
我们将批次放入dict
中,其中包含 TensorFlow 图中占位符变量的正确名称:
feed_dict = {input_dataset: batch_image, \ input_labels: batch_label}
我们使用这批训练数据运行优化器。 TensorFlow 将feed_dict_train
中的变量分配给占位符变量,然后运行优化程序:
sess.run(train_op, feed_dict=feed_dict) if step % 10 == 0: train_loss,\ summary_str =\ sess.run([loss_val,summary_op],\ feed_dict=feed_dict) summary_writer.add_summary(summary_str,\ global_step=step) print ("Training Loss: %f" % train_loss)
当运行步长是 100 的倍数时,我们在验证集上运行训练模型:
if step % 100 == 0: valid_loss = \ sess.run(loss_val, \ feed_dict={input_dataset: valid_images, input_labels: valid_labels})
然后我们打印掉损失值:
print ("%s Validation Loss: %f" \ % (datetime.now(), valid_loss))
在训练过程结束时,模型将被保存:
saver.save(sess, FLAGS.logs_dir\ + 'model.ckpt', \ global_step=step) if __name__ == "__main__": tf.app.run()
这是输出。如您所见,在模拟过程中损失函数减少:
Reading train.csv ... (4178, 48, 48, 1) (4178, 7) Reading test.csv ... Picking ... Train size: 3761 Validation size: 417 Test size: 1312 2018-02-24 15:17:45.421344 Validation Loss: 1.962773 2018-02-24 15:19:09.568140 Validation Loss: 1.796418 2018-02-24 15:20:35.122450 Validation Loss: 1.328313 2018-02-24 15:21:58.200816 Validation Loss: 1.120482 2018-02-24 15:23:24.024985 Validation Loss: 1.066049 2018-02-24 15:24:38.838554 Validation Loss: 0.965881 2018-02-24 15:25:54.761599 Validation Loss: 0.953470 2018-02-24 15:27:15.592093 Validation Loss: 0.897236 2018-02-24 15:28:39.881676 Validation Loss: 0.838831 2018-02-24 15:29:53.012461 Validation Loss: 0.910777 2018-02-24 15:31:14.416664 Validation Loss: 0.888537 >>>
然而,模型可以通过改变超参数或架构来改进。
在下一节中,我们将了解如何在您自己的图像上有效地测试模型。
在您自己的图像上测试模型
我们使用的数据集是标准化的。所有面部都指向相机,表情在某些情况下被夸大甚至滑稽。现在让我们看看如果我们使用更自然的图像会发生什么。确保脸部没有文字覆盖,情感可识别,脸部主要指向相机。
我从这个 JPEG 图像开始(它是一个彩色图像,你可以从书的代码库下载):
图 21:输入图像
使用 Matplotlib 和其他 NumPy Python 库,我们将输入颜色图像转换为网络的有效输入,即灰度图像:
img = mpimg.imread('author_image.jpg') gray = rgb2gray(img)
转换函数如下:
def rgb2gray(rgb): return np.dot(rgb[...,:3], [0.299, 0.587, 0.114])
结果如下图所示:
图 22:灰度输入图像
最后,我们可以使用此图像为网络提供信息,但首先我们必须定义一个正在运行的 TensorFlow 会话:
sess = tf.InteractiveSession()
然后我们可以回想起之前保存的模型:
new_saver = tf.train.\ import_meta_graph('logs/EmotionDetector_logs/model.ckpt-1000.meta') new_saver.restore(sess,'logs/EmotionDetector_logs/model.ckpt-1000') tf.get_default_graph().as_graph_def() x = sess.graph.get_tensor_by_name("input:0") y_conv = sess.graph.get_tensor_by_name("output:0")
要测试图像,我们必须将其重新整形为网络的有效48×48×1
格式:
image_test = np.resize(gray,(1,48,48,1))
我们多次评估相同的图片(1000
),以便在输入图像中获得一系列可能的情感:
tResult = testResult() num_evaluations = 1000 for i in range(0,num_evaluations): result = sess.run(y_conv, feed_dict={x:image_test}) label = sess.run(tf.argmax(result, 1)) label = label[0] label = int(label) tResult.evaluate(label) tResult.display_result(num_evaluations)
在几秒后,会出现如下结果:
>>> anger = 0.1% disgust = 0.1% fear = 29.1% happy = 50.3% sad = 0.1% surprise = 20.0% neutral = 0.3% >>>
最高的百分比证实(happy = 50.3%
)我们走在正确的轨道上。当然,这并不意味着我们的模型是准确的。可以通过更多和更多样化的训练集,更改网络参数或修改网络架构来实现可能的改进。
源代码
这里列出了实现的分类器的第二部分:
from scipy import misc import numpy as np import matplotlib.cm as cm import tensorflow as tf from matplotlib import pyplot as plt import matplotlib.image as mpimg import EmotionUtils from EmotionUtils import testResult def rgb2gray(rgb): return np.dot(rgb[...,:3], [0.299, 0.587, 0.114]) img = mpimg.imread('author_image.jpg') gray = rgb2gray(img) plt.imshow(gray, cmap = plt.get_cmap('gray')) plt.show() sess = tf.InteractiveSession() new_saver = tf.train.import_meta_graph('logs/model.ckpt-1000.meta') new_saver.restore(sess, 'logs/model.ckpt-1000') tf.get_default_graph().as_graph_def() x = sess.graph.get_tensor_by_name("input:0") y_conv = sess.graph.get_tensor_by_name("output:0") image_test = np.resize(gray,(1,48,48,1)) tResult = testResult() num_evaluations = 1000 for i in range(0,num_evaluations): result = sess.run(y_conv, feed_dict={x:image_test}) label = sess.run(tf.argmax(result, 1)) label = label[0] label = int(label) tResult.evaluate(label) tResult.display_result(num_evaluations)
我们实现testResult
Python 类来显示结果百分比。它可以在EmotionUtils
文件中找到。
以下是此类的实现:
class testResult: def __init__(self): self.anger = 0 self.disgust = 0 self.fear = 0 self.happy = 0 self.sad = 0 self.surprise = 0 self.neutral = 0 def evaluate(self,label): if (0 == label): self.anger = self.anger+1 if (1 == label): self.disgust = self.disgust+1 if (2 == label): self.fear = self.fear+1 if (3 == label): self.happy = self.happy+1 if (4 == label): self.sad = self.sad+1 if (5 == label): self.surprise = self.surprise+1 if (6 == label): self.neutral = self.neutral+1 def display_result(self,evaluations): print("anger = " +\ str((self.anger/float(evaluations))*100) + "%") print("disgust = " +\ str((self.disgust/float(evaluations))*100) + "%") print("fear = " +\ str((self.fear/float(evaluations))*100) + "%") print("happy = " +\ str((self.happy/float(evaluations))*100) + "%") print("sad = " +\ str((self.sad/float(evaluations))*100) + "%") print("surprise = " +\ str((self.surprise/float(evaluations))*100) + "%") print("neutral = " +\ str((self.neutral/float(evaluations))*100) + "%")
总结
在本章中,我们介绍了 CNN。我们已经看到 CNN 适用于图像分类问题,使训练阶段更快,测试阶段更准确。
最常见的 CNN 架构已经被描述:LeNet-5 模型,专为手写和机器打印字符识别而设计; AlexNet,2012 年参加 ILSVRC; VGG 模型在 ImageNet 中实现了 92.7% 的前 5 个测试精度(属于 1,000 个类别的超过 1400 万个图像的数据集);最后是 Inception-v3 模型,该模型负责在 2014 年 ILSVRC 中设置分类和检测标准。
每个 CNN 架构的描述后面都是一个代码示例。此外,AlexNet 网络和 VGG 示例有助于解释传输和样式学习技术的概念。
最后,我们建立了一个 CNN 来对图像数据集中的情感进行分类;我们在单个图像上测试了网络,并评估了模型的限制和质量。
下一章将介绍自编码器:这些算法可用于降维,分类,回归,协同过滤,特征学习和主题建模。我们将使用自编码器进行进一步的数据分析,并使用图像数据集测量分类表现。
TensorFlow 深度学习第二版:1~5(3)https://developer.aliyun.com/article/1426793