哈佛-CS50-计算机科学导论笔记-三-
哈佛 CS50 计算机科学导论笔记(三)
哈佛CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P5:L1- github操作 1(Git与GitHub基本操作) - ShowMeAI - BV1gL411x7NY
[音乐]。
好的,欢迎大家回到Python和JavaScript的网页编程课程中。所以下次我们看了两种语言,HTML和CSS,这两者都可以用来设计网页。HTML用来描述网页的结构,以决定我们希望在页面布局中包含什么内容,然后是CSS。
我们用来描述页面的样式,想要什么颜色以及周围的空间,今天我们将关注一个工具,随着我们开始开发这些网页应用,特别是我们将要查看一个叫做Git的工具,Git的作用是什么。
作为一个版本控制工具,虽然不一定仅限于网页程序,但我们将用它来开始开发更大、更复杂的网页应用,随着本学期的进展,Git将使我们能够做到的事情有几项。
首先,Git是什么,它是一个命令行工具,可以让我们跟踪代码的更改。曾经在我第一次开始开发网页应用时,我记得我在一个文件上工作,当我想进行更改时,我可能想要保存那个版本。
旧版本,所以我会复制文件,然后只对复制件进行更改,但这很快就会变得混乱,特别是当你在不同阶段有很多不同版本的代码时,跟踪它们就会变成一场噩梦,因此,Git将使我们能够做到。
跟踪我们对代码所做的更改,所以我们可能最初会创建一个文件,保存那个版本,但随着时间的推移,当我们进行更改、添加或删除内容时,我们可以保存各种不同部分代码在不同时间点的快照。
并且轻松引用我们对代码所做的所有更改。此外,Git将使我们在较大规模的网页应用中方便地同步代码,因为通常并不只是一个人工作在整个应用上,通常你是和同事一起工作的。
以及多个同事在同一应用程序上同时工作,这个过程的一个确实棘手的部分是找出如何在不同人之间同步工作。如果我在网页应用的部分做了更改,我希望确保我的同事也能看到。
这些更改,并获取我所做的最新更改,然后我想能够获取我正在合作的人的最新更改,所以我们需要。
一种同步所有内容的方法,而这将使我们能够做到这一点。我们将拥有一个版本的代码存储在我们所称的在线repository中,而我和我的合作伙伴(例如)都能够访问同一个。
repository中的相同文件处于相同状态。如果我和另一个人对这些文件进行更改,我们都可以进行更改,然后。
将它们同步回去,也就是说将更改推送到服务器上,以便服务器拥有最新的,最及时的代码版本。之后,在我们都将更改推送到服务器后,我们可以从服务器拉取更改,以便获取访问权限。
最新的代码版本,因此无论如何,只要我与我的合作伙伴在同一个项目上工作,我们可以使用Git来同步我们的工作,以便我能够访问同事的最新更改,反之亦然。Git还使我们能够执行许多。
例如,Git允许我们测试代码的更改而无需。
例如,想象一下,当我在编写程序时,可能会尝试做一些更改,但不确定这些更改是否有效,所以我想测试这些更改,但又不想失去对程序原始工作版本的访问。
如果出现问题,那么Git让我们能够这样做,它允许我们进行操作。
让我们在一个独立的分支上对代码进行更改,以便在我们满意之后,可以将这些更改合并回代码的原始版本,在我们确信这些更改是我们想要的之前能够测试这些更改。
Git提供给我们的一个强大功能是能够回退。
回到我们代码的旧版本,因此可以想象这样一种情况:我一直在处理一些代码,意识到目前的做法并不是我想要的。Git让我们能够说,最近的更改并不是我想要的,我想回到代码的先前版本。
Git使我们很容易回到之前的版本,因此今天的目标是学习如何使用这个工具,了解各种流行且常见的Git命令,以便在开发网页应用或其他与代码相关的项目时使用,因为版本控制。
控制确实提供了许多有用的功能,这些功能在你开始处理越来越大项目时非常实用,但最终,当我们开始处理这些 git 项目时,它们需要存储在某个地方,以便我们能够从任何地方下载,这样我和我的伙伴都能访问。
在同一个代码中一起处理同样的文件,因此为了做到这一点,我们需要将我们的 git 代码托管在某个地方,有许多不同的网站可以做到这一点,但其中一个最受欢迎的网站是 github。
一个存储 git 仓库的网站,所有的仓库就像是一个文件夹,里面保存了与我们的代码相关的许多代码和文件。所以我们将在一个叫做 github 的网站上托管这些代码,然后在我们的电脑上可以访问这些 github 仓库。
通过更改仓库内的文件来操纵这些仓库。所以我们先来看一下 github,看看我们该如何继续。
创建我们第一个 github 仓库,如果你还没有 github 账户,可以通过访问 github.com 免费注册一个账户。我现在就去 github.com/new。github.com/new 是我创建新 github 仓库时需要访问的页面。
让我们来看看我需要做什么来创建一个仓库。第一件事是给我的仓库命名,所以在这种情况下,我将这个仓库叫做 hello。你可以给它任何你想要的名字,只要这个名字不与你已有的其他仓库名称冲突。
github 选项让我为这个仓库提供一个描述,我就说使用 Python 和 JavaScript 的网页编程。然后 github 给我选择,我是否希望这个仓库是公开的,这样任何人都可以看到这个仓库。并不是每个人都能做出更改,但它是公开的。
如果有人想下载我的代码并试用,它是公开的,这意味着任何人都可以访问,或者是私有的,这意味着默认情况下只有我能看到这个仓库,但我可以选择是否让其他人也能看到,并且可以选择具体的个人。现在我就去做这个。
使这个仓库公开,然后我会向下滚动,点击绿色的创建仓库按钮,以便创建这个新仓库。所以我点击了创建仓库按钮,这就是 github 仓库页面。现在你会注意到这里有很多说明,但没有文件,因为目前我还没有。
我第一次创建 git 仓库时得到了一个空的仓库,里面没有任何内容。所以我现在想要做的是将这个仓库下载到我的电脑上,以便我可以添加一个包含我想要的 HTML 的 HTML 文件。
使用git来跟踪,所以我将如何做到这一点呢?为了做到这一点,我们将查看第一个与git相关的命令,它被称为git clone
,这是一个我们可以运行的命令,以从互联网获取一个代码库并下载到本地。
在我们自己的计算机上,所以你需要在计算机上安装git。
在任何Mac、PC或Linux机器上安装它,一旦你这样做,你要做的是在计算机的终端中运行git clone
。
由你尝试下载的git代码库的URL来标识,所以你可以想象,这里是你的计算机,而上面是某个服务器,那里有一个git代码库,GitHub就是这样的服务器之一,但还有其他服务器,上面是包含你关心的文件或其他文件夹的代码库。
如果我运行git clone
,后面跟着我想要的代码库的URL,效果就是代码库及其所有内容将下载到我的计算机上,以便我现在在我的计算机上有一个副本。
原本在那个git代码库内的所有内容,所以现在我们。
你需要知道如何克隆一个代码库,让我们实际尝试一下,我们刚刚使用GitHub创建了一个代码库,现在让我进入我的终端,实际尝试克隆这个代码库,这样我就可以在计算机上有一个副本,并可以开始进行一些更改。
我的lecture 1目录和第一个。
我需要的事情是git代码库的URL,所以如果我返回GitHub,你会注意到它给了我一个HTTP链接来下载我的git代码库,所以我有几种不同的方法可以用来克隆。
包括像用户名和密码这样的内容,我必须输入以证明这是我的GitHub凭证,或者如果你熟悉SSH,这是一种身份验证的另一种方法,你可以将公钥提供给GitHub以进行身份验证,但不需要。
如果你对这种技术不太熟悉,不用担心,关键是这个URL是对应我代码库的GitHub URL,所以我。
我将复制那个URL,然后在我的终端中输入git clone
,然后我会粘贴我想要克隆的包含该代码库的URL,然后按下回车,它显示我正在克隆到一个名为hello的目录,然后它说你似乎克隆了一个。
空仓库,它说这是一个警告,但没关系,因为我知道我克隆了一个空仓库,因为这个仓库是全新的。现在我可以在终端中输入 LS 命令,终端中的 LS 命令表示列表,实际上它会列出所有的。
当前在这个目录下的所有文件,所有在我的 lecture one 目录下的文件和文件夹,其中目录只是一个华丽的名称。现在我在 lecture one 目录下有一个名为 hello 的文件夹,而之前是没有的。我将进入这个 hello 目录。
要进入一个目录或文件夹,可以使用 CD 命令,CD 代表更改目录。如果我输入 CD hello,我现在就会进入 hello 目录。如果我输入 ls,你会看到这个 hello 目录现在是空的,因为这个仓库是空的,我克隆了它。
现在这个仓库里什么都没有,所以我想在这里实际添加一些内容。这个仓库只有在我跟踪我的代码以及我对代码所做的更改时才有用,所以我现在就来试着向这个仓库添加一些代码,第一件我做的事是。
创建一个新文件,我们可以通过打开文本编辑器来创建一个新文件,但在终端中实际上有一个用于创建新文件的命令,叫做 touch。所以在终端中,我可以输入 touch hello.html,这样会创建一个名为 hello.html 的新文件。如果我输入 ls。
我可以看到确实有一个文件,叫做 hello.html,现在在我的 hello 文件夹里。
让我现在在文本编辑器中打开这个 hello.html 文件。我再次使用 VS Code,现在让我给 hello.html 添加一些文本。我将添加一个简单的 HTML 页面,和我们之前看到的相同,其中我会给它一个标题,标题将是 hello,页面的主体部分将是。
hello world 同样的 HTML 页面我们已经看到好几次了,现在就在这个仓库里,当然我还没有对这个仓库进行任何保存,我还没有说我想要将这些更改保存到仓库中,并且仓库并没有跟踪我写的每一个字符,我需要告诉。
git 这是我当前文件的状态,我想要跟踪的内容。想要保存的内容,在 git 的世界里,我们称这些保存点为提交。当我说我要进行提交时,我的意思是我想保存所有文件、文件夹和其他资源的当前状态。
在仓库内部,基本上拍摄它们当前的快照。
位置,以便稍后我可以引用它们,但为了做到这一点,实际上有几个步骤,所以我们需要遵循的第一步是一个额外的命令。我们看到 git clone 是我们可以用来克隆仓库的命令,将一个仓库下载到我们的本地。
计算机接下来的命令将是一个叫做 git add 的命令,而 git add 将让我们告诉 git,我想添加一个文件,以便下次我保存时,下次我提交时,我想对所有这些文件拍摄快照。
以便我能够稍后引用它们,为此我需要告诉 git 需要跟踪哪些文件,所以例如我继续处理这个文件,我想告诉 git 我想跟踪它,我可以运行一个像 git add 的命令,后面跟着文件的名称,比如 food pie 或 dot HTML 或者其他的。
文件恰好是,然后会显示一条消息,说好的,现在我们已经添加了 food,这现在是一个下次我提交时会被保存的文件。那么这两个步骤为什么是分开的呢?一个原因可能是,如果我正在处理很多不同的文件,比如说我正在处理 10 个。
不同的文件,只有三个我满意,三个我想保存的文件。我不想只是说保存,并且让所有内容都被保存到一次提交中,我可能想说,这三个文件是我现在真正想保存的,而其他的文件我还在继续工作,所以 git 给我们这样的能力。
这种分离意味着让我明确表示我想跟踪这个文件,下次我保存,下次我提交时,而不是所有的文件。例如,尽管我们可以使用快捷方式,如果我们确实想添加所有文件,我们稍后会看到这些,所以让我们继续尝试一下。
我们回到我的仓库,我在这里创建了这个 hello dot HTML 文件,现在我想做的是说,我想将 hello dot HTML 添加到我的终端。我现在会再次说我这里有一个 hello dot HTML 文件,我会说 git add,后面跟着 hello dot HTML,你会注意到,到目前为止似乎没有发生什么。
因为到目前为止我还没有保存我刚才说的任何内容,我想添加 hello dot HTML 作为一个文件,这样下次我说保存时,我提交我的仓库时,它会跟踪我现在的更改。
制作 - hello HTML 那么我实际上是如何进行提交的,我如何说保存这些文件的状态呢?这将是一个更多的 git 命令,被称为 git commit。当我说 git commit 时,我会告诉我的 git 仓库我想保存当前状态的快照。
仓库跟踪对文件所做的任何更改。
我已经使用git add
添加了该文件,我们的运行方式是执行git commit
,后接-M
,然后用引号括起来的信息,这个信息被称为提交信息,它是对你在最近提交中所做更改的英文描述,或者使用你的语言。
随着时间推移,当你在一个大型项目上工作时,你可能会做很多提交,因为在对程序进行大量更改时,你会一次又一次地提交,每次对项目的新版本进行更改后,你可能想要参考之前的提交,但只有在你能识别出在哪个提交中进行了特定更改时,这样做才有价值。
在这种情况下,它告诉我已经对一个文件进行了九次插入,因为之前该文件并不存在,现在一个包含九行的文件存在了,现在我已经保存了hello。
例如,通过提供一些英文信息,给自己做一些备注,以便以后能回顾所有提交信息,并知道在这个提交时我做了什么更改,这样可以更容易地跟踪你对特定git仓库所做的所有更改。因此,当你输入git commit
后加上-M
时,你可能会包含类似“我添加了一行新内容”的信息,当你这样做时,git会保存你代码的一个新快照。
此时,保持旧版本或曾经存在的旧版本记录在仓库中。所以让我们尝试实际提交一次,看看这将如何工作。我们已经通过运行git add
添加了hello HTML文件作为一个需要跟踪的文件,但现在当我们满意时。
我们可以对文件进行额外的更改,如果我们想的话。我可以回到终端,输入git commit
,然后加上-M
,接着可以指定提交信息,一些英文描述我在这次最新提交中所做的事情。我所做的就是添加了hello.dot HTML文件。
我会说我添加了hello到HTML文件,这是我在最近提交中所做的更改。我会继续按下回车,这里告诉我有一个文件已更改,进行了九次插入,所以它在跟踪添加或插入的行数。
现在你可以想象,如果我返回到我的git仓库,在github网站上刷新,或许我会看到hello.dot HTML文件,刷新后,结果是,没什么变化,我没有看到我的hello.dot HTML文件。
因为在我的更改反映在线之前,我缺少最后一步,请记住,当我运行 git clone 步骤来克隆 GitHub 上的仓库时,GitHub 有一个版本的仓库,而我运行 git clone
来将该仓库的副本下载到我自己的电脑上。
我运行 git add
来添加 hello dot HTML 文件,或者我运行 git commit
来表示我想保存这些更改,我一直在对本地版本的仓库进行这些更改,实际上并没有影响到已经在 GitHub 上的内容,我所做的更改仅发生在我自己的电脑上。
如果我想将这些更改推送到 GitHub,那么我需要一些额外的命令,实际上我们可以使用一个名为 git status
的命令来查看我仓库当前发生的情况,git status
会告诉我们当前在我的仓库里发生了什么。
如果我在当前状态下运行 git status
命令,Git 会向我报告并告诉我我当前在分支 master 上,稍后再说分支的事情,但它说我的分支比 origin master 超前一个提交,因此这是一种啰嗦的说法,表明我的本地版本。
我电脑上的仓库版本比 GitHub 上的原始版本超前一个提交,也就是我拥有的一个提交是 origin GitHub 没有的,并且它友好地告诉我可以使用 git push
命令来发布本地。
git push
是我可以使用的一个命令,表示我想将更改实际推送到服务器,推送到 GitHub,以便它们在那里被反映。因此,在检查了当前状态之后,我们可以使用命令 git
。
推送时表示现在我所做的任何更改,在我运行 git push
时,这些更改会被推送到 GitHub,因此 GitHub 可以访问我现在所做的所有提交。所以让我们试试这两个命令,git status
查看我仓库当前的情况,然后 git push
表示我。
我想现在将这些更改推送到 GitHub,以便在线版的仓库与我自己电脑上的本地版本内容相同。好的,现在在我的终端中我可以运行 git status
,我看到我在分支 master 上,与之前相同,只是消息稍有不同,因为。
当前仓库里没有任何内容,但关键在于现在我可以运行 git push
命令,告诉系统将我对仓库所做的所有更改推送到 GitHub。因此,我会输入 git push
,接下来会发生的是,它会压缩所有信息。
要将其推送到 GitHub。
到这个网址,现在如果我返回到,github 网站 github.com 斜杠我的。仓库并刷新页面,我会看到,实际上我现在看到一些。不同的东西,这就是 github x 的样子!
用户界面实际上看起来像,它给我几条信息。
例如,它告诉我,目前在这个。仓库中有一次提交,就是我刚刚做的,那是在一个分支上,所以如果我只创建了一个分支,默认分支,但我们稍后会看到如何创建更多分支,特别是下面,你会看到当前的文件。
在这个仓库中存在的,当前我有这个 hello HTML 文件。就是我推送的那个,特别是旁边是提交。信息,最近一次我修改这个文件的,信息特别告诉我我添加了,hello HTML 文件。
提交影响 hello dot HTML 的内容,如果我现在不点击 hello。dot HTML 实际查看其内容,我会看到之前写入的相同内容,我看到,doctype HTML,然后是我们看到过几次的 hello world。页面,因此我在自己的文件上进行了更改。
计算机上,我现在已将它们推送到,github 所以它们现在在这个。仓库中,现在是公开的,以便,任何其他人如果他们想要。协作这个项目可以获取,这个 URL 克隆到他们自己的计算机。并在本地进行更改,因此现在我们可以探索我们。
也许我能对这个网页进行额外的更改,所以如果例如我想给,网页添加一个标题,比如我可能在。主体的顶部说些像。
在一个 h1 标签中,欢迎来到我的网站,现在如果我你知道,出于良好的措施。打开 hello dead HTML 看看是什么。
看起来这不是我网页的样子,而我已经对我的 hello dot HTML 文件进行了更改,这些更改还没有保存。我可以通过运行 git status 来判断,git status 是告诉你当前你在,仓库内发生了什么的去处,所以在这里我们看到有更改未。
已暂存以便提交,这是一个华丽的说法,指的是已更改的文件,但我还没有说我想要在下一次提交中跟踪它们,它告诉我我修改了 hello dot,HTML,但这不是 git 目前将要跟踪的东西。下次我保存时,所以如果我想保存 hello。
dot HTML 当我下一次提交时,首先需要运行 git add hello。HTML 然后我可以运行 git commit,但其实这里有一个简写,如果你想同时添加所有已经。更改的文件并提交,简写是 git commit - am 记得之前我们刚才。
使用 -M 来指定一个消息,-a.m意味着git提交所有已经更改的文件,a代表所有,并且还提供一个消息,因此你可以将git add步骤和git commit步骤合并成一个步骤,说明我想提交所有更改的文件,然后我会提供一个。
消息,我到底改变了什么,我添加了一个标题,我将按回车键,它记录了我现在更改了一个文件,并插入了一行,所有我做的只是向该文件添加了一新行,现在如果我运行git status,它会告诉我我在master分支上,并且我领先于origin/master。
origin master是github上的版本,增加了我这一提交。
添加一个标题提交,但现在在github上,如果我刷新这个页面,它仍然显示的是那个页面的旧版本,为了让我的更改生效。
在我的电脑上,确保它们在github上是最新的,我可以直接运行git。
推送,将这些更改推送到github,一旦完成,我现在可以刷新github上的页面,我会看到github现在拥有我的程序的最新版本,它现在有。
这个h1说“欢迎来到我的网站”,所以这就是git push,现在我可以说我想将我对Myatt仓库所做的更改推送到某个远程服务器,例如github上的远程服务器,但我们也可以反向操作,你可能会想象。
也许在github上的版本比我拥有的版本更新。
在我的电脑上,在这种情况下,我想下载当前存在于的最新版本的仓库。
github,为了做到这一点,我们可以使用一个叫做get pole的命令,怎么做呢?
当我运行git pull时,发生的事情是与git push相反。git push将我在电脑上的更改推送到github。git pull会说取回当前在github上存在的更改,并拉取最新的更改到本地,这样我和我本地的版本。
仓库可以获取当前在github上的所有代码的最新版本,我们可以举例演示,比如我回去看看github网站本身,因为在github上,我实际上有能力使用github界面编辑文件,所以我将模拟一下。
例如,其他人在这个项目上工作,也许其他人添加了一个第二个标题,他们添加一个h2,内容是“你好”,然后他们可以提供一个提交消息,这大致上是-dash M的图形等效物,以及我们之前提供的消息,他们可以说添加了h2。
然后提交,这是一种编辑 Git 仓库的方式,实际上是在 GitHub 界面内部进行添加和编辑。这使你可以直接编辑文件,添加或修改任何行,因此现在 GitHub 上的版本实际上与我们电脑上的版本不同。
如果我们查看 hello.html,我只看到 h1,而没有看到刚添加的 h2,因为这是一个我尚未拥有的更近期的提交。
访问,但是如果我想下载那个提交,我可以在我的终端中输入 git pull 来实现。
下载并更新了一个文件,进行了些修改,所以现在如果我回到文件,你会注意到我现在自动获得了最新版本的文件,现在有了这个说 hello 的 h2,因为我从中拉取了最新版本的文件。
通过组合 git push 和 git pull,我可以对我的代码进行更改,将其推送到 GitHub,并获得已在 GitHub 上的最新版本代码。但是在这个过程中,你可能会想象我们可能会遇到某种问题,特别是如果我一直在。
对我的代码进行更改,而其他人在我的同一个项目上工作。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P6:L1- github操作 2 (github冲突处理与分支) - ShowMeAI - BV1gL411x7NY
如果我们都对代码的同一部分进行了更改,然后尝试同步我们的工作,会发生什么呢?我们将会遇到某种冲突,因为我对同一行进行了更改,而我的同事也对其进行了更改。
这种类型的冲突称为合并冲突,当试图将我的更改与其他人的更改合并时。
其他情况让我遇到了一个情形,突然之间 git 不知道该怎么做,因为有两个不同的更改集,我们需要弄清楚如何解决这些冲突,以及在遇到这种冲突时该怎么做,所以如果我们真的遇到这种合并冲突,会发生什么。
如果我试图从其他地方合并或拉取一些更改时,通常会发生这样的情况。假设我运行 git PO,但有一些冲突的提交。
有一些在线内容与我当前版本的仓库冲突,我会收到一条消息,像这样,有一些冲突,表示某些文件的合并冲突失败,你需要解决这些冲突,然后提交结果。那么这些冲突可能是什么样的呢?通常是这样的。
git 会自动在文件中添加一些元数据,以描述它无法完全理解的内容,虽然信息看起来非常晦涩,但我们可以将其提炼为几个关键部分。所有内容在。
在这些箭头顶部和这里的等号之间是你的更改。
我在仓库的版本中所做的更改与其他一些更改发生了冲突,这些等号和下面的箭头之间的内容是远程更改,即我试图拉取的来自 github 的更改,这些更改以某种方式发生了冲突。
我目前正在处理的内容,以及这一串数字和。
这里的字符是冲突提交的哈希值,因此每个提交都有一个哈希值,它是一串数字和字符,可能是唯一的,有助于识别任何特定的提交,并且每次你进行提交时,git 会自动生成一个哈希。
提交,稍后我们将看到你如何查看所有可能的提交,但这里 git 只是很有帮助地告诉我们,这是导致冲突的提交,仅供我们参考。为了解决这个合并冲突,我们首先需要删除所有这些合并文件。
并决定我们想要的冲突解决方案,因此也许我想保留我的版本的更改,或者我想保留远程版本的更改,已经在GitHub上的更改,或者我想以某种智能的方式将它们结合起来。我作为程序员可以做出这样的决定。
我决定查看我的版本和冲突版本,并决定如何处理我。
为了解决这个冲突,我将删除任何空白行,然后提交更改,说明这是我。
我希望这个程序的合并版本看起来是这样的,现在让我们看一个合并冲突的例子,看看它是如何产生的,以及我们如何处理合并冲突。现在我在我的电脑上,打算对这个页面进行更改。
说到添加第二个感叹号,一个感叹号不够,我会。
在这个h1中添加第二个感叹号,我将继续提交这些更改,我会说git commit -am "add exclamation point"
,然后继续提交这些更改,我保存了这个程序的新版本,但我并没有。
我还不会推送代码,而是要模拟其他人在同一个文件上工作,也许GitHub上的其他人决定,你知道吗,对于这个h1,我们真的想做的是通过添加一些内联样式来给它添加样式,例如,将它的颜色设置为蓝色。
他们添加了一些CSS,我们将继续写一个提交消息,他们做了什么,他们添加了一些样式,我们将提交这些更改,现在我们创建的将是一个合并冲突,其他人在GitHub上已经。
在这一行进行了更改,将这个特定的h1标签的颜色更改为蓝色。例如,我同时也在同一行进行了更改,添加了一个感叹号,并且在添加行和删除行方面完全操作,因为我们都。
在同一行上进行更改的情况下,git会很难确定该如何处理。因此在我的终端中,我将运行git pull
,因为我想获取这些更改。当我这样做时,我会看到,好的,我收到这个消息,冲突,在hello.html中发生了合并冲突。
自动合并失败,因为通常情况下,git会尝试自动合并文件,但有时会。
现在我需要修复冲突,然后提交结果,所以让我们继续查看hello.html里面的内容,你会注意到里面有很多这些标记,而我的文本编辑器正好为我高亮显示它们,以便我能更清晰地看到,但这只是。
文本编辑器提供的高亮显示实际上并不是文本本身的一部分,但你会注意到所有这些箭头和等号。在这里是我的这行代码,代码行末尾带有额外的感叹号,下面是远程的。
相同代码的冲突版本,修改后的版本在GitHub上,而我现在正试图拉入的版本是,这个版本表明我们希望在这个特定h1元素的内联样式中使用蓝色。现在我需要做的是以某种方式弄清楚,如何将这两个合并在一起,我该怎么做。
想要解决这个冲突,在这个特定案例中,我可能会喜欢通过同时采纳两者的优点来解决这个冲突。如果在GitHub上,有人想要为这个h1元素添加一个样式属性,而我想要额外的感叹号,我可以做到两者,我可以继续添加一个额外的感叹号,然后。
摆脱我的版本,然后也去掉这些提交标记,所以继续。去掉它们,我基本上修改这个文件,直到我对它满意,直到我觉得好的,这就是我想要的解决冲突的方式。一个人添加了颜色,一个人添加了标点符号,解决它的方法是。
这个案例是同时使用它们,但这里就需要一些人类直觉。程序员并不是要查看这个文件并弄清楚,究竟我们想如何解决这个冲突,如何弄清楚如何将这些不同的更改合并在一起,但一旦我们。
满意之后,我们可以继续提交结果,我可以说git commit -m "fix merge conflict",好的,我们修复了合并冲突,现在如果我将这些结果推送回去。
当在GitHub上完成时,刷新页面,我现在在GitHub上看到的更新代码行带有内联样式的h1,和。
额外的标点符号因为我已经解决了合并冲突,然后我把这些信息也推送回了GitHub,还有其他一些git命令是非常有用的,我的意思是有很多,但我们现在会谈论几个,第一个是git log,如果。
如果你需要跟踪你对代码所做的所有更改,想要跟踪在这个特定库中所有的提交,你只需。
运行命令 git log
,它会输出一堆看起来像这样的信息。
就像这样,描述每一个提交,对于每个提交,它会告诉你提交哈希,以便你可以更轻松地引用,它会告诉你是谁进行了提交,也会告诉你该提交的日期,并且还会告诉你提交信息,这样如果你需要快速查看。
回顾一下,看看这个功能是在什么日期添加的,或者谁将这一部分添加到网页上,你可以通过查看 git log
,找到相关的提交,然后你就会知道是哪次提交。此外,如果你意识到自己进行了不想要的更改,并想回到之前的状态,这也会很有帮助。
对于上一个提交,如果是这种情况,你可以使用一个名为 git reset
的命令,它有许多不同的用法,但 git reset
实际上会。
获取当前状态的代码库,并将其恢复到一个较早的状态。
比如这个代码库,有几种方法可以使用它,像这样,你可以执行 git reset --hard
,这意味着硬重置,将一切重置回去,然后你可以插入一个提交哈希,所以 git log
,如你之前可能记得的那样,为你提供了每个不同提交的提交哈希,如果我想。
如果我想回到某个特定的提交,我可以说 git reset --hard
,然后是我想要的提交信息或提交哈希,之后我将返回到该提交。或者,我可以说类似 git reset --hard origin/master
,并回想一下,origin/master
是我的代码库的版本。
这是目前在 GitHub 上的代码库,因此如果我想将当前版本的代码库重置回 GitHub 上的版本,我可以使用类似这样的命令来实现,所以你运行 git reset
,后面跟一个提交哈希,这将把你代码库的当前状态重置回它之前的状态。
还有许多其他的 Git 命令也非常有用,特别是当你开始处理更大项目时,但这些是一些最有帮助的,也是你最常使用的命令,比如添加你想跟踪的文件并使用 git commit
。
我想保存当前所有文件的状态,推送和拉取以便能够上传和下载已更改的内容,以及一些有用的命令,如 reset
、log
和 status
,只是为了给你关于你的信息。
如果需要的话,可以让你回到一个较早的状态,但随着我们开始处理越来越多的项目,尤其是更复杂的项目时,你可能会发现,仅仅追踪一次次更改并没有你想象的那么强大。
所以我们可以探讨在一个假设的情况下可能发生的事情,比如你开始对我们的代码库进行一些更改。比如说,让我们想象你进行了第一次提交,做了一些更改,做了一些额外的更改,可能你意识到你想开始工作一个新特性。
你一直在工作的应用程序,所以你开始工作一个新特性。然后你继续在那个新特性上工作,但突然你意识到,原始代码中有一个错误,你想返回去修复那个错误,但现在我们处于一个棘手的境地。
我们想修复这个错误,但我们正在工作一个新特性。那么我们该怎么办?我们可以返回去尝试修复这个错误,但新特性又会发生什么?问题是,这个结构只是简单地变化,变化之后又变化,非常线性,只能一个接一个地进行。
一次又一次,通常在你工作一个项目时,它不会以非常线性的方式进行,你并不总是工作在一个紧接着前面的事情上,你可能在修复多个错误的同时工作在多个新特性上,你希望有某种方式来处理这些事情。
能够同时处理所有这些事情。
同时能够轻松地在它们之间切换,这就是分支派上用场的地方。分支是同时处理代码库不同部分的好方法。
所以你可能想象一个这样的情况,逐渐展开。
你进行第一次提交,开始做一些更改,做更多的更改,当你决定想要开始。
例如,针对一个新特性,而不是在同一个分支上一个接一个地进行更改,我可以创建一个新的。
分支,我可以分支出去,说“你知道吗,我们来创建一个新分支,开始在那里工作我们的新特性。”
然后继续在那个新特性上工作,如果我稍后意识到。
你知道吗,那里有一个错误,回到这个提交,我可以返回这个提交并创建一个新分支,在那里修复那个错误。现在我有两个不同的分支。
每一个可能有不同的代码,其中一个我在修复一个错误,另一个我在工作一个新特性。
新功能例如,通常每个分支都会有一个名字,所以主分支是你的默认分支,通常会包含最新稳定版本的代码,而当你在工作新的事情时,可能会有一些功能分支。
你在处理其他功能,例如,在任何时候,你的关注点只在这两个分支中的一个,关注点是你仓库的当前状态。
由我们称之为head的东西来指定,所以如果head指向master,那意味着你的仓库现在在这个修复bug的分支上,但你可以更改head,你可以。
切换你想查看的分支,你可以查看功能分支。说让我们看看那个分支,并开始在上面工作,你可以通过切换你的思维,开始在这些不同的分支上工作。切换从一个分支到另一个分支,然后再回来,只有当你。
确保你知道这个bug已被修复,并且这个功能处于令人满意的状态。
这样我们可以把这些变化合并回来,确保一切恢复正常。
合并到这个统一的主分支,这个主分支现在包含所有最新的代码。这就是git分支的真正力量,能够同时处理多个事情,并在不干扰主分支的情况下工作于某个功能。
代码的版本,所以现在让我们来看一个如何做到这一点的例子。在我的hello.html文件中,我在这个h1上添加了一些样式,添加了蓝色的颜色。假设我想做一些更改,我想。
因为我们之前决定,这对于这样的网页来说是稍微更好的设计,我可以立即进行这些更改,但如果我预期我可能会在多个更改上工作,我可以切换到另一个分支,分支出去做一些。
为了处理这些新变化,这里有一些关键命令需要了解,如果我输入git branch
,这将告诉我当前所在的分支以及在我的仓库中存在哪些分支。所以这里例如我输入git branch
,我看到我只有一个叫。
主分支和左侧的星号告诉我这是我当前所在的分支,如果我想查看新分支,我可以输入 git checkout,如果是新分支,我会输入 git checkout -b,然后是新分支的名称,我会称这个新分支为。
样式,因为我要对网页进行一些样式更改。例如,我输入 git checkout -b style,git 给我发了个消息,我已经切换到名为 style 的新分支,现在如果我再输入 git branch,你会看到我现在有两个分支,我有主分支,这是我最初所在的分支,现在我有样式分支,这是我现在所在的新分支,左侧的星号指示了这一点。
现在我在这个新分支上,我可以自由地进行任何我想要的更改,我所做的任何事情都不会搞砸什么。
在主分支上,只要我待在这个分支上,我就可以说好吧。让我们尝试删除这个样式,并在顶部添加一个样式标签,我可以说我希望我的 h1 颜色为蓝色。例如,我做了很多更改。
我现在想提交这些更改,我会说 git commit move style.properties,这是我所做的更改,但我只对样式分支进行了这些更改。如果我运行 git。
你会看到我当前所在的分支是样式分支,在这里我已移动。
将样式信息放到我页面的顶部,但我可以通过使用 git checkout 切换分支。git checkout 允许我在分支之间切换,我们用 git checkout -b 创建一个新分支,但如果你要切换到已存在的分支,我可以说 git checkout master,例如,来切换我的当前分支。
从样式分支到。
主分支,所以我运行 git checkout master,现在我在主分支上。如果我回到文件,你会看到我现在回到了内联样式。
如果我查看页面的头部样式部分,那里没有样式。
样式分支再次,文件立即回到,现在我在页面的样式部分有了样式代码,而不是内联样式,所以这些更改只在一个部分进行了。
页面,所以现在我会检查主分支。
再次,或许我想在主分支上做一些其他更改,也许我意识到我想删除这个。
额外的标点符号,你知道吗,两个感叹号太多了。我们现在只保留一个,我将提交这些更改,我会说git commit并去掉标点符号,现在我已经。
只从主分支移除了标点符号,因此这个主分支现在只有一个感叹号,但仍然保留了内联样式。因此,我现在想做的是合并我在另一个分支上所做的更改,我想取回我在样式分支上工作的内容。
将其合并到当前版本中。
在我的主分支上的仓库中,为此我们将使用的命令叫做git merge,因此git merge,注意我当前在主分支上,但如果我运行git merge,然后是样式,这将把样式分支上的内容合并到我当前的分支中,我们将。
我们发现几乎完成了,但现在出现了合并冲突。这并不总是会发生,在合并时,有时git会足够聪明,知道如果文件的一个部分有一个更改,而另一个部分有另一个更改,合并这些更改时就不会出现冲突。
git会自动解决这些合并冲突,但在这个案例中并非如此,因为我的样式分支和主分支都有更改。
对同一行代码的更改,我们稍后会看到原因。如果我在这里回溯,你会注意到在合并后的版本中,页面头部确实有这个样式标签,没有问题,没有冲突,因为只是添加了新行,所以没有冲突,冲突出现在这里。
在我的主分支版本中,我移除了这个标点符号,而在样式分支的版本中,我们可以通过“样式”这个词看到这一点。我们移除了内联样式,因此需要以某种方式解决这个问题。我最终的做法是去掉这些样式标记或冲突标记。
并且说,我希望更新版本中也没有。
不要有内联样式,也不要有额外的标点符号。因此,我现在已经做了这些更改,解决了合并冲突,现在我。
可以提交,我修复了合并冲突,这就是现在git分支的整体工作流程。当你在处理新事物时,可能会分支,以便说你想在这个网页应用程序的不同部分上工作,你会进行更改,进行提交,将更改添加到其中。
创建一个新分支,当你对这些更改感到满意并且它们处于你想要的状态时,你可以将它们合并回原始版本的仓库,有时你需要处理合并冲突,因此并不是总会发生,如果你在进行更改时小心翼翼,。
在进行更改时,尽量避免在两个不同地方对同一行代码进行修改,你可以降低实际发生合并冲突的可能性,因为 Git 最终在处理这些问题时相当智能,最后我们将查看一个。
GitHub 的几个功能在你开始进行更大项目时非常有用,这些项目往往涉及许多不同的部分。第一个功能是分叉(fork)一个 GitHub 仓库,所以让我们去一个 GitHub 仓库,例如查看 Bootstrap 的 GitHub 仓库。
Bootstrap 是我们上次查看的 CSS 库,它是一个让我们轻松访问各种不同 CSS 特性的库。
整个项目是开源的,这意味着 Bootstrap 的所有代码都是公开的,任何人都可以查阅,更重要的是,任何人都可以为其做出贡献。并不是只有一个人一直在维护 Bootstrap,而是一个社区驱动的仓库,许多人可以参与。
进行新功能的添加和对 Bootstrap 代码的修复,并利用 Git 的功能进行协作。因此,如果你发现一个希望贡献的 Git 仓库,或者如果你希望其他人能够为你的仓库做出贡献,有一件事情是。
你可以对该仓库进行分叉,分叉的意思是制作原始仓库的副本,所以在 GitHub 页面右上角有一个叫做“fork”的按钮,我们可以看到,目前大约有 68,000 人已经分叉了 Bootstrap 的仓库,制作了该仓库的副本。
登录自己的 GitHub 账户后,我们可以通过点击一个叫做“fork”的按钮,自己进行复制,从而获得该仓库的一个版本。
这样我们就可以克隆、推送和拉取,原因是 Bootstrap 的仓库虽然是公开的,但并不允许任何人直接推送,这可能不安全,因为如果世界上任何人都可以更新 Bootstrap 的主代码,那将是非常危险的。不过你可以复制代码并进行分叉。
你可以在自己的副本上进行更改,推送和拉取,当你觉得自己做出了可以贡献给 Bootstrap 的更改时,你可以打开一个叫做拉取请求(pull request),请求将你的代码合并到 Bootstrap 的代码中,我们可以看看 Bootstrap 的拉取请求示例。
请求选项卡现在看起来有 71 个打开的拉取请求。有 71 个人对 Bootstrap 的代码进行了修复或更改,你可以提交一个拉取请求,表示你希望将这些更改合并到 Bootstrap 的实际代码中。
在这个特定的代码库中维护 Bootstrap 代码的人可以审核这些拉取请求,提供反馈,要求进行额外的更改,然后当每个人满意时,他们可以将这些更改合并到 Bootstrap 的实际代码中。这是开源软件的一个关键优势,即有能力。
多个人可以在同一段代码上工作,社区能够合作寻找错误,搞清楚需要做出哪些更改,探讨如何改进现有代码库并使其在未来更好。值得注意的关于 GitHub 的最后一件事是一个。
被称为 GitHub Pages 的额外功能,GitHub Pages 是 GitHub 提供的一种免费方式,能够快速创建一个带有 HTML 和 CSS,甚至可能还有一些 JavaScript 的网站,并将其部署到互联网上供任何人查看。
任何拥有 GitHub 账户的人都可以免费创建 GitHub Pages 网站,为了演示这一点,我们现在来做。你在 GitHub 中需要做的就是创建一个新的代码库,通常我们称之为你的用户名.github,.是 GitHub 的约定名称。
页面网站尽管可以有其他名称,你只需手动启用。GitHub Pages,我们现在就来创建这个代码库。如果你创建一个名为你的用户名,github io的 GitHub 代码库,它将自动支持 GitHub Pages。
这意味着我可以拿这个网址并克隆它,我可以说 git clone,后面跟这个网址。我已克隆一个。
这是一个空的代码库,但我可以进入这个代码库并添加一些文件。我可以说,默认添加一个名为 index.html 的文件,我将创建一个 HTML 文件作为我的网站,正文将说这是我的 GitHub Pages 网站,像这样。
简单的东西,但如果你想让它更复杂,它当然可以。在我的终端中,我将添加这个 index study HTML 文件,然后我会进行一次提交,并在第一次提交中,你只需在提交信息中写上第一次提交即可。但我们知道这是第一次提交,然后我将把这些更改推送到 GitHub。
现在,如果你将更改提交到名为你的用户名的代码库,并且如果你查看设置并向下滚动,你会看到 GitHub Pages 默认是准备发布的。如果我点击这个网址,我的用户名 github do,你会看到。
部署到互联网,使任何人都可以访问这个URL并查看它。他们会看到一个大标题,上面写着“这是我的GitHub页面网站”,因为这是浏览器渲染我推送到GitHub页面仓库的HTML的方式,做这件事的好处是,现在非常容易。
能够快速更新我的网站,我只需在做出新更改时,可以提交该更改并将其推送到GitHub,当GitHub检测到我已推送到我的GitHub页面仓库时,它会更新我的网站,任何人都可以通过访问我的用户名GitHub来访问。
点和这让你能够利用所有这些功能,获得能力去分支,在不同时间处理网页的不同功能,并且能恢复代码的不同版本。所以,总的来说,Git给我们提供了许多强大的工具,赋予了我们能力。
现在能够非常快速且轻松地跟踪我们对代码所做的任何更改,跟踪代码何时更新,并迅速恢复并查看旧版本的代码(如有需要),尤其是它赋予了我们与其他人合作处理代码的能力。
这样我们可以在同一个项目的多个部分上工作,而其他人也可以在不同分支上在同一个项目上处理多个部分,并且很容易将我们的更改同步以便一起协作,因此Git是一个非常受欢迎的工具。
不仅在网页编程的世界中,特别是当处理任何较大项目时,多个可能同时在同一事物上工作的人,Git将使我们能够。
为了更轻松地开发我们的Web应用程序,在这个学期的下一次我们将看看Python,它是我们继续迈向构建更复杂Web的第一个编程语言之一。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P7:L2- Python编程语言全解 1 (变量,字符串格式化,条件与循环) - ShowMeAI - BV1gL411x7NY
好的,欢迎大家回到用 Python 和 JavaScript 进行网页编程的课程。今天我们将关注这门课程中我们将要学习的两种主要语言之一,特别是 Python。Python 是一种非常强大的语言,使构建应用程序变得非常简单。
今天的目标之一是向你介绍 Python 编程语言,如果你以前没有见过,即使你见过,也要让你更熟悉它。
给你一个对这门语言的体验,探索一些更高级的特性和我们可以使用 Python 开发应用程序的技术,让开发变得更有效。因此我们将从我们的第一个 Python 程序开始,只是一个简单的程序,输出 hello world,我们将要快速进行,因为语言中内置了许多功能,使得快速和高效的开发变得方便。
程序看起来就像这一行,如果你以前使用过其他编程语言,比如 C 或 Java 或其他语言,这可能在语法上看起来很熟悉,但为了简单说明,我们有一个内置的 print 函数在 Python 编程语言中。
在许多其他编程语言中,Python 的函数在括号内接收参数,因此在这些括号内是传递给 print 函数的参数或输入,在这种情况下就是单词 hello world,后面跟着一个感叹号,所以这就是我们实际可以采用的方式。
我将进入我的文本编辑器,创建一个新的文件。这个文件我将命名为 hello dot P Y dot py,或者 dot pi 是 Python 程序的常规扩展名,因此我将创建一个名为 hello dot PI 的文件,里面将包含我们刚才看到的 Python 代码,我们会调用 print 函数,并作为参数或输入传递给 print 函数的内容是 hello world。感叹号。
在我们的终端中这个程序恰好也叫 Python。Python 可以被称为一种解释型语言,这意味着我们将运行一个名为 Python 的程序,这是一个解释器,它将逐行读取我们的 dot PI 文件,执行每一行并解释其内容。
这意味着以计算机实际上能够理解的方式,因此我们将运行 Python,在这种情况下我们将解释 hello depay,当我们运行这个程序时,我们看到单词 hello world 被打印到终端,这就是全部,这就是程序的结束。!
这是我们使用 Python 编程语言编写的第一个程序,因此我们已经看到了 Python 的几个特性,能够解释 Python,无需先将其编译成二进制文件才能运行 Python 程序,我们已经看到了函数。
我们也见过字符串,字符串就是我们可以用引号提供的文本,作为输入提供给其他函数或以其他方式进行操作。稍后我们会看到一些字符串操作的示例。像许多其他编程语言一样,Python 也支持变量。
为变量赋值的语法看起来像这样,如果我有一行 a 等于 28,这意味着取值 28 并将其赋值,存储在名为 a 的变量中。与 C 或 Java 等其他语言不同,在这些语言中你必须明确声明。
定义每个变量的类型时,你需要像 int a 这样说明 a 是一个整数,而 Python 不要求你告诉它这些变量的实际类型。因此,我们可以直接说 a 等于 28,Python 知道这个数字是一个 int,它将代表。
将变量 a 定义为 int 类型,并且它能够推断这些值的类型,所以所有的值确实都有类型。你只需不明确地声明它们,例如这里的数字 28 是 int 类型,它是一个整数,而像 1.5 这样的数字则带有小数。
浮点数在 Python 中我们可能称之为 float 类型,任何文本类型,例如用双引号或单引号括起来的单词 hello,Python 支持两者,这种类型我们称之为字符串(string)类型。我们也有布尔类型。
在 Python 中,有很多不同的值可以是真或假,这些值用大写的 T(true)和大写的 F(false)表示,类型为 bool。我们在 Python 中还有一种特殊类型称为 none 类型,它只有一个可能的值,即大写的 n(none),当我们想表示 none 作为一个值时会使用它。
表示没有值的情况,因此如果我们有一个函数,如果不返回任何内容,它实际上返回的是 none。因此你可能会想象,当你想让一个变量表示某事的缺失时,none 是很有用的。
类型还有更多,这里只是一些可能存在于该语言中的变量和类型的示例。现在,让我们尝试在程序中实际使用一个变量,以便做一些更有趣的事情,我们将编写一个能够执行的程序。
从用户那里获取输入以便向他们说hello,例如,我会创建一个新文件,我们将其命名为name.py,输入,我想提示用户例如输入他们的名字,那么我们该怎么做呢?就像Python内置的print函数,它只是打印出任何参数。
恰好Python还有一个内置函数叫做input,它提示用户输入并要求他们输入一些内容,所以让我们提供一些输入并要求用户输入他们的名字,例如,然后我们可以将该函数的结果保存到一个变量中,在这种情况下。
我会将其保存在一个变量中,这在这种情况下也正好是。
叫做name,现在我们可以运行程序,我可以通过进入终端并输入python name.py来运行程序,我会按回车,然后我们会看到程序提示我输入我的名字,我看到name,冒号空格,这就是我提供给输入函数的字符串,现在这会提示我输入我的名字,所以我会。
之后似乎什么都没有发生,到目前为止,所以现在我想做点什么。
用我输入的名字,我想对自己说hello,例如,所以我会回到这个程序中,现在我可以说print,hello,逗号,然后我可以说plus name,这个plus运算符在Python中有很多不同的功能,如果我有两个数字,它会将这两个数字相加。
一起但用两个字符串,plus实际上可以连接或组合两个字符串,所以我可以将hello,逗号空格与无论是什么值的结合。
name恰好就是这样,所以现在我会重新运行这个程序,Python命名为PI,输入我的名字,然后。
我们看到hello Brian作为程序的输出,所以这是在Python中操作字符串的一种方式,另一种在后来的Python 3版本中相当流行的方法被称为使用f字符串,f是格式化字符串的缩写。为了在Python中使用f字符串,方法会类似但略有不同。
不同的语法而不仅仅是将字符串放在双引号中,我们会在字符串前加上字母F,并且在字符串内部我现在可以说hello,逗号,然后如果在格式化字符串中,如果我想插入一个变量的值,我可以通过在花括号中指定它来做到这一点,所以我在这里要说的是。
在花括号内的name,因此,这里发生的事情是我在告诉。
这个格式化字符串用来替换变量的值,所以我提示用户输入他们的名字,我们将他们的名字保存到一个名为name的变量中,现在在这条打印语句的第二行中,我打印出一个格式化字符串,它是hello,逗号,然后在。
这里的花括号表示插入变量name的值,因此这将产生将输入的名字打印出来的效果,这是一种稍微更有效的方式,能够快速创建字符串,通过将值插入这些字符串中。
如果我运行Python命名为pi,然后提示输入我的名字,我输入“Brian”,然后我保存。
结果是“你好,Brian”,例如,所以这些是我们处理字符串的一些方法,操作字符串和组合字符串,使用这种技巧。此外,Python也支持许多过程式编程语言的核心特性。
例如,让我们现在来看一个例子,看看一个数字是正数、负数还是零。所以我将创建一个新的文件,称为conditions.py,接下来在conditions.py内部,我会首先提示用户输入一些内容,我会说“输入一个数字”。
我们将把这个输入保存在一个我将称之为n的变量中,现在我可以问问题,比如说“如果n大于零,则打印出‘n是正数’”。在这里发生的事情是我有一个Python条件,而Python条件的工作方式是以这个关键字开头。
关键字“if”后跟一个布尔表达式,某个将评估为真或假的表达式,或者有点像真或假的表达式,我们可以对这一点稍微放松一下,然后冒号表示好,这里是if语句主体的开始。
Python中,我们知道自己在if语句的主体内或任何其他代码块的主体内,是通过缩进来区分的。在一些语言中,比如C语言或HTML,这些语言在几节课前提到过,缩进不是计算机解析所严格要求的。
在Python中理解程序内部的内容是不同的,缩进是必需的,因为缩进是程序知道什么代码在if语句内,什么代码在if语句外的方式,所以我们有“如果n大于0,冒号”,然后所有缩进在if下面的内容都是。
所有遗产的主体是“人”,如果这个布尔表达式和大于零的条件为真,则会执行这些代码行。所以如果n大于0,我们将打印出“n是正数”,然后我们可以添加一个额外的条件,我可以说,比如说“否则打印n不是”。
积极的,但我可以更具体一些,这里大致有两个分支,一个是当 n 大于 0 时,另一个是处理其他所有可能情况的 else 分支。但我真正想做的是执行第二次检查。在其他语言中,这可能被称为 else if,比如如果这个条件不满足。
但另一个条件是成立的,Python 将此缩写为 just lf-ii。LIF 是 else if 的缩写。所以我可以说,如果 n 小于 0,那么我们就继续打印 n 是负数,else 打印 n 是零。因此,现在的想法是,如果 n 大于零,我们执行某些任务,L 如果在其他条件下。
如果它不大于零,那么我们检查它是否小于零。在这种情况下,我们打印出 n 是负数。否则,如果这两个条件都不成立,那它既不是正数也不是负数,唯一剩下的可能性是 n 等于零,所以我们可以打印出 n 是零,我们可能希望这个程序能正常工作。
但是注意如果我现在尝试运行条件,即使在我们脑海中逻辑上看起来可能很合理,如果我运行 Python 条件并输入一个数字,我将输入数字五来看看会发生什么。
好吧,发生了一些奇怪的事情,这是我们第一次遇到 Python 异常,错误发生是因为我们的 Python 程序内部出现了问题。随着时间的推移,你将开始学习如何解析这个异常,理解它的含义,以及从哪里开始调试,但学习。
如何读取这些异常并弄清楚如何处理它们,绝对是你成为 Python 开发者过程中一个非常宝贵的技能。那么我们来看看能否搞清楚这个异常在说什么。通常我先看底部,我看到那里有一个类型错误,即类型错误。
发生的异常有很多,Python 中有很多事情可能出错,我们可以做的事情会导致错误。在这种情况下是类型错误,通常意味着存在某种类型不匹配,Python 预期某个东西是某种类型,但它实际上是另一种类型。
类型,所以让我们尝试理解这可能是什么,我们说大于符号不支持在字符串和整数实例之间进行比较。那么这意味着什么呢?我想这意味着大于符号在检查一个东西是否大于另一个东西时,如果你在比较字符串和整数时就不成立。
整数,这大概是合理的。说一个字符串大于或小于一个整数并不合逻辑。当我们谈论大于或小于时,通常是在谈论数字,因此它们都应该是整数。例如,为什么我们认为大于在比较时是成立的。
一个字符串和一个整数,现在我们可以,再往上看一下追踪,它将显示哪些部分。代码导致这个问题,在这种情况下,追踪相当短,只是。指向一行单个文件,说明在文件conditions,PI的第3行这是那一行。
触发了异常,如果n更大。
比0那么这里的例外是什么呢,0显然是一个整数,因为那。就是一个整数,因此如果更大,则认为它是在比较一个字符串。和一个整数,那么n某种程度上必须是一个。
字符串即使我输入了数字,5,你仍然必须认为它是一个字符串。那为什么会这样呢?让我们再看看代码,看看能否。弄清楚发生了什么,似乎这个输入函数不在乎。你输入了什么,它总是会,返回一个字符串,并且某种程度上是。
最终成为一个字符串,这是相当合理的,因为输入函数。并不知道我是否输入了数字,或者我是否输入了字母,或者我。输入了完全不同的字符,某些输入不知道以什么形式返回其。数据,无论是以int的形式,float的形式,还是以任何其他形式。
默认情况下,它只是返回一个,字符串用户输入了什么字符。作为他们的输入,所以我现在想要做的是,为了让这个程序按。我的意愿工作,将其转换为一个整数,或者可以说转换为一个整数,方法是使用一个。
在Python中有一个函数叫做int,它接受,任何内容并将其转换为整数。因此在这里我可以说int,然后作为,int函数的参数输入。nth函数,我只需包括这个完整的表达式input。number,所以我要请求用户,输入一个数字,他们输入一些文本。
输入函数返回给我一个,字符串,而该字符串将作为int函数的输入,然后被保存到这个变量中。
被称为n,因此现在我们知道n确实是一个整数,让我们再试一次。运行这个程序,我回到终端,运行Python conditions PI。系统让我输入一个数字,我输入一个,像5这样的数字,好的,这仍然。似乎没有工作,它没有工作是因为我没有保存。
文件,所以我去保存文件,再试一次,输入一个数字,。我们看到确实n是正数,我们没有更多的异常,我们成功运行了。代码,并看到n的值是正的,我可以再试一次,测试其他两个。条件分支,输入负数。
例如,看n是,负数,否则如果它不是。
正或负,那么我们知道 n 是零,因此这里是我们第一次接触 Python 中的条件,能够有多个不同的分支,根据我们要评估的某些表达式执行不同的代码,这些表达式要么是真实的,要么是假的。好吧,让我们。
看一下 Python 语言中将会出现的一些其他特性,其中一个最强大的特性是它各种不同类型的序列,这些数据类型以某种顺序或某些值的集合存储值,所以我继续。
创建一个新的文件,我们称之为 sequences pi,并且有许多不同类型的序列都遵循类似的属性,但其中一种类型的序列是我们已经看到过的一种类型,比如一个字符串。例如,如果我有一个名字,名字是哈利之类的。
这个序列让我可以访问序列中的单个元素,为了做到这一点,它很像其他语言中的数组,如果你之前有接触过它们,但我可以打印出 name[squ*re bracket 0],这个方括号表示法获取一个序列,一些有序的元素。
让我访问该序列中的一个特定元素,因此如果我有一个字符串,例如 name,我说 name[squ*re bracket 0],那么这个效果就是获取这个长序列中的零个元素。在许多编程语言中,以及更一般的编程中,我们通常从 0 开始计数。
在 0 处,所以序列中的第一个元素是项 0,第二个元素是项 1,所以很容易出现轻微的越界错误,但只需知道。
那么 name 的项 0 应该是名字中的第一个字符,如果我运行 Python,我可以确定这一点,我会保存这个文件,运行 Python sequences PI。我得到的只是哈利名字的第一个字符,正是这个。
情况是字母 H,如果我请求打印出。
字符 1 是名字中的第二个字符,如果我现在重新运行程序,我会得到字母 A,这种索引类型适用于许多不同类型的序列,而不仅仅是一个。
字符串恰好是一个字符的序列,但还有其他类型。Python 例如有一个用于数据列表的类型,因此如果我有一个我想存储的任何类型数据的序列,我可以将该信息存储在 Python 的列表中。所以也许我不是存储一个名字,而是有多个名字。
我想存储一些名字,比如哈利、罗恩和赫敏。例如,现在我有三个名字,都存储在一个 Python 列表的序列中,我可以打印出所有的名字,举个例子。
看一下变量名称的值是什么,我们会看到当我这样做时,我得到了内容的打印输出。
那个列表是哈利、罗恩和赫敏按特定顺序排列的,但你也可以像索引字符串那样索引列表,以获取特定的内容。
这个名称列表中的第一个项目,当我运行程序时将会是哈利,因此,有许多不同的序列类型可以用来表示。
另一个数据结构叫做元组,通常用于存储一对不变的值,比如两个值在一起或三个值在一起,想象一下,如果你在编写一个程序来处理实际上是一个单位的情况,它恰好有两个部分。
例如,在二维图形中,你可能想将一个点表示为X值和Y值,你可以为此创建两个变量,我可以说,坐标X将等于十点零,坐标Y将等于二十点零,但现在我为此创建了两个变量。
为了表示这个,我们可以在Python中使用元组,像这样说:坐标等于十点零,二十点零。而在列表中,我们使用方括号来表示列表的开始和结束。
在元组中,我们只需使用括号,表示我们正在将多个值组合在一起,组合一个值十点零和第二个值二十点零,现在我们可以将这两个值作为一个单元传递,只需用一个单一的名称来引用它们,这里是坐标。
有许多不同类型的这些各种序列,我们将查看的一些序列是这些数据结构。例如,列表是可变值的序列,我们已经看过,而可变值意味着我们可以改变列表中的元素。
在列表中,我可以在列表的末尾添加某个元素,可以从列表中删除某个元素,可以修改列表中的值,而元组则是一系列不可变值,这些值不能更改,你不能向现有的元组添加另一个元素,你必须创建一个新的元组。
还有其他数据结构存在,我们将在稍后查看的一些包括集合,这是一组唯一值的集合,因此如果你熟悉数学中的集合,这个概念非常相似。而列表和元组则保持内容的特定顺序。
集合不会保持任何特定顺序,它只是一个集合,所有的值需要是唯一的,在列表或元组中,你可能会有相同的值多次出现,而在集合中,每个值只出现一次,这给集合带来了一些优势以及你可以使用的一些方式。
如果你知道只需要一个集合,而不在乎顺序,如果某个元素最多只会出现一次,那么通过使用集合可以使你的程序更高效,更优雅。
设计的最后一种数据结构是相当强大的,在本课程中会多次出现的字典,在 Python 中简称为 dict,它是我们所称的键值对的集合,我喜欢用一个实际的物理例子来思考这个。
字典就像你在图书馆找到的那样,将单词映射到它们在纸质字典中的定义,你打开字典,查找一个单词,得到它的定义,而在 Python 中,字典的工作方式非常相似,它将是一个数据结构,我可以查找某些东西。
通过一个关键词或一个值来获取另一个值,我们称我正在查找的东西为键,称我查找时得到的结果为值,因此我们在实际字典的情况下保留键和值的配对,在现实世界中,键是我们想要查找的单词。
值是它的定义,但我们可以在 Python 中更一般地使用它,每当我们想要将某些东西映射到另一个值时,这样我们就可以很容易地在这个数据结构中查找该值,因此我们将看到字典的例子,所以现在让我们探索这些数据中的第一个。
这些结构是我们通过利用 Python 列表所提供的特性来探索我们可以做的事情,例如,我们将创建一个新程序,我将其命名为 lists.py,在这里我只是创建一个名称列表,所以 names = Harry Ron Hermione 和 Ginny。
举个例子,当我开始写多行代码,特别是当我的 Python 程序变得越来越长时,记录我所做的事情可能是个好主意,所以我可以说让我为这一行代码添加一个注释,这样我就知道在这行代码中做了什么,而在 Python 中。
创建注释有几种不同的方法,但最简单的方法就是使用井号或哈希标签,只要你包含它,之后该行的其余部分都会被解释器忽略,这些注释你可以随意写,这更多是为了你自己。
程序员,以及阅读你的程序的人能够查看程序,理解它所表达的内容,并弄清楚他们需要对其采取的行动。因此,我可以说定义一个名字的列表,例如,仅仅为了让我清楚我在这行代码中做了什么,以便我可以打印出来。
名字列表,就像我们之前做的那样。
我们会看到,当我打印出这个名字的列表时,我得到的是,让我运行这个列表,我得到的是这个列表哈利。
罗恩、赫敏和金妮,但我也可以像之前看到的那样,打印出其中第一个,妈妈说,你知道的,只打印出名字方括号零,这样我将只得到哈利,例如,但现在请回忆一下,列表是可变的,我可以修改这个列表中存在的元素。
所以我可以说 names.dot.append 一个新名字,例如,Rico,因此列表有许多内置的方法或函数,这些是我可以在现有列表上运行的函数,以访问特定的元素或以特定的方式修改列表,在列表的情况下,append 方法是一个。
方法或函数,我可以运行,它只是将一个值添加到现有列表的末尾,所以我已经将德拉科添加到列表中,还有许多其他方法可以在列表上使用,其中之一,例如,排序一个列表,没必要编写自己的排序算法,以对一系列对象进行排序。
在 Python 中,有一个内置的 sort 方法,适用于列表,我可以简单地说 names.dot.sort,这将自动对列表中的所有内容进行排序,现在如果我打印出所有这些名字,去打印它们并去掉。
这个旧的打印语句,现在我们看到打印出了五个名字,因为我在这个列表中原本有四个元素,但我添加了第五个,并且注意到它们实际上是按字母顺序排列的,从德拉科开始,到罗恩结束,因为我能够通过修改顺序来对列表进行排序。
这些元素实际出现的顺序,因此列表在任何需要存储的情况下绝对可以非常强大。
按顺序的元素,列表绝对是 Python 提供的一个有用工具,如果你不在意元素的顺序,而且如果你知道所有的元素都是唯一的,那么你可以使用集合,这是一种另一种 Python 数据结构,工作方式类似,语法稍有不同。
不同,所以让我们做一个例子,创建一个新文件,命名为。集合 pi,让我首先创建一个空的集合,我们可以通过简单地说 s 来实现。s 将是一个变量,它将存储我的集合,我会说 set,然后是括号,这样就会创建一个空的集合,正好是。
现在集合里是空的,我们来添加一些元素,可以说s.add
,让我们把数字一、二、三和四添加到集合中,然后我们可以打印。
打印集合,查看现在里面有什么元素,当我运行这个程序时,我们看到集合中有四个值,一、二、三和四。
集合没有自然的顺序,它们不会总是保持特定的顺序,但我可以添加,比如如果我再次将三添加到集合中。
我两次将三添加到集合中,添加了1、2、3、4,然后又添加了3。当我打印集合的内容时,它仍然只包含元素1、2、3和4,集合中从不出现重复的元素,这遵循集合的数学定义。
集合中的元素不会出现多次,你也可以从集合中移除元素。如果我想要移除数字二,比如可以说s.remove(2)
,然后打印s
来查看当前的集合状态。
现在集合中包含的所有元素,当我重新运行这个程序时。
在程序中我只得到了1、3和4,因为我从集合中移除了2,所以集合允许你向其中添加、移除元素,所有序列,无论是字符串、列表还是集合,都可以通过Python内置的函数len
来获取集合中有多少元素。
一个序列的长度,即列表中的项目数量、字符串中的字符数量或集合中的元素数量。如果我想打印集合中有多少个元素,我可以在格式化字符串中这样做,比如说这个集合有几个元素。
元素数量,我怎么知道有多少个元素呢?在这些花括号内,我可以包含任何我想在Python中替换进这个字符串的表达式,那么集合中有多少个元素,我可以得到。
通过计算s
的len
,我这里做的是我希望计算集合s
的长度,换句话说,实际包含在这个集合中的元素数量,然后使用这个花括号表示法,我说把这个数字插入到这个字符串中,这样我们可以看到集合。
包含了一些元素,比如如果我运行这个程序,Python 会告诉我集合中现在有三个元素,即一、三和四,并且它会告诉我这个集合有三个元素。
现在我们看到集合中元素的数量。所以现在我们已经看到Python中的一些不同语言特性,我们看到变量,看到条件,以便我们可以额外执行某些操作,如果某事为真,如果其他事也为真,我们还看到了一些数据结构。
Python的核心工作方式涉及列表、集合、元组和其他数据结构,这也很有帮助。现在,让我们看看Python编程语言的另一个特性,这是许多编程语言共有的,循环的概念。如果我想多次做某件事,现在可以继续。
创建一个名为loops pi的新文件,让我们创建一个简单的循环。我们可以在Python中创建的最简单的循环就是数一些数字,因此,为此我可以说像这样:for I in 1 2 3 4 5,或者也许我想从0 1 2 3 4 5开始计数。
从0开始打印I,所以这是Python循环的基本语法,这里发生的事情似乎在第一行,我有一个由方括号表示的Python列表,包含6个数字0 1 2 3 4 5,现在我有一个for循环for I in这个列表,Python的解释方式是说。
如果我逐个遍历这个列表,对每个元素调用该元素I,我们可以把它叫做任何名字,但在这种情况下,I只是一个约定的选择,用于表示一个不断递增的数字。我们将打印出每次循环迭代中I的值,所以我们可以尝试。
现在试一下并运行Python循环pi,我们看到1 0 1 2 3 4 5,太好了,它逐个打印出了从0到5的所有数字。在实际操作中,如果我们想要数到5,或者打印六个数字,这样做是可以的,但如果我们想打印像一百个数字或一千个数字。
这开始变得有些乏味了,所以,Python有一个内置函数叫做。
range,我可以说for I in range 6,以实现完全相同的效果。六意味着让我得到六个数字的范围,所以如果我们从零开始,它会从零一直到五,然后我们可以打印出该序列中的每个元素。
重新运行Python循环top I,我们得到0 1 2 3,4 5,因此循环使我们能够遍历任何类型的序列,所以如果序列是一个列表,我可以说如果我有一个名字列表,比如哈利、罗恩和赫敏,这就是我的名字列表。我可以有一个循环说对于我名字列表中的每个名字,让我们打印出。
这个名字,例如,我们有一个名为names的列表,我们正在循环。
一次遍历一个元素,并打印出来,现在如果我运行程序。看到三个人名打印出来,每行一个。
这条线,你也可以对其他序列执行此操作,也许我只有一个名叫哈利的。单一名称,现在,我可以有一行代码说,对于这个名称中的每个字符,打印出该字符,如果名称是这样的,序列就是一个个体字符的序列,因为它是一个字符串。
当我遍历那个字符串时,我会。
在这个字符串中逐个循环每个字符,所以我可以运行程序。看到每一行打印出每个字母,恰好是。
在哈利的名字中,现在我们已经看到了条件,看到循环,并且看到多种不同的数据结构,我们看到了列表和集合,还有元组,最后一个重要的数据结构我们将要查看的是Python字典,正如你会记得的,它们是一种映射。
如果我想查找某些东西,我可以使用Python字典作为一种数据结构,来存储这些值。所以我会创建一个新的文件,叫做dictionary.py,也许我想创建一个字典,用来跟踪每个。
在霍格沃茨的学生恰好在,所以我可能会有一个叫做houses的字典。我们定义字典的方式是,通过指定一个键:一个值,当我们第一次定义字典时,所以我可能会说哈利:格兰芬多,然后德拉科:斯莱特林。例如,这行代码的作用是创建一个新的。
这个字典叫做houses,在这个字典中我有两个键,两个我可以查找的东西,我可以查找哈利或德拉科。当我查找这些键时,我会得到后面的值:例如,当我查找哈利时,我得到格兰芬多,当我查找德拉科时,我得到。
例如斯莱特林,现在如果我想打印出哈利所在的房子,我可以打印出houses的方括号,哈利。所以我在这里可以说我想打印出取出houses字典,方括号表示法是我如何查找字典中的内容,这类似于我们使用方括号。
使用括号查找列表中的特定元素,例如获取元素0或元素1,在这种情况下,我们使用Python字典来说明。取出houses字典并查找。
哈利的价值希望能得到,格兰芬多,如果我们观察一下就会发现。
设置一个 Python 字典,我们确实得到了,格兰芬多作为哈利的学院的值。我们可以使用相同的语法以同样的方式向这个字典添加内容,就像我使用方括号访问字典中的值一样。如果我想要改变字典中的值,或添加新的内容,我可以说。
例如,学院和赫敏,并且说赫敏也在格兰芬多,所以这行代码表示取出学院字典,并在学院字典中查找赫敏,当你这样做时,应该将其设置为这里的值格兰芬多,所以我们取了那个值并将其赋值。
在字典中添加赫敏,以至于现在如果我们想的话,我们可以。
打印出赫敏的学院,运行程序并查看。
赫敏也在格兰芬多,因此每当我们想将某个值映射到另一个值时,无论我们是将人映射到他们所在的学院,还是将用户映射到有关这些用户的信息时,在我们的网络应用程序中,字典将成为非常非常强大的工具。
哈佛 CS50-WEB | 基于Python / JavaScript的Web编程(2020·完整版) - P8:L2- Python编程语言全解 2 (函数,面向对象,异常处理) - ShowMeAI - BV1gL411x7NY
我们将看看我们在Python中的函数,这些函数将为我们提供编写自己的函数的方法,这些函数接收输入并产生输出。我们已经看到了Python中存在的许多不同函数。我们看到过接收用户输入的输入函数。
打印函数接收一些文本或其他信息并将其打印到屏幕上,但如果我们想定义自己的函数,我们也可以这样做。所以在这里,我将继续编写一个名为function stop PI的新程序。让我们编写一个接收数字并计算平方的函数。
10是10乘以10,或者100。我想要一个非常简单的函数,接收一个数字并返回它的平方。我在Python中定义函数的方式是使用DEF关键字,def是define的缩写。在这里我可以说让我定义一个名为平方的函数,然后在括号中指定它接收的输入,这里只是平方。
接收一个输入,我称之为X,但如果有多个输入,我可以用逗号分隔它们,例如X Y Z,用于接收三个输入的函数。但在这种情况下,只有一个输入X,平方函数可以有任何逻辑,缩进在平方函数下面。
但最终这个函数相当简单,它将返回x乘以x,即x的平方。如果我想打印出很多数字的平方,我可以这样做。我可以说,假设I在范围内,让我们打印。
第4行表示对于I在范围10内,执行一个循环十次,从0循环到9。每次循环时,我们将打印一些内容。我们将打印出I的平方值。
调用我们的平方函数,使用I作为输入,因此这将会是运行这个循环十次并打印这一行十次,每次的I值不同。我可以运行Python函数pi,这里我看到0的平方是0平方。
一个是一个,两个是四,以此类推,一直到9的平方是81,所以我们现在已经编写了一个函数,并能够使用它,但理想情况下,当我们编写函数时,我们希望不仅能够在同一文件中使用它们,还希望其他人也能使用它们,那么我们该如何做到呢?
你可以从其他Python模块或文件中导入函数。让我创建一个名为squres PI的新文件。例如,这样我们就不再在这里运行这个循环,而是改为在squres PI中运行这个循环,再次将我的代码不同部分分开,我有一个定义平方的文件。
在functions pi内部的函数中,然后。
另一个名为squres pi的文件,我现在实际上在调用squre函数。
如果我尝试运行Python squres pi,你会注意到我会遇到一个错误。这里是另一个你会经常看到的错误,它是一个名称错误,另一种异常类型,这里说的是名称 squre 未定义,意味着我正在尝试使用一个未定义的变量、函数名称或其他东西。
实际上我并没有定义squre是什么,这就是因为默认情况下Python文件互不了解。如果我想使用另一个文件中定义的函数,我需要从该文件导入它。我可以这样做,我可以说从functions导入squre函数。
这个文件的名称是function stop PI,我在说我希望从该Python模块导入squ*re函数。
作为一个我想要使用的函数。
现在我可以运行Python squres da PI,我们得到了预期的输出,没有更多的异常。我现在能够从另一个模块导入某些东西并以这种方式访问它,这是一种导入方式,字面意思是从functions导入squre函数,导入一个在function stop中定义的特定名称。
另一种做法是直接说导入functions,导入整个模块,但那样我就需要说从Shin的.squre来表示进入functions模块并获取squre函数并运行该函数,这样操作也会完全一样。
有几种不同的选择,或者导入整个模块,在这种情况下我使用这个点表示法来访问模块的特定部分,或者我说从functions导入squre,以便仅将名称squre导入到这个文件中,这样我就可以随时使用squ*re这个词,这样是有效的。
这不仅适用于我们自己编写的模块,Python还附带了许多内置模块。如果你想编写与CSV文件交互的程序,而CSV是一种电子表格文件格式,我可以导入Python内置的CSV模块,以获取与CSV相关的一系列功能。
一堆与数学相关的功能可以通过导入数学模块等方式获得。此外,还有其他人编写的额外Python模块和包,你可以安装。当时机来临,下次我们看Django时,这是我们要关注的技巧之一。
使用由其他人编写的函数,因此现在是模块,以及我们如何使用模块来导入函数,以便允许某种行为,这是一种使用Python编程语言编程的方式,但另一种关键技巧是Python。
这一点也被许多其他编程语言所支持。对象导向编程的概念是一种特殊类型的编程或编程范式,从某种意义上说,它是一种思考我们如何编写程序的方式。在面向对象编程中,我们以对象的方式思考世界。
在对象可能存储信息的地方,存储一些数据并且也支持执行某些类型的操作、某种行为或方法,或者我们可能称之为可以对这些对象进行操作的函数。因此,现在我们将深入了解一些面向对象的能力。
Python编程语言将为我们提供能力,以便有这些类型。它有列表类型,它有集合类型等等。假设我们想在Python中创建一个新类型,一种表示其他数据类型的方式,例如二维点。
我们之前谈到的事情是有一个x值和y值。正如我们已经讨论过的,你可以通过使用元组来做到这一点,使用一个数字和另一个数字,但我们可以创建一个完整的对象类来表示这个数据结构,所以这就是我们将要进行的。
现在我们来看如何在Python中创建一个类,所以创建一个名为classes.py的新文件。类可以被视为我们要定义的某种对象的模板,我们将定义一个名为point的新类,然后在定义完point是什么之后,我们将能够创建其他点。
能够创建点以存储x和y值,例如。那么我们需要什么来创建一个类呢?我们需要某种方式来说明当我创建一个点时应该发生什么,在Python中,这通过被称为魔法方法的__init__来定义。
这是一个方法或函数,每次我尝试创建一个新点时都会自动调用。这个函数接受几个参数,所有操作对象本身的函数,通常称为方法,都会接受第一个参数self,这个参数。
self表示相关的对象,这一点非常重要。因为我们不想仅仅有一个名为X的单一变量来存储点的x坐标,或一个名为Y的单一变量来存储y坐标,因为两个不同的点可能有不同的X和不同的Y值,而我们希望能够。
能够分别存储这些点,并且我们将把它们存储在对象本身内部,因此这个变量self引用了我们当前处理的对象,并且可能会根据我们在任何给定时刻与之交互的点而改变。点还需要哪些输入?点还需要一个x值和一个y值。
值,因此当我们创建一个点时,我们将为该点提供一个x值和一个y值。现在,我们需要做什么才能将所有这些数据存储在点内部?好吧,记住self表示点本身,因此如果我们想在该点内部存储数据,让点存储它自己的x和y。
然后我们需要将数据存储在单元格中,可以这么说。为了做到这一点,我们可以使用这个点符号来表示self的X等于这个输入X,而self的Y等于这个参数Y,而这些值x和y可以被称为。
任何东西都可以被称为,例如,输入1和输入2。然后,你只需在这里反映它们,重要的是这两个输入值被存储在点本身的属性中,我们将称之为x和y。好的,这有点抽象,但现在让我们。
看看我们实际上如何使用它,如果我想创建一个叫P的新点。我可以说P等于点,然后自我参数会自动提供。我不需要担心这一点,但我确实需要提供输入1和输入2,x值和y值,所以我将继续提供一个。
x值为2,y值为8,例如,所以现在我创建了这个点。现在我有了一个点,我可以打印出关于该点的信息。我可以打印出点的x值,也可以打印出点的Y值。再一次,我使用这个点符号来访问数据。
存储在那个点内部访问。
它的x值和超额Y值,因此现在,当我运行这个程序Python类时。我得到的是第一行的2。
这就是x值,第二行是8,或者说这是Y值。所以我们这里有一个叫init的函数,通过将两个输入存储在对象内部的属性中,创建一个点,称为X和Y,以便稍后我可以创建一个点。
隐式调用这个init函数,创建点后,我可以访问其中的数据。我可以说,打印出P的x等于多少,打印出P的y等于多少,这样也是可以的。这是创建一个类的一个相当简单的例子,只是创建一个表示x和y的点的类。
让我们看一个更有趣的例子,假设我们正在为一家航空公司编写一个程序,该航空公司需要跟踪航班上的乘客预订,并确保没有航班被超额预订。我们不希望航班上的乘客超过其容量。
所以让我们定义一个新的类,我们将称之为flight,这次init方法将只接受一个参数,除了self之外,就是容量。每个航班需要某种容量来知道可以容纳多少人,因此我会将其存储在一个名为容量的值中。
self.capacity等于容量,那么我们还需要存储关于航班的其他信息吗?航班有一个容量,并且它还有所有乘客,所以我们可以用多种方式表示这一点,但我们知道列表可以用来存储一系列值。
继续创建一个将存储在self.passengers中的列表,该列表将等于空列表,因此我们从空乘客列表开始。现在如果我想创建一个航班,我可以说flight = 大写F flight,这是类的名称,然后提供一个容量。
说容量为三意味着可以有三个人上飞机,但不能超过三人。这是因为这是在init函数中指定的参数,当我这样做时,我会自动获得一个空的乘客列表。现在,让我们考虑一下可能出现的问题或方法。
当涉及到航班时,我们可能关心的一些函数,所以一个合理的函数是添加一个乘客的函数。如果我想让某个新乘客上这趟航班,我该如何进行呢?那么让我们定义一个新的方法,也称为。
这个航班类中有一个名为add的方法,可以调用乘客。因为这是一个将作用于单个对象的方法,我们需要某种方式来引用该对象本身,所以我们会再次使用关键字self,当我们添加乘客时,需要添加一个路径。
通过他们的名字,所以我需要指定他们的名字,这样现在我想将这个名字添加到乘客列表中,我该如何访问乘客列表呢?我会访问self,即对象本身,而我将乘客存储在self中,在self.passenger这个属性中。
self.passengers是一个最初为空的列表,但如果我想将某些内容添加到列表的末尾,我们已经看到,为此我可以说self.passengers.append(name),这样就可以将新乘客添加到这个乘客列表的末尾。现在有什么好处呢?
每次调用这个add passenger函数时,将会发生的事情是,我们将把这个名字追加到乘客列表的末尾,但我们没有考虑到航班的容量。理想情况下,我们的add passengers函数不应该让某人被添加到航班中。
如果航班已经达到容量,有很多事情我们可以做。我们可以在这个函数内部检查,但为了确保万无一失,让我们创建一个新函数,叫做开放座位,它将返回飞机上剩余的开放座位数量。
除了自我,我们不需要其他输入来计算有多少开放座位。计算开放座位所需知道的唯一事情是容量减去乘客数量。记住,自我乘客是我们的所有乘客列表,而任何时候。
我们有一个序列来获取该序列的长度,我可以说长度或该序列的长度,以获取乘客数量。所以现在我们有这个叫做开放座位的函数,它将返回容量减去乘客数量,并告诉我们还有多少开放座位。
在乘客函数中,我可以添加一些额外的逻辑。我可以说,如果没有自我开放座位,这等同于我在这种情况下说,如果自我没有开放座位等于零,意味着没有开放座位,换句话说,更为 Pythonic 的表达方式是说,如果没有自我开放座位。
如果没有更多的开放座位,我们应该怎么做?我们应该返回。也许你可以想象这个添加乘客函数如果成功添加乘客将返回真,否则返回假。所以在这种情况下,我可以返回假,表示没有足够的开放座位。
从这个函数返回假以指示出现某种错误,但如果有开放座位,我们可以添加乘客并返回真,表示一切正常,我们能够成功添加乘客。所以现在我们有这三个函数,它创建了一个新的。
航班添加乘客,该函数将新乘客添加到该航班上,以及开放座位,告诉我们有多少开放座位。现在让我们使用这些函数,实际上为这个航班添加一些乘客,让我获取一组人,比如哈利、罗恩、赫敏和金妮,然后让我循环遍历所有这些人。
列表中的每一个人,让我们尝试在航班上添加乘客,并将结果保存到一个变量中,称为成功。例如,我可以说如果成功,那么让我们打印出成功将这个人添加到航班上,但否则打印出没有。
有效的座位给那个人。那么这里发生了什么?我们有一组人,对于每一个人,我们将尝试将乘客添加到航班上,调用航班添加乘客,传入该人的名字,并将结果(真或假)保存到这里。
变量称为 success,如果 success 为 true,我们打印出“我们已成功添加他们”。否则,我们打印出“该人员没有可用座位”。现在我们可以尝试运行这个程序,我将运行 Python 类 Piatt,现在我们看到我们已成功将哈利、罗恩和赫敏添加到航班中。
航班的容量为三,这意味着对金妮来说没有可用座位,这在第四行显示为错误消息。如果你真的想进行优化,你可能会注意到,你其实不需要这个变量。我可以将整个表达式 flight add passenger person 拿掉。
在条件语句中,我可以尝试添加一个乘客,添加乘客将返回 true 或 false,如果返回 true,这意味着成功。然后我可以打印出“我们已成功将该人员添加到航班中”,这是对 Python 中面向对象编程的一次简要介绍。
以及其他编程语言,以表示像这个特定航班的对象,然后使用方法(如添加乘客的方法)来操作这些对象,该方法接收航班并将人员添加到航班中,只要该航班有可用容量,因此这是许多强大的功能之一。
Python 的一些特性将会在后期深入探讨,并在我们构建这些网页应用时使用。现在有几个最终示例,值得一看,以便让你接触到 Python 中其他可用的特性。
很快就会出现的一个概念是装饰器,就像我们可以在 Python 中获取一个值(比如数字)并修改它一样,装饰器是在 Python 中修改函数的一种方式,为该函数添加一些额外的行为。因此,我将创建一个名为 decorators 的新文件。
仅用来演示我们可以用装饰器做什么,装饰器的概念是,装饰器是一个接受函数作为输入并返回该函数的修改版本的函数。因此,与其他编程语言不同,函数独立存在,无法直接。
函数可以作为输入或输出传递给 Python 中的其他函数,函数就像任何其他值一样,可以作为输入传递给另一个函数,也可以作为另一个函数的输出,这被称为函数式编程范式,其中函数本身就是值。因此,让我们创建一个。
修改另一个函数的函数,通过声明该函数即将运行,以及该函数完成运行,来进行演示。因此,这个声明函数将接收一个函数 f 作为输入,并返回一个新函数。通常,这个函数会用一些额外的行为包装函数 f。
因此,通常称为包装函数,我们可能称这个。包装器,来说明我的包装函数将要做什么,它将首先打印即将运行函数的信息,以便宣布我们即将运行该函数,这就是我想让我的,宣布装饰器做的。
实际上运行函数f,然后,打印完成该函数。所以我所做的宣布装饰器是,接收函数f,并创建一个新函数,通过打印语句在函数执行前后进行宣布,最后我们将返回这个。
新函数即包装函数,所以这就是我们,可能称之为装饰器的地方,它是一个接收函数、通过添加一些额外功能来修改它,并最终返回一些输出的函数。现在,我可以定义一个名为hello的函数,它只是打印hello world。
这是一个例子,然后添加装饰器我使用@符号,可以说@announce,来添加宣布装饰器到这个函数上,然后我只需运行。
发生的事情是,我将运行Python decorators dot Pi,我看到即将运行这个函数,然后是hello。
在这个函数完成后,为什么会这样工作呢?是因为我们的。hello函数只是打印了hello,world,被这个。宣布装饰器所包裹,而这个,宣布装饰器所做的就是接收我们的。hello函数作为输入,并返回一个,新的函数,首先打印一个警告。
警告我们即将运行的函数实际上会运行这个函数,然后打印另一条消息,这是一个简单的示例,但装饰器在能够快速地对函数添加功能方面有很大的潜力。在一个网页应用中,如果你只想要某些。
如果用户已登录,则能够运行它,你可以想象编写一个装饰器,确保用户已登录。然后在你想确保的所有函数上使用该装饰器,这些函数只有在用户恰好已登录时才有效,所以装饰器是一个非常。
强大的工具,像Django这样的网页应用框架可以利用它。只是为了让网页应用开发过程变得更简单一点。我们再来看一下Python中存在的其他几种,技术,其中一种是我们可能如何,更高效地表示函数。
假设我现在有一个,我将称之为lambda PI,稍后你会明白原因。假设我有一个名字或人的列表,举个例子,在这个人的列表中,每个人不是一个简单的字符串,而是一个字典,包含一个名字,比如Harry和一个房子,比如Gryffindor。
让我再添加一个名字,比如 Cho 和,一个像 Ravenclaw 的学院,然后再加上一个。名字像 Draco 和一个学院像,Slytherin,所以这里我们有一个列表,其中每个。元素都是一个字典,一个键值对的映射。这在 Python 中是完全可以的,我们有能力嵌套数据结构。
我们可以在彼此内部拥有列表,嵌套在其他列表中或字典中的列表,或者在这种情况下,字典在列表中,事实上,这种数据结构的嵌套是,Python 能够表示的原因之一,结构化数据,比如一个人的列表。
每个人都有各种不同的,属性,现在我可能想做的是像对所有这些,进行排序然后打印出来,所以我。可能想说 people.dot sort,然后打印所有人,但如果我尝试。
运行这个我会遇到异常,我遇到,异常类型错误,字典和深度之间不。支持小于,这有点奇怪,因为我根本没有在任何地方使用。小于符号,但在回溯中你会看到。捕捉到的代码行是 people.dot sort,某种程度上 people.dot。
排序导致类型错误,因为,它试图使用小于进行比较。
对字典的比较,这似乎意味着 Python 不知道如何。对这些字典进行排序,它不知道哈利是在,Cho 之前还是之后,因为它不知道如何。比较这两个元素,因此如果我。想这样做,我需要告诉排序函数如何。
排序这些人,因此为了做到这一点,我可以通过。定义一个函数来告诉排序,函数如何进行排序,排序时看什么,因此如果我想要,按人名排序,让我定义一个。函数,我将其称为 f,它接受一个人作为输入并返回该。
通过查找该字典中的名字字段来获取某人的名字,现在我可以通过,使用 sort key 等于 F 来按姓名排序,这意味着。是对所有人进行排序,而排序的方式是通过运行这个函数,函数接受一个人并。
给我们返回他们的名字,现在如果我运行,Python lambda PI 你会看到我首先,得到的是 show 然后是 Drako。
然后按名字的字母顺序排列哈利,而如果我试图排序。
通过更改我使用的排序函数来按他们的学院对人进行排序,然后。重新运行这个,现在我看到的是首先,哈利在格兰芬多,然后是拉文克劳。接着是斯莱特林,例如,我们得到。
按字母顺序排列的房屋,而我之所以展示这个是因为这个函数非常简单,仅在一个地方使用,Python 实际上提供了一种更简单的方式来表示一个非常短的一行函数,使用一种称为 lambda 表达式的方式,这是一种将函数包含为单个表达式的方法。
在一行中,我可以说,不必定义一个名为 F 的函数,我可以去掉这些,只需说按这个键排序,一个 lambda 函数,它接受一个人并返回这个人的名字,所以我们说人是输入:
输出的名字作为结果,这是一种简化的表达方式,和我们刚刚看到的定义一个函数、赋予它一个名字然后在这里使用这个名字是相同的。这里就是一个完整的函数,它接受一个输入。
以人作为输入并返回他们的名称。
名称,所以 Python lambda 其实是按名称对人进行排序。
Cho、Draco 和 Harry,但如果我完全省略了这个键。
然后尝试排序时,我们得到这个类型错误,因为我们无法比较这两个字典。现在我们已经看到了许多不同的异常,这些都是 Python 中的异常。最后一个例子是关于如何处理这些异常的示例,例如当事情出错时该怎么做。
我们希望我们的程序能够处理这些可能的异常情况,情况可能真的出错,所以让我们尝试一个例子,我将创建一个新文件,叫做 exception stop PI,而这个 PI 将会获取一些输入,告诉我们:
获取一个名为 X 的整数输入,再获取一个名为 Y 的整数输入,然后让我们继续打印 X 除以 Y 的结果,所以结果等于 X 除以 Y,然后让我们打印出像 "X 除以 Y 等于结果" 这样的内容,我们可以字面上打印出 X 和 Y 的值,这就是一个。
一个简单的程序,仅仅是执行一些除法,获取一个 X 的值,获取一个 Y 的值,将它们相除并打印出结果。
我们可以尝试运行这个程序,运行 Python exception stop I,如果我输入 5 然后 10,5 除以 10 正好是 0.5,这是我可能期待的结果,但现在可能出现什么问题呢?你还记得数学中的除法吗?可能出错的情况是,如果我输入 5 然后 0,尝试 5 除以 0。
发生什么情况呢?当我这样做时,我会得到一个异常,出现一个 0 除法错误,这是在尝试除以 0 时发生的错误。不过,我希望发生的情况是,不是让我的程序显示这样的混乱错误和回溯,而是处理这个异常。
优雅地处理它,能够捕获用户做错事的情况。
并报告一个更好看的消息。那么,我该如何进行呢?我能做的一件事是,不仅仅说结果等于 X 除以 Y,而是尝试这样做,尝试将结果设为 X 除以 Y,然后说,如果发生 0 除法错误,那我们就做点别的,打印出来。
我无法除以零,然后退出程序。如何退出程序呢?结果发现,Python 中有一个模块,叫做 sis,如果我导入 Syst 模块,我可以说 sis.dot exit 1,意味着以状态代码退出程序。状态代码 1 通常意味着出了一些问题。
这个程序,现在我试图将 X 除以 Y,除了我有一个异常处理器。这是一个 try except 表达式,我在说,尝试这样做。除非发生这个异常,否则就不要让程序崩溃,只需打印出这个错误消息,无法除以。
零,然后退出程序。现在,让我们试试,python 异常停止 PI,再试 5 和 10。完全正常,得到了 0.5 的值,但现在如果我尝试 5 和 0。按回车,我得到了一个错误,无法除以 0。这个长异常对用户来说看起来复杂,不再混乱,我已经能够处理。
优雅地处理异常。现在另一个可能出现的异常是,如果我输入的不是数字的单词,比如 hello,而是 X 为 5,现在我得到了另一种类型的异常,一个值错误,这发生在我尝试将某个东西转换为整数时,因为整数,你无法将不是数字的文本。
并将其转换为整数,因此,
我在这里遇到了这个值错误,我该如何处理呢?我可以用与我获取输入 X 和 Y 时几乎相同的方式来处理。我可以说,不仅仅是获取输入,而是尝试获取。
输入,除非发生值错误,这就是我们得到的错误。
刚才的值错误,然后打印错误,无效输入,并继续执行,而不是退出 1。所以现在我已经能够。
处理这个错误,我可以说,Python 异常停止,我可以说。你好,我只是得到了错误,无效输入,我无法除以零,得到了错误,无法除以零。但是如果我输入一个有效的 X 和 Y 值,我就得到了。
将一个数字除以另一个数字,因此,异常处理通常是一个有用的工具,如果你预计某些代码行可能会遇到某种问题,无论是值错误、零除法错误还是其他错误,都能够处理这些错误。
优雅地处理错误可能正是你所想要的,如果你打算使用Python构建一个Web应用程序的话,也就是说如果发生错误,我们希望优雅地处理它,向用户显示一个友好的错误信息,告诉他们出错的原因,而不是让程序完全崩溃。
这些是Python编程语言的一些关键特性,这种语言让我们能够定义函数、循环和条件,并以非常方便的方式创建类,以便我们开始构建能够执行各种任务的对象。
下次使用Python时,我们将能够设计Web应用程序,使得用户能够向我们的Web应用程序发出请求并获得某种响应,所以我们下次见!
哈佛CS50-WEB|基于Python/JavaScript的Web编程(2020·完整版)- P9:L3- Django网络编程 1(web应用,http,路由)- ShowMeAI - BV1gL411x7NY
[音乐],欢迎大家回到使用Python和JavaScript的网页编程课程。在这门课程中,我们已经看了一些不同的技术,我们查看了HTML和CSS,这些语言可以用来描述网页的结构,并适当地进行样式设置。
我们还查看了Python,这是一种可以用来获取编程特性的编程语言,如循环、条件、变量和函数等。今天我们要介绍Django,它将结合这两者。Django是一个Python网络框架,可以让我们编写能够动态生成HTML和CSS的Python代码,最终构建动态网络应用。
这将使我们能够做一些以前仅用HTML和CSS无法做到的事情。使用HTML和CSS时,我们创建的网页是静态的。
每当你访问网页时,页面是相同的,但如果你考虑一下日常使用的网站,比如纽约时报,这种情况并不是每次你访问纽约时报主页时都是相同的。
看到的正是相同的HTML,第二天你可能会看到新的日期,你可能会看到第二天的文章,无论何时有人对文章发表评论,你都可以看到这些评论,并看到有多少人对某篇文章发表评论,而这种情况可能并不是有人在那儿。
每次有变化时,他们并不会更新纽约时报网站的HTML,而更可能是有某个程序(可能用Python或其他语言编写)动态生成HTML和CSS,使得网络应用能够根据用户的互动进行响应。
与其互动,这就是我们最终要通过这个Django网络框架创建的。我们将创建在网络服务器上运行的软件,使得运行在浏览器中的客户端可以向我们的网络服务器发出请求。
要进行某种回应,这个过程实际上是如何发生的,最终归结于这个协议HTTP,通常被称为超文本传输协议,这就是在互联网上消息如何来回发送的协议。你可以这样理解。
这可以看作是计算机中某个用户(我们称之为客户端)和我们的服务器(它将包含我们的网络应用)之间的关系。我们将编写一个在该服务器上运行的网络应用,客户端将向该服务器发出请求。
将处理该请求,并返回某种响应,该请求可能看起来像这样,从内部来看,可能以“get”一词开始,get只是请求方法的一种示例,是你可能尝试获取页面的一种方式,get仅意味着我想获取特定的。
在这种情况下,我试图获取的是根页面,仅表示网站的根目录,通常是默认的。
网站的页面HTTP 1.1只是我们正在使用的HTTP版本,主机是我们试图访问的网页的URL,比如example.com或其他我可能试图请求的网站,所以这个请求最终会由我的网络浏览器在我输入URL时发送。
按回车键,例如,服务器处理该请求,然后返回某种响应,响应通常看起来会像这样,以HTTP版本1.1或在某些情况下,版本2开头,然后是某种响应码,200是表示正常的响应码。
一切正常,页面成功返回,例如,在第二行我们看到内容类型为text/html,这仅意味着返回的响应数据格式为HTML数据,用户的web浏览器在客户端应将其呈现为HTML。
例如,还有其他类型的内容以及其他信息会在响应中返回,但关键思想是将网页视为请求和响应的关系,用户发起请求以获取特定的网页,而他们收到的则是这样的响应,希望能够。
包括状态码200,意味着正常,但还有许多其他状态码,比较常见的有200,表示正常,可能你见过404,表示未找到,如果你尝试请求一个不存在的网页,比如说,其他你可能见到的状态码还有301,表示永久移动。
如果你从一个网站重定向到另一个网站,这种情况经常发生,例如,我们将很快看看一个例子,403通常意味着禁止,你试图访问一个不该访问的页面,而500通常指的是某种内部服务器错误,通常意味着。
编写服务器的人,也许我们的Django网络应用程序存在一些bug,我们可能需要回过头去找出这个bug所在,以便修复它,同样,还有其他状态码,但这些是我们将会看到的一些最常见的状态码。
在我们使用Django构建web应用程序时进行交互,Django是一个web框架,它为我们预先构建了一系列工具,这将使我们能够通过编写Python代码来轻松入门,以设计一个功能齐全的web应用程序,并处理一些事情。
大多数网页都有的一些东西,这样我们可以专注于构建我们自己网络应用程序的有趣逻辑。因此,您在使用Django之前需要做的第一步是安装它。如果您使用的是Python,您可能对pip(Python包管理器)很熟悉。
它使得安装新包变得简单,我们可能想做的第一件事就是安装Django,在这里我们将使用Python 3,因此您可能需要指定pip 3
并安装Django,以在您的系统上安装Django。在您完成后,我们可以运行一个命令。
创建一个Django项目,该命令看起来像这样。Django - admin start project
,后面跟着我们想创建的项目名称。当我们运行这个命令时,Django将自动为我们创建一些入门文件,以便开始构建过程。
网络应用程序,所以现在让我们尝试创建一个项目。我将进入我的终端窗口,接下来我会输入那个命令,我将输入Django admin
,然后start project
,接着我需要给这个项目命名,我将这个项目命名为“lecture 3”。
您可以将项目命名为您喜欢的任何名称,当那发生时,如果我输入ls
,我看到的是Django为我创建了一个名为“lecture 3”的目录或文件夹。!
看看这个“lecture 3”目录中有哪些文件,所以实际上我会。!
接下来进入“lecture 3”并打开它,我们在“lecture 3”中看到的内容是!
一些文件供我们入门,所以有几个文件,目前对我们来说并不是所有的文件都很有趣,但最重要的是manage.py
是Django将为我们创建的一个文件。我们通常不需要修改该文件,但我们将使用它。
一个可以在此Django项目上执行命令的文件,我们将看到一些示例,现在以及未来的讲座中也会有一些示例,然后我们将要查看的另一个重要文件是settings.py
,它包含了我们Django应用程序的重要配置设置。
settings.py
预加载了一些默认设置,但我们可能想更改这些设置,以便为我们的应用程序添加功能,或者对应用程序的行为进行一些修改。另一个为我们预创建的重要文件是urls.py
。
可以将 URLs PI 视为我们网页应用的内容目录。在任何给定的网页应用上,有许多不同的 URL 或路由可供访问。例如在 Google 上,你可以访问 /search 或 /images,而 URLs PI 就是一个内容目录。
我可以最终访问的所有 URL,因此如果我想尝试运行这个网页应用,只是想看看它在终端中是什么样子。我会运行 Python manage.py
,后面跟一个命令,这个命令叫做 runserver,这通常是我们使用 manage.py 的方式。
manage.py
后面跟一个参数,指定我们想要运行什么命令。python manage.py runserver
的意思是开始实际运行这个网页服务器。如果我这样做,我现在就在本地运行这个网页服务器,并看到一堆调试输出,但对我来说有趣的是它在下面说。
在 HTTP://127.0.0.1:8000 启动开发服务器。所以这就是我的网页应用当前运行的地方,127.0.0.1 是一个 IP 地址,指向我的本地计算机。我现在看到的计算机,只有我可以访问,而其他人无法在互联网上访问。
000 是我们所称的端口号,它指的是正在运行的服务类型。你可能在不同的端口上运行多个不同的互联网服务。在这种情况下,我们的 Django 应用正在 8000 端口上运行,所以。
如果我复制这个 URL,然后直接进入我的网页浏览器并粘贴那个 URL,我看到的就是 Django 的默认页面,表明我们的 Django 安装正常工作。这现在是 Django 将提供给我们的默认页面,说明 Django 已经为这个网页应用加载完毕。
实际上开始构建这个网页应用,添加我们想要的功能。因此,我们在这里创建的是一个 Django 项目,就项目的结构而言,每个 Django 项目通常由一个或多个 Django 应用组成,所以一个项目可能包含。
在它内部有多个应用,之所以有这样的划分,是因为如果你考虑大型网站,往往一个大网站或项目有多个不同的应用,分别作为独立的服务在其中运行。例如,Google 有 Google 搜索、Google 图片和 Google 新闻。
可以把每一个独立的服务看作是一个单独的应用,归属于一个更大的项目,比如 Google,或者像亚马逊的网站。举个例子,可能是一个大型项目,其中包含几个不同的应用,一个用于购物,另一个用于亚马逊的视频。
Django 自带了将项目划分为多个独立应用程序的功能,也许对于简单的应用程序,我们只会有一个包含一个应用程序的项目,而不是多个,但它具有允许我们创建这些独立应用程序的能力。
我们有不同的功能,因此如果我们想开始使用 Django,在创建项目之后,我们首先需要创建一个 Django 应用程序,因此在终端中,我会通过按 ctrl C 退出这个服务器,表示我想退出并停止运行服务器,所以现在如果我返回那个 URL 并刷新。
它不会工作,因为 jenga 服务器不再运行,但我会运行命令 Python managed up I start,应用程序名称是我想创建的,作为我们第一个应用程序,我将其称为 hello。例如,所以我现在创建了一个名为 lecture 3 的 Django 项目。
我创建的服务示例是这样的。
我现在称为 hello 的第一个应用程序,因此如果我们现在查看我们的文件,你会看到除了之前存在的 lecture 3 目录外,我们现在还有一个 Django 为我们创建的 hello 目录,它将包含 hello 应用程序运行所需的信息,并且有很多。
默认生成的文件,我们今天不会查看所有这些文件,我们将主要关注 views.py,稍后会说明原因,它将是让我们描述用户在访问特定路由时看到的内容的文件,例如。
可以决定渲染到的内容,以及其他文件,我们将在本学期的过程中查看。看看 Django 还提供了哪些额外功能。因此,我们现在已经创建了这个 hello 应用程序,我们希望将其安装到这个项目中,为了安装它,我们需要进入这个项目的设置。
特定的 Django 项目,所以如果我进入 lecture 3 目录并打开 settings.py,我在这里看到的是一个由 Django 为我创建的大文件。我没有写任何代码,这只是 Django 项目的默认配置,如果你向下滚动到某个地方,你会看到。
有一个叫做 installed apps 的变量,这是 Django 配置在这个项目中安装了哪些应用程序的地方,还有很多由 Django 默认安装的单独应用程序,例如,其中一个管理会话,我们稍后会查看,但如果我想要。
添加我刚刚创建的新应用程序,称为 hello 到这些已安装的应用程序中。我会继续将 hello 添加到这个已安装应用程序变量的字符串列表中。因此,现在 Hello 将成为这个特定 Django 项目中的一个已安装应用程序,所以我现在想做的是,实际上让这个 hello 应用程序工作。
比如,当我尝试访问特定路由时显示一些内容。我该如何去做呢?好吧,让我们进入Hello目录,看看我hello应用中的views.py。这里是我得到的默认文件,四个views.py,我看到有一个注释给了我。
默认情况下,这个文件上写着“在这里创建你的视图”,你可以将每个视图视为用户可能想要看到的内容。因此,让我们创建一个默认视图,为了在Django中创建一个视图,我们将定义一个函数,这个函数我将称为index,这个函数按惯例接受。
作为参数一个请求参数,这将是一个代表用户为访问我们的网页服务器而发出的HTTP请求的参数。如果我们想获取关于该请求的信息,可以查看这个请求对象,以访问一些其他数据,我们也会稍后看看。
但我希望这个请求做什么呢?我现在想做的事情是简单地返回一个“hello world”的HTTP响应。HTTP响应是Django创建的一个特殊类,因此如果我想使用它,通常需要导入它。Django将提供。
我们有很多可能想要在网页应用中使用的功能,但如果我们想使用它们,通常需要先导入它们。我只知道这一点是因为阅读了Django的文档,可以通过这一行代码from django.http import HttpResponse
来表示我想要。
导入提供HTTP响应的能力,现在我有一个表示我的视图的函数,这个函数叫做index。index函数所做的只是返回一个“hello world”的HTTP响应。因此,这是一个响应,但现在我们需要告诉应用何时。
实际上返回这个响应时,用户将访问哪个URL。这是我们开始创建一些URL配置的地方,一种设置,告诉Django在访问特定URL时应运行哪个函数,以返回该特定的HTTP响应。那么它是如何做到的呢?
为此,我们需要为这个特定应用创建一个URL配置文件。Django为整个项目有一个URL配置文件,但通常每个应用会有自己的urls.py文件,以便在两个不同的地方分隔内容。
如果我们有多个不同的应用,每个应用都在独立做某事,我们可以让每个单独的应用拥有自己的URL配置文件,以控制该特定应用可用的URL。所以我将进入hello目录,创建一个新的文件。
将URLs命名为PI,那么PI需要的是定义一个变量。称为URL模式,这将是一个列表,包含所有允许的URL,可以为这个特定的应用程序访问,创建URL的方式是首先。导入Django URLs导入路径,让我们创建第一个URL,我会说。
路径,然后第一个参数,这里我将传递一个空字符串。意味着没有额外的参数,我们很快就会看到这意味着什么。末尾的意思是没有东西,路由,然后第二个参数。路径是当访问此URL时应该渲染的视图,所以如果我。
渲染我的索引视图,回想一下在views.py中,我有这个索引函数,那么当有人访问这个。URL时,我想渲染的内容是views.index,视图代表的是views.py文件,我在其中定义了所有的视图,而索引恰好就是。
当有人访问这个URL时,我想调用的函数名称,例如,然后你可以选择性地提供一个带字符串名称的路径来表示这个URL,我将给这个名称命名为index,我们稍后会看到这会有用,但想法是给特定的URL模式命名使得。
让它更容易从应用程序的其他部分引用,因此稍后当我们。可能想链接两个东西或有,表单提交到Web应用程序的不同部分时,给路径命名可以是一个有用的工具。为了使用视图,我需要在URLs.py中添加从点导入。
换句话说,从当前目录中,继续导入视图,每当我使用一个变量名像视图时,我使用那个名字,我需要从某处导入它,正好views.py和urls.py位于同一目录,所以我可以说,从点导入视图来导入所有。
这个特定文件的URLs.py文件,所以现在这是这个应用程序的URLs.py文件,但为使这一切第一次正常工作,最后一步是返回到讲座3目录并打开URLs.py。这里是整个项目的URLs.py文件,包含所有的应用程序。
可能包含在这个项目中,碰巧有一个路径是默认提供的,称为admin,它运行一个名为admin的默认Django应用程序,我们将在下一次讲座中看到更多关于它的内容,但现在我想在这里添加自己的路由,说明我希望。
特定路径的目的是不是指向管理应用,而是我刚刚创建的hello应用,所以我会给这个路径命名为hello,我想说的是,在你包含路径hello之后,继续包含来自我的hello应用的所有URLs.py。
将这两种URL配置风格结合在一起,因此这样做的命令是将include hello.urls包含在Hello模块内获取URLs文件,并且这就是我想作为URL模式添加的,现在为此我还需要导入include,因为我在URL中使用include。
这些模式,所以这有很多步骤,只是为了开始,但为了获取一个高层次的概述,这一切是如何运作的,我有一个叫做lecture 3的Django项目,里面有一个决定我可以访问哪些URL的URLs文件,我可以访问/admin,这将我带到管理员界面。
由Django创建的应用程序,我们现在不必担心,但我刚刚添加了你可以去/hello访问hello应用程序,当你这样做时,我告诉Django查看hello目录内的URLs.py,以找出我可以访问哪些额外的URL。
所以这是一个主URLs.py文件,它可能连接到多个不同的其他URL配置,然后在我应用的hello应用的URLs.py中,我说当有人访问这个默认路由时。
特定应用程序,继续运行索引函数,这是我的一个视图,所以所有这些都已决定,现在完成后,我应该能够返回到我的终端并运行Python manage.py runserver,实际上启动。
这个网络服务器,现在我可以回到这个URL,但不是仅仅访问那个默认URL,我将前往这个URL/hello,当我去。
/hello,我看到的是你好世界。那么,发生了什么事情是我输入了这个URL,它转到Django,Django查看了这个URL,并查看了我的URLs.py文件,并说,你知道的,任何以/hello开头的东西属于hello应用程序,而在我们的hello配置中我们。
他说,当我们访问默认路由时,我们应该运行索引函数,而索引函数返回这个你好世界的响应,现在我们可以开始更改那个响应,我们可以开始调整视图实际在做什么,现在它显示你好世界,但如果我想要。
将其更改为仅显示你好。
例如,我可以现在编辑它,索引函数仅返回一个HTTP。
例如的你好响应,现在我刷新了这个页面,现在它只是说你好,所以我可以编辑我的文件,当我这样做时,Django会注意到有变化并且会以我最新的代码重新出现,这样我现在可以访问/hello,我可以看到的HTTP响应的你好是最终的。
回到我这儿,所以现在让我们看看这个想法,除了在我们内部只有一个视图,我可以有任意多个视图。我可以创建额外的函数,每个函数返回不同的响应,也许我想要一个说 hello 的路由。例如,让我定义一个新函数。
然后我将调用 Brian,它接受请求参数,这个函数将返回 HTTP 响应 hello,Brian 作为例子,现在我需要将这个新视图与一个 URL 关联起来。所以我将回到我 hello 目录中的 URL PI。因此,我有一个默认路由。
由空字符串表示,它加载索引函数,但我可以在此基础上添加。只需在此列表中添加一个新的路径,如果我在 URL 中输入 Brian,这将加载 Brian 函数,我将继续给它。
现在,如果我刷新页面,我仍然在一个斜杠的 hello 路由上,它只显示 hello。
但是如果我访问斜杠 Brian,现在我看到 hello Brian,我有两个不同的 URL,一个是单纯的斜杠 hello,后面什么也没有,这是那个空字符串,另一个是斜杠 hello 后跟 Brian,它加载一个不同的视图函数,返回不同的 HTTP 响应,因此。
我们可以想象继续这样做,我可能想要一个具有多个不同 URL 的网络应用程序,我可以访问。因此,我可以添加一个像 david 函数这样的例子,它返回一个 HTTP 响应 hello David,然后在我的 URL 配置中,我将说继续给我另一个。
路由称为 David,它加载 david 函数,每次我给它一个 URL 时,所以什么。
在斜杠 hello 之后,我给它一个运行 David 函数的功能,给它一个名称,以便稍后更容易引用。所以现在我有了斜杠 hello / Brian,现在我有了斜杠 hello。
/ David 每个都显示不同的 HTTP 响应,所以你可以想象开始使用这个功能来构建一个具有多个不同路由的网络应用程序,这些路由执行不同的操作,许多网络应用程序都是通过它所具有的参数化。
实际上在 URL 中,例如在 Twitter 上,你可以访问 twitter.com/某人的用户名来查看该用户的所有推文,或者是我们现在查看的 GitHub 服务。你可以访问 github.com/你的 GitHub 用户名,以查看特定用户的所有仓库。
例如,如果你考虑一下如何实现这两个服务中的任意一个,如果我们使用Django,它们可能会有一个URL配置文件或类似的东西,定义一整堆URL,并说明当有人访问该页面时应该关联哪个路由,但你可以想象最终。
如果我想向任何人打招呼,如果我不仅要支持/hello/Brian和/hello/David,还想支持任意名字,比如/hello/Harry或/hello/wrong或/hello/Hermione,那么看来我需要创建一整堆。
每个不同的功能都会向不同的人说“你好”,然后创建一整堆不同的URL,以便能够做到这一点。但事实证明,我可以创建带有占位符的URL模式,从而实现通过某些路径来参数化路径。
所谓的转换器,那么这实际上意味着什么呢?好吧,让我们不再创建仅仅说“你好Brian”和“你好David”的函数,而是创建一个叫greet的新函数,它也接受一个请求,但还需要一个额外的参数,例如某个人的名字。
greet函数将返回一个。
HTTP响应为“你好”,然后我继续在这里插入名字。我不需要在字符串中添加F来表示它是一个格式化字符串,这只是Python语法,表示我想说“你好”。不仅仅是对任何人,而是对name变量的值。
我想把name变量替换到greet函数中,然后我可以在URLs.py中添加另一个路径,但我不会像Brian或David或Harry那样在这里说名字,而是要在尖括号中说冒号name,当有人访问该路由时,让我们去。
这个视图的greet函数,路由的名字将是greet。那么这里发生的事情是,我们有一种根本不同的路径,而不是严格规定URL应该是什么样子,比如在路由末尾后没有任何内容,或Brian在路由末尾,或David在路由末尾。
这个路由在第7行表示,这个路由可以是我们给定的任何字符串,并将其变量命名为name,但你可以用完全不同的东西替换name,这可以是任何字符串,比如Brian或David,或者其他任何名字。当发生这种情况时,我们会调用。
greet函数,当这个函数被调用时,它会将这个参数作为参数传递给那个函数,所以现在能够创建一个自定义路由,允许我指定任何字符串,并找出如何妥善处理它。所以现在如果我访问/hello/Harry,例如。
这不是我明确创建的路线,而是一个字符串,哈利是一个字符串。我按回车键,它会说“你好,哈利”,我可以去斜杠“你好,罗恩”,并说“你好,罗恩”,再去斜杠“赫敏”看看,“你好,赫敏”,你知道吗?也许为了好的度量,我想要。
将这个名字大写,比如赫敏,做法是我可以添加任意的 Python 逻辑,事实证明,对于任何 Python 字符串,有一个函数或方法叫做 capitalized,我可以说“点大写”,如果我能在 Python 中做到这一点,那么 Django 也允许我这样做。
将它融入到我所回馈的响应中,因此我现在使用 Python。
取这个名字大写并在返回给用户的响应中使用它,所以现在/剩余返回,“你好,赫敏”,首字母大写 H,同样的,“你好”。
同样,对于“你好,哈利”,我现在已经能够添加一个路由,它接受 HTTP 请求以及参数名称,返回的 HTTP 响应只对那个人说“你好”。所以这些 HTTP 响应可以是任何 HTML 内容,现在我们只是使用文本,但你可以想象添加。
也可以将列表或表格包含在内,但你可能想象一下,如果我必须将整个 HTML 页面包含在这些双引号内,那会很麻烦。很快我们会发现,有很多 HTML 只是放在 Python 程序中的一个字符串里,如果我们回想一下。
本课程到目前为止我们所观察的原则和理念,我们通常尝试分离我们应用程序的不同部分,尽可能做到这一点,这样做有很多价值,保持整洁,并确保人们能够协作。
我希望一个人专注于 Python 逻辑,而另一个人专注于 HTML。你希望他们在工作时不要互相干扰。因此,在 Django 中,我们可以将响应的 HTML 与实际的 Python 代码分开,做法是。
返回 HTTP 响应时,我可以说,对于这个默认路由,我不返回“你好”的 HTTP 响应,让我继续渲染,当我渲染某个东西时,我需要传入 HTTP 请求,但我还会传入模板的名称,我将这个模板称为“你好/索引”。
HTML 所以如果我不想渲染,只是一个字符串,但我想渲染整个 HTML 文件,我可以调用这个渲染。
函数传入请求,但还要传入模板的名称。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P1:课程内容介绍 - ShowMeAI - BV1AQ4y1y7wy
你好,世界,这是CS 15,这是一门关于使用Python进行人工智能的介绍,来自CS50的Brian。自我将在这里结束,探索现代人工智能基础的概念,我们将从人工智能如何搜索问题解决方案开始。
学习如何玩游戏或尝试找到到目的地的行车路线。那么我们将看看人工智能如何用知识来表示信息。Rai对某些信息是确定的,但也对可能不确定的信息和事件进行学习,并学习如何表示它们。而更重要的是,如何利用这些信息进行推理和得出结论。
将探讨人工智能如何解决各种类型的优化问题,尝试最大化利润、最小化成本或满足其他一些约束。在转向快速发展的机器学习领域之前,我们不会告诉她如何确切地解决一个问题。
而是让Rai访问数据和经验,以便Rai可以自主学习如何执行这些任务。特别是我们将关注神经网络,这是现代机器学习中最受欢迎的工具之一,灵感来自人类大脑学习和推理的方式,然后最终看看世界。自然语言处理不仅是我们人类在学习,人工智能也能够交流。
但也包括人工智能学习如何理解和解释人类语言。我们将探讨这些想法和算法,并在此过程中给你机会构建自己的人工智能程序来实现这一切,这就是CS50。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P10:L2- 不确定性 3 (采样,马尔可夫,HMM) - ShowMeAI - BV1AQ4y1y7wy
通过一种称为抽样的过程,在抽样过程中,我将对这个贝叶斯网络中的所有变量进行抽样。我将从每个节点中按照它们的概率分布抽取一个值,所以我可能会怎样。
对所有这些节点进行抽样,我们都从根开始,我将从雨开始,这里是雨的分布,我将使用随机数生成器或类似的东西,随机选择这三个值中的一个。我将以概率0.7选择无雨,以概率0.2选择小雨,以概率0.1选择大雨。
所以我将随机根据这个分布选择其中一个,或许在这种情况下我选择了无雨,然后我对另一个变量维护做同样的事情。维护也有一个概率分布,我将现在进行抽样,这里有三个概率分布,但我只会从第一行进行抽样。
这里因为我已经在我的样本中观察到,雨的值是无,所以鉴于没有雨,我将从这个分布中进行抽样,看看维护的值应该是多少,在这种情况下,维护的值就设为“是”,这是在没有雨的情况下发生的40%。
举个例子,我会以这种方式对其余节点进行抽样。我想从火车的分布中抽样,我将从这一行中抽样,这里没有雨,但有轨道维护,我会抽样80%的时间说火车准时,20%的时间我会。
假设火车延误,最后我们也会对我是否按时到达预约进行相同的操作,我是否出席或错过了预约。好吧,基于这个分布进行抽样,或许说在这种情况下我出席了预约,当火车准时到达时,这发生的概率是90%。
通过遍历这些节点,我可以非常快速地进行一些抽样。
从这个完整的贝叶斯网络中获取可能值的样本,依据这些概率分布。而这变得强大的是,如果我不是一次而是成千上万次地进行,并生成大量样本,全部使用这个。
分布我得到不同的样本,也许其中一些是相同的,但得到。每个可能出现的变量的值,然后,如果我面临一个问题,比如火车准时的概率是什么,你可以进行,精确的推断程序这没有。
与我们之前的推断问题不同,我可以仅仅通过边缘化,查看所有可能的其他值作为变量,并通过枚举进行推断计算,以准确找出这个概率,但如果我不关心这个。
精确的概率只是对其进行采样,近似它以便接近,这在AI中是一个强大的工具,我们不需要每次都完全正确。或者我们不需要完全正确,如果我们只需要在某种概率下是对的,我们通常可以做到这样,更有效,更高效,所以在这里。
现在所有这些可能的样本,我将突出显示那些火车准时的样本,忽略那些火车延误的样本,你知道在这种情况下,八个样本中大约有六个火车准时到达,因此也许在这种情况下我可以说,在六个中。
八个案例的可能性是火车准时到达。
可能不是一个很好的预测的样本,但如果我有成千上万的样本,那么这可能会是一个更好的推断程序,能够进行这些计算,所以这是一种直接的采样方法,来做一堆样本,然后,找出某些的概率。
事件现在是之前的一个无条件概率,即火车准时的概率,我通过查看所有样本来做这个,并弄清楚,好的,这里是火车准时的样本。但有时我想计算的不是无条件概率而是。
而是一个条件概率,像是给定火车准时的情况下,有小雨的概率,做那种计算,我可能会这样做,这里是我所有的样本,我想计算一个概率。
在我知道火车准时的情况下的分布,所以为了能够做到这一点,我可以查看火车延误的两种情况,并忽略或拒绝它们,从我正在考虑的可能样本中排除它们,现在我想查看这些剩余案例,火车。
在这里是准时的案例,其中有小雨,我说好的,这两个是六个可能案例中的两个,可以给我一个关于小雨的概率的近似,考虑到我知道火车准时到达,我几乎是以同样的方式做的,只是通过增加一个额外的步骤。
说好了,当我取每个样本时,让我拒绝所有不符合我证据的样本,仅考虑那些与我证据相符的样本,我想对其进行某种计算,而结果是使用我们为贝叶斯准备的库。
我们可以开始实现这种相同的想法,比如实现拒绝采样,这就是这种方法的名称,以便能够通过抽样来计算某些概率,而不是直接推断。因此,我在这里有一个名为sample.py的程序,导入了完全相同的模型。
我首先定义的是一个生成样本的程序,我生成样本的方式是循环所有状态。这些状态需要按照某种顺序排列,以确保我以正确的顺序循环。但实际上,如果这是一个条件分布,我将基于父节点进行采样。
否则,我将直接对没有父节点的变量进行采样,比如雨。这只是一个无条件分布,跟踪所有这些父样本,并返回最终样本。这个确切的语法再次并不是特别重要,它只是实现的一部分。
关于这个特定库的细节,下面有有趣的逻辑。如果我想知道约会随机变量的分布,我有能力生成样本。
变量在火车延误的情况下,那么我就可以开始进行这样的计算。让我取10,000个样本,并把所有结果汇总在一个名为数据的列表中。接下来,我将在这个案例中循环10000次,生成一个样本,我想知道分布情况。
考虑到火车延误的情况下的约会,因此根据拒绝采样,我只考虑火车延误的样本。如果火车没有延误,我根本不会考虑这些值。因此,我会说,好吧,如果我取样,看看火车的值。
如果火车延误,这是一个随机变量,那么让我继续添加到我正在收集的数据中。这个数据是关于在这个特定样本中,约会随机变量所取的值。因此,我只考虑火车延误的样本。
考虑一下约会的值,然后最后我使用一个名为计数器的Python类,它能快速计算数据集中的所有值。这样我就可以拿到这个数据列表,弄清楚我的约会有多少次被安排,又有多少次被错过。
所以这里的几行代码实现了拒绝采样。我可以通过运行Python sample.py来运行它。当我这样做时,我得到的结果是计数器的结果,1251次我能够参加会议,856次我能够缺席。
这意味着你可以想象,通过做更多的样本,我将能够得到更好、更准确的结果。这是一个随机化的过程,它将是概率的一个近似值。如果我在不同的时间运行,你会注意到数字是相似的,1272和905,但它们并不完全相同。
因为有一些随机性,某些事情可能更高或更低的可能性。这就是为什么我们通常想尝试使用更多样本,以便对我们的结果有更大的信心,更确信我们得到的结果是否准确反映了。
代表着这个分布内部固有的实际基础概率。因此,这样就有了拒绝采样的实例。事实证明,还有许多其他的样本方法可以用来开始尝试。拒绝采样面临的问题是,如果你寻找的证据。
是一个相当不太可能的事件,那么你将会拒绝很多样本。比如说如果我在寻找某些证据下的X的概率,如果E发生的可能性非常小,比如每一千次才会发生一次,那么我只能考虑每一千个样本中的一个。
我进行的样本,这是一种相当低效的方法来尝试进行这种计算。我丢弃了很多样本,这需要计算工作来生成这些样本,因此我希望不必做这样的事情,所以还有其他采样方法。
可以尝试解决这个问题,一种叫做可能性的方法。
在可能性加权中,我们遵循稍微不同的程序,目标是避免需要丢弃不匹配证据的样本。因此,我们将首先固定证据变量的值,而不是采样所有内容,我们将固定这些值。
我们将不对证据变量进行采样,然后以相同的方式采样所有其他非证据变量,只需使用贝叶斯网络查看概率分布,采样所有非证据变量。但是我们需要做的是根据每个样本的可能性来加权。
证据的出现几乎不太可能,我们想确保已经考虑了这一点。如果我有一个样本,证据在该样本中出现的可能性要高于另一个样本,那么我想给更可能出现的样本更高的权重,所以我们将根据每个样本的。
可能性定义为所有证据在我们拥有的所有证据下,发生的概率。在这个特定样本中,这样的事件发生的概率是多少?因此,在我们的所有样本中,它们的权重是相等的,当我们计算整体时,它们的权重都是1。
在这种情况下,我们将对每个样本进行加权,乘以每个样本的可能性,以获得更准确的分布。那么这看起来是什么样的呢?如果我问同样的问题,给定火车准时,轻微降雨的概率是多少,当我进行采样程序时。
我将开始尝试抽样,我将先固定证据变量,我将已经在我的样本中知道的东西,知道任何我只抽样那些我知道变量的值的证据是我期望的,所以我们将从雨中抽样,也许这次抽样是轻雨。
因此,我将从轨道维护中抽样,并说也许是的,确实有轨道维护,至于火车,我已经将其固定在这里。火车是一个证据变量,所以我不会再去抽样了,我将继续进行,接下来我会去预约并进行抽样。
也来自于预约,因此现在我已经生成了一个样本,我通过固定这个证据变量并抽样其他三个变量生成了一个样本,最后一步就是为这个样本赋权重,权重是基于火车准时的概率。
实际上,证据发生在这个时间点,考虑到这些其他变量的值,轻雨和确实有轨道维护。那么为了做到这一点,我可以回到火车变量,假设如果有轻雨和轨道维护,我的证据的可能性,火车准时的可能性是0.6。
因此,这列火车,这个特定的样本将具有0.6的权重,我可以重复抽样过程,一次又一次,每个样本将根据与之相关的证据的概率赋予权重,还有其他抽样方法存在,但它们都是。
旨在尝试获取相同的思想,以近似推理程序,来确定一个变量的值,因此我们现在已经处理了与特定变量相关的概率,这些变量具有离散值,但我们还没有真正考虑的是,值如何可能随着时间的推移而变化。
考虑一个类似于雨的变量,其中雨可以取值为无雨、轻雨或大雨,但实际上,通常当我们考虑像雨这样的变量的值时,我们希望考虑的是随着时间的推移,这些变量的值是如何变化的,我们该如何处理。
在一段时间内的不确定性,这在天气的背景下可能会出现。例如,如果我有阳光明媚的日子和下雨的日子,我想知道的不仅仅是雨的概率,而是雨和不雨的概率。
现在正在下雨,但明天或后天,甚至再后天下雨的概率是什么?为了解决这个问题,我们将引入一种稍微不同的模型,在这里我们将有一个随机变量,不仅仅是天气,还包括每一个可能的时间步骤。
可以根据自己的喜好定义时间步长,一种简单的方法是将天数作为时间步长,因此我们可以定义一个变量叫做X sub T,这将是时间T的天气,因此X sub 0可能是第0天的天气,X sub 1可能是第1天的天气,依此类推,X sub 2是第2天的天气。
但正如你所想象的,如果我们开始在更长的时间段内进行这个过程,可能会涉及到大量的数据。如果你正在记录一年的天气数据,那么突然间你可能在试图预测明天的天气,基于365天之前的数据。
这将会是大量需要处理、操作和计算的证据,可能没有人知道所有这些变量组合的确切条件概率分布是什么,因此当我们试图在计算机中进行推理时,我们会面临挑战。
为了合理地进行这种分析,做一些简化假设是有帮助的,我们可以假设一些关于问题的假设是真实的,以使我们的生活稍微轻松一点,尽管它们可能并不完全准确。如果这些假设接近准确或大致正确,它们通常是相当好的。
好的,我们要做的假设称为马尔可夫假设,即当前状态仅依赖于有限固定数量的前几天。因此,当前一天的天气并不依赖于所有前几天的天气,而是只依赖于昨天的天气,或者仅依赖于过去两天或三天的天气。
但很多时候,我们会处理的只是一个前一个状态,这有助于预测当前状态,并通过将大量这些随机变量放在一起使用。
通过这个马尔可夫假设,我们可以创建一个称为马尔可夫链的东西。马尔可夫链只是一系列随机变量的序列,每个变量的分布都遵循马尔可夫假设,因此我们会做一个例子,在这个例子中,马尔可夫假设是我可以预测天气。
是晴天还是下雨,我们暂时只考虑这两种可能性,尽管还有其他类型的天气,但我可以仅根据前一天的天气来预测每天的天气。利用今天的天气,我可以为明天的天气提供一个概率分布。
这是以矩阵的形式表示的,我们可以将其描述为值的行和列。在左侧,我用变量X sub T表示今天的天气,而在这里的列中,我用变量X sub t plus 1表示明天的天气,T加1的天气而不是其他的。
矩阵所表达的是,如果今天是晴天,那么明天也是晴天的可能性更大。通常天气在连续几天内保持一致。例如,假设今天是晴天,我们的模型说,明天晴天的概率是0.8。
如果今天在下雨,那么明天也是下雨的可能性大于不大,概率为0.7,而晴天的概率为0.3。因此,这个矩阵,这种从一个状态转移到下一个状态的描述就是我们所称的转移模型。
使用转移模型,你可以开始构建这个马尔可夫链,只需预测今天的天气,明天的天气发生的可能性。你可以想象做一个更简单的抽样过程,使用这个方法。
你可以抽样明天的天气,使用这个方法来抽样第二天的天气,结果是你可以形成这样的马尔可夫链:在时刻X零,第一天是晴天,第二天也是晴天,接下来可能变为下雨,然后又是下雨,这种模式就出现了。
这个马尔可夫链遵循的分布是我们可以访问的。当天气晴朗时,它往往会保持晴朗,接下来的几天也会是晴天;而下雨时,它也往往会继续下雨,因此你得到的马尔可夫链看起来是这样的。
你可以对这个进行分析,比如说今天在下雨。那么明天也在下雨的概率是多少?或者你可以开始问概率问题,比如这一系列五个值的概率:晴天、晴天、雨天、雨天、雨天,并回答这类问题。
确实有很多Python库用于与概率模型交互,这些模型具有基于之前变量的分布和随机变量。根据这个马尔可夫假设,Pomegranate 2提供了处理这些类型变量的方法,所以我将继续深入马尔可夫模型。
我将继续深入链条。
目录中有一些关于马尔可夫链的信息,我在这里定义了一个名为model pi的文件,使用了非常相似的语法。再说一次,确切的语法并不那么重要,关键是我在将这些信息编码进一个Python程序,以便程序可以访问。
对于这些分布,我听说过,找到一些起始分布。因此,每个马尔可夫模型都从某个时间点开始,我需要给它一个起始分布。我们就假设一开始你可以选择晴天和雨天各50%。我们说晴天的概率是50%,雨天也是50%。
在下面,我定义了转移模型,即我如何从一天转移到下一天,并且我编码了今天是晴天的确切概率。然后,明天是晴天的概率为0.8,而明天是雨天的概率为0.2。同样,如果今天是雨天,我也有另一种分布。
仅凭这一点就定义了马尔可夫模型,你可以开始使用该模型回答问题,但我所做的一件事是从马尔可夫链中抽样。结果发现,这个马尔可夫链库内置了一个方法,可以让我从链中抽样50个状态。
这就像模拟50个天气实例。因此,我要运行这个Python模型。当我运行它时,我得到的结果是,它将从这个马尔可夫链中抽样50个状态,50天。
天气的样本是随机的,你可以想象多次抽样以获得更多数据,从而进行更多分析。但在这里,例如,今天是晴天,连续几天都是晴天,然后下了好几天的雨,才又变回晴天,因此你会得到这种。
遵循我们最初描述的分布,遵循晴天往往会导致更多晴天,雨天往往会导致更多雨天,这就是马尔可夫模型。马尔可夫模型依赖于我们知道这些个体状态的值。我知道今天是。
无论今天是晴天还是下雨,利用这些信息,我可以对明天的天气做出某种推断,但实际上,这种情况往往并非如此。很多时候我并不知道世界的确切状态,而世界的状态往往是未知的,但我能够。
在某种程度上,有一些关于状态的信息,机器人或人工智能并不对周围的世界有确切的知识,但它有某种传感器,无论是摄像头、测距传感器,还是感应音频的麦克风,例如它在感知数据并利用。
那些数据与世界的状态在某种程度上是相关的,即使它并不真正知道。我们的人工智能并不知道世界的真实状态是什么,因此我们需要进入传感器模型的领域,描述我们如何将隐藏状态转化为可观测状态。
世界的真实状态是什么,以及观察结果是什么,即人工智能所知的实际上是访问的内容。例如,隐藏状态可能是机器人的位置。如果一个机器人正在探索未知的领土,机器人可能并不确切知道自己在哪里,但它。
具有观察能力的机器人拥有传感器数据,可以感知周围可能的障碍物有多远,利用这些信息,基于它所观察到的信息,它可以推断一些关于隐藏状态的事情,因为真实的隐藏状态会影响这些。
不论机器人的真实位置是什么,都会影响机器人能够收集的传感器数据,即使机器人并不确切知道它的真实位置。如果你考虑一个语音识别或语音识别程序,它在监听时。
它能够响应你,类似于 Alexa 或你在 Apple 和 Google 的语音识别中所做的,你可能会想象这个隐藏状态,即底层状态是实际说出的单词,世界的真实本质包括你说的特定内容。
一系列单词,但你的手机或智能家居设备并不确切知道你说了什么,AI 仅能接收到的观察是一些音频波形,而这些音频波形当然依赖于这个隐藏状态,你可以推断。
基于这些音频波形,可以推测出说出的单词,但你可能无法百分之百确定这个隐藏状态到底是什么,预测这个观察下的隐藏状态可能是一项任务。根据这些音频波形,你能否弄清楚实际说出的单词是什么。
同样,你可以想象在一个网站上,真实的用户参与可能是你无法直接访问的信息,你可以观察一些数据,比如网站或应用分析,关于这个按钮被点击的频率或人们以特定方式与页面互动的频率,你可以利用这些数据。
也可以推断用户的信息,所以这种类型的问题在我们处理 AI 时经常出现,试图推测关于世界的事情,而 AI 往往并不真正知道世界的隐藏真实状态,所有 AI 能访问的只是与隐藏真实状态相关的一些观察。
但这并不是直接的,可能会有一些噪音,音频波形可能会有一些额外的噪音,解析传感器数据可能并不完全正确,存在一些噪音可能会使你无法确定隐藏状态,但可以让你推断出。
可能是这样,所以我们在这里看的简单例子是想象一下。
隐藏状态就像天气,无论是晴天还是雨天,你可以想象。你正在一个建筑物内部编程一个 AI,而这个建筑物可能只有一个摄像头可以观察内部,而你能访问的只是一个关于员工是否带伞进入建筑物的观察。
可以检测到是否是伞,因此你可能会有一个观察。结果,看看是否有伞被带进建筑物,使用这些信息你想。预测是晴天还是雨天,即使你不知道潜在的。天气是什么,因此潜在的天气。
可能是晴天或雨天,如果下雨,显然人们更有可能。带伞。因此,无论人们是否带伞,你的观察会告诉你一些关于。隐藏状态的事情,当然,这个例子有点牵强,但这里的想法是更广泛地考虑。
更一般地说,每当你观察某事时,都会与某个潜在的。隐藏状态有关。因此,尝试建模这种想法,我们有这些。隐藏状态和观察,而不是仅仅使用马尔可夫模型,该模型有。状态、状态、状态、状态,每个状态通过那个转换矩阵连接。
如前所述,我们将使用我们称之为隐藏马尔可夫模型,非常。类似于马尔可夫模型,但这将允许我们建模一个系统。具有我们无法直接观察的隐藏状态,以及一些观察事件。我们确实可以看到,因此,除了转换模型。
我们仍然需要说明你知道的,世界的潜在状态。如果是晴天还是雨天,明天的天气概率是什么,我们。还需要另一个模型,给定某种状态,能够给我们一个观察结果。比如说,绿色是有人带了伞进办公室,红色则是没有。
如果没人带伞进办公室,那么观察可能是,如果。是晴天,那么没人会带伞进办公室,但。也许有些人只是比较谨慎,仍然带伞。进办公室。如果下雨,那么概率就高得多。
概率然后人们会带伞进办公室,但也许如果。下雨是意外,人们没有带伞,因此他们可能会有。其他概率。因此,通过使用观察,你可以开始。合理地预测潜在状态,即使你。
如果你看不到潜在状态,也就是说。你无法看到隐藏状态的实际值,这里我们通常称之为传感器。模型,它也常常被称为。
发射概率因为潜在状态发出某种。发射,然后你观察到,因此,这可以是描述。相同想法的另一种方式,而我们将使用的传感器马尔可夫假设是。这是一个假设,证据变量,也就是我们观察到的事物。
产生的发射仅依赖于相应的状态,这意味着它。可以预测人们是否会带伞,完全。依赖于今天是晴天还是雨天,当然,这个假设在实践中可能不成立。
人们是否带伞可能不仅取决于今天的天气,还可能取决于昨天和前天的天气,但为了简化,假设这可以帮助我们更容易地推理这些概率。
如果我们能够近似它,我们仍然可以得到一个非常好的答案。因此,这些隐马尔可夫模型最终看起来有点像一连串的状态,如晴天、晴天、下雨、下雨,我们反而有一个上层,代表世界的潜在状态,是晴天还是下雨。
这些状态通过我们之前描述的转移矩阵连接,但每个状态产生的观察结果我看到的是,这一天是晴天而人们没有带伞,而那一天是晴天但人们带了伞,还有这一天在下雨。
人们确实带了伞等等,这些潜在状态由 X_sub T 表示,如 X_sub 0, 1, 2 等等,产生某种观察或发射,e 代表的就是这些,e_sub 0, e_sub 1, Y_sub 2 等等,这也是一种尝试。
要表示这个理念,你需要考虑的是,这些潜在状态是真实世界的本质,机器人在时间中移动时的位置,从而产生某种可能被观察到的传感器数据,或人们实际说的话,以及关于音频的发射数据。
在这种信息下,你可能想要完成的任务有很多,其中最简单的之一是尝试推断未来或过去,或者这些可能隐藏的状态。
存在的任务往往是你会看到的,我们不打算深入探讨这些任务的数学,但它们都基于条件概率的相同理念,并使用我们拥有的概率分布来得出这些结论。其中一个任务叫做过滤,涉及到你检测到的波形,以处理这些数据并试图弄清楚。
从开始到现在的观察结果,计算当前状态的分布,这意味着根据从时间开始到现在的信息,哪些日子人们带伞或不带伞,我能否计算出当前状态的概率,也就是今天是晴天。
另一个可能的任务是预测,这意味着在观察到人们从开始到现在带伞的情况下,向未来看,我能否推测明天是晴天还是下雨。
你也可以向后推断,通过平滑处理,我可以说,从开始到现在的观察结果,某个过去状态的分布,例如,我知道今天人们带了伞,明天人们也带了伞,因此基于两天的数据,带伞的人数是多少。
昨天下雨的概率,以及我知道今天人们带了伞,这也可能影响这个决定,并可能影响这些概率。此外,还有一个最可能的解释任务,除了可能存在的其他任务,这涉及到组合。
这些观察结果从一开始到现在,找出这些。
最可能的状态序列,这就是我们现在要关注的内容。这个想法是,如果我有所有这些观察结果,伞与不伞,伞与不伞,我能否计算出最可能的太阳和雨的状态。
这些观察结果是相当常见的,特别是在你尝试做一些像语音识别的事情时,你会有这些音频波形的发射结果,并且你希望根据你所拥有的所有观察结果来计算出最可能的实际单词序列。
用户在与这个特定设备或其他可能出现的任务交谈时所发出的音节或声音,因此我们可以通过进入hmm目录来尝试这个。
hmm代表隐马尔可夫模型,这里我定义了一个模型,这个模型首先定义了我可能的状态,太阳和雨,以及它们的发射概率,观察模型或发射模型,在这里考虑到我知道是晴天,我看到人们的概率。
带伞的概率为0.2,而不带伞的概率为0.8。如果下雨,那么人们更可能带伞,带伞的概率为0.9,不带伞的概率为0.1,因此实际的潜在隐含状态是太阳和雨,但我观察到的事情就是我能看到的观察结果。
我观察到的结果要么是带伞,要么是不带伞,因此我还需要为此添加一个转移矩阵,和之前一样,说明如果今天是晴天,那么明天更可能是晴天;如果今天是雨天,那么明天更可能是下雨。和之前一样,我给它一些起始值。
概率一开始你知道,无论是晴天还是雨天,都是50-50的机会,然后我可以根据这些信息创建模型。这个语法的确切形式并不是那么重要,重要的是我现在正在编码的数据,以便我现在可以开始进行一些推断。
给我的程序一个观察列表,比如伞、伞、没伞、伞、伞,等等,没伞、没伞,我想计算,找出这些观察的最可能解释,最有可能的是否是下雨,或者说下雨。
可能更有可能的是实际上是晴天,然后又变成下雨,这是个有趣的问题,我们可能不太确定,因为可能只是恰好在这一天人们决定不带伞,或者可能是从下雨变成晴天再变回。
下雨的情况似乎不太可能,但确实可能发生,利用我们提供给隐马尔可夫模型的数据,我们的模型可以开始预测这些答案,逐步找出,所以我们将继续预测这些观察结果,然后对每个预测进行打印。
预测是什么,这个库恰好有一个函数。
被称为预测,它为我执行这个预测过程,我将运行Python序列,得到的结果是这个!
基于观察的预测,关于所有这些状态可能是什么,最有可能的是下雨,在这种情况下,它认为最有可能发生的是,先是晴天一天,然后又变成下雨,但在不同的情况下,如果下雨的时间更长,也许。
概率稍有不同,你可能想象更可能的是一直下雨,只是在某个雨天人们决定不带伞,所以这里有两个Python库可以开始进行推断程序。
利用我们所知道的,并将其转换为这些已经存在的任务,这些与隐马尔可夫模型协作的通用任务,那么每当我们能够将一个想法表述为隐马尔可夫模型,将其表述为具有隐状态和由此产生的观察发射的东西。
状态,然后我们可以利用这些已知存在的算法。
尝试做这种朋友,所以现在我们已经看到几种方式,我可以开始处理不确定性,我们查看了概率,以及如何使用概率来定量描述那些可能或更可能或不太可能发生的事情。
或其他变量,利用这些信息,我们可以开始构建这些标准类型的模型,比如贝叶斯网络、马尔可夫链和隐马尔可夫模型,这些都允许我们描述特定事件与其他事件之间的关系,或者特定变量的值与。
其他变量虽然不确定,但具有某种概率分布。通过将事物表述为这些已有模型,我们可以利用已经实现这些模型的Python库。
这样,我们的AI就能够开始处理这些不确定的问题,而不需要确切知道事情,但可以根据未知信息进行推断。下次我们将看看可以利用AI解决的其他类型的问题。
相关算法甚至超越了我们已经讨论过的问题类型的领域。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P11:L3- 优化算法 1 (优化,局部搜索,爬山算法) - ShowMeAI - BV1AQ4y1y7wy
[音乐],欢迎大家回到关于人工智能的介绍,使用Python。目前为止,我们已经看过几种不同类型的问题,我们看到经典的搜索问题,在这些问题中,我们试图从初始状态到达目标,通过找出一些最佳路径。
在对抗性搜索中,我们有一个游戏代理,它试图做出最佳移动。我们已经看到基于知识的问题,当我们试图使用逻辑和推理来弄清楚并得出一些额外结论时,我们也看过一些概率模型。
我们可能没有关于世界的某些信息,但我们希望利用我们拥有的概率知识来得出一些结论。今天我们将把注意力转向另一类问题,通常被称为优化问题,优化实际上是关于。
从一组可能的选项中选择最佳选项,我们已经在某些上下文中看到了一些优化,比如游戏玩法,我们试图创建一个选择最佳移动的人工智能,在一组可能的移动中。但今天我们要看的是一类问题和算法。
解决这些问题的算法可以用于处理更广泛的潜在优化问题,而我们将要看看的第一个算法是。
被称为局部搜索,局部搜索与我们之前看到的搜索算法不同,因为到目前为止,我们看到的搜索算法,比如广度优先搜索或A*搜索,通常会维持一大堆不同的路径,同时。
在探索时,我们同时查看一堆不同的路径,试图找到解决方案。另一方面,在局部搜索中,这将是一个搜索算法,实际上只会维持一个单一节点,查看一个单一状态,并通常通过运行此算法。
维持那个单一节点,然后在整个搜索过程中将自己移动到邻近的节点,这在上下文中通常是有用的,不像我们之前看到的那些问题,比如迷宫求解情况,我们试图从初始状态找到目标。
我们已经查看了通过某些路径来进行搜索,但局部搜索最适用于我们根本不在乎路径的情况,我们关心的只是解决方案。在解决迷宫的情况下,解决方案总是显而易见的,你可以指向解决方案,你确切知道目标是什么,真正的问题是。
到达那里需要什么路径?但是局部搜索在这种情况下会出现,其中准确找出解决方案的目标是什么正是挑战的核心。为了举一个这样的例子的例子,我们将考虑一个场景,其中有两种类型的建筑。
例如,我们有房屋和医院,我们的目标可能是在这样一个网格格式的世界中,其中有很多房屋,一个房屋在这里,一个房屋在这里,两个房屋在那边,可能我们想要找到一种方法在这张地图上放置两个医院。
现在的问题是我们想在地图上放置两个医院,但我们希望有某种目标,而我们在这种情况下的目标是尽量减少任何房屋到医院的距离。因此,你可能想知道,从每个房屋到最近医院的距离是多少。
在医院之间,我们可以有多种方式来计算距离,但一种方法是使用我们之前提到的启发式,即曼哈顿距离。这种方法是计算在这个网格布局中,你需要移动多少行和列才能到达医院,例如,如果。
你可以拿这四个房屋,看看它们离最近的医院有多近,你会得到这样的结果:这所房屋离医院三单位,这所房屋离医院六单位,而这两所房屋各离医院四单位。如果将这些数字加起来,你会得到一个总成本。
以17为例,所以在这个特定的医院配置中,一个医院在这里,另一个医院在那,这种状态我们可以说其成本为17。现在我们希望应用搜索算法来解决这个问题,找出如何最小化这个成本。
如果将所有房屋到最近医院的距离加起来,总体量如何最小化?如果我们稍微抽象化一下这个问题,从这个具体问题中抽离,考虑更普遍的类似问题。
通常通过将这些问题视为状态空间来构造这些问题。正如科林在这个状态空间景观的图示中所展示的那样,每个垂直条代表我们世界中可能的一个特定状态。
两个医院的配置,垂直条的高度通常表示那个状态的一些函数,某个值。因此,也许在这种情况下,垂直条的高度代表这个特定医院配置的成本。
从所有房屋到其最近医院的距离,通常而言,当我们有一个状态空间时,我们想做两件事情之一:我们可能试图最大化这个函数的值,试图找到一个全球最大值,可以说这个状态空间的单个。
状态的值高于我们可能选择的所有其他状态,通常在这种情况下,我们将称之为最大值,我们要优化的函数称为某个目标函数,这是一个衡量给定状态的好坏的函数,以便我们可以传递任何状态。
输入到目标函数中,并获得该状态的好坏值,最终我们的目标是找到这些状态中的一个,使其在该目标函数下具有最高的可能值。而等价但反向的问题是寻找全局最小值的问题,即找到某个状态,其具有。
在你将其传递到这个函数后,得到的值低于我们可能选择的所有其他值,一般而言,当我们试图寻找全局最小值时,我们称之为正在计算的函数为成本函数,通常每个状态都有某种成本,无论该成本是。
货币成本或时间成本,或者在我们刚才看的房屋和医院的情况下,是距离成本,衡量每个房屋距离医院的远近,我们要尽量减少成本,找到具有最低可能成本值的状态,这些是一般情况。
我们可能在状态空间中尝试的一些想法,试图找到全局最大值或全局最小值,我们到底是如何做到的,将会回忆起,在局部搜索中,我们通常通过维持一个单一状态来操作这个算法,也就是某个当前状态。
在某个节点内部表示的状态可能是在一个数据结构中,我们在其中跟踪当前的位置,最终我们要做的就是从该状态移动到它的一个邻居状态,在这种情况下,这个状态在一维空间中由紧邻的状态表示。
向左或向右移动,但对于任何不同的问题,你可能会定义某个状态的邻居的含义,例如在我们刚才查看的医院案例中,邻居可能是将一个医院移动一个空间到左边或右边,或者上下移动某个状态。
接近我们当前的状态,但略有不同,因此在其目标函数或成本函数上可能有略微不同的值,所以这将是我们在局部搜索中的一般策略,以便能够保持一个状态,维持某个当前节点并移动到我们。
在状态空间的景观中,试图找到一个全局最大值或全局最小值。也许我们可以用来实现这个局部搜索理念的最简单算法是一个称为爬山算法的算法,爬山算法的基本思路是,假设我在尝试。
最大化我的状态值,我试图弄清楚全局最大值在哪里。我将从某个状态开始,通常爬山算法将考虑那个状态的邻居,从这个状态出发,我知道我可以向左或向右走。
如果邻居的值较高而另一个邻居的值较低,在爬山算法中,如果我试图最大化值,我通常会选择我能找到的最高值,在我左边和右边的状态中,较高的状态是这个。所以我们会前进,考虑这个状态。
不断重复这个过程,查看我的所有邻居,选择邻居中值最高的,做同样的事情,查看我的邻居,选择邻居中值最高的,直到我达到像这里的某个点,在那里我考虑了我的两个邻居,而这两个邻居的值都低于我。
此时算法终止,我可以说,好吧,我现在找到了这个解决方案,反过来同样的过程适用于寻找全局最小值,但算法的基本原理是相同的。如果我试图寻找全局最小值,假设我当前的状态从这里开始,我会。
不断查看我的邻居,选择我能找到的最低值。直到我最终希望找到全局最小值,这个点在我查看两个邻居时,它们的值都更高,而我试图最小化总分或成本或结果值。
计算某种成本函数,所以我们可以用伪代码将这个图形概念公式化,爬山算法的伪代码可能看起来像这样:我们定义一个名为hill climb的函数,它接受我们试图解决的问题作为输入,通常我们会从某个地方开始。
一种初始状态,所以我将从一个名为current的变量开始。这个变量用于跟踪我的初始状态,就像医院的初始配置一样。也许有些问题适合于某个初始状态,在那里你可以开始,而在其他情况下则不一定,在这种情况下,我们可能会随机生成。
一些初始状态,举例来说,可以随机选择两个地点,四个医院,然后从那里开始考虑如何改善,但我们要存储的初始状态将放在current里面。现在我们进入循环,这个重复的过程我们会不断进行,直到算法。
终止,我们要做的是,首先说,让我们弄清楚当前状态的所有邻居,从我的状态,所有的邻居状态是什么,基于某种定义的邻居的意思,我将选择所有邻居中价值最高的,并保存它。
在这个名为邻居的变量中,跟踪最高价值的邻居,这是我试图最大化价值的情况。在我试图最小化价值的情况下,你可以想象你会选择具有最低可能值的邻居,但这些想法在根本上。
这些是可以互换的,在某些情况下,可能有多个邻居具有同样高或同样低的值。在最小化的情况下,我们可以随机选择其中一个,选择其中一个并将其保存在这个变量邻居中。
那么要问的关键问题是,这个邻居是否比我当前的状态更好。如果我能找到的最佳邻居不比我当前的状态更好,那么算法就结束了,我将返回当前状态,如果没有一个邻居比我更好,我可能。
好吧,停留在我所在的位置是爬山算法的一般逻辑,但如果邻居更好,我就可以移动到那个邻居。你可以想象将当前状态设置为邻居,基本思路是,如果我处于一个当前状态,并且看到一个比我更好的邻居。
我将继续移动到那里,然后不断重复这个过程,移动到更好的邻居,直到我达到一个点,此时没有一个邻居比我更好,在那个点我们可以说算法就可以终止了。那么我们来看一个实际的例子。
这些房子和医院,所以我们现在已经看到,如果我们将医院放在这两个位置,总成本为17,现在我们需要定义,如果我们要实现这个爬山算法,这意味着要取这个特定配置的医院,这个特定状态并获得。
这个状态的一个邻居,邻居的简单定义可能是,选择一个医院,并将其移动一个方块,向左、向右、向上或向下,例如,这意味着我们从这个特定配置中有六个可能的邻居。
这个医院可以移动到这三个可能的方块中的任何一个,或者我们将这个医院移动到那些三个可能的方块,每一个都会生成一个邻居。我可能会说,好吧,这里是位置和每个房子到最近医院的距离。
让我考虑所有邻居,看看是否有任何一个能够使成本低于17。结果发现,有几种方式可以做到这一点,随机选择其中任何一种最佳方式都没关系,但其中一种可能的方法是看看这里的这家医院。
考虑到如果我们保持这家医院不变,它可能会朝哪个方向移动。如果我们将这家医院向上移动一个单位,例如,这实际上并没有帮助它更靠近上面的房子,但却离下面的房子更远,并且对整体并没有改变什么。
但是如果我们将右侧的这家医院向下移动一个单位,问题就正好相反,它会离上面的房子更远,但却更靠近下面的房子。因此,目标应该是能够将这家医院移动一个单位。
向左移动一个单位,我们使它更接近右侧的这两栋房子,而不改变左侧房子的情况。对他们而言,这家医院仍然是更近的一个,因此他们并没有受到影响,因此我们能够通过选择一个邻居来改善情况。
这将导致我们总成本的减少,因此我们可能会自己将状态从当前状态移动到一个邻居,只需将这家医院移动一下。在这一点上,这家医院实际上能做的事情并不多,但我们仍然可以进行其他优化,移动到其他邻居。
如果我们考虑这家医院,我们可能想象现在它的位置稍微高于这两栋房子,因此我们可能通过将这家医院向下移动一个单位来做得更好。
这样一来,成本从15降到了13,对于这种特定配置来说,我们甚至可以更好地将医院向左移动一个单位,这样成本从13降到了11,因为这栋房子离医院只有1个单位,这栋房子离4个单位,这栋房子离3个单位,而这栋房子也是离3个单位。
我们已经能够在使用初始配置的情况下,做到比最初的成本好得多,仅仅通过询问自己一个问题:通过做小的增量变化,移动到一个邻居,移动到另一个邻居,我们能否做得更好?现在在这个。
此时我们可以看到,算法可能会终止,因为实际上没有邻居可以移动到并改善情况,使我们的成本低于11。如果我们将这家医院向上或向右移动,那将使它距离左侧的两栋房子更远。
我们把它移动到下方,这并没有真正改变情况,它离这栋房子更远,但离那栋房子更近。同样,这个医院的情况也是如此,我们无论移动到上、左、下还是右的任何邻居,都会使其离房子更远,并增加。
成本上,或者它根本不会对成本产生任何影响。因此,我们现在可能会问,这是我们能做到的最好的吗?这是我们能够拥有的医院的最佳位置吗?结果答案是否定的,因为有更好的方法可以放置这些医院。
这里有许多方法可以做到这一点,但其中一种方法是把这个医院移动到这个方格,例如对角线移动一个方格,这并不是我们邻居定义的一部分,我们只能向左、右、上或下移动,但实际上这更好,它有一个。
总成本为九,它离这两栋房子都没有更近,因此,总成本较低,但我们无法找到它,因为要到达那里,我们必须经过一个状态,而那个状态实际上并不比我们之前所处的当前状态更好,因此这似乎是一个。
在尝试实施爬山算法时,可能会有一个限制或顾虑,即它可能并不总是给你最优解。如果我们试图最大化任何特定状态的值,试图找到全局最大值,可能会担心我们会卡在某个。
这里用蓝色突出显示的局部最大值,局部最大值是指任何状态,其值高于任何邻居。如果我们发现自己处于这两个状态中的任何一个,当我们试图最大化状态值时,我们不会做出任何改变,我们不会向左或。
我们不会在这里向左移动,因为那些状态更糟,但我们还没有找到全局最优解,我们没有做到最好。同样,在医院的案例中,我们最终想要做的是找到一个全局最小值,找到一个低于其他所有值的值,但我们。
可能会在局部最小值之一的状态中卡住,这些状态的值低于所有邻居,但仍然不如局部最小值低。因此,这里要记住的是,当我们运行这个简单的爬山算法时,并不总是能够找到最优解。
解决方案。如果我们从这里开始,尽可能地尝试最大化我们的价值,可能会出错,我们可能会移动到最高的邻居,移动到最高的邻居,再移动到最高的邻居,然后停下来,永远不会意识到实际上有一个更好的。
如果你查看这个状态空间景观,可能会想象到各种不同类型的平坦区,例如,这里的局部最大值,其中六个状态的值完全相同。
在之前展示的算法中,没有任何邻居更好,所以我们可能会卡在这个平坦的局部最大值,甚至如果允许自己移动到一个邻居,也不清楚最终会移动到哪个邻居,可能会在这里卡住。
这里有一个变体称为随机爬山,另一个则称为肩部,这并不是真正的局部最大值,因为我们仍然可以走向更高的地方,也不是局部最小值,因为我们可以下降,因此仍然可以取得进展,但这仍然是一个平坦区域。
在这里迷失,无法向上或向下取得进展,具体取决于我们是在最大化还是最小化,因此我们可能无法找到实际上不是最佳解的解决方案。
攀登并不总是能为我们找到最佳结果,实际上有许多不同的种类和变体的爬山算法,可以帮助根据上下文和特定问题类型更好地解决问题。
迄今为止我们所考虑的更适用的是一种通常称为最陡上升爬山的版本,最陡上升爬山的想法是我们将选择在试图最大化时值最高的邻居,或者在试图最小化时值最低的邻居。
通常来说,如果我有五个邻居,而他们都比我当前的状态好,我会选择这五个邻居中最好的一个。有时这可能效果很好,这是一种贪婪的方法,试图在任何特定时间步骤中采取最佳操作,但也可能不行。
这些变体并不总是有效,可能会出现我想选择一个稍微好一点的选项,而不一定是最好的选项,因为这可能会导致最终更好的结果。
因此,选择所有更高值邻居中的一个是另一种可能性,所以如果我在当前状态,并且有五个邻居都比我好,那么与选择最佳的最陡上升相比,随机选择将从其中一个中随机选择。
认为如果它更好,那就更好,并且或许有可能向前推进,即使它不是我可能选择的局部最佳选项。首选爬山最终只是选择第一个最高价值的邻居,类似于这种想法,而不是考虑。
一旦我们找到一个比当前状态更好的邻居,我们就会去那里,也许能在这里提高一些效率,或许能找到其他策略无法找到的解决方案,尽管所有这些变体我们仍然。
面临着相同的潜在风险,即我们可能会陷入局部最小值或局部最大值,我们可以通过多次重复该过程来降低这种风险。因此,爬山的一种变体是随机重启爬山,其中一般思路是如果。
应用其最陡上升爬山,例如将从某个随机状态开始,尝试找出如何解决问题,并确定我们到达的局部最大值或局部最小值,然后我们将随机重启并重试,选择新的起始配置并尝试找出。
找出局部最大值或最小值,并进行一定次数的重复,然后在我们查看过的所有选项中选择最佳的一个,所以我们还有另一个选择可以访问。虽然我提到一般的局部搜索通常只会保持。
追踪一个单一节点,然后移动到其邻居之一。存在一种称为局部束搜索的爬山变体,其中我们不仅跟踪一个当前最佳状态,而是跟踪 K 个最高价值的邻居,因此我们不会仅从一个随机初始配置开始。
我可能从三个、四个或五个随机生成所有邻居,然后选择我找到的所有邻居中最好的三、四或五个,并不断重复这个过程,想法是我现在考虑了更多选项,有更多的方式我可以潜在地导航。
最终找到某个特定问题的最优解,所以现在让我们看看一些可以实现这些想法的实际代码,比如最陡上升爬山,以解决这个医院问题,因此我将。
继续进入我医院的目录,在这里我实际上建立了一个解决这类问题的基本框架。接下来,我将进入医院的 PI,我们来看看我们在这里创建的代码。我定义了一个类,用于表示状态空间,因此该空间具有高度。
这里是一个宽度,还有一些医院数量,所以你可以配置你的地图有多大,应该有多少医院。这里我们有一个添加新医院到状态空间的函数,然后一些函数将为我获取所有可用的空间,如果我想随机放置医院的话。
这里是爬山算法。那么我们在爬山算法中要做什么呢?我们将开始随机初始化医院的位置,我们不知道医院实际应该在哪里,所以我们就随机放置它们。
对于我没有的每个医院运行循环,我会随机在某个位置添加一所新医院,所以我基本上获得所有可用空间,并随机选择其中一个作为我希望添加这所特定医院的位置。我有一些日志输出和生成。
图片,我们稍后会看一下,但这里是关键想法,我会一直重复这个算法。我可以指定一个最大运行次数,或者我可以一直运行直到达到局部最大值或局部最小值,现在我们基本上会考虑所有当前的情况。
考虑这两所医院以及更多医院(如果有的话),并考虑该医院可以移动到的一些地方,即该医院的邻居,我们可以将邻居移动到的地方,然后看看这是否会比特定位置更好。
如果这会更好,那么我们就更新最佳邻居,跟踪我们找到的新最佳邻居,然后之后我们可以问自己,如果最佳邻居的成本大于或等于当前医院集合的成本。
这意味着如果我们最佳邻居的成本大于当前成本,意味着我们最佳邻居比当前状态更差,那么我们就不应该做任何更改,我们应该直接返回当前的医院集合。否则,如果我们可以更新医院以改变。
将它们连接到最佳邻居之一,如果有多个都是等效的,我这里使用随机选择,如果他们随机选择一个。那么这实际上只是一个Python实现,我们刚才讨论的这个想法,采用当前状态的一些当前医院集合。
生成所有邻居,查看我们可以将一所医院向左、向右、向上或向下移动一个单位的所有方式,然后根据所有这些信息找出哪个是最佳邻居,或者所有最佳邻居的集合,并从中选择一个。
每次我们生成一个图像以实现这一点。因此,现在如果我们看看底部,我将随机生成一个高度为10、宽度为20的空间,并说去随机放置三个医院在这个空间中,我将随机生成15栋房子。
我将随机位置添加进去,现在我要运行这个程序。
使用爬山算法来尝试确定我们应该将医院放置在哪里,因此我们将运行这个程序,通过运行Python来处理医院。我们看到初始状态的成本为72,但我们能够不断找到能够降低成本的邻居。
距离降到了69、66、63,依此类推,最终降到53,成为最佳方案。
邻居,最终我们能够找到的,我们可以看看那个。
通过打开这些文件可以看到,例如,最初的配置是我们随机选择了这15栋不同房子的一个位置,然后随机选择了一个、两个、三个医院的位置,这些医院就位于这个状态空间的某个地方。如果将所有的距离加起来...
从每栋房子到最近医院的距离总共约为72,现在的问题是我们能移动到哪些邻居来改善情况,算法首先找到的一个邻居是将那栋位于右侧的房子向左移动。
这可能是有道理的,因为如果你看看那个地区的房子,这五栋房子看起来相对密集,它们可能是离这家医院最近的,向左移动会减少大部分房子的总距离,尽管这会增加...
其中一个的距离,因此我们能够对情况进行这些改进,但不断寻找能够移动医院的方法,直到我们最终在这个特定状态上稳定下来,其成本为53,或者我们找到了每个医院的位置,而现在没有...
我们可以移动到的邻居是。
实际上要改善这种情况,我们可以拿这家医院和另一家医院进行比较,看看每个邻居,结果没有任何一个邻居比这个特定的配置更好。再说一次,这并不是说这是最佳方案,我们可能还有其他选择。
医院的配置是一个全局最小值,而这可能只是一个局部最小值,是所有邻居中最好的,但在整个可能的状态空间中可能不是最佳的。你可以通过考虑所有可能的医院配置来搜索整个状态空间。
但从根本上来说,这将非常耗时,尤其是当我们的状态空间变得更大时,可能会有越来越多的可能状态,查看所有状态将需要相当长的时间,因此能够使用这些局部搜索算法通常是非常有效的。
尝试找到我们能做到的最佳解决方案,尤其是如果我们不在乎做到最好,而只关心找到一个不错的医院布局,那么这些方法可能特别强大,但当然我们可以尝试通过使用其他方法来缓解一些担忧。
而不是使用爬山算法,而是随机重启这个想法,意思是我们可以进行多次爬山,而不是仅仅一次,在完全相同的地图上尝试爬山,找出我们能找到的最佳结果,因此我在这里实现了一个随机重启的函数。
随机重启一些最大次数,我们将重复这个过程,我刚刚运行爬山算法,计算从所有房屋到医院的费用,然后确定这是否比我们迄今为止的结果更好。
因此,我可以尝试这个相同的想法,而不是运行爬山算法,我将运行随机重启,并且!
我将随机重启大约20次,例如,现在我将移除所有图像,然后重新运行程序,最初运行爬山时我们找到的最佳成本是56。每次迭代都是爬山算法的不同迭代。
我们并不是运行爬山算法一次,而是在这里进行20次,每次都寻找局部最小值,看看每次是否比我们迄今为止的最佳时间更好,从56到46,这一次更高,所以我们忽略了,而41更低,所以我们继续保持。
我们尝试实施爬山算法的16次中,最后一次却没有做到比41更好,或许有更好的方法我们没有找到,但看来这条路最终成为了一个相当不错的解决方案。
问题是这是尝试的第三次,从零开始计数,我们可以看看打开第三次,这就是发生的状态。!
经过对一些特定随机初始医院配置运行爬山算法后,费用为41,这就是我们找到的局部最小值,试图最小化成本,看起来我们做得相当不错,这家医院距离这个区域很近。
离这些房子相当近的这家医院,看起来是我们能做的,尽量捕捉到那边的房子,所以这类算法对于解决这些问题相当有用,但是许多不同类型的爬山算法的真正问题在于。
陡峭上升、随机选择等算法,从不做出会让我们情况更糟的移动,对吧?它们总是会查看当前状态的邻居,考虑我们是否能比当前状态做得更好,并移动到那些邻居中的一个。
我们选择的邻居可能会因这些不同类型的算法而有所不同,但我们从不会从当前位置移动到一个比当前更糟的位置,最终,如果我们想找到一个全局最大值或全局最小值,这正是我们需要做到的。
因为有时候如果我们遇到瓶颈,我们想找到一些方法来摆脱我们在局部最大值或局部最小值的困境,以便找到。
哈佛CS50-AI | Python人工智能入门(2020·完整版)- P12:L3- 优化算法 2(线性搜索,节点一致性)- ShowMeAI - BV1AQ4y1y7wy
增加我们找到它的概率,因此最流行的技术是从这个问题入手。
角度是一种称为模拟退火的技术,之所以称为模拟是因为它模拟了一个真实的物理过程,即退火过程,你可以将其视为物理中的一种情况,涉及一些粒子系统,并且你可能会想象,当你加热某个特定的物理。
系统中有很多能量,事物随机移动得相当频繁,但随着时间的推移,随着系统降温,它最终稳定在某个最终位置,这就是模拟退火的一般思路。我们将模拟某个高温系统的过程,其中。
事物会随机移动得相当频繁,但随着时间的推移,降低这个温度。
直到我们最终稳定在我们的最终解决方案,想法是如果我们有一些状态空间,景观看起来像这样,我们从初始状态开始,如果我们在寻找全局最大值并且我们。想要最大化状态的值,我们的传统爬山。
算法只会获取这个状态,并查看两个相邻的状态和。
总是选择一个能够增加状态值的,但如果我们想有机会找到全局最大值,我们不能总是做出好的移动,有时我们必须做出糟糕的移动,允许自己朝着一个方向移动,这在目前看来似乎会让我们的情况。
更糟,这样之后我们可以找到通往全局最大值的路径,以解决该问题。当然,一旦我们达到这个全局最大值,经过大量搜索后,我们可能不想移动到比当前状态更糟的状态,因此。
这就是退火隐喻开始发挥作用的地方,我们想开始进行更多随机的移动,并随着时间的推移,基于特定的温度计划,逐渐减少这些随机移动,因此基本轮廓看起来像这样:在模拟退火的早期,我们有一个较高的温度。
状态,以及我们所指的较高温度状态是,我们更可能接受比当前状态更糟的邻居,但我们可能会查看我们的邻居,如果其中一个邻居比当前状态更糟,尤其是如果它并没有太糟,只是稍微糟一点。
那么我们可能更愿意接受,并继续移动到那个邻居那里,但随着我们运行模拟退火,稍后我们将降低温度,而在较低的温度下,我们将不太可能接受邻居。
那些比我们当前状态更差的状态,现在为了使这个更正式,下面是一些伪代码!
这个算法可能看起来像我们有一个叫做模拟退火的函数,它以我们试图解决的问题为输入,并可能还包括我们希望运行模拟退火过程的最大次数,以及我们将尝试查找多少个不同的邻居。
将根据你要解决的问题而变化,再次从某个当前状态开始,这将等于问题的初始状态,但现在我们需要重复这个过程多次,最多重复某个过程的次数。
计算一个温度,这个温度函数接受当前时间T,从1开始,一直到最大值,然后给我们一个可以在计算中使用的温度,其想法是这个温度在早期会较高,而后来会较低。
因此,这个温度函数可以有多种工作方式。最简单的方法之一就是它像剩余时间的比例,从最大时间单位中我们还剩多少时间,你一开始剩下很多时间,随着时间的推移。
温度将会下降,因为你剩下的时间越来越少。因此,我们计算当前时间的温度,然后随机选择一个当前状态的邻居,而不再选择我们可能能选择的最佳邻居。
我们能选择的较好的邻居之一,我们会随机选择一个邻居,它可能更好也可能更差,但我们会计算,计算能量的Δe,这只是邻居比当前状态好多少,因此如果Δe为正,这意味着状态。
如果Δe为负,这意味着邻居比我们当前状态更差,因此我们可以有一个条件,像这样,如果Δe大于0,这意味着邻居状态比我们当前状态更好,如果这种情况出现,我们将更新当前状态。
邻居和之前一样,移动到我们当前的邻居,因为这个邻居比我们当前状态更好,我们将接受这个选择。但现在的不同之处在于,以前我们绝对不想采取使情况变糟的移动,而现在我们有时想要采取这种“奇迹”般的移动。
移动实际上会使我们的情况变得更糟,因为有时我们需要摆脱局部最小值或局部最大值,以提高找到全局最小值或全局最大值的概率。那么我们该如何做到呢?我们如何决定有时。
接受一些实际上可能更糟的状态。我们将以某种概率接受一个更糟的状态,而这个概率需要基于几个因素,部分基于温度,如果温度更高,我们更有可能移动到更糟的邻居。
温度降低时,我们不太可能移动到更差的邻居,但也应该在某种程度上基于Delta e。如果邻居比当前状态差得多,我们可能会更不愿意选择这个邻居,而不是当邻居仅比当前状态稍差时。所以再次说,这里有一个。
有几种方法可以计算这个,但事实证明最流行的方法之一是计算e的幂,Delta e除以T,其中e只是一个常数。Delta e除以T是基于Delta e和T,在这里我们计算这个值,这个值将在0到1之间,这是概率。
我们应该说,好吧,继续移动到那个邻居。事实证明,当Delta e的值使得邻居与当前状态没有那么差时,我们更可能继续移动到那个状态,同样当。
当温度降低时,我们也不太可能移动到那个邻居状态。所以现在这是模拟退火的全局视角。这个过程涉及将问题进行处理,并生成随机邻居,如果邻居比当前状态更好,我们将始终移动到邻居。
但即使邻居比我们当前的状态更糟,有时我们仍会移动到那里,这取决于有多糟,也取决于温度。因此,这整个过程的希望和目标是,当我们开始寻找局部或全局最大值或全局最小值时,我们可以。
如果我们在局部最大值或局部最小值卡住了,我们需要摆脱这种情况,以最终探索状态空间中最佳的部分。随着温度的降低,我们最终会在我们发现的状态附近稳定下来,而不会移动太多。
到目前为止,我们可以做的最好事情。因此,在最后我们只需返回当前状态,这就是这个算法的结论,我们已经能够找出解决方案。这些类型的算法有很多不同的应用场景。
我们可以将一个问题表述为某种形式,探索一个特定的配置,然后问,是否有任何邻居比当前配置更好,并且有某种测量方法,那么对于这些爬山算法、模拟退火类型的算法就有适用的案例。
有时候这可以用于设施选址问题,比如在规划一个城市时,确定医院应该在哪里,但也确实有其他应用,计算机科学中最著名的问题之一就是旅行商问题。
销售员问题通常被表述为,我这里有一大堆城市,用这些点表示,而我想做的是找到一条经过所有城市并最终返回起点的路线。也就是说,一条从这里出发,经过所有这些城市,最后回到起点的路线。
回到我最初开始的地方,我可能想做的是最小化我必须旅行的总距离,或者说是整个路径的总成本。你可以想象这是一个在交付公司试图将物品送到许多不同房屋时非常适用的问题。
一堆不同的房子,他们想知道如何从仓库到达这些不同的房子,并且再返回,所有这些都尽可能使用最少的时间、距离和能量,所以你可能想尝试解决这些类型的问题,但事实证明,解决这个特定的销售员问题通常是这样的。
这种问题在计算上非常困难,解决它是一个非常消耗计算资源的任务,这属于被称为 NP 完全问题的类别,这些问题没有已知的高效解决方法。因此,我们...
我最终必须做的是提出某种近似方法,尝试找到一个好的解决方案,即使我们不能找到可能的全局最佳解决方案,至少在可行或可处理的时间内不能找到。因此,我们可以拿旅行商问题来尝试解决。
使用局部搜索来进行表述,问一个问题,比如,我可以选择某个状态、某个配置或节点之间的某条路线,并且我可以测量这个状态的成本,算出距离,然后我可能想尽可能地最小化这个成本。
现在的问题是,拥有这个状态的邻居意味着什么?走这条特定路线意味着什么?以及是否存在一条与之接近但略有不同的邻近路线,这可能会导致总距离的不同,而对于这类问题有许多不同的定义。
旅行推销员的邻居,配置可能看起来像,但只是一个。方法是邻居就是我们,如果选择这两个边。之间的节点并切换它们,实际上例如我可能选择。这两个边,这两个恰好交叉,这个节点就到这里。
节点到那里并切换它们,这个过程通常会看起来像是从图中移除这两个边,将这个节点连接到它原本没有连接的节点,所以在这里连接起来。我们需要将这些原本朝这个方向的箭头。
反向移动它们,朝另一个方向移动,然后填补最后一个剩余的空白,添加一个朝那个方向的箭头。因此通过取两个边并切换它们,我能够考虑这个特定配置的一个可能邻居,它看起来像这样。
邻居实际上更好,看起来像这条路线可能走更短的距离,以便经过所有城市,而不是当前状态,因此你可以想象在一个爬山或模拟退火算法中实现这个想法,在这里我们重复这个过程。
尝试取一个旅行推销员问题的状态,查看所有邻居,然后移动到邻居,如果它们更好,或者甚至移动到邻居,如果它们更糟,直到我们最终确定一些我们能够找到的最佳解决方案,结果发现这些。
即使这些近似算法并不总是找到最佳解决方案,通常也能很好地找到一些有用的解决方案。所以这是对局部搜索的一个了解,特定类别的算法可以用来解决特定类型的问题,我们并不真的。
我关心的是解决方案的路径,我并不在乎我采取了哪些步骤来决定医院应该去哪里,我只关心解决方案本身。我只关心医院应该在哪里,或者旅行推销员之旅的路线到底应该是什么,另一种类型的算法。
可能出现的被称为这些,线性规划类型的问题的类别,线性规划经常出现在我们试图优化某个数学函数的上下文中,但很多时候线性规划会出现当我们可能有实际的实数值时。
不仅仅是我们可能拥有的离散固定值,而是我们可能想要的任何小数值。!
进行计算,因此线性规划是一类问题类型,在这种情况下,线性规划的目标是最小化一个成本函数,你可以反转数字,尝试最大化它,但我们通常会将其框架为尝试最小化一个。
成本函数有一些变量X1、X2、X3,一直到Xn,只是一些涉及的变量,我想知道这些变量的值,而这个成本函数可能在这些变量前面有系数,这就是我们所称的线性方程。
我们只是有所有这些变量,可能会被一个系数相乘,然后相加,我们不会对任何东西进行平方或立方,因为那会给我们不同类型的方程式,在线性规划中我们只是处理线性方程,加上线性约束。
约束条件看起来可能是这样的,如果我们将这个特定的方程式相加,这只是所有这些变量的某种线性组合,并且小于或等于某个界限B,可能会有许多不同的约束条件,我们可能会施加在我们的线性模型上。
编程练习,同样地,我们可以有约束,表示这个线性方程小于或等于某个界限B,它也可能等于某个值,但如果你想要某个变量组合的和等于某个值,你可以指定这一点。
指定每个变量具有下限和上限,例如,它需要是一个正数,或者它需要是小于2的数字,还有其他一些选择,我们可以在这里进行,以定义变量的平衡,但事实证明,如果你能。
我们也可以将一个问题用这些术语进行公式化,公式化这个问题为你的目标是最小化一个成本函数,并且你在特定约束下最小化该成本函数,约束的方程形式像这样,一些变量的序列小于某个界限或等于某个值。
我们想要做的就是这样,但我们需要做到这一点,以等于某个特定值,然后已经存在一些算法来解决这些类型的问题,所以让我们来看一个例子,这里是一个可能在线性规划领域出现的问题的例子,通常在我们试图解决时会出现。
优化某个东西,我们想能够进行一些计算,并且我们对我们正在尝试的内容有约束!
优化它们,所以在工厂的背景下,它可能像这样,我们有两台机器x1和x2,x1的运行成本为每小时50美元,x2的运行成本为每小时80美元,我们的目标是最小化总成本。
受某些约束的影响,可能会有一个劳动约束,x1每小时需要5单位的劳动,x2每小时需要2单位的劳动,而我们总共有20单位的劳动可以使用,所以这是一个约束,我们最多只能花费20单位的劳动。
在x1和x2之间进行分配,每个都需要不同数量的劳动,并且我们可能还有一个约束条件,告诉我们x1每小时产生10单位的产出,x2每小时产生12单位的产出,而公司需要90单位的产出,因此我们有一些目标。
我们需要实现90单位的产出,但有一些限制,x1每小时只能产生10单位的产出,x2每小时产生12单位的产出,这类问题相当常见,您会开始注意到这些类型问题中的模式。
我正在尝试优化的问题,目标是最小化成本或最大化输出、利润等,并且有一些限制条件。现在我们需要将这个问题表述为,涉及这两台机器x1和x2,x1每小时成本50美元,x2每小时成本80美元。
我们可以提出一个目标函数,可能看起来像这样:这是我们的成本函数,实际上是50乘以x1加上80乘以x2,其中x1是一个变量,表示我们运行机器x1的小时数,x2是一个变量,表示我们运行机器x2的小时数。
我们要最小化的就是这个成本函数,即每小时运行这些机器的成本总和,这是一个线性方程,仅仅是这些变量加上前面的系数的某种组合,我希望最小化这个总值,但我需要。
在这些约束条件下,x1每小时需要50单位的劳动,而我们总共有20单位的劳动可以使用,这样就给了我们一个这样的约束:5乘以x1加上2乘以x2小于或等于20,20是我们可以使用的劳动总量。
这个总量分配在x1和x2之间,每个每小时需要不同数量的劳动单位,最后我们还有这个约束,x1每小时产生10单位的产出,x2产生12单位,而我们需要90单位的产出,因此这可能看起来像这样:10乘以x1加上12乘以x2——这就是。
每小时的产出数量需要至少为90,如果我们能做得更好,那很好,但至少需要达到90。如果您还记得我之前的表述,我说在一般的线性规划中,我们处理的是等式约束或小于等于约束,所以我们有一个大于或等于的约束。
这里的等于号并不是问题,当我们有一个大于或等于的符号时,我们可以将方程乘以-1,这样就会翻转成小于或等于,比如说小于或等于-90,而不是大于或等于90,这将是一个等效的表达式。
我们可以用来表示这个问题,现在我们有这个成本函数和这些约束条件,它们受到一些限制,结果是有多个算法可以用来解决这些类型的问题。
所以我们可以举个例子来看看!我们将深入探讨,但这些类型算法中最受欢迎的是单纯形法,它是最早被发现用来尝试解决线性规划的算法之一,后来还有一类内点算法也可以用来解决这种类型的问题,关键并不是理解。
这些问题涉及的几何和线性代数的内容可能会多一点,而不是确切了解这些算法是如何工作的,但要意识到这些算法存在是为了高效地找到解决方案,每当我们遇到这种特定形式的问题时。
这里的生产目录,我有一个名为 production PI 的文件,在这里我使用 scifi,这是 Python 中许多与科学相关的函数库,我可以继续运行这个优化函数来运行一个线性程序,dot Lin prog 在这里将会。
尝试为我解决这个线性程序,我提供给这个表达式给这个函数调用所有有关我的线性程序的数据,所以它需要以特定格式提供,最初可能有点困惑,但这个 scifi 优化线性编程的第一个参数是成本函数。
在这种情况下,它只是一个数组或列表,包含 50 和 80,因为我的原始成本函数是 50 乘以 X1 加 80 乘以 X2,所以我告诉 Python 50 和 80,这些是我现在试图优化的系数,然后我提供所有的约束条件,约束条件我在上面写了。
并且约束条件一是 5X1 加 2X2 小于或等于 20,约束条件二是负 10X1 加负 12X2 小于或等于负 90,所以 Syfy 期望这些约束以特定格式提供,它首先期望我提供所有的上界系数。
界限方程 ub 只是 4 4 上界,其中第一方程的系数是 5 和 2,因为我们有 5X1 + 2X2,而第二方程的系数是负 10 和负 12,因为我有负 10X1 加负 12X2,然后这里我们将其作为一个单独的。
参数仅仅是为了保持事物的分隔,实际的界限是什么,每个约束的上界是什么,第一个约束是数字一,然后对于约束!。
第二个,上界是一个 ID,所以有点神秘的表示方式,并不像简单地写下数学方程那么简单,真正期望的是所有的系数和这些方程中所有的数字,首先提供成本。
函数提供所有不等式约束的系数,然后提供所有这些不等式约束的上界。一旦所有这些信息都在,我们就可以运行任何这些内部点算法或单纯形算法,即使你不。
了解它的工作原理,你可以直接运行该函数,找出结果应该是什么。我说如果成功的结果是我们能够解决这个问题,继续打印出x1和x2的值,否则打印出没有解决方案,因此如果我运行这个。
通过运行Python程序来进行计算,计算需要一秒钟,但随后我们看到这是最佳解决方案。
x1应该运行1.5小时,x2应该运行6.25小时,我们。
我们能够通过将问题表述为线性方程来实现这一点,我们试图优化一些成本,我们想要最小化的,然后是施加在这些问题上的一些约束,许多问题都属于这种你可以解决的类别,只要你能找出如何。
使用方程和这些约束来表达那个总体想法,因此。雅典娜今天会出现几次,我们希望能够。将某个问题简化为我们知道如何解决的某个问题。以便开始寻找解决方案,并利用我们可以使用的现有方法。
为了更有效或更高效地找到解决方案,事实证明,这些类型的问题,带有约束的出现还有其他方式,并且有一整类问题更普遍地被称为约束满足问题,现在我们将进行一个。
看看你可能如何制定一个约束满足问题,以及你可能如何解决一个约束满足问题,但基本思路是。
约束满足问题的关键在于我们有一些变量需要取某些值,我们需要确定每个变量的值受特定约束的限制,这些约束会限制变量实际上可以取的值,因此,让我们看看一个现实世界的例子。
例如,让我们看看我为学生安排的考试安排。
学生1、2、3每个人都在修一些不同的课程。这里的课程将用字母ISM表示,因此学生1注册了课程a、b和c,学生2注册了课程b、d和e,以此类推,现在假设某大学正在尝试为。
所有这些课程中,周一、周二和周三只有三个考试时段,我们必须为每一门课程安排一次考试,但我们面临的约束是希望任何人都不要在同一天参加两场考试。
我们尽量最小化或消除它,如果可能的话。那么我们如何开始表示这个概念?我们如何将其结构化,以便计算机与AI算法能够开始解决这个问题呢?让我们特别关注我们可能会选修的课程,并将每门课程表示为变量。
在图中某个节点内部,如果这两个节点之间存在约束,我们将创建一条边。因此,这意味着我们可以从选修课程A、B和C的学生1开始,这意味着A和B不能同时考试。
C不能与A同时考试,B和C也不能同时考试,我可以通过在图中画边来表示这一点。A和B之间一条边,B和C之间一条边,再加上一条连接C的边。这现在编码了在这些节点之间存在约束的概念。
具体来说,约束可能是其他类型的,尽管根据你试图解决的问题类型,可能还有其他约束。我们可以对其他学生做同样的事情,对于选修课程B、D和E的学生2,这意味着B、D和E之间都需要有连接边。
学生3选修课程C、E和F,我们将继续连接C、E和F,并在它们之间画边。最后,学生4选修课程E、F和G,我们可以通过在它们之间画边来表示这一点。
尽管E和F之间已经有一条边,但我们不需要再画一条,因为这个约束只是编码了课程I和课程F不能在同一天考试的概念。所以,这就是我们可能称之为的约束图,是对我所有课程的某种图形表示。
在这种情况下,每个约束代表一个不等式约束,而B和D之间的边意味着变量B所取的任何值不能与变量D所取的值相同。
那么,什么是约束满足问题呢?约束满足问题就是一组变量x1到xn,每个变量都有一组领域。每个变量需要取某些值,也许每个变量有相同的领域,但也可能每个变量都有不同的领域。
变量有略微不同的范围,然后有一组约束,我们将称之为C,即施加在这些变量上的一些约束,例如X1不等于X2,或者X1等于X2加1,如果这些变量在它们的范围内取数值的话。
约束将根据问题的类型变化,约束满足在任何我们有特定约束的变量的情况下都随处可见,一个流行的游戏是数独,例如这个9x9的网格,你需要在其中填入数字。
每个单元格的类型,但你不想确保在任何行、任何列或3x3的单元格网格中有重复的数字,例如,这个约束满足问题可能是什么样子呢?我的变量是所有的空方格。
这里表示的谜题就像是X,Y坐标,例如所有我需要填入值的方格,而我不知道该填什么值,范围只是从1到9的所有数字,任何我可以填入这些单元格的值。
每个变量的范围,然后约束将是这样的形式:不能等于这个单元格,不能等于那个单元格,所有这些需要是不同的,例如对于所有的行和列,以及3x3的方格也是如此,因此这些约束将强制执行什么。
值实际上是允许的,我们可以在这个考试安排问题中制定相同的想法,其中我们拥有的变量是不同的课程,G的范围对于这些变量的每一个都将是周一、周二和周三,这些是每个变量的可能值。
变量可以在这个案例中取值,代表的是该课程的考试时间,然后约束的形式是A不等于B,A不等于C,这意味着A和B不能在同一天考试,A和C不能在同一天考试,或者更正式地说,这两个变量不能取相同的值。
同一值在它们的范围内,所以这就是我们可以开始使用的约束满足问题的一个公式化,约束可以以多种不同形式出现,有硬约束,必须为正确解决方案满足的约束。
在数独谜题中,像这样的情况是,你不能让同一行中的这个单元格和那个单元格取相同的值,这是一个硬约束,但问题也可以有软约束,这些约束表达某种偏好,例如A和B不能在同一天考试。
但也许有人希望 A 的考试比 B 的考试更早,这并不一定是事实,但有一些表达式表明某个解决方案优于另一个解决方案。在这种情况下,你可能会将问题表述为试图优化以最大化人们的偏好,你希望满足人们的。
尽可能满足偏好,但在这种情况下,我们将主要处理硬约束,必须满足的约束,以便问题有一个正确的解决方案,因此我们希望找出这些变量到其特定值的某种分配,这最终会给。
为我们提供解决问题的方案,通过允许我们为每门课程分配某一天,以确保我们没有任何。
类之间的冲突,因此我们可以将约束满足问题中的约束分类为多个不同的类别。其中第一个类别,也许是最简单的类型,被称为单元约束,单元约束是。
仅涉及单一变量的约束,例如单元约束可能是像 a 不等于星期一,这意味着课程 a。
如果由于某种原因课程的讲师在星期一不可用,则该课程不能在星期一进行考试。你可能在你的问题中有一个看起来像这样的约束,它只涉及一个单一变量 a,并且可能说 a 不等于星期一,或者 a 等于某个值,或者在这种情况下。
大于或小于的数字。
约束中只有一个变量的情况,我们认为这是一个单元约束,这与涉及两个变量的二元约束形成对比。例如,这将是我们所研究的那种约束。
在类似于 a 不等于 B 的情况下,这是一个二元约束,因为这是一个涉及两个变量 A 和 B 的约束,我们用一些弧或边表示这一点,K'NEX 变量 a,两个变量 B,并利用这一知识,好的,单元约束是什么。
什么是二元约束?我们可以对特定约束说出不同类型的事情。
满意度问题,我们可以说的是,我们可以尝试使问题节点一致。那么什么是“一致性”呢?节点一致性意味着变量域中的所有值都满足该变量的单元约束,因此对于我们所有变量中的每一个。
在约束满足问题中,如果所有值都满足该特定变量的一元约束,我们可以说整个问题是节点一致的,或者我们甚至可以说特定变量是节点一致的,如果我们只想让一个节点在其自身内部保持一致。
现在让我们来看一个简化的例子,而不是拥有一大堆不同的类,我们只有两个类 a 和 B,每个类在星期一、星期二或星期三都有考试。所以这是变量 a 的域,这是变量 B 的域。
现在让我们想象一下,我们有这些约束:a 不等于星期一,B 不等于星期二,B 不等于星期一,a 不等于 B。所以这些是我们在这个特定问题上的约束。现在我们可以尝试做的是强制节点一致性。节点一致性意味着我们确保所有节点都一致。
任何变量的域的值是否满足其一元约束?我们可以从尝试让节点 a 节点一致开始,检查它是否一致,a 的域内的每个值是否满足其一元约束。最初,我们会发现星期一并不满足 a 的一元约束,因为有一个约束 a 不等于星期一,但星期一仍然在 a 的域中,因此这不是节点一致的。
因为我们在域中有星期一,但这个值对于这个特定节点来说并不有效。那么我们如何让这个节点一致呢?为了使其与 B 不等于星期二的一元约束一致,我们将去掉 B 域中的星期二,现在 B 的域只包含星期一和星期三。
对于每一个值,a 可以在星期二和星期三之间取值,这样我们就会确保 a 是节点一致的。
没有任何与这一想法相冲突的一元约束。没有约束说 a 不能在星期二,没有一元约束说 a 不能在星期三。因此,现在我们可以把注意力转向 B。B 的域也有星期一、星期二和星期三。
我们开始看看这些变量是否也满足一元约束。这里有一个一元约束,B 不等于星期二,而这个约束在这个包含星期一、星期二和星期三的域中似乎并不满足。因为星期二是变量 B 可能取的值之一。
但事实证明,针对变量 B 我们还施加了另一个一元约束,即 B。
不等于星期一,这意味着B的领域中这个值星期一与B的一元约束不一致,因为我们有一个约束表示B不能是星期一,因此我们可以将星期一从B的领域中移除,现在我们已经通过了所有的一元约束,但还没有。
考虑了这个约束,它是一个二元约束,但我们已考虑了所有的一元约束,所有仅涉及单个变量的约束,我们确保每个节点都与这些一元约束一致,因此我们可以说现在我们已强制执行节点一致性,对于这些每个节点。
我们可以选择这些可能的节点,领域中的值不会。因为作为结果的一个一元约束并未被违反,所以节点一致性相对容易强制执行,我们只需考虑每一个。
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P13:L3- 优化算法 3 (回溯搜索等) - ShowMeAI - BV1AQ4y1y7wy
域满足一元约束,当我们考虑不同类型的一致性时,事情变得有趣,比如弧一致性。弧一致性指的是当变量域中的所有值满足变量的二元约束时。
我们正在努力使一个一致性,我们不再只是考虑涉及 a 的一元约束,而是尝试考虑所有涉及 a 的二元约束。因此,任何连接 a 与约束图中另一个变量的边。
更正式地说,弧一致性其实是连接约束图中两个节点的边。我们可以更准确地定义我们的一致性,以便使某个变量 X 与其他变量一致。
关于某个其他变量 Y,我们需要从 X 的域中移除任何元素,以确保 X 的每一个选择,在 X 的域中都有一个可能的 Y 的选择。换句话说,如果我有一个变量 X,想让 X 和 Y 一致,那么我要查看所有可能的值。
X 可以接受所有可能的值,并确保对于 Y 仍然存在一些选择,如果 x 和 y 之间存在某个弧,以确保 Y 也有我可以选择的可能选项,所以让我们看一个例子,回到我们强制节点的这个例子。
一致性已经通过说 A 只能是星期二或星期三来确定,因为我们知道 A 不能是星期一,而且我们也说 B 的域只包含星期三,因为我们知道 B 不等于星期二,且 B 也不等于星期一。那么现在让我们开始考虑我们的相容性。
使 A 对 B 一致,这意味着对于 A 的域中的任何选择,B 的域中都有某个选择可以保持一致。我们可以选择星期二作为 A 的一个可能值。
如果 A 选择星期二,那么是否存在满足二元约束的 B 的值?是的,B 为星期三可以满足 A 不等于 B 的约束,因为星期二不等于星期三。然而,如果我们选择星期三作为 A 的值,那么在 B 的域中没有满足此二元约束的选择。
如果我无法为 B 选择一个满足 a 不等于 B 的选项,因为我知道 B 必须是星期三,因此如果我遇到这样的情况,看到 a 有一个可能的值,而 B 的值没有满足二元约束的选择,那么这就不是一致性。
为了使其一致,并使其保持一致,我需要将星期三移出A的领域,因为星期三对A来说不是一个可能的选择,因为它与这个二元约束不一致,我无法选择星期三作为A的选择。
仍然可以通过为B选择某种东西来找到可用的解决方案,所以在这里。我已经能够执行弧一致性,通过这样做,我实际上解决了整个问题,考虑到这些约束,A和B可以在星期一、星期二或星期三考试,唯一的解决方案。
看起来A的考试必须在星期二,B的考试必须在星期三,这是唯一的选择。
如果我们想将一致性应用于一个更大的图,不仅仅关注一对变量,也有其他方法可以实现这一点,我们可以开始正式化伪代码,以尝试编写一个强制我们一致性的算法。
通过定义一个名为revise的函数。
revise函数将作为输入一个CSP(约束满足问题)以及两个变量x和y,revise的作用是使X对Y的弧一致,意味着从X的领域中移除不允许Y的可能选项的任何东西。
这是否有效,我们会首先跟踪是否进行了修订,revise最终将返回true或false。如果我们对X的领域进行了修订,它将返回true,如果没有对X的领域进行任何更改,它将返回false,我们将在一个。
此时我们开始了解这将如何有帮助,但我们先说revise等于。
如果我们没有进行任何更改,那么我们会说好的,继续。
并循环遍历X的领域中的所有可能值,因此遍历X的领域。对于X领域中的每个小X,我想确保对于每个选择,我在Y中有一些可用的选择,满足定义在我的CSP中的二元约束。
如果在Y的领域中没有值满足X和Y的约束,那就意味着这个值X不应该在X的领域中,我们将会去。
接着从X的领域中删除X,并将revise设为true,因为我确实更改了X的领域,移除了小X,因为它不一致,无法为Y选择一个满足XY约束的值,所以在这种情况下我们将。
接下来,我们将修订设置为真,我们将对X的领域中的每个值反复执行这个过程,有时这可能是可以的,但在其他情况下,它可能不允许Y的可能选择,在这种情况下,我们需要从X的领域中移除这个值,最后我们只需返回修订以指示是否。
实际上,我们进行了更改,所以这个函数,修订函数,实际上是你刚才看到的图形实现,它使得一个变量X与另一个变量Y保持一致,但一般来说,当我们希望强制一致性时。
我们通常想要强制一致性,不仅仅是为了一个单一的弧,而是为了整个约束满足问题,事实证明,还有一个算法可以做到这一点,这个算法被称为ac3,ac3处理一个约束满足问题,并在整个问题上强制弧的一致性。
它是如何做到这一点的呢?它基本上会维护一个队列,或者说是一条需要保持一致的所有弧的线,随着时间推移,我们可能会从那个队列中移除一些东西,因为我们开始处理弧的一致性,也可能需要向那个队列中添加一些东西,如果有更多的东西。
我们需要让它们保持一致,所以我们将开始一个包含约束满足问题中所有弧的队列,所有连接到具有某种二元约束的节点的边。
只要队列不为空,就还有工作要做。队列中的所有内容都是我们需要保持一致的东西,所以只要队列不为空,我们还有事情要做。我们需要做什么呢?我们将首先从队列中去掉一个元素,移除队列中的某个东西。
队列,严格来说,它不需要是一个队列,但队列是一个传统的做法,好的,从队列中去掉(DQ),这将给我们一个弧x和y,这两个变量我希望让X与Y保持一致。那么我们如何让X与Y保持一致呢?我们可以继续使用刚才提到的修订函数。
我们称之为修订函数,输入是约束满足问题,以及这些变量x和y。因为我希望让X与Y保持一致,换句话说,去掉X的领域中不留下Y可用选项的值,回想一下。
修订返回什么呢?如果我们实际上进行了更改,它返回真,如果我们从X的领域中移除了一些东西,因为没有Y的可用选项,例如,它返回假,如果我们没有对X的领域进行任何更改,结果是如果修订返回假,我们就没有进行任何。
变化不大,那么这里不需要做太多的工作,我们可以直接进入队列中的下一个弧线,但如果我们确实做了变化,减少了X的域,移除了X域中的值,那么我们可能意识到这会造成潜在的问题。
后来这可能意味着某个与X的节点一致的弧线可能不再与X一致,因为我们曾经有一个选项可以选择X,现在可能没有,因为我们可能移除了X的一些内容,这对某些其他弧线的艺术保持是必要的。
所以如果我们确实修订了X的域,我们需要向队列中添加一些额外的内容,首先我们想检查的是确保X的域不为0,如果X的域为0,意味着X没有可用的选项,这意味着无法解决。
如果我们从X的域中移除了所有元素,我们将返回false,表示没有办法解决问题,因为X的域中没有剩余的内容,但如果X的域中还有东西,但比之前少,那么我们将。
我们要做的是遍历每个变量Z,这些变量在所有X及其邻居中,除了我们已经处理过的y,我们将把X视为其他邻居,并问自己,"好吧,从这些Z到X的弧线可能不再是一致的,因为对于每个Z"。
可能有一个选项,我们可以选择让X对应于每个可能的值,但现在可能没有,因为我们从X的域中移除了某些元素,因此我们在这里要做的是继续向Q添加一些内容,这条弧Z到X,对于所有这些邻居Z。
为了将一些弧线添加回Q,以继续保持一致性,最后如果我们顺利完成了这个过程,我们就可以返回true。但这现在是一个c3算法,用于在约束满足问题上强制执行弧线一致性,主要思想就是跟踪一致性。
我们骑士可能需要使所有的弧线保持一致,以便将其称为艺术,通过调用修订功能,如果我们进行了修订,那么可能有一些新的弧线需要添加到队列中,以确保即使在删除了一些之后,一切仍然是一致的。
从某个特定变量的域中的元素,如果我们尝试在这样的图上强制执行弧线一致性,即每个变量的域是星期一、星期二和星期三,结果发现,通过在这个图上强制执行弧线一致性。
解决某些类型的问题这里实际上没有任何变化,对于任何特定的约束,只要考虑两个变量,我总有一种方法可以为我做出的选择之一做出另一种选择,因为有三种选择,我只需要确保两个是不同的。
彼此之间实际上很容易只需取一个弧并声明它不一致,因为如果我选择星期一作为D,然后为B选择一个不是星期一的东西,在弧一致性中我们只考虑两个节点之间的二元约束的一致性。
我们实际上还没有考虑所有其他节点,所以仅使用C3约束的一致性执行有时会产生缩小域的效果,从而使得找到解决方案更容易,但并不总能解决问题,我们可能仍然需要以某种方式搜索以尝试满足条件。
你会记得,搜索问题通常由这些部分组成:一些初始状态,一些行动,一个过渡模型将我从一个状态转移到另一个状态,一个目标测试来告诉我是否满足我的要求。
目标正确,然后某些路径、成本函数因为在这种情况下。像迷宫求解,我试图尽快到达我的目标,所以你可以将一个约束满足问题(CSP)或约束满足问题表述为这些搜索问题之一,初始状态将只是一个空的赋值。
赋值只是我将任何特定变量分配给任何特定值的一种方式,因此如果一个空的赋值没有变量被分配给任何值,那么我可以采取的行动是向该赋值中添加一些新的变量等于值的对,表示对于这个赋值,让我添加一个新值。
对于这个变量,过渡模型只是定义了当你采取该行动时会发生什么,你会得到一个新的赋值,该赋值中包含该变量等于那个值,目标测试只是检查确保所有变量都已被赋值,并确保所有约束已被满足。
路径成本函数我有点无关紧要,我并不在意路径的具体形式,我只关心找到一个实际上满足所有约束的赋值,因此实际上所有路径的成本都是相同的,我并不关心。
我只关心到达目标的路径,实际上我们之前谈过的问题是,如果我们仅仅通过实现简单的搜索算法,如广度优先搜索或深度优先搜索来实现,这将是非常非常低效的,还有其他方法我们可以找到解决方案,并可以使用经典的传统搜索算法来尝试实现。
利用约束满足问题本身结构中的效率,而一个关键理念是我们真的可以对这些变量进行排序,且赋值的顺序并不重要。赋值a等于2,然后B等于8与B的赋值是相同的。
等式为8,然后a等于2的切换,顺序并不会改变任何东西。关于这一基本性质,赋值存在一些方式。我们可以尝试修订这个想法,运用搜索算法来专门应用于像约束满足问题这样的一个问题。
事实证明,谈到约束满足问题时,搜索算法通常会使用称为回溯搜索的东西。回溯搜索的核心理念是,我们将继续对变量与其值进行赋值,如果我们遇到阻碍,就会到达一个地方。
在我们无法取得任何进展的情况下,同时仍然保持需要强制执行的约束,我们将继续回溯并尝试。
另一种东西,因此回溯搜索的基本草图是这样的,类似于一个叫回溯的函数,接受赋值和约束满足问题作为输入。因此最初我们没有任何分配的变量,某人将开始进行回溯搜索,这个赋值是。
将会是空的赋值,里面没有变量,但我们稍后会看到这是一个递归函数,因此回溯函数的输入是赋值和问题。如果赋值已完成,意味着所有变量都已被分配,我们只需返回该赋值。
起初这并不成立,因为我们以空的赋值开始,但随着时间的推移,我们可能会向该赋值中添加内容。因此,如果赋值实际上已完成,那么我们就完成了,接下来只需返回该赋值;否则,还有一些工作要做,我说我们需要。
要做的是选择一个未分配的变量,针对这个特定问题。需要查看已经被分配的变量,并选择一个尚未被分配的变量,然后我会继续使用该变量,接下来我需要考虑该变量的所有值。
变量的领域,因此我们将继续调用这个领域值函数,稍后我们会详细讨论,它接受一个变量并返回其领域中所有值的有序列表。所以我选择了一个随机的未选择变量,将循环遍历所有可能的值。
这个想法是让我尝试所有这些值作为变量的可能值,因此如果该值与到目前为止的赋值一致,不违反任何约束,那么我们就继续将变量等于值添加到赋值中,因为到目前为止是一致的,现在让我们递归进行。
回调回溯以尝试使其余的分配也保持一致。所以我们将继续对我添加的新分配调用回溯,这个新变量等于值2,现在我递归地调用回溯,看看结果是什么,如果结果不是失败,那么让我返回。
那个结果,否则还有什么可能发生呢?如果结果是失败,那就意味着这个值可能是这个特定变量的一个糟糕选择,因为当我将这个变量等于那个值时,最终我遇到了一个情况。
违反约束条件,它没有什么更多的意义,只是注定失败。因此,现在我将从分配中移除变量等于值,有效地进行回溯,说明,好的,这个值不行,让我们尝试另一个值,然后在最后,如果我们从未能够返回完整的分配,我们就继续。
返回失败,因为这意味着没有任何值适用于这个特定变量,这是回溯搜索的理念,尝试每个变量的值并递归地尝试回溯搜索,看看我们能否取得进展,如果我们遇到死胡同。
最终,我们遇到了一个情况,即没有可能的值可以选择,满足约束条件,我们返回失败,并且这个失败向上传播,最终我们通过回退尝试其他选择。因此,让我们将这个算法付诸实践,实际上就是尝试使用回溯。
现在,我需要解决这个问题,我需要弄清楚如何将这些课程分配到周一、周二或周三的考试时段,以满足这些约束条件,这些边意味着这两门课程不能在同一天考试,所以我可以开始。
就像从某个节点开始并不重要我选择哪个,但在这种情况下,我们将从A开始,并且我会问一个问题,比如,好吧,让我循环遍历域中的值,也许在这种情况下,我将从周一开始,并说,好的,让我们将A分配给周一。
现在,按照顺序考虑节点B,所以我已经对A进行了分配,我递归调用回溯并查看这个新的分配部分,现在我希望选择另一个未分配的变量,比如B,我会说,好吧,也许我从周一开始。
这是B的域中的第一个值,我问,好吧,周一是否违反任何约束条件,结果是,是的,它确实违反了这个约束,因为A和B现在都在周一,这不行,因为B不能和A在同一天。
我们可能尝试周二,尝试B的下一个值,这与目前的赋值一致吗?嗯,是的,周二与周一一致,因为它们不在同一天,这很好。现在我们可以递归调用回溯,再次尝试,选择另一个未分配的变量。
类似于D,我说好的,让我们看看它的可能值,周一与这个赋值一致吗?嗯,是的,B和D在不同的日子,周一对比周二,A和B也在不同的日子,周一对比周二,所以这也很好。接下来我们可以再试一下,也许我们会去。
这个变量e说,我们能使其一致吗?让我们看看可能的值,我们已经递归调用了回溯,我们可以从周一开始,好的,这不一致,因为D和E现在在同一天有考试,所以我们可能尝试周二,继续下一个,问这是否。
一致性,不,这不是,因为B和E在同一天有考试。所以我们尝试,好的,周三一致吗?结果显示,好的,是的,周三一致,因为D和E现在在不同的日子,B和E现在在不同的日子,一切似乎都很好。
到目前为止,我递归调用回溯,选择另一个未分配的变量,我们可以尝试si可能取的值,从周一开始,结果显示这不一致,因为现在A和C都有考试在同一天,所以我尝试了z',说这也不一致,因为B和C现在在同一天有考试。
同一天,然后我说好的,继续尝试周三,但这也不一致,因为C和eee也在同一天有考试。所以现在我们已经遍历了C的所有可能值,周一、周二和周三,但没有一个一致,我们无法有一个。
一致性赋值回溯在这种情况下,我们将返回失败,因此我们会说,好的,我们必须在这里回溯。现在对于e,我们已经尝试过周一、周二和周三,但没有一个有效,因为周三看似有效,结果却是失败,这意味着没有。
是我们可以给e赋值的可能方式,这也是一个失败,我们必须回到D,这意味着对D的周一赋值一定是错误的,我们必须尝试别的东西。那么我们可以尝试,假设D是2,如果我们尝试下一个值周二,结果显示这也不一致,因为B和D现在有。
同一天的考试,但结果周三有效,现在我们可以开始进行前向推进。我们回到E,说好的,哪一个值有效,周一结果有效,没有违反任何约束。然后我们回到C,周一不行,因为它违反了约束,实际上违反了两个。
星期二也不行,违反了约束,但星期三可行。那么我们可以查看下一个变量F,问星期一是否可行。我们会知道哪个违反了约束,但星期二是可行的,然后最终我们可以查看最后一个变量G,再次递归调用回溯。
时间星期一是不一致的,这违反了约束条件;星期二也违反了一个约束,但星期三则没有违反约束,因此现在我们可以说。在这一点上,我们递归调用,最后一次回溯,我们现在有了一个令人满意的所有变量的分配。
我们现在完成了,我们已经能够成功为每个变量分配一个值,以一种不违反任何约束的方式,我们将继续进行类A和E的考试在星期一,类B和F可以在星期二考试,类C和D则是。
哇,学生们可以在星期三进行考试,没有违反的约束可能会出现,因此这是一个图形化的方式来看这可能如何运作。现在让我们看一些可以实际用来解决这个问题的代码,所以我会继续。
进入调度目录,我们现在在这里,我们将开始查看schedule 0 PI。我定义了一组变量,A、B、C、D、E、F、G,这些是所有不同的班级。然后在下面,我定义了我的约束列表,比如约束A和B,因为它们不能在同一天,类似地A和C,B和C等等,强制执行这些相同的约束。
这里是回溯函数的样子,首先如果分配已完成,如果我已将每个变量分配给一个值,就继续返回该分配,然后选择一个未分配的变量。
从那个分配开始,对于星期一、星期二、星期三领域中的每一个可能值,让我们创建一个新的分配,将变量分配给该值。我会称之为一致性函数,稍后我会向你展示,这个函数只会检查这个新的分配是否一致。
但如果它是一致的,我们将继续调用回溯,继续尝试运行回溯搜索,只要结果不是None,意味着不是失败,我们就可以返回该结果。但如果我们遍历了所有值,没有任何效果,那就是失败。
没有解决方案,我们继续返回None。这些函数做什么?选择未分配的变量,它只是选择一个尚未分配的变量,所以它会遍历所有变量,如果它尚未分配,我们将返回该变量。
一致性函数运行良好,一致性函数会遍历所有约束,如果我们在分配了这两个变量的值但它们相同的情况下,那么这就是对约束的违反,在这种情况下我们将返回 false,但如果没有。
一致的情况下,该分配是一致的,我们将返回 true,然后程序所做的就是调用一个空字典,该字典没有变量被赋值。
目前还没有值,将其保存在解决方案中,然后打印出 dot 解决方案。因此通过现在运行这个,我可以运行 Python,调度 0 PI,而我得到的结果是所有这些变量到值的分配,结果我们分配给星期一的是我们预期的星期二,C 分配给星期三,正好。
我们之前讨论过的相同类型的东西,每个这些变量到值的赋值都不会。
违反任何约束,我必须做相当多的工作来实现这个想法,我必须编写 BACtrack 函数,它会递归地进行回溯搜索,但事实证明,约束满足问题是如此。
这种想法非常流行,已经存在许多库实现了这种类型的想法。同样,像之前一样,特定的库并不重要,重要的是库确实存在,这只是一个 Python 约束库的例子,现在不必从头开始完成所有工作。
1 dot pi 我只是利用一个已经实现了很多这些想法的库,因此我在这里创建了一个新问题,并添加了特定领域的变量,我添加了一整堆这些单独的约束,我称之为添加约束,并传入描述约束的函数。
约束基本上是说,这个函数接受两个变量 x 和 y,并确保 X 不等于 Y,强制这两个班级不能在同一天进行考试,然后对于任何约束满足问题,我可以调用获取解决方案来获得所有解决方案。
解决方案打印出该解决方案是什么,如果我运行 Python。
调度 1 dot PI,我看到实际上有许多不同的解决方案。
这可以用来解决问题,实际上有 6 种不同的变量赋值方案,可以给我一个令人满意的答案。
这是一个约束满足问题的实现,实际上这是一个非常基本的回溯搜索方法,我们只需逐个变量,选择一个未分配的,尝试该变量可以取的可能值,然后判断如果成功,若不成功则回溯。
如果违反任何约束条件,我们会继续尝试其他变量,如果遇到死胡同,我们就得回溯,但最终我们可能会更聪明地解决这些问题,从而提高解决这类问题的效率。
想象一下我们要回到推理的想法,利用我们已知的知识来得出结论,以便让后续的问题解决过程更容易。现在让我们回到第一次遇到困难的地方,即在解决这个约束时。
我们解决了B的满足问题,然后转向D,并将D分配给星期一,因为这似乎与之前的分配一致。到目前为止并没有违反任何约束,但后来发现这个选择实际上是错误的。
这里我们可以采用其余的值,问题是我们能否避免陷入这样的境地,避免走上一条最终不会通往任何地方的路径,通过利用我们已有的知识,结果证明我们确实拥有这些知识。
我们可以查看这个图的结构,目前C的范围包含星期一、星期二和星期三。基于这些值,我们可以说这个图不是弧一致的。回想一下,弧一致性是确保每个可能的情况都能满足。
对于特定节点的值,确实还有其他值可以选择。从这里我们可以看到,星期一和星期二并不是C可以选择的可能值,它们与B这样的节点不一致,因为B等于星期二,这意味着C不能是星期二。
因为a等于星期一,所以C也不能是星期一。通过保持C与a和B的一致性,我们可以将星期一和星期二从C的范围中移除,只留下例如星期三。如果我们继续强制保持一致性,就会发现有些值可以被选取。
我们可以得出其他结论,我们看到B的唯一选项是星期二,而C的唯一选项是星期三。因此,如果我们希望使Yi保持一致,那么II不能是星期二,因为这与B不一致;E不能是星期三,因为这与C不一致。
比如,我们可以直接说e等于星期一,然后我们可以开始再次执行这个过程。为了使d与b和e保持一致,d必须是星期三,这是唯一的选项,同样,我们也可以对F和G做出相同的判断。
结果表明,实际上我们能够在不进行任何额外搜索的情况下,通过强制保持一致性,找出所有变量的分配,而无需回溯。我们这样做的方法是通过交错的搜索过程和推理步骤。
强制我们一致性的步骤,通常称为维护我们一致性的算法,它每次我们向现有变量分配一个新值时,都会强制我们的一致性。有时我们可以使用那个ac3来强制我们的一致性。
这个算法在问题的开始阶段,即在我们开始搜索之前,就会被调用,以限制变量的域,从而使搜索更容易。我们还可以利用强制我们的一致性与搜索的交错,使得在搜索过程中每当我们进行新的分配时。
我们继续强制我们的一致性,以确保每当可能时,我们只是从域中消除可能的值。我们是怎么做到的呢?实际上,这相当于每次我们对变量X做出新的分配时,我们都会调用我们的ac3算法。
这个算法在约束满足问题上强制我们的一致性。我们开始时使用的不是所有的弧,而只是我们希望与我们刚刚分配的X保持一致的所有弧。
X的邻居是与X共享约束的某个东西,通过在回溯搜索过程中保持我们的一致性,我们最终可以使我们的搜索过程更加高效。这是这个回溯函数的修订版,和之前一样,只是这里有所变化。
每次我们向我们的分配中添加一个新的变量等于值时,我们都会运行这个推理过程。这个过程可能会做很多不同的事情,但它可能会调用维护我们一致性的算法,以确保我们能够强制我们的一致性。
可能会引入新的推论,并由此产生新的保证,例如这个变量必须等于那个值。这可能一次发生,也可能发生多次,只要这些推论没有失败,只要它们不导致某种情况。
在没有可能的前进进展的情况下,那么我们可以继续添加那些推理,有新的知识,我知道关于哪些变量应该分配给什么值的新知识,我可以将这些添加到分配中,以便更快地前进。
利用我所能获取的信息,我知道的信息是基于约束满足问题的其余结构,唯一需要做的变化是,如果发现这个值效果不好,那么我在这里需要去除的不仅仅是变量。
变量等于值,但我做出的任何推理也会从分配中去除,因此在这里,我们通常能够通过更少的回溯来解决问题,可能我们最初需要的只是利用每次进行新分配的事实。
可能将一个值降低其他变量的域。我们可以利用这些信息开始更快地得出结论,以更高效地解决问题,结果发现我们还有其他启发式方法可以用来提高我们的效率。
搜索过程也一样,实际上归结为我谈到的几个函数,但我们还没有真正讨论它们是如何工作的,其中一个是这个函数,选择未分配变量,我们在约束满足问题中选择某个变量。
到目前为止,我只是随机选择变量,就像挑选一个变量,然后决定好,这是我们将要分配的变量,然后从那里出发,但事实证明,通过稍微聪明一点,跟随。
某些启发式方法可以通过仔细选择下一个要探索的变量,使搜索过程变得更加高效,其中一些启发式方法包括最小剩余值(MRV)启发式,它通常表示如果我在选择变量时。
我应该选择具有最小域的变量,也就是剩余值最少的变量,想法是如果只剩下两个值,那我也许应该很快剪掉一个以便得到另一个,因为其中一个。
如果存在解决方案,两个必须是解决方案,有时最小剩余值可能。不会给出一个明确的结果,例如,当所有节点的剩余值相同,在这种情况下,另一个可以有帮助的启发式方法是度启发式,节点的度是节点的数量。
考虑到与该节点相连的节点数,如果你想象一下,我应该选择哪个变量,是选择一个连接许多事物的高连接度变量,还是选择一个连接较少事物的低连接度变量。
选择与最多其他节点连接的变量通常是有意义的,作为你首先搜索的对象,为什么会这样呢?因为选择一个高连接度的变量将立即限制其他变量的选择。
那这实际上可能是什么样子,让我们回到这个搜索问题,在这个特定情况下,我在这里做了一个赋值。
这里的问题是我接下来应该看什么,根据最小剩余值启发式,我应该选择剩余可能值最少的变量,在这种情况下,就是这个节点C,它在这个域中只剩下一个变量,即星期三。
这是一个非常合理的下一个赋值选择,因为我知道这是唯一的选项,例如我知道C的唯一可能选项是星期三,因此我不妨做这个赋值,然后可能探索其余空间,但与此同时在变量的数量更多的情况下,更有可能消除你根本不需要搜索的大部分状态空间。
在问题开始时,我对哪些节点应该拥有哪些值并没有任何知识,但我仍然必须选择第一个我尝试赋值的节点,我任意选择了一个在更多信息之上的节点。
尽管相同大小域的大小为3的最小剩余值并没有帮助,但我们可能注意到节点E具有最高的连接度,它与最多的事物相连,因此或许从节点E开始搜索比从最顶端的节点A开始更有意义。
从具有最高连接度的节点开始,首先从节点E进行搜索,因为从那里更容易施加约束,消除我可能不需要搜索的大部分搜索空间,实际上从E开始,我们可以立即。
我们可以实际分配其余的变量,而不需要进行任何回溯,即使我不使用这个推理过程,只需从一个度数较高的节点开始,这将很快限制其他可能值的选择。
这就是我们如何选择一个未分配变量的特定顺序,而不是随机选择一个变量。如果我们聪明地选择变量,我们可以通过确保不需要搜索来使搜索过程更高效。
穿越搜索空间的部分,最终不会影响我们尚未真正讨论的其他变量,这个领域值函数是针对一个变量,返回该变量所有值的序列。
从领域来看,天真的方法是我们之前所做的,按顺序进行,周一、周二再到周三,但问题是,这种顺序可能不是搜索的最有效顺序,有时选择更有可能有效的值会更高效。
解决方案优先,然后再考虑其他值。现在,你如何评估一个值更可能导致解决方案,或不太可能导致解决方案呢?你可以观察添加了多少约束,在你为变量赋予新值时,从领域中去除了多少东西。
针对这个特定值,我们可以使用的启发式方法是最少约束值启发式,即我们应该根据相邻值排除的选择数量,以顺序返回变量。
价值排除最少可能选项的想法是,如果我关心的仅仅是找到一个解决方案,如果我从一个排除很多其他选择的值开始,我就排除了很多可能性,这可能使得该特定节点能够采取的情况变得更不可能。
选择会导致一个解决方案,而另一方面,如果我有一个变量,并且我开始选择一个不会排除太多的值,那么我仍然有很大的空间,可能会有一个我最终可以找到的解决方案。
这与我们之前所谈论的相悖,我提到当你选择一个变量时,应该选择剩余可能值最少的变量,但在这里,我想选择那个最少约束的变量的值,基本思想是当我在选择一个变量时。
我希望通过选择一个变量来修剪搜索空间的大部分,从而快速消除可能的选项,而在考虑该变量可能取值时,我只想找到解决方案。
我想要做的,最终是选择一个仍然保留开放可能性的值。以便我找到解决方案的可能性尽可能高,不排除许多选项。我保留开放的可能性,仍然可以找到解决方案,而不需要在后来回溯,因此一个例子是。
如果我试图为节点C选择一个值,这里C可以是星期二或星期三,我们知道它不能是星期一,因为它与这里的领域发生冲突,我们已经知道A是星期一,所以C必须是星期二或星期三。
问题是我应该先尝试星期X还是先尝试星期三。如果我尝试星期X,会排除什么呢?这里会排除一个选项,第二个选项会被排除,第三个选项也会被排除。因此选择星期二会排除三个可能的选项,那可能的选项又会如何呢?
选择星期三,这会排除这里的一个选项,同时排除那里一个选项,因此我有两个选择,我可以选择星期二,这会排除三个选项,或者选择星期三,这会排除两个选项。根据最不约束值启发式,我应该选择那个排除最少选项的。
留下尽可能多的机会让我最终在状态空间中找到解决方案,最终如果你继续这个过程,我们将找到一个解决方案,一个变量与值的赋值,使我们能够给每一门考试、每一门课程设定考试日期。
不与任何同时注册两门课程的人发生冲突,因此现在的主要收获是,我们可以用多种不同的方式来表述一个问题。我们今天看过的方式是,我们可以将问题表述为局部搜索问题。
在我们观察当前节点并根据该邻居是否比我们正在观察的当前节点更好或更糟的情况下,移动到邻居。我们看到了将问题表述为线性程序,通过将事物以方程和约束的形式表达,我们能够解决。
让问题解决得更高效一点,我们看到将问题表述为约束满足问题,创建一个所有约束的图,这些约束与有某种关系的变量相连。
约束之间,并利用这些信息来确定解决方案应该是什么。因此现在的收获是,如果我们在人工智能中遇到一些问题,如果我们希望使用人工智能来解决它们,不论是试图找出医院的位置。
应该是或者试图解决旅行推销员问题,试图优化生产和成本,以及其他问题,或者试图找出如何满足某些约束,无论是数独难题还是如何安排大学的考试等。
各种类型问题的数量,如果我们能够将这个问题表述为这些类型的问题之一,那么我们就可以使用这些已知的算法,这些算法用于强制艺术一致性和回溯搜索,以及这些爬山算法和模拟退火算法。
单纯形算法和内点算法可以用来解决线性规划,我们可以利用这些技术开始解决各种各样的问题,这一切都在人工智能的优化领域内,这是一段关于人工智能的介绍。
今天我们将使用Python来看看你们好!
哈佛CS50-AI | Python人工智能入门(2020·完整版) - P14:L4- 模型学习 1 (机器学习,监督学习,感知器,svm) - ShowMeAI - BV1AQ4y1y7wy
[音乐],欢迎大家回到Python人工智能入门课程,到目前为止,我们已经利用AI解决了许多不同的问题,给出了如何寻找解决方案或如何满足某些约束条件的指示。
从某个输入点到某个输出点,以解决某种问题,今天我们将转向学习的世界,特别是机器学习的概念,它通常指的是我们不打算给计算机明确的执行任务的指示。
但是我们并不是给计算机提供关于如何执行任务的明确指示,而是让计算机访问以数据或模式的形式存在的信息,让它尝试找出这些模式,理解这些数据,以便能够独立执行任务,机器学习有许多不同的形式。
这个领域非常广泛,今天我们将深入探讨一些基础算法和概念,这些概念在机器学习的不同领域中都起着重要作用,而其中一个最受欢迎的想法是监督学习。
这种特定类型的任务是指,我们让计算机访问一个数据集,该数据集由输入/输出对组成,而我们希望计算机能够找出一些将输入映射到输出的函数,因此我们有一整套数据。
这通常由某种输入、证据或信息组成,计算机将能够访问这些信息,我们希望计算机根据这些输入信息预测某个输出将会是什么,我们将提供一些数据,以便计算机能够训练其模型,开始理解。
这些信息是如何工作的,输入和输出之间是如何关联的,但最终我们希望我们的计算机能够找出一个函数,给定这些输入,能够得到这些输出,在监督学习中有几种不同的任务,我们将重点关注其中之一。
我们将首先讨论的是分类,分类问题是,如果我给你一堆输入,你需要找出将这些输入映射到离散类别的方法,而你可以决定这些类别是什么,计算机的工作是预测。
这些类别将如何定义,例如,我给你关于某张钞票的信息,比如一美元钞票,我在询问你预测它是否属于真实钞票的类别,还是属于假钞的类别,你需要对输入进行分类。
训练计算机来找出一些函数来进行这个计算,另一个例子可能是我们在这门课上稍微提到的情况,我们想预测在某一天,知道那一天是否会下雨,以及是否会多云。
那一天,之前我们看到如果我们真的给计算机所有的确切概率,比如如果这些是条件,降雨的概率是什么,但通常我们没有访问到那些信息,不过我们确实拥有大量数据,所以如果。
我们希望能够预测一些事情,比如会不会下雨,我们会给计算机提供关于下雨和不下雨的历史信息,并让计算机寻找这些数据中的模式。那么这些数据可能是什么样的呢?我们。
我们可以像这样将数据结构化到一个表格中,这可能是我们表格的样子。对于任何特定的日子,我们有关于那天的湿度、空气压力的信息,重要的是我们有一个标签,某个人曾说过在这一天是下雨的或者是。
不下雨,所以你可以用很多数据填充这个表格,而这之所以被称为监督学习的练习,是因为有人为每一个数据点标注了标签,说明在湿度和气压为这些值的那天是一个。
下雨的日子和这一天是一个不下雨的日子,我们希望计算机能够根据这些输入,比如湿度和气压,来预测应该与那一天关联的标签。那一天看起来更像是会是一个。
这是否意味着会下雨,或者看起来更像是一个不会下雨的日子。从数学上讲,可以把这看作是一个接受两个输入的函数,这些输入是我们计算机可以获取的数据点,比如湿度和气压,因此我们可以写一个函数 f。
它以湿度和气压作为输入,输出将是我们为这些特定输入点所归类的类别,即我们会将什么标签与该输入关联。因此,我们在这里看到了一些示例数据点,给出了这个湿度值和这个气压值。
预测是会下雨还是不会下雨,我们从世界上刚收集的信息,在不同的日子里测量湿度和气压,观察在特定那天是否下雨,这个函数 f 是我们希望近似的现在。
计算机和我们人类并不确切知道这个函数f是如何工作的,它可能是一个相当复杂的函数,因此我们将尝试估计它。我们希望提出一个假设函数H,试图近似f的功能,我们想要提出一些。
函数H也会接受相同的输入,并产生一个输出:有雨或没有雨,理想情况下,我们希望这两个函数尽可能一致,因此监督学习分类任务的目标是弄清楚函数H是什么样的,我们该如何。
那么,如何开始做这件事呢?在这种情况下,我有两个数值,合理的做法是尝试将其绘制在图上。
在一个有两个轴的图表上,x轴和y轴,在这种情况下,我们将使用两个数值作为输入,但这些相同的想法在增加更多输入时也同样适用,我们将在二维中绘制事物,但如我们所见,可以添加更多输入。
想象事物在多个维度中,而我们人类在视觉上至少在三维之外的概念化方面存在困难,但计算机在尝试想象许多更多维度时没有问题,对计算机来说,每个维度只是一个独立的数字。
计算机在十维或百维中思考并不是不合理的,这样能够尝试解决问题,但现在我们只有两个输入,因此我们将在x轴上绘制事物,这里代表湿度,y轴在这里。
表示压力,我们可能会说,取所有下雨的天数,尝试在这个图表上绘制它们,看看它们在图表上的位置,你知道这里可能是所有的下雨天,每个下雨天用一个蓝点表示,代表一个特定的值。
针对湿度和特定压力值,然后我可能会对不下雨的天做同样的事情,比如取所有不下雨的日子,弄清楚这两个输入的值,并继续在这个图表上绘制它们,上面用红色绘制,而这里的蓝色则代表。
下雨天和红色在这里代表的是一个不下雨的日子,这就是我的计算机能够访问的所有输入。我希望计算机能够训练一个模型,使得如果我遇到一个没有标签的新输入时,能够开始估计基于所有这些信息和数据,应该将什么类别或标签分配给特定的数据点。
这里的白点,我想预测根据这两个输入的值,我们应该将其分类为蓝点(下雨天),还是将其分类为红点(不下雨的日子)。如果你仅仅从图像上看,试图说好吧,这个白点看起来像什么。
它属于蓝色类别还是看起来属于红色类别?我想大多数人会同意它可能属于蓝色类别。为什么呢?因为它看起来接近其他蓝点。这不是一个很正式的概念,但我们会在后面进行形式化。
稍等一下,因为它似乎接近这个蓝点,周围没有其他点比它更近,因此我们可能会说它应该被分类为蓝色。我认为这一天将会是雨天,基于这个输入,可能不是完全准确,但这是一个。
在这种算法中,做出相当不错的猜测实际上是一个非常流行的常见机器学习算法,称为最近邻分类。这是解决这些分类类型问题的算法。在最近邻分类中,它将执行这个算法。
所做的是给定一个输入,它将选择与该输入最近的数据点的类别。这里的类别我们只指雨天或非雨天、假冒或非假冒,我们根据最近的数据点选择类别或类别。
最近的数据点是蓝点还是红点?根据这个问题的答案,我们能够做出某种判断,可以说我们认为它会是蓝色的,或者我们认为它会是红色的。同样,我们可以将此应用于我们遇到的其他数据点。
如果突然出现这个数据点,它最近的数据是红色的,所以我们将其分类为红点,不下雨。但当你看看这里,问同样的问题时,事情会变得有点复杂。它应该属于蓝点(下雨天)类别,还是应该。
属于红点类别,而不是不下雨的日子。最近邻分类会说,解决这个问题的方法是看哪个点离那个点最近。你看这个最近的点,发现它是红色的,是个不下雨的日子,因此,根据最近邻。
对于这个未标记的点,我会说它也应该是红色的,它也应该被分类为不下雨的日子,但你的直觉可能会认为这是一个合理的判断,认为它最接近的东西是一个不下雨的日子,所以可以猜测它不下雨。
这一天,但从更大的角度看事情也是合理的,因为可以说,最近的点确实是一个红点,但它被许多其他蓝点包围,因此从更大的角度来看,可以认为这个点。
实际上应该是蓝色的,而仅凭这些数据我们实际上并不确定,我们给出一些输入,试图预测的内容,而我们不一定知道输出将是什么,因此在这种情况下,哪一个是正确的很难说,但通常考虑的不仅仅是一个。
考虑多个邻居有时可以给我们更好的结果,因此存在一种称为K最近邻分类算法的变体,其中K是我们选择的一个参数,即我们希望查看多少个邻居。
我们要看的一个最近邻分类是我们之前看到的,选择最近的一个邻居并使用该类别,但在K最近邻分类中,K可能是三、五或七,表示查看三个、五个或七个与该点最近的数据点。
这个点的工作方式略有不同,该算法会在给定输入的情况下,从K个最近的数据点中选择最常见的类别,因此如果我们查看五个最近的点,知道其中三个说下雨,两个说没下雨,我们将选择三个。
而不是两个,因为每个点实际上都会对他们认为类别应是什么投票,最终你选择票数最多的类别,因此K最近邻分类是一个相对简单易懂的算法,你只需查看邻居并找出。
答案可能是什么,事实证明这对于解决各种不同类型的分类问题非常有效,但并不是每个模型在每种情况下都能有效,因此今天我们特别要关注的一个方面是监督机器学习的背景。
有许多不同的方法来进行机器学习,也有许多不同的算法可以应用,所有这些算法都在解决同一种类型的问题,都是一些分类问题,我们希望将输入数据组织成不同的类别,而没有任何一个算法一定会比其他算法更好。
每种算法都有其权衡,可能根据数据的不同,一种类型的算法会更适合对该信息进行建模,而这正是许多机器学习研究的终点。
当你尝试应用机器学习技术时,往往不仅仅关注一个特定的算法,而是尝试多种不同的算法,看看哪个能够给你最好的结果,以预测将输入映射到输出的某个函数。那么,是什么呢?
K 最近邻分类的缺点,有几个。一个可能是,在一种天真的方法下,它可能会比较慢,因为必须遍历并测量一个点与这里每一个点之间的距离,现在有一些方法可以尝试解决这个问题。
有数据结构可以帮助更快速地找到这些邻居,还有一些技术可以用来尝试修剪一些数据或删除一些数据点,以便仅保留相关数据点,从而使其更容易,但。
最终,我们可能想要做的是想出另一种方法来进行分类,一种尝试分类的方法是查看邻近的点,但另一种方法可能是尝试查看所有数据,看看我们能否想出一些决策。
边界将雨天与非雨天分开,在二维的情况下,我们可以通过绘制一条线来做到这一点。例如,我们可能想尝试找到某条线,找到某个分隔符,将雨天(这里的蓝点)与非雨天分开。
雨天的红点在那边,我们现在正在尝试一种不同的方法。与仅查看输入数据点周围的局部数据的最近邻方法相对,现在我们所做的是尝试使用一种称为线性回归的技术来寻找某种。
将两部分分开的线现在有时实际上可能会得出一条完美分隔所有雨天和非雨天的线,但实际上这可能比许多数据集要干净得多,通常数据会更混乱。
存在离群值和特定系统中发生的随机噪声,我们希望仍然能够弄清楚一条线可能是什么样子。因此,实际上数据并不总是线性可分的,线性可分指的是我可以绘制一条线的一些数据集。
为了完美地分开这两个部分,而是可能会出现这样的情况:某些雨天点在这条线的一侧,而某些非雨天点在那条线的另一侧,并且可能没有一条线能够完美地分开输入的路径。
另一半完美地区分了所有雨天和非雨天,但我们仍然可以说这条线做得相当不错,我们稍后会试图正式化一下,当我们说这样的线在尝试进行预测时做得相当不错的意思。但现在让我们。
仅仅说我们正在寻找一条线,尽可能有效地将一类事物与另一类事物分开。那么现在我们试着在数学上更正式地表达这一点,我们想要想出某种函数,某种定义这条线的方法。
输入是像湿度和压力这样的东西,因此我们的输入可能称为x1,代表湿度,x2则代表压力。这些是我们将提供给机器学习算法的输入,基于这些输入,我们希望我们的模型能够。
能够预测某种输出,我们将使用我们的假设函数来进行预测,我们称之为H,假设函数将以x1和x2(湿度和压力)作为输入。在这种情况下,你可以想象如果我们不仅有两个输入,而是有三个、四个、五个或更多输入,我们可以。
让这个假设函数将所有这些作为输入,我们稍后也会看到一些例子。现在问题是,这个假设函数做什么呢?它实际上是在边界的一侧还是在另一侧?我们如何正式化这个边界呢?
边界通常将是这些输入变量的线性组合,至少在这个特定的案例中。那么我们所说的线性组合就是取每个输入并将其乘以一个我们需要弄明白的数字,我们通常称之为。
数字表示这些变量在试图确定答案时应该有多重要,因此我们将对这些变量加权,我们可能会再加上一个常数,以试图使这个函数有些不同,结果我们只需比较,看看它是大于。
零还是小于零,以说它不属于一侧的线或另一侧的线。那么,这个数学表达式可能看起来像这样:我们将取每个变量X1和X2,将它们乘以一些权重,我现在还不知道那个权重是什么,但它将是一些。
权重1,也许我们只想加上一些其他权重,因为函数可能要求我们将整个值上移或下移某个量,然后我们只需比较,如果我们做所有这些映射,是否大于或等于0,如果是,我们可能将这个数据点分类为雨天。
否则我们可能会说没有雨,因此关键在于这个表达式是我们将如何计算是否是雨天,我们将进行一系列数学运算,将每个变量乘以一个权重,也许再加一个额外的权重,看看结果是否大于或等于0。
并且利用这个表达式的结果,我们能够确定是否下雨,这个表达式在这里的情况将仅指代某条线,如果你绘制图表,它将只是一些线,而这条线的实际样子取决于这些权重x1和。
x2是输入,但这些权重实际上决定了那条线的形状、斜率以及那条线的实际样子,因此我们想要弄清楚这些权重应该是什么,我们可以选择任何权重,但我们希望以这样的方式选择权重:如果你传入。
在雨天的湿度和压力下,你最终得到的结果是大于或等于0的,我们希望这样,如果我们输入到我们的假设函数中不是雨天,那么我们得到的输出应该是不下雨,因此在到达那里之前,让我们尝试把这更正式化一些。
从数学上讲,这样你就可以理解,如果你进一步深入监督学习并探索这个概念,你会经常看到这个。一件事是,通常对于这些类别,有时只会使用类别的名称,比如“下雨”和“不下雨”,通常在数学上如果我们。
在这些事物之间进行比较时,处理数字世界更容易,所以我们可以说1和0,1代表下雨,0代表不下雨,因此我们做所有这些数学运算,如果结果大于或等于0,我们将继续说我们的假设函数输出1,意味着下雨。
否则,输出为零,意味着不下雨,通常这类表达会用向量数学来表示,而向量如果你不熟悉这个术语,是指数值序列,你可以在Python中用数值列表表示。
值或带有数值的元组,在这里我们有几个数值序列,我们的一个向量,数值序列之一是所有这些单独的权重w0、w1和w2,因此我们可以构造一个我们称之为权重向量的东西,我们稍后将看到这有什么用,称为W。
通常用加粗的W表示,这只是这三个权重的序列:权重0、权重1和权重2,为了能够基于这些权重计算我们认为一天是下雨还是不下雨,我们将把每个权重与我们的输入变量之一相乘。
权重将会乘以输入变量X2,W1将会乘以输入变量X1,而W0,嗯,它并没有被任何东西乘以,但为了确保向量长度一致,我们稍后会看到这为什么有用,我们只是说W0被乘以。
因为你可以乘以某个数,乘以1后最终得到的就是确切的相同数字,所以除了权重之外,向量W还会有一个输入向量,我们称之为X,它有三个值,再一次,因为我们只是在将W零乘以1,最终然后是X1和X2,所以这里我们已经表示出来。
两个不同的向量,即我们需要以某种方式学习的权重,机器学习算法的目标是学习这个权重向量应该是什么,我们可以选择任何任意的数字集,它将产生一个尝试预测是否下雨的函数,但它可能不会很有效。
这可能不是很好,我们想要做的是提出这些权重的良好选择,以便我们能够进行准确的预测,然后这个输入向量表示某个特定输入到函数中,即我们希望估计的一个数据点,看看那天是雨天还是非雨天。
而且这将根据提供给我们函数的输入而有所不同,我们正在尝试估计什么,然后为了进行计算,我们想要计算这个表达式,结果表明这个表达式就是我们所说的这两个向量的点积。
两个向量的乘积仅意味着将向量中的每一项相乘,W0乘以X1,W1乘以X1,W2乘以X2,这就是为什么这些向量需要相同长度的原因,然后我们将所有结果相加,所以W和X的点积。
权重向量和我们的输入向量,只会是W0乘以1或者说是W0加上W1乘以X1,将这两个项相乘,再加上W2乘以X2,将这些项相乘,所以我们有我们的权重向量,我们需要搞清楚我们需要我们的机器学习算法来弄明白什么。
权重应该是,我们有代表我们尝试预测的类别的数据点的输入向量,预测标签,我们能够通过计算这个点积来做到这一点,这在向量形式中经常被表示,但如果你之前没有见过向量,你可以。
可以把它想象成与这个数学表达式完全相同,只是进行乘法运算,将结果相加,然后查看结果是否大于或等于0,这里的表达式与我们计算的表达式是完全相同的,以查看那个答案是否成立。
因此,你通常会看到假设函数写成像这样一个更简单的表示,其中假设以某些输入向量X(某天的湿度和压力)作为输入。我们想要预测一个输出,比如下雨或不下雨,或者选择1或0。
数字表示事物的方式是通过计算权重和输入的点积。如果结果大于或等于零,我们将说输出为1;否则,输出为0。这个假设被认为是由权重参数化的,具体取决于我们选择的权重。
如果我们随机选择权重,最终得到的假设可能会有所不同,可能不会得到一个非常好的假设函数。我们会得到1或0,但这可能不会准确反映我们认为某一天是否会下雨,但如果我们正确选择权重。
我们通常可以很好地估计函数的输出应该是1还是0。那么问题是如何确定这些权重应该是什么,如何调整这些参数。有很多方法可以做到这一点,其中一种最常见的方法称为感知机学习规则,我们稍后会详细介绍。
感知机学习规则的思想是,对于我们希望从中学习的数据点来说,不会深入数学,我们主要是以概念性介绍,假设给定一些数据点。
有一个输入X和一个输出Y,其中Y为1表示下雨,0表示不下雨。然后我们将更新权重,稍后我们将看看公式,但大体思路是,我们可以从随机权重开始,然后像从数据中学习一样,逐个数据点处理。
在所有数据点中找出好吧,我们需要在权重中更改哪些参数,以更好地匹配该输入点。因此,拥有大量数据在监督学习算法中的价值在于,你逐个处理每个数据点,也许会查看。
我们会多次尝试并不断确定是否需要调整权重,以便更好地创建一个能够正确或更准确地估计输出的权重向量。无论我们认为要下雨还是不下雨。
要下雨了,那么在不深入数学的情况下,权重更新看起来像什么呢?我们将更新每个权重,使其成为原始权重加上一些附加表达式,而要理解这个表达式,为什么呢?因为实际输出大于或等于0。
是输入 X 的假设,这将是我们认为的输入,所以我可以用实际值减去我们的估计来替代,并根据实际值和估计值之间的差异,我们可能想要改变我们的假设,改变我们。
进行这种估计的方式,如果实际值和估计值是相同的,那么意味着我们能够正确预测这个数据点属于哪个类别。那么实际值减去估计值,这将是零,这意味着右边的整个项将变为零,权重。
不会改变权重,我在这里的权重是指,权重保持不变,如果我们能够正确预测输入属于哪个类别,但如果我们的假设没有正确预测输入属于哪个类别,那么也许。
我们需要做一些调整,以便更好地预测未来这种数据点,而我们可能的方式是,如果实际值大于估计值,那么现在我们就假设这些 X 是正值。
如果实际值大于估计值,那就意味着我们需要增加权重,以使输出更大,因此我们更有可能达到正确的实际值,所以如果实际值大于估计值,那么实际值减去估计值将是一个正数。
所以你可以想象,我们只是给权重增加一个正数,以稍微增加它,同样反向的情况也是如此,如果实际值小于估计值,实际值是零,但我们估计为一,意味着实际并没有下雨,但我们预测为下雨。
如果预判为下雨,那么我们想要减少权重的值,因为在这种情况下,我们想尽量降低计算该点积的总值,以使我们预测实际会下雨的可能性更小,所以不需要深入到数学。
总的思路是,每次遇到数据点时,我们可以相应地调整这些权重,以便更好地与我们所获得的实际数据对齐,你可以重复这个过程,逐个数据点,直到最终希望你的算法。
收敛到一些权重,这些权重相当好地尝试去判断一天是否会下雨,或者不会下雨,最后一点关于这个特定方程的值 alpha,这通常被称为学习率,它只是我们选择的一个参数,用来决定变化的速度。
我们实际上要更新这些权重值,因此如果 alpha 较大,我们将大幅更新这些权重值;如果 alpha 较小,我们将较少地更新权重值,你可以根据问题选择一个 alpha 的值,不同的值可能更适合。
情况,与其他情况相比是更好还是更差,所以在经历了这些之后,在我们完成了这个训练过程,利用所有这些数据并使用这个学习规则,查看所有的数据,并用每一条数据作为我们是否应该保持权重不变、增加权重或减少权重的指示。
如果是的话,那究竟是多少呢?你最终得到的实际上是一个阈值函数,我们可以看看阈值函数的样子。在 x 轴上,我们有这个函数的输出,它取权重并与输入进行点积;在 y 轴上,我们有输出的结果。
其中一个结果是零,这在这里表示不下雨,而另一个结果是1,这在这里表示下雨。我们的假设函数的工作方式是计算这个值,如果它大于零或大于某个阈值值,我们就声明今天是下雨天;否则我们就声明今天不下雨。
从图形上看,这个函数的样子是这样的:最初,当这个点积的值较小时,它是不下雨的;但一旦它超过了那个阈值,我们突然说,好吧,现在是下雨了,现在是下雨,不是的。
它是下雨的,解读这种表示的方法是,这条线这一侧的任何东西都会被视为数据点的类别,我们会说“是的,下雨了”;而落在这条线另一侧的数据点则是我们会说“没下雨”。我们希望在两者之间进行选择。
对于权重的一些值,结果是一个相当不错的函数,试图进行这种估计。但这种硬阈值有一个棘手的地方,就是它只留下两种可能的结果。没错,我们输入一些数据,输出的结果就是下雨或不下雨,根本没有其他可能。
也许这就是你想要的,或许你只想根据一些数据点,能够将其分类为这几个不同的类别之一或多个,但也可能你关心的是知道这个预测的强度,例如,如果我们回到这个。
这是一个例子,我们在这条线的这一侧有下雨的日子,而在那一侧则是非下雨的日子。你可能想象,现在来看看这两个白色的数据点。这个数据点是我们希望预测标签或类别的;而这个数据点我们也希望进行预测。
一个标签或类别似乎很可能你可以相当自信地说,这个数据点应该是一个雨天,似乎靠近其他雨天,如果我们依据最近邻策略,它在这条线的这一侧,如果我们根据仅仅是说你知道哪个。
这条线的哪一侧,它应该落在哪一侧,通过弄清楚这些权重应该是什么。如果我们使用线性策略,仅仅判断它落在哪一侧,哪个是这个决策边界的侧面,我们还可以说,这个点也是一个雨天,因为它在这条线的一侧。
这与雨天相对应,但即使在这种情况下,我们也可能知道,我们对左侧的这个数据点的信心远没有右侧的这个数据点高,因此我们对右侧的这个数据点可以非常有信心,“是的,这是一个雨天”,而这个你。
如果我们仅通过距离来判断,它距离线非常近,因此你可能不太确定,但我们的阈值函数不允许对于某事物有“更不确定”或“更确定”的概念,这就是我们所称的硬阈值。一旦你跨越了这条线,我们就立刻说“是的,这将会下雨”。
这是一个雨天,在线的前面我们将说它不是一个雨天,这在许多情况下可能没有帮助。首先,这不是一个特别容易处理的函数,如果你深入了解机器学习,并试图做一些像求导这样的事情。
这种类型的函数使事情变得复杂,但另一个挑战是,我们对事物之间的渐变没有真正的概念。我们没有“是的,这是一个非常强烈的,只有强烈的信念它会下雨”,与“可能比不下雨要更可能”相对。
可能会下雨,但也许对此并不完全确定。因此,我们可以利用一种称为逻辑回归的技术,取而代之的是使用这种硬阈值类型的函数,我们可以使用一个逻辑函数,我们可以称之为软阈值,这样就可以了。
将这个转换成更像这样的东西,使其更平滑,因此可能的输出值不再仅仅是零和一:
04 不下雨的情况,虽然下雨,但你实际上可以得到任何在零和一之间的真实数值。如果你在这边,那么你得到的值是零,好的,它不会下雨,我们对此非常确定。如果你在这边,你得到的值是一个,像是“是的,我们非常确定会下雨”。
下雨,但在两者之间,你可以得到一些真实的数值,其中一个值,比如0.7,可能意味着我们认为会下雨,基于数据它下雨的可能性比不下雨要高,但我们对某些其他数据点可能没有那么自信。因此,软阈值的一个优势。
它允许我们有一个输出,可能是某个实数,反映某种概率,即我们认为这个特定数据点属于那个特定类别的可能性。此外,还有一些其他不错的数学属性。
有两种不同的方法来解决这种分类问题,一种是最近邻的方法,你只需取一个数据点,查看附近的数据点,以估计它属于哪个类别,另一种方法则是通过。
好吧,让我们尝试使用线性回归,调整权重以确定最能分隔这两类的决策边界。结果表明,另一种非常流行的方法是,当你有一个穿过数据集的对角线。
设置并开始尝试进行一些学习,这就是我们所称的支持向量机。我们不会过多讨论支持向量机的数学,但至少会从图形上探讨它的样子以及支持向量机背后的动机。
比如说,我在这里有红数据点,而蓝数据点在这里,我可以画出这样的一条线,这条线将红点与蓝点分开。
它能够完美地将红点与蓝点分开,所有红点都在直线的一侧,所有蓝点都在另一侧。但如果你得出了一个看起来像这样的模型,可能会让你有些紧张。
我担心的是,它在其他不一定在我们拥有数据集中的数据点上的泛化能力。例如,如果有一个点正好位于直线的右侧。
我想猜测这实际上是一个红点,但它落在了线的一侧,而我们估计它是一个蓝点。因此,这条线可能不是一个很好的选择,因为它与这些数据点太接近。
这条线也成功地将所有红点从所有蓝点中分开。从某种角度来看,实际上有很多不同的决策边界可以画出,以分隔两组数据。
一些权重设置,让我们能够预测正确的输出,这条线将每次都为这组数据预测正确的输出,因为红点在一侧,蓝点在另一侧,但你应该再稍微谨慎一点。
我们会感到紧张,因为这条线与这些红点如此接近,即使我们能够正确预测输入数据。如果有一个点落在这个一般区域,我们的算法模型会说是的,我们认为它是蓝点,而实际上它可能属于红色。
分类仅仅因为它看起来接近其他红点。我们真正想说的是,给定这些数据,如何尽可能好地概括它,就是想出这样一条看似直观的线,而之所以直观,是因为它。
看起来尽可能远离红色数据,因此,如果我们稍微概括一下,假设我们有一些与输入不同的点,但仍然稍微远离,我们仍然可以说这一侧的东西可能是红色的,而那一侧的东西可能是蓝色的,我们可以做出这些。
这样进行判断,这正是支持向量机设计的目的。它们旨在寻找我们所称的最大边际分隔器,最大边际分隔器仅仅是最大化的某种边界。
在点组之间的距离,而不是想出其他某种边界。在之前的情况下,我们不会在意,只要我们能很好地对输入进行分类,这似乎就是我们需要做的一切,支持向量机会尝试找到这个最大边际分隔器,试图找出。
最大化特定的距离,它通过找到我们称之为支持向量的向量来实现,这些向量与线最近,并试图最大化线与这些特定点之间的距离。在二维中是这样,在更高维度中也是如此。
我们并不是在寻找某条将两个数据点分开的线,而是在寻找我们通常称之为超平面的东西,即有效地将一组数据与另一组数据分开的决策边界,而支持向量机在更高维度中工作的能力实际上有很多。
还有其他应用,但其中之一是它很好地处理数据可能不是线性可分的情况,因此我们之前讨论了线性可分性。这个想法是,你可以取数据,画一条线或某种输入的线性组合,这使我们能够完美地将两组分开。
彼此之间有一些数据集是不可线性分开的,有些情况下你甚至无法找到一条好的线来进行这种分离,比如像这样,或者如果你想象这里有红点和周围的蓝点,如果你试图找到一条线将其划分开来。
将红点与蓝点区分开来实际上是困难的,并不是不可能。如果你选择在这里画一条线,那么你就忽略了这些本该是蓝色而不是红色的点。无论你在哪里画线,都会有很多错误。
误差很多,这将很快称为损失,你将有很多点被错误分类。我们真正想要的是能够找到更好的决策边界,它可能不仅仅是通过这个二维空间的一条直线。
向量机所能做的是,它们可以开始在更高维度中操作,并能够找到一些其他的决策边界,比如在这种情况下的圆,实际上能够更好地区分这些数据集。
可分离的支持向量机通过在更高维度中工作,实际上可以找到有效解决这类问题的方法。因此,我们可以看到三种不同的方法来尝试解决这些问题,我们看到支持向量机,并且我们尝试使用线性回归。
感知器学习规则旨在找出如何对输入和输出进行分类,我们在最近邻方法中看到,没有一种方法一定比其他方法更好。这又要取决于你所访问的数据集和信息,以及你要处理的函数的形状。
最终的目标是进行预测,这也是很多研究和实验涉及的地方,试图找出如何最佳执行这种估计,但分类只是你可能在监督学习中遇到的任务之一。
分类我们要预测的是某个离散类别,我们试图预测红色或蓝色的雨。