密西根大学-EECS492-人工智能编程笔记-全-

密西根大学 EECS492 人工智能编程笔记(全)

001:科学计算与编程导论 🚀

在本节课中,我们将要学习现代科学计算的基础,并了解本课程的整体范围与目标。课程将使用MATLAB和Python两种语言,旨在帮助初学者掌握编程逻辑,并最终应用于人工智能领域。


我是T. Soja Kim,密西西比州立大学的数学教授。让我们从课程的范围开始讲起。

我们使用的教材名为《编程基础与人工智能》,涉及MATLAB和Python。

如果你开始学习编程,计算机语言必须简单。如果它很复杂,你将很难专注于数学逻辑。编程与简单的埃及语言MATLAB一起学习。你学习编程,专注于数学逻辑,然后学习Python,以成为一名更好的程序员。

电子版课程并非完全版,我后来添加了一些主题。然而,尽管方式如此,它将免费提供。

讲座将以此种形式进行。目标受众是本科生,然而,有天赋的高中生也能够跟上课程。没有编程经验的人将能理解大部分讲座内容。

课程大纲如下。

我希望你们理解数学基础,特别是微积分和线性代数。

我希望你们理解使用MATLAB和Python的编程,以及人工智能。我们会学习一些机器学习的基础。最后,讲座将涉及一个名为Cyon的机器学习软件,以便你们至少掌握一种机器学习软件。

在这里,编程是为了理解和分析问题,并将数学术语转换为计算机程序。

并且,或者最终验证代码。你必须获得洞察力,以便随着洞察力的获得,你可以扩展你的知识和编程能力。

通过本课程,你将学习编程。好的,介绍到此结束。从下一个视频片段开始,我们将开始编程技术的细节。

谢谢。


本节课中我们一起学习了本课程的目标、使用的工具(MATLAB和Python)以及核心学习方法:从简单的语言入手专注于数学逻辑,再过渡到更强大的工具,最终应用于人工智能和机器学习。编程的本质在于理解和分析问题,并将其转化为可执行的程序以获得洞察。下一讲我们将开始具体的编程技术学习。

002:编程基础

在本章中,我们将学习编程的基本概念,了解程序的含义,并探讨如何将数学术语转化为计算机程序。

1.1:什么是编程或编码?💻

计算机编程是构建可执行计算机程序以完成特定计算任务的过程。编程涉及多个方面,包括数学或物理分析、计算算法的生成、对效率和准确性的分析,以及最终实现算法,即编码。

编程的目的是找到一系列指令,因此编程过程通常需要多个不同领域的专业知识,例如应用领域的知识、专门的算法和形式语言。

以下是理解编程概念的两个例子。

示例一:整数求和
假设我们需要计算从2到5的整数之和。为了得到答案,我们可以从第一个数开始,依次加上3、4和5。通过这种逐步相加的方式,可以得到答案是14。在计算机实现中,就是以这种方式进行的。因此,编程就像是将你的有效思考过程在计算机中实现。

示例二:计算平方根
如果你使用计算器计算5的平方根,按下5和平方根键后,会立即得到答案。问题是,计算器是如何得到答案的?答案并非预先存储,而是通过计算得到的。

具体算法如下:输入是 Q = 5。为了求解,我们初始化一个变量 P,然后通过迭代公式 P = (P + Q/P) / 2 来更新 P 的值。这个迭代过程会重复执行多次。

例如,在MATLAB中,我们可以将 P 初始化为1,然后进行8次迭代。每次迭代后,我们打印出迭代次数和更新后的 P 值。可以看到,P 的值逐渐趋近于一个固定值。大约在6次迭代后,算法就收敛到了足够的精度,得到了平方根的近似解。这就是你的计算器或计算机内部发生的事情。

这个算法可以解释为:我们试图找到一个数 P,使得 P² = Q。我们首先猜测一个 P 值,计算其平方与 Q 的差值,然后用一个缩放因子(这里是 2P)来调整这个差值,从而更新 P 值。这里的关键问题是,我们如何知道 2P 是一个合适的缩放因子?答案来自于数学分析。为了得到一个好的算法,你需要理解其背后的数学原理。

因此,编程由数学分析、算法设计、计算机实现(即编码)以及对算法准确性和效率的验证组成。没有验证,算法可能存在错误,因此必须彻底验证你的算法和代码。

1.2:关于MATLAB与实现🔧

在完成数学分析和算法设计后,下一步是实现算法,这被称为编码。为了实现,你可以使用一种计算机语言,例如MATLAB、Python、C、C++或Java等。

在本课程中,你将学习使用简单的语言(如MATLAB和Python)进行编程的技术。我们选择简单语言的原因是为了让你专注于数学逻辑和算法设计。如果计算机语言过于复杂,这种复杂性会让你难以专注于数学逻辑。因此,我们将从MATLAB开始,让你专注于算法。在学期中期,我们将转向Python,因为在现代人工智能和机器学习领域,Python是最流行的语言,你必须学习它。

1.3:编码与编程的区分📝

此时你可能会问:什么是编码?它与编程有什么关系?编码和编程这两个术语经常互换使用。然而,在软件开发行业中,编码指的是为应用程序编写代码,而编程是一个更广泛的术语。编码基本上是将一种语言转换为另一种语言创建代码的过程,而编程则是寻找问题的解决方案并确定应如何解决。编程通常处理应用程序的整体图景。因此,在本课程中,你将学习的是编程。


总结
在本节课中,我们一起学习了编程的基础概念。我们了解到编程是一个涉及数学分析、算法设计、代码实现和验证的完整过程。我们通过求和与计算平方根的例子,看到了如何将思路转化为计算机可以执行的步骤。我们还区分了编码与编程的细微差别,并介绍了本课程将使用的MATLAB和Python语言,强调了从简单语言入手以聚焦核心逻辑的重要性。

003:函数的泛化与可复用性

在本节课中,我们将要学习如何编写具有良好泛化能力和可复用性的函数。我们将通过一个具体的例子,从一段简单的代码开始,逐步将其改进为一个可复用的函数。

概述

一个好的程序员会编写出易于修改和复用的代码。为了理解可复用性,让我们考虑以下简单示例:计算从1到10的连续整数的平方和。这个问题要求计算 1² + 2² + ... + 10² 的值。

用求和符号表示,可以写作:

S = Σ (i²), 其中 i 从 1 到 10

从简单代码到可复用函数

初始的非复用代码

我们可以编写一段MATLAB代码来解决这个问题,如下所示:

s = 0;
for i = 1:10
    s = s + i^2;
end

这段代码首先将变量 s 初始化为0,然后通过一个 for 循环,将1到10的平方依次累加到 s 中。执行后,s 的值将为385。这段代码是编程中最常见的构建块之一——循环的简单形式。

然而,这段代码的复用性很差。如果你需要计算其他范围的平方和,例如从10到20,或者从1到100,你必须重新阅读并修改代码中的数字(110)以适应新任务。

创建可复用的函数

为了使代码可复用,我们需要实现一个可以接受可变输入的程序,并将其保存为函数的形式。让我们尝试将上述程序通用化。

现在,我们创建一个名为 ssum.m 的M文件,其中包含一个函数。函数定义如下:

function sq_sum = ssum(n)
% SSUM 计算从1到n的整数平方和。
%   语法:sq_sum = ssum(n)
%   输入:n - 求和的上限(整数)
%   输出:sq_sum - 从1到n的平方和

sq_sum = 0;
for i = 1:n
    sq_sum = sq_sum + i^2;
end
end

在这个函数中:

  • 第一行 function sq_sum = ssum(n) 声明了函数。ssum 是函数名,n 是输入参数,sq_sum 是输出变量。
  • 第2至5行是帮助文档。在MATLAB或Octave中,使用 help ssum 命令可以查看这些说明。
  • 第7至10行是算法的主体部分,其逻辑与最初的代码完全相同,但循环上限 n 现在是一个变量。

通过将代码包装在函数定义中并保存为独立的M文件,我们就创建了一个可复用的函数。

使用函数

在命令窗口中,我们可以像使用内置函数一样调用 ssum

>> result = ssum(10)
result = 385

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/57849339d0242f7be48b634abbebaac3_22.png)

>> result = ssum(100)
result = 338350

现在,要计算不同范围的平方和,我们只需在调用函数时改变输入参数即可,无需修改函数内部的代码。这大大提高了代码的复用性。

总结

本节课中,我们一起学习了如何提升代码的泛化与可复用性。我们从一个解决特定问题(计算1到10的平方和)的硬编码脚本出发,分析了其复用性不足的问题。接着,我们通过引入输入参数,将核心算法封装到一个独立的函数中,从而创建了一个可以处理不同上限 n 的通用解决方案。关键步骤包括:定义函数头、编写帮助文档、以及将核心逻辑置于函数体内。最终,我们得到了一个可以通过简单改变输入值来重复使用的函数,这是编写高质量、易维护代码的重要基础。

004:成为一名优秀程序员 💻

在本节课中,我们将探讨如何成为一名优秀的程序员。我们将从编程的核心概念入手,分析编程过程的各个关键方面,并提供一些实用的编程技巧。

概述

编程,或称计算机编程,是构建一个可执行程序以解决计算任务的过程。一个任务可能包含许多子任务,每个子任务都可以实现为一个函数。在程序中,某些函数可能会被多次或重复使用。

编程的关键方面

在开始编码之前,你应该考虑编程的以下方面。

任务模块化

任务模块化是关于将给定任务进行分解。给定的计算任务可以被划分为几个较小的子任务,每个子任务称为一个模块。每个模块都应该是易于管理、便于实现,并且在数学分析和计算机实现上都高效的。这意味着每个模块都易于处理和实现。任务模块化的主要核心是构建程序的骨架。任务模块化非常重要,你必须将给定任务分解为小的子任务。

算法开发

现在,你必须考虑算法的开发。对于每个模块,你需要开发一个计算算法,并将该算法保存为函数。实际上,为了实现一个程序,你必须选择一种计算机语言。你可以选择一种计算机语言,以便能够高效且方便地实现。有时,你可能会使用不止一种计算机语言来实现子任务。通过这样做,你可以最大化最终程序的性能,同时最小化你的人力投入。如果你将一个给定任务分成几个子任务,那么对于某些子任务,可能已经有现成的程序或函数可用,你就可以使用它们。但程序可能是用其他语言编写的。例如,你可能想使用C语言,但对于某些任务,与其编写C代码,不如下载现成的Fortran代码并使用它。为了最小化人力投入并最大化性能,你可能会在这里使用不止一种语言。顺便说一下,Fortran是科学计算中最快的语言。Fortran比C语言快大约20%。这里你会使用MATLAB,但在许多情况下,MATLAB比C语言慢大约两倍。即使是脚本语言,MATLAB现在也非常快。在没有编译的情况下,我们使用MATLAB脚本,现在它比C语言慢大约两倍,这真的很惊人。MATLAB的速度非常好。如果我们使用Python,那么它可能比C语言慢50倍甚至100倍。因此,Python的内部和核心部分是用C或Fortran实现的,以便我们可以加速性能。在许多软件包中,核心部分通常使用非常高效的语言实现。所以最终,即使你以后使用Python,你也不仅仅在使用Python,同时也在使用一些非常高效的语言。

验证与调试

一旦你实现了一个程序代码,现在你必须验证其正确性和有效性。这种在计算机程序中查找和解决缺陷或问题的过程称为调试。有时,验证和调试所花费的时间比实现本身要长得多。对于一个小的程序,你可能在一天内完成实现,但有时调试可能需要超过一周,甚至超过一个月。因此,你必须从相当清晰的数学逻辑开始。如果你的思维不具逻辑性,那么你很容易犯错误,并且因为你的逻辑不清晰,很难找到错误所在的位置。所以你必须进行逻辑思考,有了清晰的逻辑,你就能更有效地编写程序。

编程技巧

以下是一些编程技巧,它们相当简单。

如果你想添加函数,那么尝试逐个添加函数。构建程序不是一个简单的问题,而是一个困难的项目,特别是当程序需要从零开始构建时。一旦你从零开始,一个有效的编程策略是在模块化分解后,逐个添加函数。每个模块都可以实现为一个函数,所以逐步添加函数,并在每次添加后检查程序是否正确。在这种情况下,你将专注于刚刚添加的最后一个函数,检查该函数是否正确,然后转向下一个函数。通过这种方式,程序员应该在整个实现期间保持程序处于工作状态。这样,你可以最小化错误,从而最小化调试时间。

此外,使用函数的修改版本。有时你必须修改和使用所有先前使用过的函数。当遇到类似情况时,你可能需要通过复制所有函数并适当重命名来开始工作,以修改函数,而不是在原始函数中添加或删除代码行。如果你尝试保留原始函数,那么调试会相当容易。这样,你也能更容易地始终保持程序处于工作状态。原因是,因为你有了原始版本作为基础,一旦新版本不工作或产生一些意外的结果,你就可以检查你的逻辑和编程,从而通过使用旧版本更容易地使其正确。所以不要试图替换原始函数或向其中添加代码行,只需将其复制并适当重命名函数即可。例如,如果你尝试复制一个函数,比如我们之前考虑的 S_sum,将其复制为 S_sum2,然后尝试调整 S_sum2 并保留原始的 S_sum 函数。

成功编程的考虑因素

为了成功编程,程序员在开始实现之前可以考虑以下几点。

首先,你必须理解问题。然后,你必须考虑可能涉及哪些输入、哪些操作以及哪些输出。同时,你必须为每个模块组织所需的算法。有时对于某些模块,你可以使用现成的函数,但有时你必须重新实现。

其次,为了使程序高效且正确地工作,你必须推导数学方法和数学判断,以便更有效地设计算法。此外,你必须考虑程序结构。实际上,你必须将操作和函数以正确的顺序放置在正确的位置,所以你必须考虑程序结构。

完成实现后,你必须验证你的程序。因此,你必须考虑如何验证代码以确保其正确性。在开始时,代码至少必须是正确的。之后,当你处理真实数据或海量数据时,如果效率不足,你可以修改你的程序以提高效率。但在开始时,你的代码必须是正确的。

示例:排序算法

让我们考虑这个例子。让我们编写一个程序,将一组数字按从小到大排序。这是一个计算任务。在编码之前,你必须考虑以下几点。

核心是什么?核心是获得一个排序算法。排序背后的原理不是比较所有可能的数对,而是比较相邻的数对,让较小的数向上移动。这是因为我们要从小到大排序。所以方法必须是:遍历这个数组,对每两个相邻的数字进行比较,然后较小的数会移到前面。

现在,如果你写下一个代码,那么你必须问自己:如何验证程序工作正确?这里,你可以使用一个随机生成的数字序列进行测试。

当我们实现代码时,你还必须考虑参数:输入和输出参数可能是什么。对于输入,它是一个数字数组,输出必须是排序后的数组。

我们可以这样实现一个排序函数。第一行有一个函数名,你必须指定清晰的函数定义,即函数名、输入和输出。现在我们有这个文档部分。当你使用 help 命令时,你会看到这部分。一旦输入数组 R 被给定,我们将其复制到 S。现在我们检查长度。然后,从最后一个元素开始,到第二个元素,我们比较两个连续的值。如果前一个值大于后一个值,我们就交换它们的位置。在MATLAB中,你必须这样做:将一个值保存到临时变量中,然后将后一个值赋给前一个位置,再将临时变量的值赋给后一个位置。通过这种方式,我们可以交换。经过这行代码后,后一个值会变得更大。对于每个 J,数组的前面部分试图获取最大的值,所以最大的值会被保存到位置 J。我们重复 Jn 递减到 2 的过程,最终最大的值会位于最右边的位置 n,最小的值会在开头。

这就是实现。现在,我们将尝试验证我们实现的函数。这里我们选择 a1。我们尝试生成一个包含10个值的随机序列,每个值在1到100之间。所以 a1 是一个1x10的向量,n 是10。然后,每个值在1到100之间。a1 是这样生成的,然后通过 my_sort 函数。现在,输出 R 是在这个范围内随机生成的。在调用 my_sort 之后,它从小到大排序。对于这个序列,它工作得很好。但你可能需要多次运行这个 my_sort 算法,通过使用 sort2array.m 脚本,你可能需要多次调用这个 sort2array.m 来确保 my_sort 工作正确。一旦它正确工作,但对于其他一些情况,它可能不行,所以你必须有时运行多次以确保你的代码工作正确。

总结

在本节课中,我们一起学习了编程的各个方面。编程包括分析、设计、编码和验证。它需要在编码之上进行创造性思维和逻辑推理。所以编码仅仅是构建代码,而编程则需要更多。实际上,你必须进行非常创造性的思考,同时也需要一些清晰的推理,因此数学逻辑是主要的事情。最好从一种简单的计算机语言开始,以便更专注于分析、设计和验证。如果你的语言很复杂,那么你将在调试上花费更多时间,导致你的分析和设计不够出色。因此,本课程将在开始时使用MATLAB。

本节内容到此结束,谢谢。

005:第5讲 - Matlab/Octave入门 🚀

在本节课中,我们将学习Matlab和Octave的基础知识。Matlab是一种强大的数值计算环境和编程语言,而Octave是其免费的开源替代品。我们将从最基本的向量和矩阵操作开始,逐步介绍如何构建和操作数据,以及如何进行数据可视化。


1.2 Matlab:一种强大的计算机语言 🧠

Matlab是“矩阵实验室”的缩写。它是一种多范式的数值计算环境和专有编程语言,由MathWorks公司开发。Matlab不是免费的,但它非常灵活,可以进行数值计算和符号计算。它易于学习和使用。

在Matlab中,你可以进行矩阵操作、函数和数据绘图、算法实现、创建用户界面,并可以调用其他语言(如C、C++、C#、Java、Fortran和Python)。Matlab尤其擅长矩阵操作和符号计算,可以说是最方便学习和使用的计算机语言之一。

学习任何编程语言,都需要掌握四个基本组成部分:

  • 循环:处理重复操作。
  • 条件语句:处理不同情况。
  • 输入/输出:与用户或文件进行数据交互。
  • 模块化与函数:将代码封装成函数,以提高代码的可重用性和实现效率。

Matlab或Octave简介 🔄

Octave是Matlab的一个免费替代品。在大多数情况下,Matlab程序可以在Octave中运行而无需修改。虽然两者并非完全相同,但对于初学者级别的任务,它们基本兼容。


向量和矩阵 📊

最基本操作是输入向量和矩阵。在Matlab的命令窗口中,你会看到提示符 >>。在Octave中,提示符是 octave:1>

定义矩阵的方法如下:

A = [1, 2; 3, 4]

这将创建一个2x2的矩阵A。行与行之间用分号 ; 或换行分隔,行内的元素用逗号 , 或空格分隔。

以下是更多示例:

  • v = [1; 2; 3] 创建一个列向量(垂直向量)。
  • w = [5, 6, 7] 创建一个行向量(水平向量)。
  • M = [2, 1; 1, 2] 创建一个2x2矩阵。

逗号和分号也可以用于在同一行组合多个命令。如果在命令末尾使用分号,Matlab会执行该命令但不打印输出

示例:

p = [2; -3; 1];
q = [2; 0; -3];
d = p' * q; % 计算点积并静默保存到d
p_q = p + q % 计算p+q并显示结果

逐步构建矩阵 🧱

除了直接输入,还可以从其行或列逐步构建矩阵。

示例:

c1 = [1; 2];
c2 = [3; 4];
M = [c1, c2]; % 组合成2x2矩阵

c3 = [5; 6];
M = [M, c3]; % 在右侧添加新列,扩展为2x3矩阵

r3 = [7, 8, 9];
M = [M; r3]; % 在底部添加新行,扩展为3x3矩阵

通过这种方式,可以灵活地构建和扩展矩阵。


访问矩阵元素与运算 🔢

在Matlab中,使用星号 * 进行标量乘法和矩阵向量乘法。

访问矩阵 M 中第 i 行第 j 列的元素,使用 M(i, j)

要一次检索多个元素,可以指定行和列的列表。例如:

  • M(2, 3) 获取第2行第3列的元素。
  • M([2, 4], :) 获取第2行和第4行的所有列。冒号 : 表示“所有”。
  • M(:, 2) 获取第2列的所有行。
  • M(2:4, :) 获取第2到第4行的所有列。2:4 等同于 [2,3,4]

示例:

M = [1, 2, 5, 6; 3, 4, 7, 8; 9, 10, 11, 12];
element = M(2, 3) % 返回 7
rows = M([2, 4], :) % 返回第2行和第4行(如果存在)
col = M(:, 2) % 返回 [2; 4; 10]

使用 eye(n) 命令可以创建 n x n 的单位矩阵。矩阵乘法同样使用 * 符号。可以使用 inv(A) 计算矩阵 A 的逆。

示例:

A = [1, 2; 3, 4];
B = [5, 6; 7, 8];
C = A * B; % 矩阵乘法

I = eye(3); % 3x3单位矩阵
D = rand(3); % 3x3随机矩阵
invD = inv(D); % D的逆矩阵
check = D * invD; % 应近似等于单位矩阵


使用Matlab绘图 📈

Matlab中最常用的绘图命令是 plot,用于创建二维线图。基本语法是 plot(X1, Y1, LineSpec1, X2, Y2, LineSpec2, ...),可以在一张图上绘制多条曲线。

以下是一个绘图脚本示例 (figure_plot.m):

close all; % 关闭所有图形窗口
clear; clc; % 清空工作区和命令窗口

% 生成第一组数据点(较稀疏)
x1 = linspace(0, 2*pi, 11); % 在0到2π间生成11个点
y1 = cos(x1);

% 生成第二组数据点(较密集)
x2 = linspace(0, 2*pi, 50);
y2 = sin(x2);

% 绘图
figure; % 新建图形窗口
plot(x1, y1, '-ro', 'LineWidth', 2); % 红色实线,圆圈标记,线宽2
hold on; % 保持当前图形,以便添加新曲线
plot(x2, y2, '-b', 'LineWidth', 2); % 蓝色实线,线宽2

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/0332c045855b9fbe7a7eccefdc5a13cb_25.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/0332c045855b9fbe7a7eccefdc5a13cb_27.png)

% 添加图例和标签
legend('cos(x)', 'sin(x)');
xlabel('x');
ylabel('y');
title('Cosine and Sine Functions');
grid on; % 显示网格

运行此脚本(在编辑器点击运行或按F5)将生成一个显示余弦和正弦曲线的图形。

对于更高级的可视化,Matlab还提供:

  • contour:绘制等高线图。
  • surfmesh:绘制三维曲面图。
    当拥有函数表达式而非离散数据点时,可以使用 fplot (二维) 和 fsurf/fmesh (三维) 直接绘制函数图形。

获取帮助 ℹ️

Matlab功能强大且文档齐全。要了解任何命令的用法,可以使用 help 命令。

例如,在命令窗口输入:

help plot

这将显示 plot 函数的详细帮助文档,包括语法、选项和示例,帮助你根据需要定制图形。


本节课中,我们一起学习了Matlab/Octave的基础知识。我们介绍了如何创建和操作向量与矩阵,包括逐步构建和访问元素。我们探索了基本的数学运算,如矩阵乘法和求逆。最后,我们掌握了使用 plot 命令进行数据可视化的方法,并了解了如何通过 help 命令获取帮助。这些是使用Matlab/Octave进行科学计算和数据分析的核心技能。

006:重复迭代循环(第二部分) 🚀

在本节课程中,我们将深入学习编程中的核心概念——循环。循环是执行重复任务的基础,对于科学计算和数据处理至关重要。我们将探讨循环的不同类型、控制语句以及如何在MATLAB中应用它们。

循环的基本概念 🔄

在科学计算中,重复是最常见的操作之一。每次重复的过程称为一次迭代。迭代是指重复一个过程以生成一系列结果或得到最终数值的行为。因此,迭代必须从初始化开始,并逐步执行以获取结果。

MATLAB中的循环类型

循环是编程中非常基础的构建块。MATLAB提供了几种类型的循环来处理循环需求,包括 while 循环、for 循环和嵌套循环。

While 循环

while 循环的格式如下:while 一个表达式,执行语句,以 end 结束。一旦表达式为真,语句就会被执行。

让我们看一个例子。假设 a = 10b = 15。在打印出 ab 的值后,给出 while 循环:如果 a 小于等于 b,则打印出 a 的值,然后 a 增加1。每次迭代,a 从10开始增加,变成11、12、13,直到15。当 a 变成16时,条件不再为真,循环停止。因此,这个循环会打印出从10到15的值。

For 循环

for 循环针对列表中的每个值执行。对于列表中的每个成员(现在成为索引),程序语句将被执行。值可以是任何数字列表。例如,你可以设置初始值和结束值,如 1:4,这将生成从1到4的列表。

我们来看一个类似环境的例子。a = 10b = 15。在打印出 ab 的值后,for 循环如下:iab。所以 a 是10,b 是15,因此 ab 的列表与数字10、11、12、13、14、15相同。i 将从这个列表中选择。开始时,i 是10,然后是11、12,依此类推。对于每个 i,值被打印出来。所以你可以看到 i 从10到15。

嵌套循环

循环可以组织成另一个循环在其中一个循环内部,这就是嵌套循环。我们有一个循环,内部可以再放另一个循环。这种循环称为嵌套 for 循环。你也可以创建嵌套的 while 循环。此外,你可以结合 for 循环和 while 循环来创建嵌套循环。你还可以嵌套超过两层,创建更深的循环。

循环控制语句

Break 语句

一个常用的控制语句是 break。一旦循环遇到 break,循环就会被中断。

让我们看这个例子。我们修改了示例1.15的代码,加入了一个 break 语句。我们添加了另一个变量 c = 12.5。首先,我们打印出 abc 的值,然后是这个 while 循环。我们添加了这一行:一旦 a 大于 cc 是12.5),例如 a 是13,那么 break 语句将被执行,while 循环停止。所以这里的结果是,当 a 是10时,条件不成立,因为 c 是12.5,所以不执行。一旦 a 变成13,条件成立,break 被执行,循环停止。

Continue 语句

有时你想中断循环,但有时你想忽略循环中的某些部分并继续执行。这时你可以使用 continue 语句。continue 只应用于调用它的循环部分。在嵌套循环中,continue 仅跳过当前循环体中的剩余语句,使程序可以继续运行。

现在我们修改示例1.16的代码。这是一个 for 循环,我们添加了 continue 语句。a = 10b = 15,与示例1.16中的代码相同。然后我们添加了这一行:如果 mod(i, 2) 的输出是1(即 i 除以2的余数),则执行 continue。所以对于奇数 i,余数为1,条件为真,continue 被执行,跳过打印部分。只有对于偶数 i,条件不成立,才会执行打印。因此,只有偶数 i 的结果会被输出。

mod(i, 2) 返回 i 除以2的余数,结果要么是0,要么是1。如果是0,则是偶数;如果是1,则等同于真,continue 被执行。一般来说,mod(a, m) 返回 a 除以 m 后的余数,其中 a 称为被除数,m 是除数。这个 mod 函数通常被称为模运算。

匿名函数

在MATLAB中,我们可以定义一个匿名函数。这是一个不存储在程序文件(M文件)中的函数。我们定义一个匿名函数 f@(x) 表示 x 是变量,后面是函数体。我们尝试打印 f(1),所以没有分号。如果你执行这个程序,f(1) 会输出。X 被定义为从1到6的整数。通过 f(X),函数 f 在这些点上求值并保存结果。因为有分号,这些值不会显示。另外,对于这个函数 f,我们尝试求定积分。如果你熟悉微积分积分,那么这是函数从1到3的定积分,输出将保存到 q。这里没有分号,所以你会看到输出。让我们尝试运行这个程序。

在目录中,我们有一个匿名函数,执行相同的程序。在顶部,close allclear all 用于关闭所有图形并清除旧变量。format long 意味着在输出中,如果不是整数值,则以长格式打印。我们可以运行它,这与键盘上的F5相同。然后它被执行,我们得到输出:匿名函数被执行,f(1) 是-2,f(X) 的6个值,最后一个是 f 从1到3的定积分。输出完全相同。

开源替代方案

最后,在本节中,我们将考虑MATLAB的开源替代方案。Octave 是MATLAB最著名的替代品。实际上,Octave 和 MATLAB 并不完全相同。然而,大多数程序可以在MATLAB和Octave中无需修改地运行。有时需要少量修改,但你可以轻松理解需要更改的地方。因此,通过Octave,你可以运行MATLAB程序。

在Python中,NumPy 是科学计算的主要包。它组织得非常好,可以处理通用数组、复数矩阵变换和线性流体变换。此外,你可以用它来调用其他语言,如C、C++和Fortran。事实上,在计算语言中,Fortran 是最快的。NumPy 的组织方式模仿了MATLAB,所以一旦你理解了MATLAB,以后可以非常容易地使用NumPy。我们将在第7章考虑Python基础。

总结 🎯

本节课我们一起学习了编程中的核心循环结构。我们介绍了 while 循环和 for 循环的基本语法与执行逻辑,探讨了如何通过嵌套循环处理更复杂的问题。同时,我们学习了 breakcontinue 这两个重要的循环控制语句,它们能帮助我们更灵活地控制程序流程。此外,我们还了解了MATLAB中匿名函数的定义与使用,并简要介绍了MATLAB的开源替代方案Octave和Python的NumPy库。掌握这些概念是进行高效科学计算和数据处理的基础。

007:第7讲 - 闭合曲线定义区域的面积估计 📐

在本节课中,我们将学习如何通过一系列定义闭合边界的点来估计一个区域的面积。这是一个在医学图像分析(如识别癌细胞区域)等领域非常实用的技术。我们将从简单的几何形状推导出通用公式,并通过编程示例来验证其准确性。


章节 2.1:闭合曲线定义区域的面积估计

在现实中,一个区域通常由一系列有序的点来界定。假设我们有 n+1 个点:(x0, y0), (x1, y1), ..., (xn, yn),其中最后一个点 (xn, yn) 与第一个点 (x0, y0) 相同。这些点依次连接,形成一条闭合的折线,内部围成一个区域。我们的目标是计算这个区域的面积。

上一节我们介绍了问题的背景,本节中我们来看看如何推导出计算面积的公式。

公式推导:从矩形和三角形开始

为了推导面积的计算公式,让我们从两个简单的形状开始:矩形和三角形。

矩形面积
考虑一个矩形,其左下角为 (a, c),右上角为 (b, d)。我们知道其面积为:
面积 = (b - a) * (d - c)

这个结果可以重新组织为一种特殊的形式。如果我们沿着边界逆时针行走,并考虑每条线段中点的x坐标乘以该线段在y方向上的变化量,并对所有线段求和,也能得到相同的结果。这暗示了一个通用公式的可能性。

三角形面积
对于一个顶点为 (a, c), (b, c), (b, d) 的直角三角形,其面积为:
面积 = 0.5 * (b - a) * (d - c)

同样,使用“每条线段中点的x坐标乘以y方向变化量”的方法进行计算,经过代数整理,结果与已知面积公式一致。水平线段(y值无变化)对该求和没有贡献。

通过以上验证,我们猜测以下公式对于由折线定义的任何区域都成立。

通用面积公式

现在,我们可以总结出通用公式。

设区域 R 由一系列点 (x0, y0), (x1, y1), ..., (xn, yn) 定义,其中 (xn, yn) = (x0, y0),形成一条闭合折线。共有 n 条线段,第 i 条线段连接点 (x_{i-1}, y_{i-1})(xi, yi)

那么,区域 R 的面积 A 可以通过以下公式计算:

A = 0.5 * Σ_{i=1}^{n} [ (x_{i-1} + x_i) * (y_i - y_{i-1}) ]

公式解释:

  • (x_{i-1} + x_i) / 2 是第 i 条线段中点的x坐标(公式中提取了系数0.5)。
  • (y_i - y_{i-1}) 是该线段在y方向上的变化量。
  • 求和 Σ 对所有线段进行。
  • 点序列必须按逆时针顺序排列,以保证面积为正。如果是顺时针顺序,结果将为负值,取其绝对值即可。

这个公式是格林定理的一个直接应用(面积 = 1/2 ∮(x dy - y dx)),当边界为分段线性时,该公式是精确的。

复杂形状验证

为了确保公式的有效性,我们将其应用到一个更复杂的L形区域上。

以下是计算步骤:

  1. 列出按逆时针顺序排列的边界点。
  2. 对每条线段应用公式中的项 (x_{i-1} + x_i) * (y_i - y_{i-1})
  3. 将所有项相加,最后乘以0.5。

通过计算,得到的结果与通过几何分割法(矩形面积相减)计算出的面积完全一致,从而验证了公式对于不规则多边形同样有效。


编程示例:估计圆的面积 🌀

现在,我们将通过一个编程实例来应用这个公式,并观察当使用更多点来近似曲线边界时,面积估计值如何逼近真实值。

我们的目标是估计单位圆(半径为1)的面积,其真实值为 π。我们用正多边形来近似圆的边界。

数据生成与面积计算

以下是实现该过程的核心步骤:

  1. 生成边界点: 对于给定的分段数 n,在 0 范围内均匀生成 n+1 个角度 theta。点的坐标为:
    (xi, yi) = (cos(theta_i), sin(theta_i))
    这形成了一个近似圆的正 n 边形。

  2. 应用面积公式: 使用推导出的公式计算这个多边形的面积。

  3. 误差分析: 随着 n 增大(点变密),多边形面积将趋近于圆的真实面积 π。我们可以观察误差 |估计面积 - π| 如何随 n 增加而减小。

代码实现要点

以下是一个概念性的代码流程,展示了如何组织计算:

function estimated_area = estimate_circle_area(n)
    % 生成 n+1 个点来近似圆
    theta = linspace(0, 2*pi, n+1); % 包含起点和终点
    x = cos(theta);
    y = sin(theta);

    % 应用面积公式
    area_sum = 0;
    for i = 1:n
        area_sum = area_sum + (x(i) + x(i+1)) * (y(i+1) - y(i));
    end
    estimated_area = 0.5 * abs(area_sum); % 取绝对值确保为正

    % 输出结果
    fprintf('n = %d, 估计面积 = %.6f, 误差 = %.6f\n', n, estimated_area, abs(estimated_area - pi));
end

运行该函数并增加 n(例如 n=10, 20, 40, 80, 160),可以观察到:

  • 估计面积逐渐逼近 π (约3.14159)。
  • 误差大致以 1/n^2 的速度减小。例如,当 n 翻倍时,误差减小到约原来的四分之一。这被称为二阶收敛

文件操作与可视化

在实际程序中,我们通常还会:

  • 将数据写入文件: 使用 csvwritedlmwrite 函数将生成的 (x, y) 坐标点保存到文本文件(如 circle_data.txt)中,以便后续使用或检查。
  • 从文件读取数据: 使用 load 函数读回数据,验证读写正确性。
  • 绘制图形: 使用 plot 函数分别绘制生成的折线(近似圆)和理想的平滑圆曲线,进行视觉对比。

总结 📝

本节课中我们一起学习了如何估计由闭合曲线定义区域的面积。

  1. 核心公式: 我们推导并验证了用于计算由点序列 (x0,y0), ..., (xn,yn) 定义的多边形面积的通用公式:
    A = 0.5 * Σ [ (x_{i-1} + x_i) * (y_i - y_{i-1}) ]
    该公式要求点按逆时针顺序排列。

  2. 数学基础: 此公式是矢量微积分中格林定理的一个特例,当边界为分段线性时,计算是精确的。

  3. 近似应用: 对于弯曲的边界(如圆),我们可以用密集的点集生成一个近似多边形,然后应用上述公式来估计面积。随着点数的增加,估计值会收敛于真实面积。

  1. 编程实践: 我们通过估计单位圆面积的例子,演示了如何生成数据、应用公式、分析误差(观察到二阶收敛),并涉及了文件读写和图形绘制的基本操作。

这个技术是许多实际应用的基础,例如从医学图像中量化特定区域的尺寸,为准确诊断和分析提供了数学工具。

008:Lecture 8 - 二次方程复值解的可视化 👁️

在本节课中,我们将学习如何可视化二次方程的复数值解。我们将从一个没有实数解的方程入手,探索其复数解的含义,并使用代码和图形来直观地展示这些解。

2.2:复数值解的可视化 📊

上一节我们介绍了二次方程及其解的基本概念。本节中,我们来看看如何可视化那些没有实数解的方程的复数值解。

例如,对于方程 x² - x + 1 = 0,我们可以很容易地发现它没有实数解。然而,如果我们使用二次求根公式,则可以找到它的复数解:(1 ± √3 i) / 2

这里我们面临两个问题:这些复数值解意味着什么?我们能否将它们可视化?本节将尝试回答这些问题。

关于复数的说明

复数系统由形如 z = x + yi 的数字组成,其中 xy 是实数,i 是虚数单位,定义为 i² = -1

核心观察

寻找方程 f(x) = 0 的实数解,等同于寻找方程 f(z) = 0 的解,其中 z = x + yi,并且通过令 y = 0 将解限制在实数轴(x轴)上。

因此,当我们尝试求解 f(z) = 0 时,我们可以找到复数解并将其可视化。通过一些代数运算,我们可以将 f(z) 写成 a + bi 的形式,其中 ab 都是实数。那么,复数解必须同时满足 a = 0b = 0

将函数展开为实部和虚部

现在,让我们尝试将函数 f(z) = z² - z + 1z = x + yi 处展开为 a + bi 的形式。

f(x + yi) = (x + yi)² - (x + yi) + 1
          = (x² - y² + 2xyi) - x - yi + 1
          = (x² - y² - x + 1) + (2xy - y)i

因此,我们得到:

  • 实部 a = x² - y² - x + 1
  • 虚部 b = 2xy - y

通过代码实现可视化

接下来,我们将通过编写 MATLAB/Octave 代码来可视化方程 x² - x + 1 = 0 的复数解。

以下是实现可视化的关键步骤代码:

% 检查并加载符号计算包(Octave需要)
if ~exist(‘OCTAVE_VERSION’, ‘builtin’)
    % 在MATLAB中,符号工具箱通常已自动加载
else
    pkg load symbolic % Octave需要手动加载
end

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/45e75ed8e55918c5c5d191222bec8fee_15.png)

% 定义符号变量
syms x y real
% 定义复数 z 和函数 f(z)
z = x + 1i*y;
f = z^2 - z + 1;
% 提取实部(a)和虚部(b)
a = simplify(real(f));
b = simplify(imag(f));

% 为绘图和打印准备字符串
str_a = char(a);
str_b = char(b);
fprintf(‘实部 a = %s\n’, str_a);
fprintf(‘虚部 b = %s\n’, str_b);

% 创建网格数据用于绘图
[X, Y] = meshgrid(linspace(-2, 2, 50), linspace(-2, 2, 50));
A = eval(subs(a, {x, y}, {X, Y}));
B = eval(subs(b, {x, y}, {X, Y}));

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/45e75ed8e55918c5c5d191222bec8fee_17.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/45e75ed8e55918c5c5d191222bec8fee_19.png)

% 图1:三维曲面图
figure(‘Position’, [100 100 1200 500])
subplot(1,2,1)
mesh(X, Y, A, ‘EdgeColor’, ‘r’, ‘FaceAlpha’, 0.5); hold on;
mesh(X, Y, B, ‘EdgeColor’, ‘b’, ‘FaceAlpha’, 0.5);
mesh(X, Y, zeros(size(X)), ‘EdgeColor’, ‘k’, ‘FaceAlpha’, 0.1); % z=0平面
legend(‘z = a (实部)’, ‘z = b (虚部)’, ‘z = 0平面’)
xlabel(‘x’); ylabel(‘y’); zlabel(‘z’);
title(‘函数实部(a)与虚部(b)的三维曲面’)
view(30, 30)

% 计算并标记解
sol_x = [0.5, 0.5];
sol_y = [sqrt(3)/2, -sqrt(3)/2];
plot3(sol_x, sol_y, [0, 0], ‘ko’, ‘MarkerSize’, 10, ‘MarkerFaceColor’, ‘g’)

% 图2:等高线图(解是a=0和b=0两条线的交点)
subplot(1,2,2)
contour(X, Y, A, [0 0], ‘r-’, ‘LineWidth’, 2); hold on; % a=0的等高线
contour(X, Y, B, [0 0], ‘b--’, ‘LineWidth’, 2); % b=0的等高线
plot(sol_x, sol_y, ‘ro’, ‘MarkerSize’, 10, ‘MarkerFaceColor’, ‘r’)
legend(‘a=0’, ‘b=0’, ‘复数解’)
xlabel(‘x (实部)’); ylabel(‘y (虚部)’);
title(‘方程解的等高线可视化 (a=0 与 b=0 的交点)’)
grid on
axis equal

可视化结果解读

运行上述代码会生成两幅图:

  1. 三维曲面图
    • 红色曲面代表实部 z = a(x, y)
    • 蓝色曲面代表虚部 z = b(x, y)
    • 黑色平面代表 z = 0
    • 方程的解位于红色曲面和蓝色曲面同时与 z=0 平面相交的位置。绿色标记点标示了这些解。

  1. 等高线图
    • 红色实线是 a(x, y) = 0 的等高线。
    • 蓝色虚线是 b(x, y) = 0 的等高线。
    • 这两条线的交点(图中红色圆点)就是方程 f(z)=0 的复数解,即 (0.5, √3/2)(0.5, -√3/2),对应 z = 0.5 ± (√3/2)i

利用符号计算简化过程

手动展开复函数可能很繁琐。我们可以直接使用符号计算工具箱来获得实部和虚部:

syms x y real
z = x + 1i*y;          % 1i 代表虚数单位 i
f_expr = z^2 - z + 1;
a_sym = simplify(real(f_expr)); % 输出: x^2 - y^2 - x + 1
b_sym = simplify(imag(f_expr)); % 输出: 2*x*y - y

这与我们手动计算的结果完全一致。

总结 🎯

本节课中,我们一起学习了如何可视化二次方程的复数值解。核心要点在于:寻找 f(x)=0 的实数解,等价于在复数域中寻找 f(z)=0 的解(z = x + yi),并通过令虚部 y=0 来约束到实数轴。通过求解 f(z)=0,我们不仅能找到复数解,还能通过绘制其实部函数和虚部函数的零值等高线,在复平面上直观地看到这些解的位置(即两条等高线的交点)。这种方法将抽象的复数解转化为清晰的几何图像。

009:第9讲 - 离散傅里叶变换 (DFT) 与快速傅里叶变换 (FFT) 📊

在本节课中,我们将要学习信号频谱分析的核心工具——傅里叶变换。我们将从连续信号的傅里叶变换开始,逐步过渡到数字信号处理中更常用的离散傅里叶变换,并了解其快速算法FFT。课程将通过一个具体的编程示例,帮助你理解如何应用DFT来分析信号的频率成分。

概述 📋

在音频数据的频谱分析中,我们的目标是确定信号的频率内容。对于模拟信号,我们使用傅里叶变换;对于数字信号,我们使用离散傅里叶变换。

2.3 离散傅里叶变换

上一节我们介绍了频谱分析的目标,本节中我们来看看实现这一目标的核心数学工具。

连续傅里叶变换

我们从傅里叶变换的定义开始。对于一个时域中的连续信号 x(t),其傅里叶变换定义为以下公式:

公式:
X(ω) = ∫_{-∞}^{∞} x(t) * e^{-iωt} dt

其中,角频率 ω = 2πff 是频率。对于每一个选定的 ω,这个积分会产生一个复数。这些值对于所有 ω 都是可用的。

X(ω) 的集合中,我们可以通过逆傅里叶变换恢复原始的时域信号:

公式:
x(t) = (1 / 2π) * ∫_{-∞}^{∞} X(ω) * e^{iωt} dω

你可以看到这两个方程之间的相似性。一个积分是关于时间 t,另一个是关于频率 ω。一个带有负号,另一个没有,并且存在一个缩放因子。

欧拉恒等式

这里用到的恒等式 e^{iθ} = cosθ + i sinθ 被称为欧拉恒等式。我们将在第3章详细讨论这个恒等式的推导及其应用。

离散傅里叶变换

在现实中,大多数信号都是数字格式的。这里,x[n] = x(nΔt) 是一个离散信号,其中 Δt 是采样点之间的时间间隔。x[n] 的集合构成了离散信号。

对于离散信号,离散傅里叶变换定义为:

公式:
X[k] = Σ_{n=0}^{N-1} x[n] * e^{-i 2π k n / N}

其中:

  • N 是离散数据点的总数。
  • T 是总采样时间,因此 Δt = T / N
  • Δf 是频率增量(频率分辨率),必须等于 1 / T 赫兹。
  • f_s 是每秒的采样频率,等于 1 / Δt = N / T

在计算中,k * n / N 这个部分会反复出现。可以看到这个公式与连续傅里叶变换的相似性:时域信号乘以 e^{-iωt} 的模拟,并对时间方向求和,从而在频域产生一个复数值。

对于每一个 k(从 0 到 N-1),频域值 X[k] 都是可用的,然后我们可以通过逆离散傅里叶变换恢复原始信号:

公式:
x[n] = (1 / N) * Σ_{k=0}^{N-1} X[k] * e^{i 2π k n / N}

这里可以很容易地看到相似性:现在求和是针对频率,使用的是频域复数值 X[k]e^{iωt} 的模拟,没有负号,缩放因子是 1/N 而不是 1/(2π)

快速傅里叶变换

如果你在处理傅里叶变换,可能会遇到“快速傅里叶变换”这个短语。FFT 本质上就是离散傅里叶变换,但它是一种通过算法操作来快速计算数值的更快方法。然而,其结果必须与离散傅里叶变换相同。因此,所有关于 DFT 的规则和细节同样适用于 FFT。

以下是关于 FFT 实现的一些注意事项:

  • 2的幂限制:许多 FFT 实现(例如 Microsoft Excel 中的 FFT)要求 N 是 2 的幂,如 64、128、256 等。
  • 无限制实现:MATLAB 中的 FFT 函数没有这样的限制。

FFT 只是离散傅里叶变换的快速版本,我们不会深入其算法细节。

示例:合成信号的 DFT 应用 💻

在这个例子中,我们将生成一个持续4秒的合成信号,采样率为 100 Hz。这意味着每秒产生100个点,总共4秒。然后计算其 DFT,并通过应用逆离散傅里叶变换来恢复原始信号。

我们将从实现离散傅里叶变换及其逆变换开始。

实现离散傅里叶变换

以下是 DFT 的代码实现核心思路。我们定义函数,计算信号长度 N,并初始化一个全零的复数数组来存放频域输出 X。核心计算涉及双重循环:

代码逻辑:

对于 每一个频率索引 k 从 0 到 N-1:
    对于 每一个时间索引 n 从 0 到 N-1:
        计算 相位因子 = -i * 2π * k * n / N
        将 x[n] * exp(相位因子) 累加到 X[k] 中

在 MATLAB 中,索引从1开始,因此代码中索引需要做相应调整(n+1, k+1)。对于每个 k,我们遍历所有时间点 n 进行求和,最终得到 X[k]

实现逆离散傅里叶变换

逆变换的代码结构与正变换类似,但角色互换:

代码逻辑:

对于 每一个时间索引 n 从 0 到 N-1:
    对于 每一个频率索引 k 从 0 到 N-1:
        计算 相位因子 = i * 2π * k * n / N
        将 X[k] * exp(相位因子) 累加到 x_reconstructed[n] 中
    x_reconstructed[n] = x_reconstructed[n] / N

这里,内循环遍历频域值 X[k],外循环为每个时间点 n 重建信号。公式中不包含负号,并在最后乘以缩放因子 1/N

应用与结果

首先,我们实现 DFT 和 IDFT 函数,然后用它们来解决问题。

  1. 生成合成信号:我们组合两个正弦波,一个频率为 10 Hz,振幅为 1;另一个频率为 20 Hz,振幅为 2。信号持续 T=4 秒,采样频率 f_s=100 Hz。
  2. 计算 DFT:对合成信号应用 DFT,得到频域值 X
  3. 计算频谱:从 X 可以计算幅度谱 (abs(X)) 和相位谱 (angle(X))。
  4. 信号重建:对 X 应用逆 DFT,恢复时域信号。
  5. 验证:计算原始信号与重建信号之间的差异,理论上除了舍入误差外应为零。

结果图显示:

  • 时域信号:由10 Hz和20 Hz正弦波叠加而成。
  • 幅度谱:在频率 10 Hz 和 20 Hz 处有明显的峰值。同时,在 80 Hz 和 90 Hz 处也有峰值,但这并非真实频率成分,而是一种“混叠”效应的表现。实际上,我们需要丢弃后半部分频谱。
  • 相位谱:显示了各频率成分的相位角。

奈奎斯特准则与混叠 ⚠️

在解释 DFT 结果时,理解奈奎斯特准则至关重要。

当以采样频率 f_s 对信号进行采样时,我们只能可靠地获得频率低于 f_s/2 的信息。这里的“可靠”是指没有混叠问题。混叠会导致高频信号被错误地表示为低频信号。

根据这个准则,我们可以计算出哪个 k 值对应的频率 f = kΔf 等于 f_s/2。求解 k 得到 k = N/2

因此,只有频率对应的 k 值小于 N/2 的部分(即前半部分频谱)才包含可靠信息。后半部分频谱并不可靠。

由此我们得出结论,从 DFT 输出中能获得的最大有用频率(也称为折叠频率)为:
公式: f_max = f_s / 2 = (N/2) * Δf

换句话说,只有一半的 DFT 输出值是有用的。通常我们只使用 k 从 0 到 N/2 - 1 对应的输出。

示例分析

假设我们以 f_s = 100 Hz 的采样率对信号采样 T=4 秒,如前例所示。

  • 数据点总数N = T * f_s = 4 * 100 = 400
  • 有用的 DFT 输出值数量N/2 = 200
  • 频率分辨率 ΔfΔf = 1/T = 1/4 = 0.25 Hz
  • 最大可靠频率f_max = (N/2) * Δf = 200 * 0.25 = 50 Hz

因此,50 Hz 是最大可靠频率。输出值中大于此折叠频率的另一半,如果用于后续分析会产生错误或伪影,必须谨慎对待。然而,当你要通过逆变换恢复原始信号时,必须使用全部的 N 个值。

总结 🎯

本节课中我们一起学习了傅里叶变换的核心概念及其在数字信号处理中的应用。我们从连续傅里叶变换出发,引出了处理实际数字信号所需的离散傅里叶变换及其快速算法FFT。通过一个编程示例,我们实践了如何用DFT分析合成信号的频率成分,并理解了幅度谱和相位谱的含义。最后,我们探讨了至关重要的奈奎斯特准则,它定义了采样过程中无混叠的频率范围,并指导我们如何正确解释DFT的输出结果(通常只使用前半部分频谱)。掌握这些知识是进行音频、图像等信号频谱分析的基础。

010:短时离散傅里叶变换 (STFT) 🎵

在本节课中,我们将要学习短时离散傅里叶变换。上一节我们介绍了离散傅里叶变换及其实现,本节中我们来看看如何将其应用于分析信号频率随时间变化的情况。

短时傅里叶变换用于分析信号的频率成分如何随时间变化。

以下是计算STFT的步骤:
首先,我们将信号分割成长度相等的短片段,然后对每个短片段分别计算离散傅里叶变换。因此,它是离散傅里叶变换的一个简单应用。

STFT的幅度平方被称为谱图,它是信号的时频表示。

计算短时傅里叶变换需要设置一个长度为 M 的分析窗口。你必须选择一个参数 M,同时还需要选择一个窗口函数 G 以及一个参数 R,即滑动长度。窗口以 R 为步长在原信号上滑动,在这种情况下,重叠长度必须为 M - R

大多数窗口函数在边缘处会逐渐衰减至零,以避免频谱泄漏。这意味着窗口函数 G 的幅度在两端会变小。

每个片段的离散傅里叶变换结果被存储到一个复数值矩阵中,该矩阵有 M 行和许多列。这里,N 是原始信号的长度。

让我们看这张图。原始信号有 N 个样本。开始时,我们选择 M 个样本,然后乘以窗口函数 G。滑动 R 个样本后,我们再次取 M 个样本并乘以 G。如此重复,直到处理完整个信号。对于每个加窗后的信号片段,我们应用离散傅里叶变换。其频率分量的幅度将如这部分所示。对于低频部分,峰值出现在频率轴的低位置;对于高频部分,峰值出现在高位置。这些峰值会逐渐变高。每个片段的变换结果构成一列,这整个集合就称为谱图

STFT很容易实现。MATLAB和Octave有内置的 spectrogram 函数,但我们将尝试在此详细实现。

首先,我们必须选择一个分析窗口。我选择了一个余弦窗,其中 M = 150。代码如下:

% 生成余弦窗函数
t = linspace(0, 2*pi, M);
G = 0.55 - 0.45 * cos(t);

t0 时,cos(t)1,因此 G 的值为 0.1。在中心点,值为 1。这就是窗口函数。

接下来是短时傅里叶变换的主要部分。我们设置采样频率 Fs = 10000,总时长 T0 = 1 秒。时间向量可以这样生成:

t = 0:1/Fs:T0-1/Fs;

然后,我们创建一个所谓的“啁啾信号”,其频率从 100 Hz 线性增加到 4000 Hz

x0 = sin(2*pi*(100 + 3900*t) .* t);

这个小片段被复制 30 次以构成测试数据 x

我们选择窗口长度 M = 150,滑动长度 R = 6。通过调用自定义函数 STFT2 并传入信号 x、窗口 G 和滑动长度 R,我们得到频率分量,其幅度平方即为谱图。

以下是 STFT2 函数的一个简单实现思路:

function STFT = STFT2(x, G, R)
    N = length(x);
    M = length(G);
    L = floor((N-M)/R) + 1; % 计算谱图的列数
    STFT = zeros(M, L); % 初始化矩阵

    col = 1;
    for n0 = 1:R:(N-M+1)
        segment = x(n0:n0+M-1) .* G'; % 加窗
        STFT(:, col) = fft(segment); % 计算DFT
        col = col + 1;
    end
end

首先,我们测量信号长度 N 和窗口长度 ML 是重叠段的数量,决定了谱图的列数。我们初始化一个 ML 列的矩阵来存储频率分量。在循环中,我们从信号中取出 M 个样本,乘以窗口函数 G,然后使用 fft 函数计算其离散傅里叶变换,结果存入矩阵的当前列。

在MATLAB中,我们也有内置的 stft 函数,但这里我们将其与 spectrogram 函数分开实现。

让我们看一下绘图结果。第一张图显示了啁啾信号的前 1000 个样本,起始频率为 100 Hz。第二张图是窗口函数。最后一张是谱图。

在谱图中,你可以看到频率从 100 Hz 开始,在 1 秒内逐渐增加到 4000 Hz。这个由 30 份啁啾信号复制连接而成的测试信号,其谱图清晰地展示了这一变化。

只有前半部分的频率分量是有用且可靠的,后半部分在大多数分析中必须被丢弃。这是由混叠现象决定的。

本节课中我们一起学习了短时离散傅里叶变换的原理、实现步骤,并通过谱图观察了信号频率随时间的变化。这就是本节的结尾。

011:误差、算法与收敛速率

概述

在本节中,我们将学习计算算法的基础概念,包括误差的定义、算法的稳定性,以及如何衡量序列的收敛速率。我们将通过具体的数学定义和例子来理解这些概念。


误差的定义

假设 p*p 的一个近似值。p* 可以是某个计算算法的输出。

绝对误差 定义为真实值与近似值之差的绝对值:
[
\text{绝对误差} = |p - p^*|
]

相对误差 是绝对误差除以真实值的绝对值:
[
\text{相对误差} = \frac{|p - p^*|}{|p|}
]
p 不为零时,可以定义相对误差。在实际应用中,相对误差比绝对误差更常用。

例如,假设 p = 1,000,000,并且我们有一个近似值 p*,其绝对误差约为 1000。虽然绝对误差看起来很大,但其相对误差为 1000 / 1,000,000 = 0.001,即 0.1%。这个相对误差非常小,因此结果是可接受的。


计算算法

算法 是一种以明确方式描述一系列步骤的过程,这些步骤需要按特定顺序执行。算法包含输入、输出和功能操作等多个步骤。

算法可以通过伪代码有效地描述。伪代码展示了主要操作,易于理解。在过去,算法通常隐藏在C或Fortran等语言的冗长代码中,难以理解。而使用伪代码,我们可以更有效地理解算法。在现代,虽然使用MATLAB或Python编写的代码可能很短,易于理解,但伪代码仍然被广泛用于更有效地描述算法。

一个算法被称为稳定的,如果初始数据(输入)的微小变化只导致最终结果(输出)的微小变化。稳定算法对输入的小扰动不敏感。否则,该算法被称为不稳定的。有些算法仅对某些输入或参数选择稳定,这种情况称为条件稳定


误差的增长速率

假设 E 是在某个阶段(可能是初始阶段)引入的误差,E_n 表示经过 n 次后续操作后的误差大小。

如果 E_n 大致为 n 乘以一个常数 C 和初始误差 E,即:
[
E_n \approx C \cdot n \cdot E
]
那么误差的增长是线性的。在这种情况下,算法是稳定的。

如果 E_n 以指数形式增长,对于某个 C > 1
[
E_n \approx C^n \cdot E
]
那么误差的增长是指数的。在这种情况下,算法是不稳定的。


收敛的速率阶

{x_n} 是一个趋于极限 x* 的实数序列。x_n 可以是一个迭代算法的输出。

线性收敛:如果存在常数 C(满足 0 < C < 1)和整数 N,使得对于所有 n ≥ N,有:
[
|x_{n+1} - x^| \leq C \cdot |x_n - x^|
]
这意味着后续误差被前一步误差的一个固定比例(小于1)所限制,误差呈线性缩小。

超线性收敛:如果存在一个趋于零的序列 {ε_n},使得:
[
|x_{n+1} - x^| \leq ε_n \cdot |x_n - x^|
]
那么误差的减少速度比线性收敛更快。

二次收敛:如果存在常数 C 和整数 N,使得对于所有 n ≥ N,有:
[
|x_{n+1} - x^*| \leq C \cdot |x_n - x*|2
]
二次收敛的速度非常快。例如,如果某步误差是 10^{-2},下一步误差约为 10^{-4},再下一步约为 10^{-8},依此类推。

一般情况:如果存在常数 C 和整数 N,使得对于所有 n ≥ N,有:
[
|x_{n+1} - x^*| \leq C \cdot |x_n - x*|
]
则称收敛速率至少为 α 阶。当 α=1 时,要求 C < 1


示例:证明二次收敛

考虑递归定义的序列:
[
x_1 = 2, \quad x_{n+1} = \frac{x_n}{2} + \frac{1}{x_n}
]

第一步:观察序列行为
通过计算(例如使用MATLAB或Python)前几项:

  • x_1 = 2
  • x_2 = 1.5
  • x_3 ≈ 1.416666...
  • x_4 ≈ 1.414215...
    序列单调递减且有下界,因此它是收敛的。

第二步:数学证明收敛性并求极限
可以证明对于所有 n,有 x_n > \sqrt{2},并且 x_{n+1} < x_n。因此序列单调递减且有下界,故收敛。
设极限为 x。在递归式两边取极限,得到:
[
x = \frac{x}{2} + \frac{1}{x}
]
解此方程:
[
2x^2 = x^2 + 2 \quad \Rightarrow \quad x^2 = 2 \quad \Rightarrow \quad x = \sqrt{2}
]
因此,序列的极限是 \sqrt{2}

第三步:证明收敛是二次的
我们需要证明存在常数 C,使得:
[
|x_{n+1} - \sqrt{2}| \leq C \cdot |x_n - \sqrt{2}|^2
]
开始推导:
[
\begin{aligned}
x_{n+1} - \sqrt{2} &= \left( \frac{x_n}{2} + \frac{1}{x_n} \right) - \sqrt{2} \
&= \frac{1}{2x_n} \left( x_n^2 + 2 - 2\sqrt{2} x_n \right) \
&= \frac{1}{2x_n} \left( x_n^2 - 2\sqrt{2} x_n + 2 \right) \
&= \frac{1}{2x_n} (x_n - \sqrt{2})^2
\end{aligned}
]
由于已知 x_n \geq \sqrt{2},所以 1/(2x_n) \leq 1/(2\sqrt{2})。因此:
[
|x_{n+1} - \sqrt{2}| = \frac{1}{2x_n} |x_n - \sqrt{2}|^2 \leq \frac{1}{2\sqrt{2}} |x_n - \sqrt{2}|^2
]
C = 1/(2\sqrt{2}),我们就证明了该序列以二次速率收敛到 \sqrt{2}


总结

本节课我们一起学习了计算算法的基础。我们定义了绝对误差和相对误差,理解了算法稳定性的概念,并探讨了误差的线性与指数增长。重点在于,我们学习了如何定义和判断序列的收敛速率,包括线性、超线性和二次收敛。最后,我们通过一个具体的序列例子,完整演示了如何证明其收敛性并确定其二次收敛的速率。理解这些概念对于分析和设计数值算法至关重要。

012:大O和小o符号 📊

概述

在本节课中,我们将学习用于描述序列和函数收敛速度的数学符号:大O符号和小o符号。这些概念对于分析算法复杂度至关重要。


大O符号(Big O Notation)🔍

上一节我们介绍了序列收敛的基本概念,本节中我们来看看如何精确描述其收敛速度。

一个序列 α_n 被称为属于 β_n 的大O,记作 α_n ∈ O(β_n),如果存在一个正数 K,使得对于足够大的 n,以下不等式成立:

|α_n| ≤ K * |β_n|

等价地,比值 |α_n / β_n| 的绝对值被常数 K 所界定。

在这种情况下,我们说 α_nβ_n 的大O,并使用符号 α_n ∈ O(β_n)α_n = O(β_n)


小o符号(Little o Notation)🐌

了解了大O符号描述的是“同阶或更低阶”的界限后,我们接下来看看小o符号,它描述的是“严格更低阶”的行为。

一个序列 α_n 被称为属于 β_n 的小o,记作 α_n ∈ o(β_n),如果存在一个趋于0的序列 ε_n,使得对于足够大的 n,满足以下不等式:

|α_n| ≤ ε_n * |β_n|

这等价于极限条件:

lim (n→∞) α_n / β_n = 0

如果 β_n 是递增的,那么 α_n 的增长(或衰减)速度严格慢于 β_n。我们使用符号 α_n ∈ o(β_n)α_n = o(β_n)


示例分析 💡

以下是应用这些定义的几个例子。

示例1:证明函数关系

考虑函数 f(n) = (n^2 + 3n) / (n^3 + 20n^2)。我们需要证明:

  1. f(n) ∈ O(n^{-2})
  2. f(n) ∈ o(n^{-1})

第一部分证明 (f(n) ∈ O(n^{-2})):
计算比值 f(n) / n^{-2}

f(n) / n^{-2} = (n^2 + 3n) * n^2 / (n^3 + 20n^2) = (n^4 + 3n^3) / (n^3 + 20n^2)

化简后,当 n → ∞ 时,该比值趋近于1。由于比值极限为有限常数(1),因此 |f(n)/n^{-2}| 有界,满足大O定义。

第二部分证明 (f(n) ∈ o(n^{-1})):
计算比值 f(n) / n^{-1}

f(n) / n^{-1} = (n^2 + 3n) * n / (n^3 + 20n^2) = (n^3 + 3n^2) / (n^3 + 20n^2)

n → ∞ 时,该比值趋近于1,并非趋近于0。等等,这里需要仔细检查原问题。原叙述是 f(n) ∈ o(n^{-1}),但根据计算,极限为1,不等于0。因此,原示例中的这个结论 f(n) ∈ o(n^{-1}) 可能不成立,除非 f(n) 的定义不同。正确的结论应为 f(n) ∈ O(n^{-1})。这提醒我们验证极限的重要性。


函数的大O与小o符号 📉

上述概念可以推广到函数(特别是当自变量趋于0时)。假设 g(h) → 0h → 0

一个函数 F(h) 被称为属于 g(h) 的大O,如果存在正数 K,使得对于足够小的 h,有:

|F(h)| ≤ K * |g(h)|

记作 F(h) ∈ O(g(h))

类似地,F(h) 被称为属于 g(h) 的小o,如果比值 F(h)/g(h)h → 0 时趋于0:

lim (h→0) F(h) / g(h) = 0

记作 F(h) ∈ o(g(h))


应用实例:泰勒展开 🔬

考虑余弦函数 cos(x)x=0 处的泰勒展开:

cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ...

如果取前四项(到 x^6 项),余项 R(x) 可以表示为:

R(x) = cos(x) - (1 - x^2/2 + x^4/24) = O(x^6)

但更常见的写法是,当展开到 x^4 项时,余项是 x^5 或更高阶的无穷小。实际上,由于 cos(x) 是偶函数,其展开式只包含偶次幂。因此,展开到 x^4 项后的余项是 O(x^6)。然而,原示例指出余项为 O(x^4)。我们来验证:
定义 F(x) = cos(x) - (1 - x^2/2)。则 F(x) 包含 x^4/24 及更高阶项。计算 F(x)/x^4 的极限,其绝对值被 1/24 界定,因此 F(x) ∈ O(x^4)。这说明余项是 x^4 阶的。


练习题与解析 ✅

以下是几个判断题,用于检验理解。

题目:判断下列断言是否正确(n → ∞)。

  1. (n^3 + n) / (n^3 + 1) ∈ o(1/n)
  2. (n^3 + n) / (n^3 + 1) ∈ O(1/n)
  3. (log n) / n ∈ o(1/n)
  4. (log n) / n ∈ O(1/n)

解析:

  • 断言1:计算 [(n^3+n)/(n^3+1)] / (1/n) = n*(n^3+n)/(n^3+1),当 n→∞ 时趋于无穷大,不满足 o(1/n)(极限需为0)。错误
  • 断言2:同上,比值无界,不满足 O(1/n)(需有界)。错误
  • 断言3:计算 [(log n)/n] / (1/n) = log n,当 n→∞ 时趋于无穷大,不满足 o(1/n)错误
  • 断言4:计算 [(log n)/n] / (1/n) = log n,当 n→∞ 时无界,不满足 O(1/n)错误
    • 注:实际上 (log n)/no(1),但比 1/n 收敛得慢。原题中所有断言似乎均不成立,这可能是为了强调仔细计算极限的重要性。


综合例题:极限与收敛速率 🧮

定义函数 F(h) = (1 + h - e^h) / h

1. 求极限:
h → 0 时,分子分母均趋于0,应用洛必达法则:

lim (h→0) F(h) = lim (h→0) (1 - e^h) / 1 = 0

因此,lim (h→0) F(h) = 0

2. 求收敛速率(即找到最大的 k 使得 F(h) ∈ O(h^k)):
我们需要找到常数 k,使得 F(h) / h^kh→0 时有界。
尝试 k=1:计算 F(h)/h^1 = (1 + h - e^h) / h^2。再次应用洛必达法则:

lim (h→0) (1 + h - e^h)/h^2 = lim (h→0) (1 - e^h)/(2h) = lim (h→0) (-e^h)/2 = -1/2

极限存在且为有限常数(-1/2),因此 F(h) / h^1 有界。所以 F(h) ∈ O(h^1),即收敛速率是线性的 (k=1)。

尝试 k=2:计算 F(h)/h^2 = (1 + h - e^h)/h^3。应用洛必达法则后,极限趋于无穷大,因此 F(h) ∉ O(h^2)

结论: F(h) 的收敛速率是 O(h)


总结

本节课中我们一起学习了描述收敛顺序的核心工具:

  • 大O符号 (O):表示函数或序列的增长(或衰减)速率不超过另一个函数的某个常数倍。它描述了一个上界。
  • 小o符号 (o):表示函数或序列的增长(或衰减)速率严格慢于另一个函数。它描述了一个更紧的、非渐进等价的上界。
  • 我们通过计算比值并观察其极限(是趋于有限常数还是0)来区分大O和小o。
  • 这些概念适用于序列(n → ∞)也适用于函数(例如 h → 0),在算法分析、数值计算和泰勒展开余项估计中非常重要。

理解大O和小o符号,是分析算法效率和数值方法精度的基础。

013:第13讲 - 逆函数与指数函数 📚

在本节课中,我们将学习函数的核心概念之一:逆函数。我们将探讨如何找到一个函数的逆,并理解指数函数及其在现实世界中的应用,例如人口增长建模。课程内容将涵盖从基本定义到实际计算的完整流程。


逆函数 🔄

函数 F 是一个规则,它为每个输入 x 分配一个输出 y。我们可以将其表示为:F(x) = y

然而,在实际问题中,我们常常需要根据输出 y 和函数规则 F 来求解输入 x,或者根据输入 x 和输出 y 来求解函数 F 本身。求解 x 的过程就涉及到寻找函数的逆运算。

如何求逆函数

以下是通过一个例子来展示求逆函数的步骤。

给定函数 F(x) = 2x + 1,其逆函数的求解过程如下:

  1. y 表示为 x 的函数:y = 2x + 1
  2. 解出 xx = (y - 1) / 2
  3. 按照惯例,将自变量和因变量互换,用 x 表示自变量,用 y 表示函数值。因此,逆函数为:F⁻¹(x) = (x - 1) / 2

我们可以将这个过程总结为以下公式:

y = F(x) → 解出 x → 交换 x 和 y → 得到 F⁻¹(x)

一一对应函数与水平线检验法

并非所有函数都有逆函数。只有当函数是“一一对应”时,其逆才是一个有效的函数。

一个函数是一一对应的,如果它永远不会取两次相同的值。即,如果 x₁ ≠ x₂,那么 F(x₁) ≠ F(x₂)

我们可以使用水平线检验法来直观判断:
如果一个函数的图像被任何水平线穿过最多一次,那么它就是一一对应的。

例如,函数 y = x² 在整个定义域上不是一一对应的,因为水平线 y=4 会与图像相交于 x=2x=-2 两点。但是,如果我们将其定义域限制在 x ≥ 0,那么它就变成一一对应的了。

逆函数的定义与图像

如果 F 是一个定义域为 A、值域为 B 的一一对应函数,那么它的逆函数 F⁻¹ 的定义域为 B,值域为 A,并由以下等式定义:
F⁻¹(y) = x ⇔ F(x) = y

由于在求解过程中我们交换了 xy,因此函数 y = F(x) 与其逆函数 y = F⁻¹(x) 的图像关于直线 y = x 对称。


指数函数 📈

上一节我们探讨了如何求函数的逆,本节我们将目光转向一类重要的函数:指数函数。

指数函数的形式为:F(x) = aˣ,其中底数 a 是一个大于0且不等于1的常数。

指数函数具有以下特性:

  • 定义域:所有实数 (-∞, ∞)
  • 值域:所有正实数 (0, ∞)
  • 单调性
    • a > 1 时,函数单调递增(如 y = 2ˣ, y = 10ˣ)。
    • 0 < a < 1 时,函数单调递减(如 y = (1/2)ˣ, y = (1/10)ˣ)。

指数回归:一个应用实例

指数函数常用于对增长或衰减过程进行建模。例如,我们可以用它来拟合20世纪世界人口的数据。

以下是进行指数回归分析的步骤:

  1. 建立模型:假设人口 P 与时间 t 的关系为 P(t) = A * Bᵗ
  2. 线性化:为了便于计算,我们对等式两边取对数:ln(P) = ln(A) + t * ln(B)
  3. 变量替换:令 α = ln(A), β = ln(B), y = ln(P)。原方程变为线性方程:y = α + βt
  4. 求解:利用最小二乘法,根据数据点 (t, ln(P)) 拟合出最佳的 αβ
  5. 还原参数:计算 A = eᵅ, B = eᵝ,即可得到最终的人口增长指数模型。

通过这种方法,我们可以得到一个能很好拟合历史数据点的指数曲线,用于描述和分析人口增长趋势。


自然常数 e 🧮

在众多指数函数 y = aˣ 中,有一个底数 a 具有独特的数学性质,它就是自然常数 e

e 被定义为满足以下条件的唯一实数:指数函数 y = eˣ 在点 (0, 1) 处的切线斜率恰好为 1

e 是一个无理数,其近似值约为 2.71828。它可以通过一个极限公式来计算:
e = lim (x→0) (1 + x)^(1/x)

我们可以编写一个简单的程序来观察这个极限的收敛过程,验证随着 x 趋近于0,表达式的值确实趋近于 e

因此,函数 y = eˣ(通常写作 exp(x))在数学、科学和工程中具有基础性的重要地位。


总结 ✨

本节课中,我们一起学习了函数的两个核心概念。

首先,我们深入探讨了逆函数:理解了它的定义,掌握了通过“解方程并交换变量”来求解逆函数的方法,并学会使用水平线检验法判断一个函数是否具有逆函数。

接着,我们研究了指数函数:了解了其一般形式和图像特性。我们通过“世界人口建模”的实际例子,学习了如何利用对数变换和线性回归进行指数回归分析。最后,我们介绍了数学中最重要的常数之一——自然常数 e,以及以其为底的指数函数的特殊性质。

掌握这些概念,是理解更复杂数学模型和进行数据分析的重要基础。

014:对数函数 📈

在本节课中,我们将要学习对数函数。我们将从回顾指数函数及其反函数开始,然后正式定义对数函数,并探讨其性质、图像、常用规则以及在实际计算中的应用。


章节 2.05:对数函数

在上一节中,我们学习了反函数和指数函数。指数函数 f(x) = a^x 是单调的(递增或递减),因此它是一一对应的,可以通过水平线测试。这意味着指数函数存在反函数。本节中,我们将定义这个反函数——对数函数。

a 为底的对数函数,记作 y = log_a(x),是函数 y = a^x 的反函数。这意味着,当你看到对数方程 y = log_a(x) 时,你必须理解为其等价形式是 a^y = x

让我们通过一个例子来更具体地理解对数。

示例:求 y = 2^x 的反函数。

要找到一个函数的反函数,第一步是解方程求出 x,第二步是交换 xy。然而,对于方程 y = 2^x,我们无法用常规代数方法解出 x。因此,我们引入新的记法 log,并写成 x = log_2(y)。然后,我们交换 xy,得到反函数 y = log_2(x)。从这个过程可以看出,对数本质上是指数函数的反函数的一种新记法。

函数 y = log_2(x) 的图像,必然是指数函数 y = 2^x 的图像关于直线 y = x 的反射,如上图所示。给定函数(指数函数)与其反函数(对数函数)的图像总是关于 y = x 这条直线对称。

这里有一个重要的注意事项。方程 a^y = x 是解出 x 的关键步骤。并且,函数 y = log_a(x) 的定义域必须是其反函数 y = a^x 的值域,即 (0, ∞),所有正数。这意味着对数函数只接受正数作为输入。如果 x 是 0 或负数,则对数没有定义。


自然对数与常用对数

有两种特别重要的对数底数。

  • 自然对数:以数学常数 e(约等于 2.718)为底的对数,称为自然对数,记作 ln(x)。即 ln(x) = log_e(x)
  • 常用对数:以 10 为底的对数,称为常用对数,通常简写为 log(x)。在数学公式中看到 log(x),通常指的就是 log_10(x)

在使用计算器或计算机程序时,需要注意记法的差异:

  • 在大多数计算器上,LN 键代表自然对数,LOG 键代表常用对数(以10为底)。
  • 在许多计算机编程语言(如MATLAB, Python)中,函数 log() 通常指自然对数,而常用对数则需要使用专门的函数,如 log10()


对数运算法则

以下是几个核心的对数运算法则,它们能帮助我们简化和计算对数表达式。

乘积法则log_a(xy) = log_a(x) + log_a(y)
商法则log_a(x/y) = log_a(x) - log_a(y)
幂法则log_a(x^r) = r * log_a(x)
换底公式log_a(x) = ln(x) / ln(a) (也可以用其他底数,如 log_b(x) / log_b(a)


反函数性质

由于对数和指数互为反函数,它们具有相互“抵消”的性质。

  • a^(log_a(x)) = x (对于 x > 0
  • log_a(a^x) = x (对于所有实数 x

特别地,当底数 a = e 时:

  • e^(ln(x)) = x (对于 x > 0
  • ln(e^x) = x (对于所有实数 x


应用示例:解对数方程

让我们应用这些法则来解一个方程。

示例:解方程 log_3(x) + log_3(x - 2) = 1。

  1. 应用乘积法则:将等式左边合并。log_3(x) + log_3(x - 2) = log_3[x(x - 2)]
  2. 简化右边:数字 1 可以写成 log_3(3),因为 log_a(a) = 1
  3. 得到等价方程:现在方程为 log_3[x(x - 2)] = log_3(3)
  4. 利用对数函数一一对应的性质:如果 log_a(M) = log_a(N),则 M = N。因此,x(x - 2) = 3
  5. 解二次方程:展开得 x^2 - 2x - 3 = 0,因式分解为 (x - 3)(x + 1) = 0。解得 x = 3x = -1
  6. 验证定义域:对数函数要求其参数为正数,即 x > 0x - 2 > 0,所以 x > 2。因此,x = -1 是无效解。
  7. 最终解:方程的唯一解是 x = 3。代入原方程验证:log_3(3) + log_3(1) = 1 + 0 = 1,成立。


重要结论:与自然对数/指数函数的关系

这里有两个非常重要的结论:

  1. 任何指数函数都是自然指数函数的幂。即,a^x = e^(x * ln(a))
  2. 任何对数函数都是自然对数的常数倍。即,log_a(x) = ln(x) / ln(a)。这正是前面提到的换底公式。

第二个结论的推导很简单:设 y = log_a(x),根据对数定义,等价于 a^y = x。两边取自然对数:ln(a^y) = ln(x)。应用幂法则:y * ln(a) = ln(x)。最后解出 yy = ln(x) / ln(a),即 log_a(x) = ln(x) / ln(a)

基于这些结论,所有的指数和对数计算,最终都可以通过自然指数函数 (exp) 和自然对数函数 (ln) 来完成。这在数学软件(如MATLAB中的 explog 函数)和编程中非常有用。


章节总结与预告

本节课中,我们一起学习了:

  1. 对数函数的定义:作为指数函数的反函数。
  2. 对数的图像:与对应指数函数图像关于 y = x 对称。
  3. 两种特殊对数:自然对数 ln(x) 和常用对数 log(x)
  4. 对数的核心运算法则:乘积、商、幂法则及换底公式。
  5. 对数的反函数性质:与指数函数相互抵消。
  6. 对数的通用性:所有指数和对数函数都可以用自然指数和自然对数表示。

本章节到此结束。在接下来的章节中,你们将开始学习编程计算,并着手进行一个名为“彩色图像边缘检测算法”的项目。希望大家能将本章学到的数学知识应用到编程实践中。

谢谢。

015:彩色图像的坎尼边缘检测算法(第一部分)📸

在本节课中,我们将要学习如何为彩色图像实现边缘检测算法。我们将从理解图像的基本概念开始,逐步探讨经典的坎尼边缘检测算法,并分析其在处理彩色图像时可能遇到的问题。最后,我们将介绍一个旨在改进彩色图像边缘检测效果的项目。

概述:什么是图像?🖼️

上一节我们介绍了课程背景,本节中我们来看看图像的基本定义。

图像是一个由像素组成的矩形阵列。对于彩色图像,每个像素拥有三个值:红色(R)、绿色(G)和蓝色(B)。在RGB表示法中,一幅图像可以被视为一个定义在图像域Ω上的函数。

例如,这是一个图像域,图像函数有三个分量,每个分量都是非负的,因此可以将其视为一个三维函数。

在实际中,图像域是整数有序对,这被称为采样。图像值也是介于0到255之间的整数,这被称为量化。相机采集的是连续信号,该信号被分离为RGB分量。为了有效存储,连续信号会被分割成整数值(0-255),这样信号可以更有效地保存在存储空间中,并且我们处理整数值也更加方便。

如果我们把图像看作一个对象I,那么:
I ∈ ℝ^(M×N×D)
其中,M是行数,N是列数,D是维度(1或3)。如果D=3,则为彩色图像;如果D=1,则为灰度图像。

彩色图像可以通过以下公式轻松转换为灰度图像:
灰度值 = 0.2989 * R + 0.5870 * G + 0.1140 * B
这是最常用的公式,在MATLAB中使用rgb2gray函数时也采用此公式。灰度值是RGB值的加权平均。

边缘检测算法概述🔍

大多数边缘检测算法是为灰度图像开发的。当然,你也可以将边缘检测算法应用于彩色图像,但通常需要先将彩色图像转换为灰度图像,然后再应用边缘检测算法。

边缘检测算法通常包含几个步骤。例如,坎尼边缘检测算法包含五个步骤。该算法由坎尼于1986年发表。

以下是五个步骤:

  1. 噪声抑制
  2. 梯度计算
  3. 非极大值抑制
  4. 双阈值处理
  5. 滞后阈值处理

我们将学习坎尼算法,并详细实现每一个步骤。

实践:使用Python的edge函数👨‍💻

上一节我们了解了算法的理论步骤,本节中我们来看看一个具体的程序实现。

一个使用Python内置函数edge的程序。其主要部分是设置参数,然后选择图像。示例数据中选择了“Lena”图像。读取后,我们将其转换为双精度值数组。图像值将被缩放,使其介于0到1之间,而不是0到255的整数值。但它仍然是彩色的,因为每个像素有三个值。然后进行缩放和偏置处理,并使用RGB转灰度公式。现在我们得到了灰度图像。接着,我们调用Python的边缘检测算法,传入灰度图像并选择“canny”方法。

现在我们保存并查看结果。这是Lena图像的彩色版、灰度版以及edge函数处理后的结果。Lena女士对图像处理学会做出了贡献,她的图像被广泛用于各种图像处理任务。我们应该感谢她的捐赠和贡献。

检测到的边缘并不完美。例如,周围区域没有边缘,但它有一些边缘成分。虽然不完美,但可以接受。通过更合理地选择一些参数,可以忽略这部分。在许多地方,结果并不完美,例如这个部分可能应该被忽略,但在这里可以看到。这揭示了边缘检测算法在处理彩色图像时的一个关键问题。

彩色图像边缘检测的挑战🎨

当彩色图像转换为灰度图像时,边缘可能会减弱强度甚至消失。让我们看这张彩色图像。一旦它被转换为灰度图像,除了这两个色块,其他各种颜色都被转换成了几乎相同的灰度值,因此你看到的是一幅非常平坦的灰度图像(除了这两个色块)。所以,如果你应用edge函数,你只会得到这两个色块的边缘。这是一个警示。

然而,如果你尝试为彩色图像进行边缘检测,那么在将其转换为灰度图像时必须小心。

在我们的项目中,我们将开发一个边缘检测算法,旨在减少此类问题,从而对彩色图像更有效。

项目目标与框架🚀

上一节我们指出了问题,本节中我们将介绍解决该问题的项目框架。

项目目标是开发一个边缘检测算法,使其对彩色图像的问题更少、更有效。我们将设计一个有效的算法,使其效果与精心调优的Python edge函数相当或更好。

为了方便和确保成功,将提供一个保存在此文件中的模型代码。从讲义中,如果你点击这个部分,就可以下载该程序包。

让我们看看模型代码。在程序中,最后我们添加了一个新的尝试。有一个名为edge_detection的函数,我们调用color_edge并传入彩色图像,这是用于文本处理的。图像名称现在仍在此程序中使用。这里,彩色图像是一个新的尝试。现在这里有彩色图像,让我们再看一下主要部分。

主要部分如下:

  • 步骤1:图像去噪或噪声抑制。你可以使用内置的高斯滤波算法。不过,我们也可以尝试使用TV去噪。我们稍后会看细节。
  • 步骤2:逐通道计算。对于每个通道(从1到3),对去噪后的图像,我们尝试使用Sobel算子获取梯度幅值,并按分量保存。同时,为每个通道估算边缘方向(角度θ)。这样就完成了步骤1和2。

在进入步骤3之前,我们必须合成梯度强度。在这里,我们尝试从逐通道的梯度中选择最大值,并保存其索引,该索引用于从分量角度中选择对应的角度。现在,将其保存。然后,使用合成的梯度强度和相应的角度,我们尝试使用非极大值抑制方法进行边缘细化。我们也会详细看这个算法。

  • 步骤4:进行双阈值处理。在此步骤中,你必须适当设置高阈值比率和低阈值比率这两个参数,然后调用双阈值函数。接着,查看被标记为强边缘、弱边缘和不相关边缘的像素,从而从中选择出强边缘像素。此函数已在程序包中实现。
  • 步骤5:这是坎尼边缘检测算法的最后一步。你将通过实现一个函数来进行滞后阈值处理。我也会给你详细的信息。

现在我们将考虑另一个步骤X和步骤6,这是一个额外的步骤,你将会明白为什么我们需要它。

这就是项目的范围和模型代码,我们将在第二部分中考虑细节。

总结📝

本节课中我们一起学习了图像的基本概念,特别是彩色图像的RGB表示和量化过程。我们回顾了经典的坎尼边缘检测算法的五个步骤,并通过一个Python示例观察了其应用效果。更重要的是,我们认识到了将彩色图像简单转换为灰度图再进行边缘检测的局限性——可能导致边缘信息丢失。为此,我们引入了一个新的项目,该项目旨在开发一个直接针对彩色图像、更有效的边缘检测算法,并初步了解了其实现框架和主要步骤。在下一部分,我们将深入探讨算法每个步骤的具体实现细节。

016:Lecture 16 - 彩色图像的坎尼边缘检测算法 Part 2

概述

在本节课中,我们将继续学习彩色图像的坎尼边缘检测算法。上一部分介绍了算法的整体框架,本节我们将深入探讨算法每一步的细节,并尝试寻找改进检测效果的新思路。


第一步:噪声抑制

上一节我们介绍了坎尼边缘检测算法。本节中我们来看看算法的第一步:噪声抑制。高斯滤波器是常用的方法,其公式为高斯函数。使用起来很方便,它是一种简单的平均算法,会平滑整幅图像。

然而,高斯滤波器不区分边缘像素和非边缘像素,因此可能会削弱边缘强度。我们将尝试另一种方法。

以下是另一种更好的方法:全变分模型。

全变分模型由这个偏微分方程给出。如果不熟悉偏微分方程,只需理解该模型的核心思想。公式中的 ∇·(∇u/|∇u|) 是平均曲率,β 是一个约束参数,属于拉格朗日乘子的一种。我们将在第30节学习拉格朗日乘子技术。

全变分模型的主要特性是倾向于收敛到一个分段常数图像,这种现象称为“阶梯效应”。因此,全变分模型可以同时用于噪声抑制和边缘保持,能非常有效地在抑制噪声的同时不削弱边缘。

我们需要求解这个偏微分方程来得到解。可以应用简单的显式方法,即这里的欧拉方法。我们尝试通过求解该方程来获得新的 n+1 层解。

u_n 项移到右边,乘以 Δt,再将 ∇·(∇u/|∇u|) 项移到右边,可以得到那个方程。现在,这个项被近似为 u_n。实际上,这是平均曲率负值的数值实现。通过使用该方程进行若干次迭代,我们可以得到解。

全变分模型要求设置 Δt 以保证稳定性。这意味着必须选择相当小的 Δt。如果选择较大的 Δt,解会发散。只需几次迭代,它就能有效减少噪声。

实现细节可以在 TVdenoise 函数以及 MATLAB 包中的 Chambolle 算法中找到。

以下是两种方法的比较。对于 Lena 图像,我们尝试对每个通道分别进行处理。对每个通道应用全变分去噪和高斯滤波,分别保存为 U_tvU_gauss,然后保存最终图像。这是原始 Lena 图像,这是全变分去噪后的图像,那是高斯滤波后的图像。

从中间这张图可以看出,全变分模型在减少噪声的同时保留了边缘。例如,这里和这里的一些小噪点几乎消失了,然而边缘被很好地保留下来。对于高斯滤波器,如果选择较小的 sigma,例如 1,可以得到更清晰的图像。但无论如何,使用高斯滤波器时必须小心,因为它会模糊所有区域。


第二步:梯度计算

现在,我们进入坎尼边缘检测算法的第二步:梯度计算。Sobel 算子对应于检测像素强度的变化。最简单的方法是应用在水平和垂直方向上都能突出强度变化的滤波器。

以下是 Sobel 算子的计算算法,用于计算 xy 方向的方向导数。通过使用所谓的 Sobel 核进行卷积来实现。这是 x 方向的 Sobel 核,这是 y 方向的 Sobel 核。

让我们看看它们是什么。这里我们考虑像素。更准确地说,图像值保存在每个格点上。对于这个点,这里的权重 -1 意味着这个值减去那个值。这代表了水平方向上的斜率。由于像素长度为 1,这个值本应除以 2,但这属于缩放问题,所以我们不除以 2。因此,这个值减去那个值代表了 x 方向导数的近似,而不是精确求值。这是水平方向的斜率,我们在这里取加权平均,这个权重为 2,那两个权重为 1,正好对应这个核。是的,两倍的斜率和一倍的斜率,由于总的贡献,我们本应除以 4,但这同样是缩放问题。所以,将这个滤波器与图像进行卷积,就会在这里得到 U_x,这是 ∂u/∂x 的近似。对于 U_y,可以类似地考虑,这里的 1-1 应代表垂直方向的斜率,这里测量那个斜率,然后对平均值赋予权重。通过与该滤波器进行卷积运算,可以得到 U_y。这就是测量 Sobel 梯度的方法。

然后,梯度有大小和方向。大小是 sqrt(U_x^2 + U_y^2),角度是 atan2(U_y, U_x)。这个角度代表了边缘的法线方向。如果边缘沿着某个角度 θ 延伸,那么其法线方向就是那个方向。在 MATLAB 中,atan2 是内置函数,可以这样使用。实现细节可以在 SobelGrad 函数中找到,下一页也会看到。

在程序中,彩色图像的梯度幅值保存在 G 中,然后被归一化。归一化并非必须,只是为了绘图方便。我们尝试将最大值设为 1,将值归一化到这个范围,这样在绘图时可以方便地显示梯度幅值剖面。

如果尝试获取 atan2 的信息,使用 help 命令可以看到,可移植的输出在这个范围内。atan2(Y, X)XY 的四象限反正切,结果在 π 之间。这是根据坐标测量的。输出值在 π 之间,例如这个角度,它指向这个方向。如果加上 π,方向将变为相反方向。但在我们的计算中,是这个方向还是那个方向并不重要,因为梯度大小才是关键。开始时,角度是通过 atan2(U_y, U_x) 测量的,但梯度大小是一个非负数。如果在这里使用部分非负数,可以转换成那种方式,所以没问题。无论如何,输出是 π

这是 SobelGrad 函数的代码。输出是梯度剖面和角度。可以看到,例如,U_x 在每个点用红色表示。我们使用索引 pqp 是垂直方向,q 是水平方向。q-1q 的左点,q+1 是右点。对于 p-1,实际上是上方的点,因为 p 是垂直方向。这是左点和右点。然而,如果它变为 0,则使用 1;如果大于 M,则使用 M。所以 maxmin 是为了处理边界,但主要思想是:p-1p 减 1(如果未触及边界),p+1p 加 1(如果未触及边界),对 q 同理。

对于 U_x,使用 p, pp, p?这里,p+1p-1 是第一个分量,然后 q-1 是水平方向左侧的差,q+1 是右侧的差。这是中心差分,水平方向的差。同样地,计算 U_y,然后将 sqrt(U_x^2 + U_y^2) 保存为梯度幅值 G,角度 theta 通过 atan2(U_y, U_x) 计算并保存。

得到梯度后,我们尝试查看每个 RGB 分量的幅值。显然,绿色分量的梯度幅值最大。然而,粉色分量也包含一些细节。这是 Lena 图像 RGB 梯度分量的幅值。

在整个项目中,我们可以通过简单地取 RGB 通道的最大值来获得单分量梯度。从这三个通道中,我们尝试取最大值。然后可以看到这个结果。

现在,我们尝试逐个通道计算梯度,然后尝试获得单值的梯度。在这种情况下,也许我们可以使用其他技术,而不是取最大值。我们得出了一个结论:显然有更好的算法。例如,在第一部分中的这个彩色棋盘图像,如果转换成灰度,这两个方块会有相同的灰度值。因此,传统方法无法检测这两个区域之间的边缘。然而,如果处理逐个通道的梯度,然后尝试从中获得一个单值梯度,就可能出现这种情况。在这个例子中,我们选择 RGB 梯度分量的最大值,但这里的结果相当好,我们可以得到更可靠的边缘。这是主要的好处和优势。


总结

本节课中,我们一起学习了彩色图像坎尼边缘检测算法的第二部分,重点探讨了噪声抑制的全变分模型和梯度计算的 Sobel 算子实现。全变分模型在去噪的同时能更好地保持边缘,而 Sobel 算子则用于计算图像梯度。我们还讨论了从多通道梯度信息中合成单值梯度的方法。在接下来的第三部分,我们将继续讲解算法的第三步、第四步和第五步。

017:彩色图像的坎尼边缘检测算法 Part 3 🖼️➡️📐

在本节课中,我们将继续学习彩色图像的坎尼边缘检测算法。上一节我们讨论了梯度计算,本节我们将重点介绍算法的后续三个核心步骤:非极大值抑制、双阈值处理和边缘跟踪。我们将详细解释每个步骤的原理与实现,并探讨如何优化参数以获得更好的边缘检测效果。

非极大值抑制 🎯

上一节我们得到了梯度幅值图像,但其中的边缘线条可能粗细不均。非极大值抑制的目标是细化边缘,只保留局部梯度幅值最大的点,从而得到单像素宽的边缘。

其核心思想是:真正的边缘中心点在其法线方向上应具有最大的梯度强度。因此,我们沿着边缘的法线方向进行比较,只保留梯度幅值为局部最大值的像素点,抑制其他非极大值点。

以下是该步骤的算法流程:

  1. 输入:梯度幅值图像 M 和梯度方向图像 theta
  2. 角度量化:将梯度方向 theta(范围 -π 到 π)量化为四个离散方向(0°, 45°, 90°, 135°),分别对应边缘的四个法线方向。
    • theta 为负值,则先加上 π 使其范围变为 0 到 π。
    • 将角度除以 π/4 并取整,再对 4 取模,得到 0, 1, 2, 3 四个方向索引。
  3. 比较与抑制:根据量化后的方向,检查每个像素点沿其法线方向的两个相邻像素。
    • 如果当前像素的梯度幅值大于这两个相邻像素的幅值,则保留该点。
    • 否则,将该点的梯度幅值置为零(抑制)。

通过此步骤,我们得到了更细、更清晰的边缘。

经过非极大值抑制后,边缘从粗细不均变为单像素宽度。

双阈值处理 🚦

经过非极大值抑制后,图像中仍包含许多由噪声或弱纹理产生的边缘点。双阈值处理旨在通过设置高低两个阈值,将像素点分为三类,以区分强边缘、弱边缘和非边缘。

以下是该步骤的具体操作:

  1. 设定阈值:设定一个高阈值 T_high 和一个低阈值 T_low。通常,T_high 用于识别强边缘像素,T_low 用于排除非边缘像素。
  2. 像素分类
    • 强像素:梯度幅值 >= T_high。这些被认为是确定的边缘部分。
    • 弱像素T_low <= 梯度幅值 < T_high。这些可能是边缘,也可能不是,需要进一步判断。
    • 非相关像素:梯度幅值 < T_low。这些直接被丢弃,不作为边缘。

在实际实现中,阈值通常通过比例来设定。例如:

  • T_high = high_ratio * max(gradient_magnitude)
  • T_low = low_ratio * T_high

其中 high_ratiolow_ratio 是需要根据图像内容调整的参数。静态阈值适用于简单情况,但对于复杂图像,研究动态阈值(根据图像局部特性调整阈值)可能获得更好效果。

双阈值处理将梯度幅值图像分割为强边缘、弱边缘和非边缘区域。

边缘跟踪(滞回阈值)🔗

双阈值处理后,强边缘像素基本构成了最终的边缘,但可能不连续。边缘跟踪(也称为滞回阈值)的目标是将与强边缘相连的弱边缘像素“连接”起来,形成完整、连续的边缘。

其规则非常简单:遍历所有弱像素,检查其8邻域(周围8个像素)。如果至少有一个邻域像素是强像素,则认为当前弱像素属于边缘,并将其提升为强像素。这个过程可以迭代进行,直到没有新的弱像素被转换。

以下是需要实现的 hysteresis 函数的核心逻辑伪代码:

def hysteresis(strong_edges, weak_edges):
    # strong_edges, weak_edges 是布尔型图像,标记了强像素和弱像素的位置
    edges = strong_edges.copy()
    # 遍历弱像素,检查其8邻域是否有强像素
    for each pixel in weak_edges:
        if any neighbor in 8-neighborhood is in strong_edges:
            edges[pixel] = True # 将该弱像素加入最终边缘
    return edges

通过这一步,许多孤立的弱像素被剔除,而与强边缘相连的弱像素被保留,从而得到了更完整、连贯的边缘轮廓。

经过边缘跟踪后,断裂的边缘被连接起来,形成了更完整的物体轮廓。

实验任务与扩展思路 💻

基于以上原理,你的项目任务如下:

  1. 完成步骤4和5:在提供的代码框架中,实现双阈值处理和边缘跟踪函数。
  2. 参数调优:尝试不同的 high_ratiolow_ratio 组合,观察它们对最终边缘检测结果的影响。这些参数没有通用值,需针对具体图像调整。
  3. 后处理(可选加分):实现一个修剪函数,用于消除孤立的强像素或长度过短(例如小于等于3个像素)的边缘片段。这需要设计巧妙的算法。
  4. 尝试不同的去噪方法(可选加分):除了课程提到的全变分去噪,可以尝试使用高斯滤波(imgaussfilt 函数)配合不同的 sigma 值进行降噪,并分析不同去噪方法和参数对边缘检测效果的影响。
  5. 探索新的梯度幅值计算方式(可选加分):当前代码(khan.m 第21行附近)从RGB三个通道的梯度中取最大值作为该点的梯度幅值。你可以思考并提出更好的融合多通道梯度信息的方法。

请将你的实现过程、参数选择、测试图像及结果、以及任何有价值的发现整理到最终报告中。

总结 📝

本节课我们一起学习了坎尼边缘检测算法的后半部分。我们深入探讨了非极大值抑制如何细化边缘,双阈值处理如何区分强、弱边缘像素,以及边缘跟踪如何连接边缘形成完整轮廓。掌握这些步骤是理解经典边缘检测算法的关键。通过完成实验任务和探索扩展思路,你将能更灵活地应用该算法,并可能对其做出改进。

018:切线的斜率 📈

在本节课中,我们将要学习微积分中的一个核心概念——微分,特别是如何通过求极限来找到函数在某一点的切线斜率。这对于理解变化率至关重要,是现代科学计算,尤其是人工智能领域的基础。

概述

微积分和线性代数在高级科学计算任务中扮演着关键角色。本章节将介绍微积分中一些精选但至关重要的主题。我们将探讨基本概念及其实际应用,因此你无需具备任何微积分预备知识。

现在,让我们进入第30.1节:微分与切线斜率。

30.1 微分:切线的斜率

函数 y = f(x) 的图像可以表示为一条曲线。在许多应用中,切线对于计算近似解起着至关重要的作用。

这里我们有两个核心问题:什么是切线?以及我们如何找到它?

让我们通过平均速度和瞬时速度的例子来理解。

从平均速度到瞬时速度

假设 f(t) 是一个关于时间的函数,用于测量在时间 t 内移动的距离。

平均速度的定义如下:在时间区间 [t₀, t₀ + h] 内的平均速度,等于在该时间段内移动的距离除以所花费的时间。用公式表示为:

平均速度 = [f(t₀ + h) - f(t₀)] / h

这个公式的几何意义可以通过图像来理解。在时间轴上,我们有 t₀t₀ + h 两个点。假设函数 y = f(t) 的图像如图所示,那么 f(t₀ + h)f(t₀) 就是曲线上对应的两个点。连接这两点的直线称为割线

如果我们绘制一个直角三角形,其底边长度为 (t₀ + h) - t₀ = h,高度为 f(t₀ + h) - f(t₀)。那么,上述公式计算出的商,正是这条割线的斜率,它代表了在该时间区间内的平均变化率。由于 f 测量的是距离,这个商就是该区间的平均速度

那么,瞬时速度呢?当 h 非常小时,在 t₀ 时刻的瞬时速度可以用这个商来近似。瞬时速度与曲线在该点的斜率有关。一旦我们选择非常小的 h,这个商就能很好地近似瞬时速度。

具体示例:自由落体

我们用一个具体函数来处理这个例子。假设一个物体自由落体,在 t 秒内下落的距离(英尺)由以下函数给出:

y = f(t) = 16t²

现在,设 t₀ = 1,我们需要找到平均速度,即差商。

以下是计算差商的一般过程。函数 f 定义为 f(t) = 16t²。因此,我们将 t₀ + h 代入函数:

差商 = [f(1 + h) - f(1)] / h = [16(1 + h)² - 16(1)²] / h

展开 (1 + h)² = 1 + 2h + h²,代入公式:

= [16(1 + 2h + h²) - 16] / h = [16 + 32h + 16h² - 16] / h = (32h + 16h²) / h

约去 h(假设 h ≠ 0):

= 32 + 16h

因此,对于给定的 h,平均速度的公式为 32 + 16h

估计瞬时速度

现在,估计在 t = t₀ = 1 时的瞬时速度。瞬时速度可以通过让 h 变得非常小来获得。

如果我们选择越来越小的 h 值(例如 0.1, 0.01, 0.001...),32 + 16h 这个值将趋近于 32。因为当 h 趋近于 0 时,16h 项也趋近于 0。

因此,我们可以估计在 t=1 时的瞬时速度为 32。这个值也正是曲线在 t=1 那一点的切线斜率。

通过编程验证

我们可以编写程序来验证这一过程。以下是一个概念性的程序描述(原示例使用特定符号计算包):

  1. 定义函数 f(t) = 16t² 和点 t₀ = 1
  2. 为绘图目的,设定一个包含 t₀ 的区间。
  3. 定义差商函数 Q(h) = [f(t₀ + h) - f(t₀)] / h
  4. 对于给定的 h,计算割线的斜率和方程。
  5. h 取一系列趋近于 0 的值(正数和负数),观察 Q(h) 的极限。
  6. 绘制函数曲线、不同 h 对应的割线以及最终的切线。

h 从正方向和负方向都趋近于 0 时,Q(h) 的值都趋近于 32。同时,图中蓝色的割线随着 h 变小,越来越接近红色的切线。切线的斜率就是瞬时速度 32

求切线方程:另一个例子

求曲线 y = x² 在点 x₀ = 2 处的切线方程。

要找到切线方程,我们需要知道一个点和一个斜率。

  • :当 x₀ = 2 时,y₀ = f(2) = 2² = 4。所以点是 (2, 4)
  • 斜率:通过差商并取极限来求。

函数是 f(x) = x²。差商为:
斜率 ≈ [f(2 + h) - f(2)] / h = [(2 + h)² - 4] / h

展开:= [4 + 4h + h² - 4] / h = (4h + h²) / h = 4 + h

h 趋近于 0 时,这个值趋近于 4。所以,切线的斜率 m = 4

现在,使用点斜式方程:
y - y₀ = m (x - x₀)
代入 y₀ = 4, m = 4, x₀ = 2
y - 4 = 4(x - 2)

整理得到切线方程:
y = 4x - 4

切线的正式定义

现在,我们正式定义曲线 y = f(x) 在点 P(x₀, f(x₀)) 处的切线斜率。

曲线在点 P 的切线斜率是以下差商的极限(如果该极限存在):

f‘(x₀) = lim_(h→0) [f(x₀ + h) - f(x₀)] / h

符号 f‘(x₀)dy/dx|_(x=x₀) 表示函数在 x₀ 处的导数,也就是切线的斜率。

因此,曲线在点 P 的切线方程可以写成点斜式:
y - f(x₀) = f‘(x₀) (x - x₀)

导数不存在的例子

是否所有点都有切线?考虑函数 y = |x³ - 1|x₀ = 1 处的情况。

这个函数的图像在 x=1 处有一个“尖角”。为了求斜率,我们需要检查极限 lim_(h→0) [f(1+h) - f(1)] / h

极限存在的条件是:当 h 从右侧(正数)趋近于 0 时的极限,和从左侧(负数)趋近于 0 时的极限都存在并且相等

对于这个函数在 x=1 处:

  • 从右侧(h > 0)逼近时,函数行为类似某条线,斜率是一个值。
  • 从左侧(h < 0)逼近时,函数行为类似另一条线,斜率是另一个值。

由于左右极限不相等,因此在该点的导数(切线斜率)不存在。从几何上看,在“尖角”处无法画出一条唯一的切线。这样的点称为函数的不可导点

我们可以通过编程绘制函数图像来直观地看到这个尖角。

总结

在本节课中,我们一起学习了:

  1. 微分的基本思想:通过让割线两点无限靠近,用极限来定义曲线在某一点的切线斜率。
  2. 导数的定义:函数 f(x) 在点 x₀ 的导数 f‘(x₀) 定义为差商当 h→0 时的极限,它几何上表示切线的斜率。
  3. 切线的应用:我们通过平均速度瞬时速度的例子,看到了导数如何描述瞬时变化率。并学会了如何利用导数求切线方程。
  4. 导数的存在性:导数存在的必要条件是左右极限相等。在函数图像出现“尖角”或间断的点,导数可能不存在。

这是微分学第一部分的内容,我们将在后续部分继续探讨导数的计算规则和更多应用。

019:第19讲 - 导数与微分规则 📚

在本节课中,我们将学习导数的定义以及一些核心的微分规则。我们将从导数的基本概念出发,逐步学习如何计算各种函数的导数,并利用这些规则解决实际问题。

概述

上一节我们介绍了如何通过差商和极限来估算瞬时斜率。本节中,我们将正式定义导数,并学习一系列计算导数的基本规则,包括幂法则、线性法则、乘积法则、商法则和链式法则。掌握这些规则是进行更复杂微积分运算的基础。

导数定义回顾 📖

导数的定义基于差商的极限。对于函数 f(x),其在点 x 的导数 f'(x) 定义为:

公式:
[
f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}
]

当这个极限存在时,我们称函数在点 x 可导。导数也常用符号 df/dx 表示。

通过定义求导数 🔍

我们可以直接使用定义来计算简单函数的导数。以下是计算步骤:

  1. 写出差商:[ \frac{f(x+h) - f(x)}{h} ]
  2. 简化表达式。
  3. 取极限 h → 0

让我们看几个例子:

例1:求 f(x) = x 的导数
差商为:[ \frac{(x+h) - x}{h} = \frac{h}{h} = 1 ]
取极限后,导数 f'(x) = 1

例2:求 f(x) = x³ 的导数

  1. 差商:[ \frac{(x+h)^3 - x^3}{h} ]
  2. 展开并简化:[ \frac{x^3 + 3x^2h + 3xh^2 + h^3 - x^3}{h} = 3x^2 + 3xh + h^2 ]
  3. 取极限 h → 0[ f'(x) = 3x^2 ]

通过类似方法,我们可以得到 f(x) = x² 的导数是 2x

幂法则 ⚡

从上面的例子,我们可以归纳出一个重要的规则——幂法则。

公式:
f(x) = x^n,其中 n 是任意实数,则其导数为:
[
f'(x) = n \cdot x^{n-1}
]

以下是应用幂法则的例子:

  • f(x) = x^6f'(x) = 6x^5
  • f(x) = x^{3/2}f'(x) = (3/2) x^{1/2}
  • f(x) = √x = x^{1/2}f'(x) = (1/2) x^{-1/2} = 1/(2√x)

基本微分法则 🧮

除了幂法则,我们还需要其他规则来处理更复杂的函数组合。

线性法则

如果函数是另两个函数的线性组合,其导数也是它们导数的线性组合。

公式:
F(x) = a \cdot u(x) + b \cdot v(x),其中 a, b 是常数,则:
[
F'(x) = a \cdot u'(x) + b \cdot v'(x)
]

乘积法则

两个函数乘积的导数,等于第一个函数的导数乘以第二个函数,加上第一个函数乘以第二个函数的导数。

公式:
F(x) = u(x) \cdot v(x),则:
[
F'(x) = u'(x) \cdot v(x) + u(x) \cdot v'(x)
]

例子:验证 f(x) = x^6 = x^2 \cdot x^4 的导数
使用乘积法则:
f'(x) = (2x) \cdot (x^4) + (x^2) \cdot (4x^3) = 2x^5 + 4x^5 = 6x^5
这与直接使用幂法则得到的结果一致。

商法则

两个函数之商的导数,公式如下。

公式:
F(x) = u(x) / v(x),则:
[
F'(x) = \frac{u'(x) \cdot v(x) - u(x) \cdot v'(x)}{[v(x)]^2}
]

商法则可以看作是乘积法则的一个特例(将 u/v 写成 u \cdot v^{-1} 并应用乘积法则和链式法则)。

链式法则

链式法则是处理复合函数导数的核心规则。如果 yu 的函数,而 u 又是 x 的函数,那么 y 关于 x 的导数等于 y 关于 u 的导数乘以 u 关于 x 的导数。

公式:
y = f(g(x)),令 u = g(x),则:
[
\frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx} \quad \text{或} \quad f'(g(x)) \cdot g'(x)
]

例子:求 g(x) = (3x+1)^{10} 的导数

  1. u = 3x+1,则 g(x) = u^{10}
  2. 应用链式法则:g'(x) = 10u^9 \cdot u'
  3. 计算 u' = 3
  4. 代入 ug'(x) = 10(3x+1)^9 \cdot 3 = 30(3x+1)^9

链式法则在形式上可以直观理解为“分子分母同时乘以 du”:
[
\frac{dy}{dx} = \frac{dy}{du} \cdot \frac{du}{dx}
]

应用:寻找水平切线 📉

导数的一个直接应用是寻找函数图像上切线斜率为零(即水平切线)的点。

问题:曲线 y = x^4 - 2x^2 + 2 是否有水平切线?

  1. 求导:y' = 4x^3 - 4x
  2. 令导数等于零以找到水平切点:4x^3 - 4x = 4x(x^2 - 1) = 0
  3. 解得:x = 0, 1, -1
  4. 代入原函数求对应 y 值:
    • x=0 时,y=2
    • x=1 时,y=1
    • x=-1 时,y=1
  5. 因此,曲线在点 (0,2), (1,1), (-1,1) 处有水平切线。利用这些关键点(包括与y轴的交点(0,2)和对称性),我们可以大致勾勒出函数的图像(呈W形)。

符号计算与规则验证 💻

我们可以通过计算机程序(如MATLAB)进行符号计算,直接使用导数定义或微分规则来验证结果。程序会:

  1. 为给定函数构造差商。
  2. 计算当 h → 0 时的极限。
  3. 简化表达式。
    通过这种方式,我们可以系统地验证幂法则、线性法则、乘积法则、商法则和链式法则的正确性。

综合练习 ✏️

尝试使用学到的规则计算复杂函数的导数,例如:
求 f(x) = (x^2 + 1)^3 \cdot √x 的导数
这需要综合运用乘积法则和链式法则。

总结

本节课中我们一起学习了:

  1. 导数的正式定义:基于差商的极限。
  2. 幂法则:计算 x^n 型函数导数的通用公式。
  3. 基本微分法则
    • 线性法则:处理函数的线性组合。
    • 乘积法则:处理两个函数的乘积。
    • 商法则:处理两个函数的商。
    • 链式法则:处理复合函数,是微积分中极其重要的工具。
  4. 导数的应用:通过令导数 f'(x)=0 可以找到函数图像上的水平切点,这有助于分析函数性质和绘制草图。

熟练掌握这些微分规则,是后续学习更高级微积分概念和应用的基础。

020:变量与基函数的变化

在本节课中,我们将学习工程与数学中的两个核心技巧:变量替换与基函数表示法。我们将通过具体例子理解它们如何简化问题,并探讨基函数在向量空间中的定义与作用。


30.2 基函数与泰勒级数

让我们从一个观察开始。在工程研发的历史中,有两种主要的数学技巧:变量替换基函数表示法

变量替换是用于简化问题的基本技巧。而一个函数可以通过一组基函数的线性组合来表示或近似。这就是基函数方法在工程研发中如此流行的原因。

变量替换示例

上一节我们介绍了变量替换的概念,本节中我们来看一个具体的例子。

问题:求解以下方程组,其中 xy 为正数,且 y > x

2x + 2y = xy
x + y = 4

解法:通过引入变量替换,我们可以更轻松地求解。令:

s = xy
t = x + y

利用新变量,原方程组可以简化为:

2t = s
t = 4

这是一个更简单的方程组。由第二个方程直接可得 t = 4,代入第一个方程得 s = 8

现在,我们回到原变量:

x + y = t = 4
xy = s = 8

我们需要找到满足 y > x > 0 的解。解这个二次方程组,可以得到两组可能的解,但只有一组满足所有条件(x=2, y=2 不满足 y > xx≈1.55, y≈5.15 等需验证)。最终满足 y > x 且均为正数的唯一解x = 2, y = 2?等等,计算有误,我们重新计算。

x+y=4xy=8,构造二次方程:z^2 - 4z + 8 = 0。其判别式为 16 - 32 = -16 < 0,无实数解。这提示我们最初的替换或推导可能有问题。让我们检查原方程。

原方程为:

  1. 2x + 2y = xy
  2. x + y = 4

将方程2代入方程1:2*4 = xy => 8 = xy。所以方程组等价于:

x + y = 4
xy = 8

这正是我们上面得到的方程组,它确实没有实数解。因此,在给定的正数且 y > x 条件下,原方程组无解。这个例子说明了变量替换如何帮助我们发现方程组的性质(例如无解),即使它没有给出最终数值解。

在微积分,特别是向量微积分中,变量替换与高维度的微分和积分密切相关。理解变量替换对于有效处理多维问题至关重要。

基函数的概念

之前我们讨论了变量替换,现在我们来探讨基函数。基函数是构建更复杂函数或向量的“积木”。

定义:向量空间 V 的一组是一个向量或函数的集合,它满足两个条件:

  1. 线性无关:集合中的任何一个元素都不能表示为集合中其他元素的线性组合。
  2. 张成空间:该空间 V 中的每一个向量或函数,都可以表示为这组基的线性组合。

以下是理解基函数的例子:

1. 多项式空间的单项式基
考虑所有次数不超过 n 的多项式构成的空间 P_n。一组自然的基函数是单项式

{1, x, x^2, ..., x^n}

任何 P_n 中的多项式 p(x) 都可以写成这些基函数的线性组合:

p(x) = a_0 * 1 + a_1 * x + a_2 * x^2 + ... + a_n * x^n

其中 a_0, a_1, ..., a_n 是系数。这组基函数是线性无关的(例如,x^2 不能由 1x 组合得到),并且张成了整个 P_n 空间。

2. R^n 空间的标准基
n 维欧几里得空间 R^n 中,标准基由以下标准单位向量组成:

e_1 = (1, 0, 0, ..., 0)
e_2 = (0, 1, 0, ..., 0)
...
e_n = (0, 0, 0, ..., 1)

R^n 中的任意向量 x = (x_1, x_2, ..., x_n) 都可以表示为:

x = x_1 * e_1 + x_2 * e_2 + ... + x_n * e_n

这组向量线性无关且张成 R^n

3. R^2 空间的另一组基
标准基 {e_1, e_2} 并非唯一的选择。例如,以下两个向量也是 R^2 的一组基:

u = (1/√2, 1/√2)
v = (-1/√2, 1/√2)

这两个向量长度均为1,并且互相垂直(夹角90度)。平面上的任何点都可以表示为 uv 的线性组合,并且它们线性无关。这表明,对于一个给定的向量空间,可以存在许多组不同的基。


总结

本节课中我们一起学习了:

  1. 变量替换:通过引入新变量简化问题,是解决复杂方程和进行积分计算的有力工具。
  2. 基函数:构成向量空间“骨架”的线性无关函数或向量集合。任何空间中的元素都可以唯一地表示为基函数的线性组合。我们以多项式空间和欧几里得空间为例,介绍了单项式基标准基

理解如何改变变量以及如何选择和使用基函数,是进行有效数学建模、简化问题以及后续学习更高级人工智能算法(如傅里叶变换、主成分分析等)的重要基础。

021:第21讲 - 幂级数与比率判别法 📊

概述

在本节课中,我们将学习幂级数的基本概念,以及如何使用比率判别法来判断一个幂级数是否收敛。幂级数是表示和分析函数的有力工具,在工程和科学计算中应用广泛。

幂级数的定义

上一节我们介绍了解析函数的概念,本节中我们来看看如何用幂级数来表示它们。

解析函数是可以无限次微分的函数,因此它们是更平滑的函数。解析函数的单项式基由以下集合给出:1, x, x², ...

幂级数定义如下:

  • 关于 x = 0 的幂级数具有以下形式:
    c₀ + c₁x + c₂x² + ... + cₙxⁿ + ...
  • 关于 x = a 的幂级数具有以下形式:
    c₀ + c₁(x - a) + c₂(x - a)² + ... + cₙ(x - a)ⁿ + ...

在这个表达式中,a 被称为级数的中心,而 c₀, c₁, c₂, ... 是系数。

一个重要的例子:几何级数

以下是幂级数的一个经典例子。

取所有系数 cₙ = 1,我们得到以下几何级数:
∑ (n=0 到 ∞) xⁿ

这个级数收敛于函数 1/(1 - x),但仅在 |x| < 1 的区间内成立。你可以通过将 (1 - x) 乘以级数 1 + x + x² + ... 来验证,结果会得到 1。

对于函数 y = 1/(1 - x),我们可以用多项式来逼近它。从常数项 1 开始,然后加上线性项 x,再加上二次项 x²,随着我们使用更高阶的多项式,它能更好地逼近原函数。然而,这种逼近仅在 x 介于 -1 和 1 之间时才有效。

收敛区间与比率判别法

从上面的例子可以得出两个关键点:

  1. 函数可以用幂级数逼近。
  2. 幂级数可能只在某个半径为 R 的区间内收敛。

因此,对于给定的幂级数,我们首先需要测试它是否收敛。如果级数不收敛,那么讨论它的值就没有意义。

比率判别法是判断级数收敛性的一种常用方法。

∑ aₙ 是一个级数,并假设以下极限存在:
L = lim (n→∞) |aₙ₊₁ / aₙ|

以下是比率判别法的结论:

  • 如果 L < 1,则级数绝对收敛。
  • 如果 L > 1,则级数发散。
  • 如果 L = 1,则该测试无法得出结论(即无法判断)。

让我们通过例子来应用这个判别法。

应用比率判别法

以下是需要判断收敛区间的幂级数。

例1: ∑ (n=1 到 ∞) ((-1)ⁿ⁻¹ xⁿ) / n

我们应用比率判别法。计算相邻项的比率绝对值:
|aₙ₊₁ / aₙ| = | [xⁿ⁺¹/(n+1)] / [xⁿ/n] | = |x| * [n/(n+1)]

n → ∞ 时,n/(n+1) → 1。因此,极限 L = |x|

  • |x| < 1 时,级数收敛。
  • |x| > 1 时,级数发散。
  • |x| = 1(即 x = 1x = -1)时,测试无效,需要单独分析。
    • x = 1 时,级数变为 1 - 1/2 + 1/3 - 1/4 + ...,这是一个收敛的交错级数。
    • x = -1 时,级数变为 -1 - 1/2 - 1/3 - 1/4 - ...,这是一个发散级数(负的调和级数)。

因此,该幂级数的收敛区间为 [-1, 1),即包含 -1 但不包含 1。

例2: ∑ (n=0 到 ∞) xⁿ / n!

我们再次应用比率判别法:
|aₙ₊₁ / aₙ| = | [xⁿ⁺¹/(n+1)!] / [xⁿ/n!] | = |x| / (n+1)

n → ∞ 时,|x|/(n+1) → 0。因为极限 L = 0(总是小于1),所以对于 所有实数 x,该级数都收敛。这个级数实际上就是函数 的幂级数展开。

幂级数的微分与积分

幂级数表示法的一个巨大优势是,在其收敛区间内,我们可以方便地进行逐项微分和积分。

假设一个幂级数 f(x) = ∑ cₙ (x - a)ⁿ 的收敛半径为 R。那么在区间 |x - a| < R 内:

  • 导数f'(x) = ∑ n * cₙ (x - a)ⁿ⁻¹
  • 积分∫ f(x) dx = C + ∑ [cₙ / (n+1)] (x - a)ⁿ⁺¹

这种逐项操作的能力在许多应用中非常方便。

实例:指数函数的导数

让我们用幂级数来验证指数函数的一个基本性质。

已知 eˣ = ∑ (n=0 到 ∞) xⁿ / n!
我们对其逐项求导:
d/dx (eˣ) = 0 + 1 + (2x)/(2!) + (3x²)/(3!) + (4x³)/(4!) + ...
简化后得到:= 1 + x + x²/2! + x³/3! + ...
这正是 本身的级数。因此,我们通过幂级数证明了 d/dx (eˣ) = eˣ

在实际工程应用中,我们经常将无穷级数截断为有限项多项式,以便进行计算或分析。这使得幂级数技术极其有用。

总结

本节课中我们一起学习了:

  1. 幂级数的定义和形式,包括其中心的概念。
  2. 使用比率判别法来确定幂级数的收敛区间。
  3. 幂级数在其收敛区间内可以逐项微分和积分的强大性质。
  4. 通过 的例子,展示了如何应用这些概念。

在接下来的部分,我们将探讨如何为给定函数寻找其幂级数表示,即泰勒级数,这将是我们进一步学习的主题。

022:Lecture 22 - 泰勒级数展开 📈

在本节课中,我们将学习如何将一个给定的函数表示为无穷级数,即泰勒级数。泰勒级数是数学分析中的核心工具,它通过多项式来近似复杂的函数,这对于数学家和科学家都极为有用。我们将从定义出发,学习如何推导泰勒级数,并了解其与麦克劳林级数的关系,最后探讨如何利用泰勒多项式进行函数近似以及误差分析。


泰勒级数的定义与推导 🔍

上一节我们介绍了收敛的幂级数可以定义一个函数。本节中,我们来看看如何将一个给定的函数表达为一个无穷级数,即泰勒级数。

如果一个幂级数被用来表示一个函数,那么这个幂级数就被称为该函数的泰勒级数。在许多情况下,泰勒级数为函数提供了有用的多项式近似。

假设一个函数 F(x)x = a 点附近可以表示为一个幂级数,且该级数的收敛半径大于零。那么我们可以将其写作:

F(x) = c₀ + c₁(x-a) + c₂(x-a)² + c₃(x-a)³ + ...

这里的问题是:系数 cₙ 是什么?我们如何找到它们?

为了找到系数,我们对上述级数进行逐项求导。

  • 一阶导数:F'(x) = c₁ + 2c₂(x-a) + 3c₃(x-a)² + ...
  • 二阶导数:F''(x) = 2c₂ + 6c₃(x-a) + ...
  • 以此类推,第 n 阶导数为:F⁽ⁿ⁾(x) = n! * cₙ + 包含 (x-a) 的项

现在,我们令 x = a。此时,所有包含 (x-a) 的项都变为零。

于是我们得到:

  • F(a) = c₀
  • F'(a) = 1! * c₁
  • F''(a) = 2! * c₂
  • F⁽ⁿ⁾(a) = n! * cₙ

由此,我们可以解出系数 cₙ

cₙ = F⁽ⁿ⁾(a) / n!


泰勒级数与麦克劳林级数 📐

基于上述推导,我们可以正式定义泰勒级数。

我们假设函数 F 在包含点 a 的某个区间内无限次可微。那么,Fx = a 处生成的泰勒级数由下式给出:

F(x) = Σ [n=0 to ∞] [ F⁽ⁿ⁾(a) / n! ] * (x - a)ⁿ

这是一个求和符号表示,实际上就是逐项写出:F(a) + F'(a)(x-a) + [F''(a)/2!](x-a)² + ...

当展开点 a = 0 时,这个特殊的泰勒级数被称为麦克劳林级数。它是泰勒级数的一个特例,在数学理论中非常常见。


常见函数的泰勒级数示例 📚

以下是几个常见函数在 x=0(即麦克劳林级数)处的展开式:

  • 几何级数1/(1-x) = 1 + x + x² + x³ + ... (当 |x| < 1 时收敛)
  • 指数函数eˣ = 1 + x + x²/2! + x³/3! + ... (对所有 x 收敛)
  • 余弦函数cos(x) = 1 - x²/2! + x⁴/4! - x⁶/6! + ... (对所有 x 收敛)
  • 正弦函数sin(x) = x - x³/3! + x⁵/5! - x⁷/7! + ... (对所有 x 收敛)
  • 自然对数ln(1+x) = x - x²/2 + x³/3 - x⁴/4 + ... (当 -1 < x ≤ 1 时收敛)
  • 反正切函数arctan(x) = x - x³/3 + x⁵/5 - x⁷/7 + ... (当 |x| ≤ 1 时收敛)

上表中所示的收敛区间可以通过比值判别法等方法来验证。


泰勒多项式与近似 💡

理解了无穷级数后,我们来看看如何用它进行实际计算。由于我们无法处理无穷项,因此需要截断级数,使用有限项的多项式来近似函数。

我们假设函数 F 在包含点 a 的区间内 n 次可微。那么,F 关于 an 阶泰勒多项式定义为泰勒级数的前 n+1 项(直到 n 次项):

Pₙ(x) = F(a) + F'(a)(x-a) + [F''(a)/2!](x-a)² + ... + [F⁽ⁿ⁾(a)/n!](x-a)ⁿ

这个多项式必须是对原函数的一个良好近似。为了分析和验证近似的准确性,我们需要一个工具来估计截断后产生的误差。


误差分析:泰勒定理与拉格朗日余项 ⚖️

以下定理,即带拉格朗日余项的泰勒定理,是误差分析的核心。

假设函数 F 及其直到 n+1 阶的导数在区间内连续。那么,对于区间内的任意 x,有:

F(x) = Pₙ(x) + Rₙ(x)

其中,Pₙ(x)n 阶泰勒多项式,而余项 Rₙ(x)(即误差)由拉格朗日形式给出:

Rₙ(x) = [ F⁽ⁿ⁺¹⁾(c) / (n+1)! ] * (x - a)ⁿ⁺¹

这里的 c 是介于 ax 之间的某个数。

这个定理非常有用,因为它将无穷级数截断后的误差表达为一个单一的项。只要我们能够控制或估计这个余项的大小,就能确信泰勒多项式是原函数的一个良好近似。


应用实例:余弦函数的近似 📉

让我们以函数 F(x) = cos(x)x₀ = 0 处为例,确定其二阶和三阶泰勒多项式。

首先计算各阶导数在0点的值:

  • F(0) = cos(0) = 1
  • F'(0) = -sin(0) = 0
  • F''(0) = -cos(0) = -1
  • F'''(0) = sin(0) = 0
  • F⁽⁴⁾(0) = cos(0) = 1

代入泰勒多项式公式:

  • 二阶泰勒多项式P₂(x) = 1 + 0*x + (-1/2!)*x² = 1 - x²/2
  • 三阶泰勒多项式:由于余弦是偶函数,其三阶项系数为0,所以 P₃(x) = 1 - x²/2(与P₂相同)。

其误差(拉格朗日余项)可以表示为:
对于P₂,R₂(x) = [ -sin(c) / 3! ] * x³ (实际上,由于余弦函数的对称性,更精确的余项应使用四阶导数)
更准确的余项(对于P₂,使用下一阶非零导数)为:R₂(x) = [ cos(c) / 4! ] * x⁴

x 很小时(即在零点附近),cos(c) 大约为1,误差项约为 x⁴/24。这是一个四阶小量,因此二阶多项式 1 - x²/2 在原点附近是对 cos(x) 的极好近似。从函数图像上也可以看到,在零点附近,这个二次多项式与余弦曲线贴合得非常紧密。


泰勒定理与中值定理的联系 🔗

最后,我们探讨泰勒定理的一个特例如何与微积分中的另一个重要定理相联系。

考虑泰勒定理中令 n = 0 的情况。此时,定理表述为:存在介于 ab 之间的某个 c,使得

F(b) = F(a) + F'(c) * (b - a)

将这个等式重写一下:

F'(c) = [ F(b) - F(a) ] / (b - a)

仔细观察这个等式:右边是函数在区间 [a, b] 上的平均变化率(斜率),而左边是函数在某一点 c 的瞬时变化率(导数)。这正是微分中值定理的表述。

其几何意义是:对于可微函数,在区间内至少存在一点,使得该点的切线斜率等于区间两端点连线的斜率。这揭示了泰勒定理(即使是零阶情形)蕴含着深刻的几何直观。


本节课中,我们一起学习了泰勒级数的核心思想:如何用无穷多项式级数精确表示一个函数,以及如何用有限项的泰勒多项式来近似函数。我们掌握了泰勒级数和麦克劳林级数的定义与推导方法,认识了几个常见函数的展开式,并学习了通过带拉格朗日余项的泰勒定理来分析近似误差。最后,我们还看到了泰勒定理与微分中值定理之间的深刻联系。泰勒级数是连接离散多项式与连续函数的有力桥梁,在数学、物理和工程领域有着广泛的应用。

023:Lecture 23 - 多项式插值拉格朗日形式

概述

在本节课中,我们将要学习多项式插值,特别是其拉格朗日形式。我们将了解如何通过一组给定的数据点构造一个多项式,并学习相关的核心定理和构造方法。


3.3 多项式插值

维尔斯特拉斯逼近定理

假设函数 f 在区间 [a, b] 上连续(记作 f ∈ C[a, b])。那么,对于任意给定的正数 ε,都存在一个多项式 P,使得对于区间内所有的 x,函数 f 与多项式 P 的差值都小于 ε

公式表示:
对于任意 ε > 0,存在多项式 P,使得:
|f(x) - P(x)| < ε, 对所有 x ∈ [a, b] 成立。

这个定理意味着,任何连续函数都可以用一个多项式来任意精确地逼近。


多项式插值定理

x₀, x₁, ..., xₙn+1 个互不相同的实数。那么,对于任意给定的一组值 y₀, y₁, ..., yₙ,都存在一个唯一的多项式 Pₙ,其次数最多为 n,并且满足 Pₙ(xᵢ) = yᵢ 对所有 i 成立。

公式表示:
给定 n+1 个点 (xᵢ, yᵢ),存在唯一的次数 ≤ n 的多项式 Pₙ(x),使得:
Pₙ(xᵢ) = yᵢi = 0, 1, ..., n

该定理的核心思想是:对于给定的 n+1 个点,满足条件的插值多项式是唯一的。无论你用何种方法构造,最终得到的多项式都是同一个。


一个插值多项式的例子

上一节我们介绍了插值定理,本节中我们通过一个具体例子来看看如何构造插值多项式。

问题: 求一个通过以下三个点的二次插值多项式 P₂(x)
点1: (-2, 3)
点2: (0, -1)
点3: (1, 0)

解法:
我们设二次多项式为一般形式:P₂(x) = ax² + bx + c

  1. 将点 (0, -1) 代入:
    P₂(0) = a0 + b0 + c = -1c = -1
  2. 将点 (-2, 3) 和 (1, 0) 代入:
    • P₂(-2) = a4 + b(-2) - 1 = 34a - 2b = 42a - b = 2
    • P₂(1) = a1 + b1 - 1 = 0a + b = 1
  3. 解方程组:
    2a - b = 2
    a + b = 1
    两式相加得:3a = 3a = 1
    代入得:1 + b = 1b = 0

因此,所求的插值多项式为:
P₂(x) = x² - 1


拉格朗日形式的插值多项式

在众多插值多项式的表达形式中,拉格朗日形式是最流行的一种。它提供了一种系统化的构造方法。

假设我们给定 n+1 个数据点 (xₖ, yₖ),其中 k = 0, 1, ..., n,且所有的 x 值互不相同。

拉格朗日插值多项式寻求以下形式:
Pₙ(x) = y₀ * Lₙ₀(x) + y₁ * Lₙ₁(x) + ... + yₙ * Lₙₙ(x) = Σ [yₖ * Lₙₖ(x)],求和从 k=0n

这里,Lₙₖ(x) 是一组依赖于节点 x₀, x₁, ..., xₙ 的多项式,称为基函数拉格朗日基多项式

例如,对于 n=2(三个点),拉格朗日形式为:
P₂(x) = y₀ * L₂₀(x) + y₁ * L₂₁(x) + y₂ * L₂₂(x)


如何构造拉格朗日基多项式 Lₙₖ(x)

为了理解如何构造 Lₙₖ(x),我们从一种特殊情况开始考虑。

假设所有 y 值中,只有 yᵢ = 1,而其他所有的 y 值都为 0。那么,根据拉格朗日形式,多项式简化为:
Pₙ(x) = Lₙᵢ(x)

另一方面,这个插值多项式必须满足数据点的条件:在 xⱼ 处,其值必须等于 yⱼ。这意味着:

  • j = i 时,Pₙ(xᵢ) = Lₙᵢ(xᵢ) = 1
  • j ≠ i 时,Pₙ(xⱼ) = Lₙᵢ(xⱼ) = 0

用克罗内克δ函数表示就是:Lₙᵢ(xⱼ) = δᵢⱼ
满足这种性质的多项式 Lₙᵢ(x) 被称为基数函数

基数函数的图像特征:
n=2 为例,有三个点 x₀, x₁, x₂

  • L₂₀(x)x₀ 处值为1,在 x₁x₂ 处值为0。其图像是一个在 x₁, x₂ 处与x轴相交的二次曲线,在 x₀ 处达到峰值1。
  • L₂₁(x)L₂₂(x) 具有类似的性质,分别在 x₁x₂ 处为1,在其他节点处为0。

基数函数的构造公式

现在,我们来看如何用公式构造这些基数函数。我们以构造 Lₙ₀(x) 为例。

目标: Lₙ₀(x₀) = 1,且对于 j = 1, 2, ..., n,有 Lₙ₀(xⱼ) = 0

  1. 为了在 x₁, x₂, ..., xₙ 处函数值为0,Lₙ₀(x) 必须包含因子 (x - x₁)(x - x₂)...(x - xₙ)
  2. 因此,我们可以设 Lₙ₀(x) = C * (x - x₁)(x - x₂)...(x - xₙ),其中 C 是待定常数。
  3. 利用条件 Lₙ₀(x₀) = 1 来求解 C
    1 = C * (x₀ - x₁)(x₀ - x₂)...(x₀ - xₙ)
    由于所有 x 互异,分母不为零,解得:
    C = 1 / [(x₀ - x₁)(x₀ - x₂)...(x₀ - xₙ)]
  4. 因此,Lₙ₀(x) 的最终形式为:
    Lₙ₀(x) = [(x - x₁)(x - x₂)...(x - xₙ)] / [(x₀ - x₁)(x₀ - x₂)...(x₀ - xₙ)]

推广到一般的 Lₙᵢ(x),其构造公式为:

Lₙᵢ(x) = Π [(x - xⱼ) / (xᵢ - xⱼ)],连乘符号 Πj = 0 到 n 进行,但 j ≠ i

公式解读:

  • 分子:(x - xⱼ) 对所有 j ≠ i 的连乘。这确保了在除 xᵢ 外的所有节点处,函数值为0。
  • 分母:(xᵢ - xⱼ) 对所有 j ≠ i 的连乘。这是一个常数,用于确保在 x = xᵢ 时,函数值恰好为1。

应用示例

理解了构造原理后,我们通过两个例子来实践拉格朗日插值法的应用。

示例1:线性插值

问题: 确定通过点 (2, 4)(5, 1) 的拉格朗日插值多项式。

解答:
这里 n=1,有两个点:(x₀, y₀) = (2, 4)(x₁, y₁) = (5, 1)

首先构造基数函数:

  • L₁₀(x) = (x - x₁) / (x₀ - x₁) = (x - 5) / (2 - 5) = -(x - 5)/3
  • L₁₁(x) = (x - x₀) / (x₁ - x₀) = (x - 2) / (5 - 2) = (x - 2)/3

然后,拉格朗日插值多项式为:
P₁(x) = y₀ * L₁₀(x) + y₁ * L₁₁(x) = 4 * [-(x - 5)/3] + 1 * [(x - 2)/3]
简化后可得最终表达式。


示例2:二次插值

问题: 对于函数 f(x) = 1/x,使用节点 x₀=2, x₁=4, x₂=5 构造二次拉格朗日插值多项式 P₂(x)

解答:
首先根据 f(x) 计算对应的 y 值:

  • y₀ = f(2) = 1/2
  • y₁ = f(4) = 1/4
  • y₂ = f(5) = 1/5

接着,构造三个二次基数函数 L₂₀(x), L₂₁(x), L₂₂(x)。以 L₂₀(x) 为例:
L₂₀(x) = [(x - x₁)(x - x₂)] / [(x₀ - x₁)(x₀ - x₂)] = [(x - 4)(x - 5)] / [(2 - 4)(2 - 5)] = [(x - 4)(x - 5)] / 6

类似地,可以构造出 L₂₁(x)L₂₂(x)

最后,插值多项式为:
P₂(x) = y₀ * L₂₀(x) + y₁ * L₂₁(x) + y₂ * L₂₂(x) = (1/2)L₂₀(x) + (1/4)L₂₁(x) + (1/5)*L₂₂(x)

将各个基数函数的表达式代入并化简,即可得到 P₂(x) 的最终形式。


使用计算机程序实现

对于更复杂或更高阶的插值问题,我们可以借助计算机程序。虽然MATLAB可以处理,但Python因其灵活性(特别是使用sympy等符号计算库)而更受欢迎。

以下是一个用Python实现拉格朗日插值的示例代码框架:

def lagrange_interpolation(x_values, y_values):
    """
    计算拉格朗日插值多项式。
    参数:
        x_values: 节点x的列表
        y_values: 对应节点y的列表
    返回:
        插值多项式(符号表达式)
    """
    n = len(x_values)
    polynomial = 0
    for i in range(n):
        # 构造基数函数 L_i(x)
        basis = 1
        for j in range(n):
            if j != i:
                basis *= (x - x_values[j]) / (x_values[i] - x_values[j])
        # 将该项加到多项式上
        polynomial += y_values[i] * basis
    return polynomial

# 示例:使用点 (2, 1/2), (4, 1/4), (5, 1/5)
x_vals = [2, 4, 5]
y_vals = [1/2, 1/4, 1/5]
P2 = lagrange_interpolation(x_vals, y_vals)
print("拉格朗日插值多项式 P2(x):")
print(P2)
print("简化后:")
print(simplify(P2))

这段代码的核心逻辑完全遵循拉格朗日公式:外层循环遍历每个数据点以构造对应的项 yᵢ * Lₙᵢ(x),内层循环则用于计算每个基数函数 Lₙᵢ(x)。运行程序后,会输出原始表达式和简化后的结果。一旦实现,这个函数就可以方便地用于各种插值计算。


总结

本节课中我们一起学习了多项式插值的核心内容。我们首先了解了维尔斯特拉斯逼近定理和多项式插值唯一性定理。然后,重点学习了插值多项式的拉格朗日形式,包括其思想、基数函数的构造原理与公式。通过具体示例,我们实践了线性与二次拉格朗日插值多项式的求解过程。最后,我们还探讨了如何使用Python编程来实现这一算法,以处理更实际的问题。拉格朗日插值法提供了一种直观且系统的方法,通过一组基函数的线性组合来精确穿过所有给定数据点。

024:多项式插值误差定理(第二部分)📊

在本节课中,我们将要学习多项式插值误差定理。上一节我们介绍了多项式插值并构建了拉格朗日形式的插值多项式。本节中,我们来看看一个核心问题:插值多项式 P_N 在近似函数 f 时,其误差是多少?我们将通过误差定理来回答这个问题。

多项式插值误差定理

定理:设函数 f 在区间 [a, b] 上连续且 M+1 次可微。设 P_N 是函数 f[a, b] 区间内 N+1 个互异点 x_0, ..., x_N 上的插值多项式。那么,对于区间内的每一个 x,都存在一个依赖于 x 的数 ξ(x),使得插值误差由以下公式给出:

f(x) - P_N(x) = [f^{(N+1)}(ξ(x)) / (N+1)!] * Π_{i=0}^{N} (x - x_i)

这个定理可以与泰勒定理进行比较。在泰勒定理中,拉格朗日余项包含 (x-a)^{N+1} 项。而在插值误差公式中,我们看到的是一系列 (x - x_i) 的乘积。当这个乘积项的和很小时,多项式就能很好地近似原函数。

示例分析:近似 sin(x)

让我们通过一个具体例子来理解误差估计。考虑函数 f(x) = sin(x),我们将在区间 [-1, 1] 上用5次多项式(即 N=5,需要6个插值点)来近似它。

我们选择等间距的6个点:x_i = -1, -0.6, -0.2, 0.2, 0.6, 1。每个子区间的长度 h = 0.4

为了测量最大可能误差,我们应用误差定理。这里 N=5,所以我们需要 sin(x) 的6阶导数。由于 sin(x) 的任意阶导数的绝对值不超过1,我们可以设 M = max|f^{(6)}(ξ)| = 1

接着,我们计算乘积项 Π (x - x_i) 在区间 [-1, 1] 上的绝对最大值。通过计算(例如使用MATLAB或Python),可以求得这个最大值为 G_max ≈ 0.0170

因此,误差的上界为:

|f(x) - P_5(x)| ≤ (1 / 6!) * G_max ≈ (1/720) * 0.0170 ≈ 2.36e-5

这表明,用5次插值多项式在给定点上近似 sin(x),最大误差大约为 2.36 × 10^{-5},近似效果相当好。

等距节点下的误差定理

当插值节点在区间 [a, b]等距分布时,误差估计有一个更简便的形式。

定理(等距节点):设节点 x_i = a + i*h,其中 i=0,...,N,步长 h = (b-a)/N。那么插值误差满足以下上界:

|f(x) - P_N(x)| ≤ (M / 4(N+1)) * (h^{N+1} / N^N)

其中,M 是函数 fN+1 阶导数在区间 [a, b] 上的绝对值的最大值,即 M = max_{ξ∈[a,b]} |f^{(N+1)}(ξ)|

这个公式的证明过程较为复杂,此处不展开,但它的优点是在等距情况下能更方便地估算误差上界。

等距节点示例:再探 sin(x)

我们再次使用函数 f(x) = sin(x),在区间 [-1, 1] 上用6个等距分布的点进行5次多项式插值。

此时,步长 h = (1 - (-1)) / 5 = 0.4sin(x) 的6阶导数的最大值 M 仍为1。将数值代入等距节点误差公式:

误差上界 ≤ (1 / 4*(5+1)) * (0.4^{6} / 5^{5})

通过Python计算可得:

import math
h = 0.4
N = 5
M = 1
upper_bound = (M / (4*(N+1))) * (h**(N+1) / (N**N))
print(upper_bound) # 输出约为 1.4e-4

计算得到的误差上界约为 1.4 × 10^{-4}

这个值比之前通过通用定理计算出的精确上界(2.36e-5)要大。这是因为等距节点公式给出的是一个更宽松但计算更方便的上界。它保证了实际误差绝不会超过这个值,虽然可能没有通用公式给出的界限那么紧。

总结

本节课中我们一起学习了多项式插值误差定理。

  • 我们首先介绍了通用的多项式插值误差公式,它给出了插值多项式与原函数之间差异的精确表达式。
  • 然后,我们通过近似 sin(x) 的例子演示了如何利用该定理计算误差的理论上界
  • 接着,我们介绍了在节点等距分布这一特殊情况下,一个更简便的误差上界公式。
  • 最后,我们比较了两种方法在同一个例子中的应用,指出等距节点公式虽然方便,但给出的上界可能更宽松。

理解这些误差估计方法,有助于我们在使用多项式插值时,对近似的精度有一个量化的认识,并能在便利性与精确性之间做出权衡。

025:第25讲 - 数值微分与有限差分公式 🧮

在本节课中,我们将学习数值微分的基本概念,特别是如何使用有限差分公式来近似计算函数的导数。我们将从最简单的两点公式开始,逐步扩展到更高阶的多点公式,并分析它们的精度。

概述 📋

数值微分是计算函数导数近似值的一种方法,当无法或难以获得解析导数时非常有用。其核心思想是利用函数在一些离散点上的值来构造导数的近似。本节将介绍基于多项式插值的有限差分公式。

两点有限差分公式

上一节我们介绍了数值微分的基本定义。本节中我们来看看如何利用两个点来构造导数的近似公式。

函数 F 在点 X0 处的导数定义为以下极限公式:

F'(X0) = lim (h->0) [F(X0 + h) - F(X0)] / h

h 很小时,这个差分商公式可以很好地近似导数值。

现在考虑两个点 x0x1,其中 x1 = x0 + h。我们试图估计 Fx0x1 处的导数。这涉及到通过这两个点的一次拉格朗日插值多项式 P_01(x)

根据多项式插值误差定理,我们有:

F(x) = P_01(x) + R_1(x)

其中 P_01(x) 可以显式地写为:

P_01(x) = L_{1,0}(x) * F(x0) + L_{1,1}(x) * F(x1)

这里 L_{1,0}L_{1,1} 是拉格朗日基函数。

对上述方程求导,我们得到:

F'(x) = P'_01(x) + R'_1(x)

具体地,P'_01(x) 的表达式为:

P'_01(x) = (1 / -h) * F(x0) + (1 / h) * F(x1)

而误差项 R'_1(x) 的推导更为复杂,因为它依赖于 x

如果我们评估 F'x0x1 处的值,误差项中的部分乘积会为零。具体来说:

  • x = x0 处,我们得到向前差分公式

    F'(x0) ≈ [F(x0 + h) - F(x0)] / h
    

    其误差项为 O(h)

  • x = x1 处,我们得到向后差分公式

    F'(x1) ≈ [F(x1) - F(x1 - h)] / h
    

    其误差项也为 O(h)

h > 0 时,x_i + h 是向前的一个点。因此,在点 x_i 处,使用 F(x_i)F(x_i + h) 的公式称为向前差分公式,记作 D_+ F(x_i)

类似地,使用 F(x_i)F(x_i - h) 的公式称为向后差分公式,记作 D_- F(x_i)

精度测试示例

以下是使用向前差分公式近似 F(x) = x^3x0 = 1 处导数的精度测试。真实导数值 F'(1) = 3

我们使用不同的步长 h 进行计算:

  • h = 0.1 时,近似值为 3.31,误差为 0.31
  • h = 0.05 时,近似值为 3.1525,误差约为 0.1525
  • h = 0.025 时,近似值为 3.075625,误差约为 0.075625

可以观察到,每次步长减半,误差也大致减半,这验证了向前差分公式具有一阶精度 O(h)

多点有限差分公式

上一节我们基于两个点推导了公式。本节中我们来看看如何利用更多点来获得更高精度的近似。

一般来说,如果我们从 m+1 个不同的点开始构造插值多项式,根据插值误差定理有:

F(x) = P_m(x) + R_m(x)

其中 P_m(x)m 次拉格朗日插值多项式,误差项 R_m(x)O(h^{m+1})

对上述方程求导:

F'(x) = P'_m(x) + R'_m(x)

如果我们希望在节点 X_i 处评估导数,公式可以简化为:

F'(X_i) ≈ Σ_{j=0}^{m} L'_{m,j}(X_i) * F(X_j)

而误差项 R'_m(X_i) 的阶数会降低一次,变为 O(h^m)。这就是 (m+1) 点差分公式

三点差分公式示例

让我们看一个 n=2(即使用三个点)的例子。为方便起见,我们选择等距点:x0, x1 = x0 + h, x2 = x0 + 2h

我们可以构造二次拉格朗日基函数 L_{2,0}, L_{2,1}, L_{2,2}。根据多项式插值误差定理:

F(x) = P_2(x) + R_2(x)

其中 P_2(x) 是二次拉格朗日多项式,R_2(x) 是误差项。

求导后,我们尝试在每个点 x0, x1, x2 处评估 F'(x)。经过详细的代数运算(对基函数求导并代入节点值),我们可以得到:

  • 左端点公式 (在 x0 处):

    F'(x0) ≈ [-3F(x0) + 4F(x1) - F(x2)] / (2h)
    

    误差为 O(h^2)

  • 中点公式 (在 x1 处):

    F'(x1) ≈ [F(x2) - F(x0)] / (2h)
    

    误差为 O(h^2)

  • 右端点公式 (在 x2 处):
    F'(x2) ≈ [F(x0) - 4F(x1) + 3F(x2)] / (2h)
    
    误差为 O(h^2)

这样,我们通过使用三个点得到了具有二阶精度的差分公式。

五点差分公式与中点公式的优势

类似地,我们可以从五个点出发,构造四次拉格朗日多项式,从而推导出五点差分公式。例如,对于中点 x0 的公式如下:

F'(x0) ≈ [F(x0-2h) - 8F(x0-h) + 8F(x0+h) - F(x0+2h)] / (12h)

其误差为 O(h^4),精度更高。通过增加使用的点数,可以获得更高阶的差分公式。

在数值微分中有一个一般性结论:如果使用 n+1 个点进行 n 次多项式插值,那么:

  • 函数近似本身具有 O(h^{n+1}) 的精度。
  • 一阶导数近似的精度会降低一阶,为 O(h^n)
  • 二阶导数近似的精度会再降低一阶,为 O(h^{n-1})

然而,如果我们只关注中点,并巧妙地利用泰勒展开式,有时可以获得比上述一般理论预测更高的精度。

利用泰勒展开推导高阶中点公式

上一节提到了中点公式的潜在优势。本节中我们具体看看如何利用泰勒定理推导二阶导数的中点公式。

回顾带有拉格朗日余项的泰勒定理。对于充分可微的函数 F,在点 x0 附近有:

F(x0 + h) = F(x0) + hF'(x0) + (h^2/2!)F''(x0) + (h^3/3!)F'''(x0) + ... + R_n(h)

我们的目标是推导 F''(x0) 的公式。我们写出 F(x0 + h)F(x0 - h) 的泰勒展开:

F(x0 + h) = F(x0) + hF'(x0) + (h^2/2)F''(x0) + (h^3/6)F'''(x0) + (h^4/24)F^{(4)}(x0) + ...
F(x0 - h) = F(x0) - hF'(x0) + (h^2/2)F''(x0) - (h^3/6)F'''(x0) + (h^4/24)F^{(4)}(x0) - ...

将这两个方程相加,奇数次项 F'(x0)F'''(x0) 等会相互抵消,得到:

F(x0+h) + F(x0-h) = 2F(x0) + h^2 F''(x0) + (h^4/12) F^{(4)}(x0) + ...

然后解出 F''(x0)

F''(x0) = [F(x0+h) - 2F(x0) + F(x0-h)] / h^2 - (h^2/12) F^{(4)}(x0) + ...

因此,我们得到了二阶导数的中点差分公式

F''(x0) ≈ [F(x0+h) - 2F(x0) + F(x0-h)] / h^2

其误差项的主项是 (h^2/12) F^{(4)}(ξ),这意味着该公式具有二阶精度 O(h^2)。这比使用三点插值多项式直接求二阶导得到的一阶精度 O(h) 要高。这种高阶精度是中点公式特有的优势,对于端点的公式则无法达到。

中点公式精度测试示例

让我们测试上述二阶导数中点公式的精度。考虑函数 F(x) = x^5 - 3x^2,在 x0=1 处计算其二阶导数。真实值 F''(1) = 14

我们使用不同的步长 h

  • h = 0.2 时,近似值为 14.4800,误差为 0.4800
  • h = 0.1 时,近似值为 14.1200,误差为 0.1200
  • h = 0.05 时,近似值为 14.0300,误差为 0.0300

可以观察到,当 h 减半时,误差大约变为原来的四分之一(例如从 0.12000.0300),这验证了该中点公式具有二阶精度 O(h^2)

总结 🎯

本节课中我们一起学习了数值微分的核心方法——有限差分公式。

  • 我们从导数的定义出发,引出了最简单的向前和向后两点差分公式,它们具有一阶精度 O(h)
  • 通过引入多项式插值理论,我们推广到了使用 m+1 个点的通用差分公式,其精度为 O(h^m),并具体推导了三点公式。
  • 我们了解到,对于导数近似,精度会比函数插值本身的精度低一阶。
  • 特别地,我们学习了如何利用泰勒展开推导特定(尤其是中点)的高阶精度公式。例如,二阶导数的中点公式 [F(x+h)-2F(x)+F(x-h)]/h^2 具有二阶精度 O(h^2),这比从三点插值直接得到的一般结果更好。
  • 数值实验验证了这些公式的精度阶数,帮助我们理解步长 h 的选择如何在计算效率与精度之间进行权衡。

掌握这些有限差分公式是进行数值计算和科学仿真的重要基础。

026:3.5 - 牛顿法求解非线性方程 📈

在本节课程中,我们将学习牛顿法,这是一种用于求解非线性方程 F(x) = 0 的数值方法。牛顿法通过迭代逼近方程的根,具有收敛速度快的特点,是科学计算和工程领域的重要工具。

概述与目标

我们的目标是找到函数 F(x) 的一个零点 P,即满足 F(P) = 0 的点。我们假设 F(x) = 0 是一个非线性方程。

基本策略是:从一个初始近似点 P0 出发,寻找一个修正项 H,使得修正后的点 P0 + H 成为比 P0 更好的近似解。理想情况下,这个点就是精确解 P

牛顿法的推导

我们假设 F 的二阶导数存在且连续,因此可以在点 P0 处应用泰勒展开定理。

F(P) = F(P0) + F'(P0) * (P - P0) + (1/2) * F''(ξ) * (P - P0)^2

其中,H = P - P0。当 |H| 很小时,二阶项 (1/2) * F''(ξ) * H^2 也会很小。因此,我们可以合理地忽略这一项,得到近似等式:

0 ≈ F(P0) + F'(P0) * H

F(P0) 移到等式另一边并除以 F'(P0),我们得到修正项 H 的近似值:

H ≈ - F(P0) / F'(P0)

于是,我们可以用这个修正项来更新我们的近似解,得到一个新的、更好的近似点 P1

P1 = P0 - F(P0) / F'(P0)

这个更新过程可以重复进行,这就是求解 F(x) = 0 的牛顿法。

迭代公式与几何解释

牛顿法的核心迭代公式如下:

P_{n+1} = P_n - F(P_n) / F'(P_n)

从几何角度看,牛顿法有非常直观的解释。给定一个初始近似点 P0,我们在曲线 y = F(x) 上对应的点 (P0, F(P0)) 处作切线。这条切线的方程是:

y = F(P0) + F'(P0) * (x - P0)

接下来,我们寻找这条切线与 x 轴的交点,即令 y = 0,解得:

x = P0 - F(P0) / F'(P0)

这个 x 值就是我们的下一个近似点 P1。通过不断重复这个过程(在 P1 处作切线,求交点得到 P2,依此类推),我们得到的点列 {P_n} 将收敛到方程的根 P

收敛性与注意事项

牛顿法并非总是收敛。其收敛性依赖于初始点 P0 的选择和函数 F 的性质。

一个经典的失败例子是求解 arctan(x) = 0。该方程的唯一解是 x = 0。如果初始点选为 P0 = π/2,牛顿迭代会发散,因为初始点离根太远,且函数的曲率导致切线交点跑到了更远的地方。

牛顿法有效的一个关键条件是导数 F'(x) 在根附近远离零。如果 F'(x) 为零,迭代公式中的分母为零,方法会失效。

理论上,如果函数 F 二阶连续可微,根 P 满足 F'(P) ≠ 0,并且初始点 P0 足够接近 P,那么牛顿法产生的序列将以二次收敛的速度逼近根 P。这意味着当前步的误差大致与上一步误差的平方成正比,收敛速度非常快。

在某些对称性良好的问题中,甚至可能出现超二次收敛(如立方收敛)。

实践应用:计算平方根

牛顿法的一个经典应用是计算一个正数 Q 的平方根,即求解 x^2 = Q

我们可以将其转化为求函数 F(x) = x^2 - Q 的零点。此时,F'(x) = 2x。代入牛顿迭代公式:

P_{n+1} = P_n - (P_n^2 - Q) / (2 * P_n)

简化后得到:

P_{n+1} = (1/2) * (P_n + Q / P_n)

这个公式非常优美:新的近似值是旧近似值 P_nQ/P_n 的算术平均。

以下是该算法的简单实现流程:

  1. 给定待求平方根的正数 Q
  2. 初始化近似值 x(例如 x = (Q + 1) / 2x = Q)。
  3. 进入循环,重复执行更新:x = (x + Q / x) / 2
  4. 当连续两次迭代的结果变化小于某个容差,或达到最大迭代次数时停止。

例如,计算 Q = 2 的平方根,迭代序列会迅速收敛到 1.414213562...,通常只需几次迭代就能达到机器精度。这个算法被高效地实现在现代计算机和计算器的 sqrt() 函数中。

总结

本节课我们一起学习了牛顿法求解非线性方程。我们首先了解了其基本思想:通过在当前近似点作切线,并用切线与x轴的交点作为新的近似点。我们推导了其核心迭代公式 P_{n+1} = P_n - F(P_n) / F'(P_n),并从几何角度进行了解释。

我们讨论了牛顿法的收敛性,它要求初始点足够接近真根且导数在根处非零,在满足条件时具有快速的二次收敛速度。最后,我们通过计算平方根这个实际例子,展示了牛顿法的强大威力和简洁实现。牛顿法是数值分析中一个基础而重要的工具。

027:牛顿法与霍纳法

概述

在本节课中,我们将学习如何寻找多项式的零点。我们将从多项式的基本理论开始,然后介绍两种重要的算法:用于高效计算多项式及其导数值的霍纳法,以及用于迭代逼近零点的牛顿法。最后,我们将看到如何结合这两种方法,高效地求解多项式的根。

多项式基础理论

多项式的定义

一个 N 次多项式具有如下形式:

P(x) = a_N * x^N + a_{N-1} * x^{N-1} + ... + a_1 * x + a_0

其中,a_i 被称为多项式 P 的系数,且最高次项系数 a_N 不为零。

我们的目标是找到多项式 P 的零点,即满足 P(x) = 0x 值。

代数基本定理

代数基本定理指出:每一个非常数多项式在复数域内至少有一个根

这意味着每个根都对应多项式的一个线性因子。因此,该定理可以重新表述为:每一个非常数多项式至少有一个线性因子。

例如:

  • 一个二次多项式至少有一个线性因子,而剩余部分也必须是线性因子,因此二次多项式总共有两个线性因子。
  • 一个三次多项式至少有一个线性因子,剩余部分是一个二次多项式,而二次多项式又可以表示为两个线性因子的乘积。

推广来说,一个 N 次多项式可以表示为 N 个线性因子的乘积。更精确地说,一个 N 次多项式在复数平面上恰好有 N 个根(计算重根),即存在唯一的复数 x_1, x_2, ..., x_k 和唯一的正整数 m_1, m_2, ..., m_k,使得:

P(x) = a_N * (x - x_1)^{m_1} * (x - x_2)^{m_2} * ... * (x - x_k)^{m_k}

其中 m_1 + m_2 + ... + m_k = N

根的定位

多项式 P 的所有根都位于一个以原点为中心的开圆盘内,该圆盘的半径 R 为:

R = 1 + (|a_0| + |a_1| + ... + |a_{N-1}|) / |a_N|

当我们使用迭代算法求根时,可以将初始猜测值设置在这个圆盘内,以获得更好的收敛性。

多项式的唯一性

PQ 是两个 N 次多项式。如果它们在多于 N 个不同的点上的取值相同,那么这两个多项式必定完全相同。
特别地,如果两个 N 次多项式在 N+1 个不同点上的取值一致,则它们相同。这意味着 N+1 个不同点的函数值唯一地决定了一个 N 次多项式。

霍纳法(嵌套乘法)

上一节我们介绍了多项式的基本性质,本节中我们来看看如何高效地计算多项式在某一点的值。霍纳法(也称为嵌套乘法或综合除法)就是一种非常高效的多项式求值算法。

余数定理

霍纳法的理论基础是余数定理。该定理指出,多项式 P(x) 除以 (x - x_0),可以得到商式 Q(x) 和余数 R(一个常数):

P(x) = (x - x_0) * Q(x) + R

x = x_0 时,上式变为 P(x_0) = R。因此,求 P(x_0) 等价于求上述除法中的余数 R

算法推导

设商式 Q(x) 为:

Q(x) = b_{N-1} * x^{N-1} + b_{N-2} * x^{N-2} + ... + b_1 * x + b_0

P(x)Q(x) 的表达式代入 P(x) = (x - x_0) * Q(x) + R 并展开,通过比较等式两边 x 的同次幂系数,我们可以得到一组递推关系式。

以下是计算系数 b_k 和余数 R 的递推公式:

b_{N-1} = a_N
b_{k-1} = a_k + x_0 * b_k, 对于 k = N-1, N-2, ..., 1
R = a_0 + x_0 * b_0

其中,R 就是我们所求的 P(x_0)

综合除法表格

上述递推过程可以用一个简洁的综合除法表格来实现,这通常在中学阶段学习过。

以下是构建表格的步骤:

  1. 在第一行写下多项式系数 a_N, a_{N-1}, ..., a_0
  2. 在左侧写下求值点 x_0
  3. 将第一个系数 a_N 直接落到第三行,作为 b_{N-1}
  4. 将刚得到的数值乘以 x_0,结果写在第二行的下一个位置。
  5. 将第二行的这个数与上方的系数相加,结果写在第三行。
  6. 重复步骤4和5,直到处理完所有系数。
  7. 第三行最后一个数就是余数 R,即 P(x_0);第三行前面的数则是商式 Q(x) 的系数。

示例

使用霍纳法计算多项式 P(x) = x^4 - 4x^3 + 7x^2 - 5x - 2x=3 处的值。

构建综合除法表格如下:

        1    -4     7    -5    -2
  3 |        3    -3    12    21
    ----------------------------
        1    -1     4     7    19

计算过程:

  1. 落下系数 1
  2. 1 * 3 = 3,写在第二行。
  3. -4 + 3 = -1,写在第三行。
  4. -1 * 3 = -3,写在第二行。
  5. 7 + (-3) = 4,写在第三行。
  6. 4 * 3 = 12,写在第二行。
  7. -5 + 12 = 7,写在第三行。
  8. 7 * 3 = 21,写在第二行。
  9. -2 + 21 = 19,写在第三行。

因此,P(3) = 19。同时,我们得到商式 Q(x) = x^3 - x^2 + 4x + 7,验证了 P(x) = (x-3)*Q(x) + 19

结合牛顿法求根

上一节我们学会了用霍纳法高效计算多项式值,本节我们来看看如何将其应用于牛顿法,以快速找到多项式的零点。

牛顿法回顾

牛顿法是一种迭代求根算法,其更新公式为:

x_{new} = x_{old} - P(x_{old}) / P'(x_{old})

其中,P'(x) 是多项式 P(x) 的导数。为了执行一次牛顿迭代,我们需要同时计算 P(x)P'(x)x_{old} 处的值。

高效计算导数值

利用霍纳法和余数定理,我们可以高效地同时计算出 P(x_0)P'(x_0)

回顾 P(x) = (x - x_0) * Q(x) + R,其中 R = P(x_0)
对这个等式两边关于 x 求导:

P'(x) = Q(x) + (x - x_0) * Q'(x)

x = x_0 时,第二项为零,因此:

P'(x_0) = Q(x_0)

这个结论非常有力:多项式 Px_0 处的导数值,恰好等于其除以 (x - x_0) 后得到的商式 Qx_0 处的函数值

因此,计算 P'(x_0) 的步骤变为:

  1. 使用霍纳法对 P(x)x_0 处求值,得到 P(x_0) 和商式 Q(x) 的系数。
  2. 再次使用霍纳法,对商式 Q(x)x_0 处求值,得到的结果就是 P'(x_0)

示例:计算函数值与导数值

继续使用多项式 P(x) = x^4 - 4x^3 + 7x^2 - 5x - 2,我们已经用一次霍纳法得到 P(3)=19Q(x)=x^3 - x^2 + 4x + 7

现在,对 Q(x)x=3 处再次应用霍纳法:

        1    -1     4     7
  3 |        3     6    30
    ----------------------
        1     2    10    37

计算得 Q(3) = 37。因此,P'(3) = Q(3) = 37

算法实现

我们可以编写一个霍纳法函数,使其同时返回 P(x_0)P'(x_0)。以下是一个MATLAB风格的伪代码描述:

function [P_val, P_prime_val] = Horner(coeffs, x0)
    % coeffs: 多项式系数向量,从高次到低次 [a_N, a_{N-1}, ..., a_0]
    % x0: 求值点
    % P_val: P(x0)
    % P_prime_val: P'(x0)

    n = length(coeffs) - 1; % 多项式次数
    b = coeffs(1); % 初始化 b_{N-1}
    d = 0;         % 用于计算导数的变量

    for i = 2:n+1
        d = b + x0 * d;       % 更新导数计算
        b = coeffs(i) + x0 * b; % 更新霍纳法主计算
    end

    P_val = b;
    P_prime_val = d;
end

牛顿-霍纳法求根

结合牛顿法和霍纳法,我们可以高效地寻找多项式零点。以下是算法步骤:

  1. 给定多项式系数、初始猜测值 x0、容差 tol 和最大迭代次数 max_iter
  2. 进入循环,直到满足收敛条件或达到最大迭代次数:
    a. 调用 Horner 函数,计算当前点 x 处的 P(x)P'(x)
    b. 计算牛顿步长 h = P(x) / P'(x)
    c. 更新点:x = x - h
    d. 如果 |h| < tol,则跳出循环,x 即为近似根。
  3. 返回最终的根 x 和迭代次数。

示例:求解多项式零点

使用牛顿-霍纳法寻找多项式 P(x) = x^4 - 4x^3 + 7x^2 - 5x - 2 的一个实根。

  • 初始猜测x0 = 3
  • 容差1e-12
  • 最大迭代:1000

运行算法后,经过7次迭代,收敛到根 x ≈ 2.0000。该多项式共有两个实根(另一个约为 -0.236)和两个复根。

总结

本节课中我们一起学习了求解多项式零点的完整流程。

  1. 我们首先回顾了多项式的基本理论,包括代数基本定理和根的定位。
  2. 接着,我们深入学习了霍纳法,这是一种通过嵌套乘法和综合除法来高效计算多项式值的方法,其核心是余数定理。
  3. 然后,我们发现霍纳法的一个巧妙应用:通过两次霍纳法求值,可以同时得到多项式在某点的函数值P(x_0)和导数值P'(x_0),这基于公式 P'(x_0) = Q(x_0)
  4. 最后,我们将霍纳法与牛顿法相结合,形成了高效的“牛顿-霍纳法”,用于迭代求解多项式的实根。这种方法在每次迭代中都能以最小的计算代价获得所需的函数值和导数值,从而快速收敛到零点。

掌握这些方法,对于在科学计算和工程应用中处理多项式问题至关重要。

028:三种初等行变换(EROs)

概述

在本节课中,我们将学习线性代数的基础知识。现实世界中的许多系统都可以近似或表示为线性方程组。我们将从线性方程组的基本概念开始,学习如何表示和求解它们,并介绍三种关键的初等行变换(EROs)。


4.1 线性方程组的解

线性方程是变量 x1, x2, ..., xn 的方程,其形式为:
a1x1 + a2x2 + ... + anxn = b
其中系数 a1an 以及常数 b 是实数或复数。

线性方程组(或简称线性系统)是一个或多个涉及相同变量的线性方程的集合。

例如,左侧是一个包含两个变量 x1x2 的二元线性方程组,右侧是一个包含三个变量 x, y, z 的三元线性方程组。

是指一组数字 (s1, s2, ..., sn),当用这组数字分别替代变量 x1, x2, ..., xn 时,能使方程组中的每一个方程都成立。所有可能解的集合称为解集。如果两个线性系统具有相同的解集,则称它们是等价的。

例如,以下两个系统是等价的,因为第二个系统是通过将第一个方程乘以2再减去第二个方程得到的,它们共享相同的解集。


线性系统的解的情况

线性系统可能无解,可能有唯一解,也可能有无穷多解。

  • 当系统无解时,我们称其为不相容的。
  • 当系统至少有一个解(即唯一解或无穷多解)时,我们称其为相容的。

我们以两个方程两个未知数的系统为例来说明这三种情况。

  1. 无解:例如,方程组 y = x + 1y = x + 3。在坐标系中,这是两条斜率相同但截距不同的平行线,它们没有交点,因此系统无解。
  2. 唯一解:例如,方程组 y = -x + 1y = x - 2。在坐标系中,这是两条斜率不同的直线,它们相交于一点,该交点即为系统的唯一解。
  3. 无穷多解:例如,将第一个方程 x + y = 1 乘以2得到 2x + 2y = 2,这与第二个方程完全相同。在坐标系中,两条直线重合,直线上每一个点都是解,因此系统有无穷多解。


如何求解线性系统:矩阵表示法

为了更便捷高效地求解线性系统,我们引入矩阵表示法。

考虑一个简单的二元线性方程组。我们可以将其系数和常数项组织成矩阵形式,即矩阵方程 Ax = b

例如,方程组:
-2x1 + 3x2 = -1
x1 + 2x2 = 4

可以写成矩阵形式:

[-2  3] [x1]   = [-1]
[ 1  2] [x2]     [ 4]

为了包含所有求解信息,我们构造增广矩阵,它将系数矩阵和常数项向量合并:

[-2  3 | -1]
[ 1  2 |  4]

使用初等行变换(EROs)求解

上一节我们介绍了用增广矩阵表示线性系统。本节中,我们来看看如何通过一系列操作(即初等行变换)来求解它。

求解过程的目标是通过变换增广矩阵,将其左侧部分(系数矩阵)化为行最简形(最终目标是单位矩阵),此时右侧的列向量就是方程组的解。

以下是求解前述方程组的步骤演示:

  1. 初始增广矩阵
    [-2  3 | -1]
    [ 1  2 |  4]
    
  2. 交换两行(使左上角元素不为0):
    [ 1  2 |  4]
    [-2  3 | -1]
    
  3. 行替换:将第二行替换为“第二行 + 2 × 第一行”,以消去第一列下方的元素:
    [1  2 |  4]
    [0  7 |  7]
    
  4. 行缩放:将第二行乘以 1/7
    [1  2 |  4]
    [0  1 |  1]
    
  5. 行替换:将第一行替换为“第一行 - 2 × 第二行”,以消去第一行第二列的元素:
    [1  0 |  2]
    [0  1 |  1]
    

最终矩阵对应方程组 x1 = 2x2 = 1。因此,解为 (2, 1)

在上述过程中,我们使用了三种核心操作,即初等行变换(EROs)

以下是三种初等行变换的定义:

  1. 行替换:将某一行替换为它本身与另一行的倍数之和。
    • 公式Ri <- Ri + k * Rj
  2. 行交换:交换两行的位置。
    • 公式Ri <-> Rj
  3. 行缩放:将某一行的所有元素乘以一个非零常数。
    • 公式Ri <- k * Ri (k ≠ 0)

如果存在一个初等行变换的序列能将矩阵A转化为矩阵B,则称矩阵A与B是行等价的。例如,通过一系列EROs,任何可逆矩阵都可以化为单位矩阵。


线性组合的矩阵视角

线性代数中的一个基本思想是将向量的线性组合视为矩阵与向量的乘积。

A 是一个 m × n 矩阵,其列向量为 a1, a2, ..., an。设 x 是一个 n 维向量。那么,乘积 Ax 被定义为列向量的线性组合:
Ax = x1a1 + x2a2 + ... + xn*an

因此,矩阵方程 Ax = b 可以等价地视为向量方程:
x1a1 + x2a2 + ... + xn*an = b

这个向量方程与原始的线性方程组具有完全相同的解集。这为我们理解线性系统提供了另一种几何视角:求解 Ax = b 就是寻找一组系数 x,使得 A 的列向量能线性组合出目标向量 b


线性系统中的两个基本问题

在求解线性系统时,有两个根本性问题:

  1. 存在性:系统是否相容?即,解是否存在?
  2. 唯一性:如果解存在,它是否是唯一的?

在许多应用中,确保解存在且唯一至关重要。否则,我们可能面临无解或无穷多解的情况,处理起来会更加复杂。

示例:确定参数 h 的值,使得以下系统相容(即至少有一个解):
x1 + hx2 = -5
2
x1 + 4*x2 = 6

我们构造增广矩阵并应用EROs:

[1  h | -5]
[2  4 |  6]

将第二行替换为“第二行 - 2 × 第一行”:

[1    h    | -5]
[0  4-2h  | 16]

系统相容的条件是,第二个方程不能导致 0 = 非零常数 的矛盾。这要求 4 - 2h ≠ 0,即 h ≠ 2

  • h ≠ 2 时,我们可以继续求解得到唯一解。
  • h = 2 时,第二行变为 [0 0 | 16],对应方程 0 = 16,系统不相容(无解)。

因此,当且仅当 h ≠ 2 时,该系统有(唯一)解。


总结

本节课我们一起学习了线性代数的基础入门知识。我们首先了解了线性方程组的定义、解集以及等价系统的概念。接着,我们探讨了线性系统解的三种可能情况:无解、唯一解和无穷多解。为了有效求解,我们引入了矩阵表示法,特别是增广矩阵,并详细讲解了求解的核心工具——三种初等行变换(行替换、行交换、行缩放)。我们还从矩阵乘法的角度理解了线性组合,并指出了分析线性系统时需要考虑的两个基本问题:解的存在性与唯一性。这些概念是后续深入学习线性代数及其在人工智能中应用的重要基石。

029:行阶梯形与行化简算法

在本节课中,我们将学习线性系统的行化简算法,以及如何通过行化简得到行阶梯形和简化行阶梯形,从而求解线性方程组。我们将介绍核心概念,并通过具体示例演示算法步骤。

概述

在上一节(4.1节)中,我们学习了三种初等行变换。本节(4.2节)将探讨行化简以及线性方程组的通解。我们将通过一个具体例子,使用三种初等行变换来求解一个线性方程组。

示例:求解线性方程组

我们从该方程组的增广矩阵开始。

增广矩阵为:

[0, 1, -2, | 0]
[1, -2,  2, | 3]
[4, -8,  6, | 14]

由于第一行首元为0,我们交换第一行和第二行。

[1, -2,  2, | 3]
[0,  1, -2, | 0]
[4, -8,  6, | 14]

接下来,用第一行的-4倍加到第三行,以消去第三行第一列的元素。

[1, -2,  2, | 3]
[0,  1, -2, | 0]
[0,  0, -2, | 2]

然后,将第三行除以-2进行缩放。

[1, -2,  2, | 3]
[0,  1, -2, | 0]
[0,  0,  1, | -1]

现在,我们有了一个行阶梯形。接下来,通过行变换将主元列的其他元素化为0。首先,用第二行的2倍加到第一行,消去第一行第二列的元素。

[1, 0, -2, | 3]
[0, 1, -2, | 0]
[0, 0,  1, | -1]

然后,用第三行的2倍加到第一行,用第三行的2倍加到第二行,消去第一行和第二行第三列的元素。

[1, 0, 0, | 1]
[0, 1, 0, | -2]
[0, 0, 1, | -1]

矩阵被化简为单位矩阵,因此方程组的解为 (1, -2, -1)。这是一个唯一解,说明该方程组是相容的。

核心方法:线性方程组可以通过将其增广矩阵化为简化行阶梯形来求解。在MATLAB中,可以使用 rref 函数实现此过程。

A = [0 1 -2; 1 -2 2; 4 -8 6];
B = [0; 3; 14];
rref([A B])

行阶梯形定义

我们定义行阶梯形并考虑行化简算法。

一个矩阵是行阶梯形,如果它满足以下性质:

  1. 所有非零行都在全零行之上。
  2. 某一行的主元(即该行最左边的非零元素)总位于上一行主元的右侧。
  3. 主元下方的所有元素均为零。

其形状类似于阶梯,因此得名。

如果一个行阶梯形矩阵还满足以下两个额外条件,则称为简化行阶梯形
4. 每个主元都为1。
5. 每个主元是其所在列中唯一的非零元素。

以下是判断矩阵形式的示例:

  • [[1, *, *]; [0, 1, *]; [0, 0, 0]] 是简化行阶梯形(满足所有条件)。
  • [[1, *, *]; [0, 0, 1]; [0, 0, 0]] 是行阶梯形(满足前3个条件)。
  • [[0, 1]; [1, 0]] 不是行阶梯形(交换两行后才是)。
  • [[2, *]; [0, 1]] 是行阶梯形,但不是简化行阶梯形(主元不是1,且其所在列有其他非零元素)。

主元位置与变量分类

主元位置是矩阵A中对应于其简化行阶梯形中主元1的位置。
主元列是包含主元位置的列。

在方程组 Ax = b 中:

  • 基本变量:对应于增广矩阵中主元列的变量。
  • 自由变量:对应于非主元列的变量。

例如,对于一个化简后主元位于第1、3、5列的矩阵,x1x3x5 是基本变量,x2x4 是自由变量。

示例:识别基本变量与自由变量

考虑以下线性方程组,我们需要识别其基本变量和自由变量。

-x1 - 2x2 = -3
 2x2 + 2x3 = 4
 3x2 + 3x3 = 6

首先,写出其增广矩阵并进行行化简。

[[-1, -2, 0, | -3],
 [ 0,  2, 2, |  4],
 [ 0,  3, 3, |  6]]

经过缩放和行变换,得到简化行阶梯形:

[[1, 2, 0, | 3],
 [0, 0, 1, | 2],
 [0, 0, 0, | 0]]

可以看到,主元位于第1列和第3列。因此,x1x3 是基本变量,x2 是自由变量。

为什么称其为自由变量?将化简后的矩阵写回方程形式:

x1 + 2x2 = 3
x3 = 2

我们可以将基本变量 x1x3 留在等式左边,其余移到右边:

x1 = 3 - 2x2
x3 = 2

对于 x2,没有方程约束它,因此它可以取任意值。解可以写为:

[x1]   [3]      [-2]
[x2] = [0] + x2*[ 1]
[x3]   [2]      [ 0]

其中 x2 是自由参数。正是由于 x2 可以自由选择,它被称为自由变量。

行化简算法详解

行化简算法通过一个例子来演示,目标是将给定矩阵 A 化为简化行阶梯形。

假设矩阵 A 为:

[[0, 3, -6, 6, 4, -5],
 [3, -7, 8, -5, 8, 9],
 [3, -9, 12, -9, 6, 15]]

向前阶段(化为行阶梯形)

  1. 选择主元:第一列第一行是0,与下方非零行交换,使左上角元素非零。
  2. 消元:使用第一行主元,通过行倍加将下方行的第一列元素化为0。
  3. 缩放:将第二行主元位置(第二行第二列)通过行缩放化为1。
  4. 继续消元:使用新的主元,将其下方的元素化为0。
  5. 重复此过程,向下向右移动,直到矩阵变为行阶梯形。

向后阶段(化为简化行阶梯形)

  1. 从最右边的非零行开始,将其主元上方的所有元素通过行倍加化为0。
  2. 向左移动到上一个主元,重复步骤1,消去其上方所有元素。
  3. 继续此过程,直到每个主元都是其所在列唯一的非零元素,且值为1。

算法要点

  • 在向前阶段,我们从上到下、从左到右进行,得到行阶梯形。
  • 在向后阶段,我们从下到上、从右到左进行,得到简化行阶梯形。
  • 一旦矩阵化为行阶梯形,其主元位置就固定了,后续的初等行变换不会改变这些位置。

唯一性与总结

  • 行阶梯形不唯一:一个矩阵可以通过不同的行变换序列得到不同的行阶梯形。
  • 简化行阶梯形唯一:任意矩阵都行等价于唯一一个简化行阶梯形矩阵。

本节课中,我们一起学习了:

  1. 行阶梯形简化行阶梯形的定义与判断。
  2. 主元位置主元列基本变量自由变量的概念。
  3. 使用行化简算法(分为向前和向后两个阶段)求解线性方程组。
  4. 理解了自由变量在描述方程组通解中的作用。
  5. 认识到简化行阶梯形的唯一性。

掌握行化简是理解线性方程组解的结构、矩阵的秩以及后续线性代数概念的基础。

030:基础变量与自由变量 🔢

在本节课中,我们将学习线性方程组通解的第二部分。我们将重点介绍如何通过识别基础变量和自由变量,来系统地描述一个线性方程组的所有可能解。

上一节我们介绍了主元、主元位置和主元列的概念。本节中,我们将基于这些概念,学习如何找到并表达方程组的通解。

从例子开始

考虑以下增广矩阵,其行最简形式如下:

其对应的方程组可以写为:

  • x1 - 5x3 = 1
  • x2 + x3 = 4
  • 0 = 0

其中,第一行和第二行的首项系数1决定了主元位置和主元列。因此,基础变量是 x1 和 x2。


为了求解,我们将所有非基础变量移到等号右边,只保留基础变量在左边:

  • x1 = 1 + 5x3
  • x2 = 4 - x3

此时,x3 不受任何方程约束,可以自由取值,因此它是一个自由变量。为了完整地描述解向量 (x1, x2, x3),我们显式地加上一行:x3 = x3。

于是,解可以写成向量形式:
x = [1, 4, 0] + x3 * [5, -1, 1]

对于 x3 的任意选择,上述表达式都给出原方程组的一个解。因此,x3 作为参数,这个表达式描述了所有解的集合。

这种用自由变量作为参数的描述方式,称为解集的参数化描述

这个包含了所有解的表达式,就称为该方程组的通解


求解通解的步骤

现在,我们通过一个具体例子来练习求解通解的完整过程。以下是需要求解的增广矩阵:

首先,我们将其化为行最简形式。通过行操作消除第二行第一列的元素后,得到:

从行最简形式写出对应的方程组:

  • x1 - 5x4 = 3
  • x2 + 4x3 - x4 = 6
  • x5 = 0

这里,基础变量是 x1, x2, x5。自由变量是 x3 和 x4。

将基础变量留在左边,其他项移到右边:

  • x1 = 3 + 5x4
  • x2 = 6 - 4x3 + x4
  • x5 = 0
  • (自由变量)x3 = x3
  • (自由变量)x4 = x4

为了更清晰地写成向量形式,我们补上系数为0的项。解向量 x = [x1, x2, x3, x4, x5] 可以表示为:

x = [3, 6, 0, 0, 0] + x3 * [-4, 0, 1, 0, 0] + x4 * [5, 1, 0, 1, 0]

其中,x3 和 x4 可以任意取值。这个表达式就是该方程组的通解。

解的存在性与唯一性分析

接下来,我们探讨方程组解的不同情况。给定以下含参数 h 和 k 的方程组,我们需要确定参数取何值时,方程组无解、有唯一解或有无限多解。

其增广矩阵为:
[1, -3, 1; 2, h, k]

通过行化简(第二行减去2倍的第一行),得到:
[1, -3, 1; 0, h+6, k-2]

以下是不同情况的判断:

  1. 无解:当 h+6 = 0k-2 ≠ 0 时,第二行表示 0 = 非零数,矛盾,故无解。
  2. 唯一解:当 h+6 ≠ 0 时,该位置成为主元,没有自由变量,因此有唯一解(假设方程组是相容的)。
  3. 无限多解:当 h+6 = 0k-2 = 0 时,第二行全为零,x2 成为自由变量,因此有无限多解。


使用计算机求解

对于更复杂的系统,我们可以借助计算机。例如,对于给定的增广矩阵,使用 rref(行最简形式)函数,可以直接得到化简后的矩阵,从而快速识别基础变量和自由变量,并写出通解。

真/假判断题

最后,我们通过几个判断题来巩固概念。

  1. 行化简算法仅适用于线性方程组的增广矩阵。
    答案:假。行化简算法可以应用于任何矩阵,不一定是增广矩阵。

  2. 若增广矩阵的行最简形式中有一行为 [0, 0, 0, 0, 0 | 0],则对应的线性方程组不相容(无解)。
    答案:假。这样的一行表示 0=0,是一个恒等式,本身不导致矛盾。不相容是由形如 [0, 0, ..., 0 | b](其中 b ≠ 0)的行决定的。

  3. 矩阵中的主元位置取决于行化简过程中是否使用了行交换。
    答案:假。主元位置由矩阵本身决定,与化简过程中是否进行行交换无关。

  4. 将矩阵化为阶梯形的过程称为行化简算法的向前阶段。
    答案:真。行化简通常分为两个阶段:向前阶段(消元得到阶梯形)和向后阶段(进一步化简得到行最简形)。




总结

本节课中,我们一起学习了线性方程组通解的求解方法。核心步骤是:将增广矩阵化为行最简形式,识别基础变量自由变量,用自由变量作为参数表示基础变量,最后将所有解写成向量形式的参数化描述,即得到通解。我们还分析了方程组无解、有唯一解和有无穷多解的条件,并了解了如何使用计算工具辅助求解。

031:线性独立性与向量张成

在本节课中,我们将学习线性代数中的两个核心概念:线性独立性向量张成。我们将通过定义、示例和矩阵方法来理解这些概念,并学习如何判断一组向量是否线性独立,以及如何确定一个向量是否属于某个向量集的张成空间。

4.3:线性独立性与向量张成

一个包含 p 个向量的集合,其中每个向量都属于 Rⁿ 空间,如果满足以下条件,则称该集合是线性独立的:方程 c₁v₁ + c₂v₂ + ... + cₚvₚ = 0 只有平凡解(即所有系数 cᵢ = 0)。否则,该向量集合被称为线性相关的

线性相关意味着存在一组不全为零的权重 c₁, c₂, ..., cₚ,使得这些向量的线性组合等于零向量。

线性独立性意味着集合中的每个向量都独立于其他向量。具体来说,在一个线性独立的集合中,任何一个向量都不能表示为集合中其他向量的线性组合。


通过定义判断线性独立性

上一节我们介绍了线性独立性的定义,本节中我们来看看如何应用这个定义。

以下是判断两个向量是否线性独立的步骤:

  1. 设定线性组合方程:x₁v₁ + x₂v₂ = 0
  2. 将向量代入方程。
  3. 解出系数 x₁x₂
  4. 如果只有 x₁ = 0x₂ = 0 这一组解,则向量线性独立。如果存在非零解,则向量线性相关。

示例 1:判断向量 v₁ = [3, 0]ᵀv₂ = [0, 5]ᵀ 是否线性独立。

  • 建立方程:x₁[3, 0]ᵀ + x₂[0, 5]ᵀ = [0, 0]ᵀ
  • 得到方程组:3x₁ = 05x₂ = 0
  • 解得:x₁ = 0, x₂ = 0
  • 只有平凡解,因此向量线性独立

示例 2:判断向量 v₁ = [3, 0]ᵀv₂ = [1, 0]ᵀ 是否线性独立。

  • 建立方程:x₁[3, 0]ᵀ + x₂[1, 0]ᵀ = [0, 0]ᵀ
  • 得到方程组:3x₁ + x₂ = 00 = 0
  • 从第一个方程可得 x₂ = -3x₁。只要 x₁ 不为零,x₂ 就不为零。
  • 例如,x₁ = 1, x₂ = -3 就是一组非零解。
  • 存在非平凡解,因此向量线性相关

通过矩阵方法判断线性独立性

对于包含多个向量的集合,使用矩阵方法更为高效。我们将向量作为列向量组合成一个矩阵 A

以下是利用矩阵判断线性独立性的关键等价关系:

  • 向量 v₁, v₂, ..., vₚ 线性独立 齐次方程 Ax = 0 只有平凡解。
  • 向量 v₁, v₂, ..., vₚ 线性独立 矩阵 A 的每一列都是主元列(即没有自由变量)。

反之:

  • 向量线性相关 齐次方程 Ax = 0 存在非平凡解。
  • 向量线性相关 矩阵 A 至少有一列是非主元列(即存在自由变量)。

示例 3:判断向量 v₁ = [1, 2, -1]ᵀ, v₂ = [-2, 0, 3]ᵀ, v₃ = [3, 6, -3]ᵀ 是否线性独立。

  1. 构造矩阵 A,其列向量为 v₁, v₂, v₃
    A = [ 1  -2   3 ]
        [ 2   0   6 ]
        [ -1  3  -3 ]
    
  2. A 进行行化简,得到行阶梯形:
    [ 1  -2   3 ]
    [ 0   4   0 ]
    [ 0   0  -1 ]
    
  3. 观察发现,每一列都有一个主元(第一行的1,第二行的4,第三行的-1)。
  4. 所有列都是主元列,因此这组向量线性独立

示例 4:判断向量 v₁ = [1, -2, 3]ᵀ, v₂ = [-2, 4, -6]ᵀ, v₃ = [2, 1, 3]ᵀ, v₄ = [0, 3, 9]ᵀ 是否线性独立。

  1. 构造矩阵 A
    A = [ 1  -2   2   0 ]
        [ -2  4   1   3 ]
        [ 3  -6   3   9 ]
    
  2. A 进行行化简:
    [ 1  -2   2   0 ]
    [ 0   0   5   3 ]
    [ 0   0  -3   9 ]
    
    继续化简可得:
    [ 1  -2   0   ? ] (具体数值不影响判断)
    [ 0   0   1   ? ]
    [ 0   0   0   1 ]
    
  3. 观察主元位置。第二列没有主元,它是一个非主元列。
  4. 存在非主元列,因此这组向量线性相关

一个重要结论:在 Rⁿ 空间中,如果向量的数量 p 大于空间的维数 n,那么这组向量必定是线性相关的。因为矩阵 A 最多只有 n 个主元(行数),但列数 p > n,所以至少有一列是非主元列。


向量的张成

接下来,我们学习另一个核心概念:张成

向量 v₁, v₂, ..., vₚ张成,记作 Span{v₁, v₂, ..., vₚ},是指所有可能的线性组合 c₁v₁ + c₂v₂ + ... + cₚvₚ(其中 cᵢ 为任意标量)所构成的集合。

简单来说,一个向量集的张成空间,就是你能用这些向量“构造”出的所有向量的集合。

示例 5:求 h 的值,使得向量 c = [9, h, 3]ᵀa = [3, -1, 1]ᵀb = [-2, 4, 3]ᵀ 的张成空间中。

  • c ∈ Span{a, b} 意味着存在标量 x₁x₂,使得 x₁a + x₂b = c
  • 这等价于求解线性方程组 Ax = c,其中 A 的列是 ab
  • 构造增广矩阵并化简:
    [ 3  -2 |  9 ]
    [ -1  4 |  h ]
    [ 1   3 |  3 ]
    
    经过行化简,最终可以得到形如:
    [ 1  0 |  3  ]
    [ 0  1 |  0  ]
    [ 0  0 | h+18]
    
  • 要使方程组有解,增广矩阵的最后一行的常数项必须为零,即 h + 18 = 0
  • 因此,h = -18。此时解为 x₁ = 3, x₂ = 0,即 c = 3a + 0b


真/假判断题

最后,我们通过几个判断题来巩固所学概念。

以下是关于线性独立与张成的几个陈述,请判断真假:

  1. 任何 3×4 矩阵的列向量都是线性相关的。
    • 。因为矩阵有3行(n=3)和4列(p=4),p > n。根据结论,它必定线性相关。
  2. 如果 u 和 v 线性独立,且集合 {u, v, w} 线性相关,那么 w 一定在 Span{u, v} 中。
    • 。{u, v} 线性独立意味着矩阵 [u v] 有两个主元。加入 w 后变得相关,说明矩阵 [u v w] 仍然只有两个主元,即 w 列不是主元列,因此 w 可以表示为 u 和 v 的线性组合,故 w ∈ Span{u, v}。
  3. 两个向量线性相关,当且仅当它们位于一条通过原点的直线上。
    • 。两个向量相关意味着其中一个向量是另一个的标量倍数。所有这种倍数向量的集合,正好形成一条通过原点的直线。如果包含零向量,也满足此条件。
  4. 矩阵 A 的列向量线性独立,如果方程 Ax = 0 有平凡解。
    • 。Ax = 0 总是有平凡解(x=0)。线性独立的关键在于它只有平凡解。如果它还有非平凡解,那么列向量就是相关的。因此,这个陈述的表述不准确。

总结

本节课中我们一起学习了线性代数中两个基础而重要的概念:

  1. 线性独立性:一组向量中,任一向量都不能由其余向量线性表示。我们学习了通过解定义方程和利用矩阵(判断主元列)两种方法来判定。
  2. 向量张成:一组向量所有可能的线性组合构成的集合。我们学习了如何通过求解线性方程组来判断一个向量是否属于某个张成空间。

理解这些概念是学习向量空间、基、维数以及后续更复杂人工智能算法(如主成分分析、线性回归)的基石。

032:可逆矩阵与可逆矩阵定理 🔄

在本节课中,我们将学习可逆矩阵的定义、性质以及一个非常重要的定理——可逆矩阵定理。这个定理提供了多种判断矩阵是否可逆的等价条件,是线性代数中的核心内容。

可逆矩阵的定义

一个 n x n 的方阵 A 被称为可逆的非奇异的,如果存在另一个 n x n 的矩阵 B,使得以下两个等式同时成立:

AB = IBA = I

其中 In x n 的单位矩阵。在这种情况下,矩阵 B 是唯一的,我们称 BA逆矩阵,记作 A⁻¹。因此,上述关系可以写作:

A A⁻¹ = IA⁻¹ A = I

逆矩阵的示例

让我们通过一个例子来理解。假设有两个矩阵:

A = [[-4, 5], [6, -9]]
B = [[9/2, 5/2], [3, 2]]

我们可以计算 ABBA,结果会发现两者都等于 2x2 的单位矩阵 I。因此,BA 的逆矩阵,反之亦然。

如何求逆矩阵:算法

上一节我们介绍了逆矩阵的定义,本节中我们来看看如何实际计算一个矩阵的逆。一个关键的观察是:一个 n x n 矩阵 A 可逆,当且仅当它行等价n x n 的单位矩阵 I。这意味着通过一系列初等行变换,可以将 A 化为 I

这个观察引出了求逆的算法:

  1. 构造一个增广矩阵 [A | I],即把原矩阵 A 和同阶的单位矩阵 I 并排放在一起。
  2. 对增广矩阵进行初等行变换,目标是将其左半部分(即 A 的部分)化为单位矩阵 I
  3. 如果能够成功做到这一点,那么此时增广矩阵的右半部分就会变成 A 的逆矩阵 A⁻¹,即最终形式为 [I | A⁻¹]
  4. 如果在化简过程中,A 的部分无法化为单位矩阵(例如出现全零行),则说明 A 不可逆。

这个过程与用高斯消元法解线性方程组 Ax = b 非常相似。在那里,我们构造增广矩阵 [A | b],通过行变换得到 [I | x],其中 x 就是解。在这里,我们把 b 换成了单位矩阵 I,解出的就是逆矩阵 A⁻¹

矩阵的转置

在深入更多性质之前,我们先简要回顾一个相关概念:矩阵的转置。对于一个 m x n 的矩阵 A,其转置矩阵记作 Aᵀ,是一个 n x m 的矩阵。Aᵀ 的第 i 行、第 j 列元素等于原矩阵 A 的第 j 行、第 i 列元素。

用公式表示,如果 A = [aᵢⱼ],那么 Aᵀ = [aⱼᵢ]

例如,若 A = [[1, 0, 5], [4, -2, 9]],则其转置为 Aᵀ = [[1, 4], [0, -2], [5, 9]]

可逆矩阵的性质

了解了定义和算法后,我们来看看可逆矩阵的一些重要性质。以下是关于可逆矩阵 AB 的一些基本事实:

  • 2x2 矩阵的逆公式:对于一个 2x2 矩阵 A = [[a, b], [c, d]],如果 ad - bc ≠ 0,那么它是可逆的,其逆矩阵为:
    A⁻¹ = (1/(ad-bc)) * [[d, -b], [-c, a]]
    这里的 ad - bc 称为矩阵的行列式,我们将在后续章节学习。

  • 逆的逆:如果 A 可逆,那么它的逆 A⁻¹ 也可逆,并且 (A⁻¹)⁻¹ = A

  • 乘积的逆:如果 AB 都是 n x n 可逆矩阵,那么它们的乘积 AB 也可逆,并且 (AB)⁻¹ = B⁻¹ A⁻¹注意顺序要颠倒

  • 转置的逆:如果 A 可逆,那么它的转置 Aᵀ 也可逆,并且 (Aᵀ)⁻¹ = (A⁻¹)ᵀ。即求逆和转置操作可以交换顺序。

可逆矩阵定理 🧮

这是本节课的核心定理。它列出了关于一个 n x n 矩阵 A 的多个陈述,这些陈述在 A 可逆时是彼此等价的。这意味着,只要其中任何一个陈述成立,其他所有陈述也必然成立,从而可以判定 A 是可逆的。这大大扩展了可逆性的定义,提供了多种验证方法。

以下是该定理的部分等价陈述(随着课程深入,列表还会扩充):

A 是一个 n x n 矩阵,则下列陈述等价(即要么全真,要么全假):

  1. A 是可逆矩阵。
  2. A 行等价于 n x n 单位矩阵 I
  3. An 个主元位置(即每一行每一列都有主元)。
  4. 齐次方程 Ax = 0 仅有平凡解 x = 0
  5. A 的列向量是线性无关的。
  6. 线性变换 x → Ax 是一对一的(单射)。
  7. 对于 Rⁿ 中的任意向量 b,方程 Ax = b 都有解(且是唯一解)。
  8. A 的列向量张成整个 Rⁿ 空间。
  9. 线性变换 x → AxRⁿ 映射到整个 Rⁿ(满射)。
  10. 存在一个 n x n 矩阵 C,使得 CA = I。(仅左逆存在即可)
  11. 存在一个 n x n 矩阵 D,使得 AD = I。(仅右逆存在即可)
  12. Aᵀ 是可逆矩阵。

这个定理的威力在于:我们不再需要直接寻找一个矩阵 B 来验证 AB = IBA = I(这通常很困难)。相反,我们可以检查更容易验证的条件,例如:

  • A 化为行最简形,看它是不是单位矩阵。
  • 解方程 Ax = 0,看是否只有零解。
  • 判断 A 的列是否线性无关。

定理应用示例

让我们使用可逆矩阵定理来判断一个矩阵是否可逆。

问题:判断矩阵 A = [[1, 0, 2], [-3, 1, 4], [-5, -1, 0]] 是否可逆。

解法:我们可以使用定理中的陈述(2)或(3)。最常用的方法是进行行化简,检查主元数量。

A 进行初等行变换,最终可以将其化为行最简形。化简过程会显示该矩阵有三个主元位置,与它的阶数 n=3 相等。

根据可逆矩阵定理,这意味着 A 的列是线性无关的,从而 A 是可逆矩阵。我们无需实际求出 A⁻¹ 即可做出判断。


本节课中我们一起学习了:

  1. 可逆矩阵的正式定义:存在逆矩阵 A⁻¹ 使得 AA⁻¹ = A⁻¹A = I
  2. 通过构造增广矩阵 [A | I] 并施行行变换来求解逆矩阵的实用算法。
  3. 可逆矩阵的一系列重要性质,如逆的逆、乘积的逆、转置的逆等。
  4. 线性代数中至关重要的可逆矩阵定理,它提供了多个判断矩阵可逆性的等价条件,极大地简化了分析和计算。

掌握这些概念和定理,是理解更高级线性代数主题和人工智能算法中矩阵运算的基础。

033:行列式 📐

在本节中,我们将学习行列式。行列式是一个与方阵相关的标量值,它具有重要的几何意义,可以理解为线性变换对体积(或面积)的缩放因子。

上一章我们学习了线性代数的基础知识。在本章中,我们将通过编程来探讨线性代数中适用于现实世界问题的热门主题。让我们进入第5.1节:行列式。


行列式的定义

对于一个 n x n 的方阵 A,其行列式是一个标量值,记作 det(A)|A|

  • 如果 A 是一个 1 x 1 矩阵,其唯一元素为 a,则其行列式就是该元素本身:det(A) = a
  • 如果 A 是一个 2 x 2 矩阵,其元素为 a, b, c, d,则其行列式为:det(A) = ad - bc

我们将通过一些例子来更深入地理解行列式。


行列式的几何意义:缩放因子

为了更好地理解行列式,我们来看一个具体的例子。

给定矩阵 A:

A = [[2, 1],
     [0, 3]]

考虑由矩阵乘法定义的线性变换 T: R² → R²

A. 计算 det(A)。
根据公式,det(A) = (2 * 3) - (1 * 0) = 6

B. 找出矩形 R 在变换 T 下的像。
矩形 R 的顶点坐标为 (0,0), (2,0), (2,1), (0,1)。通过矩阵乘法计算每个顶点变换后的坐标:

  • T(0,0) = (0,0)
  • T(2,0) = (4,0)
  • T(2,1) = (5,3)
  • T(0,1) = (1,3)

连接这些点,得到变换后的图像是一个平行四边形。

C. 计算像的面积。
该平行四边形的底为4,高为3,因此面积为 12

D. 观察 det(A) 与面积变化的关系。
原矩形 R 的面积为 2。变换后像的面积为 12。面积放大了 6 倍,恰好等于行列式 det(A) = 6 的值。

这个例子并非特例,它揭示了一个普遍规律:行列式可以看作是线性变换导致的体积(在二维中是面积)缩放因子。在高维空间中,行列式同样表示体积的缩放比例。


高阶行列式的计算:余子式展开

对于 n ≥ 2n x n 矩阵,我们通过余子式展开来定义其行列式。

首先,定义 子矩阵 A_ij:它是从原矩阵 A 中删除第 i 行和第 j 列后得到的 (n-1) x (n-1) 矩阵。

接着,定义 余子式 C_ijC_ij = (-1)^(i+j) * det(A_ij)

利用余子式,我们可以按行或按列展开来计算行列式。

按第 i 行展开:
det(A) = a_i1 * C_i1 + a_i2 * C_i2 + ... + a_in * C_in

按第 j 列展开:
det(A) = a_1j * C_1j + a_2j * C_2j + ... + a_nj * C_nj

幸运的是,无论选择哪一行或哪一列进行展开,得到的结果都是相同的。


计算示例

给定矩阵 A:

A = [[1,  5, 0],
     [2,  4, -1],
     [0, -2, 0]]

任务1:按第一行展开计算 det(A)。
det(A) = 1 * C11 + 5 * C12 + 0 * C13

  • C11 = (-1)^(1+1) * det([[4, -1], [-2, 0]]) = 1 * (40 - (-1)(-2)) = -2
  • C12 = (-1)^(1+2) * det([[2, -1], [0, 0]]) = (-1) * (20 - (-1)0) = 0
  • C13 因乘以0,无需计算。
    因此,det(A) = 1(-2) + 50 + 0 = -2

任务2:按第三列展开计算 det(A)。
det(A) = 0 * C13 + (-1) * C23 + 0 * C33

  • C23 = (-1)^(2+3) * det([[1, 5], [0, -2]]) = (-1) * (1(-2) - 50) = 2
    因此,det(A) = 0 + (-1)*2 + 0 = -2

从本例中我们可以得到两个重要观察:

  1. 计算 3x3 矩阵的行列式,最终会归结为计算几个 2x2 矩阵的行列式。
  2. 为了简化计算,应选择包含零元素最多的行或列进行展开,这样可以减少需要计算的余子式数量。

特殊矩阵的行列式

如果矩阵 A 是三角矩阵(上三角或下三角),那么其行列式等于主对角线上所有元素的乘积。

例如,对于上三角矩阵:

A = [[1, -4, 2],
     [0, -6, -7],
     [0,  0,  4]]

det(A) = 1 * (-6) * 4 = -24

这个性质可以通过对第一列反复进行余子式展开来证明。在 MATLAB 或类似工具中,可以使用 det(A) 命令轻松验证。


行列式与矩阵可逆性

观察上一个例子中的矩阵 A,它在每一列都有主元位置,因此它是可逆的。同时,它的行列式 det(A) = -24 ≠ 0

如果我们将主对角线上的某个元素改为0,例如将4改为0,那么行列式就会变成0。此时,矩阵将缺少一个主元,变得不可逆。

事实上,这是一个普遍定理:方阵 A 可逆的充分必要条件是 det(A) ≠ 0。行列式为0意味着线性变换将空间压缩到了更低的维度,因此不存在逆变换。


行变换对行列式的影响

了解初等行变换如何改变行列式,可以帮助我们更有效地计算行列式。

An x n 矩阵,B 是由 A 经过行变换得到的矩阵。

  1. 行替换:将一行加上另一行的倍数。det(B) = det(A)。(行列式不变)
  2. 行交换:交换两行的位置。det(B) = -det(A)。(行列式变号)
  3. 行缩放:将某一行乘以标量 kdet(B) = k * det(A)。(行列式缩放 k 倍)

重要提示:若将整个矩阵 A 乘以标量 k,得到 kA,这相当于对每一行都进行了缩放。因此,det(kA) = k^n * det(A),其中 n 是矩阵的阶数。


利用行变换计算行列式

给定矩阵 A:

A = [[1, -4, 2],
     [2, -8, 9],
     [0,  3, 0]]

我们可以通过行变换将其化为上三角矩阵,并跟踪行列式的变化。

  1. 对 A 进行行替换:R2 ← R2 - 2*R1。得到矩阵 B,det(B) = det(A)
    B = [[1, -4,  2],
         [0,  0,  5],
         [0,  3,  0]]
    
  2. 交换 B 的第2行和第3行,得到矩阵 C。det(C) = -det(B) = -det(A)
    C = [[1, -4,  2],
         [0,  3,  0],
         [0,  0,  5]]
    
  3. 矩阵 C 是上三角矩阵,其行列式为对角线元素乘积:det(C) = 1 * 3 * 5 = 15
    因此,-det(A) = 15,解得 det(A) = -15

行列式的其他性质

以下是行列式的一些有用性质:

  • 转置不变性det(Aᵀ) = det(A)
  • 乘积的行列式det(AB) = det(A) * det(B)
  • 逆矩阵的行列式:若 A 可逆,则 det(A⁻¹) = 1 / det(A)。这可以从 det(A⁻¹) * det(A) = det(I) = 1 推导出来。

综合例题

假设 A 是一个 5x5 矩阵,经过一系列初等行变换得到 A1, A2, A3。已知:

  • A → (行替换) → A1
  • A1 → (第3行缩放5倍) → A2
  • A2 → (交换第1行和第2行) → A3
    且已知 A3 是对角矩阵 diag(1, -2, 3, -1, 1)。

det(A)

  1. A3 是上三角矩阵,det(A3) = 1 * (-2) * 3 * (-1) * 1 = 6
  2. A3 由 A2 经行交换得到,故 det(A2) = -det(A3) = -6
  3. A2 由 A1 经行缩放(k=5)得到,故 det(A1) = det(A2) / 5 = -6/5
  4. A1 由 A 经行替换得到,行列式不变,故 det(A) = det(A1) = -6/5

总结

本节课我们一起学习了行列式的核心概念。我们首先定义了2x2矩阵的行列式,并揭示了其作为线性变换体积缩放因子的几何意义。接着,我们学习了通过余子式展开计算高阶行列式的方法,并掌握了三角矩阵行列式的简便算法。我们探讨了行列式与矩阵可逆性的深刻联系:矩阵可逆当且仅当其行列式非零。然后,我们分析了初等行变换对行列式的影响,这为我们提供了计算行列式的强大工具。最后,我们介绍了行列式的其他重要性质,如转置不变性、乘积法则和逆矩阵的行列式。理解这些内容是应用行列式解决实际编程问题的基础。

034:特征方程 📐

在本节课中,我们将学习线性代数中两个核心概念:特征值特征向量。它们是定义在方阵上的重要工具,与行列式密切相关。我们将通过定义、示例和计算方法,帮助你理解这些概念。

定义与基本概念

特征值和特征向量是针对方阵定义的。设 A 是一个 M x M 的矩阵。

如果存在一个非零向量 x 和一个标量 λ,使得等式 A x = λ x 成立,那么:

  • 标量 λ 被称为矩阵 A 的一个特征值
  • 向量 x 被称为对应于特征值 λ特征向量

为了更好地理解这些新术语,我们将通过解决一些示例来加深认识。

示例:验证特征向量

上一节我们介绍了定义,本节中我们来看看如何验证一个向量是否是特征向量。

问题:向量 [-1, 1] 是矩阵 [[5, 2], [3, 6]] 的特征向量吗?如果是,对应的特征值是什么?

解答
根据定义,如果它是特征向量,则必须满足方程 A x = λ x

  1. 计算矩阵与向量的乘积 A x
    [[5, 2], [3, 6]] * [-1, 1] = [(-5+2), (-3+6)] = [-3, 3]
  2. 观察结果:[-3, 3] 恰好等于 3 * [-1, 1]
  3. 因此,存在标量 λ = 3,使得 A x = 3 x 成立。

结论:向量 [-1, 1] 是矩阵的一个特征向量,其对应的特征值为 3

示例:寻找特征向量

现在,我们来看一个已知特征值,求解对应特征向量的例子。

问题:已知矩阵 A = [[1, 6], [5, 2]],证明 7 是它的一个特征值,并找出所有对应的特征向量。

解答
如果 7 是特征值,则存在非零向量 x,使得 A x = 7 x

  1. 将方程重写为:A x - 7 x = 0
  2. 提取公因子 x(A - 7I) x = 0,其中 I 是单位矩阵。
  3. 计算矩阵 A - 7I
    [[1, 6], [5, 2]] - 7*[[1, 0], [0, 1]] = [[-6, 6], [5, -5]]
  4. 现在需要求解齐次线性方程组 [[-6, 6], [5, -5]] * [x1, x2] = [0, 0] 的非零解。
  5. 将其增广矩阵进行行化简:
    [[-6, 6, 0], [5, -5, 0]] → 化简后得到 [[1, -1, 0], [0, 0, 0]]
  6. 解为:x1 - x2 = 0,即 x1 = x2x2 是自由变量。
  7. 因此,所有解(即特征向量)具有形式:x = x2 * [1, 1],其中 x2 为任意非零标量。

结论7 确实是矩阵 A 的特征值。所有形如 k*[1, 1](k ≠ 0)的向量都是对应于特征值 7 的特征向量。例如,[1, 1][-2, -2] 都是。

特征值与可逆性

一个重要的结论将特征值与矩阵的可逆性联系起来。

定理:数 0 是矩阵 A 的特征值,当且仅当 A不可逆的(奇异的)。

解释

  • 如果 0 是特征值,则存在非零向量 x 使得 A x = 0 * x = 0。这意味着齐次方程组有非零解,因此 A 不可逆。
  • 反之,如果 A 不可逆,则齐次方程组 A x = 0 有非零解,这个解向量 x 就满足 A x = 0 * x,因此 0 是特征值。

这补充了可逆矩阵定理:矩阵 A 可逆,等价于 0 不是 A 的特征值。

特征方程

上一节我们通过解方程寻找特征向量,本节我们来看看如何系统地寻找特征值。

从定义 A x = λ x 出发,可推导出 (A - λI) x = 0。为了存在非零解 x,矩阵 (A - λI) 必须是奇异的,即其行列式必须为零。

定义

  • 方程 det(A - λI) = 0 称为矩阵 A特征方程
  • 多项式 p(λ) = det(A - λI) 称为矩阵 A特征多项式

核心方法:矩阵 A 的特征值,就是其特征方程 det(A - λI) = 0 的根(解)。

完整示例:计算特征值与特征向量

现在,让我们应用特征方程来完整求解一个矩阵的特征值和特征向量。

问题:求矩阵 A = [[8, 3], [2, 3]] 的特征多项式、特征值及对应的特征向量。

解答

  1. 构造 A - λI
    A - λI = [[8-λ, 3], [2, 3-λ]]
  2. 计算特征多项式
    det(A - λI) = (8-λ)(3-λ) - (3*2) = λ^2 - 11λ + 24 - 6 = λ^2 - 11λ + 18
  3. 解特征方程
    λ^2 - 11λ + 18 = 0 → 因式分解为 (λ - 2)(λ - 9) = 0
    解得特征值:λ1 = 2λ2 = 9
  4. 对每个特征值求特征向量
    • 对于 λ1 = 2
      解方程组 (A - 2I)x = 0
      A - 2I = [[6, 3], [2, 1]],行化简后得 [[1, 1/2, 0], [0, 0, 0]]
      解为 x1 = -1/2 * x2。取 x2 = 2(为消除分数),得到一个基础特征向量:[-1, 2]。所有形如 k*[-1, 2](k ≠ 0)的向量都是对应 λ=2 的特征向量。
    • 对于 λ2 = 9
      解方程组 (A - 9I)x = 0
      A - 9I = [[-1, 3], [2, -6]],行化简后得 [[1, -3, 0], [0, 0, 0]]
      解为 x1 = 3 * x2。取 x2 = 1,得到一个基础特征向量:[3, 1]。所有形如 k*[3, 1](k ≠ 0)的向量都是对应 λ=9 的特征向量。

使用MATLAB进行计算

对于更复杂的矩阵,我们可以借助计算工具。以下是使用MATLAB求解的示例。

问题:求矩阵 [[0, 1, 1], [1, 0, 1], [1, 1, 0]] 的特征多项式和所有特征值。

MATLAB代码与结果

A = [0 1 1; 1 0 1; 1 1 0]; % 定义矩阵
p = charpoly(A); % 计算特征多项式
% p 的结果对应多项式:λ^3 - 3λ - 2
roots(p) % 求解特征方程的根
% 得到特征值:-2, -2, 3 (此处-2是重根)

[V, D] = eig(A); % 直接计算特征向量(V)和特征值对角矩阵(D)
% V 的列是特征向量
% D 的对角线元素是特征值
% 可以验证 A * V ≈ V * D

通过 eig 命令,我们可以直接得到特征向量矩阵 P 和对角特征值矩阵 D,并且满足关系 A = P D P^(-1),这引向了下一部分的重要主题:矩阵的相似对角化。

总结

本节课中我们一起学习了线性代数的核心概念——特征值与特征向量。

  1. 我们首先了解了它们的定义:满足 A x = λ x 的标量 λ 和向量 x
  2. 我们通过具体例子学习了如何验证特征向量,以及如何根据已知特征值求解特征向量。
  3. 我们探讨了特征值 0 与矩阵可逆性之间的重要关系。
  4. 我们引入了系统求解特征值的关键工具——特征方程 det(A - λI) = 0特征多项式
  5. 我们完成了一个从计算特征多项式、解特征值到求对应特征向量的完整示例。
  6. 最后,我们了解了如何使用MATLAB等计算工具来辅助求解。

这些概念是理解矩阵深层结构、进行矩阵对角化以及后续许多高级应用(如主成分分析PCA)的基础。在接下来的课程中,我们将继续学习矩阵的相似性与对角化定理。

035:第35讲 - 矩阵相似性与对角化定理(第二部分)🔢

在本节课中,我们将继续学习矩阵相似性的概念,并深入探讨矩阵对角化的核心定理。我们将了解如何判断一个矩阵是否可对角化,以及如何通过特征值和特征向量来实现对角化。


矩阵相似性回顾

上一节我们介绍了矩阵相似性的基本定义。本节中,我们来看看相似矩阵的一些重要性质。

两个 n×n 矩阵 AB 是相似的,如果存在一个可逆矩阵 P,使得:

A = P B P⁻¹

或者等价地:

P⁻¹ A P = B

如果我们定义 Q = P⁻¹,那么方程也可以写成 B = Q A Q⁻¹。因此,相似关系是对称的:如果 A 相似于 B,那么 B 也相似于 A。映射 A → P⁻¹ A P 被称为相似变换。


相似矩阵的性质

以下是相似矩阵的一个关键性质。

定理:如果 n×n 矩阵 AB 相似,那么它们具有相同的特征多项式,因此也具有相同的特征值。

证明
假设 B 相似于 A,即 B = P⁻¹ A P
考虑 B 的特征多项式:
det(B - λI) = det(P⁻¹ A P - λI)
将单位矩阵 I 写作 P⁻¹ P,得到:
det(P⁻¹ A P - λ P⁻¹ P) = det(P⁻¹ (A - λI) P)
根据行列式的性质,det(XY) = det(X) det(Y),因此:
det(P⁻¹ (A - λI) P) = det(P⁻¹) det(A - λI) det(P)
由于 det(P⁻¹) det(P) = 1,所以:
det(B - λI) = det(A - λI)
这表明 AB 的特征多项式相同,因此它们的特征值也完全相同。


矩阵的对角化

现在,我们进入本节课的核心:矩阵的对角化。

定义:一个 n×n 矩阵 A 是可对角化的,如果存在一个可逆矩阵 P 和一个对角矩阵 D,使得:
A = P D P⁻¹
这意味着 A 相似于一个对角矩阵。

对角化在计算矩阵的幂和行列式时非常高效。例如:

  • A² = (P D P⁻¹)(P D P⁻¹) = P D² P⁻¹
  • 推广到 k 次幂:Aᵏ = P Dᵏ P⁻¹
  • 如果 A 可逆,则 A⁻¹ = P D⁻¹ P⁻¹
  • det(A) = det(D),即所有特征值的乘积。

自学习问题示例
给定 A = P D P⁻¹,其中:

P = [[1, 3], [2, 5]], D = [[5, 0], [0, 3]]

Aᵏ 的公式。
Aᵏ = P Dᵏ P⁻¹ = P [[5ᵏ, 0], [0, 3ᵏ]] P⁻¹。通过矩阵乘法即可得到最终表达式。


对角化定理

判断一个矩阵是否可对角化,关键在于其特征向量。

对角化定理:一个 n×n 矩阵 A 是可对角化的(即相似于一个对角矩阵),当且仅当 An 个线性无关的特征向量。

在这种情况下,A 可以分解为 A = P D P⁻¹,其中:

  • 矩阵 P 的列由 An 个线性无关的特征向量组成。
  • 对角矩阵 D 的对角线元素是与 P 中特征向量顺序对应的特征值。

证明思路
假设 An 个线性无关的特征向量 v₁, v₂, ..., vₙ,对应的特征值为 λ₁, λ₂, ..., λₙ
P = [v₁ v₂ ... vₙ]D = diag(λ₁, λ₂, ..., λₙ)
根据特征值定义 A vₖ = λₖ vₖ,可以推导出 A P = P D。由于 P 的列线性无关,P 可逆,因此两边同时左乘 P⁻¹ 即得 A = P D P⁻¹


对角化实例

让我们通过一个例子来实践对角化过程。

问题:如果可能,对角化以下矩阵 A

A = [[-5,  9,  0],
     [-6, 10,  0],
     [-3,  6,  1]]


对角化的步骤是:

  1. 求特征值。
  2. 对每个特征值,求线性无关的特征向量。
  3. 检查是否找到 3 个线性无关的特征向量(因为 A 是 3×3 矩阵)。
  4. 如果能找到,用它们构造 P,并用对应的特征值构造 D

手动计算

  • 特征值为 λ₁ = 1λ₂ = -2(二重根)。
  • 对于 λ₁ = 1,找到一个特征向量,例如 v₁ = [0, 0, 1]ᵀ(经过缩放后可能是 [1, -1, 1]ᵀ)。
  • 对于 λ₂ = -2,求解 (A + 2I)x = 0。通过行化简,发现有两个自由变量,可以得到两个线性无关的特征向量,例如 v₂ = [-1, 1, 0]ᵀv₃ = [-1, 0, 1]ᵀ
  • 因此,我们找到了三个线性无关的特征向量,A 可对角化。
  • 构造:
    P = [v₁, v₂, v₃] (按对应顺序排列)
    D = diag(1, -2, -2) (特征值顺序与P中特征向量顺序一致)
    
    则有 A = P D P⁻¹

计算机辅助(如MATLAB)
使用 [P, D] = eig(A) 函数可以直接得到特征向量矩阵 P 和对角特征值矩阵 D。计算机输出的特征向量形式可能不同(例如,是手动计算得到的特征向量的线性组合),但只要它们线性无关,并且与特征值正确对应,结果就是有效的。


总结

本节课中我们一起学习了:

  1. 矩阵相似性的定义和关键性质:相似矩阵具有相同的特征多项式和特征值。
  2. 矩阵对角化:一个矩阵 A 可对角化意味着它可以表示为 A = P D P⁻¹,这能极大简化矩阵幂等运算。
  3. 对角化定理:矩阵可对角化的充要条件是它拥有 n 个线性无关的特征向量。此时,P 由这些特征向量构成,D 由对应的特征值构成。
  4. 通过实例练习了对角化的具体步骤:求特征值、求特征向量、构造 PD

理解矩阵的对角化是线性代数在人工智能和许多科学计算领域应用的基础,它帮助我们洞察矩阵的结构并简化复杂计算。

036:点积、长度与正交性 📐

在本节中,我们将学习向量运算中的三个核心概念:点积、向量的长度(或范数)以及正交性。这些概念是理解向量空间几何和后续机器学习算法的基础。

点积的定义与计算

点积,也称为内积,是两个向量之间的一种运算。设 uv 是 Rⁿ 空间中的两个向量,它们的点积定义为:

u · v = u₁v₁ + u₂v₂ + ... + uₙvₙ

这等价于将向量 u 转置后与向量 v 进行矩阵乘法:uᵀv。在矩阵乘法 AB 中,矩阵 A 的行向量与矩阵 B 的列向量的点积,就是乘积矩阵中的一个元素。

现在,我们通过一个例子来具体计算点积。

示例: 给定向量 u = [1, -2, 2] 和 v = [3, 2, -4],计算 u · v

根据定义,我们将对应分量相乘后求和:
1 × 3 = 3
(-2) × 2 = -4
2 × (-4) = -8
总和为:3 + (-4) + (-8) = -9

因此,u · v = -9

点积的运算性质

了解点积的运算规则有助于简化计算。设 u, v, w 是 Rⁿ 中的向量,c 是一个标量,则点积满足以下性质:

以下是点积的主要运算性质:

  1. 交换律u · v = v · u
  2. 分配律u · (v + w) = u · v + u · w
  3. 标量乘法结合律(c u) · v = c (u · v) = u · (c v)
  4. 自点积的非负性u · u ≥ 0,且 u · u = 0 当且仅当 u = 0(零向量)。

这些性质在证明定理和优化计算时非常有用。例如,利用分配律可以将两次点积运算合并为一次。

向量的长度与距离

上一节我们介绍了点积,本节中我们来看看如何利用点积定义向量的长度。向量 v 的长度(或欧几里得范数)记作 ||v||,定义为:

||v|| = √(v · v) = √(v₁² + v₂² + ... + vₙ²)

显然,||v||² = v · v。对于任意标量 c,有 ||c v|| = |c| * ||v||,这里需要注意标量要取绝对值。

示例:单位向量
设 W 是 R² 中由向量 v = [3, 4] 张成的子空间。我们希望找到 W 的一个单位正交基 u
首先计算 v 的长度:||v|| = √(3² + 4²) = √25 = 5
则单位向量 u = v / ||v|| = [3/5, 4/5]。所有 u 的标量倍数构成了子空间 W,因此 u 是 W 的一个基。

基于长度,我们可以定义两个向量之间的距离。向量 uv 之间的距离 dist(u, v) 定义为:

dist(u, v) = ||u - v||

示例: 计算 u = [7, 1] 和 v = [3, 2] 之间的距离。
首先,u - v = [4, -1]
然后,dist(u, v) = ||u - v|| = √(4² + (-1)²) = √17

几何上,这个距离就是连接向量 uv 终点的那条线段的长度。

正交性与勾股定理

现在,我们进入核心概念:正交性。如果两个向量的点积为零,则称它们正交:

u · v = 0 ⇔ u 与 v 正交

正交性在几何上对应着两个向量垂直(夹角为90度)。与此紧密相关的是勾股定理在向量空间中的形式:

两个向量 uv 正交,当且仅当 ||u + v||² = ||u||² + ||v||²

这可以直观理解:以 uv 为边构成一个直角三角形,那么 u + v 就是斜边。斜边长度的平方等于两直角边长度的平方和。

向量夹角公式

点积还有一个重要的几何解释,它关联了向量的长度和它们之间的夹角 θ:

u · v = ||u|| ||v|| cos θ

我们可以利用这个公式来求解两个向量之间的夹角。

示例:u = [1, √3] 和 v = [1, 0] 之间的夹角。

  1. 计算点积:u · v = 1×1 + √3×0 = 1。
  2. 计算长度:||u|| = √(1² + (√3)²) = √4 = 2||v|| = √(1² + 0²) = 1
  3. 代入公式:cos θ = (u · v) / (||u|| ||v||) = 1 / (2 × 1) = 1/2。
  4. 因此,夹角 θ = π/3 (60度)。

本节课中我们一起学习了向量的点积、长度(范数)、距离以及正交性。我们看到了点积如何同时具备代数定义和几何意义,并且通过勾股定理和夹角公式将长度与角度联系起来。理解这些概念是掌握向量空间几何和许多人工智能算法(如支持向量机、主成分分析等)的关键第一步。

037:向量范数、矩阵范数与条件数

概述

在本节课中,我们将学习线性代数中几个核心概念:向量范数矩阵范数以及条件数。这些概念是理解数值计算稳定性、误差分析以及许多机器学习算法的基础。我们将从定义开始,逐步介绍常见的范数类型及其计算方法,并探讨它们之间的关系。


5.4:向量范数

上一节我们回顾了线性代数的基础知识,本节中我们来看看如何度量向量的大小,即向量范数。

定义在 Rⁿ 上的向量范数是一个函数,它为每个向量 xRⁿ 分配一个非负数,并满足以下三个性质:

  1. 正定性:对于所有 xRⁿ,有 |x| ≥ 0,且 |x| = 0 当且仅当 x = 0
  2. 齐次性:对于所有 xRⁿ 和标量 λ,有 |λx| = |λ| |x|
  3. 三角不等式:对于所有 x, yRⁿ,有 |x + y| ≤ |x| + |y|

满足这些性质的函数可以定义出多种范数。

以下是两种最常见的范数族:

  • p-范数:对于 p ≥ 1,p-范数定义为:
    \|x\|_p = (|x₁|^p + |x₂|^p + ... + |x_n|^p)^(1/p)
  • 最大范数(无穷范数):定义为向量中绝对值最大的分量:
    \|x\|_∞ = max(|x₁|, |x₂|, ..., |x_n|)

在p-范数中,最常用的是1-范数和2-范数。

  • 1-范数\|x\|_1 = |x₁| + |x₂| + ... + |x_n|
  • 2-范数\|x\|_2 = sqrt(|x₁|² + |x₂|² + ... + |x_n|²)

2-范数也称为欧几里得范数。通常,当范数符号没有下标时,默认指的就是2-范数。

示例

让我们通过一个例子来计算不同范数。设向量 x = [4, -2, 2, -4, 3]ᵀ。

  • 1-范数\|x\|_1 = |4| + |-2| + |2| + |-4| + |3| = 4 + 2 + 2 + 4 + 3 = 15
  • 2-范数\|x\|_2 = sqrt(4² + (-2)² + 2² + (-4)² + 3²) = sqrt(16 + 4 + 4 + 16 + 9) = sqrt(49) = 7
  • 无穷范数\|x\|_∞ = max(|4|, |-2|, |2|, |-4|, |3|) = max(4, 2, 2, 4, 3) = 4

在这个例子中,1-范数值最大,无穷范数值最小,2-范数居中。一般来说,对于任意 xRⁿ,有以下不等式关系:
\|x\|_∞ ≤ \|x\|_2 ≤ \|x\|_1
你可以尝试证明这个不等式。


矩阵范数

理解了向量范数后,我们可以将其推广到矩阵上,定义矩阵范数。

定义在 m × n 矩阵空间上的矩阵范数,本质上是一个定义在 m×n 维空间(将矩阵视为长向量)上的向量范数,同样需要满足正定性、齐次性和三角不等式。

我们可以定义多种矩阵范数。一个例子是弗罗贝尼乌斯范数
\|A\|_F = sqrt(Σ_i Σ_j |a_ij|²)
这个值可以通过公式 \|A\|_F = sqrt(trace(AᵀA)) 计算,其中 trace 是矩阵的迹(主对角线元素之和)。

另一种非常重要的矩阵范数是由向量范数诱导出的算子范数(或从属范数)。给定一个向量范数 \|·\|,其诱导的矩阵范数定义为:
\|A\| = max_{x ≠ 0} ( \|Ax\| / \|x\| )
这个定义可以等价地理解为:\|A\| = max_{\|x\|=1} \|Ax\|。它衡量了矩阵 A 对向量 x 的最大“拉伸”倍数。

常见诱导范数的计算

对于由特定向量p-范数诱导出的矩阵范数,有直接的计算公式:

  • 矩阵1-范数(列和范数):由向量1-范数诱导。计算方法是求矩阵各列元素绝对值之和的最大值。
    \|A\|_1 = max_j ( Σ_i |a_ij| )
  • 矩阵无穷范数(行和范数):由向量无穷范数诱导。计算方法是求矩阵各行元素绝对值之和的最大值。
    \|A\|_∞ = max_i ( Σ_j |a_ij| )
  • 矩阵2-范数(谱范数):由向量2-范数诱导。计算方法是求矩阵 AᵀA 的最大特征值的平方根。
    \|A\|_2 = sqrt( λ_max(AᵀA) )
    如果矩阵 A 是正规矩阵(例如对称矩阵),那么 \|A\|_2 等于 A 的绝对值最大的特征值。

记忆技巧:计算1-范数时按列求和(1像一根竖线),计算无穷范数时按行求和(无穷符号像横躺的8,代表行)。


条件数

在学习了矩阵范数之后,我们可以用它来定义一个衡量矩阵“病态”程度的重要指标——条件数。

对于一个可逆矩阵 A,其条件数定义为:
cond(A) = \|A\| \|A⁻¹\|
其中,\|·\| 是某种矩阵范数。条件数与所选的矩阵范数相关,因此我们常称之为 L1条件数L2条件数L∞条件数

条件数反映了线性方程组 Ax = b 的解 x 对于输入数据 bA 中微小扰动的敏感程度。条件数越大,矩阵越“病态”,求解时对误差越敏感。

示例

设矩阵 A = [[2, 0, 0], [0, 3, -7], [0, -7, 3]],其逆矩阵 A⁻¹ = [[1/2, 0, 0], [0, 3/40, 7/40], [0, 7/40, 3/40]]。

  1. 计算 |A|₁:求各列绝对值之和的最大值。
    第一列:|2|+|0|+|0|=2;第二列:|0|+|3|+|-7|=10;第三列:|0|+|-7|+|3|=10。
    因此,\|A\|₁ = max(2, 10, 10) = 10

  2. 计算 |A|_∞:求各行绝对值之和的最大值。
    第一行:|2|+|0|+|0|=2;第二行:|0|+|3|+|-7|=10;第三行:|0|+|-7|+|3|=10。
    因此,\|A\|_∞ = max(2, 10, 10) = 10

  3. 计算 |A|₂:需要求 AᵀA 的最大特征值。经计算,AᵀA 的特征值为 2, 2, 16。最大特征值为 16。
    因此,\|A\|₂ = sqrt(16) = 4

  4. 计算 L1 条件数cond₁(A) = \|A\|₁ \|A⁻¹\|₁
    已知 \|A\|₁ = 10。计算 \|A⁻¹\|₁:求 A⁻¹ 各列绝对值之和的最大值。
    第一列:|1/2|+0+0=0.5;第二列:0+|3/40|+|7/40|=0.25;第三列:0+|7/40|+|3/40|=0.25。
    因此,\|A⁻¹\|₁ = max(0.5, 0.25, 0.25) = 0.5
    最终,cond₁(A) = 10 * 0.5 = 5


补充说明

  1. 无穷范数与p-范数的关系:向量无穷范数可以看作是p-范数当 p 趋于无穷大时的极限,即 \|x\|_∞ = lim_{p→∞} \|x\|_p。直观上,当p非常大时,绝对值最大的分量在求和式中占主导地位,其p次方根就趋近于该最大值。

  1. 矩阵2-范数的等价定义:矩阵2-范数还有另一个有用的等价定义:
    \|A\|_2 = max_{\|x\|_2=\|y\|_2=1} |yᵀAx|
    基于此,可以证明一些性质,例如 \|Aᵀ\|_2 = \|A\|_2,以及 \|AᵀA\|_2 = \|A\|_2²

总结

本节课我们一起学习了线性代数中的核心度量工具。

  • 我们首先定义了向量范数,并介绍了常见的1-范数、2-范数和无穷范数及其计算方法。
  • 接着,我们将概念推广到矩阵范数,重点讲解了由向量范数诱导出的算子范数,并给出了1-范数、2-范数和无穷范数对应的矩阵范数计算公式。
  • 最后,我们利用矩阵范数定义了条件数,它用于评估矩阵求逆和线性方程组求解的数值稳定性,并通过一个例子演示了如何计算。

理解这些概念对于后续学习数值算法、优化理论以及机器学习模型中的正则化等都至关重要。

038:第38讲 - 特征值幂法与逆幂法 🧮

概述

在本节课中,我们将学习两种用于计算矩阵特征值和特征向量的数值方法:幂法和逆幂法。这两种方法都是迭代算法,能够分别找到矩阵模最大的特征值(及其特征向量)和最接近某个给定值的特征值。

幂法

上一节我们介绍了特征值问题的背景,本节中我们来看看如何通过幂法找到矩阵模最大的特征值。

幂法是一种迭代算法,适用于方阵。该算法旨在找到一个数 μ,它是矩阵 A模最大的特征值,以及其对应的特征向量 v

要应用幂法,我们需要假设矩阵 A 满足以下性质:

  1. An 个特征值 λ₁, λ₂, ..., λₙ
  2. An 个对应的线性无关的特征向量 v₁, v₂, ..., vₙ
  3. 存在一个模最大的特征值 λ₁,且其模严格大于其他所有特征值的模,即 |λ₁| > |λ₂| ≥ |λ₃| ≥ ... ≥ |λₙ|

算法推导

我们假设 An 个线性无关的特征向量,因此它们构成一组基。对于任意向量 x₀,它可以表示为这些特征向量的线性组合:

x₀ = β₁v₁ + β₂v₂ + ... + βₙvₙ

现在,我们连续用矩阵 A 乘以 x₀

  • x₁ = A x₀ = β₁λ₁v₁ + β₂λ₂v₂ + ... + βₙλₙvₙ
  • x₂ = A x₁ = A² x₀ = β₁λ₁²v₁ + β₂λ₂²v₂ + ... + βₙλₙ²vₙ
  • ...
  • 经过 k 次迭代后:xₖ = Aᵏ x₀ = β₁λ₁ᵏv₁ + β₂λ₂ᵏv₂ + ... + βₙλₙᵏvₙ

提取公因子 λ₁ᵏ,我们得到:

xₖ = λ₁ᵏ [ β₁v₁ + β₂(λ₂/λ₁)ᵏ v₂ + ... + βₙ(λₙ/λ₁)ᵏ vₙ ]

由于 |λ₁| > |λ₂| ≥ ... ≥ |λₙ|,比值 |λᵢ/λ₁| < 1(对于 i ≥ 2)。因此,当 k 增大时,(λᵢ/λ₁)ᵏ 项会趋近于零。最终,xₖ 的方向将趋近于特征向量 v₁ 的方向。

为了避免 xₖ 的模无限增大或减小到零,我们在每一步迭代后进行缩放,通常使其无穷范数(即绝对值最大的分量)为 1。

算法步骤

以下是幂法的具体迭代步骤:

  1. 选择初始向量 x₀,并缩放使其满足 ||x₀||∞ = 1
  2. 对于 k = 0, 1, 2, ...,重复以下步骤:
    a. 计算 y = A xₖ
    b. 估计特征值:μₖ = (y)ᵢ,其中 iy 中绝对值最大分量的索引。
    c. 缩放得到新向量:xₖ₊₁ = y / μₖ

随着迭代进行,μₖ 收敛到 |λ₁|xₖ 收敛到对应的特征向量 v₁(可能需要根据 μₖ 的符号调整 λ₁ 的符号)。收敛速度由比值 |λ₂/λ₁| 决定,该比值越小,收敛越快。

代码示例

以下是在 MATLAB 和 Python 中实现幂法的核心代码逻辑。

MATLAB 实现核心部分:

x = [1; 0; 0]; % 初始向量
for k = 1:max_iter
    y = A * x;
    [mu, idx] = max(abs(y));
    mu = y(idx);
    x = y / mu;
    % 记录 mu 和 x 以观察收敛
end

Python 实现核心部分 (使用 NumPy):

import numpy as np
x = np.array([1., 0., 0.]) # 初始向量
for k in range(max_iter):
    y = A @ x
    idx = np.argmax(np.abs(y))
    mu = y[idx]
    x = y / mu
    # 记录 mu 和 x 以观察收敛

逆幂法

上一节我们学习了如何寻找模最大的特征值,本节中我们来看看逆幂法,它用于寻找最接近某个给定值 q 的特征值。

逆幂法是幂法的一个变体,用于解决以下问题:给定一个标量 q,找到矩阵 A 的特征值中最接近 q 的那个特征值及其特征向量。

算法原理

逆幂法的核心思想基于以下事实:
如果 (λ, v)A 的一个特征对,即 A v = λ v,那么对于矩阵 (A - qI)⁻¹,有:
(A - qI)⁻¹ v = (1/(λ - q)) v

这意味着 v 也是 (A - qI)⁻¹ 的特征向量,其对应的特征值为 1/(λ - q)

现在,对矩阵 (A - qI)⁻¹ 应用幂法。幂法会找到该矩阵模最大的特征值。哪个 1/(λᵢ - q) 的模最大呢?那就是分母 |λᵢ - q| 最小的那个,即 λᵢ 最接近 q 的那个特征值。

因此,对 (A - qI)⁻¹ 应用幂法,迭代向量将收敛到最接近 q 的特征值 λᵢ 所对应的特征向量 vᵢ,而估计的特征值 μₖ 将收敛到 1/(λᵢ - q)。由此我们可以恢复出 λᵢ
λᵢ ≈ q + 1/μₖ

算法步骤

逆幂法的迭代步骤与幂法类似,但作用在 (A - qI)⁻¹ 上:

  1. 选择初始向量 x₀,并缩放使其满足 ||x₀||∞ = 1。给定目标值 q
  2. 对于 k = 0, 1, 2, ...,重复以下步骤:
    a. 解线性方程组:(A - qI) y = xₖ 得到 y。这等价于计算 y = (A - qI)⁻¹ xₖ,但通常通过求解方程组来实现以避免显式求逆。
    b. 估计中间量:μₖ = (y)ᵢ,其中 iy 中绝对值最大分量的索引。
    c. 缩放得到新向量:xₖ₊₁ = y / μₖ
  3. 近似特征值:λ ≈ q + 1/μₖ

逆幂法的收敛速度由 |(λᵢ - q) / (λⱼ - q)| 决定,其中 λⱼ 是第二接近 q 的特征值。当 q 非常接近某个特征值时,收敛会非常快。

代码示例

以下是在 MATLAB 和 Python 中实现逆幂法的核心代码逻辑。

MATLAB 实现核心部分:

q = 4; % 给定的目标值
x = [1; 0; 0]; % 初始向量
for k = 1:max_iter
    % 解方程组 (A - qI) * y = x,而不是直接求逆
    y = (A - q*eye(size(A))) \ x;
    [mu, idx] = max(abs(y));
    mu = y(idx);
    x = y / mu;
    lambda_approx = q + 1/mu; % 恢复原矩阵的特征值近似值
    % 记录 lambda_approx 和 x 以观察收敛
end

Python 实现核心部分 (使用 NumPy 和 SciPy):

import numpy as np
from scipy import linalg
q = 4.0
x = np.array([1., 0., 0.])
for k in range(max_iter):
    # 解方程组 (A - qI) * y = x
    y = linalg.solve(A - q * np.eye(A.shape[0]), x)
    idx = np.argmax(np.abs(y))
    mu = y[idx]
    x = y / mu
    lambda_approx = q + 1.0 / mu # 恢复原矩阵的特征值近似值
    # 记录 lambda_approx 和 x 以观察收敛

总结

本节课中我们一起学习了两种重要的特征值迭代算法:

  1. 幂法:用于求解矩阵模最大的特征值及其对应的特征向量。其收敛速度取决于最大与次大特征值模的比值 |λ₂/λ₁|
  2. 逆幂法:是幂法的推广,用于求解最接近给定数值 q 的特征值及其特征向量。通过对方阵 (A - qI)⁻¹ 应用幂法实现。当 q 接近某个特征值时,收敛速度极快。

这两种方法都是求单个特征对的有效工具。要同时计算所有特征值,需要使用更高级的算法,例如我们将在后续课程中介绍的 QR 算法。

039:第39讲 - 多变量函数及其偏导数 📚

在本节课中,我们将学习多变量微积分的基础知识,包括多变量函数的定义、其图像表示,以及如何计算偏导数。这些概念是理解后续梯度、拉格朗日乘数法和梯度下降法等高级主题的基石。

6.1 多变量函数及其偏导数 🔍

在之前的课程中,我们学习了单变量函数。它有一个自变量 x 和一个因变量 y,可以写作 y = f(x)。本节中,我们将这个概念扩展到两个自变量的情况。

双变量函数的定义

一个双变量函数 f 是一个规则,它为每一个有序实数对 (x, y) 分配一个唯一的实数 z。记作 z = f(x, y)。其中,xy 是自变量,z 是因变量。所有可能的输入 (x, y) 的集合称为函数 f 的定义域 D,所有输出 z 的集合称为值域。

与单变量函数图像为曲线不同,双变量函数的图像是一个曲面。我们可以建立一个三维坐标系:x 轴和 y 轴构成水平面,z 轴垂直向上。定义域 Dxy 平面上的一个区域,而函数的图像则是这个区域上方的曲面,曲面上每一点的坐标是 (x, y, z),其中 z = f(x, y)

示例:函数求值与定义域

考虑函数 f(x, y) = sqrt(x + y + 1) / (x - 1)

1. 求 f(3, 2) 的值:
x=3, y=2 代入公式:
分子:sqrt(3 + 2 + 1) = sqrt(6)
分母:3 - 1 = 2
因此,f(3, 2) = sqrt(6) / 2

2. 求其自然定义域:
自然定义域是使函数有意义的 (x, y) 点的集合。需要满足两个条件:

  • 根号内非负:x + y + 1 >= 0
  • 分母不为零:x - 1 != 0,即 x != 1

因此,定义域 D 是所有满足 y >= -x - 1x != 1 的点 (x, y)。在 xy 平面上,这是直线 y = -x - 1 上方(包含该直线)的区域,但需去掉垂直于 x=1 的整条直线。

示例:定义域与值域

求函数 f(x, y) = sqrt(9 - x^2 - y^2) 的定义域和值域。

定义域:
为使根号内非负,需满足 9 - x^2 - y^2 >= 0,即 x^2 + y^2 <= 9
这表示定义域是以原点为中心、半径为3的圆盘(包含边界)。

值域:
根号值始终非负。当 x=0, y=0(圆心)时,函数取得最大值 f(0,0) = sqrt(9) = 3
当点 (x, y) 位于边界 x^2 + y^2 = 9 上时,函数取得最小值 0
因此,函数的值域是 [0, 3]

偏导数的引入 📈

在单变量微积分中,函数 y = f(x) 在点 a 的导数定义为:
f'(a) = lim (h -> 0) [f(a+h) - f(a)] / h
它表示函数图像在点 (a, f(a)) 处切线的斜率。

对于双变量函数 z = f(x, y),我们如何定义“变化率”呢?由于有两个自变量,我们需要分别考虑每个方向上的变化率,这就是偏导数。

偏导数的定义

关于 x 的偏导数:
假设我们将 y 固定为一个常数 b,即只让 x 变化。这定义了一个单变量函数 g(x) = f(x, b)。函数 f 在点 (a, b) 处关于 x 的偏导数,就是 g(x)x=a 处的普通导数。记作 f_x(a, b)∂f/∂x |_(a,b)

其极限定义为:
f_x(a, b) = lim (h -> 0) [f(a+h, b) - f(a, b)] / h

几何上,它表示曲面 z = f(x, y) 与平面 y = b 相交所得的曲线,在点 (a, b, f(a,b)) 处切线的斜率。这条切线平行于 xz 平面。

关于 y 的偏导数:
类似地,固定 x = a,定义函数 h(y) = f(a, y)。函数 f 在点 (a, b) 处关于 y 的偏导数,就是 h(y)y=b 处的普通导数。记作 f_y(a, b)∂f/∂y |_(a,b)

其极限定义为:
f_y(a, b) = lim (h -> 0) [f(a, b+h) - f(a, b)] / h

几何上,它表示曲面 z = f(x, y) 与平面 x = a 相交所得的曲线,在点 (a, b, f(a,b)) 处切线的斜率。这条切线平行于 yz 平面。

计算示例

根据定义,求函数 f(x, y) = (x^3 + y^3)^(1/3) 在点 (0,0) 处关于 x 的偏导数 f_x(0,0)

根据定义:
f_x(0,0) = lim (h -> 0) [f(h, 0) - f(0, 0)] / h
= lim (h -> 0) [(h^3 + 0^3)^(1/3) - (0^3 + 0^3)^(1/3)] / h
= lim (h -> 0) [h - 0] / h
= lim (h -> 0) 1
= 1

因此,该函数在原点处沿 x 方向的斜率为1。

偏导函数

通常,我们不只计算在某一点的偏导数,而是计算作为 (x, y) 函数的偏导函数。

  • 关于 x 的偏导函数:f_x(x, y) = lim (h -> 0) [f(x+h, y) - f(x, y)] / h
  • 关于 y 的偏导函数:f_y(x, y) = lim (h -> 0) [f(x, y+h) - f(x, y)] / h

计算规则:
f_x 时,将 y 视为常数,对 x 求导。
f_y 时,将 x 视为常数,对 y 求导。

综合计算示例

对于函数 f(x, y) = x^3 + x^2 y^3 - 2y^2

  1. 求一阶偏导函数 f_xf_y

    • f_x: 将 y 视作常数。∂/∂x (x^3) = 3x^2∂/∂x (x^2 y^3) = 2x y^3∂/∂x (-2y^2) = 0。所以 f_x = 3x^2 + 2x y^3
    • f_y: 将 x 视作常数。∂/∂y (x^3) = 0∂/∂y (x^2 y^3) = 3x^2 y^2∂/∂y (-2y^2) = -4y。所以 f_y = 3x^2 y^2 - 4y
  2. 求二阶混合偏导 f_{xy}(即先对 x 求偏导,再对 y 求偏导)。

    • 对上面求得的 f_x = 3x^2 + 2x y^3 再关于 y 求偏导(视 x 为常数)。
    • ∂/∂y (3x^2) = 0∂/∂y (2x y^3) = 2x * 3y^2 = 6x y^2
    • 所以 f_{xy} = 6x y^2
  3. 在点 (2, 1) 处计算这些偏导的值。

    • f_x(2, 1) = 3*(2)^2 + 2*(2)*(1)^3 = 12 + 4 = 16
    • f_y(2, 1) = 3*(2)^2*(1)^2 - 4*(1) = 12 - 4 = 8
    • f_{xy}(2, 1) = 6*(2)*(1)^2 = 12

扩展到三变量函数 🔄

上述概念可以自然地推广到三个或更多变量的函数。对于一个三变量函数 w = f(x, y, z)

  • f_x 时,将 yz 视为常数,对 x 求导。
  • f_y 时,将 xz 视为常数,对 y 求导。
  • f_z 时,将 xy 视为常数,对 z 求导。

三变量函数示例

对于函数 f(x, y, z) = sin( (x z) / (1 + y) ),求其三个一阶偏导数。

  • f_x: 将 y, z 视为常数。使用链式法则,外函数 sin(u) 的导数是 cos(u),内函数 u = (x z)/(1+y)x 的导数是 z/(1+y)。所以 f_x = cos( (x z)/(1+y) ) * [z/(1+y)]
  • f_y: 将 x, z 视为常数。外函数导数仍为 cos(u),内函数 u = (x z)/(1+y)y 的导数是 (x z) * (-1)/(1+y)^2 = - (x z)/(1+y)^2。所以 f_y = cos( (x z)/(1+y) ) * [ - (x z)/(1+y)^2 ]
  • f_z: 将 x, y 视为常数。外函数导数为 cos(u),内函数 u = (x z)/(1+y)z 的导数是 x/(1+y)。所以 f_z = cos( (x z)/(1+y) ) * [x/(1+y)]

总结 📝

本节课我们一起学习了多变量微积分的入门知识:

  1. 多变量函数:我们定义了双变量函数 z = f(x, y),其图像是三维空间中的曲面,并学习了如何确定其定义域和值域。
  2. 偏导数:我们引入了偏导数的概念,它描述了多变量函数在某一坐标轴方向上的变化率。f_x 表示固定 y 时函数沿 x 方向的变化率,f_y 表示固定 x 时函数沿 y 方向的变化率。我们学习了通过将其他变量视为常数来求偏导数的计算规则。
  3. 几何意义:偏导数 f_x(a, b)f_y(a, b) 分别代表了曲面在点 (a, b) 处,平行于 xz 平面和 yz 平面的切线斜率。
  4. 推广:我们将偏导数的概念简单推广到了三个自变量的函数。

理解多变量函数和偏导数是学习后续梯度向量、方向导数以及优化算法(如梯度下降法)的重要基础。

040:方向导数与梯度矢量 📈

在本节课中,我们将学习多元函数中两个核心概念:方向导数和梯度矢量。我们将了解如何计算函数在任意方向上的变化率,并探索梯度矢量如何指示函数增长最快的方向。

6.2 方向导数与梯度矢量

对于二元函数 Z = f(x, y),其偏导数 ∂f/∂x∂f/∂y 分别表示函数在 x 轴和 y 轴方向上的变化率。换句话说,它们分别对应单位向量 ij 方向上的导数。

然而,我们常常需要知道函数在任意方向上的变化率。考虑一个任意的单位向量 u = (a, b),我们想要找到曲面在该方向上的切线斜率。

方向导数的定义

函数 f 在点 (x₀, y₀) 处沿单位向量 u = (a, b) 的方向导数定义为:

这个定义测量了当点 (x₀, y₀) 沿 u 方向移动一个微小距离 h 时,函数值 f 的平均变化率在 h 趋近于 0 时的极限。

方向导数的计算公式

如果函数 f 是可微的,那么我们可以推导出方向导数的计算公式。通过对定义式进行代数变换,我们可以将其分解为与 xy 偏导数相关的部分。

h 趋近于 0 时,上述表达式中的两个部分分别收敛于 fxy 方向上的偏导数。因此,我们得到以下定理:

定理:如果 f 是关于 xy 的可微函数,那么 f 在任意单位向量 u = (a, b) 方向上都有方向导数,并且其计算公式为:

这个结果可以简洁地写成两个向量的点积形式。我们稍后会定义其中一个向量为梯度矢量。

示例计算

让我们通过一个例子来应用这个公式。

示例 1:设 f(x, y) = x³ + 2xy + 3y²。求在点 (2, 3) 处,沿角度 θ = π/4(45度)方向的单位向量 u 的方向导数 D_u f

首先,确定方向向量 u。对于角度 π/4,单位向量为:
u = (cos(π/4), sin(π/4)) = (1/√2, 1/√2)

接着,计算函数在点 (2, 3) 处的偏导数:

  • ∂f/∂x = 3x² + 2y。在 (2, 3) 处,值为 3(2)² + 23 = 12 + 6 = 18
  • ∂f/∂y = 2x + 6y。在 (2, 3) 处,值为 22 + 63 = 4 + 18 = 22

最后,应用公式计算方向导数:
D_u f(2,3) = (∂f/∂x)a + (∂f/∂y)b = 18(1/√2) + 22(1/√2) = 40/√2 = 20√2

你可以尝试用类似方法计算沿 θ = π/3(60度)方向的方向导数。

扩展到三元函数

方向导数的概念可以自然地扩展到三元甚至更多元的函数。

示例 2:设 f(x, y, z) = x² - 2y² + z⁴。求在点 (1, 3, 1) 处,沿向量 v = (2, -2, -1) 方向的方向导数。

首先,将方向向量 v 单位化得到 u
|v| = √(2² + (-2)² + (-1)²) = √(4+4+1) = √9 = 3
u = v / |v| = (2/3, -2/3, -1/3)

接着,计算函数在点 (1, 3, 1) 处的偏导数:

  • ∂f/∂x = 2x, 在 (1,3,1) 处值为 2
  • ∂f/∂y = -4y, 在 (1,3,1) 处值为 -12
  • ∂f/∂z = 4z³, 在 (1,3,1) 处值为 4

最后,计算方向导数:
D_u f(1,3,1) = (2, -12, 4) · (2/3, -2/3, -1/3) = (4/3) + (24/3) + (-4/3) = 24/3 = 8

梯度矢量 🧭

上一节我们介绍了方向导数的计算,其中涉及一个由偏导数组成的向量。这个向量极其重要,我们将其定义为梯度矢量。

定义与表示

对于一个可微的二元函数 f(x, y),其在点 (x, y) 处的梯度矢量(记作 ∇fgrad f)定义为:

∇f(x, y) = (∂f/∂x, ∂f/∂y)

我们也可以用单位向量 ij 来表示:∇f = (∂f/∂x) i + (∂f/∂y) j

示例 3:求函数 f(x, y) = sin x + e^(xy) 在点 (0, 1) 处的梯度。

  • ∂f/∂x = cos x + y e^(xy)。在 (0,1) 处,cos0=1y e^(0)=1*1=1,所以值为 2
  • ∂f/∂y = 0 + x e^(xy)。在 (0,1) 处,值为 0 * e^0 = 0
    因此,∇f(0,1) = (2, 0)

梯度与方向导数的关系

利用梯度矢量,方向导数的公式可以写得非常简洁:

D_u f(x, y) = ∇f(x, y) · u

其中 · 表示点积。这个公式是计算方向导数最常用的方式。

示例 4:设 f(x, y) = x²y³ - 4y。求在点 (2, -1) 处,沿向量 v = (3, 4) 方向的方向导数。

首先,将 v 单位化:|v| = 5,所以 u = (3/5, 4/5)
接着,计算梯度:

  • ∂f/∂x = 2xy³。在 (2, -1) 处,值为 22(-1)³ = -4
  • ∂f/∂y = 3x²y² - 4。在 (2, -1) 处,值为 341 - 4 = 12 - 4 = 8
    所以,∇f(2, -1) = (-4, 8)
    最后,计算点积:D_u f = (-4, 8) · (3/5, 4/5) = (-12/5) + (32/5) = 20/5 = 4

最大化方向导数 ⬆️

理解了方向导数与梯度的关系后,我们来看一个关键应用:如何找到函数增长最快的方向。

理论推导

根据公式 D_u f = ∇f · u = |∇f| |u| cosθ,其中 θ 是梯度矢量 ∇f 与方向向量 u 之间的夹角。
由于 u 是单位向量(|u|=1),上式简化为 D_u f = |∇f| cosθ

余弦函数 cosθ 的最大值为 1,当且仅当 θ = 0 时取得。这意味着 u 的方向与梯度 ∇f 的方向完全一致。

定理:可微函数 f 在给定点处,方向导数的最大值等于该点梯度矢量的模(长度)|∇f|,并且这个最大值发生在梯度矢量所指的方向上。

换句话说,梯度方向是函数值增加最快的方向

示例与应用

示例 5:设 f(x, y) = x e^y

  1. 求在点 P(1, 0) 处,沿 PQ(0, 2) 方向的方向导数。
    • 方向向量 v = Q - P = (-1, 2)。单位化:|v| = √5u = (-1/√5, 2/√5)
    • 梯度 ∇f = (e^y, x e^y)。在 (1,0) 处,∇f(1,0) = (1, 1)
    • D_u f = (1, 1) · (-1/√5, 2/√5) = (-1+2)/√5 = 1/√5
  2. 函数在 P 点沿哪个方向增加最快?最大增加速率是多少?
    • 根据定理,增加最快的方向就是梯度方向,即 ∇f(1,0) = (1, 1) 的方向。将其单位化:u_max = (1/√2, 1/√2)
    • 最大增加速率等于梯度的模:|∇f(1,0)| = |(1,1)| = √2

我们可以这样直观理解:对于一元函数 y = f(x),其导数 f'(x) 的正负指示了增加或减少的方向,其绝对值大小指示了变化的快慢。梯度将这个概念推广到了多元函数,它指向了“最陡峭的上山”方向。

总结

本节课我们一起学习了多元微积分中的两个核心工具:

  1. 方向导数:它度量了函数在任意指定方向上的瞬时变化率。计算公式为 D_u f = ∇f · u
  2. 梯度矢量:它是一个由函数所有一阶偏导数组成的向量,∇f = (∂f/∂x, ∂f/∂y)。梯度具有极其重要的几何意义:
    • 梯度的方向是函数在该点处增加最快的方向。
    • 梯度的模(长度)等于该方向上的最大增加速率

理解梯度的这一性质,对于后续学习优化算法(如梯度下降法)至关重要。

041:等式约束优化 🧮

在本节课中,我们将学习带有等式约束的优化问题,并掌握一种强大的求解工具——拉格朗日乘数法。我们将从几何直观入手,理解其原理,并通过具体例子学习如何应用它来求解最大值和最小值问题。

梯度与等高线 📈

上一节我们提到,梯度方向是函数值增长最快的方向。本节我们将从等高线开始,深入理解这一概念。

考虑一个简单的例子:单位圆。其方程由 x² + y² = 1 给出。我们可以将方程左边定义为一个函数 F(x, y) = x² + y²

那么,关于函数 F 的梯度,我们能得出什么结论呢?

为了回答这个问题,我们引入参数化。曲线可以用以下方程参数化表示:
r(t) = [x(t), y(t)] = [cos(t), sin(t)],其中 t0 变化到

现在,F 可以看作是 t 的函数。我们使用链式法则求 Ft 的导数:
dF/dt = (∂F/∂x)(dx/dt) + (∂F/∂y)(dy/dt) = (2x)(-sin t) + (2y)(cos t)

在圆上的任意点 (x, y),这个导数向量 [-2x sin t + 2y cos t] 实际上与向量 [-sin t, cos t] 方向一致,后者正是圆的切向量。同时,F 的梯度 ∇F = [2x, 2y]

计算 ∇F 与切向量的点积:[2x, 2y] · [-sin t, cos t] = -2x sin t + 2y cos t = 0。这表明梯度 ∇F 与曲线的切向量正交。

从几何上看,梯度是曲线的法向量,指向函数值增长最快的方向。在单位圆内部,F(x, y) < 1;在圆上,F(x, y) = 1;在圆外部,F(x, y) > 1。梯度方向正是从圆内指向圆外的方向。

我们可以将这一观察推广为一般结论:

给定一条等高线 F(x, y) = K,其梯度向量 ∇F 垂直于该曲线,并指向函数值增长最快的方向。

等式约束优化问题与拉格朗日乘数法 🎯

现在,我们准备考虑带有等式约束的优化问题,并介绍一种有效的求解技术。

我们的目标是:在满足约束条件 G(x) = c 的前提下,最大化函数 F(x)。这意味着我们必须从所有满足该等式的点 x 中,找出使 F(x) 取得最大值的点。

让我们通过一个几何图像来理解。假设我们想最大化函数 z = F(x, y),但点 (x, y) 必须位于曲线 G(x, y) = c 上。我们画出 F 的若干条等高线(F = T1, T2, T3...,其中 T1 > T2 > T3)。

我们知道,F 的梯度 ∇F 垂直于其等高线,并指向 F 增长最快的方向。同时,约束曲线 G(x, y) = c 本身也是一条等高线,其梯度 ∇G 垂直于该约束曲线。

在最大值点处,F 的某条等高线(例如 F = T_max)必然与约束曲线 G = c 相切。因为如果它们相交,我们就可以沿着约束曲线移动到 F 值更大或更小的点,该点就不是极值点。

在切点处,两条曲线拥有共同的切线。因此,它们的法向量,即梯度 ∇F∇G,必须彼此平行。于是我们得到关键结论:

在约束优化问题的极值点(最大值或最小值)处,目标函数的梯度 ∇F 与约束函数的梯度 ∇G 平行。

由此,我们推导出拉格朗日乘数法

对于寻找函数 F(x, y, z) 在约束 G(x, y, z) = c 下的极值问题,该方法步骤如下:

  1. 引入一个辅助变量 λ(称为拉格朗日乘子)。
  2. 构造拉格朗日函数:L(x, y, z, λ) = F(x, y, z) - λ (G(x, y, z) - c)
  3. L 对所有变量(包括 λ)的偏导数,并令其为零:
    • ∂L/∂x = 0 -> ∂F/∂x - λ ∂G/∂x = 0
    • ∂L/∂y = 0 -> ∂F/∂y - λ ∂G/∂y = 0
    • ∂L/∂z = 0 -> ∂F/∂z - λ ∂G/∂z = 0
    • ∂L/∂λ = 0 -> G(x, y, z) - c = 0
      这等价于求解方程组:∇F = λ ∇GG(x, y, z) = c
  4. 解这个方程组,得到所有可能的候选点 (x, y, z) 及对应的 λ
  5. 计算所有候选点处的函数值 F(x, y, z)。其中最大的即为约束下的最大值,最小的即为约束下的最小值。

应用示例:最大体积的纸盒 📦

让我们通过一个具体问题来实践拉格朗日乘数法。

问题:用 12 平方米的纸板制作一个无盖长方体纸盒。求使纸盒体积最大的尺寸。

解答
设长方体的长、宽、高分别为 x, y, z

  • 目标函数(体积):F(x, y, z) = x y z(需要最大化)。
  • 约束条件(纸板总面积):底面 xy + 前后面 2xz + 左右面 2yz = 12
    即约束函数:G(x, y, z) = xy + 2xz + 2yz = 12

现在应用拉格朗日乘数法。我们需要解方程组 ∇F = λ ∇GG = 12

计算梯度:
∇F = (yz, xz, xy)
∇G = (y+2z, x+2z, 2x+2y)

得到方程组:

  1. yz = λ (y + 2z)
  2. xz = λ (x + 2z)
  3. xy = λ (2x + 2y)
  4. xy + 2xz + 2yz = 12

这个方程组看起来复杂,但可以利用对称性简化。分别用 x, y, z 乘方程 (1), (2), (3)
1‘. x y z = λ (xy + 2xz)
2‘. x y z = λ (xy + 2yz)
3‘. x y z = λ (2xz + 2yz)

由于体积 xyz > 0,且 λ 不可能为零(否则梯度为零,与物理意义不符),因此方程 1‘, 2‘, 3‘ 的右边必须相等。
1‘2‘ 右边相等可得:xy + 2xz = xy + 2yz -> 2xz = 2yz -> x = y (假设 z ≠ 0)。
2‘3‘ 右边相等可得:xy + 2yz = 2xz + 2yz -> xy = 2xz -> y = 2z (假设 x ≠ 0)。

因此我们得到关系:x = yy = 2z,即 x = 2z, y = 2z

代入约束方程 (4)
(2z)(2z) + 2(2z)z + 2(2z)z = 12
4z² + 4z² + 4z² = 12
12z² = 12
z² = 1 -> z = 1 (取正值)。

于是,x = 2, y = 2, z = 1

最大体积为 V = x y z = 2 * 2 * 1 = 4 立方米。

应用示例:圆上的极值 🔵

问题:求函数 F(x, y) = x² + 2y² 在单位圆 x² + y² = 1 上的极值。

解答
目标函数:F(x, y) = x² + 2y²
约束函数:G(x, y) = x² + y² = 1

建立拉格朗日方程组 ∇F = λ ∇G
∇F = (2x, 4y)
∇G = (2x, 2y)

方程组为:

  1. 2x = λ (2x)
  2. 4y = λ (2y)
  3. x² + y² = 1

由方程 (1)2x(1 - λ) = 0,推出 x = 0λ = 1

情况一x = 0
代入方程 (3)0 + y² = 1 -> y = ±1
得到两个候选点:(0, 1)(0, -1)

情况二λ = 1
代入方程 (2)4y = 1 * (2y) -> 4y = 2y -> 2y = 0 -> y = 0
代入方程 (3)x² + 0 = 1 -> x = ±1
得到另外两个候选点:(1, 0)(-1, 0)

现在我们评估所有候选点的函数值:

  • F(0, 1) = 0² + 2*(1)² = 2
  • F(0, -1) = 0² + 2*(-1)² = 2
  • F(1, 0) = 1² + 2*(0)² = 1
  • F(-1, 0) = (-1)² + 2*(0)² = 1

因此,在单位圆约束下,函数 F(x, y)最大值为 2最小值为 1

内部极值与边界极值 🔍

现在考虑一个相关但不同的问题:求函数 F(x, y) = x² + 2y²单位圆盘内(包括边界 x² + y² ≤ 1)的极值。

对于区域内部的极值,我们寻找无约束的临界点,即令梯度 ∇F = (2x, 4y) = (0, 0)。解得 x=0, y=0,即原点 (0, 0)。在该点,F(0, 0) = 0

对于边界上的极值,我们已经用拉格朗日乘数法求过了,结果是最大值 2,最小值 1

比较内部临界点和边界极值:

  • 最小值:内部点 (0,0) 的函数值 0 小于边界最小值 1。因此全局最小值为 0
  • 最大值:边界最大值 2 大于内部任何点的值(因为函数在内部是连续的,且原点值最小)。因此全局最大值为 2

所以,在闭单位圆盘上,函数的最小值为 0(在原点取得),最大值为 2(在点 (0, ±1) 取得)。

拉格朗日乘数法的另一种视角:拉格朗日函数 ⚙️

最后,我们从另一个角度审视拉格朗日乘数法。

对于优化问题:最大化 F(x),满足 G(x) = c。拉格朗日乘数法要求解 ∇F = λ ∇GG(x) = c

这可以解释为:寻找以下拉格朗日函数的临界点:
L(x, λ) = F(x) - λ (G(x) - c)

如果我们对 L 求所有变量的偏导数并令其为零:

  • ∂L/∂x = 0 -> ∇F - λ ∇G = 0,即 ∇F = λ ∇G
  • ∂L/∂λ = 0 -> -(G(x) - c) = 0,即 G(x) = c

这正是我们之前推导出的方程组。因此,在优化理论中,我们通常直接构造并处理这个拉格朗日函数 L

总结 📝

本节课我们一起学习了如何使用拉格朗日乘数法解决等式约束优化问题。我们首先从等高线和梯度的几何关系出发,理解了在极值点处目标函数与约束函数的梯度必须平行的原理。然后,我们学习了该方法的具体步骤:

  1. 构造拉格朗日函数 L = F - λ(G - c)
  2. L 对所有变量(包括 λ)的偏导并令其为零。
  3. 解方程组得到候选点。
  4. 比较候选点的函数值确定极值。

我们通过“最大体积纸盒”和“圆上函数极值”两个例子实践了这一方法,并讨论了在闭区域上需要同时考虑内部临界点和边界极值的问题。最后,我们了解到拉格朗日乘数法等价于求解一个扩展的拉格朗日函数的临界点。掌握这一工具,对于解决人工智能、经济学、工程学等领域的许多优化问题至关重要。

042:不等式约束

在本节课中,我们将学习如何处理带有不等式约束的优化问题。我们将从一个简单的例子开始,逐步理解拉格朗日乘数法在不等式约束下的应用,并引入对偶问题的概念。

问题引入与拉格朗日函数

上一节我们介绍了等式约束下的优化。本节中,我们来看看当约束条件是不等式时,问题该如何处理。

考虑一个简单的极小化问题:最小化函数 f(x) = x²,约束条件为 x ≥ 1

从图像上看,曲线是 y = x²。我们试图在 x ≥ 1 的范围内找到最小值。显然,最小值出现在 x = 1 处,最优值为 1。虽然我们已经知道答案,但我们将尝试使用优化理论来求解。

首先,我们将约束条件重写为 g(x) = x - 1 ≥ 0 的形式,并构造拉格朗日函数:
L(x, α) = f(x) - α * g(x) = x² - α(x - 1)
其中,拉格朗日乘子 α 必须满足 α ≥ 0

极小极大问题的等价性

接下来,我们考虑一个极小极大问题:min_x max_{α≥0} L(x, α)

我们有一个重要的结论:原始的最小化问题与这个极小极大问题是等价的。以下是详细的论证:

  • x > 1 时,约束条件 g(x) > 0。对于非负的 α,项 -αg(x) 总是非正的。因此,L(x, α) 的最大值出现在 α = 0 时,此时拉格朗日函数退化为原始目标函数 f(x)
  • x = 1 时,约束条件 g(x) = 0。无论 α 取何值,-αg(x) 项始终为0。因此,L(x, α) = f(x)
  • x < 1 时,约束条件被违反,g(x) < 0。由于 α ≥ 0,项 -αg(x) 为正。通过选择非常大的 α,可以使 L(x, α) 趋于正无穷。

在极小极大问题中,外层是对 x 求极小。为了最小化这个极大值,优化过程会迫使 x 满足约束条件(即 x ≥ 1),以避免目标函数值变为无穷大。因此,这个极小极大问题最终等价于原始的约束优化问题。

原始问题与对偶问题

基于上述等价性,我们可以定义原始问题(Primal Problem)为:
p = min_x max_{α≥0} L(x, α)*

通过交换极小和极大的顺序,我们得到对偶问题(Dual Problem):
d = max_{α≥0} min_x L(x, α)*

其中,内层 g(α) = min_x L(x, α) 被称为拉格朗日对偶函数,α 被称为对偶变量。

根据对偶理论,原始问题的最优值 p* 总是大于等于对偶问题的最优值 d,即 p ≥ d。这被称为弱对偶性。如果两者相等,即 p = d*,则称为强对偶性。对于凸优化问题,通常满足强对偶性。

求解对偶问题

在许多情况下,求解对偶问题比直接求解原始问题更容易。因此,我们通常的策略是先求解对偶问题。

对于我们的例子 L(x, α) = x² - α(x - 1),我们首先对内层的 x 求极小。

以下是求解步骤:

  1. L 关于 x 求导并令其为零:∂L/∂x = 2x - α = 0,解得 x = α/2
  2. x = α/2 代回拉格朗日函数,得到仅关于 α 的对偶函数:
    g(α) = (α/2)² - α(α/2 - 1) = -α²/4 + α
  3. 接下来,在外层对 α ≥ 0 最大化 g(α)。对 g(α) 求导:dg/dα = -α/2 + 1 = 0,解得 α = 2*。
  4. 此时对偶函数的最大值为 g(α*) = -(2)²/4 + 2 = 1
  5. 根据强对偶性,原始问题的最优值 p = 1。将最优乘子 α = 2 代回 x = α/2,得到原始最优解 x = 1*。

通过求解对偶问题,我们得到了与直接观察相同的结果:在 x ≥ 1 的约束下,f(x) = x² 的最小值为 1,在 x = 1 处取得。

扩展到多个约束

最后,我们简要地将方法扩展到多个约束条件。考虑一个最大化问题:最大化 f(x, y, z),同时满足两个等式约束 g(x, y, z) = 0h(x, y, z) = 0

从几何角度解释,在极值点处,目标函数的梯度 ∇f 必须位于两个约束函数梯度 ∇g∇h 张成的平面内。这意味着 ∇f 可以表示为 ∇g∇h 的线性组合:
∇f = λ∇g + μ∇h
其中 λμ 是拉格朗日乘子。

结合两个约束方程,我们得到一个包含5个方程(3个来自梯度关系,2个来自约束)和5个未知数(x, y, z, λ, μ)的方程组,可以联立求解。

总结

本节课中我们一起学习了拉格朗日乘数法在不等式约束优化问题中的应用。核心要点包括:

  1. 通过引入非负的拉格朗日乘子,将不等式约束问题转化为一个极小极大问题。
  2. 原始问题与对偶问题的定义及其关系(弱对偶性与强对偶性)。
  3. 利用对偶性,通过求解相对简单的对偶问题来获得原始问题的解。
  4. 该方法可以自然地扩展到多个等式或不等式约束的情形。

这种方法为处理复杂的约束优化问题提供了一个强大而系统的框架。

043:梯度下降法的推导与分析 📉

在本节课中,我们将学习梯度下降法的核心原理与推导过程。梯度下降法是一种用于寻找函数最小值的迭代优化算法,广泛应用于机器学习和人工智能领域。

6.4:梯度下降法

上一节我们介绍了优化问题的基本形式,本节中我们来看看梯度下降法如何解决这类问题。

我们想要解决的问题定义如下:

  • ΩRⁿ 的一个子集。
  • 给定一个实值函数 F
  • 一般的最小化问题可以表述为:尝试找到 Fx ∈ Ω 上的最小值。

在这个上下文中:

  • F 被称为目标函数,有时也称为损失函数或成本函数。
  • Ω 是函数的定义域。
  • 约束条件是 x ∈ Ω

我们将尝试以迭代方式解决这个最小化问题。从一个初始猜测 x₀ 开始,我们将尝试找到一系列近似解 xₖ,其形式如下:

xₖ₊₁ = xₖ + αₖ pₖ

其中:

  • pₖ搜索方向
  • αₖ步长。在找到方向后,我们需要决定步长。

梯度下降法,也称为最速下降法,是这类迭代算法的一种。在这里,我们尝试推导梯度下降法。当 xₖ₊₁ 由上述公式决定时,问题在于如何选择 pₖαₖ

我们可能会尝试使用泰勒展开来分析这个扰动。所以,f(xₖ + αₖ pₖ) 可以展开为 f(xₖ) 加上其梯度与搜索方向的点积(乘以步长),再加上二阶项。

一旦我们假设二阶导数(海森矩阵)有界,我们就可以在这个范围内进行推导。我们的目标是使新点的函数值小于当前点的函数值,即函数值下降。

当点积为负时,因为步长是正数,我们就能得到更小的函数值。这要求步长足够小,并且海森矩阵非负。

我们的选择是:对于搜索方向 pₖ,我们尝试使用梯度的负方向。这样,点积就等于负的梯度范数的平方。当梯度非零时,它就是负的。这就是梯度下降法的选择。

以下是梯度下降法的核心公式总结:

  • 搜索方向pₖ = -∇f(xₖ)
  • 迭代更新xₖ₊₁ = xₖ - αₖ ∇f(xₖ)

现在,我们尝试在一维情况下分析这个方法。

一维情况下的分析

考虑一维最小化问题:最小化定义在实数集闭区间 S 上的函数 F(x)。那么梯度下降法可以表述为:

xₖ₊₁ = xₖ - γ f'(xₖ)

我们将尝试有效地设置步长 γ

我们从泰勒定理开始。对于一般的 xtf(x + t) 等于 f(x) 加上 t f'(x),再加上一个积分形式的余项。你也可以使用拉格朗日余项,即 t²/2 f''(ξ)

假设二阶导数有界,且上界为 L。那么我们可以得到 f(x + t) 的一个上界。

现在,在这个公式中,我们令 x = xₖt = -γ f'(xₖ)。那么我们可以得到 f(xₖ₊₁) 的一个上界。

目标是使这个上界严格小于 f(xₖ),从而实现下降。这要求某个项为正。这个要求等价于 γ02/L 之间。

这个二次项在 γ 取中点时达到最大值。当 γ = 1/L 时,我们可以得到最优的界。

通过对这个二次项求导并设为零,也可以找到这个方程。

γ = 1/L 时,我们可以重写这个不等式,得到新函数值的上界。这个不等式将在后面用于决定 γ

但在一般情况下,虽然理论上可以选择这个值,但实际上不容易设置。当我们尝试找到一个好的、合理的步长参数时,必须找到满足这个不等式的值。这将在后面用到。

这里有一个重要的结论:一旦序列 {xₖ} 收敛到 ,那么在 处,极限 f'(x̂) = 0。这意味着 是一个临界点。

这表示梯度下降法试图找到一个临界点,可能是局部极小值或鞍点。找到全局最小值在这里并不保证。无论如何,该算法试图找到局部最小值。它可能是全局最小值,但主要是在初始值附近寻找局部最小值。

例如,目标函数的图像可能如图所示。如果你从这里开始,它会下降并停在那里。如果你从那里开始,它会下降并停在那里。无论如何,该算法都试图降低函数值并达到一个局部最小值。

本节课中我们一起学习了梯度下降法的基本形式、在一维情况下的推导,以及其收敛到临界点的性质。理解这些原理是应用该算法解决实际优化问题的基础。

044:梯度下降法回溯线搜索

在本节课中,我们将学习梯度下降法在多维空间中的实际应用,并重点介绍一种改进步长选择的技术——回溯线搜索。我们将从一个经典优化函数开始,探讨固定步长带来的问题,然后学习如何通过回溯线搜索自适应地选择步长,以加速收敛。

多维空间中的梯度下降法

上一节我们推导了一维空间中的梯度下降法。本节中,我们来看看该方法在多维空间中的一些实际问题。

我们将从一个例子开始。例如,二维空间中的罗森布罗克函数定义如下:

f(x, y) = (1 - x)^2 + 100 * (y - x^2)^2

这是该函数的图像。其最小值点在 (1, 1)。当 x=1 且 y=1 时,函数值为 0,因此最小值为 0。

这个函数在优化领域被广泛使用。主要原因是,它的函数值变化非常平滑,但迭代过程中,一旦迭代点位于这个“山谷”区域附近,移动速度会变得非常缓慢。因此,你需要一些特殊的方案来加速收敛。但如果你尝试选择较大的参数,又可能导致算法发散,所以必须非常小心。总之,这个函数在优化中常用于检验算法的收敛性。如果你的算法运行良好且优于其他算法,那么可以说你的算法非常好。

现在,我们将对这个函数应用梯度下降法,并选择一个步长参数 gamma = 1/500,起始点为 (-1, 2)。罗森布罗克函数的定义和梯度计算如下:

梯度计算公式为:
grad_f = [ -2*(1-x) - 400*x*(y - x^2), 200*(y - x^2) ]

需要注意的是,罗森布罗克函数可以定义在任意维度。例如,在三维或n维空间中,有相应的公式。让我们回到程序。

当我们运行程序时,会进行迭代,同时计算修正项并更新位置。当修正项的范数小于我们设定的容差 10^-7 时,迭代停止。

结果显示,使用固定步长的方法需要超过7000次迭代才能收敛。虽然时间不长,但速度较慢。如果你能选择更好的步长,就可以获得更好的收敛效果。

回溯线搜索

这里的主要问题在于步长的选择。梯度下降法对步长的选择可能极其敏感。我们可以尝试自适应地选择步长,即在每次迭代中尝试选择不同的步长 gamma_k,并尽可能保证收敛性。

获得这种步长的一种方法称为回溯线搜索。在这种搜索中,我们将尝试找到一个满足“充分下降条件”的步长 gamma_k

在新位置,函数值必须小于或等于这个不等式的右边。步长 gamma_k 的选择基于以下分析:在一维情况下,我们有 f(x_{k+1}) <= f(x_k) - c * gamma_k * ||grad_f(x_k)||^2 这样的分析。因此,我们需要选择满足这个不等式的参数。在这种情况下,它被称为充分下降。

这被称为充分下降条件,我们将尝试找到满足这个不等式的步长。这是合理的,我们在一维中分析过它,但在多维中也应满足。

以下是该算法的伪代码描述:

我们从初始猜测 x_0 和初始步长 gamma 开始。回溯线搜索部分是额外的。在每次迭代 k 中,我们从某个较大的初始步长 gamma_k 开始。然后检查不等式是否满足。我们可以计算 f(x_k - gamma_k * grad_f(x_k)),因为 x_k 点的梯度已知。每次我们计算这个向量并评估函数值。如果满足不等式右边,我们就停止这次线搜索迭代。如果不满足,我们就尝试缩减参数,例如使用 gamma_k = beta * gamma_k0 < beta < 1)这个方程,使得在每次搜索参数时步长减小。我们重复这个过程,直到决定步长,然后进行更新。

当我们评估这个值时,我们已经有了 x_{k+1}。一旦线搜索迭代结束,x_{k+1} 就确定了。通过这种方式,我们可以使用线搜索技术。对于每次迭代 k,我们尝试从一个较大的值开始迭代地决定步长,每次缩减一半,直到决定。因此它被称为回溯线搜索。

决定步长后,我们可以更新并进入下一次迭代。

部分更新与随机梯度下降

结合线搜索或部分更新,梯度下降法是各种机器学习的主要计算算法。

部分更新的思路是:与其使用完整的梯度更新,我们可能选择梯度的一部分。在多维情况下,你可以尝试选择一个方向导数。例如,梯度向量的第一个分量可以在那里使用,或者其中几个分量可以使用。或者,如果目标函数是一个求和形式,例如目标函数 F 是根据数据点 theta_i 制定的,那么你可以尝试只使用部分数据点的梯度,而不是整个梯度。无论哪种方式,你都可以得到部分更新。

结合这种线搜索和部分更新,梯度下降法是机器学习优化中的主要工具。带有部分更新的梯度下降法被称为随机梯度下降法。

本节课中,我们一起学习了梯度下降法在多维空间的应用,重点探讨了固定步长收敛慢的问题及其解决方案——回溯线搜索。回溯线搜索通过自适应地选择满足充分下降条件的步长,有效加速了收敛。此外,我们还简要了解了部分更新和随机梯度下降的概念,它们是机器学习优化中的核心工具。在第三部分我们将继续深入。

045:第45讲 - 正定线性系统的梯度下降法 🧠

在本节课中,我们将要学习梯度下降法在求解正定线性系统中的应用。我们将从正定矩阵的定义开始,探讨如何将线性系统求解转化为一个最小化问题,并详细推导梯度下降法的迭代公式和步长选择。最后,我们会分析该方法的收敛性。


正定线性系统与最小化问题

上一节我们介绍了梯度下降法的基本思想,本节中我们来看看如何将其应用于求解正定线性系统。

首先,我们定义正定矩阵。一个矩阵 A 被称为正定的,如果对于任意非零向量 x,都有:
xᵀ A x > 0

如果矩阵 A 是对称的,那么它是正定的当且仅当它的所有特征值都是正的。

对于对称正定系统 A x = b,我们可以将其重新表述为一个最小化问题。定义函数 F(x) 为:
F(x) = (1/2) xᵀ A x - bᵀ x

可以证明,这个最小化问题的解与线性系统 A x = b 的解是相同的。这是因为,当 F(x) 取最小值时,其梯度 ∇F(x) = A x - b 必须为零,从而得到原方程。


克雷洛夫子空间方法与梯度下降法

为了求解上述代数系统,我们可以使用克雷洛夫子空间方法。其迭代形式如下:
x_{k+1} = x_k + α_k p_k

其中,p_k 是搜索方向,α_k 是正的步长。梯度下降法就是克雷洛夫子空间方法的一种,其核心在于搜索方向 p_k 和步长 α_k 的选择。


推导梯度下降法

现在,让我们为线性系统推导梯度下降法。对于函数 F(x),其梯度为 ∇F(x) = A x - b,海森矩阵(二阶导数)就是矩阵 A 本身。

我们的目标是在每一步迭代中,找到一个搜索方向 p_k 和步长 α_k,使得函数值 F(x_{k+1})F(x_k) 更小。

一个自然的选择是让搜索方向 p_k 为负梯度方向,即:
p_k = -∇F(x_k) = b - A x_k

我们通常记 r_k = b - A x_k 为第 k 步的残差。因此,在梯度下降法中,我们选择:
p_k = r_k


选择最优步长

接下来,我们需要选择步长 α_k。对于线性系统,通常通过求解一个一维最小化问题来选择最优步长:
α_k = argmin_α F(x_k + α p_k)

通过微积分技巧,我们可以求出 α_k 的解析解。将 F(x_k + α p_k)α 求导并令其为零,可以得到:
α_k = (r_kᵀ r_k) / (p_kᵀ A p_k)

由于在梯度下降法中 p_k = r_k,所以公式简化为:
α_k = (r_kᵀ r_k) / (r_kᵀ A r_k)

这是一种通用的步长选择方法,只要选定了搜索方向,就可以用此公式计算最优步长。


梯度下降算法流程

以下是求解 A x = b 的梯度下降算法步骤,其中 A 是对称正定矩阵。

  1. 初始化:给定初始猜测 x₀ 和停止容差 ε
  2. 计算初始残差r₀ = b - A x₀
  3. 开始迭代(对于 k = 0, 1, 2, ...):
    • 如果 ‖r_k‖ ≤ ε ‖r₀‖,则停止迭代,x_k 即为近似解。
    • 计算步长:α_k = (r_kᵀ r_k) / (r_kᵀ A r_k)
    • 更新解:x_{k+1} = x_k + α_k r_k
    • 更新残差:r_{k+1} = r_k - α_k A r_k。(注:此更新公式比直接计算 b - A x_{k+1} 更高效,因为它只需一次矩阵-向量乘法。


收敛性分析

最后,我们来分析梯度下降法的收敛速度。对于一个可逆矩阵 A,其条件数 κ(A) 定义为:
κ(A) = ‖A‖ ‖A⁻¹‖

A 是对称正定时,其2-范数条件数为最大特征值与最小特征值之比:
κ₂(A) = λ_max / λ_min

梯度下降法的误差满足以下不等式:
‖x_k - x‖_A ≤ [ (κ(A) - 1) / (κ(A) + 1) ]^k ‖x₀ - x‖_A
其中 x* 是精确解,‖v‖_A = √(vᵀ A v)

由此可知,要将误差减少到原来的 ε 倍,所需的迭代次数 k 约为 O(κ(A))。当矩阵 A 的条件数很大时(例如在大型问题中可能达到 O(n²)),梯度下降法会收敛得很慢。

因此,对于正定线性系统,人们通常使用收敛更快的共轭梯度法。然而,在机器学习的优化问题中,梯度下降法因其简单性,配合随机小批量更新和精心调整的步长,仍然是实际应用中的主流选择。


本节课中我们一起学习了梯度下降法求解正定线性系统的全过程。我们从定义出发,将其转化为优化问题,推导了迭代公式和最优步长,并给出了完整的算法步骤。最后,我们分析了该方法的收敛速度,认识到其效率受条件数影响较大,这为理解更高级的优化算法(如共轭梯度法)奠定了基础。

046:最小二乘问题与正规方程方法 🧮

在本节课中,我们将学习最小二乘问题及其在回归分析中的应用。我们将重点介绍一种处理含噪声数据的著名算法,并详细讲解如何使用正规方程方法来求解最小二乘问题。


最小二乘问题 📈

对于给定的数据集 (x_i, y_i),我们构造一个连续函数 P(x)。根据定义,如果 P(x) 经过所有数据点,则它是一个插值函数;否则,它是一个近似函数。

让我们看一个小程序。使用 Maple,我们尝试生成一个数据集。

基础模型是一条直线。它有 x 分量和 y 分量,并且我们添加了一个随机误差。

生成的数据是一个散点图。如果我们尝试构造一个插值多项式,由于有10个点,我们可以得到一个9次多项式。如果我们尝试得到一条最佳拟合线,那么结果由这条直线给出。

让我们看看数据点,以及插值多项式和最佳拟合线。

这里的插值多项式振荡很大,因此在现实中可能并不实用。此外,一旦数据点在一个点上有多个值,我们就无法得到这样的插值。而直线是代表数据集的最佳拟合线。在许多情况下,这条最佳拟合线更有用。这就是背景。出于某些原因,我们将考虑最小二乘问题。


定义最小二乘问题 🎯

A 是一个 m × n 矩阵,那么方程 Ax = b 可能无解,特别是当 m > n 时。在现实世界中,m 通常远大于 n。这里,m 通常代表数据点的数量,n 代表点的维度,通常是特征分量的维度。因此,数据点的数量远大于维度。

为了解决这个问题,我们需要找到最佳近似解。寻找最佳解的目标是使 Ax 最接近向量 b

现在,我们定义最小二乘问题如下:设 A 是一个 m × n 矩阵,且 m ≥ nb 属于 R^m。那么最小二乘问题就是寻找 x_hat 属于 R^n,使得以下差值(用范数衡量)最小化:

目标函数: min ||Ax - b||^2

x_hat 是这个最小化问题的解。最小化这个范数平方等价于最小化其平方根,因此这两个问题是相同的。x 被称为 Ax = b 的最小二乘解。

我们称之为“最小二乘”的原因是:对于误差向量 Ax - b,当我们衡量其范数时,每个分量被平方后求和。因此,这是误差平方和最小化的缩写,但通常我们称之为最小二乘。


正规方程方法 ⚙️

最小二乘问题可以通过使用正规方程的方法来解决。

Ax = b 的最小二乘解集与以下正规方程的解集完全一致:

正规方程: A^T A x_hat = A^T b

这个正规方程是通过将 A^T 从左乘到 Ax = b 两边得到的。让我们通过一些微积分技巧来推导这个方法。

定义目标函数 f(x) 为:

f(x) = ||Ax - b||^2 = (Ax - b)^T (Ax - b)

在向量范数中,向量 v 的范数平方等于 v^T v(即点积 v·v)。x_hatf(x) 的最小值点。如果我们对其应用导数(即梯度),并在 x = x_hat 处设为零,则可以得到解。

梯度可以如下求得:将 f(x) 展开后对 x 求导。从第一项我们得到 2 A^T A x,从第二项得到 -2 A^T b,常数项消失。因此,我们得到:

梯度: ∇f(x) = 2 A^T A x - 2 A^T b

令梯度为零:

2 A^T A x - 2 A^T b = 0

简化后得到正规方程:

A^T A x_hat = A^T b


解的存在性与唯一性 🔍

上述定理意味着 Ax = b 的最小二乘解是正规方程的解。当 A^T A 不可逆时,正规方程可能无解或有无穷多解。

为了得到合理的唯一解,数据非常重要,必须确保矩阵 A^T A 是可逆的。我们需要获取数据使得这个矩阵可逆。

以下是关于正规方程方法的逻辑等价陈述:

  1. 对于 R^m 中的每个 b,方程 Ax = b 有唯一的最小二乘解。
  2. 正规方程 A^T A x_hat = A^T b 是可解的。
  3. 矩阵 A^T A 是可逆的。
  4. A 的列是线性独立的(即 A 是列满秩的)。

当这些陈述成立时,我们说 A 具有满秩。实际上,一旦 A 的列线性独立,A^T A 就是可逆的,因为它是对称的,并且其特征值均为正。

在这些条件成立的情况下,唯一的最小二乘解 x_hat 由下式给出:

唯一解公式: x_hat = (A^T A)^{-1} A^T b


伪逆矩阵 🧩

为了方便使用,我们引入一个记号。使用 A⁺ 符号,我们定义伪逆矩阵:

伪逆定义: A⁺ = (A^T A)^{-1} A^T

这看起来像是从正规方程中推导出来的。如果 A 是方阵且可逆,我们可以直接应用逆矩阵来求解。但在这里,我们使用这样的矩阵。它扮演着逆矩阵的角色,但并非真正的逆矩阵,因此我们称之为 A 的伪逆。这是一个相当合理的名称。


实例演算 📝

让我们用纸笔来求解一个具体的最小二乘问题。给定 Ab

A = [1 1 0]
    [0 0 1]
    [0 1 1]
    [1 0 1]

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/ec8ee4ac61ecffa4ff4518983b02473a_36.png)

b = [1]
    [3]
    [8]
    [2]

首先,我们需要计算 A^T AA^T b

计算 A^T A
根据矩阵乘法规则(行点乘列):

  • 第一行第一列:(1,0,0,1) 点乘 (1,0,0,1) = 1 + 0 + 0 + 1 = 2
  • 第一行第二列:(1,0,0,1) 点乘 (1,0,1,0) = 1 + 0 + 0 + 0 = 1
  • 第一行第三列:(1,0,0,1) 点乘 (0,1,1,1) = 0 + 0 + 0 + 1 = 1
  • 第二行第一列:(1,0,1,0) 点乘 (1,0,0,1) = 1 + 0 + 0 + 0 = 1
  • 第二行第二列:(1,0,1,0) 点乘 (1,0,1,0) = 1 + 0 + 1 + 0 = 2
  • 第二行第三列:(1,0,1,0) 点乘 (0,1,1,1) = 0 + 0 + 1 + 0 = 1
  • 第三行第一列:(0,1,1,1) 点乘 (1,0,0,1) = 0 + 0 + 0 + 1 = 1
  • 第三行第二列:(0,1,1,1) 点乘 (1,0,1,0) = 0 + 0 + 1 + 0 = 1
  • 第三行第三列:(0,1,1,1) 点乘 (0,1,1,1) = 0 + 1 + 1 + 1 = 3

因此:

A^T A = [2 1 1]
        [1 2 1]
        [1 1 3]

计算 A^T b

  • 第一行:(1,0,0,1) 点乘 (1,3,8,2) = 11 + 03 + 08 + 12 = 3
  • 第二行:(1,0,1,0) 点乘 (1,3,8,2) = 11 + 03 + 18 + 02 = 9
  • 第三行:(0,1,1,1) 点乘 (1,3,8,2) = 01 + 13 + 18 + 12 = 13

因此:

A^T b = [3]
        [9]
        [13]

现在我们需要解正规方程:[2 1 1; 1 2 1; 1 1 3] * x = [3; 9; 13]

我们可以使用初等行变换(高斯消元法)来求解。通过一系列行变换(交换、倍加、倍乘),最终将增广矩阵化为行最简形式,得到解:

x1 = -4
x2 =  4
x3 =  7

因此,最小二乘解为 x_hat = [-4, 4, 7]^T。使用矩阵程序计算 (A^T A)^{-1} A^T b 也会得到完全相同的结果。


总结 📚

本节课中,我们一起学习了:

  1. 最小二乘问题的定义:寻找使 ||Ax - b||^2 最小的 x,用于处理无解或过定方程组。
  2. 正规方程方法:通过求解 A^T A x_hat = A^T b 来得到最小二乘解。
  3. 解的唯一性条件:当矩阵 A 的列线性独立(满秩)时,A^T A 可逆,存在唯一最小二乘解。
  4. 伪逆矩阵:引入了 A⁺ = (A^T A)^{-1} A^T 作为计算工具。
  5. 实例计算:通过一个具体例子,演示了如何从构建正规方程到求解出最小二乘解的全过程。

正规方程是解决线性回归和许多其他优化问题的基础工具。理解其原理和计算步骤对于深入学习机器学习至关重要。

047:线性与非线性回归

在本节课中,我们将要学习回归分析,这是一种用于估计自变量与因变量之间关系的统计方法。我们将从线性回归的基本概念开始,逐步深入到非线性模型的线性化处理技巧。

7.2:回归分析

根据定义,回归分析是一组用于估计自变量与因变量之间关系的统计方法。

观察上图,蓝色点代表数据点。通过最小二乘法技术,我们找到一条最能代表这些数据点的线,这条线被称为回归线。

以下是正式定义。假设给出一组实验数据点,形式为 (x1, y1), (x2, y2), ..., (xM, yM),共有 M 个点。例如,在 x1 处,观测到的 y 值是 y1

如果将这些数据点绘制出来,并且图形接近一条直线,如上图所示,那么我们可以确定一条形式为 y = β0 + β1x 的直线,使得该直线在平均意义上或最小二乘误差的意义上尽可能接近所有数据点。

因为直线不可能穿过所有数据点,所以我们需要确定这种形式的直线。在这种情况下,这条线被称为最小二乘线或回归线。β0β1 被称为回归系数。

如何计算最小二乘线

现在,我们考虑形式为 y = β0 + β1x 的最小二乘模型。对于给定的数据集,对于每个点 xi,我们观测到的 y 值是 yi

因此,对于模型,我们有以下方程:对于点 (x1, y1),有 y1 = β0 + β1*x1;对于点 (x2, y2),有 y2 = β0 + β1*x2。依此类推,每个点对应一个方程。

我们可以将这些方程写成矩阵形式 Xβ = y。其中,矩阵 X 是系数集合,第一列全为1(对应 β0),第二列为 x 值(对应 β1)。β 是未知向量 [β0, β1]^Ty 是观测值向量 [y1, y2, ..., yM]^T

如果使用最小二乘法,我们可以采用正规方程法。我们计算 X^T X,如果它是可逆的,那么我们可以找到唯一解。实际上,只要数据点中至少有两个点的 x 值不同,矩阵 X 的列就是线性独立的,这意味着 X^T X 是可逆的。

具体计算时,我们可以明确写出 X^T X 矩阵和右侧向量。

X^T X 是一个 2x2 矩阵:

  • 左上角元素是数据点数量 M
  • 右上角和左下角元素是所有 x 值的和 Σxi
  • 右下角元素是所有 x 值平方的和 Σxi^2

右侧向量 X^T y 为:

  • 第一个元素是所有 y 值的和 Σyi
  • 第二个元素是所有 xi*yi 的和 Σxi*yi

在实际操作中,我们可以采用逐点构建的方式。这意味着我们不是一次性构建整个矩阵,而是为每个数据点计算其贡献(一个小的矩阵和向量),然后将所有点的贡献相加,得到最终的 X^T X 矩阵和 X^T y 向量。

采用这种逐点构建方式的原因是,当我们已经有一条回归线,后来又增加了新的数据点时,我们可以轻松地添加新点的贡献。此外,如果我们想给某些点赋予更高的权重(例如,认为某些数据更可靠),我们可以在每个点的贡献中加入权重因子,从而得到更符合需求的回归线。

这种逐点构建正规方程的思想,对于其他回归模型也同样有用。

计算示例

现在,让我们尝试进行部分计算。假设我们有以下数据点:(-1, 0), (0, 1), (1, 2), (2, 4)

我们可以制作一个表格:

x y
-1 0
0 1
1 2
2 4

计算所需的和:

  • M = 4
  • Σx = (-1) + 0 + 1 + 2 = 2
  • Σx^2 = (-1)^2 + 0^2 + 1^2 + 2^2 = 1 + 0 + 1 + 4 = 6
  • Σy = 0 + 1 + 2 + 4 = 7
  • Σxy = (-1)*0 + 0*1 + 1*2 + 2*4 = 0 + 0 + 2 + 8 = 10

因此,正规方程为:

[ 4   2 ] [β0] = [7]
[ 2   6 ] [β1]   [10]

解这个方程组。将第一个方程乘以3,第二个方程乘以1,然后相减,可以求得 β1 = 1.3。代入任一方程,求得 β0 = 1.1

所以,我们寻求的回归线模型是:y = 1.1 + 1.3x

扩展到非线性回归:多项式拟合

对于其他曲线,我们可以使用相同的最小二乘拟合技术。例如,考虑二次多项式回归模型:y = β0 + β1*x + β2*x^2

对于给定的数据集,我们可以为每个数据点建立方程。在点 (x1, y1) 处,模型给出 y1 = β0 + β1*x1 + β2*x1^2。这些方程组可以写成矩阵形式 Xβ = y

此时,矩阵 X 的列是 [1, xi, xi^2],未知向量 β[β0, β1, β2]^T。使用最小二乘法和正规方程 (X^T X) β = X^T y,我们可以求解 β

计算 X^T X 会得到一个具有特定模式的矩阵,其元素涉及 xi 的各次幂的和(如 Σ1, Σxi, Σxi^2, Σxi^3, Σxi^4)。右侧向量 X^T y 的模式是 Σyi, Σxi*yi, Σxi^2*yi

线性化技巧

回归技术或最小二乘技术对于非线性模型也非常有用。通过线性化,我们可以有效地进行处理。这通常从变量替换开始。

观察以下模型示例:

  1. 模型:y = a + b/x

    • 这不是线性的,因为 x 在分母中。
    • 线性化方法:引入新变量 x_tilde = 1/xy_tilde = y。则模型变为 y_tilde = a + b * x_tilde,这是一个线性模型。
  2. 模型:y = a + b * ln(x)

    • 线性化方法:引入新变量 x_tilde = ln(x)y_tilde = y。则模型变为 y_tilde = a + b * x_tilde
  3. 模型:y = c * e^(d*x) (指数模型)

    • 线性化方法:对等式两边取自然对数:ln(y) = ln(c) + d*x
    • 引入新变量 y_tilde = ln(y),则模型变为 y_tilde = ln(c) + d*x。这是一个关于 xy_tilde 的线性模型。求出 ln(c)d 后,再通过 c = e^(ln(c)) 得到 c
  4. 模型:y = c * x^d (幂律模型)

    • 线性化方法:对等式两边取自然对数:ln(y) = ln(c) + d * ln(x)
    • 引入新变量 x_tilde = ln(x)y_tilde = ln(y)。则模型变为 y_tilde = ln(c) + d * x_tilde

下表总结了一些线性化的例子:

原模型 变量替换 线性化后的模型
y = a + b/x x_tilde = 1/x, y_tilde = y y_tilde = a + b*x_tilde
y = a + b*ln(x) x_tilde = ln(x), y_tilde = y y_tilde = a + b*x_tilde
y = c*e^(d*x) y_tilde = ln(y) y_tilde = ln(c) + d*x
y = c*x^d x_tilde = ln(x), y_tilde = ln(y) y_tilde = ln(c) + d*x_tilde

我们需要线性化的原因是,有时我们必须求解真正的非线性回归模型(例如使用牛顿法)。在这种情况下,我们需要为参数提供良好的初始值。通过线性化或近似模型,我们可以获得良好的近似参数,然后从那个起点出发,使用牛顿法等迭代方法得到更精确的估计。

线性化实例:指数拟合

假设我们有一些数据点,希望找到形式为 y = c * e^(d*x) 的最佳拟合曲线。散点图看起来像一条指数曲线。

我们采用线性化方法。对模型两边取自然对数:ln(y) = ln(c) + d*x
Y = ln(y)A0 = ln(c)A1 = d。则方程变为 Y = A0 + A1*x,这是一个线性模型。

接下来,我们对原始数据中的每个 y 值计算其自然对数,得到新的 Y 值数据集 (x, Y)。然后,对这个新数据集 (x, Y) 应用标准的线性最小二乘法,求解 A0A1

得到 A0A1 后,我们可以恢复原始参数:

  • d = A1
  • c = e^(A0)

在编程实现时,需要注意:由于要对 y 取对数,所以原始数据中所有的 y 值必须是正数。如果存在零或负值,可能需要剔除这些数据点或选择其他模型,否则算法会出错。

总结

本节课中,我们一起学习了回归分析的核心内容。

  1. 线性回归:我们学习了如何通过最小二乘法,找到一条直线 y = β0 + β1x 来最佳拟合一组数据点。关键在于构建并求解正规方程 (X^T X) β = X^T y
  2. 多项式回归:我们将线性回归的思想扩展到多项式模型(如二次函数 y = β0 + β1x + β2x^2),方法类似,只是设计矩阵 X 的构造不同。
  3. 非线性回归的线性化:这是处理复杂模型的有力工具。我们学习了通过对数变换、倒数变换等变量替换技巧,将诸如指数模型 y = c*e^(d*x)、幂律模型 y = c*x^d 等非线性模型转化为线性模型,从而可以利用成熟的线性最小二乘法进行求解。这种方法不仅能直接得到近似解,还能为更精确的非线性优化方法提供良好的初始参数估计。

通过掌握这些从线性到非线性的回归技术,你便具备了分析各种变量间关系的基础能力。

048:噪声数据加权最小二乘法和RANSAC 🧠

在本节课中,我们将学习处理噪声数据的两种关键技术:加权最小二乘法和随机抽样一致性算法。场景分析的核心任务是从获取的数据中解读出预定义的模型,这涉及到模型选择和参数估计。传统方法在处理包含大量噪声或异常值的数据时可能失效,因此我们需要更鲁棒的算法。

场景分析概述

场景分析关注于根据预定义模型来解释获取的数据。它包含两个子问题:第一个是找到最佳模型,这是一个分类问题;第二个是计算最佳参数值,这是一个参数估计问题。

传统的参数估计技术(如最小二乘法)会优化模型以拟合所有数据点。这些方法是基于平滑假设的简单平均方法。该假设认为,总是有足够多的“好”数据点来平滑掉异常偏差。

然而,现实中,这种平滑假设在许多应用中并不成立。这意味着数据集可能包含严重的误差,例如噪声。因此,为了获得更可靠的模型参数,必须有内部机制来确定哪些点与模型匹配(内点),哪些点产生错误匹配(外点)。

加权最小二乘法

上一节我们介绍了传统最小二乘法的局限性。本节中,我们来看看加权最小二乘法如何通过赋予不同数据点不同的重要性来改进参数估计。

当你获得数据点时,可能会发现某些点比其他点更重要、更可靠。在这种情况下,你可以尝试给这些重要的点赋予更大的权重。这就是加权最小二乘法的思想。它是一种按比例缩放的方法,涉及一个权重矩阵 W

权重矩阵通常被给定为一个对角矩阵,如下所示:

\[W = \begin{bmatrix} w_1 & 0 & \cdots & 0 \\ 0 & w_2 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & w_n \end{bmatrix} \]

这些权重可以手动或自动决定。

以下是加权最小二乘法的算法。对于基本数据集 (x_i, y_i),最佳拟合曲线可以通过求解以下代数系统找到:

\[(X^T W X) \beta = X^T W y \]

我们之前已经用这种形式表述过这个系统。当应用权重矩阵时,系统可以这样重写。我们只需乘以权重矩阵 W,然后相应的法方程可以通过乘以 X^T 来表述。一旦这个矩阵可逆,我们就可以唯一地得到参数 β

\[\beta = (X^T W X)^{-1} X^T W y \]

请看这个例子。给定数据,我们需要在有权重和无权重的情况下找到最小二乘线。当应用权重时,我们将第一个和最后一个数据点的权重设为四分之一。

现在,如果你观察数据,实际上我们可以在这里绘制。中间的点大致在一条线上,但边界点扰动很大。因此,我们可以认为这两个边界点不可靠。在现实中,当你尝试获取数据时,每个点都花费了成本,因此丢弃这些点并不明智。所以,你可以尝试以较小的权重使用它们。这就是这里的核心思想。

标准最小二乘法的输出曲线拟合结果,所有点权重相同。加权最小二乘法使用这个权重矩阵,对于第一个和最后一个点,权重是四分之一,内部点的权重相同,都是1。让我们看看结果,它们显然有很大不同。当我们使用权重时,我们得到实线;不使用权重时,我们得到虚线。因此,这里的加权最小二乘法更可靠,前提是我们假设这两个点是外点。

随机抽样一致性算法

上一节我们探讨了通过加权来处理噪声的方法。本节中,我们将介绍一种更强大的、专门用于从点云数据中构建正确结构的算法:随机抽样一致性算法。

RANSAC 是从点云数据中构建正确结构最强大的工具之一。该算法利用迭代搜索技术来寻找给定数据集的“内点”集合,从而提出模型。

以下是 RANSAC 算法,由 Fischler 和 Bolles 于 1981 年发明。

  • 输入
    • X:数据点集合。
    • τ:用作模型与点之间距离容差的半径。
    • η:停止阈值。
    • N:最大迭代次数。

  • 算法步骤
    1. 随机选择最小点集(足以确定一个假设模型)。
    2. 根据最小点集生成假设模型 H
    3. 通过与假设模型 H 的距离在容差 τ 内的点形成共识集 C
    4. 如果共识集 C 的大小超过阈值 η,则使用 C 中的所有点重新估计模型 H,并停止。
    5. 否则,重复步骤 1-4,最多 N 次迭代。
    6. N 次迭代后,返回具有最大共识集的模型。

让我们为回归线设置一个假设。当我们选择一条线时,我们需要两个点。从两个点,我们可以轻松确定模型。例如,斜率可以通过 Δy / Δx 计算,这是你在高中就学过的技术,可以确定模型。

对于共识集 C,它是满足以下条件的点的集合:点 (x_i, y_i) 到直线的距离公式(同样来自高中知识)小于等于 τ,则该点属于共识集。

\[\text{distance} = \frac{|Ax_i + By_i + C|}{\sqrt{A^2 + B^2}} \leq \tau \]

关于 RANSAC 的实现有几点说明:

  • 在步骤 2 中,假设 H 是模型参数的集合(例如,对于直线 y = a + bx,保存 ab),而不是模型本身。
  • 在步骤 3 中,共识集可以更方便地表示为一个索引数组 C。如果点 x_i 在共识集中(即满足上述不等式),则在索引 i 处赋值为 1,否则为 0。这样,索引值为 1 的点就是内点。

关于 RANSAC 的一个关键点是:步骤 3 中的内点函数收集那些到模型距离不大于 τ 的点。这个距离可以解释为一种自动加权机制。事实上,对于每个点 x_i,如果到模型的距离在 τ 内,我们尝试将其权重设为 1,否则为 0。那么,在最后一步重新估计假设时,可以看作是使用相应权重矩阵进行的加权最小二乘估计。因此,RANSAC 中的参数估计可以被视为加权最小二乘法。

RANSAC 的特性与变体

基本的 RANSAC 算法是一种迭代搜索方法,用于寻找可能产生准确模型参数的内点集。然而,它存在问题且常常误差很大。RANSAC 的主要缺点是不可重复。它可能在每次运行时产生不同的结果,因此没有一个结果能保证是最优的。尽管如此,它实现简单且高效,因此广受欢迎。

请看这个示例。我们有两个不同的数据集:蓝点是内点,红点是外点。如果我们使用所有点,会得到红线;如果只使用内点,会得到蓝线。现在,我们使用 RANSAC。如图所示,生成的线(来自 RANSAC 输出)每次可能不同。有时很好,但有时差异很大,可能会产生扰动很大的线。我尝试测量变化,运行 1000 次后,收集输出并尝试测量截距和斜率的标准差。对于左侧数据集,截距和斜率的标准差都很大,误差明显。对于右侧数据集,截距的标准差是 0.1,虽然不完美但误差可观察,斜率也有类似误差,并给出了计算时间。

RANSAC 既不可重复也不最优。为了克服这个缺点,文献中研究了各种变体。例如:

  • 最大似然估计样本一致性。
  • 渐进样本一致性。

因为 RANSAC 是随机算法,人们不完全知道如何克服其缺点。事实上,今年我发表了一篇论文,提出了一个称为“迭代更新一致性”的变体,它是最优的。我们使用迭代更新而不是迭代搜索,可以在计算时间和准确性上获得很大改进。然而,尽管人们创造了这些变体,RANSAC 由于其简单性和高效性,仍然是寻找内点的主流算法。

总结

本节课中,我们一起学习了处理噪声场景分析数据的两种核心方法。我们首先介绍了加权最小二乘法,它通过为不同可靠度的数据点分配权重来改进传统最小二乘估计。接着,我们深入探讨了随机抽样一致性算法,这是一种强大的迭代方法,通过随机采样和共识集验证来从包含大量外点的数据中鲁棒地估计模型参数。我们了解了 RANSAC 的基本步骤、实现细节,以及它虽然简单高效但不可重复的特点,最后简要提及了其一些改进变体。掌握这些方法对于在现实世界的不完美数据中进行可靠的模型拟合至关重要。

049:学习Python与库

在本章中,我们将学习Python编程语言及其核心库。我们将从Python的基础知识开始,然后实现一个求解多项式零点的代码,并将其与Matlab代码进行比较。最后,我们将讨论Python的类。

8.1 为何选择Python 🐍

上一节我们介绍了本章的学习目标,本节中我们来看看为什么Python是一个值得学习的编程语言。

一个好的编程语言必须易于学习和使用,同时具备灵活性和可靠性。这些正是Python的优势。此外,Python在数据科学领域被广泛使用,拥有丰富的开发工具和庞大的库支持。最后,Python是科技行业、人工智能和机器学习领域中增长最快的编程语言。

然而,Python也存在一个问题。因为它是一种解释型、动态类型的语言,所以运行速度相对较慢。与Matlab相比,Python脚本的运行速度也较慢。

8.2 加速Python程序 ⚡

既然我们了解了Python速度较慢的缺点,本节将探讨几种加速Python程序的方法。

以下是加速Python程序的策略:

  • 在数学运算中使用NumPy和SciPy库。
  • 尽可能使用内置函数。
  • 使用Cython。Cython是Python的C语言扩展,它结合了C和Python。你可以像写C语言或Python一样实现函数。如果更多地使用C语言的特性,程序会运行得更快。
  • 将自定义的C、C++或Fortran模块导入到Python中。使用编译模块比Python脚本快得多,有时甚至能快100倍。如果你正在开发新项目并需要编写长脚本,尝试使用编译语言会得到性能更好的代码。

接下来,我们考虑如何从Python调用C、C++或Fortran代码。这个脚本将编译所有用C、C++和Fortran编写的函数。

首先,我们为Fortran、C和C++定义库名称。然后,使用f2py3编译Fortran 90文件。你可以包含Fortran 90的编译选项,例如选择优化级别3,这是可用的最高优化级别。如果不选择优化,默认级别通常为1,速度会慢大约两倍。

对于C语言,你需要生成位置无关代码,因此需要添加相应的编译标志,并使用所有C文件生成共享对象。对于C++,过程类似,只是需要使用C++文件。

一旦生成了共享对象,就可以在Python中包装和使用它们。对于Fortran,你可以直接导入所有函数,使用方式类似。然而,对于C和C++,你需要使用ctypes库来加载动态链接库。

例如,对于一个C函数,你需要使用ctypes定义其参数类型和返回类型。参数类型是输入类型,返回类型是输出类型。定义好类型后,就可以调用C函数了。Python中的变量会被转换为C函数可用的格式,执行操作后,结果再从C类型转换回Python类型。通过这种方式,可以调用C函数。而对于Fortran,则可以直接使用,无需处理这些复杂步骤。

8.3 使用Python作为计算器 🧮

在学会了如何提升Python性能后,本节我们来看看如何将Python设置为一个方便的计算器。

NumPy库的设计旨在实现类似Matlab的功能,Python可以作为一个便捷的桌面计算器使用。首先,你需要设置启动环境。

例如,如果你使用Linux,可以在.bashrc文件中添加一行配置。如果你使用csh,则在.cshrc文件中添加。在home目录的这个文件中,你可以写入导入语句,例如导入NumPy、SciPy、线性代数包、绘图包以及一些常用函数。你还可以声明x, y, z, t等符号变量,并添加一些说明行。这样,当你打开桌面计算器时,就能看到已导入的内容。

打开Python后,这些信息会被打印出来,你可以看到导入了哪些库和变量。你还可以添加注释来查看详细信息。Python是一种非常方便的工具,具有高度的灵活性和易用性。


本节课中,我们一起学习了Python编程语言的核心优势与速度瓶颈,探讨了使用NumPy、Cython及编译语言模块来加速程序的方法,并了解了如何将Python配置为强大的桌面计算器。掌握这些知识,将帮助你在人工智能编程中更高效地使用Python。

050:Python 核心概念、循环与函数 🐍

在本节课中,我们将学习 Python 编程语言的核心概念,包括其基本特性、数据结构(如列表、元组、字符串、字典和集合)、变量赋值、输入输出以及如何编写函数和循环。这些是构建更复杂程序的基础。

Python 核心特性 ✨

Python 是一种简单、易读、开源的编程语言,易于学习。
它是一种解释型语言,而非编译型语言,这意味着它是脚本语言。
变量无需声明类型。
Python 支持面向对象的编程范式。
它是跨平台的,可以在 Linux、Windows 和 macOS 系统上使用。
它拥有庞大的标准库。
Python 是一种高级语言,这意味着它易于使用。高级语言不一定比低级语言更好,但它更易于使用且简单,同时也非常强大。
Python 不支持指针。
Python 代码文件必须以 .py 扩展名存储。
一个关键特性是使用缩进。Python 使用缩进来定义代码块。这非常有用,因为如果没有正确的缩进,代码将无法运行,这有助于避免错误。
代码块以缩进开始,以第一个非缩进行结束。
缩进量由用户决定,但在整个代码块中必须保持一致。
注释可以通过使用 # 符号来添加。
多行注释可以通过使用三引号(单引号或双引号)来添加。

序列数据类型 📊

Python 中有几种重要的序列数据类型:列表、元组和字符串。

列表使用方括号 [] 定义,可以包含字符串、整数或浮点数等多种类型的元素。

L = [‘a’, 20, 5.5]

元组使用圆括号 () 定义,元素之间用逗号分隔。

T = (1, 2, 3)

一个元素的元组可以这样定义。

single_element_tuple = (1,)

字符串可以使用单引号、双引号或三引号定义。

s1 = ‘hello’
s2 = “world”
s3 = “””multi-line
string”””

访问序列元素

索引从 0 开始。你可以通过索引访问单个元素,也可以使用切片同时访问多个元素。
-1 表示最后一个元素,-2 表示倒数第二个元素,依此类推。

对于字符串,在声明之后,可以通过索引访问字符。例如,从位置 2 到 5(不包括 5)的切片可以获取子字符串。
换行符 \n 在字符串中不可见,但确实存在。

切片有一些规则。对于一个列表或数组,索引从 0 开始。
切片操作 [start:end] 会返回从索引 startend-1 的元素。
例如,L[1:4] 会返回列表中索引为 1、2、3 的元素。

对于元组,切片操作方式相同。T[1:4] 会返回元组中从第二个到第四个(不包括第四个)的元素。

序列的加法与乘法

列表、元组和字符串的加法和乘法操作与常规的数学运算不同。
加法 + 用于连接两个序列。
乘法 * 用于重复序列多次。

list1 = [1, 2]
list2 = [3, 4]
result_list = list1 + list2  # 结果为 [1, 2, 3, 4]

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/dd5cdb68e84c4a5f5cd6b5428c5d23bd_3.png)

tuple1 = (‘a’, ‘b’)
result_tuple = tuple1 * 3  # 结果为 (‘a’, ‘b’, ‘a’, ‘b’, ‘a’, ‘b’)

str1 = ‘hello ‘
str2 = ‘world’
result_str = str1 + str2  # 结果为 ‘hello world’

引用与复制

在 Python 中,简单的赋值 B = A 并不会创建一个新的列表,而是创建了一个对原列表 A 的新引用 B。因此,修改 A 也会影响到 B

A = [1, 2, 3]
B = A
A.append(4)
print(B)  # 输出 [1, 2, 3, 4],B 也随之改变

要创建真正的独立副本,需要进行深拷贝。

循环与迭代 🔄

range() 函数用于生成一个数字序列,常用于循环。
range(8) 生成从 0 到 7 的整数序列。

for i in range(8):
    print(i)  # 输出 0, 1, 2, 3, 4, 5, 6, 7

使用 len() 函数可以获取列表的长度。

my_list = [1, 2, 3, 4]
length = len(my_list)  # length 的值为 4

在迭代中,可以这样使用:

for i in range(len(my_list)):
    print(my_list[i])

深拷贝与浅拷贝

之前提到,简单的赋值是引用。要创建完全独立的新对象,需要使用深拷贝。

from copy import deepcopy

A = [1, 2, 3]
B = A          # 浅拷贝,B 是 A 的引用
C = deepcopy(A) # 深拷贝,C 是 A 的完全独立副本

A[0] = 99
print(B)  # 输出 [99, 2, 3],B 随 A 改变
print(C)  # 输出 [1, 2, 3],C 保持不变

简单的赋值(=)在某种意义上只是重命名,A 有了另一个名字 B。所以当 A 的值改变时,B 的值也会改变。

Python 常用规则与技巧 🛠️

有时一行代码需要做很多事情,可能会超过一行的长度。这时可以使用反斜杠 \ 进行续行,或者使用圆括号、方括号或花括号将内容括起来,这样换行符会被忽略,被视为同一行。

# 使用反斜杠续行
long_string = “This is a very long string that “ \
              “spans multiple lines.”

# 使用括号自动续行
my_list = [
    1, 2, 3,
    4, 5, 6
]

使用分号 ; 可以在同一行中放置多个语句。

a = 1; b = 2; c = 3

可以在一行中为多个变量赋值。

a, b, c = 1, 2, “hello”

也可以这样赋值。

[a, b, c] = [1, 2, “hello”]

函数文档字符串

在定义函数时,可以编写文档字符串。使用三引号(单引号或双引号)将其括起来,它会被视为函数的帮助文档。类似于 MATLAB 中的 help 命令,输入函数名可以查看其开头的文档字符串部分。

def my_function():
    “””
    这是一个示例函数。
    它目前什么都不做。
    “””
    pass

help(my_function)  # 会显示上面的文档字符串

这种方法会捕获文档字符串部分并打印出来。

变量交换与类型

在一行中交换两个变量的值在 Python 中非常方便。

b, c = c, b  # 交换 b 和 c 的值

在其他语言中,这通常不允许,需要借助一个临时变量。

在 Python 中,一旦为变量赋值,其类型会自动确定。但是,如果你在不同上下文中使用它,类型可以很容易地转换。

a = 4      # a 是整数 (int)
b = 2.0    # b 是浮点数 (float)
c = a + b  # c 是浮点数 6.0,整数 a 被转换为浮点数进行运算

可以使用 type() 函数检查变量的类型。

print(type(a))  # 输出 <class ‘int’>
print(type(b))  # 输出 <class ‘float’>

集合与字典 🗂️

Python 集合 (set) 是一个无序且不包含重复元素的集合。

A = {1, 2, 2, 3, 4}
B = {3, 4, 5, 6}
print(A)  # 输出 {1, 2, 3, 4},重复项被移除

集合支持交集、并集等操作。

print(A & B)  # 交集,输出 {3, 4}
print(A | B)  # 并集,输出 {1, 2, 3, 4, 5, 6}

Python 字典 (dict) 对于数据处理非常有用。它由键-值对组成。

D = {‘Seth’: 22, ‘Alex’: 21}

你可以通过键来访问字典中的值。

print(D[‘Seth’])  # 输出 22

值可以是任何类型,比如字符串。

D[‘City’] = ‘Stackville’
print(D)  # 输出 {‘Seth’: 22, ‘Alex’: 21, ‘City’: ‘Stackville’}

输入与输出 📥📤

Python 的 print 函数输出格式非常灵活。

x = 5.0
y = 3
print(“x is”, x, “and y is”, y)  # 输出: x is 5.0 and y is 3

你可以使用格式化字符串来精确控制输出。

print(“x is %.1f and y is %d” % (x, y))  # 输出: x is 5.0 and y is 3
print(“y is {1} and x is {0}”.format(x, y))  # 输出: y is 3 and x is 5.0

你可以指定分隔符 sep 和结束符 end

print(“x =“, x, “; y =“, y, sep=‘|’, end=‘!\n’)  # 输出: x =|5.0|; y =|3!

使用 input() 函数可以获取用户输入。

city = input(“Enter your city: “)
print(“You entered:”, city)

函数与循环实战 🚀

现在,我们将综合运用循环和函数来编写一个程序,该程序返回自然数的立方。

我们定义一个函数 get_cubes,它接受一个数字 n 作为输入。

def get_cubes(n):
    cubes = []  # 创建一个空列表
    for i in range(1, n+1):  # 从 1 循环到 n
        cubes.append(i**3)   # 计算立方并添加到列表
    return cubes

为了让这个函数可以独立运行,我们通常使用 if __name__ == ‘__main__’: 这个条件。当这个 .py 文件被直接运行时,__name__ 的值是 ’__main__’,下面的代码块会被执行。如果它被作为模块导入到其他文件,则不会执行。

if __name__ == ‘__main__’:
    num = int(input(“Enter a number: “))
    result = get_cubes(num)
    print(“The cubes are:”, result)

从其他文件调用函数

我们可以在另一个 Python 文件中导入并使用 get_cubes 函数。

创建一个新文件,例如 caller.py

# 从 get_cubes.py 文件中导入所有内容(包括 get_cubes 函数)
from get_cubes import *

# 现在可以直接调用函数,无需交互式输入
my_cubes = get_cubes(5)
print(“Cubes from 1 to 5 are:”, my_cubes)

当运行 caller.py 时,get_cubes.py 中的 if __name__ == ‘__main__’: 代码块不会被执行,只有 get_cubes 函数被导入和使用。


本节课中我们一起学习了 Python 的核心特性、多种数据结构(列表、元组、字符串、字典、集合)的操作、变量的赋值与引用机制、灵活的输入输出方法,以及如何编写函数和循环来构建实用的程序。理解这些基础概念是迈向更高级 Python 编程的关键步骤。

051:Lecture 51 - 在Python中实现牛顿-霍纳法求多项式零点 🧮

在本节课中,我们将学习如何在Python中实现牛顿-霍纳法来寻找多项式的零点,并将其与之前学习的MATLAB实现进行比较。

概述

上一节我们介绍了牛顿迭代法的基本思想。本节中,我们来看看如何结合霍纳法,在Python中高效地实现求解多项式零点的算法。我们将从回顾牛顿-霍纳法的原理开始,然后逐步编写Python代码,并与MATLAB版本进行对比。

牛顿-霍纳法原理回顾

牛顿法用于寻找函数 \(f(x)=0\) 的近似解。其迭代公式可以表述为:

\[x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} \]

在每次迭代中,我们需要计算多项式 \(P(x)\) 在点 \(x_n\) 处的值及其导数值 \(P'(x_n)\)

为了高效地同时计算这两个值,我们可以使用霍纳法。假设多项式 \(P(x)\) 可以表示为:

\[P(x) = (x - x_0)Q(x) + R \]

根据多项式余数定理,当我们在 \(x_0\) 处求值时,\(R = P(x_0)\)。同时,\(P(x)\)\(x_0\) 处的导数 \(P'(x_0)\) 等于商式 \(Q(x)\)\(x_0\) 处的值 \(Q(x_0)\)

因此,通过一次霍纳法计算,我们可以在同一点 \(x_0\) 同时得到 \(P(x_0)\)\(P'(x_0)\) 的值,这正是牛顿迭代所需的。

在Python中实现算法

现在,我们将使用牛顿法和霍纳法在Python中实现代码,以寻找多项式 \(P(x) = x^4 - 3x^3 - 15x^2 + 19x + 30\) 的一个零点近似解。

首先,我们可以利用Python的numpy库来方便地处理多项式。numpy的设计借鉴了MATLAB,旨在提供类似的便捷性,并被大多数科学计算Python包(如SciPy、Pandas、Matplotlib)所使用。因此,如果你理解MATLAB,Python会很容易上手。

以下是实现牛顿-霍纳法的Python函数:

def newton_horner(quot, x0, tol=1e-12, max_iter=1000):
    """
    使用牛顿-霍纳法寻找多项式零点。
    参数:
        quot: 多项式系数列表(按升序排列)。
        x0: 初始猜测值。
        tol: 容差。
        max_iter: 最大迭代次数。
    返回:
        x: 找到的零点近似值。
        n_iter: 执行的迭代次数。
    """
    n_iter = 0
    x = x0
    for n_iter in range(max_iter):
        # 使用霍纳法计算 P(x) 和 P'(x)
        p = quot[-1]
        d = 0
        for coef in quot[-2::-1]:
            d = p + x * d
            p = coef + x * p
        # 计算修正项并更新x
        h = p / d
        x = x - h
        # 检查收敛条件
        if abs(h) <= tol:
            break
    return x, n_iter + 1

代码解释如下:

  • 函数newton_horner接收多项式系数quot、初始值x0等参数。
  • 我们初始化迭代计数n_iter和当前点x
  • 在循环中,我们使用霍纳法同时计算多项式值p和导数值d
  • 计算牛顿修正项 h = p / d,并更新 x = x - h
  • 如果修正项的绝对值小于容差tol,则停止迭代。
  • 函数返回最终的x值和迭代次数。

接下来,我们定义多项式系数并调用该函数:

# 多项式 P(x) = x^4 - 3x^3 - 15x^2 + 19x + 30 的系数(升序)
quot = [30, 19, -15, -3, 1]
x0 = 3.0
tolerance = 1e-12
max_iterations = 1000

x_final, num_iters = newton_horner(quot, x0, tolerance, max_iterations)
print(f"牛顿-霍纳法从 x0={x0} 开始")
print(f"最终解: {x_final}")
print(f"迭代次数: {num_iters}")

运行此代码,输出结果将显示算法从x0=3开始,经过7次迭代后收敛到解x≈2

与MATLAB代码对比

为了加深理解,我们将Python实现与之前章节中的MATLAB代码进行对比。

以下是MATLAB中的newton_horner函数核心部分:

function [x, n_iter] = newton_horner(quot, x0, tol, max_iter)
    n_iter = 0;
    x = x0;
    for n_iter = 1:max_iter
        % 霍纳法计算 P(x) 和 P'(x)
        [p, d] = horner(quot, x);
        % 牛顿更新
        h = p / d;
        x = x - h;
        % 检查收敛
        if abs(h) <= tol
            break;
        end
    end
end

以及调用方式:

quot = [30, 19, -15, -3, 1];
x0 = 3;
tol = 1e-12;
max_iter = 1000;
[x_final, n] = newton_horner(quot, x0, tol, max_iter);
fprintf('牛顿-霍纳法从 x0=%d 开始\n', x0);
fprintf('最终解: %f\n', x_final);
fprintf('迭代次数: %d\n', n);

通过对比可以发现,Python代码和MATLAB代码在算法逻辑和结构上具有高度的相似性。主要的区别在于语法细节(如循环语法、打印输出)。这种相似性使得熟悉MATLAB的用户能够相对轻松地过渡到Python进行科学计算。

结果验证

我们找到的解 x≈2 确实是给定多项式的一个实根。该多项式共有四个根,包括两个实根和两个复根。我们的算法成功地找到了其中一个实根。

总结

本节课中我们一起学习了如何在Python中实现牛顿-霍纳法来求解多项式的零点。我们首先回顾了算法的数学原理,然后逐步编写了Python代码,并通过与MATLAB版本的详细对比,展示了两种语言在实现科学计算算法时的相似性。关键点在于利用霍纳法高效计算多项式值及其导数值,从而驱动牛顿迭代。掌握这一方法后,你就能在Python环境中解决类似的数值计算问题。

052:类与继承 🐍

在本节课中,我们将要学习 Python 中一个核心概念:。类是面向对象编程的基石,它允许我们将数据和功能捆绑在一起。我们将从定义一个简单的类开始,逐步学习如何创建对象、定义方法,并最终探索如何通过继承来复用和扩展已有的类。


1. 什么是类?🧱

上一节我们介绍了编程的基本概念,本节中我们来看看 Python 中的类。

类是一种将数据和功能捆绑在一起的手段。它通常被定义为一个模板或原型,现实世界中的对象可以基于这个模板被创建出来。

使用类的主要机制在于函数和对象之间的共享。类中的函数被称为方法。初始化过程和数据共享的边界必须被清晰、方便地声明。

一个类会告诉我们:

  • 对象应该包含哪些数据。
  • 这些数据的初始默认值是什么。
  • 有哪些方法与对象关联以执行操作。

这实际上是一种实现要求,因此你必须按照这种方式来实现类。


2. 创建对象与实例化 🏗️

一个对象是类的一个实例。从一个类创建对象的过程称为实例化

在本节中,我们将构建一个简单的类,就像南卫理公会大学的数学教授 T. Su 在其书中所做的那样。我们将学习如何初始化、定义和使用类。

以下是一个简单的多项式类定义:

class Polynomial:
    """这是一个表示多项式的类。"""

    def __init__(self, coeff):
        self.coeff = coeff

    def degree(self):
        return len(self.coeff) - 1

我们来解释一下这段代码:

  • class Polynomial: 定义了一个名为 Polynomial 的类。
  • def __init__(self, coeff): 这是一个特殊的初始化方法。当像 p2 = Polynomial([1, 2, 3]) 这样创建对象时,它会自动被调用。
  • self 参数代表对象实例本身。通过 self.coeff = coeff,我们将传入的系数列表 coeff 存储为对象的一个属性。
  • 一旦通过 self.coeff 存储,这个属性就可以被类中的其他所有方法共享和访问,也可以通过对象名(如 p2.coeff)从外部访问。
  • degree 方法计算并返回多项式的次数。它不需要额外输入,因为可以通过 self.coeff 访问到系数。

例如,对于多项式 x^2 + 2x + 3,系数列表是 [1, 2, 3],次数为 2。


3. 类属性与更多方法 ⚙️

现在,我们尝试定义一个功能更丰富的多项式类。

class Polynomial:
    """这是一个更高级的多项式类。"""
    count = 0  # 这是一个类属性

    def __init__(self, coeff=None):
        if coeff is None:
            coeff = [0]
        self.coeff = coeff
        Polynomial.count += 1  # 每创建一个对象,计数加1

    def __del__(self):
        Polynomial.count -= 1  # 对象被删除时,计数减1

    def degree(self):
        return len(self.coeff) - 1

    def evaluate(self, x):
        import numpy as np
        x = np.array(x)
        result = 0
        for i, c in enumerate(self.coeff):
            result += c * (x ** i)
        return result

以下是这个类的关键点:

  • 类属性count = 0 是在类内部但在所有方法之外定义的变量。它属于类本身,通过 Polynomial.count 访问,被所有对象共享,就像一个全局参数。
  • __init__ 方法:现在它有一个默认参数 coeff=None。如果不提供系数,则默认创建一个 [0] 列表。每次初始化时,类属性 count 会增加 1,用于统计创建的对象数量。
  • __del__ 方法:这是一个析构器。当对象被删除(例如使用 del 语句)时,它会自动调用,并将 count 减 1。
  • evaluate 方法:这个方法接收一组 x 值,计算多项式在这些点上的函数值并返回。

通过这种方式,你可以在类中定义全局变量、执行不同的初始化、添加删除操作以及其他自定义方法。


4. 使用类:示例演示 💻

让我们看看如何使用上面定义的类。

# 创建第一个对象,使用默认系数
poly1 = Polynomial()
print(“poly1.coeff:”, poly1.coeff)  # 输出: [0]
print(“poly1.degree():”, poly1.degree())  # 输出: 0

# 手动赋值并检查
poly1.coeff = [1, 0, -3, 0, 5]
print(“After assignment - coeff:”, poly1.coeff)  # 输出: [1, 0, -3, 0, 5]
print(“After assignment - degree:”, poly1.degree())  # 输出: 4

# 创建第二个对象,传入系数
poly2 = Polynomial([2, -3, 0, 1])
print(“\npoly2.coeff:”, poly2.coeff)  # 输出: [2, -3, 0, 1]
print(“poly2.degree():”, poly2.degree())  # 输出: 3

# 检查创建的多项式数量
print(“\nNumber of polynomials created:”, Polynomial.count)  # 输出: 2

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/adeac8a6ff985a68af1da7fa51440eb9_19.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/adeac8a6ff985a68af1da7fa51440eb9_20.png)

# 删除一个对象
del poly1
print(“After deletion, count:”, Polynomial.count)  # 输出: 1

# 评估多项式
x_vals = [0, 1, 2, 3]
values = poly2.evaluate(x_vals)
print(“\nEvaluation of poly2 at”, x_vals, “:”, values)

输出结果将展示对象的创建、属性访问、方法调用、类属性统计以及对象删除的整个过程。


5. 类的继承 👨‍👦

如果我们想编写一个类,它只是另一个已有类的特殊版本,我们不需要从头开始重写。我们可以使用继承

被继承的类称为父类(或基类),新创建的类称为子类。子类可以继承父类的所有属性和方法,同时也可以定义自己特有的属性和方法,甚至可以重写父类的方法。

类可以导入外部已实现的函数来定义自己的方法。

让我们看一个例子,创建一个 Quadratic(二次多项式)子类,它继承自 Polynomial 父类。

from utility_poly import *  # 导入一个包含独立`roots`函数的工具模块

class Polynomial:
    # ... (父类定义与之前相同,略) ...

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/adeac8a6ff985a68af1da7fa51440eb9_26.png)

class Quadratic(Polynomial):  # Quadratic 继承自 Polynomial
    def __init__(self, coeff, power_decrease=1):
        super().__init__(coeff)  # 调用父类的初始化方法
        self.power_decrease = power_decrease  # 新增子类特有属性

    def roots(self):
        # 使用外部工具函数计算根,该函数不依赖于类变量
        return roots(self.coeff, self.power_decrease)

    def degree(self):  # 重写父类的方法
        return 2  # 对于二次多项式,次数总是2

代码解析:

  • class Quadratic(Polynomial): 表示 QuadraticPolynomial 的子类。
  • super().__init__(coeff) 调用父类的 __init__ 方法来完成基础初始化。
  • self.power_decrease = power_decrease 添加了一个子类特有的属性,用于指示系数排列顺序(幂次递减或递增)。
  • roots 方法使用了从外部模块导入的独立函数 roots()。这样做的好处是 roots() 函数可以在其他项目中复用,而不依赖于这个类。
  • degree 方法被重写,对于二次多项式直接返回 2,而不是通过计算系数长度得到。

使用子类:

# 创建一个二次多项式对象,系数按幂次递减排列 (2x^2 -3x + 1)
quad1 = Quadratic([2, -3, 1], power_decrease=1)
print(“Roots (power_decrease=1):”, quad1.roots())

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/umich-eecs492-aiprog/img/adeac8a6ff985a68af1da7fa51440eb9_30.png)

# 创建另一个对象,系数按幂次递增排列 (1 -3x + 2x^2)
quad2 = Quadratic([1, -3, 2], power_decrease=0)
print(“Roots (power_decrease=0):”, quad2.roots())

6. 总结与最佳实践 📝

本节课中我们一起学习了 Python 类的核心概念和使用方法。

关于 Python 类的实现,有以下几点最终建议:

  1. 模块化设计优先:与其它编程语言一样,在实现之前应进行良好的模块化设计。将整体操作分解为多个函数,每个函数负责一部分操作。
  2. 明智地使用类:对于小型项目,不一定非要使用类。有时,类会增加不必要的复杂性,并且其方法很难直接应用于其他对象。
  3. 增强可复用性:考虑实现独立的、不依赖于类内部状态的函数(就像我们例子中的 roots() 函数)。这些函数可以被轻松导入到其他项目中,从而增强代码的可复用性。
  4. 有效利用继承:通过继承复用已有类的功能,并通过重写和添加方法来创建更特殊的子类。

尝试聪明地使用类,你就能成为一名优秀的程序员。


本节课中我们一起学习了:

  • 类的定义、属性和方法。
  • 如何实例化对象并使用它们。
  • 类属性与实例属性的区别。
  • 如何通过继承创建子类,并重写父类方法。
  • 编写可复用代码的最佳实践,例如使用独立函数和明智地应用类。

053:从图片、PDF文件和语音数据中提取文本

概述

在本节课中,我们将学习如何构建一个文本提取项目。该项目包含两个核心部分:一是从图像和PDF文件中提取文本,二是从语音数据中提取文本。我们将介绍所需的核心技术、库以及实现步骤,目标是让初学者能够理解并动手实践。


核心技术:OCR与语音识别

文本提取应用的核心技术是OCR语音识别

OCR 是光学字符识别技术,其主要功能是从图像中提取文本。在现代应用中,结合机器学习算法,OCR不仅能提取文本,还能将图像文本转换为音频文件以便收听。目前存在一些强大的文本提取工具,准确率可达98%以上,但大多数并非完全开源可用。

我们将开发两个文本提取算法:一个用于处理图像数据,另一个用于处理语音数据。


项目目标

我们将开发两个独立的Python程序:

  1. PDF/图像转文本
  2. 语音转文本

PDF/图像转文本

  • 输入:可以是图像文件或PDF文件。你的代码需要能同时接受这两种形式。
  • 处理PDF:PDF文件可能包含图像,尤其是在由扫描件生成的情况下。在这种情况下,每一页都应被视为一张图像。
  • 核心任务:提取所有文本内容,并将其转换为音频数据。

语音转文本

  • 输入:语音数据,可以来自麦克风或音频文件。
  • 核心任务:从语音中提取文本内容,并可以播放出来。

示例:PDF/图像转文本程序

以下是实现PDF/图像转文本功能所需的主要库:

  • pytesseract:这是核心库,基于机器学习,用于从图像中提取文本。
  • pdf2image:此包用于将PDF文件转换为图像。
  • PIL / Pillow:用于图像处理。
  • gTTS:Google文本转语音库,质量很好。
  • pygame:用于播放生成的音频数据。
  • os:用于系统操作。
  • colorama:用于在控制台中彩色打印输出。

通过这些库,你可以了解程序的运行状态,并获取可能的警告或错误信息。

现在,让我们看看程序的主要流程。首先,main函数会执行。执行后,程序会提示你输入一个PDF文件或图像文件。

一旦你输入了文件路径,程序会尝试从文件名中获取主干名和扩展名。如果输入是PDF文件,则pdf_mode标志设为1,否则初始为0,表示是图像文件。然后,函数会返回输入的文件路径、主干名和pdf_mode标志。

根据pdf_mode的值,程序会调用不同的函数:如果为1,则调用pdf_to_text;否则,调用image_to_text。目前,这两个函数只是返回临时文本。

对于pdf_to_text函数,由于PDF提取的文本可能很大,你可以在此处将文本保存到文件中,而无需通过return返回。

对于image_to_text函数,你可以尝试返回文本以便播放。获取文本后,使用gTTS库将其转换为音频数据,并保存为文件。同时,程序会用黄色在控制台打印相关信息。你可以选择播放音频,也可以通过注释掉相关代码来仅保存文件。


项目文件与测试

项目目录中包含多个文件。例如,有两个空文件用于测试。通过执行程序并输入不同的文件(如PDF或图像),可以测试功能是否正常工作。

目录中还包含其他Python脚本,例如:

  • add_text_to_image.py:测试Google文本转语音。
  • channel_rating_audio_data_and_image_to_text.py:另一个图片处理示例。
  • pdf_to_text.py:PDF转文本。
  • speech_microphone_to_wave.py:处理麦克风音频。

这些文件提供了如何处理麦克风音频、如何保存和回放的示例代码,你可以从中获得实现提示。


作业任务与要求

以下是需要完成的任务:

  1. 下载资源:下载提供的压缩文件并解压。查看pdf_image_to_text.py文件。
  2. 完成代码:示例代码位于example_code子目录中。你需要首先完成pdf_image_to_text.py的功能。
    • 你可以从pdf_to_text.py文件中寻找线索。该文件是从网络下载的,由他人编写,但对于你完成此函数将非常有用。
  3. 独立完成第二部分:对于第二个程序(语音转文本),你需要从零开始编写。本项目的目的是让你有机会从头开始构建一个程序。你可以从提供的两个示例文件中获得提示。
  4. 代码组织:尝试将所有功能整合到单个文件中(每个命令对应一个文件)。这能增强命令的可移植性。如果将命令分散在多个文件中,则不易移动和复制。
  5. 工作目录:你的工作目录名应以你的姓氏开头。
  6. 测试数据:使用课程提供的30页项目文档作为PDF/图像转文本的测试数据文件。原因是该文档部分页面包含图像,并且有些文本是横向排列、分两行的,或者字体大小不同,这可以测试你的程序是否能正确识别和合并这些文本。
  7. 提交:将你的工作目录压缩为ZIP文件,通过电子邮件提交。报告需解释你所完成的工作,包括测试图像和输出文件。同时,将文件上传到课程指定的系统。

总结

本节课我们一起学习了如何构建一个综合的文本提取项目。我们介绍了从图像和PDF中提取文本的OCR技术,以及从语音中提取文本的基本概念。通过分析示例代码和项目要求,我们明确了实现两个独立程序(PDF/图像转文本、语音转文本)的步骤、所需库以及代码组织方式。最后,我们了解了具体的作业任务、测试方法和提交要求。希望你能通过动手实践,掌握这些人工智能编程的基础应用。

054:向量空间与正交性

在本章中,我们将学习一些线性代数的核心概念。主要主题包括格拉姆-施密特正交化过程、QR分解,以及用于寻找特征值的QR迭代算法。

9.1:Rn的子空间

本节我们将介绍Rn中子空间的定义和基本性质。

一个集合H要成为Rn的子空间,必须满足以下三个性质:

  1. 零向量在集合中。
  2. 对于H中的任意向量u和v,它们的和u+v也在集合中。
  3. 对于H中的任意向量u和任意标量c,标量乘法c·u的结果也在集合中。

如果这三个条件都满足,那么该集合就是Rn的一个子空间。这三个条件可以简洁地表达为:H对线性组合封闭。这意味着它必须包含零向量,并且对向量加法和标量乘法封闭。

Rn本身,连同标准的向量加法和标量乘法,构成一个向量空间。

以下是子空间的例子:

  • R2中通过原点的直线是R2的一个子空间。在二维坐标系中,选择一条通过原点的直线。零向量(原点)在直线上。如果选择直线上的任意两个向量,它们的和仍在直线上。同样,直线上任意向量的标量倍数也仍在直线上。
  • R3中通过原点的平面是R3的一个子空间。可以验证,该平面上向量的任意线性组合仍在该平面上。这个平面可以表示为两个向量v1和v2的张成空间。

列空间

给定一组向量v1, ..., vp,它们的张成空间(span)是所有这些向量所有可能线性组合的集合。根据定义,张成空间对线性组合封闭,因此它是Rn的一个子空间。

现在,考虑一个m×n的矩阵A。由A的列向量张成的子空间称为A的列空间,记作Col A。

列空间的定义
Col A = { 所有形如 c1*a1 + c2*a2 + ... + cn*an 的向量 }
其中,c1到cn是任意标量,a1到an是矩阵A的列向量。

由于每个列向量都属于Rm,它们的线性组合也属于Rm,因此Col A是Rm的一个子空间。

示例:判断向量是否在列空间中

给定矩阵A和向量b,判断b是否在Col A中。

如果b在Col A中,则意味着b是A的列向量的一个线性组合。这个条件等价于线性方程组 A x = b 有解(即系统是相容的)。因此,我们可以通过求解增广矩阵 [A b] 来判断。

例如,给定矩阵A和向量b:

A = [1  -3   0  -4;
    -4   6  -2   3;
     7  -6   7   6]
b = [30; 30; -4]

我们构建增广矩阵并进行行化简。经过一系列初等行变换后,我们得到一个行阶梯形矩阵,其中存在主元列和自由变量。这表明方程组 A x = b 有无穷多解。因此,b在矩阵A的列空间中

零空间

对于一个m×n矩阵A,其零空间(记作Nul A)是齐次线性方程组 A x = 0 的所有解向量的集合。

零空间的定义
Nul A = { x ∈ Rn : A x = 0 }

零空间是Rn的一个子空间。我们可以验证其满足子空间的三个条件:

  1. 零向量:显然,A * 0 = 0,所以零向量在Nul A中。
  2. 对加法封闭:如果u和v都在Nul A中(即 A u = 0A v = 0),那么 A(u+v) = A u + A v = 0 + 0 = 0,所以 u+v 也在Nul A中。
  3. 对标量乘法封闭:如果u在Nul A中(即 A u = 0),那么对于任意标量c,有 A(c u) = c (A u) = c * 0 = 0,所以 c u 也在Nul A中。

因此,Nul A是Rn的一个子空间。

子空间的基

子空间H的一个是一组满足以下两个条件的向量集合:

  1. 它们是线性无关的。
  2. 它们张成整个子空间H。

例如,在R2中,向量 [1; 0][1; 2] 构成R2的一个基。由它们构成的矩阵 [1 1; 0 2] 是可逆的,这意味着对于R2中的任意向量v,方程组 [1 1; 0 2] * x = v 都有唯一解x。因此,这两个向量张成整个R2,并且它们是线性无关的。

标准基:向量 e1 = [1; 0; ...; 0], e2 = [0; 1; ...; 0], ..., en = [0; 0; ...; 1] 称为Rn的标准基。这组向量线性无关,并且能张成整个Rn。

寻找列空间和零空间的基

寻找列空间的基
矩阵A的主元列(在行阶梯形中具有主元的列)构成Col A的一个基。非主元列可以表示为前面主元列的线性组合,因此主元列本身就已经是线性无关的,并且能张成整个列空间。

示例
给定矩阵B,其行阶梯形如下:

[1  0  -3   5  0;
 0  1   2  -1  0;
 0  0   0   0  1]

主元列是第1、2、5列。因此,原矩阵B的第1、2、5列构成Col B的一个基。

寻找零空间的基
通过求解齐次方程组 A x = 0,并将解表示为参数向量形式,其中自由变量对应的系数向量就构成了Nul A的一个基。

示例
给定矩阵A,经过行化简得到:

[1  0  2  0;
 0  1 -1  0;
 0  0  0  1]

对应的齐次方程组为:
x1 + 2*x3 = 0
x2 - x3 = 0
x4 = 0
这里,x3 是自由变量。令 x3 = t,则解为 x = t * [-2; 1; 1; 0]。因此,向量 [-2; 1; 1; 0] 构成了Nul A的一个基。

更一般地,如果方程组 A x = 0 的解有k个自由变量 x_{i1}, ..., x_{ik},其通解可以写成:
x = x_{i1} * u1 + ... + x_{ik} * uk
那么向量 u1, ..., uk 就构成了Nul A的一个基。

秩定理

秩定理揭示了矩阵的列空间和零空间维度之间的重要关系。

设A是一个m×n矩阵。

  • (Rank):矩阵A的秩,记作rank A,是其列空间的维度,也等于其行阶梯形中主元列的个数
  • 零度(Nullity):矩阵A的零度,记作nullity A,是其零空间的维度,也等于齐次方程组 A x = 0自由变量的个数

秩定理
rank A + nullity A = n
其中n是矩阵A的列数。

这个定理是直观的:主元列的个数(秩)加上自由变量的个数(零度),正好等于总的列数。


本章总结
在本节中,我们一起学习了Rn中子空间的核心概念。我们定义了子空间,并介绍了两个重要的子空间:由矩阵列向量张成的列空间(Col A)和齐次方程组解集构成的零空间(Nul A)。我们学习了如何为子空间寻找一组,并理解了秩定理如何将列空间的维度(秩)与零空间的维度(零度)联系起来。这些概念是理解矩阵结构和线性方程组解集的基础。在下一节中,我们将继续探讨向量空间的正交性。

055:第55讲 - 正交集与正交矩阵 📐

在本节课中,我们将学习线性代数中两个核心概念:正交集正交矩阵。我们将了解它们的定义、性质以及如何应用它们来简化向量投影和线性组合的计算。这些概念是理解许多高级算法(如QR分解)的基础。

正交基与正交投影

上一节我们介绍了向量空间的基本概念。本节中,我们来看看一种特殊的向量集——正交集。

正交集与正交基

一个向量集合 {u₁, u₂, ..., u_p} 在 ℝⁿ 中被称为正交集,如果集合中任意两个不同的向量都是正交的。这意味着对于所有 i ≠ j,都有 u_i · u_j = 0。

如果正交集内的所有向量都是非零向量,那么该集合必然是线性无关的。因此,由该集合张成的子空间 W 中,这个正交集就构成了 W 的一个正交基

公式:对于正交集中的任意两个不同向量,有 u_i · u_j = 0 (i ≠ j)。

正交性之所以有用,一个重要原因是它能极大简化向量在子空间中的坐标计算。

正交基下的坐标计算

假设 {u₁, u₂, ..., u_p} 是子空间 W 的一个正交基。那么,对于 W 中的任意向量 y,我们可以将其表示为基向量的线性组合:y = c₁u₁ + c₂u₂ + ... + c_pu_p。

以下是计算每个系数 c_j 的简单方法:

  1. 对等式两边同时与基向量 u_j 做点积。
  2. 由于正交性,所有 i ≠ j 的项 u_i · u_j 都为零。
  3. 我们得到:y · u_j = c_j (u_j · u_j)。
  4. 因此,系数 c_j 可以轻松解出:c_j = (y · u_j) / (u_j · u_j)。

公式:c_j = (y · u_j) / (u_j · u_j)

示例:利用正交性分解向量

考虑一个正交向量集 S = {u₁, u₂, u₃},其中:
u₁ = [1, -2, 1], u₂ = [0, 1, 2], u₃ = [-5, -2, 1]。
向量 y = [11, 0, -5]。

我们的目标是将 y 表示为 S 中向量的线性组合:y = c₁u₁ + c₂u₂ + c₃u₃。

首先验证 S 是正交集(点积均为0),然后应用上述公式:
c₁ = (y·u₁)/(u₁·u₁) = 6/6 = 1
c₂ = (y·u₂)/(u₂·u₂) = (-10)/5 = -2
c₃ = (y·u₃)/(u₃·u₃) = (-60)/30 = -2

因此,y = 1u₁ - 2u₂ - 2*u₃。


向量的正交投影

理解了正交基后,我们来看一个更具体的应用:将一个向量分解为平行于和垂直于另一个向量的两部分。

投影的定义与计算

给定一个非零向量 u ∈ ℝⁿ,我们希望将向量 y 分解为:
y = ŷ + z
其中,ŷ 平行于 u(即 ŷ = αu),而 z 正交于 u(即 z · u = 0)。

向量 ŷ 被称为 yu 上的正交投影zy 正交于 u 的分量。

为了找到 α,我们利用 zu 正交的条件:
z = y - ŷ = y - αu
(y - αu) · u = 0
解得:α = (y · u) / (u · u)

因此,投影 ŷ 的计算公式为:
ŷ = [ (y · u) / (u · u) ] * u

公式ŷ = proj_L(y) = ( (y·u) / (u·u) ) * u,其中 L = Span{u}。

更一般地,我们常说 y 在由 u 张成的直线 L 上的投影,记为 proj_L(y)。这在几何上更直观,无论 yu 的夹角是锐角还是钝角,投影都落在直线 L 上。

示例:计算投影与距离

给定 y = [7, 6],u = [4, 2]。设 L 为由 u 张成的直线。

a. 求 yu 上的投影。
α = (y·u)/(u·u) = (28+12)/(16+4) = 40/20 = 2
ŷ = 2 * u = [8, 4]

b. 将 y 写成 ŷ 与一个垂直于 u 的向量 z 的和。
z = y - ŷ = [7, 6] - [8, 4] = [-1, 2]
验证:ŷ · z = (8-1) + (42) = 0,正确。

c. 求 y 到直线 L 的距离。
这个距离就是正交分量 z 的长度。
距离 = ||z|| = √((-1)² + 2²) = √5


标准正交基与正交矩阵

上一节我们处理了正交投影。本节中,我们来看看当正交向量具有单位长度时,会带来哪些更优美的性质。

标准正交集与标准正交基

如果一个正交集 {u₁, u₂, ..., u_p} 中的每个向量都是单位向量(即长度为1),则称该集合为标准正交集

如果一个子空间 W 的基同时是一个标准正交集,则称该基为 W 的标准正交基

示例:已知 {v₁, v₂, v₃} 是 ℝ³ 的一个正交基,其中 v₁=[1,-2,1], v₂=[0,1,2], v₃=[-5,-2,1]。要得到对应的标准正交基,只需将每个向量单位化:
u₁ = v₁ / ||v₁|| = (1/√6)[1, -2, 1]
u₂ = v₂ / ||v₂|| = (1/√5)[0, 1, 2]
u₃ = v₃ / ||v₃|| = (1/√30)[-5, -2, 1]

具有标准正交列的矩阵

U 是一个 m × n 矩阵。如果 U 的列向量构成一个标准正交集,则称 U 具有标准正交列

一个关键性质是:矩阵 U 具有标准正交列,当且仅当 UU = I_n(n阶单位矩阵)。
证明思路UU 的第 (i, j) 元素就是 U 的第 i 列与第 j 列的点积。标准正交意味着对角线元素(i=j)为1,非对角线元素(i≠j)为0。

正交矩阵及其性质

当矩阵 U方阵(即 m = n)且具有标准正交列时,我们给它一个特殊的名字:正交矩阵

对于一个 n × n 的正交矩阵 U,其定义等价于以下任一条件:

  1. UU = U****Uᵀ = I_n
  2. Uᵀ = U⁻¹ (即其转置等于其逆矩阵)

正交矩阵具有非常重要的保性质变换特性:

  1. 保长度:对于任意向量 x ∈ ℝⁿ,有 ||Ux|| = ||x||。
  2. 保点积:对于任意向量 x, y ∈ ℝⁿ,有 (Ux) · (Uy) = x · y
  3. 保正交性:若 xy,则 UxUy

代码示例(概念演示):生成一个随机正交矩阵并验证其性质。

import numpy as np
n = 4
# 通过QR分解生成一个随机正交矩阵 U
A = np.random.randn(n, n)
Q, R = np.linalg.qr(A)
U = Q
print("正交矩阵 U:\n", U)
print("\n验证 U^T * U 是否为单位矩阵:\n", U.T @ U)
x = np.random.randn(n)
print("\n随机向量 x 的范数:", np.linalg.norm(x))
print("Ux 的范数:", np.linalg.norm(U @ x)) # 两者应相等

总结

本节课中我们一起学习了线性代数的核心工具:正交集与正交矩阵。

  • 我们首先定义了正交集正交基,并展示了如何利用正交性轻松计算向量在子空间中的坐标。
  • 接着,我们探讨了向量的正交投影,学习了如何将一个向量分解为平行于和垂直于给定方向的两个分量,并计算点到直线的距离。
  • 然后,我们引入了标准正交基的概念,并讨论了具有标准正交列的矩阵的性质,即 UU = I
  • 最后,我们聚焦于方阵情形,定义了正交矩阵Uᵀ = U⁻¹),并阐述了其最重要的保长度、保点积、保正交性的特性。这些性质使得正交矩阵在数值计算、图形变换和信号处理等领域中不可或缺。

056:正交投影与正交分解定理 🧮

在本节课程中,我们将学习线性代数中两个核心概念:正交投影正交分解定理。正交投影是许多数学和数值分析应用中的基础工具。我们将从定义出发,逐步理解如何将一个向量分解为子空间内的分量和与该子空间正交的分量,并探讨其几何意义与计算方法。

正交补空间的定义

首先,我们定义向量与子空间的正交关系。设 WR^n 的一个子空间。如果向量 z 与子空间 W 中的每一个向量 w 的内积都为零,即 z · w = 0,那么我们称向量 z 正交于子空间 W

所有正交于子空间 W 的向量构成的集合,称为 W正交补空间,记作 W^⊥

其正式定义为:
W^⊥ = { z ∈ R^n | z · w = 0, ∀ w ∈ W }

正交补空间的例子与性质

为了直观理解,我们来看一个例子。在 R^3 中,设 W 是一个通过原点的平面,L 是一条通过原点且垂直于该平面的直线。从直线 L 上任取一个向量 z,从平面 W 上任取一个向量 w,它们的点积必然为零 (z · w = 0)。实际上,直线 L 就是所有垂直于平面 W 的向量的集合,而平面 W 也是所有垂直于直线 L 的向量的集合。因此,L = W^⊥,同时 W = L^⊥

正交补空间本身也是一个子空间。要证明这一点,只需验证它对线性组合封闭。假设 uv 都在 W^⊥ 中,这意味着对于 W 中任意向量 w,都有 u · w = 0v · w = 0。现在考虑它们的线性组合 c u + d v,计算它与 w 的内积:
(c u + d v) · w = c (u · w) + d (v · w) = c * 0 + d * 0 = 0
由于内积为零,所以 c u + d v 也在 W^⊥ 中,因此 W^⊥R^n 的一个子空间。

从向量投影到子空间投影

上一节我们学习了向量在另一个向量上的投影。给定 R^n 中一个非零向量 u 和任意向量 y,我们可以将 y 唯一地分解为:
y = ŷ + z
其中,ŷ 是与 u 平行的分量(即 yu 上的投影),z 是与 u 正交的分量。我们称 ŷy 在由 u 张成的直线 L 上的正交投影

现在,我们将这个概念推广到更一般的子空间上。正交分解定理指出:设 WR^n 的一个子空间,则对于 R^n 中的任意向量 y,都可以被唯一地写成如下形式:
y = ŷ + z
其中,ŷ ∈ W,而 z ∈ W^⊥

如果 {u₁, u₂, ..., u_p} 是子空间 W 的一组正交基,那么投影分量 ŷ 可以通过一个简洁的公式计算:
ŷ = ( (y·u₁)/(u₁·u₁) ) u₁ + ( (y·u₂)/(u₂·u₂) ) u₂ + ... + ( (y·u_p)/(u_p·u_p) ) u_p
计算出 ŷ 后,正交分量 z 可以通过 z = y - ŷ 得到。

几何解释与最佳逼近

我们可以从几何上理解这个分解。设 W 是一个通过原点的子空间(例如平面),y 是空间中的一个点。那么 ŷ 就是 yW 上的垂足ŷW 中离 y 最近的点,而距离 y 到子空间 W 的距离,正是正交分量 z 的长度 ||z||

这个性质被称为最佳逼近定理。数学表述为:对于 W 中任意其他向量 v,都有
|| y - ŷ || ≤ || y - v ||
并且等号仅在 v = ŷ 时成立。因此,ŷW 中对 y最佳逼近。如果 y 本身就在 W 中,那么它的投影就是它自身,即 ŷ = y

证明利用了勾股定理。将 y - v 重写为 (y - ŷ) + (ŷ - v)。由于 y - ŷ = z ∈ W^⊥,而 ŷ - v ∈ W,这两项正交。根据勾股定理:
|| y - v ||² = || y - ŷ ||² + || ŷ - v ||²
因为 || ŷ - v ||² ≥ 0,所以 || y - v ||² ≥ || y - ŷ ||²,即 || y - v || ≥ || y - ŷ ||

计算实例

让我们通过一个具体例子来演练计算过程。设子空间 W 由两个向量张成:
u₁ = [2, 5, -1]ᵀ, u₂ = [-2, 1, 1]ᵀ
给定向量 y = [1, 2, 3]ᵀ。我们需要将 y 分解为 W 中的分量和 W^⊥ 中的分量。

以下是计算步骤:

  1. 验证 {u₁, u₂}W 的一组正交基(本题中已给出,且 u₁ · u₂ = 0)。
  2. 利用正交基的投影公式计算 ŷ
  3. 计算 z = y - ŷ
  4. ||z|| 即为从 y 到子空间 W 的距离。

首先计算 ŷ 的系数:
α₁ = (y·u₁) / (u₁·u₁) = 9 / 30 = 3/10
α₂ = (y·u₂) / (u₂·u₂) = 3 / 6 = 1/2

然后得到投影向量:
ŷ = α₁ u₁ + α₂ u₂ = (3/10)[2, 5, -1]ᵀ + (1/2)[-2, 1, 1]ᵀ = [0.6-1, 1.5+0.5, -0.3+0.5]ᵀ = [-0.4, 2, 0.2]ᵀ

接着计算正交分量:
z = y - ŷ = [1, 2, 3]ᵀ - [-0.4, 2, 0.2]ᵀ = [1.4, 0, 2.8]ᵀ

因此,分解为:
y = [-0.4, 2, 0.2]ᵀ + [1.4, 0, 2.8]ᵀ
距离为 ||z|| = √(1.4² + 0² + 2.8²)

投影的矩阵表示

最后,我们探讨正交投影的矩阵表示。如果 {u₁, u₂, ..., u_p} 不仅是正交基,还是标准正交基(即每个基向量的长度都为1),那么投影公式可以简化为:
ŷ = (y·u₁) u₁ + (y·u₂) u₂ + ... + (y·u_p) u_p

令矩阵 U = [u₁ u₂ ... u_p],其列向量由这组标准正交基构成。那么,向量 y 在子空间 W 上的正交投影可以通过一个矩阵变换得到:
ŷ = U Uᵀ y
矩阵 P = U Uᵀ 被称为到子空间 W 上的投影矩阵

我们通过一个简单例子验证。设 u₁ = (1/√10, -3/√10)ᵀ 中的单位向量,张成子空间 W。给定 y = (7, 9)ᵀ

  • 用公式计算投影:ŷ = (y·u₁) u₁ = (-20/√10) * (1/√10, -3/√10)ᵀ = (-2, 6)ᵀ
  • 用矩阵计算:构造 U = (1/√10, -3/√10)ᵀ,计算 U Uᵀ y。首先计算 U Uᵀ = (1/10)[1, -3; -3, 9],然后乘以 y 得到 (1/10)[-20, 60]ᵀ = (-2, 6)ᵀ
    两者结果一致,验证了投影的矩阵表示形式。

本节总结

本节课我们一起学习了正交投影的核心思想。我们从正交补空间的定义出发,引入了强大的正交分解定理,该定理保证了任何向量都可以唯一地分解为子空间内分量和正交分量。我们探讨了其几何意义——投影点是子空间中距离原向量最近的点(最佳逼近),并学习了在给定子空间正交基情况下的具体计算方法。最后,我们看到了当基为标准正交基时,投影操作可以优雅地表示为一个矩阵乘法 U Uᵀ y。这些概念是理解后续最小二乘法、傅里叶级数等高级课题的基石。

057:Lecture 57 - Gram-Schmidt过程与QR分解最小二乘解 👨‍🏫

在本节课中,我们将学习如何应用正交投影来构造正交基,这个过程称为Gram-Schmidt正交化。我们还将探讨如何利用此过程进行QR分解,并最终使用QR分解来求解最小二乘问题。

概述 📋

上一节我们学习了正交投影。本节中,我们将应用正交投影进行Gram-Schmidt过程和QR分解。Gram-Schmidt过程是一种算法,用于为R^N中的任何非零子空间生成一组正交或标准正交基。

Gram-Schmidt过程 🧮

Gram-Schmidt过程的核心思想是利用正交投影。给定一组线性无关的向量,我们可以通过逐步投影和减去其在已构建正交基上的分量,来获得一组正交向量。

基本思想与示例

假设我们有一个子空间W,它由向量x1和x2张成。我们的目标是找到W的一组正交基。

主要思路是使用正交投影。对于给定的x1和x2,首先将x2投影到x1上,得到的正交分量v2将与x1正交。这样,我们得到一组正交向量v1(即x1)和v2。

因为v2是x1和x2的线性组合,所以x1、x2张成的空间与v1、v2张成的空间是相同的。

让我们通过一个具体例子来理解这个过程。

W是x1和x2的张成空间。这两个向量来自R^3。我们将尝试为W找到一个正交基。

现在,x2必须被投影到x1的空间上。所以,x2 = α * x1 + v2。对于这个投影,α必须是 (x2 · x1) / (x1 · x1)。

计算如下:

  • x2与x1的点积是3。
  • x1与自身的点积是9 + 36 = 45。
  • 因此,α = 3/45 = 1/3。

所以,v2 = x2 - (1/3) * x1。

  • x2 = [1, 2, 2]。
  • (1/3) * x1 = (1/3) * [3, 6, 0] = [1, 2, 0]。
  • 因此,v2 = [1-1, 2-2, 2-0] = [0, 0, 2]。

现在,我们收集v1和v2:

  • v1 = x1 = [3, 6, 0]。
  • v2 = [0, 0, 2]。

这两个向量构成了W的一组正交基。

如上图所示,x1是一个向量,x2是另一个向量。我们不直接使用x1和x2,而是将x2投影到x1的空间上,得到投影分量和正交分量v2。这样,v1和v2就构成了一组正交基。

过程的一般化

现在我们将这个想法一般化。对于R^N中非零子空间W的一组基(即线性无关的向量){x1, x2, ..., xp},我们按如下方式定义向量{v1, v2, ..., vp}:

  1. 设 v1 = x1。
  2. 对于 k = 2 到 p,计算 vk = xk - Σ_{i=1}^{k-1} ( (xk · vi) / (vi · vi) ) * vi。

这个集合 {v1, v2, ..., vp} 就是 W 的一组正交基。此外,对于每个 k (1 ≤ k ≤ p),前 k 个 x 向量张成的子空间与前 k 个 v 向量张成的子空间是相同的。

获得标准正交基

这是一组正交基。一旦我们将这些正交向量标准化(即除以其长度),这组正交基就变成了标准正交基

实际上,Gram-Schmidt过程通常以实现为包含标准化步骤的版本,这意味着在算法中直接生成标准正交向量。

让我们尝试为W找一组标准正交基,W由x1, x2, x3张成,这些向量来自R^4。

我们将使用Gram-Schmidt过程。
首先,v1 = x1。
接着,计算v2 = x2 - α * x1,其中 α = (x2 · x1) / (x1 · x1)。

  • x2与x1的点积是 -3。
  • x1与自身的点积是 3。
  • 因此 α = -1。
  • 所以 v2 = x2 - (-1)*x1 = x2 + x1 = [0, 2, 0, 2]。

然后,计算v3 = x3 - α1 * v1 - α2 * v2。

  • α1 = (x3 · v1) / (v1 · v1) = 2 / 3。
  • α2 = (x3 · v2) / (v2 · v2) = 3 / 6 = 1/2。
  • 所以 v3 = x3 - (2/3)v1 - (1/2)v2。计算后可得v3。

最后,为了得到标准正交基{u1, u2, u3},我们将每个v除以其长度(范数):

  • u1 = v1 / ||v1||
  • u2 = v2 / ||v2||
  • u3 = v3 / ||v3||

这样,u1, u2, u3就构成了W的一组标准正交基。

QR分解 🔧

现在我们可以利用这项技术进行QR分解。设A是一个m×n矩阵,其列线性无关。那么它可以被分解为:
A = Q R
其中:

  • Q是一个m×n矩阵,其列是标准正交的(即 Q^T Q = I)。
  • R是一个n×n可逆的上三角矩阵,其对角线元素为正。

分解原理与算法

这个定理的证明相当简单。我们从矩阵A开始,其列线性无关,因此我们可以为A的列空间(即W)获得一组标准正交基。这些基向量构成了矩阵Q的列。

由于这是一组基,A的每一列xk都可以表示为这些标准正交向量的线性组合。具体来说,xk = Σ_{i=1}^{k} (xk · ui) * ui。当我们收集这些系数时,就得到了上三角矩阵R。

因此,主要思想是:我们可以通过Gram-Schmidt过程得到QR分解。

以下是QR分解的算法:

  1. 给定矩阵A,其列x1到xn线性无关。
  2. 对A的列应用Gram-Schmidt过程,得到列空间的一组标准正交基{u1, u2, ..., un}。这些构成了矩阵Q的列。
  3. 对于每个k,计算系数 r_ik = ui · xk (对于 i ≤ k)。这些系数构成了上三角矩阵R的元素。对角线元素 r_kk 应为正。如果为负,可以通过同时改变对应Q列的符号和R行/列的符号来调整。
  4. 或者,在得到Q后,直接计算 R = Q^T A

在实践中,系数 r_ij = ui · xj 可以在标准化的Gram-Schmidt过程中保存下来。

QR分解示例

让我们尝试为给定的矩阵A找到QR分解。

我们将遵循上述算法。首先,使用Gram-Schmidt过程获得标准正交基,然后这些向量的集合就是Q,R可以通过点积或矩阵乘法得到。

计算过程如下:

  • 设x1 = [4, 3], x2 = [-1, 2]。
  • 计算v1 = x1。
  • 计算α = (x2 · x1) / (x1 · x1) = 2/25。
  • 计算v2 = x2 - α*x1 = ... 最终得到v2。
  • 标准化v1和v2得到u1和u2,它们构成矩阵Q。
  • 然后可以通过公式计算R,或者简单地计算 R = Q^T A

你可以自行完成计算。

利用QR分解求解最小二乘问题 🎯

现在,我们使用QR分解技术来替代计算最小二乘解。设A是一个m×n矩阵(m ≥ n),并且假设A的秩为n(即列线性无关)。

我们已经知道最小二乘问题 min ||Ax - b|| 有唯一解,并且可以通过正规方程求解:x = (A^T A)^{-1} A^T b。矩阵 (A^T A)^{-1} A^T 被称为A的伪逆,记作 A^+

这里,我们将尝试使用QR分解来解决这个问题。对于相同的矩阵A(列线性无关),有A = QR。那么,对于任意的b ∈ R^m,方程Ax = b的唯一最小二乘解由下式给出:
x = R^{-1} Q^T b

原理证明

我们来推导这个公式。A的伪逆是 A^+ = (A^T A)^{-1} A^T。
将A = QR代入:
A^+ = ((QR)^T (QR))^{-1} (QR)^T = (R^T Q^T Q R)^{-1} R^T Q^T
由于Q的列是标准正交的,Q^T Q = I,所以:
A^+ = (R^T R)^{-1} R^T Q^T = R^{-1} (RT) R^T Q^T = R^{-1} Q^T
因此,最小二乘解 x = A^+ b = R^{-1} Q^T b。

示例

对于给定的A和b,我们可以很容易地看出A的列是线性独立的,因此可以进行具有正对角线元素的QR分解。一旦得到分解,首先计算Q^T b,然后乘以R的逆,就可以得到解。请你自己尝试计算。

总结 📝

本节课中,我们一起学习了:

  1. Gram-Schmidt正交化过程:一种通过逐步正交投影,将任意一组线性无关向量转化为正交基或标准正交基的算法。
  2. QR分解:将列线性无关的矩阵A分解为一个列标准正交的矩阵Q和一个上三角可逆矩阵R的乘积,即 A = QR。这个过程的核心是Gram-Schmidt正交化。
  3. QR分解的应用:利用QR分解可以高效、稳定地求解最小二乘问题。解的形式为 x = R^{-1} Q^T b,这避免了直接计算可能病态的 (A^T A)^{-1}

这些工具在数值线性代数、信号处理和机器学习等领域中对于处理线性系统和数据拟合问题至关重要。

058:第58讲 - QR迭代算法用于寻找特征值 🧮

概述

在本节课中,我们将要学习QR迭代算法。上一节我们介绍了QR分解过程,本节中我们将应用QR分解来寻找矩阵的特征值和特征向量。

QR迭代算法

对于对称矩阵 A,QR迭代算法在每一步中执行QR分解,并通过矩阵乘法更新矩阵 A。算法从单位矩阵开始,通过右乘正交矩阵来更新变换矩阵 U。算法非常简单。一旦算法收敛,矩阵 A 的极限将是对角矩阵 T,矩阵 U 的极限将是正交矩阵。

以下是QR迭代算法的核心步骤:

  1. 初始化:设 A₀ = AU₀ = I
  2. k = 1, 2, ... 进行迭代:
    • Aₖ₋₁ 进行QR分解:Aₖ₋₁ = Qₖ Rₖ
    • 更新矩阵:Aₖ = Rₖ Qₖ
    • 更新变换矩阵:Uₖ = Uₖ₋₁ Qₖ
  3. Aₖ 的非对角线元素足够小时,停止迭代。

算法原理

从算法中,我们可以看到 Aₖ = Rₖ Qₖ。由于 Qₖ 是正交矩阵,我们可以推导出 Aₖ = Qₖᵀ Aₖ₋₁ Qₖ。通过递归应用这个等式,我们可以得到 Aₖ = (Q₁ Q₂ ... Qₖ)ᵀ A (Q₁ Q₂ ... Qₖ)

一旦算法收敛,Uₖ 的极限就是 UAₖ 的极限就是 T。因此,我们有 T = Uᵀ A U

舒尔分解

T 是一个上三角矩阵,其对角线元素就是矩阵 A 的特征值。U 是一个正交矩阵。因此,我们可以将 A 写成 A = U T Uᵀ。这种分解被称为矩阵 A 的舒尔分解。

为什么 T 是三角矩阵?让我们观察矩阵乘法。R 是上三角矩阵,Q 是正交矩阵(其列向量是标准正交基)。在乘积 R Q 中,由于 R 的下三角部分为零,这个乘积会使得结果矩阵的下三角部分数值逐渐变小,最终收敛到零。因此,极限 T 必然是一个三角矩阵。

对称矩阵的特殊情况

一旦矩阵 A 是对称的,那么 T 就会变成由 A 的特征值构成的对角矩阵,而 U 则是对应的特征向量的集合。如果 A 对称,那么 T 也应该对称。如果 T 的下三角部分收敛到零,那么其上三角部分也必须同时收敛到零,因此 T 必须是一个对角矩阵。

示例与代码实现

我们有两个矩阵 ABA 不是对称矩阵,而 B 是对称矩阵。我们将应用QR迭代算法来寻找它们的舒尔分解。

以下是QR迭代算法的Python实现核心逻辑:

def qr_iteration(A, max_iter=1000, tol=1e-5):
    n = A.shape[0]
    T = A.copy()
    U = np.eye(n)
    for i in range(max_iter):
        Q, R = np.linalg.qr(T)
        T = R @ Q
        U = U @ Q
        # 检查非对角线元素的变化是否小于容差
        off_diag_change = np.abs(T - np.diag(np.diag(T))).max()
        if off_diag_change < tol:
            break
    return T, U

在实现中,我们初始化 T 为给定的矩阵 AU 为单位矩阵。然后进行迭代:对 T 进行QR分解,更新 T = R Q,并更新变换矩阵 U = U Q。停止条件通常是检查 T 的非对角线元素(或对角线元素的变化)是否小于一个预设的容差(例如 10^-5)。

结果分析

对于非对称矩阵 A,经过26次迭代后算法收敛。得到的 T 矩阵是上三角矩阵,其对角线元素就是特征值。我们尝试用 U T Uᵀ 恢复原始矩阵,结果成功恢复。通过特征值计算得到的特征向量也与算法结果一致。

对于对称矩阵 B,经过28次迭代后算法收敛。得到的 T 矩阵是对角矩阵,对角线上的值就是特征值。同样,我们可以成功恢复原始矩阵,并且计算得到的特征向量与算法结果匹配。

观察每次迭代后矩阵 Aₖ 的变化:在非对称情况下,矩阵的下三角部分数值随着迭代逐渐变小。在对称情况下,矩阵始终保持对称,其非对角线元素对称地逐渐减小,最终趋近于零。

总结

本节课中我们一起学习了QR迭代算法。我们了解了该算法如何通过重复的QR分解和矩阵乘法,将一个矩阵迭代地转化为(近似)上三角形式,从而得到其特征值。对于对称矩阵,该算法能直接得到对角矩阵和特征向量。通过Python代码示例,我们看到了算法的具体实现和收敛过程。QR迭代是数值线性代数中求解特征值问题的一个强大而基础的工具。

059:机器学习简介 🧠

在本章中,我们将学习什么是机器学习,并了解一些流行的机器学习分类器。我们还将介绍一个名为Scikit-learn的Python机器学习库。最后,我们会看到一些“母代码”(基础代码)。你可以修改这些母代码来实现自己的机器学习算法,并且可以轻松地将你的算法与流行的机器学习分类器进行比较。本章是对机器学习的简要介绍,希望能对你有所帮助。


人工智能编程:10.1:什么是机器学习?🤔

大多数现实世界的问题都可以表示为 f(x) = y。其中,f 是一个操作(函数),x 表示输入,y 是输出。一旦 fx 已知,得到输出就是最简单的任务。如果 fy 未知,那么求解输入就需要解方程。如果 xy 已知,我们就需要找到操作 f,但这个任务并不简单。机器学习正是处理这类问题的。

我们在此定义机器学习:

机器学习算法是能够从数据中学习并生成函数或模型的算法。机器学习是一门让机器在没有被明确编程的情况下采取行动的科学。

机器学习算法有不同的类型:

  • 监督学习:通常使用带有标签的数据集。
  • 无监督学习:使用没有标签的数据,例如聚类。
  • 强化学习:使用奖励系统。

最流行的类型是监督学习,因此我们将重点介绍它。机器学习算法基于一个信念:平均结果是正确的,至少是可接受的。

让我们看一个例子。1907年,在英国普利茅斯的一个集市上,弗朗西斯·高尔顿(查尔斯·达尔文的表弟)举办了一场猜测公牛体重的竞赛。共有800人参与猜测。其中13个答案不可靠(如0、无穷大或难以辨认),因此被排除。对于剩余的猜测,计算出的平均值为1197磅。之后,公牛的实际体重被称量为1198磅。因此,来自许多人的猜测的平均值,误差仅为1磅。这表明,如果我们使用数据集中的许多数据点,那么平均值至少在可接受的范围内是正确的。基于这个信念,机器学习算法得以发展。


人工智能编程:10.2:监督学习 👁️

上一节我们介绍了机器学习的基本概念,本节中我们来看看监督学习。

监督学习的假设是,对于一个数据集 { (x_i, y_i) },其中 y_i 是标签,存在一个真实的函数 fx 映射到 y。因此,监督学习的任务是找到这个函数 f。通过使用训练数据集 { (x_i, y_i) },我们试图得到一个函数 f_hat,它是原始真实函数 f 的良好近似。我们并不知道真实的 f,所以一旦我们找到了一个函数或模型,它就会成为原始函数的一个近似。

从图示上,我们可以解释监督学习。训练数据会与标签一起给出。利用这个数据集,大多数学习算法会构建一个模型。之后,当新的数据点出现时,就可以使用这个模型进行预测。这就是监督学习的图示解释。

如图所示,机器学习就是构建一个模型或函数,例如一个超平面。当数据集中有两类数据时,算法会尝试找到一条线或一个超平面,使得当新点到来时,其值可以是负的或正的,从而可以做出决策。这个超平面的值为0,因此对于给定的数据点(负值、正值),我们可以做出判断。这就是构建模型的主要思想。同样,对于这些数据点,我们试图找到其趋势或走向,这涉及到一种回归分析。


人工智能编程:10.3:无监督学习 🔍

上一节我们讨论了监督学习,本节我们转向无监督学习。

无监督学习可用于探索数据集的结构。在开始时,我们一无所知,没有标签。然而,一旦数据集给定,我们就必须发现一些隐藏的结构。这就是无监督学习的目的。在监督学习中我们使用标签,在强化学习中使用奖励系统,但在无监督学习中,我们没有任何这些,只是探索数据集的隐藏结构。

一个例子是聚类。在聚类中,我们尝试创建有意义的子组。请看图中的示例,理想情况下,算法能很好地创建有意义的子组。


人工智能编程:10.4:为什么机器学习不总是那么简单?⚠️

看起来机器学习的概念很简单,但在现实中,机器学习并不总是那么简单。以下是机器学习面临的一些挑战:

1. 过拟合
第一个问题是过拟合。这在训练中很容易发生。在训练中,如果我们对数据集的拟合过于紧密,就会发生过拟合。

例如,在图中,左侧的拟合过于粗糙,而右侧的拟合则过于紧密。假设有一个新的数据点出现,如果模型拟合得太紧,可能会对这个新点做出错误的预测。这就叫过拟合。其问题是,对于测试数据,准确率会显著下降。对于新的数据点,准确率可能很差,但对于给定的训练数据,准确率却非常好。例如,训练数据集的准确率可能达到100%,但对于新数据点,准确率可能远低于100%。

为了克服这个问题,我们可能会尝试使用更多的训练数据。但在现实中,这通常是不可能的。更多的数据意味着需要更多的实验和资金。在训练模型时,通常不可能获得更多的训练数据。此外,训练算法通常是迭代算法,因此你可以尝试提前停止,或者尝试选择一些重要特征来降低维度,或者为目标函数添加正则化项,或者尝试使用多个分类器并通过多数投票来决定输出。这些都是补救措施,但过拟合仍然经常发生。

2. 维度灾难
随着维度的增加,特征空间变得越来越稀疏。

观察图示,在一维空间中,数据点看起来密集。但在二维空间中,它们开始分散。在三维空间中,数据点看起来等距,且彼此远离。因此,识别点群可能变得非常困难。在许多情况下,即使是监督学习,模型也是基于点群的。一旦发生维度灾难,结果就会很差。

为了克服维度灾难,基本上又需要更多的训练数据,但这通常不可能。另一种方法是降维,通过特征选择,只选择重要的特征,可能会更好。或者通过主成分分析等降维技术,将数据集转换到更低维的空间,然后选择几个主成分,从而降低维度。这样也许能缓解维度灾难。

3. 局部最小值
现在,大多数算法都基于目标函数。这个目标函数可能有多个局部最小值。如果你使用梯度下降法,它可能会收敛到局部最小值。

例如,在图中,目标函数可能像这样。如果你从这里的初始值开始,它将收敛到这个点。如果你从这里开始,它将去到那里。但在开始时,我们不知道全局最小值在哪里,因此根据初始化的选择,输出的模型可能大不相同,有时会产生较大的误差。无论如何,这是不可重复的。

为了克服这个问题,我们可能会尝试退火或正则化。退火意味着尝试使目标函数更平滑,以便在找到一个最小值后,再次使用不那么平滑的函数,最终使用原始目标函数来找到全局最小值,但这也不容易做到。有时,我们可以非常仔细地访问数据集。一种技术是小批量处理,即在每次迭代中只访问部分数据点,而不是使用所有数据点。在这种情况下,由于数据点较少,目标函数的振荡较小,因此更有可能达到全局最小值。

4. 可解释性
尽管机器学习在某些应用领域已经取得了很大成功,但研究人员仍然不完全清楚某些算法是如何工作的。一个例子就是深度网络。如果我们不知道它是如何工作的,那么我们如何进行真正的改进?因此,我们必须理解并能够解释我们的算法,这样我们才能修改并克服缺陷,获得更好的算法。可解释性是一个难题。

在现代术语中,这被称为可解释的人工智能(XAI),这是当前机器学习领域的热门话题。

5. 数据规模
最后,我们面临学习所需数据规模的问题。我们仍然无法实现“一次性学习”。传统上,神经网络等方法的训练需要大量数据,并且通常需要非常多的迭代次数。但人类智能可以通过几个例子就理解一个对象。问题是,我们能否创造出只需少量样本就能训练的机器学习算法?如果你能解决这个问题,你很快就能成为亿万富翁。


总结 📝

在本节课中,我们一起学习了机器学习的基本概念。我们定义了机器学习,并区分了监督学习、无监督学习和强化学习。我们通过一个生动的例子理解了“群体智慧”的信念。我们详细探讨了监督学习如何通过带标签的数据寻找近似函数,以及无监督学习如何探索数据的隐藏结构(如聚类)。最后,我们深入分析了机器学习实践中面临的五大挑战:过拟合、维度灾难、局部最小值、可解释性以及数据规模问题,并简要提及了可能的解决方向。本章为后续学习具体的算法和应用奠定了基础。

060:第60讲 - 二元分类器与感知器算法 👨‍🏫

在本节课中,我们将要学习二元分类器的基本概念,并重点介绍一种经典的线性分类算法——感知器算法。我们将从神经元的工作原理出发,理解分类器的数学定义,并最终通过代码实现一个简单的感知器分类器。


概述 🧠

二元分类器是一种能够判断输入向量是否属于某个特定类别的函数。它通常指代两类分类问题,是一种线性分类器,其预测基于一个线性预测函数。常见的例子包括感知器、直线、逻辑回归和支持向量机。这些模型都受到了生物神经元的启发。

从神经元到分类器

生物神经元通过树突接收信号,细胞体整合这些信号。当累积的信号超过某个阈值时,神经元就会产生一个输出。这是神经元工作的简化描述。

人工神经元模拟了这一过程,用于处理和传输信号,其结构可以总结为下图:



二元分类器的定义

给定一个数据集 { (X_i, Y_i) },其中 X_id 维输入向量,Y_i 是标签,取值为 01。一个二元分类器的目标是找到一个超平面,将数据点分割成两个类别,如下图所示。

因此,二元分类器会寻找这样的超平面来将数据集一分为二。

标签选择 01 是为了简化,你也可以选择其他值,例如 -11。一个超平面可以用一个法向量 W 和一个偏移量 W_0 来定义,其方程可以写成以下形式:

公式: W · X + W_0 = 0

在二维空间中,这条线就是一个超平面,W 就是这条线的法向量。

为了学习 WW_0,我们需要定义一个成本函数来最小化。一种常见的公式是误差平方和。对于每个输入,我们计算一个称为净输入 Z 的值。在许多情况下,我们会使用某个激活函数 f,将激活后的值与给定标签进行比较,并尝试最小化这个目标函数。



感知器算法介绍

上一节我们介绍了二元分类器的通用概念,本节中我们来看看一个具体且历史悠久的算法——感知器算法。

感知器是一种用于监督学习的二元分类器。它始于1957年,后来被改进,甚至被实现为硬件。这意味着一旦输入进入,经过滤波器后立即产生输出。

感知器的定义

作为一个二元分类器,感知器的定义如下:X 是输入向量,W 是在训练过程中学习的权重向量。这里我们选择两个类别标签:1-1(当然也可以选择 01)。

净输入 Z 是输入值与权重值的加权和。在感知器中,我们使用阈值激活方案:一旦净输入 Z 大于或等于阈值 θ,则输出值为 1,否则为 -1

公式: Z = W · X + W_0
激活函数: y_hat = 1 if Z >= 0 else -1

获得激活值后,我们最终尝试通过选择合适的 W 来最小化误差,使得超平面能完全将数据集分成两部分。如果 W 选择准确,那么对于所有数据点 i,误差应为 0,这样误差总和最小化,训练就完成了。

我们将激活得到的值与真实标签进行比较,一旦出错,就通过迭代来更新权重。

简化形式

为简化,我们可以将阈值 θ 移到等式左边,并入 W_0。这样,我们定义 ZW · X + W_0,并使用 0 作为新的阈值。因此,激活规则变为:如果 Z >= 0,则激活值为 1,否则为 -1

在机器学习文献中,变量 W_0 通常被称为偏置,有时用字母 b 表示。方程 W · X + W_0 = 0 定义了超平面,W_0 决定了截距。



感知器学习规则

理解了感知器的基本原理后,接下来我们学习其核心:权重更新规则,即感知器如何从错误中学习。

罗森布拉特最初的感知器规则如下:

  1. 初始化权重为 0 或小的随机数。
  2. 对于每个训练样本 X_i,计算其预测的激活值(即预测的标签值)。
  3. 基于此,更新权重。

代码/公式:权重更新规则
W_new = W_old + η * (y_i - y_hat_i) * X_i
W_0_new = W_0_old + η * (y_i - y_hat_i)

其中:

  • η 称为学习率,是一个小的正数。
  • y_i 是真实的类别标签。
  • y_hat_i 是预测的类别标签。

因此,这个更新规则是:计算一个修正向量(真实标签与预测标签之差,乘以输入 X_i 和学习率 η),然后将其加到旧权重上。偏置 W_0 的更新类似,但不乘以 X_i

更新规则的理解

让我们做一个简单的思想实验:

  • 如果感知器正确预测了类别标签,那么 y_i - y_hat_i0,权重保持不变。
  • 如果预测错误,例如真实标签为 1 但预测为 -1,则 y_i - y_hat_i = 2。如果真实标签为 -1 但预测为 1,则结果为 -2。因此,整个修正项实际上是 ±2η * X_i。权重的变化会朝着正类或负类的方向推动超平面,试图将超平面定位到类别之间。


感知器的Python实现

学习了理论之后,本节我们将通过代码亲手实现一个感知器分类器,并观察其学习过程。

以下是一个感知器类的Python实现:

import numpy as np

class Perceptron:
    def __init__(self, eta=0.01, n_iter=10):
        self.eta = eta          # 学习率
        self.n_iter = n_iter    # 迭代次数(epoch数)
        self.w_ = None          # 权重向量
        self.w0_ = None         # 偏置

    def net_input(self, X):
        """计算净输入 Z = W·X + W0"""
        return np.dot(X, self.w_) + self.w0_

    def activate(self, X):
        """激活函数:返回预测标签 (1 或 0)"""
        return np.where(self.net_input(X) >= 0.0, 1, 0)

    def fit(self, X_train, y_train):
        """训练模型"""
        n_samples, n_features = X_train.shape
        # 初始化权重和偏置为0
        self.w_ = np.zeros(n_features)
        self.w0_ = 0.0

        for _ in range(self.n_iter):          # 遍历epoch
            for xi, yi in zip(X_train, y_train): # 遍历每个样本
                y_hat = self.activate(xi)     # 预测
                update = self.eta * (yi - y_hat) # 计算更新量
                self.w_ += update * xi        # 更新权重
                self.w0_ += update            # 更新偏置
        return self

    def predict(self, X_test):
        """对测试集进行预测"""
        return self.activate(X_test)

    def score(self, X_test, y_test):
        """计算分类准确率"""
        y_pred = self.predict(X_test)
        accuracy = np.mean(y_pred == y_test)
        return accuracy

代码解释:

  • __init__: 初始化学习率 eta 和迭代次数 n_iter
  • net_input: 计算线性组合 Z = W·X + W0
  • activate: 应用阶跃函数,Z >= 0 输出1,否则输出0。
  • fit: 核心训练函数。遍历所有epoch和每个训练样本,根据感知器学习规则更新权重 w_ 和偏置 w0_
  • predict: 对输入数据 X 进行预测。
  • score: 计算模型在测试集上的准确率。

在主程序中使用感知器

以下是使用鸢尾花数据集测试我们实现的感知器的主程序示例:

from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np

# 1. 加载数据
iris = datasets.load_iris()
X = iris.data
y = iris.target

# 2. 简化问题:只取前两个类别(Setosa和Versicolor),以及前两个特征(便于可视化)
X = X[y != 2]  # 只取类别0和1
y = y[y != 2]
X = X[:, [0, 2]]  # 只取第1列(花萼长度)和第3列(花瓣长度)两个特征

# 3. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)

# 4. 创建感知器实例并训练
ppn = Perceptron(eta=0.1, n_iter=10)
ppn.fit(X_train, y_train)

# 5. 评估模型
accuracy = ppn.score(X_test, y_test)
print(f‘感知器分类准确率: {accuracy:.2%}’)

# 6. 进行预测
y_pred = ppn.predict(X_test)
print(‘预测标签:’, y_pred)
print(‘真实标签:’, y_test)

运行此代码,你将看到感知器在简化后的鸢尾花数据集上的分类准确率。通过调整学习率 eta 和迭代次数 n_iter,你可以观察模型性能的变化。

可视化学习过程

为了更直观地理解感知器如何学习,我们可以可视化权重和超平面在训练过程中的变化。下图展示了一个二维两类数据集的训练过程:

感知器初始权重为 0。在训练初期,超平面(决策边界)可能无法正确分类。随着迭代进行,权重根据错误样本不断更新,超平面逐渐移动。最终,当所有训练样本都被正确分类时,算法收敛,超平面停止移动。

需要注意的是,感知器收敛到的超平面可能不是最优的(即最大间隔超平面),它依赖于初始权重和学习率的选择。但只要数据集是线性可分的,感知器保证在有限步内收敛到某个能将数据完全分开的超平面。


总结 📚

本节课中我们一起学习了二元分类器和感知器算法。

  1. 二元分类器:我们了解了二元分类器的定义,即寻找一个超平面将数据点分为两类。其数学形式为 W·X + W_0 = 0
  2. 感知器原理:感知器是一种简单的线性二元分类器,模仿了神经元的工作方式。它通过计算输入特征的加权和,并应用一个阈值函数来做出决策。
  3. 感知器学习规则:核心是错误驱动的权重更新规则 W_new = W_old + η * (y_i - y_hat_i) * X_i。当预测错误时,权重会朝着纠正错误的方向调整。
  4. 实现与实验:我们动手实现了一个感知器类,并在鸢尾花数据集上进行了测试和可视化,直观地观察了其学习过程和决策边界的演变。

感知器是神经网络的基础,虽然简单,但它引入了许多关键概念,如权重、激活函数和基于误差的学习,为后续更复杂的模型奠定了基础。

061:Adaline (自适应线性神经元) 🧠

在本节课中,我们将学习感知器的一种变体——自适应线性神经元(Adaline)。我们将探讨其与感知器的区别、其工作原理、成本函数以及如何通过梯度下降法进行优化。最后,我们将了解如何将二分类器扩展到多分类问题。


上一节我们介绍了感知器。本节中,我们来看看自适应线性神经元(Adaline)。

Adaline是感知器的一种变体。在Adaline中,激活函数是恒等函数,即输出等于净输入,且不进行阈值处理。我们直接使用净输入与真实标签之间的差异来更新权重。

因此,其成本函数是连续的。这使得我们可以使用更高级的数学优化技术,例如微积分。

下图展示了感知器与自适应线性神经元之间的区别。


在感知器中,净输入经过阈值处理后再与标签比较,其差值用于更新权重。而在Adaline中,净输入直接与标签比较,其差值用于更新权重。这里没有阈值处理,因此数值变化是连续的而非突变的,这使我们能够应用微积分。


以下是Adaline的学习算法。

给定数据集 {(x_i, y_i)},其中 i1n。我们需要学习权重 w 和偏置 b(有时记作 w_0)。

激活函数是恒等函数 φ(z) = z。因此,净输入 z_i 的计算公式为:
z_i = w^T * x_i + b

成本函数采用标准形式,其中 φ 是恒等函数。我们试图最小化平方误差和:
J(w, b) = 1/2 * Σ (y_i - φ(z_i))^2 = 1/2 * Σ (y_i - z_i)^2

这就是Adaline的优化问题。


为了优化这个成本函数,我们可以使用梯度下降法。这是最流行的方法之一。在该方法中,我们使用负梯度作为搜索方向。

权重和偏置的更新规则是负梯度乘以学习率 η
w := w - η * ∇_w J
b := b - η * ∇_b J

现在,让我们来推导梯度。

对于成本函数 J,关于权重 w_j 的偏导数可以如下计算:

具体推导如下:
∂J/∂w_j = Σ (y_i - z_i) * (-x_{ij})

类似地,关于偏置 b 的偏导数为:
∂J/∂b = Σ (y_i - z_i) * (-1)

取负梯度并乘以学习率后,我们得到更新规则:

Δw_j = η * Σ (y_i - z_i) * x_{ij}
Δb = η * Σ (y_i - z_i)

这与感知器更新规则相似,但存在关键区别:Adaline使用所有数据点的误差总和进行更新(批量梯度下降),并且更新量是连续值,而非感知器中基于阈值的离散值(0或1)。你可以轻松修改感知器代码来实现Adaline。在习题10.2中,你将看到选择学习率 η 等参数的策略。


在机器学习中,超参数是在学习过程开始前就设定好的参数。

例如,学习率 η、最大训练周期数(epoch)或最大迭代次数,以及误差容忍度,这些都是超参数。

选择学习率 η 时需要谨慎。Adaline的成本函数关于权重 w 和偏置 b 是二次的。


我们可以用下图表示:

如果 η 太小,下降速度会非常缓慢。如果 η 太大,更新步长可能过大,导致在最小值附近震荡甚至发散。因此,必须合理选择 η。此外,也有一些自适应的搜索方案可以自动设置学习率。


到目前为止,我们学习的是二分类器。那么,如果数据集包含多于两个类别呢?例如,下图中的数据集有三个类别:O, +, -

理想的分类边界可能不是直线。我们如何得到这样的分类边界呢?

我们可以使用所谓的 “一对多” 策略。

顾名思义,我们尝试构建多个二分类器。对于 K 个类别,我们构建 K 个分类器。第 k 个分类器将第 k 类作为正类,其余所有类别作为负类。

例如,对于 - vs 其余(O+),我们得到一个权重向量 w_-
对于 + vs 其余,得到 w_+
对于 O vs 其余,得到 w_O

在获得这 K 个分类器后,当一个新的数据点 x 到来时,我们分别用每个分类器计算其净输入 z_k = w_k^T * x + b_k

然后,我们选择能给出最大净输入值的那个分类器所对应的类别,作为预测结果。

例如,图中 x 点对于 w_- 分类器会得到较大的正值,而对于其他分类器可能得到负值或较小的值,因此它将被预测为 - 类。

这正好对应于图中理想的分类区域。

因此,一旦我们知道如何处理二分类问题,就可以应用“一对多”技术来解决多类别分类问题。


本节课中,我们一起学习了自适应线性神经元(Adaline)。我们了解了它与感知器的区别、其连续成本函数以及通过梯度下降进行的优化过程。最后,我们探讨了如何利用“一对多”策略将二分类器扩展到多分类场景。

062:逻辑回归

在本节课中,我们将学习三种流行的机器学习分类器:逻辑回归、支持向量机和K近邻算法。我们将从逻辑回归开始,首先介绍其核心的激活函数——逻辑Sigmoid函数。

逻辑Sigmoid函数

逻辑Sigmoid函数是逻辑回归模型的核心。它通常被用作激活函数,因为它具有良好的数学性质。

逻辑Sigmoid函数由以下公式定义:

公式:σ(x) = 1 / (1 + e^{-x})

这个函数是以下简单微分方程的解:

公式:σ‘(x) = σ(x) * (1 - σ(x))

我们可以通过求导规则轻松验证这一点。当 x 等于 0 时,函数值为 0.5。逻辑Sigmoid函数满足这个微分方程。

该函数具有对称性:

公式:σ(x) + σ(-x) = 1

如果对函数进行积分,可以得到 log(1 + e^x),这被称为“Softplus”函数。

激活函数对比

以下是几种常见的激活函数。

  • 整流线性单元:定义为 max(0, x)
  • 逻辑Sigmoid函数:如上所述,是一个平滑的S形曲线。
  • Softplus函数log(1 + e^x),是整流线性单元的一个平滑近似。

这三种函数在神经网络中经常被用作激活函数。

逻辑回归模型

上一节我们介绍了激活函数,本节中我们来看看逻辑回归模型本身。逻辑回归是一个概率模型,其目标是最大化参数 w 的似然函数。

逻辑回归的实现与线性回归非常相似。主要区别在于激活函数。

  • 在线性回归中,激活函数是恒等函数,即没有激活。
  • 在逻辑回归中,激活函数是Sigmoid函数。

这是逻辑回归与线性回归的唯一区别。

由于使用了Sigmoid函数,输出值被限制在0和1之间。因此,预测值可以被解释为样本属于类别1的概率。

对于输入 x,其属于类别1的概率为 P(y=1|x) = σ(w·x + b)。这里的 σ 就是Sigmoid函数。有一些理论支持将激活值解释为概率。

推导损失函数

现在,让我们尝试推导逻辑回归的损失函数。我们从似然函数开始。

似然函数定义如下:L(w, b) = ∏ [σ(w·x_i + b)]^{y_i} * [1 - σ(w·x_i + b)]^{1-y_i}

这里,标签 y_i 的幂次为0或1。这样组织后,对于每个数据点,该项都会被乘起来。如果我们能正确得到 wb,那么对于一个属于类别1的点,σ(...) 理想情况下应为1,而 1-σ(...) 为0,从而使似然值接近1。模型训练良好时,整个似然值将接近1。

直接最大化这个似然函数并不容易,因为它涉及幂运算和连乘。因此,我们转而最大化其对数似然。利用对数的性质,幂运算可以移出,乘法可以转化为加法。

我们不是最大化这个对数似然,而是最小化负对数似然,这实际上就是我们的目标函数(损失函数)。

公式:J(w, b) = -∑ [y_i * log(σ(w·x_i + b)) + (1 - y_i) * log(1 - σ(w·x_i + b))]

这就是逻辑回归的目标函数,其中我们使用逻辑Sigmoid函数作为激活函数。

梯度计算与参数更新

我们需要最小化这个目标函数。通过梯度下降法可以实现。为此,我们需要计算梯度,然后用学习率 η 缩放负梯度来更新参数。

计算逻辑回归中 Jw_j 的梯度。从目标函数出发,对 w_j 求偏导,应用链式法则,并利用已知的Sigmoid函数导数性质 σ‘(x) = σ(x)(1-σ(x)),我们可以得到结果。

以向量形式简化后,梯度为:

公式:∇_w J = ∑ (σ(w·x_i + b) - y_i) * x_i

类似地,对偏置 b 的导数为:

公式:∂J/∂b = ∑ (σ(w·x_i + b) - y_i)

梯度下降算法

以下是逻辑回归的梯度学习算法总结。

参数 wb 按照以下规则更新:

公式:w := w - η * ∇_w J
公式:b := b - η * ∂J/∂b

其中,η 是学习率,∇_w J∂J/∂b 是上面计算出的梯度。更新项是学习率乘以梯度。

这个形式与线性回归的算法完全相同。唯一的区别是,这里我们使用了Sigmoid激活函数将净输入映射到0和1之间,而线性回归使用的是恒等函数。因此,如果你已经有了线性回归的代码,可以很容易地修改它来实现逻辑回归。

本节课中,我们一起学习了逻辑回归的基础。我们从逻辑Sigmoid函数及其性质开始,然后介绍了逻辑回归作为一个概率模型如何工作。我们推导了其损失函数(负对数似然),并详细说明了如何通过梯度下降法计算梯度并更新模型参数。最后,我们指出了逻辑回归与线性回归在算法形式上的高度相似性。

063:支持向量机 📚

在本节课中,我们将要学习支持向量机。支持向量机可以看作是感知机或线性分类器的扩展,其核心思想是最大化分类边界与数据点之间的“间隔”,以获得更好的泛化能力。

概述

上一节我们介绍了逻辑回归。本节中,我们来看看支持向量机。支持向量机可以被视为感知机的扩展,它致力于最大化分类的“间隔”。

支持向量机的基本思想

观察下图,如果使用感知机算法,它可能会收敛于图中任意一条能将数据分开的直线。例如,如果选择了图中较细的直线,那么当一个新的数据点出现在虚线附近时,感知机可能会将其错误分类。这是因为感知机得到的分类边界“间隔”很小。

较小的间隔更容易导致过拟合,并在预测时产生较大的误差。相反,如果我们能找到一个分类超平面,使得两个不同类别之间的间隔最大化,那么模型对于未知数据的泛化误差就会更低。泛化误差低意味着模型对后续出现的新数据点预测错误率小。支持向量机的逻辑就是尝试最大化这个间隔。

如何最大化间隔

我们需要一些数学推导。对于一个分类线,我们定义其两侧的边界线。对于正类样本点,其净输入值(到分类线的距离)应大于等于1;对于负类样本点,其净输入值应小于等于-1。中间值为0的线就是分类线本身。

位于边界线上、净输入值恰好为1或-1的点被称为“支持向量”,正是这些点“支撑”着整个间隔。

通过数学推导,我们可以得到两个支持向量 x+x- 在法向量 w 方向上的投影距离。这个距离正是我们想要最大化的间隔。

具体而言,间隔 M 的计算公式为:
M = 2 / ||w||

因此,最大化间隔 M 等价于最小化权重向量 w 的范数 ||w||。于是,支持向量机的优化问题可以表述为:在满足所有数据点分类约束的条件下,最小化 ||w||

支持向量机的优化问题

支持向量机的目标函数如下所示。我们不是直接最大化间隔,而是在约束条件下最小化权重向量的范数。

其数学形式为:
最小化: (1/2) * ||w||^2
约束条件: y_i * (w^T * x_i + b) >= 1, 对于所有样本点 i

这里,y_i 是样本的标签(+1 或 -1)。这个约束条件确保了所有样本都被正确分类,并且位于间隔边界之外。

这个带约束的最小化问题可以通过拉格朗日乘子法求解。课程附录(A1, A2, A3)中详细解释了相关数学理论,如果你对背后的数学原理感兴趣,可以前往查阅。

线性支持向量机的特性

线性支持向量机的一个优点是,如果数据是线性可分的,那么该优化问题存在唯一的全局最优解。

这意味着我们能得到一个唯一的、间隔最大化的分类超平面。

处理非线性与噪声数据

理想的SVM应该产生一个能将所有向量完全分离成两个非重叠类别的超平面。然而,完美的分离并不总是可能的,或者可能导致模型过于复杂(例如支持向量过多),从而不能正确分类,尤其是在数据集存在噪声时。

为了克服线性SVM在处理非线性或噪声数据时的困难,我们可以考虑以下方法:

以下是两种主要的扩展技术:

  1. 软间隔分类:引入松弛变量,允许一些样本点落在间隔之内甚至被错误分类,从而在间隔最大化和分类错误之间取得平衡。
  2. 核技巧:通过一个核函数,将数据隐式地映射到更高维的特征空间,使得在原始空间中非线性可分的数据在高维空间中变得线性可分。这样既避免了显式计算高维特征向量的高昂成本,又提升了模型的分类能力。非线性SVM中常用的核函数包括多项式核、高斯径向基核等。

总结

本节课中我们一起学习了支持向量机。我们从感知机的局限性出发,引出了最大化间隔的核心思想。我们推导了间隔的公式,并将其转化为一个带约束的优化问题。我们了解到,对于线性可分数据,SVM能给出唯一的最优解。最后,我们简要探讨了处理非线性与噪声数据的两种关键技术:软间隔与核技巧。支持向量机是一个理论优美且在现实中非常流行的算法。

064:第64讲 - k最近邻(KNN) 👨‍🏫

在本节课中,我们将学习一种名为k最近邻(KNN)的经典机器学习算法。这是一种典型的“懒惰学习”算法,其核心思想简单而强大,在许多实际数据集上表现优异。

概述 📋

k最近邻分类器是“懒惰学习”的一个典型例子。它之所以被称为“懒惰”,并非因为其看似简单,而是因为它不会从训练数据中学习一个判别函数。该算法只是记忆整个训练数据集。对训练数据的分析会延迟到系统收到查询请求时才进行。

KNN算法的工作原理 ⚙️

上一节我们介绍了KNN的基本概念,本节中我们来看看它的具体工作步骤。KNN算法可以概括为以下几个步骤:

以下是实现KNN分类的核心步骤:

  1. 选择参数K和距离度量:确定要考虑的最近邻数量(K值)以及计算样本间距离的方法。
  2. 为新样本寻找K个最近邻:对于一个新的查询样本,计算它与训练集中所有样本的距离,并找出距离最近的K个点。
  3. 通过多数投票分配类别标签:查看这K个最近邻样本的类别,将出现次数最多的类别作为新样本的预测类别。

观看此视频以直观理解。

我们选择K=5。对于图中这个新的数据点(星号),我们找到了距离它最近的5个邻居点。其中有4个是三角形,1个是圆形。因此,系统将通过多数投票预测该点为一个三角形。这就是KNN的基本思想,相当简单。但在实际应用中,它通常非常有效。

KNN的优缺点 ⚖️

了解了算法流程后,我们来看看它的长处与短处。KNN有以下优点和缺点:

优点

  • 基于记忆:分类器可以立即适应新收集的训练数据。
  • 实现简单:概念和实现都非常直观。

缺点

  • 预测计算复杂度高:预测时的计算复杂度与训练样本数量呈线性增长(在最坏情况下)。
  • 难以丢弃训练样本:由于没有独立的训练阶段,每次获得新数据点都将其加入训练集,因此存储空间可能随着数据量增大而成为挑战。不过在实际中,这种情况并不频繁发生。

关键参数选择 🔧

一个更重要的问题是K值的选择。在许多情况下,选择合适的K值至关重要。通过选择正确的K,你可以在欠拟合过拟合之间取得平衡。

如果你使用Scikit-learn中的分类器,默认K值是5。你也可以选择不同的距离度量。默认是欧几里得距离(L2范数)。你也可以选择其他距离,例如曼哈顿距离(L1范数)或切比雪夫距离(L∞范数)。你可以根据你的数据集特性来选择不同的度量方式。

实际应用与总结 🏆

KNN算法相当简单。我个人测试了Scikit-learn中构建的各种算法,并选择了超过10个数据集。对于每个数据集,我应用了从Scikit-learn中选出的10个分类器。我发现,在大约三分之一的数据集上,KNN预测的准确率是最高的。这意味着,在整整三分之一的数据集中,KNN算法取得了最佳精度。

尽管这是一个简单的算法,但在许多情况下,它优于更先进、更复杂的算法。因此我相信,有时越简单越好。所以,KNN并非一个廉价的算法,它虽然简单,但在许多数据集中非常有用。

本节课中,我们一起学习了k最近邻(KNN)算法的核心思想、工作步骤、优缺点以及关键参数的选择。它是一种强大而直观的懒惰学习算法,是机器学习工具箱中的重要工具。

065:神经网络与MNIST数据集 🧠

在本节课中,我们将要学习神经网络的基本概念,并了解如何使用一个简单的神经网络来识别手写数字。我们将重点介绍MNIST数据集、随机梯度下降(SGD)方法,并通过一个具体的代码示例来展示如何实现和训练一个神经网络。


10.4:神经网络:感知机与基础概念

上一节我们介绍了机器学习的一些基础模型,本节中我们来看看神经网络,特别是其最基本的构建单元——人工神经元。

感知机(或称Adaline)是最简单的人工神经元模型。它通过加权求和证据来做出决策,其决策过程可以形象化地表示。

一个简单的人工神经元是对人类决策过程的另一种计算模型。然而,它们可以作为构建更复杂神经网络的基石。


如上图所示,这个复杂的神经网络就是由许多简单神经元构建而成的。因此,简单的人工神经元可以作为通用神经网络的构建模块。


10.5:MNIST数据集与手写数字识别 ✍️

我们将看到一个使用MNIST数据集来分类手写数字的简单网络。

手写数字识别问题包含两个部分:分割和分类。当数字被书写后,我们首先需要分割出单个数字,然后才能进行分类。在本节中,我们将专注于单个数字分类的识别组件。

我们有一个名为MNIST的公开数据集可用。

MNIST是NIST(美国国家标准与技术研究院)收集的两个数据集的子集。第一部分包含60000张训练图像(每个数字约6000张),第二部分是10000张测试图像。所有图像都是28x28像素的灰度图。


10.6:一个用于MNIST分类的简单神经网络 🕸️

让我们来看一个用于MNIST分类的简单神经网络。

输入是28x28的图像,即784个像素。因此,对于输入层,我们实际上有784个神经元。我们有一个隐藏层,假设有15个神经元。对于输出层,我们需要10个神经元来对应数字0到9。这就是一个简单的神经网络。


对于这个神经网络,你的问题是:神经网络如何做出正确的预测?以下是一种解释。

让我们专注于第一个输出神经元,即试图判断输入数字是否为0的那个神经元。


这个神经元试图检测“0”的图像,它通过加权汇总隐藏层神经元的证据来实现。


那么,这些隐藏神经元在做什么呢?

为了便于讨论,我们假设隐藏层的第一个神经元可能检测输入图像是否包含图像的某个特定部分(例如一个弧段)。它可以通过对相应像素赋予较大的权重,对其他像素赋予较小的权重来实现。

因此,第一个隐藏神经元试图检测图像的这一部分。

我们假设第二个神经元检测另一部分,第三个和第四个神经元也类似,分别检测图像的其他特定部分。

这四部分共同构成了一个“0”的图像。因此,如果所有这四个隐藏神经元都被激活(“点火”),那么汇总这些证据的输出层神经元就会得出结论:输入图像是一个“0”。

这是一种解释。实际上,上述是神经网络的一种常见解释,但它很难帮助我们和用户理解为什么神经网络可能被低效地设计。

我们稍后会看到这个网络的实际性能。


10.7:成本函数与梯度下降 📉

对于这个数据集,成本函数可以这样定义。设 y 为真实标签,a 为输出层的激活值。我们的目标是最小化成本函数 C

公式: C = 1/2 * Σ (y - a)^2

对于权重 w 和偏置 b,在每次迭代中,我们尝试使用梯度下降法进行更新。更新量是成本函数梯度的负值,再乘以学习率 ε

公式: Δw = -ε * ∇C_wΔb = -ε * ∇C_b

然而,如果我们试图计算这个量,即所有单个数据点梯度的平均值,当训练数据点数量非常大时,这将非常昂贵。

为了克服这个困难,我们可以尝试使用随机方法,即我们只使用一部分数据点来进行一次更新。通过这种方式,我们可以加速学习过程。


10.8:随机梯度下降(SGD) ⚡

这里我们介绍随机梯度下降法(SGD)。

其思想是通过计算一小部分随机选择的训练输入的梯度来估计总梯度。通过对这个小样本取平均,我们可以快速得到一个对真实梯度的良好估计,从而加速梯度下降。

在实践中,我们取一小批随机训练输入,称为“小批量”(mini-batch)。小批量梯度的平均值将是真实梯度的良好近似。对于MNIST手写数字分类,我们可以选择批量大小为10,即 M = 10

公式: ∇C ≈ 1/M * Σ ∇C_x,其中 x 属于一个小批量。

在实践中,随机梯度下降可以如下实现:
以下是实现步骤:

  1. 对数据集进行洗牌。
  2. 从开头开始,每次选取 M 个样本。
  3. 仅使用这 M 个样本计算出的近似梯度来更新权重和偏置。

每次更新计为一次迭代。一个“周期”(epoch)意味着你遍历了整个数据集一次。例如,如果有50000个数据点,每次迭代使用50个点,那么完成一个周期需要1000次迭代。



10.9:代码实现与性能分析 💻

以下是Michael Nielsen为MNIST数字数据集提供的实际实现。

如你所见,神经网络的主要部分在大约100行Python代码中实现。Python非常强大,可以灵活高效地实现神经网络。

现在,我们可以执行这个神经网络。首先,我们读取数据并导入网络。我们选择隐藏层有20个神经元。输入层是784(28x28)。输出层有10个神经元(对应10个数字)。我们只使用一个包含20个神经元的隐藏层,但你可以通过添加更多数字来创建多层网络。

我们定义周期数为30。这意味着算法将遍历整个数据集30次。在每次迭代中,我们使用大小为10的小批量。学习率 η 设为3.0。通过这段代码中实现的SGD,我们可以进行训练。

一旦执行,你会看到输出。从第0个周期开始,到完成第1个周期时,我们获得了大约90%的准确率。到第2个周期达到91%,然后92%,又回到91%,再次达到92%。这意味着存在一些振荡。随着这种振荡,它逐渐接近稳定。经过30个周期后,我们达到了大约94%的准确率,但即使访问数据集30次,这个过程也很慢。

对于这个简单的网络,我们得到了这个结果。如果使用带有默认设置的scikit-learn支持向量机分类器,可以达到非常相似的准确率(94%)。如果很好地调整SVM,可以达到约98.5%。如果我们使用深度学习,特别是经过良好调整的卷积神经网络,可以达到99.79%的准确率,即只有21张图像被错误分类。

这些是CNN错误分类的手写数字。你能理解这些数字吗?我相信你也很难辨认。机器(CNN)只错过了这些非常奇怪的书写方式。对于这些,即使是CNN也给出了正确答案。因此,其性能接近人类水平,甚至可以说更好,因为大多数人类也无法达到这样的准确率。


10.10:可解释性与未来方向 🔮

我相信对人工智能的解释与神经网络的设计密切相关。

这个神经网络收敛得相当慢,在30个周期之后。

你可以看到后来收敛得非常缓慢。

我们可能会考虑它收敛缓慢的原因。问题是,我们能否设计一种新的、可解释的神经网络形式,使其能在2或3个周期内达到类似的性能?这就是问题所在。

因为当前的神经网络不容易解释,也不容易解释为什么它收敛缓慢,为什么有时会振荡。但一旦你能解释并理解神经网络的工作原理,你就可以获得更快的收敛速度,同时也能提高准确率。

在机器学习课程中,我处理了一些与此问题相关的项目。

本节内容到此结束,谢谢。


总结 📚

本节课中我们一起学习了:

  1. 神经网络的基础:以感知机作为基本构建单元。
  2. MNIST数据集:一个用于手写数字识别的标准数据集。
  3. 一个简单的分类网络:了解了其层结构和一种可能的解释方式。
  4. 训练原理:引入了成本函数和梯度下降法。
  5. 随机梯度下降:一种用于加速大规模数据训练的关键优化技术。
  6. 实践与性能:通过一个约100行的Python代码示例实现了网络,并分析了其性能及与其他方法(SVM、CNN)的对比。
  7. 可解释性的重要性:讨论了当前神经网络的局限性以及对未来更高效、更可解释网络设计的展望。

066:Scikit-Learn - Python机器学习库 🧠

在本节课中,我们将要学习Scikit-Learn,一个功能强大且应用广泛的Python机器学习库。我们将了解它的核心功能、安装方法、使用流程,并通过一个简单的示例来演示如何用它构建和评估机器学习模型。


Scikit-Learn是一个Python机器学习库。它是最有用和最健壮的机器学习库之一。它提供了用于机器学习和统计建模的各种工具。

该库建立在NumPy、SciPy、Matplotlib和Pandas之上。

以下是使用Scikit-Learn所需或推荐的准备工作。你需要安装Python 3、NumPy、SciPy、Matplotlib和Pandas。此外,你也可以尝试使用Seaborn进行可视化。对于安装,例如在Ubuntu系统上,你可以尝试使用以下命令。如果你使用sudo安装,则可以将其安装到整个系统中。

pip install scikit-learn

机器学习主要包含五个步骤。

  1. 特征选择。
  2. 选择性能指标。
  3. 选择分类器和优化算法。
  4. 评估模型性能。
  5. 调整算法参数。

在实践中,每种算法都有其自身特点并基于某些假设。没有一种分类器能在所有场景下都表现最佳,这就是“没有免费午餐定理”。始终建议你至少比较几种不同学习算法的性能,以便为特定问题选择最佳模型。

以下是推荐使用Scikit-Learn的原因。它文档完善,应用广泛。该库涵盖了大多数机器学习任务、预处理模块、算法和实用工具。如果你想获得一个鲁棒的模型,那么给定一个数据集,你可以比较算法并构建集成模型。Scikit-Learn能够处理大多数数据问题。总的来说,Scikit-Learn简单、方便且足够强大。


上一节我们介绍了Scikit-Learn的基本概念和优势,本节中我们来看看一个简单的示例代码。

使用Scikit-Learn,你可以轻松加载数据集,库中内置了一些示例数据集。这里我们选择鸢尾花数据集。你可以获取一些额外信息并打印出来。同时,创建模型实例也非常容易。在导入逻辑回归和K近邻分类器后,我们创建了模型实例。lr代表逻辑回归,knn代表K近邻分类器。此外,划分训练集和测试集的操作也非常简单。train_test_split函数非常实用。

现在,对于数据集,我们获取特征向量和标签。代码将尝试运行100次来评估这些模型,并尝试获取平均准确率。在每次迭代中,我们将数据集随机划分为两部分:训练集和测试集,其中测试集占比为0.3(即30%用于测试,70%用于训练)。划分过程包含随机打乱,以确保随机分离。划分数据集后,使用训练集进行拟合(即训练),然后使用测试集进行评分(即获取准确率)。我们将每次迭代的准确率保存下来,最后计算并打印出平均准确率和标准差。

一旦有新样本到来,我们可以轻松进行预测。例如,这里创建了一个合成样本。预测过程相当简单,我们可以打印结果。并且,我们可以使用真实的目标名称(如‘setosa’)而不是标签(0, 1, 2)来输出。

以下是代码输出的部分信息。这些是数据集的特征名称。这些是目标名称。在标签中我们看到0,1,2,但原始的目标名称是这三个。这是逻辑回归在鸢尾花数据上的平均准确率。对于K近邻分类器,我们得到这个准确率。K近邻分类器的表现略好一些。

现在,对于新样本,逻辑回归和K近邻分类器对第二个样本的预测相同,但对第一个样本的预测不同。根据所使用的算法,你可能会得到不同的预测结果,因此准确率也可能不同。但你可以组合这些算法。在Scikit-Learn中,特别是通过集成方法,你可以方便且轻松地完成大多数机器学习任务。我们将在下一节中讨论集成方法。


本节课中我们一起学习了Scikit-Learn机器学习库。我们了解了它的核心依赖、安装方法以及构建机器学习模型的通用流程。通过一个鸢尾花数据集的分类示例,我们实践了如何使用Scikit-Learn加载数据、划分数据集、训练模型、评估性能并进行预测。我们还认识到不同算法可能产生不同结果,因此比较多种算法是选择最佳模型的关键。Scikit-Learn以其简洁的API和丰富的功能,成为了Python机器学习入门和实践的强大工具。

067:Scikit-Learn 比较与集成 🧠

在本节课中,我们将学习如何使用一个模板代码来轻松有效地进行机器学习编程。这个代码主要用于算法比较和集成学习。我们将逐步解析代码结构,并学习如何自定义分类器、与Scikit-Learn内置分类器进行比较,以及构建集成模型。

概述与数据准备

首先,我们需要导入必要的库并加载数据集。以下是代码的初始部分。

# 导入所需库
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split

# 加载数据集
data = datasets.load_iris()
X = data.data  # 特征矩阵
y = data.target  # 标签
info = data  # 额外信息

通过打印数据集,我们可以查看可用的信息。这里我们选择了鸢尾花(Iris)数据集。读取数据后,我们可以获得特征矩阵、标签和一些额外信息。

在设置部分,我们找出数据点的数量、特征维度和数据中的类别数量。这些是额外的超参数。我们的训练集比例设为0.7。根据这个比例,数据集将被分成两部分:70%用于训练,30%用于测试。运行次数设为50。对于每个分类器,我们将运行50次以获得平均准确率。这个参数控制执行流程:如果为0,则程序只执行自定义分类器;如果为1,则分类器将与Scikit-Learn分类器进行比较;如果为2(如本设置),则执行比较和集成。

核心执行函数

以下函数将用于每次执行,适用于基本分类器和数据集。

def run_classifier(clf, X, y, train_size=0.7, runs=50):
    accuracies = []
    total_time = 0
    for _ in range(runs):
        X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=train_size)
        start_time = time.time()
        clf.fit(X_train, y_train)
        accuracy = clf.score(X_test, y_test)
        accuracies.append(accuracy)
        total_time += time.time() - start_time
    avg_accuracy = np.mean(accuracies)
    std_accuracy = np.std(accuracies)
    elapsed_time = total_time / runs
    return avg_accuracy, std_accuracy, elapsed_time

该函数将对每个分类器运行50次。每次运行时,数据按0.7的比例分割为训练集和测试集。函数输出三个值:平均准确率、标准差和每次运行的平均耗时。

你可以定义自己的分类器,假设它保存在 my_classifier.py 文件中。然后你可以导入它。

from my_classifier import MyClassifier
my_clf = MyClassifier()

一旦导入,就可以使用你的分类器。通过执行 run_classifier 函数,我们可以获得平均准确率、标准差和耗时,并输出结果。

现在,如果参数大于或等于1,那么Scikit-Learn分类器将被加载并执行。

自定义分类器实现

让我们看看 my_classifier.py 的一些细节。

在这个文件中,我暂时选择了一个决策树分类器。一旦你创建了对象,它就会用决策树分类器进行初始化。

对于这个分类器,我们使用 fitpredictscore 作为其方法。


你可以用你自己的分类器替换这个决策树分类器。如果你的分类器要用于集成学习,它必须实现为一个子类。在这个实现中,MyClassifierBaseEstimatorClassifierMixin 这两个基类的子类。通过这种继承,你的分类器可以与其他Scikit-Learn分类器结合。此外,你必须在你的分类器中定义 fitpredictscore 这三个方法。这样,集成中的每个分类器都具有相同的结构,否则集成就不容易实现。因此,你必须首先将其实现为子类,并且必须包含这三个方法。

Scikit-Learn 分类器比较

现在让我们转到 cyclone_classifier.py。所需的数据 Xy 和多轮运行次数在第一部分定义。对于这四个参数,如果没有定义,我们会尝试使用默认值;如果给定了值,则使用给定值。我们导入库,并在这里从Scikit-Learn中选择10个分类器及其名称。这是我的选择,你可以任意选择分类器。

名称列表如下。

这是默认设置。如果数据名称未定义,我们尝试选择数据名称;如果运行次数未定义,则设为50,依此类推。

这部分用于比较。现在,对于名称列表中的每个分类器,我们将执行多轮运行,以获得平均准确率、标准差和耗时。之后,平均准确率被保存到这个数组中。在两个主要步骤中,我们计算所有分类器的平均准确率。对于每个分类器,我们打印出结果,然后再次基于此,我们打印出分类器准确率的最大值和平均值。这就是比较部分。

这里,一旦参数大于或等于2,下面的代码将被执行。现在,名称被修剪。在这里,我们尝试剔除一些分类器。执行后,你会看到结果从这里打印出来。有些分类器的准确率非常低,比如这个、这个和这个。因此,我们尝试从列表中剔除这五个分类器。基于P方法,我们从名称和分类器列表中尝试剔除这五个,你可以打印出剩余的分类器。

对于这些分类器,我们现在尝试构建用于集成分类的分类器列表。这些分类器必须至少是一个元组,包含一个字符串(名称)和分类器对象。因此,我们尝试为每个分类器收集这个元组。整体上,这是一个列表。如果你想在集成中添加你的分类器,你可以用这个字符串和分类器对象来添加。使用选定的分类器,你现在可以生成集成分类器,通过使用 VotingClassifier 方法。

这个方法是内置的。让我们回顾一下它们是什么。

好的,那个。它是内置的。

通过使用 VotingClassifier 方法,你可以生成集成分类器。

一旦生成,现在通过使用 run_classifier 函数,你可以轻松获得平均准确率、标准差和耗时。我们尝试打印出集成中使用的分类器,现在 list0。第一个名称被放入输出中,结果将随之打印出来。

结果分析与解读

让我们看看输出。这部分来自主程序的第一部分,有150个数据点,特征维度是4,有三个类别。这来自于自定义分类器。我们选择了那个分类器。对于自定义分类器,平均准确率是94.253%,几乎是最差的。实际上,这个比那个还差,但几乎是表现最差的。此外,标准差很大。通常,当我们多次运行分类器时,如果标准差很大,意味着对于数据集的不同划分,结果不一致,准确率波动很大。这意味着它不太准确,因此通常较差的分类器具有较大的标准差。

这些是每个分类器的比较部分,我们有平均准确率和标准差。这是所有分类器的平均准确率,这是最大准确率。最好的是多层感知器分类器。所以这里就是这个。现在,这部分来自集成部分,这五个分类器被使用。集成准确率是96.0%,标准差是2.53。这比最好的分类器差,但比平均准确率好。因此,简单的集成可能不会提高分类准确率。所以,当一两个分类器对你的数据集比其他分类器好得多时,你可以从最好的或最好的几个开始。它们是其他选项的变体。连同这些选择的分类器,你可以构建一个集成分类器。

让我们看看这个分类器当前的选项。

对于多层感知器分类器,我们选择了1个隐藏层,包含100个神经元。你可以改变这个设置。例如,(30, 30, 30)。那么你选择了3个隐藏层,每个有30个神经元。你可以通过设置不同的参数来创建不同的神经网络。你也可以选择不同的激活函数,当前是逻辑函数,但你可以选择整流线性单元或softplus,或双曲正切函数。Alpha是正则化系数,你可以选择不同的Alpha值,比如1、0.1或0.01。通过使用不同的Alpha,你可以得到不同的分类器。通过选择其他选项,你可以得到这个分类器的变体。如果你将它们组合起来得到一个集成分类器,那么你可能会获得更好的准确率。

只是简单的。

分类器的简单堆叠可能不会提高你的准确率。

总结

本节课中,我们一起学习了如何使用一个模板代码进行机器学习算法比较和集成。我们详细介绍了如何准备数据、实现自定义分类器、与Scikit-Learn内置分类器进行比较,以及构建和评估集成模型。关键点在于,自定义分类器需要继承特定的基类并实现核心方法,而简单的投票集成不一定能超越最优的单个分类器,但通过精心选择分类器变体进行集成,有可能提升模型性能。

posted @ 2026-03-26 13:08  布客飞龙IV  阅读(8)  评论(0)    收藏  举报