<jupyter_start><jupyter_text>HW 03 - Create a word2vec model Before you submit this assignment, please carefully read these submission instructions. You must name this .ipynb file:yourlastname_yourfirstname_HW3.ipynbYou must turn in this assignment by uploading the .ipynb file to the assignment on questrom tools. You will also need to print out a hard copy of this notebook (File->Print from colab) with the output from running all the code cells, and hand it in on the class following the due date. Do not email me the file.Points will be deducted for improper submission!For this assignment, we will use Pytorch to create a word2vec model that infers numerical vectors for words that capture their meaning. Word2vec was first introduced in 2013 by Mikolov et al. at Google. Their paper can be found [here](https://arxiv.org/pdf/1301.3781.pdf), though you do not need to read and understand it in order to implement the model. It is a very popular machine learning model that has been implemented to capture the meaning of text for many real world cases. This [blog post](https://blog.acolyer.org/2016/04/21/the-amazing-power-of-word-vectors/) is a great overview of word2vec. Please read it carefully before you create the word2vec model for this assignment. Specifically, you will build a "Continuous Bag-of-Words Model (CBOW)" word2vec model. CBOW predicts a focal (target) word from its context (the words surround it). The following Youtube videos also explain the concept of the CBOW model.- https://www.youtube.com/watch?v=UqRCEmrv1gQ- https://www.youtube.com/watch?v=gQddtTdmG_8 [CBOW structure from http://www.stokastik.in/understanding-word-vectors-and-word2vec/] Your task is to create a CBOW neural network model class called `CBOW`. `CBOW` has the structure shown above and the following properties:- `vocab_size` - Size of vocabulary($V$). Note that vocabulary is a set of unique words in a corpus (a bunch of text documents).- `embed_dim` - Dimension of the embedding vector- `window_size` - Size of window. If a focal word is at position $t$, then the CBOW model uses embedding vectors of words between ($t$-window_size) and ($t$+window_size) to predict the focal word- `hidden_dim` - Dimension of the hidden layer ($N$)`CBOW` consists of three layers:- `embedding` - An embedding layer that is initialized with `torch.nn.Embedding`- `fc1` - A linear transformation that connects the embedding layer to the hidden layer. `torch.nn.functional.relu` activation should be applied to the output of `fc1`.- `fc2` - A linear transformation that connects the activation of `fc1` to a tensor of length `vocab_size`. The training data (i.e., the features `X`, the labels `y`) that we will use to train the `CBOW` model will be:- `X` will be a tensor of length (2 * `window_size`) containing the indices of all words in the window before and after the focal word. - `y`, (the label that our model is trying to predict) should be a list containing the index of the focal word.Note that a single review in our data will produce multiple items of training data. For example, suppose a single review is: "the food was not good at all"  If our `window_size` = 2, then this would generate the following (context, focal_word) training data tuples:```python(['the','food','not','good'], ['was'])  'was' is the focal word(['food','was','good','at'], ['not'])  'not' is the focal word(['was','not','at','all'],['good'])  'good' is the focal word```However, we can't directly use these tuples to train our model. First we have to replace each word with a unique integer (its index) and then convert these to pytorch tensors. Note that, we will be using a special embedding layer (`torch.nn.Embedding`) which will convert these indexes to the one-hot vectors that are described in the videos.To get tensors from the original data, you will need to:- Create a list (or `set`) of all unique words in the cleaned text, called `vocab`.- Create a dictionary called `word_to_index` where the key is a word and the value is the index of a word (a unique number for each word). You will have to figure out how to create this dictionary from the cleaned dataset.- Write a function `make_cbow_data` that accepts a single review from cleaned_text as an input and outputs a **list of tuples** where: -  the first part of the tuple contains a tensor of the indices of words in the window before and after each focal word - the second part of the tuple is a tensor containing the index of the focal word. - The dtype of both tensors in the tuple should be `torch.long`. - You will have to figure out how to create multiple tuples of tensors from a single review (an item from `cleaned_text`) using loops  We will use restaurant customer reviews data for this assignment.**Do not change the code block below**. Below is a function that cleans up the text of a review and returns a list of all the words in the review.You will use `cleaned_text`, which is defined below, to create a training dataset for your `CBOW` model.<jupyter_code># DO NOT CHANGE THIS CODEBLOCK
import pandas as pd
import string

def clean_text(text):    
    x = text.translate(str.maketrans('', '', string.punctuation)) # remove punctuation
    x = x.lower().split() # lower case and split by whitespace to differentiate words
    return x

example_text = pd.read_csv('https://raw.githubusercontent.com/dylanwalker/BA865/master/datasets/hw3.csv')
cleaned_text = example_text.Review[:100].apply(clean_text)
cleaned_text
text_with_count=cleaned_text.explode().reset_index(name='word')['word'].drop_duplicates(keep='first')
text_with_count<jupyter_output><empty_output><jupyter_text>Create a CBOW Class The first step is to create `vocab` and `word_to_index` according to the instructions above.<jupyter_code>#Create your vocab here
vocab = list(text_with_count)
print(vocab)
vocab_index=list(range(0,len(vocab)))
print(vocab_index)
#Create your word_to_index dictionary here
word_to_index={vocab[i]: vocab_index[i] for i in range(len(vocab))} 
print(word_to_index)<jupyter_output>['wow', 'loved', 'this', 'place', 'crust', 'is', 'not', 'good', 'tasty', 'and', 'the', 'texture', 'was', 'just', 'nasty', 'stopped', 'by', 'during', 'late', 'may', 'bank', 'holiday', 'off', 'rick', 'steve', 'recommendation', 'it', 'selection', 'on', 'menu', 'great', 'so', 'were', 'prices', 'now', 'i', 'am', 'getting', 'angry', 'want', 'my', 'damn', 'pho', 'honeslty', 'didnt', 'taste', 'that', 'fresh', 'potatoes', 'like', 'rubber', 'you', 'could', 'tell', 'they', 'had', 'been', 'made', 'up', 'ahead', 'of', 'time', 'being', 'kept', 'under', 'a', 'warmer', 'fries', 'too', 'touch', 'service', 'very', 'prompt', 'would', 'go', 'back', 'cashier', 'no', 'care', 'what', 'ever', 'to', 'say', 'still', 'ended', 'wayyy', 'overpriced', 'tried', 'cape', 'cod', 'ravoli', 'chicken', 'with', 'cranberrymmmm', 'disgusted', 'because', 'pretty', 'sure', 'human', 'hair', 'shocked', 'signs', 'indicate', 'cash', 'only', 'highly', 'recommended', 'waitress', 'little', 'slow', 'in', 'worth', 'your', 'let', 'alone[...]<jupyter_text>Now define your `make_cbow_data()` function. It should take `text` (text of a single review) as an argument, and `window_size` (the number of words to the left or right of the focal word) as an argument. It may also take other arguments, depending on how you define it. It should return a list of tuples as described above.<jupyter_code>import torch
import numpy as np
def make_cbow_data(text, window_size):
  wordlist=list()
  for i in range(window_size, len(text)-window_size):
    x=[]
    context=[]
    target=[]
    y=[word_to_index[text[i]]]
    target.append(y)

    x_index = list(range(i-window_size, i+window_size+1))
    x_index.remove(i)
    
    for j in x_index:
      x.append(word_to_index[text[j]])
    context.append(x)

    wordlist.append((torch.tensor(x, dtype=torch.long), torch.tensor(y, dtype=torch.long)))
  return wordlist<jupyter_output><empty_output><jupyter_text>Now define your CBOW model class.<jupyter_code># Define your CBOW model here
class CBOW(torch.nn.Module):
  def __init__(self, vocab_size, embed_dim, window_size, hidden_dim):
    super(CBOW, self).__init__()
    self.embedding = torch.nn.Embedding(vocab_size, embed_dim)
    self.fc1 = torch.nn.Linear(2*window_size*embed_dim, hidden_dim)
    self.fc2 = torch.nn.Linear(hidden_dim, vocab_size)

  def forward(self, x):
    x = self.embedding(x)
    x = x.view(1, -1)
    x = self.fc1(x)
    x = torch.nn.functional.relu(x, inplace=True)
    x = self.fc2(x)
    return x<jupyter_output><empty_output><jupyter_text>Train the CBOW model Now that your model class is written, you must create an instance of the model and train it using the loss function `torch.nn.CrossEntropyLoss` on the output of `fc2` and `y` (the labels).Train your CBOW model for 300 epochs with `embed_dim`= 100, `window_size`=2, and `hidden_dim`=30. - Do not split the data into training and test sets (we will not be evaluating the performance of this model). - Use the SGD optimizer with learning rate = 0.001.- Append the loss at every epoch to a list (return the list if you use a function to fit your model), so that we can plot it later.<jupyter_code># Parameters
VOCAB_SIZE = len(vocab)
EMBED_DIM = 100
WINDOW_SIZE = 2
HIDDEN_DIM = 30
N_EPOCHS = 300

# Train your CBOW model here
CBOW = CBOW(VOCAB_SIZE, EMBED_DIM, WINDOW_SIZE, HIDDEN_DIM)#model
CBOW = CBOW.cuda()
loss_function = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(CBOW.parameters(), lr=0.001)
CBOW.train()
#Set up list of traind and data 
traind=[]
for i in cleaned_text:
  temp = make_cbow_data(i, WINDOW_SIZE)
  traind.append(temp)
  
data=[]
for i in traind:
  temp=[]
  temp=i
  for j in temp:
    data.append(j)
#Losses
losses=[]
for epoch in range(300):
  running_loss=0
  for X, y in data:#X is feature, y is label
    optimizer.zero_grad()
    X = X.cuda()
    y = y.cuda()
    outputs=CBOW(X)
    loss=loss_function(outputs, y)
    loss.backward()
    optimizer.step()
    running_loss += loss.item() 
  losses.append(running_loss/len(data))<jupyter_output><empty_output><jupyter_text>Plot losses by epochs (x-axis: epoch, y-axis: loss)<jupyter_code># Insert your code here to plot losses vs epoch
import matplotlib.pyplot as plt
epoch=list(range(1, N_EPOCHS+1))
plt.plot(epoch, losses);
plt.title('Plot of losses by epochs')
plt.xlabel('epoch');
plt.ylabel('loss');<jupyter_output><empty_output><jupyter_text>Print five most similar words with the word "delicious" The whole point of traiing an embedding model is to get an embedding vector for each word. The idea is that the vector somehow captures the meaning of the word. This is useful because data scientists often face scenarios where they must derive meaning from unstructured text data.Once your model has been trained, you can access the embedding vectors through `model.embedding.weight.data`. You can convert these vectors to a numpy matrix or numpy arrays if needed.Find the five most similar words with the word "delicious" by calculating cosine similarity between the embedding vector of "delicious" and the embedding vectors of all other words in the vocabulary. Hint: cosine similarity is a common metric so you should be able to find one that you can use in an existing library.  While the model learns embedding vectors (that best predict the focal word from its contexts), the vectors that it learns don't seem to truly capture the meaning of words. However, this is mainly due to the small size of our training data. Google trained a word2vec model based on large-scale data (about 100 billion words), and this model captures similarity between words well. You can find the pretrained model at https://code.google.com/archive/p/word2vec/.<jupyter_code># Insert your code here
from sklearn.metrics.pairwise import cosine_similarity
def word_similarity(feature1, feature2):
  return cosine_similarity(feature1.reshape(1,-1), feature2.reshape(1,-1))[0][0]

embed = CBOW.embedding.weight.data.cpu().numpy()
v1 = embed[word_to_index['delicious']]
similarity = []
for v2 in embed:
  similarity.append(word_similarity(v1,v2))
  
cos_index = list(range(len(similarity)))
cos_similarity = dict(zip(cos_index, similarity))
top5=sorted(cos_similarity.items(), key=lambda item: item[1], reverse=True)[1:6]

for i in list(range(5)):
  word_index = top5[i][0]
  print('One of the most similar words with the word "delicious" is:', list(word_to_index.keys())[list(word_to_index.values()).index(word_index)])<jupyter_output>One of the most similar words with the word "delicious" is: touch
One of the most similar words with the word "delicious" is: fries
One of the most similar words with the word "delicious" is: at
One of the most similar words with the word "delicious" is: dont
One of the most similar words with the word "delicious" is: would
{'fraction_non_alphanumeric': 0.10889929742388758, 'fraction_numerical': 0.008821233411397346, 'mean_word_length': 4.9752798507462686, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 28, 'https://': 6, 'lorem ipsum': 0, 'www.': 3, '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': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '552137', 'license': 'MIT', 'n_tokens_mistral': 4057, 'n_tokens_neox': 3881, 'n_words': 1630, 'path': 'zuo_shangkun_HW3.ipynb', 'repo_name': 'Sherry-Zuo/Python-Learning', 'size': '12810'}