TowardsDataScience-博客中文翻译-2021-五十四-
TowardsDataScience 博客中文翻译 2021(五十四)
我教了一台机器如何玩 Connect 4
我是如何构建一个可以在 Connect 4 中击败你的 MATLAB 程序

简介:
棋盘游戏提供了大量令人着迷的原始数据,对于对编程感兴趣的人来说非常有用。Connect 4 有 6 行 7 列,在一场比赛中包含了大量不同的游戏场景和决策。这让我有兴趣为这个经典的棋盘游戏设计一个战略算法。
工作原理:
在过去,我使用神经网络来完成所有的学习(就像我的文章迪士尼乐园算法)。这种棋盘游戏的性质使得神经网络更难选择,只是因为它需要学习大量不同的场景。这就是为什么我离开了神经网络,而更倾向于一个基于评分的系统。
用户迈出第一步,然后程序开始对接下来的 7 步棋打分,找出最好的一步。


接下来的 7 个动作都基于 3 个标准评分:
- 进攻优势:这一步在水平、垂直和对角线方向对计算机有什么好处?如果该移动将使计算机连续三次移动,则该移动在该方向获得 3 分。
- 防守的好处:和进攻一样,但是权重更大,所以电脑对游戏采取了更保守的方法。
- 关键移动:阻止用户连续获得 4 个的移动,使计算机赢得游戏的移动,避免设置用户获胜的移动。所有这些都在“关键行动”部分考虑到了。
以下是计算所有 7 个动作在垂直方向上的进攻优势的一些代码:
scores = zeros(1,7); % array for all 7 offensive scoresopp_scores = zeros(1,7); % array for all 7 defensive scoresfor j = 1:7 % repeats for all 7spotshori = 0; % sets all scoring to 0vert = 0;diag1 = 0;diag2 = 0;if row_choices(j) ~= 0 % if the row is available for play (not filled up)if row_choices(j) ~= 6 % if the choice is not in the bottom rowfor i = row_choices(i) + 1:6 % looks at all the spaces below potential moveif layout(i,j) == 2 % if computer already has moves belowvert = vert + 1; % calculates how many in a row this move would achieveelsebreakendendvert = vert + 1; % adds 1 vertical point regardless (one move is still a tower of 1 in a row)elsevert = vert + 1; % adds 1 vertical point regardless (one move is still a tower of 1 in a row)end
防守效益占得分的 70%,进攻效益占剩下的 30%。在对之前的分数进行合计后,根据特殊情况增加关键移动点。该程序返回所有 7 个可能的移动的分数,然后选择最高分的一个。
例如,下面显示的是用户和程序在一轮移动后的输出。第一行是 7 个动作中每个动作的得分,在最高得分点(或最高得分点之一)标上“2”。从左边数第三列是 1.90 分,所以它是被选择的移动。“1”是用户的移动,“2”是程序的移动。
0.9000 1.2000 1.9000 1.9000 1.9000 1.2000 0.9000 0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 2 1 0 0 0
这个过程一直持续到游戏结束。根据下面代码的结果,游戏结束,该代码检查可能存在顺子 4 的每个可能位置,并在满足该条件时中断游戏循环。
iar = 0; % variable for same colors in a rowdone = 0; % break for "for" loopall_done = 0; % break for larger "for" loopfor i = 1:6 % for all rowsfor j = 1:7 % for all columnsif layout(i,j) == 2 % if there is a "2" in that spotiar = iar + 1; % one more in a rowif iar == 4 % if 4 in a rowall_done = 1; % ends larger "for" loopdone = 1; % ends smaller "for" loopbreak % ends the search for horizontal 4 in a rowendelseiar = 0; % if not 4 in a row, sets back to 0endendiar = 0; % sets back to 0if done == 1 % breaks a "for" loopbreakendendif all_done == 1 % ends gamedisp("Loss") % displays that you lostcomp_win = 1; % indicates the computer wonbreak % ends game loopend
一场已结束的游戏在下面显示为赢(四个 1 在对角线上),或输(四个 2 在对角线上)。
Win
0 0 0 0 2 2 2
0 0 0 1 1 1 2
1 0 2 2 1 2 1
2 0 1 2 2 1 2
1 2 2 1 2 1 1
1 1 2 1 1 1 2Loss
0 0 0 0 0 0 0
0 0 0 0 2 1 1
0 0 2 0 2 1 2
0 0 1 2 1 2 1
0 0 1 2 2 2 1
0 0 2 1 1 2 1
结论:
我在一台在线 Connect 4 计算机上测试了这个 Connect 4 算法,看看它有多有效。10 场比赛后,我的 Connect 4 程序已经累积了 3 胜 3 平 4 负。虽然它不能 100%地赢得与其他计算机的比赛,但它为普通的 Connect 4 玩家提供了一个有价值的对手。在为未来的成功做准备时,它被证明是一个不错的决策者,并且在开发棋盘游戏算法时是一个很好的挑战。
我给 GPT 2 号教授哲学,它感觉我在和一个疯狂但聪明的人说话
我只是想有一个体面的谈话,但 GPT 有其他计划
到现在为止,你们每个人可能都已经看到了强大的 GPT 三号模型 OpenAI 产生的推文、应用程序、代码和文本。1750 亿参数模型是在互联网规模的大型数据语料库上训练的。它已经以其真实的结果让科技和非科技领域的人们感到惊讶,人们对未来几年的可能性垂涎三尺。

GPT 模型所做的是估计句子在现实世界中形成的可能性,例如我在地上玩了一个足球是一个字符序列 比我在地上玩了一个苹果更有可能是** 。单词或短语从文本中随机删除,模型必须学会仅使用周围的单词作为上下文来填充它们。这项训练任务已经产生了一个强大的和可推广的模型。
我仍然在 GPT-3 的等待名单中,所以我决定使用 GPT-2,并享受其 2.55 亿参数模型的乐趣。
我对乐趣的想法是向机器教授哲学课本,然后寻求人类生活和存在中最深奥问题的答案!
我告诉你,有些人真的需要生活,需要理解快乐的定义。
GPT-3 是巨大的,GPT-2 也不小,所以关于模型的实时使用有两个因素要考虑:
1.程序员有多容易接触到它们?
2.他们训练起来有多容易,在生产模式下部署时表现如何?
第二个因素是我非常固执于在本地机器上训练模型而不使用 Colab 或 Paperspace 的想法。我的机器是不起眼的 Macpro-16”没有 GPU 支持(感谢 Nvidia 的 CUDA!不是!!)
我先从少量文本开始,用亚里士多德和柏拉图的著作训练模型。在这个模型学习了柏拉图的《理想国》之后,我开始对我提出的问题获得一致的结果。微调模型非常简单,但有点耗时。

我们训练中的私人哲学家(图片由作者提供)
我创建了一个具有不错的用户界面/UX 的 streamlit 应用程序,这样我就可以在 GCP、EC2 或 Heroku 上部署它,但鉴于模型的大小,错误代码的闸门被打开了,我花了一整天来处理这些问题,但没有成功。后来,我决定专注于训练模型,而不是将这个人工智能哲学家用于公共用途。( Github 链接为回购 )

人工智能哲学家对什么是生活的回答(图片由作者提供)
我相信在阅读了尼采和伏尔泰的作品后,模型结果变得非常怪异。同样的单词和句子会被重复,一些句子会以粗体印刷。
是 GPT-2 在吼我吗?

这算什么回答?(图片由作者提供)
很快就变暗了!全世界的狗,快跑吧!嗯,这不是一个好问题,因为我们已经知道是猫去天堂,而不是狗。好,我再问一个问题。

好吧,鸟不傻(图片作者)
这时,我决定使用参数' max-length '来限制这些胡言乱语答案的长度,但没有效果。我对模型进行了更多步骤的训练,以观察它是否会给系统带来任何健全性。

哇!这个是哲学(作者图片)
一个人和一台机器之间的哲学讨论!!!
好吧,再来一个关于爱情的谚语。

多么不顾别人的混蛋!(图片由作者提供)
好吧,是的,爱不是一个简单的概念,但它是一个迷人的实验。这些句子有几次是有意义的,但在其他时候,它们是垃圾。通常情况下,这就像是在和一个有严重心理问题的人交谈。
我已经在 GPT 3 号上看到了结果,它们看起来合理而真实。我得到的答案句子在语法上是正确的,但感觉就像一个精神错乱的人在声嘶力竭地回答它们。我已经可以看到一个动画戏剧演员/女演员声嘶力竭地大喊他们,调整音调和速度,描绘一个疯狂的表演。
也许训练过的模型产生的一切只是它从阅读的书籍中学到的一系列字符。它不是人工一般智能,机器没有编造新的思想,它只是从大型语料库中读取,计算句子的概率,在没有任何新句子的情况下,它继续重复旧句子。
另一个想法,我对增加字体大小有点毛骨悚然。当然,这台机器没有获得任何 AGI 奖,但它可能是迈向它的第一步。在许多句子中有许多重复,这让我想起了哥德尔、埃舍尔、巴赫,以及递归和重复是如何在无生命的事物如何产生我们周围的有生命的世界的中心。
结论
我知道这只是一个在小型计算机上训练的小模型,但是如果在大型机器上训练,结果将意味着什么?在不久的将来,这些 GPT 模型的规模会越来越大,答案也会越来越清晰。
这种哲学模型的内部量子状态是不确定的,除非我们观察它们(感谢量子力学的深奥陈述)。那么,这是否意味着产生的这些陈述都没有任何实际意义呢?
虽然,检查模型的内部状态可能会提供一些关于机器在获得大量数据时如何思考的见解。它可以在思想上模仿人类,但同时给人的感觉是一个混蛋。
PS: 还有,我的 Github 包含了这个作品 ,我完全无法上传 PyTorch 的模型。想改进模型的可以联系我,我可以在 Dropbox 或者 GDrive 上分享训练好的模型。
在说再见之前!我又问了一个问题。

作者图片
哇!真是个反复无常、冷漠无情的混蛋!
我花了 3 个月的时间学习了 12 门数据科学课程——下面是我希望早点知道的事情
成功参加数据科学课程的利弊。

安东尼·里埃拉在 Unsplash 上的照片
听听上过不同数据科学专业课程的人是怎么说的:要想精通数据,你需要熟悉一些你能找到的最好的数据科学教育资源。
数据科学不仅仅是一个随机的基于计算机的职业。我读了无数的文章,听了播客,看了一些视频,这些视频讲述了如何用基本的计算机知识来掌握数据科学。荒谬。
IT 或计算机科学学位是有用的,但如果你想深入研究数据科学,你需要的不仅仅是学位。你需要经验、人脉、实践和有真实用例的项目。
根据研究,要想从技术职业中获得最大收益,你需要超越你的学位。幸运的是,超越你学位的最好方法是选修一些与该领域相关的课程。
有大量的数据科学课程,这不是问题。问题是在上课前要做好准备。
任何课程的有效性取决于你充分利用它的能力。本文将为您提供在学习数据科学课程之前所需的洞察力,以及如何最大化其有效性。
1。对无限的可能性保持开放的心态。
数据已经进化到了一切都可以实现的地步。
一个一切皆有可能的世界。
大约二十年前,分析当地企业的交易往往是一个漫长的过程。开始时,收集和组织过程既具有挑战性又浪费时间,然后使用传统的检查和资产负债表进行主要分析,并通过幻灯片演示呈现给管理层。
今年早些时候,我在一家快餐店担任分析师,这是一项典型的日常交易分析,我必须说,当时使用的数据分析方法和技术与现在的分析相比有很大不同,也不那么复杂。
我想说的是,学习数据科学需要你思想开放。在参加数据科学课程时,准备好吸收您阅读、观看或收听的任何内容。
如何应用:
我们大多数人已经有了一些数据科学概念的先验知识,这很好,但是,一门课程可能会提供一种不同的方法或更好的方法来实现您先前所知道的内容。
你需要理解新概念,并用你以前获得的知识来平衡它们。
您会遇到不同的数据专业人员,他们可能有不同的教学方法,不要比较哪种方法更好,而是要了解他们两者。现在你有两种方法来解决一个问题。
可教。
2。你仍然需要标准的教育。
我不想让你觉得比少,但是 88%的数据科学家拥有硕士学位,46%拥有博士学位,涉及的领域从统计、数学到计算机科学和物理科学。这些统计数据证明,你需要有很强的教育背景,以发展理解数据科学和大数据分析中使用的术语、概念和模型所需的知识。
在我成为数据科学家的早期,我申请了许多需要数据分析师的初创公司和企业。我的简历上装饰着项目和打磨过的文件夹,但是我缺少一样东西。大学学位。在我申请的众多组织中,我没有一个被录取,尽管有些组织很友好地给了我回复。
“你很聪明,但你需要完成大学学业”
“您忘记在简历中添加您的大学信息”
“你经验不足”
从某种意义上说,他们都开始像我爸爸,但事实是,他们是对的。我需要教育。你在学位期间积累的技能将提高你的熟练程度,并帮助你过渡到任何数据分析领域。
如何应用:
很简单,如果你还没有大学文凭,那就去拿一个。如果你已经有了第一学位,考虑去读硕士或博士。不断学习,让自己从其他数据科学家中脱颖而出。
我不喜欢把“获得学位”看作是一种必须,但是如果你有一个数学、统计学或计算机科学的学位,它会有很大的帮助。
3。熟悉 Python 进行机器学习和分析。
你可能会问,我真的需要数据科学的编程吗?
好吧,根据数据科学学位的这篇研究文章,数据科学行业对编程的需求怎么强调都不为过。随着越来越多的雇主看到该行业对编程的需求,对拥有高级编程技能的数据科学家的需求激增。
除了 Java 和 R,Python 是科技行业中最常用的编程语言。这并不奇怪,因为使用 python,您可以有效而轻松地执行数据分析中涉及的大多数过程,并且显然是开发和维护端到端项目的首选。这就是为什么接受奥赖利采访的 40%的受访者将 Python 作为他们的主要编程语言。
我遇到的大多数数据科学课程都假设他们的学生已经掌握了 Python 的知识。
在某些时候,可能会有某些模块的描述,告诉观众在继续学习课程之前先学习 Python。这只是告诉数据科学家、专业人士或业余爱好者,python 将简化您在整个数据科学职业生涯中的旅程。
如何应用:
python 入门。功能和效果太令人兴奋了,不容错过。
您可能想考虑安装 Anaconda 发行版,因为它提高了代码的质量,并简化了 Windows 和 OSX 上的 python 包安装。
如果你正在寻找一门入门课程, Datacamp 为初学者提供广泛的 python 培训。
4。加强你的沟通游戏。
这里有一个有趣的事实:所有的课程都是由说常规语言的人来教授和解释的。
尽管听起来很可笑,但这是事实。最好的课程对每个模块中包含的概念给出了深入的解释。这些课程大多技术性很强——少说话,多练习。他们也可能变得理论化——说得多,做得少。无论课程采取哪种形式,你都需要做好一切准备。
我的数据可视化导师让我看到了数据行业中一些非常微妙的东西。他说,
“最好的数据科学家是那些能够完美平衡人类交流和技术交流的人”
他说的那几句话让我今天一直保持坚强。在沟通的两个方面创造完美的和谐对你的成功至关重要。不幸的是,大多数数据科学家经常忽视高质量沟通技巧的力量。他们认为,一旦他们能够部署模型、编码、排序变量、编写 python 脚本和操纵数据,他们就已经安顿下来了。
我在数据科学领域定居的想法是,当我能够整理并平衡我的编码技能和我的沟通技能时。
如何应用:
阅读与数据相关的好书或研究文章。从作者的角度获得洞察力,并认识到作者试图传递的要点。
如果你不喜欢阅读,播客或多扬声器互动视频会对你有所帮助。主要目标是详细了解这些想法,并用自己的话有效地总结它们。
5。网络将消除业余爱好者的错误。
网络基本上意味着与同行业中其他有共同兴趣的专业人士建立联系。根据格雷国际集团的说法,你在任何行业的成功,很大程度上取决于你多年来建立的人脉和经验。
我今年参加了我的第一次数据科学课程,老实说,这种体验并不像我预期的那样顺利和进步。三个星期后,我开始了第二次——没有积极的事情,和第一次一样糟糕。
“需要帮助没有一个样子,但是请求总是看起来很美。”
——布列塔尼堡。
在花费了时间和精力却毫无结果之后,我决定与我目前工作的一家机构的同事分享我的挑战。我们聚在一起,看着我的课程模块,分享理解课程不同方面的独特方法。
这听起来像是现在的工作,但是一旦你付出了一次性的牺牲,你将会在整个职业生涯中受益。
如何应用:
每次会议、峰会、工作或非正式聚会都是建立新关系的机会。
不要让它溜走。
除了物理连接,建立一个积极的在线存在。加入最好的数据科学社区和论坛。
适用外卖
我喜欢数据科学的美妙之处在于,它总是有改进的空间。你可能会在编码或分析中犯一些错误,这肯定会造成伤害。但是有大量的优质资源你可以温习并提高你的技能。
你所需要的是充分的准备和学习的心态,坚持不懈,你就能达到职业的最高水平。
数据科学让我的收入翻了三倍。以下是方法。
一年多前,我被新冠肺炎·疫情抢走了工作。结果是塞翁失马,焉知非福。

仅仅一年多过去了,我边学习边做兼职家教。
在此期间,我的收入仅略高于最低工资,仅够支付食品和汽油等费用。
我在疫情期间失去了工作,并被告知只有全国封锁解除后,我才能回去教书。
这件事发生后,我突然意识到我有很多空闲时间。我再也不用去大学上课了。我也不再有工作了。
我借此机会自学了数据科学。
在短短一年多的时间里,我用我的数据科学知识创造了多种收入来源,使我的工资增加了两倍。
方法如下:
1.数据科学工作
我设法在找到了一份数据科学实习,然后它变成了一份全职工作。
从事全职数据科学工作有两个优势:
尽管这并不是我收入的主要来源,但它是稳定的,这是我喜欢的。
我每天都学到新东西。在与不同团队的人交谈时,我努力提高我的沟通技巧,建立机器学习模型并将其扩展到大型数据集,并提出不同的技术来改善客户旅程。
这份工作最棒的地方在于,不像自由职业者,我不能选择我工作的项目。
这意味着,即使我不知道如何做某事,我也别无选择,只能在一两天内学会或完成它。
任务几乎总能完成,我离开时学到了新的东西。
此外,作为一个内向的人,我过去发现很难与人交流,也很难在团队会议上发言。
一旦担任数据科学家的角色,我真的没有选择,因为我的很多工作都涉及到展示模型洞察力和从客户那里收集业务需求。
因此,在过去的一年里,我的沟通技巧有了显著的提高。
2.数据科学博客
在我的空闲时间(通常是晚上或周末),我为数据科学写作。
我开始围绕我建立的项目写文章,以加强我的投资组合。
我喜欢创作,我的文章是与他人分享我的旅程的一种方式。当时我不认识其他人一起学习,我认识的人中也没有人和我对数据科学有同样的兴趣。
我写作是为了记录我的进步,并与和我有相同目标的志趣相投的人建立联系。
随着时间的推移,我意识到我的文章为与我同路的人增加了价值。
随着我写得越来越多,最初作为爱好开始的东西开始产生收入。
我能够通过简单地写下我的经历并把它们发布到网上来获得被动收入。
我现在是媒体上的技术和人工智能的顶级作家,这是我无法想象的。
3.联盟营销
一旦我开始自学数据科学,我就会在 Medium 和 LinkedIn 上分享我所学课程的链接。
然而,直到最近我才发现联盟营销。
有了联盟营销,你可以和其他人分享你喜欢的课程。如果其他人点击了你分享的课程链接,他们的一小部分课程费用将归你所有。
我还没有从联盟营销中赚很多,主要是因为我对我推广的课程非常挑剔。
我参加了几乎所有我推广的课程,并做了大量的研究来编写其余的课程。
所以这是一个非常小的收入流,但是我仍然把它加到这个列表中。
4.当自由职业者
去年,我在寻找网上赚钱的方法。
我调查了像 Fiverr 和 Upwork 这样的网站,看看我是否能从事任何工作,因为我真的需要一个新的收入来源。
然而,我觉得这些平台竞争过度,我真的不适合这些网站的任何类别。
在学习数据科学之后,我意识到自由职业的数据科学家有一个相当大的市场。
有许多公司不需要整个数据科学团队,而是通过合同雇佣人员来为他们构建和部署模型。
我目前正在为一个客户进行一次性的机器学习项目,一路上我学到了很多东西。
我也从出版商和技术网站获得为他们撰写数据科学文章的自由职业邀请。
我的所有客户都在阅读了我的文章或我在 LinkedIn 上的帖子后联系了我,这就是为什么在 Medium 上写数据科学文章并建立社交媒体粉丝是一个好主意。
我最近还开始为试图学习数据科学或进入该行业的人提供咨询会议,这是我的另一个收入来源。再说一遍,我的大多数客户都来自我的 LinkedIn 个人资料和媒体文章。
结论
因为新冠肺炎·疫情而丢掉我的家教工作是发生在我身上最好的事情之一。
我意识到我被困了很长一段时间,不知道未来该做什么,失业给了我一些自由时间来反思、学习和决定我真正想做什么。
当然,我很幸运,有机会在这么长时间没有工作的情况下生存下来,并实际学到了成为数据科学家所必需的技能。
提高技能和学习如何编码是我做过的最好的决定之一。
本文到此为止,感谢阅读!
对知识的投资回报最高——本杰明·富兰克林
四年内做软件工程师、机器学习工程师、数据科学家,我学到了什么。
从角色转换和科技职业导航中学到的经验

图片来源:MSIA 西北部。我在教室里用我的笔记本电脑做一些事情。
嗨,我是杰森。
我是一名会计出身的数据科学家,一名数据科学家出身的软件工程师。
介绍
你好。4 年前,我得到了第一份数据科学家(DS)的工作,我在 TDS 上讲述了那次经历。两年前,我离开了数据科学家的职位,成为了一名软件工程师(SWE)。上个月,我辞去了在 GoDaddy 担任机器学习工程师(MLE)的工作。善变吗?这是一个关于我如何转入软件工程并离开数据科学的故事。这也是我在短短 4 年内拥有三个不同头衔(DS、SWE、MLE)后获得的观点。以下是我的头衔、团队、技术团队和简短的角色描述,供您参考:
- 数据科学家(市场营销、数据科学):Python、Tensorflow、PySpark、SQL →建立大量的 ML 模型,用于预测客户行为、构建特征和进行分析。
- 软件工程师(数据平台):Java、Beam、Spark →为其他团队构建和生产核心数据产品。
- 机器学习工程师 (GoDaddy 机器学习):Python,AWS →在 AWS 中搭建 ML 平台,将 ML 模型部署到生产中。
当我进入一家新公司时,我想回顾一下我的经历,并谈谈这篇文章中的要点。我喜欢和不喜欢这些角色的什么?我后悔这些开关吗?如果你曾经想换工作,我希望这些课程能帮助你做出决定。
我在数据科学中不喜欢的是
作为数据科学家的最初几年是一段狂野的旅程。我学会了如何构建和部署模型、自动化管道、分析结果等等。虽然作为一名数据科学家,我喜欢做很多事情,但也有一些事情让我感到困扰。
首先是杂乱、无组织、无文档记录的代码/查询。人们通常不太关心你的代码,主要是因为这些代码通常不会投入生产。也是因为很多 DS 项目都是单人项目。当只有您和经理时,您可能不太关心变量的格式和硬编码。只要结果是好的,我们就可以继续前进,因为你不会重新访问相同的代码。
其次是将机器学习模型部署到面向客户的生产中的困难和无力。这在很大程度上取决于你公司的内部工具。像脸书和谷歌这样的公司拥有成熟的 ML 部署工具,科学家可以用它们来部署模型。在许多其他公司,数据科学家可能需要自己解决,或者依靠其他软件工程师来生产。作为一名数据科学家,我觉得自己拥有整个管道和部署自己的机器学习模型的能力有限。在大公司中创建一个面向客户的 ML 模型并不是一件容易的事情。您需要确保模型具有低延迟,符合公司编码标准,经过审查和测试,并有随叫随到的员工来处理故障。
我喜欢软件工程的什么
首先,你去制造一个产品。当你做了一件东西,看到其他人使用它并从中受益,这是一种很棒的感觉。它可以是数据产品、web 应用程序或 API。Lyft、Shopify 和 Instagram 等公司都是软件产品的例子。一个好的产品会持续很长时间,并在世界上产生很大的影响。虽然分析是有用的,可以引导公司的方向,但它只在特定的情况下使用一次。当你真正建造一个东西时,这是一种非常不同的感觉。
第二,自动化让你的生活更轻松。尽管自动化并不是软件工程的专利,但它在数据科学中并不常见。例如,像 Jenkins 这样的工具可以帮助你从 Github 自动构建、单元测试,部署你的代码( CICD )。使用不同的工具,您可以定制如何重新格式化和测试代码。事实上,只要有意义,任何事情都可以自动化。例如,每当您对代码进行更改时,您可以让 Github 运行预定义的测试,检查未使用的导入和错误的格式,并在结束时自动发送失败或错误警报。与数据科学中的一次性分析不同,在软件工程中,您会多次重新访问相同的代码。因此,对每一个变化进行相同的测试和检查是至关重要的。我喜欢 SWE 的这个方面,因为我不喜欢重复同样的任务,喜欢事情有效率。
第三,通过版本控制的协作(如 Github) 让你和别人成为更好的程序员。作为一名数据科学家,我使用 Github 主要是作为一个存储和保存我的工作的地方。这很好,因为没有人真正检查我的代码。但是在软件工程项目中,多个工程师在同一个代码库上工作,你必须有一个清晰的版本控制系统。看到代码库在每个人的贡献下成长和发展是很有趣的。我还发现通过拉动请求提供和接收反馈是非常有建设性的。审查别人的代码和了解自己的代码一样重要。Git 允许您查看代码是如何随时间变化的,如果在最近的变化中有问题,可以恢复到以前的版本。
我不喜欢软件工程的地方(作为一名数据科学家)
第一,随叫随到烂,通常。如果你是一名数据科学家,很少会随叫随到。随叫随到类似于医生随叫随到,以及在紧急情况下他们需要如何待命。例如,Github 在上周感恩节期间宕机了。我的第一个想法是,“撕掉随叫随到的人”,因为他们必须尽快解决这个问题。我曾经遇到过每隔一周待命的情况,也曾遇到过每两个月待命一周的情况。所以这真的取决于你团队的文化和产品。不管怎样,你需要根据随叫随到的时间表来安排你的生活,你可能会在凌晨 12 点或 5 点接到电话。这是不可避免的,因为构建和维护软件产品就像在汽车行驶时修理汽车一样。你能想象一个网站只从早上 9 点到下午 5 点在线吗?这对于不同时区的人来说会很烦人。因为大多数软件工程产品需要全天候可用,所以随叫随到是必要的。
第二,如果你来自数据科学,一个僵化的 scrum 框架 会令人窒息。虽然有些过于简化,但 scrum 是敏捷开发过程的一部分,该过程使用两周的时间间隔来计划、跟踪和组织工作,其中工作被分成详细的项目。你为每两周设定目标,并确保完成所有任务。在每个 sprint 结束时,你会召开一个回顾会议,回顾 sprint 的进展并继续下一个 sprint。这种类型的计划需要非常严格的结构、详细的监控和频繁的会议。这可以被视为一种令人敬畏的组织方法或极端的微观管理。
作为一名数据科学家,我不习惯这样,因为数据科学的问题往往很模糊。你可能会遇到一个开放式的问题,比如“我们如何增加产品 X 中的度量 Y?”你也可能有几周或几个月的时间通过一些签到来解决这个问题。不仅如此,给定的问题可以用不同的方法解决,如详细分析、ML 建模或实验。你也可能找不到答案,然后转向一个新的问题。很多不确定性。所以像软件工程中那样提前几周计划好工作是很困难的。
第三,细节决定一切。作为一名数据科学家,您可能习惯于使用已经设置好并随时可用的工具和环境。但是当你自己构建一个产品时,你需要自己设置和维护这些环境。这可能是旋转集群,设置服务器,确保您的代码符合公司的安全标准,等等。不仅如此,你还需要关注一些小细节,比如格式化(比如在每个文件的末尾添加新的一行),或者选择正确的变量名(比如 describe_var ? var_describe ? VAR ? var ?).我在这里夸大其词,并不是说这些不重要,因为说到底,这就是工程。细节至关重要。当你有 10 个人在同一个代码库上工作时,拥有一个清晰的编码格式和清晰的风格是很重要的。
我希望在转换之前知道的事情
尽管我很兴奋,但当我换成 SWE 时,我很沮丧。作为一名数据科学家,我对自己的能力和表现充满信心。我很擅长我的工作。但是在我换了之后,我又成了一个菜鸟,比其他人都慢。这很令人沮丧,因为我想做得更好,但这在身体上是不可能的,因为我有很多事情要做。我被迫对自己的新角色抱有可控的期望。这是一个很大的变化,除非你以前是瑞典人。不要以为从第一天就可以开始表演。这需要时间。但是 要谦虚,虚心接受反馈,向你的队友学习,感谢他们的帮助和时间。
其次,因为我在相对较短的时间内切换到不同的角色和团队,我没有晋升的那么快。我的一些同事留在同一个角色和团队,成为经理或更高级别的个人贡献者(ICs)。如果你的目标是收入最大化和尽快升迁,这可能是不好的。如果我的目标是尽快升职,然后退役,我应该以 DS 的身份加入方,留在那里。不是说这是一条容易的路,但它会更有效地利用我的时间。这是否意味着我后悔我的决定?不,这取决于你在优化什么。
第三,从工作中你能学到的东西是有限的。你要边学东西。我将链接几个对你学习入门很重要的概念:
- 面向对象编程(OOP): 科里·斯查费的 YouTube 教程。
- 单元测试:科里斯查费的 YouTube 教程。
您将会学到许多其他的概念和工具。但是放心,所有的资源在网上都是免费的。SWE 的一生是不断学习和适应新技术和工具的一生。
结论
应该选择什么角色?你可以猜到,两者都有利弊。SWE 侧重于构建,而 DS 侧重于发现。我在这里没有过多地谈论 MLE,因为正如我在本文中所解释的,MLE 可以是 DS 或 SWE。我作为 MLE 的经历是一个在 ML 空间工作的 SWE。90%是软件工程,10%是数据科学。
我从这次经历中得出的最后一点:世上没有完美的工作!如果你不确定该选择哪个方向,我建议你使用著名的后悔最小化框架 : 在临终前,你会因为尝试而更后悔还是因为没有尝试而更后悔?当我切换的时候,我不知道该期待什么。但是我知道如果我不去尝试,我会后悔的。虽然有时很痛苦,但我很高兴我尝试了。下周,我将在 Shopify 开始新的工作。我祝你在未来的努力中好运,并在这个职业丛林中找到自己的路。祝我在新的岗位上好运!
如果你有任何问题,请在下面评论或联系我这里。
IBM 数据科学顶点项目——邻里之战
在解决问题的过程中要深思熟虑
结合结构化问题解决、数据分析&机器学习来解决业务问题
Coursera 的 IBM 数据科学专业课程的累积是一个顶点项目,要求课程参与者确定一个需要使用位置数据和邻域聚类的业务问题。分析业务问题、排除干扰、识别要解决的实际问题的能力是一项重要的技能,需要不断磨练。如果没有确定正确的问题,模型结果的有效性将会大大降低或变得毫无意义。因此,我还应用了波士顿咨询集团的几个解决问题的方法,这些方法是我最近在这个项目中接触到的,以增强问题陈述的框架。
本文提供了顶点的概述,从最初提出的问题陈述到建议。
内容
- 简介/业务问题
- 数据收集和准备
- 数据可视化和故障排除
- 探索性数据分析
- 剪影法
- 评估和建议
- 其他工具
- 编后记
简介/业务问题
背景:一位客户联系了咨询公司,就在京都开设餐厅的业务战略和执行路线图提出建议。最初的业务问题是“客户是否应该在京都设立连锁餐厅,地点在哪里?”

我没有深入问题并应用机器学习,而是仔细考虑了业务问题,并概述了以下可能不明显的考虑因素:
a)客户的目标是在京都建立餐厅
b)他们不确定市场饱和,也不确定京都的潜在地点。
将问题陈述重新组织为“如何确定客户在京都的业务定位和潜在餐厅位置?”通过重新构建的问题陈述,几个顶级业务驱动因素,即如下图所示,业务战略、运营和盈利能力成为关注的焦点。

逻辑树|作者图片
关于商业策略, K-Means 聚类将应用于相关餐馆的地理空间数据以聚类餐馆,并揭示诸如餐馆主题和合适位置的见解。
数据收集和准备
确定使用两个数据源。这些是:
-
京都选区及其各自地理坐标的列表。病房列表可从以下网页中检索,而相应的坐标可使用 geopy 库检索。
-
京都各个街区的餐厅。可以使用 Foursquare API 检索数据,并指定感兴趣的特定类别。
京都病房的名单是用熊猫 read_html() 方法刮出来的。

作者图片
然后使用 pandas get_levels() 方法来折叠标题。
然后使用 geopy 库检索的地理坐标提供数据。根据 Nominatum 的服务条款指定用户代理;也是为了避免自己的 IP 地址被屏蔽访问服务。
数据可视化和故障排除
京都病房的可视化是使用叶子库完成的。

最初的京都病房地图|图片由作者提供
发现京都的“北 Ku”和“美并 Ku”没有标在京都地图上。一种假设是他们的坐标可能是指其他城市的选区。首先,我可能会检查返回的地址,找到正确的坐标。最后,我将替换数据框中经过校正的坐标。
返回的地址证实了检索到的坐标不正确的假设。将城市名称“京都”添加到选区名称中,可以获得正确的坐标。下面是该任务的代码片段。

按作者调查地址|图片
更新后的地图将所有 11 个选区的坐标标绘在地图上,以此来验证校正后的坐标。

所有病房绘制|作者图片
探索性数据分析
然后使用 Foursquare API 来检索每个病房中餐馆的数据。指定条件以返回半径 500 米内的 100 家餐馆,结果如下所示。毫不奇怪,拉面和日本餐馆是最常见的餐馆类型。

作者图片

病房旁边的餐厅
与其他选区相比,东山-Ku、御厨-Ku、西京-Ku &萨基-Ku 的餐厅密度更高。
剪影法
注意特征(即每个区的餐厅类型比例), K 均值聚类将用于对这些实体进行聚类,并发现潜在的洞察力,如可行的餐厅主题和合适的餐厅位置。这些见解有助于企业战略的制定。
对于 K-均值聚类,首先需要确定 K 的最佳数目。这是一个可能被忽略的步骤。有几种方法可供选择,如肘法和剪影法。下面简要讨论这些方法。
肘法。针对不同的 k 值计算误差平方和的组内值(WSS)。选择 WSS 最先开始下降的 k 值(即“肘”)。然而,如果数据集没有被很好地聚类(即,重叠聚类),则肘部可能不明显。
剪影法。剪影方法测量一个点与其自己的聚类相比于其他聚类的相似性。轮廓系数的范围在+1 和-1 之间。趋向于+1 的正系数表示特定点被分配在理想聚类中。这也意味着该点实际上尽可能远离相邻的簇。系数为零表示特定点位于或非常接近两个相邻聚类之间的判定边界。负系数表示该点被分配给了错误的聚类。
选择侧影法来确定最佳 k 值。

最佳 k = 3 |作者图片
然后使用 init='k-means++和 random_state=42 实现 K-Means 聚类,以获得结果的可重复性。然后,绘制生成的聚类。

聚集的餐馆|作者图片
评估和建议
根据场馆类别检查每个集群,得出以下观察结果。

聚类 1 模式|作者图片
拉面餐馆在集群 1 中占主导地位。紧随其后的是提供亚洲风味菜肴的餐馆,如中国菜、寿司或寿司。

聚类 2 模式|作者图片
日本餐馆在集群 2 中占主导地位。紧随其后的是中式餐馆、拉面餐馆或顿武里餐馆。

聚类 3 模式|作者图片
乌龙面餐馆在第 3 组中占主导地位,其次是东武里餐馆。
探索京都、东山 Ku、Ku 和 Ku 西京和 Ku 的街区,那里的餐馆密度更高(当地超过 10 家)。较高的餐馆密度可能意味着这些地区更受游客欢迎,附近有更多的旅游景点。
例如,东山 Ku 区有许多历史景点,如八坂神社前的祇园娱乐区、九坂、三年坂和清水寺(被指定为世界遗产)。uky-Ku 也是许多著名景点的所在地,如天目山和以枫叶闻名的岚山。
初步推荐的市场进入地点是 Ku 东山和 Ku。东山-Ku 被分配到群组 2;一家提供日本料理的餐馆更有可能赢得游客的青睐。乌基什-Ku 被分配到第 1 组;一家提供拉面的餐厅可能更有可能赢得游客的青睐。不管上述建议,F&B 服务的其他基本要素,如优质的食品和服务以及严格的卫生习惯也不容忽视。
其他工具
对于这个顶点项目,我还试用了 readme.so 来创建和编辑 readme 文件。该工具提供了几个用于编辑的预定义模板。用户只需选择所需的模板,进行必要的编辑,并将生成的自述文件上传到 Github。
编后记
我想说,capstone 项目是一个丰富的体验,引入了用于数据收集的 API,并能够处理地理空间数据。虽然陈述业务问题陈述和应用机器学习技术可能很容易,但 capstone 为应用 BCG 的业务问题解决方法提供了一个好机会。它有助于加强问题陈述的框架。如果没有应用这些方法,如果没有机器学习应用的明确方向和目的,顶点可能会更加困难。
顶石的代码可以在这里获得。
IBM 数据科学专业课程回顾
每个人都在跑短跑,试试马拉松
我的数据科学课程之旅
目录

1.介绍
IBM 数据科学课程于 2020 年底推出,是 IBM 发布的为数不多的数据科学课程之一,旨在帮助寻求转向和突破数据科学的毕业生和工作专业人士。IBM 的其他课程包括 IBM 数据分析师专业课程和 IBM 数据工程专业课程。
每门课程都由几个模块组成,每个模块完成后都会链接到一个数字徽章和证书。感兴趣的学员可以选择完成特定模块或所有模块,以获得完整的课程认证。
2.课程概述
数据科学课程由十个模块组成。这些是:
- 什么是数据科学
- 数据科学工具
- 数据科学方法论
- 面向数据科学和人工智能的 Python
- 数据科学 Python 项目
- 用于数据科学的数据库和 SQL
- 使用 Python 进行数据分析
- 用 Python 进行机器学习
- 顶点工程
前四个模块的内容相对较少,如果一个人每天留出八个小时学习,可以在一周内轻松完成。在接下来的四个模块中,将向学员介绍 Python、SQL 和 IBM 的云服务(用于数据存储、笔记本执行和云计算。该课程累积在一个顶点项目中,学习者需要应用各种模块中涵盖的数据科学技能;从问题定义到数据收集、数据清理、探索性数据分析、特征工程以及模型训练和评估。
3.好人
对于门外汉,前四个模块提供了一个很好的、相当简明的数据科学介绍。助教在向学习者提供解释时反应非常迅速。
关于 SQL 的主题是通过 IBM Cloud 环境介绍的,所以学习者不需要担心下载/安装额外的软件。SQL 中的模块与数据工程课程共享,对于更复杂的 SQL 操作,还有可选的课程内容(在“荣誉”证书下)。
对于 Python 数据分析,除了 matplotlib 和 seaborn,还包括另外两个库:Plotly(用于交互式绘图)和 Dash(用于交互式分析应用)。
在 capstone 项目中,向学员介绍了 Folium(地理空间数据的数据可视化库)和 FourSquare API。提供笔记本练习来指导学员如何使用这些练习。
4.需要改进的地方
从以下几个方面开始,本课程会更好
笔记本上的错别字。几个笔记本包含一些小的拼写错误,但没有严重到影响可读性或代码执行的程度。
IBM 云账户设置说明。帐户设置说明可能已经说明,并非所有地区都允许免费云服务。我最初选择了“日本”而不是默认的“东达拉斯”来托管我的服务,并发现一些功能需要额外的费用。通过启动另一个帐户并按照相应的设置步骤解决了这个问题。
IBM 云环境可用性。服务器不稳定的可用性有时会妨碍到动手实验的连接。幸运的是,实验室练习可以离线完成,然后上传到实验室完成;在本地机器上安装一个运行 Jupyter 笔记本的 conda 会非常有帮助。
机器学习内容的覆盖面。本课程很好地介绍了使用 Python 和各种算法的机器学习。如果这些也包括在内就更好了:
机器学习的数据准备。具体来说,本可以做得更好的三个方面是:
- 在缩放之前将数据分成训练集和测试集。这是顺便提到的,并没有在笔记本练习中实际执行。在缩放之后进行训练测试分割将导致数据泄漏,因为测试集也将相应地用来自训练集的数据特征来缩放。结果是,当根据训练数据进行训练时,模型实际上在一定程度上“看到”了测试集。
- 在训练和测试中分层因变量。分层确保训练和测试数据子集将具有相同比例的类别标签。
- 处理阶层失衡的技巧。类别标签比例几乎相等的数据集很少是现实应用的情况。关于减轻班级失衡的技术的其他模块,如合成少数过采样技术或 SMOTE,将是课程的一个很好的补充。
破折号分配起始码。所提供的起始代码块中的缩进使得代码更加混乱,本来可以更好地重构。我跳过了这一部分,进入下一个模块,并在平行完成另一个作业的同时重新回顾了作业。
模型超参数调谐。向学员介绍用于模型构建的超参数概念。在一些精选部分,学员将学习如何找到最佳超参数(例如 k 均值聚类的最佳 k 值)。使用 scikit-learn 的 GridSearchCV 或 RandomizedSearchCV 进行参数搜索的覆盖范围肯定有助于建立学习者在模型构建方面的意识和能力。
深度学习。对于寻求接触深度学习的学习者,请选择 IBM 的人工智能工程专业课程。
5.关闭
我可以推荐这道菜吗?我报名参加这个课程的目的是在做自己的修改时发现盲点,同时也为我的简历添加一个证书。学费也是我考虑的事情,这就是为什么我为自己制定了一个积极的学习计划。课程注册的前七天是免费的,这更加激励了学生取得良好的进步。当我意识到我无法在一周内完成课程后,我调整自己的节奏,希望在一个月内完成。
对于初学者和想在简历中添加证书的数据科学爱好者来说,这是一门不错的课程。更高级的 ML 题目(即深度学习),找其他专业课。
课程和证书是发展能力的一种方式。在培养数据科学职业能力的背景下,我建议开展有针对性的辅助项目,加入数据科学社区/订阅数据科学期刊,并以好奇的心态培养足够的数据科学技能和知识。我很高兴我参加了这个课程,因为它帮助我在指定的时间内发现盲点。为了更深入的探索和研究,这些项目又分成了副业。我希望这篇评论有助于其他人决定一门课程,如果他们决定了的话。
ICLR 2021——你不该错过的 10 篇论文精选
学习表现的国际会议已经在这里了,它的内容非常丰富:860 篇论文,8 个研讨会和 8 个特邀报告。选择关注的地方很难,所以这里有一些值得关注的想法!

图片作者。
一年前,ICLR 2020 会议是第一个完全在线的大型会议,它为所有完全虚拟的会议设定了令人惊讶的高标准。今年,这个会议又是一个只在网上举行的活动,而且看起来很有希望:变形金刚在标题中出现的次数减少了…因为它们已经无处不在了!计算机视觉、自然语言处理、信息检索、ML 理论、强化学习……应有尽有!今年这一版内容的多样性令人瞠目结舌。
谈到受邀演讲,阵容也令人兴奋:Timnit Gebru 将在开幕式上谈论我们如何超越机器学习中的公平言论,这将在大会上引发一些关于该主题的讨论。研讨会也比以往任何时候都更加拥挤,以基于能量的模型为特色,反思 ML 论文和负责任的 AI 等。
理解这一令人印象深刻的阵容不是一件容易的事,但在人工智能研究导航员在泽塔阿尔法的帮助下,我们通过引用、twitter 人气、作者影响力、聚光灯演示和平台的一些推荐,浏览了最相关的 ICLR 论文,我们确定了一些非常酷的作品,我们想强调一下;有些已经众所周知,有些更多的是一颗隐藏的宝石。当然,这些选择并不旨在成为一个全面的概述——我们将错过许多主题,如神经架构搜索、ML 理论、强化学习或图形神经网络等——但是,嘿,我听说选择稀疏和深入往往比选择广泛和浅薄更好;所以这是我的 10 大,享受吧!
1。一幅图像相当于 16x16 个字:大规模图像识别变形金刚 | 🖥 ICLR 会议 |👾代码
Alexey Dosovitskiy,Lucas Beyer,Alexander,Dirk Weissenborn,Xiaohua Zhai 等人
作者的 TL;DR →直接应用于图像补丁并在大型数据集上进行预训练的变压器在图像分类方面非常有效。
❓Why → 第一篇论文展示了纯变形金刚如何在(某种)大图像上超越最好的 CNN,从而在过去的几个月里启动了快速的“视觉变形金刚革命”。
💡关键见解→ 迁移学习已被证明对变形金刚极其有效:所有 NLP 最新技术都包含某种类型的迁移,例如来自自我监督的预培训。概括地说,人们发现网络越大,传输越好,而对于大型网络,变压器是首屈一指的。

在这种愿景的驱动下,作者展示了一个纯粹的转换器如何在图像分类上表现得非常好,只需通过将图像作为一系列补丁嵌入——只是补丁像素的线性投影——并直接在大量监督数据 (ImageNet)上训练。该论文暗示,该模型可以受益于自我监督的预训练,但没有为此提供完整的实验。
结果显示,一旦模型离开数据受限的状态,ViT 如何胜过 CNN,甚至 CNN+注意力混合;甚至更高的计算效率!在许多有趣的实验中,作者展示了注意力的感受域如何跨层进化:最初非常多样(全局+局部),后来在网络中专门针对局部注意力。

来源:https://openreview.net/pdf?id=YicbFdNTTy
你可能也会喜欢在 ICLR: LambdaNetworks:建模远程交互而不被注意
2。与表演者一起反思注意力 | 🖥 ICLR 会议 | ✍️ 博客
作者:Krzysztof Choromanski、Valerii Likhosherstov、David Dohan、Xingyou Song、Andreea Gane、Tamas Sarlos、Peter Hawkins、Jared Davis 等人
作者的 TL;通过可证明的随机特征近似方法,不依赖于稀疏性或低秩性,线性满秩注意力转换器【T21 博士】→表演者。
❓Why → 全神贯注的复杂性仍然让许多 ML 研究者夜不能寐。高效变压器已经出现了很长一段时间,但是还没有一个提案明显地统治了这个领域……
💡关键见解→ 与其他高效变形金刚的提议不同,表演者不依赖于特定的启发式近似注意力,例如将注意力限制在较低等级的近似或加强稀疏性。相反,作者提出将自我注意机制分解成以下矩阵,这些矩阵具有线性的组合复杂度。序列长度 L: O(Ld log(d)) 而不是 O(L d)。

这种分解依赖于太多的技巧来实现,但仅仅是名称丢弃的缘故,我们谈论的是核,随机正交向量和三角 softmax 近似。都是为了建立好感+用非常严密的理论保证来估计自己的注意力。
当涉及到实际实验时,这项工作将 Performer 与现有的高效转换器(如 Linformer 和 Reformer)进行比较,在建模非常长的依赖性至关重要的任务中,如研究蛋白质序列,它的性能优于现有的架构。最后,这种方法最大的吸引力之一是,你可以使用新的线性注意力机制重用现有的预训练变压器,只需要一点微调就可以恢复大部分原始性能,如下图所示(左)。

来源:https://openreview.net/pdf?id=Ua6zuk0WRH
你可能也会喜欢: 远程竞技场:高效变形金刚的标杆,随机特性注意
3。PMI 屏蔽:相关跨度的原则性屏蔽 | 🖥 ICLR 会话
约夫·莱文等人
作者的 TL;DR →相关表征的联合掩蔽显著加快并改善了 BERT 的预训练。
❓Why → 这是一个非常清晰、直接的想法,同时也带来了同样显著的效果。它有助于我们理解掩蔽语言建模预训练的目标。
💡关键见解→ 作者不是随机屏蔽标记,而是仅使用语料库统计数据来识别高度相关的标记跨度。为此,他们创建了对任意长度跨度的标记对之间的逐点互信息的扩展,并显示了如何以该目标训练 BERT 比诸如统一掩蔽、整个单词掩蔽、随机跨度掩蔽等替代方法更有效地学习。
直觉上,这种策略是可行的,因为你阻止了模型使用经常紧挨着出现的单词的非常浅的相关性来预测屏蔽的单词,迫使模型学习语言中更深层次的相关性。在下图中,你可以看到变形金刚如何通过 PMI-MLM 学习得更快。

来源:https://openreview.net/pdf?id=3Aoft6NWFej
4。经常性独立机制 | 🖥 ICLR 会议
作者 Anirudh Goyal、Jordan Hoffmann、Shagun Sodhani 等人
作者的 TL;博士..
❓Why → 如果人工智能想要在某种程度上类似于人类智能,它需要超越训练数据分布进行推广。这篇论文——最初是在一年多后发表的——提供了洞察力、经验基础和这种概括的进展。
💡关键见解→ 循环独立机制是实现注意力瓶颈的神经网络。这种方法从人脑如何处理世界中汲取灵感;也就是说,很大程度上是通过识别相互作用很少且没有因果关系的独立机制。例如,一组四处弹跳的球可以在很大程度上独立建模,直到它们相互碰撞,这是一个很少发生的事件。
RIMs 是一种递归网络形式,其中大多数状态在大部分时间都是自己进化的,只通过一种注意机制稀疏地相互交互,这种机制可以是自上而下的(直接在隐藏状态之间)或自下而上的(在输入特征和隐藏状态之间)。当输入数据分布发生变化时,该网络显示出比常规 RNNs 更强的泛化能力。

来源:https://openreview.net/pdf?id=mLcmdlEUxy-
整个变形金刚事件的一大收获是,神经网络中电感偏差的重要性可能被夸大了。然而,当在域内对模型进行基准测试时,这是正确的。本文展示了为了证明强先验(如注意力瓶颈)的有用性,如何需要走出训练领域,并且大多数当前的 ML/RL 系统都不是以这种方式进行基准测试的。
虽然结果可能不是最令人印象深刻的,但这篇论文——以及后续工作(见下文)——提出了一个雄心勃勃的议程,即如何将我们的人工智能系统转变为类似于我们大脑的东西,我甚至可以说,将过去十年的人工智能革命与过去十年的人工智能革命结合起来。我们应该庆祝这样的尝试!
你可能还喜欢: 快速和慢速学习循环独立机制,在结构化的动态环境中分解陈述性和程序性知识,寻找丢失的领域概括。
5。通过随机微分方程建立基于分数的生成模型 | 🖥 ICLR 会议 |👾代码
作者宋洋等人
作者的 TL;DR→一个基于分数的模型的训练和采样的通用框架,它统一和概括了以前的方法,允许可能性计算,并支持可控生成。
❓Why → 甘人仍然是怪异的生物……欢迎替代物,这个很有前途:把数据变成噪音很容易,把噪音变成图像是……生成式建模!而这正是本文所要做的。
💡关键见解→ 好吧,我不能说我完全理解了所有的细节,因为有很多数学知识超出了我的理解范围。但它的要点很简单:你可以通过“扩散过程”将图像转换成“噪音”。想想单个水分子在流动的水中是如何运动的:有一些确定性的水流遵循一个梯度,还有一些附加的随机抖动。你可以对像素图像做同样的事情,扩散它们,这样它们最终就像一个易于处理的概率分布中的噪声。这个过程可以被建模为一个随机微分方程,在物理学中是已知的,基本上是一个微分方程,在每个时间点都有一些额外的抖动。

来源:https://openreview.net/pdf?id=PxTIG12RRHS
现在,如果我告诉你,这个随机扩散过程是…可逆的!你基本上可以从这种噪音中取样,然后再做一个图像。就像这样,作者在 CIFAR-10 上获得了 9.89 的 SOTA 初始分数和 2.20 的 FID。好吧,在引擎盖下还有更多的事情要做…你真的需要看看这篇文章!

来源:https://openreview.net/pdf?id=PxTIG12RRHS
6。自回归实体检索 | 🖥 ICLR 会话 |👾代码
尼古拉·曹德、戈蒂埃·伊萨卡、塞巴斯蒂安·里德尔、法比奥·彼得罗尼。
作者的 TL;DR→我们通过以自回归方式从左到右生成实体的唯一名称标识符来处理实体检索,并以显示 SOTA 结果的上下文为条件,在 20 多个数据集内使用最近系统的一小部分内存。
❓Why → 一种新的直接的实体检索方法,令人惊讶地打破了一些现有的基准。
💡关键见解→ 实体检索的任务是找到自然语言所指的精确实体(有时可能会有歧义)。现有的方法将此视为一个搜索问题,即给定一段文本,从 KG 中检索一个实体。直到现在。这项工作提出通过自回归生成实体标识符来寻找实体标识符:有点像 markdown 语法超链接的东西:[entity](identifier generated by the model)。没有搜索+重新排名,什么都没有,简单明了。实际上,这意味着对实体及其上下文进行交叉编码,这样做的好处是内存占用随着词汇表的大小而线性扩展(不需要在知识库空间中做大量的点积),也不需要对负数据进行采样。
从预先训练的 BART⁵开始,他们微调最大化具有实体的语料库(维基百科)的自回归生成的可能性。在推断时,他们使用约束波束搜索来防止模型生成无效的实体(即不在知识库中)。结果令人印象深刻,见下表中的例子。


7。密集文本检索的近似最近邻否定对比学习 | 🖥 ICLR 会话
李熊、熊等
作者的 TL;DR → 使用 ANCE 改进密集文本检索,它使用异步更新的 ANN 索引选择具有较大梯度范数的全局否定。
❓Why → 信息检索抵制“神经革命”的时间比计算机视觉多很多年。但是自从伯特以来,密集检索的进步是巨大的,这是一个极好的例子。
💡关键见解→ 当训练模型进行密集检索时,通常的做法是学习嵌入空间,其中查询-文档距离是语义相关的。对比学习是这样做的标准技术:最小化正面查询-文档对的距离,最大化负面样本的距离。然而,否定样本通常是随机选择的,这意味着它们不太能提供信息:大多数时候否定文档显然与查询不相关。
这篇论文的作者提出在训练期间从最近的邻居采样否定,这产生了接近查询的文档(即,当前模型认为相关的文档)。实际上,这意味着语料库的索引需要在训练期间异步更新(每次迭代更新索引会非常慢)。幸运的是,结果证实了 BM25 基线最终是如何落后的!


8。图像增强是你所需要的:从像素中规则化深度强化学习 | 🖥 ICLR 会话
Denis Yarats,Ilya Kostrikovm 和 Rob Fergus。
作者的 TL;DR → 首次成功演示图像增强可应用于基于图像的深度 RL,实现 SOTA 性能。
❓Why → 你支持什么?基于模型还是无模型 RL?回答问题前先看这篇论文!
💡关键见解→ 现有的无模型 RL 成功地从状态输入中学习,但很难直接从图像中学习。直觉上,这是因为当从早期重放缓冲器学习时,大多数图像高度相关,呈现非常稀疏的奖励信号。这项工作显示了无模型方法如何能够极大地受益于像素空间的增强,从而在学习中变得更具样本效率,在 DeepMind control suite⁶和 100k Atari⁷基准测试中,与现有的基于模型的方法相比,实现了有竞争力的结果。

资料来源:https://openreview.net/pdf?id=GY6-6sTvGaf
9。自适应联邦优化 | 🖥 ICLR 会话
萨尚克·雷迪、扎卡里·查理斯等人
作者的 TL;我们提出了自适应联邦优化技术,并强调了它们相对于 FedAvg 等流行方法的改进性能。
❓Why → 要让联合学习广泛传播,联合优化器必须变得无趣,就像 2021 年的亚当一样。本文正是试图做到这一点。
💡关键见解→ 联合学习是一种 ML 范式,其中由服务器托管的中央模型由多个客户端以分布式方式训练。例如,每个客户可以使用他们自己设备上的数据,计算梯度相对于损失函数,并将更新的权重传送给中央服务器。这一过程提出了许多问题,例如应该如何组合来自多个客户端的体重更新。
本文在解释联邦优化器的当前状态方面做了大量工作,构建了一个简单的框架来讨论它们,并展示了一些关于收敛保证的理论结果和实验结果,以表明他们提出的自适应联邦优化器比现有的优化器(如 FedAvg⁸.)工作得更好本文中介绍的联邦优化框架不知道客户端 (ClientOpt)和服务器 (ServerOpt)使用的优化器,并使它们能够将动量和自适应学习率等技术插入到联邦优化过程中。有趣的是,他们展示的结果总是使用 vanilla SGD 作为 ClientOpt,使用 adaptive optimizer(ADAM,YOGI)作为 ServerOpt。

来源:https://openreview.net/pdf?id=LkFG3lB13U5
10。果蝇能学习单词嵌入吗? | 🖥 ICLR 会议
梁等著
作者的 TL;果蝇大脑中的一个网络基序可以学习单词嵌入。
❓Why → 这篇论文的前提太不可抗拒了,不能不包括在这里,它也是对 massive ML 的主导菌株的极好对比。
💡关键见解→ 单词可以相当有效地表示为稀疏二进制向量(甚至上下文化!).这项工作在精神上非常类似于像 Word2Vec⁹和手套⁰这样的经典著作,在这个意义上,单词嵌入是使用非常简单的神经网络学习的,并巧妙地使用共现语料库统计来完成。
该架构的灵感来自果蝇的生物神经元的组织方式:感觉神经元(PN)映射到凯尼恩细胞(KC ),凯尼恩细胞连接到前侧成对的外侧神经元(APL ),后者负责反复关闭大多数 KC,只留下少量激活。

将此转化为语言,单词在 PN 神经元中表示为单词包上下文和中间单词的一个热点向量的串联(见下图)。然后这个向量被认为是训练样本,它被投射到 KC 神经元上并被稀疏化(只有 top-k 值存活)。通过最小化能量函数来训练网络,该能量函数强制共享上下文的单词在 KC 空间中彼此靠近。

有趣的是,这允许即时生成上下文化的单词嵌入(😉),假设在推理过程中给定单词的单词包上下文可能不同。

相当激动人心的一组论文!把范围缩小到 10 个确实是个挑战。作为结束语,我想提一下阅读 ICLR 的报纸是一件多么愉快的事情,因为它们比一般的 arxiv.org 出版物要精美得多。不管怎样,这个收藏到此结束,但是大会还有很多值得探索的地方,我真的很期待。该团队将通过我们公司的 twitter feed 在 @zetavector 现场报道有趣的见解,所以如果你不想错过任何事情,请收听。
你呢?你对大会最期待的是什么?欢迎在评论中分享一些建议👇
参考文献
[1] 林前者:具有线性复杂性的自我注意 —司农王等著 2020
[2] 改革者:高效的变压器 —作者尼基塔·基塔耶夫等人 2020
[3] 高效变压器综述 —易泰等著 2020
[4] 《大鸟:更长序列的变形金刚》——曼齐尔·扎希尔等著,2020 年
[5] BART:自然语言生成、翻译和理解的去噪序列间预训练 —刘纳曼戈亚尔等 2019
[6] DeepMind 控制套件—Yu val Tassa 等人 2018
[7]atari基于模型的强化学习——由祖卡斯·凯泽、穆罕默德·巴巴艾扎德、皮奥特·米奥斯、巴兹·̇ej·奥西斯基等人于 2019 年
[8] 从分散数据进行深度网络的通信高效学习 —作者 H.BrendanMcMahan 等人 2016
[9] 向量空间中单词表示的有效估计 —托马斯·米科洛夫等人,2013 年
[10] GloVe:单词表示的全局向量 —作者 Jeffrey Pennington , Richard Socher ,Christopher Manning2014
[11] 亚当:一种随机优化的方法 —作者 Diederik P. Kingma 等人,2015 年
使用 WhatsApp 聊天工具进行迷你项目的想法
使用 WhatsApp 聊天工具可以做的一切事情,从分析短信习惯到构建发电机模型

迭戈·PH 在 Unsplash 上的照片
个性化的触摸
您将从 Kaggle 找到大量数据集,用于各种分析和模型原型制作。然而,它们都不如分析你自己的数据、理解你的聊天习惯、建立能像你一样发短信的模型,甚至预测你在特定情况下会使用的表情符号有趣。你能和你正在分析的东西联系得如此紧密,这一事实非常令人兴奋(至少对我来说是这样!)
概观
我将列出一些对制作一些迷你项目有用的想法。我会留下资源或者库和工具的链接,你可以为每个想法所用。这些想法以难度递增的顺序列出(就实现而言)。我们将从基本 EDA 开始,继续进行时序分析,最后以变压器模型结束。我们开始吧!
将任何聊天下载为文本文件
- 打开 WhatsApp 上的聊天,点击右上方的三个点。
- 点击更多
- 点击导出聊天
- 选择“无媒体”选项(这里的所有想法都只需要文本数据)
- 聊天应该作为文本文件下载。
关于数据清理的说明
下载的聊天将不会有单独的用户,时间,日期,消息正文等功能。所有这些特性都集中在一篇专栏文章中。您必须为这些功能创建单独的列。 这里的是一个可以帮到你的笔记本。此外,由于我们没有导出任何媒体,聊天中的所有媒体都由一个名为 < Media 省略> 的占位符表示,您可以删除这些行。
私人聊天
这里有一些你可以从聊天中得到答案的问题
1.聊天中发送的消息比例相等吗?
您可以按用户名对邮件进行分组,并检查邮件的数量。
2.你发送的每条信息的平均长度是多少?
同样,对于您发送的每条消息,您可以通过空格分隔符分割文本,并计算列表的长度。然后,您可以计算该计数的平均值,并将其与其他用户发送的邮件的平均长度进行比较。
3.寻找最常用的表情符号和单词
表情符号 库是用 python 处理表情符号的一个绝妙方法。为此,你只需要适当地标记你的文本,并使用集合中的计数器类来帮助找到最常见的表情符号。同样,对于单词,可以使用 Sklearn 的 CountVectorizer 或者再次使用 Counter 类。
4.你什么时候最活跃?
再次使用日期,您可以使用日期栏上的计数器找到您发送最多消息的日期,因为 WhatsApp 会为您发送的每条消息单独记录日期。再努力一点,你就可以扩展到寻找你最活跃的时间间隔。您可以预定义一天中的时间间隔,并查找包含最多消息的时间间隔。
5.查找您发送的媒体数量
比起发短信,很多人更喜欢发语音短信。许多人通过迷因或 gif 进行交流。你可以分析你的信息中有多少是文本。
群聊
- 找出谁最活跃
- 找出群组名称或描述被更改的次数
- 弄清楚你是否只回复群里的某些人。
使用时间序列分析发现模式
这个非常有趣,因为它可以揭示许多重复的趋势。例如,在我考试之前,我总是给一定数量的人发短信,要求他们做笔记:P 在我的足球队在周末比赛中发疯之前,但在其他时间非常不活跃。以下是创建时间序列数据集的方法。
- 使用 Counter(来自集合)为日期和计数创建一个字典。
- 这里的计数表示对应于每个唯一日期发送的消息数。
- 将日期设置为数据框索引,将计数设置为单独的要素列
现在你可以将时间序列分解成趋势、季节性和残差。 这是一篇很棒的</time-series-from-scratch-decomposing-time-series-data-7b7ad0c30fe7?source=user_profile---------1---------------------------->***文章,可以帮助你分解时间序列。您还可以检查时间序列中的自相关性。*****
预测你会用什么表情符号来表达某个句子
这个特别有趣,因为每个人都用不同的表情符号来表达情绪。你可以创建自己的数据集,并训练一个模型来预测一个句子使用什么表情符号,而不是使用可能不同的表情符号的数据集。不过这个有点难!预处理需要一点努力。以下是您必须遵循的步骤的概述。
- 定义一些标签表情符号。比如我想用❤️来代表快乐,用😞和嫉妒😏。我能做的就是用这些表情符号创建一个列表。
- 此外,为他们的 demojized 版本创建一个列表,使用上一个列表中每个表情符号的 emoji.demojize()。
- 现在,对于每个句子,对于列表中的每个表情符号,使用替换功能,将表情符号替换为其 demojized 版本,例如* 表情符号😞') .***
- 现在从文本中删除所有其他表情符号。 这篇 是一篇很有帮助的文章,可以帮你去除表情符号。
- 现在创建一个名为 labels 的新列,循环遍历句子。在句子中找到 demojized 列表中的任何字符串(步骤 2)后,将该字符串添加到 labels 列。
- 现在替换句子中去掉词尾的字符串。
- 删除标签为空的所有行。(表示缺少我们试图预测的表情符号)****
- 我们只剩下一个包含句子及其表情符号的数据框。
- 现在使用任何分类器模型,就像你通常处理 NLP 分类问题一样!
教 GPT-2 像你一样发短信!

把最好的留到最后。
每个对话有近 40k 条消息,你有足够的数据来微调像 GPT-2 这样的发电机模型。 HuggingFace 提供了很好的抽象,可以使用最先进的预训练模型,如 GPT-2。只需用你自己的句子(经过一些预处理)对它进行微调,让它生成符合你风格的文本!
这里有一个很好的参考资料,可以帮助你立刻对 GPT-2 进行微调!
结论
我希望这对每个想用个性化数据快速制作项目的人有所帮助。我希望这些提示和参考足以帮助你实现这些想法。如果您觉得缺少什么,请随时向我询问实现细节。
如果你喜欢这篇文章,这里有更多!
其他一些项目。可以联系我 这里 。 感谢您的配合!
在岩石物理机器学习之前识别和处理丢失的测井数据
从探索性数据分析到利用测井数据进行机器学习系列文章的第 2 部分

马库斯·斯皮斯克在 Unsplash 上的照片
机器学习和人工智能在地球科学和岩石物理领域越来越受欢迎。尤其是在过去的十年里。机器学习是人工智能的一个分支,是计算机可以学习并根据数据进行预测的过程,而无需显式编程。我们可以在岩石物理学中以多种方式使用机器学习,包括自动化异常值检测、属性预测、相分类等。
这一系列文章将着眼于从基本测井测量到岩石物理性质预测的数据集。这些文章最初是在 2021 年 SPWLA 大会上的一个关于机器学习和人工智能的研讨会上作为 Jupyter 笔记本发表的。它们后来被扩展和更新以形成这些文章。该系列将包括以下内容,链接将包括一旦他们被释放。
1。Volve 油田数据集 2 中所选井的初始数据勘探。识别&处理缺失数据(本文)
3。使用手动和自动方法检测异常值/异常数据点
4。使用机器学习预测关键储层性质
这篇关于识别测井测量中缺失数据的文章是之前工作的高潮,具体文章如下:
此外,如果你想了解 missingno 库如何识别丢失的数据,可以查看我的 YouTube 视频,其中介绍了这个库及其特性。
识别和处理缺失数据
缺失值是数据集中的一个常见问题。在测井数据集中,数据丢失的原因有很多,包括工具/传感器故障、数据过时、遥测问题、卡住和拉动以及选择丢失。这些问题在 McDonald (2021)中有详细描述。
在 Python 世界中,我们可以利用易用库中的许多有用的函数来识别丢失的数据,这些方法包括:
- 熊猫数据帧摘要(例如。描述()和。信息())
- 缺少图书馆
- 可视化
处理丢失数据的过程可能会引起争议。许多岩石物理学家、数据科学家和其他人认为,数据的填充可能会导致最终结果增加更大的不确定性,而其他人则建议应该填充数据。可以使用简单的线性插值来填充缺失值,使用平均值进行填充,还可以扩展到使用机器学习算法来预测缺失值。和往常一样,在应用任何缺失数据插补技术后,您应该检查您的数据。
在本文中,我们将首先识别丢失的数据,然后使用多种技术删除受影响的行和列。将使用变量丢弃和列表删除方法演示数据删除。
数据
我们将在本文中使用的数据集来自 2018 年发布的流行的 Volve Field 数据集,以促进研究和学习。发布的数据包括:
- 测井记录
- 岩石物理解释
- 报告(地质、完井、岩石物理、岩心等)
- 核心测量
- 地震数据
- 地质模型
- 还有更多…
Volve 油田位于北海挪威部分斯塔万格以西约 200 公里处。1993 年在侏罗纪时代的胡金地层中发现了碳氢化合物。石油生产始于 2008 年,持续了 8 年(是计划时间的两倍),直到 2016 年停止生产。在油田寿命期内,总共生产了 63 个 MMBO,达到了 56000 桶/天的稳定产量
有关 Volve 字段和整个数据集的更多详细信息,请访问:https://www . equinor . com/en/what-we-do/Norwegian-continental-shelf-platforms/Volve . html
这些数据在 Equinor 开放数据许可证下获得许可。
导入库和数据
第一步是导入处理数据所需的库。对于本笔记本,我们将使用:
- 熊猫用于加载和存储数据
- matplotlib 和 seaborn 用于可视化数据
- numpy 为多种计算方法
- 缺失 no 显示缺失数据的位置
import pandas as pd
import matplotlib.pyplot as plt
import missingno as msno
import numpy as np
接下来,我们将使用 pandas read_csv函数加载数据,并将其赋给变量df。数据现在将存储在一个称为数据帧的结构化对象中。
df = pd.read_csv('data/spwla_volve_data.csv')
正如在上一篇文章中看到的,我们可以调用一些方法来检查数据内容和初始质量。
.head()方法允许我们查看数据帧的前 5 行。
df.head()

使用 df.head()方法从 dataframe 中获取标题行。图片由作者提供。
.describe()方法为我们提供了一些汇总统计数据。为了使用这种方法确定我们是否有丢失的数据,我们需要查看计数行。如果我们假设 MD(测量深度)是最完整的列,我们有 27,845 个数据点。现在,如果我们看一下 DT 和 DTS,我们可以看到我们分别只有 5,493 和 5,420 个数据点。一些其他栏目也有较低的数字,即:RPCELM,PHIF,西南,VSH。
df.describe()

使用 df.describe()方法的数据帧摘要统计数据—单击以放大。图片由作者提供。
为了获得更清晰的认识,我们可以调用info()方法来查看每列有多少非空值。我们可以立即看到之前突出显示的那些具有较少数量的非空值。
df.info()
这将返回以下内容:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 27845 entries, 0 to 27844
Data columns (total 16 columns):
wellName 27845 non-null object
MD 27845 non-null float64
BS 27845 non-null float64
CALI 27845 non-null float64
DT 5493 non-null float64
DTS 5420 non-null float64
GR 27845 non-null float64
NPHI 27845 non-null float64
RACEHM 27845 non-null float64
RACELM 27845 non-null float64
RHOB 27845 non-null float64
RPCEHM 27845 non-null float64
RPCELM 27600 non-null float64
PHIF 27736 non-null float64
SW 27736 non-null float64
VSH 27844 non-null float64
dtypes: float64(15), object(1)
memory usage: 3.4+ MB
使用缺失数据可视化数据稀疏性
missingno 库旨在获取数据帧,并允许您可视化可能存在的差距。
我们可以简单地调用.matrix()方法并传入 dataframe 对象。当我们这样做时,我们生成数据帧的图形视图。
在下图中,我们可以看到 DT 和 DTS 列中有明显的差距,而 RPCELM、PHIF 和 SW 列中有微小的差距。
图表右侧的迷你图提供了数据完整性的指示。如果该行位于最大值(右侧),则表明该数据行是完整的。
msno.matrix(df)

缺失没有显示缺失数据值出现位置的矩阵图。作者图片
我们可以调用的另一个图是条形图,它提供了每一列中点数的图形汇总。
msno.bar(df)

缺少所选井内数据的条形图。图片作者。
使用 matplotlib 创建自定义数据覆盖图
我们可以生成自己的图来显示每口井的数据稀疏度如何变化。为了做到这一点,我们需要操纵数据帧。
首先,我们创建一个单独处理的数据帧的副本,然后如果数据为非空,用值 1 替换每一列。
为了让我们的绘图工作,我们需要将每一列的值增加 1。这允许我们将每一列绘制为前一列的偏移。
data_nan = df.copy()
for num, col in enumerate(data_nan.columns[2:]):
data_nan[col] = data_nan[col].notnull() * (num + 1)
data_nan[col].replace(0, num, inplace=True)
当我们查看数据帧的标题时,我们现在有一系列从 1 到 14 递增值的列。
data_nan.head()

包含基本值的数据框架,可用于绘制数据覆盖范围。作者图片
接下来,我们可以按照井名列对数据帧进行分组。
grouped = data_nan.groupby('wellName')
然后,我们可以使用新的数据帧为每个井创建多个子图。如果数据存在,我们可以从前一列的最大值渐变到当前列的最大值,而不是在子情节中创建子情节。如果缺少数据,它将显示为一个缺口。

显示五口选定井的每条测井曲线的数据覆盖范围的子图。蓝色表示数据存在的位置,白色表示缺少值。图片作者。
从图中,我们不仅可以看到每口井的数据范围,还可以看到 5 口井中有 2 口井的 DT 和 DTS 曲线缺失,2 口井的 RPCELM 数据缺失,2 口井的 PHIF 和 SW 曲线值缺失。
处理缺失数据
丢弃变量
变量丢弃可用于变量中存在缺失值的情况,这反过来会使该变量不适合预期用途。因此,它可以从数据集中删除。如果做到了这一点,它可以对机器学习建模产生广泛的影响,特别是如果变量很重要并且存在于其他油井中。
在我们的示例数据集中,两个井中的 DT 和 DTS 都缺失。我们可以选择从数据集中删除这些孔,或者我们可以删除所有孔的这两列。
以下是我们如何从数据帧中移除两条曲线的示例。为此,我们可以将一个列名列表传递给函数drop(),即我们希望沿着其放置数据的轴,在本例中是列(轴=1 ),而inplace=True参数允许我们从 dataframe 中物理删除这些值。
df.drop(df[['DT', 'DTS']], axis=1, inplace=True)
如果我们查看数据帧的标题,我们会看到我们已经删除了所需的列。
df.head()

然而,如果我们调用信息方法…
df.info()
它返回下面的结果。我们可以看到,我们仍然有一些测井曲线/列缺少值。即 PHIF、西南和 VSH。其中最后三个是岩石物理输出,并且可能只存在于感兴趣的区域上。
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 27845 entries, 0 to 27844
Data columns (total 14 columns):
wellName 27845 non-null object
MD 27845 non-null float64
BS 27845 non-null float64
CALI 27845 non-null float64
GR 27845 non-null float64
NPHI 27845 non-null float64
RACEHM 27845 non-null float64
RACELM 27845 non-null float64
RHOB 27845 non-null float64
RPCEHM 27845 non-null float64
RPCELM 27600 non-null float64
PHIF 27736 non-null float64
SW 27736 non-null float64
VSH 27844 non-null float64
dtypes: float64(13), object(1)
memory usage: 3.0+ MB
使用列表式删除丢弃 nan
列表式删除,也称为事例删除,是处理不完整数据集的一种常见而方便的方法。该方法移除要素中有一个或多个缺失值的所有行(案例)。
在 Python 中,我们可以通过调用一个叫做dropna()的特殊函数来删除熊猫数据帧中缺失的值。这将从数据帧中删除任何 NaN(非数字)值。inplace=True参数允许我们从数据帧中物理删除这些值,而不必将其赋给一个新变量。
df.dropna(inplace=True)
如果我们调用df.info(),我们将看到我们的数据集已经减少到每列 27,491 个非空值。
<class 'pandas.core.frame.DataFrame'>
Int64Index: 27491 entries, 0 to 27844
Data columns (total 14 columns):
wellName 27491 non-null object
MD 27491 non-null float64
BS 27491 non-null float64
CALI 27491 non-null float64
GR 27491 non-null float64
NPHI 27491 non-null float64
RACEHM 27491 non-null float64
RACELM 27491 non-null float64
RHOB 27491 non-null float64
RPCEHM 27491 non-null float64
RPCELM 27491 non-null float64
PHIF 27491 non-null float64
SW 27491 non-null float64
VSH 27491 non-null float64
dtypes: float64(13), object(1)
memory usage: 3.1+ MB
摘要
现在,我们已经删除了丢失的值,我们可以进入下一步,即识别和处理异常值和坏数据。
这篇短文展示了可视化缺失数据的三种不同方法。第一种是使用 pandas 查询数据帧,第二种是使用 missingno 库,第三种是使用 matplotlib 创建自定义可视化。
最后,我们介绍了从数据帧中删除缺失数据的两种方法。第一种方法是丢弃变量,第二种方法是丢弃行中缺少的值。
本文中展示的例子说明了处理缺失值的基本工作流。在每个阶段都应该对数据进行彻底的质量控制,以确保其仍然符合目的。
感谢阅读!
如果您觉得这篇文章有用,请随时查看我的其他文章,这些文章介绍了 Python 和测井数据的各个方面。你也可以在 GitHub 找到我和其他人在本文中使用的代码。
如果你想联系我,你可以在LinkedIn或者我的 网站 找到我。
有兴趣了解更多关于 python 和测井数据或岩石物理学的知识吗?跟我上 中 。
https://andymcdonaldgeo.medium.com/membership
使用深度学习来量化、监控和清除海洋塑料
能够识别和量化全世界的地下塑料的通用物体检测器

我和我的团队一起参与了一个名为深度塑料的项目,这是一种利用深度学习识别海洋塑料的新方法,我想分享一下我的发现:
85%的平均精度!
使用我们的模型,我们现在可以平均检测海洋中 85%的表层塑料。我们基于名为 YOLOv5-S 的神经网络架构实现了这一级别的精度。下面我们附上了一段视频,其中包含我们运行模型的海洋塑料示例:
利用深度学习模型检测海洋塑料
文件和代码在哪里?
这篇文章将主要作为我们研究论文的无代码摘要。如果你想直接跳到神经网络或代码的本质细节,你可以在这里访问它们:
arxiv 预印本:https://arxiv.org/abs/2105.01882
Github 代码库:https://github.com/gautamtata/DeepPlastic
如果你想让我再写一篇解释代码、数据扩充等的博文。,在下面的评论里告诉我吧!
我是怎么卷进来的?
我一直对海洋充满热情。从加州州立大学蒙特利湾分校毕业后,我知道我想开发工具/软件来帮助保护海洋。在我收集、注释和管理数据以及模型之后,我意识到精度指标非常重要,可能有一份手稿可以在这里发表。因此,我向海洋塑料专家萨拉-珍妮·罗耶博士以及杰伊·劳伊·T21 和奥利维尔·波里恩博士寻求帮助,帮助撰写和出版手稿。
摘要
正浮力海洋塑料垃圾的量化对于了解垃圾如何在世界海洋中聚集以及确定急需清除垃圾的高浓度垃圾热点至关重要。
目前,量化漂浮塑料的最常见监测方法需要使用蝠鲼拖网。在分析之前,物理移除的需要会导致高成本,并需要大量的劳动力,从而阻碍了实时海洋塑料监测服务在海洋中的可扩展部署。如果没有更好的监测和采样方法,塑料污染对整个环境的总体影响,以及特定海洋区域内影响的细节,将仍然是未知的。
这项研究提出了一个自动化的工作流程,利用在海洋表层捕获的视频和图像作为输入,对海洋塑料进行实时量化,以实现精确量化和清除。
YOLOv5-S 是性能最好的车型,其平均精度(mAP)为 0.851,F1 得分为 0.89,同时保持接近实时的速度。此外,我们的方法可以利用“现成的”摄像设备和标准的低成本 GPU 来近乎实时地监测/量化表层塑料。
项目的目标
我们希望建立一个通用的物体探测器,能够识别和量化世界各地的地下塑料。
现在我们已经了解了我们的目标以及如何实现它,让我们进入工作流程。

检测海洋塑料的管道;作者照片
管理数据集
找到一个包含海洋废弃物注释图片的数据集非常困难。目前还没有关于海洋表层海洋塑料图像的数据集。所以,我决定创造一个。我买了一台 GoPro Hero 9、一套潜水服和浮潜设备,带着 2 个塑料袋和 2 个塑料瓶前往加利福尼亚的各个地方。
我去过的地方有:太浩湖,波德加湾,旧金山湾。在这里,我在 4K 拍摄了塑料的视频,然后将它们逐帧分解成图像。[在我完成视频拍摄后,所有使用的塑料都经过消毒并从环境中移除]最初的数据集有 10 万多张图像,然后我煞费苦心地逐一查看,选择了最好的图像,并使用 supervise.ly 对它们进行了注释。最终的数据集,加上从互联网上收集的图像,有 4000 张图像。
我尽力通过将物体埋在沙子里或放置在太阳对面来复制真实世界的场景,如遮挡和亮度。

(右下)照片由娜佳·贝尔托尔特·詹森在 Unsplash 上拍摄;(右上)照片由纳里曼·迈沙拉法在 Unsplash 上拍摄;(左上,右下)作者图片
数据格式编排
图像尺寸调整为 416x416,并转换为 Darknet 和 YOLOv5 PyTorch 要求的格式。
数据扩充
由于最终的数据集只包含 4000 张图像,我认为增加数据集大小的最佳方式是扩大它。所以,我用翻转,旋转,亮度来复制海洋环境。
我还使用了 B&W 来使模型不被颜色和剪切所影响,来模拟遮挡。
构建神经网络
构建神经网络是一项简单的任务。我选择模型有两个目标:模型必须有点准确,模型必须很快。足够快,可以用在浮标和无人机上。我试过很多型号,比如更快的 R-CNN,EfficientDet,SSD 等。,但坚持使用两种型号:YOLOv4-Tiny 和 YOLOv5-S。
对 YOLOv5 的代码运行感兴趣?请在下面的评论中告诉我或者联系我。
注意事项/调整超参数:
- 我使用了一个叫做 ADAM 的自适应学习率来设置一个衰减的学习率。
- 使用名为 W & B 的软件包(重量和偏差)并持续监控损失。
- 我使用了一个 softmax 作为最后一层,并且只使用了一个叫做trash_plastic 的类。
- 我用 paperspace.com 和谷歌 Colab pro 搭配英伟达 v100 GPU来训练模型。
- 使用在水下场景和深海残骸上训练的权重的迁移学习——jam stec JEDI 数据集。
所有用于模型的代码包括架构都可以在 YOLOv4 、 YOLOv5 找到
对我们的代码感兴趣吗?找到这里:https://github.com/gautamtata/DeepPlastic
结果
在对训练方法、数据扩充和微调超参数进行了大量实验后,我们最终达到了一个结果足够好的点,可以在现实世界的部署中使用。
最佳模型 YOLOv5-S :精度:96%,均值-均值-精度:85%,F1-得分:0.89,推理速度:2.1 毫秒/img。

(下排)照片由贾姆斯泰克绝地、(第二排第一张) ( 右下)照片由娜佳·贝尔托尔特·詹森在 Unsplash 上拍摄;作者提供的所有其他图像
下一步是什么?
目前,我们正在发表我们的论文。我们正试图让其他研究人员掌握这个模型,以测试和开发创新的方法来合成更多的数据。
如果你感兴趣,想投稿,或者想聊天,你可以在这里联系我:gautamtata.blog@gmail.com。
了解更多 arxiv 预印本:https://arxiv.org/abs/2105.01882
识别推特上的阿拉伯语政治错误信息
使用无监督的 NLP 主题建模和聚类来构建能够识别短文本文档中的错误信息的机器学习分类器

作者图片
TL;博士;医生
Twitter 等社交媒体平台正与政治错误信息作斗争。打击误传的一个主要挑战是缺乏带标签的数据集,特别是在低资源语言中。该项目提出了一种独特的主题建模混合方法,并将其应用于 3600 多万条阿拉伯语推文的数据集,这些推文是由 Twitter 标记为国家关联信息操作的一部分的用户发布的。混合主题聚类模型能够成功提取政治内容。由于技术限制和数据集的大小,应用进一步的聚类算法来识别政治内容,特别是错误信息还没有成功。
互联网——尤其是社交媒体——充斥着政治错误信息。除了在浏览你的推特信息时成为潜在的麻烦之外,这种错误信息也会在现实世界中产生巨大的反响,就像 1 月 6 日美国国会大厦的风暴一样。因此,对于政治、人道主义和情报部门的组织来说,发现政治错误信息和仇恨言论是一项重大挑战。打击误传的一个主要挑战是缺乏带标签的数据集,特别是在低资源语言中。
在与多个组织中涉及错误信息和仇恨言论检测的几个人交谈后,我决定使用我的跳板数据科学职业生涯的最后一个顶点项目来解决其中的一些问题。通过这个项目,我希望通过专门研究检测阿拉伯语推文中的政治错误信息所需的工具和过程,为该领域正在进行的研究做出贡献。
数据
在这个项目中,我使用了来自 Twitter 的透明中心的数据集,该数据集由 5350 个 Twitter 账户(以及所有 3600 多万条推文,其中 95%是阿拉伯语)组成,这些账户被 Twitter 认定为与国家相关的信息操作的一部分。
用推特自己的话来说,
“一个与沙特阿拉伯有关、在包括 KSA、埃及和阿联酋在内的多个国家运营的账户网络,正在放大赞扬沙特领导的内容,并批评卡塔尔和土耳其在也门的活动。共有 5350 个账户被删除。”
数据下载于 2021 年 2 月 26 日。
方法
这里需要注意的重要一点是,账户被认定为“受损”,而不是其每一条推文本身。这意味着并非所有 3600 万条推文实际上都包含政治错误信息。
当然,一个账户早上在推特上赞扬沙特王室,下午放大可爱猫的模因,这是完全合理的。尽管这两者不一定是相互排斥的。

图片来自 9gag.com
诀窍是找出如何将 3600 多万条推文浓缩成实际的政治错误信息内容。
在这一点上,真正让我着迷的问题是,是否有可能使用无监督学习方法来筛选数据集中的 3600 多万条推文,以便成功地检测出那些特别是政治错误信息的推文。虽然雄心勃勃(异想天开?),一个能够成功做到这一点的模型将是对该领域的一个巨大贡献,因为输出可以被输入到机器学习分类器中,而机器学习分类器又可以用于对其他看不见的数据进行分类。本质上,我们将构建一个半监督学习解决方案,它将绕过对大型标签数据集的需求。
从概念上讲,这意味着我想建立一个漏斗,从:

作者图片
这也成为我构建工作流程的一种非常直观的方式:

作者图片
- 争论:清理数据,只获取独特的推文。
- 主题建模:对独特的推文进行 NLP 驱动的分类,筛选出所有工作日结束时的猫迷因和其他垃圾,只获得政治内容。
- 聚类:应用无监督学习方法,进一步区分政治内容和政治 误传 。
- 分类:使用输出构建一个 ML 分类器。
扯皮
除了通常的 NLP 数据争论步骤,如删除网址,表情符号,标签,重复字符等。这个项目还涉及一些特定于阿拉伯语自然语言处理的预处理。在之前的帖子中,我提供了关于处理阿拉伯文本的特殊挑战的深入教程和解释…以及如何使用多功能的 camel-tools Python 包来克服它们。
在删除所有非阿拉伯语推文(~3%)和所有重复内容(即转发推文)后,我们剩下615 万条独特的阿拉伯语推文。
一些基本数据
在我们继续查看这 615 万条独特的推文是关于什么的之前,有必要提一下关于数据集中推文行为的一些基本信息。
数据集包含:
- 3530 万条阿拉伯语推文
- 4,273 个独立用户
- 2010 年 2 月 15 日至 2020 年 1 月 22 日之间发布的推文(2018 年和 2019 年的推文最多)****
查看用户之间的推文、关注者和追随者的分布(在完整的 3600 万数据集中),我们注意到一些非常不均衡的模式,例如:
- 每个用户的关注者数量:从 0 到 120 万不等,中位数为 100。
- #每用户关注:范围从 0 到 877k,中位数为 228。
- 最受关注的前 1%用户发布了3500 万条推文中的近一半
- 每个用户的推文:范围从 100 万到 140 万( ~整个数据集的 4.2%)—中位数:203
只看独特的推文,我们看到:
- 610 万条独特的推文
- 超过 400 万条独特的推文在数据集中只出现一次
- 78 条独特的推文出现超过 10,000 次
- 这些 78 条独特的推文一起构成了数据集的 4.5%(超过 150 万条推文)
所有这些都表明极度放大:少数用户和推文在数据集中被过度呈现,淹没了“正常”的推文行为。
仔细观察前 100 条最常出现的独特推文,也发现了大量非政治内容。以下是前 100 条推文中最常见的词汇:

作者图片
如你所见,在这里很难找到太多明确的政治内容。这或许可以为一种将政治错误信息淹没在普通“垃圾”中以避免被发现的策略提供依据。
经过一些进一步的 EDA 风格的挖掘,我可以自信地确定 3 个主要的内容集群:
- 宗教的
- 商业
- 政治的
主题的建模
这种三分法有助于指导我的下一步工作:使用主题建模从 615 万条独特的推文转移到具体的政治内容。为了筛选出政治内容,我使用了两种不同的主题建模方法:潜在狄利克雷分配 (LDA)和吉布斯抽样狄利克雷多项式混合 (GSDMM)。
虽然 LDA 是目前最流行的主题建模方法,但它假设每个文档有多个主题。另一方面,GSDMM 是专门为短文本聚类而构建的,并且假设每个文档只有一个主题。鉴于我们的文件是最大的。140 个字符长,假设它们只包含一个主题似乎是合理的。
注意:在 的一篇附带文章 中,我列出了 LDA 和 GSDMM 的利弊,还提供了帮助您开始使用 GSDMM 的材料链接。
我对每个模型进行了多次迭代,调整它们的超参数来比较它们的性能。毫无疑问,GSDMM 模型在识别连贯主题和提取特定政治主题方面做得更好。以下是 GSDMM 模型确定的一些主题的文字云:

宗教内容,作者形象

金融服务内容,作者图片

家政服务内容,图片作者

内容露骨,图片作者

政治内容(“我的父母”是“平民”的误译),作者图片
但是…
GSDMM 模型只能在包含 10 万条推文的数据子样本上运行时才能做到这一点。当我试图将 615 万条唯一推文的完整数据集传递给 GSDMM 模型时,该模型要么失败,要么需要几天时间才能运行。
由于 GSM DMM 无法并行化(然而… 本文似乎暗示有一个版本正在开发中,可以)我最终设计了一个解决方案,从最佳 LDA 模型中提取输出——总共 350,000 条,姑且称之为“主要是政治性的”推文——并将其输入我们的 GSM DMM 模型,进行最后一次筛选,从中剔除垃圾。这非常有效,产生了 285,000 条“纯政治”推文的连贯子集,分为 4 个子主题。
大集群
到目前为止,我对结果非常满意。主题建模非常有效,我已经为下一步做好了准备,尽管这是项目中最雄心勃勃的一步:使用无监督学习将政治推文分解为具体的“中立”和“错误信息”内容。
这里的想法是超越只看推文的文本内容,并在混合中添加一些功能,可能有助于区分“中立”和“错误信息”的政治内容。

作者图片
这些新功能包括以下方面的数据:
- 写这条推文的方言(可以使用 camel-tools 库推断出来)
- 推文的情绪:积极、消极还是中立
- 总结发布推文的用户的特征的多个特征,例如:该用户创建的推文的总数、该用户创建的具有相同时间戳的推文的数量(作为类似机器人行为的潜在指示),以及关注者/追随者的数量。
生成方言功能有一个有趣的副作用,就是为我们提供了关于该数据集中推文地理分布的额外数据。下面的地图显示了 26 种方言中每一种的推文数量。这些方言是根据它们最常用的主要城市命名的。当然,一种方言在比一个城市大得多的地区使用。也不能保证一个用黎巴嫩方言写作的人真的是从黎巴嫩发推特。所以虽然我们应该带着一粒(一勺!)的盐,这里仍然有一些关于这个数据集中的用户的地理和人口统计的有用信息。**

作者图片
在设计了新的特性之后,我试图通过一些不同的聚类算法来运行新的数据框架:K-Means、t-SNE 和层次聚类。不幸的是,此时我遇到了我正在使用的库的技术限制,以及项目的时间限制。由于数据集的大小(285k 行 10K+特征),以及无法有效地并行聚类算法,我还没有(到目前为止!)能够通过有意义的聚类算法运行整个数据集。*
然而,我确实设法通过 t-SNE 聚类运行了 5%的子样本。这揭示了大量的小集群,它们似乎被 GSDMM 主题标签有效地定义了,如下面的截图所示。

作者图片
有点令人沮丧;而且也是数据科学项目在技术上和实践上的现实局限性的坚实的一课。不过,我仍然认为这种方法值得探索,我渴望花更多的时间来尝试弄清楚在分布式集群中运行这些聚类算法的细节,并探索有意义的方法来区分这两种类别(错误信息与否)。
外卖
该项目在当前状态下(至少)提供了以下 4 点启示:
- 阿拉伯语自然语言处理呈现了它自己的一系列挑战……这些挑战需要深入研究,但肯定是可以克服的,尤其是在 camel-tools 包的帮助下。
- 阿拉伯推文的主题建模很有效…而且非常有效!****
- 在处理短文本文档时,选择 LDA 而不是 GSDMM 的利弊是相当大的……但是也可以将它们结合起来,从而两全其美。
- 分布式处理的世界在某些方面确实是一个不同的球赛,并提出了许多额外的挑战,需要额外的时间和勇气来克服。
下一步是什么?
展望未来,我想至少用三种方式来扩展这个项目:
- 进一步探索聚类方法,包括主成分分析、分布式 t-SNE /光谱
- 与 Python 社区就扩展 GSDMM 以分布式模式运行的可能性进行交流
- 广泛的网络分析,以检测用户交互的模式,并探索这些信息操作的归属到网络中特定节点的可能性。****
如果你正在阅读这篇文章,并有一个想法或一些建设性的意见,请在下面的评论中分享。你可以在这里找到完整的 Jupyter 笔记本。
!إلى اللقاء
确定疫情后最有可能遭受衰退的地区
使用这种方法,查看后 covid 衰退将在哪些方面产生更大影响

作者图片
由于目前围绕新冠肺炎疫情的不确定性水平,即将到来的经济衰退的影响几乎不可能预测;然而,历史告诉我们某些社会人口统计、社会经济、教育和就业因素会让某些人群面临经济危机更严重的后果。这一次,与 2008 年不同,经济衰退将在全球疫情之后到来。世界各国政府采用的非药物干预措施(NPIs);例如关闭学校、远程工作政策以及限制国内和国际流动的就地安置(封锁)战略,对零售业和旅游业等某些经济部门造成了额外的破坏。这也转化为一个额外的风险因素,对于那些靠这些受危害行业的商业活动所产生的经济支持谋生的个人来说。
基于一系列恶化的社会人口和社会经济因素,这项研究试图确定西班牙哪些地区面临即将到来的经济衰退的风险更高。为了帮助建立风险模型,使用了一组与这些地区的人口和经济活动相关的指标。

数据集
在这项研究中,我们与我们的数据合作伙伴 Unica360 合作,并利用了他们在我们的数据观测站中提供的一些数据产品。
我们从现有的不同数据集中选择具体的恶化指标的理由如下:
- 社会人口统计:老年人口较多的地区、低收入地区年轻人较多的地区、来自国内生产总值较低国家的外国人口较多的地区,以及平均收入低于全国平均水平的地区。
- 地籍:办公、商业和工业物业较多的地区,由于 NPI 措施,这些地区的活动可能会减少。
- 商业指数:旅馆业、文化业和零售业较多的地区,由于 NPI 措施,这些地区的活动可能有所减少。
- 旅游业:游客预期较高的地区,尤其是来自外国的游客,他们可能受到国际旅行限制和减少的影响。
- 工作人群:在办公室工作的员工较多的地区,可能由于疫情期间远程工作的增加而受到影响。
值得注意的是,所有这些数据集都是在覆盖全国的 100x100m 网格上提供的。



方法学
风险指数是根据文章" 2011 年西班牙各地区贫困指数"中概述的方法计算的。
该方法使用主成分分析 (PCA)构建了一个指数,允许我们通过捕获结果主成分中的大部分方差来降低问题的维度。
在应用 PCA 之前,我们需要对输入数据进行一些预处理。首先,我们删除列中没有充分通知条目的行,然后填充最后一个行子集中缺少的值。为了实现这一点,我们使用了该行数据所属的自治市的中值。一旦输入数据集中没有缺失值,我们就对数据进行标准化(这是运行 PCA 的必要步骤)。
使用标准化数据,我们然后检查每对可能的列的 Spearman 相关性,如果它们显示 Spearman 相关性高于 0.8,则删除一个。这相当于两列提供相同的信息(和方差),意味着我们可以安全地删除其中一列。然后我们应用 PCA,只保留第一个成分(这将是我们的风险指数)。
最后,我们对照主成分分析的第一个成分检查每个协变量的 Spearman 相关性,去除相关性小于 0.4 的那些。我们执行此步骤是为了最大化第一个组件捕获的方差。然后,我们用得到的协变量重新计算 PCA,提供我们的最终指数。
在下图中,我们可以看到在第一轮 PCA 中考虑的不同特征(已移除相关特征),以及每个特征与剥夺指数的相关性。这让我们感觉到了模型中特性的重要性。标有蓝色的是用于第二轮 PCA 的输入特征。

作者图片
接下来,我们为指数值计算一组聚类,以便我们可以将每个区域标记为受即将到来的衰退影响的风险低、低-中、中、中-高或高。为此,我们对指数值应用自然间断点(jenks)。这最小化了同一聚类的元素之间的差异,并且最大化了其他聚类的元素之间的差异。在下图中,我们可以看到通过计算自然间断点得出的不同风险类别的箱值。

作者图片
分析结果
对马德里不同区域的结果进行高层次分析,我们可以看到风险较高的区域(高或中高)是 M30 轨道高速公路内以及南部和西南部的邻近区域。
另一方面,被划分为低风险或中低风险的细胞密度较高的区域位于北部和西北部的 Aravaca 和 Chamartin 等社区。
通过仔细查看属于“高”和“低”风险类别的要素的分布,并将它们的分布与城市中的所有像元进行比较,我们可以更好地了解每个聚类中像元的特征。例如,下图显示了“高风险”类别中每个要素的分布与整个马德里市要素的总体分布之间的比较。

作者图片
我们可以清楚地看到,“高风险”细胞在最后一组恶化因素中的每个特征都具有更高的值。这意味着“高风险”细胞往往具有:
- 更多的旅馆和零售场所
- 更多的老年人口居住在该地区
- 更多的人在该地区的企业工作
- 国际游客数量增加
如果我们查看“低风险”单元,也会发生同样的情况,在这些单元中,分布趋向于正偏,表明这些单元的值比其余区域的值低。

作者图片
看看另一个城市,例如塞维利亚,我们可以看到高风险值分布在两个明显分开的区域:市中心和特里亚纳附近。这两个区域的酒吧和餐馆密度较高,在正常旅游条件下,预计外国游客会较多。
此外,我们还发现,绝大多数具有低风险指数和中低风险指数的像元都位于市中心周围的区域,这些区域的居民较多,游客较少。
结论
如导言中所述,本研究的目的是帮助确定西班牙哪些地区面临即将到来的经济衰退的风险较高。
鉴于当前全球的不确定性,以及 NPI 和政府经济反应的频繁和快速变化,准确预测衰退将如何以及在哪里产生最大影响是极其困难的。因此,为了做出更明智的预测,我们利用了参考最近其他经济衰退的研究的共性。
这项研究还提供了一个很好的例子,说明如何结合不同数据来源的指标,以建立一个派生指数。
本文原载于 CARTO 博客。
在 Scribd 识别文档类型
行业笔记
我们如何在多组件机器学习系统中开发一种将语义理解与用户行为相结合的方法。
用户上传的文档从一开始就是 Scribd 业务的核心组成部分;理解文档语料库中的实际上是什么,将为发现和推荐带来激动人心的新机遇。有了 Scribd,任何人都可以上传和分享文档,类似于 YouTube 和视频。多年来,我们的文档库变得越来越大,越来越多样化,这使得理解变得越来越困难。在过去的一年中,应用研究团队的任务之一是提取关键文档元数据,以丰富下游发现系统。我们的方法在多组件机器学习系统中结合了语义理解和用户行为。
这是一系列博客文章的第 1 部分,解释了在构建这个系统时遇到的挑战和解决方案。这篇文章展示了在开发一个模型来分类任意用户上传的文档时遇到的限制、挑战和解决方案。你可以在 Scribd 技术博客查看这篇文章的原始版本以及更多内容!
初始约束
Scribd 的文档集在内容、语言和结构方面延伸得很远。任意文档可以是任何内容,从数学作业到菲律宾法律到工程图表。在文档理解系统的第一阶段,我们想要利用文档中的视觉线索。这里使用的任何模型都必须是语言不可知的,才能应用于任意文档。这类似于人类的“第一眼”,我们可以快速区分漫画书和商业报告,而无需阅读任何文本。为了满足这些要求,我们使用计算机视觉模型来预测文档类型。但是什么是“类型”呢?
识别文档类型
这是一个必须要问的问题,但也是一个很难回答的问题——我们有什么样的文件?正如上一节提到的,我们感兴趣的是基于视觉线索区分文档,比如重文本、电子表格和漫画。我们对小说和非小说之类的更细致的信息还不感兴趣。
我们应对这一挑战的方法是双重的。首先,与 Scribd 的主题专家讨论他们在语料库中看到的文档类型。这在过去和现在都是非常有益的,因为他们拥有特定领域的知识,我们可以利用这些知识进行机器学习。第二个解决方案是使用数据驱动的方法来浏览文档。这包括根据文档的用途为文档创建嵌入。在交互式地图上聚集和绘制这些嵌入允许我们检查不同群中的文档结构。结合这两种方法推动了文档类型的定义。下面是我们用来探索语料库的其中一个地图的例子。

图 1:从用户交互嵌入中构建的文档语料库图。在以后的文章中会有更多关于这种方法的内容。
我们集中在 6 种文档类型上,包括乐谱、重文本、漫画和表格。更重要的是,这 6 个类并没有包含我们语料库中的所有文档。虽然在文献中有许多不同的方法来处理分布外的例子,但是我们的方法明确地向模型中添加了一个“other”类并训练它。在接下来的章节中,我们将更多地讨论它的直觉、问题的潜在解决方案以及面临的挑战。
文件分类
正如引言中提到的,我们需要一种与语言和内容无关的方法,这意味着相同的模型将适用于所有文档,无论它们包含图像、文本还是两者的组合。为了满足这些约束,我们使用计算机视觉模型来分类各个页面。然后,这些预测可以与其他元数据(如页数或字数)相结合,以形成对整个文档的预测。
收集带标签的页面和文档
在模型训练开始之前,我们面临一个有趣的数据收集问题。我们的目标是对文档进行分类,因此我们必须收集带标签的文档。然而,为了训练上面提到的页面分类器,我们还必须收集带标签的页面。天真地说,收集带标签的文档并为其每个页面使用文档标签似乎是合适的。这并不合适,因为一个文档可以包含多种类型的页面。作为一个例子,考虑这个文档的中的页面。

图 2:来自同一文档的三个不同页面,展示了为什么我们不能获取文档标签并将其分配给每个页面。
第一页和第三页可以被认为是重文本,但绝对不是第二页。把这份文件的所有页面都标上重文本的标签会严重污染我们的训练和测试数据。同样的逻辑也适用于我们的 6 个班级。
为了应对这一挑战,我们采取了主动学习的方法来收集数据。我们从每个类别的一小组手工标记的页面开始,反复训练二元分类器。二分类问题比多分类问题简单,需要较少的手动标记数据来获得可靠的结果。在每次迭代中,我们评估模型的最有信心和最没有信心的预测,以了解其归纳偏差。根据这些判断,我们为下一次迭代补充了训练数据,以调整归纳偏差,并对最终的模型和标签充满信心。活页乐谱课是调整归纳偏差的一个典型例子。下面是一个页面示例,如果模型知道乐谱是任何带有水平线的页面,则该页面会导致乐谱错误分类。在每次迭代中补充训练数据有助于消除这样的归纳偏差。

图 3:由于错误的归纳偏差导致的可能的乐谱错误分类的例子
在为每个类创建了这些二进制分类器之后,我们就有了一大组可靠的标签和分类器,如果需要的话,它们可以用来收集更多的数据。
构建页面分类器
页面分类问题与 ImageNet 分类非常相似,因此我们可以利用预先训练的 ImageNet 模型。我们在 fast.ai 和 PyTorch 中使用迁移学习来微调页面分类器的预训练计算机视觉架构。经过最初的实验,很明显,具有非常高的 ImageNet 准确性的模型,如 EfficientNet,在我们的数据集上并没有表现得更好。虽然很难准确指出为什么会这样,但我们相信这是因为分类任务的性质、页面分辨率和我们的数据。
我们发现 SqueezeNet ,一个相对成熟的轻量级架构,是准确性和推理时间之间的最佳平衡。因为像 ResNets 和 DenseNets 这样的模型太大了,它们需要花费大量的时间来训练和迭代。然而,SqueezeNet 比这些模型小一个数量级,这在我们的训练方案中开辟了更多的可能性。现在,我们可以训练整个模型,而不局限于使用预训练的架构作为特征提取器,这是较大模型的情况。

图 4:摘自论文的 SqueezeNet 架构。左:SqueezeNet 中:带简单旁路的挤压网;右图:带复杂旁路的 SqueezeNet。
此外,对于这个特定的模型,低推理时间是对数亿个文档运行它的关键。推理时间也与成本直接相关,因此最佳的成本/收益比需要显著更高的性能来证明更高的处理时间是合理的。
用于文档分类的集合页面
我们现在有一个模型来对文档页面进行分类,并需要使用它们来确定对文档的预测,并希望将这些分类与附加元数据(如总页数、页面尺寸等)结合起来。然而,我们在这里的实验表明,页面分类的简单集合提供了非常强的基线,很难用元数据来击败。
为了提高效率,我们从文档中抽取 4 页样本进行集成。这样我们就不会遇到处理数千页文档的问题。这是基于分类器的性能和文档语料库中的页面分布选择的,这从经验上验证了我们的假设,即这个样本大小合理地代表了每个文档。
错误分析和过度自信
在对来自生产的大量文档样本进行错误分析后,我们发现一些类返回了过于自信但错误的预测。这是一个非常有趣的挑战,也是最近学术研究激增的一个挑战。具体来说,我们发现有超过 99%的置信分数被错误预测的文档。这样做的一个主要后果是,它否定了为提高精度而设置模型输出阈值的有效性。
虽然有不同的处理方法,但我们的方法包括两个步骤。首先,我们利用了前面提到的“other”类。通过向“其他”类添加许多这些对立的、非分布的示例,并重新训练模型,我们能够在不改变模型架构的情况下快速改进度量。第二,这对某些阶层的影响比其他阶层更大。为此,建立了独立的二元分类器来提高精度。
我们将何去何从?

图 5:整个文档理解系统的示意图。红盒子就是我们在这篇文章中谈到的
现在我们有了一个基于视觉线索过滤文档的模型,我们可以为每种文档类型构建专用的信息提取模型——乐谱、重文本、漫画、表格。这正是我们从这里开始的方式,我们从大量文本文档中提取信息开始。
本系列的第 2 部分将深入探讨我们的团队在构建这些模型时遇到的挑战和解决方案。如果你有兴趣了解更多关于应用研究正在解决的问题或围绕这些解决方案建立的系统,请查看我们的空缺职位!
参考
用 Zingg 识别雪花中的重复项
使用开源来获得正确的维度表!
众所周知,仓库中的客户表很乱,多条记录指向一个客户。这个问题可能有多种原因,包括来自线下商店和线上渠道的数据、客人结账、多个内部客户系统(如 CRMs 和客户服务)...事实上,这个问题不仅仅局限于客户表。供应商表、位置和其他非事务性数据(传统仓库语言中的维度)也有同样的问题。
对于我们这些依赖仓库进行业务决策的人来说,这些副本可以完全抛弃我们的度量标准。如果我们的客户表没有一个可靠的客户 id,我们怎么能相信终身价值呢?细分、营销归因、个性化——没有可靠的维度数据,我们能实现什么?反向 ETL 如何——将这些数据输入我们的运营系统。如果我们用仓库里的副本来填充我们的系统,难道不会打乱我们的日常工作吗?
如果我们仍然不能正确地识别我们的核心实体,那么对仓库的投资是否值得呢?
从表面上看,这似乎是一个容易解决的问题——我们肯定有可以利用的电子邮件 id 吧?不幸的是,人们使用工作,个人,学校和其他电子邮件 id,虽然这是一个开始,但这并不能解决问题。让我们甚至不要从我们在网络和打印表单上输入姓名、地址和其他细节的不同方式开始。
让我们看看雪花中的客户表。表格中的数据是从这个 csv 加载的。

作者图片
客户表确实有一个 SSN 列,但是在许多情况下这是不一致的,所以我们不能依赖它。该表确实有标识符列,但是它仍然有多个属于同一客户的不同 id 的记录。
例如,检查属于客户 Thomas George 的以下两条记录

作者图片
或者以下五条记录都属于客户 Jackson Eglinton

作者图片
我们可以构建一些相似性规则,并使用 SQL 或编程来构建我们的标识符并匹配这些记录。然而,这将很快变得复杂,以迎合上述变化。如果我们使用雪花的编辑距离功能会怎么样?或者 fuzzywuzzy 或者类似的图书馆?不幸的是,我们在这里面对的是一头野兽——知道要比较哪些对或者找到编辑距离实际上是非常重要的,否则我们将会在多个属性上得到一个笛卡尔连接。!).
作为一个例子,看看当我们的记录数量增加 10 倍或 100 倍时,我们可以遇到的比较数量。此表假设我们比较的是单个属性。因此,很明显,可伸缩性绝对是一个巨大的挑战,需要认真规划。

作者图片
幸运的是,开源有一个解决方案(什么时候没有?).看起来有一个叫做 Zingg 的工具,专门用来解决这个实体解析的问题。(此处需要放一个免责声明,我是作者:)
让我们看看如何使用 Zingg 来解析我们的客户并识别重复项。
安装很简单,我们需要 Java、Apache Spark 和 Zingg 的二进制文件。如果您不是 Java 程序员或在 Pb 级集群上编写 Spark 程序的分布式编程极客,请不要害怕。Zingg 在幕后使用这些技术,因此对于大多数实际用途,我们可以在一台笔记本电脑或机器上工作。Zingg 是一个基于学习的工具,它根据我们的数据进行训练,并且不向外部方传输任何内容,因此当我们在自己的环境中运行 Zingg 时,安全性和隐私会得到自动保护。
我们需要告诉 Zingg 我们的雪花数据在哪里。为此, Zingg 配置是用我们的雪花实例和表细节设置的。下面是 Snowflake 中输入客户表的配置摘录。

作者图片
我们还配置 Zingg 将输出写入 UNIFIED_CUSTOMERS 表。这个表在 Snowflake 中还不存在,但是 Zingg 会在编写输出时创建它,所以我们不需要构建它。

作者图片
现在让我们指定哪些属性用于匹配,以及我们需要哪种类型的匹配。例如,名字属性是为模糊匹配类型设置的。

作者图片
我们不希望使用 SSN 进行匹配,这样我们就可以看到匹配执行得有多好,所以我们将该字段标记为 DO_NOT_USE。配置的其他部分相当样板,你可以在这里查看整个配置。
Zingg 基于训练样本学习匹配什么(尺度)和如何匹配(相似度)。它配有一个交互式学习器,可以挑选出有代表性的样本对,用户可以将其标记为可接受的匹配或不匹配。现在让我们构建训练样本,Zingg 将从中学习。我们将配置传递给 Zingg,并在 findTrainingData 阶段运行它。这是一个简单的命令行执行。
zingg.sh --phase findTrainingData --conf examples/febrl/configSnow.json
在引擎盖下,Zingg 在 findTrainingData 期间做了大量工作,以找出正确的代表性配对,从而为匹配建立训练数据。不确定对被写入 zinggDir/modelId,这是通过输入 json 配置的。但是我们不需要担心这个。一旦工作完成,我们将进入下一个阶段,然后标记或贴标签给线对。
zingg.sh --phase label --conf examples/febrl/configSnow.json
上述阶段将启动交互式学习器,该学习器读取由 findTrainingData 阶段完成的工作,并向我们显示要标记为匹配或不匹配的记录对。这有助于 Zingg 构建出为我们的数据量身定制的机器学习模型。这是它看起来的样子

作者图片
Zingg 选择不同类型的配对——绝对不匹配、确定匹配以及可疑情况,以便建立一个健壮的训练集。这些记录是在对输入进行非常严格的扫描后选择的,这样可以进行适当的归纳,并且属性之间的每个变化都不必由用户手工标记。作为一个例子,下面是我们的数据的 Zingg 输出的摘录。

作者图片
将查找训练数据和标记的阶段重复几次,直到标记出 30-50 个阳性对。这应该足以训练 Zingg 以合理的准确度运行数百万条记录。每一个案例都不需要输入 Zingg,学习者会自动选择代表并通过它进行归纳。当不确定的时候,你可以停止学习,检查 Zingg 的输出,然后回来再多训练一点。
在我们只有 65 个例子的简单例子中,一轮 findTrainingData 和 label 就足够了,所以我们在这里暂停。现在我们有了带标签的训练数据,我们通过调用训练阶段来构建机器学习模型。在内部,Zingg 进行超参数搜索、特征加权、阈值选择和其他工作,以建立一个平衡的模型——一个不遗漏匹配(召回)的模型,一个不预测错误匹配(精度)的模型。
zingg.sh --phase train --conf examples/febrl/configSnow.json
以上将保存模型,我们可以将它们应用到这个和任何其他新数据来预测匹配。只要模式、要匹配的属性和输入格式保持不变,就不需要重新训练。
现在,让我们将模型应用于我们的数据,并预测哪些记录确实是匹配的——或者彼此重复。
zingg.sh --phase match --conf examples/febrl/configSnow.json
上面的代码运行后,我们可以看到已经创建了一个包含以下各列的新表。

作者图片
Zingg 复制了原始数据,但是在输出的每一行中添加了 3 列。
- Z_CLUSTER 列是 Zingg 给出的客户 id——匹配或重复的记录获得相同的分类标识符。这有助于将匹配的记录分组在一起。
- Z_MINSCORE 列是一个指示器,指示该记录与分类中的任何其他记录最少匹配
- Z_MAXSCORE 是与聚类中的另一个记录最匹配的记录的指示器。
让我们看看输出中客户 Thomas George 的记录。两个记录获得相同的 z_cluster。没有其他记录获得相同的 id。分数也很好,这意味着我们对这场比赛充满信心。

作者图片
顾客杰克逊·艾灵顿怎么了?下面是输出的样子

作者图片
同样,这 5 条记录的标识符不同于表中的其他记录。在检查分数时,我们看到两个记录的最小分数接近 0.69,这意味着属于该聚类的这些记录的置信度较低。正确的做法是,在一个例子中,交换了街道和地址属性。在另一个例子中,姓氏不同于集群中的其他记录。
根据我们的数据,我们可以决定如何使用提供的分数。我们可以选择任何一个分数的截止值,以确保匹配,并将其余部分传送到另一个工作流——可能是人工审查。如果我们的情况允许的话,我们可以取分数的平均值。
在最有可能的场景中,Zingg 的输出在数据管道中被用作实体数据的最终来源。Zingg 的输出要么被 DBT 选中用于转换,要么被传输到湖边小屋用于数据科学。
在任何一种情况下,维度都是准确的,并且我们有一个可以信任的核心实体的统一视图。
确定全球特征与 SHAP 值的关系
如何计算要素的全局信息,并将其用于不可知的要素选择过程 x

沙哈达特·拉赫曼在 Unsplash 上拍摄的照片
现在,使用 SHAP 值是解释机器学习模型和理解数据特征与输出之间关系的最常用方法之一。
SHAP 值的最大优势之一是它们提供了局部可解释性:我们可以看到每个特性如何影响每个实例的结果。
然而,这种能力是有代价的:使用 SHAP 值进行特征选择并不那么简单。通常根据要素的平均 SHAP 值或最大 SHAP 值来截断要素,但是,这些方法并不能保证不会从数据集中移除某些重要的要素。直到今天,对于如何使用它进行特征选择还没有达成共识。
另一种方法是根据对数据集的百分比影响来定义要素重要性。然而,这需要大量的微调,而且不够标准化,不能以不可知的方式使用。
在本帖中,我们将研究一篇论文 [1】,该论文提出了一种使用 SHAP 值寻找全局解释的方法,这种方法使我们能够识别我们的要素之间的收敛程度,从而更好地理解数据集并提供更好的要素选择方法。
这篇文章的所有代码都可以在 Kaggle 上的笔记本中找到,也可以在我的 Github 上找到。
SHAP 快速概览
首先,让我们回顾一下 SHAP 价值观和 SHAP 图书馆。这个想法不是解释方法的内部工作,而是回顾对库中结果的解释,并理解如何获得它们。
当我们从一对模型+数据集获取 SHAP 值时,我们会得到一个 NxM 矩阵,其中 N 是数据集实例的数量,M 是要素的数量。我们拥有与原始数据集相同的形状。
该矩阵中的每个 I,j 条目表示特征 j 对实例 I 的预测的影响。这允许我们在局部水平上解释我们的模型如何基于特征进行预测。
如果获取模型的平均预测值,并将其与 SHAP 值矩阵中每一行的总和相加,您将获得数据集每个实例的准确预测值。
这是我们将要使用的 SHAP 的基本结构。
另一个非常重要的结构是 SHAP 相互作用矢量。
SHAP 相互作用矢量
两个要素之间的 SHAP 交互矢量定义了预测中这些要素之间的交互。为了计算它,当 j 存在和 j 不存在时,计算特征 I 的 SHAP 值。对所有可能性进行置换会生成交互向量。
如果我们将特征 I 的 SHAP 向量定义为包含每个样本的特征 I 的 SHAP 值的向量,则我们知道:

其中 pij 是特征 I 和 j 之间的 SHAP 相互作用矢量
正如我们将在下一节中看到的,这些概念很重要,因为所描述的方法有一个真正强大的几何解释,这将帮助我们理解正在发生的事情。
首先,让我们在空间上绘制这两个向量,并从那里开始构建:

SHAP 矢量和 SHAP 相互作用矢量。图片由作者根据图片[1]提供
协同作用
协同具有测量一个特征如何受益于数据集中另一个特征的直觉。如果两个要素高度协同,那么它们都应该出现在数据集上,因为它们彼此帮助很大。
对此的几何解释是,我们可以利用 pi 向量在 pij 向量上的投影来创建协同向量,该向量可以被翻译为以下等式:

视觉上我们得到:

协同矢量。图片由作者根据图片[1]提供
然后,我们可以通过计算该投影的长度来生成这些特征之间的协同值:

既然我们定义了这个向量,我们基本上是在说:给定我的特征 I 的预测能力,它有多少来自与特征 j 的相互作用?如果我们回答了这个问题,那么我们就知道有多少预测能力不是来自于此。
考虑到这一点,我们可以将这两个特征之间的自主向量定义为向量减法:

从几何学上讲,我们可以看到,协同(来自交互的内容)和自主(不来自交互的内容)的总和将合计为原始特征向量:

自主向量。图片由作者根据图片[1]提供
裁员
冗余的直觉是来自特征 I 的信息量被复制到特征 j 上。两个完全冗余的特征将具有完全相同的信息。一个例子是开尔文和摄氏度的温度。
我们可以通过将从 I 到 j 的自治向量投影到从 j 到 I 的自治向量来测量共享了多少信息。这在代数上转化为:

视觉上,我们有下面的图像。请注意,相对于 aij 和 pij 向量,aji 向量属于另一个平面:

冗余向量。图片由作者根据图片从[1]
然后,正如我们对 synergy 所做的那样,我们可以通过计算投影的长度,从这个向量生成一个值:

独立性ˌ自立性
最后,我们将定义最后一条信息:给定 I 中与特征 j 中的信息不协同或不冗余的信息,我们独立于特征 I。
这可以计算为特征之间的自主性和冗余性之间的差异:

正如我们对其他特征所做的那样,我们可以通过计算这个向量在π上的投影长度来计算标量值:

特征编码
现在,让我们深入一些编码,看看我们如何使用来自 SHAP 库的 SHAP 值结果来生成这些值。
对于这个例子,我们将使用来自 UCI 知识库的葡萄酒数据集,它是免费使用的,是从 sklearn 包的一个函数中获取的。然后,我们将应用随机森林分类器。
首先,让我们导入所需的库:
import shap
import numpy as np
import pandas as pdfrom sklearn.datasets import load_wine
from sklearn.ensemble import RandomForestClassifier
现在,让我们在数据集上安装分类器:
# Get the dataset and fit a Random Forest on it
X, y = load_wine(return_X_y=True, as_frame=True)rf = RandomForestClassifier()
rf.fit(X, y)
现在,我们可以使用 SHAP 库来生成 SHAP 值:
# Runs the explainer on the model and the dataset to grab the Shap Values
explainer = shap.Explainer(rf)
shap_values = explainer(X)
当我们执行上面的代码时,SHAP 库返回三个矩阵,所以我们将选择 SHAP 矩阵:
# The return of the explainer has three matrices, we will get the shap values one
shap_values = shap_values.values[:, :, 0]
现在,让我们生成相互作用值,以生成 SHAP 相互作用矢量:
shap_interaction_values = explainer.shap_interaction_values(X)[0]
我们现在将定义一些零矩阵来填充我们的计算。这不是最快的方法,但是,这样做是为了更有启发性:
# Define matrices to be filled
s = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))
a = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))
r = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))
i_ = np.zeros((shap_values.shape[1], shap_values.shape[1], shap_values.shape[0]))S = np.zeros((shap_values.shape[1], shap_values.shape[1]))
R = np.zeros((shap_values.shape[1], shap_values.shape[1]))
I = np.zeros((shap_values.shape[1], shap_values.shape[1]))
我们为每个向量定义了一个矩阵,为每个标量值定义了一个矩阵。现在,让我们迭代每个 SHAP 值(想象 I 和 j 上的双 for 循环),并选择我们要使用的向量:
# Selects the p_i vector -> Shap Values vector for feature i
pi = shap_values[:, i]# Selects pij -> SHAP interaction vector between features i and j
pij = shap_interaction_values[:, i, j]
# Other required vectors
pji = shap_interaction_values[:, j, i]
pj = shap_values[:, j]
有了这些,就可以很容易地根据上面提供的等式计算出以下向量:
# Synergy vector
s[i, j] = (np.inner(pi, pij) / np.linalg.norm(pij)**2) * pij
s[j, i] = (np.inner(pj, pji) / np.linalg.norm(pji)**2) * pji# Autonomy vector
a[i,j] = pi - s[i, j]
a[j,i] = pj - s[j, i]# Redundancy vector
r[i,j] = (np.inner(a[i, j], a[j, i]) / np.linalg.norm(a[j, i])**2) * a[j, i]
r[j,i] = (np.inner(a[j, i], a[i, j]) / np.linalg.norm(a[i, j])**2) * a[i, j]# Independece vector
i_[i, j] = a[i, j] - r[i, j]
i_[j, i] = a[j, i] - r[j, i]
然后,使用长度计算公式,我们得到最终的标量值:
# Synergy value
S[i, j] = np.linalg.norm(s[i, j])**2 / np.linalg.norm(pi)**2# Redundancy value
R[i, j] = np.linalg.norm(r[i, j])**2 / np.linalg.norm(pi)**2# Independence value
I[i, j] = np.linalg.norm(i_[i, j])**2 / np.linalg.norm(pi)**2
此外,论文作者在刻面库上提供了这些方法的开源实现。
特征选择模型建议
既然我们已经了解了这个方法是如何工作的,我们可以开始考虑如何使用它来生成一个特征选择方法。
方法的提议可以是:
- 给定数据集上的训练模型,从中获取 SHAP 信息
- 运行信噪比计算
- 如果一对特征的冗余度大于阈值,则将它们标记为移除
- 从与数据集其余部分协同性最小的要素对中获取要素,并将其移除。为此,您可以使用平均协同效应或其他指标。
这只是如何使用这些值来改进您的特征选择方法的基本想法。我希望在未来能产生更多关于这个主题的想法。
[1] Ittner 等,利用 SHAP 向量分解的全局模型解释中的协同性、冗余性和独立性(2021), arXiv:2107.12436【cs .LG]
识别线性回归中的异常值——库克距离
库克距离是什么,它如何帮助您识别和移除数据集中的异常值?

尼克·吉奥在 Unsplash 上的照片
有许多技术可以从数据集中移除异常值。回归设置中经常使用的一种方法是库克距离。库克距离是对一个数据点影响力的估计。它考虑了每个观察值的杠杆作用和残差。库克距离是当第一个观察值被移除时,回归模型变化程度的总结。
当查看哪些观察值可能是异常值时,一般的经验法则是调查所有距离的平均值大于 3 x 的任何点(注意:还有其他几个常用的标准)。我将展示一个例子,说明这是如何与来自 ISLR 库的一个著名数据集 Hitters 一起工作的。Hitters 数据集包含 250 多名棒球运动员及其职业统计数据和薪水的信息。
首先,我们将导入数据集:
library(ISLR)Hitters <- na.omit(Hitters)
glimpse(Hitters)

接下来,我们将使用所有可用的功能初始化一个多元线性回归模型,目标是预测球员的工资。
model <- lm(Salary ~ ., data = Hitters)
summary(model)

我们看到基线模型的调整后 R 平方为 0.5106。现在,让我们看看诊断图:
par(mfrow = c(2, 2))
plot(model)

在查看诊断图时,我们看到确实有一些异常值(在其他问题中,如异方差)。如果你看看右下角的图,残差对杠杆,你会发现一些异常值也有一些重要的杠杆。举例来说,我们希望从数据集中移除这些异常值,以便我们可以拟合更好的模型。我们如何做到这一点?我们可以在刚刚运行的模型上使用 cooks.distance 函数,然后过滤掉任何大于平均值 3 x 的值。让我们先来看看有多少观察符合这个标准:
cooksD <- cooks.distance(model)
influential <- cooksD[(cooksD > (3 * mean(cooksD, na.rm = TRUE)))]
influential

我们看到 18 个玩家的库克距离大于平均值的 3 倍。让我们排除这些玩家,重新运行模型,看看我们是否有更好的拟合。
names_of_influential <- names(influential)
outliers <- Hitters[names_of_influential,]
hitters_without_outliers <- Hitters %>% anti_join(outliers)model2 <- lm(Salary ~ ., data = hitters_without_outliers)
summary(model2)

我们的模型拟合度大幅提高。我们已经从 0.5106 的调整后 R 平方提高到 0.6445,仅去除了 18 个观察值。这证明了异常值在回归模型中的影响有多大。让我们来看看新模型的诊断图:
par(mfrow = c(2, 2))
plot(model2)

与我们以前的诊断图相比,这些图有了很大的改进。再次查看残差与杠杆率图,我们看到没有任何剩余的点具有显著的杠杆率,从而更适合我们的模型。
上面的例子只是为了演示。在没有对有问题的点进行深入彻底的分析的情况下,永远不要仅仅删除离群值。此外,这样做可能会导致对训练数据的良好拟合,但对看不见的数据的预测较差。
库克的距离是一个很好的工具,添加到您的回归分析工具箱!您现在有了一个有意义的方法来调查模型中的异常值。快乐造型!
谢谢大家的支持!
感谢您阅读本文!如果你觉得有帮助,请给我一两下掌声:)
识别 Airbnb 上的潜在欺诈房源
使用网络分析来查找由假冒评论者连接的 Airbnb 主机

Airbnb 上的主持人和评论者网络(图片由作者提供)
I 简介
我最近在做一个项目,使用了伦敦 Airbnb 房源的数据。我的项目的最初目标是使用机器学习来根据房产的特征、位置和与房源相关的评论来预测租金价格(稍后会有更多相关内容)。在研究伦敦 Airbnb 的使用情况时,我看到了《连线》【2020 年 2 月发表的一篇文章,这篇文章启发我以另一种方式看待这些数据。

激发这个项目的连线文章(来源:连线)
首先,一些背景。2015 年,一项法律出台,允许伦敦人在不需要规划许可的情况下短期出租房屋,每年最多 90 晚。这项政策的目的是让居民受益,让他们从家里赚取额外收入,而不是为商业部门提供机会。然而,人们越来越担心,短期租赁平台正变得越来越商业化,正在从长期租赁市场上清除住房存量,从而加剧伦敦的住房短缺。
2020 年 2 月大伦敦当局的一份报告显示,Airbnb 房源每晚的平均收入为 109 英镑,而长期租赁为 58 英镑。对于房东来说,超过 90 晚的限制有一个明显的激励,因为他们可以获得远高于长期租赁市场的回报。同一份报告估计,伦敦多达 23%的房源可能违反了 90 晚的限制(截至 2019 年 5 月)。占据伦敦短期租赁市场 65%份额的 Airbnb 于 2017 年在其网站上推出了一个上限,当达到 90 晚的限制时,自动使“整栋房屋”的房源不可用(除非有相关的规划许可,可以租赁更长的时间)。然而,主机可以通过重新列出不同地址或照片的属性,或者通过创建多个主机帐户,轻松绕过上限,以避免旨在检测重复的算法。在业内,这被称为“系统化”上市。
上面提到的连线文章详细描述了记者在伦敦揭露的 Airbnb 骗局。这个特殊的骗局围绕着一个事实上的酒店,该酒店是为短期租赁而建的,违反了 Airbnb 的政策和 90 夜法。公寓有重复的列表,大多数列表使用相同的照片或镜像照片,一些列表并不存在。客人经常抱怨被安排到与他们预订的不同的房间,有时房间被重复预订。许多评论和主持人简介是虚假的或误导性的。
这篇文章指出了 200 个“系统化”列表背后的主机账户网络。这些账户托管的列表有许多来自一小群评论者的虚假评论。所有这些账户本质上都是一个人,或者至少是一个公司。


连线文章的摘录(来源:连线)和数据的网络图(图片由作者提供)
通常情况下,您不会期望找到一小组主机,这些主机的列表由同一小组审核者定期审核。我的目的是看看我是否可以使用网络分析来找到这种连接的主机,首先使用本文发表时的数据来测试概念证明。自从这篇文章发表后,继在美国发现类似的欺诈房源之后,Airbnb 在一封名为“ In The Business of Trust ”的电子邮件中表示,它将在 2020 年 12 月前审查其平台上的每一个房源和主机。在测试概念证明后,目标是将相同的网络分析应用于更近的数据,以查看类似的骗局是否仍然存在。
数据
数据集来自 Airbnb 内部的,Airbnb 定期从 Airbnb 抓取数据,以促进公众讨论该网站的实际使用情况。
为了测试概念的证明,我最初查看了 2019 年 10 月(从为文章进行研究的时间)和 2018 年 10 月(捕捉已被删除的账户)收集的数据。该数据包括一个评论表(包括 reviewer_id 和 listing_id)和一个列表表(包括 listing_id 和 host_id)。我将 listing_id 上的两个表连接起来,得到一个大约 170 万条边的列表,这些边连接着评论者和主持人。当评论者已经为属于该主机的列表写了评论时,边缘是评论者和主机之间的链接。
该过程
问题:边太多,不容易找到小网络。
解决方案:我决定根据更可能显示虚假评论和“系统化”列表的特征划分子集:
- 仅包括主持人和审阅人,其中审阅人对主持人留下了多个审阅(可以跨不同列表)
- 仅包括具有多个列表的主机
- 仅包括审查过多个主机的审查者
这产生了大约 10,000 条边。
步骤 1:使用 NetworkX 创建一个二分网络,其中一组节点用于主机,一组节点用于审核者(因为主机之间或审核者之间不存在直接链接),边如上所述。
步骤 2:在主机上创建投影图。
步骤 3:删除与其它主机没有链接的主机。
下面的网络图显示了此过程应用于约 1,000 条边的另一个子集(在应用于整个数据集之前测试此概念)。红色节点是审阅者,蓝色节点是主持人。文章中的已知网络在三个步骤中的每一个步骤中都被突出显示。最后一张图显示了使用这种方法识别的 6 个相连主机网络,包括本文中命名的网络。



如上所述的步骤 1、步骤 2 和步骤 3(图片由作者提供)
结果
我将这一过程应用于完整的数据集,揭示了已知的网络和许多其他网络。然而,很难进一步调查其他网络,因为许多帐户已被删除。
2020 年 12 月数据
我将同样的过程应用于 2020 年 12 月收集的数据,得到了下面的网络图,显示了主机之间通过审查者的连接。

网络图来自 2020 年 12 月数据(图片由作者提供)
我进一步研究了下面显示的两个网络。


简单示例(L)和复杂示例(R)(图片由作者提供)
简单的例子
这两个主机帐户具有相同的名称。主持人简介照片不同,但似乎是同一个人。两个主机帐户的列表看起来像是重复的(非常相似的名字,相同的照片)。
复杂示例
这些主机帐户中的大多数都有相同名称的变体(例如 X 和 Y、Y 和 X、X 和 Y、X/Y)。有重复的列表,一些带有从不同角度拍摄的照片,以使它们看起来不同。总体评价是积极的,但有些评价非常消极。关于不入住已预订的公寓,有多种评论。多份评论提到了由于泄露导致的最后一分钟取消,并且有不少关于糟糕的客户服务的投诉。这些都是与连线文章中骗局相似的特征。

复杂示例:审查者网络(红色)和主持人网络(蓝色)(图片由作者提供)
上面的网络图显示了此网络,其中添加了连接主机的审查者(红色审查者,蓝色主机)。
该中心有五名评论者,他们已经为这些主机的列表写了近 300 篇评论。两个评论者的账户似乎被删除了。其他三个评论者有许多来自“连接的”主机的相同文本的评论。
结论
《连线》的这篇文章揭示了 Airbnb(和其他类似平台)上的托管业务正变得越来越系统化,如果企业能够绕过 90 晚的限制,它们从短期出租中获得的收入远远高于长期出租。
Airbnb 等一些平台已经实施了每年 90 晚的房源上限,但有办法绕过这一限制,一些主机也在多个平台上房源,它们之间没有协调。地方当局目前没有简单的方法来跟踪他们所在地区的短期租房数量,这意味着 90 晚的限制很难执行。
我的分析展示了一种不同的方式,试图识别 Airbnb 等平台上的系统化房源,方法是找到由(可能是假的)审核者连接的主机网络。应该指出的是,只有 Airbnb 可以获得属于这些主机的房源的实际入住数据,因此无法检查其中任何一家是否违反了 90 晚的限制。然而,您通常不会期望找到由同一小组审查者定期审查的一小组主机,因此,这些网络肯定值得进一步调查。
使用序列分类识别疲劳迹象
实践教程
使用可穿戴健康数据训练模型以对疲劳进行分类

我想你已经在路上开了一会儿,天开始黑了。你感觉你的眼睛越来越沉重,你知道你应该停下来休息一下,但你想继续前进,这样你就可以按时到达目的地…
我相信很多人在某个时候都经历过这种情况,这是一个典型的“昏昏欲睡驾驶”的例子,这使我们发生交通事故的可能性增加了 3 倍。
如果有一种方法可以识别我们何时开始感到疲劳,会怎么样?
挑战
我们将使用从我的个人 Fitbit 手表收集的睡眠和心率数据。对于这项任务,睡眠数据给出了一个关于我何时开始睡眠的想法,并且可以用作一个点来划分清醒和睡眠。此外,这有助于我们估计疲劳的时间间隔;为了方便起见,这将被定义为睡觉前的最后三十分钟。定义这些不同的阶段有助于标记我们的心率数据,即测量每分钟的心跳次数。
这为我们提供了由睡眠间隔分类的心率测量的标记的单变量序列,其将被用作我们的序列分类模型的输入。我们的模型将是一个用 Keras 建造的 GRU。
在建立和训练我们的序列分类器后,我们将看到它在一些看不见的数据上的表现。
数据加载和准备
睡眠和心率数据是存储在. csv 或。json,它代表一个月中的某一天,因此我们需要加载所有这些文件并将它们附加在一起。
#Import sleep data data and combine files
def Dataimport():
datasets = ['sleep_files *.csv']
for datatype in datasets:
file_list=[]
path = 'folder pathway'
os.chdir(path)
for file in glob.glob(datatype):
file_list.append(file)
dfs = []
for file in file_list:
data = pd.read_csv(path + file)
print('Reading: ' + str(file))
dfs.append(data)
concatenated = pd.concat(dfs, ignore_index=True)
concatenated = concatenated[['sleep_start','sleep_end']]
return concatenatedsleepdata = Dataimport()
这为我们提供了一个睡眠开始和结束的时间表,值如下:
Sleep start: 2021-01-12 22:10:00 Sleep end: 2021-01-13 05:37:30
我们可以将日期和时间分开,因为我们稍后将只需要找到记录睡眠数据和心率数据的日期:
#Splitting the date and time
def Datasplit(sleepdata):
sleepdata['date_start'] = sleepdata['sleep_start'].str.split('T', 1, expand=True)[0]
sleepdata['time_start'] = sleepdata['sleep_start'].str.split('T', 1, expand=True)[1]
sleepdata['date_end'] = sleepdata['sleep_end'].str.split('T', 1, expand=True)[0]
sleepdata['time_end'] = sleepdata['sleep_end'].str.split('T', 1, expand=True)[1]
sleepdata['start_of_sleep'] = pd.to_datetime(sleepdata['date_start'] + ' ' + sleepdata['time_start'])
sleepdata['end_of_sleep'] = pd.to_datetime(sleepdata['date_end'] + ' ' + sleepdata['time_end'])
sleepdata = sleepdata[['start_of_sleep', 'end_of_sleep']]
sleepdata = sleepdata.sort_values(by="start_of_sleep")
return sleepdatasleepdata = Datasplit(sleepdata)
为了导入心率数据,我们可以重新使用相同的 Dataimport() 函数,替换文件名,在文件名中包含“心率”。这些数据需要以稍微不同的方式进行清理,因为我们希望删除所有不必要的字符串,并以与睡眠数据相同的分辨率(即以分钟为单位)对时间序列进行重新采样:
#cleaning the columns of the heart rate data
def HRclean(heart):
heart = heart.sort_values(by=”dateTime”)
heart = heart.set_index(‘dateTime’)
heart[“value”] = heart[“value”].apply(str)
heart[“value”] = heart[“value”].str.split(“{‘bpm’:”).str[1]
heart[“value”] = heart[“value”].str.split(“,”, 1, expand = True)[0]
heart[“value”] = heart[“value”].astype(int)
heart = heart.resample(‘1Min’).mean()
heart[‘value’] = heart[‘value’].round(0)
heart[‘date’] = heart.index
heart = heart[[‘date’, ‘value’]]
return heartheartdata = Dataimport()
heart = HRclean(heartdata)
因为我们将我们的“疲劳”间隔定义为睡觉前的三十分钟,我们已经可以想象,与清醒和睡觉的时间相比,将会有一个大的等级不平衡。为了减轻这种情况,我们可以将心率数据过滤到更小的间隔,比如从傍晚到凌晨:
#selecting only values in the evening (times around the tiredness since mornings are irrelevant)
heart = heart.between_time(‘19:00’, ‘03:00’)
heart[“only_date”] = [d.date() for d in heart[“date”]]
sleepdata[“only_date”] = [d.date() for d in sleepdata[“start_of_sleep”]]#Identifying rows where sleep data exists for the given day of heart rate data
heart[‘sleep_data_exists’] = pd.Series(heart.only_date.isin(sleepdata.only_date).values.astype(int), heart.date.values)
heart = heart[heart[‘sleep_data_exists’] == 1]
我们现在可以根据睡眠的日期时间范围将心率数据标记为睡眠或清醒:
#for each HR row, need to see if that time was during sleep or not, and label as 1 or 0
def Labelling(heart):
print(‘labelling the data…’)
heart[‘sleep_label’] = 0#for each heartrate value, for each dt range, if hr date in dt range (per row), =1 else = continue
for i in range(len(heart)):
print(str(i) + ‘ of ‘+ str(len(heart)))
for j in range(len(sleepdata)):
if heart[‘date’][i] >= sleepdata[‘start_of_sleep’][j] and heart[‘date’][i] <= sleepdata[‘end_of_sleep’][j]:
heart[‘sleep_label’][i] = 1
else:
continue
return heartheart = Labelling(heart)
最后的准备步骤将是给我们的“疲惫”类贴上标签:
#selecting the time n rows before sleep starts
idx = heart.index.get_indexer_for(heart[heart['sleep_label'] == 1].index)
subset = heart.iloc[np.unique(np.concatenate([np.arange(max(i-30,0), min(i-30+1, len(heart)))
for i in idx]))]
subset = subset[subset.sleep_label == 0]
heart['tired_label'] = pd.Series(heart.date.isin(subset.date).values.astype(int))#cleaning the final labels into numerical values
heart['label'] = pd.Series()
heart['label'][heart.tired_label == 1] = 2
heart['label'][heart.sleep_label == 1] = 1
heart['label'] = heart['label'].fillna(0)
heart = heart.dropna()
探索数据
我们现在可以开始更详细地查看我们的数据集。让我们从导入有用的库开始。这是一个很好的主意,我们可以将一些标记的心率数据可视化,这样我们就可以 了解每一类心率是如何变化的:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from tensorflow import keras
import matplotlib.pyplot as plt
import scipy.stats as stats
import seaborn as sns
from tensorflow.keras import backend as K
from keras.layers import Dense, Dropoutsns.set(rc={"figure.figsize":(30, 8)})
sns.scatterplot(x = df.index, y = df['heart_value'][:1500], hue = df.label, palette = 'Spectral')
plt.xlabel("Heart Rate (BPM)")
plt.ylabel("Time (Minutes)")
plt.show()

按状态着色的心率数据(BPM)时间序列图。
我们可以清楚地看到心率在睡眠和清醒之间变化。标记为“疲劳”的心率值是过渡性的,因此不太明显,这将使我们的建模变得有趣。尽管缩短了面试时间,我们仍然有一个需要稍后解决的班级不平衡问题。
查看每类心率值的分布也是一个好主意:

比较不同类别心率分布的直方图
并查看他们的汇总统计数据:

每类心率数据汇总表
在这种情况下, 0 是醒着的,1 是睡着的,2 是累着的。“疲劳”类具有与“睡眠”类相似的平均值,然而具有与“清醒”类相似的标准差。这与我们通过观察心率时间序列得出的第一轮观察结果相呼应。
序列预处理
现在让我们把数据分成固定长度的序列。我们的序列将是 30 分钟长,以符合我们的疲劳间歇的长度。我们还需要拆分我们的数据:
def create_dataset(X, y, time_steps, step):Xs, ys = [], [] for i in range(0, len(X) - time_steps, step):
v = X.iloc[i:(i + time_steps)].values
labels = y.iloc[i: i + time_steps]
Xs.append(v)
ys.append(stats.mode(labels)[0][0])
return np.array(Xs), np.array(ys).reshape(-1, 1)interval = 30
X_train_full, y_train_full = create_dataset(x1, y1, 1, interval)X_train, X_test, y_train, y_test = train_test_split(X_train_full, y_train_full, test_size=0.05, stratify = y_train_full, random_state=42, shuffle = True)
我们可以使用 Keras 将我们的标签转换为编码类别,并进一步分割我们的数据集,这样我们就有了单独的训练集(76%)、验证集(19%)和测试集(5%)…事后回想起来,这是一个相当奇怪的分割:
y_train1 = keras.utils.to_categorical(y_train, num_classes = None)
y_test1 = keras.utils.to_categorical(y_test, num_classes = None)X_train = X_train.astype(np.float32)
y_train1 = y_train1.astype(np.float32)#making the validation set separately
X_train, X_val, y_train1, y_val = train_test_split(X_train, y_train1, test_size=0.2, stratify = y_train1, random_state=42, shuffle = True)
我们在 train_test_split() 中使用“分层”参数,以便我们的标签按比例分配给每个类。为了帮助解决类别不平衡,我们将使用类别权重,其中我们的模型将比其他类别对正确的“疲劳”预测赋予更高的权重。权重将与数据集中的观测值数量成比例:
class_weight = {3: 1., 1: 1., 2: int((sum(y_train1.iloc[:,0]) + sum(y_train1.iloc[:,2])) / sum(y_train1.iloc[:,1]))}
在这种情况下,与醒着或睡着时相比,我们的“疲劳”类有13 倍多的重量。由于这是一个多类序列分类,我们将使用 F1 分数来衡量准确性。可以在 这里 找到这方面的有用指南以及 Keras 实现的代码。
模型构建
最后走上模型建筑。如前所述我们将使用 GRU 模式 l,因为它的更快来训练。我们如下使用 Keras 建立一个模型,确保添加漏失层以最小化过度拟合,并将输出序列从一个 GRU 传递到下一个。我们的 GRU 层将从 32 个单位开始,这些单位将在连续的层中增加。我们使用一个 Adam 优化器,将激活设置为 softmax (因为我们有多个类)并测量交叉熵损失:
def create_gru_model(unit):
inputs = keras.Input(shape=(X_train.shape[1],X_train.shape[2]))
x = layers.GRU(unit*1, activation='tanh', return_sequences=True) (inputs)
x = layers.Dropout(0.25)(x)
x = layers.GRU(unit*2, activation='tanh', return_sequences=True)(x)
x = layers.Dropout(0.25)(x)
x = layers.GRU(unit*2, activation='tanh', return_sequences=True)(x)
x = layers.Dropout(0.25)(x)
x = layers.GRU(unit*3, activation='tanh')(x)outputs = layers.Dense(y_train1.shape[1], activation="softmax")(x)
model = keras.Model(inputs, outputs)opt = keras.optimizers.Adam(learning_rate=1e-3)
model.compile(loss='categorical_crossentropy', optimizer= opt, metrics=[custom_f1]) return modelmodel_2 = create_gru_model(32)
history2 = model_2.fit(X_train, y_train1, validation_data = (X_val, y_val), epochs = 200, batch_size = 256, shuffle= False, class_weight=class_weight)
经过 200 次训练和验证后,我们可以看到 F1 分数和模型损失:
sns.set(rc={"figure.figsize":(12, 12)})
plt.plot(history2.history['custom_f1'])
plt.plot(history2.history['val_custom_f1'])
plt.ylabel('F1 Score')
plt.xlabel('Epoch')
plt.legend()
plt.show()plt.plot(history2.history['loss'])
plt.plot(history2.history['val_loss'])
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend()
plt.show()

200 个时期后的模型损失和 F1 分数
超过 0.95 的最终确认分数看起来相当不错。最后,我们将对我们的测试集进行预测,并在混淆矩阵中评估结果。我们需要使用 np.argmax()来帮助我们处理多个类:
y_pred = np.argmax(model_2.predict(X_test), axis=1)
y_pred = np.expand_dims(y_pred, axis=-1)from sklearn.metrics import plot_confusion_matrix
matrix = confusion_matrix(y_test, y_pred)sns.heatmap(matrix, annot=True, fmt=’g’, yticklabels= [ ‘Awake’, ‘Asleep’, ‘Tired’], xticklabels= [ ‘Awake’, ‘Asleep’, ‘Tired’])

不可见测试数据集上的混淆矩阵
看起来大多数类都被正确预测了。我们看到疲劳和清醒之间的一些不正确的分类,这可以从我们之前看到的均值和直方图中理解。
总结/后续步骤
我们采用了心率数据的单变量时间序列,结合从睡眠数据中获得的标签,来创建一个序列分类模型,能够判断我们何时处于睡眠、清醒或疲劳状态。我们的模型表现良好,但也许还有其他方面需要考虑:
- 对于二元序列分类,模型表现如何(即累/不累)?
- 衡量行走步数的数据有助于区分清醒和疲劳吗(假设疲劳时身体活动减少)?
- 如果我们延长时间间隔来记录我们醒来时的疲劳程度,与“晚间疲劳”相比,看起来是相同还是不同?
还有很多事情要做和探索,我会让你了解我的进展。感谢阅读!
注意:本文并不声称预测或预防与疲劳相关的事故,仅应作为数据科学学习练习来阅读和使用。
为您的任务确定正确的分类标准
为业务问题选择分类标准的指南

今天,机器学习已经成为许多商业领域不可或缺的一部分。智能垃圾邮件分类器通过过滤掉恶意或促销电子邮件来保护我们的收件箱。广告系统旨在通过学习用户的活动来定位相关广告。推荐系统根据用户兴趣推荐内容。欺诈检测系统保护银行免受恶意攻击者的攻击。这个列表每天都在增加,但是为了评估这些复杂的机器学习系统的实际性能,我们需要正确的度量标准。机器学习系统在所选指标上的性能指导开发人员调整这些系统的方向。
分类的任务是使用从称为训练数据的现有标记数据集的学习,将新的观察值分配到类别或类之一。在垃圾邮件分类的情况下,任务将是从现有的标记的电子邮件数据中学习,将电子邮件标记为垃圾邮件或非垃圾邮件(垃圾邮件或非垃圾邮件的标签在之前是已知的)。通过这篇博客,我们将关注于为手头的分类任务确定正确的度量标准。
你也可以在 DataTrek 频道通过在这里浏览这个话题的视频内容。
DataTrek :逻辑回归简介
选择分类标准时要考虑的因素
我们肯定会考虑一些分类指标以及它们有意义的支持场景,但首先,让我们看看在选择分类指标时要考虑的因素。主要有两个因素决定分类的选择。
- 每个类的实例数:lot 取决于每个类的实例数。我们需要检查它是一个类不平衡数据集(一些类比其他类有更多的数据)还是一个平衡数据集,即类有大致相同数量的实例。
- 要解决的业务用例:了解业务需求,是给予每个类同等的重要性,还是给予某些类比其他类更高的重要性。这也给出了使用正确指标的方向。
重要术语和概念
让我们考虑训练机器学习分类器来识别和标记淫秽或令人不安的社交媒体内容的任务。在这种情况下,我们将对淫秽内容使用“肯定”一词,对非淫秽或有效内容使用“否定”一词。每个帖子都可以归入这四个类别中的一个。
- 真阳性:帖子含有淫秽内容,被预测为阳性。这是一个理想的场景,其中 ML(机器学习)分类器正在做它应该做的事情。
- 真阴性:帖子内容有效,预测为阴性。这也是一个理想的场景,ML 分类器按照它应该做的那样工作。
- 误报:帖子内容有效,但仍被预测为阳性。在这里,ML 分类器错误地将一个有效的帖子标记为淫秽内容。
- 假阴性:帖子有淫秽内容,但预测为阴性。在这里,ML 分类器错误地将一个淫秽帖子标记为有效内容。

社交媒体内容分类器。作者图片
混淆矩阵
根据真阳性、真阴性、假阳性和假阴性来表示 ML 分类器性能的更好方法是使用混淆矩阵。混淆矩阵是 ML 分类器性能的表格表示。行通常表示模型的预测,列表示实际的标签。

混乱矩阵。作者图片
混淆矩阵的概念也可以扩展到多类分类问题。混淆矩阵顾名思义,告诉分类器在类之间混淆的程度。
深入了解分类指标
在这一节中,我们将看看一些分类标准和它们有意义的场景。
准确(性)
准确度是正确标记的实例总数(不管是正还是负)与所有实例的总数之比。
准确度= (TP+TN)/(TP+FP+FN+TN)

垃圾邮件分类器。作者图片

社交媒体内容分类器。作者图片
在第一种情况下,准确性是一个很好的度量,因为实例在两个类中是均匀分布的。在第二种情况下,该模型的性能不好,因为在 20 个淫秽内容中,它只能正确地检测出 1 个,但准确率仍然高达 99%。
只有在以下情况下,准确性才是一个好的衡量标准。
- 类或类别具有均匀分布的实例,即它是一个平衡的数据集。
- 假阳性的代价和假阴性的代价是一样的。
精确
精度是被 ML 模型正确正标记的实例与所有正标记实例(不管那些实例实际上是否正)的数量之比。精度是一个度量标准,它表明我们对模型的正面预测有多大的信心,即使可能会遗漏一些实际的正面实例。
精度= TP/(TP+FP)

垃圾邮件分类器。作者图片

社交媒体内容分类器。作者图片
即使 ML 模型做得不好,并且只能正确地检测到 1 个淫秽内容,精确度也高达 33%,因为在预测为阳性的 3 个内容中,1 个实际上是阳性的,模型对此有 33%的把握。
S 假设该公司要求暂停发布淫秽内容的客户的账户。在这种情况下,ML 模型的精度应该非常高。如果由于风险稍低,很少的罪犯没有被发现,这仍然是好的,但是那些被标记为阳性的人实际上应该是罪犯或真正的阳性。
回忆又名敏感性
召回或灵敏度是由 ML 模型正确肯定标记的实例的数量与所有实际肯定实例的数量之比(不管 ML 模型是否能够正确检测)。召回作为一种衡量标准,强调的是假阴性的代价更高,不应遗漏任何真正的阳性。
精度= TP/(TP+FN)

垃圾邮件分类器。作者图片

社交媒体内容分类器。作者图片
在创建一个检测社交媒体中淫秽内容的分类器的例子中,召回率需要很高,因为我们不能允许任何挑衅性的内容在网站上盛行。
f1-分数
正如我们所见,精确度和召回率指标有时各有优势。 F1 得分考虑了两个指标的优劣。将其作为精度和召回率的调和平均值。
谐波是什么意思?
两个值‘a’和‘b’的简单平均值由(a+b)/2 给出。而两个值‘a’和‘b’的调和平均值是通过首先取‘a’的倒数和‘b’的倒数,取平均值,然后求倒数而得到的。

两个值的调和平均值。作者图片
因此,分类任务的 F1 分数如下所示。

f1-分类任务的分数。作者图片
F1-score 为什么取调和平均值,为什么不取简单平均值?
为了理解调和平均数的重要性,让我们看一个例子。假设学生 A 在两个科目中得了 11 和 14 分,而学生 B 在这两个科目中得了 20 和 5 分。两个学生的简单平均分是 12.5,而学生 A 的调和平均分是 12.32,B 是 8。差异的原因是调和平均值也考虑了两个分数之间的和谐性(相似性)。由于 11 和 14 比 20 和 5 彼此更相似,第一种情况下的调和平均值大于第二种情况。如果精度或召回指标中的任何一个有所提高,F1 分数不会很高。当两者和谐一致时,情况会有所改善。例如,如果 Precision 为 1,Recall 为 0,则 F1 分数为 0,而简单平均值为 0.5。
Fβ分数
如果我们仔细看看 F1 分数的公式,它给出了精确度和召回率的同等权重。

f1-得分公式。作者图片
精确度和召回率的权重都是 0.5。另一种说法是 F1 分数给出了相等的权重 1 表示精确,1 表示回忆,为了确保,总权重被归一化为 1,两个权重都除以 2。
Fβ-score 并没有给出等于 1 的精度和召回权重,而是分配归一化为 1 的权重,但是召回权重是𝛽乘以精度权重。因此,Fβ分数公式看起来像。

fβ-评分公式。作者图片
召回的权重是𝛽,精确的权重是 1。为了确保总权重归一化为 1,两者都除以(1+𝛽).
常用的𝛽值有:
- 𝛽 = 0.5,召回率低于精确度。
- 𝛽 = 1,表示召回率等于精确度。与 F1 得分相同
- 𝛽 = 2,召回率高于精确度。
结论
通过这篇博客,我们谈到了正确选择分类标准的重要因素。我们还查看了一些分类指标,如准确度、精确度、召回率、f1 分数和 fβ分数,以及它们有意义的不同示例场景。根据业务用例,如果检测所有的阳性非常重要,或者换句话说,假阴性的代价更高,我们可以使用 Fβ-score 给出更高的回忆权重。另一方面,如果避免任何误报更重要,我们可以使用 Fβ-score 来提高精确度的权重。如果假阳性和假阴性的成本相同,可以用 F1 值。对于多类分类任务,使用加权 F1 分数(通过每个类的实例数进行加权)也很常见。在平衡数据集的情况下,精确度也是一个很好的衡量标准。请以评论的形式让我知道你的想法。
我的 Youtube 频道获取更多内容:
关于作者-:
Abhishek Mungoli 是一位经验丰富的数据科学家,拥有 ML 领域的经验和计算机科学背景,跨越多个领域,并具有解决问题的思维方式。擅长各种机器学习和零售业特有的优化问题。热衷于大规模实现机器学习模型,并通过博客、讲座、聚会和论文等方式分享知识。
我的动机总是把最困难的事情简化成最简单的版本。我喜欢解决问题、数据科学、产品开发和扩展解决方案。我喜欢在闲暇时间探索新的地方和健身。在 中 、Linkedin、或insta gram上关注我,查看我以前的帖子。我欢迎反馈和建设性的批评。我的一些博客:****
- 逻辑回归手册
- 线性回归完全指南
- 确定您数据的分布
- 降维:PCA 与自动编码器
- 体验遗传算法的威力
- 每个数据科学家都应该避免的 5 个错误
- 以简单&直观的方式分解时间序列
- GPU 计算如何在工作中真正拯救了我?
- 信息论& KL 分歧第一部分和第二部分
- 使用 Apache Spark 处理维基百科,创建热点数据集
- 一种基于半监督嵌入的模糊聚类
- 比较哪种机器学习模型表现更好
- 分析 Fitbit 数据,揭开疫情封锁期间身体模式变化的神秘面纱
- 神话与现实围绕关联
- 成为面向业务的数据科学家指南
用交叉价格弹性识别价格竞争对手——一种实用的方法
实践教程
确定谁是你的价格竞争对手是价格和促销分配的关键

当你设定价格或促销时,最重要的事情之一是了解你的竞争对手是谁,还有哪些其他产品或产品促销会影响我们的产品销售。换句话说,当其他产品的价格有折扣时,这些折扣对我们产品销售的影响有多大。
在本帖中,我们将通过使用 Python 中的多元线性回归计算交叉价格弹性,来探索和确定产品之间价格竞争的本质,如星巴克咖啡 vs 胡安·瓦尔迪兹咖啡或者产品可能是价格补充,如番茄酱** 和意大利面。**和往常一样,本帖全包;我将给你一个简单明了的交叉价格弹性的解释,并与你分享一个非常容易理解的代码,它一步一步地解释了一个电子商务行业中笔记本电脑交叉价格弹性的例子。
我们将能够探索以下内容:
“当别人改变价格时,我的需求会发生什么变化?”
计算交叉价格弹性类似于价格弹性,但有一点扭曲,而基本价格弹性解释了当我们提高或降低相同产品价格时我们产品的销售需求的变化;其计算方法如下:

作者图片 1
交叉价格弹性解释了当其他产品价格上升或下降时,我们产品的销售需求的变化。换句话说,它解释了其他产品价格变化对我们产品销售需求的影响;其计算方法如下:

作者提供的图片 2
交叉价格弹性允许我们识别价格竞争者/替代品和补充。我将进一步解释这两个定义,举上面提到的价格竞争对手的例子,如星巴克咖啡 vs Juan Valdez 和价格补充,如番茄酱和意大利面。
竞争对手/替代者:
让我们说,星巴克咖啡豆和胡安瓦尔迪兹咖啡豆是强有力的竞争对手,消费者是价格意识,通常在选择一个或另一个之间周旋。在这种情况下,可能发生的情况是,当星巴克的咖啡豆打折时,Juan Valdez 咖啡豆的销售量会减少,因为消费者会更喜欢价格更便宜的星巴克,而 Juan Valdez 的销售会受到影响。
因此,这将给我们带来以下正交叉价格相关性:
当星巴克咖啡价格降低时,胡安·瓦尔迪兹售出的咖啡数量减少

补充:
假设一位消费者正在购物,看到番茄酱在打折,因此这位消费者更有可能购买意大利面来烹饪美味的意大利面“好吃!”。这是当我们确定产品是互补的,当一种产品价格下降时,消费者可能会购买另一种产品以补充它;比如番茄酱和意大利面。
因此,这会给我们带来以下负交叉价格相关性:
当番茄酱价格下降时,意大利面的销量增加

照片由丹尼耶拉·普里约维奇在 Unsplash 上拍摄
毕竟,我们如何确定价格竞争对手或价格补充?
很棒的问题!
接下来,我将详细介绍完整的分步编码过程,并为您提供一个非常简单易懂的解释,说明需要哪些数据,以及如何使用多元线性回归来解释交叉价格弹性结果。万岁,让我们开始有趣的部分!
数据收集:
收集哪些数据?
为了分析交叉价格弹性,我们需要一个或多个潜在竞争对手的价格变化,以及在竞争对手价格变化发生的同一时间段内我们的产品销售需求(销售量),以便能够观察我们的销售需求对竞争对手产品价格变化的反应。
在这种情况下,该模型将更多地关注价格竞争对手/替代品,而不是价格补充,因为我们关注的是单一类别“笔记本电脑”的产品价格。如果我们想进一步分析价格补充,那么理想情况下,我们会添加笔记本电脑组件,如笔记本电脑包、笔记本电脑外壳、外部硬盘驱动器等。
使用的 Python 库有:
我们将使用以下 python 库: pandas,numpy,matplotlib 和 statsmodels。
在这里,我在开始时导入了 pandas、numpy 和 matplotlib ,但是在接下来的步骤中你会看到其中最重要的,那就是主要用于多元线性回归模型的 statsmodels 。

表 1 按作者分列
数据框特征说明:
Date_imp: 捕获数据时的日期时间印象
Category _ name:Category name 是为了分析笔记本电脑、音箱等单独群组的价格而包含的功能
名称: 产品名称
disc _ Price:扣除折扣后的产品价格
商家: 商家是产品所在的平台
状态: 产品状态。作为建议,从相同的条件下分析产品价格。否则,这可能会影响您的结果,因为条件为“二手”的产品价格通常比新产品价格便宜
Sales _ count:销售计数是产品在各自价格下的销售需求
从数据来看,为了不出现任何不协调的情况,只选择了名为“Bestjam.com”的商家。其他商家可能有更多的用户流量(市场份额),不同的消费者细分,因此销售的产品数量可能因商家而异。
由于上述原因,数据被“Bestjam.com”电子商务平台过滤,“笔记本电脑,计算机”类别和产品状况为“新”。
数据准备:
在多元线性回归中,我们希望合理地分配自变量和因变量。当我们谈到自变量和因变量时,你可以把它们看作是因果:自变量是你认为是因的变量,因变量是果。
众所周知,价格弹性和交叉价格弹性公式非常相似,只是有一点小小的变化。价格弹性衡量产品 Y 销售量对产品 Y 价格变化的敏感程度,换句话说,它衡量需求对其自身产品价格变化的敏感程度,而交叉价格弹性衡量产品 Y 需求(销售量)对产品(Z,M,W)竞争对手价格变化的敏感程度。
价格弹性 = (x:黄色,y:黄色)
产品 Y 价格和产品 Y 销售数量
交叉价格弹性= (x:灰色,y:黄色)
产品价格(Z、M、W)和产品 Y 销售量

按作者分列的图表 1
对于交叉价格弹性,我们将把可能的竞争者的价格设定为自变量(x 值),把我们产品的销售量设定为因变量(y 值)。竞争对手的价格变化和销售数量需要与同一时间段保持一致,以便能够观察我们的产品销售数量受竞争对手产品价格变化影响的可能性。
在这种情况下,我们将分析华硕 VivoBook 笔记本电脑的交叉价格弹性。接下来,我们可以观察到,我们的数据框中有多个笔记本电脑价格的变化,这些价格来自“Bestjam.com”电子商务网站中显示的多个产品,包括华硕 VivoBook 的价格和销售量。

表 2 按作者分列
我们将笔记本电脑价格设为 x 值(独立值),将华硕 VivoBook 售出数量设为 y 值(依赖值)。
太好了,现在我们已经准备好将数据植入多线性回归模型。是时候跳到交叉价格分析中最酷的部分了,多重线性回归模型。
交叉价格弹性模型公式:
我们可能知道,交叉价格计算如下:

作者提供的图片 3
使用多元线性回归将其改写如下:
多线性回归中需求的交叉价格弹性:
系数(斜率)*产品 B 的价格平均值/产品 A 的数量平均值
简单来说,系数(斜率)是产品价格 B 和产品 A 销售量的系数
我们计算 x 值的价格平均值(笔记本电脑价格)和 y 值的销售量平均值(华硕 VivoBook 数量),以便稍后植入交叉价格弹性公式。
一旦我们有了交叉价格弹性公式的平均值。我们将计算系数,如下所示:
我们导入 python 中的 statsmodels 库用于多线性回归,并拟合我们的模型,如下所示:
太好了,我们快到了。正如您在下面的 coef 中看到的,这些是我们将用于交叉价格弹性公式的系数。
为了分析我们的系数的统计意义,观察系数 p 值和 t (t-stats)是很重要的
t_score: 等于 t-stats,表示发现的显著性。越接近 0,越可能没有显著性。t-stats 越大,无论是正面的还是负面的,研究结果的重要性就越大
系数 _ 值:检验零假设,即系数等于零(无效)。低的 p - 值 ( < 0.05)表示您可以拒绝零假设

作者图片 4
这太神奇了,现在我们有了计算交叉价格弹性所需的东西,我们知道了哪些系数可能是重要的,哪些应该从我们的分析中去掉。此外,我们将我们所有的计算播种到我们的交叉价格公式和魔术,我们将得到我们的交叉价格弹性。
祝贺你走到这一步,现在我们有了交叉价格弹性,我会进一步向你解释如何解释结果:)。
数据可视化:交叉价格弹性的发散图
作为奖励,我添加了一个数据可视化功能,它将帮助您展示交叉价格弹性,以及您如何从可视化中解读数据:)
上面,这个内置函数将帮助您可视化交叉价格弹性。为了进一步分析,使用了发散图,因为它为读者提供了一个更清晰的价格分析的负交叉价格弹性和正交叉价格弹性之间的概览。
交叉价格弹性结果
当我们有正的交叉价格弹性时,这意味着这些产品在定价上是替代品或主要竞争者。换句话说,从我们的样本数据中,我们可以观察到华硕 VivoBook (我们的产品)的主要竞争对手是苹果 MacBook Pro、戴尔 XPS 和阿里。这意味着当这些竞争对手产品的价格下降时,我们的产品需求或销售额也会下降,因为客户更有可能用这些竞争对手的产品来替代我们的产品。从交叉价格弹性中获得的这些经验对我们的促销策略至关重要,我们能够确定哪些竞争对手的产品对我们的需求影响最大,因此我们可以战略性地关注主要竞争对手的未来价格策略。

按作者分类的图表 2
如果苹果 MacBook Pro 降价 10%,华硕 VivoBook 的销量(我们的产品)可能会下降 130.56%,等等。
现在你明白了,你可以建立自己的交叉价格弹性模型,并使用数据来制定价格策略。
参考文献:
弗吉尼亚大学定价策略中的成本和经济学
感谢阅读!
身份钥匙圈
关联整个企业中使用的不同身份
这是我的书《Azure 上的数据工程》的摘录。本文中的代码示例使用 Azure Data Explorer。如果你想运行它们,创建一个 Azure Data Explorer 集群和数据库。一旦设置完成,您就可以使用数据浏览器 web UI 连接到您的集群和数据库,并运行代码示例。Azure Data Explorer 使用 KQL,Kusto 查询语言。如果不熟悉,可以查阅一下快速参考。这本书更深入地介绍了 Azure Data Explorer,但是这篇文章是独立的。
身份钥匙圈
在一个足够大的企业中,全面了解系统是如何使用的并不是一件容易的事情。通常情况下,企业的不同部分会产生并掌握自己的身份。
例如,网站团队掌握 cookie IDs 来识别未登录的用户,以及已登录用户的配置文件 id。支付团队使用客户 id 来识别客户,使用订阅 id 来跟踪客户为哪些订阅付费。客户成功团队有一个支持客户 ID,用于在他们的支持工具中识别客户,以及客户的电子邮件地址。身份钥匙圈将不同系统中的所有身份汇集在一起,使我们能够快速找到所有联系。图 1 显示了不同团队使用的各种身份,以及一个密匙环如何将它们组合在一起。

图 1:网站、支付和客户成功团队都掌握了自己的身份。钥匙圈将这些身份组合在一起。(图片由作者提供)
企业越大,我们拥有的身份就越多,就越难从更广的角度了解用户是如何与企业互动的。拥有一个密匙环可以让我们关联不同系统的活动:例如,我们可以看到在客户成功团队中解决客户问题所花费的时间如何影响用户保留,由支付团队跟踪,或者我们可以看到网站上的 A/B 测试如何影响用户注册的订阅。
构建身份密钥环
各种系统在身份之间有一些联系。例如,一旦用户登录,网站团队可能有一个将 cookie IDs 与配置文件匹配的表,并且用户配置文件包括一个电子邮件地址。支付团队保留了客户到订阅的映射,并且还拥有给定客户 ID 的配置文件 ID。客户成功团队将他们各自的 id 与一个电子邮件地址关联起来。图 2 显示了这些联系,以及如何将它们组合在一起,让我们能够对系统中的所有身份进行分组。

图 2:不同的系统维护不同的连接。将它们放在一起可以让我们对系统中的所有身份进行分组。(图片由作者提供)
让我们创建包含这些身份的 Azure 数据浏览器表:
.set Profiles <|
datatable (ProfileId: int, Email: string, CookieId: guid) [
10002, '[emma@hotmail.com](mailto:emma@hotmail.com)', '657d31b9-0614-4df7-8be6-d576738a9661',
10003, '[oliver@hotmail.com](mailto:oliver@hotmail.com)', '0864c60d-cc36-4384-81a3-e4c1eee14fe7'
].set Customers <|
datatable (CustomerId: int, ProfileId: int) [
1001, 10002,
1005, 10003
].set Subscriptions <|
datatable (CustomerId: int, SubscriptionId: guid) [
1001, 'fd10b613-8378-4d37-b8e7-bb665999d122',
1005, '55979377-ed34-4911-badf-05e07755334c'
].set SupportCustomers <|
datatable (SupportCustomerId: int, Email: string) [
21, '[emma@hotmail.com](mailto:emma@hotmail.com)',
22, '[oliver@hotmail.com](mailto:oliver@hotmail.com)'
]
这些表来自不同的系统,但最终被纳入我们的数据平台。一旦我们有了这些原始数据,我们就可以通过分组所有相关的 id 来构建一个密匙环。我们的 keyring 表的模式由一个唯一标识一组相关身份的GroupId、一个指定我们在每行中捕获哪个身份的KeyType和一个作为身份值的KeyValue组成。
这是第一批摄入:
.create table Keyring(GroupId: guid, KeyType: string, KeyValue: string).append Keyring <| Profiles
| project GroupId=new_guid(), KeyType='ProfileId', KeyValue=tostring(ProfileId).append Keyring <| Profiles
| join (Keyring | where KeyType == 'ProfileId'
| project GroupId, ProfileId=toint(KeyValue)) on ProfileId
| project GroupId, KeyType='Email', Email.append Keyring <| Profiles
| join (Keyring | where KeyType == 'ProfileId'
| project GroupId, ProfileId=toint(KeyValue)) on ProfileId
| project GroupId, KeyType='CookieId', tostring(CookieId)
.append类似于.set,但是当.set创建一个新的表时,.append期望一个现有的表被接收到。
我们首先生成新的 GUIDs,并将键类型设置为'ProfileId',将键值设置为Profiles表中的ProfileId。接下来,我们将Profiles表与ProfileId上的Keyring表连接起来,这给了我们GroupId,我们添加了电子邮件。第三,我们将Profiles表与ProfileId上的Keyring表连接起来,并添加了CookieId值。
此时,我们将Profiles表“展开”到密匙环模式中。让我们给它添加Customers和Subscriptionsid:
.append Keyring <| Customers
| join (Keyring | where KeyType == 'ProfileId'
| project GroupId, ProfileId=toint(KeyValue)) on ProfileId
| project GroupId, KeyType='CustomerId', tostring(CustomerId).append Keyring <| Subscriptions
| join (Keyring | where KeyType == 'CustomerId'
| project GroupId, CustomerId=toint(KeyValue)) on CustomerId
| project GroupId, KeyType='SubscriptionId', tostring(SubscriptionId)
这与我们之前所做的类似,除了当我们引入SubscriptionId时,我们必须加入CustomerId而不是ProfileId。这不是问题:我们可以加入任何我们已经在钥匙圈中的身份来找到GroupId并且用其他身份扩展这个组。
最后,让我们添加客户支持 id,加入电子邮件:
.append Keyring <| SupportCustomers
| join (Keyring | where KeyType == 'Email'
| project GroupId, Email = KeyValue) on Email
| project GroupId, KeyType='SupportCustomerId', tostring(SupportCustomerId)
了解钥匙圈
现在,我们将所有这些不同表中的 id 整合到一个表中,并将它们分组在一起。如果我们查询密匙环表,我们会看到类似于下表的内容:
钥匙圈表
现在,给定系统中的任何 ID,我们可以轻松地检索所有连接的 ID。例如,给定一个SupportCustomerId (21),我们可以检索所有相关的键:
Keyring
| where KeyType == 'SupportCustomerId' and KeyValue == tostring(21)
| project GroupId
| join kind=inner Keyring on GroupId
钥匙圈使我们能够关联不同的数据集,并全面了解我们的系统是如何使用的。我们使用一个模式,在这个模式中,我们可以根据需要插入任意多种类型的 ID,用一个KeyType列给出 ID 的类型,用KeyValue列存储 ID 值。
制作钥匙圈的步骤如下:
- 生成一个组 ID,并从获取一个身份开始(在我们的示例中为
ProfileId)。 - 对于每个新的身份类型,在一个已知的连接上用密匙环加入以获得
GroupId,然后将新的身份添加到它们各自的组中。
下面是一个关于构建钥匙圈的图形视图:
身份钥匙圈为图
考虑身份密匙环的另一种方式是作为一个图问题。系统中的每个身份代表图中的一个节点,每个已知的连接代表一条边。例如,
ProfileId和Profiles表中),我们在这些节点对之间有边。构建一个密匙环意味着识别所有相关联的身份组。在图的术语中,这意味着识别图的所有连接的组件,并给每个连接的组件分配一个
GroupId。提醒一下,图中的连通分支是一个子图,其中任何一对节点之间都有一条路径,并且没有其他到超图的连接。构建密匙环的另一种方法是使用图形数据库:我们加载所有节点和边,然后遍历以找到连接的组件。
钥匙圈为我们提供了系统中所有身份的统一视图。这使我们能够关联原本不相交的数据集,并跨多个团队和系统连接信息。
IEEE-CIS 欺诈检测-排名前 5%的解决方案
Kaggle 竞赛 IEEE-CIS 欺诈检测的前 5%解决方案

我知道您或您身边的任何人都是一些欺诈活动的受害者。我们无法直接控制人们通过欺诈活动轻松赚钱。在线交易是可能发生欺诈交易并导致金钱损失的领域之一。
在这场比赛中,我们将使用机器学习技术来检测在线欺诈交易。我们的目标是实施一个在 Kaggle 排行榜上排名前 5%的解决方案,这并不是一件容易的事情。IEEE-CIS 欺诈检测是由 IEEE 计算智能学会组织的 Kaggle 竞赛。Vesta Corporation 是担保电子商务支付解决方案的先行者之一,它为本次比赛提供了数据集。
在这篇博客中,我将很好地概述我是如何达到前 5%的结果的。我不会解释每一行代码。相反,我将给出我的解决方案的一个很好的概述。你可以随时在这里查阅我的 代号 。
关于数据集
数据被分成两个文件 identity 和 transaction,这两个文件通过 TransactionID 连接,分别用于训练和测试。因此,我们有 4 个文件—训练事务、训练身份、测试事务和测试身份。需要注意的是,并非所有交易都有相应的身份信息。
让我们熟悉一下目前的各种栏目:
交易数据
- 交易 id: 与交易相关的 Id
- TransactionDT:给定参考日期时间的时间增量(不是实际时间戳)
- 交易金额: 以美元计的交易支付金额
- product CD【分类】:产品代码(每笔交易的产品)
- card 1–6【分类】:支付卡相关信息,如卡种、国家等
- addr1,addr2 【分类】:地址信息
- dist1,dist2: 一些距离信息
- P _ email domain【分类】:购买者的邮箱域。
- R _ email domain【分类】:收件人的邮件域。
- C1-C14: 计数,比如找到多少个地址与支付卡相关联等。实际意义被掩盖了。
- -D15:时间差 ,如前几笔交易之间的天数等。
- M1-M9 【分类】:匹配,如名片上的姓名和地址等。
- vxxxx:Vesta 设计了丰富的功能,包括排名、计数和其他实体关系。
身份数据
- 交易 id: 与交易相关的 Id
- 设备类型 【分类】:用于交易的设备类型
- 设备信息 【分类】:关于所用设备的更多信息
- id1–38【分类+数字】:网络连接信息、浏览器信息等(id12–38 为分类信息)
注意:实际上我们无法获得关于列的确切信息。主要是因为我们在处理交易数据时的安全考虑。
商业目标
发现欺诈交易的主要业务目标如下:
- 如果发现欺诈交易,公司应立即封锁该卡。
- 我们应该能够预测欺诈交易的概率
- 我们不应将欺诈交易预测为非欺诈交易。反之亦然。所以应该注意精确度和召回率。
我的方法
- 对每一列进行探索性的数据分析,了解每个特性的影响。
- 建立一个基线模型。
- 创建新特征,并查看这些特征是否能提高模型的性能。如果是,保留这些功能,否则放弃这些功能。为此,我使用了正向特征选择。
- 迭代地继续这个方法,直到我们得到一个好的分数。
电子设计自动化(Electronic Design Automation)
在这里解释每一个特性的分析并不理想。我将解释我的一些有趣的观察。您可以从我的存储库中找到其余的分析。
让我们先加载数据
现在让我们加载保存的文件

我们有形状的训练数据(590540,434)和形状的测试数据(590540,434)。让我们看看在训练和测试数据中丢失了多少值
我们将看到 90 %以上的值缺失的列。


我们可以看到像 id_21、id_22、id_23、id_24、id_25、id_26、id_27、id_01、id_07、id_08 这样的列有超过 99%的缺失值。我们最好放弃这些功能。
Is _ 诈骗(目标特征)
这是我们需要预测的目标变量。让我们看看它是如何分布的。

观察结果:
不出所料,我们可以看到这个阶层严重失衡。这里 96.5%的交易不是欺诈,其余 3.5%的交易是欺诈。我们将选择 ROC 曲线下面积(AUC)作为 ML 问题的度量。
为什么不平衡数据集的准确性不行?
考虑这样一个场景,我们有一个不平衡的数据集。例如,考虑信用卡欺诈检测的情况,其中 98%的点数为非欺诈(1),其余 2%的点数为欺诈(1)。在这种情况下,即使我们预测所有的点都是非欺诈的,我们也将获得 98%的准确性。但实际上,情况并非如此。所以我们不能用准确性作为衡量标准。
什么是 AUC ROC?
AUC 是 ROC 曲线下的面积。它告诉我们这个模型在多大程度上能够区分不同的类。AUC 越高,模型预测 0 为 0 和 1 为 1 的能力越强。用 TPR 对 FPR 绘制 ROC 曲线,其中 TPR 在 y 轴上,FPR 在 x 轴上。
经过分析,我发现的另一件事是,在大多数情况下,如果客户的一笔交易是欺诈,他/她之前的所有交易都会被标记为欺诈。但并不是所有的情况都是如此。这里面也有例外。这使得我们的预测更加困难。
注意:我可以通过创建自己的 uid 来识别客户。当我们继续进行的时候我会解释的。
交易日期
这表示与给定参考日期时间的时间差异。例如,86400 可以是特定时间的 86400 秒,例如:从 2010 年 2 月 12 日 12:05:09 pm 开始。在这里,我们必须检查在训练中获得的数据,并且测试是随时间连续的。这对于训练测试分割是重要的。这意味着训练数据应该来自较早的时期,而测试数据应该来自较晚的时期。

观察结果:
- 两者之间略有差距,但除此之外,定型集来自早期,测试数据来自后期。这可能会影响列车验证,应使用分割或交叉验证技术。
- 另一个观察结果是,在某些日子里,事务的数量超过 10000
现在让我们看看一周中不同的日子以及不同时间的欺诈交易。

欺诈交易 vs 天数

欺诈交易 vs 小时
我们可以看到,在第 3 天,欺诈性交易非常少,同样,在第 7 个小时,欺诈性交易的百分比比其他时间高。
交易金额
这是与每笔交易相关的金额。让我们看看交易金额是如何分布的。(我还记录了交易金额,以便更好地解释)


交易金额分布
观察结果:
- 我们可以看到,在训练数据中有一个点的数量> 30000。我们最好移除离群值,因为它会影响我们的模型(尤其是基于距离的算法,如逻辑回归、knn 等)。)在预测。
- 此外,像这样的异常值会导致过度拟合问题。例如,基于树的模型可以将这些离群值放在叶节点中,这些节点是噪声,不是一般模式的一部分。因此,我决定删除训练集中大于 30,000 的值。
- “LogTransactionAmt”大于 5.5 (244 美元)且小于 3.3 (27 美元)的交易似乎具有更高的欺诈频率和概率密度。另一方面,那些“LogTransactionAmt”从 3.3 到 5.5 的人更有可能是合法的。
产品光盘
这是与每笔交易相关的产品代码。让我们看看在不同的产品代码中有多少百分比的交易是欺诈。

乘积码
我们可以看到,在与产品代码 C 相关的交易中,大约有 12%是欺诈交易。类似地,近 6%的交易是产品代码欺诈。
维列斯
我们有从 D1 到 D15 的 D 列。这些表示时间差,例如前一次交易和当前交易之间的天数、当前交易和第一次交易之间的天数等。尽管他们没有给出关于 D cols 的清晰的理解,但是我们在很好的分析之后得到了一些见解。
- 我们假设 D1 是开始使用信用卡的那一天。从交易日中减去这一数字将得出每个客户的内容价值。
df_train['D1n'] = df_train['D1'] - df_train.TransactionDT/np.float32(24*60*60)

正常化前

标准化后
我们对所有的 D 列进行了标准化,并允许模型决定哪些是重要的,哪些是不重要的。
卡 1-卡 6
这些列表示与卡相关的支付信息。从这些特性中可以观察到一些有趣的现象:
- card1 中的值范围很广。如果我们看到分布有重叠。仅 card1 无法区分欺诈性交易和非欺诈性交易。card1 中没有缺失值。
- card2 中丢失了一小部分数据。与 card1 相似,card2 中也有许多唯一的值。card3 与其他卡列的相关性较低。

- 卡 4 表示客户使用哪种卡,visa、Mastercard、American Express 还是 discover。类似地,card6 表示卡的类型——借记卡或信用卡。
P-邮箱域和 R-邮箱域:分别是购买者和接收者的邮箱域。大约 76%的值在 R-email 域中丢失。在购买者的电子邮件域名中,大多数邮件来自 gmail.com。在这些欺诈交易中,超过 90%的交易来自域名 protonmail.com,这是一个严重的问题。
addr1 和 addr2: 这是一些与客户端相关的地址相关信息。addr1 和 addr2 都丢失了大约 11%的数据。addr1 中大约有 332 个唯一值,addr2 中有 74 个唯一值。
dist1 和 dist2: 这可能表示交易地点和持卡人地址之间的距离。这只是我的假设。dist2 中大约有 93%的值缺失。dist1 和 dist2 之间没有相关性。而 dist2 与所有其他属性的相关性非常弱。
C1-C14: 这些是计数之类的信息。C3 与 Cxx 的其他功能有些不同。对于来自 box pot 的 c3,很明显,在欺诈交易的训练数据中没有超过 3 的值。对于无欺诈交易,该值的范围为 0 到 26。此外,C3 与其他 Cxx 列的相关性很弱。
M1-M9: 超过 50%的值在 M 列中缺失。这些特征不能清楚地区分欺诈交易和非欺诈交易。
V1-V399: 这些是灶神星设计的特征。这些是被屏蔽的信息,可以是排名、计数或其他实体关系。许多特征之间存在强相关性(> 0.9)。如果可能的话,我们最好减少这些特征的数量,因为这样可以降低模型的计算复杂度。
基线模型
作为基线,我们创建了 3 个模型。—逻辑回归、随机森林和 XG Boost。在小的超参数调整之后,我们获得了以下结果。
逻辑回归: 0.84018(训练 AUC),0.84245(测试 AUC)
随机森林: 0.9030(训练 AUC),0.8600(测试 AUC)
Xgboost: 0.994(训练 AUC),0.9234(测试 AUC)
由于 Xgboost 提供了更好的性能,我们决定坚持使用它。我们还绘制了特性重要性图,以获得各种特性影响的基本概念。对于 Xgboost,我们得到了 0.9004 的公共排行榜分数和 0.9238 的私人 LB 分数。
更多功能工程
TransactionDT 和 TransactionID
两列都是唯一的。一个是时间相关信息,另一个是唯一 id。把这个加到模型里没有太大意义。所以我们去掉了这些特征。
减少 V-Cols
从我们的 Xgboost 模型中,在绘制了特性重要性之后,我们知道所有的 V 列对模型没有太大的贡献。同样通过分析,我们发现在几个“Vxx”列之间存在很强的相关性。所以我们决定通过相关性分析来减少列数。
如何根据 NaN 值减少列?
- 根据缺失值的数量对列进行分组,例如,如果有 4 列 v1、v2、v3 和 v4。如果 v1 和 v3 有 56 个缺失值,v2 有 21 个缺失值,v4 有 5 个缺失值,则我们有 3 个组['v1 ',' v3'],['v2']和['v4']
- 在每个组中,对于该组中的每一列,找出与其他列的相关性,并且只取相关系数> 0.75 的列。将具有公共元素的最大列表作为子组。每个组包含几个子组。例如:如果我们有[[v1,v2],[v6],[v1,v4,v2,v5],[v5,v4]],我们的输出将是[[v1,v2,v4,v5],[v6]]。现在从每个子组中选择具有最多唯一值的列。例如,在子组[v1,v2,v4,v5]中,让 v2 具有最唯一的值。所以 out 输出变成[v2,v6]。

上图显示了我们产出的一部分。我们将对每个组进行相关性分析,以减少列数。
此处的列“V35”、“V40”、“V41”、“V39”、“V38”、“V51”、“V37”、“V52”、“V36”、“V50”、“V48”、“V42”、“V43”、“V44”、“V46”、“V47”、“V45”、“V49”缺少值。所以他们组成一个团体。现在我们将绘制一个相关矩阵。

相关图
接下来,我们将根据相关性对它们进行分组。我们将相关性> 0.75 的列视为同一组。
[['V35 ',' V36'],['V37 ',' V38'],['V39 ',' V40 ',' V42 ',' V43 ',' V50 ',' V51 ',' V52'],['V41'],['V44 ',' V45'],['V46 ',' V47'],['V48 ',' V49']]
我们将使用上面的函数来减少这种情况,并以下列结束:
['V36 ',' V37 ',' V40 ',' V41 ',' V44 ',' V47 ',' V48']。
类似地,我们将对所有组都这样做,最后得到下面的 V 列。
['V1', 'V3', 'V4', 'V6', 'V8', 'V11', 'V13', 'V14', 'V17', 'V20','V23', 'V26', 'V27', 'V30', 'V36', 'V37', 'V40', 'V41', 'V44', 'V47', 'V48', 'V54', 'V56', 'V59','V62', 'V65', 'V67', 'V68', 'V70', 'V76', 'V78', 'V80', 'V82', 'V86', 'V88', 'V89', 'V91', 'V96','V98', 'V99', 'V104', 'V107', 'V108', 'V111', 'V115', 'V117', 'V120', 'V121', 'V123', 'V124', 'V127','V129', 'V130', 'V136', 'V138', 'V139', 'V142', 'V147', 'V156', 'V162', 'V165', 'V160', 'V166', 'V178','V176', 'V173', 'V182', 'V187', 'V203', 'V205', 'V207', 'V215', 'V169', 'V171', 'V175', 'V180', 'V185','V188', 'V198', 'V210', 'V209', 'V218', 'V223', 'V224', 'V226', 'V228', 'V229', 'V235', 'V240', 'V258','V257', 'V253', 'V252', 'V260', 'V261', 'V264', 'V266', 'V267', 'V274', 'V277', 'V220', 'V221', 'V234','V238', 'V250', 'V271', 'V294', 'V284', 'V285', 'V286', 'V291','V297', 'V303', 'V305', 'V307', 'V309', 'V310', 'V320', 'V281', 'V283', 'V289', 'V296', 'V301', 'V314', 'V332', 'V325', 'V335', 'V338']
减少 V 列不会降低局部验证的分数。我们在训练数据上得到 0.993 AUC,在 cv 数据上得到 0.9231。即使它将私有 LB 分数减少了 0.001,我们也能够减少 211 列,从而提高了模型的计算复杂性。因此,放弃这些特性是一个更好的方法。
减少 C 列和 M 列
我也试图以类似的方式减少 C 和 M 列。但是它降低了超过 0.002 AUC 的分数。所以到现在为止,我还留着。
工程 D 类
从卡格尔的讨论中,我了解到 D1 是信用卡开始使用的日子。从交易日中减去这一数字将使每个客户的价值几乎保持不变。我对所有的 D 级都采用了同样的方法。为了检查新创建的功能是否有用,我选择了一个向前的功能。

在外
如何选择要修改的 D 列?
- 我们知道我们的基线提供了 0.923 的 AUC。这将是我们最初的基本分数
- 在第一次迭代中,我添加了归一化的 D1,删除了原始的 D1,并创建了一个模型,并记录了测试的 AUC。现在,对从 D1 到 D15 的所有 Dcols 重复这一过程。每次记录 AUC,并选择从基线(0.923)进一步改善 AUC 的 Dcol。这个 AUC 成为我们最好的分数。
- 在下一次迭代中,包括较早的 D 列,并且对所有剩余的列以及 D1 重复步骤 1。
- 重复迭代,直到 AUC 没有改善。
最后,我们以规范化“D15”、“D4”、“D2”、“D 11”和“D10”结束。我们当地的 AUC 变成了 0.934。
编码功能等
接下来,我创建了以下特征:
- 星期几:交易发生的星期几
- 小时:交易发生的小时
- 美分:与交易金额相关的美分
- LogTransactionAmt:交易金额的日志
- P _ email _ company:P _ email _ domain 所属的公司。它是从互联网上获得的。
- Device_corp:我根据设备信息创建了这个专栏。它基本上是从设备信息中获得的母公司详细信息。
- 小时欺诈状态:从我们在进行 EDA 时获得的图表中,我们知道每小时欺诈交易的百分比。在此基础上,我创建了 4 个类别非常低,低,中,高,这样每个小时都属于这些类别。
然后我使用 FFS,看看哪些功能可以改善结果。但是只有小时栏把分数提高到了 0.9338。正向特征选择的功能如下:
接下来,我创建了一些编码特性。在此之前,让我们熟悉以下功能。
基于这些,我创造了大约 45 个特征。
然后,我做了一个前向特征选择,并选择了那些提高模型性能的特征。结果,我得到了以下 6 个新特性
card4_addr1_R_emaildomain', 'card2_FE','card1_FE', 'card1_addr1_R_emaildomain', 'card3_addr1_P_emaildomain', 'card1_addr1','card4_addr1_P_emaildomain_FE'
这些提高了我的本地验证分数到 0.9634。
基于 UID 的功能
为什么基于 UID(识别客户)的功能会起作用?
假设我们有以下数据:

在这里,如果我们构建一个决策树,它可能如下所示

没有 UID 的决策树
在这种情况下,我们可以看到,在 11 分中,有 8 分被正确分类。假设我们可以识别个别客户。我将向你解释我如何能找到它。如果我能找到这样的 uid,我们的数据如下。

让我们从这个 uid 创建一个新的组聚合。让我们在此基础上构建一个决策树。

基于 uid 的决策树
我们可以看到,在这种情况下,所有 11 个点都被正确分类。即使在真实情况下这不会是准确的,但它肯定可以提高模型的性能。所以找到 uid 是提高我们分数的关键。
经过分析,我了解到 card1、d1 和 addr1 将帮助我识别客户。所以我把这三列组合起来,作为 uid。
现在,我用这个 uid 创建了几个平均值、标准偏差和几个特征的计数聚合。我使用下面的函数进行聚合。
我基于 uid 创建了大约 100 个聚合特性。但是在不同子集上进行正向特征选择之后,我最终得到了 9 个新特征。
'M9_uid_mean' , 'M5_uid_mean' , 'D2_uid_mean' , 'D15_uid_mean' , 'C13_uid_mean' , 'C9_uid_mean' , 'C1_uid_mean' , 'C11_uid_mean' , 'TransactionAmt_uid_std'
这使我的本地简历得分提高到 0.9470。来自私有 leader 板和公共 leader 板的结果分别是 0.9158 和 0.9423。
从分析来看,很明显没有一个 uid 是完美的。所以我试图通过将 card1 和 addr1 结合起来创建一个新的 uid。然后,我使用新的 uid 创建了类似的聚合,并查看哪些新特性改进了正向特性选择的结果。我总结了以下 4 个新特性:
'M4_uid2_mean' , 'M1_uid2_mean' , 'M7_uid2_mean' , 'M8_uid2_std'
这让我的本地 CV 提高到了 0.9481。此外,新的私立和公立 LB 分数分别为 0.917 和 0.940。因此,基于 UID 的特性确实有助于我们提高模型的性能。
超参数调整和交叉验证
这是两个重要的技术,确实提高了我的算法的性能。正如你所知道的,我们的模型在早些时候进行了调整,但是目前,已经引入了许多新功能。让我们找出符合模型的最佳参数。为此,我使用了 Sklearn 的随机搜索简历。
经过随机搜索后,我们得到了以下参数
n_estimators=5000,max_depth=12,learning_rate=0.002,
subsample=0.8, colsample_bytree=0.4
如何做交叉验证?
首先,我们创建了一个新列来指示交易的月份。
我们将使用以月为组的 GroupKFold 来预测 test.csv。训练数据为 2017 年 12 月、2018 年 1 月、2018 年 2 月、2018 年 3 月、2018 年 4 月、2018 年 5 月这几个月。我们把这些月份称为 12 月、13 月、14 月、15 月、16 月、17 月。Fold 将在第 13 个月到第 17 个月进行训练,并预测第 12 个月。请注意,第 12 个月的唯一目的是告诉 XGB 何时提前停止,我们实际上并不关心向后的时间预测。在第 13 到 17 个月训练的模型也将预测在时间上向前的 test.csv。
我们的代码如下所示:
最后,我们交叉验证后的本地 CV 分数变成了 0.951265。新的私有和公共 LB 分数分别变为 0.928873 和 0.954825。在 6351 家公司中,它大约排在第 226 位。(前 3.5%)。

我们在整个项目中的进展如下:

进步
你可以在 https://github.com/arunm8489/IEE-CIS-Fraud-detection 查看我的全部代码。这不是结束。尽管如此,还是有一些进步。一些我没有尝试过的方法:
- 我的全部工作都围绕着 XgBoost 算法。你可以尝试其他算法,如 Lightgbm、cat boost 等。
- 我已经根据 NaN 值的数量和皮尔逊相关系数减少了 v 列。您还可以在每个 NaN 组中尝试 PCA,看看它是否能提高性能
- 我已经应用了前向特征选择来选择重要的特征。我把它应用于不同的子集。在不同的子集上尝试会产生不同的结果。
参考
- https://www.kaggle.com/cdeotte/xgb-fraud-with-magic-0-9600
- https://www.kaggle.com/artgor/eda-and-models
- https://www . ka ggle . com/ysjf 13/cis-欺诈-检测-可视化-特征-工程
- https://www . ka ggle . com/xh lulu/IEEE-fraud-XG boost-with-GPU-fit-in-40s
如果数据科学感觉像是一场斗争,你可能正走在正确的道路上
作者聚焦
"大多数好事都伴随着些许不适。"
在 Author Spotlight 系列中,TDS 编辑与我们社区的成员谈论他们在数据科学领域的职业道路、他们的写作以及他们的灵感来源。今天,我们很高兴与罗伯特·贾尔科·兰格进行对话。

图片由罗伯特·兰格提供
罗伯特是柏林科技大学的二年级博士生,致力于大型多智能体系统的强化学习。此前,他在伦敦帝国理工学院获得了计算机硕士学位,并在柏林爱因斯坦神经科学中心工作时涉足了认知神经科学。他还是一位多产的 TDS 作者,撰写深度学习的最新趋势和该领域的技术进步。他喜欢在一年中的任何时候狼吞虎咽地吃冰淇淋,喜欢和他心爱的(据他说,“更上镜”)四条腿的同伴一起散步。
你目前正在完成深度强化学习的博士学位——你能告诉我们你是如何实现这一目标的吗?
在我读经济学本科期间,我迷上了统计学和博弈论课程。他们觉得很有力量,我在图书馆花了很多时间阅读约翰·纳西和计量经济学技术。那时,我确信我可以更接近地回答最令我着迷的一个问题:随着时间的推移,我们如何理解人类的决策?
我喜欢蒙地卡罗模拟和斯坦伯格平衡等概念的神秘感觉。但我也知道我必须更深入,了解更多的技术细节。我决定在巴塞罗那攻读数据科学硕士学位,然后在帝国理工学院攻读另一个计算机科学硕士学位。
在那段时间里,我参加了几门计算神经科学课程,并致力于分层强化学习。一切都很有趣:认知、运动控制、变分推理和非凸优化。我费了很大劲才读完我感兴趣的所有东西。在那一点上,我知道我必须在机器学习、神经科学和集体决策的交叉点上攻读博士学位。
从一个学科起步,然后找到通往另一个学科的道路,这很难吗?
一开始,从经济学转型是一项精神挑战。只要我还在经济大学的泡沫中,下一步似乎就是攻读经济学博士学位。决定去巴塞罗那对我来说是一个很大的飞跃——19 岁住在国外,不会说西班牙语或加泰罗尼亚语,不认识任何人。当时德国没有类似的项目,我想推动自己。我从来没有真正回头看,并相信大多数美好的事情都伴随着一丝不适和挣扎。
然后还有所有需要克服的技术障碍。大多数本科经济学数学涉及标量值:GDP,通货膨胀,失业率。你会学到很多真实的分析,以及如何推导家庭和投资问题的一阶条件。不过,没有那么多线性代数。我记得花了几个周末在巴塞罗那图书馆自学标准工程数学(傅立叶分析、泰勒展开等)。).这同样适用于我的编程技能和基本的东西,比如通过 ssh 连接到远程机器。
我一直在挣扎,但随着我建立起直觉和知识,事情一天天变得越来越自然和容易。但我也不得不说,我有一群很棒的老师、导师和同学,他们给了我很多帮助。由于数据科学如此广泛,它吸引了一群不同的人,他们都非常了解一些东西。
是什么促使你开始为更广泛的读者撰写关于数据科学的文章?
我喜欢学习,可视化概念框架,重组我的想法。写博客可以让我把所有这些东西结合在一起。一开始,出版需要一些勇气。你永远不知道——也许人们不会喜欢它。但是随着时间的推移,我开始将写作过程的大多数方面游戏化:我的许多博客文章都是从周末的业余项目开始的。我一般会有一个大概的想法,想多了解一个话题。我做了一些研究,做了笔记,编写了一些小的原型。之后,把所有的东西放到一个视觉上令人愉悦的帖子里并不需要太多时间,预期的满足感真的很激励人。
我也喜欢和那些非常关心内容的读者交流。我接触了很多很棒的人,否则我可能不会遇到他们。博客有如此多未知的外部性。接下来,你可能会因为分享你的激情而获得你梦想中的工作。
写博客的过程中,你最享受的是什么?
这听起来可能很傻:我绝对喜欢为我的博客文章整理缩略图,并在我的 iPad 上绘制可视化插图。这真的很放松,也是我写作时所期待的。以下是我最喜欢的几个例子:

图片由罗伯特·兰格提供
我也喜欢记录我的成长。浏览我的第一批帖子总是让我起鸡皮疙瘩。它基本上是我从那时起的概念和想法的一个小的精神相册。有些已经改变了,有些发展成了项目,或者给我接上了新朋友。
写博客的另一个重要部分是它帮助我提高写作技巧。作为一名博士生,你写得不够多。通常,当你要结束一个项目时,你才开始用语言来表达你的想法。那不是很多经验——尤其是如果在某个时候你不得不自己写拨款申请的话。写博客帮助我学会组织我的想法,以及我写博客前所做的工作。
数据科学和机器学习是动态领域;在短期内,有什么你特别想看到的发展吗?
谈到我的研究领域(多智能体强化学习),我对最近在跟踪大规模动物行为方面的革命感到非常兴奋。从零开始学习协调真的很难,因为联合行动空间在所考虑的代理数量上呈指数增长。为了在集中控制之外取得真正的进展,比如 DeepMind 的 AlphaStar 项目,我们需要找到正确的归纳偏差。
动物行为可以为我们提供很多关于什么可以促进大群体学习的见解。这包括探索鱼群的行为和社会学习任务中的信息共享。将高分辨率的集体跟踪数据纳入算法是一个真正具有挑战性和令人兴奋的前进方向。
我也希望看到更多的人分享他们对专业话题的经验和见解。就个人而言,我从没有成功的实验中学到了最多的东西——良好的超参数范围,训练非标准模型和放大事物的技巧。分享你的交易技巧是很有价值的。而且每个人都有自己知道的值得与世界分享的东西。
好奇想了解更多关于 Robert 的工作和研究兴趣?一个好的起点是他的中等身材。通过访问他的 GitHub 页面,或者在 Twitter 上找到他。以下是 Robert 最近在 TDS 上的一些亮点。
- 2021 年 4 月要读的四篇深度学习论文 ( TDS ,2021 年 4 月)
Rob 策划的一系列科学论文对社区来说是一项伟大的服务,让数据科学家能够与最前沿的研究保持同步。(当你在这里的时候,看看他的一些早期版本。) - 彩票假说:一项调查 ( TDS ,2020 年 6 月)
这(非常)深入地探究了深度学习中最近最流行的概念之一,包括一个广泛的概述和一个关于该主题的关键文章的详细文献综述。 - JAX 进化中的神经网络 ( TDS ,2021 年 2 月)
Rob 的帖子并没有止步于高层理论。这个实践教程就是一个很好的例子,向读者展示了 JAX 图书馆如何“驱动下一代可扩展的神经进化算法” - iPad Pro 的机器学习工作流程 ( TDS ,2020 年 5 月)
如今,先进的数据科学工作甚至机器学习研究可以在任何地方进行,包括在你的沙发上。这篇文章向我们展示了 Rob 灵活的 iPad 设置,并讨论了他最喜欢的一些应用程序。
请继续关注我们的下一位特色作者,即将推出!(如果你对你想在这个空间看到的人有建议,请在评论中给我们留言!)
如果 Python DateTime 帮不上忙,试试日历模块

使用 Python 中的日历库来填补 datetime 模块中的空白
如果你用过 Python 编程语言,我敢肯定你一定用过datetime模块,也可能你已经搞定了。的确,datetime模块作为一个重要的 Python 内置库,可以成为“Python”的代表之一。它的子模块timedelta是我最喜欢的创意之一。
然而,datetime模块在设计上有一些限制,这使得它有时不太方便。这完全没问题,因为 Python 中有大量的内置库,每个库都有自己的侧重点。在本文中,我将介绍calendar模块,它将填补datetime模块中的一些空白。
简单的例子

由 valentinsimon0 在 Pixabay 上拍摄的照片
datetime模块被设计为关注基本的日期和时间对象,因此它处理“偶数”日期/时间单位,如秒、小时和天。你注意到没有“月”吗?
例如,我们有一个日期时间对象t1,我们想给它增加 1 天。这将非常容易,因为 1 天= 24 小时= 86400 秒,无论是哪一天。
t1 + timedelta(days=1)
但是,如果我们要给它加上 1 个月呢?有几个问题阻止我们在datetime模块的范围内解决这个问题。
- 一个月中的天数是方差(28、29、30 或 31 天)
- “下个月的同一天”可能不存在,例如 2021 年 1 月 30 日(没有 2021 年 2 月 30 日)
因此,当我们需要解决任何与日历特别相关的问题时,datetime模块相当有限或不方便。在这种情况下,不要通过使用datetime定义日历规则来重新发明轮子,只需使用 Python 内置的calendar模块。
导入日历模块
Python 中的日历模块提供了许多功能。让我们来看看一些非常有用的典型例子。当然,我们需要先导入模块。
import calendar as cal
区域特定日历

当我们提到日历时,它必须伴随着地区。也就是说,不同的国家/文化/语言会有不同的定义。这一点 Python 处理的很好。语言环境信息将由操作系统决定。
使用日历模块,我们可以很容易地获得这些信息。
day_name—一周中的某一天day_abbr—星期几的缩写month_name—月份名称month_abbr—月份名称的缩写
以下是列出这些数组的一些示例。

month_name和month_abbr的第一个元素为空的原因是为了方便起见。例如,我们可以通过month_name[1]直观地得到第 1 个月的名称,而不是使用索引 0。
现在,让我们来看看日历模块中的技巧。
闰年

由 mohamed_hassan 在 Pixabay 上拍摄的照片
处理日历时的困难之一是确定一年是否是闰年。这就像调用一个函数并将年份作为参数传递一样简单。
cal.isleap(2020)

如果我们想知道一年中有多少个闰年,我们可以使用leapdays()函数。
cal.leapdays(2000, 2020)

不要被函数的名字搞糊涂了。事实上,2 月 29 日被称为闰日。所以,只有闰年才会有闰日。因此,闰年数=闰日数。
月历

通过使用函数monthcalendar(),我们可以得到一个包含一个月中所有日子的 2D 列表。例如,2021 年的四月看起来像这样。
cal.monthcalendar(2021, 4)

你注意到子列表保证有 7 个元素了吗?是的,对于每个子列表,索引代表一周中的某一天。也就是说,索引 0 表示星期一,索引 6 表示星期日(默认情况下)。
这为什么有用?
我给你举个例子。你能列出 2021 年 4 月的所有星期一吗?
[week[0] for week in cal.monthcalendar(2021, 4) if week[0] != 0]

这个月历会有更多创造性的使用方式。你自己去找就行了:)
月份范围和星期几查询

照片由 Amber_Avalona 在 Pixabay 上拍摄
另一个简单但有用的函数是monthrange()函数。给定某个月,它将返回一个元组
- 一个月的第一天是星期几
- 一个月的总天数
例如,我们可以通过简单地调用cal.monthrange(2021, 4)来获得 2021 年 4 月的上述信息。

我们还可以通过调用如下的weekday()函数来查询某一天是星期几。
cal.weekday(2021, 4, 4)
如果我们想把它转换成可读性更强的东西,只需使用 locale 数组。
cal.day_name[cal.weekday(2021, 4, 4)]

显示日历

tiger ly 713在 Pixabay 拍摄的照片
我们甚至可以使用日历模块用 Python 打印一个“日历”。这极其简单。我们只需要调用calendar()函数并如下传入年份。
year = cal.calendar(2021)
print(year)

我们还可以通过三个参数来控制这个日历的显示风格
c:月份之间的填充w:天与天之间的填充l:周(行)之间的填充
year = cal.calendar(theyear=2021, w=3, l=1, c=8)
print(year)

我们也可以显示特定月份的日历,而不是显示一整年的日历。参数完全一样。
cal.prmonth(2021, 4, w=3, l=2)

摘要

由 kaboompics 在 Pixabay 拍摄的照片
在本文中,我介绍了 Python 内置的calendar模块。大多数时候我们可以使用datetime模块来解决任何与日期或时间相关的问题。当涉及到日历时,datetime模块可能会遇到它的不足,因为它不是为这类问题设计的。
当我们处理一些特定的日历问题时,例如闰年,日历模块非常方便。通过使用这样的内置模块,Python 中的开发会更快!
https://medium.com/@qiuyujx/membership
如果你觉得我的文章有帮助,请考虑加入 Medium 会员来支持我和数以千计的其他作者!(点击上面的链接)
如果新加坡有最低工资,会是多少?

迈克尔·朗米尔在 Unsplash 上的照片
使用机器学习回归模型预测新加坡的最低工资
新加坡政府没有规定新加坡所有工人的最低工资,无论是本地工人还是外籍工人。人力部(MOM) 的官方立场是,由市场需求和劳动力供给决定的有竞争力的薪酬结构将有助于公司激励员工努力工作。精英管理的价值观根植于新加坡文化,这使得妈妈的这一立场不足为奇。
2018 年,根据减少贫富不平等的努力,新加坡在 157 个国家的乐施会指数中排名第 149 位。乐施会认为,没有最低工资是新加坡出现这种社会经济差距的原因之一。许多人就官方最低工资是否有必要减少不平等展开了辩论。
尽管公众认为需要最低工资来保护最弱势群体,但新加坡政府一直坚决避免最低工资政策。人力资源部部长 Josephine Teo 女士解释说,最低工资政策“没有充分利用不同部门的资源”。相反,新加坡采用的累进工资模式(PWM)鼓励工人提高技能或承担更多责任,以“提升工资”。
2020 年大选使反对党的宣言浮出水面,这些宣言强调新加坡需要最低工资。新加坡主要反对党工人党(Workers Party)提议,将全职工人的最低月工资定为 1300 美元,这是一个四口之家购买生活必需品所需的最低工资。这不是第一次提出这样的建议。反对新加坡最低工资政策的经济学家坚持认为,最低工资基准将违背新加坡精英管理的理想,因为无论工人的能力如何,他们都将得到最低工资的保证。经济学家还认为,如果最低工资过高,低技能工作有被自动化的风险。几十年来,公众、经济学家和政治代表参与了这场辩论,并将继续为此作出贡献。
我们不是经济学家,我们的目标不是说服你新加坡是否需要最低工资。然而,我们对这一主题感兴趣,因为最低工资影响到社会中最弱势的群体,即普遍未受教育或患有精神或身体残疾的人。已故的新加坡前总理李光耀先生坚定地认为,新加坡需要通过奖励成功来实行精英制度。新加坡人不得不努力工作,因为这个体系容不下任何人。虽然精英制度使新加坡以惊人的速度发展,但它也滋生了人们为什么“不成功”和贫穷的错误假设。“懒惰”和“无能”是经常用来形容那些贫困的人的词。然而,现实情况是,由于他们自己的行为,他们中的很大一部分人甚至可能没有这样的地位,贫困是一个循环,一个极难摆脱的循环。
在我们讨论新加坡人如何打破贫困循环之前,我们需要了解它的定义:一个人或一个社区缺乏维持最低生活水平的财政资源和必需品的状况。工资是决定一个人是否生活在贫困线以下的重要因素。如果一个人挣的钱不够买生活必需品,他或她就生活在贫困中。如果你对低工资工人在新加坡生存所需的全面分析感兴趣,请考虑阅读由什么够了撰写的完整报告。
我们决定使用机器学习回归模型,因为这是一种描述一组变量之间关系的简单而有效的方法。回归分析产生了一个数学方程,使我们能够根据其他社会经济因素预测新加坡的最低工资。我们从 100 个国家收集数据,包括年度名义最低工资、每周工作时间、人均 GDP、生活成本指数和生活质量指数的得分,后者衡量稳定性、权利、健康、气候、受欢迎程度、安全性和成本。不同国家的值将用于创建回归模型,以根据除年度名义最低工资之外的所有其他变量来预测新加坡的最低工资。我们承认,世界各地的最低工资只是基于立法,通常不足以理解低工资收入阶层的公民将支付哪些必需品。
我们选择了以下变量,因为它们是衡量一个国家总体福祉的通用指标,并且在不同国家之间是标准化的和可比较的,这使得在回归模型中使用预测成为可能。除了生活成本指数和生活质量指数可能不被全球认可之外,其他指标几乎不需要向公众解释。我们选择使用生活成本指数而不是名义平均生活成本,因为该指数更好地反映了购买必需品的成本。生活质量也包括在内,因为每个社会经济因素的个人得分可以直接比较。也就是说,也很有可能探索衡量社会经济因素的其他指标。
导入了以下库来处理数据集并创建回归分析模型:
## importing all librariesimport pandas as pdfrom sklearn.linear_model import LinearRegressionfrom sklearn import metricsfrom sklearn.model_selection import train_test_splitimport numpy as np
将除新加坡以外的所有国家的变量整理成 CSV 文件后,该文件被加载到 Pandas 数据框中:
## loading the data setdata = pd.read_csv('training.csv')sample_size = data.shape[0]print("File has been successfully read.")print("Sample size: ", sample_size)print("Excerpt of data: ")print(data.head)
为了构建回归模型,工作周(小时)、人均国内生产总值、生活成本指数、稳定性、权利、健康、安全、气候、成本、受欢迎程度被用作输入值(x ),而年度名义值(美元)是𝐗𝐰=𝐲.方程中的输出值(y)w 是计算 X 和 y 的线性模型:
## fitting the data into a modelx_columns = ["Workweek (hours)", "GDP per capita", "Cost of Living Index", "Stability", "Rights", "Health", "Safety", "Climate", "Costs", "Popularity"]x = data[x_columns]y = data["Annual Nominal (US$)"]linear_model = LinearRegression()linear_model.fit(x, y)
然后,新加坡的工作周(小时)、人均 GDP、生活成本指数、稳定性、权利、健康、安全、气候、成本、受欢迎程度被用作测试数据中的 x 值。预测的 y 值是预测的新加坡最低工资:
## predicting Singapore's minimum wagesg_data = pd.read_csv('testing.csv')x_test = sg_data[x_columns]print(x_test)y_pred = linear_model.predict(x_test)print(y_pred)
新加坡预计的年最低工资为 20,927.50 美元,与其他国家相比,是最低工资较高的国家之一。这相当于每月 1,743.96 美元或 2323.65 新加坡元。这仍然大大高于新加坡最低工资工作的最低工资。
普通清洁剂——1236 美元
桌面清洁剂——1339 美元
洗碗机/垃圾收集器——1442 美元
多技能清洁工/机器操作员——1648 美元
主管——1854 美元
国际博客估计,新加坡不包括房租在内的个人平均生活成本约为每月 800 新元,一个四口之家的平均生活成本约为每月 4400 新元。随着房租的增加,父母收入低的家庭可能会陷入困境。
为了比较新加坡和其他国家的年最低工资,我们决定在 Tableau 中绘制年最低工资和人均 GDP 以及年最低工资和生活成本指数的图表。下面的比较是专门进行的,因为它们涉及该国的经济表现和必需品的成本。

绘制年度最低工资和人均国内生产总值的图表,趋势似乎符合所反映的国家数值范围之间的二次曲线。模型有可能过度拟合数据以适应爱尔兰和卢森堡的情况,可以使用对数图来代替。在图表的左下角,年最低工资和人均国内生产总值之间似乎存在弱到中等的线性相关性。虽然图表右上角的点数不足以对人均 GDP 较高的国家的年最低工资趋势得出更明确的结论,但令人不安的是,年最低工资似乎停滞不前,甚至有所下降。
人均 GDP 是一个国家一年生产的商品和服务的最终价值除以该国的人口数量。虽然人均国内生产总值是通过个人产出来衡量一个国家经济表现的指标,并经常用来衡量一个国家的生活水平,但它不是衡量个人收入的指标。这是因为国家经济增长带来的财富平均分配的假设并不成立。也就是说,比较每个国家的数值仍然是公平的,因为我们很想知道人均 GDP 的提高是否意味着工人收入的增加。
新加坡的人均 GDP 很高,预计最低年薪也很高。新加坡似乎适度接近图表上的趋势线,但是如果将所列职业的任何 PWM 工资下限转换为年薪,并绘制在图表上作为近似的最低工资,新加坡的数据点将明显低于趋势线。

在绘制年度最低工资和生活费用指数图时,这一趋势似乎适度地遵循所反映的各国数值范围之间的指数曲线。这意味着随着每个国家生活成本的增加,预计年度最低工资将以更快的速度增长。生活费用指数以美国纽约为基准地区,衡量不同国家的生活必需品费用,以供参考。虽然指数值的增加反映了一个生活成本高的国家,但一个国家可能有必要大幅提高最低工资,因为每一项必需品的成本都在增加,这使得一个人靠低工资生存变得更加困难。
新加坡的生活成本指数也很高,预计年最低工资也很高。新加坡看起来接近图表上的趋势线,但与上面的情况类似,如果所列职业的任何 PWM 工资下限被转换为年薪,并作为近似的最低工资绘制在图表上,则新加坡的数据点将低于趋势线。尽管生活在一个生活成本很高的国家,但工资最低的工人的收入却低于预期。这就引出了一个问题,新加坡工资最低的工人工资够吗?
数字和统计数据给了我们新加坡最低工资的预测,但除此之外,我们谈论的是对成千上万个人的影响。可能还没有一个完美的解决方案,但总有进行有意义讨论的空间。
- 与 Ryan Kwok 合作撰写
- 【https://en . Wikipedia . org/wiki/List _ of _ countries _ by _ minimum _ wage
https://www . worldometers . info/world-population/population-by-country/
https://www.worlddata.info/cost-of-living.php
https://www.worlddata.info/quality-of-life.php
如果你能写函数,你可以用 Dask
立即加速你的代码,而不用花大量的时间学习新的东西。

本文是关于实践中使用 Dask 的系列文章的第二篇。本系列的每一篇文章对初学者来说都足够简单,但是为实际工作提供了有用的提示。该系列的第一篇文章是关于使用local cluster。本系列的下一篇文章是关于使用Dask data frame。
在 Saturn Cloud,我们管理着一个数据科学平台,该平台提供 Jupyter 笔记本、Dask 集群以及部署模型、仪表盘和作业的方法。我们已经看到一些客户在完全不必要的情况下开始使用多节点集群。当您第一次使用 Dask 时,应该选择 LocalCluster。
我一直在和许多听说过 Dask(用于分布式计算的 Python 框架)的数据科学家聊天,但是不知道从哪里开始。他们知道,Dask 可能会通过让许多工作流在一个机器集群上并行运行来加快它们的速度,但学习一种全新的方法似乎是一项艰巨的任务。但是我在这里告诉您,您可以开始从 Dask 获得价值,而不必学习整个框架。如果你花时间等待笔记本单元执行,Dask 很有可能会帮你节省时间。如果您仅仅知道如何编写 Python 函数,那么您可以利用这一点,而无需学习其他任何东西!这篇博文是一篇“不学全如何使用 Dask”教程。
Dask,dataframes,bags,arrays,schedulers,workers,graphs,RAPIDS,哦不!
关于 Dask 有很多复杂的内容,让人不知所措。这是因为 Dask 可以利用一个工人机器集群来做很多很酷的事情!但是现在忘掉这一切吧。这篇文章关注的是可以节省你时间的简单技巧,而不需要改变你的工作方式。
对于循环和函数
几乎每个数据科学家都做过类似的事情,将一组数据帧存储在单独的文件中,使用 for 循环读取所有数据帧,执行一些逻辑操作,然后将它们组合起来:
results **=** **[]**
**for** file **in** files**:**
defer **=** pd**.**read_csv**(**file**)** *## begin genius algorithm*
brilliant_features **=** **[]**
**for** feature **in** features**:**
brilliant_features**.**append**(**compute_brilliant_feature**(**df**,** feature**))**
magical_business_insight **=** make_the_magic**(**brilliant_features**)**
results**.**append**(**magical_business_insight**)**
随着时间的推移,你会得到更多的文件,或者genius_algorithm变得更复杂,运行时间更长。你最终会等待。还有等待。

Aleksandra Sapozhnikova 在 Unsplash 上拍摄的照片
第一步就是把你的代码封装在一个函数里。你想封装进入 for 循环的东西。这更容易理解代码在做什么(通过魔法将文件转换成有用的东西)。更重要的是,除了 for 循环之外,它还使代码的使用变得更加容易。
**def** make_all_the_magics**(**file**):**
df **=** pd**.**read_csv**(**file**)**
brilliant_features **=** **[]**
**for** feature **in** features**:**
brilliant_features**.**append**(**compute_brilliant_feature**(**df**,** feature**))**
magical_business_insight **=** make_the_magic**(**brilliant_features**)**
**return** magical_business_insight
results **=** **[]**
**for** file **in** files**:**
magical_business_insight **=** make_all_the_magics**(**file**)**
results**.**append**(**magical_business_insight**)**
第二步用 Dask 将其并行化。现在,Dask 将在集群上并行运行它们,而不是使用 for 循环,每次迭代都在前一次迭代之后进行。这将给我们带来更快的结果,并且只比 for 循环代码多了三行。
**from** dask **import** delayed
**from** dask.distributed **import** Client
*# same function but with a Dask delayed decorator*
**@delayed**
**def** make_all_the_magics**(**file**):**
df **=** pd**.**read_csv**(**file**)**
brilliant_features **=** **[]**
**for** feature **in** features**:**
brilliant_features**.**append**(**compute_brilliant_feature**(**df**,** feature**))**
magical_business_insight **=** make_the_magic**(**brilliant_features**)**
**return** magical_business_insight
results **=** **[]**
**for** file **in** files**:**
magical_business_insight **=** make_all_the_magics**(**file**)**
results**.**append**(**magical_business_insight**)**
*# new Dask code*
c **=** Client**()**
results **=** c**.**compute**(**results**,** sync**=**True**)**
工作原理:
- 延迟装饰器,转换你的函数。当你调用它的时候,它不会被求值。相反,您得到的是一个
delayed对象,Dask 可以稍后执行。 Client().compute将所有被延迟的对象发送到 Dask 集群,在那里它们被并行计算!就这样,你赢了!- 实例化一个
Client会自动提供一个LocalCluster。这意味着 Dask 并行工作进程与调用 Dask 的进程在同一台机器上。这是一个简洁的例子。对于实际工作,我建议在终端中创建本地集群。
实用话题
以上止于大多数 Dask 教程止于的地方。我在自己的工作中和众多客户中使用过这种方法,总会出现一些实际问题。接下来的这些技巧将帮助你从上面的教科书例子过渡到实践中更有用的方法,它们涵盖了经常出现的两个主题:大型对象和错误处理。
大型物体
为了在分布式集群上计算函数,需要将调用函数的对象发送给工作者。这可能会导致性能问题,因为这些需要在您的计算机上序列化(pickled ),并通过网络发送。假设您正在处理千兆字节的数据——您不希望每次在其上运行函数时都必须传输这些数据。如果您不小心发送了大型对象,您可能会看到来自 Dask 的如下消息:
Consider scattering large objects ahead of time with client.scatter to reduce scheduler burden and keep data on workers
有两种方法可以避免这种情况发生,你可以将较小的对象发送给工人,这样负担就不会太重,或者你可以尝试将每个对象只发送给工人一次,这样你就不必一直进行传输。
修复 1:尽可能发送小对象
这个例子很好,因为我们发送的是文件路径(小字符串),而不是数据帧。
*# good, do this*
results **=** **[]**
**for** file **in** files**:**
magical_business_insight **=** make_all_the_magics**(**file**)**
results**.**append**(**magical_business_insight**)**
下面是不要做的事情。这不仅是因为您将在循环中进行 CSV 读取(昂贵且缓慢),这不是并行的,还因为现在我们正在发送数据帧(可能很大)。
*# bad, do not do this*
results **=** **[]**
**for** file **in** files**:**
df **=** pd**.**read_csv**(**file**)**
magical_business_insight **=** make_all_the_magics**(**df**)**
results**.**append**(**magical_business_insight**)**
通常情况下,可以重写代码来改变数据的管理位置——要么在客户机上,要么在工人上。根据您的情况,通过考虑哪些函数作为输入以及如何最小化数据传输,可能会节省大量时间。
修复 2:只发送一次对象
如果一定要发一个大的对象,就不要多次发了。例如,如果我需要发送一个大的模型对象来进行计算,简单地添加参数将会多次序列化模型(每个文件一次)
*# bad, do not do this*
results **=** **[]**
**for** file **in** files**:**
*# big model has to be sent to a worker each time the function is called*
magical_business_insight **=** make_all_the_magics**(**file**,** big_model**)**
results**.**append**(**magical_business_insight**)**
我可以告诉 Dask 不要这样做,把它包装在一个延迟对象中。
*# good, do this*
results **=** **[]**
big_model **=** client**.**scatter**(**big_model**)** *#send the model to the workers first*
**for** file **in** files**:**
magical_business_insight **=** make_all_the_magics**(**file**,** big_model**)**
results**.**append**(**magical_business_insight**)**
处理失败
随着计算任务的增加,您经常会希望能够克服失败。在这种情况下,我的 CSV 中可能有 5%的坏数据是我无法处理的。我想成功地处理 95%的 CSV,但是要跟踪失败,以便我可以调整我的方法并再次尝试。
这个循环是这样的。
**import** traceback
**from** distributed.client **import** wait**,** FIRST_COMPLETED**,** ALL_COMPLETED
queue **=** c**.**compute**(**results**)**
futures_to_index **=** **{**fut**:** i **for** i**,** fut **in** enumerate**(**queue**)}**
results **=** **[**None **for** x **in** range**(**len**(**queue**))]**
**while** queue**:**
result **=** wait**(**queue**,** return_when**=**FIRST_COMPLETED**)**
**for** future **in** result**.**done**:**
index **=** futures_to_index**[**future**]**
**if** future**.**status **==** 'finished'**:**
**print(**f'finished computation #{index}'**)**
results**[**index**]** **=** future**.**result**()**
**else:**
**print(**f'errored #{index}'**)**
**try:**
future**.**result**()**
**except** **Exception** **as** e**:**
results**[**index**]** **=** e
traceback**.**print_exc**()**
queue **=** result**.**not_done
**print(**results**)**
由于这个函数乍一看相当复杂,所以我们来分解一下。
queue **=** c**.**compute**(**results**)**
futures_to_index **=** **{**fut**:** i **for** i**,** fut **in** enumerate**(**queue**)}**
results **=** **[**None **for** x **in** range**(**len**(**queue**))]**
我们在results上调用compute,但是因为我们没有经过sync=True,所以我们立即返回代表尚未完成的计算的期货。我们还创建了从未来本身到生成它的第 n 个输入参数的映射。最后,我们填充一个结果列表,其中暂时没有结果。
**while** queue**:**
result **=** wait**(**queue**,** return_when**=**FIRST_COMPLETED**)**
接下来,我们等待结果,并在结果出现时进行处理。当我们等待期货时,它们被分为done和not_done两种。
**if** future**.**status **==** 'finished'**:**
**print(**f'finished computation #{index}'**)**
results**[**index**]** **=** future**.**result**()**
如果未来是finished,那么我们打印我们成功了,我们存储结果。
**else:**
**print(**f'errored #{index}'**)**
**try:**
future**.**result**()**
**except** **Exception** **as** e**:**
results**[**index**]** **=** e
traceback**.**print_exc**()**
否则,我们存储异常,并打印堆栈跟踪。
queue **=** result**.**not_done
最后,我们将队列设置为那些尚未完成的期货。
结论
Dask 绝对能帮你节省时间。如果您花时间等待代码运行,您应该使用这些简单的提示来并行化您的工作。使用 Dask 还可以做许多高级的事情,但这是一个很好的起点。
声明:我是土星云的 CTO。我们让您的团队轻松连接云资源。想用 Jupyter 和 Dask?部署模型、仪表板或作业?在笔记本电脑或 4 TB Jupyter 实例上工作?完全透明地了解谁在使用哪些云资源?我们做所有这些,甚至更多。
原载于 2021 年 8 月 26 日https://Saturn cloud . io。
如果你需要摆脱困境,尝试不同的角度
作者聚焦
不确定如何超越棘手的数据科学问题?把它分成更小的部分——询问同事的观点。
在 Author Spotlight 系列中,TDS 编辑与我们社区的成员谈论他们在数据科学领域的职业道路、他们的写作以及他们的灵感来源。今天,我们很高兴与 卡罗莱纳便当 进行对话。

照片由卡罗莱纳便当提供
Carolina 是一名数据科学家,对数据可视化特别感兴趣。在她放弃专注于大规模网络中的数据分析和数据可视化的博士项目之前,她完成了计算机科学的硕士学位。从那以后,她一直致力于数据科学和分析。
当她不写关于数据科学和数据可视化的基础知识时,你可以发现她在阅读关于科学、历史和行为心理学的书籍,做健美操,或者播放高保真电子音乐。
感谢加入我们,卡罗莱娜!让我们从一些基础知识开始:您是如何在数据科学的广阔世界中找到自己的立足点的?
当我开始计算机科学硕士项目时,我需要缩小范围,将重点放在几个专业和辅修领域。
尽管那时数据科学还没有很好的定义,我还是很自然地被那些关于清理、分析和理解数据的课程所吸引。
后来,在我读博士的过程中,对数据科学的兴奋和好奇真正开始了。这个领域开始受到关注,我密切关注这项研究,并有机会深入研究数据分析和数据可视化。
我最终放弃了博士学位,但这段经历如此重要,以至于从那以后我一直从事数据科学的工作。
当您在数据科学工作中遇到挑战时,您会如何应对?
每当我在学习新东西或解决数据科学问题时遇到困难,我总是会回去问同样的问题。
- 我把这个问题分解成原子碎片了吗?有时,我可能会匆忙处理一个太大、范围太广的问题。在这种情况下,我会让自己后退一步,看看能否将问题分解成许多更小的、可单独解决的任务。它将我正在研究的问题的范围缩小到可以用更具体的算法和技术来解决的狭义问题。这不一定会马上降低复杂性,但它有助于我保持专注。
- 我可以从另一个角度来处理这个问题吗?数据科学和任何其他领域一样,受益于大量的创造力和尝试起初可能不明显的事物。有时候,当我陷入困境时,我会考虑如何从不同的角度来解决问题。对于这个具体问题,我还能问些什么问题来引导我以不同的方式思考它,并最终找到一个我以前从未想过的解决方案?
- 其他人已经做过类似的工作了吗?这通常意味着与我的同行或该领域的专家交谈,进行更多轮的研究,看看是否有人以前偶然发现过相同或类似的问题,并检查是否有关于我试图使用的特定技术的其他信息。
- 关于这个问题,我没有看到什么?当我陷入困境时,我会试着理解自己是否掌握了所有必要的背景知识,如果没有,我会回顾自己的假设和设想。有时我意识到我最初的假设不是最好的,所以我改进它并重新开始。但我发现,和我的同龄人交谈,解释我正在解决的问题,也有助于我摆脱困境。它让我彻底思考这个问题,以便把它说清楚。通常情况下,我从那些充满活力和新想法的对话中走出来——要么是因为我的同事有很好的建议,要么只是因为向不了解全部背景的人解释它的过程让我更有创造性地思考它。
这是我摆脱困境的方式,但我知道这远不是一个完美的解决方案。对于每一个新问题,我都在寻找改进的方法,这也是一个有趣的学习过程。
是什么激发了你向更广泛的受众讲述数据科学概念?
数据科学现在是一个非常热门的领域。每天都有大量新的研究、框架和应用程序出现,因此很容易感到自己置身事外。
我想要一种方法来重温核心数据科学概念,并继续扩展我的知识。但是,当我开始重温材料并试图学习新的东西时,我意识到,即使是在介绍性资源中,也有大量的抽象概念,使其难以理解。
有些人非常擅长立刻理解抽象的概念,但我发现当我有一两个详细的例子让在整个过程中牵着我的手时,学习起来会容易得多。然后我可以退一步,开始思考那个抽象的概念。
我决定写文章,因为这是我更喜欢的媒介。当我还是个孩子的时候,我经常用暑假写短篇小说,所以写作感觉更自然。
但是写作也符合我喜欢的学习过程。它迫使我深入思考,以便用一种容易理解的方式来表达自己。这是我一直想提高的技能。
除了是继续学习的好方法之外,这也是为深入的数据科学资源库做出贡献的机会。
您经常在帖子中添加漂亮的自定义插图。你认为他们给学习过程带来了什么?
创作插图非常有趣!
它们是我窥视文章主题的方式,也是我在整篇文章中引入一些视觉检查的方式。
文章的核心是故事。这个故事帮助我将主题分解成核心概念,并引导读者了解所有细节。图表和图解是我喜欢使用的其他视觉学习元素,因此读者可以在阅读故事的过程中逐渐建立他们的心智模型。
图片也在学习过程中指导我。我在学东西的时候会做很多笔记,但是我也会乱写。我发现,当我能够看到我正在学习的内容时,我可以对主题建立更强的直觉和更深的理解,并且还能更好地记住它。
我试着写文章,就好像它们是我策划和组织的笔记和涂鸦的拼贴画,所以任何人都可以跟随。
在不久的将来,你对数据科学社区有什么希望?你想看到什么样的变化?
数据科学和机器学习是如此广阔的领域,有如此多隐含的所需知识,以至于我希望看到更多的介绍性资源来指导刚刚入门的人。
更不用说在一个新的领域重新开始是令人生畏的!
最近,不同的文章、视频和课程在这方面取得了很大进展,但每个人的学习都有所不同。
因此,我很期待看到我们如何让数据科学和机器学习对任何刚起步的人来说更容易理解和接近。
想了解更多关于 Carolina 的工作和数据科学兴趣的信息吗?你可以在她的个人简介中找到她的全部帖子,你也可以在推特上关注她的更新和建议。在过去的几年里,Carolina 在 TDS 上发布了大量优秀的教程和讲解;这里只是精选的一小部分亮点。
- 中心极限定理:现实生活中的应用 ( TDS ,2020 年 10 月)
一篇来自卡罗莱纳州的典型帖子通过一个我们大多数人不费吹灰之力就能想象的相关故事,分解了统计学、数据科学或机器学习中的一个复杂概念。阅读这篇关于中心极限定理的介绍,看看她是如何用一个杂货店经理的例子做到这一点的,他需要决定订购多少苏打水以保持库存在正确的水平。 - 现实生活中的逻辑回归:构建每日生产力分类模型 ( TDS ,2021 年 3 月)
我们很多人可能偶尔会混淆线性回归和逻辑回归。在消化了卡罗莱纳州的清晰漫游之后,这种可能性会小得多,清晰漫游使用日常事务和个人生产力作为解释棘手话题的工具。 - 马尔可夫模型和马尔可夫链在现实生活中的解释:概率测试程序 ( TDS ,2020 年 12 月)
如果你正在迈出数据科学的第一步,你可能会发现大量的新名称和术语令人应接不暇。在这里,卡罗琳娜解决了最重要的一个问题,为马尔可夫模型和链带来了清晰性和可访问性。
请继续关注我们即将推出的下一位专题作者。如果你对你想在这个空间看到的人有建议,请在评论中给我们留言!
作为一名人员分析师,如果你只有时间做一件事,考虑把它花在编程上吧!
编程知识如何革新你在人员分析方面的贡献

建议你需要成为一名程序员才能最好地为人物分析做出贡献,这似乎太牵强了。但是相信我,我这么做是有原因的。概括地说,在我的职业生涯中,有一个阶段是拖延而不是学习编程技能。但是一旦我度过了那个阶段,走出了我的舒适区,我至今从未后悔在编程上投入的努力。首先,它让我质疑我传统的做事方式,通过自动化来提高效率,并为执行任何商业洞察的统计分析打开了大门。这篇文章的重点是启发(并希望说服)为什么在编程上投入的任何努力都会为任何渴望成为崭露头角的人员分析师或任何人力资源专业人士带来巨大的好处。
关于为什么编程不适合人力资源的一些常见误区
让我们先来看看为什么这整个想法听起来仍然令人担忧的一些潜在原因。

- 首先,一个人力资源组织通常不被认为是技术性的。就这一点而言,Excel 等流行的 MS Office 工具已经是一个扩展用例。那编程不就干脆把人吓跑了吗?
- 其次,编程只针对其他核心数据科学家/分析师的事实。这并不适合所有人,因为对于人力资源这样的支持职能来说,这太难了,负担太重了。
- 第三,人力资源专业人员哪里有时间?人力资源部门忙于处理不断变化的员工事务(没有特别感谢 COVID 让事情变得更糟!).编程难道不需要在你还没接触到皮毛之前就留出大量时间、参加课程、获得认证吗?许多天来,在个人生活和职业生活之间周旋已经变得模糊不清,所以根本没有时间考虑自我发展。
逐渐走出舒适的茧

我们周围的技术和数字化进步如此之快。这将不可避免地(如果不是已经)影响我们完成工作的传统方式,这在某些时候让我们所有人都感到害怕。真正的问题是,是否每个人都在他们的影响范围内行动,利用这种恐惧作为一种激励因素,在我们已经知道和舒服的事情之外扩展学习。
想象一下,让自动化和人工智能成为“家长”,而不是生活在对那些接管你当前角色的神童的恐惧中,那该有多酷?如果你想掌控全局,你可以通过学习如何掌控全局,并一步步走向编程。
我不会在这里描绘一个错误的图像,即编程是简单的,并且可以肯定地从经验中证明走在这条道路上是相当具有挑战性的,充满了每天的失败(字面意思是,当你陷入调试代码行而没有给你预期的结果时),并且肯定没有达到最终目的地的意思,因为总是有太多的东西需要学习。你的目标因此越来越远。但是一旦你确定这是前进的方向,并且像我一样,为你用你所获得的每一点点知识所能完成的事情而惊叹,你就会继续攀登。
为什么要超越现有的工具,如老式的 Excel?忠告:做了才相信!

对于那些不知道这一点的人,如果我告诉你,你可以做你的日常工具如 Excel 所能做的一切,甚至更多编程工作,会怎么样?这是真的,我对此进行了评价。事实上,像 Python 这样的编程语言甚至可以构建仪表板,就像我们在 Tableau 上所做的那样。想象一下!
这当然不意味着完全取代我们日常的用例工具。永远记住,我们需要为正确的任务使用正确的工具。我建议,如果你正在处理一个小的数据集,并且任务是一次性完成某件事情,那么尽一切办法使用适当的工具,比如 Excel 或 BI (Power BI,Tableau 等)。).虽然我编码,但我仍然使用 Excel 来组织原始数据集,然后在 Python 编程平台上使用它们进行进一步的转换和见解生成。
一步一步来

如果你一无所知,但想知道如何将基本编程融入到工作中,我建议从简单的复制 Excel 工作开始,并始终以你正在做的任何事情都将是重复的为理由。让我们考虑一个例子——您需要基于特定的过滤逻辑对一个主数据集进行切片,并与您的利益相关者共享切片后的数据集,并且您需要以重复的节奏完成此操作,比如每周一次。确实有几种方法可以完成这样的任务,比如 Excel 宏。但对我来说,这将是使用编程热身的典型用例。举几个例子,你可以做任何事情,从简单的数据过滤、旋转、查找、向利益相关者发送电子邮件。使用代码完成所有这些工作的好处是,从导入编程包的第 1 步到导入数据集的第 2 步,再到详细说明后续数据转换的进一步处理步骤,您可以一次编写一个步骤,直到获得结果。完成代码脚本后,您可以将其配置为以与之前手动工作相同的节奏自动运行,此外,您还可以面向未来,以最小的痛苦进行任何新的更改,例如过滤条件的更改,只需编辑一行代码,同时保持所有其他代码不变。一旦你构建了一个记录良好的基础代码,下一次转向增强功能会变得非常迅速。从这样的用例开始并变得舒适将会进一步提高你的编程基础,有朝一日进入更复杂的分析。关键是要再次记住的是,熟能生巧!
无名英雄

阿里·科卡布在 unsplash 上的照片
没有一个合适的支持系统,你的宝宝努力可能不可避免地会白费。值得一提的一些关键因素可能是—
- 首先,你报告的人力资源组织必须通过从自动化立场或商业见解中看到潜在的价值,允许你的工作角色在这一领域有发展机会。
- 第二,你没有编程或计算机科学背景,因此远离冒险进入编程领域。相信我,我也是从零开始,没有以前的经验或教育背景。任何程序员的基本技能都是谷歌的能力!你会意识到一旦你开始编码,你编码的越多,当你陷入困境时,你搜索的就越多。有人已经遇到了同样的问题,并提出了他们之前,所以它不太可能是你一个人在你的斗争中,最有可能找到解决方案只是搜索。像 stackoverflow 这样的论坛是我找到 90%编码问题答案的地方。至于新手的理论知识获取,Udemy(或者 Coursera 等。)有很棒的教程提供入门折扣。只需注册并浏览基础课程(如那些标题为 Python A-Z 且学生评价良好的课程),然后根据业务任务逐步提升自己的知识。
- 最后也是最重要的一点,如果你自己看不到价值,并且愿意多走一步,那么任何外部帮助都没有多大用处。
所以,你会是拖延者还是积极行动者?

对于程序员来说,永远不会有沉闷/停滞的时刻,因为你正处于一个不断发展的学习阶段。尽管该领域取得了诸多进步,但在人力资源领域找到具备编程知识的人肯定还是很少见的。如果你像我一样想成为“蓝色大象”(或脱颖而出),挤出时间来学习编程,并找到在工作中尝试这些知识的途径。你练习得越多,你就会发现你还有更多的不知道的东西,你还能学会突破界限。快乐升级!
如果你认为在掌握机器学习之后,HR 中的预测分析会一帆风顺,那就再想想吧!
在人力资源预测分析中导航未知领域时需要注意的关键重点领域和陷阱

Linus Sandvide 在 Unsplash 上拍摄的照片
以前,我不会想到写像预测分析和机器学习这样的主题。这一切过去似乎太神奇,太困难,几乎不可能接近,尤其是在人力资源领域!这篇文章旨在揭示一些黄金经验,揭示一些黑暗的地方,如果你是人力资源分析领域的第一次工作,希望有助于你取得成功。这里的智慧来自于被扔进火坑后的艰难学习,实际上是不得不为一个商业问题做预测。如果你处于这样一个位置,或者只是好奇,但一直拖延,直到这样一个任务变得重要和紧迫,事先不知道从哪里开始,如何取得进展,需要什么,以及如何才能一起到达终点,请放心,这肯定是可行的,只是要注意隐藏的陷阱,并准备采取行动。
可怕的第一步——训练

照片由 Element5 数码在 Unsplash 上拍摄
如果你要重新开始你的人力资源预测分析之旅,需要很多步骤。在预测性和描述性之间,前一种类型的分析更具挑战性,但你可以要求的回报也更大!为什么这样因为,在描述性分析中,你理解发生了什么,建立关系;在预测分析中,您将“已知的”知识扩展到“未知的”你基本上从一个调查员变成了一个算命师!
首先掌握一些基本概念是至关重要的。如果你不这样做,你将无法理解你的机器学习实验的结果,更糟糕的是,你将无法说服你的利益相关者这些发现,这可能是你作为人力资源分析师/科学家的最大失败,因为你可能在技术上知道很多,但它严重地归结为对你的利益相关者的叙述,用于商业行动(相信我,这在人力资源中特别是重要)。
为了更好地理解这些概念,你需要提高技能。对于预测分析,你需要从提高技能的角度来理解机器学习(ML ),因为你将运行几个 ML 实验,直到你可以找到一个好的预测 ML 模型,它只是产生你想要预测的响应变量的预测。如今,只要重点是获得知识,而不是证书,技能提升就不那么令人生畏了。只需在任何知名的在线培训平台上挑选知识,比如 Udemy。缩小 ML 课程的范围,让你至少掌握以下基础知识
- 对什么是 ML,ML 的类型和相关指标有基本的了解,这样你就可以针对你的业务问题缩小最适用的类型。经验之谈-通常,人力资源数据集很小(不是大数据),所以你可能会开始理解传统的 ML,而不是花更多的时间来理解需要大数据的深度学习。
示例—您的业务问题是预测可能辞职的员工。这是一个二元分类问题,因为您的响应变量(或一般意义上的输出)是一个员工是留下还是离开的类别(两种口味,因此是二元的)。对于这样的业务问题,重点理解分类 ML,它是监督 ML 的一种类型。另一方面,如果你预测的是一个连续的数字,比如薪水,那么专注于理解如何应用回归 ML。
机器学习

让我们首先解决一些问题——ML 会成为您预测分析之旅中需要克服的最大障碍吗?答案(也许是主观的)是否定的。当试图应用从你的训练阶段学到的基础知识时,这看起来肯定是最困难的部分。但是,正如我将在下一节中揭示的,随着您在解决业务问题时越来越深入,可能会有其他看不见的陷阱。
ML 是您在预测分析之旅中需要掌握的工具。顾名思义,一旦你学会了 ML 的基础知识,就该让机器学习了。机器通过从您的数据中形成特征(输入数据)和响应(旨在根据模型在学习时尚未看到的新数据进行预测的输出)之间的模式进行学习。
在学习基础知识后的早期阶段,尽可能多地实践是很重要的。对于 Python 上的 ML 探索,从 Scikit-learn 库下的 ML 算法开始可能是一个很好的起点,这将让您理解如何从数据到预测。结果可能有些生疏,但在进一步完善之前,您可以通过这些试验增强对 ML 应用程序的信心。
没有探索就不可能有 ML。如果你想缩短解决问题的时间而不影响探索的深度,Auto ML 是一个不错的选择。当您一次试验一种算法时,使用 Auto ML 就像成为指挥而不是演奏单个乐器。有很多流行的开源 Auto ML 库可以即插即用。
如果你有兴趣更深入地为你的数据集应用 Auto ML,你可以去看看这篇以前的文章。本文探索了两个自动 ML 库来构建预测辞职的模型,还介绍了创建端到端 ML 管道的不同步骤,并介绍了重要的分类器模型度量。
伪装的真正恶魔——数据!

没错。你用来做预测的数据集可能是你预测分析之旅中最大的威胁。如果这个事实令人惊讶,那么要么是因为你享受了完美数据集的特权(就像 Kaggle 上的那些一样,那是一个梦想!)或者您尚未到达预测分析之旅的这一阶段。
让我们看看为什么实际的工作场所数据集是沉默的杀手。
- 如果给了机器垃圾数据去学习,它就会那样做,而你的结果也将是垃圾。这就是确保数据集的准确性和相关性如此重要的根本原因。
垃圾结果可以用较差的度量、异常值预测等来识别。
- 确保您理解您的数据。这可能意味着去找你的利益相关者,去真正理解为什么某些信息会以这种方式被获取。
例如,某个特定特征中的零实际上可能有一些相关性,您不能在构建模型时简单地忽略这些零。比如,对于一个新的大学毕业生来说,工作经验的年限将是零,但是对于一个有经验的雇员来说,这是有价值的。通过盲目地去掉零,你阻碍了模型对如何对待新大学毕业生的学习,它最终将无法对这个群体做出好的预测。
- 注意潜在的概念漂移。在机器学习中,这意味着作为时间效应的预测劣势。如果您的数据集具有这样的特性,请通过引用一个公共时间戳来找到平衡比例的方法。在辞职预测的概念中,如果员工在公司的服务年限作为一个特征,您可能会遇到这个问题,因为离开的人与留下的人相比,服务年限总是更低。
- 要意识到生存偏差,即你在不知不觉中忽略了一群没有通过选拔过程的人。如果你预测辞职,重要的是要确保你包含了关于辞职者的信息,否则这个模型将再次处于盲态。
- 制定您将如何进行预测的未来流程,并在此基础上进行逆向工程,以导航您的要素应如何重塑。
- 最后也是最重要的,确保你有预测功能。只有一只兔子耳朵并尝试所有可能的魔法从帽子里变出一整只兔子是没有意义的。除了理解手头的业务问题之外,您还需要能够侦察、研究和包括指示性特征,这些特征将推动做出有效的预测。
善于讲故事=善于算命!

我们再次来到这里,是因为这对人力资源来说是非常重要的。如果解释不能引起你的观众的共鸣,而见解又如此专业,以至于他们无法理解,那么你所有的努力都将付之东流。思考如何向观众展示预测结果是一门非常需要的艺术。将输出简化为观众熟悉的表格或图表,但同时要努力用简单的布局来解释,你的努力是从数据到预测。
祝旅途好运!
"如果你想把松饼放在烤箱里久一点,就告诉我"
使分析可行的关键转变

作者图片
向云计算和分析的转移正在创造一个世界,在这个世界中,数据集的每一个可能的切割和聚合都可以自动完成。问题在于,虽然分析引擎可以无限扩展,但人类的注意力却不能。
有数百种可能的模型可以应用,并且每种模型都有无限可能的见解,从统计结果到明确的行动建议的过程是痛苦的、模糊的,并且缺乏传递见解的共同语言。最终,即使是精确度最高的模型也会遭遇反对——“我该拿这个怎么办?”
今天,分析师和商业领袖经常通过眯着眼睛看无数的图表和可视化来做决定,在寻找有用的见解中目测趋势。不幸的是,许多这些假设的洞察力很难被业务理解,并且由于不可解释性、与利益相关者无关或简单的不信任,大多数从未被付诸行动。
在 Sisu,我们看到顶级数据团队通过将这些观察结果与对如何将统计结果映射到具体行动的清晰理解相结合来解决这一差距。他们有效地建立了一个共同点,并以此推动对话。
当数据与行动联系在一起时,分析师可以提出行动建议,然后使用可视化作为补充机制来验证调查结果。要让更多的团队开启这种以行动为导向的分析风格,需要发生三个关键转变。
通过行动计划从数字转向建议
在许多组织中,分析师和业务运营者之间通常存在脱节。分析师呈现一张又一张的时间趋势图和条形图,常常让操作员不清楚如何应对,甚至不知道应该考虑什么样的应对措施。
在上海外国语大学,我们在与一家大型特许经营餐厅合作时亲眼目睹了这个问题。在分析方面的投资使得每周通过电子邮件向经理报告关键的单个商店指标成为可能。一份特别的电子邮件报告详细描述了一家特定餐厅烘焙食品热度的周比周 CSAT 下降超过两个标准差。
也许这对于分析师来说很容易理解,但对于餐厅经理来说却是胡言乱语。没有人将这一事实转化为明确的建议。一位餐厅经理对看着一墙的数字并被期望据此采取行动感到恼火,他大声说道:“如果你想让我把松饼放在烤箱里更久,就告诉我!”分析团队跳过了将统计术语翻译成餐厅经理可以理解的清晰简洁的建议,让经理自己得出结论或完全忽略这一发现。
这种挫败感清楚地表明,虽然分析师可能会发现一个关键的统计结果,但在成为对业务有价值的东西之前,还有很长的路要走。关键是通过深入了解运营商推动业务发展的手段,将这些统计结果转化为可行且易懂的建议。然后,就很容易解释像烘焙食品热度的每周 CSAT 变化这样的数据如何映射到像将烘焙食品在烤箱中放置更长时间这样的行为上。
虽然许多统计结果可能没有相应的动作,反之亦然,但是在动作和数据有规律的和可重复的交集的地方有巨大的机会。即使在原型形式中,这种将某些统计结果映射为明确行动的“智能行动板”已经在它们实现的任何地方产生了广泛的商业收益。在上面的研究中,麦肯锡测量了一家拉丁美洲电信公司如何实施这种行动图,从而使生产率提高了 18%。
从高层次的见解转向使用数据来触发行动

作者图片
随着像 Segment 和 mParticle 这样的 CDP 以及像 Census 这样帮助将数据从仓库传输回运营系统的工具的不断增加,我们看到了一组数量不多但迅速扩大的用例,其中可操作的见解将被使用数据触发行动所取代。
使用数据触发行动的最简单的用例是,当自动分析表明有问题时,发出精确的警报或电子邮件。例如,当页面加载时间超过阈值时,Devops 工具可以提醒开发人员和产品经理团队。传呼机值班警报是将数据表中的一些事件转化为简洁有效的行动的很好的例子。
简单、精确的警报有机会传播到更多的业务用例,而且还有工具来帮助通知后的步骤。例如,对于业务用例,CDP 可以共享推荐的消息和目标细分市场,供电子邮件营销人员在活动中使用,而不仅仅是通知电子邮件营销人员高度参与的细分市场。
我们仍然相信,员工,而不是机器,将是执行最终操作的人。这是因为最终,员工将对发送出去的营销邮件负责,无论是积极的还是消极的后果。在大多数情况下,人会想要回顾并拥有最终的发言权。
但是推荐员工可以采取的不同行动将加速数据驱动的行动。例如,在上面的 devops 用例中,开发人员可以在分析平台中看到一组按钮,这些按钮对应于可以用来解决问题的不同操作。
将分析从拉式改为推式
仪表板和高管携手并进。这些业务利益相关者需要分析团队为他们构建一个业务中关键指标如何变化的鸟瞰图,以做出明智的决策并指导执行。虽然仪表板使利益相关者一眼就能获得洞察力,但它们很少推动行动,最终,它们甚至不会被检查,因为仪表板的有用性随着时间的推移而衰减。
尽管如此,在大大小小的组织中,我们几乎每周都会看到 10 多人参加的一两个小时的圆桌会议(《每周商业评论》,或 WBR ),在会议中,业务利益相关者和分析师盯着一个或多个仪表板审查他们的年度指标,并提出一些无法回答的问题,如“为什么销售额下降”或“为什么转换率发生了变化”。有高管和副总裁参加,还要准备几个小时,这些会议是业内最昂贵的。通过我们的采访,结论几乎是一致的——这些评论的效率低得令人痛苦,因为 WBR 的对话围绕着趋势的高层次变化,而不是任何人实际可以做些什么来应对。一位移动平台运营商强调,“这是我们做的最痛苦的事情,每个人离开时都有点沮丧。”
在创新型公司中,我们看到分析从每周一次的 WBR“拉动”模式转向更多的推动模式,在这种模式下,数据近乎连续地推动一线行动。洞察力和行动建议被推送给小型灵活的分析师和操作员团队。分析师深入挖掘并验证这些见解,而操作员则根据其控制范围采取行动。
随着越来越多的商业用户得到人工智能和前所未有的洞察力的支持,未来的“每周商业评论”将让运营商和分析师展示他们做了什么,甚至是他们选择了哪个行动建议,而不是浪费时间绞尽脑汁地解释 KPI 上升或下降的原因。
引入未来的商业智能
我们期待更容易理解的商业智能,关注可操作性和及时性。
作为第一步,我们希望引入统计结果和计算与企业可能采取的具体行动之间的清晰映射。如果数据暗示烘焙食品需要在烤箱中放置更长时间,参考我们之前的餐厅经理的例子,那么这应该是可交付的,而不是一组图表和图形。
有了这种映射,我们将看到数据中的统计结果转化为行动的效率越来越高。这不是高层次的洞察力,而是数据触发行动的一种方式。也许这从给线路操作员一个精确的警告开始。但是工具也有巨大的机会来帮助完成通知后的步骤。
最后,分析的重心将转向数据仓库、分析系统和一线业务运营商之间的异步、不间断交换。高管业务审查仍将存在,但不是花时间挖掘数据来找出该做什么,而是可以自动提供见解,大多数会议时间将用于权衡每项建议的成本/收益。
在 Sisu Data ,我们期待与客户和合作伙伴共同实现这一愿景。
如果你的模型无法解释,那它真的是你的模型吗?
机器学习和人工智能系统中的可解释性不再是一个很好的功能,而是用户和决策者可以认为安全、可靠和公平的任何产品的必要组件。
我们最近已经发表了一系列关于这个主题的有见地的帖子,所以本周我们将全力以赴研究可解释和可解释模型的实际方面。
- 一个好的起点 是 Noga Gershon Barak 的介绍开源的 InterpretML 包。Noga 向我们介绍了该包的来龙去脉,并重点介绍了可解释的增强机器(EBM),“一个旨在与随机森林和增强树等机器学习模型具有可比准确性以及可解释性能力的玻璃盒模型。”
- 还不确定哪种方法适合你?如果你正在这方面迈出第一步,如果你想知道更多关于各种解释方法的信息,这是可以理解的。 Vincent Margot 的XAI 方法概述是了解部分相关图(PDP)、累积局部效应(ALE)和个人条件期望(ICE)等的一个可访问入口。
- 学习如何计算 Shapley 值——正确的方法 。 SHAP 和 ACV 是两个流行的库,它们为你的模型增加了一层可解释性,而 Cyril Lemaire 的帖子是比较这两个库如何工作以及哪一个库可能适合给定用例的有价值的入门书。
- 当交代不可能时 。迟早,你对模型可解释性的实践方法可能会碰壁——然后呢?Mathieu d'Aquin 发人深省的帖子强调了可解释性的局限性:“也许结果都是垃圾,是基于巧合,也许不是。”但他认为,这不应该让我们气馁:相反,它应该推动我们找到新的标准,以做出更好的决定并获得新的知识。

Eric Prouzet 在 Unsplash 上拍摄的照片
除了模型的可解释性,我们真的不能离开你而不分享一些关于其他主题的杰出文章——特别是当我们最近有幸发表了如此强大的作品。
- 罗伯特·兰格带着他的关于最近四篇深度学习研究论文的一贯出色的综述回来了这个月你应该节省一些时间来阅读。
- Khuyen Tran 的最新实践教程展示了如何用 PyGraphistry 可视化你的 GitHub 社交网络。
- 要了解多元分数多项式,只需看看尼古拉斯·因多夫的最新深度探讨。
- 阿曼达·伊格莱西亚斯·莫雷诺(Amanda Iglesias Moreno)回来了,她发布了一篇详尽而耐心的帖子,详细介绍了她在客户流失方面的端到端机器学习项目。
- 我们最新的 TDS 播客片段由主持人 Jeremie Harris 与 OpenAI 的 Ken Stanley 对话,内容涉及人工智能创造力和开放式学习。
- Haaya Naushan 的新教程是对基于文本的因果推理的细致入微的探索。你会想为这一次留出一些时间!
- 跟随 CJ Sullivan 的优秀指南,学习如何让 FastRP 图嵌入为你工作。
- Adam Brownell 关注业务需求和数据科学解决方案之间的频繁脱节,并提出了一些应对策略。
感谢您本周加入我们!当我们世界各地的许多人即将迎来假期和新年时,我们一如既往地感谢您的时间和您的支持。
直到下一个变量,
TDS 编辑
深度学习中张量整形的 MLP 和变压器之间的图示差异
思想和理论
深入探究数学细节,并附有插图。
在设计神经网络时,我们经常面临张量整形的需要。张量的空间形状必须随着某一层而改变,以便能够适合下游的层。就像顶部和底部表面形状不同的特殊楔形乐高积木一样,我们也需要一些神经网络中的适配块。

你为你的神经网络找到合适的形状了吗?图片来源:安德鲁·马丁来自 Pixabay
改变张量形状最常见的方法是通过汇集或步长卷积(非单位步长卷积)。例如,在计算机视觉中,我们可以使用池化或步长卷积将输入形状的空间维度从 H x W 变为 H/2 x W/2,甚至变为不对称的 H/4 x W/8。然而,为了涵盖简单缩放之外的更复杂的转换,例如执行单应性,我们需要更灵活的东西。多层感知器(MLP) 或变形金刚(带交叉注意力)是两种现成的解决方案。
计算机视觉中使用的神经网络张量一般具有 NxHxWxC 的“形状”(批次、高度、宽度、通道)。这里,我们将关注空间范围 H 和 W 中的形状变化,为了简单起见,忽略批次维度 N,并保持特征通道维度 C 不变。我们将把 HxW 笼统地称为张量的“形状”或“空间维度”。
在 pytorch 和许多其他深度学习库的标准术语中,“整形”不会改变张量中的元素总数。在这里,我们在更广泛的意义上使用单词 reshape,张量中元素的数量可以改变。
如何用 MLP 和变形金刚重塑张量?
MLP 和变形金刚有相似的输入和输出接口,如果我们忽略内部处理的详细机制(通过 MLP 中的隐藏层和变形金刚中的交叉注意模块),如下图所示。

张量整形的 MLP 与变压器的相似界面(图表由作者制作)
使用 MLP 来改变输入张量的形状是相对简单的。对于只有一个全连接层的最简单形式的 MLP,从输入 X 到输出 O 的映射如下。

如果我们在这里忽略激活函数和偏差 b,那么本质是一个矩阵乘法,并且整形过程完全被权重矩阵 w 捕获。张量整形可以通过用 w 对左乘来实现。


MLP 的核心运算是带学习矩阵 W 的 matmul(作者制作的图表)
注意,我们在上面隐含地假设特征通道维数 C=1,并且张量格式是 HWxC,并且批次维数被忽略。这样,我们必须乘以输入左侧的 W 矩阵来改变空间形状。
按照最初的变压器公式,我们有以下映射。

对于交叉注意模块,在上面的等式中,K 和 V 是线性投影的输入 X,Q 是线性投影的输出查询。输出查询具有与输出 o 相同的空间形状。Q、K 和 V 具有以下形状。

与投影矩阵 W 相乘的目标是将输入 X 和输出查询提升到相同的特征维度。请注意,这里使用了右乘,这是一个与上述 MLP 中的整形操作不同的操作。如果我们忽略比例因子和 Softmax 激活函数,我们有以下等式。


《变形金刚》中图解的交叉注意机制(图表由作者制作)
自我注意机制是原变形金刚论文进行特征提取的亮点。然而,自我关注保持原始的输入形状,因为输出查询也是自我关注模块中的输入 X。为了对输入张量进行整形,必须使用具有不同形状(期望的输出形状)的输出查询。
与 MLP 相比,我们有非常相似的公式,两者都将输入与学习的加权矩阵 W 左乘,以实现形状改变。然而,有两个不同之处。
- 输出 O 经过一次额外的线性投影,将特征通道从输入 1 提升到输出 d_k。
- 变压器中的 W 矩阵取决于输入 x。
第一个差异相对较小,我们可以用一个额外的线性投影来匹配 MLP,以改变特征通道。然而,第二个有一些重要的含义。我们将深入探讨 MLP 和变形金刚的两种加权矩阵之间的差异。
区别 1:数据依赖性
MLP 学习的 w 矩阵不依赖于输入数据,而变压器的 w 矩阵依赖于输入数据。一旦在训练期间学习,MLP 的加权矩阵在推断期间是固定的。对于变压器,加权矩阵的数据依赖性可以被视为一种动态加权,使其自身适应不同的输入。类似的讨论也可以在 StackOverflow 上找到。
这可能会让变形金刚更有表现力,但也会让变形金刚比 MLP 更难训练。具体地,对于固定的视图变换(例如逆透视映射(IPM) 或其他类型的单应),MLP 本质上只是学习输入和输出之间的固定映射。对于变压器,额外的输入数据可能会阻碍模型的初始收敛。在实现卓越性能之前,可能需要在 GPU、数据和训练时间方面做出巨大努力,才能达到与 MLP 的盈亏平衡点。

对《变形金刚》和《MLP》的投资回报率的过分简单化的看法(图表由作者绘制)
差异 2:对输入顺序的不变性
对于 MLP,输入和输出的顺序编码在矩阵 w 中。每行和每列对应于输入和输出形状的权重。MLP 不需要位置编码来帮助索引输入和输出。
另一方面,Transformers 中的交叉注意模块对输入顺序是不变的。再看一下交叉注意的等式

如果 x 沿着空间形状维度进行某种排列,红色部分\(X^T X\)将保持不变,因此输出也保持不变。从另一个角度来看,K 和 V 是一个字典的键值对,字典中的顺序并不重要,只要键值映射保持不变。交叉注意机制建立在查询和关键字之间的相似性上,而不是在位置上。
对于自我注意,其中输出查询φ= X,那么 O 的顺序也经历与输入 X 相同的排列,数学上,自我注意是排列等变,而交叉注意是排列不变。

关于注意力的等变和不变的数学陈述(来源
注意机制不对位置信息进行编码的事实也正是位置编码(PE)对于顺序很重要的应用程序索引输入是必要的原因。具体来说,在 NLP 应用中,“猫追狗”和“狗追猫”会导致词对之间完全相同的注意力,这显然是有问题的。
上述交叉注意机制也常用于图形神经网络(GNNs)。它允许网络在训练期间从所有输入实例中捕获共同特征,因为查询独立于输入并且由所有输入实例共享。这里有一条来自 GNN 先驱之一的推文,Thomas Kipf 评论自我关注模块的排列等变。
用于 BEV 感知的张量整形
我写了一篇新的博客,是关于在自动驾驶中使用交叉注意力对 BEV 感知进行张量整形的应用。有关更多详情,请参考下面的链接。
外卖食品
- MLP 和变形金刚(交叉注意力)都可以用于张量整形。
- MLP 学到的整形机制不依赖于数据,而变形金刚的机制依赖于数据。这种数据依赖性使得变压器更难训练,但可能有更高的性能上限。
- 注意力不对位置信息进行编码。自我注意是置换等变的,而交叉注意是置换不变的。MLP 对排列非常敏感,一个随机的排列可能会完全破坏 MLP 结果。
参考
- 注意力是你所需要的全部,NeurIPS 2017
- 《变形金刚中注意的数学性质》,教授纪的课堂笔记,
我正在用一个“专家”人工智能助手提高工作效率
实践教程,野外数据科学和机器学习
展示语言建模在提高工作效率方面的力量

人们在工作中做什么?
我热爱我作为数据科学领导者工作。我一直在探索人工智能可以增加价值的领域。我在自然语言理解领域看到了巨大的机会(NLU)。
让我们暂时忘记模型,把注意力放在人身上,来探索一下。人们实际上在工作中做什么?对我们许多人来说,工作主要是阅读、理解和交流。根据我的经验,90%的非会议活动都遵循这个过程。
阅读->理解->交流
在我所在的行业(银行业),法律、合规和风险部门充斥着这些活动。银行雇佣律师、合规官和风险经理团队来解释冗长的文件并为活动提供建议。工作负载可能非常大,导致效率低下。
我发现了一个机会:一个能够回答基本监管问题并帮助解决更微妙问题的人工智能助理怎么样?
有没有足够强大的语言模型来做到这一点?
1750 亿参数模型
GPT-3 是 Open AI 发布的最新生成式预训练模型。这是目前最先进的语言模型,有 1750 亿个参数。这使得它可以做一些有趣的事情,比如序列对序列建模、元学习和生成建模。用简单的英语来说,它似乎能够完成大多数语言任务,包括复杂的理解。开放人工智能已经向公众发布了 GPT-3 API,开发者已经在开发一些有趣的应用。
一些很酷的:
- SQL 翻译
- 电影到表情符号生成器
- 语言翻译
- Python 到自然语言
专家机器人概念验证
我被 GPT-3 说服了,但我知道我必须带着我的同事一起工作。我想向他们展示一些东西,让他们对语言模型有所启发,所以我决定建立一个概念证明。我称它为专家机器人。
它构建在 GPT-3 API 之上,而且出奇的简单。它接受一个知识库,有一个硬编码的上下文,并使用 GPT-3 引擎来回答与其知识库相关的问题。
我是这样建造的
- 开 AI 账号:在写的时候,他们给你 18 美元的免费 API 调用。
- 私有密钥:一旦你注册了,Open AI 会给你一个组织密钥和一个 API 密钥。
- Anaconda :还需要我多说吗,这使得管理环境和确保安装所有的包依赖项变得容易了。
- 库:你需要这些来让机器人工作。open ai:GPT 3 API 的库。 streamlit :用于创建基于 web 的仪表板/GUI 的库。
- 一个 python IDE:我用的是 PyCharm 社区版。
- 知识库:这需要在。txt 格式。为了演示,我从维基百科上抓取了一些关于 GDPR 的东西。
第一步:设置
您需要创建一个 PyCharm 项目,并确保它指向 Anaconda。您可以创建自己的虚拟环境,但我绝对推荐使用 Anaconda。
安装:你需要用 pip 安装所有的库。如果您正在使用 PyCharm,只需按 alt + f12 打开终端。然后 pip 安装先决条件中提到的所有库。
Python 代码
我用 67 行代码编写了这个机器人。
第 1 部分:导入所有库。
第 2 部分:调用 API 打开 AI。
第 3 部分:为专家机器人设置 web GUI。
第四部分:创建你的知识库
知识库是一个. txt 文件,专家机器人可以从中读取、理解和回答问题。通过提供适当的知识库,你可以让机器人成为任何领域的专家。对于我的演示,我使用在线文章和维基百科页面来管理特定主题的知识库,包括 GDPR 和 ESG ,因为这些主题与我所在的行业相关。我通过简单地复制文本并将其粘贴到。txt 文件。
GPT-3 answers API 要求将知识库转换成一种 jsonl 格式。我使用 python 脚本完成了这项工作,该脚本读取。txt 文件,并将其重写为以“text”为关键字、以文本行为值的字典。最后一行代码将您的知识库文件上传到 OpenAI 的文件 repo。
上面的代码将创建一个类似如下的“knowledge_jsonl.jsonl”文件:

Jsonl 格式的知识库
第 5 部分:查询 GPT3,并将答案打印到 UI。
答案引擎本身只是一个由 Open AI 提供的框架,位于 GPT3 模型之上。您需要为它提供一些东西来获得想要的结果。
- Search_model & model:这些基本上都是用于理解的模型。GPT3 有不同的水平,在不同的任务中表现良好。最“聪明”的是达芬奇,它可以执行需要理解上下文的任务。Ada 是最不复杂的,但是对于解析文本和分类这样的任务来说是最快的。每种模型都有资金和延迟成本,模型越智能,成本就越高,速度也越慢。
- 问题:这是用户想问专家 Bot 的问题。我已经将它设置为从第 3 部分中创建的 streamlit UI 传入。
- File:这只是 jsonl 知识库的文件 id,因为它存储在 OpenAI 的 repo 中。
- 例与例 _context:你需要给模型提供一个问题的例子,引导它朝着你想要的语气和回答格式发展。您需要根据您自己的应用对其进行定制。
- Max_tokens:这只是机器人生成的答案的最大令牌数。如果你只是想要简短的答案,你应该缩短这个。
- Max_rerank:这是搜索排序的最大文档数。一个文档对应于 jsonl 文件中的每个字典行。较高的值可以提高结果的准确性,但会产生延迟成本。
您可以从 IDE 中的终端运行该应用程序。按 alt+f12 并在命令提示符下键入以下代码。
如果你按照说明做了,你会得到类似这样的东西…

专家机器人与 GDPR 知识库,我拉了维基百科
野外专家机器人
向同事们炫耀这一点让我很开心。当我向他们展示它的功能时,我可以明显地看到他们受到了鼓舞。最大的收获是听到他们谈论语言模型的其他用例!
没有严格的测试,我可以说,机器人的性能已经足够的演示使用 GDPR 和 ESG 知识库我给它。
以下是 GDPR 的一些问答:
解释 GDPR
🤖专家 Bot 的回答 >《通用数据保护条例》(GDPR)是欧洲议会、欧盟理事会和欧盟委员会旨在加强和统一欧盟(EU)内所有个人的数据保护的法规。
GDPR 的原则是什么?
🤖专家机器人的回答 >责任、设计隐私、数据最小化、透明、合法、设计安全。
什么是设计隐私?
🤖专家机器人的回答: >隐私设计是一种增强隐私的信息系统开发方法。这是一个自 20 世纪 80 年代以来就存在的概念,但被欧盟的通用数据保护法规(GDPR)赋予了新的生命。
我还试着在公司的“关于我们”页面上进行培训。我发现这并不奏效,但是我仍然在试验这个用例的参数和知识库管理。我发现,虽然答案很棒,但在传递它们时有很大的延迟,大约 20 秒左右。
我学到的是,一个好的 POC 的力量是无与伦比的,尤其是当您试图引入一些新的东西时。
如果一张图片胜过千言万语,那么一个好的 POC 值 1750 亿美元。
更上一层楼
但是,我们不能太得意忘形,在我可以声称已经提高了工作效率之前,还有许多工作要做。
- 验证&测试:这在任何机器学习任务中都是至关重要的。您需要确保您的模型按照您期望的标准运行。我的下一步将是想出一些方法来测试和验证围绕合规性和风险管理的特定任务的 bot 的性能,这是一项完全不平凡的工作。需要做一些工作来了解这些任务的关键业务指标是什么,然后我可以设置实验来测试机器人在这些方面的表现。
- 微调 : GPT-3 已经在整个互联网上进行了训练,但是文献表明,在反映您的用例的数据子集上微调模型可以进一步提高性能。
- 缩放:如前所述,使用这些模型需要金钱成本。在多大的运营规模下,bot 具有净正贡献?只有在考虑了前两点之后,我们才能真正理解这一点。
⭐️ 我喜欢通过分享我在野外的数据科学经验来帮助人们。如果你还不是会员,可以考虑订阅 Medium,从我这里获得更多有用的内容。
https://johnadeojo.medium.com/membership
使用 DataTorch.io 进行图像注释
首次用户和图像注释初学者注释工具指南

制作计算机视觉模型,在保存的植物标本上寻找果实和花朵。(图片由作者提供)
DataTorch 是一款基于网络的机器学习数据标注工具。11 月,我需要为我的数据科学训练营的期末项目的计算机视觉模型的图像添加注释。我以前从未从头开始创建过计算机视觉模型或图像训练数据,需要找到一个快速入门、使用相对直观、易于与项目合作伙伴分享工作的程序。DataTorch 检查了所有的箱子。
DataTorch 平台仍在开发中,因此用户文档尚未完成。首席执行官给了一个简短的视频,概述了如何开始、导入数据集和导出注释,但关于注释图像的细节信息较少。
在我们开始之前澄清一下,我与 DataTorch 没有任何关系。偶然发现的,喜欢上了,觉得别人可能也会喜欢。
如果你以前有过带注释的图片,DataTorch 是一个扩展的商业版本的 COCO Annotator ,老实说,你可能不会在这篇文章中找到太多新的信息。与 COCO Annotator 相比,我更喜欢 DataTorch,因为它更容易与多人共享注释任务,并且我不必担心更新或覆盖工作。
因为 DataTorch 正在开发中,如果我注意到用户界面有变化,我会更新帖子。最后更新时间:2021 年 1 月 19 日
下图显示了正在进行的图像注释。让我们来看看左侧工具栏上可用的工具和快捷方式。

正在进行图像注释(UZH 植物标本室标本)

无论选择哪种工具,滚轮都会放大和缩小照片。
平移:拖动图像。选择:为了对一个遮罩进行编辑,需要选择它。如果只编辑一个遮罩,我们可以直接单击它,或者通过单击并拖动来创建一个突出显示的遮罩区域。只有选择区内完全的屏蔽会被突出显示。此外,如果您想将一个遮罩视为与您刚刚绘制的遮罩不同,您可能需要通过单击图像取消选择。有些工具会自动切换到新的遮罩,有些则不会。
下一组工具实际上创建了遮罩。铅笔:(官方多边形选择)最适合多边形,故名。每次单击,程序都会在点之间画一条直线,直到您单击它本身。或者你可以自由绘制,只要你在起点结束形状。跳到笔刷:非常类似于铅笔,但是你不需要关闭你开始的形状。一旦你画好了,这个程序会把你覆盖的任何区域变成一个遮罩。我使用画笔工具最多的是为了在我的图像中覆盖水果和花的奇怪形状。左右方括号键[ ]使画笔变小或变大。橡皮擦:只对选中/高亮的蒙版有效。像画笔一样,它用[ ]键改变大小。
矩形和圆形是不言自明的——单击并拖动以调整大小。如果您需要删除一个,右键单击并选择删除。到目前为止,我已经不需要像素或关键点了,尽管说实话,我还没有弄清楚它们是做什么的…
对于没有快捷方式的工具,您可以在右下角的框中更改工具设置。如果你需要改变一个标签,例如从花到果,右击并直接选择面具上的“改变标签”或底部中间框中的面具名称。

如果你想添加一些自定义信息到掩码文件的元数据中,你可以在右边的栏中。
注释完图像后,在右上角标记完成。绿色的完成复选标记将显示在注释页面以及数据集和文件页面上。
祝你的计算机视觉项目好运!
具有领域相关伪像的图像增强

图片来自 Unsplash 的 OpticalNomad
用头发,昆虫和针隆胸的例子
介绍
当在图像上训练深度神经网络模型时,扩充训练样本(图像)可以允许模型通过在由扩充人工生成的更多图像上进行训练来更好地概括。常用的增强包括水平和垂直翻转/移位、在某一角度和方向(顺时针/逆时针)的随机旋转、亮度、饱和度、对比度和缩放增强。
Python 中一个非常流行的图像扩充库是albumentations(https://albumentations.ai/),它通过直观的函数和优秀的文档使图像扩充变得容易。它也可以与 PyTorch 和 TensorFlow 等流行的深度学习框架一起使用。
领域相关伪影增强
直觉
这种用现实生活场景中可以找到的伪像来增加图像数据的方法背后的想法来自于模仿和概括现实中可能遇到的跨图像的模型的动机。例如,像雪或雨滴这样的增强物是不应在 x 光图像中发现的伪影,但胸管和起搏器是可以在 x 光图像内发现的伪影。
这个想法是从哪里来的
我第一次改变方法是从 Roman 的(@ nroman on Kaggle)为 SIIM-ISIC 黑色素瘤分类竞赛做的头发增加。他的方法的细节可以在这里找到:https://www . ka ggle . com/c/siim-ISIC-黑素瘤-分类/讨论/159176 。该增强的一个片段如下:

原图(左上)和头发增强图(右上),摘自https://www . ka ggle . com/c/siim-ISIC-黑色素瘤-分类/讨论/159176
我的团队在我们的模型训练中使用了他的增强,这有助于提高我们大多数模型的交叉验证(CV)分数。我想说这种形式的增强可能在我们的最终排名中起到了关键作用!从那以后,用头发(或一般的人工制品)来增加图像数据的想法在我参加的随后的比赛中站得很近,并在我可以的地方应用它。特别是在全球小麦检测、木薯叶部病害分类和 RANZCR CLiP —导管和线位挑战赛中推广应用了该方法。
昆虫扩增
顾名思义,这种方法包括用昆虫来增强图像。这种人工制品的正确领域可以是数据中的一种自然设置,其中昆虫通常在空中或表面上到处可见。在这个例子中,在木薯和全球小麦检测比赛中,当增强叶子图像时,蜜蜂被用作选择的昆虫。以下是增强图像的外观示例:

蜜蜂在树叶周围飞舞的增强图像,摘自https://www . ka ggle . com/khoongweihao/昆虫增强-et-al/notebook
我们还可以使用伪像的掩蔽形式,在图像中产生黑点(类似于白蛋白中的CoarseDropout),即没有颜色的黑色蜜蜂:

深色/黑色蜜蜂在树叶周围飞行的增强图像,取自https://www . ka ggle . com/khoongweihao/昆虫增强-et-al/notebook
以下以 albuminations 风格编写的代码允许工件增强与来自 albuminations 库的其他增强一起轻松使用:
from albumentations.core.transforms_interface import ImageOnlyTransform
class **InsectAugmentation**(ImageOnlyTransform):
*"""*
*Impose an image of a insect to the target image*
*-----------------------------------------------*
*Args:*
*insects (int): maximum number of insects to impose*
*insects_folder (str): path to the folder with insects images*
*"""*
def __init__(self, insects=2, dark_insect=False, always_apply=False, p=0.5):
super().__init__(always_apply, p)
self.insects = insects
self.dark_insect = dark_insect
self.insects_folder = "/kaggle/input/bee-augmentation/"
def apply(self, image, **kwargs):
*"""*
*Args:*
*image (PIL Image): Image to draw insects on.*
*Returns:*
*PIL Image: Image with drawn insects.*
*"""*
n_insects = random.randint(1, self.insects) *# for this example I put 1 instead of 0 to illustrate the augmentation*
if **not** n_insects:
return image
height, width, _ = image.shape *# target image width and height*
insects_images = [im for im **in** os.listdir(self.insects_folder) if 'png' **in** im]
for _ **in** range(n_insects):
insect = cv2.cvtColor(cv2.imread(os.path.join(self.insects_folder, random.choice(insects_images))), cv2.COLOR_BGR2RGB)
insect = cv2.flip(insect, random.choice([-1, 0, 1]))
insect = cv2.rotate(insect, random.choice([0, 1, 2]))
h_height, h_width, _ = insect.shape *# insect image width and height*
roi_ho = random.randint(0, image.shape[0] - insect.shape[0])
roi_wo = random.randint(0, image.shape[1] - insect.shape[1])
roi = image[roi_ho:roi_ho + h_height, roi_wo:roi_wo + h_width]
*# Creating a mask and inverse mask*
img2gray = cv2.cvtColor(insect, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
*# Now black-out the area of insect in ROI*
img_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
*# Take only region of insect from insect image.*
if self.dark_insect:
img_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
insect_fg = cv2.bitwise_and(img_bg, img_bg, mask=mask)
else:
insect_fg = cv2.bitwise_and(insect, insect, mask=mask)
*# Put insect in ROI and modify the target image*
dst = cv2.add(img_bg, insect_fg, dtype=cv2.CV_64F)
image[roi_ho:roi_ho + h_height, roi_wo:roi_wo + h_width] = dst
return image
如果你想使用黑色版本的神器,将dark_insect设置为True。在我的 Kaggle 笔记本中可以找到一个示例实现:https://www . ka ggle . com/khoongwihao/昆虫增强高效检测-d6/notebook 。
针头增大术
在这种方法中,使用针来增强图像,例如,图像可以是 x 射线图像或带有针线包的桌面。以下是增强图像的外观示例:

x 光左侧有针的增强图像,取自https://www . ka ggle . com/khoongweihao/x-ray-needle-augmentation-et-al/notebook
类似地,我们可以使用针状伪像的黑色版本,从而得到以下增强图像:

x 光片两侧暗/黑色针状物的增强图像,取自https://www . ka ggle . com/khoongweihao/x-ray-needle-augmentation-et-al/notebook
用作上述增强模块的代码片段如下:
def NeedleAugmentation(image, n_needles=2, dark_needles=False, p=0.5, needle_folder='../input/xray-needle-augmentation'):
aug_prob = random.random()
if aug_prob < p:
height, width, _ = image.shape *# target image width and height*
needle_images = [im for im **in** os.listdir(needle_folder) if 'png' **in** im]
for _ **in** range(1, n_needles):
needle = cv2.cvtColor(cv2.imread(os.path.join(needle_folder, random.choice(needle_images))), cv2.COLOR_BGR2RGB)
needle = cv2.flip(needle, random.choice([-1, 0, 1]))
needle = cv2.rotate(needle, random.choice([0, 1, 2]))
h_height, h_width, _ = needle.shape *# needle image width and height*
roi_ho = random.randint(0, abs(image.shape[0] - needle.shape[0]))
roi_wo = random.randint(0, abs(image.shape[1] - needle.shape[1]))
roi = image[roi_ho:roi_ho + h_height, roi_wo:roi_wo + h_width]
*# Creating a mask and inverse mask*
img2gray = cv2.cvtColor(needle, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
*# Now black-out the area of needle in ROI*
img_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
*# Take only region of insect from insect image.*
if dark_needles:
img_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
needle_fg = cv2.bitwise_and(img_bg, img_bg, mask=mask)
else:
needle_fg = cv2.bitwise_and(needle, needle, mask=mask)
*# Put needle in ROI and modify the target image*
dst = cv2.add(img_bg, needle_fg, dtype=cv2.CV_64F)
image[roi_ho:roi_ho + h_height, roi_wo:roi_wo + h_width] = dst
return image
请注意,以上不是白蛋白格式,不能直接应用于通常的白蛋白增加。必须做一些调整,使其格式与上面的昆虫/蜜蜂增强相同。但是变化应该是微小的!
类似地,如果您希望使用工件的黑色版本,请将dark_needles设置为True。一个实现的例子可以在我的 Kaggle 笔记本这里找到:https://www . ka ggle . com/khoongweihao/x-ray-needle-augmentation-et-al/notebook。
实验结果
总的来说,局部 CV 结果有所改善,但改善幅度不大(例如 0.001–0.003)。但是在训练中,使用这种伪影增强的方法会有“失败”的情况。在全球小麦探测竞赛中可以找到一个例子,其中任务涉及探测小麦穗,即物体探测任务。尽管进行了广泛的超参数调整,但使用蜜蜂的原始颜色进行蜜蜂增强导致了具有巨大波动的零星训练验证损失。虽然使用增强确实提高了 CV,但可以说这确实是一次幸运的尝试。使用仅保留黑色像素的伪影增强(如粗略丢失增强)被证明在应用领域中是稳定的。特别是,CV 的提高是实质性的,也是持续的。到目前为止,还没有找到蜜蜂扩增导致不同时期之间出现这种零星训练结果的原因,但一个假设是蜜蜂的颜色接近一些麦穗,因此“混淆”了检测算法,该算法随后在同一边界框内捕获麦穗和最近的蜜蜂。在一些边界框预测中观察到了这一点,但是没有足够多的观察案例来肯定地说该假设是正确的。在任何情况下,还应该考虑伪影的图像属性(颜色)是否具有接近目标(例如麦穗)的分布。另一方面,针隆乳被证明对两种隆乳都是相对稳定的(原始伪影及其黑色/深色版本)。在该示例中,尽管颜色分布相似,但预测的目标可能具有不同的特征(例如,胸腔管看起来与细小的针非常不同),从而分类算法不会混淆针是否是正确的目标。
基于视觉-语言转换模型的图像字幕
思想和理论
生成具有视觉注意力的自主字幕

示例生成的标题(图片由作者提供)
这是一个以实验为目的的研究项目,有很深的学术文档,所以如果你是一个论文爱好者,那么就去查看这篇文章的项目页面
https://github.com/cankocagil/Image-Captioning
这篇文章将是从论文到文章的一个几乎直接的过渡,将涵盖使用简单而有效的计算机视觉和 NLP 应用程序的图像字幕任务,并具有深刻的解释。如果你正在寻找一个带有可解释的代码、计算和文档的图像标题的启动,你来对地方了。包括一切。本文将向您介绍任何图像字幕任务的概念和上下文设计,包括全面的视觉效果和“从头开始”的代码,以及解释。为了简单起见,代码被排除在外,但托管在项目页面上。来自其他来源的视觉资料的参考文献在标题中嵌入了链接,而其他参考文献则在最后给出。让我们潜入更深的地方。
全景
本文主要关注在深度学习环境下使用最新技术的图像字幕任务。图像字幕是通过使用自然语言处理和计算机视觉应用生成图像的文本描述的过程。该网络由卷积神经网络(CNN)组成,用于将图像编码为潜在空间表示,随后是递归神经网络(RNN ),用于解码特征表示和构建语言模型。具体来说,长短期记忆(LSTM)和门控循环单位(GRU)被用作 RNN 模型与注意机制和教师强迫算法。为了实现这一点,AlexNet、VGG 网络、ResNet、DenseNet 和 SquezeeNet 等迁移学习应用程序被用作卷积编码器,而单词表示的全局向量(GloVe)被用于单词嵌入。Microsoft 上下文中的公共对象(MSCOCO)数据集用于训练和测试。实施各种数据扩充技术来提高模型性能。该模型由 Adam optimizer 以预定的学习率编译。掩蔽的交叉熵损失被用作模型的标准。最后,实现了 beam 和贪婪搜索算法,以获得最佳的图像到字幕的翻译。
要覆盖的关键字
- 图像字幕问题的表述
- 图像字幕的理据
- 视觉和语言翻译模型
- 迁移学习
- 数据扩充
- 注意模型和教师强迫算法
- 传统 CNN:Alex Net、VGG 网络、ResNet、DenseNet 和 SquezeeNet
- 语言模型:长短期记忆(LSTM)和门控循环单元(GRU)
- 单词表示的全局向量(GloVe)
- 波束和贪婪搜索
- 蓝色分数和流星
1.简介和动机
随着与人工智能相关的新技术的出现,图像字幕已经成为最吸引研究者的领域之一。图像字幕,根据图像中观察到的内容自动生成自然语言描述,是场景理解的重要组成部分,融合了计算机视觉和自然语言处理的知识[1]。图像字幕的应用非常广泛,例如连接人机交互,它还可以帮助视障人士“看”未来的世界[1]。在图像字幕的早期阶段,统计语言模型被用来提出为图像生成字幕的解决方案。李等人提出了一种基于网络规模的 n 元语法方法,收集候选短语并将其合并以形成从零开始描述图像的句子[2]。Yang 等人提出了一种从英语 Gigaword 语料库训练的语言模型,以获得图像中运动的估计以及搭配的名词、场景和介词的概率,并使用这些估计作为隐马尔可夫模型的参数[2]。根据文献综述,我们提出了深度学习背景下图像字幕技术的基本阶段。以下是图像字幕管道和广泛使用的技术的概述:
1.1 特征提取
图像字幕任务从从图像中提取特征开始,以将可能的 3 (RGB)通道高维数据的维度减少到潜在空间表示中。在文献中,已经有预先训练的模型,这些模型通过使用由超过 120 万幅自然图像组成的称为“图像网”的数据集进行训练,例如 AlexNet、VGG 网、ResNet、GoogleNet、DenseNet、SqueezeNet 等等。我们实现了所有提到的模型,除了 GoogleNet,我们将在本文后面看到。
1.2 语言模型
作为图像字幕的第二阶段,字幕和潜在空间特征向量被赋予语言模型以生成字幕。为了实现这一点,有各种各样的模型在文献中广泛使用,如 LSTM 的,双向 LSTM 的,RNN 的,CNN 的,GRU 的,和 TPGN 的。我们在实现中使用了 LSTM 和 GRU 的递归网络,我们将在方法部分讨论。
1.3 常用技巧
为了生成图像字幕模型,在文献中使用了以下图像到序列技术:
- 编码器-解码器
- 注意机制
- 新对象范式
- 语义学
此外,注意有各种算法用于实现图像到序列网络,这些是广泛使用的技术。在这个项目中,我们使用了编码器-解码器模型和带有注意机制的教师强制器。
1.4 数据集
MS COCO 和 Flickr 数据集广泛用于图像字幕任务。我们在实现中使用了 MSCOCO。
1.5 绩效指标
为了评估图像到序列模型的性能,使用以下评估指标:
- BLEU-i(对于 i = 1,…,4)(例如,BLUE-1)
- 苹果酒
- 流星
对于我们的实现,我们使用 BLUE-1、BLUE-2、BLUE-3、BLUE-4 和 METEOR 度量来评估语言模型在训练结束时的输出。
1.6 建筑管道
如上所述,在我们的例子中,MSCOCO 数据集用于训练/验证和测试,它有超过 80 000 幅自然图像。这些图像的网址给我们使用。但是,由于大约 10%的 URL 被破坏,我们有近 70 000 到 73 000 张图像用于训练和验证。整个集被分成 15%的验证集和 85%的训练集。此外,在该数据集中,每幅图像都配有 4 到 5 个描述该特定图像内容的相关标题。然后,实施各种数据扩充技术来提高模型的性能。对于特征提取,我们使用具有预训练模型的 CNN 编码器结构,ResNet152、AlexNet、VGG19-Net、DenseNet、SqueezeNet,来试验特征提取的性能。在这样的实验之后,ResNet152 的表现比我们在后面几页中看到的稍好一些。(ResNet 代表用于图像识别的深度残差学习。)我们的编码器 CNN 模型是在 ImageNet 数据集中预先训练的,由多个卷积层组成。对于 ResNet152,残差神经网络利用跳过连接或快捷方式跳过一些层,以降低层对给定图像数据的适应性。典型的 ResNet 模型通过双层或三层跳跃实现,其中包含非线性( ReLU )和批处理归一化[3]。跳过层的一个动机是避免消失梯度的问题,通过重用前一层的激活,直到相邻层学习其权重【3】。我们的剩余网络由 152 层组成。
然后,对于语言模型,通过注意机制和教师强制技术,使用 LSTM 和 GRU 递归神经网络。作为第一步,单词嵌入层用于句子长度字幕的单词表示。为了实现这一点,我们使用了单词表示的全局向量(GloVe),这是一种用于获得单词的向量表示的无监督学习算法[4]。在来自语料库的聚合的全局单词-单词共现统计上执行训练,并且产生的表示展示了单词向量空间的有趣的线性子结构[4]。我们使用的手套模型是用 60 亿个标记和 400 000 个语料库训练的。然后,为了最小化模型的开销,使用了掩蔽交叉熵损失,并通过 Adam 优化器对编码器和解码器模型进行优化。然后,在训练阶段的最后,我们希望我们的视觉和语言模型能够为自然图像生成人类级别的字幕。
2.方法
2.1 训练/验证/测试拆分
我们将给定的训练数据集分成 85%的训练和 15%的验证,以便在训练时跟踪模型的历史,并且我们还应用了交叉验证技术,以便在模型开始过度拟合时停止训练。因此,在数据集分割结束时,我们有 340 114 个唯一的训练图像和 60 021 个唯一的验证图像。此外,一个单独的测试数据集给我们,以衡量一个模型的性能与各种各样的自然图像。
2.2 预处理
由于图像有不同的大小,我们首先转换可接受大小的图像,在我们的例子中是 224x224,以满足视觉模型的要求。所有提到的 CNN 模型都接受(N x C x H x W)约定的输入,即 PyTorch RGB 图像批处理约定。在我们的例子中,所有 CNN 模型都接受 C,H,W = (3,224,224)约定。最后,对所有图像进行归一化,以获得零均值和单位标准差分布,从而加速训练。我们通过以下方式分别对每个颜色通道应用标准化
- 平均值= [0.485,0.456,0.406]
- 标准偏差= [0.229,0.224,0.225]
使用统一公式:
输出[通道] =(输入[通道] —平均[通道]) /标准[通道]
这些是 ImageNet 的 RGB 通道平均值和标准偏差,可以表示自然图像的广义平均值和标准偏差。
2.3 数据扩充
数据分析中的数据扩充是一种用于增加数据量的技术,通过添加已有数据的稍微修改的副本或从现有数据中新创建的合成数据[5]。它充当正则化子,并在训练深度学习模型时帮助减少过拟合。为了实现这一点,我们应用了以下数据扩充技术:
- 随机水平翻转
我们以 0.5 的概率水平翻转图像数据。
- 随机垂直翻转
我们以 0.5 的概率垂直翻转图像数据。
- 随机裁剪
我们随机裁剪 224x224 尺寸的图像。
- 随机调整大小裁剪
我们将图像的大小调整为 256x256,然后随机裁剪 224x224 的部分。
- 中心裁剪
我们将图像的大小调整为 256x256,然后在中心裁剪 224x224 的部分。
2.4 传输学习:编码器 CNN
如前所述,我们分别使用了 ResNet152、AlexNet、VGG19-Net、DenseNet 和 SqueezeNet 将高维图像转换为潜在空间表示。因此,我们应用特征提取,即,我们在训练时冻结 CNN 模型的层的可学习/可训练参数,即,我们不更新编码器模型的网络参数。迁移学习的另一种可能的方法是微调网络,但由于计算方面的考虑,我们更喜欢只提取特征。然后,我们将我们的特征向量传递到嵌入层,得到一个嵌入的图像特征向量。这个嵌入层以一种可学习的方式将潜在空间表示转换成嵌入空间。现在,我们的图像已经准备好输入语言模型了。下图显示了所解释的体系结构。

因此,嵌入的图像特征向量作为解码器网络的初始输入,在教师强制器算法中具有<x_start_>字幕。</x_start_>

2.5 迁移学习:单词嵌入(手套)
如前所述,我们为单词嵌入层购买了一个预训练的手套单词向量。这加快了我们的训练阶段,因为这些向量已经用 60 亿个英语标记进行了训练。因此,我们成功地将字幕转换成高级单词表示,以便它们可以输入到解码器模型中。
这一层将我们的句子长度序列字幕转换成嵌入的单词特征向量,以便字幕可以传递给语言模型。
2.6 解码器配教师强迫器
自然语言处理中的许多递归神经网络(例如,图像字幕,机器翻译)在训练过程中使用教师强制[6]。教师强制是一种快速有效地训练递归神经网络模型的方法,该模型使用来自先前时间步骤的基础事实[7]。我们举一个 teacher forcer 算法的例子,让任意图像的地面真相标题为“两个人在看书”。我们的模型在预测第二个单词时犯了一个错误,我们在第一个和第二个预测中分别有“两只”和“鸟”[8]
- 如果没有老师强迫,我们会把“鸟”喂回我们的 RNN 去预测第三个单词。假设第三个预测是“飞行”。尽管我们的模型预测“飞行”是有意义的,因为输入是“鸟”,但这与地面事实不同。[8]
- 另一方面,如果我们使用教师强制,我们将在计算并记录第二次预测的损失后,为第三次预测向我们的 RNN 提供“人”[8]
然后,我们来讨论一下 teacher forcer 的利弊。
- 优点
用老师强制训练收敛更快。在训练的早期阶段,模型的预测非常糟糕。如果我们不使用老师强制,模型的隐藏状态会被一系列错误的预测更新,误差会累积,模型很难从中学习。[10]
- 缺点
在推断过程中,由于通常没有实际情况可用,RNN 模型需要将自己之前的预测反馈给自己,以便进行下一次预测。因此,训练和推理之间存在差异,这可能会导致模型性能不佳和不稳定。这在文献[11]中被称为曝光偏差。
因此,我们在语言模型中使用了 teacher forcer 算法。
2.7 带教师强制器和注意力机制的解码器
作为模型的第二阶段,我们用 LSTM 和 GRU 网络实现了一个带注意机制的解码器。RNN 的工作是解码特征和单词向量,并将其转化为单词序列[6]。在解码器中,作为教师强制算法的一部分,我们首先在时间 t = 0 将嵌入的特征向量传递给解码器。然后,我们使用实际的 teacher forcer 算法逐字传递字幕。因此,我们实现了一个语言模型来将潜在的空间向量映射到单词空间。[7].这里的关键思想是在时间 t=0 [7]将表示图像的潜在空间向量作为输入馈送到递归单位单元。从时间 t=1 开始,我们可以开始将嵌入的目标句子馈送到递归单元中,作为 teacher forcer 算法的一部分[8]。然后,LSTM 单元的输出是隐藏状态向量。因此,我们需要某种从隐藏状态空间到词汇(字典)空间的映射[9]。我们可以通过在隐藏状态空间和词汇空间之间使用完全连接的层来实现这一点[9]。以下架构描述了 LSTM 机制:

然后,在注意力集中的设置中,我们希望解码器能够在序列中的不同点查看图像的不同部分。我们使用所有像素的加权平均值,而不是简单的平均值,重要像素的权重更大【17】。该图像的加权表示可以在每一步与先前生成的单词连接,以生成下一个单词[17]。注意力机制计算这些权重来估计图像的重要部分。我们已经使用了随机软注意机制,其中像素的权重相加为 1,如在“展示、出席和讲述”论文[18]中所提议的。如果在我们的编码图像中有 P 个像素,那么在每个时间步长 t

人们可以将这整个过程解释为计算一个像素是一个位置的概率,以生成下一个单词[17]。

数据流从卷积视觉模型开始,以创建图像的潜在空间表示,然后是递归模型,以创建 LSTM 的初始隐藏和单元状态,以及 GRU 解码器的隐藏状态。在解码的每个时间步,潜在空间表示和先前计算的递归单元的隐藏状态被用于生成图像像素的权重,作为注意机制的一部分。然后,基本事实字幕和编码的加权平均值被馈送到解码器语言模型,以生成下一个字幕,作为教师强制器和注意力算法的组合。下图代表了教师强迫者在软随机注意中的信息流。

2.8 定额渐变裁剪
梯度裁剪是一种防止在非常深的网络中(通常在递归神经网络中)爆发梯度的技术[17]。有许多方法来计算梯度裁剪,但一个常见的是重新调整梯度,使他们的范数最多是一个特定的值[17]。通过梯度裁剪,引入预定的梯度阈值,然后缩小超过该阈值的梯度范数以匹配范数[17]。这防止任何梯度具有大于阈值的范数,因此梯度被剪切[17]。

其中 g 是要限幅的梯度,thres 是作为超参数的阈值,而‖g‖是 g 的范数。
因此,我们实现了范数梯度裁剪,以将梯度保持在某个范围内,该范围由阈值表征,并且在这样的实验之后被确定为 10
2.9 GPU 加速和并行分布式处理
然后,由于我们正在使用 GPU 加速和分布式计算,我们需要将我们的数据类型转换为有能力在 GPU 中运行图像处理任务的张量。具体来说,NVIDIA Tesla K80 和 GeForce RTX 2080 TI 被用作 GPU 来加速训练。然后,我们使用了分布式数据并行(DDP ),它在模块级实现了数据并行,可以跨多个 GPU 运行。[10]使用 DDP 的应用程序产生多个进程,并为每个进程创建一个 DDP 实例[10]。因此,我们利用了 CUDA 多处理。为了实现这一点,利用了深度学习框架 PyTorch 的 DataParellel 包。
2.10 掩蔽交叉熵
屏蔽交叉熵实际上是一种分类交叉熵,将屏蔽应用于由序列长度确定的一些输入。这背后的原因是我们有填充序列,即数据集中的每个字幕都有不同的长度,因此要构建字幕向量,我们需要通过在字幕末尾添加

因此,为了不计算填充区域的损失和梯度,我们实现了采用预测字幕、实际字幕和句子长度的掩蔽交叉熵,并对非填充区域应用交叉熵。这加快了训练过程。
2.11 Adam 优化器
编码器和解码器模型都使用 Adam optimizer 通过以下数学表达式进行优化:

其中 M_i 和δ_V_i 分别是一阶和二阶矩中梯度的累加和,θ是要更新的参数。还要注意,也使用了基本的基于随机梯度下降的学习规则、RMSprop 和 AdaGrad,但是 Adam optimizer 的性能最差,因此我们继续使用 Adam optimizer。
2.12 自适应学习率调度器
在 PyTorch 优化器包的帮助下,我们应用了基于验证交叉熵和 argmax 搜索精度的动态学习率调度器。该算法在以下情况下降低学习率
- 交叉熵损失没有减少
- arg max 预测的准确性没有提高
因此,当指标停止改善时,我们降低了学习率。它启用了动态学习率调度器,并且可以在提前停止之前提高模型的性能。此外,我们还应用了一个基于批量改进的简单学习率调度器。因此,使用三种不同的学习延迟调度器来提高模型的性能。注意,作为进一步的实现,自适应学习速率调度器可以通过使用蓝分数来执行,蓝分数可以在网络内给出额外的自适应性。
2.13 交叉熵提前停止
基于交叉熵损失,以批次和时段方式跟踪模型的历史,以避免过拟合。如果训练和验证损失之间的差距开始增大,我们就停止训练。注意,由于我们应用了自适应学习速率调度,我们让模型适应新的学习速率,如果模型不能自我改进,我们就停止训练。幸运的是,我们的模型在训练和测试中表现相似,因此早期停止并不用于整个训练过程。
2.14 光束搜索
Show and Tell 论文[11]提出了波束搜索,作为在给定输入图像的情况下生成具有最高出现可能性的句子的最终步骤。[12]该算法是一种最佳优先搜索算法,该算法迭代地将直到时间 t 的 k 个最佳句子的集合视为候选,以生成大小为 t + 1 的句子,并且仅保留它们中的最佳 k 个句子,因为这更好地近似了获得论文[12]中提到的全局最大值的概率。

由 Sagar Vinodababu 对光束搜索进行视觉解释
我们使用从 1 到 9 的波束大小实现了波束搜索,以查看波束大小参数的变化。结果将在下一部分讨论。
2.15 A 框架加速深度编解码
让我们回忆并收集一下我们为提高模型性能所做的工作。我们通常主要使用多重处理和多线程的概念。为了加载和转换数据,我们使用了 4 个线程的多线程概念。此外,我们转换加载,并将应用程序转换为 GPU 格式以加速。然后,我们使用了 PyTorch 内部实现的生成器概念,这将提高 RAM 效率,并允许在训练阶段进行这样的操作。最重要的是,我们在 GeForce RTX 2080 TI 和 Tesla K80 上使用了 PyTorch 提供的分布式并行计算应用。然后,对于神经网络模型应用,我们冻结卷积模型和手套字嵌入模型的可学习参数,即,我们不计算将明显加速训练阶段的这些层的梯度。对于语言模型,我们实现了 teacher forcer 算法,该算法也提高了生成字幕的时间效率。此外,考虑到模型性能和时间效率,调整了各种超参数。最后,使用提到的不同优化器来测量优化时间,以便根据批量改进和更新时间的标准来选择最佳的优化器。
2.16 翻译模型的评价指标
如上所述,我们还使用 BLEU 分数来评估模型,BLEU 分数是双语评估替角,是用于将文本的候选翻译与一个或多个参考翻译进行比较的分数。它评估模型从一种语言翻译到另一种语言的效果。它根据生成的输出中存在的一元词、二元词或三元词为机器翻译分配一个分数,并将其与基本事实进行比较[15]。然而,它没有考虑语言的意义、句子结构和形态丰富的部分。此外,我们使用了 metric METEOR(用于评估具有显式排序的翻译的度量),该度量基于 unigram 精度和召回的调和平均值,召回权重高于精度。[14]此外,在训练模型时,还计算 argmax 准确度以查看批量改进。注意,这对于图像到序列的转换模型来说不是一个合适的度量,实现它只是为了查看学习曲线。
3。结果
我们已经为卷积编码器实现了 ResNet152、AlexNet、VGG19-Net、DenseNet 和 SqueezeNet。然后,我们建立了一个可训练的嵌入层,嵌入大小为 256(嵌入大小的值是一个超参数,它是在这样的嵌入大小实验后发现的)。之后,我们将字幕转换成嵌入大小为 256 的文字嵌入层。然后,嵌入的特征向量和单词嵌入通过教师强制器和注意机制被传递到语言模型中以生成字幕。提到的算法是我们在图像字幕生成方面的研究的最终发现。我们尝试了不同的批量大小,如 32、64、128 和 256 作为批量调整。我们的最终模型是用批量 64 训练的。当批量大小为 256 时,即使两个模型的性能都稍好一些,我们也会遇到与 CUDA 内存相关的内存问题,因此我们更倾向于将 64 作为理想的批量大小。即使应用了动态学习调度器,我们也从编码器和解码器模型的较高学习速率开始。对于编码器,选择 4x 10–2 作为初始学习速率,选择 1x 10–2 作为初始解码器学习速率。然后,再来说说车型性能。该模型在 NVIDIA Tesla K80 GPU 和 GeForce RTX 2080 TI 上训练,具有 2-3 个纪元。每个纪元需要 1-1.30 GPU 小时 NVIDIA Tesla K80 和 45-60 分钟 GeForce RTX 2080 TI。因此,每个型号的总培训时间大约需要 2-4 小时,具体取决于性能。我们每个时期的训练时间非常快,因为我们已经实现了各种运行时高效算法。(参见加速深度编码器-解码器部分的框架)此外,对于所有模型,交叉熵损失从大约 7 开始,在训练结束时,我们已经达到了 1.60–1.10(对于下面给出的模型)。我们还计算贪婪精度,即使精度不是我们的图像到序列转换模型查看学习曲线的正确度量。argmax 的准确度从 1%开始,在训练结束时达到大约 70–80%。蓝色和流星分数是为所有型号计算的,如下所示。根据表 I(见下文),到目前为止,整体视觉和语言模型中的赢家是编码器的 ResNet152 和解码器的 GRU,其注意力机制通过 BLEU 评分和 METEOR 的标准来表示所生成字幕的语言性能,因此它们是图像到序列翻译模型的最佳评估指标。我们看到,在 3 个时期结束时,模型达到 1.05 熵损失,具有 78.2 argmax 准确度(即使准确度不是翻译模型的正确度量)。更重要的是,我们对 BLUE-1 和 METEOR 的评分分别达到了 67.5 和 22.59,这对于翻译模型来说几乎是完美的。略有不同的是,2ndwinner 是由 ResNet152 和 LSTM 组成的模型。因此,我们可以得出结论,ResNet152 对于我们的特征提取任务表现良好。然而,当我们比较所有的视觉和语言模型时,我们发现我们使用 MSCOCO 数据集的实现没有很大的区别。因此,我们可以说所有的视觉和语言模型在测试案例中都表现得非常好。
然后,让我们从测试图像的标题开始。但在此之前,值得讨论的是语言的结构,以评估生成的字幕。一般来说,语言由几个部分组成,如语音学、音韵学、形态学、句法、语义学和语用学。因此,以语言结构为标准来探讨字幕生成的语言特征是值得的。
以下是从测试中随机选择的样本,并为其生成了标题:

ResNet152-to-GRU 为从测试数据中随机选择的图像引用和生成的标题(图片由作者提供)

所有模型之间的交叉熵、Argmax 精度、BLEU-1、2、3、4 和流星度量比较(图片由作者提供)
生成的标题“一个骑马的女人”是该图像的完美标题,因此我们的模型实际上完全符合语言标准,因为它具有正确的语法、可理解的语义和正确的上下文含义。让我们继续检查例子。

由从测试数据中随机选择的图像的 ResNet152 到 GRU 字幕引用和生成(图像由作者提供)

AlexNet-to-GRU 从测试数据中随机选择的图片的参考和生成的说明(图片由作者提供)
有趣的是,我们的模型捕捉到大象是一个婴儿,天气晴朗。我们很高兴看到一个完美的标题。此外,有趣的是,我们的模型在图中捕捉到了一支笔。因此,即使满足了语言标准,也很高兴看到模型能够捕捉到细节。

由从测试数据中随机选择的图像的 ResNet152 到 LSTM 字幕引用和生成(图像由作者提供)
我们看到生成的字幕是人类级别的,即它们满足所有相应的语言结构组件。它有强大的语义和语法。让我们看看另一个由 beam search 创建的标题。

通过波束解码生成的字幕示例(图片由作者提供)
我们看到由我们的视觉和语言模型生成的字幕通过额外的波束搜索算法非常强大。标题满足所有的语言标准,甚至在某些情况下,我们有比参考标题更有意义的标题。这里有更多的字幕生成的 ResNet152 到 GRU 模型与波束搜索和波束宽度是 7。

ResNet152-to-GRU 为从测试数据中随机选择的图像提供参考并生成标题(图片由作者提供)
4。讨论和结论
自动图像字幕远未成熟,有许多正在进行的研究项目旨在更准确的图像特征提取和语义更好的句子生成[19]。在我们的例子中,我们成功地生成了满足基本语言标准的标题,比如句法、语义、语用和形态意义。作为进一步的改进,有多种方法可以提高图像字幕环境中视觉和语言模型的性能。如上所述,出于计算考虑,我们没有对编码器模型和手套字向量的预训练参数进行微调,通过微调这些层可以获得额外的性能。此外,由于我们的 CUDA 内存有限,我们无法用更大的批量训练模型,这对我们的实现是一个缺点。甚至,我们使用多个 GPU 来训练模型,因为我们实现了 7 个不同的模型,它们是编码器和解码器的组合,由于计算限制,我们不能再次训练超过 2-3 个时期的模型。此外,可以通过 BLEU 分数来实现预定的学习速率,这使得学习速率对模型具有更准确的适应性。此外,你可以尝试用 BLEU 分数提前停止,当然是在训练时间较长的情况下。此外,波束搜索可以促进训练,这不是文献中常用的技术,但人们可以尝试看到差异。此外,可以使用更多 RAM 和 GPU 硬件组件实现更多超参数调整,可以尝试不同的学习速率、层数、神经元数量(隐藏和嵌入大小)、辍学率、批量标准化等。此外,更大的数据集可以用来达到更现实的字幕,人们可以尝试合并 fli̇ckr 和可可小姐数据集。在深度学习的背景下,可以尝试不同的视觉和语言模型架构作为进一步的调整。此外,还存在不止一种注意机制,如软注意(我们使用的)、硬注意、对数双线性注意、随机加倍注意等等。最后,我们在多个 GPU 的分布式并行计算的帮助下,使用不同的卷积编码器,然后使用不同的递归解码器,通过教师强制器和注意机制,成功实现了图像到序列的翻译模型。
文章到此结束。如果你有问题,不要犹豫,联系我,这里是如何。
如何联系我
电子邮件:cankocagil123@gmail.com
- 推特 : 坎科卡吉尔 2
- 领英
https://www.linkedin.com/in/can-kocagil-970506184/
- GitHub
项目页面
https://github.com/cankocagil/Image-Captioning
参考文献
- [1] X .荣,word2vec 参数学习讲解。
- [2]“迁移学习”,CS231n 卷积神经网络用于视觉识别。【在线】。可用:【https://cs231n.github.io/transfer-learning/. 【访问时间:2021 年 1 月 8 日】。
- [3]“微调 Torchvision 模型”,微调 Torchvision 模型— PyTorch 教程 1.2.0 文档。【在线】。可用:https://py torch . org/tutorials/初学者/fine tuning _ torch vision _ models _ tutorial . html .【访问时间:08-Jan-2021】。
- [4] Pulkit SharmaMy 的研究兴趣在于机器学习和深度学习领域。拥有学习新技能和技术的热情。“迁移学习:Pytorch 中的迁移学习”,分析 Vidhya,2020 年 5 月 8 日。【在线】。可用:https://www . analyticsvidhya . com/blog/2019/10/how-to-master-transfer-learning-using-py torch/?UTM _ source = blog&UTM _ medium = building-image-分类-模型-cnn-pytorch。[访问日期:2021 年 1 月 8 日]。
- [5] P. Radhakrishnan,“深度学习中的图像字幕”,Medium,2017 年 10 月 10 日。【在线】。可用:https://towardsdatascience.com/image-captioning-in-深度学习-9cd23fb4d8d2。[访问日期:2021 年 1 月 8 日]。
- [6]《基于注意力的模型教程(上)》,Karan Taneja,2018 年 6 月 2 日。【在线】。可用:https://krntneja . github . io/posts/2018/Attention-based-models-1 #:~:text = Attention % 2d based % 20 models % 20 属于%20to,in % 20general % 2C % 20of %不同的% 20lengths。[访问日期:2021 年 1 月 8 日]。
- [7] S. Ulyanin,“用 PyTorch 为图像加标题”,中型,2019 年 2 月 23 日。【在线】。可用:https://medium.com/@stepanulyanin/captioning-images-with-py torch-BC 592 e 5 FD 1 a 3。[访问日期:2021 年 1 月 8 日]。
- [8] K. Kshirsagar,“CNN 和 RNN 的自动图像字幕”,媒体,2020 年 1 月 21 日。【在线】。可用:https://towardsdatascience.com/automatic-带 cnn-rnn-aae3cd442d83 的图像字幕。[访问日期:2021 年 1 月 8 日]。
- [9]“用 nn 进行序列间建模。变形金刚和火炬报。【在线】。可用:https://py torch . org/tutorials/初学者/transformer _ tutorial . html【访问时间:2021 年 1 月 8 日】。
- [10]“nlpfromstack:translationwithesequencetosequencenetworkandattention”,nlpfromstack:translationwithesequencetosequencenetwork 和 Attention — PyTorch 教程 1.7.1 文档。【在线】。可用:https://py torch . org/tutorials/intermediate/seq 2 seq _ translation _ tutorial . html .【访问时间:08-Jan-2021】。
- [11]“序列模型和长短期记忆网络”,序列模型和长短期记忆网络 PyTorch 教程 1.7.1 文档。【在线】。可用:https://py torch . org/tutorials/初学者/NLP/sequence _ models _ tutorial . html .【访问时间:2021 年 1 月 8 日】。
- [12] KelvinXu,JimmyLeiBa,RyanKiros,KyunghyunCho,RuslanSalakhutdinov,RichardS。Zemel,andYoshuaBengio,Show,AttendandTell:带视觉注意力的神经图像标题生成,2016 年 4 月。
- [13]“单词嵌入:编码词汇语义”,单词嵌入:编码词汇语义— PyTorch 教程 1.7.1 文档。【在线】。可用:https://py torch . org/tutorials/初学者/NLP/word _ embeddings _ tutorial . html .【访问时间:2021 年 1 月 8 日】。
- [14] Sgrvinod,“Sgrvinod/a-py torch-Tutorial-to-Image-Captioning”,GitHub。【在线】。可用:https://github.com/sgrvinod/a-PyTorch-Tutorial-to-Image-字幕。[访问日期:2021 年 1 月 8 日]。
- [15] J. Brownlee,“如何实现用于自然语言处理的波束搜索解码器”,机器学习掌握,2020 年 6 月 3 日。【在线】。可用:https://machine learning mastery . com/beam-search-decoder-natural-language-processing/。【访问时间:2021 年 1 月 8 日】。
- [16] R. Khandelwal,“波束搜索的直观解释”,媒体,2020 年 2 月 3 日。【在线】。可用:https://towardsdatascience.com/an-intuitive-解释-波束-搜索-9b1d744e7a0f。[访问日期:2021 年 1 月 8 日]。
- [17] S. Sarkar,“使用注意机制的图像字幕”,Medium,2020 年 3 月 7 日。【在线】。可用:【https://medium.com/swlh/image-captioning-using-】T2 注意力-机制-f3d7fc96eb0e。[访问日期:2021 年 1 月 8 日]。
- [18] Aayush Bajaj,Avantari 等人,机器学习工程师,“理解梯度裁剪”,2020 年
Tensorflow 中带注意的图像标题,逐步
直观的图像字幕系列
Keras 和 Tensorflow 2.0 中使用编码器-解码器的端到端示例,用简单的英语讲述

马克斯·克莱恩在 Unsplash 上的照片
近年来,使用深度学习生成图像字幕已经产生了显著的结果。最广泛使用的架构之一出现在展示、出席和讲述论文中。
它引入的创新是将注意力应用于图像标题问题,这在 NLP 领域已经取得了很大的成功。注意力帮助模型专注于图像中最相关的部分,因为它生成了标题的每个单词。
在本文中,我们将通过一个简单的演示应用程序来详细了解这个体系结构是如何工作的。
我还有另一篇文章,概述了许多流行的图像标题架构。它提供了这些体系结构所使用的主要组件的背景,解释了每个体系结构的独特特征以及它们为什么有趣。如果你感兴趣,我鼓励你去看一看。
图像字幕应用程序
图像字幕应用程序将图像作为输入,并生成描述照片内容的简短文本摘要。

(图片由作者提供)
对于我们的应用程序,我们从图像文件作为输入开始,并以紧凑的编码表示提取它们的基本特征。我们将这些输入到一个序列解码器,由几个 LSTM 层组成,它将解码编码图像并预测描述照片的单词序列。

(图片由作者提供)
至于大多数深度学习问题,我们会按照以下步骤:

(图片由作者提供)
图像标题数据集
有一些众所周知的数据集常用于这类问题。这些数据集包含一组图像文件和一个文本文件,该文本文件将每个图像文件映射到一个或多个标题。每个标题都是一种语言中的一句话。
对于我们的演示,我们将使用 Flickr8K 数据集(图像,文本)。这是一个合理大小的数据集,包含大约 8000 张图像,足以训练我们的模型,而不需要大量的 RAM 和磁盘空间。
将它下载到“数据集文件夹后,我们看到它由三部分组成:
- “ Flicker8k_Dataset ”文件夹中的图像文件:该文件夹包含大约 8000 个。jpg 文件如'1000268201 _ 693 b 08 cb0e . jpg'
- 主文件夹中' Flickr8k.token.txt '文件中的字幕:包含所有图片的字幕。因为同一幅图像可以用许多不同的方式描述,所以每幅图像有 5 个标题。
- 一组中的训练、验证和测试图像的列表。主文件夹中的 txt 文件:'Flickr _ 8k . train images . txt'包含用于训练的图像文件名列表。同样,也有用于验证和测试的文件。
探索数据
标题文件中的每一行代表一个标题。它包含由制表符分隔的两列。每行的格式是:
“ #i ”,其中 0≤i≤4 为 5 个字幕中的每一个。
eg ."1000268201 _ 693 b 08 cb0e . jpg # 1 一个女孩走进一栋木屋里。
下面是一张带有五个标题的图片:

训练数据管道
我们将分两个阶段为我们的深度学习架构构建管道。

(图片由作者提供)
- 在第一阶段,我们使用迁移学习,通过预先训练的基于 CNN 的网络对原始图像进行预处理。这将图像作为输入,并产生捕获图像基本特征的编码图像向量。我们不需要进一步训练这个网络。
- 然后,我们将这些编码图像特征,而不是原始图像本身,输入到我们的图像标题模型中。我们还传入对应于每个编码图像的目标标题。该模型解码图像特征并学习预测与目标字幕匹配的字幕。
对于图像字幕模型,训练数据包括:
- 特征(X) 是编码的特征向量
- 目标标签(y) 是标题
为了准备这种格式的培训数据,我们将使用以下步骤:

(图片由作者提供)
- 加载图像和标题数据
- 预处理图像
- 预处理标题
- 使用预处理的图像和字幕准备训练数据
现在,让我们更详细地看一下这些步骤。
加载图像和标题
让我们将完整的数据集加载到 Python 字典中:

然后使用训练列表文本文件从我们的完整数据集中选择用于训练的图像子集。
预处理图像
我们将使用预训练的初始模型,这是一个众所周知的具有优异性能的图像分类模型。该模型由两部分组成:
- 第一部分由一系列 CNN 图层组成,这些图层从图像中逐步提取相关特征,以生成紧凑的特征地图表示。
- 第二部分是由一系列线性层组成的分类器。它获取图像特征图,并预测该特征所属的类别(例如,狗、汽车、房子等)。
对于我们的图像字幕模型,我们只需要图像特征图,而不需要分类器。

(图片由作者提供)
我们下载这个预训练的模型,截断分类器部分并编码训练图像。每个编码图像的特征使用图像名称和另一个扩展名保存在一个单独的文件中,例如“1000268201 _ 693 b 08 cb0e . npy

(图片由作者提供)
既然图像已经为训练做好了准备,接下来我们必须准备字幕数据。
准备标题
每个标题由一个英语句子组成。为了准备培训,我们对每个句子执行以下步骤:

(图片由作者提供)
- 通过将所有单词转换成小写字母并删除标点符号、带数字的单词和带单个字符的短单词来清理它。
- 在句首和句尾添加“
”和“ ”标记。 - 通过将每个单词映射到一个数字单词 ID 来标记句子。它通过构建标题集中出现的所有单词的词汇表来做到这一点。
- 通过追加填充标记将每个句子扩展到相同的长度。这是必要的,因为模型期望每个数据样本具有相同的固定长度。
使用张量流数据集准备训练数据
我们现在有预处理的图像和标题。我们检查每个训练图像及其匹配的标题来准备训练数据。这包括:
- 特征(X) 由图像文件路径组成
- 目标(y) 由经过清理和标记的字幕组成
我们将训练数据包装在 Tensorflow Dataset 对象中,以便可以在训练过程中高效地获取数据并一次一批地将其提供给模型。数据被缓慢提取,因此不必同时存在于内存中。这使我们能够支持非常大的数据集。
数据集加载先前保存的预处理编码图像矢量。它使用图像文件名来标识保存的文件路径。
这个例子的大部分代码取自 Tensorflow 图片说明教程。
带注意力的图像字幕模型
该模型由四个逻辑组件组成:
- 编码器:由于图像编码已经由预先训练好的 Inception 模型完成,这里的编码器非常简单。它由一个线性层组成,该层获取预编码图像特征并将其传递给解码器。
- 序列解码器:这是用 GRUs 搭建的递归网络。字幕首先通过嵌入层,然后作为输入传入。
- 注意力:当解码器生成输出序列的每个单词时,注意力模块帮助它聚焦于图像中与生成该单词最相关的部分。
- 句子生成器:这个模块由几个线性层组成。它从解码器获得输出,并为词汇表中的每个单词以及预测序列中的每个位置产生一个概率。
注意力如何增强图像字幕的表现
最早的图像字幕架构包括其他三个组件,但没有引起注意。让我们先回顾一下这个模型是如何工作的,然后看看注意力有什么不同。
在每个时间步长,解码器从先前时间步长和当前输入字中提取隐藏状态,以产生该时间步长的输出字。隐藏状态携带编码图像特征的一些表示。
在没有注意的情况下,解码器在生成输出字时平等地对待图像的所有部分。
那么注意力的表现有什么不同呢?
在每个时间步,注意力模块将编码图像以及解码器在前一时间步的隐藏状态作为输入。
它产生一个注意力分数,该分数为编码图像的每个像素分配一个权重。像素的权重越高,在下一时间步输出的单词就越相关。
例如,如果目标输出序列是“一个女孩正在吃苹果”,则在生成单词“女孩”时,照片中女孩的像素被高亮显示,而对于单词“苹果”,苹果的像素被高亮显示。
然后,该分数与该时间步长的输入字连接,并馈入解码器。这有助于解码器关注图像中最相关的部分,并生成适当的输出字。
培养
我们现在准备创建训练循环来训练模型。
我们为优化器和损失定义函数。我们为几个时期训练模型,在每次迭代中处理一批数据。
训练期间会发生很多事情,计算流程可能会有点混乱。所以让我们一步一步地来看。

(图片由作者提供)
在每个时期,训练循环执行几个操作:
设置
首先,我们设置我们需要的数据元素。
- 通过 TF 数据集获取一批数据。这将从保存的预处理文件和准备好的字幕中加载图像特征向量。
- 编码器对图像特征向量进行编码。
- 序列解码器初始化其隐藏状态。注意,也可以对编码图像应用一些变换来初始化隐藏状态。
- 通过仅用一个“Start”标记播种输入序列来开始输入序列。
多个时间步长上的流程序列
接下来,我们在多个时间步长上迭代输入序列的每个元素。让我们来看看批次中一个序列的流程:
- 注意力模块从编码器获取编码图像,从序列解码器获取隐藏状态,并计算加权注意力分数。
- 输入序列通过嵌入层,然后与关注度结合。
- 组合的输入序列被馈送到序列解码器,该解码器产生输出序列以及新的隐藏状态。
- 句子生成器处理输出序列并生成其预测单词概率。
- 我们现在为下一个时间步重复这个循环。来自该时间步长的解码器的新隐藏状态被用于下一个时间步长。我们继续这样做,直到预测到“结束”标记,或者达到序列的最大长度。
- 然而,有一点值得注意,即。老师逼的,下面解释。
教师强迫
- 通常,在推理期间,当模型被完全训练时,来自这个时间步长的输出序列被用作下一个时间步长的输入序列。这允许模型基于迄今为止预测的前一个单词来预测下一个单词。
- 然而,如果我们在模型仍在训练期间学习时这样做,则模型在这个时间步中预测输出字时所犯的任何错误都将被结转到下一个时间步。该模型将基于错误的前一个单词来预测下一个单词。
- 相反,因为我们在训练中有地面实况字幕可用,所以我们使用一种叫做教师强迫的技术。来自目标字幕的正确的预期单词被添加到下一个时间步长的输入序列,而不是模型的预测单词。
- 这样,我们就像老师一样,通过给模型一个提示来帮助它。
失败
- 在每个时间步,预测的概率与地面实况字幕进行比较,以计算损失。损失将用于通过反向传播训练网络。
通常,作为训练循环的一部分,我们还会评估验证数据的度量。然而,为了这个演示的目的,在模型被完全训练之后,我们将继续对测试数据进行推断。
推理
我们使用预先训练的模型从测试图像中提取图像特征。生成标题的后续步骤与我们在培训中所做的非常相似,但有以下变化:
- 贪婪搜索通过在每个时间步长选择概率最高的单词来预测输出。
- 由于我们没有地面真相标题,我们不使用教师强迫。在每个时间步长,预测字被附加到输入序列,并反馈到解码器,用于下一个时间步长。
- 显然,我们不计算损失和梯度,也不做反向传播。

预测说明:“人爬上岩石到大岩石”。真实描述:“一个人正在两个大岩壁之间攀岩”
结论
图像字幕是一个有趣的应用,因为它结合了计算机视觉和自然语言处理的技术,并且需要处理图像和文本。
我们仔细浏览了一个使用编码器-解码器架构的端到端图像字幕示例。我们看到注意力是如何被用来提高网络预测更好字幕的能力的。这是近年来性能较好的架构之一。
最后,如果你喜欢这篇文章,你可能也会喜欢我关于变形金刚、音频深度学习和地理定位机器学习的其他系列。
让我们继续学习吧!
具有深度学习的图像标题:最先进的架构
实践教程,直观的图像字幕系列
用简单的英语介绍图像特征编码器、序列解码器、注意力和多模态架构的简明指南

图像字幕是深度学习的一个迷人应用,近年来取得了巨大的进展。更有趣的是,它将计算机视觉和 NLP 结合在一起。
什么是图像字幕?
它接受一个图像作为输入,并产生一个描述照片内容的简短文本摘要。

描述—“站在草丛中的狗”([来源](https://en.wikipedia.org/wiki/Labrador_Retriever#/media/File:Labrador_on_Quantock_(2175262184).jpg. By IDS.photos from Tiverton, UK - Labrador on Quantock, CC BY-SA 2.0, https://commons.wikimedia.org/w/index.php?curid=25739129 )))
这张图片的相关说明可能是“站在草地上的狗”或“站在草地上的拉布拉多犬”。
在本文中,我的目标是介绍这个主题,并概述常用于解决这个问题的技术和架构。
此外,如果您感兴趣,我还有一篇文章介绍了使用 Keras 和 Tensorflow 的端到端图像标题示例。
它是如何工作的?
概括地说,图像字幕利用了三个主要部分。顺便说一下,这些组件没有标准的名称,这些名称是我为了解释它们的用途而想出来的。
1.图像特征编码器
这将源照片作为输入,并产生一个捕捉其基本特征的编码表示。

(图片由作者提供)
这使用 CNN 架构,并且通常使用迁移学习来完成。我们采用为图像分类而预先训练的 CNN 模型,并移除最后的部分,即“分类器”。有几个这样的模型,如 VGGNet、ResNet 和 Inception。
这个模型的“主干”由几个 CNN 模块组成,这些模块从照片中逐步提取各种特征,并生成一个紧凑的特征图,以捕捉照片中最重要的元素。
它首先在初始层中提取简单的几何形状,如曲线和半圆,然后发展到更高层次的结构,如鼻子、眼睛和手,最后识别出面部和车轮等元素。

(经 Vigneashwara Solairaja Pandiyan 许可,改编自来源
在图像分类模型中,该特征图然后被馈送到最后一级,该最后一级是生成图像中主要对象的类别(例如猫或车)的最终输出预测的分类器。
当将该模型应用于图像字幕时,我们感兴趣的是图像的特征映射表示,而不需要分类预测。因此,我们保留主干并删除分类器层。
2.序列解码器
它获取照片的编码表示,并输出描述照片的标记序列。
典型地,这是一个递归网络模型,包括由嵌入层馈送的一堆 LSTM 层。

(图片由作者提供)
它将图像编码向量作为其初始状态,并以仅包含“开始”标记的最小输入序列作为种子。它“解码”输入图像向量并输出一系列标记。
它在一个循环中生成这个预测,一次输出一个令牌,然后将其反馈给网络作为下一次迭代的输入。因此,在每一步,它都采用迄今为止预测的记号序列,并生成序列中的下一个记号。最后,它输出一个“结束”标记来完成序列。
3.句子生成器
句子生成器的工作是获取标记序列并输出标题,标题是描述照片的所需语言的单词句子。
它由一个线性层和一个 Softmax 组成。这为目标语言的词汇表中的每个单词,为序列中的每个位置产生一个概率。
这个概率是这个单词出现在句子中那个位置的可能性。然后,我们可以使用贪婪搜索,通过在每个位置选择概率最高的单词来生成最终的句子。

(图片由作者提供)
然后,该句子将作为预测字幕输出。
几乎所有的图像字幕架构都使用这种方法,包括我们刚刚看到的三个组件。然而,随着时间的推移,这个框架已经有了很多变化。
架构:编码器-解码器
也许最常见的用于图像字幕的深度学习架构有时被称为“注入”架构,并直接将图像特征编码器连接到序列解码器,随后是句子生成器,如上所述。

(图片由作者提供)
建筑:多模态
注入架构是图像字幕的原始架构,现在仍然非常流行。然而,一种被称为“合并”架构的替代方案被发现可以产生更好的结果。
这两个组件彼此独立工作,而不是将图像编码器作为序列解码器的输入依次连接。换句话说,我们不混合这两种模式。带文本的图像。
- CNN 网络只处理图像
- LSTM 网络只对目前产生的序列进行操作。

(图片由作者提供)
这两个网络的输出然后与多模态层(可以是线性和 Softmax 层)结合在一起。它负责解释两种输出,然后是句子生成器,生成最终的预测字幕。
这种方法的另一个优点是,它允许我们不仅对图像编码器,而且对序列解码器使用迁移学习。我们可以为序列解码器使用预先训练的语言模型。
已经尝试了许多不同的组合输出的方法,例如级联、乘法等等。通常最有效的方法是使用加法。
架构:对象检测主干
前面我们讨论了使用来自图像编码器的预训练图像分类模型的主干。这种类型的模型通常被训练来识别整个画面的单个类别。
然而,在大多数照片中,你可能会有多个感兴趣的对象。不使用图像分类主干,为什么不使用预先训练的对象检测主干从图像中提取特征?

(图片由作者提供)
对象检测模型在场景中所有突出的对象周围生成边界框。它不仅能标记多个对象,还能识别它们在图片中的相对位置。因此,它能够提供图像的更丰富的编码表示,然后序列解码器可以使用该编码表示在其标题中包括所有那些对象的提及。
架构:专注的编码器-解码器
在过去的几年里,注意力在 NLP 模型中的应用已经获得了很大的关注。已经发现它显著提高了 NLP 应用的性能。当模型生成输出中的每个单词时,注意力有助于它关注输入序列中与该输出单词最相关的单词。
因此,毫不奇怪地发现,注意力也已经被应用于图像字幕,从而产生了最先进的结果。
当序列解码器产生字幕的每个单词时,注意力被用来帮助它集中在图像中与它所产生的单词最相关的部分。

(图片由作者提供)
注意模块从 LSTM 获取编码图像向量以及当前输出令牌。它会产生一个加权注意力分数。当该分数与图像结合时,它增加了 LSTM 在预测下一个令牌时应该关注的那些像素的权重。
例如,对于标题“狗在窗帘后面”,当它生成单词“狗”时,模型聚焦于照片中的狗,然后当它到达单词“窗帘”时,将焦点转移到窗帘,如您所料。
架构:带变压器的编码器-解码器
谈到关注度,目前的巨头无疑是变形金刚。它以注意力为核心,不使用多年来一直是 NLP 支柱的循环网络。该架构与编码器-解码器非常相似,只是用变压器代替了 LSTM。

(图片由作者提供)
已经提出了变压器架构的几种不同变体来解决图像字幕问题。一种方法试图不仅编码照片中的单个对象,还编码它们的空间关系,因为这对理解场景很重要。例如,知道一个对象是在另一个对象的下面、后面还是旁边,为生成标题提供了有用的上下文。
建筑:密集字幕
对象检测方法的另一种变体被称为密集字幕。这个想法是,照片通常是图片中不同位置的物体和活动的丰富集合。
因此,它不仅可以表示单个字幕,还可以表示图像不同区域的多个字幕。这个模型帮助它捕捉图像中的所有细节。

密密麻麻的字幕(来源,经费教授、许可)
具有波束搜索的句子生成器
当句子生成器生成最终标题时,它可以使用波束搜索,而不是我们上面提到的贪婪搜索。Beam Search 不是在每个位置只选择概率最高的单个单词,而是在每一步选择几个单词,基于到该点为止句子中所有单词的组合概率。

波束搜索(图片由作者提供)
波束搜索非常有效,在许多 NLP 应用中被广泛使用。我有几篇文章以直观的方式详细解释了这些。如果你感兴趣,我鼓励你去看一看。
Bleu 分数度量
在训练期间,一旦字幕生成,我们如何决定它有多好?用于评估图像字幕模型的常见度量是 Bleu 分数。对于其他 NLP 应用程序,如翻译和语言模型,这也是一个流行的指标。
这是一个简单的指标,衡量预测标题和真实标题之间匹配的连续单词的数量。为此,它比较了从 1 到 4 的各种长度的 n 元文法。
预测说明:“一只狗站在绿色的草地上”地面真实说明:“狗站在草地上”
1-gram 的 Bleu 分数=正确预测的单词数/总预测单词数
有三个预测单词也出现在真实字幕 ie 中。“狗”、“上”、“草”,一共出了六个预言词。
Bleu 评分为 1 克(即。单字)= 3/6 = 0.5
结论
随着计算机视觉和 NLP 的进步,今天的图像字幕模型能够产生几乎与人类表现相匹配的结果。当你输入一张照片,并得到一个完美的人类可读的说明,它几乎感觉就像有一些魔术在进行!
我们刚刚探讨了实现这一目标的常用方法。我们现在处于一个很好的位置,可以看到幕后的细节。在我的下一篇文章中,我将一步一步地介绍一个示例演示应用程序,这样我们就可以确切地看到它是如何工作的。
最后,如果你喜欢这篇文章,你可能也会喜欢我关于变形金刚、音频深度学习和地理定位机器学习的其他系列。
让我们继续学习吧!
多氯联苯的图像分类及其网络应用(Flask)
使用 Flask 创建多氯联苯的图像分类模型和设计 Web 应用程序。

来源: alutriots (CC0)。
你好,这篇博客是关于为 PCB(印刷电路板)创建一个图像分类模型,以检测有缺陷的 PCB 并将其分类为好或坏。因此,我们将创建一个深度学习模型,并尝试获得最佳可能的结果,以及每个步骤的正确可视化。创建工作模型后,我们将使用 Flask 创建一个 Web 应用程序。你可以在我的 GitHub 上找到这个项目的代码,以及下面的安装说明文件。所以,让我们向前迈进,灵活应变。
要求
- Jupyter 笔记本
- Python 3.5 或更高版本。(最好是 3.7)
- 库— Tensorflow、Keras、Pandas、Numpy、Matplotlib、OpenCV、Pillow、Flask。【所有最新版本
了解数据集

来源: pcbways (CC0)。
PCB(印刷电路板)是一种广泛使用的电子元件,几乎用于任何电子设备。有时在工厂的大规模生产过程中会出现一些异常,从而导致有缺陷的产品。电子公司必须承担这一损失。所以这种深度学习图像分类模型有助于仅在生产时识别好的和坏的电路。
因此,我们使用的数据集是 PCB 的二进制图像,其中黑色部分表示电路,白色部分表示空白电路区域。图像中的点或噪声显示异常。

图 2“无缺陷”PCB 的二进制图像。
正如我们所见,图像中没有异常和缺陷。现在,我们将看到另一个有缺陷的 PCB 图像。

图 2“有缺陷”的 PCB 的二进制图像(我编辑了图像并标记了缺陷)。
由于这里标记了缺陷,这是一个有缺陷的 PCB 的例子。你可以从 这里 下载数据集。
数据准备/预处理
现在,在我们下载数据集后,我们可以看到每张图像的尺寸为 640x640。如果我们降低它们的维数会更好,这样就更容易训练数据,但为了确保图像质量不受影响,我们将使用反走样技术压缩图像。
这是我的 链接 到我的 GitHub repo,这也有助于做同样的事情。使用那里的图像调整工具,你会得到想要的结果。

图处理后的 224x224 图像。
因此,在将图像大小调整为 224x224 后,我们将它们保存到不同的文件夹中。我保存了 1,095 张良莠不齐的 PCB 图片。因此,该文件夹将成为您的训练数据集。挑选一些其他图像(不同于训练集)并把它们放到一个单独的文件夹中,作为测试数据集。确保测试文件夹是好图像和坏图像的良好混合,并保持适当的比例。
现在,我将检查我的训练数据集,它具有两种类型图像的适当比例,或者换句话说,检查可能的“类别不平衡”。作为参考,我将良好的 PCB 图像命名为 good()。jpeg 和 Bad as Bad()JPEG .然后我把好的图像标为‘0’,坏的图像标为‘1’。
然后我们画出他们的图表

因此,我们可以从图表中观察到,我们的班级相当平衡。现在为了交叉验证,我们将把训练数据集分成训练集和验证集。正如我之前提到的,我拍了 1095 张照片。我将它们分成 80%的训练数据和 20%的验证集。
train_df, validate_df = train_test_split(df, test_size=0.20, random_state=42)train_df = train_df.reset_index(drop=True)
validate_df = validate_df.reset_index(drop=True)
然后我们绘制训练和测试图像比率:-


⇙
- 这是最终“训练数据集”的绘图,即两个类别中大约 400+个图像。
⇖
⇙
- 这是“验证数据集”的绘图,即两个类别中大约有 100 多个图像。
⇖
现在,我将应用我认为最好的工具之一,即 Keras ImageDataGenerator。这是一个非常有用的工具,因为它有助于在训练模型的同时实时进行数据扩充。正如我们所知,我们的训练数据已经很少了,所以应用增强不仅有助于我们使我们的模型更健壮,而且还节省了内存。
现在,我们添加路径并将图像加载到生成器中。

所以,现在我们可以说我们的数据准备/预处理部分已经完成了。
模型实现
现在是时候设计大脑了。这里我使用了在 ImageNet 数据集上预先训练好的"MobileNet"模型。MobileNet 是一种基于深度学习的卷积神经网络架构。它是一种轻量级架构,使用深度方向可分离的卷积。你可以从它的论文 这里 了解更多。

图 MobileNets 架构。资料来源:欣达维 (CC0)。
正如我提到的,我们使用 ImageNet 数据集预训练的 MobileNet 模型,因此这里我们使用迁移学习的概念。因此,不是训练整个模型,我们将冻结基础层,并根据我们的要求添加一些其他层。
由于我们使用预训练的重量,我们将确保所有的重量是不可训练的,除了最后几个密集层。我把前 20 层设为不可训练。
for layer in model.layers[:20]:
layer.trainable=False
for layer in model.layers[20:]:
layer.trainable=True
但是如果你想把所有的层都设置成不可训练的,你可以用这个来代替。
for layer in model.layers:
layer.trainable=False
现在,我们已经完成了模型实现部分。
训练
因此,我们的数据准备与模型实现一起完成。现在,是时候训练我们的模型了。在继续之前,我想提一下,我使用了两个回调函数,分别名为“提前停止和“学习率降低”。
我们需要仔细考虑历元的数量,因为太多会导致数据过拟合,而太少会导致数据过拟合。早期停止允许您指定任意大数量的训练时期,并在模型停止对保留验证数据集进行改进时停止训练。这里有一个 链接 如果你想了解更多。
当指标停止改善时,ReduceLROnPlateau 会降低学习率。一旦学习停滞,模型通常会受益于将学习速度降低 2-10 倍。这种回调监控一个数量,如果在“耐心”次数内没有看到改进,则学习率降低。这个可以从 这里 查。
现在,我们将开始训练我们的模型。嗯,我在 Google Colab 上训练了我的模型,因为它有一定的优势,我会建议每个人尝试使用 Google Colab,你只需要在 google drive 上上传或链接数据集,然后通过给出正确的文件夹位置,使用这个简单的调用将其安装在 Colab 上
from google.colab import drive
drive.mount('/content/drive')
现在,我们将在 model.fit.generator()中设置所有变量,然后开始训练我们的模型。

嗯,我把我的纪元设定为 50,但它停在 42,因为它停止改善。我根据训练模型时提取的数据绘制了两张图表。

.
⇙
- “损失与时代”图表。
⇖
.
⇙
- “精确度与时代”图。
⇖
.
注意:-在两个图表中,蓝线表示训练,红线表示验证变量。
于是,在训练完模型后,我得到了一个95.71%的验证准确率。
现在,我们将训练好的权重保存在. h5 或. hdf5 文件中(两者相同)。
model.save_weights("model.h5")
如果你通过 Google Colab 训练它,你可以像这样直接在驱动器中保存重量
model.save('/content/drive/My Drive/folder (1)/model.h5')
因此我们的训练已经完成,现在是检查结果的时候了。
预测和结果
首先,我们将加载我们的模型。
model = load_model(r'C:/Users/nEW u/Flask/model.h5')
然后我们将使用 ImagedataGenerator 来访问测试文件夹,并共同调用图像文件。并将调用预测函数并用图像生成器函数对其进行设置。
现在,我们将把结果和图像一起绘制出来。我借助了 Matplotlib 库。
这就是我们的结果

注 :-正如我前面提到的,我将图像文件命名为好的和坏的,以供参考。因此,初始部分是图像文件的名称,结果在括号中。因此,您可以根据图像名称将结果与实际图像质量进行比较。
至此,我们的图像分类模型成功完成。现在,是时候创建 Web 应用程序了。我要用烧瓶来装它。
使用 Flask 的 Web 应用程序
Flask 是用于创建 web 应用程序的 Python API。所以,首先,我会给你一个基本的代码片段,这是我们在使用 Flask 时通常遵循的。
希望你有一点想法,现在让我们开始实施。
功能
首先,我们必须编写函数来调用模型,以便在将图像传递给我们的模型和预测函数之前处理图像,预测函数让我们的图像通过模型并返回结果。
-
用于调用模型:-
-
为了处理图像:-
-
预测函数(使用“load _ image”函数):-
设计
当我们完成了我们的功能,我们将继续设计我们的网页。因此,这里我们将需要两个页面,在第一页,它会要求选择和上传图像。第二页将显示图像和结果。
重要提示 :-使用 Flask 时,我们必须非常小心,因为我们需要创建一个单独的文件夹。在该文件夹中,我们将创建使用 flask 的 python 文件,我们还需要创建两个名为“Templates”和“Static”的子文件夹。模板是我们保存 HTML 文件的地方,在静态文件夹中,我们保存图像和其他东西。如果你想把自己从错误循环中拯救出来,你必须遵循这个惯例。
因此,由于我们需要两个页面,我相应地设计了两个 HTML 页面。出于设计目的,我使用了“自举”。Bootstrap 是一个开源的前端框架,用于创建网站和网络应用。它包含各种基于 HTML、CSS 和 JavaScript 的设计模板,用于 UI 界面元素,如按钮和表单等。
这是我的第一个网页 HTML 文件,名为 home.html
注意在第三行,使用了设计的引导链接(基于 CSS 的模板)。
这是第二页 HTML 文件,保存为 predict.html:-
这里同时使用了 CSS 和 JavaScript 模板。
编译和部署
我们的模型准备好了,功能准备好了,模板也准备好了。现在是时候编译所有这些东西,享受魔法了。
首先,我们运行烧瓶:-
app = Flask(__name__)
然后,我们通过调用函数来加载模型:-
get_model()
现在,我们调用这两个页面以及函数和模板:-
好了,现在到了最后一步,即运行一切并生成 URL:-

现在生成了一个 URL(注意蓝色部分)。点击它,我们会得到这样的第一页:-

然后,我们选择要检查的图像文件,然后单击上传。按下上传按钮后,用 url+"/predict "重定向到下一个页面。它将显示图像和结果:-

这是最终结果。因此,基于深度学习模型的 PCB 缺陷检测 Web 应用程序的实现是成功的。
您可以通过以下方式克隆存储库:-
git clone [https://github.com/utk-ink/Defect-Detection-of-PCB.git](https://github.com/utk-ink/Defect-Detection-of-PCB.git)
或者
这是我的 GitHub 的链接,是整个代码 :-
**https://github.com/utk-ink/Defect-Detection-of-PCB
希望你喜欢!!!
这个博客没有商业化,我只是想以最好的方式分享我的知识。请鼓掌,关注和分享,因为它激励我写更多。谢谢大家!!**
参考资料:-
- https://deeplizard.com/learn/video/XgzxH6G-ufA
- https://github . com/charm ve/Surface-Defect-Detection/tree/master/deep PCB
- https://arxiv.org/abs/1704.04861
- https://machine learning mastery . com/how-to-stop-training-deep-neural-networks-at-the-right-time-using-early-stopping/
- https://towards data science . com/transfer-learning-using-mobilenet-and-keras-c 75 daf 7 ff 299
- https://keras.io/api/callbacks/reduce_lr_on_plateau/
- https://www . ka ggle . com/uysimty/get-start-image-class ification
- https://get bootstrap . com/docs/5.0/入门/简介/
基于张量流的图像分类迁移学习和微调
迁移学习/微调/张量流
理解深度学习中迁移学习和微调的力量
这里使用来自tensor flow datasets的 food101 数据集上的 EfficientNetB0 模型,完成了一个带有微调的简单 CNN(卷积神经网络)迁移学习应用程序。在 Jupyter 笔记本界面上使用 Python 3 内核进行实验。
EfficientNet ,首次在 Tan 和 Le 中介绍,2019 是最高效的模型之一(即需要最少的 FLOPS 进行推理),在 imagenet 和普通图像分类迁移学习任务上都达到了最先进的精度。我们将使用 EfficientNetB0 架构,因为它是最不复杂的,可以处理较小尺寸的图像。

与其他 ImageNet 模型相比的 EfficientNet 系列(来源:谷歌人工智能博客)
从图中可以看出,尽管 EfficientNetB0 的最高精度相对较低,但我们将在本实验中使用它来实现迁移学习、特征提取和微调。EfficientNet 系列中的其他更高型号架构将需要计算能力更强的硬件。
我们从导入必要的包开始:
*#importing the necessary packages*
**import** tensorflow **as** tf
**import** tensorflow_datasets **as** tfds
**import** pandas **as** pd
**import** numpy **as** np
**import** matplotlib.pyplot **as** plt
**import** random
由于数据集包含 75750 幅训练图像和 25250 幅测试图像,因此可以将其归类为大型数据集。我们希望确保使用 GPU 来计算这个大型数据集,因为它可以显著减少处理、计算和训练时间。
print(f"The number of GPUs: {len(tf**.**config**.**list_physical_devices('GPU'))}")
**!**nvidia-smi -LThe number of GPUs: 1
GPU 0: NVIDIA GeForce RTX 3060 Laptop GPU (UUID: GPU-75f6c570-b9ee-76ef-47cd-a5fe65710429)
太好了,我的笔记本电脑的 GPU 正在使用中。
我们使用tfds.load()函数创建训练和测试数据集。shuffle_files参数设置为False,因为我们稍后将单独混洗训练数据集图像,同时保持测试数据集不混洗,因为我们希望在对测试数据集进行评估后将测试集与预测集进行比较。我们还将as_supervised参数设置为True,因为我们想要一个('image', 'label')格式的干净数据集。
注意:由于 Tensorflow 下载整个 Food101 数据集并将其存储在本地(约 4.65 GiB),根据您的网络速度和处理能力,以下步骤可能需要很长时间(约 1 小时)
(train_data, test_data), ds_info **=** tfds**.**load("food101",
data_dir**=**"D:\Food Vision",
split**=**["train", "validation"],
shuffle_files**=False**,
with_info**=True**,
as_supervised**=True**)
我们打印保存在通过传递wiht_info = True参数获得的ds_info中的数据集的元数据信息,并获得数据集结构的大量元数据信息。快速检查训练和测试数据集中的图像数量,确认训练数据集包含 75750 个图像(每类 750 个图像),测试数据集包含 101 个类中的 25250 个图像(每类 250 个图像):
len(train_data), len(test_data)(75750, 25250)
在ds_info上调用features方法产生数据集结构,该数据集结构目前是字典的形式,其中键是image和它们对应的label。它们的数据类型分别是uint8和int64。
ds_info**.**featuresFeaturesDict({
'image': Image(shape=(None, None, 3), dtype=tf.uint8),
'label': ClassLabel(shape=(), dtype=tf.int64, num_classes=101),
})
从字典中调用label键上的names方法可以获得所有 101 个食品类的类名。将显示前十个类名和列表长度:
class_names **=** ds_info**.**features['label']**.**names
class_names[:10], len(class_names)(['apple_pie',
'baby_back_ribs',
'baklava',
'beef_carpaccio',
'beef_tartare',
'beet_salad',
'beignets',
'bibimbap',
'bread_pudding',
'breakfast_burrito'],
101)
由于 EfficientNetB0 最适用于大小为(224, 224, 3)的图像,因此我们必须将数据集中的所有图像转换为上述大小。我们通过定义一个函数来做到这一点,稍后我们可以将该函数映射到训练和测试数据集中的所有元素。哦,由于神经网络最适合使用float32或float16数据类型,我们需要将图像的uint8数据类型转换为float32数据类型。
**def** resize_images(image, label):
image **=** tf**.**image**.**resize(image, size**=**(224,224))
image **=** tf**.**cast(image, dtype **=** tf**.**float32)
**return** image, label
既然我们已经定义了调整图像大小、更改数据类型和返回图像元组及其关联标签所需的函数,现在我们将跨数据集的所有元素映射创建的函数。在这个阶段,我们还创建了一个性能管道来适应内存中的数据集,并加快计算和处理速度。map()函数中的num_parallel_calls参数被设置为tf.data.AUTOTUNE,它自动调整映射函数以提高并行处理效率。接下来,我们仅对缓冲区大小为1000的训练数据集进行混洗,以避免一次消耗大量内存,并且我们还将训练数据集和测试数据集分成几批32,因为我们无法一次将训练数据集中的所有图像放入 GPU 的带宽中。我们还利用了prefetch(),它重叠了训练步骤的预处理和模型执行,从而提高了效率,减少了训练时间。这里,我们也将buffer_size参数设置为tf.data.AUTOTUNE,让 Tensorflow 自动调整缓冲区大小。你可以在这里阅读更多关于预取的内容。
train_data **=** train_data**.**map(map_func**=**resize_images, num_parallel_calls**=**tf**.**data**.**AUTOTUNE)
train_data **=** train_data**.**shuffle(buffer_size**=**1000)**.**batch(batch_size**=**32)**.**prefetch(buffer_size**=**tf**.**data**.**AUTOTUNE)
*#test_data doesn't need to be shuffled*
test_data **=** test_data**.**map(map_func**=**resize_images, num_parallel_calls**=**tf**.**data**.**AUTOTUNE)
test_data **=** test_data**.**batch(batch_size**=**32)**.**prefetch(buffer_size**=**tf**.**data**.**AUTOTUNE)
既然我们已经预处理了训练和测试数据集,那么重要的一步就是可视化数据集中的图像。我们运行下面的代码,在训练数据集中随机可视化 9 幅图像。图像被255分割,因为只有当图像张量中的所有值都在0和1之间时,plt.imshow()函数才会以正确的格式输出可视化。我们还打印训练数据集中可视化图像的类名和相应的类标签:
N 注意:每次运行这个代码块,它都会显示另一组随机图像。这对于进行几次迭代并检查数据集的结构以及任何标签错误或其他数据输入问题(如果发生的话)非常有用。
plt**.**figure(figsize**=**(16,16))
**for** i **in** range(9):
**for** image,label **in** train_data**.**take(1):
image **=** image**/**255.
plt**.**subplot(3,3,i**+**1)
plt**.**imshow(image[0])
plt**.**title("Class: " **+** class_names[label[0]**.**numpy()] **+** " || Class_label: " **+** str(label[0]**.**numpy()))
plt**.**axis(**False**);

作者图片
既然我们已经向数据集传递了resize_images()函数来调整图像大小和更改数据类型,我们可以使用element_spec属性来检查数据集的属性:
train_data**.**element_spec, test_data**.**element_spec((TensorSpec(shape=(None, 224, 224, 3), dtype=tf.float32, name=None),
TensorSpec(shape=(None,), dtype=tf.int64, name=None)),
(TensorSpec(shape=(None, 224, 224, 3), dtype=tf.float32, name=None),
TensorSpec(shape=(None,), dtype=tf.int64, name=None))
太好了!现在所有图像的大小都是(224, 224, 3),数据类型是float32数据类型。None形状表示图像和标签已经被分成我们之前指定的批次。
哦,我们还可以做另一件事来提高我们的处理速度,从而减少在这个过程中训练一个神经网络所花费的时间,那就是从keras模块中实现mixed_precision训练。混合精度是在训练期间在模型中同时使用 16 位和 32 位浮点类型,以使其运行更快并使用更少的内存。Keras 混合精度 API 允许我们混合使用 float16 和 float32,以获得 float16 的性能优势和 float32 的数值稳定性优势。你可以在这里阅读更多关于mixed_precision培训的内容。
**from** tensorflow.keras **import** mixed_precision
mixed_precision**.**set_global_policy('mixed_float16')INFO:tensorflow:Mixed precision compatibility check (mixed_float16): OK
Your GPU will likely run quickly with dtype policy mixed_float16 as it has compute capability of at least 7.0\. Your GPU: NVIDIA GeForce RTX 3060 Laptop GPU, compute capability 8.6mixed_precision**.**global_policy()<Policy "mixed_float16">
我们可以看到会话的全局数据类型策略当前被设置为mixed_float16。另一件要记住的事情是,到目前为止,混合精度训练只在计算能力为7.0或更高的 GPU 上有效。我们可以看到,我的 GPU 具有混合精确训练能力和计算能力8.6。谢谢 Nvidia!
在我们开始实际的迁移学习和微调之前,我们将创建一些方便的回调以在培训期间实施。第一个将是TensorBoard回调,它可以在 Tensorboard Dev 站点上生成定义良好的图形和我们模型指标的可视化。为此,我们只需提供一个路径来存储训练期间的回调。
其次,我们将创建一个ModelCheckpoint回调,它将为我们的模型创建一个检查点,如果需要的话,我们稍后可以恢复到这个检查点。在这里,我们只保存模型的权重,而不是整个模型本身,因为这可能需要一些时间。验证准确性是这里被监控的度量,我们还将save_best_only参数设置为True,因此回调将只保存导致最高验证准确性的模型的权重。
**def** tensorboard_callback(directory, name):
log_dir **=** directory **+** "/" **+** name
t_c **=** tf**.**keras**.**callbacks**.**TensorBoard(log_dir **=** log_dir)
**return** t_c
**def** model_checkpoint(directory, name):
log_dir **=** directory **+** "/" **+** name
m_c **=** tf**.**keras**.**callbacks**.**ModelCheckpoint(filepath**=**log_dir,
monitor**=**"val_accuracy",
save_best_only**=True**,
save_weights_only**=True**,
verbose**=**1)
**return** m_c
好了,现在我们已经预处理了我们的数据,可视化并创建了一些方便的回调,我们继续实际的迁移学习部分!我们使用tf.keras.applications.efficientnet创建基础模型,并从可用的 EfficientNet 预构建模型列表中选择EfficientNetB0。我们还将include_top参数设置为False,因为我们将使用适合我们的 food101 分类应用程序的输出分类器层。由于我们现在只是提取特征而不是微调,我们将设置base_model.trainable = False。
base_model **=** tf**.**keras**.**applications**.**efficientnet**.**EfficientNetB0(include_top**=False**)
base_model**.**trainable **=** **False**
现在我们已经准备好了特征提取基础模型,我们将使用 Keras Functional API 来创建我们的模型,该模型包括基础模型作为特征提取训练模型的功能层。首先创建输入层,并将形状设置为(224, 224, 3)。然后通过传递输入层来实例化基本模型层,同时将training参数设置为False,因为我们还没有对模型进行微调。接下来,设置GlobalAveragePooling2D层来为我们的卷积神经网络执行汇集操作。之后,在这个模型不同于传统 CNN 的地方做了一个小的改变,在传统 CNN 中,输出层被分离成Dense和Activation层。通常对于任何分类, softmax 激活可以与输出密集层一起包含在内。但是在这里,由于我们已经实现了 mixed_precision 训练,我们在最后单独传递了 Activation 层,因为我们希望 softmax activation 的输出是float32数据类型,这将通过保持数值稳定性来消除任何稳定性错误。最后,使用Model()功能创建模型。
**from** tensorflow.keras **import** layers
inputs **=** layers**.**Input(shape **=** (224,224,3), name**=**'inputLayer')
x **=** base_model(inputs, training **=** **False**)
x **=** layers**.**GlobalAveragePooling2D(name**=**'poolingLayer')(x)
x **=** layers**.**Dense(101, name**=**'outputLayer')(x)
outputs **=** layers**.**Activation(activation**=**"softmax", dtype**=**tf**.**float32, name**=**'activationLayer')(x)
model **=** tf**.**keras**.**Model(inputs, outputs, name **=** "FeatureExtractionModel")
既然我们已经构建了仅包含特征提取的模型,我们使用summary()方法来查看我们的特征提取模型的架构:
model**.**summary()Model: "FeatureExtractionModel"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
inputLayer (InputLayer) [(None, 224, 224, 3)] 0
_________________________________________________________________
efficientnetb0 (Functional) (None, None, None, 1280) 4049571
_________________________________________________________________
poolingLayer (GlobalAverageP (None, 1280) 0
_________________________________________________________________
outputLayer (Dense) (None, 101) 129381
_________________________________________________________________
activationLayer (Activation) (None, 101) 0
=================================================================
Total params: 4,178,952
Trainable params: 129,381
Non-trainable params: 4,049,571
_________________________________________________________________
我们看到,整个基本模型(EfficientNetB0)是作为我们的特征提取模型中的一个单一功能层来实现的。我们还可以看到为构建我们的模型而创建的其他层。基本模型包含大约 400 万个参数,但是它们都是不可训练的,因为我们已经冻结了基本模型,因为我们没有进行微调,而只是从基本模型中提取特征。唯一可训练的参数来自输出密集层。
接下来,我们详细了解我们的特征提取模型中的各个层、它们的名称、它们是否可训练、它们的数据类型以及它们对应的数据类型策略。
**for** lnum, layer **in** enumerate(model**.**layers):
print(lnum, layer**.**name, layer**.**trainable, layer**.**dtype, layer**.**dtype_policy)0 inputLayer True float32 <Policy "float32">
1 efficientnetb0 False float32 <Policy "mixed_float16">
2 poolingLayer True float32 <Policy "mixed_float16">
3 outputLayer True float32 <Policy "mixed_float16">
4 activationLayer True float32 <Policy "float32">
我们可以看到除了基本模型层之外的所有层都是可训练的。数据类型策略表明,除了具有float32数据类型策略的输入层和输出激活层,所有其他层都兼容并采用mixed_float16数据类型策略。
现在我们可以继续编译和拟合我们的模型了。因为我们的标签不是一次性编码的,我们使用SparseCategoricalCrossentropy()损失函数。我们使用带有默认学习率的Adam()优化器,并设置模型指标来测量accuracy。
然后我们开始拟合我们的特征提取模型。我们将模型拟合历史变量保存到hist_model中,并在 3 个时期的训练数据集上训练我们的模型。我们在训练时使用我们的测试数据集作为验证数据,并使用 10%的测试数据作为验证步骤计数,以减少每个时期所用的时间。我们还将之前创建的回调函数传递到callbacks列表中,以创建 TensorBoard Dev 可视化和 ModelCheckpoint,后者保存我们模型的最佳权重,同时监控验证准确性。
model**.**compile(loss **=** tf**.**keras**.**losses**.**SparseCategoricalCrossentropy(),
optimizer **=** tf**.**keras**.**optimizers**.**Adam(),
metrics **=** ["accuracy"])
hist_model **=** model**.**fit(train_data,
epochs **=** 3,
steps_per_epoch**=**len(train_data),
validation_data**=**test_data,
validation_steps**=**int(0.1 ***** len(test_data)),
callbacks=[tensorboard_callback("Tensorboard","model"), model_checkpoint("Checkpoints","model.ckpt")])Epoch 1/3
2368/2368 [==============================] - 115s 43ms/step - loss: 1.8228 - accuracy: 0.5578 - val_loss: 1.2524 - val_accuracy: 0.6665
Epoch 00001: val_accuracy improved from -inf to 0.66653, saving model to Checkpoints\model.ckpt
Epoch 2/3
2368/2368 [==============================] - 97s 41ms/step - loss: 1.2952 - accuracy: 0.6656 - val_loss: 1.1330 - val_accuracy: 0.6946
Epoch 00002: val_accuracy improved from 0.66653 to 0.69462, saving model to Checkpoints\model.ckpt
Epoch 3/3
2368/2368 [==============================] - 100s 42ms/step - loss: 1.1449 - accuracy: 0.7024 - val_loss: 1.0945 - val_accuracy: 0.7021
Epoch 00003: val_accuracy improved from 0.69462 to 0.70214, saving model to Checkpoints\model.ckpt
哇!我原以为每个纪元的训练时间会更长。看起来我们的输入数据管道、GPU 和混合精度策略的实施确实帮助我们显著减少了训练时间。
嗯……考虑到我们有 101 个输出类,我们的训练和验证精度还不错。我们还创建了一个模型检查点,其模型权重导致最高的验证准确性,以防我们希望在下一阶段进行微调后恢复到它。现在让我们对整个测试数据评估我们的模型,并将它存储在一个变量中。
model_results **=** model**.**evaluate(test_data)790/790 [==============================] - 27s 34ms/step - loss: 1.0932 - accuracy: 0.7053
不错,一点也不错。对于一个特征提取模型来说,70.53%的准确率已经相当不错了。
使用基于 EfficientNetB0 基本模型的特征提取模型,我们获得了大约 70.53%的准确率。我们现在通过微调我们的特征提取模型来增加准确度分数怎么样。为此,我们将模型的 base_model 层(EfficientNetB0 功能层)设置为可训练,并解冻之前冻结的基础模型。
base_model**.**trainable **=** **True**
然而,BatchNormalization层需要保持冷冻。如果他们也被转为可训练,解冻后的第一个历元将会显著降低精度(详情在此)。由于基础模型中的所有层都已设置为可训练,我们使用以下代码块再次仅冻结基础模型的批处理规范化层:
**for** layer **in** model**.**layers[1]**.**layers:
**if** isinstance(layer, layers**.**BatchNormalization):
layer**.**trainable **=** **False**
快速检查基本模型的层、它们的数据类型和策略,以及它们是否可训练:
**for** lnum, layer **in** enumerate(model**.**layers[1]**.**layers[**-10**:]):
print(lnum, layer**.**name, layer**.**trainable, layer**.**dtype, layer**.**dtype_policy)0 block6d_project_conv True float32 <Policy "mixed_float16">
1 block6d_project_bn False float32 <Policy "mixed_float16">
2 block6d_drop True float32 <Policy "mixed_float16">
3 block6d_add True float32 <Policy "mixed_float16">
4 block7a_expand_conv True float32 <Policy "mixed_float16">
5 block7a_expand_bn False float32 <Policy "mixed_float16">
6 block7a_expand_activation True float32 <Policy "mixed_float16">
7 block7a_dwconv True float32 <Policy "mixed_float16">
8 block7a_bn False float32 <Policy "mixed_float16">
9 block7a_activation True float32 <Policy "mixed_float16">
很好!看起来除了BatchNormalization层之外,基础模型中的所有层都是可训练的,并准备好进行微调。我认为我们可以继续微调我们的特征提取模型'model'。我们从再次编译模型开始,因为我们已经改变了层属性(解冻),但是这一次,作为一个通用的良好实践,我们将Adam()优化器的默认学习率降低了 10 倍,以减少过度拟合,并在很大程度上阻止模型学习/记忆训练数据。
然后我们开始拟合我们的微调模型。我们将模型拟合历史变量保存到hist_model_tuned中,并且我们在训练数据集上训练我们的模型 5 个时期,然而,我们将初始时期设置为来自我们的特征提取模型训练的最后时期,其将是3。因此,微调模型训练将从纪元编号 3 开始,并将运行到 5(总共三个纪元)。我们在训练时使用我们的测试数据集作为验证数据,并使用 10%的测试数据作为验证步骤计数,以减少每个时期所用的时间。我们再次将之前创建的回调函数传递到回调列表中,以创建 TensorBoard Dev 可视化,用于在特征提取和微调模型之间进行比较,还创建了 ModelCheckpoint,用于保存我们模型的最佳权重,同时监控验证准确性。
model**.**compile(loss **=** tf**.**keras**.**losses**.**SparseCategoricalCrossentropy(),
optimizer **=** tf**.**keras**.**optimizers**.**Adam(learning_rate**=**0.0001),
metrics **=** ["accuracy"])
hist_model_tuned **=** model**.**fit(train_data,
epochs**=**5,
steps_per_epoch**=**len(train_data),
validation_data**=**test_data,
validation_steps**=**int(0.1*****len(test_data)),
initial_epoch**=**hist_model**.**epoch[**-**1],
callbacks**=**[tensorboard_callback("Tensorboard", "model_tuned"), model_checkpoint("Checkpoints", "model_tuned.ckpt")])Epoch 3/5
2368/2368 [==============================] - 313s 127ms/step - loss: 0.9206 - accuracy: 0.7529 - val_loss: 0.8053 - val_accuracy: 0.7757
Epoch 00003: val_accuracy improved from -inf to 0.77571, saving model to Checkpoints\model_tuned.ckpt
Epoch 4/5
2368/2368 [==============================] - 297s 125ms/step - loss: 0.5628 - accuracy: 0.8457 - val_loss: 0.8529 - val_accuracy: 0.7678
Epoch 00004: val_accuracy did not improve from 0.77571
Epoch 5/5
2368/2368 [==============================] - 298s 125ms/step - loss: 0.3082 - accuracy: 0.9132 - val_loss: 0.8977 - val_accuracy: 0.7801
Epoch 00005: val_accuracy improved from 0.77571 to 0.78006, saving model to Checkpoints\model_tuned.ckpt
好极了。看来验证精度提高了不少。现在让我们在整个测试数据上评估我们的微调模型,并将其存储在一个变量中。
model_tuned_results **=** model**.**evaluate(test_data)790/790 [==============================] - 26s 33ms/step - loss: 0.9043 - accuracy: 0.7772
你看看那个!微调后的模型精度提高了约 10%,现在约为 77.72 %。仅仅通过迁移学习和微调,我们已经取得了巨大的进步。感谢 EfficientNet 提供您的模型!
作为对比, Deep Food 在 2016的6 月实现的卷积神经网络模型已经达到了 77.4 %的顶级精度。Deep Food 的 CNN 模型花了几天时间来训练,而我们的迁移学习应用模型只用了不到 15 分钟就建成了!此外,通过使用简单的迁移学习技术和微调,我们的模型几乎与它不相上下,甚至略胜一筹。在其他方法中,训练更多的时期并增加正则化技术以减少过度拟合,肯定可以提高我们微调模型的准确度分数。此时,我们还可以使用.save()函数将模型保存到一个目录中,以便在本地运行时之外使用。
由于我们已经创建了TensorBoard回调,我们可以将指定目录中保存的 TensorBoard 日志上传到 TensorBoard Dev。可以点击获得的链接在 TensorBoard Dev 中在线查看模型的度量可视化。
**!**tensorboard dev upload --logdir ./Tensorboard \
**--**name "Food101"\
**--**description "Feature Extraction Model vs Fine Tuned Model"\
**--**one_shotNew experiment created. View your TensorBoard at: https://tensorboard.dev/experiment/hveaY1MSQBK6wIN6MwXoHA/
**[2021-11-16T23:06:17]** Started scanning logdir.
Data upload starting...
Uploading binary object (884.4 kB)...
Uploading binary object (1.1 MB)...
Uploading 36 scalars...
**[2021-11-16T23:06:22]** Total uploaded: 36 scalars, 0 tensors, 2 binary objects (2.0 MB)
**[2021-11-16T23:06:22]** Done scanning logdir.
Done. View your TensorBoard at https://tensorboard.dev/experiment/hveaY1MSQBK6wIN6MwXoHA/
因为我们已经为特征提取模型和微调模型创建了两个历史对象,所以我们可以创建一个函数来一起绘制这两个历史,并比较训练和验证指标,以获得这两个模型的性能的一般概念:
**def** compare_histories(original_history, new_history, initial_epochs):
"""
Compares two model history objects.
"""
acc **=** original_history**.**history["accuracy"]
loss **=** original_history**.**history["loss"]
val_acc **=** original_history**.**history["val_accuracy"]
val_loss **=** original_history**.**history["val_loss"]
total_acc **=** acc **+** new_history**.**history["accuracy"]
total_loss **=** loss **+** new_history**.**history["loss"]
total_val_acc **=** val_acc **+** new_history**.**history["val_accuracy"]
total_val_loss **=** val_loss **+** new_history**.**history["val_loss"]
plt**.**figure(figsize**=**(9, 9))
plt**.**subplot(2, 1, 1)
plt**.**plot(total_acc, label**=**'Training Accuracy')
plt**.**plot(total_val_acc, label**=**'Validation Accuracy')
plt**.**plot([initial_epochs**-**1, initial_epochs**-**1],
plt**.**ylim(), label**=**'Start of Fine Tuning')
plt**.**legend(loc**=**'lower right')
plt**.**title('Training and Validation Accuracy')
plt**.**subplot(2, 1, 2)
plt**.**plot(total_loss, label**=**'Training Loss')
plt**.**plot(total_val_loss, label**=**'Validation Loss')
plt**.**plot([initial_epochs**-**1, initial_epochs**-**1],
plt**.**ylim(), label**=**'Start of Fine Tuning')
plt**.**legend(loc**=**'upper right')
plt**.**title('Training and Validation Loss')
plt**.**xlabel('epoch')
plt**.**show()
太好了!现在,让我们在函数中使用hist_model和hist_model_tuned对象,并将initial_epochs设置为3,因为我们已经为 3 个时期训练了第一个特征提取模型。
compare_histories(hist_model, hist_model_tuned, initial_epochs**=**3)

作者图片
嗯…这是一个很好的情节。训练和验证曲线对于特征提取模型来说看起来很好,然而,在微调之后,在仅仅一个时期之后,训练损失显著降低,而训练准确度急剧增加。在此期间,验证准确性和损失开始趋于平稳。这无疑表明我们的模型在一个时代之后是过度拟合的。其他正则化技术,包括退出、增强和提前停止回调技术,可以用来减少过度拟合,并可能提高我们的模型在看不见的测试数据上的准确性分数。然而,我们现在不会探究它们,因为这个实验只是为了展示迁移学习和微调的不可思议的力量,只需要几个简单的步骤。
既然我们已经基于 EfficientNetB0 训练了我们的微调模型,我们将使用它对整个测试数据集进行预测,并将其存储在preds中:
preds **=** model**.**predict(test_data, verbose **=** 1)790/790 [==============================] - 27s 32ms/step
注意:预测存储在单个测试图像的每个类别的预测概率的集合中。
现在我们已经有了给定测试图像的每个类别的预测概率,我们可以使用tf.argmax()函数轻松获得预测标签,该函数返回包含给定轴上最高概率的索引。我们将预测标签存储在pred_labels中:
pred_labels **=** tf**.**argmax(preds, axis**=**1)
pred_labels[:10]<tf.Tensor: shape=(10,), dtype=int64, numpy=array([ 29, 81, 91, 53, 97, 97, 10, 31, 3, 100], dtype=int64)>
现在,我们已经进行了预测并获得了预测标签,我们可以将其与测试数据集标签进行比较,以获得有关微调模型的预测效果的详细信息。我们使用下面的代码块通过列表理解从测试数据集中获取测试标签:
test_labels **=** np**.**concatenate([y **for** x, y **in** test_data], axis**=**0)
test_labels[:10]array([ 29, 81, 91, 53, 97, 97, 10, 31, 3, 100], dtype=int64)
太好了!我们现在有了预测标签和基础事实测试标签。然而,为了绘制我们的预测标签和相关图像,我们需要一个数据集,只包含从test_data提取的图像,它包含图像和标签。为此,需要两个步骤。首先,我们创建一个空列表,并传递一个for循环,该循环使用take()函数从测试数据集中提取每一批并将其附加到新创建的空列表test_image_batches。take()函数内的-1使for循环遍历测试数据中的所有批次。结果输出是测试数据中所有批次的列表。由于图像仍然是以子列表的形式批量存在于列表test_image_batches中,在步骤 2 中,我们使用列表理解来提取每个子列表,即主列表中的每个图像,并将它们存储在test_images列表中。结果列表test_images包含原始测试数据集中包含的所有25250测试图像,使用列表上的len()功能进行验证。
*# Step 1*
test_image_batches **=** []
**for** images, labels **in** test_data**.**take(**-**1):
test_image_batches**.**append(images**.**numpy())
*# Step 2*
test_images **=** [item **for** sublist **in** test_image_batches **for** item **in** sublist]
len(test_images)25250
现在我们已经有了测试图像(test_images)、它们的基本事实测试标签(test_labels)和它们相应的预测标签(pred_labels)以及预测概率(preds)的列表,我们可以很容易地可视化测试图像以及它们的预测,包括基本事实和预测类别名称。
在这里,我们从test_images中取出 9 个随机图像,并在图标题中将它们与基础事实类名和预测类名一起绘制,在class_names上使用索引与相应的基础事实和预测标签并排。我们还在每幅图像上显示预测的预测概率,以了解模型在预测中的可信度。此外,为了使图看起来更好,信息更丰富,对于错误的预测,我们将标题颜色设置为红色,而对于通过我们的微调模型进行的正确预测,我们将标题颜色设置为绿色。
****注意:每次运行这个代码块时,它都会显示一组新的随机图像和它们的预测,这在识别两个看起来相似的食物类别时特别有用,在这两个类别中,我们的模型可能会混淆,或者在一个实例中,我们的模型已经正确预测了一个图像,但是由于错误的地面真实测试标签,它落入了错误预测的类别中。
plt**.**figure(figsize **=** (20,20))
**for** i **in** range(9):
random_int_index **=** random**.**choice(range(len(test_images)))
plt**.**subplot(3,3,i**+**1)
plt**.**imshow(test_images[random_int_index]**/**255.)
**if** test_labels[random_int_index] **==** pred_labels[random_int_index]:
color **=** "g"
**else**:
color **=** "r"
plt**.**title("True Label: " **+** class_names[test_labels[random_int_index]] **+** " || " **+** "Predicted Label: " **+**
class_names[pred_labels[random_int_index]] **+** "\n" **+**
str(np**.**asarray(tf**.**reduce_max(preds, axis **=** 1))[random_int_index]), c**=**color)
plt**.**axis(**False**);

作者图片
由于我们既有测试标签又有预测标签,我们还可以使用 SciKit-Learn 库中的classification_report()函数,并通过将output_dict参数指定为True来将每个类的precision、recall和f1-scores存储在字典report中。
**from** sklearn.metrics **import** classification_report
report **=** classification_report(test_labels, pred_labels, output_dict**=True**)
*#check a small slice of the dictionary*
**import** itertools
dict(itertools**.**islice(report**.**items(), 3)){'0': {'precision': 0.5535055350553506,
'recall': 0.6,
'f1-score': 0.5758157389635316,
'support': 250},
'1': {'precision': 0.8383838383838383,
'recall': 0.664,
'f1-score': 0.7410714285714286,
'support': 250},
'2': {'precision': 0.9023255813953488,
'recall': 0.776,
'f1-score': 0.8344086021505377,
'support': 250}}
现在我们已经有了一个包含各种评估指标(precision、recall和f1-scores)的字典,不如我们创建一个pandas dataframe,它只包含类名及其对应的 F1 分数。我们在评估指标中选择 F1 分数,因为它实现了精确度和召回率之间的平衡。你可以在这里阅读更多关于评估指标的信息。
但是,为了创建所需的数据框架,我们需要从分类报告字典中提取类别标签(键)和相应的 F1 值(值的一部分)。这可以通过创建一个空字典f1scores并传递一个for循环来实现,该循环遍历原始字典report的条目(键和值),并使用键索引附加类名,通过值索引附加相应的 F1 值。
f1scores **=** {}
**for** k,v **in** report**.**items():
**if** k **==** 'accuracy':
**break**
**else**:
f1scores[class_names[int(k)]] **=** v['f1-score']
*#check a small slice of the dictionary*
dict(itertools**.**islice(f1scores**.**items(), 5)){'apple_pie': 0.5758157389635316,
'baby_back_ribs': 0.7410714285714286,
'baklava': 0.8344086021505377,
'beef_carpaccio': 0.7912087912087912,
'beef_tartare': 0.7118644067796612}
现在我们已经准备好了所需的字典,我们可以轻松地从下面的代码行创建一个数据帧F1,并按照各个类的 F1-scores 的降序对数据帧进行排序:
F1 **=** pd**.**DataFrame({"Classes":list(f1scores**.**keys()),
"F1-Scores":list(f1scores**.**values())})**.**sort_values("F1-Scores", ascending**=False**)
*#check a small slice of the dataframe*
F1[:10]

作者图片
太好了!似乎食物类edamame在所有类中取得了最高的 F1 分,其次是macarons。是什么阻止了我们创建一个整洁的柱状图,以降序显示所有 101 种食物类别及其 F1 分数?
fig, ax **=** plt**.**subplots(figsize **=** (15,20))
plt**.**barh(F1["Classes"], F1["F1-Scores"])
plt**.**ylim(**-**1,101)
plt**.**xlabel("F1-Scores")
plt**.**ylabel("Food Classes")
plt**.**title("F1-Scores across various Food Classes")
plt**.**gca()**.**invert_yaxis()
**for** i, v **in** enumerate(round(F1["F1-Scores"],3)):
ax**.**text(v, i **+** .25, str(v), color**=**'red');

作者图片

毛豆(来源)
太棒了。到目前为止,食品类别edamame的正确预测数最高,而类别steak的 F1 分数最低,表明正确预测数最低。嗯(表示踌躇等)..这有点可悲,因为我们都喜欢牛排!
作为我们基于 EfficientNetB0 的迁移学习微调模型的最终评估步骤,让我们将我们模型的最错误的预测可视化,即预测概率最高的模型的错误(错误)预测。这将有助于我们了解我们的模型是否在类似的食品类别中混淆,或者测试图像是否被错误标记,这将是数据输入错误。
为此,让我们创建一个简洁的数据帧Predictions,它包含测试数据集中各种图像的“图像索引”,它们对应的“测试标签”、“测试类别”、“预测标签”、“预测类别”和“预测概率”。
Predictions **=** pd**.**DataFrame({"Image Index" : list(range(25250)),
"Test Labels" : list(test_labels),
"Test Classes" : [class_names[i] **for** i **in** test_labels],
"Prediction Labels" : list(np**.**asarray(pred_labels)),
"Prediction Classes" : [class_names[i] **for** i **in** pred_labels],
"Prediction Probability" : [x **for** x **in** np**.**asarray(tf**.**reduce_max(preds, axis **=** 1))]})
由于我们已经创建了所需的数据帧Predictions,我们还可以创建一个新的“正确预测”列,其中包含由我们的模型正确预测的测试图像的True和错误预测的测试图像的False:
Predictions["Correct Prediction"] **=** Predictions["Test Labels"] **==** Predictions["Prediction Labels"]
Predictions[:10]

作者图片
太好了,我们的数据框看起来棒极了!然而,这不是我们数据帧的最终形式。我们希望当前数据帧的子集仅包含那些“正确预测”等于False的记录,因为我们的目标是绘制和可视化微调模型的最错误的预测。我们使用以下代码块获得最终数据帧,并按照“预测概率”的降序对数据帧记录进行排序:
Predictions **=** Predictions[Predictions["Correct Prediction"] **==** **False**]**.**sort_values("Prediction Probability", ascending**=False**)
Predictions[:10]

作者图片
印象深刻!我们的最终数据帧Predictions仅包含错误预测的图像记录(图像索引)以及它们的基本事实测试标签、测试类、预测标签、预测类,按照它们的预测概率的降序排序。我们可以手动从该数据集中取出一个图像索引,并将其传递到测试数据集的绘图函数中,以获得图像的可视化,并显示其基本真实类名和预测类名。然而,这里我们将取第一个 9 图像或图像索引,它们是我们的数据帧中最错误的图像,并将其与真实的类名、预测的类名和相应的预测概率一起绘制。
indexes **=** np**.**asarray(Predictions["Image Index"][:9]) *#choosing the top 9 records from the dataframe*plt**.**figure(figsize**=**(20,20))
**for** i, x **in** enumerate(indexes):
plt**.**subplot(3,3,i**+**1)
plt**.**imshow(test_images[x]**/**255)
plt**.**title("True Class: " **+** class_names[test_labels[x]] **+** " || " **+** "Predicted Class: " **+** class_names[pred_labels[x]] **+** "\n" **+**
"Prediction Probability: " **+** str(np**.**asarray(tf**.**reduce_max(preds, axis **=** 1))[x]))
plt**.**axis(**False**);

作者图片
嗯嗯嗯…你知道什么!通过快速的谷歌搜索(是的,我不知道这些食品类别名称中的大多数是什么),很明显,事实上,一些测试图像的地面真相测试标签被错误地标注,就像第一个图像的情况一样,它显然不是 huevos rancheros,而是我们的模型预测的类别——玉米片!此外,第五个图像显然不是大蒜面包,但也是我们的模型预测的类别——bruschetta!我们的模型加分!其他常见的错误预测来自于我们的模型混淆了相似的食物类别。例如,一些图像中的chocolate_cake与tiramisu非常相似,在某些情况下,甚至连人类都很难对其进行分类。
好在我们决定可视化最错误的预测!有了这个,我们可以执行适当的数据清理技术来纠正错误的真相标签,并可能删除一些真正令人困惑的看起来相似的食物类别。经过这些修正和调整,我认为我们的微调模型的准确性可以大幅提高!
同样,在随机浏览最错误的预测图像时,我偶然发现了这张图像,它在测试数据集中被明确标记为peking_duck,但实际上是pad_thai!再一次,这可以通过快速的谷歌搜索得到证实,因此我们的模型实际上做出了正确的预测。嗯(表示踌躇等)...也许存在许多其他带有错误地面真相标签的图像,如果它们都被纠正,想象一下我们的模型的准确度分数!
plt**.**figure(figsize**=**(5,5))
plt**.**imshow(test_images[11586]**/**255)
plt**.**title("True Class: " **+** class_names[test_labels[11586]] **+** " || " **+** "Predicted Class: " **+** class_names[pred_labels[11586]]
**+** "\n" **+** "Prediction Probability: " **+** str(np**.**asarray(tf**.**reduce_max(preds, axis **=** 1))[11586]))
plt**.**axis(**False**);

作者图片

北京烤鸭和泰式炒面完全不同!(图片由作者提供)
现在想象一下,如果我们必须建立一个超过 200 层的复杂模型(如 EfficientNetB0)来完成我们的图像分类任务,并达到这一精度水平,需要花费我们多少天时间?迁移学习的确是一种神奇的技术!
感谢您的阅读。
如果你喜欢我的作品,可以在 Linkedin 上联系我。
提前停止的影像分类—快速教程
用不到 50 行代码构建和训练一个 Keras 模型

Keras 是一个深度学习库,作为数据科学家,我们可能会经常遇到。它是最容易实现和最容易学习的深度学习框架,如果这还不够,锦上添花的是,自从 Tensorflow 2.0 的发展以来,随着它与 Tensorflow 捆绑在一起,使用 Keras 学习和构建变得更加容易。
这篇文章描述了一个项目,它将帮助你明确基础知识,并迅速掌握它们。
所以说,让我们着手建立一个深度神经网络,用于对新冠肺炎胸部 X 射线进行多类分类。
数据
我们将使用这个数据集,可在 Kaggle 上获得,可在公共领域下获得,与我在上一部分提到的标题相同。
https://www.kaggle.com/amanullahasraf/covid19-pneumonia-normal-chest-xray-pa-dataset
对于分类问题,我们有三种类型的 X 射线——正常的、肺炎的和冠状的。我们的目标是快速开发一个模型,并利用它学习 Keras 深度学习库的基础知识。

来自 Kaggle 页面的数据集图像
为了方便起见,请将下载的数据保存在**‘data/’**文件夹下。
让我们开始导入我们需要的包:
from tensorflow.keras.layers import Conv2D, Flatten, Dense, MaxPooling2D, Dropoutfrom tensorflow.keras.models import Sequentialfrom tensorflow.keras.preprocessing import imagefrom tensorflow.keras.callbacks import EarlyStoppingimport os
通常,我们的项目会采用以下结构:
image_classification_project/
classifier.ipynb
data/
train/
normal/
covid/
pneumonic/
valid/
normal/
covid/
pneumonic/
test/
normal/
covid/
pneumonic/
现在,我们继续构建模型并导入数据。
导入数据
启动您的本地环境,或者更好的是,运行时设置为“GPU”的 colab 笔记本。
让我们从定义训练和测试数据生成器对象开始:
# Data Generators for some data preprocessing and augmentationtrain_datagen = image.ImageDataGenerator(rescale = 1./255, shear_range = 0.2, zoom_range = 0.2, horizontal_flip = True)test_datagen = image.ImageDataGenerator(rescale = 1./255)
ImageDataGenerator 类为我们的图像分类任务提供了一种简单的加载、预处理和批量扩充图像的方法。
我们现在继续从上一节定义的目录中导入数据:
# importing datatraining_set= train_datagen.flow_from_directory(**os.path.join(data, "train"**), target_size = (128, 128), batch_size = 32, class_mode='binary') test_set = test_datagen.flow_from_directory(**os.path.join(data, "valid"**), target_size = (128, 128), batch_size = 32, class_mode='binary')
如果您正在使用不同的目录结构,请确保根据您自己的特定设置更改上面代码片段中的粗体部分。
执行上面几行代码后,您将得到以下输出:
Found 4830 images belonging to 3 classes.
Found 1039 images belonging to 3 classes.
现在,我们准备检查我们的训练指数。
print(training_set.class_indices, test_set.class_indices)
您将在执行时看到以下输出:
({'covid': 0, 'normal': 1, 'pneumonia': 2}, {'covid': 0, 'normal': 1, 'pneumonia': 2})
这与我们的三个图像类可以在以后被引用的方式有关,它有助于看到它们对于训练集和测试集是相同的。
现在,让我们开始建立模型!
模型
我们从序列对象开始,用 32 个 3x3 内核大小的过滤器(或神经元)添加 2D 卷积层。
我们对每张图片的输入形状是:128x128 像素,3 代表我们的图片是 RGB。
我们将激活函数定义为“ReLU”。
# the modelmodel = Sequential()model.add(Conv2D(32, (3, 3), input_shape = (128, 128, 3), activation = 'relu'))model.add(MaxPooling2D(pool_size = (2, 2)))model.add(Conv2D(32, (3, 3), input_shape = (128, 128, 3), activation = 'relu'))model.add(Flatten())model.add(Dense(units = 256, activation = 'relu'))model.add(Dropout(0.5))model.add(Dense(units = 128, activation = 'relu'))model.add(Dense(units = 3, activation = 'softmax'))model.compile(optimizer = 'adam', loss = 'sparse_categorical_crossentropy', metrics = ['accuracy'])model.summary()
然后我们添加一个池 (MaxPooling2D )层,另一个卷积层和一个全连接(密集)层。 Dropout 层旨在确保我们的模型不会过度拟合。
我们的最后一层是另一个密集层,在 softmax 激活函数的帮助下,也作为我们的输出层,输出 3 个加起来等于 1 的类概率。
提前停止
Tensorflow 文档将提前停止描述为:
当受监控的指标停止改善时,停止训练。
因为我们想最小化我们的验证损失,我们监控它,以便我们的耐心参数可以定义在哪个时期停止训练,以防它在许多时期都没有改善。
early_stopping = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4)
这是我们将在下一步拟合(或训练)模型时包括的回调。
训练模型
拟合模型很简单,只需用我们的必要参数调用模型上的拟合函数,例如用于随机梯度下降更新的批量大小,以及我们希望整个训练发生的时期数。
# Model trainingclassifier = model.fit(training_set, batch_size = 128, epochs = 10, validation_data = test_set, callbacks=[early_stopping])
您会注意到,我们还将回调指定为我们之前定义的提前停止对象。

详细的训练时期
正如你所看到的,我们的回调产生了必要的效果,没有过度训练我们的模型,并在第九纪元停止。
作为奖励,你现在可以用下面的 Keras 内置保存功能保存你的模型:
model_save_full_path = os.path.join(ROOT, 'saved_models', 'conv2d_version_1_acc_79_84')model.save(model_save_full_path)
后续步骤
感谢您的阅读!
如果你觉得这个教程很有帮助,并遵循了,下面是我建议在这个教程之后做的事情:
—构建混淆矩阵,直观地表示正确和错误分类的标签数量。
—探索用于评估模型的其他指标,如精确度、召回率和 F1 分数。
—衡量模型在测试数据集上的表现。
如果你想看本教程的下一部分,在其中我描述了上述进一步的步骤,甚至为分类器构建了一个前端,请关注我并保持关注!
在这里获得我的免费“从模型到生产电子书”,其中我教授了构建深度学习模型并将其部署为 API 的最快方法。
快乐学习!😃
基于 k-均值的图像聚类
利用迁移学习模型从图像中提取特征
在图像分类问题中,我们必须将给定的一组图像分类成给定数量的类别。在分类问题中训练数据是可用的,但是当没有训练数据时该怎么办,为了解决这个问题,我们可以使用聚类将相似的图像分组在一起。
聚类是一种无监督的机器学习,我们将相似的特征分组在一起。它解释输入数据,并在特征空间中找到自然组或聚类。
这里我使用了 k-means 进行图像聚类。我拿了猫和狗的数据集。
我已经将猫和狗的图片放在不同的文件夹中,并展示了如何在图片中进行聚类。

数据集的图像
我在 InceptionV3 中使用迁移学习模型从图像中提取特征,并使用这些特征进行聚类。
首先,让我们得到所有需要的库,
from tensorflow.keras.applications.inception_v3 import InceptionV3
from tensorflow.keras.applications.inception_v3 import preprocess_input
from tensorflow.keras.preprocessing import image
from tensorflow.keras.preprocessing.image import img_to_array
from sklearn.cluster import KMeans
import pandas as pd
import numpy as np
from tqdm import tqdm
import os
import shutil
数据集在 Kaggle 上可用,可以从这里下载。我只取了 700 张图片用于聚类,每类 350 张图片。
这些图像放在一个文件夹中,使用迁移学习模型提取特征。下面是使用 InceptionV3 模型从图像中提取特征的方法,我保存了图像名称,以便辨别哪个图像属于哪个聚类。
# Function to Extract features from the images
def image_feature(direc):
model = InceptionV3(weights='imagenet', include_top=False)
features = [];
img_name = [];
for i in tqdm(direc):
fname='cluster'+'/'+i
img=image.load_img(fname,target_size=(224,224))
x = img_to_array(img)
x=np.expand_dims(x,axis=0)
x=preprocess_input(x)
feat=model.predict(x)
feat=feat.flatten()
features.append(feat)
img_name.append(i)
return features,img_name
使用上面的函数,我提取了特征和名称,并将它们存储在 img_features,img_name 中。
img_path=os.listdir('cluster')
img_features,img_name=image_feature(img_path)
现在,这些提取的特征被用于聚类,k-Means 聚类被使用。下面是 k 均值聚类的代码,k 的值是 2,因为只有 2 个类。
#Creating Clusters
k = 2
clusters = KMeans(k, random_state = 40)
clusters.fit(img_features)
创建了两个群集,提取的 img_name 被转换为 dataframe,我添加了另一列来显示哪个映像属于哪个群集,之后,我将映像保存在它们各自的群集中。
image_cluster = pd.DataFrame(img_name,columns=['image'])
image_cluster["clusterid"] = clusters.labels_
image_cluster # 0 denotes cat and 1 denotes dog

集群信息
现在我把猫和狗的图片保存在一个单独的文件夹里。
# Made folder to seperate images
os.mkdir('cats')
os.mkdir('dogs')# Images will be seperated according to cluster they belong
for i in range(len(image_cluster)):
if image_cluster['clusterid'][i]==0:
shutil.move(os.path.join('cluster', image_cluster['image'] [i]), 'cats')
else:
shutil.move(os.path.join('cluster', image_cluster['image'][i]), 'dogs')
上面的代码将根据 clusterid 将不同文件中的图像分开。
在这篇博客中,我们讨论了如何使用迁移学习从图像中提取特征,然后执行聚类以将不同文件夹中的猫和狗的图像分开。它可以在我们不知道图像类别的情况下用于标记,因为手动标记将是一个挑战。
本文中的所有代码都驻留在这个 Github 链接上:
https://github.com/shubham7169/Projects/blob/master/Image_Clustering.ipynb
图像颜色识别与机器学习和图像处理,使用 Python

拉娅·索德伯格在 Unsplash 上的照片
用几行代码识别图像的颜色
计算机视觉中一个非常常见的任务就是识别颜色。
更一般地说,学习的一个迷人的方面是,有时我们知道一些事情,但我们无法解释它们。例如,如果我让你描述“红色”,你唯一的选择是给我看一个红色的物体,就像上面报道的红橙色……但是你不能真正解释“红色”是什么。
为此,需要付出一定的努力将颜色的定义传输给计算机。
我们开始吧!
1.图像
第一步是得到一个图像。我用过这个:

这种算法的最大优点是,正如你将看到的,它非常健壮,并且它给出的令人满意的输出完全独立于输入图像,所以你可以选择你自己的照片。
2.图书馆
让我们召唤一些恶魔。您将需要用于机器学习部分的 Sklearn 库、用于矢量转换的 Numpy 、用于最终摘要的 Pandas 和一些图像处理典型库 (cv2、skimage、matplotlib.pyplot、…)
3.机器学习部分
这篇伟大的文章 给了我们一个非常好的提示。事实上,主要思想是可以将图像用作(N_rows X N_columns X N_channels)向量。考虑到 这个向量,有可能应用 K 均值算法,识别 K 个聚类,那将是我们的颜色。
这非常有趣,原因有几个。首先,它不需要对大量图像进行任何特定的训练。第二个是你可以增加聚类的数量(从而增加颜色的数量),选择更少或更多的色调。
为此,您将需要以下功能:
这些命令:
这最后几行可能需要一段时间,但没那么长。此外,这是该过程中唯一“计算量大”的部分。
一旦完成,你将拥有你的 10 种颜色(我通过设置“颜色数量=10”来选择它们为 10)
他们来了。
特别是,采用颜色的 rgb 编码(rgb_colors list)是很有用的…但我们将在稍后实现。
3.图像处理部分
所以我们有自己的颜色。困难的是,虽然我们可以看到它们并识别它们,但我们无法将“标签”列表中的每个元素与其对应的颜色联系起来。实际上, K Means 是一种无监督的学习方法。
因此,如果我们想自动检测天空的颜色,并将其与饼图中的一种颜色相关联,我们需要更有创意一些。
主要的想法是非常基本的。事实上,图像是 RGB 编码的。这意味着,如果我们计算图像和 10 种颜色之一的 rgb 表达式之间的差异,当图像等于颜色时,我们会得到[0,0,0]。
让我告诉你,这不是正统。其实你会有负像素。但是有用,有用就别碰:)
第一步是技术性的,它基于将 RGB 转换为整数值。
然后,如果我们想要识别图像的颜色,想法是将这个图像分成更小的方块。在这种情况下,我选择每个正方形的维数为 N_rows/10 X N_columns/10,因此得到 100 个正方形。这些正方形是通过使用下面的函数得到的。
当然,方块不是单色的。这意味着每个方块将有多种颜色。尽管如此,图像之间的平均距离是我们需要的指标:我们选择平均来说比其他颜色更接近 0 的颜色。具体来说,我们通过使用以下函数来实现:
使用此功能,我们可以为每个方块绘制“最佳颜色”。
让我展示给你看:
太棒了!不是吗?
4.最后的结果
为了获得该实验对图像所有方块的总结,可以使用以下函数:
这就是:
附注:每一栏下的数字是该特定方块的颜色百分比
结论
我们被生活在一个拥有“能看见”的机器的世界的想法吓坏了。虽然这种想法可能令人担忧,因为它是可以理解的,但同时我忍不住认为它非常迷人。我喜欢认为我们在某种程度上是新世界和新自然的创造者…或者也许我只是太累了。
如果你喜欢这篇文章,你想知道更多关于机器学习的知识,或者你只是想问我一些你可以问的问题:
A.在 Linkedin 上关注我,在那里我发布我所有的故事
B .订阅我的 简讯 。这会让你了解新的故事,并给你机会发短信给我,让我收到你所有的更正或疑问。
C .成为 推荐会员 ,这样你就不会有任何“本月最大数量的故事”,你可以阅读我(以及成千上万其他机器学习和数据科学顶级作家)写的任何关于最新可用技术的文章。
再见。😃
图像压缩— DCT 方法
基于 DCT 的图像压缩

上图显示了实施以下算法的步骤流程
我们可以看到,近年来计算资源和数据呈指数级增长。尽管计算资源和数据都在增长,但二者的增长率形成了鲜明的对比。我们现在有大量的数据,但没有足够的计算资源来在相当长的时间内处理这些数据。这让我们想到了当今世界面临的一个主要问题。我们如何压缩数据信息,同时保持数据中的大部分信息?
在这个项目中,我们将处理图像信息。应用于图像的压缩主要有两种类型——无损压缩和有损压缩。无损压缩标准的一些例子是 PNG(便携式网络图形)和 PCX(图片交换)。无损压缩保留了所有信息,但压缩率较低。如果我们需要更高的压缩,我们必须考虑有损压缩算法。广泛使用的有损压缩算法之一是 JPEG 压缩算法。 JPEG 算法作用于 DCT,这是本项目讨论的主题。
DCT 代表离散余弦变换。它是一种快速计算傅立叶变换,将实信号映射到频域中的相应值。DCT 仅对复信号的实部起作用,因为大多数真实信号是没有复分量的真实信号。我们将在此讨论 DCT 算法在图像数据上的实现及其潜在用途。该项目已被托管在 GitHub 上,你可以在这里查看它。
实现图像压缩的 DCT 的步骤:
如果我们有多通道图像,我们需要将算法分别应用于每个通道。在进行 DCT 处理之前,我们必须将 RGB 图像转换成等效的 YCbCr 格式。这里的另一个重要步骤是将像素值的范围从-128 更改为 127,而不是 8 位图像的标准值范围 0 到 255。
图像被分成 N*N 个块。我们这里取 N=8,因为这是 JPEG 算法标准。
接下来,连续地对每个块应用 DCT。
量化用于限制在不丢失信息的情况下可以保存的值的数量。
量化块的子集被存储到一个数组中,可以从该数组中提取该子集以供进一步处理。
我们可以将 IDCT 应用于量化的块,然后按顺序排列 8*8 的块,以获得 YCbCr 图像,然后可以将其转换为 RGB,以获得压缩形式的原始图像。该步骤尚未作为该项目的一部分实施。
图像压缩算法实现:
下图给出了 2D DCT 的 DCT 公式。这里,P(x,y)表示输入图像中的像素。

然而,当我们处理 JPEG 压缩时,我们总是取 N = 8,这修改了等式并得到下面的等式:

但是在图像的 8*8 块的每个点上应用这种复杂的标量计算可能是耗时的,因此我们可以进一步简化方程以得出相同的矢量表示。其矢量表示可以如下给出:

我们通过以下公式计算 DCT
d = DCT _ Matrix @ Image _ Block @ DCT _ MatrixT
8*8 DCT 的量化块已经被直接编码到函数中。然而,用户可以根据进一步的应用选择所需的压缩率。
与高频分量相比,人类视觉系统对图像的低频分量更敏感。因此,我们可以很容易地从图像中丢弃高频分量,而仍然保留图像中的大部分信息内容。在量化之后,大小为 88 的经处理的阵列可以被减少到更低的维度。我们在这里取块的 55 子集,它仍然保留了大约 95%的信息,同时大小减少了 60.9%(1-(25/64))。这也有助于我们根据输入图像的大小实现 60%到 67%之间的整体压缩率。
实验结果:

这里可以找到输入图像(在 Unsplash 上),这里可以找到结果。

这里可以找到输入图像(在 Unsplash 上),这里可以找到结果。
应用:
图像可以以压缩格式保存,当必须显示时,可以重新转换为 RGB 版本。
经过处理的信息块可以通过通信信道发送,从而消耗较少的带宽。
这种经过处理的 DCT 信息可以作为基于深度学习的计算机视觉任务的输入,这些任务通常需要大量高质量的数据。
作为一名具有电子和电信背景的数据科学家,我主要关注信号处理,我已经看到并研究了应用部分提到的前两部分。我的最终目标是现在实现第三部分。
参考资料:
【https://www.math.cuhk.edu.hk/~lmlui/dct.pdf
http://fourier.eng.hmc.edu/e161/lectures/dct/node1.html
使用主成分分析(PCA)的图像压缩
行动中的维度缩减

主成分分析(PCA) 是一种线性降维技术(算法),它将一组相关变量(p)转化为更小的 k (k < p)个不相关变量,称为 主成分 ,同时尽可能保留原始数据中的可变性。
PCA 的一个用例是,它可以用于 图像压缩——一种在尽可能保持图像质量的同时最小化图像字节大小的技术。在本帖中,我们将通过使用手写数字的 MNIST 数据集来讨论这项技术。阅读完本文后,您将获得使用 Python 和 Scikit-learn 进行 PCA 图像压缩的实践经验。
我们开始吧!
加载数据集
MNIST 数据集包含手写数字的图像数据。因为它是 CSV 文件格式,所以让我们使用 Pandas read_csv() 函数加载它。
import pandas as pd
mnist = pd.read_csv('mnist.csv')
mnist.head()

MNIST 数据集的一部分(图片由作者提供)
每行包含一个单一图像的像素值。构成图像的像素可以被认为是图像数据的维度(列/变量)。‘标签’列包含数字的值(0-9)。我们的分析不需要该列,因为 PCA 是一种无监督的机器学习任务,不处理标记为的数据。因此,我们可以简单地删除该列。
mnist.drop(columns='label', inplace=True)
mnist.head()

移除标签栏后的 MNIST 数据(图片由作者提供)
现在,数据集的形状是:
mnist.shape
#(60000, 784)
该数据集包含 60,000 张 28x28 (784)像素的图像!
显示图像
让我们显示 MNIST 数据集中的第二幅图像(第二行)。该图像应包含数字“0”,因为第 2 行的标签列值为“0”。
等到加载 Python 代码!

图片 1(作者图片)
哇!是数字 0!
特征缩放
由于 PCA 方向对数据的尺度高度敏感,如果数据不是在相似的尺度上测量的,我们必须在应用 PCA 之前进行特征缩放。
在 MNIST 数据集中,每幅图像的像素值范围为 0 到 255(相似的比例)。例如:
#2nd image
print(mnist.iloc[1].min())
print(mnist.iloc[1].max())
#0
#255
因为我们的数据是在相似的尺度上测量的,所以我们不需要对 PCA 进行特征缩放。
应用 PCA
选择正确的维度数量
首先,我们需要选择正确的维数(即正确的主成分数)。为此,我们应用具有原始维数(即 784)的 PCA,并创建 scree 图,以查看 PCA 如何捕捉数据的方差。
等到加载 Python 代码!

将方差解释为组件数量的函数(图片由作者提供)
让我们尝试使用前 10 个组件来压缩图像。这些组件没有捕捉到原始数据中的很多可变性。所以,我们不会得到一个清晰的图像。
等到加载 Python 代码!

(图片由作者提供)
将此与我们之前获得的图像 1(原始图像)进行比较。这个图像不是很清晰,缺乏信息。
让我们尝试使用前 184 个组件来压缩图像。最初的 184 个成分捕获了原始数据中大约 96%的可变性。所以,这一次,我们将得到一个非常清晰的图像,与原始图像非常相似。
等到加载 Python 代码!

(图片由作者提供)
我们还可以计算 184 个成分的解释方差:
np.cumsum(pca_184.explained_variance_ratio_ * 100)[-1]
#96.11980535398752
是 96.1%。
将压缩图像与原始图像进行比较:

(图片由作者提供)
这就是我们如何使用 PCA 进行图像压缩。左边的图片是 784 维的原图。右边的图像是 184 维的压缩图像。在对图像数据应用 PCA 后,维数减少了 600 维,同时保持了原始图像数据中约 96%的可变性!通过比较这两幅图像,可以看到图像质量略有损失,但压缩后的图像内容仍然可见!
摘要
今天,我们讨论了如何使用 PCA 进行图像压缩。当维度或组件的数量增加时,图像质量损失减少。我们应该始终努力保持最佳数量的组件,以平衡解释的可变性和图像质量。
pca 对象的 inverse_transform() 方法用于将缩减的数据集解压缩回 784 维。这对于可视化压缩图像非常有用!
感谢阅读!
本教程由Rukshan Pramoditha,数据科学 365 博客作者设计创作。
在https://rukshanpramoditha.medium.com阅读我的其他文章
2021–04–12
通过图像压缩实现主成分分析的魔力
利用图像精美演示 PCA

Erik Mclean 在 Unsplash 上拍摄的照片
什么是 PCA?
主成分分析(PCA)是一种降维技术,用于具有许多连续(数字)特征或维度的数据集。它使用线性代数来确定数据集最重要的特征。在识别出这些特征之后,你可以只使用这些特征来训练机器学习模型,并在不牺牲准确性的情况下提高性能。正如我的一个好朋友兼导师所说:
“PCA 是你机器学习工具箱中的主力。”
PCA 找到具有最大方差的轴,并将这些点投影到该轴上。PCA 使用线性代数中的一个概念,称为特征向量和特征值。 栈交换 上有个帖子很漂亮的解释了一下。
图像压缩
PCA 在用于压缩图像时得到了很好的演示。图像只不过是一个像素网格和一个颜色值。让我们将一个图像加载到一个数组中,并观察它的形状。我们将使用来自matplotlib的imread。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.image import imread
image_raw = imread("cat.jpg")
print(image_raw.shape)
(3120, 4160, 3)
plt.figure(figsize=[12,8])
plt.imshow(image_raw)

作者图片
结果显示一个矩阵的大小(3120, 4160, 3)。第一个是图像的高度,第二个是宽度,第三个是 RGB 值的三个通道。考虑到这张图片的维度数量,您可以看到与经典的表格数据集相比,这是相当大的,尤其是当我们想到 3120 个列时。
在我们继续之前,让我们把它改成灰度图像,去掉 RGB 值。
# Show the new shape of the image
image_sum = image_raw.sum(axis=2)
print(image_sum.shape)
# Show the max value at any point. 1.0 = Black, 0.0 = White
image_bw = image_sum/image_sum.max()
print(image_bw.max())
(3120, 4160)
1.0
计算解释方差
接下来,我们可以fit使用 Scikit-Learn 中的 PCA 处理我们的灰度图像。在图像拟合之后,我们有了方法pca.explained_variance_ratio_,,它返回由每个主成分解释的方差的百分比。利用np.cumsum,我们可以累加每个分量的每个方差,直到它达到100%。我们将把它画在一条线上,并显示解释方差的95%在哪里。
import numpy as np
from sklearn.decomposition import PCA, IncrementalPCA
pca = PCA()
pca.fit(image_bw)
# Getting the cumulative variance
var_cumu = np.cumsum(pca.explained_variance_ratio_)*100
# How many PCs explain 95% of the variance?
k = np.argmax(var_cumu>95)
print("Number of components explaining 95% variance: "+ str(k))
#print("\n")
plt.figure(figsize=[10,5])
plt.title('Cumulative Explained Variance explained by component')
plt.ylabel('Cumulative Explained variance (%)')
plt.xlabel('Principal components')
plt.axvline(x=k, color="k", linestyle="--")
plt.axhline(y=95, color="r", linestyle="--")
ax = plt.plot(var_cumu)
Number of components explaining 95% variance: 54

作者图片
首先,我想指出一些事情。通过打印组件的长度,我们可以看到总共有3120个组件,显示了组件的数量与图像宽度的关系。
len(pca.components_)
3120
通过绘制这个图,我们可以看到曲线是如何戏剧性地向100%加速,然后变平。疯狂的是,我们只需要用原来3120组件的54来解释图像中方差的95%!这太不可思议了。
利用主成分分析降低维数
我们将使用来自IncrementalPCA模块的fit_transform方法,首先找到54主成分,并转换和表示这些54新成分中的数据。接下来,我们将使用inverse_transform方法从这些54组件中重建原始矩阵。最后,我们将绘制图像,从视觉上评估其质量。
ipca = IncrementalPCA(n_components=k)
image_recon = ipca.inverse_transform(ipca.fit_transform(image_bw))
# Plotting the reconstructed image
plt.figure(figsize=[12,8])
plt.imshow(image_recon,cmap = plt.cm.gray)

作者图片
我们可以清楚地看到图像的质量降低了,但我们可以将其识别为原始图像。当 PCA 与机器学习模型(如图像分类)一起应用时,两种训练时间都显著减少,对新数据的预测时间产生几乎一样好的结果,但数据更少。
显示 k 维的其他值
接下来,让我们对我们的图像的六个不同的 k 值进行迭代,显示在每个数字上逐渐提高的图像质量。我们将只去250组件,仍然只是原始图像的一小部分。
def plot_at_k(k):
ipca = IncrementalPCA(n_components=k)
image_recon = ipca.inverse_transform(ipca.fit_transform(image_bw))
plt.imshow(image_recon,cmap = plt.cm.gray)
ks = [10, 25, 50, 100, 150, 250]
plt.figure(figsize=[15,9])
for i in range(6):
plt.subplot(2,3,i+1)
plot_at_k(ks[i])
plt.title("Components: "+str(ks[i]))
plt.subplots_adjust(wspace=0.2, hspace=0.0)
plt.show()

作者图片
结论
就是这样!少到10的组件甚至让我们分辨出图像是什么,在250很难说出原始图像和 PCA 缩小图像之间的区别。
PCA 是一个非常强大的工具,可以集成到您的工作流程中(通过管道),以大幅减少数据集中维度的数量,而不会丢失太多信息。请记住,PCA 是为连续或数字数据使用而设计的。查看这篇文章, PCA 清楚地解释了,了解更多细节和 PCA 背后的数学原理。感谢阅读,并享受!
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。一个月 5 美元,让你可以无限制地访问成千上万篇文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
图像导数

图片来源链接,经作者允许编辑
思想和理论
图像一阶导数的分析
在卷积网络中,靠近输入的图层用于提取空间要素。这种行为受到了当我们被叫去识别一个物体时,人类视觉系统中发生的事情的启发。我们大脑解码的第一个信息是形状、颜色、纹理的存在、光线的方向和边缘。我们从世界中提取一般信息,当我们处理这些信息时,将允许我们获得越来越多的抽象信息来识别物体。
在本文中,我们将重点关注边缘检测,或者说图像一阶导数的计算,看看连续世界和离散世界之间的差异。最后,我们将通过两个求导算子来分析卷积过程,并突出它们的优缺点。
1.连续微分的概念
在数学上,导数表达了函数相对于发展方向的局部可变性的率。关于这一点,让我们考虑一个信号 f :只有一个发展方向 x 的ℝ→ ℝ,让 xi 成为其定义域中的一个点。我们想要获得的信息是在工作点 xi ( 局部可变性)的信号 f 是发生变化(增加或减少)还是保持恒定。这个想法可以是研究工作点的周围或者更好的:(I)评估 xi 中的f(ii)评估 xi 中的 f 加上一个无穷小量ε(iii)计算两者之间的差。这是在一阶导数的计算中发生的,我们可以用公式(1)来形式化。

公式(1)
如果函数的增量比的极限存在并且是有限的,则函数 f 在工作点 xi 是可导的,因为自变量的ε增量趋于零。
多维函数对下面的讨论特别重要。设 f : ℝ^n → ℝ是定义在 n 发展方向上的标量场,导数的计算通过考虑偏一阶导数,或者更确切地说是关于每个 n 方向的导数来完成。后者构成了梯度 ∇( f ) : ℝ^n → ℝ,一个关联到标量场 a 矢量的每个 n 维点的矢量场。梯度为每个点提供了三条重要的信息。第一个是模数,表示函数 f 在工作点附近的变化量,第二个是函数在计算点的增长对,第三个是方向,与标量场的轮廓线正交。在图 1 中可以找到图形表示,其中考虑了ℝ的标量场 f(x,y) = x + y 。

图 1: (a)示出了在函数 f(x,y) = x +y 的 C1 水平曲线上取的点(xi,yj)的梯度向量∇f(xi,yj 的微积分。我们注意到,梯度向量在该点的模由沿 x 和 y 的变化的和(PitagoraTheorem)给出,而方向通过计算角度θ给出。另一方面,相对取决于 f 的最大变化(在这种情况下,朝向曲线外侧)。在(b)中,我们展示了函数 f(x,y) = x +y 的向量场∇f 的构造。我们注意到,当我们沿着 x 和 y 增长时,这次在水平曲线上的不同点计算的梯度向量趋向于更大(更高的模数)。这是因为,由于梯度模数高于梯度模数,梯度向量趋向于更大。
这是因为,由于点中梯度的大小就是变化量,x 和 y 的值增加得越多,f 的变化就越大。(来源:图片由我提供)
2.边缘检测
设 I[x,y]为我们在两个发展方向上的形象,分别为 x ( 宽和 y ( 高)。我们将边缘定义为 I[x,y]的一个区域,在该区域中颜色强度发生了变化图 2

图 2 .我们可以在图像分析中找到的三种边缘的表示(图片由我提供,灵感来自 source link pg.90)
边缘检测旨在通过计算图像的梯度来突出这种变化。我们知道,梯度是由偏导数组成的。如第 1 节所述,它们的形式化在连续世界中是有效的。另一方面,图像是一个离散的多维信号。
2.1 离散偏导数
离散多维度的特征包括通过有限差分对连续偏一阶导数的近似,其中ε增量不趋向于抵消(ϵ → 0)而是呈现有限值。在我们的离散信号 I[x,y]的情况下,增量值等于一个像素。对于给定的像素[ xm,yn ],我们可以在它周围移动以评估局部变化率的最小量正是该像素。形式化,我们可以区分三种有限差分:( a )向前(b)向后(c)中心。
当我们谈论图像时,最著名的运算之一是卷积。给定一个矩阵 K ( 核),卷积使 K 沿着图像 I 的高度和宽度滑动一定的步距,对核的值和图像的重叠区域进行加权求和。通过适当的核变换,这种技术是我们通过计算在两个发展方向上的偏一阶导数来对图像应用有限差分的技术。表 1 给出了上述内容的总结和形式化。在计算机视觉领域,特别是对于边缘检测,使用了中心或也称为对称微分。

表 1:发展方向 x 和 y 上的向前、向后和中心三类有限差分以及二维工作点[xm,yn]的形式化。每一个都与对应的发展方向 x 的 1×3 维和发展方向 y 的 3×1 的核表示相关联
2.2 离散梯度
卷积产生新的图像,称为特征图(F) ,在高度和宽度方面具有与 I 相同的尺寸(跨距= 1 和填充=‘相同’),并且其中强调了 I 的具体特征。作为一个例子,如果我们计算 I[x,y]和 Kx 之间的卷积(关于 x 的导出核),结果将是新的图像 Fx,其中垂直边缘将具有非零强度值,这对水平边缘不利。特别是,我们可以说:
像素 Fx[xm,yn]的强度值对应于点 I[xm,yn]处的偏一阶导数的值。
考虑 Ky ( 关于 y 的求导核)也可以进行同样的分析。

图 3:使用图像 I[x,y]作为离散信号计算梯度的模数和方向。(图片来源:图片由我提供)
一旦获得偏导数的值,我们就可以计算梯度 G 。后者将把关于模数的信息与每个像素 I[xm,yn]相关联,该信息将指示围绕[xm,yn]的图像的变化量或幅度,以及关于方向的信息,该信息将表示感兴趣的像素周围的颜色强度的增长 h 的方向。图 3 显示了已经说过的内容的几何表示。因为我们是在离散的世界中,在图 4 中可以找到一个更正确的表示****

图 4: (a)示出了图像 I[x,y],在相应的发展方向上具有两个边缘。(b)表示 Fx = IKx,或者更确切地说,( a)相对于 x 的导数。我们注意到垂直边缘相对于水平边缘是如何突出显示的。在(c)中,Fy = IKy,或者更确切地说,表示相对于 y 的导数。在这种情况下,行为与我们在(b)中看到的相反。图(d)、(e)、(f)报告了关于梯度的信息。特别地,对于(d ),我们有 Fx 和 Fy 的 Pitagora 和。结果是幅度或模数|G[x,y]|。在(e)和(f)中,表示了梯度的增长方向。正如我们所看到的,(e)显示了边缘上每个像素的梯度的预期方向。后者用于指示感兴趣像素周围的颜色强度的增长,在这种情况下,颜色强度从 0 到 255。我们可以通过图像(f)来表示这些信息。角度θ表示渐变的方向,它将左上角的像素视为笛卡尔轴和 y 轴的交点,方向与标准方向相反。(图片来源:图片由我提供)
为了讨论的完整性,下面给出了用于获得图 5 中的示例的代码。可以看出,Kx 和 Ky 都是值-1 的预乘。更多详情请见附录 2。
3.噪音
图像可能呈现颜色强度的随机变化。这种现象被称为数字噪声,尤其发生在光线较暗的情况下,在高 ISO 值或其他情况下,可以由传感器本身引入( ex : CMOS)。对于边缘检测,噪声可以消除边缘的存在。
在这方面,让我们考虑添加了噪声的图像 Ix,y并绘制强度直方图(表 2)。我们注意到,与没有噪声的情况相比,曲线更加波动,呈现出几个称为假边缘的峰值。导数将突出显示它们中的每一个,丢失关于真实边缘的信息。

表 2:在有和没有椒盐噪声的情况下对相同图像 I[x,y]计算的导数的比较(来源:Image by me)
减少假边缘的一种方法是模糊图像。模糊的类型和数量取决于噪声的强度。下面介绍一些与 Kx 和 Ky 衍生内核相结合的图像模糊技术。
3.1 移动平均模糊:Prewitt 算子

图 5:识别垂直边缘的 Prewitt 算子
从图 5 中可以看出,Prewitt 算子由几个大小为 1×3 的对称导数核堆叠而成。具体来说,可以通过三像素移动平均核和导数核之间的矩阵乘积进行分解。在文献中,我们可以找到这个算符被 1/9 而不是 1/3 预乘。在这种情况下,我们将考虑 3x3 衍生内核的所有像素来执行平均,从而减少噪声量。对于沿 y 方向的导数核,可以进行相同的考虑。
3.2 高斯滤波器模糊:索贝尔算子

图 6:用于垂直边缘识别的 Sobel 算子(来源:me 提供的图片)
Sobel 算子通过计算高斯滤波器的导数得到。特别是可以通过离散高斯滤波器和导数核之间的矩阵乘积进行分解。图 6 给出了一个沿 x 轴的大小为 3×3 的 Sobel 算子的例子。对于沿 y 轴方向的导数核,可以进行同样的考虑。
4.衍生内核导致的问题
假设我们的图像中有一个阶梯边缘。应用衍生内核,特别是中心,边缘由最少两个像素表示。如果我们有一个斜坡或一个根部边缘,像素的数量就会增加。同样的现象不仅可以由边缘本身的性质引起,而且首先可以由图像的模糊引起,因此使用 Sobel 和 Prewitt 算子。
同样,如前一节所述,噪声的存在会导致边缘信息的丢失和假边缘的识别。模糊可以减少这种现象,但不能完全消除它。图 7 给出了一个例子,其中在模糊处理之后错误边缘仍然存在。

图 7:在对图 4 (a)的图像应用 Sobel 算子执行模糊和推导过程之后的错误边缘的识别,其中添加了椒盐噪声和二进制阈值处理(来源:Image by me)
这些影响可以通过使用 Canny、边缘检测器来减少,该边缘检测器将非最大抑制和边缘链接过程应用于由 Sobel 算子获得的结果,分别减少边缘的厚度和假阳性的存在。
附录
- 计算|G[xm,yn]|
如第 2.2 段所述,梯度模量的计算考虑了关于 x 和 y 发展方向的导数值之间的皮塔哥拉和。如果我们认为必须在 25/30 fps ( 帧每秒)上应用后者,则后者在计算方面可能是昂贵的,除非进行二次采样。为了减轻计算负荷,梯度的模数可以使用公式(2)来近似。

公式(2)
2。卷积
在连续世界中,两个信号 f(t) 和 g(t) 之间的卷积导致(I)在支持变量(例如 τ)中表达两个函数,(ii)假设沿着新方向滑动的是 g(τ),相对于其 y 轴翻转函数g(g(-τ))(iii)添加变量 t 以允许滑动(iv)此时,计算 dτ [3][4]中的 f (τ) g(t-τ)的积分。
scipy.ndimage 库的 convolve() 方法实现了这一点。因此,为了正确计算内核 Kx 和 Ky ,我们需要将它们预乘-1。此时,应用卷积()方法,特别是点( ii ),我们得到了正确的滤波器值(表 3)

表 3:翻转和执行 scipy.ndimage.convolve()方法后的衍生内核表示。(图片来源:图片由我提供)
不反转导数核并执行卷积将导致误读导数值和梯度方向的信息。梯度的模数不受影响,因为它是由正和形成的。图 8 中显示了一个示例。正如我们所看到的,对于从强度值 255 过渡到 0 的左边缘,我们预计导数为负,但我们得到的是增长值。对于右边缘可以进行同样的考虑。

图 8:没有导数核 Kx 翻转的导数计算的表示。(图片来源:图片由我提供)
参考
[1] 衍生—维基百科
[2] 第五讲:边缘检测——斯坦福大学
[3] 卷积:定义—维基百科
[4] 卷积:举例—维基百科
使用 GFPGAN 的图像增强
利用 GFPGAN 恢复模糊图像

来源:作者
随着使用深度学习的图像处理的出现,我们已经看到了 N 个预训练模型,它们不仅处理图像,还增强了它们的属性,你可能已经看到了多个为图像带来生命的深度赝品视频。
深度学习领域的主要进步之一,神经网络是生成性对抗网络的引入,它是深度假货和所有主要图像处理背后的大脑,增强了功能。
GAN 是一类机器学习框架,有助于生成与训练数据具有相同统计属性的新数据。监督学习模型由两个子模型组成,生成器模型用于生成新数据,鉴别器模型用于从训练数据中鉴别新数据。
如今,GAN 因其增强图像、创建新图像或视频等功能而广受欢迎。有多个预先训练好的 GAN 模型,任何人都可以直接使用。有多种基于 GAN 的图像增强应用非常受欢迎。
在本文中,我们将讨论一个这样的预训练 GAN 模型,即生成面部先验生成对抗网络,它有助于恢复面部图像并在图像模糊时对其进行增强。它很容易使用,因为它是一个预先训练的模型,我们不需要再次训练它。我们将使用这个模型,并增强模糊的图像。
让我们开始吧…
安装所需的库
我们将从使用 pip 安装安装依赖项和克隆 Github 存储库 GFPGAN 开始。下面给出的命令可以做到这一点。
# Clone GFPGAN and enter the GFPGAN folder
%cd /content
!rm -rf GFPGAN
!git clone [https://github.com/TencentARC/GFPGAN.git](https://github.com/TencentARC/GFPGAN.git)
%cd GFPGAN# Set up the environment
# Install basicsr - [https://github.com/xinntao/BasicSR](https://github.com/xinntao/BasicSR)
# We use BasicSR for both training and inference
!pip install basicsr
# Install facexlib - [https://github.com/xinntao/facexlib](https://github.com/xinntao/facexlib)
# We use face detection and face restoration helper in the facexlib package
!pip install facexlib
# Install other depencencies
!pip install -r requirements.txt
!python setup.py develop
!pip install realesrgan # used for enhancing the background (non-face) regions
# Download the pre-trained model
!wget [https://github.com/TencentARC/GFPGAN/releases/download/v0.2.0/GFPGANCleanv1-NoCE-C2.pth](https://github.com/TencentARC/GFPGAN/releases/download/v0.2.0/GFPGANCleanv1-NoCE-C2.pth) -P experiments/pretrained_models
导入所需的库
在这一步中,我们将导入 GFPGAN 和映像恢复所需的所有库。
import os
from google.colab import files
import shutil
上传图像
现在,我们将从读取用户的图像开始。
# upload your own images
upload_folder = 'inputs/upload'if os.path.isdir(upload_folder):
shutil.rmtree(upload_folder)
os.mkdir(upload_folder)# upload images
uploaded = files.upload()
for filename in uploaded.keys():
dst_path = os.path.join(upload_folder, filename)
print(f'move {filename} to {dst_path}')
shutil.move(filename, dst_path)
图像恢复
上传图像后,让我们使用 GFPGAN 来恢复模糊的图像并增强它。
# Now we use the GFPGAN to restore the above low-quality images
!rm -rf results
!python inference_gfpgan.py --upscale 2 --test_path inputs/upload --save_root results --model_path experiments/pretrained_models/GFPGANCleanv1-NoCE-C2.pth --bg_upsampler realesrgan!ls results/cmp

来源:作者
可视化增强的图像
现在最后一步是可视化增强的图像,为了可视化我们将使用 cv2 和 matplotlib。
# Now we use the GFPGAN to restore the above low-quality images
# We use [Real-ESRGAN]([https://github.com/xinntao/Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN)) for enhancing the background (non-face) regions
!rm -rf results# We first visualize the cropped faces
# The left are the inputs images; the right are the results of GFPGANimport cv2
import matplotlib.pyplot as plt
def display(img1, img2):
fig = plt.figure(figsize=(25, 10))
ax1 = fig.add_subplot(1, 2, 1)
plt.title('Input image', fontsize=16)
ax1.axis('off')
ax2 = fig.add_subplot(1, 2, 2)
plt.title('GFPGAN output', fontsize=16)
ax2.axis('off')
ax1.imshow(img1)
ax2.imshow(img2)
def imread(img_path):
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img# display each image in the upload folder
import os
import globinput_folder = 'results/cropped_faces'
result_folder = 'results/restored_faces'
input_list = sorted(glob.glob(os.path.join(input_folder, '*')))
output_list = sorted(glob.glob(os.path.join(result_folder, '*')))
for input_path, output_path in zip(input_list, output_list):
img_input = imread(input_path)
img_output = imread(output_path)
display(img_input, img_output)!python inference_gfpgan.py --upscale 2 --test_path inputs/upload --save_root results --model_path experiments/pretrained_models/GFPGANCleanv1-NoCE-C2.pth --bg_upsampler realesrgan!ls results/cmp

复原图像(来源:作者)
这里你可以看到我们使用 GFPGAN 恢复模糊图像是多么容易。
用不同的图片试试,让我知道你在回复部分的评论。
本文是与 Piyush Ingale 合作完成的。
在你走之前
感谢 的阅读!如果你想与我取得联系,请随时通过 hmix13@gmail.com 联系我或我的 LinkedIn 个人资料 。可以查看我的Github*简介针对不同的数据科学项目和包教程。还有,随意探索* 我的简介 ,阅读我写过的与数据科学相关的不同文章。
基于 PyTorch 的图像特征提取
案例研究:使用 K-Means 算法的图像聚类

美国宇航局在 Unsplash 拍摄的照片
总之,本文将向您展示如何使用 PyTorch 实现用于特征提取的卷积神经网络(CNN)。此外,我将向您展示如何使用 K-Means 算法根据图像的特征对图像进行聚类。尽情享受吧!
介绍
假设你看到一只猫的图像。在几秒钟内,你可以看到里面有一只猫。如果我们给电脑同样的图片会怎么样?电脑无法识别它。也许我们可以在电脑上打开图像,但它不识别它。
如你所知,计算机处理数字。他们看到的和我们不一样。因此,计算机处理的一切都应该用数字来表示。
我们如何将图像表示为数字?图像实际上由数字组成,每个数字代表颜色或亮度。不幸的是,当我们想做一些机器学习任务时,例如图像聚类,这种表示是不合适的。
聚类基本上是一项机器学习任务,我们根据数据的特征对数据进行分组,每个组都由彼此相似的数据组成。当我们想要像图像一样对数据进行聚类时,我们必须将其表示形式转换为一维向量。
但是我们不能直接把图像转换成矢量。假设您有一个彩色图像,大小为 512×512 像素,有三个通道,每个通道代表红色、绿色和蓝色。
当我们将三维矩阵转换成一维向量时,向量将由 786.432 个值组成。这是一个巨大的数字!
如果我们全部使用它们,将会使我们的计算机处理数据变慢。因此,我们需要一种方法来提取这些特征,这就是卷积神经网络(CNN)的用处。
卷积神经网络
CNN 是最受欢迎的深度学习模型之一。该模型主要用于图像数据。这个模型将对图像进行卷积处理,用一种叫做内核的东西过滤图像,这样我们就可以从中获得一个模式。
由于其分层结构和各种过滤器尺寸,CNN 可以捕捉高、中、甚至低水平的特征。此外,它还可以通过使用一种称为池化的机制将信息压缩成较小的大小。
CNN 模型的优势在于,它可以捕捉到任何位置的特征。因此,这种神经网络是处理图像数据的理想类型,特别是用于特征提取[1][2]。
k-均值算法
在我们使用 CNN 提取特征向量之后,现在我们可以根据我们的目的来使用它。在这种情况下,我们希望将图像分成几个组。我们如何对图像进行分组?
我们可以使用一种叫做 K-Means 的算法。首先,K-Means 将初始化几个称为质心的点。质心是数据进入组的参考点。我们可以任意初始化质心。
初始化质心后,我们将测量每个数据到每个质心的距离。如果距离值最小,则该数据属于该组。它随时间变化,直到聚类没有显著变化。
为了测量距离,我们可以使用一个叫做欧几里德距离的公式。公式看起来像这样,

现在我们知道了 CNN 和 K-Means 的概念。让我们开始实施吧!
履行
数据
在这种情况下,我们将使用来自人工智能人群的数据集进行一场名为人工智能闪电战 7:阶段预测的比赛。你可以在这里访问数据集。
数据集由一个包含图像的文件夹和一个 CSV 文件组成,该文件显示了一个提交给 AI Crowd 的示例。文件夹里有 1799 张图片,里面没有标签。因此,这是一个无监督的学习问题,特别是聚类。
下载数据集的代码如下所示。
建立模型
下载完数据后,我们就可以建立模型了。该模型基于 VGG-16 架构,并且已经使用 ImageNet 进行了预训练。代码看起来像这样,
因为我们只想提取要素,所以我们只提取要素图层、平均池图层和一个输出 4096 维向量的全连接图层。这是我们修改 VGG 模型之前的蓝图。
基于上面的蓝图,我们如何在 PyTorch 中使用这些特性?我们可以使用点(。)运算符来执行此操作。我们对上面提到的每一层都这样做。
在我们提取了每一层之后,我们创建了一个继承了 nn 的名为 FeatureExtractor 的新类。PyTorch 的模块。做这些事情的代码看起来像这样。
在我们这样做之后,我们会得到一个看起来像这样的蓝图。
特征抽出
现在我们已经建立了模型。是时候用它来提取特征了。步骤是打开图像,变换图像,最后提取特征。代码如下所示。
使聚集
现在我们有了特征。下一步是将其分组。为此,我们将使用 scikit-learn 库。代码如下所示。
保存结果
不错!我们已经拿到标签了。最后一步是将结果保存到数据帧。代码如下所示。
结论
干得好!您已经使用 CNN 进行了特征提取,还使用 K-Means 进行了聚类。我希望这篇文章对你有用,如果你想问什么,你可以在 LinkedIn 上联系我。
参考
[1] Simonyan,k .,& Zisserman,A. (2015 年)。用于大规模图像识别的非常深的卷积网络。ArXiv:1409.1556 [Cs]。http://arxiv.org/abs/1409.1556
[2] VGG16 —用于分类和检测的卷积网络。(2018 年 11 月 20 日)。【https://neurohive.io/en/popular-networks/vgg16/
图像处理综合指南:基础
图像处理要领
用 MATLAB 或 Python 实现的一步一步的图像处理流程
对数字图像执行的所有操作都要经过图像处理。尽管通过对图像执行各种操作来改变图像有各种目的,但计算机视觉是图像处理使用最多的领域之一。在这个系列中,我将提到不同的术语和操作,拉菲尔·冈萨雷斯和理查德·e·伍兹的《T2 数字图像处理第三版》是我在教学过程中和准备这些文章时使用的原始资料。
以下是本系列每篇文章的内容列表——点击这里开始学习吧!
图像处理第 1 部分(从下面开始🏃)
- 什么是图像?&图像采集
- 采样和量化
- 利用灰度变换的图像增强
- 直方图处理
- MATLAB 从头开始实现这些程序
图像处理第二部分
2.1
- 非线性空间滤波
- 最小最大中值滤波
- 填料
- 这些程序从零开始的 Python 实现和 PIL 库
2.2
- 线性空间滤波
- 卷积和相关
- 平滑过滤器
- 锐化滤镜
- 边缘检测过滤器
- 噪声消除滤波器
- Python 从头开始实现这些过程和 OpenCV 库
图像处理部分 3
- 形态学算子
- 侵蚀和膨胀
- 结合形态学算子的噪声去除和边缘检测
- 对图像应用这些操作的 OpenCV 示例,对 2D 矩阵应用这些操作的 Scipy 示例。
图像处理工具
一个在 QT Creator 上使用 OpenCV 3.2.0 和 C++实现的工具,可以应用这些帖子中讨论的几乎所有图像处理操作。
你可以访问我的github链接来访问这里使用的所有代码💻
让我们开始第一部分吧!
图像处理第一部分
一个场景,一个我们用眼睛看到的景象,其实是用电磁能谱得到的连续信号。我们眼睛中的受体所感知的这种信号的值基本上由两个主要因素决定:落入环境中的光量和从物体反射回我们眼睛的光量。
我们知道,地球上有各种不同波长的光源,我们只能用眼睛分辨某一种波长的光。在这里,我们可以将所有由于这个范围内的光线而能够感知到的场景,称为“图片”。我们说这个光源到达我们眼睛的信息是“连续”。当我们想将这些信息转移到数字媒体并将其存储在这个环境中时,这意味着我们在谈论数字图像的概念,即“离散信号。[1]
也就是说,数字图像是一个二维矩阵,矩阵的每个元素实际上携带了该离散信号的相关部分的值。

图 2“作者图片”
相应地,您是否注意到,随着信号中光量的增加(因此图片中的颜色值从黑色变为白色),矩阵中的值也在增加?
那么图 1 所示的转换实际上是如何完成的呢?以连续信号形式获得的信号如何转换成离散信号,即转换成数字图像?为了回答这些问题,我们将提到两个基本概念:
采样&量化
采样是将确定要获得的图像的大小的过程。它处理数字图像的像素数量,即矩阵大小。增加数字图像的尺寸被命名为“上采样,而减小被命名为“下采样”。研究采样过程时经常可以看到 spatial 这个词。采样是一个空间过程。因为它处理的是图片的大小,完全独立于图片的内容或者它将拥有的值。
量化是决定图像中每个像素值的过程。首先,确定基本灰度级,即像素可以具有的值的范围。然后,根据来自连续信号的值,计算数字图像中像素的值。如果一张图片的灰度级为 n 位,其灰度范围将具有 2ⁿ 的值。例如,像素在 3 位灰度中的取值范围是[0–7],而像素在 8 位灰度中的取值范围是[0–255]。像素所具有的值也被称为该像素的“强度”。
****!!!我们提到过,当一个场景被转移到数字图像环境中时,连续信号被转换成离散信号。这里的另一个要点是,离散信号并不携带连续信号中的所有信息。例如,如图 3 所示,我们没有接收连续信号中的所有信息,而是只接收了 16 条信息,因此我们获得了一个 16 像素的图片作为输出图片。输出画面尺寸越大,即采样做的越大,输出画面越接近场景,信息损失越少。同样,灰度等级越大,输出图像中的像素值就越接近实际场景中的值。

图 4“作者图片”
****!!!数字图像的采样或量化属性可以重复改变,从而导致同一图像的不同输出。

图 5“作者图片”
虽然上面的左图显示了图片的原始版本,但我们在图 4 中看到了该图片的不同采样示例。随着用于采样的像素数的减少,输出图像的尺寸减小,我们得到的图像质量也降低。其实过程无非就是从同一个场景得到的信息越来越少,得到一张新的图片!
上面的左图显示了黑白 uint8 类型的图片(因此图片矩阵的值可以是 0–255 之间的任何值),而图 5 显示了同一图片在不同量化级别下的输出。当我们检查图 5 时,当灰度级为 1 位,即图像矩阵中的值只能取 0 或 1 中的一个值时,输出显示图像的对比度处于最高水平,图像的质量处于最低水平。当灰度级达到 8 位时,我们看到输出图像与输入图像具有相同的质量,因为量化级别与原始图像处于相同的级别。
****!!!在图 5 中,我们看到从 5 位灰度级开始,图像质量几乎相同。但是,如果您想检查图片矩阵,可以看到矩阵值在 5 位灰度级时在 0–32 的范围内,在 7 位灰度级时在 0–64 的范围内,在 7 位灰度级时在 0–128 的范围内。
如果想对这个题目和图片做更多的练习,可以点击采样&量化访问相关的 Matlab 代码。
图像增强
用于在本质上没有任何变形的图像上获得图像的不同版本的过程包括在图像增强类别的操作中。这些可以包括改变图片的光强度、改变其对比度、操纵颜色设置等。没错,这些正是你在 Instagram 等应用中一键看到自己不同颜色、不同设置的图片背后的操作。!
我们将研究两个不同类别的图像增强。如果逐个像素地处理图像,也就是说,如果该过程单独应用于每个像素,则在灰度转换中检查该图像;如果该过程应用于大于 1 个像素的像素组,则在过滤类别中检查该图像。
****!!!强度变换、映射变换或点处理的名称也与灰度级变换含义相同。你也可能在网上或不同的书中碰到这些名字。
灰度变换
在本节中,我们将通过对下面的基本图片应用不同的操作来查看输出图片。

“作者提供的图像”
- 线性变换
当我们考虑一个取值在 0-255 之间的图片矩阵时,我们说越接近 0 的像素越接近黑色,越接近 255 的像素越接近白色。因此,当我们想使图片变亮时,增加像素值,当我们想使图片变暗时,减少像素值是明智的,对吗?将每个像素的值增加或减少一个常数 x 是一个线性过程。

“作者提供的图像”
另一个线性过程是拍摄照片的底片。这个过程是将每个像素从其补数(图片中的一个像素根据灰度范围可以获得的最大值)中减去。例如,取 8 位灰度级图像的负片等于从 255 中减去每个像素。
2.对数变换****
用于改变光强度的另一个过程是对数变换。通过将每个像素应用于以下过程来获得输出画面的像素:s = clog(1+r)。这里 c 是决定处理效果的系数,r 是相关像素。

“作者提供的图像”
3.幂律变换—伽马校正****
另一种用于改变图片光线的过程是幂律变换,也称为伽马校正。输出图像的像素通过用以下过程操作每个像素来获得:

这里 c 和γ是两个不同的系数,r 是各自的像素。
伽玛校正不仅改变光量,还改变图像的对比度设置。因为对每个像素取γ系数的幂,并且它根据这个数是在 0 -1 的范围内还是大于 1 来减小或增大像素值之间的距离。

“作者提供的图像”
当我们对相同的基础图片应用伽马校正时,我们看到,在伽马> 1 的情况下,我们得到无用的图片,因为图片的原始对比度已经很高,而在
0 <伽马< 1 的情况下,我们得到具有更低对比度的图片。
4.对比变换****
图片的对比度是其像素之间的亮度值差异。如果像素值彼此接近,则图像的对比度低,如果像素值彼此远离,则对比度高。用于增加或减少对比度的基本过程具有以下公式。

在这个公式中,r:相关像素 s: a 系数 a:> 1 是将应用的过程转换为对比度增强的系数,if 0 <= a <1, the applied process to contrast reduction.

“Image by Author”
One of the operations used to change the contrast settings of the picture is 对比度拉伸。它改变了图像中像素值的分布范围和比率,使得图像的直方图(将在下一小节中详细讨论)具有更成比例的分布。综上所述,可以表述为平衡而不是增减对比度。这个过程应用于下面的公式。

m:画面中最小像素值
M:画面中最大像素值
mj:输出画面中一个像素可以拥有的最小值
Mj:画面中一个像素可以拥有的最大值
I (r,c):画面中任意像素值

“作者提供的图像”
另一个操作是对比度阈值。在该过程中,选择固定值作为阈值,并且通过将 1 分配给高于该阈值的值,将 0 分配给较低值,获得高对比度二进制输出图像。有两种常用的方法来确定阈值。第一种是简单地选择一个固定的数(全局阈值),第二种是使用像素值的平均值作为阈值(平均全局阈值)。

“作者提供的图像”
您可以从gray _ level _ transformations链接访问相关的 Matlab 代码,自己应用这些过程并检查用不同值获得的输出。
5.直方图处理****
图片的直方图是一个离散函数,x 轴是灰度级,y 轴是图像中具有相应灰度位的像素总数。虽然直方图让我们了解与我们所拥有的图像相关的各种问题,但是它们形成了在空间域中执行的许多操作的基础。

图 9“作者图片”
在图 9 中,我们看到了黑白(单通道)图像的直方图。这张图片的灰度等级是 8 位,也就是说,它的像素可以取 0–255 之间的值。因此,x 轴的范围是 0–255,而 y 轴由描述该范围内每个值有多少像素的数字组成。例如,在这张图片中,我们看到没有像素值为 0,而有超过 1500 个像素值为 14。
****!!!在 MATLAB 中获得这些数字后,您可以在坐标轴上导航,并通过单击它来检查(x,y)对在您想要的点上的值。

图 10“作者图片”
在图 10 中,我们看到了 RGB(三通道)图像的直方图。灰度级也是 8 位。与上面的黑白图片不同的是,组成 y 轴的数字是为 3 个不同通道中的像素单独计算和收集的。例如,x = 69 y = 10780 的值告诉我们,这张图片在 3 个不同的通道中总共有 10780 个像素,值为 69。

“作者提供的图像”
也可以在 RGB 图片中单独查看每个通道的直方图。所以在上面的图像中,我们看到的是每个通道本身的总数,而不是 y 轴上 3 个通道的像素总数。
RGB 图像
彩色图片,我们在日常生活中习惯于看到的不仅仅是黑白图片,实际上是 3 种不同的矩阵组合在一起形成 1 幅数字图片的图像。我们眼睛看到的所有不同颜色实际上是三种基本颜色的混合:红色、绿色和蓝色。RGB 图像是具有用于这三种主色的单独矩阵的图像,并且 x 位置的像素值是从这三个矩阵中的值的混合中获得的。我们说在黑白图片中,一个像素越接近 0,越接近黑色,一个像素越接近 255,越接近白色。对于 3 通道图像,在红色通道中,越接近 0 的像素越接近黑色,越接近 255 的像素越接近红色;在绿色通道中,越接近 0 的像素越接近黑色,越接近 255 的像素越接近绿色;在蓝色通道中,越接近 0 的像素越接近黑色,越接近 255 的像素越接近蓝色。在这 3 个通道的混合中,无论哪个矩阵具有相关像素的最高值,该颜色都将在该像素中占主导地位。我们看到图像像素接近白色,其中 3 种颜色具有相同的高值像素,而 3 种颜色具有相同的低值像素,图像像素接近黑色。

“作者提供的图像”
在上面的图片中,我们看到 3 个不同的 3 x 9 大小的矩阵,依次形成了图片的红色、绿色和蓝色通道,以及最终的 3x9 像素图片。您可以检查由 3 个通道中不同像素值的组合所创建的颜色,并且您可以从本节末尾共享的代码链接中进行自己的实验。
直方图均衡化
这是平衡图片直方图分布的过程。图片中的每个像素都经过下面描述的步骤,从而产生一个更平衡的直方图,然后是一个更平衡的图片。如果图像的对比度分布已经足够平衡,则在图像中观察不到太多变化。
- 对于每个灰度值,计算图片中的像素总数。(这其实是图片的直方图吧?!)
- 对于每个灰度值,计算在随机像素中出现的概率。对于这种计算,前一步骤中每个灰度值的总数除以图片中的像素总数。这个计算被称为“概率密度函数”计算,显示在 pdf 中。
- **“累积密度函数”**对概率密度函数值进行计算。通过将每个灰度值的概率值与先前值相加来计算累积密度值,由 cdf 表示。
- 将每个灰度值的累积值乘以最高灰度值,并四舍五入到最接近的整数值,计算出“累积分布函数”**,用 CDF 表示。**
此时,相对于灰度值获得的值与源灰度值交换,并且像素值通过新图片的平衡直方图获得。下面我们看到一个简单的 3x3 图片矩阵,其中所有这些步骤都是一个接一个地完成的,以及如何获得平衡的图片。

“作者提供的图像”
图 11 显示了我们的初始图像及其直方图,随后是输出(均衡)图像直方图和图像。您可以从直方图 _ 处理链接访问相关的 Matlab 代码,用您自己的样本矩阵或图片进行实验,并检查这些步骤的功能。在这个链接的代码中,有函数包含了我准备的所有这些步骤。我建议你检查一下这些函数。除此之外,使用 MATLAB 的函数,均衡是在同一张图片上完成的,结果在屏幕上显示为图片+直方图。您可以检查两种使用方式。

“作者提供的图像”
直方图匹配
如果图片的直方图要比作目标图片的直方图,而不仅仅是平衡,这就是直方图匹配。直方图匹配与直方图均衡的主要区别在于,在对两幅图片进行 pdf 和 cdf 计算后,不是将源图片的 cdf 值乘以最高灰度值,而是与目标图片的 cdf 中最接近的值进行匹配。然后,查看该值来自哪个灰度级值,并将该值与源图像中的灰度级值进行交换。
下面我们看到一个简单的 3x3 图片矩阵,所有这些步骤一个接一个地完成,匹配的图片就获得了。

“作者提供的图像”
图 12 显示了我们的直方图匹配应用程序得到的匹配图片和直方图,其中 bird.jpg 是源图片,pepper.jpg 是目标图片。(每个直方图属于上面显示的图像)

图 12“作者图片”
您可以从 histogram_processing 链接访问相关的 Matlab 代码,用您自己的样本矩阵和图片进行实验,并检查这些步骤的功能。在这个链接的代码中,有函数包含了我准备的所有这些步骤。我建议你检查一下这些函数。但除此之外,使用 Matlab 的功能,对同一张图片进行匹配,结果在屏幕上显示为图片+直方图。您可以检查两种使用方式。
恭喜你!您已经完成了图像处理第 1 部分。下一节再见!对于你的问题和建议,你可以从 aktas.yagmur@gmail.com 的联系我,也可以从这个 github 链接 下载源代码。
在本帖中,用于观察不同手术结果的源图像取自 unsplash.com
图像处理和最佳运输

图 1:伊比沙岛的日落?作者照片
一个计算机视觉领域的数据科学家和一个在海边玩铲子的孩子有什么共同点?两者都在某个点传输质量。
对孩子来说,这一团显然是沙子,但数据科学家呢?如果我们把一幅画的颜色看作质量,颜色的变化就对应着质量的转移。
看似牵强的类比通过最优运输领域联系起来,当看一个例子时更有意义。
假设我们有两张图片,想要将第二张图片的颜色更改为第一张图片的颜色,同时保持相同的结构。(例如,在图 1 中我们可以看到图 2 中右图的颜色被转移到了左图中。)这可能是必要的,以使图像具有可比性或协调一天中不同时间拍摄的照片的颜色。
我们如何做到这一点?我们怎么知道哪种颜色的将传输到哪个像素?在我们能够理解这个过程并看到 Python 中的实现之前,我们先来看看一些基础理论。

图 2:白天(左)和日落(右)的伊比沙岛海岸,作者拍摄的照片
最佳运输
想象一下,我们有一堆含有 n 颗沙粒的沙子,想要用它来建造一座沙堡。我们可能想知道这需要多少努力。所以我们从计算每粒沙子需要移动的距离开始(因为这是我们建造沙堡时要做的,对吗?)。例如,我们可能有一堆图 3 中蓝色物质形式的沙子,并想把它做成红色物质的形状。

图 3:同样质量的“一堆堆沙子”;作者图片
我们有很多方法来运输质量,我们希望能有效地做到。如果 xₖ 是第颗到第颗沙粒移动前的位置,而 yₖ = T(xₖ) 是其最终位置,则总距离可计算为

当然,我们可能不仅对距离感兴趣,还对时间或任何其他我们在计算交通费用时要考虑的因素感兴趣。因此,与其计算总距离 D(T) ,我们可能想计算总成本 C(T) 定义为

对于文献中的一些代价函数 c. ,函数 T 常被称为运输图。如前所述,有很多方法可以移动物体,所以我们想找到一个最小化 C(T)的运输图。数学上,我们想求T * = arg min { C(T):T transport map }。
注:在文献中,优化问题往往是用可分度量空间 X,Y 上的测度来定义的,其中 T 从 X 映射到 Y,不清楚是否存在一个最优映射 T以及它是否唯一。可以证明它确实存在,并且在某些假设下可能是唯一的。此外,如果 c 是度量的第 p 阶矩,则关于最优映射的成本的第 p 个根被称为瓦瑟斯坦距离。这个理论在数学上很好,但是我们不要去* 那里 …
在这一点上,你可能想知道为什么我们引入了成本函数 c 并使事情变得更加困难。成本函数是至关重要的,并且取决于应用。让我们考虑一个例子,看看它为什么重要。
假设我们有一个书架,上面有 6 本书,如下图所示,我们想把书移到书架的最右边。我们有两种选择来“运输质量”。我们可以将书 A 向右移动六个位置,或者将每本书向右移动一个位置。什么叫多劳多得?你喜欢移动书 A 还是所有的书?答案可能因个人偏好而异,因为每个人可能有不同的成本函数。

图 4:放有 6 本书的书架;作者图片
“那么,”你可能会想,“对于给定的成本函数,我们如何找到最佳映射 T* ?”
坏消息是没有简单的答案,这个问题是运输理论领域的研究课题。
好消息是,对于某些情况,我们知道答案。
一维成本函数的常见选择是绝对值(在这种情况下等于欧几里德距离)。让我们假设一下,在图 3 的例子中,运输成本仅取决于 x- 轴上的距离,也就是说,我们仅在一个维度上运输质量,并且将绝对值作为成本函数 c 。在这个例子中,最佳映射 T* 存在,但不是唯一的。也许最简单和最直观的传输图是从沙堆的最右边开始,移动到目标的最右边,重复这个过程,直到我们移动了整个质量。(对于图 4 中的图书,这意味着我们首先移动图书 F,然后移动到图书 a)。这定义了相对于绝对值的最佳运输图。
图像处理
“好吧,好吧,我们有所有这些运输理论的东西,但它是如何从一开始就与图片联系在一起的?”
很高兴你这么问,让我解释一下。

图 5:作者拍摄的深色(左)和亮色(右)的伊比沙岛海岸
为了简单起见,我们只考虑灰度图像。在这种情况下,只有 0 到 255 之间的强度与每个像素相关联,这使得它是一维的(与三维 RGB 表示相反)。我们可能得到了图 5 中的两张图片,并希望将图片从左边的图片传输到右边的图片。帮助的运输理论怎么样?
在高层次上,我们希望尽可能少地改变颜色以保持结构。我们可以获取强度,并用两张图片中的颜色制作直方图,如图 6 所示。瞧,我们有如图 3 所示的质量堆,可以像以前一样继续执行策略。

图 6:图 5 中图片的像素强度的直方图;x 轴:强度,y 轴:像素数量;作者图片
“将强度从左直方图移动到右直方图”的结果是:

图 7:图 5 中左图的形状和右图的颜色;作者图片
履行
很好。现在我们已经讨论了(一小部分)理论,但是我们怎样才能真正的传输颜色呢?最简单的方法是使用 Python 的最优传输包。但是现在我们已经通过了理论,所以我们不想要最简单的方法,而是从我们自己之前实现最优映射。
因此,首先,我们需要加载包和图片,并将后者转换为 numpy 数组(因为我们处理灰度图像,所以我们只需要 RGB 表示的一维—因此[:,:,0])。
接下来,我们定义两个辅助函数 pic_as_list 和 transport_colors 。第一个函数将 numpy 数组转换为按亮度排序的像素列表[x,y,color],其中 x 和 y 是坐标,color 是给定像素的亮度。第二个函数用上面例子中的最佳映射“传输颜色”。
最后,我们将所有内容放在一起并保存结果。结果如图 7 所示。
RGB 图像
在一维中定义一个最佳映射很容易,在三维中定义它就不那么简单了,而且肯定更具技术性。然而,作为第一近似值,我们可以使用前面的程序分别应用于红色、绿色和蓝色。结果并不是一张与目标颜色相同的图片(因为我们考虑的是颜色的一维边缘分布而不是三维联合分布),但这是一个很好的近似。
以下代码的输入是图 2 中的照片,其输出是图 1 中的图片。
结论
最优运输是一个迷人的话题,我们只讨论了最基本的内容。如果你想进一步了解它以及它在统计学中的应用,我可以推荐《瓦瑟斯坦空间的统计邀请》。
如果你想看更多的图片和彩色照片的(适当的)最佳传输结果,请查看“切片和氡瓦瑟斯坦测量重心”(特别是图 14 令人惊讶)。
图像处理和像素操作:照片滤镜

我们一直在使用滤镜,从 Instagram 和 Snapchat 这样的社交媒体应用程序到 Photoshop 这样的专业软件。但是你有没有想过过滤器是如何工作的?在本文中,我将尝试回答这个问题,并用 Python 实现基本的过滤器。
像素和色彩空间

迈克尔·马森在 Unsplash 上的照片
图像以数字形式存储为像素的阵列。像素(图片元素的简称)是图像的最小元素。每个像素都有一种颜色,定义颜色的方式称为颜色空间。
最常用的色彩空间是 RGB 色彩空间。在该模型中,每种颜色都由三个值定义,一个值代表红色,一个值代表绿色,一个值代表蓝色。通常,这些值是 8 位无符号整数(范围为 0–255),这被称为色深。颜色 (0,0,0) 为黑色, (0,255,0) 为绿色, (127,0,127) 为紫色。
打开图像
用 python 导入图像非常简单。为此,我们将使用一个名为Pillow的模块。我们用pip install **pillow** 安装。
要访问图像的像素,我们有两个选项。
过滤
滤镜是数学函数,它将图像作为输入,并将新图像作为输出返回。
可以在像素级应用滤波器,即 (x,y) 处的输出像素仅取决于 (x,y) 处的输入像素。或者在全局水平上,其中某个像素的输出取决于多个输入像素。
滤镜也可以应用于通道级别,即分别应用于每个颜色通道(红色、绿色和蓝色)。例如,红色通道的输出只取决于红色通道的输入。
我们将通过创建与输入图像尺寸相同的新图像来应用过滤器,然后编辑其像素。
从现在开始,过滤器将由过滤器魔法部分定义。
我们将介绍以下过滤器:
- 灰度
- 亮度
- 对比
- 饱和度
- 伽马校正
- 模糊
以下示例中使用的图片来自 Marvz Etcoban
灰度等级
将图像转换成灰度非常简单。我们需要将三个颜色值转换成一个颜色值。我们可以通过取它们的平均值(也称为亮度)来做到这一点。

然后,我们将把这个值放在新图像的红色、绿色和蓝色中。但是,我们必须确保新值仍然是整数,所以我们将使用函数int()。


原稿(左),灰度(右)
另一种方法是对红色、绿色和蓝色值进行加权求和。

其中 α₁ 、 α₂ 、 α₃ 为正常数,这样 α₁ + α₂ + α₃ = 1 。
这些值通常被选择为(YUV 颜色编码系统):

为了获得准确的灰度,我们必须考虑伽马校正,但我们将在稍后讨论。
聪明
像素 μ 的亮度是 0 到 255 之间的值。定义如下:

要将 μ 增加δμ,我们可以将每种颜色增加δμ。

但是我们必须确保这些值仍然在 0 到 255 之间,使用的函数如下:
亮度增加量δμ在-255 和 255 之间,负值表示图像变暗,正值表示图像变亮。



-64 亮度(左)、+0 亮度(中)和+64 亮度(左)
对比
图像 C 的对比度定义如下:

对比度值为 0 意味着所有像素具有相同的亮度值。 1 的对比度值表示最高亮度和最低亮度之差为 255(最大差值)。
有许多方法可以用来增加或减少对比度。一种方法是将亮度值从 128(0–255 的中间值)展开,使用公式:

其中 μ* 是整个图像的平均亮度。该公式扩展或缩放 μ* 周围的亮度值,因子 α 大于或等于 0 。
一个因子 α=1 什么都不改变, α < 1 降低对比度, α > 1 增加对比度。
α 没有上限可能不切实际。为了克服这一点,我们使用-1 和 1 之间的系数 β ,即:

这意味着当 β 从-1 变为 1 时, α 从 0 变为无穷大。而对于 β=0 , α=1 。

如果我们希望它在-255 和 255 之间,我们可以用 β/255 代替 β ,这产生了下面的公式:

我们必须确保隔离出 β=255 的情况,因为我们不能除以 0,在这种情况下,我们设置 α= ∞。



-100 对比度(左)、+0 对比度(中)和+100 对比度(右)
浸透
要改变像素的饱和度,可以考虑将 RGB 颜色转换成 HSV/HSL (其中 S 代表饱和度),改变饱和度值,然后转换回 RGB 。虽然这种方法是精确的,但是它是复杂的并且计算量很大。我提出一个更简单(不太准确)的方法。
改变饱和度不应该影响颜色的亮度。因此,如果我们将颜色值分散在它们的亮度周围(它们的 rgb 分量的平均值,不同于在整个图像上拍摄的对比度平均值),这不会改变它。



-100 饱和度(左)、+0 饱和度(中)和+100 饱和度(右)
伽马校正
人类对光的感知不是线性的。我们往往对深色比浅色更敏感。通过储存伽玛编码值而不是原始值,伽玛校正被设定为给阴影分配比高光更多的位。我们不会详细讨论如何节省空间,但我们将实现一个伽马校正滤波器。
伽马滤波器是这样操作的:

其中指数γ(γ)是正实数。 𝑥 是一个介于 0 和 1 之间的数字。
对于 γ > 1 低值被挤压,高值被拉伸,反之亦然对于 γ < 1。

如果我们将颜色值除以 255,我们得到的数字在 0 和 1 之间。然后应用 gamma 后,我们可以乘以 255。因此,应用伽马滤波器是这样的操作:







伽玛值(从左到右,从上到下)0.33,0.66,1.00,1.33,1.66 和 2
虚化
模糊滤镜有很多种。最常见的是方框模糊和高斯模糊。我们将实现框模糊。
框模糊包括将某个像素周围的某个大小(称为内核大小)的正方形内的像素的平均颜色作为该像素的颜色值。
我们不能在靠近边缘的像素周围画一个正方形,所以我们将取它周围像素的平均值(在图像边界内)。



11 模糊(左),21 模糊(中),71 模糊(右)
结论
这些滤波器可以同时串联(一个滤波器的输出是另一个滤波器的输入)或并联(输出是两个滤波器输出的加权和)使用,以产生更复杂的滤波器。
本文中没有提到的涉及像素操作的其他滤镜包括:
- 级别
- 曲线
- 乌贼的墨
- 色彩平衡(包括白平衡)
- 振动
- 暴露
- 磨刀
由于计算的原因,Python 中通常不实现基于像素操作的过滤器。它们是用 C/C++这样的语言实现的,在这种语言中对数据结构有更多的控制,或者用 GPU 着色器来实现最佳速度(实时)。
图像处理—斑点检测
找到联系!

作者图片
图像处理主要用于提取图像中的不同特征。由于数字图像包含不同的对象和信息,显然这种信息是从这样的图像中提取的。
为了做到这一点,我们可以执行图像处理技术来挑选和检测这样的特征和对象。最有前途的技术之一被称为斑点检测。
在某种意义上,一个斑点是任何被认为是一个大的物体或任何黑暗背景中的明亮物体,在图像中,我们可以将其概括为一组像素值,形成一个群体或一个大的物体,可以从其背景中区分出来。使用图像处理,我们可以检测图像中的这种斑点。
Scikit-Image 具有不同的功能,可用于显示图像中的不同斑点。让我们逐一看一下。
让我们首先加载示例图像:
import numpy as np
from skimage.io import imshow, imread
from skimage.color import rgb2gray
import matplotlib.pyplot as pltsample = imread('flowers2.png')
sample_g = rgb2gray(sample)fig, ax = plt.subplots(1,2,figsize=(10,5))
ax[0].imshow(sample)
ax[1].imshow(sample_g,cmap='gray')
ax[0].set_title('Colored Image',fontsize=15)
ax[1].set_title('Grayscale Image',fontsize=15)
plt.show()

图 1:样本图像(作者提供的图像)
现在,让我们将图像二值化:
fig, ax = plt.subplots(1,3,figsize=(15,5))sample_b = sample_g > 0.6ax[0].set_title('Grayscale Image',fontsize=20)
ax[0].imshow(sample_g,cmap='gray')ax[1].plot(sample_g[600])
ax[1].set_ylabel('Pixel Value')
ax[1].set_xlabel('Width of Picture')
ax[1].set_title('Plot of 1 Line',fontsize=15)ax[2].set_title('Binarized Image',fontsize=15)
ax[2].imshow(sample_b,cmap='gray')

图 2:二值化图像(作者提供的图像)
对于二值化,我们在像素值上使用阈值 0.6,因为我们可以在样本绘图线上看到在该值内有一个分离。
我们通常在斑点检测之前二值化我们的图像,因为它更容易处理较小的维度和归一化的值。
scikit-image 中有一些漂亮的函数,可以使用不同的方法来检测图像中的斑点,其中一些如下:
拉普拉斯高斯(LOG)
使用高斯拉普拉斯方法确定斑点
from skimage.feature import blob_dog, blob_log, blob_dohfig, ax = plt.subplots(1,2,figsize=(10,5))
ax[0].set_title('Binarized Image',fontsize=15)
ax[0].imshow(sample_g,cmap='gray')blobs = blob_log(sample_b, max_sigma=30, threshold=0.01)ax[1].imshow(sample_b, cmap='gray')
for blob in blobs:
y, x, area = blob
ax[1].add_patch(plt.Circle((x, y), area*np.sqrt(2), color='r',
fill=False))
ax[1].set_title('Using LOG',fontsize=15)
plt.tight_layout()
plt.show()

图 3:使用日志(图片由作者提供)
请注意,我们能够检测到图像中的圆形斑点,即使是最小的斑点也能检测到。
高斯差分(DOG)
通过使用两个高斯平滑图像的差异来确定斑点
代码和上面的一样,只是通过使用 scikit-image 中的直接函数 blob_dog 对 blobs 变量进行了修改。
blobs = blob_dog(sample_b, max_sigma=30, threshold=0.01)

图 4:使用狗(图片来自作者)
将 DOG 的结果与 LOG 进行比较,我们可以看到 DOG 方法可以检测到大得多的斑点,并且与 LOG 相比,斑点的中心坐标更加居中。
黑森行列式
通过使用 Hessian 行列式的矩阵中的最大值来确定 bob。
blobs = blob_doh(sample_b, max_sigma=30, threshold=0.01)

图 5:使用 DOH(图片由作者提供)
将 DOH 的结果与 DOG 和 LOG 进行比较,与 DOG 不同,DOH 的斑点坐标更加集中在圆形地面真实斑点的边缘,并且与 LOG 相比,DOH 检测到更多的小斑点。
连接组件(标签)
处理斑点检测的另一种方法是通过使用图像中的连通分量。使用这种方法,我们可以很容易地检测到所有的形状。
要做到这一点,我们需要确保我们的二值化图像有点干净。我们可以对二值化图像执行形态学操作,以轻松清理它。以下示例:
sample_c = im_cleaned = multi_ero(multi_dil(sample_b,5),5)

图 6:变形图像(作者提供的图像)
请注意,当我们使用膨胀效果时,我们能够使白玫瑰更加完整。这将使我们的标签更容易和最小化。
scikit-image 库中已经有一个漂亮的函数,可以用来标记变形的图像。示例代码和图形如下:
sample_l = label(sample_c)

图 7:标记图片(作者图片)
请注意,在标记的图像上,我们能够标记每个斑点。所有的小斑点实际上也被标记了。我们可以通过执行以下操作来检查检测到了多少斑点:
sample_rp=regionprops(sample_l)print('How many Blobs detected?:', len(sample_rp))
检测到多少斑点?: 1541
我们能够检测到几乎一千个斑点,这是因为标签函数也将标记甚至最小的斑点,因为它只需要满足一个条件,如果它是与其周围不同的像素值。如果它有相同的像素值,那么它将把它连接到整个组,形成一个更大的斑点。
我们可以使用函数 region props 来进一步分割斑点,下面是一些代码示例:
list1 = []
for x in sample_rp:
list1.append(x.area)
list2 = sorted(list(enumerate(list1)),key=lambda x: x[1], reverse=True)[:7]
fig, ax = plt.subplots(1,4,figsize=(15,10))
ax[0].imshow(sample_l)
ax[0].set_title('Labelled Image',fontsize=15)
for x,y in enumerate(list2[:3]):
ax[x+1].imshow(sample_rp[y[0]].image)
ax[x+1].set_title('Biggest Blob'+str(x+1))

图 8:图像的区域化(作者的图像)
请注意,我们能够通过使用连接组件的像素区域来分割图像中最大的 3 个斑点。
摘要
在本文中,我们能够展示不同种类的函数用于圆形斑点检测,它们是 LoG、DoG 和 DoH。我们还能够看到连接的组件如何能够更容易地简化斑点检测,最后,我们能够通过使用区域道具来分割图像上看到的斑点。
敬请期待下一篇文章!
图像处理:图像缩放算法
了解不同的图像缩放方法并在 Python 中实现它们

照片由 Onur Binay 在 Unsplash 上拍摄
图像缩放是图像处理的重要组成部分。由于多种原因,图像需要放大或缩小。在本文中,我们将研究不同的图像缩放方法,并用 Python 实现它们。
本文是讨论图像处理概念的系列文章的一部分。查看以前的文章:
我们将假设我们有一个分辨率为width×height的图像,我们希望将它的大小调整为new_width×new_height。首先,我们将引入比例因子scale_x和scale_y,定义如下:
scale_x = new_width / width
scale_y = new_height / height
比例因子 < 1 表示缩小,比例因子 > 1 表示拉伸。
我们必须将图像导入到一个数组中,为此我将使用PIL。
从现在开始,我只说magic部分。
我们将以这两张照片(第一张由来自 Unsplash 的John-mark Smith拍摄,第二张由来自 Pexels 的hiếu·洪拍摄)为例。原始图片的分辨率为1920×1080(~ 200 万像素)。
我们将这些图像缩小到500×281并放大到7373×4147,比例因子为1/3.84和3.84。重要的是不要使用漂亮的数字,如比例因子 2、4、0.5,因为它们可能是边缘情况,算法给出的有吸引力的结果不能反映一般情况。

约翰-马克·史密斯照片来自 Unsplash

最近邻插值
这可能是最简单的图像缩放方法。每个输出像素都被其在输入中最近的像素替换。
在一维中, x 处的值是其最近点的值。

蓝线是红点的最近邻插值。图片作者。
在 2D,输出图像中具有坐标 (x,y) 的像素在输入图像中具有坐标 (x/scale_x,y/scale_y) 。由于这些坐标并不总是存在(如果它们有小数部分),我们将把坐标四舍五入到最接近的整数,从而四舍五入到最近的邻居。

绿色像素与其最近的邻居近似。图片作者。
这种方法的实现如下所示:
结果:


图片由来自 Unsplash 的约翰-马克·史密斯按比例缩小(左)和放大(右)的照片


来自 Pexels 的hiếu·洪按比例缩小(左)和放大(右)的图片
相反,你可以使用PILLOW的resize方法:
im.resize(size, Image.NEAREST)
双线性插值
线性插值(又名 lerp )相当于在每两个连续点之间画一条线。这也可以认为是取相邻点的平均值,这些点按它们的距离加权。

蓝线是红点的线性插值。图片作者。
在二维中,这种插值涉及 4 个相邻点。对顶部 2 点( Q11 和 Q12 )和底部 2 点( Q21 和 Q22 )进行线性插值,得到两个新点( P1 和 P2 )。然后,对新点进行线性插值,得到插值点 P 。

绿点是红点的双线性插值。图片作者。
用 python 实现这一点:


来自 Unsplash 的约翰-马克·史密斯按比例缩小(左)和放大(右)的照片


来自 Pexels 的hiếu·洪按比例缩小(左)和放大(右)的照片
相反,您可以使用PILLOW的resize方法:
im.resize(size, Image.BILINEAR)
双三次插值
双三次插值的一维等价物是三次样条插值。
三次样条插值绘制平滑曲线,而不是在点之间绘制直线。这些平滑曲线是三次多项式。

三次样条插值需要 4 个相邻点。并且在以下条件下计算 4 个系数:
- 两个内部相邻点的导数是内部相邻点和外部相邻点之间的梯度。
- 内部邻居必须是曲线的一部分。

4 个点的双三次插值示例。图片作者。
通过计算导数并将其设置为等于梯度,以及通过在内部相邻点评估三次公式,可以将条件转化为公式。
我们可以使用scipy的interpolate.**CubicSpline**函数来计算。

蓝线是红点的双三次插值。图片作者。
在 2D,这将涉及 16 分。我们将对 4 行点执行三次样条插值,然后对新的 4 个插值点执行最后一次三次样条插值:

维基媒体 Cmglee 的双三次插值图
高效地实现这种算法是非常困难的。相反,你可以使用PILLOW的resize方法:
im.resize(size, Image.BICUBIC)


图片缩小(左)和放大(右)照片由约翰-马克·史密斯从 Unsplash 拍摄


箱式取样

图片作者。
这种方法专门用于缩小规模。当使用上述算法之一进行缩小时,一些像素被完全忽略。这导致缩减质量很差。为了克服这一点,框采样将缩小图像中的每个像素都视为原始图像中的一个框。它的颜色是盒子内部颜色的平均值。


使用箱式采样缩小图像。约翰-马克·史密斯从 Unsplash 拍摄(左)hiếu·洪从 Pexels 拍摄(右)
相反,你可以使用PILLOW的resize方法:
im.resize(size, Image.BOX)
还有其他图像缩放算法,如【Sinc】和 Lanczos 重采样或基于深度卷积神经网络的算法,但它们稍微复杂一些。
图像缩放是图像处理的重要组成部分。在本文中,我只讨论了简单的图像缩放算法及其背后的原理。实时视频缩放算法通常在硬件(GPU)上完成,以获得更快的性能。
图像处理— OpenCV 与 PIL
辅导的
利用 Python 库从图像中提取信息

莫琳·斯格罗在 Unsplash 上的照片
数据分析师的工作不仅限于处理现成的数据,有时还需要从图像中挖掘数据。
这个故事是关于使用 Python 的不同类型的图像特征提取。此外,在这里你会发现两个图像处理库的 速度 的经典对比——OpenCV 和 PIL
____ OpenCV 比 PIL 快 1.4 倍 ___
📌想跟着去吗?这里是我的Jupyter——笔记本 。
📌更多的资源总是与💡
在我最近的项目中,我每天需要处理 10000 多张图像,并从中提取数据。也只有 Python 能帮我做这个批量处理。我通常对图像做以下处理:
1。提取图像的纵横比。
2。裁剪图像。
3。将图像更改为灰度图像。
4。图像旋转。然而,我的工作不止于此。我做更多的特别分析。
在我的项目中,很多信息也隐藏在图像名称中。这里讨论的字符串操作方法在这样的任务中非常方便。
[## Python 中五个必须知道的字符串方法
towardsdatascience.com](/five-must-know-string-methods-in-python-e97925d12eec)
什么是图像?
图像只是一个像素矩阵,每个像素是一个正方形的彩色光点。这可以用灰度图像很快解释。灰度图像是每个像素代表不同灰度的图像。

作为灰度值数组的图像|作者提供的图像
上图中,左边的原图实际上是一种灰色的深浅不同的分布。在右侧,在像素灰度值阵列中,值 0 代表黑色,值 255 代表白色。0 到 255 之间的所有值代表不同的灰色阴影。关于这方面的进一步信息可以在这里找到 。
OpenCV Vs PIL
一次随机的谷歌搜索🔍对于 Python 中的图像处理,指向这些常用的图像处理库。

Python 中的图像处理库|作者提供的图像
OpenCV 是用 C 和 C++写的,而 PIL 是用 Python 和 C 写的,因此仅从这些信息来看,OpenCV 似乎更快。在处理 1000 幅图像进行数据提取时,处理速度🚀事关重大。下面是这两个库的快速比较。

OpenCV 和 PIL 的区别|图片由作者提供
我主要使用 OpenCV 来完成我的任务,因为我发现它比 PIL 快 1.4 倍。首先,让我一步一步地向你展示如何使用 OpenCV 和 PIL 来处理图像。
用 OpenCV 进行图像处理
首先安装 OpenCV Python 包,将包导入 Jupyter-Notebook 或者 Python IDE。
pip install opencv-python
import cv2
仅在首次使用时需要安装。
将图像读入变量
img = cv2.imread(path_to_the_image)
使用这个方法从 OpenCV 包中读取图像到变量img中。正如我前面提到的,OpenCV 默认读取 BGR 格式的图像。
什么是 BGR 和 RGB 格式?
两者代表相同的颜色(R)红,(G)绿,(B)蓝,但排列这些颜色区域的顺序不同。在 BGR,红色通道被认为是最不重要的,在 RGB 中,蓝色通道是最不重要的。

使用 Python 中的 OpenCV 阅读图片|作者图片
为了这篇文章,我将使用cvtColor方法将其转换为 RGB 格式。
裁剪图像
让我们裁剪图像,保持纵横比不变。因此具有相同纵横比的区域将从图像的中心被裁剪。
图像的纵横比是其宽度与高度的比率。它通常用冒号分隔的两个数字表示,如 width:height。比如 16:9。

使用 Python 中的 OpenCV 裁剪图像|作者提供的图像
在 OpenCV 中,图像是一个 NumPy 数组,并以与 NumPy 数组切片相同的方式裁剪图像。这就是为什么它比 PIL 的 快 8210 倍。
旋转图像
图像旋转在这里非常简单。OpenCV 中的方法rotate()允许图像以 90°的倍数旋转。

使用 Python | Image by Author 中的 OpenCV 旋转图像
💡更多关于rotate()方法的信息可以在 这里 找到。
将图像转换为灰度
**灰度图像是仅由不同灰度组成的图像,从黑色到白色不等。一幅 8 位图像有 256 种不同的灰度。这意味着图像的每个像素取 0 到 255 之间的值。再次使用方法cvtColor()将旋转后的图像转换为灰度。

使用 Python 中的 OpenCV 将图像转换为灰度|作者图片
💡所有的颜色转换代码都可以在 这里 找到。
用 PIL 进行图像处理
枕头是目前使用的图书馆,这是来自 PIL。
第一次使用,从包安装开始。然后将包导入 Jupyter-Notebook 或 Python IDE。
*pip install Pillow
from PIL import Image, ImageEnhance*
阅读图像
导入的图像模块有open()方法,在 PIL 读取图像时很方便。就像 OpenCV 一样,带有扩展名或完整路径的图像名可以传递给这个方法。

使用 PIL |作者图片阅读图片
使用 PIL 进行图像裁剪
同类图像有方法crop()裁剪图像。PIL 的图像裁剪与 OpenCV 略有不同。

使用 PIL |作者图片裁剪图片
Image.crop()接受一个元组作为输入。这个元组包括要裁剪的区域的左上角和右下角的坐标。
PIL 图像旋转
图像类还包括方法rotate()。而且与 OpenCV 不同的是,只有逆时针旋转的角度作为一个整数可以传递给这个方法。

使用 PIL |作者图片旋转图片
将图像转换为灰度
就像 PIL 的其他任务一样,图像格式转换也很简单。方法convert()返回转换后的图像。

使用 PIL 的灰度图像|作者图片
图像数据挖掘的程序化方法通常用于批量图像处理。又来了 速度 的游戏⏳。
我下载了 880 张图片🎁从谷歌和处理每张图片 10 次,以有足够的程序负荷,找出在 OpenCV 和 PIL 的图像处理所需的时间。

OpenCV 与 PIL 对比|作者图片
正如所料,与 PIL 相比,OpenCV 花费的时间更少,即 OpenCV 比 PIL 更快。
💡关于 OpenCV 中如何进行图像处理的理论解释可以在这里找到 。OpenCV 的妙处在于,你可以在它的网站上看到无数用 Python、C++和 Java 编写的例子。**
同样,更多关于 PIL 的信息可以在这里https://pillow.readthedocs.io/en/stable/。💡**
这只是图像处理中最常用的两个 Python 包的概述,以及它们如何以不同的方式返回相同的结果。我希望你喜欢并从这本书中学到一些东西。
通常对于我的工作,我发现 OpenCV 更可靠和更快,然而,根据不同的图像处理,反过来也是可能的。
正如我常说的,我乐于接受建设性的反馈和通过 LinkedIn 分享知识。
感谢您的时间和阅读!
📚在这里看看我关于数据分析的其他文章。
- 用 Power BI 可视化 SQLite 数据
- Python 中的标签编码器和 OneHot 编码器
- 使用 Python 的 SQL 数据库
- 网页抓取—制作您自己的数据集
- 假设检验& p 值
- 加入表格
如何看完所有的中篇文章?
立即成为媒体会员&获得⚡ 无限制 ⚡访问所有媒体故事的权限。
当你在这里注册并选择成为付费媒介会员,我会从你的会员费中获得一部分作为奖励。
图像处理综合指南:第 2 部分
图像处理要领
从线性(相关和卷积)和非线性空间滤波到用于平滑、锐化、噪声去除和边缘检测的特殊核
第 2.1 部分
空间操作直接在给定图像的像素上执行,我们将这些操作分为三类。“ 空间域操作”是这个题目你能遇到的另一个词,这些是同一个术语!
- 单像素操作
- 邻域操作
- 几何变换

图 1“作者图片”
我们已经在 图像处理第一部分 中看到了什么是单像素操作以及如何将它们应用于图像。在这篇文章中,我将解释“邻域运算是什么意思以及如何应用它们。
因此,到目前为止,我们已经了解了应用于图像的逐像素变换(单像素操作)。与单像素操作不同, “邻域操作” 代表应用于一组像素的操作,而不是 1 乘 1。为此,我们使用了一个 【滤波器】 ,它是一个给定大小的窗口,我们通过应用所需的操作,使用这个滤波器遍历图像。

“作者提供的图像”
!!!因此,我们可以将邻域操作定义为具有 1 个过滤器(一个 窗口,另一个单词的内核或遮罩 )和 1 个我们选择的特定操作的操作。
如图 1 所示,我们可以将邻域操作分为非线性过滤和线性过滤。让我们从非线性的开始!
非线性空间滤波
如果我们选择一种非线性操作来应用我们的滤波,则该操作被称为非线性空间滤波。让我们检查各种非线性空间滤波类型。
最小滤波
当我们使用我们的过滤器遍历图像时,我们在停留在该过滤器中的像素中选择具有最小值的像素,并将该像素写入我们的输出图像。考虑到我们有一个 9x9 的输入图像和一个 3x3 的过滤器,我们将使用它来应用最小过滤,我们将有以下输出图像:


“作者提供的图像”
输出图像与输入图像大小不同,对吗?在我们想要输出尺寸完全相同的图像的情况下,我们应该应用 填充。
填充
填充是在输入图像上添加额外像素的过程,以保持输出图像的大小与输入图像相同。最常见的填充技术是添加零(其被称为 零填充 )。为了达到正确的大小,我们需要添加( filter_size — 1) / 2 额外的像素到我们的边界,如下所示(所以我们为每个边界添加 3–2 = 1 个额外的像素,并获得 2 个额外的列和 2 个额外的行)

“作者提供的图像”
最大过滤
与最小过滤的唯一区别是,对于最大过滤,我们在窗口的像素中取最大值并将其传递给输出图像。我们可以很容易地再次使用 3x3 窗口获得相同输入图像的以下结果。

“作者提供的图像”
中值滤波
另一种类型的非线性空间滤波是中值滤波,其中像素以升序排序,并且选择中间像素。对于我们的示例情况(具有 0 填充的 9x9 输入图像),我们获得的第一个窗口是{0,0,0,0,0,0,0,10},因此它已经被排序,并且选择 0 作为输出图像的第一个像素。我们获得的第二个窗口是{0,0,0,0,20,10,12},当我们对它排序时,我们获得{0,0,0,0,10,12,20},因此我们输出图像的第二个像素又是 0 。让我们继续我们获得的更多窗口:
第三个窗口:{0,0,0,2,5,10,12,15},这为输出图像的第三个像素提供了 2 。
第四个窗口:{0,0,2,5,50,12,25,15} - >顺序- > {0,0,0,2,5,12,15,25,50} - >拾取中间的像素- > 5
第五个窗口:{0,0,0,5,50,50,25,15,10} - >顺序- >
最后,我们获得以下输出:

“作者提供的图像”
实施
我准备了一个 python 代码,你可以更近距离地处理图像,并进行这些操作。
- 你可以创建一个你选择的 2D 列表,用任何填充和内核大小(过滤器大小)尝试上述操作,然后检查结果!
- 您可以使用 OpenCV 读取图像,如下例所示,将其转换为 python 列表结构,并以相同的方式将其用于这些函数。
- 同时,使用 Python 的 PIL 库,用几行代码就可以完成这些操作!
让我们来看看来自 Test_2D_list()的一些 2D 数组结果,其中我将这些函数应用于我们的示例案例:

最小过滤“按作者分类的图像”

最大过滤“按作者分类的图像”

中值滤波“按作者分类的图像”
现在让我们对一幅真实的灰度图像使用这些函数,看看 Test_Image()函数的输出:

“作者提供的图像”
最后,让我们看看 PIL 库函数如何调用 PIL 比较()函数

“作者提供的图像”
我实现的结果和 PIL 库函数看起来很相似!🎯﹡
你有没有意识到最小滤波使输出图像变暗,而最大滤波使输出图像变亮?因为正如我们在 图像处理 Part1 中提到的,较小值的像素接近黑色,较高值的像素接近白色。此外,中值滤波器通常用于去除噪声而不是改变图像的强度。看看下面的输入图像,我在上面添加了一些椒盐噪声和最小、最大、中值滤波器的效果

“作者提供的图像”

“作者提供的图像”
我们看到最小和最大滤波器对噪声没有影响,实际上它们对有噪声的图像效果很差,而中值滤波器成功地去除了噪声。
最后一点,你应该知道我们在这篇文章中使用的 PIL 图像处理函数在默认情况下使用了零T21 填充。所以输出图像的大小不会改变,你只需要给出你想要使用的图像和内核大小。
你可以从那个 github 链接到达完整的源代码。我们将在第 2.2 部分中继续线性空间滤波
第 2.2 部分
在图像处理部分 2.1 中了解非线性空间滤波技术后,现在是时候检查线性空间滤波技术了。
我们将仍然使用相同的过滤方法(所以我们仍然有一个过滤器/内核/窗口用于线性空间过滤)但是我们将改变我们应用的操作类型。
相关性
相关地,我们有一个“加权滤波器”,此时我们在滤波像素中有值,我们将它们与图像中相应的像素相乘。我们对这些乘法求和,得到我们的一个输出像素!
让我们想象一下这个操作:

“作者提供的图像”
卷积
这是与相关相同的过程,除了只有 1 个不同。这里,我们在应用相关之前将滤波器旋转 180°。因此我们得到以下结果:

“作者提供的图像”
检查下面的代码和 Test _ Correlation _ Convolution _ 2D()函数的输出,在这里我实现了卷积和相关运算,并把它们应用到我们的示例图像中,使用了与上面解释中相同的滤镜。请不要被整个代码弄糊涂了。下面会一步步讲解。目前,我们只做了 120 个。线条

“作者提供的图像”
!!!当你在谷歌上查找卷积和相关性时,你会遇到很多卷积被解释为相关性的情况,可能根本没有提到相关性。你应该知道这是一个巨大的用词不当,特别是用在神经网络卷积层。
现在是时候学习一些特定的过滤器用于卷积和相关操作,以处理我们的图像。当然,你可以创建自己的过滤器,做任何你想做的事情,但是你也需要学习一些常见类型的过滤器。基本上,我们将这些过滤器分为 4 组:
平滑(模糊)滤镜
平滑滤波器用于平滑图像。基本上,当我们将该滤波器应用于图像时,例如通过卷积运算,我们得到了输入图像的更平滑版本。
- 高斯滤波器(高斯低通滤波器)是一种基于高斯分布的常用平滑滤波器,高斯分布的公式如下,σ =标准差:

我们可以按照这个公式创建任意大小的高斯滤波器。σ = 1 的两个例子如下:

“作者提供的图像”

“作者提供的图像”
让我们看看使用这两个高斯滤波器和我们的 linear_filtering 函数的示例输出。对应代码中的“平滑”副标题

“作者提供的图像”
!!!作为一个预期的结果,随着更高的内核大小,我们获得了更突出的(更模糊的)结果。但是内核大小仍然彼此接近。使用 gkern 函数[1]已经在代码中找到并使用了几次,我们可以自动创建任何大小(实际上甚至是任何标准偏差!)来观察不同核大小和标准差的不同效果。
作为一个附加信息,你也可以使用 OpenCV 的 GaussianBlur 函数来做同样的工作,如代码中的“OpenCV 平滑”副标题所示
锐化滤镜
锐化滤波器用于锐化图像。基本上,当我们将该滤波器应用于图像时,例如通过卷积运算,我们得到输入图像更清晰的版本。
- 反锐化和高增强滤波是一种使用平滑滤波器来锐化图像的技术。是的,这也是一种常见的技术!我们从我们的输入图像中减去我们的输入图像的“平滑”版本,以获得“中间遮罩”。然后,我们将这个掩模乘以一个系数添加到我们的输入图像,以获得最终的输出图像。

对于模糊步骤,我们基本上可以使用高斯模糊(高斯平滑滤波器)。让我们来看看“反锐化掩模和高增强”部分代码的输出示例:

模糊核大小= 5,k= 1 的反锐化掩模和高增强过滤“作者的图像”

模糊核大小= 5,k= 2 的反锐化掩模和高增强过滤“作者提供的图像”
我们可以简单的认识到随着 增加 k ,锐化效果也随着增加。您可以使用 OpenCV 的函数实现反锐化和高增强过滤,如代码中的“OpenCV 反锐化掩模&高增强”部分所示
去噪滤镜
有时,我们需要处理有一些噪声的图像——不需要的像素。为了消除噪声,我们可以使用一个噪声消除滤波器。去噪的重要之处在于,这些滤镜同时具有模糊效果。因此,选择一个好的内核大小来消除一些噪声而不丢失图像中的所有细节是非常重要的。出于同样的原因,你可能会遇到噪声消除滤波器被用于模糊,它们可以被归类为不同来源的模糊滤波器。
无论如何,为此有 4 个特定的噪声消除滤波器,每个滤波器都能更好地处理特定类型的噪声。
- 最小滤波是我们在上面第 2.1 部分看到的非线性滤波类型之一。我不会再提这个话题了,但是你应该知道,那个滤波器对于盐噪声去除!
- 最大过滤是我们在上面第 2.1 部分中看到的非线性过滤类型之一。我不会再提这个话题了,但是你应该知道那个过滤器对去除胡椒噪声有好处!
- 中值滤波是我们在上面的第 2.1 部分看到的非线性滤波类型之一。我不会再提到这个话题,但你应该知道,该滤波器对去除椒盐噪声很好,而且它比均值滤波更好去除噪声,而不会使图像变得太模糊!
- 均值(平均)滤波最后是一个线性滤波器,用来去除噪声。目标是取停留在内核中的像素的平均值,并将该平均值作为输出像素。因此,我们可以通过使用以下公式来创建任何平均核:

“作者提供的图像”
基本上,对于 3×3 均值滤波器,我们有这样一个:

“作者提供的图像”
或者对于 5×5 均值滤波器:

“作者提供的图像”
让我们看看不同均值滤波器的不同输出。你可以在“噪声去除均值滤波器”部分找到相关代码。

“作者提供的图像”
我们可以看出,3x3 的过滤器不足以消除所有的噪音,25x25 的内核大小太大,导致图像中所有的细节甚至边缘都丢失了。9x9 内核大小选择似乎更适合这种情况!
边缘检测滤镜
对于图像的边缘,我们也有一些特定的过滤器。如果你想知道“什么是优势”,“什么是特征,以及我们如何在计算机视觉中使用它们”,请与我进一步的帖子保持联系!现在我们将直接用不同的过滤器来检测边缘。
- 一阶导数边缘检测滤波器
这些滤波器基于对图像进行一阶导数的思想。当您将图像视为信号时,只需在 x 或 y 方向上求导,这里我们有“离散”滤波器,它是通过在一个 n x n 矩阵中近似这一概念而获得的。
索贝尔内核
3x3 Sobel 核如下,其中 Gx 用于水平边缘检测,Gy 用于垂直边缘检测。

3x3 Sobel 核
我们再仔细看看 Gx 的结构:
中间一行由全零组成,其他行的中心像素有半行的和值,矩阵的上半部分由负值组成而下半部分由正值组成。同样的逻辑适用于只有方向差异的 Gy。
有了这个算法,你可以创建一个更大的 Sobel 核,如下所示:

5x5 Sobel 内核
Prewitt 内核
另一个基于一阶导数的核,与 Sobel 只有一个不同,如下所示:

3x3 Prewitt 内核
这里,我们有一行(在水平核中)或一列(在垂直核中)的中心像素,与相关行或列中的其他像素相同。所以,基本上你可以有一个 5x5 的 Prewitt 内核,如下所示:

5x5 Prewitt 内核
让我们看看这两种不同内核大小的内核类型的输出。您可以在代码中的“一阶导数边缘检测”部分找到相关实现:

“作者提供的图像”
结果似乎有太多的优势,对不对?看起来我们得到了一个充满噪声的输出图像,而不是检测所需的边缘。为了处理边缘检测核的强烈效果,在使用这些边缘检测核之前,我们需要模糊输入图像!

使用 5x5 高斯模糊内核模糊图像后检测到的边缘。“作者提供的图像”
我们看到了更好的结果,但是模糊处理对于 5x5 边缘检测内核是不够的。因此,我们可以理解,我们需要增加模糊内核的大小来处理它。
您可以使用 OpenCV 的函数来应用一阶导数核,并将 x 和 y 方向边的结果相加,以获得 1 个一般输出。Prewitt 内核没有实现,但是您可以应用它,如代码中的 First_Derivative_Opencv()函数所示。
作为一个额外的信息,你需要知道与 Sobel 相比,Prewitt 面具实现更简单,但对噪音非常敏感。
- 二阶导数 边缘检测滤波器
这些滤波器基于对图像进行二阶导数的想法。
拉普拉斯核
这是最常见的二阶导数内核之一,它使用以下公式来计算图像的二阶导数的梯度。

二阶导数的梯度
“二阶导数的梯度是指拉普拉斯混合已经由二阶导数检测到的 x 和 y 方向边缘。因此,我们不需要像上面那样对来自 Sobelx 和 Sobely 内核的图像进行求和和混合。
现在让我们深入这个公式,更好地了解对图像求二阶导数意味着什么:

二阶导数的展开


最终方程

拉普拉斯核模板“作者的图像”
因此,基本上我们可以使用这个拉普拉斯核模板来获得最常见的拉普拉斯核:

3x3 拉普拉斯核
还有另一个 2 拉普拉斯核是通过近似公式获得的,并且在实践中经常使用:

3x3 拉普拉斯核
让我们看看一些使用拉普拉斯核函数和 Edge_Detection()函数的输出示例,如“二阶导数边缘检测”部分所示。

用拉普拉斯核进行边缘检测
您也可以轻松使用 OpenCV 的拉普拉斯函数,如second derivative _ OpenCV()函数所示:
高斯核的拉普拉斯算子(LoG)
这只不过是一个包含“高斯模糊”和“拉普拉斯核”的核。所以你可以直接使用日志,如果你不想应用“模糊图像”“检测边缘”步骤分开,但都在一个。结合这两种滤波器的公式如下:

我不会深究将这个公式近似为离散核的细节,但是如果你搜索,你可以找到这个最常见的对数核,高斯标准差= 1.4

至于实现,如果你愿意,可以直接使用这个内核或者其他类型的日志内核。对于 OpenCV 端,没有 LoG 内核的实现,但是我们在上面通过 OpenCV 使用 after GaussianBlur 为拉普拉斯内核所做的工作是一样的!
外卖
- 在进行边缘检测之前,不要忘记模糊输入图像。选择一个合适的模糊内核和边缘检测内核大小适合你的目的。
- 你需要混合来自一阶导数核的 x 和 y 方向的检测边缘,但是通过使用拉普拉斯核,你已经获得了具有两者的一般图像。
- 你也可以使用噪声去除内核来模糊图像。但是反过来并不是一个好主意,因为当他们试图去除噪声时,他们有更大的风险去除图像中的重要特征
- Prewitt 更容易实现,但比 Sobel 更敏感。
- 中值滤波是去除噪声的较好选择,因为它既适用于椒盐噪声,又比其他滤波器更能去除噪声而不模糊图像。
您可以在我的 github 上找到完整的代码和示例图片,并使用不同大小、系数或标准偏差的不同内核进行自己的实验!图像处理第三部分见💃
[1]"来自https://stack overflow . com/questions/29731726/how-to-calculate-a-Gaussian-kernel-matrix-in-numpy的源代码"
源图像(狗和鸟)取自 unsplash.com,用于解释代码的实验。
图像处理综合指南:第 3 部分
图像处理要领
形态学算子及其在 OpenCV Python 中的使用
在本图像处理系列的倒数第二部分,我们将考察形态学图像处理。
形态学图像处理(或称形态学)描述了一系列图像处理技术处理图像中特征的形状(或形态学】。形态学操作通常用于去除分割过程中引入的缺陷,并且它们通常对二进制(其中图像的像素只能是 0 或 1)图像进行操作。
!!!阅读更多关于什么是特征,什么是图像分割等内容。,与我即将出版的刊物保持联系。
结构元素 是我们用于进行形态学运算的基本结构。结构元素的形状可以改变,这取决于我们在图像中寻找的形状类型。下面是 SE 及其矩形阵列形式的一些示例。(当然,我们需要将阿瑟转换成 2D 矩阵的形式,以便能够在编程中表现出来)

4 种不同的结构元素及其 2D 矩阵构成“作者的形象”
我们有 4 个主要的形态学操作:
- 侵蚀
- 扩张
- 开始
- 闭幕
侵蚀
在侵蚀中,结构元素在图像中穿行,当图像模式和结构元素模式完全匹配时,图像中结构元素的种子点处的像素变为 1,如果不匹配,则变为 0。注意侵蚀操作通常由 ⊖ 表示

“作者塑造的形象”
边界像素是基于窗口操作的常见问题,正如我们在图像处理 2 中看到的卷积和相关操作一样。
侵蚀中,当需要对结构元素种子 进行填充以应用于边界像素时,我们应用1-填充。所以我们在边界增加了 1 个值。它是在 MATLAB、OpenCV 或 Scipy 的内置函数中自动完成的。
侵蚀特性
- 侵蚀会移除对象边界上的像素。换句话说,它缩小****前景对象。
- 放大前景孔。
- 像在图像处理内核中一样,结构元素的尺寸越大,侵蚀的影响越大。
- 当然,不同的结构元素在相同的输入图像上给出不同的输出。
有了这些特性,腐蚀可以分开连接的物体或者增加它们之间的距离或者移除一些多余的像素。
让我们来看一些应用腐蚀后的输出图像:

我们看到前景洞变得更大,前景物体消失“作者的图像”
使用 OpenCV 是一个非常简单的过程,如本文末尾代码的“OpenCV 侵蚀” 部分所示。
如果你想用 2D 阵列来检查一些结果,与你的计算结果进行比较,你可以使用 Scipy 库,如本文末尾所附的代码的“2D 阵列侵蚀” 部分所示。
扩张
在膨胀中,结构元素穿过图像,当图像模式和结构元素模式有 1 个匹配像素时,1 作为输出像素值写入,如果没有匹配像素,则为 0。注意,膨胀操作通常由 ⊕ 表示

图 2“作者图片”
请注意,在膨胀中, 0 填充被应用不同于腐蚀!
膨胀的性质
膨胀的效果与侵蚀相反。
- 膨胀增加了对象边界上的像素。
- 填充前景中的孔洞并放大前景对象。
- 但是以腐蚀的相同方式,较大的结构元素给出较大的膨胀效果,并且结果依赖于结构元素。
有了这些特性,膨胀可以修复前景物体中的断裂和缺失像素。

“作者提供的图像”
这是一个使用 OpenCV 的非常简单的过程,如本文末尾代码的“用 OpenCV 扩展” 部分所示。

“作者提供的图像”
如果你想用 2D 阵列检查一些结果,并与你的计算结果进行比较,你可以再次使用 Scipy 库。请看附在这篇文章末尾的代码中的“2D 数组膨胀” 部分。下面是该代码部分的输出示例。

“作者提供的图像”
结合侵蚀&扩张
我们学习了形态学的基本运算。现在是时候使用它们来获得更复杂的形态学操作符了。复杂的用于执行许多不同的任务。
- 用形态学去除噪声
开幕(A⊖B) ⊕B
开放只是膨胀后侵蚀的另一个名字。去除盐噪声有效。
关闭(⊕甲 B) ⊖乙
关闭只是腐蚀后膨胀的另一个名称。去除胡椒噪声有效。
让我们检查一些开始和结束输出:

带有盐(白)和胡椒(黑)噪声的输入图像。使用了 2 种不同的结构元素。我们看到打开只消除盐噪声,而关闭只消除胡椒噪声


最左侧的图像是输入图像,我们看到中间给出了两个不同结构元素的打开或关闭操作的结果。请参考图下的注释,以便更好地理解这 3 个推论:
- 我们看到打开对盐噪声(白点)和关闭对胡椒噪声(黑点)的影响。
- 结果非常依赖于结构元素。如果你没有为你的图像选择一个好的,你可能得不到形态学操作符的预期效果。
- 在需要关闭的地方使用打开运算符,反之亦然,可能会破坏图像。
当然,如果你的图像同时有椒盐噪声,你可以先打开再关闭,这样可以更好地去除噪声。
你可以很容易地选择一个图像,并使用 Opencv 执行打开-关闭操作,如本文末尾的代码中的“打开&关闭 OpenCV”部分所示
- 用形态学进行边缘检测
我们在图像处理第 2 部分中学习的另一个操作是如何使用空间线性滤波器,也可以使用组合形态学算子
外部边界提取(A ⊕ B)-A
我们对图像进行膨胀并减去原始输入图像以获得外部边缘。
内部边界提取一- (A⊖B)
我们取图像的腐蚀并从原始输入图像中减去它以获得内部边缘。
形态梯度(⊕ B)-(A⊖B)
我们从膨胀输出图像中减去腐蚀输出图像。虽然外部边界提取对于获得边缘的外侧像素是有效的,并且内部边界提取对于获得边缘的内侧像素是有效的,但是形态梯度为我们提供了边缘“上”的像素。这使得比内部或外部边界提取的边缘更厚。
看看外部和内部边界检测的相似性,其中形态学梯度给出较厚的边缘。

“作者提供的图像”

“作者提供的图像”

“作者提供的图像”
使用膨胀和侵蚀操作,您可以使用 OpenCV 实现您自己的边缘检测代码,如下面所附代码的“形态学边缘检测”部分所示:
这就是我们将在本主题中讨论的内容,但是如果您想进一步了解您可以使用形态学算子做什么,您可以搜索区域(孔)填充、连通分量提取、骨架化、命中或未命中变换、等。
点击 github 链接,找到我在这篇文章中使用的代码和图片,自己尝试一下!
您可以使用图像处理工具点击 进入最后一部分,这里是 !
图像处理综合指南:图像处理 GUI
图像处理要领
一个包含 OpenCV、QT Creator 和 C++的图像处理工具
作为关于图像处理流程的这个系列的最后一部分,我想展示一个简单的图像处理工具,它是在带有 C++ 的 QT Creator (5.12.10)上使用 OpenCV 3.2.0 实现的,可以应用前面帖子中讨论的几乎所有图像处理操作。
要能使用,在 QT 上搭建 OpenCV 3.2.0,把一些绝对路径改成你的本地路径就够了。了解如何使用该工具的一些提示和示例:
- 主页

“作者提供的图像”
您可以选择演示图像来使用来自应用程序的默认图像,并开始使用这些功能。对于此选项,您可以选择是使用灰度默认图像还是 RGB 默认图像。
可以应用任何更改,但单击“应用”按钮,将在右侧看到结果图像和结果直方图。
使用演示图像
- 灰度

“作者提供的图像”
通过选择一个灰度演示图像,我们看到 cameraman.png 和它的初始直方图。现在,我们可以应用以下任何过程:
- 增加 alpha 将增加对比度,在 0 和 1 之间减少将降低对比度。用箭头表示的每一步的尺寸为 0.25。在后台,应用功能如下:

“作者提供的图像”

使用 alpha = 4.50 调整对比度的示例“作者提供的图像”

使用 alpha = 0.50 调整对比度的示例“作者提供的图像”
- 调节亮度:β的值可以用箭头改变,或者可以在调节亮度平面的右侧框中输入任何值。增加β将使亮度增加β>0,而减少β将使亮度减少β<0。箭头所指的每一步都是 10 码。在后台,应用功能如下:

“作者提供的图像”

使用 beta = 70 调整亮度的示例“作者提供的图像”

使用 beta = -40 调整亮度的示例“作者提供的图像”
- 每个内核大小步长为 1。如果选择高斯滤波器,可以用右边的小方框确定标准偏差。

高斯滤波器核大小= 3,标准偏差= 7 的模糊图像的示例用法“作者提供的图像”

中值过滤器内核大小= 9 的模糊图像的示例用法“作者提供的图像”

平均过滤器内核大小= 11 的模糊图像的使用示例“作者的图像”
!!!请记住,您也可以使用中值和平均值滤波器来消除噪声,通常中值滤波器比平均值滤波器更好。
- 图像锐化: 类似于模糊图像操作,用户可以选择内核大小来应用反锐化掩模&高增强。与图像模糊不同,每一步都将内核大小增加 2。原因是锐化内核需要一个奇数大小的值,否则会产生运行时错误。此外,默认为 1 的 k 值可以由用户给定。

使用反锐化掩模和高增强内核大小= 9,k = 5 的图像锐化示例“作者提供的图像”
- 形态学操作 :可以根据选择的内核大小应用膨胀、腐蚀、打开或关闭。
!!!请注意,创建结构元素将是下一版本中的一个选项。目前,结构要素是:

“作者提供的图像”
…默认情况下,您只能更改大小。

侵蚀和核大小= 5 的形态学运算的示例用法“作者提供的图像”

使用膨胀和核大小= 5 的形态学操作的示例“作者提供的图像”

具有闭合和核大小= 5 的形态学操作的示例用法“作者的图像”
- 边缘检测 :您可以通过调整内核大小(默认值和最小值均为 3)来使用拉普拉斯、索贝尔、沙尔或形态滤波器检测边缘。

使用 Sobel 滤波器和核大小= 3 的边缘检测的示例使用“作者的图像”

使用拉普拉斯滤波器和核大小= 5 的边缘检测的示例使用“作者的图像”
- 上采样和下采样: 您可以选择上采样或下采样框对图像进行上采样或下采样。向上采样会将图像大小调整 200%,向下采样会将图像大小调整 50%。

向上采样“按作者分类的图像”

缩减像素采样“按作者排列的图像”
- 直方图均衡: 通过选择直方图均衡我们得到均衡后的图像和直方图。

直方图均衡化“按作者分类的图像”
- 添加噪波: 您可以选择相关框添加胡椒、盐或高斯噪波。使用右边的圆形按钮可以确定噪波的数量。这是将添加噪波的像素数。启用了多个选项,因此对于椒盐噪声,同时选择椒盐噪声就足够了。对于高斯噪声,用户也可以确定标准偏差。

通过检查盐和胡椒框添加盐和胡椒噪声“作者图像”
- 保存按钮: 点击保存按钮可以保存任何输出图像。这会将输出图像保存到默认目录,并向主窗口发送一条消息。
- 清除按钮: 选择多个不同参数值的不同操作后,可能要转默认值。此按钮清除所有选择。
- RGB : 灰度部分显示的每一项操作也适用于 RGB 图像。在选中 RGB 按钮的情况下,单击演示按钮将显示以下默认图像。我们看到在应用直方图均衡化、边缘检测、图像模糊和对比度调整之后获得的样本输出。

演示 RGB 图像“作者提供的图像”的边缘检测
使用您自己的图像
o 图像按钮
通过单击,会出现一个文件资源管理器窗口,用户可以从本地选择任何图像。

“作者提供的图像”
现在,我们在演示部分所做的任何操作都可以应用了。
o 图像序列按钮
此按钮允许用户上传多个图像,一次对上传的所有图像应用更改。通过选择“应用于所有”或“逐个应用”,用户可以随时改变决定是将操作应用于所有图像还是仅应用于当前图像。

同时选择 4 幅图像,一起处理“按作者分类的图像”
例如,当我们选择以下 4 个不同的图像时,我们看到每个图像都已上传,我们可以使用红色标记的滑块前进和后退。

“作者提供的图像”
作为“逐个应用”选择的一个例子,当我们对当前屏幕上的最后一幅图像(立方体 2)应用对比度调整和腐蚀时,我们为立方体 2 获得一幅经过处理的输出图像,而对于其他图像,我们不应用任何改变。结果如下所示:

“作者提供的图像”
作为“应用于所有”选择的一个例子,当我们对当前屏幕上的第一幅图像(barbara)应用图像模糊和边缘检测时,我们获得所有图像的处理输出图像。结果如下所示:

“作者提供的图像”
o 凸轮按钮
使用此按钮,可以使用本地网络摄像头,拍摄照片,并将其用作初始图像。当用户按“q”时拍摄图片,并上传到输入图像位置。在此步骤之后,可以应用任何操作。

从相机中捕捉图片并处理“按作者排序的图像”
你可以在这个 github 链接找到源代码。
!!!不要伪造用你的本地路径来改变这些路径:

演示图像路径“按作者分类的图像”

通用 Opencv 构建路径和需要添加的相关库。pro 文件“按作者分类的图像”
!!!另外很重要的一点是选择 Qt 5.12.10 MinGW 64 位作为编译器,运行 app 时不会有任何问题。

“作者提供的图像”
玩得开心!
我们已经到了图像处理基础博客的结尾!我们学习了“图像”的含义,我们可以对图像进行什么样的操作以及如何操作。以下是对您后续步骤的一些建议:
- 不要忘记每个图像都是不同的。内核大小、结构元素或内核类型可能会改变,以获得您需要的结果。
- 首先使用 2D 数组对于检查您是否真正理解了想要应用的操作的逻辑是非常重要的。因此,如果你有兴趣了解基础,请检查我从头实现的代码,以玩 2D 数组的每个操作。
- 如果您想进一步了解该主题,我建议您搜索“频域图像处理”主题,在该主题中,操作不像空间域图像处理那样直接应用于图像,而是首先转换到频域,在应用所要求的滤波器或操作后,我们重新转换“频域输出”以获得最终输出图像。
谢谢大家!
用 Python 进行图像处理——傅立叶变换的应用
如何利用傅立叶变换去除图像元素

傅立叶变换(图片由作者提供)
图像处理中更高级的主题之一与傅立叶变换的概念有关。简而言之,一些图像包含用户可能想要去除的系统噪声。如果这种噪声足够规则,采用傅立叶变换调整可能有助于图像处理。在这篇文章中,我们将看到如何做到这一点。
让我们开始吧。
和往常一样,首先导入所需的 Python 库。
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imread, imshow
from skimage.color import rgb2hsv, rgb2gray, rgb2yuv
from skimage import color, exposure, transform
from skimage.exposure import equalize_hist
首先,让我们加载将在本文中使用的图像。
dark_image = imread('against_the_light.png')

带有黑色水平线的图像
我们将使用的图像是上面的一个。这是当太阳直接面对相机时拍摄的街道图像。在这个简单的练习中,我们将设法消除(或至少大幅度减少)背面的电源线。
首先,让我们把图像转换成灰度。
dark_image_grey = rgb2gray(dark_image)
plt.figure(num=None, figsize=(8, 6), dpi=80)
plt.imshow(dark_image_grey, cmap='gray');

灰度图像
很好,从这里我们可以很容易地使用 Skimage 中的 fft 函数。
dark_image_grey_fourier = np.fft.fftshift(np.fft.fft2(dark_image_grey))
plt.figure(num=None, figsize=(8, 6), dpi=80)
plt.imshow(np.log(abs(dark_image_grey_fourier)), cmap='gray');

图像的傅立叶变换
在图像中,我们可以看到两个非常明显的失真。白色的垂直和水平线指的是图像中清晰的水平和垂直元素。让我们看看如果我们屏蔽其中一个会发生什么。
def fourier_masker_ver(image, i):
f_size = 15
dark_image_grey_fourier =
np.fft.fftshift(np.fft.fft2(rgb2gray(image))) dark_image_grey_fourier[:225, 235:240] = i
dark_image_grey_fourier[-225:,235:240] = i fig, ax = plt.subplots(1,3,figsize=(15,15))
ax[0].imshow(np.log(abs(dark_image_grey_fourier)), cmap='gray')
ax[0].set_title('Masked Fourier', fontsize = f_size)
ax[1].imshow(rgb2gray(image), cmap = 'gray')
ax[1].set_title('Greyscale Image', fontsize = f_size);
ax[2].imshow(abs(np.fft.ifft2(dark_image_grey_fourier)),
cmap='gray')
ax[2].set_title('Transformed Greyscale Image',
fontsize = f_size);
fourier_masker(dark_image, 1)

傅立叶变换垂直掩蔽图像
我们可以看到,水平电源电缆的尺寸显著减小。作为一个有趣的实验,让我们看看如果我们屏蔽水平线会发生什么。
def fourier_masker_hor(image, i):
f_size = 15
dark_image_grey_fourier =
np.fft.fftshift(np.fft.fft2(rgb2gray(image))) dark_image_grey_fourier[235:240, :230] = i
dark_image_grey_fourier[235:240,-230:] = i fig, ax = plt.subplots(1,3,figsize=(15,15))
ax[0].imshow(np.log(abs(dark_image_grey_fourier)), cmap='gray')
ax[0].set_title('Masked Fourier', fontsize = f_size)
ax[1].imshow(rgb2gray(image), cmap = 'gray')
ax[1].set_title('Greyscale Image', fontsize = f_size);
ax[2].imshow(abs(np.fft.ifft2(dark_image_grey_fourier)),
cmap='gray')
ax[2].set_title('Transformed Greyscale Image',
fontsize = f_size);
fourier_masker_hor(dark_image, 1)

傅立叶变换水平掩蔽图像
我们可以看到,图像的所有垂直方面都被弄脏了。这在电杆上非常明显。虽然在某些情况下很有帮助,但在这里显然没有帮助。
虽然我们将坚持掩蔽傅立叶变换的垂直线(再次记住,当转换回原始图像时,这会模糊水平线),让我们试验不同程度的掩蔽。
def fourier_iterator(image, value_list):
for i in value_list:
fourier_masker_ver(image, i)
fourier_iterator(dark_image, [0.001, 1, 100])



屏蔽值的迭代
我们可以看到,减少该值对原始图像几乎没有影响,但是增加该值似乎使原始图像变暗。由于较小的值与 1 实际上没有区别,为了简单起见,我们还是坚持使用 1 。
最后,让我们进行傅立叶变换调整,同时保留原始图像的颜色。
def fourier_transform_rgb(image):
f_size = 25
transformed_channels = []
for i in range(3):
rgb_fft = np.fft.fftshift(np.fft.fft2((image[:, :, i])))
rgb_fft[:225, 235:237] = 1
rgb_fft[-225:,235:237] = 1
transformed_channels.append(abs(np.fft.ifft2(rgb_fft)))
final_image = np.dstack([transformed_channels[0].astype(int),
transformed_channels[1].astype(int),
transformed_channels[2].astype(int)])
fig, ax = plt.subplots(1, 2, figsize=(17,12))
ax[0].imshow(image)
ax[0].set_title('Original Image', fontsize = f_size)
ax[0].set_axis_off()
ax[1].imshow(final_image)
ax[1].set_title('Transformed Image', fontsize = f_size)
ax[1].set_axis_off()
fig.tight_layout()

傅立叶变换彩色图像
我们可以看到,水平电源线已经大大减少,而图像的其余部分基本保持不变。这展示了我们如何通过傅立叶变换对图像进行细微的改变。
总之
傅立叶变换是一种强大的工具,对于处理图像的数据科学家来说非常有用。在以后的文章中,我们将回顾如何以更有效的方式应用该技术。现在,我希望你能够对这个主题有一个基本的了解。
用 Python 处理图像——应用单应性进行图像变形
如何获得更好的视角

更好的视角(作者图片)
旋转图像并不是什么新鲜事,大多数做过基本照片编辑的人在某个时候都不得不这么做。在这篇文章中,我们将解决调整图像的方法,使它看起来像是从不同的角度拍摄的。
我们开始吧!
和往常一样,首先导入所需的 Python 库。
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
from skimage import transform
from skimage.io import imread, imshow
很好,现在让我们导入将要使用的图像。
sign = imread('words_of_wisdom.png')
plt.figure(num=None, figsize=(8, 6), dpi=80)

获得更好视角的一种方法(图片由作者提供)
正如我们所见,上面的标志微开着。虽然我故意从那个角度拍了这张照片,让我们看看我们是否可以“纠正”它。一个非常基本的方法是通过 Skimage 中的 transform.rotate 函数。下面的代码正是这样做的。
sign_rotate = transform.rotate(sign, 330)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(sign_rotate);

旋转图像
正如我们所见,图像已经旋转,因此符号现在与 x 轴平行。然而,我们看到两个非常突出的问题。
第一个是,这本质上只是旋转图像(大惊喜吧?).第二个是图像原来的地方现在有了空白。让我们设法补救这一点。
我们使用的函数实际上有一个参数名为模式,这将有助于减少空白。让我们试验一下可以使用的不同模式。
def rotate_fills(image):
modes = ['constant', 'edge','symmetric','reflect','wrap']
fig, ax = plt.subplots(3,2, figsize=(7, 10), dpi = 200)
for n, ax in enumerate(ax.flatten()):
n = n-1
if n == -1:
ax.set_title(f'original', fontsize = 12)
ax.imshow(image)
ax.set_axis_off()
else:
ax.set_title(f'{modes[n]}', fontsize = 12)
ax.imshow(transform.rotate(image, 330, mode = modes[n]))
ax.set_axis_off()
fig.tight_layout();
rotate_fills(sign)

具有各种填充的旋转图像
我们可以看到,通过试验不同的填充方法,反射和对称都脱颖而出,成为首选。然而,它们有一个非常微妙的缺点。
def comparing_fills(image):
modes = ['symmetric','reflect']
fig, ax = plt.subplots(1,2, figsize=(7, 10), dpi = 200)
ax[0].imshow(transform.rotate(image, 330, mode = modes[0]))
ax[0].set_title(f'{modes[0]}', fontsize = 15)
ax[0].set_axis_off()
ax[1].imshow(transform.rotate(image, 330, mode = modes[1]))
ax[1].set_title(f'{modes[1]}', fontsize = 15)
ax[1].set_axis_off()
fig.tight_layout();
comparing_fills(sign)

比较对称模式和反射模式
注意图像的右下角是如何被反射的。这使得这棵树看起来好像在那个角落有第二个分支。当然,当我们检查原始图像时,情况并非如此。
因此,尽管我们尽了最大努力,旋转似乎并不是我们任务的最佳功能。幸运的是,有一个函数我们可以使用,Skimage 中的 transform.warp 函数。
然而,与旋转相比,它确实需要更多的工作来实现。首先,我们必须得到我们关心的点,以及我们想把它们投射到哪里。请原谅下面的长代码,但我发现明确的编程很重要,这样就很容易理解。
points_of_interest =[[360, 110],
[420, 270],
[130, 400],
[100, 280]]projection = [[500, 200],
[500, 390],
[100, 390],
[100, 200]]color = 'red'
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)patch1 = Circle((points_of_interest[0][0],points_of_interest[0][1]),
10, facecolor = color)
patch2 = Circle((points_of_interest[1][0],points_of_interest[1][1]),
10, facecolor = color)
patch3 = Circle((points_of_interest[2][0],points_of_interest[2][1]),
10, facecolor = color)
patch4 = Circle((points_of_interest[3][0],points_of_interest[3][1]),
10, facecolor = color)patch5 = Circle((projection[0][0],projection[0][1]), 10,
facecolor = color)
patch6 = Circle((projection[1][0],projection[1][1]), 10,
facecolor = color)
patch7 = Circle((projection[2][0],projection[2][1]), 10,
facecolor = color)
patch8 = Circle((projection[3][0],projection[3][1]), 10,
facecolor = color)ax[0].add_patch(patch1)
ax[0].add_patch(patch2)
ax[0].add_patch(patch3)
ax[0].add_patch(patch4)ax[0].imshow(sign);ax[1].add_patch(patch5)
ax[1].add_patch(patch6)
ax[1].add_patch(patch7)
ax[1].add_patch(patch8)ax[1].imshow(np.ones((sign.shape[0], sign.shape[1])));

当前点和目标点
我们本质上所做的是确定标志的当前角的位置,并画出我们想要它们去的地方。
请注意,这一步只是为了澄清源位置和目标位置。为了实际实现这个函数,我们必须使用下面的代码。
points_of_interest = np.array([[360, 110],
[420, 270],
[130, 400],
[100, 280]])projection = np.array([[500, 200],
[500, 390],
[100, 390],
[100, 200]])tform = transform.estimate_transform('projective', points_of_interest, projection)
tf_img_warp = transform.warp(sign, tform.inverse, mode = 'symmetric')
plt.figure(num=None, figsize=(8, 6), dpi=80)fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
ax[0].set_title(f'Original', fontsize = 15)
ax[0].imshow(sign)
ax[0].set_axis_off();ax[1].set_title(f'Transformed', fontsize = 15)
ax[1].imshow(tf_img_warp)
ax[1].set_axis_off();

变换图像
我们看到这是对我们以前方法的巨大改进。不仅标志的角度得到了纠正,而且我们不再需要处理令人讨厌的空白问题。
然而,请注意,尽管这种方法令人印象深刻,但它并不总是产生最佳结果。以下面的图片为例。
house = imread('artsy_house.png')
plt.figure(num=None, figsize=(8, 6), dpi=80)

潮人屋
由于其明显的厚度,该功能不会提供最佳结果。为了演示,让我们画出感兴趣的点。此外,我还提供了运行前面代码的快捷方式
points_of_interest =[[105, 60],
[260, 85],
[275, 295],
[110, 290]]projection = [[100, 75],
[275, 75],
[275, 290],
[100, 290]]color = 'green'
patches = []
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
for coordinates in (points_of_interest + projection):
patch = Circle((coordinates[0],coordinates[1]), 10,
facecolor = color)
patches.append(patch)for p in patches[:4]:
ax[0].add_patch(p)
ax[0].imshow(house);for p in patches[4:]:
ax[1].add_patch(p)
ax[1].imshow(np.ones((house.shape[0], house.shape[1])));

兴趣点与投影
很好,现在让我们把图像投射到目标点。
points_of_interest = np.array(points_of_interest)
projection = np.array(projection)tform = transform.estimate_transform('projective', points_of_interest, projection)
tf_img_warp = transform.warp(house, tform.inverse, mode = 'edge')
plt.figure(num=None, figsize=(8, 6), dpi=80)fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
ax[0].set_title(f'Original', fontsize = 15)
ax[0].imshow(house)
ax[0].set_axis_off();ax[1].set_title(f'Transformed', fontsize = 15)
ax[1].imshow(tf_img_warp)
ax[1].set_axis_off();

预计艺术房子
虽然我们能够正确地将图像投影到新的点上,但是请注意它看起来有点不自然。看这幅画的左边部分,我们看到凸起的部分仍然很明显。如果我们实际上是从正面角度拍摄的,就看不到这个特征。还要注意图像的左上部分有一些轻微的扭曲,这是由于使用了边缘填充造成的。
总之
我们已经看到了使用 transform.warp 真正改变图像视角的能力。有很多方法可以应用这项技术,从地形测量到图像重建。我希望你现在对这种方法有了更好的理解,并能想象如何在你的项目中使用它。
使用 Python 进行图像处理—使用 Scikit-Image 进行斑点检测
如何识别和分离图像中的特定斑点

笑树(作者图片)
数据科学家在处理图像时需要的最重要的技能之一是能够识别图像的特定部分。一幅图像只有在特定的兴趣点能够被识别和逐项列出时才变得有用。在本文中,我们将学习如何做到这一点。
我们开始吧!
像往常一样,让我们首先导入本文所需的所有库。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import skimagefrom skimage.io import imread, imshow
from skimage.color import rgb2gray, rgb2hsv
from skimage.measure import label, regionprops, regionprops_table
from skimage.filters import threshold_otsu
from scipy.ndimage import median_filter
from matplotlib.patches import Rectangle
from tqdm import tqdm
很好,现在让我们加载将要处理的图像。
tree = imread('laughing_tree.png')
imshow(tree);

笑树(作者图片)
我们将使用上面的图像。我们的任务是识别和分离图像中包含该树的独特果实(看起来像张开的嘴)的部分。
我们应该做的第一件事是尝试看看是否有任何简单的方法来根据值识别图像。让我们将图像转换成灰度,并使用 Otsu 的方法,看看这是否给我们一个体面的面具。
tree_gray = rgb2gray(tree)
otsu_thresh = threshold_otsu(tree_gray)
tree_binary = tree_gray < otsu_thresh
imshow(tree_binary, cmap = 'gray');

二值化树木图像
这显然不太好,让我们尝试在几个阈值水平上迭代,看看我们是否能找到产生更好掩模的阈值。
def threshold_checker(image):
thresholds = np.arange(0.1,1.1,0.1)
tree_gray = rgb2gray(image)
fig, ax = plt.subplots(2, 5, figsize=(17, 10))
for n, ax in enumerate(ax.flatten()):
ax.set_title(f'Threshold : {round(thresholds[n],2)}',
fontsize = 16)
threshold_tree = tree_gray < thresholds[n]
ax.imshow(threshold_tree);
ax.axis('off')
fig.tight_layout()threshold_checker(tree)

不同阈值水平的二值化图像
我们可以看到,虽然阈值处理似乎有所帮助,但它仍然包含我们不感兴趣的图像的重要部分。让我们尝试另一种方法。
tree_hsv = rgb2hsv(tree[:,:,:-1])
plt.figure(num=None, figsize=(8, 6), dpi=80)
plt.imshow(tree_hsv[:,:,0], cmap='hsv')
plt.colorbar();

图像的 HSV(色调通道)
如果我们将图像放入 HSV 色彩空间,我们可以看到水果明显具有红色色调,而这在图像的其他部分不存在。让我们试着分离图像的这些部分。
lower_mask = tree_hsv [:,:,0] > 0.80
upper_mask = tree_hsv [:,:,0] <= 1.00
mask = upper_mask*lower_mask
red = tree[:,:,0]*mask
green = tree[:,:,1]*mask
blue = tree[:,:,2]*mask
tree_mask = np.dstack((red,green,blue))
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(tree_mask);

屏蔽图像
我们看到,随着水果,大部分的天窗部分也被保留。参考前面的色调通道图像,我们可以看到这些部分也有水果中存在的同一种红色。
为了解决这个问题,让我们检查图像的价值通道。
tree_hsv = rgb2hsv(tree[:,:,:-1])
plt.figure(num=None, figsize=(8, 6), dpi=80)
plt.imshow(tree_hsv[:,:,2], cmap='gray')
plt.colorbar();

价值渠道
我们可以看到那些灯火通明的区域有着令人难以置信的高价值。让我们在制作面具时考虑到这一点。
lower_mask = tree_hsv [:,:,0] > 0.80
upper_mask = tree_hsv [:,:,0] <= 1.00
value_mask = tree_hsv [:,:,2] < .90
mask = upper_mask*lower_mask*value_mask
red = tree[:,:,0] * mask
green = tree[:,:,1] * mask
blue = tree[:,:,2] * mask
tree_mask = np.dstack((red, green, blue))
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(tree_mask);

整合价值渠道
太好了!我们快到了。我们现在必须找到一种方法来清理图像,并删除小白点。为此,我们可以简单地使用 Skimage 库中的 median_filter 函数。
lower_mask = tree_hsv [:,:,0] > 0.80
upper_mask = tree_hsv [:,:,0] <= 1.00
value_mask = tree_hsv [:,:,2] < .90
mask = median_filter(upper_mask*lower_mask*value_mask, 10)
red = tree[:,:,0] * mask
green = tree[:,:,1] * mask
blue = tree[:,:,2] * mask
tree_mask = np.dstack((red, green, blue))
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(tree_mask);

结合中值滤波器
我们可以看到,采用中值滤波器得到了非常清晰的图像。现在我们需要识别每个斑点,为此我们需要利用 Skimage 中的标签功能。
tree_blobs = label(rgb2gray(tree_mask) > 0)
imshow(tree_blobs, cmap = 'tab10');

我们可以看到,该函数识别图像中的不同斑点。下一步是获取每个 blob 的属性。为此,我们必须使用 Skimage 中的 regionprops_table 函数。
properties =['area','bbox','convex_area','bbox_area',
'major_axis_length', 'minor_axis_length',
'eccentricity']
df = pd.DataFrame(regionprops_table(tree_blobs, properties = properties))

Blob 属性
regionprops_table 函数在一个方便的 pandas 数据帧中为我们提供了每个斑点的属性。这使我们能够轻松地操纵数据和定位特定的斑点。作为这个数据框有多有用的例子,让我们使用 bbox 特性在图像上绘制边界框。
blob_coordinates = [(row['bbox-0'],row['bbox-1'],
row['bbox-2'],row['bbox-3'] )for
index, row in df.iterrows()]fig, ax = plt.subplots(1,1, figsize=(8, 6), dpi = 80)
for blob in tqdm(blob_coordinates):
width = blob[3] - blob[1]
height = blob[2] - blob[0]
patch = Rectangle((blob[1],blob[0]), width, height,
edgecolor='r', facecolor='none')
ax.add_patch(patch)
ax.imshow(tree);
ax.set_axis_off()

带边框的图像
如果我们仔细观察,我们可以看到在图像的左上角有一个单一的边界框。边界框内的对象显然不是水果。但是我们如何摆脱它呢?
我们能做的是过滤熊猫的数据。为了简单起见,我们将通过离心率列对其进行过滤,这是因为离群点具有独特的形状。
df = df[df['eccentricity'] < df['eccentricity'].max()]

过滤数据帧
如果我们再次绘制边界框,我们看到我们能够成功地过滤掉斑点。

过滤边界框
最后,让我们从图像中剪切出边界框,并将它们显示为自己的图像。
fig, ax = plt.subplots(1, len(blob_coordinates), figsize=(15,5))
for n, axis in enumerate(ax.flatten()):
axis.imshow(tree[int(blob_coordinates[n][0]):
int(blob_coordinates[n][2]),
int(blob_coordinates[n][1]):
int(blob_coordinates[n][3])]);
fig.tight_layout()

剪下斑点
太好了,我们已经成功地识别了图像中有趣的嘴状水果。这些图像现在可以保存到一个文件中,供以后使用(可能用于机器学习项目)。
总之
对于任何处理图像的数据科学家来说,知道如何进行斑点检测都是一项很有价值的技能。它可以用来将图像的不同部分分成不同的兴趣点。实际上,您可以使用这种技术来创建数据,这些数据将被输入到您的机器学习算法中。虽然这是一个相对简单和直接的教训,我希望你现在有一个想法,如何使用斑点检测来解决基本的图像问题。
使用 Python 进行图像处理——初学者的模糊和锐化
如何将卷积核应用于彩色图像?

卷积狗(图片作者)
在这篇文章中,我们将讨论如何应用模糊和锐化内核到图像上。这些基本内核构成了许多更高级的内核应用程序的主干。在我之前的文章中,我讨论了边缘检测内核,但是我意识到我只关注灰度图像。
作为一个有用的指导,我将讨论我们如何将这些内核应用到彩色图像上,同时仍然保留核心图像。
我们开始吧!
一如既往,让我们从导入所需的 Python 库开始。
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imshow, imread
from skimage.color import rgb2yuv, rgb2hsv, rgb2gray, yuv2rgb, hsv2rgb
from scipy.signal import convolve2d
出于本文的目的,我们将使用下图。
dog = imread('fire_dog.png')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(dog);

篝火狗(图片由作者提供)
现在我们将应用于图像的内核是高斯模糊内核和锐化内核。你可以在下面看到我们如何定义他们的矩阵。
# Sharpen
sharpen = np.array([[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]])# Gaussian Blur
gaussian = (1 / 16.0) * np.array([[1., 2., 1.],
[2., 4., 2.],
[1., 2., 1.]])fig, ax = plt.subplots(1,2, figsize = (17,10))
ax[0].imshow(sharpen, cmap='gray')
ax[0].set_title(f'Sharpen', fontsize = 18)
ax[1].imshow(gaussian, cmap='gray')
ax[1].set_title(f'Gaussian Blur', fontsize = 18)
[axi.set_axis_off() for axi in ax.ravel()];

锐化和高斯模糊内核
但是我们如何将这些内核应用到我们的图像中呢?好吧,让我们首先尝试直接召集他们。我已经定义了下面的函数来允许我们迭代内核。注意我们如何设置边界填充和填充值为 0,这对于确保输出将是 s ame size 的 0 填充矩阵作为原始矩阵是很重要的。
def multi_convolver(image, kernel, iterations):
for i in range(iterations):
image = convolve2d(image, kernel, 'same', boundary = 'fill',
fillvalue = 0)
return imagemulti_convolver(dog, gaussian, 2)

出错信息
哦,不,似乎我们遇到了一个数值错误。为什么会这样呢?请记住,当我们将一个矩阵与另一个矩阵进行卷积时,这两个矩阵的维数应该相同。这意味着我们不能将 2D 卷积应用于我们的 3D(由于颜色通道)矩阵。为了解决这个问题,我们必须首先将图像转换成灰度。
dog_grey = rgb2gray(dog)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(dog_grey);

灰色的狗
现在,如果我们运行这个函数,我们应该会得到想要的效果。
convolved_image = multi_convolver(dog_grey, gaussian, 2)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(convolved_image);

模糊的狗
精彩!我们现在可以看到图像已经明显模糊。下面的代码将向我们展示如果我们继续对图像进行高斯模糊卷积,图像会发生什么。
def convolution_plotter(image, kernel):
iterations = [1,10,20,30]
f_size = 20
fig, ax = plt.subplots(1,4, figsize = (15,7))
for n, ax in enumerate(ax.flatten()):
ax.set_title(f'Iteration : {iterations[n]}', fontsize =
f_size)
ax.imshow(multi_convolver(image, kernel, iterations[n]),
cmap='gray')
ax.set_axis_off() fig.tight_layout()
convolution_plotter(dog_grey, gaussian)

高斯模糊
太好了!我们可以清楚地看到,由于我们的内核的应用图像不断模糊。
但是如果你需要模糊图像而保持颜色呢?让我们首先尝试应用每个颜色通道的卷积。
def convolver_rgb(image, kernel, iterations = 1):
convolved_image_r = multi_convolver(image[:,:,0], kernel,
iterations)
convolved_image_g = multi_convolver(image[:,:,1], kernel,
iterations)
convolved_image_b = multi_convolver(image[:,:,2], kernel,
iterations)
reformed_image = np.dstack((np.rint(abs(convolved_image_r)),
np.rint(abs(convolved_image_g)),
np.rint(abs(convolved_image_b)))) /
255
fig, ax = plt.subplots(1,3, figsize = (17,10))
ax[0].imshow(abs(convolved_image_r), cmap='Reds')
ax[0].set_title(f'Red', fontsize = 15)
ax[1].imshow(abs(convolved_image_g), cmap='Greens')
ax[1].set_title(f'Green', fontsize = 15)
ax[2].imshow(abs(convolved_image_b), cmap='Blues')
ax[2].set_title(f'Blue', fontsize = 15)
[axi.set_axis_off() for axi in ax.ravel()]
return np.array(reformed_image).astype(np.uint8)convolved_rgb_gauss = convolver_rgb(dog, gaussian, 2)

RGB 通道卷积
该函数实际上向我们返回了重组后的图像,我们只需将它插入到 show 函数中。
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(convolved_rgb_gauss);

重组高斯图像
太好了!看起来这个功能运行得很好。作为一个有趣的练习,让我们看看当我们对图像进行 10 次卷积时会发生什么。

非常模糊的图像
所以这解决了我们的问题,对吗?不完全是。要查看这个函数的问题,让我们尝试锐化图像。
convolved_rgb_sharpen = convolver_rgb(dog, sharpen, 1)

RGB 通道卷积
目前为止看起来不错,让我们看看改造后的形象是什么样的。

改良锐化图像
图像已被改造,但我们现在看到有一些轻微的扭曲。为什么会这样呢?
请记住,RGB 颜色空间隐含地将像素的亮度与颜色混合在一起。这意味着实际上不可能在不改变颜色的情况下对图像的照明应用卷积。那么我们如何处理这个问题呢?
解决这个问题的一个方法是改变图像的色彩空间。我们可以不使用 RGB 颜色空间,而是使用 Y'UV 颜色空间。我们这样做是因为 Y'UV 空间中的照明通道实际上是与颜色分离的(这是 Y 分量)。
出于本文的目的,我们将编辑函数,首先将图像转换到 Y'UV 颜色空间,然后进行所需的卷积。
def convolver_rgb(image, kernel, iterations = 1):
img_yuv = rgb2yuv(image)
img_yuv[:,:,0] = multi_convolver(img_yuv[:,:,0], kernel,
iterations)
final_image = yuv2rgb(img_yuv)
fig, ax = plt.subplots(1,2, figsize = (17,10))
ax[0].imshow(image)
ax[0].set_title(f'Original', fontsize = 20)
ax[1].imshow(final_image);
ax[1].set_title(f'YUV Adjusted, Iterations = {iterations}',
fontsize = 20)
[axi.set_axis_off() for axi in ax.ravel()]
fig.tight_layout()
return final_imagefinal_image = convolver_rgb(dog, sharpen, iterations = 1)

改良锐化图像
我们可以看到,我们的函数现在返回的图像明显更清晰,没有任何颜色失真。有许多其他方法来解决这个问题,紫外线转换只是其中之一。请记住,HSV 颜色空间的 V 分量代表几乎相同的东西。然而,Y'UV 空间的亮度分量和 HSV 空间的值分量的方式略有不同。让我们来看看使用一个比另一个更好的结果是什么。
def convolver_comparison(image, kernel, iterations = 1):
img_yuv = rgb2yuv(image)
img_yuv[:,:,0] = multi_convolver(img_yuv[:,:,0], kernel,
iterations)
final_image_yuv = yuv2rgb(img_yuv)
img_hsv = rgb2hsv(image)
img_hsv[:,:,2] = multi_convolver(img_hsv[:,:,2], kernel,
iterations)
final_image_hsv = hsv2rgb(img_hsv)
convolved_image_r = multi_convolver(image[:,:,0], kernel,
iterations)
convolved_image_g = multi_convolver(image[:,:,1], kernel,
iterations)
convolved_image_b = multi_convolver(image[:,:,2], kernel,
iterations)
final_image_rgb = np.dstack((np.rint(abs(convolved_image_r)),
np.rint(abs(convolved_image_g)),
np.rint(abs(convolved_image_b)))) /
255
fig, ax = plt.subplots(2,2, figsize = (17,17))
ax[0][0].imshow(image)
ax[0][0].set_title(f'Original', fontsize = 30)
ax[0][1].imshow(final_image_rgb);
ax[0][1].set_title(f'RGB Adjusted, Iterations = {iterations}',
fontsize = 30)
fig.tight_layout()
ax[1][0].imshow(final_image_yuv)
ax[1][0].set_title(f'YUV Adjusted, Iterations = {iterations}',
fontsize = 30)
ax[1][1].imshow(final_image_hsv)
ax[1][1].set_title(f'HSV Adjusted, Iterations = {iterations}',
fontsize = 30)
[axi.set_axis_off() for axi in ax.ravel()]
fig.tight_layout()convolver_comparison(dog, sharpen, iterations = 1)

卷积的比较
我们看到 HSV 和 Y'UV 相对于原始的 RGB 方法有一些轻微的改进。为了更好地说明,我们可以将迭代次数从 1 增加到 2。

失真比较
在 2 次迭代时,失真变得更加明显。但是也很清楚,HSV 和 Y'UV 调整后的图像比原始 RGB 调整后的图像更加平滑。在决定将卷积核应用于图像的最佳方式时,应该记住这些属性。
总之
总而言之,我们已经学会了如何对图像进行模糊和锐化卷积。这些技术对于任何在图像处理和计算机视觉领域工作的数据科学家来说都是至关重要的。非常重要的是,我们了解到简单地对单个 RGB 通道应用卷积可能不是最好的方法。当处理图像时,你应该知道有很多不同种类的颜色空间可以使用。希望这篇文章对你有所帮助,并能应用到你自己的工作中。
Python 图像处理——初学者的色彩隔离
如何根据颜色隔离图像的各个部分

颜色隔离(图片由作者提供)
需要隔离图像的特定部分是一项基本技能,在这个领域会对你很有帮助。在这篇文章中,我们将讨论一些技术,将我们的图像的特定部分隔离。
我们开始吧!
一如既往,让我们从导入所需的 Python 库开始。
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imshow, imread
from skimage.color import rgb2hsv, hsv2rgb
import cv2
首先,让我们选择一个相对容易的图片。
red_girl = imread('red_girl.PNG')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(red_girl);

吴哥窟的红姑娘(图片由作者提供)
正如我们所见,这张图片是尝试图像隔离的理想方式,因为有一个明确的目标。那么应该如何隔离红衣女士呢?
首先,回想一下图像是由三个颜色通道红、绿、蓝组成的。也许我们可以简单地过滤红色通道。
red_filtered_girl = (red_girl[:,:,0] > 150)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(red_filtered_girl, cmap = 'gray');

过滤像
这肯定不像我们想要的。让我们将这个遮罩应用到 RGB 图像中,看看结果。
red_girl_new = red_girl.copy()
red_girl_new[:, :, 0] = red_girl_new[:, :, 0]*red_filtered_girl
red_girl_new[:, :, 1] = red_girl_new[:, :, 1]*red_filtered_girl
red_girl_new[:, :, 2] = red_girl_new[:, :, 2]*red_filtered_girl
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(red_girl_new);

RGB 过滤图像
虽然图像看起来很酷,但它绝对不是我们想要的。这到底是怎么回事?回想一下,图像本身并不认为每种颜色是不同的“颜色”。所有的颜色都是红色、绿色和蓝色的简单混合。
def rgb_splitter(image):
rgb_list = ['Reds','Greens','Blues']
fig, ax = plt.subplots(1, 3, figsize=(15,5), sharey = True)
for i in range(3):
ax[i].imshow(image[:,:,i], cmap = rgb_list[i])
ax[i].set_title(rgb_list[i], fontsize = 15)rgb_splitter(red_girl)

RGB 通道
请注意,尽管人眼看不到红色,但图像中的许多其他物体中仍然存在红色成分。我们必须考虑到这一点。在下面的代码中,我们指定绿色和蓝色通道也必须低于某个阈值。
red_filtered = (red_girl[:,:,0] > 150) & (red_girl[:,:,1] < 100) & (red_girl[:,:,2] < 110)
plt.figure(num=None, figsize=(8, 6), dpi=80)
red_girl_new = red_girl.copy()
red_girl_new[:, :, 0] = red_girl_new[:, :, 0] * red_filtered
red_girl_new[:, :, 1] = red_girl_new[:, :, 1] * red_filtered
red_girl_new[:, :, 2] = red_girl_new[:, :, 2] * red_filtered
imshow(red_girl_new);

红色过滤图像
这就好多了,我们可以清楚地看到,红姑娘是与图像的其余部分隔离开来的。
但是请注意,只有当需要以具有非常明显的红色、绿色和蓝色通道的对象为目标时,才首选此方法。为了更好地分离这些颜色,我们可以利用 HSV 颜色空间。
def display_as_hsv(image):
img = cv2.imread(image)
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hsv_list = ['Hue','Saturation','Value']
fig, ax = plt.subplots(1, 3, figsize=(15,7), sharey = True)
ax[0].imshow(img_hsv[:,:,0], cmap = 'hsv')
ax[0].set_title(hsv_list[0], fontsize = 20)
ax[0].axis('off')
ax[1].imshow(img_hsv[:,:,1], cmap = 'Greys')
ax[1].set_title(hsv_list[1], fontsize = 20)
ax[1].axis('off')
ax[2].imshow(img_hsv[:,:,2], cmap = 'gray')
ax[2].set_title(hsv_list[2], fontsize = 20)
ax[2].axis('off')
fig.tight_layout()display_as_hsv('red_girl.PNG')

HSV 频道
与 RGB 色彩空间不同,HSV 色彩空间明确地将色彩色调通道与饱和度和值分离开来。这使我们能够轻松地锁定特定的颜色。为了帮助我们找到特定的颜色,让我们显示图片的颜色条。
plt.figure(num=None, figsize=(8, 6), dpi=80)
plt.imshow(red_girl_hsv[:,:,0], cmap='hsv')
plt.colorbar();

带有颜色条的色调图像
我们可以看到,每种颜色指的是色谱上的一个特定范围。让我们试着分离红色。
lower_mask = red_girl_hsv [:,:,0] > 0.90
upper_mask = red_girl_hsv [:,:,0] < 1.00
mask = upper_mask*lower_maskred = red_girl[:,:,0]*mask
green = red_girl[:,:,1]*mask
blue = red_girl[:,:,2]*mask
red_girl_masked = np.dstack((red,green,blue))
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(red_girl_masked);

色调掩蔽图像
我们可以看到,图像清楚地隔离了红色女士。然而,它也拾取了几个斑点。如果我们将其与 HSV 颜色空间的色调通道进行比较,我们会看到这些斑点也被视为红色,即使它们看起来是灰色/棕色。为了减轻这一点,让我们添加另一个饱和度过滤器。
lower_mask = red_girl_hsv [:,:,0] > 0.90
upper_mask = red_girl_hsv [:,:,0] < 1.00
saturation = red_girl_hsv [:,:,1] > 0.50mask = upper_mask*lower_mask*saturationred = red_girl[:,:,0]*mask
green = red_girl[:,:,1]*mask
blue = red_girl[:,:,2]*mask
red_girl_masked = np.dstack((red,green,blue))
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(red_girl_masked);

添加了饱和度过滤器
太棒了。我们利用单纯疱疹病毒通道成功地隔离了红娘。让我们试着区分不同的颜色。下面的代码为三种不同的颜色设置做了同样的事情。
def color_isolates(image):
image_hsv = rgb2hsv(image[:,:,:-1])
titles = ['Mask 1','Mask 2','Mask 3']
f_size = 22
#Sky Filter
lower_mask_1 = image_hsv [:,:,0] > 0.45
upper_mask_1 = image_hsv [:,:,0] < 0.75
saturation_1 = image_hsv [:,:,1] > 0.15
mask_1= lower_mask_1*upper_mask_1*saturation_1
#Red Filter
lower_mask_2 = image_hsv [:,:,0] > 0.90
upper_mask_2 = image_hsv [:,:,0] < 1.00
saturation_2 = image_hsv [:,:,1] > 0.50
mask_2= lower_mask_2*upper_mask_2*saturation_2
#Earth Fikter
lower_mask_3 = image_hsv [:,:,0] > 0.05
upper_mask_3 = image_hsv [:,:,0] < 0.15
saturation_3 = image_hsv [:,:,1] < 0.75
value_3 = image_hsv [:,:,2] < 0.70
mask_3= lower_mask_3*upper_mask_3*saturation_3*value_3 sky_filtered = np.dstack((image[:,:,0]*mask_1,
image[:,:,1]*mask_1,
image[:,:,2]*mask_1))
red_filtered = np.dstack((image[:,:,0]*mask_2,
image[:,:,1]*mask_2,
image[:,:,2]*mask_2))
earth_filtered = np.dstack((image[:,:,0]*mask_3,
image[:,:,1]*mask_3,
image[:,:,2]*mask_3))
images = [sky_filtered,red_filtered,earth_filtered]
fig, ax = plt.subplots(1, 3, figsize=(15,12))
for n, ax in enumerate(ax.flatten()):
ax.set_title(f'{titles[n]}', fontsize = f_size)
ax.imshow(images[n])
ax.set_axis_off()
fig.tight_layout()
color_isolates(red_girl)

不同颜色的分离物
当然,人们可以很容易地看到手动调节的问题。它要求对每个图像进行不同的处理。如果我们处理数百张图像,这种方法是不可行的。
总之
我们看到,起初看起来复杂的问题,比如告诉机器隔离特定的颜色,可以相对容易地处理。我们学到了不要把自己局限于仅仅处理 RGB 颜色空间的重要性。当处理需要颜色识别的问题时,HSV 颜色空间的知识非常有用。在未来,我们将学习如何创建脚本来识别相似的颜色并将它们组合在一起,但现在我希望你能够了解图像分割的许多可能性。
使用 Python 进行影像处理-提取影像数据进行聚类
如何从图像中提取更多的特征来改善聚类结果

k 表示生成的艺术(作者的图像)
在之前的一篇文章中,我们探讨了应用 K-Means 算法来自动分割我们的图像的想法。然而,我们只关注 RGB 颜色空间。当然,RGB 颜色空间是大多数图像的原生格式,但是在本文中,我们将超越它,看看使用不同的颜色空间对结果聚类的影响。
我们开始吧!
像往常一样,让我们从导入所需的 Python 库开始
import numpy as np
import pandas as pd
import matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3D
from matplotlib import colors
from skimage.color import rgb2gray, rgb2hsv, hsv2rgb
from skimage.io import imread, imshow
from sklearn.cluster import KMeans
很好,现在让我们加载将要使用的图像。
island = imread('island_life.png')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(island);

拍摄于菲律宾东部的一个岛屿(图片由作者提供)
像以前一样,我们可以尝试通过 K-Means 算法分离这张图像。
def image_to_pandas(image):
df = pd.DataFrame([image[:,:,0].flatten(),
image[:,:,1].flatten(),
image[:,:,2].flatten()]).T
df.columns = ['Red_Channel','Green_Channel','Blue_Channel']
return dfdf_island = image_to_pandas(island)plt.figure(num=None, figsize=(8, 6), dpi=80)
kmeans = KMeans(n_clusters= 5, random_state = 42).fit(df_island)
result = kmeans.labels_.reshape(island.shape[0],island.shape[1])plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(result, cmap='twilight');

聚类图像
将聚类计数设置为 5,该算法将图像聚类成这些不同的聚类。为了更好地了解每个集群代表什么,让我们将这个遮罩应用到我们的原始图像。

被群集屏蔽的原始图像
我们可以看到,K 均值算法把图像分成了以上几个部分。我们注意到的一个明显的事情是,该算法似乎将植物的亮部分从暗部分中分离出来,它做了一些类似于天空的事情。考虑到这一点,让我们尝试通过减少集群的数量来折叠这些集群。
plt.figure(num=None, figsize=(8, 6), dpi=80)
kmeans = KMeans(n_clusters = 3, random_state = 42).fit(df_island)
result = kmeans.labels_.reshape(island.shape[0],island.shape[1])plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(result, cmap='magma');

将聚类设置为 3
现在让我们将这些结果应用于原始图像。
def masker(image,masks):
fig, axes = plt.subplots(1, 3, figsize=(12, 10))
image_copy = image.copy()
for n, ax in enumerate(axes.flatten()):
masked_image = np.dstack((image_copy[:, :, 0]*(masks==[n]),
image_copy[:, :, 1]*(masks==[n]),
image_copy[:, :, 2]*(masks==[n])))
ax.imshow(masked_image)
ax.set_title(f'Cluster : {n+1}', fontsize = 20)
ax.set_axis_off();
fig.tight_layout()
masker(island,result)

集群设置为 3
我们可以看到,该算法现在认为天空是一个单一的集群。另外两个星团似乎是根据它们绿色的亮度来划分的。尽管这种算法似乎做得很好,但尝试通过不同的色彩空间对图像进行聚类可能会有所收获。
HSV 颜色空间
另一种可视化图像的方式是通过它的 HSV 颜色空间表示。简单来说,HSV 代表色相、饱和度和值。由于其直观性,它是最终用户的理想色彩空间。在未来,我们将回顾不同的色彩空间及其应用。然而,现在让我们坚持将其应用于 K 均值聚类算法。
def image_to_pandas_hsv(image):
hsv_image = rgb2hsv(image)
df = pd.DataFrame([hsv_image[:,:,0].flatten(),
hsv_image[:,:,1].flatten(),
hsv_image[:,:,2].flatten()]).T
df.columns = ['Hue','Saturation','Value']
return dfdf_hsv = image_to_pandas_hsv(island)
df_hsv.head(5)

带有 HSV 元素的熊猫数据帧
很好,现在让我们将 K 均值算法应用于它并检查结果。
plt.figure(num=None, figsize=(8, 6), dpi=80)
kmeans = KMeans(n_clusters = 3, random_state = 42).fit(df_hsv)
result_hsv = kmeans.labels_.reshape(island.shape[0],island.shape[1])plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(result_hsv, cmap='magma');

HSV 聚类图像
不幸的是,HSV 色彩空间似乎生成了与 RGB 色彩空间非常相似的聚类。对原始图像应用蒙版证实了这一点。

HSV 聚类图像
那么,我们能做些什么来产生一种不同的集群呢?我们可以把两个数据帧连接起来。将元素彼此相加将允许 K 均值算法能够更好地区分每个特定的聚类。
连接数据帧
为了连接数据帧,我们只需要在 Pandas 中运行 concat 函数。
stacked_features = pd.concat([df_island, df_hsv], axis=1)
stacked_features.head(5)

级联数据帧
现在让我们应用 K 均值聚类算法,看看是否有任何显著的差异。
plt.figure(num=None, figsize=(8, 6), dpi=80)
kmeans = KMeans(n_clusters= 3, random_state =
42).fit(stacked_features)
result_stacked = kmeans.labels_.reshape
(island.shape[0],island.shape[1])
imshow(result_stacked, cmap='magma')
plt.show();

聚集堆栈
可悲的是,结果似乎是一样的,我希望它至少会有点不同。也许将来我们会讨论不同的聚类算法,并最终得到一些区别。
总之
尽管这一发现并不光彩,但它们确实说明了 K 均值算法的局限性。虽然理论上我们可以将更多的数据添加到数据帧中,但是我们最好使用其他的聚类算法,比如谱聚类和凝聚聚类。现在,我希望你对如何使用 K 均值算法的参数有一个更好的想法,并且希望你可以更有创造性地表现你的图像。
使用 Python 进行图像处理—使用 Scikit-Image 进行模板匹配
如何识别图像中的相似物体

鲁汶市政厅的照片(图片由作者提供)
模板匹配是用于识别图片中感兴趣的对象的有用技术。不同于类似的物体识别方法,例如图像遮蔽和斑点检测。模板匹配很有帮助,因为它允许我们识别更复杂的图形。本文将讨论如何在 Python 中实现这一点。
我们开始吧!
和往常一样,首先导入所需的 Python 库。
import numpy as np
from skimage.io import imread, imshow
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, Rectangle
from skimage import transform
from skimage.color import rgb2gray
from skimage.feature import match_template
from skimage.feature import peak_local_max
很好,现在让我们加载将要使用的图像。
leuven = imread('leuven_picture.PNG')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(leuven);

鲁汶市政厅(图片由作者提供)
上面的图片是我几年前拍摄的鲁汶市政厅。它极具装饰性的拱门绝对是一个值得一看的景观。对于我们的任务,让我们尝试使用模板匹配来尽可能多地识别它们。我们的第一步当然是将图像转换成灰度。
leuven_gray = rgb2gray(leuven)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(leuven_gray);

灰度鲁汶市政厅
太好了,现在让我们挑选一个窗口,并将其用作模板。要做到这一点,我们只需剪切掉图像的那一部分。
template = leuven_gray[310:390,240:270]
imshow(template);

一片鲁汶
此时我们可以将模板送入 Skimage 的 match_template 函数中。
resulting_image = match_template(leuven_gray, template)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(resulting_image, cmap='magma');

函数生成的图像
以上是使用 match_template 函数的结果。简而言之,图像的部分越亮,就越接近模板。让我们看看函数认为与模板最匹配的图像部分。
x, y = np.unravel_index(np.argmax(resulting_image), resulting_image.shape)
template_width, template_height = template.shape
rect = plt.Rectangle((y, x), template_height, template_width,
color='r', fc='none')
plt.figure(num=None, figsize=(8, 6), dpi=80)
plt.gca().add_patch(rect)
imshow(leuven_gray);

最佳匹配标识
我们可以看到图像能够正确地识别模板的完美匹配(为了验证,您可以使用我们使用的切片坐标进行检查)。这对于任何需要在图像中搜索对象的精确匹配的任务来说都是非常有用的。
现在让我们看看我们是否能得到识别其他窗口的函数,这些窗口或多或少与我们的模板相似。
template_width, template_height = template.shape
plt.figure(num=None, figsize=(8, 6), dpi=80)
for x, y in peak_local_max(result, threshold_abs=0.5,
exclude_border = 20):
rect = plt.Rectangle((y, x), template_height, template_width,
color='r', fc='none')
plt.gca().add_patch(rect)
imshow(leuven_gray);

多个模板匹配
我们看到,虽然该函数确实准确地识别了其他几个窗口。它还会错误地识别出其他几个明显不是窗户的物体。让我们看看能否减少假阳性的数量。
我们可以补救的一种方法是利用单应矩阵。我以前写过一篇关于如何在 Skimage 中使用 transform.warp 函数的文章,但是通常它会扭曲图像,使图像看起来像是从另一个角度拍摄的。
points_of_interest =[[240, 130],
[525, 255],
[550, 545],
[250, 545]]
projection = [[180, 150],
[520, 150],
[520, 550],
[180, 550]]
color = 'red'
patches = []
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
for coordinates in (points_of_interest + projection):
patch = Circle((coordinates[0],coordinates[1]), 10,
facecolor = color)
patches.append(patch)for p in patches[:4]:
ax[0].add_patch(p)
ax[0].imshow(leuven_gray);for p in patches[4:]:
ax[1].add_patch(p)
ax[1].imshow(np.ones((leuven_gray.shape[0], leuven_gray.shape[1])));

原始角点与目标角点
points_of_interest = np.array(points_of_interest)
projection = np.array(projection)
tform = transform.estimate_transform('projective', points_of_interest, projection)
tf_img_warp = transform.warp(leuven, tform.inverse)
plt.figure(num=None, figsize=(8, 6), dpi=80)
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
ax[0].set_title(f'Original', fontsize = 15)
ax[0].imshow(leuven)
ax[0].set_axis_off();
ax[1].set_title(f'Transformed', fontsize = 15)
ax[1].imshow(tf_img_warp)
ax[1].set_axis_off();

变换图像
我们可以看到图像现在面向前方。由于我们已经减轻了角度对模板匹配的影响,让我们看看我们是否得到了更好的结果。像以前一样,让我们首先将图像转换成灰度,然后应用变换函数。
points_of_interest = np.array(points_of_interest)
projection = np.array(projection)
tform = transform.estimate_transform('projective', points_of_interest, projection)
tf_img_warp = transform.warp(leuven_gray, tform.inverse)
plt.figure(num=None, figsize=(8, 6), dpi=80)
fig, ax = plt.subplots(1,2, figsize=(15, 10), dpi = 80)
ax[0].set_title(f'Original', fontsize = 15)
ax[0].imshow(leuven_gray, cmap = 'gray')
ax[0].set_axis_off();
ax[1].set_title(f'Transformed', fontsize = 15)
ax[1].imshow(tf_img_warp, cmap = 'gray')
ax[1].set_axis_off();

转换灰度
result = match_template(tf_img_warp, template)
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(result, cmap=’magma’);

模板匹配的结果(或地下世界的城堡)
现在让我们应用和以前完全一样的代码,看看我们是否能得到更好的结果。
template_width, template_height = template.shape
plt.figure(num=None, figsize=(8, 6), dpi=80)
for x, y in peak_local_max(result, threshold_abs=0.5,
exclude_border = 10):
rect = plt.Rectangle((y, x), template_height, template_width,
color='r', fc='none')
plt.gca().add_patch(rect)
imshow(tf_img_warp);

变换图像上的多个模板匹配
我们可以看到,该算法仍然可以识别图像上的每个窗口,但是它仍然有那些讨厌的假阳性。为了减轻这种情况,让我们应用一个与模板匹配的过滤器。下面是一些代码来处理我们的数据争论,如果它们有点晦涩,我很抱歉。
template_width, template_height = template.shape
matched_list = []
for x, y in peak_local_max(result, threshold_abs=0.50, exclude_border = 10):
rect = plt.Rectangle((y, x), template_height, template_width)
coord = Rectangle.get_bbox(rect).get_points()
matched_list.append(coord)
matched_patches = [tf_img_warp[int(match[0][1]):int(match[1][1]),
int(match[0][0]):int(match[1][0])] for match in matched_list]difference = [abs(i.flatten() - template.flatten()) for i in matched_patches]
summed_diff = [array.sum() for array in difference]final_patches =list(zip(matched_list,summed_diff))
statistics.mean(summed_diff)
运行上述代码后,我们现在可以创建模板匹配的过滤列表。再次道歉,如果代码可能不是那么容易遵循。
summed_diff = np.array(summed_diff)
filtered_list_mean =list(filter(lambda x: x[1] <=
summed_diff.mean(), final_patches))
filtered_list_median =list(filter(lambda x: x[1] <=
np.percentile(summed_diff, 50),
final_patches))
filtered_list_75 =list(filter(lambda x: x[1] <=
np.percentile(summed_diff, 75),
final_patches))
上面的代码应该通过平均差、中值差和 75%百分位差来过滤匹配项。本质上,它将只保存绝对差异低于这些阈值的匹配。最后一步是将这些绘制出来,看看结果是否有所改善。
fig, ax = plt.subplots(1,3, figsize=(17, 10), dpi = 80)
template_width, template_height = template.shapefor box in filtered_list_mean:
patch = Rectangle((box[0][0][0],box[0][0][1]), template_height,
template_width, edgecolor='b',
facecolor='none', linewidth = 3.0)
ax[0].add_patch(patch)
ax[0].imshow(tf_img_warp, cmap = 'gray');
ax[0].set_axis_off()for box in filtered_list_median:
patch = Rectangle((box[0][0][0],box[0][0][1]), template_height,
template_width, edgecolor='b',
facecolor='none', linewidth = 3.0)
ax[1].add_patch(patch)
ax[1].imshow(tf_img_warp, cmap = 'gray');
ax[1].set_axis_off()for box in filtered_list_75:
patch = Rectangle((box[0][0][0],box[0][0][1]), template_height,
template_width,
edgecolor='b', facecolor='none',
linewidth = 3.0)
ax[2].add_patch(patch)
ax[2].imshow(tf_img_warp, cmap = 'gray');
ax[2].set_axis_off()fig.tight_layout()

模板过滤图像
我们可以看到,它们看起来确实比原始图像好得多。然而,我们注意到,尽管均值和中值的假阳性少得多,但它们的真阳性也少得多。然而,75 Perc 过滤器能够保留几乎所有的真阳性。
总之
如果模板是特别复杂的图像,那么模板匹配可能是一件棘手的事情。我们必须记住,虽然我们作为人类可以将图像理解为一个简单的窗口,但机器只能看到一个矩阵。有两种方法可以解决这个问题。一个是通过确保模板足够独特,假阳性将很少,另一个是开发一个复杂的过滤系统,能够准确地从数据中删除任何假阳性。像这样的主题值得写几篇文章,将来我们将回顾一些关于模板匹配的最佳实践。现在,我希望你能够学会如何在你自己的项目中使用模板匹配,并提前考虑如何处理不可避免的问题。
基于 Python 的图像处理——图像分割的无监督学习
如何使用 K-Means 算法自动分割图像

现代艺术 Doggos(作者图片)
到目前为止,我们讨论的大多数技术都要求我们通过图像的特征来手动分割图像。但是我们实际上可以使用无监督聚类算法来为我们做到这一点。在这篇文章中,我们将回顾如何做到这一点。
我们开始吧!
和往常一样,我们从导入所需的 Python 库开始。
import numpy as np
import pandas as pd
import matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3D
from matplotlib import colors
from skimage.color import rgb2gray, rgb2hsv, hsv2rgb
from skimage.io import imread, imshow
from sklearn.cluster import KMeans
很好,现在让我们导入将要使用的图像。
dog = imread('beach_doggo.PNG')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(dog);

海滩上的狗(图片由作者提供)
我们知道,图像本质上是一个三维矩阵,每个单独的像素包含红色、绿色和蓝色通道的值。但是我们实际上可以使用心爱的熊猫库将每个像素存储为一个单独的数据点。下面的代码就是这么做的。
def image_to_pandas(image):
df = pd.DataFrame([image[:,:,0].flatten(),
image[:,:,1].flatten(),
image[:,:,2].flatten()]).T
df.columns = [‘Red_Channel’,’Green_Channel’,’Blue_Channel’]
return dfdf_doggo = image_to_pandas(dog)
df_doggo.head(5)

图像为熊猫数据帧
这使得图像的操作更简单,因为更容易将其视为可以输入机器学习算法的数据。在我们的例子中,我们将利用 K 均值算法对图像进行聚类。
plt.figure(num=None, figsize=(8, 6), dpi=80)
kmeans = KMeans(n_clusters= 4, random_state = 42).fit(df_doggo)
result = kmeans.labels_.reshape(dog.shape[0],dog.shape[1])
imshow(result, cmap='viridis')
plt.show()

集群狗
正如我们所看到的,图像分为 4 个不同的区域。让我们分别想象每个区域。
fig, axes = plt.subplots(2,2, figsize=(12, 12))
for n, ax in enumerate(axes.flatten()):
ax.imshow(result==[n], cmap='gray');
ax.set_axis_off()
fig.tight_layout()

每个集群
正如我们所见,该算法根据 R、G 和 B 像素值分割图像。一个不幸的缺点是,这是一个完全无监督的学习算法。它并不特别关心任何特定群集背后的含义。作为证据,我们可以看到,第二个和第四个集群都有一个突出的狗的一部分(阴影的一半和非阴影的一半)。也许运行 4 个集群太多了,让我们重试集群,但是将集群的数量设置为 3。

重新聚集的狗
很好,我们可以看到狗是作为一个整体出来的。现在让我们看看,如果我们将每个聚类作为单独的蒙版应用于我们的图像,会发生什么。
fig, axes = plt.subplots(1,3, figsize=(15, 12))
for n, ax in enumerate(axes.flatten()):
dog = imread('beach_doggo.png')
dog[:, :, 0] = dog[:, :, 0]*(result==[n])
dog[:, :, 1] = dog[:, :, 1]*(result==[n])
dog[:, :, 2] = dog[:, :, 2]*(result==[n])
ax.imshow(dog);
ax.set_axis_off()
fig.tight_layout()

给狗戴上面具
我们可以看到,该算法生成了三个不同的集群,沙子、生物和天空。当然,算法本身并不太关心这些聚类,只关心它们共享相似的 RGB 值。这是由我们人类来解释这些集群。
在我们离开之前,我认为如果我们简单地在 3D 图上画出来,实际上展示我们的图像看起来像什么会有帮助。
def pixel_plotter(df):
x_3d = df['Red_Channel']
y_3d = df['Green_Channel']
z_3d = df['Blue_Channel']
color_list = list(zip(df['Red_Channel'].to_list(),
df['Blue_Channel'].to_list(),
df['Green_Channel'].to_list())) norm = colors.Normalize(vmin=0,vmax=1.)
norm.autoscale(color_list)
p_color = norm(color_list).tolist()
fig = plt.figure(figsize=(12,10))
ax_3d = plt.axes(projection='3d')
ax_3d.scatter3D(xs = x_3d, ys = y_3d, zs = z_3d,
c = p_color, alpha = 0.55);
ax_3d.set_xlim3d(0, x_3d.max())
ax_3d.set_ylim3d(0, y_3d.max())
ax_3d.set_zlim3d(0, z_3d.max())
ax_3d.invert_zaxis()
ax_3d.view_init(-165, 60)pixel_plotter(df_doggo)

像素的三维表示
我们应该记住,这实际上是算法如何定义“接近度”的。如果我们将 K-Means 算法应用于这个图,它分割图像的方式就变得非常清楚了。
df_doggo['cluster'] = result.flatten()def pixel_plotter_clusters(df):
x_3d = df['Red_Channel']
y_3d = df['Green_Channel']
z_3d = df['Blue_Channel']
fig = plt.figure(figsize=(12,10))
ax_3d = plt.axes(projection='3d')
ax_3d.scatter3D(xs = x_3d, ys = y_3d, zs = z_3d,
c = df['cluster'], alpha = 0.55);
ax_3d.set_xlim3d(0, x_3d.max())
ax_3d.set_ylim3d(0, y_3d.max())
ax_3d.set_zlim3d(0, z_3d.max())
ax_3d.invert_zaxis()
ax_3d.view_init(-165, 60)pixel_plotter_clusters(df_doggo)

聚集像素
总之
K-Means 算法是一种流行的无监督学习算法,任何数据科学家都应该能够轻松使用。虽然它非常简单,但对于像素差异非常明显的图像来说,它尤其强大。在未来的文章中,我们将回顾其他机器学习算法,我们可以使用图像分割以及微调超参数。但是现在,我希望你能想象在你自己的任务中使用这种方法。
使用 Python 进行图像处理—使用 RG 色度
如何使用高斯分布进行图像分割

蒙面红树(图片由作者提供)
根据颜色分割图像是一项非常有用的技能。我以前写过一篇文章,介绍如何通过 RGB 和 HSV 色彩空间实现这一点。然而,另一种基于颜色分割图像的有效方法是利用 RG 色度和高斯分布。本文将讨论如何做到这一点。
我们开始吧!
和往常一样,首先导入所需的 Python 库。
import numpy as np
import matplotlib.pyplot as plt
from skimage.io import imread, imshow
from numpy import ndarray
from matplotlib.patches import Rectanglefrom mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib import colors
很好,现在让我们导入将要使用的图像。
budapest = imread('budapest.png')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(budapest);

匈牙利议会(图片由作者提供)
你们中的一些人可能对上面的建筑很熟悉,这是我几年前拍摄的匈牙利议会的照片。因为这是一个非常丰富多彩的图像,所以它非常适合我们的文章。
我相信我们大多数人都知道,我们屏幕上呈现的图像实际上是在 RGB 色彩空间中。使用红、绿和蓝的不同组合生成不同颜色光谱的颜色空间。为了帮助我们形象化,让我们把图像分成不同的部分。
def rgb_splitter(image):
rgb_list = ['Reds','Greens','Blues']
fig, ax = plt.subplots(1, 3, figsize=(17,7), sharey = True)
for i in range(3):
ax[i].imshow(image[:,:,i], cmap = rgb_list[i])
ax[i].set_title(rgb_list[i], fontsize = 22)
ax[i].axis('off')
fig.tight_layout()rgb_splitter(budapest)

不同的颜色通道
本质上,如果我们分离每种颜色,这将是结果。注意,仅仅因为某一颜色存在于像素中,并不一定意味着该像素在我们的人眼看来是黄色的。请记住,我们的屏幕只是模拟颜色,并不产生颜色本身。
现在让我们讨论 RG 色度。
RG 色度仅仅是 RGB 颜色空间的二维标准化版本。我们可以通过将每个颜色通道的值除以该特定像素的总和值,将图像转换到这个颜色空间。从编程的角度来看,它将采用下面的形式。
budapest_r = budapest[:,:,0] /budapest.sum(axis=2)
budapest_g = budapest[:,:,1] /budapest.sum(axis=2)
您可能会注意到,我们没有包括蓝色通道,这是特意设计的。我们知道,不管怎样,所有归一化像素之和应该等于 1 。因此,蓝色通道可以从其他两个通道获得。下面的代码就是这么做的。
one_matrix = np.ones_like(float,shape=budapest_r.shape)
budapest_b = one_matrix- (budapest_r +budapest_g)
现在你可能会问自己,“这个图像看起来像什么?”。让我们看看图像发生了什么变化。

原始与 RG 色度
请注意,在我们的图像中,白色实际上由黑色表示,所有像素的亮度都相同。在这一点上,你可能很难真正看到图像,这是因为人眼更适合根据光线对比度来区分物体(但我不是眼科医生,所以请不要问我细节)。
当然,我们是否能够实际可视化图像并不重要,RG 色度的真正优势在于能够在一个 2 轴图形中表示所有像素值(请记住可以导出蓝色通道)。
让我们画出如何在这样的图上表现我们的形象。
def RG_Chroma_plotter(red,green):
p_color = [(r, g, 1-r-g) for r,g in
zip(red.flatten(),green.flatten())]
norm = colors.Normalize(vmin=0,vmax=1.)
norm.autoscale(p_color)
p_color = norm(p_color).tolist() fig = plt.figure(figsize=(10, 7), dpi=100)
ax = fig.add_subplot(111) ax.scatter(red.flatten(),
green.flatten(),
c = p_color, alpha = 0.40) ax.set_xlabel('Red Channel', fontsize = 20)
ax.set_ylabel('Green Channel', fontsize = 20) ax.set_xlim([0, 1])
ax.set_ylim([0, 1]) plt.show()RG_Chroma_plotter(budapest_r,budapest_g)

RG 色度图
上面的图表给了我们一个很好的图像中不同像素的视觉表现。现在,我们如何利用这些知识来正确地分割颜色。
我们首先需要做的是从图像中选择一个补丁。
patch = budapest[500:510,50:60]
imshow(patch);

黄色补丁
让我们看看我们的补丁在 RG 色度图上的位置。
patch_r = patch[:,:,0] /patch.sum(axis=2)
patch_g = patch[:,:,1] /patch.sum(axis=2)
RG_Chroma_plotter(patch_r,patch_g)

补丁 RG 色度
我们要做的是创建一个遮罩,它将使用面片的属性作为输入来创建一个高斯分布。这样做的目的是定位补片的像素在原始图像上的位置,并使用该信息来创建蒙版。
下面是一个很有用的函数,它能帮我们做到这一点。
def gaussian(p,mean,std):
return np.exp(-(p-mean)**2/(2*std**2))*(1/(std*((2*np.pi)**0.5)))def rg_chroma_patch(image, patch_coor, mean = 1, std = 1):patch = image[patch_coor[0]:patch_coor[1],
patch_coor[2]:patch_coor[3]]
image_r = image[:,:,0] /image.sum(axis=2)
image_g = image[:,:,1] /image.sum(axis=2)
patch_r = patch[:,:,0] / patch.sum(axis=2)
patch_g = patch[:,:,1] / patch.sum(axis=2)
std_patch_r = np.std(patch_r.flatten())
mean_patch_r = np.mean(patch_r.flatten()) std_patch_g = np.std(patch_g.flatten())
mean_patch_g = np.mean(patch_g.flatten()) masked_image_r = gaussian(image_r, mean_patch_r, std_patch_r)
masked_image_g = gaussian(image_g, mean_patch_g, std_patch_g)
final_mask = masked_image_r * masked_image_g fig, ax = plt.subplots(1,2, figsize=(15,7))
ax[0].imshow(image)
ax[0].add_patch(Rectangle((patch_coor[2], patch_coor[0]),
patch_coor[1] - patch_coor[0],
patch_coor[3] - patch_coor[2],
linewidth=2,
edgecolor='b', facecolor='none')); ax[0].set_title('Original Image with Patch', fontsize = 22)
ax[0].set_axis_off()
#clean the mask using area_opening
ax[1].imshow(final_mask, cmap = 'hot');
ax[1].set_title('Mask', fontsize = 22)
ax[1].set_axis_off()
fig.tight_layout()
return final_maskfinal_mask = rg_chroma_patch(budapest, [500,510,50,60])

原始图像和遮罩
我们可以看到,该函数在创建遮罩方面做得相当不错。为了验证,让我们将蒙版应用到我们的图像,看看结果。首先让我们二值化我们的面具。
binarized_mask = final_mask > final_mask.mean()
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(binarized_mask)

二值化掩模
太好了,下面的函数将获取原始图像和二值化的蒙版,并给出最终的结果。
def apply_mask(image,mask):
yuv_image = rgb2yuv(image)
yuv_image[:,:,0] = yuv_image[:,:,0] * mask
masked_image = yuv2rgb(yuv_image)
fig, ax = plt.subplots(1,2, figsize=(15,7))
ax[0].imshow(image)
ax[0].set_title('Original Image', fontsize = 22)
ax[0].set_axis_off()
ax[1].imshow(masked_image);
ax[1].set_title('Masked Image', fontsize = 22)
ax[1].set_axis_off()
fig.tight_layout()

原始图像与蒙版图像
我们可以看到面具做得非常好。除了田地,其他都被涂黑了。注意我们是如何利用 Y'UV 颜色空间的,这是因为 Y'UV 颜色空间有一个专用于亮度的通道。
为了好玩,让我们将所有这些应用到另一个图像,看看我们是否能得到同样令人印象深刻的结果。
singapore = imread('singapore_street.png')
plt.figure(num=None, figsize=(8, 6), dpi=80)

新加坡一条空荡荡的街道(图片由作者提供)
我们将利用新加坡街道的上述图像。首先让我们看看图像的 RG 色度图是什么样的。
singapore_r = singapore[:,:,0] /singapore.sum(axis=2)
singapore_g = singapore[:,:,1] /singapore.sum(axis=2)
RG_Chroma_plotter(singapore_r,singapore_g)

新加坡 RG 色度
我们看到,与匈牙利议会的图像不同,新加坡的图像有更多的绿色和蓝色像素。
现在让我们试着挑选一个补丁并得到相关的掩码。
final_mask_singapore = rg_chroma_patch(singapore, [125,150,290,310])

新加坡面具
我们可以看到,蒙版在定位图像中的暗粉色物体方面做得很好。让我们看看最终的蒙版图像是什么样的。
binarized_mask_singapore = final_mask_singapore >
final_mask_singapore.mean()
apply_mask(singapore,binarized_mask_singapore)

蒙面新加坡形象
我们可以看到,它在屏蔽图像方面也做得很好。注意灯笼是如何被整齐地切割出来的。然而,请注意许多建筑也包括在内。在未来的文章中,我们将学习如何调整参数,以确保最终的图像更干净。
总之
RG 色度是您可以用于颜色分割的最强大的工具之一。它干净地屏蔽图像的能力是我们所介绍的其他技术无法比拟的。前进将调整参数,以确保我们可以获得更清晰的图像,但现在我希望你能够欣赏这个非常有用的方法。
使用 Python 进行图像处理-使用熵
如何使用熵分离物体纹理

熵图像(作者提供的图像)
除了根据颜色分离物体,另一种分离物体的方法是通过物体的纹理。要做到这一点,我们可以利用 Skimage 中的熵函数。在这篇文章中,我们将学习如何使用函数有效地提取图像中感兴趣的对象。
我们开始吧!
和往常一样,首先导入所需的 Python 库。
import matplotlib.pyplot as plt
import numpy as np
from skimage.io import imread, imshowfrom skimage import data
from skimage.util import img_as_ubyte
from skimage.filters.rank import entropy
from skimage.morphology import disk
from skimage.color import rgb2hsv, rgb2gray, rgb2yuv
现在让我们导入将要使用的图像。
shawls = imread('shawls.PNG')
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(shawls);

彩色披肩(图片由作者提供)
上图是不同印花和纹理的披肩。让我们试着看看我们是否能根据这些特征把他们分开。作为开始,让我们首先把我们的图像转换成灰度。
shawl_gray = rgb2gray(imread('shawls.PNG'))
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(shawl_gray);

灰色披肩
很好,从这一点我们现在可以应用撇除的熵函数。
entropy_image = entropy(shawl_gray, disk(5))
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(entropy_image, cmap = 'magma');

应用熵函数
简而言之,熵函数给出一个值,该值表示图像的某一部分的复杂程度。结果值当然取决于我们选择的初始结构元素。作为一个例子,让我们通过改变圆盘的初始半径来进行实验。
def disk_iterations(image):
image_gray = rgb2gray(image)
f_size = 20
radi = list(range(1,10))
fig, ax = plt.subplots(3,3,figsize=(15,15))
for n, ax in enumerate(ax.flatten()):
ax.set_title(f'Radius at {radi[n]}', fontsize = f_size)
ax.imshow(entropy(shawl_gray, disk(radi[n])), cmap =
'magma');
ax.set_axis_off()
fig.tight_layout()

半径设置为不同的值
我们可以看到,如果我们增加光盘半径,图像变得越来越模糊。由于本练习的目标是基于纹理分割图像,因此我们不需要包含不同纹理图像部分的大小。让我们选择 6 的半径,因为它似乎是 1 的锐度和 9 的钝度之间的良好平衡。
我们的下一个任务是把它变成一个面具。为此,让我们使用图像二值化。下面的代码将迭代几个阈值。
def threshold_checker(image):
thresholds = np.arange(0.1,1.1,0.1)
image_gray = rgb2gray(image)
entropy_image = entropy(image_gray, disk(6))
scaled_entropy = entropy_image / entropy_image.max()
fig, ax = plt.subplots(2, 5, figsize=(17, 10))
for n, ax in enumerate(ax.flatten()):
ax.set_title(f'Threshold : {round(thresholds[n],2)}',
fontsize = 16)
threshold = scaled_entropy > thresholds[n]
ax.imshow(threshold, cmap = 'gist_stern_r') ;
ax.axis('off')
fig.tight_layout()

二值化的不同阈值
我们可以看到,增加二值化的阈值会减少图像的显示量。直观上,这是有意义的,因为一旦阈值等于 1,就没有图像的任何部分可以匹配该熵水平。
出于可视化的目的,让我们将阈值设置为 0.8,看看当我们使用它作为图像的遮罩时会发生什么。
scaled_entropy = shawl_gray / shawl_gray.max()
entropy_image = entropy(scaled_entropy, disk(6))
scaled_entropy = entropy_image / entropy_image.max()
mask = scaled_entropy > 0.8
plt.figure(num=None, figsize=(8, 6), dpi=80)
imshow(shawl_gray * mask, cmap = 'gray');

熵屏蔽图像
正如预期的那样,可以看到只有违反熵的对象才能被渲染。如果我们翻转数学运算符,我们可以看到相反的效果。

反向熵掩蔽图像
注意只有低熵物体被渲染。为了有助于可视化,让我们将这两个遮罩转换为其原始彩色图像,并并排比较它们。
def entropy_mask_viz(image):
image_gray = rgb2gray(image)
entropy_image = entropy(image_gray, disk(6))
scaled_entropy = entropy_image / entropy_image.max()
f_size = 24
fig, ax = plt.subplots(1, 2, figsize=(17, 10))
ax[0].set_title('Greater Than Threshold',
fontsize = f_size)
threshold = scaled_entropy > 0.8
image_a = np.dstack([image[:,:,0]*threshold,
image[:,:,1]*threshold,
image[:,:,2]*threshold])
ax[0].imshow(image_a)
ax[0].axis('off')
ax[1].set_title('Less Than Threshold',
fontsize = f_size)
threshold = scaled_entropy < 0.8
image_b = np.dstack([image[:,:,0]*threshold,
image[:,:,1]*threshold,
image[:,:,2]*threshold])
ax[1].imshow(image_b)
ax[1].axis('off')
fig.tight_layout()
return [image_a, image_b]
entropic_images = entropy_mask_viz(shawls)

基于熵值的彩色图像分割
我们可以看到,我们能够成功地按照对象的复杂程度来划分图形。左图中的物品展示了更复杂的设计图案(以及由更复杂纹理的织物制成)。右图中的物体要简单得多,只包含一种颜色。
一个有趣的现象是,我们是如何将文字从书写文字的纸上分离出来的。

正文段
记住这一点很有用。人类的文本倾向于写在简单的背景上以方便阅读。了解这一点意味着从图像中提取所有文本特征是可能的,但这是我们将留待下次完成的任务。
总之
熵屏蔽是一种有用的技术,可以帮助数据科学家根据复杂性分割图像的各个部分。应用范围从纹理分析、图像过滤,甚至文本提取(这一特性非常适合自然语言处理)。我希望在读完这篇文章之后,你能对如何使用这个工具有一个更好的评价和理解。
图像分割:改变你的车的颜色!分步指南。

图片来自 Gabor Papp 来自 unsplash 由本人标注
你有没有想过你的车换成另一种颜色会是什么样子?或者你只是对训练一个不需要理论知识的图像分割任务的深度学习模型感兴趣?或者两者都有?那么这个帖子就是给你的!
将有两个主要部分:首先如何为图像分割任务创建数据集,然后如何创建和训练模型。如果你只对第二部分感兴趣,可以直接看!我为它提供了一个数据集。
收集图像和创建遮罩注释
首先,我们需要收集汽车侧面图像。收集这类数据的方法有很多,我可以建议以下方法:
不溅
Unsplash 是一个伟大的网站,它分享了大量免费提供的图片,并附有宽松的版权条款。这对我特别有用,因为我想公开分享一些照片,比如在这个博客上。这里的缺点是没有太多的图片特别是汽车侧视图。
谷歌图片
Google Chrome 和 Firefox 有一个很好的扩展,可以让你下载当前活动标签的所有图片。非常有用,如果你用谷歌搜索“汽车侧面-侧面-素描-卡通”,然后进入图像标签。“-”符号表示您希望从搜索中排除这些关键字。
在野外
如果你有一个好相机,你也可以简单地四处走走,自己拍照!如果要公开数据集,请确保模糊人物和车牌。出于版权原因,我个人选择以这种方式调整我的数据集。
你可能需要收集大约 200 张图像,才能通过我们的深度学习模型获得满意的结果。
屏蔽注释
掩膜注记是一幅图像,其像素值代表类,如下图所示。它可以遵循不同的格式:黑白 PNG、彩色 PNG、COCO style JSON 等…

多彩 PNG 蒙版标注:黑色为背景,红色为轮子,绿色为窗户,紫色为灯光,蓝色为车身。由我自己生成。
还有许多工具可用于注释图像。我决定用 VoTT ,可以从他们的 GitHub 页面这里下载。如果你想为图像分割创建边界框或多边形,这真的很有用。多边形并不是所有的图像分割模型都能直接理解的,但是有软件能够为我们将多边形转化为实际的遮罩。我们将使用智能人工智能。
一旦你下载了 VoTT,启动它。建立一个项目,其中“目标连接”是您想要保存注释图像的位置,“源连接”是您的图像数据集的位置。


图片来自 Malusi Msom 本人来自 unsplash 自己注释
点击左上角的多边形选项,开始你的注释之旅!完成多边形绘制后,按 escape 键并选择标签。如果你纠结于这个软件,我建议你看看这个快速的教程。
当您完成注释后,还有最后一件重要的事情要做!在左侧面板中,转到导出设置并选择 VoTT 格式。然后在顶部面板上,单击导出。它应该会产生一个大的 JSON 文件,类似于[project-name]-export.json。这个文件非常重要:它包含了 Intelec AI 可以理解并为我们翻译的格式的所有注释。
正如上面的截图所示,我个人决定将汽车分为 4 个不同的类别:汽车本身,以了解它在图片上的位置,车轮,前后灯(标有灯),最后是窗户,后窗和挡风玻璃(标有窗)
如果你想在不标注自己数据集的情况下尝试训练一个模型,我在这里提供了我的!
使用智能人工智能训练模型
对于这个演示,我选择向您展示 Intelec AI,因为它易于使用,并且经常产生良好的效果。他们为不同的任务提出不同的模型,并为我们处理大部分数据争论。多亏了 VoTT 和 Intelec AI,在本教程中你实际上不会看到一行代码!
Intelec AI 可以从这个链接下载。你需要有码头工人。其安装在第页中有很好的解释。我不会在这里详细解释 docker 是如何工作的,但是如果你和我一样是 Linux 用户,我建议你设置 Docker 在没有 sudo 的情况下使用,就像这个页面建议的那样。
一旦安装了 Docker 和 Intelec AI,我们就可以创建一个 ZIP 文件夹,其结构与下面的一个截图相同。您需要注意结构:文件夹和 JSON 文件的名称应该与下面的截图完全匹配。
如果您自己注释了图像,请注意将您的【项目名称】-export.json 重命名为 masks.json


你的 zip 文件夹应该是什么样子的例子。这个演示是基于左边的,或者如果你直接下载数据集的话,是基于右边的。
然后,进入 Intelec AI 的“文件浏览器”标签,上传你的 zip 文件夹。右键点击解压。现在,我们可以直接进入培训页面,创建一个图像分割培训器!将它链接到我们刚刚上传的数据,选择一个收缩因子(如果你有一个强大的 GPU,我建议因子为 2,否则因子为 5),然后按“训练”。你可以喝杯咖啡,过一会儿回来看看你的结果。
对我来说,用一个 GPU 训练 22 个纪元不到 5 分钟!Intelec AI 提供了如下所示的摘要:


Intelec AI 软件截图
如果你想知道模型在不同时期的训练效果如何,我保存了一些中间结果,如下所示:




从左上到右下,经过 14、6、4 和 2 个时期。除封面使用的图片外,其他图片均为本人拍摄,来自 Gabor Papp 取自 unsplash。
右下角显示,它已经开始了解汽车的两个主要部件之间的区别:车身和车轮。左下角显示了围绕实际汽车进行分段时的良好改进。右上角显示,它也开始正确猜测窗口的位置。左上角显示了它变得有多精确,也开始检测背光。不过,它的前灯还是有问题。如果这是你真正想要检测的,它可能需要更多的历元,因为它比其他物体小。Intelec AI 让您轻松训练额外的 epochs:只需再次按下“开始”按钮。
做出推论
我很好奇,想看看模型是否过度拟合,所以决定尝试分割我自己的车!我在 Intelec AI 软件中部署了我的模型,给我漂亮的菲亚特 Seicento sport 拍了张照片,然后上传到“部署的模型”标签中。下面是结果!

我的菲亚特 Seicento 的原图,我自己拍的


结果:左边完全不透明,右边半不透明
老实说,我真的没想到这个模特能做这么棒的工作!现在,很容易使用你想在自己的车上试用的车轮的精灵,因为它的位置是由模型以非常好的精确度自动检测的。
正如你现在所看到的,现在进行深度学习不再困难:你不需要高超的编码技能,也不需要对所有数学和神经网络的微妙之处有深刻的理解。
图像分割(第一部分)
阈值、Otsu 和 HSV 分割

(图片由作者提供)
图像处理对数据科学最重要的贡献之一是能够使用处理技术对图像进行不同的分割。所谓分割,我们是指从背景中分割出不同的对象。通常,如果我们有一个原始图像,并且我们想要创建图像中的对象的数据集,我们会想要首先隔离这些对象。但是我们怎么做呢?
我们使用不同的图像分割技术来分离这些不同的对象。
有许多常用的分段技术,但是对于本文的第 1 部分,我们将强调并讨论以下内容:
- 试错阈值
- 大津法
- HSV 空间分割
让我们加载一个示例图像:

图 1:样本图像(作者提供的图像)
我们的示例图像是一组简单的棕色背景上的小花。我们在这篇文章中的挑战是能够从背景中分割出每一朵花。我们将尝试使用提及图像分割,看看我们是否最终获胜。
试错阈值
图像处理中的反复试验一直是常态,尤其是在处理新图像时。这与阈值方法的方式相同。我们试图确定最佳值,我们可以阈值的图像和削弱我们想要的对象。
通常在阈值处理中,我们尝试不同的阈值,比较和对比哪个结果更好。下面的例子说明了我们如何做到这一点:
#experimented threshold values
sample_t = sample_g>0.70
sample_t1 = sample_g>0.50fig, ax = plt.subplots(1,3,figsize=(15,5))
im = ax[0].imshow(sample_g,cmap='gray')
fig.colorbar(im,ax=ax[0])
ax[1].imshow(sample_t,cmap='gray')
ax[0].set_title('Grayscale Image',fontsize=15)
ax[1].set_title('Threshold at 0.70',fontsize=15)ax[2].imshow(sample_t1,cmap='gray')
ax[2].set_title('Threshold at 0.50',fontsize=15)
plt.show()

图 2:使用阈值分割(图片由作者提供)
我们可以在图中看到,两个不同的阈值分别为 0.7 和 0.6。请注意,这些阈值彼此非常接近,但是使用每个阈值的结果都很明显。在值为 0.70 时,我们可以清楚地分割白花,而在值为 0.50 时,我们确实分割了白花,但被该范围内的其他像素值连接。如果我们只想分割白花,那么最佳阈值大约是 0.7。
找到最佳阈值的一个方法是查看灰度图旁边的颜色条,从这里我们可以选择我们可以设置的范围来提取我们需要的对象。
大津法
下一个技术叫做 Otsu 法,实际上与试错法几乎相同,但这次是自动化的。这种方法是由大津信行开发的。otsu 方法背后的思想是,该方法检查像素值,并通过最小化直方图上的方差来找到可以将两个类一分为二的最佳平衡点。
已经有一个预定义的 scikit-function,可以调用它以方便使用。
from skimage.filters import threshold_otsuthresh = threshold_otsu(sample_g)
sample_ot = sample_g > thresh

图 3:使用 Otsu 方法的分割(图片由作者提供)
从图 3 中,我们可以看到使用 Otsu 方法和使用试错阈值之间的区别。请注意,与 0.7 阈值图相比,otsu 的结果具有更明确的斑点对象,这意味着它能够分割并看到整个白花。
HSV 颜色分割
请注意,从上面的两种分割技术中,很容易区分亮暗像素值,因为我们处理的是灰度图像维度。在 RGB 颜色通道维度方面,我们使用 HSV 或色调、饱和度和值空间来正确分割花朵。
让我们首先展示样本图像在 HSV 值中的样子。
from skimage.color import rgb2hsv
#convert to hsv scale
sample_h= rgb2hsv(sample)#graph per HSV Channel
fig, ax = plt.subplots(1, 3, figsize=(15,5))
ax[0].imshow(sample_h[:,:,0], cmap='hsv')
ax[0].set_title('Hue',fontsize=15)
ax[1].imshow(sample_h[:,:,1], cmap='hsv')
ax[1].set_title('Saturation',fontsize=15)
ax[2].imshow(sample_h[:,:,2], cmap='hsv')
ax[2].set_title('Value',fontsize=15);
plt.show()

图 4:样本图像 HSV 空间(图片由作者提供)
该图示出了 HSV 颜色空间的不同通道,并且注意,从这个不同的通道中,我们可以识别所需的分割对象。从数值图中,我们可以看到白色花朵的亮度与背景不同。
与前两种技术相比,使用 HSV 颜色空间,我们实际上可以更恰当地分割花朵。示例代码如下:
fig, ax = plt.subplots(1,3,figsize=(15,5))
im = ax[0].imshow(sample_h[:,:,0],cmap='hsv')
fig.colorbar(im,ax=ax[0])
ax[0].set_title('Hue Graph',fontsize=15)#set the lower and upper mask based on hue colorbar value of the desired fruit
lower_mask = sample_h[:,:,0] > 0.11
upper_mask = sample_h[:,:,0] < 0.3
mask = upper_mask*lower_mask# get the desired mask and show in original image
red = sample[:,:,0]*mask
green = sample[:,:,1]*mask
blue = sample[:,:,2]*mask
mask2 = np.dstack((red,green,blue))
ax[1].imshow(mask)
ax[2].imshow(mask2)ax[1].set_title('Mask',fontsize=15)
ax[2].set_title('Final Image',fontsize=15)
plt.tight_layout()
plt.show()

(图片由作者提供)
从我们的代码中,注意我们首先为预期的对象定义了一个 lower 和 upper 掩码。蒙版的值是从色调图一侧的颜色条值得出的。请注意,这朵花有一种与背景完全不同的色调。使用该值,我们已经可以将小花束作为一个整体进行分割。
我们还展示了当我们将蒙版乘以原始图像时得到的最终图像。请注意背景中的花朵是如何定义的。我们能够把它们每一个都分割开来!
摘要
从结果中,我们可以看到,通过使用试错阈值法以及使用 otsu 方法,我们成功地从图像中分割出了白花。与其他两种技术相比,通过使用 HSV 颜色通道,我们能够更精确地分割花束。总之,需要注意的是,这些技术有不同的优缺点,可以根据您的需要同时使用。
请继续关注第 2 部分!
图像分割:第一部分
各种图像分割技术的数学和实际实现

(图片由作者提供)
图象分割法
图像分割是一种将数字图像分解成称为图像片段的各种子组的方法,这有助于降低图像的复杂性,从而使图像的进一步处理或分析更简单。简单来说,分段就是给像素分配标签。属于同一类别的所有图像元素或像素具有分配给它们的公共标签。例如:让我们考虑一个问题,其中必须提供图片作为对象检测的输入。检测器可以输入由分割算法选择的区域,而不是处理整个图像。这将防止检测器处理整个图像,从而减少推断时间。

(图片由作者提供)
图像分割方法
- 相似性方法:这种方法基于检测图像像素之间的相似性,以基于阈值形成片段。像聚类这样的 ML 算法就是基于这种方法来分割图像的。
- 不连续性方法:这种方法依赖于图像像素亮度值的不连续性。线、点和边缘检测技术使用这种类型的方法来获得中间分割结果,这些结果可以在以后被处理以获得最终的分割图像。
图像分割技术
- 基于阈值的分割
- 基于边缘的分割
- 基于区域的分割
- 基于聚类的分割
- 基于人工神经网络的分割
在本文中,我们将涵盖基于阈值和基于边缘的分割。其他分割技术将在后面的部分讨论。
基于阈值的分割
图像阈值分割是图像分割的一种简单形式。这是一种基于对原始图像的像素强度设置阈值来创建二值或多色图像的方法。
在这个阈值处理过程中,我们将考虑图像中所有像素的强度直方图。然后,我们将设置一个阈值,将图像分成多个部分。例如,考虑到图像像素范围从 0 到 255,我们设置阈值为 60。因此,所有值小于或等于 60 的像素将被提供值 0(黑色),所有值大于 60 的像素将被提供值 255(白色)。
考虑具有背景和对象的图像,我们可以基于对象和背景的强度将图像划分成区域。但是这个阈值必须被完美地设置,以将图像分割成对象和背景。
各种阈值技术有:
- 全局阈值:在这种方法中,我们使用双模态图像。双峰图像是在强度分布图中具有两个强度峰值的图像。一个用于物体,一个用于背景。然后,我们推导出整个图像的阈值,并将该全局阈值用于整个图像。这种阈值的一个缺点是,在图像中光照不足的情况下,它的性能很差。
- 手动阈值处理:下面的过程如下

(图片由作者提供)
3.自适应阈值处理:为了克服光照的影响,图像被分成不同的子区域,所有这些区域都使用为所有这些区域计算的阈值进行分割。然后这些子区域被组合以成像完整的分割图像。这有助于在一定程度上减少照明的影响。
4.最佳阈值处理:最佳阈值处理技术可用于最小化由分割执行的像素的错误分类。

(图片由作者提供)
通过最小化像素的误分类损失,使用迭代方法计算最佳阈值。
像素值的概率由以下公式给出:

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)
我们考虑阈值 T 作为初始阈值。如果待确定像素的像素值小于或等于 T,则它属于背景,否则它属于物体。

(图片由作者提供)

(图片由作者提供)
E1:错误,如果一个背景像素被误分类为一个对象像素。
E2:如果对象像素被错误分类为背景像素,则出现错误。

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)
E(T):将像素分类为背景或对象的总误差。为了获得更好的分割,我们必须最小化这个误差。我们通过对下面的方程求导并使其等于零来达到这个目的。

(图片由作者提供)
考虑高斯像素密度,P(Z)的值可以计算为:

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)
此后,T 的新值可以通过将其输入以下等式来计算:

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)
5.局部自适应阈值处理:由于图像中像素亮度的变化,全局阈值处理可能难以分割图像。然后将图像分成更小的子组,然后对这些单独的组进行自适应阈值处理。在对这些子组进行单独分割之后,将所有这些子组组合起来以形成原始图像的完整分割图像。因此,子组的直方图有助于提供更好的图像分割。
基于阈值分割的实际实现
基于边缘的分割
基于边缘的分割依赖于使用各种边缘检测算子在图像中找到的边缘。这些边缘标记了灰度、颜色、纹理等不连续的图像位置。当我们从一个区域移动到另一个区域时,灰度级可能会改变。所以如果我们能找到那个不连续点,我们就能找到那个边。各种边缘检测算子是可用的,但是所得图像是中间分割结果,并且不应该与最终分割图像混淆。我们必须对图像进行进一步的处理来分割它。附加步骤包括将获得的边缘片段组合成一个片段,以便减少片段的数量,而不是可能阻碍区域填充过程的小边界的块。这样做是为了获得对象的无缝边界。边缘分割的目标是获得中间分割结果,我们可以应用基于区域或任何其他类型的分割来获得最终的分割图像。
边缘的类型

(图片由作者提供)
边缘通常与“大小”和“方向”联系在一起。一些边缘检测器既给出方向又给出幅度。我们可以使用各种边缘检测器,如 Sobel 边缘算子、canny 边缘检测器、Kirsch 边缘算子、Prewitt 边缘算子、Robert 边缘算子等。

(图片由作者提供)
三个公式中的任何一个都可以用来计算 g 的值。在计算 g 和θ之后,我们获得了具有幅度和方向的边缘向量。

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)
基于边缘分割的实际实现
参考
- 基于区域的分割操作。阈值技术,【https://www.youtube.com/watch?v=4S7BezwSAmQ
- 基于边缘的分割,【https://www.youtube.com/watch?v=ygVCjURa7ZE
- 大学资源,https://www . CSE . unr . edu/~ bebis/cs 791 e/Notes/thresholding . pdf
我希望这篇文章和解释对你有用。请继续关注后续部分中的其他分段技术。
请随时联系并给出您的建议:https://www.linkedin.com/in/mrinal-tyagi-02a1351b1/
https://github.com/MrinalTyagi
图像分割:第二部分
深入研究各种图像分割技术

(图片由作者提供)
什么是图像分割?
图像分割是一种技术,其中将计算机化的图片分成称为片段的不同子组,这些子组有助于降低图片的复杂性,从而降低图片的进一步处理或研究的难度。简单的单词分割就是把名字分配给像素。具有相似类别位置的所有图像成分或像素都具有分配给它们的典型标记。图像分割可以作为应用机器学习算法之前的预处理步骤,以便降低机器学习算法处理图像所需的时间复杂度。例如:上面给出的花的图像是使用聚类的图像分割的例子,其中图像的颜色被分割。然后,该图像可以由任何机器学习算法通过仅提供感兴趣区域来处理,从而降低算法的时间复杂度。

(图片由作者提供)
图像分割技术
- 基于阈值的分割
- 基于边缘的分割
- 基于区域的分割
- 基于聚类的分割
- 基于人工神经网络的分割
在本文中,我们将讨论基于区域和基于聚类的分割。其他分割技术将在后面的部分讨论。
基于区域的分割
一个区域可以被分类为表现出相似属性的一组相连的像素。像素之间的相似性可以在强度、颜色等方面。在这种类型的分割中,存在一些预定义的规则,像素必须遵守这些规则才能被分类到相似的像素区域中。在噪声图像的情况下,基于区域的分割方法优于基于边缘的分割方法。基于区域的技术根据它们遵循的方法进一步分为两种类型。
- 区域生长法
- 区域分割和合并方法
区域生长技术
在区域生长方法的情况下,我们从某个像素作为种子像素开始,然后检查相邻的像素。如果相邻像素遵守预定义的规则,则该像素被添加到种子像素的区域,并且后续过程继续,直到没有相似性。这种方法遵循自底向上的方法。在区域增长的情况下,优选的规则可以被设置为阈值。例如:考虑给定图像中的种子像素 2 和阈值 3,如果像素具有大于 3 的值,则它将被认为在种子像素区域内。否则会考虑换个地区。因此,基于阈值 3,在下面的图像中形成了 2 个区域。

(图片由作者提供)区域增长工作流
区域分裂和合并技术
在区域分割中,首先将整个图像作为单个区域。如果该区域不遵循预定义的规则,则将其进一步划分为多个区域(通常为 4 个象限),然后对这些区域执行预定义的规则,以便决定是进一步细分还是将其分类为一个区域。接下来的过程继续,直到不再需要区域的进一步划分,即每个区域都遵循预定义的规则。在区域合并技术中,我们把每个像素看作一个独立的区域。我们选择一个区域作为种子区域,根据预定义的规则检查相邻区域是否相似。如果它们是相似的,我们将它们合并成一个单独的区域,然后继续前进,以构建整个图像的分割区域。区域分割和区域合并都是迭代过程。通常,首先对图像进行区域分割,以将图像分割成最大区域,然后将这些区域合并,以形成原始图像的良好分割图像。
在区域分割的情况下,可以检查以下条件以决定是否细分区域。如果一个区域中的最大和最小像素强度之差的绝对值小于或等于用户决定的阈值,则该区域不需要进一步分割。

(图片由作者提供)区域分割和合并工作流

(图片由作者提供)

(图片由作者提供)

(图片由作者提供)
基于聚类的分割
聚类是一种无监督的机器学习算法。它被广泛用于图像分割。用于分割的最主要的基于聚类的算法之一是 KMeans 聚类。这种类型的聚类可用于在彩色图像中制作片段。
k 均值聚类
为了更好的可视化,让我们想象一个二维数据集。首先,在数据集中,质心(由用户选择)首先被随机初始化。然后计算所有点到所有聚类的距离,并将该点分配给具有最小距离的聚类。然后,通过将该聚类的平均值作为质心来重新计算所有聚类的质心。然后,再次将数据点分配给这些聚类。并且该过程继续,直到算法收敛到好的解。通常,该算法只需要很少的迭代次数就可以收敛到一个解,并且不会反弹。

(图片由作者提供)KMeans 聚类工作流

(图片由作者提供)对颜色类似于 KMeans 算法中输入的聚类数的图像执行聚类。
参考
- 印地语中基于区域的图像分割|数字图像处理,https://www.youtube.com/watch?v=mPJTOcEJOhY
- 使用 Scikit-Learn、Keras 和 TensorFlow 进行机器实践学习:构建智能系统的概念、工具和技术
我希望这篇文章和解释对你有用。请继续关注后续部分中的其他分段技术。
请随时联系并给出您的建议:https://www.linkedin.com/in/mrinal-tyagi-02a1351b1/
https://github.com/MrinalTyagi
图像分割(第二部分)
RG 色度

(图片由作者提供)
在第 1 部分中,我们讨论了 3 种不同的技术(阈值、Otsu 和 HSV 颜色空间)来分割图像中的不同对象。
这一次,我们将展示一种更明确的方法,使用一种用于复杂图像的更复杂的技术来分割对象。
假设我们的图像中有不同种类的独特颜色,我们如何利用图像处理技术来显示每个像素值的独特性?
最好的方法是使用色度分割。
在某种意义上,色度分割查看图像内像素值的组合,并将其映射到红色和绿色通道以确定比例。色度分割也可以被称为 RG 色度。
为了更好地展示它是如何工作的,让我们做一些实验:
让我们加载第 1 部分中的旧样本:
import numpy as np
from skimage.io import imshow, imread
from skimage.color import rgb2gray
import matplotlib.pyplot as pltsample = imread('flowers2.png')
sample_g = rgb2gray(sample)fig, ax = plt.subplots(1,2,figsize=(10,5))
ax[0].imshow(sample)
ax[1].imshow(sample_g,cmap='gray')
ax[0].set_title('Colored Image',fontsize=15)
ax[1].set_title('Grayscale Image',fontsize=15)
plt.show()

图 1:样本图像(作者提供的图像)
RG 色度
对于本文,我们仍将尝试从图像中分割出白花。这一次,我们将使用 RG 色度作为图像分割技术。
为了更好地理解图像,我们应该首先检查图像像素值的散点图。显示这一点的代码如下:
sample_R = sample[:,:,0]*1.0/sample.sum(axis=2)
sample_G = sample[:,:,1]*1.0/sample.sum(axis=2)fig, ax = plt.subplots(1,2,figsize=(10,5))
ax[0].imshow(sample)
ax[1].scatter(sample_R.flatten(),sample_G.flatten())
ax[0].set_title('Colored Image',fontsize=15)
ax[1].set_title('Pixel Values',fontsize=15)
ax[1].set_xlabel('Red Channel',fontsize=10)
ax[1].set_ylabel('Green Channel',fontsize=10)
plt.tight_layout()
plt.show()

图 2:像素值散点图(图片由作者提供)
图 2 显示了以红色通道为 x 轴,绿色通道为 y 轴的散点图。请注意,像素值在高亮度绿色通道像素值上更明显。
为了能够分割白花,我们应该首先在图像中定义一个样本面片。其代码如下:
from matplotlib.patches import Rectanglefig, ax = plt.subplots(1,2,figsize=(10,10))
ax[0].imshow(sample)
ax[0].add_patch(Rectangle((590, 570), 100, 100, edgecolor='r', facecolor='none'));
ax[0].set_title('Patch Location',fontsize=15)#Showing Patch
patch = sample[570:670, 590:680]
ax[1].imshow(patch)
ax[1].set_title('Patch',fontsize=15)
plt.show()

图 3:补丁分配(图片由作者提供)
请注意,在图 3 中,我们使用了花朵最白的部分,因为我们将只分割与该补丁像素值相同的像素值。为了形象化,我们可以绘制补丁本身的散点图:
patch_R = patch[:,:,0]*1.0/patch.sum(axis=2)
patch_G = patch[:,:,1]*1.0/patch.sum(axis=2)
fig, ax = plt.subplots(1,2,figsize=(10,5))ax[0].imshow(patch)
ax[1].scatter(patch_R.flatten(),patch_G.flatten())
ax[0].set_title('Patch',fontsize=15)
ax[1].set_title('Pixel Values',fontsize=15)
ax[1].set_xlabel('Red Channel',fontsize=10)
ax[1].set_ylabel('Green Channel',fontsize=10)
ax[1].set_xlim(0,1)
ax[1].set_ylim(0,1)
plt.tight_layout()
plt.show()

图 4:补丁散点图(图片由作者提供)
图 4 显示红色通道和绿色通道的像素值仅位于图的中间。为了分割白花,我们需要这个区域内的所有像素值。
为了分割它,我们将采用标准偏差和补丁初始化的平均值。
std_patch_R = np.std(patch_R.flatten())
mean_patch_R = np.mean(patch_R.flatten())std_patch_G = np.std(patch_G.flatten())
mean_patch_G = np.mean(patch_G.flatten())def gaussian(p,mean,std):
return np.exp(-(p-mean)**2/(2*std**2))*(1/(std*((2*np.pi)**0.5)))
在得到平均值和标准偏差后,我们将把原始图像插值到小块上,这样它将遵循该分布。对于本文,我们使用高斯分布作为分布曲线。
prob_R = gaussian(sample_R,mean_patch_R,std_patch_R)
prob_G = gaussian(sample_G,mean_patch_G,std_patch_G)fig, ax = plt.subplots(1,3,figsize=(15,10))
ax[0].imshow(sample_g,cmap='gray')
ax[1].imshow(prob_R,cmap='gray')
ax[2].imshow(prob_R,cmap='gray')
ax[1].set_title('Grayscale',fontsize=15)
ax[1].set_title('Red Channel',fontsize=15)
ax[2].set_title('Green Channel',fontsize=15)
plt.show()

图 5:红色和绿色通道色度(图片由作者提供)
图 5 显示了获得红色和绿色通道插值的结果,注意,在灰度模式下,我们也能够分割花束。
为了得到白花的特定部分,我们需要将结果相乘。
prob = prob_R * prob_Gfig, ax = plt.subplots(1,2,figsize=(10,10))
ax[0].imshow(sample)
ax[1].imshow(prob,cmap='gray')
ax[0].set_title('Colored Image',fontsize=15)
ax[1].set_title('Final Image',fontsize=15)
plt.show()

图 6:使用色度的最终图像(图片由作者提供)
图 6 显示了我们使用 RG 色度技术的最终图像。结果表明,RG 色度在分割具有不同颜色空间的对象时实际上更好。
摘要
在本文中,我们展示了如何使用红色和绿色通道来分割图像中的对象。我们还能够展示如何使用色度图将像素值从图像背景中分割出来。总之,图像分割非常有助于为未来的数据科学项目创建数据集。
敬请关注更多关于图像处理的文章!
基于经典计算机视觉方法的图像分割
经典的基于计算机视觉的图像分割方法,如阈值分割,基于区域,基于边缘检测和形态学分割,将在 1 篇文章中解释,以便快速了解这个领域
图像分割是将数字图像细分成多个片段(对象)的过程。分割的目标是将图像的表示变成更有意义和更容易分析的东西。我们可以组合不同的分割方法,如下所示。
- 经典的基于计算机视觉的方法
- 阈值处理
- 基于区域的方法
- 基于边缘/边界的方法
- 基于人工智能的方法
- 基于聚类的方法
- 神经网络
在这篇文章中,我将回顾经典的基于计算机视觉的方法,你可以阅读我的基于聚类的方法的文章,并关注神经网络的文章!
阈值处理
阈值处理是分割图像的最简单的方法,它根据图像像素的亮度值将其分成不同的组。通常,它用于获取二值图像,将图像分割成两部分。主要思想是选择一个阈值 T,将低于该阈值的像素值改为 0 (背景像素),将较高的像素值改为 1 (前景像素)。如果要保留图像数据类型,可以选择 0 作为背景像素,选择该数据类型的最大值作为前景像素值。例如,如果您使用 uint8 数据类型图像,您的图像像素可以采用的最大值是 255,因此将 255 分配给前景像素,将 0 分配给背景像素也是一种适用的方法。
全局阈值 阈值是在开始时选定的,在处理过程中不能改变。您可以手动选择阈值,或者使用 Otsu 阈值法根据图像直方图自动获得阈值。
基本阈值
手动选择阈值 T。
您可以使用 OpenCV 的阈值函数和 cv2 非常容易地应用基本阈值。THRESH_BINARY 选项。
import cv2
import numpy as npimg = cv2.imread("dog.jpg", cv2.IMREAD_GRAYSCALE)
_, segmented1 = cv2.threshold(img, 127,255,cv2.THRESH_BINARY)
print(segmented1)cv2.imshow("Segmented Output Image", segmented1)
cv2.waitKey(0)
这里,127 是阈值,255 是分配给前景像素的值。

背景像素= 0,前景像素= 255 的基本阈值“作者图像”
OpenCV 的 threshold 函数返回与输入图像具有相同数据类型的分割图像。因此,如果您希望前景像素为 1,背景像素为 0,您将看到如下所示的错误结果:
_, segmented1 = cv2.threshold(img, 127,1,cv2.THRESH_BINARY)
print(segmented1)cv2.imshow("Segmented Output Image", segmented1)
cv2.waitKey(0)

“作者提供的图像”
即使分割是正确的,以至于我们在图像像素中看到了 1 和 0,但是在输出图像中,我们的前景像素(1)在哪里呢?
如果我们想与 OpenCV 的功能更紧密地合作,这是非常重要的一点。正如我所说的,cv2.threshold 函数为我们提供了一个与输入图像具有相同数据类型的分割图像。由于我们的“dog.jpg”图像来自 uint8 数据类型,因此像素范围在 0 和 255 之间,当我们的输出图像在 0 和 255 范围内只有 0 和 1 时,0 和 1 都对应于黑色像素!为了做到这一点,我们需要将输出图像数据类型改为 float32:
_, segmented2 = cv2.threshold(img, 127,1,cv2.THRESH_BINARY)
segmented2 = segmented2.astype(dtype='f')
print(segmented2)cv2.imshow("Segmented Output Image", segmented1)
cv2.waitKey(0)

背景像素= 0,前景像素= 1 的基本阈值“作者图像”
这里又是正确的输出!
注意,具有 0 或 1 值和 0 或 255 值的两个图像都是二进制,因为它们只有两种类型的像素值。
大津阈值法
这是一种自动阈值决定方法,试图最小化下面的等式以获得最佳的 t

Po:前景像素根据阈值 T 的概率
Pb:背景像素根据阈值 T 的概率
σ o:前景像素的方差
σ b:背景像素的方差
您可以使用 OpenCV 的阈值函数和 cv2 非常容易地应用 Otsu 阈值。OTSU 选项。这一次,我们可以使用函数的第一个返回值来查看选择的阈值是什么。THRESH_BINARY 中不存在这个选项,因为是我们已经给出了阈值。😉
thresh, segmented2 = cv2.threshold(img, 127,1,cv2.THRESH_OTSU)
segmented2 = segmented2.astype(dtype='f')
print(thresh)
print(segmented2)cv2.imshow("Segmented Output Image", segmented2)
cv2.waitKey(0)
cv2.destroyAllWindows()

Otsu 阈值,我们看到 Otsu 给我们 119 作为阈值“作者的图像”
局部阈值处理
在局部阈值处理中,对于给定大小的区域,不是全局地而是局部地选择阈值。为此,我们取停留在该区域的像素的平均值,从该平均值中减去一个常数值 C,就得到我们的阈值!
局部阈值化背后的思想是图像可以在不同的区域暴露于不同的光。用于整个图像的通用阈值可能不适用于这种例外区域。因此,全局阈值化对于这种图像来说不是最好的方法,对于不同的光线条件选择不同的阈值会给我们带来更好的结果。

在这个问题中,我们需要决定 n、 C、和平均值的计算方法。对于局部阈值处理,有两种不同的常用方法来计算平均值。您可以选择标准平均值计算,将所有像素强度相加,然后除以该区域中的像素数量,或者您可以使用高斯平均值(或加权平均值),其中距离区域中心最近的像素的强度贡献更大——为计算加权。
选择 n 是另一个问题:太大的 n 可能导致与全局阈值处理类似的输出,因为它没有将图像分割成足够小的区域,而太小的 n 可能导致该区域不包含像素多样性。
我们可以使用 OpenCV cv2 应用自适应阈值。自适应阈值函数很容易:
segmented4 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY,15,2)
print(segmented4)cv2.imshow("Segmented Output Image", segmented4)
cv2.waitKey(0)segmented5 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY,15,2)
print(segmented5)cv2.imshow("Segmented Output Image", segmented5)
cv2.waitKey(0)
255 是前景像素的值, ADAPTIVE_THRESH_MEAN_C 是选择局部阈值计算的标准均值方法,ADAPTIVE _ THRESH _ GAUSSIAN _ C是高斯均值。看到我们还在用 cv2。THRESH_BINARY !这意味着,在局部选择我们的阈值之后,我们应用简单的阈值方法来决定一个像素是前景像素还是背景像素。 15 是区域 n 的大小,所以我们对于每个局部区域有一个 15×15 的窗口,2 是常量值 c。
在这种情况下,不同参数的一些输出结果如下所示:

“作者提供的图像”

“作者提供的图像”
由于随着增加 C ,阈值减少,我们在输出中看到更多的前景像素。
可能不太清楚自适应阈值在这里做什么,因为来自全局阈值的结果也被很好地分割。现在,让我们来看一些在不同区域有光线差异或标记了不同颜色的图像:

“作者提供的图像”

“作者提供的图像”

“作者提供的图像”
现在我们可以看到,对于这些图像,局部阈值处理要比全局阈值处理好得多。以书籍为例,自适应阈值处理的输出初看起来可能更嘈杂、更复杂,但如果你仔细观察,你会发现,在全局阈值处理中有一些部分丢失了,这是我们在课堂上再也看不到的,而自适应阈值处理则不是这样。
最终,选择哪种阈值方法取决于您和您的图像类型!
以上就是图像分割与阈值分割,相关代码可以在我的 GitHub 链接中找到,现在让我们继续下一个话题✋
基于区域的图像分割
在本帖中,我们将回顾基于“区域”方法的图像分割方法,其中使用了像素之间的邻域和连接关系。如果你对邻域、连接、连通分量术语没有任何概念,可以 点击 看我的简短帖子。
区域生长
一种基于区域的方法是区域生长,我们选择种子点,让种子按照符合一组规则的像素生长。有一套不同的规则来决定增长的过程和停止的条件,这里我们将研究两种不同的方法。
第一种方法:
- 从那个种子像素开始,我们查看种子的4-连接或8-连接像素。4 还是 8,看你的选择。
- 在那些有可能被添加到该区域中的像素中,选择与该区域的平均强度具有最小距离 的像素。如果该距离低于确定的“相似性阈值,则我们最终将该像素添加到该区域中。****
- 当一个新的像素被添加到该区域时,现在它就是我们的新种子, so 1。第二。将对该种子点重复这些步骤,直到在种子的邻域中不存在任何符合这些规则的可能像素(因此它不能再增长)或者图像中的所有像素已经被分类。
我不认为 OpenCV 有一个内置的区域增长算法函数,但是我准备了一个实现来按照我上面解释的规则做一些实验。首先,在尝试真实图像之前,让我们来看看 2D 阵列的一些结果,以便更好地了解发生了什么:

表示要分割的图像的初始 8x8 数组。我们将用两个种子来分割它,第一个在[1,1]位置,第二个在[7,7]。阈值是 25,我们使用 4-连接“作者的图像”

“作者提供的图像”
因此,对于分割的图像,我们从与我们的输入大小相同的空图像开始,我们看到该区域如何增长,同时为每个种子区域分配一个唯一标签,其中 10 是[1,1]种子的标签,30 是[7,7]种子的标签。
您也可以跟踪种子像素,但在此之前,我建议您注释掉分段输出以获得更好的效果。

“作者提供的图像”
现在让我们用这种方法来看一个真实的图像示例:
在左侧,我们看到输入图像,我想分割中心的圆,所以我在该区域选择一些种子点,如[[245,245],[240,195],[300,245],[230,300]]。这是以 8-连通性和 10 作为阈值的结果。请注意,分割的图像是一个到处都是 255 的空白图像(对于 2D 实验来说,这是 0,以使它的噪声更少),因此黑线是我们分割的唯一部分。

"自动生成的图像"
这不是一个好的输出,对不对?
因为即使图像非常容易分割,当种子到达对象(这里是圆)的边界时只有 0 和 255 个值,但是种子很可能不能走得更远,因为它的相邻像素都是 0,且其他相邻像素已经被分类。让我们思考一下如何改进这种方法:
- 我们可以添加与该区域兼容的种子的所有邻居,而不是最小距离的邻居。
- 当种子停止生长时,我们可以在该区域中搜索另一个具有兼容性条件的像素,而不是直接搜索该种子的邻居。
让我们用实现的第二个版本来检查结果:

"自动生成的图像"
这个远比第一个输出好,对吗?
您可以尝试这两种实现,并通过点击我的 Github 链接,用多种子选项做自己的实验。
请注意,种子点的选择非常重要,因为它们是起点。例如,它可以是您想要分割的对象的中心。种子点选择甚至可以自动完成,此时您需要计算图像的直方图并返回 n 个峰值点。然后选择 1 个具有该峰值点值的像素作为每个峰值点的种子。
峰值点:如果您有一个 0–255 范围内的灰度图像,并且您看到直方图峰值(占大多数)在 200、100 和 10 处,您可以选择 3 个具有 200、100 和 10 强度值的种子点。最后,你将有 3 个分割区域。
此外,这是一个非常好的区域增长算法的可视化,其中该方法应用于 RGB 图像。https://www.youtube.com/watch?v=VaR21S8ewCQ
!!!注意,在 RGB 图像中,由于我们有 3 个通道,而不是像在灰色图像中那样有 1 个通道,所以要添加到区域中的条件从最接近区域平均值变为距离区域平均值最小。并且两个 RGB 像素之间的距离为:
distance = sqrt((R2-R1)^2 +(G2-G1)^2 +(B2-B1)^2)
拆分/合并
分割和合并方法与区域增长相反。我们从整个图像开始,把它作为一个区域
- 将该区域分成 4 个子区域
- 如果 4 个子区域中任何区域不是同质的,则将其再次分割成 4 个子区域。如果任何相邻子区域是同质的,则合并它们。
- 重复 2。直到所有的区域都是同质的。
如何检查一个区域是否同质?
有不同的方法来检查一个区域是否是同质的。您可以选择其中一个并将其作为分割条件来分割满足条件的区域,并将其作为合并条件来合并满足条件的邻居区域。
- 如果像素的灰度等级在给定的范围内(或者全部相同),则该区域是同质的
- 如果该区域的平均值高于整个图像的平均值,则该区域是同质的
- 如果该区域的方差低于确定的阈值,则该区域是同质的。其中区域的方差是

区域 R [1]的方差
随着

区域的强度和除以 1/N,其中 N:区域中的像素数[2]
分割和合并方法通常由树形结构表示。我们看到下面的输出示例:


[3]
注意,基于区域的分割方法经常用于医学图像分割,并且它们仍然是图像分割的重要基本方法之一。
基于边缘的图像分割
正如我们在空间线性滤波中已经看到的,有不同类型的内核为我们提供图像的边缘。基于边缘的图像分割就是这样!我们需要跟踪图像的边缘,当我们获得边缘图时,我们通常使用形态学填充那些边缘中的空洞,然后就完成了!如果你对我们用来检测边缘的“一阶导数和二阶导数核”没有任何概念,你可以看看我之前提到的那篇文章。
除了这些内核类型之外,还有一些“边缘检测器”,我们不仅仅使用边缘检测内核,而是使用一些额外的步骤来获得更好的结果。一个非常常见的边缘检测器是 Canny 边缘检测器,它在下面的帖子中有很好的解释和详细说明:
让我们试着看看边缘检测器和孔洞填充一起使用的一些结果,同时使用 Scipy 的内置函数:
"""Edge Based Segmentation """""" edge detection with canny """edges = canny(img)fig, ax = plt.subplots(figsize=(4, 4))
ax.imshow(edges, cmap=plt.cm.gray)
ax.axis('off')
ax.set_title('Canny detector')""" region - hole filling """fill_holes = ndi.binary_fill_holes(edges)fig, ax = plt.subplots(figsize=(4, 3))
ax.imshow(fill_holes, cmap=plt.cm.gray, interpolation='nearest')
ax.axis('off')
ax.set_title('Filling the holes')

“作者提供的图像”
请注意,处理太大的图像或大的对象可能会破坏您的结果图像,因为边缘大多没有闭合。如果边缘不闭合,填充洞算法也不能正常工作。让我们在不调整大小的情况下检查相同的图像:

没有调整输入图像“作者的图像”的大小
记住这些小技巧,祝你自己的试验好运!你可以从那个 Github 链接到达代码。
我试图在一篇文章中收集经典的方法,让你对每一种方法有一个简单的了解。我希望代码和解释对那些开始研究这个领域的人有所帮助!如果你想用经典方法更进一步,我建议你继续用分水岭算法和提取连通分量。两者都在某些部分使用了形态学运算,值得一看!
要进一步了解更多先进的方法,请点击我的基于聚类的方法帖子,在那里我解释了聚类分割,这是基于人工智能的图像分割的一个子组。
谢谢✋
[1] [2] [3]:图片来自https://en.wikipedia.org/wiki/Split_and_merge_segmentation
在这篇文章中,所有用于不同代码部分实验的图片都来自 unsplash.com
聚类图像分割
K 均值和模糊 C 均值聚类的基本原理及其在图像分割中的应用
在我之前的文章中,我们概述了经典的图像分割方法。现在是时候检查基于人工智能的图像分割方法了,我们将从聚类开始。
在开始使用聚类进行图像分割之前,让我们先回顾一下“什么是聚类?”"如何用 Python 实现一个基本的聚类方法?"
什么是集群
无非是把给定的数据按照相似性进行分组,最后得到不同的聚类。根据我们使用的聚类方法,我们对数据分组的方式会发生变化。让我们考察 2 种不同的最常用的图像分割类型:分割聚类和模糊聚类
划分聚类
分区聚类方法将数据细分为 k 个组,其中 k 是用户预定义的数字。对于 K-means 聚类,哪一种是最流行的划分聚类方法
- 我们选择数据中的 k 个随机点作为聚类的中心,并通过查看该点与中心之间的 L2 距离将每个点分配给最近的聚类。
- 计算每个聚类的平均值,将该平均值指定为该聚类的新中心。
- 将每个数据点重新分配到其最近的聚类中心。重复步骤 2。
该过程继续进行直到没有新的分配被执行(因此模型被收敛,没有什么可进一步进行的)或者对于给定的迭代次数。因此,K-means 聚类是一种迭代方法,其中我们也可以确定迭代次数。
现在让我们可视化一些随机数据,对每个数据应用 10 次迭代的 K 均值聚类。
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
import numpy as np
from matplotlib import pyplot as plt
import cv2""" Plot the data """data = np.array([[1, 2], [1, 4], [1, 0],[10, 2], [10, 4], [10, 0]])
data = np.array([[1, 5], [3, 1], [10, 3], [10, 2], [10, 1], [1, 0], [2, 15], [0.5, 4.9], [5, 3], [7, 13], [18, 18], [1.9, 0.5]])
data = np.random.randint(100, size=(2,2))centers = [[1, 1], [-1, -1], [1, -1]]
data, _ = make_blobs(n_samples=10000, centers=centers, cluster_std=0.6)# Plot the data
plt.scatter(data[:,0],data[:,1])
plt.xlabel('x'),plt.ylabel('y')
plt.show()""" Visualize K means for each iteration """""" create an empty list for each cluster, k is the cluster number """k = 2
clusters = [[[0 for _ in range(2)] for _ in range(1)] for _ in range(k)]for i in range(k):
clusters[i].pop() #if we dont do that, additional [0,0] points will be stayed added in our data
""" Visualize each iteration. """for i in range(1,10):
kmeans = KMeans(n_clusters=k, random_state = 0, max_iter=i).fit(data)
for index,data_point in enumerate(data):
clusters[kmeans.labels_[index]].append(list(data_point))
for i in range(k):
clusters[i] = np.array(clusters[i])
plt.scatter(clusters[i][:,0],clusters[i][:,1])
clusters[i] = clusters[i].tolist()
plt.show()

前两个示例被分为 2 个组,第三个示例分为 3 个组“作者图片”
我们看到,甚至在第一次迭代中,我们就获得了最终结果。因为数据非常小,初始聚类效果很好。让我们对下面随机生成的数据运行相同的代码,并将它们分成 5 个组。
centers = [[1,1],[-1,-1],[1,-1]]
data,_ = make_blobs(n_samples=10000,centers=centers,cluster_std=0.6)

“作者提供的图像”
现在我们可以更好地看到在每次迭代中集群是如何更新的!
是时候对真实图像应用 K-means 聚类并获得分段输出了!
""" Image Segmentation """img = cv2.imread("bird1.jpg", cv2.IMREAD_UNCHANGED)
img = cv2.imread("birds2.jpg", cv2.IMREAD_UNCHANGED)
img = cv2.imread("peppers3.jpg", cv2.IMREAD_UNCHANGED)
vectorized = img.reshape((-1,3))kmeans = KMeans(n_clusters=5, random_state = 0, n_init=5).fit(vectorized)centers = np.uint8(kmeans.cluster_centers_)
segmented_data = centers[kmeans.labels_.flatten()]
segmented_image = segmented_data.reshape((img.shape))
plt.imshow(segmented_image)
plt.pause(1)

“作者提供的图像”

“作者提供的图像”

“作者提供的图像”
你可以意识到我没有使用那次确定迭代次数参数的 max_iter 。我将这一决定留给模型,这样当聚类中没有发生变化时,分割就停止了。另一方面, n_init 参数决定了在初始步骤中用不同的随机聚类中心完成多少次不同的试验。该算法选择最佳拟合,这给出了最佳分割。
我们可以意识到随着增加 k ,结果会变得更好和更详细!
下面是该节中使用的代码的github链接:
模糊聚类
模糊聚类是一种硬聚类类型,而划分聚类被称为软。其原因是,在划分聚类中, 1 个数据点可能仅在 1 个聚类中,在模糊聚类中,我们有每个聚类的数据点的概率,并且它们可能属于该概率水平的任何聚类。****
让我们来看看最常见的模糊聚类类型模糊 C 均值聚类,看看如何计算点的概率、聚类的中心等。[1]
- 随机初始化概率矩阵。因此,为每个数据-聚类对分配权重,这是指数据 x 位于聚类 C 中的概率。
- 计算聚类的中心(质心),
- 根据新的聚类中心计算新的概率。
- 重复 2。第三。步进,直到中心不变或给定迭代次数
让我们想象一个例子:
我们有 4 个数据点 p1、p2、p3、p4 二维,所以我们有这些点的 x 和 y 坐标,我们想把它们分成两个组。
- 我们随机初始化权重矩阵:

“作者提供的图像”
2.我们根据初始概率用下面的公式计算聚类的质心:

“作者提供的图像”
注意模糊参数是我们应该选择的,就像聚类数一样,它可以在 1 < m < ∞之间选择
让我们通过选择 m = 2 将公式应用于我们的示例案例:

“作者提供的图像”
因为我们有 2D 点,所以群集的中心也是 2D。所以我们用我们点的 x 坐标计算 C1x(第一个聚类的 x 坐标),用我们点的 y 坐标计算 C1y(第一个聚类的 y 坐标)。
当我们对 C2 也应用同样的公式时,我们得到如下结果

“作者提供的图像”
我们得到了我们的聚类中心,现在是时候根据新的聚类中心来计算点的概率了。
3.我们使用以下公式计算新的概率——权重:

“作者提供的图像”
别忘了在我们的例子中 m = 2,所以 1/m-1 = 1。

“作者提供的图像”

“作者提供的图像”
诸如此类!我没有计算每个点和聚类的权重,而是想手动显示每个步骤的逻辑。
我希望大家清楚模糊 C 均值聚类的基本逻辑和应用。现在是时候做一些实验并检查结果了。

“作者提供的图像”
选择模糊参数 m 太大会导致结果失真。我们再次看到,随着聚类数的增加,我们获得了更详细和更好的分割结果。
如果您使用 Linux,您可以尝试使用 PyPI [1]中的 fuzzy c means 模块,我在 windows 上安装它时遇到了一些问题,尽管我按照建议使用了 pip install fuzzy-c-means[windows]命令。感谢[2]中的 fuzzy c 意味着从头开始实现,我在准备最后一部分时使用了它。
在图像分割与神经网络部分再见!👐
[1]https://pypi.org/project/fuzzy-c-means/
[2]https://github . com/jeongHwarr/variable _ FCM _ segmentation/blob/master/FCM . py
在这篇文章中,所有用于不同代码部分实验的图片都来自 unsplash.com
图像相似性:理论与代码
一种计算非结构化图像数据相似性的启发式方法。

未来的预告片。所有图片均为作者本人,使用牛津 Pet 数据集。
介绍
我们如何计算一幅图像与另一幅图像有多相似呢?对于矢量化形式的数据之间的相似性,我们可以找到两个示例之间的平方差之和,或者使用类似的方法,如余弦相似性。然而,在图像上执行这种技术(将每个像素值之间的平方差求和)会失败,因为图像中的信息存在于像素之间的相互作用中。如果我们要继续的话,我们必须首先从图像中提取有意义的特征,并转换成矢量化的形式。
但是我们如何从图像这样的非结构化数据中提取特征呢?为了对单词做到这一点,我们使用可学习的嵌入——包含特定单词含义的特征向量。我们用向量来表示单词中的意思,我们也可以用图像做类似的事情。CNN 可以被训练成将图像映射到向量,我们可以像使用单词嵌入一样使用这些向量。这是零射击学习发展领域的一个中心任务;然而,这个项目采用了一种不同的、更加端到端的方法。
我提出了一种复合深度学习管道,作为自动预测图像之间相似性的可解释启发式方法。为此,我使用了牛津 PETS 数据集。这个管道可能类似于面部识别技术,尽管我对这些方法不熟悉。在这篇文章中,我走过了我的项目的每一步,从宠物品种的分类到寻找与暹罗模型的相似性,以及用类激活图(CAMs)解释预测。代码是用 PyTorch 和 fastai 写的。最后,我将讨论这种启发式算法的潜在应用,作为最低标记数据集的原始聚类算法,并为医疗预后匹配相似的患者。
这是原项目的笔记本。我建议你在阅读下面的评论时仔细阅读笔记本,因为为了简洁我省略了一些细节。而且,如果你希望以更清晰的格式阅读这篇文章,我推荐你从我自己的网站上阅读。

预测相似的一对宠物。
深入实施
让我们从能清楚看到整个项目的地方开始:在最后。
类是我的推理管道的模块化版本,一旦我们理解了它的三个方法,我们就已经理解了这个项目的本质。SimilarityFinder将两个模型串在一起,一个是预测宠物品种的分类器,一个是确定两幅图像是否相似的比较(Siamese)模型。我们使用它们来预测比较图像文件中与输入图像最相似的图像。
**class** **SimilarityFinder**:
**def** **__init__**(self, classifier_learner, siamese_learner, files):
**def** **predict**(self, fn, compare_n**=**15):
**def** **similar_cams**(self):
在__init__中,我们将用于比较的图像文件预处理为lbl2files,这是一个对predict有用的映射,并初始化我们的两个Learner。一个Learner是 fastai 类,它将模型、数据和其他一些训练组件包装到一个类中,因此我们可以将它们视为管道的两个部分。
**def** **label_func**(fname):
"""extracts the pet breed from a file name"""
**return** re.match(r'^(.+)_\d+.jpg$', fname.name).groups()[0]**class** **SimilarityFinder**:
**def** **__init__**(self, classifier_learner, siamese_learner, files):
self.clearn,self.slearn **=** classifier_learner,siamese_learner
labels **=** L(map(label_func, files)).unique()
self.lbl2files **=** {l:[f **for** f **in** files **if** label_func(f)**==**l]
**for** l **in** labels}
分类器Learner将作为一种启发,减少我们在预测相似性时必须筛选的图像数量。连体Learner预测两幅图像之间的相似性。它们一起将允许我们在一个相当大的数据集中找到最相似的图像。
让我们继续看看我们是如何构建这两个Learner的。
分类
我们从宠物的图像中预测宠物的品种。这是一个标准的分类问题,因此对于熟悉 CNN 的人来说,这似乎是微不足道的。有三个基本步骤:
- 从目录中提取图像文件。默认情况下,fastai 库中有 PETS 数据集,所以我们使用
untar_data来访问它。
path = untar_data(URLs.PETS)
files = get_image_files(path/"images")
2.对图像文件进行预处理,用 fastai 的数据块 API 存储在DataLoaders中。
cdls = DataBlock(blocks = (ImageBlock, CategoryBlock),
get_items = get_image_files,
get_y = using_attr(RegexLabeller(r'(.+)_\d+.jpg$'),'name',
splitter = RandomSplitter(),
item_tfms = Resize(224),
batch_tfms = aug_transforms()).dataloaders(path/'images')
3.用 fastai Learner包好所有东西,训练模特。我在项目中使用了一些技巧进行训练(标签平滑,混合精度训练),但是为了简单起见,我在这里省略了它们。它们可以在原始笔记本中找到。
clearn = cnn_learner(cdls, resnet34, metrics=accuracy) clearn.fit_one_cycle(n_epochs, lr)
分类管道已完成;让我们转到更复杂的比较管道。

显示分类器的结果
比较
我们训练了一个模型来预测宠物品种。现在,我们训练使用一个模型来预测两个图像是否属于同一种类。这将需要定义一些自定义数据类型和一个自定义模型,因为它不是一个标准的应用程序。下面的实现来自 fastai 文档的 Siamese 教程,但是我对模型和训练过程做了修改。
实现暹罗模型与实现分类器非常相似;然而,有两个关键的修改。
我们将两幅图像输入到模型中,而不是一幅。这意味着,首先,我们需要用每个例子的三个元素来表示我们的DataLoaders——第一个图像,第二个图像,以及它们是否相似——其次,我们通过同一个主体单独传递每个图像,并在头部连接主体的输出。
- 与之前完全一样,检索图像文件。
path **=** untar_data(URLs.PETS)
files **=** get_image_files(path**/**"images")
2.用 fastai 的中级 API 对数据进行预处理。我们创建一个Transform来打开文件,将它们与其他文件配对,并将其输出为一个SiameseImage,它本质上是一个用于显示数据的容器。然后,我们用TfmdLists和dataloaders对所有文件进行必要的转换。
**class** **SiameseTransform**(Transform):
**def** **__init__**(self, files, splits):
"""setup files into train and valid sets"""
**def** **encodes**(self, f):
"""applies transforms on f and pairs it with another image"""
f2,same **=** self.valid.get(f, self._draw(f))
im1,im2 **=** PILImage.create(f),PILImage.create(f2)
**return** SiameseImage(im1,im2,int(same))
**def** **_draw**(self, f, splits**=**0):
"""retrieve a file--same class as f with probability 0.5"""
splits **=** RandomSplitter(seed**=**23)(files)
tfm **=** SiameseTransform(files, splits)
tls **=** TfmdLists(files, tfm, splits**=**splits)
sdls **=** tls.dataloaders(after_item**=**[Resize(224), ToTensor],
after_batch**=**[IntToFloatTensor, Normalize.from_stats(*****imagenet_stats)])
3.建立模型。我们将图像对中的每个图像通过主体(也称为编码器),连接输出,并通过头部来获得预测。注意,两个图像只有一个编码器,而不是每个图像有两个编码器。然后,我们下载一些预训练的重量,并将它们组装成一个模型。
**class** **SiameseModel**(Module):
**def** **__init__**(self, encoder, head):
self.encoder,self.head **=** encoder,head
**def** **forward**(self, x1, x2):
ftrs **=** torch.cat([self.encoder(x1), self.encoder(x2)], dim**=**1)
**return** self.head(ftrs)
encoder **=** create_body(resnet34, cut**=-**2)
head **=** create_head(512*****2, 2, ps**=**0.5)
smodel **=** SiameseModel(encoder, head)
4.创建Learner并训练模型。我们在Learner中处理小褶皱:用siamese_splitter指定身体和头部的位置,在loss_func中把目标投成浮动。请注意,在我们定制了数据和模型之后,其他的一切都就位了,我们可以以标准的方式进行训练。
slearn = Learner(sdls, smodel, loss_func=loss_func,
splitter=siamese_splitter, metrics=accuracy)
slearn.fit_one_cycle(n_epochs, lr)
我们使用确定共享品种的能力作为图像相似性的启发。我使用两只宠物是同一品种的概率作为相似性的代理:如果模型有 95%的信心认为两只宠物是同一品种,那么它们就被认为比模型以 80%的信心预测的更相似。
现在,让我们回到项目的核心,SimilarityFinder,我们将这些功能串联在一起。

展示了连体模型的结果
SimilarityFinder.predict
这是项目中最复杂的方法,所以我将一点一点地分解它。要点如下:输入一个图像文件,预测其类别,在同一类别的图像库中搜索,用钩子记录身体的活动(对于similar_cams),并输出最相似的图像。
**class** **SimilarityFinder**:
**def** **predict**(self, fn, compare_n**=**15):
self.preds,self.acts,self.images,self.fns **=** [],[],[],[] *# 1\. predict breed of input image
* cls **=** predict_class(fn,self.clearn) *# 2\. retrieve a list of same-class images for comparison
* compare_fns **=** self.lbl2files[cls][:compare_n] *# 3\. register a hook to record activations of the body
* hook_layer **=** self.slearn.model.encoder
**with** Hook(hook_layer) **as** hook:
**for** f2 **in** compare_fns: *# 4\. preprocess image files for comparison and predict similarity
* im1,im2 **=** PILImage.create(fn),PILImage.create(f2)
ims **=** SiameseImage(im1,im2)
output **=** slearn.siampredict(ims)[0][1] *# 5\. record state and outputs
* self.preds.append(torch.sigmoid(output))
self.fns.append((fn,f2))
self.images.append((im1,im2))
self.acts.append(hook.stored)
hook.reset() *# 6\. retrieve most similar image and show it with original
* self.idx **=** np.array(self.preds).argmax()
sim_ims **=** self.images[self.idx]
title **=** f'{self.preds[self.idx].item()*****100:.2f}% Similarity'
SiameseImage(sim_ims[0], sim_ims[1], title).show()
**return** self.fns[self.idx][1]
- 预测输入图像的种类。
predict_class对图像文件进行预处理,并使用分类器模型输出预测类别。
**def** **predict_class**(fn,learn):
im **=** first(learn.dls.test_dl([fn,]))[0].cpu()
**with** torch.no_grad(): output **=** learn.model.eval().cpu()(im)
**return** learn.dls.vocab[output.argmax()]
2.检索同类图像列表进行比较。我使用预测类作为启发,以减少我们必须通过搜索来检索最相似的图像的数量。compare_n指定我们要搜索的图片数量,所以如果我们想快速得到结果,我们可以减少compare_n。如果compare_n是 20,调用predict大约需要一秒钟。
3.注册一个钩子来记录身体的活动。钩子是我们注入 PyTorch 模型的代码片段,如果我们希望它们执行额外的功能。它们与上下文管理器(with块)配合得很好,因为我们必须在使用钩子后移除它。在这里,我使用钩子来存储模型身体的最终激活,这样我就可以实现similar_cams(稍后解释)。
**class** **Hook**():
**def** **__init__**(self, m):
self.hook **=** m.register_forward_hook(self.hook_func)
self.stored **=** []
**def** **hook_func**(self,m,i,o): self.stored.append(o.detach().cpu())
**def** **reset**(self): self.stored **=** []
**def** **__enter__**(self,*****args,******kwargs): **return** self
**def** **__exit__**(self,*****args,******kwargs): self.hook.remove()
4.预处理图像文件以进行比较和预测相似性。SiameseImage是一个修改过的元组,用于分组和显示我们的图像。siampredict方法是Learner.predict的一个版本,通过修改默认值来处理定制模型的一些问题。
5.记录一些统计数据。
6.检索具有最大预测相似概率的图像对,将它们视为所考虑的图像中最相似的。用SiameseImage.show并排显示图像,并输出最相似图像的文件名。
这是管道的主要功能,但是,如果这样实现,我们将不知道为什么图像被认为是“最相似的”。换句话说,如果我们能够确定模型用来进行预测的图像特征,这将是非常有用的。为了避免模型预测两个图像由于外部因素而相似(例如,相似的背景),我添加了一个 CAM 功能。

SimilarityFinder.predict 的输出
计算机辅助制造(computer aided manufacturing)
类激活图是显示原始图像上对输出影响最大的位置的网格。我们通过将模型身体的激活(称为空间地图)与包含输出梯度的矩阵相乘来创建一个。这里,我使用模型最后一层的权重矩阵作为梯度,因为最后一层的输出相对于输入的导数就是最后一层的权重。
直观地说,空间地图显示了影像中每个位置的要素的突出程度,梯度矩阵将每个要素与输出联系起来,显示了每个要素的使用程度。结果是图像中的每个位置如何影响输出的图示。
**class** **SimilarityFinder**:
**def** **similar_cams**(self):
*# 1\. grab the final weights and spatial maps of the most similar images
* sweight **=** self.slearn.model.head[**-**1].weight.cpu()
act1,act2 **=** self.acts[self.idx]
*# 2\. matrix multiply the weights and spatial maps
* cam_map1 **=** torch.einsum('ik,kjl->ijl', sweight, act1[0])
cam_map2 **=** torch.einsum('ik,kjl->ijl', sweight, act2[0])
*# 3\. open the most similar images to show them
* f1,f2 **=** self.fns[self.idx]
t1,t2 **=** to_tensor(f1,slearn.dls),to_tensor(f2,slearn.dls)
*# 4\. show the CAMs overlain on the images
* _,axs **=** plt.subplots(ncols**=**2)
show_cam(t1,cam_map1,axs[0])
show_cam(t2,cam_map2,axs[1])
- 获取连体模型的最终重量以及最相似图像的空间地图,我们在
predict中用钩子记录了这些图像。 - 用
torch.einsum(一种自定义矩阵乘法的方法)执行权重和空间图之间的点积。 - 打开
predict中预测最相似的文件,将它们转换成我们能够显示的预处理张量。 - 将凸轮叠加在原始图像上,并并排显示。
**def** **show_cam**(t, cam_map, ctx):
show_image(t, ctx**=**ctx)
ctx.imshow(cam_map[0].detach().cpu(),
extent[0, t.shape[2], t.shape[1],0],
alpha**=**.7, interpolation**=**'BILINEAR', cmap**=**'magma')

show _ cam 的输出
最后的话
在这个项目中,我们预测了最相似的宠物,然后用 CAMs 解释这个预测。最后,我将尝试更精确地定义“最相似”,并解释为什么这个微妙的定义具有实际意义。
这个项目的核心观点是,我们可以使用连体模型在预测中的置信度作为图像相似性的代理。然而,本文中的“图像相似性”并不意味着图像整体上的相似性。相反,它指的是两个图像如何明显地共享区分目标类的特征。当使用SimilarityFinder时,我们用来标记图像的类别会影响哪个图像被预测为最相似。
例如,如果我们像这里一样用品种来区分宠物,SimilarityFinder可能会预测两只狗共享,比如说,他们品种特有的尖鼻子,是最相似的,即使它们的其他特征有很大不同。相比之下,如果我们根据另一个类别来区分宠物,例如它们是否可爱,模型可能会在预测中更多地考虑类似的软耳朵,而不是尖鼻子,因为软耳朵对可爱程度的贡献更大。因此,SimilarityFinder过分强调了对确定它被训练的类最重要的特征。
基于训练标签的预测图像相似性的可变性是SimilarityFinder的一个有用特征,如果我们要将其应用于更多实际问题的话。例如,SimilarityFinder对于发现肺炎患者的 CT 扫描之间的相似性将是一个有用的启发,因为相似性度量将有助于评估治疗方案。举例来说,如果我们能找到与肺炎病例最相似的既往患者,并且他们对治疗反应良好,比如说克罗星,那么克罗星可能是目前患者的一个好的治疗选择。
我们将从 CT 扫描图像中确定病例的相似性,但是我们不希望该模型由于诸如骨骼结构或扫描质量之类的外来因素而预测相似性;我们希望这种相似性是基于疾病的发展和性质。因此,通过指定类别标签(如肺炎的严重程度和类型)来确定有助于预测的特征,并通过分析我们的 cam 来确认使用了适当的特征是非常有用的。
这个项目的目的是实现一个算法,可以计算非结构化图像数据的相似性。作为一种可解释的启发式方法来实现这一目的。目前,我感兴趣的是将这种启发式方法应用到医学领域,为临床任务提供额外的数据,如随机对照试验的配对解释。更多信息将在后续文章中发布。
参考
《纽约客》漫画的图像到文本生成
我如何使用深度学习来创建《纽约客》的标题
从来没有一台电脑赢得过《纽约客》漫画字幕大赛的冠军,理由很充分:这很难。《纽约客》杂志每周举办一次竞赛,让读者为一幅无字幕的漫画提交字幕,如下图所示。获奖说明刊登在杂志上。

上一届《纽约客》字幕大赛的图片示例。图片来自 NextML 。 CC 乘 4.0 。
人们已经创建了纽约客字幕生成器,但没有一个真正使用漫画的图像来生成字幕。相反,他们使用以前的标题来预测新的标题会是什么样子。
直接从卡通形象转到标题很难,原因如下:
- 卡通通常描绘现实生活中从未发生的情景和图像。(一条坐在酒吧的鱼?)
- 即使我们有一个模型可以识别漫画中的主题,也很难将这种理解转化为诙谐的标题。
我将这项任务作为图像到文本算法的学习练习,经过大量的反复试验,我创建了一个图像到文本的模型,仅基于图像就可以为《纽约客》漫画制作像样的字幕。我是这样做的。

由模型产生的样本标题。图片来自 NextML 。 CC 乘 4.0 。
数据
第一步是获取训练数据。NEXTml 有一个 Github 知识库,在那里你可以访问来自 186 场比赛的原始卡通图像和候选人说明。每场比赛平均有大约 5000 个候选字幕。在下载了这个数据集之后,我将它分成了分别包含 127 个和 59 个图像的训练和测试数据集。
型号
我从 sgrvinod 的教程中的深度图像到文本模型开始。给定一幅图像,该模型通过将图像编码到描述图像中内容的向量空间中来工作。然后,该模型获取编码图像,并将其翻译成文字(“解码”)。在将编码图像翻译成文字时,注意力层会告诉模型应该关注图像的哪一部分。注意力层特别酷,因为它可以让你看到模型在选择每个单词时的焦点在哪里。

该模型集中在滑雪板上,以确定“一个人骑着滑雪板”,并围绕滑雪板确定“雪覆盖的斜坡”图片来自 COCO2014 。 CC BY-SA 2.0 。
我在大约 16 万张图片和说明的 COCO2014 数据集上训练了一个模型(“COCO 模型”)。然而,如下图所示,COCO2014 的图片和说明与我们想为《纽约客》制作的图片和说明大相径庭。这些图像是真实生活中的照片,描述非常简单。看不到喜剧。下一步是弄清楚:我们如何把这个预先训练好的模型改编成《纽约客》的字幕?这是最困难的部分。

来自 COCO2014 数据集的样本图像和标题。 CC BY-SA 2.0 。
文本预处理
对上述问题的回答最终是“非常仔细的预处理”
词汇扩展
我需要做的第一件事是扩展 COCO 模型的词汇。《纽约客》漫画中包含的“雪人”或“穴居人”等概念从未出现在 COCO2014 数据集中。为了解决这个问题,我将《纽约客》数据集中的单词添加到 COCO 模型的词汇表中,并重新训练了 COCO 模型。这使词汇量从 9490 个单词增加到 11865 个单词。
字幕过滤
在《纽约客》的数据集中,一幅漫画的候选标题彼此大相径庭。有的提到了图像中的物体,有的没有。字幕也以不同的单词开始,并且具有不同的语法结构:
“你什么意思?你只在星期五供应鱼”
“每个学校都拒绝了我”
“我知道什么东西被掺水了,伙计。”
为了使模型更容易解决图像到文本的问题,我用两种方法过滤了候选标题:
- 结构:标题必须以代词开头(如“我”、“你”、“他们”)。这是为了使标题结构更容易预测。
- 内容:标题必须引用图像中的对象。这有两个原因。首先,由于我们直接从图像到标题,我想强制模型使用图像中的对象来获得标题。第二是关键词(例如“鱼”)出现在标题中,使模型更容易学习。为了弄清楚图像中有哪些物体,我使用了一种粗略的启发式方法,从漫画的描述中提取名词,并迫使标题至少引用一两个名词。
这些过滤器将每部漫画的平均标题数量从 5000 个减少到 10 个,但这仍然是足够训练模型的数据[3]。
迁移学习
清理完数据后,我继续训练 COCO 模型,给它提供来自《纽约客》数据集的图片和说明。额外的训练意味着模型可以对其参数进行小的调整以适应新的任务。这种技术通常被称为迁移学习。值得注意的是,我将学习率保持在较高的水平(本例中为 1e-4),以迫使模型学习一种新的“有趣”的句法结构,并识别黑白图像中的对象。
结果
这里有一些由测试集上的模型生成的示例字幕。

模型生成的示例标题。来自 NextML 的图片。 CC 乘 4.0 。
许多字幕有 70%在那里,只需要一些调整就可以使它们配得上字幕(例如,中间底部的照片:“对不起,这是一个社交派对”→“对不起,这是一个数字派对”或“反社交派对”)。话虽如此,我对迁移学习如何让模型识别卡通黑白图像中的主题印象深刻(例如,洞穴画中的“洞穴”,走进厨房的人的“厨师”)。也许再做一些调整,我们就能拥有一个有竞争力的人工智能卡通字幕了。
这是这位模特提交给本周《纽约客》字幕竞赛(竞赛#749)的作品。

模特参加《纽约客》字幕竞赛的作品。
结束语
有很多方法可以改进这个模型,但希望这里的技术和知识可以让你在创建自己的有趣人工智能方面领先一步。我将会更多地使用这个模型,并且我很乐意听到你关于我们如何改进它的任何想法!大部分建模代码来自于 sgrvinod 的教程,你可以在这个 Github repo 中找到我用来预处理纽约客字幕的代码。
笔记
[1]更准确地说,以前的尝试在以前的字幕上训练语言模型来生成新的字幕。比如说马尔可夫模型。一些简单的例子包括:
[2]技术说明:我根据漫画是否带有图像内容的文本描述来分割数据,我帮助使用这些数据来过滤用于训练的标题。如果这幅漫画没有文字描述,我就把它放在测试集中。此外,GitHub 的 readme 声明有 155 个图像,但在删除重复项后,数据库实际上有 184 个。
[3]在这种情况下,图像到文本模型的主要数据约束是字幕图像的数量,而不是每个图像的字幕数量。相比之下,COCO2014 每张图片有 5 个标题。
基于神经风格转移和张量流的图像生成
使用机器学习算法创建独特的图像
3 分钟机器学习
3 分钟机器学习是一系列与机器学习世界相关的教程和内容丰富的视频。你可以在 YouTube 上找到完整的视频。库包含视频中显示的所有文件。这个系列是在实验阶段,完全免费开发,为了娱乐和文化!欢迎任何反馈。
为什么要看这篇文章?
本文将介绍如何通过开发一种有趣的机器学习算法来创建独特和原始的图像:神经风格转移。我们将看到如何上传已经训练好的神经网络模型来加快过程,以及如何上传自己的照片来测试算法并生成自己的图像。
下面是我们最终结果的预览:

神经风格转移应用程序—图片由作者提供
神经类型转移
神经类型转移是一种优化技术。它的实现包括使用两个图像:一个表示主要内容(内容图像),另一个表示代表我们希望获得的图像的样式(样式引用图像)。该算法混合这些图像,获取内容图像并对其应用样式参考图像。这是通过优化最终图像以匹配内容图像的内容统计和样式参考图像的样式统计来完成的。该算法的实现涉及卷积网络的使用。

神经类型转移工作原理—学分:篇
更多信息请参考 Tensorflow 原创文章或者这个有趣的教程。
代码
代码需要导入几个库和定义一些环境变量。 Matplotlib 用于图像绘制, Tensorflow 和 Keras 用于代理模型管理和图像预处理, os 系统库用于定义一些系统变量, NumPy 作为数值分析的基本工具。我们直接从 Google Colab 导入一些工具,方便直接从你的 PC 导入图片。
剩下的工作就是导入我们想要执行分析的图像。请记住,第一个将是基础图像,它将为最终结果分配主题(内容图像)。第二个将分配样式(样式参考图像)。这段代码非常有用,因为它允许我们用几行代码直接从本地存储空间上传图像文件,同时在代码共享的情况下创建与用户的交互界面(如本例所示)。我们注意到,正如所写的,这段代码允许同时导入几个图像,但是在这种情况下,我们只需要两个图像:只有最后一个将被存储。图像会自动上传并保存在您的工作区中。
让我们定义一些有用的函数:
tensor_to_image 允许将 TensorFlow 张量转换为图像文件,并以此表示。 load_img 从所需路径加载图像。 imshow 允许我们可视化一个图像,注意输入文件的大小。
让我们展示这些图像:
让我们创建我们的 神经风格转移模型 :
要开发 Tensorflow Hub 的潜力,只需:
- 从 Tensorflow Hub 加载预训练模型
- 生成新的风格化图像,将我们的两个图像作为常量张量流张量传递给模型
- 将新图像转换为图像
- 让我们来想象一下结果吧!

神经风格转移应用程序—图片由作者提供
让我们添加一些 Python 命令来自动命名和下载新图像:
结论
我希望你喜欢这篇文章的内容。我提醒你,你可以在这里找到源代码,在 GitHub 和 YouTube 上可以找到完整的库。
下次见,
马可
Python 中的不平衡分类:SMOTE-ENN 方法

奥马尔·弗洛雷斯在 Unsplash 上拍摄的照片
使用 Python 将 SMOTE 与编辑过的最近邻(ENN)相结合以平衡数据集
动机
在分类建模中,有许多方法可以通过过采样少数类或欠采样多数类来克服不平衡数据集。为了进一步提高模型性能,许多研究人员建议结合过采样和欠采样方法来更好地平衡数据集。
在我的上一篇文章中,我已经解释过采样和欠采样相结合的方法之一,称为 SMOTE-Tomek 链接方法。这一次,我将通过结合 SMOTE 和编辑最近邻(ENN)方法(简称 SMOTE-ENN)及其使用 Python 的实现来解释另一种变体。
概念:K-最近邻(KNN)
KNN 的思想是假设基于距离的每个数据的最近邻具有相似的类。当数据集中的新观测值存在时,KNN 将搜索其 K-最近邻,以确定新观测值将属于的类别。许多距离度量可用于计算 KNN 的每个观察距离,但最常用的是使用欧几里德距离。
例如,假设数据集由两个类组成,黑色和白色。现在,假设有一个未知类的新观察。通过使用 KNN,如果大部分新观测值的 K-最近邻属于黑色类,那么新观测值将属于该黑色类,反之亦然。
给定由 N 个观测值组成的数据集,KNN 的算法可以解释如下。
- 将 K 确定为最近邻居的数量。
- 对于数据集中的每个观测值,计算每个观测值之间的距离,然后将距离和观测值添加到有序集。
- 根据距离按升序对距离和观测值的有序集合进行排序。
- 从排序的有序集合中挑选前 K 个条目。换句话说,选择每个观察值的 K 个最近邻。
- 从选定的 K 个条目中返回多数类。
概念:编辑过的最近邻(ENN)
由 Wilson (1972)开发的 ENN 方法的工作原理是首先找到每个观测值的 K-最近邻,然后检查来自观测值的 K-最近邻的多数类是否与观测值的类相同。如果观测值的 K-最近邻的多数类和观测值的类不同,则从数据集中删除观测值及其 K-最近邻。默认情况下,ENN 使用的最近邻数为 K=3。
ENN 的算法可以解释如下。
- 给定具有 N 个观测值的数据集,将 K 确定为最近邻的数量。如果不确定,那么 K=3。
- 在数据集中的其他观测值中查找该观测值的 K-最近邻,然后从 K-最近邻返回多数类。
- 如果观测值的类和观测值的 K-最近邻中的多数类不同,则从数据集中删除观测值及其 K-最近邻。
- 重复第 2 步和第 3 步,直到达到每个类别的期望比例。
此方法比托梅克链接更有效,在托梅克链接中,当观测值的类与观测值的 K-最近邻的多数类不同时,ENN 会移除观测值及其 K-最近邻,而不是仅移除具有不同类的观测值及其 1-最近邻。因此,可以预期 ENN 会比 Tomek 链接提供更深入的数据清理。
斯莫特-ENN 方法
该方法由巴蒂斯塔等人 (2004)开发,结合了 SMOTE 为少数类生成合成样本的能力和 ENN 从两个类中删除一些观察值的能力,这些观察值被识别为在观察值的类和其 K-最近邻多数类之间具有不同的类。SMOTE-ENN 的过程可以解释如下。
- (开始 SMOTE )从少数类中选择随机数据。
- 计算随机数据与其 k 个最近邻之间的距离。
- 将差值乘以 0 到 1 之间的随机数,然后将结果添加到少数类作为合成样本。
- 重复步骤 2-3,直到达到所需的少数族裔比例。(击打结束)
- (ENN 的开始)确定 K,作为最近邻居的数量。如果不确定,那么 K=3。
- 在数据集中的其他观测值中查找该观测值的 K-最近邻,然后从 K-最近邻返回多数类。
- 如果观测值的类和观测值的 K-最近邻中的多数类不同,则从数据集中删除观测值及其 K-最近邻。
- 重复第 2 步和第 3 步,直到达到每个类别的期望比例。(ENN 结束
为了在实践中更好地理解这种方法,这里我将使用imbalanced-learn库给出 SMOTE-ENN 在 Python 中的一些实现。对于本文,我将通过使用AdaBoostClassifier使用 AdaBoost 分类器。为了评估我们的模型,这里我将使用重复分层交叉验证方法。
履行
为了实现,这里我使用来自 Kaggle 的 Pima Indians 糖尿病数据库。这个数据集中的文件名是diabetes.csv。

皮马印第安人糖尿病数据库(图片取自 Kaggle
首先,我们需要导入我们需要的数据和库,如下所示。

让我们来看看数据描述,并检查数据集中是否有任何缺失值,如下所示。
> data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Pregnancies 768 non-null int64
1 Glucose 768 non-null int64
2 BloodPressure 768 non-null int64
3 SkinThickness 768 non-null int64
4 Insulin 768 non-null int64
5 BMI 768 non-null float64
6 DiabetesPedigreeFunction 768 non-null float64
7 Age 768 non-null int64
8 Outcome 768 non-null int64
dtypes: float64(2), int64(7)
memory usage: 54.1 KB> data.isnull().sum()
Pregnancies 0
Glucose 0
BloodPressure 0
SkinThickness 0
Insulin 0
BMI 0
DiabetesPedigreeFunction 0
Age 0
Outcome 0
dtype: int64
我们可以看到数据集中没有丢失的值,所以我们可以跳到下一步,我们需要通过编写如下代码行来计算属于Outcome变量中每个类的数据的数量。
> data['Outcome'].value_counts()
0 500
1 268
数据非常不平衡,多数阶级属于“0”(我们称之为负)标签,少数阶级属于“1”(我们称之为正)标签。接下来,我们通过编写如下代码将数据分为特性和目标。
Y=data['Outcome'].values #Target
X=data.drop('Outcome',axis=1) #Features
预处理完成。现在,让我们跳到建模过程。为了给你一些性能比较,这里我创建了两个模型,其中第一个没有使用任何不平衡数据处理,而另一个使用 SMOTE-ENN 方法来平衡数据。
在不使用 SMOTE-ENN 平衡数据的情况下,产生的模型性能如下。
Mean Accuracy: 0.7535
Mean Precision: 0.7346
Mean Recall: 0.7122

我们可以看到,准确率分数相当高,但召回分数略低(大约 0.7122)。这意味着正确预测少数类标签的模型性能不够好。
让我们使用 SMOTE-ENN 来平衡我们的数据集,看看有什么不同。注意,我在 **EditedNearestNeighbours** 中使用的 **sampling_strategy** 是 **'all'** ,因为 ENN 的目的是从两个类中删除一些观察值,这些观察值被识别为在观察值的类与其 K 近邻多数类之间具有不同的类。
Mean Accuracy: 0.7257
Mean Precision: 0.7188
Mean Recall: 0.7354

我们可以看到,召回分数增加,虽然准确性和精确度分数略有下降。这意味着通过使用 SMOTE-ENN 平衡数据,正确预测少数类标签的模型性能正在变得更好。
结论
我们到了。现在,您已经了解了如何使用 SMOTE-ENN 方法来平衡分类建模中使用的数据集,从而提高模型性能。像往常一样,如果您有任何问题,请随时提问和/或讨论!
我的下一篇文章再见!一如既往,保持健康,保持安全!
作者的联系人
中:【https://medium.com/@radenaurelius】T2
参考
[1]舒拉、鲍耶、霍尔和凯格尔迈耶(2002 年)。 SMOTE:合成少数过采样技术。《人工智能研究杂志》,第 16 卷,第 321–357 页。
https://www.kaggle.com/uciml/pima-indians-diabetes-database
[3]何和马,杨(2013)。 不平衡学习:基础、算法、应用 。第一版。威利。
[4]盖,T. M .和哈特,P. E. (1967 年)。最近邻模式分类。 IEEE 信息论汇刊,第 13 卷,第 1 期,第 21–27 页。
[5]韩,j .,Kamber,m .,和裴,J. (2012 年)。 数据挖掘概念与技术 。第三版。波士顿:爱思唯尔。
[6]威尔逊法学博士(1972 年)。使用编辑数据的最近邻规则的渐近性质。 IEEE 系统、人和控制论汇刊,SMC-2 卷,第 3 期,第 408-421 页。
[7]巴蒂斯塔,两性平等协会,普拉蒂共和国,和莫纳德,M. C. (2004 年)。对平衡机器学习训练数据的几种方法的行为的研究。 ACM SIGKDD 探索时事通讯,第 6 卷,第 1 期,第 20–29 页。
[8]https://unbalanced-learn . org/stable/references/generated/imb learn . combine . smote enn . html
Python 中的不平衡分类:SMOTE-Tomek 链接方法

结合 SMOTE 和 Tomek 链接的 Python 不平衡分类
动机
在实际应用中,分类建模经常会遇到不平衡数据集问题,多数类的数量远大于少数类,从而使模型无法很好地从少数类中学习。当来自少数类的数据集中的信息更重要时,例如疾病检测数据集、客户流失数据集和欺诈检测数据集,这就成为一个严重的问题。
解决这种不平衡数据集问题的一种流行方法是对少数类进行过采样或对多数类进行欠采样。然而,这些方法都有自己的弱点。在普通的过采样方法中,想法是从少数类中复制一些随机样本,因此这种技术不会从数据中添加任何新信息。相反,欠采样方法是通过从多数类中移除一些随机样本来进行的,代价是原始数据中的一些信息也被移除。
克服这一弱点的解决方案之一是从现有的少数类中生成新的综合示例。这种方法就是众所周知的合成少数过采样技术或 SMOTE 。SMOTE 有许多变体,但在本文中,我将解释 SMOTE-Tomek 链接方法及其使用 Python 的实现,其中该方法结合了 SMOTE 的过采样方法和 Tomek 链接的欠采样方法。
概念:SMOTE
SMOTE 是最流行的过采样技术之一,由 Chawla 等人开发。(2002).与仅从少数类中复制一些随机样本的随机过采样不同,SMOTE 基于每个数据的距离(通常使用欧氏距离)和少数类最近邻来生成样本,因此生成的样本与原始少数类不同。
简而言之,生成合成样本的过程如下。
- 从少数类中选择随机数据。
- 计算随机数据与其 k 个最近邻之间的欧几里德距离。
- 将差值乘以 0 到 1 之间的随机数,然后将结果添加到少数类作为合成样本。
- 重复该过程,直到达到所需的少数族裔比例。
这种方法是有效的,因为生成的合成数据与少数类上的特征空间相对接近,从而在数据上添加了新的“信息”,这与原始过采样方法不同。
概念:托梅克链接
Tomek 链接是由 Tomek (1976)开发的压缩最近邻(CNN,不要与卷积神经网络混淆)欠采样技术的修改之一。与 CNN 方法不同,其仅从想要移除的多数类中随机选择具有 k 个最近邻的样本,Tomek Links 方法使用规则来选择满足这些属性的观测值对(例如, a 和 b ):
- 观测值 a 的最近邻居是 b 。
- 观测值 b 的最近邻居是 a 。
- 观察值 a 和 b 属于不同的类别。即 a 和 b 分别属于少数派和多数派阶级(或者反之)。
数学上可以表述如下。
设 d(x_i,x_j)表示 x_i 和 x_j 之间的欧几里德距离,其中 x_i 表示属于少数类的样本,x_j 表示属于多数类的样本。如果没有样本,x_k 满足以下条件:
1.d(x_i,x_k) < d(x_i, x_j), or
2。d(x j,x k)<d(x I,x j)那么(x_i,x_j)对就是一个托梅克链。
该方法可用于从与少数类数据具有最低欧几里德距离的多数类中找到所需的数据样本(即,来自与少数类数据最接近的多数类的数据,从而使其模糊不清),然后将其移除。
SMOTE-Tomek 链接
首先由巴蒂斯塔等人介绍。(2003),该方法结合了 SMOTE 为少数类生成合成数据的能力和 Tomek 链接从多数类中移除被识别为 Tomek 链接的数据的能力(即,来自与少数类数据最接近的多数类的数据样本)。SMOTE-Tomek 链接的过程如下。
- (SMOTE 的开始)从少数类中选择随机数据。
- 计算随机数据与其 k 个最近邻之间的距离。
- 将差值乘以 0 到 1 之间的随机数,然后将结果添加到少数类作为合成样本。
- 重复步骤 2-3,直到达到所需的少数族裔比例。(击打结束)
- (Tomek 链接的开始)从多数类中选择随机数据。
- 如果随机数据的最近邻是来自少数类的数据(即创建 Tomek 链接),则移除 Tomek 链接。
为了在实践中更好地理解这种方法,这里我将给出一些如何使用imbalanced-learn库(或简称为imblearn)在 Python 中实现 SMOTE-Tomek 链接的例子。我们将使用的模型是通过使用RandomForestClassifier随机森林。对于评估程序,这里我将使用重复分层 K-fold 交叉验证方法,以确保我们在每次重复中以不同的随机化保留每个 fold 中每个类别的样本百分比(即每个 fold 在每个类别中必须有一些样本)。
实现:合成数据集
对于第一个例子,我将使用来自sklearn.datasets库的make_classification生成的合成数据集。首先,我们需要导入库(第二个例子中也会用到这些库)。
import pandas as pd
import numpy as np
from imblearn.pipeline import Pipeline
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_validate
from sklearn.model_selection import RepeatedStratifiedKFold
from sklearn.ensemble import RandomForestClassifier
from imblearn.combine import SMOTETomek
from imblearn.under_sampling import TomekLinks
接下来,我们通过编写这些代码行来生成我们想要使用的合成数据。
#Dummy dataset study case
X, Y = make_classification(n_samples=10000, n_features=4, n_redundant=0,
n_clusters_per_class=1, weights=[0.99], flip_y=0, random_state=1)
我们可以从weights参数中看到,数据集将包含 99%属于多数类的数据,而其余的属于少数类。
在这里,我创建了两个模型— ,第一个模型不使用任何不平衡数据处理,而另一个模型使用 SMOTE-Tomek 链接方法,为您提供使用和不使用 SMOTE-Tomek 链接不平衡处理方法的一些性能比较。
## No Imbalance Handling
# Define model
model_ori=RandomForestClassifier(criterion='entropy')
# Define evaluation procedure (here we use Repeated Stratified K-Fold CV)
cv_ori=RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# Evaluate model
scoring=['accuracy','precision_macro','recall_macro']
scores_ori = cross_validate(model_ori, X, Y, scoring=scoring, cv=cv_ori, n_jobs=-1)# summarize performance
print('Mean Accuracy: %.4f' % np.mean(scores_ori['test_accuracy']))
print('Mean Precision: %.4f' % np.mean(scores_ori['test_precision_macro']))
print('Mean Recall: %.4f' % np.mean(scores_ori['test_recall_macro']))
如果没有 SMOTE-Tomek 链接,产生的模型性能如下。
Mean Accuracy: 0.9943
Mean Precision: 0.9416
Mean Recall: 0.7480
正如我们可以从不平衡的数据集预期的那样,准确性指标得分非常高,但是召回指标得分非常低(大约 0.748)。这意味着模型未能很好地“学习”少数民族类别,因此未能正确预测少数民族类别标签。
让我们看看是否可以通过使用 SMOTE-Tomek 链接来处理不平衡的数据,从而提高模型的性能。
## With SMOTE-Tomek Links method
# Define model
model=RandomForestClassifier(criterion='entropy')
# Define SMOTE-Tomek Links
resample=SMOTETomek(tomek=TomekLinks(sampling_strategy='majority'))
# Define pipeline
pipeline=Pipeline(steps=[('r', resample), ('m', model)])
# Define evaluation procedure (here we use Repeated Stratified K-Fold CV)
cv=RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# Evaluate model
scoring=['accuracy','precision_macro','recall_macro']
scores = cross_validate(pipeline, X, Y, scoring=scoring, cv=cv, n_jobs=-1)# summarize performance
print('Mean Accuracy: %.4f' % np.mean(scores['test_accuracy']))
print('Mean Precision: %.4f' % np.mean(scores['test_precision_macro']))
print('Mean Recall: %.4f' % np.mean(scores['test_recall_macro']))
结果如下。
Mean Accuracy: 0.9805
Mean Precision: 0.6499
Mean Recall: 0.8433
准确度和精确度指标可能会下降,但我们可以看到召回指标更高,这意味着通过使用 SMOTE-Tomek 链接处理不平衡数据,该模型在正确预测少数类标签方面表现更好。
实施:电信流失数据集
对于第二个例子,这里我使用来自 Kaggle 的电信客户流失数据集。这个数据集中有两个数据文件,但是在本文中,我将使用churn-bigml-80.csv数据文件。

电信客户流失数据集(图片取自 Kaggle
首先,我们导入库(就像第一个例子)和数据,如下所示。
data=pd.read_csv("churn-bigml-80.csv")
data.head()

让我们看看数据描述,找出每个变量的类型。
> data.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2666 entries, 0 to 2665
Data columns (total 20 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 State 2666 non-null object
1 Account length 2666 non-null int64
2 Area code 2666 non-null int64
3 International plan 2666 non-null object
4 Voice mail plan 2666 non-null object
5 Number vmail messages 2666 non-null int64
6 Total day minutes 2666 non-null float64
7 Total day calls 2666 non-null int64
8 Total day charge 2666 non-null float64
9 Total eve minutes 2666 non-null float64
10 Total eve calls 2666 non-null int64
11 Total eve charge 2666 non-null float64
12 Total night minutes 2666 non-null float64
13 Total night calls 2666 non-null int64
14 Total night charge 2666 non-null float64
15 Total intl minutes 2666 non-null float64
16 Total intl calls 2666 non-null int64
17 Total intl charge 2666 non-null float64
18 Customer service calls 2666 non-null int64
19 Churn 2666 non-null bool
dtypes: bool(1), float64(8), int64(8), object(3)
memory usage: 398.5+ KB
然后,我们检查数据中是否存在缺失值,如下所示。
> data.isnull().sum()State 0
Account length 0
Area code 0
International plan 0
Voice mail plan 0
Number vmail messages 0
Total day minutes 0
Total day calls 0
Total day charge 0
Total eve minutes 0
Total eve calls 0
Total eve charge 0
Total night minutes 0
Total night calls 0
Total night charge 0
Total intl minutes 0
Total intl calls 0
Total intl charge 0
Customer service calls 0
Churn 0
dtype: int64
没有缺失值!接下来,我们通过编写如下代码来计算属于Churn变量中每个类的数据数量。
> data['Churn'].value_counts()False 2278
True 388
数据相当不平衡,其中多数阶级属于False标签(我们将其标记为 0),少数阶级属于True标签(我们将其标记为 1)。
对于下一个预处理步骤,我们删除State变量(因为它包含了太多的类别),然后我们重新编码Churn变量(False=0,True=1),并通过编写这些代码行来创建虚拟变量。
data=data.drop('State',axis=1)
data['Churn'].replace(to_replace=True, value=1, inplace=True)
data['Churn'].replace(to_replace=False, value=0, inplace=True)
df_dummies=pd.get_dummies(data)
df_dummies.head()#Churn dataset study case
Y_churn=df_dummies['Churn'].values
X_churn=df_dummies.drop('Churn',axis=1)
数据预处理完成。现在,我们使用与第一个例子相同的方法进行建模。
## No Imbalance Handling
# Define model
model2_ori=RandomForestClassifier(criterion='entropy')
# Define evaluation procedure (here we use Repeated Stratified K-Fold CV)
cv2_ori=RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# Evaluate model
scoring=['accuracy','precision_macro','recall_macro']
scores2_ori = cross_validate(model2_ori, X_churn, Y_churn, scoring=scoring, cv=cv2_ori, n_jobs=-1)# summarize performance
print('Mean Accuracy: %.4f' % np.mean(scores2_ori['test_accuracy']))
print('Mean Precision: %.4f' % np.mean(scores2_ori['test_precision_macro']))
print('Mean Recall: %.4f' % np.mean(scores2_ori['test_recall_macro']))
如果没有不平衡的数据处理,结果如下。
Mean Accuracy: 0.9534
Mean Precision: 0.9503
Mean Recall: 0.8572
请记住,我们使用的数据是不平衡的,因此我们不能仅仅通过观察精确度指标来简单地说模型性能良好。尽管准确性指标得分相当高,但召回指标得分仍然不够高,这意味着该模型正在努力正确预测少数类标签(即被重新编码为 1 的True标签)。
现在,让我们对数据进行 SMOTE-Tomek 链接方法,以查看性能改进。
## With SMOTE-Tomek Links method
# Define model
model2=RandomForestClassifier(criterion='entropy')
# Define SMOTE-Tomek Links
resample2=SMOTETomek(tomek=TomekLinks(sampling_strategy='majority'))
# Define pipeline
pipeline2=Pipeline(steps=[('r', resample2), ('m', model2)])
# Define evaluation procedure (here we use Repeated Stratified K-Fold CV)
cv2=RepeatedStratifiedKFold(n_splits=10, n_repeats=3, random_state=1)
# Evaluate model
scoring=['accuracy','precision_macro','recall_macro']
scores2 = cross_validate(pipeline2, X_churn, Y_churn, scoring=scoring, cv=cv2, n_jobs=-1)# summarize performance
print('Mean Accuracy: %.4f' % np.mean(scores2['test_accuracy']))
print('Mean Precision: %.4f' % np.mean(scores2['test_precision_macro']))
print('Mean Recall: %.4f' % np.mean(scores2['test_recall_macro']))
结果如下。
Mean Accuracy: 0.9449
Mean Precision: 0.8981
Mean Recall: 0.8768
准确度和精确度分数可能会稍微降低,但是召回分数会增加!这意味着该模型能够更好地正确预测该流失数据集中的少数类标签。
结论
就是这样!现在,您将了解如何在 Python 中使用 SMOTE-Tomek Links 方法来提高不平衡数据集中分类模型的性能。像往常一样,如果您有任何问题,请随时提问和/或讨论!
我的下一篇文章再见!保持安全,保持健康!
作者的联系方式
中:https://medium.com/@radenaurelius
参考
[1]舒拉、鲍耶、霍尔和凯格尔迈耶(2002 年)。 SMOTE:合成少数过采样技术。人工智能研究杂志,第 16 卷,第 321–357 页。
[2]https://www.kaggle.com/mnassrib/telecom-churn-datasets
[3]托梅克,1976 年。CNN 的两次修改。 IEEE 系统、人和控制论汇刊,第 6 卷,第 11 期,第 769-772 页。
[4]何和马,杨(2013)。 不平衡学习:基础、算法、应用 。第一版。威利。
[5]曾,m,邹,b,魏,f,刘,x,王,L. (2016)。结合 SMOTE 和 Tomek links 技术对不平衡医疗数据进行有效预测。 2016 年 IEEE 在线分析与计算科学国际会议(ICOACS) ,第 225–228 页。
[6] Batista,G. E. A. P. A .,Bazzan,A. L. C .和 Monard,M. A. (2003 年)。平衡关键字自动标注的训练数据:案例研究。第二届巴西生物信息学研讨会会议录,第 35-43 页。
[7]https://sci kit-learn . org/stable/modules/cross _ validation . html
不平衡数据—使用高斯混合模型进行过采样
其他创成式模型也可以类似地用于过采样

图片来自皮克斯拜
TL;DR —从高斯混合模型(GMM)或其他生成模型中提取样本,是另一种创造性的过采样技术,可能优于 SMOTE 变体。
目录
- 介绍
- 数据集准备
- GMM 介绍
- 使用 GMM 作为过采样技术
- 绩效指标的评估
- 结论
介绍
在之前的文章中,我讨论了一个人如何想出许多能够胜过 SMOTE 变体的创造性过采样技术。我们看到了使用“交叉”的过采样如何优于 SMOTE 变体。
在本文中,我们将展示如何使用高斯混合模型(GMM) 或生成模型对不平衡数据集中的少数类实例进行过采样。
数据集准备
我们首先使用 scikit-learn 的 make_classification 函数生成一个包含两个类的 5000 个数据点的图像分类数据集(二元分类)。有 95%的可能性目标是 0,有 5%的可能性目标是 1。我们准备了一个类似于上一篇文章的数据集。
from sklearn.datasets import make_classification
import seaborn as snsX, y = make_classification(
n_samples=5000, n_classes=2,
weights=[0.95, 0.05], flip_y=0
)sns.countplot(y)
plt.show()

为练习生成的不平衡数据集(图片由作者提供)
默认情况下会创建 20 个特征,下面是我们的 X 数组中的一个示例条目。

使用 scikit-learn 的 make_classification 创建的包含 20 个特征的示例条目(图片由作者提供)
最后,我们将数据分成训练和测试数据集。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y)
高斯混合模型介绍(GMM)
高斯混合模型(GMM)假设数据来自多个正态分布的子群体。
示例:假设我们有一万个人的身高数据。下面是一个样本分布。看起来不像钟形正态分布。

万人身高的概率分布(图片由作者提供)
然而,它实际上来自正态分布的平均身高不同的 4 个不同人群。一组的平均身高为 150 厘米,其他组的平均身高分别为 160、170 和 180 厘米。
下面是用于生成图表的代码。您可以看到 4 个不同的组,我们分别从中生成 4000、1000、1000 和 4000 个样本数据,总共 10,000 个样本个体。
samples_150 = ss.norm(150, 10).rvs(4000)
samples_160 = ss.norm(160, 10).rvs(1000)
samples_170 = ss.norm(170, 10).rvs(1000)
samples_180 = ss.norm(180, 10).rvs(4000)samples_total = np.hstack([samples_150, samples_160,
samples_170, samples_180])plt.figure(figsize=(10, 5))
ax = sns.distplot(samples_total)
ax.set_xlabel("Height (in cm)")
plt.show()
各组身高呈正态分布。
plt.figure(figsize=(10, 5))
ax = sns.distplot(samples_150)
ax.set_xlabel("Height (in cm)")
plt.show()

显示平均身高 150 cm 的群体如何正态分布的示例(图片由作者提供)
GMM 背后的想法是,一个数据集可能有来自不同子群体的观察值,这些子群体有自己的特征。GMM 帮助从数据中提取子群体,这样每个子群体就形成了一个我们可以使用的集群。
使用 GMM 作为过采样技术
如果我们回到在“数据集准备”一节中生成的分类数据集,我们可以尝试从数据中提取子群体/聚类。让我们以提取 5 个集群为例。
gmm = GaussianMixture(5)
gmm.fit(X_train)
就是这样!
在引擎盖下,我们的 GMM 模型现在已经创建了 5 个不同的聚类,它们具有不同的正态分布,反映了每个聚类可以采用的特征值。
下面的示例显示了 20 个特征中每个特征的聚类平均值。
pd.DataFrame(gmm.means_.T)

例如,我们可以看到,对于特征 1,聚类 0 的平均值为-0.159613(图片由作者提供)
更重要的是,GMM 模型可以帮助我们实现两个功能:
1-它可以查看特定样本的特征值,并将该样本分配给一个聚类。
gmm.predict(X_test)

示例预测显示 X_test 中的前 3 个样本分别属于聚类 0、2 和 4(图片由作者提供)
2-它可以使用拟合的正态分布来生成新的样本,我们可以将其用于过采样。
gmm.sample(5)

生成了具有特征值的 5 个样本,即 X,它们分别属于聚类 0、1、2、2 和 2(图片由作者提供)
最后,在本例中,我们将数据聚类到 5 个不同的桶中,但我们的问题是一个二元分类问题,其中我们的目标变量可以是 0 或 1。
一个想法是检查每个聚类和目标(y)变量之间的关系。
cluster_mean = pd.DataFrame(data={
"Cluster": gmm.predict(X_train),
"Mean Target Variable (y)": y_train
}).groupby("Cluster").mean().reset_index(drop=False)plt.figure(figsize=(10, 5))
sns.barplot(data=cluster_mean, x="Cluster",
y="Mean Target Variable (y)")
plt.show()

聚类 4 与一个积极的目标变量有最强的关联(图片由作者提供)
我们可以看到集群 4 的目标值平均值最高,略高于 40%。
请记住,这是一个不平衡的数据集,其中只有 5%的样本的目标变量(y)值= 1,因此 40%是一个很大的数字。
最后一步是从 GMM 模型中生成随机样本,并且只保留属于聚类 4 的样本。我们可以用一个正的目标变量(y=1)来标记它们。
samples, clusters = gmm.sample(100)
samples_to_keep = samples[clusters==4]
我们终于可以将它们添加到我们的训练数据中了!
我们同样可以从与 y=1 关联最强的前 2–3 个集群中抽取样本。或者我们可以从 y 的平均值高于预定义阈值的任何集群中抽取样本。
绩效指标的评估
测试这种过采样技术的性能时,我们将使用下面的函数,该函数总结了我们讨论的所有步骤。
def oversample_gmm(X, y, rows_1, no_clusters=2,
no_sampling_clusters=1, rs=1):
# Instantiate GMM model
gmm = GaussianMixture(
no_clusters,
covariance_type='tied',
max_iter=10000,
random_state=rs
)
gmm.fit(X)
# Finding cluster with y=1 most likely
cluster_mean = pd.DataFrame(data={
"Cluster": gmm.predict(X_train),
"Mean Target Variable (y)": y_train
}).groupby("Cluster").mean().sort_values(
by='Mean Target Variable (y)', ascending=False)top_clusters = cluster_mean.sort_values(
by='Mean Target Variable (y)', ascending=False).index[
:no_sampling_clusters]
# Number of rows we will generate before
# filtering by required cluster.
# Multiply by 5 to ensure we have sufficient samples
rows_initial = rows_1 * no_clusters * 5
# Generate samples
samples, clusters = gmm.sample(rows_initial)# Keep samples coming from clusters where y=1 is likely
top_clusters_filter = [np.any([
cluster == x for x in top_clusters]
) for cluster in clusters]
samples_to_keep = samples[top_clusters_filter]
# Keep only required number of additional samples
rows_required = rows_1 - np.sum(y)
np.random.shuffle(samples_to_keep)
samples_to_keep = samples_to_keep[:rows_required]
# Add samples to training dataset
X_gmm = np.vstack([X, samples_to_keep])
y_gmm = np.hstack([y, np.ones(samples_to_keep.shape[0])])
return X_gmm, y_gmm
与我们的上一篇文章类似,我们遍历了 30 个随机状态,并比较了随机森林分类器在原始数据集和过采样方法上的性能,确保我们有 2000 个具有正目标值(target = 1)的训练样本:
- 随机过采样
- SMOTE — 1 个邻居
- SMOTE — 3 个邻居
- SMOTE — 5 个邻居
- SMOTE — 10 个邻居
- GMM — 2
- GMM — 3
- GMM — 5
- GMM — 10
- GMM——15 人
我们还研究了 7 个分类指标:
- ROC AUC—ROC 曲线下的面积
- PR AUC——精确召回曲线下的面积
- 平衡的准确性—这也相当于两个标签的宏观平均召回率
- 最大 F1 —不同概率临界值的最大 F1 分数(即,如果我们在预测概率> 0.2 或 0.3 时预测 1,等等。而不是默认的 0.5)
- 回忆
- 精确
- F1 分数
以下是结果…

SMOTE 在 ROC AUC 上领先(查看中位数),但 PR AUC 更适合不平衡数据集(图片由作者提供)

GMM 在 PR AUC 上领先 10 或 15,这是不平衡数据集的良好指标(图片由作者提供)

具有 10 个和 15 个簇的 GMM 在 Max F1 上也表现非常好,但是随机过采样稍有优势(图片由作者提供)

拥有两个集群的 GMM 在平衡准确性方面领先(图片由作者提供)

GMM 在回忆方面领先 2 或 3 个集群(图片由作者提供)

原始数据集和具有 10 个聚类的 GMM 在精度指标上领先(图片由作者提供)

具有 5 个以上聚类的 GMM 具有最高的 F1 分数(图片由作者提供)
当然,结果有好有坏。
当查看与不平衡数据集最相关的指标时,具有 10 个聚类的 GMM 是最佳选择:精度-召回曲线、F1 得分和最大 F1(F1 的替代方案,其中我们考虑所有概率阈值)。
结论
我们已经展示了另一种用于不平衡数据集的创新而简单的过采样技术如何在几个指标上优于 SMOTE 和随机过采样等默认技术。
同样基于 GMM 的方法也可用于生成合成数据样本,以调整您的预测模型。这在预期会有大量模型/数据漂移并且交叉验证结果会产生误导的用例中尤其有用。
这是一个在后续文章中讨论的话题…
人工智能分散自治组织在机器对机器经济中的影响
介绍自主经济主体、机器学习和机器对机器经济中的新商业模式(M2M)

纳斯蒂亚·杜尔希尔在 Unsplash 上的照片
即将到来的机器对机器(M2M)时代将彻底改变公司和资产的管理方式。因此,大多数现有的商业模式将会过时。
基于这一评估,我接受了为我的客户建立 M2M 战略以及一个试点项目的任务。这一新战略包括创建新的商业模式,选择新的机器学习方法,以及确定可能的技术合作伙伴。
在本文中,您将了解未来的关键概念,如自主经济主体,发现 M2M 用例,以及公司如何从数据角度重塑自己以保持相关性。
机器对机器经济介绍(M2M)
M2M 时代的特点是,消费者/公司将拥有更少的资产和机器,同时共享更多的数据。
我们将更加依赖自主机器来代表我们采购和签订合同。最终,“这些机器将自主决策,买卖服务,作为市场参与者的新资产类别参与未来经济”( 1 ): 自主经济代理人(AEA) 或自主智能代理人(AIA) 。
根据 Anton Korinek 的说法,要被视为智能代理,计算机系统必须具有以下特性( 2 ):

作者图片,想法来自迈克尔·j·伍尔德里奇,“智能代理”,在韦斯·格哈德(编辑。)多智能体系统:分布式人工智能的现代方法,剑桥,马萨诸塞州,麻省理工学院
M2M 经济的定义是,智能机器决定购买什么,以什么价格购买,并在没有中间人的情况下通过区块链分布式账本完成交易。
机器之间的微支付可以带来更高的效率,例如寻找特定备件的汽车、无人机或农场可以直接相互协商以实现其目标(建筑工地、维护工作等)。)而不需要人类的参与。
由于多代理人工智能系统和分散的自治组织,机器将有能力出租自己,雇佣维护专业人员,并支付更换零件的费用。
在这种新经济中,每台机器都有不同的能力,但有一条共同的线索将它们联系在一起——相互生产和消费商品和服务的能力。此外,这些智能代理/机器能够协商价格(并发现新的提供商)以自主运行。
参与 M2M 交易的设备可以被编程为基于个人或商业需求进行购买。
它是如何工作的
简而言之,加密货币和智能合约使自动机器在区块链上相互交易成为可能。微支付渠道和纳米支付在 M2M 经济中将非常有用。我相信 M2M 经济中的大多数机器都会有某种数字钱包和 ID。
机器学习在 M2M 通信发展中的作用至关重要。事实上,机器学习提供了获取物联网生成的数据并将其转换为支付决策的能力,从而消除了用户的明确决策。
用例#1 —来自 戴姆勒卡车的试点项目戴姆勒一直致力于与卡车相关的 M2M 项目。事实上,他们希望他们的卡车能够“自主地与其他机器通信,并执行具有法律约束力的交易,如支付。这个想法是让汽车拥有自己的装有电子货币的钱包,这样就可以不用人工干预就能支付燃料或通行费”。
在大多数 M2M 项目中,数字身份证和钱包是必需的。因此,他们的试点项目包括“卡车 ID 和卡车钱包,它们还可以通过证明卡车何时由哪个用户使用来部分接管对分包商的控制和计费。
M2M 通信可以创建多个新的用例。例如,车辆租赁和短期使用合同(如“按使用付费”)的“平均处理”可以直接由卡车本身控制:客户直接在车辆上付款,卡车自行决定是否满足运营准备所需的条件。作为试点项目的一部分,他们已经创建了数字卡车 ID** 和相关的卡车钱包——这是 M2M 卡车经济的必要先决条件。**
机器经济伙伴关系
M2M 经济将需要企业建立新的生态系统合作伙伴关系。我期望看到公司创造的机器会因为一些合同协议而偏向于一个 M2M 平台或公司。我还希望看到越来越多的公司建立 M2M 平台,并竞争成为每个行业中领先的 M2M 平台。

作者图片
例如,德国电信的 M2M 解决方案使机器、传感器和车辆能够相互通信,或者与所有商业领域的中央数据处理平台通信。
用例#2
戴姆勒卡车与德国商业银行合作,在一个电动充电站完成了自动支付。他们的目标是使戴姆勒卡车能够在各种应用领域发挥自己的作用。( 4
如何准备你的组织
行业专家认为,在未来 10 到 20 年“机器客户”将分三个阶段发展。

图片由作者提供,创意来自 唐·谢本瑞夫
可以有把握地假设,在不久的将来,大多数公司应该会开始开发这样的机器,它们将获得自己的机器身份,以及自己的数字钱包,这些身份具有将它们与其他机器区分开来的个人属性。。
此外,应该为 M2M 设备提供一个唯一、安全的身份,使其能够在生产网络中向其他机器、实例和参与者进行身份验证。
作为一家无人机制造商试点项目的一部分,我们已经创建了数字无人机 ID 和相关的无人机钱包——这是具有 M2M 功能的无人机的必要先决条件。
新的商业模式和威胁
当机器能够自由地从其他机器获得服务时,新的市场就会出现。M2M 通信将影响所有产品,从通过联网家用设备订购的日常产品,到制造机器人购买的备件和原材料。
机器可以与他人互动的事实将创造或摧毁一些商业模式。例如,通过制造具有 M2M 功能的新车,汽车制造商可以告诉他们的客户何时需要更换零部件。显然,这允许他们在一个全新的水平上提供客户服务,但也阻止人类业主选择其他供应商。
IBM 的 ADEPT 项目通过将物联网与区块链集成来创造半自动设备,可以进行预测性维护,然后使用虚拟“钱包”来订购零件和用品,从而开辟了新的天地。
获胜的未来商业模式将是如何创造相关的机器,为它们的所有者创造收入。因此,大多数公司在开发产品时都认为产品可以与其他产品交流,并有可能自己购买服务。我相信 M2M 通信将最终加强现有的锁定效应。
与此同时,M2M 可能对零售商构成威胁。的确,零售商靠冲动或无计划的购买赚钱。机器不会刺激购买…
严重依赖市场营销的公司将不得不改变他们的沟通方式。他们必须意识到,为机器设计不仅仅是为非人类客户调整现有的产品营销。
处于危险中的公司的一个很好的例子是那些专门从事售后产品的公司…如果某个特定品牌的一台机器被指示只考虑特定供应商的维护或零件,而忽略其他的,那该怎么办?这种情况可能会摧毁专门从事售后产品的公司。
实时出售数据
大多数公司将利用 M2M 通信通过出售或交易实时数据来创造新的收入流。
例如,想象商店中的智能传感器向营销公司提供店内访客的交易数据,以换取在线广告的折扣,或者向零售商提供销售数据,以帮助评估未来产品的需求。
此外,你的汽车可以出售数据,为你创造收入。事实上,在驾驶时,您的车辆会自动收集数据(例如,是否有可用的停车位或充电站)。这些数据可以出售给其他汽车使用。
在不久的将来,客户将期望公司创造出不仅能满足需求,还能为他们创造收入的产品。
我还预计一些公司会说他们的产品永远不会收集用户的数据,除非他们决定这样做。理想情况下,客户将能够决定他们是否想要参与特定的数据市场。

作者图片
自我管理的机器和商业模式
我相信,机器不仅会通过 M2M 平台购买维护、检查和保险等服务,还会将它们的服务作为产品出售,企业按小时或按结果付费,而不是直接购买资产。例如,你的车可以在你不用的时候出租,为你创造收入…
新的商业模式将会出现,减少企业拥有、维护或管理资产的需求,并降低资产报废周期的风险。
我相信这些发展也将创造几个数据市场。一些公司将成为专门从事匹配买家和卖家(包括人类和非人类市场参与者)的软件的。人类将与非人类参与者竞争/参与数据市场。
因此,数据交易将“超越人对人和人对机器的交易,包括旨在为自主机器参与者服务的商业化机制”。( 6 )
新的“客户”之旅
公司未来在 M2M 面临的一个关键挑战将是确保它们的产品在适当的在线交易所上市,描述完整,并采用适当的机器友好格式。我希望看到一些产品经理专门研究 M2M 的这个方面。
我们将从以人为中心过渡到以机器为中心的客户之旅。
在客户之旅中,认知步骤将变得更加数据驱动,而不是基于情绪。由于机器会相互作用,考虑阶段也将完全由数据驱动。因此,公司需要确保他们的机器能够将最相关的信息传递给附近的另一台机器。
如今,包装、品牌形象、在线内容和用户界面等元素对消费者有着重要的影响。在 M2M 时代,公司将必须确保它们能够轻松地与其他机器交流,它们的服务在多大程度上满足了设备或软件的技术需求。营销要素将被代码的效率、对所需 API 或协议的遵从性以及交易的速度或可靠性所取代。

作者图片
AI 去中心化自治组织(AI DAOs)
M2M 时代也将见证人工智能去中心化自治组织(AI DAOs)的崛起。
定义:****道是一种去中心化的商业模式。它是由管理企业如何运作的智能合同组成的。所有的“道的经营决策和财务行为都记录在一个公开的、不可更改的区块链上”( 7 )。
一个 DAO 的每个参与者/投资者都可以就公司的运营方式投上一票。该区块链对所有投资者/代币持有者开放,便于与每个投资者分享所有信息。
一个人工智能 DAO 将是一个使用多个人工智能代理(群体智能)的 DAO。还有其他方法可以创建 AI DAO。
机器交易数据,支付维护、能源或责任保险。
M2M 经济恰恰是把这种道的概念转移到了机器上。在 M2M 经济中,汽车制造商可以制造最终成为自主组织的汽车;他们可以拥有自己的数字身份和钱包来存储数字值(代币),他们将使用这些数字值来管理和存储他们通过向人类乘客提供运输服务而获得的值,以及支付他们的充值和维护费用。
在 M2M 经济中,艾道斯本身被认为是一个经济主体,有自己的经济,自给自足,甚至有自己的商业模式。此外,同样的汽车或自动售货机将围绕它培育新的微服务生态系统。
当涉及到所有权时,这些自治组织可以作为一种众筹融资,用初始投资创建一个 AI DAO。AI DAO 将允许代币所有者治理、决策和利润分享。也许艾道斯会以某种方式与创造它们的公司竞争……
尽管最近的技术进步,机器经济仍然是一个实验性的概念,需要解决不同的挑战。例如,安全的基于硬件的数字身份、群体智能、机器学习中的因果关系、互操作性和数据主权,甚至分布式机器治理模型。
想了解更多关于 M2M 经济的信息,我推荐以下链接:
- [机器经济发展的四个步骤](http://Four Steps in the Evolution of the Machine Economy)
- 多智能体系统:分布式人工智能的现代方法
- 人工智能代理的崛起:人工智能对经济日益增长的影响,第一部分
- 戴姆勒卡车公司正在教卡车如何付款
- M2M 经济——智能机器代理:交付有形的、可工作的区块链原型
- 欢迎来到机器对机器经济:互联世界中的机遇和挑战
- 什么是 M2M 技术?
- 如何将物联网货币化:机器经济中的数据交易
- 机器经济已经到来:为互联世界提供动力
- 区块链:从工业 4.0 到机器经济
- 人工智能能模拟经济选择吗?
- 机器客户:下一个新兴市场?
新冠肺炎疫情和地震对狭窄市中心交通流的影响
如何通过挖掘流量数据发现隐藏现象

恐慌(图片作者:作者)
交通科学家和工程师通常使用交通数据来建模和预测交通行为。但是,利用交通数据有没有可能提取出一些与交通流量没有严格关系的“隐藏”现象呢?在本文中,介绍了论文 " 的结果:新冠肺炎疫情和地震对狭窄城市中心交通流的混合影响:萨格勒布-克罗埃西亚【1】的案例研究,其中作者发现了面对自然灾害所激发的有趣的人类行为模式。
1.介绍
2020 年对全人类来说都是充满挑战的一年。在我的家乡(萨格勒布,克罗埃西亚),在 COVID 疫情旁边,几场地震在 100 多年没有震动后发生了。作为一名交通工程师,对于上述事件,观察市中心交通流量的变化是很有趣的。
作为一组来自萨格勒布大学运输和交通科学学院的研究人员,我们试图调查新冠肺炎疫情之前和期间的交通流量趋势。此外,我们分析了地震期间的交通流量,地震于 2020 年 3 月 22 日周日早上 6:24 袭击了萨格勒布。
除了疫情造成的“正常”交通流量下降,我们还发现了关于地震反应的有趣的人类行为模式。
2.疫情对交通流的影响
为了说明疫情对交通流的影响,我们提出了两个经典的交通参数:速度和交通量。出于本研究的目的,我们观察了市中心的一条道路,基于雷达的交通计数器放置在这里。
图一。显示了在现场观察到的速度曲线,其中每个速度曲线代表每周平均的 24 小时内的平均速度变化(见图例)。我们可以看到,在 3 月 16 日之后,车辆的速度急剧增加,在繁忙时间经常出现的拥挤情况已经消失。由于政府在疫情采取了限制行动的措施,这是意料之中的行为。

图一。速度曲线(图片来源:[1])
图二。只是确认了交通流的预期行为。红色显示 2019 年的平均值,而红色显示疫情期间的值。在左侧,我们可以看到交通量和速度之间的关系,在右侧,我们可以看到交通量曲线。两个图表都显示了道路上超过 50%的车辆数量较少,因此道路上的速度值较高。

图二。[左]2019 年平均工作日(黑色)和有新冠肺炎限制的一周(红色)的量和速度的关系;[右]2019 年平均工作日(黑色)和新冠肺炎限行周(红色)的交通量概况(图片来源:[1])
3.面对自然灾害时人类的行为模式
在分析了疫情的交通模式后,我们分析了地震时的交通模式。
就我个人而言,我并不期待有什么不同。但是,我不这么认为。
图 3。显示地震发生当天的交通量(红线)和前一天的交通量(黑线)。地震发生在早上 6 点 24 分。可以看到,早上 7:30 的车流量增加了 100%以上。在检查了每日新闻后,我们看到大量的人只是跑向他们的汽车,试图尽可能远。那种行为显然是由地震引起的恐慌和恐惧引发的。

图 3。交通模式显示了由恐惧和惊慌行为引起的道路上活动的增加(图片来源:作者)
4.结论
本文展示了一个例子,展示了在挖掘数据时如何提取出意想不到的知识。我们展示了一个通过挖掘流量数据集提取人类行为模式的例子。
如果你对类似的、与交通相关的话题感兴趣,请关注我的媒体简介,或者在研究门户:)查看发表的研究
如有任何问题或建议,欢迎评论或联系我!https://www.linkedin.com/in/leo-tisljaric-28a56b123/
类似文章:
参考
作为数据科学家影响您的社区
如何利用数据科学技能获得“真实世界”的体验。

作为一名数据科学家新手,我发现的一个主要挑战是由于缺乏“真实世界的经验”而无法进入这个领域。尽管有许多很棒的训练营提供从事激情项目的专业人员,但我注意到新数据科学家仍然需要一些时间才能在许多公司找到工作。
我发现了一个“窍门”,可以获得经验,并快速跟踪一个新手将他们的数据科学技能应用到现实世界的项目中。我将在这里概述我的一些经历,我是如何发现自己在马萨诸塞州菲奇堡的当地社区从事一个数百万美元的项目的,这一切都是因为我能够找到机会,并自愿将我的技能提供给最需要它们的利益相关者。
从 Metis 数据科学训练营毕业后,我发现自己刚刚掌握了机器学习和数据科学技能,但却找不到任何有意义的项目。即使在完成了多个在线教程和项目之后,它对我来说似乎并不重要,因为它并没有被人们日常使用。因此,我采取了以下步骤来确保我能够以一种能够产生重大影响的方式来运用我的技能。
- 采访当地商业领袖:2021 年,我决定花一些时间在我的当地社区与企业主、政府官员、居民和学生讨论,试图了解他们面临的一些挑战。我问了许多开放式的问题,没有明确的目标,只是想看看我能在哪些方面发挥我的技能。在这里度过的时间实际上给了我一个很好的机会去了解马萨诸塞州菲奇堡的历史,它的风俗和它的人民。通过这一过程,我在该地区结交了很多好朋友,并建立了惊人的关系。

采访当地的小企业主。作者图片
2.从讨论中发现真知灼见:通过我与不同领导的交谈,以及在发出调查问卷后。我发现市政当局正面临一些巨大的挑战。
3.大问题:面临的问题是每年大约有 3000 万美元花费在城外,原因是在菲奇堡普遍缺乏某些种类的商业和对新商业的认识。例如,菲奇堡的居民会开 30 分钟到 1 小时的车去光顾波士顿、剑桥和伍斯特等大城市的商店,而这些商店离他们只有 5 到 15 分钟的路程。这是一个很大的问题,因为“资金外流”,即从长远来看,社区支出的资金会影响许多小企业、社区和经济发展。社区领导已经制定了一个 5-10 年的计划来解决这个问题,并鼓励我制定有用的解决方案。
4.真实世界的数据解决方案和影响:我参与了两个具体的项目,以解决该地区的商业意识和商业可用性问题。第一个项目是帮助提高当地的商业意识,第二个项目是帮助改善在镇上开办新企业的过程。在商业意识方面,我建立了 SeeksCo ,这是一个面向小城镇和城市的 yelp,允许个人轻松找到该地区的企业,并为这些企业提供评论。到目前为止,SeeksCo 正在帮助提高这些企业的意识。

与意式浓缩披萨的老板讨论(开始于 50 多年前)。图片作者。
第二个项目是与市长办公室合作,在菲奇堡市区建立店面空置的数据可视化(使用 Tableau)。该项目将允许投资者和未来的企业主查看他们可以在该地区租赁或购买的店面物业空缺,并提供详细信息,如最新照片、业主信息和物业状态。目前,菲奇堡有 100 多处空置房产,为小企业提供了许多机会,为该地区的 40,000 多名市民提供服务。这个项目的影响是显而易见的,因为它为商业领袖、投资者和其他利益相关者节省了时间。过去,个人必须手动搜索这些信息,但现在他们可以在向当地政府经济部门提出请求后访问这些数据。

菲奇堡的空置店面。图片作者。
通过这两个项目,我能够向当地领导和全球领导展示数据在社区中的作用以及如何鼓励最佳数据实践。
5.社区的持续支持:作为一名数据科学家,尽管我们的工作主要集中在数据和算法上,但我个人认为,提出解决方案的最佳方式是从数据中创造有意义的见解。通过了解数据分析影响的个人故事,我们的工作可以做得更好。
这就是为什么我志愿帮助组织菲奇堡社区的活动,以帮助提高小企业的意识。我与当地一个名为 Reimagine North of Main 的组织合作,在 2021 年夏天组织了一次街区聚会,以促进许多小企业的发展。我们有 500 多人参加,许多小企业能够获得新客户。

为小企业组织街区聚会。图片作者。
当我开始面试一份数据科学的工作时,我在当地社区的经历很有帮助。在面试数据科学工作时,我能够讨论真实世界的项目,并接受了在波士顿地区一家现代咨询公司担任数据科学家顾问的邀请。我的许多采访讨论都围绕着我在社区中所做的影响工作,并为我提供了一个用数据科学专业人员可以理解的术语进行解释的机会。
总之,数据科学领域是一个快节奏的领域,人们可能会觉得不得不进入这个领域,但找到创造性的方法来利用自己获得的技能,以一种实际上对真实的人有用的方式,不仅对提高我们的数据科学家技能有巨大的好处,而且对立即需要我们帮助的人也有好处。
更多关于我的影响力工作,请访问我的页面:【dotunopasina.com/impact】T2,并在 LinkedIn 上与我联系。
从头开始实现高斯过程
高斯过程模型的基本理论和实际实现的演练

(图片由作者提供)
G 高斯过程 (GP)是一种强大的监督机器学习方法,主要用于回归设置。这种方法在实践中是可取的,因为:
- 它在小数据范围内表现很好;
- 它具有高度的可解释性;
- 它自动估计预测的不确定性。
这最后一点是 GP 区别于许多其他机器学习技术的地方:对于 GP 模型,它在位置 x 的预测 f ( x )不是确定性的值,而是遵循正态分布的随机变量,即f(x)~N(μ(x),【T20 这里, μ ( x )表示预测均值, σ ( x )是预测方差,作为预测不确定性的指标。
那么,为什么估计预测不确定性很重要呢?有两个原因:首先,它通过告诉我们可以在多大程度上相信一个特定的预测,使能够做出可靠的决策;其次,它有助于主动学习,这意味着我们可以智能地分配对模型性能贡献最大的训练数据。
尽管高斯过程在实际应用中很重要,但它在机器学习书籍和教程中出现的次数并不多。部分原因是 GP 理论充满了高级统计学和线性代数,对新来者不太友好。
在这篇博客中,我们将采用边做边学的方法,从头开始用 Python 实现一个 GP 模型。最后,我们将把我们的原型付诸实施,并近似两个分析函数:

图 1 我们的测试函数。(图片由作者提供)
我为这篇文章创建了一个 Jupyter 笔记本。另外,我已经编译了一个备忘单,它总结了 GP 相关的公式。您可以在编写自己的 GP 模型时使用它作为快速参考。
所以,让我们开始吧!
目录
1。了解高斯过程
2。内核函数
3。 [GaussianProcess](#025e) 类
4。初始化 GP 模型
5。构造相关矩阵。
7 训练一个 GP 模型(理论)。
8 训练一个 GP 模型(代码)。
GP 模型预测(理论)9。GP 模型预测(代码)
10。基准
11。外卖
关于作者
1.理解高斯过程
使用 GP 方法的一个常见情况是:我们收集了一些训练数据= {(x*ᵢ, y ᵢ), i =1,…,n}, y ᵢ为实值标签。我们希望训练一个模型,在给定输入 x *的情况下,预测函数输出 y **
简而言之,GP 通过将底层真函数 y ( x )建模为高斯随机过程的实现来工作。正是这种统计观点使 GP 能够预测复杂的投入产出模式,并在此过程中估计预测的不确定性。我们将在接下来的章节中对此进行更详细的阐述。
1.1 高斯过程中的“过程”
其名称中的“进程”部分指的是 GP 是一个随机进程的事实。简单来说,一个随机过程就是一个函数 f (。)具有以下属性:
- 在任意位置 x , f ( x )为随机变量;
- 在不同的位置 x ᵢ和 x ⱼ,随机变量 f ( x ᵢ)和 f ( x ⱼ)相关;
- f ( x ᵢ)和 f ( x ⱼ)之间的关联强度取决于 x ᵢ和 x ⱼ.之间的距离一般来说,随着 x ⱼ远离 x ᵢ,它们的关联强度会衰减。
这里的关键要点是,我们可以将随机过程解释为相关随机变量的无限集合。
那么,我们应该用什么概率分布来描述那些随机变量呢?
1.2 高斯过程中的“高斯”
其名字中的“高斯”部分表示 GP 用高斯分布(或正态分布)来表征随机过程。
为了描述具有高斯分布的单个随机变量,我们只需要一个平均值和一个方差值。然而,为了描述包含无限数量随机变量的随机过程,我们需要将高斯分布升级为高斯随机过程。
形式上,一个高斯随机过程 f (。)的特征是均值函数 μ ( x )和协方差函数σK(x*, x *)。这里, σ 表示整体进程方差, K ( x , x *)是相关函数,也称为核函数。当 x = x , K ( x ,x)= 1;当x≦x, K ( x , x *)表示 f ( x )与 f ( x )之间的相关性。
配备了 μ ( x )和σ**K(x*, x )的符号,我们可以介绍一个高斯随机过程 f (。):
- 对于单个位置 x , f ( x )服从高斯分布,即f(x)~N(μ(x),σ);
- 对于任意一组位置[ x ₁、 x ₂,…、 x ₙ、f=[f(x*₁)、 f ( x ₂),…、 f ( x*

1.3 GP 的“世界观”
回想一下,在本节开始时,我们提到 GP 通过将底层真函数 y ( x )建模为高斯随机过程的实现来工作。这意味着 GP 模型将训练数据的观察到的标签[ y ₁、 y ₂,…、 y ₙ]视为从上述多元高斯分布中随机抽取的。**
*因此,我们能够通过推断最可能产生观察到的标签的 μ ( x )、 σ 和 K ( x 、 x )来训练 GP 模型。这通常通过最大似然估计来完成。随后,我们可以用训练好的 GP 模型对未知地点进行预测,并估计相关的预测不确定性。
乍一看,通过引入随机过程来模拟确定性函数值,从而增加额外的复杂性,这似乎是违反直觉的(y₁、y₂,…、yₙ).事实上,这种治疗方法源于贝叶斯统计。这里,随机过程假设编码了我们对函数形式 y(x)的先验信念。这个方向我们就不深入了。对于 GP 方法的贝叶斯解释,看一看墨菲的书第 15 章。
2.核函数
在我们深入研究编码之前,让我们先讨论一下 GP 模型的关键要素:内核函数。之前我们只陈述了它的一般形式 K ( x ᵢ, x ⱼ),没有给出具体的例子。我们将填补这一部分的空白。
2.1 核心特征
在 GP 建模设置中,核函数测量两个不同预测之间的“相似性”,即,较高的核函数值意味着两个预测更相似,反之亦然。
内核函数必须满足某些要求。其中之一是内核函数 K ( x ᵢ, x ⱼ)必须能够构造一个对称和正定的相关矩阵。
实际上,内核函数经常被假设为静态。这意味着核函数值仅取决于输入之间的距离,即 K ( x ᵢ,xⱼ)=k(xᵢ-xⱼ).
此外,当输入具有两个或更多特征时,我们通常假设多维核函数 K ( x ᵢ, x ⱼ)是针对每个输入特征的一维核函数的一系列乘法:

其中 m 表示特征总数,即输入尺寸。这种处理是有益的,因为它允许为
单个输入特征定制相关结构。
2.2 高斯核
常见的核函数包括立方核、指数核、高斯核和 Matérn 核。本文将使用高斯核,这是最流行的选择之一。
一维高斯核表示为:

其中 θ 是控制相关强度的核参数。高斯核的一个 m 维版本被表示为:

这只是一维高斯核的一系列乘法。这里,我们有内核参数θ=**θ₁、 θ ₂,…、 θ ₘ].
2.3 GP 的可解释性
内核参数 θ 是 GP 模型可解释性的关键,因为它表明了特征在进行预测时的重要性。让我们详细说明一下。
首先,让我们关注一维情况,其中我们只有一个特征和一个关联的 θ 。下图显示了 θ 的选择如何影响相关性。

图 2θ对相关强度的影响。(图片由作者提供)
当 θ 值较低时,遥远地点的预测保持高水平的相关性。这意味着无论 x 值如何,底层函数都会产生相似的输出。换句话说,特征 x 没有那么好预测。
同样的推理也适用于多特征设置:如果第 k 个特征的 θ 值相当低,那么当沿着第 k 个维度移动时,底层函数将产生类似的输出值。由于输出对第 k 个特征不太敏感,我们可以得出结论,在进行预测时,第 k 个特征并不那么重要。
因此,通过对( θ ₁, θ ₂,…, θ ₘ)的值进行排序,我们可以对特征的重要性进行排序,这有助于进行敏感性分析或降维。
高斯过程的基础知识到此为止。在下文中,我们将从实现的角度来研究 GP,并介绍缺失的理论和代码。
3.GaussianProcess阶级
让我们从头开始编写一个GaussianProcess类吧!为了帮助你浏览与 GP 建模相关的方程,我为你准备了这个备忘单。
3.1 课程概述
下面是对GaussianProcess类的方法和属性的总结。这个类通过使用.fit()方法训练模型和使用.predict()方法进行预测来模仿scikit-learn风格。这种一致性使得将开发的GaussianProcess估计器与其他scikit-learn函数进一步集成成为可能,如Pipeline、GridSearchCV等。

图 3 GaussianProcess类概述。(图片由作者提供)
我们将在下面的小节中讨论每一种方法。
3.2 包装
首先,我们加载所有必需的包。
我们将主要使用numpy进行矩阵操作,使用matplotlib进行数据可视化。此外,我们需要
[scipy.optimize.minimize](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html):进行优化;[scipy.optimize.Bounds](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.Bounds.html):指定优化的参数界限;[pyDOE.lhs](https://pythonhosted.org/pyDOE/randomized.html):为优化器生成随机起始点。这里,我们采用拉丁超立方体采样方法,因为它善于生成均匀分布的随机样本。
4.初始化 GP 模型
在本节中,我们开发GaussianProcess.__init__(self, n_restarts, optimizer, bounds)。
为了训练 GP 模型,我们使用多起点优化策略来估计模型参数。因此,我们需要指定我们希望优化器尝试多少个起始点,以及这个优化器应该使用哪个算法。
5.构建相关矩阵
在本节中,我们开发GaussianProcess.Corr(self, X1, X2),它计算一对特征矩阵 X1 和 X2 之间的相关矩阵。因为计算相关矩阵涉及训练和预测(稍后将讨论),所以有一个专用函数来实现这个目标是有益的。
下面给出了如何使用高斯核来构建 X1 和 X2 的相关矩阵的图示:

图 4 是构造相关矩阵的示意图。(图片由作者提供)
以下代码可以实现所需的功能:
6.训练一个全科医生模型(理论)
6.1 GP 模型参数
一个工作的 GP 模型需要一个均值函数 μ ( x )、过程方差 σ 和核参数向量 θ 。实际操作中, μ ( x )被简单假设为常数,即 μ ( x ) = μ。这种处理极大地简化了数学,并且不会对模型的准确性造成太大的伤害。我们将在编码部分实现一个平均值不变的 GP。
所以,训练一个 GP 模型意味着估计 μ 、 σ 、 θ 。但是我们到底应该怎么做呢?介绍最大似然估计。
6.2 最大似然估计
简单地说,这种估计方法通过找到一个特定的组合 μ 、 σ 和 θ 来工作,这样观察训练实例的标签( y ₁、 y ₂,…、 y ₙ、 x ₁、 x ₂,…、x的可能性**
由于 GP 假设[ y ₁,…, y ₙ]是由多元高斯分布生成的,我们可以很容易地根据多元高斯分布的概率密度函数来表示可能性 L :

其中y=[y₁,…, y ₙ], 1 为个例向量, K 为训练实例的关联矩阵。
在实践中,我们倾向于最大化可能性的对数 L 以避免舍入误差:

幸运的是,如果我们将对数似然相对于 μ 和 σ 的导数设置为零,则 μ 和 σ 的最佳值存在解析表达式:

然而,对于T5【θ来说,由于它嵌套在相关矩阵中,因此推导最优 θ 的解析式是不可行的。因此,我们需要采用一种优化算法来寻找一个使可能性最大化的θL。
我们可以通过将最优的 σ 代入 L 来简化 L 方程。经过一些代数运算后,我们得到了下面的优化问题:

在本文中,我们采用多起点策略来估计最优 θ 。基本上,这种策略会多次运行优化器,每次都从不同的初始 θ 值开始。最后,选择产生最佳目标函数值的 θ 值作为最终优化结果。
7.训练 GP 模型(代码)
在本节中,我们开发了GaussianProcess.likelihood(self, theta)和GaussianProcess.fit(self, X, y)方法。
这里值得一提的是:从实现的角度来看,在对数尺度上搜索 θ 有利于加速优化算法的收敛。因此,在下面的代码中,我们将进行优化( μ 、 σ 和 10^t59】θ)。 θ 的合适搜索范围是[-3,2],其转化为在 0.001 和 100 之间变化的相关长度。
7.1 似然函数
首先,我们开发GaussianProcess.Neglikelihood(self, theta)来计算负的可能性。在下面的代码片段中,self。x 和 self.y 构成了训练数据集。它们分别是 shape (n_samples,n_features)和(n_samples,1)的数组。
这里有几件事值得一提:
- 我们在 K 的对角线上加一个小项(1e-10)。这一项也称为“块金”项,它有助于在求 K 的倒数时缓解数值不稳定问题;
- 我们返回计算的似然值的负值。这一步是必要的,因为我们稍后将使用
scipy.optimize.minimize,最小化负似然值相当于最大化原始似然值; - 为了便于理解,我天真地使用了
np.linalg.inv()和np.linalg.det()来计算**的倒数和行列式K。然而,在实践中,这些处理可能是耗时的,并且可能导致数值不稳定。更可靠的替代方案是计算 K 的乔莱斯基因式分解,并使用获得的下三角矩阵进行进一步的矩阵乘法和行列式计算。请参阅配套的笔记本了解这种可靠的实现方式。**
7.2 模型拟合功能
现在我们开发GaussianProcess.fit(self, X, y)来实现模型拟合。在下面的代码片段中,我们使用拉丁超立方体方法为优化器的运行生成起点 θ 。由于pyDOE.lhs只生成[0,1]内的随机样本,我们需要将样本缩放到我们的目标范围[-3,2]。
8.GP 模型预测(理论)
*现在我们知道了如何训练 GP 模型,让我们换个角度,看看如何用训练好的 GP 模型进行预测。我们的目标是预测测试点 x *的基础函数值。我们将预测表示为 f 。
回想一下,我们在开头提到 GP 预测 f 不是一个确定值,而是一个遵循高斯分布的随机变量。这个结果实际上是通过计算【y】,**即训练实例的观察标签条件下的 f *的分布得到的。在贝叶斯语言中,我们是在计算 f 的后验分布。
下面,我们讨论如何计算 f 的这个条件分布。我们就从 f 和 y 的联合分布开始,即**P(y, f )。从那里,我们可以导出期望的条件分布P(f** |y)。**
8.1 测试/训练数据的联合分发
首先,注意,根据高斯随机过程的定义,我们可以将联合分布P(y*, f )表示为多元高斯分布:

*其中 K 是训练实例的相关矩阵, k 是测试实例和训练实例之间的相关向量:

3.2 测试数据的条件分布
第二步,我们推导出P(*f |y),它描述了在给定观察到的标签 y 的情况下 f 是如何分布的。*
高斯随机过程的一个很好的性质是,*f的期望条件分布也是高斯分布,即f** |y~ N(μ,*σ),其中**

f |y~ N(μ,*σ)充分刻画了 x *处的 GP 预测。在实践中,我们用均值 μ 作为预期预测值,用方差σ来表示预测的不确定性。
从它们的联合分布 P( f, y )中导出 P( f| y )在分析上是容易处理的。为了让我们的讨论更集中,我跳过冗长的代数,让读者参考这个 [StackExchange 页面](http://Stackexchange page)了解更多细节。
9.GP 模型预测(代码)
在本节中,我们开发了GaussianProcess.predict(self, X_test)方法来启用 GP 模型预测。
在下面的代码片段中,我们打算一次预测多个测试实例。结果,在第 17 行中,我们将在所有测试实例和训练实例之间有一个相关矩阵 k (而不是理论部分所述的仅仅一个相关向量)。因此,在第 20 行和第 23 行,计算出的 f 和 SSqr 现在是数组。
最后,让我们开发GaussianProcess.score(self, X_test, y_test)方法,以均方根误差来评估模型的准确性。
10.基准
这一节将我们开发的 GP 类投入使用,并测试它逼近函数的能力。我们将首先在 1D 函数上测试 GP 类。稍后,我们将测试扩展到 2D 案例。
10.1 1D 试验
对于本案例研究,我们的目标是训练一个 GP 模型,它可以精确地逼近以下函数:

这个函数绘制如下。正如我们所看到的,这个函数是高度非线性的,并且在 x =0.6 之前和之后具有不同的“活动性”水平。这些特征在构建全球精确模型方面提出了挑战。

图 5 1D 测试函数。(图片由作者提供)
为了训练 GP 模型,我们选择分布在[0,1]范围内的 8 个训练实例。在下面的代码片段中,我们首先创建一个训练数据集和一个测试数据集。然后,我们初始化一个GaussianProcess实例,并采用一个 L-BFGS-B 算法来寻找最优的 θ。因为我们使用的是多起点优化策略,所以我们希望优化器通过每次使用不同的初始点来运行 10 次。在训练数据集上训练模型之后,该模型用于预测测试数据标签。
预测结果如下所示。我们可以看到 GP 的预测与真实函数几乎完全相同。图中还显示了 GP 预测的 95%置信区间,计算为*μ(x)+/-1.96σ**(x*)。我们可以看到真正的函数完全位于置信带内。**

图 6 GP 预测设法捕捉潜在的测试函数。(图片由作者提供)
因此,我们可以得出结论,开发的 GP 类工作正常,GP 模型足够精确,可以近似当前的 1D 测试函数。
10.2 2D 试验
在这个案例研究中,我们的目标是训练一个 GP 模型,它可以精确地逼近 2D·罗森布罗克函数:

这个函数绘制如下。

图 7 2D 测试函数。(图片由作者提供)
为了训练这个 GP 模型,我们总共选择了 25 个训练实例,如下所示。这些训练样本由拉丁超立方体采样器生成。

图 8 训练样本。(图片由作者提供)
下面的代码为 GP 模型定型,并对测试数据网格进行预测。通常在[0,1]范围内调整 GP 输入特性。因此,我们采用一个MinMaxScaler来进行缩放,并将其集成到一个流水线中。
最后,利用.score方法对模型预测精度进行了检验。计算的 RMSE 误差是 2.44,考虑到我们的目标函数从 0 到 2400 变化,这是相当低的。为了直观地评估模型的准确性,我们可以在等值线图中绘制 GP 预测:

图 9 GP 预测成功地恢复了真实的 2D 测试函数。(图片由作者提供)
事实上,GP 近似值与底层的真实函数几乎相同,这表明经过训练的 GP 模型非常准确,并且我们对GaussianProcess类的实现工作正常。
11.外卖食品
在本文中,我们已经讨论了高斯过程建模方法的基本理论。通过从头构建一个原型,我们将理论转化为代码,并从实际实现的角度深入到本质。
肯定有更多关于如何从基础 GP 到更高级版本并实现迷人分析的内容。这篇文章涵盖了一些高级概念:
**
关于作者
我是一名博士研究员,从事航空航天应用的不确定性分析和可靠性分析。统计学和数据科学是我日常工作的核心。我喜欢分享我在迷人的统计世界中学到的东西。查看我以前的帖子以了解更多信息,并在 中 和Linkedin上与我联系。**
实现和训练文本分类转换器模型——简单的方法
了解如何用几行代码实现和训练文本分类转换器模型,如 BERT、DistilBERT 等

作者图片
文本分类无疑是自然语言处理最常见的应用。而且,像大多数 NLP 应用一样,变压器模型近年来在该领域占据了主导地位。在本文中,我们将讨论如何使用一个 Python 包(我是名为快乐转换器的主要维护者)用几行代码实现和训练文本分类转换器模型。Happy Transformer 建立在 Hugging Face 的 transformers 库之上,允许程序员只需几行代码就可以实现和训练 Transformer 模型。
预训练模型
在拥抱脸的模型分发网络上有 100 种预训练的文本分类模型可供选择。因此,我建议在你花太多时间担心训练模型之前——看看是否有人已经为你的特定应用微调了模型。例如,我已经制作了关于如何为情感分析和仇恨言论检测实现预训练变压器模型的内容。在本教程中,我们将实现一个名为 finbert 的模型,它是由一家名为 Prosus 的公司创建的。该模型检测金融数据的情绪。
装置
PyPI 上有 Happy Transformer,因此我们可以用一行代码安装它。
pip install happytransformer
实例化
让我们导入一个名为 HappyTextClassification 的类,我们将使用它来加载模型。
from happytransformer import HappyTextClassification
在这里,我们可以使用 HappyTextClassification 类为模型实例化一个对象。第一个 position 参数指定模型的类型,并且全部大写。例如,“BERT”、“ROBERTA”和“ALBERT”都是有效的模型名称。第二个位置参数表示模型的名称,可以在模型的网页上找到。最后一个参数是一个名为“num_labels”的参数,它指定了模型拥有的类的数量。在这种情况下,模型有三个标签:“正面”、“中性”和“负面”
重要提示:实例化模型时,不要忘记设置 num_labels。否则,可能会发生错误。
happy_tc = HappyTextClassification("BERT", "ProsusAI/finbert", num_labels=3)
使用
我们可以用一行代码通过“classify_text”方法对文本进行begin 分类
result = happy_tc.classify_text("Tesla's stock just increased by 20%")
让我们把结果打印出来,以便更好地理解它。
print(result)
输出:TextClassificationResult(label = ' positive ',score=0.929110586643219)
如您所见,the 的输出是一个数据类,有两个变量:“标签”和“分数”标签是一个字符串,用于指示输入被分类到哪个类。“分数”变量指定模型以浮点形式分配给答案的概率。我们不能孤立这两个变量。
print(result.label)
print(result.score)
结果:
正向
0.929110586643219
这是另一个显示负输入输出的例子。
result = happy_tc.classify_text("The price of gold just dropped by 5%")
print(result.label)
print(result.score)
输出:
负
0.8852565288543701
培训—自然语言处理情感分析
现在让我们来讨论培训。我们将训练一个模型来检测与 NLP 相关的文本的情感。我们将只使用两个例子进行训练——这当然不足以稳健地训练一个模型。但是,这只是为了演示。
我们必须创建一个包含两列的 CSV 文件:文本和标签。文本列包含我们希望分类的文本。标签列包含大于或等于 0 的整数形式的标签类型。下表给出了一个培训 CSV 的示例。

下面是生成上述 CSV 文件的代码:
import csv
cases= [("Wow I love using BERT for text classification", 0), ("I hate NLP", 1)]
with open("train.csv", 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["text", "label"])
for case in cases:
writer.writerow([case[0], case[1]])
首先,我们将安装一个普通版本的 DistilBERT 作为起点。您还可以使用其他模型,如 BERT、ALBERT、RoBERTa 等。访问拥抱脸的模特分销网络获取更多模特。
happy_tc = HappyTextClassification(model_type="DISTILBERT", model_name="distilbert-base-uncased", num_labels=2)
然后,我们可以使用新实例化的类简单地调用方法“train”。
happy_tc.train("train.csv")
就是这样!我们刚刚训练了模型。我们现在可以像在上一节中一样继续使用它。所以,举个例子,你现在可以像以前一样调用“happy_tc.classify_text()”,新微调的模型就会被使用。
自定义参数
通过使用一个名为“TCTrainArgs”的类,我们可以很容易地修改学习参数,比如时期数、学习速率等等。让我们导入 TCTrainArgs。
from happytransformer import TCTrainArgs
现在,我们可以使用 TCTrainArgs 类创建一个对象来包含训练参数。在这里你可以修改的参数列表。让我们将训练时期的默认数量从 3 增加到 5。
args = TCTrainArgs(num_train_epochs=5)
让我们像以前一样调用 happy_tc 的 train 方法,但是这次将 args 对象传递给该方法的 args 参数。
happy_tc.train("train.csv", args=args)
好了,我们刚刚修改了学习参数!
评价
HappyTextGeneration 对象有一个内置的方法,允许您快速评估您的模型。首先,按照培训中讨论的方式格式化数据,然后调用。eval()"方法。为了简单起见,让我们使用训练文件来评估。
result = happy_tc.eval("train.csv")
print(result)
结果:eval Result(loss = 0.2848379611968994)
然后,我们可以像这样隔离损失变量:
print(result.loss)
输出:0.284879611968994
我建议你用总体数据的一部分来训练,另一部分来评估。然后,在训练前后评估你的模型。如果损失减少,那就意味着你的模型学会了。你也可以创建你的数据的第三部分来运行实验,以找到最佳的学习参数——但这是另一个时间的话题。
结论
就是这样!您刚刚学习了如何实现和训练文本分类转换器模型。使用 Happy Transformer 只用几行代码就能完成这么多工作,这真是令人惊讶。
相关文章
这是我最近在《走向数据科学》上发表的一篇相关文章。它涵盖了如何使用零镜头文本分类模型来标记训练数据。这样,您可以在没有任何标记数据的情况下微调小型监督模型。
本文中的代码:
https://colab . research . Google . com/drive/1 jq 3o 8 whs gel 994 nos 14 qyv 98 JT 5 we-pU?usp =共享
原载于 2021 年 6 月 14 日https://www . vennify . ai。
用 Python 实现策略迭代——一个最小的工作示例
了解这个经典的动态规划算法,以优化解决马尔可夫决策过程模型

几天前我写了一篇关于价值迭代(理查德·贝尔曼,1957),今天是政策迭代的时候了(罗纳德·霍华德,1960)。策略迭代是求解马尔可夫决策过程模型的精确算法,保证找到最优策略。与值迭代相比,一个好处是有一个明确的停止标准——一旦策略稳定,它就是可证明的最优策略。然而,对于具有许多状态的问题,它通常具有较高的计算负担。
策略迭代是许多现代强化学习算法的基础,更具体地说是策略近似算法的类。事实上,bertsekis(1996)将其解释为一个行动者-批评家模型,将政策更新与价值函数相结合。在讨论最接近的策略优化和自然策略梯度之前,先掌握这个基本算法是有意义的。
[## 强化学习的四个策略类别
towardsdatascience.com](/the-four-policy-classes-of-reinforcement-learning-38185daa6c8a)
策略迭代
与价值迭代相比,策略迭代更加广泛。萨顿和巴尔托(2019)将其分为三个步骤。

政策迭代算法[来源:萨顿&巴尔托(公开发表),2019]
步骤 1 — 初始化 —设置(可能任意)值函数和策略。值函数给出了处于每个状态的感知值,策略是返回任何给定状态的动作的规则。
在步骤 2 — 策略评估 —以非常类似于值迭代的方式确定每个状态的值。然而,我们不是通过最大化所有操作来确定值,而是简单地使用当前策略来计算值。简而言之,我们将行动的价值a=π(s)(直接回报r +下游价值V(s’))乘以转移概率p。更多细节——比如误差容限θ——请查看关于价值迭代的文章。
步骤 3 — 策略改进 —寻求使用主流价值函数来改进策略。对于每个状态,它验证当前策略建议的行动是否确实是价值最大化行动。如果没有,则更新策略并重复步骤 2。该算法在这两个步骤之间交替,直到策略保持稳定。此时,达到了最优策略,算法终止。
对于价值迭代和策略迭代之间的直接比较,请在这里并排查看它们。

策略迭代(左)和价值迭代(右)的比较。关键区别在于策略迭代分离了评估和策略更新。[改编自萨顿&巴尔托出版社(公开发行),2019 年]
一个最小的工作示例
编码示例的时间到了。我喜欢用非常简单的问题来演示算法,正如下面的简要概述所证明的那样。
问题是
感兴趣的问题是一维世界(一排瓷砖)。在中间,有一个瓷砖作为终止状态。落在这块瓷砖上产生+10 的奖励,在瓷砖间走动每移动一次花费-1。代理可以决定向左或向右移动,但最终有 10%的时间走错了方向。有了直接回报、预期下游回报和转移概率,它就具备了 MDP 的基本要素。
该算法
Python 算法与前面展示的数学过程没有太大的不同。注意,最大迭代次数是为了限制计算量而进行的简化。特别是,如果两个或多个策略执行得同样好,算法可能会在它们之间无休止地循环。
一些实验
是时候做点实验了。我将提供一些计算示例以及对结果的一些思考。
我们设置所有的值函数V(s)=0,并在π=[0,0,0,0,0]初始化策略(即总是向左移动)。
然后,我们转向政策评估。重申:在这一步中,我们根据当前策略 π确定状态值。策略评估是一个迭代步骤,重复直到所有值都低于错误阈值θ。在这种情况下(状态 0,迭代 1),初始估计值V(0)是 0。由于直接奖励是-1,误差Δ也是 1,我们需要重复。

第一步政策评估。在策略评估过程中,我们根据现行策略计算每个状态的值。重复评估步骤,直到值在预定的误差范围内。
达到正确的价值观需要一些时间。如果我们取θ= 1e-10,对于所有状态,我们需要在Δ<θ之前进行不少于 185 次迭代。我们获得V=[-8.62, -7.08, 10.00, 7.61, 5.68]。
现在,我们继续讨论政策改进。我们测试我们的初始策略(“始终向左”)是否确实是最优的— 剧透警告:根据我们刚刚确定的值,它不是最优的。

状态 1 的第一个政策改进步骤。给定之前确定的状态值,我们验证当前策略是否是最佳的,或者不同的操作会产生更好的结果。如果我们可以通过采取不同的措施来改进当前策略,我们就更新策略并返回到策略评估。
显然,在最左边的瓷砖上,向右比向左好。因此,不符合停止标准。我们更新π并返回到策略评估步骤来重新计算值。
最终,我们应该到达一个政策不再改变的点。在这种情况下,它只是一个循环。更新策略后,我们在策略评估期间计算新的状态值(16 次迭代),并且在随后的策略改进步骤中不进行任何策略更改。算法已经终止,产生了π=[1,1,0,0,0]。
除此之外,其实没什么好说的。在θ和步骤 2 和步骤 3 之间的循环数之间有一个计算上的权衡,但是对于这个特殊的问题,它几乎无关紧要。
虽然在许多方面与值迭代相似,但注意策略迭代返回一个策略,而值迭代返回一组值函数。当转向近似(强化学习)算法时,这种区别变得相关。你可以有一个好的政策,而不需要很好的价值函数(或者甚至不知道它们),你也可以有准确的价值函数,而不需要对政策本身有明确的理解。
最后的话
与值迭代一样,策略迭代是一种基本算法,许多近似算法都是从它派生出来的。在真正进入强化学习的世界之前,确保理解策略迭代。
您可能感兴趣的一些更简单的工作示例:
参考
贝尔曼河(1957 年)。马尔可夫决策过程。数学与力学学报, 6 (5),679–684。
Bertsekas 博士和 Tsitsiklis,J. N. (1996 年)。神经动态规划。雅典娜科技公司。
霍华德,R. A. (1960)。动态规划和马尔可夫过程。
萨顿和巴尔托(2019 年)。强化学习:简介。麻省理工出版社。
用 Python 实现值迭代——一个最小的工作示例
掌握简单和经典的动态规划算法,寻找马尔可夫决策过程模型的最优解

在人工智能时代,精确算法并不完全是热。如果机器不能自己学习,那还有什么意义呢?为什么要费事去解决马尔可夫决策过程模型,而这些模型的解决方案无论如何都无法扩展呢?为什么不直接研究强化学习算法呢?
学习经典的动态编程技术仍然是值得的。首先,它们在实践中仍然被广泛使用。许多软件开发工作将动态编程作为面试过程的一部分。尽管您可以列举的状态和动作只有这么多,但您可能会惊讶于现实世界中的问题仍然可以通过优化来解决。
第二,即使只对强化收入感兴趣,该领域的许多算法都牢牢植根于动态编程。在强化学习中可以区分四个策略类别,其中之一是价值函数近似。在使用这些方法之前,理解经典的值迭代算法是非常重要的。幸运的是,这篇文章恰好概述了这一点。
[## 强化学习的四个策略类别
towardsdatascience.com](/the-four-policy-classes-of-reinforcement-learning-38185daa6c8a)
价值迭代
价值迭代算法的优雅是值得钦佩的。它只需要几行数学表达式,而不是更多的代码行。让我们看看萨顿&巴尔托的开创性外延:

价值迭代算法[来源:萨顿&巴尔托(公开资料),2019]
直觉相当简单。首先,你为每个状态初始化一个值,例如 0。
然后,对于每个状态,你通过将每个动作的奖励a(直接奖励r +下游值V(s’))乘以转移概率p来计算 值 V(s)。
假设我们确实初始化为 0,并且直接奖励r是非零的。差异将直接在差异表达式|v-V(s)|中可见,其中v是旧的估计值,V(s)是新的估计值。因此,误差Δ将超过阈值θ(一个小值),新的迭代随之而来。
通过执行足够多的迭代,算法将收敛到一点,在该点|v-V(s)|<θ对于每个状态。然后,您可以解析argmax来找到每个状态的最佳操作;知道真正的价值函数V(s)等同于拥有最优策略π。
请注意,如果θ设置过大,则不能保证最优。出于实际目的,合理的小误差容限就可以了,但是对于我们当中的数学爱好者来说,记住最优性条件是有好处的。
一个最小的工作示例
数学已经过时了,让我们继续编码的例子。把重点放在算法上,问题极其简单。
问题是
考虑一个一维世界(一排瓷砖),只有一个终止状态。进入终结状态获得+10 奖励,其他动作花费-1。代理可以向左或向右移动,但是——为了不使它变得太微不足道——代理在 10%的时间里向错误的方向移动。这是一个非常简单的问题,但是它有(I)直接回报,(ii)预期下游回报,和(iii)转移概率。
该算法
Python 算法非常接近萨顿&巴尔托提供的数学大纲,所以不需要扩展太多。完整的代码符合一个要点:
一些实验
好吧,那就做些实验。我们首先详细展示了该算法的两次迭代。
首先,我们设置v等于V(0),等于 0: v=V(0)=0
接下来,我们更新V(0)。注意r对于每个状态是固定的;我们实际上只通过s’对下一组状态求和。

值迭代步骤 1,状态 0[作者图片]
对于这样一个小问题来说,这似乎是很大的计算工作量。事实上,很容易理解为什么动态编程不能很好地扩展。在这种情况下,所有值V(s)仍然为 0——正如我们刚刚开始的那样——所以估计的状态值V(0)就是直接奖励-1。
让我们再尝试一个,更进一步。同样的计算,但是现在值V(s)已经在几次迭代中更新了。我们现在有V=[5.17859, 7.52759, 10.0, 7.52759, 5.17859]。同样,我们插入这些值:

值迭代步骤 4,状态 0[作者图片]
因此,我们将V(0)从 5.18 更新到 5.56。误差将是Δ=(5.56–5.18)=0.38。反过来,这将影响其他状态的更新,并且对于所有状态,该过程持续到Δ<θ。对于状态 0,最佳值是 5.68,在 10 次迭代内命中。
这里要测试的最有趣的参数是误差容限θ,它影响收敛前的迭代次数。

各种误差容差θ所需的迭代次数。容差越低,算法收敛前需要的迭代次数就越多。
最后的话
值迭代是强化学习的基石之一。很容易实现和理解。在转向更高级的实现之前,请确保掌握这个基本算法。
您可能感兴趣的一些其他最小工作示例:
参考
萨顿,理查德 s 和安德鲁 g 巴尔托。强化学习:简介。麻省理工学院出版社,2018。
LSTM 层的实现差异:TensorFlow 与 PyTorch
在张量流 LSTM 层和 PyTorch LSTM 层之间画平行线。

Tensorflow 和 Pytorch 是深度学习中使用最广泛的两个库。当涉及到实现神经网络时,这两个库有不同的方法。这两个库都有很大的不同。通常,TF 由于其更好的优化而更倾向于开发生产就绪的模型,Pytorch 由于其更“pythonic 化”的语法和急切的执行而更倾向于研究工作。但是有了 Torchscript 和 TF 2.0,两个库的差距缩小了。
这两个图书馆都有很好的社区支持和积极的贡献。因此,我们可以很容易地实现不同类型的神经网络,而在这两个库中没有任何重大问题。但是这两个库在架构上有所不同。因此,在实现神经网络时,我们需要关注某些差异。
其中一个区别是层 API。在这两个库中,可以使用子类方法或顺序模型 API 方法来设计神经网络(NN)。子类化方法是这两种方法中更受欢迎的,因为它是面向对象的和可扩展的。在这些库中实现神经网络时,我们可以使用已经设计好的层——线性(完全连接)层、卷积层、递归层等。并将它们扩展到我们的模型中。在 TF 中,我们使用tensorflow.keras.layers,在 Pytorch 中,我们使用torch.nn来访问这些层。正如我前面提到的,这些层的实现有细微的差别。大多数时候,它们都是次要的,直观的。但是在 LSTM(长短期记忆)层,这些差异有些主要和显著。在 LSTM 图层中,Pytorch 和 TF 的图层参数化方式、参数的默认值以及图层的默认输出都有很大不同。
在这篇文章中,我将尝试解释这两个库中 LSTM 层的区别。如果有人提到在一个库中实现的 NN(带有 LSTM 层)并试图在另一个库中复制它,我希望这能有所帮助。
LSTM 介绍
我添加这一部分只是为了完善和复习(如果有人需要的话)。但是如果你正在尝试理解使用 LSTM 层时的实现差异,那么我希望你已经有了深度学习的背景,并且知道 LSTMs 的基础知识。所以你可以跳过这一部分。
递归神经网络(RNN)是一种特殊类型的神经网络,用于从序列数据中学习。传统的神经网络会接受一个输入,并仅基于该输入给出预测。它不会查看以前的输入/输出并做出决定。但是当预测序列数据中的下一个值时(例如,句子,一个城市的日平均温度),我们不能只看当前的数据点。我们必须把这个系列的行为(到目前为止)作为一个整体来看待,并获得“系列的背景”来做出有意义的预测。这就是 rnn 的专业用途。rnn 用于诸如语言建模、机器翻译、序列预测等任务。
图 1 示出了 RNN 如何将当前值和先前的横向输出作为输入。递归机制传递前一时间步的横向输出(如aᵗ)。预计(并证明)RNN 层将学会捕捉横向输出中序列的“上下文”。请注意,为了更好地理解,图 1 (b)中所示的 RNN 的展开版本将相同的 层描绘为副本,并不表示多个层。

图 1 — (a)具有环路的一般 rnn(z⁻表示单位时间延迟)。(b)相同的 RNN,时间序列循环展开(由作者绘制,灵感来自 Chris 的作品)
RNN 的设计原则期望从系列中较早的值得到的“上下文”沿着序列持续很长一段时间。但是在实际场景中观察到,RNN 人学习序列“上下文”的能力随着距离的增加而减弱。也就是说,rnn 不足以捕获序列中的长期依赖性。长短期记忆(LSTM)网络是 RNN 的一个特殊版本,它的引入是为了在序列中保存长期的“上下文”信息。LSTMs 的固有设计使它们能够通过所谓的单元状态(用cᵗ表示)来捕捉长期环境。下图(图 2)显示了一个典型的 LSTM 图层。我使用的图表是基于 Chris Olah 在关于理解 LSTMs 的博客文章中使用的图表。(很精彩的博文。一定要去看看。)

图 2——一个基本的 LSTM 图层(由作者绘制,灵感来自克里斯的作品)
正如 Chris 在他的博客中解释的那样,细胞状态可以被认为是一个传送带,它从序列的开始到结束携带着长期的“上下文”。在基于当前输入(*x*ᵗ)和先前输出(hᵗ⁻¹)的每个时间步,LSTM 层决定从“上下文”中忘记什么信息以及将什么信息添加到“上下文”中。遗忘由遗忘门处理(Γ𝒻)。Γ𝒻可以被认为是一个应用于cᵗ⁻¹的面具。如果Γ𝒻中某个位置的值为 0(或更接近),当与cᵗ⁻¹相乘时,从上下文中删除该位置的信息。如果掩码的值在某个其他位置为 1(或更接近),在与cᵗ⁻¹相乘后,它允许该位置的信息在上下文中保持不变。操作Γ𝒻.*cᵗ⁻¹ ( )。 —逐元素乘法*)确保上下文中不需要的部分被忽略。向单元状态添加新信息分两步处理。首先,基于*x*ᵗ和hᵗ⁻¹,通过激活tanh的子层创建候选向量(c̃ᵗ)。然后,更新门(Γᵤ)生成一个掩码(就像在遗忘门中一样),该掩码决定将候选向量的哪一部分添加到单元状态中。然后将Γᵤ.*c̃ᵗ加到cᵗ⁻¹上生成cᵗ。最后,为了生成输出,我们让cᵗ 通过一个tanh非线性并使用它。但是为了决定给出哪一部分,我们使用了更新门(Γₒ)。Γₒ.*tanh(cᵗ)作为 LSTM 层在时间步长t的输出给出。
请注意,cᵗ⁻¹和hᵗ⁻¹都是作为 LSTM 层的横向输入给出的,相比之下,普通 RNN 层只给出了aᵗ⁻¹。
如果你需要更多关于 RNN 和 LSTM 原理的信息,我会推荐你去吴恩达的序列模型课程的第一周(可以免费旁听)并阅读克里斯·奥拉的这篇精彩的博客文章。
张量流中的 LSTM 层
在撰写本文时,Tensorflow 版本是 2.4.1
在 TF 中,我们可以使用tf.keras.layers.LSTM并创建一个 LSTM 层。初始化 LSTM 层时,唯一需要的参数是units。参数units对应于该层的输出特征数量。用我们的术语来说就是units = nₕ。nₓ将根据前一层的输出进行推断。因此,该库可以初始化 LSTM 层中的所有权重和偏置项。
TF LSTM 层期望一个三维张量作为正向传播期间的输入。这个输入应该是(batch, timesteps, input_features)的形状。这显示在下面的代码片段中。假设我们正在使用这个 LSTM 层来训练一个语言模型。我们的输入将是句子。第一个维度对应于我们使用多少个句子作为一批来训练模型。第二个维度对应于一个这样的句子中有多少单词。在实际设置中,每个句子的字数因句而异。因此,为了批量处理这些句子,我们可以选择训练语料库中最长句子的长度作为这个维度,并用尾随零填充其他句子。最后一个维度对应于用于表示每个单词的特征的数量。为了简单起见,如果我们说,我们正在使用一个热编码,并且在我们的词汇表中有 10000 个单词,那么这个维度将是 10000。
但是在初始化层的时候,如果我们设置了time_major = True,那么输入将会在 shape - (timesteps, batch, feature)中被期望。
从上面的代码片段可以看出,LSTM 的输出(带有默认参数)是形状(32,4),它对应于(batch, output_features)。因此,如果我们回到语言模型的例子,输出每个句子有一个向量,每个句子有nₕ个特征(nₕ = units =输出特征的数量)。这一个向量(每个句子)是对应于最后时间步长T(句子的最后一个单词)的 LSTM 层的输出。这个输出在我们的符号中是hᵀ。这在图 3 中进行了描述。

图 3-tensor flow LSTM 图层的默认输出(图表由作者提供)
但是,如果我们想要堆叠多个 LSTM 图层,下一个图层也需要一个时间序列作为输入。在这种情况下,我们可以在初始化层时设置return_sequences=True。那么输出将是对应于(batch, timesteps, output_features)的形状(32,10,4)。如果return_sequence设置为True,那么hᵗ : ∀t = 1,2…T将作为输出返回。这显示在下面的代码片段和图 4 中的第一个 LSTM 层。

图 4 —一个简单的模型,包含两个 LSTM 层和两个完全连接的层。注意LSTM 1层输出一个序列,LSTM 2 输出一个单一矢量。(作者配图)
如果我们想获得单元状态(cᵗ)作为输出,我们需要在初始化层时设置return_state=True。然后我们得到一个 3 个张量的列表作为输出。根据文档,如果我们同时设置return_sequences=True和return_state=True,那么这三个张量将是——whole_seq_output, final_memory_state,和final_carry_state。这显示在下面的代码片段中。
在我们的符号中,
whole_seq_output—所有时间步长对应的输出。
hᵗ : ∀t = 1,2…T;形状—(batch, timesteps, output_features)final_memory_state—对应于最后一个时间步长的输出。
hᵀ;形状—(batch, output_features)final_carry_state—最后一个单元格状态。
T1;形状—(batch, output_features)
如果我们设置return_sequences=False和return_state=True,那么这三个张量就是——final_memory_state, final_memory_state, 和final_carry_state。
单个 LSTM 层有五个使用激活函数的地方。但是如果我们查看参数,我们只看到两个参数来设置激活函数— activation和recurrent_activation。如果我们给activation参数设置一个值,它会改变应用于候选向量的激活和应用于单元状态的激活,就在与输出门进行逐元素乘法之前。将值设置为recurrent_activation将改变忽略门、更新门和输出门的激活功能。
其他参数很容易理解或者很少使用。还有一点需要注意的是,我们可以设置unroll=True,网络就展开了。这将加快训练过程,但会占用大量内存(因为同一层会被复制多次)。
下面的代码片段使用 TF 实现了图 4 所示的模型。注意每层的输出形状和每层中可训练参数的数量。
图 4 中模型的张量流实现。
Pytorch 的 LSTM 层
在撰写本文时,Pytorch 版本是 1.8.1
在 Pytorch 中,可以使用torch.nn.LSTM创建一个 LSTM 层。初始化时需要两个参数input_size和hidden_size。input_size和hidden_size分别对应于该层的输入特征数和该层的输出特征数。在我们的术语中,hidden_size = nₕ和input_size = nₓ。
在正向传播期间,Pytorch LSTM 层期望一个三维张量作为输入(类似于 TF)。但是维度的默认顺序发生了变化。输入张量的形状应该是(timesteps, batch, input_features)。如果想得到和 TF 一样的维数阶,就应该在层初始化的时候设置batch_first=True。
Pytorch LSTM API 中可以看到的另一个主要区别是,在启动时,我们可以设置num_layers=k并启动一个作为单个对象堆叠的k LSTM 层块。然而,我个人不喜欢这种方法,因为它使得整个实现可读性和可维护性更差。
下一个大的区别是 Pytorch LSTM 图层的输出。Pytorch LSTM 图层的输出是包含两个元素的元组。元组的第一个元素是形状为(timesteps, batch, output_features)的所有时间步长(hᵗ : ∀t = 1,2…T)对应的 LSTM 输出。元组的第二个元素是具有两个元素的另一个元组。这个二元组的第一个元素是对应于最后一个时间步长的输出(hᵀ)。它的形状是(1, batch, output_features)。这个第二元组的第二个元素是对应于最后一个时间步长的单元状态(cᵀ)。它也有形状(1, batch, output_features)。如果我们已经通过设置num_layers=k将 LSTM 初始化为堆叠层的块,那么hᵀ和cᵀ将具有形状(k, batch, output_features)。这里,hᵀ和cᵀ都具有堆栈中所有 k 层的最后状态。进一步在初始化时,如果我们设置了batch_first=True,那么timesteps和batch尺寸将在输出中交换(类似于输入)。
据我所知,在 Pytorch 中改变 LSTM 层内部的激活函数是不可能的。此外,不可能限制 LSTM 层只给出一个输出(如在 TF 中)。但是,我们可以将输出分配给变量,使用所需的输出,忽略其他输出,如下面的代码段所示。除此之外,其他参数应该是不言自明的。
注意,如果你设置 LSTM 层是双向的(我在这篇文章中没有谈到),那么输出形状将与我上面提到的不同。请参考文档了解这种情况。
下面的代码片段使用 Pytorch 实现了图 4 所示的模型。注意每层的输出形状和每层中可训练参数的数量。
图 4 中模型的 Pytorch 实现。
如果我们观察图 4 中模型的两种实现中的参数数量。可以观察到,LSTM 层中的参数数量存在差异。这又是一个设计选择。Pytorch 在实现 LSTM 方程(1)、(2)、(3)和(4)时做了微小的改变。TF 在每个方程中加入一个偏置向量(如我们的方程中所示)。但是 Pytorch(如这里的所示)为每个等式添加了两个偏置向量。由于每个偏置向量的形状是(nₕ,1)并且每层有四个这样的附加向量,因此 Pytorch LSTM 层中将有更多的4*nₕ个参数。在LSTM1层nₕ=8,所以有 32 个附加参数。在LSTM2层nₕ=4,所以多了 16 个参数。
参考
https://medium.com/analytics-vidhya/demystifying-lstm-weights-and-biases-dimensions-c47dbd39b30a [## 揭秘 LSTM 权重和偏见维度。
medium.com](https://medium.com/analytics-vidhya/demystifying-lstm-weights-and-biases-dimensions-c47dbd39b30a) https://stackoverflow.com/questions/44947842/can-someone-explain-to-me-the-difference-between-activation-and-recurrent-activa
用 python 从头开始实现字符级三元模型语言模型
预测是困难的,但它可以在小的方面得到解决,比如预测某人将要说的下几个单词或完成正在键入的单词或句子的下几个字符。这就是我们将要尝试去做的。

作者图片
本文的完整代码可以在这里找到
什么是 N-gram
N-gram 是来自给定文本或语音样本的 N 个项目(在这种情况下是单词)的序列。例如,给定文本“Susan 是一个善良的灵魂,她会帮助你,只要这是在她的界限之内”从开头开始的上述文本的样本 n-gram 是:
unigram : ['苏珊','是',' a ','善良','灵魂','她','意志','帮助'…………。]
bigram : ['苏珊是','是一个','一个善良','善良的灵魂','灵魂她','她会','会帮助','帮助你'…………。]
三元组 : ['苏珊是一个','是一种','一个善良的灵魂','善良的灵魂她','灵魂她会','她会帮助你'…………。]
从上面的例子中,我们可以看到 n-grams 中的 n 可以是不同的值,1 个 gram 的序列称为一元 gram,2 个 gram 的序列称为二元 gram,3 个 gram 的序列称为三元 gram。
三元模型
我们将在本文中讨论三元模型。
二元模型通过仅使用前面单词的条件概率来近似给定所有前面单词的单词的概率,而三元模型查看过去的两个单词。
因此,基于以上所述,为了计算给定先前单词 x,z 的单词 y 的特定三元模型概率,我们将计算三元模型 C(xzy)的计数,并通过共享相同单词 x 和 z 的所有三元模型的总和进行归一化,这可以使用以下等式来简化:

作者图片
也就是说,为了计算单词“soul”的特定三元模型概率,给定前面的单词“kind”、“hearted”,我们将计算三元模型 C(“kind hearted soul”)的计数,并通过共享相同第一单词“kind hearted”的所有三元模型的总和进行归一化。
我们总是以对数概率的形式表示和计算语言模型的概率。因为概率(根据定义)小于或等于 1,所以我们相乘的概率越多,乘积就越小。将足够多的 n 元文法相乘会导致数字下溢,所以我们使用对数概率,而不是原始概率。对数空间中的相加相当于线性空间中的相乘,所以我们通过相加来组合对数概率。
密码
我们将使用来自古腾堡项目的大量数据。这包含了来自不同书籍的段落。我们将以预测人物的人物级三元模型语言模型为例,考虑奥斯汀的这句话:
艾玛·伍德豪斯,英俊、聪明、富有,拥有舒适的家庭和快乐的性情,似乎联合了一些最好的存在的祝福;在这个世界上生活了近 21 年,很少让她苦恼或烦恼。
下面是这个句子中字符级三元组的一些例子:
Emm,mma,Woo,ood,…
首先,我们将对我们的数据做一些预处理,我们将把所有段落中的单词组合成一个大的语料库,删除数字值(如果有的话),并加双空格。
def preprocess(self):
output = ""
for file in self.texts:
with open(os.path.join(os.getcwd(), file), 'r', encoding="utf-8-sig", errors='ignore') as suffix:
sentence = suffix.read().split('\n')
for line in sentence:
output += " " + line
return output
接下来是生成 n 元文法的代码,我们将编写一个通用函数,它接受我们的语料库以及描述我们希望如何划分 n 元文法的值。见下文:
接下来,我们构建一个计算词频的函数,当看不到这个词时,我们将通过用一个普通字符替换频率低于 5 的词来平滑这个例子,在这个例子中, UNK 。
def UNK_treated_ngram_frequency(self, ngram_list):
frequency = {}
for ngram in ngram_list:
if ngram in frequency:
frequency[ngram] += 1
else:
frequency[ngram] = 1
sup = 0
result = {}
for k, v in frequency.items():
if v >= 5:
result[k] = v
else:
sup += v
result["UNK"] = sup
return result
接下来,我们有了三元模型,我们将对未知概率使用拉普拉斯加一平滑,我们还将把所有概率(在对数空间中)加在一起:
评估我们的模型
有两种不同的方法来评估和比较语言模型,外在评估和内在评估。我们将从本质上进行评估,因为这是一种快速评估模型的有用方式。我们将用一种叫做困惑的度量来评估,这是一种内在的评估方法,虽然不如内在评估好,但是这篇文章可以更好地解释评估概念。
我们将通过模型在一些测试数据上的表现来衡量模型的质量。语言模型在测试集上的困惑度是测试集的逆概率,用单词数归一化。因此单词序列的条件概率越高,困惑度越低,并且最大化困惑度等同于根据语言模型最大化测试集概率。
对于我们的例子,我们将使用困惑来比较我们的模型和两个测试句子,一个是英语,另一个是法语。
困惑度的计算方法如下:

作者图片
实现为:
def perplexity(total_log_prob, N):
perplexity = total_log_prob ** (1 / N)
return perplexity
测试下面的两个句子,我们得到以下困惑:
perp = self.perplexity(sum_prob, len(trigram_value))
print("perplexity ==> ", perp)
英语句子:0.1 的困惑。36860.68868688661
If we do not change our economic and social policies, we will be in danger of undermining solidarity, the very value on which the European social model is based.
The rapid drift towards an increasingly divided society is happening not only in Europe but also on a much wider scale. An entire continent, Africa - about which you made a highly relevant point in your speech, Prime Minister - has lost contact even with the developing world.
We must do everything in our power to stop this unjust development model and to give voices and rights to those who have neither.
Ladies and gentlemen, Prime Minister, the Laeken Summit and declaration are also vitally important for another reason.
Laeken must determine how we are to structure the second stage of the 'Future of Europe' debate.
法语句子:0.2 的困惑。56860.68868888661
Je suis reconnaissante à la Commission d' avoir adopté ce plan d' action.
Cependant, la condition de son applicabilité est que les personnes atteintes d' un handicap puissent disposer des moyens financiers nécessaires, et qu' elles aient la possibilité purement physique de passer les frontiÚres.
Il serait intéressant de savoir si la Commission est aussi disposée à débloquer des fonds en faveur des personnes handicapées, pour qu' elles puissent, elles aussi, parcourir le monde, aussi loin que pourra les emmener le fauteuil roulant.
J'ai mentionné la directive que la Commission a proposée pour l'aménagement des moyens de transport collectifs, afin que les handicapés puissent les utiliser.
Le Conseil n'a pas encore fait avancer cette question, qui en est au stade de la concertation.
不出所料,我们的模型被法语句子搞糊涂了,目前这已经足够好了,但是,我们的模型还可以改进。
欢迎评论和反馈。
不要忘记点击“关注”按钮。
使用 C++从头开始实现决策树
来自数据科学家的教训
Python 已经成为数据科学的语言之王。大多数新的数据科学家和程序员继续学习 Python 作为他们的第一语言。这是有充分理由的;Python 有很浅的学习曲线,强大的社区和丰富的图书馆数据科学生态系统。
我从 Python 开始了我的数据科学之旅,它仍然是我解决数据科学问题最常用的工具。我感兴趣的是更好地理解 Python 从你那里抽象出了什么,以及用更高性能的语言编写更快的代码的成本与收益。
为了获得 C++的典型介绍,我需要一个典型的应用程序,C++将是一个合适的选择。从头开始实现分类决策树分类器似乎是一个适当的挑战。事实证明,这是一次考验但有益的学习之旅,我想分享一些我在这一过程中的主要收获。
主要学习内容:
- C++很少提供指导或保护
- 尽早做出好的架构决策
- 从长远来看,编写测试将会拯救你
- 一门语言的在线社区很有价值
- 便携性是一个重要的考虑因素
C++很少提供指导或保护
在 Python 中你可以逃避很多。你可以创建一个变量,随心所欲地改变它的类型,然后不用担心如何处理它。这可以让你在实现某件事情的中途改变想法。非常适合动态迭代原型。
在 C++中,你必须预先决定你希望你的变量是什么类型。你还必须预先决定你希望你的函数返回什么类型。如果你声明错误,例如试图从一个被声明为返回整数的函数中返回一个字符串,你的进程将会停止。在这种情况下,编译器会阻止你编译你的程序,通常会显示一个复杂的错误信息。尽管这可能令人沮丧,但编译器是你的朋友,它会在这个问题导致以后的问题之前提醒你。在 Python 中,当发现问题时已经太晚了,例如在代码进入生产阶段之后,这种情况并不少见。

在上面的例子中,编译器捕捉到一个被定义为返回整数的函数试图返回一个字符串。
也有编译器不支持你的时候。有可能访问一个被认为存储在特定内存地址的变量,却收到一个垃圾值,因为该变量已经被删除了。在这里,您通常不会在编译时收到一个错误,并且很容易在代码中留下错误,而您对此并不知情。

在上面的例子中,即使我们试图访问一个已经被删除的变量的内存地址的值,编译也不会给出错误。
尽早做出好的架构决策
在 Python 中,在试图解决问题的过程中,很容易在早期就开始编写解决方案。由于 C++的不灵活性和较慢的开发速度,这种方法在使用 C++时效果不佳。
我在这个项目中遭受了痛苦,因为最初使用我的 Pythonic 方法,只是编写代码,而没有制定端到端的解决方案。最终,我坐下来想出了一个总体架构来解决这个问题。
下面列出了在决策树分类器的实现中开发的关键对象。这些包括一个Node类和一个Tree类,以及它们相关的属性和方法,并且大部分可以在编写任何代码之前定义:
*Node* - Node constructor
- Node destuctor
- Attributes
- children nodes
- data
- best split feature chosen
- best split category chosen
- Methods
- giniImpurity() - metric for scoring quality of split
- bestSplit() - best split feature and category*Tree* - Tree constructor
- Tree destructor
- Attributes
- root node of tree
- Methods
- traverse() - traverse nodes of tree
- fit() - fit tree to dataset
- predict() - make predictions classes with unseen data
- CSVReader() - read a csv
决策树项目的核心文件(不包括测试文件)如下所示,以供参考。
.
├── CMakeLists.txt
├── CSVReader.cpp
├── CSVReader.hpp
├── DecisionTree.cpp
├── DecisionTree.hpp
├── Main.cpp
├── Node.cpp
├── Node.hpp
└── README.md
一旦这种架构就位,解决方案自然随之而来。类及其成员函数(类和函数参数以及返回的对象)的接口的前瞻性设计也可以使事情变得更加简单。
从长远来看,编写测试将会拯救你
C++缺乏安全性,这使得测试代码的每一部分是否成功完成其预期功能变得至关重要。使用 CMake 构建的用于 C++的 Google Test 测试框架很好地服务于这个项目。
预先以可测试的方式编写代码使得识别和隔离 bug 更加容易。方法是为实现的类编写静态定义的成员函数。静态定义的成员函数可以在没有父类实例化的情况下独立执行。这使得能够为这些功能中的每一个编写特定的、独立的测试用例,从而完成决策树的业务逻辑的一个方面。

上面显示了 Google Test 通过终端测试后的输出。
在线社区很有价值
Python 开发人员有一个开发人员社区,他们使用 Stack Overflow 和博客等工具贡献集体知识。这个资源是 Python 数据科学的命脉。C++没有对等的社区。在谷歌上搜索开发 C++代码时遇到的许多问题和错误信息通常会导致无益的结果。一门语言的社区价值很大。

从上面我们可以看到,现在每月回答的 Python 相关问题比 C++多 4 倍多。在此查看这些统计数据的当前状态。
便携性是一个重要的考虑因素
在 Python 中,你可以确信任何安装了 Python 解释器的系统都能够执行你的 Python 程序。在 C++中,你不再有这种奢侈。由于 C++是一种编译语言,所以在运行程序之前,必须先对其进行编译,并且必须针对要运行程序的主机的体系结构进行编译。
当试图使用 Github 动作远程测试代码时,这成为一个重大问题。由于主机是不同的操作系统和架构,因此需要先编译代码,然后在虚拟机上进行测试。这是部署代码时需要管理的额外开销。
结论
学习像 C++这样的低级语言会让你接触到更快的程序所需的许多核心概念,比如内存管理、数据结构和编译语言。它让人们意识到,Python 中预先实现的数据结构(如 Pandas DataFrames)将拥有用于处理内存管理的系统,这些系统必须做出一系列假设,因此具有限制。
在实践中,不太可能有很多数据科学家会使用 C++来解决实验数据科学问题,但是在一些问题上,Python 不再是最好的工具,例如编写快速数据解析器或实现昂贵的算法。即使在这种情况下,我也将探索现代低级语言,如 Go-lang 和 Rust,而不是 C++。C++语法让人感觉冗长,而且它缺少许多你可以从这些现代语言中获得的安全特性。
你可以从头开始查看 C++决策树分类器的完整源代码点击这里。你还可以找到一个 Jupyter 笔记本的例子,它直接从 Python 调用实现的决策树分类器,并在 Titanic 数据集上训练决策树。
从零开始实现深度学习象棋引擎
实践教程
用极小极大和深度学习平衡直觉和计算

JESHOOTS.COM在 Unsplash 上拍照
目录:
注意:所有代码都是片段形式,单独执行时无法工作。完整代码可以在我的 github 页面 这里 找到。
简介:

国际象棋中出现的一个常见主题是直觉和计算之间的权衡。直觉是找到在这个位置上“感觉正确”和“运作良好”的招式,而计算则是精确地寻找对手可以用来反驳你的招式的招式。
直觉通常与位置游戏联系在一起,而计算与战术游戏联系在一起。然而,没有直觉就找不到战术打法,没有一些计算就无法进行位置打法。在这个项目中,我将尝试把两个算法联系起来,一个基于直觉的算法和一个基于计算的算法,以形成一个比它们各自更强大的算法。
神经网络:

神经网络是混合算法的直观和定位端。它是在数以千计的大师级国际象棋比赛中训练出来的。
左边的这个游戏是在两个神经网络之间进行的。当查看引擎播放的移动时,很明显网络已经学习了一些基本的位置概念。
例如,您可以看到引擎将骑士推到中心,将主教固定,并推动卒来获得空间。引擎也城堡,以保护他们的国王和部队棋子的中心。
当你看到双方都犯的错误和失去的机会时,神经网络的弱点就很明显了。黑棋在游戏中错过了一个岔口,白棋不断地将他的骑士放在兵的正前方。这可以归因于这样一个事实,即对成千上万个游戏进行归纳会导致精确度降低,尤其是在应用于特定情况时。
这证明了神经网络是强大的,但如果没有其他工具来防止失误并允许战术发挥作用,神经网络是无用的。
实施:
这些是定义和运行神经网络所需的先决条件。棋盘转换是存储库中的另一个文件,包含将棋盘转换为矩阵的函数。文件中有太多的小细节需要在这里解释,但文件中定义的函数是运行神经网络所必需的。
有必要将神经网络定义为一个类,以便它可以适当地用于混合算法中。混合算法中的每个预测算法都必须具有相同的输入集,并且都必须具有预测函数来生成输出。
我用作概念验证的神经网络是一个基本的卷积网络:
该网络将经过处理的棋盘作为输入数据,并输出一个 64 乘 64 的矩阵。每个单元格描述了棋盘上可以进行的一步棋。我们可以把列想象成棋子开始的方块,把行想象成棋子结束的方块。
自然,在一个位置上,不是所有的移动都是合法的。要获得合法移动,必须使用过滤器来删除所有非法移动。
这个函数通过创建一个掩码矩阵来删除所有的非法移动。该矩阵与网络的输出矩阵大小相同,但由 1 和 0 组成。1 放在代表合法移动的单元格中,而 0 放在代表非法移动的单元格中。当我们将输出矩阵乘以合法移动矩阵时,我们得到一个矩阵,其中所有非零值都是合法移动。
然后,我们找到新矩阵中的最大值,并找到单元格代表的移动。然后,我们输出该移动作为算法生成的最终移动。
极大极小树:
极大极小树的工作原理是将某个位置一定深度内可以到达的所有位置可视化。然后,它评估所有可以达到的最终位置,并追溯到第一组合法移动的评估。
为了做到这一点,我们假设我们会走能给我们带来最大利益的那一步,并假设对手会走能给我们带来最小利益的那一步。因此,极大极小树的名字。

这个游戏是由两棵极大极小树玩的。你可以看到所有的移动都是围绕着保护或获得材料。这些动作中的大部分你在真实游戏中是看不到的。例如,在游戏早期推车来保护棋子。然而,这些错误在使用极大极小树时很容易发生,因为它只关心材料。
极大极小树的另一个问题是最终位置的数量随着深度的增加呈指数增加。因此极小极大树不能实现延伸超过其深度的计划/战术。
实施:
这些是极大极小树的先决条件。极大极小树给出了混合算法的搜索深度和目标物质计算。
驱动极大极小树的主要函数是材料计数器算法和可能性连续算法。材料计数器计算位置中的材料。这是用来判断位置的评价函数。
可能性延续是用于扩展极大极小树的函数。对于从给定位置开始的每个可能的合法移动,该函数在给定棋盘上执行每个移动并返回它。每当树的深度增加时,这个函数就迭代一次。
极大极小树是一个相当复杂的算法,因为成千上万的板子需要追溯到它们的源头。为了使流程更加整洁,必须定义一个节点类。
极小极大树将从这些节点构建。每个节点包含它所代表的棋盘,以及所有“子节点”的列表(该位置的所有可能延续)。
evaluate 函数用于计算每个节点的效用。通过将 child_nodes 作为节点的一个属性,您可以轻松地在树的各个层中传播,以获得正确的效用值。
定义了节点之后,我们就可以构造极大极小树了。极大极小树是用类定义的,所以我们可以在函数执行后读取信息。
我们首先创建一个根节点。所有未来的节点都可以追溯到根节点。
之后,我们可以构造极大极小树。在给定深度的情况下,我们扩展树,将上一代的子代添加到树的底部。
然后,我们定义将作用于树的每一层的函数。我们为所有涉及我们所下的棋的层设置一个最大值函数,为所有涉及对手所下的棋的层设置一个最小值函数。请记住,评估函数是从我们的角度来看的,因此对手移动上的最小函数意味着我们找到了对手可能的最佳移动。
为了让这个算法适合象棋引擎,我们必须包含一个预测函数。它返回预测的移动以及有效性值。该值在混合算法中进行权衡,以选择要使用的算法。
混合算法:
混合算法是这个项目中最薄弱的环节。我不得不将这两种算法联系起来的许多想法太复杂,无法立即实现。
例如,我的一个想法是让每个算法产生一个输出矩阵。通过将输出矩阵相乘,我们得到一个“集合”矩阵,然后可以将其转换为移动。然而,我需要以某种方式将极大极小树的输出转换成 64 乘 64 的矩阵,这在不改变数据的情况下是不可能的。
实施:
这是混合算法设置的源代码。它需要神经网络和极大极小算法中描述的所有代码。
当产生一步棋时,它判断不同算法输出的效力值,并找出哪个算法具有最高的效力值。
请注意,如果两种算法具有相同的有效性值,则只输出由算法列表中第一个列出的算法输出的移动。这是由于 argmax 的特性,它从左到右查看列表。
该文件是一个故障排除工具,也是查看混合算法运行输出的一种方式。混合算法自己下一百步棋,输出棋盘。它还显示了两种算法输出的有效性值。
我添加到文件中的另一个功能是可以粘贴到 PGN 文件中的字符串。这保存了游戏的所有数据,然后可以用它来分析引擎更强的游戏。
请注意,该文件的输出仅在使用 python notebook 时可见,因为它使用 IPython 工具渲染电路板。
分析游戏:

这个游戏是由两种混合算法来玩的。虽然移动本身可能并不完美(可能是由于混合算法的糟糕设计),但它完美地封装了算法使用的计算和直觉。
以下是我对这个游戏的一些观察:
开场:
奇怪的开场:
尽管在许多不同的游戏中训练过神经网络,但它扮演了一个几乎不会在任何游戏中出现的角色。这可能是因为该算法所玩的开局可能是它所看到的所有开局的某种平均值。
错误的判断:
似乎有很多动作试图在开场赢得素材。这通常不应该发生在开局,因为它可能会导致残局失败。这证明了用于评估游戏移动的有效性可能是错误的,并且在错误的时间被激活。
中局:
战术别针:
在中局中,黑王被压在车下。这使得白赢得车。这个战术其实很幸运。创建图钉的移动是由神经网络完成的,但是取车是由极大极小树完成的。这肯定是事实,因为最小最大树不会比神经网络有更高的有效性值,因为没有直接的捕获。
将死尝试:
象棋引擎的另一个明显的盲点是缺乏一个处理和执行将死的系统。白方王后拿走后排的车,并检查黑方国王。黑棋只能用女王挡。然后白方用车过黑王。然而,白车会立即被取走。这出戏是由神经网络完成的,表明这种位置很常见,足以对神经网络产生真正的影响。然而,没有足够的计算和系统来检查将死,所以车最终丢失了。
终局:
推动通过的棋子:
神经网络在残局中完成了大部分繁重的工作,因为没有太多的战术机会。它的想法是推动卒晋升为皇后,并试图阻止对手传递的卒。这表明神经网络已经清楚地从数据中学习了如何推动棋子。
重复:
游戏在重复中结束。令人失望但绝对在意料之中。国际象棋引擎的一个问题是,它往往会经常重复移动。一个可能的原因是这两种算法都是从左向右解析合法的移动。如果逆转前一步棋的棋又是第一个合法的棋,那么很可能会被下。算法中应该有某种机制来防止三重重复。
结论:
有了向混合算法添加更多算法的框架和极大极小树的适应性,我认为有很多方法可以提高算法的能力。
1.阿尔法-贝塔剪枝
这项技术将加快极大极小树的速度,从而增加计算每一步棋的深度。
2.更多算法
如果我们增加象棋引擎中的算法数量,并找到一种可靠的方法将它们组合在一起,这个引擎会产生更好的结果。
3.内置故障保险
如果我们防止三重重复,赋予请求和棋的能力,并添加一个检查将死的系统,引擎将更有能力与更强的玩家进行游戏。
我的链接:
如果你想看更多我的内容,点击这个 链接 。
在 PyTorch 中实现高效的广义核感知器
理论、实现和实验

图片来自模型图
感知器是一种古老的线性二进制分类算法,它形成了许多机器学习方法的基础,包括神经网络。像许多线性方法一样,核技巧可以用来使感知器在非线性数据上表现良好,并且与所有二进制分类算法一样,它可以推广到 k 类问题。
我第一次实现感知器算法是在 Numpy,我在 MNIST 数据集上进行实验,以确定一般化模型在各种任务上的表现。不幸的是,当我写我的初始实现时,我生病了,所以它充满了错误,非常慢,而且占用大量内存。休息了一段时间并更好地思考了这个问题后,我决定再试一次,但这次使用我新喜欢的库:PyTorch。
本文从简单介绍感知器算法和内核化版本开始。然后深入到 k 类的归纳、实现和优化,最后到实验,在实验中,通过多种测试来确定模型在一系列任务中的性能。本文以我在处理这个问题时遇到的一些陷阱和关键要点作为结尾。
感知机是如何工作的
在线学习与批量学习
感知器使用在线学习来优化其权重,这与大多数其他使用批量学习的机器学习模型不同。这些方法的关键区别在于如何计算权重。
利用批量学习,基于所有数据一次性计算成本函数,结果是在同一个步骤中更新权重。

例如,在梯度下降法中,我们计算批次(或最小批次)J 的总成本,然后求出它相对于重量的导数。然后我们一次性更新权重向量。λ是步长。
在在线学习中,数据是按顺序输入的,因此,权重会随着每个新的数据点而更新。

数据被视为一个序列(如时间序列)。因此,下一个权重由当前权重和当前数据点(也可能包括所有先前的数据点)确定
记住这一点,让我们看看感知器的训练算法是如何工作的:

单个时期(通过数据的一个完整周期)的感知器训练算法。如果你有兴趣了解更多关于模型的复杂性,请访问这个讲座。
请注意该模型的几个关键方面:
- 由于点积运算,该模型只能学习线性决策边界
- 对于 wt 点 xi 为零的情况,符号函数具有模糊性。在这种情况下,我们可以随机选择-1 或 1 作为输出,或者我们可以选择一个,例如≥ 0 → 1,< 0 → -1 或> 0 → 1,≤ 0 → -1(我们将在后面修改)
- 由于学习是在线的,我们不能对 for 循环操作进行矢量化
- λ是学习率
- 第一个权重始终为零
现在,对于使用模型的预测,我们使用“离线”方案,也就是说,我们不再有更新方法。因此,我们的测试简单地由下式给出:

由于这个方法是离线的,我们可以将 for 循环转换成一个有效的矢量化实现
感知器的一个明显的局限性是,点积的线性使其无法捕捉数据中的非线性相关性。
幸运的是,内核方法可以用来纠正这一点。
内核方法的修订
在数学中,核是一种允许你从线性空间映射到复杂度为 O(n) 的非线性空间的函数,而不是非线性空间所需的复杂度。
假设我们有一个包含 3 个特征的数据点,我们希望将其映射到一个非线性空间中:

如果我们取 x 自身的点积,我们就得到一个线性映射:

不要被方块弄糊涂了。二次项确实是“非线性”的,然而这里我们所说的线性是指关于特征的线性,也就是说,我们是否有特征的交叉相互作用?在上述情况下,答案是否定的。
然而,如果我们平方它,我们得到下面的非线性映射:

我们的非线性项来自 x1 和 x2、x1 和 x3 以及 x2 和 x3 的相互作用。
这是强大的,因为:a)我们现在已经捕获了非线性项,b)我们只需要复杂的点积运算。
如果我们想在不使用核函数的情况下进行这个映射,那么我们的复杂度将为 O(N) ,其中 N=6,因为我们有 6 个新的特征(映射的大小)。
所以形式上,我们把内核写成:

这只是一种类型的核,多项式核。
当使用内核方法时,关键的区别在于测试模型。代替符号(w_star dot x_test) 用于预测,我们有:

其中α是与每个训练点相关联的权重(在训练期间确定),而 K(x_test,x_j) 是测试点和训练点 x_j 之间的核。
注意:内核是机器学习的一个活跃领域,它随处可见。如果你对它不熟悉,我强烈建议你深入研究一下,了解一下执行线性回归的对偶和原始形式(Tibshirani 的统计学习元素是一个很好的参考)
核感知器算法
内核感知器训练算法如下所示:

更多详情见此链接
和测试:

推广到 k 类
一般来说,我发现有两种方法可以概括二元分类器,它们是:
- One vs. All(或 OvA): 这个方法创建了 k 个分类器,每个分类器都被训练来挑选一个类,而不是其余的类。这意味着对于每个类 c_i ,创建一个合成训练集 y` ,其中:

在预测过程中,我们可以让经过训练的模型“竞争”,看看哪个类最适合给定的 X_test 。因此,预测由下式给出:

这里 H_i 代表正在讨论的分类器。我们找到对我们的输入给出最大预测的分类器,然后找到它出现的索引。该索引对应于我们的数据所属的类别!
重要的是,要注意这种方法要求分类输出是连续的。因此,当使用感知器算法时,我们不能在预测步骤应用符号函数。
对于每个历元,该算法的复杂度为 O(Mk) 。其中 M(m,n) 是所使用的二元分类器的复杂度。
- 一对一(或 OvO): 这种方法创建 k(k-1)/2 个分类器,每个分类器在每一个类的组合上被训练。与上面类似,创建一个合成数据集。然而,这里我们在应用转换之前,在每一步过滤数据,只包括属于 c_i 和 c_j 的类。
为了更好地理解这一点,让我们考虑一个 10 级的例子(数字 0-9,如在 MNIST)。我们将有以下组合:(0,1),(0,2),(0,3),……(1,2),(1,3)……(8,9),因此有以下分类器:

记住每个 H_ij 是一个分类器。这里 H 代表我们必须训练的分类器集合。注意,我们不需要为排列进行训练,例如(0,1)和(1,0),因为从(0,1)分类的输出,我们可以推导出类 0 和类 1 的预测!
所有这些都是在过滤的数据集上训练的,y _ filtered = y[(y = = I)|(y = = j)]。变量的变换是这样进行的:

最终分类输出由下式给出:

与“一个对所有”不同,分类器之间并没有太多的“竞争”,而是对分类输出进行“投票”。有了这个公式,我们可以将符号函数应用于感知器的输出,因为我们关心计数。或者,如果我们决定不应用符号,那么我们将取一个加权和,其中感知机输出的值是模型对我们预测的数字的信任度(这里它的行为更像一个‘竞争’)。
这个模型的复杂性更难确定,因为 m 对于每个组合都是可变的(记住我们使用的是过滤数据集!).假设类是均匀分布的情况,每次迭代的数据集大小 m_ij = 2m/k。因此,复杂度是 O(Mk(k-1)/2) 其中 M=M(2m/k,n) 是二元分类器的复杂度。
算法在我们的数据上会有什么不同?
为了更直观地了解每种算法的工作原理,让我们检查一下数据。
MNIST 数据集是从 0 到 9 的手写数字的集合。每个图像的大小为 16x16,但为了建模,它们被展平。因此,每个图像由一个 256 长的向量表示。数据集包含大约 9000 张这样的图片。
为了更好地理解 256 个维度代表了什么,我用 TSNE 在 2D 笛卡尔坐标轴上绘制了这些图像。

图 1:MNIST 数据集的 TSNE 嵌入。请注意,不同类之间有很多噪声。每个簇代表一个数字(见图例)。如果你不熟悉 TSNE 的做法,可以把它看作是一种将数据“压缩”到更小维度的方法。在这种情况下,每个 256 像素的图像被“压缩”成二维,这样我们就可以在笛卡尔(x,y)平面上绘制它。(注意添加 MNIST 图片)
对于一对所有分类器,每个分类器学习预测一个数字对其余数字。例如,我们的第一个分类器将尝试预测给定的输入是否为 0。肯定的分类是“+1”,例如模型认为输入图像是 0,而不成功的分类是“-1”,例如模型认为图像不是 0。下图显示了分类器对 MNIST 数据集的作用。

图 2:对于每一个类,该模型学习一个决策边界来将它与其余的类分开
在一对一的情况下,我们正在学习使用在较小数据集上训练的 k(k-1)/2 个分类器来预测每个类别组合。下面提供了一个关于 MNIST 数据集的示例。

图 3:对于每一个类的组合,模型学习一个决策边界来将它们彼此分开。最终输出是所有分类器的“投票”。在这种情况下,k=10,所以我们有(10)(9)/2 = 45 个分类器!然而,仅仅因为这听起来很多并不意味着它一定会更慢。回想一下复杂性。
理论性能比较
以上两种方法都是将二进制分类问题推广到 k 类的合理方法。然而,它们都可能遭受所做预测的不确定性。在一对一分类器和加权一对一分类器中,在应用 argmax 操作之前,输出权重的值可能非常相似。在非加权一对一分类器中,两个类别可以具有相同的投票数。
总的来说,我认为 OvO 模型比 OvA 模型表现得更好,因为后者存在阶级不平衡的问题。考虑到数据集中的 k 类同样频繁。这意味着在那次培训中,你的班级比例将是 1:k1,分别代表y′= 1 和y′= 1。这对于大值的 k 来说是非常不平衡的。此外,由于所有其他类都被集中在一起,因此将一个类与所有类分开的边界可能会很复杂,也更难预测。另一方面,一对一模型在数据集很小且噪声很大的情况下可能不太有效。对于上述的相等类别分布,一对一模型具有实际训练集的 2/ k 的有效训练规模。如果成对的类非常嘈杂,并且由于数据集很小,由于类的高度重叠,决策边界将很难区分。在这种情况下,一个对所有分类器更多地强调训练样本的其余部分,而不是噪声。
然而,我的实验结果只是部分证实了上述观点,我有兴趣进一步探索这个问题。
优化
如果设计不当,感知器和广义分类器的计算效率会非常低。仅对于感知器,你循环通过 N 个时期的数据,每个时期通过 m 个数据点,计算总计为 m 和内核 K(xi,xj) (这需要 n 个运算,因为它是基于点积的)。在最坏的情况下,复杂度是 O(Nm n) ,如果不使用矢量化,操作会非常慢。盲目推广感知器只会让情况变得更糟。对于一个对所有分类器,一个简单的实现将重新初始化感知器 k 次,导致总体复杂度为 O(Nm nk) 。对于一对一分类器,假设类分布相等,我们得到复杂度为 O(2Nm n(k-1)/k) 。
因此,我们仔细研究了这个问题,以确保 a)尽可能多地使用矢量化,b)尽可能少地重复概括。
感知器优化
首先,注意到内核可以通过创建一个 Gram 矩阵来矢量化(例如,一个矩阵,其中每个单元是 xi 点 xj 的函数)。因此,代替在训练循环期间为每个数据点计算 K(xi,xj) ,我们可以预先创建矩阵 K=(K(xi,xj) : i,j = 1 … m) 。这样,总和:

计算方法如下:

其中 Ki 是指克矩阵的第 i 行。当然,优势在于内核计算现在相对于数据集的循环是线性的,并且它一次使用 PyTorch 的后端向量操作。这意味着复杂度现在是O(m ^ n+Nmn)而不是O(Nm ^ n),其中第一项 m ^ n 是完全向量化的运算。
对于多项式核,创建 Gram 矩阵非常简单,因为它在表达式中使用了点积。
对于多项式核,这相当简单,因为格拉姆矩阵由下式给出:

我们可以将此改写为:

记住一个矩阵(m,n)乘以它的转置将得到一个矩阵(m,m ),其中每个单元是相关向量的点积!
对于这个项目,高斯核也被使用。它由下式给出:

这更难以向量化,因为范数有一个减法运算。之前在我的 numpy 实现中,我尝试将 X 传播到第三维,然后减去 2D 版本,最后使用求和操作将第三维折叠回来。但是,这种方法不适合大型数组,因为它占用太多内存。也是扑朔迷离,牵扯其中。
对于 PyTorch 实现,我做了更多的思考,我意识到范数运算可以写成点积的和:

因此,高斯核可以由下式给出:

两个内核都被有意地写成这样,以确保操作可以应用于具有形状 (m1,n) 和 (m2,n) 的任意两个矩阵 X1 和 X2 ,以确保在创建预测 Gram 矩阵时,它们可以在预测期间被重用。
一对所有优化
这里有两个主要的认识。首先,不需要每次都创建 Gram 矩阵,因为输入 X 总是相同的。因此,不是复杂度为 O(k(m n+Nmn)) ,而是复杂度为 O(m n+Nmnk) 。第二,在预测而不是重复期间

对于每个分类器,保存的字母被连接起来并用于执行单个操作:

在应用 argmax 函数之前,给出数据的正确格式。
一对一优化
理论上,因为内核方法使用对偶公式,所以在过滤的数据上训练 k(k-1)/2 分类器应该比在未过滤的数据上训练 k 分类器表现得更好(就像一个对所有分类器一样),因为我们在小得多的 m 上训练。但是,因为 k 分类器只使用单个 Gram 矩阵,所以在我们的实验中它们更快。标准一对一实施无法重复使用 Gram 矩阵,因为基于 and ci 和 cj 对 X 进行过滤。
我确实尝试过改进这一点,通过实现一个积极矢量化的一对一算法的替代公式。这就对感知器做了一点小小的修改,所以我们不使用 if 语句来更新权重,而是这样做:

如果 yt 在 [-1,1] 中,那么 alpha 按预期更新。然而,如果我们允许 yt 包含 0,那么当 yt=0 时,不管分类输出如何,权重将保持不变。因此,模型忽略了 yt=0 的数据点。
我们进行以下转换,而不是上述转换:

这允许我们在训练时输入未经过滤的 X 。因此,尽管我们在全尺寸数据集 m 上训练 k(k-1)/2 个分类器,但我们不必重新初始化 Gram 矩阵 k(k-1)/2 次,我在实验中使用了单个时期,因此时间复杂度中的主要项是 Gram 矩阵的时间复杂度,因此这种实现导致了稍微更快的结果。然而,一般来说,情况可能不是这样,速度的瓶颈来自训练循环,而不是来自创建 Gram 矩阵,这是一种矢量化操作。对于非常大的 k 值,这可能很有用。
实施策略
基于以上所述,我们可以通过多种方式为不同的任务设计感知器,总结如下:
- 基于二进制数据训练的内核感知器,具有高斯或多项式内核
- 使用一对一公式在 k 类上训练的广义感知器
- 使用一对一公式在 k 类上训练的广义感知器
- 以上既可以用加权票,也可以用等额票
- 它还可以选择使用积极的矢量化
为了在不编写高度特定的代码的情况下促进所有上述功能,模型是使用 Python 类编写的,并遵循带有fit和predict方法的sklearn API 风格。这实现了“即插即用”功能,使得执行子任务更加容易。
感知器初始化取入epsilon、epochs、、kernel和**kernel_kw。ε是添加的术语,因此:

这是因为符号(0) 不明确,所以epsilon允许用户在以下两者之间选择:

或者

kernel是为以下输入实现特定内核(例如多项式或高斯)的函数:
- 矩阵输入:(m1,n)和(m2,n) →输出 Gram 矩阵形状:(m1,m2)
- 向量输入:(n,1)和(n,)→输出核形状:(1,1)
- 矩阵和向量输入:(m1,n)和(n,)→输出 Gram 矩阵形状:(m1,1)
并且**kernel_kw是与核相关联的任何参数,因此d用于多项式核, c 用于高斯核。这种编写函数的方式允许使用多个参数。
fit方法是标准的,不需要解释,除了权重被初始化并保存为类变量。predict方法也以标准方式工作,但是有两个额外的可选参数,weights=None和return_labels=True。前者在 OneVsAll 和 OneVsOne 期间使用,在预测步骤使用来自每个分类器的训练权重。默认情况下,它是 None,这意味着使用模型中包含的权重。return_labels 确定感知器的输出是否是预测的强度

或者实际的预测

这使我们能够很容易地指定一个对所有或一个对一个的不同行为。OneVsAll 和 OneVsOne 的类遵循类似的结构,并被编写为与任何使用操作 M 点 w ( M 是矩阵, w 是权重)来确定分类输出的二元分类器一起工作。这意味着这可以很好地用于原始公式,只要这些模型遵循与上述感知器算法相同的风格。
实验
多项式与高斯核
多项式核和高斯核训练和测试精度使用一个对所有实现进行了比较。多项式核的范围是 d=1…7,高斯核的范围是 c = 0.01…0.5。结果以及 d 和 c 的最佳值(分别为 d_star 和 c_star)如下表所示。

表 1:具有多项式内核的 OvA 感知器的训练和测试误差

表 2:具有多项式内核的 OvA 感知器的最佳 d_star,以及相应的测试误差

表 3:具有高斯核的 OvA 感知器的训练和测试误差

表 4:具有高斯核的 OvA 感知器的最佳 c_star,以及相应的测试误差
高斯核的性能优于多项式核。这可能是因为高斯核可以捕获比多项式核所能捕获的更复杂的决策边界。然而,这是有代价的。高斯核倾向于更大程度的过度拟合(比较表 1 和表 3 中从训练到测试的误差增加)。这表明高斯核记住了训练数据中的“噪声”,而不是学习潜在的趋势。
卵子对卵子

表 5:具有多项式内核的 OvO 感知器的训练和测试误差

表 6:具有多项式内核的 OvO 感知器的最佳 d_star,以及相应的测试误差
比较 OvO(表 5)模型与 OvA 模型(表 1)的训练/测试误差,我们可以看到,对于 d=1,OvO 的性能稍好。这可能是因为正在学习的决策曲面是线性的,但是 OvA 实现显然更适合非线性曲面(参见图 1 并与图 2 进行比较)。随着模型复杂度的增加,我们发现 OvA 比 OvO 表现得更好。
难以分类的图像

图 3:用 1 到 7 度的 OvA 感知器正确分类困难的图像
绘制了 OvA 多项式核模型发现难以区分的一些示例。上面的强度项表示应用符号函数之前的分类输出。不出所料,这些都是负面的。结果并不特别令人惊讶。对于第一个图像,虽然预测正确,但它可能很容易是 6。第二幅图像可能是阿德画得不好,但无论哪种方式,每个像素的激活都是暗淡的,形状是高度扭曲和单薄的。下一张图片看起来很像 4,如果人类平均正确地将其分类,我会感到惊讶。下一个图像也是稀疏的。最终图像具有很强的曲率,可以被模型解释为 2。
混淆矩阵
最后,为每个测试模型绘制混淆矩阵。这是为了让读者更好地理解模型在不同图像下的表现。

图 4:在表 4 所示的最佳 c_star 上训练的 OvA 高斯核的混淆矩阵

图 5:表 2 中在最佳 d_star 上训练的 OvA 多项式核的混淆矩阵

图 6:在表 6 所示的最佳 d_star 上训练的 OvO 多项式核的混淆矩阵
结束语
陷阱
- 如果使用 PyTorch,确保你实例化你知道你的数据类型,我得到的一个常见错误是转换 double 为 float!
- 确保你对你的张量运算有信心,特别是当你试图使它一般化的时候
- 从
itertools返回一个只能运行一次的生成器对象。所以如果你想保存它,你必须把它转换成另一个保存在内存中的对象(如list)。使用torch.combinations等替代方法是可能的,但这需要一个torch.Tensor作为类。 - 非常小心一对一分类器中的逻辑要求。训练 k(k-1)/2 模型很容易,但是当你试图把它转换成 k 类的预测时,问题就变得不那么简单了。
关键要点
- 核心感知器使用核心方法,通过经典感知器算法实现非线性决策表面的学习
- 由于在线学习训练,如果没有正确初始化,感知器是高度计算密集型的。优化可以通过向量化 Gram 矩阵和编写广义核函数来实现
- 任何二元分类器都可以使用 OvA 或 OvO 方案来推广
- OvA 创建 k 个分类器,为每个类别区分 1 个类别和其余类别。这些方法可能会受到类不平衡的影响,但是当数据集很小且有噪声时,它们可能会工作得更好。它们将数据置于直接竞争中,并且当分类输出未被加权时无法工作。
- OvO 创建 k(k-1)/2 个分类器,用于区分类别(ci,cj)的唯一组合。他们的优势是允许加权和平等的投票方案。
再现性
如果你想重现结果,请查看 GitHub 的页面。如果您对它的任何方面有任何问题,请联系我!
如果你真的喜欢我的工作并愿意支持我,请考虑使用我的推荐链接来获得一个媒体订阅。
除非另有说明,所有图片均由作者创作。
使用 Azure Data Factory 实现端到端的机器学习工作流
从收集数据到发送结果,ADF 在一个屏幕上构建了正确的 MLOps 生命周期
我在 3 年前实现机器学习的印象是,用 Python 构建一个模型,并将项目部署到自动化 CI/CD 管道中。虽然它解决了执行预测的基本标准,但它永远也不能被称为端到端工作流,因为数据存储和报告是该工作流中缺少的两个重要组件,必须分开处理。在这篇文章中,我将走过一个完整的机器学习操作周期,并展示如何使用 Azure Data Factory (ADF)建立每一步。是的,这是可能的,容易的,非常可靠的。另外,它还会自动设置您接收整个流程中出现的任何类型的数据异常的警报,因此您不必担心手动监控工作流程。

现在是时候坐下来放松一下,而 ADF 做所有的重活|图片由约书亚阿拉贡在 Unsplash 上
你如何定义一个端到端(E2E) ML 工作流程?
我对完整工作流程的定义是,从数据开始流入系统开始,到最终报告准备好呈现给目标受众时结束。该流程需要数据收集、存储、后端工程、中间件和前端工程。这是产品准备发货的时候。我们将通过 Azure Data Factory 实现整个工作流程。
- 数据工程:数据采集、准备、预处理、管理存储。
- ML 模型工程:模型建立、训练、测试、提炼、发布。
- 代码工程:将 ML 模型集成到最终产品后端(服务结构 API 等)。)
- 报告:数据可视化和讲故事。

机器学习周期从开始到结束的完美呈现| 图片来源:MLOps (以知识共享署名 4.0 国际公共许可发布,并与署名(" INNOQ ")一起使用)
什么是 Azure 机器学习?
机器学习是一种数据科学技术,属于更大的人工智能范畴,允许计算机使用历史数据负载来预测未来的行为、结果和趋势。通过使用机器学习或人工智能,计算机可以在没有显式编程的情况下学习执行任务。来自机器学习的预测或预言可以使应用程序和设备更加智能。例如,当你在网上购物时,机器学习会根据你在过去六个月里购买的东西,帮助推荐你可能想要的其他产品。或者当你的信用卡被刷时,机器学习会将交易与交易数据库进行比较,并帮助检测欺诈。
Azure Machine Learning 是微软 Azure 提供的一项服务,可用于各种机器学习任务,从经典的 ML 到深度、有监督、无监督和强化学习。无论是使用 Python/R SDK 编写代码,还是使用 ML Studio 实现低代码/无代码模型,都可以在 Azure Machine Learning Workspace 中构建、训练和跟踪机器学习和深度学习模型。
Azure 允许你在本地机器上开始训练,然后扩展到云端。该服务还与流行的深度学习和强化开源工具互操作,如 PyTorch、TensorFlow、sci-kit-learn 和 Ray RLlib。
逐步完成工作流程
在下面详述的五个步骤中,我们将执行所有必需的任务,从数据收集、处理、建模、培训到为客户构建预测分析报告。在我们开始深入细节之前,我在下面的图表中用简短的描述表示了所有的步骤。

完整机器学习系统的流程图|图片作者
1.Azure 函数为管道创建触发器
我们通过定义一个函数来开始我们的工作流,该函数将评估管道何时应该开始运行。这通常被称为“触发器”。这是一个启动工作流的事件。 每个数据管道都需要数据来开始工作。 这种数据可以以结构化流的形式(。ss 文件)、文本文件(。csv,。tsv 等)或者它可以是来自 Azure 事件中心的火花流。在我们的场景中,假设我们有结构化的数据流,每 6-12 个小时被转储到一个 Cosmos 位置(COSMOS 是 Azure 的 NoSQL 商店)。为了通知机器学习服务上游文件可用并且新的原始数据存在以供运行,Azure 函数每小时运行一次以检查是否有新文件到达。只有当这个 Azure 函数返回 True 并指定存在新数据时,工作流的剩余部分才会执行。

第一步。添加 Azure 函数来检查我们使用的原始数据流的可用性。
a.Azure Function 每小时运行一次,检查是否有新数据可用。如果有新数据可用,它返回一个真布尔值,进而触发工作流的剩余部分。如果没有新文件,它返回 False,什么也不做。它会在一小时后再次运行,以执行相同的检查。
2.Azure 数据块用于数据预处理和存储到数据湖
ADF 支持所有现代数据结构,包括通过数据湖和数据仓库等存储服务输入的结构化和非结构化数据流。但是处理数据的最好方法是将 ADF 与 Azure Databricks 笔记本集成。这是一个典型的 Python 环境,运行在 Azure**中创建的工作空间之上,可以执行 Python 有能力运行的每一个机器学习和数据处理活动。
一旦 Azure 函数发出新数据可供处理的信号,数据块集群被激活,笔记本开始运行。我们使用 Python 导入数据,执行初始清理、排序和其他预处理工作,如输入 null 和无效输入,拒绝不会用于机器学习模型的数据。准备好发送到机器学习管道的经过处理的干净数据被安全地放入 ADLS (Azure 数据湖存储)Gen2 位置。数据湖是满足临时和永久存储需求的快速存储选项,可以由 ML 活动直接访问。

第二步。构建数据处理 Python 笔记本,该笔记本可清理原始数据、运行预处理脚本并将输出存储在 ADL 位置|按作者分类的图像中
a.一旦 Azure 函数返回 True,data bricks Python 笔记本开始收集新数据。
b .该数据由笔记本进行清理和处理,并为 ML 模型做好准备。然后,它被推送到数据湖存储服务,ML 活动可以从那里获取它并运行模型。
**创建 Databricks 工作空间以及使用该工作空间使用 Python 执行数据处理的详细步骤在本教程中进行了说明— 使用 Databricks 笔记本转换数据。
3.使用 Azure 机器学习从存储的数据中构建和训练模型
我们现在到达了机器学习工作流程的核心——奇迹发生的地方!Azure 机器学习提供了两种构建 ML 管道的方式。
您可以选择使用 Python 或 R SDK 来构建、训练和测试 ML 模型,或者使用 Azure ML Studio 来创建无代码的拖放管道
为了从头开始运行并遵循为您的 ML 模型创建训练和测试模型的步骤,下面的存储库中显示了一个详细的示例。
https://github.com/MicrosoftDocs/pipelines-azureml
无论您选择哪种方法,ADF 都会公正地对待管道。管道从 ADL 存储帐户读取数据,并对新数据运行其训练和预测脚本,并在每次运行时刷新模型,以微调已训练的算法。这个机器学习管道的输出是一个结构化的数据集,作为日常输出文件存储在 Azure Blob 存储中。

第三步。从步骤 2 ADL 位置读取数据,并在其上运行机器学习模型|作者图片
a.连接到 ADL 存储帐户以获取已处理的数据。
b .对数据运行 ML 模型。c .将输出和预测指标发送到 Azure Blob 存储。
**使用机器学习管道的替代方法是直接使用 Databricks 环境实现 MLFlow。这个实现的详细教程可以在这里找到——用 MLflow 跟踪 Azure Databricks ML 实验
4.将最终输出推入 Azure Blob 存储

第四步。将基于架构的结构化输出推送到 Azure Blob Storage | Image by Author
Azure Blob Storage 是一项基于云的服务,可帮助您为分析需求创建数据湖,并提供存储来构建强大的云原生和移动应用。存储是可扩展的,定价与使用的存储量成反比。Blobs 是通过使用基于 SSD 的存储架构从头开始构建的,建议用于无服务器应用程序,如 Azure Functions。我使用 Blob 存储的主要原因是它与 Power BI 无缝连接的能力。完整的执行教程可以在这里看到。
a.将分析的数据安全地存储在专用 blobs 中。
b .连接 Power BI 检索数据,进行数据可视化。
5.从 Power BI 访问 Blob 存储以构建报告仪表板
留在微软 Azure 生态系统中的最大好处是在同一个堆栈中可以使用 Power BI。Power BI 是一个非常强大的工具,可以用来构建报告和呈现从模型中提取的洞察力。因此,我个人通过在每次 ADF 管道完成运行时更新 Power BI 仪表板来结束我的工作流。它提供了每天在给定时间更新数据的选项,因此所有洞察、表格、指标和可视化都是最新的,并与数据处理工作流保持同步。

第五步。连接您的 Azure Blob 存储以按作者检索 Power BI | Image 中的数据
“讲故事”是任何分析过程中最重要的部分。
工作流的客户或消费者永远不会对将预测带到桌面上的代码感兴趣。他们只会对提交产品需求时所提问题的答案感兴趣。因此,在我的字典中,只有在可交付成果在手之后,工作流才被称为完成。这里的结果是显示机器学习模型预测了什么和它每天预测了什么,指标如何改进,以及需要哪些增强的报告。这是机器学习赋予我们执行能力的决策科学艺术。
a.将 Power BI 与 Azure Blob 存储连接以获取数据。
b .设置 Power BI 每天刷新,所以所有数据都是更新的。
警报、监控和异常检测
每个 DevOps 或 MLOps 工作流都有一项非常繁琐但又极其艰巨的任务,那就是警报和监控。需要这些监视器来检查数据,避免数据过时,或者在分析的任何步骤中发现任何类型的异常或意外结果时得到通知。Azure Data Factory (ADF)方便地允许我们在 ADF 监控仪表板上设置几个警报。
可以设置的警报示例:
- 从上游接收的数据陈旧。
- 管道故障或执行时间超过给定阈值。
- 存储在数据湖(ADLS)中的结果与过去的数据有很大不同。
- 从上游接收或由工作流生成的文件大小异常大或异常小。
可以设置警报,以便在检测到异常或故障时通知电子邮件组别名、安全组或在 DevOps 门户中创建事件/错误。
一个端到端的 MLOps 存储库托管在下面:
https://github.com/microsoft/MLOps
结论
构建机器学习工作流有多种方式,几乎每个云提供商都有自己的实现方式。对我来说,使用 Azure Data Factory (ADF)已经被证明是最简单的方法,具有最少的设计和架构需求。从收集数据到报告见解,一切都可以在一个地方完成,并通过触发器自动化。最棒的是,ADF 内置了警报、监控和异常检测功能,因此无需任何人工干预。本教程的目的是说明使用 ADF 的 ML 管道的工作流程。下面提到的所有参考资料都可以用来更深入地研究代码的每个步骤。

完整的处理流程|作者图片
关于我
我是一名数据工程师和人工智能研究员,目前在微软 Xbox game analytics 工作,我在日常工作中实现了类似的管道,以分析游戏收购、使用和健康情况。除了我的专业工作,我还在研究如何实施人工智能来平衡世界各地受多年来逐渐气候变化影响的地区的经济。如果您有任何想要合作的讨论、问题或项目,请随时通过 Twitter 或 LinkedIn 与我联系。
[参考文献]
- 【https://ml-ops.org/content/end-to-end-ml-workflow
- https://docs . Microsoft . com/en-us/azure/machine-learning/how-to-data-ingest-ADF
- https://docs . Microsoft . com/en-us/azure/machine-learning/how-to-use-ml flow-azure-data bricks
- https://www . blue granite . com/blog/monitoring-azure-data-factory-v2-using-power-bi
- https://docs . Microsoft . com/en-us/azure/data-factory/常见问题解答
- https://www . sqlshack . com/how-to-access-data-from-azure-blob-storage-using-power-bi/
在 Python 3+上从头开始实现反向传播
让我们看看理论和实践是不是一回事。
在的最后一个故事中,我们从头开始推导了所有必要的反向传播方程。我们还介绍了所用的符号,并掌握了算法的工作原理。在这个故事中,我们将着重于用 python 实现这个算法。
让我们首先为我们的神经网络提供一些结构

我们将让属性structure成为一个列表,它包含神经网络的每一层中神经元的数量。所以如果我们做model = Network([784, 30, 10]),那么我们的模型有三层。具有 784 个神经元的输入层,具有 30 个神经元的隐藏层,以及具有 10 个神经元的输出层。请注意,当涉及到偏差和权重等参数时,我们不会考虑输入层。正如我们在乐谱中讨论的。
下一个重要属性是Bₙ,这将是一个列表,包含整个网络中的所有偏置向量(bᴸ),逐层排序。让我们停下来谈谈这里的符号。我们已经抛弃了索引符号,取而代之的是逐层(矢量)符号;Bₙ只有下标表示它拥有网络中的所有偏置向量。
如你所见Bₙ是通过从第一个隐藏层开始迭代所有层,并创建一个长度等于每个层神经元数量的随机 NumPy 数组(偏置向量)构建的。所以本质上,对于我们刚刚讨论的网络,列表Bₙ将涉及分别为(30,1)和(10,1)的两个 NumPy 数组(偏置向量)。
类似于前面的属性,我们也有Wₙ,正如你所猜测的,它将是一个列表,包含每一层的权重矩阵(Wᴸ).也就是说,对于我们的设置,有两个维数为(784,30)和(30,10)的 NumPy 数组。至于它是如何构造的,zip()使得同时在多个集合上循环成为可能;这里使用它来循环两个版本的structure列表,这样我们总是有当前层和下一层中神经元的数量,并且可以使用它们来初始化正确维度的随机权重矩阵。关于随机性的最后一点是,我们使用 NumPy 的randn()函数,它从标准正态分布中提取值。这对于防止由于像 Sigmoid 这样的一些激活引起的消失梯度问题特别有用,因为我们的大多数值将在 0 附近,这是 Sigmoid 具有强梯度的地方。
既然我们已经定义了网络的结构,我们就可以开始处理反向传播了。让我们从提出我们可能需要的所有方程开始。

当我们在实现中使用它们时,我们将引用它们。
现在让我们直接行动吧。

整个功能。

初始化
在这里,我们从数据集中的一个例子开始。我们的目标是使用它来查找∂j/∂bᴸ∂j/∂wᴸ的每一层,以便最小化给定示例的成本。因此,我们首先初始化两个变量მJⳆმWₙₛ和მJⳆმBₙₛ,它们看起来与Wₙ和Bₙ相同,但都是零。因此,一旦我们完成,如果Wₙ[L]具有与某层 l 相关联的权重矩阵,那么მJⳆმWₙₛ[L]将是该层的∂J/∂Wᴸ。这同样适用于Bₙ[L]和მJⳆმBₙₛ。对两者来说,下标“s”只是提醒,这些是偏导数,只因为一个例子。
如果你还记得的话,这个算法包括两遍。首先,通过网络向前传递,其中它使用前两个方程来使用当前权重和偏差找到所有层的 a ᴸ和 zᴸ向量,然后再向后传递,其中我们从δᴴ开始,使用之前找到的 zᴸ's 和 a ᴸ's 来找到每层的δᴸ,从而找到∂J/∂Wᴸ和∂J/∂bᴸ。

向前传球
正向传递在函数中的第一个 for 循环中传递。我们首先创建两个空列表Zₙ和Aₙ,它们最终将包括我们网络中的所有 zᴸ's 和ᴸ's。然后我们开始逐层循环,同时保持当前层的偏移向量和权重矩阵(NumPy 数组)。
对于每一层,我们使用权重矩阵和偏置向量以及来自前一层的激活(或 x 如果是第一层),根据前两个等式(在上图中)找到 a ᴸ和 zᴸ。)
如果还不清楚,行z = W.T @ a + b if Zₙ else W.T @ x + b使用 NumPy 的.T进行转置,使用 NumPy 的@乘以 NumPy 数组(矩阵乘积),如果Zₙ不为真,只使用 else 旁边右边的表达式;这意味着它是空的,因此它必须是第一次迭代(第一层)。
下一行简单地在 zᴸ上应用激活来查找一个 ᴸ.(第二个等式。)在这个实现中,我们使用 sigmoid 函数作为激活;因此,我们也在类外定义了函数

h(z) = σ(z)像我们预期的那样,按元素方式处理向量。

我们以后会用这个。
最后两行只是负责将为当前层计算的 zᴸ和ᴸ添加到我们之前介绍的两个列表中。
所以这里的概要是,一旦这个循环完成,我们就把我们网络的所有 zᴸ's 和一个ᴸ's 逐层存储在Zₙ和Aₙ中。

向后传球
现在,就向后传递而言,我们从计算最后一层的索引开始。因为我们从第一个隐藏层 0 开始计数,所以这个数字将等于我们网络的总层数减去 2(或者我们在前面的故事中提到的隐藏层数)。)一旦我们准备好了,我们就用它来在我们网络中循环。带有range(H, -1, -1)的 for 循环意味着我们从 H 开始,每次迭代减去 1(最后一个参数),只要没有达到-1,我们就会继续下去。(因此,最后一次迭代将涉及 L = 0,这是第一个隐藏层。)至于我们在 for 循环体中做了什么,我们只是简单地使用了最后三个等式,非常简单。

注意,在代码中,我们使用了*来表示虚线圆。(for 循环中的第一行。)这是因为当乘以 NumPy 数组时,默认情况下通过*的乘法是元素方式的。除此之外,一切都应该说得通。一旦 for 循环完成,意味着它已经遍历了 h 层到 0 层,并更新了每个层的∂J/∂Bᴸ & ∂J/∂Wᴸ,是时候简单地

今天到此为止。
现在剩下的就是实现一个函数来处理我们的小批量数据集,为每个内部观察调用backprop(x,y),并使用梯度下降更新我们网络的权重。也就是说,一些简单的事情

梯度下降
这就是了

整个功能

初始化
类似于我们在backprop(x,y)函数中所做的,我们首先初始化两个变量მJⳆმWₙ和მJⳆმBₙ,它们看起来与Wₙ和Bₙ相同,但都是零。这一次我们去掉了 s 下标,因为这些将累积所有的მJⳆმWₙₛ和მJⳆმBₙₛ由于小批量中的例子,我们将准备使用它们更新权重Wₙ和Bₙ。

累积梯度
在 for 循环中,我们检查小批量中的每个示例,使用backprop(x, y)函数找到因其产生的მJⳆმBₙₛ和მJⳆმWₙₛ,然后通过列表理解将结果逐层累积到მJⳆმBₙ和მJⳆმWₙ中。
这两行可以分别替换为მJⳆმBₙ = np.add(მJⳆმBₙ ,მJⳆმBₙₛ)和მJⳆმWₙ = np.add(მJⳆმWₙ + მJⳆმWₙₛ),但是这将导致警告,除非您在指定dtype=object的同时将列表转换为 NumPy 数组。最后重要的是,一旦这个 for 循环结束,მJⳆმBₙ和მJⳆმWₙ已经在所有的მJⳆმBₙₛ和მJⳆმWₙₛ中累加,由于小批量中的所有例子,这意味着下面公式中的两个和已经准备好了。

梯度下降

使用这些公式
在最后两行,我们用上面的两个公式更新了每一层的 bᴸ和 Wᴸ。您应该会多次调用该函数,这取决于历元数(整个数据集的迭代次数)和您的小批量大小。如果您的数据集被分成 40 个小批,并且您的历元计数是 20,那么您将调用 20x40=800 次。负责的功能如下所示

训练网络
此时,您可以尝试初始化网络,并向其输入一些随机的小批量数据,看看这会如何改变权重和偏差。
最后,您可能想知道我使用的 Python 变量名是怎么回事。事实证明,只要你的角色在世界范围内的某种语言的字母表/字符集中,Python 就不会有问题。例如,მJⳆმWₙ中的ⳇ和მ都是随机字母表中的字母。出于某种原因,unicode 下标对 Python 来说也不成问题。这是我在给变量名时利用的两个事实,以避免做变量名决定,并使它看起来尽可能数学化。
如果你喜欢阅读,并希望看到更多这样的故事,那么请考虑给帖子一些掌声,并跟我来。下次见,再见。
学习资源:
尼尔森迈克尔。神经网络和深度学习。2019,CHP。1,2.
在 Go 中实现命令行选项
了解如何编写接受 Go 中命令行标志和选项的控制台应用程序

虽然今天人们使用的大多数应用程序都是基于 GUI 的,但控制台应用程序仍然非常活跃,而且看起来不会很快消失。控制台应用程序更简单,开发成本更低,更健壮,是自动化不可或缺的工具。也许唯一让用户却步的是控制台应用程序并不容易使用——你经常不得不记住许多命令和选项,满屏的文本让许多有网络恐惧症的人感到害怕。

与用户使用鼠标进行交互的基于 GUI 的应用程序不同,控制台应用程序通过由文本字符串组成的命令行选项与用户进行交互。使用命令行选项,用户可以指定应用程序正常工作所需的各种信息。
在本文中,我将向您展示如何构建一个支持各种命令行选项的简单 Go 应用程序。
了解不同类型的命令行选项
谈到命令行选项,您需要了解三种主要类型:
- 命令行参数
- 命令行标志
- 命令行标志参数
理解以上类型的最简单的方法是用例子。如果你是一个 Unix 用户,你应该熟悉ls命令(用于列表)。
要列出一个特定的文件,你使用文件名作为 命令行参数 :

如果你想以 long 格式列出当前目录下的所有文件,你使用-a和-l 命令行标志 :

如果您想以长格式列出一个特定的文件,使用-l标志和一个 命令行标志参数 :

在 Go 中实现命令行参数
现在您已经对各种命令行选项有了清晰的了解。让我们编写一个 Go 程序来实现所有这些功能。
首先,在终端(或命令提示符)中创建一个名为命令行的目录:
$ mkdir ~/commandline
然后,创建一个名为 main.go 的新文件,并保存在命令行目录中。用以下语句填充 main.go 文件:
package mainimport (
"fmt"
"os"
"reflect"
)func main() {
fmt.Println(os.Args)
fmt.Println(os.Args[1:])
for _, arg := range os.Args[1:] {
fmt.Print(arg, " ")
fmt.Println(reflect.TypeOf(arg))
}
}
下面是上述程序的工作原理。要检索命令行参数,可以使用os包。os包中的Args变量以一段字符串的形式返回所有命令行参数:
fmt.Println(os.Args)
结果包括程序的名称,如果您不想包括它,请对字符串切片执行切片:
fmt.Println(os.Args[1:])
您可以遍历切片并打印出每个参数:
for _, arg := range os.Args[1:] {
fmt.Print(arg, " ")
fmt.Println(reflect.TypeOf(arg))
}
要测试上面的程序,首先构建它,然后运行它的可执行文件。在下面的例子中,我用下面的参数运行程序—“hello 100 3.14”:
$ **go build main.go**
$ **./main hello 100 3.14**
[./main hello 100 3.14] // all the arguments incl app name
[hello 100 3.14] // all the arguments excl app name
hello string // first argument
100 string // second argument
3.14 string // third argument; all string types
注意,传入程序的每个参数都表示为一个string类型,从上面显示的输出中可以明显看出。
在 Go 中实现命令行标志
实现命令行标志比实现命令行参数稍微复杂一些。为此,让我们编写一个程序,允许用户多次打印一条消息,并可以选择在打印的字符串之间插入空格。我不想给你看代码,我想先介绍一下我们的程序是如何工作的。只有这样,我们才会看代码。
案例 1
当程序在没有任何标志的情况下运行时,您会打印一条默认消息“Hello,World!”:
$ ./main
Hello, World!
案例 2
-msg标志指定要打印的消息:
$ ./main **-msg Cool!** Cool!
消息“酷!”是标志参数。默认情况下,消息打印一次。
案例 3
您可以在命令行标志后指定一个“=”字符:
$ ./main **-msg=Cool!** Cool!
消息“酷!”是标志参数。默认情况下,消息打印一次。
案例 4
您可以使用-times标志来指示打印消息的次数。默认情况下,所有消息都用空格字符分隔:
$ ./main **-msg Cool! -times 3**
Cool! Cool! Cool!
案例五
-spacing标志指定要重复的消息是否使用空格字符隔开:
$ ./main **-msg Cool! -times 3 -spacing=false**
Cool!Cool!Cool!
请注意,对于 Go 程序,当指定布尔标志参数时,必须使用“=”字符。
案例 6
指定标志时,也可以使用双“-”:
$ ./main **--msg Cool! --times 3 --spacing=false**
Cool!Cool!Cool!
案例 7
如果指定无法识别的命令行标志,将显示一条错误消息:
% ./main **-message "Hello"**
flag provided but not defined: -message
Usage of ./main:
-msg string
Message to print on console (default "Hello, World!")
-spacing
Insert a space between messages (default true)
-times int
Number of times to print message on console (default 1)
案例 8
除了传入标志及其相关参数之外,程序还可以接受非标志命令行参数:
$ ./main **--msg Cool! --times 3 --spacing=false additional message 10 3.14**
Cool!Cool!Cool!
string - additional
string - message
string - 10
string - 3.14
编写程序
我们现在准备编写程序来接受各种标志和参数。首先,使用flag包中的各种函数定义各种标志:
msgPtr := flag.String("msg", "Hello, World!",
"Message to print on console") timesPtr := flag.Int("times", 1,
"Number of times to print message on console") spacingPtr := flag.Bool("spacing", true,
"Insert a space between messages")
例如,对于-msg标志,您使用了flag.String函数。该函数的第一个参数指定标志名称,第二个参数指定标志的默认值,最后一个参数指定该标志的用法说明(向用户显示)。在-msg标志的情况下,您指出该标志接受类型为string的参数。对于-times标志,数据类型为int,而对于-spacing标志,数据类型为bool。这些函数中的每一个都返回一个指向标志值的指针。您将利用这个指针来检索用户传入的标志参数值。
接下来,调用Parse函数将命令行解析成定义的标志:
flag.Parse()
现在,您可以根据用户指定的参数打印消息:
//---print out the message---
for i := 0; i < *timesPtr; i++ {
fmt.Print(*msgPtr)
if *spacingPtr == true {
fmt.Print(" ")
}
}
fmt.Println()
记住参数可以通过指针引用。
最后,您可以打印出所有其他非标志参数:
//---returns all the non-flags arguments---
for _, arg := range flag.Args() {
fmt.Print(arg, " ")
fmt.Println(reflect.TypeOf(arg))
}
整个程序如下所示:
//===main.go===
package mainimport (
"flag"
"fmt"
"os"
"reflect"
)func main() {
//---Define the various flags---
msgPtr := flag.String("msg", "Hello, World!",
"Message to print on console") timesPtr := flag.Int("times", 1,
"Number of times to print message on console") spacingPtr := flag.Bool("spacing", true,
"Insert a space between messages") //---parse the command line into the defined flags---
flag.Parse() //---print out the message---
for i := 0; i < *timesPtr; i++ {
fmt.Print(*msgPtr)
if *spacingPtr == true {
fmt.Print(" ")
}
}
fmt.Println() //---returns all the non-flags arguments---
for _, arg := range flag.Args() {
fmt.Print(arg, " ")
fmt.Println(reflect.TypeOf(arg))
}
}
摘要
控制台应用程序在计算中有很多重要的用途。这些无名英雄在幕后默默工作,让您的服务保持正常运行。希望这篇文章能让您轻松开始编写接受命令行参数的控制台应用程序。
Google sheets 上的 CRM
如何在 Google Apps 脚本上为您的企业实施 CRM 解决方案?
我们大多数人可能都听过数学家 Clive Humby 的名言“数据是新的石油”,一些行业领袖对此表示同意。他们的大多数观点都指出,在现代商业时代,数据已经变得多么重要。
许多 IT 巨头和初创公司正在利用他们的数据科学能力进行更好的预测,改进他们的产品,提供更好的见解,并做出数据驱动的决策。有些人甚至成功地用他们聪明的广告位置惹恼了整个世界(有意幽默)。这些只是一小部分应用,之所以成为可能,完全是因为正确使用了数据。本文重点关注小企业主、制造商和初创企业,以及他们如何建立数据收集和分析基础设施,并帮助他们的业务增长。

弗兰基·查马基在 Unsplash 上拍摄的照片
为了满足中小型企业的数据需求,Google Sheets 是一个有效的选择。Google Sheets 是一个基于云的电子表格应用程序,企业使用它来管理他们的数据。它提供了一个用户友好的界面,强大的功能和一个由 GCP (谷歌云控制台)支持的脚本编辑器工具,你可以在那里编写脚本来自动化流程。
首先,我将描述总体架构/蓝图,然后使用 Google Apps 脚本(Google 应用程序中的编程语言,类似于 JavaScript)在工作表中实现它。
体系结构
为了方便起见,我考虑了并行操作工作流。所有三个操作同时进行。虽然它可能看起来类似于 ER 图,但这只是一个基本的数据流程图,每个块都是一个不同的跟踪器(google sheet ),箭头代表数据流。

作者图片
Source :作为一个数据源,订单的必要参数将在其中填写,可以是一个 web 表单或链接到回应表的 google 表单。用于生成订单/请求/工作等。每一行代表一个唯一的订单,可以通过一个唯一的键(在这种情况下是表单响应 id)来标识。
主管理跟踪器:主订单跟踪器(google sheet)包含来自源表单的所有字段和关于处理订单所需的所有操作的信息。这些行将被同时导出到各个操作跟踪器,只显示与特定操作相关的字段。该表可用于监控正在进行的订单,以跟踪流程的哪一端被卡住。订单完成后,该行条目可从该表中移除,并移至档案表(主数据库)。
运营追踪器:所有的业务都运行在某一组运营上,通常有不同的团队(或个人)处理不同的任务。要成功处理订单,必须完成所有任务。操作跟踪器将监控特定任务的状态,并将相关信息反馈给主管理跟踪器。
主数据库:最后,当所有操作完成,所有相关信息反馈到主跟踪器时,订单处于已实现状态,可以迁移到主数据库。该表作为所有订单的存档,包含来自源表和操作表的所有信息,以分析数据并做出数据驱动的决策。该表可以直接用于任何开源可视化工具来创建仪表板/摘要。
请注意,在上图中,黑色箭头表示流程,蓝色箭头表示来自操作单的反馈。这两个流程都将实现自动化,以减少人工干预并简化工作流程。这是最基本的设计,可以根据业务需求/工作流程添加/编辑元素。
履行
步骤 1:创建一个 google 表单,作为创建订单的源表单
- 创建表单并添加描述订单的必要问题,可以包括客户详细信息,还可以创建客户数据库。
步骤 2:为创建的每个订单分配一个唯一的键
- 现在我们有了创建订单的数据源,我们需要跟踪所有单独的行(订单)。这可以通过为每一行分配一个唯一的键来实现。
- 创建一个响应表(转到 responses 选项卡并单击电子表格图标),创建一个列以打开脚本编辑器(工具> >脚本编辑器),并粘贴下面给出的代码以生成每个订单的唯一键。
function generate_key()
{
var form = FormApp.openById("formid");//Found in form URL
Logger.log("function started");
var ss = SpreadsheetApp.openById(form.getDestinationId());
var s = ss.getSheetByName("your sheet name");// sheet
var resp= form.getResponses();
var resp_id = resp[resp.length-1].getId();//fetch response id
var h= s.getRange(1,1,1,s.getLastColumn()).getValues();//headers
var last_row = s.getLastRow();
var key_column = h[0].indexOf("key")+1;//Enter your Col name
s.getRange(last_row,key_column).setValue(resp_id);
Logger.log("function completed");}
步骤 3:创建主管理跟踪器,操作跟踪器,并添加一个功能,以添加新行,编辑行和删除行
- 创建单独的电子表格管理跟踪器,不同的操作跟踪器,最后你的主数据库表。
- 在管理跟踪器和操作跟踪器中,我们将添加以下功能:
- import_data() ->从源工作表导入新行。
- on _ Edit()-->捕捉用户操作并传递/存储相关信息,例如标记日期、更改状态等。
- archive()-->从跟踪器中删除该行,并将其移动到存档的数据库表中
我在下面演示了这些函数的实现
function import_data()
{
var source = SpreadsheetApp.openById("sourcesheetId")
var destination = SpreadsheetApp.openById("destinationsheetId")
var source_headers = source.getRange(1,1,1,source.getLastColumn()).getValues()[0]
var destination_headers = destination.getRange(1,1,1,destination.getLastColumn()).getValues()[0]
var source_data = source.getDataRange().getValues()
//"Unique_Id" is the column name for unique reference Id
var destination_ids = destination.getDataRange.getValues().map(function(x){return x.slice(destination_headers.indexOf("Unique_Id"),destination_headers.indexOf("Unique_Id")+1)})
var destination_keys = []
destination_keys = destination_keys.concat.apply(destination_keys,destination_ids) for (var i =0;i<source_data.length();i++)
{
if(destination_ids.indexOf(source_data[i][source_headers.indexOf("unique_id")]) >= 0)
{
destination.appendRow(source_data[i]) }
}
}function on_edit()
{var tracker = SpreadsheetApp.getActive().getSheetByName(sheetname)
var headers =
tracker.getRange(1,1,1,tracker.getLastColumn()).getValues()
var actRange = tracker.getActiveRange()
var edit_row = actRange.getRowIndex()
var edit_col = actRange.getColumn()
if(edit_row>=2 && edit_col == headers.indexOf("Opeartion_task_1")+1)
{
traker.getRange(edit_row,headers.indexOf("operation_date")).setValue(new Date())
tracker.getRange(edit_row,hheaders.indexOf("operation_column")).setValue(Value) }
}function archive()
{
var tracker = SpreadsheetApp.getActive().getSheetByName(sheetname)
var archive_sheet = SpreadsheetApp.openById("archiveshetId")
var headers =
tracker.getRange(1,1,1,tracker.getLastColumn()).getValues()
var actRange = tracker.getActiveRange()
var edit_row = actRange.getRowIndex()
var edit_col = actRange.getColumn()
if(edit_row>=2 && edit_col == headers.indexOf("archive")+1)
{
var row_data = tracker.getRange(edit_row,1,1,tracker.getLastColumn()).getValues()[0]
archive_sheet.appendRow(row_data)
tracker.deleteRow(edit_row) }}
第四步:为你编写的函数添加触发器
要向您的函数添加触发器,请单击左侧的时钟图标,一个新窗口将会打开,然后单击右下角的添加触发器。将弹出如下所示的触发窗口

作者图片
触发器可以是-
- 基于表单的事件
- 基于电子表格的事件
- 基于时间的事件
在这个演示中,我使用基于表单和基于电子表格的事件。
对于 on_edit 和 archive 函数,我使用基于电子表格的 on_edit 触发器。选择函数名。选择事件来源为电子表格,事件类型为编辑时,然后单击保存。您已经为函数添加了一个触发器,它们将在每次编辑电子表格时运行。
类似地,您可以为 generate_key 和 import_data 添加触发器。对于 generate_key,选择“事件源”作为表单,选择“事件类型”作为表单提交。对于 import_data,将事件源用作电子表格,将事件类型用作打开时,以便在每次打开电子表格时更新数据并提取新行。您可以根据需要选择不同的功能触发组合。
快乐编码:)
使用 PySpark 的 K-Means 聚类实现客户细分
通过 Python 和 Apache Spark (PySpark)使用 K-Means 集群实现客户细分的分步指南

在之前的文章中,我们看到了如何根据消费者最近的购买、交易频率和其他购买习惯对他们进行分类。在那里,我们利用了 RFM 分析,一种管理细分方法。在本帖中,我们将了解如何使用机器学习算法 k-means clustering 对相同的客户数据集进行细分,使我们能够更好地为客户服务,同时提高盈利能力。
和以前一样,本文中使用的完整代码和数据集可以在 GitHub 上获得。
· [Introduction](#495c)
· [Dataset](#b1d3)
· [Feature Engineering](#bf94)
· [Find the optimal number of clusters](#c533)
· [Implementing K-Means Clustering](#9145)
· [Observation](#a758)
· [Conclusion](#e0b4)
介绍
K-Means 是最流行的无监督聚类算法之一。它可以通过简单地利用输入向量进行推断,而不参考已知或标记的结果。输入参数“k”代表我们希望在给定数据集中形成的聚类或组的数量。
我们不会讨论 k-means 算法的数学细节,因为这超出了本文的范围。相反,我们将专注于业务需求:使用算法识别不同的客户群,看看我们如何更好地为客户服务。
我推荐以下站点来了解更多关于 k-means 算法、其应用、优点和缺点的信息:
要应用 k-means 聚类,我们所要做的就是告诉算法我们想要多少个聚类,它就会把数据集划分成所请求数量的聚类。有几种方法可以确定最佳的集群数量。我们将在本文中使用的肘方法就是其中之一。
本质上,我们将使用不同的 k 值(例如 2-10)运行聚类算法几次,然后计算并绘制每次迭代产生的成本函数。随着聚类数量的增加,平均失真将减少,每个数据点将更接近其聚类质心。然而,平均失真的改善将随着 k 的增加而下降。最后,我们将得到一个图表(其中我们绘制了每个 k 的平均失真),它类似于一只胳膊有一个弯曲的肘部。失真的改善在臂弯曲最严重的 k 值处下降最多。这个点被称为肘部,这将是最佳的集群大小。
资料组
我们将从上一篇文章中的一个半准备好的数据集开始,其中已经计算了每个唯一客户的最近值、频率和货币值。如果你想从原始数据集开始,你可以参考我以前的文章以及 GitHub 上的代码。
rfm_numbers = spark.read.csv("retail_rfm_numbers.csv",
inferSchema=True,
header=True)

我们有三个显著的特点:
- 最近度:顾客购买的时间。
- 频率:为了简单起见,我们将统计每位顾客购物的次数。
- 他们花掉的钱的总数。
使用 Pandas+Seaborn 探索数据集
为了更好地理解数据集,让我们使用分布图来看看数据集的特性。
import seaborn as snsrfm_scores_df = rfm_scores.toPandas()
fig, ax = plt.subplots(1, 3, figsize=(16, 8))
*# Recency distribution plot*
sns.histplot(rfm_scores_df['Recency'], kde=True, ax=ax[0])
*# Frequency distribution plot*
sns.histplot(rfm_scores_df.query('Frequency < 1000')['Frequency'], kde=True, ax=ax[1])
*# Monetary distribution plot*
sns.histplot(rfm_scores_df.query('Monetary < 10000')['Monetary'], kde=True, ax=ax[2])

特征工程
正如我们可以看到的,所有三个特征(最近、频率和货币)都是右偏的并且处于不同的尺度和范围,因此我们需要标准化数据,以便 ML 算法可以评估特征之间的相对距离并识别特征之间的趋势。令人欣慰的是,Spark ML 为我们提供了一个类" StandardScaler ",它允许我们轻松地缩放和标准化这些特性。
在以下代码中,我们执行这些特征工程步骤:
- 从货币列中删除零和负值
- 向量化所有的特征(从 Spark ML 到工作的 K-Means 是强制性的)
- 标准化特征向量
*# Remove zero and negative numbers*rfm_data = (
rfm_numbers.withColumn("Monetary",
F.when(F.col("Monetary") <= 0, 1)
.otherwise(F.col("Monetary")))
)*# Identifying feature columns*
features = rfm_data.columns[1:]*# vectorize all the features*
assembler = VectorAssembler(
inputCols=features,
outputCol="rfm_features")
assembled_data = assembler.transform(rfm_data)
assembled_data = assembled_data.select(
'CustomerID', 'rfm_features')*# Standardization*
scaler = StandardScaler(inputCol='rfm_features',outputCol='rfm_standardized')
data_scale = scaler.fit(assembled_data)
scaled_data = data_scale.transform(assembled_data)
找到最佳的聚类数
正如我们在开始时所讨论的,我们将使用肘方法来确定数据集的最佳聚类数。
costs = {}*# Apply k-means with different value of k*
for k in range(2, 10):
k_means = KMeans(featuresCol='rfm_standardized', k=k)
model = k_means.fit(scaled_data)
costs[k] = model.computeCost(scaled_data)
*# Plot the cost function*
fig, ax = plt.subplots(1, 1, figsize =(16, 8))
ax.plot(costs.keys(), costs.values())
ax.set_xlabel('k')
ax.set_ylabel('cost')

在 k=4 的值处,线看起来像肘部一样弯曲(失真的改善下降最多)。所以我们可以假设 k=4 是最佳的聚类数。
实现 K 均值聚类
在这一步中,我们将使用聚类数“k”等于 4,并对整个数据集最后一次运行 k-means 算法,我们将在名为“prediction”的列中获得每个客户的预测聚类数。
k_means = KMeans(featuresCol='rfm_standardized', k=4)
model = k_means.fit(scaled_data)
predictions = model.transform(scaled_data)
result = predictions.select('CustomerID', 'prediction')


观察
让我们将预测与起始数据集连接起来,这样我们就可以用一些图表来检查结果。
*# Join other information with the prediction result-set*
rfm_score = spark.read.csv(retail_rfm_numbers.csv',
inferSchema=True,
header=True)
rfm_score = rfm_score.select("CustomerID", "Recency", "Frequency", "Monetary", "RFM_Score", "RFM_ScoreGroup", "Loyalty")combined_result = result.join(rfm_score, on='CustomerID', how='inner')

现在,为了更好地理解预测结果,我们在箱线图中绘制了每个聚类的最近值、频率和货币值。生成的图表揭示了这些关键点:
- ccluster 2与其他聚类相比明显具有更高的频率和货币数字,并且与其他聚类相比具有最低的新近值。
- 另一方面,群 1 具有最高的新近数字,但是最低的频率和货币数字。
- 其他集群(1 和 3)位于中间。
这一观察表明,我们的顾客被分成不同的群体,其中某些群体表现出最佳表现顾客的特征(聚类 2),而其他群体对与我们一起购物失去了兴趣(聚类 1,他们已经很久没有光顾我们了)。其他顾客(第 0 类和第 3 类)可能需要更多的关注/激励,以便更频繁地与我们一起购物。基于这种分析,企业可以对不同的客户群体做出不同的反应,以增加利润。
analysis_df = combined_result.toPandas()
fig, ax = plt.subplots(1, 3, figsize=(20, 12))
sns.boxplot(x='prediction', y='Recency', data=analysis_df, ax=ax[0])
sns.boxplot(x='prediction', y='Frequency', data=analysis_df, ax=ax[1])
sns.boxplot(x='prediction', y='Monetary', data=analysis_df, ax=ax[2])

我们还可以查看成对特征比较,以了解这些特征如何影响每个分段。
*# Monetary vs Frequency (combined)*
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
sns.scatterplot(x='Recency', y='Monetary',
data=selected_result_df,
hue='prediction',
palette="deep")
*# Monetary vs Frequency (combined)*
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
sns.scatterplot(x='Recency', y='Frequency',
data=selected_result_df,
hue='prediction',
palette="deep")
*# Monetary vs Frequency (combined)*
fig, ax = plt.subplots(1, 1, figsize=(8, 8))
sns.scatterplot(x='Monetary', y='Frequency',
data=selected_result_df,
hue='prediction',
palette="deep")



结论
K-means 聚类是最流行和广泛使用的数据聚类分析技术之一。但是,它的性能通常不如其他复杂的聚类技术。但它仍然可以为我们提供很好的见解,帮助我们理解数据。你对 K-means 聚类有什么想法?
使用 PySpark 的 RFM 分析实现客户细分
使用 Python 和 Apache Spark 的 RFM 方法实现客户细分的分步指南。

德文·艾弗里在 Unsplash 上的照片
客户细分是一种营销工具,根据共同特征对客户进行分组,以便您可以有效地关注和营销每个群体,并最大限度地提高每个客户对企业的价值。
像许多其他学科一样,在商业领域,我们也看到了古老的 80-20 法则。其中 80%的收入来自 20%的客户。这就是为什么要增加你的业务,你需要了解你的客户。
客户细分至少有两个主要目标:
- 继续为您最好的客户提供最好的服务。
- 关注与你的最佳客户相似的潜在客户。
目录
∘ [RFM Model](#e548)
∘ [Dataset](#6803)
∘ [Recency, Frequency & Monetary value calculation](#87d9)
∘ [RFM score calculation](#221f)
∘ [Segmentation based on RFM Score](#a4f5)
∘ [Segmentation results](#aac0)
∘ [Conclusion](#1ef1)
RFM 模型
RFM 代表近期、频率和货币,这是一个高度灵活的管理客户细分模型。
本文将通过最流行的分布式数据处理框架 PySpark 使用 RFM 模型逐步细分客户群。在未来,我们将看到如何利用机器学习算法(如 K-Means)来改善细分过程,甚至尝试预测客户流失。
本文中使用的完整代码可以在 GitHub 上获得。
数据集
对于本文,我们将使用 Kaggle 的一个公开的在线零售交易数据集数据集,它包含了来自世界各地的每个客户的交易信息。它包括发票号、发票日期、客户 id、产品描述、购买数量和客户居住的国家等信息。为了保持文章简短,我将排除数据探索和数据准备步骤,从一个干净的数据集开始。
由于超过 90%的数据点来自英国,我们将只考虑这些数据点。让我们读取干净的数据集并检查它。

新近性、频率和货币价值计算
我们首先要计算的是 RFM 分析的三个关键因素(近期、频率和货币)。
- 最近度:顾客购买的时间。
- 频率:为了简单起见,我们将统计每位顾客购物的次数。
- 货币:他们总共花了多少钱。
我们将通过按客户分组来计算这三个关键因素,并将“2011/12/10”作为我们的参考结束日期,因为这是我们数据集中列出的最后一个交易日期。

使用熊猫+海牛探索 RFM 价值观
一旦我们计算出每个客户的个人最近次数、频率和货币价值,我们希望看到分布图来更好地理解数据。不幸的是,Apache Spark 不太适合可视化。这就是为什么我们在这部分会用熊猫和 Seaborn。

RFM 分数计算
正如我们在图表中看到的,我们的近期、频率和货币价值在不同的尺度和范围上,所有三个指标都是右偏的。对于 RFM 细分模型来说,这不是问题,但是如果我们想要使用机器学习模型进行细分(我们将在未来这样做),我们必须将这些数据标准化。
先说细分。首先,我们将为每个客户分配一个特定的分数,以反映他们个人的近期、频率和货币价值。然后,我们将汇总这些单独的分数,得到一个组合的细分分数。就像大学成绩一样。你个人的科目分数会被转换成科目成绩,然后通过合并个人成绩计算出最终成绩。
我们将把我们的客户分成三个相等的部分(每个部分 33%),并给每个部分打分,从 1 到 3(最好到最差)。
对于最近,我们将为最近购买的客户(前 33%)分配分数“1”,为中间组分配分数“2”,为第三组分配分数“3”(最后一次购买是在很久以前)。由于最近购买的客户更有可能再次做生意,我们会给他们更高的分数。
但是对于频率和金钱,我们会给最后 33%的顾客打分“1”,这些顾客购物更频繁,花费更多。并将“3”分配给前 33%的购物频率较低且花费较少的客户。
最后,我们将得到一个从“111”到“333”(最好到最差)的分段等级,以及一个从 3 到 9 的总分数。
我们的评分矩阵如下:

让我们看看 RFM 得分的代码。评分基准将取决于每个指标的百分比。

使用熊猫+海豹探索 RFM 分数

正如我们在左图中看到的,我们汇总了客户的个人 rfm 得分,我们得到了一个分布相当均匀的图表。但是当我们查看元素方面的分数(右图)时,我们看到它们并不是均匀分布的,并且由于聚合,我们丢失了重要的细节。例如,rfm 得分“5”(在左图中)可以通过元素得分“212”或“131”或“221”甚至“113”来实现。但它们并不相同。
基于 RFM 分数的分割
到目前为止,我们已经计算了我们客户的个人新近度、频率和货币价值,然后是他们单独的 r_score、f_score 和 m_score,最后是一个聚合的 rfm-score。我们还保留了元素方面的 rfm 分数。
现在,根据业务需求,我们可以按照我们想要的方式划分客户群。但是,为简单起见,我们将根据 rfm 总分将我们的客户群分为 3 个部分,并分配一个忠诚度徽章(白金、黄金、白银):
- 第 1 部分(白金):前 33%
- 第二段(黄金) : 33% — 66%
- 第三部分(银):最后 33%

使用熊猫+海豹来检查我们的 3 个忠诚度等级

检查结果
最后,让我们用分布图来看看分割结果。
有趣的是,当我们观察近期与货币和近期与频率图表时,可以观察到我们的白金和白银客户之间的明显对比。看起来白金顾客比其他人购物更频繁,花费更多(这很好)。也许银顾客可能已经失去了和我们一起购物的兴趣。他们购物更少,消费更少(这并不太好)。但是,黄金客户处于中间位置,看起来最近黄金客户的交易更加活跃。
因此,从分析中可以得出的结论是,我们需要思考如何才能继续满足我们的白金顾客继续与我们一起购物。我们可能还需要考虑如何影响我们的黄金客户来增加与我们的接触。而且追究那些银卡用户可能为时已晚。
货币与近期

频率与新近度

货币 vs 频率

结论
RFM 细分模型非常有洞察力,是了解你的客户的有力工具。在本文中,我试图展示如何用 PySpark 实现 RMF 管理细分模型。我希望这篇文章是有帮助的。我将在以后的帖子中尝试向您展示一种机器学习方法来进行客户细分。
不用外部库用 C 语言实现深度卷积神经网络
YUV 视频超分辨率案例研究

罗伯特·库伦尼在 Unsplash 上拍摄的照片
在本帖中,我们将讨论如何在没有任何外部库的情况下,用 C 语言实现一个预先训练好的深度卷积神经网络(CNN)的推理。此代码是为 YUV 视频超分辨率应用程序开发的。然而,这些功能是模块化的,稍加修改就可以应用于其他应用程序和网络结构。由于这段代码是用纯 C 编写的,没有任何外部库,因此可以很容易地集成到其他框架中。在这篇文章中,假设读者熟悉 C 编程和 CNN 的基本主题。在下文中,首先,我们简要讨论 YUV 视频格式和用于超分辨率的 theFSRCNN 算法。然后更详细地讨论了用于 YUV 视频超分辨率的这种深度 CNN 的实现。代码可以在这里找到。
YUV 4:2:0 视频格式
YUV 是和 RGB 一样的色彩空间。在 YUV 视频中,每一帧都有三个组成部分。y 是亮度分量,它是 R、G 和 B 分量的加权平均值:
Y = k₁R + k₂G + k₃B
使用 U 和 V 分量作为色差或色度来表示颜色信息。YUV 等亮度/色度系统的一个主要优势是兼容黑白模拟电视,因为 Y 分量保存所有黑白数据。然而,YUV 色彩空间相对于 RGB 的主要优势在于,U 和 V 可以用比 Y 更低的分辨率来表示,因为人类视觉系统(HVS)对色彩的敏感度低于亮度。这减少了表示色度分量所需的数据量,而没有明显的视频质量差异。

RGB 和 YUV420 色彩空间中 4×4 像素网格的组件数。(图片由作者提供,灵感来自视频编解码器设计)
YUV 的一种流行模式是 4:2:0(也称为 YUV420),这意味着 U 和 V 分量的水平和垂直分辨率都是 y 的一半。例如,对于 4×4 像素网格,RGB 使用 8×16×3 位,而 YUV420 使用 8×(16+4+4)。这样,YUV420 视频平均每像素使用 12 位,而在 RGB 色彩空间中每像素使用 24 位。这意味着节省 50%的带宽,而对质量没有明显的影响。
像 HEVC 或 H.264/AVC 这样的视频编码器对这种 YUV420 视频进行编码,压缩后的输出用于存储或通信。为了播放视频,视频播放器解码压缩的比特流。在视频分辨率较低的情况下,我们可以在播放视频之前使用超分辨率(SR)方法人工提高视频的分辨率。接下来,我们将讨论一种简单而有效的 SR 方法。
使用 FSRCNN 的视频超分辨率
快速超分辨率卷积神经网络( FSRCNN )是一种简单而有效的算法,用于将低分辨率(LR)输入图像映射到高分辨率(HR)图像。它包括五个主要部分,前四部分是卷积层,第五部分是去卷积(逆卷积)。为简单起见,我们使用 Conv( k , o , i )来表示一个卷积层,其内核大小为 k , i 个输入通道,以及 o 个滤波器。与 DeConv (k,o,i) 相同的符号用于去卷积层(逆卷积)。
- 第一部分是提取 LR 特征的特征提取器。
- 第二部分是收缩以降低 LR 特征的维度。
- 非线性映射由 4 个卷积层组成,将 LR 特征映射到 HR 特征。
- 扩展增加 HR 特征的尺寸,使其为重建做好准备。
- 最后,去卷积用一组去卷积滤波器对 HR 特征进行上采样和聚合。
参数整流线性单元( PReLU )用作激活功能,定义如下:
f(xᵢ)= max(xᵢ,0)+aᵢmin(xᵢ,0)
参数 aᵢ 是可学习的负部分的系数。PReLU 用于避免死特征。连接这五个部分形成一个八层网络:Conv(5,56,1) —普雷卢— Conv(1,12,56) —普雷卢— 4 × {Conv(3,12,12) —普雷卢} — Conv(1,56,12) —普雷卢—德孔夫(9,1,12)。有关 FSRCNN 设计细节的更多信息,请参考原文。
用 C 语言实现
我们对 YUV420 视频的 Y 分量应用 FSRCNN。由于 HVS 对 U 和 V 的敏感度较低,因此可以使用双三次等更简单的插值算法对这些分量进行上采样。因此,在这篇文章中,我们关注于在 C 上实现 CNN,对于上采样 U 和 V 分量,我们简单地重复现有的元素来填充未知的位置。为了在 C 中实现预训练深度 CNN 的推断,我们需要执行以下步骤:
- 读取输入数据
- 实施所需的操作(Conv、DeConv、PReLU 等)。)
- 读取预训练网络的权重
用 C 语言读取 YUV 视频
为了用 C 读取 YUV 文件,我们需要知道视频的维度(宽度和高度)。那么 YUV 文件可以被认为是一个二进制文件,其中对于每一帧,首先是 Y 分量的数据,然后是 U 和 V 分量的数据。空间分辨率为 m×n 的视频示例如下所示:

YUV 视频作为二进制文件。在具有 m×n 空间分辨率的视频中,对于每一帧,第一个 m×n 字节的数据属于 Y 分量,然后随后的两个 m×n/4 字节分别属于 U 和 V 分量。(图片由作者提供)
然后我们可以使用 C 语言的函数读取二进制文件。以下代码片段显示了如何从终端获取输入 YUV 视频的名称,并返回一个指示视频文件开头的指针:
实施所需的操作
因为我们是用纯 C 实现 CNN 的,所以我们需要实现所需的函数,包括卷积、填充、反卷积和 PReLU。
卷积。我们不会深入研究卷积运算符的数学细节,但会尝试回忆它如何以矩阵形式处理图像。然后将其更改为二进制数据情况,其中所有行都连接在一起并形成一个向量。在 2D 卷积中,有一个称为核的权重矩阵。这个内核在 2D 数据上“滑动”,对它当前所在的输入部分执行逐元素乘法,然后将结果相加到单个输出像素中。

一个简单的 2D 卷积运算
我们实现的挑战部分是,我们没有数据矩阵,只有一个指向二进制文件开头的指针。因此,我们需要正确地使用这个指针来模拟 2D 卷积中出现的确切情况。下图显示了一个将卷积内核应用于二进制文件的示例,其中输入数据为 4 × 4,内核为 3 × 3:

传统的 2D 卷积映射到二进制文件的矢量化空间(图片由作者提供)。
我们将该卷积运算实现为以下 imfilter 函数:
这个函数接受以下指针作为输入:img,kernel 和img_fltr。例如,img 指向内存中输入图像的开始元素。使用这个指针,我们可以通过添加偏移值来访问输入图像的每个元素。例如,输入图像的第三个元素(2D 空间中的行 0,列 2)可以由*(img+2)访问。请注意,因为我们使用指针修改值,所以 imfilter 函数隐式地应用更改,并且不返回任何内容(void 函数)。
为了开始这个过程,我们首先在卷积之前填充输入图像。为此,我们使用 pad_image 函数,该函数简单地重复填充位置的边界像素值。假设 kernel_size=k,和 padsize=(k-1)/2 。然后,对于输入图像中的每个像素位置,我们确定一个 k×k 区域,该像素位于该区域的中心。由于每个帧的 raw 都连接在二进制文件中,因此我们的代码中该像素的偏移值将是CNT =(I-padsize) cols+(j-padsize)。接下来,我们在内核和确定的区域之间执行逐元素乘法。为此,我们循环遍历内核的位置,并以相同的方式找到相邻像素的偏移值:CNT _ pad =(I+k1)* cols _ pad+j+k2*。然后可以通过 *(img_pad+cnt_pad) 访问每个邻居像素的值。为了访问内核中的值,我们使用一个简单的增量计数器 cnt_krnl 。
预科。该函数非常简单,因为它对每个像素值独立执行。以下代码片段显示了我们对 YUV 过滤帧的 PReLU 的 C 实现:
反卷积。fsr CNN 中使用的反卷积层可以认为是反卷积过程。对于卷积,如果滤波器以步幅与图像进行卷积,输出将是输入的 1/s 倍。那么,如果我们交换输入和输出的位置,输出将是输入的 s 倍。通过选择 s 作为期望的放大因子(在我们的实现中为 2),输出将直接是 HR 图像。

反卷积可以被认为是将步长参数设置为放大因子的反卷积。(图片由作者提供,灵感来自 FSRCNN )
考虑到这一点,我们可以实现类似于 imfilter 函数的反卷积,但方式相反。C 语言中反卷积函数的代码可以在这里找到。
读取预训练网络的参数
*在实现所需的功能后,下一步将是在 C 中加载预训练网络的权重。由于在深度学习框架(如 PyTorch 或 TensorFlow)中训练的网络的保存文件格式在 C 语言中不受支持,我们可以使用一个简单的技巧来加载参数。我们将网络的参数(包括权重和偏差)保存为 。txt 文件,并使用 fread 在 c 中访问它们。以下代码片段是读取第一层的权重和偏差的示例:
重要的一点是,由于权重是 3D 张量格式,我们应该按行连接它们。转换后的权重和偏差可以在 GitHub 页面上以 txt 文件的形式获得。
把所有东西放在一起,然后编译
在实现所需的功能,并找到读取网络参数的方法后,我们通过调用主函数中所需的功能来实现网络的不同层。作为一个例子,我们在 foreman_qcif.yuv 视频上运行这个。为了在 Linux 终端中编译和运行代码,我们使用以下命令:
*gcc source.c -o videosr
./videosr foreman_qcif_146x144.yuv output_foreman_352x288.yuv*
这是一个看起来像这样的例子:

左:输入视频,右:上采样视频。(图片由作者提供)
注意,你需要一个 YUV 视频播放器来播放这些视频!
用 PyTorch 编码深度学习—图像识别
通过编写一个能够识别手写数字图像的简单神经网络,开始深度学习

乔治——【stock.adobe.com】T2。作者购买的标准许可。
在这篇文章中,我使用 PyTorch 框架解释了一个简单的神经网络实现。我遍历了代码的每一步,最后,你应该从实用的角度学习深度学习的基本概念。
神经网络在数据集(MNIST)上训练,该数据集包括数千个手写数字的 28×28 像素图像。每张图片都有各自的标签——是 1 吗?一个 7?等等。
这篇文章分为,
- 先决条件和库
- 加载和转换数据
- 定义和初始化神经网络
- 培养
- 验证培训
- 玩模型
- 全代码
- 最后的想法
让我们深入研究一下。
先决条件和库
首先必须安装 Python ( 指令)、PyTorch ( 指令)和 torchvision 库(pip install torchvision)。总共需要八次导入。让我们逐一查看。
- 火炬:py torch 的组件之一。这是一个库用来帮助张量工作。它类似于 NumPy,但它有强大的 GPU 支持。
- 火炬视觉:PyTorch 项目的计算机视觉库的一部分。您必须安装 torchvision 和 pip install torchvision。
- matplotlib.pyplot: 一组用于以类似 MATLAB 的方式绘图的函数。
- PyTorch 模块有助于使用神经网络。
- torch.nn.functional: 它包含了处理神经网络时有用的函数。
- torch.optim: 各种优化算法的包。
- 转换:一个 torchvision 子包,用于帮助图像转换,比如转换为张量格式、裁剪、翻转等。
- 数据集:由图像和视频数据集组成的 torchvision 子包。
加载和转换数据
首先要做的是将数据分成训练集和测试集。您可以通过设置 train=True/False 来实现这一点。
从用于训练的样本中定义测试集很重要。因为一种方法是只进行训练,然后在训练之后,使用部分训练数据进行验证。
但是这样做有点像让开发人员也成为测试人员。测试肯定会有偏差。测试集必须包含机器以前没有遇到过的数据。
批量大小对应于一次传递给模型的数据量。使用批量的好处不多:
- 内存更少。在深度学习中,数据往往有数百万条记录。将所有内容一次性传递到模型可能不适合计算机内存。
- 权重更新更快。批处理越大,更新操作花费的时间就越长。
Shuffle (shuffle=True)有助于数据的泛化。它增加了方差,因此减少了偏差和过度拟合。
假设数据按顺序有许多“1”值。假设机器将变得过于专业,只能识别 1。当另一个数字到来时,机器在识别特定类型的数据时过于“博学”(过度拟合),发现自己陷入了困境。
定义和初始化神经网络
这里我们定义神经网络。它由四层组成。一个输入层、两个隐藏层和一个输出层。fc1 表示全连接网络 1,fc2 表示全连接网络 2,以此类推。
类型是线性的,这意味着常规的神经网络。它不是递归的(RNN),也不是卷积的(ConvNet),而是一个简单的神经网络。
对于每一层,您必须定义输入和输出数。一层的输出数就是下一层的输入。
输入层有 784 个节点。784 是 28 乘以 28(图像像素)的结果。第一层定义为输出 86。因此,第一个隐藏层必须有 86 和输入值。同样的逻辑也适用于第二个隐藏层。
86 是一个任意的数字。它可以是另一个值。
输出层包含 10 个节点,因为图像表示从 0 到 9 的数字。
接下来,我声明数据经过的路径。每当数据通过一个层时,数据就被馈送给一个激活函数。
有几个激活函数,本教程中使用的一个被称为 ReLU —整流激活函数。如果值为负,函数返回 0,如果值为正,函数返回值。
激活函数是最后一层(输出)的 softmax。Softmax 将值规格化。最后,它给出了一个概率——例如,数字 1 的 80%,数字 5 的 30%,等等。选择最高的概率。
培养
现在是训练阶段。简而言之,优化器计算实际数据和预测数据之间的差异(损失),调整权重,再次计算损失,并循环进行,直到损失最小。
一个历元相当于数据向前和向后传递一次。定义三个时期意味着数据将前进和后退三次。
zero_grad:由于数据是成批处理的,因此损失计算和累积是特定于正在处理的当前批的。目标是不要将损失从一批转移到另一批。
optimizer.step 是更新权重的时间。
验证培训
在这里,它将基础事实与模型(模型=训练好的神经网络)做出的预测进行比较。在本教程中,它给出了很高的准确性,这意味着该模型非常善于识别每个数字。
但是现实世界的问题往往不是这样。高精度可能意味着过度拟合,应谨慎分析。
玩模型
现在让我们通过显示某个数字的图像并将其传递给模型来测试它,看看它是否能正确识别。

它确实认出了那是一个 9!
密码
实现有很多资源作为参考,因为我也在学习深度学习。但是主要是来自pythonprogramming.net
最后的想法
浏览代码对我理解概念有很大帮助。我希望我在这篇教程中对你也有所帮助。我打算继续为其他神经网络类型撰写类似的帖子。
如果你觉得运行代码太慢,你可能想试试谷歌合作实验室(文章由Orhan g . Yal Zan分享),因为你可以使用 GPU 。Google Colab 允许你在浏览器中运行 Python 代码。这是谷歌云上的免费 Jupyter 笔记本。
下次见。感谢阅读。
实现梯度推进树的可解释性
梯度推进中的决策路径实现证明了无需任何 XAI 方法的局部可解释性。

特雷弗·派伊的照片:我们喜欢这片森林,因为我们喜欢可解释性
作者:Bea hernández@chucheria,angel Delgado@ thin baker
1.动机
在机器学习中,树模型的集合如随机森林(RF)和梯度推进(GB)已经在分类和回归问题(如房价预测)中给出了很好的结果。
我和一个朋友一直在做一个房价预测项目,我们意识到,尽管 GB 的结果很好,但人们仍继续使用线性回归(LR)模型来解决这类问题。这是因为 LR 具有的一个重要特征:不仅模型可解释,而且特征对模型预测有贡献。
RF 和 GB 就是这种黑盒模型的例子。然而,它们是建立在完全可解释的决策树之上的,所以我们可以基于系综的可解释性为系综建立一个解释算法。
2.可解释性
据说,如果我们能够回答这个问题,那么这个模型就是可解释的。”。这主要取决于模型的算法,有些模型不支持这个问题。
与 LR 相比,更高容量的模型可以学习更复杂的模式来进行预测。然而,代价是预测是不可解释的,这意味着我们既不能说出这种预测的原因,也不能说出每个特征的贡献大小。这种不可解释的模型被称为黑箱模型。
同时,有多种方法来解释预测。
- 全局可解释性:我们试图回答“一般来说,一个特性对预测有多大贡献?”。例如,在房价问题中,我们想要找出“特征平方米是否比浴室数量对预测有更大的影响”。
- 局部可解释性:我们试图回答是什么特征值给了我们一个特定的预测。例如,“在这个样本中,预测值是某一个,因为房间数是 4,并且房子位于这个特定的街道上”。
目前,绝大多数机器学习模型的性能都比 LR 好得多。之所以还是热门机型,是因为一个 LR 的可解释性很强。有了 LR,你不仅可以理解预测,还可以量化每个特征对预测的贡献,因为 LR 公式的本质。

作者图片:线性回归预测贡献
LR 中的预测由下式给出:

这可以解释为

其中 c_i 是第 i 个特征的贡献。
除此之外,还有一些可解释的模型,在最终决策中没有每个特征的贡献。比如决策树。决策树执行一组分层决策以获得解决方案(例如房间数量> 3 ),从而获得最终结果。每个决策都是一次针对一个特性做出的,但是每个决策对最终结果都没有贡献(至少,我们会直接提出一个解决方案)。

作者图片:决策树逐节点预测
在决策树中,预测是:

其中 leaf(x) 是样本 x 所在的树的叶节点

GB(以及 RF)是决策树模型的集合。集成意味着 GB 是一组共同执行预测的决策树。一个树的集合比它单独的任何一个树表现得好得多,然而,问题是这个集合不是局部可解释的,因为每棵树都有它自己的完全独立于其他树的决策路径,我们只能知道哪些特征在总体上更重要(全局解释)。
具体来说,GB 集成算法基于一种叫做剩余可加性的东西。这意味着系综的每一棵树都从之前的树的预测和真实值(残差)中学习差异,因此最终的预测是所有树的预测(贡献)的总和(可加性)。

作者图片:在树贡献级别解释梯度推进预测
你可能想知道为什么可解释性对我们如此重要。原因是因为在某些问题上,解释和预测一样重要。在房价预测中,您想知道是什么使某个房子如此便宜/昂贵,或者如果某个特征值不同,价格会变化多少。
考虑到所有这些信息,并考虑到 GB 为回归问题提供的巨大结果,我们开发了一种算法,以某种方式聚合模型所有树的贡献,以理解预测。
3.GB 决策路径
我们已经提到,模型的可解释性只取决于它的算法,有可解释的模型和那些不是根据它的算法的模型,但从统计上来说,我们总是可以分析模型返回的结果来理解它们。
这些统计方法允许模型不可知的解释性分析。例如,一种常见的分析方法是分解法。如果我们仅仅改变一个特征,这种分解允许我们测量一个预测改变了多少。预测中的差异给出了特定特征在最终结果中的贡献的估计。
特征分解贡献 X_i :

值得注意的是,这个重要性度量并不完全精确,而是一个统计估计。
即使 GB 的性质没有给出其预测的解释,树算法预测一次执行每个决策值。这种性质以及剩余活动允许我们自然地将分解特征贡献的想法包含在梯度增强算法中。
算法
1.节点贡献
对于每棵树,通过一组特征决策分割(例如浴室数量> 3)进行预测,并且它们中的每一个对应于树中的一个节点。这些节点中的每一个都分配有预测值。树的预测是最后一个节点的值。
类似于分解分析,我们可以将每个决策的贡献估计为该节点的值与前一个节点的值之间的差:

其中 c_i 是决策 i 的贡献。
2.树贡献
这是由于来自梯度增强系综的剩余添加。最终的预测是所有树的预测之和,所以每棵树的贡献就是它的预测:

其中 treePred_i 是第棵棵的预测。
3.决策贡献
我们的目的是了解每个决策对最终结果的贡献有多大,因此,按照前面的两个步骤,我们可以根据决策的贡献来编写预测:

其中 value(node_i)_j 是树 j 的节点 i 的值。
4.特征贡献
如果我们意识到每个决策都有一个与之绑定的特征,我们不仅可以获得每个决策的贡献,还可以根据特征将它们分组并求和,这样我们就可以获得特征贡献和局部可解释性。
所以解释应该是
- 决策 1(特征 j)-贡献节点 1
- 决策 2(特征 k)-贡献节点 2
由于我们一直在与 ScikitLearn、 的 GB 合作,我们已经在库 内部实现了我们的方法。ScikitLearn 是最受欢迎的机器学习库之一,因此在其中实现可解释性对许多人来说非常有帮助(我们仍在等待我们的 PR 被接受)。

作者图片:我们对节点贡献可解释性的实现
4.代码示例
为了在实践中解释整个算法,我们将展示如何使用 ScikitLearn GB 类来解释 Boston House Pricing 数据集的预测(正如我们提到的,我们已经打开了 一个问题 ,其中我们在 decision_path 方法中实现了我们的解决方案)。
a.决策可解释性示例
import numpy as np
from sklearn.datasets import load_boston
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_splitX, y = load_boston(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)reg = GradientBoostingRegressor(random_state=0, n_estimators=5)
reg.fit(X_train, y_train)init, values, explanations = reg.decision_path(X[0:1])print("Prediction for X_i: ", reg.predict(X[0:1]))
print("init: ", init)
print("values: ", np.nansum(values), "\\n")print("Breakdown:")
for val, exp in zip(values.tolist()[0],explanations):
if ~np.isnan(val) and val!=0:
print("contribution of ", round(val,5),
"\\t because column ", round(exp[0])," ", exp[1], " ", round(exp[2],2))Prediction for X_i: 23.48966781
Init: 22.608707124010557
values: 0.8809606811142934Breakdown:
contribution of 0.85498 because column 12 < 8.13
contribution of -0.27826 because column 5 < 7.43
contribution of -0.36345 because column 5 < 6.66
contribution of 0.67168 because column 12 < 9.63
contribution of -0.36681 because column 5 < 7.01
contribution of -0.04358 because column 7 > 1.48
contribution of -0.24703 because column 5 < 6.84
contribution of 0.2979 because column 12 < 14.4
contribution of -0.02668 because column 7 > 1.47
contribution of 0.63668 because column 12 < 8.13
contribution of -0.325 because column 5 < 7.01
contribution of -0.04324 because column 7 > 1.48
contribution of 0.48953 because column 12 < 9.71
contribution of -0.33782 because column 5 < 6.8
contribution of -0.03794 because column 0 < 4.73
正如我们所看到的,预测值(23.48)是初始平均值(22.60)加上分解中可以看到的特征贡献值(0.88)之和。
b.特征可解释性示例
正如我们之前提到的,利用之前的结果,我们可以按特征对决策进行分组,以获得特征贡献
from collections import defaultdict
counter = defaultdict(float)
cols = [i[0] if i else None for i in explanations]for col,val in zip(cols, values.tolist()[0]):
if ~np.isnan(val) and val!=0:
counter[col]+=valfor col, val in counter.items():
print("column ", col, "\\tcontributed ", val)column 12 contributed 2.9507737309447437
column 5 contributed -1.918373871627328
column 7 contributed -0.11350035836622434
column 0 contributed -0.03793881983689874
我们不仅可以看到这个决策最重要的特性是第 12、5、7、0 列,还可以看到每一列对最终结果的贡献。
5.结论
- 在某些问题中,可解释性是一个关键概念,不仅是为了理解预测,也是为了衡量每个特征对最终结果的影响。模型的可解释性是它固有的东西。
- 除了模型的可解释性,还有一些统计工具可以让我们理解和估计每个值对模型的最终预测的影响,比如分解。
- GB 的性质(即来自树的剩余学习和决策路径)允许内在地估计每个决策和每个特征对预测的贡献,因此即使它们不是可解释的,它们也是可解释的。

马克·泰格霍夫在 Unsplash 上的照片:我们也喜欢这个盆景
参考
- 比切克、普热米斯劳和布日科夫斯基、托马兹。解释性模型分析
- Gosiewska、Alicja 和 Przemyslaw Biecek。2019.iBreakDown:非加性预测模型的模型解释的不确定性
- 决策路径 GitHub 问题
- Scikit-Learn 集成:梯度推进回归器
感谢阅读!💖
实现生成对抗网络以提高卷积神经网络的性能

布雷特·乔丹在 Unsplash 上的照片
简介
机器学习已经成为分析高级数据集和推断隐藏信息的最先进领域。出于各种原因,在机器学习中创建模型,例如用于分类或预测任务。具体来说,卷积神经网络(CNN s)在图像分析和特征提取方面显示出巨大的潜力。在图像上训练机器学习模型的一个问题是使用小的和缺乏多样性的数据集导致无效和不准确的模型。为了克服这个问题,可以在小数据集中扩充图像,以增加数据集的大小,并为要完成的任务创建更有效的模型。
有多种数据扩充技术可用于创建新样本来增加影像数据集的大小。这些技术包括裁剪、添加噪声、调整大小、翻转、旋转和改变图像的颜色。使用这些技术的一个缺点是没有向模型中引入“新”数据。模型已经在不同的状态下观察到这些样本(例如:将图像旋转 45 度向模型显示相同的图像,只是状态不同,其中状态是图像的角度)。虽然模型可以对不同州的相同样本进行分类很重要,但使用相同的数据可能不会对模型的概化能力产生很大影响。 概化 是模型正确识别和理解未观测数据样本的能力[【1】](http://. Goodfellow, Y. Bengio, and A. Courville,Deep learning.MITpress, 2016)。
生成对抗网络(GANs)是一种数据增强技术,它产生新的数据样本。GANs 从潜在空间获取随机噪声,并生成模拟原始数据集特征分布的独特图像。一个 GAN 在其架构中包含两个不同的网络,它们竞争实现纳什均衡(更多关于博弈论和什么是纳什均衡可以在这里找到)。在这项研究中,GAN 网络是根据 DCGAN 和 WGAN 建模的,它们使用卷积神经网络作为其两个竞争模型的框架。 WGAN 使用与 DCGAN 类似的结构,但其损失函数采用 Wasserstein-1 距离,将鉴别器变成预测样本来自原始数据集的可能性的“评论家”。
GAN 中的“鉴别器”的唯一目的是分析假样本和真样本,将每个样本标记为“假”或“真”真实样本的批次从原始数据集被馈送到鉴别器,而假样本来自生成器。顾名思义,“生成器”的目的是从潜在空间获取随机噪声作为输入,并生成“假”数据提供给鉴别器。生成器的目标是创建如此逼真的图像,以至于鉴别器认为它们是真实的。通过使用反向传播来随着时间的推移更新这些模型的权重和偏差,生成器将慢慢学会创建模拟原始数据集的物理和数学分布的样本。
虽然 GANs 的实现涉及到大量的数学知识,但我只想提供一个基本的框架(提示!关于 GANs 的未来文章将更深入地讨论这一数学问题!)Goodfellow 等人[3]在 2014 年概述了 GAN 的一般损失函数。
网络的损失函数是:

等式:GAN 损失函数
其中鉴别器希望最大化函数值,而生成器希望最小化函数值。【x】是样本为“真”的概率 D(G(z)) 是样本为“假”的概率。'
鉴频器的损失函数为:

等式:鉴别器的损失函数
发电机的损耗函数为:

等式:发电机的损耗函数
后来改成了:

等式:发电机的损耗函数
Goodfellow 后来改变了发电机的损耗函数,以提高稳定性并克服饱和问题。虽然 GAN 由于不稳定性而有些难以训练(更多关于 GAN 不稳定性的信息此处),但是如果您能够用您的给定数据训练 GAN,您就可以增加更接近原始数据集的数据,以及分类模型从未见过的数据,这对于提高模型的概化能力是必要的。这就是使用 GANs 优于其他数据增强技术的理由。我们能够成功地为数据创建新的样本,而不必亲自出去获取,并且我们可以确信,如果经过适当的训练,扩充的数据遵循原始数据的类似概率分布。
数据集
用于该分析的数据集是手写数字的 MNIST 数据集。在此分析中,使用了两种不同大小的数据集。一个称为“原始数据集”的数据集是 MNIST 的原始大小,有 60,000 个样本用于训练,10,000 个样本用于测试。称为“缩减数据集”的第二个数据集是从 MNIST 中随机选择的 3000 幅图像用于训练数据集,10000 幅图像用于测试数据集。

表:MNIST 数据集中的类分布
预处理
由于分析主要是使用 Keras API 进行的,灰度、28x28 图像必须首先转换为 NumPy 数组。然后,这些数组被转换为“float32”格式,并除以 255 以进行像素缩放。
CNN 分类模型

创建了两个不同的模型,一个用于原始数据集,一个用于缩减后的数据集。如上所示,除了卷积层中的神经元数量之外,这些模型在架构上非常相似。在两次实验中,模型没有改变,以观察来自 GAN 的增强数据将如何影响现有架构。
生成性对抗网络
我大部分的 GAN 学习来自 Jason Brownlee 的书Python 中的生成对抗网络。(强烈推荐!超级有帮助!).我还在 Coursera 上学习了生成对抗网络专业化课程,该课程真正深入理解了 GANs 的理论和实现。
创建了两种不同类型的 gan。创建的第一种 GAN 是条件 GAN (cGAN) ,创建的第二种 GAN 是 Wasserstein GAN (WGAN) 。虽然这些网络背后的数学公式超出了本文的范围,但我还是想介绍两种 GAN 框架之间的一些一般差异。使用的两种 gan 都以类别标签(0-9)为条件。

弗雷歇初始距离
扩充数据的一个困难是评估数据的质量。这里的“质量”是指合成数据与原始数据集的相似程度,以及原始数据集与扩充数据集相比的概率分布的相似程度。一种用于量化图像“质量”的测量评估是弗雷歇初始距离得分度量。

FID 分数比较从 Inception V3 模型获得的两个图像集激活的平均值和协方差。使用这种度量的一个警告是,比较的图片数量较少可能导致来自相同或相似分布的数据集的初始得分不太重要。
实验
进行了两个实验来分析如何通过使用用于图像数据扩充的 GAN 来影响模型的性能。
实验 1:使用没有扩充数据的两个不同大小的数据集来训练 CNN。

图:没有增加数据的两个不同模型的精确度和损失
虽然可以为 MNIST 创建更好的模型,但目标是获得两个没有机器学习模型训练中出现的过拟合/欠拟合问题的模型。如您所见,原始 MNIST 数据集达到了 99.26%的准确率,较小的数据集达到了 92.63%的准确率。较小的数据集也有更多的损失,这是意料之中的,因为用有限的数据量训练模型通常要困难得多(这也是我们使用数据扩充技术的原因之一!)
实验 2:使用没有扩充数据的两个不同大小的数据集来训练 CNN。
扩充数据
如前所述,使用两种不同类型的天然气水合物来扩充数据。一旦数据被扩充,图像和标签就被添加到原始数据集中。在整个研究过程中,cGAN 和 WGAN 数据从未合并到任何数据集中。以下是来自“0”类的两个合成样本,由每个 GANs 生成。

图:由 cGAN 生成的“0”

图:WGAN 生成的“0”
我能够成功地训练一个 cGAN 100 个纪元,但是,我无法让我的 WGAN 在 Google Colab 上完全训练有限的 GPU 空间(我已经订阅了一个 Pro 帐户!).
弗雷歇初始距离得分
由于 Google Colab 的内存有限,我只能分析来自两个不同 GANSs 的多达 5000 张图像的 FID 分数。我的目标是观察 100 张图片和 5000 张图片相比,分数是否有下降的趋势(分数越低越好)。

如您所见,FID 分数随着时间的推移从 100 张图像下降到 5000 张图像。虽然 FID 分数有些高,但随着我们添加更多图像,看到这种下降趋势,证明这些图像适合用于重新训练用于 MNIST 数据分类的 CNN 模型,特别是在添加 10,000 张图像时。
模特培训

图:附加 cGAN 样本的数据集的重新训练模型的准确性和损失
使用 cGAN,原始数据集在准确性方面没有太大变化(增加了 0.19%)。损失也几乎不受影响(培训下降 1.23%,验证下降 1.96%)。重要的是,GAN 的使用并没有减损 型号的性能。GAN 的使用显示了小数据集的巨大改进。该模型实现了 97.72%的准确率(提高了 5.09%),并显示了损失指标的巨大改善(训练损失减少了 13.16%,验证损失减少了 15.19%)。

图:附加了 WGAN 样本的数据集的重新训练模型的准确性和损失
对于 WGAN,原始模型并没有真正改变(类似于使用从 cGAN 扩充的数据所发生的情况)。该模型的损失指标仍有所下降,但准确性停滞不前,几乎没有提高。对于缩减的数据集模型,实现的准确度为 98.00%(增加 5.37%),而损失再次降低(训练损失降低 14.48%,验证损失降低 10.72%)。
使用由两个不同的 gan 生成的数据,然后添加到数据集的结果表明,gan 确实有助于实现模型性能的大幅提高!正如您所看到的,较小的数据集对其大小的增加更敏感,并且他们的模型被认为从 CNN 训练中 GAN 的使用中获得了最大的积极影响。
k-褶皱分析
在重新训练模型之后,进行了k-折叠分析,以查看新训练的模型在不同折叠级别上的表现。对于这个分析,我选择了 5 个折叠,然而,有许多程序可以找到最佳数量的 k 值。(一般行业标准是 5 或 10)。结果是 k 倍非常令人惊讶,实际上显示了与 cGAN 数据混合的数据集在准确性上的更大提高和模型损失的减少。

表:k 倍分析的结果
k -Folds 分析反映了先前的观察结果,即增强图像对原始大小的数据集没有巨大影响。对于较小的训练集,k-折叠显示了与扩充数据相结合的数据集在准确性和损失方面的重大改进
结论
生成式对抗网络是一种强大的数据增强技术,它可以产生具有增强的概化能力的健壮模型。cGAN 显示出比 WGAN 更有效地改进更大的数据集。cGAN 高效的原因可能是它创建了一个更加多样化的图像集,并且能够针对整整 100 个时代进行训练。在研究的初始试验中,WGAN 对于训练较小的数据集更有效,但是通过倍分析表明,WGAN 对模型的影响小于 cGAN。这一观察解释了 GANs 在其训练过程中通常产生更多时期的更高质量图像的重要性。使用 GANs 进行数据扩充可以提高模型精度,减少模型损失,同时克服过度拟合和欠拟合,支持其用于提高模型的概化能力。
请在 LinkedIn 上加我或者随时联系!
其他来源
- 。古德费勒,y .本吉奥,和 a .库维尔,深度学习。MITpress,2016 年
- 重新思考电脑视觉的盗梦空间架构。IEEE 计算机视觉和模式识别会议论文集。2016.
- 伊恩·古德菲勒、让·普吉-阿巴迪、迈赫迪·米尔扎、徐炳、戴维·沃德-法利、谢尔吉尔·奥泽尔、亚伦·库维尔和约舒阿·本吉奥。生成对抗性网络。神经信息处理系统进展,27,2014
在 Streamlit 中实现 Google OAuth
保护您的应用

在 HousingAnywhere,我们经常利用 Streamlit 为我们的指标和目标构建交互式仪表盘。Streamlit 是一个相对较新的工具。它有很多潜在的用例,非常容易使用,但在某些领域(如安全性)可能会有所欠缺。大多数人用于 Streamlit 的一种流行、简单的安全方法是利用 SessionState ,但是它非常容易受到攻击(例如暴力破解)。
在本文中,我将向您展示如何实现 Google OAuth 2.0 来更好地保护您的应用程序。
设置 Google OAuth
首先,让我们配置 OAuth 同意屏幕。
- 转到 Google API 控制台 OAuth 同意屏幕页面。
- 选择内部以便只有您组织内的用户可以访问该应用程序。
- 填写必要的信息。
- 单击添加范围,然后添加您需要的任何必要范围。对于这个例子,我们不需要任何。
接下来,我们需要从 GCP 创建一个授权凭证:
- 进入 GCP 控制台的[凭证页面](http://Credentials page)
- 点击创建凭证> OAuth 客户端 ID。
- 在应用类型中选择 Web 应用,并填写您的客户名称。
- 为您的申请填写重定向 URIs。这些是您希望用户登录后重定向回的链接。例如,在本地环境中,可以使用
http://localhost:8501 - 记下客户端 ID 和客户端密码以备后用。
实现逻辑
先决条件
安装以下库
streamlit==0.81.1
httpx-oauth==0.3.5
我们将利用 SessionState 来存储从 Google API 返回的令牌。将此存储在session_state.py中:
在主应用程序中,我们首先定义这三个变量来存储之前的客户端 ID 和密码:
client_id = os.environ[**'GOOGLE_CLIENT_ID'**]
client_secret = os.environ[**'GOOGLE_CLIENT_SECRET'**]
redirect_uri = os.environ[**'REDIRECT_URI'**]
我们将使用 httpx-oauth 作为我们的授权客户端:
client = GoogleOAuth2(client_id, client_secret)
现在,创建一个处理创建授权 URL 的函数:
**async def** write_authorization_url(client,
redirect_uri):
authorization_url = **await** client.get_authorization_url(
redirect_uri,
scope=[**"email"**],
extras_params={**"access_type"**: **"offline"**},
)
**return** authorization_urlauthorization_url = asyncio.run(
write_authorization_url(client=client,
redirect_uri=redirect_uri)
)st.write(**f'''<h1>
Please login using this <a target="_self"
href="{**authorization_url**}">url</a></h1>'''**,
unsafe_allow_html=**True**)
一旦用户从谷歌授权页面被重定向回来,授权码就包含在 URL 中。我们将使用st.experimental_get_query_params()获得它
code = st.experimental_get_query_params()[**'code'**]
并使用此函数从 Google 获取令牌:
**async def** write_access_token(client,
redirect_uri,
code):
token = **await** client.get_access_token(code, redirect_uri)
**return** tokentoken = asyncio.run(
write_access_token(client=client,
redirect_uri=redirect_uri,
code=code))
session_state.token = token
该过程可以如下所示:

来源:谷歌
我们使用 SessionState 实用程序存储令牌,这样用户就不需要在会话期间重新授权。由于我们存储每个会话的令牌,刷新页面将再次触发授权过程。
保护数据安全是构建数据应用程序的一个重要方面。有了 Google OAuth,您可以高枕无忧,因为您知道您的应用程序可以更好地抵御可能的入侵。你可以在 GitHub 上点击查看整个应用程序的代码。
梯度下降的实现
实施梯度下降的一个基本例子
我们在这里已经推导出一个如何实现梯度下降的算法,但是在代码中实现这个算法的时候有很多细微差别。如果你一开始就试图用一个巨大的项目来解决这个算法,你会发现很难跟踪所有矩阵的形状。因此,我们将从推导中使用的相同架构开始。
首先,让我们只关注这个算法工作的一般形式。当我们有了可用的东西后,我们可以清理它,使它可用。为了测试我们的算法,我们将尝试将一个输入映射到一个输出。
初始化我们的神经网络
我们的第一步是为我们的神经网络创建权重。我们可以使用一个简单的 for 循环来做到这一点。首先,我们指定我们的神经网络将具有的层。

这是我们将要创建的神经网络。**作者图片
我们可以这样指定神经网络的层次:层= [2,3,3,2]。需要注意的是,我们没有将偏差作为每一层的一部分。我们稍后将包括它们。
在我们创建 for 循环来生成权重之前,看一下每个层的权重的形状以及它们与层大小的关系。

这里只关注每个矩阵形状。**作者图片
我们可以看到我们的体重形状是:(下一层神经元,当前层神经元+ 1)。利用这一点,我们可以创建一个 for 循环,为我们生成整个网络的权重。这些权重被初始化为随机数开始。
正向阶段
现在我们有了权重,我们需要执行我们的前进阶段,并收集所有层的输入。对于这个例子,我们将使用 2 个随机输入,它们在 0 和 1 之间。重要的是它们在 0 和 1 之间,所以我们在计算中不需要处理巨大的数字(稍后会详细介绍)。
首先,我们定义 sigmoid 函数,因为这是我们将要使用的激活函数。现在,如果我们简单地做权重和输入的点积,我们会得到一个错误。这是因为我们必须将我们的偏差项加到我们的输入中。因此,我们首先将偏差添加到输入中,然后在权重和输入之间进行点积,激活这个加权和,最后记录输出作为下一层的输入。
好了,运行完上面的代码后,让我们来看看列表' x_s'。

最后一个列表是我们神经网络的最终输出。这是我们与预期输出进行比较以找出错误的地方。**作者图片
一切都没问题。因此,我们的下一步是更新我们的权重,使我们的最终输出与我们的预期输出相匹配。对于这个例子,我们的预期输出将再次是 0 和 1 之间的两个虚构数字。
反向相位
如果你没有浏览我们推导出我们的梯度下降算法的页面,或者你没有很好地理解它,实用的信息是这样的:找到我们所有层的梯度可以在 3 个步骤中完成。

然后我们将使用这些梯度来更新我们的权重。记住有 x 穿过的圆圈意味着我们要将行相乘。**作者图片
我们的第一步是初始化 psi。psi 的每一项都是∂E/∂Xi * ∂Xi/∂Zi.利用 sigmoid 函数的导数= σ(x)(1-σ(x))和我们的误差的导数=-2(y _ truei-Xi),我们很容易找到这个偏导数。因此,为了初始化 psi,我们只需遍历每个 y_true 值,并追加与该 y_true 值相关联的∂E/∂Xi * ∂Xi/∂Zi。
现在是第一个棘手的细节。现在,psi 的形状是(2,)。为了使我们的算法有效,我们需要 psi 的形状与我们的派生算法中的形状相同,即(2,1)。这是一个小的,有点令人沮丧的细节;但是很容易解决。
**psi = np.reshape(psi,(psi.shape[0],1))**
很简单。现在我们已经初始化了 psi,我们可以继续第 2 步,这是找到最后一层的梯度。这只是 psi *倒数第二个输入。或者;
**gradients = []
gradients.append(psi*x_s[-2])**
Numpy 为我们处理行乘。现在我们准备进入第三步,这有点棘手。我们想从倒数第二层开始,因为我们刚刚找到了最后一层的梯度。向后移动,然后我们更新 psi 并找到层梯度,直到我们到达最后一层。
我们首先定义我们用来更新 psi 的术语中要处理的权重和输入。这只是排除偏差的层权重和输入,因为我们试图找到的权重根本不会影响偏差(无论如何它们总是= 1)。然后,我们把这个术语变换成正确的形状。然后,我们像以前一样更新 psi 并重塑它。最后,我们添加我们的梯度。
在我们运行上面的 for 循环后,我们应该得到下面的梯度张量:

作者提供的图片
请注意,第一个渐变与最后一个层的权重大小相同,第二个渐变与倒数第二个层的渐变大小相同,依此类推。这是一个很好的健全检查,以便我们知道一切都正常工作。我们现在用以下有趣的索引来更新我们的权重:
**for i in range(len(gradients)):
weights[i] -= .001*gradients[-(i+1)]**
就这样!我们刚刚更新了一次权重。现在,我们可以在一个 for 循环中运行上面的代码来更新我们的权重,比如说 2500 次,看看它是否有效。
现在是关键时刻了。让我们将最终输出与 y_true 值进行比较。

作者提供的图片
如您所见,我们的输出与 y_true 值完全匹配。现在,在我们清理这段代码并把它放到一个类中之前,让我们试着对它进行扩展。让我们看看这是否仍然工作将 1000 个神经元在每个隐藏层(伏笔,1000 个神经元的加权总和可能是一个非常大的数字!).去吧,你自己试试。
现在,让我向你展示当我将层更改为[2,1000,1000,2]时,经过 10,000 次训练迭代后得到的输出;

作者提供的图片
嗯,如你所见…根本不起作用!这是为什么呢?!
为了诊断这一点,让我们看看我们的梯度。

作者提供的图片
嗯,有个问题…试着想想为什么我们的梯度都是 0。我给你一个提示,它与我们的激活函数及其导数有关。
让我们看看最后一个梯度。∂e/∂w = ∂e/∂x[-1]* ∂x[-1]/∂z * ∂z/∂w.误差的 PD(相对于)最后输出,乘以最后输入的 PD,乘以最后加权和,乘以最后加权和的 PD。
如果我们的梯度是 0,这些中至少有一个必须是 0。记住∂X[-1]/∂Z 只是我们激活函数的导数。

对于非常高或非常低的 x 值,斜率是多少
如果 x 很大或者很小,这个函数的导数会是多少?应该是 0!所以,让我们改变我们的 sigmoid 函数,使它更线性。我们可以通过将 x 乘以一个小常数 c 来实现这一点(这里最好用 0.01)。这是一种“贴上创可贴”的解决方案,更好的解决方案是切换到所谓的“泄漏 Relu”激活功能。如果你想的话,你可以,但是我要在 sigmoid 函数中,把 x 乘以 0.01。

注意拉长的 x 轴。**作者图片
现在,我们要做的就是在 sigmoid 函数中加一个 0.01。从技术上讲,我们的新导数应该是 0.01 * σ(x)(1-σ(x)),但我们不必担心这一点,因为它只是被吸收到学习率中。此外,这样做的全部目的是扩大对我们衍生产品的规模。新的输出和对代码的唯一更改如下所示:

对代码的唯一修改是用蓝色圈起来的。**作者图片
如你所见,我们已经解决了我们的问题!这也是为什么我们必须在 0 和 1 之间输入。如果我们的加权和非常大,那么我们的导数将接近于 0。最后,我们可以稍微整理一下,然后把它放到一个类中。
现在,我们终于可以做一些有趣的事情了!有了这个算法,我们将得到一个识别手写数字的程序。这个项目很经典,但是你从来没有看到有人从零开始做(也许有很好的理由,但是我们正在学习)!你可以在这里找到这个项目。
感谢您的阅读!如果这篇文章在某种程度上帮助了你,或者你有什么意见或问题,请在下面留下回复,让我知道!此外,如果你注意到我在某个地方犯了错误,或者我可以解释得更清楚一些,那么如果你能通过回复让我知道,我会很感激。
这是一系列文章的继续,这些文章从头开始对神经网络进行了直观的解释。其他文章请参见下面的链接:
第 4 部分:梯度下降的实现(一个例子)
用谷歌 JAX 在 Python 中实现线性算子

作者插图
线性算子综述
线性算子或线性映射是从一个向量空间到另一个向量空间的映射,保持向量加法和标量乘法运算。换句话说,如果 T 是线性算子,那么 T(x+y) = T(x) + T(y) 和 T (a x) = a T(x) 其中 x 和 y 是向量,a 是标量。
线性算子在信号处理、图像处理、数据科学和机器学习中有广泛的应用。
在信号处理中,信号通常表示为正弦曲线的线性组合。离散傅立叶变换是一个线性算子,它将信号分解成其单独的成分频率。小波变换通常用于将信号分解成单个位置尺度的特定小波,从而可以轻松识别和定位信号内部的有趣事件或模式。
在统计学中,线性模型用于将观察值或目标变量描述为特征的线性组合。
我们可以把线性算子看作是从模型空间到数据空间的映射。每个线性算子都有一个矩阵表示。如果线性算子 T 用矩阵 A 表示,那么线性算子 y = T(x) 的应用可以写成:
y = A x
其中 x 是模型, y 是数据。在傅立叶变换中, A 的列是单独的正弦波,模型 x 描述了每个正弦波对观察信号 y 的贡献。通常,我们被给定数据/信号 y ,我们的任务是估计模型向量 x 。这就是所谓的逆问题。对于标准正交基,反问题是容易的。简单的解决方法是x = A^H y。但是,如果模型大小小于数据大小或更大,这就不起作用了。当模型尺寸更小时,我们有一个过度拟合的问题。一个基本的方法是解决最小二乘问题:
minimize \| A x - y \|_2^2
这导致了一个由正规方程组成的系统:
A^T A x = A^T y
像 NumPy 和 JAX 这样的库为矩阵代数提供了广泛的支持。然而,从时间和空间复杂性的角度来看,矩阵代数的直接方法对于大型系统是不允许的。对于非常大的矩阵,存储 A 本身可能非常昂贵。计算A^T A是一个 O(n)运算。随着 n 的增加,计算它的逆来解正规方程变得不可行。
线性算子的函数表示
幸运的是,许多在科学文献中有用的线性算子可以用简单的函数来实现。例如,考虑 R⁸有限大小向量 x 的前向差分算子(8 维实向量)。矩阵表示是:
A = jnp.array([[-1\. 1\. 0\. 0\. 0\. 0\. 0\. 0.]
[ 0\. -1\. 1\. 0\. 0\. 0\. 0\. 0.]
[ 0\. 0\. -1\. 1\. 0\. 0\. 0\. 0.]
[ 0\. 0\. 0\. -1\. 1\. 0\. 0\. 0.]
[ 0\. 0\. 0\. 0\. -1\. 1\. 0\. 0.]
[ 0\. 0\. 0\. 0\. 0\. -1\. 1\. 0.]
[ 0\. 0\. 0\. 0\. 0\. 0\. -1\. 1.]
[ 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]])
然而,矩阵向量乘法计算 A @ x 可以更有效地写成:
import jax.numpy as jnp
def forward_diff(x):
append = jnp.array([x[-1]])
return jnp.diff(x, append=append)
这将计算从 O(n)降低到 O(n)。
正向和伴随算子
一般来说,我们需要为一个线性操作符实现两个操作。从模型空间到数据空间的正向运算符:
y = A x
以及从数据空间到模型空间的伴随算子:
x = A^T y
对于复向量空间,伴随算子将是埃尔米特转置:
x = A^H y
现有实施
SciPy 为实现SciPy . sparse . Lina LG中的线性运算符提供了一个非常好的接口。线性算子 。PyLops 建立在它的基础上,提供了线性操作符的广泛集合。
CR-Sparse 中基于 JAX 的实现
JAX 是一个新的基于函数式编程范例的高性能数值计算库。它使我们能够用纯 Python 编写高效的数值程序,这些程序可以使用针对 CPU/GPU/TPU 硬件的 XLA 进行编译,以获得最先进的性能。
CR-Sparse是一个基于 JAX 开发的新开源库,旨在为基于稀疏表示的信号处理提供 XLA 加速功能模型和算法。它现在包括了一个建立在 JAX 基础上的线性操作符的集合。文档在这里。我们用一对函数times和trans来表示线性算子。times函数实现正向运算,而trans函数实现伴随运算。
入门指南
pip install cr-sparse
对于最新的代码,直接从 GitHub 安装
python -m pip install git+https://github.com/carnotresearch/cr-sparse.git
在下面的交互式代码示例中,以>开头的行有代码,没有>的行有输出。
一阶导数算子
要创建一阶导数运算符(使用向前差分):
> from cr.sparse import lop
> n = 8
> T = lop.first_derivative(n, kind='forward')
可以看到线性算子的矩阵表示:
> print(lop.to_matrix(T))
[[-1\. 1\. 0\. 0\. 0\. 0\. 0\. 0.]
[ 0\. -1\. 1\. 0\. 0\. 0\. 0\. 0.]
[ 0\. 0\. -1\. 1\. 0\. 0\. 0\. 0.]
[ 0\. 0\. 0\. -1\. 1\. 0\. 0\. 0.]
[ 0\. 0\. 0\. 0\. -1\. 1\. 0\. 0.]
[ 0\. 0\. 0\. 0\. 0\. -1\. 1\. 0.]
[ 0\. 0\. 0\. 0\. 0\. 0\. -1\. 1.]
[ 0\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]]
计算正向操作 T x
> x = jnp.array([1,2,3,4,5,6,7,8])
> y = T.times(x)
> print(y)
[1\. 1\. 1\. 1\. 1\. 1\. 1\. 0.]
计算伴随运算 T^H x
> y = T.trans(x)
> print(y)
[-1\. -1\. -1\. -1\. -1\. -1\. -1\. 7.]
对角矩阵乘法算子
对角矩阵非常稀疏,基于线性算子的实现对它们来说是理想的。让我们建造一个:
> d = jnp.array([1., 2., 3., 4., 4, 3, 2, 1])
> T = lop.diagonal(d)
> print(lop.to_matrix(T))
[[1\. 0\. 0\. 0\. 0\. 0\. 0\. 0.]
[0\. 2\. 0\. 0\. 0\. 0\. 0\. 0.]
[0\. 0\. 3\. 0\. 0\. 0\. 0\. 0.]
[0\. 0\. 0\. 4\. 0\. 0\. 0\. 0.]
[0\. 0\. 0\. 0\. 4\. 0\. 0\. 0.]
[0\. 0\. 0\. 0\. 0\. 3\. 0\. 0.]
[0\. 0\. 0\. 0\. 0\. 0\. 2\. 0.]
[0\. 0\. 0\. 0\. 0\. 0\. 0\. 1.]]
应用它:
> print(T.times(x))
[ 1\. 4\. 9\. 16\. 20\. 18\. 14\. 8.]
> print(T.trans(x))
[ 1\. 4\. 9\. 16\. 20\. 18\. 14\. 8.]
在后台
所有线性运算符都被构建为命名元组运算符。见其文档此处。下面是一个基本的轮廓。
class Operator(NamedTuple):
times : Callable[[jnp.ndarray], jnp.ndarray]
"""A linear function mapping from A to B """
trans : Callable[[jnp.ndarray], jnp.ndarray]
"""Corresponding adjoint linear function mapping from B to A"""
shape : Tuple[int, int]
"""Dimension of the linear operator (m, n)"""
linear : bool = True
"""Indicates if the operator is linear or not"""
real: bool = True
"""Indicates if a linear operator is real i.e. has a matrix representation of real numbers"""
对角线线性算子的实现(如上所述)实际上非常简单:
def diagonal(d):
assert d.ndim == 1
n = d.shape[0]
times = lambda x: d * x
trans = lambda x: _hermitian(d) * x
return Operator(times=times, trans=trans, shape=(n,n))
其中功能_hermitian如下:
def _hermitian(a):
"""Computes the Hermitian transpose of a vector or a matrix
"""
return jnp.conjugate(a.T)
JAX 的伟大之处在于,当它实时编译 Python 代码时,它可以删除不必要的操作。例如,如果d是实向量,那么_hermitian是 NOOP,并且可以在编译期间被优化掉。[cr.sparse.lop](https://cr-sparse.readthedocs.io/en/latest/source/lop.html)中的所有操作符都是经过精心设计的,因此可以很容易地进行 JIT 编译。我们提供了一个实用函数[lop.jit](https://cr-sparse.readthedocs.io/en/latest/source/_autosummary/cr.sparse.lop.jit.html#cr.sparse.lop.jit)来快速包装带有[jax.jit](https://jax.readthedocs.io/en/latest/jax-101/02-jitting.html)的线性操作符的时间和传递函数。
T = lop.jit(T)
在此之后,T.times和T.trans操作将运行得更快(快一两个数量级)。
对于上面的正规方程,类似于A^H A的东西可以建模为一个函数:
gram = lambda x : T.trans(T.times(x))
其中假设T已经被创建并且在闭包中可用。
带线性算子的预处理共轭梯度
一旦我们手头有了线性算子的框架,它可以用来以 JAX 兼容的方式编写像预处理共轭梯度这样的算法(即它们可以被 JIT 编译)。该版本包含在[cr.sparse.opt.pcg](https://github.com/carnotresearch/cr-sparse/blob/master/src/cr/sparse/_src/opt/pcg.py)中。
CR-Sparse 包含了一个很好的使用线性操作符解决逆问题的算法集合。
压缩感测示例
我们考虑一个压缩感测示例,它由部分 Walsh Hadamard 测量、余弦稀疏化基础和基于 ADMM 的信号恢复组成。在压缩感知中,数据大小远小于模型大小。因此等式A x = b不成立。寻找解决方案需要额外的假设。一个有用的假设是寻找稀疏的x(即其大部分条目为零)。
这是我们感兴趣的n=8192样品的x信号。

非稀疏累积随机游走信号(类似于股票市场)
我们将使用类型 II 离散余弦正交基来模拟该信号。请注意,正常的 DCT 不是正交的。
Psi = lop.jit(lop.cosine_basis([n](https://docs.python.org/3/library/functions.html#int)))
在此基础上来看看信号是否稀疏:
alpha = Psi.trans(x)

x 在正交离散余弦基中的表示
很明显,离散余弦基中的大多数系数都非常小,可以安全地忽略。
接下来,我们引入结构化压缩感知算子,该算子在沃尔什哈达玛变换空间中获取x的测量值,但是仅保留少量的m=1024随机选择的测量值。输入x也可以在测量过程中随机置换。
*from jax import random
key = random.PRNGKey(0)
keys = random.split(key, 10)
# indices of the measurements to be picked*
p = random.permutation(keys[1], n)
picks = jnp.sort(p[:m])
*# Make sure that DC component is always picked up*
picks = picks.at[0].set(0)
*# a random permutation of input*
perm = random.permutation(keys[2], [n](https://docs.python.org/3/library/functions.html#int))
*# Walsh Hadamard Basis operator*
Twh = lop.walsh_hadamard_basis([n](https://docs.python.org/3/library/functions.html#int))
*# Wrap it with picks and perm*
Tpwh = lop.jit(lop.partial_op(Twh, picks, perm))
我们现在可以用操作员Tpwh在x上进行测量。测量过程也可能添加一些高斯噪声。
*# Perform exact measurement*
b = Tpwh.times(x)
*# Add some noise*
sigma = 0.2
noise = sigma * random.normal(keys[3], ([m](https://docs.python.org/3/library/functions.html#int),))
b = b + noise

使用基于 Walsh Hadamard 变换的结构化压缩感知矩阵对 x 进行随机测量。
我们现在可以使用包含在 CR-Sparse 中的 yall1 解算器从测量值 b 中恢复原始信号 x。
# tolerance for solution convergence
tol = 5e-4
# BPDN parameter
rho = 5e-4
# Run the solver
sol = yall1.solve(Tpwh, b, rho=rho, tolerance=tol, W=Psi)
iterations = int(sol.iterations)
#Number of iterations
print(f'{iterations=}')
# Relative error
rel_error = norm(sol.x-xs)/norm(xs)
print(f'{rel_error=:.4e}')
求解器在 150 次迭代中收敛,相对误差约为 3.4e-2。
让我们看看恢复的有多好。

完整的示例代码请参见这里的。
摘要
在本文中,我们回顾了线性运算符的概念以及与它们相关的计算优势。我们提出了一个使用 JAX 的线性算子的函数式编程实现。然后,我们研究了这些算子在压缩传感问题中的应用。我们可以看到,使用这种方法可以实现复杂的信号恢复算法,它完全符合 JAX 对 JIT 编译的要求。我们的目标是为 CR-稀疏中的逆问题提供广泛的算子和算法集合。
进一步阅读
用 Google ML 工具包实现活性检测

活体检测是在给定一组照片的情况下,区分一个活生生的人和一个人的静态图像的能力。当您想要使用面部生物识别进行身份验证时,这是一项重要的功能,可以防止攻击者用用户的照片欺骗系统。
一些供应商提供了作为商业软件的活性检测的良好实现。然而,根据应用和使用量的不同,它们的成本会很快变得令人望而却步。出于好奇和好玩,我想知道用免费提供的组件构建自己的解决方案有多难。
解决这个问题有几种方法。我选择的方法包括以下几个主要步骤:
- 检测人脸及其标志(参考点,如眼角、鼻子、嘴巴等。)在一个图像中
- 对一系列图像(帧)执行此操作,并使用帧之间的差异来确定它们是真人还是照片
第一部分是个难题,有几十年的研究。目前的技术水平涉及训练深度神经网络,这需要大型数据集和计算能力,所以我去寻找一个免费的实现。我能找到的最好的是谷歌的 ML Kit Face Detection ,它是免费使用的,可用于 Android 和 iOS 移动设备(“免费使用”部分有点棘手——见帖子末尾的注释)。我开始着手研究是否有可能基于它构建一个活性检测系统。
实验设置
第一步是运行 ML Kit 的示例应用程序,添加一些日志来从 API 的输出中捕获以下信息:
- 包围盒
- 轮廓点
- 欧拉角
- 微笑、左眼和右眼睁开的概率
我只进行了两项测试:一项是让我的真实面孔出现在摄像机前,另一项是让摄像机对准我在笔记本电脑屏幕上的照片。我将这两个实验称为“活的”和“不活的”。
在这次探索中,我只是将日志保存到文本文件中,并做了一些 grep/sed 转换,将我想要的数据转换成 CSV 和 JSON 文件。然后,我将它们加载到一个笔记本中,并添加了一些 Python 数据折磨技巧。
第一次尝试:轮廓点差异
我的第一个想法是查看不同帧中相同轮廓点之间的坐标差异。假设是,由于自然的运动,活动人脸中的点会比静态人脸中的点有更多的变化。
我首先使用欧拉 Z 角围绕边界框的中心点旋转所有点,然后在边界框边界内对它们进行归一化,以产生范围为[0,1]的浮点数。然后,我计算了一帧中同一点与其后续点之间的欧几里德距离(每帧正好有 133 个点,这些点似乎对应于一张脸的特定位置,并且总是以相同的顺序排列,所以可以直接比较它们),并逐帧求和。
结果如下:

连续帧中各点之间的距离总和
【悲伤长号】。看起来我的假设是不正确的,而且没有办法从这些信息中区分活体和非活体样本。也许算法是故意这样工作的,试图保持面部标志不变,尽管有运动。这让我想知道:一个人可以简单地通过索引和比较人脸上的这些点来实现面部识别吗?🤔
第二次尝试:欧拉角变化
接下来,我查看了库生成的欧拉角。有三个角度,对应于 3D 空间中的三个旋转轴:
- x 表示向上或向下看的脸
- y 表示向左或向右看的脸
- z 表示平行于图像平面的面部旋转,例如摇头晃脑
简单地绘制每一帧的角度就可以得到实时实验的结果:

现场实验中每一帧的欧拉角
这些是针对非实时情况的:

非实时实验中每帧的欧拉角
现在我们有进展了!在非实时情况下,Z 角度的变化似乎比其他角度大得多,这具有直观的意义:静态照片不能向上/向下或向左/向右看,但图片的角度可以改变。这可以通过查看标准偏差来确认:
- z 角度标准偏差(活): 0.680392
- z 角度标准偏差(非现场): 4.112100
X 和 Y 角度在现场情况下有更高的变化,但不是如此明显的差异(X 1.54 和 Y 1.10 现场,X 1.07 和 Y 1.06 非现场)。
因此,也许我们已经可以用 Z 角度标准差的简单阈值,或者 Z 与 X 或 Y 角度的 stddev 之比来检测活性。尽管如此,我不相信这是非常可靠的,所以我又尝试了一件事。
第三次尝试:睁眼和微笑的概率
ML Kit 可以检测一个人的眼睛是睁开还是闭上(独立于每个人)以及他们是否在微笑。这通过这些属性中的每一个的概率来表示。
为现场案件策划他们,我得到了这个:

真实案例中睁眼和微笑的概率
对于非实时情况:

非活体情况下睁眼和微笑的概率
头奖!微笑概率变化不大(看起来我的扑克脸不错😎),但对于非直播来说,眼睛睁开实际上是一条平坦的线,而对于直播来说则是过山车。
观察标准差可以证实这一点:
- 直播:左眼 0.158587 ,右眼 0.154239 微笑 0.006452
- 不直播:左眼 0.022749 ,右眼 0.003405 ,微笑 0.022245
这很有意义,因为眨眼是用于活性检测的最常见标记之一。一些系统甚至明确要求用户眨眼,但在这种情况下似乎没有必要——我没有有意识地试图眨眼,即使我没有眨眼,算法分配给我的眼睛睁开的概率也有明显的波动。
结论
看起来用 ML 工具包实现活性检测是可行的!我发现至少有两组变量提供了有意义的区分:旋转角度和睁眼概率,可能还有其他我没有发现的变量。当然,这只是一个初步的探索,每个案例都有一个单独的例子。为了确定可行性,我们需要至少几十个不同的人在不同的照明条件和硬件下的例子。有了这些例子,训练一个分类模型应该是相对简单的——像对标准差特征进行逻辑回归这样简单的事情看起来是可行的。
当然,为了保护有价值的资源,一个真正的活性检测系统需要对几种类型的利用策略进行严格的测试。我可能还会寻找额外的检查,比如基于纹理的算法。
如果你最终实现了这样的东西,我希望你能在评论中分享你的成果!
关于 ML Kit 许可证(或缺少许可证)的说明
ML 工具包显然是由谷歌提供的“免费使用”,但它不是开源的,我找不到实际的许可证——最接近的是这些术语。没有关于支持或责任的保证。而且,作为谷歌的产品,它被弃用的概率接近 100%——尤其是像这样的免费小众产品。
尽管如此,由于这是一个可下载的库,所有处理都是离线进行的——而不是依赖于谷歌基础设施的在线服务——即使他们停止更新,你也有可能继续使用一段时间。
您可以在本笔记本中查看用于生成该分析的代码。
实现机器学习算法来检测物联网流量中的攻击
本文阐述了通过使用随机森林分类器算法来检测物联网流量中发现的攻击和异常。
介绍
物联网基础设施中的攻击和异常检测是物联网领域日益关注的问题。由于物联网基础设施的使用越来越多,对这些基础设施的攻击也呈指数级增长。因此,有必要开发一个智能、安全的物联网环境,能够检测其漏洞。这里,提出了一种基于机器学习的解决方案,可以检测攻击类型并保护物联网系统。

Bermix 工作室在 Unsplash 拍摄的照片
解决方案的设计和工作流程
该解决方案的整个工作流程将在下面以图示的形式介绍。

物联网攻击检测解决方案的工作流程。作者图片
工作流程中涉及的步骤如下:
- 数据集收集和描述: 使用 分布式智能空间协调系统 ( DS2OS )创建虚拟物联网环境,该系统具有一组基于物联网的服务,如温度控制器、窗户控制器、灯光控制器等。用户和服务之间的通信被捕获并存储在 CSV 文件格式中。在数据集中,有 357,952 个样本和 13 个特征。该数据集包含 347,935 个正常数据和 10,017 个异常数据,并包含八个已分类的类。这 8 类攻击是拒绝服务(DoS)、数据类型探测、恶意控制、恶意操作、扫描、刺探、错误设置、正常。该数据集可以免费使用,并且可以在Kagglewebistehttps://www.kaggle.com/francoisxa/ds2ostraffictraces上找到。“mainsimulationaccesstraces . CSV”文件包含使用 pandas 库读取的数据集
import pandas as pd #Pandas library for reading csv file
import numpy as np #Numpy library for converting data into arrayDataset=pd.read_csv('mainSimulationAccessTraces.csv')
x=Dataset.iloc[:,:-2].values
y=Dataset.iloc[:,12].values
- 数据预处理:数据预处理的第一步是处理数据集中的缺失值。在数据集中,我们可以看到“访问的节点类型”列和“值”列包含由于数据传输过程中出现异常而导致的缺失数据。因为“访问的节点类型”列是分类类型,所以我将使用一个常数值来填充它。“值”列是数字类型的,因此我使用均值策略来填充缺失的值。下一步是特性选择,它包括删除对数据没有任何意义的时间戳特性。下一步涉及使用标签编码将名义分类数据转换成向量。
from sklearn.impute import SimpleImputer
imputer=SimpleImputer(missing_values=np.nan,strategy='constant',verbose=0)
imputer=imputer.fit(x[:,[8]])
x[:,[8]]=imputer.transform(x[:,[8]])imputer1=SimpleImputer(missing_values=np.nan,strategy='mean',verbose=0)
imputer1=imputer1.fit(x[:,[10]])
x[:,[10]]=imputer1.transform(x[:,[10]])from sklearn.preprocessing import LabelEncoder
labelencoder_X = LabelEncoder()
for i in range(0,10):
x[:,i] = labelencoder_X.fit_transform(x[:,i])
x=np.array(x,dtype=np.float)y=labelencoder_X.fit_transform(y)
- 采样:这个阶段包括将数据集分成训练和测试数据集。我已经分配了 80%的数据集用于训练,剩下的 20%用于测试。
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,random_state=0)
- 归一化:使用标准定标器库对训练和测试数据集进行归一化,使所有特征值在相似范围内。
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
x_train = sc.fit_transform(x_train)
x_test = sc.transform(x_test)
- 建立 ML 模型:将训练数据集传递给随机森林分类器算法进行训练,生成模型/预测器。我已经用 sklearn 库完成了这一步。我用了 10 棵树做模型。训练后,测试数据集被传递给预测器/模型,它将判断数据是否受到攻击。

凯文·Ku 在 Unsplash 上的照片
from sklearn.ensemble import RandomForestClassifier
classifier=RandomForestClassifier(n_estimators=10,criterion='entropy',random_state=0)
classifier.fit(x_train,y_train)y_pred = classifier.predict(x_test)
- 模型评估:最后一步是确定我们模型的准确性,我们已经使用混淆矩阵和准确性参数来确定性能。
使用随机森林算法,我得到了 99.37%的准确率。我还在下面附上了混淆矩阵的快照
from sklearn.metrics import confusion_matrix
from sklearn.metrics import accuracy_score
cm = confusion_matrix(y_test, y_pred)
accuracy=accuracy_score(y_test, y_pred)

混淆矩阵
结论
随机森林算法能够在虚拟物联网环境数据集上提供 99.37%的准确率。还可以在此基础上执行交叉折叠验证,以避免模型过度拟合。要了解有关用于生成数据的物联网环境的更多信息,请参考以下 URLhttps://www . research gate . net/publication/330511957 _ Machine _ Learning-Based _ Adaptive _ Anomaly _ Detection _ in _ Smart _ Spaces
实现 ML 系统教程:服务器端还是客户端模型?
编写小型 Flask 服务器与编写 WebDNN 浏览器模型

Krzysztof Kowalik 在 Unsplash 上的照片
开发机器学习模型很有趣,也很好,但开发完之后,你可能会考虑将它们部署到应用程序中来使用它们。问题是你应该把它们放在客户端(可能是手机)还是应该把模型放在服务器上,把数据发送给服务器,然后把结果拿回来?在这篇文章中,我将讨论这两种方法的利弊,以及这两种方法需要什么。
服务器端模型
服务器端机器学习模型是最广泛使用的 ML 系统,因为其架构实现起来更简单。例如,您在手机(客户端)上运行了一个对图像进行分类的应用程序。用户拍了一张照片,这张照片被翻译成字节,手机用这些字节向服务器发送 POST 请求。服务器对这些字节运行模型的预测功能,并将带有预测的 JSON 响应发送回移动设备。这里没什么特别的。
现在,如果您以这种方式实施您的 ML 系统,您将面临以下一些挑战:
- 延迟:如果您的应用需要密集的最大似然预测,如实时视频对象检测,您可能会在不充足的时间内获得结果,这将使用户体验非常不愉快。
- 成本:典型的 web 应用程序不需要配备高端 GPU 的昂贵机器来快速运行预测。然而,如果你在服务器上托管模型,你将需要一个具有强大 GPU 的服务器,这是非常昂贵的。例如,我看到过这样的例子,在 AWS EC2 实例上托管一个图像字幕模型的成本大约是每天 24 美元(每年大约 9000 美元)。这与典型的 web 应用程序相比非常昂贵。
因此,尽管这种方法可能更容易实现,但也有一些缺点需要考虑。当然,这取决于具体情况,因此测试您的系统并检查这些缺点是否真的会导致问题是值得的。
如果您对以这种方式实现 ML 系统感兴趣,我建议尝试 Python Flask 服务器。它们非常容易实现和开始使用。你可以在这里查看我的教程关于构建一个使用 Flask 的 ML messenger 聊天机器人。您实际上是从运行服务器的样板代码开始,然后添加一个如下所示的预测函数:
#Client sends a POST request with the image url[@app](http://twitter.com/app).route('/predict', methods=['POST'])
def predict(): if request.method == 'POST': data = json.loads(request.data)
url = data[0]["payload"]["url"]
image = requests.get(url) # Get the image from the url sent by the client
img = Image.open(BytesIO(image.content))
result = predictor_api.make_prediction(url)
return jsonify({'result': result})
客户端模型

这是事情开始变得更有趣的地方。有几种方法可以做到这一点(与服务器端模型不同),客户端模型更多的是一个正在进行的研究基础。两种最典型的客户端是浏览器或微控制器。我们将讨论浏览器,因为它们是更可能的客户端选项。
客户端 ML 模型当然比服务器端模型便宜得多,并且理论上它们应该具有更低的延迟,因为你不会向另一个服务器发送和接收请求,你将在一个地方(客户端)做所有的事情。然而,实际上由于硬件限制,延迟实际上会更大。而且,它们可能会变得更加难以实现,因为你需要进行大量的优化,以便 ML 系统能够顺利运行,这就是我们将要在这里讨论的。
在移动设备/浏览器上部署 ML 模型的主要问题是由于硬件和集成的限制。本质上,大多数在浏览器上运行的 web 应用程序通常不需要像预测那样的计算密集型操作(除了 web 应用程序上的游戏)。然而,最近有大量的优化被引入,使得客户端 ML 模型变得更加可能。
其中几个例子是:
- WebGL (浏览器实现OpenGL):web GL 在 2011 年已经发布(所以其并不算太新),但是对于 ML 机型来说还是挺有用的。WebGL 是一个 Javascript API,用于允许在浏览器上运行的任何应用程序平滑地交互和使用 GPU,通常用于渲染游戏中的图形和物理交互
- Web Assembly:Web Assembly 大约出现在 4 年前你大概能从关键词“Assembly”中感觉到它与编译和底层代码有关。WebAssembly 引入了一种将 web 应用程序编译成更紧凑的二进制格式的新方法,这种方法从本质上减小了它们的大小,并允许它们在浏览器上更流畅地运行。它通过定义一个以二进制格式https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md存储的抽象语法树 (AST)来实现这一点。在“Javascript 的 nitrous boost”之前已经调用过了。
- WebGPU :这本质上是在 WebGL 上的升级。WebGPU 是一个正在进行的项目,用于升级 WebGL 以优化浏览器上的 GPU 操作。简而言之,WebGPU 是一种新标准,它试图通过将标准分解为更多模块化的着色器组件来提高 WebGL 的核心性能,这些组件可以并发运行以获得更好的性能。
- WebDNN:WebDNN 是在浏览器上运行深度神经网络最好的开源库之一。WebDNN 的第一个组件是 Python API,它将模型转换成图形。然后,这个图被移交给第二个组件,该组件是一个 JS API,它将这个图移植成兼容的格式,以便在浏览器上运行(适用于 WebGL、Web Assembly 等..)
WebDNN 其实挺有意思的。我想研究一下它的使用和设置有多简单,所以下一节将是一个关于如何操作的小教程。第一个组件是 python API,它按照上面的讨论转换模型。请注意,这不是一个精确的一步一步的教程,这里的主要目的是大致演示如何做到这一点,因为一个精确的教程可能需要一篇完整的文章,如果你想让我这样做,请在评论中告诉我。
步骤 Python API:
# - Imports
from keras.applications import resnet50
from webdnn.frontend.keras import KerasConverter
from webdnn.backend import generate_descriptor# 1\. load model
model = resnet50.ResNet50(include_top=True, weights='imagenet')# 2\. Convert model to a graph
graph = KerasConverter(batch_size=1).convert(model)
exec_info = generate_descriptor("webgpu,webgl,webassembly,fallback", graph)# 3\. Save the graph & weights
exec_info.save("./output")
来源: WebDNN 文档
步骤 JavaScript API:
let runner, image, probabilities;
// Step 1: load the modelasync function init() {
// Initialize descriptor runner
runner = await WebDNN.load('./output');
image = runner.inputs[0];
probabilities = runner.outputs[0];
}// Step2: define a function that runs predicitionsasync function run() {
// Set the value into input variable.
image.set(await WebDNN.Image.getImageArray('./input_image.png'));
// Run
await runner.run();// Show the result
console.log('Output', WebDNN.Math.argmax(probabilities));
}
来源: WebDNN 文档
这看起来一点都不漫长也不复杂!在典型的客户端 ML web 应用程序中,您还需要一个额外的步骤,就是将模型集成到 chrome 扩展中。第一部分是在浏览器中持续运行的后台脚本,第二部分是注入每个运行页面的内容脚本。内容脚本负责将数据发送到后台脚本,以便在模型上运行预测。这实际上很容易实现。您需要一个函数来发送数据(例如,当一个按钮被点击时),一个函数来监听/接收数据。
这里有一个很好的例子:
// content script-1, sends a message
$('img').each(function() {
var img = $(this);
chrome.extension.sendMessage({
msg: "run_model",
image: img[0].currentSrc
});
};// background script-1, listens to the message and runs the prediction
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse){
if (request.msg == "run_model") {
chrome.tabs.query({active: true, currentWindow: true},
function(tabs) {
run(request.image, tabs[0].id)
}
)
}
}
)// background script-2, send predictions back
chrome.tabs.sendMessage(
tab_id,
{result: results}, function (response) {}
);// content-script-2, receive model output
chrome.extension.onMessage.addListenser(
function(request, sender, sendResponse){
var imgid = request.imgid;
var result = request.result
// use output here
}
)
来源: Github 示例项目
就是这样!我希望你喜欢这篇文章,并学到了一些东西。客户端模型是 ML 中一个有趣的领域,你可以期待我在将来发表更多这样的文章。
欢迎订阅我的时事通讯:
https://artisanal-motivator-8249.ck.page/5524b8f934
从头开始实现朴素贝叶斯
从头做起
只用 Python 和 NumPy 编写内部工作代码

我不喜欢“黑盒”。我个人有一种强烈的欲望想知道事情是如何运作的。我想摸摸它。我想修补一下。我想自己编码,即使已经存在一个即插即用的解决方案。这正是我们在这篇文章中要做的。
在接下来的部分中,我们将使用 Python 和 NumPy 一步一步地从头开始实现朴素贝叶斯分类器。
但是,在我们开始编码之前,让我们简单地谈谈朴素贝叶斯分类器的理论背景和假设。
朴素贝叶斯快速理论
朴素贝叶斯分类器的基本原理是贝叶斯定理——因此得名。在我们的例子中,我们可以将贝叶斯定理表述如下:

我们的总体目标是用给定的数据预测一个类的条件概率。这个概率也可以称为后验置信。那么我们如何计算后验概率呢?
首先,我们需要确定数据属于某一类分布P(Data|Class)的可能性。然后我们需要将它乘以之前的P(Class)。为了计算先验,我们需要计算特定类的样本数(行),然后除以数据集中的样本总数。
注意:为了简化计算,我们可以省略分母,因为
P(Data)可以被视为一个归一化常数。然而,我们将不再收到从零到一的概率分数。
你可能会问,朴素贝叶斯有什么天真的?
朴素贝叶斯的一个重要假设是特征的独立性。这意味着,一个事件的发生不会影响另一个事件的发生。因此,这些特性之间的所有交互和关联都将被忽略。由于这个简化的前提,我们现在能够在计算具有多个特征的某个类的概率时应用乘法规则。
这基本上是我们从零开始实现朴素贝叶斯分类器所需要知道的全部。
概述
现在,我们简要地讨论了理论背景,我们可以考虑我们需要实施的不同步骤。这为我们提供了一个高层次的概述,我们可以将其用作某种蓝图。
- 拟合:计算(训练)数据集中每个类的汇总统计和先验
- 预测:计算(测试)数据集中每个样本的每个类的概率。因此,获得给定类别(高斯)分布的数据的概率,并将其与先验结合。
在下面,我们将只实现一个类。下面可以看到的骨架代码,我们将在下一节逐步完成。
从头开始实施
拟合数据
如概述中所述,我们需要计算每个类(和特性)以及先验的汇总统计数据。
首先,我们需要收集数据集的一些基本信息,并创建三个零矩阵来存储每个类的均值、方差和先验。
接下来,我们迭代所有的类,计算统计数据并相应地更新我们的零矩阵。
例如,假设我们的数据集中有两个唯一的类(0,1)和两个要素。因此,存储平均值的矩阵将具有两行和两列(2x2)。每个类一行,每个功能一列。
先验只是一个向量(1x2),包含单个类别的样本除以总样本量的比率。

汇总统计数据示例[图片由作者提供]
做一个预测
现在,稍微复杂一点的部分…
为了进行预测,我们需要获得数据属于某一类的概率,或者更具体地说,来自同一分布的概率。
为了方便起见,我们假设数据的基本分布是高斯分布。我们创建一个类方法,它返回新样本的概率。

高斯函数。μ =均值;σ =方差;σ =标准偏差。
我们的方法接收单个样本并计算概率。然而,从参数中我们可以看出,我们还需要提供平均值和方差。
因此,我们创建另一个类方法。该方法遍历所有类,收集汇总统计数据、先验信息,并为单个样本计算新的后验置信。
请注意,我们应用了对数变换,以便通过增加概率来简化计算。我们还返回具有最高后验置信度的类索引。
最后,我们可以用 predict 方法把它们联系起来。
测试我们的分类器
现在我们已经完成了朴素贝叶斯分类器,只剩下一件事要做了——我们需要测试它。
我们将使用虹膜数据集,它由 150 个样本组成,具有 4 个不同的特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度)。我们的目标是在 3 种不同类型的虹膜中预测正确的类别。

通过绘制前两个特征对虹膜数据集进行概述[图片由作者提供]
通过运行上面的代码,我们加载并准备虹膜数据集,并训练我们的分类器。在对测试数据进行预测时,我们达到了大约 96.6%的准确率。
下面的混淆矩阵告诉我们,我们的分类器犯了一个错误,错误地将一类分类为二类。

混淆矩阵(预测数量)[图片由作者提供]
结论
在本文中,我们仅使用 Python 和 NumPy 从头实现了一个朴素贝叶斯分类器。我们了解了理论背景,并有机会以实际方式应用贝叶斯定理。
如果你不喜欢“黑盒”并且想要完全理解一个算法,从头开始实现它是获得关于内部工作的亲密和深入知识的最好方法之一。
你可以在我的 GitHub 上的这里找到完整的代码。

从零开始的 ML 算法
View list6 stories


喜欢这篇文章吗?成为 中级会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@marvinlanhenke/membership
使用 pytesseract 实现光学字符识别(OCR)
我们如何将图像转换成机器编码的文本?

来源:维基百科
各位 python 爱好者,我想和你们分享一个简单但非常有效的 OCR 服务,它使用 pytesseract,并通过 Flask 提供 web 界面。
光学字符识别(OCR)可用于多种用途,例如用于支付目的的信用卡扫描,或将文档的.jpeg扫描转换为.pdf
没有出卖使用 OCR 的想法,或者如何使用它,让我们开始编码过程!
先决条件
我们将使用 pytesseract(用于 OCR)和 Flask(用于 web 界面)
pip install pytesseractfrom PIL import Image
import pytesseract
注意:如果你在导入宇宙魔方时遇到一些问题,你可能需要下载并安装 pytesseract.exe,可以在这里找到。
安装后,您必须包含 pytesseract 可执行文件的路径,这可以通过一行代码完成:
pytesseract.pytesseract.tesseract_cmd = r'YOUR-PATH-TO-TESSERACT\tesseract.exe'
核心 OCR 功能
因为我们已经安装并导入了 pytesseract,所以让我们创建核心函数并检查它是否按预期工作:
def ocr_core(filename):
text = pytesseract.image_to_string(Image.open(filename))
return text
很简单,对吧? ocr_core 函数获取图像文件的路径并返回其文本组件。根据文档,image_to_string 函数使用英语作为默认识别语言,具体语言我们稍后再说。

此图片与 ocr_core 函数【作者图片】一起使用
结果如下:

ocr_core 函数将我们的图像作为字符串返回,耶!【作者图片】
一旦我们确定一切正常,就该为我们的 OCR 服务创建一个 web 界面了
使用 Flask 的 Web 界面
现在我们要创建一个简单的 html 表单,允许用户上传一个图像文件,并从中检索文本。
from flask import Flask, render_template, requestapp = Flask(__name__)# from ocr_core import ocr_core
# uncomment the line above, if your Flask fails to get access to your function, or your OCR & Flask are on different scripts
保持简单,让我们为上传的文件创建一些后端测试,例如允许的扩展名:
ALLOWED_EXTENSIONS = ['png', 'jpg', 'jpeg']def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
保持它的美丽和简单,就像《Python 的禅》建议的那样,对吗?
现在,让我们创建一个保存用户上传图像的路径:
import ospath = os.getcwd()UPLOAD_FOLDER = os.path.join(path, 'uploads\\')if not os.path.isdir(UPLOAD_FOLDER):
os.mkdir(UPLOAD_FOLDER)app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
如果您想知道我们为什么要导入os : 我们将需要它来获取上传图像的路径(用于后端),并且在用户成功上传文件后为我们的<img>标记创建一个源。如果你想特别指定一个文件夹,只需将os.getcwd()更改为你想使用的任何路径。
如果你从未创建过“上传”文件夹,上面的代码也会为你创建(用爱)。
基于 HTML 表单的 web 界面
现在,让我们构建一个简单的表单,并将文件命名为upload.html
<!DOCTYPE html>
<html>
<head>
<title>OCR</title>
</head>
<body>
{% if msg %}
<h1 style="color: green"> {{ msg }} </h1>
{% endif %}<h1> Upload your file </h1><form method=post enctype=multipart/form-data>
<input type="file" name=file>
<input type="submit" name=Upload value="Upload">
</form>{% if extracted %}
<h1> Results: </h1>
{% endif %}{% if img_src %}
<img src="{{ img_src }}">
{% endif %}{% if extracted %}
<p> {{ extracted }} </p>
{% endif %}</body>
</html>
等等,为什么有大括号?嗯,不是完全 HTML 形式,我撒了个谎。实际上,这是 jinja 格式化,它允许我们相应地格式化我们的 HTML。这里没什么特别的,{% if %}代表 if 语句,所以如果有什么消息,就会被包裹在<h1>标签里,等等。
这个表单非常简单,但是不要忘记enctype=multipart/form-data允许表单接受文件上传。
一旦后端进入操作,花括号内的所有变量就会出现,所以在上传任何东西之前,您的upload.html应该是这样的:

你的upload.html should look like this. 【图片由作者提供】
重要提示:不要忘记将你的“upload.html”文件保存到与你的主 python 脚本相同的路径,不管你是在中心还是本地计算机上工作。如果你不知道你的工作目录在哪里,参考上面的
os.getcwd()。
最终确定 OCR web 服务
一旦我们完成了以上所有的准备工作,就该通过 Flask 创建一个 web 界面了:
[@app](http://twitter.com/app).route('/', methods = ['GET', 'POST'])def upload_page():
if request.method == 'POST':
if 'file' not in request.files:
return render_template('upload.html', msg = 'No file selected')
file = request.files['file']if file.filename == '':
return render_template('upload.html', msg = 'No file')if file and allowed_file(file.filename):
file.save(os.path.join(app.config['UPLOAD_FOLDER'], file.filename))
extracted = ocr_core(file)
return render_template('upload.html',
msg = 'OCR completed',
extracted = extracted,
img_src = UPLOAD_FOLDER + file.filename)
else:
return render_template('upload.html')
if __name__ == '__main__':
app.run()
首先,我们检查请求方法是否为POST,主要是为了安全措施。然后,我们必须检查文件是否被选择和上传(换句话说,文件是否到达后端)。一旦入门级测试完成,我们检查file是否存在,以及它的格式是否被允许。
一旦满足所有这些条件,我们将上传的文件保存到上传文件夹,并将其赋给一个名为extracted的变量
运行代码后,您应该会看到应用程序运行的地址:

应用程序运行的地址。【作者图片】
所以在上传之前,你的界面应该是这样的:

上传前的 App。【作者图片】
一旦您选择了您的文件,它应该会有如下的细微变化:

上传图片后的 App。【作者图片】
瞧啊。您刚刚创建了一个 OCR web 服务!
最后的想法
正如承诺的那样,我们已经创建了一个简单但非常有效的 OCR web 服务,它允许从图像中识别文本。但是有很多方法可以改善它。例如,您可以添加选项来选择 OCR 应该使用哪种语言。后端和前端都是这样:
def ocr_core(filename):
text = pytesseract.image_to_string(Image.open(filename), lang=selected_language)
return text
你所要做的就是在ocr_core函数中指定lang属性。并在您的upload.html文件中添加一个<select>标签。只是不要忘记从前端获取数据,并将其传递给你的ocr_core函数

未来的改进。【图片作者】
希望这篇文章证明对你有用,并使一些事情变得更清楚。感谢您的时间,如果您有任何反馈或任何问题,请随时在下面留下评论。
再见。
从零开始用 Viterbi 算法实现英语单词的词性标注
词类是句子结构和意义的有用线索。以下是我们识别它们的方法。
本文的完整代码可以在这里找到
词性标注是为文本中的每个单词分配词性的过程,这是一项消除歧义的任务,单词可能有多个词性,我们的目标是找到适合这种情况的正确标签。
我们将使用一个经典的序列标记算法, 隐马尔可夫模型 来演示,序列标记是一个任务,其中我们给输入单词序列中的每个单词 x1 分配一个标签 y1,因此输出序列 Y 与输入序列 x 具有相同的长度。HMM 是一个基于扩充马尔可夫链的概率序列模型。马尔可夫链做了一个很强的假设,如果我们想预测序列中的未来,唯一重要的是当前状态。例如,预测我下周写一篇文章的概率取决于我本周写一篇文章,仅此而已。你可以观看视频这里更多的例子,我会说一个更详细的解释。
隐马尔可夫模型允许我们谈论观察到的事件——我们在 put 中看到的单词和隐藏事件——我们认为是概率模型中偶然因素的部分语音标签。
我们将使用 Brown Corpus 来实现我们的 tagger,其中每个文件包含标记化单词的句子,后跟 POS 标签,每行包含一个句子。您可以在这里找到描述标签的数据手册。注意,我们将使用二元模型 HMM 实现我们的 POS 标记器。
我们可以在下面看到我们的数据样本:
[('RB', 'manifestly'), ('VB', 'admit')]
首先,让我们创建一个生成 n 元文法的函数。
def ngrams(self, text, n):
n_grams = []
for i in range(len(text)): n_grams.append(tuple(text[i: i + n]))
return n_grams
转移概率

作者图片
一个 HMM 有两个组成部分,即 跃迁概率 A 和 发射概率 B 。

作者图片
转移概率是给定前一个标签时标签出现的概率,例如,动词 将 最有可能后跟另一种形式的动词,如 舞 ,因此它将具有高概率。我们可以使用上面的等式来计算这个概率,实现如下:
这里,我们将二元模型的计数除以我们创建的每个二元模型的一元模型计数,并将其存储在 transition_probabilities 字典中。
排放概率

作者图片
发射概率是给定标签,它将与给定单词相关联的概率。我们可以使用上面的等式来计算这个概率,实现如下:
在这里,我们将该单词后面的标签的计数除以同一标签的计数,并将其存储在 emission_probabilities 字典中。
HMM taggers 做了两个进一步简化的假设。第一个假设是,一个单词出现的概率只取决于它自己的标签,而与相邻的单词和标签无关;第二个假设,二元模型假设,是一个标签的概率只取决于前一个标签,而不是整个标签序列。将这两个假设代入我们的 bigram 标记器,得到最可能的标记序列的以下等式:

作者图片
解码 HMM
对于任何模型,例如包含隐藏变量(词性)的 HMM,确定对应于观察序列的隐藏变量序列的任务称为解码,这是使用 维特比算法 完成的。
维特比算法
维特比算法是一种动态规划 算法,用于获得最有可能的隐藏状态序列的最大后验概率估计—称为维特比路径——其产生一系列观察到的事件,特别是在马尔可夫信息源和隐藏马尔可夫模型 (HMM)的环境中。此外,一个很好的解释视频可以找到这里
维特比解码有效地从指数多的可能性中确定最可能的路径。它通过查看我们的传输和发射概率,将这些概率相乘,然后找到最大概率,从而找到一个单词相对于所有标签的最高概率。我们将为未知概率定义一个默认值0.000000000001。
我们将从计算初始概率/该状态的开始概率开始,这是单词开始句子的概率,在我们的例子中,我们使用了“开始”标记
def initial_probabilities(self, tag):
return self.transition_probabilities["START", tag]
测试
为了测试我们的解决方案,我们将使用一个已经分解成单词的句子,如下所示:
test_sent = ["We",
"have",
"learned",
"much",
"about",
"interstellar",
"drives",
"since",
"a",
"hundred",
"years",
"ago",
"that",
"is",
"all",
"I",
"can",
"tell",
"you",
"about",
"them",
]cleaned_test_sent = [self.clean(w) for w in test_sent]
print(self.vertibi(cleaned_test_sent, all_tags))
我们的结果一:
we,PPSS
have,HV-HL
learned,VBN
much,AP-TL
about,RB
interstellar,JJ-HL
drives,NNS
since,IN
a,AT
hundred,CD
years,NNS
ago,RB
that,CS
is,BEZ-NC
all,QL
i,PPSS
can,MD
tell,VB-NC
you,PPO-NC
about,RP
them,DTS
根据我们的文档,这是正确的。
我期待听到反馈或问题。
从头开始实施 PCA
将实现与 Scikit-Learn 的 PCA 进行比较

本文是故事的续篇主成分分析变量约简。在上一篇文章中,我谈到了一种最著名和最广泛使用的方法,称为主成分分析。它采用高效的线性变换,在获取最大信息量的同时降低高维数据集的维数。它生成主成分,即数据集中原始要素的线性组合。此外,我一步一步地展示了如何用 Python 实现这项技术。起初我认为这篇文章足以解释 PCA,但我觉得缺少了一些东西。我使用单独的代码行实现了 PCA,但是当您每次想为不同的问题调用它们时,它们是低效的。更好的方法是创建一个类,当您想在一个地方封装数据结构和过程时,这是很有效的。此外,由于所有代码都在这个独特的类中,修改起来真的很容易。
目录:
1.资料组
在实现 PCA 算法之前,我们将导入乳腺癌威斯康星数据集,该数据集包含关于在 569 名患者中诊断出的乳腺癌的数据
。
*import* pandas *as* pd
*import* numpy *as* np
*import* random
*from* sklearn.datasets *import* load_breast_cancer
import plotly.express as pxdata = load_breast_cancer(as_frame=True)
X,y,df_bre = data.data,data.target,data.frame
diz_target = {0:'malignant',1:'benign'}
y = np.array([diz_target[y1] for y1 in y])
df_bre['target'] = df_bre['target'].apply(lambda x: diz_target[x])
df_bre.head()

作者插图。
我们可以注意到有 30 个数字特征和一个目标变量,指定肿瘤是良性的(目标=1)还是恶性的(目标=0)。我将目标变量转换为字符串,因为 PCA 不使用它,我们只在以后的可视化中需要它。
在这种情况下,我们希望了解肿瘤是良性还是恶性时,其特征可变性的差异。这真的很难用简单的探索性分析来显示,因为我们有两个以上的协变量。例如,我们可以设想一个只有六个特征的散布矩阵,用目标变量来着色。
fig = px.scatter_matrix(df_bre,dimensions=list(df_bre.columns)[:5], color="target")
fig.show()

作者插图。
当然,我们可以在所有这些散点图中观察到两个不同的集群,但是如果我们同时绘制所有的特征,这将是混乱的。因此,我们需要这个多元数据集的一个紧凑的表示,它可以由主成分分析提供。
2.认证后活动的实施

PCA 程序的框图。作者插图。
上图总结了获取主成分(或 k 维特征向量)的步骤。将应用相同的逻辑来构建该类。
我们定义了PCA_impl类,它在开始时初始化了三个属性。最重要的属性是我们想要提取的组件的数量。此外,我们还可以通过设置random_state等于 True 并仅在需要时标准化数据集来每次重现相同的结果。
这个类也包括两个方法,fit和fit_transform,类似于 scikit-learn 的 PCA。虽然第一种方法提供了计算主成分的大部分程序,但fit_transform方法也对原始特征矩阵 x 进行了变换。除了这两种方法,我还想可视化主成分,而无需每次指定 Plotly 表达式的函数。加速 PCA 产生的潜在变量的分析是非常有用的。
3.未经标准化的 PCA
最后,定义了PCA_impl类。我们只需要毫不费力地调用类和相应的方法。

作者插图。
我们可以访问在fit和fit_transform方法中计算出的var_explained和cum_var_explained属性。值得注意的是,我们只用一个组件就捕获了 98%。让我们使用之前定义的方法来可视化 2D 和 3D 散点图:
pca1.pca_plot2d()

作者插图。
从可视化中,我们可以观察到出现了两个聚类,一个用蓝色标记,代表患有恶性癌症的患者,另一个关于良性癌症。此外,蓝色星团似乎比其他星团包含更多的可变性。此外,我们看到两组之间有轻微的重叠。
pca1.pca_plot3d()

作者插图。
现在,我们来看看包含前三个部分的 3D 散点图。它没有之前的散点图那么清晰,但即使在这个图中也出现了类似的行为。基于目标变量,肯定有两个不同的组。通过观察这种三维表示,我们发现了新的信息:两名恶性肿瘤患者相对于所有其他患者而言,似乎具有完全不同的价值观。这一点在我们之前展示的 2D 图或散点图中可以稍微注意到。
4.标准化的 PCA
让我们重复上一节的相同过程。我们只在开始时添加标准化,以检查结果中是否有任何差异。

作者插图。
与前一种情况不同,我们可以注意到关于主成分的值的范围更受限制,并且解释的方差的 80%被三个成分捕获。特别地,第一成分的贡献从 0.99 变为 0.44。这可以通过以下事实来证明:所有变量都具有相同的度量单位,因此,PCA 能够对每个特征赋予相同的权重。
pca1.pca_plot2d()

作者插图。
通过查看带有前两个分量的散点图,可以确认这些观察结果。聚类更加明显,并且具有更低的值。
pca1.pca_plot3d()

作者插图。
3D 表示更容易阅读和理解。最后,我们可以得出结论,两组患者具有不同的特征变异性。此外,仍有两个数据点与其余数据分开。
5.带 Sklearn 的 PCA
此时,我们可以应用 Sklearn 实现的 PCA 与我的实现进行比较。我应该指出,在这种比较中有一些差异需要考虑。虽然我的 PCA 实现基于协方差矩阵,但 scikit-learn 的 PCA 涉及输入数据的居中,并采用奇异值分解将数据投影到更低维的空间。
之前我们看到标准化是应用 PCA 前非常重要的一步。由于 Sklearn 的算法已经从每个特征的列中减去了平均值,所以我们只需要将每个数值变量除以它自己的标准偏差。
X_copy = X.copy().astype('float32')
X_copy /= np.std(X_copy, axis=0)
现在,我们将组件数量和 random_state 传递给 PCA 类,并调用fit_transform方法来获取主组件。

作者插图。
sklearn 的 PCA 实现了与标准化实现的 PCA 相同的结果。
fig = px.scatter(components, x=0, y=1, color=df.label,labels={'0': 'PC 1', '1': 'PC 2'})
fig.show()

作者插图。
fig = px.scatter_3d(components, x=0, y=1,z=2, color=df.label,labels={'0': 'PC 1', '1': 'PC 2','2':'PC 3'})
fig.show()

作者插图。
同样,散点图复制了我们在上一节中看到的内容。
最终想法:
我希望这篇文章对你有用。本文的目的是提供主成分分析的一个更紧凑的实现。在这种情况下,我的实现和 Sklearn 的 PCA 提供了相同的结果,但如果您使用不同的数据集,有时它们可能会略有不同。这里的 GitHub 代码是。感谢阅读。祝您愉快!
参考文献:
[1] 乳腺癌威斯康星州(诊断)数据集
你喜欢我的文章吗? 成为会员 每天无限获取数据科学新帖!这是一种间接的支持我的方式,不会给你带来任何额外的费用。如果您已经是会员, 订阅 每当我发布新的数据科学和 python 指南时,您都会收到电子邮件!
从头开始实施 PCA
从头做起
仅用 Python 和 NumPy 来提高您的线性代数技能

Pawel Czerwinski 在 Unsplash 上的照片
我一直对简化数据的不同方式感到惊讶,这些方式使数据更容易获取和分析。这有时感觉像是巫术——简单地应用一个算法,它就以某种方式完成了工作。
主成分分析(PCA)就是其中一种算法。这是一种无监督的学习算法,目的是通过将一大组特征转换为一个较小的特征来降低维度,同时保留尽可能多的信息。
在本文中,我们将通过从头开始实现 PCA 来揭开一些巫术的神秘面纱。我们将使用 Python 和 NumPy 一步一步地完成这项工作。
概述
在直接进入实现细节之前,让我们从高层次上了解一下算法实际上是如何工作的。
主成分分析主要包括三个步骤:
- 首先,我们需要计算协方差矩阵。
- 一旦我们获得这个矩阵,我们需要使用特征分解来分解它。
- 接下来,我们可以基于特征值选择最重要的特征向量,以最终将原始矩阵投影到其降维的维度中。
在接下来的部分中,我们将仔细研究每个步骤,在一个单独的类中实现 PCA。下面我们已经可以看一下骨架类,它为我们提供了某种蓝图。
从头开始实施
计算协方差矩阵
如概述中所述,我们的第一步主要涉及协方差矩阵的计算。那么我们为什么需要这样做呢?
粗略地说,协方差矩阵告诉我们两个随机变量一起变化了多少。如果协方差为正,则两个变量相关,因此沿相同方向移动(增加或减少)。如果协方差是负的,那么变量是反向相关的,意味着它们向相反的方向移动(例如,一个在增加,而另一个在减少)
假设我们有一个包含三个要素的数据集。如果我们计算协方差矩阵,我们将得到一个 3x3 的矩阵,包含每一列与所有其他列及其自身的协方差。

协方差矩阵的例子[图片由作者提供]
当应用特征分解时,协方差矩阵将是我们的核心,允许我们选择主向量或主方向,这解释了我们数据集中的大多数方差。
现在我们知道了什么是协方差矩阵,我们仍然需要知道如何计算它。计算矩阵的公式可以表述如下:

如果我们预先将数据居中,我们可以分别省略、【x-bar】、、【y-bar】、的减法。简化我们的方程,用线性代数符号表示,我们得到如下结果:

其简单地是以平均值为中心的数据矩阵本身除以样本数量的点积。
有了这些新知识,我们就可以实现前两个类方法了。
注:我们通过减去平均值并除以标准差来标准化数据。这会将所有要素转换到相同的比例,从而为分析提供同等的贡献。也可以使用 NumPy 函数 numpy.cov(x)计算协方差矩阵。
分解协方差矩阵
我们实现的下一步主要关注协方差矩阵的分解,或者更具体地说,是特征分解。
特征分解描述了将矩阵分解为个特征向量和个特征值。特征向量为我们提供了关于数据方向的信息,而特征值可以解释为系数,告诉我们每个特征向量中包含多少方差。
长话短说,如果我们分解我们的协方差矩阵,我们获得特征向量,它解释了我们数据集中的大部分方差。我们可以用这些向量将原始矩阵投影到一个更低的维度。
那么我们如何分解协方差矩阵呢?
幸运的是,我们可以简单地依赖内置的 NumPy 函数,因为特征值和特征向量的‘手动’计算相当繁琐。我们唯一要做的事情,就是对特征值和特征向量进行相应的排序,允许我们根据指定的分量数选择最重要的特征向量。
投射并把它们放在一起
到现在为止,我们已经完成了大部分工作。
通过选择最重要的特征向量,我们现在能够将原始矩阵投影到低维空间中。这是通过取矩阵和特征向量的点积来实现的,这也可以解释为简单的线性变换。
在实现了剩下的两个方法之后,我们的最终类如下所示:
测试我们的实现
现在我们已经完成了我们的实现,只剩下一件事要做——我们需要测试它。
出于测试目的,我们将使用虹膜数据集,该数据集由 150 个样本组成,具有 4 个不同的特征(萼片长度、萼片宽度、花瓣长度、花瓣宽度)。让我们通过绘制前两个特征来看看原始数据。

通过绘制前两个特征对虹膜数据集进行概述[图片由作者提供]
我们现在可以实例化我们自己的 PCA 类,并将其放在数据集上。当运行下面的代码时,我们得到下面的结果。

投影到前两个主成分上的虹膜数据集[图片由作者提供]
通过应用 PCA,我们清楚地解开了一些类关系,并且更清楚地分离了数据。这种低维数据结构应该使分类任务变得容易得多。
结论
在本文中,我们从零开始用一种更加手动的方法实现了主成分分析。我们学习了主要的计算步骤,包括一些非常重要的线性代数概念。
主成分分析是一种简单而有效的方法来减少,压缩和解开高维数据。从零开始理解和实现该算法提供了一个很好的方法来构建强大的基础和修改我们的线性代数知识,因为它将许多重要的概念联系在一起。
你可以在我的 GitHub 上的这里找到完整的代码。

从零开始的 ML 算法
View list6 stories


喜欢这篇文章吗?成为 中级会员 继续无限学习。如果你使用下面的链接,我会收到你的一部分会员费,不需要你额外付费。
https://medium.com/@marvinlanhenke/membership
实现随机森林

雨果·德劳尼在 Unsplash 上拍摄的照片
在这篇文章中,我们将介绍随机森林背后的基本概念,讨论实现的实际问题,例如高度相关的特性、特性稀疏性、不平衡的类。然后,比较随机森林与 Boosting 树和决策树的概念和性能!如果你想讨论什么或发现错误,随时给我发电子邮件到 lvqingchuan@gmail.com:)
基本概念

构建随机森林
森林是如何建成的?随机森林通过 bagging(bootstrapping-aggregation 的简称)建立在决策树上。“随机性”来自两个部分:
- Bootstrapping 是一种通过重新采样来评估建模精度的通用工具。在随机森林的框架下,使用 bootstrapping 在每棵树之间创建随机变量。作为第一步,我们用替换从训练数据中取样。每个样本都具有与原始训练数据相同的数据点。我们用每个样本建立一棵树。为什么要替换取样?实际上,没有替换的采样可能以每棵树只有非常少的数据结束,例如,从 5,000 个数据点构建 100 棵树,每棵树只给我们 5 个数据点。这意味着高偏差。理论上,替换抽样有助于保持随机森林的低方差,并防止过度拟合。
- 为每个决策树随机选择部分特征。这再次增加了树之间的随机变化,并防止过度拟合。经验法则是从 sqrt(特征数量)开始,然后通过优化器(如梯度搜索)调整每棵树的随机特征数量。
一旦构建了树,我们通过每棵树输入一个数据点,并对分类问题进行多数投票,或者对回归问题计算每棵树输出的平均值。这叫做聚合。
边条:装袋和袋装
如上所述,Bagging 是一种集成方法(引导,然后聚合)。你不必在装袋系统中使用一棵树。然而,装袋的树使用了树的所有特征。
高度相关的特征
特征非常相似的情况下还能用随机森林吗?看情况。具有高相关性的特征可能在因果推理研究中引起问题。如果让随机森林通过移除多少杂质来评估要素的重要性,两个高度相关的要素的排名将会非常不同,因为第一个要素移除的杂质不会被第二个要素再次移除。因此,报告的第二个特征的重要性将明显低于第一个特征,尽管它们在与目标变量的关系方面密切相关。这种情况可以通过随机选择特性来稍微改善,但不能完全解决。此外,高相关性可能不是机器学习研究中的问题。当我们的目标是进行预测时,特征之间的高度相关性很少会损害预测分数。友情提示:当你有很多冗余特性和一个大数据集时,运行时间会很长。
特征稀疏度/密度
特征稀疏/密度意味着数据集不平衡,例如,零(一)个值支配二进制特征称为稀疏(密集)。特征稀疏/密度是一个严重的问题,对随机森林的性能有负面影响。在零值支配二进制特征的例子中,树将朝着零的方向生长。无信息分割导致运行时间长、树深度大和错误率高。


种植一棵有偏见的树
如果不考虑运行时,构建更多的树可能有助于解决这个问题,因为每棵树都有另一组随机选择的特性。但是,不建议使用这种方法,因为随机森林在设计上容易变得稀疏/密集。如果您喜欢使用树算法,XGBoost 对稀疏/密集数据不敏感,也值得一试。
不平衡的班级
类别不平衡意味着一个或几个类别占了大部分样本。在极端情况下,您预测的是一个二进制类,而几乎所有的训练样本都属于零类。然后,您的决策树可能会预测所有训练样本都落在零类中,并在训练阶段达到“假的”(或“夸大的”)高精度。当看不见的测试数据确实经常落在一个类中时,这可能会给你带来麻烦。您也可以将这种极端情况推广到多标签分类。
要检测不平衡的类是否会提高模型性能:
- 使用修改后的准确度分数。通常,准确度=正确预测的样本数/总样本数。要使其反映出阶级不平衡的影响,用精度* = 1/2 (正预测精度+负预测精度)。当你的分类器在每个类上表现得不一样好时(换句话说,多数类膨胀了模型性能),修改后的精度将低于常规精度;当你没有不平衡的阶级问题时,准确度* =准确度。
要解决这个问题,你有两个简单的方法:
- ' class_weight '参数:这是一个可选的超参数,供您在决策树或随机森林模型中指定。对于不平衡的类,你可以设置' class_weight' = 'balanced '此模式自动调整输入数据中与类别频率成反比的权重,如 n _ samples/(n _ classes * NP . bin count(y))。
- 对代表性不足的类别进行过采样:通过制作属于少数类别的样本的副本,我们增加了少数类别的类别频率,从而朝着 50/50 的类别频率分割移动(用于二进制分类)。然而,我们需要确保原始数据没有偏见。再次考虑一个极端的二元情况,当我们在 A 类中有 10,000 个样本,在 B 类中有 10 个样本时,我们希望确保这 10 个样本确实包含关于 B 类人口的信息。事实上,10 个样本的特征很少能提供 B 类的所有信息;他们可能只是局外人。
随机森林和决策树
随机森林是一种基于决策树的集成方法。通过为每棵树引导样本和随机选择的特征,然后在所有树的最终输出上聚集决策(多数投票或平均),随机森林应该比决策树更好地处理过拟合问题和噪声。从统计学导论课程中,我们知道:
均方差=方差+偏差。
在大多数情况下,随机森林能够减少方差(过拟合)部分的误差。在我的沃尔玛食品销售预测和爱荷华州房价预测项目中,Random Forest 确实胜过决策树。但是决策树能胜过随机森林吗?的确是的!由于随机森林专注于解决上述公式的“方差”部分,您可以想象,当涉及到对没有太多噪声的训练数据进行预测时,决策树不一定表现得更差-其偏差不必比随机森林设计得更高。另一个原因是随机森林的性能随着树的数量而稳定。换句话说,当你有一个非常大的训练数据集和非常少的特征时,一个决策树就足够了。(再次考虑一个极端的例子,你有 10,000 个样本和两个特征。你的随机森林能在这里施展什么魔法?)
随机森林和梯度增强树
Boosting 是一个连续的过程,不断更新前一阶段弱分类器的权重,最终得到一个稳定的最大化分类器集合。把分类器想象成分区,你给那些样本应该在别处的分区分配更多的权重。构建 M 个梯度提升树序列的步骤:
- 初始化一个最优常数模型:单个终端节点树,每个样本的初始权重为 1/N,假设总共有 N 个样本。

- 对于接下来的每棵树:
1.为 N 个采样点中的每一个计算梯度下降(因此梯度提升非常耗时:/);然后,用梯度下降参数拟合树。

2.用当前树计算最小化损失函数的权重。

该权重将作为下一个树的一部分应用:

k 是树 m 的分区总数,I()是指示函数。
- 从最后一棵树输出预测。搞定!
一个简单的比较告诉你梯度提升树,不同于随机森林,不使用额外的采样方法,按顺序而不是并行构建树,最后不使用聚集策略。直观地说,如果你仔细调整参数,梯度推进树比随机森林更好地处理偏差。经验研究表明,当树的数量足够大时,梯度增强树几乎总是优于随机森林,稳定的梯度增强树通常比稳定的随机森林更好。然而,如上所述,当数据中有大量噪声时,随机森林非常好。在这种情况下,梯度增强树有很大的机会过度拟合。
注:如果你对基本机器学习模型的详细解释感兴趣,我推荐 Hastie、Tibshirani 和 Friedman 的《统计学习的要素》。
用 PyTorch 和 OpenCV 实现实时目标检测系统
使用 python 实现实时对象检测系统的实践指南

文森特·梵高(1853–1890),巴黎,1887 年 5 月至 7 月(来源)
无人驾驶汽车可能仍然很难理解人类和垃圾桶之间的区别,但这并不影响最先进的物体检测模型在过去十年中取得的惊人进展。
结合 OpenCV 等库的图像处理能力,现在在几个小时内构建一个实时对象检测系统原型要容易得多。在本指南中,我将尝试向您展示如何开发一个简单的对象检测应用程序的子系统,以及如何将所有这些放在一起。
Python vs C++
我知道你们中的一些人可能会想为什么我使用 Python,对于实时应用程序来说,它是不是太慢了,你是对的;在某种程度上。
大多数计算繁重的操作,如预测或图像处理,都是由 PyTorch 和 OpenCV 执行的,它们都在幕后使用 c++来实现这些操作,因此,如果我们在这里使用 c++或 python,不会有太大的区别。
但同样,它只是一个原型,只有很少的基础设施代码和附加开销。如果你想学习生产级的实时实现,我建议你不要选择 python,至少现在不要。
读取视频流
您的输入视频流源可以是任何东西,您可能想从您的网络摄像头读取,或解析已存在的视频,或从连接到网络的外部摄像机。无论是什么问题,OpenCV 都是解决方案。在这个例子中,我将展示如何从 youtube 或网络摄像头读取视频流。

您可以使用 OpenCV 创建一个视频流来支持您的应用程序。(来源
从你的试管中读取
对于您的原型,您可能不想出去创建一个新的视频,而是使用许多在线可用的视频之一。在这种情况下,你可以从 youtube 上阅读视频流。
import cv2 # opencv2 package for python.
import pafy # pafy allows us to read videos from youtube.URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" #URL to parse
play = pafy.new(self._URL).streams[-1] #'-1' means read the lowest quality of video.
assert play is not None # we want to make sure their is a input to read.
stream = cv2.VideoCapture(play.url) #create a opencv video stream.
从网络摄像头读取
有时候你只想看看自己的脸。在这种情况下,请随意使用内置网络摄像头。
import cv2stream = cv2.VideoCapture(0) # 0 means read from local camera.
读取 IP 摄像头
如果您正在构建一个将部署在服务器上的应用程序,您的摄像机将拥有一个 IP 地址,您可以从该地址访问视频流。
import cv2camera_ip = "rtsp://username:password@IP/port"
stream = cv2.VideoCapture(camera_ip)
加载模型
今天的机器学习工程师被选择宠坏了,或者我应该说被选择弄糊涂了。有许多伟大的对象检测模型,每一个都有其优点和缺点。为了简单起见,我们将使用 YoloV5 ,因为它为我们提供了快速推理,这对我们的实时应用程序至关重要。你也可以看看其他的模型,比如 FasterRCNN。

根据 Yolov5 文件,它是目前市场上最快的型号。(来源)
我们可以直接从 PyTorch hub 加载模型,第一次运行代码可能需要几分钟,因为它会从互联网上下载模型,但下一次它将直接从磁盘加载。
from torch import hub # Hub contains other models like FasterRCNNmodel = torch.hub.load( \
'ultralytics/yolov5', \
'yolov5s', \
pretrained=True)
对单帧进行评分
俗话说“千里之行,始于足下”,所以我们可以说“解析一个视频流,始于一帧”。让我们看看如何对单个帧进行评分和解析。
我们用来执行推理的设备对我们的推理速度产生了巨大的影响,现代深度学习模型在与 GPU 一起工作时效果最佳,所以如果你有一个带有 CUDA 内核的 GPU,它将大幅提高你的性能。在我的经验中,即使只有一个 GPU 的系统也可以达到每秒 45-60 帧,而 CPU 最多只能给你 25-30 帧。
"""
The function below identifies the device which is availabe to make the prediction and uses it to load and infer the frame. Once it has results it will extract the labels and cordinates(Along with scores) for each object detected in the frame.
"""def score_frame(frame, model):
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model.to(device)
frame = [torch.tensor(frame)]
results = self.model(frame)
labels = results.xyxyn[0][:, -1].numpy()
cord = results.xyxyn[0][:, :-1].numpy()
return labels, cord
在框架上绘制方框
一旦我们对该帧进行了评分,我们就需要在将该帧写入输出流之前,在该帧上绘制出已标识的对象及其盒子。为此,我们可以使用 OpenCV 的图像处理工具包。
"""
The function below takes the results and the frame as input and plots boxes over all the objects which have a score higer than our threshold.
"""
def plot_boxes(self, results, frame):
labels, cord = results
n = len(labels)
x_shape, y_shape = frame.shape[1], frame.shape[0]
for i in range(n):
row = cord[i]
# If score is less than 0.2 we avoid making a prediction.
if row[4] < 0.2:
continue
x1 = int(row[0]*x_shape)
y1 = int(row[1]*y_shape)
x2 = int(row[2]*x_shape)
y2 = int(row[3]*y_shape)
bgr = (0, 255, 0) # color of the box
classes = self.model.names # Get the name of label index
label_font = cv2.FONT_HERSHEY_SIMPLEX #Font for the label.
cv2.rectangle(frame, \
(x1, y1), (x2, y2), \
bgr, 2) #Plot the boxes
cv2.putText(frame,\
classes[labels[i]], \
(x1, y1), \
label_font, 0.9, bgr, 2) #Put a label over box.
return frame
一旦完成,这个函数将产生类似这样的输出。

你可以尝试不同的物体有不同的颜色。(图片由作者提供)
一个将他们团结起来的功能
很抱歉引用了《指环王》,但是是的,现在我们把它们都放在一个单独的调用函数中,在一个循环中执行整个操作。
因此,让我们回顾一下我们的主函数要成功运行应用程序必须执行的步骤。
- 创建视频流输入。
- 加载模型。
- 当输入可用时,读取下一帧。
- 对框架进行评分,以获得标签和坐标。
- 在探测到的物体上画出方框。
- 将处理后的帧写入输出视频流。
六个简单的操作步骤,虽然我们会添加一些基础设施代码来帮助我们使应用程序更加健壮,但基本原理是一样的。所以让我们开始吧。
"""
The Function below oracestrates the entire operation and performs the real-time parsing for video stream.
"""
def __call__(self):
player = self.get_video_stream() #Get your video stream.
assert player.isOpened() # Make sure that their is a stream.
#Below code creates a new video writer object to write our
#output stream.
x_shape = int(player.get(cv2.CAP_PROP_FRAME_WIDTH))
y_shape = int(player.get(cv2.CAP_PROP_FRAME_HEIGHT))
four_cc = cv2.VideoWriter_fourcc(*"MJPG") #Using MJPEG codex
out = cv2.VideoWriter(out_file, four_cc, 20, \
(x_shape, y_shape))
ret, frame = player.read() # Read the first frame.
while rect: # Run until stream is out of frames
start_time = time() # We would like to measure the FPS.
results = self.score_frame(frame) # Score the Frame
frame = self.plot_boxes(results, frame) # Plot the boxes.
end_time = time()
fps = 1/np.round(end_time - start_time, 3) #Measure the FPS.
print(f"Frames Per Second : {fps}")
out.write(frame) # Write the frame onto the output.
ret, frame = player.read() # Read next frame.
您应该将所有这些组件打包到一个好的类中,该类可以与 URL 和您希望将输出流写入的输出文件一起被调用。您的最终产品将看起来像这样。

60 FPS 的实时处理视频输出流。(图片由作者提供)
结论
当然,生产级别的实时应用程序要比这复杂得多,但是本指南并不打算教这个。它将向您展示 Python 的惊人威力,它允许我们在几个小时内构建如此复杂的应用程序原型。这里的可能性只受到你的想象力的限制。
你可以在我的 Github 个人资料上查看完整的代码和更多这类令人敬畏的应用。
如果你喜欢这本指南。
留下评论让我知道你的想法。
通过 Twitter 、 Linkedin 或 Medium 与我联系。
与你的网络分享这篇文章。
编码快乐!!
在 Keras 中实现单触发探测器(SSD ):第二部分——损失函数
Keras 中的物体检测
在 Keras 中实现 SSD 丢失功能
一.导言
在本系列的第一部分中,我们已经了解了固态硬盘的网络结构。本文现在将深入探讨“网络如何学习”。更具体地说,我们将研究它的损失函数,以及如何用它来优化网络的参数。像往常一样,文章将首先单独讨论一些主要概念。然后,会解释那些概念是如何构成 SSD 损失函数的。最后,本文将以代码示例结束,向您展示如何在 Keras 中实现它。
这篇文章是一个更大的系列的一部分,叫做在 Keras 中实现单次检测器(SSD)。下面是系列的概要
二。概念
损失函数— L( ŷ,y )
损失函数是用于在训练期间产生损失值的数学公式。在训练期间,模型的性能通过模型为每个样本或每批样本产生的损失( L )来衡量。损失主要衡量预测值( ŷ )与期望值( y )之间的“距离”(Pere,2020)。如果 ŷ 离 y 很远(很不一样),那么损耗就高。然而,如果 ŷ 接近于 y ,则损耗较低。该模型使用损失作为“指标”来更新其参数,以便在未来的预测中产生非常小的损失。这意味着生产非常接近 y 的ŷ。参数更新过程(也称为反向传播)可以通过使用优化算法(梯度下降、Ada-grad、Adam 等)来实现。
优化算法本身就是一个巨大的研究领域。因此,我建议你把它添加到你的“要学习的 ML 主题”列表中。
回归和分类损失函数
机器学习中的两个常见任务是回归和分类。为了在训练期间测量模型性能,在每个任务中使用不同的损失函数。在分类任务中,模型试图预测一组“离散”的值(深度学习去神秘化,2020)。这项任务的损失函数倾向于将概率值作为输入。因此,模型最后一层的输出在用作损失函数的输入之前,首先通过适当的激活层(用于二分类的 Sigmoid,用于多类分类的 Softmax)。对于回归任务,模型试图预测“连续”值(深度学习非神秘化,2020)。回归任务的损失函数倾向于直接从网络的最后一层获取值,而不必将这些值转换为概率。
可以参考这篇文章通过深度学习去神秘化了解更多关于损失函数的知识。
平滑 L1 损耗

作者图片
平滑 L1 损失是一种回归损失函数。平滑 L1 损耗有几个变体,但 SSD 中使用的是休伯损耗的特例,δ = 1。你可以把它看作是 L1 损失和 L2 损失的组合。当| a| 小于或等于 1 时,则表现为 L2 损失。否则,它的行为就像 L1 的损失。它首先由 Ross Girshick 在他的论文“Fast-RCNN”中提出,作为物体检测的 L2 损失的替代。根据 Girshick (2015 年):
平滑 L1 损失是一种稳健的 L1 损失,与 R-CNN 和 SPPnet 中使用的 L2 损失相比,它对异常值不太敏感。当回归目标是无界的时,具有 L2 损失的训练可能需要仔细调整学习速率,以防止爆炸梯度。 (Girshick,2015 年,第 3 页)
软最大损失(又名分类交叉熵损失)

作者图片
Softmax 损失是一种分类损失函数。更具体地说,它用于多类分类。它由两个步骤组成。首先,将网络最后一层的输出输入到 Softmax 激活中,以概率分布的形式为每个类别生成一个置信度得分,其中所有值的总和为 1。其次,得到的概率分布然后被用作交叉熵损失的输入。交叉熵损失测量标签的预测概率分布和实际概率分布之间的差异。值得注意的是,为了将实际的标签 y 转换成概率分布,使用了一种称为一键编码的方法。
三。SSD 损失函数(学习目标)
SSD 学什么?

由作者编辑。来源:卡斯滕·怀恩吉尔特
回想一下,在本系列的第一部分中,我们了解到 SSD 输出的预测 ŷ 具有(total _ default _ box, num_classes + 1 + 4 + 8)的形状。每个total _ default _ box项目中包含的内容有:
- num _ classes+1背景类的置信度得分
- 4 边界框属性:到匹配的默认框中心的 x 偏移( cx )、到匹配的默认框中心的 y 偏移( cy )、边界框宽度的对数比例变换( w )、边界框高度的对数比例变换( h )
- 4 默认框值:默认框从图像左侧的中心 x 偏移、默认框从图像顶部的中心 y 偏移、默认框的宽度和默认框的高度
- 4 方差值:用于编码/解码边界框数据的值
在上述 4 点中,只有前两点可由 SSD 网络学习。其他值是常量,用于帮助将来的边界框解码过程。
为了找到基础事实标签匹配边界框,执行匹配过程。我们将在以后的文章中研究这个匹配过程,以及如何对基本事实边界框和置信度得分进行编码。
它是如何学习的?

作者图片
SSD 将回归损失( L_loc )和分类损失( L_conf )与α值组合在一起,作为定位损失的比例因子。 L_loc 是匹配的正框的所有边界框属性( cx , cy , w , h )的平滑 L1 损失的总和。这意味着它不考虑其类为背景类的默认框或不与任何基础真值框匹配的默认框。通过这种方式,它将因做出包含对象的边界框预测而获得奖励。为了产生 L_conf ,作者使用 Softmax loss 分别计算匹配的正默认框和负默认框的损失,然后将它们加在一起。负框也被考虑到分类损失中的原因是因为我们也想惩罚错误的预测,同时奖励背景类的正确预测。
四。代码
本文中显示的所有代码都可以在这个回购:https://github.com/Socret360/object-detection-in-keras。在本文中,我将展示代码的关键片段,并提供包含完整代码的相关文件的链接,而不是直接展示所有代码示例。这将确保文章不会因代码示例而过载。GitHub repo 中的大部分代码都是从https://github.com/pierluigiferrari/ssd_keras中获取和修改的。
在 Keras 中,损失函数可以传递给 model.compile 方法。损失函数将被赋予两个自变量 y_true 和 y_pred。这实质上是 y y 和ŷ的。为了构建整体损失函数,我们首先需要对 Softmax 损失和 Smooth L1 损失的损失函数进行编码。你可以参考 smooth_l1_loss.py 和 softmax_loss.py 来实现这些目标。另一个需要注意的重要事情是,在计算 SSD 损耗时,你会发现负默认框比正默认框多。为了补救这种情况并防止类不平衡,执行硬负挖掘。这实质上意味着只保留适当数量的负默认框。
以下代码片段演示了 Keras 中的 SSD 丢失功能:
结论
本文演示了在 Keras 中实现 SSD 丢失功能所需的概念和代码。在下一篇文章中,我们将了解用于为 SSD 生成训练数据的 Keras 数据生成器。
喜欢这篇文章并想表达你的支持?关注我或给我买咖啡
参考
布朗利,J. (2020 年 12 月 22 日)。机器学习交叉熵的温和介绍。机器学习精通。检索自https://machine learning mastery . com/cross-entropy-for-machine-learning/
深度学习去神秘化(2020)。损失函数解释。从 https://deeplearningdemystified.com/article/fdl-3取回
吉尔希克河(2015 年)。Fast-RCNN。https://arxiv.org/pdf/1504.08083.pdf
劳尔戈麦斯(2018 年 5 月 23 日)。理解范畴交叉熵损失、二元交叉熵损失、Softmax 损失、Logistic 损失、焦点损失以及所有那些容易混淆的名称。劳尔·戈麦斯博客。从 https://gombru.github.io/2018/05/23/cross_entropy_loss/取回
Koech,E. K. (2020 年 10 月 3 日)。交叉熵损失函数。走向数据科学。检索自https://towards data science . com/cross-entropy-loss-function-f 38 C4 EC 8643 e
刘,w,安盖洛夫,d,尔汉,d,塞格迪,c,里德,s,傅,C.Y,&伯格,A. C. (2016)。SSD:单次多盒探测器。https://arxiv.org/abs/1512.02325
Pere,C. (2020 年 6 月 17 日)。什么是损失函数?。走向数据科学。检索自https://towards data science . com/what-is-loss-function-1e 2605 aeb 904
d . rade ci(2020 年 6 月 19 日)。 Softmax 激活功能说明。走向数据科学。检索自https://towards data science . com/soft max-activation-function-explained-a 7 E1 BC 3 ad 60
在 Keras 中实现单触发探测器(SSD ):第三部分——数据准备
Keras 中的物体检测
实施 Keras 的 SSD 数据生成器

给狗的图像贴标签。由作者编辑。来源: Alvan Nee
一.导言
在之前的文章中,我们已经了解了 SSD 的网络结构(第一部分)和用于训练网络的 SSD 损失函数(第二部分)。本文关注在 Keras 中实现 SSD 的下一个重要步骤:为训练准备数据。本文将首先讨论用于训练 SSD 网络的 PASCAL VOC 数据集。然后,它将研究如何从该数据集中准备一个适合训练的格式的训练样本的细节。本文将以一个用于训练 SSD 网络的 Keras 数据生成器的完整代码示例结束。
本文是一个更大的系列的一部分,称为在 Keras 中实现单次检测器(SSD)。下面是这个系列的概要
第一部分:网络结构第二部分:损失函数 第三部分:数据准备(本文) 第四部分:数据扩充第五部分:预测解码</implementing-single-shot-detector-ssd-in-keras-part-vi-model-evaluation-c519852588d1?sk=797df0a4bf29d36ddd1e7ee9fe5c81a3>
二。SSD 数据准备

图一。为 SSD 网络准备训练数据的过程。来源:图片由作者提供。
你可以在他们的官方网页这里找到更多关于 PASCAL VOC 数据集的信息。
在 SSD 论文中,作者使用不同的数据集来训练/测试他们的 SSD 网络。对于 SSD 的实施,重点将放在 PASCAL VOC 数据集上。这是一个流行的数据集,用于训练/基准对象检测网络。数据集中有 20 个对象类。这些类别是:人、鸟、猫、牛、狗、马、羊、飞机、自行车、船、公共汽车、汽车、摩托车、火车、瓶子、椅子、餐桌、盆栽植物、沙发、电视/监视器。它由 xml 格式的图像及其相应的标签组成。在标签文件中,对象的类以字符串格式保存。每个对象的边界框以角格式保存(xmin、ymin、xmax、ymax)。
如前所述,PASCAL VOC 数据集中的每个训练样本都由一个图像及其对应的标签 xml 文件组成。为了将训练样本转换成适合训练 SSD 的格式,我们需要执行两个主要任务。首先,我们需要读取图像,将其调整到合适的尺寸(例如 300x300),然后将其归一化。为了实现 SSD,使用了相对于 ImageNet 平均值的零中心标准化。零中心归一化调整像素值的分布,使其以零为中心(Brownlee,2019)。其次,我们需要创建一个形状数组(num_default_boxes,num_classes + 4 + 8d ),它与 SSD 网络的输出形状相匹配。在此任务中,我们需要读取类标签并对它们进行一次性编码,将所有边界框读入内存,缩放它们以适应输入大小,将它们与正确的默认框匹配,并将它们编码到适合 SSD 网络预测的范围。在这些子任务中,最复杂的两个是匹配过程和编码过程。因此,我们将在下面详细讨论它们。
将基础事实框与默认框相匹配
匹配过程依赖于基础真值框和所有默认框之间的交集/并集(也称为 IOU,框重叠)的计算。检索 IOU 值后,匹配过程包括三个步骤。首先,每个基本事实框与具有最高 IOU 的默认框相匹配。这确保了基础事实框在训练期间将具有匹配的默认框。第二,剩余的默认框与 iou 大于某个阈值(例如 0.5)的基本事实框相匹配。这允许基础事实框与多个默认框匹配。第三,还会有既不是背景也没有足够的 IOU 分数来作为匹配的框,这些框被称为“中性框”。找到这样的默认框也很重要,这样我们就可以适当地设置它们的类标签。
编码边界框

图 2:编码边界框属性的公式。来源:作者图片
SSD 的这种实现使用基于质心的包围盒格式来训练网络。因此,为了对边界框属性进行编码,我们使用 Figure 2–1 中的公式。此外,SSD 的许多实现用相应的方差(σ)值来划分每个边界框属性,以进一步规范化它们的范围。根据毛(2019),这种方法可能不是正确的方式。他认为:
“在我看来,这实际上是一个标准归一化的过程,而不是用方差进行编码……这样,如果编码后的包围盒 X’遵循某种高斯分布,经过归一化后,该分布将变成均值为 0、方差为 1 的标准正态分布。这将是机器学习预测的理想选择。”
因此,不是用相应的方差(σ)值除每个编码的边界框属性,而是用相应的标准差(σ)除编码的边界框属性,如图 2–2 所示。
三。Keras 的固态硬盘数据生成器
本文中显示的所有代码都可以在这个回购:【https://github.com/Socret360/object-detection-in-keras。在本文中,我将展示代码的关键片段,并提供包含完整代码的相关文件的链接,而不是直接展示所有代码示例。这将确保文章不会因代码示例而过载。GitHub repo 中的许多代码都是从 https://github.com/pierluigiferrari/ssd_keras 的中截取并修改而来的。
下面的代码演示了如何编写 Keras 的数据生成器来训练 SSD 网络,如第一部分所示。你需要参考match _ gt _ boxes _ to _ default _ boxes . py来匹配边界框和缺省框,参考 encode_bboxes.py 来相应地编码边界框。
四。结论
从本文中,您了解了如何从 PASCAL VOC 数据集准备一个训练样本,并将其转换成与 SSD 网络训练兼容的格式。在下一篇文章中,我们将研究如何用物体包围盒来扩充训练图像。
喜欢这篇文章并想表达你的支持?关注我或给我买咖啡
动词 (verb 的缩写)参考
j . brown lee(2019 年 3 月 25 日)。深度学习如何手动缩放图像像素数据。机器学习精通。检索自https://machine learning mastery . com/how-to-manually-scale-image-pixel-data-for-deep-learning/
刘,w,安盖洛夫,d,尔汉,d,塞格迪,c,里德,s,傅,C.Y,&伯格,A. C. (2016)。SSD:单次多盒探测器。https://arxiv.org/abs/1512.02325
毛,L. (2019)。物体检测中的包围盒编码和解码。检索自https://lei Mao . github . io/blog/Bounding-Box-Encoding-Decoding/
在 Keras 中实现单发探测器(SSD ):第四部分——数据扩充
Keras 中的物体检测
用于增加 SSD 网络的训练样本的 Python 代码

由作者编辑。来源:贾斯汀·艾金
一.导言
在本系列的最后三部分中,我们已经完成了 SSD 网络训练的完整管道。我们对 SSD 网络、其损失函数进行编码,并将数据样本格式化/准备成适合训练的格式。虽然仅使用这些组件设置来训练网络是可能的,但 SSD 的作者建议,数据增强是提高网络泛化能力的关键(刘,Anguelov,Erhan,Szegedy,Reed,Fu & Berg,2016)。因此,本文主要关注 SSD 论文中提到的光度增强和几何增强这两种数据增强技术。
这篇文章是一个更大的系列的一部分,叫做在 Keras 中实现单次检测器(SSD)。下面是系列的概要
注意:尽管这篇文章单独讨论了每种增强技术中的不同方法,但在实践中,这些方法被链接在一起以创建一个增强管道,该管道能够产生足够随机化的新图像来训练 SSD 网络。
二。光度增强
光度增强通过转换原始图像的 RGB 通道来生成新的数据样本。这是通过将每个原始像素值( r 、 g 、 b )转换成新的像素值(r′、g′、b′)(泰勒&尼茨克,2015)。在对象检测中,这种增强技术影响原始图像的照明和颜色,但是不影响图像内对象周围的边界框。当我们想要用各种颜色和光照条件描绘物体时,这种增强技术是有用的。我们可以使用许多方法来转换图像,以实现光度增强。本节的剩余部分将讨论:随机亮度、随机对比度、随机色调、随机照明噪声和随机饱和度。
随机亮度

图 1:改变图像亮度的效果。由作者编辑。来源:贾斯汀·艾金
这种光度增强的方法是在原始像素值( r 、 g 、 b )的 [-255,255】范围内增加一个δ值。如果δ为正值,新图像将比原始图像更亮。相反,负的δ值会使新图像看起来比原始图像更暗。
随机对比

图 2:改变图像对比度的效果。由作者编辑。来源:贾斯汀·艾金
这种光度增强方法通过将那些像素值乘以系数δ来减少或增加原始像素值( r 、 g 、 b ),其中δ≥0。当0≤δ<1,新图像中的暗区和亮区之间的区别没有原始图像中的明显。另一方面,当δ>1时,新图像中的暗区域和亮区域变得比原始图像中更清晰。
随机色调

图 3:改变图像色调的效果。由作者编辑。来源:贾斯汀·艾金
要使用此方法实现光度增强,需要对原始图像的色调通道进行更改。更具体地,为了产生新的图像,将在 [-360,360] 范围内的δ值添加到原始图像的色调通道中的像素值。这导致在色轮上顺时针或逆时针移动图像的色调通道。
随机照明噪声

图 4:交换图像通道的效果。由作者编辑。来源:贾斯汀·艾金
这种光度增强方法可以通过交换原始图像的不同通道(RGB)的顺序来实现。类似于随机色调,这种方法允许模型关注对象的形状而不是它们的颜色。
随机饱和

图 5:改变图像饱和度的效果。由作者编辑。来源:贾斯汀·艾金
为了执行这种光度增强方法,通过将那些像素值乘以因子δ来增加或减少原始图像的饱和度通道的像素值,其中δ≥0。当0≤δ<1,新图像的颜色比原始图像更加柔和/苍白。相比之下,当δ>1时,新图像中的颜色比原始图像更强烈/更鲜明。
三。几何增强
几何增强通过将原始图像中的每个像素映射到新图像中的新位置来生成新的数据样本(Taylor & Nitschke,2015)。这种增强过程会影响对象周围边界框的几何形状,但这些对象的类别保持不变。当我们想要以各种形状和大小描绘对象时,这种增强技术是有用的。像光度增强一样,我们也可以使用许多方法来实现几何增强。这部分包括:随机垂直和水平翻转,随机扩展和随机裁剪。
随机垂直和水平翻转

图 6:垂直和水平翻转图像的效果。由作者编辑。来源:贾斯汀·艾金
这种几何增强方法在给定的轴上镜像原始图像。对于垂直翻转,轴是水平轴。至于水平翻转,镜像轴是垂直轴。在这两种情况下,对象周围的边界框需要相对于相同的轴进行镜像。
随机扩展

图 7:扩展图像的效果。由作者编辑。来源:贾斯汀·艾金
为了使用这种方法实现几何增强,原始图像被放置在比原始图像大一倍δ的新图像内,其中δ≥1。由于原始图像将不能覆盖新图像的所有区域,所以新图像上的剩余像素值被设置为平均像素值(例如,对于 ImageNet 平均值为[0.485,0.456,0.406])。
随机作物

图 8:裁剪图像的效果。由作者编辑。来源:贾斯汀·艾金
这种放大方法裁剪原始图像的某一部分,以创建可以具有不同纵横比的新图像。除非裁剪中存在对象,否则裁剪是有效的。要被视为包含在裁剪内,对象边界框的中心必须在裁剪内。此外,裁剪和对象边界框之间的重叠(IOU)必须超过随机选择的某个阈值。同样,作物的长宽比也是随机选择的。
四。结论
总之,这篇文章展示了许多用于实现光度和几何增强的方法。此时,我们可以有效地训练 SSD 网络。在下一篇文章中,我们将研究如何解码网络产生的结果。
喜欢这篇文章,想表示你的支持?关注我或者给我买咖啡
参考
KDNuggets。(2018).包围盒的数据扩充:重新思考对象检测的图像变换。检索自https://www . kdnugges . com/2018/09/data-augmentation-bounding-box-image-transforms . html/2
刘,w,安盖洛夫,d,尔汉,d,塞格迪,c,里德,s,傅,C.Y,&伯格,A. C. (2016)。SSD:单次多盒探测器。https://arxiv.org/abs/1512.02325
像素杂志。(2017).摄影师的饱和度和饱和度指南(及其差异)。检索自:https://medium . com/the-coffeelicious/a-photos-guide-to-vibrance-and-saturation-and-thes-differences-4 FDE 529 cc 19
泰勒和尼茨克(2015 年)。使用通用数据增强改进深度学习。https://arxiv.org/pdf/1708.06020.pdf
远程传感器。(2018).SSD(单发探测器)中的数据增强。检索自:https://www . telesens . co/2018/06/28/data-augmentation-in-SSD/# RandomLightingNoise



浙公网安备 33010602011771号