Image Classification
Keras
litav commited on
Commit
168c33d
·
verified ·
1 Parent(s): 5e118d2

Update cnn_SaveInPainting.py

Browse files
Files changed (1) hide show
  1. cnn_SaveInPainting.py +255 -281
cnn_SaveInPainting.py CHANGED
@@ -1,281 +1,255 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Created on Sat May 18 16:15:32 2024
4
- @author: litav
5
- """
6
-
7
- # -*- coding: utf-8 -*-
8
- """
9
- Created on Sat May 18 16:15:32 2024
10
- @author: litav
11
- """
12
-
13
-
14
- #dropout 0.5
15
- # Set parameters for cross-validation
16
- #kf = KFold(n_splits=4, shuffle=True, random_state=42)
17
- #batch_size = 64
18
- #epochs = 15
19
- #Average accuracy across all folds: 78.56%
20
- #Test Loss: 0.49228477478027344, Test Accuracy: 0.7706093192100525
21
- #Classification Summary:
22
- #Real images correctly classified: 107
23
- #Real images incorrectly classified: 32
24
- #Fake images correctly classified: 108
25
- #Fake images incorrectly classified: 32
26
- #Classification Report:
27
- # precision recall f1-score support
28
- #
29
- # Real 0.77 0.77 0.77 139
30
- # Fake 0.77 0.77 0.77 140
31
-
32
-
33
-
34
- import numpy as np
35
- import tensorflow as tf
36
- import random
37
- import os
38
- import pandas as pd
39
- import cv2
40
- import matplotlib.pyplot as plt
41
- from sklearn.model_selection import KFold
42
- from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, Flatten
43
- from tensorflow.keras.optimizers import Adam
44
- from tensorflow.keras.models import Sequential
45
- from sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay
46
- from tensorflow.keras.layers import Dropout
47
- from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
48
- from sklearn.metrics import precision_score, recall_score, f1_score, classification_report
49
-
50
-
51
- # Suppress iCCP warning
52
- import warnings
53
- warnings.filterwarnings("ignore", category=UserWarning, message=".*iCCP:.*")
54
-
55
- # Define data paths
56
- train_real_folder = 'datasets/training_set/real/'
57
- train_fake_folder = 'datasets/training_set/fake/'
58
- test_real_folder = 'datasets/test_set/real/'
59
- test_fake_folder = 'datasets/test_set/fake/'
60
-
61
- # Load train image paths and labels
62
- train_image_paths = []
63
- train_labels = []
64
-
65
- # Load train_real image paths and labels
66
- for filename in os.listdir(train_real_folder):
67
- image_path = os.path.join(train_real_folder, filename)
68
- label = 0 # Real images have label 0
69
- train_image_paths.append(image_path)
70
- train_labels.append(label)
71
-
72
- # Load train_fake image paths and labels
73
- for filename in os.listdir(train_fake_folder):
74
- image_path = os.path.join(train_fake_folder, filename)
75
- label = 1 # Fake images have label 1
76
- train_image_paths.append(image_path)
77
- train_labels.append(label)
78
-
79
- # Load test image paths and labels
80
- test_image_paths = []
81
- test_labels = []
82
-
83
- # Load test_real image paths and labels
84
- for filename in os.listdir(test_real_folder):
85
- image_path = os.path.join(test_real_folder, filename)
86
- label = 0 # Assuming test real images are all real (label 0)
87
- test_image_paths.append(image_path)
88
- test_labels.append(label)
89
-
90
- # Load test_fake image paths and labels
91
- for filename in os.listdir(test_fake_folder):
92
- image_path = os.path.join(test_fake_folder, filename)
93
- label = 1 # Assuming test fake images are all fake (label 1)
94
- test_image_paths.append(image_path)
95
- test_labels.append(label)
96
-
97
- # Create DataFrames
98
- train_dataset = pd.DataFrame({'image_path': train_image_paths, 'label': train_labels})
99
- test_dataset = pd.DataFrame({'image_path': test_image_paths, 'label': test_labels})
100
-
101
- # Function to preprocess images
102
- def preprocess_image(image_path):
103
- """Loads, resizes, and normalizes an image."""
104
- image = cv2.imread(image_path)
105
- resized_image = cv2.resize(image, (128, 128)) # Target size defined here
106
- normalized_image = resized_image.astype(np.float32) / 255.0
107
- return normalized_image
108
-
109
- # Preprocess all images and convert labels to numpy arrays
110
- X = np.array([preprocess_image(path) for path in train_image_paths])
111
- Y = np.array(train_labels)
112
-
113
- # Define discriminator network
114
- def build_discriminator(input_shape, dropout_rate=0.5):
115
- model = Sequential()
116
- model.add(Conv2D(32, (3, 3), activation='relu', input_shape=input_shape))
117
- model.add(MaxPooling2D((2, 2)))
118
- model.add(Conv2D(64, (3, 3), activation='relu'))
119
- model.add(MaxPooling2D((2, 2)))
120
- model.add(Conv2D(64, (3, 3), activation='relu'))
121
- model.add(Flatten())
122
- model.add(Dense(64, activation='relu'))
123
- model.add(Dropout(dropout_rate)) # Adding dropout layer
124
- model.add(Dense(1, activation='sigmoid'))
125
- return model
126
-
127
- # Function to check if previous weights exist
128
- def load_previous_weights(model, fold_number):
129
- weights_file = f'model_weights/model_fold_{fold_number}.weights.h5'
130
- if os.path.exists(weights_file):
131
- model.load_weights(weights_file)
132
- print(f"Loaded weights from {weights_file}")
133
- else:
134
- print("No previous weights found.")
135
-
136
- # Function to save weights if current accuracy is higher
137
- def save_updated_weights(model, fold_number, val_accuracy, best_accuracy):
138
- weights_file = f'model_weights/model_fold_{fold_number}.weights.h5'
139
- if val_accuracy > best_accuracy:
140
- model.save_weights(weights_file)
141
- print(f"Saved updated weights to {weights_file} with val_accuracy: {val_accuracy:.4f}")
142
- return val_accuracy
143
- else:
144
- print(f"Did not save weights for fold {fold_number} as val_accuracy {val_accuracy:.4f} is not better than {best_accuracy:.4f}")
145
- return best_accuracy
146
-
147
- # Set parameters for cross-validation
148
- kf = KFold(n_splits=4, shuffle=True, random_state=42)
149
- batch_size = 32
150
- epochs = 15
151
-
152
- # Lists to store accuracy and loss for each fold
153
- accuracy_per_fold = []
154
- loss_per_fold = []
155
- # Store the best accuracies for each fold
156
- best_accuracies = [0] * kf.get_n_splits()
157
-
158
-
159
- # Perform K-Fold Cross-Validation
160
- for fold_number, (train_index, val_index) in enumerate(kf.split(X), 1):
161
- X_train, X_val = X[train_index], X[val_index]
162
- Y_train, Y_val = Y[train_index], Y[val_index]
163
-
164
- # Create and compile model
165
- input_dim = X_train.shape[1:] # Dimensionality of the input images
166
- model = build_discriminator(input_dim)
167
- model.compile(loss='binary_crossentropy', optimizer=Adam(0.0002, 0.5), metrics=['accuracy'])
168
-
169
- # Load previous weights if they exist
170
- load_previous_weights(model, fold_number)
171
-
172
- # Define Early Stopping callback
173
- early_stopping = EarlyStopping(monitor='val_accuracy', patience=5, restore_best_weights=True)
174
-
175
- # Define ModelCheckpoint callback to save the best weights
176
- checkpoint = ModelCheckpoint(filepath=f'best_model_weights/model_fold_{fold_number}.best.weights.h5.keras', monitor='val_accuracy', save_best_only=True, mode='max')
177
-
178
- # Train the model with the callbacks
179
- history = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=2,
180
- validation_data=(X_val, Y_val), callbacks=[early_stopping, checkpoint])
181
-
182
- # Store the accuracy and loss for this folds
183
- average_val_accuracy = np.mean(history.history['val_accuracy'])
184
- accuracy_per_fold.append(average_val_accuracy)
185
- average_val_loss = np.mean(history.history['val_loss'])
186
- loss_per_fold.append(average_val_loss)
187
-
188
- # Save updated weights if accuracy is high
189
- best_accuracies[fold_number - 1] = save_updated_weights(model, fold_number, average_val_accuracy, best_accuracies[fold_number - 1])
190
-
191
-
192
- # Print fold accuracy
193
- print(f'Fold {fold_number} average accuracy: {average_val_accuracy*100:.2f}%')
194
-
195
- # Print average accuracy across all folds
196
- print(f'Average accuracy across all folds: {np.mean(accuracy_per_fold)*100:.2f}%')
197
-
198
- # Load the model weights of the best model
199
- best_model_index = np.argmax(accuracy_per_fold)
200
- best_model_path = f'best_model_weights/model_fold_{best_model_index + 1}.best.weights.h5.keras'
201
- model.load_weights(best_model_path)
202
-
203
- # Evaluate the preprocessed test images using the best model
204
- test_loss, test_accuracy = model.evaluate(np.array([preprocess_image(path) for path in test_image_paths]), np.array(test_labels))
205
- print(f"\nTest Loss: {test_loss}, Test Accuracy: {test_accuracy}")
206
-
207
- # Predict labels for the test set using the best model
208
- predictions = model.predict(np.array([preprocess_image(path) for path in test_image_paths]))
209
- predicted_labels = (predictions > 0.5).astype(int).flatten()
210
-
211
- # Summarize the classification results
212
- true_real_correct = np.sum((np.array(test_labels) == 0) & (predicted_labels == 0))
213
- true_real_incorrect = np.sum((np.array(test_labels) == 0) & (predicted_labels == 1))
214
- true_fake_correct = np.sum((np.array(test_labels) == 1) & (predicted_labels == 1))
215
- true_fake_incorrect = np.sum((np.array(test_labels) == 1) & (predicted_labels == 0))
216
-
217
- print("\nClassification Summary:")
218
- print(f"Real images correctly classified: {true_real_correct}")
219
- print(f"Real images incorrectly classified: {true_real_incorrect}")
220
- print(f"Fake images correctly classified: {true_fake_correct}")
221
- print(f"Fake images incorrectly classified: {true_fake_incorrect}")
222
-
223
-
224
- # Print detailed classification report
225
- print("\nClassification Report:")
226
- print(classification_report(test_labels, predicted_labels, target_names=['Real', 'Fake']))
227
-
228
- print(model.summary())
229
-
230
-
231
- # Plot confusion matrix
232
- cm = confusion_matrix(test_labels, predicted_labels)
233
- disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Real', 'Fake'])
234
- disp.plot(cmap=plt.cm.Blues)
235
- plt.title("Confusion Matrix")
236
- plt.show()
237
-
238
- # Plot training & validation accuracy values
239
- plt.figure(figsize=(12, 4))
240
- plt.subplot(1, 2, 1)
241
- plt.plot(history.history['accuracy'])
242
- plt.plot(history.history['val_accuracy'])
243
- plt.title('Model accuracy')
244
- plt.ylabel('Accuracy')
245
- plt.xlabel('Epoch')
246
- plt.legend(['Train', 'Validation'], loc='upper left')
247
- plt.xticks(np.arange(0, len(history.history['accuracy']), step=1), np.arange(1, len(history.history['accuracy']) + 1, step=1))
248
-
249
-
250
- # Plot training & validation loss values
251
- plt.subplot(1, 2, 2)
252
- plt.plot(history.history['loss'])
253
- plt.plot(history.history['val_loss'])
254
- plt.title('Model loss')
255
- plt.ylabel('Loss')
256
- plt.xlabel('Epoch')
257
- plt.legend(['Train', 'Validation'], loc='upper left')
258
- plt.xticks(np.arange(0, len(history.history['loss']), step=1), np.arange(1, len(history.history['loss']) + 1, step=1))
259
-
260
-
261
- plt.tight_layout()
262
- plt.show()
263
-
264
- # Plot validation accuracy and loss per fold
265
- plt.figure(figsize=(12, 4))
266
- plt.subplot(1, 2, 1)
267
- plt.plot(range(1, kf.get_n_splits() + 1), accuracy_per_fold, marker='o')
268
- plt.title('Validation Accuracy per Fold')
269
- plt.xlabel('Fold')
270
- plt.ylabel('Accuracy')
271
-
272
- plt.subplot(1, 2, 2)
273
- plt.plot(range(1, kf.get_n_splits() + 1), loss_per_fold, marker='o')
274
- plt.title('Validation Loss per Fold')
275
- plt.xlabel('Fold')
276
- plt.ylabel('Loss')
277
-
278
- plt.tight_layout()
279
- plt
280
-
281
-
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Sat May 18 16:15:32 2024
4
+ @author: litav
5
+ """
6
+
7
+
8
+ import numpy as np
9
+ import tensorflow as tf
10
+ import random
11
+ import os
12
+ import pandas as pd
13
+ import cv2
14
+ import matplotlib.pyplot as plt
15
+ from sklearn.model_selection import KFold
16
+ from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, Flatten
17
+ from tensorflow.keras.optimizers import Adam
18
+ from tensorflow.keras.models import Sequential
19
+ from sklearn.metrics import accuracy_score, confusion_matrix, ConfusionMatrixDisplay
20
+ from tensorflow.keras.layers import Dropout
21
+ from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
22
+ from sklearn.metrics import precision_score, recall_score, f1_score, classification_report
23
+
24
+
25
+ # Suppress iCCP warning
26
+ import warnings
27
+ warnings.filterwarnings("ignore", category=UserWarning, message=".*iCCP:.*")
28
+
29
+ # Define data paths
30
+ train_real_folder = 'datasets/training_set/real/'
31
+ train_fake_folder = 'datasets/training_set/fake/'
32
+ test_real_folder = 'datasets/test_set/real/'
33
+ test_fake_folder = 'datasets/test_set/fake/'
34
+
35
+ # Load train image paths and labels
36
+ train_image_paths = []
37
+ train_labels = []
38
+
39
+ # Load train_real image paths and labels
40
+ for filename in os.listdir(train_real_folder):
41
+ image_path = os.path.join(train_real_folder, filename)
42
+ label = 0 # Real images have label 0
43
+ train_image_paths.append(image_path)
44
+ train_labels.append(label)
45
+
46
+ # Load train_fake image paths and labels
47
+ for filename in os.listdir(train_fake_folder):
48
+ image_path = os.path.join(train_fake_folder, filename)
49
+ label = 1 # Fake images have label 1
50
+ train_image_paths.append(image_path)
51
+ train_labels.append(label)
52
+
53
+ # Load test image paths and labels
54
+ test_image_paths = []
55
+ test_labels = []
56
+
57
+ # Load test_real image paths and labels
58
+ for filename in os.listdir(test_real_folder):
59
+ image_path = os.path.join(test_real_folder, filename)
60
+ label = 0 # Assuming test real images are all real (label 0)
61
+ test_image_paths.append(image_path)
62
+ test_labels.append(label)
63
+
64
+ # Load test_fake image paths and labels
65
+ for filename in os.listdir(test_fake_folder):
66
+ image_path = os.path.join(test_fake_folder, filename)
67
+ label = 1 # Assuming test fake images are all fake (label 1)
68
+ test_image_paths.append(image_path)
69
+ test_labels.append(label)
70
+
71
+ # Create DataFrames
72
+ train_dataset = pd.DataFrame({'image_path': train_image_paths, 'label': train_labels})
73
+ test_dataset = pd.DataFrame({'image_path': test_image_paths, 'label': test_labels})
74
+
75
+ # Function to preprocess images
76
+ def preprocess_image(image_path):
77
+ """Loads, resizes, and normalizes an image."""
78
+ image = cv2.imread(image_path)
79
+ resized_image = cv2.resize(image, (128, 128)) # Target size defined here
80
+ normalized_image = resized_image.astype(np.float32) / 255.0
81
+ return normalized_image
82
+
83
+ # Preprocess all images and convert labels to numpy arrays
84
+ X = np.array([preprocess_image(path) for path in train_image_paths])
85
+ Y = np.array(train_labels)
86
+
87
+ # Define discriminator network
88
+ def build_discriminator(input_shape, dropout_rate=0.5):
89
+ model = Sequential()
90
+ model.add(Conv2D(32, (3, 3), activation='relu', input_shape=input_shape))
91
+ model.add(MaxPooling2D((2, 2)))
92
+ model.add(Conv2D(64, (3, 3), activation='relu'))
93
+ model.add(MaxPooling2D((2, 2)))
94
+ model.add(Conv2D(64, (3, 3), activation='relu'))
95
+ model.add(Flatten())
96
+ model.add(Dense(64, activation='relu'))
97
+ model.add(Dropout(dropout_rate)) # Adding dropout layer
98
+ model.add(Dense(1, activation='sigmoid'))
99
+ return model
100
+
101
+ # Function to check if previous weights exist
102
+ def load_previous_weights(model, fold_number):
103
+ weights_file = f'model_weights/model_fold_{fold_number}.weights.h5'
104
+ if os.path.exists(weights_file):
105
+ model.load_weights(weights_file)
106
+ print(f"Loaded weights from {weights_file}")
107
+ else:
108
+ print("No previous weights found.")
109
+
110
+ # Function to save weights if current accuracy is higher
111
+ def save_updated_weights(model, fold_number, val_accuracy, best_accuracy):
112
+ weights_file = f'model_weights/model_fold_{fold_number}.weights.h5'
113
+ if val_accuracy > best_accuracy:
114
+ model.save_weights(weights_file)
115
+ print(f"Saved updated weights to {weights_file} with val_accuracy: {val_accuracy:.4f}")
116
+ return val_accuracy
117
+ else:
118
+ print(f"Did not save weights for fold {fold_number} as val_accuracy {val_accuracy:.4f} is not better than {best_accuracy:.4f}")
119
+ return best_accuracy
120
+
121
+ # Set parameters for cross-validation
122
+ kf = KFold(n_splits=4, shuffle=True, random_state=42)
123
+ batch_size = 32
124
+ epochs = 15
125
+
126
+ # Lists to store accuracy and loss for each fold
127
+ accuracy_per_fold = []
128
+ loss_per_fold = []
129
+ # Store the best accuracies for each fold
130
+ best_accuracies = [0] * kf.get_n_splits()
131
+
132
+
133
+ # Perform K-Fold Cross-Validation
134
+ for fold_number, (train_index, val_index) in enumerate(kf.split(X), 1):
135
+ X_train, X_val = X[train_index], X[val_index]
136
+ Y_train, Y_val = Y[train_index], Y[val_index]
137
+
138
+ # Create and compile model
139
+ input_dim = X_train.shape[1:] # Dimensionality of the input images
140
+ model = build_discriminator(input_dim)
141
+ model.compile(loss='binary_crossentropy', optimizer=Adam(0.0002, 0.5), metrics=['accuracy'])
142
+
143
+ # Load previous weights if they exist
144
+ load_previous_weights(model, fold_number)
145
+
146
+ # Define Early Stopping callback
147
+ early_stopping = EarlyStopping(monitor='val_accuracy', patience=5, restore_best_weights=True)
148
+
149
+ # Define ModelCheckpoint callback to save the best weights
150
+ checkpoint = ModelCheckpoint(filepath=f'best_model_weights/model_fold_{fold_number}.best.weights.h5.keras', monitor='val_accuracy', save_best_only=True, mode='max')
151
+
152
+ # Train the model with the callbacks
153
+ history = model.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=2,
154
+ validation_data=(X_val, Y_val), callbacks=[early_stopping, checkpoint])
155
+
156
+ # Store the accuracy and loss for this folds
157
+ average_val_accuracy = np.mean(history.history['val_accuracy'])
158
+ accuracy_per_fold.append(average_val_accuracy)
159
+ average_val_loss = np.mean(history.history['val_loss'])
160
+ loss_per_fold.append(average_val_loss)
161
+
162
+ # Save updated weights if accuracy is high
163
+ best_accuracies[fold_number - 1] = save_updated_weights(model, fold_number, average_val_accuracy, best_accuracies[fold_number - 1])
164
+
165
+
166
+ # Print fold accuracy
167
+ print(f'Fold {fold_number} average accuracy: {average_val_accuracy*100:.2f}%')
168
+
169
+ # Print average accuracy across all folds
170
+ print(f'Average accuracy across all folds: {np.mean(accuracy_per_fold)*100:.2f}%')
171
+
172
+ # Load the model weights of the best model
173
+ best_model_index = np.argmax(accuracy_per_fold)
174
+ best_model_path = f'best_model_weights/model_fold_{best_model_index + 1}.best.weights.h5.keras'
175
+ model.load_weights(best_model_path)
176
+
177
+ # Evaluate the preprocessed test images using the best model
178
+ test_loss, test_accuracy = model.evaluate(np.array([preprocess_image(path) for path in test_image_paths]), np.array(test_labels))
179
+ print(f"\nTest Loss: {test_loss}, Test Accuracy: {test_accuracy}")
180
+
181
+ # Predict labels for the test set using the best model
182
+ predictions = model.predict(np.array([preprocess_image(path) for path in test_image_paths]))
183
+ predicted_labels = (predictions > 0.5).astype(int).flatten()
184
+
185
+ # Summarize the classification results
186
+ true_real_correct = np.sum((np.array(test_labels) == 0) & (predicted_labels == 0))
187
+ true_real_incorrect = np.sum((np.array(test_labels) == 0) & (predicted_labels == 1))
188
+ true_fake_correct = np.sum((np.array(test_labels) == 1) & (predicted_labels == 1))
189
+ true_fake_incorrect = np.sum((np.array(test_labels) == 1) & (predicted_labels == 0))
190
+
191
+ print("\nClassification Summary:")
192
+ print(f"Real images correctly classified: {true_real_correct}")
193
+ print(f"Real images incorrectly classified: {true_real_incorrect}")
194
+ print(f"Fake images correctly classified: {true_fake_correct}")
195
+ print(f"Fake images incorrectly classified: {true_fake_incorrect}")
196
+
197
+
198
+ # Print detailed classification report
199
+ print("\nClassification Report:")
200
+ print(classification_report(test_labels, predicted_labels, target_names=['Real', 'Fake']))
201
+
202
+ print(model.summary())
203
+
204
+
205
+ # Plot confusion matrix
206
+ cm = confusion_matrix(test_labels, predicted_labels)
207
+ disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Real', 'Fake'])
208
+ disp.plot(cmap=plt.cm.Blues)
209
+ plt.title("Confusion Matrix")
210
+ plt.show()
211
+
212
+ # Plot training & validation accuracy values
213
+ plt.figure(figsize=(12, 4))
214
+ plt.subplot(1, 2, 1)
215
+ plt.plot(history.history['accuracy'])
216
+ plt.plot(history.history['val_accuracy'])
217
+ plt.title('Model accuracy')
218
+ plt.ylabel('Accuracy')
219
+ plt.xlabel('Epoch')
220
+ plt.legend(['Train', 'Validation'], loc='upper left')
221
+ plt.xticks(np.arange(0, len(history.history['accuracy']), step=1), np.arange(1, len(history.history['accuracy']) + 1, step=1))
222
+
223
+
224
+ # Plot training & validation loss values
225
+ plt.subplot(1, 2, 2)
226
+ plt.plot(history.history['loss'])
227
+ plt.plot(history.history['val_loss'])
228
+ plt.title('Model loss')
229
+ plt.ylabel('Loss')
230
+ plt.xlabel('Epoch')
231
+ plt.legend(['Train', 'Validation'], loc='upper left')
232
+ plt.xticks(np.arange(0, len(history.history['loss']), step=1), np.arange(1, len(history.history['loss']) + 1, step=1))
233
+
234
+
235
+ plt.tight_layout()
236
+ plt.show()
237
+
238
+ # Plot validation accuracy and loss per fold
239
+ plt.figure(figsize=(12, 4))
240
+ plt.subplot(1, 2, 1)
241
+ plt.plot(range(1, kf.get_n_splits() + 1), accuracy_per_fold, marker='o')
242
+ plt.title('Validation Accuracy per Fold')
243
+ plt.xlabel('Fold')
244
+ plt.ylabel('Accuracy')
245
+
246
+ plt.subplot(1, 2, 2)
247
+ plt.plot(range(1, kf.get_n_splits() + 1), loss_per_fold, marker='o')
248
+ plt.title('Validation Loss per Fold')
249
+ plt.xlabel('Fold')
250
+ plt.ylabel('Loss')
251
+
252
+ plt.tight_layout()
253
+ plt
254
+
255
+