一、NumPy
1.1 参考资料
这篇文章写得实在是太好了图解链接 。
1.2 Array Matrix
这两种东西是不一样的,np.array 也可以有多维,但是它的 * 就是点对点乘法,而 @ 是点乘。但是对于 np.matrix 来说,无论是 * 还是 @ 都是点乘,sp.Matrix 同理。有如下演示
a3 = [[1, 2], [3, 4]] a3 = np.matrix(a3) a4 = [[5, 6], [7, 8]] a4 = np.matrix(a4) print("np.matrix") print(a3 * a4) print(a3 @ a4) a3 = [[1, 2], [3, 4]] a3 = np.array(a3) a4 = [[5, 6], [7, 8]] a4 = np.array(a4) print("np.array") print(a3 * a4) print(a3 @ a4) i, j = sp.symbols('i, j') a3 = [[i, i], [i, i]] a3 = sp.Matrix(a3) a4 = [[j, j], [j, j]] a4 = sp.Matrix(a4) print("sp.Matrix") print(a3 * a4) print(a3 @ a4)
|
输出为
np.matrix [[19 22] [43 50]] [[19 22] [43 50]] np.array [[ 5 12] [21 32]] [[19 22] [43 50]] sp.Matrix Matrix([[2*i*j, 2*i*j], [2*i*j, 2*i*j]]) Matrix([[2*i*j, 2*i*j], [2*i*j, 2*i*j]])
|
1.3 文件操作
genfromtxt 可以从文件(或者字符串)中读取数据,并且转化成 np.Array 类型,具体用法如下
data=np.genfromtxt("data.txt", delimiter=",", dtype=int, autostrip=True, comments="#") print(type(data)) print(data) print(sum(data))
|
data.txt 如下
1, 2, 3, 4, 5 # this is a comment
|
最终输出为
<class 'numpy.ndarray'> [1 2 3 4 5] 15
|
如果文件是多行的,那么就会读出一个二维的 Array 来,新的 data.txt
1, 2, 3, 4, 5 # this is a comment. 6, 7, 8, 9, 10 # this is a comment, too. 11, 12, 13, 14, 15
|
最终的结果是
<class 'numpy.ndarray'> [[ 1 2 3 4 5] [ 6 7 8 9 10] [11 12 13 14 15]]
|
如果想要控制读取的数据的范围,还可以
data=np.genfromtxt("data.txt", delimiter=",", dtype=int, autostrip=True, comments="#", usecols=range(4), skip_header=1, skip_footer=1)
|
其中的参数
| 参数 |
含义 |
| fname |
文件名 |
| delimiter |
分隔符 |
| dtype |
数据的类型 |
| autostrip |
自动去掉空白符 |
| comments |
从指定符号后部分视为注释 |
| skip_header |
在开头跳过几行 |
| skip_footer |
在结尾跳过几行 |
| usecols |
读取的列数 |
1.4 SymPy 和 NumPy
这个问题我也没有太好的思考,只能说尽管两者十分相似,比如说都有 array 结构,但是二者是并不能显然的混用的,numpy 本质上操作的依然是 python 基础的数据类型,但是 SymPy 操作的基本对象,已经变成了它定义的对象了。
换句话说,sympy 的隔离性更好。
二、SymPy
2.1 变量
有两种方法,似乎并没有差别
x, y = sp.symbols('x, y') k = sp.var('k')
|
最后声明出来的 x, y, k 都是符号对象,如下所示
print(type(x)) <class 'sympy.core.symbol.Symbol'>
|
我们可以通过在声明上加假设条件,来获得更加精确的结果,比如说
x_normal = sp.symbols('x_normal')
print(x.is_Symbol) x_positive = sp.symbols('x_positive', positive=True)
print(x_positive.is_positive) x_real = sp.symbols('x_real', real=True)
print(x_real.is_real)
print(x_real.is_imaginary)
print(x_real.is_complex) x_integer = sp.symbols('x_integer', integer=True)
print(x_integer.is_integer)
|
另外对于变量的声明也有一些批量声明的技术
x_array = sp.symbols('x0:5') print(x_array) output: (x0, x1, x2, x3, x4)
|
2.2 常量
为了保护常量的 “纯洁性”,我们可以用 sympy.S() 来声明常量,比如说
print(1 / 13) print(sp.S(1) / sp.S(13))
|
可以看到常量的纯洁性被很好的保护了起来。
我们可以看一下常量的类型
print(type(sp.S(1))) print(type(sp.S(13)))
|
可以看到似乎都是在 core.numbers 中的
除了可以自己设置常量,sympy 还提供了一些默认的常量
print(sp.pi) print(type(sp.pi)) print(sp.I) print(type(sp.I)) print(type(sp.oo))
|
2.3 表达式
为了表示 a + b,可以使用 sp 的内置函数
a, b = sp.symbols('a, b') print(sp.Add(a, b))
|
当然方便的 python 肯定会重载运算符,所以一定有以下的等价写法
与之类似的还有 Pow, Add, Sin, Cos, Mul 之类的,能完成重载的,都完成了重载。
e = sp.Add(sp.Mul(a, b, c), sp.Pow(a, b), sp.sin(c)) print(e)
|
最后输出为
本质是和这个相同的
e_2 = a*b*c + a**b + sp.sin(c) print(e_2)
|
Add 不是一个方法,而是一个类(所以上面的方法其实是构造器方法),他有两个重要的属性,func 属性得到对象的类,而 args 得到其参数。使用这两个属性可以观察 SymPy 所创建的表达式
t = x - y print(t.func) print(t.args)
|
输出为
<class 'sympy.core.add.Add'> (x, -y)
|
其实本质上说,构建的表达式就和 TensorFlow 一样,是构建了一幅表达式树。我们可以写一个函数来显示这个事情
def print_expression(e, level=0): spaces = " " * level if level != 0: spaces = spaces[:-3] spaces += "|__" if isinstance(e, (sp.Symbol, sp.Number)): print(spaces + str(e)) return if len(e.args) > 0: print(spaces + e.func.__name__) for arg in e.args: print_expression(arg, level + 1) else: print(spaces + e.func.__name__)
e_2 = a * b * c + a**b + sp.sin(c) print_expression(e_2)
|
可以看到输出如图
Add |__Pow |__a |__b |__Mul |__a |__b |__c |__sin |__c
|
似乎只要是大写开头的构造器方法,都会生成一个节点,比如说更加复杂的函数 Function
e_3 = sp.Eq(sp.Derivative(x**2+y**2, x), 7) print(e_3) print_expression(e_3)
|
会输出
Equality |__Derivative |__Add |__Pow |__x |__2 |__Pow |__y |__2 |__Tuple |__x |__1 |__7
|
正如 tensorFlow 一样,只要不执行,这个东西绝对不会运算,但是似乎这里的运算不只有 run 这一种形式。回头细说。
2.4 自定义函数
我们可以自己定义函数,两种形式是等价的
f = sp.Function("f") print(type(f)) g = sp.symbols("g", cls=sp.Function) print(type(g))
|
输出为
<class 'sympy.core.function.UndefinedFunction'> <class 'sympy.core.function.UndefinedFunction'>
|
与之形成对比的是
print(type(sp.Derivative(x**2 + y**2, x)))
|
输出为
<class 'sympy.core.function.Derivative'>
|
请注意 Function 虽然是一个类,但是上面的语句所得到的 f 并不是 Function 类的实例。和预定义的数学函数一样,f 是一个类,它从 Function 类继承。只有指定了具体的参数(本质是将 f 做为一个构造器),才可以实例化出这样的函数,如下所示:
t = f(x, y) print(type(t)) print(t.func) print(t.args)
|
有一个很有意思的事情,就是自定义函数并没有我们常以为的 “表示某种特定的符号和运算符的组合” 的效果,因为这个定义其实是
sp.Derivative(x**2 + y**2, x)
|
就可以完成的。所以 f(x, y) 就真的只是 f(x, y) ,我们并不知道里面是啥内容,而且永远不知道,它就真的只是一个函数符号的意思。
所以我们一般用它来实现函数本身的求解(不是求解某个变量)。比如求解数列的通项公式,微分方程的求解。
2.5 关系
最近只用到了 Eq 来构造方程,里面似乎包括很多种,比如说 Less 啥的
在 SymPy 中,表达式可以直接表示值为 0 的方程,这使得后面的各种操作会更加灵活。
2.6 矩阵
矩阵的声明方法与 numpy 中的类似
A = sp.Matrix([[x, a], [b, y]]) print(A) print(A.is_Matrix) print(type(A))
|
输出为
Matrix([[x, a], [b, y]]) True <class 'sympy.matrices.dense.MutableDenseMatrix'>
|
并且和 numpy 一样,都重载了运算符
print(A * 3) print(type(A * 3)) print(A @ B) print(type(A @ B)) print(A.T) print(A**2) print(type(A**2))
|
输出为
Matrix([[3*x, 3*a], [3*b, 3*y]]) <class 'sympy.matrices.dense.MutableDenseMatrix'> Matrix([[2*a + x], [b + 2*y]]) <class 'sympy.matrices.dense.MutableDenseMatrix'> Matrix([[x, b], [a, y]]) Matrix([[a*b + x**2, a*x + a*y], [b*x + b*y, a*b + y**2]]) <class 'sympy.matrices.dense.MutableDenseMatrix'>
|
可以看到,不再有表达式树的结构了。希望表达式树,似乎应该求助 TensorFlow 。
2.7 操作
运算一般都是对于表达式对象或者方程对象进行操作。
2.7.1 变形
化简
simplify((x+2)**2 - (x+1)**2)
|
展开
替换
xn = c1 * lams[0]**n + c2 * lams[1]**n x0 = xn.subs(n, 0) x1 = xn.subs(n, 1)
|
可以利用 subs 将某个表达式换成一个新的表达式(不一定非得是常量)。
级数展开
print((1/cos(x)).series(x, 0, 10))
|
输出
1 + x**2/2 + 5*x**4/24 + 61*x**6/720 + 277*x**8/8064 + O(x**10)
|
2.7.2 求解
solve 可以求解普通方程
也可以求解方程组,后面需要依次写定要求解的变量,比如这里的 x, y
solve ((x**2+x*y+1, y ** 2+x*y+2 ), x, y )
|
dsolve 用于微分方程的求解
dsolve(Derivative(f(x),x) - f(x), f(x))
|
rsolve 用于递归表达式的求解(可以用于解差分方程),相对于需要给出带求解的通项表达式,还需要给出初始值。
n = sp.var('n')
x = sp.Function('x') f = 2 * x(n + 2) - x(n + 1) - 2 * x(n)
xn = sp.rsolve(f, x(n), {x(0): -2, x(1): 0})
|
2.7.3 微积分
微积分都用两种方式,因为微分和积分都是有固定的 Function 去描述的,所以可以直接求值表达式树,如下
t = Derivative(sin(x), x) print(t.doit())
|
当然也可以直接用 diff ,并且更加直观
diff(sin(2*x), x) sin(2*x).diff(x)
|
对于多个变量求高阶导数,可以有如下形式
print(diff(sin(x * y), x, 2, y, 3))
|
利用 Integral 进行不定积分
e = Integral(x*sin(x), x) e.doit()
|
进行定积分
e2 = Integral(sin(x)/x, (x, 0, 1)) e2.doit() e2.evalf()
|
不过 SymPy 本质是符号运算系统,而定积分本质是数值运算,所以很有可能运算不出来。
对于多重定积分,格式如下
integrate(f, (x, a, b), (y, c, d))
|
对应的表达式如下
c∫da∫bf(x,y)dxdy
2.7.4 求极限
limit(sin(x)/x, x, 0) limit(sin(x)/x, x, oo)
|
三、SciPy
3.1 线性规划
首先谈一下对于线性规划的理解
object:z=max c⋅xconstaint:A⋅x=bbound(constaint):x≥0
本质上,最终的求解结果 z ,可以看做是一个涉及 c,A,b 的隐函数,再加上边界条件,我们就可以完成求解,而实际也是这样的,scipy 中的 linprog 就是这样,只需要输入这些东西,就可以获得一个最大值 z 的结果。其函数签名(简化版)如下
linprog(c, A_ub, b_ub, A_eq, b_eq, bounds)
|
多说无益,举个复杂的例子
minz=−2x1−3x2+5x3st.⎩⎨⎧x1+x2+x3=72x1−5x2+x3≥10x1+3x2+x3≤12x1,x2,x3≥0
其求解代码如下
import scipy as sp import numpy as np
c = np.array([2, 3, -5]) A_eq = np.array([[1, 1, 1]]) b_eq = np.array([7]) A_ub = np.array([[-2, 5, -1], [1, 3, 1]]) b_ub = np.array([-10, 12]) bounds = [(0, 7), (0, 7), (0, 7)]
result = sp.optimize.linprog(-c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds) print('最优解为:', result.x) print('最优值为:', result.fun)
|
输出如下
最优解为: [6.42857143 0.57142857 0. ] 最优值为: -14.571428571428573
|
3.2 拟合
scipy 有内置的拟合函数,大致写法如下
scipy.optimize.curve_fit(f, xdata, ydata, p0, maxfev)
|
其参数有:
f :拟合函数的形式,是一个函数,其函数参数就是我们拟合的时候需要的东西
xdata :自变量数据
ydata :因变量数据
p0 :起始数据(给出的是 f 的参数),可以不指定,指定后有可能会使迭代变快,不过更多的用于规范参数的格式(f 有可能含有变长参数)
maxfev :最大拟合次数
其返回值为
popt: 数组,参数的最佳值,以使的平方残差之和最小。f(xdata, *popt) - ydata pcov: 二维阵列,popt的估计协方差。对角线提供参数估计的方差。
|
可以看出,我们最想要的是 popt ,对于 pcov 课程不太涉及。
常见例子为
def my_func1(x, w1, a1, b1, w2, a2, b2): return w1 * Gauss(x, a1, b1) + w2 * Gauss(x, a2, b2) popt = optimize.curve_fit(my_func1, x_train, y_train, maxfev=500000)[0]
|
四、CvxPy
4.1 更加自然
cvxpy 是在学线性规划的时候介绍的,在我的理解中,线性规划问题可以用 SciPy 库去求解,cvxpy 似乎是为了让线性规划的描述更加自然而开发的包,比如说在 3.1 具的例子,如果用 cvxpy 来写的话,可以写成这样
minz=−2x1−3x2+5x3st.⎩⎨⎧x1+x2+x3=72x1−5x2+x3≥10x1+3x2+x3≤12x1,x2,x3≥0
x = cp.Variable(3, pos=True) obj = cp.Minimize(-2 * x[0] - 3 * x[1] + 5 * x[2]) cons = [ x[0] + x[1] + x[2] == 7, 2 * x[0] - 5 * x[1] + x[2] >= 10, x[0] + 3 * x[1] + x[2] <= 12, ]
prob=cp.Problem(obj, cons) prob.solve()
print("最优值为:", prob.value) print("最优解为:", x.value)
print(type(x)) print(type(obj)) print(type(cons))
|
最后输出为
最优值为: -14.571428570710513 最优解为: [6.42857143 0.57142857 0. ] <class 'cvxpy.expressions.variable.Variable'> <class 'cvxpy.problems.objective.Minimize'> <class 'list'>
|
4.2 凸优化
cvxpy 本质上是一个凸优化库,所以不是凸优化的问题是没有办法用这个库来求解的。
五、Matplotlib
5.1 架构
如图所示

但是似乎学不学的吧,因为这种比较普世和灵活,如果只是为了应付简单的画图任务,完全不需要这种复杂的结构,其比较规范的结构如下所示
import matplotlib.pyplot as plt import numpy as np
x = np.arange(0, np.pi*2, 0.05) y = np.sin(x)
fig = plt.figure()
axes1 = fig.add_axes([0.1, 0.1, 0.8, 0.8]) axes2 = fig.add_axes([0.55, 0.55, 0.3, 0.3])
axes1.plot(x, y, 'b') axes2.plot(x,np.cos(x),'r')
axes1.set_title('sine') axes2.set_title("cosine")
plt.show()
|
其效果如下,可以看到很灵活

另外我们一般只用 pyplot,虽然我不知道为啥,但是写成这样就可以了
from matplotlib import pyplot as plt
|
5.2 基本操作
首先说,下面的代码似乎并不规整,规整的代码是先创建一个 figure 然后再其上创建 Axes 然后再干一大堆事情,但是我懒了,所以就选了一些比较简单的方法。可以说,上面的写法是面向对象的,而下面的写法是面向过程的。
5.2.1 画一个图
from matplotlib import pyplot as plt import numpy as np
x = np.arange(0, np.pi * 2, 0.05) y = np.sin(x)
plt.plot(x,y)
plt.xlabel("angle") plt.ylabel("sine") plt.title('sine wave')
plt.show()
|
5.2.2 在一张图上画两个曲线
from matplotlib import pyplot as plt import numpy as np
x = np.arange(0, np.pi * 2, 0.05) y1 = np.sin(x) y2 = np.cos(x)
plt.plot(x, y1, 'b-') plt.plot(x, y2, 'r*')
plt.xlabel("angle") plt.ylabel("sine") plt.title('sine wave')
plt.show()
|
5.2.3 在一个画布上画多张图
import numpy as np import matplotlib.pyplot as plt
x = np.arange(0.1, 100)
plt.subplot(2,2,1)
plt.plot(x, x)
plt.subplot(2, 2, 2) plt.plot(x, -x)
plt.subplot(2, 2, 3) plt.plot(x, x**2)
plt.grid(color='r', linestyle='--', linewidth=1, alpha=0.3)
plt.subplot(224) plt.plot(x, np.log(x))
plt.show()
|
其效果如下:

5.2.4 多种不一样的图
import numpy as np import matplotlib import matplotlib.pyplot as plt
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] matplotlib.rcParams['font.family']='sans-serif'
matplotlib.rcParams['axes.unicode_minus'] = False
x = np.arange(0.1, 10)
plt.subplot(3, 2, 1)
plt.stackplot(x, x)
plt.subplot(3, 2, 2) plt.bar(x, x)
plt.subplot(3, 2, 3) plt.stem(x, x**2)
plt.subplot(324) plt.scatter(x, np.log(x))
plt.subplot(325) labels=['第一季度', '第二季度', '第三季度', '第四季度'] data=[23, 45, 50, 40] explodes=[0.1,0.1,0.1,0.1]
plt.pie(x=data,labels=labels,explode=explodes,autopct="%0.1f%%",shadow=True)
plt.savefig("plt.png") plt.show()
|
效果如下:

5.2.5 保存图像
plt.savefig("plt.png")
plt.show()
|
5.2.6 图例
可以通过在绘图的时候增加 label 参数,然后用 plt.lengend(1) 来解决
import matplotlib.pyplot as plt import numpy as np import math x1 = np.arange(0,5.1,0.01) x1 = math.pi*x1 y1 = np.sin(x1) y2 = np.cos(x1)
p1 = plt.plot(x1,y1,label='sin(x)',color='b') p2 = plt.plot(x1,y2,label='cos(x)',color='r') plt.xlim(0,5)
plt.legend(loc=1) plt.show()
|
效果

5.3 解决乱码问题
按照如下操作即可在 win 下解决问题
import matplotlib import matplotlib.pyplot as plt
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] matplotlib.rcParams['font.family']='sans-serif'
matplotlib.rcParams['axes.unicode_minus'] = False
|
但是如果是 manjaro 需要拷贝字体:
- 将 windwos 中
fonts 目录下的 simhei.ttf 拷贝到 /.../lib/python2.7/site-packages/matplotlib/mpl-data/fonts/ttf (文件路径根据实际情况修改) 目录中。
- 删除
~/.cache/matplotlib 的缓冲目录