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!

19
demandé sur Krt_Malta 2012-12-07 18:45:42

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.

20
répondu Christopher Manning 2012-12-18 21:32:58

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;
}
5
répondu Remi Mélisson 2016-10-10 08:47:46

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.

3
répondu Vincent Labatut 2016-01-07 08:24:14

Code:

<List> result = classifier.classifyToCharacterOffsets(text);

for (Triple<String, Integer, Integer> triple : result)
{
    System.out.println(triple.first + " : " + text.substring(triple.second, triple.third));
}
2
répondu user21513 2015-06-20 10:02:37
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.

2
répondu Hari 2016-03-24 13:55:10

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);
}
1
répondu Gavy 2016-10-29 13:39:09

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;
}
}
0
répondu Chanuka 2014-08-29 04:37:36

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;
0
répondu Mike B. 2016-03-24 16:53:35