网站优化 情况,衡阳seo优化推荐,电子商务网站建设的问题,网站建设项目管理基本要求坡凑是喊虽然说是手算#xff0c;但是我还是会写一点 C# 代码#xff0c;避免敲坏了计算器。我和大家保证#xff0c;整个手算过程中#xff0c;最终的计算结果只需要用到初高中知识。推导过程会用到部分高数的知识。我尽量将用到的知识点全列举出来#xff0c;本文对学渣…坡凑是喊虽然说是手算但是我还是会写一点 C# 代码避免敲坏了计算器。我和大家保证整个手算过程中最终的计算结果只需要用到初高中知识。推导过程会用到部分高数的知识。我尽量将用到的知识点全列举出来本文对学渣友好期望能够拿出纸笔和 VisualStudio 的伙伴阅读完本文能够真的理解神经网络BP传播算法是如何计算的看了一下时间今年确实 2025 年而不是 2015 年。在 2025 时还在聊BP 算法实在有点一言难尽。我在 10 多年前尝试写过贴近的程序当时写的时候有一些概念没有理解但代码是写了也能跑甚至于在当时世界上最快的超级计算机跑了我的代码。但不能理解的部分就是不能理解。最近在散步的时候我的伙伴 https://github.com/SeWZC 和我说明白了求偏导的数学意义。我当初高数学了3年别人都是只学一年因为我不断挂科那会以为偏导没有什么于是缺了一个知识点导致我对 BP 的部分理解错误。尽管代码能跑也能符合预期进行训练但里面关于传播算法中如何计算各个权重参数的过程我是不能全说明白的。我最近理解了偏导的数学意义之后再到知乎上阅读了 通俗理解神经网络BP传播算法(https://zhuanlan.zhihu.com/p/24801814 ) 文章尝试按照知乎文章的给定的内容和方法自己手算了一遍我就完全理解了之前我所写的代码了。担心本金鱼会忘了之前的想法或者担心下次和伙伴聊天的时候又说错了我就编写了本文。可以认为本文没有给出比 通俗理解神经网络BP传播算法(https://zhuanlan.zhihu.com/p/24801814 ) 文章更多的内容只是按照我自己的方式一步步推导和计算。阅读完本文预期大家能够对神经网络有了更明了的理解。如果大家还是在迷迷糊糊地训练人工智能做玄学的活那阅读本文可以让大家能够稍微有点落地的感觉至少大概知道简单的 BP 神经网络是如何工作起来的我努力让大家尽量少知识地开局开始之前期望大家已经听说过一些神经网络的概念。这里只要求听说过不解其中的内容也没关系有部分概念我没提到的也许大家看完了自然就能明白或阅读完本文之后再继续在网上搜关键词继续了解。期望听说过的概念有神经网络、图论、矩阵、激活函数、损失函数、权重神经网络可以理解图论上的一张图尽管在几乎所有的科普文章中都会使用到矩阵但从原理上说都是图论的一张图。经费有限有了节省矩阵的出场费本文就不带入矩阵的内容了直接平铺进行计算。本文也不会用到多少图论的知识只是用了一点图论上的概念。本文介绍的手算的神经网络是一个简单的有向图只有5个点。即使没有了解过图论的伙伴也许通过示意图也能够看懂本文的手算内容是假定咱就是一个无情的计算机正在被训练的人工智能。咱的需要计算出图上图论的图的各个点的权重。为了让人类更方便理解以下图中我给定了明确的数值。假定输入是两个数字期望经过咱训练出来的神经网络之后能够获取符合预期的输出数字。这样的模拟情况就能够覆盖非常多的情况了。也许有伙伴此时还会有一些疑惑没关系先带着疑惑看下去吧我也不能一口气全交代清楚情况以下是咱的神经网络图图论的图的示意图其中 a 和 b 两个点负责输入e点负责输出。各个点之间的连接存在权重其示意图如下下图的 wxx 表示的就是各个边的权重。写成 wxx 的形式其实就是在隐含矩阵的概念了只是咱本文这里不需要请出矩阵而已如果真套上矩阵的话这里的权重就是 w行列的表示法即 w21 就是表示第2行第1列的值。这也是一个习惯表示方法假定有输入源 x00.35 x10.9 , 期望的输出是yout0.5。这就是一组训练值当然了在实际的神经网络训练里面肯定会给许多组训练值的。只是现在换成人类手算需要降低难度就只要求训练这一组数据。假定没有神经网络就是一个正常人类在拿到 x00.35 x10.9的输入要求写出一个函数让这个函数能够根据此输入计算出yout0.5的值估计也没那么简单禁止直接写f(x0,x1)0.5这样的拖出去打靶的函数为了更加方便大家理解这里先给各个权重添加随机的初始值。初始值很多时候真的是随机给的依靠的是训练过程中不断算法修改参数从而训练出一个可用的神经网络。咱现在这个神经网络只是期望训练出当拿到 x00.35 x10.9 的输入时能够返回yout0.5的输出。标记了添加了随机权重初始值的示意图如下好的勤奋的伙伴可以开始拿出纸笔在纸上画出以上示意图内容了。云程序猿们没有纸笔那就进入幻想模式我不会让大家不至于说没纸笔就看不下去的对于一个正常的神经网络来说每个点就是一个神经元神经元里面包含了激活函数。在本文这里选定的f(x)激活函数的定义如下世界上的激活函数有许许多多没有说一定要用哪个激活函数大家也可以在阅读完本文之后自己尝试其他的激活函数在本文的 BP 神经网络里面是取所有的输入乘以各自的权重之和进入激活函数计算从而得到输出。我将取所有的输入乘以各自的权重之和记为zn将经过激活函数之后的输出记为yn其示意图如下也许大家看到这里开始有疑惑了凭什么神经网络是这样子的。没为什么最简单的神经网络只需要一个神经元即可但一个神经元能做的事情就太弱了。虽然本文的图图论的图有5个点但实际上 a 和 b 点都是在接收输入实际参与计算只有三个点相对来说已经足够简化了。在阅读完本文之后大家也可以试试玩玩更多点和更多层的神经网络只不过这时候就需要多写写代码啦全靠手算可有点难哦似乎现在这个示意图看起来就有些复杂了但相信如果是一步步看下来的伙伴自然能够很快理解示意图的内容了写到这里咱距离整个简单 BP 神经网络就只剩下 损失函数Loss Function的定义了。简单说明损失函数就是用来度量神经网络输出的值与预期期望值的差异程度的函数损失函数要能反馈结果和预期的距离且还要能够求导有意义。一听到反馈结果和预期的距离大家可能想到的就是直接减就可以了如这样的一个函数Cy2?yout但在后续步骤里一求导就约分没了算出来的C′1自然就无法继续进行下去了。那求差的平方呢如C(y2?yout)2呢这个就好多了加上平方也不用去烦恼绝对值的问题了那自然就求结果和预期的差的平方好了。试试看求导的结果C′2y2?2yout这一求导发现约分出了2的值那索性继续乘以二分之一好了这样求导返回结果刚好就是简单的减法计算机看了非常开森。于是决定出来的损失函数定义如下看到这里相信大家也感受到了照现在的人类的数学能力还不能让大家随意写一个函数就能获取预期的结果。在本文的手算过程中也许大家也就能够理解神经网络的能力边界。所使用的各个函数都有一些原因不一定是期望获取咋样的结果就能用对应的函数还需要所使用的函数刚好从数学上能够帮助后续的计算如此咱的所有内容就都定义完成了。为了防止大家忘记现在的状态我再次放入当前的示意图咱接下来就按照这个意义图开始将自己当成一个神经网络进行手算过程拿出纸笔的伙伴可以将上述的示意图抄在草稿纸上咱现在就来开始第一轮的计算过程。打开了 VisualStudio 的伙伴还请创建好控制台项目开始记录一些代码先是一些初始化的变量或常量的值。为了确保让大家能够校验自己的计算结果内容我这里的代码就写固定值而不是使用随机数这样也好方便纸笔手算的伙伴对比计算差异const double x0 0.35;const double x1 0.9;const double y_out 0.5;var a x0;var b x1;var w11 0.1;var w12 0.8;var w21 0.4;var w22 0.6;var w31 0.3;var w32 0.9;var count 0;看到以上的代码也许有伙伴有疑惑以上代码中的 y_out 是不是不符合 C# 代码规范我这里是借用 Latex 的表示表示 y_out 常量按照 Latex 写法就是 y_out 啦。因此就不要将其改成 yOut 或 YOut 哦来进行一步步的计算过程求z0的值对应的代码实现如下var z0 w11 * a w12 * b;这个过程看起来十分简单就是将所有进入 c 点的输入乘以对应的权重返回的和。同理继续计算d点的z1的值对应的代码如下var z1 w21 * a w22 * b;分别让z0和z1经过f(x)激活函数可分别得到y0和y1的值也许大家用计算器算出来的小数点精度有些差异但只要前面几位差不多就可以对于神经网络来说不用太精确对应的代码如下封装了 F 函数double F(double x){return 1.0 / (1 Math.Pow(Math.E, -x));}var y0 F(z0);var y1 F(z1);继续计算z2的值对应的代码如下var z2 w31 * y0 w32 * y1;让z2经过激活函数可得到y2的值对应的代码如下var y2 F(z2);此时可见这一轮的输出y2距离预期的yout还有点距离。具体度量的距离有多大那就要经过损失函数计算看对应的代码如下var c C(y2);static double C(double y2){return 1.0 / 2 * Math.Pow((y2 - y_out), 2);}完成了第一轮计算接下来就是进行第一轮训练进行 BP 的传播算法。此时在推导计算过程就需要用到偏导的知识了。其中包括了偏导的链式计算和对函数求导的知识。借用从 https://www.cnblogs.com/awei040519/articles/18529084 博客的一张图来说明偏导在这里的意义。在这里的意义就是假定输入到输出中间经过的神经网络的某些权重变量计算之后能够获取预期的输出内容求解神经网络中的权重变量的值。这个求解过程有解即是求让损失函数快速到达一个极小点。在偏导意义里面极大和极小这些极值是相同的从极大到极小的转换只需乘以负一即可没有经过损失函数可不可以可以的但是存在的问题是如果不用随机数随便乱碰那怎么能够知道各个权重应该如何调应该是朝着加号方向调还是减号方向调好呢。假定咱有无穷的计算时间那就不停各个参数加0.00001或0或-0.00001给他全遍历即可这样自然能够获取很不敢说最哈优解。但大家肯定也看出来问题这样的做法需要大量的时间且随着图图论的图上的点越多这个计算时间就会越长且是排列组合的快速加长也许此时只有量子计算机才能计算了。聪明的数学家想到了利用数学上的偏导工具协助更快速求较优解的权重数值解。如上图所示利用偏导数协助求某一个切面上的函数导数的极值方向从而让权重的修改能够更快到达较优解如从 https://zhuanlan.zhihu.com/p/1945144971243000845 拷贝的动图所示读过高数的伙伴也许有疑惑由于偏导只是求一个切面方向上的而整个网络是依靠多个权重一起工作的那是否会导致画出来的函数图实际上有多个极值呢没错这就是局部最优解陷阱。好在本文这里足够简单还不会踩到这个坑。进入局部最优解陷阱时也许经过了很多轮的训练其输出也不能达到预期这时候就是大佬们常说的炼丹结果是废丹了需要重新修改权重为随机数重新炼丹求出来各个权重对应偏导数值之后就可以更新各个权重的值进行下一轮计算。下一轮计算结果输出之后再经过损失函数判断是否结果已经接近了。如果不接近则再次求各个权重对应偏导数值用各个权重对应偏导数值更新各个权重的值再进行下一轮计算。这样的过程就是简化后的 BP 神经网络训练的过程。其计算方式如下以下列举的公式中带“’”的部分表示更新之后的权重的值通过以上公式不难看出其中的核心点就是在于如何求偏导。只要求出偏导了那自然就有了更新权重的数值的方法了开始求偏导准备来修改权重参数。开始之前先简单介绍链式法则的计算方式。在本文用到的偏导知识部分非常少本文也只介绍本文需要用到的部分知识。假定有一个参数 x 计算出了 y 的过程中经过了两个步骤先经过了 g 函数求出了 t 结果。再将 t 结果传入 f 函数从而计算出 y 结果。此时求x的偏导则可用以下的公式可以看到此时将计算拆分为两步先求t的偏导再求x的偏导。如此过程恰好就可以用在神经网络上逐层逐个求偏导且刚好可以实现累加的过程。当然本文为了简单起见不会减少计算过程没有将计算过的值缓存起来省略重新计算的过程但相信大家看到后面一眼就看出来重复部分很简单就可以省略重复计算如求w31的偏导就可以从y2到z2再到w31这样一步步求偏导做乘法如以下示意图所示如此即可得出以下公式这个过程里面各个步骤都是一个简单的确定的函数求偏导过程也就很简单了。不需要说整个一路将 w31带入到最终计算式里面。大家如果好奇真的一路将w31带入表达式里面最终损失函数C有多长且求导稍微困难。通过链式的方式可以进行一级级的计算整体复杂度也能够降低接下来咱来开始一步步求这个偏导的内容相信大家的求导知识还没忘记求导数学意义上就是求速率。如对于常量函数如 y5 的函数其速率就是平的如下图所示自然求出来的导数就是 0 的值求偏导的话只要对求偏导的变量当成变量其他当成常量计算。这也就是为什么求出来的式子里面1/2y2out会是0的值的原因。那对于一次函数呢如y2x 函数呢其导数反映的速率就刚好是变量前面的系数了。如下图所示于是就有那平方呢平方的话可以认为速率需要乘以变量自身几倍方就是几倍速率于是就有回顾一下整条式子算起来就是是不是看到这里感觉损失函数确实不是随便选的刚好这个函数能够非常好的适应求导的结果。求导结果里面非常方便进行计算计算机看了非常开森因为求导结果是两个已经计算出结果的值而且只做减法。在第一轮训练结果里面相信大家能够直接口算结果看吧没骗大家只有过程推导用到了一点高数的知识最终计算都是小学到高中的知识且大部分都是小学知识希望看到这里的大家会有很多信心接下来开始解下一个偏导内容这是最难的一个部分了激活函数求导过程如下咱选用的激活函数都是现成的有很多大佬帮忙求导过了的函数的。如果大家感觉这个过程有点难那也可以跳过直接看求导结果就可以了可以看到激活函数求导结果也是非常开森的计算直接就是 fx·(1-fx) 的值计算非常简单。试试代入公式看起来这也是非常简单的小学数学就能完成计算结果虽然本次计算过程有点难但结果却是简单的。也依然是计算机非常开森的计算表达式都是已知的变量的基础加减乘除计算最后一步的求导就特别简单啦因为只对w31求偏导于是 w32 就可以视为常量。本身y0和y1就是被视为常量直接 w32 乘以 y1就算出0的值。而y0作为w31的系数求导结果就是y0了。这个过程看起来很解压。好了各个式子就在这里求完了将其组合起来换成代码的话其实现如下var dc_dw31 (y2 - y_out) * (y2 * (1 - y2)) * y0;不知道大家这一步求出来的结果是否和我的接近呢第一步的求偏导计算咱算了很久后续的步骤咱就能快非常多了。如对w32求偏导可以看到只有最后一条式子有所不同代码实现如下var dc_dw32 (y2 - y_out) * (y2 * (1 - y2)) * y1;从这里也可以看到对 w31 和 w32 求偏导也只有最后一个乘数不相同而已。对于程序猿来说看到有很多相同的代码本能地自然就会去想优化这是非常正确的那就作为课后作业给到大家去优化啦下一步尝试求w11的偏导这一次链式过程就稍微长了一些了如以下示意图有示意图相信大家很容易就知道了上面这条表达式是如何写出来的。核心关键点就是倒推看看w11在哪一级表达式中然后再继续往上找直到能到达C损失函数里去。这个过程就类似于已知树图论的树进行求到根的路径的过程。求导数的前面两项咱已经在w32求偏导中计算过了其结果如下接下来的各项的求导过程如下将几个表达式合起来即可计算结果表达式虽然长但拆开看却非常清晰如下图所示以上的各个变量参数都是已经计算出结果的其整个表达式最终结果也是非常简单的小学计算题。计算结果是 0.000929 的值写成代码如下var dc_dw11 (y2 - y_out) * (y2 * (1 - y2)) * w31 * (y0 * (1 - y0)) * a;同理来求 w12 的偏导接近和 w11 相同其差别只有最后的一步计算。如下示意图所示可见计算公式为对应的代码实现如下var dc_dw12 (y2 - y_out) * (y2 * (1 - y2)) * w31 * (y0 * (1 - y0)) * b;同理也可以继续求w21的偏导通过以上示意图可知各自代入可得对应的代码如下var dc_dw21 (y2 - y_out) * (y2 * (1 - y2)) * w32 * (y1 * (1 - y1)) * a;按照以上求偏导方式可得对应的代码如下var dc_dw22 (y2 - y_out) * (y2 * (1 - y2)) * w32 * (y1 * (1 - y1)) * b;最后将各个权重减去对应的偏导更新之后的权重我给它上面多标了一瞥对应的代码如下w31 w31 - dc_dw31;w32 w32 - dc_dw32;w11 w11 - dc_dw11;w12 w12 - dc_dw12;w21 w21 - dc_dw21;w22 w22 - dc_dw22;按照更新的值再跑一遍计算可得到y2输出为 0.6820 的值求损失函数为 0.0165 的值可以看出比原来的第一遍计算得到的 0.0181 更小证明更加贴近。好在我过程中顺带编写了代码可以愉快地让计算机帮我跑个100次跑了100次之后的各个值如下w110.099497286575324431w120.79870730833654868w210.35650028026826369w220.48814357783267731w31-0.30047225429264057w320.32533602822418006y20.50076396515258481c2.9182137718196697E-07可见看到此时输出的 y2 已经非常贴近预期的输出了。于是大家可以开森地认为咱手算训练出一个能够当输入为 x00.35 x10.9时获得0.5输出值的简单BP神经网络啦相信看了整个过程的大家也能感受出来本文选用的例子中所涉及的计算是非常简单的。再回头看看总的代码可见实现代码量是非常少的const double x0 0.35;const double x1 0.9;const double y_out 0.5;var a x0;var b x1;var w11 0.1;var w12 0.8;var w21 0.4;var w22 0.6;var w31 0.3;var w32 0.9;var count 0;while (true){var z0 w11 * a w12 * b;var z1 w21 * a w22 * b;var y0 F(z0);var y1 F(z1);var z2 w31 * y0 w32 * y1;var y2 F(z2);var c C(y2);if (c 0.0000001){break;}var dc_dw31 (y2 - y_out) * (y2 * (1 - y2)) * y0;var dc_dw32 (y2 - y_out) * (y2 * (1 - y2)) * y1;var dc_dw11 (y2 - y_out) * (y2 * (1 - y2)) * w31 * (y0 * (1 - y0)) * a;var dc_dw12 (y2 - y_out) * (y2 * (1 - y2)) * w31 * (y0 * (1 - y0)) * b;var dc_dw21 (y2 - y_out) * (y2 * (1 - y2)) * w32 * (y1 * (1 - y1)) * a;var dc_dw22 (y2 - y_out) * (y2 * (1 - y2)) * w32 * (y1 * (1 - y1)) * b;w31 w31 - dc_dw31;w32 w32 - dc_dw32;w11 w11 - dc_dw11;w12 w12 - dc_dw12;w21 w21 - dc_dw21;w22 w22 - dc_dw22;count;}Console.WriteLine(Hello, World!);double F(double x){return 1.0 / (1 Math.Pow(Math.E, -x));}static double C(double y2){return 1.0 / 2 * Math.Pow((y2 - y_out), 2);}也许此时有人会说明明我看到的py代码量更少为什么换成C#的代码量就这么多了别急大家试试看回忆一下上文布置的课后作业。之所以网上看到的很多py实现的版本的代码量少是因为使用了本文没有付出场费的矩阵来帮忙计算许多重复的计算逻辑被封装起来了。试试看给以上的代码进行优化将重复的计算进行合并将重复的结构进行抽象很好此时相信大家一和py代码进行对比就会发现接近相同啦本文代码放在 github 和 gitee 上可以使用如下命令行拉取代码。我整个代码仓库比较庞大使用以下命令行可以进行部分拉取拉取速度比较快先创建一个空文件夹接着使用命令行 cd 命令进入此空文件夹在命令行里面输入以下代码即可获取到本文的代码git initgit remote add origin https://gitee.com/lindexi/lindexi_gd.gitgit pull origin 3621fbea66658c99f11ed4faa28aa60bb5ac4466以上使用的是国内的 gitee 的源如果 gitee 不能访问请替换为 github 的源。请在命令行继续输入以下代码将 gitee 源换成 github 源进行拉取代码。如果依然拉取不到代码可以发邮件向我要代码git remote remove origingit remote add origin https://github.com/lindexi/lindexi_gd.gitgit pull origin 3621fbea66658c99f11ed4faa28aa60bb5ac4466获取代码之后进入 Bp/DewhigarjejelDaykogiqem 文件夹即可获取到源代码