在本章中,我们将在多个领域为 Python 编程语言和计算思维的应用提供示例。我们将探索多个领域,如人文、语言学、密码学等。我们将使用到目前为止所学的关于计算思维和Python编程语言的知识来完成以下工作:
- 分析历史演讲
- 写故事
- 计算文本可读性
- 找到最有效的路线
- 实现加密
- 实施网络安全
- 创建聊天机器人
本章与其他章节不同,因为我们将在评估每个场景后,专门介绍问题并提供算法解决方案。
您需要安装最新版本的 Python 才能运行本章中的代码。
您需要为 Python 安装以下库和包:
- NLTK
- 凯洛斯
- 熊猫
- Matplotlib
- Seaborn
您可以在此处找到本章中使用的完整源代码:https://github.com/PacktPublishing/Applied-Computational-Thinking-with-Python/tree/master/Chapter15
历史是非常引人入胜的,我们之所以要研究编写算法来评估历史数据和上下文,有很多原因。
对于这个问题,我们想分析一些历史文本。特别是,我们要看一看亚伯拉罕·林肯的第二次就职演说。我们的目标是找到一些单词的频率。我们之所以想进行一些简单的文本分析,有很多原因,尤其是对于历史文本。我们可能想要比较它们,了解潜在的主题,等等。
对于我们的算法,我们将使用一个相当简单的设计,使用nltk包。由于一些组件的安装与我们目前所做的有点不同,我们将提供一些信息,以防您的软件包尚未安装。
在 Python shell 中,如果您在活动控制台中,请创建一个新文件,并在安装主包后导入nltk(使用pip install nltk。
请注意,您不应处于活动的外壳窗口中。如果您在行首看到>>>,请点击文件****新建文件选项,然后为您的空壳输入以下代码指令行:
import nltk
nltk.download()从前面的代码中注意,您还将在nltk中打开下载程序。前面的代码将弹出一个窗口,如下面的屏幕截图所示(请注意,nltk库占用大约 7MB 的内存,而安装额外的包也需要内存,每个包的内存范围从几 KB 到 14 到 15MB):
图 15.1–NLTK 下载程序
如您所见,我的软件包都已安装。如果不是,请选择所有,然后单击该窗口左下角的下载按钮。安装软件包后,可以关闭窗口。
因为我们的问题相当简单,所以在这一节中我们将跳过大部分计算思维过程。我们只是想知道演讲中使用的词的频率。因此,让我们直接进入算法,看看我们将如何使用nltk包来获得我们需要的,包括数据的可视化表示:
-
First, we'll need to import
nltkand theword_tokenizefunction. Theword_tokenizefunction allows us to divide the speech into individual words and/or punctuation marks. We'll need the speech text. In this case, the speech is copied into the algorithm. You could potentially import the file into the algorithm and do it that way.sent_tokenize函数是句子标记化的缩写。与单词标记化通过单词和标点符号分解文本的方式相同,句子标记化功能允许将文本分解为完整的句子。输出将包含以逗号分隔的列表中的句子。重要提示:
重要的是要知道,所有引号都已分别使用
\'或\"进行了转义,以便维护原始文本而不会在代码中产生错误。以下算法包含了我们分析亚伯拉罕·林肯第二次就职演说所需的一切:
ch15_historicalTextAnalysis.py
import nltk from nltk.tokenize import sent_tokenize, word_tokenize
演讲的全文包含在 GitHub 存储库文件中。出于篇幅的考虑,我们在这里只包含了该文本的一半。请注意,我们将在算法和解释之后共享的输出将对应于被截断的语音,但可视化绘图将包括来自整个语音的数据。在
text定义末尾的以下代码中可以看到[…],它只是用来表示有额外的文本:text = 'Fellow-Countrymen: At this second appearing to take the oath of the Presidential office there is less occasion for an extended address than there was at the first. Then a statement somewhat in detail of a course to be pursued seemed fitting and proper. Now, at the expiration of four years, during which public declarations have been constantly called forth on every point and phase of the great contest which still absorbs the attention and engrosses the energies of the nation, little that is new could be presented. The progress of our arms, upon which all else chiefly depends, is as well known to the public as to myself, and it is, I trust, reasonably satisfactory and encouraging to all. With high hope for the future, no prediction in regard to it is ventured. On the occasion corresponding to this four years ago all thoughts were anxiously directed to an impending civil war. All dreaded it, all sought to avert it. While the inaugural address was being delivered from this place, devoted altogether to saving the Union without war, urgent agents were in the city seeking to destroy it without war—seeking to dissolve the Union and divide effects by negotiation. Both parties deprecated war, but one of them would make war rather than let the nation survive, and the other would accept war rather than let it perish, and the war came. One-eighth of the whole population were colored slaves, not distributed generally over the Union, but localized in the southern part of it. These slaves constituted a peculiar and powerful interest. All knew that this interest was somehow the cause of the war. To strengthen, perpetuate, and extend this interest was the object for which the insurgents would rend the Union even by war, while the Government claimed no right to do more than to restrict the territorial enlargement of it. Neither party expected for the war the magnitude or the duration which it has already attained. […]'
-
现在我们已经定义了我们想要分析的文本,如前面的代码片段所示,我们可以告诉算法我们想要标记文本,也就是说,我们想要将其划分为单词。算法将打印出一个列表,其中包含由逗号分隔的每个单词或标点符号,如以下代码所示:
tokenized_word = word_tokenize(text) print(tokenized_word)
-
在我们有了单词列表之后,我们想要得到单词的频率分布。为此,我们将从
nltk.probability导入包,如下面的代码片段所示:from nltk.probability import FreqDist fdist = FreqDist(tokenized_word) print(fdist) fdist.most_common(2)
-
Once we have the distribution, we'll want a visual plot of this data, so we'll use
matplotlibto create our distribution plot, as shown in the following code snippet:import matplotlib.pyplot as plt fdist.plot(30, cumulative = False) plt.show()
这就是我们需要的整个算法。当我们运行算法时,我们的输出是这样的:
['Fellow-Countrymen', ':', 'At', 'this', 'second', 'appearing', 'to', 'take', 'the', 'oath', 'of', 'the', 'Presidential', 'office', 'there', 'is', 'less', 'occasion', 'for', 'an', 'extended', 'address', 'than', 'there', 'was', 'at', 'the', 'first', '.', 'Then', 'a', 'statement', 'somewhat', 'in', 'detail', 'of', 'a', 'course', 'to', 'be', 'pursued', 'seemed', 'fitting', 'and', 'proper', '.', 'Now', ',', 'at', 'the', 'expiration', 'of', 'four', 'years', ',', 'during', 'which', 'public', 'declarations', 'have', 'been', 'constantly', 'called', 'forth', 'on', 'every', 'point', 'and', 'phase', 'of', 'the', 'great', 'contest', 'which', 'still', 'absorbs', 'the', 'attention', 'and', 'engrosses', 'the', 'energies', 'of', 'the', 'nation', ',', 'little', 'that', 'is', 'new', 'could', 'be', 'presented', '.', 'The', 'progress', 'of', 'our', 'arms', ',', 'upon', 'which', 'all', 'else', 'chiefly', 'depends', ',', 'is', 'as', 'well', 'known', 'to', 'the', 'public', 'as', 'to', 'myself', ',', 'and', 'it', 'is', ',', 'I', 'trust', ',', 'reasonably', 'satisfactory', 'and', 'encouraging', 'to', 'all', '.', 'With', 'high', 'hope', 'for', 'the', 'future', ',', 'no', 'prediction', 'in', 'regard', 'to', 'it', 'is', 'ventured', '.', 'On', 'the', 'occasion', 'corresponding', 'to', 'this', 'four', 'years', 'ago', 'all', 'thoughts', 'were', 'anxiously', 'directed', 'to', 'an', 'impending', 'civil', 'war', '.', 'All', 'dreaded', 'it', ',', 'all', 'sought', 'to', 'avert', 'it', '.', 'While', 'the', 'inaugural', 'address', 'was', 'being', 'delivered', 'from', 'this', 'place', ',', 'devoted', 'altogether', 'to', 'saving', 'the', 'Union', 'without', 'war', ',', 'urgent', 'agents', 'were', 'in', 'the', 'city', 'seeking', 'to', 'destroy', 'it', 'without', 'war—seeking', 'to', 'dissolve', 'the', 'Union', 'and', 'divide', 'effects', 'by', 'negotiation', '.', 'Both', 'parties', 'deprecated', 'war', ',', 'but', 'one', 'of', 'them', 'would', 'make', 'war', 'rather', 'than', 'let', 'the', 'nation', 'survive', ',', 'and', 'the', 'other', 'would', 'accept', 'war', 'rather', 'than', 'let', 'it', 'perish', ',', 'and', 'the', 'war', 'came', '.', 'One-eighth', 'of', 'the', 'whole', 'population', 'were', 'colored', 'slaves', ',', 'not', 'distributed', 'generally', 'over', 'the', 'Union', ',', 'but', 'localized', 'in', 'the', 'southern', 'part', 'of', 'it', '.', 'These', 'slaves', 'constituted', 'a', 'peculiar', 'and', 'powerful', 'interest', '.', 'All', 'knew', 'that', 'this', 'interest', 'was', 'somehow', 'the', 'cause', 'of', 'the', 'war', '.', 'To', 'strengthen', ',', 'perpetuate', ',', 'and', 'extend', 'this', 'interest', 'was', 'the', 'object', 'for', 'which', 'the', 'insurgents', 'would', 'rend', 'the', 'Union', 'even', 'by', 'war', ',', 'while', 'the', 'Government', 'claimed', 'no', 'right', 'to', 'do', 'more', 'than', 'to', 'restrict', 'the', 'territorial', 'enlargement', 'of', 'it', '.', 'Neither', 'party', 'expected', 'for', 'the', 'war', 'the', 'magnitude', 'or', 'the', 'duration', 'which', 'it', 'has', 'already', 'attained', '.']
-
Recall that
word tokenizationonly included the truncated text. However, the frequency information and the plot that follow are for the entire speech. Thech15_historicalTextAnalysis.pyGitHub file includes the full speech:<FreqDist with 365 samples and 782 outcomes>
以下屏幕截图显示了该算法的频率分布可视化图:
图 15.2——亚伯拉罕·林肯第二次就职演说的频率分布图
一旦我们掌握了这些信息,我们就可以开始更仔细地研究最常用的单词。当使用这种分析时,您可能需要考虑删除一些单词,如 AutoT0} to Tyl T1,Ont2,Ty3 T3,and Ty4 T4。然而,像年和联盟这样的词语可能与我们的分析相关。
这个算法可以做很多调整,但是现在,我们已经设法至少得到了历史语音的频率分布图。现在,我们将进入下一个问题。
让我们看一个相当简单的问题。在本节中,我们希望创建一个算法,根据用户的输入生成一个故事。我们可以让它变得简单,或者添加一些选项。但让我们深入了解这是什么。
首先,*我们想要创造什么?*好吧,一个故事。由于这个问题的性质,我们将从相反的方向开始,用我们想要实现的输出样本,即一个样本故事。让我们看一个由我们的 To3 T3 算法生成的快速故事,然后我们进入算法:
There once was a citizen in the town of Narnia, whose name was Malena. Malena loved to hang with their trusty dog, King Kong.
You could always see them strolling through the market in the morning, wearing their favorite blue attire.前面的输出是由一个算法创建的,该算法替换了名称、位置、时间、宠物和宠物名。这是一个简短的故事,但它可以在更广泛的应用中使用,例如使用输入编写社交媒体帖子,以及填写邀请、表单等信息。
所以,让我们倒过来写一下我们的算法。*为什么这次我从最后开始?*在这种情况下,我们从最终结果中知道我们想要什么。你可以写你的故事。你可以有一个你需要填写的婚礼邀请模板的例子,或者表格。现在我们必须弄清楚如何获得输入,然后输出我们想要的。
从所示的故事中,我们可以获得以下内容的原始输入:
- 字符名
- 城镇名称
- 宠物种类
- 宠物名称
- 参观部分城市
- 一天中的时间
- 喜爱的颜色
当我们编写我们的算法时,我们需要获得前面提到的所有输入。让我们看一看,在 To.T0A.文件中找到的算法:
-
We will need inputs from the user, so we want to use a
printstatement and input requests that include instructions for what is needed:print('Help me write a story by answering some questions. ') name = input('What name would you like to be known by? ') location = input('What is your favorite city, real or imaginary? ') time = input('Is this happening in the morning or afternoon? ') color = input('What is your favorite color? ') town_spot = input('Are you going to the market, the library, or the park? ') pet = input('What kind of pet would you like as your companion? ') pet_name = input('What is your pet\'s name? ')
前面的代码片段获取了用户的所有输入,因此我们可以编写我们的故事。
-
Once we have those, we have to
printour story. Notice that we wrote it in simple terms, using%sso we could replace it with the corresponding inputs. We also used backslashes so that we can see our code on multiple lines, rather than have it in one long line:print('There once was a citizen in the town of %s, whose name was %s. %s loved to hang \ with their trusty %s, %s.' % (location, name, name, pet, pet_name)) print('You could always see them strolling through the %s \ in the %s, wearing their favorite %s attire.' % (town_spot, time, color))
让我们再运行一次代码,看看我们的故事现在是怎么说的:
Help me write a story by answering some questions. What name would you like to be known by? Azabache What is your favorite city, real or imaginary? Rincon Is this happening in the morning or afternoon? afternoon What is your favorite color? magenta Are you going to the market, the library, or the park? library What kind of pet would you like as your companion? dog What is your pet's name? Luna There once was a citizen in the town of Rincon, whose name was Azabache. Azabache loved to hang with their trusty dog, Luna. You could always see them strolling through the library in the afternoon, wearing their favorite magenta attire.
请注意,角色和设置等详细信息已更改。在教育学习环境中,这样一个简单的算法可以成为向学生展示如何与故事互动和识别其中关键信息的一个很好的工具。
虽然这是一个三句话的故事,但这些算法可能要复杂得多,提供了一个利用用户输入编写精彩原创故事的机会。如果您想尝试其中的一些方法,您甚至可以根据某些输入设置要使用的短语的条件,例如根据输入的名称的长度更改使用的句子。享受代码和编写故事的乐趣吧!
在本节中,我们将看一个与语言学相关的应用程序,特别是任何文本的可读性级别。我们将在下面的代码片段中使用马丁·路德·金的我有一个梦想演讲。您可以将其替换为任何文本文件,只要您更改文件的位置和文件名以准确反映在代码中。完整代码可在ch15_Readability.py文件中找到。
在我们进入代码之前,让我们先谈谈我们在寻找什么以及为什么它很重要。了解文本的可读性有助于我们决定是否将其包含在演示文稿、学校年级以及更多内容中。Flesch-Kincaid 评分是用来确定可读性的,是在 20 世纪 40 年代开发的。
鲁道夫·弗莱希(Rudolf Flesch)在美联社(Associated Press)担任顾问时创建了该网站,旨在提高报纸的可读性。最初被称为Flesch Reading Ease,后来被现代化为美国海军目前使用的设备。Flesch-Kincaid 分数现在提供了一个等级分数,而不是一个必须转换成等级的分数。
虽然我们不会使用这个公式,但了解我们使用的背景是很重要的。Flesch 读数公式如下所示:
Flesch-Kincaid 品位公式如下所示:
前面的公式存在于 Python 可用的可读性包中。如果我们导入包,我们就能够用相当简单的代码执行可读性分析。
让我们看看我们需要的损坏代码,以便对马丁·路德·金演讲进行可读性分析:
-
Firstly, remember to change the path for your file in the code and then import the necessary packages for the code:
ch15_readability.py
from readability import Readability text = open('C:\\...\\ch15_MLK-IHaveADream.txt') text_up = text.read() r = Readability(text_up) flesch_kincaidR = r.flesch_kincaid()
从前面的代码中,您将看到我们将
readability包导入了我们的程序。如果需要安装库/包,可以使用pip install readability进行安装。一旦我们有了必要的库,我们就可以打开要分析的文件。我们还想告诉算法读取文本,我在这里调用
text_up进行文本上传,所以我不会忘记我正在读取一个打开的文件。这是我们在前面代码中从文件位置打开的文本。最后,我们要求程序使用Readability函数分析文本。请注意,我们将其保存到了r。 -
After we've done all of that, we can
printour grade level with the following code snippet:print('The text has a grade '+ flesch_kincaidR.grade_level + ' readability level.')
当我们运行算法时,我们的输出也相当简单。请看以下输出:
The text has a grade 9 readability level.
既然您已经知道如何验证任何文本的可读性,请尝试对其他类型的文本进行分析,包括诗歌、故事、演讲、歌曲等。
对于这个问题,当学习算法时,我们将使用一种常见的算法旅行商问题(TSP。让我们自己来解决这个问题。
销售人员需要前往一定数量的城市或地点。假设销售人员有 10 个地点要去。他们可以按不同的顺序前往这 10 个地点。我们使用此算法的目标是创建尽可能最好、最有效的路线来命中这些位置。
请注意,对于这个特定场景,正如我们将在下一个问题中所做的那样,我们将通过使用计算思维过程的四个元素进行直接分析。
这个问题比它最初的表现要复杂一点。这样想吧。如果我们有 10 个目的地,并且我们正在计算往返排列以检查最快的路线,那么剩下的可能排列和组合就超过 300000 个。作为提醒,排列考虑顺序,而组合不考虑顺序。
例如,数字3344和3434是两种不同的排列。但是,它们仅被计算为一个组合,因为数字的顺序并不重要。
但回到我们的问题上来。我们需要知道的是,我们想要创建一种算法,以最有效的方式将我们带到目的地。我们必须确定要访问的城市和确定我们的旅行方式,如下所示:
- 共有五个城市,分别是纽约****纽约、费城、巴尔的摩、芝加哥、克利夫兰。
- 我们将使用一辆车,因为我们使用的是 TSP 而不是车辆路径问题(VRP)。
- 第一个城市是 0,也就是纽约。纽约市与自身之间的距离为 0。
现在让我们看看这个模式。
对于每个城市,总共有五个距离,距离本身等于 0。我们需要一个数组或列表,用于每个城市的所有距离。为了访问算法中的数据,我们需要创建一个模型。我们将在设计算法时考虑这一点。首先,让我们讨论一下模式的一般化。
对于这个特殊问题,我们将手动将城市输入算法本身。你可能要考虑的一件事是如何从用户那里获得输入,以便创建距离所需的数组。
您还可以为主要城市之间的距离创建一个数据库,您可以从.csv文件中访问该数据库,以便可以在那里找到个人输入的城市数据,然后将其添加到我们的模型中。这个特殊的算法有许多附加项,这不是一个可以用一种方法解决的问题。现在,我们将使用一组已定义的城市,以便创建我们的算法。
另一方面,我们参考了中的源代码 https://developers.google.com/optimization/routing/tsp 针对这个问题。
是时候看看我们在说什么了。让我们从纽约市开始,首先构造这个数组。其他数组也是以相同的方式创建的。所有距离均以英里为单位,并根据谷歌地图数据进行了近似和舍入,如下所示:
- 从纽约到纽约的距离是 0。
- 从纽约到费城的距离是 95。
- 从纽约到巴尔的摩的距离是 192。
- 从纽约到芝加哥的距离是 789。
- 从纽约到克利夫兰的距离是 462。
下表显示了每个城市之间以及城市本身之间的距离:
表 15.1——从一个城市到另一个城市的距离
因此,正如您在上表中所看到的,如果我们将这些距离作为数组写入,我们将使用以下代码:
[0, 95, 192, 789, 462]对于费城,我们将有以下阵列:
[95, 0, 105, 759, 431]对于巴尔的摩,我们将有以下阵列:
[192, 105, 0, 701, 374]对于芝加哥,我们将有以下阵列:
[789, 759, 701, 0, 344]最后,对于克利夫兰,我们将有以下阵列:
[462, 431, 374, 344, 0]请注意,我们将为每个城市提供索引,以便识别它们。纽约是0,费城是1,巴尔的摩是2,芝加哥是3,克利夫兰是4。让我们看看这个问题的算法是什么样子的(注意,或工具库用于优化车辆路线、线性规划、约束规划等):
-
First, let's start by importing the packages and libraries we'll need. The full file for this algorithm is
ch15_travel.pyand available on GitHub:from ortools.constraint_solver import routing_enums_pb2 from ortools.constraint_solver import pywrapcp
请记住,如果您计划访问更多的城市和/或不同的城市,则此算法需要获得新的距离矩阵。这是您需要修改的唯一代码部分。每次需要调整的代码片段是
create_data_model()下的矩阵,如下代码片段所示:#Create data model. def create_data_model(): data = {} data['distance_matrix'] = [ [0, 95, 192, 789, 462], [95, 0, 105, 759, 431], [192, 105, 0, 701, 374], [789, 759, 701, 0, 344], [462, 431, 374, 344, 0], ] data['num_vehicles'] = 1 data['depot'] = 0 return data
-
After we've defined our data model, we'll need to print a solution. The following function provides that information:
#Provide solution as output - print to console def print_solution(manager, routing, solution): print('Objective: {} miles'.format(solution.ObjectiveValue())) index = routing.Start(0) plan_output = 'Route for vehicle 0:\n' route_distance = 0 while not routing.IsEnd(index): plan_output += ' {} ->'.format(manager.IndexToNode(index)) previous_index = index index = solution.Value(routing.NextVar(index)) route_distance += routing.GetArcCostForVehicle(previous_index, index, 0) plan_output += ' {}\n'.format(manager.IndexToNode(index)) print(plan_output) plan_output += 'Route distance: {}miles\n'.format(route_distance)
正如您从前面的代码中看到的,我们正在创建一个函数,以便根据数组和数组中的距离打印解决方案。回想一下,您将确定出发点,即您要离开的城市。然后我们运行算法来收集信息并创建我们的
print语句。 -
Finally, we'll need to define our
main()function in order to run our algorithm. Themain()function tells the algorithm to go ahead and create that data model we had defined, and then store it as data. We then create the routing model to find our solution. Take a look at the following code snippet:def main(): data = create_data_model() manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), data['num_vehicles'], data['depot']) # Create Routing Model. routing = pywrapcp.RoutingModel(manager) def distance_callback(from_index, to_index): """Returns the distance between the two nodes.""" # Convert from routing variable Index to distance matrix NodeIndex. from_node = manager.IndexToNode(from_index) to_node = manager.IndexToNode(to_index) return data['distance_matrix'][from_node][to_node] transit_callback_index = routing.RegisterTransitCallback(distance_callback) routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = ( routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) solution = routing.SolveWithParameters(search_parameters) if solution: print_solution(manager, routing, solution) if __name__ == '__main__': main()
前面的代码显示了如何定义
main()函数。值得注意的是,main()函数可以被命名为我们想要的任何名称。当使用多个函数时,我们有时使用main()来识别将从算法中输出我们最初想要的内容的函数。对于这个问题,我们正在创建一个main()函数,它将确定我们旅行的最佳路线。 -
Now let's take a look at what we get for our output when we run this code. The code provides us with the
Objectivetotal of miles and the route we should take for the trip. Here's the output:Objective: 1707 miles Route for vehicle 0: 0 -> 1 -> 2 -> 4 -> 3 -> 0
正如你所看到的,如果我们按照以下顺序走,我们从纽约到纽约的旅程将是最有效的:纽约|费城|巴尔的摩|克利夫兰|芝加哥|纽约。
这不是解决旅行问题的唯一方法。如果我们想一天运行多次,例如,针对不同的旅行者,这也不一定是最方便用户的方法。要做到这一点,您需要自动化更多的操作,如前面的示例所述。有些事情你可以考虑如下:
- 能够输入城市
- 有一个能获取信息以确定距离的计算器
- 使用自动过程创建距离矩阵
*但现在,您已经看到 TSP 在运行!*我们将在下一节中研究一个新问题。
密码学是我们用来编码和解码信息的。我们在第 9 章中使用了一个简单的凯撒密码,理解输入和输出,设计了一个求解算法。对于这个问题,我们将使用 Python 中可用的一些包来加密和解码信息。
请注意,对于这个特定场景,我们将使用计算思维过程的四个元素进行简单的分析。虽然我们并不总是完全遵循它们,但这个特定的问题本身就有一个相当直接的用途。
您正在处理一个机密项目,需要加密您的信息以确保其安全。
Python 有一个可以安装的加密包,就像我们安装其他库时一样,比如熊猫和NumPy。在我们的问题中,我们需要知道的主要事情之一是我们可能需要继续加密消息。我们可能还想解码我们收到的消息,但我们将首先关注加密方面的事情。
当我们设计我们的算法时,我们将需要一些我们可以在项目的整个生命周期中不费吹灰之力就能继续使用的东西。也就是说,每当我们想要加密一条新消息时,我们都可以运行算法并输入消息,而不是每次都将消息本身添加到算法体中。这是我们特定问题的一般模式。这意味着我们已经准备好进行设计。
为了编写我们的算法,让我们首先看看我们需要做什么:
-
定义字母。
-
将所有字母更改为小写以运行我们的算法。
-
定义所需的功能-
encryption、decoding和main。 -
Call the cryptography
mainfunction.提示:
这个问题的完整算法可以在
ch15_cryptographyA.py文件中找到。
我们将按照以下步骤开始设计我们的算法:
-
让我们从定义字母开始。下面的代码片段定义了我们的字母,然后以小写形式呈现每个字母:
LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' LETTERS = LETTERS.lower()
-
Next, we define our encryption function. This function will take two arguments—
messageandkey. Themessagefunction will be user-defined, which will be done in themainfunction. For now, we'll use an empty message by adding empty quotes ('') as the definition of theencryptedMvariable, as shown in the following code snippet:def encrypt(message, key): encryptedM = '' for letts in message: if letts in LETTERS: num = LETTERS.find(letts) num += key encryptedM += LETTERS[num] return encryptedM
请注意,我们迭代了要加密的消息中的字母,然后使用用户在
main函数中定义的密钥对消息进行加密。然后,此函数返回加密的消息。但如果我们从中获取输入,为什么我们还没有定义
main*函数?*因为main函数需要另外两个函数对消息进行加密或解码。请容忍我们;我们很快就会进入main功能。 -
Now let's take a look at the decoding function. This is what we'll use when we have an encrypted message and want to know what the original message was:
def decode(message, key): decodedM = '' for chars in message: if chars in LETTERS: num = LETTERS.find(chars) num -= key decodedM += LETTERS[num] return decodedM
前面的代码显示了我们将用于解码消息的函数。它使用消息中的字符和加密密钥对消息进行解码。请注意,如果您没有原始密钥,则无法解码消息,除非您有时间坐下来尝试每一个密钥,即。
-
Finally, we'll need that
mainfunction we keep referring to. This is the function that takes all the inputs needed in order for this algorithm to run. Here are the three things necessary for it to run correctly—the message to be encrypted or decoded; the key, which can be any number in the range 1 to 26; and whether we are encrypting or decoding.以下是
main功能:def main(): message = input('What message do you need to encrypt or decrypt? ') key = int(input('Enter the key, numbered 1-26: ')) choice = input('Do you want to encrypt or decode? Type E for encrypt or D for decode: ') if choice.lower().startswith('e'): print(encrypt(message, key)) else: print(decode(message, key)) if __name__ == '__main__': main()
从前面的代码中注意,我们定义了一个
main函数。在代码末尾,我们调用了该函数。别忘了在算法中调用那个main*函数!*这就是运行算法的方式。
下面是我们尝试加密输入消息时的示例输出,the name of the dog is King Kong,使用9密钥加密消息:
What message do you need to encrypt or decrypt? the name of the dog is King Kong
Enter the key, numbered 1-26: 9
Do you want to encrypt or decode? Type E for encrypt or D for decode: E
cqnwjvnxocqnmxprbrwpxwp如您所见,我们获取加密文本,cqnwjvnxocqnmxprbrwpxwp,作为密文,现在我们创建了一种算法,可以对任何消息进行加密或解码。现在让我们进入一个新问题。
对于这个问题,我们决定进行一次相当短的网络安全检查。首先,让我们谈谈网络安全。根据大观研究报告,到 2027 年,网络安全市场预计将增长 10%。
将其转化为就业市场有点棘手。例如,目前在美国,市场对网络安全的需求超过了现有的人员或工作。2018 年至 2028 年间,就业市场的增长率预计将略高于 30%。所以学习一点网络安全和加密技术不会有什么坏处。
对于这个特殊的问题,我们将探讨一些事情。首先,我们来谈谈散列。在网络安全中,散列是指那些真正长的数字和字母字符串,用来代替密码之类的东西。例如,如果您输入了一个密码password1(请不要这样做,永远不要使用password作为密码),哈希过程会将其替换为类似以下内容:
27438d623d9e09d7b0f8083b9178b5bb8ff8bc321fee518af 4466f6aadb68a8f:100133bfdbff492cbc8f5d17af46adab当我们创建密码算法时,我们必须添加随机数据,我们称之为盐。盐只是提供了额外的输入,帮助我们在存储密码时使密码更加安全。
当我们在 Python 中使用哈希时,我们可以使用uuid库。UUID代表通用唯一标识符。当我们想要生成随机的 128 位对象作为 ID 时,可以使用uuid库。但我们到底在谈论什么?让我们来看看在 Type T2A.文件中发现的算法:
-
We will import libraries first:
import uuid import hashlib
我们正在导入两个库,这两个库允许我们使用 salt 和 hash 保存密码。
-
In the next code snippet from the file, we define the function to hash our password:
def hash_pwd(password): salt = uuid.uuid4().hex return hashlib.sha1(salt.encode() + password.encode()).hexdigest() + ':' + salt
我们使用
uuid包对密码进行加密,然后使用安全哈希算法 1sha1返回哈希。这只是我们可以使用的算法之一。我们可以使用其他的,比如SHA-256、SHA-384和等等。sha1散列的输出大小为 160,sha256的输出大小为 256。sha1和sha256的块大小均为 512 位,而sha384的块大小为 1024 位。在选择我们将使用的哈希、它们的安全性等等时,所有这些都变得相关。我们在这里使用
sha1更多的是为了怀旧,但它不像sha256和sha384那样安全。当受到攻击时,sha1将无法抵抗长时间的攻击。另外两个会坚持更长时间,但仍然不是最好的。hash 如shake128和shake256在抵御此类攻击时更为成功。 -
现在我们来看一下
check函数。我们总是希望通过要求输入两次来确认密码。下面的代码片段定义了算法在收到第二个密码时将执行的操作:def check_pwd(hashed_pwd, user_pwd): password, salt = hashed_pwd.split(':') return password == hashlib.sha1(salt.encode() + user_pwd.encode()).hexdigest()
-
Now let's ask for some input. First, we'll ask for the password. Because we're curious about what the program is doing, we'll print the hashed password, but you can omit that line when we are building this into a site or other application. After that, we ask to verify the password and provide output for the user so they know whether they match, in which case, we'd probably want them to try again. For now, this algorithm either confirms it or lets the user know that it is now confirmed:
new_pwd = input('Enter new password: ') hashed_pwd = hash_pwd(new_pwd) print('Hashed password: ' + hashed_pwd) confirm_pwd = input('Confirm password: ') if check_pwd(hashed_pwd, confirm_pwd): print('Confirmed!') else: print('Please try again')
运行程序后,我们得到以下输出:
图 15.3–加密和哈希密码确认的输出
从前面的屏幕截图中可以看到,密码已由系统确认。
-
Now let's see what happens when we enter two different passwords. Let's take a look at the following screenshot for that:
图 15.4–确认失败的盐渍和哈希密码的输出
如您所见,程序要求用户重试。但是,除非进程重新启动,否则该算法不会提供这样做的方法。我们可以让它像那样运行,或者我们可以添加条件,让程序再运行一次、两次或无限次,直到得到确认为止。
-
Now let's take a look at what happens if we run the algorithm using
sha256instead ofsha1. The following screenshot shows the result when a password is confirmed usingsha256:图 15.5–在密码确认算法中将 sha1 替换为 sha256 时的输出
请注意,散列相对于
sha256算法具有更长的长度。当我们使用密码学时,random 和 long 总是很有用的。破解非随机密码(如password或mycat)比破解很长且包含随机数字和字母的密码更容易。这就是为什么我们试图以保护数据免受攻击的方式存储数据。 -
Let's take a look at what we could do to provide one more chance for someone to enter the password. At the end of the algorithm, let's add some code after the last line:
new_pwd = input('Enter new password: ') hashed_pwd = hash_pwd(new_pwd) print('Hashed password: ' + hashed_pwd) confirm_pwd = input('Confirm password: ') if check_pwd(hashed_pwd, confirm_pwd): print('Confirmed!') else: print('Please try again later')
请注意,前面代码段中的最后一条语句声明了
'Please try again later'。这让用户知道,如果他们想保存密码,他们必须再次启动该过程。此时,算法已停止。 -
如果我们将前面的代码放在
else、print()语句之后,那么算法将再次运行一次。以下屏幕截图显示了用户第二次尝试时的输出:
图 15.6–先运行不正确匹配的算法后的输出
在我们继续本例之前,请注意,提供的散列密码是不同的,即使我们输入的新密码在两种情况下都是test。如前所述,每次都会创建哈希密码。否则,每个人都会知道存储的密码是什么,因为只要我们使用相同的散列,那么test将是相同的,在本例中为sha256。
在网络安全和密码学方面还有很多需要探索的地方。这只是我们如何加密信息的一个尝试。
是时候创建一个简单的聊天机器人了。在过去的几年里,你可能至少与十几个聊天机器人进行过互动。当你访问一些网站时,你可能会遇到一个人,他想和你聊天,并问你一些简单的问题,比如你在做什么,他们能帮你做些什么。对于大多数网站来说,人不是人,而是聊天机器人。
在某些情况下,聊天机器人会引导你找到一个真实的人。但大多数时候,他们只是通过在他们的网站上为你指出可用答案的方向来回答你的问题。
我们将在这里创建类似聊天机器人的东西。在开始之前,我们需要一些组件。其中一个是intents文件。该文件应为.json文件,包含机器人将使用和/或响应的问候语和响应。以下是 intents 内容的示例:
{"intents": [
{"tag": "greeting",
"patterns": ["Hi", "How are you", "Hello?", "Welcome!", "Hello"],
"responses": ["Hello! Thank you for visiting our site! ", "Welcome back!", "Hello, how can I help you?", "What can I do for you? "],
"context_set": ""
},
{"tag": "goodbye",
"patterns": ["Bye", "See you later", "Goodbye"],
"responses": ["See you later, thanks for visiting", "Thank you and have a wonderful day!", "Bye! See you soon!"]
}
]
} 正如你所看到的,这只是一组可能的反应。我们向.json文件提供的数据越多,我们的机器人就越健壮和准确。
我们应该注意,intents.json文件需要在JSON 编辑器中编辑。您可以在使用在线编辑器 https://jsoneditoronline.org 您可以在其中创建自己的或编辑现有文件。
*我们为什么需要机器人?*类似的东西有多种用途,例如,从创建和向社交媒体发布消息,到向客户提供机器人,询问他们在访问网页时是否需要帮助。这些只是聊天机器人可以做的一些事情。
现在让我们来看看一个创建聊天机器人的算法。可以在存储库中找到完整文件。我们已经在代码片段的某些部分中对所发生的事情进行了评论和描述:
-
Let's start by importing the libraries here:
ch15_chatBot.py
import nltk import json import pickle import numpy as np nltk.download('punkt') nltk.download('wordnet') from nltk.stem import WordNetLemmatizer lemmatizer = WordNetLemmatizer()
从前面的代码中,请注意,您不必每次都下载
nltk模块。然而,拥有这段代码不会有什么坏处。系统不会每次安装多个副本;它只会识别它们在那里,而不会再次安装它们。 -
让我们继续从我们的库和包中获取我们需要的:
from keras.models import Sequential from keras.optimizers import SGD from keras.layers import Activation, Dense, Dropout import random
-
Now that we have what we need, we have to look at our
.jsonfile. That file contains those intents, as mentioned earlier in this problem. We'll need to not only open that file, but also divide the components and sort them in ways that our algorithm can understand. Take a look at the following code snippet:#Upload intents file and create our lists words=[] classes = [] doc = [] ignore_words = ['?', '!', ',', '.'] data_words = open(r'C:\...\intents.json').read() intents = json.loads(data_words)
请记住,除非您为要访问的文件指定了正确的位置(在本例中为
.json文件),否则程序不会运行。还要注意,这一次我们以稍微不同的方式打开它,如前面的代码片段所示。像这样打开文件,这与我们用 Pandas 打开.csv文件时不同,意味着我们不需要在路径中使用双\\。 -
Now let's tell the algorithm what to do with that file:
for intent in intents['intents']: for pattern in intent['patterns']: #Tokenize all the words (separate them) w = nltk.word_tokenize(pattern) words.extend(w) #Add all the words into doc doc.append((w, intent['tag'])) #Add the classes if intent['tag'] not in classes: classes.append(intent['tag']) print(doc)
在这里,我们正在标记我们的信息,也就是说,我们将所有内容分解成单词,然后将它们添加到列表中。这就是使信息处理成为可能的原因。在我们把他们分开之后,我们将根据这些词的意思把他们分组。
-
The words are then sorted, as can be seen in the following code snippet:
#lemmatization words = [lemmatizer.lemmatize(w.lower()) for w in words if w not in ignore_words] words = sorted(list(set(words))) classes = sorted(list(set(classes))) pickle.dump(words,open('words.pkl','wb')) pickle.dump(classes,open('classes.pkl','wb'))
请注意,在前面的代码中,我们使用了
pickle()。Pickle 是 Python 中的一种方法,我们可以使用它来序列化(或反序列化)数据。然后使用该方法替换当前文件数据,以便将其用作转换文件。 -
Now that we have done all of that, we need to create our training model. We won't go through all the sections of that process here, but the entire code can be found in the GitHub repository file. Remember that you first train, then create, and then compile the model.
一旦我们运行了这个过程,我们就会保存模型以便使用它。现在让我们看看聊天机器人的功能:
#Define chatbot functions def clean_up_sentence(sentence): sentence_words = nltk.word_tokenize(sentence) sentence_words = [lemmatizer.lemmatize(word.lower()) for word in sentence_words] return sentence_words def bow(sentence, words, show_details=True): sentence_words = clean_up_sentence(sentence) bag = [0]*len(words) for s in sentence_words: for i,w in enumerate(words): if w == s: bag[i] = 1 return(np.array(bag)) def predict_class(sentence, model): p = bow(sentence, words,show_details=False) res = model.predict(np.array([p]))[0] ERROR_THRESHOLD = 0.25 results = [[i,r] for i,r in enumerate(res) if r>ERROR_THRESHOLD] results.sort(key=lambda x: x[1], reverse=True) return_list = [] for r in results: return_list.append({"intent": classes[r[0]], "probability": str(r[1])}) return return_list
前三个函数用于为聊天机器人创建响应并进行预测。这些课程将关系到我们如何从机器人那里得到这些响应。让我们这样想想,如果我打招呼,我就不想让聊天机器人说再见了。那太粗鲁了。但请记住,我们的机器人只和我们的训练一样好。因此,如果我们在
.json文件中没有足够的内容,并且没有正确地训练模型,那么机器人将相当无用。 -
Now let's define how we get the responses:
def getResponse(ints, intents_json): tag = ints[0]['intent'] list_of_intents = intents_json['intents'] for i in list_of_intents: if(i['tag']== tag): result = random.choice(i['responses']) break return result def chatbot_response(msg): ints = predict_class(msg, model) res = getResponse(ints, intents) return res
正如您从前面的代码片段中看到的,bot 将创建一个响应并返回它。我们将在文件中接下来的几段代码中调用这些东西。
-
But we'll skip that here and go into the look of our chatbot:
base = Tk() base.title("Chat with Customer Service") base.geometry("400x500") base.resizable(width=FALSE, height=FALSE)
注意,我们在前面的代码片段中建立了一些关键信息。我们确定了窗口的大小并阻止其调整大小。
-
In the next code snippet, we'll establish the background, add
scrollbar, and establish the look of the Send button:#Create chatbot window ChatLog = Text(base, bd=6, bg="white", height="8", width="70", font="Calibri") ChatLog.config(state=DISABLED) #Scrollbar scrollbar = Scrollbar(base, command=ChatLog.yview, cursor="arrow") #Create Send button SendButton = Button(base, font=("Calibri",12,'bold'), text="Send", width="15", height=5, bd=0, bg="pink", activebackground="light green",fg='black', command= send ) EntryBox = Text(base, bd=0, bg="white",width="29", height="5", font="Arial") scrollbar.place(x=376,y=6, height=386) ChatLog.place(x=6,y=6, height=386, width=370) EntryBox.place(x=128, y=401, height=90, width=265) SendButton.place(x=6, y=401, height=90) base.mainloop()
简而言之,就是这样。*我们创建了一个聊天机器人!**但它运行时是什么样子?*看看的输出:
图 15.7–聊天机器人窗口
注意我们的组件,左边的滚动条,粉红色的发送按钮,以及聊天机器人的标题。还要注意的是,“最大化”按钮是灰色的。那是因为我们说过我们不想调整窗口的大小。
-
另外,当我们点击发送按钮时,我们想让用户知道它是否被点击。否则,如果您不确定,可以多次单击它。这就是在代码中更改活动背景颜色的原因。以下屏幕截图显示了按钮处于活动状态时的外观:
图 15.8–活动发送按钮
许多聊天机器人都有类似这样的功能,因此我们可以避免代码错误。
一旦我们打招呼,机器人就会响应。在我们离开这个问题之前,让我们来看看下面截图中的聊天机器人的快速对话:
图 15.9–带有响应的聊天机器人窗口
正如您所见,几行代码和一个文件可以用来创建与聊天机器人的交互体验。
您可以随意使用这些代码来添加一些天赋,创建一个不同的intents.json文件,并使其更符合您的需要。
在本章中,我们在研究实际问题的同时,有机会在一些非常不同的应用程序中探索 Python。
在前几章中,我们学习了计算思维过程,以及分解、模式识别、模式泛化和算法设计等元素,这些元素使算法变得有意义。当我们处理来自客户的问题时,或者只是作为一种爱好创建脚本时,我们必须经历必要的过程来定义我们用算法创建的内容。这一关键过程将确保我们正在设计尽可能最好的算法。
在本章中,我们学习了如何读取文件、上传文件、创建密码和解码器、使用算法编写用户输入的故事,以及在给定我们将访问的城市时制定最有效的旅行计划。此外,我们还创建了一个基本的聊天机器人,它可以根据用户输入进行交互和调整。
在下一章中,我们将继续探索 Python 和计算思维,以及在科学应用、住房、股票市场分析等数据分析中的其他应用问题。











