Apache OpenNLP 소개

1. 개요

Apache OpenNLP는 오픈 소스 Natural Language Processing Java 라이브러리입니다.

명명 된 엔티티 인식, 문장 감지, POS 태깅 및 토큰 화와 같은 사용 사례를위한 API를 제공합니다.

이 튜토리얼에서는 다양한 사용 사례에이 API를 사용하는 방법을 살펴 보겠습니다.

2. Maven 설정

먼저 pom.xml에 주요 종속성을 추가해야합니다 .

 org.apache.opennlp opennlp-tools 1.8.4 

안정적인 최신 버전은 Maven Central에서 찾을 수 있습니다.

일부 사용 사례에는 훈련 된 모델이 필요합니다. 여기에서 사전 정의 된 모델을 다운로드하고 여기에서 이러한 모델에 대한 자세한 정보를 다운로드 할 수 있습니다.

3. 문장 감지

문장이 무엇인지 이해하는 것부터 시작합시다.

문장 감지는 일반적으로 사용중인 언어에 따라 문장의 시작과 끝을 식별하는 것 입니다. 이를 "Sentence Boundary Disambiguation"(SBD)이라고도합니다.

어떤 경우 에는 마침표 문자의 모호한 특성으로 인해 문장 감지가 매우 어렵습니다 . 마침표는 일반적으로 문장의 끝을 나타내지 만 이메일 주소, 약어, 소수점 및 기타 여러 자리에 나타날 수도 있습니다.

대부분의 NLP 작업의 경우 문장 감지를 위해 학습 된 모델이 입력으로 필요하며 / resources 폴더에 있을 것으로 예상됩니다 .

문장 감지를 구현하기 위해 모델을로드하고 SentenceDetectorME 의 인스턴스에 전달합니다 . 그런 다음 텍스트를 sentDetect () 메서드에 전달하여 문장 경계에서 분할합니다.

@Test public void givenEnglishModel_whenDetect_thenSentencesAreDetected() throws Exception { String paragraph = "This is a statement. This is another statement." + "Now is an abstract word for time, " + "that is always flying. And my email address is [email protected]"; InputStream is = getClass().getResourceAsStream("/models/en-sent.bin"); SentenceModel model = new SentenceModel(is); SentenceDetectorME sdetector = new SentenceDetectorME(model); String sentences[] = sdetector.sentDetect(paragraph); assertThat(sentences).contains( "This is a statement.", "This is another statement.", "Now is an abstract word for time, that is always flying.", "And my email address is [email protected]"); }

노트 :접미사 "ME"는 Apache OpenNLP의 많은 클래스 이름에 사용되며 "최대 엔트로피"를 기반으로하는 알고리즘을 나타냅니다.

4. 토큰 화

이제 텍스트 코퍼스를 문장으로 나눌 수 있으므로 문장을 더 자세히 분석 할 수 있습니다.

토큰 화의 목표는 문장을 토큰이라고하는 작은 부분으로 나누는 것 입니다. 일반적으로 이러한 토큰은 단어, 숫자 또는 구두점입니다.

OpenNLP에는 세 가지 유형의 토크 나이저가 있습니다.

4.1. TokenizerME 사용

이 경우 먼저 모델을로드해야합니다. 여기에서 모델 파일을 다운로드하여 / resources 폴더 에 넣고 거기에서로드 할 수 있습니다.

다음으로 로드 된 모델을 사용하여 TokenizerME 의 인스턴스를 만들고 tokenize () 메서드를 사용하여 모든 문자열 에 대해 토큰 화를 수행합니다 .

@Test public void givenEnglishModel_whenTokenize_thenTokensAreDetected() throws Exception { InputStream inputStream = getClass() .getResourceAsStream("/models/en-token.bin"); TokenizerModel model = new TokenizerModel(inputStream); TokenizerME tokenizer = new TokenizerME(model); String[] tokens = tokenizer.tokenize("Baeldung is a Spring Resource."); assertThat(tokens).contains( "Baeldung", "is", "a", "Spring", "Resource", "."); }

보시다시피 토크 나이 저는 모든 단어와 마침표 문자를 별도의 토큰으로 식별했습니다. 이 토크 나이 저는 맞춤 학습 된 모델과 함께 사용할 수도 있습니다.

4.2. WhitespaceTokenizer

이름에서 알 수 있듯이이 토크 나이 저는 공백 문자를 구분 기호로 사용하여 문장을 토큰으로 분할합니다.

@Test public void givenWhitespaceTokenizer_whenTokenize_thenTokensAreDetected() throws Exception { WhitespaceTokenizer tokenizer = WhitespaceTokenizer.INSTANCE; String[] tokens = tokenizer.tokenize("Baeldung is a Spring Resource."); assertThat(tokens) .contains("Baeldung", "is", "a", "Spring", "Resource."); }

문장이 공백으로 분할되어 "리소스"를 얻는 것을 볼 수 있습니다. (마침표 끝에 마침표가 있음) 단어 "Resource"와 마침표 문자에 대해 두 개의 다른 토큰 대신 단일 토큰으로.

4.3. SimpleTokenizer

이 토크 나이 저는 WhitespaceTokenizer 보다 조금 더 정교 하며 문장을 단어, 숫자 및 구두점으로 분할합니다. 기본 동작이며 모델이 필요하지 않습니다.

@Test public void givenSimpleTokenizer_whenTokenize_thenTokensAreDetected() throws Exception { SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String[] tokens = tokenizer .tokenize("Baeldung is a Spring Resource."); assertThat(tokens) .contains("Baeldung", "is", "a", "Spring", "Resource", "."); }

5. 명명 된 개체 인식

이제 토큰 화를 이해 했으므로 성공적인 토큰 화를 기반으로하는 첫 번째 사용 사례 인 NER (Named entity Recognition)를 살펴 보겠습니다.

NER의 목표는 주어진 텍스트에서 사람, 위치, 조직 및 기타 명명 된 항목과 같은 명명 된 엔터티를 찾는 것입니다.

OpenNLP는 사람 이름, 날짜 및 시간, 위치 및 조직에 대해 사전 정의 된 모델을 사용합니다. 우리는 사용하여 모델로드해야 TokenNameFinderModelNameFinderME 의 인스턴스에 전달합니다 . 그런 다음 find () 메서드를 사용하여 주어진 텍스트에서 명명 된 엔티티를 찾을 수 있습니다.

@Test public void givenEnglishPersonModel_whenNER_thenPersonsAreDetected() throws Exception { SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String[] tokens = tokenizer .tokenize("John is 26 years old. His best friend's " + "name is Leonard. He has a sister named Penny."); InputStream inputStreamNameFinder = getClass() .getResourceAsStream("/models/en-ner-person.bin"); TokenNameFinderModel model = new TokenNameFinderModel( inputStreamNameFinder); NameFinderME nameFinderME = new NameFinderME(model); List spans = Arrays.asList(nameFinderME.find(tokens)); assertThat(spans.toString()) .isEqualTo("[[0..1) person, [13..14) person, [20..21) person]"); }

어설 션에서 볼 수 있듯이 결과는 텍스트에서 명명 된 엔터티를 구성하는 토큰의 시작 및 끝 인덱스를 포함하는 Span 개체 목록입니다 .

6. 품사 태깅

입력으로 토큰 목록이 필요한 또 다른 사용 사례는 품사 태깅입니다.

품사 (POS)는 단어의 유형을 식별합니다. OpenNLP는 다양한 품사에 다음 태그를 사용합니다.

  • NN – 명사, 단수 또는 질량
  • DT – determiner
  • VB – verb, base form
  • VBD – verb, past tense
  • VBZ – verb, third person singular present
  • IN – preposition or subordinating conjunction
  • NNP – proper noun, singular
  • TO – the word “to”
  • JJ – adjective

These are same tags as defined in the Penn Tree Bank. For a complete list please refer to this list.

Similar to the NER example, we load the appropriate model and then use POSTaggerME and its method tag() on a set of tokens to tag the sentence:

@Test public void givenPOSModel_whenPOSTagging_thenPOSAreDetected() throws Exception { SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String[] tokens = tokenizer.tokenize("John has a sister named Penny."); InputStream inputStreamPOSTagger = getClass() .getResourceAsStream("/models/en-pos-maxent.bin"); POSModel posModel = new POSModel(inputStreamPOSTagger); POSTaggerME posTagger = new POSTaggerME(posModel); String tags[] = posTagger.tag(tokens); assertThat(tags).contains("NNP", "VBZ", "DT", "NN", "VBN", "NNP", "."); }

The tag() method maps the tokens into a list of POS tags. The result in the example is:

  1. “John” – NNP (proper noun)
  2. “has” – VBZ (verb)
  3. “a” – DT (determiner)
  4. “sister” – NN (noun)
  5. “named” – VBZ (verb)
  6. “Penny” –NNP (proper noun)
  7. “.” – period

7. Lemmatization

Now that we have the part-of-speech information of the tokens in a sentence, we can analyze the text even further.

Lemmatization is the process of mapping a word form that can have a tense, gender, mood or other information to the base form of the word – also called its “lemma”.

A lemmatizer takes a token and its part-of-speech tag as input and returns the word's lemma. Hence, before Lemmatization, the sentence should be passed through a tokenizer and POS tagger.

Apache OpenNLP provides two types of lemmatization:

  • Statistical – needs a lemmatizer model built using training data for finding the lemma of a given word
  • Dictionary-based – requires a dictionary which contains all valid combinations of a word, POS tags, and the corresponding lemma

For statistical lemmatization, we need to train a model, whereas for the dictionary lemmatization we just need a dictionary file like this one.

Let's look at a code example using a dictionary file:

@Test public void givenEnglishDictionary_whenLemmatize_thenLemmasAreDetected() throws Exception { SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String[] tokens = tokenizer.tokenize("John has a sister named Penny."); InputStream inputStreamPOSTagger = getClass() .getResourceAsStream("/models/en-pos-maxent.bin"); POSModel posModel = new POSModel(inputStreamPOSTagger); POSTaggerME posTagger = new 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"); }

As we can see, we get the lemma for every token. “O” indicates that the lemma could not be determined as the word is a proper noun. So, we don't have a lemma for “John” and “Penny”.

But we have identified the lemmas for the other words of the sentence:

  • has – have
  • a – a
  • sister – sister
  • named – name

8. Chunking

Part-of-speech information is also essential in chunking – dividing sentences into grammatically meaningful word groups like noun groups or verb groups.

Similar to before, we tokenize a sentence and use part-of-speech tagging on the tokens before the calling the chunk() method:

@Test public void givenChunkerModel_whenChunk_thenChunksAreDetected() throws Exception { SimpleTokenizer tokenizer = SimpleTokenizer.INSTANCE; String[] tokens = tokenizer.tokenize("He reckons the current account deficit will narrow to only 8 billion."); InputStream inputStreamPOSTagger = getClass() .getResourceAsStream("/models/en-pos-maxent.bin"); POSModel posModel = new POSModel(inputStreamPOSTagger); POSTaggerME posTagger = new POSTaggerME(posModel); String tags[] = posTagger.tag(tokens); InputStream inputStreamChunker = getClass() .getResourceAsStream("/models/en-chunker.bin"); ChunkerModel chunkerModel = new ChunkerModel(inputStreamChunker); ChunkerME chunker = new ChunkerME(chunkerModel); String[] chunks = chunker.chunk(tokens, tags); assertThat(chunks).contains( "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"); }

As we can see, we get an output for each token from the chunker. “B” represents the start of a chunk, “I” represents the continuation of the chunk and “O” represents no chunk.

Parsing the output from our example, we get 6 chunks:

  1. “He” – noun phrase
  2. “reckons” – verb phrase
  3. “the current account deficit” – noun phrase
  4. “will narrow” – verb phrase
  5. “to” – preposition phrase
  6. “only 8 billion” – noun phrase

9. Language Detection

Additionally to the use cases already discussed, OpenNLP also provides a language detection API that allows to identify the language of a certain text.

For language detection, we need a training data file. Such a file contains lines with sentences in a certain language. Each line is tagged with the correct language to provide input to the machine learning algorithms.

A sample training data file for language detection can be downloaded here.

We can load the training data file into a LanguageDetectorSampleStream, define some training data parameters, create a model and then use the model to detect the language of a text:

@Test public void givenLanguageDictionary_whenLanguageDetect_thenLanguageIsDetected() throws FileNotFoundException, IOException { InputStreamFactory dataIn = new MarkableFileInputStreamFactory( new File("src/main/resources/models/DoccatSample.txt")); ObjectStream lineStream = new PlainTextByLineStream(dataIn, "UTF-8"); LanguageDetectorSampleStream sampleStream = new 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 = new LanguageDetectorME(model); Language[] languages = ld .predictLanguages("estava em uma marcenaria na Rua Bruno"); assertThat(Arrays.asList(languages)) .extracting("lang", "confidence") .contains( tuple("pob", 0.9999999950605625), tuple("ita", 4.939427661577956E-9), tuple("spa", 9.665954064665144E-15), tuple("fra", 8.250349924885834E-25))); }

The result is a list of the most probable languages along with a confidence score.

And, with rich models , we can achieve a very higher accuracy with this type of detection.

5. Conclusion

여기에서 OpenNLP의 흥미로운 기능을 많이 살펴 보았습니다. 우리는 lemmatization, POS 태깅, 토큰 화, 문장 감지, 언어 감지 등과 같은 NLP 작업을 수행하는 몇 가지 흥미로운 기능에 집중했습니다.

항상 그렇듯이 위의 모든 구현은 GitHub에서 찾을 수 있습니다.