[TIL] 데이터분석 데브코스 63일차 (1) - 토픽 모델링(Topic Modeling)/LDA/워드 클라우드(Word Cloud)
토픽 모델링(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. 최종 결과 분석
- 토픽에 존재하는 단어를 보고 토픽이 의미하는 주제를 사용자가 정의 (ex. 토픽 1은 주제가 '먹거리' 구나)
- 할당된 토픽을 기준으로 문서에 존재하는 토픽을 분석 (ex. 문서 1은 토픽이 2, 6, 8이 있구나)
토픽 모델링 실습
사용 데이터
🔗 실습 링크 : https://www.kaggle.com/datasets/marklvl/sentiment-labelled-sentences-data-set
※ 감성 분석이 목적이 아니기 때문에 감정 상태를 나타내는 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
문제 정의
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()