jesse-tong commited on
Commit
acf97d8
·
1 Parent(s): 832e945

Add test dataset and example inference/evaluation for LSTM model

Browse files
Files changed (7) hide show
  1. .gitignore +2 -1
  2. README.md +2 -25
  3. dataset_lstm.py +28 -4
  4. example_uses.md +8 -3
  5. inference_lstm.py +100 -0
  6. test_data.csv +0 -0
  7. test_data_creation.ipynb +476 -0
.gitignore CHANGED
@@ -7,4 +7,5 @@ __pycache__/
7
  *.db
8
  metrics.txt
9
  predictions.txt
10
- *.pth
 
 
7
  *.db
8
  metrics.txt
9
  predictions.txt
10
+ *.pth
11
+ news-category-dataset/
README.md CHANGED
@@ -1,32 +1,9 @@
1
- # DocBERT - Improved Document Classification with BERT
2
-
3
- This repository contains an improved implementation of BERT for document classification, combining techniques from [jesse-tong/docbert](https://github.com/jesse-tong/docbert) and [castorini/hedwig](https://github.com/castorini/hedwig).
4
-
5
- ## Key Improvements
6
-
7
- 1. **Advanced Regularization Techniques**:
8
- - Dropout in multiple layers
9
- - Layer normalization
10
- - Gradient clipping
11
- - Weight decay optimization
12
-
13
- 2. **Training Stability Enhancements**:
14
- - Learning rate scheduling with ReduceLROnPlateau
15
- - Gradient accumulation for effective larger batch sizes
16
- - Label smoothing to improve generalization
17
- - Early stopping based on validation F1 score
18
-
19
- 3. **Architectural Changes**:
20
- - Better BERT pooling strategies
21
- - More robust tokenization with attention masks
22
- - Configurable hyperparameters for different document types
23
-
24
  ## Installation
25
 
26
  ```bash
27
  # Clone the repository
28
- git clone https://github.com/yourusername/docbert-improved.git
29
- cd docbert-improved
30
 
31
  # Install dependencies
32
  pip install -r requirements.txt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ## Installation
2
 
3
  ```bash
4
  # Clone the repository
5
+ git clone https://github.com/jesse-tong/docbert2.git
6
+ cd docbert2
7
 
8
  # Install dependencies
9
  pip install -r requirements.txt
dataset_lstm.py CHANGED
@@ -99,10 +99,17 @@ class LSTMDataset(Dataset):
99
  'attention_mask': encoding['attention_mask'],
100
  'label': torch.tensor(label, dtype=torch.long)
101
  }
 
 
 
 
 
 
 
102
 
103
  def prepare_lstm_data(data_path, text_col='text', label_col='label',
104
  max_vocab_size=30000, max_seq_length=512,
105
- val_split=0.1, test_split=0.1, batch_size=32, seed=42):
106
  """
107
  Load data and prepare for LSTM model
108
  """
@@ -154,10 +161,27 @@ def prepare_lstm_data(data_path, text_col='text', label_col='label',
154
  train_dataset = LSTMDataset(train_texts, train_labels, tokenizer)
155
  val_dataset = LSTMDataset(val_texts, val_labels, tokenizer)
156
  test_dataset = LSTMDataset(test_texts, test_labels, tokenizer)
 
 
 
157
 
158
  # Create data loaders
159
- train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
160
- val_loader = DataLoader(val_dataset, batch_size=batch_size)
161
- test_loader = DataLoader(test_dataset, batch_size=batch_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  return train_loader, val_loader, test_loader, tokenizer.vocab_size
 
99
  'attention_mask': encoding['attention_mask'],
100
  'label': torch.tensor(label, dtype=torch.long)
101
  }
102
+
103
+ def get_text_(self, idx):
104
+ """Get original text for a given index"""
105
+ return {
106
+ 'text': self.texts[idx],
107
+ 'label': self.labels[idx]
108
+ }
109
 
110
  def prepare_lstm_data(data_path, text_col='text', label_col='label',
111
  max_vocab_size=30000, max_seq_length=512,
112
+ val_split=0.1, test_split=0.1, batch_size=32, seed=42, return_datasets=False):
113
  """
114
  Load data and prepare for LSTM model
115
  """
 
161
  train_dataset = LSTMDataset(train_texts, train_labels, tokenizer)
162
  val_dataset = LSTMDataset(val_texts, val_labels, tokenizer)
163
  test_dataset = LSTMDataset(test_texts, test_labels, tokenizer)
164
+
165
+ if return_datasets:
166
+ return train_dataset, val_dataset, test_dataset, tokenizer.vocab_size
167
 
168
  # Create data loaders
169
+ if len(train_dataset.texts) == 0:
170
+ logger.warning("Training dataset is empty. Please check your data.")
171
+ train_loader = None
172
+ else:
173
+ train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
174
+
175
+ if len(val_dataset.texts) == 0:
176
+ logger.warning("Validation dataset is empty. Please check your data.")
177
+ val_loader = None
178
+ else:
179
+ val_loader = DataLoader(val_dataset, batch_size=batch_size)
180
+
181
+ if len(test_dataset.texts) == 0:
182
+ logger.warning("Test dataset is empty. Please check your data.")
183
+ test_loader = None
184
+ else:
185
+ test_loader = DataLoader(test_dataset, batch_size=batch_size)
186
 
187
  return train_loader, val_loader, test_loader, tokenizer.vocab_size
example_uses.md CHANGED
@@ -5,12 +5,17 @@
5
  ```
6
  python train.py --data_path train.csv --label_column "Class Index" --text_column "Description" --epochs 4 --num_classes 4
7
  ```
8
- - Inference with BERT model (train.csv is ag_news dataset with 4 classes)
9
  ```
10
- python .\inference_example.py --model_path "./bert_base_uncased/best_model.pth" --num_classes 4 --class_names "World" "Sports" "Business" "Science" --text_column "Description" --label_column "Class Index" --data_path "./train.csv" --inference_batch_limit 10
11
  ```
12
 
13
- - Train LSTM model from BERT model using distillation
14
  ```
15
  python .\distill_bert_to_lstm.py --bert_model bert-base-uncased --bert_model_path "./bert_base_uncased/best_model.pth" --output_dir "./docbert_lstm" --batch_size 32 --epochs 10 --data_path "./train.csv" --text_column "Description" --label_column "Class Index" --num_classes 4
 
 
 
 
 
16
  ```
 
5
  ```
6
  python train.py --data_path train.csv --label_column "Class Index" --text_column "Description" --epochs 4 --num_classes 4
7
  ```
8
+ - Inference with BERT model (test_data.csv is test dataset with 4 classes like ag_news)
9
  ```
10
+ python .\inference_example.py --model_path "./bert_base_uncased/best_model.pth" --num_classes 4 --class_names "World" "Sports" "Business" "Science" --text_column "Description" --label_column "Class Index" --data_path "./test_data.csv" --inference_batch_limit 10
11
  ```
12
 
13
+ - Train LSTM model from BERT model using distillation (train dataset should be the same as distillation training dataset)
14
  ```
15
  python .\distill_bert_to_lstm.py --bert_model bert-base-uncased --bert_model_path "./bert_base_uncased/best_model.pth" --output_dir "./docbert_lstm" --batch_size 32 --epochs 10 --data_path "./train.csv" --text_column "Description" --label_column "Class Index" --num_classes 4
16
+ ```
17
+
18
+ - Inference with distilled LSTM model (test_data.csv is test dataset with 4 classes like ag_news)
19
+ ```
20
+ python .\inference_lstm.py --model_path "./docbert_lstm/distilled_lstm_model.pth" --num_classes 4 --class_names "World" "Sports" "Business" "Science" --text_column "Description" --label_column "Class Index" --data_path "./test_data.csv" --inference_batch_limit 10
21
  ```
inference_lstm.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataset_lstm import prepare_lstm_data, LSTMTokenizer, LSTMDataset
2
+ from models.lstm_model import DocumentBiLSTM
3
+ import sklearn, torch
4
+ import numpy as np
5
+ import argparse
6
+
7
+ if __name__ == "__main__":
8
+ parser = argparse.ArgumentParser(description="Document Classification with LSTM")
9
+ parser.add_argument("--data_path", type=str, required=True, help="Path to the dataset")
10
+ parser.add_argument("--model_path", type=str, required=True, help="Path to the trained model")
11
+ parser.add_argument("--max_seq_length", type=int, default=512, help="Maximum sequence length for LSTM")
12
+ parser.add_argument("--batch_size", type=int, default=32, help="Batch size for training and evaluation")
13
+ parser.add_argument("--num_classes", type=int, required=True, help="Number of classes for classification")
14
+ parser.add_argument("--text_column", type=str, default="text", help="Column name for text data")
15
+ parser.add_argument("--label_column", type=str, default="label", help="Column name for labels")
16
+ parser.add_argument("--class_names", type=str, nargs='+', required=True, help="List of class names for classification")
17
+ parser.add_argument("--inference_batch_limit", type=int, default=-1, help="Limit for inference batch counts")
18
+ parser.add_argument("--print_predictions", type=bool, default=False, help="Print predictions to console")
19
+ args = parser.parse_args()
20
+
21
+ class_names = args.class_names
22
+
23
+ # Set device
24
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
25
+
26
+ # Prepare data
27
+ tokenizer = LSTMTokenizer(max_length=args.max_seq_length)
28
+ test_dataset, _, _, vocab_size = prepare_lstm_data(args.data_path,
29
+ text_col=args.text_column,
30
+ label_col=args.label_column,
31
+ tokenizer=tokenizer,
32
+ batch_size=args.batch_size,
33
+ max_length=args.max_seq_length, val_split=0.0, test_split=0.0, return_datasets=True)
34
+
35
+ test_loader, _, _, vocab_size = prepare_lstm_data(args.data_path,
36
+ text_col=args.text_column,
37
+ label_col=args.label_column,
38
+ tokenizer=tokenizer,
39
+ batch_size=args.batch_size,
40
+ max_length=args.max_seq_length, val_split=0.0, test_split=0.0)
41
+
42
+ # Load model
43
+ model = DocumentBiLSTM(input_dim=tokenizer.vocab_size,
44
+ embedding_dim=128,
45
+ hidden_dim=64,
46
+ output_dim=args.num_classes)
47
+
48
+ model.load_state_dict(torch.load(args.model_path))
49
+ model = model.to(device)
50
+
51
+ all_labels = np.array([], dtype=int)
52
+ all_predictions = np.array([], dtype=int)
53
+
54
+ # Inference
55
+ batch_count = 0
56
+ with torch.no_grad():
57
+ for batch in test_loader:
58
+ input_ids = batch['input_ids'].to(device)
59
+ labels = batch['label'].to(device)
60
+ all_labels = np.append(all_labels, labels.cpu().numpy())
61
+
62
+ outputs = model(input_ids)
63
+ predictions = torch.argmax(outputs, dim=1)
64
+ all_predictions = np.append(all_predictions, predictions.cpu().numpy())
65
+
66
+ if args.print_predictions:
67
+ for i in range(len(predictions)):
68
+ print(f"Text: {test_dataset.get_text_(batch_count * args.batch_size + i)}, Prediction: {class_names[predictions[i]]}, True Label: {class_names[labels[i]]}")
69
+
70
+ if args.inference_batch_limit > 0 and batch_count >= args.inference_batch_limit:
71
+ break
72
+
73
+ batch_count += 1
74
+
75
+ # Print classification report
76
+ # Calculate accuracy, F1 score, recall, and precision
77
+ accuracy = sklearn.metrics.accuracy_score(all_labels, all_predictions)
78
+ f1 = sklearn.metrics.f1_score(all_labels, all_predictions, average='weighted')
79
+ precision = sklearn.metrics.precision_score(all_labels, all_predictions, average='weighted')
80
+ recall = sklearn.metrics.recall_score(all_labels, all_predictions, average='weighted')
81
+
82
+ print(f"Accuracy: {accuracy}")
83
+ print(f"F1 Score: {f1}")
84
+ print(f"Precision: {precision}")
85
+ print(f"Recall: {recall}")
86
+
87
+ with open("predictions_lstm.txt", "w") as f:
88
+ for i in range(len(all_labels)):
89
+ idx = int(i)
90
+ f.write(f"Text: {test_dataset.get_text_(idx)}\n")
91
+ f.write(f"True Label: {all_labels[idx]}, Predicted Label: {all_predictions[idx]}\n")
92
+ f.write(f"Predicted Class: {class_names[all_predictions[idx]] if len(class_names) > all_predictions[idx] else "Unknown"}, True Class: {class_names[all_labels[idx]] if len(class_names) > all_predictions[idx] else "Unknown"}\n")
93
+ f.write("\n")
94
+
95
+ with open("metrics_lstm.txt", "w") as f:
96
+ f.write(f"Accuracy: {accuracy}\n")
97
+ f.write(f"F1 Score: {f1}\n")
98
+ f.write(f"Precision: {precision}\n")
99
+ f.write(f"Recall: {recall}\n")
100
+
test_data.csv ADDED
The diff for this file is too large to render. See raw diff
 
test_data_creation.ipynb ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stdout",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "c:\\Users\\admin\\Documents\\DocBERT2\\news-category-dataset\n"
13
+ ]
14
+ },
15
+ {
16
+ "name": "stderr",
17
+ "output_type": "stream",
18
+ "text": [
19
+ "Cloning into 'news-category-dataset'...\n",
20
+ "c:\\Users\\admin\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\IPython\\core\\magics\\osm.py:417: UserWarning: This is now an optional IPython functionality, setting dhist requires you to install the `pickleshare` library.\n",
21
+ " self.shell.db['dhist'] = compress_dhist(dhist)[-100:]\n"
22
+ ]
23
+ }
24
+ ],
25
+ "source": [
26
+ "# Dataset is from Heegyu Kim, available at https://huggingface.co/datasets/heegyu/news-category-dataset, under CC-BY 4.0 license.\n",
27
+ "# The dataset is a collection of news articles from various categories from Huffington Post.\n",
28
+ "!git clone https://huggingface.co/datasets/heegyu/news-category-dataset"
29
+ ]
30
+ },
31
+ {
32
+ "cell_type": "code",
33
+ "execution_count": 3,
34
+ "metadata": {},
35
+ "outputs": [],
36
+ "source": [
37
+ "import pandas as pd\n",
38
+ "import numpy as np"
39
+ ]
40
+ },
41
+ {
42
+ "cell_type": "code",
43
+ "execution_count": 6,
44
+ "metadata": {},
45
+ "outputs": [
46
+ {
47
+ "data": {
48
+ "text/html": [
49
+ "<div>\n",
50
+ "<style scoped>\n",
51
+ " .dataframe tbody tr th:only-of-type {\n",
52
+ " vertical-align: middle;\n",
53
+ " }\n",
54
+ "\n",
55
+ " .dataframe tbody tr th {\n",
56
+ " vertical-align: top;\n",
57
+ " }\n",
58
+ "\n",
59
+ " .dataframe thead th {\n",
60
+ " text-align: right;\n",
61
+ " }\n",
62
+ "</style>\n",
63
+ "<table border=\"1\" class=\"dataframe\">\n",
64
+ " <thead>\n",
65
+ " <tr style=\"text-align: right;\">\n",
66
+ " <th></th>\n",
67
+ " <th>link</th>\n",
68
+ " <th>headline</th>\n",
69
+ " <th>category</th>\n",
70
+ " <th>short_description</th>\n",
71
+ " <th>authors</th>\n",
72
+ " <th>date</th>\n",
73
+ " </tr>\n",
74
+ " </thead>\n",
75
+ " <tbody>\n",
76
+ " <tr>\n",
77
+ " <th>0</th>\n",
78
+ " <td>https://www.huffpost.com/entry/covid-boosters-...</td>\n",
79
+ " <td>Over 4 Million Americans Roll Up Sleeves For O...</td>\n",
80
+ " <td>U.S. NEWS</td>\n",
81
+ " <td>Health experts said it is too early to predict...</td>\n",
82
+ " <td>Carla K. Johnson, AP</td>\n",
83
+ " <td>2022-09-23</td>\n",
84
+ " </tr>\n",
85
+ " <tr>\n",
86
+ " <th>1</th>\n",
87
+ " <td>https://www.huffpost.com/entry/american-airlin...</td>\n",
88
+ " <td>American Airlines Flyer Charged, Banned For Li...</td>\n",
89
+ " <td>U.S. NEWS</td>\n",
90
+ " <td>He was subdued by passengers and crew when he ...</td>\n",
91
+ " <td>Mary Papenfuss</td>\n",
92
+ " <td>2022-09-23</td>\n",
93
+ " </tr>\n",
94
+ " <tr>\n",
95
+ " <th>2</th>\n",
96
+ " <td>https://www.huffpost.com/entry/funniest-tweets...</td>\n",
97
+ " <td>23 Of The Funniest Tweets About Cats And Dogs ...</td>\n",
98
+ " <td>COMEDY</td>\n",
99
+ " <td>\"Until you have a dog you don't understand wha...</td>\n",
100
+ " <td>Elyse Wanshel</td>\n",
101
+ " <td>2022-09-23</td>\n",
102
+ " </tr>\n",
103
+ " <tr>\n",
104
+ " <th>3</th>\n",
105
+ " <td>https://www.huffpost.com/entry/funniest-parent...</td>\n",
106
+ " <td>The Funniest Tweets From Parents This Week (Se...</td>\n",
107
+ " <td>PARENTING</td>\n",
108
+ " <td>\"Accidentally put grown-up toothpaste on my to...</td>\n",
109
+ " <td>Caroline Bologna</td>\n",
110
+ " <td>2022-09-23</td>\n",
111
+ " </tr>\n",
112
+ " <tr>\n",
113
+ " <th>4</th>\n",
114
+ " <td>https://www.huffpost.com/entry/amy-cooper-lose...</td>\n",
115
+ " <td>Woman Who Called Cops On Black Bird-Watcher Lo...</td>\n",
116
+ " <td>U.S. NEWS</td>\n",
117
+ " <td>Amy Cooper accused investment firm Franklin Te...</td>\n",
118
+ " <td>Nina Golgowski</td>\n",
119
+ " <td>2022-09-22</td>\n",
120
+ " </tr>\n",
121
+ " </tbody>\n",
122
+ "</table>\n",
123
+ "</div>"
124
+ ],
125
+ "text/plain": [
126
+ " link \\\n",
127
+ "0 https://www.huffpost.com/entry/covid-boosters-... \n",
128
+ "1 https://www.huffpost.com/entry/american-airlin... \n",
129
+ "2 https://www.huffpost.com/entry/funniest-tweets... \n",
130
+ "3 https://www.huffpost.com/entry/funniest-parent... \n",
131
+ "4 https://www.huffpost.com/entry/amy-cooper-lose... \n",
132
+ "\n",
133
+ " headline category \\\n",
134
+ "0 Over 4 Million Americans Roll Up Sleeves For O... U.S. NEWS \n",
135
+ "1 American Airlines Flyer Charged, Banned For Li... U.S. NEWS \n",
136
+ "2 23 Of The Funniest Tweets About Cats And Dogs ... COMEDY \n",
137
+ "3 The Funniest Tweets From Parents This Week (Se... PARENTING \n",
138
+ "4 Woman Who Called Cops On Black Bird-Watcher Lo... U.S. NEWS \n",
139
+ "\n",
140
+ " short_description authors \\\n",
141
+ "0 Health experts said it is too early to predict... Carla K. Johnson, AP \n",
142
+ "1 He was subdued by passengers and crew when he ... Mary Papenfuss \n",
143
+ "2 \"Until you have a dog you don't understand wha... Elyse Wanshel \n",
144
+ "3 \"Accidentally put grown-up toothpaste on my to... Caroline Bologna \n",
145
+ "4 Amy Cooper accused investment firm Franklin Te... Nina Golgowski \n",
146
+ "\n",
147
+ " date \n",
148
+ "0 2022-09-23 \n",
149
+ "1 2022-09-23 \n",
150
+ "2 2022-09-23 \n",
151
+ "3 2022-09-23 \n",
152
+ "4 2022-09-22 "
153
+ ]
154
+ },
155
+ "execution_count": 6,
156
+ "metadata": {},
157
+ "output_type": "execute_result"
158
+ }
159
+ ],
160
+ "source": [
161
+ "df = pd.read_json(\"./news-category-dataset/data.json\", lines=True)\n",
162
+ "df.head(5)"
163
+ ]
164
+ },
165
+ {
166
+ "cell_type": "code",
167
+ "execution_count": 8,
168
+ "metadata": {},
169
+ "outputs": [
170
+ {
171
+ "name": "stdout",
172
+ "output_type": "stream",
173
+ "text": [
174
+ "Unique categories in the dataset: ['U.S. NEWS' 'COMEDY' 'PARENTING' 'WORLD NEWS' 'CULTURE & ARTS' 'TECH'\n",
175
+ " 'SPORTS' 'ENTERTAINMENT' 'POLITICS' 'WEIRD NEWS' 'ENVIRONMENT'\n",
176
+ " 'EDUCATION' 'CRIME' 'SCIENCE' 'WELLNESS' 'BUSINESS' 'STYLE & BEAUTY'\n",
177
+ " 'FOOD & DRINK' 'MEDIA' 'QUEER VOICES' 'HOME & LIVING' 'WOMEN'\n",
178
+ " 'BLACK VOICES' 'TRAVEL' 'MONEY' 'RELIGION' 'LATINO VOICES' 'IMPACT'\n",
179
+ " 'WEDDINGS' 'COLLEGE' 'PARENTS' 'ARTS & CULTURE' 'STYLE' 'GREEN' 'TASTE'\n",
180
+ " 'HEALTHY LIVING' 'THE WORLDPOST' 'GOOD NEWS' 'WORLDPOST' 'FIFTY' 'ARTS'\n",
181
+ " 'DIVORCE']\n"
182
+ ]
183
+ }
184
+ ],
185
+ "source": [
186
+ "unique_categories = df['category'].unique()\n",
187
+ "print(\"Unique categories in the dataset: \", unique_categories)"
188
+ ]
189
+ },
190
+ {
191
+ "cell_type": "code",
192
+ "execution_count": 13,
193
+ "metadata": {},
194
+ "outputs": [
195
+ {
196
+ "name": "stdout",
197
+ "output_type": "stream",
198
+ "text": [
199
+ "Number of WORLD category articles: 9542\n"
200
+ ]
201
+ }
202
+ ],
203
+ "source": [
204
+ "world_category = df[(df['category'] == 'WORLD') | (df['category'] == 'WORLD NEWS') | (df['category'] == 'WORLDPOST') | (df['category'] == 'THE WORLDPOST')]\n",
205
+ "print(\"Number of WORLD category articles: \", len(world_category))"
206
+ ]
207
+ },
208
+ {
209
+ "cell_type": "code",
210
+ "execution_count": null,
211
+ "metadata": {},
212
+ "outputs": [
213
+ {
214
+ "name": "stdout",
215
+ "output_type": "stream",
216
+ "text": [
217
+ "Number of SPORTS category articles: 5077\n"
218
+ ]
219
+ }
220
+ ],
221
+ "source": [
222
+ "sports_category = df[(df['category'] == 'SPORTS') | (df['category'] == 'SPORT')]\n",
223
+ "print(\"Number of SPORTS category articles: \", len(sports_category))"
224
+ ]
225
+ },
226
+ {
227
+ "cell_type": "code",
228
+ "execution_count": 16,
229
+ "metadata": {},
230
+ "outputs": [
231
+ {
232
+ "name": "stdout",
233
+ "output_type": "stream",
234
+ "text": [
235
+ "Number of BUSINESS category articles: 5992\n"
236
+ ]
237
+ }
238
+ ],
239
+ "source": [
240
+ "business_category = df[(df['category'] == 'BUSINESS')]\n",
241
+ "print(\"Number of BUSINESS category articles: \", len(business_category))"
242
+ ]
243
+ },
244
+ {
245
+ "cell_type": "code",
246
+ "execution_count": 17,
247
+ "metadata": {},
248
+ "outputs": [
249
+ {
250
+ "name": "stdout",
251
+ "output_type": "stream",
252
+ "text": [
253
+ "Number of SCIENCE category articles: 4310\n"
254
+ ]
255
+ }
256
+ ],
257
+ "source": [
258
+ "science_category = df[(df['category'] == 'SCIENCE') | (df['category'] == 'SCIENCE & TECH') | (df['category'] == 'SCIENCE & TECH') | (df['category'] == 'TECH')]\n",
259
+ "print(\"Number of SCIENCE category articles: \", len(science_category))"
260
+ ]
261
+ },
262
+ {
263
+ "cell_type": "code",
264
+ "execution_count": null,
265
+ "metadata": {},
266
+ "outputs": [
267
+ {
268
+ "name": "stderr",
269
+ "output_type": "stream",
270
+ "text": [
271
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:1: SettingWithCopyWarning: \n",
272
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
273
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
274
+ "\n",
275
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
276
+ " science_category['Description'] = science_category['headline'] + \"\\n\\n\" + science_category['short_description']\n",
277
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:2: SettingWithCopyWarning: \n",
278
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
279
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
280
+ "\n",
281
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
282
+ " science_category['Class Index'] = 3\n",
283
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:4: SettingWithCopyWarning: \n",
284
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
285
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
286
+ "\n",
287
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
288
+ " business_category['Description'] = business_category['headline'] + \"\\n\\n\" + business_category['short_description']\n",
289
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:5: SettingWithCopyWarning: \n",
290
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
291
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
292
+ "\n",
293
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
294
+ " business_category['Class Index'] = 2\n",
295
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:7: SettingWithCopyWarning: \n",
296
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
297
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
298
+ "\n",
299
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
300
+ " sports_category['Description'] = sports_category['headline'] + \"\\n\\n\" + sports_category['short_description']\n",
301
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:8: SettingWithCopyWarning: \n",
302
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
303
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
304
+ "\n",
305
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
306
+ " sports_category['Class Index'] = 1\n",
307
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:10: SettingWithCopyWarning: \n",
308
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
309
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
310
+ "\n",
311
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
312
+ " world_category['Description'] = world_category['headline'] + \"\\n\\n\" + world_category['short_description']\n",
313
+ "C:\\Users\\admin\\AppData\\Local\\Temp\\ipykernel_6844\\3064268492.py:11: SettingWithCopyWarning: \n",
314
+ "A value is trying to be set on a copy of a slice from a DataFrame.\n",
315
+ "Try using .loc[row_indexer,col_indexer] = value instead\n",
316
+ "\n",
317
+ "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
318
+ " world_category['Class Index'] = 0\n"
319
+ ]
320
+ },
321
+ {
322
+ "data": {
323
+ "text/html": [
324
+ "<div>\n",
325
+ "<style scoped>\n",
326
+ " .dataframe tbody tr th:only-of-type {\n",
327
+ " vertical-align: middle;\n",
328
+ " }\n",
329
+ "\n",
330
+ " .dataframe tbody tr th {\n",
331
+ " vertical-align: top;\n",
332
+ " }\n",
333
+ "\n",
334
+ " .dataframe thead th {\n",
335
+ " text-align: right;\n",
336
+ " }\n",
337
+ "</style>\n",
338
+ "<table border=\"1\" class=\"dataframe\">\n",
339
+ " <thead>\n",
340
+ " <tr style=\"text-align: right;\">\n",
341
+ " <th></th>\n",
342
+ " <th>Description</th>\n",
343
+ " <th>Class Index</th>\n",
344
+ " </tr>\n",
345
+ " </thead>\n",
346
+ " <tbody>\n",
347
+ " <tr>\n",
348
+ " <th>0</th>\n",
349
+ " <td>Trump’s Saudi Trip Should Be About Ending The ...</td>\n",
350
+ " <td>0</td>\n",
351
+ " </tr>\n",
352
+ " <tr>\n",
353
+ " <th>1</th>\n",
354
+ " <td>Russia Vows To Expand 'Black List' Of American...</td>\n",
355
+ " <td>0</td>\n",
356
+ " </tr>\n",
357
+ " <tr>\n",
358
+ " <th>2</th>\n",
359
+ " <td>It's Been A Long, Crazy Year Since Britain's S...</td>\n",
360
+ " <td>0</td>\n",
361
+ " </tr>\n",
362
+ " <tr>\n",
363
+ " <th>3</th>\n",
364
+ " <td>First Penis Transplants In U.S. Planned For Wo...</td>\n",
365
+ " <td>3</td>\n",
366
+ " </tr>\n",
367
+ " <tr>\n",
368
+ " <th>4</th>\n",
369
+ " <td>U.S.-Backed Forces Prepare For ISIS To Use Che...</td>\n",
370
+ " <td>0</td>\n",
371
+ " </tr>\n",
372
+ " <tr>\n",
373
+ " <th>5</th>\n",
374
+ " <td>The European Migrant Crisis Is A Nightmare. Cl...</td>\n",
375
+ " <td>0</td>\n",
376
+ " </tr>\n",
377
+ " <tr>\n",
378
+ " <th>6</th>\n",
379
+ " <td>A Bunch Of Strange Things Just Went Down At NB...</td>\n",
380
+ " <td>1</td>\n",
381
+ " </tr>\n",
382
+ " <tr>\n",
383
+ " <th>7</th>\n",
384
+ " <td>Minor Explosion Rattles IMF Headquarters In Pa...</td>\n",
385
+ " <td>0</td>\n",
386
+ " </tr>\n",
387
+ " <tr>\n",
388
+ " <th>8</th>\n",
389
+ " <td>Shalane Flanagan Becomes First U.S. Woman To W...</td>\n",
390
+ " <td>1</td>\n",
391
+ " </tr>\n",
392
+ " <tr>\n",
393
+ " <th>9</th>\n",
394
+ " <td>10 Instagrams To Celebrate Crown Prince Hussei...</td>\n",
395
+ " <td>0</td>\n",
396
+ " </tr>\n",
397
+ " </tbody>\n",
398
+ "</table>\n",
399
+ "</div>"
400
+ ],
401
+ "text/plain": [
402
+ " Description Class Index\n",
403
+ "0 Trump’s Saudi Trip Should Be About Ending The ... 0\n",
404
+ "1 Russia Vows To Expand 'Black List' Of American... 0\n",
405
+ "2 It's Been A Long, Crazy Year Since Britain's S... 0\n",
406
+ "3 First Penis Transplants In U.S. Planned For Wo... 3\n",
407
+ "4 U.S.-Backed Forces Prepare For ISIS To Use Che... 0\n",
408
+ "5 The European Migrant Crisis Is A Nightmare. Cl... 0\n",
409
+ "6 A Bunch Of Strange Things Just Went Down At NB... 1\n",
410
+ "7 Minor Explosion Rattles IMF Headquarters In Pa... 0\n",
411
+ "8 Shalane Flanagan Becomes First U.S. Woman To W... 1\n",
412
+ "9 10 Instagrams To Celebrate Crown Prince Hussei... 0"
413
+ ]
414
+ },
415
+ "execution_count": 18,
416
+ "metadata": {},
417
+ "output_type": "execute_result"
418
+ }
419
+ ],
420
+ "source": [
421
+ "science_category['Description'] = science_category['headline'] + \"\\n\\n\" + science_category['short_description']\n",
422
+ "science_category['Class Index'] = 3\n",
423
+ "\n",
424
+ "business_category['Description'] = business_category['headline'] + \"\\n\\n\" + business_category['short_description']\n",
425
+ "business_category['Class Index'] = 2\n",
426
+ "\n",
427
+ "sports_category['Description'] = sports_category['headline'] + \"\\n\\n\" + sports_category['short_description']\n",
428
+ "sports_category['Class Index'] = 1\n",
429
+ "\n",
430
+ "world_category['Description'] = world_category['headline'] + \"\\n\\n\" + world_category['short_description']\n",
431
+ "world_category['Class Index'] = 0\n",
432
+ "\n",
433
+ "science_category = science_category[['Description', 'Class Index']]\n",
434
+ "business_category = business_category[['Description', 'Class Index']]\n",
435
+ "sports_category = sports_category[['Description', 'Class Index']]\n",
436
+ "world_category = world_category[['Description', 'Class Index']]\n",
437
+ "\n",
438
+ "test_data_df = pd.concat([science_category, business_category, sports_category, world_category], ignore_index=True)\n",
439
+ "# Shuffle the DataFrame\n",
440
+ "test_data_df = test_data_df.sample(frac=1, random_state=42).reset_index(drop=True)\n",
441
+ "\n",
442
+ "test_data_df.head(10)"
443
+ ]
444
+ },
445
+ {
446
+ "cell_type": "code",
447
+ "execution_count": 19,
448
+ "metadata": {},
449
+ "outputs": [],
450
+ "source": [
451
+ "test_data_df.to_csv(\"test_data.csv\", index=False)"
452
+ ]
453
+ }
454
+ ],
455
+ "metadata": {
456
+ "kernelspec": {
457
+ "display_name": "Python 3",
458
+ "language": "python",
459
+ "name": "python3"
460
+ },
461
+ "language_info": {
462
+ "codemirror_mode": {
463
+ "name": "ipython",
464
+ "version": 3
465
+ },
466
+ "file_extension": ".py",
467
+ "mimetype": "text/x-python",
468
+ "name": "python",
469
+ "nbconvert_exporter": "python",
470
+ "pygments_lexer": "ipython3",
471
+ "version": "3.12.1"
472
+ }
473
+ },
474
+ "nbformat": 4,
475
+ "nbformat_minor": 2
476
+ }