六、神经网络
在本章中,我们将介绍神经网络以及如何在 TensorFlow 中实现它们。大多数后续章节将基于神经网络,因此学习如何在 TensorFlow 中使用它们非常重要。在开始使用多层网络之前,我们将首先介绍神经网络的基本概念。在上一节中,我们将创建一个神经网络,学习如何玩井字棋。
在本章中,我们将介绍以下秘籍:
- 实现操作门
- 使用门和激活函数
- 实现单层神经网络
- 实现不同的层
- 使用多层网络
- 改进线性模型的预测
- 学习玩井字棋
读者可以在 Github 和 Packt 仓库中找到本章中的所有代码。
介绍
神经网络目前在诸如图像和语音识别,阅读手写,理解文本,图像分割,对话系统,自动驾驶汽车等任务中打破记录。虽然这些上述任务中的一些将在后面的章节中介绍,但重要的是将神经网络作为一种易于实现的机器学习算法引入,以便我们以后可以对其进行扩展。
神经网络的概念已经存在了几十年。然而,它最近才获得牵引力,因为我们现在具有训练大型网络的计算能力,因为处理能力,算法效率和数据大小的进步。
神经网络基本上是应用于输入数据矩阵的一系列操作。这些操作通常是加法和乘法的集合,然后是非线性函数的应用。我们已经看到的一个例子是逻辑回归,我们在第 3 章,线性回归中看到了这一点。逻辑回归是部分斜率 - 特征乘积的总和,其后是应用 Sigmoid 函数,这是非线性的。神经网络通过允许操作和非线性函数的任意组合(包括绝对值,最大值,最小值等的应用)来进一步概括这一点。
神经网络的重要技巧称为反向传播。反向传播是一种允许我们根据学习率和损失函数输出更新模型变量的过程。我们使用反向传播来更新第 3 章,线性回归和第 4 章,支持向量机中的模型变量。
关于神经网络的另一个重要特征是非线性激活函数。由于大多数神经网络只是加法和乘法运算的组合,因此它们无法对非线性数据集进行建模。为了解决这个问题,我们在神经网络中使用了非线性激活函数。这将允许神经网络适应大多数非线性情况。
重要的是要记住,正如我们在许多算法中所看到的,神经网络对我们选择的超参数敏感。在本章中,我们将探讨不同学习率,损失函数和优化程序的影响。
学习神经网络的资源更多,更深入,更详细地涵盖了该主题。这些资源如下:
- 描述反向传播的开创性论文是 Yann LeCun 等人的 Efficient Back Prop
- CS231,用于视觉识别的卷积神经网络,由斯坦福大学提供。
- CS224d,斯坦福大学自然语言处理的深度学习。
- [深度学习,麻省理工学院出版社出版的一本书,Goodfellow 等人,2016]http://www.deeplearningbook.org)。
- 迈克尔·尼尔森(Michael Nielsen)有一本名为“神经网络与深度学习”的在线书籍。
- 对于一个更实用的方法和神经网络的介绍,Andrej Karpathy 用 JavaScript 实例写了一个很棒的总结,称为黑客的神经网络指南。
- 另一个总结深度学习的网站被 Ian Goodfellow,Yoshua Bengio 和 Aaron Courville 称为初学者深度学习。
实现操作门
神经网络最基本的概念之一是作为操作门操作。在本节中,我们将从乘法操作开始作为门,然后再继续考虑嵌套门操作。
准备
我们将实现的第一个操作门是f(x) = a · x
。为优化此门,我们将a
输入声明为变量,将x
输入声明为占位符。这意味着 TensorFlow 将尝试更改a
值而不是x
值。我们将创建损失函数作为输出和目标值之间的差异,即 50。
第二个嵌套操作门将是f(x) = a · x + b
。同样,我们将a
和b
声明为变量,将x
声明为占位符。我们再次将输出优化到目标值 50。值得注意的是,第二个例子的解决方案并不是唯一的。有许多模型变量组合可以使输出为 50.对于神经网络,我们并不关心中间模型变量的值,而是更加强调所需的输出。
将这些操作视为我们计算图上的操作门。下图描绘了前面两个示例:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HdAgokLV-1681566911063)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/d954838e-bc84-4659-be40-91028c951c64.png)]
图 1:本节中的两个操作门示例
操作步骤
要在 TensorFlow 中实现第一个操作门f(x) = a · x
并将输出训练为值 50,请按照下列步骤操作:
- 首先加载
TensorFlow
并创建图会话,如下所示:
import tensorflow as tf sess = tf.Session()
- 现在我们需要声明我们的模型变量,输入数据和占位符。我们使输入数据等于值
5
,因此得到 50 的乘法因子将为 10(即5X10=50
),如下所示:
a = tf.Variable(tf.constant(4.)) x_val = 5. x_data = tf.placeholder(dtype=tf.float32)
- 接下来,我们使用以下输入将操作添加到计算图中:
multiplication = tf.multiply(a, x_data)
- 我们现在将损失函数声明为输出与
50
的期望目标值之间的 L2 距离,如下所示:
loss = tf.square(tf.subtract(multiplication, 50.))
- 现在我们初始化我们的模型变量并将我们的优化算法声明为标准梯度下降,如下所示:
init = tf.global_variables_initializer() sess.run(init) my_opt = tf.train.GradientDescentOptimizer(0.01) train_step = my_opt.minimize(loss)
- 我们现在可以将模型输出优化到
50
的期望值。我们通过连续输入 5 的输入值并反向传播损失来将模型变量更新为10
的值,如下所示:
print('Optimizing a Multiplication Gate Output to 50.') for i in range(10): sess.run(train_step, feed_dict={x_data: x_val}) a_val = sess.run(a) mult_output = sess.run(multiplication, feed_dict={x_data: x_val}) print(str(a_val) + ' * ' + str(x_val) + ' = ' + str(mult_output))
- 上一步应该产生以下输出:
Optimizing a Multiplication Gate Output to 50\. 7.0 * 5.0 = 35.0 8.5 * 5.0 = 42.5 9.25 * 5.0 = 46.25 9.625 * 5.0 = 48.125 9.8125 * 5.0 = 49.0625 9.90625 * 5.0 = 49.5312 9.95312 * 5.0 = 49.7656 9.97656 * 5.0 = 49.8828 9.98828 * 5.0 = 49.9414 9.99414 * 5.0 = 49.9707
接下来,我们将对两个嵌套的操作门f(x) = a · x + b
进行相同的操作。
- 我们将以与前面示例完全相同的方式开始,但将初始化两个模型变量
a
和b
,如下所示:
from tensorflow.python.framework import ops ops.reset_default_graph() sess = tf.Session() a = tf.Variable(tf.constant(1.)) b = tf.Variable(tf.constant(1.)) x_val = 5\. x_data = tf.placeholder(dtype=tf.float32) two_gate = tf.add(tf.multiply(a, x_data), b) loss = tf.square(tf.subtract(two_gate, 50.)) my_opt = tf.train.GradientDescentOptimizer(0.01) train_step = my_opt.minimize(loss) init = tf.global_variables_initializer() sess.run(init)
- 我们现在优化模型变量以将输出训练到
50
的目标值,如下所示:
print('Optimizing Two Gate Output to 50.') for i in range(10): # Run the train step sess.run(train_step, feed_dict={x_data: x_val}) # Get the a and b values a_val, b_val = (sess.run(a), sess.run(b)) # Run the two-gate graph output two_gate_output = sess.run(two_gate, feed_dict={x_data: x_val}) print(str(a_val) + ' * ' + str(x_val) + ' + ' + str(b_val) + ' = ' + str(two_gate_output))
- 上一步应该产生以下输出:
Optimizing Two Gate Output to 50\. 5.4 * 5.0 + 1.88 = 28.88 7.512 * 5.0 + 2.3024 = 39.8624 8.52576 * 5.0 + 2.50515 = 45.134 9.01236 * 5.0 + 2.60247 = 47.6643 9.24593 * 5.0 + 2.64919 = 48.8789 9.35805 * 5.0 + 2.67161 = 49.4619 9.41186 * 5.0 + 2.68237 = 49.7417 9.43769 * 5.0 + 2.68754 = 49.876 9.45009 * 5.0 + 2.69002 = 49.9405 9.45605 * 5.0 + 2.69121 = 49.9714
这里需要注意的是,第二个例子的解决方案并不是唯一的。这在神经网络中并不重要,因为所有参数都被调整为减少损失。这里的最终解决方案将取决于
a
和b
的初始值。如果这些是随机初始化的,而不是值 1,我们会看到每次迭代的模型变量的不同结束值。
工作原理
我们通过 TensorFlow 的隐式反向传播实现了计算门的优化。 TensorFlow 跟踪我们的模型的操作和变量值,并根据我们的优化算法规范和损失函数的输出进行调整。
我们可以继续扩展操作门,同时跟踪哪些输入是变量,哪些输入是数据。这对于跟踪是很重要的,因为 TensorFlow 将更改所有变量以最小化损失而不是数据,这被声明为占位符。
每个训练步骤自动跟踪计算图并自动更新模型变量的隐式能力是 TensorFlow 的强大功能之一,也是它如此强大的原因之一。
使用门和激活函数
现在我们可以将操作门连接在一起,我们希望通过激活函数运行计算图输出。在本节中,我们将介绍常见的激活函数。
准备
在本节中,我们将比较和对比两种不同的激活函数:Sigmoid 和整流线性单元(ReLU)。回想一下,这两个函数由以下公式给出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dn1Y6ZrA-1681566911064)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/99793b89-d872-4349-adf9-0b04b07b05dd.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rMfXutIV-1681566911065)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/e27c24a9-4e98-404e-a085-9019936fe3d4.png)]
在这个例子中,我们将创建两个具有相同结构的单层神经网络,除了一个将通过 sigmoid 激活并且一个将通过 ReLU 激活。损失函数将由距离值 0.75 的 L2 距离控制。我们将从正态分布(Normal(mean=2, sd=0.1))
中随机抽取批量数据,然后将输出优化为 0.75。
操作步骤
我们按如下方式处理秘籍:
- 我们将首先加载必要的库并初始化图。这也是我们可以提出如何使用 TensorFlow 设置随机种子的好点。由于我们将使用 NumPy 和 TensorFlow 中的随机数生成器,因此我们需要为两者设置随机种子。使用相同的随机种子集,我们应该能够复制结果。我们通过以下输入执行此操作:
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt sess = tf.Session() tf.set_random_seed(5) np.random.seed(42)
- 现在我们需要声明我们的批量大小,模型变量,数据和占位符来输入数据。我们的计算图将包括将我们的正态分布数据输入到两个相似的神经网络中,这两个神经网络的区别仅在于激活函数。结束,如下所示:
batch_size = 50 a1 = tf.Variable(tf.random_normal(shape=[1,1])) b1 = tf.Variable(tf.random_uniform(shape=[1,1])) a2 = tf.Variable(tf.random_normal(shape=[1,1])) b2 = tf.Variable(tf.random_uniform(shape=[1,1])) x = np.random.normal(2, 0.1, 500) x_data = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 接下来,我们将声明我们的两个模型,即 sigmoid 激活模型和 ReLU 激活模型,如下所示:
sigmoid_activation = tf.sigmoid(tf.add(tf.matmul(x_data, a1), b1)) relu_activation = tf.nn.relu(tf.add(tf.matmul(x_data, a2), b2))
- 损失函数将是模型输出与值 0.75 之间的平均 L2 范数,如下所示:
loss1 = tf.reduce_mean(tf.square(tf.subtract(sigmoid_activation, 0.75))) loss2 = tf.reduce_mean(tf.square(tf.subtract(relu_activation, 0.75)))
- 现在我们需要声明我们的优化算法并初始化我们的变量,如下所示:
my_opt = tf.train.GradientDescentOptimizer(0.01) train_step_sigmoid = my_opt.minimize(loss1) train_step_relu = my_opt.minimize(loss2) init = tf.global_variable_initializer() sess.run(init)
- 现在,我们将针对两个模型循环我们的 750 次迭代训练,如下面的代码块所示。我们还将保存损失输出和激活输出值,以便稍后进行绘图:
loss_vec_sigmoid = [] loss_vec_relu = [] activation_sigmoid = [] activation_relu = [] for i in range(750): rand_indices = np.random.choice(len(x), size=batch_size) x_vals = np.transpose([x[rand_indices]]) sess.run(train_step_sigmoid, feed_dict={x_data: x_vals}) sess.run(train_step_relu, feed_dict={x_data: x_vals}) loss_vec_sigmoid.append(sess.run(loss1, feed_dict={x_data: x_vals})) loss_vec_relu.append(sess.run(loss2, feed_dict={x_data: x_vals})) activation_sigmoid.append(np.mean(sess.run(sigmoid_activation, feed_dict={x_data: x_vals}))) activation_relu.append(np.mean(sess.run(relu_activation, feed_dict={x_data: x_vals})))
- 要绘制损失和激活输出,我们需要输入以下代码:
plt.plot(activation_sigmoid, 'k-', label='Sigmoid Activation') plt.plot(activation_relu, 'r--', label='Relu Activation') plt.ylim([0, 1.0]) plt.title('Activation Outputs') plt.xlabel('Generation') plt.ylabel('Outputs') plt.legend(loc='upper right') plt.show() plt.plot(loss_vec_sigmoid, 'k-', label='Sigmoid Loss') plt.plot(loss_vec_relu, 'r--', label='Relu Loss') plt.ylim([0, 1.0]) plt.title('Loss per Generation') plt.xlabel('Generation') plt.ylabel('Loss') plt.legend(loc='upper right') plt.show()
激活输出需要绘制,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5DtPK1j-1681566911065)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/e8f656c0-3ccb-4dee-b6ff-1f05dcb0a1d6.png)]
图 2:来自具有 Sigmoid 激活的网络和具有 ReLU 激活的网络的计算图输出
两个神经网络使用类似的架构和目标(0.75),但有两个不同的激活函数,sigmoid 和 ReLU。重要的是要注意 ReLU 激活网络收敛到比 sigmoid 激活所需的 0.75 目标更快,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bafT8Qi2-1681566911065)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/59a4dcbb-73a7-4598-80e9-28690b520a8d.png)]
图 3:该图描绘了 Sigmoid 和 ReLU 激活网络的损耗值。注意迭代开始时 ReLU 损失的极端程度
工作原理
由于 ReLU 激活函数的形式,它比 sigmoid 函数更频繁地返回零值。我们认为这种行为是一种稀疏性。这种稀疏性导致收敛速度加快,但失去了受控梯度。另一方面,Sigmoid 函数具有非常良好控制的梯度,并且不会冒 ReLU 激活所带来的极值的风险,如下图所示:
激活函数 | 优点 | 缺点 |
Sigmoid | 不太极端的输出 | 收敛速度较慢 |
RELU | 更快地收敛 | 可能有极端的输出值 |
更多
在本节中,我们比较了神经网络的 ReLU 激活函数和 Sigmoid 激活函数。还有许多其他激活函数通常用于神经网络,但大多数属于两个类别之一;第一类包含形状类似于 sigmoid 函数的函数,如 arctan,hypertangent,heavyiside step 等;第二类包含形状的函数,例如 ReLU 函数,例如 softplus,leaky ReLU 等。我们在本节中讨论的关于比较这两个函数的大多数内容都适用于任何类别的激活。然而,重要的是要注意激活函数的选择对神经网络的收敛和输出有很大影响。
实现单层神经网络
我们拥有实现对真实数据进行操作的神经网络所需的所有工具,因此在本节中我们将创建一个神经网络,其中一个层在Iris
数据集上运行。
准备
在本节中,我们将实现一个具有一个隐藏层的神经网络。重要的是要理解完全连接的神经网络主要基于矩阵乘法。因此,重要的是数据和矩阵的大小正确排列。
由于这是一个回归问题,我们将使用均方误差作为损失函数。
操作步骤
我们按如下方式处理秘籍:
- 要创建计算图,我们首先加载以下必要的库:
import matplotlib.pyplot as plt import numpy as np import tensorflow as tf from sklearn import datasets
- 现在我们将加载
Iris
数据并将长度存储为目标值。然后我们将使用以下代码启动图会话:
iris = datasets.load_iris() x_vals = np.array([x[0:3] for x in iris.data]) y_vals = np.array([x[3] for x in iris.data]) sess = tf.Session()
- 由于数据集较小,我们需要设置种子以使结果可重现,如下所示:
seed = 2 tf.set_random_seed(seed) np.random.seed(seed)
- 为了准备数据,我们将创建一个 80-20 训练测试分割,并通过最小 - 最大缩放将 x 特征标准化为 0 到 1 之间,如下所示:
train_indices = np.random.choice(len(x_vals), round(len(x_vals)*0.8), replace=False) test_indices = np.array(list(set(range(len(x_vals))) - set(train_indices))) x_vals_train = x_vals[train_indices] x_vals_test = x_vals[test_indices] y_vals_train = y_vals[train_indices] y_vals_test = y_vals[test_indices] def normalize_cols(m): col_max = m.max(axis=0) col_min = m.min(axis=0) return (m-col_min) / (col_max - col_min) x_vals_train = np.nan_to_num(normalize_cols(x_vals_train)) x_vals_test = np.nan_to_num(normalize_cols(x_vals_test))
- 现在,我们将使用以下代码声明数据和目标的批量大小和占位符:
batch_size = 50 x_data = tf.placeholder(shape=[None, 3], dtype=tf.float32) y_target = tf.placeholder(shape=[None, 1], dtype=tf.float32)
- 重要的是要用适当的形状声明我们的模型变量。我们可以将隐藏层的大小声明为我们希望的任何大小;在下面的代码块中,我们将其设置为有五个隐藏节点:
hidden_layer_nodes = 5 A1 = tf.Variable(tf.random_normal(shape=[3,hidden_layer_nodes])) b1 = tf.Variable(tf.random_normal(shape=[hidden_layer_nodes])) A2 = tf.Variable(tf.random_normal(shape=[hidden_layer_nodes,1])) b2 = tf.Variable(tf.random_normal(shape=[1]))
- 我们现在分两步宣布我们的模型。第一步是创建隐藏层输出,第二步是创建模型的
final_output
,如下所示:
请注意,我们的模型从三个输入特征到五个隐藏节点,最后到一个输出值。
hidden_output = tf.nn.relu(tf.add(tf.matmul(x_data, A1), b1)) final_output = tf.nn.relu(tf.add(tf.matmul(hidden_output, A2), b2))
- 我们作为
loss
函数的均方误差如下:
loss = tf.reduce_mean(tf.square(y_target - final_output))
- 现在我们将声明我们的优化算法并使用以下代码初始化我们的变量:
my_opt = tf.train.GradientDescentOptimizer(0.005) train_step = my_opt.minimize(loss) init = tf.global_variables_initializer() sess.run(init)
- 接下来,我们循环我们的训练迭代。我们还将初始化两个列表,我们可以存储我们的训练和
test_loss
函数。在每个循环中,我们还希望从训练数据中随机选择一个批量以适合模型,如下所示:
# First we initialize the loss vectors for storage. loss_vec = [] test_loss = [] for i in range(500): # We select a random set of indices for the batch. rand_index = np.random.choice(len(x_vals_train), size=batch_size) # We then select the training values rand_x = x_vals_train[rand_index] rand_y = np.transpose([y_vals_train[rand_index]]) # Now we run the training step sess.run(train_step, feed_dict={x_data: rand_x, y_target: rand_y}) # We save the training loss temp_loss = sess.run(loss, feed_dict={x_data: rand_x, y_target: rand_y}) loss_vec.append(np.sqrt(temp_loss)) # Finally, we run the test-set loss and save it. test_temp_loss = sess.run(loss, feed_dict={x_data: x_vals_test, y_target: np.transpose([y_vals_test])}) test_loss.append(np.sqrt(test_temp_loss)) if (i+1)%50==0: print('Generation: ' + str(i+1) + '. Loss = ' + str(temp_loss))
- 我们可以用
matplotlib
和以下代码绘制损失:
plt.plot(loss_vec, 'k-', label='Train Loss') plt.plot(test_loss, 'r--', label='Test Loss') plt.title('Loss (MSE) per Generation') plt.xlabel('Generation') plt.ylabel('Loss') plt.legend(loc='upper right') plt.show()
我们通过绘制下图来继续秘籍:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbd0QHmh-1681566911066)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/e9f5eb6c-586d-4ad8-8ac0-9a4e00482688.png)]
图 4:我们绘制了训练和测试装置的损失(MSE)。请注意,我们在 200 代之后略微过拟合模型,因为测试 MSE 不会进一步下降,但训练 MSE 确实
工作原理
我们的模型现已可视化为神经网络图,如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oSaVZUEy-1681566911066)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/tf-ml-cookbook-2e-zh/img/52077e5c-eee3-4684-97fa-cdac28fceec6.png)]
图 5:上图是我们的神经网络的可视化,在隐藏层中有五个节点。我们馈送三个值:萼片长度(S.L),萼片宽度(S.W.)和花瓣长度(P.L.)。目标将是花瓣宽度。总的来说,模型中总共有 26 个变量
更多
请注意,通过查看测试和训练集上的loss
函数,我们可以确定模型何时开始过拟合训练数据。我们还可以看到训练损失并不像测试装置那样平稳。这是因为有两个原因:第一个原因是我们使用的批量小于测试集,尽管不是很多;第二个原因是由于我们正在训练训练组,而测试装置不会影响模型的变量。
实现不同的层
了解如何实现不同的层非常重要。在前面的秘籍中,我们实现了完全连接的层。在本文中,我们将进一步扩展我们对各层的了解。
准备
我们已经探索了如何连接数据输入和完全连接的隐藏层,但是 TensorFlow 中有更多类型的层是内置函数。最常用的层是卷积层和最大池化层。我们将向您展示如何使用输入数据和完全连接的数据创建和使用此类层。首先,我们将研究如何在一维数据上使用这些层,然后在二维数据上使用这些层。
虽然神经网络可以以任何方式分层,但最常见的用途之一是使用卷积层和完全连接的层来首先创建特征。如果我们有太多的特征,通常会有一个最大池化层。在这些层之后,通常引入非线性层作为激活函数。我们将在第 8 章卷积神经网络中考虑的卷积神经网络(CNN)通常具有卷积,最大池化,激活,卷积,最大池化和激活形式。
操作步骤
我们将首先看一维数据。我们需要使用以下步骤为此任务生成随机数据数组:
- 我们首先加载我们需要的库并启动图会话,如下所示:
import tensorflow as tf import numpy as np sess = tf.Session()
- 现在我们可以初始化我们的数据(长度为
25
的 NumPy 数组)并创建占位符,我们将通过以下代码提供它:
data_size = 25 data_1d = np.random.normal(size=data_size) x_input_1d = tf.placeholder(dtype=tf.float32, shape=[data_size])
- 接下来,我们将定义一个将构成卷积层的函数。然后我们将声明一个随机过滤器并创建卷积层,如下所示:
请注意,许多 TensorFlow 的层函数都是为处理 4D 数据而设计的(
4D = [batch size, width, height, and channels]
)。我们需要修改输入数据和输出数据,以扩展或折叠所需的额外维度。对于我们的示例数据,我们的批量大小为 1,宽度为 1,高度为 25,通道大小为 1。要扩展大小,我们使用expand_dims()
函数,并且为了折叠大小,我们使用squeeze()
函数。另请注意,我们可以使用output_size=(W-F+2P)/S+1
公式计算卷积层的输出大小,其中W
是输入大小,F
是滤镜大小,P
是填充大小,S
是步幅大小。
def conv_layer_1d(input_1d, my_filter): # Make 1d input into 4d input_2d = tf.expand_dims(input_1d, 0) input_3d = tf.expand_dims(input_2d, 0) input_4d = tf.expand_dims(input_3d, 3) # Perform convolution convolution_output = tf.nn.conv2d(input_4d, filter=my_filter, strides=[1,1,1,1], padding="VALID") # Now drop extra dimensions conv_output_1d = tf.squeeze(convolution_output) return(conv_output_1d) my_filter = tf.Variable(tf.random_normal(shape=[1,5,1,1])) my_convolution_output = conv_layer_1d(x_input_1d, my_filter)
- 默认情况下,TensorFlow 的激活函数将按元素方式执行。这意味着我们只需要在感兴趣的层上调用激活函数。我们通过创建激活函数然后在图上初始化它来完成此操作,如下所示:
def activation(input_1d): return tf.nn.relu(input_1d) my_activation_output = activation(my_convolution_output)
- 现在我们将声明一个最大池化层函数。此函数将在我们的一维向量上的移动窗口上创建一个最大池化。对于此示例,我们将其初始化为宽度为 5,如下所示:
TensorFlow 的最大池化参数与卷积层的参数非常相似。虽然最大池化参数没有过滤器,但它确实有
size
,stride
和padding
选项。由于我们有一个带有有效填充的 5 的窗口(没有零填充),因此我们的输出数组将减少 4 个条目。
def max_pool(input_1d, width): # First we make the 1d input into 4d. input_2d = tf.expand_dims(input_1d, 0) input_3d = tf.expand_dims(input_2d, 0) input_4d = tf.expand_dims(input_3d, 3) # Perform the max pool operation pool_output = tf.nn.max_pool(input_4d, ksize=[1, 1, width, 1], strides=[1, 1, 1, 1], padding='VALID') pool_output_1d = tf.squeeze(pool_output) return pool_output_1d my_maxpool_output = max_pool(my_activation_output, width=5)
- 我们将要连接的最后一层是完全连接的层。在这里,我们想要创建一个多特征函数,输入一维数组并输出指示的数值。还要记住,要使用 1D 数组进行矩阵乘法,我们必须将维度扩展为 2D,如下面的代码块所示:
def fully_connected(input_layer, num_outputs): # Create weights weight_shape = tf.squeeze(tf.stack([tf.shape(input_layer), [num_outputs]])) weight = tf.random_normal(weight_shape, stddev=0.1) bias = tf.random_normal(shape=[num_outputs]) # Make input into 2d input_layer_2d = tf.expand_dims(input_layer, 0) # Perform fully connected operations full_output = tf.add(tf.matmul(input_layer_2d, weight), bias) # Drop extra dimensions full_output_1d = tf.squeeze(full_output) return full_output_1d my_full_output = fully_connected(my_maxpool_output, 5)
- 现在我们将初始化所有变量,运行图并打印每个层的输出,如下所示:
init = tf.global_variable_initializer() sess.run(init) feed_dict = {x_input_1d: data_1d} # Convolution Output print('Input = array of length 25') print('Convolution w/filter, length = 5, stride size = 1, results in an array of length 21:') print(sess.run(my_convolution_output, feed_dict=feed_dict)) # Activation Output print('Input = the above array of length 21') print('ReLU element wise returns the array of length 21:') print(sess.run(my_activation_output, feed_dict=feed_dict)) # Maxpool Output print('Input = the above array of length 21') print('MaxPool, window length = 5, stride size = 1, results in the array of length 17:') print(sess.run(my_maxpool_output, feed_dict=feed_dict)) # Fully Connected Output print('Input = the above array of length 17') print('Fully connected layer on all four rows with five outputs:') print(sess.run(my_full_output, feed_dict=feed_dict))
- 上一步应该产生以下输出:
Input = array of length 25 Convolution w/filter, length = 5, stride size = 1, results in an array of length 21: [-0.91608119 1.53731811 -0.7954089 0.5041104 1.88933098 -1.81099761 0.56695032 1.17945457 -0.66252393 -1.90287709 0.87184119 0.84611893 -5.25024986 -0.05473572 2.19293165 -4.47577858 -1.71364677 3.96857905 -2.0452652 -1.86647367 -0.12697852] Input = the above array of length 21 ReLU element wise returns the array of length 21: [ 0\. 1.53731811 0\. 0.5041104 1.88933098 0\. 0\. 1.17945457 0\. 0\. 0.87184119 0.84611893 0\. 0\. 2.19293165 0\. 0\. 3.96857905 0\. 0\. 0\. ] Input = the above array of length 21 MaxPool, window length = 5, stride size = 1, results in the array of length 17: [ 1.88933098 1.88933098 1.88933098 1.88933098 1.88933098 1.17945457 1.17945457 1.17945457 0.87184119 0.87184119 2.19293165 2.19293165 2.19293165 3.96857905 3.96857905 3.96857905 3.96857905] Input = the above array of length 17 Fully connected layer on all four rows with five outputs: [ 1.23588216 -0.42116445 1.44521213 1.40348077 -0.79607368]
对于神经网络,一维数据非常重要。时间序列,信号处理和一些文本嵌入被认为是一维的并且经常在神经网络中使用。
我们现在将以相同的顺序考虑相同类型的层,但是对于二维数据:
- 我们将从清除和重置计算图开始,如下所示:
ops.reset_default_graph() sess = tf.Session()
- 然后我们将初始化我们的输入数组,使其为
10x10
矩阵,然后我们将为具有相同形状的图初始化占位符,如下所示:
data_size = [10,10] data_2d = np.random.normal(size=data_size) x_input_2d = tf.placeholder(dtype=tf.float32, shape=data_size)
- 就像在一维示例中一样,我们现在需要声明卷积层函数。由于我们的数据已经具有高度和宽度,我们只需要将其扩展为二维(批量大小为 1,通道大小为 1),以便我们可以使用
conv2d()
函数对其进行操作。对于滤波器,我们将使用随机2x2
滤波器,两个方向的步幅为 2,以及有效填充(换句话说,没有零填充)。因为我们的输入矩阵是10x10
,我们的卷积输出将是5x5
,如下所示:
def conv_layer_2d(input_2d, my_filter): # First, change 2d input to 4d input_3d = tf.expand_dims(input_2d, 0) input_4d = tf.expand_dims(input_3d, 3) # Perform convolution convolution_output = tf.nn.conv2d(input_4d, filter=my_filter, strides=[1,2,2,1], padding="VALID") # Drop extra dimensions conv_output_2d = tf.squeeze(convolution_output) return(conv_output_2d) my_filter = tf.Variable(tf.random_normal(shape=[2,2,1,1])) my_convolution_output = conv_layer_2d(x_input_2d, my_filter)
- 激活函数在逐个元素的基础上工作,因此我们现在可以创建激活操作并使用以下代码在图上初始化它:
def activation(input_2d): return tf.nn.relu(input_2d) my_activation_output = activation(my_convolution_output)
- 我们的最大池化层与一维情况非常相似,只是我们必须声明最大池化窗口的宽度和高度。就像我们的卷积 2D 层一样,我们只需要扩展到两个维度,如下所示:
def max_pool(input_2d, width, height): # Make 2d input into 4d input_3d = tf.expand_dims(input_2d, 0) input_4d = tf.expand_dims(input_3d, 3) # Perform max pool pool_output = tf.nn.max_pool(input_4d, ksize=[1, height, width, 1], strides=[1, 1, 1, 1], padding='VALID') # Drop extra dimensions pool_output_2d = tf.squeeze(pool_output) return pool_output_2d my_maxpool_output = max_pool(my_activation_output, width=2, height=2)
- 我们的全连接层与一维输出非常相似。我们还应该注意到,此层的 2D 输入被视为一个对象,因此我们希望每个条目都连接到每个输出。为了实现这一点,我们需要完全展平二维矩阵,然后将其展开以进行矩阵乘法,如下所示:
def fully_connected(input_layer, num_outputs): # Flatten into 1d flat_input = tf.reshape(input_layer, [-1]) # Create weights weight_shape = tf.squeeze(tf.stack([tf.shape(flat_input), [num_outputs]])) weight = tf.random_normal(weight_shape, stddev=0.1) bias = tf.random_normal(shape=[num_outputs]) # Change into 2d input_2d = tf.expand_dims(flat_input, 0) # Perform fully connected operations full_output = tf.add(tf.matmul(input_2d, weight), bias) # Drop extra dimensions full_output_2d = tf.squeeze(full_output) return full_output_2d my_full_output = fully_connected(my_maxpool_output, 5)
- 现在我们需要初始化变量并使用以下代码为我们的操作创建一个馈送字典:
init = tf.global_variables_initializer() sess.run(init) feed_dict = {x_input_2d: data_2d}
- 每个层的输出应如下所示:
# Convolution Output print('Input = [10 X 10] array') print('2x2 Convolution, stride size = [2x2], results in the [5x5] array:') print(sess.run(my_convolution_output, feed_dict=feed_dict)) # Activation Output print('Input = the above [5x5] array') print('ReLU element wise returns the [5x5] array:') print(sess.run(my_activation_output, feed_dict=feed_dict)) # Max Pool Output print('Input = the above [5x5] array') print('MaxPool, stride size = [1x1], results in the [4x4] array:') print(sess.run(my_maxpool_output, feed_dict=feed_dict)) # Fully Connected Output print('Input = the above [4x4] array') print('Fully connected layer on all four rows with five outputs:') print(sess.run(my_full_output, feed_dict=feed_dict))
- 上一步应该产生以下输出:
Input = [10 X 10] array 2x2 Convolution, stride size = [2x2], results in the [5x5] array: [[ 0.37630892 -1.41018617 -2.58821273 -0.32302785 1.18970704] [-4.33685207 1.97415686 1.0844903 -1.18965471 0.84643292] [ 5.23706436 2.46556497 -0.95119286 1.17715418 4.1117816 ] [ 5.86972761 1.2213701 1.59536231 2.66231227 2.28650784] [-0.88964868 -2.75502229 4.3449688 2.67776585 -2.23714781]] Input = the above [5x5] array ReLU element wise returns the [5x5] array: [[ 0.37630892 0\. 0\. 0\. 1.18970704] [ 0\. 1.97415686 1.0844903 0\. 0.84643292] [ 5.23706436 2.46556497 0\. 1.17715418 4.1117816 ] [ 5.86972761 1.2213701 1.59536231 2.66231227 2.28650784] [ 0\. 0\. 4.3449688 2.67776585 0\. ]] Input = the above [5x5] array MaxPool, stride size = [1x1], results in the [4x4] array: [[ 1.97415686 1.97415686 1.0844903 1.18970704] [ 5.23706436 2.46556497 1.17715418 4.1117816 ] [ 5.86972761 2.46556497 2.66231227 4.1117816 ] [ 5.86972761 4.3449688 4.3449688 2.67776585]] Input = the above [4x4] array Fully connected layer on all four rows with five outputs: [-0.6154139 -1.96987963 -1.88811922 0.20010889 0.32519674]
工作原理
我们现在应该知道如何在 TensorFlow 中使用一维和二维数据中的卷积和最大池化层。无论输入的形状如何,我们最终都得到相同的大小输出。这对于说明神经网络层的灵活性很重要。本节还应该再次向我们强调形状和大小在神经网络操作中的重要性。
使用多层神经网络
我们现在将通过在低出生体重数据集上使用多层神经网络将我们对不同层的知识应用于实际数据。
准备
现在我们知道如何创建神经网络并使用层,我们将应用此方法,以预测低出生体重数据集中的出生体重。我们将创建一个具有三个隐藏层的神经网络。低出生体重数据集包括实际出生体重和出生体重是否高于或低于 2,500 克的指标变量。在这个例子中,我们将目标设为实际出生体重(回归),然后在最后查看分类的准确率。最后,我们的模型应该能够确定出生体重是否小于 2,500 克。
操作步骤
我们按如下方式处理秘籍:
- 我们将首先加载库并初始化我们的计算图,如下所示:
import tensorflow as tf import matplotlib.pyplot as plt import os import csv import requests import numpy as np sess = tf.Session()
- 我们现在将使用
requests
模块从网站加载数据。在此之后,我们将数据拆分为感兴趣的特征和目标值,如下所示:
# Name of data file birth_weight_file = 'birth_weight.csv' birthdata_url = 'https://github.com/nfmcclure/tensorflow_cookbook/raw/master' \ '/01_Introduction/07_Working_with_Data_Sources/birthweight_data/birthweight.dat' # Download data and create data file if file does not exist in current directory if not os.path.exists(birth_weight_file): birth_file = requests.get(birthdata_url) birth_data = birth_file.text.split('\r\n') birth_header = birth_data[0].split('\t') birth_data = [[float(x) for x in y.split('\t') if len(x) >= 1] for y in birth_data[1:] if len(y) >= 1] with open(birth_weight_file, "w") as f: writer = csv.writer(f) writer.writerows([birth_header]) writer.writerows(birth_data) # Read birth weight data into memory birth_data = [] with open(birth_weight_file, newline='') as csvfile: csv_reader = csv.reader(csvfile) birth_header = next(csv_reader) for row in csv_reader: birth_data.append(row) birth_data = [[float(x) for x in row] for row in birth_data] # Pull out target variable y_vals = np.array([x[0] for x in birth_data]) # Pull out predictor variables (not id, not target, and not birthweight) x_vals = np.array([x[1:8] for x in birth_data])
TensorFlow 机器学习秘籍第二版:6~8(2)https://developer.aliyun.com/article/1426838