Bevezetés az Apache OpenNLP-be

1. Áttekintés

Az Apache OpenNLP egy nyílt forráskódú Natural Language Processing Java könyvtár.

API-t tartalmaz olyan felhasználási esetekhez, mint a Named Entity Recognition, Sentence Detection, POS tagging és Tokenization.

Ebben az oktatóanyagban megvizsgáljuk, hogyan lehet ezt az API-t különböző felhasználási esetekben használni.

2. Maven Setup

Először hozzá kell adnunk a fő függőséget a sajátunkhoz pom.xml:

 org.apache.opennlp opennlp-tools 1.8.4 

A legújabb stabil verzió megtalálható a Maven Central oldalán.

Egyes használati esetekhez képzett modellekre van szükség. Az előre definiált modelleket itt töltheti le, és részletes információkat a modellekről itt.

3. Mondatfelismerés

Kezdjük azzal, hogy megértsük, mi az a mondat.

A mondatészlelés a mondat kezdetének és végének azonosításáról szól, amely általában az adott nyelvtől függ. Ezt nevezzük „mondathatár-pontosításnak” (SBD) is.

Egyes esetekben, a mondatdetektálás meglehetősen nagy kihívást jelent a periódus karakterének kétértelmű jellege miatt. A pont általában a mondat végét jelöli, de megjelenhet e-mail címben, rövidítésben, tizedesjegyben és sok más helyen is.

Ami a legtöbb NLP feladatot illeti, a mondatészleléshez bemenetként egy képzett modellre van szükségünk, amely várhatóan a /erőforrások mappába.

A mondatészlelés megvalósításához betöltjük a modellt és átadjuk a SentenceDetectorME. Ezután egyszerűen átadunk egy szöveget a sentDetect () módszer a mondathatárok felosztásához:

@Test public void givenEnglishModel_whenDetect_thenSentencesAreDetected () dobja a Kivételt {String paragrafus = "Ez egy utasítás. Ez egy másik állítás." + "Most egy elvont szó az időre," + ", amely mindig repül. És az e-mail címem [e-mail védett]"; Az InputStream = getClass (). GetResourceAsStream ("/ models / en-sent.bin"); SentenceModel model = új SentenceModel (is); SentenceDetectorME sdetector = új SentenceDetectorME (modell); Karaktersorozatok [] = sdetector.sentDetect (bekezdés); assertThat (mondatok) .contains ("Ez egy állítás.", "Ez egy másik állítás.", "Most egy elvont szó az időre, amely mindig repül.", "És az e-mail címem [e-mail védett]" ); }

Jegyzet:az „ME” utótagot számos osztálynévben használják az Apache OpenNLP-ben, és ez egy olyan algoritmust képvisel, amely a „Maximum Entrópia” alapú.

4. Tokenizálás

Most, hogy egy szövegtestet mondatokra oszthatunk, elkezdhetjük egy mondat részletesebb elemzését.

A tokenizálás célja az, hogy egy mondatot kisebb részekre bontsunk, amelyeket tokeneknek nevezünk. Ezek a tokenek általában szavak, számok vagy írásjelek.

Három típusú tokenizátor érhető el az OpenNLP-ben.

4.1. Használata TokenizerME

Ebben az esetben először be kell töltenünk a modellt. A modellfájlt innen tölthetjük le, helyezhetjük a /erőforrások mappába, és onnan töltse be.

Ezután létrehozunk egy példányt TokenizerME a betöltött modell használatával, és a tokenize () módszer bármelyik tokenizálásának végrehajtására Húr:

@Test public void givenEnglishModel_whenTokenize_thenTokensAreDetected () dobja a Kivételt {InputStream inputStream = getClass () .getResourceAsStream ("/ models / en-token.bin"); TokenizerModel model = új TokenizerModel (inputStream); TokenizerME tokenizer = új TokenizerME (modell); String [] tokenek = tokenizer.tokenize ("A Baeldung egy tavaszi erőforrás."); assertThat (tokenek). tartalmaz ("Baeldung", "is", "a", "Spring", "Resource", "."); }

Mint láthatjuk, a tokenizer az összes szót és a periódus karaktert külön tokenekként azonosította. Ez a tokenizer egyedi képzett modellel is használható.

4.2. WhitespaceTokenizer

Ahogy a neve is sugallja, ez a tokenizer egyszerűen szétválasztja a mondatot tokenekké, szóközöket használva elválasztóként:

@Test public void givenWhitespaceTokenizer_whenTokenize_thenTokensAreDetected () dobja a Kivételt {WhitespaceTokenizer tokenizer = WhitespaceTokenizer.INSTANCE; String [] tokenek = tokenizer.tokenize ("A Baeldung egy tavaszi erőforrás."); assertThat (tokenek). tartalmaz ("Baeldung", "is", "a", "Spring", "Resource"); }

Láthatjuk, hogy a mondatot fehér szóközök tagolták, és így „Erőforrást” kapunk. (a periódus karakterrel a végén) egyetlen jelsorozatként, az „Erőforrás” szó és a periódus karakter két különböző jelző helyett.

4.3. SimpleTokenizer

Ez a tokenizer egy kicsit kifinomultabb, mint WhitespaceTokenizer és szavakat, számokat és írásjeleket oszt a mondatra. Ez az alapértelmezett viselkedés, és nem igényel semmilyen modellt:

@Test public void givenSimpleTokenizer_whenTokenize_thenTokensAreDetected () dobja a Kivételt {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokenek = tokenizer .tokenize ("A Baeldung egy tavaszi erőforrás."); assertThat (tokenek). tartalmaz ("Baeldung", "is", "a", "Spring", "Resource", "."); }

5. Elnevezett entitás-felismerés

Most, hogy megértettük a tokenizálást, vessünk egy pillantást az első felhasználási esetre, amely a sikeres tokenizáláson alapul: a nevezett entitásfelismerés (NER).

A NER célja megnevezett entitások, például emberek, helyek, szervezetek és egyéb megnevezett dolgok megtalálása egy adott szövegben.

Az OpenNLP előre meghatározott modelleket használ a személynevekhez, a dátumhoz és az időhöz, a helyekhez és a szervezetekhez. A modell használatával kell betöltenünk TokenNameFinderModel ésadja át a NameFinderME. Akkor használhatjuk a megtalálja() módszer a megnevezett entitások megkeresésére egy adott szövegben:

@Test public void givenEnglishPersonModel_whenNER_thenPersonsAreDetected () dobja a Kivételt {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokenek = tokenizer .tokenize ("John 26 éves. Legjobb barátja" + "neve Leonard. Van egy húga, Penny."); InputStream inputStreamNameFinder = getClass () .getResourceAsStream ("/ models / en-ner-person.bin"); TokenNameFinderModel model = új TokenNameFinderModel (inputStreamNameFinder); NameFinderME nameFinderME = új NameFinderME (modell); Fesztávolságok listája = Arrays.asList (nameFinderME.find (tokenek)); assertThat (spans.toString ()) .isEqualTo ("[[0..1) személy, [13..14) személy, [20..21) személy]"); }

Amint az állításban láthatjuk, az eredmény egy Span a tokenek kezdő és befejező indexét tartalmazó objektumok, amelyek megnevezett entitásokat alkotnak a szövegben.

6. Beszédrész címkézése

Egy másik olyan felhasználási eset, amelyhez a tokenek listájára van szükség bemenetként, a beszédrész tagje.

A beszédrész (POS) azonosítja a szó típusát. Az OpenNLP a következő címkéket használja a beszéd különböző részeihez:

  • NN - főnév, egyes szám vagy tömeg
  • DT - meghatározó
  • VB - ige, alapforma
  • VBD - ige, múlt idő
  • VBZ - ige, egyes személy harmadik személy jelen
  • BAN BEN - elöljárószó vagy alárendelő kötőszó
  • NNP - tulajdonnév, egyes szám
  • NAK NEK - a „to” szó
  • JJ - melléknév

Ezek ugyanazok a címkék, mint amelyeket a Penn Tree Bank definiált. A teljes listát lásd ebben a listában.

A NER példához hasonlóan betöltjük a megfelelő modellt, majd felhasználjuk POSTaggerME és annak módszere címke() a mondat címkézésén:

@Test public void givenPOSModel_whenPOSTagging_thenPOSAreDetected () dobja a Kivételt {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokenek = tokenizer.tokenize ("Johnnak van Penny nevű nővére."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = új POSModel (inputStreamPOSTagger); POSTaggerME posTagger = new POSTaggerME (posModel); Karakterláncok [] = posTagger.tag (tokenek); assertThat (tagek). tartalmaz ("NNP", "VBZ", "DT", "NN", "VBN", "NNP", "."); }

A címke() A metódus a tokeneket a POS címkék listájába térképezi. A példa eredménye:

  1. „John” - NNP (tulajdonnév)
  2. „Van” - VBZ (ige)
  3. „A” - DT (meghatározó)
  4. „Nővér” - NN (főnév)
  5. „Megnevezett” - VBZ (ige)
  6. „Penny” -NNP (tulajdonnév)
  7. „.” - időszak

7. Lemmatization

Most, hogy egy mondatban megvan a tokenek beszédrészére vonatkozó információ, még tovább elemezhetjük a szöveget.

A lemmatizálás a szóalak feltérképezésének folyamata ennek feszültsége, neme, hangulata vagy más információja lehet a szó alapalakjára - amelyet „lemmának” is neveznek.

A lemmatizer egy tokent és beszédrész címkét vesz be bevitelként, és a szó lemmáját adja vissza. Ezért a Lemmatization előtt a mondatot egy tokenizeren és POS taggeren kell átadni.

Az Apache OpenNLP kétféle lemmatizációt kínál:

  • Statisztikai - szüksége van egy lemmatizer modellre, amely képzési adatok alapján épül fel az adott szó lemma megtalálásához
  • Szótár alapú - szótárra van szükség, amely tartalmazza a szó, a POS tagek és a megfelelő lemma összes érvényes kombinációját

A statisztikai lemmatizáláshoz modellt kell kiképeznünk, míg a szótár lemmatizálásához csak egy ehhez hasonló szótárfájlra van szükségünk.

Nézzünk meg egy kód példát egy szótárfájl segítségével:

@Test public void givenEnglishDictionary_whenLemmatize_thenLemmasAreDetected () dobja a Kivételt {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokenek = tokenizer.tokenize ("Johnnak van Penny nevű nővére."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = új POSModel (inputStreamPOSTagger); POSTaggerME posTagger = new POSTaggerME (posModel); Karakterláncok [] = posTagger.tag (tokenek); InputStream dictLemmatizer = getClass () .getResourceAsStream ("/ models / hu-lemmatizer.dict"); DictionaryLemmatizer lemmatizer = new DictionaryLemmatizer (dictLemmatizer); Karakterlánc [] lemmas = lemmatizer.lemmatize (tokenek, tagek); assertThat (lemmas). tartalmaz ("O", "volna", "a", "nővér", "név", "O", "O"); }

Mint láthatjuk, minden jelhez megkapjuk a lemmát. Az „O” azt jelzi, hogy a lemma nem határozható meg, mivel a szó tulajdonnév. Tehát nincs "John" és "Penny" lemma.

De azonosítottuk a mondat többi szójának lemmáit:

  • van - van
  • a - a
  • nővér - nővér
  • megnevezett - név

8. Darabolás

A beszédrészről szóló információ elengedhetetlen a darabolás során is - a mondatokat nyelvtanilag értelmes szócsoportokra osztjuk, mint például főnévcsoportokra vagy igealakokra.

A korábbiakhoz hasonlóan tokenizálunk egy mondatot, és beszédrészes címkézést használunk a tokeneken a hívás előtt darab () módszer:

@Test public void givenChunkerModel_whenChunk_thenChunksAreDetected () dobja a Kivételt {SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String [] tokenek = tokenizer.tokenize ("Úgy véli, hogy a folyó fizetési mérleg hiánya csak 8 milliárdra szűkül."); InputStream inputStreamPOSTagger = getClass () .getResourceAsStream ("/ models / en-pos-maxent.bin"); POSModel posModel = új POSModel (inputStreamPOSTagger); POSTaggerME posTagger = new POSTaggerME (posModel); Karakterláncok [] = posTagger.tag (tokenek); InputStream inputStreamChunker = getClass () .getResourceAsStream ("/ models / en-chunker.bin"); ChunkerModel chunkerModel = új ChunkerModel (inputStreamChunker); ChunkerME chunker = új ChunkerME (chunkerModel); String [] chunks = chunker.chunk (tokenek, címkék); assertThat (darabok). tartalmaz ("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 "); }

Mint láthatjuk, minden egyes tokenhez kimenetet kapunk a darabolóból. A „B” egy darab kezdetét, az „I” a darab folytatását jelenti, az „O” pedig nem egy darabot.

A példánk kimenetét elemezve 6 darabot kapunk:

  1. „He” - főnévi kifejezés
  2. „Számol” - ige kifejezés
  3. „A folyó fizetési mérleg hiánya” - főnévi kifejezés
  4. „Lesz szűkíteni” - ige kifejezés
  5. „To” - elöljáró kifejezés
  6. „Csak 8 milliárd” - főnévi kifejezés

9. Nyelvészlelés

A már tárgyalt felhasználási esetek mellett Az OpenNLP egy nyelvfelismerő API-t is biztosít, amely lehetővé teszi egy bizonyos szöveg nyelvének azonosítását.

A nyelv észleléséhez szükségünk van egy képzési adatfájlra. Egy ilyen fájl egy adott nyelvű mondatokat tartalmazó sorokat tartalmaz. Minden sort megcímkéznek a megfelelő nyelvvel, hogy inputot adjanak a gépi tanulási algoritmusokhoz.

A nyelvfelismeréshez szükséges képzési adatfájl letölthető innen.

Betölthetjük az edzés adatállományát a LanguageDetectorSampleStream, definiáljon néhány képzési adatparamétert, hozzon létre egy modellt, majd használja a modellt a szöveg nyelvének felismerésére:

@Test public void givenLanguageDictionary_whenLanguageDetect_thenLanguageIsDetected () dobja a FileNotFoundException, IOException {InputStreamFactory dataIn = new MarkableFileInputStreamFactory (új fájl ("src / main / resources / models / DoccatSample.); ObjectStream lineStream = új PlainTextByLineStream (dataIn, "UTF-8"); LanguageDetectorSampleStream sampleStream = új LanguageDetectorSampleStream (lineStream); TrainingParameters params = new 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 = új LanguageDetectorME (modell); Nyelv [] nyelvek = ld .predictLanguages ​​("estava em uma marcenaria na Rua Bruno"); assertThat (Arrays.asList (languages)) .extracting ("lang", "confidence"). tartalmaz (tuple ("pob", 0.9999999950605625), tuple ("ita", 4.939427661577956E-9), tuple ("spa", 9,665954064665144E-15), duplázó ("fra", 8,250349924885834E-25)); }

Az eredmény a legvalószínűbb nyelvek felsorolása, valamint a bizalmi pontszám.

Gazdag modellekkel nagyon nagy pontosságot érhetünk el az ilyen típusú érzékeléssel.

5. Következtetés

Sokat kutattunk itt, az OpenNLP érdekes képességeiből. Az NLP feladatok elvégzésére néhány érdekes tulajdonságra összpontosítottunk, mint például a lemmatization, POS tagging, Tokenization, Sentence Detection, Language Detection és még sok más.

Mint mindig, a fentiek teljes megvalósítása megtalálható a GitHubon.