9

假设我有:

f :: Double -> Double
f x = 3*x^2 + 5*x + 9

我想计算这个函数的导数并写

derivate f

以便

derivate f == \x -> 6*x + 5

但如何定义derivate

derivate :: (a -> a) -> (a -> a)
derivate f = f' -- how to compute f'?

我知道没有本地方法可以做到这一点,但是有没有图书馆可以做到这一点?

我们是否必须依靠“元”数据类型来实现这一点?

data Computation = Add Exp Expr | Mult Expr Expr | Power Expr Expr -- etc

那么,为每个函数做一个对应的构造函数是不是很痛苦呢?但是,数据类型不应代表函数(解析器除外)。

由于其术语重写功能,Pure是一个很好的选择吗?它不是也有它的缺点吗?

清单负担得起吗?

f :: [Double]
f = [3, 5, 9]

derivate :: (a -> [a])
derivate f = (*) <$> f <*> (getNs f)

compute f x = sum $
    ((*) . (^) x) <$> (getNs f) <*> f

getNs f = (reverse (iterate (length f) [0..]))

Haskell 现在看起来它依赖于 LISP,但语法不太合适。等待一起使用的函数和参数完全存储在数据类型中。另外,这不是很自然。

它们似乎不够“灵活”,无法实现derivate多项式以外的函数,例如单应函数。

例如,现在我想在游戏中使用衍生品。角色在使用函数制作的地板上奔跑,如果地板足够陡峭,我希望他滑行。

我还需要为各种目的求解方程。一些例子:

我是一艘宇宙飞船,我想打个盹。在我睡觉的时候,如果我不小心放置自己,我可能会因为重力而坠毁在一个星球上。我没有足够的气体远离天体,我也没有地图。所以我必须把自己放在这个区域的物体之间,这样它们对我的引力影响的总和就会被抵消。 x是我的y坐标。gravity是一个函数,它接受两个对象并返回它们之间的引力矢量。如果有两个物体,比如说地球和月球,除了我之外,我需要做的就是找到去哪里解决:

gravity earth spaceship + gravity moon spaceship == (0, 0)

它比从头开始创建一个新函数要简单和快速得多equigravityPoint :: Object -> Object -> Object -> Point

如果除了我之外还有 3 个对象,那还是很简单的。

gravity earth spaceship + gravity moon spaceship + gravity sun spaceship == (0, 0)

4 和 n 相同。以这种方式处理对象列表比使用equigravityPoint.

其他例子。我想编写一个射杀我的敌人机器人。如果他只是针对我当前的位置射击,如果我跑向我,他会抓住我,但如果我跳起来摔倒在他身上,他会想念我。一个更聪明的机器人会这样想:“嗯,他从墙上跳了下来。如果我瞄准他现在所在的位置射击,子弹不会射中他,因为他会一直移动到那时。所以我要预测他会去哪里几秒钟后到那里射击,这样子弹和他同时到达这一点”。基本上,我需要计算轨迹的能力。例如,对于这种情况,我需要 的解trajectoryBullet == trajectoryCharacter,它给出了直线和抛物线相交的点。

一个不涉及速度的类似且更简单的示例。我是消防员机器人,有一栋建筑物着火了。另一队消防员正在用水枪灭火。我在,还有人从. 当我的朋友们在射水时,我拿着蹦床。我需要先到人们会跌倒的地方。所以我需要轨迹和方程求解。

4

3 回答 3

29

这样做的一种方法是进行自动微分而不是符号微分;这是一种在一次计算中同时计算f ( x ) 和f ′( x ) 的方法。我从Dan“sigfpe”Piponi 的关于自动微分的优秀博文中了解到,有一种使用双数的非常酷的方法。你可能应该去读一下,但这是基本的想法。不是使用实数(或者,我们最喜欢的(?)它们的复制品),而是定义一个新集合,我将称之为 D,通过将新元素ε连接到 ℝ 使得ε 2Double= 0。这很像我们定义复数 ℂ 的方式,通过将新元素i连接到 ℝ 使得i 2 = -1。(如果你喜欢代数,这与说 D = ℝ[x]/⟨x 2 ⟩ 相同。)因此,D 的每个元素都是a + 的形式,其中ab是实数。对偶数的算术如您所料:

  • (+ ) ± ( c + ) = (+ c ) ± ( b + d ) ε ; 和
  • ( a + )( c + ) = 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 ) + … + ( 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 + ... + 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 Kmettad包是最完整的,它处理了多种不同的自动微分方式——事实证明,他在编写一个包以正确回答另一个包之后上传了该包 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!唯一需要放置导数相关代码的地方是新数据类型的定义和微分函数;您可以免费获得现有功能的衍生产品。

于 2013-02-27T05:16:46.510 回答
4

数值导数可以很容易地完成:

derive f x = (f (x + dx) - f (x - dx)) / (2 * dx) where dx = 0.00001

但是,对于符号导数,您需要创建一个 AST,然后通过匹配和重写 AST 来实现派生规则。

于 2013-02-27T01:32:07.990 回答
2

我不明白您使用自定义数据类型的问题

data Expr = Plus Expr Expr 
           | Times Expr Expr 
           | Negate Expr 
           | Exp Expr Expr 
           | Abs Expr
           | Signum Expr
           | FromInteger Integer
           | Var

instance Num Expr where
   fromInteger = FromInteger
   (+) = Plus
   (*) = Times
   negate = Negate
   abs = Abs
   signum = Signum

toNumF :: Num a => Expr -> a -> a
toNumF e x = go e where
  go Var = x
  go (FromInteger i) = fromInteger i
  go (Plus a b) = (go a) + (go b)
  ...

然后,您可以像使用它一样使用它,Int或者Double一切都会正常工作!你可以定义一个函数

deriveExpr :: Expr -> Expr

然后让您定义以下(RankN)函数

derivate :: Num b => (forall a. Num a => a -> a) -> b -> b
derivate f = toNumF $ deriveExpr (f Var)

您可以扩展它以与数字层次结构的其他部分一起使用。

于 2013-02-27T04:35:14.003 回答