项目文件夹

文件
2026-07-13 10:48:06 +00:00

61 KiB

Note

本文档由 WeHub 基于上游 README 翻译整理,属于社区翻译,非官方中文文档。
English · 原始项目 · 上游 README
原作者、版权与许可证归属以原始项目及本仓库 LICENSE 文件为准。

compromise
轻量自然语言处理
npm install compromise
你不觉得奇怪吗,
    文本是多么容易生成

     ᔐᖜ   而实际上要解析使用它却又多么困难?

compromise 尽力将文本转换为数据。
它做出有限且合理的决策。
它并没有你想象的那么聪明。
import nlp from 'compromise'

let doc = nlp('she sells seashells by the seashore.')
doc.verbs().toPastTense()
doc.text()
// 'she sold seashells by the seashore.'
完全不必花哨:
if (doc.has('simon says #Verb')) {
  return true
}
提取文本中的部分:
let doc = nlp(entireNovel)
doc.match('the #Adjective of times').text()
// "the blurst of times?"

并获取数据:

import plg from 'compromise-speech'
nlp.extend(plg)

let doc = nlp('Milwaukee has certainly had its share of visitors..')
doc.compute('syllables')
doc.places().json()
/*
[{
  "text": "Milwaukee",
  "terms": [{
    "normal": "milwaukee",
    "syllables": ["mil", "wau", "kee"]
  }]
}]
*/

避免脆弱解析器带来的问题:

let doc = nlp("we're not gonna take it..")

doc.has('gonna') // true
doc.has('going to') // true (implicit)

// transform
doc.contractions().expand()
doc.text()
// 'we are not going to take it..'

并像操作数据一样随意摆弄内容:

let doc = nlp('ninety five thousand and fifty two')
doc.numbers().add(20)
doc.text()
// 'ninety five thousand and seventy two'

——因为它实际上就是数据——

let doc = nlp('the purple dinosaur')
doc.nouns().toPlural()
doc.text()
// 'the purple dinosaurs'

在客户端使用:

<script src="https://unpkg.com/compromise"></script>
<script>
  var doc = nlp('two bottles of beer')
  doc.numbers().minus(1)
  document.body.innerHTML = doc.text()
  // 'one bottle of beer'
</script>

或者同样地:

import nlp from 'compromise'

var doc = nlp('London is calling')
doc.verbs().toNegative()
// 'London is not calling'

compromise 约为 ~250kbminified):

它相当快,可在按键时运行:

它主要通过为基础词表做全部词形变化(conjugating all forms来实现。

最终词库约为 ~14,000 words

你可以在这里. 了解更多工作原理——挺奇怪的。

好吧 —

compromise/one

对单词、句子和标点进行 tokenizer(分词器)。

import nlp from 'compromise/one'

let doc = nlp("Wayne's World, party time")
let data = doc.json()
/* [{
  normal:"wayne's world party time",
    terms:[{ text: "Wayne's", normal: "wayne" },
      ...
      ]
  }]
*/

compromise/one 会把文本拆分,并封装为便捷的 API,

    除此之外不做其他事 —

/one 很快——大多数句子只需十分之一毫秒。

它每秒可处理 ~1mb 文本——相当于 10 个 Wikipedia 页面。

Infinite jest 需要 3 秒。

你也可以并行处理,或通过 compromise-speed 向其流式输入文本。

compromise/two

part-of-speech(词性)标注器与语法解释器。

import nlp from 'compromise/two'

let doc = nlp("Wayne's World, party time")
let str = doc.match('#Possessive #Noun').text()
// "Wayne's World"

compromise/two 会自动计算每个词的最基础语法。

这有时比人们意识到的更有用。

轻量语法有助于你编写更清晰的模板,并更接近信息本身。

compromise 拥有 83 tags,排列在 一张美观的图谱中。

#FirstName#Person#ProperNoun#Noun

运行 doc.debug() 即可查看每个词的语法

使用 nlp.verbose('tagger') 可查看每个标签的推理过程。

如果你更喜欢 Penn tags,可以这样派生:

let doc = nlp('welcome thrillho')
doc.compute('penn')
doc.json()

compromise/three

Phrase 与句子工具集。

import nlp from 'compromise/three'

let doc = nlp("Wayne's World, party time")
let str = doc.people().normalize().text()
// "wayne"

compromise/three 是一套用于聚焦文本片段并进行操作的工具。

例如,.numbers() 会抓取文档中的所有数字,并通过 .subtract() 等方法进行扩展。

当你有一个短语或词组时,可用 .json() 查看其附加元数据

let doc = nlp('four out of five dentists')
console.log(doc.fractions().json())
/*[{
    text: 'four out of five',
    terms: [ [Object], [Object], [Object], [Object] ],
    fraction: { numerator: 4, denominator: 5, decimal: 0.8 }
  }
]*/
let doc = nlp('$4.09CAD')
doc.money().json()
/*[{
    text: '$4.09CAD',
    terms: [ [Object] ],
    number: { prefix: '$', num: 4.09, suffix: 'cad'}
  }
]*/

API

Compromise/one

输出
  • .text() - 以文本形式返回文档
  • .json() - 以数据形式返回文档
  • .debug() - 漂亮打印已解析的文档
  • .out() - 具名或自定义输出
  • .html({}) - 为匹配项输出自定义 HTML 标签
  • .wrap({}) - 为文档匹配项生成自定义输出
工具
  • .found [getter] - 该文档是否为空?
  • .docs [getter] 以 json 形式获取 term 对象
  • .length [getter] - 统计文档中的字符数(字符串长度)
  • .isView [getter] - 识别 compromise 对象
  • .compute() - 对文档运行具名分析
  • .clone() - 深拷贝文档,使不留任何引用
  • .termList() - 返回匹配中所有 Term 对象的扁平列表
  • .cache({}) - 冻结文档当前状态,以提升速度
  • .uncache() - 解冻文档当前状态,以便可继续变换
  • .freeze({}) - 阻止这些 term 上的任何标签被移除
  • .unfreeze({}) - 恢复默认,允许标签再次变更
访问器(Accessors
  • .all() - 返回完整原始文档('zoom out'
  • .terms() - 按每个单独词项拆分结果
  • .first(n) - 仅使用第一个(前几个)结果
  • .last(n) - 仅使用最后一个(后几个)结果
  • .slice(n,n) - 获取结果的子集
  • .eq(n) - 仅使用第 n 个结果
  • .firstTerms() - 获取每个匹配中的第一个词
  • .lastTerms() - 获取每个匹配中的最后一个词
  • .fullSentences() - 获取每个匹配所在的完整句子
  • .groups() - 从匹配中提取所有命名捕获组(named capture-groups
  • .wordCount() - 统计文档中的词项数量
  • .confidence() - 词性标注(POS tag)解释的平均置信度分数
匹配(Match

(匹配方法使用 match-syntax.)

  • .match('') - 返回一个新的 Doc,并以当前文档作为父文档
  • .not('') - 返回除此匹配外的所有结果
  • .matchOne('') - 仅返回第一个匹配
  • .if('') - 仅当当前短语包含此匹配时才返回该短语('only'
  • .ifNo('') - 过滤掉包含此匹配的所有当前短语('notIf'
  • .has('') - 若存在此匹配则返回布尔值
  • .before('') - 返回每个短语中匹配之前的所有词项
  • .after('') - 返回每个短语中匹配之后的所有词项
  • .union() - 返回合并后的匹配,不含重复项
  • .intersection() - 仅返回重复的匹配
  • .complement() - 获取不在另一匹配中的所有内容
  • .settle() - 从匹配中移除重叠部分
  • .growRight('') - 在每个匹配之后立即添加任何匹配的词项
  • .growLeft('') - 在每个匹配之前立即添加任何匹配的词项
  • .grow('') - 在每个匹配之前或之后添加任何匹配的词项
  • .sweep(net) - 将一系列匹配对象应用于文档
  • .splitOn('') - 为每个匹配返回包含三部分的 Document'splitOn'
  • .splitBefore('') - 在每个匹配片段之前分割短语
  • .splitAfter('') - 在每个匹配片段之后分割短语
  • .join() - 合并每个匹配中所有相邻的词项
  • .joinIf(leftMatch, rightMatch) - 在给定条件下合并相邻词项
  • .lookup([]) - 快速查找字符串匹配数组
  • .autoFill() - 在文档上创建自动补全(type-ahead)假设
标注(Tag
  • .tag('') - 为所有词项赋予给定标签
  • .tagSafe('') - 仅当与当前标签一致时才将标签应用于词项
  • .unTag('') - 从给定词项中移除此词项
  • .canBe('') - 仅返回可以具有此标签的词项
大小写(Case
空白(Whitespace
循环(Loops
  • .map(fn) - 对每个短语运行函数并创建新文档
  • .forEach(fn) - 将每个短语作为独立文档对其运行函数
  • .filter(fn) - 仅返回结果为 true 的短语
  • .find(fn) - 返回仅包含第一个匹配短语的文档
  • .some(fn) - 若存在一个匹配短语则返回 true 或 false
  • .random(fn) - 对结果子集进行抽样
插入(Insert
Transform
Lib

(这些方法位于主 nlp 对象上)

compromise/two:

Contractions

compromise/three:

Nouns
Verbs
Numbers
句子
形容词
其他选择项

.extend():

本库内置了一套体贴且符合常识的英语语法基线。

你可以随意修改,甚至彻底推翻任何设置——这其实也是乐趣所在。

最简单的做法是为任意给定词语建议标签:

let myWords = {
  kermit: 'FirstName',
  fozzie: 'FirstName',
}
let doc = nlp(muppetText, myWords)

或者通过 compromise-plugin. 进行更大幅度的修改

import nlp from 'compromise'
nlp.extend({
  // add new tags
  tags: {
    Character: {
      isA: 'Person',
      notA: 'Adjective',
    },
  },
  // add or change words in the lexicon
  words: {
    kermit: 'Character',
    gonzo: 'Character',
  },
  // change inflections
  irregulars: {
    get: {
      pastTense: 'gotten',
      gerund: 'gettin',
    },
  },
  // add new methods to compromise
  api: View => {
    View.prototype.kermitVoice = function () {
      this.sentences().prepend('well,')
      this.match('i [(am|was)]').prepend('um,')
      return this
    }
  },
})

文档:

入门导读:
文档:
概念 API 插件
准确度 (Accuracy) Accessors Adjectives
缓存 (Caching) Constructor-methods Dates
大小写 (Case) Contractions Export
文件大小 (Filesize) Insert Hash
内部机制 (Internals) Json Html
对齐 (Justification) Character Offsets Keypress
词典 (Lexicon) Loops Ngrams
匹配语法 (Match-syntax) Match Numbers
性能 (Performance) Nouns Paragraphs
插件 (Plugins) Output Scan
项目 (Projects) Selections Sentences
标注器 (Tagger) Sorting Syllables
标签 (Tags) Split Pronounce
分词 (Tokenization) Text Strict
命名实体 (Named-Entities) Utils Penn-tags
空白字符 (Whitespace) Verbs Typeahead
世界数据 (World data) Normalization Sweep
模糊匹配 (Fuzzy-matching) Typescript Mutation
词根形式 (Root-forms)
演讲:
文章:
一些有趣的应用:
对比

插件:

以下是一些实用的扩展:

日期

npm install compromise-dates

统计

npm install compromise-stats

语音

npm install compromise-syllables

Wikipedia

npm install compromise-wikipedia


Typescript

我们致力于支持 TypeScript/Deno,主库与官方插件均提供支持:

import nlp from 'compromise'
import stats from 'compromise-stats'

const nlpEx = nlp.extend(stats)

nlpEx('This is type safe!').ngrams({ min: 1 })

局限性:

  • slash-support 我们目前会像处理连字符一样,将斜杠拆成不同的词,因此像下面这样的情况无法正常工作: nlp('the koala eats/shoots/leaves').has('koala leaves') //false

  • inter-sentence match 默认情况下,句子是顶层抽象单元。 若无 插件,则不支持跨句或多句匹配: nlp("that's it. Back to Winnipeg!").has('it back')//false

  • nested match syntax 正则表达式的危险之美在于可以无限递归。 我们的匹配语法要弱得多。像下面这样的情况尚(尚未)支持: doc.match('(modern (major|minor))? general') 复杂匹配必须通过连续的 .match() 调用来实现。

  • dependency parsing 正确的句子变换需要理解句子的 syntax tree,我们目前尚未实现。 我们应当支持!欢迎在此方面提供帮助。

常见问题

    ☂️ JavaScript 是不是太……

      没错,确实如此!
      它并非为与 NLTK 竞争而构建,也未必适合所有项目。
      字符串处理也是同步的,并行化 Node 进程也颇为别扭。
      有关速度与性能的信息请见 此处,项目动机请见 此处

    💃 能在我的 Arduino 手表上运行吗?

      除非它防水!
      请阅读 快速入门,了解如何在 Worker、移动应用等各种有趣环境中运行 compromise。

    🌎 其他语言的 Compromise?

    部分构建?

      我们确实提供 仅分词 构建,其中 POS 标注器已被移除。
      但除此之外,compromise 不易进行 tree-shaking。
      标注方法彼此竞争且呈贪婪式,因此不建议剥离部分功能。
      请注意,若没有完整的 POS 标注,缩略形式解析器将无法完美工作。((spencer's cool)(spencer's house)
      建议完整运行该库。

另请参阅:

  •   en-pos - 非常精巧的 JavaScript POS 标注器 by Alex Corvi

  •   naturalNode - JavaScript 中更精致的统计 NLP

  •   winkJS - JavaScript 中的 POS 标注器、分词器与机器学习

  •   dariusk/pos-js - JavaScript 版 fastTag 分支

  •   compendium-js - JavaScript 中的 POS 与情感分析

  •   nodeBox linguistics - JavaScript 中的词形变化与屈折

  •   reText - JavaScript 中令人印象深刻的 text utilities

  •   superScript - JavaScript 对话引擎

  •   jsPos - 久经考验的 Brill 标注器的 JavaScript 构建版

  •   spaCy - C/Python 实现的快速多语言标注器(tagger)

  •   Prose - Joseph Kato 用 Go 编写的快速标注器

  •   TextBlob - Python 标注器

MIT