アカリの部屋

人工神经网络

神经网络结构

简单来讲,人工神经网络就是将很多的分类器分层加权叠加的集成学习方式,整个网络分为输入层、输出层和一到多个隐层三部分,每个节点都是一个神经元感知器。

logo

感知器做的事情其实就是一个回归算法,比如,对于特征 $ X{_1} $ 和 $ X{_2} $(包括常数项1),每个感知器都把它当做一个刺激(输入),这个刺激有一定强度 $ z = θ{_0} + θ{_1}X{_1} + θ{_2}X{_2} $,每一项前面有个权重,感知器就是要让这个刺激过一个激活函数如 $ g(z) = \frac{1}{1 + e^{-z}} $,这时就会输出一个0到1的概率值。

这就回到了逻辑回归做分类,可是传统模型要面临一些问题,LR要做高次项组合项,在维度本来就很高的时候容易大幅度继续抬高维度数,SVM要加核又慢,而且这些模型都很分出“两边和中间”之类的线性不可分问题,于是就希望有个方式不关心它的特征到底是什么样就能分类。

隐层的作用

神经网络要用多个感知器解决这个问题,并且在网络当中添加隐层。只有一层的神经网络叫浅层神经网络(SNN),层次较多的叫深层神经网络(DNN),每一层的输出结果经过组合后,再通过非线性的激活函数传递给下一层当做输入。

如,对于线性不可分的问题,想识别某个点是否在两条直线所夹成的范围内,就需要一个两个神经元的隐层,其中每个神经元都是一个线性分类器,它们各自学得一个分类边界,这样,隐层的功能就是将两个分类器的结果做或/且逻辑操作,组合成一个新的分类器输出给下一层。神经网络要学习的东西,就是每个神经元各自的参数权重,来达到一个最佳组合效果。

也就是说,隐层会将上层的特征做组合,这等于在帮你做特征工程,来挖掘出那些线性不可分的隐含特征,使得这些特征在这个神经元里变成线性可分的

logo

显然,LR就可以看做一个无隐层的感知器,它分出的形状是一条线,但是因为无法实现异或组合,所以这条线无论如何调整斜率截距,都无法“分出两边”。

添加一个隐层后,就可以完成AND和OR逻辑操作来汇总两条决策边界,在这种情况下就能识别任何开/闭凸区域。理论上讲,只要神经元足够多,单隐层神经网络就可以逼近任何连续函数,因为不论曲线多么曲折,都可以看成端点足够近的线段组合。更多的神经元会增加模型表达能力,但是也会带来过拟合的风险。

再添加一个隐层后,相当于可以在AND的基础上做OR,所以两层神经网络可以区分开/闭凸多边形的区域。对于分类问题,2层神经网络能得到很好的工程效果,3层的神经网络效果也许更好,可加到更多层则效果提升越发不明显,而且容易过拟合,但是在过拟合的时候应该正则化,而不是盲目地降低模型的拟合能力。

激活函数和训练方法

当一个信号经过感知器后,要通过一个激活函数如Sigmoid函数处理才能向后输出到下一层,为什么不能直接把结果向后传呢?如果 $ wx+b $ 直接往后走,成为 $ w(wx+b)+b $,那本质还是 $ wx+b $ ,隐层就毫无意义了,所以一定要用一个“非线性”的函数来整合多个分类器。

神经网络的训练原理,是经过多个隐层,将信号从x变成o,输出层每个节点都是一个答案o,然而这些o和标准答案d有偏差,那么就会定一个损失函数,然后把这个误差从后向前传递,追溯回去,来修正每一层各个感知器的权重。

假设这个函数是L2损失函数,即差的平方再平均:$ E = \frac{1}{2} (d-O)^2 $
那么这个误差展开到隐层就是:$ E = \frac{1}{2} \sum{_k} [d{_k}-sigmoid(net{_k})]^2 $ ,这里的net表示这一层的输入是前一层的输出。
那么再往前传递,直到输入层,误差就可以一层一层以嵌套复合函数的方式来表示,在习惯上,每一层的激活函数都设置成一样的,比如sigmoid。

注意,这里的误差展开到隐层中存在一个sigmoid项,这个函数的性质是接近1的时候斜率很小,这会导致如果初始误差很大的话,损失会收敛得很慢,神经网络训练起来就会很慢,然而我们又不能粗暴地换掉Sigmoid激活函数,因为它有很多很好的数学性质,所以常见的手段是用没有Sigmoid项的交叉熵损失函数替代L2损失函数

机器学习要做的就是修正每个节点的权值 $ w $,就要用到随机梯度下降。
当比较出输出层结果d和o的差距时,可以发现这个差距来自前面一个隐层的所有神经元的贡献,那么就可以通过对前一层每个神经元贡献的权重求偏导的方式追溯出每个神经元的修正值,就可以用梯度下降更新这个权重了。然后再重复这个过程逐层向前追溯。
当然,输出层(后一层)也有多个神经元,那么后一层每个神经元的误差都会对前一层某一个神经元的误差做贡献,那实际这就是对一个加法复合函数 $ f(x)+g(x) $ 求偏导的过程了。

训练中的问题

有些多层复合函数的导数的形式是 $ F’ = m’ * n’ * g’ * f’ $ ,这种连乘形式代表了函数之前会相互影响,一旦某个值非常大或小,那么对Sigmoid求出来的偏导就会逼近0,等于梯度没有了,由于有扩散的效应,这个网络因为某个节点挂了,基本就学不到东西了,这个现象叫做梯度消失

因为复合函数的损失函数是一个很复杂的函数,在梯度下降当中非常容易陷入局部最低点,但是在工业上,并不在意神经网络走到局部最低。

逻辑回归实现

单个神经元的本质就是一个逻辑回归,它用TensorFlow实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import time
import numpy as np
import tensorflow as tf
# 准备数据
# 使用tensorflow自带的工具加载MNIST手写数字集合
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('/data/mnist', one_hot=True)
# 准备placeholder
batch_size = 128 # 每一批训练数据为128个(因为要采用minibatch方式的SGD)
# 这个参数可以定义为None,但是是一种容易出错的方式
X = tf.placeholder(tf.float32, [batch_size, 784], name='X_placeholder')
Y = tf.placeholder(tf.int32, [batch_size, 10], name='Y_placeholder')
# 准备变量
w = tf.Variable(tf.random_normal(shape=[784, 10], stddev=0.01), name='weights') # 初始化随机的w
b = tf.Variable(tf.zeros([1, 10]), name="bias") # 初始化全为0的偏置项
# 定义损失的计算方式
# 1.计算得分
logits = tf.matmul(X, w) + b
# 2.计算交叉熵损失
entropy = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=Y, name='loss')
# 3. 计算元素平均值
loss = tf.reduce_mean(entropy)
# 定义优化的方式(随机梯度下降)
learning_rate = 0.01
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)
# 迭代总轮次30次
n_epochs = 30
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
writer = tf.summary.FileWriter('./lession/graphs/logistic_reg', sess.graph)
start_time = time.time()
n_batches = int(mnist.train.num_examples/batch_size) # 在batch_size下要运行的batch数
# 总共迭代30轮
for i in range(n_epochs):
total_loss = 0
# 对每个批次
for _ in range(n_batches):
# 拿到这个批次的X和Y
X_batch, Y_batch = mnist.train.next_batch(batch_size)
# 执行这一轮训练,拿到损失
_, loss_batch = sess.run([optimizer, loss], feed_dict={X: X_batch, Y:Y_batch})
total_loss += loss_batch
# 输出当前批次的损失
print('Average loss epoch {0}: {1}'.format(i, total_loss/n_batches))
# 输出30轮的时间
print('Total time: {0} seconds'.format(time.time() - start_time))
print('Optimization Finished!')
# 测试模型,定义如何算分
# 1.计算得分,即wx+b,会得到在每个类别上的得分,一个1,9个0
preds = tf.nn.softmax(logits)
# 2.查看最大的类别和标准答案是不是一致的
correct_preds = tf.equal(tf.argmax(preds, 1), tf.argmax(Y, 1))
# 3.求准确率
accuracy = tf.reduce_sum(tf.cast(correct_preds, tf.float32))
# 用minibatch方式在测试集上跑预测
n_batches = int(mnist.test.num_examples/batch_size)
total_correct_preds = 0
for _ in range(n_batches):
X_batch, Y_batch = mnist.test.next_batch(batch_size)
# 执行跑分任务,算总分
accuracy_batch = sess.run([accuracy], feed_dict={X: X_batch, Y:Y_batch})
total_correct_preds += accuracy_batch[0]
print('Accuracy {0}'.format(total_correct_preds/mnist.test.num_examples))
writer.close()

DNN全连接网络实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import time
import numpy as np
import tensorflow as tf
# 准备数据
# 使用tensorflow自带的工具加载MNIST手写数字集合
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('/data/mnist', one_hot=True)
# 准备placeholder
batch_size = None # 每一批训练数据为128个,但是用于测试的数据不止这个数,所以设置为None自动识别
# 这个参数可以定义为None,但是是一种容易出错的方式
X = tf.placeholder(tf.float32, [batch_size, 784], name='X_placeholder')
Y = tf.placeholder(tf.int32, [batch_size, 10], name='Y_placeholder')
# 网络参数
n_hidden_1 = 256 # 第1个隐层的神经元个数
n_hidden_2 = 256 # 第2个隐层的神经元个数
n_input = 784 # MNIST 数据输入(28*28*1=784)
n_classes = 10 # MNIST 总共10个手写数字类别
# 初始化各层的参数,从高斯分布随机采集,需要注意各层的维度数,b的维度和对应层的节点数一致
weights = {
'h1': tf.Variable(tf.random_normal([n_input, n_hidden_1]), name='W1'),
'h2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2]), name='W2'),
'out': tf.Variable(tf.random_normal([n_hidden_2, n_classes]), name='W')
}
biases = {
'b1': tf.Variable(tf.random_normal([n_hidden_1]), name='b1'),
'b2': tf.Variable(tf.random_normal([n_hidden_2]), name='b2'),
'out': tf.Variable(tf.random_normal([n_classes]), name='bias')
}
# 构造计算图
def multilayer_perceptron(x, weights, biases):
# 第一个隐层,用输入的X和第一层的w相乘,加上第一层的b,这一层起名叫fc_1,结果过relu激活函数
layer_1 = tf.add(tf.matmul(x, weights['h1']), biases['b1'], name='fc_1')
layer_1 = tf.nn.relu(layer_1, name='relu_1')
# 第2个隐层
layer_2 = tf.add(tf.matmul(layer_1, weights['h2']), biases['b2'], name='fc_2')
layer_2 = tf.nn.relu(layer_2, name='relu_2')
# 输出层
out_layer = tf.add(tf.matmul(layer_2, weights['out']), biases['out'], name='fc_3')
return out_layer
# 定义如何计算得分
pred = multilayer_perceptron(X, weights, biases)
# 定义如何计算和优化损失
learning_rate = 0.001
loss_all = tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=Y, name='cross_entropy_loss')
loss = tf.reduce_mean(loss_all, name='avg_loss')
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)
# 训练总轮数
training_epochs = 30
# 一批数据大小
batch_size = 128
# 信息展示的频度
display_step = 1
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
writer = tf.summary.FileWriter('./lession/graphs/MLP_DNN', sess.graph)
# 训练
for epoch in range(training_epochs):
avg_loss = 0.
total_batch = int(mnist.train.num_examples/batch_size)
for i in range(total_batch):
batch_x, batch_y = mnist.train.next_batch(batch_size)
_, l = sess.run([optimizer, loss], feed_dict={X: batch_x, Y: batch_y})
avg_loss += l / total_batch
# 每一步都展示信息
if epoch % display_step == 0:
print("Epoch:", '%04d' % (epoch+1), "cost=", \
"{:.9f}".format(avg_loss))
print("Optimization Finished!")
# 在测试集上评估
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print("Accuracy:", accuracy.eval({X: mnist.test.images, Y: mnist.test.labels}))
writer.close()