第10章_人工神经网络简介
参考书
《机器学习实战——基于Scikit-Learn和TensorFlow》
工具
python3.5.1,Jupyter Notebook, Pycharm
感知器
- 《行为的组织》,1949,Donald Hebb:如果一个生物神经元总是出发另外的神经元,那么这两个神经元之间的连接就会变得更强。
- Siegrid Lowel:同时处于激活状态的细胞时会连在一起的。
- Hebb定律(又叫Hebbian学习):当两个神经元有相同的输出时,它们之间的连接权重就会增强。
- 感知器收敛定理(Rosenblatt):如果训练实例是线性可分的,这个算法会收敛到一个解。
线性阈值单元(LTU)
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)
per_clf.fit(X, y)
y_pred = per_clf.predict([[2, 0.5]])
print(y_pred)
-
事实上,在sklearn中,Perceptron类的行为等同于使用以下超参数的SGDClassifier:
loss="perceptron", learning_rate="constant", eta0=1(学习速率), penalty=None(不做正则化)
-
注意和逻辑回归分类器相反,感知器不输出某个类的概率,它只能根据一个固定的阈值来做预测。这也是更应该使用逻辑回归而不是感知器的一个原因。
-
感知器无法处理一些很微小的问题,比如异或分类问题(XOR),不过事实证明感知器的一些限制可以通过多层感知器来解决(Multi-Layer Perceptron,MLP)
多层感知器和反向传播
- 如果一个ANN有2个及以上的隐藏层,则被称为深度神经网络(DNN)。
- 反向自动微分的梯度下降法:对于每个训练实例,反向传播算法先做一次预测(正向过程),度量误差,然后反向的遍历每个层次来度量每个连接的误差贡献度(反向过程),最后再微调每个连接的权重来降低误差(梯度下降)。
使用TensorFlow的高级API来训练MLP
-
用DNNClassifier类来训练一个有着任意数量隐藏层,并包含一个用来计算类别概率的sofmax输出层的神经网络。
from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf mnist = input_data.read_data_sets("D:/李添的数据哦!!!/BookStudy/book4/MNIST_data/MNIST_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") config = tf.contrib.learn.RunConfig(tf_random_seed=42) # not shown in the config feature_cols = 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_cols, config=config) dnn_clf = tf.contrib.learn.SKCompat(dnn_clf) # if TensorFlow >= 1.1 dnn_clf.fit(X_train, y_train, batch_size=50, steps=40000)
-
模型评估
from sklearn.metrics import accuracy_score y_pred = dnn_clf.predict(X_test) accuracy_score(y_test, y_pred['classes'])
from sklearn.metrics import log_loss y_pred_proba = y_pred['probabilities'] log_loss(y_test, y_pred_proba)
-
库还包含了一些方便的函数来评估模型:
dnn_clf.score(X_test, y_test)
使用纯TensorFlow训练DNN
1. 构建阶段
-
创建用于输入和目标值占位符节点
import tensorflow as tf import numpy as np n_inputs = 28*28 n_hidden1 = 300 n_hidden2 = 100 n_outputs = 10 X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X") y = tf.placeholder(tf.int64, shape=(None), name="y")
-
创建用以创建神经网络的函数,使用它创建DNN
-
手动
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 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")
-
利用fully_connected()函数来替换自己写的neuron_layer()函数
from tensorflow.contrib.layers import fully_connected with tf.name_scope("dnn"): hidden1 = fully_connected(X, n_hidden1, scope="hidden_1") hidden2 = fully_connected(hidden1, n_hidden2, scope="hidden_2") logits2 = fully_connected(hidden2, n_outputs, scope="outputs_", activation_fn=None)
-
-
定义成本函数
with tf.name_scope("loss"): xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits2) 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(logits2, y, 1) accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
-
最后初始化和保存模型
init = tf.global_variables_initializer() saver = tf.train.Saver()
-
tf.truncated_normal((n_inputs, n_neurons), stddev=np.sqrt(n_inputs))
:使用标准偏差为2/sqrt(n_inputs)的阶段正态(高斯)分布进行随机初始化。使用截断的正态分布而不是常规的正态分布,保证这里不存在任何减慢训练的大权重。使用一个指定的标准偏差会让算法收敛得更快。 -
为所有隐藏层随机地初始化连接权重值是非常重要的,这可以避免任何可能导致梯度下降出现无法终止的对称性。
-
tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
:它会根据“logits”来计算交叉熵(比如,在通过softmax激活函数之前网络的输出),并且期望以0到分类个数减1的整数形式标记。这会计算出一个包含每个实例的交叉熵的一维张量。可以用TensorFlow的reduce_mean()函数来计算所有实例的平均交叉熵。 -
函数
sparse_softmax_cross_entropy_with_logits()
与先应用softmax函数再计算交叉熵的效果是一样的,不过它更高效一些,另外它会处理一些边界值如logits等于0的情况。这也是为什么我们之前没有使用softmax激活函数的原因。此外,还有一个softmax_cross_entropy_with_logits()
函数,它以one-hot的形式获取标签(而不是从0到分类数量-1)
2. 执行阶段
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("D:/李添的数据哦!!!/BookStudy/book4/MNIST_data/MNIST_data/")
n_epochs = 400
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, "D:/李添的数据哦!!!/BookStudy/book4/model/my_model_final.ckpt")
- 先打开一个TensorFlow的对话。
- 运行初始化代码来初始化所有的变量。
- 运行主训练循环:在每一个周期(epoch)中,迭代一组和训练集大小相对应的批次,每一个小批次通过next_batch()方法来获得,然后执行训练操作,将当前小批次的输入数据和目标传入。
- 接下来,在每个周期阶数的时候,代码会用上一个小批次以及全量的训练集来评估模型,并打印结果。
- 最后,将模型的参数保存到硬盘。
使用神经网络
- 保留构建期的代码,修改执行期的代码。
with tf.Session() as sess:
saver.restore(sess, "D:/李添的数据哦!!!/BookStudy/book4/model/my_model_final.ckpt")
X_new_scaled = X_test[0].reshape(1, -1)
Z = logits.eval(feed_dict={X: X_new_scaled})
y_pred = np.argmax(Z, axis=1)
- 首先从硬盘上加载模型参数。
- 然后加载需要被分类的新图片。
- 然后评估logits节点。
- 如果你想知道所有分类的概率,你可以给logits使用softmax函数,如果你只是想预测一个分类,只需要选出那个有最大的logit值即可(argmax函数)。
微调神经网络的超参数
- 利用随机搜索法
- 使用像Oscar这样的工具
1. 隐藏层的个数
- 深层网络比浅层网络有更高的参数效率:深层网络可以用非常少的神经元来建模复杂函数,因此训练起来更加快速。
- 现实世界的数据往往会按照层次结构组织,而DNN天生的就很擅长处理这种数据:低级隐藏层用以建模低层结构,中级隐藏层组合这些低层结构来建模中层结构,高级隐藏层和输出层组合这些中层结构来构建高层结构。
- 分层的架构不仅可以帮助DNN更快的归纳出好方案,还可以提高对于新数据集的泛化能力。
2. 每个隐藏层中的神经元数
- 输入输出层中的神经元数由任务要求的输入输出类型决定。
- 对于隐藏层来说,一个常用的实践是以漏斗型来定义其尺寸,每层的神经元数依次减少:原因是许多低级功能可以合并成数量更少的高级功能。
- 不过,这种实践现在也不那么常用了,你可以将所有层次定义为同一尺寸,每个隐藏层各150个神经元:这只是一个超参数调整。
- 一个更简单的做法是使用(比实际所需)更多的层次和神经元,然后提前结束训练来避免过度拟合(以及其他的正则化技术,特别是dropout)。这被称为弹力裤方法。无须花费时间找刚好适合你的裤子,随便挑弹力裤,它会缩小到合适的尺寸。
3. 激活函数
- 大多数情况下,你可以在隐藏层中使用ReLU激活函数(或者其变种),它比其他激活函数要快一些,因为梯度下降对于大输入值没有上限,会导致它无法终止(与逻辑函数或者双曲正切函数刚好相反,它们会在1处饱和)。
- 对于输出层,softmax激活函数对于分类任务(如果分类是互斥的)来说是一个很不错的选择。对于回归任务,则完全可以不使用激活函数。
练习摘抄
-
为什么通常更倾向用逻辑回归分类器而不是经典的感知器?如何调整一个感知器,让它与逻辑回归分类器等价?
经典的感知器只有在数据集是线性可分的情况下才会收敛,并且不能估计分类的概率。作为对比,逻辑回归分类器即使在数据集不是线性可分的情况下也可以很好的收敛,而且还能输出分类的概率。
如果你将感知器的激活函数修改为逻辑激活函数(或者如果有多个神经元的时候,采用softmax激活函数),然后训练其使用梯度下降(或者使成本函数最小化的一些其他优化算法,通常使交叉熵法),那么它就会变成一个逻辑回归分类器了。
-
为什么逻辑激活函数是训练第一个MLP的关键因素?
因为它的导数总是非零的,所以梯度下降总是可以持续的。当激活功能是一个阶梯函数时,渐变下降就不能再持续了,因为这时候根本没有斜率。
-
什么时反向传播,它时如何工作的?反向传播与反式自动微分有何区别?
反向传播是一种用于训练人工神经网络的技术。它首先计算关于每个模型参数(所有的权重和偏差)的成本函数的梯度,然后使用这些梯度执行梯度下降。这种反向传播步骤通常执行数千次或数百万次,并需要多个训练批次,直到模型参数收敛到最小化成本函数的值为止。为了计算梯度,反向传播使用反向模式autodiff(尽管在反向传播被发明的时候还不叫autodiff,事实上autodiff的概念已经被重新发明了多次)。
反向模式的autodiff会先在计算图上正向执行一次,计算按当前训练批次的每个节点的值,然后反向执行一次,一次性计算所有梯度。
那和反向传播有什么区别呢?反向传播是指使用多个反向传播步骤来训练人工神经网络的全部过程,每个步骤计算梯度并使用它们执行梯度下降的过程。相反,反向模式autodiff只是一种简单的计算梯度的技术,只是恰好被反向传播使用了而已。
我的CSDN:https://blog.csdn.net/qq_21579045
我的博客园:https://www.cnblogs.com/lyjun/
我的Github:https://github.com/TinyHandsome
纸上得来终觉浅,绝知此事要躬行~
欢迎大家过来OB~
by 李英俊小朋友