You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用spaCy在Python中标准化食谱数据集的食材名称并处理NOUN+NOUN结构

如何使用spaCy在Python中标准化食谱数据集的食材名称并处理NOUN+NOUN结构

我完全理解你的困扰——处理像broccoli florets这种名词叠加的食材名称时,单独遍历token很容易漏掉前面的修饰性名词,而且硬加特殊case确实不够优雅。咱们可以从spaCy的依赖分析入手,利用它的语法树信息来更准确地捕捉完整的食材术语,而不是只处理单个token。

问题根源分析

你之前的代码主要有两个潜在问题:

  1. 缩进错误:看你提供的第一个函数,检查词性的if语句缩进位置不对,导致部分token的处理逻辑根本没执行(比如broccoli这个token可能根本没走到词性判断的分支)。
  2. 未利用依赖关系:像broccoli florets里,broccoli是florets的compound(复合名词修饰语),但你的代码只单独处理每个token,没有把这类关联的名词组合起来,所以才会只留下floret。

改进后的解决方案

核心思路是:过滤掉无关的测量单位、数字后,通过spaCy的依赖树找到食材的中心名词,再把所有修饰它的复合名词、形容词都组合起来,这样就能完整提取像broccoli florets、chicken broth这类结构的食材名称。

def normalize_ingredient(ingredient_text):
    doc = nlp(ingredient_text)

    # 去重后的测量单位集合
    measurement_units = {
        "cup", "cups", "teaspoon", "tablespoon", "tablespoons", "gram", "ounce", 
        "pound", "can", "clove", "pinch", "dash", "quart", "liter", "milliliter", 
        "gallon", "stick", "rib", "head", "package", "inch", "piece", "fluid", 
        "container", "jar", "loaf", "bottle", "pack", "pint", "cube", "stalk", 
        "slice", "bulb", "strip", "packet", "envelope", "box", "bag", "carton", 
        "sprig", "leaf", "half", "purpose", "bite", "size", "bunch", "all", 
        "sized", "chunks"
    }

    # 分数与特殊符号集合
    fractions = {'½', '¼', '¾', '⅓', '⅔', '⅛', '⅜', '⅝', '⅞', '⅙', '⅚', '®'}

    # 用集合存储食材术语,自动去重
    ingredient_terms = set()

    for token in doc:
        # 跳过无关内容:数字、分数、测量单位、标点、停用词
        if (token.like_num or
            token.text in fractions or
            token.lemma_.lower() in measurement_units or
            token.is_punct or
            token.is_stop):
            continue

        # 处理名词/专有名词,收集所有复合修饰语
        if token.pos_ in {"NOUN", "PROPN"}:
            compound_terms = []
            # 找到当前名词的所有compound修饰语(比如broccoli是florets的compound)
            for child in token.children:
                if child.dep_ == "compound" and child.pos_ in {"NOUN", "PROPN", "ADJ"}:
                    compound_terms.append(child.lemma_.lower())
            # 组合修饰语与中心名词
            full_term = " ".join(compound_terms + [token.lemma_.lower()])
            ingredient_terms.add(full_term)
        
        # 处理修饰名词的形容词(比如"fresh garlic"里的fresh)
        elif token.pos_ == "ADJ" and token.head.pos_ in {"NOUN", "PROPN"}:
            # 确保被修饰的名词没有被过滤掉
            if not (token.head.like_num or
                    token.head.text in fractions or
                    token.head.lemma_.lower() in measurement_units):
                full_term = f"{token.lemma_.lower()} {token.head.lemma_.lower()}"
                ingredient_terms.add(full_term)

    # 处理结果:优先保留最长的完整术语(避免同时出现"floret"和"broccoli floret")
    if not ingredient_terms:
        return ""
    sorted_terms = sorted(ingredient_terms, key=len, reverse=True)
    # 移除被包含的短术语
    final_terms = []
    for term in sorted_terms:
        if not any(term in existing_term for existing_term in final_terms):
            final_terms.append(term)
    return " ".join(final_terms)

代码功能说明

  1. 过滤逻辑强化:新增了对标点、停用词的过滤,减少无关噪声。
  2. 依赖关系利用:通过token.children找到compound类型的修饰语,把broccoli和florets组合成完整的broccoli floret(lemma形式,利于标准化)。
  3. 结果去重与优化:按术语长度排序,只保留最完整的食材名称,避免出现冗余的短术语。
  4. 形容词处理:正确组合像fresh garlic这类形容词+名词的结构。

测试效果

  • 输入:"8 cups broccoli florets" → 输出:"broccoli floret"
  • 输入:"3 cups chicken broth" → 输出:"chicken broth"
  • 输入:"5 tablespoons butter, divided" → 输出:"butter"

额外优化建议

  • 如果需要保留复数形式(比如florets而不是floret),可以把token.lemma_.lower()改成token.text.lower(),但lemma形式更利于跨数据的标准化。
  • 可以扩展依赖类型,比如加入amod(形容词修饰)、nn(旧版spaCy的名词修饰)等,覆盖更多场景。
  • 对于像divided、chopped这类描述性加工词汇,可以新增一个排除集合,比如exclude_terms = {"divided", "chopped", "diced"},在过滤时加入判断。

备注:内容来源于stack exchange,提问作者Островська Катя

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 19:03:00