神经网络基础1:实现一个简单的神经网络

神经网络基础1:实现一个简单的神经网络_第1张图片

线性回归网络

在神经网络基础0:线性逻辑回归理论实现章节,我们通过对y = wx+b的预测,实现了一个最简单的线性回归模型;线性回归模型也是最简单的神经网络模型,只有一个输入参数,一个神经元节点和一个输出参数

神经网络基础1:实现一个简单的神经网络_第2张图片
线性回归

神经元(Neurons),它是神经网络的基本单元。神经元先获得输入,然后执行某些数学运算后,再产生一个输出。

在这个神经元中,输入总共经历了2步数学运算
先将一个输入乘以权重(weight):
x→x × w,

再加上一个偏置(bias):
x × w+b

得到最后的结果:y = x × w+b

这算是最简单的神经网络了,只有一个神经元,并且没有激活函数对其进行处理(因为输出y是连续的情况下,输出层的激活函数可以使用线性函数y = x)

接下来,我们将根据一组真实的的场景数据,搭建一个具备两个神经元的网络

神经网络实现方法

神经网络基础1:实现一个简单的神经网络_第3张图片

我们有以上身高,体重,和性别数据,接下来将搭建一个神经网络,能够根据输入的体重和身高,预测该群众属于什么性别

基本模块搭建-神经元

神经元(Neurons),它是神经网络的基本单元。神经元先获得输入,然后执行某些数学运算后,再产生一个输出。以下是一个2输入神经元的例子:

神经网络基础1:实现一个简单的神经网络_第4张图片

在这个神经元中,输入总共经历了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函数:

神经网络基础1:实现一个简单的神经网络_第5张图片

sigmoid函数的输出介于0和1,我们可以理解为它把 (−∞,+∞) 范围内的数压缩到 (0, 1)以内。正值越大输出越接近1,负向数值越大输出越接近0

为了计算方便,我们将输入值统一减去一个常量:(因为当输出值很大的时候,激活函数的斜率(梯度)很小。因此,在这个区域内,梯度下降算法会运行得比较慢。在实际应用中,应尽量避免是的输出落在这个区域,使输出尽可能限定在零值附近,从而提高梯度下降算法运算速度

神经网络基础1:实现一个简单的神经网络_第6张图片

举个例子,上面神经元里的权重和偏置取如下数值:

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

搭建神经网络

神经网络就是把一堆神经元连接在一起,下面是一个根据我们的数据搭建的简单神经网络的:

神经网络基础1:实现一个简单的神经网络_第7张图片

这个网络有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. 准备数据

我们要训练的数据如下(性别男用1表示,女用0表示):

神经网络基础1:实现一个简单的神经网络_第8张图片
  1. 确定要使用的损失函数
    在训练神经网络之前,我们需要有一个标准定义它到底好不好,以便我们进行改进,这就是损失(loss)。
  1. 求各个权重和偏置的偏导数

在神经网络基础: 线性回归理论实现章节我们已经讨论过损失函数的意义,如果要使得损失函数求得最小值,我们需要运用梯度下降法不断更新各个变量的值,直到达到稳定:

而在我们现在的场景中,损失函数是包含以下九个变量的函数,不过都符合以上变量更新公式

因此为了迭代更新L,使得其值不断减小,我们需要求出各个变量的偏导数。

在求各个变量的偏导数之前,我们先求出,在输入值x = [x0,x1] = [weight,height]已知的情况下,各个神经元节点的输出表达公式; 因为这些表达式,在计算偏导数的时候会用到

各个神经元的输出表达式:

神经网络基础1:实现一个简单的神经网络_第9张图片

上文已经说过,每个神经元的输出,都需要经过激活函数的转换处理,将其中从[-∞,∞]转换到[0,1],已符合我们的值的输出预期



在f(x)的基础上,我们可以求出

神经网络基础1:实现一个简单的神经网络_第10张图片
前馈过程

以上计算过程中,各个神经元的输入等于各个前一层神经元的加权和,这个过程被称为神经网络的前馈过程

各个神经元的偏导数

已知损失函数的表达式如下:


其中,N表示样本数据的大小,当我们对单个样本数据计算损失函数时,其表达式为:

我们先求得求得L对ypred的导数:


根据链式求导法则:


因此,如果要求得各个变量参数的偏导数,我们从后向前求总各个参数的偏导数:

神经网络基础1:实现一个简单的神经网络_第11张图片
各个神经元的偏导数.gif

这种从后之前求各个神经元上输入变量的的偏导数的过程,称为神经网络的后馈过程

  1. 梯度下降更新每个权重和偏置
    通过以上公式,求出所有权重和偏置后,就可以通过梯度下降法,更新每个权重和偏置
神经网络基础1:实现一个简单的神经网络_第12张图片
梯度下降

总结下训练过程:
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)

损失函数收敛曲线:


神经网络基础1:实现一个简单的神经网络_第13张图片

用训练的模型进行预测:

# 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

待梳理的内容

  1. 对数据的预处理能够极大的改进你的模型,使得训练的结果更符合预期
  2. 损失函数和激活函数的选择

github工程

你可能感兴趣的:(神经网络基础1:实现一个简单的神经网络)