系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI
点击star加星不要吝啬,星越多笔者越努力。

4.3 神经网络法

在梯度下降法中,我们简单讲述了一下神经网络做线性拟合的原理,即:

  1. 初始化权重值
  2. 根据权重值放出一个解
  3. 根据均方差函数求误差
  4. 误差反向传播给线性计算部分以调整权重值
  5. 是否满足终止条件?不满足的话跳回2

一个不恰当的比喻就是穿糖葫芦:桌子上放了一溜儿12个红果,给你一个足够长的竹签子,选定一个角度,在不移动红果的前提下,想办法用竹签子穿起最多的红果。

最开始你可能会任意选一个方向,用竹签子比划一下,数数能穿到几个红果,发现是5个;然后调整一下竹签子在桌面上的水平角度,发现能穿到6个......最终你找到了能穿10个红果的的角度。

4.3.1 定义神经网络结构

我们是首次尝试建立神经网络,先用一个最简单的单层单点神经元,如图4-4所示。

图4-4 单层单点神经元

下面,我们用这个最简单的线性回归的例子,来说明神经网络中最重要的反向传播和梯度下降的概念、过程以及代码实现。

输入层

此神经元在输入层只接受一个输入特征,经过参数w,b的计算后,直接输出结果。这样一个简单的“网络”,只能解决简单的一元线性回归问题,而且由于是线性的,我们不需要定义激活函数,这就大大简化了程序,而且便于大家循序渐进地理解各种知识点。

严格来说输入层在神经网络中并不能称为一个层。

权重w/b

因为是一元线性问题,所以w/b都是一个标量。

输出层

输出层1个神经元,线性预测公式是:

\[z_i = x_i \cdot w + b\]

z是模型的预测输出,y是实际的样本标签值,下标 \(i\) 为样本。

损失函数

因为是线性回归问题,所以损失函数使用均方差函数。

\[loss(w,b) = \frac{1}{2} (z_i-y_i)^2\]

4.3.2 反向传播

由于我们使用了和上一节中的梯度下降法同样的数学原理,所以反向传播的算法也是一样的,细节请查看4.2.2。

计算w的梯度

\[
{\partial{loss} \over \partial{w}} = \frac{\partial{loss}}{\partial{z_i}}\frac{\partial{z_i}}{\partial{w}}=(z_i-y_i)x_i
\]

计算b的梯度

\[
\frac{\partial{loss}}{\partial{b}} = \frac{\partial{loss}}{\partial{z_i}}\frac{\partial{z_i}}{\partial{b}}=z_i-y_i
\]

为了简化问题,在本小节中,反向传播使用单样本方式,在下一小节中,我们将介绍多样本方式。

4.3.3 代码实现

其实神经网络法和梯度下降法在本质上是一样的,只不过神经网络法使用一个崭新的编程模型,即以神经元为中心的代码结构设计,这样便于以后的功能扩充。

在Python中可以使用面向对象的技术,通过创建一个类来描述神经网络的属性和行为,下面我们将会创建一个叫做NeuralNet的class,然后通过逐步向此类中添加方法,来实现神经网络的训练和推理过程。

定义类

class NeuralNet(object):
    def __init__(self, eta):
        self.eta = eta
        self.w = 0
        self.b = 0

NeuralNet类从object类派生,并具有初始化函数,其参数是eta,也就是学习率,需要调用者指定。另外两个成员变量是w和b,初始化为0。

前向计算

    def __forward(self, x):
        z = x * self.w + self.b
        return z

这是一个私有方法,所以前面有两个下划线,只在NeuralNet类中被调用,不对外公开。

反向传播

下面的代码是通过梯度下降法中的公式推导而得的,也设计成私有方法:

    def __backward(self, x,y,z):
        dz = z - y
        db = dz
        dw = x * dz
        return dw, db

dz是中间变量,避免重复计算。dz又可以写成delta_Z,是当前层神经网络的反向误差输入。

梯度更新

    def __update(self, dw, db):
        self.w = self.w - self.eta * dw
        self.b = self.b - self.eta * db

每次更新好新的w和b的值以后,直接存储在成员变量中,方便下次迭代时直接使用,不需要在全局范围当作参数内传来传去的。

训练过程

只训练一轮的算法是:


for 循环,直到所有样本数据使用完毕:

  1. 读取一个样本数据
  2. 前向计算
  3. 反向传播
  4. 更新梯度

    def train(self, dataReader):
        for i in range(dataReader.num_train):
            # get x and y value for one sample
            x,y = dataReader.GetSingleTrainSample(i)
            # get z from x,y
            z = self.__forward(x)
            # calculate gradient of w and b
            dw, db = self.__backward(x, y, z)
            # update w,b
            self.__update(dw, db)
        # end for

推理预测

    def inference(self, x):
        return self.__forward(x)

推理过程,实际上就是一个前向计算过程,我们把它单独拿出来,方便对外接口的设计,所以这个方法被设计成了公开的方法。

主程序

if __name__ == '__main__':
    # read data
    sdr = SimpleDataReader()
    sdr.ReadData()
    # create net
    eta = 0.1
    net = NeuralNet(eta)
    net.train(sdr)
    # result
    print("w=%f,b=%f" %(net.w, net.b))
    # predication
    result = net.inference(0.346)
    print("result=", result)
    ShowResult(net, sdr)

4.3.4 运行结果可视化

打印输出结果:

w=1.716290,b=3.196841
result= [3.79067723]

最终我们得到了W和B的值,对应的直线方程是\(y=1.71629x+3.196841\)。推理预测时,已知有346台服务器,先要除以1000,因为横坐标是以K(千台)服务器为单位的,代入前向计算函数,得到的结果是3.74千瓦。

结果显示函数:

def ShowResult(net, dataReader):
    ......

对于初学神经网络的人来说,可视化的训练过程及结果,可以极大地帮助理解神经网络的原理,Python的Matplotlib库提供了非常丰富的绘图功能。

在上面的函数中,先获得所有样本点数据,把它们绘制出来。然后在[0,1]之间等距设定10个点做为x值,用x值通过网络推理方法net.inference()获得每个点的y值,最后把这些点连起来,就可以画出图4-5中的拟合直线。

图4-5 拟合效果

可以看到红色直线虽然穿过了蓝色点阵,但是好像不是处于正中央的位置,应该再逆时针旋转几度才会达到最佳的位置。我们后面小节中会讲到如何提高训练结果的精度问题。

4.3.5 工作原理

就单纯地看待这个线性回归问题,其原理就是先假设样本点是呈线性分布的,注意这里的线性有可能是高维空间的,而不仅仅是二维平面上的。但是高维空间人类无法想象,所以我们不妨用二维平面上的问题来举例。

在4.2的梯度下降法中,首先假设这个问题是个线性问题,因而有了公式\(z=xw+b\),用梯度下降的方式求解最佳的\(w、b\)的值。

在本节中,用神经元的编程模型把梯度下降法包装了一下,这样就进入了神经网络的世界,从而可以有成熟的方法论可以解决更复杂的问题,比如多个神经元协同工作、多层神经网络的协同工作等等。

如图4-5所示,样本点摆在那里,位置都是固定的了,神经网络的任务就是找到一根直线(注意我们首先假设这是线性问题),让该直线穿过样本点阵,并且所有样本点到该直线的距离的平方的和最小。

可以想象成每一个样本点都有一根橡皮筋连接到直线上,连接点距离该样本点最近,所有的橡皮筋形成一个合力,不断地调整该直线的位置。该合力具备两种调节方式:

  1. 如果上方的拉力大一些,直线就会向上平移一些,这相当于调节b值;
  2. 如果侧方的拉力大一些,直线就会向侧方旋转一些,这相当于调节w值。

直到该直线处于平衡位置时,也就是线性拟合的最佳位置了。

如果样本点不是呈线性分布的,可以用直线拟合吗?

答案是“可以的”,只是最终的效果不太理想,误差可以做到在线性条件下的最小,但是误差值本身还是比较大的。比如一个半圆形的样本点阵,用直线拟合可以达到误差值最小为1.2(不妨假设这个值的单位是厘米),已经尽力了但能力有限。如果用弧线去拟合,可以达到误差值最小为0.3。

所以,当使用线性回归的效果不好时,即判断出一个问题不是线性问题时,我们会用第9章的方法来解决。

代码位置

ch04, Level3

思考和练习

  1. 请把上述代码中的dw和db也改成私有属性,然后试着运行程序。

[ch04-03] 用神经网络解决线性回归问题的更多相关文章

  1. 机器学习中梯度下降法原理及用其解决线性回归问题的C语言实现

    本文讲梯度下降(Gradient Descent)前先看看利用梯度下降法进行监督学习(例如分类.回归等)的一般步骤: 1, 定义损失函数(Loss Function) 2, 信息流forward pr ...

  2. 03_利用pytorch解决线性回归问题

    03_利用pytorch解决线性回归问题 目录 一.引言 二.利用torch解决线性回归问题 2.1 定义x和y 2.2 自定制线性回归模型类 2.3 指定gpu或者cpu 2.4 设置参数 2.5 ...

  3. 02_利用numpy解决线性回归问题

    02_利用numpy解决线性回归问题 目录 一.引言 二.线性回归简单介绍 2.1 线性回归三要素 2.2 损失函数 2.3 梯度下降 三.解决线性回归问题的五个步骤 四.利用Numpy实战解决线性回 ...

  4. [ch05-02] 用神经网络解决多变量线性回归问题

    系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI, 点击star加星不要吝啬,星越多笔者越努力 5.2 神经网络解法 与单特征值的线性回归问题类似,多变量 ...

  5. [ch04-02] 用梯度下降法解决线性回归问题

    系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI, 点击star加星不要吝啬,星越多笔者越努力. 4.2 梯度下降法 有了上一节的最小二乘法做基准,我们这 ...

  6. [ch04-01] 用最小二乘法解决线性回归问题

    系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI, 点击star加星不要吝啬,星越多笔者越努力. 4.1 最小二乘法 4.1.1 历史 最小二乘法,也叫做 ...

  7. Java并发编程实战 03互斥锁 解决原子性问题

    文章系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 摘要 在上一篇文章02Java如何解决可见性和有序性问题当中,我们解决了可见性和 ...

  8. Tensorflow学习笔记03-使用神经网络做线性回归

    import tensorflow as tf import numpy as np #input就是输入数据,输入矩阵,in_size就是输入矩阵的列数(数据属性数量),out_size输出矩阵列数 ...

  9. TensorFlow 用神经网络解决非线性问题

    本节涉及点: 激活函数 sigmoid 产生随机训练数据 使用随机训练数据训练 加入偏移量b加快训练过程 进阶:批量生产随机训练数据 在前面的三好学生问题中,学校改变了评三好的标准 —— 总分> ...

随机推荐

  1. django & celery - 关于并发处理能力和内存使用的小结

    背景 众所周知,celery 是python世界里处理分布式任务的好助手,它的出现结合赋予了我们强大的处理异步请求,分布式任务,周期任务等复杂场景的能力. 然鹅,今天我们所要讨论的则是如何更好的在使用 ...

  2. Android_Fragment

    (一) Faragment有自己的生命周期 Fragment依赖于Activity Fragmen通过getActivity()可以获取所在Activity:Activity通过FragmentMan ...

  3. C函数库ctype.h概况

    1 字符测试函数 1> 函数原型均为int isxxxx(int) 2> 参数为int, 任何实参均被提升成整型 3> 只能正确处理处于[0, 127]之间的值 2 字符映射函数 1 ...

  4. uboot启动完成,kernel启动时lcd屏幕出现杂色解决办法

    先说说开发环境吧: 1 内核:linux2.6.xx 2 uboot:买开发板带的 注释:在最后我又添加了问题得到完美解决的办法. 问题:uboot启动完成,kernel启动时lcd屏幕出现杂色(比如 ...

  5. JavaScript闭包使用姿势指南

    目录 JavaScript闭包使用姿势指南 引言 闭包的定义 来个有趣的例子吧 现在我们换个例子吧 我们再来个有趣的例子 内存泄露问题 用闭包解决递归调用问题 用闭包模拟私有方法 在循环中使用闭包 性 ...

  6. java多线程与线程并发五:多个线程访问共享对象和数据的方式

    本节的内容主要是对前面几节提到的线程间共享数据的方式做一个总结. 线程之间共享数据有以下几种方式: 1.如果每个线程执行的代码相同,可以使用同一个Runnable对象. 2.如果每个线程执行的代码不同 ...

  7. 手把手教你用netty撸一个ZkClient

    原文地址: https://juejin.im/post/5dd296c0e51d4508182449a6 前言 有这个想法的缘由是前一阵子突发奇想, 想尝试能不能直接利用js连接到zookeeper ...

  8. 通过javascript 执行环境理解她

    古往今来最难的学的武功(javascript)算其一. 欲练此功必先自宫,愿少侠习的此功,笑傲江湖. 你将了解 执行栈(Execution stack) 执行上下文(Execution Context ...

  9. pat 1023 Have Fun with Numbers(20 分)

    1023 Have Fun with Numbers(20 分) Notice that the number 123456789 is a 9-digit number consisting exa ...

  10. HttpClient在高并发场景下的优化实战

    在项目中使用HttpClient可能是很普遍,尤其在当下微服务大火形势下,如果服务之间是http调用就少不了跟http客户端找交道.由于项目用户规模不同以及应用场景不同,很多时候可能不需要特别处理也. ...