Mathematische Funktionenklasse

samosa

Mitglied
Hi,

ich möchte eine Funktionenklasse, z.B. für Gradientenabstieg implementieren. Wie macht man das? Meine Idee sieht so aus:

Java:
public interface Function {

    public double f(Object ...params);

    public void df();
}


Eine konkrete Funktion f(x) = w*x + b auf den R^n könnte so aussehen:
Java:
public class Linear {

    public double[] gradient;
    double[] w;

    public double f(Object ...params) {
        double[] x = (double[])params[0];
        this.w = (double[])params[1];
        double b = (Double)params[2];

        double y = b;
        for(int i = 0; i < x.length; i++) {
            y += w[i]*x[i];
        }
        return y;
    }

    public void df() {
        gradient = w;
    }
}

Das Attribut w ist hier im speziellen Fall nicht notwendig. Den Gradienten hätte man auch bereits in der Funktion f() setzen können. Bei anderen Funktionen muss man den Gradient etwas aufwändiger berechnen. Deswegen wurde hier eine Implementierung gezeigt, die das für den allgemeinen Fall simulieren soll. Trotzdem finde ich das alles nicht schön. Wie macht man das besser?
 
Zuletzt bearbeitet:

VfL_Freak

Top Contributor
Moin,

Java:
public interface Function {

    public double f(Object ...params);

    public void df();
}

Eine konkrete Funktion f(x) = w*x + b auf den R^n könnte so aussehen:
Java:
public class Linear 
{
    public double[] gradient;
    double[] w;

    public double f(Object ...params) {
        double[] x = (double[])params[0];
        this.w = (double[])params[1];
        double b = (Double)params[2];

        double y = b;
        for(int i = 0; i < x.length; i++) {
            y += w[i]*x[i];
        }
        return y;
    }

    public void df() {
        gradient = w;
    }
}

Erstmal einige Anmerkungen:

(a) Wo und mit welchen Parametern wird "f()" denn konkret aufgerufen ???:L
(b) Ist das ganze denn so compilierbar ???:L (irgendwie erscheint der Cast auf "double[]" recht gewagt zu sein ...)
(c) Stellst Du sicher, dass immer (mindestens) drei Parameter übergeben ???:L Sind es bspw. nur zwei, dann knallt der Zugriff auf "params[2]" !!

Gruß
Klaus
 

Tobse

Top Contributor
Also von den Implementierungs-Fehlern die VfL-Freak schon aufgezeigt hat trifft das schon in etwa zu. Ich weiss jetzt nicht wozu du diese Funktionalität brauchst. Hier mal der Code, den ich für einen Wissenschaftlichen Taschenrechner benutzt habe:
Java:
package com.scalc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
// war für die evaulierung von mathematischen ausdrücken als string
import net.astesana.javaluator.*;

public class Function
{
    protected static final double STEP = Double.MIN_VALUE * 1E308 * 1E10;
    protected static int PRECISION = 40;
    protected static double THETA = 1E-9;
    protected static final Function NULL_F = new Function("0");
    
    protected String term;
    protected Domain domain;
    private StaticVariableSet defaultSet = new StaticVariableSet();
    protected static final StaticVariableSet EMPTY_SET = new StaticVariableSet();
    protected Function derivative;
    protected Function integral;
    private boolean hasX = false;
    
    public Function(String term, Domain d)
    {
        this.term = term;
        hasX = term.indexOf("x") != -1;
        domain = d;
    }
    public Function(String term)
    {
        this(term, null);
    }
    
    public Double calc(StaticVariableSet<Double> vars)
    {
        if (domain != null && !domain.contains(vars.get("x")))
        {
            return null;
        }
        return SCEvaluator.evalTerm(this.term, vars);
    }
    public Double calc(double x)
    {
        if (domain != null && !domain.contains(x))
        {
            return null;
        }
        synchronized(defaultSet)
        {
            defaultSet.set("x", x);
            return SCEvaluator.evalTerm(this.term, defaultSet);
        }
    }
    public Double calc(double x, StaticVariableSet<Double> env)
    {
        if (domain != null && !domain.contains(x))
        {
            return null;
        }
        env.set("x", x);
        return calc(env);
    }
    
    public Collection<Place> getRoots(Domain d, StaticVariableSet<Double> env)
    {
        double start = d.getStart();
        double end = d.getEnd();
        if (d.getStartType() == Domain.EXCLUSIVE)
        {
            start += THETA;
        }
        if (d.getEndType() == Domain.EXCLUSIVE)
        {
            end -= THETA;
        }
        return getRoots(start, end, env);
    }
    public Collection<Place> getRoots(Domain d)
    {
        return getRoots(d, EMPTY_SET);
    }
    public Collection<Place> getRoots(StaticVariableSet<Double> env)
    {
        return getRoots(-Double.MAX_VALUE, Double.MAX_VALUE, env);
    }
    public Collection<Place> getRoots()
    {
        return getRoots(EMPTY_SET);
    }
    public Collection<Place> getRoots(double start, double end,
        StaticVariableSet<Double> env)
    {
        if (!hasX)
        {
            return null;
        }
        // implementation nicht voll funktionsfähig, daher ausgelassen
    }
    
    public Collection<TurningPoint> getTurningPoints(Domain d, StaticVariableSet<Double> env)
    {
        double start = d.getStart();
        double end = d.getEnd();
        if (d.getStartType() == Domain.EXCLUSIVE)
        {
            start += THETA;
        }
        if (d.getEndType() == Domain.EXCLUSIVE)
        {
            end -= THETA;
        }
        return getTurningPoints(start, end, env);
    }
    public Collection<TurningPoint> getTurningPoints(Domain d)
    {
        return getTurningPoints(d, EMPTY_SET);
    }
    public Collection<TurningPoint> getTurningPoints(StaticVariableSet<Double> env)
    {
        return getTurningPoints(-Double.MAX_VALUE, Double.MAX_VALUE, env);
    }
    public Collection<TurningPoint> getTurningPoints(double start, double end,
        StaticVariableSet<Double> env)
    {
        // implementation nicht voll funktionsfähig, daher ausgelassen
    }
    
    public Function getDerivative(final StaticVariableSet<Double> env)
    {
        final Function p = this;
        return new Function(this.domain) {
            public Double calc(StaticVariableSet<Double> vars)
            {
                return p.getDerivationAt(vars.get("x"), vars);
            }
            public Double calc(double x)
            {
                return p.getDerivationAt(x, env);
            }
            public Double calc(double x, StaticVariableSet<Double> _env)
            {
                return p.getDerivationAt(x, _env);
            }
        };
    }
    public Function getDerivative()
    {
        if (this.derivative == null)
        {
            this.derivative = getDerivative(EMPTY_SET);
        }
        return this.derivative;
    }
    
    public Function getIntegral(final StaticVariableSet<Double> env)
    {
        final Function c = this;
        return new Function(this.domain) {
            public Double calc(StaticVariableSet<Double> vars)
            {
                return c.getIntegralAt(vars.get("x"), vars);
            }
            public Double calc(double x)
            {
                return c.getIntegralAt(x, env);
            }
            public Double calc(double x, StaticVariableSet<Double> _env)
            {
                return c.getIntegralAt(x, _env);
            }
        };
    }
    public Function getIntegral()
    {
        if (this.integral == null)
        {
            this.integral = getIntegral(EMPTY_SET);
        }
        return this.integral;
    }
    
    public Double getDerivationAt(double x, StaticVariableSet<Double> vars)
    {
        if (domain != null && !domain.contains(x))
        {
            return null;
        }
        double a = x - THETA;
        double b = x + THETA;
        return Math.round(((calc(b, vars) - calc(a, vars)) / (2 * THETA)) * 1E11) / 1E11;
    }
    public Double getDerivationAt(double x)
    {
        return getDerivationAt(x, EMPTY_SET);
    }
    
    public Double getIntegralAt(double x, StaticVariableSet<Double> vars)
    {
        if (domain != null && !domain.contains(x))
        {
            return null;
        }
        double a = calc(x - THETA, vars);
        double b = calc(x + THETA, vars);
        return Math.round(((a * THETA + b * THETA) / 2) * 1E11) / 1E11;
    }
    public Double getIntegralAt(double x)
    {
        return getIntegralAt(x, EMPTY_SET);
    }
}

EDIT:
Dann kann eine Quadratische Funktion so aussehen:
Java:
class QuadraticFunction extends Function
{

    public QuadraticFunction(double a, double b, double c)
    {
        ...
    }

    public double calc(double x)
    {
        return a * Math.pow(x, 2) + b * x + c;
    }
}
 
Zuletzt bearbeitet:

samosa

Mitglied
Hi,

erst einmal danke für Deine Zeit. Nun zu Deinen Fragen.

(a) Wo und mit welchen Parametern wird "f()" denn konkret aufgerufen ???:L

Angenommen wir möchten eine Klasse für den Gradientenabstieg implementieren. Der Gradientenabstieg ist für eine Funktion
Code:
f(x)
von der Form

Java:
x <- x - eta df(x)

wobei
Code:
eta > 0
die Schrittweite ist und
Code:
df(x)
der Gradient von f an der Stelle x.

Nun kann ich für jede Funktion einen eigenen Gradientenabstieg implementieren oder aber einen allgemeinen Gradientenabstieg für alle Funktionen. Zu beachten ist, dass der hier beschriebene Gradientenabstieg recht einfach ist und auf den ersten Blick ein allgemeines Verfahren vielleicht nicht rechtfertigt. Mir geht es jedoch um das allgemeines Schema, weil ich etwas "komplizierte" Optimierungs-Verfahren implementieren möchte, die man nicht für jede Funktion kopieren möchte.

Das heißt also die Parameter von f hängen von der konkreten zu implementierenden Funktion f ab. Zum Beispiel die Funktion
Code:
f(x) = exp(-||x-m||/s)
wird dann in meiner Java-Implementierung mit
Code:
f(x, m, s)
aufgerufen. Ein Polynom
Code:
f(x) = a_0 + a_1 x + a_2 x^2
mit
Code:
f(x, a)
, wobei a vom Typ
Code:
double[]
ist.


(b) Ist das ganze denn so compilierbar ???:L (irgendwie erscheint der Cast auf "double[]" recht gewagt zu sein ...)

Der Cast ist compilierbar.

(c) Stellst Du sicher, dass immer (mindestens) drei Parameter übergeben ???:L Sind es bspw. nur zwei, dann knallt der Zugriff auf "params[2]" !!

Den Parameter-Check habe ich hier ausgelassen, weil mich in erster Linie der grundsätzliche Entwurf interessiert.

Beste Grüße

samosa
 

samosa

Mitglied
@Tobse,

danke für den Taschenrechner. Wozu ich das benötige habe ich bereits in meiner Antwort an Klaus geschrieben. Die Funktionen, die ich betrachte sind sehr allgemein. Es sind teilweise Funktionen auf Sequenzen, Bäumen und Graphen, für die Subgradientenmethoden zum Einsatz kommen, also f(x, y) ist zum Beispiel die Ähnlichkeit zweier Sequenzen x und y (optimal alignment) oder die dynamic time warping distance zwischen zwei Zeitreihen.

Gruß

samosa
 

samosa

Mitglied
Habe eben Java 8 angeschaut. Dort gibt es eine gute Lösung:

Function (Java Platform SE 8 )

Java:
Interface Function<T,R>

Type Parameters:
  • T - the type of the input to the function
  • R - the type of the result of the function

Dann geht es weiter mit

Java:
R apply(T t)

Irre.
 

Ähnliche Java Themen

Neue Themen


Oben