关于 HUHUHANG

AI By Doing 作者,成都开发者。

RSS 地址: https://huhuhang.com/feed

请复制 RSS 到你的阅读器,或快速订阅到 :

HUHUHANG RSS 预览

QuakeSense 震感,一款简洁原生的 iOS 地震通知 App

1970-01-01 08:00:00

2015 年,还在上大学的我买了一部 iPhone 5c。对,就是那款非常漂亮的聚碳酸酯外壳手机。

同年,我在网上看到了少数派,并向其投递了一篇关于 VSCO App 的 体验文章。正是那篇如今看来非常青涩的短文,让我开始不断地体验各式各样的 iOS 应用,并且写下了一篇又一篇的 App 体验文章。

我不曾想过,十年后的今天,我会自己编写一款 iOS App,并且发布到 App Store 上。

这是我的第 187 篇 App 体验文章,也可能是我最特别的一篇文章,将这款由我自己业余时间开发的 App 分享给你。

即兴之作

我日常会关注地震消息,作为 2008 年大地震的亲历者,我对地震有着特殊的敏感度。虽然不是每一次地震都会影响到我,但我还是会习惯性看看最近哪里发生了地震。

我之前一直在用某款「地震预警」App,直到有一天我发现它的更新包已经超过 500 MB。我实在无法理解一款查看地震信息的 App 为什么会有如此庞大的体积,于是就开玩笑地在社交媒体上说,我打算自己写一个地震信息 App。

但是我完全不懂 iOS 开发,没有学过 Objective-C,也没有学过 Swift。我只是学过一点点 Python,了解一些基本的开发概念。

然而,自从 ChatGPT 问世之后,AI 的能力让大家印象深刻。尤其是前段时间推出的 Claude 3.5 Sonnet 模型,得到了很多人的好评。

虽然 AI 没办法从零写出一个完整的 iOS App,但我认为它能帮我解决很多问题。于是,我真的开始了这个项目。

编码之前

开始行动,不意味着我就可以开始编写代码。我需要先解决一些基本问题:

  1. iOS 应用的技术选型,选择什么编程语言,用什么框架?
  2. iOS 应用的开发工具,我需要什么软件?
  3. 地震 App 的架构设计,数据从哪里来,如何储存,查询,展示?如何实现地震通知推送?
  4. App 的界面设计,我需要哪些页面,如何设计?
  5. App 的发布,如何提交到 App Store?
  6. 预计的时间成本,我需要多久才能完成?

当然,除了这些问题,我还需要解决很多其他问题。但是,这些关键问题是我真正开始开发之前必须要了解或想清楚的。

于是,我通过询问 AI + 查询资料的方式,大致得出了一些结论:

  1. 使用 Apple 推荐的 SwiftUI 来开发一款纯原生的 iOS App,不使用第三方框架,尽量减少依赖。
  2. 参考 Apple 提供的 人机界面指南,尽量使用原生控件,省去界面设计。
  3. 使用 Cloudflare 提供的各种无服务器产品来搭建后端架构,存储数据,提供后端接口,消息队列推送等。避免需要自己搭建和维护云服务器。
  4. 我可能需要数个周末来完成这个项目,同时我需要学习一些基本的 Swift 语法和 SwiftUI 的使用方法,以便我能很好地和 AI 合作。
  5. 我需要每年交 688 元来开通 Apple Developer 账号,以便我能提交 App 到 App Store。

One Month Later...

我的 App 终于完成了,我将它命名为 QuakeSense(震感)。这是一款简单的 iOS App,它可以帮助你查看全球的地震信息,包括多个数据源,同时支持地震通知推送。

地震感知

QuakeSense 的界面非常简洁,它包含 4 个页面:主界面,地震详情页面,数据洞察页面和设置页面。iOS 端的安装包体积也做到了 1 MB 以下,非常轻量。

主界面

主页面使用了类似 Apple Maps 的抽屉样式,呈现地震信息列表。你可以在这里看到最新的地震信息,包括地震的分级,位置,震级,时间等关键信息。

主界面
主界面

你可以通过切换选项进行排序,按时间最近或者距离你位置最近来查看。

同时,上方使用了 iOS 原生地图来展示地震的位置,以及列表中所有地震的分布。

除了直接展示里氏震级之外,更醒目的标题留给了地震分级。这里采用了中国地质科学院地质研究所的 地震分级标准,将地震分为:

  1. 弱震:震级小于 3 级。如果震源不是很浅,这种地震人们一般不易觉察。
  2. 有感地震:震级等于或大于 3 级、小于或等于 4.5 级。这种地震人们能够感觉到,但一般不会造成破坏。
  3. 中强震:震级大于 4.5 级、小于 6 级。属于可造成破坏的地震,但破坏轻重还与震源深度、震中距等多种因素有关。
  4. 强震:震级等于或大于 6 级。其中震级大于等于 8 级的又称为巨大地震。

相比于里氏地震的数字,这种分级更加直观,更容易让用户理解和第一时间了解地震的危险程度。

地震详情

你可以通过点击地图上的标记,列表中的地震条目来打开地震详情页面。

地震详情页面
地震详情页面

详情页面同样采用了抽屉样式,这里会展示更加详细的地震信息,包括地震的深度,震中经纬度坐标,震级,烈度,距离,测定机构等详细信息。

数据洞察

数据洞察页面展示了地震数据的统计信息,包括全球最近 24 小时和 7 天的地震数量,最大震级,平均震级等信息。

数据洞察页面
数据洞察页面

同时,这里还会展示距离你所在位置的震距分布图,震级分布图等。

你可以通过右上角的筛选项,来切换不同测定机构的数据,单独查看某个机构的数据洞察结果。

设置

我希望做一款普通人易用的 App,无论是主界面还是设置页面,我都尽量保持简洁,减少用户不必要的操作,关注真正值得关注的信息。

在设置页面,你可以选择默认的排序方式,默认的数据源,以及默认关注的最小震级。

设置页面
设置页面

目前,QuakeSense 支持美国地质调查局(USGS)、欧洲地中海地震中心(EMSC)、中国地震台网中心(CEIC)、日本气象厅(JMA)、台湾中央气象局(CWB)等多个数据源,你可以根据自己所在地区,选择合适的数据源。

理论上,你所在地区的测定机构往往可以更加及时和准确地提供地震信息,因为他们安装的测定站密度更大且距离更近。如果你所在地区有更准确的地震数据源,欢迎反馈给我,我会尽量支持添加。

同时,你可以在设置里面开启地震通知推送。当你所在位置,500 Km 以内发生有感地震,或者全球发生 7 级以上的大地震时,你会收到来自 QuakeSense 的推送通知。

推送通知基于服务端消息队列,确保你可以在不打开 App 的情况下,第一时间了解到地震信息。

地震预警

在测试期间,有用户问我为什么不做地震预警功能,因为很多 App 都有所谓「预警」的功能。我愿意分享一下我自己对地震预警和地震通知的理解。

首先,这里提到一个基本常识:地震是无法被有效预测的。 也就是说,没有人能够准确预测地震是否会发生,更不要说地震发生的时间、地点和震级。

目前最为可行的地震预警方案,是利用电波比地震波快的原理。即当地震发生后,因为震中距离用户有一定距离,地震波传播速度比电波(光速)慢,可以利用这个时间差,提前到通知用户。

所以,你可能在之前看过一些新闻,电视或者小区广播会进行倒计时播报,说「地震波预计 XX 秒后到达」。

那么,QuakeSense 为什么没有地震预警功能呢?

先说结论:我无法做到足够低的延迟,提供有效的、能够真正起到预警作用的地震预警通知。

准确有效的地震预警,需要各环节的延迟都足够低,才能在地震波到达用户之前,提供有效的预警通知。但是很多环节都存在延迟:

  1. 地震测定基站不太可能刚好位于震源上方,所以收到地震波信号,存在延迟;
  2. 测定基站收到信号后,需要计算震源位置,传输到数据中心,存在延迟;
  3. 测定数据源发布数据,存在延迟;
  4. App 获取数据,根据用户位置信息,计算与震源的距离,存在延迟;
  5. 不同地区的地质构造,导致地震波在地层中的传播速度不同,需要独立计算,存在延迟;
  6. 处理消息队列,通过 APNs 推送服务向大规模用户推送消息,存在延迟。

所有这些延迟加起来,可能已经远超地震波传播的时间,导致有效的地震预警几乎不可能在 app 端实现。而电视或者专门的预警广播,由于其专用的传输网络,可以更快地传递信息。

小震不用跑,大震跑不了。这是四川人民经常调侃的一句话,但也是地震预警面对的现实。

所以,QuakeSense 不能帮助你提前预知地震,或者逃离地震。但它可以帮助你了解地震信息,及时获取最新的地震通知,关心你的家人和朋友,保持警惕,做好应对准备。

隐私保护

我为 QuakeSense 上线了一个 网站,包含常见问题和 隐私政策。我个人非常重视隐私保护,虽然现实往往非常残酷,但我仍然希望自己的应用能够尽可能保护用户的隐私。

QuakeSense 官网
QuakeSense 官网

QuakeSense 只会收集你的两项信息:你的大致位置信息和设备 ID。

  1. 大致地理位置,上报坐标只会到所在城市区域,用于计算你和震中的距离,以便于推送地震通知和实现 App 中的距离功能;
  2. 匿名设备 ID,用于 Apple 推送服务,以便于向你的设备推送地震通知。

这些数据被加密存放在 Cloudflare 的 D1 数据库中,当用户关闭通知功能或者删除 App 导致通知不可达时,这些数据会被自动触发删除。

macOS 和 iPadOS

得益于 SwiftUI 的跨平台特性,QuakeSense 也可以在 macOS 和 iPadOS 上运行。我为大屏设备重新设计了界面布局,使得用户在大屏设备上也能够更好地使用 QuakeSense。

适配大屏幕设备
适配大屏幕设备

目前,QuakeSense 以及适配了中文简体、中文繁体、英文和日文,为不同数据源的主要用户提供了更好的体验。

定价和结语

QuakeSense 是我开发的第一款 iOS App,是否收费的确思考了很久。最终,我决定将它设置为一次性收费应用,不包含任何内购和订阅。

QuakeSense 的中国区定价为免费下载 + 2.99 美元/年 Pro。

Pro 版本拥有消息通知、更完整的历史数据和数据洞察功能。

以下是我设为付费应用的原因:

  1. 我付出了时间,特别是牺牲了陪伴我的家人和朋友周末时间,长期维护和更新 App 需要付出更多的时间和精力;
  2. QuakeSense 需要支付一定的服务器费用,以及 Apple Developer 账号费用;
  3. 免费会带来更多用户,但是消息队列服务的延迟会增加,排在队列后面的用户可能会更晚收到地震通知;

QuakeSense 诞生于一念之间,我没有想到我会写一款 iOS App,也没有想到我会在少数派上分享这篇文章。我只是想做一款自己用得上的 App,也许你也会用得上。

你可以前往 App Store 免费下载 QuakeSense,或者访问 QuakeSense 网页 了解更多信息。

App Store 中国大陆 ICP 备案最佳实践

1970-01-01 08:00:00

App Store
App Store

政策背景

2023 年 07 月,工信部下发 关于开展移动互联网应用程序备案工作的通知,要求所有在中国境内提供服务的移动应用程序(APP)必须进行备案。未经备案的APP将不被允许提供服务。

根据通知,App 主办者应该按以下方式进行备案:

  1. 备案地点:向 App 主办者住所所在地的省级通信管理局履行备案手续。
  2. 备案方式:通过网络接入服务提供者,或 App 分发平台,使用 国家互联网基础资源管理系统 进行在线备案。
  3. 备案流程:采取网上提交申请、查验审核的方式进行。

备案选择

理论上,你可以自己通过国家互联网基础资源管理系统进行备案。但是,由于备案流程繁琐,需要提供大量资料,而且对于不熟悉备案流程的人来说,容易出现错误,导致备案失败。

这里还是建议选择通过所谓的「网络接入服务提供商」进行备案。也就是阿里云、腾讯云、华为云等云服务商。

如果你已经在某一个云服务商有域名或者服务器,那么直接选择该云服务商进行备案即可。如果没有,可以选择任意一个云服务商进行备案。

接下来,以我选择的阿里云为例,介绍具体的备案流程。

购买云资源

阿里云提供了完善的 备案文档,这里不再赘述。

简单来说,备案的前提是你需要在阿里云拥有一个域名对应符合要求的云资源。如果没有,则需要先购买。

这里我省略了公司备案,以及需要许可证的情况。仅针对于个人开发者,以及不需要许可证的情况。当然大致流程大同小异,只是非个人开发者需要提供更多资料。

我将以阿里云新账号为例,也就是你没有任何域名和云资源的情况下,如何进行备案。

购买域名

首先你需要从 阿里云万网 购买一个域名。这个域名你不一定需要在 app 中真正使用,只是备案的一个必要条件。

  1. 如果你需要使用这个域名,认真挑选,购买一个符合你需求的域名。
  2. 如果你不需要使用这个域名,可以选一个最便宜的,例如 .top 域名,一年几块钱。

注意:购买域名时,需要认真阅读说明,避免买到无法备案的域名后缀。

仔细了解可以备案的后缀
仔细了解可以备案的后缀

购买云资源

除了域名之外,你还需要购买一个符合要求的云资源。你可以查看 支持 ICP 备案的云服务器及 ICP 备案互联网信息服务数量 表格。

和域名一样,云资源也不是必须使用的,只是备案的一个必要条件。但是并不能随便买,需要符合备案要求,否则无法关联到备案申请中。

  1. 如果你的 app 需要使用云服务器,可以购买一个符合要求的云服务器。但注意最低配置需要满足上方表格中的要求。
  2. 如果你不需要使用云服务器。可以购买一个最便宜的,且满足要求的云服务器。这里推荐选择 轻量应用服务器,一年几十块钱,未来可以关联最多 5 个备案 app。

轻量应用服务器 2 核 2G 即满足备案需求,新用户是 82 元/年,老用户是 99 元/年。(截止 2024 年 08 月)

域名 + 云服务器,约 100 元左右,备案所需的最低成本。

填写备案信息

点击阿里云官网右上角的「备案」,进入备案申请页面。

按照流程引导填写备案信息,关联对应的域名和云资源。进行实人认证,提交备案申请。

这里不在赘述具体的填写流程,因为阿里云的流程引导非常清晰,只要按照提示填写即可。

等待备案审核

备案提交后,需要等待备案审核。

  1. 你会接到阿里云的初审电话,核实备案信息。如果是新购买的域名和云资源,客服可能会告知需要等待 2 天他们才会提交资料,因为管局资料同步需要时间。
  2. 之后阿里云会提交到管局审核,等待管局审核通过。

不出意外的话,管局会在后续审核通过,你的备案就完成了。

通过短信
通过短信

选择阿里云等云服务商进行备案,可以大大简化备案流程,避免出现错误,提高备案成功率。并且由于他们会和管局保持良好的合作关系,理论上不会出现备案失败的情况。

填写备案信息

最后,你可以到 App Store Connect 中填写备案信息。

这里有一个坑,如果你直接填写管局短信中的备案号,可能会出现「ICP备案号与中国工业和信息化部(MIIT)记录不符,请输入有效的ICP备案号」的提示。这时候你需要到 工信部备案查询 查询你的备案号。

然后复制 ICP 备案服务信息中的备案号,一般情况下后面会带有 -1A,填写到 App Store Connect 中。

复制下方的备案号
复制下方的备案号

至此,你的 App Store 中国大陆 ICP 备案就完成了。

几款有意思的 Google Chrome 侧边栏扩展应用

1970-01-01 08:00:00

侧边栏(Side Panel)是 Google Chrome 浏览器于 2022 年首次推出的特性,它可以让用户在浏览网页的同时,打开一个侧边栏,显示一些常用的小工具或者应用。

早期,一些扩展实现了类似侧边栏的功能,即支持在网页的右侧或左侧显示一个窗口。但是,这些扩展的体验并不是很好,它可能会遮挡网页的内容,或者在切换标签页时,每次都需要重新打开。

而 Chrome 的侧边栏特性,可以保持侧边栏的显示状态,不受标签页切换的影响。开发者能实现的功能更多,用户体验也更好。

Chrome 内置了一些默认的侧边栏应用,例如:阅读清单、书签、历史记录、Google 搜索等。如今,越来越多的开发者开始适配这个特性,推出了各种各样的侧边栏扩展应用。接下来,我将介绍一些我使用过的,还不错的 Chrome 侧边栏应用。

GetVM:免费的云端 Linux 和 IDE

Linux 是一个非常强大的操作系统,它在服务器领域有着广泛的应用。我还记得十多年前上大学时,为了体验 Fedora 和 Ubuntu 等 Linux 发行版,我曾经在 Windows 系统上学会了安装双系统。

GetVM 这款 Chrome 侧边栏扩展,可以帮你在浏览器中快速创建一个运行在云端的 Linux 虚拟机。使用了侧边栏特性的好处在于,你无需频繁切换窗口,从而更加专注于学习或者工作。

GetVM 在侧边栏打开 Ubuntu Desktop
GetVM 在侧边栏打开 Ubuntu Desktop

你可以根据需要直接打开预设模板,例如 Ubuntu Desktop,Jupyter Notebook,VS Code,以及各类数据库等。或者提交自己的 Docker Hub 镜像链接到 GetVM

无论是学习编程,测试代码,或者出于安全考虑打开一个不太放心的网站,都可以在 GetVM 中完成。

GetVM 最大的优势是轻量便捷,你不需要在刚入门编程时就被复杂的环境配置困扰,也不需要担心污染本地环境把系统搞得乱糟糟,甚至崩溃。只需要在浏览器菜单栏点击一下,就可以在侧边栏中立刻获得一个 Linux 的虚拟机,是新手入门 Linux 非常不错的选择。你可以前往 Chrome 应用商店安装 GetVM

豆包:浏览器中的 AI 助手

两年前,ChatGPT 引爆了生成式 AI 的热潮,如今已经被广泛应用在各种场景中。豆包是字节推出的 AI 助手,同时提供了支持 Chrome 侧边栏的扩展应用,它可以帮助你在浏览网页时,快速调用 AI 助手,获取文本摘要、翻译、生成图片等功能。

豆包
豆包

如今,浏览器扩展类的 AI 助手已成红海,甚至成为了侧边栏应用最多的类别之一。除了豆包的海外版 Cici,还有包括 SiderMonicaManganumElmo Chat 等都运用了 Chrome 的侧边栏特性,并且做得都很不错。

当然,豆包的优势之一是目前完全免费,例如之前你可能需要基于大模型的网页翻译,都需要配置自己的 API 密钥或者订阅,而这一切在豆包和 Cici 中都是免费的。此外,豆包在不支持侧边栏的浏览器,例如 Arc 中也通过技术手段实现了类似的功能,的确是一款很用心的产品。

豆包在 Arc 浏览器实现了类似效果
豆包在 Arc 浏览器实现了类似效果

Tab Shelf:借助侧边栏实现垂直标签页

Edge 浏览器在 2021 年就推出了垂直标签页的功能,受到了很多用户的好评。垂直标签,顾名思义就是可以将标签页放在浏览器的左侧,而不是传统的上方。这样做的好处在于,可以更好地利用宽屏显示器的空间,标题显示更长,标签页显示更多,也就更利于区分和筛选。

Edge 垂直标签页
Edge 垂直标签页

虽然有很多浏览器都支持了垂直标签页,但是 Chrome 本身并没有这个功能。不过,有一款叫做 Tab Shelf 的侧边栏应用,可以帮助你实现类似的效果。

Tab Shelf
Tab Shelf

Tab Shelf 可以让你在 Chrome 浏览器的侧边栏中,管理当前的所有标签页,提供了标签页分组、静音、复制、固定、睡眠、关闭等功能。

无论从颜值还是功能,Tab Shelf 都是一款非常不错侧边栏垂直标签页扩展,值得一试。唯一不足的地方是没有提供中文界面,当然你可以用上面提到的豆包对 官方手册 进行翻译或总结,应该不会有太大问题。

Chrome 应用商店中类似通过侧边栏实现垂直标签页的扩展还有很多,例如国内开发者做的 侧边栏垂直标签页

Page Sidebar:侧边栏打开网页

如果你之前用过 Arc 或者 Edge 浏览器,那么对于分屏显示网页应该不陌生。分屏显示可以让你同时查看两个网页,在需要跨页面操作或者对比内容时非常有用。

在 Arc 中使用 Split View 分屏显示网页
在 Arc 中使用 Split View 分屏显示网页

Google Chrome 本身并不支持分屏显示,但是 Page Sidebar 的侧边栏应用,通过曲线救国的方式实现类似的效果。

你可以通过右键选中当前页面的任意链接,从菜单中找到「Open link in sidebar」的选项,然后就可以在侧边栏中打开这个链接。例如在浏览文章列表的同时,可以在侧边栏中打开文章详情。

Page Sidebar
Page Sidebar

当然,Page Sidebar 使用了 iframe 的方式来打开网页,所以有些网站可能会有安全策略的限制,导致无法正常打开。但是对于大部分网站,Page Sidebar 都能正常工作。此外,Page Sidebar 的开发者还提供了另一款侧边栏扩展应用 Note Sidebar,这是一款笔记应用,功能非常简单,这里就不再赘述了。

嘀嗒清单:轻量的待办事项工具

嘀嗒清单是知名的国产待办事项工具,它的 Chrome 扩展应用也支持了侧边栏。由于是采用了类似 Page Sidebar 的方式直接嵌入了嘀嗒清单的 Web 版本,所以你可以侧边栏中完成 Web 版本的所有操作,包括添加任务、查看任务、设置提醒等。

嘀嗒清单
嘀嗒清单

如果你是嘀嗒清单的用户,推荐 安装试一试

侧边游戏:百余款小游戏

除了上面提到的工具类应用,侧边栏也可以用于放松一下,玩一局小游戏。侧边游戏 提供了百余款免费的小游戏,你可以在侧边栏中直接打开,无需切换标签页。

侧边游戏
侧边游戏

这些小游戏包括了各种类型,例如射击、益智、休闲、竞速等,适合各种年龄段的用户。随时随地玩一局,劳逸结合。

小结

最后再提一下 Chrome 侧边栏相关的设置选项。你可以在 Chrome 浏览器的设置中,找到“外观”选项,然后在“侧边栏”中选择默认的侧边栏位于左侧或右侧,这个根据个人习惯选择即可。

Chrome 设置
Chrome 设置

此外,你还可以前往 chrome://flags 中,搜索 side-panel,可以看到一些关于侧边栏的实验性特性。但需要谨慎使用,因为这些特性可能会导致浏览器不稳定。

侧边栏的出现,让一些工具类和效率类的应用有了更好的展示方式和体验,使用起来更加便捷,无需频繁切换窗口和打开单独的网页。

但目前 Google Chrome 侧边栏应用的生态也还不够完善,官方时常做功能上的变更,而开发者往往也是直接嵌入 Web 版本的页面,缺乏一些原生的适配,让整体的产品体验略显粗糙。

目前,AI 助手类的应用基本占据了 Chrome 侧边栏应用的半壁江山,也希望未来能有更多有趣的应用出现。当然,我更加希望 Google Chrome 不要原地踏步,在完善侧边栏特性的同时,也积极吸收其他浏览器的优秀特性,让用户体验越来越好。

GetVM 让编程学习更快一步

1970-01-01 08:00:00

早些时候,我整理了自己历年编写的机器学习教程,取名为 《动手实践人工智能 AI By Doing》,并将其免费公开出来。希望在如今的 AI 热潮下,能够帮助更多的小白入门机器学习。非常开心,有大量的读者给予了积极的反馈。

这个系列的教程主要是基于 Python 编程语言,通过 Jupyter Notebook 进行交互式编程,让读者能够更加直观地理解人工智能的原理和实现方法。在系列教程中,我也提到了一些 Linux 的使用技巧,因为在人工智能领域,Linux 是一个非常重要的操作系统。但是,我发现很多读者对 Linux 的使用还不是很熟悉,有的读者甚至都没有使用过。

于是,我开始动手开发一个工具,帮助读者更加方便地学习 AI 编程,同时也能够便捷地使用 Linux。这个工具就是 GetVM

GetVM 是一个 Google Chrome 浏览器扩展,它可以帮助你在浏览器中快速创建一个 Linux 虚拟机,与本地环境完全隔离。你可以选择直接打开预设模板,例如 Jupyter Notebook,VS Code,Ubuntu Desktop 等。

你可能会问,支持云端运行 IDE 或 Jupyter Notebook 的服务很多,例如最知名的 Google Colab,CodeSandbox,GitHub Codespaces 等。GetVM 有什么优势呢?或者有什么特别之处呢?

实践出真知

编程学习是 GetVM 的一个重要应用场景。而在编程中,我们经常会说 “Talk is cheap, show me the code.”,也印证了实践的重要性。

GetVM 的优势之一是轻量便捷,你不需要在刚入门编程时就被复杂的环境配置困扰,也不需要担心污染本地环境把系统搞得乱糟糟,甚至崩溃。只需要在浏览器菜单栏点击一下,就可以获得一个 Linux 的虚拟机,可以在其中安装各种编程工具,进行实践。

我之前也长期使用 Google Colab 等其他的云端服务,或者在本地安装 Jupyter Notebook,但是这些方式都不那么便捷。尤其是在学习场景下,往往需要外接显示器的加持,打开多个窗口,或者分屏操作。

GetVM 使用了 Chrome 提供的 Side Panel 特性,可以在浏览器中直接打开一个侧边栏,无需切换窗口。如下图所示,你可以在设置中,选择默认的侧边栏位于左侧或右侧。

例如,当你在阅读我的 AI By Doing 教程时,可以直接在侧栏中打开 Jupyter Notebook,边学习边实践。又或者,你购买了少数派的《100 小时后请叫我程序员》专栏,同样可以非常方便地在 GetVM 中打开 VS Code 进行编程练习。

除了文字类教程,B 站或 YouTube 上的优质视频也很多,GetVM 也可以帮助你在学习视频的同时,直接实践。

我还特别收集整理了网络上的一些优质教程,大学公开课,视频教程等,你可以访问 GetVM Explore 页面查看。当然,目前主要收集了一些英文的教程,后续也会在推出 GetVM 中文版本时(正在走国内合规流程),会增加各类优质的中文教程。

云端开发

云端开发已经被谈论很多年了,甚至早在 2021 年,GitHub 就在 官方博客 中介绍过团队都使用 Codespaces 进行云端协作开发的情况。然而,云端开发的优势和劣势都很明显。优势是可以随时随地开发,不受本地设备和环境限制。劣势是需要整个团队的配合,稳定的网络,往往不如本地开发流畅。而另一部分云端开发的需求,主要在于本地的配置不够。例如训练 AI 模型时,因为需要大量的计算资源,经常需要使用云服务商提供的 GPU 或 TPU。

由于 GetVM 提供了一个完整的 Linux 虚拟机,所以你可以在其中安装各种软件,服务于一部分云端开发需求。以前端开发为例,你可以在 GetVM 中安装 Node.js,Vue CLI,React 等工具,进行前端项目的开发和编译。

当然,和入门学习不同,我们深知服务于专业的开发人士会面对更多的挑战。例如,如何保证服务的稳定性,虚拟机的配置足够,数据的安全性等。因此,GetVM 还有很长的路要走。后续也会针对专业人士的需求,增加开放端口,提供更高配置的虚拟机或者独立云主机等功能。也欢迎开发者们试用,并给出宝贵的建议。

当然,除了编程学习和开发的主要场景,你还可以使用 GetVM 来应对一些安全的需求。例如,你不确定一个网站是否安全,可以在 GetVM 中打开,避免泄露个人信息。

或者测试一些不那么放心的脚本,担心污染或影响到本地环境,这些之前需要在隔离环境中操作的需求,也可以在 GetVM 中完成。

存在的问题

GetVM 目前仍然属于早期阶段,还有很多功能和细节需要完善。例如,目前只支持 Ubuntu Desktop,VS Code,Jupyter Notebook,Terminal 等几种基础模板,后续会增加更多的模板,例如 PyCharm,IDEA 等开发工具,以及不同的 Linux 发行版等。或许,你可以在不久之后定制自己的模板。此外,GetVM 也规划了很多新的功能,例如文件上传下载,开放端口等,进一步提高 GetVM 的灵活性和可用性。

另外,上面提到 GetVM 使用了 Side Panel 侧边栏的特性,这个特性目前只支持 Google Chrome 浏览器上支持的最好。其他一些使用 Chromium 内核的浏览器,例如 Microsoft Edge,Brave,Arc 等,可能无法达到很好的体验。我们正在增加对其他浏览器的适配支持,以提供更好的用户体验。

价格和付费计划

目前,GetVM 提供了免费计划,每天可以开启 5 次虚拟机,满足低频日常学习的需求。同时,为了避免被挖矿等滥用行为搞崩,不得已增加了网络限制。很遗憾免费计划无法访问外网,虽然我个人非常愿意开放。不过,但对于 Python,Linux 学习等场景,网络限制并不是很大的问题。

此外,GetVM 还提供了 Pro 付费计划,可以解锁网络和无限制的虚拟机开启次数。Pro 计划当前的早鸟价是 1.9 美金(约 15 元人民币)/月,由于较高的资源成本,这个价格只能算是真正的亏本早鸟价。😂 后续也会为 Pro 用户增加更多的专属功能和特性。由于尚未正式在国内推出,目前仅支持国内的银联信用卡付款。付款时,可以使用 TRYFREE 的兑换码,获得首月免费权益。

欢迎有兴趣的读者前往 Google Chrome 商店 下载 GetVM 体验,或者访问 GetVM 官网 了解更多信息。

WildCard - 让国内用户轻松订阅 ChatGPT、Claude 等海外 AI 服务

1970-01-01 08:00:00

WildCard
WildCard

ChatGPT 等对话式 AI 吸引了全球众多用户,但不少身在国内的伙伴在注册和使用过程中都会遇到一些障碍。

海外邮箱注册、信用卡支付、网络访问等问题经常令人望而却步。不过,现在有了一个很棒的一站式解决方案 WildCard,让普通人也能轻松体验最新的 AI 服务和优质的海外服务。

WildCard 核心亮点

WildCard 是一个面向全球用户的 OpenAI 辅助服务平台。它的核心是美国虚拟银行卡服务,用于解决国内用户在注册和使用海外服务时遇到的支付问题。

WildCard 并不仅仅是一个虚拟银行卡服务,它提供了一站式解决方案,几乎解决了普通人在使用海外服务时遇到的所有问题。

WildCard 核心亮点如下:

  1. 注册全家桶: 提供海外邮箱、海外手机号、美国银行卡等一站式服务,解决注册和支付问题。
  2. 中转访问: 如果你嫌自己注册麻烦,还可以直接使用「ChatGPT + Claude 随心用」服务或 API 中转服务;可以在付款时输入我的邀请码 HUHUHANG,享受立减 1 美金优惠。
  3. 代理浏览器: 如果你没有可靠的科学上网线路,WildCard 提供了集成了代理服务的浏览器,无需任何设置,即可访问;
  4. 人民币直接支付: 可使用支付宝直接以人民币结算

总之,你能遇到的门槛,WildCard 都帮你解决了。

WildCard 网站整体设计简洁,操作流程也非常直观,我个人非常喜欢,也在长期使用。

WildCard 仪表盘
WildCard 仪表盘

有需求的话,可以用我的 WildCard 邀请码注册立减 2 美金(我也会得 2 美金 😄):HUHUHANG

无需上传证件照片

很多海外的服务平台,为了保障账户安全,会要求用户上传身份证、护照等证件照片。这对于一些用户来说,可能会有一定的隐私顾虑。毕竟证件照片一旦泄露,可能会被不法分子利用。

WildCard 采用了支付宝实名认证的方式,无需上传证件照片,一键即可完成实名认证,保护了用户的隐私。这一点已经区别于 99% 的海外服务商。

零门槛体验 ChatGPT

除了以上这些,WildCard 还拥有自己的海外资源池,可以提供美国家庭宽带 IP、海外手机号、海外邮箱等注册所需的各种信息。这些服务都可以在统一的平台一站式获取,免去四处奔波的麻烦。

零门槛注册
零门槛注册

它甚至提供了专用浏览器,集成了代理服务,也就是说如果你不懂科学上网,也可以直接使用 WildCard 提供的浏览器,无需任何设置,即可访问 ChatGPT。

考虑到近期 OpenAI 新账号的注册审核变得严格,WildCard 团队还推出了备用账号租用服务。也就是说,即便你自己的申请没有通过,也可以通过租用而直接使用。对于急于尝试 ChatGPT 的朋友,这无疑是一个方便的临时之选。

不止于 ChatGPT

WildCard 的支付服务不局限于 OpenAI,还支持其他海外服务,目前包括:OpenAI、Claude (Anthropic)、Midjourney、GitHub、Poe、Suno、Perplexity、HeyGen、Google Play、App Store、Amazon (AWS)、Microsoft、Twitter (X)、Overleaf、Cloudflare、Adobe、Patreon、Pixiv Fanbox、DLsite、Elenlabs、Runway 等。

特别是 Claude,目前甚至已经超过 ChatGPT 成为很多人的首选。其出色的网页版体验和模型,让很多人都对其赞不绝口。

WildCard 支持服务商
WildCard 支持服务商

又或者 App Store,很多人担心云上贵州和国内备案制会导致许多优质的国外应用无法在国区上架。而之前注册一个美区 App Store 账号,最大的问题就是支付。美区账号必须使用美国信用卡,国内的双币信用卡无法绑定。而 WildCard 提供的美国银行卡服务,就可以解决这个问题,让你非常方便地购买美区 App Store 的应用。

小结

总的来说,WildCard 极大地方便了国内普通用户使用海外的优质服务,打破了代理访问、身份认证、支付安全等多个障碍,让用户可以更加轻松地订阅 ChatGPT、Claude 等海外 AI 服务。通过我的查询,WildCard 应该在国内拥有运营实体,跑路的可能性也较低。WildCard 目前执行的 3.5% 的费率其实已经非常亲民合理,值得推荐给大家。

使用过程中遇到的问题可以在评论区交流讨论,也可以阅读 WildCard 帮助手册

实用的 macOS 内置命令,省下买第三方应用的钱

1970-01-01 08:00:00

本文灵感来自于 Advanced macOS Command-Line Tools,作者筛选了部分更常用的命令,并提供了使用场景和案例。

每个人都有自己买 Mac 的理由,而我喜欢 Mac 是因为其提供了简约的外形、易用的 macOS 系统、独享的 macOS 应用、以及基于 Unix 的终端。而今天,我要介绍的就是 macOS 终端中的一些实用的内置命令行工具。

你或许早已在影视作品里见过终端的样子,电影里的黑客们总是在一个黑色的背景下,输入一些看不懂的命令,然后就可以实现各种高级操作。它往往看起来很「酷」,但这种操作方式却是计算机发展的早期阶段就已经存在的。在那个时候,计算机还没有图形用户界面(GUI),用户只能通过输入命令来与计算机进行交互。

时至今日,随着计算机的发展,图形用户界面已经成为了主流,但终端仍然是一种非常有用的工具。它可以让用户以更高级、快捷的方式控制和配置系统,执行操作系统提供的各种命令和脚本,从而实现特定的功能和任务。例如:创建、复制、移动、删除文件和目录,查看和修改文件权限,管理网络连接、系统服务和进程等。

macOS 中默认提供了终端 App,你可以从 Launchpad 启动台中找到它。Apple 还贴心了提供了 终端使用手册,你可以从里面了解到一些终端的使用和配置方法。

接下来,我会分享一些实用的 macOS 内置命令行工具,它们通过终端来使用,可以提升你的 Mac 使用体验。

实用的 macOS 内置命令

防止系统休眠:caffeinate

Mac 上有一些能够让系统保持唤醒的应用,它对于需要长时间运行的任务非常有用,如文件下载、数据同步等。

这些应用有的甚至是收费的。其实你完全不需要,因为 macOS 本身就提供了一个 caffeinate 命令,它可以让你的 Mac 保持唤醒状态。

使用 caffeinate 命令时,你可以通过 -s 参数来保持系统唤醒状态,通过 -u 参数来仅防止系统进入睡眠状态,而不锁定屏幕。你还可以通过 -t <seconds> 参数来指定保持唤醒状态的时间。例如:

caffeinate -s

在终端中执行上述命令后,你的 Mac 将会保持唤醒状态,直到你按下 Control + C 组合键来终止 caffeinate 命令。

如果你希望 Mac 在 1 小时内保持唤醒状态,那么你可以执行以下命令:

caffeinate -t 3600

上面的 3600 代表 3600 秒,即 1 小时。

你还可以通过 man caffeinate 命令来查看 caffeinate 命令的帮助文档。接下来介绍的其他命令也都可以使用 man 命令来查看帮助文档,后续不再赘述。

复制文件内容:pbcopy

如果你希望复制一个文本文件的全部内容,之前会怎么做呢?

我想大多数人都会打开这个文本文件,然后按下 Command + A 组合键来全选文本,再按下 Command + C 组合键来复制文本,最后关闭这个文本文件。

这种方式虽然可以实现目的,但是却比较繁琐。其实你可以使用 pbcopy 命令来实现同样的效果。

pbcopy 命令用于在剪贴板和终端之间传递文本内容。它可以将终端输出的文本复制到剪贴板,而 pbpaste 命令则可以将剪贴板的内容粘贴到终端。

例如,我们有一个文本文件 hello.txt,它位于当前用户的主目录下,你就可以使用以下命令来复制它的全部内容:

cat ~/hello.txt | pbcopy

上面的命令中,cat 命令用于输出文件的全部内容,| 符号用于将 cat 命令的输出作为 pbcopy 命令的输入。这样就可以把 hello.txt 文件的全部内容复制到剪贴板了。

接下来,你就可以在任意地方粘贴即可。

网络质量监测:networkQuality

平常如果你需要测试网络的质量和延迟,你可能会使用一些第三方的工具,如 Speedtest

但实际上,我们有更简单的方法来测试网络质量,那就是使用 networkQuality 命令。

你可以直接在终端中执行此命令:

networkQuality

执行后,命令会输出当前网络的质量和延迟信息,如下所示:

上面的输出中,Uplink capacityDownlink capacity 分别代表上行和下行的带宽,Responsiveness 代表网络的响应速度,Idle Latency 代表网络的延迟。

该工具会使用 Apple 的 CDN 服务器来测试网络质量,你也可以通过 Apple 的 官方文档 来了解更多信息。

图像处理工具:sips

sips 是一个非常强大的图像处理工具,它可以用于调整图像的大小、分辨率,转换图像格式,添加元数据等。

例如,我们有一张图片 image.png,它位于当前用户的主目录下,你就可以使用以下命令来将它转换为 JPEG 格式:

sips -s format jpeg ~/image.png --out ~/image.jpg

上面的命令中,-s format jpeg 用于指定输出图像的格式,--out ~/image.jpg 用于指定输出的文件路径。

sips 提供了一系列实用的参数,例如 -s formatOptions 参数可以用于指定格式选项,如 JPEG 压缩质量。举例:

sips -s format jpeg -s formatOptions 80 ~/image.png --out ~/image.jpg

上面的命令中,-s formatOptions 80 用于指定 JPEG 的压缩质量为 80。

如果你希望批量转换某个目录(例如 ~/images)下的所有图片,那么你可以使用以下命令:

cd ~/images
for file in *.png; do sips -s format jpeg "$file" --out "${file%.*}.jpg"; done

上面的命令中,for 循环用于遍历目录下的所有 PNG 图片,"${file%.*}.jpg" 用于将 PNG 图片的后缀名替换为 JPG。

由于 sips 支持的自定义参数过多,甚至比很多第三方的图片格式转换工具都强大,这里就不再一一介绍。文章的末尾会分享小白如何更高效地使用不了解的命令行工具。

文件转换工具:textutil

和 sips 类似,textutil 是一个非常强大的文件转换工具,它可以用于转换文本文件、HTML 文件、RTF 文件、Microsoft 文件、iWork 文件等。

例如,我们有一个 RTF 文件 hello.rtf,它位于当前用户的主目录下,你就可以使用以下命令来将它转换为纯文本文件:

textutil -convert txt ~/hello.rtf

上面的命令中,-convert txt 用于指定输出文件的格式为纯文本。

textutil 提供了一系列实用的参数,例如 -encoding 参数可以用于指定输出文件的编码格式。举例:

textutil -convert txt -encoding UTF-8 ~/hello.rtf

上面的命令中,-encoding UTF-8 用于指定输出文件的编码格式为 UTF-8。

同样,你可以结合 Shell 脚本来批量转换某个目录(例如 ~/documents)下的所有 RTF 文件,例如:

cd ~/documents
for file in *.rtf; do textutil -convert txt -encoding UTF-8 "$file"; done

从终端中打开:open

如果你希望在终端中打开某个文件或目录,那么你可以使用 open 命令。这对于习惯在终端中操作的人来说,是一个非常实用的命令。

例如,我们有一个文本文件 hello.txt,它位于当前用户的主目录下,你就可以使用以下命令来在终端中打开它:

open ~/hello.txt

上面的命令中,open 命令用于在 macOS 上打开文件、目录或 URL。它会根据文件类型自动选择合适的应用程序打开。

如果你希望在终端中打开当前目录,那么你可以使用以下命令:

open .

上面的命令中,. 代表当前目录。你会发现,执行后会自动打开 Finder。

你甚至可以用 open 打开网页,例如:

open https://sspai.com

文件快速搜索:mdfind

如果你需要在 Mac 中搜索,大概率会使用 Spotlight 或者 Finder 右上角的搜索框。但是,你可能不知道,终端中也有一个非常强大的搜索工具,它就是 mdfind

例如,我们想要搜索当前用户主目录下的所有 PNG 图片,那么你可以使用以下命令:

mdfind -name "*.png"

可以指定路径搜索,例如:

mdfind -onlyin ~/documents -name "*.png"

你甚至可以搜索文件内容,例如:

mdfind -onlyin ~/documents -name "*.txt" -literal "hello"

上面的命令中,-literal "hello" 用于指定搜索内容为 hello

我们还可以指定类型搜索,例如:

mdfind kind:image -name "hello"

上面的命令中,kind:image 用于指定搜索类型为图片。即全部图片中,文件名包含 hello 的图片。

由于 mdfind 支持通配符,所以在操作的灵活性上比图形界面的搜索工具更强大。此外,其搜索的速度也非常快,这对于大量文件的用户来说,是一个非常实用的命令。

屏幕截图:screencapture

你可能会使用快捷键或者第三方 App 实现屏幕截图,但是你可能不知道,终端中也有一个非常强大的截图工具,它就是 screencapture

例如,我们想要截取整个屏幕,那么你可以使用以下命令:

screencapture screenshot.jpg
open screenshot.jpg

上面的命令中,screencapture 命令用于在 macOS 上捕获屏幕截图并保存为文件。open 命令用于在终端中打开文件。

如果你想要截取某个窗口,那么你可以使用以下命令:

screencapture -i screenshot.png
open screenshot.png

你可能会问,这比起来似乎没有快捷键方便。当然,对于日常使用来说,快捷键的方式更加方便。但是,如果你需要更多的控制选项,或者用于脚本自动化时,screencapture 就会变得非常有用。

我们介绍命令行工具也不是说要摒弃掉图形界面的方式,而是希望你能够了解到,终端中也有很多实用的工具,它们可以帮助你更好地完成工作。

更新软件和系统:softwareupdate

Mac 上还有一些系统配置和管理的命令行工具,例如 softwareupdate。它可以用于更新软件和系统。

例如,我们想要列出可用的更新,那么你可以使用以下命令:

softwareupdate -l

执行更新的话,你可以使用以下命令:

softwareupdate -i -a

参数太多看不懂怎么办?

这篇文章中,我们只是介绍了一些常用的,或者是说我本人常用的命令行工具。实际上,macOS 中还有很多命令行工具,你可以通过 这个列表 查看。

应该有很多人会问,这么多命令行工具,参数那么多,我怎么记得住?尤其是对于很多不了解 Linux 命令,或者没有编程经验的用户来说,这些命令行工具可能会让人望而生畏。

面对这样的问题,我建议你可以通过以下方式来解决:

  1. 阅读命令行工具的帮助文档,例如 man 命令。这个过程不需要你完全看懂,只是大致了解这个工具能干什么就行。
  2. 借助 ChatGPT 解决实际需求。

由于 ChatGPT 的出现,我们可以通过对话的方式来解决实际需求。例如,你可以把自己的需求描述清楚,然后让 ChatGPT 帮你完成操作。

需求的描述过程中需要注意:

  1. 尽量表述详细,例如你希望在哪个路径下操作,怎样操作等;
  2. 给出一些例子,比如操作前是什么样的,你希望操作后的效果是什么样的,这样 ChatGPT 才能更好地理解你的需求。

小结

macOS 内置的命令行工具提供了丰富的功能和灵活性,使用户能够更高效地管理和操作系统。这些工具广泛应用于各种场景,从日常任务到系统维护都能发挥作用。

每个命令行工具都有其独特的作用和使用场景,我们可以根据自己的需求选择合适的工具。同时,这些工具提供了丰富的参数选项,用户可以根据具体需求进行定制和调整。对于有一定技术基础的用户和开发者来说,这些命令行工具是提高效率和解决问题的强大工具。

本文介绍的只是一小部分 macOS 内置命令行工具,还有很多其他有用的工具等待你去发现和探索。希望本文能为你提供一些有用的信息和启发,帮助你将 Mac 物尽其用。

Prophet 因素分解工具实践

1970-01-01 08:00:00

本文已更新,你可以访问 AI By Doing 以获得更好的阅读体验。
本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

介绍

时间序列分析实验中,我们学习了使用 ARMA 和 ARIMA 对平稳和非平稳时间序列建模方法,对时间序列分析流程中涉及到的平稳检验和纯随机性检验进行了充分了解。这篇文章中,我们将了解另一种常用的时间序列建模方法,同时学习 Facebook 开源的时间序列建模利器 Prophet 的使用。

知识点

季节性趋势序列

前面我们学习了使用 ARMA 和 ARIMA 对平稳和非平稳时间序列建模方法。实际上,我们所面对的序列都表现出一定程度的随机特性。当然,这种随机特性还没有到达不值得分析的地步。在时间序列的预测分析中,还有一种非常常见的序列,它们表现出十分显著的季节特性。

例如,下面给出了伦敦市历年(2000-2016)表面气温数据集。你会发现,气温的变化会随着时间的变化,呈现出明显的规律。

import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline

london = pd.read_csv(
    "http://labfile.oss.aliyuncs.com/courses/1176/surface-temperature-observations.csv", index_col=0)
london.index = pd.to_datetime(london.index)  # 转换时间索引
print("数据量:", len(london))
london.plot(figsize=(10, 5))  # 绘图

因素分解介绍

那么,对于这样的序列数据,我们往往会使用一种叫因素分解方法来分析。因素分解法最早由英国统计学家 W.M.Persons 在 1919 年提出。简单来讲,我们认为时间序列的波动虽然各式各样,但都可以总结为受以下四类因素影响:

于是,在进行时序分析时,我们假定序列会受到这四个因素中的全部或部分的影响,呈现出不同的波动特征。从而衍生出两种模型:加法模型和乘法模型。

加法模型,顾名思义就是将上述四类因素相加组合在一起:

$$ x_t = T_t + C_t + S_t + I_t$$

乘法模型,同样就是将上述四类因素相乘组合在一起:

$$ x_t = T_t * C_t * S_t * I_t$$

因素分解思想主要有以下两个优点:

  1. 克服其它因素干扰,单纯测度出某一个确定性因素(季节、趋势等)对序列的影响。
  2. 根据序列出现的确定性特征,推断出各种确定性因素彼此之间的相互作用关系以及它们对序列的综合影响。

Prophet 工具介绍

Prophet 是 Facebook 于 2017 年开源的时间序列分析工具,其提供支持 R 和 Python 语言的接口。Prophet 开发的理论基础依赖于因素分解法中的加法模型,故其非常适合于季节性时间序列数据分析。与此同时,Prophet 内建了缺失值和异常值处理机制,能从一定程度上减少序列预处理的负担。

Prophet 原本是 Facebook 核心数据分析团队的内部工具,开源之后的确有助于更多人对季节性时间序列完成确定性分析。加法模型虽然听起来很简单,但其推导和实现非常复杂,Prophet 从一定程度上降低了使用和分析门槛。

加法模型

接下来,我们将使用上面提供的伦敦市表面气温数据集,来学习 Prophet 工具的使用。

Prophet 规定传入的数据必须遵循一定的数据结构,其只能包含两列数据,分别是:时间 ds 和数值 y。于是,我们现对 london 数据集进行处理。

"""此单元格只能执行一次,重复执行需重启 kernel
"""
london = london.reset_index()  # 重置索引
london.columns = ['ds', 'y']
london.head()

上方的单元格只能执行一次,原因是 reset_index() 重置索引的操作重复运行会有累计效应而报错。

接下来,我们需要检查 ds 的格式,根据 Prophet 规定,推荐格式为 YYYY-MM-DDYYYY-MM-DD HH:MM:SS。同时,y 列必须为数字。由于 london 符合要求,就无需再做改变。

Prophet 提供了与 scikit-learn 相似的 API 结构。当我们使用其创建加法模型示例后,调用 fitpredict 方法即可完成训练和预测。

from fbprophet import Prophet
import warnings

warnings.filterwarnings('ignore')  # 忽略警告
m = Prophet()  # 创建加法模型
m.fit(london)  # 训练

接下来,我们创建一个预测序列,这里可以直接使用 make_future_dataframe 方法,也可以自己通过 Pandas 创建相应格式的 DataFrame。make_future_dataframe 方法支持 pd.date_range 方法支持的全部 freq。原数据频率为天 freq='D',所以这里创建接下来 365 天的待预测序列时间索引。

future = m.make_future_dataframe(periods=365, freq='D')  # 生成预测序列
future.tail()  # 显示序列最后 5 个数据

预测当然就是调用 predict 方法。注意,predict 会返回一个 19 列的 DataFrame。该 DataFrame 包含了季节性指标数据以及相应的置信区间。这里,我们只取出 'yhat', 'yhat_lower', 'yhat_upper' 3 列数据,其代表预测值 $\hat y$,以及对应的置信区间。

forecast = m.predict(future)  # 预测
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].head()  # 仅保留预测值和相应的置信区间

最后,我们可以将预测数据绘图。

fig = m.plot(forecast)  # 绘图

如上所示,黑色点代表真实值,蓝色线代表预测值,蓝色区间代表置信区间。由于 make_future_dataframe 在生成预测序列时也会包含原序列的时间,所以在预测时,实际上传入的是原序列 + 预测序列。故上图你可以看到置信区间一直从 2000 年延伸到 2018 年。

实际上,我们也可以不使用 Prophet 提供的相应方法,自行生成预测序列以及绘图。这样可以保证在原序列不变的前提下,绘制出预测值和置信区间。

"""此单元格只能执行一次,重复执行需重启 kernel
"""

future_ = future[len(london):]  # 得到不包含原序列的预测序列时间索引
forecast_ = m.predict(
    future_)[['ds', 'yhat', 'yhat_lower', 'yhat_upper']]  # 预测

london.columns = ['ds', 'yhat']  # 修改原数据列名
forecast_ = pd.concat([london, forecast_], sort=False)  # 合并原数据和预测数据 DataFrame
forecast_ = forecast_.reset_index(drop=True)  # 将合并后 DataFrame 重置索引编号

fig, axes = plt.subplots(figsize=(12, 7))
axes.plot(forecast_.index, forecast_['yhat'])  # 绘制原数据图
axes.fill_between(forecast_.index, forecast_['yhat_lower'],
                  forecast_['yhat_upper'], color='orange', alpha=.3)  # 绘制预测序列和置信区间

可以看到,上面就保证了原数据图像不变的情况下,直接在最后添加上了预测值和对于的置信区间。由图可见,预测数据的变化趋势和原数据相似,延续了序列季节性变化特性。

趋势变化点

上面的小节中,我们使用真实数据集对 Prophet 快速建立加法模型进行了学习。实际上,Prophet 还提供了其他一些有用的方法帮助我们在时序预测时使用,其中一个就是绘制趋势变化点。

趋势变化点,也就是时间序列突然引起的变化,例如从某时刻开始序列整体趋势突然由下降转为增长。标注趋势变化点是观察序列周期和变化趋势非常好的手段。Prophet 能自动检测并将这些变化点标记出来,我们需要借助于 add_changepoints_to_plot 方法。

from fbprophet.plot import add_changepoints_to_plot

fig_base = m.plot(forecast)  # 绘制预测结果
fig = add_changepoints_to_plot(fig_base.gca(), m, forecast)  # 将变化点添加到预测结果图

请注意,这里的变化点并不是对序列值由增至减或由减至增变化时的标记,而是序列变化的整体趋势。如上图所示,横向红线标记了序列的整体变化趋势,此趋势线的变化点即被标记出趋势变化点。

乘法模型

上面伦敦气温变化的示例中,加法模型能够很好地应对,因为预测时只需要将季节性趋势增加到序列中即可,类似于复制这种变化趋势。但在有一些序列中,加法模型就无法准确反映出变化的趋势,季节性趋势有可能成倍数增加状态。此时,就需要使用到乘法模型。

例如,下面提供了 1949 年至 1960 年期间某航空公司的乘客数量变化序列。

air = pd.read_csv(
    'http://labfile.oss.aliyuncs.com/courses/1176/example_air_passengers.csv')
air.plot(figsize=(10, 5))  # 绘图

可以明显看出,乘客数量反应到季节性上,呈现出逐年扩大的趋势。此时,如果我们应用加法模型预测。

m_additive = Prophet()  # 加法模型
m_additive.fit(air)  # 训练
future = m_additive.make_future_dataframe(50, freq='MS')  # 生成预测时间序列
forecast = m_additive.predict(future)  # 预测
fig = m_additive.plot(forecast)  # 绘图

如上图所示,加法模型在季节性周期变化上是固定的。虽然整体呈现增长趋势,但后续周期中的序列值预测明显无法准确反应原数据的变化。此时,我们就引入乘法模型。

m_multiplicative = Prophet(seasonality_mode='multiplicative')  # 乘法模型
m_multiplicative.fit(air)  # 训练
future = m_multiplicative.make_future_dataframe(50, freq='MS')  # 生成预测时间序列
forecast = m_multiplicative.predict(future)  # 预测
fig = m_multiplicative.plot(forecast)  # 绘图

如上图所示,乘法模型就能反映出乘客数量随周期倍增的特性,相比于加法模型,预测结果一定会更加准确。

小结

这篇文章中,我们接触到了呈现出季节性特征的序列数据,并学会使用 Prophet 工具建立加法和乘法模型。简单来讲,如果序列每个季节周期内的变化不明显,那么就可以使用加法模型。反之,如果序列随着时间增加,每个季节周期内的趋势增加或减小,则偏向于使用乘法模型。

本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

系列文章

时间序列数据建模分析

1970-01-01 08:00:00

本文已更新,你可以访问 AI By Doing 以获得更好的阅读体验。
本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

介绍

前面,我们已经学习了使用 Pandas 进行时间序列处理的一些方法和技巧。这篇文章中,将带大家学习时间序列数据分析的相关模型,并使用一些知名的工具来完成时间序列建模分析。

知识点

前面,我们学习了使用 Pandas 对时序数据进行处理的相关操作。实际上,当我们面对时序数据时,是想从中分析出某种规律并预测未来时间中数值可能发生的变化。例如,我们可能需要从股票数据中分析出公司未来的股价走势,从气象数据中得到未来某一时刻的天气信息,从访问日志数据中发现可能迎来的高峰。

当我们学习完机器学习建模之后,你可能会想到将其应用到时间序列建模过程中。例如,对于一个以时间为横轴,数值为纵轴的时间序列变化图像。是否可以引入回归模型进行预测呢?

上图呈现了股票变化曲线。我们是否可以把年份转换为序号,从而引入回归分析的手段得到未来的预测结果呢?答案肯定是「可以的」,因为用回归的手段实现,没有原理上的阻碍。不过,这只是方法应用上的探讨,至于回归预测的效果如何,我们就无从所知了。

回归预测中,我们举过一个十分经典的例子,那就是房价预测。一般情况下,房屋的价格和其面积正相关。也就是说如果通过二者构建回归模型,面积越大的房子,价格一般会越高。那么,如果我们将数据换成气温变化数据。春夏过度期间,温度会随着时间逐渐升高,但是这样的关系却不会像房价那样持续下去。因为气温的变化到一定时候就会变得平缓,并维持在某个区间波动。此时,如果我们应用回归模型去预测气温,就无法准确地反应现状了。

时序数据特点及分类

要想对时间数据进行分析并发现规律,首先得摸清楚时间序列数据有哪些特点:

与此同时:

了解完上面的 4 个特点,你会感觉如果直接将机器学习中的回归分析搬到时间序列的预测中了,效果可能并不会很理想。

在对时间序列数据的研究中,我们通常会根据不同的维度将其细分为以下几种不同的类型:

描述性时序分析

描述性时序分析又被称之为确定型时序分析,它主要是通过直观的数据比较或绘图观测,寻找序列中蕴含的发展规律。该方法简单直接,所以一般也是时序分析的第一步。

例如,1844 年,德国天文学家海因利希·史瓦贝在 Astronomische Nachrichten 报告了太阳黑子数量的周期性变化规律。其通过系统性的连续观测,发现太阳黑子的爆发呈现出 11 年作用的周期变化。

统计时序分析

描述性分析方法纵然直观,但其对数据的要求很高,需要保证数据分布呈现出一定的规律性,才能从中得到可信的结论。但是,很多时候我们遇到的时序数据都呈现出明显的随机性,准确预测其走势和变化规律变得困难。

于是,统计时序分析相关方法开始出现,人们尝试利用数理统计学相关的原理和方法来分析时间序列。

在统计时序分析中,一般又被细分为两类不同的分析方法,分别是:频域分析和时域分析。

频域分析,简单来讲就是我们假设任何一种无趋势的时间序列都可以分解成若干不同频率的周期波动。早期的频域分析方法借助傅里叶分析从频率的角度揭示时间序列的规律。后来,其借助了傅里叶变换,用正弦、余弦项之和来逼近某个函数。再到极大熵谱估计理论的引入,频域分析进入了现代谱分析阶段。由于谱分析依赖于强数学背景且不利于直观解释,导致该方法具有很大的局限性,我们也不再做过多介绍。

与频域分析不同,时域分析方法应用要广泛很多。时域分析的原理主要是参照事件发展过程中的惯性,从而通过惯性用统计来描述就是时间序列值之间存在的相关关系,而这种相关关系通常具有某种统计规律。时域分析的目的是,通过寻找出时间序列值之间相关关系的统计规律,并拟合出适当的数学模型来描述这种规律,进而利用这个拟合模型预测序列未来的走势。

时域分析方法的产生最早可以追溯到 1927 年出现的自回归 AR 模型。不久之后,英国数学家、天文学家 Walker 爵士在分析印度大气规律时使用了移动平均 MA 模型和自回归移动平均 ARMA 模型。这些模型奠定了时间序列时域分析方法的基础,其中的 ARMA 模型也被得以广泛应用。再到后来,美国统计学家 Box 和英国统计学家 Jenkins 系统地阐述了对求和自回归移动平均 ARIMA 模型的识别、估计、检验及预测的原理及方法。这些知识现在被称为经典时间序列分析方法,是时域分析方法的核心内容。

其中,ARMA 模型通常被用于平稳时间序列分析过程,而 ARIMA 模型则广泛应用于非平稳序列随机分析过程。

小贴士

注意:接下来的内容中,将涉及大量的统计学理论和计量经济学中涉及到的原理。我们不会对复杂理论进行推导和演算,而是直接拿出概念告诉你它的用途和用法。

平稳时间序列检验

什么是平稳时间序列?这就需要我们从概率统计的角度来定义。一般来讲,平稳时间序列有两种定义,分别是:严平稳时间序列和宽平稳时间序列。其中,严平稳要求序列所有的统计性质都不会随着时间的推移而发生变化。宽平稳则认为只要保证序列 二阶矩 平稳,就代表序列稳定。显然,严平稳比宽平稳的条件严格。严平稳是对序列联合分布的要求,以保证序列所有的统计特征都相同。

关于序列平稳性的检验,一般有两种方法,分别是:图检验和假设检验。图检验是根据时序图和自相关图显示的特征作出判断,因其操作简便而运用广泛。简单来讲,如果一张时序图呈现出明显的增长和下降趋势,那么就一定不平稳。

下面我们给出中国的人口数量变化曲线图,这是一个由年份组成的时间序列。我们称该组数据为 series1

import pandas as pd
from matplotlib import pyplot as plt
%matplotlib inline

series1 = pd.read_csv(
    "http://labfile.oss.aliyuncs.com/courses/1176/total-population.csv", index_col=0)
series1.plot(figsize=(9, 6))

可以看到,series1 呈现出明显的增长趋势,就一定不是平稳时间序列。

接下来,我们看一看另外一个时间序列数据。这里我们使用 NumPy 生成随机序列,并称之为 series2

import numpy as np

np.random.seed(10)  # 随机数种子
series2 = np.random.rand(1000)  # 生成随机序列
plt.plot(series2)  # 绘图
plt.ylim(5, -5)

可以看到,series2 的序列围绕着某个值上下随机波动,没有明显的趋势和周期性。一般来讲,我们会认为这类序列是平稳的。

不过,对于如上所示的平稳曲线图,我们一般还需要进一步通过自相关图来确认。

自相关图

自相关(英语:Autocorrelation),又称之为序列相关,是一个统计学上的概念。相关性其实就是变量之间的关系强度,这里会用到前面学过的皮尔逊相关系数进行检验。

在时间序列中,当我们使用以前的时间步长来计算时间序列观测的相关性时。由于时间序列的相关性与之前的相同系列的值进行了计算,就被称之为自相关。其中自相关函数 ACF 用来度量时间序列中延迟为 $k$ 时,相距 $k$ 个时间间隔(延迟期)的序列值之间的相关性,所形成的图称之为自相关图。

ACF 计算图示如下,原序列为延迟 $k$ 个时间间隔后计算与原序列之间的相关性系数(绿色部分)。

Python 中,我们可以利用 statsmodels 统计计算库中的 plot_acf() 函数计算和绘制自相关图,也可以使用 Pandas 提供的 autocorrelation_plot() 方法绘制。

from statsmodels.graphics.tsaplots import plot_acf
from pandas.plotting import autocorrelation_plot

fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(12, 4))

plot_acf(series1, ax=axes[0])
autocorrelation_plot(series1, ax=axes[1])

上图中,我们绘制 series1 的自相关图。其中,ACF 图纵轴数值表示相关性,皮尔逊相关系数对应 -1 到 1 之间。

接下来,我们同样绘制 series2 的自相关图。

fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(12, 4))

plot_acf(series2, ax=axes[0])
autocorrelation_plot(series2, ax=axes[1])

那么,怎样通过自相关图来判断时间序列的平稳性呢?如 series1series2 所对应的自相关图所示,平稳序列通常具有短期相关性。该性质用自相关系数来描述就是随着延迟 $k$ 的增加,平稳序列的自相关系数会很快地衰减向零。反之,非平稳序列的自相关系数衰减向零的速度通常比较慢,这就是我们利用自相关图进行平稳性判断的标准。所以,series2 对应的序列也就是一个平稳时间序列,而 series1 则是非平稳时间序列了。

与此同时,由于 series2 对应的自相关图中显示的序列自相关系数一直较小,在 0 附近震荡波动,那么我们会认为这是一个随机性非常强的平稳时间序列。

纯随机性检验

一般来讲,我们拿到一个时间序列之后,就会对其进行平稳性检验。如果序列平稳,那么就可以应用 ARMA 等成熟的建模方法完成分析。不过,并不是所有平稳序列都值得建模,例如上面的 series2 序列,虽然平稳,但是随机性太强。一般来讲,纯随机序列是没有任何分析价值的。

那么,又引出了新的问题:怎样判断一个平稳序列是否随机呢?这就会用到纯随机性检验。纯随机性检验的过程中,一般会涉及到两个统计量,分别是:Q 统计量和 LB 统计量(Ljung-Box)。但由于 LB 统计量是 Q 统计量的修正,所以业界通常所称的 Q 统计量也就是 LB 统计量。

Python 中,我们可以利用 statsmodels 统计计算库中的 acorr_ljungbox() 函数计算 LB 统计量,该函数默认会返回 LB 统计量和 LB 统计量的 P 值。如果 LB 统计量的 P 值小于 0.05,我们则认为该序列为非随机序列,否则就为随机序列。

from statsmodels.sandbox.stats.diagnostic import acorr_ljungbox

LB2, P2 = acorr_ljungbox(series2)
plt.plot(P2)

如上所示,series2 对应 LB 统计量的 P 值远大于 0.05。所以,可以判定其为随机序列,也就再无分析必要了。

下面,我们再看一个非随机性平稳时间序列的各项参数如何。实验给出了 1820-1870 年的太阳黑子统计数据 series3,绘制如下:

series3 = pd.read_csv(
    "http://labfile.oss.aliyuncs.com/courses/1176/sunspot.csv", index_col=0)
series3.index = pd.period_range("1820", "1869", freq='Y')  # 将索引转换为时间
series3.plot(figsize=(9, 6))

然后进行时间序列的第一步处理:平稳性检验。绘制自相关图如下:

fig, axes = plt.subplots(ncols=2, nrows=1, figsize=(12, 4))

plot_acf(series3, ax=axes[0])
autocorrelation_plot(series3, ax=axes[1])

可以看到自相关图随着延迟期数增加,接近于 0。但没有像上面随机序列那样在 0 附近波动,而后面由呈现出周期变化。所以,可以判断其为周期变化平稳序列。

接下来,我们进行纯随机性检验。计算序列随延迟期数增加的 LB 统计量和对应的 P 值,并绘制 P 值变化图像。

LB3, P3 = acorr_ljungbox(series3)
plt.plot(P3)

如上图所示,P 值远小于 0.05(注意纵轴单位),那么说明序列为非纯随机序列。

以上,我们介绍了对时间序列的预处理步骤,分别是:平稳性检验和纯随机性检验,而这两步往往是时间序列分析建模前的重要步骤。接下来,我们就要正式介绍时间序列建模过程。

ARMA 介绍及建模

ARMA 模型的全称是自回归移动平均模型,它是目前最常用的拟合平稳序列的模型。ARMA 模型一般又可以被细分为 AR 自回归模型,MA 移动平均模型和 ARMA 三类。

AR 模型非常简单,其思路来源于线性回归。即假设序列包含线性关系,然后使用 $x_{{1}}$ 至 ${x_{t-1}}$ 序列来预测 ${x_{t}}$。其中,$p$ 阶 AR 模型的公式为:

$$ X_{t}=c+\sum _{{i=1}}^{p}\varphi _{i}X_{{t-i}}+\varepsilon _{t}$$

其中,${c}$ 为常数项。$ \varepsilon _{t}$ 被假设为平均数等于 0,标准差等于 ${\sigma }$的随机误差值。${\sigma }$ 被假设为对于任何的 ${t}$ 都不变。$p$ 则代表落后期数。

若随机过程 $x_t$ 为现在与过去 $q$ 期随机过程 $ε_t, ε_{t-1},..., ε_{t−q}$ 之加权平均,则 $q$ 阶 MA 模型的公式为:

$$ {x_{t}=\varepsilon _{t} + \theta _{1}\varepsilon _{t-1} + \theta _{2}\varepsilon _{t-2} + \cdots + \theta _{q}\varepsilon _{t-q}}$$

其中,$θ_1,..., θ_q$ 是参数,$ε_t, ε_{t-1},..., ε_{t−q}$ 都是白噪声。

这里,我们没有对 AR 和 MA 模型的证明过程进行阐述,所以上面的公式会很难看懂。由于推导过程太过复杂,有兴趣的话需要另外抽时间自学。

ARMA 模型一般记作:$ARMA(p,q)$,即为 $p$ 阶 AR 和 $q$ 阶 MA 模型的组合。Python 中,我们可以利用 statsmodels 统计计算库中的 tsa.ARMA 类完成 ARMA 建模和预测。

接下来,我们以太阳黑子数据集 series3 为例,完成一次 ARMA 建模过程。建模前,首先需要确定 $p$ 和 $q$ 的取值。一般来讲,确定二者的取值有 3 种方法,分别是 AIC(Akaike Information Criterion ),BIC(Bayesian Information Criterion ) 和 HQIC(Hannan-Quinn Criterion )。

三种指标的计算方法如下:

import warnings
from statsmodels.tsa.stattools import arma_order_select_ic

train_data = series3[:-10]  # 80% 训练
test_data = series3[-10:]

warnings.filterwarnings('ignore')
arma_order_select_ic(train_data, ic='aic')['aic_min_order']  # AIC
arma_order_select_ic(train_data, ic='bic')['bic_min_order']  # BIC
arma_order_select_ic(train_data, ic='hqic')['hqic_min_order']  # HQIC

计算方法不一样带来的结果不一样,可以都尝试一下看看结果。既然 AIC 和 HQIC 结果一致。那我们就按照 AIC 方法建议我们使用 $p=2$ 和 $q=1$。接下来,我们开始定义搭建 ARMA 模型。

from statsmodels.tsa.arima_model import ARMA

arma = ARMA(train_data, order=(2, 1)).fit()  # 定义并训练模型

接下来我们输出测试结果,并将其与真实结果对比。

plt.plot(arma.forecast(steps=10)[0], '.-', label="predict")  # 输出后续 10 个预测结果
plt.plot(test_data.values, '.-', label="real")
plt.legend()

可以看出,预测结果还是能比较准确地反应出数据趋势。关于 ARMA 模型评估这里就不再赘述了,因为都是连续型数值预测,可以使用在回归分析中学到的相关方法进行评估。例如,计算 MSE 等指标。

最后,我们总结一下 ARMA 建模步骤如下:

获取序列 → 通过平稳性检验 → 通过纯随机性检验 → 估计 $p$ 和 $q$ 参数 → ARMA 建模 → 模型评估。

上面,我们通过 ARMA 对时间序列建模的过程中,首先就要使得序列满足「平稳」要求。那么,对于不平稳序列而言,例如一开始人口变化的 series1 序列,如果要分析该怎么办呢?

此时,如果能想办法使序列变得更加平稳,那么就可以应用 ARMA 进行解算了。而这里要学习一种使序列平稳的方法:差分。

差分运算

差分运算实际上是一种从序列中提取确定性信息的方法,也是一种非常基础的数学分析手段。下面,我们对差分计算的方法和公式进行回顾。

如果我们对两个序列相邻值(延迟 1 期)作减法运算,就可以记 $\nabla x_t$ 为 $x_t$ 的 $1$ 阶差分,公式如下:

$$ \nabla x_t = x_t - x_{t-1}$$

此时,如果对 $1$ 阶差分后的序列再进行一次 $1$ 阶差分运算,就可以记 $\nabla^2 x_t$ 为 $x_t$ 的 $2$ 阶差分,公式如下:

$$ \nabla^2 x_t = \nabla x_t - \nabla x_{t-1}$$

那么,依次类推,对 $p-1$ 阶差分后序列再进行一次 $1$ 阶差分运算,就可以记 $\nabla^p x_t$ 为 $x_t$ 的 $p$ 阶差分,公式如下:

$$ \nabla^p x_t = \nabla^{p-1} x_t - \nabla^{p-1} x_{t-1}$$

除此之外,如果两个序列值之间延迟 $k$ 期再做减法运算称为 $k$ 步差分运算,记 $\nabla_k x_t$ 为 $x_t$ 的 $k$ 阶步差分,公式如下:

$$ \nabla_k x_t = x_t - x_{t-k}$$

接下来,我们使用 Pandas 对 series1 序列进行 1 阶差分运算并绘图,同时对差分后的数据进行平稳性和纯随机性检验。

fig, axes = plt.subplots(ncols=3, nrows=1, figsize=(15, 3))

diff1 = series1.diff().dropna()  # 1 阶差分

axes[0].plot(diff1)  # 绘图
autocorrelation_plot(diff1, ax=axes[1])  # 平稳性检验
axes[2].plot(acorr_ljungbox(diff1)[1])  # 纯随机检验

尝试一下 1 阶 2 步差分:

fig, axes = plt.subplots(ncols=3, nrows=1, figsize=(15, 3))

diff1 = series1.diff(periods=2).dropna()  # 1 阶 2 步差分

axes[0].plot(diff1)  # 绘图
autocorrelation_plot(diff1, ax=axes[1])  # 平稳性检验
axes[2].plot(acorr_ljungbox(diff1)[1])  # 纯随机检验

看来调整差分的步长,并不能有效地改善稳定性。接下来,我们执行 2 阶差分:

fig, axes = plt.subplots(ncols=3, nrows=1, figsize=(15, 3))

diff2 = series1.diff().diff().dropna()  # 2 阶差分

axes[0].plot(diff2)  # 绘图
autocorrelation_plot(diff2, ax=axes[1])  # 平稳性检验
axes[2].plot(acorr_ljungbox(diff2)[1])  # 纯随机检验

如上所述,2 阶差分让数据平稳很多,但是纯随机性并没有通过检验。在这种情况下,我们可能更偏向于选择数据不那么随机的参数,也就是 $d=1$。

一般在差分时阶数不宜过大。原因在于差分其实是对信息提取加工的过程,每次差分都会带来信息损失,过度差分会导致有效信息损失而降低精度。一般情况下,线性变化通过 1 次差分即可平稳,非线性趋势 2,3 次差分也能变得平稳,一般差分次数不超过 2 次。

ARIMA 介绍及建模

上面我们提到过,ARIMA 模型适合于对非平稳序列进行建模分析。你可能观察到 ARIMA 比 ARMA 多了一个 I 字母,其实这个 I 就代表差分。与此同时,相比于 ARMA 模型中存在的 $p$, $q$ 参数,ARIMA 多了一个参数,那就是使非平稳序列成为平稳序列所做的差分阶数 $d$。所以,ARIMA 模型通常记作:$ARIMA(p, d, q)$。

接下来,我们使用差分后的数据确定 $p$, $q$ 参数,这一步和 ARMA 建模相似,这里我们使用 AIC 解算结果确定参数,当然也可以使用另外两种。

train_data = series1[:-40]  # 约 80% 训练
test_data = series1[-40:]

arma_order_select_ic(train_data.diff().dropna(), ic='aic')['aic_min_order']  # AIC

注意,我们会根据差分结果来确定 $p$, $q$ 参数。根据 AIC 的值,确定 $p=2$, $q=2$。

from statsmodels.tsa.arima_model import ARIMA

arima = ARIMA(train_data, order=(2, 1, 2)).fit()  # 定义并训练模型

同样,这里输出测试结果,并将其与真实结果对比。

plt.plot(arima.forecast(steps=40)[0], '.-', label="predict")  # 输出后续 40 个预测结果
plt.plot(test_data.values, '.-', label="real")
plt.legend()

由图可见预测结果还算理想,基本符合真实数据的趋势。最后我们总结时间序列分析建模大致流程如下:

小结

这篇文章中,我们了解到了一些时间序列分析建模相关的理论方法,并学习了使用 ARMA 和 ARIMA 对平稳和非平稳时间序列的建模思想,同时应用 Python 中著名的 statsmodels 库完成了建模分析过程。

实验梳理了时间序列建模的流程,但并没有对其中涉及到的统计学理论进行深入讨论和推导。原因在于,其所涉及到的理论知识内容量已大大超出预计。如果你对时间序列分析过程非常感兴趣。欢迎购买和阅读中国人民大学出版社出版的《应用时间序列分析》。中国人民大学作为统计学专业国内最著名的院校,也采用了该书用于本科生教学。

本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

系列文章

时间序列数据分析处理

1970-01-01 08:00:00

本文已更新,你可以访问 AI By Doing 以获得更好的阅读体验。
本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

介绍

时间序列是数据分析中经常会遇到的数据类型。了解并掌握相关特征及处理方法,能够帮助我们应对时间序列分析任务。这篇文章中,我们重点学习使用 Pandas 对时间序列的一系列预处理技巧。

知识点

时间序列分析是数据分析过程中,尤其是在金融数据分析过程中会经常遇到的。时间序列,就是以时间排序的一组随机变量,例如国家统计局每年或每月定期发布的 GDP 或 CPI 指数;一段时间内股票、基金、指数的数值变化等,都是时间序列。

下图呈现了 Google 公司仅 5 年的股价变化曲线。实际上,这条曲线由每天的收盘价绘制而成,这就是一个典型的时间序列数据集。

除了金融领域,时间序列在环境、物理等其他方面都广为存在。例如空气质量指数随时间变化,服务器日志数据随时间的产生。

同样,Pandas 提供了一系列标准的时间序列处理工具和算法,是使用 Python 对时间序列处理分析中必不可缺的法宝。接下来,我们将了解时间的产生,时间序列的生成、索引、切片、采样等内容。

时间生成

在学习时间序列前,首先需要了解时间。Python 中,我们通常会使用 datetimetime 模块来完成时间操作。例如,datetime.datetime.now 可以打印出当前时间。

import datetime

datetime.datetime.now()  # 获取当前时间

可以看到,datetime.datetime.now() 返回了一个时间对象,依次为:年、月、日、时、分、秒、毫秒。其中,毫秒的取值范围在 0 <= microsecond < 1000000datetime.datetime.now() 还有一个等效方法为 datetime.datetime.today()

datetime.datetime.today()  # 获取当前时间

你可以选择只返回时间的一部分。

datetime.datetime.now().year  # 返回当前年份

除了获取当前时间,你可以手动指定时间对象。

datetime.datetime(2017, 10, 1, 10, 59, 30)  # 指定任意时间

时间计算

datetime 时间对象是可以参与计算的。比如增加一定时间和年份,年份间隔计算等。

datetime.datetime(2018, 10, 1) - datetime.datetime(2017, 10, 1)  # 计算时间间隔

可以看到,上面返回了 datetime.timedelta 对象。timedelta 可以用于表示时间上的不同,但最多只保留 3 位,分别是:天、秒、毫秒。例如:

datetime.datetime.now() - datetime.datetime(2017, 10, 1)  # timedelta 表示间隔时间

所以,如果要在当前时间上增加一年。你需要将年份转换为天来计算。

datetime.datetime.now() + datetime.timedelta(365)  # 需将年份转换为天

时间格式转换

datetime 对象固然精确,但很多时间我们想将其转换为自定义的时间表示样式,例如:2018 年 10 月 1 日,或者 2018/10/1 等。此时,就需要将 datetime 对象转换为字符串对象了。

我们可以使用 datetime.date.strftime 来完成时间与字符串之间的转换。

datetime.datetime.now().strftime('%Y-%m-%d')  # 转换为自定义样式
datetime.datetime.now().strftime('%Y 年 %m 月 %d 日')  # 转换为自定义样式

你会发现我们使用到了占位符。那么,strftime 支持的占位符有:

除了 datetime 对象转换为字符串,你也可以将字符串时间转换为 datetime 对象。此项操作主要是考虑到 datetime 对象的灵活性,可以进行二次转换使用。例如,你只需要用占位符表示出原字符串的规则,就能自动将其转换为 datetime 时间对象,非常方便。

datetime.datetime.strptime('2018-10-1', '%Y-%m-%d')

时区

时区是地球上的区域使用同一个时间的定义。如果时间是以协调世界时(UTC)表示,那么我们就可以使用 UTC 偏移量来定义不同时区的时间。例如,因为北京位于东八区,那么北京时间就是 UTC +08:00,其代表比协调世界时快 8 小时的时区。

来源

当我们使用 datetime.now() 打印出来当前的时间时,是不包含时区信息的。这也被称之为 Naive datetime object,即「朴素时区」。我们可以通过 datetime.datetime.utcnow() 来获取 UTC 时间。

datetime.datetime.utcnow()  # 获取 UTC 时间

那么,此时如果要获取北京时间,就可以 UTC+8:00。

utc = datetime.datetime.utcnow()  # 获取 UTC 时间
tzutc_8 = datetime.timezone(datetime.timedelta(hours=8))  # + 8 小时
utc_8 = utc.astimezone(tzutc_8)  # 添加到时区中
print(utc_8)

上面介绍了时间,如果我们将时间和采集到的数据对应起来,就组成了时间序列。在一个时间序列中,主要存在两种类型:

接下来,我们学习使用 Pandas 处理时间序列的方法。

时间戳 Timestamp

时间戳,即代表一个时间时刻。Pandas 中,我们可以直接用 pandas.Timestamp 来创建时间戳。

import pandas as pd

pd.Timestamp("2018-10-1")

或者结合 datetime 模块创建时间戳。

pd.Timestamp(datetime.datetime.now())

除此之外,我们还可以使用 pandas.to_datetime 来创建时间戳,例如:

pd.to_datetime("1-10-2018")

上面默认创建了 2018-01-10 的时间戳,如果想要其变成 2018-10-1 呢?可以通过 dayfirst=True 参数进行修正。

pd.to_datetime('1-10-2018', dayfirst=True)

时间索引 DatetimeIndex

时间戳 Timestamp 并不是在时间序列中经常遇到的类型。如果我们拿到一个由时间排序的数据表,那么更重要的数据类型是 DatetimeIndex。顾名思义,时间索引就是由一系列时间戳组成,不过在 Pandas 中的数据类型为 DatetimeIndex。

我们同样可以使用 pd.to_datetime() 来创建时间索引。与上方创建时间戳不同的地方在于,你只需要输入包含多个时刻的列表即可。

pd.to_datetime(["2018-10-1", "2018-10-2", "2018-10-3"])  # 生成时间索引

当然,Pandas 的 Seris 和 DataFrame 也可以直接通过 to_datetime 转换。

s = pd.Series(["2018-10-1", "2018-10-2", "2018-10-3"])
pd.to_datetime(s)  # 将 Seris 中字符串转换为时间

这里值得注意的是,原 Series 中的时间是字符串,经过 pd.to_datetime() 转换之后变成了 datetime64 时间。但并不是严格意义上的时间索引,而时间索引是需要将其放到 Series 或 DataFrame 的索引位置才行。例如:

pd.Series(index=pd.to_datetime(s)).index  # 当时间位于索引时,就是 DatetimeIndex

现在看到的就是 DatetimeIndex 类型了。

事实上,生成 DatetimeIndex 另一个更常用的方法是 pandas.date_range。我们可以通过指定规则,让 pandas.date_range 生成有序的 DatetimeIndex。

date_range 方法带有的默认参数如下:

pandas.date_range(start=None, end=None, periods=None, freq=D, tz=None, normalize=False,
name=None, closed=None, **kwargs)

其中:

特别地,freq= 频度参数非常关键,可以设置的周期有:

pd.date_range('2018-10-1', '2018-10-2', freq='H')  # 按小时间隔生成时间索引
# 从 2018-10-1 开始,以天为间隔,向后推 10 次
pd.date_range('2018-10-1', periods=10, freq='D')
# 从 2018-10-1 开始,以 1H20min 为间隔,向后推 10 次
pd.date_range('2018-10-1', periods=10, freq='1H20min')

通过 date_range,我们可以生成任何以一定规律变化的时间索引。

前面,我们了解了 timedelta 可以用于时间运算。而在 DatetimeIndex 中,我们可以通过 offset 对象对时间戳索引进行更加灵活的变化。例如:

  1. 可以让时间索引增加或减少一定时间段。
  2. 可以让时间索引乘以一个整数。
  3. 可以让时间索引向前或向后移动到下一个或上一个特定的偏移日期。
time_index = pd.date_range('2018-10-1', periods=10, freq='1D1H')
time_index

使用 offset 对象让 time_index 依次增加 1 个月 + 2 天 + 3 小时。

from pandas import offsets

time_index + offsets.DateOffset(months=1, days=2, hours=3)

或者,使用 offset 对象让 time_index 向后偏移 2 周。

time_index + 2 * offsets.Week()

除了示例中提到的 offsets.DateOffsetoffsets.Week,常用的 offsets 对象还有:

DateOffset 名称 描述
DateOffset 自定义,默认一周
BDay 工作日
CDay 自定义工作日
MonthEnd 月末
QuarterEnd 季度结束
YearEnd 年末

更详细的 DateOffset Objects 表单可以阅读官方文档。

你可以发现,DateOffset 对象非常灵活,只要稍加组合,就能实现任意想要的时间偏移结果。实际上,DateOffset 对象不仅对 DatetimeIndex 有效,对于时间戳 Timestamp 也是可以操作的。这一点应该很好理解,比较 DatetimeIndex 相当于 Timestamp 的延展。

时间间隔 Periods

上面,我们对 Timestamp 时间戳和 DatetimeIndex 时间戳索引都有了较为充分的认识。除此之外 Pandas 中还存在 Period 时间间隔和 PeriodIndex 时间间隔索引对象。什么是 Periods?比如:天,月,季,年。

# 1 年跨度
pd.Period('2018')
# 1 个月跨度
pd.Period('2018-1')
# 1 天跨度
pd.Period('2018-1-1')

你会看到,每一个时间间隔后面都有一个字母,其实这就是时间间隔所对应的 freq= 频度。你可能在想,Periods 看起来和上面的 Timestamp 没有区别呢?那我们重新生成一个 Timestamp 看一看。

pd.Timestamp('2018-1-1')

此时,你应该能发现 Timestamp 和 Periods 的区别了吧。Periods 代表是 2018-01-01 这一天,而 Timestamp 仅代表 2018-01-01 00:00:00 这一时刻。

时间间隔索引 PeriodsIndex

与「时间戳 → 时间索引」相仿,时间间隔也对应着时间间隔索引 PeriodsIndex。而我们可以通过 pandas.period_range() 方法来生成索引序列。

p = pd.period_range('2018', '2019', freq='M')  # 生成 2018-1 到 2019-1 序列,按月分布
p

生成索引序列时,就必须指定 freq= 频度,这和上文生成 DatetimeIndex 时相似。同时,我们可以使用 asfreq 来重新设定频度。

p.asfreq(freq='D', how='S')  # 频度从 M → D

这里的 how=S 代表每月的第一天(Start),也可以设为 how=E(End)。

时序数据选择、切片、偏移

你或许在纳闷,Pandas 为什么要使用时间戳,时间索引,时间间隔等呢?实际上,这些基础数据类型都是方便我们对时间序列数据进行操作而出现的。有了 Timestamp 和 Periods,我们就可以完成对数据的选择、切片,以及进行偏移、重新采样等更为复杂的组合变换了。

下面,我们尝试生成包含时间索引的 Series 示例,并对数据进行选择。

import numpy as np

timeindex = pd.date_range('2018-1-1', periods=20, freq='M')
s = pd.Series(np.random.randn(len(timeindex)), index=timeindex)
s

选择 2018 年的所有数据。

s['2018']

选择 2018 年 7 月到 2019 年 3 月之间的所有数据。

s['2018-07': '2019-03']

除了查询和切片,我们还可以用到 Shifting 方法,将时间索引进行整体偏移。

s.shift(3)  # 时间索引以默认 Freq: M 向后偏移 3 个单位

你可以把上面的过程理解为时间索引向后(未来)位移 3 个单位,缺失数据 Pandas 会用 NaN 自动填充。除此之外,还可以指定 freq= 参数,确定偏移的单位大小。

s.shift(-3, freq='D')  # 时间索引以 Freq: D 向前偏移 3 个单位

时序数据重采样

重采样是时序数据处理中经常会使用到的操作。Resample 的目的是提升或降低一个时间索引序列的频率。例如:当时间序列数据量非常大时,我们可以通过低频率采样的方法得到规模较小到时间覆盖依然较为全面的新数据集。另外,对于多个不同频率的数据集需要数据对齐时,重采样可以十分重要的手段。

同样,初始化一个示例 Series。

dateindex = pd.period_range('2018-10-1', periods=20, freq='D')
s = pd.Series(np.random.randn(len(dateindex)), index=dateindex)
s

下面,我们对 Series 按照 2 天进行降采样,并对 2 天对应的数据求和作为新数据。注意,这里执行 pandas.DataFrame.resample 降频采样时需要选择一个计算方法(求和、求平均、最大值、最小值等)。

s.resample('2D').sum()  # 降采样,并将删去的数据依次合并到保留数据中

那么,如果降采样时并不想对数据索引对应的数据进行操作,而仅仅保留原时间戳对应的数据。可以使用 .asfreq() 方法。

s.resample('2D').asfreq()  # 降采样,直接舍去数据

还可以这样,按照 2 天进行降采样,并将对应 2 天数据的原值、最大值、最小值等列出。而这个方法主要用于股票分析中,其中,open、high、low、close 也就对应着股票交易过程中的开盘、最高、最低以及收盘价。

s.resample('2D').ohlc()

除了降采样,升采样同样可行。不过,我们要考虑到升采样时,新增加到时间索引中的时间戳对应的数值怎么办?是沿用临近数据,还是其他方法填充?

例如,我们可以让时间频率从天提升到小时,并使用相同的数据对新增加行填充。

s.resample('H').ffill()  # 升采样,使用相同的数据对新增加行填充

通过设置 limit= 控制填充的最大数目,而不填充的数值将自动标记为 NaN。

s.resample('H').ffill(limit=3)  # 升采样,最多填充临近 3 行

时序数据时区处理

上面,我们了解了 datetime 中的时区处理,而在 Pandas 可能也会遇到时区处理的情况。与 datetime 相似,Pandas 中的时间序列依旧是朴素时区。

naive_time = pd.date_range('1/10/2018 9:00', periods=10, freq='D')
naive_time

所以,当我们需要转换为本地时间,就需要先向时间序列添加 UTC 时区信息,再转换为本地时间。

utc_time = naive_time.tz_localize('UTC')
utc_time

接下来,我们使用 tz_localize 方法实现时区转换。

utc_time.tz_convert('Asia/Shanghai')

注意,一般我们定义 UTC+8:00 时会使用 Asia/ChongqingAsia/Shanghai,没有 Asia/Beijing。具体是因为历史遗留原因。

实际上,你可以在生成时间戳或者时间索引时,指定 tz= 参数来定义 UTC 时区。

pd.date_range('1/10/2018 9:00', periods=10, freq='D', tz='Asia/Shanghai')

但需注意,由于 UTC 时间的基准发生了变化(tz= 会将指定时间作为 UTC),所以上一行结果与前面通过 tz_convert 的转换结果有所不同。

小结

时间序列,无非就是一系列按照时间点采样的数据,形式简单。这篇文章中,我们了解了时间序列数据在 Pandas 中的基本要素,学习了 Pandas 处理时间序列数据的方法和技巧。只有掌握了这些方法,才能更好地玩转时间序列数据,从中挖掘出有价值的信息。

本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

系列文章

Apriori 关联规则学习方法

1970-01-01 08:00:00

本文已更新,你可以访问 AI By Doing 以获得更好的阅读体验。
本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

介绍

关联规则学习是一种在大型数据库中发现变量之间的有趣关系的方法。它的目的是利用一些有趣的量度来识别数据库中发现的强规则。关联规则被广泛应用于购物篮分析、网络用法挖掘、入侵检测、连续生产及生物信息学。

知识点

关联规则

关联规则(英语:Association Rule)是数据挖掘中的一种发现变量之间有趣关系的手段和方法,它主要的用途是发现数据之间的关联性和强规则。简单来讲,当某类别数据或某个模式在数据集中出行的频率增大时,自然大家都会认为其肯能会更为常见或重要。那么,如果发现两个或多个类别数据同时出现,自然就可以认为其相互之间存在联系或规则。

关联规则最典型的例子就是用于购物篮分析。一些商家发现当顾客购买商品 A 的时候,很容易同时购买商品 B,于是就可以因此调整商品的摆放位置或变换定价策略。这里有一个关于 啤酒和尿布 的故事:大意是一家大型超市的销售团队发现,每周五下午购买尿布的年轻美国男性也有购买啤酒的倾向,于是每周五都会将一个尿布货架放置在啤酒货架旁边,结果是二者的销量都有所上升。

上面这个例子几乎每一篇讲「关联分析」的文章都会用到。那么,销售团队是怎样发现这个购买规律的呢?其中就包含了「关联分析」的使用。

在正式介绍关联分析涉及到的相关算法前,我们先搞清楚关联分析过程中常接触到的几个概念。

频繁项集

首先,我们了解项集的概念。假设有一个集合 $I$ 如下:

$$ I=\{i_1, i_2, ..., i_m\}$$

那么,我们称其中的 $i_1, i_2, ..., i_m$ 为集合的项 Item,而 $I$ 自然被称之为项 Item 的集合。

此时,若集合 $S$ 满足 $S={i|i∈I}$,则 $S$ 被称之为 $I$ 的项集 Itemset。其中,包含 $k$ 个项的项集称为 $k$-项集。

举个例子,当我们在超市购物时,如果我们的购物车中包含有许多品类的货品,购物车就可以被看作是集合 $I$。此时,如果购物车中有啤酒和尿布,那么啤酒和尿布就可以组成集合 $I$ 的项集 $S$。如果我们把整个超市一天的全部账单看作是集合 $I$,那么单个购物车中出现的商品集合就可以被看作是项集 $S$。

而频繁项集则表明一个项集出现很「频繁」。从直观的角度理解,你应该会认为对频繁项集的挖掘是有意义的,就例如上面的「啤酒和尿布」,因为它们出现的频率很高,最终我们就可以通过调整销售规则来获益。

但是,如何去衡量频繁项集中的「频繁」呢?达到怎样的程度才能被称之为「频繁」呢?

在关联规则中,我们通常会使用 $X → Y$ 这样的表达式。其中,$X$ 和 $Y$ 是不相交项集,即 $X\cap Y=∅$。而关联规则的强度可以用它的支持度(support)和置信度(confidence)度量。

支持度

支持度 support 用来衡量事件发生的频率。$support(X \to Y)$ 则表示项集 ${X,Y}$ 在总项集 $I$ 里出现的频率,记作:

$$ support(X \to Y) = P(X∪Y) = \frac{num(XUY)}{num(I)}$$

如果依旧拿超市购物举例。上式中,$support(X \to Y)$ 表示同时包含商品 $X$ 和 $Y$ 的购物车占总购物车数量的百分比。例如,$X$ 代表啤酒,$Y$ 代表尿布。那么,我们统计同时包含啤酒和尿布的购物车数量,将结果除以购物车总数,得到该项集的支持度。如果项集的支持度超过预先设定的最小支持阈值,则认为该项集可能是有用的,也就被称之为「频繁项集」。

置信度

置信度 confidence 用来表示在 $X$ 出现情况下,出现 $Y$ 的可能性,其公式为:

$$ confidence(X \to Y) = P(Y|X)= \frac{support(X → Y)}{support(X)}$$

那么,$confidence(X \to Y)$ 表示在 $X$ 出现的情况下,$Y$ 出现的概率。即购物车中出现啤酒时,尿布出现的概率。置信度是同时包含 $X$ 和 $Y$ 的购物车的百分比除以只包含 $X$ 的购物车百分比。

简而言之,支持度表示 $X$ 和 $Y$ 同时出现的概率,而置信度表示在 $X$ 出现的情况下,$Y$ 也出现的概率,即条件概率 $P(Y|X)$。

有了支持度和置信度之后,频繁项集就可以被扩展为关联规则了。也就是说如果我们有一条规则如下:

即表示,在超市的销售记录中。有 $10\%$ 的顾客的购物车中出现了啤酒和尿布的组合。同时,购买啤酒的顾客有 $60\%$ 的几率会购买尿布。关联规则的左侧是确定项,称作先导。右侧是结果项,称作后继。

示例购物数据

为了帮助大家熟悉关联规则中出现的相关概念,接下来我们设定一组示例数据,并尝试计算几条规则的支持度和置信度。

购物车 商品 商品 商品 商品 商品
购物车 A 牛奶 咖啡 - - -
购物车 B 可乐 咖啡 牛奶 啤酒 -
购物车 C 啤酒 尿布 牛奶 - -
购物车 D 咖啡 牛奶 香蕉 雪碧 啤酒
购物车 E 啤酒 尿布 咖啡 - -

上面的表格中,出现了 5 个购物车的记录。接下来,我们指定规则为:${牛奶,咖啡} \rightarrow {啤酒}$

首先,项集 ${牛奶,咖啡,啤酒}$ 的支持度计数为 2,总计数为 5,所以:

$$ support(\{牛奶,咖啡\} \rightarrow \{啤酒\}) = \frac{2}{5} = 0.4$$

由于项集 ${牛奶,咖啡}$ 的支持度计数是 3,因此该规则的置信度为:

$$ confidence(\{牛奶,咖啡\} \rightarrow \{啤酒\}) = \frac{2}{3} = 0.67$$

也就是说,有 $40\%$ 的顾客同时买了牛奶、咖啡和啤酒。而买牛奶和咖啡的顾客中,买啤酒的概率为 $67\%$。

对于一条规则的支持度和置信度而言。支持度的意义在于帮助我们确定这条规则是否有必要被关注。也就是说,我们通常会直接忽略掉支持度低的规则。于此同时,置信度的意义在于帮助我们确定规则的可靠性,是否有足够的数据表明该条规则是大概率存在的。

关联规则任务

上面的内容中,我们学习了关联规则的表示方法,以及单条规则所涉及到的 3 个与之相关的概念。事实上,关联规则挖掘就是从众多的项集中去发现支持度和置信度大于我们所设定阈值的规则。

上面这句话听起来简单,但可以想象的是,实现起来一点都不容易。因为,真实情况下面对的数据集可远复杂于我们上面呈现的示例数据。那么,不同项集的组合,规则的组合数量会变得非常庞大。更具体地说,从包含 $n$ 个项的数据集中提取出的可能规则总数为(排列组合问题):

$$ N = 3^n - 2^{n+1}+1$$

例如,对于上面包含 5 个不同商品的数据集而言,可以组合 $3^5 - 2^{5+1}+1 = 180$ 条不同的规则。其项集 ${牛奶,咖啡,啤酒}$ 就可以组合为 $3^3 - 2^{3+1}+1 = 12$ 条不同的规则:

$$ \{牛奶\} → \{咖啡,啤酒\}$$
$$ \{咖啡\} → \{牛奶,啤酒\}$$
$$ \{啤酒\} → \{牛奶,咖啡\}$$
$$ \{咖啡,啤酒\} → \{牛奶\}$$
$$ \{牛奶,啤酒\} → \{咖啡\}$$
$$ \{牛奶,咖啡\} → \{啤酒\}$$
$$ \{牛奶\} → \{咖啡\}$$
$$ \{牛奶\} → \{啤酒\}$$
$$ \{咖啡\} → \{牛奶\}$$
$$ \{咖啡\} → \{啤酒\}$$
$$ \{啤酒\} → \{牛奶\}$$
$$ \{啤酒\} → \{咖啡\}$$

虽然项集 ${牛奶,咖啡,啤酒}$ 可以产生上面的 12 条规则,但后面的 6 条规则也可以被看作是其子集 ${牛奶,咖啡}$ ,${牛奶,啤酒}$ ,${咖啡,啤酒}$ 产生。对于这 12 条规则而言,前面 6 条支持度相同,后面 6 条拥有相同项集的两两之间的支持度相同。由于支持度的计算方法只有项集在数据集中出现的频率有关。那么,如果项集 ${牛奶,咖啡,啤酒}$ 是非频繁项集,就可以直接剪掉前 6 条规则而不计算其置信度,从而节省计算开销。

也就是说,关联规则挖掘就被分解成 2 个子任务:

Apriori 算法

我们通常使用「格结构」来枚举数据集可能存在的项集。如下方格结构图所示,牛奶,咖啡,啤酒组成的数据集可能产生的项集如下:

如上所说,由牛奶,咖啡,啤酒组成的数据集中,可能产生 7 个候选项集(包含单个项组成的项集)。推广到一般情况,则包含 $n$ 个项的数据集,可能产生 $2^n - 1$ 个候选项集。

那么,当我们想从候选项集中找出频繁项集时。笨办法是通过计算每个候选项集的支持度计数,但随着 $n$ 增加,计算开销可想而知。于是,就有了使用 Apriori 先验原理来减少候选项集数量的方法。

Apriori 算法最先于 1994 年在论文 Fast algorithms for mining association rules in large databases 中首次出现,它也是关联规则挖掘中最常用的方法。

Apriori 先验原理说起来非常简单,它主要是告诉我们 2 条定理,即:

下图呈现了一个比上面例子更大的候选项集格结构图。

基于先验原理,如果我们发现 ${a, b}$ 是非频繁项集,则整个包含 ${a, b}$ 的超集子图都可以被剪枝。于是使用 Apriori 算法产生频繁项集算法过程如下:

首先,令 $F_k$ 表示频繁 $k$ 项集的集合,$C_k$ 表示候选 $k$ 项集的集合:

产生完频繁项集,就可以从给定的频繁项集中提取关联规则。一般情况下,每个频繁 $k$ 项集能够产生 $2^k−2$ 个关联规则。产生出来的规则需要基于置信度剪枝,以满足提前设定的置信度阈值。

这里,我们又要给出一条关于置信度的定理。假设项集 $Y$ 可以被划分为两个非空子集 $X$ 和 $Y-X$,那么:

Apriori 算法使用逐层方法来产生关联规则,其中每层对应于规则后件中的项数。初始时,提取规则后半部分只含一个项的所有高置信度规则;然后,使用这些规则来产生新的候选规则。

关联规则实战

虽然关联规则说到底就是找频繁项集和计算置信度,听起来很简单,但是想要计算变得高效还是比较麻烦的过程。就拿 Apriori 算法举例,Python 完整的实现大约需要 200+ 行代码。所以,这篇文章中就不再动手实现。

接下来,我们使用关联规则挖掘来对一个示例销售数据集进行实战练习。这里,我们使用到了 mlxtend 机器学习算法库提供的 Apriori 算法 API。该 API 完全按照 Fast algorithms for mining association rules in large databases 论文进行实现。

首先,这里给出一个示例购物数据集,二维列表中的每一个列表代表单个购物车中商品名称。

dataset = [['牛奶', '洋葱', '牛肉', '芸豆', '鸡蛋', '酸奶'],
           ['玉米', '洋葱', '洋葱', '芸豆', '豆腐', '鸡蛋'],
           ['牛奶', '香蕉', '玉米', '芸豆', '酸奶'],
           ['芸豆', '玉米', '香蕉', '牛奶', '鸡蛋'],
           ['香蕉', '牛奶', '鸡蛋', '酸奶'],
           ['牛奶', '苹果', '芸豆', '鸡蛋']]
dataset

我们尝试通过 Apriori 算法生成频繁项集。这里先通过 mlxtend.preprocessing.TransactionEncoder 将列表数据转换为 Apriori 算法 API 可用的格式。mlxtend.preprocessing.TransactionEncoder 类似于独热编码,可以提取数据集中的不重复项,并将每个购物车数据转换为等长度的布尔值表示。mlxtend 提供的 API 使用方法和 scikit-learn 相似。

import pandas as pd
from mlxtend.preprocessing import TransactionEncoder

te = TransactionEncoder()  # 定义模型
te_ary = te.fit_transform(dataset)  # 转换数据集
te_ary

最终,购物车数据集表示如下:

df = pd.DataFrame(te_ary, columns=te.columns_)  # 将数组处理为 DataFrame
df

接下来,我们就可以使用 mlxtend.frequent_patterns.apriori API 来生成频繁项集。根据前面的学习内容可知,生成频繁项集时需要指定最小支持度阈值,满足阈值的候选集将被定为频繁项集。

from mlxtend.frequent_patterns import apriori

apriori(df, min_support=0.5)  # 最小支持度为 0.5

默认情况下,apriori 返回项的是列索引,这是为了方便生成关联规则时使用。为了更好的可读性,这里可以设置 use_colnames=True 参数将索引转换为相应的项目名称:

apriori(df, min_support=0.5, use_colnames=True)

此时,如果你只想查看至少包含两个项的频繁项集,就可以通过 Pandas 选择完成:

frequent_itemsets = apriori(df, min_support=0.5, use_colnames=True)
frequent_itemsets[frequent_itemsets.itemsets.apply(lambda x: len(x)) >= 2]  # 选择长度 >=2 的频繁项集

有了频繁项集,接下来我们就可以使用 mlxtend.frequent_patterns.association_rules 来生成关联规则了。此时,需要指定最小置信度阈值。

from mlxtend.frequent_patterns import association_rules

association_rules(frequent_itemsets, metric="confidence", min_threshold=0.6) # 置信度阈值为 0.6
association_rules(frequent_itemsets, metric="confidence", min_threshold=0.8) # 置信度阈值为 0.8

mlxtend 使用了 DataFrame 而不是 → 来展示关联规则。其中:

前面的内容中,我们并没有介绍提升度、杠杆率和确信度的概念。这其实是关联规则中用于评估的补充规则,并不必须。

提升度的计算公式如下:

$$ {\displaystyle \mathrm {lift} (X \to Y)={\frac {\mathrm {support} (X \cap Y)}{\mathrm {support} (X)\times \mathrm {support} (Y)}}}$$

其中,当先导项与后继项独立分布时,值为 1,提升度越大,表示先导项与后继项的关联性越强。

杠杆率的计算公式如下:

$$ leverage(X \to Y) = {support(X \cap Y)} - {support(X) * support(Y)}$$

其中,当先导项与后继项独立分布时,值为 0。杠杆率越大,表示先导项与后继项的关联性越强。

确信度的计算公式如下:

$$ {\mathrm {conviction}}(X\to Y)={\frac {1-{\mathrm {support}}(Y)}{1-{\mathrm {confidence}}(X\to Y)}}$$

确信度值越大,则先导项与后继项的关联性越强。

小结

这篇文章中,我们介绍了关联规则学习方法,重点理解了其中涉及到的几个概念,最后使用 mlxtend 机器学习库对 Apriori 算法进行了实战练习。实验并没有从原理上对算法进行纯 Python 实现,因为涉及到的算法原理实在有些麻烦。当然如果你有兴趣的话,推荐几个 Apriori 算法开源项目查阅源代码学习,它们分别是:

本篇文章需 特别授权许可,内容版权归作者所有,未经授权,禁止转载。

系列文章