faisalsns commited on
Commit
7bb1e78
·
1 Parent(s): a658b59

initial commit

Browse files
Files changed (5) hide show
  1. app.py +446 -0
  2. config.json +15 -0
  3. ev_classifier_model.pth +3 -0
  4. inference.py +32 -0
  5. model.py +25 -0
app.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #df = pd.read_csv("data\Electric_Vehicle_Population_Data_fixed.csv", nrows=10)
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+ import torch
6
+ import torch.nn as nn
7
+ import torch.optim as optim
8
+ from torch.utils.data import Dataset, DataLoader, TensorDataset
9
+ from sklearn.model_selection import train_test_split
10
+ from sklearn.preprocessing import StandardScaler, LabelEncoder
11
+ from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
12
+ import matplotlib.pyplot as plt
13
+ import seaborn as sns
14
+ import warnings
15
+ warnings.filterwarnings('ignore')
16
+
17
+ # Define a simple TabularModel class
18
+ class TabularModel(nn.Module):
19
+ def __init__(self, input_size, hidden_sizes, output_size, dropout_rate=0.2):
20
+ super(TabularModel, self).__init__()
21
+
22
+ layers = []
23
+ prev_size = input_size
24
+
25
+ # Create hidden layers
26
+ for hidden_size in hidden_sizes:
27
+ layers.extend([
28
+ nn.Linear(prev_size, hidden_size),
29
+ nn.BatchNorm1d(hidden_size),
30
+ nn.ReLU(),
31
+ nn.Dropout(dropout_rate)
32
+ ])
33
+ prev_size = hidden_size
34
+
35
+ # Output layer
36
+ layers.append(nn.Linear(prev_size, output_size))
37
+
38
+ self.model = nn.Sequential(*layers)
39
+
40
+ def forward(self, x):
41
+ return self.model(x)
42
+
43
+ # Data preprocessing function
44
+ def preprocess_data(df, target_column, test_size=0.2):
45
+ """
46
+ Preprocess tabular data for neural network training
47
+ """
48
+ # Separate features and target
49
+ X = df.drop(columns=[target_column])
50
+ y = df[target_column]
51
+
52
+ # Handle categorical variables
53
+ categorical_columns = X.select_dtypes(include=['object']).columns
54
+ numerical_columns = X.select_dtypes(include=['int64', 'float64']).columns
55
+
56
+ # Encode categorical variables
57
+ label_encoders = {}
58
+ for col in categorical_columns:
59
+ le = LabelEncoder()
60
+ X[col] = le.fit_transform(X[col].astype(str))
61
+ label_encoders[col] = le
62
+
63
+ # Scale numerical features
64
+ scaler = StandardScaler()
65
+ X[numerical_columns] = scaler.fit_transform(X[numerical_columns])
66
+
67
+ # Encode target variable if it's categorical
68
+ target_encoder = None
69
+ if y.dtype == 'object':
70
+ target_encoder = LabelEncoder()
71
+ y = target_encoder.fit_transform(y)
72
+
73
+ # Split the data
74
+ X_train, X_test, y_train, y_test = train_test_split(
75
+ X.values, y.values, test_size=test_size, random_state=42, stratify=y
76
+ )
77
+
78
+ return (X_train, X_test, y_train, y_test, scaler, label_encoders, target_encoder)
79
+
80
+ # Training function
81
+ def train_model(model, train_loader, val_loader, epochs=100, lr=0.001):
82
+ """
83
+ Train the tabular model
84
+ """
85
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
86
+ model.to(device)
87
+
88
+ criterion = nn.CrossEntropyLoss()
89
+ optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=1e-5)
90
+ scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience=10)
91
+
92
+ train_losses = []
93
+ val_losses = []
94
+
95
+ for epoch in range(epochs):
96
+ # Training phase
97
+ model.train()
98
+ train_loss = 0.0
99
+ for batch_idx, (data, target) in enumerate(train_loader):
100
+ data, target = data.to(device), target.to(device)
101
+
102
+ optimizer.zero_grad()
103
+ output = model(data)
104
+ loss = criterion(output, target)
105
+ loss.backward()
106
+ optimizer.step()
107
+
108
+ train_loss += loss.item()
109
+
110
+ # Validation phase
111
+ model.eval()
112
+ val_loss = 0.0
113
+ with torch.no_grad():
114
+ for data, target in val_loader:
115
+ data, target = data.to(device), target.to(device)
116
+ output = model(data)
117
+ val_loss += criterion(output, target).item()
118
+
119
+ avg_train_loss = train_loss / len(train_loader)
120
+ avg_val_loss = val_loss / len(val_loader)
121
+
122
+ train_losses.append(avg_train_loss)
123
+ val_losses.append(avg_val_loss)
124
+
125
+ scheduler.step(avg_val_loss)
126
+
127
+ if (epoch + 1) % 20 == 0:
128
+ print(f'Epoch [{epoch+1}/{epochs}], Train Loss: {avg_train_loss:.4f}, Val Loss: {avg_val_loss:.4f}')
129
+
130
+ return train_losses, val_losses
131
+
132
+ # Evaluation function
133
+ def evaluate_model(model, test_loader, target_encoder=None):
134
+ """
135
+ Evaluate the trained model
136
+ """
137
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
138
+ model.eval()
139
+
140
+ all_predictions = []
141
+ all_targets = []
142
+
143
+ with torch.no_grad():
144
+ for data, target in test_loader:
145
+ data, target = data.to(device), target.to(device)
146
+ output = model(data)
147
+ predictions = torch.argmax(output, dim=1)
148
+
149
+ all_predictions.extend(predictions.cpu().numpy())
150
+ all_targets.extend(target.cpu().numpy())
151
+
152
+ # Convert back to original labels if target was encoded
153
+ if target_encoder:
154
+ all_predictions = target_encoder.inverse_transform(all_predictions)
155
+ all_targets = target_encoder.inverse_transform(all_targets)
156
+
157
+ accuracy = accuracy_score(all_targets, all_predictions)
158
+ report = classification_report(all_targets, all_predictions)
159
+
160
+ return accuracy, report, all_predictions, all_targets
161
+
162
+ # Plotting function for training history
163
+ def plot_training_history(train_losses, val_losses):
164
+ """
165
+ Plot training and validation losses
166
+ """
167
+ plt.figure(figsize=(12, 5))
168
+
169
+ plt.subplot(1, 2, 1)
170
+ plt.plot(train_losses, label='Training Loss', color='blue')
171
+ plt.plot(val_losses, label='Validation Loss', color='red')
172
+ plt.xlabel('Epoch')
173
+ plt.ylabel('Loss')
174
+ plt.title('Training and Validation Loss')
175
+ plt.legend()
176
+ plt.grid(True)
177
+
178
+ plt.subplot(1, 2, 2)
179
+ plt.plot(train_losses, label='Training Loss', color='blue')
180
+ plt.plot(val_losses, label='Validation Loss', color='red')
181
+ plt.xlabel('Epoch')
182
+ plt.ylabel('Loss (Log Scale)')
183
+ plt.title('Training and Validation Loss (Log Scale)')
184
+ plt.yscale('log')
185
+ plt.legend()
186
+ plt.grid(True)
187
+
188
+ plt.tight_layout()
189
+ plt.show()
190
+
191
+ # Function to plot confusion matrix
192
+ def plot_confusion_matrix(y_true, y_pred, labels=None):
193
+ """
194
+ Plot confusion matrix
195
+ """
196
+ cm = confusion_matrix(y_true, y_pred)
197
+ plt.figure(figsize=(8, 6))
198
+ sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
199
+ xticklabels=labels, yticklabels=labels)
200
+ plt.xlabel('Predicted')
201
+ plt.ylabel('Actual')
202
+ plt.title('Confusion Matrix')
203
+ plt.show()
204
+
205
+ # Function to save model
206
+ def save_model(model, filepath, scaler, label_encoders, target_encoder=None):
207
+ """
208
+ Save the trained model and preprocessing objects
209
+ """
210
+ torch.save({
211
+ 'model_state_dict': model.state_dict(),
212
+ 'scaler': scaler,
213
+ 'label_encoders': label_encoders,
214
+ 'target_encoder': target_encoder
215
+ }, filepath)
216
+ print(f"Model saved to {filepath}")
217
+
218
+ # Function to load model
219
+ def load_model(filepath, input_size, hidden_sizes, output_size, dropout_rate=0.2):
220
+ """
221
+ Load the trained model and preprocessing objects
222
+ """
223
+ checkpoint = torch.load(filepath)
224
+
225
+ model = TabularModel(input_size, hidden_sizes, output_size, dropout_rate)
226
+ model.load_state_dict(checkpoint['model_state_dict'])
227
+
228
+ return model, checkpoint['scaler'], checkpoint['label_encoders'], checkpoint['target_encoder']
229
+
230
+ # Main training pipeline
231
+ def main():
232
+ # Load your CSV file
233
+ # Replace 'electric_vehicles.csv' with your actual CSV file path
234
+ #df = pd.read_csv('data\Electric_Vehicle_Population_Data_fixed.csv", nrows=10')
235
+ df = pd.read_csv("Electric_Vehicle_Population.csv")
236
+
237
+ # Data preprocessing for Electric Vehicle dataset
238
+ print(f"Original dataset shape: {df.shape}")
239
+ print(f"Columns: {list(df.columns)}")
240
+
241
+ # Clean and prepare the data
242
+ # Remove or handle missing values
243
+ df = df.dropna(subset=['Make', 'Model', 'Electric Vehicle Type', 'Model Year'])
244
+
245
+ # Extract useful features and create target variable
246
+ # For this example, let's predict Electric Vehicle Type (BEV vs PHEV)
247
+ df_clean = df.copy()
248
+
249
+ # Clean numeric columns
250
+ df_clean['Model Year'] = pd.to_numeric(df_clean['Model Year'], errors='coerce')
251
+ df_clean['Electric Range'] = pd.to_numeric(df_clean['Electric Range'], errors='coerce')
252
+ df_clean['Base MSRP'] = pd.to_numeric(df_clean['Base MSRP'], errors='coerce')
253
+ df_clean['Legislative District'] = pd.to_numeric(df_clean['Legislative District'], errors='coerce')
254
+
255
+ # Fill missing values
256
+ df_clean['Electric Range'] = df_clean['Electric Range'].fillna(df_clean['Electric Range'].median())
257
+ df_clean['Base MSRP'] = df_clean['Base MSRP'].fillna(df_clean['Base MSRP'].median())
258
+ df_clean['Legislative District'] = df_clean['Legislative District'].fillna(0)
259
+
260
+ # Create binary target: BEV vs PHEV
261
+ df_clean['target'] = (df_clean['Electric Vehicle Type'] == 'Battery Electric Vehicle (BEV)').astype(int)
262
+
263
+ # Select relevant features for training
264
+ feature_columns = [
265
+ 'Model Year', 'Make', 'Model', 'Electric Range', 'Base MSRP',
266
+ 'Legislative District', 'County', 'State', 'Clean Alternative Fuel Vehicle (CAFV) Eligibility'
267
+ ]
268
+
269
+ # Create final dataset with selected features
270
+ df_final = df_clean[feature_columns + ['target']].copy()
271
+
272
+ # Clean column names for easier handling
273
+ df_final.columns = [
274
+ 'model_year', 'make', 'model', 'electric_range', 'base_msrp',
275
+ 'legislative_district', 'county', 'state', 'cafv_eligibility', 'target'
276
+ ]
277
+
278
+ # Handle categorical variables with too many categories
279
+ # Keep only top N categories for Make and Model
280
+ top_makes = df_final['make'].value_counts().head(10).index
281
+ df_final['make'] = df_final['make'].apply(lambda x: x if x in top_makes else 'OTHER')
282
+
283
+ top_models = df_final['model'].value_counts().head(15).index
284
+ df_final['model'] = df_final['model'].apply(lambda x: x if x in top_models else 'OTHER')
285
+
286
+ top_counties = df_final['county'].value_counts().head(20).index
287
+ df_final['county'] = df_final['county'].apply(lambda x: x if x in top_counties else 'OTHER')
288
+
289
+ # Remove rows where target might be ambiguous
290
+ df_final = df_final.dropna()
291
+
292
+ df = df_final
293
+ print(f"Processed dataset shape: {df.shape}")
294
+ print(f"Target distribution:")
295
+ print(f"BEV (1): {(df['target'] == 1).sum()}")
296
+ print(f"PHEV (0): {(df['target'] == 0).sum()}")
297
+
298
+ # Specify your target column name
299
+ target_column = 'target'
300
+
301
+ # Preprocess the data
302
+ X_train, X_test, y_train, y_test, scaler, label_encoders, target_encoder = preprocess_data(
303
+ df, target_column
304
+ )
305
+
306
+ # Convert to PyTorch tensors
307
+ X_train_tensor = torch.FloatTensor(X_train)
308
+ y_train_tensor = torch.LongTensor(y_train)
309
+ X_test_tensor = torch.FloatTensor(X_test)
310
+ y_test_tensor = torch.LongTensor(y_test)
311
+
312
+ # Create validation split from training data
313
+ X_train_split, X_val_split, y_train_split, y_val_split = train_test_split(
314
+ X_train_tensor, y_train_tensor, test_size=0.2, random_state=42, stratify=y_train_tensor
315
+ )
316
+
317
+ # Create data loaders
318
+ batch_size = 64
319
+ train_dataset = TensorDataset(X_train_split, y_train_split)
320
+ val_dataset = TensorDataset(X_val_split, y_val_split)
321
+ test_dataset = TensorDataset(X_test_tensor, y_test_tensor)
322
+
323
+ train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
324
+ val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
325
+ test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
326
+
327
+ # Model parameters
328
+ input_size = X_train.shape[1]
329
+ hidden_sizes = [128, 64, 32] # You can adjust these
330
+ output_size = len(np.unique(y_train))
331
+
332
+ # Create the model
333
+ model = TabularModel(
334
+ input_size=input_size,
335
+ hidden_sizes=hidden_sizes,
336
+ output_size=output_size,
337
+ dropout_rate=0.3
338
+ )
339
+
340
+ print(f"\nModel architecture:")
341
+ print(f"Input size: {input_size}")
342
+ print(f"Hidden layers: {hidden_sizes}")
343
+ print(f"Output size: {output_size}")
344
+ print(f"Total parameters: {sum(p.numel() for p in model.parameters())}")
345
+
346
+ # Train the model
347
+ print("\nStarting training...")
348
+ epochs = 100
349
+ learning_rate = 0.001
350
+
351
+ train_losses, val_losses = train_model(
352
+ model, train_loader, val_loader, epochs=epochs, lr=learning_rate
353
+ )
354
+
355
+ # Plot training history
356
+ plot_training_history(train_losses, val_losses)
357
+
358
+ # Evaluate the model
359
+ print("\nEvaluating model on test set...")
360
+ accuracy, report, predictions, targets = evaluate_model(model, test_loader, target_encoder)
361
+
362
+ print(f"Test Accuracy: {accuracy:.4f}")
363
+ print("\nClassification Report:")
364
+ print(report)
365
+
366
+ # Plot confusion matrix
367
+ labels = ['PHEV', 'BEV'] if target_encoder is None else None
368
+ plot_confusion_matrix(targets, predictions, labels)
369
+
370
+ # Save the model
371
+ model_filepath = 'ev_classifier_model.pth'
372
+ save_model(model, model_filepath, scaler, label_encoders, target_encoder)
373
+
374
+ print(f"\nTraining completed successfully!")
375
+ print(f"Final test accuracy: {accuracy:.4f}")
376
+
377
+ return model, scaler, label_encoders, target_encoder
378
+
379
+ # Function to make predictions on new data
380
+ def predict_new_data(model, new_data, scaler, label_encoders, target_encoder=None):
381
+ """
382
+ Make predictions on new data
383
+ """
384
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
385
+ model.to(device)
386
+ model.eval()
387
+
388
+ # Preprocess new data
389
+ new_data_processed = new_data.copy()
390
+
391
+ # Apply label encoders
392
+ for col, encoder in label_encoders.items():
393
+ if col in new_data_processed.columns:
394
+ # Handle unseen categories
395
+ new_data_processed[col] = new_data_processed[col].apply(
396
+ lambda x: x if x in encoder.classes_ else 'OTHER'
397
+ )
398
+ new_data_processed[col] = encoder.transform(new_data_processed[col].astype(str))
399
+
400
+ # Apply scaler to numerical columns
401
+ numerical_columns = new_data_processed.select_dtypes(include=['int64', 'float64']).columns
402
+ new_data_processed[numerical_columns] = scaler.transform(new_data_processed[numerical_columns])
403
+
404
+ # Convert to tensor
405
+ X_new = torch.FloatTensor(new_data_processed.values)
406
+ X_new = X_new.to(device)
407
+
408
+ # Make predictions
409
+ with torch.no_grad():
410
+ outputs = model(X_new)
411
+ probabilities = torch.softmax(outputs, dim=1)
412
+ predictions = torch.argmax(outputs, dim=1)
413
+
414
+ # Convert back to original labels if needed
415
+ if target_encoder:
416
+ predictions = target_encoder.inverse_transform(predictions.cpu().numpy())
417
+ else:
418
+ predictions = predictions.cpu().numpy()
419
+
420
+ return predictions, probabilities.cpu().numpy()
421
+
422
+ if __name__ == "__main__":
423
+ # Run the main training pipeline
424
+ model, scaler, label_encoders, target_encoder = main()
425
+
426
+ # Example of how to use the trained model for predictions
427
+ # Uncomment and modify the following code to make predictions on new data
428
+
429
+ # # Load new data for prediction
430
+ # new_data = pd.DataFrame({
431
+ # 'model_year': [2020, 2021, 2019],
432
+ # 'make': ['TESLA', 'NISSAN', 'CHEVROLET'],
433
+ # 'model': ['MODEL S', 'LEAF', 'BOLT EV'],
434
+ # 'electric_range': [370, 150, 259],
435
+ # 'base_msrp': [80000, 32000, 32000],
436
+ # 'legislative_district': [43, 11, 36],
437
+ # 'county': ['King', 'Snohomish', 'Pierce'],
438
+ # 'state': ['WA', 'WA', 'WA'],
439
+ # 'cafv_eligibility': ['Clean Alternative Fuel Vehicle Eligible',
440
+ # 'Clean Alternative Fuel Vehicle Eligible',
441
+ # 'Clean Alternative Fuel Vehicle Eligible']
442
+ # })
443
+ #
444
+ # predictions, probabilities = predict_new_data(model, new_data, scaler, label_encoders, target_encoder)
445
+ # print(f"Predictions: {predictions}")
446
+ # print(f"Probabilities: {probabilities}")
config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "tabular_classifier",
3
+ "task": "binary_classification",
4
+ "input_size": 9,
5
+ "hidden_sizes": [128, 64, 32],
6
+ "output_size": 2,
7
+ "dropout_rate": 0.3,
8
+ "features": [
9
+ "model_year", "make", "model", "electric_range",
10
+ "base_msrp", "legislative_district", "county",
11
+ "state", "cafv_eligibility"
12
+ ],
13
+ "target": "Electric Vehicle Type (BEV vs PHEV)",
14
+ "classes": ["PHEV", "BEV"]
15
+ }
ev_classifier_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:519c34c4eec9511725b2922fa1f8556f66aecca7dff4458bc581310cc55bb96d
3
+ size 61754
inference.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### 6. **Inference Script** (`inference.py`)
2
+ import torch
3
+ import pandas as pd
4
+ from model import TabularModel
5
+
6
+ def load_model_and_predict(data):
7
+ # Load model
8
+ checkpoint = torch.load('ev_classifier_model.pth')
9
+ model = TabularModel(input_size=9, hidden_sizes=[128, 64, 32], output_size=2)
10
+ model.load_state_dict(checkpoint['model_state_dict'])
11
+ model.eval()
12
+
13
+ # Get preprocessors
14
+ scaler = checkpoint['scaler']
15
+ label_encoders = checkpoint['label_encoders']
16
+
17
+ # Preprocess and predict
18
+ # ... (preprocessing code)
19
+
20
+ return predictions
21
+
22
+ # Example usage
23
+ if __name__ == "__main__":
24
+ sample_data = pd.DataFrame({
25
+ 'model_year': [2021],
26
+ 'make': ['TESLA'],
27
+ 'model': ['MODEL 3'],
28
+ # ... other features
29
+ })
30
+
31
+ prediction = load_model_and_predict(sample_data)
32
+ print(f"Prediction: {prediction}")
model.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Create a standalone file with just the model class
2
+ import torch
3
+ import torch.nn as nn
4
+
5
+ class TabularModel(nn.Module):
6
+ def __init__(self, input_size, hidden_sizes, output_size, dropout_rate=0.2):
7
+ super(TabularModel, self).__init__()
8
+
9
+ layers = []
10
+ prev_size = input_size
11
+
12
+ for hidden_size in hidden_sizes:
13
+ layers.extend([
14
+ nn.Linear(prev_size, hidden_size),
15
+ nn.BatchNorm1d(hidden_size),
16
+ nn.ReLU(),
17
+ nn.Dropout(dropout_rate)
18
+ ])
19
+ prev_size = hidden_size
20
+
21
+ layers.append(nn.Linear(prev_size, output_size))
22
+ self.model = nn.Sequential(*layers)
23
+
24
+ def forward(self, x):
25
+ return self.model(x)