.. raw:: html .. _sec_sentiment_rnn: Phân tích Cảm xúc: Sử dụng Mạng Nơ-ron Hồi tiếp =============================================== .. raw:: html Tương tự như tìm kiếm các từ đồng nghĩa và loại suy, phân loại văn bản cũng là một tác vụ xuôi dòng của embedding từ. Trong phần này, ta sẽ áp dụng các vector từ đã được tiền huấn luyện (GloVe) và mạng nơ-ron truy hồi hai chiều với nhiều lớp ẩn :cite:`Maas.Daly.Pham.ea.2011`, như được minh họa trong :numref:`fig_nlp-map-sa-rnn`. Ta sẽ sử dụng mô hình này để xác định xem một chuỗi văn bản có độ dài không xác định chứa cảm xúc tích cực hay tiêu cực. .. raw:: html .. _fig_nlp-map-sa-rnn: .. figure:: ../img/nlp-map-sa-rnn.svg Phần này sẽ truyền các vector GloVe đã được tiền huấn luyện vào một kiến trúc RNN cho bài toán phân tích cảm xúc. .. code:: python from d2l import mxnet as d2l from mxnet import gluon, init, np, npx from mxnet.gluon import nn, rnn npx.set_np() batch_size = 64 train_iter, test_iter, vocab = d2l.load_data_imdb(batch_size) .. raw:: html Sử dụng Mạng Nơ-ron Hồi tiếp ---------------------------- .. raw:: html Trong mô hình này, đầu tiên mỗi từ nhận được một vector đặc trưng tương ứng từ tầng embedding. Sau đó, ta mã hóa thêm chuỗi đặc trưng bằng cách sử dụng mạng nơ-ron hồi tiếp hai chiều để thu được thông tin chuỗi. Cuối cùng, ta chuyển đổi thông tin chuỗi được mã hóa thành đầu ra thông qua tầng kết nối đầy đủ. Cụ thể, ta có thể ghép nối các trạng thái ẩn của bộ nhớ ngắn hạn dài hai chiều (*bidirectional long-short term memory*) ở bước thời gian ban đầu và bước thời gian cuối cùng và truyền nó tới tầng phân loại đầu ra như là đặc trưng mã hóa của thông tin chuỗi. Trong lớp ``BiRNN`` được lập trình bên dưới, thực thể ``Embedding`` là tầng embedding, thực thể ``LSTM`` là tầng ẩn để mã hóa chuỗi, và thực thể ``Dense`` là tầng đầu ra sinh kết quả phân loại. .. code:: python class BiRNN(nn.Block): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, **kwargs): super(BiRNN, self).__init__(**kwargs) self.embedding = nn.Embedding(vocab_size, embed_size) # Set `bidirectional` to True to get a bidirectional recurrent neural # network self.encoder = rnn.LSTM(num_hiddens, num_layers=num_layers, bidirectional=True, input_size=embed_size) self.decoder = nn.Dense(2) def forward(self, inputs): # The shape of `inputs` is (batch size, no. of words). Because LSTM # needs to use sequence as the first dimension, the input is # transformed and the word feature is then extracted. The output shape # is (no. of words, batch size, word vector dimension). embeddings = self.embedding(inputs.T) # Since the input (embeddings) is the only argument passed into # rnn.LSTM, it only returns the hidden states of the last hidden layer # at different time step (outputs). The shape of `outputs` is # (no. of words, batch size, 2 * no. of hidden units). outputs = self.encoder(embeddings) # Concatenate the hidden states of the initial time step and final # time step to use as the input of the fully connected layer. Its # shape is (batch size, 4 * no. of hidden units) encoding = np.concatenate((outputs[0], outputs[-1]), axis=1) outs = self.decoder(encoding) return outs .. raw:: html Ta sẽ tạo một mạng nơ-ron hồi tiếp hai chiều với hai tầng ẩn như sau. .. code:: python embed_size, num_hiddens, num_layers, devices = 100, 100, 2, d2l.try_all_gpus() net = BiRNN(len(vocab), embed_size, num_hiddens, num_layers) net.initialize(init.Xavier(), ctx=devices) .. raw:: html Nạp các Vector Từ đã qua Tiền huấn luyện ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. raw:: html Bởi vì tập dữ liệu huấn luyện cho việc phân loại cảm xúc không quá lớn, để xử lý vấn đề quá khớp, ta sẽ dùng trực tiếp các vector từ đã được tiền huấn luyện trên tập ngữ liệu lớn hơn làm các vector đặc trưng cho tất cả các từ. Ở đây, ta nạp vector từ Glove 100-chiều cho mỗi từ trong từ điển ``vocab``. .. code:: python glove_embedding = d2l.TokenEmbedding('glove.6b.100d') .. raw:: html Truy vấn các vector từ nằm trong từ vựng của chúng ta. .. code:: python embeds = glove_embedding[vocab.idx_to_token] embeds.shape .. parsed-literal:: :class: output (49339, 100) .. raw:: html Tiếp theo, ta sử dụng các vector từ đó làm vector đặc trưng cho mỗi từ trong các đánh giá. Lưu ý là các chiều của vector từ đã qua tiền huấn luyện cần nhất quán với kích thước đầu ra ``embed_size`` của tầng embedding trong mô hình đã tạo. Thêm vào đó, ta không còn cập nhật các vector từ này trong suốt quá trình huấn luyện. .. code:: python net.embedding.weight.set_data(embeds) net.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ể bắt đầu thực hiện huấn luyện. .. code:: python lr, num_epochs = 0.01, 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.283, train acc 0.881, test acc 0.859 1451.3 examples/sec on [gpu(0)] .. figure:: output_sentiment-analysis-rnn_vn_1038ca_13_1.svg .. raw:: html Cuối cùng, định nghĩa hàm dự đoán. .. code:: python #@save def predict_sentiment(net, vocab, sentence): sentence = np.array(vocab[sentence.split()], ctx=d2l.try_gpu()) label = np.argmax(net(sentence.reshape(1, -1)), axis=1) return 'positive' if label == 1 else 'negative' .. raw:: html Tiếp theo, sử dụng mô hình đã huấn luyện để phân loại cảm xúc cho hai câu đơn giản. .. code:: python predict_sentiment(net, vocab, 'this movie is so great') .. parsed-literal:: :class: output 'positive' .. code:: python predict_sentiment(net, vocab, 'this movie is so bad') .. parsed-literal:: :class: output 'negative' Tóm tắt ------- .. raw:: html - Phân loại văn bản ánh xạ một chuỗi văn bản có độ dài không xác định thành hạng mục tương ứng của văn bản đó. Đây là một tác vụ xuôi dòng của embedding từ. - Ta có thể áp dụng các vector từ được tiền huấn luyện và mạng nơ-ron hồi tiếp để để phân loại cảm xúc trong văn bản. Bài tập ------- .. raw:: html 1. Hãy tăng số epoch. Bạn có thể đạt được độ chính xác là bao nhiêu trên tập huấn luyện và tập kiểm tra? Thử tinh chỉnh các siêu tham số khác và đánh giá kết quả. 2. Liệu sử dụng vector từ được tiền huấn luyện có kích thước lớn hơn, ví dụ vector từ GloVe có kích thước chiều là 300, có thể cải thiện độ chính xác hay không? 3. Ta có thể cải thiện độ chính xác bằng cách sử dụng công cụ token hoá từ spaCy không? Bạn cần cài đặt spaCy bằng lệnh ``pip install spacy`` và cài đặt gói ngôn ngữ tiếng Anh bằng lệnh ``python -m spacy download en``. Trong mã nguồn, đầu tiên hãy nhập thư viện spaCy với câu lệnh ``import spacy``. Tiếp theo, hãy nạp gói spacy tiếng Anh ``spacy_en = spacy.load('en')``. Cuối cùng, hãy định nghĩa hàm ``def tokenizer(text): return [tok.text for tok in spacy_en.tokenizer(text)]`` và thay thế hàm ``tokenizer`` ban đầu. Lưu ý rằng vector từ GloVe sử dụng “-” để kết nối mỗi từ trong cụm danh từ. Ví dụ, cụm từ “new york” được biểu diễn bằng “new-york” trong GloVe. Sau khi sử dụng công cụ token hoá spaCy, “new york” có thể sẽ được lưu thành “new york”. 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 - Nguyễn Văn Quang - Nguyễn Mai Hoàng Long - Nguyễn Lê Quang Nhật - Phạm Hồng Vinh - Phạm Minh Đức *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: 29/08/2020)*