Загружаем каталог…
Загружаем каталог…
네이버 영화 리뷰 데이터에 대한 이해와 전처리 LSTM을 이용한 네이버 영화 리뷰 분류 모델 평가 코드 작성 학습 모델 로드 및 평가 모델 테스트 <딥러닝 파이토치 교과서 - 입문부터 LLM 파인튜닝까지> 교재 13-2장의 내용을 공부하고 정리한 포스트입니다. https://wikidocs.net/book/2788 - 교재 https://github.com/ukairia777/pytorch-nlp-tutorial - 깃허브 이번 주에 배운 걸 전부 합쳐보자. 4.3의 형태소 토큰화로 리뷰를 나누고, 정수 인코딩한 뒤 4.4의 nn.Embedding() 으로 벡터로 바꾸고, 4.2의 LSTM에 넣어 긍정/부정을 맞히는 모델을 만든다. 4.1에서 본 다 대 일 (many-to-one) 구조다. 데이터는 네이버 영화 리뷰 데이터다. 총 200,000개 리뷰로, 리뷰 텍스트와 긍정이면 1, 부정이면 0인 레이블로 구성돼 있다. 1. 네이버 영화 리뷰 데이터에 대한 이해와 전처리 데이터 다운로드 링크 : https://github.com/e9t/nsmc/ import pickle import pandas as pd import numpy as np import matplotlib.pyplot as plt import re import urllib.request from konlpy.tag import Mecab from tqdm import tqdm from sklearn.model_selection import train_test_split from collections import Counter 1) 데이터 로드하기 훈련 데이터 ratings_train.txt와 테스트 데이터 ratings_test.txt를 다운로드한다. urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt", filename="ratings_train.txt") urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt", filename="ratings_test.txt") pandas로 훈련 데이터는 train_data 에, 테스트 데이터는 test_data 에 저장한다. train_data = pd.read_table('ratings_train.txt') test_data = pd.read_table('ratings_test.txt') print('훈련용 리뷰 개수 :',len(train_data)) # 훈련용 리뷰 개수 출력 훈련용 리뷰 개수 : 150000 훈련 데이터는 150,000개다. 상위 5개를 출력해보자. train_data[:5] # 상위 5개 출력 id, document, label 3개 열로 구성돼 있다. id는 감성 분류에 도움이 안 되니 무시한다. 결국 모델은 리뷰 내용인 document와 긍정(1)/부정(0)인 label 두 열을 학습해야 한다. 상위 5개만 봐도 한국어 데이터의 특징이 보인다. 2번 샘플은 4.3에서 본 것처럼 띄어쓰기를 안 해도 이해되는 한국어 특성 때문에 띄어쓰기가 안 돼 있다. 테스트 데이터도 확인하자. print('테스트용 리뷰 개수 :',len(test_data)) # 테스트용 리뷰 개수 출력 테스트용 리뷰 개수 : 50000 test_data[:5] 테스트 데이터는 50,000개이고 형식은 훈련 데이터와 같다. 2) 데이터 정제하기 훈련 데이터에 중복이 있는지 확인한다. # document 열과 label 열의 중복을 제외한 값의 개수 train_data['document'].nunique(), train_data['label'].nunique() (146182, 2) 150,000개 중 document 열의 고유값이 146,182개라는 건 약 4,000개가 중복이라는 뜻이다. label은 0 또는 1이라 2가 나온다. 중복을 제거하자. # document 열의 중복 제거 train_data.drop_duplicates(subset=['document'], inplace=True) print('총 샘플의 수 :',len(train_data)) 총 샘플의 수 : 146183 146,182가 아니라 146,183인 이유는 nunique() 가 Null을 세지 않기 때문이다. 아래에서 보는 Null 샘플 1개가 남아 있다. 레이블 분포를 보자. train_data['label'].value_counts().plot(kind = 'bar') 긍정과 부정 모두 약 72,000개로 분포가 균일해 보인다. 정확한 개수를 확인하자. print(train_data.groupby('label').size().reset_index(name = 'count')) label count 0 0 73342 1 1 72841 레이블 0인 리뷰가 근소하게 많다. Null 값을 가진 샘플이 있는지 확인한다. print(train_data.isnull().values.any()) True Null이 있다. 어느 열에 있는지 보자. print(train_data.isnull().sum()) id 0 document 1 label 0 dtype: int64 document 열에 1개 있다. 어떤 샘플인지 출력해보자. train_data.loc[train_data.document.isnull()] Null 샘플을 제거한다. train_data = train_data.dropna(how = 'any') # Null 값이 존재하는 행 제거 print(train_data.isnull().values.any()) # Null 값이 존재하는지 확인 False print(len(train_data)) 146182 1개가 제거됐다. 이제 온점(.)이나 ? 같은 특수문자를 지우고 한글만 남기기 위해 정규 표현식을 쓴다. 영어로 먼저 보자. 알파벳을 나타내는 정규 표현식은 [a-zA-Z] 다. 이를 응용하면 알파벳과 공백을 제외하고 모두 지울 수 있다. #알파벳과 공백을 제외하고 모두 제거 eng_text = 'do!!! you expect... people~ to~ read~ the FAQ, etc. and actually accept hard~! atheism?@@' print(re.sub(r'[^a-zA-Z ]', '', eng_text)) 'do you expect people to read the FAQ etc and actually accept hard atheism' 한국어도 같은 원리다. 한글 범위만 정하면 된다. 자음은 ᄀ ~ ᄒ, 모음은 ᅡ ~ ᅵ로 지정한다. 자모 참고 : https://www.unicode.org/charts/PDF/U3130.pdf ᄀ ~ ᄒ : 3131 ~ 314E ᅡ ~ ᅵ : 314F ~ 3163 완성형 한글은 가 ~ 힣으로 지정한다. 완성형 참고 : https://www.unicode.org/charts/PDF/UAC00.pdf 이 범위를 모두 반영해 한글과 공백을 제외하고 전부 제거하자. # 한글과 공백을 제외하고 모두 제거 train_data['document'] = train_data['document'].str.replace("[^ᄀ-하-ᅵ가-힣 ]","", regex=True) train_data[:5] 띄어쓰기는 유지되고 구두점은 제거됐다. 그런데 네이버 영화 리뷰는 영어, 숫자, 특수문자로만 쓸 수도 있다. 한글이 없던 리뷰는 이제 빈(empty) 값이 됐을 것이다. 공백만 있거나 빈 값인 행을 Null로 바꾸고 확인하자. train_data['document'] = train_data['document'].str.replace('^ +', "", regex=True) # white space 데이터를 empty value로 변경 train_data['document'].replace('', np.nan, inplace=True) print(train_data.isnull().sum()) id 0 document 789 label 0 dtype: int64 Null이 789개나 새로 생겼다. 5개만 보자. train_data.loc[train_data.document.isnull()][:5] 레이블은 긍정일 수도 부정일 수도 있지만, 텍스트가 없으니 의미 없는 데이터다. 제거한다. train_data = train_data.dropna(how = 'any') print(len(train_data)) 145393 145,393개가 남았다. 테스트 데이터에도 같은 전처리를 한다. test_data.drop_duplicates(subset = ['document'], inplace=True) # document 열에서 중복인 내용이 있다면 중복 제거 test_data['document'] = test_data['document'].str.replace("[^ᄀ-하-ᅵ가-힣 ]","", regex=True) # 정규 표현식 수행 test_data['document'] = test_data['document'].str.replace('^ +', "", regex=True) # 공백은 empty 값으로 변경 test_data['document'].replace('', np.nan, inplace=True) # 공백은 Null 값으로 변경 test_data = test_data.dropna(how='any') # Null 값 제거 print('전처리 후 테스트용 샘플의 개수 :',len(test_data)) 전처리 후 테스트용 샘플의 개수 : 48852 3) 토큰화 토큰화하면서 불용어 (stopwords)도 제거한다. 불용어는 정의하기 나름이다. 조사, 접속사 같은 보편적인 불용어를 쓸 수도 있지만, 결국 데이터를 계속 검토하면서 추가해가는 경우가 많다. 현업이라면 보통 아래보다 더 많은 불용어를 쓴다. stopwords = ['도', '는', '다', '의', '가', '이', '은', '한', '에', '하', '고', '을', '를', '인', '듯', '과', '와', '네', '들', '듯', '지', '임', '게'] 형태소 분석기는 KoNLPy의 Mecab을 쓴다. mecab = Mecab() mecab.morphs('와 이런 것도 영화라고 차라리 뮤직비디오를 만드는 게 나을 뻔') ['와', '이런', '것', '도', '영화', '라고', '차라리', '뮤직', '비디오', '를', '만드', '는', '게', '나을', '뻔'] 4.3에서 본 것처럼 한국어는 띄어쓰기가 아니라 형태소 분석기로 토큰화한다. 훈련 데이터를 토큰화하면서 불용어를 제거해 X_train 에 저장한다. X_train = [] for sentence in tqdm(train_data['document']): tokenized_sentence = mecab.morphs(sentence) # 토큰화 stopwords_removed_sentence = [word for word in tokenized_sentence if not word in stopwords] # 불용어 제거 X_train.append(stopwords_removed_sentence) print(X_train[:3]) [['아', '더', '빙', '진짜', '짜증', '나', '네요', '목소리'], ['흠', '포스터', '보고', '초딩', '영화', '줄', '오버', '연기', '조차', '가볍', '않', '구나'], ['너무', '재', '밓었다그래서보는것을추천한다']] 세 번째 샘플처럼 띄어쓰기가 안 된 데다 오타('밓었다')까지 있으면 형태소 분석기도 제대로 못 나눈다. 테스트 데이터도 똑같이 토큰화한다. X_test = [] for sentence in tqdm(test_data['document']): tokenized_sentence = mecab.morphs(sentence) # 토큰화 stopwords_removed_sentence = [word for word in tokenized_sentence if not word in stopwords] # 불용어 제거 X_test.append(stopwords_removed_sentence) 4) 학습 데이터, 검증 데이터, 테스트 데이터 학습 중 성능을 평가할 검증 데이터 가 추가로 필요하다. 레이블 열을 따로 분리해 y_train , y_test 로 저장한다. 그러면 학습 데이터는 X_train , y_train , 테스트 데이터는 X_test , y_test 가 된다. 학습 데이터의 20%를 떼어 검증 데이터를 만든다. 데이터 분리는 주로 사이킷런의 train_test_split 을 쓰고, test_size 에 비율을 넣으면 그만큼 분할해준다. 랜덤 분할 중 레이블 불균형이 생기지 않도록 레이블 비율을 유지하고 싶다면 stratify 에 y 데이터를 넣는다. y_train = np.array(train_data['label']) y_test = np.array(test_data['label']) X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.2, random_state=0, stratify=y_train) 비율이 잘 유지됐는지 확인하자. print('--------학습 데이터의 비율-----------') print(f'부정 리뷰 = {round(np.sum(y_train==0)/len(y_train) * 100,3)}%') print(f'긍정 리뷰 = {round(np.count_nonzero(y_train)/len(y_train) * 100,3)}%') print('--------검증 데이터의 비율-----------') print(f'부정 리뷰 = {round(np.sum(y_valid==0)/len(y_valid) * 100,3)}%') print(f'긍정 리뷰 = {round(np.count_nonzero(y_valid)/len(y_valid) * 100,3)}%') print('--------테스트 데이터의 비율-----------') print(f'부정 리뷰 = {round(np.sum(y_test==0)/len(y_test) * 100,3)}%') print(f'긍정 리뷰 = {round(np.count_nonzero(y_test)/len(y_test) * 100,3)}%') --------학습 데이터의 비율----------- 부정 리뷰 = 50.238% 긍정 리뷰 = 49.762% --------검증 데이터의 비율----------- 부정 리뷰 = 50.239% 긍정 리뷰 = 49.761% --------테스트 데이터의 비율----------- 부정 리뷰 = 49.808% 긍정 리뷰 = 50.192% 학습 데이터와 검증 데이터의 레이블 비율이 같다. 5) 단어 집합 만들기 텍스트를 숫자로 처리하려면 정수 인코딩을 해야 한다. 먼저 훈련 데이터로 단어 집합 (vocabulary)을 만든다. word_list = [] for sent in X_train: for word in sent: word_list.append(word) word_counts = Counter(word_list) print('총 단어수 :', len(word_counts)) 총 단어수 : 45296 단어가 45,000개가 넘는다. Counter() 로 셌기 때문에 각 단어의 등장 빈도가 저장돼 있다. print('훈련 데이터에서의 단어 영화의 등장 횟수 :', word_counts['영화']) print('훈련 데이터에서의 단어 공감의 등장 횟수 :', word_counts['공감']) 훈련 데이터에서의 단어 영화의 등장 횟수 : 45791 훈련 데이터에서의 단어 공감의 등장 횟수 : 756 빈도수가 높은 순서로 정렬하자. vocab = sorted(word_counts, key=word_counts.get, reverse=True) print('등장 빈도수 상위 10개 단어') print(vocab[:10]) 등장 빈도수 상위 10개 단어 ['영화', '보', '있', '없', '좋', '나', '었', '만', '는데', '너무'] 빈도수가 낮은 단어는 배제하려고 한다. 등장 빈도가 3회 미만인 단어가 얼마나 되는지 보자. threshold = 3 total_cnt = len(word_counts) # 단어의 수 rare_cnt = 0 # 등장 빈도수가 threshold보다 작은 단어의 개수를 카운트 total_freq = 0 # 훈련 데이터의 전체 단어 빈도수 총 합 rare_freq = 0 # 등장 빈도수가 threshold보다 작은 단어의 등장 빈도수의 총 합 # 단어와 빈도수의 쌍(pair)을 key와 value로 받는다. for key, value in word_counts.items(): total_freq = total_freq + value # 단어의 등장 빈도수가 threshold보다 작으면 if(value < threshold): rare_cnt = rare_cnt + 1 rare_freq = rare_freq + value print('단어 집합(vocabulary)의 크기 :',total_cnt) print('등장 빈도가 %s번 이하인 희귀 단어의 수: %s'%(threshold - 1, rare_cnt)) print("단어 집합에서 희귀 단어의 비율:", (rare_cnt / total_cnt)*100) print("전체 등장 빈도에서 희귀 단어 등장 빈도 비율:", (rare_freq / total_freq)*100) 단어 집합(vocabulary)의 크기 : 45296 등장 빈도가 2번 이하인 희귀 단어의 수: 26105 단어 집합에서 희귀 단어의 비율: 57.63202048746025 전체 등장 빈도에서 희귀 단어 등장 빈도 비율: 2.2769635638286716 2회 이하로 나온 단어가 단어 집합의 절반 이상(57.6%)을 차지하지만, 실제 등장 빈도로는 2.27%밖에 안 된다. 별로 중요하지 않은 단어들이니 정수 인코딩에서 배제한다. 단어 집합의 최대 크기를 이 단어들을 뺀 개수로 제한하자. # 전체 단어 개수 중 빈도수 2이하인 단어는 제거. vocab_size = total_cnt - rare_cnt vocab = vocab[:vocab_size] print('단어 집합의 크기 :', len(vocab)) 단어 집합의 크기 : 19191 단어 집합의 크기는 19,191이다. 여기에 패딩과 모르는 단어를 위해 <PAD> 와 <UNK> 를 추가한다. 실제 의미는 없지만 특별한 용도로 쓰는 이런 단어를 스페셜 토큰 (Special Token)이라고 한다. 각각 0과 1에 할당한다. 4.4에서 <unk> , <pad> 를 단어 집합에 넣었던 것과 같다. word_to_index = {} word_to_index['<PAD>'] = 0 word_to_index['<UNK>'] = 1 for index, word in enumerate(vocab) : word_to_index[word] = index + 2 vocab_size = len(word_to_index) print('패딩 토큰과 UNK 토큰을 고려한 단어 집합의 크기 :', vocab_size) 패딩 토큰과 UNK 토큰을 고려한 단어 집합의 크기 : 19193 print('단어 <PAD>와 맵핑되는 정수 :', word_to_index['<PAD>']) print('단어 <UNK>와 맵핑되는 정수 :', word_to_index['<UNK>']) print('단어 영화와 맵핑되는 정수 :', word_to_index['영화']) 단어 <PAD>와 맵핑되는 정수 : 0 단어 <UNK>와 맵핑되는 정수 : 1 단어 영화와 맵핑되는 정수 : 2 빈도 1위인 '영화'가 스페셜 토큰 바로 다음인 2가 됐다. 6) 정수 인코딩 이제 정수 인코딩을 하자. 단어 집합에 없는 단어는 일괄로 <UNK> , 즉 정수 1로 매핑한다. def texts_to_sequences(tokenized_X_data, word_to_index): encoded_X_data = [] for sent in tokenized_X_data: index_sequences = [] for word in sent: try: index_sequences.ap
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
4.5 LSTM을 이용한 네이버 영화 리뷰 분류. 네이버 영화 리뷰 데이터에 대한 이해와 전처리 LSTM을 이용한 네이버 영화 리뷰 분류 모델 평가 코드 작성 학습 모델 로드 및 평가 모델 테스트 교재 13-2장의 내용을 공부하고 정리한 포스트입니다. https://wikidocs.net/book/2788 - 교재 https://github.com/ukairia777/pytorch-nlp-tutorial - 깃허브 이번 주에 배운 걸 전부 합쳐보자. 4.3의 형태소 토큰화로 리뷰를 나누고, 정수 인코딩한 뒤 4.4의 nn.Embedding() 으로 벡터로 바꾸고, 4.2의 LSTM에 넣어 긍정/부정을 맞히는 모델을 만든다. 4.1에서 본 다 대 일 (many-to-one) 구조다. 데이터는 네이버 영화…
Открыть источник