Langkah 4: Build, Latih, dan Evaluasi Model Anda

Di bagian ini, kita akan bekerja untuk membangun, melatih, dan mengevaluasi model. Pada Langkah 3, kita memilih untuk menggunakan model n-gram atau model urutan, menggunakan rasio S/W. Sekarang, saatnya untuk menulis algoritma klasifikasi kita dan melatihnya. Kita akan menggunakan TensorFlow dengan tf.keras API untuk ini.

Membangun model machine learning dengan Keras berkaitan dengan menyusun lapisan, elemen penyusun pemrosesan data, seperti saat kita merakit batu bata Lego. Lapisan ini memungkinkan kita menentukan urutan transformasi yang ingin dilakukan pada input. Karena algoritma pembelajaran kami mengambil satu input teks dan menghasilkan satu klasifikasi, kita dapat membuat tumpukan linear lapisan menggunakan Model Sekuensial API.

Tumpukan linear lapisan

Gambar 9: Tumpukan linear lapisan

Lapisan input dan lapisan perantara akan dibuat secara berbeda, bergantung pada apakah kita membangun model n-gram atau urutan. Namun, apa pun jenis modelnya, lapisan terakhir untuk masalah tertentu akan sama.

Membangun Lapisan Terakhir

Jika hanya memiliki 2 class (klasifikasi biner), model akan menghasilkan skor probabilitas tunggal. Misalnya, menghasilkan output 0.2 untuk contoh input tertentu berarti “20% keyakinan bahwa sampel ini berada di class pertama (class 1), 80% bahwa sampel ini berada di kelas kedua (class 0).” Untuk menghasilkan skor probabilitas tersebut, fungsi aktivasi lapisan terakhir harus berupa fungsi sigmoid, dan fungsi kerugian yang digunakan untuk melatih model silang. (Lihat Gambar 10, kiri).

Jika ada lebih dari 2 class (klasifikasi kelas multi-class), model kita harus menghasilkan satu skor probabilitas per class. Jumlah skor ini harus 1. Misalnya, menghasilkan output {0: 0.2, 1: 0.7, 2: 0.1} berarti “20% keyakinan bahwa sampel ini berada di kelas 0, 70% bahwa sampel ini berada di kelas 1, dan 10% berada di kelas 2”. Untuk menghasilkan skor ini, fungsi aktivasi lapisan terakhir harus berupa softmax, dan fungsi kerugian yang digunakan untuk melatih model harus berupa entropi silang kategoris. (Lihat Gambar 10, kanan).

Lapisan terakhir

Gambar 10: Lapisan terakhir

Kode berikut menentukan fungsi yang menggunakan jumlah class sebagai input, dan menghasilkan jumlah unit lapisan yang sesuai (1 unit untuk klasifikasi biner; jika tidak, 1 unit untuk setiap class) dan fungsi aktivasi yang sesuai:

def _get_last_layer_units_and_activation(num_classes):
    """Gets the # units and activation function for the last network layer.

    # Arguments
        num_classes: int, number of classes.

    # Returns
        units, activation values.
    """
    if num_classes == 2:
        activation = 'sigmoid'
        units = 1
    else:
        activation = 'softmax'
        units = num_classes
    return units, activation

Dua bagian berikut akan memandu pembuatan lapisan model yang tersisa untuk model n-gram dan model urutan.

Jika rasio S/W kecil, kami menemukan bahwa model n-gram berperforma lebih baik daripada model urutan. Model urutan akan lebih baik ketika ada sejumlah besar vektor yang kecil dan padat. Hal ini karena hubungan embedding dipelajari di ruang padat, dan ini paling baik terjadi pada banyak sampel.

Membangun model n-gram [Opsi A]

Kami menyebut model yang memproses token secara independen (tidak memperhitungkan urutan kata) sebagai model n-gram. Perceptron multi-lapisan sederhana (termasuk regresi logistik mesin penguat gradien, dan model mendukung mesin vektor) semuanya termasuk dalam kategori ini; semuanya tidak dapat menggunakan informasi apa pun tentang pengurutan teks.

Kami membandingkan performa beberapa model n-gram yang disebutkan di atas dan mengamati bahwa perceptron multi-lapisan (MLP) biasanya berperforma lebih baik daripada opsi lainnya. MLP mudah untuk didefinisikan dan dipahami, memberikan akurasi yang baik, dan memerlukan komputasi yang relatif sedikit.

Kode berikut menentukan model MLP dua lapis di tf.keras, dengan menambahkan beberapa Lapisan pelacur untuk regularisasi guna mencegah overfitting ke sampel pelatihan.

from tensorflow.python.keras import models
from tensorflow.python.keras.layers import Dense
from tensorflow.python.keras.layers import Dropout

def mlp_model(layers, units, dropout_rate, input_shape, num_classes):
    """Creates an instance of a multi-layer perceptron model.

    # Arguments
        layers: int, number of `Dense` layers in the model.
        units: int, output dimension of the layers.
        dropout_rate: float, percentage of input to drop at Dropout layers.
        input_shape: tuple, shape of input to the model.
        num_classes: int, number of output classes.

    # Returns
        An MLP model instance.
    """
    op_units, op_activation = _get_last_layer_units_and_activation(num_classes)
    model = models.Sequential()
    model.add(Dropout(rate=dropout_rate, input_shape=input_shape))

    for _ in range(layers-1):
        model.add(Dense(units=units, activation='relu'))
        model.add(Dropout(rate=dropout_rate))

    model.add(Dense(units=op_units, activation=op_activation))
    return model

Model urutan build [Opsi B]

Kami menyebut model yang dapat belajar dari keterkaitan token sebagai model urutan. Ini termasuk class model CNN dan RNN. Data telah diproses sebelumnya sebagai vektor urutan untuk model ini.

Model urutan umumnya memiliki jumlah parameter yang lebih besar untuk dipelajari. Lapisan pertama dalam model ini adalah lapisan embedding, yang mempelajari hubungan antara kata-kata dalam ruang vektor padat. Mempelajari hubungan kata akan berfungsi paling baik jika dibandingkan dengan banyak sampel.

Kata dalam {i>dataset<i} tertentu kemungkinan besar tidak unik untuk {i>dataset<i} tersebut. Dengan demikian, kita dapat mempelajari hubungan antara kata-kata dalam set data menggunakan set data lain. Untuk melakukannya, kita dapat mentransfer embedding yang dipelajari dari set data lain ke lapisan embedding. Embeddings ini disebut sebagai sematan terlatih. Dengan penyematan terlatih, model dapat memulai lebih awal dalam proses pembelajaran.

Ada penyematan terlatih yang tersedia dan telah dilatih menggunakan korpora besar, seperti GloVe. GloVe telah dilatih dengan berbagai korpora (terutama Wikipedia). Kami menguji pelatihan model urutan menggunakan versi embedding GloVe dan mengamati bahwa jika kita membekukan bobot embedding yang telah dilatih sebelumnya dan melatih bagian jaringan lainnya, model tersebut tidak berperforma baik. Ini bisa jadi karena konteks pelatihan lapisan embedding mungkin berbeda dengan konteks saat kita menggunakannya.

Penyematan GloVe yang dilatih pada data Wikipedia mungkin tidak selaras dengan pola bahasa dalam set data IMDb kami. Hubungan yang disimpulkan mungkin memerlukan beberapa pembaruan—yaitu, bobot embedding mungkin memerlukan penyesuaian kontekstual. Kita melakukannya dalam dua tahap:

  1. Pada proses pertama, dengan bobot lapisan embedding dibekukan, kami mengizinkan seluruh jaringan untuk mempelajarinya. Pada akhir proses ini, bobot model akan mencapai status yang jauh lebih baik daripada nilai yang tidak diinisialisasi. Untuk operasi kedua, kita mengizinkan lapisan embedding juga belajar, sehingga melakukan penyesuaian halus pada semua bobot dalam jaringan. Kami menyebut proses ini sebagai menggunakan embedding yang telah di-fine-tune.

  2. Embedding yang di-fine-tune menghasilkan akurasi yang lebih baik. Namun, hal ini mengakibatkan biaya peningkatan daya komputasi yang diperlukan untuk melatih jaringan. Dengan jumlah sampel yang cukup, kita juga dapat mempelajari embedding dari awal. Kami mengamati bahwa untuk S/W > 15K, yang dimulai dari awal secara efektif menghasilkan akurasi yang sama seperti menggunakan penyematan yang disesuaikan secara mendetail.

Kami membandingkan berbagai model urutan seperti CNN, sepCNN, RNN (LSTM & GRU), CNN-RNN, dan RNN bertumpuk, yang memvariasikan arsitektur model. Kami menemukan bahwa sepCNN, varian jaringan konvolusional yang sering kali lebih hemat data dan hemat komputasi, berperforma lebih baik daripada model lainnya.

Kode berikut menyusun model sepCNN empat lapis:

from tensorflow.python.keras import models
from tensorflow.python.keras import initializers
from tensorflow.python.keras import regularizers

from tensorflow.python.keras.layers import Dense
from tensorflow.python.keras.layers import Dropout
from tensorflow.python.keras.layers import Embedding
from tensorflow.python.keras.layers import SeparableConv1D
from tensorflow.python.keras.layers import MaxPooling1D
from tensorflow.python.keras.layers import GlobalAveragePooling1D

def sepcnn_model(blocks,
                 filters,
                 kernel_size,
                 embedding_dim,
                 dropout_rate,
                 pool_size,
                 input_shape,
                 num_classes,
                 num_features,
                 use_pretrained_embedding=False,
                 is_embedding_trainable=False,
                 embedding_matrix=None):
    """Creates an instance of a separable CNN model.

    # Arguments
        blocks: int, number of pairs of sepCNN and pooling blocks in the model.
        filters: int, output dimension of the layers.
        kernel_size: int, length of the convolution window.
        embedding_dim: int, dimension of the embedding vectors.
        dropout_rate: float, percentage of input to drop at Dropout layers.
        pool_size: int, factor by which to downscale input at MaxPooling layer.
        input_shape: tuple, shape of input to the model.
        num_classes: int, number of output classes.
        num_features: int, number of words (embedding input dimension).
        use_pretrained_embedding: bool, true if pre-trained embedding is on.
        is_embedding_trainable: bool, true if embedding layer is trainable.
        embedding_matrix: dict, dictionary with embedding coefficients.

    # Returns
        A sepCNN model instance.
    """
    op_units, op_activation = _get_last_layer_units_and_activation(num_classes)
    model = models.Sequential()

    # Add embedding layer. If pre-trained embedding is used add weights to the
    # embeddings layer and set trainable to input is_embedding_trainable flag.
    if use_pretrained_embedding:
        model.add(Embedding(input_dim=num_features,
                            output_dim=embedding_dim,
                            input_length=input_shape[0],
                            weights=[embedding_matrix],
                            trainable=is_embedding_trainable))
    else:
        model.add(Embedding(input_dim=num_features,
                            output_dim=embedding_dim,
                            input_length=input_shape[0]))

    for _ in range(blocks-1):
        model.add(Dropout(rate=dropout_rate))
        model.add(SeparableConv1D(filters=filters,
                                  kernel_size=kernel_size,
                                  activation='relu',
                                  bias_initializer='random_uniform',
                                  depthwise_initializer='random_uniform',
                                  padding='same'))
        model.add(SeparableConv1D(filters=filters,
                                  kernel_size=kernel_size,
                                  activation='relu',
                                  bias_initializer='random_uniform',
                                  depthwise_initializer='random_uniform',
                                  padding='same'))
        model.add(MaxPooling1D(pool_size=pool_size))

    model.add(SeparableConv1D(filters=filters * 2,
                              kernel_size=kernel_size,
                              activation='relu',
                              bias_initializer='random_uniform',
                              depthwise_initializer='random_uniform',
                              padding='same'))
    model.add(SeparableConv1D(filters=filters * 2,
                              kernel_size=kernel_size,
                              activation='relu',
                              bias_initializer='random_uniform',
                              depthwise_initializer='random_uniform',
                              padding='same'))
    model.add(GlobalAveragePooling1D())
    model.add(Dropout(rate=dropout_rate))
    model.add(Dense(op_units, activation=op_activation))
    return model

Latih Model

Setelah membangun arsitektur model, kita perlu melatih model. Pelatihan melibatkan pembuatan prediksi berdasarkan status model saat ini, menghitung seberapa salah prediksi tersebut, dan mengupdate bobot atau parameter jaringan untuk meminimalkan error ini dan membuat model memprediksi dengan lebih baik. Kita mengulangi proses ini sampai model kita dikonvergensi dan tidak bisa lagi belajar. Ada tiga parameter utama yang harus dipilih untuk proses ini (Lihat Tabel 2.)

  • Metrik: Cara mengukur performa model kami menggunakan metrik. Kami menggunakan akurasi sebagai metrik dalam eksperimen.
  • Fungsi yang hilang: Fungsi yang digunakan untuk menghitung nilai kerugian yang kemudian dicoba untuk diminimalkan oleh proses pelatihan dengan menyesuaikan bobot jaringan. Untuk masalah klasifikasi, kerugian entropi silang berfungsi dengan baik.
  • Optimizer: Fungsi yang menentukan bagaimana bobot jaringan akan diperbarui berdasarkan output dari fungsi kerugian. Kami menggunakan pengoptimal Adam populer dalam eksperimen.

Di Keras, kita dapat meneruskan parameter pembelajaran ini ke model menggunakan metode compile.

Tabel 2: Parameter pembelajaran

Parameter pembelajaran Nilai
Metrik akurasi
Fungsi kerugian - klasifikasi biner binary_crossentropy
Fungsi kerugian - klasifikasi multi-kelas sparse_categorical_crossentropy
Taktis adam

Pelatihan yang sebenarnya dilakukan dengan menggunakan metode fit. Tergantung pada ukuran set data Anda, metode ini merupakan metode yang akan menghabiskan sebagian besar siklus komputasi. Dalam setiap iterasi pelatihan, jumlah batch_size sampel dari data pelatihan Anda digunakan untuk menghitung kerugian, dan bobot diperbarui sekali, berdasarkan nilai ini. Proses pelatihan menyelesaikan epoch setelah model melihat seluruh set data pelatihan. Di akhir setiap epoch, kami menggunakan set data validasi untuk mengevaluasi seberapa baik model dalam melakukan pembelajaran. Kita mengulangi pelatihan menggunakan set data untuk jumlah epoch yang telah ditentukan. Kami dapat mengoptimalkan hal ini dengan menghentikannya lebih awal, saat akurasi validasi stabil di antara epoch berturut-turut, yang menunjukkan bahwa model tidak lagi dilatih.

Melatih hyperparameter Nilai
Kecepatan pembelajaran 1e-3
Epoch 1.000
Ukuran batch 512
Penghentian awal parameter: val_loss, kesabaran: 1

Tabel 3: Melatih hyperparameter

Kode Keras berikut mengimplementasikan proses pelatihan menggunakan parameter yang dipilih dalam Tabel 2 & 3 di atas:

def train_ngram_model(data,
                      learning_rate=1e-3,
                      epochs=1000,
                      batch_size=128,
                      layers=2,
                      units=64,
                      dropout_rate=0.2):
    """Trains n-gram model on the given dataset.

    # Arguments
        data: tuples of training and test texts and labels.
        learning_rate: float, learning rate for training model.
        epochs: int, number of epochs.
        batch_size: int, number of samples per batch.
        layers: int, number of `Dense` layers in the model.
        units: int, output dimension of Dense layers in the model.
        dropout_rate: float: percentage of input to drop at Dropout layers.

    # Raises
        ValueError: If validation data has label values which were not seen
            in the training data.
    """
    # Get the data.
    (train_texts, train_labels), (val_texts, val_labels) = data

    # Verify that validation labels are in the same range as training labels.
    num_classes = explore_data.get_num_classes(train_labels)
    unexpected_labels = [v for v in val_labels if v not in range(num_classes)]
    if len(unexpected_labels):
        raise ValueError('Unexpected label values found in the validation set:'
                         ' {unexpected_labels}. Please make sure that the '
                         'labels in the validation set are in the same range '
                         'as training labels.'.format(
                             unexpected_labels=unexpected_labels))

    # Vectorize texts.
    x_train, x_val = vectorize_data.ngram_vectorize(
        train_texts, train_labels, val_texts)

    # Create model instance.
    model = build_model.mlp_model(layers=layers,
                                  units=units,
                                  dropout_rate=dropout_rate,
                                  input_shape=x_train.shape[1:],
                                  num_classes=num_classes)

    # Compile model with learning parameters.
    if num_classes == 2:
        loss = 'binary_crossentropy'
    else:
        loss = 'sparse_categorical_crossentropy'
    optimizer = tf.keras.optimizers.Adam(lr=learning_rate)
    model.compile(optimizer=optimizer, loss=loss, metrics=['acc'])

    # Create callback for early stopping on validation loss. If the loss does
    # not decrease in two consecutive tries, stop training.
    callbacks = [tf.keras.callbacks.EarlyStopping(
        monitor='val_loss', patience=2)]

    # Train and validate model.
    history = model.fit(
            x_train,
            train_labels,
            epochs=epochs,
            callbacks=callbacks,
            validation_data=(x_val, val_labels),
            verbose=2,  # Logs once per epoch.
            batch_size=batch_size)

    # Print results.
    history = history.history
    print('Validation accuracy: {acc}, loss: {loss}'.format(
            acc=history['val_acc'][-1], loss=history['val_loss'][-1]))

    # Save model.
    model.save('IMDb_mlp_model.h5')
    return history['val_acc'][-1], history['val_loss'][-1]

Temukan contoh kode untuk melatih model urutan di sini.