com.thoughtworks
一个Layer表示一个神经网络。每个Layer可以作为子网络被包含在其它Layer中,构成更复杂的神经网络。Layer的嵌套结构可以用来表示数学公式或粗粒度神经网络结构。 当神经网络被编写完成后,其中大部分元素都是占位符,当网络开始训练时数据才真正进入到网络。
Layer
val myLayer: Layer.Aux[Tape.Aux[Double, Double], Tape.Aux[Double, Double]] = { Times( Plus( Literal(1.0), Identity[Double, Double]() ), Weight(2.0) ) }
以上代码等价的数学公式可以用Symbolic写作:(1.0 + x) * 2.0.toWeight。2.0.toWeight表示一个变量,其初始值是2,在神经网络迭代时,其值会更新。 Times、Plus都是 case class, 因此myLayer是一个case class构成的嵌套结构的树。Times和Plus都是占位符。
(1.0 + x) * 2.0.toWeight
2.0.toWeight
2
myLayer
Times
Plus
Weight是一个包含权重的Layer,初始值是2.0。 Identity是一个输入和输出相同的Layer,它会将输入原样返回。Identity在这里是Input的占位符。 Literal是一个包含常量的Layer。
2.0
Identity
Input
网络每次训练称为一个迭代,分为forward和backward两个阶段,构成一次完整的反向传播流程。
在Layer.Aux[A,B]中调用forward时,A是输入类型,B是输出类型,A和B都是Tape。下面开始逐段解释代码:
Layer.Aux[A,B]
forward
A
B
例如:
val inputTape: Tape.Aux[Double, Double] = Literal(a) val outputTape = myLayer.forward(inputTape)
当调用myLayer.forward(inputData)时,首先调用Times的forward,其伪代码如下:
myLayer.forward(inputData)
final case class Times(operand1: Layer, operand2: Layer) extends Layer { def forward(inputData: Tape): Output = { val upstream1 = operand1.forward(input) val upstream2 = operand2.forward(input) new Output(upstream1, upstream2)//这里忽略具体实现,而关注递归细节 } final class Output(upstream1: Tape, upstream2: Tape) extends Tape { ... } }
在myLayer.operand1是Plus,myLayer.operand2是Weight,因此,upstream1和upstream2分别是operand1和operand2 forward 的结果。
myLayer.operand1
myLayer.operand2
Weight
upstream1
upstream2
operand1
operand2
以此类推,Plus的forward代码与Times的forward类似,当调用Plus的forward时,operand1是Literal,operand2是Identity,这时会各自调用Literal和Identity的forward。
Literal
当调用Identity的forward时会原样返回输入, Identity的forward的伪代码如下:
def forward(inputTape: Tape.Aux[Double, Double]) = inputTape
所以Input即 数学公式(1.0 + x) * 2.0.toWeight 中的x,这样Input就被传递给了神经网络。
x
myLayer.forward的返回值outputTape 是 Tape类型,所以最终会生成一棵Tape构成的树,结构和myLayer一样。 因此,通过层层传播 myLayer.forward(inputTape)最终被Identity原样返回,组合进新生成的Tape树。
myLayer.forward
outputTape
Tape
myLayer.forward(inputTape)
outputTape 的包含forward 的计算结果,计算结果可以用来 backward 比如:
backward
try { val loss = outputTape.value outputTape.backward(loss) loss } finally { outputTape.close() }
outputTape.value 是数学公式 (1.0 + x) * 2.0.toWeight 的计算结果。
outputTape.value
outputTape.backward即Times.Output的backward ,伪代码如下:
outputTape.backward
Times.Output
case class Times(operand1: Layer, operand2: Layer) extends Layer { def forward = ... class Output(upstream1, upstream2) extends Tape { private def upstreamDelta1(outputDelta: Double) = ??? private def upstreamDelta2(outputDelta: Double) = ??? override protected def backward(outputDelta: Double): Unit = { upstream1.backward(upstreamDelta1(outputDelta)) upstream2.backward(upstreamDelta2(outputDelta)) } } }
outputTape.upstream1和outputTape.upstream2分别是operand1和operand2 forward 的结果。然后outputTape.upstream1和outputTape.upstream2分别进行backward。
outputTape.upstream1
outputTape.upstream2
以此类推,Plus的backward代码与Times的backward类似,当调用Plus的backward时,upstream1和upstream2分别是Literal和Identity forward的结果,这时会各自调用upstream1和upstream2的backward。
Weight在backward时会更新自己,参考updateDouble
Layer.Aux[A,B]表示Input的类型是A,Output的类型是B。Tape.Aux[C,D]表示Data的类型是C,Delta的类型是D。 Layer.Aux和Type.Aux可以组合起来使用,比如Layer.Aux[Tape.Aux[A,B],Tape.Aux[C,D]]可以用来表示一个layer的输入类型是一个Tape,这个Tape的数据类型为A,delta类型为B,layer的输出类型是一个Tape,这个Tape的数据类型为C,delta类型为D。
Output
Tape.Aux[C,D]
Data
C
Delta
D
Layer.Aux
Type.Aux
Layer.Aux[Tape.Aux[A,B]
Tape.Aux[C,D]]
layer
delta
Aux是一种实现了type refinement的设计模式,可以用来限制类型参数的范围。
通常我们不会手写Aux类型,因为我们可以使用Symbolic实现同样的功能,例如在用于符号方法内部变量和返回值时:Layer.Aux[Tape.Aux[INDArray, INDArray], Tape.Aux[INDArray, INDArray 和 INDArray @Symbolic 是等价的,所以我们经常使用Symbolic来替代Aux的写法。
Aux
Symbolic
Layer.Aux[Tape.Aux[INDArray, INDArray], Tape.Aux[INDArray, INDArray
INDArray @Symbolic
Backpropagation
type refinement
aux pattern evolution
aux pattern
一个
Layer
表示一个神经网络。每个Layer
可以作为子网络被包含在其它Layer
中,构成更复杂的神经网络。Layer
的嵌套结构可以用来表示数学公式或粗粒度神经网络结构。 当神经网络被编写完成后,其中大部分元素都是占位符,当网络开始训练时数据才真正进入到网络。Layer
的树结构以上代码等价的数学公式可以用Symbolic写作:
(1.0 + x) * 2.0.toWeight
。2.0.toWeight
表示一个变量,其初始值是2
,在神经网络迭代时,其值会更新。 Times、Plus都是 case class, 因此myLayer
是一个case class构成的嵌套结构的树。Times
和Plus
都是占位符。Weight是一个包含权重的
Layer
,初始值是2.0
。 Identity是一个输入和输出相同的Layer
,它会将输入原样返回。Identity
在这里是Input
的占位符。 Literal是一个包含常量的Layer
。迭代
网络每次训练称为一个迭代,分为forward和backward两个阶段,构成一次完整的反向传播流程。
forward
在
Layer.Aux[A,B]
中调用forward
时,A
是输入类型,B
是输出类型,A
和B
都是Tape。下面开始逐段解释代码:例如:
当调用
myLayer.forward(inputData)
时,首先调用Times
的forward
,其伪代码如下:在
myLayer.operand1
是Plus
,myLayer.operand2
是Weight
,因此,upstream1
和upstream2
分别是operand1
和operand2
forward
的结果。以此类推,
Plus
的forward
代码与Times
的forward
类似,当调用Plus
的forward
时,operand1是Literal
,operand2是Identity
,这时会各自调用Literal
和Identity
的forward
。当调用
Identity
的forward
时会原样返回输入,Identity
的forward
的伪代码如下:所以
Input
即 数学公式(1.0 + x) * 2.0.toWeight
中的x
,这样Input
就被传递给了神经网络。myLayer.forward
的返回值outputTape
是Tape
类型,所以最终会生成一棵Tape
构成的树,结构和myLayer
一样。 因此,通过层层传播myLayer.forward(inputTape)
最终被Identity
原样返回,组合进新生成的Tape
树。outputTape
的包含forward
的计算结果,计算结果可以用来backward
比如:outputTape.value
是数学公式(1.0 + x) * 2.0.toWeight
的计算结果。backward
outputTape.backward
即Times.Output
的backward
,伪代码如下:outputTape.upstream1
和outputTape.upstream2
分别是operand1
和operand2
forward
的结果。然后outputTape.upstream1
和outputTape.upstream2
分别进行backward
。以此类推,
Plus
的backward
代码与Times
的backward
类似,当调用Plus
的backward
时,upstream1
和upstream2
分别是Literal
和Identity
forward
的结果,这时会各自调用upstream1
和upstream2
的backward
。Weight
在backward
时会更新自己,参考updateDoubleAux & Symbolic API
Layer.Aux[A,B]
表示Input
的类型是A
,Output
的类型是B
。Tape.Aux[C,D]
表示Data
的类型是C
,Delta
的类型是D
。Layer.Aux
和Type.Aux
可以组合起来使用,比如Layer.Aux[Tape.Aux[A,B]
,Tape.Aux[C,D]]
可以用来表示一个layer
的输入类型是一个Tape
,这个Tape
的数据类型为A
,delta
类型为B
,layer
的输出类型是一个Tape
,这个Tape
的数据类型为C
,delta
类型为D
。Aux是一种实现了type refinement的设计模式,可以用来限制类型参数的范围。
通常我们不会手写
Aux
类型,因为我们可以使用Symbolic
实现同样的功能,例如在用于符号方法内部变量和返回值时:Layer.Aux[Tape.Aux[INDArray, INDArray], Tape.Aux[INDArray, INDArray
和INDArray @Symbolic
是等价的,所以我们经常使用Symbolic
来替代Aux
的写法。Symbolic
Backpropagation
type refinement
aux pattern evolution
aux pattern