AIFFEL/Going Deeper(NLP)

[Going Deeper(NLP)] 3. 텍스트의 분포로 벡터화 하기

알밤바 2022. 3. 23. 18:57
728x90
반응형

해당 포스팅은 AIFFEL에서 제공한 학습자료를 통해 공부한 것을 정리한 것임을 밝힙니다.

 

목차

  1. 단어 빈도를 이용한 벡터화
    (1) Bag of Words
    (2) Bag of Words 구현해보기
    (3) DTM과 코사인 유사도
    (4) DTM의 구현과 한계점
    (5) TF-IDF
    (6) TF-IDF 구현하기
  2. LSA와 LDA
    (1) LSA
    (2) LSA 실습
    (3) LDA
    (4) LDA 실습
  3. 텍스트 분포를 이용한 비지도 학습 토크나이저
    (1) 형태소 분석기와 단어 미등록 문제
    (2) soynlp

1. 단어 빈도를 이용한 벡터화

머신러닝 모델은 기계이기에 텍스트보다 수치화된 숫자를 잘 처리할 수 있다. 그래서 자연어 처리에서는 전처리 과정에서 텍스트를 숫자 벡터로 변환하는 벡터화(Vectorization)를 거친다. 벡터화 방법으로는 크게 '통계와 머신러닝을 활용한 방법', '인공 신경망을 활용하는 방법' 2가지가 있다.

 

(1) Bag of Words

자연어처리나 정보 검색에서 쓰이는 매우 간단한 단어 표현 방법으로 BoW라고 표기한다.

문서 내의 단어들의 분포를 보고 해당 문서의 특성을 파악하는 기법이다.

 

한 문서에 있는 텍스트를 단어 단위로 토큰화(tokenization)를 한다. 그렇게 나온 단어들을 전부 가방에 집어 넣고 섞는다. 그래서 Bag of Words는 단어들의 순서는 무시하고 단어들의 빈도 정보를 그대로 보존한다.

 

doc1 = 'John likes to watch movies. Mary likes movies too.'

위의 문장을 BoW로 표현하면 다음과 같다.

BoW1 = {"John":1, "likes":2, "to":1, "watch":1, "movies":2, "Mary":1, "too":1}

BoW는 문서 내 단어의 순서가 중요하지 않기에 어순에 따라 달라지는 의미를 반영하지 못하는 것이 BoW의 한계점이다. (BoW 방식으로 문장을 벡터화하면 I ate lunch. 와 Lunch ate I 이 같은 문장이 됨.)

 

(2) Bag of Words 구현해보기

텐서플로우 케라스와 사이킷런 라이브러리를 사용해 Bag of Words를 만들어보자.

 

Keras Tokenizer 활용

from tensorflow.keras.preprocessing.text import Tokenizer

sentence = ["John likes to watch movies. Mary likes movies too! Mary also likes to watch football games."]

tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentence) # 단어장 생성
bow = dict(tokenizer.word_counts) # 각 단어와 각 단어의 빈도를 bow에 저장

print("Bag of Words :", bow) # bow 출력
print('단어장(Vocabulary)의 크기 :', len(tokenizer.word_counts)) # 중복을 제거한 단어들의 개수

Bag of Words : {'john': 1, 'likes': 3, 'to': 2, 'watch': 2, 'movies': 2, 'mary': 2, 'too': 1, 'also': 1, 'football': 1, 'games': 1}
단어장(Vocabulary)의 크기 : 10

 

💡 단어장(Vocabulary)란?

중복을 제거한 단어들의 집합으로 Bag of Words랑은 다른 개념이다.

 

scikit-learn CountVectorizer 활용

from sklearn.feature_extraction.text import CountVectorizer

sentence = ["John likes to watch movies. Mary likes movies too! Mary also likes to watch football games."]

vector = CountVectorizer()
bow = vector.fit_transform(sentence).toarray()

print('Bag of Words : ', bow) # 코퍼스로부터 각 단어의 빈도수를 기록한다.
print('각 단어의 인덱스 :', vector.vocabulary_) # 각 단어의 인덱스가 어떻게 부여되었는지를 보여준다.

Bag of Words :  [[1 1 1 1 3 2 2 2 1 2]]
각 단어의 인덱스 : {'john': 3, 'likes': 4, 'to': 7, 'watch': 9, 'movies': 6, 'mary': 5, 'too': 8, 'also': 0, 'football': 1, 'games': 2}

 

bow 결과에는 각 단어의 빈도만 출력되고 어떤 단어의 빈도인지 나오지 않는다. 그래서 vector.vocabulary_를 통해 각 단어에 부여된 인덱스를 확인하면되는데, 이 인덱스는 0부터 시작한다는 것이다.

'also'의 인덱스는 0이므로 빈도는 1이 된다. 'like'의 인덱스는 4 이므로 4번째에 위치한 값은 3이므로 빈도는 3이 된다.

 

print('단어장(Vocabulary)의 크기 :', len(vector.vocabulary_))

단어장(Vocabulary)의 크기 : 10

 

단어장의 크기도 위와 같이 구현할 수 있다.

 

(3) DTM과 코사인 유사도

DTM(Document-Term Matrix)은 여러 문서의 Bag of Words를 하나의 행렬로 구현한 것으로, 각 문서에 등장한 단어의 빈도수를 하나의 행렬로 통합시킨다.

DTM은 문서를 행으로, 단어를 열로 가지는 행렬이지만, 문헌에 따라서는 열을 문서로 하고 단어를 행으로 하여 TDM(Term-Document Matrix)라고 부르기도 한다.

 

Doc 1: Intelligent applications creates intelligent business processes
Doc 2: Bots are intelligent applications
Doc 3: I do business intelligence

총 3개의 문서가 있다. 이 문서들로부터 얻을 수 있는 DTM은 다음과 같다.

[ 출처 : https://www.darrinbishop.com/blog/2017/10/text-analytics-document-term-matrix/ ]

각 행은 문서를 나타내고 있으며 열은 문서 3개의 통합 단어장(Vocabulary)에 있는 단어들로 구성되어있다. 이렇게 만들게 되면 문서 1개로 BoW를 만들었을 때와 달리 각 행은 많은 값이 0으로 구성된다.

 

각 행을 문서 벡터(document vector), 열을 단어 벡터(word vector)라고 부를 수 있다.

문서의 수가 많아질수록 통합 단어장의 크기도 커져 DTM은 문서 벡터와 단어 벡터 모두 대부분의 값이 0이 되는 성질을 가지고 있다.

 

문서1 : I like dog
문서2 : I like cat
문서3 : I like cat I like cat

DTM을 사용하면 각 문서들을 비교하고 각 문서 간 유사도를 구할 수 있다. 여기서는 가장 보편적으로 사용되는 유사도 계산 방법인 코사인 유사도를 구할 것이다.

 

import numpy as np
from numpy import dot
from numpy.linalg import norm

doc1 = np.array([0,1,1,1]) # 문서1 벡터
doc2 = np.array([1,0,1,1]) # 문서2 벡터
doc3 = np.array([2,0,2,2]) # 문서3 벡터

def cos_sim(A, B):
    return dot(A, B)/(norm(A)*norm(B))

각 문서 벡터와 코사인 유사도 함수를 위와 같이 정의한다.

 

print(cos_sim(doc1, doc2)) #문서1과 문서2의 코사인 유사도
print(cos_sim(doc1, doc3)) #문서1과 문서3의 코사인 유사도
print(cos_sim(doc2, doc3)) #문서2과 문서3의 코사인 유사도

0.6666666666666667
0.6666666666666667
1.0000000000000002

 

문서2와 문서 3의 코사인 유사도가 1인 이유는 한 문서 내의 모든 단어의 빈도수가 똑같이 증가하는 경우이기 때문이다.

 

1) 코사인 유사도(Cosine Similarity)

BoW에 기반한 단어 표현 방법인 DTM, TF-IDF, 또는 뒤에서 배우게 될 Word2Vec 등과 같이 단어를 수치화할 수 있는 방법을 이해했다면 이러한 표현 방법에 대 ...

wikidocs.net

 

(4) DTM의 구현과 한계점

scikit-learn CountVectorizer 활용

사이킷런의 countVectorizer를 이용하여 DTM을 만드는 방법은 앞서 Bag of Words를 만드는 방법과 동일하다. 단지, 다수의 문서를 입력값으로 주면된다.

from sklearn.feature_extraction.text import CountVectorizer

corpus = [
    'John likes to watch movies',
    'Mary likes movies too',
    'Mary also likes to watch football games',    
]
vector = CountVectorizer()

print(vector.fit_transform(corpus).toarray()) # 코퍼스로부터 각 단어의 빈도수를 기록.
print(vector.vocabulary_) # 각 단어의 인덱스가 어떻게 부여되었는지를 보여준다.

[[0 0 0 1 1 0 1 1 0 1]
 [0 0 0 0 1 1 1 0 1 0]
 [1 1 1 0 1 1 0 1 0 1]]
{'john': 3, 'likes': 4, 'to': 7, 'watch': 9, 'movies': 6, 'mary': 5, 'too': 8, 'also': 0, 'football': 1, 'games': 2}

 

 

DTM의 한계점

1) DTM에서 문서의 수와 단어의 수가 계속 늘어날수록, 행과 열은 대부분의 값이 0을 가진다는 특징이 있다. 이는 저장 공간 측면에서 낭비이다. (지나친 차원의 크기는 차원의 저주라는 또 다른 문제를 발생시킨다.)

 

2) 단어의 빈도에만 집중하는 방법 자체의 한계이다. 예를 들어 영어 데이터를 가지고 DTM을 만들었을때, 불용어 'the'는 어떤 문서에서도 자주 등장하는 경향이 있다. 그런데 문서1과 문서2에서 둘 다 the가 많이 등장하였다고 하여 이 두 문서가 유사한 문서라고 볼 수 있을까? 그리고 중요한 단어와 중요하지 않은 단어에 가중치를 따로 선별해주는 방법이 없을까?

 

(5) TF-IDF

TF-IDF(Term Frequency-Inverse Document Frequency)는 모든 문서에서 자주 등장하는 단어는 중요도가 낮다고 판단하며, 특정 문서에서만 자주 등장하는 단어는 중요도가 높다고 판단하는 것이다. (+ 단어의 빈도와 문서의 빈도의 역수를 활용)

 

TF-IDF는 불용어처럼 중요도가 낮으면서 모든 문서에 등장하는 단어들이 노이즈가 되는 것을 완화해준다.

TF-IDF를 사용하는 것이 DTM을 사용하는 것보다 성능이 항상 뛰어나지 않기에 TF-IDF를 사용하기 위해서는 우선 DTM을 만든 뒤에 TF-IDF 가중치를 DTM에 적용한다.

 

TF-IDF 계산하기

[ 출처 : http://openuiz.blogspot.com/2018/11/tf-idf.html ]

y는 문서, x는 단어라고 할때의 TF-IDF의 수식이다.

TF는 각 문서에 등장하는 단어의 빈도를 의미하므로 DTM을 만들면 자연스럽게 해결이 된다.

 

log항이 IDF로, 역문서 빈도인 IDF를 어떻게 구할 수 있을까?

Question.
전체 문서의 수가 5개라고 해봅시다. 그리고 단어 'like'가 문서2에서 200번, 문서 3에서 300번 등장했다고 해봅시다. 다른 문서에서 단어 'like'는 등장하지 않았습니다. 이때, 단어 'like'의 IDF는 몇일까요?

IDF를 구하기 위해서는 우선 문서 빈도 DF와 전체 문서의 수 N을 이해해야 한다.

단어 'like'의 DF는 2이다. 단어 'like'가 몇 개의 문서에서 등장했는지가 중요하다. (어떤 문서에서 몇 번 등장했는지 중요하지 않음)

전체 문서는 5개로 단어 'like'의 IDF는 log5/2가 된다. (보통 TF-IDF를 지원하는 파이썬 패키지들에서 log는 자연로그를 사용하기에 여기서도 자연로그를 사용한다고 가정하자)

자연로그는 로그의 밑으로 자연상수 e  를 사용하는 로그를 말한다. 자연로그는 보통 ln으로 표현하기에 단어 'like'의 IDF 계산식은 In5/2가 된다. 이 값을 계산하게 되면 0.91629073187이 된다.

 

​Question.
문서2과 문서3에서의 단어 'like'의 TF-IDF값은 몇 일까요?

문서2의 단어 'like'의 TF-IDF를 구하면 200 x In 5/2 = 183.258146375이다.

문서3의 단어 'like'의 TF-IDF는 300 x In5/2 = 274.887219562이다.

 

문서3에서의 TF-IDF 값이 더 높다. TF-IDF의 목적이 특정 문서에서만 자주 등장하는 단어는 중요도가 높다고 판단하는 것이었으므로 당연한 결과이다.

 

TF-IDF를 이용해서 문서의 유사도를 구하는 과정 영상

 

(6) TF-IDF 구현하기

TF-IDF를 실제로 파이썬을 통해 구현해보자.

from math import log
import pandas as pd
print('=3')

 

3개의 문서를 사용하자.

docs = [
  'John likes to watch movies and Mary likes movies too',
  'James likes to watch TV',
  'Mary also likes to watch football games',  
]
print('=3')

 

DTM의 열을 만들기 위해 문서 3개의 단어들이 모두 들어간 통합 단어장(Vocabulary)을 만들자.

vocab = list(set(w for doc in docs for w in doc.split()))
vocab.sort()
print('단어장의 크기 :', len(vocab))
print(vocab)

단어장의 크기 : 13
['James', 'John', 'Mary', 'TV', 'also', 'and', 'football', 'games', 'likes', 'movies', 'to', 'too', 'watch']

 

총 문서의 수를 변수 N에다가 저장을 해보자.

N = len(docs) # 총 문서의 수

 

이제 TF 함수, IDF 함수, TF-IDF 함수를 만들어보자.

(IDF를 실제 구현할 때는 앞서 배운 식과는 다소 다른 식을 사용하게 된다.)

 

log항의 분모에 1을 더해준다. 이는 (Bag of Words를 사용할 때 일반적인 상황은 아니지만) 특정 단어가 전체 문서에서 등장하지 않을 경우에 분모가 0이 되는 상황을 방지하기 위함이다.

log항에 1을 더해주는데, 이는 log항의 분자와 분모 값이 동일해졌을 때, log의 진수가 1이 됨에 따라 IDF 값이 0이 되는 것을 방지하기 위함이다.

 

def tf(t, d):
    return d.count(t)
 
def idf(t):
    df = 0
    for doc in docs:
        df += t in doc    
    return log(N/(df + 1)) + 1
 
def tfidf(t, d):
    return tf(t,d)* idf(t)

 

TF 함수를 사용하여 DTM을 만들어보자.

result = []
for i in range(N): # 각 문서에 대해서 아래 명령을 수행
    result.append([])
    d = docs[i]
    for j in range(len(vocab)):
        t = vocab[j]
        
        result[-1].append(tf(t, d))
        
tf_ = pd.DataFrame(result, columns = vocab)
tf_

 

각 단어의 IDF를 구해보자.

result = []
for j in range(len(vocab)):
    t = vocab[j]
    result.append(idf(t))

idf_ = pd.DataFrame(result, index = vocab, columns=["IDF"])
idf_

IDF는 'likes'나 'to', 'watch'와 같은 모든 문서에 등장한 단어가 가장 낮은 값을 가진다. 1개의 문서에만 등장한 단어들이 가장 높은 값을 가지는 것을 볼 수 있다.

 

TF-IDF 행렬을 출력해보자.

DTM에 있는 각 단어의 TF에 각 단어의 IDF를 곱해준 값이다.

result = []
for i in range(N):
    result.append([])
    d = docs[i]
    for j in range(len(vocab)):
        t = vocab[j]
        
        result[-1].append(tfidf(t,d))

tfidf_ = pd.DataFrame(result, columns = vocab)
tfidf_

 

scikit-learn TFidVectorizer 활용

사이킷런으로 DTM을 만들기 위해서는 CountVectorizer를 사용하였다. TF-IDF를 자동으로 계산하여 출력하는 TfidVectorizer도 제공한다.

TfidVectorizer는 log항의 분자에도 1을 더해주며, TF-IDF의 결과에 L2 Norm까지 추가로 수행한다.

 

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
  'John likes to watch movies and Mary likes movies too',
  'James likes to watch TV',
  'Mary also likes to watch football games',  
]

tfidfv = TfidfVectorizer().fit(corpus)
vocab = list(tfidfv.vocabulary_.keys()) # 단어장을 리스트로 저장
vocab.sort() # 단어장을 알파벳 순으로 정렬

# TF-IDF 행렬에 단어장을 데이터프레임의 열로 지정하여 데이터프레임 생성
tfidf_ = pd.DataFrame(tfidfv.transform(corpus).toarray(), columns = vocab)
tfidf_

 


2. LSA와 LDA

(1) LSA

Back of Words를 기반으로 한 표현 방법(ex. DTM, TF-IDF 행렬)은 근본적으로 단어의 의미를 벡터로 표현하지 못한다는 한계를 가지고 있다.

그래서 단어들의 빈도가 아닌 의미와 주제를 알고 싶다면, DTM이나 TF-IDF만으로는 어렵고 LSA(Latent Semantic Analysis)로 해결할 수 있다.

문서의 집합에서 토픽을 찾아내는 프로세스를 토픽 모델링(Topic Modelling)이라고 한다. 이는 고객의 소리와 같이 많은 문서에서 주요 주제를 알아내는 일이 중요할 때 사용한다.

 

LSA(Latent Semantic Analysis)

잠재 의미 분석이라고 불리며, 전체 코퍼스에서 문서 속 단어들 사이의 관계를 찾아내는 자연어 처리 정보 검색 기술이다.

LSA를 사용하면 단어와 단어 사이, 문서와 문서 사이, 단어와 문서 사이의 의미적 유사성 점수를 찾아낼 수 있다. 이 방법은 어떤 문서에서 특정 단어들의 빈도가 몇인지를 판단하는 것보다 효과적인 경우가 많다.

 

특잇값 분해

LSA를 이해하려면 먼저 선형대수학의 특잇값 분해(Singular Value Decompotion)에 대해 이해해야 한다.

 

💡 행렬의 종류

 

머신러닝 - 19. 고유값(eigenvalue), 고유벡터(eigenvector), 고유값 분해(eigen decomposition)

이번 시간에는 고유값과 고유 벡터, 그리고 고유값 분해에 대해 알아보겠습니다. 참고로 고유값, 고유 벡터를 이해하기 위해서는 행렬의 몇 가지 종류에 대해 알고 있어야 합니다. 본 글의 맨 아

bkshin.tistory.com

- 단위행렬 : 주대각 성분이 모두 1이며, 나머지 성분은 모두 0인 정사각행렬

- n차 정사각행렬 A에 대해 어떤 행렬을 곱했을 때, 결과 행렬이 단위 행렬이라면 이 어떤 행렬은 A의 역행렬이라고 한다.

 

[ 출처 : https://wikidocs.net/24949 ]

특잇값 분해(SVD)란, m x n 크기의 임의의 사각 행렬 A를 위의 그림의 Full SVD와 같이 특이 벡터(singular vector)의 행렬과 특잇값(singular value)의 대각 행렬로 분해하는 것을 말한다.

행렬 A에 특잇값 분해를 수행하면 U, Σ, V라는 세 가지 행렬로 분해가 된다. 이 때, 특잇값(singular value)Σ의 대각 성분에서 얻을 수 있다.

 

[참고 자료]

특잇값 분해 (데이터 사이언스 스쿨)

Singular value decomposition의 목적 (영상)

 

특잇값 분해 중 가장 특별한 특잇값 분해가 있다.

특잇값 가운데 가장 큰 (가장 중요한) t개만 남기고 해당 특잇값에 대응되는 특이 벡터(singular vector)들로 행렬 A를 근사(approximate)하도록 하면, 이를 절단된 특잇값 분해(Truncated SVD)라고 한다.

 

Truncated SVD를 수행하면 행렬 Σ의 대각 원솟값 중에서 상윗값 t개만 남게 되며, U행렬과 V행렬의 t열까지만 남는다. 이로 인해 세 행렬의 값의 손실이 일어나 기존의 행렬 A를 정확히 복구할 수 없게 된다.

여기서 t는 하이퍼파라미터로, t를 크게 잡으면 기존의 행렬 A로부터 다양한 의미를 가져갈 수 있지만 노이즈를 제거하려면 t를 작게 잡아야 한다.

 

LSA와 Truncated SVD

[ 출처 : https://www.analyticsvidhya.com/blog/2018/10/stepwise-guide-topic-modeling-latent-semantic-analysis/ ]

LSA는 DTM이나 TF-IDF 행렬 등에 Truncated SVD를 수행한다. 이렇게 분해하여 행렬 3개(Uk, VkT, S)를 얻을 수 있다.

- Uk : 문서들과 관련된 의미들을 표현한 행렬

- VkT : 단어들과 관련된 의미를 표현한 행렬

- S : 각 의미의 중요도를 표현한 행렬

(위의 그림은 하이퍼파라미터 t를 k로 표현하였다.)

 

[Uk]

m을 문서의 수, n을 단어의 수라고 하였을 때, Truncated SVD를 통해 얻은 Uk는 m x k의 크기를 가진다. 이 때 m의 크기는 줄어들지 않으며 Uk의 각 행은 각 문서를 표현하는 문서 벡터이다.

 

[VkT]

k x n의 크기를 가지는 행렬이다. n의 크기는 줄어들지 않으며 각 열은 각 단어를 나타내는 n차원의 단어 벡터가 된다. A에서 단어 벡터의 크기는 m이었는데, VkT는 k의 크기를 가지게 되었으니 벡터의 차원이 저차원으로 축소된 셈이다. 벡터의 차원을 저차원으로 축소하면서 잠재된 의미를 끌어내는 여러 방법은 추후 다룬다.

 

DTM이나 TF-IDF 행렬에 Truncated SVD를 수행하고 얻은 VkT 행렬의 k열은 전체 코퍼스로부터 얻어낸 k개의 주요 주제(topic)라고 간주할 수 있다.

 

(2) LSA 실습

필요한 라이브러리를 임포트한다.

import pandas as pd
import numpy as np
import urllib.request
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
print('=3')

 

NLTK 데이터셋을 다운로드한다. (다운로드하지 않는다면, NLTK의 도구들이 제대로 작동하지 않는다.)

 

💡 NLTK란?

- 교육용으로 개발된 자연어 처리 및 문서 분석용 파이썬 패키지

- NLTK 패키지는 말뭉치, 토큰 생성, 형태소 분석, 품사 태깅 등을 제공한다.

 

NLTK 자연어 처리 패키지 — 데이터 사이언스 스쿨

NLTK(Natural Language Toolkit) 패키지는 교육용으로 개발된 자연어 처리 및 문서 분석용 파이썬 패키지다. 다양한 기능 및 예제를 가지고 있으며 실무 및 연구에서도 많이 사용된다. NLTK 패키지가 제공

datascienceschool.net

 

nltk.download('punkt')
nltk.download('wordnet')
nltk.download('stopwords')

 

데이터 다운로드 및 확인

import os

csv_filename = os.getenv('HOME')+'/aiffel/topic_modelling/data/abcnews-date-text.csv'

urllib.request.urlretrieve("https://raw.githubusercontent.com/franciscadias/data/master/abcnews-date-text.csv", 
                           filename=csv_filename)

 

다운로드한 데이터를 데이터프레임에 저장하고 전체 샘플의 수를 출력한다.

data = pd.read_csv(csv_filename, error_bad_lines=False)
data.shape

(1082168, 2)

 

data.head()

 

publish_date는 필요 없기에 headline_text만 별도로 저장한다.

text = data[['headline_text']].copy()
text.head()

데이터 중복이 있는지 확인하자.

text.nunique() # 중복을 제외하고 유일한 시퀀스를 가지는 샘플의 개수를 출력

headline_text    1054983
dtype: int64

 

약 108만개 중 유일한 시퀀스를 가지는 샘플은 105만개로 중복샘플이 3만개정도 있는 것을 확인하였다.

중복을 제거하자.

text.drop_duplicates(inplace=True) # 중복 샘플 제거
text.reset_index(drop=True, inplace=True)
text.shape

(1054983, 1)

 

데이터 정제 및 정규화

NLTK의 토크나이저를 이용해 전체 텍스트 데이터에 대해서 단어 토큰화를 수행하고, NLTK가 제공하는 불용어 리스트를 사용하여 불용어를 제거한다.

 

# NLTK 토크나이저를 이용해서 토큰화
text['headline_text'] = text.apply(lambda row: nltk.word_tokenize(row['headline_text']), axis=1)

# 불용어 제거
stop_words = stopwords.words('english')
text['headline_text'] = text['headline_text'].apply(lambda x: [word for word in x if word not in (stop_words)])

text.head()

 

동일한 단어지만 다른 표현을 가지는 단어들을 하나의 단어로 통합(lemmatization)하는 단어 정규화 과정, 그리고 길이가 1~2인 단어를제거하는 전처리를 해보자.

# 단어 정규화. 3인칭 단수 표현 -> 1인칭 변환, 과거형 동사 -> 현재형 동사 등을 수행한다.
text['headline_text'] = text['headline_text'].apply(lambda x: [WordNetLemmatizer().lemmatize(word, pos='v') for word in x])

# 길이가 1 ~ 2인 단어는 제거.
text = text['headline_text'].apply(lambda x: [word for word in x if len(word) > 2])
print(text[:5])

0     [aba, decide, community, broadcast, licence]
1    [act, fire, witness, must, aware, defamation]
2       [call, infrastructure, protection, summit]
3            [air, staff, aust, strike, pay, rise]
4    [air, strike, affect, australian, travellers]
Name: headline_text, dtype: object

 

💡 표제어 추출(lemmatization)

- NLTK에서는 표제어 추출을 위한 도구인 WordNetLemmatizer를 지원한다.

 

03) 어간 추출(Stemming) and 표제어 추출(Lemmatization)

정규화 기법 중 코퍼스에 있는 단어의 개수를 줄일 수 있는 기법인 표제어 추출(lemmatization)과 어간 추출(stemming)의 개념에 대해서 알아봅니다. 또한 이 ...

wikidocs.net

 

역토큰화 및 DTM 생성

DTM을 생성하는 CountVectorizer 또는 TF-IDF 행렬을 생성하는 TfidfVectorizer의 입력으로 사용하기 위해서 토큰화 과정을 역으로 되돌리는 역토큰화(detokenization)를 수행해보자.

 

# 역토큰화 (토큰화 작업을 역으로 수행)
detokenized_doc = []
for i in range(len(text)):
    t = ' '.join(text[i])
    detokenized_doc.append(t)

train_data = detokenized_doc
print('=3')

 

전처리 최종 결과는 train_data에 저장하였다. 5개의 샘플을 출력해보자.

train_data[:5]

['aba decide community broadcast licence',
 'act fire witness must aware defamation',
 'call infrastructure protection summit',
 'air staff aust strike pay rise',
 'air strike affect australian travellers']

 

CountVectorizer를 사용하여 DTM을 생성해보자. 단어의 수는 5,000로 제한한다.

# 상위 5000개의 단어만 사용
c_vectorizer = CountVectorizer(stop_words='english', max_features = 5000)
document_term_matrix = c_vectorizer.fit_transform(train_data)
print('=3')

 

DTM을 생성하였다. DTM의 크기를 확인해보자.

DTM의 크기는 (문서의 수 x 단어 집합의 크기)이다.

print('행렬의 크기 :',document_term_matrix.shape)

행렬의 크기 : (1054983, 5000)

 

scikit-learn TruncatedSVD 활용

Truncated SVD를 통해 LSA를 수행해보자.

토픽의 수(k)는 10으로 정하여 진행해보자. 행렬 VkT가 k x (단어의 수)의 크기를 가지도록 DTM에 TruncatedSVD를 수행한다.

 

from sklearn.decomposition import TruncatedSVD

n_topics = 10
lsa_model = TruncatedSVD(n_components = n_topics)
lsa_model.fit_transform(document_term_matrix)

array([[ 1.20425698e-02, -3.69645493e-03,  1.82731230e-02, ...,
         3.69352803e-03,  3.20318861e-03,  1.78514292e-02],
       [ 2.90462294e-02, -1.08728301e-02,  1.81434381e-02, ...,
        -7.68434618e-04, -1.25179492e-02, -5.79808669e-04],
       [ 5.03784288e-03, -2.02565621e-03,  9.74446900e-03, ...,
        -2.64780380e-03,  1.79543098e-03,  4.60667566e-03],
       ...,
       [ 2.97074605e-02,  3.96207618e-03,  2.50752577e-02, ...,
         3.38042403e-02,  1.09309069e-03,  1.81105561e-02],
       [ 6.17764234e-02, -4.09844337e-03,  1.37865287e-01, ...,
         8.69208315e-01,  8.33639845e-01, -2.47180429e-01],
       [ 7.12861824e-02,  2.90982535e-02,  1.25870308e-03, ...,
         1.92239382e-02,  2.74326674e-02,  2.74924823e-02]])

 

TruncatedSVD를 통해 얻은 행렬 VkT의 크기를 확인해보자.

행렬 VkT가 k x (단어의 수)의 크기를 가지는 것을 확인할 수 있다.

print(lsa_model.components_.shape)

(10, 5000)

 

각 행을 전체 코퍼스의 k개의 주제(topic)로 판단하고 각 주제에서 n개씩 단어를 출력해보자.

terms = c_vectorizer.get_feature_names() # 단어 집합. 5,000개의 단어가 저장됨.

def get_topics(components, feature_names, n=5):
    for idx, topic in enumerate(components):
        print("Topic %d:" % (idx+1), [(feature_names[i], topic[i].round(5)) for i in topic.argsort()[:-n - 1:-1]])
get_topics(lsa_model.components_, terms)

Topic 1: [('police', 0.74638), ('man', 0.4536), ('charge', 0.21075), ('new', 0.14089), ('court', 0.11159)]
Topic 2: [('man', 0.69411), ('charge', 0.30043), ('court', 0.16747), ('face', 0.11418), ('murder', 0.10619)]
Topic 3: [('new', 0.83655), ('plan', 0.23669), ('say', 0.18261), ('govt', 0.11091), ('council', 0.1091)]
Topic 4: [('say', 0.73902), ('plan', 0.36011), ('govt', 0.16589), ('council', 0.12759), ('urge', 0.07562)]
Topic 5: [('plan', 0.73075), ('council', 0.17746), ('govt', 0.14706), ('urge', 0.08427), ('water', 0.06737)]
Topic 6: [('govt', 0.50867), ('court', 0.25799), ('urge', 0.22228), ('face', 0.18787), ('fund', 0.1828)]
Topic 7: [('court', 0.52699), ('charge', 0.44584), ('face', 0.29081), ('plan', 0.13184), ('murder', 0.09411)]
Topic 8: [('win', 0.57333), ('court', 0.39756), ('kill', 0.17599), ('crash', 0.11728), ('australia', 0.10554)]
Topic 9: [('win', 0.59397), ('charge', 0.42727), ('council', 0.25867), ('cup', 0.08789), ('world', 0.08252)]
Topic 10: [('council', 0.71933), ('crash', 0.22366), ('water', 0.13091), ('kill', 0.12119), ('fund', 0.09645)]

 

 

(3) LDA

잠재 디리클레 할당(Latent Dirichlet Allocation, LDA)은 토픽 모델링의 또 다른 대표적인 알고리즘이다.

LDA는 문서들이 토픽들의 혼합으로 구성되어 있으며, 토픽들은 확률 분포에 기반하여 단어들을 생성한다고 가정한다. 그리고 데이터가 주어지면, LDA는 이 가정에 따라 단어들의 분포로부터 문서가 생성되는 과정을 역추적해 문서의 토픽을 찾아낸다.

 

LDA(Latent Dirichlet Allocation) 시뮬레이션

LDA Topic Modeling Simulation

 

위의 사이트로 LDA를 시뮬레이션을 하면 2개의 행렬이 결과로 나온다.

첫 번째 행렬의 행은 단어 집합의 단어들, 열은 Topic이다.

두 번째 행렬의 행은 문서이고 열은 Topic이다.

 

LDA의 두 가지 결과

LDA는 각 토픽의 단어 분포와 각 문서의 토픽 분포를 추정해낸다.

 

[ 출처 : https://noduslabs.com/cases/tutorial-lda-text-mining-network-analysis/ ]

 

LDA는 각 토픽의 단어 분포, 즉 특정 토픽에 특정 단어가 나타날 확률을 추정한다.

 

- Topics : 노란색, 분홍색, 초록색, 파란색 토픽에서 각 단어들이 등장할 확률이 있다.

- Documents : 세 가지 토픽이 있는 것으로 보이고, 노란색 네모가 가장 많은 것처럼 보인다.

- Topic proportions and assignments :

  막대 그래프는 문서에 존재하는 토픽의 비율을 시각화한 그래프이다.

  노란색 토픽의 비중이 가장 크다. 그래서 이 문서는 노란색 토픽의 단어들이 가장 많이 등장하며 노란색 토픽일 가능성이 크다.

 

이렇게 LDA는 단어들의 분포로부터 해당 문서의 토픽 분포를 추정하게 된다.

 

LDA의 가정

LDA는 전체 코퍼스, 즉 다수의 문서들로부터 토픽을 뽑아내기 위해 아래의 가정을 두고 있다.

'나는 이 문서를 작성하기 위해 이런 주제들을 넣을 것이고, 이런 주제들을 위해서는 이런 단어를 넣을 것이다.'

 

문서1 : 저는 사과랑 바나나를 먹어요
문서2 : 우리는 귀여운 강아지가 좋아요
문서3 : 저의 깜찍하고 귀여운 강아지가 바나나를 먹어요

<각 문서의 토픽 분포>
문서1 : 토픽 A 100%
문서2 : 토픽 B 100%
문서3 : 토픽 B 60%, 토픽 A 40%

<각 토픽의 단어 분포>
토픽A : 사과 20%, 바나나 40%, 먹어요 40%, 귀여운 0%, 강아지 0%, 깜찍하고 0%, 좋아요 0%
토픽B : 사과 0%, 바나나 0%, 먹어요 0%, 귀여운 33%, 강아지 33%, 깜찍하고 16%, 좋아요 16%

각각의 문서는 다음의 과정을 거쳐 작성되었다고 가정한다.

 

1) 문서에 사용할 단어의 개수 N을 정한다.

   → 5개의 단어를 정함

2) 문서에 사용할 토픽의 혼합을 확률분포에 기반하여 결정한다.

   → 토픽을 2개라고 하였을 때, 강아지 토픽을 60%, 과일 토픽을 40%와 같이 선택

3) 문서에 사용할 각 단어를 정한다.

3-1) 토픽 분포에서 토픽 T를 확률적으로 고른다.

   → 60% 확률로 강아지 토픽을 선택하고, 40% 확률로 과일 토픽을 선택할 수 있다.

3-2) 선택한 토픽 T에서 단어의 출현 확률 분포에 기반해 문서에 사용할 단어를 고른다.

   → 강아지 토픽을 선택하였다면, 33%의 확률로 강아지란 단어르 선택할 수 있다.

 

3)을 반복하면서 문서를 완성한다.

 

LDA의 수행 과정

1) 사용자는 알고리즘에게 토픽의 개수 k를 알려준다.

LDA는 토픽의 개수 k를 입력받으면, k개의 토픽이 M개의 전체 문서에 걸쳐 분포되어있다고 가정

 

2) 모든 단어를 k개 중 하나의 토픽에 할당한다.

 

3) 이제 모든 문서의 모든 단어에 대해서 아래의 사항을 반복 진행한다.

3-1) 어떤 문서의 각 단어 w는 자신은 잘못된 토픽에 할당되어져 있지만, 다른 단어들은 전부 올바른 토픽에 할당되어져 있는 상태라고 가정한다. 이에 따라 단어 w는 아래의 두 가지 기준에 따라 토픽이 재할당된다.

- p(topic t | document d) : 문서 d의 단어들 중 토픽 t에 해당하는 단어들의 비율
- p(word w | topic t) : 각 토픽들 t에서 해당 단어 w의 분포

 

doc1의 세번째 단어 apple의 토픽을 결정하고자 한다.

 

p(topic t | document d) 기준은 문서 doc1의 단어들이 어떤 토픽에 해당하는지 본다.

doc1의 모든 단어들은 토픽 A와 토픽 B에 50대 50 비율로 할당되어 있으므로, 이 기준에 따르면 apple은 토픽A나 토픽B 둘 중 어디에도 속할 가능성이 있다.

 

p(word w | topic t) 기준은 apple이 전체 문서에서 어떤 토픽에 할당되어져 있는지 본다.

이 기준에 따르면 apple은 토픽B에 할당될 가능성이 높다.

 

잠재 디리클레 할당(LDA)과 잠재 의미 분석(LSA)의 차이

- LDA : 단어가 특정 토픽에 존재할 확률과 문서에 특정 토픽이 존재할 확률을 결합 확률로 추정하여 토픽을 추출한다.

- LSA : DTM을 차원 축소하여 축소 차원에서 근접 단어들의 토픽으로 묶는다.

 

[참고 자료] 잠재 디리클레 할당(Latent Dirichlet Allocation, LDA)

 

55. 텍스트 데이터 분석 [토픽 모델] - (4) LDA 모형 (동영상 강의)

 

(4) LDA 실습

TF-IDF 행렬 생성

LDA는 DTM 또는 TF-IDF를 입력으로 받을 수 있다.

TfidfVectorizer를 사용하여 TF-IDF 행렬을 생성해보자. (단어의 수는 5,000로 제한한다.)

# 상위 5,000개의 단어만 사용
tfidf_vectorizer = TfidfVectorizer(stop_words='english', max_features=5000)
tf_idf_matrix = tfidf_vectorizer.fit_transform(train_data)

# TF-IDF 행렬의 크기를 확인해봅시다.
print('행렬의 크기 :', tf_idf_matrix.shape)

행렬의 크기 : (1054983, 5000)

 

scikit-learn LDA Model 활용

사이킷런의 LDA 모델을 사용하여 학습한다. LSA와 마찬가지로 동일한 사이킷런 패키지이므로 실습 과정은 LSA와 유사하다.

토픽의 개수는 10개이다. (n_components)

from sklearn.decomposition import LatentDirichletAllocation

lda_model = LatentDirichletAllocation(n_components=10, learning_method='online', random_state=777, max_iter=1)
lda_model.fit_transform(tf_idf_matrix)

array([[0.0335099 , 0.0335099 , 0.0335099 , ..., 0.17024867, 0.0335099 ,
        0.0335099 ],
       [0.03365631, 0.03365631, 0.03365631, ..., 0.03365631, 0.03365631,
        0.03365631],
       [0.25184095, 0.0366096 , 0.0366096 , ..., 0.0366096 , 0.0366096 ,
        0.0366096 ],
       ...,
       [0.26687206, 0.02914502, 0.02914502, ..., 0.13007484, 0.02916018,
        0.28739608],
       [0.10378115, 0.02637829, 0.12325014, ..., 0.02637829, 0.02637829,
        0.02637829],
       [0.03376055, 0.03376055, 0.2255442 , ..., 0.03376055, 0.03376055,
        0.03376055]])

 

 

LDA를 통해 얻은 결과 행렬의 크기를 확인해보자.

print(lda_model.components_.shape)

(10, 5000)

 

전체 코퍼스로부터 얻은 10개의 토픽과 각 토픽에서의 단어의 비중을 확인해보자.

# LDA의 결과 토픽과 각 단어의 비중을 출력합시다
terms = tfidf_vectorizer.get_feature_names() # 단어 집합. 5,000개의 단어가 저장됨.

def get_topics(components, feature_names, n=5):
    for idx, topic in enumerate(components):
        print("Topic %d:" % (idx+1), [(feature_names[i], topic[i].round(5)) for i in topic.argsort()[:-n-1:-1]])

get_topics(lda_model.components_, terms)

Topic 1: [('australia', 9359.06334), ('sydney', 5854.97288), ('attack', 4784.76322), ('change', 4193.63035), ('year', 3924.88997)]
Topic 2: [('government', 6344.07413), ('charge', 5947.12292), ('man', 4519.7974), ('state', 3658.16422), ('live', 3625.10473)]
Topic 3: [('australian', 7666.65651), ('say', 7561.01807), ('police', 5513.22932), ('home', 4048.38409), ('report', 3796.04446)]
Topic 4: [('melbourne', 5298.35047), ('south', 4844.59835), ('death', 4281.78433), ('china', 3214.44581), ('women', 3029.28443)]
Topic 5: [('win', 5704.0914), ('canberra', 4322.0963), ('die', 4025.63057), ('open', 3771.65243), ('warn', 3577.47151)]
Topic 6: [('court', 5246.3124), ('world', 4536.86331), ('country', 4166.34794), ('woman', 3983.97748), ('crash', 3793.50267)]
Topic 7: [('election', 5418.5038), ('adelaide', 4864.95604), ('house', 4478.6135), ('school', 3966.82676), ('2016', 3955.11155)]
Topic 8: [('trump', 8189.58575), ('new', 6625.2724), ('north', 3705.40987), ('rural', 3521.42659), ('donald', 3356.26657)]
Topic 9: [('perth', 4552.8151), ('kill', 4093.61782), ('break', 2695.71958), ('budget', 2596.93268), ('children', 2586.01957)]
Topic 10: [('queensland', 5552.68506), ('coast', 3825.32603), ('tasmanian', 3550.75997), ('shoot', 3185.71575), ('service', 2695.21462)]

 

[GD-3] LSA, LDA 실습.ipynb
0.03MB


3. 텍스트 분포를 이용한 비지도학습 토크나이저

(1) 형태소 분석기와 단어 미등록 문제

텍스트의 분포를 이용해서 토큰화를 수행하는 비지도학습 토크나이저에 대해 알아보자.

 

형태서 분석기의 필요성

한국어는 교착어로, 하나의 낱말(어절)이 하나의 어근과 각각 단일한 기능을 가지는 하나 이상의 접사의 결합으로 이루어져 있는 언어이다. 조사라는 품사는 교착어에만 존재한다.

그래서 한국어는 영어처럼 띄어쓰기 단위 토큰화가 제대로 작동하지 않는다.

 

영어를 띄어쓰기 단위 토큰화해보자.

en_text = "The dog ran back to the corner near the spare bedrooms"
print(en_text.split())

['The', 'dog', 'ran', 'back', 'to', 'the', 'corner', 'near', 'the', 'spare', 'bedrooms']

 

한국어를 띄어쓰기 단위 토큰화해보자.

kor_text = "사과의 놀라운 효능이라는 글을 봤어. 그래서 오늘 사과를 먹으려고 했는데 사과가 썩어서 슈퍼에 가서 사과랑 오렌지 사 왔어"
print(kor_text.split())

['사과의', '놀라운', '효능이라는', '글을', '봤어.', '그래서', '오늘', '사과를', '먹으려고', '했는데', '사과가', '썩어서', '슈퍼에', '가서', '사과랑', '오렌지', '사', '왔어']

 

사과라는 단어가 총 4번 등장했는데, '', '', '', '' 등이 붙어있어 이를 제거하지 않으면 기계는 전부 다른 단어로 인식한다. 그래서 한국어는 주로 형태소 분석기를 사용한다.

 

형태소 분석기 중 하나인 Okt를 사용하여 형태소 분석을 해보자.

from konlpy.tag import Okt

tokenizer = Okt()
print(tokenizer.morphs(kor_text))

['사과', '의', '놀라운', '효능', '이라는', '글', '을', '봤어', '.', '그래서', '오늘', '사과', '를', '먹으려고', '했는데', '사과', '가', '썩어서', '슈퍼', '에', '가서', '사과', '랑', '오렌지', '사', '왔어']

 

단어 미등록 문제

print(tokenizer.morphs('모두의연구소에서 자연어 처리를 공부하는 건 정말 즐거워'))

['모두', '의', '연구소', '에서', '자연어', '처리', '를', '공부', '하는', '건', '정말', '즐거워']

 

모두의연구소는 하나의 단어인데 전부 분리된 결과를 보여준다.

 

(2) soynlp

soynlp는 품사 태깅, 형태소 분석 등을 지원하는 한국어 형태소 분석기이다. 비지도학습으로 형태소 분석을 한다는 특징이 있으며, 데이터에 자주 등장하는 단어들을 형태소로 분석한다.

soynlp 형태소 분석기는 내부적으로 단어 점수표로 동작한다. 이 점수는 응집 확률(cohesion probability)브랜칭 엔트로피(branching entropy)를 활용한다.

 

soynlp 실습

soynlp 깃허브에서 제공하는 예제 말뭉치를 다운로드하자.

import urllib.request
import os

txt_filename = os.getenv('HOME')+'/aiffel/topic_modelling/data/2016-10-20.txt'

urllib.request.urlretrieve("https://raw.githubusercontent.com/lovit/soynlp/master/tutorials/2016-10-20.txt",\
                            filename=txt_filename)

다운로드한 말뭉치를 문서 단위로 분리하자.

from soynlp import DoublespaceLineCorpus

# 말뭉치에 대해서 다수의 문서로 분리
corpus = DoublespaceLineCorpus(txt_filename)
len(corpus)

30091

 

총 30091개의 문서가 존재한다.

 

공백이 아닌 문서에 한해 상위 3개의 문서만 출력하자.

i = 0
for document in corpus:
  if len(document) > 0:
    print(document)
    i = i+1
  if i == 3:
    break

[출력 결과 생략]

 

soynlp는 비지도학습 형태소 분석기이므로 기존의 형태소 분석기와는 달리 학습 과정을 거쳐야 한다.

전체 코퍼스로부터 응집 확률과 브랜칭 엔트로피 단어 점수표를 만드는 과정이다.

WordExtractor.extract()를 통해서 전체 코퍼스에 대해 단어 점수표를 계산할 수 있다.

from soynlp.word import WordExtractor

word_extractor = WordExtractor()
word_extractor.train(corpus)
word_score_table = word_extractor.extract()

training was done. used memory 1.217 Gb
all cohesion probabilities was computed. # words = 223348
all branching entropies was computed # words = 361598
all accessor variety was computed # words = 361598

 

soynlp의 응집 확률 (cohesion probability)

응집 확률은 내부 문자열이 얼마나 응집하여 자주 등장하는지를 판단하는 척도이다.

응집 확률은 문자열을 문자 단위로 분리하여 내부 문자열을 만드는 과정에서, 왼쪽부터 순서대로 문자를 추가하면서 각 문자열이 주어졌을 때, 그 다음 문자가 나올 확률을 계산하여 누적 곱을 한 값이다.

이 값이 높을수록 전체 코퍼스에서 이 문자열 시퀀스는 하나의 단어로 등장할 가능성이 높다.

 

'반포한강공원에'라는 7의 길이를 가진 문자 시퀀스에 대해서 각 내부 문자열의 스코어를 구하는 과정이다.

실습을 통해 응집 확률을 계산해보자.

word_score_table["반포한"].cohesion_forward

0.08838002913645132

 

word_score_table["반포한강"].cohesion_forward

0.19841268168224552

 

word_score_table["반포한강공"].cohesion_forward

0.2972877884078849

 

word_score_table["반포한강공원"].cohesion_forward

0.37891487632839754

 

word_score_table["반포한강공원에"].cohesion_forward

0.33492963377557666

 

응집 확률이 최대화되는 구간은 '반포한강공원'으로 이것이 하나의 단어일 확률이 높다.

 

soynlp의 브랜칭 엔트로피(branching entropy)

브랜칭 엔트로피는 확률 분포의 엔트로피 값을 사용한다. 이는 주어진 문자열에서 다음 문자가 등장할 수 있는 가능성을 판단하는 척도이다.

브랜칭 엔트로피를 주어진 문자 시퀀스에서 다음 문자 예측을 위해 헷갈리는 정도라고 비유해보면, 브랜칭 엔트로피의 값은 하나의 완성된 단어에 가까워질수록 문맥으로 인해 정확히 예측할 수 있게 되므로 점차 줄어든다.

 

word_score_table["디스"].right_branching_entropy

1.6371694761537934

 

word_score_table["디스플"].right_branching_entropy

-0.0

 

word_score_table["디스플레"].right_branching_entropy

-0.0

 

다음 문자가 어떤 것이 올지 명백하기에 값이 0이 나온다.

 

word_score_table["디스플레이"].right_branching_entropy

3.1400392861792916

 

하나의 단어가 끝이 났기에 이후 다양한 단어들이 올 수 있으므로 값이 증가한다.

 

soynlp의 LTokenizer

띄어쓰기 단위로 잘 나뉜 문장L 토크나이저를 사용하면 좋다.

 

한국어는 띄어쓰기 단위로 나눈 어절 토큰이 주로 L토큰 + R토큰의 형식을 가질 때가 많다.

(ex. 공원에 → 공원 + 에 / 공부하는 → 공부 + 하는)

L 토크나이저는 L토큰+R토큰으로 나누되 점수가 가장 높은 L 토큰을 찾아내는 분리 기준을 가지고 있다.

from soynlp.tokenizer import LTokenizer

scores = {word:score.cohesion_forward for word, score in word_score_table.items()}
l_tokenizer = LTokenizer(scores=scores)
l_tokenizer.tokenize("국제사회와 우리의 노력들로 범죄를 척결하자", flatten=False)

[('국제사회', '와'), ('우리', '의'), ('노력', '들로'), ('범죄', '를'), ('척결', '하자')]

최대 점수 토크나이저

최대 점수 토크나이저(MaxScoreTokenizer)띄어쓰기가 되어 있지 않은 문장에서 점수가 높은 글자 시퀀스를 순차적으로 찾아내는 토크나이저이다.

from soynlp.tokenizer import MaxScoreTokenizer

maxscore_tokenizer = MaxScoreTokenizer(scores=scores)
maxscore_tokenizer.tokenize("국제사회와우리의노력들로범죄를척결하자")​

['국제사회', '와', '우리', '의', '노력', '들로', '범죄', '를', '척결', '하자']

 

728x90
반응형