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]