Natural Language Processing with RNNs - Toxic Comment Classification
Outline:
- Download and explore the data
- Prepare the data for training
- Build a recurrent neural network
- Train & evaluate the model
- Make predictions & submit to Kaggle
Download & Explore Data
Outline:
- Download the data from Kaggle
- Load data into Pandas dataframes
- Explore the dataset
import osos.environ['KAGGLE_CONFIG_DIR'] = '.'!kaggle competitions download -c jigsaw-toxic-comment-classification-challengeWarning: Your Kaggle API key is readable by other users on this system! To fix this, you can run 'chmod 600 ./kaggle.json'
Downloading jigsaw-toxic-comment-classification-challenge.zip to /content
36% 19.0M/52.6M [00:00<00:00, 197MB/s]
100% 52.6M/52.6M [00:00<00:00, 281MB/s]
!unzip jigsaw-toxic-comment-classification-challenge.zip -d dataArchive: jigsaw-toxic-comment-classification-challenge.zip
inflating: data/sample_submission.csv.zip
inflating: data/test.csv.zip
inflating: data/test_labels.csv.zip
inflating: data/train.csv.zip
import pandas as pdraw_df = pd.read_csv('data/train.csv.zip')
test_df = pd.read_csv('data/test.csv.zip')
sub_df = pd.read_csv('data/sample_submission.csv.zip')raw_df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 159571 entries, 0 to 159570
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 id 159571 non-null object
1 comment_text 159571 non-null object
2 toxic 159571 non-null int64
3 severe_toxic 159571 non-null int64
4 obscene 159571 non-null int64
5 threat 159571 non-null int64
6 insult 159571 non-null int64
7 identity_hate 159571 non-null int64
dtypes: int64(6), object(2)
memory usage: 9.7+ MB
raw_df.sample(10)raw_df.comment_text.values[0]"Explanation\nWhy the edits made under my username Hardcore Metallica Fan were reverted? They weren't vandalisms, just closure on some GAs after I voted at New York Dolls FAC. And please don't remove the template from the talk page since I'm retired now.89.205.38.27"target_cols = ['toxic', 'severe_toxic', 'obscene', 'threat', 'insult', 'identity_hate']for col in target_cols:
print(raw_df[col].value_counts(normalize=True))0 0.904156
1 0.095844
Name: toxic, dtype: float64
0 0.990004
1 0.009996
Name: severe_toxic, dtype: float64
0 0.947052
1 0.052948
Name: obscene, dtype: float64
0 0.997004
1 0.002996
Name: threat, dtype: float64
0 0.950636
1 0.049364
Name: insult, dtype: float64
0 0.991195
1 0.008805
Name: identity_hate, dtype: float64
test_dfsub_dfPrepare the Dataset for Training
Outline:
- Create a vocabulary using TorchText
- Create training & validation sets
- Create PyTorch dataloaders
from torchtext.data.utils import get_tokenizertokenizer = get_tokenizer('basic_english')sample_comment = raw_df.comment_text.values[0]sample_comment"Explanation\nWhy the edits made under my username Hardcore Metallica Fan were reverted? They weren't vandalisms, just closure on some GAs after I voted at New York Dolls FAC. And please don't remove the template from the talk page since I'm retired now.89.205.38.27"sample_comment_tokens = tokenizer(sample_comment)
sample_comment_tokens[:10]['explanation',
'why',
'the',
'edits',
'made',
'under',
'my',
'username',
'hardcore',
'metallica']from torchtext.vocab import build_vocab_from_iteratorcomment_tokens = raw_df.comment_text.map(tokenizer)VOCAB_SIZE = 1500unk_token = '<unk>'
pad_token = '<pad>'vocab = build_vocab_from_iterator(comment_tokens,
specials=[unk_token, pad_token],
max_tokens=VOCAB_SIZE)vocab[unk_token]0vocab.set_default_index(vocab[unk_token])vocab['this']18vocab['harcore']0sample_indices = vocab.lookup_indices(sample_comment_tokens)
sample_indices[:10][667, 85, 3, 140, 142, 185, 39, 663, 0, 0]sample_comment_recovered = vocab.lookup_tokens(sample_indices)
sample_comment_recovered[:10]['explanation',
'why',
'the',
'edits',
'made',
'under',
'my',
'username',
'<unk>',
'<unk>']Create Training & Validation Sets
- Define a custom Pytorch Dataset
- Pass raw data into the dataset
- Split the PyTorch Dataset
raw_df.comment_text.sample(1000).map(tokenizer).map(len).plot(kind='hist')<matplotlib.axes._subplots.AxesSubplot at 0x7f9253909cd0>MAX_LENGTH = 150def pad_tokens(tokens):
if (len(tokens) >= MAX_LENGTH):
return tokens[:MAX_LENGTH]
else:
return tokens + [pad_token] * (MAX_LENGTH - len(tokens))import torchfrom torch.utils.data import Datasetclass JigsawDataset(Dataset):
def __init__(self, df, is_test=False):
self.df = df
self.is_test = is_test
def __getitem__(self, index):
comment_text = self.df.comment_text.values[index]
comment_tokens = pad_tokens(tokenizer(comment_text))
input = torch.tensor(vocab.lookup_indices(comment_tokens))
if self.is_test:
target = torch.tensor([0,0,0,0,0,0]).float()
else:
target = torch.tensor(self.df[target_cols].values[index]).float()
return input, target
def __len__(self):
return len(self.df)raw_ds = JigsawDataset(raw_df)raw_df.head(5)raw_ds[0](tensor([ 667, 85, 3, 140, 142, 185, 39, 663, 0, 0, 1287, 96,
329, 27, 57, 0, 9, 31, 0, 4, 61, 0, 21, 71,
0, 158, 6, 0, 45, 130, 1167, 0, 0, 2, 8, 55,
70, 9, 31, 253, 3, 369, 41, 3, 50, 38, 155, 6,
9, 81, 0, 99, 2, 0, 2, 0, 2, 0, 2, 1122,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1]),
tensor([0., 0., 0., 0., 0., 0.]))from torch.utils.data import random_splitVAL_FRAC = 0.25train_ds, val_ds = random_split(raw_ds, [1-VAL_FRAC, VAL_FRAC])len(raw_ds), len(train_ds), len(val_ds)(159571, 119679, 39892)test_ds = JigsawDataset(test_df, is_test=True)test_df.head(5)test_ds[0](tensor([ 0, 636, 0, 649, 12, 69, 0, 93, 10, 9, 169, 359,
23, 0, 89, 30, 10, 8, 0, 10, 1488, 0, 2, 2,
2, 6, 68, 636, 0, 0, 0, 690, 0, 8, 105, 10,
5, 0, 39, 419, 10, 699, 0, 46, 2, 0, 649, 12,
47, 0, 15, 0, 668, 439, 2, 443, 0, 13, 332, 21,
167, 2, 8, 0, 12, 251, 0, 62, 0, 59, 34, 11,
0, 171, 2, 2, 2, 0, 690, 0, 105, 226, 160, 490,
95, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1]),
tensor([0., 0., 0., 0., 0., 0.]))from torch.utils.data import DataLoaderBATCH_SIZE = 256train_dl = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=8, pin_memory=True)
val_dl = DataLoader(val_ds, batch_size=BATCH_SIZE*2, num_workers=8, pin_memory=True)
test_dl = DataLoader(test_ds, batch_size=BATCH_SIZE*2, num_workers=8, pin_memory=True)for batch in train_dl:
b_inputs, b_targets = batch
print('b_input.shape', b_inputs.shape)
print('b_targets.shape', b_targets.shape)
breakb_input.shape torch.Size([256, 150])
b_targets.shape torch.Size([256, 6])
Build a Recurrent Neural Network
Outline:
- Understand how recurrent neural networks work
- Create a recurrent neural network
- Pass some through the network
import torch.nn as nn
import torch.functional as Fvocab[pad_token]1emb_layer = nn.Embedding(VOCAB_SIZE, 256, 1)rnn_layer = nn.RNN(256, 128, 1, batch_first=True)for batch in train_dl:
b_inputs, b_targets = batch
print('b_input.shape', b_inputs.shape)
print('b_targets.shape', b_targets.shape)
emb_out = emb_layer(b_inputs)
print('emb_out.shape', emb_out.shape)
rnn_out, hn = rnn_layer(emb_out)
print('rnn_out.shape', rnn_out.shape)
print('hn.shape', hn.shape)
breakb_input.shape torch.Size([256, 150])
b_targets.shape torch.Size([256, 6])
emb_out.shape torch.Size([256, 150, 256])
rnn_out.shape torch.Size([256, 150, 128])
hn.shape torch.Size([1, 256, 128])
!pip install pytorch_lightning --quiet |████████████████████████████████| 800 kB 4.6 MB/s
|████████████████████████████████| 125 kB 83.0 MB/s
|████████████████████████████████| 512 kB 79.4 MB/s
import pytorch_lightning as plimport torch.nn.functional as Fimport numpy as npclass JigsawModel(pl.LightningModule):
def __init__(self):
super().__init__()
self.emb = nn.Embedding(VOCAB_SIZE, 256, 1)
self.lstm = nn.LSTM(256, 128, 1, batch_first=True)
self.linear = nn.Linear(128, 6)
self.learning_rate = 0.001
def forward(self, x):
out = self.emb(x)
out, hn = self.lstm(out)
out = F.relu(out[:,-1,:])
out = self.linear(out)
return out
def training_step(self, batch, batch_idx):
inputs, targets = batch
outputs = self(inputs)
probs = torch.sigmoid(outputs)
loss = F.binary_cross_entropy(probs, targets)
return loss
def validation_step(self, batch, batch_idx):
inputs, targets = batch
outputs = self(inputs)
probs = torch.sigmoid(outputs)
loss = F.binary_cross_entropy(probs, targets)
return loss.item()
def validation_epoch_end(self, validation_step_outputs):
loss = np.mean(validation_step_outputs)
print("Epoch #{}; Loss: {:4f} ".format(self.current_epoch, loss))
def predict_step(self, batch, batch_idx):
inputs, targets = batch
outputs = self(inputs)
probs = torch.sigmoid(outputs)
return probs
def configure_optimizers(self):
return torch.optim.Adam(self.parameters(), lr=self.learning_rate)model = JigsawModel()for batch in train_dl:
b_inputs, b_targets = batch
print('b_input.shape', b_inputs.shape)
print('b_targets.shape', b_targets.shape)
outputs = model(b_inputs)
print('outputs.shape', outputs.shape)
probs = torch.sigmoid(outputs)
loss = F.binary_cross_entropy(probs, b_targets)
print('Loss', loss)
breakb_input.shape torch.Size([256, 150])
b_targets.shape torch.Size([256, 6])
outputs.shape torch.Size([256, 6])
Loss tensor(0.6917, grad_fn=<BinaryCrossEntropyBackward0>)
trainer = pl.Trainer(max_epochs=3, accelerator='gpu', auto_lr_find=True)INFO:pytorch_lightning.utilities.rank_zero:GPU available: True (cuda), used: True
INFO:pytorch_lightning.utilities.rank_zero:TPU available: False, using: 0 TPU cores
INFO:pytorch_lightning.utilities.rank_zero:IPU available: False, using: 0 IPUs
INFO:pytorch_lightning.utilities.rank_zero:HPU available: False, using: 0 HPUs
trainer.tune(model, train_dl)/usr/local/lib/python3.8/dist-packages/pytorch_lightning/trainer/configuration_validator.py:108: PossibleUserWarning: You defined a `validation_step` but have no `val_dataloader`. Skipping val loop.
rank_zero_warn(
INFO:pytorch_lightning.accelerators.cuda:LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Finding best initial lr: 0%| | 0/100 [00:00<?, ?it/s]INFO:pytorch_lightning.utilities.rank_zero:`Trainer.fit` stopped: `max_steps=100` reached.
INFO:pytorch_lightning.tuner.lr_finder:Learning rate set to 0.00478630092322638
INFO:pytorch_lightning.utilities.rank_zero:Restoring states from the checkpoint path at /content/.lr_find_50b3f325-e61c-4318-8d53-dfd48c1ccde0.ckpt
INFO:pytorch_lightning.utilities.rank_zero:Restored all states from the checkpoint file at /content/.lr_find_50b3f325-e61c-4318-8d53-dfd48c1ccde0.ckpt
{'lr_find': <pytorch_lightning.tuner.lr_finder._LRFinder at 0x7f9249b40550>}model.learning_rate0.00478630092322638trainer.fit(model, train_dl, val_dl)INFO:pytorch_lightning.accelerators.cuda:LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
INFO:pytorch_lightning.callbacks.model_summary:
| Name | Type | Params
-------------------------------------
0 | emb | Embedding | 384 K
1 | lstm | LSTM | 197 K
2 | linear | Linear | 774
-------------------------------------
582 K Trainable params
0 Non-trainable params
582 K Total params
2.330 Total estimated model params size (MB)
Sanity Checking: 0it [00:00, ?it/s]Epoch #0; Loss: 0.691875
Training: 0it [00:00, ?it/s]Validation: 0it [00:00, ?it/s]Epoch #0; Loss: 0.073076
Validation: 0it [00:00, ?it/s]Epoch #1; Loss: 0.065023
Validation: 0it [00:00, ?it/s]INFO:pytorch_lightning.utilities.rank_zero:`Trainer.fit` stopped: `max_epochs=3` reached.
Epoch #2; Loss: 0.064172
test_df.head(5)for batch in test_dl:
b_inputs, b_targets = batch
print('b_inputs.shape', b_inputs.shape)
print('b_targets.shape', b_targets.shape)
breakb_inputs.shape torch.Size([512, 150])
b_targets.shape torch.Size([512, 6])
test_preds = trainer.predict(model, test_dl)INFO:pytorch_lightning.accelerators.cuda:LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Predicting: 468it [00:00, ?it/s]test_preds = torch.cat(test_preds)test_preds.shapetorch.Size([153164, 6])test_predstensor([[9.8514e-01, 3.6960e-01, 9.3268e-01, 4.5492e-02, 8.6001e-01, 2.4571e-01],
[6.6544e-03, 9.5455e-05, 1.4566e-03, 2.8526e-04, 2.3743e-03, 8.5336e-04],
[9.5926e-03, 1.2386e-04, 2.3447e-03, 3.2115e-04, 2.8670e-03, 9.5028e-04],
...,
[6.3757e-03, 1.0199e-04, 1.4886e-03, 3.1001e-04, 2.3857e-03, 8.5090e-04],
[3.4499e-03, 5.9353e-05, 9.4897e-04, 1.3394e-04, 1.1450e-03, 4.0778e-04],
[6.7744e-01, 2.3407e-02, 2.9260e-01, 1.6682e-02, 3.3222e-01, 6.3561e-02]])test_probs = torch.sigmoid(test_preds)sub_df[target_cols] = test_probs.detach().cpu().numpy()sub_dfsub_df.to_csv('submission.csv', index=None)!head submission.csvid,toxic,severe_toxic,obscene,threat,insult,identity_hate
00001cee341fdb12,0.72812605,0.5913613,0.7176182,0.51137114,0.70266306,0.56111944
0000247867823ef7,0.5016636,0.50002384,0.5003641,0.5000713,0.5005936,0.5002134
00013b17ad220c46,0.50239813,0.500031,0.5005862,0.5000803,0.50071675,0.5002376
00017563c3f7919a,0.501505,0.50002253,0.5003276,0.50006527,0.5005442,0.500191
00017695ad8997eb,0.50200754,0.5000269,0.50047714,0.50007606,0.50064564,0.5002039
0001ea8717f6de06,0.50149107,0.50002205,0.50032103,0.50006306,0.5005344,0.50018644
00024115d4cbde0f,0.5014186,0.5000208,0.50028896,0.5000587,0.500487,0.5001697
000247e83dcc1211,0.5343304,0.50087047,0.5057776,0.50117475,0.5125339,0.503937
00025358d4737918,0.50083727,0.5000131,0.50024486,0.5000303,0.50024164,0.5000872
sample_df = raw_df.sample(10)sample_dfsample_ds = JigsawDataset(sample_df)sample_dl = DataLoader(sample_ds, batch_size=10)sample_preds = trainer.predict(model, sample_dl)INFO:pytorch_lightning.accelerators.cuda:LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
/usr/local/lib/python3.8/dist-packages/pytorch_lightning/trainer/connectors/data_connector.py:224: PossibleUserWarning: The dataloader, predict_dataloader 0, does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` (try 12 which is the number of cpus on this machine) in the `DataLoader` init to improve performance.
rank_zero_warn(
Predicting: 468it [00:00, ?it/s]sample_probs = torch.sigmoid(sample_preds[0])sample_probstensor([[0.5018, 0.5001, 0.5005, 0.5000, 0.5004, 0.5002],
[0.5020, 0.5000, 0.5004, 0.5001, 0.5008, 0.5003],
[0.5018, 0.5000, 0.5004, 0.5001, 0.5006, 0.5002],
[0.5084, 0.5001, 0.5016, 0.5002, 0.5029, 0.5008],
[0.5015, 0.5000, 0.5003, 0.5001, 0.5006, 0.5002],
[0.5408, 0.5008, 0.5047, 0.5024, 0.5152, 0.5039],
[0.5027, 0.5001, 0.5006, 0.5001, 0.5006, 0.5005],
[0.5046, 0.5001, 0.5014, 0.5002, 0.5012, 0.5004],
[0.7199, 0.5369, 0.6775, 0.5104, 0.6677, 0.5424],
[0.7088, 0.5169, 0.6377, 0.5067, 0.6428, 0.5278]])sample_dfReference
- Quora Insincere Classification: https://www.kaggle.com/c/quora-insincere-questions-classification