본문 바로가기
Computer Science/Machine Learning

Feature Extract : NLP [한국어 텍스트 데이터를 활용한 영화 리뷰 분류] (2)

by BaekDaBang 2024. 6. 10.
preds = clf.predict(x_test_tfidf)​
##########################################################################################
# Empty Module #6
# 입력: BoW 형태로 변환된 (N, M) 크기의 데이터
# 출력: 등장 빈도가 적은 단어들을 제외한 (N, m) 크기의 더 작은 데이터
# 힌트
# 1. 먼저 전체 데이터에서 각 단어가 등장한 횟수를 세어보세요.
# 2. 그 다음, 등장 횟수가 50회 미만인 단어들을 찾습니다.
# 3. 해당 단어들을 데이터에서 제거하는 코드를 작성합니다.
# 4. 설계를 잘 하고 구현을 시작해야 어렵지 않습니다.
##########################################################################################
# 코드 작성
total_BoW = np.sum(x_train_BoW, axis=0) # 전체 데이터에서 각 단어가 등장한 횟수
indices = np.where(total_BoW >= 50)[0] # 등장횟수 50회 미만 단어를 제거해서 indice에 담아줍니다.
x_train_BoW_reduced = x_train_BoW[:, indices]
print("원본 BoW 크기:", x_train_BoW.shape)
print("차원 축소 후 크기:", x_train_BoW_reduced.shape)

1. 데이터 불러오기

# train data 확인
train_data.head()

x_train = train_data["review"]
y_train = np.array(train_data["rating"])

 

2. 자연어 전처리

[Empty Module #1] 데이터 전처리

먼저, 리뷰를 분류하는데 도움이 되거나, 머신러닝 처리에 어려운 단어들을 제거해봅시다.

  1. 텍스트 데이터 전처리에 있어 정규 표현식은 유용한 도구입니다. 아래 조건에 맞는 정규표현식을 작성하여 영어와 한글 문자를 제외한 특수문자나 이모지, 숫자 등을 제거해봅시다.
    • 한글 문자(초성 제외), 영어 대문자, 영어 소문자, 띄어쓰기 이외의 문자를 제외하는 정규표현식 작성
  2. 영어 단어의 경우 같은 단어들이 같은 토큰으로 분류될 수 있도록 대문자로 통일해줍니다.
##########################################################################################
# Empty Module #1
# 입력: 자연어 상태의 리뷰 텍스트
# 출력: 한글(초성 제외), 영어 대문자, 띄어쓰기로만 구성된 텍스트 
# 입력 예시: "안녕 Hello!!!:)ㅎㅎ반갑다."
# 출력 예시: "안녕 HELLO반갑다"
##########################################################################################
import re

pattern = r"[^가-힣A-Z\s]"  # 한글(초성 제외), 영어 대문자, 띄어쓰기가 아닌 문자에 대한 정규표현식 패턴

def apply_regex(pattern, text): 
    text = re.sub(r"[a-z]",lambda x:group().upper(),test)  # 영어 소문자를 대문자로 치환
    text = re.sub(pattern, "", text)  # 정규표현식 패턴에 맞지 않는 값들을 텍스트에서 제거
    return text

x_train_preprocessed = [apply_regex(pattern, str(x[1])) for x in tqdm(x_train.items(), total=len(x_train), desc="pre-processing data")]

 

[Empty Module #2] 단어 토큰화

Open Korean Text(OKT)를 이용한 단어 토큰화(Tokenization)

##########################################################################################
# Empty Module #2
# 입력: 자연어 상태의 리뷰 데이터
# 출력: 토큰화와 과정을 거쳐 단어들의 리스트로 변환된 데이터
# 입력 예시: "커피는 역시 학생회관 커피"
# 출력 예시: ["커피", "는", "역시", "학생", "회관", "커피"]
##########################################################################################
from konlpy.tag import Okt
okt = Okt()

def tokenize_words(sentence):
    sentence_tokenized = okt.morphs(sentence, stem=True)
    return sentence_tokenized
# 약 10-15분 정도 소요됩니다. 
x_train_tokenized = [tokenize_words(x) for x in tqdm(x_train_preprocessed, desc="tokenizing data")]

 

[Empty Module #3] 불용어 제거

  • 조사를 비롯한 불용어들은 많은 횟수 등장하지만, 리뷰의 긍정과 부정 여부를 판단하는데는 도움이 되지 않습니다.
  • 데이터에서 아래 리스트로 정의된 불용어들을 제거해줍니다.
#별다른 의미가 없는 불용어들
stopwords = ['의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']

def exclude_stopwords(text):
    result = [] # text에서 stopword가 아닌 것들을 담는 리스트
    for word in text:
        # 위 stopwords 리스트에 포함된 불용어들을 제거하는 코드 작성
        if word not in stopwords:  # stopwords 리스트에 포함된 불용어들을 제거
            result.append(word)
            
    return result

x_train_stopwords_excluded = [exclude_stopwords(x) for x in x_train_tokenized]

 

[Empty Module #4] 단어 임베딩

단어 임베딩 코드 구현

  • 토큰화를 거쳐 분리된 단어들을 하나의 정수 값으로 매핑해주는 희소 표현법을 직접 구현해봅시다.
  • 입력된 단어가 새로운 단어라면 새로운 정수 값을 할당하고, 이전에 등장한 단어라면 이전에 할당한 정수를 할당하는 함수를 작성합니다.
    • 단, 테스트 데이터에 대해서는 새로운 단어가 등장하면 값을 할당하지 않습니다.
##########################################################################################
# Empty Module #4
# 입력: 단어 토큰화된 데이터
# 출력: 임베딩 과정을 거쳐, 각 단어가 하나의 실수 값으로 표현된 데이터
# 입력 예시: ["커피", "역시", "학생", "회관", "커피"]
# 출력 예시: [0, 1, 2, 3, 0]
##########################################################################################

embedding_dict = dict()  # 단어 임베딩을 위한 딕셔너리
embedding_value = 0

def embed_tokens(sentence_tokenized, mode):
    assert mode.upper() in ["TRAIN", "TEST"]
    global embedding_value
    
    sentence_embedded = list()
    for word in sentence_tokenized: 
        # 코드 작성
        if mode.upper() == "TRAIN":
            if word not in embedding_dict:
                embedding_dict[word] = embedding_value
                embedding_value += 1
            sentence_embedded.append(embedding_dict[word])
        elif mode.upper() == "TEST":
            if word in embedding_dict:
                sentence_embedded.append(embedding_dict[word])
            else:
                sentence_embedded.append(-1)  # 새로운 단어는 -1로 표시
    
    return sentence_embedded
# 실행 시간이 상당히 소요될 수 있습니다. 
x_train_embedded = [embed_tokens(x, mode="TRAIN") for x in tqdm(x_train_stopwords_excluded, desc="embedding data")]
print(x_train_embedded[:5])
print("총 %d개의 단어가 임베딩되었습니다."%(embedding_value))

 

[Empty Module #5] 문장 벡터화

Bag of Words 방법을 사용한 문장 벡터화

  • 캐글 프로젝트 설명 페이지의 Bag of Words 방법 설명을 참고하여 Bag of Words 방법을 직접 구현해봅시다.
##########################################################################################
# Empty Module #5
# 입력: 임베딩 과정을 거친 데이터
# 출력: BoW 형태로 변환되어, M차원의 고정된 크기를 가진 벡터로 변환된 데이터
# 힌트: np.zeros((2, 3))는 [2, 3] 크기의 0으로 가득 찬 행렬을 생성합니다.
##########################################################################################
M = len(embedding_dict) # 전체 단어의 수

def to_BoW_representation(x):
    shape = (len(x), M) # BoW는 어떤 shape를 가져야 할지 고민해봅시다.
    x_BoW = np.zeros(shape)
    for i in tqdm(range(len(x)), desc="making BoW representation"):
        # 여기에 BoW 구현
        for word_index in x[i]:
            if word_index != -1:
                x_BoW[i, word_index] += 1
    return x_BoW

x_train_BoW = to_BoW_representation(x_train_embedded)

 

[Empty Module #6] 차원 축소

  • 우리가 만든 BoW는 수많은 리뷰에 등장하는 모든 단어들을 사용하여 만들어졌기 때문에, 엄청난 양의 단어들을 가지고 있습니다.
  • 그러나, 실제로 영화 리뷰에 쓰이는 단어들은 이보다 적기 때문에, 많은 단어들이 전체 데이터에서 실제로는 한번도 등장하지 않거나, 매우 조금 등장하면서 공간을 차지하고 있을 것 입니다.
  • 데이터의 크기를 줄여 머신러닝 모델이 중요한 정보에 집중할 수 있도록 해봅시다.
  • 학습 데이터에서 50번 미만으로 등장한 단어들을 제외해줍니다.
##########################################################################################
# Empty Module #6
# 입력: BoW 형태로 변환된 (N, M) 크기의 데이터
# 출력: 등장 빈도가 적은 단어들을 제외한 (N, m) 크기의 더 작은 데이터
##########################################################################################

 

[Empty Module #7] 분류 수행 및 제출: Bag of Words

  • 이제 모든 문장이 고정된 크기 𝑚 차원의 벡터로 변환되었습니다.
  • 로지스틱 회귀 모델을 사용하여, 각 문장의 영화에 대한 긍정적인 리뷰인지, 부정적인 리뷰인지 분류해봅시다.
  • 그 다음, 결과를 기록하여 Kaggle에 제출해봅시다.
# TEST 데이터를 전처리
x_test = test_data["review"]
x_test_preprocessed = [apply_regex(pattern, str(x[1])) for x in tqdm(x_test.items(), total=len(x_test), desc="pre-processing data")]
x_test_tokenized = [tokenize_words(x) for x in tqdm(x_test_preprocessed, desc="tokenizing data")]
x_test_stopwords_excluded = [exclude_stopwords(x) for x in x_test_tokenized]
x_test_embedded = [embed_tokens(x, mode="TEST") for x in tqdm(x_test_stopwords_excluded, desc="embedding data")]
x_test_BoW = to_BoW_representation(x_test_embedded)
x_test_BoW_reduced = x_test_BoW[:, indices]
##########################################################################################
# Empty Module #7
# 지금까지 전처리한 데이터로 분류를 수행하여, kaggle에 제출해봅시다.
# Baseline은 로지스틱 회귀입니다.
##########################################################################################
# 분류기 정의 및 학습 수행 코드 작성
from sklearn.linear_model import LogisticRegression

clf = LogisticRegression(max_iter=1000)
clf.fit(x_train_BoW_reduced, y_train)
preds = clf.predict(x_test_BoW_reduced)

 

[Empty Module #8] TF-IDF 적용

  • BoW에서는 고려하지 않는 각 단어들의 중요도를 고려하기 위해, TF-IDF를 적용해봅시다.
  • 캐글 프로젝트 설명 페이지의 설명을 참고하여 빈칸을 채워, TF-IDF를 구현해봅시다.
##########################################################################################
# Empty Module #8
# 빈칸을 적절히 채워넣어 TF-IDF를 위한 Inverse Document Frequency를 계산해봅시다.
##########################################################################################
N = len(x_train_BoW)  # 총 데이터 샘플의 수

def calculate_document_frequency(x):
    bool_arr = (x > 0).astype(int) # 어떤 단어가 등장하는 데이터 샘플(문서)의 수(DF)를 계산하는 코드 작성
    return bool_arr.sum(axis=0)

def calculate_inverse_document_frequency(document_frequency):
    inverse_doc_freq = np.log((N / (document_frequency + 1)) + 1) # DF에 반비례하는 IDF를 계산하는 코드 작성
    return inverse_doc_freq

document_frequency = calculate_document_frequency(x_train_BoW) # 여기에 코드 작성
inverse_document_frequency = calculate_inverse_document_frequency(document_frequency) # 여기에 코드 작성
x_train_tfidf = x_train_BoW * inverse_document_frequency # 데이터에 위에서 구한 IDF를 곱하는 코드 작성

 

[Empty Module #9] 분류 수행 및 제출: TF-IDF

  • TF-IDF를 적용한 결과를 기록하여 Kaggle에 제출해봅시다.
# TEST 데이터를 전처리하는 코드 작성
document_frequency = calculate_document_frequency(x_test_BoW)
inverse_document_frequency = calculate_inverse_document_frequency(document_frequency)
x_test_tfidf = x_test_BoW * inverse_document_frequency
##########################################################################################
# Empty Module #9
# TEST 데이터에 TF-IDF를 적용하여 모델을 학습, 예측을 수행하고 kaggle에 제출해봅시다.
# 이때, BoW와 같은 모델을 사용하여 성능을 비교해봅시다.
##########################################################################################
# 분류기 정의 및 학습 수행 코드 작성
from sklearn.linear_model import LogisticRegression

clf = LogisticRegression(max_iter=1000)
clf.fit(x_train_tfidf, y_train)
preds = clf.predict(x_test_tfidf)