これは、自動微分の文脈での話です。そのようなシステムは、SKI コンビネーターのような機能を使って何をするのでしょうmap
か?filter
例: 次の機能があります。
def func(x):
return sum(map(lambda a: a**x, range(20)))
その派生物は何でしょうか?その結果、AD システムは何をもたらすでしょうか? (この関数は、実数入力に対して明確に定義されています)。
これは、自動微分の文脈での話です。そのようなシステムは、SKI コンビネーターのような機能を使って何をするのでしょうmap
か?filter
例: 次の機能があります。
def func(x):
return sum(map(lambda a: a**x, range(20)))
その派生物は何でしょうか?その結果、AD システムは何をもたらすでしょうか? (この関数は、実数入力に対して明確に定義されています)。
上位の関数を有効に区別できない、または実際にそれを実証することによってそれらの特に小さなサブセットに自分自身を制限する必要があるという受け入れられた答えに反対したいと思います。
私のHaskell'ad'パッケージを使用すると、次のようになります。
ghci> :m + Numeric.AD
ghci> diff (\x -> sum (map (**x) [1..20])) 10
7.073726805128313e13
トレースされた数値型を悪用して、導関数の質問に答えることによって、何が行われたかを抽出できます。
ghci> :m + Debug.Traced
ghci> putStrLn $ showAsExp $ diff (\x -> sum (map (**x) [1..20])) (unknown "x" :: Traced Double)
1.0 * (1.0 ** x * log 1.0) +
1.0 * (2.0 ** x * log 2.0) +
1.0 * (3.0 ** x * log 3.0) +
1.0 * (4.0 ** x * log 4.0) +
1.0 * (5.0 ** x * log 5.0) +
1.0 * (6.0 ** x * log 6.0) +
1.0 * (7.0 ** x * log 7.0) +
1.0 * (8.0 ** x * log 8.0) +
1.0 * (9.0 ** x * log 9.0) +
1.0 * (10.0 ** x * log 10.0) +
1.0 * (11.0 ** x * log 11.0) +
1.0 * (12.0 ** x * log 12.0) +
1.0 * (13.0 ** x * log 13.0) +
1.0 * (14.0 ** x * log 14.0) +
1.0 * (15.0 ** x * log 15.0) +
1.0 * (16.0 ** x * log 16.0) +
1.0 * (17.0 ** x * log 17.0) +
1.0 * (18.0 ** x * log 18.0) +
1.0 * (19.0 ** x * log 19.0) +
1.0 * (20.0 ** x * log 20.0)
完全に共有すると、かなり恐ろしい結果が得られますが、漸近的に効率が上がることもあります。
ghci> putStrLn $ showAsExp $ reShare $ diff (\x -> sum (map (**x) [1..20]))
(unknown "x" :: Traced Double)
let _21 = 1.0 ** x;
_23 = log 1.0;
_20 = _21 * _23;
_19 = 1.0 * _20;
_26 = 2.0 ** x;
_27 = log 2.0;
_25 = _26 * _27;
_24 = 1.0 * _25;
_18 = _19 + _24;
_30 = 3.0 ** x;
_31 = log 3.0;
_29 = _30 * _31;
_28 = 1.0 * _29;
_17 = _18 + _28;
_34 = 4.0 ** x;
_35 = log 4.0;
_33 = _34 * _35;
_32 = 1.0 * _33;
_16 = _17 + _32;
_38 = 5.0 ** x;
_39 = log 5.0;
_37 = _38 * _39;
_36 = 1.0 * _37;
_15 = _16 + _36;
_42 = 6.0 ** x;
_43 = log 6.0;
_41 = _42 * _43;
_40 = 1.0 * _41;
_14 = _15 + _40;
_46 = 7.0 ** x;
_47 = log 7.0;
_45 = _46 * _47;
_44 = 1.0 * _45;
_13 = _14 + _44;
_50 = 8.0 ** x;
_51 = log 8.0;
_49 = _50 * _51;
_48 = 1.0 * _49;
_12 = _13 + _48;
_54 = 9.0 ** x;
_55 = log 9.0;
_53 = _54 * _55;
_52 = 1.0 * _53;
_11 = _12 + _52;
_58 = 10.0 ** x;
_59 = log 10.0;
_57 = _58 * _59;
_56 = 1.0 * _57;
_10 = _11 + _56;
_62 = 11.0 ** x;
_63 = log 11.0;
_61 = _62 * _63;
_60 = 1.0 * _61;
_9 = _10 + _60;
_66 = 12.0 ** x;
_67 = log 12.0;
_65 = _66 * _67;
_64 = 1.0 * _65;
_8 = _9 + _64;
_70 = 13.0 ** x;
_71 = log 13.0;
_69 = _70 * _71;
_68 = 1.0 * _69;
_7 = _8 + _68;
_74 = 14.0 ** x;
_75 = log 14.0;
_73 = _74 * _75;
_72 = 1.0 * _73;
_6 = _7 + _72;
_78 = 15.0 ** x;
_79 = log 15.0;
_77 = _78 * _79;
_76 = 1.0 * _77;
_5 = _6 + _76;
_82 = 16.0 ** x;
_83 = log 16.0;
_81 = _82 * _83;
_80 = 1.0 * _81;
_4 = _5 + _80;
_86 = 17.0 ** x;
_87 = log 17.0;
_85 = _86 * _87;
_84 = 1.0 * _85;
_3 = _4 + _84;
_90 = 18.0 ** x;
_91 = log 18.0;
_89 = _90 * _91;
_88 = 1.0 * _89;
_2 = _3 + _88;
_94 = 19.0 ** x;
_95 = log 19.0;
_93 = _94 * _95;
_92 = 1.0 * _93;
_1 = _2 + _92;
_98 = 20.0 ** x;
_99 = log 20.0;
_97 = _98 * _99;
_96 = 1.0 * _97;
_0 = _1 + _96;
in _0
一般に、自動微分は上位の関数では問題ありません。ただし、特定のツールの制限によっては、ソースからソースへの変換でいくつかの問題が発生する場合があります。
優れたADシステムは、それを難なく通過します。AD コードがソースからソースへの変換を実行する場合、sum
. しかし、算術演算子をオーバーロードすることによって動作する AD システムの場合、AD コードは実際には関数を「認識」せず、関数が呼び出す操作sum
だけを認識します。+
sum
高階関数は離散的です。それらは、あるn次元空間の点への明確に定義されたマッピングを持つ引数を持つというデカルトの性質を持っていません。
しかし、答えを明確にすると、言えることがいくつかあります。いくつかの高階関数を記号的に区別することは可能ですが、それはよく知られた関数に解決される特定の呼び出しパターンに対してのみです。
おそらく、与えられた関数を繰り返し評価することによって導関数を推定できるので、数値微分はより実り多いでしょう。
また、完全に一般的な関数(そしてあなたの例は比較的任意の関数を使用してそのように進んでいます)は、最終的にチューリング完全性に到達します。これは、シンボリック微分器の側の巧妙さの量ができないことを意味します機能を自動的に区別します。