기록/그 외 프로젝트 기록

[NLP] Open Korean Text 자바로 구현

5월._. 2022. 10. 10.
728x90

몇년 전에 파이썬의 konlpy 라이브러리를 이용해서 okt를 써보았던 적이 있다. konlpy는 한국어 정보처리를 위한 파이썬 패키지로, 여러 종류의 한국어 자연어 처리기를 쉽게 사용할 수 있도록 돕는다. 나는 이번에 okt를 스프링부트에서 사용하기 위해서 자바로 직접 사용해보았다.

 

1. gradle

인코딩을 utf-8로 설정한다.

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
}

 그 후 dependencies에 다음을 추가한다.

implementation('org.openkoreantext:open-korean-text:2.1.0')

 

2. 분석코드

분석을 위해 작성한 클래스다.

analyseText 함수는 파라미터로 text와 선택할 품사태그배열을 받는다.

그 다음 정규화시킨 텍스트를 형태소 단위로 쪼갠다.

쪼갠 형태소는 모두 result라는 hashmap에 담아서 반환하는데, 이 때 key는 형태소, value는 카운팅횟수이다.

pickMorpheme 함수는 text를 받아서 다시 analyseText(text, {명사, 동사, 형용사}) 를 반환한다.

pickNouns 함수는 text를 받아서 다시 analyseText(text,{명사})를 반환한다. 

@Component
public class MorphemeAnalyzer {
	public Map<String, Integer> analyseText(String text, Enumeration.Value[] pos){
		CharSequence normalized = OpenKoreanTextProcessorJava.normalize(text);    //정규화
		Seq<KoreanTokenizer.KoreanToken> tokens = OpenKoreanTextProcessorJava.tokenize(normalized);        //토큰화
		List<KoreanPhraseExtractor.KoreanPhrase> phrases = OpenKoreanTextProcessorJava.extractPhrases(tokens, true, false);

		Map<String,Integer> result = new HashMap<>();
		for (KoreanPhraseExtractor.KoreanPhrase phrase : phrases) {
			Iterator<KoreanTokenizer.KoreanToken> iter = phrase.tokens().iterator();
			int cnt = 0;
			StringBuilder val = new StringBuilder();
			while (iter.hasNext() && cnt < 2) {
				KoreanTokenizer.KoreanToken token = iter.next();
				for(Enumeration.Value p:pos){
					if(token.pos().equals(p)){
						val.append(token.text().trim()).append(' ');
						break;
					}
				}
				cnt++;
			}
			if(val.length() > 0) result.put(val.toString().trim(), result.getOrDefault(val.toString(),0)+1);
		}
		return result;
	}

	public Map<String, Integer> pickMorpheme(String text) {
		return analyseText(text,new Enumeration.Value[]{KoreanPos.Noun(),KoreanPos.Verb(), KoreanPos.Adjective()});
	}

	public List<String> pickNouns(String text){
		return new ArrayList<>(analyseText(text, new Enumeration.Value[]{KoreanPos.Noun()}).keySet());
	}
}

 

예시

예를 들어,

1) "안녕하세욬ㅋㅋㅋㅋㅋ오늘도 공부중입니다. 바나나필통 귀엽다."라는 텍스트를 pickMorpheme의 파라미터로 넣어서 호출하면 

2) 정규화 된 텍스트, 즉 normalized 변수에는 "안녕하세요ㅋㅋㅋ오늘도 공부중입니다. 바나나필통 귀엽다."가 저장된다.

3) phrases에는 다음이 저장된다. 바나나필통은 두 단어가 붙어있는 형태인데, okt에서는 두 단어가 붙어있을 때의 의미가 달라지는 경우도 파악하기 위해서 바나나필통은 바나나필통, 바나나, 필통 이렇게 세 가지 경우로 분류를 한다. 그런데 이때도 바나나필통으로 바로 분류하는 것이 아니라 그 안에서 tokens 리스트에 바나나, 필통 이라는 값을 저장해둔다.

4) 바나나필통이라는 phrase는 그 안의 tokens를 iterator로 불러서 반복하며 값을 저장한다. tokens에 바나나, 필통이 들어가있으니 두 번 반복한다. 띄어쓰기를 제대로 하지 않은 텍스트의 경우 이 tokens 길이가 무한대로 길어질 수 있다. 나는 이 함수를 검색할 때 사용하기 때문에 빠른 실행을 위해서 두 단어 조합까지만 보고 그 이후로는 끊어주었다. 

5) iter.next는 파라미터로 받은 pos 중 하나와 품사태그가 일치해야 StringBuilder에 append시킨다.

6) iter를 전부 돌았거나 cnt >2(두 단어 조합 끝)이고, StringBuilder의 길이가 0보다 클 때만 result에 결과를 더해주었다. StringBuilder의 길이를 비교하는 이유는 pos에 해당하지 않는 형태소만 있을 경우를 대비하기 위해서다.

7) 결론적으로 result에는 마지막에 다음과 같이 저장된다.

댓글