本篇文章只做一个神经网络入门知识的梳理和个人的理解。
关于神经网络的一些基本概念在这里就不做介绍,首先我们进行一些概念和符号上的约定。
假设一个矩阵如下:
X=⎣⎢⎡123232311⎦⎥⎤
点乘: ⋅
X⋅X=⎣⎢⎡1∗12∗23∗32∗23∗32∗23∗31∗11∗1⎦⎥⎤=⎣⎢⎡149494911⎦⎥⎤
叉乘: *
X∗X=⎣⎢⎡1∗1+2∗2+3∗32∗1+3∗2+1∗33∗1+2∗2+1∗31∗2+2∗3+3∗22∗2+3∗3+1∗23∗2+2∗3+1∗21∗3+2∗1+3∗12∗3+3∗1+1∗13∗3+2∗1+1∗1⎦⎥⎤=⎣⎢⎡14111014151481012⎦⎥⎤
已知数据: 矩阵X为训练数据集,矩阵y为训练数据集的标签
最简单的神经网络
基于Numpy实现神经网络:反向传播
最简单的神经网络就是只有输入层和输出层的网络结构(双层神经网络)。
关于激活函数简单介绍:
如果不使用激活函数,那么就与多层感知机(MLP)相当。引入之后,下一层的输入就不再是线性组合,输出就有意义。
如果不使用激活函数,那么输入与输出都是线性变换,无法做到非线性分类。
激活函数
这是公式,值域为[0,1],它的导数自证。
作用:
- 引入非线性因素
- 线性变换
- 激活函数,并不是去激活什么,而是指如何把“激活的神经元的特征”通过函数把特征保留并映射出来(保留特征,去除一些数据中是的冗余。激励就是样本的特征值),这是神经网络能解决非线性问题关键。
sigmoid
的导数,即使用了 deriv=True
。
作用:用它的输出创建它的导数,降低高信度预测的错误
注意:如果神经网络提前达到预期的结果(损失函数小于一定的范围),可以提前终止
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
|
import numpy as np
import random
def sigmoid(x, deriv=False): if (deriv == True): return x * (1 - x) return 1 / (1 + np.exp(-x))
X = np.array([[0,0,1],[0,1,1],[1,0,1],[1,1,1]]) y = np.array([[0,1,1,0]]).T
syn_0 = 2 * np.random.random((3, 1)) - 1
for _ in range(10000): l0 = X l1 = sigmoid(np.dot(l0, syn_0)) l1_error = y - l1 l1_delta = l1_error * sigmoid(l1, deriv=True) syn_0 += np.dot(l0.T, l1_delta)
print(l1)
|
三层神经网络
增加了一层网络,在输入层和输出层中间。这里做一个规定,在输入层和输出层之间的网络结构我们称为隐藏层。
一层隐层网络就是一层特征层次,每一个神经元可以类似看作一个特征属性
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
| import numpy as np import random
def sigmoid(x, deriv=False): if (deriv == True): return x * (1 - x) return 1 / (1 + np.exp(-x)) X = np.array([[0,0,1],[0,1,1],[1,0,1],[1,1,1]]) y = np.array([[0,1,1,0]]).T
syn_0 = 2 * np.random.random((3, 4)) - 1 syn_1 = 2 * np.random.random((4, 1)) - 1
for _ in range(10000): l0 = X l1 = sigmoid(np.dot(l0, syn_0)) l2 = sigmoid(np.dot(l1, syn_1)) l2_error = y - l2 l2_delta = l2_error * sigmoid(l2, deriv=True) l1_error = np.dot(l2_delta, syn_1.T) l1_delta = l1_error * sigmoid(l1, deriv=True) syn_1 += np.dot(l1.T, l2_delta) syn_0 += np.dot(l0.T, l1_delta)
print(l1)
|
改进神经网络v1.0——增加梯度
A Neural Network in 13 lines of Python (Part 2 - Gradient Descent)
关于梯度的定义请自行了解。
增加了梯度可以更快的找到较为理想的结果。
关于梯度上升有下面几种方法(梯度下降同理):
-
原始的梯度上升
目标:找到某个函数的最大值。每次沿函数的梯度方向探寻。一直进行迭代,直到到达某个停止条件(迭代次数限制或某个误差范围)
-
随机梯度上升(SGD)
不同点在于,第一种方法每次是遍历所有的数据集(一百以内的数据集可以接受用上面的方法)。而随机梯度则是只使用一个样本点来更新回归系数。
-
改进版随机梯度上升
用随机的一个样本来更新回归系数。
-
批梯度上升
切分样本集,随机取出切分后的某些样本,进行遍历
损失函数(代价函数)
-
二次代价函数
C= \frac{1}{2n}\sum_\limits{n}[y(x)-a^L(x)]^2
-
交叉熵代价函数
C=\frac{1}{2n}\sum_\limits{x}[y\ln a-(1-y)\ln(1-a)]
-
对数似然函数
C=\frac{1}{n}\sum_\limits{x}\sum_\limits{k}y_k\log a_k
前向传播是函数求偏导,反向传播减少了这个计算量
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
| import numpy as np import random
def sigmoid(x, deriv=False): if (deriv == True): return x * (1 - x) return 1 / (1 + np.exp(-x)) X = np.array([[0,0,1],[0,1,1],[1,0,1],[1,1,1]]) y = np.array([[0,1,1,0]]).T
syn_0 = 2 * np.random.random((3, 4)) - 1 syn_1 = 2 * np.random.random((4, 1)) - 1
alphas = [0.001,0.01,0.1,1,10,100,1000]
for alpha in alphas: for _ in range(10000): l0 = X l1 = sigmoid(np.dot(l0, syn_0)) l2 = sigmoid(np.dot(l1, syn_1)) l2_error = y - l2 l2_delta = l2_error * sigmoid(l2, deriv=True) l1_error = np.dot(l2_delta, syn_1.T) l1_delta = l1_error * sigmoid(l1, deriv=True) syn_1 += alpha * np.dot(l1.T, l2_delta) syn_0 += alpha * np.dot(l0.T, l1_delta)
print("alpha:", alpha, "l1:", l1)
|
改进神经网络v2.0——更改隐单元个数
更改网络的神经元个数,这个可能就是所谓的玄学调参
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
| import numpy as np import random
def sigmoid(x, deriv=False): if (deriv == True): return x * (1 - x) return 1 / (1 + np.exp(-x)) X = np.array([[0,0,1],[0,1,1],[1,0,1],[1,1,1]]) y = np.array([[0,1,1,0]]).T
hidden_dim = np.random.randint(2, 8)
syn_0 = 2 * np.random.random((3, hidden_dim)) - 1 syn_1 = 2 * np.random.random((hidden_dim, 1)) - 1
for _ in range(10000): l0 = X l1 = sigmoid(np.dot(l0, syn_0)) l2 = sigmoid(np.dot(l1, syn_1)) l2_error = y - l2 l2_delta = l2_error * sigmoid(l2, deriv=True) l1_error = np.dot(l2_delta, syn_1.T) l1_delta = l1_error * sigmoid(l1, deriv=True) syn_1 += np.dot(l1.T, l2_delta) syn_0 += np.dot(l0.T, l1_delta)
print(l1)
|
改进神经网络v3.0——dropout
Hinton's Dropout in 3 Lines of Python
增加dropout_percent。目的是为了防止过拟合,一种正则化的手段。在迭代的时候,对某一(几)层神经网络进行drop_out。这里举例用二项分布采样的方法进行。np.random.binomial(n, p, size=None)
。
Dropout是指在模型训练时随机让网络某些隐含层节点的权重不工作,不工作的那些节点可以暂时认为不是网络结构的一部分,但是它的权重得保留下来(只是暂时不更新而已)。
一些理由解释:
- 权值的更新不依赖于一些固定关系隐含节点的共同作用。阻止了某些特征仅仅在其它特定特征下才有效果的情况。即随机选取当前隐含层的部分节点
- 模型平均概念(选取每次计算过程的相对最优解),使用了
dropout
之后每次的网络结构不同,样本不同对应的输出模型也不同。(个人感觉有点像加入随机)
- 生物进化解释,不断适应变化情况,有效阻止过拟合(避免环境改变物种面临死亡)
- 朴素贝叶斯(native bayes)属于dropout的一种特例:各个特征之间相互独立。在训练样本少的情况下,单独对每个特征进行学习(dropout是训练一部分特征)。
这里假设对l1进行drop_out。
定义 len(x)=3, hidden_dim=4, dropout_percent=0.2
代入数值计算,
1 2 3
| np.random.binomial([array([[ 1., 1., 1., 1.], [ 1., 1., 1., 1.], [ 1., 1., 1., 1.]])], 0.8)[0] * (1/0.8)
|
1 2 3 4 5 6 7
|
dropout_percent = 0.2
... l1 *= np.random.binomial([np.ones((len(X), hidden_dim))],1 - dropout_percent)[0] *(1.0/(1 - dropout_percent)) ...
|
P.S:
对二项分布进行采样。这里的n为 [np.ones((len(X), hidden_dim))]
,p为 1-dropout_percent
np.random.binomial(n, p, size=None)
公式如下:
P(N)=Cn0∗p0∗(1−p)n=(1−p)n
下一篇:神经网络笔记Ⅱ
推荐阅读
零基础入门深度学习