Recurrent Neural Networks dengan Attention Mechanism ™

Alfan Farizki Wicaksono

Information Retrieval Lab.
Fakultas Ilmu Komputer, Universitas Indonesia

Tutorial ini hanya ditujukan jika Anda sudah mengikuti tutorial RNNs awal. Potongan kode yang ada di halaman ini hanyalah tambahan kode saja dari kode yang ada di tutorial sebelumnya.

Pada kuliah sebelumnya, proses komputasi yang terjadi pada Recurrent Neural Networks (RNNs) adalah sebagai berikut.

S_t = tanh(W^{(xh)} X_t + W^{(hh)} S_{t-1} + b_s)
P_t = W^{(hy)} S_t + b_p

Sekarang, kita akan tambahkan Attention Mechanism pada bagian output RNNs sehingga dalam penentuan label dari sebuah input kalimat, masing-masing kata mempunyai kontribusi (bobot attention) yang berbeda-beda. Kita akan coba mengimplementasikan model attention yang mirip dengan Raffel dan Ellis [1] (attention pada bagian ujung sequence, tanpa konteks). Secara matematis, persamaan komputasi RNNs di atas ditambahkan dengan persamaan berikut:

E_t = A P_t + b_a
\alpha_t = softmax(E_t) = \frac{exp(E_t)}{\sum_{k=1}^{T} exp(E_k)}
y' = \sum_{t=1}^{T} \alpha_t P_t

Pertama, tambahkan potongan kode berikut di dalam fungsi add_variables(.) yang sudah Anda implementasikan sebelumnya.


# attention layer
with tf.variable_scope('attention'):
    A = tf.get_variable('A', [output_size, 1])
    ba = tf.get_variable('ba', [1], initializer=tf.constant_initializer(0.0))

Lalu, Anda perlu mendefinisikan fungsi yang akan membungkus komputasi dari attention layer.


# attention layer
# rnn_outputs: bobot attetion & tensor 2D (num_steps,output_size)
def attention(rnn_outputs):
    with tf.variable_scope('attention', reuse=True):
        A = tf.get_variable('A', [output_size, 1])
        ba = tf.get_variable('ba', [1], initializer=tf.constant_initializer(0.0))

        #hitung weights softmax, setelah dikali matrix, lalu di softmax pada axis = 0
        #karena inputnya adalah tensor 2D (num_steps, 1)
        #weights adalah tensor 2D (num_steps, 1) yang berisi bobot untuk masing-masing posisi timesteps
        #[[0.1], [0.15], ..., [0.3]] -> total harus 1
        weights = tf.nn.softmax(tf.matmul(rnn_outputs, A) + ba, dim = 0)

        # kali bobot ke output
        weighted_outputs = weights * rnn_outputs

        return weights, weighted_outputs

Kemudian, fungsi inference(.) yang sudah diimplementasikan sebelumnya perlu di-update menjadi sebagai berikut.


# sentence di parameter adalah list of word index, contoh: [3, 0, 4, 45, 3, ...]
# membangun computational graph untuk satu kalimat, dari input hingga output
def inference(sentence):

    # sekarang, sentence adalah tensor 2D (num_steps, embed_size)
    sentence = embed_lookup(sentence)

    # layer RNN
    rnn_outputs = rnn(sentence)

    # beri bobot attention
    _, rnn_outputs = attention(rnn_outputs)

    # Average Pooling
    #avg_outs = avg_pool(rnn_outputs)

    # Sum Poooling
    sum_outs = sum_pool(rnn_outputs)

    # Terakhir, Logreg Classifier
    logreg_outs = logreg(sum_outs)

    return logreg_outs

Kemudian, kita juga perlu membuat fungsi yang mengembalikan bobot attention untuk setiap kata ketika kita sedang mengklasifikasikan sebuah kalimat.


# fungsi untuk melibat bobot attention setiap kata dari sebuah kalimat
def see_weights(sentence):
    with tf.Graph().as_default(), tf.Session() as sess:
        # Restore trainable parameters
        add_variables()
        saver = tf.train.Saver()
        saver.restore(sess, "rnn.model")

        sentence = embed_lookup(sentence)
        rnn_outputs = rnn(sentence)
        weights, _ = attention(rnn_outputs)

        print (sess.run(weights))

Terakhir, kita coba training sekali lagi, lalu coba klasifikasi beberapa kalimat. Untuk beberapa kalimat yang lain, kita coba lihat bobot attention-nya untuk melihat siapa yang paling berpengaruh dalam penentuan label kelas tersebut.


# prediksi

# dari training data
predict(v.to_idx(['laptop', 'suka', 'banget']))
predict(v.to_idx(['film', 'buruk']))

# instance baru
predict(v.to_idx(['suka', 'laptop', 'dan', 'bagus']))
see_weights(v.to_idx(['suka', 'laptop', 'dan', 'bagus']))

predict(v.to_idx(['film', 'mengecewakan']))
see_weights(v.to_idx(['film', 'mengecewakan']))

predict(v.to_idx(['film', 'bagus']))
see_weights(v.to_idx(['film', 'bagus']))

[1] Colin Raffel, Daniel P. W. Ellis, Feed Forwar Networks with Attention Can Solve Some Long-Term Memory Problems, Workshop track - ICLR 2016