displaCy.js:面向现代Web的开源NLP可视化库
随着来自某中心和某机构等公司的服务涌现,如今有一系列优秀的云端API可用于句法依赖分析。这些服务的一个关键部分是交互式演示,用户可以输入句子并查看标注结果。我们很高兴宣布发布displaCy.js,这是一个现代化的、独立于服务的可视化库。我们希望这能让比较不同服务以及探索内部模型变得容易。
更新 (2018年2月) 自 spaCy v2.0 起,displaCy 可视化器已集成到核心库中。它支持在浏览器中提供可视化、生成原始标记或在 Jupyter 笔记本中输出结果。更多详情,请参阅可视化器文档。
displaCy 的历史
我们于 2015 年将 displaCy 作为 NLP 库 spaCy 的可视化器推出,并于 2016 年 8 月开源了代码。
第一版依赖一个旧的 CSS 技巧。新版本使用 SVG 来生成灵活且易于导出的输出。
这里有一个由新版基于SVG的displaCy渲染的句子示例。
在项目中使用 displacy.js
兼容性说明
displaCy 使用 ECMAScript 6 编写。为确保完全的跨浏览器兼容性,请确保使用像 Babel 这样的编译器。更多信息,请参阅此兼容性表。
只需包含 displacy.js 并初始化一个指定 API 和设置的新实例。parse(text, model, settings) 方法将 spaCy 生成的解析结果在容器中渲染为 SVG。默认情况下,它期望使用 spaCy 的服务,您可以免费下载并运行。如果使用某中心的 NLP API,请将 format 设置为 'google'。
// Your API
const api = 'http://localhost:8000'
// Init displaCy
const displacy = new displaCy(api, {
container: '#displacy',
format: 'spacy',
distance: 300,
offsetX: 100,
})
// Parse sentence
displacy.parse('This is a sentence.', 'en', {
collapsePunct: false,
collapsePhrase: false,
color: '#ffffff',
bg: '#000000',
})
displaCy 的工作原理
一个依赖可视化由三个主要组件构成:
- 单词及其对应的词性标签,按顺序水平显示。
- 不同长度的弧线连接两个单词,并带有相应的标签,显示它们的关系类型。
- 箭头头部位于每条弧线的起点或终点,指示其方向。
关于 SVG
可缩放矢量图形格式自 2000 年代初就已存在。与其他图像格式不同,SVG 使用易于使用 CSS 或 JavaScript 操作的 XML 标记。SVG 甚至提供了强大的颜色滤镜和动态裁剪功能,并且随着浏览器支持的改进,已在许多网站上取代了图标字体。
所有三个组件都可以使用 SVG 元素 <path> 和 <text> 实现,使用 <tspan> 分隔文本范围,使用 <textPath> 将弧线标签沿着弯曲的弧线路径包裹。让我们看一下第一个单词 “Robots” 以及将其连接到 “are” 的箭头。这是 displaCy 生成的标记的简化示例:
SVG 标记示例(摘录)
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Token -->
<text y="440" text-anchor="middle">
<tspan x="150" fill="currentColor">Robots</tspan>
<tspan x="150" dy="2em" fill="currentColor">NNS</tspan>
</text>
<!-- Arc -->
<path id="arrow-0" d="M150,400 C150,0 950,0 950,400" stroke-width="2px" stroke="currentColor" fill="none"></path>
<!-- Arc label -->
<text dy="1em">
<textPath xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-0"
startOffset="50%" text-anchor="middle" fill="currentColor">nsubj</textPath>
</text>
<!-- Arrow head -->
<path d="M150,402 L144,392 156,392" fill="currentColor"></path>
</svg>
上面的标记是根据如下所示的 JSON 数据生成的:
API 返回的 JSON 示例(摘录)
{
"arcs": [
{ "dir": "left", "end": 4, "label": "nsubj", "start": 0 }
],
"words": [
{ "tag": "NNS", "text": "Robots" }
]
}
为了将 JSON 格式转换为 SVG 标记,我们需要两个主要函数:一个用来绘制单词,一个用来绘制弧线。
绘制单词
渲染单词相当简单,因为它们独立于整个句子。每个单词需要两个坐标:x(距左侧的距离)和 y(距顶部的距离)。从左侧的一个固定偏移量开始,第一个单词将放置在 offsetX 处,第二个单词在 offsetX + distance 处,第三个单词在 offsetX + 2 * distance 处,依此类推。这可以分解为一个简单的公式:offsetX + i * distance。我们暂时不要太关注 y 坐标,因为它们对于所有组件几乎相同——对于单词,我只是添加了一点间距,以免它们太靠近箭头。
const offsetX = 150; // distance from the left
const distance = 300; // distance between words
// Iterate over words
const markup = words.map(({ text, tag }, i) => `
<text y="${offsetY + wordSpacing}" text-anchor="middle">
<tspan x="${offsetX + i * distance}">${text}</tspan>
<tspan x="${offsetX + i * distance}" dy="2em">${tag}</tspan>
</text>`.trim()).join('');
绘制弧线
每条弧线都带有其起点和终点的索引,因此计算其长度很简单:end - start。这使得弧线的起点为 offsetX + start * distance,终点为 offsetX + (end - start) * distance。现在,如果我们将这些数字添加到路径定义中,我们得到一条连接两点的漂亮的直线:
<path stroke="currentColor" d="M150,400 950,400" />
曲线则稍微复杂一些。对于每条曲线,我们需要在路径定义中添加四个额外的值:左侧和右侧三次贝塞尔曲线控制点的 x 和 y 坐标。为了展示这是如何实现的,我分叉了 SitePoint 的这个很棒的演示。您可以移动控制点,看看它是如何影响 <path> 标记的。
曲线的高度需要适应弧线的长度。跨越三个单词的箭头需要比跨越两个单词的箭头更高——否则它们会重叠。根据句子的语法结构,我们通常会有很多长度为 1 和 2 的弧线,连接单词与其相邻和隔邻的单词,然后是一两条长度为 3 或 4 的箭头,也许还有一个长度为 10 的大箭头。像这样的长依赖关系在关系从句、疑问句和标点符号中尤其常见,并且在像德语这样的语言中也很常见,因为动词和动词前缀通常放在从句的末尾。
Bugfix note 这个问题也是旧版 displaCy 中一个主要 bug 的起因。我使用了一个技巧,如果句子有特别长的弧线,就按一定百分比降低整体弧线高度。然而,这有时会导致最小的弧线变得不可见。
如果我们仅使用弧线的长度来计算其曲线,在可视化复杂句子时会很快遇到一个问题:最大的箭头变得巨大并产生过多的空白,使得可视化几乎无法使用。
最长的弧线相对于其他弧线来说非常巨大,产生了过多的空白。这里最大箭头的高度与其长度 21 相关,即使相对于长度 8 的高度也足以使其比第二大箭头更高。我们可以通过生成所有出现长度的有序列表来解决这个问题。在渲染弧线时,我们现在可以使用每个长度的索引(+1,从级别 1 开始)。
// Create an array from a set to avoid duplicate values and sort
const levels = [...new Set(
arcs.map(({ end, start }) => end - start).sort((a, b) => a - b))];
// [1, 2, 3, 4, 5, 6, 7, 21]
// Get level for arc
const arc = { dir: "right", end: 28, label: "punct", start: 7 };
const level = levels.indexOf(arc.end - arc.start) + 1;
// 8
同一个句子的效果更好——最大的弧线仍然是最大的。 我们现在可以根据整体级别生成箭头及其曲线:
// Get highest level (index + 1 of highest length value)
const highestLevel = levels.indexOf(levels.slice(-1)[0]) + 1
const offsetX = 150 // distance from the left
const distance = 300 // distance between words
const startX = offsetX + start * distance
const startY = (distance / 2) * highestLevel
const endpoint = offsetX + (end - start) * distance
const curve = startY - ((end - start) * distance) / 2
// Combine values for path definition
const d = `M${startX},${startY} C${startX},${curve} ${endpoint},${curve} ${endpoint},${startY}`
// Generate path markup
const path = `<path d="${d}" stroke-width="2px" fill="none" stroke="currentColor" ></path>`
箭头头部只是一个形成三角形的路径,放置在弧线的起点或终点。为了将标签沿着弧线路径的中间包裹,我们可以利用 <textPath> 元素并将其链接到弧线的 id:
<path id="arrow-0" d="..."></path>
<textPath xlink:href="#arrow-0" startOffset="50%" text-anchor="middle">Label</textPath>
使用 CSS 设置可视化样式
为了允许自定义样式,SVG 中包含的所有元素都带有标签和数据属性。默认情况下,使用元素的 currentColor 进行着色,这意味着你只需要在 CSS 中更改 color 属性。
例如,箭头具有类 .displacy-arrow 以及 data-label 和 data-dir 属性。使用这些选择器的组合和一些基本的 CSS 逻辑,你可以创建非常强大的模板,根据元素在解析中的角色和功能来设置它们的样式。
/* Make all noun phrases (tags that start with "NN") green */
.displacy-tag[data-tag^='NN'] {
color: green;
}
/* Hide all tags for verbs (tags that start with "VB") that are NOT the base form ("VB") */
.displacy-tag[data-tag^='VB']:not([data-tag='VB']) {
display: none;
}
配合 Harp 或 Node / Express 使用 displaCy
由于 SVG 图形由基本的 XML 组成,我们可以使用像 Jade 这样的模板引擎来动态生成标记。对于这篇博客,我编写了一个简单的 mixin,可以为任何给定的解析 JSON 表示生成静态内联 SVG。它甚至比 displacy.js 更紧凑(不到 50 行!),并且可在此处获得。它适用于基于 Jade 的静态站点生成器(如 Harp)或使用原生支持 Jade 模板的 Express 的 Node 应用程序。
要使用该 mixin,请在文件顶部包含它,并以完整的解析对象作为参数调用 +displacy():
include _displacy
+displacy({ arcs: [ ... ], words: [ ... ] })
要为单个弧线添加自定义类名,你可以在相应的弧线对象中添加 style: "classname"。我们已在此帖中使用此功能,在一张图中说明正确的依赖关系与不正确的依赖关系。
下一步计划?
我们计划支持更多标注格式,如 CoreNLP。同时,你可以添加自己的自定义转换器。
我们还推出了一个现代化且轻量级的命名实体可视化器——请继续关注另一篇深入的博客文章!
更新 (2018年2月) 自 spaCy v2.0 起,displaCy 可视化器已集成到核心库中。它支持在浏览器中提供可视化、生成原始标记或在 Jupyter 笔记本中输出结果。更多详情,请参阅可视化器文档。