Introduktion til Apache OpenNLP

1. Oversigt

Apache OpenNLP er et open source Natural Language Processing Java-bibliotek.

Den har en API til brugssager som Named Entity Recognition, Sentence Detection, POS tagging og Tokenization.

I denne vejledning skal vi se på, hvordan du bruger denne API til forskellige brugssager.

2. Maven-opsætning

Først skal vi tilføje den vigtigste afhængighed af vores pom.xml:

 org.apache.opennlp opennlp-værktøjer 1.8.4 

Den seneste stabile version kan findes på Maven Central.

Nogle brugssager har brug for uddannede modeller. Du kan downloade foruddefinerede modeller her og detaljerede oplysninger om disse modeller her.

3. Detektion af sætninger

Lad os starte med at forstå, hvad en sætning er.

Setningsopdagelse handler om at identificere starten og slutningen af ​​en sætning, som normalt afhænger af det aktuelle sprog. Dette kaldes også "Sentence Boundary Disambiguation" (SBD).

I nogle tilfælde, sætningsregistrering er ret udfordrende på grund af periodens karakters tvetydige karakter. En periode betegner normalt slutningen af ​​en sætning, men kan også vises i en e-mail-adresse, en forkortelse, et decimal og mange andre steder.

Som for de fleste NLP-opgaver, til sætningsregistrering, har vi brug for en uddannet model som input, som vi forventer at opholde sig i / ressourcer folder.

For at implementere sætningsregistrering indlæser vi modellen og sender den til en instans af SentenceDetectorME. Derefter sender vi simpelthen en tekst ind i sentDetect () metode til at opdele det ved sætningsgrænserne:

@Test offentlig ugyldighed givenEnglishModel_whenDetect_thenSentencesAreDetected () kaster undtagelse {String afsnit = "Dette er en erklæring. Dette er en anden erklæring." + "Nu er et abstrakt ord for tid," + "der altid flyver. Og min e-mail-adresse er [e-mailbeskyttet]"; InputStream er = getClass (). GetResourceAsStream ("/ models / en-sent.bin"); SentenceModel model = ny SentenceModel (er); SentenceDetectorME sdetector = ny SentenceDetectorME (model); Strengsætninger [] = sdetector.sentDetect (afsnit); assertThat (sætninger). indeholder ("Dette er en erklæring.", "Dette er en anden erklæring.", "Nu er et abstrakt ord for tid, der altid flyver.", "Og min e-mail-adresse er [e-mail-beskyttet]" ); }

Bemærk:suffikset “ME” bruges i mange klassenavne i Apache OpenNLP og repræsenterer en algoritme, der er baseret på “Maximum Entropy”.

4. Tokenisering

Nu hvor vi kan opdele et tekstkorpus i sætninger, kan vi begynde at analysere en sætning mere detaljeret.

Målet med tokenisering er at opdele en sætning i mindre dele kaldet tokens. Normalt er disse tokens ord, tal eller tegnsætningstegn.

Der er tre typer tokenizers tilgængelige i OpenNLP.

4.1. Ved brug af TokenizerME

I dette tilfælde skal vi først indlæse modellen. Vi kan downloade modelfilen herfra og lægge den i / ressourcer mappe og indlæse den derfra.

Derefter opretter vi en forekomst af TokenizerME ved hjælp af den indlæste model, og brug tokenize () metode til at udføre tokenisering på enhver Snor:

@Test offentlig ugyldighed givenEnglishModel_whenTokenize_thenTokensAreDetected () kaster undtagelse {InputStream inputStream = getClass () .getResourceAsStream ("/ models / en-token.bin"); TokenizerModel model = ny TokenizerModel (inputStream); TokenizerME tokenizer = ny TokenizerME (model); String [] tokens = tokenizer.tokenize ("Baeldung er en springressource."); assertThat (tokens) .contains ("Baeldung", "er", "a", "Spring", "Resource", "."); }

Som vi kan se, har tokenizer identificeret alle ord og periodekarakter som separate tokens. Denne tokenizer kan også bruges med en tilpasset trænet model.

4.2. WhitespaceTokenizer

Som navnet antyder, deler denne tokenizer simpelthen sætningen i tokens ved hjælp af mellemrumstegn som afgrænsere:

@Test offentlig ugyldighed givenWhitespaceTokenizer_whenTokenize_thenTokensAreDetected () kaster Undtagelse {WhitespaceTokenizer tokenizer = WhitespaceTokenizer.INSTANCE; String [] tokens = tokenizer.tokenize ("Baeldung er en springressource."); assertThat (tokens) .contains ("Baeldung", "er", "a", "Spring", "Resource."); }

Vi kan se, at sætningen er opdelt af hvide rum, og derfor får vi "Ressource." (med periodetegnet i slutningen) som et enkelt token i stedet for to forskellige tokens for ordet “Resource” og periodetegnet.

4.3. SimpleTokenizer

Denne tokenizer er lidt mere sofistikeret end WhitespaceTokenizer og deler sætningen i ord, tal og tegnsætningstegn. Det er standardadfærden og kræver ingen model:

@Test offentlig ugyldighed givenSimpleTokenizer_whenTokenize_thenTokensAreDetected () kaster undtagelse {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer .tokenize ("Baeldung er en springressource."); assertThat (tokens) .contains ("Baeldung", "er", "a", "Spring", "Resource", "."); }

5. Navngivet enhedsgenkendelse

Nu hvor vi har forstået tokenisering, lad os se på en første brugssag, der er baseret på vellykket tokenisering: navngivet enhedsgenkendelse (NER).

Målet med NER er at finde navngivne enheder som mennesker, placeringer, organisationer og andre navngivne ting i en given tekst.

OpenNLP bruger foruddefinerede modeller til personnavne, dato og klokkeslæt, placeringer og organisationer. Vi er nødt til at indlæse modellen ved hjælp af TokenNameFinderModel ogvideregive det til en instans af NameFinderME. Så kan vi bruge finde() metode til at finde navngivne enheder i en given tekst:

@Test offentlig ugyldighed givenEnglishPersonModel_whenNER_thenPersonsAreDetected () kaster Undtagelse {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer .tokenize ("John er 26 år gammel. Hans bedste vens" + "navn er Leonard. Han har en søster ved navn Penny."); InputStream inputStreamNameFinder = getClass () .getResourceAsStream ("/ models / en-ner-person.bin"); TokenNameFinderModel model = ny TokenNameFinderModel (inputStreamNameFinder); NameFinderME nameFinderME = ny NameFinderME (model); List spans = Arrays.asList (nameFinderME.find (tokens)); assertThat (spans.toString ()) .isEqualTo ("[[0..1) person, [13..14) person, [20..21) person]"); }

Som vi kan se i påstanden, er resultatet en liste over Span objekter, der indeholder start- og slutindeks for tokens, der sammensætter navngivne enheder i teksten.

6. Mærkning af del-af-tale

En anden brugssag, der har brug for en liste over tokens som input, er tagging af tale.

En del af tale (POS) identificerer typen af ​​et ord. OpenNLP bruger følgende tags til de forskellige sprogdele:

  • NN - substantiv, ental eller masse
  • DT - bestemmende
  • VB - verb, basisform
  • VBD - verb, fortid
  • VBZ - verb, tredje person ental til stede
  • IN - præposition eller underordnet sammenhæng
  • NNP - egennavn, ental
  • TIL - ordet "til"
  • JJ - adjektiv

Disse er de samme tags som defineret i Penn Tree Bank. For en komplet liste henvises til denne liste.

I lighed med NER-eksemplet indlæser vi den relevante model og bruger derefter POSTaggerME og dens metode tag () på et sæt tokens for at tagge sætningen:

@Test offentlig ugyldighed givenPOSModel_whenPOSTagging_thenPOSAreDetected () kaster undtagelse {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer.tokenize ("John har en søster ved navn Penny."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = ny POSModel (inputStreamPOSTagger); POSTaggerME posTagger = ny POSTaggerME (posModel); String tags [] = posTagger.tag (tokens); assertThat (tags). indeholder ("NNP", "VBZ", "DT", "NN", "VBN", "NNP", "."); }

Det tag () metode kortlægger tokens i en liste over POS-tags. Resultatet i eksemplet er:

  1. "John" - NNP (substantiv)
  2. “Har” - VBZ (verb)
  3. “A” - DT (determinator)
  4. “Søster” - NN (substantiv)
  5. “Navngivet” - VBZ (verb)
  6. “Penny” -NNP (egennavn)
  7. “.” - periode

7. Lemmatisering

Nu hvor vi har toppen i tale i en sætning, kan vi analysere teksten yderligere.

Lemmatisering er processen med at kortlægge en ordform der kan have en anspændt, køn, stemning eller andre oplysninger til ordets basisform - også kaldet dets “lemma”.

En lemmatizer tager et token og dets del af tale-tag som input og returnerer ordets lemma. Derfor bør sætningen før lemmatisering sendes gennem en tokenizer og POS-tagger.

Apache OpenNLP giver to typer lemmatisering:

  • Statistisk - har brug for en lemmatizer-model bygget med træningsdata til at finde lemmaet for et givet ord
  • Ordbog-baseret - kræver en ordbog, der indeholder alle gyldige kombinationer af et ord, POS-tags og det tilsvarende lemma

For statistisk lemmatisering er vi nødt til at træne en model, mens vi til ordbogslemmatiseringen kun har brug for en ordbogfil som denne.

Lad os se på et kodeeksempel ved hjælp af en ordbogsfil:

@Test offentlig ugyldighed givenEnglishDictionary_whenLemmatize_thenLemmasAreDetected () kaster Undtagelse {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer.tokenize ("John har en søster ved navn Penny."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = ny POSModel (inputStreamPOSTagger); POSTaggerME posTagger = ny POSTaggerME (posModel); String tags [] = posTagger.tag (tokens); InputStream dictLemmatizer = getClass () .getResourceAsStream ("/ models / en-lemmatizer.dict"); DictionaryLemmatizer lemmatizer = new DictionaryLemmatizer (dictLemmatizer); String [] lemmas = lemmatizer.lemmatize (tokens, tags); assertThat (lemmas) .contains ("O", "have", "a", "sister", "name", "O", "O"); }

Som vi kan se, får vi lemmaet for hvert symbol. “O” indikerer, at lemmaet ikke kunne bestemmes, da ordet er et selvstændigt navneord. Så vi har ikke et lemma for "John" og "Penny".

Men vi har identificeret lemmas for de andre ord i sætningen:

  • har har
  • a - a
  • søster - søster
  • navngivet - navn

8. klumpning

Oplysninger om tale-ord er også vigtige i chunking - at opdele sætninger i grammatisk meningsfulde ordgrupper som substantivgrupper eller verbgrupper.

På samme måde som før, tokeniserer vi en sætning og bruger tag-til-tag-mærkning på tokens før vi kalder på luns() metode:

@Test offentlig ugyldighed givenChunkerModel_whenChunk_thenChunksAreDetected () kaster undtagelse {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer.tokenize ("Han regner med, at underskuddet på betalingsbalancen vil kun indsnævres til kun 8 mia."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = ny POSModel (inputStreamPOSTagger); POSTaggerME posTagger = ny POSTaggerME (posModel); String tags [] = posTagger.tag (tokens); InputStream inputStreamChunker = getClass () .getResourceAsStream ("/ models / en-chunker.bin"); ChunkerModel chunkerModel = ny ChunkerModel (inputStreamChunker); ChunkerME chunker = ny ChunkerME (chunkerModel); Streng [] klumper = chunker.chunk (tokens, tags); assertThat (bidder) .indholder ("B-NP", "B-VP", "B-NP", "I-NP", "I-NP", "I-NP", "B-VP", " I-VP "," B-PP "," B-NP "," I-NP "," I-NP "," O "); }

Som vi kan se, får vi en output for hvert token fra chunkeren. "B" repræsenterer starten på en klump, "I" repræsenterer fortsættelsen af ​​klumpen og "O" repræsenterer ingen klump.

Når vi analyserer output fra vores eksempel, får vi 6 bidder:

  1. “Han” - navneordssætning
  2. “Regner” - udsagnsord
  3. “Underskuddet på den løbende konto” - navneordssætning
  4. "Vil indsnævre" - udsagnsord
  5. "Til" - sætning med præposition
  6. “Kun 8 milliarder” - navneordssætning

9. Sprogpåvisning

Ud over de anvendelsessager, der allerede er diskuteret, OpenNLP giver også en sprogdetekterings-API, der gør det muligt at identificere sproget i en bestemt tekst.

Til sprogpåvisning har vi brug for en træningsdatafil. En sådan fil indeholder linjer med sætninger på et bestemt sprog. Hver linje er mærket med det korrekte sprog for at give input til maskinindlæringsalgoritmerne.

En prøve træningsdatafil til sprogpåvisning kan downloades her.

Vi kan indlæse træningsdatafilen i en LanguageDetectorSampleStream, definere nogle træningsdataparametre, oprette en model og derefter bruge modellen til at registrere sproget i en tekst:

@Test offentlig ugyldighed givenLanguageDictionary_whenLanguageDetect_thenLanguageIsDetected () kaster FileNotFoundException, IOException {InputStreamFactory dataIn = ny MarkableFileInputStreamFactory (ny fil ("src / main / resources / models / DoccatSample.txt)) ObjectStream lineStream = ny PlainTextByLineStream (dataIn, "UTF-8"); LanguageDetectorSampleStream sampleStream = ny LanguageDetectorSampleStream (lineStream); TrainingParameters params = nye TrainingParameters (); params.put (TrainingParameters.ITERATIONS_PARAM, 100); params.put (TrainingParameters.CUTOFF_PARAM, 5); params.put ("DataIndexer", "TwoPass"); params.put (TrainingParameters.ALGORITHM_PARAM, "NAIVEBAYES"); LanguageDetectorModel model = LanguageDetectorME .train (sampleStream, params, ny LanguageDetectorFactory ()); LanguageDetector ld = ny LanguageDetectorME (model); Sprog [] sprog = ld .predictLanguages ​​("estava em uma marcenaria na Rua Bruno"); assertThat (Arrays.asList (sprog)) .ekstraktion ("lang", "tillid"). indeholder (tuple ("pob", 0.9999999950605625), tuple ("ita", 4.939427661577956E-9), tuple ("spa", 9.665954064665144E-15), tuple ("fra", 8.250349924885834E-25))); }

Resultatet er en liste over de mest sandsynlige sprog sammen med en tillidsscore.

Og med rige modeller kan vi opnå en meget højere nøjagtighed med denne type detektion.

5. Konklusion

Vi udforskede meget her fra de interessante muligheder i OpenNLP. Vi fokuserede på nogle interessante funktioner til at udføre NLP-opgaver som lemmatisering, POS-tagging, Tokenization, Sentence Detection, Language Detection og mere.

Som altid kan den komplette implementering af alt ovenfor findes på GitHub.