【问题标题】:Automatic Differentiation in C# and F#C# 和 F# 中的自动微分
【发布时间】:2010-09-08 19:53:38
【问题描述】:

我在让自动区分在 C# 和 F# 之间工作时遇到问题。

在 C# 中,我有一个接受双精度并返回双精度的函数,比如:

private double Price(double yield)
{
    double price;

    price = 0;

    for (int index = 1; index <= _maturity * _frequency; index++)
    {
        price += (_coupon / _frequency) * _nominal / Math.Pow(1 + (yield / _frequency), index);
    }

    price += _nominal / Math.Pow(1 + (yield / _frequency), _maturity * _frequency);

    return price;
}

我专门选择了这个函数,因为 Math.pow 非常禁止,它的参数只允许使用 double 或 int。

我想使用自动微分来区分这个函数。我已经在 F# 中为此编写了方法:

type Diff(d : double, df : Lazy<Diff>) = class
    member x.d = d
    member x.df = df
    static member (+) (x : Diff, y : Diff) = 
        Diff(x.d + y.d, lazy (x.df.Value + y.df.Value)) 
    static member (-) (x : Diff, y : Diff) = 
        Diff(x.d - y.d, lazy (x.df.Value - y.df.Value))
    static member (*) (x : Diff, a : double) = 
        Diff(x.d * a, lazy (x.df.Value * a))
    static member (*) (x : Diff, y : Diff) = 
        Diff(x.d * y.d, lazy ((x.df.Value * y) + (y.df.Value * x)))
    override x.ToString() =
        x.d.ToString()
end

let rec dZero = Diff(0.0, lazy dZero)

let dConst x = Diff(x, lazy dZero)

let dId x = Diff(x, lazy dConst 1.0)

let Differentiate (x:Diff) = x.df.Value

// Example function
let f (x:Diff) = x*x*x;

// Example usage:
// (f (dId 5)).ToString = "125"
// (Differentiate (f (dId 5))).ToString = "75"
// (Differentiate (Differentate (f (dId 5)))).ToString = "30"

不幸的是,我需要将类型 Diff 输入到我的 Price(..) 函数中以生成类型 Diff,然后将其输入到我的 Differente(..) 函数中以返回另一种类型 Diff。

然而,我的 C# 函数仅适用于双精度(我希望它保持这种状态,因为它在我的 C# 程序的其他地方使用)。

我能想到解决这个问题的唯一方法是将每个函数编写两次,这显然很糟糕:

1) 还不如每次都写一个差异化的版本 2) 这不是一个非常可扩展的模型

那么有什么办法可以解决这个问题,或者可能将我的双重函数强制转换为 Diff 函数(最好在 F# 中)。理想情况下,我只想抛出一个 (double -> double) 函数并得到一个 Diff.ToString() 。

抱歉,如果这完全模糊或无法理解。如果不清楚,我会在 cmets 中回答任何问题。

我希望有一个解决方案!提前致谢,

阿什利

【问题讨论】:

  • 我不确定我是否明白你在这里问什么。如果我正确阅读了您的问题,您似乎希望能够在使用示例函数f 时使用您的Price 方法(未修改)。但是f 使用您的自定义运算符,而对双精度进行操作的 C# 方法将始终使用 double 运算符。
  • 请注意,如果您只对导数的数值感兴趣而没有符号项,只需使用近似公式:f'(x) = (f(x+h)-f(x))/h 用于一些小的 h
  • @dtb 是的,你完全正确。理想情况下,我想对函数参数使用转换/强制,以将它们从 double 转换为 Diff。
  • @Dario 是的,我只对数值导数感兴趣。然而,使用近似有两个问题。 1)它非常不准确(价格),2)h 需要从一个函数变为另一个函数并使用变量 x 来优化该方法,因此我更喜欢一个更通用的。
  • maturity=5frequency=10coupon=11nominal=13 我得到Price(Id(2))==71.493Differentiate(Price(Id(2)))==-423.782Differentiate(Differentiate(Price(Id(2))))==5039.610。对吗?

标签: c# f# automatic-differentiation


【解决方案1】:

您可以重新发明 Haskell 类型类:

interface Eq<T>
{
    bool Equal(T a, T b);
    bool NotEqual(T a, T b);
}

interface Num<T> : Eq<T>
{
    T Zero { get; }
    T Add(T a, T b);
    T Subtract(T a, T b);
    T Multiply(T a, T b);
    T Negate(T a);
}

sealed class Int : Num<int>
{
    public static readonly Int Instance = new Int();
    private Int() { }
    public bool Equal(int a, int b) { return a == b; }
    public bool NotEqual(int a, int b) { return a != b; }
    public int Zero { get { return 0; } }
    public int Add(int a, int b) { return a + b; }
    public int Subtract(int a, int b) { return a - b; }
    public int Multiply(int a, int b) { return a * b; }
    public int Negate(int a) { return -a; }
}

那么你可以这样做:

static T F<M, T>(M m, T x) where M : Num<T>
{
    return m.Multiply(x, m.Multiply(x, x));
}

static void Main(string[] args)
{
    Console.WriteLine(F(Int.Instance, 5));  // prints "125"
}

然后用:

class Diff
{
    public readonly double d;
    public readonly Lazy<Diff> df;

    public Diff(double d, Lazy<Diff> df)
    {
        this.d = d;
        this.df = df;
    }
}

class DiffClass : Floating<Diff>
{
    public static readonly DiffClass Instance = new DiffClass();
    private static readonly Diff zero = new Diff(0.0, new Lazy<Diff>(() => DiffClass.zero));
    private DiffClass() { }
    public Diff Zero { get { return zero; } }
    public Diff Add(Diff a, Diff b) { return new Diff(a.d + b.d, new Lazy<Diff>(() => Add(a.df.Value, b.df.Value))); }
    public Diff Subtract(Diff a, Diff b) { return new Diff(a.d - b.d, new Lazy<Diff>(() => Subtract(a.df.Value, b.df.Value))); }
    public Diff Multiply(Diff a, Diff b) { return new Diff(a.d * b.d, new Lazy<Diff>(() => Add(Multiply(a.df.Value, b), Multiply(b.df.Value, a)))); }
    ...
}

你可以这样做:

static T Price<M, T>(M m, T _maturity, T _frequency, T _coupon, T _nominal, T yield) where M : Floating<T>
{
    T price;

    price = m.Zero;

    for (T index = m.Succ(m.Zero); m.Compare(index, m.Multiply(_maturity, _frequency)) <= 0; index = m.Succ(index))
    {
        price = m.Add(price, m.Divide(m.Multiply(m.Divide(_coupon, _frequency), _nominal), m.Power(m.Add(m.Succ(m.Zero), m.Divide(yield, _frequency)), index)));
    }

    price = m.Add(price, m.Divide(_nominal, m.Power(m.Add(m.Succ(m.Zero), m.Divide(yield, _frequency)), m.Multiply(_maturity, _frequency))));

    return price;
}

但这不是很漂亮。

事实上,它读起来几乎就像创建 LINQ 表达式树的代码。或许可以用源码表达式树变换代替算子重载来实现自动微分

【讨论】:

  • 真的想要更多解释,这个答案会很震撼!不过谢谢
  • 我认为这个答案过于抽象了。请参阅下面的答案,了解自动微分的超级简单 C# 实现,它保留了原始函数的大部分性能,同时允许在需要时进行微分。
【解决方案2】:

没有办法使用现有的 C# 函数,也没有任何简单的方法可以将其提升为可以对 Diff 类型的成员进行操作的函数。一旦函数被编译,它是不透明的,内部结构是不可用的;您所能做的就是使用双参数调用该函数并获得双精度结果。此外,您的 Price 方法使用了您甚至还没有在 Diff 类上定义的操作((\)Pow)。

我不确定它是否适合您的目的,但一种可能的替代方法是在 F# 中编写 Price 函数的通用内联版本,然后可以在双精度或 Diffs 上运行(假设您添加了(\)Pow 运算符)。

【讨论】:

    【解决方案3】:

    如果您只需要简单的自动微分,则先前有关“重新实现类型类”的答案完全是矫枉过正。使用运算符重载并将导数具体化为数组,如I show in this project。您需要的核心类型只是:

    public readonly struct Number
    {
        public readonly double Magnitude;
        public readonly double[] Derivatives;
    
        internal Number(double m, params double[] d)
        {
            this.Magnitude = m;
            this.Derivatives = d;
        }
    }
    

    然后实现operator translations shown on Wikipedia,这样Number上的每个操作符也对Derivatives数组进行操作。

    您需要将您的函数定义为对该Number 类型进行操作,但是通过在Number 上定义全套算术运算符,这通常只是更改参数类型,并更改对静态@987654327 的任何调用@函数对应的Number.X函数,即。 Math.Sin(x) -> x.Sin().

    空/空数组主要在原始双精度数上运行,因此它可能非常接近原始代码的速度。

    【讨论】:

      猜你喜欢
      • 2017-09-13
      • 1970-01-01
      • 2021-06-07
      • 1970-01-01
      • 1970-01-01
      • 2018-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多