11-08 周三 图解机器学习之实现逻辑异或,理解输出层误差和隐藏层误差项和动量因子

11-08 周三 图解机器学习之实现逻辑异或,理解输出层误差和隐藏层误差项
时间版本修改人描述
2023年11月8日14:36:36V0.1宋全恒新建文档

简介

 最近笔者完成了《图解机器学习》这本书的阅读,由于最近深度学习网络大行其是,所以也想要好好的弄清楚神经网络的工作原理。比如说训练、比如说验证,比如说权重更新,之前也曾经写过两个博客来描述感知机和BP算法示意。

  • 10-09 周一 图解机器学习之深度学习感知机学习
  • 11-06 周一 神经网络之前向传播和反向传播代码实战

 反向传播这个博客里主要通过一个样本,来不断的更新参数,但实际的神经网络结构是不会像博客中name简单的,因此还是需要给出一个计算公式的。在阅读图解机器学习P169页,如下代码时,自己没有看懂:

        # 计算输出层误差
        for k in range(self.no):
            error = targets[k] - self.ao[k]
            output_deltas[k] = dsigmoid(self.ao[k]) * error
            
        # 计算隐藏层的误差
        hidden_deltas = [0.0]*self.nh
        for j in range(self.nh):
            error = 0
            for k in range(self.no):
                error = error + output_deltas[k] * self.wo[j][k]
            hidden_deltas[j] = dsigmoid(self.ah[j]) * error
            
        # 更新输出层权重
        for j in range(self.nh):
            for k in range(self.no):
                change = output_deltas[k]*self.ah[j]
                self.wo[j][k] = self.wo[j][k] + N*change + M * self.co[j][k]

 上述在计算过程中求出了输出层误差和隐藏层误差项。如何理解这个代码片段呢?

完整代码

import math
import random
import string
random.seed(0)

# 生成区间[a, b)内的随机数
def rand(a, b):
    return (b - a) *random.random() + a


# 生成I*J大小的矩阵, 默认零矩阵
def makeMatrix(I, J, fill=0.0):
    m = []
    for i in range(I):
        m.append([fill]*J)
    return m

# 函数 sigmoid, 采用tanh函数, 比起标准的1/(1+exp(-x))更好
def sigmoid(x):
    return math.tanh(x)

# 函数sigmoid的派生函数 tanh(x)' = 1 - tanh(x)^2
def dsigmoid(x):
    return 1.0 - x**2


class BPNeuralNet:
    '''建立三层反向传播神经网络'''
    def __init__(self, ni, nh, no) -> None:
        self.ni = ni + 1
        self.nh = nh
        self.no = no
        
        # 激活神经网络的所有节点
        self.ai = [1.0]* self.ni
        self.ah = [1.0]*self.nh
        self.ao = [1.0]* self.no
        
        # 建立权重矩阵
        self.wi = makeMatrix(self.ni, self.nh)
        self.wo = makeMatrix(self.nh, self.no)
        
        # 设为随机值
        for i in range(self.ni):
            for j in range(self.nh):
                self.wi[i][j] = rand(-0.2, 0.2)
                
        for i in range(self.nh):
            for j in range(self.no):
                self.wo[i][j] = rand(-2.0, 2.0)
                
                
        # 建立动量因子
        self.ci = makeMatrix(self.ni, self.nh)
        self.co = makeMatrix(self.nh, self.no)
        
    # 前向传播,得到预计的输出。
    # 各个神经元的输出分别位于self.ah 和self.ao
    # inputs 代表一个样本
    def fp(self, inputs):
        if len(inputs) != self.ni -1:
            raise ValueError('与输入层节点数不符错误!')
        
        for i in range(self.ni-1):
            self.ai[i] = inputs[i]
        
        for j in range(self.nh):
            sum = 0.0
            for i in range(self.ni):
                sum += self.ai[i]* self.wi[i][j]
            self.ah[j] = sigmoid(sum)
        
        # 激活输出层
        for j in range(self.no):
            sum = 0
            for i in range(self.nh):
                sum += self.ah[i]*self.wo[i][j]
                
            self.ao[j] = sigmoid(sum)
        return self.ao[:]
    
    # N 学习速率 learning factor
    # M 动量因子 momentum factor
    # 基本思路是直接求出每个神经元的误差
    def back_propagate(self, targets, N, M):
        '''反向传播'''
        if len(targets) != self.no:
            raise ValueError("与输出层节点数不符!")
        
        output_deltas = [0.0] * self.no
        
        
        # 计算输出层误差
        for k in range(self.no):
            error = targets[k] - self.ao[k]
            output_deltas[k] = dsigmoid(self.ao[k]) * error
            
        # 计算隐藏层的误差
        hidden_deltas = [0.0]*self.nh
        for j in range(self.nh):
            error = 0
            for k in range(self.no):
                error = error + output_deltas[k] * self.wo[j][k]
            hidden_deltas[j] = dsigmoid(self.ah[j]) * error
            
        # 更新输出层权重
        for j in range(self.nh):
            for k in range(self.no):
                change = output_deltas[k]*self.ah[j]
                self.wo[j][k] = self.wo[j][k] + N*change + M * self.co[j][k]
                self.co[j][k] = change
        
        # 更新输入层权重
        for i in range(self.ni):
            for j in range(self.nh):
                change=hidden_deltas[j]*self.ai[i]
                self.wi[i][j] += N * change + M * self.ci[i][j]
                self.ci[i][j] = change
        
        error = 0.0
        for k in range(len(targets)):
            error = error + 0.5*(targets[k]-self.ao[k])**2
        return error
    

    def test(self, patterns):
        for p in patterns:
            print(p[0], '->', self.fp(p[0]))
            
    
    def weights(self):
        print('输入层权重')
        for i in range(self.ni):
            print(self.wi[i])
            
        print()
        print("输出层权重")
        for j in range(self.nh):
            print(self.wo[j])
    
    def train(self, patterns, iterations=100000, N =0.5, M=0.1):
        for i in range(iterations):
            error = 0.0
            for p in patterns:
                inputs = p[0]
                targets = p[1]
                self.fp(inputs)
                error = error + self.back_propagate(targets, N, M)
                
            if i % 100 ==0:
                print('计算误差的值是: %-.5f'%error)
                    
def trainprog():
    # BP神经网络学习逻辑异或
    pat = [
        [[0, 0], [0]],
        [[0, 1], [1]],
        [[1, 0], [1]],
        [[1, 1], [0]]
    ]
    # 创建一个神经网络,输入层两个节点, 输出层两个节点,输出层一个节点:
    net = BPNeuralNet(2, 3, 1)
    net.train(pat)
    
    # 测试训练的成果
    net.test(pat)
    
if __name__ == '__main__':
    trainprog()

 上述代码在理解上并不复杂,主要是通过三层神经网络来拟合逻辑异或运算。采用的是个案更新的策略来更新权重参数。

权重更新

基础知识

 多层前馈神经网络。

 一个示例: 奶酪是否喜爱。

 为此我们构建一个神经网络:

激活函数

  激活函数

  • 指数函数
  • sigmoid
  • 逻辑回归

 校正因子的概念如下:

 权重更新的策略有多种:

  • 个案更新 case-based 更容易得到准确的结果。
  • 批量更新 batch 优点就是比较快,加速。

迭代终止条件

 迭代终止条件:

  • 当权重和偏置差异与上一次非常小
  • 误差达到之前设置的阈值
  • 运行次数

存疑代码

        # 计算输出层误差
        for k in range(self.no):
            error = targets[k] - self.ao[k]
            output_deltas[k] = dsigmoid(self.ao[k]) * error
            
        # 计算隐藏层的误差
        hidden_deltas = [0.0]*self.nh
        for j in range(self.nh):
            error = 0
            for k in range(self.no):
                error = error + output_deltas[k] * self.wo[j][k]
            hidden_deltas[j] = dsigmoid(self.ah[j]) * error
            
        # 更新输出层权重
        for j in range(self.nh):
            for k in range(self.no):
                change = output_deltas[k]*self.ah[j]
                self.wo[j][k] = self.wo[j][k] + N*change + M * self.co[j][k]
                self.co[j][k] = change
        
        # 更新输入层权重
        for i in range(self.ni):
            for j in range(self.nh):
                change=hidden_deltas[j]*self.ai[i]
                self.wi[i][j] += N * change + M * self.ci[i][j]
                self.ci[i][j] = change

 上述分别计算出了输出层的误差项和输出层的误差项。按照上述代码理解,前两个for循环用于计算误差项,后两个循环用来更新权重,顺序从后向前,这也是反向传播得名的由来。关键是为什么输出层的误差是这么得来的呢?

 参考 BP神经网络-第6集 反向传播误差,调整全部权重,这对于理解是非常关键的。

 我们以同样的方式,就可以得到每个神经元的误差。如下图

 可以采用矩阵相乘的方法

 权重通过矩阵乘表示。

gpt辅助理解

 自己还是无法理解,但感觉输出层的误差项与选用的损失函数密切相关,因此,笔者询问了GPT,得到了如下的结果:

  • 为什么要乘以激活函数的导数?
  • 交叉熵损失函数的输出层误差项
  • 均方差 输出层误差项:

 由此,我们可以得到如下的图示:

在计算h1节点的误差项时,输出层两个误差项以w7 和 w8进行作用,进而可以得到h1神经元的误差项:

errorh1=w7*e1 + w8 * e2。 依次可以得到h1, h2, h3神经元的误差项损失。

动量因子

 代码片段,其实整体贯彻了P166 图解机器学习图13.10,

 相互映照,也可以通过代码来理解上述的过程:

 代码参见 11-06 周一 神经网络之前向传播和反向传播代码实战

总结

 这部分的代码片段比之前的全部手动计算权重更新的过程复杂一些,因为抽象出了输出层误差项和隐藏层误差项,代码的抽象知识更加复杂了,但 BP神经网络-第6集 反向传播误差,调整全部权重则直接给出了误差项的矩阵乘表示,而这种方式,应该也是机器学习库中默认使用的方式吧。总之,这篇文章试图解释《图解机器学习》中第13章深度学习网络的代码,弄清楚其中权重更新的方式,包括为什么使用动量因子进行更新这种优化技术。希望读者能够读懂,进而在自己的工程实践中使用深度学习解决自己的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/124806.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023.11.09 homework

4年级数学:不会做就作图,画出来,简单化就容易懂 2023.11.09 homework (2)-CSDN博客

Langchain-Chatchat实践详解

简介 本质上是在Langchain基础上封装的一层聊天服务,可以对接底层多种离线LLM和在线的LLM(也可以对接自定义的在线LLM)。提供基于知识库聊天功能相关的一系列API。 下载源码 源码地址: https://github.com/chatchat-space/Lang…

QT第2课-GUI程序实例分析

GUI程序开发概述 不同的操作系统GUI开发原理相同不同的操作系统GUI SDK 不同 GUI 程序开发原理 GUI程序在运行时会创建一个消息队列系统内核将用户的键盘鼠标操作翻译成对应的程序消息程序在运行过程中需要实时处理队列中的消息当队列中没有消息时,程序将处于停滞…

微信小程序自动化采集方案

本文仅供学习交流,只提供关键思路不会给出完整代码,严禁用于非法用途,拒绝转载,若有侵权请联系我删除! 一、引言 1、对于一些破解难度大,花费时间长的目标,我们可以先采用自动化点击触发请求&…

Read-Easy Excel源码解析(一)

Read&Write-Easy Excel 当我们需要导入大Excel时候,用POI会内存溢出,这时候我们用EasyExcel来解决,它底层采用的是SAX(Simple Api for Xml)事件驱动,解析xml的方式来解析excel文件。 首先我们看他的re…

c++ 信奥编程 1129:统计数字字符个数

#include<bits/stdc.h> using namespace std; int main() {string arr; //定义变量 arr getline(cin, arr); //通过函数输入并存储到变量中 int a 0; // 定义变量a&#xff0c;并赋初值0for(int i 0; i < arr.length(); i){ if(arr[i] < 57 && arr[i] &…

《视觉SLAM十四讲》-- 相机与图像

04 相机与图像 4.1 相机模型 4.1.1 针孔相机模型 针孔模型描述了一束光线通过针孔后&#xff0c;在针孔背面投影成像的关系&#xff08;类似小孔成像原理&#xff09;。 根据相似三角关系 Z f − X X ′ − Y Y ′ (3-1) \frac{Z}{f}-\frac{X}{X^{\prime}}-\frac{Y}{Y^{\p…

C【整数正序分解】

// 整数正序分解 #include <stdio.h> #include <stdlib.h>int main() {int x;scanf("%d", &x);// 13425/10000->1(int一个d)// 13425%10000->3425(这是x)// 10000/10-.1000(这是mask)int mask 1;int t x;while (t > 9){t / 10;mask * 10;…

【数据结构初级(2)】单链表的基本操作和实现

文章目录 Ⅰ 概念及结构1. 单链表的概念2. 单链表的结构 Ⅱ 基本操作实现1. 定义单链表结点2. 创建新结点3. 单链表打印4. 单链表尾插5. 单链表头插6. 单链表尾删7. 单链表头删8. 单链表查找9. 在指定 pos 位置前插入结点10. 删除指定 pos 位置的结点11. 单链表销毁 本章实现的…

计算机二级公共基础

知识点 1.树 树的最大层次&#xff08;最长路径的长度&#xff09;称为树的深度 二叉树的后件最多不超过两个 满二叉树&#xff1a;除最后一层每一层的所有节点都有两个子节点。&#xff08;满二叉树一定是完全二叉树&#xff09; 完全二叉树&#xff1a;所有节点均达到最大数…

vue3怎么获取el-form的元素节点

在元素中使用ref设置名称 在ts中通过从element-plus引入formInstance,设置formRef同名名称字段来获取el-form节点

疏散及应急照明灯在地下建筑中的运用探析

安科瑞 华楠 摘要&#xff1a;新型疏散及应急照明灯在地下建筑中的有效应用&#xff0c;可以有效的促使地下建筑提升自身的安全性能&#xff0c;尤其是在发生火灾时&#xff0c;改变传统的应急疏散的局限性&#xff0c;充分发挥出自身的新型特殊功能&#xff0c;为人们提供更为…

ElementUI-tree拖拽功能与节点自定义

前言 在管理端会遇到多分类时&#xff0c;要求有层次展示出来&#xff0c;并且每个分类有额外的操作。例如&#xff1a;添加分类、编辑分类、删除、拖到分类等。 下面将会记录这样的一个需求实习过程。 了解需求 分类展示按层级展示分类根据特定的参数展示可以操作的按钮&a…

基于SpringBoot+Vue的婚恋相亲交友系统

基于SpringBootVue的婚恋相亲交友系统~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 管理员界面 摘要 基于SpringBootVue的婚恋相亲交友系统是一个现代化的、高效的交…

使用bitmap实现可回收自增id

需求描述 设计一个方法&#xff0c;每次调用返回一个自增id&#xff0c;同时需要满足以下要求。 可更新id的状态为已使用&#xff0c;已使用的id下次调用时不再返回可修改某个id的状态为未使用&#xff0c;下次调用时设为未使用状态的id可重新被返回 思路 思路一&#xff1…

全志T507-H技术帖 | 去掉IO扩展芯片后保留扩展引脚功能的实现方法

飞凌嵌入式推出的OKT507-C作为一款广受欢迎的开发板拥有丰富的功能接口&#xff0c;而实际上OKT507-C开发板的CPU引脚资源是比较紧缺的&#xff0c;那么它究竟是如何提供如此丰富的接口资源的呢&#xff1f;答案就是IO扩展芯片——TCA6424A。 这是一个24 位 I2C 和系统管理总线…

第三方商城对接项目(202311)

文章目录 1. 项目背景和目标2. 项目成果3. 项目经验总结4. 展望和建议 1. 项目背景和目标 竞标成功接口对接第三方商城&#xff0c;商品&#xff0c;订单&#xff0c;售后尽快完成对接 2. 项目成果 完成整个项目功能流程对接新业务功能移交项目等业务部门使用 3. 项目经验总…

MySQL中表的增删查改(进阶),超详细!

目录 一、数据库的约束 1、约束类型 2、NULL约束 3、UNIQUE&#xff1a;唯一约束 4、DEFAULT&#xff1a;默认值约束 5、PRIMARY KEY&#xff1a;主键约束&#xff08;主键只能定义一个&#xff0c;NOT NULL 和 UNIQUE 的结合&#xff09; 6、FOREIGN KEY&#xff1a;外键约…

C++之List容器

1.list容器简介 list是序列容器&#xff0c;允许在序列中的任何位置执行固定O(1)时间复杂度的插入和删除操作&#xff0c;并在两个方向进行迭代。list容器是一个双向循环链表。 list容器与vector容器区别&#xff1a; ①list中空间是随机的&#xff0c;通过指针域保存下一个成员…

【C++】类和对象的关系,对象的存储方式以及对象内存的计算

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …