这样做的一种方法是进行自动微分而不是符号微分;这是一种在一次计算中同时计算f ( x ) 和f ′( x ) 的方法。我从Dan“sigfpe”Piponi 的关于自动微分的优秀博文中了解到,有一种使用双数的非常酷的方法。你可能应该去读一下,但这是基本的想法。不是使用实数(或者,我们最喜欢的(?)它们的复制品),而是定义一个新集合,我将称之为 D,通过将新元素ε连接到 ℝ 使得ε 2Double
= 0。这很像我们定义复数 ℂ 的方式,通过将新元素i连接到 ℝ 使得i 2 = -1。(如果你喜欢代数,这与说 D = ℝ[x]/⟨x 2 ⟩ 相同。)因此,D 的每个元素都是a + bε的形式,其中a和b是实数。对偶数的算术如您所料:
- (一+ bε ) ± ( c + dε ) = (一+ c ) ± ( b + d ) ε ; 和
- ( a + bε )( c + dε ) = ac + bcε + adε + bdε 2 = ac + ( bc + ad ) ε。
(由于ε 2 = 0,除法更复杂,尽管您对复数使用的共轭相乘技巧仍然有效;有关更多信息,请参阅Wikipedia 的解释。)
现在,为什么这些有用?直观地说,ε就像一个无穷小,允许你用它计算导数。事实上,如果我们用不同的名字重写乘法规则,它就变成了
- ( f + f ′<i>ε)( g + g ′<i>ε) = fg + ( f ′<i>g + fg ′) ε
那里的ε系数看起来很像微分函数乘积的乘积规则!
那么,让我们来看看一大类函数会发生什么。由于我们忽略了上面的除法,假设我们有一些函数f : ℝ → ℝ 由幂级数定义(可能是有限的,所以任何多项式都是可以的,就像 sin( x )、cos( x ) 和e x)。然后我们可以用显而易见的方式定义一个新函数f D : D → D:我们不加实数,而是加对偶数等。然后我声明f D ( x + ε ) = f ( x ) + f ′( x ) ε. 首先,我们可以通过归纳证明对于任何自然数i,情况是 ( x + ε ) i = x i + ix i -1 ε;这将为f ( x ) = x k的情况建立我们的导数结果。在基本情况下,当i = 0 时,这个等式显然成立。然后假设它对i成立,我们有
- ( x + ε ) i +1 = ( x + ε )( x + ε ) i通过分解出一份 ( x + ε )
- = ( x + ε )( x i + ix i -1 ε ) 通过归纳假设
- = x i+1 + ( x i + x ( ix i -1 )) ε由双数乘法的定义
- = x i+1 + ( i+1 ) x i ε通过简单代数。
事实上,这正是我们想要的。现在,考虑到我们的幂级数f,我们知道
- f ( x ) = a 0 + a 1 x + a 2 x 2 + ... + a i x i + ...</li>
然后我们有
- f D ( x + ε ) = a 0 + a 1 ( x + ε ) + a 2 ( x + ε ) 2 + ... + a i ( x + ε ) i + ...</li>
- = a 0 + ( a 1 x + a 1 ε ) + ( a 2 x 2 + 2 a 2 xε ) + … + ( a i x i + ia i x i -1 ε ) + … 通过上述引理
- = ( a 0 + a 1 x + a 2 x 2 + ... + a i x i + ...) + ( a 1 ε + 2 a 2 xε + ... + ia i x i -1 ε + ...) 通过交换律
- = ( a 0 + a 1 x + a 2 x 2 + … + a i x i + …) + ( a 1 + 2 a 2 x + … + ia i x i -1 + …) ε通过分解ε
- = f ( x ) + f ′( x ) ε根据定义。
伟大的!所以对偶数(至少对于这种情况,但结果通常是正确的)可以为我们做微分。我们所要做的就是将我们的原始函数应用于,不是实数x,而是对偶数x + ε,然后提取ε的结果系数。我敢打赌,你可以看到如何在 Haskell 中实现这一点:
data Dual a = !a :+? !a deriving (Eq, Read, Show)
infix 6 :+?
instance Num a => Num (Dual a) where
(a :+? b) + (c :+? d) = (a+c) :+? (b+d)
(a :+? b) - (c :+? d) = (a-c) :+? (b-d)
(a :+? b) * (c :+? d) = (a*c) :+? (b*c + a*d)
negate (a :+? b) = (-a) :+? (-b)
fromInteger n = fromInteger n :+? 0
-- abs and signum might actually exist, but I'm not sure what they are.
abs _ = error "No abs for dual numbers."
signum _ = error "No signum for dual numbers."
-- Instances for Fractional, Floating, etc., are all possible too.
differentiate :: Num a => (Dual a -> Dual a) -> (a -> a)
differentiate f x = case f (x :+? 1) of _ :+? f'x -> f'x
-- Your original f, but with a more general type signature. This polymorphism is
-- essential! Otherwise, we can't pass f to differentiate.
f :: Num a => a -> a
f x = 3*x^2 + 5*x + 9
f' :: Num a => a -> a
f' = differentiate f
然后,你瞧:
*Main> f 42
5511
*Main> f' 42
257
正如 Wolfram Alpha 可以证实的那样,这正是正确的答案。
关于这些东西的更多信息肯定是可用的。我不是这方面的专家。我只是觉得这个想法真的很酷,所以我借此机会模仿我所阅读的内容并得出一两个简单的证明。Dan Piponi 写了更多关于对偶数/自动微分的文章,包括一篇文章,其中除其他外,他展示了一个更一般的结构,它允许偏导数。Conal Elliott 有一篇文章,他展示了如何以类似的方式计算导数塔( f ( x ), f ′( x ), f ″( x ), ...)。上面链接的关于自动微分的维基百科文章进入更多细节,包括其他一些方法。(这显然是“正向模式自动微分”的一种形式,但“反向模式”也存在,而且显然可以更快。)
最后,有一个关于自动区分的 Haskell wiki 页面,它链接到一些文章——而且,重要的是,一些 Hackage 包!我从来没有使用过这些,但似乎Edward Kmett的ad
包是最完整的,它处理了多种不同的自动微分方式——事实证明,他在编写一个包以正确回答另一个包之后上传了该包 Stack Overflow问题。
我确实想添加另一件事。你说“但是,数据类型不应该代表函数(解析器除外)。” 我不得不不同意——将你的函数具体化为数据类型对于这方面的各种事情都是很好的。(无论如何,是什么让解析器特别?)任何时候你有一个想要内省的函数,将它具体化为一种数据类型可能是一个很好的选择。例如,这里是符号微分的编码,很像上面的自动微分的编码:
data Symbolic a = Const a
| Var String
| Symbolic a :+: Symbolic a
| Symbolic a :-: Symbolic a
| Symbolic a :*: Symbolic a
deriving (Eq, Read, Show)
infixl 6 :+:
infixl 6 :-:
infixl 7 :*:
eval :: Num a => (String -> a) -> Symbolic a -> a
eval env = go
where go (Const a) = a
go (Var x) = env x
go (e :+: f) = go e + go f
go (e :-: f) = go e - go f
go (e :*: f) = go e * go f
instance Num a => Num (Symbolic a) where
(+) = (:+:)
(-) = (:-:)
(*) = (:*:)
negate = (0 -)
fromInteger = Const . fromInteger
-- Ignoring abs and signum again
abs = error "No abs for symbolic numbers."
signum = error "No signum for symbolic numbers."
-- Instances for Fractional, Floating, etc., are all possible too.
differentiate :: Num a => Symbolic a -> String -> Symbolic a
differentiate f x = go f
where go (Const a) = 0
go (Var y) | x == y = 1
| otherwise = 0
go (e :+: f) = go e + go f
go (e :-: f) = go e - go f
go (e :*: f) = go e * f + e * go f
f :: Num a => a -> a
f x = 3*x^2 + 5*x + 9
f' :: Num a => a -> a
f' x = eval (const x) $ differentiate (f $ Var "x") "x"
再一次:
*Main> f 42
5511
*Main> f' 42
257
这两种解决方案(或其中的一部分)的美妙之处在于,只要您的原件f
是多态的(类型Num a => a -> a
或类似的),您就不必修改f
!唯一需要放置导数相关代码的地方是新数据类型的定义和微分函数;您可以免费获得现有功能的衍生产品。