한 문장을 벡터로 변환하는 가장 기초적인 방법임.
단어의 순서는 무시하고, 존재 여부나 등장 횟수만 세서 벡터에 기록.
특징
단점
실무에서는 TF-IDF, Word2Vec, BERT 등 발전된 방법이 많이 쓰임.
벡터화란 텍스트 데이터를 수치 벡터로 바꾸는 작업임.
머신러닝 모델은 문자(텍스트)를 이해하지 못함 → 반드시 숫자 형태로 변환해야 함.
벡터화는 이 과정을 자동으로 해주는 핵심 절차.
가장 기초적인 벡터화 기법.
문서에서 단어의 순서 무시
단어가 몇 번 등장했는지 빈도(count)만 기록함.
각 단어는 벡터의 하나의 차원에 매핑됨.
예: 단어 사전이 ['밥', '학교', '영화']라면, 문장 "밥 먹고 학교 감"은 [1,1,0] 로 표시함.
import numpy as np
import pandas as pd
import re
import json
from konlpy.tag import Okt
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix, precision_recall_curve, roc_curve, auc
from collections import Counter
Okt: 형태소 분석기from collections import Counter: 단어 카운트okt = Okt()
DATA_PATH = "C:/Users/user/LG/텍스트분석/0318/" #current path
print(os.listdir(DATA_PATH))
out:
['0318.ipynb', 'ratings_test.txt', 'ratings_train.txt']
okt = Okt() : 형태소 분석기 인스턴스 생성함. 한국어 문장에서 단어 분리 및 어간 추출에 사용함.DATA_PATH : 데이터 경로 변수 선언. 추후 파일 불러올 때 사용.print(os.listdir(DATA_PATH)) : 경로 안에 어떤 파일 있는지 확인함. 준비 상태 점검 목적.def load_data(file_path):
data = pd.read_csv(file_path, sep='\t') # TSV 파일 불러오기
data = data.dropna() # 결측치 제거
def preprocess(text):
text = str(text).lower() # 대소문자 혼용 방지 위해 소문자로 변환
text = re.sub(r'[^가-힣a-zA-Z0-9\s]', '', text) # 특수문자 제거 (한글, 영문, 숫자, 공백만 남김)
text = re.sub(r'\d+', '', text) # 숫자 제거 (감성 분석에는 의미 없음)
text = re.sub(r'\s+', ' ', text).strip() # 여러 공백 → 하나의 공백 처리 후 양 끝 공백 제거
tokens = okt.morphs(text, stem=True) # 형태소 분석 및 어간 추출
stopwords = [
'은', '는', '이', '가', '을', '를', '과', '도', '에', '의',
'한', '하다', '되다', '것', '있다', '없다', '그', '수', '이다', '그것'
] # 학습에 방해되는 불용어 리스트 정의
tokens = [word for word in tokens if word not in stopwords and len(word) > 1] # 불용어와 한 글자 제거
return tokens
data['tokens'] = data['document'].apply(preprocess) # 각 리뷰에 대해 전처리 적용
return data
train_data = load_data(DATA_PATH+'ratings_train.txt')
test_data = load_data(DATA_PATH+'ratings_test.txt')
train_data['doc_length'] = train_data['tokens'].apply(len)
plt.figure(figsize=(8,5))
sns.histplot(train_data['doc_length'], bins=50 ,kde=True)
plt.xlabel('length of review (the number of words)')
plt.ylabel('the number of review')
plt.title('distribution of length of review')
plt.show()

all_tokens = [token for tokens in train_data['tokens'] for token in tokens]
word_counts = Counter(all_tokens)
word_to_index = {word: i for i, (word, _) in enumerate(word_counts.most_common(20000))}
train_data['tokens']: 각 리뷰의 토큰 리스트.
for tokens in train_data['tokens']: 각 리뷰마다 반복.
for token in tokens: 해당 리뷰의 각 토큰 반복.
결과: 전체 리뷰에 등장한 모든 토큰(flattened list).
Counter(all_tokens): 각 단어의 등장 횟수를 세고, 가장 많이 등장한 순서대로 저장.
word_counts.most_common(20000): 가장 많이 등장한 20,000개 단어를 반환.
(word, _): 튜플 형태에서 단어만 가져옴.
enumerate(): 순서 번호(i) 부여.
word_to_index: 단어-인덱스 매핑 딕셔너리 생성.
이렇게 만들어야 벡터화할 때 단어를 해당 인덱스에 카운트 누적 가능.
def token_to_vector(tokens):
vector = np.zeros(len(word_to_index)) # all words initialize zero
#purpose: save the words frequencies, need to zero to change counting
for token in tokens: #repeat tokens list and confirm.
if token in word_to_index: # confirm the token is in word_to index
vector[word_to_index[token]] +=1 # increase one to find the index
return vector
X_train = np.array([token_to_vector(tokens) for tokens in train_data['tokens']])
X_test = np.array([token_to_vector(tokens) for tokens in test_data['tokens']])
y_train = train_data['label']
y_test = test_data['label']
train_data['tokens']: 학습 데이터셋의 각 리뷰가 토큰 리스트 형태로 저장되어 있음.for tokens in train_data['tokens']: 각 리뷰마다 반복.token_to_vector(tokens): 해당 리뷰의 토큰 리스트를 BoW 벡터로 변환.np.array(...) : 리스트를 넘파이 배열로 변환 (모델 학습 입력으로 사용하기 위함).word_to_index 사전 사용.y_train, y_test에 저장.model = LogisticRegression(max_iter = 1000, C =1.0 )
model.fit(X_train,y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test,y_pred)
print('Accuarcy', accuracy)
print(classification_report(y_test,y_pred))
out:
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test,y_pred)
print('Accuarcy', accuracy)
print(classification_report(y_test,y_pred))
conf_matrix = confusion_matrix(y_test, y_pred)
sns.heatmap(conf_matrix, annot=True, fmt = 'd', cmap='Blues', xticklabels=['Negative', 'Positive'], yticklabels=['Negative','Positive'])
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Confusion matrix')
plt.show()

precision, recall,_ = precision_recall_curve(y_test,model.predict_proba(X_test)[:,1])
plt.figure(figsize=(8,5))
plt.plot(recall, precision, marker='.', label='Precision-Recall Curve')
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.legend()
plt.show()

fpr, tpr,_ = roc_curve(y_test,model.predict_proba(X_test)[:,1])
roc_auc = auc(fpr,tpr)
plt.figure(figsize=(8,5))
plt.plot(fpr, tpr, label=f'Roc Curve(AUC = {roc_auc:.2f})')
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.legend()
plt.show()

missclassified = test_data.iloc[y_test[y_test != y_pred].index]
print('five sample that the model fail to classify')
missclassified
print(missclassified[['document', 'label']].head())
out:
five sample that the model fail to classify
document label
0 굳 ㅋ 1
5 음악이 주가 된, 최고의 음악영화 1
10 괜찮네요오랜만포켓몬스터잼밌어요 1
12 청춘은 아름답다 그 아름다움은 이성을 흔들어 놓는다. 찰나의 아름다움을 잘 포착한 ... 1
13 눈에 보이는 반전이었지만 영화의 흡인력은 사라지지 않았다. 1
important_words = [word for word,_ in word_counts.most_common(10)]
print('the most using word 10', important_words)
#preprocessing(delete uneccessary things) and analyze
def predict_sentiment(review):
review = review.lower()
review = re.sub(r'[가-힣a-zA-Z0-9\s]','',review) #delete all thing except Korean and English
review = re.sub(r'\d+', '',review) # delete num
review = re.sub(r'\s+', ' ', review).strip() #delete long blank
tokens = okt.morphs(review, stem = True) #analyze morpheme and extract stem
stopwords = ['은', '는', '이', '가', '을', '를', '과', '도', '에', '의', '한', '하다', '되다', '것', '있다', '없다', '그', '수', '이다', '그것']
tokens = [word for word in tokens if word not in stopwords and len(word)>1] #del bools and length of word is 1
vector = token_to_vector(tokens)
pred = model.predict([vector])
return "Positive" if pred[0] == 1 else"Negative"
print(predict_sentiment('이 영화 너무 재밌어요! 다시 보고 싶어요.'))
print(predict_sentiment('이딴게 영화?'))
out:
Positive
Negative