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] 데이터 전처리
먼저, 리뷰를 분류하는데 도움이 되거나, 머신러닝 처리에 어려운 단어들을 제거해봅시다.
- 텍스트 데이터 전처리에 있어 정규 표현식은 유용한 도구입니다. 아래 조건에 맞는 정규표현식을 작성하여 영어와 한글 문자를 제외한 특수문자나 이모지, 숫자 등을 제거해봅시다.
- 한글 문자(초성 제외), 영어 대문자, 영어 소문자, 띄어쓰기 이외의 문자를 제외하는 정규표현식 작성
- 영어 단어의 경우 같은 단어들이 같은 토큰으로 분류될 수 있도록 대문자로 통일해줍니다.
##########################################################################################
# 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)
- 한국어 자연어 처리 라이브러리인 konlpy의 OKT 래퍼를 통하여 문장을 단어로 토큰화해봅시다.
- 아래 공식 문서를 참고하여 토큰화를 수행합니다.
- 이때, OKT 클래스의 특정 매개변수를 이용해 어근화를 진행해줍니다. (document 참고)
- konlpy document: https://konlpy.org/ko/latest/api/konlpy.tag/#okt-class
- OKT document: https://github.com/open-korean-text/open-korean-text
##########################################################################################
# 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)
'Computer Science > Machine Learning' 카테고리의 다른 글
Feature Extract : Speech [음악 장르 분류] (4) (0) | 2024.06.10 |
---|---|
Feature Extract : Speech [음악 장르 분류] (3) (1) | 2024.06.10 |
Feature Extract : NLP [한국어 텍스트 데이터를 활용한 영화 리뷰 분류] (1) (2) | 2024.06.10 |
차원 축소(Dimension Reduction) : 기타 (4) (0) | 2024.06.09 |
차원 축소(Dimension Reduction) : PCA (3) (0) | 2024.06.09 |