线性回归网络
在神经网络基础0:线性逻辑回归理论实现章节,我们通过对y = wx+b的预测,实现了一个最简单的线性回归模型;线性回归模型也是最简单的神经网络模型,只有一个输入参数,一个神经元节点和一个输出参数
神经元(Neurons),它是神经网络的基本单元。神经元先获得输入,然后执行某些数学运算后,再产生一个输出。
在这个神经元中,输入总共经历了2步数学运算
先将一个输入乘以权重(weight):
x→x × w,
再加上一个偏置(bias):
x × w+b
得到最后的结果:y = x × w+b
这算是最简单的神经网络了,只有一个神经元,并且没有激活函数对其进行处理(因为输出y是连续的情况下,输出层的激活函数可以使用线性函数y = x)
接下来,我们将根据一组真实的的场景数据,搭建一个具备两个神经元的网络
神经网络实现方法
我们有以上身高,体重,和性别数据,接下来将搭建一个神经网络,能够根据输入的体重和身高,预测该群众属于什么性别
基本模块搭建-神经元
神经元(Neurons),它是神经网络的基本单元。神经元先获得输入,然后执行某些数学运算后,再产生一个输出。以下是一个2输入神经元的例子:
在这个神经元中,输入总共经历了3步数学运算,
先将两个输入乘以权重(weight):
x1→x1 × w1
x2→x2 × w2
把两个结果想加,再加上一个偏置(bias):
(x1 × w1)+(x2 × w2)+ b
上式的线性输出区间为整个实数范围,而我们预测要求输出范围在[0,1]之间,所以还需要使用激活函数对上式的线性函数输出进行处理(之所以使用激活函数,是因为身高和体重的的数据范围是不一样的,如果直接使用原始的数据输出,那么他们对性别的影响程度的影响程度将是不一样的;上文中,我们直接将身高和体重直接减去一个固定值,也是相当于使用了一个激活函数,只不过这个激活函数是线性的(y = x - b),实际使用中,需要使用非线性函数对每个神经元的输出进行修正, 关于激活函数我们为在单独的章节中进行讨论)
y = f(x1 × w1 + x2 × w2 + b)
一种常用的激活函数是sigmoid函数:
sigmoid函数的输出介于0和1,我们可以理解为它把 (−∞,+∞) 范围内的数压缩到 (0, 1)以内。正值越大输出越接近1,负向数值越大输出越接近0
为了计算方便,我们将输入值统一减去一个常量:(因为当输出值很大的时候,激活函数的斜率(梯度)很小。因此,在这个区域内,梯度下降算法会运行得比较慢。在实际应用中,应尽量避免是的输出落在这个区域,使输出尽可能限定在零值附近,从而提高梯度下降算法运算速度)
举个例子,上面神经元里的权重和偏置取如下数值:
w=[0,1]
b = 4
w=[0,1]是w1=0、w2=1的向量形式写法。给神经元一个输入(张二的身高体重数据)x=[27,17],可以用向量点积的形式把神经元的输出计算出来:
w·x+b =(x1 × w1)+(x2 × w2)+ b = 0×27+1×17+4=21
y=f(w⋅X+b)=f(21)=0.9999999992417439
以上步骤的Python代码是:
imort numpy as np
def sigmoid(x):
# Our activation function: f(x) = 1 / (1 + e^(-x))
return 1 / (1 + np.exp(-x))
class Neuron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def feedforward(self, inputs):
# Weight inputs, add bias, then use the activation function
total = np.dot(self.weights, inputs) + self.bias
print("total:",total)
return sigmoid(total)
weights = np.array([0, 1]) # w1 = 0, w2 = 1
bias = 4 # b = 4
n = Neuron(weights, bias)
x = np.array([133, 165]) #
print(n.feedforward(x)) # 1
搭建神经网络
神经网络就是把一堆神经元连接在一起,下面是一个根据我们的数据搭建的简单神经网络的:
这个网络有2个输入(分别代表身高和年龄)、一个包含2个神经元的隐藏层(h1和h2)、包含1个神经元的输出层o1。
隐藏层是夹在输入输入层和输出层之间的部分,一个神经网络可以有多个隐藏层;把神经元的输入向前传递获得输出的过程称为前馈(feedforward)
我们假设上面的网络里所有神经元都具有相同的权重w=[0,1]和偏置b=0,激活函数都是sigmoid,那么我们会得到什么输出呢?
h1=h2=f(w⋅x+b)=f((0×133)+(1×165)+0)
=f(165)
=1.0
o1=f(w⋅[h1,h2]+b)=f((0∗h1)+(1∗h2)+0)
=f(1.0)
=0.7310
# -*- coding: utf-8 -*-
"""
Created on Sun Apr 5 22:35:35 2020
@author: Administrator
"""
import numpy as np
def sigmoid(x):
# Our activation function: f(x) = 1 / (1 + e^(-x))
return 1 / (1 + np.exp(-x))
#--------create Neuron-----------
class Neuron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def feedforward(self, inputs):
# Weight inputs, add bias, then use the activation function
total = np.dot(self.weights, inputs) + self.bias
print("total:",total)
return sigmoid(total)
weights = np.array([0, 1]) # w1 = 0, w2 = 1
bias = 4 # b = 4
n = Neuron(weights, bias)
x = np.array([133, 165]) #
print("Neuron:", n.feedforward(x)) #
#--------create NeuronNetwork-----------
class OurNeuralNetwork:
'''
A neural network with:
- 2 inputs
- a hidden layer with 2 neurons (h1, h2)
- an output layer with 1 neuron (o1)
Each neuron has the same weights and bias:
- w = [0, 1]
- b = 0
'''
def __init__(self):
weights = np.array([0, 1])
bias = 0
# The Neuron class here is from the previous section
self.h1 = Neuron(weights, bias)
self.h2 = Neuron(weights, bias)
self.o1 = Neuron(weights, bias)
def feedforward(self, x):
out_h1 = self.h1.feedforward(x)
out_h2 = self.h2.feedforward(x)
# The inputs for o1 are the outputs from h1 and h2
out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))
return out_o1
network = OurNeuralNetwork()
x = np.array([133, 165])
print(network.feedforward(x)) # 0.7310585786300049
训练神经网络
现在我们已经学会了如何搭建神经网络,现在我们来学习如何训练它。
- 准备数据
我们要训练的数据如下(性别男用1表示,女用0表示):
- 确定要使用的损失函数
在训练神经网络之前,我们需要有一个标准定义它到底好不好,以便我们进行改进,这就是损失(loss)。
- 求各个权重和偏置的偏导数
在神经网络基础: 线性回归理论实现章节我们已经讨论过损失函数的意义,如果要使得损失函数求得最小值,我们需要运用梯度下降法不断更新各个变量的值,直到达到稳定:
而在我们现在的场景中,损失函数是包含以下九个变量的函数,不过都符合以上变量更新公式
因此为了迭代更新L,使得其值不断减小,我们需要求出各个变量的偏导数。
在求各个变量的偏导数之前,我们先求出,在输入值x = [x0,x1] = [weight,height]已知的情况下,各个神经元节点的输出表达公式; 因为这些表达式,在计算偏导数的时候会用到
各个神经元的输出表达式:
上文已经说过,每个神经元的输出,都需要经过激活函数的转换处理,将其中从[-∞,∞]转换到[0,1],已符合我们的值的输出预期
在f(x)的基础上,我们可以求出
以上计算过程中,各个神经元的输入等于各个前一层神经元的加权和,这个过程被称为神经网络的前馈过程
各个神经元的偏导数
已知损失函数的表达式如下:
其中,N表示样本数据的大小,当我们对单个样本数据计算损失函数时,其表达式为:
我们先求得求得L对ypred的导数:
根据链式求导法则:
因此,如果要求得各个变量参数的偏导数,我们从后向前求总各个参数的偏导数:
这种从后之前求各个神经元上输入变量的的偏导数的过程,称为神经网络的后馈过程
- 梯度下降更新每个权重和偏置
通过以上公式,求出所有权重和偏置后,就可以通过梯度下降法,更新每个权重和偏置
总结下训练过程:
1、从数据集中选择一个样本;
2、确定和定义要使用的损失函数
3、计算每个神经元的输出表达式,并计算损失函数对所有权重和偏置的偏导数;
4、使用更新公式更新每个权重和偏置;
5、回到第1步。
实现代码为:
import numpy as np
#激活函数
def sigmoid(x):
# Our activation function: f(x) = 1 / (1 + e^(-x))
return 1 / (1 + np.exp(-x))
#激活函数求导
def deriv_sigmoid(x):
fx = sigmoid(x)
return fx * (1 - fx)
#损失函数
def mse_loss(y_pred, y_true):
return ((y_pred - y_true) ** 2).mean()
#--------create NeuronNetwork-----------
# Define dataset
data = np.array([
[0, 0],
[27, 17],
[19, 12],
[-13, -13],
[27, 10],
[-13, 0],
])
all_y_trues = np.array([
0,
1,
1,
0,
1,
0,
])
class OurNeuralNetwork:
'''
A neural network with:
- 2 inputs
- a hidden layer with 2 neurons (h1, h2)
- an output layer with 1 neuron (o1)
*** DISCLAIMER ***:
The code below is intended to be simple and educational, NOT optimal.
Real neural net code looks nothing like this. DO NOT use this code.
Instead, read/run it to understand how this specific network works.
'''
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()
# Biases
self.b1 = np.random.normal()
self.b2 = np.random.normal()
self.b3 = np.random.normal()
self.epochs = []
self.losses = []
def setFeatures(self, w1,w2,w3,w4,w5,w6,b1,b2,b3):
# Weights
self.w1 = w1
self.w2 = w2
self.w3 = w3
self.w4 = w4
self.w5 = w5
self.w6 = w6
# Biases
self.b1 = b1
self.b2 = b2
self.b3 =b3
def feedforward(self, x):
# x is a numpy array with 2 elements.
h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
o1 = sigmoid(self.w5 * h1 + self.w6 * h2 + self.b3)
return o1
def train(self, data, all_y_trues):
'''
- data is a (n x 2) numpy array, n = # of samples in the dataset.
- all_y_trues is a numpy array with n elements.
Elements in all_y_trues correspond to those in data.
'''
learn_rate = 0.1
epochs = 1000 # number of times to loop through the entire dataset
for epoch in range(epochs):
for x, y_true in zip(data, all_y_trues):
# --- Do a feedforward (we'll need these values later)
sum_h1 = self.w1 * x[0] + self.w2 * x[1] + self.b1
h1 = sigmoid(sum_h1)
sum_h2 = self.w3 * x[0] + self.w4 * x[1] + self.b2
h2 = sigmoid(sum_h2)
sum_o1 = self.w5 * h1 + self.w6 * h2 + self.b3
o1 = sigmoid(sum_o1)
y_pred = o1
# --- Calculate partial derivatives.
# --- Naming: d_L_d_w1 represents "partial L / partial w1"
d_L_d_ypred = 2 * (y_pred - y_true)
# Neuron o1
d_ypred_d_w5 = h1 * deriv_sigmoid(sum_o1)
d_ypred_d_w6 = h2 * deriv_sigmoid(sum_o1)
d_ypred_d_b3 = deriv_sigmoid(sum_o1)
d_ypred_d_h1 = self.w5 * deriv_sigmoid(sum_o1)
d_ypred_d_h2 = self.w6 * deriv_sigmoid(sum_o1)
# Neuron h1
d_h1_d_w1 = x[0] * deriv_sigmoid(sum_h1)
d_h1_d_w2 = x[1] * deriv_sigmoid(sum_h1)
d_h1_d_b1 = deriv_sigmoid(sum_h1)
# Neuron h2
d_h2_d_w3 = x[0] * deriv_sigmoid(sum_h2)
d_h2_d_w4 = x[1] * deriv_sigmoid(sum_h2)
d_h2_d_b2 = deriv_sigmoid(sum_h2)
# --- Update weights and biases
# Neuron h1
self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
# Neuron h2
self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2
# Neuron o1
self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3
# --- Calculate total loss at the end of each epoch
if epoch % 100 == 0:
#操作数据的列方向,x[0] = [133,160,152,...] x[1] = [165,182,177]
y_preds = np.apply_along_axis(self.feedforward, 1, data)
loss = mse_loss(y_preds, all_y_trues)
self.epochs.append(epoch)
self.losses.append(loss)
print("Epoch %d loss: %.3f" % (epoch, loss))
# Train our neural network!
network = OurNeuralNetwork()
network.train(data, all_y_trues)
损失函数收敛曲线:
用训练的模型进行预测:
# Make some predictions
emily = np.array([47, 11]) #
frank = np.array([-23, -5]) #
print("Emily: %.3f" % network.feedforward(emily)) # 0.951 - F
print("Frank: %.3f" % network.feedforward(frank)) # 0.039 - M
Emily: 0.957
Frank: 0.030
待梳理的内容
- 对数据的预处理能够极大的改进你的模型,使得训练的结果更符合预期
- 损失函数和激活函数的选择
github工程