.. raw:: html .. _sec_sentiment_cnn: Phân tích Cảm xúc: Sử dụng Mạng Nơ-ron Tích Chập ================================================ .. raw:: html Trong :numref:`chap_cnn`, chúng ta đã tìm hiểu cách xử lý dữ liệu ảnh hai chiều với mạng nơ-ron tích chập hai chiều. Ở chương trước về các mô hình ngôn ngữ và các tác vụ phân loại văn bản, ta coi dữ liệu văn bản như là dữ liệu chuỗi thời gian với chỉ một chiều duy nhất, và vì vậy, chúng sẽ được xử lí bằng mạng nơ-ron hồi tiếp. Thực tế, ta cũng có thể coi văn bản như một bức ảnh một chiều, và sử dụng mạng nơ-ron tích chập một chiều để tìm ra mối liên kết giữa những từ liền kề nhau. Như mô tả trong :numref:`fig_nlp-map-sa-cnn`, chương này sẽ miêu tả một hướng tiếp cận đột phá bằng cách áp dụng mạng nơ-ron tích chập để phân tích cảm xúc: textCNN :cite:`Kim.2014`. .. raw:: html .. _fig_nlp-map-sa-cnn: .. figure:: ../img/nlp-map-sa-cnn.svg Phần này truyền mô hình tiền huấn luyện GloVe vào một kiến trúc mạng nơ-ron tích chập cho tác vụ phân loại cảm xúc .. raw:: html Đầu tiên, nhập những gói thư viện và mô-đun cần thiết cho thử nghiệm. .. code:: python from d2l import mxnet as d2l from mxnet import gluon, init, np, npx from mxnet.gluon import nn npx.set_np() batch_size = 64 train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size) .. raw:: html Mạng Nơ-ron Tích chập Một chiều ------------------------------- .. raw:: html Trước khi giới thiệu mô hình, chúng ta hãy xem mạng nơ-ron tích chập một chiều họat động như thế nào. Tương tự như mạng nơ-ron tích chập hai chiều, mạng nơ-ron tích chập một chiều sử dụng phép tính tương quan chéo một chiều. Trong phép tính tương quan chéo một chiều, cửa sổ tích chập bắt đầu từ phía ngoài cùng bên trái của mảng đầu vào và trượt lần lượt từ trái qua phải. Xét trên một vị trí nhất định của cửa sổ tích chập khi trượt, ta nhân từng phần tử của mảng đầu vào con trong cửa sổ đó với mảng hạt nhân rồi cộng lại để lấy được phần tử ở vị trí tương ứng trong mảng đầu ra. Như ví dụ ở :numref:`fig_conv1d`, đầu vào là một mảng một chiều với độ rộng là 7 và độ rộng của mảng hạt nhân là 2. Ta có thể thấy rằng độ rộng của đầu ra là :math:`7-2+1=6` và phần tử đầu tiên được tính bằng cách nhân theo từng phần tử mảng đầu vào con chứa 2 phần tử ngoài cùng bên trái với mảng hạt nhân, rồi cộng lại với nhau. .. raw:: html .. _fig_conv1d: .. figure:: ../img/conv1d.svg Phép tính tương quan chéo một chiều. Những vùng in đậm là phần tử đầu ra đầu tiên, cùng phần tử đầu vào và mảng hạt nhân được dùng trong phép tính đó: :math:`0\times1+1\times2=2`. .. raw:: html Tiếp theo, chúng ta sẽ lập trình phép tương quan chéo một chiều trong hàm ``corr1d``. Hàm này nhận mảng đầu vào ``X`` và mảng hạt nhân ``K`` và cho ra đầu ra là mảng ``Y``. .. code:: python def corr1d(X, K): w = K.shape[0] Y = np.zeros((X.shape[0] - w + 1)) for i in range(Y.shape[0]): Y[i] = (X[i: i + w] * K).sum() return Y .. raw:: html Bây giờ chúng ta sẽ tái tạo lại kết quả của phép tính tương quan chéo một chiều ở :numref:`fig_conv1d`. .. code:: python X, K = np.array([0, 1, 2, 3, 4, 5, 6]), np.array([1, 2]) corr1d(X, K) .. parsed-literal:: :class: output array([ 2., 5., 8., 11., 14., 17.]) .. raw:: html Phép tính tương quan chéo một chiều cho nhiều kênh đầu vào cũng tương tự như phép tương quan chéo hai chiều cho nhiều kênh đầu vào. Với mỗi kênh, toán tử này thực hiện phép tính tương quan chéo một chiều trên từng hạt nhân và đầu vào tương ứng, và cộng các kết quả trên từng kênh lại với nhau để thu được đầu ra. :numref:`fig_conv1d_channel` minh họa phép tính tương quan chéo một chiều với ba kênh đầu vào. .. raw:: html .. _fig_conv1d_channel: .. figure:: ../img/conv1d-channel.svg Phép tính tương quan chéo một chiều với ba kênh đầu vào. Những vùng được in đậm là phần tử đầu ra thứ nhất cũng như đầu vào và các phần tử của mảng hạt nhân được sử dụng trong phép tính: :math:`0\times1+1\times2+1\times3+2\times4+2\times(-1)+3\times(-3)=2`. .. raw:: html Bây giờ, ta sẽ tái tạo lại kết quả của phép tính tương quan chéo một chiều với đa kênh đầu vào trong :numref:`fig_conv1d_channel`. .. code:: python def corr1d_multi_in(X, K): # First, we traverse along the 0th dimension (channel dimension) of `X` # and `K`. Then, we add them together by using * to turn the result list # into a positional argument of the `add_n` function return sum(corr1d(x, k) for x, k in zip(X, K)) X = np.array([[0, 1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6, 7], [2, 3, 4, 5, 6, 7, 8]]) K = np.array([[1, 2], [3, 4], [-1, -3]]) corr1d_multi_in(X, K) .. parsed-literal:: :class: output array([ 2., 8., 14., 20., 26., 32.]) .. raw:: html Định nghĩa phép tính tương quan chéo hai chiều cho ta thấy phép tính tương quan chéo một chiều với đa kênh đầu vào có thể được coi là phép tính tương quan chéo hai chiều với một kênh đầu vào. Như minh họa trong :numref:`fig_conv1d_2d`, ta có thể biểu diễn phép tính tương quan chéo một chiều với đa kênh đầu vào trong :numref:`fig_conv1d_channel` tương tự như phép tính tương quan chéo hai chiều với một kênh đầu vào. Ở đây, chiều cao của hạt nhân bằng với chiều cao của đầu vào. .. raw:: html .. _fig_conv1d_2d: .. figure:: ../img/conv1d-2d.svg Phép tính tương quan chéo hai chiều với một kênh đầu vào. Vùng được tô đậm là phần tử đầu ra thứ nhất và đầu vào cũng như các phần tử của mảng hạt nhân được sử dụng trong phép tính: :math:`2\times(-1)+3\times(-3)+1\times3+2\times4+0\times1+1\times2=2`. .. raw:: html Cả hai đầu ra trong :numref:`fig_conv1d` và :numref:`fig_conv1d_channel` chỉ có một kênh. Ta đã thảo luận cách chỉ định đa kênh đầu ra trong tầng tích chập hai chiều tại :numref:`sec_channels`. Tương tự, ta cũng có thể chỉ định đa kênh đầu ra trong tầng tích chập một chiều để mở rộng các tham số mô hình trong tầng tích chập đó. .. raw:: html Tầng Gộp Cực đại Theo Thời gian ------------------------------- .. raw:: html Tương tự, ta có tầng gộp một chiều. Tầng gộp cực đại theo thời gian được dùng trong TextCNN thực chất tương tự như tầng gộp cực đại toàn cục một chiều. Giả sử đầu vào có nhiều kênh, mỗi kênh bao gồm các giá trị bước thời gian khác nhau, đầu ra của mỗi kênh sẽ là giá trị lớn nhất qua tất cả bước thời gian trong từng kênh. Do đó, đầu vào của tầng gộp cực đại theo thời gian có thể có số lượng bước thời gian khác nhau tại mỗi kênh. .. raw:: html Để cải thiện chất lượng tính toán, ta thường kết hợp những mẫu thời gian có độ dài khác nhau vào một minibatch và làm cho chiều dài theo thời gian của từng mẫu đồng nhất bằng cách thêm các ký tự đặc biệt (ví dụ 0) vào cuối những mẫu ngắn hơn. Tất nhiên, các ký tự được thêm vào không làm thay đổi bản chất ngữ nghĩa. Bởi vì, mục tiêu chính của tầng gộp cực đại theo thời gian là học được những đặc trưng quan trọng của thời gian, thông thường điều đó cho phép mô hình không bị ảnh hưởng bởi các ký tự được thêm vào thủ công. .. raw:: html Mô hình TextCNN --------------- .. raw:: html TextCNN chủ yếu sử dụng tầng tích chập một chiều và tầng gộp cực đại theo thời gian. Giả sử chuỗi văn bản đầu vào gồm :math:`n` từ, mỗi từ được biểu diễn bởi một vector :math:`d` chiều. Lúc này mẫu đầu vào có chiều rộng là :math:`n`, chiều cao là 1, và :math:`d` kênh đầu vào. Quá trình tính toán của textCNN chủ yếu được chia thành các bước sau: .. raw:: html 1. Định nghĩa nhiều hạt nhân tích chập một chiều để thực hiện các phép tính tích chập trên đầu vào. Những hạt nhân tích chập với độ rộng khác nhau có thể học được sự tương quan của các cụm từ liền kề với số lượng khác nhau. 2. Thực hiện gộp cực đại theo thời gian trên tất cả các kênh đầu ra, sau đó nối các giá trị gộp được của các kênh này thành một vector. 3. Vector nối trên sẽ được biến đổi thành đầu ra cho từng hạng mục bằng thông qua tầng kết nối đầy đủ. Tầng dropout có thể được sử dụng ở bước này để giải quyết tình trạng quá khớp. .. raw:: html .. _fig_conv1d_textcnn: .. figure:: ../img/textcnn.svg Thiết kế TextCNN. .. raw:: html :numref:`fig_conv1d_textcnn` minh họa một ví dụ cho textCNN. Đầu vào ở đây là một câu gồm 11 từ, với mỗi từ được biểu diễn bằng một vector từ 6 chiều. Vì vậy, câu đầu vào có độ rộng là 11 và số kênh đầu vào là 6. Chúng ta giả sử rằng 2 hạt nhân tích chập một chiều có độ rộng lần lượt là 2 và 4, tương ứng với số kênh đầu ra là 4 và 5. Cho nên sau phép tính tích chập một chiều, đầu ra 4 kênh có chiều rộng là là :math:`11-2+1=10`, trong khi đó độ rộng của đầu ra 5 kênh còn lại là :math:`11-4+1=8`. Thậm chí độ rộng của mỗi kênh có khác nhau đi nữa, chúng ta vẫn có thể thực hiện gộp cực đại theo thời gian cho mỗi kênh và nối đầu ra sau gộp của 9 kênh thành một vector 9 chiều. Cuối cùng, chúng ta dùng một tầng kết nối đầy đủ để biến đổi vector 9 chiều đó thành một đầu ra 2 chiều: dự đoán cảm xúc tích cực và cảm xúc tiêu cực. .. raw:: html Tiếp theo chúng ta bắt đầu lập trình mô hình textCNN. So với phần trước, ngoài việc thay mạng nơ-ron hồi tiếp bằng một tầng tích chập một chiều, ở đây chúng ta dùng 2 tầng embedding, một được giữ trọng số cố định và tầng còn lại tham gia quá trình huấn luyện. .. code:: python class TextCNN(nn.Block): def __init__(self, vocab_size, embed_size, kernel_sizes, num_channels, **kwargs): super(TextCNN, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) # The embedding layer does not participate in training self.constant_embedding = nn.Embedding(vocab_size, embed_size) self.dropout = nn.Dropout(0.5) self.decoder = nn.Dense(2) # The max-over-time pooling layer has no weight, so it can share an # instance self.pool = nn.GlobalMaxPool1D() # Create multiple one-dimensional convolutional layers self.convs = nn.Sequential() for c, k in zip(num_channels, kernel_sizes): self.convs.add(nn.Conv1D(c, k, activation='relu')) def forward(self, inputs): # Concatenate the output of two embedding layers with shape of # (batch size, no. of words, word vector dimension) by word vector embeddings = np.concatenate(( self.embedding(inputs), self.constant_embedding(inputs)), axis=2) # According to the input format required by Conv1D, the word vector # dimension, that is, the channel dimension of the one-dimensional # convolutional layer, is transformed into the previous dimension embeddings = embeddings.transpose(0, 2, 1) # For each one-dimensional convolutional layer, after max-over-time # pooling, an ndarray with the shape of (batch size, channel size, 1) # can be obtained. Use the flatten function to remove the last # dimension and then concatenate on the channel dimension encoding = np.concatenate([ np.squeeze(self.pool(conv(embeddings)), axis=-1) for conv in self.convs], axis=1) # After applying the dropout method, use a fully connected layer to # obtain the output outputs = self.decoder(self.dropout(encoding)) return outputs .. raw:: html Tạo một thực thể TextCNN có 3 tầng tích chập với chiều rộng hạt nhân là 3, 4 và 5, tất cả đều có 100 kênh đầu ra. .. code:: python embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100] devices = d2l.try_all_gpus() net = TextCNN(len(vocab), embed_size, kernel_sizes, nums_channels) net.initialize(init.Xavier(), ctx=devices) .. raw:: html Nạp Vector Từ đã được Tiền huấn luyện ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. raw:: html Tương tự phần trước, ta nạp GloVe 100 chiều đã được tiền huấn luyện và khởi tạo các tầng embedding ``embedding`` và ``constant_embedding``. Ở đây, ``embedding`` sẽ tham gia quá trình huấn luyện trong khi ``constant_embedding`` có trọng số cố định. .. code:: python glove_embedding = d2l.TokenEmbedding('glove.6b.100d') embeds = glove_embedding[vocab.idx_to_token] net.embedding.weight.set_data(embeds) net.constant_embedding.weight.set_data(embeds) net.constant_embedding.collect_params().setattr('grad_req', 'null') .. raw:: html Huấn luyện và Đánh giá Mô hình ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. raw:: html Bây giờ ta có thể huấn luyện mô hình. .. code:: python lr, num_epochs = 0.001, 5 trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr}) loss = gluon.loss.SoftmaxCrossEntropyLoss() d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices) .. parsed-literal:: :class: output loss 0.091, train acc 0.968, test acc 0.869 6584.2 examples/sec on [gpu(0)] .. figure:: output_sentiment-analysis-cnn_vn_c71b21_15_1.svg .. raw:: html Dưới đây, ta sử dụng mô hình đã được huấn luyện để phân loại cảm xúc của hai câu đơn giản. .. code:: python d2l.predict_sentiment(net, vocab, 'this movie is so great') .. parsed-literal:: :class: output 'positive' .. code:: python d2l.predict_sentiment(net, vocab, 'this movie is so bad') .. parsed-literal:: :class: output 'negative' Tóm tắt ------- .. raw:: html - Ta có thể dùng tích chập một chiều để xử lý và phân tích dữ liệu theo thời gian. - Phép tương quan chéo một chiều đa kênh đầu vào có thể xem như phép tương quan chéo hai chiều đơn kênh đầu vào. - Đầu vào của tầng gộp cực đại theo thời gian có thể có số bước thời gian trên mỗi kênh khác nhau. - TextCNN chủ yếu sử dụng một tầng chập một chiều và một tầng gộp cực đại theo thời gian. Bài tập ------- .. raw:: html 1. Điều chỉnh các tham số mô hình và so sánh hai phương pháp phân tích cảm xúc giữa mạng nơ-ron truy hồi và mạng nơ-ron tích chập, xét trên khía cạnh độ chính xác và hiệu suất tính toán. 2. Bạn có thể cải thiện thêm độ chính xác của mô hình trên tập kiểm tra thông qua việc sử dụng ba phương pháp đã được giới thiệu ở phần trước: điều chỉnh các tham số mô hình, sử dụng các vector từ tiền huấn luyện lớn hơn, và sử dụng công cụ token hóa từ spaCy. 3. Bạn còn có thể sử dụng TextCNN cho những tác vụ xử lý ngôn ngữ tự nhiên nào khác? Thảo luận --------- - Tiếng Anh: `MXNet `__ - Tiếng Việt: `Diễn đàn Machine Learning Cơ Bản `__ Những người thực hiện --------------------- Bản dịch trong trang này được thực hiện bởi: - Đoàn Võ Duy Thanh - Trương Lộc Phát - Nguyễn Văn Quang - Lý Phi Long - Nguyễn Mai Hoàng Long - Nguyễn Lê Quang Nhật - Phạm Hồng Vinh - Lê Khắc Hồng Phúc - Nguyễn Văn Cường *Lần cập nhật gần nhất: 26/09/2020. (Cập nhật lần cuối từ nội dung gốc: 20/09/2020)*