深度学习小记00--神经元的工作原理与简单代码实现
本系列博客用于记录学习深度学习的过程。
一、神经元的基本模型
如图所示是一个神经元的基本模型,神经元在训练时的主要步骤分为蓝色线(前向传播)和红色线(反向传播–梯度下降)。约定符号:X11表示第一个样本的第一个特征值,X1表示第一个样本,Y1表示第一个样本的实际值,A1表示第一个样本的预测值,W1表示加在第一个特征值的权重,W=[W1,W2,…,Wn]
(一)前向传播
如上图所示,前向传播的过程分为3个或4个步骤(平均损失值的计算)。
1.假设神经元有N个特征值输入,则通过初始化W和B构造一个线性关系(N+1维线性超平面)。
2.对上面关系进行第二次映射(一般是非线性的)得到预测值A。(激励函数的作用下)
3.通过损失函数评估预测值和实际值之间的距离。(类似于统计学中方差的作用)
4.计算出全部样本通过W和B变换后且通过激励函数后的预测值与实际值的距离并取其平均值作为参考。(在后面的训练中该值正常情况下应越来越小)
(二)反向传播–梯度下降法
在整个神经元的前向传播中,我们可以分析出损失平均值J可以表示如以下形式。J=F(L),J=F(A,Y),J=F(Z,Y),J=F(W,B,Y)。所以我们可以看出损失函数在根本上是跟W和B的取值有关的。在神经网络的训练中,我们的目的是想要让实际值和预测值之间的距离减少,这意味着我们想要让得到的J值下降。
假设损失函数的图像如上为y=x²,即y=F(x),我们可以通过y对x求导,得到y对x的导数,根据导数的定义,导数为与图像相切的直线,当导数为正时,即x=x+导数乘以一个正数,y值增加,x=x+导数乘以一个负数,y值减少。所以我们可以通过损失函数L对其参数W进行更新,即W= W+dL/dWα(α<0)对其参数B进行更新,即B= B+ dL/dBα(α<0),可以使得损失函数值下降,从而到达我们想要的预测值与实际值之间距离减少的目的。而dL/dW,dL/dB可以通过上图神经元模型中红色线得到,即在设计神经元时,先初始化dL/dA,dA/dZ,dZ/dW,dZ/dB(这些是在整个过程中不变的表达式,所以可以轻易地设计出来),再通过样本的输入动态的调整W和B。
PS:一次训练更新一次W和B,所以设计上是将所有样本的dW平均值作为W的更新,所有样本的dB取平均值作为B的更新。
(三)为什么需要进行线性拟合再做非线性映射(激励函数)?
如上图所示,如果直接采用非线性的函数,在某些区间中函数值变化对于参数的变化的敏感度很低,会导致梯度消失或者梯度下降缓慢的现象。
如果只采用线性的函数,就不能很好的拟合出现实中的实际问题(一般实际问题不会是线性的关系),另一方面,深度学习是通过建立多层次的神经网络去获取特征与实际值Y之间深层次的关系,如果每个神经层都是线性的,那么深度高的神经网络与深度低的神经网络本质上可能没有什么区别。
二、python代码实现
基本神经元
class basic_neuron:
def __init__(self, W, X, B, activity_func, dA_dZ, loss_func, Y, alfa, print_accuracy=True):
"""
设count(X) = n, Xn = m,即特征值个数为m,样本数为n
:param W: shape == 1*Xn
:param X: shape = Xn*n
:param B: shape = 1*Xn
:param activity_func: 激活函数 返回激活函数计算后的值A
:param dA_dZ 返回 dA(激活函数)/dZ 的 函数
:param loss_func: 损失函数 返回用于梯度下降的dW,dB,accuracy,total_loss
:param Y: 实际值
:param alfa:学习率
:param print_accuracy:是否打印准确度
"""
self.W = W.astype('float64')
self.X = X.astype('float64')
self.B = B
self.activity_fun = activity_func
self.loss_func = loss_func
self.Y = Y.astype('float64')
self.alfa = -alfa
self.print_accuracy = print_accuracy
self.dA_dZ = dA_dZ
def change_param(self, dW, dB):
self.W += self.alfa * dW
self.B += self.alfa * dB
def calculate_yp(self):
return self.activity_fun(np.dot(self.W, self.X) + self.B)
def run(self):
YP = self.calculate_yp()
dA, accuracy, total_loss = self.loss_func(YP, self.Y)
Z = np.dot(self.W, X) + self.B
dZ = np.multiply(dA, self.dA_dZ(Z))
dW = np.dot(dZ, self.X.T) / self.X.shape[1]
dB = dZ.sum(axis=1, keepdims=True) / self.X.shape[1]
# print('/*------------------------------*/')
# print('YP:',YP)
# print('dW:',dW,'dB:',dB)
# print('Loss:',total_loss)
# print('/*------------------------------*/')
self.change_param(dW, dB)
if self.print_accuracy:
print('accuracy:', accuracy)
print('Loss:', total_loss)
def predict(self, X):
return self.activity_fun(np.dot(self.W, X) + self.B)
基本损失函数和激活函数
def activity_func(Z):
# sigmoid函数
yp = 1 / (1 + np.exp(-1 * Z))
return yp
def dA_dZ(Z):
# 返回sigmoid函数的导数
return np.multiply((1 / (1 + np.exp(-1 * Z))), (1 - (1 / (1 + np.exp(-1 * Z)))))
def loss_func(YP, Y):
size = YP.shape[1] # 得到YP的数量
Y_arr = Y.tolist()
YP_arr = YP.tolist()
# 避免后续求dA出现除0现象
temp = np.where(YP == 1)
for index, item in enumerate(temp[0]):
YP[item, temp[1][index]] = 0.999999999999
dA = -(np.true_divide(Y, YP) + np.true_divide((Y - 1), (1 - YP)))
loss_part1 = np.dot(Y, np.log(YP).T)
loss_part2 = np.dot(1 - Y, np.log(1 - YP).T)
loss = -(loss_part1 + loss_part2)
count = 0
for index, item in enumerate(Y_arr[0]):
if abs(item - YP_arr[0][index]) < 0.5:
count += 1
accuracy = count / size
return dA, accuracy, float(loss) / size
运行代码
if __name__ == '__main__':
# 生成训练集,X1<100,X2>100为1类,X1>100,X2<100为1类
X_list = []
for i in range(200):
if i < 100:
temp1 = random.random() * 100
temp2 = 100 + random.random() * 100
else:
temp1 = 100 + random.random() * 100
temp2 = random.random() * 100
X_list.append([temp1, temp2])
Y_list = []
for i in range(200):
Y_list.append(1 if i < 100 else 0)
# 初始化参数
W = np.array([[0, 0]]).astype('float64').reshape(1, 2)
B = 0
X = np.array(X_list).T
Y = np.array(Y_list).reshape(1, 200)
alfa = 0.01
# 生成神经元
neuron = basic_neuron(W, X, B, activity_func, dA_dZ, loss_func, Y, alfa)
# 迭代训练
for i in range(3):
neuron.run()
# 进行检测
pre = neuron.predict(np.array([[120], [30]]).reshape([2, 1]))
print(pre)