【问题标题】:Can I use lambdify to evaluate the derivative of a python function?我可以使用 lambdify 来评估 python 函数的导数吗?
【发布时间】:2017-05-31 12:21:19
【问题描述】:

I asked a question yesterday 关于区分 python 函数,然后当我发现发布的答案都不能满足我评估(在某些变量中)然后绘制导数的需要时,我能够找出自己的解决方案。

之前的代码:

import sympy as sym
import math


def f(x,y):
    return x**2 + x*y**2


x, y = sym.symbols('x y')

def fprime(x,y):
    return sym.diff(f(x,y),x)

print(fprime(x,y)) #This works.

print(fprime(1,1)) 

新代码:

import sympy as sym
import math


def f(x,y):
    return x**2 + x*y**2


x, y = sym.symbols('x y')

def fprime(x,y):
    return sym.diff(f(x,y),x)

print(fprime(x,y)) #This works.

DerivativeOfF = sym.lambdify((x,y),fprime(x,y),"numpy")

print(DerivativeOfF(1,1))

如您所见,我克服了无法评估导数 fprime 的问题,方法是创建一个新函数 DerivativeOfF,它是 fprime 的“lambdified”版本。从那里,我能够评估 DerivativeOfF,并将其绘制在其中一个变量中。

我的问题是:为什么会这样?我到底做了什么?这种方法有什么缺点?我曾尝试阅读lambdify 文档,但这让我非常困惑(我是Python 的初学者)。我的猜测是我将 Python 函数 fprime 转换为 Sympy 表达式 DerivativeOfF 或类似的东西。任何帮助解释发生的事情和原因,以及lambdify 究竟做了什么(用外行的话),将不胜感激。

【问题讨论】:

  • 这可能是因为第一个版本先替换了x和y,然后尝试进行导数。
  • 您的 fprime 是一个 sympy.Add 对象。它不是 Python 函数(“可调用”)。您已经使用 sympy 提供的方法或函数来“评估”它。 lambdify 就是这样一种功能。 .subs 是另一个,请尝试:fp.sums([(x,1), (y,2)])。我感觉您正在尝试使用 sympy 而不阅读它的大部分教程。不要将sympy 与基本的 Python 或 numpy 混淆。
  • @hpaulj 你说得对,我没有阅读所有教程就进入了 sympy。我什至对这些教程中的许多基础知识都感到非常困惑。以lambdify为例:它的教程页面我一个字都看不懂。我觉得有时深入研究代码会更好,尤其是当您对阅读它感到困惑时。当我感到困惑并且教程没有帮助时,我就会在这里提出问题。
  • @hpaulj 我上面的lambdify代码是做什么的?它真的将 sympy 函数转换为对应的 numpy 函数吗?如果这是真的,为什么我能够使用 sympy 来区分lambdify 结果,如果 numpy 函数无法区分?感谢您的宝贵时间。
  • 更正。 f 是一个 Python 函数。 f(x,y) 是一个同情对象。它是由sym.symbols('x y') 隐式创建的。 sym.diff 使用 f(x,y),而不是 f。你不能打电话给f(x,y),但你可以lambdify它。

标签: python python-3.x numpy sympy lambdify


【解决方案1】:

让我们看看我能不能说明这个动作。我很了解 Python 和 numpy,但没有使用过很多 sympy(但使用过其他符号代数包,如 macsyma)。

在 ipython numpy 会话中:

In [1]: def f(x,y):
   ...:     return x**2 + x*y**2
   ...: 
In [2]: f(1,3)
Out[2]: 10
In [3]: f(np.arange(1,4), np.arange(10,13))
Out[3]: array([101, 246, 441])

f是一个python函数;它返回的内容取决于输入如何处理***+ 等操作。标量和数组有效。列表处理 +*(连接、复制)但不是 **

In [4]: import sympy as sym
In [5]: x, y = sym.symbols('x y')
In [6]: type(x)
Out[6]: sympy.core.symbol.Symbol
In [7]: x+y
Out[7]: x + y
In [8]: type(_)
Out[8]: sympy.core.add.Add

定义symbols 会创建几个新对象。他们以自己的符号方式处理+ 等。

In [9]: fsym = f(x,y)
In [10]: type(fsym)
Out[10]: sympy.core.add.Add
In [11]: print(fsym)
x**2 + x*y**2

使用这 2 个符号对象调用 f 会创建一个新的 sym 对象。我也可以用其他符号和数字甚至数组的组合来调用它。

In [12]: f(x,0)
Out[12]: x**2
In [13]: f(1,x)
Out[13]: x**2 + 1
In [14]: f(np.arange(3), x)
Out[14]: array([0, x**2 + 1, 2*x**2 + 4], dtype=object)

如果我将这个Add 对象传递给sym.diff,我会得到一个新的Add 对象

In [15]: fprime = sym.diff(fsym,x)
In [16]: fprime
Out[16]: 2*x + y**2

fsymfprime 均不可调用。它们不是 Python 函数。 fsym(1,2) 不起作用。

但是fsym.subs可以用来替换x或/和y为其他值,无论是数字还是其他符号:

In [19]: fsym.subs(x,1)
Out[19]: y**2 + 1
In [20]: fsym.subs(y,2*x)
Out[20]: 4*x**3 + x**2
In [21]: fsym.subs([(x,1),(y,2)])
Out[21]: 5
In [22]: fprime.subs([(x,1),(y,2)])
Out[22]: 6

lambdify 是一个 sympy 函数,它接受一个 sympy 对象并返回一个 Python 函数,可能是 numpy compatible`。

In [24]: fl = sym.lambdify((x,y), fsym, "numpy")
In [25]: fl
Out[25]: <function numpy.<lambda>>
In [26]: fl(1,2)
Out[26]: 5
In [27]: fl(np.arange(1,4), np.arange(10,13))   # cf with f(same) above
Out[27]: array([101, 246, 441])

这个fl 函数类似于原来的f。它不完全相同,例如它有一个help/doc 表达式。

lambdify 应用于fprime 做同样的事情,但符号表达式不同:

In [28]: fpl = sym.lambdify((x,y), fprime, "numpy")
In [29]: fpl(1,2)
Out[29]: 6
In [30]: fpl(np.arange(1,4), np.arange(10,13))
Out[30]: array([102, 125, 150])

python/numpy 函数或表达式与 sympy 函数或表达式之间的这种透明度是有限制的。另一个(已删除)答案试图探索这些。例如math.sinnumpy.sinsym.sin 之间存在差异。

在这些示例中,sym.diff 函数象征性地进行微分。

In [35]: fsym
Out[35]: x**2 + x*y**2
In [36]: fprime
Out[36]: 2*x + y**2

sym.lambdify 只是将这些 sympy 对象中的任何一个转换为 Python 函数的一种方式。

触发示例

在讨论中选择示例以获得其他答案

定义一个使用sym 版本的 sin/cos 的函数:

In [53]: def f1(x,y):
    ...:     return sym.sin(x) + x*sym.sin(y)
    ...: 
In [54]: f1(x,y)
Out[54]: x*sin(y) + sin(x)
In [55]: f1(1,2)
Out[55]: sin(1) + sin(2)
In [56]: f1(1, np.arange(3)
...
SympifyError: Sympify of expression 'could not parse '[0 1 2]'' failed, because of exception being raised:
SyntaxError: invalid syntax (<string>, line 1)

我认为这是因为sym.sin(&lt;array&gt;) 不起作用;它必须是np.sin(...),但这不适用于符号。

和以前一样,我们可以取符号导数:

In [57]: sym.diff(f1(x,y),x)
Out[57]: sin(y) + cos(x)
In [58]: sym.diff(f1(x,y),y)
Out[58]: x*cos(y)
In [59]: sym.diff(sym.diff(f1(x,y),x),y)
Out[59]: cos(y)

同样没有这些功能。必须使用subslambdify 进行评估。

In [60]: f2 = sym.lambdify((x,y),f1(x,y),"numpy")
In [61]: f2
Out[61]: <function numpy.<lambda>>
In [62]: f2(1, np.arange(3))
Out[62]: array([ 0.84147098,  1.68294197,  1.75076841])

我可以使用数组输入评估f2,而我无法使用f1。大概是sympynp.sin 代替sym.sin

事实上,当我尝试用符号评估 f2 时,numpy 抱怨:

In [63]: f2(1,y)
...
/usr/local/lib/python3.5/dist-packages/numpy/__init__.py in <lambda>(_Dummy_30, _Dummy_31)
AttributeError: 'Symbol' object has no attribute 'sin'

In [66]: sym.diff(f2(x,y),x)
 ....
AttributeError: 'Symbol' object has no attribute 'sin'

【讨论】:

  • 我还没有完全阅读这个答案,但我很感动你在写这篇文章时遇到了麻烦。非常感谢。我会阅读它,如果我有任何问题,我会发表评论。
  • 我刚刚读完您的出色回答。关于最后的例子:你说“大概sympy 已经用np.sin 代替了sym.sin。”看起来应该是这样,但我们仍然可以将sym.diff 应用于f2,这让我觉得肯定有其他事情发生。如果 f2 由于lambdify 现在应该由 numpy 函数组成,你知道为什么我们仍然可以将 sym.diff 应用到 f2 吗?
  • 我得到同样的错误。 sym.diff 不在 Python 或 numpy 函数上运行,而是在 sym 对象上运行,该对象可以通过将符号传递给函数来生成(这不是唯一的方法)。 f2 不适用于符号,因为这种 np.sin 使用。
【解决方案2】:

在您的示例中,sym.lambdify 创建了一个看起来像的 python 函数

def DerivativeOff(x,y):
    return 2*x + y**2

这里的 numpy 后端没有任何作用,因为乘法、加法和取幂是原始的 python 函数。因此,您可以将任何参数传递给 DerivativeOff,尤其是 sympy 符号。在代码末尾尝试DeravativeOff(x,y)

如果你的函数包含更复杂的表达式,python 本身无法处理,情况就会发生变化。举个例子:

def f2(x,y):
    return sym.sin(x)*y

def fprime2(x,y):
    return sym.diff(f2(x,y),x)

DerivativeOff2 = sym.lambdify((x,y), fprime2(x,y), "numpy")

print(DerivativeOff2(1,2)) #will return a number

print(DerivativeOff2(x,y)) #will give an error

在这个例子中,lambdify 需要用一些非标准的 python 替换 sin 函数。为此,它将求助于 numpy(您为这种情况指定了“numpy”)。因此 DerivatifeOff2 看起来像

def DerivativeOff2(x,y):
    return numpy.cos(x)*y

显然 numpy 无法处理 sympy 符号...

现在如果你只想绘图,sympy 有一些绘图模块(依赖于 matplotlib):http://docs.sympy.org/latest/modules/plotting.html

您甚至可以用它制作 3d 图。

编辑: 以下也有效:

import sympy as sym
def f(x,y):
    return sym.sin(x) + x*sym.sin(y)
def fprime(x,y):
    return sym.diff(f(x,y),y)
x, y = sym.symbols('x y')
print(fprime(1,y)) #works perfectly fine
print(fprime(x,1)) #does not work because it would mean to derive with respect to 1
print(fprime(x,y).subs(y,1)) #works, derives with respect to y, then substitutes 1 for y

【讨论】:

  • 我多次阅读您的回答。所以基本上你不能评估一个同情的表达。就像我说 symcos = sym.cos(x),那么我不能做 symcos(1)。但是如果我做了 npcos = np.cos(x),那么 python 可以计算 npcos(1)。因此lambdify 将替换我所有的符号。 np 的输出函数。函数,然后我可以评估它们并绘制它们。为了在您的答案中填写(在我看来)缺失的细节,fprime2(x,y) 返回 sym.cos(x)*y 而 DerivativeOff2(x,y) 返回 numpy.cos(x)*y。我说的对吗?
  • 现在我怀疑您的回答,因为以下原因:我以稍微复杂的方式使用了它。我有 f(x,y) = sym.sin(x) + x*sym.sin(y)。例如,我创建了 python 函数 fprime(x,y) 以返回 sym.diff(f(x,y),x)。现在,如果我想对 y 求导,比如 fprime(1,y),我不能这样做。但如果我对 fprime(x,y) 进行lambdify,我可以做到这一点。说:fPRIME(x,y) = lambdify((x,y),fprime(x,y),"numpy")。然后我可以评估 fPRIME(1,y),甚至区分:fyPRIME = sym.diff(fPRIME(1,y),y)。如果 sym 表达式是,为什么允许我区分 fPRIME(1,y)
  • ...现在 np 表达式?
  • 是的,fprime2(x,y) 最终返回 sym.cos(x)*y。但我不能按照你的例子 npcos=np.cos(x)。 x是什么?它不能是一个sympy符号(numpy不知道这些),如果我事先设置x=27,那么npcos=np.cos(27),我仍然不能做npcos(1)。
  • 我真的无法理解您的第二条评论。考虑以下几点:[移至我的答案以正确显示,见下文编辑]当然对我有用。
【解决方案3】:

lambdify 如果您想将 SymPy 表达式转换为可以数值计算的函数,则应使用。由于您在这里的兴趣是进行符号计算(微分),因此您不应该使用lambdify(无论如何,在您的计算中)。

问题出在这段代码

x, y = sym.symbols('x y')

def fprime(x,y):
    return sym.diff(f(x,y),x)

当您为函数设置变量名时,例如“def fprime(x, y)”,这些变量名有效地覆盖了x 的变量名和y 为函数内部的任何代码在函数上方定义。因此,代码sym.diff(f(x,y),x) 不会对从symbols('x y') 返回的符号对象进行操作,而是对传入fprime 的任何值进行操作。当然,如果你将这些对象作为参数传入,它会是一样的,但你可以传入任何东西。 f(x, y) 完全一样。

相反,我会完全避免使用函数。相反,创建两个符号表达式。

x, y = sym.symbols('x y')
expr = x**2 + x*y**2
fprime = diff(expr, x)

现在,要在某个数字上评估 fprime,您可以使用 subs

fprime.subs({x: 1, y: 1})

如果此时您想创建一个使用 NumPy 将表达式计算为数字或数字数组的快速函数,则可以使用lambdify

f = lambdify((x, y), expr, 'numpy')
import numpy as np
f(np.array([1]), np.array([2]))

(同样,作为一般规则,如果您 lambdify 使用 'numpy' 的表达式,您应该将 NumPy 数组作为参数传递给lambdified 函数)

【讨论】:

  • 感谢您的出色回答。看来我需要使用 lambdify 而不仅仅是 subs 因为我想,正如你所说,创建一个函数,以便我可以绘制导数。
  • 在这种情况下,lambdify 就是您想要的。但是 SymPy 的一般工作流程是先进行所有符号计算,然后在获得所需表达式后使用 lambdify 创建一个用于数值计算的函数。
  • 知道了。感谢您提供宝贵的 cmets。 :)
猜你喜欢
  • 2019-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-28
  • 2018-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多