基于weka手工实现多层感知机(BPNet)

news2025/1/18 3:19:15

一、BP网络

1.1 单层感知机

单层感知机,就是只有一层神经元,它的模型结构如下1

在这里插入图片描述

对于权重 w w w的更新,我们采用如下公式:

w i = w i + Δ w i Δ w i = η ( y − y ^ ) x i (1) w_i=w_i+\Delta w_i \\ \Delta w_i=\eta(y-\hat{y})x_i\tag{1} wi=wi+ΔwiΔwi=η(yy^)xi(1)

其中, y y y 为标签, y ^ \hat{y} y^ 为模型输出值, η \eta η 为学习率, x i x_i xi 为模型输入。

这样我们就实现了对一层感知机的参数调整。

但是,单层感知机由于是线性的,拟合能力很有限,对于非线性可分的问题(比如异或)都是无法处理的。

1.2 多层感知机

如果是多层感知机,参数调整复杂一些,需要采用误差逆向传播算法。网络结构如图,这里假设我们只考虑包含一个隐含层的神经网络:

在这里插入图片描述

网络的具体参数如上图所示。

其在第k个样例上的误差为:

E k = 1 2 ∑ j = 1 l ( y ^ j k − y j k ) E_k=\frac{1}{2}\sum_{j=1}^l(\hat{y}_j^k-y_j^k) Ek=21j=1l(y^jkyjk)

对于任意参数更新:
v = v + Δ v v=v+\Delta v v=v+Δv

对于参数 w w w

Δ w h j = − η ∂ E k ∂ w h j \Delta w_{hj}=-\eta \frac{\partial E_k}{\partial w_{hj}} Δwhj=ηwhjEk

∂ E k ∂ w h j = ∂ E k ∂ y ^ j k ⋅ ∂ y ^ j k ∂ β j ⋅ ∂ β j ∂ w h j \frac{\partial E_k}{\partial w_{hj}}=\frac{\partial E_k}{\partial \hat{y}_j^k} \cdot \frac{\partial \hat{y}_j^k}{\partial \beta_j} \cdot \frac{\partial \beta_j}{\partial w_{hj}} whjEk=y^jkEkβjy^jkwhjβj

β i \beta_i βi为输出层的输入,根据其定义,

∂ β j ∂ w h j = b h \frac{\partial \beta_j}{\partial w_{hj}}=b_h whjβj=bh

其中 b h b_h bh为隐含层的输出。

因为每一层要经过一个激活函数,这里我们采用的是sigmoid函数,他有一个很好的性质:

f ′ ( x ) = f ( x ) ( 1 − f ( x ) ) f'(x)=f(x)(1-f(x)) f(x)=f(x)(1f(x))

于是就有:

g j = − ∂ E k ∂ y ^ j k ⋅ ∂ y ^ j k ∂ β j = − ( y ^ j k − y j k ) f ′ ( β j − θ j ) = y ^ j k ( 1 − y ^ j k ) ( y j k − y ^ j k ) \begin{aligned} g_j=& -\frac{\partial E_k}{\partial \hat{y}_j^k} \cdot \frac{\partial \hat{y}_j^k}{\partial \beta_j} \\ =&-(\hat{y}_j^k-y_j^k)f'(\beta_j-\theta_j)\\ =&\hat{y}_j^k(1-\hat{y}_j^k)(y_j^k-\hat{y}_j^k) \end{aligned} gj===y^jkEkβjy^jk(y^jkyjk)f(βjθj)y^jk(1y^jk)(yjky^jk)

其中, θ j \theta_j θj 为第j个输出神经元的 bias。

这样,我们就得到了 w w w的更新量(注意:这里不需要加负号了,因为我们已经在 g j g_j gj中添加过了),

Δ w h j = η g j b h \Delta w_{hj}=\eta g_j b_h Δwhj=ηgjbh

Δ θ j = − η g j Δ v i h = η e h x i Δ γ h = − η e h \Delta \theta_j=-\eta g_j\\ \Delta v_{ih}=\eta e_h x_i \\ \Delta \gamma_h=-\eta e_h Δθj=ηgjΔvih=ηehxiΔγh=ηeh

在BP算法中,假设我们的只有3层网络,1个输入层、一个隐含层、一个输出层,那么参数的数量为 d × q + q × l + q + l d\times q+q\times l+q+l d×q+q×l+q+l(其中,d,q,l分别为输入层、隐藏层、输出层的神经元数量),我们根据上面的公式就可以进行参数的迭代计算了。

而根据不同的输入数据,隐藏层神经元的数量如何设置呢?我这里采用了一个经验公式,参考自bp隐含层节点数经验公式。

文中指出:

q = ( d + l ) × 2 3 q=(d+l)\times \frac{2}{3} q=(d+l)×32

在这里插入图片描述

二、基于weka代码实现

package weka.classifiers.myf;

import weka.classifiers.Classifier;
import weka.core.*;
import weka.core.matrix.Matrix;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.NominalToBinary;

/**
 * @author YFMan
 * @Description 自定义的 BP 神经网络 分类器
 * @Date 2023/6/7 11:30
 */
public class myBPNet extends Classifier {
    /*
     * @Author YFMan
     * @Description //主函数
     * @Date 2023/6/7 19:14
     * @Param [argv 命令行参数]
     * @return void
     **/
    public static void main(String[] argv) {
        runClassifier(new myBPNet(), argv);
    }

    // 训练数据
    private Instances m_instances;

    // 当前正在训练的样例
    private Instance m_currentInstance;

    // 训练总次数
    private final int m_numEpochs;

    // 二值化过滤器,将标称属性转换为二值属性,进而输入到神经网络中
    private final NominalToBinary m_nominalToBinaryFilter;

    // 学习率
    private final double m_learningRate;

    // 当前的训练误差
    private double m_error;

    // 输入维度(输入层神经元数量)
    private int m_numInputs;

    // 隐藏层维度(隐藏层神经元数量)
    private int m_numHidden;

    // 输出维度(输出层神经元数量)
    private int m_numOutputs;

    // 输入层的输出
    private Matrix m_input;

    // 输入层到隐藏层的权重矩阵
    private Matrix m_weightsInputHidden;

    // 隐藏层到输出层的权重矩阵
    private Matrix m_weightsHiddenOutput;

    // 隐藏层的阈值
    private Matrix m_hiddenThresholds;

    // 输出层的阈值
    private Matrix m_outputThresholds;

    // 隐藏层的输出
    private Matrix m_hiddenOutput;

    // 输出层的输出
    private Matrix m_output;

    // 输出层对应的标签
    private Matrix m_labels;

    /*
     * @Author YFMan
     * @Description // BP 网络 构造函数
     * @Date 2023/6/7 19:04
     * @Param []
     * @return
     **/
    public myBPNet() {
        // 训练数据
        m_instances = null;
        // 当前样例
        m_currentInstance = null;
        // 训练误差
        m_error = 0;
        // 二值化过滤器,将标称属性转换为二值属性
        m_nominalToBinaryFilter = new NominalToBinary();
        // 训练总次数
        m_numEpochs = 500;
        // 学习率
        m_learningRate = .3;
    }

    /*
     * @Author YFMan
     * @Description //Sigmoid函数
     * @Date 2023/6/7 19:53
     * @Param [x]
     * @return double
     **/
    double sigmoid(double x) {
        return 1.0 / (1.0 + Math.exp(-x));
    }

    /*
     * @Author YFMan
     * @Description //前向传播
     * @Date 2023/6/7 19:47
     * @Param []
     * @return void
     **/
    void forwardPropagation() {
        // 初始化输入层的输出
        for (int i = 0; i < m_numInputs; i++) {
            m_input.set(0, i, m_currentInstance.value(i));
        }

        // 计算隐藏层的输出
        m_hiddenOutput = m_input.times(m_weightsInputHidden);
        // 减去阈值
        m_hiddenOutput = m_hiddenOutput.minus(m_hiddenThresholds);
        // 激活函数
        for (int i = 0; i < m_numHidden; i++) {
            m_hiddenOutput.set(0, i, sigmoid(m_hiddenOutput.get(0, i)));
        }

        // 计算输出层的输出
        m_output = m_hiddenOutput.times(m_weightsHiddenOutput);
        // 减去阈值
        m_output = m_output.minus(m_outputThresholds);
        // 激活函数
        for (int i = 0; i < m_numOutputs; i++) {
            m_output.set(0, i, sigmoid(m_output.get(0, i)));
        }
    }

    /*
     * @Author YFMan
     * @Description //反向传播
     * @Date 2023/6/7 20:20
     * @Param []
     * @return void
     **/
    void backPropagation() {
        // 获取当前样例的标签
        m_labels = new Matrix(1, m_numOutputs);
        for (int i = 0; i < m_numOutputs; i++) {
            // 将标签转换为矩阵(最后部分的 binary 对应 类标签 标值)
            m_labels.set(0, i, m_currentInstance.value(m_numInputs + i));
        }


        // 计算 g = m_output * (1 - m_output) * (m_labels - m_output)
        // 输出层的误差
        Matrix m_outputError = m_output.copy();
        m_outputError = m_outputError.times(-1);
        // 创建一个全为 1 的矩阵 用于 1 - m_output
        Matrix one = new Matrix(1, m_numOutputs, 1);
        m_outputError = m_outputError.plus(one);
        m_outputError = m_outputError.arrayTimes(m_output);
        m_outputError = m_outputError.arrayTimes(m_labels.minus(m_output));

        // 计算 隐藏层到输出层的权重矩阵的增量 deltaWeightsHiddenOutput = m_learningRate * g * b_h
        // 隐藏层到输出层的权重矩阵的增量
        Matrix m_deltaWeightsHiddenOutput = new Matrix(m_numHidden, m_numOutputs);
        for(int h = 0; h < m_numHidden; h++) {
            for(int j = 0; j < m_numOutputs; j++) {
                m_deltaWeightsHiddenOutput.set(h, j, m_learningRate * m_outputError.get(0, j) * m_hiddenOutput.get(0, h));
            }
        }

        // 计算 隐藏层到输出层的阈值的增量 deltaOutputThresholds = - m_learningRate * g
        // 输出层的阈值的增量
        Matrix m_deltaOutputThresholds = m_outputError.times(-m_learningRate);

        // 计算 隐藏层的误差 hiddenError = b_h * (1 - b_h) * g * m_weightsHiddenOutput.transpose()
        Matrix one1 = new Matrix(1, m_numHidden, 1);
        // 隐藏层的误差
        Matrix m_hiddenError = m_hiddenOutput.copy();
        m_hiddenError = m_hiddenError.times(-1);
        m_hiddenError = m_hiddenError.plus(one1);
        m_hiddenError = m_hiddenError.arrayTimes(m_hiddenOutput);
        m_hiddenError = m_hiddenError.arrayTimes(m_outputError.times(m_weightsHiddenOutput.transpose()));

        // 计算 输入层到隐藏层的权重矩阵的增量 deltaWeightsInputHidden = m_learningRate * hiddenError * x_i
        // 输入层到隐藏层的权重矩阵的增量
        Matrix m_deltaWeightsInputHidden = new Matrix(m_numInputs, m_numHidden);
        for(int i = 0; i < m_numInputs; i++) {
            for(int h = 0; h < m_numHidden; h++) {
                m_deltaWeightsInputHidden.set(i, h, m_learningRate * m_hiddenError.get(0, h) * m_input.get(0, i));
            }
        }

        // 计算 输入层到隐藏层的阈值的增量 deltaHiddenThresholds = - m_learningRate * hiddenError
        // 隐藏层的阈值的增量
        Matrix m_deltaHiddenThresholds = m_hiddenError.times(-m_learningRate);

        // 更新 隐藏层到输出层的权重矩阵
        m_weightsHiddenOutput = m_weightsHiddenOutput.plus(m_deltaWeightsHiddenOutput);
        // 更新 隐藏层到输出层的阈值
        m_outputThresholds = m_outputThresholds.plus(m_deltaOutputThresholds);

        // 更新 输入层到隐藏层的权重矩阵
        m_weightsInputHidden = m_weightsInputHidden.plus(m_deltaWeightsInputHidden);
        // 更新 输入层到隐藏层的阈值
        m_hiddenThresholds = m_hiddenThresholds.plus(m_deltaHiddenThresholds);

        // 更新训练误差
        m_error += calculateError();
    }


    /*
     * @Author YFMan
     * @Description //计算训练误差
     * @Date 2023/6/7 21:04
     * @Param []
     * @return double
     **/
    double calculateError() {
        // 计算误差
        double error = 0;
        for (int i = 0; i < m_numOutputs; i++) {
            error += Math.pow(m_labels.get(0, i) - m_output.get(0, i), 2);
        }
        error /= 2;
        return error;
    }

    /*
     * @Author YFMan
     * @Description //训练BP网络
     * @Date 2023/6/7 19:44
     * @Param []
     * @return void
     **/
    void train() {
        // 遍历epochs次训练
        for (int i = 0; i < m_numEpochs; i++) {
            // 初始化训练误差
            m_error = 0;
            // 遍历训练数据
            for (int j = 0; j < m_instances.numInstances(); j++) {
                // 获取当前训练样例
                m_currentInstance = m_instances.instance(j);
                // 前向传播
                forwardPropagation();
                // 反向传播
                backPropagation();
            }
            // 打印训练误差
            System.out.println("Epoch " + (i + 1) + " error: " + m_error);
            // 训练误差小于0.01,停止训练
            if (m_error < 0.01) {
                break;
            }
        }
    }

    /*
     * @Author YFMan
     * @Description //根据训练数据训练BP网络
     * @Date 2023/6/7 19:12
     * @Param [instances 训练数据]
     * @return void
     **/
    public void buildClassifier(Instances instances) throws Exception {
        // 训练数据
        m_instances = instances;
        // 获取训练数据的标签
        m_nominalToBinaryFilter.setInputFormat(m_instances);
        m_instances = Filter.useFilter(m_instances, m_nominalToBinaryFilter);
        m_numInputs = 0;
        // 遍历训练数据,获取输入维度、隐藏层维度、输出维度
        for (int i = 0; i < instances.numAttributes(); i++) {
            // 获取输入维度
            if (i != instances.classIndex()) {
                if(instances.attribute(i).numValues()<=2){
                    m_numInputs += 1;
                }
                else{
                    m_numInputs += instances.attribute(i).numValues();
                }
            } else { // 获取输出维度
                if(instances.attribute(i).numValues()<=2){
                    m_numOutputs = 1;
                }
                else{
                    m_numOutputs = instances.attribute(i).numValues();
                }
            }
        }
        // 隐藏层维度(输入维度+输出维度) * 2 / 3
        m_numHidden = (m_numInputs + m_numOutputs) * 2 / 3;
        // 初始化输入层
        m_input = new Matrix(1, m_numInputs);

        // 初始化权重矩阵
        m_weightsInputHidden = Matrix.random(m_numInputs, m_numHidden);
        m_weightsHiddenOutput = Matrix.random(m_numHidden, m_numOutputs);

        // 初始化隐藏层阈值向量
        m_hiddenThresholds = new Matrix(1, m_numHidden);
        // 初始化输出层阈值向量
        m_outputThresholds = new Matrix(1, m_numOutputs);
        // 训练误差
        m_error = 0;
        // 训练
        train();

    }

    /*
     * @Author YFMan
     * @Description //根据样例分类数据,返回各个类别的概率
     * @Date 2023/6/7 19:11
     * @Param [instance 输入样例]
     * @return double[] 各个类别的概率
     **/
    public double[] distributionForInstance(Instance instance) throws Exception {
        // 将输入样例由标称属性转换为二值属性
        m_nominalToBinaryFilter.input(instance);
        m_currentInstance = m_nominalToBinaryFilter.output();

        // 前向传播
        forwardPropagation();
        // 如果输出维度为1,返回概率
        double[] result;
        if (m_numOutputs == 1) {
            result = new double[2];
            result[0] = 1 - m_output.get(0, 0);
            result[1] = m_output.get(0, 0);
        }
        else{ // 否则,进行归一化处理
            result = m_output.getRowPackedCopy();
            // 归一化处理
            double sum = 0;
            for (double v : result) {
                sum += v;
            }
            for (int i = 0; i < result.length; i++) {
                result[i] /= sum;
            }
        }
        return result;
    }
}

  1. 周志华.《机器学习》 ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java Jsp+Json+阿贾克斯

0目录 1.补充阿贾克斯 2.实战&#xff08;加入Json&#xff09; 1.补充阿贾克斯 创建工程&#xff0c;加入jason依赖和数据库 新建数据库&#xff0c;表和实体类 先新建一个查询方法 FruitServlet 修改Web.xml 加入Js包&#xff08;版本1.9.1&#xff09; …

electron在BrowserWindow中禁止右键菜单

最近使用 electron vite solid.js 做一个网络流量实时监控的小工具&#xff0c;其中需要禁止用户在获取 BrowserWindow 焦点后弹出默认右键菜单。 解决方案 在 new BrowserWindow 后中添加以下代码&#xff1a; // 禁止右键菜单弹出 startmainWindow.hookWindowMessage &…

吴恩达机器学习2022-Jupyter-Scikit-Learn教学

1可选实验室: 线性回归使用 Scikit-Learn 有一个开源的、商业上可用的机器学习工具包&#xff0c;叫做 scikit-learn。本工具包包含您将在本课程中使用的许多算法的实现。 1.1目标 在这个实验室里: 利用 scikit-学习使用线性回归梯度下降法来实现 1.2工具 您将利用 sciki…

CSS3绘制3D银行卡片层叠展示特效

使用纯css3绘制3D银行卡层叠展示特效 具体示例如下 <template><div><div class"tariffCards"><div class"economy"><img src"../images/css-article-imgs/example-css3D-card/tarcs.png" alt"中信银行" he…

2022 Robocom CAIP国赛 第四题 变牛的最快方法

原题链接&#xff1a; PTA | 程序设计类实验辅助教学平台 题面&#xff1a; 这里问的是把任意一种动物的图像变成牛的方法…… 比如把一只鼠的图像变换成牛的图像。方法如下&#xff1a; 首先把屏幕上的像素点进行编号&#xff1b;然后把两只动物的外轮廓像素点编号按顺时针记…

python 第十四章 模块和包

系列文章目录 第一章 初识python 第二章 变量 第三章 基础语句 第四章 字符串str 第五章 列表list [] 第六章 元组tuple ( ) 第七章 字典dict {} 第八章 集合set {} 第九章 常用操作 第十章 函数 第十一章 文件操作 第十二章 面向对象 第十三章 异常 文章目录 系列文章目录14.…

【阅读笔记】Rapid, Detail-Preserving Image Downscaling

Rapid, Detail-Preserving Image Downscaling&#xff08;快速的图像缩放技术&#xff09; 该论文提出了一种基于卷积滤波器的算法&#xff0c;并确定滤波器的权值&#xff0c;使重要的细节保留在缩小比例的图像。更具体地说&#xff0c;它为更偏离局部图像邻域的像素分配更大…

Python多进程加快图片读取速度、多进程下图片的有序读取(mp.Queue)

Python多进程加快图片读取速度(mp.Queue) 多进程&#xff0c;加快图片读取&#xff0c;多进程下图片的有序读取&#xff0c;Python&#xff0c;multiprocessing&#xff0c;multiprocessing.Queue&#xff0c;opencv-python 文章结构 快速使用&#xff0c;多进程读取图片&…

冯诺依曼体系结构以及回答操作系统(是什么,为什么,怎么办)问题

目录 一、硬件冯诺依曼体系结构 二、软件2.1 计算机的层状结构2.2 操作系统的概念2.3 操作系统是什么&#xff1f;2.4 为什么要有操作系统&#xff1f;2.5 操作系统是怎么管理底层的软硬件资源的呢&#xff1f; 一、硬件 冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记…

HTML+CSS+JavaScript:九九乘法表

一、需求如图 二、思路及代码 1、JavaScript代码 稍微刷过一点算法题的小伙伴就很容易想到这题需要利用双层for循环来实现&#xff0c;思路也是比较简单的&#xff0c;我在这里就直接放代码了 不添加CSS渲染的代码如下 <!DOCTYPE html> <html lang"en"&…

JVM学习笔记(三)垃圾回收

相关文章&#xff1a; JVM中的新生代和老年代&#xff08;Eden空间、两个Survior空间&#xff09;_jvm eden_样young的博客-CSDN博客JAVA命令行工具&#xff08;一&#xff09;--JAVA - 简书JAVA命令行工具&#xff08;二&#xff09;-jps - 简书JAVA命令行工具&#xff08;三&…

AttributeError: module ‘torch.nn‘ has no attribute ‘module‘

import torch import torch.nn as nnclass LinearModel(nn.Module):def _init_(self,ndim):super(LinearModel,self)._init_()self.ndimndimself.weightnn.Parameter(torch.randn(ndim,1))#定义权重self.biasnn.Parameter(torch.randn(1)) #定义偏置def forward(self,x):# y …

【离散数学实验报告】最小生成树的生成

实验四&#xff1a;最小生成树 一、实验目的&#xff1a; 理解最小生成树的画法。提高学生编写实验报告&#xff0c;总结实验结果的能力&#xff0c;培养学生的逻辑思维能力和算法设计思想。能够独立完成简单的算法设计和分析&#xff0c;进一步用他们来解决实际问题&#xf…

谁能成为首个RedCap规模商用的厂商?

RedCap在“降本、小尺寸、低功耗”的呼声中逐渐成为后5G时代的宠儿&#xff0c;随着相关技术的成熟&#xff0c;RedCap如何进一步商用成为行业关注的焦点。RedCap的发展&#xff0c;离不开运营商、芯片厂商、终端厂商、模组厂商等产业关键节点的通力合作。那RedCap离正式商用还…

关于hessian2的一些疑点(0CTF来分析)

目录 前言&#xff1a;csdn很久不用了&#xff0c;打算最近拾起来&#xff0c;主要是监督自己。 非常可疑的点 另一种方法通过JNDI注入来 构造完整的链子 这里&#xff0c;希望佬们解答解答&#xff0c;非常感谢&#xff01;&#xff01;&#xff01; 前言&#xff1a;csdn很…

【C++】开源:cpp-tbox百宝箱组件库

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍cpp-tbox百宝箱组件库。 无专精则不能成&#xff0c;无涉猎则不能通。。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;…

ingress之503问题

ingress之503问题 背景&#xff1a; 部署好应用服务(nsyai-test名称空间下)后&#xff0c;通过ingress做七层反代&#xff0c;浏览器访问域名一直出现503的错误&#xff0c;其中30086端口为ingress-controller控制器nodeport型service端口 问题&#xff1a; 网上查看发现是不…

【http-server】http-server的安装、前端使用http-server启动本地dist文件服务:

文章目录 一、http-server 简介:二、安装node.js:[https://nodejs.org/en](https://nodejs.org/en)三、安装http-server:[https://www.npmjs.com/package/http-server](https://www.npmjs.com/package/http-server)四、开启服务&#xff1a;五、http-server参数&#xff1a;【1…

OpenMMLab MMTracking目标跟踪官方文档学习(一)

介绍 MMTracking 是PyTorch的开源视频感知工具箱。它是OpenMMLab项目的一部分。 它支持 4 个视频任务&#xff1a; 视频对象检测 (VID) 单目标跟踪 (SOT) 多目标跟踪 (MOT) 视频实例分割 (VIS) 主要特点 第一个统一视频感知平台 我们是第一个统一多功能视频感知任务的开源工…

自建DNSlog服务器

DNSlog简介 在某些情况下&#xff0c;无法利用漏洞获得回显。但是&#xff0c;如果目标可以发送DNS请求&#xff0c;则可以通过DNS log方式将想获得的数据外带出来。 DNS log常用于以下情况&#xff1a; SQL盲注无回显的命令执行无回显的SSRF 网上公开提供dnslog服务有很多…