Photo credit: Pixabay

Multi-Class Text Classification Model Comparison and Selection

Natural Language Processing, word2vec, Support Vector Machine, bag-of-words, deep learning

Susan Li
Towards Data Science
7 min readSep 25, 2018

--

When working on a supervised machine learning problem with a given data set, we try different algorithms and techniques to search for models to produce general hypotheses, which then make the most accurate predictions possible about future instances. The same principles apply to text (or document) classification where there are many models can be used to train a text classifier. The answer to the question “What machine learning model should I use?” is always “It depends.” Even the most experienced data scientists can’t tell which algorithm will perform best before experimenting them.

This is what we are going to do today: use everything that we have presented about text classification in the previous articles (and more) and comparing between the text classification models we trained in order to choose the most accurate one for our problem.

The Data

We are using a relatively large data set of Stack Overflow questions and tags. The data is available in Google BigQuery, it is also publicly available at this Cloud Storage URL: https://storage.googleapis.com/tensorflow-workshop-examples/stack-overflow-data.csv.

Exploring the Data

explore
Figure 1

10276752

We have over 10 million words in the data.

my_tags = ['java','html','asp.net','c#','ruby-on-rails','jquery','mysql','php','ios','javascript','python','c','css','android','iphone','sql','objective-c','c++','angularjs','.net']
plt.figure(figsize=(10,4))
df.tags.value_counts().plot(kind='bar');
Figure 2

The classes are very well balanced.

We want to have a look a few post and tag pairs.

def print_plot(index):
example = df[df.index == index][['post', 'tags']].values[0]
if len(example) > 0:
print(example[0])
print('Tag:', example[1])
print_plot(10)
Figure 3
print_plot(30)
Figure 4

As you can see, the texts need to be cleaned up.

Text Pre-processing

The text cleaning techniques we have seen so far work very well in practice. Depending on the kind of texts you may encounter, it may be relevant to include more complex text cleaning steps. But keep in mind that the more steps we add, the longer the text cleaning will take.

For this particular data set, our text cleaning step includes HTML decoding, remove stop words, change text to lower case, remove punctuation, remove bad characters, and so on.

clean_text

Now we can have a look a cleaned post:

Figure 5

Way better!

df['post'].apply(lambda x: len(x.split(' '))).sum()

3421180

After text cleaning and removing stop words, we have only over 3 million words to work with!

After splitting the data set, the next steps includes feature engineering. We will convert our text documents to a matrix of token counts (CountVectorizer), then transform a count matrix to a normalized tf-idf representation (tf-idf transformer). After that, we train several classifiers from Scikit-Learn library.

X = df.post
y = df.tags
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state = 42)

Naive Bayes Classifier for Multinomial Models

After we have our features, we can train a classifier to try to predict the tag of a post. We will start with a Naive Bayes classifier, which provides a nice baseline for this task. scikit-learn includes several variants of this classifier; the one most suitable for text is the multinomial variant.

To make the vectorizer => transformer => classifier easier to work with, we will use Pipeline class in Scilkit-Learn that behaves like a compound classifier.

nb
Figure 6

We achieved 74% accuracy.

Linear Support Vector Machine

Linear Support Vector Machine is widely regarded as one of the best text classification algorithms.

svm
Figure 7

We achieve a higher accuracy score of 79% which is 5% improvement over Naive Bayes.

Logistic Regression

Logistic regression is a simple and easy to understand classification algorithm, and Logistic regression can be easily generalized to multiple classes.

logreg
Figure 8

We achieve an accuracy score of 78% which is 4% higher than Naive Bayes and 1% lower than SVM.

As you can see, following some very basic steps and using a simple linear model, we were able to reach as high as an 79% accuracy on this multi-class text classification data set.

Using the same data set, we are going to try some advanced techniques such as word embedding and neural networks.

Now, let’s try some complex features than just simply counting words.

Word2vec and Logistic Regression

Word2vec, like doc2vec, belongs to the text preprocessing phase. Specifically, to the part that transforms a text into a row of numbers. Word2vec is a type of mapping that allows words with similar meaning to have similar vector representation.

The idea behind Word2vec is rather simple: we want to use the surrounding words to represent the target words with a Neural Network whose hidden layer encodes the word representation.

First we load a word2vec model. It has been pre-trained by Google on a 100 billion word Google News corpus.

from gensim.models import Word2Vecwv = gensim.models.KeyedVectors.load_word2vec_format("GoogleNews-vectors-negative300.bin.gz", binary=True)
wv.init_sims(replace=True)

We may want to explore some vocabularies.

from itertools import islice
list(islice(wv.vocab, 13030, 13050))
Figure 9

BOW based approaches that includes averaging, summation, weighted addition. The common way is to average the two word vectors. Therefore, we will follow the most common way.

word_averaging

We will tokenize the text and apply the tokenization to “post” column, and apply word vector averaging to tokenized text.

w2v_tokenize_text

Its time to see how logistic regression classifiers performs on these word-averaging document features.

from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(n_jobs=1, C=1e5)
logreg = logreg.fit(X_train_word_average, train['tags'])
y_pred = logreg.predict(X_test_word_average)
print('accuracy %s' % accuracy_score(y_pred, test.tags))
print(classification_report(test.tags, y_pred,target_names=my_tags))
Figure 10

It was disappointing, worst we have seen so far.

Doc2vec and Logistic Regression

The same idea of word2vec can be extended to documents where instead of learning feature representations for words, we learn it for sentences or documents. To get a general idea of a word2vec, think of it as a mathematical average of the word vector representations of all the words in the document. Doc2Vec extends the idea of word2vec, however words can only capture so much, there are times when we need relationships between documents and not just words.

The way to train doc2vec model for our Stack Overflow questions and tags data is very similar with when we train Multi-Class Text Classification with Doc2vec and Logistic Regression.

First, we label the sentences. Gensim’s Doc2Vec implementation requires each document/paragraph to have a label associated with it. and we do this by using the TaggedDocument method. The format will be “TRAIN_i” or “TEST_i” where “i” is a dummy index of the post.

label_sentences

According to Gensim doc2vec tutorial, its doc2vec class was trained on the entire data, and we will do the same. Let’s have a look what the tagged document looks like:

all_data[:2]
Figure 11

When training the doc2vec, we will vary the following parameters:

  • dm=0 , distributed bag of words (DBOW) is used.
  • vector_size=300 , 300 vector dimensional feature vectors.
  • negative=5 , specifies how many “noise words” should be drawn.
  • min_count=1, ignores all words with total frequency lower than this.
  • alpha=0.065 , the initial learning rate.

We initialize the model and train for 30 epochs.

train_doc2vec

Next, we get vectors from trained doc2vec model.

get_vectors

Finally, we get a logistic regression model trained by the doc2vec features.

logreg = LogisticRegression(n_jobs=1, C=1e5)
logreg.fit(train_vectors_dbow, y_train)
logreg = logreg.fit(train_vectors_dbow, y_train)
y_pred = logreg.predict(test_vectors_dbow)
print('accuracy %s' % accuracy_score(y_pred, y_test))
print(classification_report(y_test, y_pred,target_names=my_tags))
Figure 12

We achieve an accuracy score of 80% which is 1% higher than SVM.

BOW with Keras

Finally, we are going to do a text classification with Keras which is a Python Deep Learning library.

The following code were largely taken from a Google workshop. The process is like this:

  • Separate the data into training and test sets.
  • Use tokenizer methods to count the unique words in our vocabulary and assign each of those words to indices.
  • Calling fit_on_texts() automatically creates a word index lookup of our vocabulary.
  • We limit our vocabulary to the top words by passing a num_words param to the tokenizer.
  • With our tokenizer, we can now use the texts_to_matrix method to create the training data that we’ll pass our model.
  • We feed a one-hot vector to our model.
  • After we transform our features and labels in a format Keras can read, we are ready to build our text classification model.
  • When we build our model, all we need to do is tell Keras the shape of our input data, output data, and the type of each layer. keras will look after the rest.
  • When training the model, we’ll call the fit() method, pass it our training data and labels, batch size and epochs.
keras_training
Figure 13

The accuracy is:

score = model.evaluate(x_test, y_test,
batch_size=batch_size, verbose=1)
print('Test accuracy:', score[1])
Figure 14

So, which model is the best for this particular data set? I will leave it to you to decide.

Jupyter notebook can be found on Github. Have a productive day!

References:

https://github.com/RaRe-Technologies/movie-plots-by-genre/blob/master/ipynb_with_output/Document%20classification%20with%20word%20embeddings%20tutorial%20-%20with%20output.ipynb

https://github.com/tensorflow/workshops/blob/master/extras/keras-bag-of-words/keras-bow-model.ipynb

https://datascience.stackexchange.com/questions/20076/word2vec-vs-sentence2vec-vs-doc2vec

--

--