用 GitHub API 构建一个 Python 爬虫来获取活动流
从头做起
在我之前的文章用一个简单的 Python 爬虫下载课程资料中,我介绍了一个简单的 Python 爬虫来下载文件。要构建这样一个爬虫,我们必须自己找到文件的模式。但是如果我们想从著名的网站收集数据,比如 Twitter 和 GitHub,事情可能会简单一些。因为通常,这些网站都提供了 API,我们可以直接获取我们想要的数据。在这篇文章中,我将构建一个 Python 爬虫来用 GitHub API 获取活动流。
1 个目标
活动流显示用户最近的活动,例如下面显示的是我在 GitHub 中的活动。
activity stream in GitHub
我想得到如下这些活动
ShusenTang starred lyprince/sdtw_pytorch
chizhu starred markus-eberts/spert
Hexagram-King starred BrambleXu/knowledge-graph-learning
Yevgnen starred BrambleXu/knowledge-graph-learning
......
2 用 GitHub API 获取数据
2.1 GitHub API
首先,我们来看看 GitHub API 文档。如果您没有启用 双因素认证 ,您可以运行下面的命令来测试 API。输入密码后,您应该会看到响应。
$ curl -u '<usename>' [https://api.github.com](https://api.github.com)
Enter host password for user '<usename>': <password>{
"current_user_url": "[https://api.github.com/user](https://api.github.com/user)",
"current_user_authorizations_html_url": "[https://github.com/settings/connections/applications{/client_id](https://github.com/settings/connections/applications{/client_id)}",
"authorizations_url": "[https://api.github.com/authorizations](https://api.github.com/authorizations)",
......
但是如果您已经启用了 双因素认证 ,我们需要使用 个人访问令牌 来进行认证。按照帮助页面创建个人令牌。至于访问权限/范围,因为我们只想获得活动流,选择通知就足够了。
如果一切顺利,我们现在应该有一个令牌。按照认证指令使用以下命令测试令牌。
$ curl -H "Authorization: token <TOKEN>" [https://api.github.com](https://api.github.com)
{
"current_user_url": "[https://api.github.com/user](https://api.github.com/user)",
"current_user_authorizations_html_url": "[https://github.com/settings/connections/applications{/client_id](https://github.com/settings/connections/applications{/client_id)}",
"authorizations_url": "[https://api.github.com/authorizations](https://api.github.com/authorizations)",
.......
2.2 获取激活流
在其他认证方式中,我们可以使用下面的命令直接获取用户数据。
$ curl -u <usename>:<TOKEN> [https://api.github.com/user](https://api.github.com/user){
"login": "xxxx",
"id": xxxxx,
"node_id": "xxxx",
"avatar_url": "[x](https://avatars1.gi)xxx",
......
接下来,我们需要浏览 API 文档来找到与活动相关的 API。在右侧的切换列表中,“活动”下有“事件”、“提要”、“通知”。但是我们需要确定哪个适合我们的需求。
浏览文档后,我们可以知道“事件”下的“列出用户收到的的事件是我们需要的。
我们可以用这个 API 命令测试curl
命令。
$ curl -u <usename>:<TOKEN> https://api.github.com/users/<usename>/received_events
这会返回很多消息,所以最好将响应保存为 JSON 文件。
$ curl -u <usename>:<TOKEN> https://api.github.com/users/<usename>/received_events > github_stream.json
3 分析 JSON 文件
我们可以查看 JSON 数据来熟悉格式。
似乎有不同种类的事件类型。我们可以编写一个简单的脚本来获取各种事件类型。
import jsonwith open('github_stream.json', 'r') as f:
data_string = f.read() # read object as string
data = json.loads(data_string) # convert JSON (str, bytes or bytearray) to a Python object# Get all event types
events = set()
for event in data:
events.add(event['type'])
print(events)# output
{'IssueCommentEvent', 'ForkEvent', 'PushEvent', 'PullRequestEvent', 'WatchEvent', 'IssuesEvent'}
通过比较我们在 GitHub 主页上看到的活动流,我们可以看到只存在三种事件类型,WatchEvent(星号)、ForkEvent(分叉)、PushEvent(推送)。
所以我们只需要从这三种事件类型中获取活动。下面是剧本。
# github-api-json-parse.pyimport jsonwith open('github_stream.json', 'r') as f:
data_string = f.read() # read object as string
data = json.loads(data_string) event_actions = {'WatchEvent': 'starred', 'PushEvent': 'pushed to'}for event in data:
if event['type'] in event_actions:
name = event['actor']['display_login']
action = event_actions[event['type']]
repo = event['repo']['name']
print('{name} {action} {repo}'.format(name=name, action=action, repo=repo))if event['type'] == 'ForkEvent':
name = event['actor']['display_login']
repo = event['repo']['name']
forked_repo = event['payload']['forkee']['full_name']
print('{name} forked {forked_repo} from {repo}'.format(name=name, forked_repo=forked_repo,repo=repo))
运行脚本,我们可以得到下面的输出。
ShusenTang starred lyprince/sdtw_pytorch
chizhu starred markus-eberts/spert
Hexagram-King starred BrambleXu/knowledge-graph-learning
icoxfog417 pushed to arXivTimes/arXivTimes
icoxfog417 pushed to arXivTimes/arXivTimes
Yevgnen starred BrambleXu/knowledge-graph-learning
......
4 实时获取激活流
直到现在,我们都可以通过 API 获取 JSON 数据文件,解析 JSON 文件得到活动流。接下来,我们将消除 JSON 文件,直接获得活动流。
参考
- 用一个简单的 Python 爬虫下载课程资料
- https://developer.github.com/v3/
- https://developer . github . com/v3/auth/# via-oauth-and-personal-access-tokens
- https://developer . github . com/v3/activity/events/# list-events-a-user-has-received
构建一个 React + Flask 应用程序,用 Python 图形推荐小说
Photo by Kourosh Qaffari on Unsplash
项目总结:
- 建立一个用户和他们阅读的书籍的图表数据库
- 开发一个 Flask 应用程序,根据用户提交的收藏夹向用户提供稀有、有趣的书籍
- 实现一个与 Flask +我们的 Graph 集成的 React 应用程序,向用户展示他们下一本最喜欢的书
有很多学习数据科学技术的好资源:MOOCS、博客、教程和训练营都是学习的途径。就我个人而言,我通过从事我觉得有趣和吸引人的项目学习得最好。没有什么比解决一个有趣的问题更能激励我把自己的理解水平推向新的高度了。我如何确定我可以探索的新途径?一般靠看别人的作品!
这个项目的想法最初是通过阅读维基·博伊基斯在伟大的博客上发表的关于网飞推荐引擎性质变化的帖子而实现的。简而言之(抱歉,维基),通过矩阵分解或深度学习来最小化用户评级偏好的 RMSE 的日子已经一去不复返了。推荐在一定程度上仍然是一种艺术形式,上下文、细微差别和设计都在为用户提供良好体验方面发挥着重要作用。此外,推荐的商业价值不仅仅是为用户提供最好的内容。在网飞的情况下,可能是为了给用户提供最好的网飞内容。
我很高兴参加今年在哥本哈根举行的 RecSys 2019 大会。最佳论文的获胜者是而不是最先进的神经网络方法。相反,它是对以前的开源推荐系统、公共数据集以及它们在面对面比赛中的实际表现的仔细检查。结果,你可能会问?简单模型(基于图形、用户/项目最近邻居、热门项目)几乎总是优于最佳深度神经网络。
可以肯定地说,鉴于这些启示,我一直在重新思考推荐系统。作为一个用户,我想从 YouTube 的推荐引擎中得到什么?像 Ecosia /DuckDuckGo 这样的搜索引擎呢?在前一个例子中,我可能想看在那个时刻娱乐/通知我的视频。在后一个例子中,我希望快速找到最相关的信息,而不用担心利益冲突。在这两种情况下,返回的项目可能不是最受欢迎的,最高评级的,或有争议的。广告商有时发现病毒式传播是有利可图的,但我可能不是一个粉丝。
那么我真正想要的是什么?我怎样才能建立一个系统来优化这个难以实现的、无利可图的目标呢?有一天,当我步行去图书馆时,我的脑海中出现了一个具有里程碑意义的清晰时刻:当我找到一个我从未考虑过或听说过的新作者或新书时,我绝对喜欢。当我翻阅书页时,那个时刻的纯粹发现和新奇感可以激发几个星期的快乐。
花在阅读一本书上的时间是一种投资,因此尝试任何新事物(探索与利用)可能是一个困难的命题。一个仅仅推荐随机项目的系统可能会提供意想不到的结果,但它们可能不是好的结果。我想鱼和熊掌兼得。我想找到我以前从未考虑过的好书。
这个想法很简单:
- 和我阅读和喜欢相同书籍的人可能和我有大致相似的品味
- 这些邻居可能读过很多书,其中一些我没有读过
- 这些未读的书中,有些可能是如此晦涩/冷门,以至于我不太可能自己去发现它们
以上陈述都不会让你感到惊讶,尤其是前两点。当然,最后一点对这项工作来说是最基本的。如果我建立一个图书、作者和读者的图形数据库,然后沿着图形的节点寻找这些隐藏的图书瑰宝,会怎么样?
GoodReads 网站已经发布了一个包含 10,000 本书、50,000+用户和近 600 万个书籍评级(1-5 星)的数据集。我肯定能在这个宝藏中找到一些有趣的东西!我从来没有建立过真正的图形,所以我认为这将是一个伟大的尝试。节点和边,能有多难?这个图的节点是Users
、Books
和Authors:
边缘是与每个(User, Book, Author, rating)
元组相关联的评级:
我首先创建了 4 个类来解释我的图形组件。每个节点还会有一个与之关联的列表:Users
会有一个Books
的书架,Authors
会有他们的参考书目,Books
会有他们的受众。Graph
将把所有这些对象连接成一个可以被遍历的内聚单元。
上面的代码块很大,里面有很多方法,这里就不探讨了。我们将关注一个可以直接与一个简单的、面向消费者的 React 应用程序 API 一起使用的 API: _book2book
。所以让我们说,我们现在可以绕着我们的图走,一切都编译好了。我们如何才能找到有意义的Books
呈现给User
?在我们看来,让我们假设一下:
- 作为一个
User
,你喜欢一个Author
的Book
(例如,五星评级) - 这个
Author
粉丝很多,她也写过类似的Books
你可能也会喜欢。这些不是小说,你可能已经知道了。 - 这个
Author
有很多粉丝,有些可能和你差不多 - 这些
Users
中的一个被随机选中,将会有一个收藏Books
的架子 - 从这个货架上选择最不受欢迎的五星级
Book
- 要么将此提交给
User
,要么返回步骤 1,继续浏览图表
Simplistic example of our graph
现在我们只需要将这个Book
返回给我们的User
作为一个可能的伟大发现!不过,我们遗漏了这个项目的关键要素。我们需要应用程序来提供这些有趣的Book
。为了熟悉起见,让我们从仍然基于 Python 的部分开始:我们需要构建处理 API 请求的应用程序后端,因此,当然,我们将使用 Flask 。Flask 允许我们用 Python 制作一个小型的 web 服务器,而且非常容易使用。我发现两个教程非常有用,它们很好地链接在一起:一个 Flask 教程和一个调用前面提到的 Flask 应用的 React 应用。
所以我们首先创建一个新目录,我们称之为api/
。在这个文件夹中,我们将有两个 python 文件。__init__.py
将建立我们的烧瓶应用程序的基础,导入必要的变量等。:
然后是一个app.py
(或者随便你怎么称呼):
app.py Example
在app.py
里面,我们正在做一些重要的事情。首先,我们调用 python 代码来构建我们的图形数据库,以查找新书。它还设置了我们后端的Blueprint
,所以当我们对这个服务进行 API 调用时,我们指向了正确的方向。让我们从最基本的层面解开这些东西:
- 我们的应用程序名称被初始化为
@main
,但是我们也可以在这里有其他的东西 - 我们在我们的
@main
应用中托管了两个.routes
:/input_book
和/novel_novel
- 你可以把它们想象成我们 Web 应用程序上的独立页面,它们都处理不同的行为
GET
和POST
的 HTTP 方法,它们从我们的后端获取一些东西或者向它发送一些东西(好术语!)
对于/input_book
调用,我们正在做的是从网页上的User
接收一个原始文本输入,将其打包成一个看起来像{"book": "book_title"}
的 JSON,然后更新后端服务器。在这种情况下,我们使用这本书来寻找User
可能喜欢的类似的稀有书籍。我们用这个输入调用 Graph,并将一个global
python 变量设置为输出图书的图像 URL。
现在你可以想象 WebApp 会对 Flask App 进行第二次 API 调用,说“那个输入的输出是什么?”。这是我们在/novel_novel
中的GET
调用,它也返回一个 JSON。对你们大多数人来说,这听起来很合理,对我来说也是如此。你可能以前用过 Flask。但是你以前做过React
App 吗?
React 是一个脸书赞助的开源 JavaScript 库,旨在使用户界面更容易编码。我仍然花了一些时间来学习一些 JavaScript 基础知识,理解 React 的语法,并试图让一切看起来体面。我使用的两个重要资源是之前提到的 YouTube 和这篇关于数据科学的文章。视频指南利用了第二个 React 库semantic-ui-react
,这使得应用程序的构建更加容易,所以我基本上遵循了他们的建议。老实说,你也应该这样。安装必要的工具、构建样板文件App.js
以及解释编写函数的基础工作最好留给 JavaScript 专业人员去做。
不过,我将详细介绍一下实际的 React 组件,这些组件是我为了与我们的图表集成在一起而组装的。这里的主要函数是App.js
文件,所以我们将首先解包它。我们导入必要的库,初始化App()
,然后返回一些类似 HTML/JavaScript 的实体(部门、容器、图像等)。):
App.js
你会注意到在Container
里面我们有两个组件:<BookEntry/>
和<GrabBook/>
。这些对应于我们 Flask 应用程序中的两个 API 调用,这是我们的图形理解的标题POST
,然后执行一个GET
调用来为用户抓取我们感兴趣的新小说。让我们先来看看更复杂的组件BookEntry
:
我们从这个组件{ Form, Input, Button }
中的semantic-ui-react
导入了一些额外的包。我们希望用户将文本输入到与他们喜欢的书名对应的Form
中,然后点击提交Button
。首先,我们初始化几个变量(const [title, setTitle]
)并设置默认值= useState('');
,一个空字符串。
接下来,我们构建<Form.Field>
,并添加一个placeholder
值,为用户提供做什么的指示。一旦用户输入了一些文本,我们将title
的useState
设置为输入的文本。轻松点。
接下来,我们添加一个形状为Button
的新<Form.Field>
,用户可以在文本输入的正下方点击它。单击按钮(<Button onClick= ...
),我们通过await fetch("/input_book"
调用对我们的 API 进行第一次调用。回想一下,在我们的 Flask 应用程序中,我们的蓝图有@main.route('/input_book'
,所以这个调用在这个接触点上到达我们的后端。这个POST
调用的返回值只是一个response
,表示当且仅当我们在图中找到标题时一切正常。否则我们返回一个400
并要求用户提交一个新的标题。有可能我们的数据库没有标题,或者可能拼写不同,等等。,所以我们不希望我们的应用程序在这些情况下崩溃。
在进入下一个组件之前,我鼓励任何试图构建这些 web 应用程序的人在代码中到处使用console.log()
调用。它们不仅有注释的双重作用,在调试代码时也很有帮助。回想一下,你只需右键点击 Chrome 中的任何网页,Inspect
,然后打开console
标签,你就可以看到正在发生的一切。
所以,让我们假设一切进展顺利,我们已经找到了相关的标题。现在我们实际上必须在我们的GrabBook
组件中为用户返回一本新书:
这里我们通过一个fetch("/novel_novel")
对POST
端点进行另一个 API 调用。请记住,我们正在返回一个类似于{"image_url": "http://image_of_book.png"}
的 JSON 对象。然后,我们将它传递给一个<Image>
块,该块呈现图像并将其返回给我们的应用程序。酷!
The input was Neuromancer by William Gibson. The output is a great book!
公平地说,这个页面看起来不怎么样。如果你是 React 专家,请告诉我如何才能让布局看起来更好:
- 提交请求后,需要刷新页面以显示正确的图像。
- 我也更喜欢背景图片覆盖整个页面,并且表单域在它的前景中。
这项工作并不打算成为一个商业化、生产就绪的系统。这是一次有趣的学习冒险,并且在它的范围内发人深省。如果你有兴趣查看的话,这里有一个回购的链接。
谢谢你的来访!欢迎在下面评论,求书。我个人已经从图表中找出了 10 多条建议,我几乎总是对结果感到惊讶。我很高兴我研究了这些建议,虽然有些不是我喜欢的,但其他的都是很棒的发现。
在 30 分钟内构建一个实时对象检测 Web 应用程序
Image Credit: https://github.com/tensorflow/models/tree/master/research/object_detection
张量流。射流研究…
Tensorflow.js 是一个开源库,使我们能够使用 Javascript 在浏览器中定义、训练和运行机器学习模型。我将使用 Angular 中的 Tensorflow.js 框架来构建一个 Web 应用程序,该应用程序可以检测网络摄像头视频馈送上的多个对象。
COCO-SSD 型号
首先,我们必须选择我们将用于对象检测的预训练模型。Tensorflow.js 提供了几个预训练模型,用于分类、姿态估计、语音识别和对象检测目的。查看所有 Tensoflow.js 预训练模型了解更多信息。
COCO-SSD 模型是一种预训练的对象检测模型,旨在定位和识别图像中的多个对象,是我们将用于对象检测的模型。
Photo by Brooke Cagle on Unsplash
原 ssd_mobilenet_v2_coco 模型大小为 187.8 MB,可从 TensorFlow 模型动物园下载。与原始模型相比,Tensorflow.js 版本的模型非常轻量级,并针对浏览器执行进行了优化。
Tensorflow.js COCO-SSD 默认的物体检测模型是**‘lite _ mobilenet _ v2’体积非常小,1MB 以下,推理速度最快。如果您想要更好的分类准确性,您可以使用‘mobilenet _ v2’**,在这种情况下,模型的大小增加到 75 MB,这不适合 web 浏览器体验。
**“model . detect”**直接从 HTML 中获取图像或视频输入,因此在使用之前,您无需将输入转换为张量。它返回检测到的对象的类、概率分数以及边界框坐标的数组。
model.detect(
img: tf.Tensor3D | ImageData | HTMLImageElement |
HTMLCanvasElement | HTMLVideoElement, maxDetectionSize: number
)
[{
bbox: [x, y, width, height],
class: "person",
score: 0.8380282521247864
}, {
bbox: [x, y, width, height],
class: "kite",
score: 0.74644153267145157
}]
Photo by Marc Kleen on Unsplash
ANGULAR WEB 应用正在初始化
在我们清楚了模型之后,是时候使用 Angular 命令行界面来初始化 Angular web 应用程序了。
npm install -g @angular/cli
ng new TFJS-ObjectDetection
cd TFJS-ObjectDetection
然后我将使用 NMP 包管理器加载 Tensorflow.js 和 COCO-SSD 库。
TFJS-ObjectDetection **npm install @tensorflow/tfjs --save** TFJS-ObjectDetection **npm install @tensorflow-models/coco-ssd --save**
现在都准备好了。所以我们可以开始编码了。我先从**‘app . component . ts’**导入 COCO-SSD 模型开始。
import { Component, OnInit } from '@angular/core';**//import COCO-SSD model as cocoSSD**
import * as cocoSSD from '@tensorflow-models/coco-ssd';
开始网络摄像机馈送
然后,我将使用以下代码启动网络摄像头。
webcam_init()
{
this.video = <HTMLVideoElement> document.getElementById("vid");
navigator.mediaDevices
.getUserMedia({
audio: false,
video: {facingMode: "user",}
})
.then(stream => {
this.video.srcObject = stream;
this.video.onloadedmetadata = () => {
this.video.play();};
});
}
物体检测功能
我们需要另一个函数来加载 COCO-SSD 模型,该模型也调用**‘detect frame’**函数来使用来自网络摄像头馈送的图像进行预测。
public async predictWithCocoModel()
{
const model = await cocoSSD.load('lite_mobilenet_v2');
this.detectFrame(this.video,model);
}
**‘detect frame’**函数使用 requestAnimationFrame 通过确保视频馈送尽可能平滑来反复循环预测。
detectFrame = (video, model) => {
model.detect(video).then(predictions => {
this.renderPredictions(predictions);
requestAnimationFrame(() => {
this.detectFrame(video, model);});
});
}
Photo by Mikhail Vasilyev on Unsplash
渲染预测
同时,**‘render predictions’**函数将检测到的对象的边界框和类名绘制到屏幕上。
renderPredictions = predictions => {const canvas = <HTMLCanvasElement> document.getElementById ("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
**// Fonts**
const font = "16px sans-serif";
ctx.font = font;
ctx.textBaseline = "top";
ctx.drawImage(this.video,0,0,300,300);
predictions.forEach(prediction => {
**// Bounding boxes's coordinates and sizes**
const x = prediction.bbox[0];
const y = prediction.bbox[1];
const width = prediction.bbox[2];
const height = prediction.bbox[3];**// Bounding box style**
ctx.strokeStyle = "#00FFFF";
ctx.lineWidth = 2;**// Draw the bounding
** ctx.strokeRect(x, y, width, height);
**// Label background**
ctx.fillStyle = "#00FFFF";
const textWidth = ctx.measureText(prediction.class).width;
const textHeight = parseInt(font, 10); // base 10
ctx.fillRect(x, y, textWidth + 4, textHeight + 4);
});
predictions.forEach(prediction => {
**// Write prediction class names**
const x = prediction.bbox[0];
const y = prediction.bbox[1];
ctx.fillStyle = "#000000";
ctx.fillText(prediction.class, x, y);});
};
现在,在浏览器上执行对象检测所需的所有功能都已准备就绪。
我们只需要在**【ngOnInit】上调用‘web cam _ init’和‘predictwithcocomoodel’**就可以在启动时初始化 app。
ngOnInit()
{
this.webcam_init();
this.predictWithCocoModel();
}
Photo by Andrew Umansky on Unsplash
用于对象检测的 HTML 元素
剩下的最后一步是修改 ‘app.component.html’ ,以包含上述功能工作所需的 < video > 和 < canvas > HTML 元素。
<div style="text-align:center">
<h1>Tensorflow.js Real Time Object Detection</h1>
<video hidden id="vid" width="300" height="300"></video>
<canvas id="canvas"></canvas>
</div>
完整代码
访问我的 GitHub 资源库获取该项目的完整代码。
演示 WEB 应用程序
访问现场演示应用程序,查看运行中的代码。该应用程序在 Google Chrome 浏览器中运行没有任何问题。如果您使用任何其他浏览器,请确保您使用的浏览器支持’ requestAnimationFrame’ 。
用 Python 和 Google Search 构建一个简单的聊天机器人
Build a Simple Python ChatBot from Scratch Using Google Search
今天我们要构建一个 Python 3 聊天机器人 API 和 web 界面。聊天机器人很难构建,因为有无限多的输入。正因为如此,一个能够不断给出好答案的聊天机器人需要大量的知识。
开发人员通常会将机器学习算法、NLP 和预定义答案的语料库应用到他们的聊天机器人系统设计中。我们将保持我们的代码基本,所以我们将绕过为我们的聊天机器人创建一个复杂的“大脑”。
我们不会建立一个人工智能大脑,而是使用一个免费的和已经建立的大脑:谷歌搜索。
我们的聊天机器人将对用户的查询执行 Google 搜索,从第一个结果中抓取文本,并用该页面文本的第一句话回复用户。
我们开始吧!顺便说一下,所有提到的代码都在 Python ChatBot GitHub 库中。
用 Python 在 Google 中查询聊天机器人回复
为了用 omniscience(无限知识)编写简单的聊天机器人,我们将在 Python API 中进行 Google 搜索。幸运的是,有一个 Google search Python 库,我们可以用 pip 安装它。
在本地安装了 Google 库之后,您可以像这样编写 Python 代码:
一旦我们从搜索结果中获得了一个 URL 列表,我们就可以使用 Python 请求库对该网页进行 GET 请求。我们也可以通过使用来自 lxml 的html
和 BeautifulSoup 来解析 HTML。
这个项目的所有 Python 依赖项都可以在 GitHub 存储库的[requirements.txt](https://github.com/lmzach09/Python_ChatBot_Google/blob/master/requirements.txt)
文件中找到。
这是一个完整的文件,我们的 HTTP 服务器可以将它作为一个依赖项导入。我制作了一个方法,它可以进行 Google 搜索,获取网页上的第一个<p>
,并以字符串形式返回其内容。如果搜索以任何方式失败,聊天机器人将回复“对不起,我想不出答案。”
现在我们可以接受用户输入并进行谷歌搜索。我们将对搜索的第一个结果发出一个 HTTP GET 请求。然后我们解析返回的 HTML,分离出该页面上第一个<p>
中的第一句话。这是我们聊天机器人的回复算法,不需要机器学习。
一个简单聊天机器人的 Python API
接下来,我们需要建立一个服务器应用程序,这将是我们的聊天机器人查询的 API。它将响应 HTTP 请求。首先,这些请求将来自一个简单的 HTML 页面,我们稍后会制作这个页面。
首先,我们将导入 Python 3 HTTP 服务器和 socket 服务器库,以及我们之前创建的 Google 搜索文件。
我们的 API 将在端口 8080 上提供服务,我们将从项目的父目录中名为public
的文件夹中提供网页资产。接下来,我们将为 GET 和 POST 请求创建自己的处理程序。
HTTP GET 请求将尝试从public
文件夹中返回相应的文件。这些将是我们 web 浏览器界面的 HTML、CSS 和 JavaScript 文件。帖子请求将用于聊天机器人查询。
最后,我们将启动服务器并使用我们的处理程序。这是整个文件,包括上面的代码片段。
我们可以使用 CURL 来测试带有 POST 请求的聊天机器人 API。
curl -d "how old is samuel l jackson" [http://localhost:8080](http://localhost:8080)
Screenshot of POST request output of the ChatBot API
接下来我们将制作一个可以查询这个 API 的 HTML 页面。最终,我们将拥有一个端到端的聊天机器人,提供复杂的答案。
构建聊天机器人网页
我们的网页将非常简单。它将包含一个机器人的图片,一个文本输入字段和一个提交按钮。每当用户提交一个输入,chatbot API 就会通过 POST 请求到达。从 API 返回的文本答案将被填入网页。
这是 HTML 页面。将此作为index.html
保存在我们之前提到的public
文件夹中。机器人的图像文件也在完整的 Python ChatBot GitHub 库中。
接下来,我们将为这个网页做一些快速的样式。将这个 CSS 文件也保存在public
文件夹中。它已经在 HTML 文件的<head>
部分被引用。
现在,您应该有一个简单的聊天机器人网页可供用户输入。以下是截图:
ChatBot Web Page Screenshot
网页还没有完全为用户准备好。它需要 JavaScript。
我们将编写一些 JS 来检测用户按下回车键并点击提交按钮。当这些事件中的任何一个发生时,我们将获得用户输入字段中的文本,并将其作为我们的 Python 服务器的 POST 主体。
我们将使用fetch
方法向 Python API 服务器发出 HTTP POST 请求。现代网络浏览器现在默认包含了获取 API 。
这是我们与 3 个 HTML 元素交互的简单 JavaScript。将此作为app.js
保存在公共文件夹中。
我们几乎准备好将我们的 Python 聊天机器人投入生产了。
运行我们从头开始制作的简单 Python 聊天机器人
现在我们已经写完了所有的代码,在运行全栈 Python 聊天机器人之前,我们还有一个步骤。如果还没有,在项目的父目录中创建一个requirements.txt
文件,与 2 个 Python 文件放在一起。该文件是一个 Python 范例,用于轻松安装项目的依赖项。
使用命令行转到 ChatBot 项目的父目录。运行 Python 库安装命令。
pip install -r requirements.txt
您的机器现在拥有运行聊天机器人所需的所有库!让我们运行全栈应用程序。
python server.py
接下来打开你的网络浏览器,进入 http://localhost:8080/ 。如果你看到聊天机器人的图像,它正在工作。
尝试一些输入!
谁扮演了钢铁侠
塞缪尔·杰克逊多大了
火星上的天气怎么样
你可以看到,我们的聊天机器人回复并不完美,但对于几分钟的工作来说已经很不错了。
我会继续用 Python 探索 AI 和机器学习,我会分享我的发现。查看我的媒体页面的 JavaScript、 Dart 和 Python 指南。
用 TensorFlow.js 构建一个简单的神经网络
创建一个神经网络模型来做出笔记本电脑购买决策
TL;DR 在 TensorFlow.js 中建立一个简单的神经网络模型,做出笔记本电脑购买决策。了解为什么神经网络需要激活函数,以及应该如何初始化它们的权重。
现在是午夜,你正微笑着做着一些令人惊恐的梦。突然,你的电话开始响了,相当国际化。你半睡半醒地拿起电话,听一些奇怪的事情。
你的一个朋友从地球的另一边打电话来,请求帮助挑选一台笔记本电脑。毕竟今天是黑色星期五!
这是你五年来第一次收到朋友的来信,这让你有点惊讶。不过,你是个好人,同意帮忙。也许是时候将你的 TensorFlow.js 技能付诸实践了?
不如你做一个模型来帮助你的朋友,这样你就可以继续睡觉了?你听说过神经网络现在非常热门。现在是凌晨 3 点,你不需要太多的劝说。这一次你将使用神经网络!
在你的浏览器中运行本教程的完整源代码:
神经网络
什么是神经网络?按照经典的扣人心弦的方式,我们将从远离回答这个问题开始。
神经网络已经存在了一段时间(从 20 世纪 50 年代开始)?为什么它们最近才变得流行起来(最近 5-10 年)?由沃伦·麦卡洛克和沃尔特·皮茨于年首次提出的神经活动固有思想的逻辑演算神经网络直到 20 世纪 80 年代中期才真正流行起来,当时支持向量机和其他方法控制了整个社区。
通用逼近定理指出,神经网络可以逼近任何函数(在一些温和的假设下),即使只有一个隐藏层(稍后将详细介绍)。第一批证明之一是由乔治·西本科于 1989 年为乙状窦激活函数做的。
最近,深度学习领域越来越多的进展使得神经网络再次成为热门话题。为什么?我们稍后会讨论这个问题。首先,我们从基础开始!
感知器
最初的模型是由弗兰克·罗森布拉特在 20 世纪 50 年代提出的,旨在模拟人类大脑如何处理视觉数据和学习识别物体。感知器接受一个或多个二进制输入 x1,x2,…,xn,并产生一个二进制输出:
要计算输出,您必须:
- 具有表示相应输入的重要性的权重 w1、w2、…、wn
- 二进制输出(0 或 1)取决于加权和∑ wj xj 是大于还是小于某个阈值
让我们看一个例子。假设您需要决定是否需要一台新的笔记本电脑。最重要的特征是它的颜色和大小(她是这么说的)。因此,您有两个输入:
1.它是粉红色的吗?
2.它小吗(明白了)?
你可以用二元变量 x_ pink ,x_ small 来表示这些因素,并给每一个赋予权重/重要性 w_ pink ,w_ small 。根据你对每个因素的重视程度,你可以得到不同的模型。
我们可以进一步简化感知器。我们可以将∑ w_j x_j 重写为两个向量 w . x 的点积,接下来,我们将引入感知机的偏差,b = -threshold。使用它,我们可以将模型重写为:
偏差是衡量感知器输出 1(触发)的难易程度。较大的正偏置使输出 1 变得容易,而较大的负偏置使输出 1 变得困难。
让我们使用 TensorFlow.js 构建感知器模型:
有人出价购买笔记本电脑。它不是粉红色的,但它是小 x = [0,1]。你偏向于不买笔记本电脑,因为你破产了。你可以用负偏压来编码。你是一个比较聪明的用户,你更注重尺寸,而不是颜色 w = [0.5,0.9]:
1
是的,你必须买那台笔记本电脑!
乙状结肠神经元
为了使从数据中学习成为可能,我们希望在给出一个例子时,模型的权重只发生少量的变化。也就是说,每个示例都会导致输出发生微小的变化。
这样,人们可以在呈现新数据的同时不断调整权重,而不必担心单个示例会抹去模型迄今为止已经学习的所有内容。
感知器不是理想的,因为输入的小变化会线性传播到输出。我们可以使用一个s 形神经元来克服这个问题。
乙状结肠神经元具有输入 x1,x2,…,xn,其值可以在 0 和 1 之间。输出由σ(w . x + b)给出,其中σ为 sigmoid 函数,定义如下:
让我们用 TensorFlow.js 和 Plotly 来看看:
使用权重和输入,我们得到:
让我们更深入地了解乙状结肠神经元,并理解它与感知器的相似之处:
- 假设 z 是一个大的正数。那么 e^{-z} ≈ 0,σ(z) ≈ 1
- 假设 z 是一个大的负数。那么 e^{-z} → ∞,σ(z) ≈ 0
- 当 z“有些适度”时,我们观察到与感知器相比的显著差异。
让我们使用 TensorFlow.js 构建 sigmoid 神经元模型:
另一个笔记本电脑的报价出现了。这一次你可以指定颜色接近粉红色的程度,以及它有多小。
颜色有些粉,大小刚好 x = [0.6,0.9]。其余的保持不变:
0.6479407548904419
是的,你还是想买这款笔记本电脑,但是这款也输出了其决策的自信。很酷,对吧?
构建神经网络
扩展上述模型的一个自然方法是以某种方式将它们分组。一种方法是制造多层神经元。这里有一个简单的神经网络,可以用来做出购买笔记本电脑的决定:
神经网络是神经元的集合,连接在一个非循环图中。一些神经元的输出被用作其他神经元的输入。它们被组织成层。我们的示例由全连接层(两个相邻层之间的所有神经元都是连接的)组成,它是一个 2 层神经网络(我们不计算输入层)。由于构建神经网络的神经元做出的简单决定的组合,神经网络可以做出复杂的决定。
当然,输出图层包含您正在寻找的答案。让我们来看看使训练神经网络成为可能的一些因素:
激活功能
感知器模型只是一个线性变换。将多个这样的神经元相互堆叠会导致矢量积和偏差相加。不幸的是,有很多函数不能通过线性变换来估计。
激活函数使模型能够逼近非线性函数(预测更复杂的现象)。好消息是,你已经遇到了一个激活函数 sigmoid:
Sigmoid 函数的一个主要缺点是,它在[-3,+3]范围之外变得非常平坦。这导致权重接近 0——没有学习发生。
ReLU
ReLU,在整流线性单元的神经网络环境中引入,改进受限玻尔兹曼机器,在大于 0 和 0 的值处具有线性输出。
让我们来看看:
ReLU 的一个缺点是负值“消亡”,停留在 0——没有学习。
泄漏的 ReLU
整流器非线性改善神经网络声学模型中引入的泄漏 ReLU 解决了 ReLU 引入的死值:
请注意,负值会被缩放,而不是清零。缩放可通过 tf.leakyRelu() 中的参数进行调整。
重量初始化
训练神经网络做出“合理”预测的过程包括多次调整神经元的权重。这些权重需要有初始值。你应该如何选择那些?
初始化过程必须考虑我们用来训练模型的算法。通常,这个算法是随机梯度下降(SGD) 。它的工作是搜索可能的参数/权重,并选择那些使我们的模型产生的误差最小的参数/权重。此外,该算法严重依赖于随机性和良好的起点(由权重给出)。
相同常数初始化
假设我们使用相同的常数(是的,包括 0)初始化权重。网络中的每个神经元将计算相同的输出,这导致相同的权重/参数更新。我们刚刚挫败了拥有多个神经元的目的。
过小/过大值初始化
让我们用一组小值初始化权重。将这些值传递给激活函数将使它们呈指数级下降,从而使每个权重都变得同等重要。
另一方面,用大值初始化将导致指数增长,使得权重同样不重要。
随机小数字初始化
我们可以使用平均值为 0、标准偏差为 1 的正态分布用小随机数初始化权重。
每个神经元会计算不同的输出,从而导致不同的参数更新。当然,还有其他多种方式。检查 TensorFlow.js 初始化器
你应该买笔记本电脑吗?
现在你已经知道了一些神经网络的功夫,我们可以使用 TensorFlow.js 来建立一个简单的模型,并决定你是否应该购买一台给定的笔记本电脑。
笔记本电脑数据
这么说吧,对于你的朋友来说,大小比粉嫩程度重要多了!你坐下来设计以下数据集:
干得好!你在整合朋友偏好方面做得很好。
建立模型
回想一下我们将要构建的神经网络:
让我们将其转换为 TensorFlow.js 模型:
我们有一个 2 层网络,输入层包含 2 个神经元,隐藏层包含 3 个神经元,输出层包含 2 个神经元。
注意,我们在隐藏层使用 ReLU 激活函数,在输出层使用 softmax。我们在输出层有 2 个神经元,因为我们想知道我们的神经网络在购买/不购买决策中有多确定。
我们使用二元交叉熵通过测量预测的“好”程度来衡量我们模型的当前权重/参数的质量。
我们的训练算法,随机梯度下降,试图找到使损失函数最小化的权重。对于我们的例子,我们将使用 Adam 优化器。
培训
既然我们的模型已经定义,我们可以使用我们的训练数据集来教授它关于我们的朋友偏好的信息:
我们在训练前重组数据,并在每个时期完成后记录进度:
Epoch 1
Loss: 0.703386664390564 accuracy: 0.5
Epoch 2
Loss: 0.6708164215087891 accuracy: 0.5555555820465088
Epoch 3
Loss: 0.6340110898017883 accuracy: 0.6666666865348816
Epoch 4
Loss: 0.6071969270706177 accuracy: 0.7777777910232544
...
Epoch 19
Loss: 0.08228953927755356 accuracy: 1
Epoch 20
Loss: 0.06922533363103867 accuracy: 1
在经历了大约 20 个时期之后,这个模型似乎已经了解了你朋友的偏好。
评价
你保存模型,并将其发送给你的朋友。连接到您朋友的计算机后,您找到一台合适的笔记本电脑,并将信息编码到模型中:
等待几毫秒后,您会收到一个回答:
0: 0.45
1: 0.55
这个模型适合你。它“认为”你的朋友应该购买笔记本电脑,但它对此并不确定。你做得很好!
结论
你的朋友似乎对结果很满意,而你正考虑通过将你的模型作为浏览器扩展来销售,从而赚上百万。不管怎样,你学到了很多:
- 感知器模型
- 为什么需要激活功能,使用哪一个
- 如何初始化神经网络模型的权重
- 建立一个简单的神经网络来解决一个(有点)实际的问题
在浏览器中运行本教程的完整源代码:
躺在舒适的枕头上,你开始思考。我可以用深度学习来做这个吗?
参考
原载于https://www.curiousily.com。
建立机器学习模型(特别是深度神经网络),可以轻松地与现有或新的 web 应用程序集成。想想您的 ReactJs、Vue 或 Angular 应用程序通过机器学习模型的强大功能得到了增强:
建立机器学习模型(特别是深度神经网络),您可以轻松地与现有或新的网络集成…
leanpub.com](https://leanpub.com/deep-learning-for-javascript-hackers)
用不到 50 行 Python 代码构建一个文本生成器 Web 应用程序
学习构建一个自动完成任何输入文本的 web 应用程序
我们将使用 OpenAI 的 GPT-2 作为模型,使用面板作为网络仪表板框架。本指南将分为两部分。在第一部分中,我们将加载我们的模型并编写一个预测函数。第二,我们将构建 web 应用程序。
Example text generation application. We will be building a simpler variation of this web app.
你需要什么
本教程假设你已经安装了 Python 3.7+ ,并且对语言模型有所了解。虽然相关步骤可以在 Jupyter 之外完成,但是强烈推荐使用 jupyter 笔记本。
我们将使用 PyTorch 作为我们深度学习库的选择。在 PyTorch 内,我们将使用 变形金刚 库导入预先训练好的 OpenGPT-2 模型。您可以通过在 bash 中单独输入以下命令来安装这些库:
pip install torchpip install transformers
对于我们的 web 应用程序,我们将利用 面板 ,这是一个很好的工具,可以从 jupyter 笔记本或常规 python 脚本轻松创建可服务的仪表板。使用以下命令安装 panel:
pip install panel
第一部分:建立模型
OpenAI 的 GPT 是一种变形金刚模型,它产生类似人类文本的能力引起了很多关注。如果你以前没有尝试过,在读完这篇文章后,你可能会有同样的想法。
加载模型
首先,我们需要导入所需的包。
import numpy as np
import torch
import torch.nn.functional as F
from transformers import GPT2Tokenizer, GPT2LMHeadModel
from random import choice
接下来,我们将加载 OpenGPT2 标记器和语言模型:(如果第一次运行,可能需要几分钟来下载预训练的模型)
tok = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2")
预测功能
在这个阶段,大部分工作已经完成。由于我们的模型是预先训练好的,所以我们不需要训练它,也不需要做任何修改。我们只需要编写一个函数,它可以向模型输入文本并生成预测。
def get_pred(text, model, tok, p=0.7):
input_ids = torch.tensor(tok.encode(text)).unsqueeze(0)
logits = model(input_ids)[0][:, -1]
probs = F.softmax(logits, dim=-1).squeeze()
idxs = torch.argsort(probs, descending=True)
res, cumsum = [], 0.
for idx in idxs:
res.append(idx)
cumsum += probs[idx]
if cumsum > p:
pred_idx = idxs.new_tensor([choice(res)])
break
pred = tok.convert_ids_to_tokens(int(pred_idx))
return tok.convert_tokens_to_string(pred)
这个函数中发生了很多事情。所以,我们来分解一下。首先,我们对来自 input_ids 的输入文本进行标记和编码。然后,我们要求我们的模型为下一个单词/单词生成一个 logits 向量。在应用 softmax 并按降序排列这些概率后,我们有了一个向量, idxs ,它按概率顺序列出了我们的 vocab 中每个令牌的索引。
在这个阶段,我们可以只选择概率最高的令牌。然而,我们希望能够混合我们的结果,以便相同的输入文本可以生成多种文本。为此,我们将添加一个随机元素,从最有可能的下一个令牌列表中选择一个随机令牌。这样,我们就不会每次都选择相同的预测标记。为此,我们利用 核(Top- p )采样 。
我们通过遍历每个概率来执行这个操作,直到我们遍历的所有概率之和大于 *p,*0 到 1 之间的任意数。在超过 p 之前迭代的所有令牌都存储在列表 res 中。一旦超过了 p ,我们就从这个列表中随机选择一个令牌。请记住,我们正在循环的概率列表包含按概率排序的索引。请注意,如果 p 更高,我们的列表中将包含更多的令牌。反之亦然。因此,如果每次想要相同的结果,可以将 p 设置为 0。
现在,让我们测试几次 pred 函数:
每一次,都有不同的结果,这正是我们所期待的。我们的预测函数现在已经准备好了。让我们构建我们的 web 应用程序吧!
第 2 部分:构建 Web 应用程序
面板概述
如果您不熟悉面板,它可以简化创建 web 仪表盘和应用程序的过程。乍一看,您需要知道它有三个主要组件:
- 面板:可以包含一个或多个窗格(对象)的容器,如文本、图像、图表、小部件等。(它们也可以包含其他面板)
- 窗格:任何单个对象,如文本、图像、数据帧等。
- Widgets :用户可调整的项目,如文本输入、滑块、按钮、复选框,可以改变窗格的行为
为了我们的目的,你需要知道的下一件也是最后一件事是,我们有多种方法来定义不同的窗格和小部件如何相互交互。这些被称为“回调”例如,如果某个按钮被按下,其他窗格应该如何更新?稍后我们将定义一个回调函数来完成这个任务。
高级应用概述
我们的文本生成器应用程序将有一个输入用户输入他们想要的文本。接下来,用户应该能够通过按下按钮来生成新的令牌。此后,将使用我们在第 1 部分中定义的函数中的预测标记生成新文本。最后,用户应该能够在已经预测的标记之上继续生成新的文本。
履行
让我们首先导入面板并创建文本输入小部件:
import panel as pn
pn.extension() # loading panel's extension for jupyter compatibility text_input = pn.widgets.TextInput()
现在,如果我们在 jupyter 中执行 text_input ,我们会得到以下结果:
接下来,我们需要一个面板,它将在我们生成越来越多的令牌时存储整个文本:
generated_text = pn.pane.Markdown(object=text_input.value)
注意,我们将文本对象设置为 text_input 的值。我们希望 generated_text 的值与 text_input 的值相同,因为我们将在 generated_text 的顶部预测新文本。随着更多的令牌被添加到我们的序列中,我们将继续预测 generated_text ,直到用户更改 text_input 。在这种情况下,该过程将重新开始。
然而,我们还没有完全完成。虽然 generated_text 在初始化时会采用 text_input 的值,但如果 text_input 的值发生变化,它不会自行更新。为此,我们需要将这两个对象链接在一起,如下所示:
text_input.link(generated_text, value='object')
这里,我们在 text_input 到 generated_text 之间形成了一个单向链接。因此,每当 text_input 的值改变时, generated_text 的值也会改变为新值。参见:
observing linked behavior between text_input and generated_text in a panel. Note: pn.Row as a component is a panel i.e. container of panes and widgets
现在我们有了两个文本对象,让我们创建按钮小部件:
button = pn.widgets.Button(name="Generate",button_type="primary")
很好,现在我们有了一个按钮,我们只需要把它和我们想要的行为联系起来。为此,我们将编写一个回调函数,它将在每次单击按钮时运行:
def click_cb(event):
pred = get_pred(generated_text.object, model, tok)
generated_text.object += pred
这里发生了两件事。首先,我们将 generated_text 作为输入传递给我们之前编写的预测函数,该函数给出了一个新的令牌。其次,将此令牌添加到 generated_text 中。每当有新的按钮点击时,重复这个过程。
说到这里,我们还是要把按钮点击和回调函数捆绑起来。我们可以通过以下方式做到这一点:
button.on_click(click_cb)
我们现在已经创建了所有的小部件、窗格和功能。我们只需要把这些东西放在一个面板上,瞧:
app = pn.Column(text_input, button, generated_text); app
Note: pn.Column, similar to pn.Row is another type of panel i.e. container of widgets, panes and even other panels.
让我们添加一个标题和一个简短的描述,我们通过了!
title = pn.pane.Markdown("# **Text Generator**")
desc = pn.pane.HTML("<marquee scrollamount='10'><b>Welcome to the text generator! In order to get started, simply enter some starting input text below, click generate a few times and watch it go!</b></marquee>")final_app = pn.Column(title, desc ,app)
为应用服务
Panel 使服务应用程序变得非常容易。有两种方法可以用来做这件事。第一个是”。show()"命令。这通常用于调试,用法如下。这将启动一个新窗口,我们的 final_app 面板作为 web 应用程序运行。
final_app.show()
为了将其投入生产环境,您需要使用“.servable()"方法。但是,如果您以类似于 show 方法的方式运行它,那么在您当前的笔记本中不会发生任何不同。相反,您必须像这样通过计算机的 bash 来服务笔记本:
panel serve --show text_generation_app.ipynb
这将在本地端口上启动您的应用程序,只要您在笔记本中有以下代码:
final_app.servable()
完成了。
到目前为止,您已经有能力构建自己的文本生成应用程序了。您可以通过添加更多面板组件来进一步构建它。你甚至可以将这个应用嵌入到你的其他项目中。一如既往,你可以在 github 上找到我的代码库。注:题图中的 app 是我在教程笔记本中找到的高级变体:text _ generation _ app . ipynb。
[## devkosal/gpt-panel-app
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/devkosal/gpt-panel-app)
额外资源
- OpenAI GPT-2:通过可视化理解语言生成
- 开始使用面板
- 轻松可视化任何数据,从笔记本到仪表盘| Scipy 2019 教程| James Bednar(视频:第一个小时是关于面板)
- 在服务器上部署 Panel 应用
在 GCP 建立一个有用的 ML 模型来预测披头士的听众
AutoML“自动化机器学习”最近受到了数据科学界的抨击。我在 Google Cloud ML 上发表了一些批评 Google Cloud 的 AutoML first look(Google AutoML 团队确实回答了我的一些请求,参见本文结尾的*** 新*** 事情)这篇文章将展示一个公民数据科学家在 ML 下处理整个机器学习生命周期是完全合理的。目标是:
- 解决一个真正的商业问题
- 在 GCP 托管数据
- 使用 GCP 的云自动化
- 进行一些 EDA“探索性数据分析”
- EDA 如何影响结果
- GCP AutoML 表上的数据处理
- 利用内置的数据管道
- 使用维持验证模型
- 将模型投入生产
- 随着时间的推移改进模型
-
-
- Google 刚刚发布的 AutoML 表格中的新内容
-
启动你的计时器,准备,设置,开始…
解决一个真正的商业问题
目标是基于音乐听众的收听模式来确定他们是否会听披头士;我们将建立一个推荐系统。约翰·列侬难道不会对人工智能的力量印象深刻吗?!
当然,你不会想推荐披头士,所以有人可能不会听披头士。谁会不喜欢披头士呢?不管怎样,这个模型可以适用于任何有足够听力模式数据的艺术家。你只需要关掉目标变量就可以了。
在 GCP 托管数据
数据来源是 ListenBrainz 用户的音乐收听历史。
我们根据听众数量挑选出前 300 名最受欢迎的艺术家。由于 big query 1TB/mo 的自由层处理,这些数据是可用的。
创建数据集的代码可以在我的 github 上找到。如果你不耐烦,只想在这里下载数据(2.19 MB)。
使用 GCP 的云自动化
我已经在 Google Cloud 的 AutoML first look 中发布了更多关于如何开始使用 GCP 云 AutoML 表的细节
进行一些 EDA“探索性数据分析”
Select the Target “Like_The_Beatles”
Cloud AutoML Analyze Tab
通过 Cloud AutoML,我们可以更深入地研究数据。这与你作为一名在人工智能平台上工作的数据科学家所能做到的细节水平相去甚远。尽管如此,这里还是有一些值得观察的地方。例如,听得最多的艺术家是那些空值最少的艺术家。
EDA 如何影响结果
在这种情况下,我们如何处理空值:我们让它们为空。原因是 Null 确实意味着在我们的数据集中没有监听;因此等于零。这里的中间值是错误的。了解你的数据!
GCP AutoML 表上的数据处理
我们需要删除“披头士”栏,因为我们的目标是直接基于这一点。否则,我们总是会得到 100%准确的模型,咄。
利用内置的数据管道
最大的好处是你可以通过管道构建模型,而不需要很多代码。事实上,您可以使用支持多种语言(包括 Python )的客户端库来编写完整的训练代码。
使用维持验证模型
我们使用 AutoML 中的自动数据分割功能。他们说:
数据集在定型、验证和测试子集之间拆分的方式。默认情况下,AutoML 表随机选择 80%的数据行用于定型,10%用于测试,10%用于验证。
模型运行得非常好!我们看到了一个. 937 AUC ROC。
这是 50%分数阈值的混淆矩阵:
现在将模型投入生产
有这么简单吗:
这是有成本的。如果我将主持这个特定的模型,它将花费 0.126 美元每小时,或 91.98 美元/月(乘数 730 小时在一个月内假设)。
下面是我用的等式:0.005 * 2.8 * 9 = 0.126 美元。0.005 美元是机器的数量(他们使用 9 来实现低延迟,我相信这是硬编码的)。2.8 是因为我的型号是 2.8G。
import json
from google.cloud import automl_v1beta1 as automlproject_id = 'mwpmltr'
compute_region = 'us-central1'
model_display_name = 'beatles_machine_l_20191104025339'
input = ['usr', 5.0, None, None, 10, ... ] # list of valuesclient = automl.TablesClient(project=project_id, region=compute_region)response = client.predict(
model_display_name=model_display_name,
inputs=inputs)
response###############################################################payload {
tables {
score: 0.9896599650382996
value {
string_value: "True"
}
}
}
payload {
tables {
score: 0.010340098291635513
value {
string_value: "False"
}
}
}
随着时间的推移改进模型
通过使用标签系统在推荐中寻找模式,我们看到整体准确率有了 3.5%的提高**。**
“诀窍”是纠正不良记录(改进地面真实数据)和引入新数据。
在这种情况下,这个新数据是专家应用于音乐的“标签”。这些标签很有预见性。例如,标签“60 年代”和“英国入侵”是高度预测性的。另一方面,与所有机器学习一样,它也可以预测艺术家不属于哪个标签;例如,那些高水平演奏被标记为“电子乐”的人,他们不太可能听甲壳虫乐队的音乐。
* * Google 刚刚发布的 AutoML 表格中的新内容
以下是谷歌添加到 AutoML 表格中的三个有意义的新功能:
- 您可以看到最终的模型架构是如何被训练的,以及在每一轮训练中尝试的实验。
详细信息存储在 Stackdriver 中。
我没有仔细看过这些日志。此时,我不知道日志是否包含了重新创建模型所需的所有信息。
2)在线预测可以得到预测级的特征属性。
TypeError: list_models() got an unexpected keyword argument 'feature_importance'
我可能需要更新我的软件包。
3)您可以导出您的模型容器(现在每个人都可以使用)
结论
踏上汽车之旅并不像我曾经想象的那么可怕。这位数据科学家抱怨说,AutoML 的黑箱性质正在慢慢被打开。易用性越来越强。错误和其他问题似乎正在得到解决。
我谨慎地建议人们开始把目光放在 AutoML 上,作为一个跳跃启动器。这并没有减轻对数据科学之外的数据建模的需求。它并没有减轻工具集之外的数据处理和 EDA 的需要(为此,看看 AI 平台笔记本吧!).同样,这并不意味着这些工具所针对的业务分析师类型不需要很好地理解预测建模理论、术语和最佳实践。
构建基于用户的动漫协同过滤推荐引擎
基于用户之间的统计相似性进行推荐
今天,我们将为动画建立一个推荐引擎,由基于用户的协同过滤提供动力。这只是推荐系统的几种不同方法中的一种
介绍
在基于用户的协同过滤中:
-如果用户喜欢相似的项目就被认为是相似的
-我们首先发现哪些用户是相似的
-然后推荐其他相似用户喜欢的项目
看看我在上面(煞费苦心)画的图。
桑尼喜欢莫奈、毕加索和达利的画。泰德喜欢莫奈和毕加索的画。
桑尼和泰德很相似,因为他们喜欢一些相同的艺术家。桑妮喜欢达利画,但泰德从未看过达利画。
所以让我们向 Ted 推荐 Dali。
清澈如泥?现在我们已经了解了它是如何工作的,让我们来构建一个推荐器。
构建推荐器
从 Kaggle 下载数据并加载到 2 个数据帧中。
anime . CSV—我们数据库中关于动漫的详细信息
rating . CSV—特定用户对特定动漫的评分
DIR = 'anime-recommendations-database/'import pandas as pdanimes = pd.read_csv(DIR + 'anime.csv')
ratings = pd.read_csv(DIR + 'rating.csv')
通过阅读这些文档,我知道评分值为-1
意味着用户已经观看了这部电影,但还没有对它进行评分。我假设这不会给我们任何有用的信息并删除那些记录。
ratings = ratings[ratings.rating != -1]
ratings.head()
animes.head()
数据探索
没有人比我更不喜欢花 75%的时间在数据探索上,所以让我们先了解一下数据的大小和分布。
# number of ratings
len(ratings)
=> 6337241# number of users
len(ratings['user_id'].unique())
=> 69600# number of unique animes (in anime list, not ratings)
len(animes['anime_id'].unique())
#=> 11200# avg number of anime rated per user
import statistics
ratings_per_user = ratings.groupby('user_id')['rating'].count()
statistics.mean(ratings_per_user.tolist())
#=> 91.05231321839081# distribution of ratings per user
# (we may want to exclude users without many data points)
import matplotlib.pyplot as plt
%matplotlib inline
ratings_per_user.hist(bins=20, range=(0,500))
most users have rated fewer than 100 anime
# avg number of ratings given per anime
ratings_per_anime = ratings.groupby('anime_id')['rating'].count()
statistics.mean(ratings_per_anime.tolist())
=> 638.3843054296364# distribution of ratings per anime
import matplotlib.pyplot as plt
%matplotlib inline
ratings_per_anime.hist(bins=20, range=(0,2500))
most anime received less than 500 ratings
回到推荐者
在基于用户的协同过滤中,代表用户的向量实际上是他们给出的评级列表。所以我们宇宙中的动漫越多,每个用户的维度就越多。
让我们通过删除没有被很多用户评价的动画来减少数据量。根据 id 做一个动漫保留清单。
# counts of ratings per anime as a df
ratings_per_anime_df = pd.DataFrame(ratings_per_anime)# remove if < 1000 ratings
filtered_ratings_per_anime_df = ratings_per_anime_df[ratings_per_anime_df.rating >= 1000]# build a list of anime_ids to keep
popular_anime = filtered_ratings_per_anime_df.index.tolist()
以及没怎么评价过动漫的用户。
# counts ratings per user as a df
ratings_per_user_df = pd.DataFrame(ratings_per_user)# remove if < 500
filtered_ratings_per_user_df = ratings_per_user_df[ratings_per_user_df.rating >= 500]# build a list of user_ids to keep
prolific_users = filtered_ratings_per_user_df.index.tolist()
现在过滤掉那些列表之外的动漫和用户。
filtered_ratings = ratings[ratings.anime_id.isin(popular_anime)]
filtered_ratings = ratings[ratings.user_id.isin(prolific_users)]
len(filtered_ratings)
=> 1005314
我们的评级数据点从 600 万降至 100 万。很好。
我们来建立一个用户和动漫之间的评分矩阵。
rating_matrix = filtered_ratings.pivot_table(index='user_id', columns='anime_id', values='rating')# replace NaN values with 0
rating_matrix = rating_matrix.fillna(0)# display the top few rows
rating_matrix.head()
写一个函数,用余弦相似度找出与 current_user 最相似的用户。我们随意决定找出 3 个最相似的用户。
并选择“226”作为我们的当前用户,但我们可以选择任何人。
from sklearn.metrics.pairwise import cosine_similarity
import operatordef similar_users(user_id, matrix, k=3):
# create a df of just the current user
user = matrix[matrix.index == user_id]
# and a df of all other users
other_users = matrix[matrix.index != user_id]
# calc cosine similarity between user and each other user
similarities = cosine_similarity(user,other_users)[0].tolist()
# create list of indices of these users
indices = other_users.index.tolist()
# create key/values pairs of user index and their similarity
index_similarity = dict(zip(indices, similarities))
# sort by similarity
index_similarity_sorted = sorted(index_similarity.items(), key=operator.itemgetter(1))
index_similarity_sorted.reverse()
# grab k users off the top
top_users_similarities = index_similarity_sorted[:k]
users = [u[0] for u in top_users_similarities]
return users current_user = 226# try it out
similar_user_indices = similar_users(current_user, rating_matrix)print(similar_user_indices)
#=> [30773, 39021, 45603]
现在写一个函数来做推荐。我们已经设置了返回 5 部热门推荐动漫的功能。
def recommend_item(user_index, similar_user_indices, matrix, items=5):
# load vectors for similar users
similar_users = matrix[matrix.index.isin(similar_user_indices)] # calc avg ratings across the 3 similar users
similar_users = similar_users.mean(axis=0) # convert to dataframe so its easy to sort and filter
similar_users_df = pd.DataFrame(similar_users, columns=['mean'])
# load vector for the current user
user_df = matrix[matrix.index == user_index] # transpose it so its easier to filter
user_df_transposed = user_df.transpose() # rename the column as 'rating'
user_df_transposed.columns = ['rating'] # remove any rows without a 0 value. Anime not watched yet
user_df_transposed = user_df_transposed[user_df_transposed['rating']==0] # generate a list of animes the user has not seen
animes_unseen = user_df_transposed.index.tolist()
# filter avg ratings of similar users for only anime the current user has not seen
similar_users_df_filtered = similar_users_df[similar_users_df.index.isin(animes_unseen)] # order the dataframe
similar_users_df_ordered = similar_users_df.sort_values(by=['mean'], ascending=False) # grab the top n anime
top_n_anime = similar_users_df_ordered.head(items)
top_n_anime_indices = top_n_anime.index.tolist() # lookup these anime in the other dataframe to find names
anime_information = animes[animes['anime_id'].isin(top_n_anime_indices)]
return anime_information #items # try it out
recommend_item(226, similar_user_indices, rating_matrix)
我们做到了!我们的当前用户尚未观看的来自最相似用户的最高评级的 5 部动画。
实际上,我们想要试验不同的相似性算法和不同数量的相似用户。但是我希望你把它当作一个基于用户的协作过滤的粗略框架。
下次见。
在 pySpark 中用 MLlib 搭建一个端到端的机器学习模型。
对于具有不平衡类别的二元分类问题
photo credit: pexels
介绍
内存计算和并行处理是 Apache Spark 在大数据行业非常受欢迎的一些主要原因,以处理大规模的数据产品并执行更快的分析。 MLlib 建立在 Spark 之上,是一个可扩展的机器学习库,提供高质量的算法和超快的速度。它拥有优秀的 Java、 Python 和 Scala API,是数据分析师、数据工程师和数据科学家的首选。MLlib 由常见的学习算法和实用程序组成,包括分类、回归、聚类、协同过滤(矩阵分解)、降维等。
履行
在本文中,我们将在 pySpark 中使用 MLlib 构建一个端到端的机器学习模型。我们将使用 kaggle 上的家庭信用违约风险竞赛的真实数据集。这项竞赛的目的是根据从每个申请人那里收集的数据,确定贷款申请人是否有能力偿还贷款。目标变量为 0(有能力偿还贷款的申请人)或 1(没有能力偿还贷款的申请人)。这是一个目标标签高度不平衡的二元分类问题。分配比率接近 0.91 比 0.09,0.91 是能够偿还贷款的申请人的比率,0.09 是不能偿还贷款的申请人的比率。
让我们先来看看数据集的结构:
#we use the findspark library to locate spark on our local machineimport findspark
findspark.init('home Diredtory of Spark')from pyspark.sql import SparkSession# initiate our session and read the main CSV file, then we print the #dataframe schemaspark = SparkSession.builder.appName('imbalanced_binary_classification').getOrCreate()
new_df = spark.read.csv('application_train.csv', header=True, inferSchema=True)
new_df.printSchema()root
|-- SK_ID_CURR: integer (nullable = true)
|-- TARGET: integer (nullable = true)
|-- NAME_CONTRACT_TYPE: string (nullable = true)
|-- CODE_GENDER: string (nullable = true)
|-- FLAG_OWN_CAR: string (nullable = true)
|-- FLAG_OWN_REALTY: string (nullable = true)
|-- CNT_CHILDREN: integer (nullable = true)
|-- AMT_INCOME_TOTAL: double (nullable = true)
|-- AMT_CREDIT: double (nullable = true)
|-- AMT_ANNUITY: double (nullable = true)
|-- AMT_GOODS_PRICE: double (nullable = true)
|-- NAME_TYPE_SUITE: string (nullable = true)
|-- NAME_INCOME_TYPE: string (nullable = true)
|-- NAME_EDUCATION_TYPE: string (nullable = true)
|-- NAME_FAMILY_STATUS: string (nullable = true)
|-- NAME_HOUSING_TYPE: string (nullable = true)
|-- REGION_POPULATION_RELATIVE: double (nullable = true)
...
printSchema()只显示了列名及其数据类型。我们将删除 SK_ID_CURR 列,将“TARGET”列重命名为“label ”,并查看我们的目标变量的分布:
# Sk_ID_Curr is the id column which we dont need it in the process #so we get rid of it. and we rename the name of our
# target variable to "label"
drop_col = ['SK_ID_CURR']
new_df = new_df.select([column for column in new_df.columns if column not in drop_col])
new_df = new_df.withColumnRenamed('TARGET', 'label')
new_df.groupby('label').count().toPandas()
我们可以用 matplotlib 可视化标签的分布:
# let's have a look at the distribution of our target variable:
# to make it look better, we first convert our spark df to a Pandasimport matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
df_pd = new_df.toPandas()
print(len(df_pd))
plt.figure(figsize=(12,10))
sns.countplot(x='label', data=df_pd, order=df_pd['label'].value_counts().index)
以下是熊猫数据帧格式的数据:
# let's see how everything look in Pandasimport pandas as pd
pd.DataFrame(new_df.take(10), columns= new_df.columns)
数据争论
现在我们对数据集的一般结构有了一些想法,让我们继续一些数据争论。首先我们检查我们有多少分类和数字特征。接下来,我们构建一个函数,输出数据集中缺失值的基本信息:
# now let's see how many categorical and numerical features we have:cat_cols = [item[0] for item in new_df.dtypes if item[1].startswith('string')]
print(str(len(cat_cols)) + ' categorical features')num_cols = [item[0] for item in new_df.dtypes if item[1].startswith('int') | item[1].startswith('double')][1:]print(str(len(num_cols)) + ' numerical features')**16 categorical features
104 numerical features**
下面是我们如何得到缺失信息的表格:
# we use the below function to find more information about the #missing valuesdef info_missing_table(df_pd):
"""Input pandas dataframe and Return columns with missing value and percentage"""
mis_val = df_pd.isnull().sum() #count total of null in each columns in dataframe#count percentage of null in each columns
mis_val_percent = 100 * df_pd.isnull().sum() / len(df_pd)
mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1) #join to left (as column) between mis_val and mis_val_percent
mis_val_table_ren_columns = mis_val_table.rename(
columns = {0 : 'Missing Values', 1 : '% of Total Values'}) #rename columns in table
mis_val_table_ren_columns = mis_val_table_ren_columns[
mis_val_table_ren_columns.iloc[:,1] != 0].sort_values('% of Total Values', ascending=False).round(1)
print ("Your selected dataframe has " + str(df_pd.shape[1]) + " columns.\n" #.shape[1] : just view total columns in dataframe
"There are " + str(mis_val_table_ren_columns.shape[0]) +
" columns that have missing values.") #.shape[0] : just view total rows in dataframe return mis_val_table_ren_columns missings = info_missing_table(df_pd)
missings
121 列中有 67 列缺少值。它没有在图像中显示所有这些列,但总体而言,这 67 列中的大多数都有超过 50%的缺失值。所以我们正在处理大量缺失的值。**我们将用每列的平均值填充数值缺失值,用每列最常见的类别填充分类缺失值。**但首先,让我们统计每一列中缺失的值:
miss_counts = count_missings(new_df)
miss_counts[('AMT_ANNUITY', 12),
('AMT_GOODS_PRICE', 278),
('NAME_TYPE_SUITE', 1292),
('OWN_CAR_AGE', 202929),
('OCCUPATION_TYPE', 96391),
('CNT_FAM_MEMBERS', 2),
('EXT_SOURCE_1', 173378),
('EXT_SOURCE_2', 660),
('EXT_SOURCE_3', 60965),
('APARTMENTS_AVG', 156061),
('BASEMENTAREA_AVG', 179943),
('YEARS_BEGINEXPLUATATION_AVG', 150007),
('YEARS_BUILD_AVG', 204488),
...
我们将缺失值的分类列和数字列分开:
# here we seperate missing columns in our new_df based on #categorical and numerical typeslist_cols_miss=[x[0] for x in miss_counts]
df_miss= new_df.select(*list_cols_miss)
#categorical columns
catcolums_miss=[item[0] for item in df_miss.dtypes if item[1].startswith('string')] #will select name of column with string data type
print("cateogrical columns_miss:", catcolums_miss)### numerical columns
numcolumns_miss = [item[0] for item in df_miss.dtypes if item[1].startswith('int') | item[1].startswith('double')] #will select name of column with integer or double data type
print("numerical columns_miss:", numcolumns_miss)
接下来,我们填充缺失的值:
# now that we have seperated the columns based on categorical and #numerical types, we will fill the missing categiracl
# values with the most frequent categoryfrom pyspark.sql.functions import rank,sum,col
df_Nomiss=new_df.na.drop()
for x in catcolums_miss: mode=df_Nomiss.groupBy(x).count().sort(col("count").desc()).collect()[0][0]
print(x, mode) #print name of columns and it's most categories
new_df = new_df.na.fill({x:mode})# and we fill the missing numerical values with the average of each #columnfrom pyspark.sql.functions import mean, roundfor i in numcolumns_miss:
meanvalue = new_df.select(round(mean(i))).collect()[0][0]
print(i, meanvalue)
new_df=new_df.na.fill({i:meanvalue})
既然我们的数据集中不再有缺失值,让我们来研究如何处理不平衡类。有不同的方法来缓解这个问题。一种方法是下采样多数阶级或者上采样少数阶级以取得更平衡的结果。另一种方法是为每个类分配权重,通过分配较小的权重来惩罚多数类,通过分配较大的权重来促进少数类。我们将在数据集中创建一个名为“weights”的新列,并将每个类的倒数 比率指定为权重。这是如何做到的:
# adding the new column weights and fill it with ratiosfrom pyspark.sql.functions import whenratio = 0.91
def weight_balance(labels):
return when(labels == 1, ratio).otherwise(1*(1-ratio))new_df = new_df.withColumn('weights', weight_balance(col('label')))
下面是添加重量列后的样子:
特征工程
下一步是特征工程。pySpark 使得提取特征变得如此简单,我们不需要做太多工作。以下是步骤:
- 我们应用 StringIndexer()为分类列中的每个类别分配索引。
- 我们应用 OneHotEncoderEstimator()将分类列转换为 onehot 编码的向量。
- 我们应用 VectorAssembler()从所有分类和数字特征中创建一个特征向量,我们将最终向量称为“特征”。
# we use the OneHotEncoderEstimator from MLlib in spark to convert #aech v=categorical feature into one-hot vectors
# next, we use VectorAssembler to combine the resulted one-hot ector #and the rest of numerical features into a
# single vector column. we append every step of the process in a #stages arrayfrom pyspark.ml.feature import OneHotEncoderEstimator, StringIndexer, VectorAssemblerstages = []
for categoricalCol in cat_cols:
stringIndexer = StringIndexer(inputCol = categoricalCol, outputCol = categoricalCol + 'Index')
encoder = OneHotEncoderEstimator(inputCols=[stringIndexer.getOutputCol()], outputCols=[categoricalCol + "classVec"])stages += [stringIndexer, encoder]assemblerInputs = [c + "classVec" for c in cat_cols] + num_cols
assembler = VectorAssembler(inputCols=assemblerInputs, outputCol="features")
stages += [assembler]
现在让我们把所有东西都放到一个管道里。这里我们执行一系列转换,因此我们使用管道一次完成所有转换:
# we use a pipeline to apply all the stages of transformationfrom pyspark.ml import Pipelinecols = new_df.columns
pipeline = Pipeline(stages = stages)
pipelineModel = pipeline.fit(new_df)
new_df = pipelineModel.transform(new_df)selectedCols = ['features']+cols
new_df = new_df.select(selectedCols)
pd.DataFrame(new_df.take(5), columns=new_df.columns)
以下是我们的新数据集在特征工程后的样子:
训练和超参数调整
对于训练,我们首先将数据集分成训练集和测试集。然后,我们开始使用逻辑回归进行训练,因为它对二元分类问题表现良好。
# split the data into trainign and testin setstrain, test = new_df.randomSplit([0.80, 0.20], seed = 42)
print(train.count())
print(test.count())# first we check how LogisticRegression perform
from pyspark.ml.classification import LogisticRegressionLR = LogisticRegression(featuresCol = 'features', labelCol = 'label', maxIter=15)
LR_model = LR.fit(train)
我们将为训练数据绘制 ROC 曲线,以了解如何执行逻辑回归,然后我们将使用 ROC 曲线下面积(一种用于评估二元分类的标准度量)作为评估模型的度量:
#plotting the ROC CurvetrainingSummary = LR_model.summaryroc = trainingSummary.roc.toPandas()
plt.plot(roc['FPR'],roc['TPR'])
plt.ylabel('False Positive Rate')
plt.xlabel('True Positive Rate')
plt.title('ROC Curve')
plt.show()print('Training set ROC: ' + str(trainingSummary.areaUnderROC))
在测试集上检查模型的性能:
from pyspark.ml.evaluation import BinaryClassificationEvaluatorpredictions_LR = LR_model.transform(test)
evaluator = BinaryClassificationEvaluator()
print("Test_SET (Area Under ROC): " + str(evaluator.evaluate(predictions_LR, {evaluator.metricName: "areaUnderROC"})))**Test_SET (Area Under ROC): 0.7111434396856681**
0.711 对于逻辑回归来说并不是一个很差的结果。接下来,我们尝试另一个模型,**梯度推进树(GBT)。**这是一种非常流行的分类和回归方法,使用决策树的集合。
# next we checkout gradient boosting treesfrom pyspark.ml.classification import GBTClassifiergbt = GBTClassifier(maxIter=15)
GBT_Model = gbt.fit(train)
gbt_predictions = GBT_Model.transform(test)evaluator = BinaryClassificationEvaluator()
print("Test_SET (Area Under ROC): " + str(evaluator.evaluate(gbt_predictions, {evaluator.metricName: "areaUnderROC"})))**Test_SET (Area Under ROC): 0.7322019340889893**
我们使用 GBT 获得了更好的结果,0.732。作为这里的最后一个策略,我们将使用网格搜索实现超参数调整,然后我们运行交叉验证来更好地提高 GBT 的性能。
from pyspark.ml.tuning import ParamGridBuilder, CrossValidatorparamGrid = (ParamGridBuilder()
.addGrid(gbt.maxDepth, [2, 4, 6])
.addGrid(gbt.maxBins, [20, 30])
.addGrid(gbt.maxIter, [10, 15])
.build())cv = CrossValidator(estimator=gbt, estimatorParamMaps=paramGrid, evaluator=evaluator, numFolds=5)# Run cross validations.cvModel = cv.fit(train)
gbt_cv_predictions = cvModel.transform(test)
evaluator.evaluate(gbt_cv_predictions)**CV_GBT (Area Under ROC) = 0.7368288195372332**
结果有了一点改善,这意味着我们仍然可以通过超参数调整来看看我们是否可以进一步改善结果。
在这个项目中,我们建立了一个**端到端的机器学习模型(具有不平衡类的二元分类)。**我们展示了 Apache Spark 的 MLlib 的强大功能,以及它如何应用于端到端 ML 项目。
像往常一样,代码和 jupyter 笔记本在我的 Github 上可用。
非常感谢您的提问和评论。
参考资料:
- https://github.com/elsyifa/Classification-Pyspark
- https://spark . Apache . org/docs/2 . 3 . 0/ml-classification-regression . html
用 Plotly 和 Dash 创建交互式 Choropleth 地图
悉尼郊区房价中位数的可视化
L 上周,我完成了 IBM 数据科学课程的期末作业,就是根据位置数据,寻找一个理想的郊区,开一家意大利餐厅。在这个过程中,我在网上收集了悉尼每个郊区的房产中值价格(即房屋购买/租赁和单位购买/租赁),并分别绘制在 Choropleth 地图上。
Choropleth maps for different parameters
然而,我想知道是否有可能将所有这些地图组合在一起,并通过点击下拉菜单中的名称来选择其中之一。此外,我想在地图旁边再添加一个地块,以相应地显示中位数价格最高的前 10 个郊区。这些附件将使地图更加丰富和用户友好。在这篇文章中,我将分享我关于如何使用 Plotly 和 Dash 创建一个带有 choropleth 地图和条形图的交互式仪表板的笔记。此外,我假设你以前有与 Plotly 的经验。
先决条件
在系统上安装plotly,
dash
和pandas
。我使用conda
创建了一个虚拟环境,以保持系统有序,避免与系统中的其他包混淆。如果你想了解更多关于conda env
的信息,我已经在上一篇文章中介绍了康达虚拟环境。以下代码将创建一个虚拟环境,其中包含plotly
和dash
所需的所有包。
conda create -n <whatever name you want for this env> -c plotly plotly=4.4.1 -c conda-forge dash pandas
为了能够在plotly
中绘制地图,我们还需要一个来自 Mapbox 的令牌,它提供了各种漂亮的地图样式,最重要的是免费的。此外,在这个仪表板中使用了两个数据集,它们可以从我的 github 下载(你也可以在这里找到 dash 应用程序代码)。
子弹头列车路线
如果您现在想探索 dash 应用程序,在完成上述步骤后,您需要在这个脚本中将您的 Mapbox 令牌分配给mapbox_accesstoken
,并在两个数据集所在的同一目录下运行它。一旦弹出以下消息,只需在您首选的浏览器中打开这个地址[http://127.0.0.1:8050/](http://127.0.0.1:8050/)
,dash 就会加载到那里。
$ python dash_project_medium.py
Running on [http://127.0.0.1:8050/](http://127.0.0.1:8050/)
Debugger PIN: 880-084-162
* Serving Flask app "dash_project" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
蒸汽火车道
如下图所示,我已经标记了脚本中用于在仪表板中创建相应元素的关键函数。
Dissecting the dashboard
通过plotly
和dash
构建这个仪表板的一般原则是 1)在一个定义好的画布上安排和组合不同的元素;2)将所有元素编译到一个容器中:fig=go.Figure(data=trace2 + trace1, layout=layout)
;3)将这个容器传递给dcc.Graph
,其中dcc
是 dash 核心组件,dash
将为仪表盘创建一个基于 html 的 web 应用。
该仪表板的一个关键功能是通过下拉菜单在两个轨迹(choropleth 图和条形图)中显示特定参数(即House_buy
、House_rent
、Unit_buy
和Unit_rent
)。我的方法是创建四个层,每个层有一个参数visible=False
。然后使用updatemenus
的buttons
功能转动visible=True
至给定参数。
Bar plot code
因此,每个轨迹中有四个图形层,只有一个是可见的,这取决于在给定的时间点点击了哪个按钮。
Drop down menu bar code
由于地图不是在笛卡尔坐标系(x/y)上绘制的,而笛卡尔坐标系是条形图中使用的坐标系,因此我在仪表板中为地图和条形图分别设置了两个坐标系。对于条形图(trace2
),其坐标轴被分配到**xaxis='x2'**
、**yaxis='y2'**
。相反,地图(trace1
)在Layout
中有自己的特征,它被赋给变量mapbox1
,mapbox
和x/y
后面的数字是任意的。话虽如此,你可以指定任意多的坐标系,只要确保你在Layout
中将正确的系统锚定到它的轨迹上。
Choropleth map code
然后在Layout
设置中,我们分别对这两个坐标系进行调整。如通过domain
显示轨迹在仪表盘中的位置,通过showticklabels
显示每个轴上的刻度,通过autorange
显示条的升序。
Layout code
在连接了fig=go.Figure
中的所有元素之后,我将fig
分配给dcc.Graph
,将所有代码打包成一个py
文件,并运行它。嘣,我的第一个交互式仪表盘来了。
Dash app implementation
我应该指出,我在这里只使用了非常基本的破折号结构,大部分代码仍然是用 Plotly 编写的。我的方法的一个缺点是,将所有四层堆叠到同一轨迹上可能会降低应用程序的速度,这是因为所有数据都需要在 dash 应用程序初始化时加载。当点击下拉菜单事件发生时,进行实时更新会更有效。希望通过进一步学习高级 Dash 代码,我能找到一个解决方案。
以下是一些学习 Dash 的资源:
- Dash 用户指南
- 来自数据营的初学者 Dash
- Dash 社区论坛
- Github 上的牛逼 Dash 资源指南
- 一张详细的仪表盘构建贴
和往常一样,我欢迎反馈、建设性的批评和倾听您的数据科学项目。你可以在 Linkedin 上找到我。
构建并比较 3 个模型— NLP 预测
从零开始用 3 种算法预测情绪-初学者友好。
Python 上的自然语言处理(Jupyter)!
创建这个项目是为了学习和理解各种分类算法在自然语言处理模型中是如何工作的。自然语言处理,我现在称之为 NLP,是机器学习的一个分支,专注于使计算机能够解释和处理语音和文本形式的人类语言。
Photo by Patrick Tomasso on Unsplash
在这条流水线上,我经历了以下几个步骤:
- 导入所需的包和库
- 导入数据集
- 在数据集中的文本可以被计算机分析之前对其进行处理
- 创建一个单词袋模型
- 将数据集分成训练集和测试集
- 朴素贝叶斯算法
- 决策树算法
- 随机森林算法
- 比较准确度、精确度、召回率和 F1 分数
||二||问题
Photo by Zulmaury Saavedra on Unsplash
对于这个项目,我将使用来自 Kaggle 的数据集,其中包含不同用户对一家披萨店的 1000 条评论。|链接到数据集|
人类可以阅读评论,并判断它是积极的还是消极的。如果我们可以创建一个模型来将他们分为积极的或消极的呢?做这件事的最好方法是什么?
首先说一下流程。我们首先对数据进行预处理,删除对我们的预测没有帮助的不必要的词。然后,我们采用词干形式的重要单词*(例如 lov 是 loved、loving 或 lovely 的词干)*。然后,我们训练机器根据词干来学习哪些评论是正面的。之后,我们使用类似的信息测试数据,看看我们的机器能够多准确地预测评论是正面还是负面(1 或 0)。
|| III || 导入基本库
在这里,我们导入这个模型工作所需的所有库。在开始之前,请确保安装了所有的依赖项。我们将主要与pandas, numpy, re, nltk, matplotlib, and sci-kit learn.
合作
确保在命令行上运行上面提到的所有库。
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import re
import nltk
from nltk.corpus import stopwords
from nltk.stem.porter import PorterStemmer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import confusion_matrix
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
|| IV ||导入数据集
dataset = pd.read_csv(‘Restaurant_Reviews.tsv’, delimiter = ‘\t’, quoting = 3)
在上面的代码中,我使用的是. tsv 文件,而不是. csv 文件。当我们分解缩略词时,区别就更容易分辨了。. tsv(制表符分隔的值)在文本文件中由空格分隔,而. csv(逗号分隔的值)使用逗号分隔不同的值。
pd.read_csv
可用于两者,但为了指定 tsv 文件,我在分隔符中添加了’ \t ',以告诉机器值由制表符而不是逗号分隔。引用被设置为 3,因为我们的评论包含一些双引号,如果我们不添加这一行,机器将无法将它们解释为常规文本。
下面我们可以看到我们数据集的前 10 个评论和结果:正面(1)或负面(0)
dataset.head(10)
|| V || 文本预处理
nltk.download(‘stopwords’)
corpus = []for i in range(0, 1000):
review = re.sub(‘[^a-zA-Z]’, ‘ ‘, dataset[‘Review’][i])
review = review.lower()
review = review.split()
ps = PorterStemmer()
review = [ps.stem(word) for word in review if not word in set(stopwords.words(‘english’))]
review = ‘ ‘.join(review)
corpus.append(review)
上面的代码显示了这个模型中最重要的一步。我从导入 Regex 作为re
开始。这个类允许我们匹配和搜索字符串对象。我使用 re 的子功能来允许机器包含我们需要的数据元素,即大写和小写的字母 A-Z。
我还导入了代表自然语言工具包的nltk
。从nltk
中,我导入了两个类:stopwords
类和PorterStemmer
类。
stopwords
允许我们删除对我们的模型没有帮助的单词(如“the”、“an”、“this”等)。stopwords 类已经包含了这些单词,所以我不必手动输入它们。我使用了一个 for 循环来告诉机器,如果这个单词不在 stopwords 类中,我们需要把它取出来。
PorterStemmer
允许我们提取一个单词的词干,并将其归类为相似单词的常见预测值。例如,在第一个评论中,“Lov”是单词“loving”或“loved”的词干,这两个单词本质上都转化为积极的评论,因此允许我们的模型有更少的单词。
最后,为了将预处理步骤应用于我们数据集中的所有 1000 条评论,我在文本处理步骤之前添加了另一个 for 循环。
|| VI || 创建单词袋模型
cv = CountVectorizer(max_features = 1500)X = cv.fit_transform(corpus).toarray()
y = dataset.iloc[:, 1].values
单词袋模型允许我们从文本数据中提取特征,在本例中,我们从每个观察结果中提取所有单词,并将它们集中在一个“袋”中,通过不计算重复项来减少冗余。我是通过从sklearn
导入CountVectorizer
类来做到这一点的。
每个单词以某种方式形成自己的列,因为有这么多单词,我们可以有大量的列。然而,我使用CountVectorizer
类的max_features
参数指定了最大列数。
然后,我所要做的就是将单词列与 X(输入)变量相匹配,并将 y(输出)变量指定为数据集中的第二列,这将根据评论是正面还是负面分别给出 1 或 0。
|| VII || 分成训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)
然后,我必须将数据集分为训练集和测试集,并使用 0.2 的测试大小,这样我们就有 800 个值来训练数据集,200 个值来测试数据集。
下面的代码向我们展示了训练和测试集的样子
X_train[0, 0:10] *#First 10 rows of the first column of X_train.***Output**: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int64)X_test[0, 0:10] *#First 10 rows of the first column of X_test.***Output**: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=int64)y_train[:10] *#First 10 values of the first column of y_train.***Output**: array([1, 1, 1, 0, 1, 0, 1, 0, 0, 0])y_test[:10] *#First 10 values of the first column of y_test.***Output**: array([0, 0, 0, 0, 0, 0, 1, 0, 0, 1])
现在我已经完成了所有的预处理步骤,我将开始对 model 应用一些分类算法,以帮助它预测评论。
|| VIII || 朴素贝叶斯模型
我使用的第一个模型是朴素贝叶斯模型。在机器学习中,朴素贝叶斯分类器是一系列简单的“概率分类器”,基于应用贝叶斯定理,在特征之间具有强(朴素)独立性假设。
给定类变量,所有朴素贝叶斯分类器都假设特定特征的值独立于任何其他特征的值。在我们的模型中,朴素贝叶斯算法根据输出集,查看评论的特定关键字来描述它是正面还是负面的。
在下面的代码中,我导入了 GaussianNB 类,它假设我们的数据是正态分布的(具有高斯钟形曲线)。
VIII~i ||将朴素贝叶斯拟合到训练集
classifier = GaussianNB()
classifier.fit(X_train, y_train)**Output**: GaussianNB(priors=None, var_smoothing=1e-09)
VIII~ii ||预测测试集结果
我创建了y_pred_NB
,这是我们的模型的预测将被存储的地方。下面的输出向我们展示了预测矩阵的样子。它是一堆 1 和 0,就像我们的y_train
数据集。这些是我们的模型做出的预测。y_pred_NB
可以与y_test
进行比较,并确定我们的模型有多精确。
y_pred_NB = classifier.predict(X_test)
y_pred_NB**Output**: array([1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1])
VIII~iii ||制作混淆矩阵
朴素贝叶斯的混淆矩阵在左上角显示我们的真阴性,在右上角显示假阴性,在右下角显示真阳性,在左下角显示假阳性。
下面,我用缩写对每个数字进行了编码,T 代表真,F 代表假,N 代表负,P 代表正。
cm_NB = confusion_matrix(y_test, y_pred_NB)
cm_NB**Output**: array([[55, 42],[12, 91]])TP_NB = 91 *#True Positives (Naive Bayes)*TN_NB = 55 *#True Negatives (Naive Bayes)*FP_NB = 12 *#False Positives (Naive Bayes)*FN_NB = 42 *#False Negatives (Naive Bayes)*
下面,我将使用真/假阳性和阴性来计算准确度、精确度、召回率和 F1 分数。
VIII~iv ||朴素贝叶斯算法的精度
准确性顾名思义。它通过将真实预测相加并除以预测总数来衡量准确性。
Accuracy_NB = (TP_NB + TN_NB) / (TP_NB + TN_NB + FP_NB + FN_NB) Accuracy_NB **Output**: 0.73
VIII~v || 朴素贝叶斯算法的精度
精度是指两个或多个测量值之间的接近程度。它是通过将真阳性除以总阳性来计算的
Precision_NB = TP_NB / (TP_NB + FP_NB)Precision_NB**Output**: 0.883495145631068
VIII~vi ||回忆朴素贝叶斯算法
回忆是正确预测的正面观察与实际类中所有观察的比率。将真阳性除以真阳性和假阴性之和。
Recall_NB = TP_NB / (TP_NB + FN_NB)Recall_NB**Output**: 0.6842105263157895
VIII~vii ||朴素贝叶斯算法的 F1 得分
F1 分数是精确度和召回率的加权平均值。如果我们需要在精确度和召回率之间寻求平衡,并且存在不均匀的类别分布,F1 分数可能是一个更好的衡量标准。它的计算方法是将精度和召回率相乘,将结果除以精度和召回率之和,然后将最终结果乘以 2。
F1_Score_NB = 2 * Precision_NB * Recall_NB / (Precision_NB + Recall_NB) F1_Score_NB**Output**: 0.7711864406779663
|| IX || 决策树
我使用的下一个算法是决策树。决策树允许您开发分类系统,该系统基于一组决策规则对未来的观察结果进行预测或分类。
IX~i || 将决策树分类拟合到训练集
classifier = DecisionTreeClassifier(criterion = ‘entropy’, random_state = 0)classifier.fit(X_train, y_train)**Output:** DecisionTreeClassifier(class_weight=None, criterion=’entropy’,max_depth=None,max_features=None,max_leaf_nodes=None,min_impurity_decrease=0.0,min_impurity_split=None,min_samples_leaf=1,min_samples_split=2,min_weight_fraction_leaf=0.0, presort=False, random_state=0,splitter=’best’)
IX~ii ||预测测试集结果
In [59]: y_pred_DT = classifier.predict(X_test)y_pred_DT**Output**: array([0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0,1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0,0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1,0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1,0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1,0, 0])
IX~iii ||制作混淆矩阵
cm_DT = confusion_matrix(y_test, y_pred_DT) cm_DT**Output**: array([[74, 23],[35, 68]])TP_DT = 68 *#True Positives (Decision Tree)*TN_DT = 74 *#True Negatives (Decision Tree)*FP_DT = 35 *#False Positives (Decision Tree)*FN_DT = 23 *#False Negatives (Decision Tree)*
IX~iv ||决策树算法的准确性
Accuracy_DT = (TP_DT + TN_DT) / (TP_DT + TN_DT + FP_DT + FN_DT)Accuracy_DT**Output**: 0.71
IX~v ||决策树算法的精度
Precision_DT = TP_DT / (TP_DT + FP_DT)Precision_DT**Output**: 0.6601941747572816
IX~vi ||召回决策树算法
Recall_DT = TP_DT / (TP_DT + FN_DT)Recall_DT**Output**: 0.7472527472527473
IX~vii ||决策树算法的 F1 得分
F1_Score_DT = 2 * Precision_DT * Recall_DT / (Precision_DT + Recall_DT)F1_Score_DT**Output**: 0.7010309278350515
|| X ||随机森林
最后,我使用了随机森林算法,这只是一些决策树的组合。在我的例子中,我选择使用 300 棵树,但是我可以根据我想要的模型精度来改变这个数字。
X~I | |将随机森林分类拟合到训练集
classifier = RandomForestClassifier(n_estimators = 300, criterion = ‘entropy’, random_state = 0)classifier.fit(X_train, y_train)**Output**: RandomForestClassifier(bootstrap=True, class_weight=None, criterion=’entropy’,max_depth=None,max_features=’auto’,max_leaf_nodes=None,min_impurity_decrease=0.0,min_impurity_split=None,min_samples_leaf=1, min_samples_split=2,min_weight_fraction_leaf=0.0, n_estimators=300, n_jobs=None,oob_score=False, random_state=0, verbose=0, warm_start=False)
X~ii ||预测测试集结果
y_pred_RF = classifier.predict(X_test)y_pred_RF**Output**: array([0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0])
X~iii ||制作混淆矩阵
cm_RF = confusion_matrix(y_test, y_pred_RF) cm_RF**Output**: array([[87, 10],[47, 56]])*# Calculating True/False Positives/Negatives* TP_RF = 56 *#True Positives (Random Forest)* TN_RF = 87 *#True Negatives (Random Forest)* FP_RF = 47 *#False Positives (Random Forest)* FN_RF = 10 *#False Negatives (Random Forest)*
X~iv ||随机森林的精度
Accuracy_RF = (TP_RF + TN_RF) / (TP_RF + TN_RF + FP_RF + FN_RF)Accuracy_RF**Output**: 0.715
x~v | |随机森林的精度
Precision_RF = TP_RF / (TP_RF + FP_RF)Precision_RF**Output**: 0.5436893203883495
X~vi ||随机森林的回忆
Recall_RF = TP_RF / (TP_RF + FN_RF)Recall_RF**Output**: 0.8484848484848485
X~vii ||随机森林 F1 得分
F1_Score_RF = 2 * Precision_RF * Recall_RF / (Precision_RF + Recall_RF)F1_Score_RF**Output**: 0.6627218934911243
XI ||对比模型
在最后一部分,我将比较我使用的每种算法的准确度、精确度、召回率和 F1 值。我将把它们绘制在条形图上,以图形方式展示不同型号之间的比较。
Xi~I | |比较模型精度
Accuracy = [Accuracy_RF, Accuracy_DT, Accuracy_NB]
Methods = [‘Random_Forest’, ‘Decision_Trees’, ‘Naive_Bayes’]
Accuracy_pos = np.arange(len(Methods))plt.bar(Accuracy_pos, Accuracy)
plt.xticks(Accuracy_pos, Methods)
plt.title(‘comparing the accuracy of each model’)plt.show()
正如我们在上面的柱状图中看到的,朴素贝叶斯在所有算法中具有最高的准确性,有 73%的正确预测。决策树和随机森林算法也很接近,准确率分别为 71%和 71.5%。
XI~ii ||比较模型精度
Precision = [Precision_RF, Precision_DT, Precision_NB]
Precision_pos = np.arange(len(Methods))plt.bar(Precision_pos, Precision)
plt.xticks(Precision_pos, Methods)
plt.title(‘comparing the precision of each model’)plt.show()
朴素贝叶斯是最精确的模型,精度为 88.35%,而决策树的精度为 66%。随机森林的准确率最低,约为 54.4%。
XI~iii ||对比车型召回
Recall = [Recall_RF, Recall_DT, Recall_NB]
Recall_pos = np.arange(len(Methods))plt.bar(Recall_pos, Recall)
plt.xticks(Recall_pos, Methods)
plt.title(‘comparing the recall of each model’)plt.show()
随机森林的召回率最高,约为 84.8%,而决策树的召回率为 74.7%。朴素贝叶斯的召回率最低,为 68.4%
XI~iv ||对比 F1 车型分数
F1_Score = [F1_Score_RF, F1_Score_DT, F1_Score_NB]F1_Score_pos = np.arange(len(Methods))plt.bar(F1_Score_pos, F1_Score)plt.xticks(F1_Score_pos, Methods)plt.title(‘comparing the F1 Score of each model’)plt.show()
- 朴素贝叶斯的 F1 值最高,为 77.1%
- 决策树的 F1 值为 70.1%。
- 随机森林的 F1 得分最低,为 66.2%
十二||结论
平均而言,我们的模型大约有 71.8%的准确率。虽然这可能意味着机器无法准确预测每一篇评论,但它也向我们展示了我们的模型没有过度拟合数据的证据。过度拟合是一种建模错误,发生在函数与有限的一组数据点过于接近的时候。过度拟合模型通常发生在使用过于复杂的模型来解释数据中的特质时。然而,由于我们的模型正在被训练成像人脑一样思考,因此可以公平地假设,即使是人类也无法 100%地预测评论是正面还是负面的。这实际上取决于数据及其处理方式。
那么这个模型的最佳算法是什么呢?在本项目使用的 3 种算法中,最准确和精确的是朴素贝叶斯算法。
然而,我们的随机森林算法的召回率是最高的。这意味着随机森林算法实际上通过将它标记为阳性(真阳性)来计算我们的模型捕获了多少实际阳性。当存在与假阴性相关联的高成本时,这将是我们用来选择最佳模型的良好指标。
事实存在于我们的 F1 分数中,这实际上可能是在所选的三个模型中哪个模型最好的最佳预测器。朴素贝叶斯算法具有最高的 F1 分数,这意味着它定义了特定模型的召回率和精确度之间的关系。如果我们需要在精确度和召回率之间寻求平衡,并且如果存在不均匀的类别分布(大量实际否定),F1 分数可能是更好的衡量标准。
感谢阅读!希望你学到了有用的东西。
关注 Rohan Gupta,了解更多与数据科学和机器学习相关的内容
将人脸识别构建为 REST API
有很多开源的人脸识别包,比如 face_recognition ,你可以很容易地将它们安装在 Linux 服务器上。但是在移动和物联网设备上部署它们是非常困难或者不可能的。一种选择是使用机器学习移动框架,如 TensorFlow Lite 来调用预先训练好的模型。
但是有更简单的选择吗?是啊!随着 5G 的到来,上传 100KB 的图像只需 0.01 秒,速度约为 100Mbps ,因此我们可以部署几乎所有东西,包括服务器端的人脸识别服务和客户端的轻量级应用。这篇文章将展示如何使用 Python Flask 在 Linux 服务器上为 face_recognition 构建一个 RESTful API。
人脸识别项目
face_recognition 是一个基于 dlib 的非常棒的人脸识别开源项目,正如它自己所描述的:
世界上最简单的 Python 面部识别 api 和命令行
只需几行 Python 代码,就可以进行人脸比对、人脸检测、面部特征查找。
例如,运行示例find _ face _ features _ in _ picture . py(只有 40 行代码)获取一幅奥巴马的图像,您可以得到如下绘制的所有面部特征。
要在 Linux 服务器上安装它,你可以遵循 Github 上 face_recognition 项目中的步骤,或者只需下载预配置的虚拟机。对于 Windows 用户,Sasiwut Chaiyadecha 提供的步骤对我很管用。
构建 REST API
让我们使用 face_recognition 包定义两个 API。
- 对比两张脸:上传两张图片,返回真/假进行匹配
- **从已知的数据集中识别一张脸:**上传一张图片并返回这个人的名字。
面部识别功能
在文件 face_util.py 中为这两个 API 定义了两个人脸识别函数作为 util。
第一个函数 compare_faces 比较两个图像文件,如果两个图像中的人脸是同一个人,则返回 True。
face_util_part1.py
第二个函数 face_rec 检查图像文件中的人脸是否是数据集中的已知人脸,并返回人名。在这个例子中有两个已知的面。
face_util_part2.py
带烧瓶的 REST API
有了上面的函数,我们可以很容易地用 Flask 定义 REST API,如下。
第一个 API 是 face_match 。它从 POST 请求的表单数据中获取两个图像文件,并调用 compare_faces 来检查它们是否匹配,然后返回一个 JSON 格式的结果。
flask_server_v1_part1.py
第二个 API 是 **face_rec,**它取一个图像文件作为输入,调用 face_rec 检查是否是已知人脸,然后以 JSON 格式返回人名。
flask_server_v1_part2.py
你可以在这里下载完整的 flask_server_v1.py 文件。
通过pip install -U flask
安装烧瓶模块,然后运行python flask_server_v1.py
启动服务器 API。启动输出如下所示。
* Serving Flask app "flask_server_v1" (lazy loading)
...
* Debug mode: on
* Running on [http://0.0.0.0:5001/](http://0.0.0.0:5001/) (Press CTRL+C to quit)
您可以使用以下 URL 以 POST 表单数据格式访问这两个 API:
- 比较两张脸 : http:// <服务器 IP 地址> : 5001/face_match
- 认个脸: http:// <服务器 IP 地址> : 5001/face_rec
REST API 客户端示例
您可以使用任何编程语言调用 API。我只是以 Python 为例。它需要请求模块,你可以通过pip install -U requests
安装。
在第一个例子中,我们用同一个人的两幅图像调用 face_match API。
demo_client_part1.py
通过python demo_client_part1.py
运行示例,您将得到如下响应:
{"match": true}
在第二个例子中,我们调用 face_rec API 来查找一个人的名字。
demo_client_part2_v1.py
运行脚本,您将得到如下响应:
{"name": "Obama"}
了解发布表单数据
如果你已经知道 HTML 表单数据,跳过这一节。
表单数据只不过是用户提交给服务器端的 HTML 表单。检查不同的 HTML 表单。例如,一个简单的输入表单如下所示。
当您单击 Submit 时,它实际上向 web 服务器发送一个 HTTP POST 请求,其中包含如下数据:
在我们的例子中,我们使用文件形式。为了更好地理解它,让我们修改 face_rec API,以便当用户通过 GET 访问 API 时返回一个 HTML 表单页面,即在 web 浏览器中键入 URL,当请求内容是 POST 时打印它。
首先,我们定义了一个新函数 print_request() 来打印 POST 请求的 URL、键头和正文内容。应用了一个技巧,用字符串’< raw image data >'替换 raw image data,这样我们就可以打印完整的 POST 请求。
flask_server_v2_part1.py
然后我们修改 face_rec API 如下:
- 在方法中添加 GET 支持。(第一行)
- 如果是 POST,打印请求。(第 3 行)
- 如果是 GET,返回一个 HTML 文件表单页面。(第 12–20 行)
flask_server_v2_part2.py
你可以在这里下载完整的 flask_server_v2.py 。
运行新的服务器 API python flask_server_v2.py
,然后打开 web 浏览器,输入 face_rec URL,例如 http://127.0.0.1: 5001/face_rec。
你会看到上面的网页,点击浏览选择一个图像文件,然后点击上传按钮。它将向服务器端发送一个包含所选文件的 POST 请求,即调用 face_rec API。因此您将在浏览器上获得 API 响应。
同时,POST 请求内容将在服务器端打印出来,如下所示。
Base64 图像编码
表单数据传输图像数据是有效的,因为数据是直接以原始二进制传输的。但是如果你想以 JSON body 格式传输图像, base64 编码是一种流行的解决方案,它将二进制数据转换成可打印的 ASCII 文本字符串,但是有 33%的开销,即(8–6)/6。
有了 Python base64 库,简直易如反掌。只需调用 b64encode 和 b64decode 函数。但是有一个技巧,你需要将 b64encode 的字节字符串输出转换为普通字符串,例如 b’abc ‘转换为’ abc ',然后再将其传递给 JSON 对象,因为 JSON 不支持 Python 字节字符串。
我们可以如下使用 JSON 格式重新编写 face_rec API 客户端。
base64 encode client
你可以在这里下载完整的客户端脚本 v3 。
并相应地修改服务器端以支持 JSON 格式,如下所示。
base64 encode REST API server
我们将 base64 字符串解码回二进制数据,并将其保存为图像文件,然后调用我们在基于表单数据的 API 中使用的相同的 face_rec 函数。比较帖子内容长度,你会看到 33%的开销如下。
Peter2.jpg file size: 89KBPost form data content length: 91162Post base64 data content length: 121396(121396 - 91162) / 91162 = 33%
你可以在这里找到完整的服务器端脚本 v3。
进一步的工作
您可以向 API 添加更多功能,例如,返回面部位置和面部特征,这样您就可以在移动设备上实现与在 Linux 服务器上相同的功能。
我已经实现了一个使用 REST API 绘制面部位置和面部特征的版本,并带有额外的参数,如果感兴趣,你可以在这里下载完整的库。
感谢阅读到此结束。
自己构建—使用 Keras/TensorFlow 模型的聊天机器人 API
在 Keras/TensorFlow 模型上构建简单聊天机器人的分步解决方案及源代码
Source: Pixabay
构建自己的聊天机器人(或助手,这个词是聊天机器人的一个新的流行术语)并不像你想象的那么复杂。各种聊天机器人平台正在使用分类模型来识别用户意图。虽然很明显,在现有平台的基础上构建聊天机器人时,你会得到一个很好的提示,但研究背景概念并尝试自己构建它不会有什么坏处。为什么不自己用一个类似的模型。聊天机器人实施的主要挑战是:
- 对用户输入进行分类以识别意图(这可以通过机器学习来解决,我正在使用带有 TensorFlow 后端的 Keras)
- 保持上下文。这部分是编程,这里没有什么 ML 相关的。我使用 Node.js 后端逻辑来跟踪对话上下文(在上下文中,通常我们不需要对用户意图进行分类——用户输入被视为聊天机器人问题的答案)
我的 GitHub repo(开源)上提供了本文的完整源代码和自述说明。
这是实现中使用的 Python 库的列表。 Keras 深度学习库用于建立分类模型。Keras 在 TensorFlow 后端上运行培训。兰开斯特词干库用于折叠不同的单词形式:
import nltk
from nltk.stem.lancaster import LancasterStemmer
stemmer = LancasterStemmer()# things we need for Tensorflow
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.optimizers import SGD
import pandas as pd
import pickle
import random
聊天机器人的意图和学习模式在普通的 JSON 文件中定义。不需要有庞大的词汇量。我们的目标是建立一个特定领域的聊天机器人。也可以为小词汇量创建分类模型,它将能够识别为训练提供的一组模式:
chatbot training data
在我们开始分类模型训练之前,我们需要首先建立词汇。模式被处理以构建词汇表。每个单词被词干化以产生通用词根,这将有助于覆盖用户输入的更多组合:
words = []
classes = []
documents = []
ignore_words = ['?']
# loop through each sentence in our intents patterns
for intent in intents['intents']:
for pattern in intent['patterns']:
# tokenize each word in the sentence
w = nltk.word_tokenize(pattern)
# add to our words list
words.extend(w)
# add to documents in our corpus
documents.append((w, intent['tag']))
# add to our classes list
if intent['tag'] not in classes:
classes.append(intent['tag'])# stem and lower each word and remove duplicates
words = [stemmer.stem(w.lower()) for w in words if w not in ignore_words]
words = sorted(list(set(words)))# sort classes
classes = sorted(list(set(classes)))# documents = combination between patterns and intents
print (len(documents), "documents")
# classes = intents
print (len(classes), "classes", classes)
# words = all words, vocabulary
print (len(words), "unique stemmed words", words)
这是词汇创造的输出。共有 9 个意图(类别)和 82 个词汇:
45 documents
9 classes ['adverse_drug', 'blood_pressure', 'blood_pressure_search', 'goodbye', 'greeting', 'hospital_search', 'options', 'pharmacy_search', 'thanks']
82 unique stemmed words ["'s", ',', 'a', 'advers', 'al', 'anyon', 'ar', 'awesom', 'be', 'behavy', 'blood', 'by', 'bye', 'can', 'caus', 'chat', 'check', 'could', 'dat', 'day', 'detail', 'do', 'dont', 'drug', 'entry', 'find', 'for', 'giv', 'good', 'goodby', 'hav', 'hello', 'help', 'hi', 'hist', 'hospit', 'how', 'i', 'id', 'is', 'lat', 'list', 'load', 'loc', 'log', 'look', 'lookup', 'man', 'me', 'mod', 'nearby', 'next', 'nic', 'of', 'off', 'op', 'paty', 'pharm', 'press', 'provid', 'react', 'rel', 'result', 'search', 'see', 'show', 'suit', 'support', 'task', 'thank', 'that', 'ther', 'til', 'tim', 'to', 'transf', 'up', 'want', 'what', 'which', 'with', 'you']
训练不会基于单词的词汇来运行,单词对于机器来说是没有意义的。我们需要将单词翻译成包含 0/1 数组的单词包。数组长度将等于词汇大小,当当前模式中的一个单词位于给定位置时,将设置 1:
# create our training data
training = []
# create an empty array for our output
output_empty = [0] * len(classes)# training set, bag of words for each sentence
for doc in documents:
# initialize our bag of words
bag = []
# list of tokenized words for the pattern
pattern_words = doc[0]
# stem each word - create base word, in attempt to represent related words
pattern_words = [stemmer.stem(word.lower()) for word in pattern_words]
# create our bag of words array with 1, if word match found in current pattern
for w in words:
bag.append(1) if w in pattern_words else bag.append(0)
# output is a '0' for each tag and '1' for current tag (for each pattern)
output_row = list(output_empty)
output_row[classes.index(doc[1])] = 1
training.append([bag, output_row])# shuffle our features and turn into np.array
random.shuffle(training)
training = np.array(training)# create train and test lists. X - patterns, Y - intents
train_x = list(training[:,0])
train_y = list(training[:,1])
训练数据— X(转换为数组[0,1,0,1…,0]的模式),Y(转换为数组[1,0,0,0,…,0]的 intents,intents 数组将只有一个 1)。模型是用 Keras 构建的,基于三层。根据我的实验,三层提供了良好的结果(但这都取决于训练数据)。分类输出将是多类数组,这将有助于识别编码意图。使用 softmax 激活生成多类分类输出(结果返回 0/1 的数组:[1,0,0,…,0] —该集合标识编码意图):
# Create model - 3 layers. First layer 128 neurons, second layer 64 neurons and 3rd output layer contains number of neurons
# equal to number of intents to predict output intent with softmaxmodel = Sequential()
model.add(Dense(128, input_shape=(len(train_x[0]),), activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(len(train_y[0]), activation='softmax'))
使用 SGD 优化器编译 Keras 模型:
# Compile model. Stochastic gradient descent with Nesterov accelerated gradient gives good results for this modelsgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])
拟合模型—执行训练并构建分类模型。我在 200 次迭代中执行训练,批量大小= 5:
# Fit the modelmodel.fit(np.array(train_x), np.array(train_y), epochs=200, batch_size=5, verbose=1)
模型已建立。现在我们可以定义两个辅助函数。函数 bow 帮助将用户句子翻译成数组为 0/1 的单词包:
def clean_up_sentence(sentence):
# tokenize the pattern - split words into array
sentence_words = nltk.word_tokenize(sentence)
# stem each word - create short form for word
sentence_words = [stemmer.stem(word.lower()) for word in sentence_words]
return sentence_words# return bag of words array: 0 or 1 for each word in the bag that exists in the sentence
def bow(sentence, words, show_details=True):
# tokenize the pattern
sentence_words = clean_up_sentence(sentence)
# bag of words - matrix of N words, vocabulary matrix
bag = [0]*len(words)
for s in sentence_words:
for i,w in enumerate(words):
if w == s:
# assign 1 if current word is in the vocabulary position
bag[i] = 1
if show_details:
print ("found in bag: %s" % w)return(np.array(bag))
看看这个例子——把这个句子翻译成一个单词包:
p = bow("Load blood pessure for patient", words)
print (p)
print (classes)
当该函数在 chatbot 词汇表的句子中找到一个单词时,它会在数组中的相应位置设置 1。该数组将被发送到模型进行分类,以识别其所属的意图:
found in bag: load
found in bag: blood
found in bag: for
found in bag: paty
[0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
将训练好的模型保存到 pickle 文件中是一个很好的做法,以便能够重用它来通过 Flask REST API 发布:
# Use pickle to load in the pre-trained model
global graph
graph = tf.get_default_graph()with open(f'katana-assistant-model.pkl', 'rb') as f:
model = pickle.load(f)
在通过 Flask REST API 发布模型之前,运行一个额外的测试总是好的。使用 model.predict 函数对用户输入进行分类,并根据计算出的概率返回意图(可以返回多个意图):
def classify_local(sentence):
ERROR_THRESHOLD = 0.25
# generate probabilities from the model
input_data = pd.DataFrame([bow(sentence, words)], dtype=float, index=['input'])
results = model.predict([input_data])[0]
# filter out predictions below a threshold, and provide intent index
results = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]
# sort by strength of probability
results.sort(key=lambda x: x[1], reverse=True)
return_list = []
for r in results:
return_list.append((classes[r[0]], str(r[1])))
# return tuple of intent and probability
return return_list
句子分类示例:
classify_local('Fetch blood result for patient')
意图计算正确:
found in bag: blood
found in bag: result
found in bag: for
found in bag: paty[('blood_pressure_search', '1.0')]
为了通过 REST 端点发布相同的函数,我们可以将包装到 Flask API 中:
app = Flask(__name__)
CORS(app)[@app](http://twitter.com/app).route("/katana-ml/api/v1.0/assistant", methods=['POST'])
def classify():
ERROR_THRESHOLD = 0.25
sentence = request.json['sentence']
# generate probabilities from the model
input_data = pd.DataFrame([bow(sentence, words)], dtype=float, index=['input'])
results = model.predict([input_data])[0]
# filter out predictions below a threshold
results = [[i,r] for i,r in enumerate(results) if r>ERROR_THRESHOLD]
# sort by strength of probability
results.sort(key=lambda x: x[1], reverse=True)
return_list = []
for r in results:
return_list.append({"intent": classes[r[0]], "probability": str(r[1])})
# return tuple of intent and probability
response = jsonify(return_list)
return response# running REST interface, port=5000 for direct test, port=5001 for deployment from PM2
if __name__ == "__main__":
app.run(debug=False, host='0.0.0.0', port=5001)
我已经解释了如何实现分类部分。在帖子开头引用的 GitHub repo 中,你会找到如何维护上下文的完整例子。上下文由用 JavaScript 编写并运行在 Node.js 后端的逻辑维护。一旦意图被分类并且后端逻辑找到了上下文的起点,就必须在意图列表中定义上下文流——我们进入循环并询问相关的问题。上下文处理有多高级完全取决于后端实现(这超出了现阶段机器学习的范围)。
聊天机器人用户界面:
Chatbot UI implemented with Oracle JET