手写西瓜书bp神经网络 mnist10 c#版本

news2024/9/21 2:48:44

本文根据西瓜书第五章中给出的公式编写,书中给出了全连接神经网络的实现逻辑,本文在此基础上编写了Mnist10手写10个数字的案例,网上也有一些其他手写的例子参考。demo使用unity进行编写,方便且易于查错。
该案例仅作为学习,博主也只是业余时间自学一些机器学习知识,欢迎各位路过的大佬留下建议~

测试效果:
在这里插入图片描述

源码下载地址:
https://download.csdn.net/download/grayrail/87802798

1.符号的意义

首先理顺西瓜书第五章中的各符号的意义:

  • x x x 输入的原始值
  • y y y 输出的原始值
  • y ^ \hat{y} y^ 输出的预测值
  • η eta,学习率,取值在0-1之间
  • d d d 输入层神经元的个数
  • q q q 隐层神经元的个数
  • l l l 输出层神经元的个数
  • i i i 输入层相关的下标索引
  • h h h 隐层相关的下标索引
  • j j j 输出层相关的下标索引
  • v i , h v_{i,h} vi,h 输入层隐层神经元的连接权
  • w i , h w_{i,h} wi,h 隐层神经元到输出层神经元的连接权
  • γ h γ_{h} γh gamma, 隐层神经元的阈值(阈值即y=ax+b中的b)
  • θ j θ_{j} θj theta, 输出层神经元的阈值(阈值即y=ax+b中的b)
  • α h α_{h} αh alpha, 隐层接收到的输入,书中公式 α h = ∑ i = 1 d v i , h x i α_{h}=\sum_{i=1}^{d} v_{i,h}x_{i} αh=i=1dvi,hxi
  • β j β_{j} βj beta,输出层接收到的输入,书中公式 β j = ∑ h = 1 q w h , j b h β_{j}=\sum_{h=1}^{q} w_{h,j}b_{h} βj=h=1qwh,jbh
  • b h b_{h} bh 存放隐层经过激活函数后的值,数组长度是隐层长度
  • y h a t s yhats yhats 存放输出层经过激活函数后的值,数组长度是输出层长度,书中没有,但是需要这样一个集合
  • g j g_{j} gj 反向传播g项,数组长度是输出层长度
  • e j e_{j} ej 反向传播e项,数组长度是隐层长度

2.正向传播(ForwardPropagation)

西瓜书第五章直接讲了反向传播,所以在这之前简单讲一下正向传播。

输入层(d)
x1
x2
x3
隐层(q)
可能性1
x1*vi1
x2*vi1
可能性2
x1*vi2
x2*vi2
...
输出层(l)
yhat1
yhat2
yhat3
yhat4

做了一个图讲下常规3层神经网络,输入层通常就是输入的信息,输入层的维度可以是n * 1,隐层用于超参数与中间环节计算,隐层的维度是n * m,n是输入层的数据自身维度,m就是m种可能性,假设隐层的第二个维度是50,就是假设了50种可能性进行训练。

基于此,那么正向传播的流程如下:

  1. 初始化隐层的连接权重v,维度1是输入数据的长度,维度2是有多少种可能性,维度2可以自己填一个适合的值。
  2. 以隐层的长度q进行循环,对每个h维度下的集合进行点乘求和,每个元素 x i x_{i} xi乘以 v i , h v_{i,h} vi,h(即西瓜书中: α h = ∑ i = 1 d v i , h x i α_{h}=\sum_{i=1}^{d} v_{i,h}x_{i} αh=i=1dvi,hxi),然后减去阈值γ并传入激活函数,写入b集合。
  3. 以输出层的长度l进行循环,每个l维度下的存放着所有可能性的点乘结果(博主自己的理解),即西瓜书中的: β j = ∑ i = 1 q w h , j b h β_{j}=\sum_{i=1}^{q} w_{h,j}b_{h} βj=i=1qwh,jbh。然后减去阈值θ并传入激活函数,写入yhats集合。
  4. 可以再对yhats加一个softmax操作,筛选集合中的最大值返回下标索引,即输出结果。

3.反向传播(BackPropagation)

反向传播的难点之一是链式求导,西瓜书中已经帮我们把求导过程写好了,这里我先讲tips,再梳理反向传播流程。

3.1 E的公式乘以1/2的问题

在这里插入图片描述
这个直接问chat gpt:
∂ M S E ∂ w i j = ∂ ∂ w i j [ 1 2 n ∑ k = 1 n ( y k − y ^ k ) 2 ] = − 1 n ( y i − y ^ i ) f ′ ( β j ) x i \frac{\partial MSE}{\partial w_{ij}} = \frac{\partial}{\partial w_{ij}} \left[ \frac{1}{2n} \sum_{k=1}^n (y_k - \hat{y}_k)^2 \right] = -\frac{1}{n}(y_i - \hat{y}_i)f'(\beta_j)x_i wijMSE=wij[2n1k=1n(yky^k)2]=n1(yiy^i)f(βj)xi

ChatGPT:显然,在这个偏导数公式中出现了一个因子 1 n \frac{1}{n} n1
,而这个因子的存在是由于我们将MSE除以了2所致。如果不将MSE除以2,那么这个因子就会变成 1 2 n \frac{1}{2n} 2n1
,这在后续计算中会带来一些不必要的复杂性和麻烦。

3.2 公式梳理

书中的公式有点乱,下面给出按照顺序的梳理图:

3.2.1 W对于E的偏导数

在这里插入图片描述

3.2.2 V对于E的偏导数

在这里插入图片描述
这一部分应该是精髓所在,不是非常了解,不多加评论。

3.2.3 流程梳理

基于此,那么反向传播的流程如下:

  1. 单独对 g g g项求值并存入数组,用真实值 y y y和预测值 y ^ \hat{y} y^带入Sigmoid的导数公式
  2. 单独对 e e e项求值并存入数组
  3. 计算阈值(bias偏置) θ θ θ的delta量并赋值, Δ θ j = − η g j Δθ_{j}=-ηg_{j} Δθj=ηgj
  4. 计算 w w w的delta量并赋值, Δ w h , j = η g j b h Δw_{h,j}=ηg_{j}b_{h} Δwh,j=ηgjbh(注意这里的b是上一层的最终输出,不是bias)
  5. 计算阈值(bias偏置) γ γ γ的delta量并赋值, Δ γ j = − η e h Δγ_{j}=-ηe_{h} Δγj=ηeh
  6. 计算 v v v的delta量并赋值, Δ v i , h = η e h x i Δv_{i,h}=ηe_{h}x_{i} Δvi,h=ηehxi

4.代码实现

以Mnist案例为例,该案例使用神经网络识别27x27像素内图片的0-9个手写数字,接下来给出C#版本的Mnist代码实现,脚本挂载后有3种模式:
在这里插入图片描述

  • Draw Image Mode 用于绘制0-9个数字
  • User Mode 使用已经训练好的神经网络进行数字识别(没有做缓存的功能,需要手动先训练几次)
  • Train Mode 训练模式,DataPath中填入图片路径,图片格式首先取前缀,例如:3_04,表明这个图片真实值数字是3,是第三个数字是4的图片

该案例在西瓜书的基础上又加入了momentum动量、softmax、初始随机值范围修改(-1,1),softmax使用《深度学习入门 基于PYTHON的理论与实现》一书中提供的公式。经过一些轮次训练后的运行结果:
在这里插入图片描述

c#代码如下:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;

public class TestMnist10Watermelon : MonoBehaviour
{
    public enum EMode { Train, DrawImage, User }

    /// <summary>
    /// d个输入神经元
    /// </summary>
    int d;

    /// <summary>
    /// q个隐层神经元
    /// </summary>
    int q;

    /// <summary>
    /// l个输出神经元
    /// </summary>
    int l;

    /// <summary>
    /// 输入层原始值
    /// </summary>
    float[] x;

    /// <summary>
    /// 输入层到隐层神经元的连接权
    /// </summary>
    float[][] v;

    /// <summary>
    /// 缓存上一次的v权值
    /// </summary>
    float[][] lastVMomentum;

    /// <summary>
    /// 隐层神经元到输出层神经元的连接权
    /// </summary>
    float[][] w;

    /// <summary>
    /// 缓存上一次的w权值
    /// </summary>
    float[][] lastWMomentum;

    /// <summary>
    /// 反向传播g项
    /// </summary>
    float[] g;

    /// <summary>
    /// 反向传播e项
    /// </summary>
    float[] e;

    /// <summary>
    /// 隐层接收到的输入(通常List长度是隐层长度)
    /// </summary>
    List<float> b;

    /// <summary>
    /// 输出层接收到的输入(通常List长度是输出层长度)
    /// </summary>
    List<float> yhats;

    /// <summary>
    /// 输出层神经元的阈值
    /// </summary>
    float[] theta;

    /// <summary>
    /// 隐层神经元的阈值
    /// </summary>
    float[] gamma;


    public void Init(int inputLayerCount, int hiddenLayerCount, int outputLayerCount)
    {
        d = inputLayerCount;
        q = hiddenLayerCount;
        l = outputLayerCount;

        x = new float[inputLayerCount];
        b = new List<float>(1024);
        yhats = new List<float>(1024);

        e = new float[hiddenLayerCount];
        g = new float[outputLayerCount];

        v = GenDimsArray(typeof(float), new int[] { q, d }, 0, () => UnityEngine.Random.Range(-1f, 1f)) as float[][];
        w = GenDimsArray(typeof(float), new int[] { l, q }, 0, () => UnityEngine.Random.Range(-1f, 1f)) as float[][];

        lastVMomentum = GenDimsArray(typeof(float), new int[] { q, d }, 0, null) as float[][];
        lastWMomentum = GenDimsArray(typeof(float), new int[] { q, d }, 0, null) as float[][];

        theta = GenDimsArray(typeof(float), new int[] { l }, 0, () => UnityEngine.Random.Range(-1f, 1f)) as float[];
        gamma = GenDimsArray(typeof(float), new int[] { q }, 0, () => UnityEngine.Random.Range(-1f, 1f)) as float[];
    }

    public void ForwardPropagation(float[] input, out int output)
    {
        x = input;

        b.Clear();
        for (int hIndex = 0; hIndex < q; ++hIndex)
        {
            var sum = 0f;
            for (int iIndex = 0; iIndex < d; ++iIndex)
            {
                var u = input[iIndex] * v[hIndex][iIndex];
                sum += u;
            }
            var alpha = sum - gamma[hIndex];
            b.Add(Sigmoid(alpha));
        }

        yhats.Clear();
        for (int jIndex = 0; jIndex < l; ++jIndex)
        {
            var sum = 0f;
            for (int hIndex = 0; hIndex < q; ++hIndex)
            {
                var u = b[hIndex] * w[jIndex][hIndex];
                sum += u;
            }
            var beta = sum - theta[jIndex];
            yhats.Add(Sigmoid(beta));
        }

        var softmaxResult = Softmax(yhats.ToArray());
        for (int i = 0; i < yhats.Count; i++)
        {
            yhats[i] = softmaxResult[i];
        }

        int index = 0;
        float maxValue = -999999f;
        for (int jIndex = 0; jIndex < l; ++jIndex)
        {
            if (yhats[jIndex] > maxValue)
            {
                maxValue = yhats[jIndex];
                index = jIndex;
            }
        }
        output = index;
    }

    public void BackPropagation(float[] correct)
    {
        const float kEta1 = 0.03f;
        const float kEta2 = 0.01f;

        const float kMomentum = 0.3f;

        for (int jIndex = 0; jIndex < l; ++jIndex)
        {
            var yhat = this.yhats[jIndex];
            var y = correct[jIndex];
            g[jIndex] = yhat * (1f - yhat) * (y - yhat);
        }

        for (int hIndex = 0; hIndex < q; ++hIndex)
        {
            var bh = b[hIndex];
            var sum = 0f;
            //这个for循环的内容,个人感觉是精妙之处,可以拿到别的神经元的梯度。
            for (int jIndex = 0; jIndex < l; ++jIndex)
                sum += w[jIndex][hIndex] * g[jIndex];
            e[hIndex] = bh * (1f - bh) * sum;
        }

        for (int jIndex = 0; jIndex < l; ++jIndex)
        {
            theta[jIndex] += -kEta1 * g[jIndex];
        }

        for (int hIndex = 0; hIndex < q; ++hIndex)
        {
            for (int jIndex = 0; jIndex < l; ++jIndex)
            {
                var bh = b[hIndex];
                var delta = kMomentum * lastWMomentum[jIndex][hIndex] + kEta1 * g[jIndex] * bh;
                w[jIndex][hIndex] += delta;
                lastWMomentum[jIndex][hIndex] = delta;
            }
        }

        for (int hIndex = 0; hIndex < q; ++hIndex)
        {
            gamma[hIndex] += -kEta2 * e[hIndex];
        }

        for (int hIndex = 0; hIndex < q; ++hIndex)
        {
            for (int iIndex = 0; iIndex < d; ++iIndex)
            {
                var delta = kMomentum * lastVMomentum[hIndex][iIndex] + kEta2 * e[hIndex] * x[iIndex];
                v[hIndex][iIndex] += delta;
                lastVMomentum[hIndex][iIndex] = delta;
            }
        }
    }

    void Start()
    {
        Init(784, 64, 10);
    }

    EMode mMode;
    int[] mDrawNumberImage;

    string mDataPath;

    float Sigmoid(float val)
    {
        return 1f / (1f + Mathf.Exp(-val));
    }

    float[] Softmax(float[] inputs)
    {
        float[] outputs = new float[inputs.Length];
        float maxInput = inputs.Max();

        for (int i = 0; i < inputs.Length; i++)
        {
            outputs[i] = Mathf.Exp(inputs[i] - maxInput);
        }

        float expSum = outputs.Sum();
        for (int i = 0; i < outputs.Length; i++)
        {
            outputs[i] /= expSum;
        }

        return outputs;
    }

    float[] GetOneHot(string input)
    {
        if (input.StartsWith("0"))
            return new float[] { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
        if (input.StartsWith("1"))
            return new float[] { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
        if (input.StartsWith("2"))
            return new float[] { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 };
        if (input.StartsWith("3"))
            return new float[] { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 };
        if (input.StartsWith("4"))
            return new float[] { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 };
        if (input.StartsWith("5"))
            return new float[] { 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 };
        if (input.StartsWith("6"))
            return new float[] { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 };
        if (input.StartsWith("7"))
            return new float[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 };
        if (input.StartsWith("8"))
            return new float[] { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 };
        else
            return new float[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
    }

    void Shuffle<T>(List<T> cardList)
    {
        int tempIndex = 0;
        T temp = default;
        for (int i = 0; i < cardList.Count; ++i)
        {
            tempIndex = UnityEngine.Random.Range(0, cardList.Count);
            temp = cardList[tempIndex];
            cardList[tempIndex] = cardList[i];
            cardList[i] = temp;
        }
    }

    /// <summary>
    /// 快速得到多维数组
    /// </summary>
    Array GenDimsArray(Type type, int[] sims, int deepIndex, Func<object> initFunc = null)
    {
        if (deepIndex < sims.Length - 1)
        {
            var sub_template = GenDimsArray(type, sims, deepIndex + 1, null);
            var current = Array.CreateInstance(sub_template.GetType(), sims[deepIndex]);

            for (int i = 0; i < sims[deepIndex]; ++i)
            {
                var sub = GenDimsArray(type, sims, deepIndex + 1, initFunc);
                current.SetValue(sub, i);
            }

            return current;
        }
        else
        {
            var arr = Array.CreateInstance(type, sims[deepIndex]);
            if (initFunc != null)
            {
                for (int i = 0; i < arr.Length; ++i)
                    arr.SetValue(initFunc(), i);
            }

            return arr;
        }
    }

    void OnGUI()
    {
        if (mDrawNumberImage == null)
            mDrawNumberImage = new int[784];

        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Draw Image Mode"))
        {
            mMode = EMode.DrawImage;

            Array.Clear(mDrawNumberImage, 0, mDrawNumberImage.Length);
        }
        if (GUILayout.Button("User Mode"))
        {
            mMode = EMode.User;

            Array.Clear(mDrawNumberImage, 0, mDrawNumberImage.Length);
        }
        if (GUILayout.Button("Train Mode"))
        {
            mMode = EMode.Train;
            mDataPath = Directory.GetCurrentDirectory() + "/TrainData";
        }
        GUILayout.EndHorizontal();

        var lastRect = GUILayoutUtility.GetLastRect();

        switch (mMode)
        {
            case EMode.Train:
                {
                    GUILayout.BeginHorizontal();
                    GUILayout.Label("Data Path: ");
                    mDataPath = GUILayout.TextField(mDataPath);
                    GUILayout.EndHorizontal();

                    if (GUILayout.Button("Train 10"))
                    {
                        var files = Directory.GetFiles(mDataPath);
                        List<(string, float[])> datas = new(512);
                        for (int i = 0; i < files.Length; ++i)
                        {
                            var strArr = File.ReadAllText(files[i]).Split(',');
                            datas.Add((Path.GetFileNameWithoutExtension(files[i]), Array.ConvertAll(strArr, m => float.Parse(m))));
                        }

                        for (int s = 0; s < 10; ++s)
                        {
                            Shuffle(datas);

                            for (int i = 0; i < datas.Count; ++i)
                            {
                                ForwardPropagation(datas[i].Item2, out int output);
                                UnityEngine.Debug.Log("<color=#00ff00> Input Number: " + datas[i].Item1 + " output: " + output + "</color>");
                                BackPropagation(GetOneHot(datas[i].Item1));
                                //break;
                            }
                        }
                    }
                }
                break;
            case EMode.DrawImage:
                {
                    lastRect.y += 50f;
                    var size = 20f;
                    var spacing = 2f;
                    var mousePosition = Event.current.mousePosition;
                    var mouseLeftIsPress = Input.GetMouseButton(0);
                    var mouseRightIsPress = Input.GetMouseButton(1);
                    var containSpacingSize = size + spacing;

                    for (int y = 0, i = 0; y < 27; ++y)
                    {
                        for (int x = 0; x < 27; ++x)
                        {
                            var rect = new Rect(lastRect.x + x * containSpacingSize, lastRect.y + y * containSpacingSize, size, size);
                            GUI.DrawTexture(rect, mDrawNumberImage[i] == 1 ? Texture2D.blackTexture : Texture2D.whiteTexture);

                            if (rect.Contains(mousePosition))
                            {
                                if (mouseLeftIsPress)
                                    mDrawNumberImage[i] = 1;
                                else if (mouseRightIsPress)
                                    mDrawNumberImage[i] = 0;
                            }

                            ++i;
                        }
                    }

                    if (GUILayout.Button("Save"))
                    {
                        File.WriteAllText(Directory.GetCurrentDirectory() + "/Assets/tmp.txt", string.Join(",", mDrawNumberImage));
                    }
                }
                break;
            case EMode.User:
                {
                    lastRect.y += 150f;
                    var size = 20f;
                    var spacing = 2f;
                    var mousePosition = Event.current.mousePosition;
                    var mouseLeftIsPress = Input.GetMouseButton(0);
                    var mouseRightIsPress = Input.GetMouseButton(1);
                    var containSpacingSize = size + spacing;

                    for (int y = 0, i = 0; y < 27; ++y)
                    {
                        for (int x = 0; x < 27; ++x)
                        {
                            var rect = new Rect(lastRect.x + x * containSpacingSize, lastRect.y + y * containSpacingSize, size, size);
                            GUI.DrawTexture(rect, mDrawNumberImage[i] == 1 ? Texture2D.blackTexture : Texture2D.whiteTexture);

                            if (rect.Contains(mousePosition))
                            {
                                if (mouseLeftIsPress)
                                    mDrawNumberImage[i] = 1;
                                else if (mouseRightIsPress)
                                    mDrawNumberImage[i] = 0;
                            }

                            ++i;
                        }
                    }

                    if (GUILayout.Button("Recognize"))
                    {
                        ForwardPropagation(Array.ConvertAll(mDrawNumberImage, m => (float)m), out int output);
                        Debug.Log("output: " + output);
                    }
                    break;
                }
        }
    }
}

参考文章

  • Java实现BP神经网络MNIST手写数字识别https://www.cnblogs.com/baby7/p/java_bp_neural_network_number_identification.html
  • 反向传播算法对照 https://zhuanlan.zhihu.com/p/605765790

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

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

相关文章

Linux网络——shell编程之免交互

Linux网络——shell编程之shell编程之免交互 一、概述1.常用的交互程序&#xff1a;2.语法格式&#xff1a; 二、Here Document常规用法1.read 命令的读取2.wc -l 的内容行数统计3.passwd用户密码的修改4.cat 查看内容并输出到文件中5.cat 查看交互内容并输出到新的文件中6.交互…

【计算机系统基础4】程序的机器级表示

4.程序的机器级表示 4.1&#xff08;&#x1f3eb; CMU补充 &#xff09;x86-64 Linux 寄存器使用 %rax 返回值调用函数保存可以通过程序修改 rdi&#xff0c;…&#xff0c;%r9 传入参数&#xff08;arguments&#xff09;调用函数保存可通过程序进行修改 %r10&#xff0c;%…

【网络协议详解】——VLAN技术(学习笔记)

目录 &#x1f552; 1. VLAN介绍&#x1f558; 1.1 目标&#x1f558; 1.2 帧格式&#x1f558; 1.3 划分方式&#x1f558; 1.4 链路&#x1f558; 1.5 端口模式&#x1f564; 1.5.1 接入端口&#x1f564; 1.5.2 干道端口&#x1f564; 1.5.3 混合端口&#xff08;仅华为交换…

信息收集-端口

&#xff08;一&#xff09;端口号 端口号&#xff0c;是指在Internet传输控制协议&#xff08;TCP&#xff09;或用户数据报协议&#xff08;UDP&#xff09;中&#xff0c;用于标识具体应用程序与计算机之间通信的端口号码 互联网上有许多使用TCP和UDP协议进行通信的应用程…

【网络协议详解】——STP技术(学习笔记)

目录 &#x1f552; 1. STP技术工作原理&#x1f552; 2. BPDU报文&#x1f558; 2.1 配置BPDU&#x1f558; 2.2 TCN BPDU &#x1f552; 3. 实验&#xff1a;了解STP生成过程 &#x1f552; 1. STP技术工作原理 以太网交换机使用生成树协议STP&#xff08;Spanning Tree Pro…

连续签到积分兑换试用流量主小程序开发

每日签到积分兑换试用流量主小程序开发 打卡兑奖小程序。用户签到活得积分。积分可以兑换商品。观看激励视频广告可以积分翻倍。 用户可以参加试用商品活动参加试用需要提交信息。可以通过分享方式直接获取试用资格。 以下是流量主小程序的功能列表&#xff1a; 广告位管理&a…

JavaWeb——HTTP 协议的基本格式和 fiddler 的用法

目录 一、HTTP定义 二、HTTP协议的工作流程 三、抓包工具Fiddler的用法 1、介绍 2、原理 3、抓包结果 &#xff08;1&#xff09;、HTTP请求 &#xff08;2&#xff09;、HTTP响应 四、HTTP协议的格式 1、HTTP请求 &#xff08;1&#xff09;、请求行 &#xff08;2…

OpenAPI的签名校验

前言 作为一个后端开发&#xff0c;提供API接口或者对接第三方API接口的时候&#xff0c;考虑接口的防刷、重放等安全问题&#xff0c;严格意义上&#xff0c;都需要加上双方约定的签名规则。 大致思路 一般情况下&#xff0c;签名规则没有墨守成规的规定&#xff0c;只要考…

MediaPipe Face Detection可运行在移动设备上的亚毫秒级人脸检测

MediaPipe人脸检测 MediaPipe人脸检测是一种超快速的人脸检测解决方案,具有6个界标和多人脸支持。它基于BlazeFace,BlazeFace是为移动GPU推理量身定制的轻巧且性能良好的面部检测器。检测器的超实时性能使其可应用于需要准确地关注面部区域作为其他任务特定模型: 例如 1、3…

如何有效解决企业文件安全事件频发问题?

企业文件安全是企业必须解决的一个关键问题。随着数字化趋势的不断发展&#xff0c;企业严重依赖于以电子格式存储和访问数据。这种转变使得组织必须实施适当的安全协议&#xff0c;以确保其敏感数据免受未经授权的访问或盗窃。 企业网盘的使用已经在公司中流行起来&#xff0c…

ChatGPT:3. 使用OpenAI创建自己的AI网站:2. 使用 flask web框架快速搭建网站主体

使用OpenAI创建自己的AI网站 如果你还是一个OpenAI的小白&#xff0c;有OpenAI的账号&#xff0c;但想调用OpenAI的API搞一些有意思的事&#xff0c;那么这一系列的教程将仔细的为你讲解如何使用OpenAI的API制作属于自己的AI网站。博主只能利用下班时间更新&#xff0c;进度慢…

mybatis是如何集成到spring的之SqlSessionFactoryBean

文章目录 1 前言1.1 集成spring前使用mybatis的方式1.2 集成mybatis到spring的关键步骤 2 SqlSessionFactoryBean对象分析2.1 buildSqlSessionFactory做了什么事情&#xff1f;2.2 为什么是SqlSessionFactoryBean却可以使用SqlSessionFactory&#xff1f; 3 验证demo4 举一反三…

【QT】自定义工程封装成DLL并如何调用(带ui界面的)

一、动态库的封装 1、首先新建一个Library工程 2、修改类型为共享库&#xff0c;自定义项目名称以及项目路径 3、选择编译器 4、选择动态库所需要的模块 5、自定义类名&#xff0c;点击下一步 6、点击下一步 7、项目总览 8、此时的文件中还没有ui文件&#xff0c;因为要封装带…

南京邮电大学算法与设计实验四:回溯法(最全最新,与题目要求一致)

要求用回溯法求解8-皇后问题&#xff0c;使放置在8*8棋盘上的8个皇后彼此不受攻击&#xff0c;即&#xff1a;任何两个皇后都不在同一行、同一列或同一斜线上。请输出8皇后问题的所有可行解。 用回溯法编写一个递归程序解决如下装载问题&#xff1a;有n个集装箱要装上2艘载重分…

pg事务:隔离级别(2)

事务隔离级别的历史 ANSI SQL-92定义的隔离级别和异常现象确实对数据库行业影响深远&#xff0c;甚至30年后的今天&#xff0c;绝大部分工程师对事务隔离级别的概念还停留在此&#xff0c;甚至很多真实的数据库隔离级别实现也停留在此。但后ANSI92时代对事物隔离有许多讨论甚至…

【5.20】五、安全测试——渗透测试

目录 5.3 渗透测试 5.3.1 什么是渗透测试 5.3.2 渗透测试的流程 5.3 渗透测试 5.3.1 什么是渗透测试 渗透测试是利用模拟黑客攻击的方式&#xff0c;评估计算机网络系统安全性能的一种方法。这个过程是站在攻击者角度对系统的任何弱点、技术缺陷或漏洞进行主动分析&#x…

如何在项目管理中实现任务活动的留痕管理?

项目工作为什么需要留痕呢? 1&#xff0c;记录项目工作&#xff1a;在项目管理工作中常常涉及多部门协作&#xff0c;工作留痕可以帮助我们有效复原已经发生了的工作活动&#xff0c;从而留下印迹供日后查证。 2&#xff0c;支撑工作复盘&#xff1a;在项目工作结束之后&…

SpringBoot程序启动速度提速分析

传统的破程序&#xff08;百万行级一个微服务&#xff09;&#xff0c;在我的P15-gen2代电脑上启动一次需要80秒左右(直接运行三次&#xff0c;取平均值&#xff09;&#xff0c;在其它人电脑上可想而知了。 大概记录几点 1 优化肯定是需要找工具观察的&#xff0c;不观测还…

MyBatis、MyBatis-plus

文章目录 MyBatis一、MyBatis简介1. 什么是MyBatis2. MyBatis开发步骤3. 环境搭建4. MyBatis的映射文件&#xff08;UserMapper)5. 动态sql语句6. MyBatis的增删改查 二、MyBatis核心配置文件&#xff08;sqlMapConfig&#xff09;1. MyBatis常用配置1&#xff09;environments…

使用JAVA代码实现跳动爱心(脱单节程序员必备哦)

520&#xff01;&#xff01;&#xff01;表白日&#xff0c;你脱单了吗&#xff1f;你跟对象彻夜不归了吗&#xff1f; 如果没有说明&#xff0c;你的诚心不够&#xff0c;来给对象一个代码表白吧&#xff01; 话不多说&#xff0c;先上效果图&#xff1a; 实现代码如下&…