Search what you want

Sunday, December 2, 2018

Sentiment Prediction with RNN, Pytorch

Sentimen analisis merupakan sebuah sistem yang dapat membantu manusia untuk mengetahui sebuah sentimen dari sebuah kalimat teks yang ada. Sistem ini dapat mengetahui apakah suatu kalimat positif ataupun negatif. Pada kesempatan kali ini akan dijabarkan salah satu cara, step-step, untuk melakukan sentimen analisis, mulai dari praprocessing hingga menggunaan model yang sudah dibuat.

>>>SOURCE CODE<<<

1. Pra-processing data

hal pertama yang harus dilakukan adalah mempersiapkan data untuk dapat dipelajari oleh sistem.

a. Load data
Untuk mengolah data, kita perlu untuk memindahkan seluruh datanya ke memory.
import numpy as np

# read data from text files
with open('data/reviews.txt', 'r') as f:
    reviews = f.read()
with open('data/labels.txt', 'r') as f:
    labels = f.read()

b. Cleaning dan Tokenize data
Cleaning data diperlukan pada setiap pra-processing data untuk machine learning. Hal ini di lakukan unutk membuat data lebih mudah di proses. Pada proses dibawah ini kita akan:
- mejadikan seluruh kata menjadi lowercase
- menghlangkan puctuation
- memisahkan kalimat berdasarkan '\n'
- menghitung seluruh kata unik dalam dataset

from string import punctuation

print(punctuation)

# get rid of punctuation
reviews = reviews.lower() # lowercase, standardize
all_text = ''.join([c for c in reviews if c not in punctuation])

# split by new lines and spaces
reviews_split = all_text.split('\n')
all_text = ' '.join(reviews_split)

# create a list of words
words = all_text.split()

c. Encoded data
Pada tahap ini kita akan menjadikan dataset yang ada agar dimengerti oleh komputer. komputer hanya mengerti angka-angka, atau melakukan perhitungan, komputer tidak mengetahui maksud dari kalimat manusia. Maka dari itu kita harus mengubah bentuk datanya ke angka.

Setiap kata akan dipasangkan ke angkan yang unik, lalu setiap kalimat akan diubah ke dalam representasi angka :
# feel free to use this import 
from collections import Counter

## Build a dictionary that maps words to integers
counters = Counter(words)
vocab = sorted(counters, key=counters.get, reverse=True)
vocab_to_int = {word:intgr for intgr, word in enumerate(vocab, 1) }
# print(vocab_to_int)
## use the dict to tokenize each review in reviews_split
## store the tokenized reviews in reviews_ints
reviews_ints = []
for review in reviews_split:
    reviews_ints.append([vocab_to_int[word] for word in review.split()])
# print(reviews_ints)



f. Remove outlier
Data train yang tidak bagus, akan mempengaruhi hasil dari model, termasuk data-data outlier. Pada kasus ini, data outlier ditandai oleh data, jumlah kata, yang terlalu pendek maupun yang terlalu panjang. Maka dari itu, harus kita proses terlebih dahulu. Proses yang akan dilalukan pada kasus ini adalah:
- menghapus data yang memiliki panjang kata < 1
- menghapus label data yang memiliki pangang kata pada datanya < 1

print('Number of reviews before removing outliers: ', len(reviews_ints))

## remove any reviews/labels with zero length from the reviews_ints list.
non_zero_index = [ii for ii, review in enumerate(reviews_ints) if len(review)!=0]

reviews_ints = [reviews_ints[ii] for ii in non_zero_index]
encoded_labels = np.array([encoded_labels[ii] for ii in non_zero_index])

print('Number of reviews after removing outliers: ', len(reviews_ints))


f. format data
Data pada data set yang ada, pasti tidak akan seragan dalam hal panjangnya, ada yang terlalu panjang dan ada yang sedikit pendek. Untuk data outlier, panjang kata < 1, sudah kita bersihkan pada tahap sebelumnya. Tetapi bagai mana dengan kalimat yang memiliki jumlah kata yang panjang. Untuk itu kita harus memprosesnya juga:
- tentukan panjang seqence (dalam kasus ini panjang yang baik adalah 200)
- potong data yang memiliki panjang lebih dari panjang maksimal sequence
- berikan padding pada bagian kiri dengan nilai 0, jika panjang data kurang dari panjang sequence

def pad_features(reviews_ints, seq_length):
    ''' Return features of review_ints, where each review is padded with 0's 
        or truncated to the input seq_length.
    '''
    ## implement function
    
    features=np.zeros((len(reviews_ints), seq_length), dtype=int)
    
    for i, review in enumerate(reviews_ints):
#         print(i, -len(review))
        features[i, -len(review):] = np.array(review)[:seq_length]
        
    return features


# Test your implementation!

seq_length = 200

features = pad_features(reviews_ints, seq_length=seq_length)

## test statements - do not change - ##
assert len(features)==len(reviews_ints), "Your features should have as many rows as reviews."
assert len(features[0])==seq_length, "Each feature row should contain seq_length values."

# print first 10 values of the first 30 batches 
print(features[:30,:10])

g. Split data menjadi data train, data validasi, data test
Kita sudah sampai pada tahap terakhir pra-processing data, yaitu membagi data yang ada untuk testing, validasi, dan juga testing. Kita akan membagi data dalam koposisi:
- training data : 80%
- validasi data : 10%
- testing data : 10%

pastikan data validasi yang kita sediakan layak untuk validasi selama proses training berlangsung. Data validasi yang kurang akan menjadikan hasil validasi selama training menjadi noisy dan juga tidak merepresentasikan hasil validasi yang informatif.

split_frac = 0.8
split_frac_test = 0.5
## split data into training, validation, and test data (features and labels, x and y) split_idx = int(len(features)*split_frac) train_x, remaining_x = features[:split_idx], features[split_idx:] train_y, remaining_y = encoded_labels[:split_idx], encoded_labels[split_idx:] test_idx = int(len(remaining_x)*split_frac_test) val_x, test_x = remaining_x[:test_idx], remaining_x[test_idx:] val_y, test_y = remaining_y[:test_idx], remaining_y[test_idx:] ## print out the shapes of your resultant feature data print("\t\t\tFeature Shapes:") print("Train set: \t\t{}".format(train_x.shape), "\nValidation set: \t{}".format(val_x.shape), "\nTest set: \t\t{}".format(test_x.shape))


2. Prepare for training

a. Data loader and batching
Proses ini dilakukan untuk memudahkan framework untuk membaca data yang ada. Sehingga data dapat dibaca dengan baik. Tahapan prosesnya sebagai berikut:
- tentukan bacth size yang diinginkan (dalam kasus ini sebesar 50)
- pindahkan data ke TensorDataset (unutk pytorch)
- pindahkan data hasil sebelumnya ke DataLoader (untuk pytorch)

import torch
from torch.utils.data import TensorDataset, DataLoader

# create Tensor datasets
train_data = TensorDataset(torch.from_numpy(train_x), torch.from_numpy(train_y))
valid_data = TensorDataset(torch.from_numpy(val_x), torch.from_numpy(val_y))
test_data = TensorDataset(torch.from_numpy(test_x), torch.from_numpy(test_y))

# dataloaders
batch_size = 50

# make sure the SHUFFLE your training data
train_loader = DataLoader(train_data, shuffle=True, batch_size=batch_size)
valid_loader = DataLoader(valid_data, shuffle=True, batch_size=batch_size)
test_loader = DataLoader(test_data, shuffle=True, batch_size=batch_size)

b. Membuat arsitektur RNN
Sampailah kita pada tahap ini. Tahap ini mengharuskan kita unutk membuat arsitektur dari RNN yang kita inginkan. Untuk mendesain Arsitektur untuk menghasilkan model yang baik, ada saran yang cukup bagus dari Andrej, kita dapat memplajarinya dari tulisannya disini, atau saya sudah salinkan ke salah satu postingan di blog saya ini.
Arsitektur yang digunakan pada kasus ini adalah:
- Algoritma : LSTM
- Dropout : 0.3
- Fully connected layer menggunakan aktivasi sigmoid

# First checking if GPU is available

train_on_gpu=torch.cuda.is_available()

if(train_on_gpu):

    print('Training on GPU.')

else:

    print('No GPU available, training on CPU.')


import torch.nn as nn

class SentimentRNN(nn.Module):
    """
    The RNN model that will be used to perform Sentiment analysis.
    """

    def __init__(self, vocab_size, output_size, embedding_dim, hidden_dim, n_layers, drop_prob=0.5):
        """
        Initialize the model by setting up the layers.
        """
        super(SentimentRNN, self).__init__()

        self.output_size = output_size
        self.n_layers = n_layers
        self.hidden_dim = hidden_dim
        
        # embedding and LSTM layers
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, hidden_dim, n_layers, 
                            dropout=drop_prob, batch_first=True)
        
        # dropout layer
        self.dropout = nn.Dropout(0.3)
        
        # linear and sigmoid layers
        self.fc = nn.Linear(hidden_dim, output_size)
        self.sig = nn.Sigmoid()
        

    def forward(self, x, hidden):
        """
        Perform a forward pass of our model on some input and hidden state.
        """
        batch_size = x.size(0)

        # embeddings and lstm_out
        embeds = self.embedding(x)
        lstm_out, hidden = self.lstm(embeds, hidden)
    
        # stack up lstm outputs
        lstm_out = lstm_out.contiguous().view(-1, self.hidden_dim)
        
        # dropout and fully-connected layer
        out = self.dropout(lstm_out)
        out = self.fc(out)
        # sigmoid function
        sig_out = self.sig(out)
        
        # reshape to be batch_size first
        sig_out = sig_out.view(batch_size, -1)
        sig_out = sig_out[:, -1] # get last batch of labels
        
        # return last sigmoid output and hidden state
        return sig_out, hidden
    
    
    def init_hidden(self, batch_size):
        ''' Initializes hidden state '''
        # Create two new tensors with sizes n_layers x batch_size x hidden_dim,
        # initialized to zero, for hidden state and cell state of LSTM
        weight = next(self.parameters()).data
        
        if (train_on_gpu):
            hidden = (weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().cuda(),
                  weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().cuda())
        else:
            hidden = (weight.new(self.n_layers, batch_size, self.hidden_dim).zero_(),
                      weight.new(self.n_layers, batch_size, self.hidden_dim).zero_())
        
        return hidden

kita akan menentukan output, hidden dan sebagainya
# Instantiate the model w/ hyperparams
vocab_size = len(vocab_to_int)+1 # +1 for the 0 padding + our word tokens
output_size = 1
embedding_dim = 400
hidden_dim = 256
n_layers = 2

net = SentimentRNN(vocab_size, output_size, embedding_dim, hidden_dim, n_layers)

print(net)

SentimentRNN(
  (embedding): Embedding(74073, 400)
  (lstm): LSTM(400, 256, num_layers=2, batch_first=True, dropout=0.5)
  (dropout): Dropout(p=0.3)
  (fc): Linear(in_features=256, out_features=1, bias=True)
  (sig): Sigmoid()
)

c. penentuan parameter training (training hyparameters)
beberapa parameter yang akan kita konfig adalah:

lr: Learning rate for our optimizer.
epochs: Number of times to iterate through the training dataset.
clip: The maximum gradient value to clip at (to prevent exploding gradients).

# loss and optimization functions
lr=0.001

criterion = nn.BCELoss()
optimizer = torch.optim.Adam(net.parameters(), lr=lr)

# training params

epochs = 4 # 3-4 is approx where I noticed the validation loss stop decreasing

counter = 0
print_every = 100
clip=5 # gradient clipping



3. Training model

a. train
 model to GPU, if available
if(train_on_gpu):
    net.cuda()

net.train()
# train for some number of epochs
for e in range(epochs):
    # initialize hidden state
    h = net.init_hidden(batch_size)

    # batch loop
    for inputs, labels in train_loader:
        counter += 1

        if(train_on_gpu):
            inputs, labels = inputs.cuda(), labels.cuda()

        # Creating new variables for the hidden state, otherwise
        # we'd backprop through the entire training history
        h = tuple([each.data for each in h])

        # zero accumulated gradients
        net.zero_grad()

        # get the output from the model
        output, h = net(inputs, h)

        # calculate the loss and perform backprop
        loss = criterion(output.squeeze(), labels.float())
        loss.backward()
        # `clip_grad_norm` helps prevent the exploding gradient problem in RNNs / LSTMs.
        nn.utils.clip_grad_norm_(net.parameters(), clip)
        optimizer.step()

        # loss stats
        if counter % print_every == 0:
            # Get validation loss
            val_h = net.init_hidden(batch_size)
            val_losses = []
            net.eval()
            for inputs, labels in valid_loader:

                # Creating new variables for the hidden state, otherwise
                # we'd backprop through the entire training history
                val_h = tuple([each.data for each in val_h])

                if(train_on_gpu):
                    inputs, labels = inputs.cuda(), labels.cuda()

                output, val_h = net(inputs, val_h)
                val_loss = criterion(output.squeeze(), labels.float())

                val_losses.append(val_loss.item())

            net.train()
            print("Epoch: {}/{}...".format(e+1, epochs),
                  "Step: {}...".format(counter),
                  "Loss: {:.6f}...".format(loss.item()),
                  "Val Loss: {:.6f}".format(np.mean(val_losses)))


b. testing

kita dapat melihat performa dari model dengan melakukan testing dengan data yang belum pernah sama sekali kita.


# Get test data loss and accuracy

test_losses = [] # track loss
num_correct = 0

# init hidden state
h = net.init_hidden(batch_size)

net.eval()
# iterate over test data
for inputs, labels in test_loader:

    # Creating new variables for the hidden state, otherwise
    # we'd backprop through the entire training history
    h = tuple([each.data for each in h])

    if(train_on_gpu):
        inputs, labels = inputs.cuda(), labels.cuda()
    
    # get predicted outputs
    output, h = net(inputs, h)
    
    # calculate loss
    test_loss = criterion(output.squeeze(), labels.float())
    test_losses.append(test_loss.item())
    
    # convert output probabilities to predicted class (0 or 1)
    pred = torch.round(output.squeeze())  # rounds to the nearest integer
    
    # compare predictions to true label
    correct_tensor = pred.eq(labels.float().view_as(pred))
    correct = np.squeeze(correct_tensor.numpy()) if not train_on_gpu else np.squeeze(correct_tensor.cpu().numpy())
    num_correct += np.sum(correct)


# -- stats! -- ##
# avg test loss
print("Test loss: {:.3f}".format(np.mean(test_losses)))

# accuracy over all test data
test_acc = num_correct/len(test_loader.dataset)
print("Test accuracy: {:.3f}".format(test_acc))

4. Menggunakan Model

# negative test review
test_review_neg = 'The worst movie I have seen; acting was terrible and I want my money back. This movie had bad acting and the dialogue was slow.'



from string import punctuation

def tokenize_review(test_review):
    test_review = test_review.lower() # lowercase
    # get rid of punctuation
    test_text = ''.join([c for c in test_review if c not in punctuation])

    # splitting by spaces
    test_words = test_text.split()

    # tokens
    test_ints = []
    test_ints.append([vocab_to_int[word] for word in test_words])

    return test_ints

# test code and generate tokenized review
test_ints = tokenize_review(test_review_neg)
print(test_ints)


# test sequence padding
seq_length=200
features = pad_features(test_ints, seq_length)

print(features)


# test conversion to tensor and pass into your model
feature_tensor = torch.from_numpy(features)
print(feature_tensor.size())

def predict(net, test_review, sequence_length=200):
    
    net.eval()
    
    # tokenize review
    test_ints = tokenize_review(test_review)
    
    # pad tokenized sequence
    seq_length=sequence_length
    features = pad_features(test_ints, seq_length)
    
    # convert to tensor to pass into your model
    feature_tensor = torch.from_numpy(features)
    
    batch_size = feature_tensor.size(0)
    
    # initialize hidden state
    h = net.init_hidden(batch_size)
    
    if(train_on_gpu):
        feature_tensor = feature_tensor.cuda()
    
    # get the output from the model
    output, h = net(feature_tensor, h)
    
    # convert output probabilities to predicted class (0 or 1)
    pred = torch.round(output.squeeze()) 
    # printing output value, before rounding
    print('Prediction value, pre-rounding: {:.6f}'.format(output.item()))
    
    # print custom response
    if(pred.item()==1):
        print("Positive review detected!")
    else:
        print("Negative review detected.")
        

# positive test review
test_review_pos = 'This movie had the best acting and the dialogue was so good. I loved it.'

# call function
seq_length=200 # good to use the length that was trained on

predict(net, test_review_neg, seq_length)







Monday, November 26, 2018

Recurrent Neural Network [pytorch Challenge] #Part4

Pada subbab kali ini kita diajarkan untuk mengetahui lebih jauh tentang RNN serta LSTM. RNN dibuat secara khusus untuk membatu sistem mempelajari sequence dari sebuah data. dimana sequence data di proses ke hidden state dan kemudian akan menghasilkan output sequence yang lainnya. Sedangkan Long Short Time Memory (LSTM) merupakan improvement dari RNN yang dapat mengingat data sebelumnya dan data dari waktu yang lama sebelumnya.

A. Konsep
1. Basic LSTM

secara umum ada beberapa komponen yang terdapat di arsitektur LSTM, yaitu:
input, yang terdiri dari data sebelumnya (Long Term Memory dan Short Term Memory) serta masukkan pada event sekarang. Terdapat juga komponen output, yaitu LTM baru dan STM baru. Didalamnya terdapat beberapa gate, pertama ada Forget Gate, Learn Gate, Remember Gate, dan juga Use Gate.

Secara matematik terdapat beberapa perhitungan yang berada di dalam setiap Gate, yang merupakan gabungan dari beberapa fungsi dan fungsi aktivasi-nya. Dibawah ini akan sedikit dijelaskan untuk setiap Gate yang ada.

a. Learn Gate 

(1)(2)
                 
(3)(4)

pada Gate ini inputan terdiri dari STM dan Event, kemudian masuk ke fungsi combine yang mengkombinasikan kedua iput tersebut. setelah itu masuk ke fungsi ignore yang menghilangkan sedikit ingetan pada LTM, lalu menghasilkan output. secara matematik dapat di terjemahkan kedalam grafik dibawah.



b. Forget Gate
pada Forget Gate, Gate ini yang menentukan informasi mana yang harus di hapus dan yang harus di pertahankan dari informasi yang diberikan oleh LTM.

secara matematik, dapat didefinisikan oleh fungsi diatas. Dimana input LTM harus dioperasikan dengan sebuah forget vactor. Forget vaktor itu dihasilkan oleh operasi dari STM dan Event.

c. Remember Gate
Gate ini di fungsikan untuk mengingat semua inputan dari semua inputan.

pada persamaan matematisnya, merupakan penjumlahan dari keluaran yang dihasilkan oleh Forgate Gate dan juga Learn Gate.

d. Use Gate
Tujuan pada gate ini yaitu untuk memprediksi suatu informasi inputan saat ini (Event) yang kemudian menghasikan prediksi yang diinginkan dengan membawa beberapa informasi tambahan dari ingatan sebelumnya. Secara fungsi matematis dapat ditulis dengan fungsi dibawah ini.


2. GRU, arsitektur RNN yang lainnya 


Pada arsitektur ini, singkatnya, Forget Gate dan Learn Gate disatukan menjadi Update Gate,  kemudian hasilnya dilanjutkan dengan proses di Combine Gate. Pada arsitektur ini hanya dihasilkan satu outpu saja.

3. Implementasi pada Pytorch
a. Code di pytorch (Code)

 import semua library yang dibutuhkan:

import torch
from torch import nn
import numpy as np
import matplotlib.pyplot as plt

buat arsitektur RNN yang diinginkan:

class RNN(nn.Module):
    def __init__(self, input_size, output_size, hidden_dim, n_layers):
        super(RNN, self).__init__()
        
        self.hidden_dim=hidden_dim

        # define an RNN with specified parameters
        # batch_first means that the first dim of the input and output will be the batch_size
        self.rnn = nn.RNN(input_size, hidden_dim, n_layers, batch_first=True)
        
        # last, fully-connected layer
        self.fc = nn.Linear(hidden_dim, output_size)

    def forward(self, x, hidden):
        # x (batch_size, seq_length, input_size)
        # hidden (n_layers, batch_size, hidden_dim)
        # r_out (batch_size, time_step, hidden_size)
        batch_size = x.size(0)
        
        # get RNN outputs
        r_out, hidden = self.rnn(x, hidden)
        # shape output to be (batch_size*seq_length, hidden_dim)
        r_out = r_out.view(-1, self.hidden_dim)  
        
        # get final output 
        output = self.fc(r_out)
        
        return output, hidden

Parameter method RNN adalah sebagai berikut:
- input_size    : ukuran dari parameter input
- hidden_dim : jumlah dari fitur pada output RNN dan jumlah pada hidden state
- n_layers       : jumlah layar pada RNN, umumnya 1-3 layar
- batch_first   : whether or not the input/output of the RNN will have the batch_size as the first dimension (batch_size, seq_length, hidden_dim)

buat instance dari kelas RNN:

# decide on hyperparameters
input_size=1 
output_size=1
hidden_dim=32
n_layers=1

# instantiate an RNN
rnn = RNN(input_size, output_size, hidden_dim, n_layers)
print(rnn)

definisikan Loss dan Optimizer kriteria:

# MSE loss and Adam optimizer with a learning rate of 0.01
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(rnn.parameters(), lr=0.01)

buat fungsi train:
# train the RNN
def train(rnn, n_steps, print_every):
    
    # initialize the hidden state
    hidden = None      
    
    for batch_i, step in enumerate(range(n_steps)):
        # defining the training data 
        time_steps = np.linspace(step * np.pi, (step+1)*np.pi, seq_length + 1)
        data = np.sin(time_steps)
        data.resize((seq_length + 1, 1)) # input_size=1

        x = data[:-1]
        y = data[1:]
        
        # convert data into Tensors
        x_tensor = torch.Tensor(x).unsqueeze(0) # unsqueeze gives a 1, batch_size dimension
        y_tensor = torch.Tensor(y)

        # outputs from the rnn
        prediction, hidden = rnn(x_tensor, hidden)

        ## Representing Memory ##
        # make a new variable for hidden and detach the hidden state from its history
        # this way, we don't backpropagate through the entire history
        hidden = hidden.data

        # calculate the loss
        loss = criterion(prediction, y_tensor)
        # zero gradients
        optimizer.zero_grad()
        # perform backprop and update weights
        loss.backward()
        optimizer.step()

        # display loss and predictions
        if batch_i%print_every == 0:        
            print('Loss: ', loss.item())
            plt.plot(time_steps[1:], x, 'r.') # input
            plt.plot(time_steps[1:], prediction.data.numpy().flatten(), 'b.') # predictions
            plt.show()
    
    return rnn


lakukan training:
# train the rnn and monitor results
n_steps = 75
print_every = 15

trained_rnn = train(rnn, n_steps, print_every)

Jika kita ingin mengganti arsitekturnya (RNN, LSTM, GRU) kita dapat melihat pada dokumentasi pada pytorch: https://pytorch.org/docs/stable/nn.html#recurrent-layers 


b. contoh kasus,  Character-Level LSTM in PyTorch
sekarang kita mencoba untuk membuat sebuah model dari kasus untuk melanjutkan karakter dari sebuah input karakter yang diberikan. oya sekarang akan dibahas sebuah contoh dalam kasus teks prosesing. Beberapa tahap yang harus dilakukan adalah sebagai berikut:

(1) Load dataset
# open text file and read in data as `text`
with open('data/anna.txt', 'r') as f:
    text = f.read()
(2) encode data
kita akan mengubah karakter ke angka, agar dapat di proses oleh komputer. Sekarang kita akan menentukan jumlah huruf yang unik yang ada dalam dataset. Setelah diketahui, masing-masing karakter akan di pasangkan dengan sebuah angka (Integer). Lalu seluruh teks di dataset kita uba menjadi angka seluruhnya.
# encode the text and map each character to an integer and vice versa

# we create two dictionaries:
# 1. int2char, which maps integers to characters
# 2. char2int, which maps characters to unique integers
chars = tuple(set(text))
int2char = dict(enumerate(chars))
char2int = {ch: ii for ii, ch in int2char.items()}

# encode the text
encoded = np.array([char2int[ch] for ch in text])
(3) mapping data ke dalam bentuk batch
untuk dapat men-trainingkan data, kita diharuskan membuat data menjadi batch. Nantinya data akan di proses batch per batch.

PEMBUATAN BATCH
(a) membuang beberapa teks, sehingga hanya memiliki mini-batch yang lengkap
setiap batch memiliki ukuran N x M karakter, dimana N merupakan jumlah batch (batch size) dan M merupakan panjang susunan (sequence length) atau M merupakan jumlah step dalam sebuah sequence.  Sedangkan total jumlah batch, K, yang kita dapat tentukan dari panjang array karakter dibagi dengan jumlah karakter per batch. Total karakter yang akan di proses yaitu N x M x K

(b) splid data karakter ke N batch
ubahlah dimensi data menjadi N x (M * K). kita bisa gunakan numpy.reshape

(c) sekarang kita memiliki array ini. dan kita bisa melakukakan iterasi untuk mendapatkan mini-batch
pada tahap ini gita bisa melakukan iterasi dari 0 hingga (M * K), dimana peningkatannya dilakukan sebanyak squence length.


(4) one-hot-encoding
Input dari arsitektur RNN yang akan digunakan megharuskan membentuk data dalam bentuk one-hot-encoding. Maka kita akan coba mengubah dataset kita dalam bentuk on-hot-encoding agar dapat di proses. Karakter dari setiap dataset yang ada akan diubah menjadi sebuah vektor yang hanya terdiri dan hanya dua jenis nilai, yaitu 1 dan 0, dua jenis karakter ya dan bukan hanya dua nilai ([1, 0]).

dibawah ini merupakan contoh hasil encoding dalam one-hot-encoding:
[[[0. 0. 0. 1. 0. 0. 0. 0.]
  [0. 0. 0. 0. 0. 1. 0. 0.]
  [0. 1. 0. 0. 0. 0. 0. 0.]]]

ada 3 karakter (row), dengan sequence length sebanyak 8 (column)
def one_hot_encode(arr, n_labels):
    
    # Initialize the the encoded array
    one_hot = np.zeros((np.multiply(*arr.shape), n_labels), dtype=np.float32)
    
    # Fill the appropriate elements with ones
    one_hot[np.arange(one_hot.shape[0]), arr.flatten()] = 1.
    
    # Finally reshape it to get back to the original array
    one_hot = one_hot.reshape((*arr.shape, n_labels))
    
    return one_hot

(5) Mendefinisikan Arsitektur dari RNN 
class CharRNN(nn.Module):
    
    def __init__(self, tokens, n_hidden=256, n_layers=2,
                               drop_prob=0.5, lr=0.001):
        super().__init__()
        self.drop_prob = drop_prob
        self.n_layers = n_layers
        self.n_hidden = n_hidden
        self.lr = lr
        
        # creating character dictionaries
        self.chars = tokens
        self.int2char = dict(enumerate(self.chars))
        self.char2int = {ch: ii for ii, ch in self.int2char.items()}
        
        ## TODO: define the LSTM
        self.lstm = nn.LSTM(len(self.chars), n_hidden, n_layers, 
                            dropout=drop_prob, batch_first=True)
        
        ## TODO: define a dropout layer
        self.dropout = nn.Dropout(drop_prob)
        
        ## TODO: define the final, fully-connected output layer
        self.fc = nn.Linear(n_hidden, len(self.chars))
      
    
    def forward(self, x, hidden):
        ''' Forward pass through the network. 
            These inputs are x, and the hidden/cell state `hidden`. '''
                
        ## TODO: Get the outputs and the new hidden state from the lstm
        r_output, hidden = self.lstm(x, hidden)
        
        ## TODO: pass through a dropout layer
        out = self.dropout(r_output)
        
        # Stack up LSTM outputs using view
        # you may need to use contiguous to reshape the output
        out = out.contiguous().view(-1, self.n_hidden)
        
        ## TODO: put x through the fully-connected layer
        out = self.fc(out)
        
        # return the final output and the hidden state
        return out, hidden
    
    
    def init_hidden(self, batch_size):
        ''' Initializes hidden state '''
        # Create two new tensors with sizes n_layers x batch_size x n_hidden,
        # initialized to zero, for hidden state and cell state of LSTM
        weight = next(self.parameters()).data
        
        if (train_on_gpu):
            hidden = (weight.new(self.n_layers, batch_size, self.n_hidden).zero_().cuda(),
                  weight.new(self.n_layers, batch_size, self.n_hidden).zero_().cuda())
        else:
            hidden = (weight.new(self.n_layers, batch_size, self.n_hidden).zero_(),
                      weight.new(self.n_layers, batch_size, self.n_hidden).zero_())
        
        return hidden
        
(6) buat method untuk training
def train(net, data, epochs=10, batch_size=10, seq_length=50, lr=0.001, clip=5, val_frac=0.1, print_every=10):
    ''' Training a network 
    
        Arguments
        ---------
        
        net: CharRNN network
        data: text data to train the network
        epochs: Number of epochs to train
        batch_size: Number of mini-sequences per mini-batch, aka batch size
        seq_length: Number of character steps per mini-batch
        lr: learning rate
        clip: gradient clipping
        val_frac: Fraction of data to hold out for validation
        print_every: Number of steps for printing training and validation loss
    
    '''
    net.train()
    
    opt = torch.optim.Adam(net.parameters(), lr=lr)
    criterion = nn.CrossEntropyLoss()
    
    # create training and validation data
    val_idx = int(len(data)*(1-val_frac))
    data, val_data = data[:val_idx], data[val_idx:]
    
    if(train_on_gpu):
        net.cuda()
    
    counter = 0
    n_chars = len(net.chars)
    for e in range(epochs):
        # initialize hidden state
        h = net.init_hidden(batch_size)
        
        for x, y in get_batches(data, batch_size, seq_length):
            counter += 1
            
            # One-hot encode our data and make them Torch tensors
            x = one_hot_encode(x, n_chars)
            inputs, targets = torch.from_numpy(x), torch.from_numpy(y)
            
            if(train_on_gpu):
                inputs, targets = inputs.cuda(), targets.cuda()

            # Creating new variables for the hidden state, otherwise
            # we'd backprop through the entire training history
            h = tuple([each.data for each in h])

            # zero accumulated gradients
            net.zero_grad()
            
            # get the output from the model
            output, h = net(inputs, h)
            
            # calculate the loss and perform backprop
            loss = criterion(output, targets.view(batch_size*seq_length))
            loss.backward()
            # `clip_grad_norm` helps prevent the exploding gradient problem in RNNs / LSTMs.
            nn.utils.clip_grad_norm_(net.parameters(), clip)
            opt.step()
            
            # loss stats
            if counter % print_every == 0:
                # Get validation loss
                val_h = net.init_hidden(batch_size)
                val_losses = []
                net.eval()
                for x, y in get_batches(val_data, batch_size, seq_length):
                    # One-hot encode our data and make them Torch tensors
                    x = one_hot_encode(x, n_chars)
                    x, y = torch.from_numpy(x), torch.from_numpy(y)
                    
                    # Creating new variables for the hidden state, otherwise
                    # we'd backprop through the entire training history
                    val_h = tuple([each.data for each in val_h])
                    
                    inputs, targets = x, y
                    if(train_on_gpu):
                        inputs, targets = inputs.cuda(), targets.cuda()

                    output, val_h = net(inputs, val_h)
                    val_loss = criterion(output, targets.view(batch_size*seq_length))
                
                    val_losses.append(val_loss.item())
                
                net.train() # reset to train mode after iterationg through validation data
                
                print("Epoch: {}/{}...".format(e+1, epochs),
                      "Step: {}...".format(counter),
                      "Loss: {:.4f}...".format(loss.item()),
                      "Val Loss: {:.4f}".format(np.mean(val_losses)))
# define and print the net
n_hidden=512
n_layers=2

net = CharRNN(chars, n_hidden, n_layers)
print(net)
batch_size = 128
seq_length = 100
n_epochs = 20 # start smaller if you are just testing initial behavior

# train the model
train(net, encoded, epochs=n_epochs, batch_size=batch_size, seq_length=seq_length, lr=0.001, print_every=10)
4. Tips dalam melakukan Training (referensi)
a. Monitoring Validation Loss vs. Training Loss
(1) jika training loss lebih kecil dari validation loss maka ini berarti network mengalami overfitting. Solusinya perkecil ukuran network atau naikkan dropout.
(2) jika training loss hampir sama dengan validation loss maka ini berarti network mengalami underfitting. Solusinya naikkan ukuran network, bisa saja jumlah layar nya maupun jumlah neuron pada setiap layarnya.
b. Perkiraan jumlah parameter
Dua parameter paling penting yang dapat mengontrol model adalah jumlan hidden dan jumlah layer. Terdapat saran unutk selalu menggunakan jumlah layer (n_layer) sebanyak 2/3. komponen hidden (n_hidden) dapat di atur dari berapa banyak data yang dimiliki.
(1) jumlah parameter selalu tercetak pada awal training.
(2) jumlah dataset sebanyak 1 MB, kemungkinnan memiliki 1 juta karakter.
parameter kedua (jumlah dataset) mungkin menjadi tricky untuk dilakukan pengaturan, contohnya:
(1) saya memiliki 100MB dataset, dan saya menggunakan parameter default (sekarang tercetak 150K parameter). Data yang saya miliki secara signifikan lebih besar (100 juta >> 0.15 juta), jadi menduga akan underfitting. Jadi saya pikir untuk menaikkan jumlah hidden komponennya (n_hidden).
(2) saya memiliki 10MB data, dan saya jalankan pada 10 juta parameter model, saya agak gugup dan terus memperhatikan validation loss. Jika lebih besar dari trainnig loss, maka saya akan menaikkan sedikit dropout, dan kita lihat apakah membatu validation loss?
c. Strategi mendapatkan model terbaik
(1) strategi untuk membuat model yang sangat bagus (jika anda tidak masalah dengan waktu komputasi) adalah membuat jaringan sebesar mungkin (sebesar kemampuan anda untuk menunggu waktu komputasinya). dan coba berbagai macan nilai dropout (antara 0.1). Validation loss yang terkecil merupakan model yang terbaik.
(2) sangat biasa di dalam Depp Learning, unutk menjalankan sekaligus dibeberapa parameter yang berbeda dalam sekali waku. akhirnya ambil model yang terbaik
(3) pemecahan data validasi dan data training. pastikan anda memiliki sejumlah data yang layak pada data validasi, jika tidak kinerja validasi akan noisy atau tidak terlalu informatif

Sunday, November 25, 2018

Convolutional Neural Network [pytorch Challenge] #Part3

Pada part ke 3 saya mencoba untuk menuliskan catatan saya saat belajar tentang CNN. Sebelum masuk ke Convolutional Neural Network kita akan coba membandingkan dengan sedikit materi pelajaran sebelumnya, yaitu Multi Layer Perceptron (MLP) yang telah kita bahas di Part 1 dan Part 2  dengan CNN ini.

A. Perbedaan MLP dan CNN
Pada MLP, part sebelumnya:
1. data yang digunakan cukup bagus, clean, dan terpusat
2. gambar angka dari tulisan tangan
3. database MINST
4. hanya menerima vektor sebagai input data
5. hanya menggunakan Fully connected layer

Pada CNN, part ini kita akan belajar:
1. data cukup bervariasi, noise, dan tidak tersusun terpusat
2. gambar objek benda dengan background
3. database ImageNet, CIFAR dll
4. input kernel dapat berupa matriks maupun vektor
5. menggunakan Sparsely connected layer dan Fully connected layer

B. Tahapan Pembuatan Model 

sepertinya hal yang biasa ketika kita ingin membuat sebuah model untuk sebuah kasus, mau ga mau kita harus mengikuti proses-proses ini.

C. Filter untuk deteksi garis
pada part kali ini data yang kita olah merupakan data gambar, dan akan banyak berhubungan dengan karakteristik dari sebuah gambar. Terdapat beberapa definisi-definisi yang ada pengolahan suatu gambar yang harus kita pelajari.

High frequency image: intensitas pencahayaan dari sebuah gambar yang berubah cukup signifikan
Low frequency image: intensitas pencahayaan dari sebuah gambar yang berubah tidak signifikan

frekuensi pencahayaan dari sebuah gambar kita dapat gunakan untuk mengetahui garis yang ada dalam gambar tersebut. Dari salah satu karakteristik ini kita dapat mendeteksi garis dalam sebuah gambar dengan menggunakan sebuah filter. High pass filter digunakan pada gambar yang memiliki High frequency brightness. Untuk itu kita membutuhkan sebuah ata beberapa kernel untuk itu.

Convolution kernels merupakan matiks dari angka-angka yang dapat memodifikasi sebuah gambar.


D. Arsitektur CNN

Arsitektur sederhana dari sebuah CNN dapat kita liat pada gambar diatas.
1. gambar akan diubah menjadi Convolutional Layer.
2. Convolutional layer yang terdiri dari beberapa matriks, banyaknya sebanyak kernel yang digunakan, banyaknya kernel akan disebut depth.
3. Dari Convolutional layer akan menjadi Pooling layer yang size width x height nya di ubah menjadi lebih kecil, agar meringankan komputasi. disisi lain depth dari poolong layer akan menjadi lebih besar.
4. Setelah sepasang proses diatas (Convolutional Layer dan Pooling Layer) kemudian akan diproses ke fully-connected layer yang kemudian akan digunakan untuk prediksi kelas sebuah gambar.


Gambar dibawah ini merupakan 4 kernel yang digunakan untuk membuat Convolutional Layer yang memiliki depth 4.

E. Stride and padding

Stride merupakan jauhnya kernel perpindah pada satu stepnya. Stride ini dapat digunakan untuk meeduksi besarnya width dan height daru sebuah matriks gambar.semakin besar stride maka akan semakin kedih hasil dari proses ini.


Sedangkan padding itu merupakan banyaknya colom atau row yang ditambahkan agar semua area gambar dapat diproses oleh besaran kernel yang telah diberikan.

Lalu bagaimana cara menentukan nilai stride dan padding? mudah saja, pada fungsi nn.MaxPool2d(kernel_size, stride).
1. Jadi untuk down-sample matriks dengan faktor 4 yaitu:
    - nn.MaxPool2d(2, 4)
    - nn.MaxPool2d(4, 4)
    - nn.MaxPool2d(n, 4)
2. Untuk menentukan padding dengan jumlah kernel:
    - kernel 7 = padding-nya adalah 2
    - kernel 3 = padding-nya adalah 1
    - kernel n = padding-nya adalah (n-1)/2  -> Ganjil


F. Pooling
Pooling pada CNN digunakan untuk melakukan reduksi dimensi pada sebuah convolutional layer. reduksi dimensi ini digunakan pada sebuah layer convolutional yang banya untuk menghindari overfitting. Secara umum satu Convolutional Layer memiliki satu pooling layer didepannya.


G. CNN in Pytorch
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0)
Arguments harus mengikuti argumen dibawah ini:
1. in_channels - Jumlah input (in depth), 3 untuk gambar RGB, untuk contoh.
2. out_channels - Jumlah output channel
3. kernel_size - Jumlah spesifik dari tinggi dan lebar (square) convolutional kernel.

Argumen tambahan yang dapat diatur:
4. stride - The stride of the convolution. If you don't specify anything, stride is set to 1.
5. padding - The border of 0's around an input array. If you don't specify anything, padding is set to 0.
self.pool = nn.MaxPool2d(kernel_size,stride)
Arguments harus mengikuti argumen dibawah ini:
1. kernel_size = Jumlah spesifik dari tinggi dan lebar (square) convolutional kernel.
2. stride = lebar step yang harus diambil

Contoh:
import torch.nn as nn
import torch.nn.functional as F

# define the CNN architecture
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # convolutional layer (sees 32x32x3 image tensor)
        self.conv1 = nn.Conv2d(3, 16, 3, padding=1)
        # convolutional layer (sees 16x16x16 tensor)
        self.conv2 = nn.Conv2d(16, 32, 3, padding=1)
        # convolutional layer (sees 8x8x32 tensor)
        self.conv3 = nn.Conv2d(32, 64, 3, padding=1)
        # max pooling layer
        self.pool = nn.MaxPool2d(2, 2)
        # linear layer (64 * 4 * 4 -> 500)
        self.fc1 = nn.Linear(64 * 4 * 4, 500)
        # linear layer (500 -> 10)
        self.fc2 = nn.Linear(500, 10)
        # dropout layer (p=0.25)
        self.dropout = nn.Dropout(0.25)

    def forward(self, x):
        # add sequence of convolutional and max pooling layers
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        # flatten image input
        x = x.view(-1, 64 * 4 * 4)
        # add dropout layer
        x = self.dropout(x)
        # add 1st hidden layer, with relu activation function
        x = F.relu(self.fc1(x))
        # add dropout layer
        x = self.dropout(x)
        # add 2nd hidden layer, with relu activation function
        x = self.fc2(x)
        return x

# create a complete CNN
model = Net()
print(model)

G. Data Augmentation
Jika kita hanya memiliki data yang tidak banyak dan hasil akurasi model dari data yang kita miliki tidak memuaskan kita. maka salah satu cara yang dapat kita lakukan yaitu menambah jumlah data train yang kita miliki. Tetapi untuk menambah data yang terlabeli membutuhkan effort yang tidak sedikit, maka dari itu Data Augmentation kita butuhkan.

Dalam kasus disini kita mengolah data gambar, maka dari itu kita dapat mengolah data-data yang ada, misalnya:
1. rotasi
2. translasi
3. scale size

H. Jenis-jenis CNN Arsitektur

(2012) AlexNet

(2014) VGG

(2015) ResNet