2016-12-26 3 views
1

Ich versuche, das folgende minimale Beispiel von Numeric.AD zu kompilieren:minimal Numeric.AD Beispiel nicht kompiliert

import Numeric.AD 
timeAndGrad f l = grad f l 
main = putStrLn "hi" 

und ich laufe in diesen Fehler:

test.hs:3:24: 
    Couldn't match expected type ‘f (Numeric.AD.Internal.Reverse.Reverse 
             s a) 
            -> Numeric.AD.Internal.Reverse.Reverse s a’ 
       with actual type ‘t’ 
     because type variable ‘s’ would escape its scope 
    This (rigid, skolem) type variable is bound by 
     a type expected by the context: 
     Data.Reflection.Reifies s Numeric.AD.Internal.Reverse.Tape => 
     f (Numeric.AD.Internal.Reverse.Reverse s a) 
     -> Numeric.AD.Internal.Reverse.Reverse s a 
     at test.hs:3:19-26 
    Relevant bindings include 
     l :: f a (bound at test.hs:3:15) 
     f :: t (bound at test.hs:3:13) 
     timeAndGrad :: t -> f a -> f a (bound at test.hs:3:1) 
    In the first argument of ‘grad’, namely ‘f’ 
    In the expression: grad f l 

Jeder Hinweis als warum passiert das? bei den vorherigen Beispielen aus der Betrachtung entnehme ich, dass dies „Abflachung“ grad ‚s Typ:

grad :: (Traversable f, Num a) => (forall s. Reifies s Tape => f (Reverse s a) -> Reverse s a) -> f a -> f a

aber ich brauche eigentlich so etwas wie dies in meinem Code zu tun. In der Tat ist dies das minimalste Beispiel, das nicht kompiliert wird. Je komplizierter, was ich tun möchte, ist so etwas wie diese:

example :: SomeType 
example f x args = (do stuff with the gradient and gradient "function") 
    where gradient = grad f x 
      gradientFn = grad f 
      (other where clauses involving gradient and gradient "function") 

Hier ist eine etwas kompliziertere Version mit Typ-Signaturen, die kompilieren lassen.

{-# LANGUAGE RankNTypes #-} 

import Numeric.AD 
import Numeric.AD.Internal.Reverse 

-- compiles but I can't figure out how to use it in code 
grad2 :: (Show a, Num a, Floating a) => (forall s.[Reverse s a] -> Reverse s a) -> [a] -> [a] 
grad2 f l = grad f l 

-- compiles with the right type, but the resulting gradient is all 0s... 
grad2' :: (Show a, Num a, Floating a) => ([a] -> a) -> [a] -> [a] 
grad2' f l = grad f' l 
     where f' = Lift . f . extractAll 
     -- i've tried using the Reverse constructor with Reverse 0 _, Reverse 1 _, and Reverse 2 _, but those don't yield the correct gradient. Not sure how the modes work 

extractAll :: [Reverse t a] -> [a] 
extractAll xs = map extract xs 
      where extract (Lift x) = x -- non-exhaustive pattern match 

dist :: (Show a, Num a, Floating a) => [a] -> a 
dist [x, y] = sqrt(x^2 + y^2) 

-- incorrect output: [0.0, 0.0] 
main = putStrLn $ show $ grad2' dist [1,2] 

aber ich kann nicht herausfinden, wie die erste Version zu verwenden, grad2, im Code, weil ich nicht weiß, wie man mit Reverse s a beschäftigen. Die zweite Version, grad2', hat den richtigen Typ, weil ich den internen Konstruktor Lift verwende, um eine Reverse s a zu erstellen, aber ich muss nicht verstehen, wie die Interna (speziell der Parameter s) funktioniert, weil der Ausgabegradient alle 0s ist. Die Verwendung des anderen Konstruktors Reverse (hier nicht gezeigt) erzeugt ebenfalls den falschen Gradienten.

Alternativ gibt es Beispiele für Bibliotheken/Code, wo Menschen den Code ad verwendet haben? Ich denke, mein Anwendungsfall ist sehr verbreitet.

+2

Was passiert, wenn Sie timeAndGrad eine Art signiture zur Verfügung stellen? Vielleicht haben Sie auch mehr Glück mit dem Rang-1-Ansatz. – ocharles

+0

Ich habe meine Frage bearbeitet, um eine Typensignatur und einen anderen Ansatz hinzuzufügen (was auch nicht funktioniert). – kye

Antwort

2

Mit where f' = Lift . f . extractAll erstellen Sie im Wesentlichen eine Hintertür in den zugrunde liegenden Typ der automatischen Differenzierung, der alle Ableitungen wegwirft und nur die Konstantenwerte behält. Wenn Sie dies dann für grad verwenden, ist es kaum überraschend, dass Sie ein Null-Ergebnis erhalten!

Der vernünftige Weg ist nur grad zu verwenden, wie es ist:

dist :: Floating a => [a] -> a 
dist [x, y] = sqrt $ x^2 + y^2 
-- preferrable is of course `dist = sqrt . sum . map (^2)` 

main = print $ grad dist [1,2] 
-- output: [0.4472135954999579,0.8944271909999159] 

Sie haben nicht wirklich etwas komplizierter müssen wissen, automatische Unterscheidung zu verwenden. Solange Sie nur Num oder Floating -polymorphe Funktionen unterscheiden, funktioniert alles wie es ist. Wenn Sie eine Funktion differenzieren müssen, die als Argument übergeben wird, müssen Sie dieses Argument auf Rang 2 polymorph machen (eine Alternative wäre, zu der Rang-1-Version der ad-Funktionen zu wechseln, aber ich wage zu behaupten, dass das weniger elegant ist und bringt dir nicht wirklich viel).

{-# LANGUAGE Rank2Types, UnicodeSyntax #-} 

mainWith :: (∀n . Floating n => [n] -> n) -> IO() 
mainWith f = print $ grad f [1,2] 

main = mainWith dist 
+0

Ja, ich muss eine Funktion unterscheiden, die als Argument übergeben wird. Kannst du ein bisschen mehr erklären, was du meinst, wenn du dieses Argument "Rang-2 polymorph" machst? Ich habe auch versucht, auf die Rang-1-Version von grad zu wechseln, und ich muss angeben, dass die Funktion type hat ([Forward a] -> Forward a). – kye

+0

Der Typ erlaubt keine Funktion mit Typ (Num a => [a] -> a), wie Sie hier verwenden, aber ich bin in der Lage, das im Code zu übergeben. Ich bin mir nicht sicher, warum die Typen sich so verhalten. – kye