Bước 4: Xây dựng, đào tạo và đánh giá mô hình của bạn

Trong phần này, chúng ta sẽ xây dựng, huấn luyện và đánh giá mô hình của mình. Ở Bước 3, chúng tôi chọn sử dụng mô hình n-gram hoặc mô hình trình tự theo tỷ lệ S/W. Giờ là lúc chúng ta viết và huấn luyện thuật toán phân loại. Chúng tôi sẽ sử dụng TensorFlow với API tf.keras cho việc này.

Việc xây dựng các mô hình học máy bằng Keras xoay quanh việc lắp ráp các lớp với nhau, các khối dựng xử lý dữ liệu, giống như cách chúng ta lắp ráp các viên gạch Lego. Các lớp này cho phép chúng ta chỉ định trình tự biến đổi mà chúng ta muốn thực hiện trên dữ liệu đầu vào. Vì thuật toán học của chúng tôi sử dụng một dữ liệu đầu vào văn bản và xuất ra một thông tin phân loại duy nhất, nên chúng ta có thể tạo một ngăn xếp tuyến tính gồm các lớp bằng cách sử dụng API Mô hình tuần tự.

Ngăn xếp tuyến tính của lớp

Hình 9: Ngăn xếp tuyến tính của các lớp

Lớp đầu vào và các lớp trung gian sẽ được xây dựng khác nhau, tuỳ thuộc vào việc chúng ta đang xây dựng mô hình n-gram hay mô hình trình tự. Nhưng bất kể loại mô hình là gì, lớp cuối cùng sẽ giống nhau cho một vấn đề nhất định.

Xây dựng lớp cuối cùng

Khi chỉ có 2 lớp (phân loại nhị phân), mô hình của chúng ta sẽ đưa ra một điểm xác suất duy nhất. Chẳng hạn, việc xuất 0.2 cho một mẫu đầu vào nhất định có nghĩa là "độ tin cậy 20% rằng mẫu này đang ở lớp đầu tiên (lớp 1), 80% rằng mẫu nằm trong lớp thứ hai (lớp 0)". Để xuất điểm xác suất như vậy, hàm kích hoạt của lớp cuối cùng phải là hàm sigmoidhàm mất dữ liệu dùng để huấn luyện nhị phân của mô hình. (Xem Hình 10, bên trái).

Khi có nhiều hơn 2 lớp (phân loại nhiều lớp), mô hình của chúng tôi sẽ đưa ra một điểm xác suất cho mỗi lớp. Tổng các điểm số này phải là 1. Ví dụ: xuất {0: 0.2, 1: 0.7, 2: 0.1} có nghĩa là “độ tin cậy 20% rằng mẫu này nằm ở lớp 0, 70% rằng mẫu này đang ở lớp 1 và 10% rằng mẫu nằm trong lớp 2”. Để xuất các điểm số này, hàm kích hoạt của lớp cuối cùng phải là Softmax và hàm mất dùng để huấn luyện mô hình phải là entropy chéo theo danh mục. (Xem Hình 10, bên phải).

Lớp cuối cùng

Hình 10: Lớp cuối cùng

Mã sau đây xác định một hàm lấy số lượng lớp làm đầu vào và xuất ra số lượng đơn vị lớp thích hợp (1 đơn vị để phân loại nhị phân; nếu không thì 1 đơn vị cho mỗi lớp) và hàm kích hoạt thích hợp:

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

Hai phần sau đây trình bày cách tạo các lớp mô hình còn lại cho mô hình n-gram và mô hình trình tự.

Khi tỷ lệ S/W nhỏ, chúng tôi nhận thấy rằng mô hình n-gram hoạt động tốt hơn so với mô hình trình tự. Mô hình trình tự sẽ hiệu quả hơn khi có một số lượng lớn các vectơ nhỏ, dày đặc. Lý do là các mối quan hệ nhúng được học trong không gian dày đặc và điều này xảy ra tốt nhất trên nhiều mẫu.

Xây dựng mô hình n-gram [Lựa chọn A]

Chúng tôi đề cập đến các mô hình xử lý mã thông báo một cách độc lập (không tính đến thứ tự từ) dưới dạng mô hình n-gram. Các bộ cảm biến nhiều lớp đơn giản (bao gồm cả máy tăng độ dốc hồi quy logisticmô hình hỗ trợ máy vectơ) đều thuộc danh mục này; chúng không thể sử dụng bất kỳ thông tin nào về thứ tự văn bản.

Chúng tôi so sánh hiệu suất của một số mô hình n-gram nêu trên và quan sát thấy rằng perceptron nhiều lớp (MLP) thường hoạt động tốt hơn so với các lựa chọn khác. MLP rất đơn giản để định nghĩa và dễ hiểu, cung cấp độ chính xác cao và yêu cầu hoạt động tính toán tương đối ít.

Đoạn mã sau đây xác định mô hình MLP hai lớp trong tf.keras, thêm một vài lớp thả để điều chỉnh quy trình để ngăn trang bị quá mức cho các mẫu huấn luyện.

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

Mô hình trình tự xây dựng [Lựa chọn B]

Chúng tôi đề cập đến những mô hình có thể học hỏi từ sự liền kề của mã thông báo để làm mô hình trình tự. Điều này bao gồm các lớp CNN và RNN của các mô hình. Dữ liệu được xử lý trước dưới dạng vectơ trình tự cho các mô hình này.

Mô hình trình tự thường có số lượng tham số lớn hơn cần tìm hiểu. Lớp đầu tiên trong các mô hình này là một lớp nhúng, có chức năng tìm hiểu mối quan hệ giữa các từ trong một không gian vectơ dày đặc. Việc học mối quan hệ giữa các từ hoạt động hiệu quả nhất trên nhiều mẫu.

Các từ trong một tập dữ liệu nhất định có nhiều khả năng không phải là duy nhất cho tập dữ liệu đó. Do đó, chúng tôi có thể tìm hiểu mối quan hệ giữa các từ trong tập dữ liệu bằng cách sử dụng(các) tập dữ liệu khác. Để làm như vậy, chúng ta có thể chuyển một thành phần nhúng đã học được từ một tập dữ liệu khác vào lớp nhúng. Các mục nhúng này được gọi là tệp nhúng được huấn luyện trước. Việc sử dụng tính năng nhúng đã huấn luyện trước sẽ giúp mô hình này bắt đầu quá trình học tập.

Có các mục nhúng được huấn luyện trước và đã được huấn luyện bằng cách sử dụng các tập sao lục lớn, chẳng hạn như GloVe. GloVe đã được huấn luyện trên nhiều kho nội dung (chủ yếu trên Wikipedia). Chúng tôi đã thử nghiệm việc huấn luyện các mô hình trình tự bằng cách sử dụng một phiên bản nhúng GloVe và quan sát thấy rằng nếu chúng tôi cố định trọng số của các mục nhúng được huấn luyện trước và chỉ huấn luyện phần còn lại của mạng, thì các mô hình sẽ không hoạt động tốt. Điều này có thể là do bối cảnh mà lớp nhúng được huấn luyện có thể khác với bối cảnh mà chúng ta đang sử dụng lớp nhúng đó.

Các mục nhúng GloVe được huấn luyện dựa trên dữ liệu trên Wikipedia có thể không phù hợp với mẫu ngôn ngữ trong tập dữ liệu IMDb của chúng tôi. Các mối quan hệ được dự đoán có thể cần cập nhật một số – tức là các trọng số nhúng có thể cần được điều chỉnh theo ngữ cảnh. Chúng tôi thực hiện việc này qua 2 giai đoạn:

  1. Trong lần chạy đầu tiên, với trọng số của lớp nhúng bị treo, chúng tôi cho phép phần còn lại của mạng học. Khi kết thúc lần chạy này, trọng số mô hình sẽ đạt đến trạng thái tốt hơn nhiều so với giá trị chưa khởi tạo. Trong lần chạy thứ hai, chúng tôi cũng cho phép lớp nhúng tìm hiểu, điều chỉnh chi tiết cho tất cả các trọng số trong mạng. Chúng tôi gọi quá trình này là sử dụng phương thức nhúng được tinh chỉnh.

  2. Các mục nhúng được tinh chỉnh mang lại độ chính xác cao hơn. Tuy nhiên, điều này khiến bạn phải tốn thêm công suất tính toán cần thiết để huấn luyện mạng. Khi có đủ số lượng mẫu, chúng tôi cũng có thể tìm hiểu cách nhúng từ đầu. Chúng tôi quan sát thấy rằng đối với S/W > 15K, việc thiết lập từ đầu có hiệu quả mang lại độ chính xác tương tự như khi sử dụng tính năng nhúng tinh chỉnh.

Chúng tôi đã so sánh nhiều mô hình trình tự như CNN, sepCNN, RNN (LSTM và GRU), CNN-RNN và RNN xếp chồng, làm thay đổi kiến trúc mô hình. Chúng tôi nhận thấy rằng sepCNN, một biến thể mạng tích chập thường tiết kiệm dữ liệu và điện toán hiệu quả hơn, hoạt động tốt hơn các mô hình khác.

Đoạn mã sau đây xây dựng mô hình sepCNN bốn lớp:

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

Huấn luyện mô hình của bạn

Bây giờ, chúng ta đã xây dựng kiến trúc mô hình, chúng ta cần huấn luyện mô hình. Quá trình huấn luyện bao gồm việc đưa ra thông tin dự đoán dựa trên trạng thái hiện tại của mô hình, tính toán mức độ chính xác của thông tin dự đoán và cập nhật trọng số hoặc tham số của mạng để giảm thiểu lỗi này và giúp mô hình dự đoán chính xác hơn. Chúng tôi lặp lại quá trình này cho đến khi mô hình của chúng tôi hội tụ và không thể học được nữa. Có 3 tham số chính được chọn cho quy trình này (Xem Bảng 2).

  • Chỉ số: Cách đo lường hiệu suất của mô hình bằng một chỉ số. Chúng tôi đã sử dụng độ chính xác làm chỉ số trong các thử nghiệm.
  • Hàm mất: Hàm dùng để tính toán giá trị tổn hao mà sau đó quá trình huấn luyện sẽ cố gắng giảm thiểu bằng cách điều chỉnh trọng số mạng. Đối với các bài toán phân loại, tổn thất chéo entropy hoạt động tốt.
  • Trình tối ưu hoá: Một hàm quyết định cách cập nhật trọng số mạng dựa trên kết quả của hàm mất dữ liệu. Chúng tôi đã sử dụng trình tối ưu hoá Adam phổ biến trong các thử nghiệm của mình.

Trong Keras, chúng ta có thể truyền các tham số học tập này đến một mô hình bằng phương thức compile.

Bảng 2: Các tham số học

Thông số tìm hiểu Giá trị
Chỉ số độ chính xác
Hàm mất – phân loại nhị phân binary_crossentropy
Hàm mất – phân loại nhiều lớp sparse_categorical_crossentropy
Cao thủ tối ưu hoá adam

Quá trình huấn luyện thực tế diễn ra bằng phương thức fit (phù hợp). Tuỳ thuộc vào kích thước của tập dữ liệu, đây là phương thức mà hầu hết các chu kỳ điện toán sẽ được sử dụng. Trong mỗi lần lặp lại quá trình huấn luyện, số lượng batch_size mẫu từ dữ liệu huấn luyện sẽ được dùng để tính toán mức tổn thất và trọng số sẽ được cập nhật một lần, dựa trên giá trị này. Quá trình huấn luyện sẽ hoàn tất epoch sau khi mô hình đã xem toàn bộ tập dữ liệu huấn luyện. Vào cuối mỗi khoảng thời gian bắt đầu của hệ thống, chúng tôi sẽ sử dụng tập dữ liệu xác thực để đánh giá mức độ hiệu quả của mô hình. Chúng tôi lặp lại quá trình huấn luyện bằng cách sử dụng tập dữ liệu trong một số khoảng thời gian bắt đầu của hệ thống xác định trước. Chúng tôi có thể tối ưu hoá việc này bằng cách dừng sớm, khi độ chính xác xác thực ổn định giữa các khoảng thời gian bắt đầu liên tiếp, cho thấy mô hình không còn huấn luyện nữa.

Siêu tham số huấn luyện Giá trị
Tốc độ học 1e-3
Các thời kỳ 1000
Kích thước lô 512
Dừng sớm tham số: val_loss, kiên nhẫn: 1

Bảng 3: Siêu tham số huấn luyện

Mã Keras sau đây triển khai quy trình huấn luyện bằng cách sử dụng các tham số đã chọn trong Bảng 2 và 3 ở trên:

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]

Vui lòng tìm ví dụ về mã để huấn luyện mô hình trình tự tại đây.