2024年8月

Kotlin 循环

当您处理数组时,经常需要遍历所有元素。

要遍历数组元素,请使用
for
循环和
in
操作符:

示例

输出
cars
数组中的所有元素:

val cars = arrayOf("Volvo", "BMW", "Ford", "Mazda")
for (x in cars) {
  println(x)
}

您可以遍历各种类型的数组。在上面的示例中,我们使用了一个字符串数组。

在下面的示例中,我们遍历一个整数数组:

示例

val nums = arrayOf(1, 5, 10, 15, 20)
for (x in nums) {
  println(x)
}

传统的
For
循环


Java
和其他编程语言不同,
Kotlin
中没有传统的
for
循环。


Kotlin
中,
for
循环用于遍历数组、范围以及其他包含可计数值的事物。

Kotlin 范围

使用
for
循环,您还可以使用
..
创建值的范围:

示例

打印整个字母表:

for (chars in 'a'..'x') {
  println(chars)
}

您还可以创建数字范围:

示例

for (nums in 5..15) {
  println(nums)
}

注意:第一个和最后一个值包含在范围内。

检查值是否存在

您还可以使用
in
操作符检查值是否存在于范围内:

示例

val nums = arrayOf(2, 4, 6, 8)
if (2 in nums) {
  println("存在!")
} else {
  println("不存在。")
}

示例

val cars = arrayOf("Volvo", "BMW", "Ford", "Mazda")
if ("Volvo" in cars) {
  println("存在!")
} else {
  println("不存在。")
}

中断或继续一个范围

您还可以在范围/
for
循环中使用
break

continue
关键字:

示例


nums
等于
10
时停止循环:

for (nums in 5..15) {
  if (nums == 10) {
    break
  }
  println(nums)
}

示例

跳过循环中的
10
并继续下一个迭代:

for (nums in 5..15) {
  if (nums == 10) {
    continue
  }
  println(nums)
}

Kotlin 函数

函数是只有在调用时才运行的一段代码。

您可以将数据(称为参数)传递给函数。

函数用于执行某些操作,它们也被称为方法。

预定义函数

事实证明,您已经知道函数是什么。在本教程中,您一直在使用它!

例如,
println()
是一个函数。它用于向屏幕输出/打印文本:

示例

fun main() {
  println("Hello World")
}

创建您自己的函数

要创建您自己的函数,请使用
fun
关键字,并写出函数名称,后跟括号
()

示例

创建一个名为
myFunction
的函数,该函数应该输出一些文本:

fun myFunction() {
  println("I just got executed!")
}

调用函数

现在您已经创建了一个函数,可以通过调用它来执行。

要在
Kotlin
中调用函数,写出函数名,后跟两个括号
()

在下面的示例中,
myFunction()
被调用时会打印一些文本(操作):

示例

fun main() {
  myFunction() // 调用 myFunction
}

// 输出 "I just got executed!"

如果您愿意,可以多次调用一个函数:

示例

fun main() {
  myFunction()
  myFunction()
  myFunction()
}

// I just got executed!
// I just got executed!
// I just got executed!

函数参数

信息可以作为参数传递给函数。

参数在函数名称之后指定,在括号内。您可以添加任意数量的参数,只需用逗号分隔。请注意,您必须指定每个参数的类型(
Int

String
等)。

下面的示例有一个函数,它接收一个名为
fname
的字符串参数。当调用函数时,我们传递一个名字,它在函数内部用于打印全名:

示例

fun myFunction(fname: String) {
  println(fname + " Doe")
}

fun main() {
  myFunction("John")
  myFunction("Jane")
  myFunction("George")
}

// John Doe
// Jane Doe
// George Doe

当一个参数传递给函数时,它被称为实参。所以,从上面的示例中:
fname
是一个参数,而
John

Jane

George
是实参。

多个参数

您可以拥有任意数量的参数:

示例

fun myFunction(fname: String, age: Int) {
  println(fname + " is " + age)
}

fun main() {
  myFunction("John", 35)
  myFunction("Jane", 32)
  myFunction("George", 15)
}

// John is 35
// Jane is 32
// George is 15

注意:使用多个参数时,函数调用必须具有与参数数量相同的实参,并且实参必须以相同的顺序传递。

返回值

在上面的示例中,我们使用函数输出一个值。在下面的示例中,我们将使用函数返回一个值并将其赋值给一个变量。

要返回一个值,请使用
return
关键字,并在函数的括号后指定返回类型(在此示例中为
Int
):

示例

具有一个
Int
参数和
Int
返回类型的函数:

fun myFunction(x: Int): Int {
  return (x + 5)
}

fun main() {
  var result = myFunction(3)
  println(result)
}

// 8 (3 + 5)

使用两个参数

示例

具有两个
Int
参数和
Int
返回类型的函数:

fun myFunction(x: Int, y: Int): Int {
  return (x + y)
}

fun main() {
  var result = myFunction(3, 5)
  println(result)
// 8 (3 + 5)

返回值的简写语法

还有一种更简短的语法来返回值。您可以使用
=
操作符代替
return
,而无需指定返回类型。
Kotlin
足够聪明,可以自动找出返回类型:

示例

fun myFunction(x: Int, y: Int) = x + y

fun main() {
  var result = myFunction(3, 5)
  println(result)
// 8 (3 + 5)

最后

为了方便其他设备和平台的小伙伴观看往期文章:

微信公众号搜索:
Let us Coding
,关注后即可获取最新文章推送

看完如果觉得有帮助,欢迎 点赞、收藏、关注

书接上回,在文章
《论文图谱当如是:Awesome-Graphs用200篇图系统论文打个样》
中,我向大家讲述了如何用图的方式思考图计算系统的演进历史,并在后续的系列文中结合
Awesome-Graphs
项目解读了数篇图计算领域的经典论文。

这次,我们发布了Awesome-Graphs的新版本(
v1.1.0
),相比于上个版本不仅收录了更完善的论文集,还增加了一系列便捷的交互能力,让图计算技术的学习更轻松:

  • 收录了207个图计算系统,维护了509条引用关系。
  • 补充了189篇论文pdf源文件。(其中有13篇无法在公网找到)
  • 支持论文标题与产品官网一键预览。
  • 支持论文引用和被引用关系一键追溯。

没错,整个项目的核心就一个
html文件
(下载就能用),接下来我们对核心功能逐一展示。

1. 论文图谱,一览无余

下载打开index.html文件,200多篇图计算系统论文图谱,所见即所得。

2. 搜索定位,直达内容

当然,搜索功能自然必不可少。选中节点后,点击论文标题,直达论文文件。

除了论文文件,产品官网必须要有。

3. 正向反向,各得其所

节点上的右键菜单,让论文引用的追根溯源无比轻松。

看看TuGraph Analytics的论文GeaFlow的引用网络。

或者看看TuGraph团队Gemini这篇论文的被引用网络,再也不用一点点Google学术了。

看看上下游的邻居也是“家常便饭”。

4. 提交贡献,两行代码

不要纠结于代码贡献章节描述的内容,大多数情况只需要两行代码:

代码贡献

  • 项目只依赖前端组件
    vis.js
    ,要对图谱数据进行调整,只需要修改
    index.html
    文件中
    nodes

    edges
    的定义。
  • 新增图计算系统论文节点,如
    {id: 'Pregel'},

    • 使用
      _paper
      属性指定论文标题,如
      {id: 'GeaFlow', _paper: 'GeaFlow: A Graph Extended and Accelerated Dataflow System'},
    • 使用
      _website
      属性指定关联网址,如
      {id: 'TuGraph', _website: 'https://tugraph.tech'},
    • 设置
      _type: 'db'
      表示产品节点,如
      {id: 'TuGraph', _type: 'db'},
  • 新增图计算系统论文/产品引用,如
    {from: 'GeaFlow', to: 'TuGraph'},

    • 设置属性
      _bidirectional: true
      表示双向引用。如
      {from: 'GraphChi', to: 'PowerGraph', _bidirectional: true},
      ,表示
      GraphChi

      PowerGraph
      存在互相引用(同时发表于OSDI-2012)。
  • 新增论文统一放到
    papers/<图系统名>.pdf
    路径,并按字母序修改
    docs/graph-system-list.md
    的引用链接。
  • 受人工整理限制,数据中难免出现错误和遗漏,欢迎大家一起贡献勘误和未录入的论文和产品信息。

一行代码填论文,一行代码填引用。

{id: 'TuGraph', _paper: 'TuGraph: Graph Database from Ant Group'},
{from: 'TuGraph', to: 'Pregel'},

5. 文献资源,随用随取

所有的文献资料,都在这里:
https://github.com/TuGraph-family/Awesome-Graphs/blob/master/docs/graph-system-list.md

感兴趣的话统统打包带走,不用客气,最后别忘了给项目一个小
Star
,内容更新通知可以第一时间收到。

一:背景

1. 讲故事

前些天有位朋友找到我,说他们的系统出现了CPU 100%的情况,让我帮忙看一下怎么回事?dump也拿到了,本想着这种情况让他多抓几个,既然有了就拿现有的分析吧。

二:WinDbg 分析

1. 为什么会爆高

既然说是 100%,作为调试者得拿数据说话,可以使用
!tp
来观测一下。


0:000:x86> !tp
CPU utilization: 100%
Worker Thread: Total: 382 Running: 382 Idle: 0 MaxLimit: 8191 MinLimit: 8
Work Request in Queue: 8694
    Unknown Function: 6f62b650  Context: 4a36bbbc
    Unknown Function: 6f62b650  Context: 4a36e1d4
    Unknown Function: 6f62b650  Context: 4a372384
    Unknown Function: 6f62b650  Context: 239adfec
    Unknown Function: 6f62b650  Context: 4a374994
    Unknown Function: 6f62b650  Context: 239b9e14
    Unknown Function: 6f62b650  Context: 2399fd9c
    ...

从卦中看,不得了,CPU 100% 之外,所有的线程池线程全部被打满,
人生自古最忌满,半贫半富半自安
。同时线程池队列还累计了8694个任务待处理,说明这时候的线程池已经全面沦陷,要想找到这个答案,需要用
~*e !clrstack
命令观察每一个线程此时正在做什么,输出如下:


0:000:x86> ~*e !clrstack
OS Thread Id: 0x22f4 (429)
Child SP       IP Call Site
4bc1e060 0000002b [GCFrame: 4bc1e060] 
4bc1e110 0000002b [HelperMethodFrame_1OBJ: 4bc1e110] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
4bc1e19c 24aad7da System.Threading.Monitor.Wait(System.Object, Int32, Boolean)
4bc1e1ac 2376f0d6 ServiceStack.Redis.PooledRedisClientManager.GetClient()
4bc1e1dc 2420bbc6 xxx.Service.CacheService.GetClient()
...
4bc1e234 24206fbe xxxBLL.GetxxxCount(System.Collections.Generic.Dictionary`2<System.String,System.Object>)
4bc1e3e0 216e25f9 DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure, System.Web.Mvc.ControllerBase, System.Object[])
4bc1e3f0 238b86b7 System.Web.Mvc.ActionMethodDispatcher.Execute(System.Web.Mvc.ControllerBase, System.Object[])
...
4bc1eee0 2353d448 System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(IntPtr, IntPtr, IntPtr, Int32)
4bc1efb8 00a9e3c2 [ContextTransitionFrame: 4bc1efb8] 

从卦中可以看到当前有 371个线程在
PooledRedisClientManager.GetClient
中的 Wait 上出不来,那为什么出不来呢?

2. 探究源码

要想找到这个答案,只能从源代码中观察,简化后的代码如下:


    public IRedisClient GetClient()
    {
        lock (writeClients)
        {
            AssertValidReadWritePool();
            RedisClient inActiveWriteClient;
            while ((inActiveWriteClient = GetInActiveWriteClient()) == null)
            {
                if (!Monitor.Wait(writeClients, PoolTimeout.Value))
                {
                    throw new TimeoutException("Redis Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use.");
                }
            }
        }
    }

    private RedisClient GetInActiveWriteClient()
    {
        int num = WritePoolIndex % writeClients.Length;
        for (int i = 0; i < ReadWriteHosts.Count; i++)
        {
            int num2 = (num + i) % ReadWriteHosts.Count;
            RedisEndPoint redisEndPoint = ReadWriteHosts[num2];
            for (int j = num2; j < writeClients.Length; j += ReadWriteHosts.Count)
            {
                if (writeClients[j] != null && !writeClients[j].Active && !writeClients[j].HadExceptions)
                {
                    return writeClients[j];
                }
            }
        }
        return null;
    }

仔细阅读卦中代码,之所以进入Wait主要是因为
GetInActiveWriteClient()
方法返回 null 所致,从异常信息看也知道此时是因为 writeClients 池已满,那这个池是不是满了呢?可以把 writeClients 数组挖出来,使用
!dso
命令。


0:429:x86> !dso
OS Thread Id: 0x22f4 (429)
ESP/REG  Object   Name
...
4BC1E0D0 0ea38d18 ServiceStack.Redis.RedisClient[]
4BC1E100 0ea38bb0 ServiceStack.Redis.PooledRedisClientManager
...

0:429:x86> !da 0ea38d18
Name:        ServiceStack.Redis.RedisClient[]
MethodTable: 237af1c0
EEClass:     0129a224
Size:        52(0x34) bytes
Array:       Rank 1, Number of elements 10, Type CLASS
Element Methodtable: 237ae954
[0] 0ea38dd4
[1] 0a9f9f58
[2] 0296e468
[3] 0c9786a0
[4] 0a9fe768
[5] 04a21f24
[6] 0aa0d758
[7] 10946d90
[8] 04a8c8b0
[9] 02a2a2a0

0:429:x86> !DumpObj /d 0ea38dd4
Name:        ServiceStack.Redis.RedisClient
MethodTable: 237ae954
EEClass:     2375d154
Size:        152(0x98) bytes
File:        C:\Windows\xxx\ServiceStack.Redis.dll
Fields:
...
0129aa48  4000169       7d       System.Boolean  1 instance        1 <Active>k__BackingField
...

从卦中看 writeClients 池只有10个大小,并且都是
Active=1
,所以返回 null 就不足为奇了。

3. 为什么client都在使用中呢

要想找到这个答案,需要看下上层的
xxxBLL.GetxxxCount
方法是如何调用的,为了保护隐私,就多模糊一点。

从图中可以看到,问题出在用 foreach 去不断的迭代 ServiceStack.Redis 导致 writeClient 池耗尽,导致大量的请求在不断的阻塞,不要忘了这里有371个线程在争抢哦,真是大忌。

接下来顺带洞察下这个 foreach 要 foreach 多少次? 继续用 !dso 去挖。


0:429:x86> !DumpObj /d 077cec20
Name:        System.Collections.Generic.List`1[[xxxInfo, xxx]]
MethodTable: 241ad794
EEClass:     0193166c
Size:        24(0x18) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
01860eec  4001891        4     System.__Canon[]  0 instance 077e0048 _items
0129c9b0  4001892        c         System.Int32  1 instance      307 _size
0129c9b0  4001893       10         System.Int32  1 instance      307 _version
01296780  4001894        8        System.Object  0 instance 00000000 _syncRoot
01860eec  4001895        4     System.__Canon[]  0   static  <no information>

从卦中看当前需要循环307次,也就再次验证了池耗尽的说法,我知道心细的朋友肯定会说,卡死这个我认,但能导致 CPU爆高 我就不能理解了,其实你仔细阅读源码就能理解了,这是经典的
锁护送(lock convoy)
现象,因为满足如下两个条件。

  1. 多线程的 foreach 高频调用。
  2. Wait 导致线程暂停进入等待队列。

4. 如何解决这个问题

知道了前因后果,解决起来就比较简单了,三种做法:

  1. 将 foreach 迭代 改成 批量化处理,减少对 writeclient 的租用。
  2. 增加 writeclient 的池大小,官网有所介绍。
  3. ServiceStack.Redis 的版本非常老,又是收费的,最好换掉已除后患。

三:总结

这次生产事故分析还是非常有意思的,一个看似阻塞的问题也会引发CPU爆高,超出了一些人的认知吧,对,其实它就是经典的
lock convoy
现象,大家有任何dump问题可以找我,一如既往的免费分析。

图片名称

前言

WaterCloud 是一个集成了 LayUI 的高效敏捷开发框架,专为 .NET 开发者设计。

它不仅支持多种 .NET 版本(.NET 4.5、.NET Core 3.1、.NET 5、.NET 6),还内置了丰富的功能,如权限管理、流程表单设计以及多数据库支持下的多租户架构。使用了 ORM(SqlSugar 和 Chloe ) 能够轻松应对复杂的数据处理需求。

WaterCloud 基于ASP.NET 6.0 MVC + API + SqlSugar + LayUI的框架,帮我们解决.NET 开发中的重复工作,提升开发效率。

该框架采用了主流的架构模式,易于学习与使用,有效降低了学习成本,欢迎有需求的小伙伴们来试用!

框架介绍

.NET 和 LayUI 集成的最佳实践;

敏捷开发优选框架,自带权限包含字段、数据权限,自带流程表单设计,基于多数据库的多租户等;

项目版本包含.NET 4.5、.NET Core 3.1、.NET 5、.NET 6;

ORM 包含SqlSugar 和 Chloe;

框架特点

  • 完全开源
    : 源代码开放,便于二次开发和定制。
  • 主流技术栈
    : 支持 ASP.NET 6.0、LayUI、SqlSugar 等技术。
  • 多数据库兼容
    : 支持 SQL Server、MySQL 等多种数据库。
  • 模块化设计
    : 层次结构清晰,便于维护和扩展。
  • 内置功能
    : 包括权限管理、数据权限控制、代码生成器等企业级功能。
  • 权限管理
    : 基于 RBAC 的精细权限控制,覆盖从菜单到字段级别的访问控制。
  • 数据权限
    : 精细化的数据权限管理,确保数据安全。
  • 表单设计器
    : 提供直观的拖拽式表单设计工具。
  • 流程设计器
    : 动态设计工作流程,灵活调整业务逻辑。
  • 内容管理
    : 集成 wangEditor 编辑器,方便内容编辑。
  • 文件管理
    : 支持文件上传与下载功能。
  • 响应式布局
    : 支持多种设备访问,包括 PC、平板、手机等。
  • 实用工具
    : 封装了日志记录、缓存管理、数据验证等功能。
  • 多租户支持
    : 基于 Database 的多租户功能。
  • 定时任务
    : 支持基于 Quartz 的定时任务,具备集群能力。
  • 广泛的适用性
    : 可用于开发OA、ERP、BPM、CRM、WMS、TMS、MIS、BI、电商、物流、快递、教务管理系统等各类管理软件。

框架技术栈

前端技术

1、JavaScript 框架

  • jQuery 3.4.1
  • LayUI
  • LayUI mini (开源)

2、图标

  • Font Awesome 4.7.0
  • LayUI 自带图标

3、控件

  • 客户端验证
    : LayUI verify
  • 富文本编辑器
    : wangEditor (开源), LayUI editor
  • 文件上传
    : LayUI upload
  • 动态页签
    : LayUI mini miniTab
  • 数据表格
    : LayUI table, soul-table (已实现后端筛选)
  • 下拉选择框
    : LayUI select, xmselect
  • 树结构控件
    : LayUI dtree
  • 树状表格
    : treetable-lay (兼容 soul-table 组件,修复了固定列等 BUG)
  • 穿梭框
    : LayUI transfer
  • 日期控件
    : LayUI laydate
  • 图标选择
    : LayUI IconPicker
  • 省市区选择
    : LayUI layarea

4、页面布局

  • LayUI
  • LayUI mini

5、图表插件

  • echarts

后端技术

  • 核心框架
    : ASP.NET 6.0, WEB API
  • 定时任务
    : QuartZ (支持 web 控制)
  • 持久层框架

    SqlSugar (支持多种数据库, 复杂查询, 多租户, 分库分表等);
    Chloe (支持多种数据库, 复杂查询, 稳定)
  • 安全支持
    : 过滤器, Sql 注入防护, 防止请求伪造
  • 服务端验证
    : 实体模型验证
  • 缓存框架
    : Redis/Memory (支持单点登录控制)
  • 消息队列
    : RabbitMQ
  • 事件总线
    : Jaina
  • 日志管理
    : 日志记录, 登录日志, 操作日志
  • 工具类
    : MiniExcel, Newtonsoft.Json, 验证码生成, 通用公共类

环境要求

1、VS 2022 及以上版本;

2、ASP.NE 6.0;

3、Mysql 或者 SQL Server 2005及以上版本,database文件夹下有SQL文件可执行;

4、请使用 VS 2022 及以上版本打开解决方案。

5、Redis 和 RabbitMq 在项目文件夹里有

框架演示

1、
演示地址
:
http://47.116.127.212:5000/
(登录: admin / 0000; 数据库每两小时恢复一次)

2、
在线文档
:
https://gitee.com/qian_wei_hong/WaterCloud/wikis/pages

3、
在线项目
:
https://replit.com/@MonsterUncle/WaterCloud

效果页面

1、登录页面

2、首页展示

3、系统管理

4、流程中心

系统还包含了丰富的功能模块,如文件中心、信息中心、内容管理和订单管理等。可以下载源码,并运行体验这些功能。

项目地址

Gitee:
https://gitee.com/qian_wei_hong/WaterCloud

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号
[DotNet技术匠]
社区,与其他热爱技术的同行一起交流心得,共同成长!

前言

数学的学习跟数学的计算是要分开的,现在回头再去看大学的高数和线性代数,如果只是学习的话,其实一门课程3天,也就学完了。
学校的课程之所以上那么久,其实是为了考试,也就是为计算准备的。计算是有意义的,但在有计算机的情况下,计算的意义并不是很大。
所以,如果大学数学没学好,只要花一星期,就能补回来。甚至你没上过大学,只要你上过初中,同样,只需要一个星期就能学会高数和线性代数。
但,但,但,问题是,没有人这样给你上课,也没有这样资料让你学习。至少国内是没有这样学习的信息,国内全是耽误我们学习效率的学习模式。
明明是一个星期的知识,非得浪费我们一年到一年半。而且大学数学学习的计算,也没有法应对考研的题,你想考研还是得报班或者自己再继续学习解题技巧。
总之,大学数学是个即浪费学习时间,又完全没有目的性的扯淡课程。

Gradient

上一篇介绍了一点梯度,正向传播,逆向传播。这里再详细介绍一下。
不要被这些名词吓住了,名词的本质都是总结,而总结的名词,其实是最阻碍我们学习的,我们要讨厌它,但不用害怕它。
先看一下requires_grad这个参数的使用,代码如下:

print("============求梯度1==============")
a = torch.randn(3) #这里是randn 不是rand  torch.randn:生成服从标准正态分布(均值为0,标准差为1)的随机数。  torch.rand:生成服从均匀分布(在区间 [0, 1) 之间)的随机数。
print(a)
b=a+2
print(b) # 输出tensor是a+2计算后的结果
x = torch.randn(3,requires_grad=True) #这里是randn 不是rand
print(x)
y=x+2
print(y)  # 输出tensor是x+2计算后的结果,同时记录了函数,grad_fn=<AddBackward0> 表示是加法函数 grad=gradient  fn=Function

这里a和x分别是开启了requires_grad和没有开始requires_grad的模式。如下图:
image
开启了requires_grad的x,多了一个属性requires_grad=True。
经过y=x+2计算的y,多了一个属性grad_fn=< AddBackward0 >,这里grad=gradient fn=Function,就是梯度函数的意思;里面的Add是加法的意思。
而这个y=x+2,这个计算就是前向传播,前向传播就是这一堆我们定义的函数。

正态分布简介

上面提到了正态分布,这里简单解释一下。
正态分布 若以0为中心,称为均值为0。若均值为0,标准差为1,数据点在不同区间内的分布遵循68-95-99.7规则
均值0,标准差1(数据在68%[-1, 1]95%[-2, 2]99.7%[-3, 3])。即数据以0为中心,向两边扩散,68%的数据点位于均值的1个标准差范围内(即[-1, 1]区间)95%的数据点位于均值的2个标准差范围内(即[-2, 2]区间)。99.7%的数据点位于均值的3个标准差范围内(即[-3, 3]区间)。
均值0,标准差2,数据在68%[-2, 2]95%[-4, 4]99.7%[-6, 6])。

标量函数和逆向传播

上面我们使用了前向传播,并设置y=x+2这样的函数,现在我们在增加一个前向传播函数:z=y * y * 2,然后再设置标量函数,最后在执行逆向传播。
注1:在使用backward前,必须给一个scale value(标量值,即常数C) 比如z=z.mean(),或者给一个权度tensor,这里先介绍传递标量函数。
注2:标量函数就是前向传播中的计算损失的损失函数。
注3:标量函数其实是一个标量,或称常量,或称常数,或称值,或者称一个数。(这里要是说传递的是一个数,那就low了,但要说传了一个标量,就明显较高大上了,这就是名词阻碍我们学习的最完美体现了)。

x = torch.randn(3,requires_grad=True)
print(x)
y=x+2
z=y*y*2
print(z) # 这里会增加属性,grad_fn=<MulBackward0> ,这里的mul表示是乘法
z=z.mean() # 指定标量函数
#这里必须指定标量函数,如果删除z=z.mean() 这句话会提示 grad can be implicitly created only for scalar outputs 
print(z) # 属性grad_fn=<MeanBackward0>,Mean表示平均值函数
z.backward() #逆向传播 如果requires_grad=False,则执行z.backward()回抛异常,因为没有记录grad_fn
print(x.grad)

运行如下图:
image

代码简介如下

x 是启用了自动求导的张量。
y = x + 2,y 仍是一个启用了自动求导的张量。
z = y * y * 2,z 是一个启用了自动求导的张量。
z = z.mean(),z 是标量(因为 mean() 返回的是张量的平均值,结果是标量)。
调用 z.backward(),计算 z 对 x 的梯度,并将其存储在 x.grad 中。
此时,x.grad 中存储的是 z 对 x 的梯度,即 dz/dx;梯度的结构是跟x的结构一样的。

梯度清0

在第二次计算梯度(调用backward())之前需要清零梯度。
如果在第二次调用 backward() 之前没有清零梯度,那么第二次调用 backward() 计算出的梯度会叠加在第一次计算出的梯度上。

print("============清零grad==============")
weights =torch.ones(4,requires_grad=True)
for epoch in range(3):
    model_output =(weights*3).sum()#设置标量值,这里是连写了,分开就是a=weight*3 model_output=a.sum()
    model_output.backward()
    print(model_output.grad)
    model_output.zero_()#可以注释这一行,看看不清零的效果

加权

在计算梯度(调用backward())之前没有设置标量或权度,就会报错。
上面说过了标量,现在来介绍加权。
代码如下:

x = torch.randn(3,requires_grad=True)
print(x)
y=x+2
z=y*y*2
v = torch.tensor([1.0,2.0,3.0],dtype=torch.float32)
z.backward(v)
print(x.grad)

这里再z.backward()调用前,增加了一个赋值权度的操作。
所谓赋值权度就是使用z.backward()时传递一个参数,这个参数就是一个张量(tensor);这个tensor的结构要求和x一样。
在计算梯度时,会把梯度的计算结果,x,权度tensor,全提出来,然后相乘。
因为梯度,x,权度tensor的结构是相同的,对应元素相乘,应该比较好理解。

具体计算

y=x+2,dy/dx=y导=1
z=2y²,dz/dy=z导=4y
dz/dx=(dz/dy) * (dy/dx)=1 * 4y=4y
因为y=x+2所以4y=4(x+2)
加权后是三个元素分别是 1 * 4(x1+2) 2 * 4(x2+2) 3 * 4(x3+2)
带入x即可得到梯度。
如下图,4 * (0.8329+2)=11.3316,下图是11.3317,这里应该是有个进位。
image

传送门:
零基础学习人工智能—Python—Pytorch学习(一)

基础学习就先到这。


注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!



若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

https://www.cnblogs.com/kiba/p/18348414