풀스택 개발 학습 과정/머신러닝, 딥러닝

[텍스트 마이닝] 문서 군집화

육츠 2024. 8. 1. 00:57

파일의 내용을 참고하여 해당 테마 군집화 해보기

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

path = r'.\topics'

all_files = glob.glob(os.path.join(path,'*.data'))  #해당경로에 있는 파일을 다 가져와 목록을 만듦
filename_list = []
opinion_text = []

for file in all_files:
    df = pd.read_table(file, encoding= 'latin1')  # 파일이나 url로 읽어오는 
    # 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})
document_df.head()

 

어근 변환 함수 선언

import string

string.punctuation
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

 

안의 내용 확인

from nltk.stem import WordNetLemmatizer
import nltk
import string  # 구두점 정보 위한

# 문장에 포함된 구두점을 삭제하기 위한 매핑 정보 생성
# ord() : 매개변수로 전달된 문자에 해당하는 유니코드를 반환
remove_punct_dict = { ord(punct):None for punct in string.punctuation } 
lemmar = WordNetLemmatizer()

# 입력으로 문장을 받아서 -> stopword를 제거 -> 
# 소문자로 변환 -> 단어로 다시 토큰화 -> 어근 변환
def LemNormalize(text):
    tokens = nltk.word_tokenize(text.lower().translate(remove_punct_dict)) # 소문자 변환 -> 구두점 제거
    return [lemmar.lemmatize(token) for token in tokens ] # 어근 찾기

 

translate() 실습

- 문자열 내에 특정 문자를 다른 문자로 일괄 변경하는 함수
- 매개변수로 변경하고자 하는 정보를 딕셔너리로 전달한다 {(k) 원본 문자 유니코드 : (v) 변경 문자 유니코드}

# 원래 문자와 바꿀문자에 대한 맵핑정보(딕셔너리)를 만드는 
# 바꿀 문자는 같은 길이를 가지고 있어야한다.

map = str.maketrans('[],','   ')
print(map)
# 32 : 공백의 유니코드 값

text = '[고양이, 강아지, 다람쥐, 토끼]'
text = text.translate(map)
print(text)
{91: 32, 93: 32, 44: 32}
 고양이  강아지  다람쥐  토끼 

 

TF - IDF 기반 Vectorizer

from sklearn.feature_extraction.text import TfidfVectorizer


# tokenizer : 토큰화를 별도의 사용자 정의 함수로 지정 가능

tfidf_vect = TfidfVectorizer(tokenizer=LemNormalize, stop_words= 'english', 
                             ngram_range=(1,2), 
                             min_df= 0.05, 
                             # 정수 지정: 지정한 n 개 이하는 피처 추출에서 제외(전체 문서에 걸처 너무 낮은 빈도수를 가지는 단어)
                             # 실수 지정: = 백분율) 이하 빈도수 까진 피처추출 제외
                            max_df= 0.85) # 의미없는 불용어와 같다.

feature_vect = tfidf_vect.fit_transform(document_df['opinion_text'].values)
# feature_vect.shape
import numpy as np
df = pd.DataFrame(feature_vect.toarray())
df['a'] = np.random.randint(1,10,51)
df.head()

 

군집분석

(1) 군집 수 5 개

from sklearn.cluster import KMeans

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()

마지막 cluster_label  = 군집 번호

 

전자기기에 대한 리뷰로 군집

# 전자기기 관련
document_df[document_df['cluster_label']== 0].sort_values(by = 'filename')

 

호텔에 대한 리뷰로 군집

# 호텔과 관련된
document_df[document_df['cluster_label']== 1].sort_values(by = 'filename')

 

(2) 군집 수 3개

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
document_df.head()

 

클러스터 0은 호텔에 대한 리뷰로 군집

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

 

자동차에 대한 리뷰로 군집

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

 

전자기기에 대한 리뷰로 군집

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

 

군집별 핵심 단어 추출하기 (cluster_centers_)

피처(단어)가 군집의 중심으로부터 얼마나 가깝게 있는지를 군집 내의 피처들과의 상대 위치로 나타낸 값
0 ~ 1 사이의 값으로 1에 가까울 수록 중심과 가까운 것을 의미

cluster_centers = km_cluster.cluster_centers_
print(cluster_centers.shape)
# (3,4611) : 군집이 3개, 단어가 4611 개
print(cluster_centers)
(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.        ]]

 

 

군집별 핵심 단어 추출 함수 정의

[매개변수 목록]
model : Kmeans 모델 객체
cluster_data : 리뷰 문서 데이터 및 군집 결과 값을 가지고 있는 Dataframe 객체
feature_names: 피처 카운트 벡터화 된 각 피처(단어)의 이름 목록
cluster_num: 군집 수
top_n : 상위 n개의 수

def get_cluster_details(model, cluster_data, feature_names, cluster_num, top_n =10):
    ordered_idx = model.cluster_centers_.argsort()[:,::-1]   # 군집별 중심으로 부터 상대적 군집 거리를 가져옴
    
    # 결과 포맷
    # {0: {'cluster':0, 'top_features':[...]}, {1: ... },} 키: 군집 번호
    cluster_details = {}
    for cluster_num in range(cluster_num):
        cluster_details[cluster_num] = {}  # 키
        
        cluster_details[cluster_num]['cluster']  = cluster_num # 0번째 키
        top_feature_index = ordered_idx[cluster_num, : top_n]  # 내림차순 정렬된 
        
        top_features = [ feature_names[idx] for idx in top_feature_index]  # 핵심단어 n개
        cluster_details[cluster_num]['top_features'] = top_features

    return cluster_details
feature_names = tfidf_vect.get_feature_names_out()
print(feature_names.shape)
print(feature_names)
(4611,)
['0 5' '0 great' '0 room' ... 'zoom' '\x96' '£6']

 

cluster_details = get_cluster_details(km_cluster, document_df, feature_names, cluster_num= 3, top_n= 10)

for cluster_num , cluster_detail in cluster_details.items():
    print(f'cluster num: {cluster_num}')
    print(f'top features: {cluster_detail["top_features"]}')
    print('-'*40)
cluster num: 0
top features: ['room', 'hotel', 'service', 'staff', 'food', 'location', 'bathroom', 'clean', 'price', 'parking']
----------------------------------------
cluster num: 1
top features: ['interior', 'seat', 'mileage', 'comfortable', 'gas', 'gas mileage', 'transmission', 'car', 'performance', 'quality']
----------------------------------------
cluster num: 2
top features: ['screen', 'battery', 'keyboard', 'battery life', 'life', 'kindle', 'direction', 'video', 'size', 'voice']
----------------------------------------