简介:广东工业大学数值分析课程实验四的Python实验报告,完整覆盖超定方程组最小二乘解与最小二乘拟合两大核心实验。报告为docx文档,共1个文件、112KB,包含题目三、题目六及石油产量预测三道典型问题,均附有可运行的Python源码、算法介绍和结果分析。题目三基于最小二乘解的充要条件,构造正规方程组ATAx=ATb,借助numpy的线性代数求解器高效算出两组超定方程的解;题目六以1和1/x为基函数建立线性组合模型,调用sklearn的LinearRegression拟合观察数据,得到形如y=2.0143+1.00931/x的拟合函数;石油产量预测则演示了将数值方法应用于实际数据建模的完整流程。读者可从中学习最小二乘问题的矩阵推导、正规方程构造、Python库调用及结果分析方法,适合正在学习数值分析、需要参考完整实验报告或复习相关算法的高校学生。目前已有212人学习该资源。
1. 某高校Python数值分析实验四:为什么代码三分钟跑完,作业却要写一整晚
数值分析这门课,真正动手做起来和听课时完全是两个世界。某高校的Python数值分析实验四,表面上是插值和拟合两个经典主题——拉格朗日插值、牛顿插值、最小二乘拟合,代码用NumPy就能写完,跑通不过三分钟。但真正消耗时间的,是把课本上的公式变成能交代得清楚的程序,把结果画成能放进实验报告的图表,以及处理那些课本没写、IDE也不会告诉你的边界情况。这篇笔记按我实际完成实验四的顺序来写,从任务拆解、环境准备、算法实现到避坑排查,每一步都给可复现的代码和参数说明。适合第一次做数值分析实验的学生,也适合想快速拾起插值与拟合实现的工程师。
2. 拆开实验四的作业要求:任务清单、环境准备与报告结构
2.1 先搞清实验四考什么:两个主线任务和三类题型
数值分析实验四在多数高校的教学安排里,主题都落在“插值法与曲线拟合”这一章。拆开来看,主线任务基本是固定的:用Python实现拉格朗日插值和牛顿插值,再用最小二乘法做曲线拟合,最后把结果和理论做对比。不同年份、不同老师的题目形式会有变化,但底层要求逃不出三类题型。第一类是给定函数或数据表,要求计算指定点的插值结果;第二类是给一组观测数据,要求用多项式拟合出趋势;第三类是综合题,比如对某个病态函数做插值并讨论误差,或者用拟合预测下一组数据点。
先确认自己拿到的是哪类题,再决定代码怎么组织,能省下大量返工时间。我见过不少同学上来就写拉格朗日的循环,写完才发现题目要求的是拟合。更常见的坑是,题目给了十个数据点,但要求分别用三次、五次、七次多项式做拟合,这时候如果没提前把拟合逻辑封装成函数,后面每一个次数都要复制粘贴改参数,实验报告的代码部分会显得很乱,查错也麻烦。所以第一步不是写代码,是把题目要求翻译成一个清单:要实现的算法有几个、输入输出是什么、要求画几张图、误差分析要算哪些量。
2.2 实验环境准备:conda创建虚拟环境与依赖清单
实验四的代码依赖面不宽,但版本不一致会在后面图表上翻车。我一般会先建一个干净的虚拟环境,避免和机器上其他项目共用NumPy和SciPy。用conda创建虚拟环境的命令如下:
conda create -n numeric-analysis python=3.11 conda activate numeric-analysis pip install numpy matplotlib scipy选Python 3.11而不是最新版本,是因为NumPy和SciPy对最新Python版本的支持往往滞后几个月,装完才发现某个依赖编译失败,再回退更浪费时间。NumPy负责数组计算和多项式运算,SciPy在拟合部分会用到curve_fit,Matplotlib负责画图。这三个库覆盖面足够,pandas在这个实验里用不上,装不装看个人习惯。
依赖安装完以后,建议顺手验证一下版本号是否匹配,尤其是NumPy和SciPy之间的版本兼容。我遇到过NumPy 1.x和SciPy 1.11搭配时某个接口行为变化,导致polyfit返回结果格式不同。验证方式很简单,在Python交互环境里分别import并打印版本号,确认能正常加载再开始写实验代码。如果用的是机房预装的环境,版本往往偏旧,建议先确认能不能装新包,不能装就基于现有版本调整写法,别到最后一刻才暴露问题。
2.3 实验报告的结构:算法原理、程序实现、结果分析三位一体
实验报告怎么写,决定了这份作业的最终成绩。数值分析实验的评分点通常有三个:算法原理是否说清楚、代码是否能跑出正确结果、结果分析是否有深度。三者缺一不可,但很多同学只重视代码,原理部分抄课本,分析部分写“图像符合预期”,这等于把最容易拿分的部分放弃了。
我的习惯是先在报告里画一个整体框架,再按照框架去补内容。第一部分写算法原理,包括公式推导和算法步骤,不需要长篇大论,但要写清楚公式中每个符号的含义,比如插值节点、基函数、差商。第二部分写程序实现,代码按功能拆分,关键函数要给注释。第三部分写结果分析,这一部分要包含数值结果和图像的对比,以及误差分析——插值法算出的值和理论值差多少、拟合的残差有多大、为什么会有这个误差。整份报告的逻辑是:读者只看报告不看你跑代码,也能复现你的结果,这才是一份合格的实验报告。
3. 拉格朗日插值与牛顿插值:从公式到Python实现
3.1 拉格朗日插值法的数学原理与代码实现
插值问题的本质,是已知n+1个数据点,找一条通过所有这些点的曲线。拉格朗日插值的思想最直白:构造一组基函数,每个基函数在某个数据点上取值为1,在其他数据点上取值为0,然后把所有基函数加权叠加。权重就是对应数据点的函数值。这样做的好处是公式对称、好理解,代码实现也不需要额外存储中间量。
import numpy as np def lagrange_interp(x_points, y_points, x_eval): """ 拉格朗日插值:计算单个点 x_eval 处的插值结果 x_points: 插值节点数组,例如 [1, 2, 3] y_points: 对应的函数值数组,例如 [1, 4, 9] x_eval: 需要求插值的点,可以是标量或数组 """ n = len(x_points) if n == 0: raise ValueError("插值节点不能为空") total = 0.0 for i in range(n): term = y_points[i] for j in range(n): if j != i: term *= (x_eval - x_points[j]) / (x_points[i] - x_points[j]) total += term return total这段代码的逻辑是两层循环:外层遍历每个数据点,内层累乘计算对应基函数在当前求值点x_eval处的值。内层循环里分母是x_points[i] - x_points[j],当x_eval恰好等于某个插值节点时,对应项的分母会精确抵消成一,其他项因为分子出现零而整个为0,最终返回该节点的函数值,这是拉格朗日插值最基本的性质。代码里加了个空节点检查,防止传入空数组时报出难以理解的numpy错误。
用的时候注意两个参数语义:x_points是插值节点而非插值区间端点,它决定多项式的次数,n个节点对应n-1次多项式。x_eval可以是单个数值,也可以是numpy数组,函数内部会自动做广播。实测中,对数组求值时这个函数会在每个元素上独立执行两层循环,数据量上千点时速度会明显下降,所以如果只需要在特定几个点求值,不要传一个很长的密集数组,这属于代码效率和精度无关但和体验相关的问题。
3.2 牛顿插值法:差商表与递推实现
牛顿插值和拉格朗日插值数学上等价,但实现思路不同。牛顿插值通过构造差商表来逐步构建多项式,它的优势在于,当新增一个插值节点时,不需要重写所有基函数,只需在原有多项式上加一项。这个性质在实验里体现得最明显的是:题目要求先取前三个点插值看结果,再取前四个点插值看结果,用牛顿插值可以复用之前的计算。
def newton_divided_differences(x_points, y_points): """ 计算差商表,返回对角线上的差商系数数组 """ n = len(x_points) coef = np.array(y_points, dtype=float) for j in range(1, n): for i in range(n - 1, j - 1, -1): coef[i] = (coef[i] - coef[i - 1]) / (x_points[i] - x_points[i - j]) return coef def newton_interp(coef, x_points, x_eval): """ 用差商系数计算牛顿插值多项式在 x_eval 处的值 coef: newton_divided_differences 的返回值 """ n = len(coef) result = coef[0] for i in range(1, n): term = coef[i] for j in range(i): term *= (x_eval - x_points[j]) result += term return result差商表计算是原地更新的,coef数组在每一轮外层循环中被覆盖,最终coef[i]存放的是第i阶差商。注意内层循环从后向前遍历,这保证计算新值时用到的是上一轮的结果而不是本轮已经覆盖的值,顺序反了整个差商表就是错的。牛顿插值的求值过程用到了嵌套乘法,从最高次项开始逐层提取公因子,这样比直接展开多项式运算量更小,也更符合数值分析教材里对秦九韶算法的介绍。
实操中,差商表函数只在构造阶段调用一次,之后对多个x_eval求值时只需要反复调用newton_interp。如果你要插值的点有几百上千个,这个做法的优势非常明显——差商构造是O(n²),而每个点求值是O(n)。拉格朗日插值对每个求值点都要重新计算所有基函数,同样是O(n²)但乘在点的数量上,数据量一大差距就拉开了。
3.3 用matplotlib画出插值效果对比图
实验报告里最直观的内容是一张插值效果图:原始数据点、拉格朗日插值曲线、牛顿插值曲线在同一坐标系中展示。画图代码不长,但有两个细节要注意:插值曲线的横坐标要足够密,不然曲线会显得很生硬;纵坐标范围要留出余量,避免曲线在边缘处超出画布。
import matplotlib.pyplot as plt x_points = np.array([0.0, 1.0, 2.0, 3.0, 4.0]) y_points = np.array([1.0, 2.0, 1.5, 3.0, 2.8]) x_dense = np.linspace(0, 4, 200) fig, ax = plt.subplots(figsize=(8, 5)) ax.plot(x_points, y_points, 'o', label='原始数据点') ax.plot(x_dense, lagrange_interp(x_points, y_points, x_dense), '-', label='拉格朗日插值') coef = newton_divided_differences(x_points, y_points) ax.plot(x_dense, newton_interp(coef, x_points, x_dense), '--', label='牛顿插值') ax.set_xlabel('x') ax.set_ylabel('y') ax.legend() ax.grid(True, linestyle='--', alpha=0.6) plt.show()画图时x_dense用np.linspace(0, 4, 200)生成200个均匀分布的点,具体点数取决于插值多项式的次数和区间跨度,一般200到500足够。如果两条插值曲线完全重合,说明两个算法在精度上一致,这是正常的;如果出现细微差异,优先检查差商表的迭代顺序是否正确。对比图存在的意义是验证两个算法结果一致,顺带检查曲线的形状是否符合直觉。
4. 最小二乘曲线拟合:把公式拟合到数据点上的完整流程
4.1 最小二乘原理与numpy.polyfit最小实现
拟合和插值的核心区别在于:插值要求曲线严格穿过每个数据点,拟合只要求曲线在整体上最接近数据点。最小二乘的思想是找一组参数,让模型计算值与观测值的平方误差之和最小。平方误差比绝对误差更敏感,而且方便求导,所以成为最常用的拟合准则。
import numpy as np x_data = np.array([0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]) y_data = np.array([0.1, 0.8, 1.8, 2.9, 5.1, 7.2, 10.0]) degree = 2 coeffs = np.polyfit(x_data, y_data, degree) p = np.poly1d(coeffs) print("拟合系数:", coeffs) print("拟合多项式:", p)np.polyfit返回的是多项式系数,从最高次到常数项排列,系数顺序和poly1d的输入是对应的。degree参数是最关键的调试项:取1时是线性拟合,取2时是抛物线拟合,取大了会出现过拟合——曲线为了贴近个别数据点而剧烈抖动,尤其是在数据本身不是高阶多项式的情况下。判断次数是否合适的标准很简单:看拟合曲线走势是否符合数据的物理含义,再看残差是否随机分布,而不是呈现系统性偏差。
poly1d是把系数数组包装成可以直接调用的多项式对象,调用p(x_eval)就能批量求值。这里有个小细节:polyfit内部用的是基于SVD的稳定算法,对于次数不超过8的多项式拟合,很少出现数值问题,但超过10次就要警惕矩阵病态,结果可能不可信。所以实验题里如果要求拟合次数特别高,建议检查一下拟合系数是否出现数量级异常的大值,那通常是条件数过大导致的数值性翻车。
4.2 自定义函数拟合:scipy.optimize.curve_fit的使用
实验题里有时候不要求多项式拟合,而是指定一个函数形式,比如指数衰减模型或者带偏移的正弦函数。np.polyfit只支持多项式,这时候要换用scipy的curve_fit,它可以拟合任意你写出的Python函数。核心用法是:先定义一个函数形式,函数的第一个参数是自变量,后面是待拟合参数。
from scipy.optimize import curve_fit def exp_model(x, a, b, c): """指数衰减加常数偏移的拟合模型""" return a * np.exp(-b * x) + c popt, pcov = curve_fit(exp_model, x_data, y_data, p0=[1.0, 1.0, 0.0]) a_opt, b_opt, c_opt = popt print(f"拟合参数: a={a_opt:.4f}, b={b_opt:.4f}, c={c_opt:.4f}") x_smooth = np.linspace(x_data.min(), x_data.max(), 100) y_fit = exp_model(x_smooth, *popt)p0是参数初值,这个参数在很多时候决定了拟合能不能收敛。初值差得太远,算法可能陷入局部极小值,甚至直接报错。给p0的经验法则是:先看数据点的取值范围,大致估计一下参数的量级。比如数据y值在0到10之间,c的初值可以设为数据中点或最小值附近,a的初值设为y的跨度,b的初值根据曲线衰减的快慢估一个正数。如果你发现拟合结果明显不合理,最先该改的是p0而不是模型本身。
curve_fit返回的pcov是参数协方差矩阵,对角线元素开根号是参数的标准差,代表拟合参数的不确定度。实验报告里如果想体现严谨性,可以把参数误差也打印出来。当协方差矩阵里的值大到离谱时,往往说明模型参数之间存在强相关性,或者数据量不足以确定这么多参数,这时候的拟合系数虽然能算出数,但不能当作物理解释使用。这个点算是一个隐藏的知识点,写在报告的分析部分会很加分。
4.3 拟合效果的量化评估:残差、R方与可视化
拟合做完不评估就写报告,等于黑匣子输出直接交差。评估拟合质量最常用的是三个量:残差、均方根误差和R方。残差是每个数据点的观测值减拟合值,R方表示拟合曲线解释了数据方差的百分比,越接近1越好。
residuals = y_data - exp_model(x_data, *popt) ss_res = np.sum(residuals**2) ss_tot = np.sum((y_data - np.mean(y_data))**2) r_squared = 1 - ss_res / ss_tot rmse = np.sqrt(np.mean(residuals**2)) print(f"R方 = {r_squared:.4f}") print(f"均方根误差 RMSE = {rmse:.4f}")注意R方有一个陷阱:当模型形式比较复杂、参数很多时,R方会虚高,甚至接近1,但这不代表模型真的好。一个十次多项式拟合十个数据点,R方必然是1,但那完全没有预测意义。所以评估时要同时看残差图——如果残差随x增大而增大,说明模型形式不对;如果残差呈现波浪形,说明漏掉了周期性分量。这些判断是数值分析实验最有价值的训练,比单纯算对拉格朗日插值结果重要得多。把残差图画出来,和拟合效果图画在一起,报告的分析部分立刻有了深度。
5. 实验四避坑指南:5个最常见的翻车点与排查方法
5.1 拉格朗日插值次数过高导致龙格现象,数据越多结果越离谱
现象:用拉格朗日插值去逼近某些连续函数,插值节点增加到十几个甚至更多,插值曲线在区间两端剧烈震荡,误差反而比少用几个节点时更大。
原因:这是数值分析教材里反复强调的龙格现象。拉格朗日插值多项式在等距节点下对龙格函数、绝对值函数这类目标做高次逼近时,插值余项在端点附近会放大,多项式次数越高震荡越明显。
解决:明确实验四的题目通常只要求4到6个节点的插值,不需要追求高次。如果题目要求对比不同节点数下的误差,把插值点数从3逐个增加到10,画误差曲线,让读者直观看到误差先降后升的过程,这本身就是一份合格的现象分析。如果实际工程中遇到类似需求,应该改用分段插值或样条插值,而不是硬上高次多项式。
5.2 图表的横纵坐标出现方块字,中文标注全部乱码
现象:matplotlib画图时,标题和图例里的中文显示成一个个小方块,英文和数字正常,实验报告截图里一眼就能看出问题。
原因:matplotlib默认字体不包含中文字形,需要显式指定中文字体。Windows系统一般有SimHei或Microsoft YaHei,Linux和macOS的字体名不同,用系统自带字体名逐个尝试。
解决:在画图代码最前面加上字体设置,让matplotlib全局使用中文字体,如下所示:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC'] plt.rcParams['axes.unicode_minus'] = False第二行去掉坐标轴负号的特殊处理,是因为切换了中文字体之后,负号有可能显示成一个奇怪的方框。这个问题属于程序能跑但报告不能看的隐形翻车,查错成本高,提前设置好就能完全避免。
5.3 差商表计算顺序写反,牛顿插值结果对不上拉格朗日
现象:牛顿插值和拉格朗日插值结果不一致,差距可能很小也可能很大,越小规模的数据差距越小,很多同学因此没发现。
原因:newton_divided_differences函数里内层循环的方向错了。差商表必须从后向前原地更新,如果写成从前向后,计算新的差商时用的是已经被本轮覆盖过的值,导致差商全部错误。数据点少时误差不明显,数据点多时完全对不上。
解决:内层循环固定使用for i in range(n - 1, j - 1, -1),从后往前遍历。排查思路也很简单:用三个点的手工差商计算结果和Python输出对比,如果差商对不上,问题就在循环方向。这类错误的特点是代码能跑、结果贴近但不对,最容易在实验报告的误差分析部分暴露,靠直觉很难发现。
5.4 拟合结果出现巨大系数,多项式次数太高导致矩阵病态
现象:polyfit指定degree=9或更高时,拟合系数出现10的6次方以上的大数,拟合曲线在数据点之间剧烈波动,部分区间甚至超出数据范围好几倍。
原因:高次多项式拟合的等效设计矩阵条件数随次数指数增长,数据点x跨度大时尤其严重。矩阵接近奇异,SVD分解得到的解被微小舍入误差放大,系数自然不稳定。
解决:首先判断题目是否真的要求高次拟合。如果要求做不同次数拟合的对比,把次数范围控制在1到6之间,重点观察拟合误差变化趋势。如果必须拟合高次多项式,考虑先对x数据做归一化,把数据平移到零均值并缩放到单位方差,再拟合,最后把系数还原回原始坐标。归一化这步在实验报告里是加分项,能体现你对数值稳定性的理解。
5.5 求值点落在插值节点范围之外,结果完全失控
现象:用插值函数计算区间外的点,计算结果非常大,和真实函数值差出几个数量级,有的同学以为是代码精度不够。
原因:插值多项式只在节点覆盖的区间内有可靠逼近能力。区间外没有任何数据点约束多项式形状,高次项起主导作用,结果必然外溢。这是插值方法的固有属性,不是bug。
解决:求值前先检查x_eval是否在x_points的范围内。如果题目故意要求计算外推值,那么在报告里要明确记录这个现象,用误差对比说明外推不可靠,这属于正面的实验结果。如果在做拟合预测,用polyfit拟合出的多项式做短距离预测是可行的,做远距离预测同样会失效。边界意识是从数值分析实验里能带走的最重要工程习惯。
6. 用一份可视化脚本把实验结果整理成答辩图表
实验四答辩或提交报告时,图表质量往往直接影响印象分。同样是解决同一个问题,一个用默认绘图风格、图例位置遮挡曲线、坐标轴没标单位,和另一个图面干净、曲线粗细分明、每个子图都有标题和注释,给阅卷人的感受完全不同。整理图表这一步建议独立写一个脚本,不要和算法代码混在一起,输出为高分辨率图片再插入报告,而不是直接截图。
import matplotlib.pyplot as plt import numpy as np fig, axes = plt.subplots(1, 3, figsize=(14, 4)) # 第一个子图:插值结果对比 axes[0].plot(x_points, y_points, 'o', label='数据点') axes[0].plot(x_dense, lagrange_interp(x_points, y_points, x_dense), label='拉格朗日') coef = newton_divided_differences(x_points, y_points) axes[0].plot(x_dense, newton_interp(coef, x_points, x_dense), '--', label='牛顿') axes[0].set_title('插值结果对比') axes[0].legend() # 第二个子图:拟合效果 axes[1].plot(x_data, y_data, 'o', label='观测数据') axes[1].plot(x_smooth, y_fit, '-', label='拟合曲线') axes[1].set_title('最小二乘拟合') # 第三个子图:残差分布 axes[2].plot(x_data, residuals, 'o-', label='残差') axes[2].axhline(0, color='gray', linestyle='--') axes[2].set_title('拟合残差分布') axes[2].legend() fig.tight_layout() plt.savefig('experiment4_summary.png', dpi=200, bbox_inches='tight') plt.show()一处细节是plt.savefig里的参数:dpi至少200才够打印清晰,bbox_inches='tight'可以把空白边缘裁掉,避免图片四周留白过多。如果你是准备提交电子版报告,300dpi更好,图片文件大一点没关系,打印效果匀称。另一个细节是每个子图的标题要能自解释,也就是说读者不看正文只看图,就知道这张图在表达什么,这是实验报告图表规范的基本要求,多数同学没意识到,所以认真做反而会留下印象。
我自己的习惯是,在画图脚本开头放一段注释,记录这份图表对应的题目要求、数据文件、生成日期,方便复查时快速定位。数值分析实验四做完,回头看最大的收获往往不是那几个算法本身,而是理解了“数据点给出的信息量是有限的,任何试图从有限数据外推的做法都必须说明假设和边界”。这个意识在后面的课程和工程项目里反复被验证。希望这篇笔记能帮你把实验四从头到尾走通,拿到一个能进报告、能讲清楚、也能经得起提问的结果。
本文还有配套的精品资源,点击获取