机器学习实战_人工神经网络
感知器:线性阈值单元(LTU)
其中,step(z)为阶跃函数,最常见的在感知器中使用的阶跃函数是 Heaviside 阶跃函数。有时使用符号函数代替。
单一的 LTU 可被用作简单线性二元分类。它计算输入的线性组合,如果结果超过阈值,它输出正类或者输出负类(就像一个逻辑回归分类或线性 SVM)。图 10-5 表示具有两个输入和三个输出的感知器。该感知器可以将实例同时分类为三个不同的二进制类,这使得它是一个多输出分类器
感知器的权重进行如下调整:
$$W_{i,j}^{Next}=W_{i,j}+\eta(\widehat{y}_j-y_j)x_i$$
- 其中$W_{i,j}$ 是第i输入神经元与第j个输出神经元之间的连接权重。
每个输出神经元的决策边界是线性的,因此感知机不能学习复杂的模式.klearn 提供了一个感知器类,它实现了一个 LTU 网络。它可以像你所期望的那样使用,例如在 iris 数据集
import numpy as np from sklearn.datasets import load_iris from sklearn.linear_model import Perceptron iris = load_iris() X = iris.data[:, (2, 3)] # 花瓣长度,宽度 y = (iris.target == 0).astype(np.int) per_clf = Perceptron(random_state=42) # Perceptron为感知器模型 per_clf.fit(X, y) y_pred = per_clf.predict([[2, 0.5]])
您可能已经认识到,感知器学习算法类似于随机梯度下降。事实上,sklearn 的感知器类相当于使用具有以下超参数的 SGD 分类器:loss="perceptron",learning_rate="constant"(学习率),eta0=1,penalty=None(无正则化)。
事实证明,感知器的一些局限性可以通过堆叠多个感知器来消除。由此产生的人工神经网络被称为多层感知器(MLP)。特别地,MLP 可以解决 XOR异或问题
多层感知器(MLP)与反向传播
MLP 由一个(通过)输入层、一个或多个称为隐藏层的 LTU 组成,一个最终层 LTU 称为输出层。除了输出层之外的每一层包括偏置神经元,并且全连接到下一层。当人工神经网络有两个或多个隐含层时,称为深度神经网络(DNN)
为了使算法能够正常工作,作者对 MLP 的体系结构进行了一个关键性的改变:改变了阶跃函数,以下是常用的阶跃激活函数
- 逻辑函数 σ(z) = 1 / (1 + exp(–z))
- 双曲正切函数 tanh (z) = 2σ(2z) – 1
- Relu 函数 ReLU (z) = max (0, z) (效果会更好一点)
这些流行的激活函数及其衍生物如图所示
MLP 通常用于分类,每个输出对应于不同的二进制类(例如,垃圾邮件/正常邮件,紧急/非紧急,等等)。当类是多类的(例如,0 到 9 的数字图像分类)时,输出层通常通过用共享的 softmax 函数替换单独的激活函数来修改。
用 TensorFlow 高级 API 训练 MLP
与 TensorFlow 一起训练 MLP 最简单的方法是使用高级 API TF.Learn,这与 sklearn 的 API 非常相似。DNNClassifier可以很容易训练具有任意数量隐层的深度神经网络,而 softmax 输出层输出估计的类概率。例如,下面的代码训练两个隐藏层的 DNN(一个具有 300 个神经元,另一个具有 100 个神经元)和一个具有 10 个神经元的 SOFTMax 输出层进行分类:
import tensorflow as tf feature_columns = tf.contrib.learn.infer_real_valued_columns_from_input(X_train) # 构造数值类型的特征列 dnn_clf = tf.contrib.learn.DNNClassifier(hidden_units=[300, 100], n_classes=10, feature_columns=feature_column) dnn_clf.fit(x=X_train, y=y_train, batch_size=50, steps=40000)
如果你在 MNIST 数据集上运行这个代码(在缩放它之后,例如,通过使用 skLearn 的StandardScaler),你实际上可以得到一个在测试集上达到 98.1% 以上精度的模型!这比我们在之前训练的最好的模型都要好:
>>> from sklearn.metrics import accuracy_score >>> y_pred = list(dnn_clf.predict(X_test)) >>> accuracy_score(y_test, y_pred) 0.98180000000000001
TF.Learn 学习库也为评估模型提供了一些方便的功能:
>>> dnn_clf.evaluate(X_test, y_test) {'accuracy': 0.98180002, 'global_step': 40000, 'loss': 0.073678359}
NNClassifier基于 Relu 激活函数创建所有神经元层(我们可以通过设置超参数activation_fn来改变激活函数)。输出层基于 SoftMax 函数,损失函数是交叉熵。
使用普通 TensorFlow 训练 DNN
如果您想要更好地控制网络架构,您可能更喜欢使用 TensorFlow 的较低级别的 Python API。在本节中,我们将使用与之前的 API 相同的模型,我们将实施 Minibatch 梯度下降来在 MNIST 数据集上进行训练。 第一步是建设阶段,构建 TensorFlow 图。 第二步是执行阶段,您实际运行计算图谱来训练模型。
构造阶段:
首先我们需要导入tensorflow库。 然后我们必须指定输入和输出的数量,并设置每个层中隐藏的神经元数量:
import tensorflow as tf n_inputs = 28*28 # MNIST n_hidden1 = 300 n_hidden2 = 100 n_outputs = 10
接下来,可以使用占位符节点来表示训练数据和目标。X的形状仅有部分被定义。 我们知道它将是一个 2D 张量(即一个矩阵),沿着第一个维度的实例和第二个维度的特征,我们知道特征的数量将是28×28(每像素一个特征) 但是我们不知道每个训练批次将包含多少个实例。 所以X的形状是(None, n_inputs)。 同样,我们知道y将是一个 1D 张量,每个实例有一个入口,但是我们再次不知道在这一点上训练批次的大小,所以形状(None)。
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X") y = tf.placeholder(tf.int64, shape=(None), name="y")
现在让我们创建一个实际的神经网络。 占位符X将作为输入层; 在执行阶段,它将一次更换一个训练批次(注意训练批中的所有实例将由神经网络同时处理)。 现在您需要创建两个隐藏层和输出层。 两个隐藏的层几乎相同:它们只是它们所连接的输入和它们包含的神经元的数量不同。 输出层也非常相似,但它使用 softmax 激活函数而不是 ReLU 激活函数。 所以让我们创建一个neuron_layer()函数,我们将一次创建一个图层。 它将需要参数来指定输入,神经元数量,激活函数和图层的名称:
def neuron_layer(X, n_neurons, name, activation=None): # 输入、神经元数量、图层名称、激活函数 with tf.name_scope(name): n_inputs = int(X.get_shape()[1]) # 输入数量 stddev = 2 / np.sqrt(n_inputs) init = tf.truncated_normal((n_inputs, n_neurons), stddev=stddev) W = tf.Variable(init, name="weights") b = tf.Variable(tf.zeros([n_neurons]), name="biases") z = tf.matmul(X, W) + b if activation == "relu": return tf.nn.relu(z) else: return z
我们逐行浏览这个代码:
- 首先,我们使用名称范围来创建每层的名称:它将包含该神经元层的所有计算节点。 这是可选的,但如果节点组织良好,则 TensorBoard图形将会更加出色。
- 接下来,我们通过查找输入矩阵的形状并获得第二个维度的大小来获得输入数量(第一个维度用于实例)。
- 接下来的三行创建一个保存权重矩阵的W变量。它将是包含每个输入和每个神经元之间的所有连接权重的2D张量;因此,它的形状将是(n_inputs,n_neurons)。它将被随机初始化,使用具有标准差为2/√n的截断的正态(高斯)分布(使用截断的正态分布而不是常规正态分布确保不会有任何大的权重,这可能会减慢训练。).使用这个特定的标准差有助于算法的收敛速度更快(我们将在第11章中进一步讨论这一点),这是对神经网络的微小调整之一,对它们的效率产生了巨大的影响)。重要的是为所有隐藏层随机初始化连接权重,以避免梯度下降算法无法中断的任何对称性。(例如,如果将所有权重设置为 0,则所有神经元将输出0,并且给定隐藏层中的所有神经元的误差梯度将相同。 然后,梯度下降步骤将在每个层中以相同的方式更新所有权重,因此它们将保持相等。换句话说,尽管每层有数百个神经元,你的模型就像每层只有一个神经元一样。)
- 下一行创建一个偏差的b变量,初始化为 0(在这种情况下无对称问题),每个神经元有一个偏置参数。
- 然后我们创建一个子图来计算z = X·W + b。该向量化实现将有效地计算输入的加权和加上层中每个神经元的偏置,对于批次中的所有实例,仅需一次.
- 最后,如果激活参数设置为relu,则代码返回relu(z)(即max(0,z)),否则它只返回z。
好了,现在你有一个很好的函数来创建一个神经元层。 让我们用它来创建深层神经网络! 第一个隐藏层以X为输入。 第二个将第一个隐藏层的输出作为其输入。 最后,输出层将第二个隐藏层的输出作为其输入。
with tf.name_scope("dnn"): hidden1 = neuron_layer(X, n_hidden1, "hidden1", activation="relu") hidden2 = neuron_layer(hidden1, n_hidden2, "hidden2", activation="relu") logits = neuron_layer(hidden2, n_outputs, "outputs")
请注意,为了清楚起见,我们再次使用名称范围。 还要注意,logit 是在通过 softmax 激活函数之前神经网络的输出:为了优化,我们稍后将处理 softmax 计算。
正如你所期望的,TensorFlow 有许多方便的功能来创建标准的神经网络层,所以通常不需要像我们刚才那样定义你自己的neuron_layer()函数。 例如,TensorFlow 的fully_connected()函数创建一个完全连接的层,其中所有输入都连接到图层中的所有神经元。 它使用正确的初始化策略来负责创建权重和偏置变量,并且默认情况下使用 ReLU 激活函数(我们可以使用activate_fn参数来更改它)我们来调整上面的代码来使用fully_connected()函数,而不是我们的neuron_layer()函数。 只需导入该功能,并使用以下代码替换 dnn 构建部分:
from tensorflow.contrib.layers import fully_connected with tf.name_scope("dnn"): hidden1 = fully_connected(X, n_hidden1, scope="hidden1") hidden2 = fully_connected(hidden1, n_hidden2, scope="hidden2") logits = fully_connected(hidden2, n_outputs, scope="outputs", activation_fn=None)
tensorflow.contrib包包含许多有用的功能,但它是一个尚未分级成为主要 TensorFlow API 一部分的实验代码的地方。 因此,full_connected()函数(和任何其他contrib代码)可能会在将来更改或移动。
现在我们已经有了神经网络模型,我们需要定义我们用来训练的损失函数。TensorFlow 提供了几种计算交叉熵的功能。 我们将使用sparse_softmax_cross_entropy_with_logits()。 然后,我们可以使用 TensorFlow 的reduce_mean()函数来计算所有实例的平均交叉熵。
with tf.name_scope("loss"): xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits) loss = tf.reduce_mean(xentropy, name="loss")
该sparse_softmax_cross_entropy_with_logits()函数等同于应用 SOFTMAX 激活函数,然后计算交叉熵,但它更高效,它妥善照顾的边界情况下,比如 logits 等于 0,这就是为什么我们没有较早的应用 SOFTMAX 激活函数。 还有称为softmax_cross_entropy_with_logits()的另一个函数,该函数在标签单热载体的形式(而不是整数 0 至类的数目减 1)
我们有神经网络模型,我们有损失函数,现在我们需要定义一个GradientDescentOptimizer来调整模型参数以最小化损失函数
learning_rate = 0.01 with tf.name_scope("train"): optimizer = tf.train.GradientDescentOptimizer(learning_rate) training_op = optimizer.minimize(loss)
建模阶段的最后一个重要步骤是指定如何评估模型。 我们将简单地将精度用作我们的绩效指标。 首先,对于每个实例,通过检查最高 logit 是否对应于目标类别来确定神经网络的预测是否正确。 为此,您可以使用in_top_k()函数。 这返回一个充满布尔值的 1D 张量,因此我们需要将这些布尔值转换为浮点数,然后计算平均值。 这将给我们网络的整体准确性.
with tf.name_scope("eval"): correct = tf.nn.in_top_k(logits, y, 1) accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
而且,像往常一样,我们需要创建一个初始化所有变量的节点,我们还将创建一个Saver来将我们训练有素的模型参数保存到磁盘中:
init = tf.global_variables_initializer() saver = tf.train.Saver()
建模阶段结束。 这是不到 40 行代码,但相当激烈:我们为输入和目标创建占位符,我们创建了一个构建神经元层的函数,我们用它来创建 DNN,我们定义了损失函数,我们 创建了一个优化器,最后定义了性能指标。 现在到执行阶段。
执行阶段
这部分要短得多,更简单。 首先,我们加载 MNIST。 我们可以像之前的章节那样使用 ScikitLearn,但是 TensorFlow 提供了自己的助手来获取数据,将其缩放(0 到 1 之间),将它洗牌,并提供一个简单的功能来一次加载一个小批量:
from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("/tmp/data/")
现在我们定义我们要运行的迭代数,以及小批量的大小:
n_epochs = 10001 batch_size = 50
现在我们去训练模型:
with tf.Session() as sess: init.run() for epoch in range(n_epochs): for iteration in range(mnist.train.num_examples // batch_size): X_batch, y_batch = mnist.train.next_batch(batch_size) sess.run(training_op, feed_dict={X: X_batch, y: y_batch}) acc_train = accuracy.eval(feed_dict={X: X_batch, y: y_batch}) acc_test = accuracy.eval(feed_dict={X: mnist.test.images, y: mnist.test.labels}) print(epoch, "Train accuracy:", acc_train, "Test accuracy:", acc_test) save_path = saver.save(sess, "./my_model_final.ckpt")
该代码打开一个 TensorFlow 会话,并运行初始化所有变量的init节点。 然后它运行的主要训练循环:在每个时期,通过一些小批次的对应于训练集的大小的代码进行迭代。 每个小批量通过next_batch()方法获取,然后代码简单地运行训练操作,为当前的小批量输入数据和目标提供。 接下来,在每个时期结束时,代码评估最后一个小批量和完整训练集上的模型,并打印出结果。 最后,模型参数保存到磁盘。
现在神经网络被训练了,你可以用它进行预测。 为此,您可以重复使用相同的建模阶段,但是更改执行阶段,如下所示:
with tf.Session() as sess: saver.restore(sess, "./my_model_final.ckpt") # or better, use save_path X_new_scaled = mnist.test.images[:20] Z = logits.eval(feed_dict={X: X_new_scaled}) y_pred = np.argmax(Z, axis=1)
首先,代码从磁盘加载模型参数。 然后加载一些您想要分类的新图像。 记住应用与训练数据相同的特征缩放(在这种情况下,将其从 0 缩放到 1)。 然后代码评估对数点节点。 如果您想知道所有估计的类概率,则需要将softmax()函数应用于对数,但如果您只想预测一个类,则可以简单地选择具有最高 logit 值的类(使用argmax()函数做的伎俩)。
注意隐藏层数量,每层隐藏层的神经元数量,激活函数的选择
完整代码:
from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf from sklearn.metrics import accuracy_score import numpy as np if __name__ == '__main__': n_inputs = 28 * 28 n_hidden1 = 300 n_hidden2 = 100 n_outputs = 10 mnist = input_data.read_data_sets("/tmp/data/") X_train = mnist.train.images X_test = mnist.test.images y_train = mnist.train.labels.astype("int") y_test = mnist.test.labels.astype("int") X = tf.placeholder(tf.float32, shape= (None, n_inputs), name='X') y = tf.placeholder(tf.int64, shape=(None), name = 'y') with tf.name_scope('dnn'): hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu ,name= 'hidden1') hidden2 = tf.layers.dense(hidden1, n_hidden2, name='hidden2', activation= tf.nn.relu) logits = tf.layers.dense(hidden2, n_outputs, name='outputs') with tf.name_scope('loss'): xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels = y, logits = logits) loss = tf.reduce_mean(xentropy, name='loss')#所有值求平均 learning_rate = 0.01 with tf.name_scope('train'): optimizer = tf.train.GradientDescentOptimizer(learning_rate) training_op = optimizer.minimize(loss) with tf.name_scope('eval'): correct = tf.nn.in_top_k(logits ,y ,1)#是否与真值一致 返回布尔值 accuracy = tf.reduce_mean(tf.cast(correct, tf.float32)) #tf.cast将数据转化为0,1序列 init = tf.global_variables_initializer() n_epochs = 20 batch_size = 50 with tf.Session() as sess: init.run() for epoch in range(n_epochs): for iteration in range(mnist.train.num_examples // batch_size): X_batch, y_batch = mnist.train.next_batch(batch_size) sess.run(training_op,feed_dict={X:X_batch, y: y_batch}) acc_train = accuracy.eval(feed_dict={X:X_batch, y: y_batch}) acc_test = accuracy.eval(feed_dict={X: mnist.test.images, y: mnist.test.labels}) print(epoch, "Train accuracy:", acc_train, "Test accuracy:", acc_test)