Matlab问题求解

2 年前 · 来自专栏 Matlab教程

一、函数和子函数

一个M文件中,可能会有多个函数,其中第一个称为主函数,后面的所有函数称为子函数

  • 脚本文件中,也可以直接在脚本的最后添加子函数,在当前文件夹内,如果有同名函数,按照子函数 \rightarrow MATLAB内建函数 \rightarrow 其他 \rightarrow M文件主函数的顺序访问。子函数最后的 end 不能省略
  • 一个M文件的主函数通常和M文件名相同(否则MATLAB仍以文件名主名作为识别标准),一个M包含多个函数时,每个函数最后的 end 或者都省略掉,或者都不省略。
  • 所有的子函数都可以被M文件内的脚本或主函数调用,但无法被其他M文件或命令行直接调用。因此,子函数是一种减少M文件数量,封装外部脚本不直接调用的函数的好方法。

1. 子函数

编写一个内含子函数的M函数绘图文件

HC = Draw_d('circle');
HL = Draw_d('line');
function Hr = Draw_d(flag)
    % exm060301.m   Demo for handles of primary functions and subfunctions
    %        flag       %允许使用字符串’line’或’circle’     
    %   Hr     %返回子函数cirline的句柄 
    t = (0:50)/50*2*pi;    %0~2pi等分了50个区间
    x = sin(t);
    y = cos(t);
    Hr = @cirline;     %创建cirline的句柄(一种函数名的不同解读,类似于C++的指针)
    feval(Hr, flag, x, y, t);  %feval结合句柄调用,等价于cirline(flag,x,y,t)
function cirline(wd, x, y, t)
    %           wd         %主函数传递来的flag,可能为’line’或’circle’    
    %           t,x,y  %分别为绘图参数、横坐标与纵坐标    
    switch wd
        case 'line'
            plot(t, x, 'b', t, y, 'r', 'LineWidth', 2);
        case 'circle'
            plot(x, y, '-g', 'LineWidth', 8);
            axis square off;
        otherwise
            error('输入变量只能取“line”或“circle”!')

HC 的输出结果为:

HL 的输出结果为:

另外我们可以将 t 的采样距离缩小,比如绘制正五边形采样点分布:

t=0:2*pi/5:2*pi;x=cos(t);y=sin(t);
HH('circle',x,y,t)
%利用m文件主函数返回的句柄可以间接调用到子函数。
%如果没有返回的句柄,则子函数cirline无法被外部调用

2. 匿名函数

  • 适用于结构简单、无需创建m文件或子函数体来定义的“一句话函数”
  • 例如: F=@(x) sin(x).*x ,就定义了一个自变量为 x ,函数值 F(x)=sin(x).*x 的匿名函数。命名上, F 称为函数句柄,括号内的 x 称为参数(参数可以由多个变量构成参数列表), sin(x).*x 称为函数主体表达式。
  • 匿名函数可以通过如 F(3) F(x) 直接调用,也可以通过 feval(F,x) 间接调用,有时,函数句柄 F 还可以作为参数代入一些更为复杂的函数体。

3. 函数句柄

函数的句柄类似于指针,除 plot 绘图返回值,匿名函数定义外,还可对MATLAB内建函数或用户已定义函数创建句柄。

hm = @magic;
class(hm)           % ans = 'function_handle'
functions(hm)       % 查询句柄对应函数信息,包含function(函数名), type(函数类型sinple), file(文件位置)
hm(4)
16     2     3    13
5    11    10     8
9     7     6    12
4    14    15     1

调用子函数时,函数句柄可以“完全的代替”本身子函数的函数名,格式与子函数直接调用相同。但利用函数句柄多次调用可以大大节省调用的时间(不必重复搜索路径)。

二、函数极值的数学方法

  • [x,fval,exitflag]=fminbnd(fun,x1,x2) 可以求一元函数 fun [x1,x2] 的一个极小值, fun 可使用字符串,匿名函数或函数句柄。返回值列表 x 为极小值点, fval 为函数极小值, exitflag>0 代表此函数成功找到了一个极值点。其余功能 output option 可以通过帮助系统了解用法,它们主要可以让MATLAB显示迭代过程中的各种运算指标。
  • [x,fval,exitflag]=fminsearch(fun,x0) 可以利用无导数方法(如单纯形法),从 x_0 点出发,求多元函数 fun 在在多维空间中的一个极小值, fun 建议使用多元匿名函数或多元函数句柄(输入值为向量)。返回值列表 x 为极小值点向量, fval 为函数极小值,其余功能与 fminbnd 类似。
  • 注意到两个函数均为找一个极小值,有时也称为局部最小值,并不一定是定义域内的全局最小值。希望获取全局最小值,需要设立较好的区间或起始点,或反复遍历所有极值点。

1. 划分区间求极值

例:求 y=e^{-0.1x}\text{sin}^2x-0.5(x+0.1)\text{sin}x -50\le x\le 50 的极小值

x1=-50;x2=5;  
yx=@(x)(sin(x).^2.*exp(-0.1*x)-0.5*sin(x).*(x+0.1));    %定义函数句柄
[xc0,fc0,exitflag]=fminbnd(yx,x1,x2) %找到的其中一个极小值 
ezplot(yx,[-50,5]);                     %ezplot同样可用于函数句柄,只需指定x的范围即可
xlabel('x'),grid on 
xx=[-23,-20,-18];                       %观察最小值疑似存在的两个区段
fc=fc0;xc=xc0;                          %暂时设立最小值点和最小值为初始搜索的结果
for k=1:2
 [xw,fw]=fminbnd(yx,xx(k),xx(k+1));  %分别计算两个区段的极小值
 if fw<fc, xc=xw;fc=fw;end           %若有更小的极小值则更换最小值
fprintf('函数最小值%6.5f发生在x=%6.5f处',fc,xc)

2. 单峰函数求极值之黄金分割法

对于向内的试探法,从 [a_k,b_k] 开始,当 α_1≈0.618a_k+0.382b_k,α_2≈0.382a_k+0.618b_k 时,探索的效率最高,这样的试探法就成为黄金分割法(0.618法)

黄金分割法的算法复杂度为 O(\text{ln}\frac{b-a}{\epsilon}) ,而且可以处理目标函数不可导的特殊情况。操作代码如下:

x1=-20;x2=-18;      
yx=@(x)(sin(x).^2.*exp(-0.1*x)-0.5*sin(x).*(x+0.1));
%设立初始最小值
minf = yx(x1);
if(yx(x2)<minf) 
    minf = yx(x2); 
while(1)
    alpha1 = x1*0.618+x2*0.382;
    alpha2 = x1*0.382+x2*0.618;
    if(yx(alpha1)>yx(alpha2))
        x1 = alpha1;
        if(yx(alpha2)<minf) 
            minf = yx(alpha2); 
        x2 = alpha2;
        if(yx(alpha1)<minf)
            minf = yx(alpha1);
    if(alpha2-alpha1<1e-8)
        break;
minf, alpha1

3. 牛顿迭代法

  • 牛顿迭代法起源于一阶泰勒展开近似
    f(x) \approx f\left(x_{0}\right)+f^{\prime}\left(x_{0}\right)\left(x-x_{0}\right) \\ 此时, 设存在 \bar{x}, 满足 f(\bar{x})=0 ,则给定任意一点 x,
    f(x) \approx f(\bar{x})+f^{\prime}(\bar{x})(x-\bar{x})=f^{\prime}(\bar{x})(x-\bar{x}) \\ 移项后即可得到
    \bar{x} \approx x-\frac{f(x)}{f^{\prime}(x)} \\ 对于线性函数 f(x), 此法可快速求解 f(x)=\mathbf{0}
  • 几何上讲, 牛顿迭代法类似于寻找切线方向并移动至x轴交点, 迭代公式记为:
    x_{n+1} = x_n-\frac{f(x_n)}{f^`(x_n)} \\
  • 对于求局部最小值问题, 因
    \text{argmin }_x f(x) \Leftrightarrow f^{\prime}(x)=0 \\ 因此对应的牛顿迭代法公式只需改为导数方程问题,迭代公式:

x_{n+1}=x_{n}-\frac{f^{\prime}\left(x_{n}\right)}{f^{\prime \prime}\left(x_{n}\right)} \\

例:求 y=e^{-0.1x}\text{sin}^2x-0.5(x+0.1)\text{sin}x -20\le x\le -18 的最小值,代码如下:

x1=-20;x2=-18;syms x;  
yx=@(x)(sin(x).^2.*exp(-0.1*x)-0.5*sin(x).*(x+0.1));
yxp = diff(yx,x);  %MATLAB默认使用sym类型储存导函数表达式
yxp2 = diff(yxp,x);  %二阶导函数表达式
x_old = -19;iter = 0; %迭代初始值与迭代次数
while(1)
   iter = iter + 1;
   x_new = x_old - double(subs(yxp, x, x_old) / subs(yxp2, x, x_old));
   if(abs(x_new-x_old)<1e-8)
       break
   x_old = x_new;
disp([iter, x_new, yx(x_new)]);

三、非线性方程组Matlab求解

求解非线性方程或方程组,除了单调函数二分法、光滑函数牛顿法外,还可以使用MATLAB函数进行求解,主要有以下两种形式:

  • [x,fval]=fzero(fun,x0) 将以 x_0 为初值,尝试寻找函数句柄或匿名函数 fun 的一个零点, fval 为对应函数值
  • [x,fval]=fsolve(fun,x0) 将以 x_0 为初值(向量),尝试寻找函数向量 fun 的一个零点, fval 为对应函数向量值

例:求 f(t)=sin^2t·e^{-0.1t}-0.5|t| 的零点,代码如下:

% solve函数求解
syms t;
ft=sin(t)^2*exp(-0.1*t)-0.5*abs(t);
S = solve(ft, t);
ftS=subs(ft,t,S);
disp([S, ftS]);
% 作图观察
y_C=@(t) sin(t).^2.*exp(-0.1*t)-0.5*abs(t); %函数句柄形式的定义
t=-10:0.01:10; Y=y_C(t); %句柄很多时候可以跟函数名一样使用
clf,plot(t,Y,'r');hold on
plot(t,zeros(size(t)),'k'); %另画一条黑色0函数曲线
xlabel('t');ylabel('y(t)')
hold off  
% 观察零点位置
zoom on  %鼠标拉出方框,将方框选定区域放大
[tt,yy]=ginput(5); %鼠标点击五个位置(零点近似值),并记录坐标
zoom off  %回到正常比例

四、多元函数最优化

例:求 f(x,y)=100(y-x^2)^2+(1-x)^2 的极小值点,代码如下:

ff=@(x)(100*(x(2)-x(1)^2)^2+(1-x(1))^2);
%函数句柄,x为输入的(行或列)向量,利用两个元素分别进行计算
syms x y,ezsurfc(ff([x,y]),[-2,2,-2,2]) %将横纵坐标x,y认定为ff二维自定义变量,即可进行surfc操作  
format short g %五位有效数字,省略小数点后尾数的0
x0=[-5,-2,2,5;-5,-2,2,5]; %设立4种不同的搜索起点(每一种为列向量)
[sx,sfval,sexit,soutput]=fminsearch(ff,x0)
%收敛到了四种不同的解,但仅有第一个x=1,y=1是正确的
format short e %短科学计数法
disp([ff(sx(:,1)),ff(sx(:,2)),ff(sx(:,3)),ff(sx(:,4))]) 
%比较四个极小值点对应的函数值,显然第一个点为四个极小值点中取最小值的 
clear,clc
ff=@(x) (100*(x(2)-x(1)^2)^2+(1-x(1))^2); %匿名函数需以向量为变量
x0=[5,5];                  %仅接受一种初值计算,以5,5为例
options = optimoptions(@fminunc,'OptimalityTolerance',1e-8);
%设置迭代的终止条件,以确保误差小于1e-8(默认1e-6)
[x,fval] = fminunc(ff,x0,options)
%求解(前述四种初值点均可以成功找到准确的解)

1. 梯度下降法求解多元最小值问题

将一元最优化问题推广到多元问题 \text{argmin}_{x\in \mathbb{R}^n}\phi(x) ,设 \phi(x) 满足一定的光滑性且其梯度除极值点外不为0向量。则 \phi(x) x=x^{(k)} 下降最快的方向为其负梯度方向: \boldsymbol{p}^{(k)}=-\nabla \varphi\left(x^{(k)}\right)

因此对于凸问题,只需按 x^{(k+1)}=x^{(k)}+\lambda p^{(k)} 进行迭代即可无限接近理论最小值,其中 \lambda>\mathbf{0} 为待定步长。对于非凸问题,迭代有可能会收敛到某个 局部最小值

对于多元情形:

  • 多元问题同样存在最速下降法,不过有时由于最佳步长计算复杂,也可能用固定步长 λ 代替。
  • 多元问题是否为凸问题,等价于目标函数 φ(x) 的Hesse矩阵是否半正定的判定问题。

例:用梯度下降法求 f(x,y)=100(y-x^2)^2+(1-x)^2 的极小值点,代码如下:

clear,clc
ff=@(x,y) (100*(y-x^2)^2+(1-x)^2);%函数及其导数
dff =@(x,y) [2*x - 400*x*(- x^2 + y) - 2;- 200*x^2 + 200*y];
x0 = -5; y0 = -2;   %初始条件(可改变) 
x_old = x0;y_old = y0;iter=0;
while(1)
    iter = iter+1;
    Grad = dff(x_old,y_old); %梯度方向的获得
    lsf = @(lambda) ff(x_old-lambda*Grad(1),y_old-lambda*Grad(2));  %生成对应方向关于步长的一元函数
    [lambda,~]=fminbnd(lsf,0,10);%搜索最佳步长
    x_new=x_old-lambda*Grad(1);
    y_new=y_old-lambda*Grad(2);
    if(abs(x_new-x_old)<1e-8 && abs(y_new-y_old)<1e-8)    break; %当x与y均保持稳定时结束迭代
    x_old = x_new;y_old = y_new;
iter,x_new,y_new
err = ff(x_new,y_new)-0

本题使用的最速梯度下降法,虽然速度较慢,但在迭代的收敛性和准确性上,均优于基于单纯形法的MATLAB函数 fminsearch ,速度上不如新函数 fminunc

2. 牛顿法解多元最小值问题

多元问题 \text{argmin}_{x\in \mathbb{R}^n}\phi(x) 在使用最速梯度下降法求解时,由于相邻迭代的梯度方向正交,导致移动方向呈现锯齿型,收敛速度与效率不佳。且对于病态矩阵无健壮性。

定义Hesse矩阵 :

\nabla^{2} f(x, y)=\left[\begin{array}{ll} \frac{\partial^{2} f}{\partial x^{2}} & \frac{\partial^{2} f}{\partial x \partial y} \\ \frac{\partial^{2} f}{\partial x \partial y} & \frac{\partial^{2} f}{\partial y^{2}} \end{array}\right] \\

多元问题 argmin_{x \in \mathbb{R}^{n}} \varphi(x) 的牛顿迭代公 式为 (注意矩阵相乘后方向可能改变) :

x^{(k+1)}=x^{(k)}-\left.\left(\nabla^{2} f(x, y)\right)^{-1} \nabla f(x, y)\right|_{x^{(k)}} \\

牛顿法的一个缺点即Hessian不能出现不可逆的点。但此方法既可以增加收敛的概率,也可以大大减少运行时间。

例:用牛顿法求 f(x,y)=100(y-x^2)^2+(1-x)^2 的极小值点,代码如下:

clear,clc
ff=@(x,y) (100*(y-x^2)^2+(1-x)^2); 
hidff =@(x,y) [ (x - 1)/(200*x^2 - 200*y + 1), -(200*x^4 - 400*x^2*y - x^2 + 2*x + 200*y^2 - y)/(200*x^2 - 200*y + 1)];
%提前计算的迭代步长,即inv(Hessian)*Gradient
x0 = -5; y0 = -5;x_old = x0;y_old = y0;iter=0;
while(1)
    iter = iter+1;