이 글은 한빛미디어의 '파이토치로 배우는 자연어처리' 글을 읽고 정리한 것입니다.
퍼셉트론과 지도 학습 훈련 방법을 사용해 옐프(Yelp) 의 레스토랑 리뷰가 긍정적인지 부정적인지 분류하는 작업을 해보자. 데이터를 로드하고 전처리하는 전체 코드는 여기서 확인할 수 있다.
Dataset: 옐프 리뷰 데이터셋
이 실습에서는 2015년에 옐프가 주최한 레스토랑 등급 예측 대회에서 사용되었던 레스토랑 리뷰 데이터를 간소화한 뒤 훈련 샘플과 테스트 샘플로 분류해 둔 데이터셋 중 10%를 사용한다.
전체 데이터셋 중 일부만 사용하는 이유는 크게 2가지가 있다.
- 작은 데이터셋을 사용하면 훈련-테스트 반복이 빠르게 이루어져 실험 속도를 높일 수 있다.
- 전체 데이터를 사용할 때보다 모델의 정확도가 낮다. 낮은 정확도는 일반적으로 크게 문제가 되지 않으며, 작은 데이터셋에서 얻은 지식으로 전테 데이터셋에서 모델을 다시 훈련할 수 있기 때문에, 훈련 데이터 양이 아주 많을 때 딥러닝 모델을 훈련할 수 있는 유용한 방법이다.
Preprocessing: 데이터 가져오고 다듬기
데이터 가져오기
우선 데이터 전처리에 필요한 라이브러리를 임포트하자. 천리길도 임포트부터 🧗🏻♀️
import numpy as np
import pandas as pd
import regex as re
import collections
from argparse import Namespace
전처리 과정에서 사용할 변수들도 세팅하자.
args = Namespace(
raw_train_dataset_csv="/content/drive/MyDrive/NLP-Pytorch/data/yelp/raw_train.csv",
raw_test_dataset_csv="/content/drive/MyDrive/NLP-Pytorch/data/yelp/raw_test.csv",
proportion_subset_of_train=0.1,
train_proportion=0.7,
val_proportion=0.15,
test_proportion=0.15,
output_munged_csv="/content/drive/MyDrive/NLP-Pytorch/data/yelp/reviews_with_splits_lite.csv",
seed=1337
)
이제 본격적으로 데이터를 다뤄보자. 먼저, 원본 데이터를 가져온 다음 head()
로 데이터의 생김새를 간단하게 파악하자.
train_reviews = pd.read_csv(args.raw_train_dataset_csv, header=None, names=['rating', 'review'])
train_reviews.head()
rating | review | |
---|---|---|
0 | 1 | Unfortunately, the frustration of being Dr.Go... |
1 | 2 | Been going to Dr. Goldberg for over 10 years. ... |
2 | 1 | I don't know what Dr. Goldberg was like before... |
3 | 1 | I'm writing this review to give you a heads up... |
4 | 2 | All the foods is great here. But the best thing... |
데이터 분리하기
우리는 전체 데이터셋 중 10%에 해당하는 서브셋만 사용할 것이다. 하지만 이 서브셋에 특정 rating 값을 가진 데이터만 편중되게 포함된다면 학습이 제대로 진행되지 않을 것이다. 그래서 서브셋 내의 각 rating 클래스 비율이 동일하도록, 표본집단이 모집단의 성질을 반영하도록 서브셋을 구성해야 한다.
by_rating = collections.defaultdict(list)
for _, row in train_reviews.iterrows():
by_rating[row.rating].append(row.to_dict())
review_subset = []
for _, item_list in sorted(by_rating.items()):
n_total = len(item_list) # 전체 데이터셋 중 해당 rating value를 갖는 데이터의 개수
n_subset = int(args.proportion_subset_of_train * n_total)
review_subset.extend(item_list[:n_subset]) # 전체 데이터셋 중 n_total * 0.1만큼만 슬라이싱하여 사용
review_subset = pd.DataFrame(review_subset)
review_subset.head()
rating | review | |
---|---|---|
0 | 1 | Unfortunately, the frustration of being Dr.Go... |
1 | 1 | I don't know what Dr. Goldberg was like before... |
2 | 1 | I'm writing this review to give you a heads up... |
3 | 1 | Wing sauce is like water. Pretty much a lot of... |
4 | 1 | Owning a driving range inside the city limits ... |
여기서 데이터셋의 각 rating 클래스의 개수를 세어보면 서로 동일한 개수의 데이터를 갖는다.
train_reviews.rating.value_counts() # 2 280000 / 1 280000
set(review_subset.rating) # {1, 2}
이렇게 처리된 데이터셋을 훈련, 검증, 테스트 세트로 분리해야 한다. 미리 정해둔 각 세트의 비율에 따라 데이터를 나눈다. 각 세트의 비율은 이 글의 2번째 코드블럭에서 선언한 args
Namespace에 정의되어 있으며, args.train_proportion
= 0.75, args.val_proportion
= 0.15, args.test_proportion
= 0.15다. 3개의 세트로 분리한 뒤에는 split
속성에 이 데이터가 어느 세트에 속한 데이터인지 표시한다.
# 별점 기준으로 나누어 훈련, 검증, 테스트 데이터셋을 만든다.
by_rating = collections.defaultdict(list)
for _, row in review_subset.iterrows():
by_rating[row.rating].append(row.to_dict())
# 분할 데이터를 만든다.
final_list = []
np.random.seed(args.seed)
for _, item_list in sorted(by_rating.items()):
np.random.shuffle(item_list)
n_total = len(item_list)
n_train = int(args.train_proportion * n_total)
n_val = int(args.val_proportion * n_total)
n_test = int(args.test_proportion * n_total)
# 각 데이터에 레이블을 붙여준다.
for item in item_list[:n_train]:
item['split'] = 'train'
for item in item_list[n_train:n_train+n_val]:
item['split'] = 'val'
for item in item_list[n_train+n_val:n_train+n_val+n_test]:
item['split'] = 'test'
final_list.extend(item_list)
final_reviews = pd.DataFrame(final_list)
데이터 정제하기
이후, 데이터를 정제하는 작업이 필요하다. 우리가 구현할 클래스는 리뷰 데이터를 공백을 기준으로 나누어 토큰을 획득하므로, 구두점 기호 앞뒤에 공백을 넣고, 구두점이 아닌 다른 기호들은 제거한다.
def preprocess_text(text):
text = text.lower()
text = re.sub(r"([.,!?])", r" \1 ", text)
text = re.sub(r"[^a-zA-Z.,!?]+", r" ", text)
return text
final_reviews.review = final_reviews.review.apply(preprocess_text)
다음으로, 숫자(1, 2)로 표시된 rating
열의 값들을 각각 'negative'와 'positive'로 바꿔준다.
final_reviews['rating'] = final_reviews.rating.apply({1: 'negative', 2: 'positive'}.get)
잘 바뀌었는지 final_reviews.head()
로 확인해 보자.
rating | review | split | |
---|---|---|---|
0 | negative | terrible place to work for i just heard a stor... | train |
1 | negative | hours , minutes total time for an extremely s... | train |
2 | negative | my less then stellar review is for service . w... | train |
3 | negative | i m granting one star because there s no way t... | train |
4 | negative | the food here is mediocre at best . i went aft... | train |
잘 처리되었다! 이대로 csv로 저장하자 😚
final_reviews.to_csv(args.output_munged_csv, index=False)
다음 글에서는 전처리된 데이터셋을 가지고 본격적으로 분류 모델을 만들어볼 것이다.
'Machine Learning > NLP' 카테고리의 다른 글
[NLP] Transformer 알아보기 - (1) Encoder (0) | 2022.01.11 |
---|---|
Stanford cs224n (Winter 2019) | Lecture 2: Word Vectors and Word Senses (0) | 2021.12.09 |
Stanford CS224N (Winter 2019) | Lecture 1: Introduction and Word Vectors (2) | 2021.12.01 |
[파이토치로 배우는 자연어처리] 레스토랑 리뷰 감성 분류하기 - (3) 감성 분류 모델 만들기 (2) | 2021.10.28 |
[파이토치로 배우는 자연어처리] 레스토랑 리뷰 감성 분류하기 - (2) 문장 토큰화와 Dataset (0) | 2021.10.25 |