1.基本模块搭建:
说明:这个基本的神经元模块中经历了三步运算:
先将两个输入乘以权重(weight):
x1 --> x1 * w1
x2 --> x2* w2
两个结果相加之后,再加上一个偏置(bias):
(x1 * w1 + x2 * w2) + b
最后经过激活函数(activation function):
y = f(x1 * w1 + x2 * w2 + b)
激活函数:将无限制的输入转换为可测试的输出。常用的有sigmoid函数:
上述过程以python程序表达为:
#模块导入
import numpy as np
#激活函数选取:此处选择sigmoid函数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
#神经元类定义:
class Neuron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def feedforward(self, inputs):
#权重输入,加入权重
total = np.dot(self.weights, inputs) + self.bias
return sigmoid(total)
weights = np.array([0, 1]) #w1, w2 = 0, 1
bias = 4 #b = 4
n = Neuron(weights, bias)#类实例化
x = np.array([2, 3])#输入变量x1, x2
print(n.feedforward(x))
神经网络搭建:
神经元输入向前传递到输出的过程称为前馈(feedforward)
假设上述神经网络中所有神经元的权重w = [0, 1]和偏置b = 0,激活函数为sigmoid函数。
那么最终输出的过程为:
第二层(隐藏层)的神经元:
h1= h2 = f(w * x + b) = f([0, 1]*[2, 3].T + 0) = f(3) = sigmoid(3) = 0.9526
第三层(输出层)的神经元:
o1 = f(w * h + b) = f([0, 1] * [h1, h2].T + 0) = f(0.9526) = sigmoid(0.9526) = 0.7216
以上python程序表达为:
#模块导入
import numpy as np
'''
A neural network with:
-2 inputs
-a hidden layer with 2 neurons(h1, h2)
-a output layer with 1 neurons(o1)
Each neuron has the same weights and bias:
-w = [0,1]
-b = 0
'''
def sigmoid(z):
return 1 / (1 + np.exp(-z))
class MyNeuralNetwork:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
self.h1 = 0
self.h2 = 0
self.o1 = 0
def feedforward(self, inputs):
temp_hidden = np.dot(self.weights, inputs)
self.h1 = sigmoid(temp_hidden + self.bias)
self.h2 = self.h1
temp_out = np.dot(self.weights, np.array([self.h1, self.h2]))
self.o1 = sigmoid(temp_out + self.bias)
return self.o1
x = np.array([2, 3])
weights = np.array([0, 1])
bias = 0
n = MyNeuralNetwork(weights, bias)
print(n.feedforward(x))
训练神经网络
例如:对于一个数据集,包含4个人的身高和体重和性别。
目前的目标在于训练一个神经网络,实现根据体重和身高来预测一个人的性别。
(1)数据处理:
对身高,体重固定的减去一个固定数值,把性别男定义为1,性别女定义为0。
此时训练集变化为:
(2)训练数据效果评价:
评价训练的神经网络的好坏,使用**损失(loss)来定义。
比如用均方误差(MSE)**定义损失:
M
S
E
=
1
n
∗
∑
i
=
1
4
(
y
t
r
u
e
−
y
p
r
e
d
)
2
MSE = \frac{1}{n}*\sum_{i =1}^{4}(y_{true} - y_{pred})^{2}
MSE=n1∗i=1∑4(ytrue−ypred)2
其中,n代表样本实例的数量,这个数据集中是4;y代表性别,ytrue代表真实的y值,ypred代表变量的预测值。
训练神经网络就是将损失最小化。
(3)均方误差例化:
如果预测输出一直为0,即y_pred = [0, 0, 0, 0]
那么最终的损失为:
def Mse_Loss(y_true, y_pred):
return ((y_true - y_pred) ** 2).mean()
y_true = np.array([1, 0, 0, 1])
y_pred = np.array([0, 0, 0, 0])
Mse_Loss(y_true, y_pred)
最终结果为:0.5。
(4)减少损失:
初始想法:改变权重和偏置可以影响预测值。
具体做法:由单个样本实例作为整个数据集,选择Alice一个人的数据,则损失函数为:
M
S
E
=
1
1
∗
∑
i
=
1
1
(
1
−
y
p
r
e
d
)
2
=
(
1
−
y
p
r
e
d
)
2
MSE = \frac{1}{1}*\sum_{i =1}^{1}(1 - y_{pred})^{2} = (1 - y_{pred})^{2}
MSE=11∗i=1∑1(1−ypred)2=(1−ypred)2
而预测值由一系列的网络权重和偏置计算出来的:
由上图可知,损失函数与多个变量(w1, w2, w3, w4, w5, w6, b1, b2, b3)有关。
即为:
L(w1, ,w2, w3, w4, w5, w6, b1, b2, b3)
猜想:如果调整一下w1,看loss的大小变化?
实操:
(
∂
L
∂
w
1
)
=
(
∂
L
∂
y
p
r
e
d
)
∗
(
∂
y
p
r
e
d
∂
w
1
)
−
−
(
1
)
(\frac{\partial L}{\partial w_{1}}) = (\frac{\partial L}{\partial y_{pred}})*(\frac{\partial y_{pred}}{\partial w_{1}})--(1)
(∂w1∂L)=(∂ypred∂L)∗(∂w1∂ypred)−−(1)
L
=
(
(
1
−
y
p
r
e
d
)
2
)
−
−
(
2
)
L = ((1 - y_{pred})^{2})--(2)
L=((1−ypred)2)−−(2)
则:
∂
L
∂
y
p
r
e
d
=
−
2
∗
(
1
−
y
p
r
e
d
)
−
−
(
3
)
\frac{\partial L}{\partial y_{pred}} = -2 * (1 - y_{pred}) --(3)
∂ypred∂L=−2∗(1−ypred)−−(3)
此时我们获取y_pred和w1的关系,则由于:
y
p
r
e
d
=
o
1
=
f
(
w
5
∗
h
1
+
w
6
∗
h
2
+
b
3
)
−
−
(
4
)
y_{pred} = o1 = f(w5 * h1 + w6 * h2 + b3) --(4)
ypred=o1=f(w5∗h1+w6∗h2+b3)−−(4)
但是只有神经元h1和w1有关系,那么y_pred对w1的导数为:
∂
y
p
r
e
d
∂
h
1
=
w
5
∗
f
′
(
w
5
h
1
+
w
6
h
2
+
b
3
)
−
−
(
5
)
\frac{\partial y_{pred}}{\partial h1} = w5 * f^{'}(w5h1 + w6h2 +b3)-- (5)
∂h1∂ypred=w5∗f′(w5h1+w6h2+b3)−−(5)
h
1
=
f
(
w
1
x
1
+
w
2
x
2
+
b
1
)
−
−
(
6
)
h1 = f(w1x1 + w2x2 + b1)--(6)
h1=f(w1x1+w2x2+b1)−−(6)
∂
h
1
∂
w
1
=
x
1
∗
f
′
(
w
1
x
1
+
w
2
x
2
+
b
1
)
−
−
(
7
)
\frac{\partial h1}{\partial w1} = x1 * f^{'}(w1x1 + w2x2 +b1) --(7)
∂w1∂h1=x1∗f′(w1x1+w2x2+b1)−−(7)
激活函数选为sigmoid函数,那么导数求解为:
s
i
g
m
o
i
d
(
x
)
=
f
(
x
)
=
1
1
+
e
−
x
−
−
(
8
)
sigmoid(x) = f(x) = \frac{1}{1 + e^{-x}}--(8)
sigmoid(x)=f(x)=1+e−x1−−(8)
f
′
(
x
)
=
e
x
(
1
+
e
−
x
)
2
=
f
(
x
)
∗
(
1
−
f
(
x
)
)
−
−
(
9
)
f^{'}(x) = \frac{e^{x}}{(1 + e^{-x})^{2}} = f(x) * (1-f(x))--(9)
f′(x)=(1+e−x)2ex=f(x)∗(1−f(x))−−(9)
标注:这种向后计算倒数的系统称为反向传播(backpropagation)
此时,我们以实际的数值带入进行计算:
由Alice的weight和height数据进行标准化后的数据可知:对于上述模型,实际的参数取值为:
x1 = -2, x2 = -1
w1 = w2 = w3 = w4 = w5 = w6 = 1
b1 = b2 = b3 = 0
所以:
h1 = f(x1w1 + x2w2 + b1) = 0.0474
h2 = f(x1w3 + x2w4 + b2) = 0.0474
o1 = f(w5h1 + w6h2 + b3) = f(0.0948) = 0.524
此时神经网络输出为y = 0.524,并没有明显的显示出男(0)和女(1)的证据匹配。
而此时∂L/∂w1为:
∂
L
∂
w
1
=
x
1
∗
f
′
(
w
1
x
1
+
w
2
x
2
+
b
1
)
∗
w
5
∗
f
′
(
w
5
h
1
+
w
6
h
2
+
b
3
)
∗
(
−
2
)
∗
(
1
−
y
p
r
e
d
)
−
−
(
10
)
\frac{\partial L}{\partial w1} = x1 * f^{'}(w1x1 + w2x2 + b1) * w5 * f^{'}(w5h1 + w6h2 + b3) * (-2) * (1 - y_{pred})--(10)
∂w1∂L=x1∗f′(w1x1+w2x2+b1)∗w5∗f′(w5h1+w6h2+b3)∗(−2)∗(1−ypred)−−(10)
最终结果为:∂L/∂w1 = 0.0214
结论:增大w1,损失函数会有一个非常小的增长
随机梯度下降:
使用**随机梯度下降(SGD)**的优化算法训练神经网络。
SGD算法说明:定义了权重和偏置的自动调整的方法:
w
1
←
w
1
−
η
∂
L
∂
w
1
−
−
(
11
)
w1 \leftarrow w1 - \eta \frac{\partial L}{\partial w1} --(11)
w1←w1−η∂w1∂L−−(11)
参数说明:η是一个常数,被称作学习率(learning rate),决定了训练网络速率的快慢。上式是权重更新的方法。
训练流程如下:
1.数据集中挑选一个样本;
2.计算损失函数对所有权重和偏置的偏导数
3.更新每一个权重和偏置
4.回到第一步循环进行每一个训练实例的训练
python实现为:
模块导入及相关辅助函数
#模块导入
import numpy as np
import matplotlib.pyplot as plt
#sigmoid激活函数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
#激活函数导数求解方式
def deriv_sigmoid(x):
fx = sigmoid(x)
return fx * (1 - fx)
#均方误差函数-损失
def mse_loss(y_true, y_pred):
return ((y_true - y_pred) ** 2).mean()
训练神经网络(更新权重和偏置):
class OurNeuralNetwork:
'''
A neural network with:
- 2 inputs
- a hidden layer with neurons (h1, h2)
- an output layer with 1 neuron (o1)
'''
def __init__(self):
#weights
self.w1 = np.random.normal()
self.w2 = np.random.normal()
self.w3 = np.random.normal()
self.w4 = np.random.normal()
self.w5 = np.random.normal()
self.w6 = np.random.normal()
#bias
self.b1 = np.random.normal()
self.b2 = np.random.normal()
self.b3 = np.random.normal()
def feedforward(self, inputs):
#inputs为一个数组包含两个值
h1_par = np.array([self.w1, self.w2])
h2_par = np.array([self.w3, self.w4])
o1_par = np.array([self.w5, self.w6])
h1 = sigmoid(np.dot(h1_par, inputs) + self.b1)
h2 = sigmoid(np.dot(h2_par, inputs) + self.b2)
o1_inputs = np.array([h1, h2])
o1 = sigmoid(np.dot(o1_par, o1_inputs) + self.b3)
return o1
def train_example(self, data, all_y_trues):
'''
- data 是一个(n * 2)的数组,n代表训练集的实例个数
- all_y_trues是一个含有n个元素的数组
'''
#设定学习率
learning_rate = 0.1
loops = 1000#循环的次数
loss_ypred = []
loop_ypred = []
for loop in range(loops):
for x, y_true in zip(data, all_y_trues):
#参数向量化
h1_par = np.array([self.w1, self.w2])
h2_par = np.array([self.w3, self.w4])
o1_par = np.array([self.w5, self.w6])
h1 = sigmoid(np.dot(h1_par, x) + self.b1)
h2 = sigmoid(np.dot(h2_par, x) + self.b2)
o1_inputs = np.array([h1, h2])
o1 = sigmoid(np.dot(o1_par, o1_inputs) + self.b3)
#预测值就是o1
y_pred = o1
#计算偏导数
d_L_d_ypred = -2 * (y_true - y_pred)
#神经元o1涉及到的偏导数求解:
d_ypred_d_w5 = h1 * deriv_sigmoid(np.dot(o1_par, o1_inputs))#∂y_pred/∂w5
d_ypred_d_w6 = h2 * deriv_sigmoid(np.dot(o1_par, o1_inputs))#∂y_pred/∂w6
d_ypred_d_b3 = deriv_sigmoid(np.dot(o1_par, o1_inputs))#∂y_pred/∂b3
d_ypred_d_h1 = self.w5 * deriv_sigmoid(np.dot(o1_par, o1_inputs))
d_ypred_d_h2 = self.w6 * deriv_sigmoid(np.dot(o1_par, o1_inputs))
#神经元h1涉及到的偏导数求解:
d_h1_d_w1 = x[0] * deriv_sigmoid(np.dot(h1_par, x))
d_h1_d_w2 = x[1] * deriv_sigmoid(np.dot(h1_par, x))
d_h1_d_b1 = deriv_sigmoid(np.dot(h1_par, x))
#神经元h2涉及到的偏导数求解
d_h2_d_w3 = x[0] * deriv_sigmoid(np.dot(h2_par, x))
d_h2_d_w4 = x[1] * deriv_sigmoid(np.dot(h2_par, x))
d_h2_d_b2 = deriv_sigmoid(np.dot(h2_par, x))
#进行权重和偏置的更新
#神经元h1部分:
self.w1 -= learning_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
self.w2 -= learning_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
self.b1 -= learning_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
#神经元h2部分:
self.w3 -= learning_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
self.w4 -= learning_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
self.b2 -= learning_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2
#神经元o1部分:
self.w5 -= learning_rate * d_L_d_ypred * d_ypred_d_w5
self.w6 -= learning_rate * d_L_d_ypred * d_ypred_d_w6
self.b3 -= learning_rate * d_L_d_ypred * d_ypred_d_b3
#计算损失的值
if loop % 10 == 0:
y_preds = np.apply_along_axis(self.feedforward, 1, data)
loss = mse_loss(all_y_trues, y_preds)
print('loop %d loss : %.3f' % (loop, loss))
loss_ypred.append(loss)
loop_ypred.append(loop)
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['font.serif'] = ['KaiTi']
plt.rcParams['axes.unicode_minus'] = False#显示中文标签
plt.plot(loop_ypred, loss_ypred, c = 'b', label = u'损失函数曲线')
plt.xlabel(u'循环次数')
plt.ylabel(u'损失值')
plt.legend()
plt.show()
#训练数据集
data = np.array([
[-2, -1],#Alice
[25, 6],#Bob
[17, 4],#Charlie
[-15, -6],#Diana
])
all_y_trues = np.array([
1,#Alice
0,#Bob
0,#Charlie
1,#Diana
])
#训练神经网络
network = OurNeuralNetwork()
network.train_example(data, all_y_trues)
结果(部分):
loop 0 loss : 0.353
loop 10 loss : 0.229
loop 20 loss : 0.150
loop 30 loss : 0.105
loop 40 loss : 0.077
loop 50 loss : 0.059
loop 60 loss : 0.048
loop 70 loss : 0.039
loop 80 loss : 0.033
loop 90 loss : 0.029
loop 100 loss : 0.025
图示为:
随机梯度下降算法总结:
1.部分函数用法详解:
#(1)numpy.random.normal()产生一个正态分布下的数值
#用法例子:
numpy.random.normal(loc=0,scale=1e-2,size=shape)
#loc代表正态分布的均值。loc = 0代表这是一个以y轴为对称轴的正态分布
#scale代表正态分布的标准差。
#size代表生成数据的维数,默认是产生一个元素;例如:
a = numpy.random.normal(size = (2, 3))
#产生一个2*3维度的服从正态分布的数组
#(2)numpy.apply_along_axis(func, axis, arr, *args, **kwargs)--将arr数组中每一个元素经过func函数进行处理后生成一个新的数组
#用法例子:
numpy.apply_along_axis(self.feedforward, 1, data)
#如上述随机梯度下降法中的例子,即将data中每一个元素经过self.feedforward函数进行处理后返回一个数组
#func是我们写的一个函数
#axis表示函数func对arr是作用于行还是列
#arr便是我们要进行操作的数组了
#axis=1代表按行进行处理,axis=0代表按列进行处理
def solve(a):
return a[0]
b = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
c = np.apply_along_axis(solve, 1, b)#此时c为array([1, 5, 9])
c = np.apply_along_axis(solve, 0, b)#此时c为array([1, 2, 3, 4])
#zip函数用法:
zip(a, b)#返回值为一个对象,需要list(zip(a, b))才能返回一个列表形式的压缩效果
zip(*zip(a, b))#解压,需要list(zip(a, b))才能返回一个列表形式的解压缩效果
#例子:
a = [[1, 2], [3, 4], [5, 6], [7, 8]]
b = [4, 5, 6, 7]
>list(zip(a, b))
#结果为:[([1, 2], 4), ([3, 4], 5), ([5, 6], 6), ([7, 8], 7)]
>list(zip(*zip(a, b)))
#结果为:[([1, 2], [3, 4], [5, 6], [7, 8]), (4, 5, 6, 7)]
>x1, x2 = list(zip(*zip(a, b)))
>print(list(x1), '\n', list(x2))
#结果为:[[1, 2], [3, 4], [5, 6], [7, 8]]
[2, 3, 4, 5]