본문 바로가기

Euron/정리

[Week16] 08. 텍스트 분석 - 실습

06. 토픽 모델링(Topic Modeling) - 20 뉴스그룹

 

토픽 모델링(Topic Modeling)이란 문서 집합에 숨어 있는 주제를 찾아내는 것이다.

사람은 더 함축적으로 문장을 요약적으로 하지만, 머신러닝 기반의 토픽 모델링은 숨겨진 주제를 효과적으로 표현할 수 있는 중심 단어를 함축적으로 추출한다.

 

머신러닝 기반의 토픽 모델링에는 LSA와 LDA 기법이 사용된다.

이번 절에서는 LDA만 사용한다.(차원 축소의 LDA와 다르다.)

 

이전에 사용한 20 뉴스그룹 데이터 세트를 이용해 토픽 모델링을 적용한다. 이를 위해 여러 주제의 데이터 중 8개의 주제를 추출한다.

fetch_20newsgroups() API는 categories 파라미터를 통해 필요한 주제만 필터링해 추출할 수 있다. 추출된 텍스트를 Count 기반으로 벡터화 변환을 수행한다.(LDA는 Count 기반의 벡터화만 사용한다.)

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation

# 모터사이클, 야구, 그래픽스, 윈도우즈, 중동, 기독교, 의학, 우주 주제를 추출. 
cats = ['rec.motorcycles', 'rec.sport.baseball', 'comp.graphics', 'comp.windows.x',
        'talk.politics.mideast', 'soc.religion.christian', 'sci.electronics', 'sci.med'  ]

# 위에서 cats 변수로 기재된 category만 추출. featch_20newsgroups( )의 categories에 cats 입력
news_df= fetch_20newsgroups(subset='all',remove=('headers', 'footers', 'quotes'), 
                            categories=cats, random_state=0)

#LDA는 Count 기반의 Vectorizer만 적용합니다.  
count_vect = CountVectorizer(max_df=0.95, max_features=1000, min_df=2, stop_words='english', ngram_range=(1,2))
feat_vect = count_vect.fit_transform(news_df.data)
print('CountVectorizer Shape:', feat_vect.shape)
CountVectorizer Shape: (7862, 1000)

 

 

피처 벡터화된 데이터 세트를 기반으로 LDA 토픽 모델링을 수행한다.

토픽의 개수는 추출한 주제의 개수와 같은 8개로 정한다.

lda = LatentDirichletAllocation(n_components=8, random_state=0)
lda.fit(feat_vect)

print(lda.components_.shape)
lda.components_
(8, 1000)
array([[3.60992018e+01, 1.35626798e+02, 2.15751867e+01, ...,
        3.02911688e+01, 8.66830093e+01, 6.79285199e+01],
       [1.25199920e-01, 1.44401815e+01, 1.25045596e-01, ...,
        1.81506995e+02, 1.25097844e-01, 9.39593286e+01],
       [3.34762663e+02, 1.25176265e-01, 1.46743299e+02, ...,
        1.25105772e-01, 3.63689741e+01, 1.25025218e-01],
       ...,
       [3.60204965e+01, 2.08640688e+01, 4.29606813e+00, ...,
        1.45056650e+01, 8.33854413e+00, 1.55690009e+01],
       [1.25128711e-01, 1.25247756e-01, 1.25005143e-01, ...,
        9.17278769e+01, 1.25177668e-01, 3.74575887e+01],
       [5.49258690e+01, 4.47009532e+00, 9.88524814e+00, ...,
        4.87048440e+01, 1.25034678e-01, 1.25074632e-01]])

LatentDirichletAllocation.fit()을 수행하면 객체는 components_ 속성값을 가지는데, 이는 개별 토픽별로 각 word 피처가 얼마나 많이 그 토픽에 할당됐는지에 대한 수치를 가지고 있다.

높은 값일수록 해당 word 피처는 그 토픽의 중심 word가 된다.

즉, components_array의 0번째 row, 10번째 col에 있는 값은 Topic 0번에 대해서 피처 벡터화된 행렬에서 10번째 칼럼에 해당하는 피처가 Topic 0번과 연관되는 수치값을 가지고 있는 것이다.

 

이를 토픽별로 연관도가 높은 순으로 word를 나열해본다.

def display_topics(model, feature_names, no_top_words):
    for topic_index, topic in enumerate(model.components_):
        print('Topic #',topic_index)

        # components_ array에서 가장 값이 큰 순으로 정렬했을 때, 그 값의 array index를 반환. 
        topic_word_indexes = topic.argsort()[::-1]
        top_indexes=topic_word_indexes[:no_top_words]
        
        # top_indexes대상인 index별로 feature_names에 해당하는 word feature 추출 후 join으로 concat
        feature_concat = ' '.join([feature_names[i] for i in top_indexes])                
        print(feature_concat)

# CountVectorizer객체내의 전체 word들의 명칭을 get_features_names( )를 통해 추출
feature_names = count_vect.get_feature_names_out() # get_feature_names_out()으로 속성 변경 

# Topic별 가장 연관도가 높은 word를 15개만 추출
display_topics(lda, feature_names, 15)
일부 불분명한 주제어들이 있지만 주로 비슷한 주제어가 추출됐음을 확인할 수 있다.
 
 

 


07. 문서 군집화 소개화 실습(Opinion Review 데이터 세트)

문서 군집화 개념

 

문서 군집화(Document Clustering)는 비슷한 텍스트 구성의 문서를 군집화(Clustering)하는 것이다. 

텍스트 분류 기반의 문서 분류와 다르게 학습 데이터 세트가 필요없는 비지도학습 기반으로 동작한다.

 

Opinion Review 데이터 세트를 이용한 문서 군집화 수행하기

 

UCI 머신러닝 레포지토리에 있는 Opinion Review 데이터 세트를 이용한다.

Opinosis Opinion / Review - UCI Machine Learning Repository

 

UCI Machine Learning Repository

This dataset is licensed under a Creative Commons Attribution 4.0 International (CC BY 4.0) license. This allows for the sharing and adaptation of the datasets for any purpose, provided that the appropriate credit is given.

archive.ics.uci.edu

 

여러 개의 파일을 한 개의 DataFrame으로 로딩해 데이터 처리를 진행한다.

먼저 디렉터리 내의 모든 파일에 대해 각각 for 반복문으로 반복하면서 개별 파일명을 파일명 리스트에 추가하고 개별 파일은 DataFrame으로 읽은 후 다시 문자열로 반환한 뒤 파일 내용 리스트에 추가한다.

import pandas as pd
import glob, os
import warnings 
warnings.filterwarnings('ignore')
pd.set_option('display.max_colwidth', 700)

# 아래는 제 컴퓨터에서 압축 파일을 풀어 놓은 디렉터리이니, 여러분의 디렉터리를 설정해 주세요  
path = r"C:\Users\jain5\Desktop\Euron\Data_Handling\opinosis+opinion+frasl+review\OpinosisDataset1.0\topics"
# path로 지정한 디렉터리 밑에 있는 모든 .data 파일들의 파일명을 리스트로 취합
all_files = glob.glob(os.path.join(path, "*.data"))    
filename_list = []
opinion_text = []

# 개별 파일들의 파일명은 filename_list 리스트로 취합, 
# 개별 파일들의 파일 내용은 DataFrame 로딩 후 다시 string으로 변환하여 opinion_text 리스트로 취합 
for file_ in all_files:
    # 개별 파일을 읽어서 DataFrame으로 생성 
    df = pd.read_table(file_,index_col=None, header=0,encoding='latin1')
    
    # 절대경로로 주어진 file 명을 가공. 만일 Linux에서 수행시에는 아래 \\를 / 변경. 
    # 맨 마지막 .data 확장자도 제거
    filename_ = file_.split('\\')[-1]
    filename = filename_.split('.')[0]

    # 파일명 리스트와 파일 내용 리스트에 파일명과 파일 내용을 추가. 
    filename_list.append(filename)
    opinion_text.append(df.to_string())

# 파일명 리스트와 파일 내용 리스트를  DataFrame으로 생성
document_df = pd.DataFrame({'filename':filename_list, 'opinion_text':opinion_text})
document_df.head()

 

문서를 TF-IDF 형태로 피처 벡터화한다.

RfidfVectorizer는 Lemmatization 같은 어근 변환을 직접 지원하진 않지만, tokenizer 인자에 커스텀 어근 변환 함수를 적용해 어근 변환을 수행할 수 있다.

먼저 이를 위한 LemNormalize()함수를 만든 후 진행한다.

from nltk.stem import WordNetLemmatizer
import nltk
import string

remove_punct_dict = dict((ord(punct), None) for punct in string.punctuation)
lemmar = WordNetLemmatizer()

# 입력으로 들어온 token단어들에 대해서 lemmatization 어근 변환. 
def LemTokens(tokens):
    return [lemmar.lemmatize(token) for token in tokens]

# TfidfVectorizer 객체 생성 시 tokenizer인자로 해당 함수를 설정하여 lemmatization 적용
# 입력으로 문장을 받아서 stop words 제거-> 소문자 변환 -> 단어 토큰화 -> lemmatization 어근 변환. 
def LemNormalize(text):
    return LemTokens(nltk.word_tokenize(text.lower().translate(remove_punct_dict)))
    
    
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf_vect = TfidfVectorizer(tokenizer=LemNormalize, stop_words='english' , \
                             ngram_range=(1,2), min_df=0.05, max_df=0.85 )

#opinion_text 컬럼값으로 feature vectorization 수행
feature_vect = tfidf_vect.fit_transform(document_df['opinion_text'])

 

RF-IDF 변환된 피처 벡터화 행렬 데이터에 대해서 군집화를 수행한다. 군집화 기법은 K-Means를 적용한다.

먼저 5개의 centroid 기반으로 수행한다.

from sklearn.cluster import KMeans

# 5개 집합으로 군집화 수행. 예제를 위해 동일한 클러스터링 결과 도출용 random_state=0 
km_cluster = KMeans(n_clusters=5, max_iter=10000, random_state=0)
km_cluster.fit(feature_vect)
cluster_label = km_cluster.labels_
cluster_centers = km_cluster.cluster_centers_

document_df['cluster_label'] = cluster_label
document_df.head()

 

document_df DataFrame에 'cluster_label' 컬럼을 추가해 저장하였다.

이 컬럼의 칼럼명을 이용하여 정렬을 수행하면 군집화 결과를 확인할 수 있다.

document_df[document_df['cluster_label']==3].sort_values(by='filename')

 

잘 군집화가 되었으나, 전반적으로 군집 개수가 약간 많게 설정돼 있어서 세분화되어 군집화되었다.

centroid를 3개로 낮추어 결과를 확인한다.

from sklearn.cluster import KMeans

# 3개의 집합으로 군집화 
km_cluster = KMeans(n_clusters=3, max_iter=10000, random_state=0)
km_cluster.fit(feature_vect)
cluster_label = km_cluster.labels_


# 소속 클러스터를 cluster_label 컬럼으로 할당하고 cluster_label 값으로 정렬
document_df['cluster_label'] = cluster_label
document_df.sort_values(by='cluster_label')

(결과 생략)

군집이 잘 구성됐다.

 

군집별 핵심 단어 추출하기

 

각 군집(Cluster)에 속한 문서는 핵심 단어를 주축으로 군집화돼있다.

KMeans 객체는 각 군집을 구성하는 단어 피처가 군집의 Centroid를 기분으로 얼마나 가깝게 위치해 있는지 clusters_centers_라는 속성으로 제공한다.

해당 속성은 배열 값으로 제공되며, 행은 개별 군집, 열은 개별 피처를 의미한다.

즉, cluster_centers[0,1]은 0번 군집에서 두 번째 피처의 위치 값이다.

cluster_centers = km_cluster.cluster_centers_
print('cluster_centers shape :',cluster_centers.shape)
print(cluster_centers)
cluster_centers shape : (3, 4611)
[[0.         0.00099499 0.00174637 ... 0.         0.00183397 0.00144581]
 [0.         0.00092551 0.         ... 0.         0.         0.        ]
 [0.01005322 0.         0.         ... 0.00706287 0.         0.        ]]

숫자가 1에 가까울수록 중심과 가까운 값을 의미한다.

 

ndarray의 argsort()[:,::-1]를이용하면 cluster_centers 배열 내 값이 큰 순으로 정렬된 위치 인덱스 값을 반환한다.

핵심 단어 피처의 이름을 출력하기 위해 정렬된 값이 아닌 위치 값을 반환한다.

get_cluster_details() 함수를 생성해 가장 값이 큰 데이터를 cluster_detail 변수에 기록하고 반환한다.

# 군집별 top n 핵심단어, 그 단어의 중심 위치 상대값, 대상 파일명들을 반환함. 
def get_cluster_details(cluster_model, cluster_data, feature_names, clusters_num, top_n_features=10):
    cluster_details = {}
    
    # cluster_centers array 의 값이 큰 순으로 정렬된 index 값을 반환
    # 군집 중심점(centroid)별 할당된 word 피처들의 거리값이 큰 순으로 값을 구하기 위함.  
    centroid_feature_ordered_ind = cluster_model.cluster_centers_.argsort()[:,::-1]
    
    #개별 군집별로 iteration하면서 핵심단어, 그 단어의 중심 위치 상대값, 대상 파일명 입력
    for cluster_num in range(clusters_num):
        # 개별 군집별 정보를 담을 데이터 초기화. 
        cluster_details[cluster_num] = {}
        cluster_details[cluster_num]['cluster'] = cluster_num
        
        # cluster_centers_.argsort()[:,::-1] 로 구한 index 를 이용하여 top n 피처 단어를 구함. 
        top_feature_indexes = centroid_feature_ordered_ind[cluster_num, :top_n_features]
        top_features = [ feature_names[ind] for ind in top_feature_indexes ]
        
        # top_feature_indexes를 이용해 해당 피처 단어의 중심 위치 상댓값 구함 
        top_feature_values = cluster_model.cluster_centers_[cluster_num, top_feature_indexes].tolist()
        
        # cluster_details 딕셔너리 객체에 개별 군집별 핵심 단어와 중심위치 상대값, 그리고 해당 파일명 입력
        cluster_details[cluster_num]['top_features'] = top_features
        cluster_details[cluster_num]['top_features_value'] = top_feature_values
        filenames = cluster_data[cluster_data['cluster_label'] == cluster_num]['filename']
        filenames = filenames.values.tolist()
        cluster_details[cluster_num]['filenames'] = filenames
        
    return cluster_details
    
# 보기 좋게 표현하기 위한 print 함수
def print_cluster_details(cluster_details):
    for cluster_num, cluster_detail in cluster_details.items():
        print('####### Cluster {0}'.format(cluster_num))
        print('Top features:', cluster_detail['top_features'])
        print('Reviews 파일명 :',cluster_detail['filenames'][:7])
        print('==================================================')
feature_names = tfidf_vect.get_feature_names_out()

cluster_details = get_cluster_details(cluster_model=km_cluster, cluster_data=document_df,\
                                  feature_names=feature_names, clusters_num=3, top_n_features=10 )
print_cluster_details(cluster_details)
####### Cluster 0
Top features: ['room', 'hotel', 'service', 'staff', 'food', 'location', 'bathroom', 'clean', 'price', 'parking']
Reviews 파일명 : ['bathroom_bestwestern_hotel_sfo', 'food_holiday_inn_london', 'food_swissotel_chicago', 'free_bestwestern_hotel_sfo', 'location_bestwestern_hotel_sfo', 'location_holiday_inn_london', 'parking_bestwestern_hotel_sfo']
==================================================
####### Cluster 1
Top features: ['interior', 'seat', 'mileage', 'comfortable', 'gas', 'gas mileage', 'transmission', 'car', 'performance', 'quality']
Reviews 파일명 : ['comfort_honda_accord_2008', 'comfort_toyota_camry_2007', 'gas_mileage_toyota_camry_2007', 'interior_honda_accord_2008', 'interior_toyota_camry_2007', 'mileage_honda_accord_2008', 'performance_honda_accord_2008']
==================================================
####### Cluster 2
Top features: ['screen', 'battery', 'keyboard', 'battery life', 'life', 'kindle', 'direction', 'video', 'size', 'voice']
Reviews 파일명 : ['accuracy_garmin_nuvi_255W_gps', 'battery-life_amazon_kindle', 'battery-life_ipod_nano_8gb', 'battery-life_netbook_1005ha', 'buttons_amazon_kindle', 'directions_garmin_nuvi_255W_gps', 'display_garmin_nuvi_255W_gps']
==================================================

함수 호출 인자는 KMeans 군집화 객체, 파일명 추출을 위한 document_df DataFrame, 핵심 단어 추출을 위한 피처명 리스트, 전체 군집 개수, 그리고 핵심 단어 추출 개수이다.

print 결과로 군집화 결과 주요 관심사를 파악할 수 있다.

 


08. 문서 유사도

문서 유사도 측정 방법 - 코사인 유사도

 

문서간 유사도 비교는 코사인 유사도(Cosine Similarity)를 사용한다.

두 벡터 사이의 사잇각을 구해 얼마나 유사한지 수치로 적용한다.

 

두 벡터 사잇각

 

유사도 코사인은 다음과 같이 구할 수 있다.

이를 함수로 구현하면 다음과 같다.

import numpy as np

def cos_similarity(v1, v2):
    dot_product = np.dot(v1, v2)
    l2_norm = (np.sqrt(sum(np.square(v1))) * np.sqrt(sum(np.square(v2))))
    similarity = dot_product / l2_norm     
    
    return similarity

 

간단한 문서를 TF-IDF로 벡터화된 행렬로 변환 후 유사도를 비교한다.

from sklearn.feature_extraction.text import TfidfVectorizer

doc_list = ['if you take the blue pill, the story ends' ,
            'if you take the red pill, you stay in Wonderland',
            'if you take the red pill, I show you how deep the rabbit hole goes']

tfidf_vect_simple = TfidfVectorizer()
feature_vect_simple = tfidf_vect_simple.fit_transform(doc_list)

# TFidfVectorizer로 transform()한 결과는 Sparse Matrix이므로 Dense Matrix로 변환. 
feature_vect_dense = feature_vect_simple.todense()

#첫번째 문장과 두번째 문장의 feature vector  추출
vect1 = np.array(feature_vect_dense[0]).reshape(-1,)
vect2 = np.array(feature_vect_dense[1]).reshape(-1,)

#첫번째 문장과 두번째 문장의 feature vector로 두개 문장의 Cosine 유사도 추출
similarity_simple = cos_similarity(vect1, vect2 )
print('문장 1, 문장 2 Cosine 유사도: {0:.3f}'.format(similarity_simple))
문장 1, 문장 2 Cosine 유사도: 0.402

 

사이킷런은 코사인 유사도 측정을 위한 API를 제공한다.

cosime_silmilarity() 함수는 비교할 문서의 두 피처 행렬을 입력 파라미터로 받는다.

from sklearn.metrics.pairwise import cosine_similarity

similarity_simple_pair = cosine_similarity(feature_vect_simple[0] , feature_vect_simple)
print(similarity_simple_pair)
[[1.         0.40207758 0.40425045]]

첫 번째 유사도 값인 1은 자신과의 유사도 측정 값이다.

 

또한 cosine_similarity()는 pair로 코사인 유사도 값을 제공할 수 있다.

similarity_simple_pair = cosine_similarity(feature_vect_simple , feature_vect_simple)
print(similarity_simple_pair)
print('shape:',similarity_simple_pair.shape)
[[1.         0.40207758 0.40425045]
 [0.40207758 1.         0.45647296]
 [0.40425045 0.45647296 1.        ]]
shape: (3, 3)

 

Opinion Review 데이터 세트를 이용한 문서 유사도 측정

 

앞서 문서 군집화에서 사용한 Opinion Review 데이터 세트를 이용해 이들 간의 유사도를 측정한다.

import pandas as pd
import glob, os
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
import warnings
warnings.filterwarnings('ignore')

path = r"C:\Users\jain5\Desktop\Euron\Data_Handling\opinosis+opinion+frasl+review\OpinosisDataset1.0\topics"
all_files = glob.glob(os.path.join(path, "*.data"))     
filename_list = []
opinion_text = []

for file_ in all_files:
    df = pd.read_table(file_,index_col=None, header=0,encoding='latin1')
    filename_ = file_.split('\\')[-1]
    filename = filename_.split('.')[0]
    filename_list.append(filename)
    opinion_text.append(df.to_string())

document_df = pd.DataFrame({'filename':filename_list, 'opinion_text':opinion_text})

tfidf_vect = TfidfVectorizer(tokenizer=LemNormalize, stop_words='english' , \
                             ngram_range=(1,2), min_df=0.05, max_df=0.85 )
feature_vect = tfidf_vect.fit_transform(document_df['opinion_text'])

km_cluster = KMeans(n_clusters=3, max_iter=10000, random_state=0)
km_cluster.fit(feature_vect)
cluster_label = km_cluster.labels_
cluster_centers = km_cluster.cluster_centers_
document_df['cluster_label'] = cluster_label
from sklearn.metrics.pairwise import cosine_similarity

# cluster_label=2인 데이터는 호텔로 클러스터링된 데이터임. DataFrame에서 해당 Index를 추출
hotel_indexes = document_df[document_df['cluster_label']==2].index
print('호텔로 클러스터링 된 문서들의 DataFrame Index:', hotel_indexes)

# 호텔로 클러스터링된 데이터 중 첫번째 문서를 추출하여 파일명 표시.  
comparison_docname = document_df.iloc[hotel_indexes[0]]['filename']
print('##### 비교 기준 문서명 ',comparison_docname,' 와 타 문서 유사도######')

''' document_df에서 추출한 Index 객체를 feature_vect로 입력하여 호텔 클러스터링된 feature_vect 추출 
이를 이용하여 호텔로 클러스터링된 문서 중 첫번째 문서와 다른 문서간의 코사인 유사도 측정.'''
similarity_pair = cosine_similarity(feature_vect[hotel_indexes[0]] , feature_vect[hotel_indexes])
print(similarity_pair)
호텔로 클러스터링 된 문서들의 DataFrame Index: Index([ 0,  2,  3,  4,  5,  8,  9, 10, 11, 12, 19, 23, 26, 27, 33, 34, 35, 36,
       41, 42, 43, 44, 48, 49, 50],
      dtype='int64')
##### 비교 기준 문서명  accuracy_garmin_nuvi_255W_gps  와 타 문서 유사도######
[[1.         0.03004155 0.0399055  0.03321949 0.01659167 0.34657949
  0.19665144 0.03271487 0.03613626 0.01828703 0.02061394 0.0511192
  0.03626147 0.01762981 0.10104874 0.15598296 0.03667107 0.03388559
  0.03301419 0.02971984 0.20146574 0.02735475 0.18343275 0.01715513
  0.12148216]]

 

숫자로만 표시된 유사도를 이해하기 위해 유사도가 높은 순으로 정렬 후 시각화를 진행한다.

cosine_similarity()는 쌍 형태의 ndarray를 반환하므로 이를 판단스 인덱스로 이용하기 위해 reshape(-1)로 차원을 변경한다.

import seaborn as sns
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# argsort()를 이용하여 앞예제의 첫번째 문서와 타 문서간 유사도가 큰 순으로 정렬한 인덱스 반환하되 자기 자신은 제외. 
sorted_index = similarity_pair.argsort()[:,::-1]
sorted_index = sorted_index[:, 1:]

# 유사도가 큰 순으로 hotel_indexes를 추출하여 재 정렬. 
hotel_sorted_indexes = hotel_indexes[sorted_index.reshape(-1)]

# 유사도가 큰 순으로 유사도 값을 재정렬하되 자기 자신은 제외
hotel_1_sim_value = np.sort(similarity_pair.reshape(-1))[::-1]
hotel_1_sim_value = hotel_1_sim_value[1:]

# 유사도가 큰 순으로 정렬된 Index와 유사도값을 이용하여 파일명과 유사도값을 Seaborn 막대 그래프로 시각화
hotel_1_sim_df = pd.DataFrame()
hotel_1_sim_df['filename'] = document_df.iloc[hotel_sorted_indexes]['filename']
hotel_1_sim_df['similarity'] = hotel_1_sim_value

fig1 = plt.gcf()
sns.barplot(x='similarity', y='filename',data=hotel_1_sim_df)
plt.title(comparison_docname)
fig1.savefig('p553_hotel.tif', format='tif', dpi=300, bbox_inches='tight')

 


09. 한글 텍스트 처리 - 네이버 영화 평점 감성 분석

한글 NLP 처리의 어려움

 

한글 언어 처리는 띄어쓰기와 조사로 인해 라틴어 처리보다 어렵다.

 

KoLNPy 소개

 

KoNLPy는 파이썬의 대표적인 한글 형태소 패키지이다.

자바나..기타 등등 설정은 이미 되어있어서 책 내용은 많이 생략하고 진행했다.

https://velog.io/@qw4735/konlpy-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95

 

konlpy 설치방법

필요한 것! 설치 가장 첫 화면에서 “ADD to PATH”에 꼭 체크할 것 (하지 않으면 이후 과정 모두 실패)https://www.oracle.com/java/technologies/downloads/ (x32, x64는 Window 버전 기준)설치시,

velog.io

위 블로그가 간단하게 설명해줌.

 

한가지 JAVA_HOME 설정은 기존의 "C:\Program Files\Java\jdk-17" 가 아닌 "C:\Program Files\Java\jdk-17\bin\server" 로 바꿔줘야 했는데...나중에 문제 없겠지?

이정도도 따로 변수를 못찾네...

혹시 변수를 계속 못찾으면 jupyter notebook을 아예 껐다가 다시 시작하면 된다!

 

데이터 로딩

 

https://github.com/e9t/nsmc

 

GitHub - e9t/nsmc: Naver sentiment movie corpus

Naver sentiment movie corpus. Contribute to e9t/nsmc development by creating an account on GitHub.

github.com

위에서 다운 받았다.

망할 깃허브도 제대로 작동을 안해서...데이터 다운 받기 위해 직접 git clone까지 해서 경로 옮겼음..ㅜ

 

ratings_train.txt 파일은 '\t' 로 칼럼이 분리되어 있고, 한글 파일 인코딩 이슈로 인해 encoding을 cp949로 설정한다.

import pandas as pd

train_df = pd.read_csv(r"C:\Users\jain5\Desktop\Euron\Data_Handling\ratings_train.txt", sep='\t')
train_df.head(3)

무슨 영화일까...

1이 긍정, 0이 부정 감성이다.

 

데이터 가공을 수행한다.

import re

train_df = train_df.fillna(' ')
# 정규 표현식을 이용하여 숫자를 공백으로 변경(정규 표현식으로 \d 는 숫자를 의미함.) 
train_df['document'] = train_df['document'].apply( lambda x : re.sub(r"\d+", " ", x) )

# 테스트 데이터 셋을 로딩하고 동일하게 Null 및 숫자를 공백으로 변환
test_df = pd.read_csv(r"C:\Users\jain5\Desktop\Euron\Data_Handling\ratings_test.txt", sep='\t')
test_df = test_df.fillna(' ')
test_df['document'] = test_df['document'].apply( lambda x : re.sub(r"\d+", " ", x) )

# id 칼럼 삭제 수행
train_df.drop('id', axis=1, inplace=True) 
test_df.drop('id', axis=1, inplace=True)

 

이제 TF-IDF 방식으로 단어를 벡터화 한다.

먼저 각 문장을 한글 형태소 분석을 통해 형태소 단어로 토큰화 한다.

한글 형태소 엔진은 SNS 분석에 적합한 Twitter 클래스를 이용한다. Twitter 객체의 morphs() 메서드를 이용하면 입력 인자로 들어온 문장을 형태소 단어 형태로 토큰화해 list 객체로 반환한다.

문장을 형태소 단어 형태로 반환하는 별도의 tokenizer 함수를 만든다.

from konlpy.tag import Twitter

twitter = Twitter()
def tw_tokenizer(text):
    # 입력 인자로 들어온 text 를 형태소 단어로 토큰화 하여 list 객체 반환
    tokens_ko = twitter.morphs(text)
    return tokens_ko

 

이제 사이킷런의 TfidVectorizer를 이용해 TF-IDF 피처 모델을 생성한다.(10분 정도 걸림)

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV

# Twitter 객체의 morphs( ) 객체를 이용한 tokenizer를 사용. ngram_range는 (1,2) 
tfidf_vect = TfidfVectorizer(tokenizer=tw_tokenizer, ngram_range=(1,2), min_df=3, max_df=0.9)
tfidf_vect.fit(train_df['document'])
tfidf_matrix_train = tfidf_vect.transform(train_df['document'])

 

로지스틱 회귀를 이용해 분류 기반의 감성분석을 수행한다.(+GridSearchCV)

# Logistic Regression 을 이용하여 감성 분석 Classification 수행. 
lg_clf = LogisticRegression(random_state=0, solver='liblinear')

# Parameter C 최적화를 위해 GridSearchCV 를 이용. 
params = { 'C': [1 ,3.5, 4.5, 5.5, 10 ] }
grid_cv = GridSearchCV(lg_clf , param_grid=params , cv=3 ,scoring='accuracy', verbose=1 )
grid_cv.fit(tfidf_matrix_train , train_df['label'] )
print(grid_cv.best_params_ , round(grid_cv.best_score_,4))
Fitting 3 folds for each of 5 candidates, totalling 15 fits
{'C': 3.5} 0.8593

 

이제 테스트 세트를 이용해 최종 감성 분석 예측을 수행한다.

테스트 세트를 이용해 예측할 때는 피처 개수를 같게 하기 위해 학습할 때 적용한 TfidVectorizer를 그대로 사용해야한다.

from sklearn.metrics import accuracy_score

# 학습 데이터를 적용한 TfidfVectorizer를 이용하여 테스트 데이터를 TF-IDF 값으로 Feature 변환함. 
tfidf_matrix_test = tfidf_vect.transform(test_df['document'])

# classifier 는 GridSearchCV에서 최적 파라미터로 학습된 classifier를 그대로 이용
best_estimator = grid_cv.best_estimator_
preds = best_estimator.predict(tfidf_matrix_test)

print('Logistic Regression 정확도: ',accuracy_score(test_df['label'],preds))
Logistic Regression 정확도:  0.86172

 

 


# 회고

 

텍스트 분석 끝..은 아니고 마지막 절이 남았는데 과제에 포함이 안됐다..왜지?

중간에 JAVA_HOME 변수 편집 과정을 지나쳐서 오류가 났다. 불안해서 변수 편집은 하기 싫고, path에 추가하고 싶었는데 적용이 잘 안됐다.

https://uyt8989.tistory.com/124

 

[파이썬 오류 해결] No JVM shared library file (jvm.dll) found. Try setting up the JAVA_HOME environment variable properly

자연어 처리 공부를 하려고 KoNLPy를 사용하고 싶었다. 그런데 자꾸 다음과 같은 오류가 날 괴롭혔다. No JVM shared library file (jvm.dll) found. Try setting up the JAVA_HOME environment variable properly. 이 오류가 무

uyt8989.tistory.com

나중에 문제가 생기면 위 블로그 보고 다시 참고해야지...