@atry
2017-11-16T10:08:03.000000Z
字数 8159
阅读 1453
神经网络与函数式编程
在本系列的神经网络与函数式编程(五)怎么让神经网络自动调参?中学习了插件的用法,能用插件添加类型和函数或者修改现有类型和函数的行为。在本篇文章中,我们将讨论DeepLearning.scala如何实现插件系统。
Factory
在DeepLearning.scala中的插件都是普通trait
,比如最简单的插件是空trait
。比如:
trait Plugin1
trait Plugin2
Plugin1
和Plugin2
都可以看成插件。我们先前已经知道,插件可以用Factory
组装到一起:
val plugin1WithPlugin2: Plugin1 with Plugin2 = Factory[Plugin1 with Plugin2].newInstance()
在这种简单情况下,上述代码创建了一个匿名对象,把Plugin1
和Plugin2
两个插件混入到一起,等价于以下代码:
val plugin1WithPlugin2: Plugin1 with Plugin2 = new Plugin1 with Plugin2 {}
Factory
与Scala的new
关键字都能创建匿名对象。不过,Factory
是个类型类,可以配合类型参数使用,比如
case class SeqFactory[A]() {
def createTenObjects[ConstructorForA](
implicit factory: Factory.Aux[A, ConstructorForA],
constructorConstraint: (ConstructorForA <:< (() => A))
): Seq[A] = {
Seq.fill(10)(constructorConstraint(factory.newInstance)())
}
}
val fooWithBarSeq: Seq[Plugin1 with Plugin2] = SeqFactory[Plugin1 with Plugin2]().createTenObjects
以上代码创建了case class
SeqFactory[A]
,当用户调用SeqFactory[A]
中的createTenObjects
方法时,会利用根据隐式提供的Factory
创建出包含10个A
的Seq
。
其中Factory
是个依赖类型的类型类,能通过隐式宏自动实体化。
trait Factory[Output] {
type Constructor
val newInstance: Constructor
}
object Factory {
type Aux[A, Constructor0] = Factory[A] {
type Constructor = Constructor0
}
implicit def apply[Output]: Factory[Output] = macro ...
}
由于Factory.apply
是个宏,所以Factory[Plugin1 with Plugin2].newInstance()
会在编译时被替换成类似这样的代码:
(new Factory[Plugin1 with Plugin2] {
type Constructor = () => Plugin1 with Plugin2
val newInstance = { () =>
new Plugin1 with Plugin2 {}
}
}).newInstance()
而且,Factory.apply
是个隐式宏,能在搜索隐式参数implicit factory: Factory.Aux[A, ConstructorForA]
时也触发宏。这是因为Factory.Aux[A, ConstructorForA]
等价于Factory[A] { type Constructor = ConstructorForA }
,其中ConstructorForA
是个有待确定的未知类型参数。
比如,在代码SeqFactory[Plugin1 with Plugin2]().createTenObjects
中,已经确定A
为Plugin1 with Plugin2
,但ConstructorForA
尚未确定。当其中第一个隐式参数implicit factory: Factory.Aux[A, ConstructorForA]
被宏替换为后,等价于以下代码:
SeqFactory[Plugin1 with Plugin2]().createTenObjects(
// 第一个隐式参数
new Factory[Plugin1 with Plugin2] {
type Constructor = () => Plugin1 with Plugin2
val newInstance = { () =>
new Plugin1 with Plugin2 {}
}
},
// 第二个隐式参数,尚未确定
???
)
其中的第一个隐式参数会与Factory.Aux[A, ConstructorForA]
进行type unification,从而帮助Scala编译器确定ConstructorForA
的类型为() => Plugin1 with Plugin2
,因此第二个隐式参数的类型是(() => Plugin1 with Plugin2) <:< (() => Plugin1 with Plugin2)
,可以通过Predef.$conforms[() => Plugin1 with Plugin2]
召唤。
那么,最终编译器推断的类型参数和隐式值如下:
SeqFactory[Plugin1 with Plugin2]().createTenObjects[
() => Plugin1 with Plugin2 // 推断出的类型参数
](
// 第一个隐式参数
new Factory[Plugin1 with Plugin2] {
type Constructor = () => Plugin1 with Plugin2
val newInstance = { () =>
new Plugin1 with Plugin2 {}
}
},
// 第二个隐式参数
Predef.$conforms[() => Plugin1 with Plugin2]
)
当第二个隐式参数召唤出来时,就能证明第一个隐式参数factory
内部的Constructor
类型是() => Plugin1 with Plugin2
的子类型。换句话说,在createTenObjects
中只要调用constructorConstraint(factory.newInstance)
,就能获得() => Plugin1 with Plugin2
类型的Constructor
,用来创建Plugin1 with Plugin2
了。
先前在神经网络与函数式编程(五)怎么让神经网络自动调参?中,我们已经知道,插件可以包含抽象方法。这些方法会自动变成Factory.Constructor
的参数,可以在Factory.newInstance
调用时传入,比如:
trait MyAbstractTrait {
val myAbstractField: Int
def myAbstractGetter: Double
def myAbstractMethod0(): Float
def myAbstractMethod1(parameter: String): Unit
}
val myAbstractTrait: MyAbstractTrait = Factory[MyAbstractTrait].newInstance(
myAbstractField = 42,
myAbstractGetter = 1.0,
myAbstractMethod0 = { () => 2.0f },
myAbstractMethod1 = { parameter: String => }
)
其中,Factory[myAbstractTrait]
调用了Factory.apply
宏,会被Scala编译器替换为:
new Factory[myAbstractTrait] {
type Constructor = ((Int, => Double, () => Float, String => Unit) => MyAbstractTrait) {
def apply(
myAbstractField: Int,
myAbstractGetter: => Double,
myAbstractMethod0: () => Float,
myAbstractMethod1: String => Unit
): MyAbstractTrait
}
val newInstance: Constructor = { (_myAbstractField: Int,
_myAbstractGetter: => Double,
_myAbstractMethod0: () => Float,
_myAbstractMethod1: String => Unit) =>
new MyAbstractTrait {
val myAbstractField: Int = _myAbstractField
def myAbstractGetter: Double = _myAbstractGetter
def myAbstractMethod0(): Float = _myAbstractMethod0()
def myAbstractMethod1(parameter: String): Unit = _myAbstractMethod1(parameter)
}
}
}
这样一来,Constructor
是一个函数,接受的参数与MyAbstractTrait
中定义的myAbstractField
、myAbstractGetter
、myAbstractMethod0
和myAbstractMethod1
一一对应,newInstance
的类型是Constructor
,那么就可以向newInstance
传入参数,创建MyAbstractTrait
了。其中MyAbstractTrait
中的字段myAbstractField
对应了一个call-by-value参数,无参数的方法myAbstractGetter
则对应了call-by-name参数,而有参数列表的方法myAbstractMethod0
和myAbstractMethod0
则对应了回调函数参数。
此外,Constructor
还是个细化类型(refinement type),重新定义了apply
方法中的参数名称。所以,当调用newInstance
时,可以使用命名参数。
Factory
还支持注解@inject
,可以自动用隐式值实现插件中抽象方法,比如:
trait InjectedTrait {
def abstractMethod: Int
@inject def injectedAbstractMethod: Ordering[Int]
}
Factory[InjectedTrait].newInstance(abstractMethod = 42)
在这段代码中,abstractMethod
和injectedAbstractMethod
都是抽象方法,但实现方式不同。abstractMethod
需要用户手动实现,而injectedAbstractMethod
则被自动实现为一个隐式值。
Factory[InjectedTrait]
在宏展开后会被替换为:
new Factory[InjectedTrait] {
type Constructor = ((=> Int) => InjectedTrait) {
def apply(injectedAbstractMethod: => Ordering[Int]): InjectedTrait
}
val newInstance: Constructor = { (_injectedAbstractMethod: => Ordering[Int]) =>
new InjectedTrait {
def abstractMethod: Int = _injectedAbstractMethod
def injectedAbstractMethod = The[Ordering[Int]].value
}
}
}
其中The[Ordering[Int]].value
类似Predef.implicitly
,可以用来召唤Ordering[Int]
的隐式值。
利用@inject
可以向一个插件注入另一个插件所需的类型,甚至可以递归注入另一个Factory
,比如:
trait FieldInitializer {
type Field
@inject val fieldFactory: Factory[Field]
@inject def constructorAsFunction: fieldFactory.Constructor <:< (() => Field)
val field: Field = {
constructorAsFunction(fieldFactory.newInstance)()
}
}
trait FieldApiProvider1 {
trait FieldApi1 {
def method1() = println("method1 in in FieldApi1")
}
type Field = FieldApi1
}
val mixed: FieldInitializer with FieldApiProvider1 = Factory[FieldInitializer with FieldApiProvider1].newInstance()
mixed.field.method1() // 输出 "method1 in in FieldApi1"
在Factory[FieldInitializer with FieldApiProvider1]
宏展开后,Field
的类型被设为FieldApi1
,那么标有@inject
的抽象字段fieldFactory
会被实现为Factory[FieldApi1]
类型的隐式值。然后,当fieldFactory
被成功召唤以后,另一处标有@inject
的抽象方法constructorAsFunction
可以证明fieldFactory.Constructor
是个能创建FieldApi1
的零参数函数。最后FieldInitializer
再利用constructorAsFunction
和fieldFactory
就能把FieldApi1
类型的field
创建出来。
Factory
除了能自动实现抽象方法和字段以外,还能自动把抽象类型实现为该抽象类型的上界类型。比如:
trait HasAbstractType {
type MyAbstractType >: Null <: String
}
val hasAbstractType = Factory[HasAbstractType].newInstance()
hasAbstractType.MyAbstractType
的类型是抽象类型MyAbstractType
的上界String
而不是下界Null
,而hasAbstractType
的类型是HasAbstractType { type MyAbstractType = String }
。
Factory[HasAbstractType]
宏展开后是这样的:
new Factory[HasAbstractType] {
type Constructor = () => HasAbstractType { type MyAbstractType = String }
val newInstance: Constructor = { () =>
new HasAbstractType { type MyAbstractType = String }
}
}
如果Factory
要创建的类型混入了多个插件,而且这些插件包含同名的抽象类型,那么Factory
创建的匿名对象会把该抽象类型实现为所有插件中的该抽象类型的上界的共同子类型。DeepLearning.scala利用了这一行为,使得一个插件可以向抽象类型中添加功能,或者修改其他插件提供的函数的行为。比如:
trait FieldInitializer {
type Field
@inject val fieldFactory: Factory[Field]
@inject def constructorAsFunction: fieldFactory.Constructor <:< (() => Field)
val field: Field = {
constructorAsFunction(fieldFactory.newInstance)()
}
}
trait HasMethod1 {
def method1(): Unit
}
trait FieldApiProvider1 {
trait FieldApi1 extends HasMethod1 {
def method1() = println("method1 in in FieldApi1")
}
type Field <: FieldApi1
}
trait FieldApiProvider2 {
trait FieldApi2 extends HasMethod1 {
def method2() = println("method1 in FieldApi2")
abstract override def method1() = {
super.method1()
println("method1 in FieldApi2")
}
}
type Field <: FieldApi2
}
val mixedWithProvider1 = Factory[FieldInitializer with FieldApiProvider1].newInstance()
mixedWithProvider1.field.method1() // 输出 "method1 in in FieldApi1"
val mixedWithProvider1AndProvider2 = Factory[FieldInitializer with FieldApiProvider1 with FieldApiProvider2].newInstance()
mixedWithProvider1AndProvider2.field.method1() // 输出 "method1 in in FieldApi1" 和 "method1 in FieldApi2"
mixedWithProvider1AndProvider2.field.method2() // 输出 "method2 in FieldApi2"
mixedWithProvider1
启用了插件FieldInitializer
和FieldApiProvider1
。其中FieldInitializer
负责创建field
,FieldApiProvider1
负责为field
提供method1
功能。
mixedWithProvider1
除了启用插件FieldInitializer
和FieldApiProvider1
以外,还额外启用了FieldApiProvider2
。FieldApiProvider2
除了为field
新增了method2
以外,还会修改原本FieldApiProvider1
中的method1
的功能。FieldApiProvider2
中的FieldApi2
的method1
的修饰符是abstract override
,这意味着,FieldApi2
的实现者不必知道用户会把FieldApi2
与哪些其他trait
混入到一起,就可以调用其他trait
中的method1
实现。
与以上FieldApiProvider2
的机制类似,在DeepLearning.scala中,DoubleLayer
、DoubleWeight
等抽象类型的行为,也是由启用的插件共同决定的。
DeepLearning.scala使用Factory
组装插件,能够自动实现插件中的抽象成员,具体方式如下:
插件中如何定义抽象成员 |
Factory 如何实现抽象成员
|
---|---|
抽象字段 | call-by-value参数 |
无参数列表的抽象方法 | call-by-name参数 |
有参数列表的抽象方法 | 函数类型的参数 |
@inject 注解的抽象方法 | 隐式值 |
抽象类型 | 该抽象类型的上界类型 |
利用Factory
实现抽象成员的能力,DeepLearning.scala的插件系统能向系统中注入一切功能,比如修改神经网络的优化方式、添加新的原子操作、网络层或者子网络、记录日志、甚至替换掉整个后端。