Prenez cette équation simple :
$$ f(x,y,z) = ( x + y ) \times z $$
L’objectif de cet article est de vous montrer comment Gorgonia peut évaluer le gradient $\nabla f$ avec ses dérivées partielles :
$$ \nabla f = [\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z}] $$
En utilisant la règle de dérivation d’une fonction composée, on peut obtenir la valeur du gradient à chaque étape comme démontré ici :
Pour plus d’informations sur le calcul de gradient, veuillez lire cet article de cs231n (en anglais) de Stanford.
Nous allons représenter cette équation dans un exprgraph et voir comment demander à Gorgonia de calculer le gradient.
Quand le calcul est effectué, chaque point aura une double valeur qui contiendra à la fois sa valeur réelle et la dérivée wrt de x.
par exemple, prenons le point x :
var x *gorgonia.Node
Une fois que Gorgonia a évalué l’exprgraph, il est possible d’extraire la valeur de x
aet la valeur du gradient $\frac{\partial f}{\partial x}$ en appelant :
xValue := x.Value() // -2
dfdx, _ := x.Grad() // -4, please check for errors in proper code
Voyons comment faire cela.
D’abord, créons l’exprgraph qui représente l’équation.
Si vous voulez plus d’infos sur cette partie, veuillez lire le tutoriel hello world.
g := gorgonia.NewGraph()
var x, y, z *gorgonia.Node
var err error
// define the expression
x = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("x"))
y = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("y"))
z = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("z"))
q, err := gorgonia.Add(x, y)
if err != nil {
log.Fatal(err)
}
result, err := gorgonia.Mul(z, q)
if err != nil {
log.Fatal(err)
}
Et définissez quelques valeurs :
gorgonia.Let(x, -2.0)
gorgonia.Let(y, 5.0)
gorgonia.Let(z, -4.0)
Il y a deux manières d’obtenir les gradients :
La dérivation automatique n’est possible qu’avec la LispMachine. Par défaut, lispmachine fonctionne avec des mode d’exécution en avant et en arrière.
Ainsi, utiliser la méthode RunAll suffit pour obtenir le résultat.
m := gorgonia.NewLispMachine(g)
defer m.Close()
if err = m.RunAll(); err != nil {
log.fatal(err)
}
Les valeurs et gradients peuvent à présent être extraites :
fmt.Printf("x=%v;y=%v;z=%v\n", x.Value(), y.Value(), z.Value())
fmt.Printf("f(x,y,z) = %v\n", result.Value())
if xgrad, err := x.Grad(); err == nil {
fmt.Printf("df/dx: %v\n", xgrad)
}
if ygrad, err := y.Grad(); err == nil {
fmt.Printf("df/dy: %v\n", ygrad)
}
if xgrad, err := z.Grad(); err == nil {
fmt.Printf("df/dx: %v\n", xgrad)
}
Une autre option est d’utiliser le calcul symbolique. Le calcul symbolique fonctionne en ajoutant des points aux graphiques. Ces nouveaux points contiennent les gradients par rapport aux nœuds passés en argument.
Pour créer ces nouveaux points, on utilise la fonction Grad().
Grad prend un point de coût scalaire et une liste de ce qui concerne, et renvoie le gradient.
Prenez le code suivant :
var grads Nodes
if grads, err = Grad(result,z, x, y); err != nil {
log.Fatal(err)
}
Cela signifie qu’il faut calculer les dérivées partielles (gradients) par rapport à z
, x
et y
.
grads
dans un tableau de []*gorgonia.Node
, dans le même ordre que les WRTs qui y sont passés :
grads[0]
= $\frac{\partial f}{\partial z}$grads[1]
= $\frac{\partial f}{\partial x}$grads[2]
= $\frac{\partial f}{\partial y}$Le gradient est compatible avec TapeMachine et LispMachine. Mais TapeMachine est beaucoup plus rapide.
machine := gorgonia.NewTapeMachine(g)
defer machine.Close()
if err = machine.RunAll(); err != nil {
log.Fatal(err)
}
fmt.Printf("result: %v\n", result.Value())
if zgrad, err := z.Grad(); err == nil {
fmt.Printf("dz/dx: %v | %v\n", zgrad, grads[0].Value())
}
if xgrad, err := x.Grad(); err == nil {
fmt.Printf("dz/dx: %v | %v\n", xgrad, grads[1].Value())
}
if ygrad, err := y.Grad(); err == nil {
fmt.Printf("dz/dy: %v | %v\n", ygrad, grads[2].Value())
}
Notez que vous pouvez accéder aux dérivées partielles de deux manières :
.Grad()
par exemple pour le gradient de x
dans l’exemple présent, utilisez x.Grad()
.Value()
du point gradient par exemple pour le gradient de x
de l’exemple, utilisez grads[1].Value()
.La raison d’avoir ces deux manières différentes de faire les choses se résume à la pertinence. Lorsqu’il est plus significatif d’obtenir des valeurs à partir des points de gradient (par exemple, vous pouvez vouloir calculer la dérivée seconde), utilisez les nœuds de gradient. Mais si vous voulez une récupération rapide des valeurs de gradient, la méthode .Grad()
pourrait être la plus appropriée. En fin de compte, cela dépend de votre goût.
func main() {
g := gorgonia.NewGraph()
var x, y, z *gorgonia.Node
var err error
// define the expression
x = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("x"))
y = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("y"))
z = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("z"))
q, err := gorgonia.Add(x, y)
if err != nil {
log.Fatal(err)
}
result, err := gorgonia.Mul(z, q)
if err != nil {
log.Fatal(err)
}
// set initial values then run
gorgonia.Let(x, -2.0)
gorgonia.Let(y, 5.0)
gorgonia.Let(z, -4.0)
// by default, lispmachine performs forward mode and backwards mode execution
m := gorgonia.NewLispMachine(g)
defer m.Close()
if err = m.RunAll(); err != nil {
log.fatal(err)
}
fmt.Printf("x=%v;y=%v;z=%v\n", x.Value(), y.Value(), z.Value())
fmt.Printf("f(x,y,z)=(x+y)*z\n")
fmt.Printf("f(x,y,z) = %v\n", result.Value())
if xgrad, err := x.Grad(); err == nil {
fmt.Printf("df/dx: %v\n", xgrad)
}
if ygrad, err := y.Grad(); err == nil {
fmt.Printf("df/dy: %v\n", ygrad)
}
if xgrad, err := z.Grad(); err == nil {
fmt.Printf("df/dz: %v\n", xgrad)
}
}
qui donne :
$ go run main.go
x=-2;y=5;z=-4
f(x,y,z)=(x+y)*z
f(x,y,z) = -12
df/dx: -4
df/dy: -4
df/dz: 3
func main() {
g := gorgonia.NewGraph()
var x, y, z *gorgonia.Node
var err error
// define the expression
x = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("x"))
y = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("y"))
z = gorgonia.NewScalar(g, gorgonia.Float64, gorgonia.WithName("z"))
q, err := gorgonia.Add(x, y)
if err != nil {
log.Fatal(err)
}
result, err := gorgonia.Mul(z, q)
if err != nil {
log.Fatal(err)
}
if grads, err = Grad(result,z, x, y); err != nil {
log.Fatal(err)
}
// set initial values then run
gorgonia.Let(x, -2.0)
gorgonia.Let(y, 5.0)
gorgonia.Let(z, -4.0)
machine := gorgonia.NewTapeMachine(g)
defer machine.Close()
if err = machine.RunAll(); err != nil {
log.Fatal(err)
}
fmt.Printf("x=%v;y=%v;z=%v\n", x.Value(), y.Value(), z.Value())
fmt.Printf("f(x,y,z)=(x+y)*z\n")
fmt.Printf("f(x,y,z) = %v\n", result.Value())
if zgrad, err := z.Grad(); err == nil {
fmt.Printf("dz/dx: %v | %v\n", zgrad, grads[0].Value())
}
if xgrad, err := x.Grad(); err == nil {
fmt.Printf("dz/dx: %v | %v\n", xgrad, grads[1].Value())
}
if ygrad, err := y.Grad(); err == nil {
fmt.Printf("dz/dy: %v | %v\n", ygrad, grads[2].Value())
}
}
qui donne :
$ go run main.go
x=-2;y=5;z=-4
f(x,y,z)=(x+y)*z
f(x,y,z) = -12
df/dx: -4 | -4
df/dy: -4 | -4
df/dz: 3 | 3