Loves Me, Loves Me Not: Classify Texts with TensorFlow and Twilio
Tags: tensorflow, machinelearning, ai, twilio
Published: 2020-02-14 01:38:42

---
title: Loves Me, Loves Me Not: Classify Texts with TensorFlow and Twilio
published: true
description: 
tags: tensorflow, machinelearning, ai, twilio
---

![cover](https://twilio-cms-prod.s3.amazonaws.com/images/2J3PTh5SFa4VkdFCJNeTjN28UgJbG6nvfEZxa_FMNe5mXF.width-808.png)
This blog post was written for [Twilio](https://www.twilio.com/) and [originally published on the Twilio blog](https://www.twilio.com/blog/classify-texts-with-tensorflow-and-twilio-to-answer-loves-me-loves-me-not).

Valentine's Day is coming up and both love and machine learning are in the air. Some would use flower petals to determine if someone loves them or not, but developers might use a tool like TensorFlow. This post will go over how to perform binary text classification with neural networks using Twilio and TensorFlow in Python.
![sms example](https://twilio-cms-prod.s3.amazonaws.com/images/AQsxdzmUnZGmk_MbrrZXzKxrYyJjpshaQjVmNfeJW5fEJj.width-500.png)
### Prerequisites
- A Twilio account - [sign up for a free one here](https://www.twilio.com/try-twilio)
- A Twilio phone number with SMS capabilities - [configure one here](https://www.twilio.com/console/phone-numbers/search)
- [Set up your Python and Flask developer environment](https://www.twilio.com/docs/usage/tutorials/how-to-set-up-your-python-and-flask-development-environment) - Make sure you have [Python 3](https://www.python.org/downloads/) downloaded as well as [ngrok](https://ngrok.com/).

### Setup
![bear picking flowers loves me loves me not](https://twilio-cms-prod.s3.amazonaws.com/original_images/lovesmenotbear.gif)
Activate a virtual environment in Python 3 and download this <em>requirements.txt</em> file. Be sure to use Python 3.6.x for TensorFlow. On the command line run `pip3 install -r requirements.txt` to import all the necessary libraries and then to import `nltk`, make a new directory with `mkdir nltk_data`, `cd` into it and then run `python3 -m nltk.downloader`. You should see a window like this, select all packages as shown in the screenshot below:
![nltk downloader](https://twilio-cms-prod.s3.amazonaws.com/images/Hg4YuyA3k1mPRDQypW4qugewg1N6cqrF79809MlHkmNKI.width-1000.png)
Your Flask app will need to be visible from the web so Twilio can send requests to it. Ngrok simplifies this. With Ngrok installed, run `ngrok http 5000` in the directory your code is in.
![ngrok url](https://twilio-cms-prod.s3.amazonaws.com/images/Screen_Shot_2020-02-13_at_3.58.34_PM.width-500.png)

You should see the screen above. Grab that ngrok URL to configure your Twilio number:
![configure Twilio phone number](https://twilio-cms-prod.s3.amazonaws.com/images/oSUGCLGGKkAeNQ-EjRHUBFMUgl_xjMoZ9bw98UrWPOcbD.width-1000.png)

### Prepare Training Data
Make a new file called <strong>data.json</strong> to contain two arrays of phrases corresponding to labels: either "loves me" or "loves me not". Feel free to modify phrases to the arrays or add your own (the more training data, the better--this is not close to being enough but it's a fun start.)

```json
{
    "loves me": [
        "do you want some food",
        "you're so nice",
        "i got you some food",
        "I like your hair",
        "You looked nice today",
        "Let's dance",
        "I spent time on this for you",
        "i got this for you",
        "heyyyyyyy",
        "i got you pizza"
    ],
    "loves me not": [
        "I didn't have the time",
        "Can you get your own food",
        "You'll have to get your own food",
        "Do it yourself",
        "i can't",
        "next time",
        "i'm sorry",
        "you up",
        "hey",
        "wyd",
        "k", 
        "idk man",
        "cool"
    ]
}
```
Make a Python file called `main.py`. At the top import the required libraries, then make a function `open_file` to save the data from `data.json` as a variable `data`.
```python
import re
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
import numpy as np
import tflearn
import tensorflow as tf
import random
import json
from twilio.twiml.messaging_response import MessagingResponse
from flask import Flask, request

def open_file(file):
    with open(file, 'r') as f:
        data = json.load(f)
        return data
data = open_file('data.json')
print(data)
```

### Read Training Data
This post will use a [lemmatizer](https://nlp.stanford.edu/IR-book/html/htmledition/stemming-and-lemmatization-1.html) to get to the base of a word, ie. turning "going" into "go". A stemmer, which also reduces words to their word stem, could be used for this task but would be unable to identify that "good" is the lemma of "better." Though lemmas take more time to use, they tend to be more efficient. You can experiment with both stemmers and lemmatizers when working with natural language processing (NLP).

Right underneath the `data` variable declaration, initialize the lemmatizer and make this function to stem each word:
```python
lemma = WordNetLemmatizer()
def tokenize_and_stem_text(text):
    return [lemma.lemmatize(word.lower()) for word in text] 
binary_categories = list(data.keys())
training_words = []
json_data = []
```
This next function will read the training data, remove punctuation, handle contractions, and extract words in each sentence, appending them to a word list.

Next, get the possible labels ("loves me" and "loves me not") that the model will train for and initialize an empty list `json_data` to hold tuples of words from the sentence and also the label name. The `training_words` list will contain all the unique stemmed words from the training data JSON and `binary_categories` contains the possible categories they could classify as.
```python
def read_training_data(data):
    for label in data.keys(): 
        for text in data[label]:
            for word in text.split():
                if word.lower() in contractions:
                    text = text.replace(word, contractions[word.lower()])
            text = re.sub("[^a-zA-Z' ]+", ' ', text)
            training_words.extend(word_tokenize(text))
            json_data.append((word_tokenize(text), label))
    return json_data
```
The `json_data` returned is a list of words from each sentence and either <em>loves_me</em> or <em>loves_me_not</em>; for example, one element of that list is `([“do”, “you”, “want”, "some", "food], “loves_me”)`. This list does not cover every possible contraction but you get the idea:
```python
contractions = {
    "aren't": "are not",
    "can't": "cannot",
    "could've": "could have",
    "couldn't": "could not",
    "didn't": "did not",
    "don't": "do not",
    "hadn't": "had not",
    "hasn't": "has not",
    "haven't": "have not",
    "how'd": "how did",
    "how's": "how is",
    "i'd": "I had",
    "i'll": "I will",
    "i'm": "I am",
    "i've": "I have",
    "isn't": "is not",
    "let's": "let us",
    "should've": "should have",
    "shouldn't": "should not",
    "that'd": "that had",
    "that's": "that is",
    "there's": "there is",
    "wasn't": "was not",
    "we'd": "we would",
    "we'll": "we will",
    "we're": "we are",
    "we've": "we have",
    "what'll": "what  will",
    "what's": "what is",
    "when's": "when is",
    "where'd": "where did",
    "where's": "where is",
    "won't": "will not",
    "would've": "would have",
    "wouldn't": "would not",
    "you'd": "you had",
    "you'll": "you will",
    "you're": "you are",
}
```
Then stem each word to remove duplicates and call the `read_training_data` function.
```python
training_words = tokenize_and_stem_text(training_words)
print(read_training_data(data))
read_training_data(data)
```
For TensorFlow to understand this data the strings must be converted into numbers. This can be done with the [bag-of-words NLP model](https://en.wikipedia.org/wiki/Bag-of-words_model), which keeps a count of the total number of occurrences of the most commonly-used words. For example, the sentence "Never gonna give you up never gonna let you down" could be represented as:
!["never gonna give you up, never gonna let you down" bag-of-words nlp model representation](https://twilio-cms-prod.s3.amazonaws.com/images/dZj3AsDLqlLSS2l0dd7lSD5VLAF2qor4nMrOVkpHSzl5t.width-1000.png)
For the <em>loves_me</em> and <em>loves_me_not</em> labels a bag-of-words is initiated as a list of tokenized words, called `vector` here. We loop through the words in the phrase, stemming them and comparing with each word in the vocabulary. If the sentence has a word in our training data or vocabulary, `1` is appended to the vector, signaling which label the word belongs to. If not, a `0` is appended.

At the end our training set has a bag-of-words model and the output row corresponding to the label the bag belongs to.
```python
training = []
for item in json_data:
    bag_vector = []
    token_words = item[0]
    token_words = [lemma.lemmatize(word.lower()) for word in token_words]
    for word in training_words:
        if word in token_words:
            bag_vector.append(1) 
        else:
            bag_vector.append(0)
    output_row = list([0] * len(binary_categories)) 
    output_row[binary_categories.index(item[1])] = 1
    training.append([bag_vector, output_row])
```
Convert `training` to a [`numpy` array](https://numpy.org/doc/1.18/reference/generated/numpy.array.html) so TensorFlow can process it as well, and split it into two variables: `data` has the bag of words and `labels` has the label.
```python
training = np.array(training)
data = list(training[:, 0])
labels = list(training[:, 1])
```
Now reset the underlying graph data, and clear defined variables and operations from the previous cell each time the model is run. Next build a neural network with three layers:
1. The `input_data` input layer is for inputting or feeding data to a network, and the input to the network has size `len(data[0])` for the length of our encoded bag of words and labels.
2. Then make two fully-connected intermediate layers with 32 hidden units or neurons. While some functions need more than one layer to run, more than three layers probably won't make a difference, so two layers is enough and shouldn't be too computationally-expensive. We use the `softmax` activation function in this case because the labels are exclusive.
3. Lastly, we make the final net from the [estimator](http://tflearn.org/layers/estimator/) layer, like regression. At a high level, regression (linear or logistic) helps predict the outcome of an event based on the data. Neural networks have multiple layers to better learn more complicated abstractions relationships from the input.
```python
tf.reset_default_graph()
net = tflearn.input_data(shape=[None, len(data[0])]) 
net = tflearn.fully_connected(net, 32)
net = tflearn.fully_connected(net, len(labels[0]), activation='softmax') 
net = tflearn.regression(net)
```
A deep neural network (DNN) automatically performs neural network classifier tasks like [training](https://developers.google.com/machine-learning/crash-course/training-neural-networks/video-lecture) the model and prediction based on input. Calling the `fit` method begins training and applies the gradient descent algorithm, a common first-order optimization deep learning algorithm. `n_epoch` is the number of times the network will see all the data and `batch_size` is the size data is sliced in to for the model to train on.
```python
model = tflearn.DNN(net)
model.fit(data, labels, n_epoch=100, batch_size=16, show_metric=True)
```
Similar to how the data for the bag-of-words model was processed, this data needs to be converted to a numerical form that can be passed to TensorFlow.
```python
def clean_for_tf(text):
    input_words = tokenize_and_stem_text(word_tokenize(text))
    vector = [0]*len(training_words)
    for input_word in input_words:
        for ind, word in enumerate(training_words):
            if word == input_word:
                vector[ind] = 1
    return(np.array(vector))
```
To test this without text messages you could add
```python
tensor = model.predict([clean_for_tf(INSERT-TEXT-HERE)])
print(binary_categories[np.argmax(tensor)])
```
This calls the `predict` method on the model, getting the position of the largest value which represents the prediction.

We will test this with text messages by building a Flask application.

### Create a Flask App
Add the following code to make a Flask app, get the inbound text message, create a tensor, and call the model.
```python
app = Flask(__name__)
@app.route("/sms", methods=['POST'])
def sms():
    resp = MessagingResponse()
    inbMsg = request.values.get('Body').lower().strip()
    tensor = model.predict([clean_for_tf(inbMsg)])
    resp.message(
        f'The message {inbMsg!r} corresponds to {binary_categories[np.argmax(tensor)]!r}.')
    return str(resp)
```
Open a new terminal tab separate from the one running ngrok. In the folder housing your code run and text your Twilio number a phrase like "get someone else to do it" and you should see something like this:
![classify "get someone else to do it"](https://twilio-cms-prod.s3.amazonaws.com/images/oO45c-EyYf93hzpIRxuTB16pR2c9hvBXw_LyFABdJH-OH.width-1000.png)
The complete code and `requirements.txt` can be found on [GitHub here](https://github.com/elizabethsiegle/Loves-me-loves-me-not-tensorflow-python-sms).

### What's Next
![Ariel doing loves me-loves me not with flowers underwater gif](https://twilio-cms-prod.s3.amazonaws.com/original_images/rB9dehQ4Gixo4rg5UREdvWipnBCgpRMiKwA10mjwPyEInx3l80heNGfQP3K05Rmsu_6x1jvQUN3wJH)
What will you classify next? You could use [TensorFlow's Universal Sentence Encoder](https://github.com/tensorflow/tfjs-models/tree/master/universal-sentence-encoder) to perform similar text classification in JavaScript, classify phone calls or emails, use a different activation function like <em>sigmoid</em> if you have categories that are mutually exclusive, and more. Let me know what you're building online or in the comments.  
* GitHub: [elizabethsiegle](http://github.com/elizabethsiegle)
* Twitter: [@lizziepika](http://twitter.com/lizziepika)
* email: [lsiegle@twilio.com](lsiegle@twilio.com)