diff --git a/.vitepress/config.js b/.vitepress/config.js index cb7de6c..7b68de9 100644 --- a/.vitepress/config.js +++ b/.vitepress/config.js @@ -305,6 +305,15 @@ export default defineConfig({ { text: '4.3.2.2项目:扫雷,骑士与流氓问题', link: '/4.人工智能/4.3.2.2项目:扫雷,骑士与流氓问题' }, ] }, + { + text: '4.3.3不确定性问题', + collapsed: true, + items: [ + { text: '4.3.3不确定性问题', link: '/4.人工智能/4.3.3不确定性问题' }, + { text: '4.3.3.1程序示例', link: '/4.人工智能/4.3.3.1程序示例' }, + { text: '4.3.3.2项目:遗传', link: '/4.人工智能/4.3.3.2项目:遗传' }, + ] + }, ] }, { text: '4.4FAQ:常见问题', link: '/4.人工智能/4.4FAQ:常见问题' }, @@ -558,6 +567,8 @@ export default defineConfig({ items: [ { text: '5.富有生命的嵌入式', link: '/5.富有生命的嵌入式/5.富有生命的嵌入式' }, { text: '5.1嵌入式是什么?可以吃吗?', link: '/5.富有生命的嵌入式/5.1嵌入式是什么?可以吃吗?' }, + { text: '5.2New meaning of C', link: '/5.富有生命的嵌入式/5.2New meaning of C' }, + { text: '5.3还玩裸机?上操作系统!', link: '/5.富有生命的嵌入式/5.3还玩裸机?上操作系统!' }, ] }, { diff --git a/4.人工智能/4.3.3.1程序示例.md b/4.人工智能/4.3.3.1程序示例.md new file mode 100644 index 0000000..8e5e85f --- /dev/null +++ b/4.人工智能/4.3.3.1程序示例.md @@ -0,0 +1,53 @@ +# 程序示例 +::: tip +阅读程序,然后“玩一玩”程序! + +完成习题 +::: + +本节代码不做额外梳理,[不确定性问题](./4.3.3%E4%B8%8D%E7%A1%AE%E5%AE%9A%E6%80%A7%E9%97%AE%E9%A2%98.md) 中已有解释。 + +## Quiz + +1. 考虑一副标准的 52 张牌,在四种花色(梅花、方块、红心、黑桃)中各有 13 种牌值(A、K、Q、J 和 2-10)。如果随机抽出一张牌,它是黑桃或 2 的概率是多少? + 1. About 0.019 + 2. About 0.077 + 3. About 0.17 + 4. About 0.25 + 5. About 0.308 + 6. About 0.327 + 7. About 0.5 + 8. None of the above +2. 想象一下,抛出两枚硬币,每枚硬币都有正面和反面,50% 的时间出现正面,50% 的时间出现反面。抛出这两枚硬币后,其中一枚是正面,另一枚是反面的概率是多少? + 1. 0 + 2. 0.125 + 3. 0.25 + 4. 0.375 + 5. 0.5 + 6. 0.625 + 7. 0.75 + 8. 0.875 + 9. 1 +3. 回答关于贝叶斯网络的问题,问题如下: + +  + + 以下哪句话是真的? + + 1. 假设我们知道有轨道维护,那么是否有雨并不影响列车准时到达的概率。 + 2. 假设我们知道有雨,那么是否有轨道维修并不影响列车准时到达的概率。 + 3. 假设我们知道火车是准时的,是否有雨会影响到赴约的概率。 + 4. 假设我们知道火车是准时的,那么是否有轨道维修并不影响赴约的概率。 + 5. 假设我们知道有轨道维护,那么是否有雨并不影响参加约会的概率。 +4. 两家工厂--A 厂和 B 厂--设计用于手机的电池。A 厂生产 60% 的电池,B 厂生产另外 40%。A 厂 2% 的电池有缺陷,B 厂 4% 的电池有缺陷。一个电池既由 A 厂生产又有缺陷的概率是多少? + 1. 0.008 + 2. 0.012 + 3. 0.024 + 4. 0.028 + 5. 0.02 + 6. 0.06 + 7. 0.12 + 8. 0.2 + 9. 0.429 + 10. 0.6 + 11. None of the above diff --git a/4.人工智能/4.3.3.2项目:遗传.md b/4.人工智能/4.3.3.2项目:遗传.md new file mode 100644 index 0000000..352424e --- /dev/null +++ b/4.人工智能/4.3.3.2项目:遗传.md @@ -0,0 +1,93 @@ +# 项目:遗传 + +::: tip +我们为你提供了一个简单有趣的项目,帮助你进行知识巩固,请认真阅读文档内容。 + +如果你卡住了,请记得回来阅读文档,或请求身边人的帮助。 +::: +## 背景 + +- GJB2 基因的突变版本是导致新生儿听力障碍的主要原因之一。每个人都携带两个版本的基因,因此每个人都有可能拥有 0、1 或 2 个听力障碍版本的 GJB2 基因。不过,除非一个人接受基因测试,否则要知道一个人拥有多少个变异的 GJB2 基因并不那么容易。这是一些 "隐藏状态":具有我们可以观察到的影响(听力损伤)的信息,但我们不一定直接知道。毕竟,有些人可能有 1 或 2 个突变的 GJB2 基因,但没有表现出听力障碍,而其他人可能没有突变的 GJB2 基因,但仍然表现出听力障碍。 +- 每个孩子都会从他们的父母那里继承一个 GJB2 基因。如果父母有两个变异基因,那么他们会将变异基因传给孩子;如果父母没有变异基因,那么他们不会将变异基因传给孩子;如果父母有一个变异基因,那么该基因传给孩子的概率为 0.5。不过,在基因被传递后,它有一定的概率发生额外的突变:从导致听力障碍的基因版本转变为不导致听力障碍的版本,或者反过来。 +- 我们可以尝试通过对所有相关变量形成一个贝叶斯网络来模拟所有这些关系,就像下面这个网络一样,它考虑了一个由两个父母和一个孩子组成的家庭。 + + + +- 家庭中的每个人都有一个 `Gene` 随机变量,代表一个人有多少个特定基因(例如,GJB2 的听力障碍版本):一个 0、1 或 2 的值。家族中的每个人也有一个 `Trait` 随机变量,它是 `yes` 或 `no`,取决于该人是否表达基于该基因的性状(例如,听力障碍)。从每个人的 `Gene` 变量到他们的 `Trait` 变量之间有一个箭头,以编码一个人的基因影响他们具有特定性状的概率的想法。同时,也有一个箭头从母亲和父亲的 `Gene` 随机变量到他们孩子的 `Gene` 随机变量:孩子的基因取决于他们父母的基因。 +- 你在这个项目中的任务是使用这个模型对人群进行推断。给出人们的信息,他们的父母是谁,以及他们是否具有由特定基因引起的特定可观察特征(如听力损失),你的人工智能将推断出每个人的基因的概率分布,以及任何一个人是否会表现出有关特征的概率分布。 + +## 理解 + +- 打开 `data/family0.csv`,看看数据目录中的一个样本数据集(你可以在文本编辑器中打开,或者在 Google Sheets、Excel 或 Apple Numbers 等电子表格应用程序中打开)。注意,第一行定义了这个 CSV 文件的列:`name`, `mother`, `father`, 和 `trait`。下一行表明 Harry 的母亲是 Lily,父亲是 James,而 `Trait` 的空单元格意味着我们不知道 Harry 是否有这种性状。同时,James 在我们的数据集中没有列出父母(如母亲和父亲的空单元格所示),但确实表现出了性状(如 `Trait` 单元格中的 1 所示)。另一方面,Lily 在数据集中也没有列出父母,但没有表现出这种性状(如 `Trait` 单元格中的 0 表示)。 +- 打开 `heredity.py`,首先看一下 `PROBS` 的定义。`PROBS` 是一个包含若干常数的字典,代表各种不同事件的概率。所有这些事件都与一个人拥有多少个特定的突变基因,以及一个人是否基于该基因表现出特定的性状有关。这里的数据松散地基于 GJB2 基因的听力障碍版本和听力障碍性状的概率,但通过改变这些值,你也可以用你的人工智能来推断其他的基因和性状! +- 首先,`PROBS["gene"]` 代表了该基因的无条件概率分布(即如果我们对该人的父母一无所知的概率)。根据分布代码中的数据,在人群中,有 1% 的机会拥有该基因的 2 个副本,3% 的机会拥有该基因的 1 个副本,96% 的机会拥有该基因的零副本。 +- 接下来,`PROBS["trait"]` 表示一个人表现出某种性状(如听力障碍)的条件概率。这实际上是三个不同的概率分布:基因的每个可能值都有一个。因此,`PROBS["trait"][2]` 是一个人在有两个突变基因的情况下具有该特征的概率分布:在这种情况下,他们有 65% 的机会表现出该特征,而有 35% 的机会不表现出该特征。同时,如果一个人有 0 个变异基因,他们有 1% 的机会表现出该性状,99% 的机会不表现出该性状。 +- 最后,`PROBS["mutation"]` 是一个基因从作为相关基因突变为不是该基因的概率,反之亦然。例如,如果一个母亲有两个变异基因,并因此将其中一个传给她的孩子,就有 1% 的机会突变为不再是变异基因。相反,如果一个母亲没有任何变异基因,因此没有把变异基因传给她的孩子,但仍有 1% 的机会突变为变异基因。因此,即使父母双方都没有变异基因,他们的孩子也可能有 1 个甚至 2 个变异基因。 +- 最终,你计算的概率将以 `PROBS` 中的这些数值为基础。 +- 现在,看一下 `main` 函数。该函数首先将数据从一个文件加载到一个字典 `people` 中。`people` 将每个人的名字映射到另一个包含他们信息的字典中:包括他们的名字,他们的母亲(如果数据集中有一个母亲),他们的父亲(如果数据集中有一个父亲),以及他们是否被观察到有相关的特征(如果有则为 `True`,没有则为 `False`,如果我们不知道则为 `None`)。 +- 接下来,`main` 中定义了一个字典 `probabilities`,所有的概率最初都设置为 0。这就是你的项目最终要计算的内容:对于每个人,你的人工智能将计算他们有多少个变异基因的概率分布,以及他们是否具有该性状。例如,`probabilities["Harry"]["gene"][1]` 将是 Harry 有 1 个变异基因的概率,而 `probabilities["Lily"]["trait"][False]` 将是 Lily 没有表现出该性状的概率。 +- 如果不熟悉的话,这个 `probabilities` 字典是用 [python 字典](https://www.python.org/dev/peps/pep-0274/)创建的,在这种情况下,它为我们的 `people` 中的每个 `person` 创建一个键/值对。 +- 最终,我们希望根据一些证据来计算这些概率:鉴于我们知道某些人有或没有这种特征,我们想确定这些概率。你在这个项目中的任务是实现三个函数来做到这一点: `joint_probability` 计算一个联合概率,`update` 将新计算的联合概率添加到现有的概率分布中,然后 `normalize` 以确保所有概率分布最后和为 1。 + +## 明确 + +- 完成 `joint_probability`、`update` 和 `normalize` 的实现。 +- `joint_probability` 函数应该接受一个 `people` 的字典作为输入,以及关于谁拥有多少个变异基因,以及谁表现出该特征的数据。该函数应该返回所有这些事件发生的联合概率。 + + - 该函数接受四个数值作为输入:`people`, `one_gene`, `two_genes`, 和 `have_trait`。 + + - `people` 是一个在 "理解"一节中描述的人的字典。键代表名字,值是包含 `mother` 和 `father` 键的字典。你可以假设 `mother` 和 `father` 都是空白的(数据集中没有父母的信息),或者 `mother` 和 `father` 都会指代 `people` 字典中的其他人物。 + - `one_gene` 是一个集合,我们想计算所有集合元素有一个变异基因的概率。 + - `two_genes` 是一个集合,我们想计算所有集合元素有两个变异基因的概率。 + - `have_trait` 是一个集合,我们想计算所有集合元素拥有该性状的概率。 + - 对于不在 `one_gene` 或 t `wo_genes` 中的人,我们想计算他们没有变异基因的概率;对于不在 `have_trait` 中的人,我们想计算他们没有该性状的概率。 + + - 例如,如果这个家庭由 Harry、James 和 Lily 组成,那么在 `one_gene = {"Harry"}`、`two_genes = {"James"}` 和 `trait = {"Harry"、"James"}` 的情况下调用这个函数,应该计算出 Lily 没有变异基因、Harry 拥有一个变异基因、James 拥有两个变异基因、Harry 表现出该性状、James 表现出该性状和 Lily 没有表现出该性状的联合概率。 + - 对于数据集中没有列出父母的人,使用概率分布 `PROBS["gene"]` 来确定他们有特定数量基因的概率。 + - 对于数据集中有父母的人来说,每个父母都会把他们的两个基因中的一个随机地传给他们的孩子,而且有一个 `PROBS["mutation"]` 的机会,即它会发生突变(从变异基因变成正常基因,或者相反)。 + - 使用概率分布 `PROBS["trait"]` 来计算一个人具有或不具有形状的概率。 +- `update` 函数将一个新的联合分布概率添加到 `probabilities` 中的现有概率分布中。 + + - 该函数接受五个值作为输入:`probabilities`, `one_gene`, `two_genes`, `have_trait`, 和 `p`。 + + - `probabilities` 是一个在 "理解 "部分提到的字典。每个人都被映射到一个 `"gene"` 分布和一个 `"trait"` 分布。 + - `one_gene` 是一个集合,我们想计算所有集合元素有一个变异基因的概率。 + - `two_genes` 是一个集合,我们想计算所有集合元素有两个变异基因的概率。 + - `have_trait` 是一个集合,我们想计算所有集合元素拥有该性状的概率。 + - `p` 是联合分布的概率。 + - 对于概率中的每个人,该函数应该更新 `probabilities[person]["gene"]` 分布和 `probabilities[person]["trait"]` 分布,在每个分布中的适当数值上加上 `p`。所有其他数值应保持不变。 + - 例如,如果"Harry"同时出现在 `two_genes` 和 `have_trait` 中,那么 `p` 将被添加到 `probabilities["Harry"]["gene"][2]` 和 `probabilities["Harry"]["trait"][True]`。 + - 该函数不应返回任何值:它只需要更新 `probabilities` 字典。 +- `normalize` 函数更新 `probabilities` 字典,使每个概率分布被归一化(即和为 1,相对比例相同)。 + + - 该函数接受一个单一的值:`probabilities`。 + + - `probabilities` 是一个在"理解"部分提到的字典。每个人都被映射到一个 `"gene"` 分布和一个 `"trait"` 分布。 + - 对于 `probabilities` 中每个人的两个分布,这个函数应该将该分布归一化,使分布中的数值之和为 1,分布中的相对数值是相同的。 + - 例如,如果 `probabilities["Harry"]["trait"][True]` 等于 `0.1`,概率 `probabilities["Harry"]["trait"][False]` 等于 `0.3`,那么你的函数应该将前一个值更新为 `0.25`,后一个值更新为 `0.75`: 现在数字之和为 1,而且后一个值仍然比前一个值大三倍。 + - 该函数不应返回任何值:它只需要更新 `probabilities` 字典。 +- 除了规范中要求你实现的三个函数外,你不应该修改 `heredity.py` 中的任何其他东西,尽管你可以编写额外的函数和/或导入其他 Python 标准库模块。如果熟悉的话,你也可以导入 `numpy` 或 `pandas`,但是你不应该使用任何其他第三方 Python 模块。 + +## 一个联合概率例子 + +- 为了帮助你思考如何计算联合概率,我们在下面附上一个例子。 +- 请考虑以下 `people` 的值: + +```python +{ + 'Harry': {'name': 'Harry', 'mother': 'Lily', 'father': 'James', 'trait': None}, + 'James': {'name': 'James', 'mother': None, 'father': None, 'trait': True}, + 'Lily': {'name': 'Lily', 'mother': None, 'father': None, 'trait': False} +} +``` + +- 这里我们将展示 `joint_probability(people, {"Harry"}, {"James"}, {"James"})` 的计算。根据参数,`one_gene` 是 `{"Harry"}`,`two_genes` 是 `{"James"}`,而 `has_trait` 是 `{"James"}`。因此,这代表了以下的概率:Lily 没有变异基因,不具有该性状;Harry 有一个变异基因,不具有该性状;James 有 2 个变异基因,具有该性状。 +- 我们从 Lily 开始(我们考虑人的顺序并不重要,只要我们把正确的数值乘在一起,因为乘法是可交换的)。Lily 没有变异基因,概率为 `0.96`(这就是 `PROBS["gene"][0]`)。鉴于她没有变异基因,她没有这个性状的概率为 `0.99`(这是 `PROBS["trait"][0][False]`)。因此,她没有变异基因且没有该性状的概率是 `0.96*0.99=0.9504`。 +- 接下来,我们考虑 James。James 有 2 个变异基因,概率为 `0.01`(这是 `PROBS["gene"][2]`)。鉴于他有 2 个变异基因,他确实具有该性状的概率为 `0.65`。因此,他有 2 个变异基因并且他确实具有该性状的概率是 `0.01*0.65=0.0065`。 +- 最后,我们考虑 Harry。Harry 有 1 个变异基因的概率是多少?有两种情况可以发生。要么他从母亲那里得到这个基因,而不是从父亲那里,要么他从父亲那里得到这个基因,而不是从母亲那里。他的母亲 Lily 没有变异基因,所以 Harry 会以 `0.01` 的概率从他母亲那里得到这个基因(这是 `PROBS["mutation"]`),因为从他母亲那里得到这个基因的唯一途径是基因突变;相反,Harry 不会从他母亲那里得到这个基因,概率是 `0.99`。他的父亲 James 有 2 个变异基因,所以 Harry 会以 `0.99` 的概率从他父亲那里得到这个基因(这是 `1-PROBS["mutation"]`),但会以 `0.01` 的概率从他母亲那里得到这个基因(突变的概率)。这两种情况加在一起可以得到 `0.99*0.99+0.01*0.01=0.9802`,即 Harry 有 1 个变异基因的概率。 +- 考虑到 Harry 有 1 个变异基因,他没有该性状的概率是 `0.44`(这是 `PROBS["trait"][1][false]`)。因此,哈利有 1 个变异基因而没有该性状的概率是 `0.9802 * 0.44 = 0.431288`。 +- 因此,整个联合概率是三个人中每个人的所有这些数值相乘的结果: `0.9504 * 0.0065 * 0.431288 = 0.0026643247488`。 + +## 提示 + +- 回顾一下,要计算多个事件的联合概率,你可以通过将这些概率相乘来实现。但请记住,对于任何孩子来说,他们拥有一定数量的基因的概率是以他们的父母拥有什么基因为条件的。 diff --git a/4.人工智能/4.3.3不确定性问题.md b/4.人工智能/4.3.3不确定性问题.md new file mode 100644 index 0000000..bbfb314 --- /dev/null +++ b/4.人工智能/4.3.3不确定性问题.md @@ -0,0 +1,1049 @@ +# 不确定性问题 + +- 上一讲中,我们讨论了人工智能如何表示和推导新知识。然而,在现实中,人工智能往往对世界只有部分了解,这给不确定性留下了空间。尽管如此,我们还是希望我们的人工智能在这些情况下做出尽可能好的决定。例如,在预测天气时,人工智能掌握了今天的天气信息,但无法 100% 准确地预测明天的天气。尽管如此,我们可以做得比偶然更好,今天的讲座是关于我们如何创造人工智能,在有限的信息和不确定性的情况下做出最佳决策。 + +## 概率(Probability) + +- 不确定性可以表示为多个事件以及每一个事件发生的可能性或概率。 + +### 概率世界 + +- 每一种可能的情况都可以被视为一个世界,由小写的希腊字母$ω$表示。例如,掷骰子可以产生六个可能的世界:骰子出现 1 的世界,骰子出现 2 的世界,依此类推。为了表示某个世界的概率,我们写$P(ω)$。 + +### 概率公理 + +- $0
$。为了便于解释,这些值有一个固定的顺序(在我们的情况下,准时、延迟、取消)。
+
+### 独立性(Independence)
+
+- 独立性是指一个事件的发生不会影响另一个事件发生的概率。例如,当掷两个骰子时,每个骰子的结果与另一个骰子的结果是独立的。用第一个骰子掷出 4 不会影响我们掷出的第二个骰子的值。这与依赖事件相反,比如早上的云和下午的雨。如果早上多云,下午更有可能下雨,所以这些事件是有依赖性的。
+- 独立性可以用数学定义:事件$a$和$b$是独立的,当且仅当$a$并且$b$的概率等于$a$的概率乘以$b$的概率:$P(a∧b)=P(a)P(b)$。
+
+## 贝叶斯规则(Bayes’ Rule)
+
+- 贝叶斯规则在概率论中常用来计算条件概率。换句话说,贝叶斯规则说,给定
+ $$
+ b$$条件下
+ $$
+
+ a
+ $$
+ 的概率等于给定
+ $$
+
+ a
+ $$
+ 的条件下
+ $$
+
+ b
+ $$
+ 概率,乘以
+ $$
+
+ b
+ $$
+ 的概率除以
+ $$
+
+ a$$ 的概率。
+- $$
+ P(b|a)=\frac{P(a|b)P(b)}{P(a)}
+ $$
+- 例如,如果早上有云,我们想计算下午下雨的概率,或者
+ $$
+ P(雨|云)
+ $$
+
+ 。我们从以下信息开始:
+
+ - 80% 的雨天下午开始于多云的早晨,或
+ $$
+ P(云|雨)
+ $$
+
+ 。
+ - 40% 的日子早晨多云,或
+ $$
+ P(云)
+ $$
+
+ 。
+ - 10% 的日子有下雨的下午,或
+ $$
+ P(雨)
+ $$
+
+ 。
+- 应用贝叶斯规则,我们计算
+ $$
+ \frac{0.8*0.1}{0.4}=0.2
+ $$
+
+ 。也就是说,考虑到早上多云,下午下雨的可能性是 20%。
+- 除了
+ $$
+ P(a)
+ $$
+
+ 和
+ $$
+ P(b)
+ $$
+
+ 之外,知道
+ $$
+ P(a|b)
+ $$
+
+ 还允许我们计算
+ $$
+ P(b|a)
+ $$
+
+ 。这是有帮助的,因为知道给定未知原因的可见效应的条件概率
+ $$
+ P(可见效应|未知原因)
+ $$
+
+ ,可以让我们计算给定可见效应的未知原因的概率
+ $$
+ P(未知原因|可见效应)
+ $$
+
+ 。例如,我们可以通过医学试验来学习
+ $$
+ P(医学测试结果|疾病)
+ $$
+
+ ,在医学试验中,我们对患有该疾病的人进行测试,并观察测试结果发生的频率。知道了这一点,我们就可以计算出
+ $$
+ P(疾病|医学检测结果)
+ $$
+
+ ,这是有价值的诊断信息。
+
+## 联合概率(Joint Probability)
+
+- 联合概率是指多个事件全部发生的可能性。
+- 让我们考虑下面的例子,关于早上有云,下午有雨的概率。
+
+| C=云 | C=
+$$
+\lnot
+$$
+
+| 云 |
+| --- | --- |
+| 0.4 | 0.6 |
+
+| R=雨 | R=
+$$
+\lnot
+$$
+
+| 雨 |
+| --- | --- |
+| 0.1 | 0.9 |
+
+- 从这些数据来看,我们无法判断早上的云是否与下午下雨的可能性有关。为了做到这一点,我们需要看看这两个变量所有可能结果的联合概率。我们可以将其表示在下表中:
+
+| | R=雨 | R=
+$$
+\lnot
+$$
+
+| 雨 |
+| ---- | ---- | ---- |
+| C=云 | 0.08 | 0.32 |
+| C= |
+$$
+\lnot
+$$
+
+云 | 0.02 | 0.58 |
+
+- 现在我们可以知道有关这些事件同时发生的信息了。例如,我们知道某一天早上有云,下午有雨的概率是 0.08。早上没有云,下午没有雨的概率是 0.58。
+- 使用联合概率,我们可以推导出条件概率。例如,如果我们感兴趣的是在下午下雨的情况下,早上云层的概率分布。
+ $$
+ P(C|雨)=\frac{P(C,雨)}{P(雨)}
+ $$
+
+ (旁注:在概率上,逗号和
+ $$
+ ∧
+ $$
+
+ 可以互换使用。因此,
+ $$
+ P(C,雨)=P(C\land 雨)
+ $$
+
+ 。换句话说,我们将降雨和云层的联合概率除以降雨的概率。
+- 在最后一个方程中,可以将
+ $$
+ P(雨)
+ $$
+
+ 视为
+ $$
+ P(C,雨)
+ $$
+
+ 乘以的某个常数
+ $$
+ \alpha=\frac{1}{P(雨)}
+ $$
+
+ 。因此,我们可以重写
+ $$
+ P(C|雨)=\frac{P(C,雨)}{P(雨)}=αP(C,雨)
+ $$
+
+ ,或
+ $$
+ α<0.08,0.02>=<0.8,0.2>
+ $$
+
+ 。考虑到下午有雨,将
+ $$
+ α
+ $$
+
+ 分解后,我们可以得到 C 的可能值的概率比例。也就是说,如果下午有雨,那么早上有云和早上没有云的概率的比例是
+ $$
+ 0.08:0.02
+ $$
+
+ 。请注意,0.08 和 0.02 的总和不等于 1;然而,由于这是随机变量 C 的概率分布,我们知道它们应该加起来为 1。因此,我们需要通过计算
+ $$
+ α
+ $$
+
+ 来归一化这些值,使得
+ $$
+ α0.08+α0.02=1
+ $$
+
+ 。最后,我们可以说
+ $$
+ P(C|雨)=<0.8,0.2>
+ $$
+
+ 。
+
+## 概率规则(Probability Rules)
+
+- 否定(Negation):
+ $$
+ P(\lnot a)=1-P(a)
+ $$
+
+ 。这源于这样一个事实,即所有可能世界的概率之和为 1,互补事件
+ $$
+ \lnot a
+ $$
+
+ 和 $$a$$ 包括所有可能世界。
+- 包含-排除 Inclusion-Exclusion:
+ $$
+ P(a\lor b)=P(a)+P(b)-P(a\land b)
+ $$
+
+ 。这可以用以下方式解释:
+ $$
+ a$$或
+ $$
+
+ b
+ $$
+ 为真的世界等于
+ $$
+
+ a
+ $$
+ 为真的所有世界,加上
+ $$
+
+ b
+ $$
+ 为真的所有世界。然而,在这种情况下,有些世界被计算两次(a和
+ $$
+
+ b
+ $$
+ 都为真的世界)。为了消除这种重叠,我们将
+ $$
+
+ a
+ $$
+ 和
+ $$
+
+ b$$ 都为真的世界减去一次(因为它们被计算了两次)。
+
+> 下面是一个例子,可以说明这一点。假设我 80% 的时间吃冰淇淋,70% 的时间吃饼干。如果我们计算今天我吃冰淇淋或饼干的概率,不减去
+> $$
+> P(冰淇淋∧饼干)
+> $$
+>
+> ,我们错误地得出 0.7+0.8=1.5。这与概率在 0 和 1 之间的公理相矛盾。为了纠正我同时吃冰淇淋和饼干的天数计算两次的错误,我们需要减去
+> $$
+> P(冰淇淋∧饼干)
+> $$
+>
+> 一次。
+
+- 边缘化(Marginalization):
+ $$
+ P(a)=P(a,b)+P(a,\lnot b)
+ $$
+
+ 。这里的观点是,
+ $$
+ b$$和
+ $$
+
+ \lnot b
+ $$
+ 是独立的概率。也就是说,
+ $$
+
+ b
+ $$
+ 和
+ $$
+
+ \lnot b
+ $$
+ 同时发生的概率为0。我们也知道
+ $$
+
+ b
+ $$
+ 和
+ $$
+
+ \lnot b
+ $$
+ 的总和为1。因此,当
+ $$
+
+ a
+ $$
+ 发生时,
+ $$
+
+ b
+ $$
+ 可以发生也可以不发生。当我们把
+ $$
+
+ a
+ $$
+ 和
+ $$
+
+ b
+ $$
+ 发生的概率加上
+ $$
+
+ a
+ $$
+ 和
+ $$
+
+ \lnot b
+ $$
+ 的概率时,我们得到的只是
+ $$
+
+ a$$ 的概率。
+- 随机变量的边缘化可以用:
+ $$
+ P(X=x_i)=\sum_jP(X=x_i,Y=y_j)
+ $$
+
+ 表示
+- 方程的左侧表示“随机变量
+ $$
+ X$$具有
+ $$
+
+ x_i
+ $$
+ 值的概率” 例如,对于我们前面提到的变量C,两个可能的值是早上有云和早上没有云。等式的正确部分是边缘化的概念。
+ $$
+
+ P(X=x_i)
+ $$
+ 等于
+ $$
+
+ x_i
+ $$
+ 以及随机变量
+ $$
+
+ Y
+ $$
+ 的每一个值的所有联合概率之和。例如,
+ $$
+
+ P(C=云)=P(C=云,R=雨)+P(C=云,R=\lnot 雨)=0.08+0.32=0.4$$。
+
+ - 条件边缘化:
+ $$
+ P(a)=P(a|b)P(b)+P(a|\lnot b)P(\lnot b)
+ $$
+
+ 。这是一个类似于边缘化的想法。事件
+ $$
+ a$$发生的概率等于给定
+ $$
+
+ b
+ $$
+ 的概率乘以
+ $$
+
+ b
+ $$
+ 的概率,再加上给定
+ $$
+
+ \lnot b
+ $$
+ 的概率乘以
+ $$
+
+ \lnot b$$ 的概率。
+ - $$
+ P(X=x_i)=\sum_jP(X=x_i|Y=y_i)P(Y=y_i)
+ $$
+ - 在这个公式中,随机变量
+ $$
+ X$$取
+ $$
+
+ x_i
+ $$
+ 值概率等于
+ $$
+
+ x_i
+ $$
+ 以及随机变量
+ $$
+
+ Y
+ $$
+ 的每个值的联合概率乘以变量
+ $$
+
+ Y
+ $$
+ 取该值的概率之和。如果我们还记得
+ $$
+
+ P(a|b)=\frac{P(a,b)}{P(b)}
+ $$
+ ,就可以理解这个公式。如果我们将这个表达式乘以
+ $$
+
+ P(b)
+ $$
+ ,我们得到
+ $$
+
+ P(a,b)$$,从这里开始,我们做的与边缘化相同。
+
+## 贝叶斯网络(Bayesian Networks)
+
+- 贝叶斯网络是一种表示随机变量之间相关性的数据结构。贝叶斯网络具有以下属性:
+
+ - 它们是有向图。
+ - 图上的每个节点表示一个随机变量。
+ - 从 X 到 Y 的箭头表示 X 是 Y 的父对象。也就是说,Y 的概率分布取决于 X 的值。
+ - 每个节点 X 具有概率分布
+ $$
+ P(X|Parents(X))
+ $$
+
+ 。
+- 让我们考虑一个贝叶斯网络的例子,该网络包含影响我们是否按时赴约的随机变量。
+
+
+
+- 让我们从上到下描述这个贝叶斯网络:
+
+ - rain 是这个网络的根节点。这意味着它的概率分布不依赖于任何先前的事件。在我们的例子中,Rain 是一个随机变量,可以采用以下概率分布的值
+ $$
+ \set{none,light,heavy}
+ $$
+
+ :
+
+| none | light | heavy |
+| ---- | ----- | ----- |
+| 0.7 | 0.2 | 0.1 |
+
+```
+- Maintenance对是否有列车轨道维护进行编码,取值为$$\set{yes,no}$$。Rain是Maintenance的父节点,这意味着Maintenance概率分布受到Rain的影响。
+```
+
+| R | yes | no |
+| ----- | --- | --- |
+| none | 0.4 | 0.6 |
+| light | 0.2 | 0.8 |
+| heavy | 0.1 | 0.9 |
+
+```
+- Train是一个变量,用于编码列车是准时还是晚点,取值为$$\set{on\ time,delayed}$$。请注意,列车上被“Maintenance”和“rain”指向。这意味着两者都是Train的父对象,它们的值会影响Train的概率分布。
+```
+
+| R | M | On time | Delayed |
+| ------ | --- | ------- | ------- |
+| none | yes | 0.8 | 0.2 |
+| none | no | 0.9 | 0.1 |
+| light | yes | 0.6 | 0.4 |
+| light | no | 0.7 | 0.3 |
+| heavry | yes | 0.4 | 0.6 |
+| heavy | no | 0.5 | 0.5 |
+
+```
+- Appointment 是一个随机变量,表示我们是否参加约会,取值为$$\set{attend, miss}$$。请注意,它唯一的父级是Train。关于贝叶斯网络的这一点值得注意:父子只包括直接关系。的确,Maintenance会影响Train是否准时,而Train是否准时会影响我们是否赴约。然而,最终,直接影响我们赴约机会的是Train是否准时,这就是贝叶斯网络所代表的。例如,如果火车准时到达,可能会有大雨和轨道维护,但这对我们是否赴约没有影响。
+```
+
+| T | attend | miss |
+| ------- | ------ | ---- |
+| on time | 0.9 | 0.1 |
+| delayed | 0.6 | 0.4 |
+
+```
+- 例如,如果我们想找出在没有维护和小雨的一天火车晚点时错过约会的概率,或者$$P(light,no,delayed,miss)$$,我们将计算如下:$$P(light)P(no|light)P(delayed|light,no)P(miss|delayed)$$。每个单独概率的值可以在上面的概率分布中找到,然后将这些值相乘以产生$$P(light,no,delayed,miss)$$。
+```
+
+### 推理(Inference)
+
+- 在知识推理,我们通过蕴含来看待推理。这意味着我们可以在现有信息的基础上得出新的信息。我们也可以根据概率推断出新的信息。虽然这不能让我们确切地知道新的信息,但它可以让我们计算出一些值的概率分布。推理具有多个属性。
+- Query 查询变量 $$X$$:我们要计算概率分布的变量。
+- Evidence variables 证据变量
+ $$
+ E$$: 一个或多个观测到事件
+ $$
+
+ e$$ 的变量。例如,我们可能观测到有小雨,这一观测有助于我们计算火车延误的概率。
+- Hidden variables 隐藏变量 $$H$$: 不是查询结论的变量,也没有被观测到。例如,站在火车站,我们可以观察是否下雨,但我们不知道道路后面的轨道是否有维修。因此,在这种情况下,Maintenance 将是一个隐藏的变量。
+- The goal 目标: 计算
+ $$
+ P(X|e)
+ $$
+
+ 。例如,根据我们知道有小雨的证据 $$e$$ 计算 Train 变量(查询)的概率分布。
+- 举一个例子。考虑到有小雨和没有轨道维护的证据,我们想计算 Appointment 变量的概率分布。也就是说,我们知道有小雨,没有轨道维护,我们想弄清楚我们参加约会和错过约会的概率是多少,
+ $$
+ P(Appointment|light,no)
+ $$
+
+ 。从联合概率部分中,我们知道我们可以将约会随机变量的可能值表示为一个比例,将
+ $$
+ P(Appointment|light,no)
+ $$
+
+ 重写为
+ $$
+ αP(Appointment,light,no)
+ $$
+
+ 。如果 Appointment 的父节点仅为 Train 变量,而不是 Rain 或 Maintenance,我们如何计算约会的概率分布?在这里,我们将使用边缘化。
+ $$
+ P(Appointment,light,no)
+ $$
+
+ 的值等于
+ $$
+ α[P(Appointment,light,no,delay)+P(Appointment,light,no,on\ time)]
+ $$
+
+ 。
+
+### 枚举推理
+
+- 枚举推理是在给定观测证据
+ $$
+ e$$和一些隐藏变量
+ $$
+
+ Y
+ $$
+ 的情况下,找到变量
+ $$
+
+ X$$ 的概率分布的过程。
+- $$
+ P(X|e)=\alpha P(X,e)=\alpha \sum_yP(X,e,y)
+ $$
+- 在这个方程中,
+ $$
+ X$$代表查询变量,
+ $$
+
+ e
+ $$
+ 代表观察到的证据,
+ $$
+
+ y
+ $$
+ 代表隐藏变量的所有值,
+ $$
+
+ α
+ $$
+ 归一化结果,使我们最终得到的概率加起来为1。用文字来解释这个方程,即给定
+ $$
+
+ e
+ $$
+ 的
+ $$
+
+ X
+ $$
+ 的概率分布等于
+ $$
+
+ X
+ $$
+ 和
+ $$
+
+ e
+ $$
+ 的归一化概率分布。为了得到这个分布,我们对
+ $$
+
+ X、e
+ $$
+ 和
+ $$
+
+ y
+ $$
+ 的归一化概率求和,其中
+ $$
+
+ y
+ $$
+ 每次取隐藏变量
+ $$
+
+ Y$$ 的不同值。
+- Python 中存在多个库,以简化概率推理过程。我们将查看库 `pomegranate`,看看如何在代码中表示上述数据。
+
+```python
+from pomegranate import *
+'''创建节点,并为每个节点提供概率分布'''
+# Rain节点没有父节点
+rain = Node(DiscreteDistribution({
+ "none": 0.7,
+ "light": 0.2,
+ "heavy": 0.1
+}), name="rain")
+# Track maintenance节点以rain为条件
+maintenance = Node(ConditionalProbabilityTable([
+ ["none", "yes", 0.4],
+ ["none", "no", 0.6],
+ ["light", "yes", 0.2],
+ ["light", "no", 0.8],
+ ["heavy", "yes", 0.1],
+ ["heavy", "no", 0.9]
+], [rain.distribution]), name="maintenance")
+# Train node节点以rain和maintenance为条件
+train = Node(ConditionalProbabilityTable([
+ ["none", "yes", "on time", 0.8],
+ ["none", "yes", "delayed", 0.2],
+ ["none", "no", "on time", 0.9],
+ ["none", "no", "delayed", 0.1],
+ ["light", "yes", "on time", 0.6],
+ ["light", "yes", "delayed", 0.4],
+ ["light", "no", "on time", 0.7],
+ ["light", "no", "delayed", 0.3],
+ ["heavy", "yes", "on time", 0.4],
+ ["heavy", "yes", "delayed", 0.6],
+ ["heavy", "no", "on time", 0.5],
+ ["heavy", "no", "delayed", 0.5],
+], [rain.distribution, maintenance.distribution]), name="train")
+# Appointment节点以列车为条件
+appointment = Node(ConditionalProbabilityTable([
+ ["on time", "attend", 0.9],
+ ["on time", "miss", 0.1],
+ ["delayed", "attend", 0.6],
+ ["delayed", "miss", 0.4]
+], [train.distribution]), name="appointment")
+'''我们通过添加所有节点来创建模型,然后通过在节点之间添加边来描述哪个节点是另一个节点的父节点(回想一下,贝叶斯网络是一个有向图,节点之间由箭头组成)。'''
+# 创建贝叶斯网络并添加状态
+model = BayesianNetwork()
+model.add_states(rain, maintenance, train, appointment)
+# 添加连接节点的边
+model.add_edge(rain, maintenance)
+model.add_edge(rain, train)
+model.add_edge(maintenance, train)
+model.add_edge(train, appointment)
+# 最终确定模型
+model.bake()
+'''模型可以计算特定条件下的概率'''
+# 计算给定观测的概率
+probability = model.probability([["none", "no", "on time", "attend"]])
+print(probability)
+'''我们可以使用该模型为所有变量提供概率分布,给出一些观测到的证据。在以下情况下,我们知道火车晚点了。给定这些信息,我们计算并打印变量Rain、Maintenance和Appointment的概率分布。'''
+# 根据火车晚点的证据计算预测
+predictions = model.predict_proba({
+ "train": "delayed"
+})
+# 打印每个节点的预测
+for node, prediction in zip(model.states, predictions):
+ # 预测已确定时返回字符串
+ if isinstance(prediction, str):
+ print(f"{node.name}: {prediction}")
+ else:
+ # 预测不确定时返回概率分布
+ print(f"{node.name}")
+ for value, probability in prediction.parameters[0].items():
+ print(f" {value}: {probability:.4f}")
+```
+
+- 上面的代码使用了枚举推理。然而,这种计算概率的方法效率很低,尤其是当模型中有很多变量时。另一种方法是放弃精确推理,转而采用近似推理。这样做,我们在生成的概率中会失去一些精度,但这种不精确性通常可以忽略不计。相反,我们获得了一种可扩展的概率计算方法。
+
+### 采样(Sampling)
+
+- 采样是一种近似推理技术。在采样中,根据每个变量的概率分布对其值进行采样。
+
+> 要使用骰子采样生成分布,我们可以多次掷骰子,并记录每次获得的值。假设我们把骰子掷了 600 次。我们计算得到 1 的次数,应该大约是 100,然后对其余的值 2-6 重复采样。然后,我们将每个计数除以投掷的总数。这将生成掷骰子的值的近似分布:一方面,我们不太可能得到每个值发生概率为 1/6 的结果(这是确切的概率),但我们会得到一个接近它的值。
+
+- 如果我们从对 Rain 变量进行采样开始,则生成的值 none 的概率为 0.7,生成的值 light 的概率为 0.2,而生成的值 heavy 的概率则为 0.1。假设我们的采样值为 none。当我们得到 Maintenance 变量时,我们也会对其进行采样,但只能从 Rain 等于 none 的概率分布中进行采样,因为这是一个已经采样的结果。我们将通过所有节点继续这样做。现在我们有一个样本,多次重复这个过程会生成一个分布。现在,如果我们想回答一个问题,比如什么是
+ $$
+ P(Train=on\ time)
+ $$
+
+ ,我们可以计算变量 Train 具有准时值的样本数量,并将结果除以样本总数。通过这种方式,我们刚刚生成了
+ $$
+ P(Train=on\ {time})
+ $$
+
+ 的近似概率。
+
+
+
+
+
+- 我们也可以回答涉及条件概率的问题,例如
+ $$
+ P(rain=light|train=on\ {time})
+ $$
+
+ 。在这种情况下,我们忽略 Train 值为 delay 的所有样本,然后照常进行。我们计算在
+ $$
+ Train=\text{on time}
+ $$
+
+ 的样本中有多少样本具有变量
+ $$
+ Rain=light
+ $$
+
+ ,然后除以
+ $$
+ Train=\text{on time}
+ $$
+
+ 的样本总数。
+
+
+
+
+
+去除
+$$
+T= on time
+$$
+
+的样本
+
+
+
+
+
+选择
+$$
+R=light
+$$
+
+的样本
+
+- 在代码中,采样函数可以是 `generate_sample`:
+
+```python
+'''如果你对pomegrante库不熟悉,没有关系,考虑generate_sample为一个黑盒,或者你可以在python解释器中查看model.states, state.distribution的值以了解model在该库中的实现方式'''
+import pomegranate
+from collections import Counter
+def generate_sample():
+ # 随机变量与生成的样本之间的映射
+ sample = {}
+ # 概率分布与样本的映射
+ parents = {}
+ # 为所有状态节点采样
+ for state in model.states:
+ # 如果我们有一个非根节点,则以父节点为条件进行采样
+ if isinstance(state.distribution, pomegranate.ConditionalProbabilityTable):
+ sample[state.name] = state.distribution.sample(parent_values=parents)
+ # 否则,只根据节点的概率分布单独取样
+ else:
+ sample[state.name] = state.distribution.sample()
+ # 追踪映射中的采样值
+ parents[state.distribution] = sample[state.name]
+ # 返回生成的样本
+ return sample
+# 采样
+# 观测到train=delay,计算appointment分布
+N = 10000
+data = []
+# 重复采样10000次
+for i in range(N):
+ # 根据我们之前定义的函数生成一个样本
+ sample = generate_sample()
+ # 如果在该样本中,Train的变量的值为delay,则保存样本。由于我们对给定train=delay的appointment概率分布感兴趣,我们丢弃了train=on time的采样样本。
+ if sample["train"] == "delayed":
+ data.append(sample["appointment"])
+# 计算变量的每个值出现的次数。我们可以稍后通过将结果除以保存的样本总数来进行归一化,以获得变量的近似概率,该概率加起来为1。
+print(Counter(data))
+```
+
+### 似然加权
+
+- 在上面的采样示例中,我们丢弃了与我们所掌握的证据不匹配的样本。这是低效的。解决这一问题的一种方法是使用似然加权,使用以下步骤:
+
+ - 首先固定证据变量的值。
+ - 使用贝叶斯网络中的条件概率对非证据变量进行采样。
+ - 根据其可能性对每个样本进行加权: 所有证据出现的概率。
+- 例如,如果我们观察到
+ $$
+ Train=\text{on time}
+ $$
+
+ ,我们将像之前一样开始采样。我们对给定概率分布的 Rain 值进行采样,然后对 Maintenance 进行采样,但当我们到达 Train 时,我们总是按照观测值取值。然后,我们继续进行,并在给定
+ $$
+ Train=\text{on time}
+ $$
+
+ 的情况下,根据其概率分布对 Appointment 进行采样。既然这个样本存在,我们就根据观察到的变量在给定其采样父变量的情况下的条件概率对其进行加权。也就是说,如果我们采样了 Rain 并得到了 light,然后我们采样了 Maintenance 并得到了 yes,那么我们将用
+ $$
+ P(Train=\text{on time}|light,yes)
+ $$
+
+ 来加权这个样本。
+
+## 马尔科夫模型(Markov Models)
+
+- 到目前为止,我们已经研究了概率问题,给出了我们观察到的一些信息。在这种范式中,时间的维度没有以任何方式表示。然而,许多任务确实依赖于时间维度,例如预测。为了表示时间变量,我们将创建一个新的变量
+ $$
+ X$$,并根据感兴趣的事件对其进行更改,使
+ $$
+
+ X_t
+ $$
+ 是当前事件,
+ $$
+
+ X_{t+1}$$ 是下一个事件,依此类推。为了能够预测未来的事件,我们将使用马尔可夫模型。
+
+### 马尔科夫假设(The Markov Assumption)
+
+- 马尔科夫假设是一个假设,即当前状态只取决于有限的固定数量的先前状态。想想预测天气的任务。在理论上,我们可以使用过去一年的所有数据来预测明天的天气。然而,这是不可行的,一方面是因为这需要计算能力,另一方面是因为可能没有关于基于 365 天前天气的明天天气的条件概率的信息。使用马尔科夫假设,我们限制了我们以前的状态(例如,在预测明天的天气时,我们要考虑多少个以前的日子),从而使这个任务变得可控。这意味着我们可能会得到感兴趣的概率的一个更粗略的近似值,但这往往足以满足我们的需要。此外,我们可以根据最后一个事件的信息来使用马尔可夫模型(例如,根据今天的天气来预测明天的天气)。
+
+### 马尔科夫链(Markov Chain)
+
+- 马尔科夫链是一个随机变量的序列,每个变量的分布都遵循马尔科夫假设。也就是说,链中的每个事件的发生都是基于之前事件的概率。
+- 为了构建马尔可夫链,我们需要一个过渡模型,该模型将根据当前事件的可能值来指定下一个事件的概率分布。
+
+
+
+- 在这个例子中,基于今天是晴天,明天是晴天的概率是 0.8。这是合理的,因为晴天之后更可能是晴天。然而,如果今天是雨天,明天下雨的概率是 0.7,因为雨天更有可能相继出现。使用这个过渡模型,可以对马尔可夫链进行采样。从一天是雨天或晴天开始,然后根据今天的天气,对第二天的晴天或雨天的概率进行采样。然后,根据明天的情况对后天的概率进行采样,以此类推,形成马尔科夫链:
+
+
+
+- 给定这个马尔可夫链,我们现在可以回答诸如“连续四个雨天的概率是多少?”这样的问题。下面是一个如何在代码中实现马尔可夫链的例子:
+
+```python
+from pomegranate import *
+# 定义起始概率
+start = DiscreteDistribution({
+ "sun": 0.5,
+ "rain": 0.5
+})
+# 定义过渡模型
+transitions = ConditionalProbabilityTable([
+ ["sun", "sun", 0.8],
+ ["sun", "rain", 0.2],
+ ["rain", "sun", 0.3],
+ ["rain", "rain", 0.7]
+], [start])
+# 创造马尔科夫链
+model = MarkovChain([start, transitions])
+# 采样50次
+print(model.sample(50))
+```
+
+## 隐马尔科夫模型(Hidden Markov Models)
+
+- 隐马尔科夫模型是一种具有隐藏状态的系统的马尔科夫模型,它产生了一些观察到的事件。这意味着,有时候,人工智能对世界有一些测量,但无法获得世界的精确状态。在这些情况下,世界的状态被称为隐藏状态,而人工智能能够获得的任何数据都是观察结果。下面是一些这方面的例子:
+
+ - 对于一个探索未知领域的机器人来说,隐藏状态是它的位置,而观察是机器人的传感器所记录的数据。
+ - 在语音识别中,隐藏状态是所讲的话语,而观察是音频波形。
+ - 在衡量网站的用户参与度时,隐藏的状态是用户的参与程度,而观察是网站或应用程序的分析。
+- 举个例子。我们的人工智能想要推断天气(隐藏状态),但它只能接触到一个室内摄像头,记录有多少人带了雨伞。这里是我们的传感器模型(sensor model),表示了这些概率:
+
+
+
+- 在这个模型中,如果是晴天,人们很可能不会带伞到大楼。如果是雨天,那么人们就很有可能带伞到大楼来。通过对人们是否带伞的观察,我们可以合理地预测外面的天气情况。
+
+### 传感器马尔科夫假设
+
+- 假设证据变量只取决于相应的状态。例如,对于我们的模型,我们假设人们是否带雨伞去办公室只取决于天气。这不一定反映了完整的事实,因为,比如说,比较自觉的、不喜欢下雨的人可能即使在阳光明媚的时候也会到处带伞,如果我们知道每个人的个性,会给模型增加更多的数据。然而,传感器马尔科夫假设忽略了这些数据,假设只有隐藏状态会影响观察。
+- 隐马尔科夫模型可以用一个有两层的马尔科夫链来表示。上层,变量
+ $$
+ X$$,代表隐藏状态。底层,变量
+ $$
+
+ E$$,代表证据,即我们所拥有的观察。
+
+
+
+- 基于隐马尔科夫模型,可以实现多种任务:
+
+ - 筛选 Filtering: 给定从开始到现在的观察结果,计算出当前状态的概率分布。例如,给从从特定时间开始到今天人们带伞的信息,我们产生一个今天是否下雨的概率分布。
+ - 预测 Prediction: 给定从开始到现在的观察,计算未来状态的概率分布。
+ - 平滑化 Smoothing: 给定从开始到现在的观察,计算过去状态的概率分布。例如,鉴于今天人们带了雨伞,计算昨天下雨的概率。
+ - 最可能的解释 Most likely explanation: 鉴于从开始到现在的观察,计算最可能的事件顺序。
+- 最可能的解释任务可用于语音识别等过程,根据多个波形,人工智能推断出给这些波形带来的最有可能的单词或音节的序列。接下来是一个隐马尔科夫模型的 Python 实现,我们将用于最可能的解释任务:
+
+```python
+from pomegranate import *
+# 每个状态的观测模型
+sun = DiscreteDistribution({
+ "umbrella": 0.2,
+ "no umbrella": 0.8
+})
+rain = DiscreteDistribution({
+ "umbrella": 0.9,
+ "no umbrella": 0.1
+})
+states = [sun, rain]
+# 过渡模型
+transitions = numpy.array(
+ [[0.8, 0.2], # Tomorrow's predictions if today = sun
+ [0.3, 0.7]] # Tomorrow's predictions if today = rain
+)
+# 起始概率
+starts = numpy.array([0.5, 0.5])
+# 建立模型
+model = HiddenMarkovModel.from_matrix(
+ transitions, states, starts,
+ state_names=["sun", "rain"]
+)
+model.bake()
+```
+
+- 请注意,我们的模型同时具有传感器模型和过渡模型。对于隐马尔可夫模型,我们需要这两个模型。在下面的代码片段中,我们看到了人们是否带伞到大楼的观察序列,根据这个序列,我们将运行模型,它将生成并打印出最可能的解释(即最可能带来这种观察模式的天气序列):
+
+```python
+from model import model
+# 观测到的数据
+observations = [
+ "umbrella",
+ "umbrella",
+ "no umbrella",
+ "umbrella",
+ "umbrella",
+ "umbrella",
+ "umbrella",
+ "no umbrella",
+ "no umbrella"
+]
+# 预测隐藏状态
+predictions = model.predict(observations)
+for prediction in predictions:
+ print(model.states[prediction].name)
+```
+
+- 在这种情况下,程序的输出将是 rain,rain,sun,rain,rain,rain,rain,sun,sun。根据我们对人们带伞或不带伞到大楼的观察,这一输出代表了最有可能的天气模式。
diff --git a/4.人工智能/static/AptYbb5MZoylvex7LvPcSqivnef.png b/4.人工智能/static/AptYbb5MZoylvex7LvPcSqivnef.png
new file mode 100644
index 0000000..c8e147c
Binary files /dev/null and b/4.人工智能/static/AptYbb5MZoylvex7LvPcSqivnef.png differ
diff --git a/4.人工智能/static/CreObGAg4oXB0oxe2hMcQbYZnAc.png b/4.人工智能/static/CreObGAg4oXB0oxe2hMcQbYZnAc.png
new file mode 100644
index 0000000..64a5453
Binary files /dev/null and b/4.人工智能/static/CreObGAg4oXB0oxe2hMcQbYZnAc.png differ
diff --git a/4.人工智能/static/E0TtbfgiCoV2dtxbbPHcjPgXnQe.png b/4.人工智能/static/E0TtbfgiCoV2dtxbbPHcjPgXnQe.png
new file mode 100644
index 0000000..0998dcb
Binary files /dev/null and b/4.人工智能/static/E0TtbfgiCoV2dtxbbPHcjPgXnQe.png differ
diff --git a/4.人工智能/static/FNyab3RWQo3EA8xu8T7cyLwhnyh.png b/4.人工智能/static/FNyab3RWQo3EA8xu8T7cyLwhnyh.png
new file mode 100644
index 0000000..577abbf
Binary files /dev/null and b/4.人工智能/static/FNyab3RWQo3EA8xu8T7cyLwhnyh.png differ
diff --git a/4.人工智能/static/FnyrbYSEWohimaxIYPSchotGnse.png b/4.人工智能/static/FnyrbYSEWohimaxIYPSchotGnse.png
new file mode 100644
index 0000000..732e23a
Binary files /dev/null and b/4.人工智能/static/FnyrbYSEWohimaxIYPSchotGnse.png differ
diff --git a/4.人工智能/static/GKc6be6ueopUYZxxQg4cS4AVnmb.png b/4.人工智能/static/GKc6be6ueopUYZxxQg4cS4AVnmb.png
new file mode 100644
index 0000000..74b7603
Binary files /dev/null and b/4.人工智能/static/GKc6be6ueopUYZxxQg4cS4AVnmb.png differ
diff --git a/4.人工智能/static/GqlRbfW7Yom5a9xKCBHckMBuniF.png b/4.人工智能/static/GqlRbfW7Yom5a9xKCBHckMBuniF.png
new file mode 100644
index 0000000..6478c5e
Binary files /dev/null and b/4.人工智能/static/GqlRbfW7Yom5a9xKCBHckMBuniF.png differ
diff --git a/4.人工智能/static/Ilj3bPKuwo0l6Dx13rZcVXfenOb.png b/4.人工智能/static/Ilj3bPKuwo0l6Dx13rZcVXfenOb.png
new file mode 100644
index 0000000..9304f09
Binary files /dev/null and b/4.人工智能/static/Ilj3bPKuwo0l6Dx13rZcVXfenOb.png differ
diff --git a/4.人工智能/static/KsELbuMTCoKZkGxU9U5czQpanKg.png b/4.人工智能/static/KsELbuMTCoKZkGxU9U5czQpanKg.png
new file mode 100644
index 0000000..c4f2a0f
Binary files /dev/null and b/4.人工智能/static/KsELbuMTCoKZkGxU9U5czQpanKg.png differ
diff --git a/4.人工智能/static/MkZ6bIPFroAm3lxzLydcsn5QnNg.png b/4.人工智能/static/MkZ6bIPFroAm3lxzLydcsn5QnNg.png
new file mode 100644
index 0000000..7a9d381
Binary files /dev/null and b/4.人工智能/static/MkZ6bIPFroAm3lxzLydcsn5QnNg.png differ
diff --git a/4.人工智能/static/MrP0b2FbXofDsOxgnmncufUynAB.png b/4.人工智能/static/MrP0b2FbXofDsOxgnmncufUynAB.png
new file mode 100644
index 0000000..15219e1
Binary files /dev/null and b/4.人工智能/static/MrP0b2FbXofDsOxgnmncufUynAB.png differ
diff --git a/4.人工智能/static/PUesbhgsFoiucAxWBKYcUUU3nMd.png b/4.人工智能/static/PUesbhgsFoiucAxWBKYcUUU3nMd.png
new file mode 100644
index 0000000..f816755
Binary files /dev/null and b/4.人工智能/static/PUesbhgsFoiucAxWBKYcUUU3nMd.png differ
diff --git a/4.人工智能/static/VBGxbrNgAovuKXxnTKYcm7UinFd.png b/4.人工智能/static/VBGxbrNgAovuKXxnTKYcm7UinFd.png
new file mode 100644
index 0000000..1bb97ce
Binary files /dev/null and b/4.人工智能/static/VBGxbrNgAovuKXxnTKYcm7UinFd.png differ
diff --git a/4.人工智能/static/Vr96bdSafoV4kBxJ3x2cAU0TnOg.png b/4.人工智能/static/Vr96bdSafoV4kBxJ3x2cAU0TnOg.png
new file mode 100644
index 0000000..1336a5f
Binary files /dev/null and b/4.人工智能/static/Vr96bdSafoV4kBxJ3x2cAU0TnOg.png differ
diff --git a/4.人工智能/static/XBghbKBaVoz0C4xa85rch804ngd.png b/4.人工智能/static/XBghbKBaVoz0C4xa85rch804ngd.png
new file mode 100644
index 0000000..2b31049
Binary files /dev/null and b/4.人工智能/static/XBghbKBaVoz0C4xa85rch804ngd.png differ
diff --git a/4.人工智能/static/XZfhbR6sBorTI9x7hVVchGLUn3b.png b/4.人工智能/static/XZfhbR6sBorTI9x7hVVchGLUn3b.png
new file mode 100644
index 0000000..fc1460b
Binary files /dev/null and b/4.人工智能/static/XZfhbR6sBorTI9x7hVVchGLUn3b.png differ
diff --git a/4.人工智能/static/Y8EbbcnUsoHHlFxHCrGcIUDNn0f.png b/4.人工智能/static/Y8EbbcnUsoHHlFxHCrGcIUDNn0f.png
new file mode 100644
index 0000000..87c9027
Binary files /dev/null and b/4.人工智能/static/Y8EbbcnUsoHHlFxHCrGcIUDNn0f.png differ
diff --git a/5.富有生命的嵌入式/5.2New meaning of C.md b/5.富有生命的嵌入式/5.2New meaning of C.md
new file mode 100644
index 0000000..dc20fd2
--- /dev/null
+++ b/5.富有生命的嵌入式/5.2New meaning of C.md
@@ -0,0 +1,161 @@
+# New meaning of C
+
+> Author:肖扬
+
+在上一篇文章中,我们介绍了嵌入式相关的产品,其中芯片充当着及其重要的作用,本篇我们将以孵化器实验室的部分考核流程与教程作为参考,并且加以一定的概念解析,逐步地为大家解开单片机裸机开发的面纱。
+
+PS:在学习此模块之前,我希望你们能熟练掌握 C 语言的基础知识,特别是指针方面的内容。
+
+如果你还没有完成 C 语言的学习,可以参考此文章:[3.4C 语言](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.4C%E8%AF%AD%E8%A8%80.md)
+
+## STC89C52--一款入门级的芯片
+
+### 相关介绍
+
+Intel 的 STC89C51\C52 系列早在上世纪 80 年代就已经广泛运用,考虑到其精简但较为充足的硬件资源以及其低廉的价格,笔者十分建议可以从 51 单片机系列开始去接触整个嵌入式行业(因为你需要知道自己适不适合进入到嵌入式行业,大学本就是一个试错的过程,而低廉的价格使得其试错成本变得很低)
+
+以下是比较推荐的学习路线:
+
+1、购买 51 单片机:[https://m.tb.cn/h.UuGJR5G?tk=kN94dm040JX](https://m.tb.cn/h.UuGJR5G?tk=kN94dm040JX) CZ0001 「普中 51 单片机学习板开发板 stc89c52 单片机实验板 C51 单片机 diy 套件」
+
+2、推荐学习视频:【51 单片机入门教程-2020 版 程序全程纯手打 从零开始入门】[https://b23.tv/KmaWgUK](https://b23.tv/KmaWgUK)
+
+3、相关学习资料:
+
+软件安装包、开发板资料、课件及程序源码百度网盘链接:
+
+[https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng](https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng) 提取码:gdzf,链接里压缩包的解压密码:51 ,如果打不开请复制链接到浏览器再打开
+
+### 一些简单的任务
+
+咳咳,摆烂一下,放一下 22 年实验室的培养计划(只给第一期,不能再多了):
+
+在学习完这些内容后,你可以尝试如下的工程实践:
+
+## 寄存器(VERRY--IMPORTANT!!!)
+
+相信学完 C51 的内容,你已经对寄存器有了一定的了解。
+
+但是由于其重要性,以及为了巩固各位的基础知识,防止出现有些人学了 A 只会 A,学了 B 只会 B,而不会 CDEF...的现象,所以这里我必须要着重重新讲解一下寄存器的概念。
+
+在 C 语言中我们有 int、short、long 等变量,实际上 C 语言中还可以定义一个寄存器变量(register)。
+
+那么为什么需要寄存器呢,或者说我们为什么需要一个寄存器变量呢。
+
+这里我不从计算机组成原理或者微机接口与技术的概念入手,而是举一个简单的例子:
+
+如果我们在图书馆上准备看书,去获取知识,此时我们是 CPU、书则是数据。
+
+如果我们去图书馆里的书架上拿书并观看,则需要:走到对应书架-拿书(获取数据)-回到书桌,这需要花费相当一部分的时间,此时硬盘相当于书架;如果我们直接拿书桌上的书,则相对速度会快很多,此时书桌相当于主存;如果我们手上就有一本书,那么我们低头就可以看到,手就相当于寄存器。所以,寄存器是 CPU 内部用来存放数据的一些小型的存储区域,用来暂时存放参与运算的数据和运算结果以及一些 CPU 运行所需要的信息。
+
+以我举例而言,可见寄存器获得数据的速度会快于主存与硬盘,而存储数据的大小将会小于主存与硬盘,如果这块不清楚的话也可以去看 也许你会用上的基础知识 中的存储器知识部分。
+
+而从汇编语言的角度来讲(此为 Intel 的汇编语法):
+
+```asm
+WAIT_FOR_TIMER:
+ MOV A, LED_COUNT ;读取当前亮灯的编号
+ CJNE A, #00H, NOT_FIRST_LED ;如果不是第一个灯,则跳转到NOT_FIRST_LED标签
+ MOV A, #03H ;如果是第一个灯,则将延时设为3秒
+ SJMP DELAY ;跳转到延时
+NOT_FIRST_LED:
+ .......
+DELAY:
+ .......
+;此汇编代码块中,累加器A作为一个常用的特殊寄存器,充当着暂时存储数据信息的作用
+;存储LED_COUNT的编号并用于比较大小,存储所需延时时间并用于DELAY块
+```
+
+```asm
+ORG 0BH
+ PUSH ACC ; 保存寄存器状态
+ PUSH PSW
+ ......
+ POP PSW ; 恢复寄存器状态
+ POP ACC
+ RETI ; 返回中断
+;此汇编代码块用于基本的中断处理,我们需要保存ACC、PSW的状态来维持中断以及中断后程序的正常进行
+```
+
+以上简单例举了寄存器的一般作用,以汇编语言出发去讲的原因是:它能有效地展现底层代码的工作原理,既不会像机器语言那样只用 0、1 表示的晦涩难懂,又不会像高级语言那般难以直观地看到底层的工作方式。但是,我们做嵌入式入门开发的过程中并不会让你直接去写汇编语言,而是以最基础的 C 语言(相比汇编而言,C 在功能上、结构性、可读性、可维护性上有明显的优势),通过 Keil、IAR 等拥有交叉编译器的 C 语言软件开发系统来完成高级语言、汇编语言、机器语言的转码工作,从而通过 C 语言的编写来控制单片机等嵌入式系统的开发。
+
+而抽象层面的 C 代码需要通过访问寄存器来直接控制硬件。所以在嵌入式开发的过程中,C 有了特殊的含义:C 里的数字代表的可能只是一个地址或者一个数据,但是在嵌入式开发里,这样一个数字也可以代表着一种寄存器状态。
+
+下面引入一个 STM32F1 系列的 GPIO 部分寄存器图(来源正点原子提供的 F1 参考手册):
+
+
+
+
+
+如果我们想做一个简单的实验-驱动一个 LED 灯(假设此 LED 灯以 PB5 为输出驱动口),在对相应的 RCC 时钟等配置之外,最重要的是对相应的 GPIO 口的配置,首先我们查阅其寄存器的物理起始地址:
+
+
+
+
+
+可见 GPIO 外设通过 APB2 总线进行地址定位与传输数据的,所以我们要控制 PB5 的话首先需要定位到对应的地址:
+
+```c
+#define PERIPH_BASE ((uint32_t)0x40000000) //外设基址
+
+#define APB1PERIPH_BASE PERIPH_BASE
+#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) //APB2基址
+#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
+
+#define AFIO_BASE (APB2PERIPH_BASE + 0x0000)
+#define EXTI_BASE (APB2PERIPH_BASE + 0x0400)
+#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
+#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)//GPIOB基址,计算可得0x40010C00
+#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
+#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
+#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
+#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
+#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
+//APB2还有相关定时器的基址,这里就不再展示
+
+#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
+```
+
+根据如上提供的 CRL、ODR 寄存器的功能映射,要使得 PB5 推挽输出且初始电平输出为高,则需要:
+
+```c
+void LED_Init(void)
+{
+ RCC->APB2ENR|=1<<3; //GPIOB的时钟使能,只有使能对应的时钟后GPIO才能正常工作
+
+ GPIOB->CRL&=0XFF0FFFFF; //由图可知,CRL的第20-23位控制5口,此举是对第20-23位清零
+ GPIOB->CRL|=0X00300000; //此举是对第20-23位赋值0011,根据寄存器功能可知此代表50Mhz推挽输出
+ GPIOB->ODR|=1<<5; //设置ODR第5位为1,输出高电平
+}
+```
+
+其中 GPIOB 的结构体如下所示:
+
+```c
+typedef struct
+{
+ __IO uint32_t CRL;
+ __IO uint32_t CRH;
+ __IO uint32_t IDR;
+ __IO uint32_t ODR;
+ __IO uint32_t BSRR;
+ __IO uint32_t BRR;
+ __IO uint32_t LCKR;
+} GPIO_TypeDef;
+```
+
+所以由以上提到的例子而言,C 语言可以从如下三方面进行与寄存器之间的控制:
+
+1. 寄存器的地址可以使用指针变量来访问。
+2. C 语言中的结构体可以用于表示寄存器映射。
+3. C 语言中的位域可以用于表示寄存器中的位。
+
+而且 C 语言中的内联汇编可以用于直接访问寄存器。在某些情况下,如果我们需要直接访问寄存器来完成复杂的硬件控制操作,则可以使用汇编语言指令来直接访问寄存器,从而实现复杂的硬件控制操作。常见的如,堆栈大小的设置等。
+
+```asm
+Stack_Size EQU 0x00000400
+
+ AREA STACK, NOINIT, READWRITE, ALIGN=3
+Stack_Mem SPACE Stack_Size
+__initial_sp
+```
diff --git a/5.富有生命的嵌入式/5.3还玩裸机?上操作系统!.md b/5.富有生命的嵌入式/5.3还玩裸机?上操作系统!.md
new file mode 100644
index 0000000..fbfd3f5
--- /dev/null
+++ b/5.富有生命的嵌入式/5.3还玩裸机?上操作系统!.md
@@ -0,0 +1,172 @@
+# 还玩裸机?上操作系统!
+
+> Author:肖扬
+
+在 [New meaning of C](./5.2New%20meaning%20of%20C.md) 栏目中,我们已经从 stc51 单片机系列和 stm32 单片机系列进行了一定的裸机开发讲解,并且延伸到了通过相关的 datasheet 对刚接触到手的芯片进行开发(毕竟你总不能成为 stm32 开发工程师或者 cubemx 工程师对吧【然鹅大多数急于求成的开发者往往忽视了这方面的能力,也许是为了更快入手竞赛】)
+
+而在此栏目中,我们将讲述相关操作系统在嵌入式上的应用,让你的嵌入式产品更加的智慧!(当然裸机并不一定就比带操作系统的嵌入式产品差,只是应用方向不同或者说有时候需要考虑产品的成本问题)
+
+Ps:本栏目只例举几个目前开发工程中常见的操作系统的学习与开发,具体的移植过程可 web 或者自行探索-相信我,出色的移植能力是嵌入式开发者必不可少的。
+
+## RTOS
+
+MCU 本身就能做到一定的实时性,那为什么还是需要 RTOS(实时操作系统)呢,其实评判实时系统的关键并不是系统对外部事件的处理速度,而是处理事件的时间的可预见性和确定性。举个例子,Windows 操作系统在 CPU 没有其他任务时可以在很短的时间内对外部事件作出一定的响应,但是当某些后台任务在运行时,有时候响应速度会变得很慢甚至出现假死现象,这并不是因为 Windows 速度不够快或者效率不够高导致的,而是因为 Windows 对事件的响应不能提供准确性,所以其不能算作一个实时操作系统。并且在笔者看来,实时操作系统除了可以达到完成每次任务所需时间的一致性外,其相应的操作系统产品(例如我们本栏目将重点介绍的 FreeRTOS,这里可以简单提一下为啥要选 FreeRTOS,显而易见因为-Free)还具有可以简化整体架构、开发等工作的作用。
+
+RTOS 中最重要的概念则是“任务”。
+
+我们可以回想一下在 MCU 开发过程中,一般都是在 main 函数里做个 while(1)来完成大部分的处理,将一些相对来说对实时性要求高的函数(如 PID 控制器)扔到定时器中断当中,即应用程序是个无限的循环,是个单任务系统(前后台系统),while(1)作为后台,中断服务函数作为前台。这里采用了“正点原子”的一张图:
+
+
+
+而 RTOS 则是一个多任务系统,那么它这么做有什么好处呢?
+
+2>1 嘛(乐),实际上在前后台系统中,你的每项 Task 要轮流排队等着上次 Task 执行结束后再进行自己的程序,大大影响了其系统的实时性要求;而 RTOS 中我们把整个 while(1)区分成了很多小任务,并且在表面上看起来这些任务运行起来像是同时进行,实际上是因为任务所需的时间较少导致它看起来像是并行,但这将会带来新的疑问,到底什么任务先执行呢?RTOS 就为此提供了任务的相关 API 接口,赋予任务相应的执行优先级属性,并通过任务调度器来控制任务的执行顺序。这里同样采用了“正点原子”的一张图:
+
+
+
+所以,其实可以这么说:RTOS 将整个流程变成了很多个 while(1)【每个任务都是个 while(1)】。
+
+并且根据我上述所描述的内容,一个任务需要的属性大致如下(以启动函数为例进行介绍):
+
+```c
+#define START_TASK_PRIO 1 //任务优先级
+#define START_STK_SIZE 256 //任务堆栈大小
+TaskHandle_t StartTask_Handler; //任务句柄
+void start_task(void *pvParameters); //任务函数
+
+int main(){
+ systemInit();//硬件初始化
+ xTaskCreate(
+ TaskFunction_t start_task, //任务函数
+ const char * const "start_task", //任务名称
+ const uint16_t START_STK_SIZE, //任务堆栈大小
+ void * const NULL, //传递给任务函数的参数
+ UBaseType_t START_TASK_PRIO, //任务优先级
+ TaskHandle_t * const StartTask_Handler //任务句柄
+ )
+}
+```
+
+至于有关于任务的运行态、挂起态、就绪态、阻塞态等我便不在此栏目进行讲解,希望读者能根据以上的引入去学习 FreeRTOS 的开发,可供参考的文档或者学习视频如下:
+
+1、b 站正点原子官方 FreeRTOS 教学(在今年有做全面的更新,比之前讲的更为清晰,难得的优秀入门视频)
+
+[https://www.bilibili.com/video/BV19g411p7UT/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7](https://www.bilibili.com/video/BV19g411p7UT/?spm_id_from=333.999.0.0&vd_source=6ee6cc25ef1d46409e1f14084644c1f7)
+
+2、FreeRTOS 官网(官网往往是最适合学习的地方)[www.freertos.org](http://www.freertos.org)
+
+## Linux(以 ROS 为例)
+
+首先,如果你不了解 Linux 系统的话,我建议你去 [附加模块:Linux](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.Y%20%E9%99%84%E5%8A%A0%E6%A8%A1%E5%9D%97%EF%BC%9ALinux.md) 中进行一定的学习。
+
+如果你不了解 Python,我建议你去以及 [3.6Python(灵巧的胶水)](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.6Python%EF%BC%88%E7%81%B5%E5%B7%A7%E7%9A%84%E8%83%B6%E6%B0%B4%EF%BC%89.md)中进行学习。
+
+如果你已经对以上所涉及到的方面有了一定的了解,那么欢迎来到机器人开发者的殿堂-Robot Operating System!
+
+由于硬件技术的飞速发展,针对于机器人软件设计的框架也面临着极大的挑战,而 ROS 的出现无异是所有机器人开发者的福音,因为如果按照以前的制作一个机器人流程来讲,也许你要经历以下步骤:硬件结构搭建、控制处理、相关算法构建等等,但是 ROS 的开源共享模式令其可以在其平台上巧妙利用别人的开源模型完成自己的机器人搭建,也就是说 Ros 的出现打破了原本各个开发者(或团队)闭门造车的开发现象,使得其可以共享优秀的机器人应用软件,换句话说就是提高了机器人研发的软件复用率。(毕竟哪个团队都不可能同时在建图、导航、视觉等机器人应用方面处于顶尖位置)
+
+
+
+由于 ROS 中完成度最高的是 Ubuntu,所以我们建议你以此展开学习,当然你也可以选择 macOS、Debian 等 OS。
+
+但是 Linux 只是一个通用系统,并没有在机器人开发上提供相应的中间件,所以 ROS 提供了基于 TCP/UDP 的通信接口(机器人的工作当然需要通讯),在再此之上提供了相应的基础开发库供给至应用层。
+
+此时应用层则有个很重要的概念-Master(管理者),其负责管理整个系统的正常运行。如果我们需要获得比较成熟的相关领域开源机器人包,按以往的操作必将是进行一次比较复杂的移植(你需要考虑各种因素:比如硬件支持、与其他移植包是否冲突等等)。但是在 ROS 系统中引入了功能包的概念,你可以在 ROS 社区中下载相关版本(与你的 ROS 版本相匹配)的机器人应用功能包,完成一次非常简单的移植过程(CV),你不需要关注其内部的具体运行机制,只需关注接口的定义与使用规则便可进行相应的二次开发(在 ROS 中你需要关注的是相关节点之间的关系,可以通过 rqt_graph 获取清晰的节点图),相当于给你做好了一个跟机器人开发有关的高集成度SDK平台。(当然如果你感兴趣的话可以做一定的了解,但这将牵扯到相关内容的庞大体系,比如如果你想了解自主导航是如何运行的,你首先需要了解 SLAM 算法的运行机制以及激光雷达或者相关深度摄像机的运用,然后你需要了解什么是深度信息什么是里程计信息,为什么可以表示机器人的位置信息,要如何进行一些相关的位置信息修正,然后 bulabula。以笔者自身的学习经历而言,学习相关的理论基础体系,将对你的二次开发有极大的帮助,而不会造成盲目使用接口的情况)
+
+根据以上我讲述的相关内容可知:ROS 系统的优点在于,你能将社区里的有关机器人的开发模块集大成于一身,并且通过 ROS 与控制板通讯(此时类似于 STM32 的裸机主控便变成了控制板-用于接收 ROS 的调控完成相应电机、舵机的控制,或者完成一定的例如 oled 显示的简单任务),从而完成 ROS 系统内部开源算法对整个机器人的控制。
+
+以下我简单介绍一下 ROS 的基础通讯方式:
+
+在裸机开发中,我们的通讯(不论是蓝牙、WiFi 还是最基础的串口通讯)本质上都来自于串口中断,也就是说裸机开发引入了 ISR 来处理相应的数据接收。ROS 通讯中同样需要这样的函数来完成对目标数据的处理,而在 ROS 中我们称之为回调函数。
+
+我们以话题通讯机制来做简要介绍,在此通讯中需要有两个节点,一个 Publisher(发布者)以及一个 Listener(订阅者),他们将发布和订阅同一个来完成相应的通讯-将发布和订阅的内容交给 ROS Master(整体流程类似于 WiFi 中的 mqtt 协议通信,将发布和订阅的内容交给公共服务器,形成一个转接的效果)。
+
+所以通过这样的一个流程,具体的代码如下(以 C++ 为例):
+
+```cpp
+//Publisher
+int main(int argc, char **argv)
+{
+ //首先我们要初始化节点
+ ros::init(argc, argv, "talker");
+ //其次,为了对这个类更好地进行处理,我们需要创建节点句柄
+ ros::NodeHandle n;
+ //创建一个Publisher=> pub,发布名为chat的topic,消息类型为std_msgs::String
+ ros::Publisher pub = n.advertise