dersblog



Uzun Kısa-Vade Hafıza Ağları (Long Short-Term Memory Networks, LSTM)

Kendini tekrarlayan YSA (RNN) yapılarının içindeki gizli konum $h_t$ (önceki yazıda $s_t$) olarak bir zaman diliminden bir diğerine aktarılabiliyordu, ve bu sırada bir matris çarpımı üzerinden değişime uğrayabiliyordu. Böylece her zaman diliminde yeni görülen verinin "hafıza" olarak ta tanımlanabilen $h_t$'ye etkisi olabiliyordu. RNN dış dünya hakkındaki iç modelini böyle güncelliyordu.

Fakat RNN ile tarif edilen bu güncellemeye hiç bir sınır getirmedik. Biraz düşünürsek bu güncellemenin biraz kaotik bir hal alabileceğini görebiliriz [1]. Mesela bir filmi kare kare izleyerek filmde neler olduğunu tarif etmeye uğraşan bir RNN düşünelim. Bir karede bir karakterin ABD'de olduğunu düşünebilir, ama sonraki karede karakterin suşi yediğini görüyor ve Japonya'da olduğuna karar verebilir, sonra Panda ayısı görüyor ve karakteri kuzey kutbunda zannediyor.

Bu tarif edilen kaos enformasyonun çok hızlı etki ettiğini ve aynı hızda yokolduğuna işaret. Bu tür bir yapıda modelin uzun vadeli hafıza tutması oldukça zor. Bize gereken modelin sadece güncelleme yapması değil, güncelleme yapmayı da öğrenmesi. Ali adlı bir karakter film karesinde yoksa o kareler Ali hakkındaki bilgiyi güncellemek için kullanılmamalı, aynı şekilde Ayşe'nin içinde olmadığı kareler onun hakkındaki bilgiyi güncellemek için kullanılmamalı.

Çözüm için şöyle bir yaklaşım kullanabiliriz.

1) Bir "unutma" mekanizması ekle. Film seyrediyoruz, bir sahne bitiyor, o sahnenin hangi gün, saat kaçta, nerede olduğunu unutuyoruz. Fakat bir karakter o sahnede ölmüşse, bunu hatırlıyoruz. Modelin ne zaman hatırlayacağını, ne zaman unutacağını öğrenmesini istiyoruz (dikkat sadece belli bir şekilde unutması, hatırlaması değil, tüm bunları nasıl, ne zaman yapacağını öğrenmesi).

2) Bir belleğe yazma (zulaya atma?) mekanizması. Modelin yeni bir kare gördüğünde o karedeki bilginin kaydetmeye değer olup olmadığına karar vermesi lazım, ve bu öğrenilse iyi olur.

3) .. ki yeni bir girdi gelince model ihtiyacı olmadığı bilgiyi unutacak. Sonra girdinin hangi kısmının faydalı olduğuna karar verecek ve o kısmı uzun-vadeli hafızasına kaydedecek.

4) Bir odaklanma mekanizması. Uzun-vadeli hafızanın hangi kısmı sık kullanım gerekiriyor, işlem hafızası (working memory) hangisi, buna karar vermek.

Bize gereken bir uzun kısa-vade hafıza ağıdır, teknik ismiyle LSTM. RNN her zaman adımında hafızasını kontrolsüz bir şekilde güncelleyebiliyorken, bir LSTM hafızasını çok daha seçici, kararlı bir şekilde günceller, bunu yaparken spesifik öğrenme mekanizmaları kullanır ki bu mekanizma ona görülen bilginin hangi kısmının hatırlanmaya değer, hangisinin güncellenmesinin gerekli olduğunu, ve hangisinin daha fazla odaklanılmaya ihtiyaç duyduğunu belirler.

Matematiksel olarak $t$ anında bir $x_t$ girdisi alıyoruz, uzun-vadeli ve işlem hafızası $C_{t-1}$ ve $h_{t-1}$ bir önceki zaman diliminden bir önceki bu zamana aktarılıyor ve onları bir şekilde güncellemek istiyoruz. Bize gereken bir tür hatırlama geçidi (remember gate), bu elektronik devrelerdeki gibi bir geçit, 0 ile 1 arasında olacak $n$ tane sayı, bu sayı $n$ hafıza ögesinin ne kadar hatırlanacağını, yani ne kadar uzun-vadeli olup olmayacağını belirleyecek. 1 tut, 0 unut demek olacak.

Ufak bir YSA kullanarak bu geçidi öğrenebiliriz,

$$ f_t = \sigma (W_r x_t + U_r h_{t-1}) $$

Bu basit, sığ (derin olmayan) bir YSA, $\sigma$ sigmoid aktivasyonu. Sigmoid kullandık çünkü 0 ile 1 arasında çıktıya ihtiyacımız var. Şimdi girdiden öğreneceğimiz bilgiyi hesaplamamız lazım, bu bilgi uzun-vadeli hafızamız için bir aday olacak.

$$ C_t' = \phi(W_l x_t + U_l h_{t-1})$$

$\phi$ bir aktivasyon fonksiyonu, çoğunlukla $\tanh$ olarak seçilir.

Fakat bir adayı hafızamıza eklemeden önce hangi bölümlerinin kullanıma, kaydetmeye değer olduğunu öğrenmemiz gerekir. Web'de bir şey okurken kendi zihnimizde neler olduğunu düşünelim. Bir haber makalesi okuyoruz mesela, Trabzonspor'un hep kötüye gittiğini, hep yanlış tranferler yaptığını anlatan bir haber okuyoruz, ama bu haberi fenerbahce.org sitesinde okuyorsak o habere daha az önem verebiliriz.

$$ i_t = \sigma (W_s x_t + U_s h_{t-1}) $$

Şimdi tüm bu basamakları birleştirelim. İhtiyacımız olmayan hafızaları unuttuktan ve bilgilerin faydalı olabilecek kısımlarını sakladıktan sonra, elimize bir güncellenmiş uzun-vadeli bellek geçer,

$$ C_t = f_t \circ c_{t-1} \circ i_t \circ \tilde{C}_t $$

ki $\circ$ operasyonu her iki taraftaki değişkenin içindeki her ögenin birer birer çarpılması (element-wise multiplication) demek.

Sonra işlem hafızasını güncellememiz lazım, uzun-vade belleğimizi anlık işlem için faydalı olabilecek şekilde nasıl odaklarız, onu öğrenmek istiyoruz. O zaman bir odaklanma vektörü öğreniriz,

$$ o_t = \sigma (W_f x_t + U_f h_{t-1}) $$

O zaman işlem hafızamız

$$ h_t = o_t \circ \phi (C_t)$$

Yani odak değeri 1 olan öğelere tam dikkatimizi veriyoruz, 0 olanlara hiç dikkat etmiyoruz.

Kuşbakışı ile tüm resmi görelim, kendini tekrar eden LSTM yapısı alttaki gibi,

Sol ve sağdaki hücreler ortadakinin kopyası.

Kıyasla bir RNN'nin iç yapısı çok daha basittir,

LSTM resmindeki her birimin formülleriyle beraber teker teker tekrar üzerinden geçmek gerekirse [2] - ilk adım hücre bilgisinden neleri atacağımıza karar vermek. Bu karar unutma karar tabakası adı verilen bir sigmoid tabakasında veriliyor, tabaka $h_{t-1}$ ve $x_t$'ye bakıyor ve $C_{t-1}$ hücre konumundaki her değer için 0 ile 1 arasında bir sayı üretiyor. 1 değeri tamamen tut, 0 değeri tamamen unut anlamına geliyor (tüm $b$ değerleri yanlılık -bias- için).

Bir sonraki adım hücrede hangi yeni bilgiyi depolayacağımıza karar vermek. Bu kararın iki parçası var, önce girdi geçit tabakası (input gate layer) hangi değerleri güncelleyeceğimize karar veriyor, ardından bir $\tanh$ tabakası bir "aday vektörü" $\tilde{C}_t$ üretiyor, bu vektör, adı üzerinde, hücre konumuna eklenmeye aday bilgiler. Ardından bu iki vektör birleştirilip konumu güncellemek için yeni bir vektör yaratılıyor.

Sıra güncelleme iş bantına (update conveyor) geldi. Herhalde bu isim verilmiş çünkü hücrenin en üstünde direk soldan sağa giden bu ok bir tür fabrika iş bantı gibi, bir ana akım hattı. Bir kaç bilgi akımı bu ana kola giriyor. Bu iş bantının görevi hücrenin eski konum bilgisini güncellemek, $C_{t-1}$'i $C_t$ haline getirmek. Önceki adımlar ne yapılması gerektiğine karar vermişti, şimdi bu kararları eyleme geçirme zamanı. Eski konum bilgisini $f_t$ ile çarpıyoruz, böylece unutmak istediklerimizi unutuyoruz. Sonra $i_t * C_t'$'yi ekliyoruz, bunlar yeni konumu ne kadar güncellemek istediğimizi belirleyen ağırlıklarla ölçeklenmiş yeni aday değerler.

En son adım tahmin adımı, bu adımda artık çıktının ne olacağına karar veriyoruz. Çıktı mevcut hücre konumunu baz alacak, ama onun filtrelenmiş bir hali olacak. İlk önce bir sigmoid işleterek konumun hangi kısmının çıktıya verileceğini kararlaştırıyoruz. Ardından mevcut konum bilgisini bir $\tanh$'e veriyoruz, ki bu değerlerin $-1,+1$ arasında gelmesini sağlıyoruz, ve bu sonucu sigmoid'den gelen değer ile çarpıyoruz, ki sadece istediğimiz kısmın dışarı verilmesini sağlayalım.

Şimdi üstteki formülleri kullanarak TensorFlow ile sıfırdan bir LSTM kodlayalım. Amacımız daha önce RNN için gördüğümüz zaman serisini öğrenmek, bu seriyi 5'er 5'er okuyacağız, yani yanyana 5 seri öğesini okuyacağız, ve 6. seri değeri hedef değeri olacak, seri içinden bu şekilde örneklem yaparak bir ufak toptan eğitim seti yaratacağız. Sonra serinin hiç görmediğimiz "geleceğini" tahmin etmeye uğraşacağız.

import tensorflow as tf
import numpy as np
from pprint import pprint
import datetime
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

tf.reset_default_graph()
np.random.seed(1)
tf.set_random_seed(1)

LSTM_SIZE = 30
t_min, t_max = 0, 30
resolution = 0.1
sequence_length = 4
instruction_count = 1
epoch = 500

def f(t):
    return t * np.sin(t) / 3 + 2 * np.sin(t*5)

def next_batch(batch_size, n_steps):
    t0 = np.random.rand(batch_size, 1) * (t_max - t_min - n_steps * resolution)
    Ts = t0 + np.arange(0., n_steps + 1) * resolution
    ys = f(Ts)
    X = ys[:, :-1].reshape(-1, n_steps, 1)
    y = ys[:, 1:].reshape(-1, n_steps, 1)
    y = y[:,-1,:]
    y = y.flatten()
    print y.shape    
    return list(X),y

reference_input_data,reference_output_data = next_batch(400, sequence_length)

# verinin 1/4'u egitim gerisi test
NUM_EXAMPLES = len(reference_input_data) / 4 
test_input = reference_input_data[NUM_EXAMPLES:]
test_output = reference_output_data[NUM_EXAMPLES:] 
train_input = reference_input_data[:NUM_EXAMPLES]
train_output = reference_output_data[:NUM_EXAMPLES]

print train_input[1]
print train_output[1]

data = tf.placeholder(tf.float32, 
                      [None, sequence_length, instruction_count], 
                      name='data')

target = tf.transpose(tf.placeholder(tf.float32, [None], name='target'))

FEATURE_SIZE = 1 

def default_weights_and_bias():
    Weights = tf.Variable(tf.truncated_normal([LSTM_SIZE, 
                                               LSTM_SIZE + FEATURE_SIZE], 
                                               -0.2, 0.1))
    bias = tf.Variable(tf.constant(0.0, shape = [LSTM_SIZE, 1]))

    return Weights, bias

W_f, _ = default_weights_and_bias()

b_f = tf.Variable(tf.constant(1.0, shape = [LSTM_SIZE, 1]))

# Unutma tabakasi
def f_t(ht_minus_1_and_xt):
    return tf.sigmoid(tf.matmul(W_f, ht_minus_1_and_xt) + b_f)

W_i, b_i = default_weights_and_bias()

# Girdi gecidi tabakasi
def i_t(ht_minus_1_and_xt):
    return tf.sigmoid(tf.matmul(W_i, ht_minus_1_and_xt) + b_i)

W_C, b_c = default_weights_and_bias()

# is banti icin adaylar
def candidate_C_t(ht_minus_1_and_xt):
    return tf.tanh(tf.matmul(W_C, ht_minus_1_and_xt) + b_c)

def C_t(ht_minus_1_and_xt, Conveyor, CandConv):
    return f_t(ht_minus_1_and_xt) * Conveyor + i_t(ht_minus_1_and_xt) * CandConv

W_o, b_o = default_weights_and_bias()

# guncellenmis is banti
def h_t(ht_minus_1_and_xt, FinalConveyor):
    o_t = tf.sigmoid(tf.matmul(W_o, ht_minus_1_and_xt) + b_o)    
    return o_t * tf.tanh(FinalConveyor)

def lstm_cell(ht_minus_1_and_Conveyor, xt):
    ht_minus_1, Conveyor = ht_minus_1_and_Conveyor

    ht_minus_1_and_xt = tf.transpose(tf.concat([ht_minus_1, xt], 1))

    CandidateConveyor = candidate_C_t(ht_minus_1_and_xt)

    FinalConveyor = C_t(ht_minus_1_and_xt, Conveyor, CandidateConveyor)

    lstm_prediction = tf.transpose(h_t(ht_minus_1_and_xt, FinalConveyor))

    return(lstm_prediction, FinalConveyor)

data_length = tf.shape(data)[0]

def lstm_loop(last_lstm_prediction, last_state, step):
    lstm_prediction, state = lstm_cell((last_lstm_prediction, last_state),
                                       data[:, step, :])
    return lstm_prediction, state, tf.add(step, 1)

initial_Conveyor = tf.zeros([LSTM_SIZE, data_length])

initial_prediction = tf.zeros([data_length, LSTM_SIZE])

timesteps = sequence_length

for_each_time_step = lambda a, b, step: tf.less(step, timesteps)

arg = (initial_prediction, initial_Conveyor, 0)
lstm_prediction, lstm_state, _ = tf.while_loop(for_each_time_step,
                                               lstm_loop, arg,
                                               parallel_iterations=32)

weight = tf.Variable(tf.truncated_normal([LSTM_SIZE, 1]))

bias = tf.Variable(tf.constant(0.0, shape=[1]))

prediction = tf.matmul(lstm_prediction, weight) + bias

with tf.name_scope('mean_square_error'):
    mean_square_error = tf.reduce_sum(tf.square(tf.subtract(target, tf.unstack(prediction, axis = 1))))

tf.summary.scalar('mean_square_error', mean_square_error)

optimizer = tf.train.AdamOptimizer()

minimize = optimizer.minimize(mean_square_error)

mistakes = tf.not_equal(target, tf.round(tf.unstack(prediction, axis = 1)))

sess = tf.InteractiveSession()

date = str(datetime.datetime.now())

init_op = tf.global_variables_initializer()

saver = tf.train.Saver() 

sess.run(init_op)

for i in range(epoch):
    if (i + 1) % 20 == 0:
        mean_squ_err = sess.run(mean_square_error, {data: test_input, target: test_output})
        print('Epoch {:4d} | mean squ error {: 3.1f}'.format(i + 1, mean_squ_err))

    sess.run(minimize,{data: train_input, target: train_output})
(400,)
[[ 4.00159218]
 [ 4.4298434 ]
 [ 4.67217731]
 [ 4.52697138]]
3.87853505042
Epoch   20 | mean squ error  3181.8
Epoch   40 | mean squ error  1860.6
Epoch   60 | mean squ error  1307.7
Epoch   80 | mean squ error  903.1
Epoch  100 | mean squ error  628.8
Epoch  120 | mean squ error  477.8
Epoch  140 | mean squ error  390.5
Epoch  160 | mean squ error  323.5
Epoch  180 | mean squ error  271.9
Epoch  200 | mean squ error  233.8
Epoch  220 | mean squ error  204.4
Epoch  240 | mean squ error  180.8
Epoch  260 | mean squ error  161.2
Epoch  280 | mean squ error  144.9
Epoch  300 | mean squ error  131.7
Epoch  320 | mean squ error  121.4
Epoch  340 | mean squ error  113.0
Epoch  360 | mean squ error  105.7
Epoch  380 | mean squ error  99.0
Epoch  400 | mean squ error  92.6
Epoch  420 | mean squ error  86.6
Epoch  440 | mean squ error  80.9
Epoch  460 | mean squ error  75.4
Epoch  480 | mean squ error  70.1
Epoch  500 | mean squ error  64.9

Tahminleri üretirken eğitim verisinin en sonundaki sequence_length kadar öğeyi alıp sonraki 1 değeri üretiyoruz, bu değeri alıp tahmin için kullanılan biraz önce kullandığımız sequence_length kadar değerin sonuna ekleyip son sequence_length değer üzerinden tekrar bir tahmin üretiyoruz, böyle gidiyor, ve bu şekilde serinin hiç görmediğimiz kısmını tahmin ediyoruz.

def f(t):
    return t * np.sin(t) / 3 + 2 * np.sin(t*5)

t = np.linspace(t_min, t_max, int((t_max - t_min) / resolution))
y = f(t)

newx = list(t[-sequence_length:])
newy = list(y[-sequence_length:])

for i in range(40): # bu kadar daha uret
   tst_input = np.array(newy[-sequence_length:]).reshape(sequence_length,1)   
   res = sess.run(prediction, { data: [ tst_input ]  } )
   newy.append(res)
   newx.append(t_max + (i*resolution))

plt.plot(t,y)
plt.plot(newx,newy,'g')
plt.savefig('lstm_01.png')

Yeşil renkli kısım tahmin. Fena değil.

Zaman Serisi Sınıflandırmak

Şimdi TF'in kendi LSTM çağrısını kullanarak zaman serisi sınıflandırması yapalım. İki farklı sınıfa ait olan zaman serisi verimiz var [3], mikroelektronik yarı-iletken üretiminden gelen bir veri, fabrikasyon sırasında algılayıcılar bu iki türlü seriyi kaydediyor, onları ayırtetmek bizim görevimiz. Okuma yöntemi kodlayalım,

import pandas as pd, zipfile
import tensorflow as tf
from tensorflow.contrib import rnn

learning_rate = 0.001
training_iters = 100000
batch_size = 25
display_step = 10

n_input = 1 
n_steps = 152 
n_hidden = 128 
n_classes = 2

with zipfile.ZipFile('wafer.zip', 'r') as z:
      df_train =  pd.read_csv(z.open('Wafer/wafer_TRAIN.txt'),header=None)
      df_test =  pd.read_csv(z.open('Wafer/wafer_TEST.txt'),header=None)

def minibatches(batch_size,input="train"):
      df = None
      if input=="train": df=df_train
      if input=="test": df=df_test
      df = np.array(df)
      for i in range(len(df)):
            batch_x = []; batch_y = []
            for j in range(batch_size):
                  batch_x.append(list(df[i,1:]))
                  batch_y.append([int(df[i,0]==-1), int(df[i,0]==1) ])
            batch_x = np.array(batch_x).reshape(batch_size,n_steps,1)
            batch_y = np.array(batch_y).reshape(batch_size,2)
            yield batch_x, batch_y                  

TF hesap çizitini kodlayalım ve eğitelim,

def reset_graph(seed=42):
    tf.reset_default_graph()
    tf.set_random_seed(seed)
    np.random.seed(seed)

reset_graph()

x = tf.placeholder("float", [None, n_steps, n_input])
y = tf.placeholder("float", [None, n_classes])

weights = {
    'out': tf.Variable(tf.random_normal([n_hidden, n_classes]))
}
biases = {
    'out': tf.Variable(tf.random_normal([n_classes]))
}

def LSTM(x, weights, biases):
    x = tf.unstack(x, n_steps, 1)
    lstm_cell = rnn.BasicLSTMCell(n_hidden)
    outputs, states = rnn.static_rnn(lstm_cell, x, dtype=tf.float32)
    return tf.matmul(outputs[-1], weights['out']) + biases['out']

pred = LSTM(x, weights, biases)

correct_pred = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
new_pred = tf.argmax(y,1)

print 'cost'
scf = tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y)
cost = tf.reduce_mean(scf)
print 'optimizer'
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)

init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    step = 1
    # Keep training until reach max iterations
    b_it = minibatches(batch_size)
    while step < int(1000 / batch_size):
          batch_x, batch_y = next(b_it)
          sess.run(optimizer, feed_dict={x: batch_x, y: batch_y})
          if step % display_step == 0:
                # Calculate batch accuracy
                acc = sess.run(accuracy, feed_dict={x: batch_x, y: batch_y})
                # Calculate batch loss
                loss = sess.run(cost, feed_dict={x: batch_x, y: batch_y})
                print("Iter " + str(step) + ", Minibatch Loss= " + \
                      "{:.6f}".format(loss) + ", Training Accuracy= " + \
                      "{:.5f}".format(acc))
          step += 1

    print("Optimization Finished!")
cost
optimizer
Iter 10, Minibatch Loss= 1.847300, Training Accuracy= 0.00000
Iter 20, Minibatch Loss= 0.049264, Training Accuracy= 1.00000
Iter 30, Minibatch Loss= 0.176535, Training Accuracy= 1.00000
Optimization Finished!
saver = tf.train.Saver()
from sklearn import metrics
real = []
pred = []
with tf.Session() as sess:
    for batch_x, batch_y in minibatches(1,input="test"):
      res = sess.run(new_pred, feed_dict={x: batch_x, y: batch_y})
      pred.append(res[0])
      real.append(np.argmax(batch_y[0]))
    fpr, tpr, thresholds = metrics.roc_curve(np.array(real), np.array(pred))
    print 'AUC', metrics.auc(fpr, tpr)      
AUC 1.0

Sonuç yüzde 100.

Kaynaklar

[1] Chen, Exploring LSTMs: Understanding Basics (Part One), https://www.topbots.com/exploring-lstm-tutorial-part-1-recurrent-neural-network-deep-learning/

[2] Shell, Do It Yourself LSTM with TensorFlow, https://chrisschell.de/2017/07/10/do_it_yourself_lstm_with_tensorflow.html

[3] Olszewski, Wafer Dataset, http://timeseriesclassification.com/description.php?Dataset=Wafer

[4] Olah, Understanding LSTM Networks, https://colah.github.io/posts/2015-08-Understanding-LSTMs


Yukarı