手榴弹
首先你要取出圣别的别针,然后你要数到三,不多不少。
数到三就是你需要数的数字,三就是要数的数字。
你不要数到四,也不要数到二,除非你接下来数到三。
五是绝对不行的。
💣 机器学习可能会在你脸上爆炸 💣
Grenade 是一个可组合的、依赖类型的、实用的、快速的循环神经网络库, 用于在Haskell中简洁和精确地指定复杂的网络。
例如,可以用几行代码指定并初始化权重随机的一个能在MNIST达到约1.5%错误率的网络:
type MNIST
= Network
'[ Convolution 1 10 5 5 1 1, Pooling 2 2 2 2, Relu
, Convolution 10 16 5 5 1 1, Pooling 2 2 2 2, Reshape, Relu
, FullyConnected 256 80, Logit, FullyConnected 80 10, Logit]
'[ 'D2 28 28
, 'D3 24 24 10, 'D3 12 12 10 , 'D3 12 12 10
, 'D3 8 8 16, 'D3 4 4 16, 'D1 256, 'D1 256
, 'D1 80, 'D1 80, 'D1 10, 'D1 10]
randomMnist :: MonadRandom m => m MNIST
randomMnist = randomNetwork
就是这样。由于类型非常丰富,不需要特定的术语级代码来构建这个网络;尽管当然有可能且容易手动构建和分解网络与层。
如果你更喜欢循环神经网络,可以尝试定义一些"不可思议的有效" 用
type Shakespeare
= RecurrentNetwork
'[ R (LSTM 40 80), R (LSTM 80 40), F (FullyConnected 40 40), F Logit]
'[ 'D1 40, 'D1 80, 'D1 40, 'D1 40, 'D1 40 ]
设计
Grenade中的网络可以被认为是层的异构列表,其中它们的类型不仅包括网络的层,还包括在层之间传递的数据的形状。
网络的定义出奇地简单:
data Network :: [*] -> [Shape] -> * where
NNil :: SingI i
=> Network '[] '[i]
(:~>) :: (SingI i, SingI h, Layer x i h)
=> !x
-> !(Network xs (h ': hs))
-> Network (x ': xs) (i ': h ': hs)
Layer x i o
约束确保层 x
可以合理地在输入和输出形状 i
和 o
之间进行转换。
提升的数据种类 Shape
定义了我们的一维、二维和三维类型,用于声明在层之间传递的数据形状。
在上面的MNIST示例中,可以看到输入层是一个有28 x 28像素的二维(D2
)图像。当第一个 卷积 层运行时,它输出一个三维(D3
)24x24x10图像。列表中的最后一项是一维的(D1
)具有10个值,代表MNIST数据的类别。
用法
要进行反向传播,可以调用同名函数
backPropagate :: forall shapes layers.
Network layers shapes -> S (Head shapes) -> S (Last shapes) -> Gradients layers
它接受一个网络、适当的输入和目标数据,并返回网络的反向传播梯度。梯度的形状对于每一层来说是适当的,对于如 Relu
等没有可学习参数的层可能是微不足道的。
然而这些梯度总是可以应用的,生成一个新的(希望更好的)层,通过
applyUpdate :: LearningParameters -> Network ls ss -> Gradients ls -> Network ls ss
Grenade中的层被表示为Haskell类,因此在下游代码中创建自己的层是很容易的。如果网络的形状指定得不正确且层不能合理地在两种形状之间进行操作,则会导致编译时错误。
组成
Grenade中的网络与层在类型级别上容易组成。由于 Network
是 Layer
的一个实例,可以很容易地将一个训练好的网络作为一个小组件在更大的网络中使用。此外,我们提供了2个旨在并行运行的层,并通过在一个维度上串联它们的输出或通过逐点累加它们的激活来合并它们的输出。这使得人们可以编写任何可以表示为串并联图的网络。
例如,残差网络层规范可以这样写:
type Residual net = Merge Trivial net
如果 net
的类型是 Layer
的一个实例,那么 Residual net
也将是。它将在运行网络的同时,通过 Trivial
层保留其输入,并将原始图像与输出合并。
参见MNIST示例,其中已经过度设计,包含了残差风格学习以及初始风格卷积。
生成对抗网络
由于Grenade是纯函数式的,可以以灵活的方式组合其训练功能。GAN-MNIST 示例展示了一种有趣的、类型安全的方式,用10行代码编写一个生成对抗训练函数。
层动物园
Grenade层是普通的Haskell数据类型,是 Layer
的一个实例,所以在下游代码中构建自己的层很容易。然而,我们确实提供了一套不错的层,包括卷积、反卷积、池化、填充、裁剪、逻辑、Relu、Elu、Tanh和全连接。
构建说明
Grenade最容易用仓库中的mafia脚本构建。你还需要 lapack
和 blas
库和开发工具。一旦你拥有了所有这些,可以使用:
./mafia build
构建,并使用:
./mafia test
运行测试。
Grenade在ghc 7.10,8.0,8.2和8.4下构建。
感谢
编写一个这样的库在我心中已经有一段时间了,但必须大力感谢 Justin Le,他的依赖类型的全连接网络启发了我开始动手,给了我很多类型级工具的想法,并且是编写这个库的一个很好的起点。
性能
Grenade由hmatrix、BLAS和LAPACK支持,关键功能在C中优化。使用Caffe流行的im2col技巧,它应该足以解决许多问题。
由于它是纯函数式的,因此也很容易并行运行批处理,这对于更大的网络是适当的,但我当前的示例是单线程的。
在单核心上对Kaggle 41000个样本MNIST训练集训练15代大约花了12分钟,在1000个样本的保留集上取得了1.5%的错误率。
贡献
欢迎贡献。