0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于numpy实现合成梯度

zhKF_jqr_AI 来源:未知 作者:李倩 2018-05-14 17:32 次阅读

DeepMind提出用合成梯度取代反向传播,让网络层可以独立学习,加快训练速度。让我们和DeepMind数据科学家、Udacity深度学习导师Andrew Trask一起,基于numpy实现合成梯度。

TLDR本文将通过从头实现DeepMind的Decoupled Neural Interfaces Using Synthetic Gradients论文中的技术,学习这一技术背后的直觉。

一、合成梯度概述

通常,神经网络比较预测和数据集,以决定如何更新权重。它接着使用反向传播找出每个权重移动的方向,使得预测更精确。然而,在合成梯度(Synthetic Gradient)的情况下,每层各自做出数据的“最佳猜测”,然后根据猜测更新其权重。“最佳猜测”称为合成梯度。数据用来帮助更新每层的“猜测器”(合成梯度生成器)。在大多数情况下,这让网络层可以独立学习,以加快训练的速度。

上图(来自论文)提供了一个直观的表示(自左向右)。圆角方块为网络层,菱形为合成梯度生成器。

二、使用合成梯度

让我们暂时忽略合成梯度是如何生成的,直接看看它们是如何使用的。上图最左展示了如何更新神经网络的第一层。第一层前向传播至合成梯度生成器(Mi+1),合成梯度生成器返回一个梯度。网络使用这个合成梯度代替真实的梯度(计算真实梯度需要一次完整的前向传播和反向传播)。接着照常更新权重,假装合成梯度是真实梯度。如果你需要温习下权重是如何根据梯度更新的,请参考我之前写的基于Numpy实现神经网络:反向传播和梯度下降。

所以,简单来说,合成梯度和平常的梯度一样,而且出于一些神奇的原因,它们看起来很精确(在没有查看数据的情况下)!看起来像魔法?让我们看看它们是如何生成的。

三、生成合成梯度

好吧,这部分非常巧妙,坦白地说,它可以起效真令人惊讶。如何为一个神经网络生成合成梯度?好吧,你当然需要另一个网络!合成梯度生成器不过是一个神经网络,该网络经训练可以接受一个网络层的输出,然后预测该网络层的梯度。

边注:Geoffrey Hinton的相关工作

事实上这让我回想起几年前Geoffrey Hinton的工作,随机合成权重支持的深度学习网络(arXiv:1411.0247)。基本上,你可以通过随机生成矩阵进行反向传播,仍然能够完成学习。此外,他展示了这具有某种正则化效应。这肯定是一项有趣的工作。

好,回到合成梯度。论文同时提到其他相关信息可以用作合成梯度生成网络的输入,不过论文本身看起来在普通前馈网络上只使用了网络层的输出作为生成器的输入。此外,论文甚至声称单线性层可以用作合成梯度生成器。令人惊奇!我们将尝试一下这个。

网络如何学习生成梯度?

这提出了一个问题,生成合成梯度的网络如何学习?当我们进行完整的前向传播和反向传播时,我们实际得到了“正确”的梯度。我们可以将其与“合成”梯度进行比较,就像我们通常比较神经网络输出和数据集一样。因此,我们可以假装“真梯度”来自某个神秘的数据集,以此训练合成梯度网络……所以我们像训练平常的网络一样训练。酷!

等一下……如果合成梯度网络需要反向传播……这还有什么意义?

很好的问题!这一技术的全部价值在于允许独立训练网络层,无需等待所有网络层完成前向传播和反向传播。如果合成梯度网络需要等待完整的前向/反向传播步骤,我们岂不是又回到了原点,而且需要进行的计算更多了(比原先还糟)。为了找到答案,让我们重新看下论文中对网络架构的可视化。

让我们聚焦左边的第二块区域。看到了没有?梯度(Mi+2)经fi+1反向传播至Mi+2。如你所见,每个合成梯度生成器实际上仅仅使用下一层生成的合成梯度进行训练。因此,只有最后一层实际在数据上训练。其他层,包括合成梯度生成网络,基于合成梯度训练。因此,训练每层的合成梯度生成网络时,只需等待下一层的合成梯度(没有其他依赖)。太酷了!

四、基线神经网络

到了写代码的时间了!我将首先实现一个通过反向传播进行训练的原味神经网络,风格与基于Numpy实现神经网络:反向传播中的类似。(所以,如果你有不明白的地方,可以先去阅读我之前写的文章,然后再回过头来阅读本文)。然而,我将额外增加一层,不过这不会造成理解问题。我只是觉得,既然我们在讨论减少依赖,更多的网络层可能有助于形成更好的解释。

至于我们训练的数据集,我们将使用二进制加法生成一个合成数据集(哈哈!)。所以,网络将接受两个随机的二进制数作为输入,并预测两者之和(也是一个二进制数)。这使我们可以方便地根据需要增加维度(大致相当于难度)。下面是生成数据集的代码。

import numpy as np

import sys

def generate_dataset(output_dim = 8,num_examples=1000):

def int2vec(x,dim=output_dim):

out = np.zeros(dim)

binrep = np.array(list(np.binary_repr(x))).astype('int')

out[-len(binrep):] = binrep

return out

x_left_int = (np.random.rand(num_examples) * 2**(output_dim - 1)).astype('int')

x_right_int = (np.random.rand(num_examples) * 2**(output_dim - 1)).astype('int')

y_int = x_left_int + x_right_int

x = list()

for i in range(len(x_left_int)):

x.append(np.concatenate((int2vec(x_left_int[i]),int2vec(x_right_int[i]))))

y = list()

for i in range(len(y_int)):

y.append(int2vec(y_int[i]))

x = np.array(x)

y = np.array(y)

return (x,y)

num_examples = 1000

output_dim = 12

iterations = 1000

x,y = generate_dataset(num_examples=num_examples, output_dim = output_dim)

print("Input: two concatenated binary values:")

print(x[0])

print("\nOutput: binary value of their sum:")

print(y[0])

下面则是相应的神经网络代码:

batch_size = 10

alpha = 0.1

input_dim = len(x[0])

layer_1_dim = 128

layer_2_dim = 64

output_dim = len(y[0])

weights_0_1 = (np.random.randn(input_dim,layer_1_dim) * 0.2) - 0.1

weights_1_2 = (np.random.randn(layer_1_dim,layer_2_dim) * 0.2) - 0.1

weights_2_3 = (np.random.randn(layer_2_dim,output_dim) * 0.2) - 0.1

for iter in range(iterations):

error = 0

for batch_i in range(int(len(x) / batch_size)):

batch_x = x[(batch_i * batch_size):(batch_i+1)*batch_size]

batch_y = y[(batch_i * batch_size):(batch_i+1)*batch_size]

layer_0 = batch_x

layer_1 = sigmoid(layer_0.dot(weights_0_1))

layer_2 = sigmoid(layer_1.dot(weights_1_2))

layer_3 = sigmoid(layer_2.dot(weights_2_3))

layer_3_delta = (layer_3 - batch_y) * layer_3 * (1 - layer_3)

layer_2_delta = layer_3_delta.dot(weights_2_3.T) * layer_2 * (1 - layer_2)

layer_1_delta = layer_2_delta.dot(weights_1_2.T) * layer_1 * (1 - layer_1)

weights_0_1 -= layer_0.T.dot(layer_1_delta) * alpha

weights_1_2 -= layer_1.T.dot(layer_2_delta) * alpha

weights_2_3 -= layer_2.T.dot(layer_3_delta) * alpha

error += (np.sum(np.abs(layer_3_delta)))

sys.stdout.write("\rIter:" + str(iter) + " Loss:" + str(error))

if(iter % 100 == 99):

print("")

现在,我真心觉得有必要做些我几乎从不在学习时做的事,加上一点面向对象结构。通常,这会略微混淆网络,更难看清代码做了什么。然而,由于本文的主题是“解耦网络接口”(Decoupled Neural Interfaces)及其优势,如果不解耦这些接口的话,解释起来会相当困难。因此,我将把上面的网络转换为一个Layer类,之后将进一步转换为一个DNI(解耦网络接口)。

classLayer(object):

def __init__(self,input_dim, output_dim,nonlin,nonlin_deriv):

self.weights = (np.random.randn(input_dim, output_dim) * 0.2) - 0.1

self.nonlin = nonlin

self.nonlin_deriv = nonlin_deriv

def forward(self,input):

self.input = input

self.output = self.nonlin(self.input.dot(self.weights))

return self.output

def backward(self,output_delta):

self.weight_output_delta = output_delta * self.nonlin_deriv(self.output)

return self.weight_output_delta.dot(self.weights.T)

def update(self,alpha=0.1):

self.weights -= self.input.T.dot(self.weight_output_delta) * alpha

在这个Layer类中,我们有一些变量。weights是我们从输入到输出进行线性变换的矩阵(就像平常的线性层)。我们同时引入了一个输出nonlin函数,给我们的网络输出加上了非线性。如果我们不想要非线性,我们可以直接将其值设为lambda x:x。在我们的情形中,我们将传入sigmoid函数。

我们传入的第二个函数是nonlin_deriv,这是一个导数。该函数将接受我们的非线性输出,并将其转换为导数。就sigmoid而言,它的值为(out * (1 - out)),其中out为sigmoid的输出。

现在,让我们看下类中的几个方法。forward,顾名思义,前向传播,首先通过一个线性转换,接着通过一个非线性函数。backward接受一个output_delta参数,该参数表示从下一层经反向传播返回的真实梯度(非合成梯度)。我们接着使用这个参数来计算self.weight_output_delta,也就是权重输出的导数。最后,反向传播发送给前一层的误差,并返回误差。

update也许是其中最简单的函数。它直接接受权重输出的导数,并使用它更新权重。如果有任何步骤不明白,请再次参考基于Numpy实现神经网络:反向传播。

接着,让我们看看layer对象是如何用于训练的。

layer_1 = Layer(input_dim,layer_1_dim,sigmoid,sigmoid_out2deriv)

layer_2 = Layer(layer_1_dim,layer_2_dim,sigmoid,sigmoid_out2deriv)

layer_3 = Layer(layer_2_dim, output_dim,sigmoid, sigmoid_out2deriv)

for iter in range(iterations):

error = 0

for batch_i in range(int(len(x) / batch_size)):

batch_x = x[(batch_i * batch_size):(batch_i+1)*batch_size]

batch_y = y[(batch_i * batch_size):(batch_i+1)*batch_size]

layer_1_out = layer_1.forward(batch_x)

layer_2_out = layer_2.forward(layer_1_out)

layer_3_out = layer_3.forward(layer_2_out)

layer_3_delta = layer_3_out - batch_y

layer_2_delta = layer_3.backward(layer_3_delta)

layer_1_delta = layer_2.backward(layer_2_delta)

layer_1.backward(layer_1_delta)

layer_1.update()

layer_2.update()

layer_3.update()

如果你将上面的代码和之前的脚本对比,基本上所有事情发生在基本相同的地方。我只是用方法调用替换了脚本中的相应操作。

所以,我们实际上做的是从之前的脚本中提取步骤,将其切分为类中不同的函数。

如果你搞不明白这个新版本的网络,不要继续下去。确保你在继续阅读下文之前习惯这种抽象的方式,因为下面会变得更复杂。

五、基于层输出的合成梯度

现在,我们将基于了解的合成梯度的知识改写Layer类,将其重新命名为DNI。

class DNI(object):

def __init__(self,input_dim, output_dim,nonlin,nonlin_deriv,alpha = 0.1):

# 和之前一样

self.weights = (np.random.randn(input_dim, output_dim) * 0.2) - 0.1

self.nonlin = nonlin

self.nonlin_deriv = nonlin_deriv

# 新东西

self.weights_synthetic_grads = (np.random.randn(output_dim,output_dim) * 0.2) - 0.1

self.alpha = alpha

# 之前仅仅是`forward`,现在我们在前向传播中基于合成梯度更新权重

def forward_and_synthetic_update(self,input):

# 缓存输入

self.input = input

# 前向传播

self.output = self.nonlin(self.input.dot(self.weights))

# 基于简单的线性变换生成合成梯度

self.synthetic_gradient = self.output.dot(self.weights_synthetic_grads)

# 使用合成梯度更新权重

self.weight_synthetic_gradient = self.synthetic_gradient * self.nonlin_deriv(self.output)

self.weights += self.input.T.dot(self.weight_synthetic_gradient) * self.alpha

# 返回反向传播的合成梯度(这类似Layer类的backprop方法的输出)

# 同时返回前向传播的输出(我知道这有点怪……)

return self.weight_synthetic_gradient.dot(self.weights.T), self.output

# 和之前的`update`方法类似……除了基于合成权重之外

def update_synthetic_weights(self,true_gradient):

self.synthetic_gradient_delta = self.synthetic_gradient - true_gradient

self.weights_synthetic_grads += self.output.T.dot(self.synthetic_gradient_delta) * self.alpha

我们有了一些新的变量。唯一关键的是self.weights_synthetic_grads,这是我们的合成梯度生成器神经网络(只是一个线性层……也就是……一个矩阵)。

前向传播和合成更新:forward方法变为forward_and_synthetic_update。还记得我们不需要网络的其他部分来更新权重吗?这就是魔法发生之处。首先,照常进行前向传播。接着,我们通过将输出传给一个非线性生成合成梯度。这一部分本可以是一个更复杂的神经网络,不过我们没有这么做,而是决定保持简单性,直接使用一个简单的线性层生成我们的合成梯度。得到我们的梯度之后,我们继续更新权重。最后,我们反向传播合成梯度,以便发送给之前的层。

更新合成梯度:下一层的update_synthetic_gradient方法将接受上一层的forward_and_synthetic_update方法返回的梯度。所以,如果我们位于第二层,那么第三层的forward_and_synthetic_update方法返回的梯度将作为第二层的update_synthetic_weights的输入。接着,我们直接更新合成权重,就像在普通的神经网络中做的那样。这和通常的神经网络的学习没什么两样,只不过我们使用了一些特别的输入和输出而已。

基于合成梯度方法训练网络,我发现它不像我预料的那样收敛。我的意思是,它在收敛,但是收敛得非常慢。我仔细调查了一下,发现隐藏的表示(也就是梯度生成器的输入)在开始时比较扁平和随机。换句话说,两个不同的训练样本在不同网络层结果会有几乎一样的输出表示。这大大增加了梯度生成器工作的难度。在论文中,作者使用的解决方案是批归一化,批归一化将所有网络层输出缩放至0均值和单位方差。此外,论文还提到你可以使用其他形式的梯度生成器输入。对于我们的简单玩具神经网络而言,批归一化会加入大量复杂度。因此,我尝试了使用输出数据集。这并没有破坏解耦状态(秉持了DNI的精神),但在开始阶段给网络提供了非常强力的信息。

进行了这一改动后,训练起来快多了!思考哪些可以充当梯度生成器的优良输入真是一项迷人的活动。也许输入数据、输出数据、批归一化层输出的某种组合会是最佳的(欢迎尝试!)希望你喜欢这篇教程

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 神经网络
    +关注

    关注

    42

    文章

    4574

    浏览量

    98758
  • 生成器
    +关注

    关注

    7

    文章

    302

    浏览量

    20218

原文标题:基于Numpy实现神经网络:合成梯度

文章出处:【微信号:jqr_AI,微信公众号:论智】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    基于FPGA形态学开运算、闭运算和梯度实现

    形态学梯度。计算图b与图c的差得到图d,图d显示出了区域间的边界被清楚地描绘出来。这与二维微分图像的预期结果相同。2 matlab实现基于matlab的形态学开运算以及闭运算源码:%% image
    发表于 08-10 09:12

    如何更新权重实现梯度下降

    实现梯度下降
    发表于 07-15 10:09

    Linux的numpy安装步骤

    今天想使用pyspark的交互模式下执行from pyspark.mllib.regression import LabeledPoint时,报了下面这个错误,错误提示没有安装numpy
    发表于 07-24 06:47

    Numpy的学习总结

    Numpy学习笔记
    发表于 07-16 08:27

    基于NumPy的pandas工具

    :pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。二、使用步骤1.引入库代码如下(示例):import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport
    发表于 08-16 08:47

    灵活运用Python中numpy库的矩阵运算

    Python的numpy库提供矩阵运算的功能,因此我们在需要矩阵运算的时候,需要导入numpy的包。 1.numpy的导入和使用 from numpy import *;#导入
    发表于 11-15 20:07 2176次阅读

    基于python的numpy深度解析

    numpy(Numerical Python)提供了python对多维数组对象的支持:ndarray,具有矢量运算能力,快速、节省空间。numpy支持高级大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。
    的头像 发表于 01-24 13:55 5001次阅读
    基于python的<b class='flag-5'>numpy</b>深度解析

    基于梯度孔中空纤维膜(HFM)构建了新型酶膜生物传感平台

    浙江大学黄小军团队基于梯度孔中空纤维膜(HFM)构建了新型酶膜生物传感平台。首先将梯度孔中空纤维膜作为三维支架,在支架上原位合成导电聚苯胺(PANI)和铂纳米颗粒(Pt NPs),以实现
    的头像 发表于 10-20 10:10 2108次阅读

    基于梯度孔中空纤维膜构建了新型酶膜生物传感平台

    浙江大学黄小军团队基于梯度孔中空纤维膜(HFM)构建了新型酶膜生物传感平台。首先将梯度孔中空纤维膜作为三维支架,在支架上原位合成导电聚苯胺(PANI)和铂纳米颗粒(Pt NPs),以实现
    的头像 发表于 10-21 17:02 1650次阅读
    基于<b class='flag-5'>梯度</b>孔中空纤维膜构建了新型酶膜生物传感平台

    Numpy详解-轴的概念

    NumPy数组的维数称为秩(rank),一维数组的秩为1,二维数组的秩为2,以此类推。在NumPy中,每一个线性的数组称为是一个轴(axes),秩其实是描述轴的数量。
    的头像 发表于 04-25 10:25 2565次阅读

    详解Python中的Pandas和Numpy

    pandas、numpy是Python数据科学中非常常用的库,numpy是Python的数值计算扩展,专门用来处理矩阵,它的运算效率比列表更高效。
    的头像 发表于 05-25 12:49 1971次阅读

    使用Numpy和OpenCV实现傅里叶和逆傅里叶变换

      文章从实际出发,讲述了什么是傅里叶变换,它的理论基础以及Numpy和OpenCV实现傅里叶和逆傅里叶变换,并最终用高通滤波和低通滤波的示例。
    的头像 发表于 07-05 16:04 1255次阅读

    Python 梯度计算模块如何实现一个逻辑回归模型

    AutoGrad 是一个老少皆宜的 Python 梯度计算模块。 对于初高中生而言,它可以用来轻易计算一条曲线在任意一个点上的斜率。 对于大学生、机器学习爱好者而言,你只需要传递给它Numpy这样
    的头像 发表于 10-21 11:01 290次阅读
    Python <b class='flag-5'>梯度</b>计算模块如何<b class='flag-5'>实现</b>一个逻辑回归模型

    List和Numpy Array有什么区别

    Numpy 是Python科学计算的一个核心模块。它提供了非常高效的数组对象,以及用于处理这些数组对象的工具。一个Numpy数组由许多值组成,所有值的类型是相同的。 Python的核心库提供
    的头像 发表于 10-30 10:49 353次阅读
    List和<b class='flag-5'>Numpy</b> Array有什么区别

    基于NumPy的机器学习算法实现

    David Bourgin 表示他一直在慢慢写或收集不同模型与模块的纯 NumPy 实现,它们跑起来可能没那么快,但是模型的具体过程一定足够直观。每当我们想了解模型 API 背后的实现,却又不想看复杂的框架代码,那么它可以作为快
    发表于 01-17 12:36 120次阅读
    基于<b class='flag-5'>NumPy</b>的机器学习算法<b class='flag-5'>实现</b>