神经网络basics
神经网络
1,神经网络的引入
神经网络的渊源-百度简介
提及神经网络大家第一想到的肯定是生物神经网络了,神经元,轴突,树突,突触…一时间很多名词就会袭击我们的大脑,那么接下来讲的就是人工神经网络,类比得到,所以本博主会在通篇加入生物神经网络的相关内容来帮助大家理解。
先放一个图大家类比一下:
这里我们要实现的就是大脑的功能是这???
NoNoNo!!! 不要被这么多复杂的“成品”所吓到,其实秉着 一生二,二生三,三生万物的原则,我们只需要实现简单的有一个隐藏层的人工神经网络就算打下了基础,像实现了这个:
下面就让我们进入神经网络的学习吧!
2,神经网络的思想
通过大量的训练集来对人工神经网络构建的相关函数进行多次的模拟训练(这个过程要知道的是这个训练集的输入与输出均已知,且要求最后的结果是输入与输出极相近,也就是近乎相等),这样就形成了记忆。就好比教child认识人类的忠诚玩伴——狗,我们把上百张狗的图片摆在孩纸的面前,并一张张的告诉她,这张是狗,这张也是狗……如此,孩纸见到狗的时候就会骄傲的脱口而出“哥哥,你看那条可爱的小狗。”这个例子只是人工神经网络的一个应用—图像识别。当然人工神经网络还会有很多的应用。
一,神经网络的类比细节
人工神经元模型已经把自然神经元的复杂性进行了高度抽象的符号性概括。为了更直观的表达细节,我们放一张表格进行对比:(表格里的内容纯属个人理解,如有不妥,欢迎指正)
生物神经网路 | 人工神经网络 |
---|---|
树突神经刺激 | 多个输入 |
信号的强度控制 | 权值的大小 |
完整突触 | 充当隐藏层的节点 |
轴突 | 一个或多个输出 |
反向传播 | 反馈调节 |
这样是不是感觉自己高中的时候就已经有了学习人工神经网络的基础呢?我们接下来得到问题就是如何让毫无温情可言的电脑具有人情味。
二,神经网络的几个概念
1,公式套路
两层神经网络计算公式
h
=
w
1
∗
x
+
b
1
h=w_1*x+b_1
h=w1∗x+b1
y
=
h
∗
w
2
+
b
2
y=h*w_2+b_2
y=h∗w2+b2
2,权值(W)
权值是反应输出对运行结果的影响程度大小的控制,(上面说过就是指刺激信号的强弱),所以权值的设定决定了主要影响因素和次要因素。
注意点:
(1)权值越大表示输入的信号对神经元影响越大。
(2)权值可以为负值,意味着输入信号受到了抑制。
(3)权值不同那么神经元的计算也不同。
(4)通过调整权值可以得到固定输入下需要的输出值。
(5)调整权重的过程称为“学习”或者“训练”。
权重指某一因素或指标相对于某一事物的重要程度,其不同于一般的比重,体现的不仅仅是某一因素或指标所占的百分比,强调的是因素或指标的相对重要程度
(拿来主义,上面的注意点取自人工智能“涛神”的总结)
3,偏置(b)和激活函数
为什么要加上偏置b?不加偏置会显得函数的构造过于单一线性化,不具有普遍的适用性。
为了更直观的让大家理解偏置的作用,下面上实例:
分类任务:我们需要得到圆圈和三角的分解函数:
此时我们发现如果用y=w*x来近似,无论如何都不能很好的去划分,此时就体现出了偏置的地位。
激活函数:激活函数的作用是将无限制的输入转换为可预测形式的输出。 是为了让结果有更好的对应性,就好比于对人群进行分类,要分出青年和非青年,巧妙引入阶跃函数(阶跃函数:当输入小于等于0时,输出0;当输入大于0时,输出1。),把中14-35岁的映射为1,那非1的就表示非青年了;如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,组合单一。
常用的激活函数:
分别是阶跃函数、Sigmoid和ReLU。名字起的再复杂,也改不了他们简单的本质(这里应该自讽一波)
阶跃函数:当输入小于等于0时,输出0;当输入大于0时,输出1。
f
(
x
)
=
{
0
(
x
<
0
)
1
(
x
>
0
)
f(x)=\left\{ \begin{aligned} 0(x<0)\\ \\ 1(x>0)\\ \end{aligned} \right.
f(x)=⎩⎪⎨⎪⎧0(x<0)1(x>0)
Sigmoid:当输入趋近于正无穷/负无穷时,输出无限接近于1/0。
f
(
x
)
=
1
1
+
e
−
x
f(x)=\frac{1}{1+e^{-x}}
f(x)=1+e−x1
ReLU:当输入小于0时,输出0;当输入大于0时,输出等于输入。
f
(
x
)
=
m
a
x
(
0
,
x
)
f(x)=max(0,x)
f(x)=max(0,x)
其中,阶跃函数输出值是跳变的,且只有二值,较少使用;Sigmoid函数在当x的绝对值较大时,曲线的斜率变化很小(梯度消失),并且计算较复杂;ReLU是当前较为常用的激活函数
激活函数的映射根据具体的情况选用合适的模型。
4,正向传播
与反向传播相比,那么正向传播就显得极为容易理解了。正向传播走的是输出层—隐藏层—输出层,计算过程说白了就是 y = w ∗ x + b y=w*x+b y=w∗x+b的计算。用每一层计算的结果来进行下一层的计算。里面涉及的隐藏层的构造有点类似于矩阵分解里的虚拟矩阵的构造,不同之处在于,这个矩阵的构造有特定的维度要求限制。
5,反向传播
反向传播是神经网络的灵魂,当然也是比较难理解的一个点。
反向传播就像是生物神经网络里的反馈调节一样,生物中反馈调节可以通过减少或增加相关激素的分泌,来维持生物体的正常生理机能。类比于人工神经网络,反向传播起的就是这样一个作用,反向传播后会对参数进行优化,从而更好的去模拟结果。进行反向传播的前提是输出与实际误差太大。
反向传播时,将输出误差(期望输出与实际输出之差)按原通路反传计算,通过隐层反向,直至输入层,在反传过程中将误差分摊给各层的各个单元,获得各层各单元的误差信号,并将其作为修正各单元权值的根据。这一计算过程使用梯度下降法完成,在不停地调整各层神经元的权值和阈(yù)值后,使误差信号减小到最低限度。
阈值:阈值的设定是为了判断最终所接受的误差范围。
三,神经网络的运行过程
自然语言描述:
1,用较小的随机数初始化各个权值和阈值,但是不能使得初始化得权值全部相等,否则网络不可能从这样的结构运行到一种非等权值结构
2,读取输入层的数据和权值
3,进行相关计算
(1)正向计算各层的输出
(2)计算期望输出与实际输出的误差
(3)反向传播优化权值和阈值
4,若满足精度要求或其他退出条件,则结束训练,否则转步骤3继续。
5,结果分析与输出
流程图表示:
四,实际应用
1,分类任务
象限的分类问题,此处的任务是为了实现象限的分类,现在我们给出训练集{ ( 2 , 1 ) , ( − 1 , 1 ) , ( − 1 , − 1 ) , ( 1 , − 1 ) {(2,1),(-1,1),(-1,-1),(1,-1)} (2,1),(−1,1),(−1,−1),(1,−1)}训练集再少也要包含所有涉及的分类情况。
(1)手工推导实现
(2)代码实现
代码中用到的numpy函数一览表
函数格式 | 用途 |
---|---|
np.array() | 定义数组 |
x.shape[0] | 得到矩阵的行数 |
x.shape[1] | 得到矩阵的列数 |
x.reshape(N,-1) | 固定行数,-1表示行数需要计算 |
np.dot(m,n) | m和n两个矩阵的叉乘 |
np.sum() | 求和函数 |
axis=0 | 返回按列相加的一个数组 |
keepdims | 保持原有维度输出 |
np.random.randn(m,n) | 随机生成一个维度为m*n的矩阵(元素大小【0,1)之间) |
np.zeros(m,n) | 生成一个m*n的元素全为0的矩阵 |
np.maximum(0,H) | 只返回0和H中大的元素 |
np.exp(x) | 计算e的x次幂 |
np.arange(N) | 生成[0,1,…N-1]的数组 |
np.copy() | 函如其名,就是copy |
np.log(x) | 计算以e为底的x的log值 |
softmax层函数
类似于归一化处理,目的是为了使输出的结果更为直观。那么我们能不能使得一系类预测值y变为相应的概率,概率大的就为输出的最终结果呢?当然可以。
softmax:
S
i
=
e
i
∑
j
e
j
S_i=\frac{e^{i}}{\sum_je^{j}}
Si=∑jejei
简单来说分三步进行:
(1)以e为底对所有元素求指数幂;
(2)将所有指数幂求和;
(3)分别将这些指数幂与该和做商。
这样求出的结果中,一系列的和一定为1,而每个元素可以代表概率值。
代码
import numpy as np
def affine_forward(x, w, b):
out = None # 初始化返回值为None
N =x.shape[0] # 重置输入参数X的形状
x_row = x.reshape(N, -1) # (N,D)
out = np.dot(x_row, w) + b # (N,M)
cache = (x, w, b) # 缓存值,反向传播时使用
return out,cache
def affine_backward(dout, cache):
x, w, b = cache # 读取缓存
dx, dw, db = None, None, None # 返回值初始化
dx = np.dot(dout, w.T) # (N,D)
dx = np.reshape(dx, x.shape) # (N,d1,...,d_k)
x_row = x.reshape(x.shape[0], -1) # (N,D)
dw = np.dot(x_row.T, dout) # (D,M)
db = np.sum(dout, axis=0, keepdims=True) # (1,M)
return dx, dw, db
X = np.array([[2,1],
[-1,1],
[-1,-1],
[1,-1]]) # 用于训练的坐标,对应的是I、II、III、IV象限
t = np.array([0,1,2,3]) # 标签,对应的是I、II、III、IV象限
np.random.seed(1) # 有这行语句,你们生成的随机数就和我一样了
# 一些初始化参数
input_dim = X.shape[1] # 输入参数的维度,此处为2,即每个坐标用两个数表示
num_classes = t.shape[0] # 输出参数的维度,此处为4,即最终分为四个象限
hidden_dim = 50 # 隐藏层维度,为可调参数
reg = 0.001 # 正则化强度,为可调参数
epsilon = 0.001 # 梯度下降的学习率,为可调参数
# 初始化W1,W2,b1,b2
W1 = np.random.randn(input_dim, hidden_dim) # (2,50)
W2 = np.random.randn(hidden_dim, num_classes) # (50,4)
b1 = np.zeros((1, hidden_dim)) # (1,50)
b2 = np.zeros((1, num_classes)) # (1,4)
for j in range(10000): #这里设置了训练的循环次数为10000
# ①前向传播
H,fc_cache = affine_forward(X,W1,b1) # 第一层前向传播
H = np.maximum(0, H) # 激活
relu_cache = H # 缓存第一层激活后的结果
Y,cachey = affine_forward(H,W2,b2) # 第二层前向传播
# ②Softmax层计算
probs = np.exp(Y - np.max(Y, axis=1, keepdims=True))
probs /= np.sum(probs, axis=1, keepdims=True) # Softmax算法实现
# ③计算loss值
N = Y.shape[0] # 值为4
print(probs[np.arange(N), t]) # 打印各个数据的正确解标签对应的神经网络的输出
loss = -np.sum(np.log(probs[np.arange(N), t])) / N # 计算loss
print(loss) # 打印loss
# ④反向传播
dx = probs.copy() # 以Softmax输出结果作为反向输出的起点
dx[np.arange(N), t] -= 1
dx /= N # 到这里是反向传播到softmax前
dh1, dW2, db2 = affine_backward(dx, cachey) # 反向传播至第二层前
dh1[relu_cache <= 0] = 0 # 反向传播至激活层前
dX, dW1, db1 = affine_backward(dh1, fc_cache) # 反向传播至第一层前
# ⑤参数更新
dW2 += reg * W2
dW1 += reg * W1
W2 += -epsilon * dW2
b2 += -epsilon * db2
W1 += -epsilon * dW1
b1 += -epsilon * db1
test = np.array([[2,2],[-2,2],[-2,-2],[2,-2]])
H,fc_cache = affine_forward(test,W1,b1) #仿射
H = np.maximum(0, H) #激活
relu_cache = H
Y,cachey = affine_forward(H,W2,b2) #仿射
# Softmax
probs = np.exp(Y - np.max(Y, axis=1, keepdims=True))
probs /= np.sum(probs, axis=1, keepdims=True) # Softmax
print(probs)
for k in range(4):
print(test[k,:],"所在的象限为",np.argmax(probs[k,:])+1)
注意点: 反向传播代码段返回的是求偏导之后的结果,由于
y
=
w
∗
x
+
b
y=w*x+b
y=w∗x+b,其对x求偏导就是w,所以dx=w*y 右角的T表示矩阵的装置。
运行结果展示: