代码详情|如何快速查找硬盘中的小电影?
看电影还要找豆瓣?别人的喜好怎能左右你的欢心~ 豆瓣评分不靠谱,关键时刻得自己动手!
本文将手把手教你打造一个专属电影机器人,它能根据你的要求来推荐电影。科幻悬疑恐怖还是爱情文艺小清新,统统hold住!
本文基于SAP Conversational AI 来构建模型,并通过电影数据库 来获取电影信息。和简单的Q / A聊天机器人相比,与第三方API交互能实现更多有趣的案例。通过Bot Skills,我们添加了直接从构建器调用webhooks的选项,非常简单。
今天的机器人学习,分为以下步骤: 1.在句子中提取关键信息 2.构建机器人流程(触发,需求,操作) 3.创建并连接能从电影数据库中获取数据的 API
你需要一个SAP Conversational AI 帐户 ,Node.js ,测试时可能还需要Ngrok 。
1.在句中提取关键信息
“意图机制”有助于确定句子的整体含义。对实际运用而言,仅仅知道用户想看“某些”是不够的,我们需要知道用户想看“什么”。
“实体”被设计出来,正是旨在解决这个问题:它们可以在句子中提取关键信息。“意图机制”让你明白你必须做什么,而“实体”帮助你怎么做。
假设你是一家提供电话和互联网接入的电信公司,而机器人的意图是了解人们何时抱怨断网:
“实体”就可以从句中提取出关键信息。提取出的字符有助于了解“什么、哪里、何时”出了问题。
对于电影机器人,我们将尝试提取3关键信息块:
- 用户想看什么(电影还是电视节目)
- 用户想看的电影类别
- 语种
2.使用“黄金实体”
为了加速开发进程,SAP Conversational AI可以默认提取多个实体:日期、位置、电话号码等。
在“language”实体中,名称旁的小星星就是黄金实体与普通实体的区别。
我们可以用这个实体解决第三个要求:电影语种。
3.创建自定义实体
创建自定义实体,可以有效提取我们所需要的信息。与“意图”一样,训练非常重要:添加到机器人的示例越多,获得的样本准确性就越高。
你可以通过多种意图对实体进行训练: 对电影机器人而言,只需要1个意图discover和2个实体: • recording并识别用户想看电影还是电视节目 • genre(观看类别)
打开你的意图“discover”,添加表达式,一定要确保表达式包含了所有可能情况,如: • 无实体:“我男朋友今晚想看点东西” • 一个实体:“我想看一部电影” • 多个实体:“你能给我推荐一些法国戏剧电视节目吗?”
要想标记表达式,请选择要标记的文本,并输入实体名称。
15个示例虽然可以,但应该添加更多的示例。生产就绪的机器人需要至少50个示例,才能表现良好。为了加快过程,你可以把机器人 中构建的实体[记录实 体 ,派别 实体 ]进行分叉,然后在机器中发现意图 。
你可以发现,在这里“法国”被检测为国籍,而不是语种,因为这就是实体在这种情况下的含义。在构建bot流程时,我们将确保检查这两个实体。
4.添加自定义进行丰富
我们已经标记了实体,现在让它们更丰富一些吧! 在训练选项卡下打开机器人中的实体面板,如下所示:
现在,打开genre实体。如果你注意一下面板的右上角,你会看到一个切换:free - restricted and settings。打开它,以便我们对你可以访问的不同选项进行详细解释:
在实体面板中,你可以访问实体的不同选项:
• Free与Restricted——你没有严格的值列表,但你希望机器学习能检测到所有可能的值,这时使用免费的(Free)自定义实体。然而,如果你有严格的单词列表要检测,则使用受限制的自定义实体不需要自动检测实体。 • 模糊匹配——模糊匹配是0和1之间的索引,用于表示单词与实体值列表中单词的接近程度。如果单词在此索引之外,平台则会按照列表中最接近的值,对它进行标记。 • 值列表——你可以在此处添加实体的所有值列表,可以是不同的值,或者同义词。
在我们的例子中,我们的genre实体将受到限制,因为Movie Database API仅管理特定的类别列表,如下:
{ id: 28, name: 'Action' },
{ id: 12, name: 'Adventure' },
{ id: 16, name: 'Animation' },
{ id: 35, name: 'Comedy' },
{ id: 80, name: 'Crime' },
{ id: 99, name: 'Documentary' },
{ id: 18, name: 'Drama' },
{ id: 10751, name: 'Family' },
{ id: 14, name: 'Fantasy' },
{ id: 36, name: 'History' },
{ id: 27, name: 'Horror' },
{ id: 10402, name: 'Music' },
{ id: 9648, name: 'Mystery' },
{ id: 10749, name: 'Romance' },
{ id: 878, name: 'Science Fiction' },
{ id: 53, name: 'Thriller' },
{ id: 10752, name: 'War' },
{ id: 37, name: 'Western' }
把所有的类型添加到我们的值列表中,但不要忘记添加同义词,如科幻小说(SF,Sci-Fi),浪漫主义(Romantic)或卡通动画(AnimatedCartoon)等。你会发现,正如JSON中那样,会有一系列ID与类型相关联,因为电影数据库无法根据英文名称搜索特定类型,而只能搜索自定义数字。我们可以做的,就是为每个类型值关联一个特定的id,它将在NLP API的JSON中执行返回,这样我们就可以将它传递给Movie Database API。这就是丰富自定义的目的:每当检测到实体时,从NLP API返回的JSON都会添加有关该实体的信息。
在自定义的面板中,我们需要创建3个键: • name——在同一值下映射同义词 • id——丰富电影数据库的id • article——添加该类型的文章(稍后我们将用到它)
要添加自定义,请单击add new key 并添加上面列出的三个键——关于article,将默认键值设置为“a”,因为大多数类型都使用“a”。在name中,你可以开始添加特定的内容并将所有不同的值映射到name、ID、article中,如下所示:
你可以从此页面开始,分叉整个实体,其中包括丰富的自定义部分。既然已经完成了,就可以在测试控制台中测试一下。假设发送句子“我想看动画电影”,你应该可以看到以下自定义内容:
"genre": [
{
"value": "animated",
"raw": "animated",
"confidence": 0.99,
"name": "animation",
"id": 16,
"article": "an"
}
现在这些添加为我们提供了通用名称,ID和Article。我们会以同样的方法操作录音实体。返回实体面板并单击录音,然后进行限制,并为电视节目和电影添加所有可能的值和同义词(如tv shows, shows, motion picture, film, films, movies等等)。
现在转到自定义丰富界面并添加Key选项,赋2个特定值: • movie -所有电影的同义词 • tv - 所有电视节目的同义词
就像这样:
发回我们的句子:“我想看一部动画电影”,我们便有了录音的丰富内容:
"recording": [
{
"value": "movie",
"raw": "movie",
"confidence": 0.99,
"type": "movie"
}
]
5.建立你的机器人流程
由于我们只需要在调用Node.JS API之前确保填写所有条件,因此构建部分将非常简单。 我们只需要一种技能 —— Discover。
5.1触发器
如果意图@Discover已经存在,我们将触发它:
5.2要求
此选项卡可帮助你在操作之前收集数据。我们希望确保用户在继续之前指定录音、类别、语种以及意图的是否:
这些要求将被逐一检查,它们都可以在第一条消息上实现。例如,如果用户说“我观看英语犯罪电影”,会立即触发操作。
对于每个要求,你都可以选择在消息完成或缺失时发送消息。
在要求完成后发送消息,可以让机器人更生动:“一部犯罪电影?我也爱他们!”但是,当缺少要求时,它们几乎是强制性的:你需要让用户告诉你想要的内容。
例如,如果缺少#genre,我就会发送带有建议类型的快速回复:
为了确认,我们将使用内存来显示动态消息,以验证用户对意图的选择@yes还是@no:
(使用内存显示动态消息)
一旦4组实体的问题都设置完毕,你就可以进行Actions了。
5.3操作(Actions)
一旦满足要求,如果用户说是,我们将调用API来实际执行搜索,否则我们会重置内存并再次询问用户想观看什么。
如果_memory.no存在,请重置整个内存并发送消息,例如“让我们重新开始,你想看什么?” 如果_memory.yes存在,则创建一个CALL WEHBOOK操作。你可以输入完整的URL(例如:https://mydomainname.com/discover-movies)或相对URL(/ discover-movies)。当你输入相对URL时,SAP Conversational AI将在机器人设置中使用参数Bot Base URL。
接下来,添加操作(actions)UPDATE CONVERSATION> EDIT MEMORY> RESET ALL MEMORY,以在调用完成后清空内存。
(操作)
如果你没有公共服务器,或者你想在开发过程中测试你的机器人,那么ngrok是一个非常方便的工具,它会创建一个公共URL,并将请求转发给你的计算机。
安装后,运行...
ngrok http 5000
并将HTTPS中的转发URL(https://XXX.ngrok.io)复制到机器人设置(“Bot webhook基本URL”字段),对这些URL发出的所有请求都将转发到你计算机的端口5000。 现在,机器人只需要API来获取电影了!
6.创建Movie Bot API
机器人的NodeJS部分非常简单,它将作为SAP会话AI和电影数据库之间的HTTP代理。
当你的应用程序收到来自SAP Conversational AI的请求时,它会根据用户标准向电影数据库发送搜索查询,并将JSON答案格式化为SAP Conversational AI消息格式。
选择1:自动操作
你可以直接从Git存储库复制整个项目:https://github.com/plieb/movie-bot-skills-training .
选择2:手动操作
第一步:支持你的项目
mkdir movie-bot && cd movie-bot
npm init
npm install --save express body-parser axios
touch index.js config.js
mkdir discover-movies && cd discover-movies
touch index.js movieApi.js
cd..
第二步:获取TMDb API令牌 你将需要一个令牌来使用Movie Database API,然后编辑你的config.js文件:
module.exports = {
MOVIEDB_TOKEN: process.env.MOVIEDB_TOKEN || 'PURYOURTOKENHERE',
PORT: process.env.PORT || 5000,
};
第三步:使用Express应用程序填充index.js 让我们创建一个Express应用程序来处理来自SAP Conversational AI的请求。为了更好地组织项目,如第1步所示,我们有一个文件夹/ discover-movies /,包含了我们机器人代码的核心(不是将所有文件放在同一个文件夹中),我们通过loadMovieRoute调用它。
const express = require('express');
const bodyParser = require('body-parser');
const config = require('./config');
const loadMovieRoute = require('./discover-movies');
const app = express();
app.use(bodyParser.json());
loadMovieRoute(app);
app.post('/errors', function(req, res) {
console.log(req.body);
res.sendStatus(200);
});
const port = config.PORT;
app.listen(port, function() {
console.log(`App is listening on port ${port}`);
});
第四步:填写discover-movies / index.js 当用户填写搜索条件时,我们要求SAP Conversational AI向/ discover-movies发送POST请求。
控制器的主要目标是从内存中选择和格式化首选项,以将它们发送到电影数据库的API:
const config = require('../config');
const { discoverMovie } = require('./movieApi');
function loadMovieRoute(app) {
app.post('/discover-movies', function(req, res) {
console.log('[GET] /discover-movies');
const kind = req.body.conversation.memory['recording'].type;
const genre = req.body.conversation.memory['genre'].id;
const language = req.body.conversation.memory['language'];
const nationality = req.body.conversation.memory['nationality'];
const isoCode = language
? language.short.toLowerCase()
: nationality.short.toLowerCase();
return discoverMovie(kind, genreId, isoCode)
.then(function(carouselle) {
res.json({
replies: carouselle,
conversation: {
}
});
})
.catch(function(err) {
console.error('movieApi::discoverMovie error: ', err);
});
});
}
module.exports = loadMovieRoute;
第五步:填写 discover-movies/movieApi.js 现在我们已经提取并格式化了请求的所有过滤器,我们需要将请求发送到电影数据库并把答案格式化:
const axios = require('axios');
const config = require('../config');
function discoverMovie(kind, genreId, language) {
return moviedbApiCall(kind, genreId, language).then(response =>
apiResultToCarousselle(response.data.results)
);
}
function moviedbApiCall(kind, genreId, language) {
return axios.get(`https://api.themoviedb.org/3/discover/${kind}`, {
params: {
api_key: config.MOVIEDB_TOKEN,
sort_by: 'popularity.desc',
include_adult: false,
with_genres: genreId,
with_original_language: language,
},
});
}
function apiResultToCarousselle(results) {
if (results.length === 0) {
return [
{
type: 'quickReplies',
content: {
title: 'Sorry, but I could not find any results for your request :(',
buttons: [{ title: 'Start over', value: 'Start over' }],
},
},
];
}
const cards = results.slice(0, 10).map(e => ({
title: e.title || e.name,
subtitle: e.overview,
imageUrl: `https://image.tmdb.org/t/p/w600_and_h900_bestv2${e.poster_path}`,
buttons: [
{
type: 'web_url',
value: `https://www.themoviedb.org/movie/${e.id}`,
title: 'View More',
},
],
}));
return [
{
type: 'text',
content: "Here's what I found for you!",
},
{ type: 'carousel', content: cards },
];
}
module.exports = {
discoverMovie,
};
第六步:启动吧! 就这样! 准备好测试你的机器人。
运行:启动应用程序 —— node index.js 一切顺利,你应该会看到: App started on port 5000
电影推荐,天气,健康,交通...使用第三方API,一切皆有可能!
留言 点赞 发个朋友圈
我们一起分享AI学习与发展的干货
编译组:姚宇歌、陈孔亮
相关链接:
https://dzone.com/articles/how-to-build-a-movie-bot-using-nodejs
如需转载,请后台留言,遵守转载规范
推荐文章阅读
ACL2018论文集50篇解读
EMNLP2017论文集28篇论文解读
2018年AI三大顶会中国学术成果全链接
ACL2017 论文集:34篇解读干货全在这里
10篇AAAI2017经典论文回顾
长按识别二维码可添加关注
读芯君爱你
上一篇: 如何从在线视频网站下载视频
下一篇: 你正在偷看的小电源正被黑客监控着。
推荐阅读
-
代码详情|如何快速查找硬盘中的小电影?
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
如何在Java中快速创建一个3行4列的矩阵,并通过逐步从键盘获取输入,实现周围元素置零的代码操作
-
如何快速提升网页视频在浏览器中的播放速度:通过直接改动代码来实现
-
用GitHub Copilot快速编写代码,借助正则表达式查找字符串中的信息
-
Grid++Report 锐浪报表开发常见问题解答集锦-报表设计 问:怎样在设计时打印预览报表? 答:为了及时查看报表的设计效果,Grid++Report 报表设计应用程序提供了四种查看视图:普通视图、页面视图、预览视图与查询视图。通过窗口下边的 Tab 按钮可以在四种视图中任意切换。在预览视图中查看报表的打印预览效果,在查询视图中查看报表的查询显示效果。如果在报表的记录集提供了数据源连接串与查询 SQL,在进入预览视图与查询视图时会利用数据源连接串与查询 SQL 从数据源中自动取数,否则 Grid++Report 将自动生成模拟数据进行模拟打印预览与查询显示。注意:在预览视图与查询视图中看到的报表运行结果有可能与在你程序中的最终运行结果有差异,因为在报表的生成过程中我们可以在程序中对报表的生成行为进行一定的控制。 问:怎样用 Grid++Report 设计交叉表? 答:Grid++Report 没有提供专门实现交叉表的功能,其它的报表构件提供的交叉表功能一般也比较死板和功能有限。利用 Grid++Report 的编程接口可以做出灵活多变,功能丰富的交叉表。示例程序 CrossTab 就是一个实现交叉表的例子程序,认真领会此例子程序,你就可以做出自己想要各种交叉表,并能提取一些共用代码,便于重复使用。 问:怎样设置整个报表的缺省字体? 答:设置报表主对象的字体属性,也就是设置了整个报表的缺省字体。如果改变报表主对象的字体属性,则没有专门的设置字体属性的子对象的字体属性也跟随改变。同样每个报表节与明细网格也有字体属性,他们的字体属性也就是其拥有的子对象的缺省字体。 问:怎样在打印时限制一页的输出行数? 答:设定明细网格的内容行的‘每页行数(RowsPerPage)’属性即可。另外要注意‘调节行高(AdjustRowHeight)’属性值:为真时根据页面的输出高度自动调整行的高度,使整个页面的输出区域充满。为假时按设计时的高度输出行。 问:怎样显示中文大写金额? 答:将对象的“格式(Format)”属性设为 “$$” 及可,可以设置格式的对象有:字段(IGRField)、参数(IGRParameter)、系统变量(IGRSystemVarBox)与综合文字框(IGRMemoBox),其中综合文字框是在报表式上设格式。 问:能否实现自定义纸张与票据打印? 答:Grid++Report 完全支持自定义纸张的打印,只要在报表设定时在页面设置中选定自定义纸张,并指定准确的纸张尺寸。当然要在最终输出时得道合适的打印结果,输出打印机必须支持自定义纸张打印。Windows2000/XP/2003 操作系统上可以在打印机上定义自定义纸张,也可以采用这种方式实现自定义纸张打印。 问:怎样实现 0 值不打印? 答:直接设置格式串就可以,在“数字格式”设置对话框中选定“0 不显示”,就会得到合适的格式串。也可以通过直接录入格式串来指定 0 不显示,但格式串必须符合 Grid++Report 的规定格式。另一种实现办法是在报表获取明细记录数据时,在 BeforePostRecord 事件中将值为零的字段设为空,调用字段的 Clear 方法将字段置为空。 问:怎样实现多栏报表? 答:在明细网格上设‘页栏数(PageColumnCount)’属性值大于 1 即可。通过 Grid++Report 的“页栏输出顺序”还可以指定多栏报表的输出顺序是“先从上到下”还是“先从左到右”。 问:如何实现票据套打? 答:Grid++Report 为实现票据套打做了很多专门的安排:报表设计器提供了页面设计模式,按照设定的纸张尺寸显示设计面板,如果将空白票据的扫描图设为设计背景图,在定位报表内容的输出位置会非常方便。报表部件可以设定打印类别,非套打输出的内容在套打打印模式下就不会输出。 问:Grid++Report 有没有横向分页功能? 答:回答是肯定的,在列的总宽度超过打印页面的输出宽度时,Grid++Report 可以另起新页输出剩余的列,如果左边存在锁定列,锁定列可以在后面的新页中重复输出,这样可以保证关键数据列在每一页都有输出。仔细体会 Grid++Report 提供的多种打印适应策略,选用最合适的方式。Grid++Report 的多种打印适应策略为开发动态报表提供了很好的支持。 问:怎样实现报表本页小计功能? 答:定义一个报表分组,将本分组定义为页分组,在本分组的分组头与分组尾上定义统计。页分组就是在每页产生一个分组项,在每页的上端与下端都会分别显示页分组的分组头与分组尾,页分组不用定义分组依据字段。 报表运行 问:怎样与数据库建立连接? 答:如果在设计报表时指定了数据集的数据源连接串与查询 SQL 语句,Grid++Report 采用拉模式直接从数据源取得报表数据,Grid++Report 利用 OLE DB 从数据源取数,OLE DB 提供了广泛的数据源操作能力。如果 Grid++Report 的数据来源采用推模式,即 Grid++Report 不直接与数据库建立连接,各种编程语言/平台都提供了很好的数据库连接方式,并且易于操作,应用程序在报表主对象(IGridppReport)的 FetchRecord 事件中将数据传入,例子程序提供了各种编程语言填入数据的通用方法,对C++Builder 和 Delphi 还进行了专门的包装,直接关联 TDataSet 对象也可以将 TDataSet 对象中的数据传给报表。 问:打印时能否对打印纸张进行自适应?支持表格的折行打印吗? 答:Grid++Report 在打印时采用多种适应策略,通过设置明细网格(IGRDetailGrid)的‘打印策略(PrintAdaptMethod)’属性指定打印策略。(1)丢弃:按设计时列的宽度输出,超出范围的内容不显示。(2)绕行:按设计时列的宽度输出,如果在当前行不能完整输出,则另起新行进行输出。(3)缩放适应:对所有列的输出宽度进行按比例地缩放,使总宽度等于页面的输出宽度。(4)缩小适应:如果列的总宽度小于页面的输出宽度,对所有列的输出宽度进行按比例地缩小,使总宽度等于页面的输出宽度。(5)横向分页:超范围的列在新页中输出。(6)横向分页并重复锁定列。 问:如何改变缺省打印预览窗口的窗口标题? 答:改变报表主对象的‘标题(Title)’属性即可。 问:利用集合对象的编程接口取子对象的接口引用,但不是自己期望的结果。 答:Grid++Report中所有集合对象的下标索引都是从 1 开始,另按对象的名称查找对象的接口引用时,名称字符是不区分大小写的。 问:怎样在运行时控制报表中各个对象的可见性?即怎样在运行时显示或隐藏对象? 答:在报表主对象(GridppReport)的 SectionFormat 事件中设定相应报表子对象的可见(Visible)属性即可。 问:报表主对象重新载入数据,设计器中为什么没有反映新载入的数据? 答:应调用 IGRDesigner 的 Reload 方法。 问:怎样实现不进入打印预览界面,直接将报表打印出来?