데이터 분석 Data Analytics/프로그래머스 데이터분석 데브코스 2기

[TIL] 데이터분석 데브코스 63일차 (1) - 토픽 모델링(Topic Modeling)/LDA/워드 클라우드(Word Cloud)

상급닌자연습생 2024. 5. 18. 15:17

토픽 모델링(Topic Modeling) = 토픽 + 모델링

: 대규모 텍스트 데이터 내 다양한 주제를 자동으로 식별/분류하는 과정

  • 토픽(Topic) : 문서 집합 안에서 논의되는 주제나 개념
  • 모델링(Modeling) : 통계적인 방법으로 데이터의 패턴을 추출하는 과정

 

토픽 모델링의 주제

  • 일반적으로 단어의 집합으로 표현
  • 텍스트 내의 특정 패턴이나 빈도를 기반으로 선택
  • 데이터 소스에 따라 주제의 범위가 결정 : 뉴스 기사, 소셜 미디어, 학술 논문 등
  • 하나의 문서에 다수의 주제를 포함
  • 데이터의 트렌드와 특정 이벤트를 반영하는 경향
  • 선택된 주제는 다른 텍스트 분석에 긍정적인 도움이 되는 방향으로 사용
    • 특정 주제와 비슷한 텍스트만 추림
    • 분석 결과를 해석하는 과정에서 토픽과 관련된 부분만을 취함

 

 

 

 

 

 

 

LDA (Latent Dirichlet Allocation)

: 문서 표면에 드러나지 않고 숨어있는 토픽의 확률 분포(Latent Dirichlet)를 가정하고, 각 단어를 토픽에 할당(Allocation)하는 토픽 모델링 대표 알고리즘

 

📌 Dirichlet 분포
: 확률이나 비율의 집합을 '분포'로 표현한 것

 

📌 Latent Dirichlet 가정

전체 문서는 여러 개별적인 문서들의 집합으로 구성되며,
하나의 개별 문서는 여러 개의 주제로 구성되고, 
하나의 주제는 여러 개의 단어들로 구성된다.

 

 

예제

  • 문서 1은 A 토픽이 존재하며, 그 단어는 빨간색으로 표시
  • 문서 2는 B 토픽이 존재하고, 파란색으로 표시
  • 문서 3은 A와 B 토픽이 둘 다 존재
문서 1 : 우리 부모님은 건강을 위해 아침마다 수영을 하시고 저녁에는 산책을 합니다.
문서 2 : 나와 동생은 햄버거를 좋아합니다. 특히 치킨이 들어간 햄버거를 좋아하고, 어제는 피자를 먹었습니다.
문서 3 : 오늘은 나의 생일이라 햄버거를 먹었습니다. 그런데 살이 너무 많이 쪄서 산책수영을 시작했습니다.

 

  • 문서를 구성하는 토픽의 관점 :
    • 문서 1 : 100% 토픽 A
    • 문서 2 : 100% 토픽 B
    • 문서 3 : 67% 토픽 A & 33% 토픽 B
  • 토픽을 구성하는 단어의 관점 : 
    • 토픽 A : 건강 (20%) / 수영 (40%) / 산책 (40%)
    • 토픽 B : 햄버거 (60%) / 치킨 (20%) / 피자 (20%)

 

 

 

 

LDA의 두 확률값

: LDA 알고리즘을 결정짓는 중요한 두 개의 확률값

 

1) P(토픽 t | 문서 d)

: 문서에 어떤 토픽이 들어있는가

  • 특정 문서 d에 토픽 t가 차지하는 비율
  • 문서에서 각 토픽이 얼마나 중요한지를 나타냄

 

2) P(단어 w | 토픽 t)

: 각 토픽에 어떤 단어가 들어있는가

  • 특정 토픽 t에서 단어 w가 차지하는 비율
  • 토픽에 특정 단어가 나타낼 확률

 

 

► 실제로 구해야하는 값 :  P(토픽 t | 문서 d, 단어 w)

: 특정 단어가 어떤 문서의 주제에 속할 확률(=어떤 단어가 문서의 주제와 얼마나 잘 맞는가)

  • 값이 클 경우 : 특정 단어가 그 문서의 주제와 매우 밀접한 관련이 있음을 의미
  • but, 직접적으로 구하기 어려움 → ∝ P(토픽 t | 문서 d) × P(단어 w | 토픽 t)

 

 

 

 

 

알고리즘 적용 과정

1. 사용자가 토픽 개수 K 설정

예시) 문서 3개, 토픽 2개 (K = 2(A, B))

 

 

 

2. 문서 내 모든 단어에 무작위로 K개 토픽 중 1개를 할당

 

 

3.(위에서 할당된 단어의 토픽이 잘못 할당 되고 나머지 단어들은 맞게 할당 되었다는 가정 하에,)

단어 w의 토픽 할당을 결정하기 위해 나머지 단어들의 할당 결과를 활용 P(토픽 t | 문서 d)× P(단어 w | 토픽 t) 계산

  • 이 값이 가장 커지는 t를 w에 할당
  • 전체 문서의 모든 단어를 대상으로 `for`문을 돌며 종료 시점에 도달할 때 까지 반복 연산 진행
    • w에 할당된 의 변화가 없는 시점까지
    • 정해진 업데이트 횟수 도달까지

 

 

4. 최종 결과 분석

  1. 토픽에 존재하는 단어를 보고 토픽이 의미하는 주제를 사용자가 정의 (ex. 토픽 1은 주제가 '먹거리' 구나)
  2. 할당된 토픽을 기준으로 문서에 존재하는 토픽을 분석 (ex. 문서 1은 토픽이 2, 6, 8이 있구나)

 

 

 

 

 

 

 

 


토픽 모델링 실습

 

사용 데이터

🔗 실습 링크 : https://www.kaggle.com/datasets/marklvl/sentiment-labelled-sentences-data-set

 

Sentiment Labelled Sentences Data Set

From Group to Individual Labels using Deep Features, Kotzias et. al,. KDD 2015

www.kaggle.com

 

※ 감성 분석이 목적이 아니기 때문에 감정 상태를 나타내는 0과 1은 사용하지 않음

※ 본문과 tab(`'\t'`)로 구분되어 있음

 

 

 

 

문제 정의

데이터 셋에 존재하는 잠재적인 주제를 가정하고 이를 찾아내는 것

 

입력 (input 출력 (output)
- 전체 텍스트 문서 집합
- 토픽의 수
- 문서 별 토픽 분포
- 토픽 별 단어 분포

 

 

 

 

 

1단계. 데이터 로드

# txt 파일을 pandas의 형태로 변환하지 않고
# 직접적으로 load
# 과정에서 0 혹은 1 부분은 제거

file_path = 'yelp_labelled.txt'

with open(file_path, 'r', encoding='utf-8') as file :
    data = [line.split('\t')[0] for line in file]

print(data[:5])

 

 

 

 

 

 

 

2단계. 전처리 함수 정의 (Tokenize, Stop Words, Stemming + alpha)

  • Token : 단어 레벨 (∵LDA의 결과 분석에 단어 해석이 사용됨)
  • Tokenize : 띄어 쓰기 단위
  • Stop Words 제거 : The, a, an 과 같은 단어들 제거
  • Stemming : PorterStemmer 사용
  • 정규화 : 소문자화
  • 비 단어적 요소 제거

 

import re
import nltk
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer

nltk.download('stopwords')
stop_words = set(stopwords.words('english'))
stemmer = PorterStemmer()

def preprocessing(text) :
    # 소문자 변환
    text = text.lower()
    # 비 단어적 요소 제거
    text = re.sub(r'\W', ' ', text)
    # Tokeinize (띄어쓰기 단위로)
    text = text.split()
    # Stop words 제거
    text = [t for t in text if t not in stop_words]
    # 어간 추출
    text = [stemmer.stem(word) for word in text]
    return text

 

 

전처리 결과 확인해보기

for d in data[:5] :
    print(f'원래 문장 : {d}')
    print(f'전처리 후 문장 : {preprocessing(d)}')
    print()

 

preproc_data = [preprocessing(d) for d in data]
preproc_data[:5]

 

 

 

 

 

 

 

 

3단계. Document-Term Matrix 생성

📌 문서-단어 행렬(Document-Term Matrix, DTM)
: 전처리된 데이터에서 각 단어의 빈도를 나타내는 행렬

- 행의 방향 : 문서
- 열의 방향 : 단어

1) 어떤 열 번호(index)에 어떤 단어(term)가 들어갈지 정한다.
► Gensim 패키지의 Dictionary Class 이용
2) 만들어진 단어(term)-번호(index) 객체를 활용 DTM 생성
► 출력 결과의 형태 : (단어 ID, 빈도수)

 

 

from gensim import corpora

# Gensim의 Dictionary 객체를 생성
dictionary = corpora.Dictionary(preproc_data)
list(dictionary.token2id.items())[:10]

 

# 문서-단어 행렬을 생성
corpus = [dictionary.doc2bow(text) for text in preproc_data]

# 첫 번째 문장의 DTM
corpus[0]

 

# 이는 0번째 단어가 1번, 1번째 단어가 1번, 2번째 단어가 1번 나옴을 의미
# 각 단어를 확인하기 위해서는 아래의 과정을 통해 알 수 있음

print(f'0 번째 단어 : {dictionary[0]}')
print(f'1 번째 단어 : {dictionary[1]}')
print(f'2 번째 단어 : {dictionary[2]}')

 

 

 

 

 

4단계. LDA 적용

from gensim.models import ldamodel

topicK = 3
num_trains = 10

lda_model = ldamodel.LdaModel(corpus,
                              num_topics=topicK, # 선정한 토픽 수
                              id2word=dictionary, # 몇 번째 단어가 어떤건지를 지정
                              passes=num_trains, # 학습 횟수
                              random_state=42)

 

 

토픽을 구성하는 단어의 분포를 확인해보자. `show_topic()` 함수를 사용하면 된다.

# 토픽 별 단어 분포 확인
for k in range(topicK):
    print(lda_model.show_topic(k, topn=20))

두번째 숫자 : 해당 토픽을 구성하는데 단어가 얼마나 사용되었는지에 대한 수치적인 정보

ex. 0번째(각 행) 토픽을 구성하는데 'service'라는 단어가 0.017% 정도 쓰였다.

 

 

다음으로 문서를 구성하는 토픽의 분포를 확인해보자.

# 문서 별 토픽 분포 확인
for document in corpus[:5]:
    origin_doc = [dictionary[word_idx] for word_idx, word_num in document]
    print(f'{origin_doc}에 속한 토픽의 분포는 아래와 같습니다.')
    for topic_idx, topic_dist in lda_model[document]:
        print(f'{topic_idx} 번째 토픽 : {topic_dist*100:.2f}% 확률')

 

 

 

# 토픽 별 단어 분포 확인
for k in range(topicK):
    print(f'{k}번째 토픽을 구성하는 단어의 분포는...')
    for word, prob in lda_model.show_topic(k, topn=5) :
        print(f'{word} : {prob*100:.2f}%', end= ' ')
    print()
    print()

 

 

 

 

 

 

 

 

5단계. LDA 결과 해석

  • LDA의 결과로 생성된 토픽의 의미는 사용자가 선정해야 함
  • 토픽을 구성하는 단어를 보고 토픽의 의미를 선정
  • 이 과정은 데이터 친숙도, 도메인 전문성이 큰 힘을 발휘함

 

 

# 0번째 토픽을 구성하는 상위 10개 단어
for word, prob in lda_model.show_topic(0, topn=10):
    print(f'{word}, {prob*100:.2f}%')

► 식당 서비스 품질과 고객 경험

 

# 1번째 토픽을 구성하는 상위 10개 단어
for word, prob in lda_model.show_topic(1, topn=10):
    print(f'{word}, {prob*100:.2f}%')

음식 품질과 재방문에 관련된 의사표현

 

# 2번째 토픽을 구성하는 상위 10개 단어
for word, prob in lda_model.show_topic(2, topn=10):
    print(f'{word}, {prob*100:.2f}%')

 식당 전반의 분위기와 음식의 퀄리티에 대한 내용

 

 

# 원문장과 토픽 분포를 보고 해석하기

target_idx = 10

print('원 문장 : ', data[target_idx])
print('전처리 문장 : ', preproc_data[target_idx])
print('문장 내 토픽 분포 : ')
for topic_idx, prob in lda_model[corpus[target_idx]]:
    print(f'  - {topic_idx}번 토픽 : {prob*100:.2f}%')

 

[결론]

모델의 추론 결과 0번 토필의 비율이 가장 컷다.

식당의 서비스와 고객 경험을 이야기하는 0번 토픽과 문서가 제일 가깝다는 것을 알 수 있다.

 실제 문장 의미와 분석 결과가 일맥상통함을 확인했다.

 

 

 

 

 

 

 

 

 


워드 클라우드 (Word Cloud)

: 텍스트 데이터를 시각적으로 표현한 것으로, 문서 or 데이터에서 자주 등장하는 단어들을 시각적으로 두드러지게 표현하는 방법

 

  • 데이터 해석을 위한 직관적인 접근 가능
  • 활용 분야 : 
    • 소셜 미디어 : 트렌드, 핫 토픽
    • 시장 조사 분석 : 소비자 리뷰, 피드백
    • 교육 : 학습 자료 요약, 시각화

 

 

 

 

 

 

워드 클라우드 실습

 

사용 데이터

🔗 실습 링크 : https://www.kaggle.com/datasets/marklvl/sentiment-labelled-sentences-data-set

 

Sentiment Labelled Sentences Data Set

From Group to Individual Labels using Deep Features, Kotzias et. al,. KDD 2015

www.kaggle.com

 

 

 

 

 

문제 정의

Yelp 데이터 전체를 활용한 워드 클라우드 생성

 

 

 

 

1단계. 데이터 로드

# txt 파일을 pandas의 형태로 변환하지 않고
# 직접적으로 load
# 과정에서 0 혹은 1 부분은 제거

file_path = 'yelp_labelled.txt'

with open(file_path, 'r', encoding='utf-8') as file :
    data = [line.split('\t')[0] for line in file]

 

 

 

2단계. 데이터 전처리

※ 일반적으로 특수문자와 숫자는 제거

※ 보고자 하는 목적에 맞게 데이터를 전처리 해야 한다.

ex) 뉴스, 소셜 미디어, 학술 논문 → '명사' 활용

ex) 리뷰, 고객 피드백 → '형용사' 혹은 '동사' 활용

import re
import nltk
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer

nltk.download('stopwords')
stop_words = set(stopwords.words('english'))
stemmer = PorterStemmer()

def preprocessing(text) :
    # 소문자 변환
    text = text.lower()
    # 비 단어적 요소 제거
    text = re.sub(r'\W', ' ', text)
    # Tokeinize (띄어쓰기 단위로)
    text = text.split()
    # Stop words 제거
    text = [t for t in text if t not in stop_words]
    # 어간 추출
    text = [stemmer.stem(word) for word in text]
    return text

 

 

# 모든 토큰을 하나의 리스트에 넣어줌
preproc_data = [' '.join(preprocessing(d)) for d in data]
combined_text = ' '.join(preproc_data)

 

 

 

 

3단계. 워드 클라우드 생성

1) 워드 클라우드 객체 만들기

from wordcloud import WordCloud

wordcloud = WordCloud(width=800, height=800, # 워드 클라우드 이미지 크기
                      background_color='white', # 배경색
                      min_font_size=10, # 가장 작은 폰트 사이즈
                      max_font_size=200).generate(combined_text) # 가장 큰 폰트 사이즈

 

 

 

2) 텍스트 넣어서 시각화

import matplotlib.pyplot as plt

plt.figure(figsize=(8, 8), facecolor=None)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.tight_layout(pad=0)
plt.show()