<jupyter_start><jupyter_text>Name that Speaker> Using fastai to guess who might say a particular phrase in a chat conversation- toc: true- badges: true- comments: true- author: <NAME>- categories: [jupyter]- comments: true  IntroThe first few lessons of the [fastai course](https://course.fast.ai/) lean heavily towards computer vision problems with their examples. Personally, I am a little more interested in natural language processing and work with text applications, so I glommed onto their example of doing sentiment analysis of movie reviews using fastai. Here's how they built that model using the IMDB dataset internal to the library:<jupyter_code>from fastai.text.all import *

dls = TextDataLoaders.from_folder(untar_data(URLs.IMDB), valid='test', encoding='utf8', bs=32)
learn = text_classifier_learner(dls, AWD_LSTM, drop_mult=0.5, metrics=accuracy)
learn.fine_tune(4, 1e-2)<jupyter_output><empty_output><jupyter_text>The generated model `learn` can then be used to predict the sentiment of a statement. I picked three statements below to show what it's predictions are like. The model predicts the first two statements accurately and is fairly confident in its prediction. For the third, the model predicts the sentiment, but isn't as confident, which is not surprising since I picked that one to be intentionally tricky.<jupyter_code>x = learn.predict("I really liked that movie!")
y = learn.predict("At no point in your rambling, incoherent response was there anything that could even be considered a rational thought. Everyone in this room is now dumber for having listened to it. I award you no points, and may God have mercy on your soul.")
z = learn.predict("I thought it was going to be good, but it really was not in the end.")
print(f"Sentiment of x: {x[0]}, prob={x[2][1]:.4f}")
print(f"Sentiment of y: {y[0]}, prob={y[2][0]:.4f}")
print(f"Sentiment of z: {z[0]}, prob={z[2][0]:.4f}")<jupyter_output><empty_output><jupyter_text>My idea was to take this template for building a text classification model and use it to classify the "speaker" of a given statement, given a previous set of chat conversations to train on.  Data PreparationIn a previous post, I took some data from a Google Hangouts chat and converted it to a format more palatable to feeding into a PyTorch LSTM, i.e. each chat message was broken up to be in the format > Speaker :: Message I'm going to use the same underlying data here, but format it slightly differently to ease import into [fastai](https://www.fast.ai/). This might not be the cleanest way to do this, but it worked :smile:The format I ended up using was a modified csv. Commas are pretty prevalent in the data and I hate using quotes and escapes, so I used `|` to separate the columns {% fn 1 %}. Since I had already done the separation of speaker and message using `::` before, the script to convert was fairly straightforward, minus one spot where someone had used an SAT-style analogy > Kappa :: Omega:OK :: Gamma:"Here's the thing"<jupyter_code>chat_filename = "/notebooks/fastbook/chat/chatFile.txt"
chat_csv = "/notebooks/fastbook/chat/chatFile.csv"

# Read in the chat file with 
data = open(chat_filename, encoding='utf8').read()
# As software developers, we used "||" a few places to mean OR
data = data.replace("||", "or")
data = data.splitlines()

# Write to csv
with open(chat_csv, encoding='utf8', mode='w') as csv:
    # Header
    csv.write("Name|Message")
    # New message
    for line in data:
        if "::" in line:
            x = line.split("::")
            if len(x) > 2:
                (name, msg) = ("Kappa", "Omega:Ok :: Gamma:Here's the thing")
            else:
                (name, msg) = line.split("::")
            name = name.strip()
            msg = msg.strip()
            csv.write('\n')
            csv.write(name)
            csv.write("|")
            csv.write(msg)
        else:
            csv.write(" " + msg)
    csv.write('\n')<jupyter_output><empty_output><jupyter_text>Build ModelThe csv now matched each message to a particular speaker in a format that was easily digestible by `fastai`. Next, I mimicked the sentimental analysis example above to make my speaker identification model. I'm essentially just swapping `from_folder` out for `from_csv`, with some extra arguments to give details about my csv.<jupyter_code>from fastai.text.all import *

dls = TextDataLoaders.from_csv('.', csv_fname=chat_csv, 
                               delimiter="|", text_col = 1, label_col = 0)
learn = text_classifier_learner(dls, AWD_LSTM, drop_mult=0.5, metrics=accuracy)
learn.fine_tune(4, 1e-2)<jupyter_output><empty_output><jupyter_text>Note that there's a lot less data here than in the IMDB set, so training is much faster. Also, I ignored the warning now since it was just a deprecation warning. Not sure if that'll bite me later. Next, let's save the model to a file for later use<jupyter_code>learn.export("/notebooks/fastbook/chat/chat_model.pkl")<jupyter_output><empty_output><jupyter_text>My First AppThe challenge in the second lesson of the `fastai` course was to create a model using `fastai` and turn it into a prototype web app. The structure of how to do so using `ipywidgets` and `voila` was pretty straightforward. A box for giving the text to evaluate<jupyter_code>import ipywidgets as widgets
txtInput = widgets.Textarea(placeholder='Input text...', description='Text:')
txtInput<jupyter_output><empty_output><jupyter_text>A button to execute the prediction for the model<jupyter_code>button = widgets.Button(description='Predict',
                        tooltip='Click me',
                        icon='question')
button<jupyter_output><empty_output><jupyter_text>Set up the output widget with a dividing line<jupyter_code>outWidget = widgets.Output(layout={'border': '1px solid black'})
outWidget
def on_click_classify(change):
    # predictions and probabilities from the model
    prediction, idx, probs = learn_inf.predict(txtInput.value)   
    # pair the probabilities with each speaker
    outputs = list(zip(probs, learn_inf.dls.vocab[1]))
    # sort the list with the most likely speaker first
    outputs.sort(reverse=True)
    outWidget.clear_output()
    # Print the output, with the most likely speaker in bold
    with outWidget:
        header = widgets.HTML()
        header.value = '<u>Scores</u>'
        display(header)
        lblPred = widgets.HTML()
        lblPred.value = f'<b>{outputs[0][1]}</b>: <b>{100 * outputs[0][0]:.2f}%</b>'
        display(lblPred)
        for (prob, name) in outputs[1:]:
            lbl = widgets.Label()
            lbl.value = f'{name}:  {100 * prob:.2f}%'
            display(lbl)

button.on_click(on_click_classify)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09900401367623012, 'fraction_numerical': 0.0068381150587185965, 'mean_word_length': 3.968980797636632, 'pattern_counts': {'":': 3, '<': 40, '<?xml version=': 0, '>': 44, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '80950', 'license': 'Apache-2.0', 'n_tokens_mistral': 2020, 'n_tokens_neox': 1951, 'n_words': 771, 'path': '_notebooks/2020-11-18-name-that-speaker.ipynb', 'repo_name': 'bobowedge/adventures-in-telework', 'size': '6731'}