1

给定一条线,例如

1磅牛肉

我想提取成分。最初我只对成分名称感兴趣

我看过 ruby​​s 著名的时间解析器 Chronic 并且喜欢它对正则表达式的使用。

 def self.scan_for_month_names(token)
scanner = {/^jan\.?(uary)?$/ => :january,
           /^feb\.?(ruary)?$/ => :february,
           /^mar\.?(ch)?$/ => :march,
           /^apr\.?(il)?$/ => :april,
           /^may$/ => :may,
           /^jun\.?e?$/ => :june,
           /^jul\.?y?$/ => :july,
           /^aug\.?(ust)?$/ => :august,
           /^sep\.?(tember)?$/ => :september,
           /^oct\.?(ober)?$/ => :october,
           /^nov\.?(ember)?$/ => :november,
           /^dec\.?(ember)?$/ => :december}
scanner.keys.each do |scanner_item|
  return Chronic::RepeaterMonthName.new(scanner[scanner_item]) if scanner_item =~ token.word
end
return nil

结尾

但是在我的情况下,Id 可能必须为每种单独的成分创建 300 多个正则表达式。

我还必须考虑香菜和芫荽叶等同义词

我以前从未做过解析,但在这里使用正则表达式仍然是最好的方法。我想不出任何其他合理的选择。

4

3 回答 3

4

首先,我假设成分并不总是采用以下形式QUANTITY UNIT of INGREDIENT- 否则,这将是一项非常微不足道的任务(只需在之后复制子字符串of

这是一个难题——解决方案并不简单。

我认为在这里使用正则表达式可能不是最好的方法:

  • 正如你提到的,你必须为每种成分写很多表达式
  • 您的可能成分列表将始终受到正则表达式列表的限制,如果不编译更多成分,您将无法检测到新成分。
  • 解析某些成分将非常困难(cheese, 1 pound (parmesan)

我认为自然语言处理是要走的路。您有非结构化输入,但在非常有限的上下文中。

也许与直觉相反,我认为找到这种成分的最佳方法很可能是不去寻找它——而是寻找其他所有东西。如果你假设一条线总是有

  • 一个数字(数量)
  • 一个单位(磅、茶匙等)
  • 一种成分

并且很容易检测数字和单位,首先识别它们然后提取成分应该很简单。

如果您使用词性标注器,则很容易识别相关词: [('1', 'LS'), ('pound', 'NN'), ('of', 'IN'), ('Beef', 'NNP')]

从那里,您可能想要使用分类器。为此,您需要在大量行(例如数百行)上手动标记成分。一些可能不错的功能:

  • 单词在行中的位置
  • 存在于预先计算的成分字典中(可能使用一些部分字符串匹配指标,如Levenshtein 的
  • 词性标注器的输出
  • 之前和之后的单词(如果你在单词之前有一个'of',那么它很可能是一种成分

我敢肯定,在几行工作之后,您将能够找到无数其他人。

最后,我预计有些行会很难处理。1 pound of parmesan cheese, 1 pound of emmentaler: 你不得不推断第二种成分也是奶酪。

至于软件,如果你可以选择使用的语言,python 有很棒的Natural Language Toolkit。我不能保证其他语言的工具包,但也许其他人会。

于 2012-12-22T15:42:28.247 回答
1

我想我会首先对每一行运行一系列正则表达式检查,并随时调整解析的文本。例如(伪代码):

首先,检查指令:

/^(add|fold in|stir in|etc...)/

如果您找到了一条指令,请将其从该行中删除,设置一个标志,然后继续:

instruction = $1
this_line = this_line.substring(instruction.length())

如果找到指令,请检查是否有后续指令(如“和覆盖”或“并搁置”)

/\b(and\s)(.*)$/

如果找到,将其剥离并插入到配方的下一行之前

instruction = instruction.substring(0, instuction.length - $1.length - $2.length)
splice $2 into the array of lines immediately following this one

接下来,也许你会检查一个介词:

/((?in)to\s(.+)/

如果找到,您可以使用它来检查设备名称、碗、量杯等。即使您不使用它,您也可以将它从您正在解析的字符串中删除,以改善您的匹配。

最后,真正的工作是用剩下的文本完成的:

Check against /^(\d+\s+(?a\s)?\w+)\s*(?of\s*)?(.+)$/

哪个应该给你$1包含计量单位和$2包含成分。

泡沫。冲洗。重复。之后,用这些信息做任何你的应用程序的魔法。

于 2012-12-22T11:42:46.820 回答
1

首先,我建议进行一些搜索,看看其他人是否已经为这个问题创建了一个足以让你使用的解决方案,而不是重新发明轮子。

例如,您可能会发现这个项目很有趣。它使用机器学习来尝试解析成分短语,包括成分类型和数量。

谷歌搜索“成分解析器”时也会出现其他有趣的项目。

如果您真的决定自己编写,那么我建议您对称为“解析器生成器”的软件工具类别进行一些研究,该工具可以让您编写您想要识别的语言抽象形式(“语法”),然后将生成您选择的语言的代码,该代码将根据该语法解析文本并非常有效地识别其中的特定子结构(比数百个正则表达式更有效)火柴)。

例如,用作解析器生成器输入的语法可能如下所示:

// I am making up the following syntax for demonstration purposes, but it illustrates the
// sort of things that one could specify in a grammar, and is not terribly different from
// the grammar languages that real parser generators use.
//
// Note that everything in the curly braces is code to be inserted into the generated parser.
// Each such code block will be invoked when the preceding parsing rule is matched.

%declare { bool organic=false; bool dried=false; bool smoked=false; }

INGREDIENT ::= "organic" INGREDIENT          { organic=true; }
             | INGREDIENT "(" "organic" ")"  { organic=true; }
             | "dried" INGREDIENT            { dried=true; }
             | "smoked" INGREDIENT           { smoked=true; }
             | AMOUNT "of" INGREDIENT
             | INGREDIENT "(" AMOUNT ")"
             | BASE_INGREDIENT

BASE_INGREDIENT ::= ( WORD )* {
   doSomethingWithBaseIngredient(organic, dried, smoked, $BASE_INGREDIENT);
}

AMOUNT ::= NUMBER ( VOLUME_UNIT | WEIGHT_UNIT )
VOLUME_UNIT ::= "cup" | "liter"
WEIGHT_UNIT ::= "mg" | "kg" | "pound"
NUMBER ::= [0-9]+
WORD ::= [a-zA-Z]+

... and so forth.

解析器生成器在运行时会将此语法作为输入,并以您想要的编程语言生成代码作为输出。此代码将根据语法解析输入文本,并在匹配某些解析规则时根据需要设置变量和/或调用您的函数。此类工具生成的解析器通常使用特殊的解析技术(通常涉及大型表、状态机等)在单次传递中非常有效地解析,而无需做任何不必要的工作,并尽可能避免回溯。

解析器生成器的一些常见示例是 lexx/yacc、bison 和 Antlr。许多其他的存在。(就个人而言,我过去在 Antlr 上取得了不错的成绩,并且特别喜欢它可以生成许多不同编程语言的解析器这一事实。)这些解析器生成器中的许多主要是供编译器编写者使用的,但确实如此并不意味着它们不能用于其他目的,例如识别食谱中成分的各种形式。

本文提供解析器生成器的概述,本文包含各种解析器生成器及其属性(输出语言等)的表格,以及在哪里可以找到更多信息的链接。

于 2018-06-27T23:32:15.120 回答