<jupyter_start><jupyter_text>Let's make a **convolution** work on MOVIE REVIEW CLASSIFICATION and utilize TORCHTEXT<jupyter_code>import torchtext as TT
import torch
import numpy as np
from torch.utils.data import DataLoader, Dataset
import torch.nn as nn
from torchsummary import summary
with open('./data/reviews.txt','r') as f:
    data = f.readlines()
with open('./data/labels.txt', 'r') as f:
    labels = f.readlines()
i = np.random.randint(low=1,high=len(data))
print(data[i], labels[i])
len(data)
%config Completer.use_jedi = False<jupyter_output><empty_output><jupyter_text>Install spacy```shsudo su - pip install spacypython -m spacy download en_core_web_sm```<jupyter_code># train, test, validation split
split_frac = 0.8

## split data into training, validation, and test data (features and labels, x and y)

split_idx = int(len(data)*split_frac) + 1
test_val_idx = split_idx + int((len(data) - split_idx)//2)
split_idx, test_val_idx<jupyter_output><empty_output><jupyter_text>Make a vocabulary<jupyter_code>%%time
from collections import Counter
from torchtext.vocab import Vocab

tokenizer = TT.data.utils.get_tokenizer('spacy', 'en_core_web_sm')
train_iter = zip(labels[:split_idx], data[:split_idx])
counter = Counter()

for (label, line) in train_iter:
    counter.update(tokenizer(line))
    
vocab = TT.vocab.Vocab(counter,min_freq=1)
tokenizer(line)[:10]<jupyter_output><empty_output><jupyter_text>Load pre-trained word embeddings from GloVe<jupyter_code>vocab.load_vectors('glove.6B.50d')
vocab.vectors.shape # these are the vectors that we can set as weights for embeddings
vocab.vectors[vocab.stoi['apple']]<jupyter_output><empty_output><jupyter_text>Check if load_vectors gives the same result as file with embedding<jupyter_code>def get_word_embedding(word):
    with open('glove.6B.50d.txt','r') as f:
        while True:
            line = f.readline().split()
            if line[0] == word: return(torch.from_numpy(np.array(line[1:], dtype=np.float32)))
assert get_word_embedding('apple').equal(vocab.vectors[vocab.stoi['apple']])
# change the UNK vector with the mean of all known words
vocab.vectors[0] = vocab.vectors.mean(axis=0)
# show 10 most frequent tokens along with the frequency
sorted(vocab.freqs.items(), key=lambda x: x[-1], reverse=True)[:10]
# vocabulary : map integer to a string
vocab.itos[:10]
# 10 words in a zero sentence
[s for s in data[0].split()[:10]] 
# 10 fifst codes of words in a zero sentence
[vocab[s] for s in data[0].split()[:10]]
# function that converts a line of text into a line of vocab indexes
text_pipeline = lambda x: [vocab[token] for token in tokenizer(x)]
data[0]
print(text_pipeline(data[0]))
vocab.stoi['  \n'], vocab.itos[214]
text_pipeline('scary succumb')
# function to convert text label to a number label 0|1
label_pipeline = lambda x: 1 if x == 'positive\n' else 0
label_pipeline(labels[2])
class TextData(torch.utils.data.Dataset):
    '''
    very basic dataset for processing text data
    holds text and label
    implements len and getitem methods
    '''
    def __init__(self, labels, text):
        super(TextData, self).__init__()
        self.labels = labels
        self.text = text
        
    def __getitem__(self, index):
        return self.text[index], self.labels[index] 
    
    def __len__(self):
        return len(self.labels)
one, two = torch.ones(3).unsqueeze(dim=0), torch.zeros(3).unsqueeze(dim=0)
print(one)
print(two)
torch.cat([one, two], dim=0)
def tokenize_batch(batch, max_len=200):
    '''
    tokenizer to use in DataLoader
    takes a text batch of text dataset and produces a tensor batch, converting text and labels though tokenizer, labeler
    tokenizer is a global function text_pipeline
    labeler is a global function label_pipeline
    max_len is a fixed len size, if text is less than max_len it is padded with ones
    if text is larger that max_len it is truncated but from the end of the string
    '''
    labels_list, text_list = [], []
    for _text, _label in batch:
        labels_list.append(label_pipeline(_label))
        text_holder = torch.ones(max_len, dtype=torch.int32) # fixed size tensor of max_len
        processed_text = torch.tensor(text_pipeline(_text), dtype=torch.int32)
        pos = min(200, len(processed_text))
        text_holder[-pos:] = processed_text[-pos:]
        text_list.append(text_holder.unsqueeze(dim=0))
    return torch.cat(text_list, dim=0), torch.FloatTensor(labels_list)
train_dataset = TextData(labels[:split_idx], data[:split_idx])
train_loader = DataLoader(train_dataset, batch_size=2, shuffle=False, collate_fn=tokenize_batch)
train_loader.dataset.text[0]
vocab.stoi['bromwell']
txt, lbl = iter(train_loader).next()
lbl, txt
lbl.shape, txt.shape
batch_size = 50
train_dataset = TextData(labels[:split_idx-1], data[:split_idx-1])
valid_dataset = TextData(labels[split_idx-1:test_val_idx], data[split_idx-1:test_val_idx])
test_dataset = TextData(labels[test_val_idx:], data[test_val_idx:])
len(test_dataset), len(valid_dataset), len(train_dataset)
train_loader =  DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=tokenize_batch)
valid_loader =  DataLoader(valid_dataset, batch_size=batch_size, shuffle=True, collate_fn=tokenize_batch)
test_loader =  DataLoader(test_dataset, batch_size=batch_size, shuffle=True, collate_fn=tokenize_batch)<jupyter_output><empty_output><jupyter_text>Make a model<jupyter_code>import torch.nn as nn
import torch.nn.functional as F

#         Input shape     torch.Size([2, 200])
#         Embedding shape torch.Size([2, 200, 32]) 50
#         Conv1d shape    torch.Size([2, 32, 30]) 48
#         MaxPool shape   torch.Size([2, 32, 15]) 24
#         LSTM shape      torch.Size([2, 32, 64])
#         Dense shape     torch.Size([2, 32, 1])
#         Sigmoid shape   torch.Size([2])

class SentimentConvNN(nn.Module):
    def __init__(self, vocab_size, output_size=1, embedding_dim=32, hidden_dim=64, out_channels=32, drop_prob=0.5, vocab_vectors=None):
        super(SentimentConvNN, self).__init__()
        self.vocab_size = vocab_size
        self.embedding_dim = embedding_dim
        self.output_size = output_size
        self.hidden_dim = hidden_dim
        self.drop_prob = drop_prob
        self.out_channels = out_channels
        self.n_layers = 1
        
        # if we provide vocab_vectors then initialize weights
        if vocab_vectors is not None:
            self.embed = nn.Embedding.from_pretrained(vocab_vectors, freeze=True)
        else:
            self.embed = nn.Embedding(self.vocab_size, self.embedding_dim)
            
        self.conv1d = nn.Conv1d(in_channels=200, out_channels=self.out_channels, kernel_size=3, bias=False, padding=False)
        self.maxpool = nn.MaxPool1d(kernel_size=2)
        
        lstm_input = int((self.embed.weight.shape[-1]-2)/2) # repoduce the logic of conv1d resulting dimention
        
        self.lstm = nn.LSTM(input_size=lstm_input,
                            hidden_size=hidden_dim,
                            num_layers=1,
                            batch_first=True,
                            dropout=0)
        self.dense = nn.Linear(hidden_dim, 1)
        self.drop = nn.Dropout(p=drop_prob)
        
        self.bn_embedding = nn.BatchNorm1d(num_features=200)
        self.bn_conv1d = nn.BatchNorm1d(num_features=self.out_channels)
        self.bn_lstm = nn.BatchNorm1d(num_features=self.out_channels)
    
    def num_parameters(self):
        '''
        get the number of parameters in a network
        '''

        # return sum((list(map(lambda x: torch.as_tensor(x.flatten().size()).sum().item(), self.parameters()))))
        s=""
        for k, v in self.named_parameters():
            s+=f'{k:20} {v.shape}\n'
        s+=f'Total number of parameters = {sum(list(map(lambda x: x.numel(), self.parameters()))):,}'
        return s
    
    def init_hidden(self, batch_size):
        ''' Initializes hidden state '''
        # Create two new tensors with sizes n_layers x batch_size x hidden_dim,
        # initialized to zero, for hidden state and cell state of LSTM
        weight = next(self.parameters()).data
        hidden = (weight.new_zeros(self.n_layers, batch_size, self.hidden_dim),
                  weight.new_zeros(self.n_layers, batch_size, self.hidden_dim))

        return hidden
    
    def forward(self, x, hidden):
        '''
        Input shape 	torch.Size([2, 200])
        Embedding shape torch.Size([2, 200, 32])
        Conv1d shape 	torch.Size([2, 32, 30])
        MaxPool shape 	torch.Size([2, 32, 15])
        LSTM shape 		torch.Size([2, 32, 64])
        Dense shape 	torch.Size([2, 32, 1])
        Sigmoid shape 	torch.Size([2])
        '''
        #print(x.dtype)
        embed_out = self.embed(x)
        embed_out = self.bn_embedding(embed_out)
        embed_out = self.drop(embed_out)
        
        conv_out = self.conv1d(embed_out)
        conv_out = self.bn_conv1d(conv_out)
        conv_out_relu = F.relu(conv_out)
        maxpool_out = self.maxpool(conv_out_relu)
        
        lstm_out, _ = self.lstm(maxpool_out, hidden)
        lstm_out = self.bn_lstm(lstm_out)
        lstm_out = self.drop(lstm_out)
        
        out_dense = self.dense(lstm_out)
        out = nn.Sigmoid()(out_dense[:,-1,:]).view(out_dense.shape[0])
        
        return out, 1
# Instantiate the model with hyperparams
vocab_size = len(vocab) 
output_size = 1 # not needed
embedding_dim = 32
hidden_dim = 64
n_layers = 1
convRNN = SentimentConvNN(vocab_size=vocab_size)
print(convRNN.num_parameters())
h0 = convRNN.init_hidden(batch_size=2)
with torch.no_grad():
    out, _ = convRNN.forward(txt, h0)
out, out.shape
device = torch.device('cuda') if torch.cuda.is_available() else 'cpu'
convRNN.to(device)<jupyter_output><empty_output><jupyter_text>Training conv RNN<jupyter_code>def val_score(net, val_loader, criterion):
    '''
    calculates validation loss
    does not put a net into eval mode - have to do this manually before val_score call
    
    '''
    from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
    
    loss = []
    # that is the number of objects in loader, not batches
    number_of_objects = len(val_loader.dataset)
    
    # make array of zeros with the shape of response
    pred_y = np.zeros(number_of_objects)
    true_y = np.zeros_like(pred_y)

    # store a batch size 
    batch_size = val_loader.batch_size
    
    with torch.no_grad():
        for ii, (test_x, test_y) in enumerate(val_loader):
            h = net.init_hidden(test_y.shape[0])
            test_x, test_y = test_x.to(device), test_y.to(device)
            out, _ = net.forward(test_x, h)
            batch_loss = criterion(out, test_y)
            
            # store predictions and true labels
            pred_y[ii*batch_size:ii*batch_size + len(test_y)] = out.to('cpu').numpy()
            true_y[ii*batch_size:ii*batch_size + len(test_y)] = test_y.to('cpu').numpy()
            
            loss.append(batch_loss.item())
    
    precision = precision_score(true_y, np.round(pred_y))
    recall = recall_score(true_y, np.round(pred_y))
    accuracy = accuracy_score(true_y, np.round(pred_y))
    fscore = f1_score(true_y, np.round(pred_y))
    
    metrics = {'precision':precision, 'recall':recall, 'accuracy':accuracy, 'fscore':fscore}
    
    return np.mean(loss), metrics
def trainer_SWA(net, criterion, optimizer, train_loader, valid_loader, clip_value=5, epochs=10, print_every=200, max_fscore=-np.inf):
    '''
    Train the network
        net - network to trian
        criterion - loss function 
        optimizer - your optimiser of choice 
        train_loader - loader for training data
        vlid_loader - lodaer for validation/test data
        clip_value - upper limit for gradient 
        epochs - number of epochs to train the net
        print_every - prin stats every number of batches
        max_fscore - best fscore on validation set - used in mutiple runs of training
    '''
    from tqdm.notebook import tqdm, trange
    
    steps = 0

    net.train()

    # run over epochs of training
    for e in trange(epochs):
        
        # array to keep value of losses over current epoch
        train_loss = []

        # run one pass through training samples = one epoch
        for train_x, train_y in train_loader:
            steps +=1

            # zero out the grads 
            net.zero_grad()
            # optimizer.zero_grad()

            # send data to device
            train_x, train_y = train_x.to(device), train_y.to(device)

            # initialize hidden state
            h = net.init_hidden(len(train_x))

            # calculate the output of the network
            out, _ = net(train_x, h)

            # compute the loss
            loss = criterion(out, train_y)
            # backprop grads of the loss wrt to net parameters
            loss.backward()
            nn.utils.clip_grad_norm_(net.parameters(), clip_value)

            # upadate parameters of network
            optimizer.step()

            # append current batch loss (loss per object in current batch)
            train_loss.append(loss.item())

            # test loss calc every 
            if steps%print_every == 0:
                # calculate loss on test set
                if max_fscore > 0.75:
                    optimizer.update_swa()
                    optimizer.swap_swa_sgd() # use SWA weights for the calc of validation loss
                    for g in optimizer.param_groups:
                        g['lr'] = 0.0005
                net.eval()
                test_loss, metrics = val_score(net, valid_loader, criterion)
                if metrics['fscore'] > max_fscore:
                    max_fscore = metrics['fscore']
                    message = '=)'
                    check_point = {'vocab_size':net.vocab_size, 
                                   'embedding_dim': net.embedding_dim, 
                                   'hidden_dim':net.hidden_dim, 
                                   'n_layers':net.n_layers, 
                                   'net_params':net.state_dict()}
                    torch.save(check_point, f"spam_model_fscore_{metrics['fscore']:.3f}.pt")
                else:
                    message = ';('
                if max_fscore > 0.75:
                    optimizer.swap_swa_sgd() # swap back normal weights and continue training
                net.train()
                print(f"Step {steps} epoch {e+1}. {message}\nTest loss is {test_loss:.4f}. Train loss is {np.mean(train_loss):.4f}.\
                F1 Score={metrics['fscore']:.2%} Precision={metrics['precision']:.2%} Recall={metrics['recall']:.2%} Accuracy={metrics['accuracy']:.2%}\n")
    return max_fscore
max_fscore = 0.5
help(SWA)
model = torch.load('spam_model_fscore_0.843.pt')
model.keys()
convRNN.load_state_dict(model['net_params'])
next(convRNN.parameters()).is_cuda
# base_opt = torch.optim.SGD(model.parameters(), lr=0.1)
# opt = torchcontrib.optim.SWA(
#                 base_opt, swa_start=10, swa_freq=5, swa_lr=0.05)
# for _ in range(100):
#     opt.zero_grad()
#     loss_fn(model(input), target).backward()
#     opt.step()
# opt.swap_swa_sgd()
# loss and optimization functions
criterion = nn.BCELoss(reduction='mean')
base_opt = torch.optim.Adam(convRNN.parameters(), lr=lr, weight_decay=0.005)
#optimizer = torch.optim.SGD(convRNN.parameters(), lr=lr, momentum=0.9, nesterov=True)
#optimizer = torch.optim.RMSprop(convRNN.parameters(), lr=lr, momentum=0.9)
lr = 0.001
#base_opt = torch.optim.SGD(convRNN.parameters(), lr=lr)
optimizer = SWA(base_opt)
for g in optimizer.param_groups:
    print(g['lr'])
max_fscore = trainer_SWA(convRNN, criterion, optimizer, train_loader, valid_loader, clip_value=10, epochs=50, print_every=200, max_fscore=max_fscore)<jupyter_output><empty_output><jupyter_text>SWA result - you can not train a ntework with SGD... SWA does not give the result here BUT with Adam in our case it works almost on par with vanilla attention  Make convRNN with pretrained embedding from GloVe<jupyter_code># Instantiate the model with hyperparams
vocab_size = len(vocab) 
output_size = 1 # not needed
embedding_dim = 50
hidden_dim = 64
n_layers = 1
# vocab_size, output_size=1, embedding_dim=32, hidden_dim=64, out_channels=32, drop_prob=0.5, vocab_vectors=None
convRNN = SentimentConvNN(vocab_size=vocab_size, embedding_dim=embedding_dim, vocab_vectors=vocab.vectors)
print(convRNN.num_parameters())
# if you want to make a grad
convRNN.embed.weight.requires_grad_(True)
convRNN.embed.weight.requires_grad, convRNN.dense.weight.requires_grad
device = torch.device('cuda') if torch.cuda.is_available() else 'cpu'
convRNN.to(device)
def trainer(net, criterion, optimizer, train_loader, valid_loader, clip_value=5, epochs=10, print_every=200, max_fscore=-np.inf):
    '''
    Train the network
        net - network to trian
        criterion - loss function 
        optimizer - your optimiser of choice 
        train_loader - loader for training data
        vlid_loader - lodaer for validation/test data
        clip_value - upper limit for gradient 
        epochs - number of epochs to train the net
        print_every - prin stats every number of batches
        max_fscore - best fscore on validation set - used in mutiple runs of training
    '''
    from tqdm.notebook import tqdm, trange
    
    steps = 0

    net.train()

    # run over epochs of training
    for e in trange(epochs):
        
        # array to keep value of losses over current epoch
        train_loss = []

        # run one pass through training samples = one epoch
        for train_x, train_y in train_loader:
            steps +=1

            # zero out the grads 
            net.zero_grad()
            # optimioptimizer.zero_grad()

            # send data to device
            train_x, train_y = train_x.to(device), train_y.to(device)

            # initialize hidden state
            h = net.init_hidden(len(train_x))

            # calculate the output of the network
            out, _ = net(train_x, h)

            # compute the loss
            loss = criterion(out, train_y)
            # backprop grads of the loss wrt to net parameters
            loss.backward()
            nn.utils.clip_grad_norm_(net.parameters(), clip_value)

            # upadate parameters of network
            optimizer.step()

            # append current batch loss (loss per object in current batch)
            train_loss.append(loss.item())

            # test loss calc every 
            if steps%print_every == 0:
                # calculate loss on test set
                test_loss, metrics = val_score(net, valid_loader, criterion)
                if metrics['fscore'] > max_fscore:
                    max_fscore = metrics['fscore']
                    message = '=)'
                    check_point = {'vocab_size':net.vocab_size, 
                                   'embedding_dim': net.embedding_dim, 
                                   'hidden_dim':net.hidden_dim, 
                                   'n_layers':net.n_layers, 
                                   'net_params':net.state_dict()}
                    torch.save(check_point, f"spam_model_fscore_{metrics['fscore']:.3f}.pt")
                else:
                    message = ';('
                net.train()
                print(f"Step {steps} epoch {e+1}. {message}\nTest loss is {test_loss:.4f}. Train loss is {np.mean(train_loss):.4f}.\
                F1 Score={metrics['fscore']:.2%} Precision={metrics['precision']:.2%} Recall={metrics['recall']:.2%} Accuracy={metrics['accuracy']:.2%}\n")
    return max_fscore
batch_size
# loss and optimization functions
lr = 0.001
criterion = nn.BCELoss(reduction='mean')
optimizer = torch.optim.Adam(convRNN.parameters(), lr=lr, weight_decay=1e-2)
#optimizer = torch.optim.SGD(convRNN.parameters(), lr=lr, momentum=0.9, nesterov=True, weight_decay=1e-2)
#optimizer = torch.optim.RMSprop(convRNN.parameters(), lr=lr, momentum=0.9)
max_fscore
max_fscore = trainer(convRNN, criterion, optimizer, train_loader, valid_loader, clip_value=10, epochs=50, print_every=200, max_fscore=max_fscore)<jupyter_output><empty_output><jupyter_text>CONV RNN with pretrained embedding gives no gain in metrics - you must make embedding trainable otherwise the results are horrible --- Manual conv network assembly---```pythonmodel = Sequential()model.add(Embedding(top_words, embedding_vecor_length,input_length=max_review_length)) model.add(Conv1D(filters=32, kernel_size=3, padding='same', activation='relu'))model.add(MaxPooling1D(pool_size=2)) model.add(LSTM(100))model.add(Dense(1, activation='sigmoid'))```<jupyter_code>embed = nn.Embedding(vocab_size, embedding_dim)
conv1d_sentiment = nn.Conv1d(in_channels=200, out_channels=32, kernel_size=3, bias=False, padding=False)
maxpool_sentiment = nn.MaxPool1d(kernel_size=2)
lstm_sentiment = nn.LSTM(input_size=15,
                         hidden_size=hidden_dim,
                         num_layers=1,
                         batch_first=True,
                         dropout=0)
lstm_h0 = (torch.zeros(1,2,hidden_dim), torch.zeros(1,2,hidden_dim))
dense_sentiment = nn.Linear(hidden_dim, 1)
drop_sentiment = nn.Dropout(p=0.5)
conv1d_sentiment.weight.shape
with torch.no_grad():
    embed_out = embed(txt)
    embed_out = drop_sentiment(embed_out)
    conv_out = conv1d_sentiment(embed_out)
    conv_out_relu = F.relu(conv_out)
    maxpool_out = maxpool_sentiment(conv_out_relu)
    lstm_out, _ = lstm_sentiment(maxpool_out, lstm_h0)
    lstm_out = drop_sentiment(lstm_out)
    out_dense = dense_sentiment(lstm_out)
    out = nn.Sigmoid()(out_dense[:,-1,:]).view(out_dense.shape[0])
# Input:  (N, Cin,  Lin)
# Output: (N, Cout, Lout)
print(f'Input shape \t{txt.shape}\nEmbedding shape {embed_out.shape}\nConv1d shape \t{conv_out.shape}\
\nMaxPool shape \t{maxpool_out.shape}\nLSTM shape \t{lstm_out.shape}\nDense shape \t{out_dense.shape}\nSigmoid shape \t{out.shape}')<jupyter_output>Input shape 	torch.Size([2, 200])
Embedding shape torch.Size([2, 200, 32])
Conv1d shape 	torch.Size([2, 32, 30])
MaxPool shape 	torch.Size([2, 32, 15])
LSTM shape 	torch.Size([2, 32, 64])
Dense shape 	torch.Size([2, 32, 1])
Sigmoid shape 	torch.Size([2])<jupyter_text>--- END:Manual conv network assembly---  Conv1din the simplest case, the output value of the layer with input size (N,Cin,L) and output (N,Cout,Lout)<jupyter_code>conv_1d_input_temp = torch.arange(18).to(torch.float).view(3,2,-1)
conv_1d_input_temp, conv_1d_input_temp.shape
extra = torch.arange(6).view((3,2,1))
extra, extra.shape
conv_1d_input = torch.cat((conv_1d_input_temp, extra), dim = 2)
conv_1d_input, conv_1d_input.shape
def init_weight(m):
    from functools import reduce
    l = reduce(lambda x,y: x*y, m.weight.data.shape)
    if type(m) == nn.Conv1d:
        m.weight.data = torch.ones(l).to(torch.float).reshape(m.weight.data.shape)
        m.weight.data[1] += m.weight.data[1]

layer_conv1d = nn.Conv1d(in_channels=2, out_channels=4, kernel_size=2, bias=False)
layer_conv1d.apply(init_weight)

layer_conv1d.weight.data, layer_conv1d.weight.data.shape
conv_1d_out = layer_conv1d(conv_1d_input)

conv_1d_out<jupyter_output><empty_output><jupyter_text>Convolution of kernel size 1it is equal to linear layer for point-wise transformation in transformer<jupyter_code>layer_conv1d = nn.Conv1d(in_channels=3, out_channels=4, kernel_size=1, bias=False)
layer_conv1d.apply(init_weight)

layer_conv1d.weight.data, layer_conv1d.weight.data.shape
kernel1input = torch.arange(6).to(torch.float).view(2,3,-1)
kernel1input, kernel1input.shape
layer_conv1d(kernel1input)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1110688474705211, 'fraction_numerical': 0.01808883817252018, 'mean_word_length': 3.0607516732452376, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '472840', 'license': 'MIT', 'n_tokens_mistral': 8151, 'n_tokens_neox': 7780, 'n_words': 1765, 'path': 'sentiment-rnn/Movie Review with Conv1d and Torchtext.ipynb', 'repo_name': 'chorus12/deep-learning-v2-pytorch', 'size': '23661'}