Multi-terme des entités nommées dans Stanford de Reconnaissance des entités Nommées
j'utilise le Stanford Named Entity Recognizer http://nlp.stanford.edu/software/CRF-NER.shtml et ça marche très bien. C'est
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());
}
}
}
cependant le problème que je trouve est d'identifier les noms et les noms de famille. Si le reconnaisseur rencontre "Joe Smith", il renvoie" Joe "et" Smith " séparément. J'aimerais vraiment qu'il revienne "Joe Smith" comme un seul terme.
Est-ce que cela pourrait être réalisé par le recognizer peut-être par une configuration? Je n'ai pas trouvé rien dans la javadoc jusqu'à maintenant.
Merci!
8 réponses
c'est parce que votre intérieur pour boucle itère sur des jetons individuels (mots) et les ajoute séparément. Vous devez changer les choses pour ajouter des noms entiers à la fois.
Une solution est de remplacer la boucle intérieure avec une boucle for avec une boucle while à l'intérieur qui prend adjacente non O les choses de la même classe et les ajoute comme une seule entité.*
une autre façon serait d'utiliser L'appel de méthode CRFClassifier:
List<Triple<String,Integer,Integer>> classifyToCharacterOffsets(String sentences)
qui vous donnera entière entities, dont vous pouvez extraire la forme de chaîne de caractères en utilisant substring
sur l'entrée d'origine.
la contrepartie de la méthode classifyToCharacterOffsets est que (AFAIK) vous ne pouvez pas accéder à l'étiquette des entités.
proposé par Christopher, voici un exemple de boucle qui assemble "non adjacents O les choses". Cet exemple compte également le nombre d'occurrences.
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;
}
j'ai eu le même problème, donc je l'ai regardé, trop. La méthode proposée par Christopher Manning est efficace, mais le point délicat est de savoir comment déterminer le type de séparateur est approprié. On pourrait dire que seul un espace devrait être autorisé, par exemple "John Zorn" >> une entité. Cependant, je peux trouver la forme "J. Zorn", donc je dois également autoriser certaines marques de ponctuation. Et "Jack, James et Joe"? Je pourrais avoir 2 entités au lieu de 3 ("Jack James" et "Joe").
En creusant un peu dans les cours de Stanford NER, j'ai trouvé une bonne implémentation de cette idée. Ils l'utiliser pour exporter les entités sous la forme de String
objets. Par exemple, dans la méthode PlainTextDocumentReaderAndWriter.printAnswersTokenizedInlineXML
nous avons:
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);
}
}
ils itèrent sur chaque mot, vérifiant s'il a la même classe (réponse) que la précédente, comme expliqué ci-dessus. Pour cela, ils tirent avantage du fait que les expressions considérées comme n'étant pas des entités sont marquées en utilisant ce qu'on appelle backgroundSymbol
(classe "E"). Ils également utiliser la propriété BeforeAnnotation
, qui représente la chaîne séparant le mot courant du mot précédent. Ce dernier point permet de résoudre le problème que j'ai soulevé, concernant le choix d'un séparateur.
Code:
<List> result = classifier.classifyToCharacterOffsets(text);
for (Triple<String, Integer, Integer> triple : result)
{
System.out.println(triple.first + " : " + text.substring(triple.second, triple.third));
}
List<List<CoreLabel>> out = classifier.classify(text);
for (List<CoreLabel> sentence : out) {
String s = "";
String prevLabel = null;
for (CoreLabel word : sentence) {
if(prevLabel == null || prevLabel.equals(word.get(CoreAnnotations.AnswerAnnotation.class)) ) {
s = s + " " + word;
prevLabel = word.get(CoreAnnotations.AnswerAnnotation.class);
}
else {
if(!prevLabel.equals("O"))
System.out.println(s.trim() + '/' + prevLabel + ' ');
s = " " + word;
prevLabel = word.get(CoreAnnotations.AnswerAnnotation.class);
}
}
if(!prevLabel.equals("O"))
System.out.println(s + '/' + prevLabel + ' ');
}
je viens d'écrire une petite logique, et ça fonctionne très bien. ce que j'ai fait, c'est regrouper les mots avec la même étiquette s'ils sont adjacents.
Utilisez les classificateurs déjà fournis. Je crois que c'est ce que vous êtes à la recherche de:
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);
}
Voici mon code complet, J'utilise le noyau Stanford NLP et j'écris l'algorithme pour concaténer les noms de plusieurs termes.
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;
}
}
une autre approche pour traiter des entités multi mots. Ce code combine plusieurs tokens ensemble s'ils ont la même annotation et vont dans une rangée.
Origine:
Si même a deux différentes annotations, le dernier sera enregistré.
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;
}
Importations:
Document: org.bson.Document;
NERClassifierCombiner: edu.stanford.nlp.ie.NERClassifierCombiner;