{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":13385318,"sourceType":"datasetVersion","datasetId":8493189}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ==============================================================================\n# 1. SETUP AND IMPORTS\n# ==============================================================================\nimport numpy as np\nimport pandas as pd\nimport os\nimport cv2\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport albumentations as A\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, accuracy_score, confusion_matrix\nimport random\nfrom tqdm import tqdm\n\nprint(\"TensorFlow Version:\", tf.__version__)\n\n# ==============================================================================\n# 2. CONFIGURATION\n# ==============================================================================\n# Set seeds for reproducibility\nrandom.seed(42)\ntf.random.set_seed(42)\nnp.random.seed(42)\n\n# --- Path to the dataset ---\nDATA_DIR = \"../input/camera-model-detection-dataset-from-image/dataset/compressed\"\n\n# Define constants\nPATCH_SIZE = 128\nIMAGE_SHAPE = (PATCH_SIZE, PATCH_SIZE, 3)\nNUM_PATCHES_PER_IMAGE_TRAIN = 5\nNUM_PATCHES_PER_IMAGE_TEST = 10\nBATCH_SIZE = 64\nNUM_CLASSES = 10\nEPOCHS = 15\n\n# ==============================================================================\n# 3. DATA LOADING AND PREPARATION\n# ==============================================================================\nprint(\"Step 3: Loading data paths...\")\n\nimage_paths = []\nlabels = []\nsocial_media_platforms = ['facebook', 'instagram', 'telegram', 'whatsapp']\n\nfor platform in social_media_platforms:\n    platform_path = os.path.join(DATA_DIR, platform)\n    if not os.path.isdir(platform_path): continue\n    for camera_model in os.listdir(platform_path):\n        camera_path = os.path.join(platform_path, camera_model)\n        if not os.path.isdir(camera_path): continue\n        for file_name in os.listdir(camera_path):\n            if file_name.lower().endswith(('.png', '.jpg', '.jpeg')):\n                image_paths.append(os.path.join(camera_path, file_name))\n                labels.append(camera_model)\n\ndata_df = pd.DataFrame({'path': image_paths, 'class': labels})\nprint(f\"Found {len(data_df)} total images from {len(data_df['class'].unique())} camera models.\")\n\n# ==============================================================================\n# 4. DATA SPLITTING & ENCODING\n# ==============================================================================\nX = data_df['path']\ny = data_df['class']\n\n# Split the data into Train (80%) and Test (20%) sets\nX_train_full, X_test, y_train_full, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)\n\n# Split the training data again to create a validation set\nX_train, X_val, y_train, y_val = train_test_split(X_train_full, y_train_full, test_size=0.15, random_state=42, stratify=y_train_full)\n\nprint(f\"\\nTraining image samples: {len(X_train)}\")\nprint(f\"Validation image samples: {len(X_val)}\")\nprint(f\"Test image samples: {len(X_test)}\")\n\n# One-hot encode labels\ny_train_encoded = pd.get_dummies(y_train)\ny_val_encoded = pd.get_dummies(y_val)\nclass_names = y_train_encoded.columns.tolist()\ny_test_encoded = pd.get_dummies(y_test, columns=class_names).reindex(columns=class_names, fill_value=0)\ny_test_indices = y_test.map({name: i for i, name in enumerate(class_names)})\n\n# ==============================================================================\n# 5. AUGMENTATION AND PATCH-BASED DATA GENERATOR\n# ==============================================================================\nprint(\"\\nStep 5: Defining data generators...\")\ntrain_augmentations = A.Compose([\n    A.HorizontalFlip(p=0.5), A.RandomGamma(p=0.5), A.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))\n])\nval_test_augmentations = A.Compose([A.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))])\n\ndef extract_random_patches(image, num_patches, patch_size):\n    h, w, _ = image.shape\n    patches = []\n    for _ in range(num_patches):\n        if h > patch_size and w > patch_size:\n            x, y = random.randint(0, w - patch_size), random.randint(0, h - patch_size)\n            patches.append(image[y:y+patch_size, x:x+patch_size])\n        else:\n            patches.append(cv2.resize(image, (patch_size, patch_size)))\n    return patches\n\ndef extract_deterministic_patches(image, num_patches, patch_size):\n    h, w, _ = image.shape\n    patches = []\n    locations = [(0, 0), (w - patch_size, 0), (0, h - patch_size), (w - patch_size, h - patch_size), (int((w - patch_size)/2), int((h - patch_size)/2))]\n    while len(locations) < num_patches:\n        locations.append((random.randint(0, w - patch_size), random.randint(0, h - patch_size)))\n    for i in range(min(num_patches, len(locations))):\n        x, y = locations[i]\n        if h > patch_size and w > patch_size:\n            patches.append(image[y:y+patch_size, x:x+patch_size])\n        else:\n            patches.append(cv2.resize(image, (patch_size, patch_size)))\n    return patches\n\nclass PatchDataset(tf.keras.utils.Sequence):\n    def __init__(self, x_set, y_set, batch_size, augmenter, num_patches, patch_size, is_train=True):\n        self.x, self.y, self.batch_size, self.augmenter, self.num_patches, self.patch_size, self.is_train = x_set, y_set, batch_size, augmenter, num_patches, patch_size, is_train\n    def __len__(self):\n        return int(np.ceil(len(self.x) / self.batch_size))\n    def __getitem__(self, idx):\n        batch_x_paths = self.x.iloc[idx * self.batch_size:(idx + 1) * self.batch_size]\n        batch_y_labels = self.y.iloc[idx * self.batch_size:(idx + 1) * self.batch_size]\n        batch_patches, batch_y = [], []\n        for path, label in zip(batch_x_paths, batch_y_labels.values):\n            image = cv2.imread(path)\n            if image is None: continue\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            patches = extract_random_patches(image, self.num_patches, self.patch_size) if self.is_train else extract_deterministic_patches(image, self.num_patches, self.patch_size)\n            for patch in patches:\n                batch_patches.append(self.augmenter(image=patch)['image'])\n                batch_y.append(label)\n        return np.array(batch_patches), np.array(batch_y)\n\ntrain_dataset = PatchDataset(X_train, y_train_encoded, BATCH_SIZE, train_augmentations, NUM_PATCHES_PER_IMAGE_TRAIN, PATCH_SIZE, is_train=True)\nval_dataset = PatchDataset(X_val, y_val_encoded, BATCH_SIZE, val_test_augmentations, NUM_PATCHES_PER_IMAGE_TRAIN, PATCH_SIZE, is_train=False)\ntest_dataset = PatchDataset(X_test, y_test_encoded, BATCH_SIZE, val_test_augmentations, NUM_PATCHES_PER_IMAGE_TEST, PATCH_SIZE, is_train=False)\n\n# ==============================================================================\n# 6. MODEL DEFINITION AND PLOTTING FUNCTIONS\n# ==============================================================================\nprint(\"\\nStep 6: Defining EfficientNetB0 model and helper functions...\")\n\ndef create_efficientnet_model(input_shape, num_classes, trainable_layers=20):\n    \"\"\"Creates a fine-tuned EfficientNetB0 model.\"\"\"\n    base_model = tf.keras.applications.EfficientNetB0(weights='imagenet', include_top=False, input_shape=input_shape)\n    base_model.trainable = True\n    for layer in base_model.layers[:-trainable_layers]:\n        layer.trainable = False\n        \n    model = tf.keras.Sequential([\n        base_model,\n        tf.keras.layers.GlobalAveragePooling2D(),\n        tf.keras.layers.Dense(256, activation='relu'),\n        tf.keras.layers.Dropout(0.5),\n        tf.keras.layers.Dense(num_classes, activation='softmax')\n    ])\n    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4), loss='categorical_crossentropy', metrics=['accuracy'])\n    return model\n\ndef plot_history(history, model_name):\n    plt.figure(figsize=(12, 5))\n    plt.subplot(1, 2, 1)\n    plt.plot(history.history['accuracy'], label='Training Acc')\n    plt.plot(history.history['val_accuracy'], label='Validation Acc')\n    plt.title(f'{model_name} - Accuracy')\n    plt.legend(); plt.subplot(1, 2, 2)\n    plt.plot(history.history['loss'], label='Training Loss')\n    plt.plot(history.history['val_loss'], label='Validation Loss')\n    plt.title(f'{model_name} - Loss')\n    plt.legend(); plt.show()\n\ndef plot_confusion_matrix(cm, class_names, model_name):\n    plt.figure(figsize=(10, 8))\n    sns.heatmap(cm, annot=True, fmt='g', cmap='Blues', xticklabels=class_names, yticklabels=class_names)\n    plt.title(f'{model_name} - Confusion Matrix')\n    plt.xlabel('Predicted Label'); plt.ylabel('True Label'); plt.show()\n\n# ==============================================================================\n# 7. TRAINING AND EVALUATION FOR EfficientNetB0\n# ==============================================================================\nmodel_name = \"EfficientNetB0\"\nprint(f\"\\n{'='*25} MODEL: {model_name} {'='*25}\")\n\n# --- Create Model ---\nmodel = create_efficientnet_model(IMAGE_SHAPE, NUM_CLASSES)\nmodel.summary()\n\n# --- TRAINING ---\nprint(f\"\\n--- Training {model_name} ---\")\ncheckpoint_path = f\"{model_name}_best.keras\"\ncallbacks = [\n    tf.keras.callbacks.ModelCheckpoint(checkpoint_path, save_best_only=True, monitor=\"val_accuracy\"),\n    tf.keras.callbacks.EarlyStopping(patience=5, monitor=\"val_accuracy\", restore_best_weights=True),\n]\nhistory = model.fit(train_dataset, validation_data=val_dataset, epochs=EPOCHS, callbacks=callbacks, verbose=1)\n\n# --- VISUALIZATION OF TRAINING ---\nplot_history(history, model_name)\n\n# --- TESTING ON THE 20% TEST SPLIT (with Patch Voting) ---\nprint(f\"\\n--- Evaluating {model_name} on Test Set (with Patch Voting) ---\")\nbest_model = tf.keras.models.load_model(checkpoint_path)\ntest_predictions = []\n\nfor image_path in tqdm(X_test, desc=f\"Evaluating {model_name}\"):\n    image = cv2.imread(image_path)\n    if image is None: continue\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    patches = extract_deterministic_patches(image, NUM_PATCHES_PER_IMAGE_TEST, PATCH_SIZE)\n    preprocessed_patches = np.array([val_test_augmentations(image=p)['image'] for p in patches])\n    patch_predictions = best_model.predict(preprocessed_patches, verbose=0)\n    avg_prediction = np.mean(patch_predictions, axis=0)\n    test_predictions.append(np.argmax(avg_prediction))\n\n# --- Report Metrics on the Test Set ---\ntrue_labels = y_test_indices.values[:len(test_predictions)]\naccuracy = accuracy_score(true_labels, test_predictions)\ncm = confusion_matrix(true_labels, test_predictions)\n\nprint(f\"\\nTest Set Accuracy for {model_name}: {accuracy:.4f}\")\nprint(\"\\nClassification Report (Test Set):\")\nprint(classification_report(true_labels, test_predictions, target_names=class_names))\nplot_confusion_matrix(cm, class_names, model_name)\n\nprint(f\"\\n{'='*25} WORKFLOW FOR {model_name} COMPLETE {'='*25}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-15T04:41:46.438012Z","iopub.execute_input":"2025-10-15T04:41:46.43868Z"}},"outputs":[{"name":"stderr","text":"2025-10-15 04:41:48.819185: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\nWARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nE0000 00:00:1760503309.028801      37 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\nE0000 00:00:1760503309.090434      37 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n","output_type":"stream"},{"name":"stdout","text":"TensorFlow Version: 2.18.0\nStep 3: Loading data paths...\nFound 11000 total images from 10 camera models.\n\nTraining image samples: 7480\nValidation image samples: 1320\nTest image samples: 2200\n\nStep 5: Defining data generators...\n\nStep 6: Defining ResNet50 model and helper functions...\n\n========================= MODEL: ResNet50 =========================\n","output_type":"stream"},{"name":"stderr","text":"I0000 00:00:1760503328.119661      37 gpu_device.cc:2022] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 15513 MB memory:  -> device: 0, name: Tesla P100-PCIE-16GB, pci bus id: 0000:00:04.0, compute capability: 6.0\n","output_type":"stream"},{"name":"stdout","text":"Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/resnet/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5\n\u001b[1m94765736/94765736\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m5s\u001b[0m 0us/step\n","output_type":"stream"},{"output_type":"display_data","data":{"text/plain":"\u001b[1mModel: \"sequential\"\u001b[0m\n","text/html":"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\">Model: \"sequential\"</span>\n</pre>\n"},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n┃\u001b[1m \u001b[0m\u001b[1mLayer (type)                   \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape          \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m      Param #\u001b[0m\u001b[1m \u001b[0m┃\n┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n│ resnet50 (\u001b[38;5;33mFunctional\u001b[0m)           │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m4\u001b[0m, \u001b[38;5;34m4\u001b[0m, \u001b[38;5;34m2048\u001b[0m)     │    \u001b[38;5;34m23,587,712\u001b[0m │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ global_average_pooling2d        │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m2048\u001b[0m)           │             \u001b[38;5;34m0\u001b[0m │\n│ (\u001b[38;5;33mGlobalAveragePooling2D\u001b[0m)        │                        │               │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ dense (\u001b[38;5;33mDense\u001b[0m)                   │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m256\u001b[0m)            │       \u001b[38;5;34m524,544\u001b[0m │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ dropout (\u001b[38;5;33mDropout\u001b[0m)               │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m256\u001b[0m)            │             \u001b[38;5;34m0\u001b[0m │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ dense_1 (\u001b[38;5;33mDense\u001b[0m)                 │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m10\u001b[0m)             │         \u001b[38;5;34m2,570\u001b[0m │\n└─────────────────────────────────┴────────────────────────┴───────────────┘\n","text/html":"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n┃<span style=\"font-weight: bold\"> Layer (type)                    </span>┃<span style=\"font-weight: bold\"> Output Shape           </span>┃<span style=\"font-weight: bold\">       Param # </span>┃\n┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n│ resnet50 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Functional</span>)           │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">4</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">4</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">2048</span>)     │    <span style=\"color: #00af00; text-decoration-color: #00af00\">23,587,712</span> │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ global_average_pooling2d        │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">2048</span>)           │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n│ (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">GlobalAveragePooling2D</span>)        │                        │               │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ dense (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                   │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">256</span>)            │       <span style=\"color: #00af00; text-decoration-color: #00af00\">524,544</span> │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ dropout (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dropout</span>)               │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">256</span>)            │             <span style=\"color: #00af00; text-decoration-color: #00af00\">0</span> │\n├─────────────────────────────────┼────────────────────────┼───────────────┤\n│ dense_1 (<span style=\"color: #0087ff; text-decoration-color: #0087ff\">Dense</span>)                 │ (<span style=\"color: #00d7ff; text-decoration-color: #00d7ff\">None</span>, <span style=\"color: #00af00; text-decoration-color: #00af00\">10</span>)             │         <span style=\"color: #00af00; text-decoration-color: #00af00\">2,570</span> │\n└─────────────────────────────────┴────────────────────────┴───────────────┘\n</pre>\n"},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"\u001b[1m Total params: \u001b[0m\u001b[38;5;34m24,114,826\u001b[0m (91.99 MB)\n","text/html":"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Total params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">24,114,826</span> (91.99 MB)\n</pre>\n"},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m9,458,442\u001b[0m (36.08 MB)\n","text/html":"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">9,458,442</span> (36.08 MB)\n</pre>\n"},"metadata":{}},{"output_type":"display_data","data":{"text/plain":"\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m14,656,384\u001b[0m (55.91 MB)\n","text/html":"<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"font-weight: bold\"> Non-trainable params: </span><span style=\"color: #00af00; text-decoration-color: #00af00\">14,656,384</span> (55.91 MB)\n</pre>\n"},"metadata":{}},{"name":"stdout","text":"\n--- Training ResNet50 ---\n","output_type":"stream"},{"name":"stderr","text":"/usr/local/lib/python3.11/dist-packages/keras/src/trainers/data_adapters/py_dataset_adapter.py:121: UserWarning: Your `PyDataset` class should call `super().__init__(**kwargs)` in its constructor. `**kwargs` can include `workers`, `use_multiprocessing`, `max_queue_size`. Do not pass these arguments to `fit()`, as they will be ignored.\n  self._warn_if_super_not_called()\n","output_type":"stream"},{"name":"stdout","text":"Epoch 1/15\n","output_type":"stream"},{"name":"stderr","text":"WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\nI0000 00:00:1760503358.300568      95 service.cc:148] XLA service 0x7d7b3c0029e0 initialized for platform CUDA (this does not guarantee that XLA will be used). Devices:\nI0000 00:00:1760503358.301280      95 service.cc:156]   StreamExecutor device (0): Tesla P100-PCIE-16GB, Compute Capability 6.0\nI0000 00:00:1760503360.728266      95 cuda_dnn.cc:529] Loaded cuDNN version 90300\n","output_type":"stream"},{"name":"stdout","text":"\u001b[1m  1/117\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m59:31\u001b[0m 31s/step - accuracy: 0.1031 - loss: 2.5784","output_type":"stream"},{"name":"stderr","text":"I0000 00:00:1760503371.817735      95 device_compiler.h:188] Compiled cluster using XLA!  This line is logged at most once for the lifetime of the process.\n","output_type":"stream"},{"name":"stdout","text":"\u001b[1m117/117\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m304s\u001b[0m 2s/step - accuracy: 0.1397 - loss: 2.3308 - val_accuracy: 0.1282 - val_loss: 2.3440\nEpoch 2/15\n\u001b[1m117/117\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m209s\u001b[0m 2s/step - accuracy: 0.1791 - loss: 2.2081 - val_accuracy: 0.1476 - val_loss: 2.3062\nEpoch 3/15\n\u001b[1m117/117\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m213s\u001b[0m 2s/step - accuracy: 0.1955 - loss: 2.1753 - val_accuracy: 0.1841 - val_loss: 2.2132\nEpoch 4/15\n\u001b[1m  1/117\u001b[0m \u001b[37m━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m3:31\u001b[0m 2s/step - accuracy: 0.1969 - loss: 2.1706","output_type":"stream"}],"execution_count":null}]}