我试着用Haskell写一个简单的自动区分包。
在Haskell中表示类型安全(有向)计算图的有效方法是什么?我知道广告包为此使用了“data”方法,但我对它不太熟悉。有人能给我提供一些见解吗?谢谢!
发布于 2020-08-06 04:43:50
正如Ness的评论所指出的,AD的正确抽象是一个类别,而不是一个图表。不幸的是,标准的班级并没有真正做到这一点,因为它需要任何Haskell类型之间的箭头,但是在光滑流形之间的区分才有意义。大多数库不知道流形,并将其进一步限制在欧氏向量空间(它们表示为“向量”或“张量”,即数组)。没有一个令人信服的理由来限制--任何仿射空间都可以用于前向模式AD;对于反向模式,还需要一个对偶空间的概念来区分向量。
data FwAD x y = FwAD (x -> (y, Diff x -> Diff y))
data RvAD x y = RvAD (x -> (y, DualVector (Diff y) -> DualVector (Diff x)))
其中Diff x -> Diff y
函数必须是线性函数。(您可以使用这类函数的专用箭头类型,也可以只使用碰巧是线性的(->)
函数。)在反向模式下唯一不同的地方是,这个线性映射的伴随被表示,而不是映射本身。(在实值矩阵实现中,线性映射是雅可比矩阵,伴随版本是它的转置,但不要使用矩阵,它们在这方面效率很低。)
很干净,对吧?人们一直在谈论的那些图/遍历/变异/向后传递的无稽之谈并不是真正需要的。(详细说明请参见科诺氏纸。)
因此,要使它在Haskell中有用,您需要实现类别组合器。这正是我写受限-类别包的目的。下面是您所需的大纲实例化:
import qualified Prelude as Hask
import Control.Category.Constrained.Prelude
import Control.Arrow.Constrained
import Data.AffineSpace
import Data.AdditiveGroup
import Data.VectorSpace
instance Category FwAD where
type Object FwAD a
= (AffineSpace a, VectorSpace (Diff a), Scalar (Diff a) ~ Double)
id = FwAD $ \x -> (x, id)
FwAD f . FwAD g = FwAD $ \x -> case g x of
(gx, dg) -> case f gx of
(fgx, df) -> (fgx, df . dg)
instance Cartesian FwAD where
...
instance Morphism FwAD where
...
instance PreArrow FwAD where
...
instance WellPointed FwAD where
...
这些实例都很简单,而且几乎毫不含糊,让编译器消息来指导您(类型化的漏洞_
非常有用)。基本上,每当需要范围内的类型的变量时,就使用它;当需要一个不在作用域中的向量空间类型的变量时,使用zeroV
。
到那时,您将真正拥有所有基本的可微函数工具,但是要真正定义这些函数,您需要使用大量的.
、&&&
和***
组合器和硬编码的数字基元,这些都是非常规的,也是相当混乱的。为了避免这种情况,您可以使用代理值:值,这些值基本上假装是简单的数字变量,但实际上包含来自某个固定域类型的整个类别箭头。(这基本上就是“建立一个图表”的一部分。)您可以简单地使用提供的GenericAgent
包装器。
instance HasAgent FwAD where
type AgentVal FwAD a v = GenericAgent FwAD a v
alg = genericAlg
($~) = genericAgentMap
instance CartesianAgent FwAD where
alg1to2 = genericAlg1to2
alg2to1 = genericAlg2to1
alg2to2 = genericAlg2to2
instance PointAgent (GenericAgent FwAD) FwAD a x where
point = genericPoint
instance ( Num v, AffineSpace v, Diff v ~ v, VectorSpace v, Scalar v ~ v
, Scalar a ~ v )
=> Num (GenericAgent FwAD a v) where
fromInteger = point . fromInteger
(+) = genericAgentCombine . FwAD $ \(x,y) -> (x+y, \(dx,dy) -> dx+dy)
(*) = genericAgentCombine . FwAD $ \(x,y) -> (x*y, \(dx,dy) -> y*dx+x*dy)
abs = genericAgentMap . FwAD $ \x -> (abs x, \dx -> if x<0 then -dx else dx)
...
instance ( Fractional v, AffineSpace v, Diff v ~ v, VectorSpace v, Scalar v ~ v
, Scalar a ~ v )
=> Fractional (GenericAgent FwAD a v) where
...
instance (...) => Floating (...) where
...
如果所有这些实例都已完成,并且可能需要一个简单的助手来提取结果。
evalWithGrad :: FwAD Double Double -> Double -> (Double, Double)
evalWithGrad (FwAD f) x = case f x of
(fx, df) -> (fx, df 1)
然后,您可以编写代码,例如
> evalWithGrad (alg (\x -> x^2 + x) 3)
(12.0, 7.0)
> evalWithGrad (alg sin 0)
(0.0, 1.0)
在此基础上,这些代数表达式建立了FwAD
箭头的组合,&&&
“分割”数据流和***
并行合成,即即使输入和最终结果是简单的Double
,中间结果也将通过一个合适的元组类型得到。[我猜,这就是你标题问题的答案:在某种意义上,有向图表示为一个分支的合成链,原则上与你在这些S中发现的一样。]
https://stackoverflow.com/questions/63279576
复制