我正在使用Stanford命名实体识别器http://nlp.stanford.edu/software/CRF-NER.shtml,它运行正常。这是
List<List<CoreLabel>> out = classifier.classify(text);
for (List<CoreLabel> sentence : out) {
for (CoreLabel word : sentence) {
if (!StringUtils.equals(word.get(AnswerAnnotation.class), "O")) {
namedEntities.add(word.word().trim());
}
}
}
然而,我发现的问题是识别名字和姓氏。如果识别器遇到“Joe Smith”,它将分别返回“Joe”和“Smith”。我真的希望将“乔·史密斯”作为一个词来回归。
这可以通过识别器通过配置来实现吗?到目前为止,我在javadoc中找不到任何东西。
谢谢!
答案 0 :(得分:20)
这是因为你的内部for循环遍历单个标记(单词)并单独添加它们。您需要更改内容以立即添加全名。
一种方法是用一个常规for循环替换内部for循环,其中包含一个while循环,它接受相同类的相邻非O事物并将它们作为单个实体添加。*
另一种方法是使用CRFClassifier方法调用:
List<Triple<String,Integer,Integer>> classifyToCharacterOffsets(String sentences)
将为您提供整个实体,您可以在原始输入上使用substring
提取字符串形式。
*我们分发的模型使用简单的原始IO标签方案,其中标记为PERSON或LOCATION,并且适当的做法是简单地合并具有相同标签的相邻标记。许多NER系统使用更复杂的标签,例如IOB标签,其中像B-PERS这样的代码表示人员实体的起始位置。 CRFClassifier类和功能工厂支持此类标签,但它们并未在我们当前分发的模型中使用(截至2012年)。
答案 1 :(得分:5)
classifyToCharacterOffsets方法的对应部分是(AFAIK)您无法访问实体的标签。
正如克里斯托弗所提出的,这里是一个循环的例子,它汇集了“相邻的非O事物”。此示例还计算出现次数。
public HashMap<String, HashMap<String, Integer>> extractEntities(String text){
HashMap<String, HashMap<String, Integer>> entities =
new HashMap<String, HashMap<String, Integer>>();
for (List<CoreLabel> lcl : classifier.classify(text)) {
Iterator<CoreLabel> iterator = lcl.iterator();
if (!iterator.hasNext())
continue;
CoreLabel cl = iterator.next();
while (iterator.hasNext()) {
String answer =
cl.getString(CoreAnnotations.AnswerAnnotation.class);
if (answer.equals("O")) {
cl = iterator.next();
continue;
}
if (!entities.containsKey(answer))
entities.put(answer, new HashMap<String, Integer>());
String value = cl.getString(CoreAnnotations.ValueAnnotation.class);
while (iterator.hasNext()) {
cl = iterator.next();
if (answer.equals(
cl.getString(CoreAnnotations.AnswerAnnotation.class)))
value = value + " " +
cl.getString(CoreAnnotations.ValueAnnotation.class);
else {
if (!entities.get(answer).containsKey(value))
entities.get(answer).put(value, 0);
entities.get(answer).put(value,
entities.get(answer).get(value) + 1);
break;
}
}
if (!iterator.hasNext())
break;
}
}
return entities;
}
答案 2 :(得分:3)
我遇到了同样的问题,所以我也查了一下。克里斯托弗曼宁提出的方法是有效的,但最重要的是要知道如何确定哪种分离器是合适的。可以说只允许一个空间,例如“John Zorn”&gt;&gt;一个实体。但是,我可能会找到“J.Zorn”的形式,所以我也应该允许某些标点符号。但是“杰克,詹姆斯和乔”呢?我可能得到2个实体而不是3个(“Jack James”和“Joe”)。
通过在斯坦福NER课程中挖掘一点,我实际上找到了这个想法的正确实现。他们使用它以单String
个对象的形式导出实体。例如,在方法PlainTextDocumentReaderAndWriter.printAnswersTokenizedInlineXML
中,我们有:
private void printAnswersInlineXML(List<IN> doc, PrintWriter out) {
final String background = flags.backgroundSymbol;
String prevTag = background;
for (Iterator<IN> wordIter = doc.iterator(); wordIter.hasNext();) {
IN wi = wordIter.next();
String tag = StringUtils.getNotNullString(wi.get(AnswerAnnotation.class));
String before = StringUtils.getNotNullString(wi.get(BeforeAnnotation.class));
String current = StringUtils.getNotNullString(wi.get(CoreAnnotations.OriginalTextAnnotation.class));
if (!tag.equals(prevTag)) {
if (!prevTag.equals(background) && !tag.equals(background)) {
out.print("</");
out.print(prevTag);
out.print('>');
out.print(before);
out.print('<');
out.print(tag);
out.print('>');
} else if (!prevTag.equals(background)) {
out.print("</");
out.print(prevTag);
out.print('>');
out.print(before);
} else if (!tag.equals(background)) {
out.print(before);
out.print('<');
out.print(tag);
out.print('>');
}
} else {
out.print(before);
}
out.print(current);
String afterWS = StringUtils.getNotNullString(wi.get(AfterAnnotation.class));
if (!tag.equals(background) && !wordIter.hasNext()) {
out.print("</");
out.print(tag);
out.print('>');
prevTag = background;
} else {
prevTag = tag;
}
out.print(afterWS);
}
}
他们迭代每个单词,检查它是否具有与前一个相同的类(答案),如前所述。为此,他们利用被认为不是实体的事实表达式使用所谓的backgroundSymbol
(类“O”)标记。它们还使用属性BeforeAnnotation
,它表示将当前单词与前一单词分隔开的字符串。关于选择合适的分隔符,最后一点可以解决我最初提出的问题。
答案 3 :(得分:2)
以上代码:
<List> result = classifier.classifyToCharacterOffsets(text);
for (Triple<String, Integer, Integer> triple : result)
{
System.out.println(triple.first + " : " + text.substring(triple.second, triple.third));
}
答案 4 :(得分:2)
www.example.com/drupal
我刚写了一个小逻辑,它工作得很好。如果它们相邻,我所做的就是将具有相同标签的单词组合在一起。
答案 5 :(得分:1)
利用已经提供给您的分类器。我相信这就是你要找的东西:
private static String combineNERSequence(String text) {
String serializedClassifier = "edu/stanford/nlp/models/ner/english.all.3class.distsim.crf.ser.gz";
AbstractSequenceClassifier<CoreLabel> classifier = null;
try {
classifier = CRFClassifier
.getClassifier(serializedClassifier);
} catch (ClassCastException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(classifier.classifyWithInlineXML(text));
// FOR TSV FORMAT //
//System.out.print(classifier.classifyToString(text, "tsv", false));
return classifier.classifyWithInlineXML(text);
}
答案 6 :(得分:0)
这是我的完整代码,我使用Stanford核心NLP和编写算法来连接多项名称。
import edu.stanford.nlp.ling.CoreAnnotations;
import edu.stanford.nlp.ling.CoreLabel;
import edu.stanford.nlp.pipeline.Annotation;
import edu.stanford.nlp.pipeline.StanfordCoreNLP;
import edu.stanford.nlp.util.CoreMap;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
/**
* Created by Chanuka on 8/28/14 AD.
*/
public class FindNameEntityTypeExecutor {
private static Logger logger = Logger.getLogger(FindNameEntityTypeExecutor.class);
private StanfordCoreNLP pipeline;
public FindNameEntityTypeExecutor() {
logger.info("Initializing Annotator pipeline ...");
Properties props = new Properties();
props.setProperty("annotators", "tokenize, ssplit, pos, lemma, ner");
pipeline = new StanfordCoreNLP(props);
logger.info("Annotator pipeline initialized");
}
List<String> findNameEntityType(String text, String entity) {
logger.info("Finding entity type matches in the " + text + " for entity type, " + entity);
// create an empty Annotation just with the given text
Annotation document = new Annotation(text);
// run all Annotators on this text
pipeline.annotate(document);
List<CoreMap> sentences = document.get(CoreAnnotations.SentencesAnnotation.class);
List<String> matches = new ArrayList<String>();
for (CoreMap sentence : sentences) {
int previousCount = 0;
int count = 0;
// traversing the words in the current sentence
// a CoreLabel is a CoreMap with additional token-specific methods
for (CoreLabel token : sentence.get(CoreAnnotations.TokensAnnotation.class)) {
String word = token.get(CoreAnnotations.TextAnnotation.class);
int previousWordIndex;
if (entity.equals(token.get(CoreAnnotations.NamedEntityTagAnnotation.class))) {
count++;
if (previousCount != 0 && (previousCount + 1) == count) {
previousWordIndex = matches.size() - 1;
String previousWord = matches.get(previousWordIndex);
matches.remove(previousWordIndex);
previousWord = previousWord.concat(" " + word);
matches.add(previousWordIndex, previousWord);
} else {
matches.add(word);
}
previousCount = count;
}
else
{
count=0;
previousCount=0;
}
}
}
return matches;
}
}
答案 7 :(得分:0)
处理多字实体的另一种方法。 如果这些代码具有相同的注释并且连续存在,则此代码将多个令牌组合在一起。
限制:
如果相同的令牌有两个不同的注释,则最后一个将被保存。
private Document getEntities(String fullText) {
Document entitiesList = new Document();
NERClassifierCombiner nerCombClassifier = loadNERClassifiers();
if (nerCombClassifier != null) {
List<List<CoreLabel>> results = nerCombClassifier.classify(fullText);
for (List<CoreLabel> coreLabels : results) {
String prevLabel = null;
String prevToken = null;
for (CoreLabel coreLabel : coreLabels) {
String word = coreLabel.word();
String annotation = coreLabel.get(CoreAnnotations.AnswerAnnotation.class);
if (!"O".equals(annotation)) {
if (prevLabel == null) {
prevLabel = annotation;
prevToken = word;
} else {
if (prevLabel.equals(annotation)) {
prevToken += " " + word;
} else {
prevLabel = annotation;
prevToken = word;
}
}
} else {
if (prevLabel != null) {
entitiesList.put(prevToken, prevLabel);
prevLabel = null;
}
}
}
}
}
return entitiesList;
}
进口:
Document: org.bson.Document;
NERClassifierCombiner: edu.stanford.nlp.ie.NERClassifierCombiner;