本文首发于zhengfei.xin
报名参加了2021年秋季的华为杯数学建模大赛,以往对这类比赛并无经验,比赛在即,几个月来断断续续看了些东西,故决定做一个简单总结。虽说是总结,但更多的还是使用代码解决各类问题,对各种算法的了解还远远不够。这次比赛目测是难以获奖,之后还有机会,希望能在未来的一年时间中,多抽点时间给数学建模,希望最终能取得一个还不错的成绩。
插值是离散函数逼近的重要方法,通过插值,可以依据已经得到的点数据来推算未给出的点的数据
插值通常选用Python SciPY中的interpolate来实现,其中的interp1d函数即可实现一维插值的功能。
该函数有3个参数,除x、y轴点坐标外,“kind”参数指明了插值的方式,具体如下表。
值 | 效果 |
---|---|
‘zero’,‘nearest’ | 阶梯插值,即0阶B样条曲线 |
‘slinear’,‘linear’ | 线性插值,用一条直线连接所有的取样点,相当于1阶B样条曲线 |
‘quadraic’,‘cubic’ | 二阶和三阶B样条曲线,更高阶的曲线可以直接使用整数值指定 |
样条插值是使用一种名为样条的特殊分段多项式进行插值的形式。由于样条插值可以使用低阶多项式样条实现较小的插值误差,这样就避免了使用高阶多项式所出现的龙格现象,所以样条插值得到了流行
import numpy as np
from scipy import interpolate
import pylab as pl
x = np.linspace(0, 10 * np.pi, 5)
y = np.sin(x)
fc = interpolate.interp1d(x, y, kind="cubic")
xint = np.linspace(x.min(), x.max(), 5)
pl.plot(xint, fc(xint), color="red", label="interpid")
pl.savefig("test.jpg")
下表给出了待加工零件下轮廓线的一组数据,现需要得到x坐标每改变0.1时所对应的y的坐标
x | y |
---|---|
0 | 0 |
3 | 1.2 |
5 | 1.7 |
7 | 2.0 |
9 | 2.1 |
11 | 2.0 |
12 | 1.8 |
13 | 1.2 |
14 | 1.0 |
15 | 1.6 |
解题代码如下:
import numpy as np
import pylab as pl
from scipy import interpolate
x = np.array([0, 3, 5, 7, 9, 11, 12, 13, 14, 15])
y = np.array([0, 1.2, 1.7, 2.0, 2.1, 2.0, 1.8, 1.2, 1.0, 1.6])
fc = interpolate.interp1d(x, y, kind="cubic")
result = np.linspace(x.min(), x.max(), 1000)
print(result)
print(fc(result))
二维插值与一维类似,示例代码如下
import numpy as np
from scipy import interpolate
import pylab as pl
import matplotlib as mpl
def func(x, y):
return (x + y) * np.exp(-5 * (x ** 2 + y **2))
y, x = np.mgrid[-1:1:15j, -1:1:15j]
newFunc = interpolate.interp2d(x, y, func(x, y), kind='cubic')
xNew = np.linspace(-1, 1, 100)
yNew = np.linspace(-1, 1, 100)
fNew = newFunc(xNew, yNew)
pl.imshow(fNew, extent=[-1, 1, -1, 1], cmap=mpl.cm.hot, interpolation="nearest", origin="lower")
pl.savefig("test.jpg")
有一个长度为5个单位,宽度为3个单位的金属薄片上测得的15个点的温度数据,求此薄片的温度分布,并绘制等温线图
横为x竖为y | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 82 | 81 | 80 | 82 | 84 |
2 | 79 | 63 | 61 | 65 | 87 |
3 | 84 | 84 | 82 | 85 | 86 |
解题代码如下:
import numpy as np
from scipy import interpolate
import pylab as pl
import matplotlib as mpl
source = np.array(
[
[11, 12, 13, 14, 15],
[21, 22, 23, 24, 25],
[31, 32, 33, 34, 35]
]
)
z = np.array([
[82, 81, 80, 82, 84],
[79, 63, 61, 65, 87],
[84, 84, 82, 85, 86]
])
# z = np.loadtxt("./Documents/Blogs/数学建模中数据处理类型题目的主要处理流程与方法/data.01.txt")
x = np.arange(1, 6, 1)
y = np.arange(1, 4, 1)
# linear 1
# cubic 3
# quintic 5
f = interpolate.interp2d(x, y, z, kind="linear")
xn = np.linspace(1, 6, 5000)
yn = np.linspace(1, 4, 3000)
zn = f(xn, yn)
pl.imshow(zn, extent=[1, 5, 1, 3], cmap=mpl.cm.hot, interpolation="nearest", origin="lower")
pl.savefig("test.jpg")
拟合指的是对一组数据按其规律方程化。
多项式拟合是指,给出要拟合的阶数n,拟合出一个n阶多项式
如下代码用于生成数据并绘制原始数据图
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
# numpy中的两个多项式拟合方法
from numpy import polyfit, poly1d
x = np.linspace(-5, 5, 100)
y = 4 * x + 1.5
noise_y = y + np.random.randn(y.shape[-1]) * 2.5
p = plt.plot(x, noise_y, "rx")
p = plt.plot(x, y, "b:")
plt.savefig("test.1.jpg")
由上图,可以观察到数据点的整体情况,接下来使用如下代码进行拟合操作,并绘制出拟合函数图
# 拟合一阶
coeff = polyfit(x, noise_y, 1)
print(coeff) # [3.94809446 1.66144869]
p = plt.plot(x, noise_y, 'rx')
p = plt.plot(x, coeff[0] * x + coeff[1], 'k-')
p = plt.plot(x, y, 'b--')
plt.savefig("test.2.jpg")
# 利用参数生成对应函数
f = poly1d(coeff)
# 可以利用此函数生成其他的多项式方程
"""
2
33.36 x + 23.01 x + 3.844
"""
print(f + 2 * f ** 2)
一般情况下,当使用一个N-1阶的多项式拟合M个点时,存在如下关系:
X C = Y XC = Y XC=Y
即
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \left[ …
对于微分和积分计算,除了给出具体的函数表达式进行计算外,还可以给出散列的点来计算,本块内容主要展示了根据散列点进行微积分计算的方式。
在SciPy中,提供了scipy.intergrate
模块进行积分计算。通过散列的点进行数值积分计算主要有梯形法、复合梯形法、Simpson法、Romberg法。如下将展示复合梯形法与Simpson法的计算过程。
对于样本点间隔随机的情况,通常在梯形法和Simpson法中选择。梯形法和Simpson法分别使用一阶和二阶的Newton-Coates公式进行积分。梯形法将函数近似为相邻点间的直线进行计算,Simpson法将函数近似为3个相邻点之间的抛物线进行计算。对于等距的奇数个样本,如果函数是三阶或更少阶的多项式,Simpson法是准确的。如果样本不等距,则只有当函数是二阶或更少阶才是准确的
如下为使用梯形法、复合梯形法、Simpson法的示例代码。
import numpy as np
from scipy import integrate
def f1(x):
return x ** 2
x = np.array([1, 3, 4])
y = f1(x)
# Simpson法
result = integrate.simps(y, x)
# 梯形法
result1 = integrate.trapz(y, x)
# 复合梯形法
result2 = integrate.cumtrapz(y, x)
print(result, result1, result2) # 21.0 22.5 22.5
如果一个样本等距,且对于k范围内有 2 k + 1 2^k+1 2k+1个点,则使用Romberg法将会获得高精度的结果。Romberg基于梯形法,步长与2的幂相关,然后对这些估计值进行Richard外推,以得到更高精度的积分结果。
x = np.linspace(1, 4, 1025)
y = f1(x)
print(x[1] - x[0])
result3 = integrate.romb(y, 0.0029296875)
print(result3) # 21.0
{ d y d t = f ( y , t ) y ( t 0 ) = y 0 \left\{ \begin{array}{lcr} \frac{dy}{dt} = f(y, t) & \\ y(t_0)=y_0 \end{array} \right. {dtdy=f(y,t)y(t0)=y0
主要参数:
{ d y d t = s i n ( t 2 ) y ( − 10 ) = 1 \left\{ \begin{array}{lcr} \frac{dy}{dt} = sin(t^2) & \\ y(-10) = 1 \end{array} \right. {dtdy=sin(t2)y(−10)=1
from scipy.integrate import odeint # 导入 scipy.integrate 模块
import numpy as np
import matplotlib.pyplot as plt
def dy_dt(y, t): # 定义函数 f(y,t)
return np.sin(t**2)
y0 = [1] # y0 = 1 也可以
t = np.arange(-10,10,0.01) # (start,stop,step)
y = odeint(dy_dt, y0, t) # 求解微分方程初值问题
# 绘图
plt.plot(t, y)
plt.show()
{ d x d t = σ ( y − x ) d y d t = x ( ρ − z ) − y d z d t = x y − β z \left\{ \begin{array}{lcr} \frac{dx}{dt} = \sigma(y - x) & \\ \frac{dy}{dt} = x(\rho - z) - y & \\ \frac{dz}{dt} = xy - \beta z \end{array} \right. ⎩⎨⎧dtdx=σ(y−x)dtdy=x(ρ−z)−ydtdz=xy−βz
from scipy.integrate import odeint # 导入 scipy.integrate 模块
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 导数函数, 求 W=[x,y,z] 点的导数 dW/dt
def lorenz(W,t,p,r,b):
x, y, z = W # W=[x,y,z]
dx_dt = p*(y-x) # dx/dt = p*(y-x), p: sigma
dy_dt = x*(r-z) - y # dy/dt = x*(r-z)-y, r:rho
dz_dt = x*y - b*z # dz/dt = x*y - b*z, b;beta
return np.array([dx_dt,dy_dt,dz_dt])
t = np.arange(0, 30, 0.01) # 创建时间点 (start,stop,step)
paras = (10.0, 28.0, 3.0) # 设置 Lorenz 方程中的参数 (p,r,b)
# 调用ode对lorenz进行求解, 用两个不同的初始值 W1、W2 分别求解
W1 = (0.0, 1.00, 0.0) # 定义初值为 W1
track1 = odeint(lorenz, W1, t, args=(10.0, 28.0, 3.0)) # args 设置导数函数的参数
W2 = (0.0, 1.01, 0.0) # 定义初值为 W2
track2 = odeint(lorenz, W2, t, args=paras) # 通过 paras 传递导数函数的参数
# 绘图
fig = plt.figure()
ax = Axes3D(fig)
ax.plot(track1[:,0], track1[:,1], track1[:,2], color='magenta') # 绘制轨迹 1
ax.plot(track2[:,0], track2[:,1], track2[:,2], color='deepskyblue') # 绘制轨迹 2
ax.set_title("Lorenz attractor by scipy.integrate.odeint")
plt.show()
高阶常微分方程,必须做变量替换,化为一阶微分方程组,再用 odeint 求数值解
零输入响应的 RLC 振荡电路可以由如下的二阶微分方程描述:
{ d 2 u d t 2 + R L ∗ d u d t + 1 L C ∗ u = 0 u ( 0 ) = U 0 u ′ ( 0 ) = 0 \left\{ \begin{array}{lcr} \frac{d^2u}{dt^2} + \frac{R}{L} * \frac{du}{dt} + \frac{1}{LC} * u = 0 & \\ u(0) = U_0 & \\ u'(0) = 0 \end{array} \right. ⎩⎨⎧dt2d2u+LR∗dtdu+LC1∗u=0u(0)=U0u′(0)=0
令 α = R 2 L ω 0 2 = 1 L C \alpha = \frac{R}{2L} \omega_0^2=\frac{1}{LC} α=2LRω02=LC1,在零输入响应 u s = 0 u_s=0 us=0时,上式可写成:
{ d 2 u d t 2 + 2 α d u d t + ω 0 2 ∗ u = 0 u ( 0 ) = U 0 u ′ ( 0 ) = 0 \left\{ \begin{array}{lcr} \frac{d^2u}{dt^2} + 2\alpha \frac{du}{dt} + \omega_0^2 * u = 0 & \\ u(0) = U_0 & \\ u'(0) = 0 \end{array} \right. ⎩⎨⎧dt2d2u+2αdtdu+ω02∗u=0u(0)=U0u′(0)=0
对二阶微分方程问题,引入变量 v = d u d t v=\frac{du}{dt} v=dtdu,通过变量替换就把原方程化为如下的微分方程组:
{ d u d t = v d v d t = − 2 α v − ω 0 2 u u ( 0 ) = U 0 v ( 0 ) = 0 \left\{ \begin{array}{lcr} \frac{du}{dt} = v & \\ \frac{dv}{dt} = -2 \alpha v - \omega_0^2 u u(0) = U_0 & \\ v(0) = 0 \end{array} \right. ⎩⎨⎧dtdu=vdtdv=−2αv−ω02uu(0)=U0v(0)=0
from scipy.integrate import odeint # 导入 scipy.integrate 模块
import numpy as np
import matplotlib.pyplot as plt
# 导数函数,求 Y=[u,v] 点的导数 dY/dt
def deriv(Y, t, a, w):
u, v = Y # Y=[u,v]
dY_dt = [v, -2*a*v-w*w*u]
return dY_dt
t = np.arange(0, 20, 0.01) # 创建时间点 (start,stop,step)
# 设置导数函数中的参数 (a, w)
paras1 = (1, 0.6) # 过阻尼:a^2 - w^2 > 0
paras2 = (1, 1) # 临界阻尼:a^2 - w^2 = 0
paras3 = (0.3, 1) # 欠阻尼:a^2 - w^2 < 0
# 调用ode对进行求解, 用两个不同的初始值 W1、W2 分别求解
Y0 = (1.0, 0.0) # 定义初值为 Y0=[u0,v0]
Y1 = odeint(deriv, Y0, t, args=paras1) # args 设置导数函数的参数
Y2 = odeint(deriv, Y0, t, args=paras2) # args 设置导数函数的参数
Y3 = odeint(deriv, Y0, t, args=paras3) # args 设置导数函数的参数
# W2 = (0.0, 1.01, 0.0) # 定义初值为 W2
# track2 = odeint(lorenz, W2, t, args=paras) # 通过 paras 传递导数函数的参数
# 绘图
plt.plot(t, Y1[:, 0], 'r-', label='u1(t)')
plt.plot(t, Y2[:, 0], 'b-', label='u2(t)')
plt.plot(t, Y3[:, 0], 'g-', label='u3(t)')
plt.plot(t, Y1[:, 1], 'r:', label='v1(t)')
plt.plot(t, Y2[:, 1], 'b:', label='v2(t)')
plt.plot(t, Y3[:, 1], 'g:', label='v3(t)')
plt.axis([0, 20, -0.8, 1.2])
plt.legend(loc='best')
plt.title("Second ODE by scipy.integrate.odeint")
plt.show()
附:
结果讨论
RLC串联电路是典型的二阶系统,在零输入条件下根据 α 与 ω 的关系,电路的输出响应存在四种情况:
过阻尼: α2−ω2>0 ,有 2 个不相等的负实数根;
临界阻尼: α2−ω2=0,有 2 个相等的负实数根;
欠阻尼: α2−ω2<0,有一对共轭复数根;
无阻尼:R=0,有一对纯虚根
示例问题:基本线性规划
m i n : z = 2 x 1 + 3 x 2 + x 3 min: z = 2x_1 + 3x_2 + x_3 min:z=2x1+3x2+x3
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \left\{ \b…
import numpy as np
from scipy import optimize
z = np.array([2, 3, 1])
a = np.array([[1, 4, 2], [3, 2, 0]])
b = np.array([8, 6])
x1_bound = x2_bound = x3_bound = (0, None)
res = optimize.linprog(z, A_ub=-a, b_ub=-b, bounds=(x1_bound, x2_bound, x3_bound))
print(res)
某商品有m个产地,n个销地,各产地的产量分别是 a 1 a_1 a1, a 2 a_2 a2, …… a m a_m am,若该商品由i产地运到j销地的单位运价为 c i j c_{ij} cij,应该如何调运才能使总运费最省?
引入变量 x i j x_{ij} xij,其取值为i产地运往j销地的商品数量,数学模型如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13lQtRvx-1632730965032)(https://gitee.com/tremblingv5/my-bed/raw/master/%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1%E4%B8%AD%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%B1%BB%E5%9E%8B%E9%A2%98%E7%9B%AE%E7%9A%84%E4%B8%BB%E8%A6%81%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E4%B8%8E%E6%96%B9%E6%B3%95/%E8%BF%90%E8%BE%93%E9%97%AE%E9%A2%98%E5%9B%BE1.0924.svg)]
示例代码如下
import pulp
import numpy as np
from pprint import pprint
def transportation_problem(costs, x_max, y_max):
row = len(costs)
col = len(costs[0])
prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMaximize)
var = [[pulp.LpVariable(f'x{i}{j}', lowBound=0, cat=pulp.LpInteger) for j in range(col)] for i in range(row)]
flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
prob += pulp.lpDot(flatten(var), costs.flatten())
for i in range(row):
prob += (pulp.lpSum(var[i]) <= x_max[i])
for j in range(col):
prob += (pulp.lpSum([var[i][j] for i in range(row)]) <= y_max[j])
prob.solve()
return {'objective':pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for i in range(row)]}
if __name__ == '__main__':
costs = np.array([[500, 550, 630, 1000, 800, 700],
[800, 700, 600, 950, 900, 930],
[1000, 960, 840, 650, 600, 700],
[1200, 1040, 980, 860, 880, 780]])
max_plant = [76, 88, 96, 40]
max_cultivation = [42, 56, 44, 39, 60, 59]
res = transportation_problem(costs, max_plant, max_cultivation)
print(f'最大值为{res["objective"]}')
print('各变量的取值为:')
pprint(res['var'])
拟分配n人去干n项工作,每个人干且仅干一项工作,若分配第i人去干第j项工作,需要花 c i j c_{ij} cij单位时间,问应该如何分配工作才能使工人花费的总时间最少?
假设指派问题的系数矩阵如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYTj0onj-1632730965034)(https://gitee.com/tremblingv5/my-bed/raw/master/%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1%E4%B8%AD%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%B1%BB%E5%9E%8B%E9%A2%98%E7%9B%AE%E7%9A%84%E4%B8%BB%E8%A6%81%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E4%B8%8E%E6%96%B9%E6%B3%95/%E6%8C%87%E6%B4%BE%E9%97%AE%E9%A2%98%E5%9B%BE1.0924.svg)]
引入变量 x i j x_{ij} xij,若分配i干j工作,则 x i j = 1 x_{ij}=1 xij=1否则 x i j = 0 x_{ij}=0 xij=0,上述指派问题的数学模型如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YVbs6GFW-1632730965036)(https://gitee.com/tremblingv5/my-bed/raw/master/%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1%E4%B8%AD%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%B1%BB%E5%9E%8B%E9%A2%98%E7%9B%AE%E7%9A%84%E4%B8%BB%E8%A6%81%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E4%B8%8E%E6%96%B9%E6%B3%95/math.0924.svg)]
指派问题的可行解用矩阵表示,每行每列有且仅有一个元素为1,其余元素为0
import numpy as np
# linear_sum_assignment 为指派问题专用库
from scipy.optimize import linear_sum_assignment
"""
定义了开销矩阵(指派问题的系数矩阵)efficiency_matrix,
传入linear_sum_assignment,结果返回的是最优指派的行和列,
例如第一行选择第二列,意为:将第一个人派往第二个工作。
而根据numpy.array的性质,传入行和列就会返回行列所对应的值,
即为输出的第三列
"""
efficiency_matrix = np.array([
[12,7,9,7,9],
[8,9,6,6,6],
[7,17,12,14,12],
[15,14,6,6,10],
[4,10,7,10,6]
])
row_index, col_index=linear_sum_assignment(efficiency_matrix)
print(row_index+1)
print(col_index+1)
print(efficiency_matrix[row_index,col_index])
print(efficiency_matrix[row_index, col_index].sum())
要求一部分值必须使整数的规划问题(线性规划、非线性规划、二次规划)
相比于非整数规划,对非整数规划的解进行四舍五入或者取整,并不能保证结果依然使最优解。
基本思想:把整数规划转换成一个个线性规划问题,在求解这些线性规划问题时过程中不断趋近原问题的上下界
分支:全部可行解空间反复地分割为越来越小地子集
定界:对每个子集计算一个目标上界
设有最大化的整数规划问题 A,先解与之相应的线性规划问题 B,若 B 的最优解不符合 A 的整数条件,则 B 的最优目标函数必是 A 的最优目标函数 z 的上界,记为 z2,而 A 的任意可行解的目标函数值将是 z 的一个下界 z1。分支定界法就是将 B 的可行域分成子区域(分支)的方法,逐步减小 z2 和增大 z1,最终求到 z*
分支定界法是一个迭代算法,随着迭代过程不断更新上界和下界,直到上界和下界非常接近时结束。通常设置 Gap < 0.1%,就可把当前的最优可行解近似为问题的全局最优解了。因此,分支定界法的“收敛” 不是分析意义上的而是算法意义上的,优化结果是近似解而不是精确解。
分支定界法不用区分完全整数规划与混合整数规划,算法便于实现,但计算量比较大
基本思路:先求普通线性规划问题地最优解,再对非整数解添加约束条件使可行或缩小,如此反复求解直到得到整数解
不考虑整数约束条件,直接求松弛问题地最优解,在此基础上再添加新地约束条件。
割平面法的计算量比较小,但对问题的结构及求解的要求较高,算法比较复杂。
某厂生产甲乙两种饮料,每百箱甲饮料需用原料 6千克、工人 10名,获利 10万元;每百箱乙饮料需用原料 5千克、工人 20名,获利 9万元。
今工厂共有原料 60千克、工人 150名,又由于其他条件所限甲饮料产量不超过8百箱。
问题 1:问如何安排生产计划,即两种饮料各生产多少使获利最大?
问题 2:若投资0.8万元可增加原料1千克,是否应作这项投资?投资多少合理?
问题 3:若不允许散箱(按整百箱生产),如何安排生产计划,即两种饮料各生产多少使获利最大?
问题 4:若不允许散箱(按整百箱生产),若投资0.8万元可增加原料1千克,是否应作这项投资?投资多少合理?
规划类问题:问题定义、模型构建、模型求解
问题定义, 确定决策变量、目标函数和约束条件。
决策变量是问题中可以在一定范围内进行变化而获得不同结果的变量。
对于问题 1,问题描述中说的很明确,希望通过改变甲、乙两种饮料的产量使总利润最大,甲、乙两种饮料的产量就是决策变量。
对于问题 2 则要注意,如果只看前一句,就是比较问题 1 与问题 2 的利润,还是把甲、乙两种饮料的产量作为决策变量。但要回答后一句“投资多少合理”,这就出现了一个新的变量“投资额”,因此对问题 2 要建立 3个决策变量:甲产量、乙产量和投资额。
目标函数是决策变量的函数,我们希望通过改变决策变量的值而获得目标函数的最大值或最小值,通常是总成本(最小)、总利润(最大)、总时间(最短)。
对于本案例,每个问题都是希望获得最大利润,目标函数都是总利润,问题是求目标函数即总利润的最大值。
约束条件是决策变量所要满足的限制条件。
约束条件 3 种情况:
一是不等式约束,例如题目指出共有原料 60千克、工人 150名,因此生产计划所用的原料、工人的需求不能大于题目中数值。
二是等式约束,本题没有等式约束条件。
三是决策变量取值范围的约束。
通常,题目隐含着决策变量大于等于 0 的条件,例如工人人数、原料数量都要大于等于 0。
另外,如果能通过分析前面的等式约束或不等式约束,得出决策变量的上限,将会极大的提高问题求解的速度和性能。后文将对此举例说明。
模型构建, 由问题描述建立数学方程,并转化为标准形式的数学模型。
对于问题 1,目标函数是生产甲、乙两种饮料的总利润,约束条件是原料总量、工人总数的约束,而且原料、工人都要大于等于 0。
maxf(x)=10∗x1+9∗x2s.t.:⎧⎩⎨⎪⎪⎪⎪6∗x1+5∗x2≤6010∗x1+20∗x2≤1500≤x1≤8x2≥0
进一步分析决策变量取值范围的约束条件,由原料数量、工人数量的不等式约束可以推出:
x1≤15x2≤7.5
对于问题 2,可以通过增加投资来获得更多的原料,投资额是一个新的变量。要注意的是,此时目标函数虽然也是生产两种饮料的总利润,但总利润不等于总收入,而是总收入减去总成本,在本例中就是要减去购买原料的投资。
maxf(x)=10∗x1+9∗x2−x3s.t.:⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪6∗x1+5∗x2≤60+x3/0.810∗x1+20∗x2≤1500≤x1≤150≤x2≤7.5x3≥0
对于问题 3 和问题 4,区别只是不允许散箱,明确提出了决策变量 x1、x2 的取值要取整数值,所以是整数规划问题。
需要注意的是,问题 4 中对增加的投资额即购买的原料数量并没有整数限制,因此 x1、x2 的取值范围是正整数,但 x3 的取值范围是正数,这是一个混合整数规划问题。
还要说明的是,对于问题 1 和问题 2,虽然题目中没有明确要求生产甲、乙饮料的工人人数为整数,但是人数也不可能是小数的,那么这是不是也是整数规划问题呢?
如果你能提出这个问题,那么恭喜你,你已经从小白升级为菜鸟了。
我的理解是,这个问题怎么说都可以。如果要简化问题,使用线性规划模型,最好在问题假设中说一句,假设甲乙饮料在同一车间先后生产,只要允许甲乙饮料散箱生产,即使根据产量所求出的工人数是小数,也可以解释的通。如果你掌握了整数规划问题的求解,那就先按线性规划建模,再补充讨论工人人数也必须是整数的条件,按整数规划建模求解,这就是妥妥的获奖论文了。
python pulp求解步骤
import pulp
# 定义问题,求最大值
ProbLP1 = pulp.LpProblem("ProbLP1", sense=pulp.LpMaximize)
问题1
# 定义x1
x1 = pulp.LpVariable('x1', lowBound=0, upBound=15, cat='Continuous')
# 定义x2
x2 = pulp.LpVariable('x2', lowBound=0, upBound=7.5, cat='Continuous')
pulp.LpVariable
定义决策变量
lowBound
,upBound
决策变量上下界
cat
定义变量类型,连续变量:Continuous
,离散变量:Integer
,0/1变量:Binary
问题3
x1 = pulp.LpVariable('x1', lowBound=0, upBound=15, cat='Integer')
x2 = pulp.LpVariable('x2', lowBound=0, upBound=7.5, cat='Integer')
ProbLP1 += (10*x1 + 9*x2) # 设置目标函数 f(x)
# 不等式约束
ProbLP1 += (6*x1 + 5*x2 <= 60)
# 不等式约束
ProbLP1 += (10*x1 + 20*x2 <= 150)
ProbLP1.solve()
# 输出求解状态
print(ProbLP1.name)
print("Status:", pulp.LpStatus[ProbLP1.status])
for v in ProbLP1.variables():
# 输出每个变量的最优值
print(v.name, "=", v.varValue)
# 输出最优解的目标函数值
print("F1(x) =", pulp.value(ProbLP1.objective))
数值优化算法是寻找一个函数最优解的问题,这个函数被成为目标函数,通常使用
scipy.optimize
进行优化
梯度下降法通常是沿着梯度下降的方向寻找最优解,但是对于一些复杂函数,梯度下降法不一定可以找到最优解。
在scipy中,带有梯度下降的方法名通常包含cg
,其中简单梯度下降方法的函数名为scipy.optimize.fmin_cg()
from scipy import optimize
def f(x):
return 0.5 * (1 - x[0]) ** 2 + (x[1] - 7) ** 2
optimize.fmin_cg(f,[0,0])
另,可以设置梯度,设置梯度有助于提升算法性能
def fprime(x):
return np.array((-2 * .5 * (1 - x[0]), 2 * (x[1] - 7)))
optimize.fmin_cg(f, [0, 0], fprime=fprime)
牛顿法在现有极小点估计值的附近对目标函数做二阶展开,进而找到极小点的下一个估计值。
def f(x):
return 0.5 * (1 - x[0]) ** 2 + (x[1] - 7) ** 2
# 一阶导
def fprime(x):
return np.array((-2 *.5 * (1 - x[0]), 2 * (x[1] - 7)))
# 二阶导
def hessian(x):
return np.array(-2 *.5 *x[0], 2 * x[1])
# 最少需要传入一阶导
optimize.fmin_ncg(f, [200,800], fprime=fprime)
optimize.fmin_ncg(f, [200,800], fprime=fprime, fhess=hessian)
改进了每一步对Hessian的近似。在一些正常的函数中,BFGS 虽然不如牛顿法快,但是还是比较快的。而且对于一些情况复杂的函数,BFGS 要比牛顿法好,因为它对 Hessian 进行了改进
def f(x):
return 0.5 * (1 - x[0]) ** 2 + (x[1] - 7) ** 2
# 一阶导
def fprime(x):
return np.array((-2 *.5 * (1 - x[0]), 2 * (x[1] - 7)))
optimize.fmin_bfgs(f, [200, 800], fprime=fprime)
类似梯度下降法
def f(x):
return 0.5*(1 - x[0])**2 + (x[1] - 7)**2
# 一阶导
def fprime(x):
return np.array((-2*.5*(1 - x[0]), 2*(x[1] - 7)))
optimize.fmin_powell(f,[0,0])
对噪音有很好的抵抗性,不依赖于梯度,可以在局部光滑的函数上发挥作用;在光滑、非噪音函数上比梯度法更慢
def f(x):
return 0.5*(1 - x[0])**2 + (x[1] - 7)**2
# 一阶导
def fprime(x):
return np.array((-2*.5*(1 - x[0]), 2*(x[1] - 7)))
optimize.fmin(f, [2, 2])
scipy.optimize
中的3种限制条件在指定边界内寻找最优解
def f(x):
return 0.5*(1 - x[0])**2 + (x[1] - 7)**2
# 一阶求导
def fprime(x):
return np.array((-2*.5*(1 - x[0]), 2*(x[1] - 7)))
# 这里限制了 x[0] 的取值在(1,2), x[1] 的取值在(9,19)
optimize.fmin_l_bfgs_b(f, [0,0], approx_grad=1, bounds=((1,2),(9,19)))
def f(x):
return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2)
# 自定义限制条件
def constraint(x):
return x[0]+x[1] - 4
# eqcons == 0.0
optimize.fmin_slsqp(f, np.array([0, 0]), eqcons=[constraint,])
def f(x):
return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2)
# 自定义限制条件
def constraint(x):
return x[0]+x[1] - 4
# eqcons == 0.0
optimize.fmin_slsqp(f, np.array([0, 0]), eqcons=[constraint,])
组合优化算法是一组求离散状态数据集最优解的算法,这些算法常常用来解决NP-Hard级问题。后续组合优化算法均使用python库:scikit-opt来调用。
模拟退火算法是一种贪心算法,通过模拟淬火降温过程,以一定的概率接受一个比当前解要差的解,从而跳出局部最优解,通过多次迭代计算,从而不断趋近于最优解。
模拟退火算法是一种随机算法,并不能保证得到最优解,可以以较高的效率获取到近似最优解。
求函数 f(x) = x ^ 2 - 2 * x + 1的最小值
设置目标函数
func = lambda x: x[0] ** 2 - x[0] * 2 + 1
调用库函数
"""
初始值设置为50
停止上限为100
停止下限为1e-9
"""
sa = SA(func=func, x0=[50], T_max=100, T_min=1e-9, max_stay_counter=150)
执行并获取结果
best_x, best_y = sa.run()
print(best_x, best_y)
plt.plot(pd.DataFrame(sa.best_y_history).cummin(axis=0))
plt.savefig("test.jpg")
[0.99999992] 5.773159728050814e-15
粒子群算法是一种进化算法,源于对鸟类捕食的研究,通过群体中个体之间的协作和信息共享来寻找最优解
优势:简单易实现,不需要调节过多参数
一个“粒子”只具有两个属性,速度和位置。
每个粒子在空间中单独的寻找最优解,并记录为单个粒子的极值,同时将这个局部最优解和其他粒子共享,每个粒子再通过共享的局部最优解来调整自身的速度和位置,从而使整个粒子群不断趋近最优解
示例代码如下,与模拟退火算法类似,主要流程即定义问题,然后调用库中的算法进行解题:
from sko.PSO import PSO
import matplotlib.pyplot as plt
def target(x):
x1, x2, x3 = x
return x1 ** 2 + (x2 - 0.05) ** 2 + x3 ** 2
pso = PSO(
func=target,
n_dim=3,
max_iter=150,
lb=[0, -1, 0.5],
ub=[1, 1, 1],
c1=0.5,
c2=0.5
)
x, y = pso.run()
print(f"x: {x}, y: {y}")
遗传算法的抽象理解:给定一个初始解,经过一系列的变化(遗传、变异、交叉、复制),最终进化出最优解决。
与模拟退火算法使用场景类似,都适用于解决np-hard问题,从随机中不断趋近最优解
示例代码如下:
import pandas as pd
import matplotlib.pyplot as plt
from sko.GA import GA
def schaffer(p):
x1, x2 = p
x = np.square(x1) + np.square(x2)
return 0.5 + (np.square(np.sin(x)) - 0.5) / np.square(1 + 0.001 * x)
ga = GA(
func=schaffer,
n_dim=2,
size_pop=50,
max_iter=800,
prob_mut=0.001,
lb=[-1, -1],
ub=[1, 1],
precision=1e-7
)
x, y = ga.run()
print(f"x: {x}, y: {y}")
蚁群算法是一种用来寻找优化路径的概率型算法。它由Marco Dorigo于1992年在他的博士论文中提出,其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。
示例代码如下:
from sko.ACA import ACA_TSP
from scipy import spatial
import numpy as np
points_coordinate = np.random.rand(50, 2)
distance_matrix = spatial.distance.cdist(points_coordinate, points_coordinate, metric='euclidean')
def cal_total_distance(routine):
num_points, = routine.shape
return sum([distance_matrix[routine[i % num_points], routine[(i + 1) % num_points]] for i in range(num_points)])
aca = ACA_TSP(func=cal_total_distance, n_dim=50,
size_pop=50, max_iter=200,
distance_matrix=distance_matrix)
best_x, best_y = aca.run()
print(best_x, best_y)
插值:在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点。 插值是离散函数逼近的重要方法,利用它可通过函数在有限个点处的取值状况,估算出函数在其他点处的近似值
拟合:是用一个连续函数(曲线)靠近给定的离散数据,使其与给定的数据相吻合
回归:研究一组随机变量与另一组随机变量之间关系的统计分析方法,包括建立数学模型并估计模型参数,并检验数学模型的可信度,也包括利用建立的模型和估计的模型参数进行预测或控制
预测:指对获得的数据、信息进行定量研究,据此建立与预测目的相适应的数学模型,然后对未来的发展变化进行定量地预测
statsmodels
的线性回归import statsmodels.api as sm
from statsmodels.sandbox.regression.predstd import wls_prediction_std
nSample = 100
# 起点为 0,终点为 10,均分为 nSample个点
x1 = np.linspace(0, 10, nSample)
# 正态分布随机数
e = np.random.normal(size=len(x1))
# y = b0 + b1*x1
yTrue = 2.36 + 1.58 * x1
# 产生模型数据
yTest = yTrue + e
本案例是一元线性回归问题,(yTest,x)是导入的样本数据,我们需要通过线性回归获得因变量 y 与自变量 x 之间的定量关系。yTrue 是理想模型的数值,yTest 模拟实验检测的数据,在理想模型上加入了正态分布的随机误差
一元线性回归模型方程为
y = β0 + β1 * x + e
先通过 sm.add_constant() 向矩阵 X 添加截距列后,再用 sm.OLS() 建立普通最小二乘模型,最后用 model.fit() 就能实现线性回归模型的拟合,并返回拟合与统计分析的结果摘要。
X = sm.add_constant(x1) # 向 x1 左侧添加截距列 x0=[1,...1]
model = sm.OLS(yTest, X) # 建立最小二乘模型(OLS)
results = model.fit() # 返回模型拟合结果
statsmodels.OLS 是 statsmodels.regression.linear_model 的函数,有 4个参数 (endog, exog, missing, hasconst)。
第一个参数 endog 是回归模型中的因变量 y(t), 是1-d array 数据类型。
第二个输入 exog 是自变量 x0(t),x1(t),…,xm(t),是(m+1)-d array 数据类型。
需要注意的是,statsmodels.OLS 的回归模型没有常数项,其形式为:
y = BX + e = β0x0 + β1*x1 + e, x0 = [1,…1]
而之前导入的数据 (yTest,x1) 并不包含 x0,因此需要在 x1 左侧增加一列截距列 x0=[1,…1],将自变量矩阵转换为 X = (x0, x1)。函数 sm.add_constant() 实现的就是这个功能。
参数 missing 用于数据检查, hasconst 用于检查常量,一般情况不需要。
print(results.summary()) # 输出回归分析的摘要
coef:回归系数(Regression coefficient),即模型参数 β0、β1、…的估计值。
std err :标准差( Standard deviation),也称标准偏差,是方差的算术平方根,反映样本数据值与回归模型估计值之间的平均差异程度 。标准差越大,回归系数越不可靠。
t:t 统计量(t-Statistic),等于回归系数除以标准差,用于对每个回归系数分别进行检验,检验每个自变量对因变量的影响是否显著。如果某个自变量 xi的影响不显著,意味着可以从模型中剔除这个自变量。
P>|t|:t检验的 P值(Prob(t-Statistic)),反映每个自变量 xi 与因变量 y 的相关性假设的显著性。如果 p<0.05,可以理解为在0.05的显著性水平下变量xi与y存在回归关系,具有显著性。
[0.025,0.975]:回归系数的置信区间(Confidence interval)的下限、上限,某个回归系数的置信区间以 95%的置信度包含该回归系数 。注意并不是指样本数据落在这一区间的概率为 95%。
此外,还有一些重要的指标需要关注:
R-squared:R方判定系数(Coefficient of determination),表示所有自变量对因变量的联合的影响程度,用于度量回归方程拟合度的好坏,越接近于 1说明拟合程度越好。
F-statistic:F 统计量(F-Statistic),用于对整体回归方程进行显著性检验,检验所有自变量在整体上对因变量的影响是否显著。
Statsmodels 也可以通过属性获取所需的回归分析的数据,例如:
print("OLS model: Y = b0 + b1 * x") # b0: 回归直线的截距,b1: 回归直线的斜率
print('Parameters: ', results.params) # 输出:拟合模型的系数
yFit = results.fittedvalues # 拟合模型计算出的 y值
ax.plot(x1, yTest, 'o', label="data") # 原始数据
ax.plot(x1, yFit, 'r-', label="OLS") # 拟合数据
判别分析是一种分类方法,根据已掌握的每个类别的若干个样本信息,求出判别函数,再根据判别函数判别未知类别的样本点的类别。
距离判别法为根据待判定对象的距离,以就近原则进行判别。这里的距离通常采用Mahalanobis距离(马氏距离)
蠓虫是一种昆虫,分为很多类型,其中有一种名为Af,是能传播花粉的益虫;另一种名为Apf,是会传播疾病的害虫。这两种蠓虫在形态上十分相似,难以区分。现测得9只Af和6只Apf的触角长度和翅膀长度数据如下:
Af:
触角长度 | 翅膀长度 |
---|---|
1.24 | 1.27 |
1.36 | 1.74 |
1.38 | 1.64 |
1.38 | 1.82 |
1.38 | 1.90 |
1.40 | 1.70 |
1.48 | 1.82 |
1.54 | 1.82 |
1.56 | 2.08 |
Apf:
触角长度 | 翅膀长度} |
---|---|
1.14 | 1.78 |
1.18 | 1.96 |
1.20 | 1.86 |
1.26 | 2.00 |
1.28 | 2.00 |
1.30 | 1.96 |
若两类蠓虫协方差矩阵相同,试判别如下3只蠓虫属于哪一类
触角长度 | 翅膀长度} |
---|---|
1.24 | 1.80 |
1.28 | 1.84 |
1.40 | 2.04 |
示例代码如下:
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
X0 = np.array(
[
[1.24, 1.27],
[1.36, 1.74],
[1.38, 1.90],
[1.38, 1.82],
[1.38, 1.90],
[1.40, 1.70],
[1.48, 1.82],
[1.54, 1.82],
[1.56, 2.08],
[1.14, 1.78],
[1.18, 1.96],
[1.20, 1.86],
[1.26, 2.00],
[1.28, 2.00],
[1.30, 1.96]
]
)
x = np.array(
[
[1.24, 1.80],
[1.28, 1.84],
[1.40, 2.04]
]
)
g = np.hstack(
[
np.ones(9),
2 * np.ones(6)
]
)
v = np.cov(X0.T)
knn = KNeighborsClassifier(2, metric='mahalanobis', metric_params={'V': v})
knn.fit(X0,g)
pre = knn.predict(x)
print("马氏距离:", pre)
print("马氏距离已知样本误判率:", 1 - knn.score(X0, g))
knn2 = KNeighborsClassifier(2)
knn2.fit(X0,g)
pre2 = knn2.predict(x)
print("欧式距离", pre2)
print("欧氏距离误判率", 1 - knn2.score(X0,g))
输出结果如下:
马氏距离: [2. 2. 1.]
马氏距离已知样本误判率: 0.0
欧式距离 [2. 1. 2.]
欧氏距离误判率 0.0
Fisher判别法是基于方差分析的判别法
示例代码如下:
import numpy as np
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.neighbors import KNeighborsClassifier
X0 = np.array(
[
[1.24, 1.27],
[1.36, 1.74],
[1.38, 1.90],
[1.38, 1.82],
[1.38, 1.90],
[1.40, 1.70],
[1.48, 1.82],
[1.54, 1.82],
[1.56, 2.08],
[1.14, 1.78],
[1.18, 1.96],
[1.20, 1.86],
[1.26, 2.00],
[1.28, 2.00],
[1.30, 1.96]
]
)
x = np.array(
[
[1.24, 1.80],
[1.28, 1.84],
[1.40, 2.04]
]
)
g = np.hstack(
[
np.ones(9),
2 * np.ones(6)
]
)
clf = LDA()
clf.fit(X0,g)
print("判别结果为", clf.predict(x))
print("已知样本的误判率为:", 1 - clf.score(X0,g))
结果如下:
判别结果为 [2. 2. 1.]
已知样本的误判率为: 0.0
报名参加了2021年秋季的华为杯数学建模大赛,以往对这类比赛并无经验,比赛在即,几个月来断断续续看了些东西,故决定做一个简单总结。虽说是总结,但更多的还是使用代码解决各类问题,对各种算法的了解还远远不够。这次比赛目测是难以获奖,之后还有机会,希望能在未来的一年时间中,多抽点时间给数学建模,希望最终能取得一个还不错的成绩。
插值是离散函数逼近的重要方法,通过插值,可以依据已经得到的点数据来推算未给出的点的数据
插值通常选用Python SciPY中的interpolate来实现,其中的interp1d函数即可实现一维插值的功能。
该函数有3个参数,除x、y轴点坐标外,“kind”参数指明了插值的方式,具体如下表。
值 | 效果 |
---|---|
‘zero’,‘nearest’ | 阶梯插值,即0阶B样条曲线 |
‘slinear’,‘linear’ | 线性插值,用一条直线连接所有的取样点,相当于1阶B样条曲线 |
‘quadraic’,‘cubic’ | 二阶和三阶B样条曲线,更高阶的曲线可以直接使用整数值指定 |
样条插值是使用一种名为样条的特殊分段多项式进行插值的形式。由于样条插值可以使用低阶多项式样条实现较小的插值误差,这样就避免了使用高阶多项式所出现的龙格现象,所以样条插值得到了流行
import numpy as np
from scipy import interpolate
import pylab as pl
x = np.linspace(0, 10 * np.pi, 5)
y = np.sin(x)
fc = interpolate.interp1d(x, y, kind="cubic")
xint = np.linspace(x.min(), x.max(), 5)
pl.plot(xint, fc(xint), color="red", label="interpid")
pl.savefig("test.jpg")
下表给出了待加工零件下轮廓线的一组数据,现需要得到x坐标每改变0.1时所对应的y的坐标
x | y |
---|---|
0 | 0 |
3 | 1.2 |
5 | 1.7 |
7 | 2.0 |
9 | 2.1 |
11 | 2.0 |
12 | 1.8 |
13 | 1.2 |
14 | 1.0 |
15 | 1.6 |
解题代码如下:
import numpy as np
import pylab as pl
from scipy import interpolate
x = np.array([0, 3, 5, 7, 9, 11, 12, 13, 14, 15])
y = np.array([0, 1.2, 1.7, 2.0, 2.1, 2.0, 1.8, 1.2, 1.0, 1.6])
fc = interpolate.interp1d(x, y, kind="cubic")
result = np.linspace(x.min(), x.max(), 1000)
print(result)
print(fc(result))
二维插值与一维类似,示例代码如下
import numpy as np
from scipy import interpolate
import pylab as pl
import matplotlib as mpl
def func(x, y):
return (x + y) * np.exp(-5 * (x ** 2 + y **2))
y, x = np.mgrid[-1:1:15j, -1:1:15j]
newFunc = interpolate.interp2d(x, y, func(x, y), kind='cubic')
xNew = np.linspace(-1, 1, 100)
yNew = np.linspace(-1, 1, 100)
fNew = newFunc(xNew, yNew)
pl.imshow(fNew, extent=[-1, 1, -1, 1], cmap=mpl.cm.hot, interpolation="nearest", origin="lower")
pl.savefig("test.jpg")
有一个长度为5个单位,宽度为3个单位的金属薄片上测得的15个点的温度数据,求此薄片的温度分布,并绘制等温线图
横为x竖为y | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 82 | 81 | 80 | 82 | 84 |
2 | 79 | 63 | 61 | 65 | 87 |
3 | 84 | 84 | 82 | 85 | 86 |
解题代码如下:
import numpy as np
from scipy import interpolate
import pylab as pl
import matplotlib as mpl
source = np.array(
[
[11, 12, 13, 14, 15],
[21, 22, 23, 24, 25],
[31, 32, 33, 34, 35]
]
)
z = np.array([
[82, 81, 80, 82, 84],
[79, 63, 61, 65, 87],
[84, 84, 82, 85, 86]
])
# z = np.loadtxt("./Documents/Blogs/数学建模中数据处理类型题目的主要处理流程与方法/data.01.txt")
x = np.arange(1, 6, 1)
y = np.arange(1, 4, 1)
# linear 1
# cubic 3
# quintic 5
f = interpolate.interp2d(x, y, z, kind="linear")
xn = np.linspace(1, 6, 5000)
yn = np.linspace(1, 4, 3000)
zn = f(xn, yn)
pl.imshow(zn, extent=[1, 5, 1, 3], cmap=mpl.cm.hot, interpolation="nearest", origin="lower")
pl.savefig("test.jpg")
拟合指的是对一组数据按其规律方程化。
多项式拟合是指,给出要拟合的阶数n,拟合出一个n阶多项式
如下代码用于生成数据并绘制原始数据图
import numpy as np
import matplotlib as mpl
from matplotlib import pyplot as plt
# numpy中的两个多项式拟合方法
from numpy import polyfit, poly1d
x = np.linspace(-5, 5, 100)
y = 4 * x + 1.5
noise_y = y + np.random.randn(y.shape[-1]) * 2.5
p = plt.plot(x, noise_y, "rx")
p = plt.plot(x, y, "b:")
plt.savefig("test.1.jpg")
由上图,可以观察到数据点的整体情况,接下来使用如下代码进行拟合操作,并绘制出拟合函数图
# 拟合一阶
coeff = polyfit(x, noise_y, 1)
print(coeff) # [3.94809446 1.66144869]
p = plt.plot(x, noise_y, 'rx')
p = plt.plot(x, coeff[0] * x + coeff[1], 'k-')
p = plt.plot(x, y, 'b--')
plt.savefig("test.2.jpg")
# 利用参数生成对应函数
f = poly1d(coeff)
# 可以利用此函数生成其他的多项式方程
"""
2
33.36 x + 23.01 x + 3.844
"""
print(f + 2 * f ** 2)
一般情况下,当使用一个N-1阶的多项式拟合M个点时,存在如下关系:
X C = Y XC = Y XC=Y
即
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \left[ …
对于微分和积分计算,除了给出具体的函数表达式进行计算外,还可以给出散列的点来计算,本块内容主要展示了根据散列点进行微积分计算的方式。
在SciPy中,提供了scipy.intergrate
模块进行积分计算。通过散列的点进行数值积分计算主要有梯形法、复合梯形法、Simpson法、Romberg法。如下将展示复合梯形法与Simpson法的计算过程。
对于样本点间隔随机的情况,通常在梯形法和Simpson法中选择。梯形法和Simpson法分别使用一阶和二阶的Newton-Coates公式进行积分。梯形法将函数近似为相邻点间的直线进行计算,Simpson法将函数近似为3个相邻点之间的抛物线进行计算。对于等距的奇数个样本,如果函数是三阶或更少阶的多项式,Simpson法是准确的。如果样本不等距,则只有当函数是二阶或更少阶才是准确的
如下为使用梯形法、复合梯形法、Simpson法的示例代码。
import numpy as np
from scipy import integrate
def f1(x):
return x ** 2
x = np.array([1, 3, 4])
y = f1(x)
# Simpson法
result = integrate.simps(y, x)
# 梯形法
result1 = integrate.trapz(y, x)
# 复合梯形法
result2 = integrate.cumtrapz(y, x)
print(result, result1, result2) # 21.0 22.5 22.5
如果一个样本等距,且对于k范围内有 2 k + 1 2^k+1 2k+1个点,则使用Romberg法将会获得高精度的结果。Romberg基于梯形法,步长与2的幂相关,然后对这些估计值进行Richard外推,以得到更高精度的积分结果。
x = np.linspace(1, 4, 1025)
y = f1(x)
print(x[1] - x[0])
result3 = integrate.romb(y, 0.0029296875)
print(result3) # 21.0
{ d y d t = f ( y , t ) y ( t 0 ) = y 0 \left\{ \begin{array}{lcr} \frac{dy}{dt} = f(y, t) & \\ y(t_0)=y_0 \end{array} \right. {dtdy=f(y,t)y(t0)=y0
主要参数:
{ d y d t = s i n ( t 2 ) y ( − 10 ) = 1 \left\{ \begin{array}{lcr} \frac{dy}{dt} = sin(t^2) & \\ y(-10) = 1 \end{array} \right. {dtdy=sin(t2)y(−10)=1
from scipy.integrate import odeint # 导入 scipy.integrate 模块
import numpy as np
import matplotlib.pyplot as plt
def dy_dt(y, t): # 定义函数 f(y,t)
return np.sin(t**2)
y0 = [1] # y0 = 1 也可以
t = np.arange(-10,10,0.01) # (start,stop,step)
y = odeint(dy_dt, y0, t) # 求解微分方程初值问题
# 绘图
plt.plot(t, y)
plt.show()
{ d x d t = σ ( y − x ) d y d t = x ( ρ − z ) − y d z d t = x y − β z \left\{ \begin{array}{lcr} \frac{dx}{dt} = \sigma(y - x) & \\ \frac{dy}{dt} = x(\rho - z) - y & \\ \frac{dz}{dt} = xy - \beta z \end{array} \right. ⎩⎨⎧dtdx=σ(y−x)dtdy=x(ρ−z)−ydtdz=xy−βz
from scipy.integrate import odeint # 导入 scipy.integrate 模块
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 导数函数, 求 W=[x,y,z] 点的导数 dW/dt
def lorenz(W,t,p,r,b):
x, y, z = W # W=[x,y,z]
dx_dt = p*(y-x) # dx/dt = p*(y-x), p: sigma
dy_dt = x*(r-z) - y # dy/dt = x*(r-z)-y, r:rho
dz_dt = x*y - b*z # dz/dt = x*y - b*z, b;beta
return np.array([dx_dt,dy_dt,dz_dt])
t = np.arange(0, 30, 0.01) # 创建时间点 (start,stop,step)
paras = (10.0, 28.0, 3.0) # 设置 Lorenz 方程中的参数 (p,r,b)
# 调用ode对lorenz进行求解, 用两个不同的初始值 W1、W2 分别求解
W1 = (0.0, 1.00, 0.0) # 定义初值为 W1
track1 = odeint(lorenz, W1, t, args=(10.0, 28.0, 3.0)) # args 设置导数函数的参数
W2 = (0.0, 1.01, 0.0) # 定义初值为 W2
track2 = odeint(lorenz, W2, t, args=paras) # 通过 paras 传递导数函数的参数
# 绘图
fig = plt.figure()
ax = Axes3D(fig)
ax.plot(track1[:,0], track1[:,1], track1[:,2], color='magenta') # 绘制轨迹 1
ax.plot(track2[:,0], track2[:,1], track2[:,2], color='deepskyblue') # 绘制轨迹 2
ax.set_title("Lorenz attractor by scipy.integrate.odeint")
plt.show()
高阶常微分方程,必须做变量替换,化为一阶微分方程组,再用 odeint 求数值解
零输入响应的 RLC 振荡电路可以由如下的二阶微分方程描述:
{ d 2 u d t 2 + R L ∗ d u d t + 1 L C ∗ u = 0 u ( 0 ) = U 0 u ′ ( 0 ) = 0 \left\{ \begin{array}{lcr} \frac{d^2u}{dt^2} + \frac{R}{L} * \frac{du}{dt} + \frac{1}{LC} * u = 0 & \\ u(0) = U_0 & \\ u'(0) = 0 \end{array} \right. ⎩⎨⎧dt2d2u+LR∗dtdu+LC1∗u=0u(0)=U0u′(0)=0
令 α = R 2 L ω 0 2 = 1 L C \alpha = \frac{R}{2L} \omega_0^2=\frac{1}{LC} α=2LRω02=LC1,在零输入响应 u s = 0 u_s=0 us=0时,上式可写成:
{ d 2 u d t 2 + 2 α d u d t + ω 0 2 ∗ u = 0 u ( 0 ) = U 0 u ′ ( 0 ) = 0 \left\{ \begin{array}{lcr} \frac{d^2u}{dt^2} + 2\alpha \frac{du}{dt} + \omega_0^2 * u = 0 & \\ u(0) = U_0 & \\ u'(0) = 0 \end{array} \right. ⎩⎨⎧dt2d2u+2αdtdu+ω02∗u=0u(0)=U0u′(0)=0
对二阶微分方程问题,引入变量 v = d u d t v=\frac{du}{dt} v=dtdu,通过变量替换就把原方程化为如下的微分方程组:
{ d u d t = v d v d t = − 2 α v − ω 0 2 u u ( 0 ) = U 0 v ( 0 ) = 0 \left\{ \begin{array}{lcr} \frac{du}{dt} = v & \\ \frac{dv}{dt} = -2 \alpha v - \omega_0^2 u u(0) = U_0 & \\ v(0) = 0 \end{array} \right. ⎩⎨⎧dtdu=vdtdv=−2αv−ω02uu(0)=U0v(0)=0
from scipy.integrate import odeint # 导入 scipy.integrate 模块
import numpy as np
import matplotlib.pyplot as plt
# 导数函数,求 Y=[u,v] 点的导数 dY/dt
def deriv(Y, t, a, w):
u, v = Y # Y=[u,v]
dY_dt = [v, -2*a*v-w*w*u]
return dY_dt
t = np.arange(0, 20, 0.01) # 创建时间点 (start,stop,step)
# 设置导数函数中的参数 (a, w)
paras1 = (1, 0.6) # 过阻尼:a^2 - w^2 > 0
paras2 = (1, 1) # 临界阻尼:a^2 - w^2 = 0
paras3 = (0.3, 1) # 欠阻尼:a^2 - w^2 < 0
# 调用ode对进行求解, 用两个不同的初始值 W1、W2 分别求解
Y0 = (1.0, 0.0) # 定义初值为 Y0=[u0,v0]
Y1 = odeint(deriv, Y0, t, args=paras1) # args 设置导数函数的参数
Y2 = odeint(deriv, Y0, t, args=paras2) # args 设置导数函数的参数
Y3 = odeint(deriv, Y0, t, args=paras3) # args 设置导数函数的参数
# W2 = (0.0, 1.01, 0.0) # 定义初值为 W2
# track2 = odeint(lorenz, W2, t, args=paras) # 通过 paras 传递导数函数的参数
# 绘图
plt.plot(t, Y1[:, 0], 'r-', label='u1(t)')
plt.plot(t, Y2[:, 0], 'b-', label='u2(t)')
plt.plot(t, Y3[:, 0], 'g-', label='u3(t)')
plt.plot(t, Y1[:, 1], 'r:', label='v1(t)')
plt.plot(t, Y2[:, 1], 'b:', label='v2(t)')
plt.plot(t, Y3[:, 1], 'g:', label='v3(t)')
plt.axis([0, 20, -0.8, 1.2])
plt.legend(loc='best')
plt.title("Second ODE by scipy.integrate.odeint")
plt.show()
附:
结果讨论
RLC串联电路是典型的二阶系统,在零输入条件下根据 α 与 ω 的关系,电路的输出响应存在四种情况:
过阻尼: α2−ω2>0 ,有 2 个不相等的负实数根;
临界阻尼: α2−ω2=0,有 2 个相等的负实数根;
欠阻尼: α2−ω2<0,有一对共轭复数根;
无阻尼:R=0,有一对纯虚根
示例问题:基本线性规划
m i n : z = 2 x 1 + 3 x 2 + x 3 min: z = 2x_1 + 3x_2 + x_3 min:z=2x1+3x2+x3
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \left\{ \b…
import numpy as np
from scipy import optimize
z = np.array([2, 3, 1])
a = np.array([[1, 4, 2], [3, 2, 0]])
b = np.array([8, 6])
x1_bound = x2_bound = x3_bound = (0, None)
res = optimize.linprog(z, A_ub=-a, b_ub=-b, bounds=(x1_bound, x2_bound, x3_bound))
print(res)
某商品有m个产地,n个销地,各产地的产量分别是 a 1 a_1 a1, a 2 a_2 a2, …… a m a_m am,若该商品由i产地运到j销地的单位运价为 c i j c_{ij} cij,应该如何调运才能使总运费最省?
引入变量 x i j x_{ij} xij,其取值为i产地运往j销地的商品数量,数学模型如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPZbDsVg-1632730912718)(https://gitee.com/tremblingv5/my-bed/raw/master/%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1%E4%B8%AD%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%B1%BB%E5%9E%8B%E9%A2%98%E7%9B%AE%E7%9A%84%E4%B8%BB%E8%A6%81%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E4%B8%8E%E6%96%B9%E6%B3%95/%E8%BF%90%E8%BE%93%E9%97%AE%E9%A2%98%E5%9B%BE1.0924.svg)]
示例代码如下
import pulp
import numpy as np
from pprint import pprint
def transportation_problem(costs, x_max, y_max):
row = len(costs)
col = len(costs[0])
prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMaximize)
var = [[pulp.LpVariable(f'x{i}{j}', lowBound=0, cat=pulp.LpInteger) for j in range(col)] for i in range(row)]
flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
prob += pulp.lpDot(flatten(var), costs.flatten())
for i in range(row):
prob += (pulp.lpSum(var[i]) <= x_max[i])
for j in range(col):
prob += (pulp.lpSum([var[i][j] for i in range(row)]) <= y_max[j])
prob.solve()
return {'objective':pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for i in range(row)]}
if __name__ == '__main__':
costs = np.array([[500, 550, 630, 1000, 800, 700],
[800, 700, 600, 950, 900, 930],
[1000, 960, 840, 650, 600, 700],
[1200, 1040, 980, 860, 880, 780]])
max_plant = [76, 88, 96, 40]
max_cultivation = [42, 56, 44, 39, 60, 59]
res = transportation_problem(costs, max_plant, max_cultivation)
print(f'最大值为{res["objective"]}')
print('各变量的取值为:')
pprint(res['var'])
拟分配n人去干n项工作,每个人干且仅干一项工作,若分配第i人去干第j项工作,需要花 c i j c_{ij} cij单位时间,问应该如何分配工作才能使工人花费的总时间最少?
假设指派问题的系数矩阵如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRKV9qo6-1632730912721)(https://gitee.com/tremblingv5/my-bed/raw/master/%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1%E4%B8%AD%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%B1%BB%E5%9E%8B%E9%A2%98%E7%9B%AE%E7%9A%84%E4%B8%BB%E8%A6%81%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E4%B8%8E%E6%96%B9%E6%B3%95/%E6%8C%87%E6%B4%BE%E9%97%AE%E9%A2%98%E5%9B%BE1.0924.svg)]
引入变量 x i j x_{ij} xij,若分配i干j工作,则 x i j = 1 x_{ij}=1 xij=1否则 x i j = 0 x_{ij}=0 xij=0,上述指派问题的数学模型如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXZJnEnq-1632730912723)(https://gitee.com/tremblingv5/my-bed/raw/master/%E6%95%B0%E5%AD%A6%E5%BB%BA%E6%A8%A1%E4%B8%AD%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E7%B1%BB%E5%9E%8B%E9%A2%98%E7%9B%AE%E7%9A%84%E4%B8%BB%E8%A6%81%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B%E4%B8%8E%E6%96%B9%E6%B3%95/math.0924.svg)]
指派问题的可行解用矩阵表示,每行每列有且仅有一个元素为1,其余元素为0
import numpy as np
# linear_sum_assignment 为指派问题专用库
from scipy.optimize import linear_sum_assignment
"""
定义了开销矩阵(指派问题的系数矩阵)efficiency_matrix,
传入linear_sum_assignment,结果返回的是最优指派的行和列,
例如第一行选择第二列,意为:将第一个人派往第二个工作。
而根据numpy.array的性质,传入行和列就会返回行列所对应的值,
即为输出的第三列
"""
efficiency_matrix = np.array([
[12,7,9,7,9],
[8,9,6,6,6],
[7,17,12,14,12],
[15,14,6,6,10],
[4,10,7,10,6]
])
row_index, col_index=linear_sum_assignment(efficiency_matrix)
print(row_index+1)
print(col_index+1)
print(efficiency_matrix[row_index,col_index])
print(efficiency_matrix[row_index, col_index].sum())
要求一部分值必须使整数的规划问题(线性规划、非线性规划、二次规划)
相比于非整数规划,对非整数规划的解进行四舍五入或者取整,并不能保证结果依然使最优解。
基本思想:把整数规划转换成一个个线性规划问题,在求解这些线性规划问题时过程中不断趋近原问题的上下界
分支:全部可行解空间反复地分割为越来越小地子集
定界:对每个子集计算一个目标上界
设有最大化的整数规划问题 A,先解与之相应的线性规划问题 B,若 B 的最优解不符合 A 的整数条件,则 B 的最优目标函数必是 A 的最优目标函数 z 的上界,记为 z2,而 A 的任意可行解的目标函数值将是 z 的一个下界 z1。分支定界法就是将 B 的可行域分成子区域(分支)的方法,逐步减小 z2 和增大 z1,最终求到 z*
分支定界法是一个迭代算法,随着迭代过程不断更新上界和下界,直到上界和下界非常接近时结束。通常设置 Gap < 0.1%,就可把当前的最优可行解近似为问题的全局最优解了。因此,分支定界法的“收敛” 不是分析意义上的而是算法意义上的,优化结果是近似解而不是精确解。
分支定界法不用区分完全整数规划与混合整数规划,算法便于实现,但计算量比较大
基本思路:先求普通线性规划问题地最优解,再对非整数解添加约束条件使可行或缩小,如此反复求解直到得到整数解
不考虑整数约束条件,直接求松弛问题地最优解,在此基础上再添加新地约束条件。
割平面法的计算量比较小,但对问题的结构及求解的要求较高,算法比较复杂。
某厂生产甲乙两种饮料,每百箱甲饮料需用原料 6千克、工人 10名,获利 10万元;每百箱乙饮料需用原料 5千克、工人 20名,获利 9万元。
今工厂共有原料 60千克、工人 150名,又由于其他条件所限甲饮料产量不超过8百箱。
问题 1:问如何安排生产计划,即两种饮料各生产多少使获利最大?
问题 2:若投资0.8万元可增加原料1千克,是否应作这项投资?投资多少合理?
问题 3:若不允许散箱(按整百箱生产),如何安排生产计划,即两种饮料各生产多少使获利最大?
问题 4:若不允许散箱(按整百箱生产),若投资0.8万元可增加原料1千克,是否应作这项投资?投资多少合理?
规划类问题:问题定义、模型构建、模型求解
问题定义, 确定决策变量、目标函数和约束条件。
决策变量是问题中可以在一定范围内进行变化而获得不同结果的变量。
对于问题 1,问题描述中说的很明确,希望通过改变甲、乙两种饮料的产量使总利润最大,甲、乙两种饮料的产量就是决策变量。
对于问题 2 则要注意,如果只看前一句,就是比较问题 1 与问题 2 的利润,还是把甲、乙两种饮料的产量作为决策变量。但要回答后一句“投资多少合理”,这就出现了一个新的变量“投资额”,因此对问题 2 要建立 3个决策变量:甲产量、乙产量和投资额。
目标函数是决策变量的函数,我们希望通过改变决策变量的值而获得目标函数的最大值或最小值,通常是总成本(最小)、总利润(最大)、总时间(最短)。
对于本案例,每个问题都是希望获得最大利润,目标函数都是总利润,问题是求目标函数即总利润的最大值。
约束条件是决策变量所要满足的限制条件。
约束条件 3 种情况:
一是不等式约束,例如题目指出共有原料 60千克、工人 150名,因此生产计划所用的原料、工人的需求不能大于题目中数值。
二是等式约束,本题没有等式约束条件。
三是决策变量取值范围的约束。
通常,题目隐含着决策变量大于等于 0 的条件,例如工人人数、原料数量都要大于等于 0。
另外,如果能通过分析前面的等式约束或不等式约束,得出决策变量的上限,将会极大的提高问题求解的速度和性能。后文将对此举例说明。
模型构建, 由问题描述建立数学方程,并转化为标准形式的数学模型。
对于问题 1,目标函数是生产甲、乙两种饮料的总利润,约束条件是原料总量、工人总数的约束,而且原料、工人都要大于等于 0。
maxf(x)=10∗x1+9∗x2s.t.:⎧⎩⎨⎪⎪⎪⎪6∗x1+5∗x2≤6010∗x1+20∗x2≤1500≤x1≤8x2≥0
进一步分析决策变量取值范围的约束条件,由原料数量、工人数量的不等式约束可以推出:
x1≤15x2≤7.5
对于问题 2,可以通过增加投资来获得更多的原料,投资额是一个新的变量。要注意的是,此时目标函数虽然也是生产两种饮料的总利润,但总利润不等于总收入,而是总收入减去总成本,在本例中就是要减去购买原料的投资。
maxf(x)=10∗x1+9∗x2−x3s.t.:⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪6∗x1+5∗x2≤60+x3/0.810∗x1+20∗x2≤1500≤x1≤150≤x2≤7.5x3≥0
对于问题 3 和问题 4,区别只是不允许散箱,明确提出了决策变量 x1、x2 的取值要取整数值,所以是整数规划问题。
需要注意的是,问题 4 中对增加的投资额即购买的原料数量并没有整数限制,因此 x1、x2 的取值范围是正整数,但 x3 的取值范围是正数,这是一个混合整数规划问题。
还要说明的是,对于问题 1 和问题 2,虽然题目中没有明确要求生产甲、乙饮料的工人人数为整数,但是人数也不可能是小数的,那么这是不是也是整数规划问题呢?
如果你能提出这个问题,那么恭喜你,你已经从小白升级为菜鸟了。
我的理解是,这个问题怎么说都可以。如果要简化问题,使用线性规划模型,最好在问题假设中说一句,假设甲乙饮料在同一车间先后生产,只要允许甲乙饮料散箱生产,即使根据产量所求出的工人数是小数,也可以解释的通。如果你掌握了整数规划问题的求解,那就先按线性规划建模,再补充讨论工人人数也必须是整数的条件,按整数规划建模求解,这就是妥妥的获奖论文了。
python pulp求解步骤
import pulp
# 定义问题,求最大值
ProbLP1 = pulp.LpProblem("ProbLP1", sense=pulp.LpMaximize)
问题1
# 定义x1
x1 = pulp.LpVariable('x1', lowBound=0, upBound=15, cat='Continuous')
# 定义x2
x2 = pulp.LpVariable('x2', lowBound=0, upBound=7.5, cat='Continuous')
pulp.LpVariable
定义决策变量
lowBound
,upBound
决策变量上下界
cat
定义变量类型,连续变量:Continuous
,离散变量:Integer
,0/1变量:Binary
问题3
x1 = pulp.LpVariable('x1', lowBound=0, upBound=15, cat='Integer')
x2 = pulp.LpVariable('x2', lowBound=0, upBound=7.5, cat='Integer')
ProbLP1 += (10*x1 + 9*x2) # 设置目标函数 f(x)
# 不等式约束
ProbLP1 += (6*x1 + 5*x2 <= 60)
# 不等式约束
ProbLP1 += (10*x1 + 20*x2 <= 150)
ProbLP1.solve()
# 输出求解状态
print(ProbLP1.name)
print("Status:", pulp.LpStatus[ProbLP1.status])
for v in ProbLP1.variables():
# 输出每个变量的最优值
print(v.name, "=", v.varValue)
# 输出最优解的目标函数值
print("F1(x) =", pulp.value(ProbLP1.objective))
数值优化算法是寻找一个函数最优解的问题,这个函数被成为目标函数,通常使用
scipy.optimize
进行优化
梯度下降法通常是沿着梯度下降的方向寻找最优解,但是对于一些复杂函数,梯度下降法不一定可以找到最优解。
在scipy中,带有梯度下降的方法名通常包含cg
,其中简单梯度下降方法的函数名为scipy.optimize.fmin_cg()
from scipy import optimize
def f(x):
return 0.5 * (1 - x[0]) ** 2 + (x[1] - 7) ** 2
optimize.fmin_cg(f,[0,0])
另,可以设置梯度,设置梯度有助于提升算法性能
def fprime(x):
return np.array((-2 * .5 * (1 - x[0]), 2 * (x[1] - 7)))
optimize.fmin_cg(f, [0, 0], fprime=fprime)
牛顿法在现有极小点估计值的附近对目标函数做二阶展开,进而找到极小点的下一个估计值。
def f(x):
return 0.5 * (1 - x[0]) ** 2 + (x[1] - 7) ** 2
# 一阶导
def fprime(x):
return np.array((-2 *.5 * (1 - x[0]), 2 * (x[1] - 7)))
# 二阶导
def hessian(x):
return np.array(-2 *.5 *x[0], 2 * x[1])
# 最少需要传入一阶导
optimize.fmin_ncg(f, [200,800], fprime=fprime)
optimize.fmin_ncg(f, [200,800], fprime=fprime, fhess=hessian)
改进了每一步对Hessian的近似。在一些正常的函数中,BFGS 虽然不如牛顿法快,但是还是比较快的。而且对于一些情况复杂的函数,BFGS 要比牛顿法好,因为它对 Hessian 进行了改进
def f(x):
return 0.5 * (1 - x[0]) ** 2 + (x[1] - 7) ** 2
# 一阶导
def fprime(x):
return np.array((-2 *.5 * (1 - x[0]), 2 * (x[1] - 7)))
optimize.fmin_bfgs(f, [200, 800], fprime=fprime)
类似梯度下降法
def f(x):
return 0.5*(1 - x[0])**2 + (x[1] - 7)**2
# 一阶导
def fprime(x):
return np.array((-2*.5*(1 - x[0]), 2*(x[1] - 7)))
optimize.fmin_powell(f,[0,0])
对噪音有很好的抵抗性,不依赖于梯度,可以在局部光滑的函数上发挥作用;在光滑、非噪音函数上比梯度法更慢
def f(x):
return 0.5*(1 - x[0])**2 + (x[1] - 7)**2
# 一阶导
def fprime(x):
return np.array((-2*.5*(1 - x[0]), 2*(x[1] - 7)))
optimize.fmin(f, [2, 2])
scipy.optimize
中的3种限制条件在指定边界内寻找最优解
def f(x):
return 0.5*(1 - x[0])**2 + (x[1] - 7)**2
# 一阶求导
def fprime(x):
return np.array((-2*.5*(1 - x[0]), 2*(x[1] - 7)))
# 这里限制了 x[0] 的取值在(1,2), x[1] 的取值在(9,19)
optimize.fmin_l_bfgs_b(f, [0,0], approx_grad=1, bounds=((1,2),(9,19)))
def f(x):
return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2)
# 自定义限制条件
def constraint(x):
return x[0]+x[1] - 4
# eqcons == 0.0
optimize.fmin_slsqp(f, np.array([0, 0]), eqcons=[constraint,])
def f(x):
return np.sqrt((x[0] - 3)**2 + (x[1] - 2)**2)
# 自定义限制条件
def constraint(x):
return x[0]+x[1] - 4
# eqcons == 0.0
optimize.fmin_slsqp(f, np.array([0, 0]), eqcons=[constraint,])
组合优化算法是一组求离散状态数据集最优解的算法,这些算法常常用来解决NP-Hard级问题。后续组合优化算法均使用python库:scikit-opt来调用。
模拟退火算法是一种贪心算法,通过模拟淬火降温过程,以一定的概率接受一个比当前解要差的解,从而跳出局部最优解,通过多次迭代计算,从而不断趋近于最优解。
模拟退火算法是一种随机算法,并不能保证得到最优解,可以以较高的效率获取到近似最优解。
求函数 f(x) = x ^ 2 - 2 * x + 1的最小值
设置目标函数
func = lambda x: x[0] ** 2 - x[0] * 2 + 1
调用库函数
"""
初始值设置为50
停止上限为100
停止下限为1e-9
"""
sa = SA(func=func, x0=[50], T_max=100, T_min=1e-9, max_stay_counter=150)
执行并获取结果
best_x, best_y = sa.run()
print(best_x, best_y)
plt.plot(pd.DataFrame(sa.best_y_history).cummin(axis=0))
plt.savefig("test.jpg")
[0.99999992] 5.773159728050814e-15
粒子群算法是一种进化算法,源于对鸟类捕食的研究,通过群体中个体之间的协作和信息共享来寻找最优解
优势:简单易实现,不需要调节过多参数
一个“粒子”只具有两个属性,速度和位置。
每个粒子在空间中单独的寻找最优解,并记录为单个粒子的极值,同时将这个局部最优解和其他粒子共享,每个粒子再通过共享的局部最优解来调整自身的速度和位置,从而使整个粒子群不断趋近最优解
示例代码如下,与模拟退火算法类似,主要流程即定义问题,然后调用库中的算法进行解题:
from sko.PSO import PSO
import matplotlib.pyplot as plt
def target(x):
x1, x2, x3 = x
return x1 ** 2 + (x2 - 0.05) ** 2 + x3 ** 2
pso = PSO(
func=target,
n_dim=3,
max_iter=150,
lb=[0, -1, 0.5],
ub=[1, 1, 1],
c1=0.5,
c2=0.5
)
x, y = pso.run()
print(f"x: {x}, y: {y}")
遗传算法的抽象理解:给定一个初始解,经过一系列的变化(遗传、变异、交叉、复制),最终进化出最优解决。
与模拟退火算法使用场景类似,都适用于解决np-hard问题,从随机中不断趋近最优解
示例代码如下:
import pandas as pd
import matplotlib.pyplot as plt
from sko.GA import GA
def schaffer(p):
x1, x2 = p
x = np.square(x1) + np.square(x2)
return 0.5 + (np.square(np.sin(x)) - 0.5) / np.square(1 + 0.001 * x)
ga = GA(
func=schaffer,
n_dim=2,
size_pop=50,
max_iter=800,
prob_mut=0.001,
lb=[-1, -1],
ub=[1, 1],
precision=1e-7
)
x, y = ga.run()
print(f"x: {x}, y: {y}")
蚁群算法是一种用来寻找优化路径的概率型算法。它由Marco Dorigo于1992年在他的博士论文中提出,其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。
示例代码如下:
from sko.ACA import ACA_TSP
from scipy import spatial
import numpy as np
points_coordinate = np.random.rand(50, 2)
distance_matrix = spatial.distance.cdist(points_coordinate, points_coordinate, metric='euclidean')
def cal_total_distance(routine):
num_points, = routine.shape
return sum([distance_matrix[routine[i % num_points], routine[(i + 1) % num_points]] for i in range(num_points)])
aca = ACA_TSP(func=cal_total_distance, n_dim=50,
size_pop=50, max_iter=200,
distance_matrix=distance_matrix)
best_x, best_y = aca.run()
print(best_x, best_y)
插值:在离散数据的基础上补插连续函数,使得这条连续曲线通过全部给定的离散数据点。 插值是离散函数逼近的重要方法,利用它可通过函数在有限个点处的取值状况,估算出函数在其他点处的近似值
拟合:是用一个连续函数(曲线)靠近给定的离散数据,使其与给定的数据相吻合
回归:研究一组随机变量与另一组随机变量之间关系的统计分析方法,包括建立数学模型并估计模型参数,并检验数学模型的可信度,也包括利用建立的模型和估计的模型参数进行预测或控制
预测:指对获得的数据、信息进行定量研究,据此建立与预测目的相适应的数学模型,然后对未来的发展变化进行定量地预测
statsmodels
的线性回归import statsmodels.api as sm
from statsmodels.sandbox.regression.predstd import wls_prediction_std
nSample = 100
# 起点为 0,终点为 10,均分为 nSample个点
x1 = np.linspace(0, 10, nSample)
# 正态分布随机数
e = np.random.normal(size=len(x1))
# y = b0 + b1*x1
yTrue = 2.36 + 1.58 * x1
# 产生模型数据
yTest = yTrue + e
本案例是一元线性回归问题,(yTest,x)是导入的样本数据,我们需要通过线性回归获得因变量 y 与自变量 x 之间的定量关系。yTrue 是理想模型的数值,yTest 模拟实验检测的数据,在理想模型上加入了正态分布的随机误差
一元线性回归模型方程为
y = β0 + β1 * x + e
先通过 sm.add_constant() 向矩阵 X 添加截距列后,再用 sm.OLS() 建立普通最小二乘模型,最后用 model.fit() 就能实现线性回归模型的拟合,并返回拟合与统计分析的结果摘要。
X = sm.add_constant(x1) # 向 x1 左侧添加截距列 x0=[1,...1]
model = sm.OLS(yTest, X) # 建立最小二乘模型(OLS)
results = model.fit() # 返回模型拟合结果
statsmodels.OLS 是 statsmodels.regression.linear_model 的函数,有 4个参数 (endog, exog, missing, hasconst)。
第一个参数 endog 是回归模型中的因变量 y(t), 是1-d array 数据类型。
第二个输入 exog 是自变量 x0(t),x1(t),…,xm(t),是(m+1)-d array 数据类型。
需要注意的是,statsmodels.OLS 的回归模型没有常数项,其形式为:
y = BX + e = β0x0 + β1*x1 + e, x0 = [1,…1]
而之前导入的数据 (yTest,x1) 并不包含 x0,因此需要在 x1 左侧增加一列截距列 x0=[1,…1],将自变量矩阵转换为 X = (x0, x1)。函数 sm.add_constant() 实现的就是这个功能。
参数 missing 用于数据检查, hasconst 用于检查常量,一般情况不需要。
print(results.summary()) # 输出回归分析的摘要
coef:回归系数(Regression coefficient),即模型参数 β0、β1、…的估计值。
std err :标准差( Standard deviation),也称标准偏差,是方差的算术平方根,反映样本数据值与回归模型估计值之间的平均差异程度 。标准差越大,回归系数越不可靠。
t:t 统计量(t-Statistic),等于回归系数除以标准差,用于对每个回归系数分别进行检验,检验每个自变量对因变量的影响是否显著。如果某个自变量 xi的影响不显著,意味着可以从模型中剔除这个自变量。
P>|t|:t检验的 P值(Prob(t-Statistic)),反映每个自变量 xi 与因变量 y 的相关性假设的显著性。如果 p<0.05,可以理解为在0.05的显著性水平下变量xi与y存在回归关系,具有显著性。
[0.025,0.975]:回归系数的置信区间(Confidence interval)的下限、上限,某个回归系数的置信区间以 95%的置信度包含该回归系数 。注意并不是指样本数据落在这一区间的概率为 95%。
此外,还有一些重要的指标需要关注:
R-squared:R方判定系数(Coefficient of determination),表示所有自变量对因变量的联合的影响程度,用于度量回归方程拟合度的好坏,越接近于 1说明拟合程度越好。
F-statistic:F 统计量(F-Statistic),用于对整体回归方程进行显著性检验,检验所有自变量在整体上对因变量的影响是否显著。
Statsmodels 也可以通过属性获取所需的回归分析的数据,例如:
print("OLS model: Y = b0 + b1 * x") # b0: 回归直线的截距,b1: 回归直线的斜率
print('Parameters: ', results.params) # 输出:拟合模型的系数
yFit = results.fittedvalues # 拟合模型计算出的 y值
ax.plot(x1, yTest, 'o', label="data") # 原始数据
ax.plot(x1, yFit, 'r-', label="OLS") # 拟合数据
判别分析是一种分类方法,根据已掌握的每个类别的若干个样本信息,求出判别函数,再根据判别函数判别未知类别的样本点的类别。
距离判别法为根据待判定对象的距离,以就近原则进行判别。这里的距离通常采用Mahalanobis距离(马氏距离)
蠓虫是一种昆虫,分为很多类型,其中有一种名为Af,是能传播花粉的益虫;另一种名为Apf,是会传播疾病的害虫。这两种蠓虫在形态上十分相似,难以区分。现测得9只Af和6只Apf的触角长度和翅膀长度数据如下:
Af:
触角长度 | 翅膀长度 |
---|---|
1.24 | 1.27 |
1.36 | 1.74 |
1.38 | 1.64 |
1.38 | 1.82 |
1.38 | 1.90 |
1.40 | 1.70 |
1.48 | 1.82 |
1.54 | 1.82 |
1.56 | 2.08 |
Apf:
触角长度 | 翅膀长度} |
---|---|
1.14 | 1.78 |
1.18 | 1.96 |
1.20 | 1.86 |
1.26 | 2.00 |
1.28 | 2.00 |
1.30 | 1.96 |
若两类蠓虫协方差矩阵相同,试判别如下3只蠓虫属于哪一类
触角长度 | 翅膀长度} |
---|---|
1.24 | 1.80 |
1.28 | 1.84 |
1.40 | 2.04 |
示例代码如下:
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
X0 = np.array(
[
[1.24, 1.27],
[1.36, 1.74],
[1.38, 1.90],
[1.38, 1.82],
[1.38, 1.90],
[1.40, 1.70],
[1.48, 1.82],
[1.54, 1.82],
[1.56, 2.08],
[1.14, 1.78],
[1.18, 1.96],
[1.20, 1.86],
[1.26, 2.00],
[1.28, 2.00],
[1.30, 1.96]
]
)
x = np.array(
[
[1.24, 1.80],
[1.28, 1.84],
[1.40, 2.04]
]
)
g = np.hstack(
[
np.ones(9),
2 * np.ones(6)
]
)
v = np.cov(X0.T)
knn = KNeighborsClassifier(2, metric='mahalanobis', metric_params={'V': v})
knn.fit(X0,g)
pre = knn.predict(x)
print("马氏距离:", pre)
print("马氏距离已知样本误判率:", 1 - knn.score(X0, g))
knn2 = KNeighborsClassifier(2)
knn2.fit(X0,g)
pre2 = knn2.predict(x)
print("欧式距离", pre2)
print("欧氏距离误判率", 1 - knn2.score(X0,g))
输出结果如下:
马氏距离: [2. 2. 1.]
马氏距离已知样本误判率: 0.0
欧式距离 [2. 1. 2.]
欧氏距离误判率 0.0
Fisher判别法是基于方差分析的判别法
示例代码如下:
import numpy as np
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.neighbors import KNeighborsClassifier
X0 = np.array(
[
[1.24, 1.27],
[1.36, 1.74],
[1.38, 1.90],
[1.38, 1.82],
[1.38, 1.90],
[1.40, 1.70],
[1.48, 1.82],
[1.54, 1.82],
[1.56, 2.08],
[1.14, 1.78],
[1.18, 1.96],
[1.20, 1.86],
[1.26, 2.00],
[1.28, 2.00],
[1.30, 1.96]
]
)
x = np.array(
[
[1.24, 1.80],
[1.28, 1.84],
[1.40, 2.04]
]
)
g = np.hstack(
[
np.ones(9),
2 * np.ones(6)
]
)
clf = LDA()
clf.fit(X0,g)
print("判别结果为", clf.predict(x))
print("已知样本的误判率为:", 1 - clf.score(X0,g))
结果如下:
判别结果为 [2. 2. 1.]
已知样本的误判率为: 0.0