技术

数据湖 高性能计算与存储 Linux2.1.13网络源代码学习 《大数据经典论文解读》 三驾马车学习 Spark 内存管理及调优 Yarn学习 从Spark部署模式开始讲源码分析 容器狂占内存资源怎么办? 多角度理解一致性 golang io使用及优化模式 Flink学习 c++学习 学习ebpf go设计哲学 ceph学习 学习mesh kvm虚拟化 学习MQ go编译器以及defer实现 学习go 为什么要有堆栈 汇编语言 计算机组成原理 运行时和库 Prometheus client mysql 事务 mysql 事务的隔离级别 mysql 索引 坏味道 学习分布式 学习网络 学习Linux go堆内存分配 golang 系统调用与阻塞处理 Goroutine 调度过程 重新认识cpu mosn有的没的 负载均衡泛谈 单元测试的新解读 《Redis核心技术与实现》笔记 《Prometheus监控实战》笔记 Prometheus 告警学习 calico源码分析 对容器云平台的理解 Prometheus 源码分析 并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go channel codereview gc分析 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes扩缩容 神经网络模型优化 直觉上理解深度学习 如何学习机器学习 TIDB源码分析 什么是云原生 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 共识算法 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全访问机制 jvm crash分析 Prometheus 学习 Kubernetes监控 容器日志采集 Kubernetes 控制器模型 容器狂占资源怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes类型系统 源码分析体会 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 jib源码分析之细节 线程排队 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI——容器网络是如何打通的 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 精简代码的利器——lombok 学习 《深入剖析kubernetes》笔记 编程语言那些事儿 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 如何分发计算 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 calico学习 AQS——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记 log4j学习 为什么netty比较难懂? 递归、回溯、动态规划 apollo client源码分析及看待面向对象设计 学习并发 docker运行java项目的常见问题 OpenTSDB 入门 spring事务小结 分布式事务 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型和jvm内存布局 java exception Linux IO学习 netty内存管理 测试环境docker化实践 netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker namespace和cgroup zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 深度学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM执行 git maven/ant/gradle/make使用 再看tcp kv系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 Go基础 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

架构

controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 容器和CPU那些事儿 kubevela源码分析 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群及clusternet学习 AutoML和AutoDL 特征平台 实时训练 分布式链路追踪 helm tensorflow原理——python层分析 如何学习tensorflow 数据并行——allreduce 数据并行——ps 机器学习中的python调用c 机器学习训练框架概述 embedding的原理及实践 tensornet源码分析 大模型训练 X的生成——特征工程 tvm tensorflow原理——core层分析 模型演变 《深度学习推荐系统实战》笔记 keras 和 Estimator tensorflow分布式训练 分布式训练的一些问题 基于Volcano的弹性训练 图神经网络 pytorch弹性分布式训练 从混部到统一调度 RNN pytorch分布式训练 CNN 《动手学深度学习》笔记 pytorch与线性回归 多活 volcano特性源码分析 推理服务 kubebuilder 学习 mpi 学习pytorch client-go学习 tensorflow学习 提高gpu 利用率 GPU与容器的结合 GPU入门 AI云平台梳理 tf-operator源码分析 k8s批处理调度 喜马拉雅容器化实践 Kubernetes 实践 学习rpc BFF openkruise学习 可观察性和监控系统 基于Kubernetes选主及应用 《许式伟的架构课》笔记 Admission Controller 与 Admission Webhook 发布平台系统设计 k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 controller 组件介绍 openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? 《软件设计之美》笔记 mecha 架构学习 Kubernetes events学习及应用 CRI 资源调度泛谈 业务系统设计原则 grpc学习 元编程 以应用为中心 istio学习 下一代微服务Service Mesh 《实现领域驱动设计》笔记 概率论 serverless 泛谈 《架构整洁之道》笔记 处理复杂性 那些年追过的并发 服务器端编程 网络通信协议 架构大杂烩 如何学习架构 《反应式设计模式》笔记 项目的演化特点 反应式架构摸索 函数式编程的设计模式 服务化 ddd反模式——CRUD的败笔 研发效能平台 重新看面向对象设计 业务系统设计的一些体会 函数式编程 《左耳听风》笔记 业务程序猿眼中的微服务管理 DDD实践——CQRS 项目隔离——案例研究 《编程的本质》笔记 系统故障排查汇总及教训 平台支持类系统的几个点 代码腾挪的艺术 abtest 系统设计汇总 《从0开始学架构》笔记 初级权限系统设计 领域驱动理念入门 现有上传协议分析 移动网络下的文件上传要注意的几个问题 推送系统的几个基本问题 做配置中心要想好的几个基本问题 不同层面的异步 分层那些事儿 性能问题分析 当我在说模板引擎的时候,我在说什么 用户认证问题 资源的分配与回收——池 消息/任务队列

标签

controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 容器和CPU那些事儿 kubevela源码分析 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群及clusternet学习 helm 从混部到统一调度 volcano特性源码分析 kubebuilder 学习 client-go学习 tf-operator源码分析 k8s批处理调度 喜马拉雅容器化实践 Kubernetes 实践 openkruise学习 基于Kubernetes选主及应用 Admission Controller 与 Admission Webhook k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 controller 组件介绍 openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? Kubernetes events学习及应用 CRI 资源调度泛谈 如何学习Kubernetes 以应用为中心 kubernetes operator kubernetes扩缩容 serverless 泛谈 自定义CNI IPAM docker和k8s安全访问机制 Kubernetes监控 Kubernetes 控制器模型 Kubernetes资源调度——scheduler Kubernetes类型系统 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 Kubernetes源码分析——从kubectl开始 kubernetes yaml配置 CNI——容器网络是如何打通的 当我在说PaaS时,我在说什么 《深入剖析kubernetes》笔记 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件

直觉上理解深度学习

2019年08月31日

简介

《深入浅出Pytorch》理论上,根据万能近似定理(Universal Approximation Theorem)3,对于多层感知机来说,只要参数和隐含层的数量足够,就能够拟合任意的连续函数。此时,多层感知机是模型,模型固定了,就得调整参数 来使模型能够尽量拟合真实世界,调参问题 ==> 求损失函数最小值/梯度下降 ==> 复杂函数对参数微分 ==> 自动微分 ==> 正向传播+反向传播。

学习路径上,先通过单层神经网络(线性回归、softmax回归)理解基本原理,再通过两层感知机理解正向传播和反向传播,增加层数可以增强表现能力,增加特殊的层来应对特定领域的问题。

典型的监督学习由3部分组成:模型、损失函数和优化算法。在数据中发现潜在的模式就是人们常说的模型。有的模型可以通过解析式精确定义,如线性回归、逻辑回归、高斯混合模型等;有的模型则不能,如多层感知机、深度神经网络等。模型训练的过程将模型、损失函数和优化算法 三个部分联系起来,模型f(x)可以千变万化,但本质都是输入数据为x,输出推理值为y的数学函数。损失函数的作用是定量描述推理值y与真实值y_ 的不一致程度,即求得损失值loss。利用损失值和模型的拓扑结构,可以计算出模型参数的梯度,优化算法以一种高效而合理的方式将梯度值更新到对应的模型参数,完成模型的进一步迭代训练。

线性回归

很多人工智能问题可以理解为分类问题,比如判断一个邮件是否为垃圾邮件,根据身高体重预测一个人的性别,当我们将目标特征数值化之后,可以将目标映射为n维度空间上的一个点

从几何意义上来说,分类问题就是 在n维空间上将 一系列点 划分成 不同的集合,以二分为例

  1. 对于二维空间,分类器就是一条线,直线or 曲线
  2. 对于三维空间,分类器就是一个面,平面or 曲面
  3. 对于高维空间,xx

我们要做的就是根据一系列样本点,找到这条“分界线/面”。线性回归假设特征和结果满足线性关系,线性关系的表达能力非常强大,通过对所有特征的线性变换加一次非线性变换,我们可以划一条“近似的分界线/面”,虽然并不完全准确,但大部分时候已堪大用。就像数学中的泰特展开式一样,不管多么复杂的函数,泰勒公式可以用这些导数值做系数构建一个多项式来近似函数在这一点的邻域中的值。而不同的函数曲线其实就是这些基础函数的组合,理所当然也可以用多项式去趋近。

正向传播计算loss

【机器学习】代价函数(cost function)给出一个输入数据,我们的算法会通过一系列的过程得到一个估计的函数,这个函数有能力对没有见过的新数据给出一个新的估计,也被称为构建一个模型。假设有训练样本(x, y),数据有两个特征x1,x2,模型为h,参数为θ,$\theta=(w1,w2,b)$,估计函数$h(x)=g(θ^T x)=g(w_1x_1+w_2x_2+b)$。即对x1,x2 施加一次线性变换 + 非线性变换,($θ^T$表示θ的转置)。因为x1,x2 都是已知的,所以h(θ) 是一个关于θ的函数

两层神经网络的表示(不同文章的表示用语略微有差异),Z = WX + B Z 可以看做是X经过 线性变换W后的表示。

反向传播计算gradient

反向传播的思路:

  1. 概况来讲,任何能够衡量模型预测出来的值h(θ)与真实值y之间的差异的函数都可以叫做代价函数C(θ),如果有多个样本,则可以将所有代价函数的取值求均值,记做J(θ)。因此很容易就可以得出以下关于代价函数的性质:

    • 对于每种算法来说,代价函数不是唯一的;
    • 代价函数是参数θ的函数;
    • 总的代价函数J(θ)可以用来评价模型的好坏,代价函数越小说明模型和参数越符合训练样本(x, y);
    • J(θ)是一个标量;
  2. 当我们确定了模型h,后面做的所有事情就是训练模型的参数θ。那么什么时候模型的训练才能结束呢?这时候也涉及到代价函数,由于代价函数是用来衡量模型好坏的,我们的目标当然是得到最好的模型(也就是最符合训练样本(x, y)的模型)。因此训练参数的过程就是不断改变θ,从而得到更小的J(θ)的过程。理想情况下,当我们取到代价函数J的最小值时,就得到了最优的参数θ.例如,J(θ) = 0,表示我们的模型完美的拟合了观察的数据,没有任何误差。
  3. 代价函数衡量的是模型预测值h(θ) 与标准答案y之间的差异,所以总的代价函数J是h(θ)和y的函数,即J=f(h(θ), y)。又因为y都是训练样本中给定的,h(θ)由θ决定,所以,最终还是模型参数θ的改变导致了J的改变。
  4. 在优化参数θ的过程中,最常用的方法是梯度下降,这里的梯度就是代价函数J(θ)对θ1, θ2, ..., θn的偏导数。由于需要求偏导,我们可以得到另一个关于代价函数的性质:选择代价函数时,最好挑选对参数θ可微的函数(全微分存在,偏导数一定存在)

如何让计算机 求复杂函数的(偏)导数

用pytorch 代码来观察反向传播

y1 = x * w1 + b1
y2 = y1 * w2 + b2
dy2_dy1 = autograd.grad(y2,[y1],retain_graph=True)[0]
dy1_dw1 = autograd.grad(y1,[w1],retain_graph=True)[0]
dy2_dw1 = autograd.grad(y2,[w1],retain_graph=True)[0]

dy2_dw1 可以用公式 推导直接算出来, 也可以 根据 $\frac{dy2}{dw1} = \frac{dy2}{dy1} * \frac{dy1}{dw1}$ 先计算 $\frac{dy2}{dy1}$ ,再使用 $\frac{dy2}{dy1} * \frac{dy1}{dw1}$ 计算 $\frac{dy2}{dw1}$。$\frac{dy2}{dw1}$ 偏导数的计算 先用到了 $\frac{dy2}{dy1}$,此为“反向”。y1 将 y2 对自己的梯度/导数 $\frac{dy2}{dy1}$ 和自己 对w1 梯度 $\frac{dy1}{dw1}$ 传给 w1,w1 将这两者 相乘 即可得到 y2(output) 对自己的梯度, 传播的是 output 对 y1 中间值的 偏导数,此为“反向传播”。PS: 对应到 《用python实现深度学习框架》,从计算图中作为结果的节点开始,依次从后向前,每个节点都将结果 对自己的雅克比矩阵 和 自己对父节点的雅克比矩阵 传给父节点,根据链式法则,父节点将这二者想乘就得到 结果对自己的雅克比矩阵。

优化算法:梯度下降法 ==> 求使得J极小的(w1,w2,b)

优化背后的数学基础任何机器学习算法的目标都是最小化成本函数,找到能得到最低 Y值的 X 值。如果成本函数是 Y=X*X 的形式,是一个抛物线,定位其最小值很容易,但在更高维度上情况却非如此。在这些情况下,我们需要设计一个能定位最小值的算法,这个算法就是梯度下降,是一种用于寻找函数最小值的迭代式优化算法。

  1. 从简单的地方开始。假设要最大化单变量函数。导数即某点切线的斜率,斜率为正,直线向上走;斜率为负,直线向下走。绝对值越大,直线越陡。如果想要到达山顶,应该沿切线上升的方向前进。如果切线的斜率较大,可以大步迈进;如果斜率接近零,应该小步小步往上爬,以免越过峰值。
  2. 两个变量的函数,图像是一个曲面,很难定义切线的概念,用到了切平面,每个偏导数表示切平面上的一个方向。最陡的方向根据梯度确定,梯度为两个偏导数构成的向量。 所谓的梯度下降,如果要求函数最小值,就要沿负梯度的方向迈出一步,也就是下降最陡的方向。PS:梯度决定方向,学习率决定走多远,迭代式走到最小值(梯度为0)。

优化算法通常采用迭代方式实现:首先设定一个初始的可行解,然后基于特定函数反复重新计算可行解,直到找到一个最优解或达到预设的收敛条件。不同的优化算法采用的迭代策略各有不同:

  1. 使用目标函数的一阶导数,如梯度下降法。新解 = 老解 + 学习率 * 梯度
  2. 使用目标函数的二阶导数,如牛顿法
  3. 使用前几轮迭代的信息,如Adam。PS: 所以优化器不同,耗费的显存也很不一样

典型的机器学习和深度学习问题通常都需要转化为最优化问题进行求解,模型就是通过不断地减小损失函数值的方式来进行学习的。

给定绝对值足够小的数$\xi$,根据泰勒展开式 $f(x+\xi)\approx f(x)+\xi f’(x)$,f’(x) 是f 在x 处的梯度,一维函数的梯度是一个标量,也称导数。

找一个 $\eta > 0$,使得 $ \eta f’(x) $ 足够小,那么可以 用 $-\eta f’(x)$ 替换$\xi$ 代入 $f(x+\xi)\approx f(x)+\xi f’(x)$,得到
\[f(x-\eta f'(x)) \approx f(x) - \eta f'(x)^2\]

如果导数 f’(x) !=0 , 那么$\eta f’(x)^2 > 0$,所以 \(f(x-\eta f'(x)) < f(x)\)

意味着如果 用$x-\eta f’(x)$ 来迭代 $x$,函数 f(x) 的值会降低。在多元微积分中 也有类似 的证明。这就是梯度下降法可以降低目标函数值的原因

函数所有偏导数构成的向量就叫做梯度,某个点的 梯度的方向是方向导数中取到最大值的方向,某个点梯度的值(向量的模)是方向导数的最大值(函数在当前点增长的速率)。举例来说,对于3变量函数 $f=x^2+3xy+y^2+z^3$ ,它的梯度可以这样求得 \(\frac{d_f}{d_x}=2x+3y\) \(\frac{d_f}{d_y}=3x+2y\) \(\frac{d_f}{d_z}=3z^2\)

于是,函数f的梯度可表示为 由3个偏导数组成的向量:$grad(f)=(2x+3y,3x+2y,3z^2)$,针对某个特定点,如点A(1, 2, 3),带入对应的值即可得到该点的梯度(8,7,27),朝着向量点(8,7,27)方向进发,函数f的值增长得最快。

对于机器学习来说

  1. 首先对(w1,w2,b)赋值,这个值可以是随机的,也可以让(w1,w2,b)是一个全零的向量
  2. 改变(w1,w2,b)的值,使得J(w1,w2,b)按梯度下降的方向进行减少,梯度方向由J(w1,w2,b)对(w1,w2,b)的偏导数确定。PS:求梯度 就是求损失值对每个参数的偏导数
  3. $w1=w1+\sigma d_{w1}$ 依次得到 w1,w2,b 的新值,$\sigma$为学习率。 在代码上经常表示为 $w1=w1+\sigma * grad(w1)$

AI初识:为了围剿SGD大家这些年想过的那十几招

以 ResNet 为例。ResNet18 有 11,689,512 个参数。寻找最佳参数配置,也就是在 11,689,512 维的空间中定位一个点。。如果暴力搜索的话,可以把这个空间分割成网格。假设将每个维度分成十格,那么就要检查 10^11689512(10 的 11689512 次方)组可能的配置,对每一组配置都要计算损失函数,并找出损失最小的配置。10 的 11689512 次方是一个什么概念?已知宇宙中的原子才只有 10^83 个,宇宙的年龄只有 4.32 x 10^17 秒(约 137 亿年)。如果从大爆炸开始,每秒检查 10^83 个原子,我们现在才检查了 4.32*10^1411 个,远远小于上述网格可能的配置数。

细节问题

正向传播为什么需要非线性激活函数

如果要用线性激活函数,或者没有激活函数,那么无论你的神经网络有多少层,神经网络只是把输入线性组合再输出,两个线性函数组合本身就是线性函数,所以不如直接去掉所有隐藏层。唯一可以用线性激活函数的通常就是输出层。

常见激活函数: sigmoid,tanh,ReLU,softMax 机器学习中常用激活函数对比与总结

sigmoid的作用是把输入信号从(-∞,+∞)的定义域映射到(0,1)的值域,且有处处可导的优良数学形式,方便之后的梯度下降学习过程,所以它成为了经常使用的激活函数。

损失函数如何确定

损失函数可以把模型拟合程度量化成一个函数值,损失函数值越小,说明 实际输出 和预期输出 的差值就越小,也就表明构建的模型精确度就越高。损失函数里一般有两种参数:权重和偏置。所谓”学习”,就是不断调整权重和偏置,从而找到神经元之间最合适的权重和偏置,让损失函数的值达到最小。

机器学习-损失函数

损失函数是h(θ)和y的函数,本质是关于$\theta$的函数,分为

  1. log对数损失函数,PS:概率意义上,假设样本符合伯努利分布

    \[L(Y,P(Y|X))=-logP(Y|X)\]
  2. 平方损失函数 ,PS:概率意义上,假设误差符合高斯分布

    \[L(Y,h_\theta(X))=(Y-h\_\theta(x))^2\] \[MSE(\theta)=MSE(X,h_\theta)=\frac{1}{m}\sum\_{i=1}^m(\theta^T.x^i-y^i)^2\]
  3. 指数损失函数
  4. Hinge损失函数
  5. 0-1损失函数
  6. 绝对值损失函数

    \[L(Y,h_\theta(X))=|Y-h\_\theta(x)|\]

https://zhuanlan.zhihu.com/p/46928319

对线性回归,logistic回归和一般回归的认识 深度学习领域最常用的10个激活函数,一文详解数学原理及优缺点

从自动微分法来理解线性回归

梯度下降等优化算法使用导数来实际决定是增大还是减小权重,以增大或减小目标函数。如果我们可以计算出一个函数的导数,我们就会知道要继续的方向就是最小化该函数的方向。

英文论文Automatic Differentiation in Machine Learning: a Survey

《深入理解神经网络》用训练样本做输入向量,逐层计算直到计算出神经网络的输出,此过程称为前向传播。用网络输出和训练标签计算损失值。在训练样本和标签给定的情况下, 损失值可以视作<W,b> 的函数。用梯度下降法来更新<W,b> 以降低损失值,这就是神经网络的训练。 梯度下降法需要计算损失值 对 <W,b> 梯度,也就是损失值对 <W,b> 梯度,也就是损失值对 的偏导数。

最优解(极大值/极小值)在 导数为0 的地方,对于y=f(x),手动可以求解 f'(x)=0 的解(解析解,得到的是 解的表达式而非具体的数值),大部分深度学习模型并没有解析解(比如导数永远大于0)。计算机不擅长求微分方程,所以只能通过插值等方法进行海量尝试,把函数的极值点求出来。 已知 $x_i$,计算dy/dx,进而得到 $x_{i+1}$,使得$f’(x_{i+1})$ 更接近0。结论:求最优解必须 先求微分。

一文读懂自动微分( AutoDiff)原理假设我们定义了 $f(x,y)=x^2y+y+2$ ,需要计算它的偏微分 df/dxdf/dy 来进行梯度下降,微分求解大致可以分为4种方式:

  1. 手动求解法(Manual Differentiation),直接算出来 $\frac{df}{dx}=2xy$,用代码实现这个公式。然而大部分深度学习模型不好算公式。 PS: 一些课程 用实际的公式计算单层/两层 感知机 W的梯度
  2. 数值微分法(Numerical Differentiation),直接对原函数代入数值近似求解。直接 $\lim_{x \to x_0}\frac{h(x)-h(x_0)}{x-x_0}$。

  3. 符号微分法(Symbolic Differentiation),代替第一种手动求解法的过程,强调直接对代数进行求解,最后才代入问题数值;
  4. 自动微分法(Automatic Differentiation)

自动微分法是一种介于符号微分和数值微分的方法,自动微分基于一个事实,即每一个计算机程序,不论它有多么复杂,都是在执行加减乘除这一系列基本算数运算,以及指数、对数、三角函数这类初等函数运算。于是自动微分先将符号微分法应用于最基本的算子(不直接算完),比如常数,幂函数,指数函数,对数函数,三角函数等,然后代入数值,保留中间结果,最后再通过链式求导法则应用于整个函数。因此它应用相当灵活,可以做到完全向用户隐藏微分求解过程,由于它只对基本函数或常数运用符号微分法则,所以它可以灵活结合编程语言的循环结构,条件结构等,使用自动微分和不使用自动微分对代码总体改动非常小,并且由于它的计算实际是一种图计算,可以对其做很多优化,这也是为什么该方法在现代深度学习系统中得以广泛应用。

要计算 dy/dx 也就是 dn7/dx,可以先计算 dn7/dn5,即 dn7/dx=dn7/dn5 * dn5/dx,这么链式推导下去。

如果你在Tensorflow 中加入一个新类型的操作,你希望它和自动微分兼容,你需要提供一个函数来建一个计算它的偏导数(相对于它的输入)的图。例如你实现了一个函数来计算输入的平方,$f(x)=x^2$,你需要提供一个偏导数函数 $f’(x)=2x$。

个人理解,对于y=f(x)f'(x) 要么是个常量,要么也是x 的函数。也就是dn7/dn5 不是常量 就是关于n5 的函数,在自上到下 求导(反向传播)用到 dn7/dn5之前,得先自下而上(正向传播)求出来n5 再说。所以正向 + 负向传播加起来 是为了求微分,反向传播依赖正向传播,下一次迭代正向传播依赖本次反向传播。在机器学习框架里,每个node 会包含一个grad 属性,记录了当前的node微分函数或值。

深度学习利器之自动微分(1) 自动微分是一种数值计算方式,计算复杂函数(多层复合函数)在某一点处对某个的导数。又是一种计算机程序,是深度学习框架的标配,是反向传播算法的泛化。文中对比了各种微分算法,解释了梯度以及雅克比矩阵等,值得一读。 深度学习利器之自动微分(2) 讲了自动微分的前向模式与反向模式。

引申:可微分编程是一个比较新的概念,是反向传播和weight-tying的延伸。用户仅指定了函数的结构以及其调用顺序,函数程序实际上被编译成类似于反向传播所需的计算图。图的各个组成部分也必须是可微的,可微分编程把实现/部署的细节留给优化器——语言会使用反向传播根据整个程序的目标自动学习细节,基于梯度进行优化,就像优化深度学习中的权重一样。

早期 Pytorch≈Numpy+AutoGrad,pytorch 封装了torch.autograd包,torch.autograd is PyTorch’s automatic differentiation engine that powers neural network training. 封装了前向后向传播逻辑(实现自动微分)

深度学习利器之自动微分(1) 深度学习利器之自动微分(2)

道理我都懂,但是神经网络反向传播时的梯度到底怎么求? 大牛推荐

[矩阵求导]神经网络反向传播梯度计算数学原理关于反向传播的数学原理,可能就不是那么好理解了,因为这里面需要用到矩阵的高级算法,一般的理工科数学的《线性代数》甚至《高等代数》里面都没有提到相关的内容,对于矩阵的微分是一个“三不管”的地带,但是这个内容又是深度学习神经网络中用得最多的数学原理,所以基本上已经超过了大多数高校学生的知识范围了。在这个时候,就要祭出张贤达的《矩阵分析》了。像数学工具这种内容,建议大家还是去看书,因为书作为几十年的经典教材,其推导过程,内容的完整性,认证的严密性都是经得起推敲的。网络文章只能帮大家启蒙一下,学几个术语,但是具体想深入了解细节,建议还是看书。

逻辑回归

逻辑回归是线性回归的一种特例,激活函数使用 Sigmoid,损失函数使用 log对数损失函数

正向传播过程

\(f(x)=\theta^Tx=w_1x_1+w_2x_2+b\) \(g(z)=\frac{1}{1+e^{-z}}\)

细节问题

激活函数为什么使用Sigmoid

  1. 它的输入范围是正负无穷,而值刚好为(0,1),正好满足概率分布为(0,1)的要求。我们用概率去描述分类器,自然比单纯的某个阈值要方便很多
  2. 它是一个单调上升的函数,具有良好的连续性,不存在不连续点

损失函数为什么使用对数损失函数

$h_\theta(x)$与y 只有0和1两个取值

对数损失函数的直接意义

logistic回归详解(二):损失函数(cost function)详解

\[cost(h_\theta(x),y)= \begin{cases} -logh_\theta(x) & \text{if y=1} \\ -log(1-h_\theta(x)) & \text{if y=0} \end{cases}\]

当y=1时

  1. 如果此时$h_θ(x)=1$,$logh_θ(x)=0$,则单对这个样本而言的cost=0,表示这个样本的预测完全准确。
  2. 如果此时预测的概率$h_θ(x)=0$,$logh_θ(x)=-\infty$,$-logh_θ(x)=\infty$,相当于对cost加一个很大的惩罚项。

当y=0 时类似,所以,取对数在直觉意义上可以将01二值与 正负无穷映射起来

汇总一下就是

$h_\theta(x)$ y cost
0 0 0
0 1 $\infty$
1 0 $\infty$
1 1 0

将以上两个表达式合并为一个,则单个样本的损失函数可以描述为:

\[cost(h_\theta(x),y)= -y\_ilogh\_\theta(x)- (1-y\_i)log(1-h\_\theta(x))\]

因为$h_\theta(x)$与y 只有0和1两个取值,该函数与上述表格 或分段表达式等价

全体样本的损失函数可以表示为:

\[cost(h_\theta(x),y)= \sum\_{i=1}^m -y\_ilogh\_\theta(x)- (1-y\_i)log(1-h\_\theta(x))\]

对数损失函数的概率意义

逻辑回归为什么使用对数损失函数

对于逻辑回归模型,假定的概率分布是伯努利分布(p未知)

\[P(X=n)= \begin{cases} 1-p & \text{n=0} \\ p & \text{n=1} \end{cases}\]

概率公式可以表示为( x只能为0或者1)

\[f(x)=p^x(1-p)^{1-x}\]

假设我们做了N次实验,得到的结果集合为 data={x1,x2,x3},对应的最大似然估计函数可以写成

\[P(data|p)= \prod_{i=1}^Nf(x\_i)= \prod\_{i=1}^Np^{x\_i}(1-p)^{1-x\_i}\]

要使上面的式子最大,等价于使加上ln底的式子值最大,我们加上ln的底就可以将连乘转换为加和的形式

\[lnP(data|p)= \sum\_{i=1}^Nx\_ilnp+(1-x\_i)(1-p)\]

对数损失函数与上面的极大似然估计的对数似然函数本质上是等价的,xi 对应样本实际值yi,p即 $h_\theta(x)$,所以逻辑回归直接采用对数损失函数来求参数,实际上与采用极大似然估计来求参数是一致的

几何意义和概率意义殊途同归(当然,不是所有的损失函数都可以这么理解)

参数和超参数

parameters: $W^{[1]},b^{[1]},W^{[2]},b^{[2]},…$

超参数hyperparameters: 每一个参数都能够控制w 和 b

  1. learning rate
  2. 梯度下降算法循环的数量
  3. 隐层数
  4. 每个隐层的单元数
  5. 激活函数

深度学习的应用领域,很多时候是一个凭经验的过程,选取一个超参数的值,试验,然后再调整。

吴恩达:我通常会从逻辑回归开始,再试试一到两个隐层,把隐藏数量当做参数、超参数一样调试,这样去找比较合适的深度。

为什么批量是一个超参数

训练的目的是 得到一个 <W,b> 使损失函数值最小,损失函数是所有样本数据 损失值的和,是一个关于<W,b> 的函数。理论上来讲,要减小优化的损失是在整个训练集上的损失,所以每次更新参数时应该用训练集上的所有样本来计算梯度。但是实际中,如果训练集太大,每次更新参数时都用所有样本来计算梯度,训练将非常慢,耗费的计算资源也很大。在进行ResNet152对120G的ImageNet数据进行训练时,在使用Batch Size=128,Input Image Size=512x512的前提下,训练过程占用了16G的内存,在GPU一般具有16G内存的情况下,没办法把模型放在一个一块AI芯片上。想进一步加大每次执行数据的Batch size,模型训练的内存占用也会随之增长,最后高于AI芯片的内存容量。

所以用训练集的一个子集上计算的梯度来近似替代整个训练集上计算的梯度。这个子集的大小即为batch size,相应地称这样的优化算法为batch梯度下降。batch size可以为训练集大小,可以更小,甚至也可以为1,即每次用一个样本来计算梯度更新参数。batch size越小收敛相对越快,但是收敛过程相对不稳定,在极值附近振荡也比较厉害。

尤其是batch-size,在GPU处理单元已经充分利用的情况下:

  1. 增大batch size能增大速度,但是很有限(主要是并行计算的优化)
  2. 增大batch size能减缓梯度震荡,需要更少的迭代优化次数,收敛的更快,但是每次迭代耗时更长。
  3. 增大batch size使得一个epoch所能进行的优化次数变少,收敛可能变慢,从而需要更多时间才能收敛(比如batch_size 变成全部样本数目)。

梯度震荡:理论和实践证明,随机数据产生的小batch梯度方向,一定程度上可以模拟整个数据的梯度下降方向。但是在实际情况下,小batch梯度并不足够代替整个数据集的梯度方向,每次小batch在BP算法求解出来的梯度方向,与整体数据集并不完全一致。这样就会导致优化迭代(训练)过程不断震荡。

小结

首先你要知道 机器学习的两个基本过程

  1. 正向传播,线性 + 非线性函数(激活函数) 得到一个估计值
  2. 反向传播,定义损失函数,通过(链式)求导 更新权重

关键问题

  1. 激活函数的选择
  2. 损失函数的选择
  3. 对损失函数及$\theta$链式求偏导数,涉及到矩阵的导数,根据偏导 + 学习率 调整$\theta$
  4. 向量化上述过程
  5. 使用python 中线程的 矩阵/向量计算的库 将上述过程代码化, 涉及到numpy的学习