机器学习算法原理总结系列---算法基础之(7)神经网络(Neural Network)

终于开始这几年最火的神经网络算法一章了,说实话,神经网络的原理应该是比SVM要简单的,所以当我们没有深度神经网络的时候,或者没有GPU并行计算,SVM的算法是可以优于神经网络的。但是,GPU横行的现在,我们可以随意构建深度学习网络架构,而且速度也很快。神经网络基础,这篇教程从开始到结束每个字都是精华。一篇文章就可以入门,而且搞懂原理,这里面涉及很多公式,不要急着弄懂它,先会使用,知道是干什么的就好,最后有一个计算的例子,自己动手算一遍,神经网络怎么运算的,你自然就懂了。

一、原理详解

  1. 背景:
    1.1 以人脑中的神经网络为启发,历史上出现过很多不同版本
    1.2 最著名的算法是1980年的 backpropagation

  2. 多层向前神经网络(Multilayer Feed-Forward Neural Network)
    2.1 Backpropagation被使用在多层向前神经网络上
    2.2 多层向前神经网络由以下部分组成:
    输入层(input layer), 隐藏层 (hidden layers), 输入层 (output layers)
    这里写图片描述
    2.3 每层由单元(units)组成
    2.4 输入层(input layer)是由训练集的实例特征向量传入
    2.5 经过连接结点的权重(weight)传入下一层,一层的输出是下一层的输入
    2.6 隐藏层的个数可以是任意的,输入层有一层,输出层有一层
    2.7 每个单元(unit)也可以被称作神经结点,根据生物学来源定义
    2.8 以上成为2层的神经网络(输入层不算)
    2.8 一层中加权的求和,然后根据非线性方程转化输出
    2.9 作为多层向前神经网络,理论上,如果有足够多的隐藏层(hidden layers) 和足够大的训练集, 可以模拟出任何方程

  3. 设计神经网络结构
    3.1 使用神经网络训练数据之前,必须确定神经网络的层数,以及每层单元的个数
    3.2 特征向量在被传入输入层时通常被先标准化(normalize)到0和1之间 (为了加速学习过程)

    3.3 离散型变量可以被编码成每一个输入单元对应一个特征值可能赋的值
    比如:特征值A可能取三个值(a0, a1, a2), 可以使用3个输入单元来代表A。
    如果A=a0, 那么代表a0的单元值就取1, 其他取0;
    如果A=a1, 那么代表a1de单元值就取1,其他取0,以此类推

    3.4 神经网络即可以用来做分类(classification)问题,也可以解决回归(regression)问题
    3.4.1 对于分类问题,如果是2类,可以用一个输出单元表示(0和1分别代表2类)
    如果多余2类,每一个类别用一个输出单元表示
    所以输入层的单元数量通常等于类别的数量

    3.4.2 没有明确的规则来设计最好有多少个隐藏层
    根据实验测试和误差,以及准确度来实验并改进

  4. 交叉验证方法(Cross-Validation)
    这里写图片描述
    K-fold cross validation

  5. Backpropagation算法
    5.1 通过迭代性的来处理训练集中的实例
    5.2 对比经过神经网络后输入层预测值(predicted value)与真实值(target value)之间
    5.3 反方向(从输出层=>隐藏层=>输入层)来以最小化误差(error)来更新每个连接的权重(weight)
    5.4 算法详细介绍
    输入:D:数据集,l 学习率(learning rate), 一个多层前向神经网络
    输入:一个训练好的神经网络(a trained neural network)

    5.4.1 初始化权重(weights)和偏向(bias): 随机初始化在-1到1之间,或者-0.5到0.5之间,每个单元有
    一个偏向
    5.4.2 对于每一个训练实例X,执行以下步骤:
    5.4.2.1: 由输入层向前传送
    这里写图片描述
    这里写图片描述
    这里写图片描述
    这里写图片描述
    5.4.2.2 根据误差(error)反向传送
    对于输出层:
    这里写图片描述
    对于隐藏层:
    这里写图片描述
    权重更新:
    这里写图片描述
    偏向更新:
    这里写图片描述

    5.4.3 终止条件
    5.4.3.1 权重的更新低于某个阈值
    5.4.3.2 预测的错误率低于某个阈值
    5.4.3.3 达到预设一定的循环次数

  6. Backpropagation 算法举例
    这里写图片描述
    对于输出层:
    这里写图片描述
    对于隐藏层:
    这里写图片描述
    权重更新:
    这里写图片描述
    偏向更新:
    这里写图片描述
    这里写图片描述

二、代码实现

任务:简单非线性关系数据集测试(XOR):

X: Y
0 0 0
0 1 1
1 0 1
1 1 0

这个模型是最简单的基本模型,没有隐藏层,输入层3个神经元,输出层1个神经元,但是却可以完全反应整个神经网络从前馈到反向传播的过程,内容一点也不少。

 from numpy import exp, array, random, dot


class NeuralNetwork(object):
    def __init__(self):
        # 为随机数生成器生成种子,因此每次程序运行时都会生成相同的数字。
        random.seed(1)

        # 我们建模一个神经元,有3个输入连接和1个输出我们为3×1矩阵赋值随机权值,其值在-1到0之间。
        self.synaptic_weights = 2 * random.random((3, 1)) - 1

    # Sigmoid函数描述一个S形曲线。 我们通过这个函数的输入的加权和来将它们在0和1之间归一化。
    def __sigmoid(self, x):
        return 1 / (1 + exp(-x))

    # Sigmoid函数的导数。
    # 这是Sigmoid曲线的梯度。
    # 这表明我们对现有的weight有多信心。
    def __sigmoid_derivative(self, x):
        return x * (1 - x)

    # 我们通过反复试验来训练神经网络,每次调整突触权重。
    def train(self, training_set_inputs, training_set_outputs, number_of_training_iterations):
        for iteration in range(number_of_training_iterations):
            # 通过我们的神经网络(单个神经元)传递训练集。
            output = self.think(training_set_inputs)

            # 计算错误(期望输出与预测输出之间的差异)。
            error = training_set_outputs - output

            # 通过输入乘以误差并再次通过Sigmoid的梯度
            # 这意味着不太确定权重是否被调整
            # 这意味着输入是零,不会导致权重的变化。
            adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))

            # Adjust the weights.
            self.synaptic_weights += adjustment

    # The neural network thinks.
    def think(self, inputs):
        # 通过我们的神经网络(我们的单个神经元)传递输入。
        return self.__sigmoid(dot(inputs, self.synaptic_weights))


if __name__ == "__main__":
    # 初始化单个神经元神经网络。
    neural_network = NeuralNetwork()

    print("随机开始突触权重:")
    print(neural_network.synaptic_weights)

    # 训练集。 我们有4个例子,每个例子包括3个输入和1个输出值。
    training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])
    training_set_outputs = array([[0, 1, 1, 0]]).T

    # 使用训练集训练神经网络。
    # 做10000次,每次做小调整。
    neural_network.train(training_set_inputs, training_set_outputs, 10000)

    print("训练后的新突触权重:")
    print(neural_network.synaptic_weights)

    # 用新的情况来测试神经网络。
    print("考虑到新情况[1,0,0] - >?:")
    print(neural_network.think(array([1, 0, 0])))

这里写图片描述

但是,神经网络的架构不突出,功能模块化也不好。下面这个创建了一个神经网络类,实现功能是:提供2层基本神经网络架构(输入层不算层数),实现任务依然是任务1,但你明显可以看出,代码架构的设计风格会更加优雅。
neural_network.py

import numpy as np
from random import random, uniform


class Layer:
    def __init__(self):
        self.prev_layer = None
        self.next_layer = None
        self.num_node = 0
        self.weights = []  # 2d list
        self.theta = []
        self.activation_function = []
        self.output = []
        self.delta = []
        self.learning_rate = float()

    def add_random_sigmoid_node(self, is_input_node=False):
        if not is_input_node:
            weight_for_node = []
            for i in range(self.prev_layer.num_node):
                weight_for_node.append(uniform(-0.5, 0.5))
            self.weights.append(weight_for_node)
            self.theta.append(random())
            self.activation_function.append(sigmoid)
        self.num_node = self.num_node + 1

    def calc_outputs(self):
        if self.prev_layer is not None:
            for i in range(len(self.weights)):
                out = self.activation_function[i](np.dot(self.prev_layer.output, self.weights[i]) + self.theta[i])
                self.output.append(out)

    # 目标是与输出长度相同的列表。只需要输出层
    def calc_deltas_and_adjust(self, target=None):
        if self.next_layer is None:
            for i in range(len(self.output)):
                self.delta.append(self.output[i] * (1 - self.output[i]) * (self.output[i] - target[i]))
                # adjust weight and bias
                for j in range(len(self.weights[i])):
                    self.weights[i][j] = self.weights[i][j] - self.learning_rate * self.delta[-1] * \
                                                              self.prev_layer.output[j]
                    self.theta[i] = self.theta[i] - self.learning_rate * self.delta[-1]
        else:
            for i in range(len(self.output)):
                sum_ = 0
                for j in range(len(self.next_layer.delta)):
                    sum_ = sum_ + self.next_layer.delta[j] * self.next_layer.weights[j][i]
                out = self.output[i] * (1 - self.output[i]) * sum_
                self.delta.append(out)
                # adjust weight and bias
                for j in range(len(self.weights[i])):
                    self.weights[i][j] = self.weights[i][j] - self.learning_rate * self.delta[-1] * \
                                                              self.prev_layer.output[j]
                    self.theta[i] = self.theta[i] - self.learning_rate * self.delta[-1]

    def flush_output_and_delta(self):
        self.output.clear()
        self.delta.clear()


class Network:
    def __init__(self):
        self.layers = []

    def add_layer(self):
        new_layer = Layer()
        if len(self.layers) == 0:
            self.layers.append(new_layer)
        else:
            new_layer.prev_layer = self.layers[-1]
            self.layers[-1].next_layer = new_layer
            self.layers.append(new_layer)

    # 输入是一个长度与第一层节点数相同的列表
    # 目标是一个长度与最后一层节点数相同的列表
    def run_one_iteration(self, input_, target, learning_rate):
        self.layers[0].learning_rate = learning_rate
        self.layers[0].output = input_
        for i in range(1, len(self.layers)):
            self.layers[i].learning_rate = learning_rate
            self.layers[i].flush_output_and_delta()
            self.layers[i].calc_outputs()
        for i in range(len(self.layers) - 1, -1, -1):
            if i == len(self.layers) - 1:
                self.layers[i].calc_deltas_and_adjust(target)
            elif i != 0:
                self.layers[i].calc_deltas_and_adjust()

    def calc_output(self, input_):
        self.layers[0].output = input_
        for i in range(1, len(self.layers)):
            self.layers[i].flush_output_and_delta()
            self.layers[i].calc_outputs()
        return self.layers[-1].output


def sigmoid(x):
    return 1 / (1 + np.exp(-x))

模块定义好了,我们创建XOR的神经网络代码:

import neural_network

if __name__ == "__main__":
    network = neural_network.Network()
    network.add_layer()
    network.add_layer()
    network.add_layer()

    network.layers[0].add_random_sigmoid_node(True)
    network.layers[0].add_random_sigmoid_node(True)
    network.layers[1].add_random_sigmoid_node()
    network.layers[1].add_random_sigmoid_node()
    network.layers[1].add_random_sigmoid_node()
    network.layers[1].add_random_sigmoid_node()
    network.layers[2].add_random_sigmoid_node()

    input_ = [[0, 0], [0, 1], [1, 0], [1, 1]]
    target = [[0], [1], [1], [0]]

    for c in range(10000):
        for i in range(len(input_)):
            network.run_one_iteration(input_[i], target[i], learning_rate=0.1)

    print(network.calc_output(input_[0]))
    print(network.calc_output(input_[1]))
    print(network.calc_output(input_[2]))
    print(network.calc_output(input_[3]))

    print("Weights: ", network.layers[1].weights)
    print("Theta: ", network.layers[1].theta)
    print("Output: ", network.layers[1].output)

    print("Weights: ", network.layers[-1].weights)
    print("theta: ", network.layers[-1].theta)
    print("Output: ", network.layers[-1].output)

这里写图片描述

两种代码入门足够了。大家最好每一段都吃透。对于BP的算法的基本理解已经到位了。

猜你喜欢

转载自blog.csdn.net/tong_t/article/details/78931475