<jupyter_start><jupyter_text>data는 e9t(<NAME>)님께서 github에 공유해주신 네이버 영화평점 데이터를 사용하였습니다. https://github.com/e9t/nsmc<jupyter_code>from collections import defaultdict

import torch.nn as nn
import torch
from torch.autograd import Variable
import torch.nn.functional as F
import random
import numpy as np

# data를 읽어옴
def read_txt(path_to_file):
    txt_ls = []
    label_ls = []

    with open(path_to_file) as f:
        for i, line in enumerate(f.readlines()[1:]):
            id_num, txt, label = line.split('\t')
            txt_ls.append(txt)
            label_ls.append(int(label.replace('\n','')))
    return txt_ls, label_ls<jupyter_output><empty_output><jupyter_text>불러오기<jupyter_code># 데이터 불러오기
x_train, y_train = read_txt('../ratings_train.txt')
x_test, y_test = read_txt('../ratings_test.txt')
x_train[0]<jupyter_output><empty_output><jupyter_text>비어있는 리뷰 제거<jupyter_code>def remove_empty_review(X, Y):
    empty_idx_ls = []
    
    for idx, review in enumerate(X):
        if len(review) == 0:
            empty_idx_ls.append(idx)
    
    # idx 값이 큰 것부터 제거 (앞으로 밀리는 것을 방지)
    empty_idx_ls = sorted(empty_idx_ls, reverse = True)
    
    for empty_idx in empty_idx_ls:
        del X[empty_idx], Y[empty_idx]
    
    return X, Y
x_train, y_train = remove_empty_review(x_train, y_train)
x_test, y_test = remove_empty_review(x_test, y_test)
x_train[0]<jupyter_output><empty_output><jupyter_text>토큰 인덱싱 (token2idx)<jupyter_code># 단어에 대한 idx 부여
def convert_token_to_idx(token_ls):
    for tokens in token_ls:
        yield [token2idx[token] for token in tokens.split(' ')]
    return
token2idx = defaultdict(lambda : len(token2idx)) # token과 index를 매칭시켜주는 딕셔너리
pad = token2idx['<PAD>']  # pytorch Variable로 변환하기 위해, 문장의 길이를 맞춰주기 위한 padding 

x_train = list(convert_token_to_idx(x_train))
x_test = list(convert_token_to_idx(x_test))

idx2token = {val : key for key,val in token2idx.items()}<jupyter_output><empty_output><jupyter_text>인덱싱 결과 확인<jupyter_code>x_train[0]<jupyter_output><empty_output><jupyter_text>원본 텍스트로 변환 확인<jupyter_code>[idx2token[x] for x in x_train[0]]<jupyter_output><empty_output><jupyter_text>Add Padding<jupyter_code># Pytorch Variable로 변환하기 위해서는 모든 data의 길이(length)가 동일하여야 한다.
# 영화 리뷰는 길이가 제각각이므로, 길이를 맞춰주는 작업을 수행
# 짧은 문장에는 padding(공간을 채우기 위해 사용하는 더미)을 추가하고,
# 긴 문장은 짤라서 줄인다.
# Sequence Length를 맞추기 위한 padding
def add_padding(token_ls, max_len):
    for i, tokens in enumerate(token_ls):
        n_token = len(tokens)
        
        # 길이가 짧으면 padding을 추가
        if n_token < max_len:
            token_ls[i] += [pad] * (max_len - n_token) # 부족한 만큼 padding을 추가함
        
        # 길이가 길면, max_len에서 짜름
        elif n_token > max_len:
            token_ls[i] = tokens[:max_len]
    return token_ls
max_len = 30
x_train = add_padding(x_train, max_len)
x_test = add_padding(x_test, max_len)<jupyter_output><empty_output><jupyter_text>Padding 결과 확인<jupyter_code>' '.join([idx2token[x] for x in x_train[0]])<jupyter_output><empty_output><jupyter_text>Pytorch 모델 학습을 위해 Data의 type을 Variable 로 변환<jupyter_code># torch Variable로 변환
def convert_to_long_variable(w2i_ls):
    return Variable(torch.LongTensor(w2i_ls))
x_train = convert_to_long_variable(x_train)
x_test = convert_to_long_variable(x_test)

y_train = convert_to_long_variable(y_train)
y_test = convert_to_long_variable(y_test)
x_train[0]<jupyter_output><empty_output><jupyter_text>CNN with Pytorch [<NAME>, 2014, Convolutional Neural Networks for Sentence Classification](https://www.aclweb.org/anthology/D14-1181) ![](https://datawarrior.files.wordpress.com/2016/10/cnn.png?w=640) Pre-train된 Embedding은 사용하지 않았습니다.모든 embedding은 랜덤으로 초기화된 상태로 학습을 진행하였습니다. (Rand)hyper-paramter는 자의적인 기준에서 선정하였습니다<jupyter_code>class CNN_text(nn.Module):
    
    def __init__(self, n_words, embed_size, pad_index, hid_size, drop_rate, kernel_size_ls, num_filter, n_class):
        super(CNN_text, self).__init__()
        
        self.pad_index = pad_index              # 단어 embedding 과정에서 제외시킬 padding token
        self.embed_size = embed_size            # 임베딩 차원의 크기
        self.hid_size = hid_size                # 히든 레이어 갯수
        self.drop_rate = drop_rate              # 드롭아웃 비율
        self.num_filter = num_filter            # 필터의 갯수 
        self.kernel_size_ls = kernel_size_ls    # 각기 다른 필터 사이즈가 담긴 리스트
        self.num_kernel = len(kernel_size_ls)   # 필터 사이즈의 종류 수
        self.n_class = n_class                  # 카테고리 갯수
        
        self.embed = nn.Embedding(
            num_embeddings=n_words, 
            embedding_dim=embed_size,
            padding_idx=self.pad_index
        )
        
        
        # kernel size는 (n-gram, embed_size)이다.
        # 커널의 열(column)의 크기는 embed_size와 일치하므로, 단어 임베딩 벡터를 모두 커버한다.
        # 따라서, n의 row 크기를 갖는 커널은 한번에 n개의 단어를 커버하는 n-gram 커널이라고 볼 수 있다.
        self.convs = nn.ModuleList([nn.Conv2d(1, num_filter, (kernel_size, embed_size)) for kernel_size in kernel_size_ls])
        
        self.lin = nn.Sequential(
            nn.Linear(self.num_kernel*num_filter, hid_size), nn.ReLU(), nn.Dropout(drop_rate),
            nn.Linear(hid_size, n_class),
        )
        
    def forward(self, x):
        embed = self.embed(x) # batch_size x max_length x embed_size
        embed.unsqueeze_(1)       # batch_size, 1, max_length, embed_size : convolution을 위해 4D로 차원을 조절
        
        # convolution
        conved = [conv(embed).squeeze(3) for conv in self.convs] # [batch_size, num_filter, max_length -kernel_size +1]
        
        # max_pooling
        pooled = [F.max_pool1d(conv, (conv.size(2))).squeeze(2) for conv in conved] # [batch_size, num_kernel, num_filter]
            
        # dropout
        dropouted = [F.dropout(pool, self.drop_rate) for pool in pooled]
        
        # concatenate
        concated = torch.cat(dropouted, dim = 1) # [batch_size, num_kernel * num_filter]
        logit = self.lin(concated)
        
        return logit
        
params = {
    'n_words' : len(token2idx),        # 고유한 단어 토큰의 갯수
    'embed_size' : 128,                # 임베딩 차원의 크기
    'pad_index' : token2idx['<PAD>'],  # 패딩 토큰
    'hid_size' : 128,                  # 히든 레이어 갯수
    'drop_rate' : 0.5,                 # 드롭아웃 비율          (원문에서는 0.5를 사용)
    'kernel_size_ls' : [2,3,4,5],      # 커널 사이즈 리스트        (원문애서는 3,4,5를 사용)
    'num_filter' : 32,                 # 각 사이즈 별 커널 갯수 (원문에서는 100을 사용)
    'n_class' : 2,                  # 카테고리 갯수
}
model = CNN_text(**params)
model
len(list(model.parameters()))
epochs = 30
lr = 0.0003
batch_size = 10000

train_idx = np.arange(x_train.size(0))
test_idx = np.arange(x_test.size(0))
optimizer = torch.optim.Adam(model.parameters(),lr) # 원문에서는 Adadelta 알고리즘을 사용
criterion = nn.CrossEntropyLoss(reduction='sum')

loss_ls = []

for epoch in range(epochs):
    model.train()
    
    # input 데이터 순서 섞기
    random.shuffle(train_idx)
    x_train = x_train[train_idx]
    y_train = y_train[train_idx]
    train_loss = 0

    for start_idx, end_idx in zip(range(0, x_train.size(0), batch_size),
                                  range(batch_size, x_train.size(0)+1, batch_size)):
        
        x_batch = x_train[start_idx : end_idx]
        y_batch = y_train[start_idx : end_idx].long()
        
        scores = model(x_batch)
        predict = F.softmax(scores, dim=1).argmax(dim = 1)
        
        acc = (predict == y_batch).sum().item() / batch_size
        
        loss = criterion(scores, y_batch)
        train_loss += loss.item()
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
    print('Train epoch : %s,  loss : %s,  accuracy :%.3f'%(epoch+1, train_loss / batch_size, acc))
    print('=================================================================================================')
    
    loss_ls.append(train_loss)
    
    if (epoch+1) % 10 == 0:
        model.eval()
        scores = model(x_test)
        predict = F.softmax(scores, dim=1).argmax(dim = 1)
        
        acc = (predict == y_test.long()).sum().item() / y_test.size(0)
        loss = criterion(scores, y_test.long())
        
        print('*************************************************************************************************')
        print('*************************************************************************************************')
        print('Test Epoch : %s, Test Loss : %.03f , Test Accuracy : %.03f'%(epoch+1, loss.item()/y_test.size(0), acc))
        print('*************************************************************************************************')
        print('*************************************************************************************************')<jupyter_output>ERROR:root:Internal Python error in the inspect module.
Below is the traceback from this internal error.
{'fraction_non_alphanumeric': 0.16102857142857144, 'fraction_numerical': 0.01302857142857143, 'mean_word_length': 3.017906336088154, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '376613', 'license': 'MIT', 'n_tokens_mistral': 3660, 'n_tokens_neox': 3827, 'n_words': 656, 'path': 'NLP_sentiment_classification/CNN (Yoon Kim, 2014)/CNN.ipynb', 'repo_name': 'jazzrabbit/Liturmation', 'size': '8755'}