🐞 fix(4.3人工智能导论及机器学习): 按照CS50AI课程note格式化文档

删除了大量“无辜”的无序列表
This commit is contained in:
E1PsyCongroo
2023-09-10 14:33:08 +08:00
parent 13d74be332
commit bbbe90dbe2
9 changed files with 791 additions and 637 deletions

View File

@@ -29,55 +29,58 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
## 背景
- 你如何生成一个填字游戏?考虑到填字游戏的结构(即网格中哪些方格需要填入字母),以及要使用的单词列表,问题就变成了选择哪些单词应该填入每个垂直或水平的方格序列。我们可以将这种问题建模为一个约束满足问题。每一个方格序列都是一个变量,我们需要决定它的值(在可能的单词域中哪个单词将被填入该序列)。考虑一下下面的字谜结构。
你如何生成一个填字游戏?考虑到填字游戏的结构(即网格中哪些方格需要填入字母),以及要使用的单词列表,问题就变成了选择哪些单词应该填入每个垂直或水平的方格序列。我们可以将这种问题建模为一个约束满足问题。每一个方格序列都是一个变量,我们需要决定它的值(在可能的单词域中哪个单词将被填入该序列)。考虑一下下面的字谜结构。
![4.3.4.2-1](static/4.3.4.2-1.png)
- 在这个结构中,我们有四个变量,代表了我们需要填入这个字谜的四个单词(在上图中每个单词都用数字表示)。每个变量由四个值定义:它开始的行(`i`值),它开始的列(`j`值),单词的方向(纵向或横向down or across),以及单词的长度。例如,`变量1`将是一个由第1行(假设从顶部计数的0索引)、第1列(假设从左边计数的0索引)、方向为`across``4`的长度表示的变量。
在这个结构中,我们有四个变量,代表了我们需要填入这个字谜的四个单词(在上图中每个单词都用数字表示)。每个变量由四个值定义:它开始的行(`i`值),它开始的列(`j`值),单词的方向(纵向或横向down or across),以及单词的长度。例如,`变量1`将是一个由第1行(假设从顶部计数的0索引)、第1列(假设从左边计数的0索引)、方向为`across``4`的长度表示的变量。
- 与许多约束满足问题一样,这些变量有一元和二元约束。变量的一元约束是由其长度决定的。例如,对于`变量1`来说,数值`BYTE`可以满足一元约束,但是数值`BIT`则不能(它有错误的字母数量)。因此,任何不满足变量的一元约束的值都可以立即从变量的域中删除。
与许多约束满足问题一样,这些变量有一元和二元约束。变量的一元约束是由其长度决定的。例如,对于`变量1`来说,数值`BYTE`可以满足一元约束,但是数值`BIT`则不能(它有错误的字母数量)。因此,任何不满足变量的一元约束的值都可以立即从变量的域中删除。
- 变量的二元约束是由其与相邻变量的重合度给出的。`变量1`有一个邻居:`变量2``变量2`有两个邻居:`变量1``变量3`。对于每一对相邻的变量,这些变量都有一个重叠部分:一个它们共同的单方块。我们可以将这种重叠表示为每个变量有用相同字符位置的索引对。例如,`变量1``变量2`之间的重叠可以表示为一对`(1, 0)`,这意味着`变量1`在索引1处的字符必然与`变量2`在索引0处的字符相同(再次假设索引从0开始)。因此,`变量2``变量3`之间的重叠将被表示为一对`(3, 1)``变量2`的值的字符`3`必须与`变量3`的值的字符`1`相同。
变量的二元约束是由其与相邻变量的重合度给出的。`变量1`有一个邻居:`变量2``变量2`有两个邻居:`变量1``变量3`。对于每一对相邻的变量,这些变量都有一个重叠部分:一个它们共同的单方块。我们可以将这种重叠表示为每个变量有用相同字符位置的索引对。例如,`变量1``变量2`之间的重叠可以表示为一对`(1, 0)`,这意味着`变量1`在索引1处的字符必然与`变量2`在索引0处的字符相同(再次假设索引从0开始)。因此,`变量2``变量3`之间的重叠将被表示为一对`(3, 1)``变量2`的值的字符`3`必须与`变量3`的值的字符`1`相同。
- 对于这个问题,我们将增加一个额外的约束条件,即所有的单词必须是不同的:同一个单词不应该在谜题中重复出现多次。
对于这个问题,我们将增加一个额外的约束条件,即所有的单词必须是不同的:同一个单词不应该在谜题中重复出现多次。
- 那么,接下来的挑战是写一个程序来找到一个满意的赋值:为每个变量提供一个不同的词(来自给定的词汇表),从而满足所有的一元和二元约束。
理解
那么,接下来的挑战是写一个程序来找到一个满意的赋值:为每个变量提供一个不同的词(来自给定的词汇表),从而满足所有的一元和二元约束。
- 这个项目中有两个Python文件`crossword.py``generate.py`。第一个文件已经完全为你写好了,第二个文件有一些函数留给你去实现。
## 理解
- 首先,让我们看一下`crossword.py`。这个文件定义了两个类,`Variable`(代表填字游戏中的变量)和`Crossword`(代表填字游戏本身)
这个项目中有两个Python文件`crossword.py``generate.py`。第一个文件已经完全为你写好了,第二个文件有一些函数留给你去实现
- 注意,要创建一个变量,我们必须指定四个值:它的第`i`行,第`j`列,它的方向(常数`Variable.ACROSS`或常数`Variable.DOWN``),以及它的长度(`length``)。
首先,让我们看一下`crossword.py`。这个文件定义了两个类,`Variable`(代表填字游戏中的变量)和`Crossword`(代表填字游戏本身)。
- 字谜类需要两个值来创建一个新的字谜:一个定义了字谜结构的`structure_file`(`_`用来代表空白单元格,任何其他字符都代表不会被填入的单元格)和一个定义了字词列表(每行一个)的`word_file`,用来作为游戏的词汇表。这些文件的三个例子可以在项目的数据目录中找到,也欢迎你自己创建
注意,要创建一个变量,我们必须指定四个值:它的第`i`行,第`j`列,它的方向(常数`Variable.ACROSS`或常数`Variable.DOWN``),以及它的长度(`length``)
- 特别要注意的是,对于任何一个字谜对象的字谜,我们都会存储以下的数值:
- `crossword.height`是一个整数,代表填字游戏的高度。
- `crossword.width`是一个整数,代表填字游戏的宽度。
- `crossword.structure`是一个二维列表代表字谜的结构。对于任何有效的第i行和第j列如果该单元格是空白的`crossword.structure[i][j]`将为真(必须在该单元格中填入一个字符),否则将为假(该单元格中没有字符要填)。
- `crossword.words`是一个包含所有单词的集合,在构建填字游戏的时候,可以从这些单词中提取。
- `crossword.variables`是谜题中所有变量的集合(每个变量都是一个Variable对象)。
- `crossword.overlaps`是一个字典它将一对变量映射到它们的重合处。对于任何两个不同的变量v1和v2如果这两个变量没有重叠`crossword.overlaps[v1, v2]`将是`None`,如果这两个变量有重叠,则是一对整数`(i, j)`。这对`(i, j)`应被解释为:`v1`的第`i`个字符的值必须与`v2`的第`j`个字符的值相同。
字谜类需要两个值来创建一个新的字谜:一个定义了字谜结构的`structure_file`(`_`用来代表空白单元格,任何其他字符都代表不会被填入的单元格)和一个定义了字词列表(每行一个)的`word_file`,用来作为游戏的词汇表。这些文件的三个例子可以在项目的数据目录中找到,也欢迎你自己创建。
- `Crossword`对象还支持一个方法`neighbors`,它可以返回与给定变量重叠的所有变量。也就是说,`crossword.neighbors(v1)`将返回一个与变量`v1`相邻的所有变量的集合。
特别要注意的是,对于任何一个字谜对象的字谜,我们都会存储以下的数值:
- 接下来,看一下`generate.py`。在这里,我们定义了一个`CrosswordCreator`类,我们将用它来解决填字游戏。当一个`CrosswordCreator`对象被创建时,它得到一个填字游戏的属性,它应该是一个`Crossword`对象(因此具有上面描述的所有属性)。每个`CrosswordCreator`对象还得到一个域属性:一个字典,它将变量映射到该变量可能作为一个值的一组词。最初,这组词是我们词汇表中的所有词,但我们很快就会写函数来限制这些域
- `crossword.height`是一个整数,代表填字游戏的高度
- `crossword.width`是一个整数,代表填字游戏的宽度。
- `crossword.structure`是一个二维列表代表字谜的结构。对于任何有效的第i行和第j列如果该单元格是空白的`crossword.structure[i][j]`将为真(必须在该单元格中填入一个字符),否则将为假(该单元格中没有字符要填)。
- `crossword.words`是一个包含所有单词的集合,在构建填字游戏的时候,可以从这些单词中提取。
- `crossword.variables`是谜题中所有变量的集合(每个变量都是一个Variable对象)。
- `crossword.overlaps`是一个字典它将一对变量映射到它们的重合处。对于任何两个不同的变量v1和v2如果这两个变量没有重叠`crossword.overlaps[v1, v2]`将是`None`,如果这两个变量有重叠,则是一对整数`(i, j)`。这对`(i, j)`应被解释为:`v1`的第`i`个字符的值必须与`v2`的第`j`个字符的值相同。
- 我们还为你定义了一些函数,以帮助你测试你的代码:`print`将向终端打印你的填字游戏的一个给定的赋值(每个赋值,在这个函数和其他地方,是一个字典,将变量映射到它们相应的词)。同时,`save`将生成一个与给定作业相对应的图像文件(如果你无法使用这个函数,你需要`pip3 install Pillow`)。 `letter_grid`是一个被`print``save`使用的辅助函数它为给定的赋值生成一个所有字符在其适当位置的2D列表你可能不需要自己调用这个函数但如果你想的话欢迎你这样做
`Crossword`对象还支持一个方法`neighbors`,它可以返回与给定变量重叠的所有变量。也就是说,`crossword.neighbors(v1)`将返回一个与变量`v1`相邻的所有变量的集合
- 最后,注意`solve`函数。这个函数做了三件事:首先,它调用`enforce_node_consistency`来强制执行填字游戏的节点一致性,确保变量域中的每个值都满足一元约束。接下来,该函数调用`ac3`来强制执行弧一致性,确保二元约束得到满足。最后,该函数在最初的空赋值(空字典dict())上调用`backtrack`,试图计算出问题的解决方案
接下来,看一下`generate.py`。在这里,我们定义了一个`CrosswordCreator`类,我们将用它来解决填字游戏。当一个`CrosswordCreator`对象被创建时,它得到一个填字游戏的属性,它应该是一个`Crossword`对象(因此具有上面描述的所有属性)。每个`CrosswordCreator`对象还得到一个域属性:一个字典,它将变量映射到该变量可能作为一个值的一组词。最初,这组词是我们词汇表中的所有词,但我们很快就会写函数来限制这些域
- 不过,`enforce_node_consistency``ac3``backtrack`等函数还没有实现(以及其他函数)。这就是你的任务
我们还为你定义了一些函数,以帮助你测试你的代码:`print`将向终端打印你的填字游戏的一个给定的赋值(每个赋值,在这个函数和其他地方,是一个字典,将变量映射到它们相应的词)。同时,`save`将生成一个与给定作业相对应的图像文件(如果你无法使用这个函数,你需要`pip3 install Pillow`)。 `letter_grid`是一个被`print`和`save`使用的辅助函数它为给定的赋值生成一个所有字符在其适当位置的2D列表你可能不需要自己调用这个函数但如果你想的话欢迎你这样做
最后,注意`solve`函数。这个函数做了三件事:首先,它调用`enforce_node_consistency`来强制执行填字游戏的节点一致性,确保变量域中的每个值都满足一元约束。接下来,该函数调用`ac3`来强制执行弧一致性,确保二元约束得到满足。最后,该函数在最初的空赋值(空字典dict())上调用`backtrack`,试图计算出问题的解决方案。
不过,`enforce_node_consistency`、`ac3`和`backtrack`等函数还没有实现(以及其他函数)。这就是你的任务。
## 明确
- 完成`grece_node_consistency`, `revise`, `ac3`, `assignment_complete`, `consistent`, `order_domain_values`, `selected_unassigned_variable``backtrack``generate.py`中的实现,这样如果有有解的话你的人工智能就能生成完整的字谜。
完成`grece_node_consistency`, `revise`, `ac3`, `assignment_complete`, `consistent`, `order_domain_values`, `selected_unassigned_variable`和`backtrack`在`generate.py`中的实现,这样如果有有解的话你的人工智能就能生成完整的字谜。
- `enforce_node_consistency`函数应该更新`self.domains`,使每个变量都是节点一致的。
- 回顾一下,当对每个变量来说,其域中的每个值都与该变量的一元约束一致时,就实现了节点一致性。在填字游戏的情况下,这意味着要确保变量域中的每个值的字母数与变量的长度相同。
- 要从一个变量`v`的域中移除一个值`x`,因为`self.domains`是一个将变量映射到数值集的字典,你可以调用`self.domains[v].remove(x)`。
- 这个函数不需要返回值。
- `revise`函数应该使变量x与变量y保持弧一致性。
- `x`和`y`都是`Variable`对象,代表谜题中的变量。
- 回顾一下,当`x`的域中的每一个值在`y`的域中都有一个不引起冲突的可能值时,`x`就与`y`保持弧一致性。(在填字游戏的背景下,冲突是指一个方格,两个变量对它的字符值意见不一)。
@@ -123,7 +126,7 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
- 如果有可能生成一个令人满意的字谜,你的函数应该返回完整的赋值:一个字典,其中每个变量是一个键,值是该变量应该承担的单词。如果不可能产生令人满意的赋值,该函数应该返回`None`。
- 如果你愿意,你可能会发现,如果你把搜索和推理交织在一起,你的算法会更有效率(比如每次做新的赋值时都要保持弧一致性)。我们不要求你这样做,但允许你这样做,只要你的函数仍然产生正确的结果。(正是由于这个原因,`ac3`函数允许一个`arcs`的参数,以防你想从不同的弧队列开始)。
- 除了要求你实现的函数外,你不应该修改`generate.py`中的任何其他东西,尽管你可以编写额外的函数和/或导入其他Python标准库模块。如果你熟悉`numpy``pandas`你也可以导入它们但是你不应该使用任何其他的第三方Python模块。你不应该修改`crossword.py`中的任何东西。
除了要求你实现的函数外,你不应该修改`generate.py`中的任何其他东西,尽管你可以编写额外的函数和/或导入其他Python标准库模块。如果你熟悉`numpy`或`pandas`你也可以导入它们但是你不应该使用任何其他的第三方Python模块。你不应该修改`crossword.py`中的任何东西。
## 提示