<jupyter_start><jupyter_text>네이버 영화리뷰 감정 분석 문제에 SentencePiece 적용해 보기 Step 1. SentencePiece 설치하기SentencePiece는 SentencePiece는 Google에서 제공하는 오픈소스 기반 Sentence Tokenizer/Detokenizer 로서, BPE와 unigram 2가지 subword 토크나이징 모델 중 하나를 선택해서 사용할 수 있도록 패키징한 것입니다. 아래 링크의 페이지에서 상세한 내용을 파악할 수 있습니다.google/sentencepiece (https://github.com/google/sentencepiece)위 페이지의 서두에서도 언급하고 있듯, SentencePiece는 딥러닝 자연어처리 모델의 앞부분에 사용할 목적으로 최적화되어 있는데, 최근 pretrained model들이 거의 대부분 SentencePiece를 tokenizer로 채용하면서 사실상 표준의 역할을 하고 있습니다. 앞으로의 실습 과정에서 자주 만나게 될 것이므로 꼭 친숙해지시기를 당부드립니다.다음과 같이 설치를 진행합니다. SentencePiece는 python에서 쓰라고 만들어진 라이브러리는 아니지만 편리한 파이썬 wrapper를 아래와 같이 제공하고 있습니다.<jupyter_code>!pip install sentencepiece
import os

import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np

%matplotlib inline
import os
path_to_file = os.getenv('HOME')+'/aiffel/GD_02_sp_tokenizer/data/korean-english-park.train.ko'

with open(path_to_file, "r") as f:
    raw = f.read().splitlines()

print("Data Size:", len(raw))

print("Example:")
for sen in raw[0:100][::20]: print(">>", sen)
min_len = 999
max_len = 0
sum_len = 0

for sen in raw:
    length = len(sen)
    if min_len > length: min_len = length
    if max_len < length: max_len = length
    sum_len += length

print("문장의 최단 길이:", min_len)
print("문장의 최장 길이:", max_len)
print("문장의 평균 길이:", sum_len // len(raw))

sentence_length = np.zeros((max_len), dtype=np.int)

for sen in raw:
    sentence_length[len(sen)-1] += 1

plt.bar(range(max_len), sentence_length, width=1.0)
plt.title("Sentence Length Distribution")
plt.show()
def check_sentence_with_length(raw, length):
    count = 0
    
    for sen in raw:
        if len(sen) == length:
            print(sen)
            count += 1
            if count > 100: return

check_sentence_with_length(raw, 1)
for idx, _sum in enumerate(sentence_length):
    # 문장의 수가 1500을 초과하는 문장 길이를 추출합니다.
    if _sum > 1500:
        print("Outlier Index:", idx+1)
check_sentence_with_length(raw, 11)
min_len = 999
max_len = 0
sum_len = 0

cleaned_corpus = list(set(raw))  # set를 사용해서 중복을 제거합니다.
print("Data Size:", len(cleaned_corpus))

for sen in cleaned_corpus:
    length = len(sen)
    if min_len > length: min_len = length
    if max_len < length: max_len = length
    sum_len += length

print("문장의 최단 길이:", min_len)
print("문장의 최장 길이:", max_len)
print("문장의 평균 길이:", sum_len // len(cleaned_corpus))

sentence_length = np.zeros((max_len), dtype=np.int)

for sen in cleaned_corpus:   # 중복이 제거된 코퍼스 기준
    sentence_length[len(sen)-1] += 1

plt.bar(range(max_len), sentence_length, width=1.0)
plt.title("Sentence Length Distribution")
plt.show()
max_len = 150
min_len = 10

# 길이 조건에 맞는 문장만 선택합니다.
filtered_corpus = [s for s in cleaned_corpus if (len(s) < max_len) & (len(s) >= min_len)]

# 분포도를 다시 그려봅니다.
sentence_length = np.zeros((max_len), dtype=np.int)

for sen in filtered_corpus:
    sentence_length[len(sen)-1] += 1

plt.bar(range(max_len), sentence_length, width=1.0)
plt.title("Sentence Length Distribution")
plt.show()<jupyter_output><empty_output><jupyter_text>Step 2. SentencePiece 모델 학습¶앞서 배운 tokenize() 함수를 기억하나요? 다시 한번 상기시켜드릴게요!<jupyter_code>def tokenize(corpus):  # corpus: Tokenized Sentence's List
    tokenizer = tf.keras.preprocessing.text.Tokenizer(filters='')
    tokenizer.fit_on_texts(corpus)

    tensor = tokenizer.texts_to_sequences(corpus)

    tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor, padding='post')

    return tensor, tokenizer
import sentencepiece as spm
import os


temp_file = os.getenv('HOME')+'/aiffel/GD_02_sp_tokenizer/data/korean-english-park.train.ko.temp'

vocab_size = 8000

with open(temp_file, 'w') as f:
    for row in filtered_corpus:   # 이전 스텝에서 정제했던 corpus를 활용합니다.
        f.write(str(row) + '\n')

# spm.SentencePieceTrainer.Train(
#     '--input={} --model_prefix=korean_spm --vocab_size={}'.format(temp_file, vocab_size)    
# )
# #위 Train에서  --model_type = 'unigram'이 디폴트 적용되어 있습니다. --model_type = 'bpe' 로 옵션을 주어 변경할 수 있습니다.


# # 왜인지 오류가 나서 학습이 되지 않음 


vocab_size_8k = 8000
vocab_size_16k = 16000




# vocab size=8000, model_type=unigram
spm.SentencePieceTrainer.Train(
    '--input={} --model_prefix=kor_spm_unigram_8k --vocab_size={}'.format(temp_file, vocab_size_8k)    
)


# vocab size=16000, model_type=unigram
spm.SentencePieceTrainer.Train(
    '--input={} --model_prefix=kor_spm_uingram_4k --vocab_size={}'.format(temp_file, vocab_size_8k/2)    
)

# vocab size=8000, model_type=bpe
spm.SentencePieceTrainer.Train(
    '--input={} --model_prefix=kor_spm_bpe_8k --model_type=bpe --vocab_size={}'.format(temp_file, vocab_size_8k)    
)

# vocab size = 16000, model_type=bpe
spm.SentencePieceTrainer.Train(
    '--input={} --model_prefix=kor_spm_bpe_16k --model_type=bpe --vocab_size={}'.format(temp_file, vocab_size_8k*2)    
)

#위 Train에서  --model_type = 'unigram'이 디폴트 적용되어 있습니다. --model_type = 'bpe' 로 옵션을 주어 변경할 수 있습니다.

!ls -l korean_spm*


s = spm.SentencePieceProcessor()
s.Load('korean_spm.model')

# SentencePiece를 활용한 sentence -> encoding
tokensIDs = s.EncodeAsIds('아버지가방에들어가신다.')
print(tokensIDs)

# SentencePiece를 활용한 sentence -> encoded pieces
print(s.SampleEncodeAsPieces('아버지가방에들어가신다.',1, 0.0))

# SentencePiece를 활용한 encoding -> sentence 복원
print(s.DecodeIds(tokensIDs))<jupyter_output>[1244, 11, 306, 7, 3599, 11, 286, 38, 3]
['▁아버지', '가', '방', '에', '들어', '가', '신', '다', '.']
아버지가방에들어가신다.<jupyter_text>Step 3. Tokenizer 함수 작성우리는 위에서 훈련시킨 SentencePiece를 활용하여 위 함수와 유사한 기능을 하는 sp_tokenize() 함수를 정의할 겁니다. 하지만 SentencePiece가 동작하는 방식이 단순 토큰화와는 달라 완전히 동일하게는 정의하기 어렵습니다. 그러니 아래 조건을 만족하는 함수를 정의하도록 하습니다.1) 매개변수로 토큰화된 문장의 list를 전달하는 대신 온전한 문장의 list 를 전달합니다.2) 생성된 vocab 파일을 읽어와 { : } 형태를 가지는 word_index 사전과 { : } 형태를 가지는 index_word 사전을 생성하고 함께 반환합니다.3) 리턴값인 tensor 는 앞의 함수와 동일하게 토큰화한 후 Encoding된 문장입니다. 바로 학습에 사용할 수 있게 Padding은 당연히 해야겠죠?<jupyter_code>def sp_tokenize(s, corpus):

    tensor = []

    for sen in corpus:
        tensor.append(s.EncodeAsIds(sen))

    with open("./korean_spm.vocab", 'r') as f:
        vocab = f.readlines()

    word_index = {}
    index_word = {}

    for idx, line in enumerate(vocab):
        word = line.split("\t")[0]

        word_index.update({idx:word})
        index_word.update({word:idx})

    tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor, padding='post')

    return tensor, word_index, index_word
#sp_tokenize(s, corpus) 사용예제

my_corpus = ['나는 새싹 캠퍼스에 출근해서 일을 했습니다.', '하지만 ㅠㅠ 월급을 받지 못해서 밥을 굶었습니다...']
tensor, word_index, index_word = sp_tokenize(s, my_corpus)
print(tensor)<jupyter_output>[[1949  630 7718    4 7979  756   47    7 1657 2677 1024 1795    4  399
     3    0    0    0]
 [ 781    4    0    4   56  640    5 2166 5102   94 5662    5    4 7867
  1983    3    3    3]]<jupyter_text>Step 4. 네이버 영화리뷰 감정 분석 문제에 SentencePiece 적용해 보기아마 여러분들은 네이버 영화리뷰 감정 분석 태스크(https://github.com/e9t/nsmc/)를 한 번쯤은 다루어 보았을 것입니다. 한국어로 된 corpus를 다루어야 하므로 주로 KoNLPy에서 제공하는 형태소 분석기를 사용하여 텍스트를 전처리해서 RNN 모델을 분류기로 사용했을 것입니다.만약 이 문제에서 tokenizer를 SentencePiece로 바꾸어 다시 풀어본다면 더 성능이 좋아질까요? 비교해 보는 것도 흥미로울 것입니다.네이버 영화리뷰 감정 분석 코퍼스에 SentencePiece를 적용시킨 모델 학습하기학습된 모델로 sp_tokenize() 메소드 구현하기구현된 토크나이저를 적용하여 네이버 영화리뷰 감정 분석 모델을 재학습하기KoNLPy 형태소 분석기를 사용한 모델과 성능 비교하기(보너스) SentencePiece 모델의 model_type, vocab_size 등을 변경해 가면서 성능 개선 여부 확인하기Word Vector는 활용할 필요가 없습니다. 활용이 가능하지도 않을 것입니다. 머지않아 SentencePiece와 BERT 등의 pretrained 모델을 함께 활용하는 태스크를 다루게 될 것입니다. Reference 1) https://github.com/miinkang/AI_Project_AIFFEL/blob/main/%5BGD-02%5DSentencePiece.ipynb2) https://piaojian.tistory.com/35  1) 데이터 준비와 확인<jupyter_code>import pandas as pd
import urllib.request
%matplotlib inline
import matplotlib.pyplot as plt
import re
from konlpy.tag import Okt
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer
import numpy as np
from tensorflow.keras.preprocessing.sequence import pad_sequences
from collections import Counter

# 데이터를 읽어봅시다. 
train_data_org = pd.read_table('~/aiffel/GD_02_sp_tokenizer/data/nsmc/ratings_train.txt')
test_data = pd.read_table('~/aiffel/GD_02_sp_tokenizer/data/nsmc/ratings_test.txt')

train_data.head()<jupyter_output><empty_output><jupyter_text>2) 데이터로더 구성실습 때 다루었던 IMDB 데이터셋은 텍스트를 가공하여 imdb.data_loader() 메소드를 호출하면 숫자 인덱스로 변환된 텍스트와 word_to_index 딕셔너리까지 친절하게 제공합니다. 그러나 이번에 다루게 될 nsmc 데이터셋은 전혀 가공되지 않은 텍스트 파일로 이루어져 있습니다. 이것을 읽어서 imdb.data_loader()와 동일하게 동작하는 자신만의 data_loader를 만들어 보는 것으로 시작합니다. data_loader 안에서는 다음을 수행해야 합니다.데이터의 중복 제거NaN 결측치 제거한국어 토크나이저로 토큰화불용어(Stopwords) 제거사전word_to_index 구성텍스트 스트링을 사전 인덱스 스트링으로 변환X_train, y_train, X_test, y_test, word_to_index 리턴<jupyter_code>!pip install --upgrade pip
!pip install --upgrade gensim==3.8.3
!pip install konlpy
!sudo apt-get install curl git
!bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)
## LMS Exploration 06 Movie Sentiment Analysis codes


# from konlpy.tag import Mecab
# tokenizer = Mecab()
# stopwords = ['의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']

# def load_data(train_data, test_data, num_words=10000):
#     train_data.drop_duplicates(subset=['document'], inplace=True)
#     train_data = train_data.dropna(how = 'any') 
#     test_data.drop_duplicates(subset=['document'], inplace=True)
#     test_data = test_data.dropna(how = 'any') 
    
#     X_train = []
#     for sentence in train_data['document']:
#         temp_X = tokenizer.morphs(sentence) # 토큰화
#         temp_X = [word for word in temp_X if not word in stopwords] # 불용어 제거
#         X_train.append(temp_X)

#     X_test = []
#     for sentence in test_data['document']:
#         temp_X = tokenizer.morphs(sentence) # 토큰화
#         temp_X = [word for word in temp_X if not word in stopwords] # 불용어 제거
#         X_test.append(temp_X)
    
#     words = np.concatenate(X_train).tolist()
#     counter = Counter(words)
#     counter = counter.most_common(10000-4)
#     vocab = ['', '', '', ''] + [key for key, _ in counter]
#     word_to_index = {word:index for index, word in enumerate(vocab)}
        
#     def wordlist_to_indexlist(wordlist):
#         return [word_to_index[word] if word in word_to_index else word_to_index[''] for word in wordlist]
    
    
#     X_train = list(map(wordlist_to_indexlist, X_train))
#     X_test = list(map(wordlist_to_indexlist, X_test))
        
#     return X_train, np.array(list(train_data['label'])), X_test, np.array(list(test_data['label'])), word_to_index
    
# X_train, y_train, X_test, y_test, word_to_index = load_data(train_data, test_data) 
print("Unique training data : ", train_data_org['document'].nunique())
print("Unique target data : ", train_data_org['label'].nunique())
# delete the duplicate data
train_data_org.drop_duplicates(subset=['document'], inplace=True)
# use the drop_duplicate method
# check the number of the trainig data again
print('number of total sample : ',len(train_data_org))
# check the null value in the document (label)
print(train_data_org_.isnull().values.any())
# check the location of the null data
train_data_org.loc[train_data_org.document.isnull()]
# remove the NULL data
train_data_none = train_data_org.dropna(how = 'any')
print(train_data_none.isnull().values.any())


# data preprocessing
document = train_data_none['document']
naver_review = document


min_len = 999
max_len = 0
sum_len = 0

for sen in document:
    length = len(sen)
    if min_len > length: 
        min_len = length
        
    if max_len < length: 
        max_len = length
    
    sum_len += length

print("문장의 최단 길이:", min_len)
print("문장의 최장 길이:", max_len)
print("문장의 평균 길이:", sum_len // len(document))

sentence_length = np.zeros((max_len), dtype=np.int)

for sen in document:
    sentence_length[len(sen)-1] += 1

plt.bar(range(max_len), sentence_length, width=1.0)
plt.title("Sentence Length Distribution")
plt.show()<jupyter_output>문장의 최단 길이: 1
문장의 최장 길이: 146
문장의 평균 길이: 35<jupyter_text>최장 길이 140에 가까운 데이터가 꽤 많습니다. 어떤 문장인지 확인해봅니다.<jupyter_code>def check_sentence_with_length(raw, length):
    count = 0
    
    for sen in raw:
        if len(sen) == length:
            print(sen)
            count += 1
            if count > 100: return 

check_sentence_with_length(document, 135)
check_sentence_with_length(document, 1)
check_sentence_with_length(document, 8)<jupyter_output>카밀라벨 발연기
내용이 이상해;
너무 좋은 영화
가발 쓰고 싶다
0점은 없나??
말랑말랑 ...
그냥. 최고다.
감동적인 영화다
평점 조절...
재미없음 -_-
전율을 느꼈다.
재미잇음매우~~
좋네..괜찮고.
ㅋㅋㅋ 재미없음
파괴된 관객들.
나수윤의 발견!
너무 배배꼬았다
정말 재미있어요
그래서 2편은?
이건그냥 야동임
나에게는 수면제
같이울게햇으니까
레알 쩐다 ㅇㅇ
별반개도 아깝다
사랑해요 공효진
좋은 로맨스물.
개 쌈마이 영화
재밌다. 그뿐.
빛 좋은 개살구
쵝오의 드라마~
테러방지법...
진정 한심하다.
꼭보소 두번보소
여지없는 푸념.
아 양민수(2)
까브리머싯씀ㅎㅎ
개연성 개나줘.
그냥병맛ㅋㅋㅋㅋ
흠 좀 그렇던데
기대 이하다..
최고의 액션배우
명작...ㅎㅎ;
추억의 명작만화
진리와 평등이제
공포영화 맞니?
개구쟁이 스머프
닥치고 내돈내놔
감독의 내공부족
이거 꾀 재밌다
양요섭 표절논란
별로 같은뎅..
멋지다 조문탁!
인내력 테스트.
이건 뭘 까요?
저평가된 007
어덜트 비디오용
박찬욱편의 승리
화끈한 액션영화
디카프리오, 끝
심형래니까 1점
쫌 아니다...
시간잘때웠습니당
희극 또는 비극
진부한 재난영화
So What?
멸망 ㅈㄹ 한다
하나의 예술작품
완전 재밋습니다
zzzzzzzz
아프네요 가슴이
오자룡은기어갔냐
김리나가 이쁘다
동영상 안 나와
아름다우네요,,
안보여요 ///
이거 너무 좋아
그닥 재미없던데
이것도 영화냐?
한국영화 참아라
흑 부럽다.ㅋㅋ
너무 뻔한 전개
얘네들 뭐냐;;
형식미의 절정.
인상적인 영화.
전설까지야...
휴그랜트만의영화
2도 볼만함..
헛웃음만 나와.
보지마요....
사토시가나오니까
제목 그대로 ~
재밌을것 같아요
스토리가 좋네요
평점 왜케 낮어
영화이해가 안됨
ㅡ.,ㅡ; 뭐얌
재밌겠당..~~
난졸라재밋게봣음
0점은 없는건지
으헝 재미없겠디
충무로의 OOO<jupyter_text>최적의 max_len을 찾는 함수<jupyter_code>def below_threshold_len(max_len, nested_list):
    cnt = 0
    for s in nested_list:
        if(len(s) <= max_len):
            cnt = cnt + 1
    print('전체 샘플 중 길이가 %s 이하인 샘플의 비율: %s'%(max_len, (cnt / len(nested_list))*100))
below_threshold_len(36, document)  # 평균
below_threshold_len(140, document)  # 최장 
below_threshold_len(70, document)  # 중앙값
below_threshold_len(50, document)  
below_threshold_len(60, document)  
max_len = 55   
min_len = 0

# 길이 조건에 맞는 문장만 선택합니다.

filtered_corpus = [s for s in naver_review if (len(s) < max_len)]


# 데이터프레임 조건부 슬라이싱 
# str.len() 두 함수에 대해 알 수 있었다. 
train_data = naver_review[naver_review.str.len() < max_len]   



sentence_length = np.zeros((max_len), dtype=np.int)

naver_review.head()

for sen in train_data:   # 중복이 제거된 코퍼스 기준
    sentence_length[len(sen)-1] += 1

plt.bar(range(max_len), sentence_length, width=1.0)
plt.title("Sentence Length Distribution")
plt.show()
plt.figure(figsize=(12,5))
plt.boxplot([sentence_length], showmeans=True)
plt.show()<jupyter_output><empty_output><jupyter_text>전처리 하다가 몇개 데이터 날려먹어서 LSTM 모델 돌릴 때 사이즈가 안 맞아서 다시 EDA 처음부터 시작!<jupyter_code>train_data = pd.read_table("data/nsmc/"+"ratings_train.txt")
test_data = pd.read_table("data/nsmc/"+"ratings_test.txt")

display(train_data.head())
display(test_data.head())
print("학습데이터 유니크 :",train_data['document'].nunique()) #  약 4,000개의 중복 샘플이 존재
print("타겟데이터 유니크 : ",train_data['label'].nunique())
# 중복데이터 제거
train_data.drop_duplicates(subset=['document'], inplace=True) # drop_duplicates 활용
# 학습데이터 개수 다시 확인
print('총 샘플의 수 :',len(train_data))
# document에 null 값이 있는지 확인
print(train_data.isnull().values.any())
# 어느 위치에 누락값이 있는지 확인
train_data.loc[train_data.document.isnull()]
# Null값 제거
train_data = train_data.dropna(how = 'any') # Null 값이 존재하는 행 제거
print(train_data.isnull().values.any()) # Null 값이 존재하는지 확인
# 데이터 전처리
train_data['document'] = train_data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","") # 한글과 공백을 제외하고 모두 제거
train_data[:5]
# 바뀐 데이터 내부에 null값이 있는지 다시 확인
train_data['document'] = train_data['document'].str.replace('^ +', "") # white space 데이터를 empty value로 변경
train_data['document'].replace('', np.nan, inplace=True)
print(train_data.isnull().sum())
# null 데이터 삭제
train_data = train_data.dropna(how = 'any')
print(len(train_data))
# 테스트데이터에도 동일한 과정 진행
test_data.drop_duplicates(subset = ['document'], inplace=True) # document 열에서 중복인 내용이 있다면 중복 제거
test_data['document'] = test_data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","") # 정규 표현식 수행
test_data['document'] = test_data['document'].str.replace('^ +', "") # 공백은 empty 값으로 변경
test_data['document'].replace('', np.nan, inplace=True) # 공백은 Null 값으로 변경
test_data = test_data.dropna(how='any') # Null 값 제거
print('전처리 후 테스트용 샘플의 개수 :',len(test_data))
# 데이터 확인
train_data.info()
print("="*50)
test_data.info() 
# 데이터 확인
display(train_data.head())
display(test_data.head())

print("훈련데이터 : ",len(train_data))
print("테스트데이터 : ",len(test_data))
# 데이터 길이 분포 확인하기
import matplotlib.pyplot as plt

min_len = 999
max_len = 0
sum_len = 0

for sen in train_data['document']:
    length = len(sen)
    
    # 문장 최소 길이 찾기
    if min_len > length: 
        min_len = length
    
    # 문장 최대 길이 찾기
    if max_len < length: 
        max_len = length
        
    # 전체 문장을 합치면 길이가 얼마나 될까요?
    sum_len += length

print("문장의 최단 길이:", min_len)
print("문장의 최장 길이:", max_len)
print("문장의 평균 길이:", sum_len // len(train_data))

# 전체 길이만큼 0벡터 ==> 길이에 따른 문장의 수를 저장하기 위해 먼저 0으로 이루어진 리스트를 만든다!!
sentence_length = np.zeros((max_len), dtype=np.int)
print("sentence_length : ",sentence_length)

print("="*60)

# raw는 위에서 다운로드한 데이터셋!! 전체 길이와 상관없음
for sen in train_data['document']:
    sentence_length[len(sen)-1] += 1 # 0으로 이루어진 벡터에 문장 count를 더한 뒤 넣는다.

plt.bar(range(max_len), sentence_length, width=1.0) # 너비는 1.0씩 늘어나도록 설정
plt.title("Sentence Length Distribution")
plt.show()
# 길이 45이하로 한정해 데이터를 만듭니다. ==> 너무 짧은 리뷰는 의미가 없음

# 이렇게 하면 document 내용만 list로 전달됨 ==> 나는 데이터프레임을 원한다!!
train_list = [s for s in train_data['document'] if (len(s) <= 45)]
test_list = [s for s in test_data['document'] if (len(s) <= 45)]

train_list = list(set(train_list))
test_list = list(set(test_list))

train_df = pd.DataFrame(train_list)
test_df = pd.DataFrame(test_list)
# 길이 40이하인 데이터를 기존 데이터와 병합합니다.

new_train_df = pd.merge(train_data, train_df, how='inner', left_on='document', right_on=0)
new_test_df = pd.merge(test_data, test_df, how='inner', left_on='document', right_on=0)

train_data = new_train_df[['id', 'document', 'label']]
test_data = new_test_df[['id', 'document', 'label']]
display(train_data.head())
display(test_data.head())

print("훈련데이터 : ",len(train_data))
print("테스트데이터 : ",len(test_data))<jupyter_output><empty_output><jupyter_text>Sentence Piece subwords 점검<jupyter_code>!ls -l kor_spm_unigram_8k*
!ls -l kor_spm_unigram_4k*
!ls -l kor_spm_bpe_8k*
!ls -l kor_spm_bpe_16k*

#왜인지 unigram_4k 가 없다.
import csv

vocab_list_uni_8k = pd.read_csv('kor_spm_unigram_8k.vocab', sep='\t', header=None, quoting=csv.QUOTE_NONE)
vocab_list_uni_8k[:10]
print('subword의 개수 :', len(vocab_list_uni_8k))
vocab_list_uni_8k.sample(10)
vocab_list_bpe_8k = pd.read_csv('kor_spm_bpe_8k.vocab', sep='\t', header=None, quoting=csv.QUOTE_NONE)
vocab_list_bpe_8k[:10]
print('subword의 개수 :', len(vocab_list_bpe_8k))
vocab_list_bpe_8k.sample(10)
vocab_list_bpe_16k = pd.read_csv('kor_spm_bpe_16k.vocab', sep='\t', header=None, quoting=csv.QUOTE_NONE)
vocab_list_bpe_16k[:10]
print('subword의 개수 :', len(vocab_list_bpe_16k))
vocab_list_bpe_16k.sample(10)<jupyter_output>subword의 개수 : 16000<jupyter_text>SentencePiece 성능 비교평가 - vocab size = 8k, 16k - model type = unigram, bpe - kor_spm_unigram_8k - kor_spm_unigram_16k - kor_spm_bpe_8k - kor_spm_bpe_16k<jupyter_code>def test_performance(model):
    s = spm.SentencePieceProcessor()
    s.Load(model)

    # SentencePiece를 활용한 sentence -> encoding
    tokensIDs = s.EncodeAsIds('아버지가방에들어가신다.')
    print(tokensIDs)

    # SentencePiece를 활용한 sentence -> encoded pieces
    print(s.SampleEncodeAsPieces('아버지가방에들어가신다.',1, 0.0))

    # SentencePiece를 활용한 encoding -> sentence 복원
    print(s.DecodeIds(tokensIDs))
print('kor_spm_unigram_8k')
test_performance(model='kor_spm_unigram_8k.model')

# # unigram은 오류가 나서 학습이 되지 않았다. 
# print('kor_spm_unigram_4k')
# test_performance(model='kor_spm_unigram_4k.model')

print('kor_spm_bpe_8k')
test_performance(model='kor_spm_bpe_8k.model')

print('kor_spm_bpe_16k')
test_performance(model='kor_spm_bpe_16k.model')<jupyter_output>kor_spm_unigram_8k
[1244, 11, 306, 7, 3599, 11, 286, 38, 3]
['▁아버지', '가', '방', '에', '들어', '가', '신', '다', '.']
아버지가방에들어가신다.
kor_spm_bpe_8k
[1520, 1154, 6614, 6710, 2644, 6782, 6685, 6687]
['▁아버', '지가', '방에', '들', '어가', '신', '다', '.']
아버지가방에들어가신다.
kor_spm_bpe_16k
[7651, 6614, 14710, 2644, 14782, 14685, 14687]
['▁아버지가', '방에', '들', '어가', '신', '다', '.']
아버지가방에들어가신다.<jupyter_text>3. SPM을 받을 수 있는 Tokenizer 함수 작성 훈련시킨 SentencePiece를 활용해 tokenizer역할을 하는 함수를 정의합니다.함수의 조건1) 매개변수로 토큰화된 문장의 list를 전달하는 대신 온전한 문장의 list 를 전달합니다.2) 생성된 vocab 파일을 읽어와 { \ : \ } 형태를 가지는 word_index 사전과 { \ : \} 형태를 가지는 index_word 사전을 생성하고 함께 반환합니다.3) 리턴값인 tensor는 앞의 함수와 동일하게 토큰화한 후 Encoding된 문장입니다. 바로 학습에 사용할 수 있게 Padding은 당연히 해야겠죠?<jupyter_code>def sp_tokenize(s, corpus, spm):

    tensor = []

    for sen in corpus:
        tensor.append(s.EncodeAsIds(sen))

    with open("./{}.vocab".format(spm), 'r') as f: 
        vocab = f.readlines()

    word_index = {}
    index_word = {}

    for idx, line in enumerate(vocab):
        word = line.split("\t")[0]

        word_index.update({idx:word})
        index_word.update({word:idx})

    tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor, padding='pre', maxlen=max_len)

    return tensor, word_index, index_word
# sp_tokenize(s, corpus) 사용 예제

s = spm.SentencePieceProcessor()
# s.Load('kor_spm_bpe_16k.model')  # lstm 학습시 오류

s.Load('kor_spm_unigram_8k.model') 

my_corpus = ['나는 새싹 캠퍼스에 출근해서 일을 했습니다.', '하지만 ㅠㅠ 월급을 받지 못해서 밥을 굶었습니다...']
tensor, word_index, index_word = sp_tokenize(s, my_corpus, 'kor_spm_unigram_8k')           
print(tensor)
# print(word_index)
# print(index_word)
model_uni_8k='kor_spm_unigram_8k'
model_uni_4k='kor_spm_unigram_4k'
model_bpe_8k='kor_spm_bpe_8k'
model_bpe_16k='kor_spm_bpe_16k'<jupyter_output><empty_output><jupyter_text>훈련데이터 / 테스트 데이터 나누기<jupyter_code># document와 label을 나눠봅시다.

x_train,x_train_word_index,x_train_index_word = sp_tokenize(s, train_data['document'], model_uni_8k)
x_test,x_test_word_index, x_test_index_word = sp_tokenize(s, test_data['document'], model_uni_8k)
# 현재 list 상태 ==> ndarray로 바꿔주기
y_train = np.array(list(train_data['label']))
y_test = np.array(list(test_data['label']))
print(x_train[:3])
print(x_test[:3])
print(y_train[:3])
print(y_test[:3])
print("학습데이터 :",len(x_train))
print("타겟데이터 :",len(y_train))
from sklearn.model_selection import train_test_split
train_input, val_input, train_target, val_target = train_test_split(X_train, y_train, test_size=0.2, random_state=42)
print(train_input[:3])
print(val_input[:3])<jupyter_output>[[   0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0 2009   24  812    7 1001 2295  379  113
   199  265   10 2760    8   14  218 1526    6 3582  128  222  620  521]
 [   0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0    0    0
     0    0    0    0    0    0    0    0    0    0    0    0  [...]<jupyter_text>4. lstm으로 감정분석 모델 학습SentencePiece 토크나이저가 적용된 모델을 학습 후 수렴하는 것을 확인합니다.SentencePiece 토크나이저 성능 평가<jupyter_code>tensor, word_index, index_word = sp_tokenize(s, naver_review, 'kor_spm_unigram_8k')
from tensorflow.keras.initializers import Constant

vocab_size = vocab_size
word_vector_dim = 32  

model_LSTM = keras.Sequential()
model_LSTM.add(keras.layers.Embedding(vocab_size, 
                                 word_vector_dim,
                                    ))
            
model_LSTM.add(keras.layers.LSTM(128))
# model_LSTM.add(keras.layers.Dense(128, activation='relu'))  
model_LSTM.add(keras.layers.Dense(1, activation='sigmoid'))  # 최종 출력은 긍정/부정을 나타내는 1dim 입니다.

model_LSTM.summary()<jupyter_output>Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 32)          256000    
_________________________________________________________________
lstm (LSTM)                  (None, 128)               82432     
_________________________________________________________________
dense (Dense)                (None, 1)                 129       
=================================================================
Total params: 338,561
Trainable params: 338,561
Non-trainable params: 0
_________________________________________________________________<jupyter_text>Earlystopping & Model Checkpoint<jupyter_code>from tensorflow.keras.models import load_model
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint


es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4)

mc = ModelCheckpoint('best_model.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)
model_LSTM.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

batch_size=64

history_LSTM = model_LSTM.fit(train_input,
                              train_target,
                              epochs=20,
                              batch_size=batch_size,
                              validation_data=(val_input, val_target),
                              callbacks=[es, mc],
                              verbose=1
                             )
results = model_LSTM.evaluate(x_test,  y_test, verbose=2)
print(results)
def visualize_train(train_history, param):
    # summarize history for accuracy
    plt.plot(train_history.history['accuracy'])
    plt.plot(train_history.history['val_accuracy'])
    plt.title('{}_accuracy'.format(param))
    plt.ylabel('accuracy')
    plt.xlabel('epoch')
    plt.legend(['train', 'val'], loc='upper left')
    plt.show()
    # summarize history for loss
    plt.plot(train_history.history['loss'])
    plt.plot(train_history.history['val_loss'])
    plt.title('{}_loss'.format(param))
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train', 'val'], loc='upper left')
    plt.show()
visualize_train(history_LSTM, param='unigram_8k_dim_8')<jupyter_output><empty_output><jupyter_text>5. 모델 성능 비교 평가비교 대상 - LSTM의 input x_data를 토크나이징한 SentencePiece 모델 - LSTM모델의 Word_vector_dimension<jupyter_code>def performance_test_spm(spm_model, vocab_size, word_vector_dim):
    s = spm.SentencePieceProcessor()
    s.Load("{}.model".format(spm_model)) 
    
    # document와 label을 나눠봅시다.
    x_train,x_train_word_index,x_train_index_word = sp_tokenize(s, train_data['document'], model_uni_8k)
    x_test,x_test_word_index, x_test_index_word = sp_tokenize(s, test_data['document'], model_uni_8k)

    # 현재 list 상태 ==> ndarray로 바꿔주기
    y_train = np.array(list(train_data['label']))
    y_test = np.array(list(test_data['label']))


    train_input, val_input, train_target, val_target = train_test_split(x_train, y_train, test_size=0.2, random_state=42)


    model_LSTM = keras.Sequential()
    model_LSTM.add(keras.layers.Embedding(vocab_size, 
                                     word_vector_dim,
                                        ))

    model_LSTM.add(keras.layers.LSTM(128))
    model_LSTM.add(keras.layers.Dense(1, activation='sigmoid'))  # 최종 출력은 긍정/부정을 나타내는 1dim 입니다.

    model_LSTM.summary()
    
    model_LSTM.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])
              
    epochs=10
    batch_size=64
    
    es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4)
    mc = ModelCheckpoint('best_model.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)
    
    
    history_LSTM = model_LSTM.fit(train_input,
                              train_target,
                              epochs=20,
                              batch_size=batch_size,
                              validation_data=(val_input, val_target),
                              callbacks=[es, mc],
                              verbose=1
                             )
    
    results = model_LSTM.evaluate(x_test,  y_test, verbose=2)
    print(results)
    
    return history_LSTM


## 모델에서 변수들의 이름이 섞여서 (train_target 과 x_train 등) 애를 많이 먹었다. 결국 이를 고쳐준 나의 승리!
history_unigram_8k_dim_8 = performance_test_spm('kor_spm_unigram_8k', vocab_size, word_vector_dim=8)
history_unigram_8k_dim_64 = performance_test_spm('kor_spm_unigram_8k', vocab_size, word_vector_dim=64)
visualize_train(history_unigram_8k_dim_8, param='unigram_8k_dim_8')
# 학습을 시킬 때 에러가 났다.

# history_bpe_16k_dim_32 = performance_test_spm('kor_spm_bpe_16k', vocab_size, word_vector_dim=32)


# 아래는 에러코드입니다.
# InvalidArgumentError: 2 root error(s) found.
#   (0) Invalid argument:  indices[60,129] = 15311 is not in [0, 8000)
# 	 [[node sequential_7/embedding_7/embedding_lookup (defined at <ipython-input-213-39b87160ea35>:44) ]]
# 	 [[sequential_7/embedding_7/embedding_lookup/_20]]
#   (1) Invalid argument:  indices[60,129] = 15311 is not in [0, 8000)
# 	 [[node sequential_7/embedding_7/embedding_lookup (defined at <ipython-input-213-39b87160ea35>:44) ]]
# 0 successful operations.
# 0 derived errors ignored. [Op:__inference_train_function_226789]

# Errors may have originated from an input operation.
# Input Source operations connected to node sequential_7/embedding_7/embedding_lookup:
#  sequential_7/embedding_7/embedding_lookup/225557 (defined at /opt/conda/lib/python3.7/contextlib.py:112)

# Input Source operations connected to node sequential_7/embedding_7/embedding_lookup:
#  sequential_7/embedding_7/embedding_lookup/225557 (defined at /opt/conda/lib/python3.7/contextlib.py:112)

# Function call stack:
# train_function -> train_function


visualize_train(history_unigram_8k_dim_64, param='unigram_8k_dim_64')
visualize_train(history_bpe_8k_dim_8, param='bpe_8k_dim_8')
# 왠지 모르지만 16k는 학습할 때 에러가 났다.

# visualize_train(history_bpe_16k_dim_64, param='bpe_8k_dim_64')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12266098421788639, 'fraction_numerical': 0.03312912033029233, 'mean_word_length': 3.7032571249608517, 'pattern_counts': {'":': 0, '<': 66, '<?xml version=': 0, '>': 77, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '49574', 'license': 'MIT', 'n_tokens_mistral': 15037, 'n_tokens_neox': 16417, 'n_words': 2752, 'path': 'GD_02_SP_movie_sentiment.ipynb', 'repo_name': 'peter1505/AIFFEL', 'size': '30034'}