范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

手把手实现一个Pytroch

  【自动微分实现】反向OO实现自动微分(Pytroch核心机制)
  写【自动微分】原理和实现系列文章,存粹是为了梳理在 MindSpore 当SE时候最核心的自动微分原理。网上看了很多文章,基本上都是很零散,当然Automatic Differentiation in Machine Learning: a Survey[1] 这篇文章是目前ZOMI觉得比较好关于自动微分的综述论文。 【自动微分原理】01. 一文看懂AD原理 【自动微分原理】02. AD的正反向模式 【自动微分原理】03. AD常用实现方案 【自动微分原理】04. 正向OO实现自动微分 【自动微分原理】05. 反向OO实现自动微分(Pytroch核心机制)
  这里记录一下使用操作符重载(OO)编程方式的自动微分,其中数学实现模式则是使用反向模式(Reverse Mode),综合起来就叫做反向OO实现AD啦。 基础知识
  下面一起来回顾一下操作符重载和反向模式的一些基本概念,然后一起去尝试着用Python去实现Pytorch这个AI框架中最核心的自动微分机制是如何实现的。 操作符重载 OO
  操作符重载 :操作符重载或者称运算重载(Operator Overloading,OO),利用现代语言的多态特性(例如C++/JAVA/Python等高级语言),使用操作符重载对语言中基本运算表达式的微分规则进行封装。同样,重载后的操作符在运行时会记录所有的操作符和相应的组合关系,最后使用链式法则对上述基本表达式的微分结果进行组合完成自动微分。
  在具有多态特性的现代编程语言中,运算符重载提供了实现自动微分的最直接方式,利用了编程语言的第一特性(first class feature),重新定义了微分基本操作语义的能力。
  在 C++ 中使用运算符重载实现的流行工具是 ADOL-C(Walther 和 Griewank,2012)。 ADOL-C 要求对变量使用启用 AD 的类型,并在 Tape 数据结构中记录变量的算术运算,随后可以在反向模式 AD 计算期间"回放"。 Mxyzptlk 库 (Michelotti, 1990) 是 C++ 能够通过前向传播计算任意阶偏导数的另一个例子。 FADBAD++ 库(Bendtsen 和 Stauning,1996 年)使用模板和运算符重载为 C++ 实现自动微分。对于 Python 语言来说,autograd 提供正向和反向模式自动微分,支持高阶导数。在机器学习 ML 或者深度学习 DL 领域,目前AI框架中使用操作符重载 OO 的一个典型代表是 Pytroch,其中使用数据结构 Tape 来记录计算流程,在反向模式求解梯度的过程中进行 replay Operator。
  下面总结一下操作符重载的一个基本流程: 操作符重载 :预定义了特定的数据结构,并对该数据结构重载了相应的基本运算操作符 Tape记录 :程序在实际执行时会将相应表达式的操作类型和输入输出信息记录至特殊数据结构 遍历微分 :得到特殊数据结构后,将对数据结构进行遍历并对其中记录的基本运算操作进行微分 链式组合 :把结果通过链式法则进行组合,完成自动微分
  操作符重载法的 优点 可以总结如下: 实现简单,只要求语言提供多态的特性能力 易用性高,重载操作符后跟使用原生语言的编程方式类似
  操作符重载法的 缺点 可以总结如下: 需要显式的构造特殊数据结构和对特殊数据结构进行大量读写、遍历操作,这些额外数据结构和操作的引入不利于高阶微分的实现 对于类似 if,while 等控制流表达式,难以通过操作符重载进行微分规则定义。对于这些操作的处理会退化成基本表达式方法中特定函数封装的方式,难以使用语言原生的控制流表达式 反向模式 Reverse Mode
  反向自动微分同样是基于链式法则。仅需要一个前向过程和反向过程,就可以计算所有参数的导数或者梯度。因为需要结合前向和后向两个过程,因此反向自动微分会使用一个特殊的数据结构,来存储计算过程。
  而这个特殊的数据结构例如 Tensorflow 或者 MindSpore,则是把所有的操作以一张图的方式存储下来,这张图可以是一个有向无环(DAG)的计算图;而Pytroch 则是使用 Tape 来记录每一个操作,他们都表达了函数和变量的关系。
  反向模式根据从后向前计算,依次得到对每个中间变量节点的偏导数,直到到达自变量节点处,这样就得到了每个输入的偏导数。在每个节点处,根据该节点的后续节点(前向传播中的后续节点)计算其导数值。
  整个过程对应于多元复合函数求导时从最外层逐步向内侧求导。这样可以有效地把各个节点的梯度计算解耦开,每次只需要关注计算图中当前节点的梯度计算。
  从下图可以看出来,reverse mode和forward mode是一对相反过程,reverse mode从最终结果开始求导,利用最终输出对每一个节点进行求导。下图虚线就是反向模式。
  前向和后向两种模式的过程表达如下,表的左列浅色为前向计算函数值的过程,与前向计算时相同,右面列深色为反向计算导数值的过程。
  反向模式的计算过程如图所示,其中:
  根据链式求导法则展开有:
  可以看出,左侧是源程序分解后得到的基本操作集合,而右侧则是每一个基本操作根据已知的求导规则和链式法则 由下至上 计算的求导结果。
  手把手实现一个Pytroch
  下面的代码主要介绍反向模式自动微分的实现。目的是通过了解PyTorch的auto diff实现,来了解到上面复杂的反向操作符重载实现自动微分的原理,值的主要的是千万不要在乎这是 MindSpore 的实现还是 Tensorflow 版的实现(实际上都不是哈)。
  首先,需要通过 typing 库导入一些辅助函数。 from typing import List, NamedTuple, Callable, Dict, Optional_name = 1def fresh_name():    global _name    name = f"v{_name}"    _name += 1    return name
  fresh_name   用于打印跟  tape   相关的变量,并用  _name   来记录是第几个变量。
  为了能够更好滴理解反向模式自动微分的实现,实现代码过程中不依赖PyTorch的autograd。代码中添加了变量类  Variable   来跟踪计算梯度,并添加了梯度函数  grad()   来计算梯度。
  对于标量损失l来说,程序中计算的每个张量 x 的值,都会计算值dl/dX。反向模式从 dl/dl=1 开始,使用偏导数和链式规则向后传播导数,例如:
  dl/dx dx/dy=dl/dy
  下面就是具体的实现过程,首先我们所有的操作都是通过Python进行操作符重载的,而操作符重载,通过  Variable   来封装跟踪计算的 Tensor。每个变量都有一个全局唯一的名称  fresh_name  ,因此可以在字典中跟踪该变量的梯度。为了便于理解, __init__   有时会提供此名称作为参数。否则,每次都会生成一个新的临时值。
  为了适配上面图中的简单计算,这里面只提供了 乘、加、减、sin、log 五种计算方式。 class Variable:    def __init__(self, value, name=None):        self.value = value        self.name = name or fresh_name()    def __repr__(self):        return repr(self.value)    # We need to start with some tensors whose values were not computed    # inside the autograd. This function constructs leaf nodes.     @staticmethod    def constant(value, name=None):        var = Variable(value, name)        print(f"{var.name} = {value}")        return var    # Multiplication of a Variable, tracking gradients    def __mul__(self, other):        return ops_mul(self, other)    def __add__(self, other):        return ops_add(self, other)    def __sub__(self, other):        return ops_sub(self, other)    def sin(self):        return ops_sin(self)    def log(self):        return ops_log(self)
  接下来需要跟踪  Variable   所有计算,以便向后应用链式规则。那么数据结构  Tape   有助于实现这一点。 class Tape(NamedTuple):    inputs : List[str]    outputs : List[str]    # apply chain rule    propagate : "Callable[List[Variable], List[Variable]]"
  输入  inputs   和输出  outputs   是原始计算的输入和输出变量的唯一名称。反向传播使用链式规则,将函数的输出梯度传播给输入。其输入为 dL/dOutputs,输出为 dL/dinput。Tape只是一个记录所有计算的累积 List 列表。
  下面提供了一种重置 Tape 的方法  reset_tape  ,方便运行多次自动微分,每次自动微分过程都会产生 Tape List。 gradient_tape : List[Tape] = []# reset tapedef reset_tape():    global _name    _name = 1    gradient_tape.clear()
  现在来看看具体运算操作符是如何定义的,以乘法为例子啦,首先需要计算正向结果并创建一个新变量来表示,也就是  x = Variable(self.value * other.value)  。然后定义了反向传播闭包  propagate  ,使用链规则来反向支撑梯度。 def ops_mul(self, other):    # forward    x = Variable(self.value * other.value)    print(f"{x.name} = {self.name} * {other.name}")    # backward    def propagate(dl_doutputs):        dl_dx, = dl_doutputs        dx_dself = other # partial derivate of r = self*other        dx_dother = self # partial derivate of r = self*other        dl_dself = dl_dx * dx_dself        dl_dother = dl_dx * dx_dother        dl_dinputs = [dl_dself, dl_dother]        return dl_dinputs    # record the input and output of the op    tape = Tape(inputs=[self.name, other.name], outputs=[x.name], propagate=propagate)    gradient_tape.append(tape)    return xdef ops_add(self, other):    x = Variable(self.value + other.value)    print(f"{x.name} = {self.name} + {other.name}")    def propagate(dl_doutputs):        dl_dx, = dl_doutputs        dx_dself = Variable(1.)        dx_dother = Variable(1.)        dl_dself = dl_dx * dx_dself        dl_dother = dl_dx * dx_dother        return [dl_dself, dl_dother]    # record the input and output of the op    tape = Tape(inputs=[self.name, other.name], outputs=[x.name], propagate=propagate)    gradient_tape.append(tape)    return xdef ops_sub(self, other):    x = Variable(self.value - other.value)    print(f"{x.name} = {self.name} - {other.name}")    def propagate(dl_doutputs):        dl_dx, = dl_doutputs        dx_dself = Variable(1.)        dx_dother = Variable(-1.)        dl_dself = dl_dx * dx_dself        dl_dother = dl_dx * dx_dother        return [dl_dself, dl_dother]    # record the input and output of the op    tape = Tape(inputs=[self.name, other.name], outputs=[x.name], propagate=propagate)    gradient_tape.append(tape)    return xdef ops_sin(self):    x = Variable(np.sin(self.value))    print(f"{x.name} = sin({self.name})")    def propagate(dl_doutputs):        dl_dx, = dl_doutputs        dx_dself = Variable(np.cos(self.value))        dl_dself = dl_dx * dx_dself        return [dl_dself]    # record the input and output of the op    tape = Tape(inputs=[self.name], outputs=[x.name], propagate=propagate)    gradient_tape.append(tape)    return xdef ops_log(self):    x = Variable(np.log(self.value))    print(f"{x.name} = log({self.name})")    def propagate(dl_doutputs):        dl_dx, = dl_doutputs        dx_dself = Variable(1 / self.value)        dl_dself = dl_dx * dx_dself        return [dl_dself]    # record the input and output of the op    tape = Tape(inputs=[self.name], outputs=[x.name], propagate=propagate)    gradient_tape.append(tape)    return x
  grad   呢是将变量运算放在一起的梯度函数,函数的输入是 l 和对应的梯度结果 results。 def grad(l, results):    dl_d = {} # map dL/dX for all values X    dl_d[l.name] = Variable(1.)    print("dl_d", dl_d)    def gather_grad(entries):        return [dl_d[entry] if entry in dl_d else None for entry in entries]    for entry in reversed(gradient_tape):        print(entry)        dl_doutputs = gather_grad(entry.outputs)        dl_dinputs = entry.propagate(dl_doutputs)        for input, dl_dinput in zip(entry.inputs, dl_dinputs):            if input not in dl_d:                dl_d[input] = dl_dinput            else:                dl_d[input] += dl_dinput    for name, value in dl_d.items():        print(f"d{l.name}_d{name} = {value.name}")    return gather_grad(result.name for result in results)
  以公式5为例:
  因为是基于操作符重载OO的方式进行计算,因此在初始化自变量 x 和 y 的值需要使用变量  Variable   来初始化,然后通过代码  f = Variable.log(x) + x * y - Variable.sin(y)   来实现。 reset_tape()x = Variable.constant(2., name="v-1")y = Variable.constant(5., name="v0")f = Variable.log(x) + x * y - Variable.sin(y)print(f)v-1 = 2.0v0 = 5.0v1 = log(v-1)v2 = v-1 * v0v3 = v1 + v2v4 = sin(v0)v5 = v3 - v411.652071455223084
  从  print(f)   可以看到是下面图中的左边正向运算,计算出前向的结果。下面的代码  grad(f, [x, y])   就是利用前向最终的结果,通过 Tape 一个个反向的求解。得到最后的结果啦。
  dx, dy = grad(f, [x, y])print("dx", dx)print("dy", dy)dl_d {"v5": 1.0}Tape(inputs=["v3", "v4"], outputs=["v5"], propagate=.propagate at 0x7fd7a2c8c0d0>)v9 = v6 * v7v10 = v6 * v8Tape(inputs=["v0"], outputs=["v4"], propagate=.propagate at 0x7fd7a2c8c378>)v12 = v10 * v11Tape(inputs=["v1", "v2"], outputs=["v3"], propagate=.propagate at 0x7fd7a234e7b8>)v15 = v9 * v13v16 = v9 * v14Tape(inputs=["v-1", "v0"], outputs=["v2"], propagate=.propagate at 0x7fd7a3982ae8>)v17 = v16 * v0v18 = v16 * v-1v19 = v12 + v18Tape(inputs=["v-1"], outputs=["v1"], propagate=.propagate at 0x7fd7a3982c80>)v21 = v15 * v20v22 = v17 + v21dv5_dv5 = v6dv5_dv3 = v9dv5_dv4 = v10dv5_dv0 = v19dv5_dv1 = v15dv5_dv2 = v16dv5_dv-1 = v22dx 5.5dy 1.7163378145367738

王岳伦被拍与美女同归酒店,手搭对方肩膀显亲昵,视频流出引热议饿了吗?戳右边关注我们,每天给您送上最新出炉的娱乐硬核大餐!11月29日,有媒体爆料称王岳伦醉酒后搂着长发美女回酒店,第二天早上还在酒店门口拥抱告别,两人举止亲密疑似出轨。据悉,事总有一些人,活在我们没见过的地方今天分享第4段我们首先回顾一下,上一篇的主要内容讲了知道了世界的广度和深度,我由此而感到焦虑,因此更加懂得努力,努力想进入别人更美好的世界去看看。可是很残忍地发现一个真相,那就是越俄罗斯环形山蕴含丰富的铂金据西伯利亚时报报道,位于俄罗斯哈巴罗夫斯克边疆区的昆丢尔山丘(KondyorMassif)形如月球环形山,是地球上难得一见的地质奇观。就在这宽8千米高600米的环形地块中,蕴藏着丰5G魅力几何?看看上汽把5GL4智能重卡玩得多溜就知道了为什么全世界都在强调5G技术应用?5G带给我们的不仅仅是带宽更宽网速更快,广阔的应用场景更带来创新浪潮的涌现。在湖北武汉召开的首届中国5G工业互联网大会上,邬贺铨院士表示5G广覆盖怀柔喇叭沟门原始森林夜斓山居私汤民宿借山而居,一房难求藏秘密北京怀柔喇叭沟门原始森林夜斓山居私汤民宿位于北京市怀柔区喇叭沟门满族乡对角沟门村,两层独栋别墅,山里的民宿,借山而居,借水而乐,一切皆虚无,感受山水的灵气,岁月的缱绻,回忆一个梦里游戏日报每日奇谈14半人马究竟是一种什么样的龌龊生物?游戏奇谈是游戏日报推出的固定内容栏目,旨在探索分享游戏内外的冷知识,专做那些奇怪又有趣的事儿!上一期咱们讲了夜魔的来历和故事,本期内容将以半人马为主角,探究这种生物的背景来历以及游误入原始森林如何走出来,比如云南省的哀牢山和湖北省的神农架原始,这两个字多少都有点神秘色彩,自然而然就会让人想到古老的或者无人的地方,凡是带有原始的大山或者森林还是湖泊,多少都会流传着神秘的故事或者诡异的传说。原始森林,顾名思义就是说这片灵魂知己,一生难求我想有一个知己,一个可以精神灵魂对话的知己,不以占有对方为目的,甚至不用见面也不用越矩,我们三观一致,心灵相通,永远也有说不完的话,怎么聊也不会厌倦和疲惫,那是一个能懂你所懂痛你所今生,你是我灵魂相依的人文飞鱼01hr因为懂得,所以慈悲,因为理解,所以宽容。今生遇见你,是好运的开始,也是幸福的开始。你是我灵魂相依的人,与你之交,淡淡如水,却是能够在漫长的光阴中成为彼此最好的人。人群要钱没钱,要貌没貌!7位丑男演员娶的老婆,一个比一个漂亮娱乐圈中,不乏俊男靓女的养眼明星,但美女配野兽却是少之又少。今天,咱就来盘一盘娶了漂亮媳妇的丑男演员,他们各有各的故事,有的成了老婆奴,有的在外拈花惹草,背弃妻子另娶她人。01巨兴陈梦拖延比赛领黄牌神情沮丧,伊藤美诚胜陈梦进决赛开心大笑11月29日,休斯敦世乒赛继续进行中,随着比赛的进行,已经到了各单项半决赛的阶段了。在早些时候进行的混双半决赛中,中国选手孙颖莎王楚钦以31的比分战胜头号种子林昀儒郑怡静晋级决赛,
重磅消息特斯拉将装配宁德时代M3P电池,续航里程大增宁德时代推出M3P电池大家好我是大鱼。宁德时代又放大招了,您知道M3P电池量产,会对行业有多大影响吗?您能想象特斯拉如果今年的新车装配上M3P电池,会给竞争者造成多大冲击吗?续航里不止独一无二,还有4899良心价!一加11木星岩限定版4月3号发售自从春季开始手机厂商就马不停蹄召开新机发布会,比如今晚即将发布的魅族20系列华为推出的华为P60系列手机三星也带来了最新S系列产品一加也是接连推出新机。这对于想要换购手机的小伙伴来AI逻辑运算中的世界上最美丽的十大女人据人工智能AI的预测,2023年可能会诞生出全球最美丽的十大女人。这一榜单是通过对全球各地美容行业的数据分析和大数据处理而得出的。10凯莉詹娜(KylieJenner)美国名人,模王坚院士谈ChatGPT计算是对人工智能最关键的技术如果你只能选择一项对人工智能非常关键的技术,那就是计算。3月30日,中国工程院院士阿里云创始人王坚在财富全球科技论坛上谈到ChatGPT时表示,人工智能和计算的关系,就像电视和电的充得快还是用得久哪个才能真正触达消费者的痛点?手机中国充电五分钟,通话两小时,这句经典的广告语可谓是开启了手机行业一个新的发展方向。彼时的手机圈还没有着重于充电速度这一方面的技术,而如今快充技术经过多年的发展,充电功率已经从个BTCETH和XRP价格分析今天市场横盘整理,因为一些代比特币美元比特币(BTC)的汇率在过去24小时内上涨了0。38。今天的小幅上涨并未影响比特币(BTC)的整体技术位置。汇率继续在通道内交易,为进一步的走爱了爱了!第一次见有人把生成式AI原理讲的这么通俗易懂生成式AI是一种基于深度学习技术的人工智能模型,能够模拟人类的创造性思维,生成具有一定逻辑性和连贯性的语言文本图像音频等内容。本篇博客将从技术角度介绍生成式AI的工作原理主要算法和复合肥行业巨头纷纷盈利,世纪阳光却连续三年亏损处危险边缘界面新闻记者实习记者岳栋界面新闻编辑3月27日,港股世纪阳光发布2022年年报。年报显示,公司在2022年整体收入5。32亿港元(约4。67亿人民币),同比减少60。1,2019年美国债务陷阱为何越挖越深参考消息网3月30日报道美国一周周刊网站近日发表了题为为什么美国的债务变得如此之多的文章,作者是乔尔马西斯,文章编译如下美国的债务在增加。据纽约时报报道,美国政府未来10年将增加1钱都去哪了?欧洲人一个月取了5000多亿元,银行存款额创纪录暴跌本文来源时代周报作者马欢3月,全球银行业如同惊弓之鸟。先是硅谷银行的破产,引爆了全美银行业的地雷随后瑞信的危机,进一步加剧人们对全球银行体系压力的担忧。但风波还未平息,更多问题逐渐回想起那些令人感慨的日子大有学问阳光明媚的早晨,我沐浴着温暖的阳光,坐在舒适的椅子上,感受着空气中弥漫的花香,心情格外愉悦。于是,我静静地想起了一段往事,回想起那些令人感慨的日子。记得那时候,我还是个不懂