Introduksjon til Apache OpenNLP

1. Oversikt

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

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

I denne opplæringen vil vi se på hvordan du bruker denne API-en til forskjellige brukstilfeller.

2. Maven-oppsett

Først må vi legge til den viktigste avhengigheten til vår pom.xml:

 org.apache.opennlp opennlp-verktøy 1.8.4 

Den siste stabile versjonen finner du på Maven Central.

Noen brukssaker trenger trente modeller. Du kan laste ned forhåndsdefinerte modeller her og detaljert informasjon om disse modellene her.

3. Setningsregistrering

La oss begynne med å forstå hva en setning er.

Setningsoppdagelse handler om å identifisere begynnelsen og slutten av en setning, som vanligvis avhenger av språket som er tilgjengelig. Dette kalles også "Sentence Boundary Disambiguation" (SBD).

I noen tilfeller, setningsdeteksjon er ganske utfordrende på grunn av den tvetydige karakteren til periodekarakteren. En periode betegner vanligvis slutten av en setning, men kan også vises i en e-postadresse, en forkortelse, en desimal og mange andre steder.

Som for de fleste NLP-oppgaver, for setningsdeteksjon, trenger vi en trent modell som input, som vi forventer å ligge i / ressurser mappe.

For å implementere setningsdeteksjon laster vi inn modellen og sender den til en forekomst av SentenceDetectorME. Så sender vi ganske enkelt en tekst inn i sentDetect () metode for å dele den ved setningsgrensene:

@Test offentlig tomrom gittEnglishModel_whenDetect_thenSentencesAreDetected () kaster Unntak {String avsnitt = "Dette er en uttalelse. Dette er en annen uttalelse." + "Nå er det et abstrakt ord for tid," + "som alltid flyr. Og e-postadressen min er [e-postbeskyttet]"; InputStream er = getClass (). GetResourceAsStream ("/ models / en-sent.bin"); SentenceModel model = new SentenceModel (is); SentenceDetectorME sdetector = ny SentenceDetectorME (modell); Strengsetninger [] = sdetector.sentDetect (avsnitt); assertThat (setninger) .contains ("Dette er en uttalelse.", "Dette er en annen uttalelse.", "Nå er et abstrakt ord for tid, som alltid flyr.", "Og e-postadressen min er [e-postbeskyttet]" ); }

Merk:suffikset “ME” brukes i mange klassenavn i Apache OpenNLP og representerer en algoritme som er basert på “Maximum Entropy”.

4. Tokenisering

Nå som vi kan dele et tekstkorps i setninger, kan vi begynne å analysere en setning mer detaljert.

Målet med tokenisering er å dele en setning i mindre deler som kalles tokens. Vanligvis er disse tokens ord, tall eller skilletegn.

Det er tre typer tokenizers tilgjengelig i OpenNLP.

4.1. Ved hjelp av TokenizerME

I dette tilfellet må vi først laste inn modellen. Vi kan laste ned modellfilen herfra, legge den i / ressurser mappen og last den derfra.

Deretter oppretter vi en forekomst av TokenizerME ved hjelp av den lastede modellen, og bruk tokenize () metode for å utføre tokenisering på hvilken som helst Streng:

@Test offentlig ugyldighet gittEnglishModel_whenTokenize_thenTokensAreDetected () kaster Unntak {InputStream inputStream = getClass () .getResourceAsStream ("/ models / en-token.bin"); TokenizerModel model = ny TokenizerModel (inputStream); TokenizerME tokenizer = ny TokenizerME (modell); String [] tokens = tokenizer.tokenize ("Baeldung er en vårressurs."); assertThat (tokens) .contains ("Baeldung", "is", "a", "Spring", "Resource", "."); }

Som vi kan se, har tokeniser identifisert alle ord og periodekarakter som separate tokens. Denne tokenizer kan også brukes med en tilpasset opplært modell.

4.2. WhitespaceTokenizer

Som navnet antyder, deler denne tokenizeren bare setningen i tokens ved å bruke mellomromstegn som skilletegn:

@Test offentlig ugyldighet givenWhitespaceTokenizer_whenTokenize_thenTokensAreDetected () kaster Unntak {WhitespaceTokenizer tokenizer = WhitespaceTokenizer.INSTANCE; String [] tokens = tokenizer.tokenize ("Baeldung er en vårressurs."); assertThat (tokens) .contains ("Baeldung", "is", "a", "Spring", "Resource."); }

Vi kan se at setningen er delt av hvite mellomrom, og derfor får vi "Ressurs". (med periodetegnet på slutten) som et enkelt token i stedet for to forskjellige tokens for ordet “Ressurs” og periodetegnet.

4.3. SimpleTokenizer

Denne tokenizer er litt mer sofistikert enn WhitespaceTokenizer og deler setningen i ord, tall og skilletegn. Det er standard oppførsel og krever ingen modell:

@Test offentlig ugyldighet givenSimpleTokenizer_whenTokenize_thenTokensAreDetected () kaster Unntak {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer .tokenize ("Baeldung is a Spring Resource."); assertThat (tokens) .contains ("Baeldung", "is", "a", "Spring", "Resource", "."); }

5. Navngitt enhetsgjenkjenning

Nå som vi har forstått tokenisering, la oss ta en titt på en første brukssak som er basert på vellykket tokenisering: heter enhetsgjenkjenning (NER).

Målet med NER er å finne navngitte enheter som mennesker, lokasjoner, organisasjoner og andre navngitte ting i en gitt tekst.

OpenNLP bruker forhåndsdefinerte modeller for personnavn, dato og klokkeslett, steder og organisasjoner. Vi må laste modellen med TokenNameFinderModel oggi den til en forekomst av NameFinderME. Da kan vi bruke finne() metode for å finne navngitte enheter i en gitt tekst:

@Test public void givenEnglishPersonModel_whenNER_thenPersonsAreDetected () kaster Unntak {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer .tokenize ("John er 26 år gammel. Hans beste venns" + "navn er Leonard. Han har en søster som heter Penny."); InputStream inputStreamNameFinder = getClass () .getResourceAsStream ("/ models / en-ner-person.bin"); TokenNameFinderModel model = ny TokenNameFinderModel (inputStreamNameFinder); NameFinderME nameFinderME = ny NameFinderME (modell); Liste spenner = 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 som inneholder start- og sluttindeksene til tokens som komponerer navngitte enheter i teksten.

6. Merking av tale-for-tale

En annen brukssak som trenger en liste over tokens som input er merking av tale.

En del av tale (POS) identifiserer typen ord. OpenNLP bruker følgende koder for de forskjellige delene av talen:

  • NN - substantiv, entall eller masse
  • DT - bestemmer
  • VB - verb, grunnform
  • VBD - verb, fortid
  • VBZ - verb, tredje person entall presens
  • IN - preposisjon eller underordnet sammenheng
  • NNP - egennavn, entall
  • TIL - ordet “til”
  • JJ - adjektiv

Dette er de samme kodene som definert i Penn Tree Bank. For en komplett liste, se denne listen.

I likhet med NER-eksemplet, laster vi inn riktig modell og bruker deretter POSTaggerME og dens metode stikkord() på et sett med tokens for å merke setningen:

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

De stikkord() metoden kartlegger tokens i en liste over POS-koder. Resultatet i eksemplet er:

  1. “John” - NNP (substantiv)
  2. “Har” - VBZ (verb)
  3. “A” - DT (determiner)
  4. “Søster” - NN (substantiv)
  5. “Navngitt” - VBZ (verb)
  6. "Øre" -NNP (substantiv)
  7. "." - periode

7. Lemmatisering

Nå som vi har taleinformasjonen til tokens i en setning, kan vi analysere teksten ytterligere.

Lemmatisering er prosessen med å kartlegge en ordform som kan ha anspent, kjønn, humør eller annen informasjon til grunnformen av ordet - også kalt "lemma".

En lemmatizer tar et token og dets del-av-tale-koden som input og returnerer ordets lemma. Derfor, før lemmatisering, skal setningen sendes gjennom en tokenizer og POS-tagger.

Apache OpenNLP tilbyr to typer lemmatisering:

  • Statistisk - trenger en lemmatizer-modell bygget med treningsdata for å finne lemmaet til et gitt ord
  • Ordboksbasert - krever en ordbok som inneholder alle gyldige kombinasjoner av et ord, POS-koder og tilhørende lemma

For statistisk lemmatisering trenger vi å trene en modell, mens vi for ordbokslemmatisering trenger bare en ordlistefil som denne.

La oss se på et kodeeksempel ved hjelp av en ordlistefil:

@Test public void givenEnglishDictionary_whenLemmatize_thenLemmasAreDetected () kaster Unntak {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer.tokenize ("John har en søster som heter Penny."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = ny POSModell (inputStreamPOSTagger); POSTaggerME posTagger = ny POSTaggerME (posModel); Strengmerker [] = 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, ettersom ordet er et substantiv. Så vi har ikke et lemma for "John" og "Penny".

Men vi har identifisert lemmaene for de andre ordene i setningen:

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

8. Chunking

Informasjon om tale-tale er også viktig i klumping - dele setninger i grammatisk meningsfulle ordgrupper som substantivgrupper eller verbgrupper.

I likhet med før, tokeniserer vi en setning og bruker merking av tale på tokens før vi ringer til klump () metode:

@Test offentlig ugyldighet givenChunkerModel_whenChunk_thenChunksAreDetected () kaster Unntak {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokens = tokenizer.tokenize ("Han regner at underskuddet på betalingsbalansen vil begrense seg til bare 8 milliarder."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = ny POSModell (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 [] biter = chunker.chunk (tokens, tags); assertThat (biter). inneholder ("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 utdata for hvert token fra chunkeren. "B" representerer starten på en del, "I" representerer fortsettelsen av klumpen og "O" representerer ingen klump.

Når vi analyserer utdataene fra eksemplet vårt, får vi 6 biter:

  1. “Han” - substantivuttrykk
  2. “Regner” - verbuttrykk
  3. “The current account underskudd” - substantiv setning
  4. “Vil begrense” - verbuttrykk
  5. "Til" - setningsuttrykk
  6. “Bare 8 milliarder” - substantivuttrykk

9. Språkoppdagelse

I tillegg til brukssakene som allerede er diskutert, OpenNLP tilbyr også et språkoppdagelses-API som gjør det mulig å identifisere språket til en bestemt tekst.

For språkgjenkjenning trenger vi en treningsdatafil. En slik fil inneholder linjer med setninger på et bestemt språk. Hver linje er merket med riktig språk for å gi input til maskinlæringsalgoritmene.

En eksempeldatafil for språkoppdagelse kan lastes ned her

Vi kan laste treningsdatafilen inn i en LanguageDetectorSampleStream, definere noen treningsdataparametere, lage en modell og deretter bruke modellen til å oppdage språket i en tekst:

@Test offentlig ugyldighet gittLanguageDictionary_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, new LanguageDetectorFactory ()); LanguageDetector ld = ny LanguageDetectorME (modell); Språk [] språk = ld .predictLanguages ​​("estava em uma marcenaria na Rua Bruno"); assertThat (Arrays.asList (språk)) .extracting ("lang", "confidence") .contains (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 sannsynlige språkene sammen med en tillits score.

Og med rike modeller kan vi oppnå en veldig høyere nøyaktighet med denne typen deteksjon.

5. Konklusjon

Vi utforsket mye her, fra de interessante mulighetene til OpenNLP. Vi fokuserte på noen interessante funksjoner for å utføre NLP-oppgaver som lemmatisering, POS-merking, tokenisering, setningsregistrering, språkoppdagelse og mer.

Som alltid kan fullstendig implementering av alt ovenfor bli funnet på GitHub.