如何在NLTK中自定义分词标签?及命名实体识别结果异常问题
嘿,我来帮你搞定这两个NLTK的问题,都是实际使用中常见的痛点:
1. 如何在NLTK工具中为数据分词设置自定义标签?
NLTK默认的词性标注器用的是预训练好的通用模型,要添加自定义标签,主要有两种方式,看你的需求场景选:
方式一:手动修改标注结果(适合少量文本)
如果只是个别词需要自定义标签,可以先跑默认的词性标注,再手动替换目标词的标签。举个例子:
from nltk import word_tokenize, pos_tag sentence = "My custom term needs a special tag" tokens = word_tokenize(sentence) # 先获取默认标注结果 default_tagged = pos_tag(tokens) # 替换指定词的标签为自定义的CUSTOM_TAG custom_tagged = [(word, 'CUSTOM_TAG') if word == 'custom' else (word, tag) for word, tag in default_tagged] print(custom_tagged)
方式二:训练自定义词性标注器(适合大量领域文本)
如果你的场景有很多需要自定义标签的词汇,最好训练自己的标注器。可以基于NLTK的UnigramTagger或BigramTagger,结合自定义语料和预训练模型来提升准确率:
from nltk.tag import UnigramTagger from nltk.corpus import treebank # 准备带自定义标签的语料,格式是[[(词1, 标签1), (词2, 标签2)], ...] custom_corpus = [ [("Antacids", "MEDICATION"), ("is", "VBZ"), ("given", "VBN"), ("to", "TO")], [("Sodium", "MEDICATION"), ("Bicarbonate", "MEDICATION"), ("is", "VBZ"), ("given", "VBN")] ] # 用预训练的treebank标注器作为回退,避免未见过的词标注错误 base_tagger = UnigramTagger(treebank.tagged_sents()) # 训练自定义标注器,优先用自定义语料, fallback到预训练模型 custom_tagger = UnigramTagger(custom_corpus, backoff=base_tagger) # 测试效果 tokens = word_tokenize("Antacids is given to John") print(custom_tagger.tag(tokens))
2. 修正NLTK命名实体识别(NER)的错误
你遇到的问题很典型——NLTK默认的ne_chunk用的是基于通用语料训练的模型,对医学术语、特定人名这类领域实体的识别能力很有限,容易把药物名、人名误判为GPE(地理实体)。这里有几种解决思路:
方案一:手动修正NER树结构(适合少量测试文本)
可以解析ne_chunk返回的树结构,根据已知的实体列表手动替换错误的标签:
from nltk import word_tokenize, pos_tag, ne_chunk from nltk.tree import Tree sentence = "Antacids is given to Jhon,Sodium Bicarbonate is given to Carl,Folic Acid to Jeery all works at Google " tokens = word_tokenize(sentence) tagged = pos_tag(tokens) ner_tree = ne_chunk(tagged) def correct_ner(tree): corrected_nodes = [] # 定义已知的实体和正确标签 entity_map = { "Antacids": "MEDICATION", "Sodium Bicarbonate": "MEDICATION", "Folic Acid": "MEDICATION", "Jhon": "PERSON", "Carl": "PERSON", "Jeery": "PERSON", "Google": "ORGANIZATION" } for node in tree: if isinstance(node, Tree): # 提取实体文本 entity_text = " ".join([word for word, _ in node.leaves()]) # 如果在映射表里,替换标签;否则保留原标签 if entity_text in entity_map: corrected_nodes.append(Tree(entity_map[entity_text], node.leaves())) else: corrected_nodes.append(node) else: corrected_nodes.append(node) return Tree('S', corrected_nodes) # 得到修正后的NER树 corrected_ner_tree = correct_ner(ner_tree) print(corrected_ner_tree)
方案二:训练自定义NER模型(适合大量领域数据)
如果有足够多的标注好的领域语料,训练自己的NER模型是长期解决方案。NLTK可以用基于分类器的chunk parser来实现,这里用MaxEnt分类器举个例子:
from nltk.classify import MaxentClassifier from nltk.tag import ClassifierBasedTagger from nltk.chunk import ChunkParserI from nltk import word_tokenize, pos_tag from nltk.tree import Tree # 准备训练数据,用IOB标注格式(B-开头表示实体起始,I-表示实体延续,O表示非实体) training_data = [ (["Antacids", "is", "given", "to", "Jhon"], ["B-MEDICATION", "O", "O", "O", "B-PERSON"]), (["Sodium", "Bicarbonate", "is", "given", "to", "Carl"], ["B-MEDICATION", "I-MEDICATION", "O", "O", "O", "B-PERSON"]), (["Folic", "Acid", "to", "Jeery", "all", "works", "at", "Google"], ["B-MEDICATION", "I-MEDICATION", "O", "B-PERSON", "O", "O", "O", "B-ORGANIZATION"]) ] # 定义特征提取函数,给分类器提供词的上下文信息 def ner_features(tokens, index, history): word = tokens[index] pos_tag_val = pos_tag(tokens)[index][1] return { 'word': word.lower(), 'pos': pos_tag_val, 'is_capitalized': word.istitle(), 'prev_word': tokens[index-1].lower() if index > 0 else '', 'next_word': tokens[index+1].lower() if index < len(tokens)-1 else '' } # 自定义NER解析器类 class CustomNERParser(ChunkParserI): def __init__(self, training_data): train_set = [] for tokens, tags in training_data: tagged_tokens = pos_tag(tokens) history = [] for i, _ in enumerate(tagged_tokens): features = ner_features(tokens, i, history) train_set.append((features, tags[i])) history.append(tags[i]) # 训练MaxEnt分类器 self.classifier = MaxentClassifier.train(train_set, trace=0) def parse(self, tagged_tokens): tokens = [word for word, _ in tagged_tokens] history = [] predicted_tags = [] for i, _ in enumerate(tagged_tokens): features = ner_features(tokens, i, history) tag = self.classifier.classify(features) predicted_tags.append(tag) history.append(tag) # 把IOB标签转换为NLTK的Tree结构 return self._iob_to_tree(tagged_tokens, predicted_tags) def _iob_to_tree(self, tagged_tokens, iob_tags): tree_nodes = [] current_chunk = [] current_entity_type = None for (word, pos), tag in zip(tagged_tokens, iob_tags): if tag.startswith('B-'): # 结束上一个chunk(如果有的话) if current_chunk: tree_nodes.append(Tree(current_entity_type, current_chunk)) current_chunk = [] current_entity_type = tag.split('-')[1] current_chunk.append((word, pos)) elif tag.startswith('I-') and current_entity_type == tag.split('-')[1]: # 延续当前chunk current_chunk.append((word, pos)) else: # 非实体,结束当前chunk(如果有的话) if current_chunk: tree_nodes.append(Tree(current_entity_type, current_chunk)) current_chunk = [] tree_nodes.append((word, pos)) # 处理最后一个chunk if current_chunk: tree_nodes.append(Tree(current_entity_type, current_chunk)) return Tree('S', tree_nodes) # 训练并测试 parser = CustomNERParser(training_data) test_tokens = word_tokenize(sentence) test_tagged = pos_tag(test_tokens) corrected_tree = parser.parse(test_tagged) print(corrected_tree)
额外提示
如果你的场景对NER准确率要求很高,也可以考虑结合spaCy这类工具的领域预训练模型(比如医学领域的en_med7_lg),不过如果必须用NLTK,上面的两种方案完全可以解决你的问题。
内容的提问来源于stack exchange,提问作者Meta
相关产品推荐
相关产品推荐

