在数据科学中使用 Git:独唱大师
The solo tree in Tongva Park, Santa Monica, CA (I took the photo during a lunch break back in 2014)
Git 是一个非常流行的版本控制系统,用于跟踪计算机文件中的变化,并协调多人对这些文件的工作。它在数据科学项目中被很好地用于跟踪代码和维护并行开发。Git 可以以非常复杂的方式使用,但是,对于数据科学家来说,我们可以保持简单。在这篇文章中,如果你是一个“独行侠”,我将介绍主要的用例。
什么是“Solo Master”?
当你使用 GIT 只是为了保护你的代码安全,避免在你的笔记本电脑坏了/被偷了之后变得疯狂,所有的改变都只在“主”分支上。现在你处于“独唱大师”模式。在这种模式下,事情相对容易,主要有六种可能的情况:
Credit. https://vignette.wikia.nocookie.net/character-stats-and-profiles/images/a/a9/Neo_%28BOS%29.png
- 一个工作空间,没有出错
- 一个工作空间,在“git add”前出错
- 一个工作空间,在“git commit”之前出错
- 一个工作空间,在“git push”之前出错
- 一个工作空间,在“git push”后出错
- 多个工作空间
注意。有很多很棒的资源在谈论“什么是 Git”和“Git 的基本概念”,我会参考 Git 官方网站上的“入门— Git 基础”
现在我们可以开始一些很酷的项目了!首先,让我们去 Github 创建一个空项目,然后开始在本地笔记本电脑上正确配置它。
Create the Git repository in GitHub
git clone git@github.com:PanWu/git-solo-master.gitcd git-solo-master/git config user.name "Your Name"git config user.email yourname@gmail.com
案例一。一个工作空间,一切正常
这是最理想也是最简单的情况,您需要做的只是在一次提交中添加更多的文件,提交代码,然后推送到远程主分支。在这样的情况下,生活是如此轻松。
echo 'GOOD CODE' > feature_1.pygit add feature_1.pygit commit -m 'add feature'git commit --amend -m 'add one feature'git push origin master# after this, your Git structure will look like following
案例二。一个工作空间,在“git add”前出错
这总是会发生…你开始玩弄你的想法,并在文件中添加了一些草稿代码,然后很快发现这个想法不可行,现在你想重新开始。怎么做呢?幸运的是,如果您没有在新文件上运行任何“git add ”,这是非常容易的。
更多详情请参考“ Git checkout ”。
echo 'BAD CODE' > feature_1.pygit checkout -- feature_1.py# Now, feature_1.py file will contain only "GOOD CODE"
案例三。一个工作空间,在“git commit”之前出错
您认为这个想法可行,添加了一些文件,做了一些更改,做了一些“git add ”,最后,您发现结果不对。现在你想摆脱混乱,回到美好的,正确的,旧的代码。
更多详情,请参见“ Git 复位”。
echo 'BAD CODE' >> feature_1.pyecho 'BAD CODE' > feature_2.pygit add feature_1.py feature_2.pygit resetgit checkout -- feature_1.py# Now, feature_1.py file will contain only "GOOD CODE"# and feature_2.py will be considered as "Untracked files" in the folder
案例四。一个工作空间,在“git push”之前出错
这次你更进一步,不仅你做了“git add ”,而且这个修改花了几个小时,你还做了几个“git commit ”!啊,又一个巨大的错误,怎么办?!
更多详情请参考“ Git 复位”。
# if there is only 1 incorrect "git commit"echo 'BAD CODE' > feature_1.pygit add feature_1.pygit commit -m 'add an unnecessary feature'git reset HEAD~git checkout -- feature_1.py# if there is more than 1 incorrect "git commit"echo 'BAD CODE' >> feature_1.pyecho 'BAD CODE' > feature_2.pygit add feature_1.pygit commit -m 'add first unnecessary feature'git add feature_2.pygit commit -m 'add second unnecessary feature'# now you need to run "git log", find out where the "origin/master"# and "origin/HEAD" points to d317a62a12481a850be4c3cf5bc9a7bf45c094b7# now the "HEAD -> master" is 2 commits ahead of the "origin/HEAD"git loggit reset d317a62a12481a850be4c3cf5bc9a7bf45c094b7git checkout -- feature_1.py# if you run "git log" again, you will see now "HEAD -> master" is the same# as "origin/master"
Git commits history: when you made a mistake and commit into the branch
Git commits history: after you reset the HEAD to the previous “good” commit
案例五。一个工作空间,在“git push”后出错
您将代码推向生产,而其他成员发现这是一个大错误。现在您需要将代码恢复到原来的位置。
更多详情请参见“ Git revert ”。
echo 'BAD CODE' >> feature_1.pyecho 'BAD CODE' > feature_2.pygit add feature_1.py feature_2.pygit commit -m 'add unnecessary features'git push origin mastergit revert HEAD# or first use "git log" to find current head# then run "git revert 66cda7e93661df0c81c8b51fec6eec50cf1e5477"# either way, you need to edit and save the revert messagegit push origin master# now although the master/HEAD gets to where it was, your mistake is forever# recorded in Git system :( so pay attention to your push!
Git commits history: it is recorded on the GitHub server!
Git commits history: after revert, the mistake is no longer there. However, you leave a permanent log in the server.
案例六。多个工作空间
你有两个工作空间,一个在你的笔记本电脑上,一个在你的桌面工作站上。您在一个工作空间开发特征 2,在另一个工作空间开发特征 3。
# SPACE 1git clone git@github.com:PanWu/git-solo-master.gitcd git-solo-master/git config user.name "Your Name"git config user.email yourname@gmail.comecho 'NEW CODE 2' > feature_2.pygit add feature_2.pygit commit -m 'add feature 2'# SPACE 2git clone git@github.com:PanWu/git-solo-master.gitcd git-solo-master/git config user.name "Your Name"git config user.email yourname@gmail.comecho 'NEW CODE 3' > feature_3.pygit add feature_3.pygit commit -m 'add feature 3'# In SPACE 1: we pushed successfullygit push origin master# In SPACE 2: the same cod will failgit push origin master# error: failed to push some refs to 'git@github.com:PanWu/git-solo-master.git'# hint: Updates were rejected because the remote contains work that you do# hint: not have locally. This is usually caused by another repository pushing# hint: to the same ref. You may want to first integrate the remote changes# hint: (e.g., 'git pull ...') before pushing again.# hint: See the 'Note about fast-forwards' in 'git push --help' for details.
现在你看到问题了,解决方法是先用“git pull”。
“Git pull”=“Git fetch”+“Git merge”或“Git fetch”+“Git rebase”
具体参见“ Git pull ”。记住,现在远程分支如下所示
# In SPACE 2# first, pull the current most up-to-date HEADgit pull# this equals "git fetch" + "git merge"# then, edit and save the merge message# you may also try 'git pull --rebase", see what's the differencegit push origin master
现在,只要你在每一个工作空间开发每一个单独的功能,这个过程就不会有问题。这被认为是比在不同的工作空间中处理同一特征更好的做法。因为如果同一个文件在不同的空间被修改,“合并”过程将会有许多冲突,解决这些冲突对“单飞大师”来说是一件大事。
很好,现在在这些简单的案例研究之后,您成为了在数据科学中使用 Git 的真正的“独行侠”。你永远不会丢失任何代码(它会一直被推到云端)或者担心多个工作空间的代码不一致(只要正确使用“git pull”)。
享受使用 Git 吧!
注意。这篇文章最初发布在我的个人博客上,并在 Medium 上转载。
使用 Gitlab 的 CI 进行周期性数据挖掘
用最少的代码和努力无服务器地周期性挖掘新闻门户 RSS 提要
Photo by Patrick Lindenberg on Unsplash
标准数据科学开发流程中最耗时、最困难的阶段之一是创建数据集。在这种情况下,你已经提供了一个数据集 kudos 给你!你刚刚为自己节省了大量的时间和精力。尽管如此,在许多情况下,情况并非如此。事实上,数据挖掘阶段可能是项目时间表中最没有动力的阶段之一。因此,当有简单和容易的技术来挖掘所需的数据时,它总是有利的。
也就是说,在这篇文章中,我将描述 GitLab 的 CI 管道如何用于周期性的数据挖掘工作,而不需要存储桶、VPSes、外部服务器等等。所以,事不宜迟,让我们在教程潜水。
用例
为了实现本教程的价值,我将把这项技术放在一个用例中,这是我正在做的一个附带项目的一部分。更具体地说,我一直在尝试对一个希腊新闻数据集应用一些自然语言处理。因此,我们将使用塞浦路斯新闻门户(【http://www.sigmalive.com】T2)的 RSS 提要来定期获取和存储发布在门户中的新闻文章。
在对该网站的全球 RSS 提要进行了一些初步检查后,发现它返回了最近发表的 25 篇文章。考虑到帖子的频率,每小时拉一次 RSS 提要应该不会错过任何东西。但是即使我们错过了一些,这也不是什么大事。
因此,我需要写一个脚本,让下载并存储来自 RSS feed 的文章,但是一直在后台运行并且每小时触发一次。这些是我的主要要求。
实施前的一些想法
直觉上,当谈到重复的周期性任务时,cron 作业是最常见的事情。一种选择是编写一个 python 脚本,作为 cron 作业每小时下载、存储并执行一次。嗯……看起来很简单,但是我需要确保我的电脑 24/7 都可以上网。不太方便。
或者,我可以从云提供商那里获得一个 VPS,并在那里运行我的 cron 作业。听起来似乎可行,但这需要设置 VPS,在远程文件系统中存储新闻文件,并随着时间的推移维护这一切。另外,我需要支付服务器的费用。
我的懒惰本能坚持认为应该有更简单的方法…
就在那时,我突然想到了!从 DevOps 的角度来看,我可以创建定期运行的 CI 管道!因为我不想托管任何东西,所以我可以免费使用 Gitlab 的托管 CI 平台。此外,在存储新闻方面,我可以将它们作为 CI 作业的工件公开,然后将它们下载到一起进行聚合。鉴于 Gitlab 每月提供 2000 个免费管道小时,它们应该绰绰有余。
使用 GitLab 的 CI 完成这项任务的另一个额外好处是内置的监控和报告。如果任何管道作业失败,将向您的收件箱发送一封电子邮件。多方便啊?
没有云桶,没有 google drive,没有外部服务器。听起来是个不错的计划。让我们继续讨论实现。
但是在开始之前,我假设你已经有一个 GitLab 帐户,并且知道如何使用 Git。你也可以从这里的克隆我的库,直接跳到完整的代码。
履行
为了透明起见,我将使用 Python 3.6.5,但它应该可以在任何 Python 3 版本中工作。
为了获取新闻,我编写了一个 python 脚本,它执行一个普通的 HTTP 请求,解析 XML 并将其保存在一个 JSON 文件中。
事实上,我正在使用 tiny DB(【https://tinydb.readthedocs.io/en/latest/】)一个非常轻量级的 python 包,它在存储中间件之上提供了一个简单而干净的 DB API。(默认情况下,它只是将它们存储在一个 JSON 文件中,这样就可以了)。
以下是脚本源代码:
您可以随意测试代码,但是要确保通过运行以下命令安装了所有附加的依赖项:
pip install requests
pip install tinydb
pip install xmltodict
太好了,现在是时候做些开发工作了。
首先,我们应该将 python 依赖项导出到 Gitlab 作业的 requirements.tx t 文件中:
pip freeze > requirements.txt
任务列表上的下一件事是通过配置 CI 管道。 gitlab-ci.yml 文件:
如果您以前从未见过这些文件,它只是 Gitlab 的一个配置文件,用于了解在每个 CI 管道中执行什么。在上面的配置中,我定义了一个名为“scrape”的阶段(这可以是您喜欢的任何东西),我在执行脚本之前安装 python 需求,最后在“scrape”作业中运行脚本,目录中的所有 JSON 文件都作为工件公开。
让我们把它付诸实践。创建一个新的 GitLab 存储库,并推送我们刚刚创建的文件。这些应该是:
- feed_miner.py
- .gitlab-ci.yml
- requirements.txt
如果您导航回 GitLab 项目页面,CI 作业应该已经开始运行。
更重要的是,在 GitLab 中导航到 CI/CD- > Pipelines 以获得所有作业状态的概述并下载工件:
下载工件并提取内容证实了我们的脚本在 Gitlab 的 runners 上运行得非常好。但是等一下,我们希望它每小时运行一次!为此,我们将使用 GitLab 的 CI 计划。
导航至 CI / CD - >日程并点击新日程
填写描述以及希望作业运行的频率。频率应该是 cron 格式(http://www.nncron.ru/help/EN/working/cron-format.htm)。最后,点击保存管道进度表。
你都准备好了!此时,我们有一个脚本,它下载并存储我们需要的新闻,并且每小时运行一次。
然而,工件在每次作业运行时都会被分割,因此我们需要编写另一个脚本来下载我们所有的 JSON 工件,并将它们聚集在一个数据集中。
聚集工件
因为我们将使用 GitLab 的 API 来下载工件,所以我们需要获得一些初始信息,比如项目的 ID 和 HTTP 请求的访问令牌。
要查找项目 id,只需导航到项目的 GitLab 页面:
要创建新的访问令牌,请从右上角转到您的配置文件设置:
点击访问令牌选项卡,填写令牌名称,点击创建个人访问令牌:
令牌应该显示在页面的顶部。把它保存在某个地方,因为我们在接下来的步骤中会用到它。
有了这些,您可以使用下面的脚本来下载所有工件,将它们提取到一个目录中,并加载到内存中:
确保在运行之前已经替换了 CONFIG 类中的 project-id 和 access-token 值。此外,还需要一个额外的进程依赖项,因此您可以继续安装它:
pip install progress
这是本教程最后需要的部分。在等待了几天之后,我运行了我的聚合脚本,我的数据集中已经有 340 个不同的新闻条目了!整洁!
概述
如果您遵循了前几节中的所有步骤,您应该会得到以下文件:
- feed_miner.py
- requirements.txt
- aggregate.py
- .gitlab-ci.yml
其中包括:
- 一个将 RSS 提要下载并存储到 json 文件的脚本。
- Gitlab CI 配置文件,定义了安装 python 依赖项和运行 miner 脚本的管道。(计划每小时运行一次)
- 一个聚合脚本,从成功的作业中下载所有工件,提取它们并读取内存中的所有新闻记录,同时删除重复项。
有了这些,您就可以高枕无忧了,因为数据正在被挖掘并存储在您的 Gitlab 存储库中。一个潜在的改进是创建另一个管道,大约每周运行一次聚合脚本,并创建一个 csv 文件,但是进一步的数据处理完全取决于您。
我希望你喜欢这个教程!你可以在我的 github 库这里找到完整的代码。
使用谷歌趋势数据来利用你的预测模型
在预测模型中使用谷歌趋势数据有一些缺陷。本文描述了一种使 Google 趋势数据可用于模型的方法,通过使用 Google 搜索量预测电影成功的实际例子来提供突破性的结果。
自 2006 年发布以来,谷歌趋势成为人们可能想到的任何主题的搜索量的综合信息来源——从 2019 年 10 月加拿大大选的信息内容到美国哪个州对泰勒·斯威夫特更感兴趣以及哪个州更喜欢搜索金·卡戴珊的不太相关的数据。
Source: Google Trends
当然,如果发现如此丰富的数据源,首先想到的是如何在您的模型中利用它。然而,也有一些陷阱:
- 一次只能获得 5 个搜索词的数据
- Google Trends 对数据进行了缩放,使其对用户而言具有可比性,但对模型而言则不具可比性
- 即使你能够以一种无标度的方式得到每一个搜索词,由于搜索词的模糊性,它仍然可能是有偏见的
尽管如此,在我的研究生课程期间,我从事了一个项目,该项目需要使用谷歌趋势数据来预测电影在首映周末的成功。我们和一名同学一起开发了一种方法,使数据变得可用,并将其输入到模型中。遗憾的是,我一直没有抽出时间来重构代码,放到 github 上。直到现在。该项目的 R 代码可在此处找到,并且将参照该项目描述使 Google Trends 数据可用于任何类型的预测模型的一般方法
我们收到了一个约 900 部电影的数据集,其中包含类型、评级、制片厂等信息,当然还有首映周末的观影人数。受谷歌自己发布的白皮书的启发,我们试图从谷歌趋势收集数据,以将搜索量纳入我们的预测模型。在谷歌搜索上捕捉 900 部电影的流行度是一项艰巨的任务,主要是由于 3 个事实。首先,一次只能提取 5 个搜索词的数据。这个问题可以很容易地通过分块搜索词来解决。然而,这导致了块之间不可比的结果。其次,电影名称并不总是模糊的,这意味着搜索 【被解放的姜戈】 几乎肯定会将你带到与塔伦蒂诺电影相关的结果,而搜索像 希区柯克 这样的传记电影可能会导致关于导演本身的网页。第三,谷歌上的搜索词的搜索量信息不是以可评估的形式公开提供的,这意味着谷歌趋势只提供自然数,从而在观察的时间和地理框架中的最大相对值被缩放到 100,这使得不可能将像 《星球大战——原力觉醒》 这样的电影与像 《头号通缉犯》 这样的独立电影进行比较,因为*《头号通缉犯》*的量即使不总是被缩放,也大多会被缩放因此,使谷歌趋势数据可用于预测模型的主要任务有三个方面:
- 获取所有感兴趣的搜索词的可比数据
- 从艺术电影到大片,将数据换算成所有电影的真实值
- 确保所提供的请求仅涉及电影
为了获得捕捉人们对电影的兴趣的 KPI,定义了 3 个可能的搜索词的线性组合。这些搜索词分别是电影主标题(如*《霍比特人》)、电影主标题+后缀电影*(德语为电影)和完整标题(如 《霍比特人:意外之旅》 )。根据谷歌发表的这篇文章,搜索词的绝对搜索量除以给定时间和地理范围内的总搜索量。
对讲故事最有用的是我们的标准化趋势数据。这意味着,当我们查看一段时间内对某个主题的搜索兴趣时,我们会将该兴趣视为在该时间和地点谷歌上对所有主题的所有搜索的比例。当我们查看某个主题的区域搜索兴趣时,我们是在查看某个给定区域中该主题的搜索兴趣占同一时间同一地点谷歌上所有主题的所有搜索的比例。
在此之后,数据被归一化到从 0 到 100 的自然数范围,由此五个转换的查询结果的相对最大值被缩放到 100。
我们的数据背景也很重要。我们将数据索引为 100,其中 100 是所选时间和地点的最大搜索兴趣。
这导致不可比的值,使得预测不可行。
Google Trends data for the search terms Django Unchained, A most wanted Man, Mama and Star Wars. Vertical lines mark the premiere date of each movie.
上面的图显示了电影星球大战——原力觉醒、被解放的姜戈、 妈妈 和 通缉犯 的谷歌趋势数据。垂直线标志着这些电影的首映日期。显而易见,《星球大战》甚至远远超过了像《被解放的姜戈》这样的大片。相比之下,Mama(德语中妈妈的意思)的得分稳定在 5 分左右,仅在所谓的电影首映前几周略有上升。*《头号通缉犯》*即使在首映周也是定值 0,因此根本没有可比性。首映前 6 周的搜索词的确切数据如下:
Search volume for the movies 6 weeks prior premiere date t
表格中的最后一行显示了首映周末在时间 t. 的搜索量和实际访客数量之间的相关性。相关性本身已经非常高了,但是特别是和头号通缉犯的数量根本不能用于预测,而和的数量似乎有很大偏差。
好吧,但是如何克服这些问题呢?让我们解决一个又一个问题。首先,让我们将数据相互比较,得到真实值而不是自然数,以便得到可用的数据,即使是对一个头号通缉犯*。我们可以通过引入锚定条款计划来实现这一点。这背后的想法是为每个搜索词的创建一个到*单个值的关系。为此,我们需要定义一个锚项列表,其搜索量尽可能随时间保持不变,以防止扰乱搜索项的标准化。城市或报纸是这类术语的一个好选择;从大城市开始,继续到地方报纸。第一个锚点由其自己的中值标准化,以获得第一个缩放因子:
为了将每个搜索项链接到第一个锚点的中间值,有必要自上而下地链接锚点。为此,计算 s 缩放因子作为一个锚和下一个较低锚项的搜索量的分数。比例因子的一般公式由下式给出:
为了制作类似《星球大战——原力觉醒》和《头号通缉犯》的电影,搜索词被从上到下提取并链接到锚词。因此,在第一步中,使用第一锚点绘制搜索项,并使用其缩放因子进行缩放。级别 i 的缩放搜索量现在给出为
如果感兴趣的时间窗口内的搜索词的最大缩放搜索量低于缩放锚的给定百分比(在我们的项目中我们使用 25%),则必须用下一个较低的锚词来重新绘制。链接的锚项的结果(以及每个级别 i 的缩放因子)如下所示,其中右边的图只是放大了低级锚项。
Scaling factors used in the project
特别是在右侧,可以看到缩放搜索项的效果。未缩放的粉红色搜索词的数量大多在 5 到 6 之间,而缩放版本的数量介于两者之间。将这种缩放方法应用于我们感兴趣的 4 部电影的搜索词,会产生下面的图。
Scaled Google Trends data for the search terms Django Unchained, A most wanted Man, Mama and Star Wars. Vertical lines mark the premiere date of each movie.
仅通过观察该图,人们可能看不出与第一个图相比有如此大的差异,除了 y 轴现在在 0 和 1 之间缩放,而不是在 0 和 100 之间。这是因为我们现在看到的数字是相对于第一个锚项的中值进行缩放的。换句话说,《星球大战》现在在首映周末的搜索量是第一个主播词中值的 65%。四舍五入后的数字如下。
Scaled search volume for the movies 6 weeks prior premiere date t
相关性的微小改善并不值得,但看看一个头号通缉犯的数字,你会注意到它们现在是实值的,这意味着我们解决了第一个问题:数据现在是可比较的,因为我们根据固定的绝对值对其进行了缩放,这样做甚至变得实值化和更具信息性。然而,妈妈的数字似乎还是太高了。为了解决这个问题,我们简单地减去我们感兴趣的时间范围之前的时间序列的中值。
下图显示了 4 部电影在减去预测范围第一天前 52 周(即首映前 58 周)每个时间序列的中值后的谷歌趋势数据。
Scaled Google Trends data for the search terms Django Unchained, A most wanted Man, Mama and Star Wars after median substraction. Vertical lines mark the premiere date of each movie.
这张图说明了中位数减法的用处。每个搜索项在其首播日支配所有其他搜索项。甚至在 2014 年 8 月首播的《头号通缉犯T5》中,蓝线也占据了其他所有搜索词的首位。请注意,这并不意味着一个头号通缉犯在那段时间里比星球大战被谷歌搜索的次数更多,而是一个头号通缉犯的搜索量比前 52 周的中位数高 1 个百分点,而星球大战的搜索量比相应的中位数低 5 个百分点。所有的数据点仍然是相对于上锚位进行缩放的。
Scaled search volume for the movies 6 weeks prior premiere date t after median substraction Multiplied by factor 100.
下表显示了减去中位数后每个搜索项的数字。看看《妈妈》的数字,我们发现我们能够解决我们的模糊问题,因为它们不再像《被解放的姜戈》的数字那么高了——如果你看看访问者的数量,这就说得通了。与未缩放的数据相比,这导致了更高的相关性,并允许我们在我们的模型中使用它的无偏版本来预测首映周末的电影观众数量。
如上所述,该项目的代码,也包括预测模型和进一步的描述性分析,在 github 上,可以适应任何用例。记住锚词的重要之处:它们必须尽可能的稳定,并且应该与你所在的地区相关。使用我们的美国或印度谷歌趋势数据的锚词列表根本行不通,因为(我假设)在洛杉矶或钦奈没有人会对在阿尔高出版的本地报纸感兴趣。
使用 GPT-2 生成神奇宝贝动画集
从数据采集到为真正愚蠢的东西提供网络服务的旅程。
乐天河童是萨尔萨舞大师,他会教亚什如何像神一样移动。他会取笑亚什不能快速移动,甚至会攻击他身体虚弱。
接下来的故事讲述了某人利用一项极其复杂的技术做出了一件愚蠢的事情。OpenAI 展示的 GPT-2 模型是人工智能生成文本的游戏改变者。以至于开发该模型的团队推迟了它的公开发布,以便人们可以准备好迎接一个像假新闻这样的事情可以毫不费力地产生而无需太多人工干预的世界。是的,今天我将展示如何使用这种危险的神器来制作神奇宝贝剧集。
你可以在这里查看最终结果:所有代码都可以在我的Github资源库找到。如果你只是想看一些例子,可以跳到文章的末尾。
我不能保证网站会在任何时候回答所有的请求,因为服务器不能同时处理 3 个以上的请求(我将很快解释为什么),而且我已经脱离了在 AWS 上托管它的自由层。但是,如果网站产生一个错误,只需等待几秒钟,再试一次:)
在本文中,我将尽力解释端到端机器学习项目的主要挑战,每一步都是如此。
数据
****机器学习方法用于从数据中提取信息和推断模式。传统的统计方法在建模阶段会有统计人员选择的许多参数和假设,而机器学习方法会让数据自己说话。这是一个众所周知的可解释(经典)模型和精确(机器学习)模型之间的权衡。机器学习的预测能力基本上来自于拥有大量数据和足够复杂的模型,以从中捕捉高度微妙的模式。有一个“平滑度”假设,即模型正在一个足够大的现实样本上进行训练,以推断(概括)它没有直接看到的东西,假设它接近它确实已经训练过的某个例子(因此是平滑度)。
最近的 NLP(自然语言处理)模型没有什么不同,它们需要大量的文本和计算能力来训练。这些新模型从零语言知识开始,到最后,它们变得非常擅长从单词序列中测量上下文信息。就理解完全相同的单词在句子的不同位置具有不同的含义而言,这是经典的 NLP 模型不太擅长实现的。
GPT-2 模型是在维基百科、Reddit 和许多其他地方预先训练好的。我所做的是在来自互联网的一组更具体的文本上对模型进行微调。这是互联网的子集,由神奇宝贝动画剧集的摘要组成(并且在 Bulbapedia 上)。向 Bulbapedia 大喊,因为它是一个优秀的社区驱动的神奇宝贝网站!
我写的爬虫下载了社区写的大约 400 个剧集摘要。以下是其中一集的样本:
亚什向自己和全世界的神奇宝贝宣布,他将成为一名神奇宝贝大师。然而,他的演讲被他的妈妈打断了,妈妈告诉他去睡觉,因为明天是他的大日子。阿什抗议说,他太兴奋了,睡不着,所以他的妈妈告诉他,如果他不睡觉,那么至少要为第二天做好准备,因为她打开了由镇上的神奇宝贝专家奥克教授主持的一个节目。Ash 看着 Oak 解释新的训练者从三个神奇宝贝中选择一个开始他们的旅程;草型妙蛙种子,火型小火龙或水型杰尼龟。
我的 crawler 在文件 crawler_bulbapedia.py 上,运行时会创建一个名为 data/pokeCorpusBulba 的文件夹,它会将每一集存储在一个单独的文本文件中。
数据尚未准备好提供给模型。另一个名为 prepare_corpus.py 的脚本将清理文本,并将它们合并到一个名为 train.txt 的文件中,准备用于 GPT-2。
模型
GPT-2 是一种基于变压器的模型,它使用一种称为自我关注的技术,以一种令人惊讶的自然方式学习单词如何完成或继续句子。我不认为我能比这些倍数 优秀 来源更好地解释这个模型的数学和内部运作。但是我可以从纯编程的角度,就如何使用这个预先训练好的模型提供一些见解,就像它是一个文本生成 API 一样。为此,我找到了一个很好的资源, gpt-2-simple python 库,它使所有 Tensorflow 的复杂性基本上不可见,并提供了一些非常简单的函数,可以从 gpt-2 模型中下载、微调和采样。
基本上,语言模型试图从一个句子中预测下一个单词,我们可以不断从模型中获得预测以生成新的文本,将最后的预测作为新的输入,以获得越来越多的单词。因此,作为一个例子,我们可以为我们的模型提供前缀输入“Ash 和 Pikachu were”:
GPT-2 使用注意力机制,动态评估最后一个单词对预测下一个单词的重要性。在这个模型中有一个叫做“transformer cell”的东西,它计算输入序列中每个单词相对于其他每个单词的关注值。这些都被传递以生成输出,即预测句子中的下一个单词。
作为一个稍微简化的例子,我们可以通过注意力值的强度(紫色-er 越多的注意力)看到,显然“Ash”和“Pikachu”与确定“was”之后的内容相关。这是这个模型的一个非常好的地方,经典的“计算单词”方法,比如朴素贝叶斯方法不能做到。
通过从语料库中删除句子上的单词并微调模型以正确预测它们来进行训练。 最后,我们有一个检查点文件夹,这是我们从这个模型生成文本唯一需要的东西。tensorflow 创建的这个文件夹包含了用我的神奇宝贝语料库微调后模型的整个状态,gpt-2-simple 库在生成新文本时会寻找它。
服务器
这是迄今为止最具挑战性的部分。在互联网上为这个推理模型提供服务不是一项简单的任务,因为文本生成需要大量的内存。
基本上,服务器结构响应指向端口 5000 的 GET 请求。它有一个函数来响应这个请求,获取参数(用户输入),初始化模型,生成一些固定数量的文本,并返回 JSON 中的所有内容。困难的部分在于,该模型占用了高达 1GB 的内存来进行推理。因此,在做任何事情之前,我必须有一个具有相当大内存的服务器(再见 AWS 免费层!很高兴见到你)。所以我最终选择了 AWS 上的 EC2 t2-medium 实例,并在我的朋友joo的帮助下设置了它。
下面这个 EC2 实例内部的 web 服务器结构完全是从我的另一个朋友 Gabriela,从她受欢迎的媒体的帖子中复制来的。
我选择在这个 EC2 实例上运行的 web 服务器是 nginx,它监听请求,然后将它们转发给 uWSGI web 服务器,后者通过 WSGI 协议与 Flask 应用程序通信。基本上我们有这样的结构:
Diagram by Gabriela Melo
WSGI 协议的目的是为用 Python 编写的 web 应用程序创建一个公共接口。例如,我可以改变应用程序框架(Flask 到 Django)或应用程序服务器(uWSGI 到 Unicorn ),这对于其他部分来说是不可见的。
现在,为什么我不把 uWSGI 服务器提供给网络呢?为什么要用另一层,比如 nginx?嗯,简单的答案是 nginx 抽象出了服务器负载可能带来的一些问题,uWSGI 本身不适合处理这些问题。
当然,我不得不把所有这些软件打包在一个 Docker 容器里,因为这就是现在所有酷孩子做的事情。所有的代码都可以在我的 Github 资源库中找到,但是要阅读关于这种配置的真正深入和完善的解释,我建议访问 Gabriela 的帖子,因为我的设置基本上是相同的,有一些小的调整,因为我的应用程序有点不同。
烧瓶应用程序
Flask 应用程序(模型在服务器上运行的地方)有一个请求的入口点,即生成函数:
经验教训
- 如果你想用机器学习做出新的东西,数据是非常重要的。体面的清洁也同样重要。
- GPT-2 模型作为按需文本生成工具是不切实际的,它需要太多的内存和 CPU 来运行。拥有一个需要 1GB 内存来处理每个请求的服务是非常昂贵的。
- docker 系统修剪是你的朋友。
- web 服务器的 Python 生态系统并不那么难用,并且有大量的例子。
一些输出示例
模型的输入信息以粗体显示。当然,这涉及到一些樱桃的挑选,但这就是生成模型的方式。
****艾希和米丝蒂恋爱时正在约会。当他们都回忆起各自的第一次经历时,阿什第一次与神奇宝贝擦肩而过是他所记得的一切,因为他还是个孩子。后来,在 ash 做了他的第一个扑克球后,他跳过了午餐,去追一个朋友,并换了运动鞋。这导致他们都坠入爱河,留下道恩和布洛克泪流满面。当他们外出寻找亚什的白头龙时,一只野生的 gyarados 把他们打跑了。(…)
皮卡丘厌倦了这一切,他害怕草地,也害怕驯兽师。杰西和詹姆斯跑出去,跑出去。(…)
亚什想要成为最棒的,为了达到这个目标,他训练了所有的神奇宝贝。他告诉他的教练整个故事,并承诺成为一名伟大的教练。他告诉他的父母和朋友,他会尽最大努力训练他们。他们很惊讶,正准备放弃他时,他的父母开始哭了。他的母亲告诉他回家找他的朋友。他们别无选择,只能跟他走。
****皮卡丘因逃税被捕。三人组一走出橄榄石城神奇宝贝中心,就立刻遭到一名前警官、一名警探、一名护士 joy 和一名护士 joy 的 glameow 的攻击。
对时间序列预测任务使用梯度增强
简单的时间序列建模
时间序列预测问题在零售领域相当常见。
像沃尔玛和塔吉特这样的公司需要记录有多少产品应该从配送中心运送到商店。在劳动力管理、库存成本和缺货损失方面,即使对这种需求预测系统进行很小的改进也可以帮助节省大量的美元。
虽然有许多技术来解决这个特殊的问题,如 ARIMA,先知和 LSTMs,我们也可以把这样的问题作为一个回归问题,并使用树来解决它。
在本帖中,我们将尝试使用 XGBoost 解决时间序列问题。
我要关注的主要事情是这样一个设置需要什么样的功能,以及如何创建这样的功能。
资料组
Kaggle 大师 Kazanova 和他的一些朋友一起发布了一个“如何赢得数据科学竞赛” Coursera 课程。这门课程包括一个期末专题,它本身是一个时间序列预测问题。
在这场比赛中,我们得到了一个由日常销售数据组成的具有挑战性的时间序列数据集,该数据集由俄罗斯最大的软件公司之一 1C 公司提供。
我们必须预测下个月每个产品和商店的总销售额。
数据如下所示:
我们得到了每天的数据,我们希望建立一个模型来预测下个月每个产品和商店的总销售额。
变量 date_block_num 是一个连续的月数,为方便起见而使用。2013 年 1 月为 0,2015 年 10 月为 33。你可以把它看作月变量的代理。我认为所有其他变量都是不言自明的。
那么我们如何处理这类问题呢?
数据准备
我注意到的主要事情是,当我们试图使用回归来解决时间序列问题时,数据准备和特征生成方面是最重要的事情。
1.进行基本的 EDA 并去除异常值
sales = sales[sales['item_price']<100000]
sales = sales[sales['item_cnt_day']<=1000]
2.按照您希望的预测级别对数据进行分组:
我们首先创建一个不同的日期块数量、商店和商品组合的数据框架。
这一点很重要,因为在我们没有商品商店组合数据的几个月里,机器学习算法需要被明确告知销售额为零。
网格数据框架包含所有商店、商品和月份的组合。
然后,我们将网格与销售额合并,得到月销售额数据框架。对于没有任何销售的月份,我们也用零替换所有的 NA。
3.创建目标编码
为了创建目标编码,我们按特定的列分组,并取平均值/最小值/总和等。目标列的名称。这些特征是我们在模型中创建的第一个特征。
请注意,这些特性可能会在我们的系统中引起大量泄漏/过拟合,因此我们不会在模型中直接使用它们。我们将在接下来创建的模型中使用这些特性的基于滞后的版本。
我们按item_id
、shop_id
和item_category_id
分组,并在item_price
和item_cnt_day
列上聚合,以创建以下新特性:
We create the highlighted target encodings
我们也可以为此使用特征工具。特征工具是一个执行自动化特征工程的框架。它擅长将时态和关系数据集转换为机器学习的特征矩阵。
4.创建滞后要素
我们的模型需要的下一组特征是基于滞后的特征。
当我们创建常规分类模型时,我们将训练示例视为彼此完全独立。但在时间序列问题的情况下,在任何时间点,模型都需要过去发生的信息。
我们不能对过去的所有日子都这样做,但是我们可以使用我们的目标编码特征为模型提供最新的信息。
因此,我们的目标是在数据中添加一些特征的过去信息。我们为我们创建的所有新功能和item_cnt_day
功能都这样做。
一旦有了滞后特性,我们就用零填充 NA。
我们最终创建了许多具有不同滞后的滞后特征:
'item_id_avg_item_price_lag_1','item_id_sum_item_cnt_day_lag_1', 'item_id_avg_item_cnt_day_lag_1','shop_id_avg_item_price_lag_1', 'shop_id_sum_item_cnt_day_lag_1','shop_id_avg_item_cnt_day_lag_1','item_category_id_avg_item_price_lag_1','item_category_id_sum_item_cnt_day_lag_1','item_category_id_avg_item_cnt_day_lag_1', 'item_cnt_day_lag_1','item_id_avg_item_price_lag_2', 'item_id_sum_item_cnt_day_lag_2','item_id_avg_item_cnt_day_lag_2', 'shop_id_avg_item_price_lag_2','shop_id_sum_item_cnt_day_lag_2', 'shop_id_avg_item_cnt_day_lag_2','item_category_id_avg_item_price_lag_2','item_category_id_sum_item_cnt_day_lag_2','item_category_id_avg_item_cnt_day_lag_2', 'item_cnt_day_lag_2',...
系统模型化
1.删除不需要的列
如前所述,我们将删除目标编码特征,因为它们可能会导致模型中的大量过度拟合。我们还失去了项目名称和项目价格功能。
2.只取最近的一点数据
当我们创建滞后变量时,我们在系统中引入了很多零。我们使用的最大滞后为 12。为了应对这种情况,我们删除了前 12 个月的指数。
sales_means = sales_means[sales_means['date_block_num']>11]
3.训练和 CV 分割
当我们进行时间序列分割时,我们通常不进行横截面分割,因为数据是与时间相关的。我们想创建一个模型,看到现在,并能很好地预测下个月。
X_train = sales_means[sales_means['date_block_num']<33]
X_cv = sales_means[sales_means['date_block_num']==33]Y_train = X_train['item_cnt_day']
Y_cv = X_cv['item_cnt_day']del X_train['item_cnt_day']
del X_cv['item_cnt_day']
4.创建基线
在我们继续建模步骤之前,让我们检查一个简单模型的 RMSE,因为我们想让有一个与比较的 RMSE。我们假设我们将预测上个月的销售额作为基线模型的本月销售额。我们可以使用这个基线 RMSE 来量化我们模型的性能。
1.1358170090812756
5.列车 XGB
我们使用来自xgboost
scikit API 的 XGBRegressor 对象来构建我们的模型。参数取自这个 kaggle 内核。有时间的话可以用 hyperopt 来自动自己找出超参数。
from xgboost import XGBRegressormodel = XGBRegressor(
max_depth=8,
n_estimators=1000,
min_child_weight=300,
colsample_bytree=0.8,
subsample=0.8,
eta=0.3,
seed=42)model.fit(
X_train,
Y_train,
eval_metric="rmse",
eval_set=[(X_train, Y_train), (X_cv, Y_cv)],
verbose=True,
early_stopping_rounds = 10)
运行这个之后,我们可以在 CV 集上看到 RMSE 在 0.93 的范围内。基于我们对 1.13 的基线验证 RMSE,这是非常令人印象深刻的。因此,我们致力于部署这个模型,作为我们持续集成工作的一部分。
5.地块特征重要性
我们还可以看到来自 XGB 的重要特性。
Feature importances
结论
在这篇文章中,我们讨论了如何使用树进行时间序列建模。目的不是在 kaggle 排行榜上获得满分,而是了解这些模型是如何工作的。
几年前,当我作为课程的一部分参加这个比赛时,通过使用树木,我接近了排行榜的顶端。
随着时间的推移,人们在调整模型、超参数调整和创建更多信息功能方面做了大量工作。但是基本方法保持不变。
你可以在 GitHub 上找到完整的运行代码。
看看 Google Cloud Specialization 上的高级机器学习。本课程将讨论模型的部署和生产。绝对推荐。
将来我也会写更多初学者友好的帖子。让我知道你对这个系列的看法。在关注我或者订阅我的 博客 了解他们。一如既往,我欢迎反馈和建设性的批评,可以通过 Twitter @mlwhiz 联系。
此外,一个小小的免责声明——这篇文章中可能会有一些相关资源的附属链接,因为分享知识从来都不是一个坏主意。
使用 GraphSAGE 学习 CORA 中的论文嵌入
这里我们使用 stellargraph 库通过 GraphSAGE 算法学习 CORA 上的论文嵌入。
简介
CORA[1]是七个不同班级的学术论文的数据集。它包含论文之间的引用关系,以及每篇论文的二进制向量,该向量指定某个单词是否出现在论文中。因此,CORA 包含每篇论文的基于内容的特征和论文之间的关系特征。我们可以用一个网络来模拟这些特征,在这个网络中,每篇论文用一个节点来表示,这个节点带有基于内容的特征,引用用边来表示。
利用图模型,我们可以使用几何深度学习方法来学习每篇论文的嵌入。在这个故事中,我们使用 GraphSAGE。
GraphSAGE 是一种无监督的节点嵌入算法,因其在大型图上的成功而闻名。它可以利用节点特征和节点关系来学习表示图中邻域结构的每个节点的向量。要阅读更多关于 GraphSAGE 的内容,你可以参考链接中的故事。
归纳学习在动态数据集中很有用。这里我们讨论一个关于图的归纳学习算法。
towardsdatascience.com](/an-intuitive-explanation-of-graphsage-6df9437ee64f)
为了实现 GraphSAGE,我们使用了一个 Python 库 stellargraph ,其中包含了几种流行的几何深度学习方法的现成实现,包括 GraphSAGE。stellargraph 的安装指南和文档可以在这里找到。此外, 本文中使用的代码基于该库的 GitHub 库[2]中的示例。
图形创建
stellargraph 库使用 StellarGraph 对象来表示图形。幸运的是,我们可以很容易地从 networkx 图中初始化 StellarGraph 对象。因此,我们通过将 CORA 中的链接视为一个边列表来创建一个 networkx 图。请注意,这将自动创建必要的节点。然后,我们通过解析 cora.content 文件并从 1 到唯一单词数(1433)索引每个唯一单词,向每个节点添加基于内容的特性。我们还将这些特性分别存储在一个名为 node_features 的变量中。我们使用这些特性来创建一个 StellarGraph 对象。
**cora_dir = './cora/'
edgelist = pd.read_csv(cora_dir+'cora.cites, sep='\t', header=None, names=['target', 'source'])
edgelist['label'] = 'cites'** # set the edge type**Gnx = nx.from_pandas_edgelist(edgelist, edge_attr='label')
nx.set_node_attributes(Gnx, 'paper', 'label')**# Add content features **feature_names = ["w_{}".format(ii) for ii in range(1433)]
column_names = feature_names + ['subject']
node_data = pd.read_csv(data_dir+'cora.content), sep='\t', header=None, names=column_names)
node_features = node_data[feature_names]**# Create StellarGraph object
**G = sg.StellarGraph(Gnx, node_features=node_features)**
多亏了 networkx 和 pandas,我们将 CORA 加载到 networkx 图中,然后只用几行代码就创建了一个 StellarGraph 对象。我们现在可以使用创建的对象来实现 GraphSAGE。
模特培训
为了实现 GraphSAGE,我们将使用 stellargraph 中的模块。stellargraph 包含一个 UnsupervisedSampler 类,用于从图中对给定长度的多个行走进行采样。我们还使用 GraphSAGELinkGenerator 来生成损失函数中需要的边。注意,GraphSAGE 利用链路预测任务为相邻节点创建类似的嵌入。生成器根据采样的行走创建边。
# Specify model and training parameters **nodes = list(G.nodes())
number_of_walks = 1
length = 5
batch_size = 50
epochs = 4
num_samples = [10, 5]****unsupervised_samples = UnsupervisedSampler(G, nodes=nodes, length=length, number_of_walks=number_of_walks)****train_gen = GraphSAGELinkGenerator(G, batch_size, num_samples)
.flow(unsupervised_samples)**
生成了遍历和链接生成器之后,我们现在定义并构建 GraphSAGE 模型。构建的对象返回输入/输出占位符,供我们稍后填充。基于输出占位符,我们添加一个具有 sigmoid 激活的预测层,因为链接预测是一个二元分类问题。
**layer_sizes = [50, 50]
graphsage = GraphSAGE(layer_sizes=layer_sizes, generator=train_gen, bias=True, dropout=0.0, normalize='l2')**# Build the model and expose input and output sockets of graphsage, # for node pair inputs **x_inp, x_out = graphsage.build()****prediction = link_classification(output_dim=1, output_act='sigmoid', edge_embedding_method='ip')(x_out)**
最酷的部分来了!我们可以使用预测层和输入占位符来创建一个 keras 模型,并训练它学习嵌入!由于 stellargraph 在其代码中使用了 keras 的层,每个实现的图算法都与 keras 兼容。因此,我们可以以本机方式使用 keras 的实用程序,如损失跟踪和超参数调整。
**model = keras.Model(inputs=x_inp, outputs=prediction)****model.compile(
optimizer=keras.optimizers.Adam(lr=1e-3),
loss=keras.losses.binary_crossentropy,
metrics=[keras.metrics.binary_accuracy],
)****history = model.fit_generator(
train_gen,
epochs=epochs,
verbose=1,
use_multiprocessing=False,
workers=4,
shuffle=True,
)**
我们已经训练了边缘预测模型来学习纸张嵌入。给定一个节点,我们可以使用这个模型创建一个嵌入生成器。为此,我们创建另一个名为 embedding_model 的 keras 模型,并创建一个 GraphSAGENodeGenerator 对象。结合这两个,我们可以获得每篇论文在 CORA 中的嵌入情况!
**x_inp_src = x_inp[0::2]
x_out_src = x_out[0]
embedding_model = keras.Model(inputs=x_inp_src, outputs=x_out_src)****node_ids = node_data.index
node_gen = GraphSAGENodeGenerator(G, batch_size,num_samples)
.flow(node_ids)****node_embeddings = embedding_model.predict_generator(node_gen, workers=4, verbose=1)**
为了验证这些嵌入是有意义的,我们使用 TSNE 在 2D 上绘制了结果嵌入。我们根据数据集中的类别对每篇论文进行着色,并观察到该类别的论文在图中被分组在一起。鉴于我们在训练中没有利用职业标签,这是一个惊人的结果!
2D visualization of paper embeddings learned by GraphSAGE [2].
结论
在这个故事中,我们在 CORA 数据集上运行 GraphSAGE,并学习了论文嵌入。我们使用了 stellargraph 库,因为它提供了一个易于使用的接口,并且与 networkx 和 keras 库兼容。多亏了 stellargraph,我们可以快速尝试不同的几何不同学习方法,并比较它们的结果。这使得 stellargraph 成为创建基线和尝试基于网络的方法来解决手头问题的理想起点。
参考
[1] 科拉
[2] s tellargraph GitHub
使用图像数据确定文本结构
Painting by Patrick Henry Bruce
点点滴滴,循线而行
在我之前的文章中,我讨论了如何实现相当简单的图像处理技术来检测图像中的文本斑点。实际上,该算法只不过是在图像中找到高对比度的像素区域。然而,这个简单的过程仍然为基本的光学字符识别(OCR) Python 脚本奠定了基础。
在这篇文章中,我将讨论对前面的代码所做的添加。具体来说,我感兴趣的是通过将字母分配到它们所属的行来检测文本的结构。为了使算法更加稳健,我将结合字母“I”和“j”上面的点。阅读本系列文章的读者会记得,在前面的过程中,字母上面的点被认为是独立的对象。正如后面将要说明的,这些点会干扰图像中的检测线。
观察差距
说实话,在解决点问题之前,我已经开始探索线检测。由于我对如何实现线检测有一个模糊的理解,清理点和它们的父符号之间的间隙是一个相当大的设计决策。一种想法是使用图像中的数据,我最终需要这些数据来进行线条检测。但是这种方法感觉太不稳定了。或者,使用点的几何属性及其与周围字母的关系的解决方案似乎是更具体的解决方案。
我还是做了一些假设。首先,我认为圆点并不常见。此外,我假设一个点将有一个小面积的手写和打字文本。这个点应该是圆形的。为了将异常点与其他笔画和破折号区分开,这是必要的。有一些方法被避免了。一个想法是定位圆点,然后沿着 y 轴找到它下面的第一段文本。虽然实现的解决方案与这个想法相差不远,但是这种简单的方法对于倾斜的文本可能会失败。
That dot is not above the “i”
这些假设影响了接下来的设计。首先收集每个字母的边界框的面积。计算这些区域的平均值和标准偏差,并用于找出异常值。这种寻找点的方法基于我们的假设,即点的出现和面积是异常值。然后循环通过每个异常值,并使用 OpenCV 工具在每个异常值周围找到一个紧密的边界框。minAreaRect()"。这个函数用来计算物体的宽高比,也就是它的圆度。这个比率越接近 1,形状就越像一个圆。误差幅度包括在内。最初,我试验了一个‘±0.4’的误差。我使用的字体似乎特别加宽了几个像素,如果某些字母在它旁边的话。因此,误差幅度增加到了“±1”。
Success (Left) and failure (Right)
一旦这些点被识别出来,寻找它们父母的行动就开始了。目前使用一个基本策略。对于每个点,每个潜在的字母都被扫描。首先,字母在图像中必须较低(在 y 轴上较高)。其次,选择与点最接近的字符作为最佳选项。这是否总是给出正确的结果?不,还有很多可以改进的地方。
我想利用第二个字符的圆形,或者考虑被比较字符的两个中心之间的斜率。这两个想法都需要更多的硬编码数字,这些数字对于其他字体和风格来说不太可能是可靠的。我想将来识别一个字母有一个随机的点可能会更容易。虽然连接点并不总是产生完美的解决方案,但它大大有助于清理线条检测的图像。
一往无前
有两种方法可以找到属于同一行的所有字母。这两个想法都是基于处理从图像中提取的数据。一行文本的特征是一系列单词在页面上处于相同的高度。简单地说,属于特定行的每个字母应该有一个相似的 y 轴值。该方法受主成分分析的启发,主成分分析是一种简化数据集维数的手段。
Number of overlapping letters (Right) the projection of the constitution on the Y-axis (Left)
此时,将坐标投影到 y 轴上的想法似乎很巧妙。然而,如何从这一步开始还不清楚。最初,我想把它框架为一个聚类问题,并应用均值漂移算法来寻找潜在的聚类。我担心均值漂移需要引入额外的硬编码参数。我不想建立一个只能读取宪法文本的 OCR。
对于第一种方法,我从著名的图像处理技术 Otsu’s threshold 中获得灵感。Otsu 阈值传统上用于寻找图像的全局阈值。当直方图中有双峰分布时,效果最好。我在许多其他的项目中使用过这种方法。
阈值方法的核心是一种数学技术,可以应用于组织成直方图的任何集合。寻找阈值是基于最小化直方图数据的类内方差的技术。在 OpenCV 文档中展示了用 Python 实现该算法的有力解释。
我没有直接使用投影坐标,而是从这些点推断出更远的日期。在每一对相邻的投影点之间,我找到了位置的差异(分开的距离)。我认为 y 轴上每个点之间的距离可以区分一条新的线是否已经开始。这些距离被放入直方图中。
Histogram of typed (Left) and handwritten (Right) text
一旦确定了阈值,就依次检查每个字母。找到了字母和它所引导的字母之间的距离。如果差异大于阈值,则认为这两个字母在不同的行上。否则,字母在同一行。
这种方法对键入的文本相当有效。虽然 Otsu 法有时很难拾取线条,但当线条间距一致时,它工作得很好。手写文本没有产生成功的结果,这是一个慷慨的批评。该方法确实找到了行。然而,从测试图像来看,只找到了一半的线条。此外,由逗号、括号和下标引起的不规则性会产生假阳性。
另一种方法是将每个像素之间的距离视为一个函数。这样的函数创建一个图,其中出现新线条的区域包含一个大尖峰。同一行上的字母将产生低值区域,理想情况下为零。
这种方法仍然使用一个阈值来确定一个字母是否在下一行。取距离的平均值和标准偏差。平均值和标准偏差之和作为阈值。沿着函数的每个点都被循环通过。两个阈值通过尖峰之间的区域被认为是单线。找到这些区域的中心点就给出了那条线上的一个字母的索引。字母的底部尺寸用于确定 y 轴上线条的位置。
第二种方法产生了令人印象深刻的好结果。使用基本示例完全确定了键入的文本。一个更复杂的例子(显示在最后)确实遇到了超级脚本的问题,比如引号。手写笔迹能够找到线条,尽管结果确实更杂乱。在下面的例子中,每行字母的上方和下方都有一个红色条。上面的红线实际上是检测悬挂的上标。在打印的文本中,这通过组合我们的点而减轻。手写的文本取自线性代数讲座,并且充满了被检测为单独行的指数。
总的来说,我对我的定制方法的结果相当满意。在这种情况下,最好检测无关的线,而不是没有线。可以执行进一步的数据分析来识别哪些行属于上标。一旦确定了这一点,就可以将该线与其较低的相邻线合并。
既然我们知道了哪些字母属于哪一行,我们就可以确定哪些字母属于哪个单词。为了找到这些线,y 轴坐标被投影。为了找到单词,应该投影每行的 x 轴坐标。类似的距离算法将能够检测出哪个字母是同一个单词的一部分。
后来添加的东西
虽然我喜欢从零开始构建 OCR 的绝对起点,但对于这个项目的剩余部分,我计划切换到使用 Tesseract 。Tesseract 是 Google 支持的开源 OCR。我可能会花更多的文章来编写我自己的复杂的神经网络来解释文本。然而,我确实希望这个项目最终是强大的,我相信谷歌的好人们会在这方面帮助我。
开源代码库
快速免责声明。这个 Github 中的脚本目前非常混乱,并不代表最终代码。我计划以后花些时间清理代码。
一个基本的图像处理代码,用于检测高对比度图像上的文本
github.com](https://github.com/TimChinenov/PictureText)
Notice the error caused by the quotations between 400 and 500
使用图像分割来处理 Photoshop 图像
在这一集用 Colab 和 Python 做有趣的事情中,我们将使用深度学习从一幅图像中裁剪出对象,并将其粘贴到另一幅图像中。
深度学习部分是图像分割,也就是识别图像中的对象,我们可以随后屏蔽并最终剔除这些对象。
当作者对 Opencv 的有限知识变得太明显时,我们使用令人敬畏的 Opencv 库进行所有的剪切和粘贴。
为了好玩,让我们把一只北极熊放在杰夫·高布伦旁边。
你可以从这里开始:
编辑描述
colab.research.google.com](https://colab.research.google.com/drive/19-lKjG_8xhaniXfuANfoOPdhvPtHvkXy)
我们从下载所需的图像开始。为此,我们需要一个前景图像(裁剪对象的图像)和背景图像(粘贴对象的图像)。
我们可以观看图像。
Foreground Image Credit: https://live.staticflickr.com/1399/1118093174_8b723e1ee5_o.jpg & Background Image Credit: https://live.staticflickr.com/7860/46618564664_be235e82e8_b.jpg
为了裁剪出熊,我们需要生成一个遮罩。接下来的几个细胞这样做,我们得到:
现在我们有了蒙版,我们可以将前景图像粘贴到背景图像上。通过传入遮罩,我们可以确保只粘贴前景图像的一部分,而忽略其余部分。
耶!有用!但是我们看到熊被贴在一个尴尬的地方。让我们使用 x 和 y 滑块来调整它的位置。对于这个例子,我们只需要将熊一直移动到右边。
成功!我们在杰夫·高布伦旁边放了一只北极熊!
我相信你可以想出更多有创意的例子,所以试一试,请分享结果。😃
Colab 链接:
编辑描述
colab.research.google.com](https://colab.research.google.com/drive/19-lKjG_8xhaniXfuANfoOPdhvPtHvkXy)
这是完整的代码:
利用信息增益对兴奋性神经元进行无监督训练
寻找一种生物学上更合理的方法来训练神经网络。
传统上,人工神经网络是使用 Delta 规则和反向传播来训练的。但这与神经科学对大脑功能的发现相矛盾。不存在通过生物神经元向后传播的梯度误差信号(见这里的和这里的)。此外,人脑可以在自己的视听训练数据中找到模式,而不需要训练标签。当父母向孩子展示一只猫时,孩子并没有利用这些信息去学习构成猫的每一个细节,而只是将一个名字与猫的概念联系起来。另一方面,深度神经网络需要数以千计的有猫和没有猫的图像,以及这些图像的精确训练标签。深度神经网络将试图在其输出层学习猫的概念。但它也会尝试学习中间层中较低级别的功能。例如,该网络可以尝试学习像猫耳朵、胡须、爪子、猫眼等概念,以帮助识别整只猫。然而,问题是,这些低级概念是以一种非常间接和模糊的方式从标签中学来的,这些标签告诉我们哪些图像显示了猫,哪些没有。在我看来,这不是学习这些概念的好方法。或许更好的方法是直接从数据中学习这些概念,而不依赖于输出标签。如果我们仔细观察猫眼,我们会发现有许多低级特征,如瞳孔的形状、眼睑的形状、眼睛出现的环境等等,这些特征同时出现,构成了眼睛的模式。那么我们为什么不利用这种特征的共现来学习构成眼睛的模式呢?
如果我们回到人工神经网络研究的最开始,我们会偶然发现唐纳德 o 赫布的一些非常有趣的想法。在他的书《行为的组织》中,Hebb 认为现实世界物体的内部表征由外部刺激激活的皮层细胞组成。他称这组同时激活的神经元为细胞集合体。赫布认为所有这些细胞都是相互联系的。只要活动在神经元细胞集合的回路中循环,物体的内部表征就会保留在短期记忆中。此外,他假设,如果神经元细胞组合的激活持续足够长的时间,它将通过“生长过程”导致巩固,通过这一过程,相互回路变得更有效;一起放电的神经元也会连接在一起。因此,如果组件中只有一小部分细胞被后来的刺激激活,现在放大的互易电路将使整个细胞组件再次激活,回忆起外部刺激的整个内部表征。他的理论被称为“赫布边理论”,遵循这一理论的模型被认为表现出“赫布边学习”。Hebb 规则的等式如下,其中 wi 是突触权重, α 是学习速率, y 是神经元的输出激活,x i 是突触 i 的输入激活:
正如我们很容易看到的那样,这个等式过于简单,并且不会导致稳定的训练模型,因为权重只是增加了。
兴奋性和抑制性神经元
那么,我们怎样才能找到一种避免这个问题的希伯来人学习模式呢?首先,我们需要认识到,Hebbian 学习只描述了某一类神经元(即兴奋性神经元)的学习机制。赫比学习适用于共现特征的学习,但不适用于类别的学习。兴奋性神经元本质上是连接的。它们允许表示由许多单独特征组成的模式。另一方面,抑制性神经元本质上是分离的,并且可以将兴奋性神经元分组。它们被称为抑制性神经元,因为它们可以形成负反馈环路,抑制兴奋性神经元。兴奋性和抑制性神经元的生物学角色模型是大脑皮层的棘状锥体细胞和棘状星状细胞。锥体细胞通常表现出兴奋性特征,有些具有连接大脑其他部分的长距离轴突。另一方面,星状细胞通常是具有短轴突的抑制性中间神经元,与附近的神经元形成回路。
信息增益
由于我们只是试图训练兴奋性神经元,让我们退一步,从信息论的角度来看这个问题。如果我们将神经元视为数据源,将神经元激活视为事件,我们可以使用香农熵来测量神经元产生信息的平均速率。(在我们的例子中, k 只迭代活动和非活动两个选项。)
作为一个例子,考虑一组神经元,每个代表一个字母,其中每个神经元都有自己的熵值。如果我们认为这些神经元是独立的,我们可以简单地将熵值相加。但是这些神经元真的是独立的吗?当然不是。在一些单词中,这些字母神经元的子集经常一起被激活。因此,我们或许可以通过引入代表单词或其他类型模式的兴奋性神经元来降低网络的整体熵。这些兴奋性神经元然后压缩包含在字母神经元激活中的信息。换句话说,在一个单词神经元中,我们需要比用单个字母神经元更少的信息来表示一个单词。如果我们遵循降低网络总熵的目标,我们可能可以使用这个信息论框架来为我们的兴奋性神经元提出一个训练规则。为了调整我们兴奋性神经元的突触权重,以降低总熵,我们首先需要为我们的兴奋性神经元定义一个成本函数,我们可以在优化过程中使用它。让我们通过查看通过兴奋性神经元的单个突触 i 获得的相对熵来开始制定这样的成本函数。这种相对熵也被称为库尔贝克-莱布勒散度,可以表述如下:
其中 Xi 是输入神经元的离散随机变量,而 Y 是输出神经元的离散随机变量。对于分布 Q ,假设 Xi 和 Y 的联合概率分布为独立。这是计算相对熵的参考分布。所以,如果分布 P(Xi,Y) 也是独立的,那么信息增益为 0。
因此,我们为神经元获得的总信息增益看起来像这样:
由于我们已经向网络添加了一个新的兴奋性神经元来降低总熵,因此我们也需要考虑这个新神经元的熵:
我们需要注意的另一件事是,只考虑那些实际上参与压缩的突触。如果兴奋性神经元的输入突触的权重为 0,它也应该对成本函数没有影响。为了实现这一点,我们使用覆盖项 covi 来确定突触 i 对输出神经元的影响有多强。稍后我们将更详细地讨论如何计算 covi 。现在我们最终的成本函数看起来像这样:
兴奋性神经元模型
在我们展示如何使用成本函数来优化突触权重之前,让我们更仔细地看看兴奋性神经元的模型。该模型实际上非常简单,因为它计算所有输入突触的加权和,并使用偏差值来设置阈值。
然后通过激活函数 φ 发送结果,以计算输出激活值 y 。
兴奋性神经元和普通神经元之间的唯一区别是前者的偏置值可以拆分如下:
因此,如果兴奋性神经元的所有输入都是完全活跃的,那么 net 将等于 b 。我们使用的激活函数 φ 是双曲正切函数的正半部分。
它的导数,我们稍后会用到,由下式给出:
最后,我们已经提到的覆盖项 covi 计算如下:
这表明突触 i 能够多好地抑制兴奋性神经元的激活。如果突触权重 wi 等于或大于偏差 b ,那么 covi 值将为 1。
计数频率
由于熵的计算依赖于概率分布,我们应该简短地介绍一下这些是如何计算的。首先,通过对所有训练实例 d 上的输入激活 xd 和输出激活 yd 求和来计算频率。
然后通过将频率除以训练实例的数量 N 来计算概率分布。
然而,这种简单形式的频率计数有两个问题。首先,样本可能太小,不可靠。在这种情况下,狄利克雷分布可能有助于估计分布的可靠性,但这变得相当复杂。第二个问题是分布是一个移动的目标。由于我们不断调整兴奋性神经元的权重,这意味着概率分布也在变化。这个问题可以通过使用移动平均线来计算概率来解决。
成本函数的导数
为了得到可以调整突触权重的训练规则,现在必须计算成本函数的导数。首先,我们移除等式中的信息内容部分,并在以下步骤中将它们视为常数:
假设 covi 对于除当前训练实例 d 之外的所有实例都是常数,我们可以导出以下外部推导:
当涉及到神经元输出值的导数y’l时,我们可能需要调整几个变量。存在所有输入突触 j 的突触权重 wj 并且存在偏差。当调整突触权重 wj 时,我们需要将 b 视为常数,或者将 bc 视为常数,这取决于我们看到的是活动输入突触还是非活动输入突触。当然,如果假设一个偏置值不变,另一个也必须改变。主动输入突触的情况如下:
不活跃输入突触的情况是这样的:
偏置值的推导由下式给出:
当考虑主动输入突触的情况时,我们需要引入不对称性。如果兴奋性神经元的代价函数想要增加神经元的活性,只有增加突触的权重。这就是“什么一起点燃电线”的情况。但是如果成本函数想要降低神经元的活动,则仅降低偏差值。当输入字母出现在由兴奋性神经元表示的单词之外时,就是这种情况。在这种情况下,我们只想降低神经元的活性,而不降低突触的强度。
最终的训练规则将是这样的,其中 α 是学习率:
因此,为了训练突触 j ,兴奋性神经元的所有其他输入突触 i 被用于计算调整突触 j 的权重的目标方向。等式G′的cov′I部分充当一种用于弱突触的自举机制。等式的这一部分也防止了偏差变得太大,因为如果发生这种情况,许多突触突然变成弱突触,covi 再次变得活跃。推导*G′*也很好地抓住了直觉,即频繁激活的输入突触应该被认为是不太相关的。甚至有可能通过连接负反馈突触作为输入,以受监督的方式训练兴奋性神经元。
开放式问题
迄今尚未回答的一些问题是:
- 如何诱导新的兴奋性神经元?为了解决这个问题,我们可以从一个成熟的输入神经元开始,并开始观察是否有其他神经元同时放电。
- 如何防止重复?
对于这个问题,我们可能需要抑制神经元来形成一个反馈回路,可以抑制潜在的重复。 - 抑制性神经元的训练是什么样的?
本文描述的更新规则仅涵盖基于共现特征的训练。但是基于类别的抑制神经元的训练需要完全不同的训练方法。一种可能性是根据共有的属性将兴奋性神经元聚集在一起。
实施和相关工作
这个训练规则的实现可以在【GitHUB】上的 【爱歌】 项目的 兴奋性神经元 类中找到。 本文 给出了一些可以使用兴奋性神经元的上下文。最后, 这篇文章 描述了兴奋性神经元是如何嵌入反馈回路的。
利用 K-Means 聚类算法重新定义 NBA 位置并探索花名册的构建
项目描述和动机
NBA 中的传统位置并不能准确反映球员为球队提供的比赛风格或功能角色。打球的整体风格已经发生了巨大的变化,NBA 各个时代都表明了这一点。同样,球员的比赛风格也反映了这种变化。目前联盟的节奏很快,场地空间也越来越大。证明这一点的一个例子是中锋们投三分球并为他们的球队拓展场地。这些中心是多方面的,但仍然与传统中心归为一类,没有方法区分两者。这个项目的目的是找到一个更好的方法来定义这些球员的角色,基于他们给他们的团队带来的价值。
数据源
从篮球参考中收集信息,并收集 2011 年至 2018 年每个球员的统计数据。2011 年被用作最初的开始年,因为它反映了无位置篮球开始形成的时间(勒布朗是迈阿密的主要推动者,也是金州王朝的开始)。最终的数据集中包含了大约 3000 个观察值。总共有 30 个特征描述每个球员。功能包括方块得分指标,如分,篮板,盖帽,抢断。还使用了高级指标,如:USG%、PER 和正负分数。所有的特征都是由每 100 人拥有量来定义的。这样做是为了确保球员的统计数据是可比的,不管他们打了多少分钟或多少场比赛。游戏时间不超过 400 分钟的玩家被排除在数据集之外,因为他们对游戏没有显著影响。
初步探索性数据分析
为了更好地理解我的数据集,我开发了一些初步的视觉效果。
散点图显示一段时间内联盟平均 3 个百分点
League Average for 3 pt % over time
显示一段时间内各种特征(Ortg,Drtg,3P%)的联赛平均水平的图表
League Average for various features over time
这两个图是集中于某些特征的例子,并表明随着时间的推移呈上升趋势。这些图表的特点包括三个指针,以及展示效率和整体进攻的其他指标。从联盟的角度来看,这种变化表明球员的角色/风格也随着赛季的发展而变化。
说明常规头寸平均统计数据的图表。
Average stats by conventional NBA positions
这个图表不能告诉我们那些不符合他们传统角色的球员。举个例子,大前锋也是促进者吗?还是扮演多维角色的警卫?
显示常规头寸平均分布的饼图
Distribution of conventional positions
常规位置的聚类可视化
Clusters representing conventional NBA positosn which take all 30 features into consideration
每个点代表一个玩家。分配到相同常规位置的玩家分散在整个图形中。表明它们没有相似的演奏风格。(主成分分析用于将 30 个特征的维度减少为 3 个成分)。
聚类后…
Results After Clustering…
使用的数据科学方法
- 主成分分析(PCA)用于降低视觉效果的维数。因为有超过 30 个特征描述每个球员,所以不可能用这么多特征创建视觉集群。使用主成分分析后,所有特征中 90 %的变异仍然保留。它们被简化为 3 个组件,因此是一个 3D 模型。PCA 用于常规职位聚类的可视化,以及在实施 k 均值聚类算法后的新角色的可视化。
- K-means 聚类是一种无监督的算法,其中没有给定标签。基本思想是指定质心(n ),然后根据观察(玩家)与质心的接近程度开始聚类(分组)。
- 肘法和剪影评分。为了计算出有多少集群(新角色)是理想的,使用了 silhoutte 分数。轮廓分数解释了每个簇的密度和簇与簇之间的分离。肘方法显示随着聚类数的增加,侧影分数变化的程度。一旦速率明显下降,选择一个更高的“n”个簇的数量就没什么用了。对于这个项目,我最终总共有 9 个集群。
- 缩放功能。由于 k-means 使用距离度量来评估和分配每个观察值所属的聚类,因此缩放所有特征非常重要。比如 40 分和 2 块是没有可比性的,它们本质上是不同的单位。我还为各种特征分配了权重,以突出某些风格。例如,助攻和失误可以说代表了控球,所以与其他特征相比,它被赋予了不同的权重。
K-均值聚类后的结果
新角色饼图
分配了新角色的群集可视化
通过查看每个集群的平均统计数据,然后查看每个集群中的球员列表,标签被分配给新的 9 个集群。通过使用领域知识和聚类中揭示的内容,生成一个标签来描述该聚类。
与为传统角色生成的集群相比,现在的集群更加精简和有序。这表明集群代表了他们的比赛风格/角色,并为球队和球迷提供了更多的洞察力。
Lets take a look at how labels were assigned to each cluster
深入了解每个集群,以及如何为新角色分配新标签
值得注意的是,虽然 5-6 名玩家被列在“著名玩家”下,但实际上每个集群都有超过 250 名玩家。
集群 1 : 外围边锋/得分手
著名球员:
- 威尔森·钱德勒
- 杰伦·亚当斯
- 凯文·诺克斯(尼克斯球迷)
- 康特尼·李
- 斯坦利·约翰逊
群组 2:“三个& D”
值得注意的玩家:
- 特雷沃·阿里扎
- 肯特·巴兹摩尔
- 威尔·巴顿
- OG Anounoby
- 托里恩·普林斯
- 罗伯特·卡温顿
群组 3:“全部完成”
值得注意的玩家:
- 凯尔·安德森
- 布兰登·英格拉姆
- 达尼罗·加里纳利
- 凯文·乐福
- 凯利·乌布雷
- 扎克·拉文
集群 4:精英之翼
值得注意的玩家:
哈里森·巴恩斯
杰伦·布朗
德马雷·卡罗尔
阿隆·戈登
丹尼·格伦
埃里克·戈登
集群 5:备份 big(内部)
值得注意的玩家:
乔丹·贝尔
泰森·钱德勒
-内内·希拉里奥
科斯塔·库佛斯
阿隆·贝恩斯
集群 6:精英大佬(内部)
值得注意的玩家:
- 艾德·戴维斯
- 特里斯坦·汤普森
- 梅森·普拉姆利
- 格雷格·门罗
- 扎扎·帕楚里亚
- 马辛·戈塔特
星团 7:大恒星(内部)
值得注意的玩家:
- 史蒂芬·亚当斯
- 拉马库斯·阿尔德里奇
- 安德烈·德拉蒙德
- 尤素夫·努尔基奇
- 德里克·费沃斯
第八组:所有的星星
值得注意的球员:
- 布拉德利·比尔
- 德文·布克
- 吉米·巴特勒
- 布雷克·格里芬
- 托拜厄斯·哈里斯
- 克莱·汤普森
- 拉塞尔·维斯特布鲁克
集群 9:超级明星
值得注意的玩家:
- 扬尼斯·阿德托昆博
- 斯蒂芬·库里
- 安东尼·戴维斯
- 凯文·杜兰特
- 詹姆斯·哈登
- 勒布朗·詹姆斯
然后,我想发展更实际的洞察力,并创造了一些问题,探索名册的多样性。
商业洞察力问题
- 精英队伍&一般队伍在球员角色/风格上有什么区别?
- 获胜的球队有更多或更少的球员有特定的角色/风格吗?花名册的多样性对获胜有影响吗?
为了回答这些问题,我比较了过去 4 年中被认为“一般”(几乎没有进入季后赛或者是第 8 种子)的球队和过去 4 年中进入总决赛的球队的阵容结构。两个小组都有同等数量的来自西部和东部联盟的球队。
过去 4 年进入 NBA 总决赛的球队名单。
回答:
总的来说,NBA 决赛球队拥有更多的明星力量,他们的内线球员有一个保留/确定的角色。相比之下,“普通”NBA 球队的明星影响力更小,他们依赖明星内线作为焦点。想要赢的球队应该让他们的名单更能反映 NBA 决赛球队的饼状图。
希望你们喜欢我的项目!这里有一个链接到我的 GitHub 账户,它进一步解释了我为这个项目采取的步骤。最后,如果你想讨论我的项目或者只是谈论篮球,你可以在 LinkedIn 上找到我。
将 Kaggle 用于您的数据科学工作。
停止在你的小笔记本电脑上运行神经网络!
作为数据科学家,我们都喜欢 Jupyter 笔记本。但是,当你在处理一个非常大的数据集和/或一个复杂的模型时,你的计算机就不能胜任了。好消息是,您可以将 Jupyter 笔记本文件导入 Kaggle。如果你是数据科学的新手,Kaggle 是一个举办数据科学竞赛并提供现金奖励的网站。Kaggle 还拥有丰富的信息和一个非常愿意帮助您发展数据科学教育的社区。
Kaggle 的另一个特点是他们有免费的在线云计算(有一些限制)。因此,如果您的计算机变得太热,运行时间太长,或者没有足够的处理能力或内存来运行您的模型,您可以使用 Kaggle 的内核来运行您的代码!报名就好!
使用 Kaggle 的好处
- 免费的!有了 Kaggle 账户,你可以免费使用他们的服务器。
- 云计算。您可以运行一个模型,提交更改,去市中心,然后在另一台计算机上调出您的模型。只要你能上网,你的作品就能跟着你(不用 Git)!
- GPU。对于计算密集型机型,您可以使用多达 2 个内核和 13 GB 的 GPU RAM。对于那些买不起昂贵 GPU 的人来说,为什么不用 Kaggle 的呢?
- 笔记本或脚本。以你喜欢的方式导入你的代码!
- 没必要
pip install
。Kaggle 已经预装了大多数 python 包(你甚至可以pip install
打包 Kaggle 不支持的包)。 - 黑暗模式。因为这样更好。
缺点和局限性
不全是神经网阳光和内核彩虹。首先,Kaggle 归谷歌所有。因此,如果你对 Alphabet 在其服务器上安装面部识别模型感到不安,那么 Kaggle 的内核可能不适合你。
还有,你网页上运行的内核,没有用户输入只能运行一个小时。所以如果你运行你的模型,离开超过一个小时,你的内核就会停止。您将丢失所有的输出,并且必须重启您的内核。您可以通过提交代码来克服这个问题。代码将在一个独立的内核中运行,而不是你在网页上看到的那个。但是提交的一个警告是,提交的内核的输出只有在内核完全运行后才能看到。因此,如果您的总运行时间是 5 个小时,那么您不能在 5 个小时内检查您提交的内核。如果你的代码有一个致命的错误,你要到 5 个小时后才能知道🙃
以下是使用 Kaggle 时的硬件和时间限制:
- 9 小时执行时间
- 5gb 自动节省的磁盘空间(/kaggle/working)
- 16gb 的临时暂存磁盘空间(外部/ka ggle/工作)
CPU 规格
- 4 个 CPU 内核
- 16 千兆字节的内存
GPU 规格
- 2 个 CPU 内核
- 13 千兆字节的内存
如果你要用这些规格制造一台计算机,你很容易就能花掉 1000 多美元。只要确保您的数据少于 16GB 的磁盘空间(除非您使用 Kaggle 数据集),并且运行速度可以超过 9 小时。如果您的模型可以在这些限制下运行,那么上传您的数据并开始工作吧!
Kaggle 入门
- 登录您的 Kaggle 帐户
- 在顶部栏中,点击笔记本
- 然后选择新笔记本
- 选择 Python 或 R
- 选择编码风格
- 如果要使用 GPU,点击显示高级设置,然后在上选择 GPU
- 然后点击创建
ka ggle 内核
Your new online Jupyter Notebook.
如果你选择了笔记本风格,你会有宾至如归的感觉。要上传您的数据,点击右上方的**+ Add Data**
**。**您可以选择预先存在的 Kaggle 数据集或上传您自己的数据集。请记住,您只能使用 16GBs 的数据。
在右边栏,你可以跟踪你的在线内核。“会话”选项卡会记录您有多少可用的计算能力。将您的工作区选项卡想象成一个 GUI 文件结构。如果您使用 Kaggle 数据集,您的文件将位于/kaggle/input/your-kaggle-dataset
中。如果你上传了一个数据集,你的文件将会在/kaggle/input/your-uploaded-data
中。在“设置”选项卡上,您可以更改之前设置的设置。
现在你都准备好了!编码并享受您的免费在线笔记本。当您完成或准备提交时,点击右上角的提交按钮。您的代码将在一个单独的内核中运行。一旦你所有的代码都运行了,它就变成了一个版本。您可以返回到任何版本或您提交的代码,并查看输出(如果它运行正常)。
如果你正在提交一个 kaggle 竞赛,你将进入你的内核版本。在左侧点击输出。如果您有一个. csv 输出,您将能够在这里看到它。选择您的。cvs 文件,点击提交参赛。
Kaggle 是数据科学家的强大工具。他们甚至有关于 python 的课程,使用熊猫和神经网络,都使用它们的内核。另一个免费的在线云服务,请查看 Google Colab。
使用 Keras 和 TensorFlow 预测登革热暴发
使用时间序列天气数据的机器学习预测模型。
Image licensed from Adobe Stock
什么是登革热?
登革热通常被称为登革热,是一种蚊媒疾病,发生在世界的热带和亚热带地区。在轻度病例中,症状与流感相似:发热、皮疹、肌肉和关节痛。在严重的情况下,登革热会导致严重出血、低血压,甚至死亡。
因为登革热是由蚊子传播的,所以登革热的传播动态与温度和降水等气候变量有关。尽管与气候的关系很复杂,但越来越多的科学家认为,气候变化可能会产生分布变化,这将对全世界的公共卫生产生重大影响。
近年来,登革热一直在蔓延。历史上,这种疾病在东南亚和太平洋群岛最为流行。如今,每年近 5 亿例病例中的许多发生在拉丁美洲(邓艾:预测疾病传播,挑战总结)。
准确预测给定地区将感染登革热的人数的能力对于公共卫生官员、医生以及最终处于感染风险中的任何人都具有重大价值。使用来自美国国家海洋和大气管理局 (NOAA)和黑暗天空的历史数据,我的目标是创建一个机器学习模型,能够准确预测两个地方每周将发生的登革热病例数:波多黎各的圣胡安和秘鲁的伊基托斯。
该图显示了从 1990 年中期到 2008 年初波多黎各圣胡安每周报告的登革热病例。
在圣胡安,报告的登革热病例通常在一年的后半段达到高峰,显然有些年份感染人数会显著增加。据了解,这与蚊子数量的增加有关。蚊子在温暖潮湿的条件下和雨量充足的时候更容易繁殖。这些条件创造了更多的静水区域,因此有更多适合产卵的地方。
埃及伊蚊与“领域知识”
Image licensed from Adobe Stock
一只雌性埃及伊蚊吸血后,平均会产下 100 到 200 个卵,一生中最多能产五次。卵产在可能暂时被洪水淹没的潮湿表面,如树洞和人造容器,如桶、鼓、罐、盆、桶、花瓶、花盆托盘、罐、废弃的瓶子、罐、轮胎、水冷却器等。以及更多收集或储存雨水的地方。她分开产卵,不像大多数物种。并非所有的卵都是一次产下的,但它们可以分散几个小时或几天,这取决于是否有合适的基质。鸡蛋通常被放置在吃水线以上不同的距离。此外,她不会在一个地方产卵,而是将卵分散在几个地方。卵可以在干燥的环境中存活一年以上,但是当它们在潮湿的环境中时,它们可以在短短两天内孵化。剩下的成长阶段到成年可能在短短六天内发生(【denguevirusnet.com】T2)。
当训练机器学习模型时,了解这种生命周期是有用的,因为天气事件和/或条件之间可能存在关系,从而产生持续足够长时间的静止水,以使蛋达到成熟。在数据科学领域,这种类型的信息被称为“领域知识”。拥有广泛领域知识的数据科学家在其专业领域内开发机器学习模型方面具有优势。
获取数据
如上所述,数据是从两个来源获得的。NOAA 的每周数据已经准备好了 CSV 文件供下载。更详细和日常的黑暗天空数据必须使用 API(应用程序编程接口)获得。大约有 15,000 个 API 调用了 Dark Sky 来获取这两个城市的每日天气数据。数据被保存到文本文件中,随后被处理以创建 CSV 文件供重复使用。
这个项目的数据类型被称为时间序列。时间序列数据需要独特的模型、考虑因素和机器学习的预处理。尤其是缺失值,必须在训练任何模型之前解决。以下是这两个地方每天可用的天气变量的示例,包括圣胡安的 6,073 条记录中每个变量的缺失值计数:
date 16
latitude 0
longitude 0
timezone 0
offset 0
apparentTemperatureHigh 18
apparentTemperatureHighTime 18
apparentTemperatureLow 17
apparentTemperatureLowTime 17
apparentTemperatureMax 18
apparentTemperatureMaxTime 18
apparentTemperatureMin 18
apparentTemperatureMinTime 18
cloudCover 175
dewPoint 18
humidity 18
icon 16
moonPhase 16
precipIntensity 3695
precipIntensityMax 3695
precipIntensityMaxTime 5041
precipProbability 3695
precipType 1561
pressure 18
summary 16
sunriseTime 16
sunsetTime 16
temperatureHigh 18
temperatureHighTime 18
temperatureLow 17
temperatureLowTime 17
temperatureMax 18
temperatureMaxTime 18
temperatureMin 18
temperatureMinTime 18
time 16
uvIndex 16
uvIndexTime 16
visibility 3918
windBearing 18
windGust 4854
windGustTime 4854
windSpeed 18
几乎所有的变量都有缺失值,有些接近 80%。我如何处理这些缺失数据的细节可以在 GitHub 的 Jupyter 笔记本上查看。如果你想了解更多关于处理缺失数据的内容,我在这里写了一篇关于这个主题的简短文章:走向数据科学。
在所有的数据被获取、清理和评估之后,它就可以用于一些机器学习模型了。
构建序列模型
我使用 Keras 和 Tensorflow 库来构建我的模型。如果使用 LSTM 的递归神经网络的细节听起来很无聊,就跳过这一节。
当我写这篇文章时,LSTM(长短期记忆)是 Keras 库中处理时序数据最强大的层,但它也是计算开销最大的层。如果您的硬件平台对于给定的时间序列项目足够强大,我推荐尝试 LSTM。
我开始使用的基本模型如下:
model = models.Sequential()
model.add(LSTM(103, input_shape=(train_X.shape[1],
train_X.shape[2])))
model.add(Dense(50, activation='relu'))
model.add(Dense(1))model.compile(loss='mae', optimizer='adam')
然后,我尝试添加不同比例的脱落层:
model = models.Sequential()
model.add(LSTM(103, input_shape=(train_X.shape[1],
train_X.shape[2])))
**model.add(Dropout(0.3)) # 0.3, 0.4, and 0.5 were tested**
model.add(Dense(50, activation='relu'))
model.add(Dense(1))model.compile(loss='mae', optimizer='adam')
添加下降图层显著提高了模型的性能。丢弃层可以是防止模型过度拟合的有效工具,这是机器学习中的一个常见问题,我计划很快写更多关于使用丢弃层的内容。
该模型使用平均绝对误差来评估其性能。
做预测
处理时序数据的另一个挑战是无法使用复杂的数据分割方法,如交叉验证。对于时态数据,训练/验证/测试分割的可用方法是有限的,超出了本文的范围。关于这个主题的更多信息,请看杰森·布朗利**、**博士的这篇优秀文章
我希望训练和测试数据都包括登革热感染低和高的年份。我发现下面的分割很适合这个项目:
以下是我用来评估模型的输出示例:
在本例中,该模型使用四周的天气数据来预测下一周的登革热病例数。蓝线表示模型在训练数据上的表现,越低越好,橙线表示模型从未“见过”的数据上的表现。从左到右代表模型的每次连续迭代(在机器学习术语中称为“时期”)。上面的例子代表了我测试的模型参数的最佳组合:
- 回顾期(模型用于预测的前期周数)
- 丢弃比例(在一个时期内随机“关闭”的节点的比例。
- 要运行的时期数
我用这些参数的几种组合进行了测试。
下面的图表显示了最佳模型预测的案例数与实际案例数的对比:
7.68 天的平均绝对误差(实际病例和预测的登革热病例之间的平均差异)表明还有改进的空间,但该模型确实准确预测了第 95 周左右超过 150 例的巨大高峰。正如本文开头所提到的,这样的信息对于一个地区的决策者来说具有巨大的价值。
展望未来,我将尝试几种方法来改进这一模式:
- 尝试用不同的方法从每天的黑暗天空数据中计算出一周的数值。
- 通过获取每周的值并从上周的值中减去它们来区分数据。
- 测试其他一些为时序数据设计的机器学习模型。
感谢您阅读我的项目——我对数据科学让世界变得更美好的潜力充满热情!
简单介绍一下我:
在毛伊岛拥有/管理了一家旅馆 15 年后,我搬到了旧金山,开始了自己的科技职业生涯。我将于 2019 年 9 月完成 Flatiron School 数据科学项目,并于 2022 年获得约翰霍普金斯大学的数据科学硕士学位。
使用 LinkedIn 个人资料图片进行面部识别
Photo by Kyle Glenn on Unsplash
您可能听说过《华盛顿邮报》上的一则报道,政府机构正在使用机动车管理局(DMV)的数据进行面部识别。你可以在这里找到那篇文章,但是当你看完之后,请回到这里。
虽然我知道这是非常令人担忧的,但我们作为一个社交媒体社会泄漏(消防水管?)几乎公开的个人信息。
我将向你们展示我是如何使用一张 LinkedIn 个人资料图片来创建一个面部识别系统的,这些图片来自许多同事的 LinkedIn 个人资料,全部使用免费的开源工具。政府不需要翻遍车管所的记录——他们只需要登录 LinkedIn 或脸书或任何其他社交媒体网站。我们每天都在多次放弃我们最容易识别的属性——我们的脸。
题外话:由于我没有权限展示或使用我在本文中实际使用的同事的 LinkedIn 个人资料照片,我将保持它的抽象性。但是你要做的就是收集一些图片。
背景材料
我通常参考的技术站点是 PyImageSearch.com 的和 MachineLearningIsFun.com 的和。在这种情况下,我结合了以下帖子中的信息:
- PyImageSearch,利用 OpenCV、Python 和深度学习进行人脸识别
- PyImageSearch, Keras 图像数据生成器和数据扩充
- MachineLearningIsFun,深度学习的现代人脸识别
如果你对我的 Github 回购感兴趣,你可以在这里找到。然而,你不会发现我使用的任何个人资料图片。此外,我只在 Mac 上运行过,我不确定对于 Windows 用户来说这些指令会有什么变化。
灵感
几个月前,我使用上面的第一个链接在我的电脑上进行面部识别,我用电脑摄像头拍了大约 30 张自己和其他几个人的照片。PyImageSearch 文章使用了一个名为 face_recognition 的库,它是由 MachineLearningIsFun 网站的所有者编写的。让我印象深刻的是,要获得良好的面部识别结果,一个人的不同照片是如此之少。
在阅读了 ImageDataGenerator 上的 PyImageSearch 文章和《华盛顿邮报》的文章后,我想知道我是否可以用单张照片训练一个面部识别模型,并生成同一张照片的 30 张新的增强图像。
对于单张照片,我选择 LinkedIn 作为我的来源,因为大多数人都有正面的干净照片。
训练一个模特的流程是怎样的?
第一步——下载一些 LinkedIn 个人资料图片
我下载了大约 8 张同事的个人资料照片,这样我可以很快试验出结果。LinkedIn 个人资料图片的好处是它不是很大,并且创建面部编码相对较快。
技术细节
在“original_images”文件夹中创建一个格式为“firstname_lastname”的文件夹,并将图片放入该文件夹中。
第 2 步—扩充个人资料图片
要了解图像增强,请参见我上面提到的这篇文章。PyImageSearch, Keras ImageDataGenerator 和数据扩充。
在那篇文章中,作者向您展示了如何创建一个 Python 脚本来使用 Keras 从原始图像创建 30 个增强图像。增强后的图片是同一个 LinkedIn 个人资料图片——但在增强后,图片可能会稍微旋转,或垂直翻转,或稍微剪切。
当你完成所有的个人资料图片后,你应该有一个数据集文件夹,子文件夹中有人名,每个人名文件夹将有 30 张(或你选择的数量)LinkedIn 个人资料图片。
技术细节
要从一个图像生成多个图像,您可以在终端执行:
python pr_generate_images.py --image original_images/john_public/john_public.jpg --output generated_images/john_public --total 30 --prefix jp
在 generated_images 文件夹中,将人名作为文件夹名是很重要的。编码过程使用文件夹名称作为目标或标签值。
对您下载的每张 LinkedIn 个人资料图片运行以上命令
完成后,检查 generated_images 目录中的图像。你会看到 30 张图片,每一张都略有不同
步骤 3-从数据集创建面部编码
使用这些文章中的材料:
- PyImageSearch、用 OpenCV、Python 和深度学习进行人脸识别
- MachineLearningIsFun,深度学习的现代人脸识别
你会明白面部识别软件在照片中定位一张脸,然后识别 68 个兴趣点。从那里,它创建一个 128 维值向量。对于每个扩充的 LinkedIn 个人资料图片,收集 128 个值的向量,以及与该向量相关联的人的姓名。
这些信息最终被收集到一个表格中,表格中的每一行都代表一张个人资料图片,并带有一个人名标签列。这个集合被腌制并被称为编码。
技术细节
现在是时候创建一个单独的编码文件了。您可以通过以下方式实现这一点:
python encode_faces.py --dataset generated_images --encodings-file encodings/linkedin_encodings.pkl
这将对“generated_images”父目录中的所有目录进行编码,并将编码文件写入编码目录。这个编码文件包含每张图片的 128 维编码,以及标签,在本例中是人名。
第 4 步—启动笔记本电脑网络摄像头并预测
使用本文、PyImageSearch、 Face Recognition with OpenCV、Python 和 deep learnin g 中的信息,您将了解如何创建一个脚本来读取网络摄像头,逐帧捕获输入帧,并运行 face_recognition 包来预测视频帧与步骤 1–3 中的编码人脸的最接近匹配。
技术细节
现在,您可以打开计算机的摄像头,查看面部识别功能是否正常工作。执行以下操作:
python recognize_faces_video.py --encodings-file encodings/linkedin_encodings.pkl --input camera
如果一切顺利,你下载了你的个人资料图片,你应该看看是否预测自己。
结果
在我按照本文描述准备的 8 张 LinkedIn 个人资料照片中,所有照片都正确地识别了我的同事。
如果你有兴趣亲自尝试一下,我强烈推荐背景材料中的链接。他们将为您提供所有必要的技术背景和示例实现。
来自我的 LinkedIn 个人资料图片:
LinkedIn Profile Picture
并生成 30 张新图片:
30 Auto Generated and Augmented pictures
我可以训练一个面部识别模型来识别我。
离别的思绪
人们对面部识别的使用有很大的担忧,这种担忧是有充分理由的。然而,我们不能把这个技术精灵放回瓶子里——所以我们最好理解它是如何工作的,以及用一张照片创建一个面部识别系统是多么容易。政府不需要搜索车管所,只需点击任何一个社交媒体网站,拉一张照片。
使用机器学习算法
如何评估和改进机器学习模型的指南。
此时,您必须对线性回归、逻辑回归和神经网络等最常用的机器学习算法有一个基本的了解。但是,为了有效地应用这些算法,您可能需要阅读一些要点。
A note on the notation. x_{i} means x subscript i and x_{^th} means x superscript th.
特征工程
很明显,一个好的数据集可以产生一个好的模型。然而,考虑这样一个场景,你在一家计算机公司工作,你的新任务是预测服务器多久崩溃一次。为了构建模型,该公司向您提供了如下所示的服务器日志。
- - [30/Apr/2019:14:57:29 +0530] "GET /launched-version/scripts/vendor/angular-animate.min.js.map HTTP/1.1" 404 547 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36"
您不能立即使用日志文件来开始预测服务器的健康状况。将原始数据转换成计算机可以操作的数据集的任务被称为*特征工程。*诸如服务器的运行时间、服务器的停机时间、客户端经历的等待时间、服务器抛出的错误类型或任何可测量的事物都可以被认为是特征。目标是创建有助于学习算法做出正确预测的特征。
现在,让我们来看几个用于特征工程的方案。
一键编码
大多数学习算法都适用于数值。但是,特征向量有可能是分类的。在这种情况下,我们需要将分类变量转换成数字变量。假设分类变量“血型”有四个可能的值——A、B、AB 和 o。我们可以使用以下映射将“血型”特征转换为数值。
A = [1,0,0,0]
B = [0,1,0,0]
AB = [0,0,1,0]
O = [0,0,0,1]
请注意,转换增加了特征的维度。人们可能会争论执行编码,其中 1 对应于 A,2 对应于 B,3 对应于 AB,4 对应于 o。但是,这应该避免,因为它意味着值之间的排序。它可能会混淆学习算法,因为算法会试图在没有订单时找到订单。这也可能导致过度拟合。
扔掉
可能会出现将数值转换为分类值的罕见情况。宁滨允许进行这样的转换。例如,不是将“标记”表示为单个连续的特征,而是可以将它分成箱:100-90 之间的标记放入一个箱,89-80 之间的标记放入第二个箱,依此类推。
Example of Binning
宁滨告诉学习算法,重要的不是特定的数值(比如 85 分),而是数值的范围(比如 89-80 分)。它有助于更好地概括学习算法。
分割数据集
假设约翰被分配了训练机器学习模型的任务。他决定训练一个线性回归模型。
A linear regression model *f(x)*,is a linear combination of the features of the input examples *x*, and is represented by *f(x) = wx+b*.Transforming the original features (consider a 1-dimensional feature vector *x*) by changing their degree such as squaring or cubing them results in polynomial models (such as *f(x) = wx²+b* or *f(x) = wx³+b*).
然而,他在为他的线性回归模型选择度 d 时遇到了麻烦。变量,如定义模型本身的度被称为超参数。超参数甚至在训练开始前就已经设置好了。
一个解决方案是将数据集分成三个子集—
- 训练集— 70%
- 验证集— 15%
- 测试集— 15%
在分割数据集之前,将数据集随机化是很重要的。此外,分割应该代表原始数据集。
The idea behind splitting the dataset so that the split is a good representation of the original data set.
分割完成后,训练集被用于训练,这为我们提供了一个模型。然后根据验证集评估获得的模型。
数据分析师可能会调整 hyper 参数,在训练集上训练模型,在验证集上评估模型,最后选择在验证集上表现最佳的模型。一旦选择了最佳模型,就要在测试集上再次评估它的性能和通用性。
拥有测试集的想法是,持续使用训练集和验证集来调整超参数可能会“磨损它们”,并导致过度拟合的模型,而不是一般化的模型。
Using the three sets.
欠配合与过配合
如果算法欠拟合,则称其具有高偏差。也就是说,算法对数据有很强的先入之见,不想去适应数据集。另一方面,过拟合算法被认为具有高方差。
Over fitting and Under fitting
一个模型很可能不适合——
- 模型对于数据来说过于简单(例如,线性模型可能不适合);
- 这些特征的信息量不足(假设您想要预测某个学生是否会通过某项考试,但是用于解决问题的特征是该学生的身高和体重。显然,功能的选择是有问题的。它们与手头的问题不相关)。
一个模型可能会过度拟合,以防—
- 模型对于数据太复杂(考虑包括所有二次和三次项来训练简单的线性回归模型);
- 您有太多的特征,但训练示例的数量较少(只有 50 个示例的数据集,其中每个示例有 100 个特征,这可能会过度拟合,因为算法没有足够的信息来观察数据集所有特征的模式)。
防止过度配合最常用的方法之一是调整。
规范化
正则化有助于降低学习算法的复杂性。观察下面的等式—
Regularisation
这里的 J 是未调整的成本函数。我们的目标是找到假设函数 f(x) 的参数 w 和 b 的值,使得非正则成本函数值最小。
现在观察正则化项,它只是我们假设函数 f(x) 所有参数的平均值。
正则化成本函数 J_{reg} 是这两项的简单总和。现在的目标是最小化正则化成本函数*。*由于 J_{reg} 是两项之和,因此最小化其中任何一项都会导致其最小化。正则项给新的成本函数增加了额外的负担。成本函数上的每次迭代都受到正则化项的惩罚,因此,参数值随着每次后续迭代而被简化,从而防止过拟合。
λ 是一个超参数,用于控制惩罚成本函数的强度。如果 λ=0 ,则成本函数完全不被惩罚,导致高方差模型。在 λ 被设置为非常大的值的情况下,通过将大多数参数 w_{i} 设置为零来最小化成本函数,这导致高偏差模型。通常会测试一系列的 λ 值,并为模型选择在验证集上表现最佳的值。
学习曲线
学习曲线也有助于解决高偏差和高方差的问题。针对训练示例的数量绘制了由验证集和训练集生成的误差的图表。有目的地限制训练样本的数量,并绘制误差图。诸如均方误差函数的任何标准误差函数可用于计算验证集和训练集的误差。
Learning Curve
训练误差最初很小,因为训练样本的数量较少,或者我们可以说要处理的数据中的变化较少。然而,随着训练样本数量的增加,数据内的变化也增加,因此训练误差也增加。
最初,使用少量训练示例来训练模型,因此它不能很好地概括,从而在验证集上给出较高的误差。但是,随着训练样本数量的增加,模型开始泛化,验证集上的错误减少。
在过拟合模型的情况下,训练集和验证集的误差之间的差距很小,并且两个误差都取大值。因此,如果一个算法显示了这种行为,那么获得更多的训练数据将没有任何帮助。
另一方面,在欠拟合模型的情况下,训练集和验证集的误差之间的差距相对较大,验证误差也保持较高,因为模型不能很好地概括。这种行为可以通过收集更多的训练样本来改进。
模型性能指标
一旦我们有了模型,我们就使用测试集来评估模型的性能,并决定模型的泛化程度。有一些评估指标可以帮助我们分析模型的性能—
混淆矩阵
混淆矩阵总结了分类问题的预测结果。考虑下面的混淆矩阵—
Confusion Matrix
从该表中可以得出结论,在总共 25 封实际垃圾邮件中,该模型正确地预测了 20 封是垃圾邮件(真阳性),然而将 5 封电子邮件误分类为非垃圾邮件(假阴性)。类似地,在总共 510 封非垃圾邮件中,该模型正确地将 500 封电子邮件标记为非垃圾邮件(真阴性),但是将 10 封非垃圾邮件误分类为垃圾邮件(假阳性)。
混淆矩阵让我们了解学习算法所产生的错误模式。假设一个模型具有较高的假阴性率,这意味着该模型经常将垃圾邮件预测为非垃圾邮件。在这种情况下,用更多的垃圾邮件例子训练模型听起来是一个合理的选择。
在多类分类的情况下,使用一个对所有的方法,其中除了感兴趣的类之外的所有类被分组为单个类,从而将我们的问题转换为二元分类问题。一个 n 类多分类器给了我们 n 个混淆矩阵。
A classifier predicting three classes High, Moderate and Low is evaluated for the High class using a confusion matrix.
精确
精度是模型错误警报率的量度。
Calculating Precision of a Model
考虑到上面的混淆矩阵——在被模型分类为垃圾邮件的 30 封电子邮件中,20 封实际上是电子邮件,而剩余的 10 封被错误地标记为垃圾邮件,因此我们的准确率为 66%,误报率为 33%。
回忆/敏感度
Recall also known as Sensitivity.
召回反映了该车型的敏感度。它显示了真实阳性率。对于上述混淆矩阵,在总共 25 封实际垃圾邮件中,该模型正确地标记了 20 封电子邮件,但是将 5 封电子邮件错误地分类为非垃圾邮件,从而使得该模型对将电子邮件标记为垃圾邮件 80%敏感。
特征
特异性是一个相关的衡量标准,它告诉我们有多少被归类为非垃圾邮件的电子邮件实际上不是垃圾邮件。它测量真实负利率。上述混淆矩阵具有 98%的特异性。
Specificity.
作为一名分析师,我们希望控制精确度和召回率之间的平衡。大多数情况下,这取决于手头的问题——考虑一个试图将患者分类为癌症或非癌症的模型的情况。在这种情况下,人们可能希望有更高的回忆,也就是说,你不希望将实际的癌症患者错误地分类为健康的(避免假阴性)。无论如何,较低的准确率是可以接受的,这意味着,将一个健康的人归类为癌症患者并对他进行疾病测试(愿意接受假阳性)不会有什么坏处。然而,如果我们能以某种方式将精确度和召回率合并成一个术语,那将会非常有帮助。这将为我们提供一种更简单的方法来使用单一指标评估和比较模型,而 F-measure 正是这样做的。
f-测度
The F-Measure value.
数学上,F-measure 是精度和*召回的调和平均值。*对较低的值赋予较高的权重。如果精度或召回率达到一个较低的值,那么 F-measure 就会下降,迫使我们调整我们的模型以给出更好的 F-measure 值。
使用机器学习将来自大脑的 EEG 信号分类为单词
这篇文章最初发表在我 2017 年的旧博客上,我现在再次拾起它,并将在不久的将来研究扩展这项技术,特别是智能手机和物联网设备的内置 AI/ML 功能…
这篇博文的标题差不多就是 TL;我已经尝试过(并取得了一些成功)的博士,尽管我可能一开始就不够资格去尝试。我的想法是在受试者思考说某些单词时从大脑中收集数据。在那里,我会用收集到的数据训练一个机器学习分类算法,然后使用得到的模型实时读取受试者的脑电波,并将它们分类为他们可能试图说的话。预期的结果将是一个应用程序,它可以读取试图说话的人的想法,并将这些话输出给他们试图与之交流的人。
这是在我祖母被诊断出患有一种叫做延髓麻痹的运动神经元疾病之后发生的,这种疾病开始影响她说话的方式。随着病情恶化,我开始想办法让她能够交流。她去看的医生给她提供了一个不合适的 iPad,而且对她不起作用。意大利语是她的第一语言,当她试图拼写英语单词时,使用苹果的文本到语音引擎就不会正确地拼写出来。我设法将安卓手机的文本到语音引擎切换到谷歌的文本到语音引擎,这允许她使用带有意大利口音的扬声器,据说这是用来将意大利文本转换成意大利语音的。有趣的是,当她用被认为是说意大利语的引擎用英语写作时,你能理解她想说什么。
下一个要考虑的问题是,如果她失去了双手的功能,或者有人试图使用这种方法,但他不会拼写,就像我祖母这个年龄的许多人一样。这是我想到用机器学习把脑电波转换成文字的时候。我对脑电波知之甚少,而且当时我只是狂热地阅读和宣传机器学习,并没有真正将其付诸实践。
读取脑电波
我在某处看到了脑电图(EEG)读数,意识到这些读数一定是由来自大脑的原始数字组成的,所以用某种软件解析它们会有多难。当我看到另一个关于 NeuroSky MindWave 移动耳机的视频时,我正在看一个 YouTube 视频,视频中有一个人把湿海绵绑在他的头上。你可以在这里阅读《科学美国人》的一篇文章,但它本质上是一个用来制作游戏和冥想应用的设备,符合我的两个需求:
1)它能读取脑电波,而
2) 它有一个 API ,我可以用它来读取原始数字格式的脑电波。
我不希望任何内部软件处理我的数据,尽管 MindWave 有一个内部处理器来确定你有多放松或专注,但它也允许你获得一个原始的脑电图读数,可以很容易地分成正确类型的脑波类别。
我从阅读 Neurosky 的文档中了解到,SDK 会解析来自大脑的数据,并将其放在以下频带中:
这些值与我的想法之间的联系是,这些类别的脑电波用于解释大脑的不同状态,如上表右栏所述。更重要的是,我可以将这些值传递给机器学习算法,让它在识别这些值中的特定模式时预测一个人在想什么。我想依靠机器学习算法进行分类的原因是,我知道数据中会有噪声和差异,我无法可靠地用代码中的基本条件过滤掉自己。
Python 原型
在我花 150 美元买耳机之前,我决定做一个软件原型来测试我的想法是否真的可行。我首先完成了微软机器学习工作室的课程,然后进行了一些涉及住房数据的测试实验;预测房价等。一旦我很高兴四处导航并熟悉了不同算法的能力,我就开始用 Python 模拟一些脑电图数据。在 Python 中,我使用了下面的脚本,我已经将它上传到 GitHub 来将我的测试数据生成到一个 csv 文件中,然后我可以将它上传到我在 Azure 中的机器学习实验中。我让数据重叠,试图模拟真实数据中会出现的一些差异和噪声。我拼凑了一个机器学习实验来测试我的模拟数据,在我对结果感到满意后,我从 Neurosky 订购了耳机。
该系统
然后,我继续设计一个整体系统,这样我就能够朝着某个目标努力,而不会忘记我在做什么。我将我的项目命名为 Cerasermo,并提出了以下粗略的系统图…
该系统包括各种子系统,例如保存用户数据和潜在的大量 EEG 数据的数据库。我也有一些小的子程序计划去做一些我认为在解析大脑数据时可能需要做的事情。一个这样的子程序将处理这样一个事实,即我需要根据受试者的性别和可能的其他我还不知道的因素对数据进行归一化。这个想法是我从英格丽德·尼乌文胡伊斯和弗雷德里克·鲁在 T2 的交流中得到的,他们讨论了男性和女性的脑电图数据可能不同。我计划采用的跨主题数据标准化的实际实现是在 R-Bloggers 上发布的。然而,就目前而言,实施将针对单一主题;我自己,最终还有我的祖母。
安卓应用
我开始编写一个 Xamarin 应用程序,因为我熟悉这个框架,并且以前有过将 C# Xamarin Android 应用程序发送到 Google Play 商店的经验。当我将蓝牙添加到实现中时,我遇到了问题,因为 Xamarin 当时不支持常规的蓝牙(只有低功耗的蓝牙实现)。这迫使我切换到 Android Studio,并因此将我的应用程序语言从 C#翻译成 Java。我最终得到了计划中的 Cerasermo 系统的一个极其简化的版本;我让我的应用程序从耳机中读取 EEG 数据并导出到文件中。这整个过程,包括上传到我的 Azure 机器学习实验,非常慢,最终如果要按照我的计划在日常对话中使用,系统将需要完成。
Azure 机器学习
我使用了下面的机器学习实验,其中我将我的数据一分为二,以便我可以并排训练和测试我的两个入围算法…
我这里的两个分类算法是多类决策森林和多类神经网络。我使用了微软的调谐模型超参数模块与整个网格扫描,以便自动选择两个模型的最佳参数。在这个神奇的备忘单的帮助下,我决定使用多类决策森林算法,因为我知道很难获得大量数据,我需要一个易于在短时间内用有限数据训练的模型。此外,根据我对算法本身的理解,该模型基于建立非线性决策边界,我认为这将作为分类模型很好地工作。出于好奇,我选择了多类神经网络算法来测试更多。
我最初选择了“是”和“不是”这两个词来检测,因为我觉得它们是彼此情绪上的对立面。在两个模型都达到接近 100%的预测准确率后,我决定添加单词“水”、“食物”、“快乐”和“悲伤”。我觉得我试图检测的单词类型在情感意义上是彼此对立的,因此会有很大不同的脑电波模式,使它们更容易分类。我将来自耳机的大部分数据输入算法,包括 EEG 信号的原始值、分类信号值以及注意力、冥想和眨眼值。我为每个单词记录了 5 个 30 秒间隔的数据。
两种算法的结果如下,左边是多类决策森林,右边是多类神经网络…
值得注意的是,除了比多类神经网络更准确之外,多类决策森林的训练速度也快得多。我不确定为什么多类神经网络没有更好地对脑电图数据进行分类,但我认为这与我输入模型的数据量有关。我可能需要记录更多的数据以供算法使用。对我的数据集中的字段应用权重也可能有所帮助。
我从实验中移除了多类神经网络,并在用实际数据测试之前重新训练了模型。测试时,我发现得到的模型极其准确;大约 95%的人在一系列 20 次测试中有一次错误地预测了水。当你看上面的混淆矩阵时,这是完全可以预料的。
对结果的反思
有趣的是,当我第二天早上再次测试该模型时,我的结果不太准确,我认为这与大脑在一段时间后和一天中不同时间的状态不同有关。大脑在处理单词时可能会发出不同的信号,这取决于情绪、饥饿、口渴和周围环境等因素。为了减轻这一点,我已经考虑过包括某种预训练,几乎是冥想,例行公事,以便集中精神提供一个更标准化的阅读。我可以通过潜在地向受试者展示某种模式或物体以及声音来实现这一点(万一他们失去了视力,他们仍然有一个可以依靠的基石)
尽管我的祖母可能无法从这项努力中受益,但我认为继续这项实验是值得的,因为它可能会帮助其他处于类似或更糟糕情况的人。我目前正在从我的祖母那里收集一天中不同时间的数据,以抵消我在训练后的早晨所经历的不准确性。我预计有了这些新数据,我可能需要重新配置算法。
虽然这个实验是一个成熟的实验,但我想象像这样的东西可以用于许多不同的目的,例如固定不动的人,或者它甚至可以用作一种安全形式或一种类似心灵感应的通信方法。另一种训练模型的方法是让它一直听并记录你的声音。和你呆了一天,听你说话后,它会知道当你想说某些单词时会发生什么,从而增加你的潜在词汇量。
在未来的博客文章中,我计划更深入地研究这个解决方案的数学原理,因为我希望有一天为了成本和速度的利益,使用我自己的算法而不是微软的算法。
欢迎在此评论或联系。有人对我的工作进行评估并提供反馈是非常好的,也是非常受欢迎的。我在业余时间做这个,和这个领域的其他人有些脱节,希望得到反馈和/或建议。
感谢阅读!
Cerasermo is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
使用机器学习来了解你的胸痛是否是心脏病的征兆
医学机器学习
开发机器学习模型,以通过给定的胸痛和其他属性来分类该人是否患有心脏病
Photo by Marcelo Leal on Unsplash
先决条件
在开始项目之前,您应该具备以下基本知识:
- 计算机编程语言
- 熊猫(数据分析库)
- 科学计算库
- Scikit 学习(用于数据预处理和模型选择)
资料组
我们将使用加州大学 机器学习库提供的 心脏病数据集 。
UCI 页面提到以下人员是负责数据收集的主要研究者:
1。匈牙利心脏病研究所。布达佩斯:医学博士安朵斯·雅诺西。瑞士苏黎世大学医院:医学博士威廉·斯坦布伦。瑞士巴塞尔大学医院:医学博士马蒂亚斯·普菲斯特勒。弗吉尼亚医疗中心,长滩和克利夫兰诊所基金会:罗伯特·德特拉诺,医学博士,哲学博士。
介绍
在这个项目中,我们将通过使用患者的年龄、性别、胸痛类型、血压、胆固醇水平及其心脏病状况数据来对胸痛是否是心脏病的指征进行分类。
代码部分
2.1 准备数据
首先让我们看看我们的数据字典:
这里我们总共有 14 列,13 列是我们的特性,1 列是标签。
import **pandas** as **pd**df = pd.read_csv('data.csv')
我们正在用熊猫进行数据处理。
df.head()
在我们的数据集中,我们没有列的名称,现在我们的任务是根据数据字典为相应的列添加名称。
同样,我们将使用 pandas 读取 csv 文件,但这次我们也将向列中添加名称。
df = pd.read_csv('data.csv', sep=",", names=["Age", "Sex", "CP", "Trestbps", "Chol", "Fbs", "Restecg", "Thalach", "Exang", "Oldpeak", "Slope", "CA", "Thal", "Label"])df.head()
现在看起来不错。
2.2 探索数据
让我们检查信息,看看数据集中是否有空值。
df.info()
如您所见,每列有 303 个条目,没有任何空值。这对我们来说是个好兆头。
现在,让我们立即开始特征工程。
2.3 特征工程
众所周知,特征工程是数据科学中最关键的部分。从这里,我们应该能够深入了解我们的数据,并设计出对我们的分类贡献更大的最佳特征。
2.3.1 固定数据
通过观察数据,我们可以看到标签栏中的值偏差很小。因为这是一个二元分类问题,应该有 0 或 1 个值,但这里我们有 5 个不同的值。
如果我们查看由 UCI 提供的 数据描述 我们可以看到 label(num)列值分为两类:
- 值 0:表示没有心脏病
- 价值 1:意味着有心脏病
由此我们假设大于 1 的值也属于有心脏病一类。
让我们编写一个函数来映射这些值:
def **mapLabels**(value):
if value > 0:
return 1
else:
return 0
上面的函数获取值,如果值大于 0 则返回 1,否则返回 0。
让我们将其映射到我们的标签:
df['Label'] = df['Label'].map(mapLabels)
df['Label'].value_counts()
我们数据中的另一个问题是:
在我们的 CA 和 Thal 列中,我们将它作为对象数据类型,但是值是浮动的。
import **numpy** as **np**df.select_dtypes([np.object]).head()
这里我们使用 numpy 从数据集中选择对象数据类型。
经过一点挖掘,我发现,CA 和 Thal 列有一些空值设置为“?”。
让我们也修复这些数据:
def **caToFloat**(value):
if not value == '?':
return float(value)
else:
return 0df['CA'] = df['CA'].map(**caToFloat**)
这里,我们将 CA 列的值映射到 float 值和任何带“?”的值到 0。因为只有几个人?数据,我们的数据最初属于[0–3]类;可以将它们映射到 0。
对于 Thal 数据:
df['Thal'].value_counts()
如我们所见,我们的大多数数据都是 3.0 值,因此我们可以映射出**‘?’**值到 3.0 ,因为它们只有 2 个数字。
df['Thal'] = df['Thal'].map(lambda x : 3.0 if x == '?' else float(x))df['Thal'].value_counts()
现在让我们检查我们的数据信息:
df.info()
我们所有的值都是数字。
让我们将连续年龄数据转化为类数据:
df.loc[df['Age'] <= 16, 'Age'] = 0,
df.loc[(df['Age'] > 16) & (df['Age'] <= 26), 'Age'] = 1,
df.loc[(df['Age'] > 26) & (df['Age'] <= 36), 'Age'] = 2,
df.loc[(df['Age'] > 36) & (df['Age'] <= 62), 'Age'] = 3,
df.loc[df['Age'] > 16, 'Age'] = 4
这里,我们将年龄数据分为[0,1,2,3,4]类,其中:
儿童:0 青少年:1 成人:2 中年:3 老年人:4
df['Age'].value_counts()
如你所见,我们的数据中没有孩子或年轻人。
这有助于更好的分类。你也可以寻找其他的特性并设计它们。
2.4 数据预处理
现在是准备分类数据的时候了。
labels = df['Label']
features = df.drop(['Label], axis=1)
这里,我们将特征和标签从数据帧中分离出来。
现在,我们将使用 scikitlearn 将我们的数据分成训练和测试数据。
from sklearn.model_selection import train_test_splittran_x, test_x, train_y, test_y = train_test_split(features,labels, shuffle=True)
2.5 型号选择
现在我们准备好训练我们的数据。
为了选择我们的最佳模型并在此基础上训练我们的数据,我们将使用 sklearn。
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import KFold, cross_val_score
from sklearn.ensemble import GradientBoostingClassifierk_fold = KFold(n_splits=12, shuffle=True, random_state=0)
在这里,我们将使用 KFold 交叉验证我们的数据,并从这些不同的分类模型中选择我们的最佳模型。
KFold 允许我们验证所有的训练数据,并帮助找到我们的最佳模型。
KFold
这里我们将使用 12 个折叠拆分。
现在,让我们训练我们的第一个模型是 SVC 或 SVM(支持向量机)
clf = SVC(gamma='auto')
scoring = 'accuracy'
score = cross_val_score(clf, train_x, train_y, cv=k_fold, n_jobs=1, scoring=scoring)
print(score)
## OUTPUT: 61.38
从 SVC 我们只能得到 61.38%的准确率。
让我们看看其他模型:
梯度推进分类器
clf = GradientBoostingClassifier()
scoring = 'accuracy'
score = cross_val_score(clf, train_x, train_y, cv=k_fold, n_jobs=1, scoring=scoring)
round(np.mean(score)*100, 2)## OUTPUT: 77.35
决策树分类器
clf = DecisionTreeClassifier()
scoring = 'accuracy'
score = cross_val_score(clf, train_x, train_y, cv=k_fold, n_jobs=1, scoring=scoring)
round(np.mean(score)*100, 2)## OUTPUT: 75.91
随机森林
clf = RandomForestClassifier(n_estimators=10)
scoring = 'accuracy'
score = cross_val_score(clf, train_x, train_y, cv=k_fold, n_jobs=1, scoring=scoring)
round(np.mean(score)*100, 2)## OUTPUT: 83.28
朴素贝叶斯
clf = GaussianNB()
scoring = 'accuracy'
score = cross_val_score(clf, train_x, train_y, cv=k_fold, n_jobs=1, scoring=scoring)
round(np.mean(score)*100, 2)## OUTPUT : 85.95
正如我们可以看到的,我们在朴素贝叶斯上得到了高分,所以我们将使用这个分类器进行分类。
我们的分数很低,因为我们没有正确设计我们的数据。你可以通过更多的数据清理和特征选择来获得更多的分数。
您还可以调整 hyper 参数以获得更好的效果。
2.6 培训模式
让我们使用朴素贝叶斯分类器算法来训练我们的模型:
clf = GaussianNB()
clf.fit(train_x, train_y)
现在让我们看看我们的预测:
predictions = clf.predict(test_x)
values = list(zip(predictions, test_y.values))status = []
for x, y in list_of_values:
status.append(x == y)
list_of_values = list(zip(predictions, test_y.values, status))final_df = pd.DataFrame(list_of_values, columns=['Predicted', 'Actual', "Status"])
这里我们可以看到我们也有一些错误的分类,您可以通过改进我们的特征选择来改善这些。
结论
现在,我们的模型能够通过给定的各种特征来预测心脏病的迹象😀。
分类问题有时容易,有时非常困难,这完全取决于您使用哪种数据以及您在特征选择+模型超参数选择上付出了多少努力。
对于像我们这样的初学者来说,了解 ML 如何工作,探索数据集和解决问题是最好的学习方式。
暂时就这样了。希望你喜欢这个项目,并学到了一些东西。
使用机器学习来预测房价
LassoCV、RidgeCV 和线性回归机器学习算法。
Photo by Gus Ruballo on Unsplash
在这篇文章中,我将向你展示我使用机器学习来预测房价的数据科学过程。在开始之前,我想概述一下数据科学流程:
- 定义问题
- 收集数据
- 清理和探索数据
- 对数据建模
- 评估模型
- 回答问题
定义问题
我的任务是创建一个机器学习模型,使用 Kaggle 的 Ames 住房数据集预测房价。为了使数据科学问题更加现实,我添加了提高 Zillow 的 Zestimate 准确性(星级)的需求,用于像 Ames 这样有 3 颗星的地区(良好的 Zestimate)。这可能是由于假设的三星级酒店的用户网络流量增加。
收集数据
该数据集包含来自评估人办公室的信息,用于计算 2006 年至 2010 年在爱荷华州埃姆斯出售的个人住宅物业的评估价值。数据集包含 2051 行和 81 列(特征)信息。这些数据由 Kaggle 收集和提供。如果没有提供数据,我收集数据的方法是做研究,看看它是否公开,或者通过 Zillow 的 API 下载。
清理和探索数据
接下来,我执行了数据清理和探索性分析。对于前者,我检查空值和数据输入错误并做出决定。对于后者,我将数据可视化,以便更好地查看和理解关系和分布。
下面是我的目标变量——销售价格的两个可视化视图。我想了解它的分布。
the distribution of Sale Price, the target, which we can see is right-skewed
the distribution of Sale Price, the target, after taking the log — now it’s more normally distributed
我把目标的日志输入到模型中。我这样做是因为该模型将试图找到我们目标的满意中值,但如果不加处理,它会一直低估离群值,这会对 R 平方得分产生负面影响。根据 R 平方公式,我们的模型越错误,它将受到越多的惩罚,因为分子中的残差(误差)被平方。因此,对我们记录的目标进行预测将允许模型具有与欠猜一样多的过猜或更正态分布的预测。
接下来,我想了解我的特征与我的目标变量之间的关系,所以我绘制了我的特征与售价 0.50 或更高的相关性的热图。没有一个与销售价格呈负相关,相关系数为-0.50 或更低。
我想进一步研究这些关系,所以我从上面的关联热图中绘制了六个特征的散点图。
如您所见,与 0.80 的销售价格具有最强相关性的total _ qual在所选的六个中显示了最强的线性关系。
对数据建模
在这一步中,我将我的数据分为训练集和测试集,选择我的特征以馈入模型、特征工程方法和模型。我最初从一个向前选择的过程开始——选择一个特性,运行我的模型,检查性能,然后重复这些步骤。我还手动创建了一些交互术语,dummied(将分类特征转换为布尔矩阵),并映射(评分/加权分类特征)了一些我的分类特征。我选择的每一个功能都是有意为之的,无论是原样还是手工设计的。我主要依赖于该功能与销售价格的强相关性,以及我对某个功能是否会影响销售价格所做的直觉假设。使用这种方法,我得到了很好的结果,但后来我变得急切,并做出了(不正确的)假设,即如果我模拟或映射每个分类特征,然后在它们是数字后对所有特征应用多项式特征(整体相互作用项),那么我的所有特征对我的模型都将发挥最佳作用。
我检查了我新设计的功能与我的目标变量销售价格的相关性。你可以看到下面几个的结果。
correlations of the new dummied features to Sale Price
correlations of the new mapped features to Sale Price
正如你所看到的,我绘制的特征(第二张图)比我模拟的特征与销售价格有更强的相关性。我仍然坚持我的假设,继续给我的模型添加我所有的特征(当然除了售价)。
我选择了三种机器学习算法,分别是线性回归、RidgeCV 和 LassoCV。我知道有了大量的特征,我会提高我的过度拟合的潜力,所以我想包括后面的模型,因为它们有严格的正则化方法。我还假设多项式要素会处理我的要素之间的任何多重共线性。
评估模型
我的模特是拉索夫。它在我的训练数据上达到了 94%的 R 平方,在看不见的数据上达到了 86%。我的测试数据的 RMSE 是 37,334。这意味着两件事:1)模型解释了数据中 86%的可变性,2)模型过度拟合。
下面你可以看到我的预测(yhat)的分布覆盖在目标变量(y)上。
最终的模型不是我最好的模型。不幸的是,我重写了我的最佳模型中的特征——在我的特征选择和工程过程中我是故意的——因为做出了错误的假设,即把所有特征转换为数值并使用多项式特征会产生更好的结果。
回答问题
LassoCV 模型很好地处理了看不见的数据,所以我会用它来预测房价。
建议
我的一个建议是,通过以下方式继续测试/学习改进模型:
- 重新审视我最初的正向选择流程
- 选择与销售价格高度相关的功能
- 创建新的并检查它们的相关性
- 输入模型并检查结果
- 跟踪改进模型的特性,并丢弃(但也跟踪)不改进模型的特性
使用机器学习预测 Kickstarter 的成功
Kickstarter 适合你的项目吗?你如何优化以获得成功?
项目目标
近年来,个人和小公司创建的项目的融资选择范围大大扩大。除了储蓄、银行贷款、朋友和家人的资助以及其他传统选择,众筹已经成为一种流行且容易获得的替代方式。 Kickstarter ,成立于 2009 年,是一个特别知名和受欢迎的众筹平台。它有一个全有或全无的资助模式,即一个项目只有在达到其目标金额时才能获得资助;否则,资助者不会给项目提供资金。
一个项目的成败取决于各种各样的因素——一般来说,Kickstarter 也是如此。其中一些能够被量化或分类,这允许构建一个模型来尝试预测一个项目是否会成功。这个项目的目的是构建这样一个模型,并更广泛地分析 Kickstarter 项目数据,以帮助潜在的项目创建者评估 Kickstarter 对他们来说是否是一个好的融资选择,以及他们成功的机会有多大。
数据源
本项目中使用的数据集下载于。csv 格式,来自一个名为 Web Robots 的网络抓取网站。该数据集包含 Kickstarter 上托管的所有项目的数据,这些项目是从该公司 2009 年 4 月启动到 2019 年 3 月 14 日的网络搜集之日。该数据集包含 209,222 个项目,尽管其中一些是重复的。
清洁和预处理
为了将数据集转换成适合应用机器学习模型的格式,需要进行大量的清理工作。如果你对df.isna()
和df.drop()
还不够,你可以在我的 GitHub 库中查看完整的 Jupyter 笔记本代码。
在删除了重复的和不相关的行(例如,在活动中期取消的项目,或者仍然有效的项目)之后,我得到了一个相当大的数据集,包含 168,979 个项目。
保留或计算的列有:
- 项目目标(美元)
- 活动持续时间——从发布到截止日期的天数
- 从页面创建到项目启动的天数
- 广告词长度
- 名字字长
- 项目是否突出显示为员工选择(一键编码)
- 类别(一键编码)
- 国家(一键编码)
- 项目启动的月份(一键编码)
- 项目截止日期的月份(一次性编码)
- 项目启动的星期几(一键编码)
- 项目截止日期的星期几(一键编码)
- 项目启动的两小时时间窗口(一键编码)
- 项目期限的两小时时间窗口(一键编码)
一些功能最初是为了探索性数据分析(EDA)的目的而保留的,但后来为了使用机器学习模型而被放弃了。这些包括与结果相关的特征(例如承诺的金额和支持者的数量),而不是与项目本身的属性相关的特征(例如类别、目标、活动的长度)。
探索性数据分析
现在是精彩的部分。
自 2009 年推出以来,Kickstarter 已经大幅增长,特别是在 2014 年,扩张真正开始加速。然而,在这一点上,成功的项目的比例大大降低了,因为站点被大量的项目淹没了。尽管近年来成功率一直在上升,但还是有希望的。
总体而言,56%的已完成项目(即那些已经完成且未被取消或暂停的项目)是成功的。
Changes over time in the number of projects launched on Kickstarter
Changes over time in project successes and failures
下图显示了成功项目和失败项目在某些特征上的差异。这里的关键要点是:
- 不出所料,成功的项目往往有更小(因此更现实)的目标 —成功项目寻求的金额中位数是失败项目的一半左右(使用中位数是因为资金和目标金额的正偏高)。
- 每个项目的承诺金额中位数的差异更令人惊讶。每个成功项目的承诺金额中值明显高于申请金额中值,这表明达到目标的项目往往会获得更多资金,并变得“资金过剩”。
- 与此相关的是,与目标金额相比,失败的公司和成功的公司在承诺金额和支持者数量方面的差异要大得多。可能一旦潜在的资助者看到一个项目看起来会成功,他们就更有可能加入并资助它。
- 成功的项目活动时间稍短,但启动时间稍长(从项目首次在网站上创建时算起)。
- 大约 20%的成功项目在网站上以员工选择的方式突出显示。在这里提出一种因果关系似乎是合理的,也就是说,被选作员工选择的项目更有可能继续成功,只有少数员工选择继续失败。
Comparison of features between successful and failed projects
在项目数量、目标和资金数额、支持者和成功率方面,探讨了各种其他特征。例如,下图显示了不同项目类别之间的差异(也提供了代码)。这里的关键要点是:
- 在 Kickstarter 上推出的最佳项目类型是漫画(根据成功率、支持人数和承诺金额)、舞蹈(成功率和承诺金额)和游戏(承诺金额和支持人数)。这可能至少部分是由于它们相对较小的筹资目标——如上所述,目标较小的项目往往更成功。
- 虽然漫画和游戏往往吸引最多的支持者,但每个支持者往往认捐相对较少。舞蹈和电影&视频往往吸引最慷慨的资助者。
- 技术项目到目前为止拥有最高的中值目标规模。然而,就实际承诺的中值金额而言,他们在排行榜上垫底。
- 表现最差的类别是食品、新闻和科技。
# Code used to create the graphs below# Importing the required libraries
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np# Creating a dataframe grouped by category with columns for failed and successful
cat_df = pd.get_dummies(df.set_index('category').state).groupby('category').sum()# Plotting
fig, ((ax1, ax2), (ax3, ax4), (ax5, ax6)) = plt.subplots(3, 2, figsize=(12,12))color = cm.CMRmap(np.linspace(0.1,0.8,df.category.nunique()))df.groupby('category').category.count().plot(kind='bar', ax=ax1, color=color)
ax1.set_title('Number of projects')
ax1.set_xlabel('')df.groupby('category').usd_goal.median().plot(kind='bar', ax=ax2, color=color)
ax2.set_title('Median project goal ($)')
ax2.set_xlabel('')df.groupby('category').usd_pledged.median().plot(kind='bar', ax=ax3, color=color)
ax3.set_title('Median pledged per project ($)')
ax3.set_xlabel('')cat_df.div(cat_df.sum(axis=1), axis=0).successful.plot(kind='bar', ax=ax4, color=color) # Normalizes counts across rows
ax4.set_title('Proportion of successful projects')
ax4.set_xlabel('')df.groupby('category').backers_count.median().plot(kind='bar', ax=ax5, color=color)
ax5.set_title('Median backers per project')
ax5.set_xlabel('')df.groupby('category').pledge_per_backer.median().plot(kind='bar', ax=ax6, color=color)
ax6.set_title('Median pledged per backer ($)')
ax6.set_xlabel('')fig.subplots_adjust(hspace=0.6)
plt.show()
Exploring the ‘category’ feature
为了空间和视网膜的利益,只有“成功比例”图表将显示在下面的额外功能。同样,要了解更多细节,请随时查看我的 GitHub 库。这里的关键要点是:
- 香港的成功项目比例更高(它们的支持者人数和资助金额的中位数也更高)。
- 周二是启动一个项目的最佳日子,周末最糟糕(筹集的金额和支持者的数量也是如此)。
- 世界标准时间下午 12 点到下午 2 点是启动项目的最佳时间——这也是支持者人数和资金数额最多的时候。世界协调时下午 6 点到凌晨 4 点是最差的发射时间。
- 十月是启动一个项目的最佳月份——它也拥有最多的支持者和资金。七月和十二月是最糟糕的月份。
Top left: success rates by country. Top right: success rates by the day of the week on which projects were launched. Bottom left: success rates by the time of day at which projects were launched (in UTC/GMT). Bottom right: success rates by the month in which projects were launched.
为机器学习准备数据
这个项目的最终目标是创建一个模型,该模型能够以较高的准确度预测一个项目是成功还是失败。
为了准备机器学习的数据,采取了以下步骤(代码如下):
- 一次性编码分类变量。
- 将数据分为从属目标变量“y”(在本例中为“状态”,即项目成功或失败)和独立特征“X”。
- 变换 X 轴上的要素,使其比例相同。对于这个项目,使用 Scikit-learn 的 StandardScaler 将每个特征转换为平均值 0 和标准差 1。
- 数据被分成训练集和测试集,用于模型的稳健评估。
# Importing the required libraries
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split# 1) Creating dummy variables
df_transformed = pd.get_dummies(df_transformed)# 2) Separating into X and y
X_unscaled = df_transformed.drop('state', axis=1)
y = df_transformed.state# 3) Transforming the data
scaler = StandardScaler()
X = pd.DataFrame(scaler.fit_transform(X_unscaled), columns=list(X_unscaled.columns))# 4) Splitting into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.3, random_state=123)
在运行机器学习模型之前,而不是之后,选择一种评估方法是一种很好的做法。选择加权平均 F1 分数。F1 分数计算精确度和召回率之间的调和平均值,并且是一个合适的度量,因为在这种情况下没有对假阳性或假阴性的偏好(两者都同样不好)。将使用加权平均值,因为这些类的大小略有不同,并且我们希望能够预测成功和失败。
在运行机器学习模型之前,而不是之后,选择一种评估方法是一种很好的做法。
模型 1:标准逻辑回归
逻辑回归可用作二元分类器,以预测数据点属于两个类别中的哪一个。
为了创建一个基线模型进行改进,使用默认参数将逻辑回归模型拟合到数据中。
# Importing the required libraries
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report# Fitting a logistic regression model with default parameters
logreg = LogisticRegression()
logreg.fit(X_train,y_train)# Making predictions
y_hat_train = logreg.predict(X_train)
y_hat_test = logreg.predict(X_test)# Logistic regression scores
print("Logistic regression score for training set:", round(logreg.score(X_train, y_train),5))
print("Logistic regression score for test set:", round(logreg.score(X_test, y_test),5))
print("\nClassification report:")
print(classification_report(y_test, y_hat_test))
Results of the vanilla linear regression model
还不错。该模型的加权平均 F1 分数为 0.70。现在的目标是提高这个分数。
主成分分析
用于初始逻辑回归模型的数据集中有大量的要素(106)。PCA(主成分分析)被用于将其减少到更少数量的成分,这些成分仍然可以解释尽可能多的数据变化。这有助于提高模型拟合度和精确度。
下图(由下面的代码生成)显示 PCA 中使用的组件数量没有明显的界限。
# Importing the required libraries
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA# Fitting PCA
pca = PCA()
pca.fit_transform(X)
explained_var = np.cumsum(pca.explained_variance_ratio_)# Plotting the amount of variation explained by PCA with different numbers of components
plt.plot(list(range(1, len(explained_var)+1)), explained_var)
plt.title('Amount of variation explained by PCA', fontsize=14)
plt.xlabel('Number of components')
plt.ylabel('Explained variance');
Plotting the amount of variation in the data explained by PCA using various numbers of components
发现了以下结果:
Number of components explaining 80% of variance: 58
Number of components explaining 90% of variance: 70
Number of components explaining 99% of variance: 90
为了选择在机器学习模型中使用的组件数量,使用默认参数将这些值中的每一个插入到逻辑回归模型的管道中:
# Running a for loop to test different values of n_components
n_comps = [58,70,90]
for n in n_comps:
pipe = Pipeline([(‘pca’, PCA(n_components=n)), (‘clf’, LogisticRegression())])
pipe.fit(X_train, y_train)
print(“\nNumber of components:”, n)
print(“Score:”, round(pipe.score(X_test, y_test),5))
结果显示,90 个组件的得分最高,尽管差异很小(从 58 个组件改进了约 3%):
Number of components: 58
Score: 0.67831Number of components: 70
Score: 0.6858Number of components: 90
Score: 0.70799
模型 2:具有 PCA 和参数优化的逻辑回归
逻辑回归模型可以通过优化其参数来进一步改进。GridSearchCV 用于测试多个不同的正则化参数(C 值)、惩罚(l1 或 l2)以及有截距和无截距的模型。
# Importing the required libraries
from sklearn.model_selection import GridSearchCV# Timing how long the model takes to run
logreg_start = time.time()# Building the pipeline
pipe_logreg = Pipeline([('pca', PCA(n_components=90)),
('clf', LogisticRegression())])# Creating the parameters to test
params_logreg = [
{'clf__penalty': ['l1', 'l2'],
'clf__fit_intercept': [True, False],
'clf__C': [0.001, 0.01, 1, 10]
}
]# Using GridSearchCV to test multiple different parameters
grid_logreg = GridSearchCV(estimator=pipe_logreg,
param_grid=params_logreg,
cv=5)grid_logreg.fit(X_train, y_train)logreg_end = time.time()logreg_best_score = grid_logreg.best_score_
logreg_best_params = grid_logreg.best_params_# Printing the results
print(f"Time taken to run: {round((logreg_end - logreg_start)/60,1)} minutes")
print("Best accuracy:", round(logreg_best_score,2))
print("Best parameters:", logreg_best_params)
结果:
Time taken to run: 48.56 minutes
Best accuracy: 0.71
Best parameters: {‘clf__C’: 10, ‘clf__fit_intercept’: True, ‘clf__penalty’: ‘l2’}
然后使用最佳参数(根据准确度得分)为逻辑回归模型生成分类报告和混淆矩阵。
使用以下函数生成混淆矩阵:
def plot_cf(y_true, y_pred, class_names=None, model_name=None):
"""Plots a confusion matrix"""
cf = confusion_matrix(y_true, y_pred)
plt.imshow(cf, cmap=plt.cm.Blues)
plt.grid(b=None)
if model_name:
plt.title("Confusion Matrix: {}".format(model_name))
else:
plt.title("Confusion Matrix")
plt.ylabel('True Label')
plt.xlabel('Predicted Label')
class_names = set(y_true)
tick_marks = np.arange(len(class_names))
if class_names:
plt.xticks(tick_marks, class_names)
plt.yticks(tick_marks, class_names)
thresh = cf.max() / 2.
for i, j in itertools.product(range(cf.shape[0]), range(cf.shape[1])):
plt.text(j, i, cf[i, j], horizontalalignment='center', color='white' if cf[i, j] > thresh else 'black')plt.colorbar()
最佳逻辑回归模型的完整结果如下:
Results of the best logistic regression model
超参数调整后,模型的准确度得分与使用默认参数的逻辑回归模型相同(加权平均 F1 得分为 0.70)。令人失望。
模型 3:随机森林
接下来,使用随机森林分类器。随机森林算法是一种监督学习算法,可用于分类。它通过构建多个不同的决策树来预测数据点属于哪个类别。
同样,GridSearchCV 用于测试多个不同的超参数,以优化模型。
# Importing the required libraries
from sklearn.ensemble import RandomForestClassifier# Using GridSearchCV to test multiple different parameters
rf_start = time.time()pipe_rf = Pipeline([('pca', PCA(n_components=90)),
('clf', RandomForestClassifier())])params_rf = [
{'clf__n_estimators': [100],
'clf__max_depth': [20, 30, 40],
'clf__min_samples_split':[0.001, 0.01]
}
]grid_rf = GridSearchCV(estimator=pipe_rf,
param_grid=params_rf,
cv=5)grid_rf.fit(X_train, y_train)rf_end = time.time()rf_best_score = grid_rf.best_score_
rf_best_params = grid_rf.best_params_print(f"Time taken to run: {round((rf_end - rf_start)/60,1)} minutes")
print("Best accuracy:", round(rf_best_score,2))
print("Best parameters:", rf_best_params)
结果:
Time taken to run: 72.2 minutes
Best accuracy: 0.7
Best parameters: {'clf__max_depth': 30, 'clf__min_samples_split': 0.001, 'clf__n_estimators': 100}
最佳随机森林模型的完整结果如下:
Results of the best Random Forest model
在超参数调整之后,模型的加权平均 F1 分数从具有默认设置的模型的 0.65 增加到 0.69。这类似于逻辑回归模型,尽管比它稍差。此外,训练集和测试集的得分之间的差异表明可能存在一些过度拟合。这里可能有更多的超参数调整空间来进一步改进模型,但是时间不允许。
模型 4: XGBoost
啊,卡格尔世界的宠儿。XGBoost 现在非常流行。这是一种梯度推进算法。与随机森林类似,它是一种生成多个决策树以改善数据点分类的集成方法,但它使用梯度下降来改善特别难以分类的数据点的模型性能。
返回用于超参数测试的 good ol’ GridSearchCV:
# Importing the required libraries
from sklearn.model_selection import GridSearchCV# Using GridSearchCV to test multiple different parameters
xgb_start = time.time()pipe_xgb = Pipeline([('pca', PCA(n_components=90)),
('clf', xgb.XGBClassifier())])params_xgb = [
{'clf__n_estimators': [100],
'clf__max_depth': [25, 35],
'clf__learning_rate': [0.01, 0.1],
'clf__subsample': [0.7, 1],
'clf__min_child_weight': [20, 100]
}
]grid_xgb = GridSearchCV(estimator=pipe_xgb,
param_grid=params_xgb,
cv=5)grid_xgb.fit(X_train, y_train)xgb_end = time.time()xgb_best_score = grid_xgb.best_score_
xgb_best_params = grid_xgb.best_params_print(f"Time taken to run: {round((xgb_end - xgb_start)/60,1)} minutes")
print("Best accuracy:", round(xgb_best_score,2))
print("Best parameters:", xgb_best_params)
结果:
Time taken to run: 865.4 minutes
Best accuracy: 0.7
Best parameters: {'clf__learning_rate': 0.1, 'clf__max_depth': 35, 'clf__min_child_weight': 100, 'clf__n_estimators': 100, 'clf__subsample': 0.7}
呀。14 个半小时,它仍然只能达到与初始回归模型相同的精度(这也仅比使用默认参数运行的 XGBoost 模型提高了 0.01 个精度)。
最佳 XGBoost 模型的完整结果如下:
Results of the best XGBoost model
与随机森林模型一样,训练集和测试集的准确性得分之间的差异表明可能存在一些过度拟合。同样,这里可能有更多的超参数调整空间来进一步改进模型——但是我没有另外 14 个半小时的空闲时间。
模型评估
在参数调整之后,每个模型能够实现大约 70% 的精度。虽然达到这一精度水平相对容易,但参数调整只能少量提高精度水平。可能只有两类中的每一类都有相当大的数据量,这意味着即使是相对简单的模型(如具有默认设置的逻辑回归)也有足够的数据来达到良好的验证准确度。
创建的最佳随机森林和 XGBoost 模型仍然表现出一定程度的过度拟合。需要进一步调整参数来减少这种情况。
最终选择的模型是调整后的逻辑回归模型。这是因为,尽管每个模型都能够为测试集达到相似的精度水平,但这是唯一没有表现出过度拟合的模型。
有趣的是,与成功相比,每个模型在预测失败方面表现更差,真实的否定率低于真实的肯定率。也就是说,它将相当多的失败项目归类为成功,而将相对较少的成功项目归类为失败。可能导致项目失败的因素更有可能超出数据的范围,例如营销不善、更新不足或没有回复潜在支持者的消息。
假阳性和假阴性率意味着,如果一个新项目的数据通过模型来预测其成功或失败:
- 如果这个项目最终会成功,这个模型将会在大约 80%的情况下正确地预测它会成功
- 如果项目以失败告终,模型只会在大约 60%的时间里正确地将其预测为失败(而其余的时间会错误地将其预测为成功)
对考虑 Kickstarter 的项目创建者的建议
对成功率和/或收到的资金数量产生积极影响的一些因素有:
最重要的是:
- 较小的项目目标
- 被选为员工选择(质量的衡量标准)
- 漫画、舞蹈和游戏项目
- 来自香港的项目
不太重要:
- 较短的活动
- 从创建到发布需要更长时间
- 电影、视频和音乐项目(网站上的热门类别,相当成功)
- 周二发布(尽管这也是最常见的项目发布日,所以要小心竞争)
- 10 月发布
- 在 UTC 时间下午 12 点到下午 2 点之间发布(这当然与项目发布的国家有关,但请记住,支持者可能来自世界各地)
对成功率和/或收到的金额有负面影响的因素有:
最负面:
- 大目标
- 食品和新闻项目
- 来自意大利的项目
负面影响较小:
- 长期活动
- 在周末发布
- 7 月或 12 月发布
- 在世界协调时下午 6 点到凌晨 4 点之间发布
总体而言,Kickstarter 非常适合小型、高质量的项目,尤其是漫画、舞蹈和游戏。它不太适合大型项目,尤其是食品(如餐馆)和新闻项目。
感谢您读到这里!如果您有任何想法、意见或建议,请在下面添加。