Skip to content

Commit 4c2a6d0

Browse files
jeremiedbhetong007
authored andcommitted
blog RNN bucketing mxnet R (#42)
* blog RNN bucketing mxnet R * apply review fixes
1 parent 28d87fe commit 4c2a6d0

File tree

1 file changed

+205
-0
lines changed

1 file changed

+205
-0
lines changed
+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
---
2+
layout: post
3+
title: "RNN made easy with MXNet R"
4+
date: 2017-10-11
5+
author: Jeremie Desgagne-Bouchard
6+
categories: rstats
7+
comments: true
8+
---
9+
10+
This tutorial presents an example of application of RNN to text classification using padded and bucketed data to efficiently handle sequences of varying lengths. Some functionalities require running on a GPU with CUDA.
11+
12+
Example based on sentiment analysis on the [IMDB data](http://ai.stanford.edu/~amaas/data/sentiment/).
13+
14+
What's special about sequence modeling?
15+
---------------------------------------
16+
17+
Whether working with times series or text at the character or word level, modeling sequences typically involves dealing with samples of varying length.
18+
19+
To efficiently feed the Recurrent Neural Network (RNN) with samples of even length within each batch, two tricks can be used:
20+
21+
- Padding: fill the modeled sequences with an arbitrary word/character up to the longest sequence. This results in sequences of even lengths, but potentially of excessive size for an efficient training.
22+
23+
![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/pad-1.png)![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/pad-2.png)
24+
25+
- Bucketing: apply the padding trick to subgroups of samples split according to their lengths. It results in multiple training sets, or buckets, within which all samples are padded to an even length. Diagram below illustrates how the two previous samples would be pre-processed if using buckets of size 4 and 6.
26+
27+
![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/bucket1-1.png)![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/bucket1-2.png)
28+
29+
Non numeric features such as words need to be transformed into a numeric representation. This task is commonly performed by the embedding operator which first requires to convert words into a 0 based index. The embedding will map a vector of features based on that index. In the example below, the embedding projects each word into 2 new numeric features.
30+
31+
![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/bucket2-1.png)![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/bucket2-2.png)
32+
33+
Data preparation
34+
----------------
35+
36+
For this demo, the data preparation is performed by the script [data_preprocessing_seq_to_one.R](https://github.com/apache/incubator-mxnet/tree/master/example/rnn/bucket_R/data_preprocessing_seq_to_one.R) which involves the following steps:
37+
38+
- Import IMDB movie reviews
39+
- Split each review into a word vector and apply some common cleansing (remove special characters, lower case, remove extra blank space...)
40+
- Convert words into integers and define a dictionary to map the resulting indices with former words
41+
- Aggregate the buckets of samples and labels into a list
42+
43+
To illustrate the benefit of bucketing, two datasets are created:
44+
45+
- `corpus_single_train.rds`: no bucketing, all samples are padded/trimmed to 600 words.
46+
- `corpus_bucketed_train.rds`: samples split into 5 buckets of length 100, 150, 250, 400 and 600.
47+
48+
Below is the example of the assignation of the bucketed data and labels into `mx.io.bucket.iter` iterator. This iterator behaves essentially the same as the `mx.io.arrayiter` except that is pushes samples coming from the different buckets along with a bucketID to identify the appropriate network to use.
49+
50+
``` r
51+
corpus_bucketed_train <- readRDS(file = "data/corpus_bucketed_train.rds")
52+
corpus_bucketed_test <- readRDS(file = "data/corpus_bucketed_test.rds")
53+
54+
vocab <- length(corpus_bucketed_test$dic)
55+
56+
### Create iterators
57+
batch.size = 64
58+
59+
train.data.bucket <- mx.io.bucket.iter(buckets = corpus_bucketed_train$buckets,
60+
batch.size = batch.size,
61+
data.mask.element = 0, shuffle = TRUE)
62+
63+
eval.data.bucket <- mx.io.bucket.iter(buckets = corpus_bucketed_test$buckets,
64+
batch.size = batch.size,
65+
data.mask.element = 0, shuffle = FALSE)
66+
```
67+
68+
Define the architecture
69+
-----------------------
70+
71+
Below are the graph representations of a seq-to-one architecture with LSTM cells. Note that input data is of shape `batch.size X seq.length` while the output of the RNN operator is of shape `hidden.features X batch.size X seq.length`.
72+
73+
For bucketing, a list of symbols is defined, one for each bucket length. During training, at each batch the appropriate symbol is bound according to the bucketID provided by the iterator.
74+
75+
``` r
76+
symbol_single <- rnn.graph(config = "seq-to-one", cell.type = "lstm",
77+
num.rnn.layer = 1, num.embed = 2, num.hidden = 4,
78+
num.decode = 2, input.size = vocab, dropout = 0.5,
79+
ignore_label = -1, loss_output = "softmax",
80+
output_last_state = F, masking = T)
81+
```
82+
83+
``` r
84+
bucket_list <- unique(c(train.data.bucket$bucket.names, eval.data.bucket$bucket.names))
85+
86+
symbol_buckets <- sapply(bucket_list, function(seq) {
87+
rnn.graph(config = "seq-to-one", cell.type = "lstm",
88+
num.rnn.layer = 1, num.embed = 2, num.hidden = 4,
89+
num.decode = 2, input.size = vocab, dropout = 0.5,
90+
ignore_label = -1, loss_output = "softmax",
91+
output_last_state = F, masking = T)})
92+
93+
graph.viz(symbol_single, type = "graph", direction = "LR",
94+
graph.height.px = 50, graph.width.px = 800, shape=c(64, 5))
95+
```
96+
97+
![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/architect-1.png)
98+
99+
The representation of an unrolled RNN typically assumes a fixed length sequence. The operator `mx.symbol.RNN` simplifies the process by abstracting the recurrent cells into a single operator that accepts batches of varying length (each batch contains sequences of identical length).
100+
101+
Train the model
102+
---------------
103+
104+
First the non bucketed model is trained for 6 epochs:
105+
106+
``` r
107+
devices <- mx.gpu(0)
108+
109+
initializer <- mx.init.Xavier(rnd_type = "gaussian", factor_type = "avg", magnitude = 2.5)
110+
111+
optimizer <- mx.opt.create("rmsprop", learning.rate = 1e-3, gamma1 = 0.95, gamma2 = 0.92,
112+
wd = 1e-4, clip_gradient = 5, rescale.grad=1/batch.size)
113+
114+
logger <- mx.metric.logger()
115+
epoch.end.callback <- mx.callback.log.train.metric(period = 1, logger = logger)
116+
batch.end.callback <- mx.callback.log.train.metric(period = 50)
117+
118+
system.time(
119+
model <- mx.model.buckets(symbol = symbol_single,
120+
train.data = train.data.single, eval.data = eval.data.single,
121+
num.round = 5, ctx = devices, verbose = FALSE,
122+
metric = mx.metric.accuracy, optimizer = optimizer,
123+
initializer = initializer,
124+
batch.end.callback = NULL,
125+
epoch.end.callback = epoch.end.callback)
126+
)
127+
```
128+
129+
## user system elapsed
130+
## 205.214 17.253 210.265
131+
132+
![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/logger1-1.png)
133+
134+
Then training with the bucketing trick. Note that no additional effort is required: just need to provide a list of symbols rather than a single one and have an iterator pushing samples from the different buckets.
135+
136+
``` r
137+
devices <- mx.gpu(0)
138+
139+
initializer <- mx.init.Xavier(rnd_type = "gaussian", factor_type = "avg", magnitude = 2.5)
140+
141+
optimizer <- mx.opt.create("rmsprop", learning.rate = 1e-3, gamma1 = 0.95, gamma2 = 0.92,
142+
wd = 1e-4, clip_gradient = 5, rescale.grad=1/batch.size)
143+
144+
logger <- mx.metric.logger()
145+
epoch.end.callback <- mx.callback.log.train.metric(period = 1, logger = logger)
146+
batch.end.callback <- mx.callback.log.train.metric(period = 50)
147+
148+
system.time(
149+
model <- mx.model.buckets(symbol = symbol_buckets,
150+
train.data = train.data.bucket, eval.data = eval.data.bucket,
151+
num.round = 5, ctx = devices, verbose = FALSE,
152+
metric = mx.metric.accuracy, optimizer = optimizer,
153+
initializer = initializer,
154+
batch.end.callback = NULL,
155+
epoch.end.callback = epoch.end.callback)
156+
)
157+
```
158+
159+
## user system elapsed
160+
## 129.578 11.500 125.120
161+
162+
![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/logger2-1.png)
163+
164+
The speedup is substantial, around 125 sec. instead of 210 sec., a 40% speedup with little effort.
165+
166+
Plot word embeddings
167+
--------------------
168+
169+
Word representation can be visualized by looking at the assigned weights in any of the embedding dimensions. Here, we look simultaneously at the two embeddings learnt in the LSTM model.
170+
171+
![](https://raw.githubusercontent.com/dmlc/web-data/master/mxnet/blog_mxnet_R_rnn_bucket/embed-1.png)
172+
173+
Since the model attempts to predict the sentiment, it's no surprise that the 2 dimensions into which each word is projected appear correlated with words' polarity. Positive words are associated with lower X1 values ("great", "excellent"), while the most negative words appear at the far right ("terrible", "worst"). By representing words of similar meaning with features of values, embedding much facilitates the remaining classification task for the network.
174+
175+
Inference on test data
176+
----------------------
177+
178+
The utility function `mx.infer.buckets` has been added to simplify inference on RNN with bucketed data.
179+
180+
``` r
181+
ctx <- mx.gpu(0)
182+
batch.size <- 64
183+
184+
corpus_bucketed_test <- readRDS(file = "data/corpus_bucketed_test.rds")
185+
186+
test.data <- mx.io.bucket.iter(buckets = corpus_bucketed_test$buckets,
187+
batch.size = batch.size,
188+
data.mask.element = 0, shuffle = FALSE)
189+
```
190+
191+
``` r
192+
infer <- mx.infer.buckets(infer.data = test.data, model = model, ctx = ctx)
193+
194+
pred_raw <- t(as.array(infer))
195+
pred <- max.col(pred_raw, tie = "first") - 1
196+
label <- unlist(lapply(corpus_bucketed_test$buckets, function(x) x$label))
197+
198+
acc <- sum(label == pred)/length(label)
199+
roc <- roc(predictions = pred_raw[, 2], labels = factor(label))
200+
auc <- auc(roc)
201+
```
202+
203+
Accuracy: 87.6%
204+
205+
AUC: 0.9436

0 commit comments

Comments
 (0)