본문 바로가기

NPL with ML

문서 군집화 실전 예제 - Opinion Review Data Set

반응형

문서 군집화는 비슷한 텍스트 구성의 문서를 군집화(Clustering)하는 것입니다. 문서 군집화는 동일한 군집에 속하는 문서를 같은 카테고리 소속으로 분류할 수 있으므로 앞에서 소개한 텍스트 분류 기반의 문서 분류와 유사합니다. 하지만 텍스트 분류 기반의 문서 분류는 사전에 결정 카테고리 값을 가진 학습 데이터 세트가 필요한 데 반해, 문서 군집화는 학습 데이터 세트가 필요없는 비지도학습 기반으로 동작합니다.

 

 

예제는 UCI의 Opinion Review의 데이타를 이용합니다.  하기 화일을 다운로드 후 압축을 풀어도 됩니다.

 

topics.zip
0.27MB

import pandas as pd
import glob, os

path = r'C:\\Users\\HANA\\PycharmProjects\\HANATOUR\\NLP\\TEXT_Example\\topic'
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})
print(document_df.head())

여러 개의 파일을 DataFrame으로 읽은 후 다시 문자열로 반환한 뒤 파일 내용 리스트에 추가합니다.

 

문서를 TF-IDF 형태로 피처 벡터화하기전에 공통 함수를 작성합니다.

#WordNet is a semantically-oriented dictionary of English included in NLTK.
def LemTokens(tokens):
    lemmer = nltk.stem.WordNetLemmatizer()
    return [lemmer.lemmatize(token) for token in tokens]
    # remove_punct_dict = dict((ord(punct), None) for punct in string.punctuation)

def LemNormalize(text):
    remove_punct_dict = dict((ord(punct), None) for punct in string.punctuation)
    return LemTokens(nltk.word_tokenize(text.lower().translate(remove_punct_dict)))

 

tokenizer는 Lemmatiation을 이용한 위의 함수를 이용하여 opinion_text칼럼을 입력하면 개별 문서 텍스트에 대해 TF-IDF변환된 피처 벡터화된 행렬을 구할 수 있습니다.

from sklearn.feature_extraction.text import TfidfVectorizer
import Common_Module.CMNLP as CMNLP

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

 

각 문서별 텍스트가 TF-IDF변환된 피처 벡터화 행렬 데이터에 대해서 군집화를 수행해 어떤 문서끼리 군집화되는지 확인해 보겠습니다. 여기서는 Kmeans를 이용합니다.

from sklearn.cluster import KMeans

km_cluster = KMeans(n_clusters=5, max_iter=1000, 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

print(document_df.head())
print(document_df[document_df['cluster_label']==0].sort_values(by='filename'))
print(document_df[document_df['cluster_label']==1].sort_values(by='filename'))
print(document_df[document_df['cluster_label']==2].sort_values(by='filename'))
print(document_df[document_df['cluster_label']==3].sort_values(by='filename'))
print(document_df[document_df['cluster_label']==4].sort_values(by='filename'))

 

군집별 핵심 단어 추출하기

이번에는 각 군집(Cluster)에 속한 문서는 핵심 단어를 주축으로 군집화돼 있을 것입니다. 이번에는 각 군집을 구성하는 핵심 단어가 어떤 것이 있는지 확인해 보겠습니다.

 

Kmeans객체는 각 군집을 구성하는 단어 피처가 군집의 중심(Centroid)을 기준으로 얼마나 가깝게 위치해 있는지 cluster_centers_라는 속성으로 제공합니다. cluster_centers_는 배열 값으로 제공되며, 행은 개별 군집을, 열은 개별 피처를 의미합니다. 각 배역 내의 값은 개별 군집 내의 상대 위치를 숫자 값으로 표현한 일종의 좌표 값입니다. 예를 들어 cluster_centers[0,1]은 0번 군집에서 두 번째 피처의 위치 값입니다.

 

cluster_centers = km_cluster.cluster_centers_
print('cluster centers shape :', cluster_centers.shape)
print(cluster_centers)

 

위의 결과를 보면 (5, 4611)로 5개의 군집, word 피처가 2409개로 구성되었음을 의미합니다. 각 행의 배열 값은 각 군집 내의 2409개 피처의 위치가 개별 중심과 얼마나 가까운가를 상대 값으로 나타낸 것입니다. 0에서 1까지의 값을 가지며 수 있으며 1에 가까울수록 중심과 가까운 값을 의미합니다.

cluster centers shape : (5, 4611)
[[0.00857792 0.         0.         ... 0.01710556 0.         0.        ]
 [0.         0.00099499 0.00174637 ... 0.         0.00183397 0.00144581]
 [0.         0.00102835 0.         ... 0.         0.         0.        ]
 [0.01282853 0.         0.         ... 0.         0.         0.        ]
 [0.00881133 0.         0.         ... 0.00331061 0.         0.        ]]

 

각 그룹과 과장 연관성이 높은 단어의 핵심을 뽑기 위한 함수와 명령어를 실행한다.

# 군집별 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]['filename'] = filenames

    return cluster_details


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('filename :', cluster_detail['filename'][:7])
        print('==================================================')

 

feature_names = tfodf_vect.get_feature_names()
cluster_details = CMNLP.get_cluster_details(cluster_model=km_cluster, cluster_data=document_df, feature_names=feature_names, clusters_num=5, top_n_features=10)
CMNLP.print_cluster_details(cluster_details)

 

하기 결과를 확인하면 각 결과를 확인할 수 있다.

==================================================
####### Cluster 1
Top features: ['room', 'hotel', 'service', 'staff', 'food', 'location', 'bathroom', 'clean', 'price', 'parking']
filename : ['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 2
Top features: ['interior', 'seat', 'mileage', 'comfortable', 'gas', 'gas mileage', 'car', 'performance', 'quality', 'comfort']
filename : ['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 3
Top features: ['battery', 'battery life', 'life', 'video', 'performance', 'sound', 'ipod', 'sound quality', 'camera', 'video camera']
filename : ['battery-life_amazon_kindle', 'battery-life_ipod_nano_8gb', 'battery-life_netbook_1005ha', 'performance_netbook_1005ha', 'sound_ipod_nano_8gb', 'video_ipod_nano_8gb']
==================================================
####### Cluster 4
Top features: ['screen', 'keyboard', 'size', 'kindle', 'page', 'button', 'font', 'voice', 'feature', 'book']
filename : ['buttons_amazon_kindle', 'eyesight-issues_amazon_kindle', 'features_windows7', 'fonts_amazon_kindle', 'keyboard_netbook_1005ha', 'navigation_amazon_kindle', 'price_amazon_kindle']
==================================================

Process finished with exit code 0
반응형