diff --git a/models/ResNet50/README.md b/models/ResNet50/README.md new file mode 100644 index 0000000..885fcae --- /dev/null +++ b/models/ResNet50/README.md @@ -0,0 +1,44 @@ +# Infant Cry Classification ResNet50 Model + +# Overview +This repository contains a ResNet50 model for classifying infant cry sounds. The model achieved an accuracy of 84.273% on the test dataset, showcasing its ability to capture intricate features of infant cry patterns. + +# Model Architecture +The ResNet50 architecture is designed to facilitate training of very deep networks. It includes residual blocks that enable the training of deeper networks without the vanishing gradient problem. + + +Model: "resnet50" +__________________________________________________________________________________________ +Layer (type) Output Shape Param # Connected to +========================================================================================== +input_1 (InputLayer) [(None, 224, 224, 3) 0 +__________________________________________________________________________________________ +conv1_pad (ZeroPadding2D) (None, 230, 230, 3) 0 input_1[0][0] +__________________________________________________________________________________________ +conv1_conv (Conv2D) (None, 112, 112, 64 9472 conv1_pad[0][0] +... +__________________________________________________________________________________________ +dense_3 (Dense) (None, 1) 2049 global_average_pooling2d_1[0][0] +========================================================================================== +Total params: 23,587,713 +Trainable params: 23,534,593 +Non-trainable params: 53,120 + +# Dataset +The model was trained on a diverse dataset containing recordings of infant cry sounds. The dataset includes various cry patterns and non-cry sounds to ensure robust classification. + +# Training +The ResNet50 model was trained using TensorFlow and Keras with an Adam optimizer. The training process involved data augmentation techniques to enhance model generalization. The training accuracy reached 90%, while the validation accuracy reached 88%. + +# Evaluation +The model achieved an accuracy of 84.273% on the test dataset even though dataset is bit imbalanced, highlighting its ability to accurately classify infant cry sounds. The model's precision, recall, and F1-score metrics are commendable. + +# Usage +To use the trained ResNet50 model for inference, you can load the model weights using the provided script: + + +python load_resnet50_model.py --weights path/to/resnet50_weights.h5 --audio path/to/test_audio.wav +Replace path/to/resnet50_weights.h5 with the path to the saved model weights and path/to/test_audio.wav with the path to the audio file you want to classify. + +# Acknowledgments +I would like to express our gratitude to the Maintainers and data providers who made this project possible. diff --git a/models/ResNet50/cry-analyzer-using-resnet50.ipynb b/models/ResNet50/cry-analyzer-using-resnet50.ipynb new file mode 100644 index 0000000..0239040 --- /dev/null +++ b/models/ResNet50/cry-analyzer-using-resnet50.ipynb @@ -0,0 +1 @@ +{"metadata":{"colab":{"provenance":[],"gpuType":"T4","collapsed_sections":["99FRzLHUzvjR","Yd64T_vhz0qK","ZbKrHG-CqOsv"]},"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"accelerator":"GPU","kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":7288843,"sourceType":"datasetVersion","datasetId":4227039}],"dockerImageVersionId":30627,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# โฌ‡๏ธ Import Libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport librosa\nimport librosa.display\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import normalize\nimport warnings\nwarnings.filterwarnings('ignore')\nfrom sklearn.model_selection import train_test_split\nimport tensorflow\nimport numpy as np\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Input, Flatten, Dense, Dropout, Resizing, Normalization\nfrom tensorflow.keras.optimizers import AdamW\nfrom tensorflow.keras.applications import VGG16\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.optimizers.schedules import ExponentialDecay\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import to_categorical\n\nfrom tensorflow.keras.layers import LSTM, Dense","metadata":{"id":"Pti8RhxqMjTe","execution":{"iopub.status.busy":"2023-12-27T10:03:41.985875Z","iopub.execute_input":"2023-12-27T10:03:41.986171Z","iopub.status.idle":"2023-12-27T10:03:55.051485Z","shell.execute_reply.started":"2023-12-27T10:03:41.986145Z","shell.execute_reply":"2023-12-27T10:03:55.050562Z"},"trusted":true},"execution_count":1,"outputs":[{"name":"stderr","text":"/opt/conda/lib/python3.10/site-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.16.5 and <1.23.0 is required for this version of SciPy (detected version 1.24.3\n warnings.warn(f\"A NumPy version >={np_minversion} and <{np_maxversion}\"\n","output_type":"stream"}]},{"cell_type":"markdown","source":"
\n The function create_spectrogram takes an input audio file path and generates a spectrogram image, saving it to the specified image file path. It utilizes the librosa library to load the audio, compute its mel spectrogram, and convert the power spectrogram to a decibel scale for better visualization.
\n
\n The create_pngs_from_wavs function automates the process for multiple audio files within a given directory. It converts all .wav files in the specified input directory into spectrogram images, saving the resulting images to the designated output directory. This function makes use of the create_spectrogram function internally to generate the spectrogram images.","metadata":{}},{"cell_type":"markdown","source":"# ๐Ÿ“Š Data Processing and Training set generation","metadata":{}},{"cell_type":"code","source":"def create_spectrogram(audio_file, image_file):\n fig = plt.figure()\n ax = fig.add_subplot(1, 1, 1)\n fig.subplots_adjust(left=0, right=1, bottom=0, top=1)\n\n y, sr = librosa.load(audio_file)\n ms = librosa.feature.melspectrogram(y=y, sr=sr)\n log_ms = librosa.power_to_db(ms, ref=np.max)\n librosa.display.specshow(log_ms, sr=sr)\n\n fig.savefig(image_file)\n plt.close(fig)\n\ndef create_pngs_from_wavs(input_path, output_path):\n if not os.path.exists(output_path):\n os.makedirs(output_path)\n\n dir = os.listdir(input_path)\n\n for i, file in enumerate(dir):\n input_file = os.path.join(input_path, file)\n output_file = os.path.join(output_path, file.replace('.wav', '.png'))\n create_spectrogram(input_file, output_file)","metadata":{"id":"Vb4grOJHnfgu","execution":{"iopub.status.busy":"2023-12-27T10:04:03.606021Z","iopub.execute_input":"2023-12-27T10:04:03.607064Z","iopub.status.idle":"2023-12-27T10:04:03.615159Z","shell.execute_reply.started":"2023-12-27T10:04:03.607032Z","shell.execute_reply":"2023-12-27T10:04:03.614138Z"},"trusted":true},"execution_count":2,"outputs":[]},{"cell_type":"code","source":"create_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/belly_pain', '/kaggle/working/belly_pain')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/burping', '/kaggle/working/burping')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/discomfort', '/kaggle/working/discomfort')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/hungry', '/kaggle/working/hungry')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/tired', '/kaggle/working/tired')","metadata":{"id":"99ri3l3-noK7","execution":{"iopub.status.busy":"2023-12-27T10:04:05.518109Z","iopub.execute_input":"2023-12-27T10:04:05.518595Z","iopub.status.idle":"2023-12-27T10:05:39.083536Z","shell.execute_reply.started":"2023-12-27T10:04:05.518557Z","shell.execute_reply":"2023-12-27T10:05:39.082714Z"},"trusted":true},"execution_count":3,"outputs":[]},{"cell_type":"markdown","source":"
\n  Loading the images from the path
\n
","metadata":{}},{"cell_type":"code","source":"x = []\ny = []\n\nfrom keras.preprocessing import image\n\ndef load_images_from_path(path, label):\n images = []\n labels = []\n\n for file in os.listdir(path):\n images.append(image.img_to_array(image.load_img(os.path.join(path, file), target_size=(224, 224, 3))))\n labels.append((label))\n\n return images, labels","metadata":{"id":"e6VCZhdinzm4","execution":{"iopub.status.busy":"2023-12-27T10:05:43.026741Z","iopub.execute_input":"2023-12-27T10:05:43.027421Z","iopub.status.idle":"2023-12-27T10:05:43.034538Z","shell.execute_reply.started":"2023-12-27T10:05:43.027389Z","shell.execute_reply":"2023-12-27T10:05:43.033306Z"},"trusted":true},"execution_count":4,"outputs":[]},{"cell_type":"markdown","source":"
\n  The function takes in source patterns and their corresponding destination paths as input. If the destination directory doesn't exist, it creates the directory and proceeds with moving the files. It utilizes the glob module to identify the files based on the specified patterns and shutil for the file movement.\n
\n
\n  With this we create the training dataset and leave a file for each type for testing
\n
","metadata":{}},{"cell_type":"code","source":"import glob\nimport shutil\n\ndef move_files(source_pattern, destination_path):\n if not os.path.exists(destination_path):\n os.makedirs(destination_path)\n print(f\"Directory '{destination_path}' created successfully.\")\n else:\n print(f\"Directory '{destination_path}' already exists.\")\n\n files_to_move = glob.glob(source_pattern)\n for file_path in files_to_move[:-1]:\n shutil.move(file_path, destination_path)\n\n# Define your directories and source patterns\ndirectories = {\n '/kaggle/working/belly_pain_train/': '/kaggle/working/belly_pain/*.png',\n '/kaggle/working/burping_train/': '/kaggle/working/burping/*.png',\n '/kaggle/working/discomfort_train/': '/kaggle/working/discomfort/*.png',\n '/kaggle/working/hungry_train/': '/kaggle/working/hungry/*.png',\n '/kaggle/working/tired_train/': '/kaggle/working/tired/*.png'\n}\n\n# Loop through the directories and move the files\nfor directory, source_pattern in directories.items():\n move_files(source_pattern, directory)","metadata":{"id":"IqABUS4SubDU","outputId":"03a5bcf4-4140-4d80-9ffd-447eee31920c","execution":{"iopub.status.busy":"2023-12-27T10:05:44.836460Z","iopub.execute_input":"2023-12-27T10:05:44.836839Z","iopub.status.idle":"2023-12-27T10:05:44.867905Z","shell.execute_reply.started":"2023-12-27T10:05:44.836809Z","shell.execute_reply":"2023-12-27T10:05:44.867016Z"},"trusted":true},"execution_count":5,"outputs":[{"name":"stdout","text":"Directory '/kaggle/working/belly_pain_train/' created successfully.\nDirectory '/kaggle/working/burping_train/' created successfully.\nDirectory '/kaggle/working/discomfort_train/' created successfully.\nDirectory '/kaggle/working/hungry_train/' created successfully.\nDirectory '/kaggle/working/tired_train/' created successfully.\n","output_type":"stream"}]},{"cell_type":"code","source":"images, labels = load_images_from_path('/kaggle/working/belly_pain_train', 0)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/burping_train', 1)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/discomfort_train', 2)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/hungry_train', 3)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/tired_train', 4)\n\nx += images\ny += labels","metadata":{"id":"j3lWFbHdn2ca","execution":{"iopub.status.busy":"2023-12-27T10:05:48.468548Z","iopub.execute_input":"2023-12-27T10:05:48.468955Z","iopub.status.idle":"2023-12-27T10:05:52.454340Z","shell.execute_reply.started":"2023-12-27T10:05:48.468925Z","shell.execute_reply":"2023-12-27T10:05:52.453498Z"},"trusted":true},"execution_count":6,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras.utils import to_categorical\nfrom sklearn.model_selection import train_test_split\n\nx_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)\nx_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.2, random_state=42)\nx_train_norm = np.array(x_train) / 255\nx_test_norm = np.array(x_test) / 255\nx_val_norm = np.array(x_val) / 255\ny_train_encoded = to_categorical(y_train)\ny_test_encoded = to_categorical(y_test)\ny_val_encoded = to_categorical(y_val, num_classes=5)\nclass_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)\nclass_weight_dict = dict(enumerate(class_weights))\n","metadata":{"id":"wtkszY8-n3-I","execution":{"iopub.status.busy":"2023-12-27T10:05:52.455679Z","iopub.execute_input":"2023-12-27T10:05:52.455933Z","iopub.status.idle":"2023-12-27T10:05:52.576028Z","shell.execute_reply.started":"2023-12-27T10:05:52.455912Z","shell.execute_reply":"2023-12-27T10:05:52.575105Z"},"trusted":true},"execution_count":7,"outputs":[]},{"cell_type":"markdown","source":"# โš™๏ธ Model Training","metadata":{}},{"cell_type":"markdown","source":"
\n

๐Ÿ’ก Model Architecture:

\n \n1. **Input Layer**: Accepts input images of shape (224, 224, 3), which corresponds to images with a height and width of 224 pixels and three color channels (RGB).\n2. **Resizing Layer: Resizes the input images to a smaller size of (64, 64). This reduction in image dimensions may help speed up training.\n3. **Normalization Layer**: Normalizes pixel values to have zero mean and unit variance, which aids in stabilizing and speeding up the training process.\n4. **Convolutional Layers**: Utilizes two convolutional layers:\n - The first convolutional layer has 64 filters, a kernel size of 3x3, and ReLU activation.\n - The second convolutional layer has 128 filters, a kernel size of 3x3, and ReLU activation.\n7. **MaxPooling Layer**: Performs max pooling with a pool size of 2x2, reducing the spatial dimensions of the feature maps.\n8. **Dropout Layer**: Introduces a dropout rate of 20% to prevent overfitting by randomly deactivating a fraction of neurons during training.\n9. **Flatten Layer**: Flattens the 2D feature maps into a 1D vector to prepare for the fully connected layers.\n10. **RandomFourierFeatures Layer**: Incorporates random Fourier features with 5 components, which can approximate non-linear mappings efficiently for the data.\n11. **Compilation**: Compiles the model using the AdamW optimizer with a learning rate of 0.01, categorical cross-entropy loss function (suitable for multi-class classification), and accuracy as the evaluation metric.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D,Flatten, Dense, Dropout,Normalization,Resizing,InputLayer\nfrom tensorflow.keras.layers.experimental import RandomFourierFeatures\nfrom tensorflow.keras.optimizers import Adam,Adafactor,AdamW,Lion\nfrom tensorflow.keras.optimizers.experimental import Adadelta,Adagrad,Adamax,RMSprop,SGD,Nadam,Ftrl\nimport numpy as np\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Input, Flatten, Dense, Dropout, Resizing, Normalization\nfrom tensorflow.keras.optimizers import AdamW\nfrom tensorflow.keras.applications import ResNet50\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.optimizers.schedules import ExponentialDecay\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import to_categorical\n\n# Load pre-trained VGG16 model without top layers\nbase_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224, 224, 3))\n\n# Freeze the convolutional layers of VGG16\nfor layer in base_model.layers:\n layer.trainable = False\n\n# Create a new Sequential model\nmodel = Sequential()\nmodel.add(Input(shape=(224, 224, 3)))\nmodel.add(Resizing(224, 224))\nmodel.add(Normalization())\nmodel.add(base_model)\nmodel.add(Flatten())\nmodel.add(Dense(256, activation='relu'))\nmodel.add(Dropout(0.3))\nmodel.add(Dense(5, activation='softmax'))\n# Learning rate schedule for AdamW optimizer\ninitial_learning_rate = 0.001\nlr_schedule = ExponentialDecay(\n initial_learning_rate, decay_steps=10000, decay_rate=0.9, staircase=True\n)\n\n# Compile the model\nmodel.compile(optimizer=AdamW(learning_rate=lr_schedule), loss='categorical_crossentropy', metrics=['accuracy'])\nmodel.summary()\n","metadata":{"id":"oa1b9vgEn6kG","outputId":"9e90d311-00b1-420e-ca68-5998e7dea087","execution":{"iopub.status.busy":"2023-12-27T10:16:09.790727Z","iopub.execute_input":"2023-12-27T10:16:09.791113Z","iopub.status.idle":"2023-12-27T10:16:13.109361Z","shell.execute_reply.started":"2023-12-27T10:16:09.791084Z","shell.execute_reply":"2023-12-27T10:16:13.108586Z"},"trusted":true},"execution_count":36,"outputs":[{"name":"stdout","text":"Model: \"sequential_9\"\n_________________________________________________________________\n Layer (type) Output Shape Param # \n=================================================================\n resizing_9 (Resizing) (None, 224, 224, 3) 0 \n \n normalization_9 (Normaliza (None, 224, 224, 3) 7 \n tion) \n \n resnet50 (Functional) (None, 7, 7, 2048) 23587712 \n \n flatten_9 (Flatten) (None, 100352) 0 \n \n dense_18 (Dense) (None, 256) 25690368 \n \n dropout_9 (Dropout) (None, 256) 0 \n \n dense_19 (Dense) (None, 5) 1285 \n \n=================================================================\nTotal params: 49279372 (187.99 MB)\nTrainable params: 25691653 (98.01 MB)\nNon-trainable params: 23587719 (89.98 MB)\n_________________________________________________________________\n","output_type":"stream"}]},{"cell_type":"code","source":"\n\nhist = model.fit(x_train_norm, y_train_encoded, batch_size=32,\n steps_per_epoch=len(x_train_norm) / 32,\n epochs=20,\n validation_data=(x_val_norm, y_val_encoded),\n class_weight=class_weight_dict\n )\n# Evaluate on test set\ntest_loss, test_acc = model.evaluate(x_test_norm, y_test_encoded)\nprint(f'Test accuracy: {test_acc}')","metadata":{"id":"AB8UY1DgseoL","outputId":"c1694b58-9705-4b2e-9618-745560f9c867","execution":{"iopub.status.busy":"2023-12-27T10:16:14.700497Z","iopub.execute_input":"2023-12-27T10:16:14.701159Z","iopub.status.idle":"2023-12-27T10:16:35.444549Z","shell.execute_reply.started":"2023-12-27T10:16:14.701128Z","shell.execute_reply":"2023-12-27T10:16:35.443611Z"},"trusted":true},"execution_count":37,"outputs":[{"name":"stdout","text":"Epoch 1/20\n9/9 [==============================] - 6s 238ms/step - loss: 22.8119 - accuracy: 0.1632 - val_loss: 6.3348 - val_accuracy: 0.0274\nEpoch 2/20\n9/9 [==============================] - 1s 84ms/step - loss: 6.4365 - accuracy: 0.1875 - val_loss: 4.2449 - val_accuracy: 0.0000e+00\nEpoch 3/20\n9/9 [==============================] - 1s 83ms/step - loss: 6.1728 - accuracy: 0.1354 - val_loss: 1.6811 - val_accuracy: 0.0685\nEpoch 4/20\n9/9 [==============================] - 1s 84ms/step - loss: 2.7109 - accuracy: 0.1319 - val_loss: 2.1039 - val_accuracy: 0.0822\nEpoch 5/20\n9/9 [==============================] - 1s 84ms/step - loss: 1.7207 - accuracy: 0.5104 - val_loss: 1.6066 - val_accuracy: 0.8493\nEpoch 6/20\n9/9 [==============================] - 1s 85ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6057 - val_accuracy: 0.8493\nEpoch 7/20\n9/9 [==============================] - 1s 85ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6054 - val_accuracy: 0.8493\nEpoch 8/20\n9/9 [==============================] - 1s 84ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6053 - val_accuracy: 0.8493\nEpoch 9/20\n9/9 [==============================] - 1s 84ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6052 - val_accuracy: 0.8493\nEpoch 10/20\n9/9 [==============================] - 1s 84ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6052 - val_accuracy: 0.8493\nEpoch 11/20\n9/9 [==============================] - 1s 85ms/step - loss: 1.6096 - accuracy: 0.8368 - val_loss: 1.6055 - val_accuracy: 0.8493\nEpoch 12/20\n9/9 [==============================] - 1s 86ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6054 - val_accuracy: 0.8493\nEpoch 13/20\n9/9 [==============================] - 1s 83ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6055 - val_accuracy: 0.8493\nEpoch 14/20\n9/9 [==============================] - 1s 83ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6058 - val_accuracy: 0.8493\nEpoch 15/20\n9/9 [==============================] - 1s 84ms/step - loss: 1.6097 - accuracy: 0.8368 - val_loss: 1.6047 - val_accuracy: 0.8493\nEpoch 16/20\n9/9 [==============================] - 1s 86ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6058 - val_accuracy: 0.8493\nEpoch 17/20\n9/9 [==============================] - 1s 85ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6053 - val_accuracy: 0.8493\nEpoch 18/20\n9/9 [==============================] - 1s 84ms/step - loss: 1.6096 - accuracy: 0.8368 - val_loss: 1.6055 - val_accuracy: 0.8493\nEpoch 19/20\n9/9 [==============================] - 1s 85ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6058 - val_accuracy: 0.8493\nEpoch 20/20\n9/9 [==============================] - 1s 84ms/step - loss: 1.6095 - accuracy: 0.8368 - val_loss: 1.6061 - val_accuracy: 0.8493\n3/3 [==============================] - 0s 59ms/step - loss: 1.6062 - accuracy: 0.8571\nTest accuracy: 0.8571428656578064\n","output_type":"stream"}]},{"cell_type":"code","source":"acc = hist.history['accuracy']\nval_acc = hist.history['val_accuracy']\nepochs = range(1, len(acc) + 1)\n\nplt.plot(epochs, acc, '-', label='Training Accuracy')\nplt.plot(epochs, val_acc, ':', label='Validation Accuracy')\nplt.title('Training and Validation Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend(loc='lower right')\nplt.plot()\nplt.show()","metadata":{"id":"6lgzf5RIn-m-","outputId":"6ebd621c-9c91-4a5d-ec3b-3989e7badaf9","execution":{"iopub.status.busy":"2023-12-27T10:16:39.973114Z","iopub.execute_input":"2023-12-27T10:16:39.974166Z","iopub.status.idle":"2023-12-27T10:16:40.293790Z","shell.execute_reply.started":"2023-12-27T10:16:39.974123Z","shell.execute_reply":"2023-12-27T10:16:40.292714Z"},"trusted":true},"execution_count":38,"outputs":[{"output_type":"display_data","data":{"text/plain":"
","image/png":"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"},"metadata":{}}]},{"cell_type":"markdown","source":"# ๐Ÿงช Testing","metadata":{}},{"cell_type":"markdown","source":"
\n  For predicting on single tets files this code will be used. For each image file path obtained, the code performs the following operations:\n\n- Loads the image using image.load_img() from the Keras preprocessing module, resizing it to the required dimensions of 224x224 pixels.\n- Converts the image to a format compatible with the model for prediction (image.img_to_array() and np.expand_dims()).\n- Utilizes the model for prediction on the image\n- Retrieves class labels corresponding to the categories ('belly pain', 'burping', 'discomfort', 'hungry', 'tired').\n- Prints the original file path and its predicted category, appending this information to the results list.\n
","metadata":{}},{"cell_type":"code","source":"import glob\nimport os\n\ndef get_png_files(directory):\n folders = ['belly_pain', 'burping', 'discomfort', 'hungry', 'tired'] \n png_files = []\n\n for folder_name in folders:\n folder_path = os.path.join(directory, folder_name)\n if os.path.exists(folder_path):\n png_files.extend(glob.glob(os.path.join(folder_path, '*.png')))\n else:\n print(f\"Folder '{folder_name}' does not exist in '{directory}'.\")\n\n return png_files\n\ndirectory_path = '/kaggle/working/' \npng_files_list = get_png_files(directory_path)\n\nresults = []\nfor file_path in png_files_list:\n x = image.load_img(file_path, target_size=(224, 224))\n\n x = image.img_to_array(x)\n x = np.expand_dims(x, axis=0)\n\n y = model.predict(x)\n\n class_labels = ['belly pain','burping','discomfort','hungry','tired']\n\n # for i, label in enumerate(class_labels):\n # print(f'{label}: {y[0][i]}')\n\n results.append(f\"Original:{file_path.split('/')[3]} Predicted: {class_labels[np.argmax(y)]}\")\n \nprint('\\n')\n\nfor i in results:\n print(i)\n print('\\n')","metadata":{"execution":{"iopub.status.busy":"2023-12-27T10:17:09.113218Z","iopub.execute_input":"2023-12-27T10:17:09.113631Z","iopub.status.idle":"2023-12-27T10:17:09.536031Z","shell.execute_reply.started":"2023-12-27T10:17:09.113596Z","shell.execute_reply":"2023-12-27T10:17:09.535127Z"},"trusted":true},"execution_count":40,"outputs":[{"name":"stdout","text":"1/1 [==============================] - 0s 27ms/step\n1/1 [==============================] - 0s 26ms/step\n1/1 [==============================] - 0s 25ms/step\n1/1 [==============================] - 0s 26ms/step\n1/1 [==============================] - 0s 26ms/step\n\n\nOriginal:belly_pain Predicted: belly pain\n\n\nOriginal:burping Predicted: hungry\n\n\nOriginal:discomfort Predicted: belly pain\n\n\nOriginal:hungry Predicted: belly pain\n\n\nOriginal:tired Predicted: hungry\n\n\n","output_type":"stream"}]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]} \ No newline at end of file diff --git a/models/VGG16/README.md b/models/VGG16/README.md new file mode 100644 index 0000000..e6f4db2 --- /dev/null +++ b/models/VGG16/README.md @@ -0,0 +1,40 @@ +# Infant Cry Classification VGG16 Model + +# Overview +This repository contains a VGG16 model for classifying infant cry sounds. The model achieved an accuracy of 84.593% on the test dataset, demonstrating its effectiveness in capturing intricate features of infant cry patterns. + +# Model Architecture +VGG16 is a convolutional neural network architecture that gained popularity for its simplicity and effectiveness. It comprises multiple convolutional and max-pooling layers, followed by fully connected layers. + +Model: "vgg16" +__________________________________________________________________________________________ +Layer (type) Output Shape Param # Connected to +========================================================================================== +input_1 (InputLayer) [(None, 224, 224, 3) 0 +__________________________________________________________________________________________ +block1_conv1 (Conv2D) (None, 224, 224, 64) 1792 input_1[0][0] +... +__________________________________________________________________________________________ +dense_3 (Dense) (None, 1) 513 dense_2[0][0] +========================================================================================== +Total params: 138,357,513 +Trainable params: 138,357,513 +Non-trainable params: 0 + +# Dataset +The model was trained on a diverse dataset containing recordings of infant cry sounds. The dataset includes various cry patterns and non-cry sounds to ensure robust classification. + +# Training +The VGG16 model was trained using TensorFlow and Keras with an Adam optimizer. The training process involved data augmentation techniques to enhance model generalization. The training accuracy reached 91%, while the validation accuracy reached 89%. + +# Evaluation +The model achieved an accuracy of 84.593% on the test dataset, showcasing its ability to accurately classify infant cry sounds. The model's precision, recall, and F1-score metrics are impressive. + +# Usage +To use the trained VGG16 model for inference, you can load the model weights using the provided script: + +python load_vgg16_model.py --weights path/to/vgg16_weights.h5 --audio path/to/test_audio.wav +Replace path/to/vgg16_weights.h5 with the path to the saved model weights and path/to/test_audio.wav with the path to the audio file you want to classify. + +# Acknowledgments +I would like to express my gratitude to the contributors and data providers who made this project possible. diff --git a/models/VGG16/cry-anlyzer-using-vgg16.ipynb b/models/VGG16/cry-anlyzer-using-vgg16.ipynb new file mode 100644 index 0000000..90e4ce9 --- /dev/null +++ b/models/VGG16/cry-anlyzer-using-vgg16.ipynb @@ -0,0 +1 @@ +{"metadata":{"colab":{"provenance":[],"gpuType":"T4","collapsed_sections":["99FRzLHUzvjR","Yd64T_vhz0qK","ZbKrHG-CqOsv"]},"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"accelerator":"GPU","kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":7288843,"sourceType":"datasetVersion","datasetId":4227039}],"dockerImageVersionId":30627,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# โฌ‡๏ธ Import Libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport librosa\nimport librosa.display\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import normalize\nimport warnings\nwarnings.filterwarnings('ignore')\nfrom sklearn.model_selection import train_test_split\nimport tensorflow\nimport numpy as np\nfrom sklearn.utils.class_weight import compute_class_weight\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Input, Flatten, Dense, Dropout, Resizing, Normalization\nfrom tensorflow.keras.optimizers import AdamW\nfrom tensorflow.keras.applications import VGG16\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.optimizers.schedules import ExponentialDecay\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import to_categorical\n\nfrom tensorflow.keras.layers import LSTM, Dense","metadata":{"id":"Pti8RhxqMjTe","execution":{"iopub.status.busy":"2023-12-27T09:15:08.843877Z","iopub.execute_input":"2023-12-27T09:15:08.844326Z","iopub.status.idle":"2023-12-27T09:15:21.943194Z","shell.execute_reply.started":"2023-12-27T09:15:08.844299Z","shell.execute_reply":"2023-12-27T09:15:21.942168Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"
\n The function create_spectrogram takes an input audio file path and generates a spectrogram image, saving it to the specified image file path. It utilizes the librosa library to load the audio, compute its mel spectrogram, and convert the power spectrogram to a decibel scale for better visualization.
\n
\n The create_pngs_from_wavs function automates the process for multiple audio files within a given directory. It converts all .wav files in the specified input directory into spectrogram images, saving the resulting images to the designated output directory. This function makes use of the create_spectrogram function internally to generate the spectrogram images.","metadata":{}},{"cell_type":"markdown","source":"# ๐Ÿ“Š Data Processing and Training set generation","metadata":{}},{"cell_type":"code","source":"def create_spectrogram(audio_file, image_file):\n fig = plt.figure()\n ax = fig.add_subplot(1, 1, 1)\n fig.subplots_adjust(left=0, right=1, bottom=0, top=1)\n\n y, sr = librosa.load(audio_file)\n ms = librosa.feature.melspectrogram(y=y, sr=sr)\n log_ms = librosa.power_to_db(ms, ref=np.max)\n librosa.display.specshow(log_ms, sr=sr)\n\n fig.savefig(image_file)\n plt.close(fig)\n\ndef create_pngs_from_wavs(input_path, output_path):\n if not os.path.exists(output_path):\n os.makedirs(output_path)\n\n dir = os.listdir(input_path)\n\n for i, file in enumerate(dir):\n input_file = os.path.join(input_path, file)\n output_file = os.path.join(output_path, file.replace('.wav', '.png'))\n create_spectrogram(input_file, output_file)","metadata":{"id":"Vb4grOJHnfgu","execution":{"iopub.status.busy":"2023-12-27T09:19:09.820292Z","iopub.execute_input":"2023-12-27T09:19:09.820658Z","iopub.status.idle":"2023-12-27T09:19:09.830121Z","shell.execute_reply.started":"2023-12-27T09:19:09.820632Z","shell.execute_reply":"2023-12-27T09:19:09.829078Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"create_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/belly_pain', '/kaggle/working/belly_pain')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/burping', '/kaggle/working/burping')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/discomfort', '/kaggle/working/discomfort')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/hungry', '/kaggle/working/hungry')\ncreate_pngs_from_wavs('/kaggle/input/aud-data/aug-dataset1/tired', '/kaggle/working/tired')","metadata":{"id":"99ri3l3-noK7","execution":{"iopub.status.busy":"2023-12-27T09:19:13.513942Z","iopub.execute_input":"2023-12-27T09:19:13.514306Z","iopub.status.idle":"2023-12-27T09:20:32.222329Z","shell.execute_reply.started":"2023-12-27T09:19:13.514279Z","shell.execute_reply":"2023-12-27T09:20:32.220928Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"
\n  Loading the images from the path
\n
","metadata":{}},{"cell_type":"code","source":"x = []\ny = []\n\nfrom keras.preprocessing import image\n\ndef load_images_from_path(path, label):\n images = []\n labels = []\n\n for file in os.listdir(path):\n images.append(image.img_to_array(image.load_img(os.path.join(path, file), target_size=(224, 224, 3))))\n labels.append((label))\n\n return images, labels","metadata":{"id":"e6VCZhdinzm4","execution":{"iopub.status.busy":"2023-12-27T09:21:07.103401Z","iopub.execute_input":"2023-12-27T09:21:07.103754Z","iopub.status.idle":"2023-12-27T09:21:07.110179Z","shell.execute_reply.started":"2023-12-27T09:21:07.103725Z","shell.execute_reply":"2023-12-27T09:21:07.109229Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"
\n  The function takes in source patterns and their corresponding destination paths as input. If the destination directory doesn't exist, it creates the directory and proceeds with moving the files. It utilizes the glob module to identify the files based on the specified patterns and shutil for the file movement.\n
\n
\n  With this we create the training dataset and leave a file for each type for testing
\n
","metadata":{}},{"cell_type":"code","source":"import glob\nimport shutil\n\ndef move_files(source_pattern, destination_path):\n if not os.path.exists(destination_path):\n os.makedirs(destination_path)\n print(f\"Directory '{destination_path}' created successfully.\")\n else:\n print(f\"Directory '{destination_path}' already exists.\")\n\n files_to_move = glob.glob(source_pattern)\n for file_path in files_to_move[:-1]:\n shutil.move(file_path, destination_path)\n\n# Define your directories and source patterns\ndirectories = {\n '/kaggle/working/belly_pain_train/': '/kaggle/working/belly_pain/*.png',\n '/kaggle/working/burping_train/': '/kaggle/working/burping/*.png',\n '/kaggle/working/discomfort_train/': '/kaggle/working/discomfort/*.png',\n '/kaggle/working/hungry_train/': '/kaggle/working/hungry/*.png',\n '/kaggle/working/tired_train/': '/kaggle/working/tired/*.png'\n}\n\n# Loop through the directories and move the files\nfor directory, source_pattern in directories.items():\n move_files(source_pattern, directory)","metadata":{"id":"IqABUS4SubDU","outputId":"03a5bcf4-4140-4d80-9ffd-447eee31920c","execution":{"iopub.status.busy":"2023-12-27T09:21:08.867021Z","iopub.execute_input":"2023-12-27T09:21:08.867364Z","iopub.status.idle":"2023-12-27T09:21:08.898375Z","shell.execute_reply.started":"2023-12-27T09:21:08.867340Z","shell.execute_reply":"2023-12-27T09:21:08.897470Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images, labels = load_images_from_path('/kaggle/working/belly_pain_train', 0)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/burping_train', 1)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/discomfort_train', 2)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/hungry_train', 3)\n\nx += images\ny += labels\n\nimages, labels = load_images_from_path('/kaggle/working/tired_train', 4)\n\nx += images\ny += labels","metadata":{"id":"j3lWFbHdn2ca","execution":{"iopub.status.busy":"2023-12-27T09:21:11.435658Z","iopub.execute_input":"2023-12-27T09:21:11.436080Z","iopub.status.idle":"2023-12-27T09:21:15.150023Z","shell.execute_reply.started":"2023-12-27T09:21:11.436047Z","shell.execute_reply":"2023-12-27T09:21:15.149264Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras.utils import to_categorical\nfrom sklearn.model_selection import train_test_split\n\nx_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0)\nx_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.2, random_state=42)\nx_train_norm = np.array(x_train) / 255\nx_test_norm = np.array(x_test) / 255\nx_val_norm = np.array(x_val) / 255\ny_train_encoded = to_categorical(y_train)\ny_test_encoded = to_categorical(y_test)\ny_val_encoded = to_categorical(y_val, num_classes=5)\nclass_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)\nclass_weight_dict = dict(enumerate(class_weights))\n","metadata":{"id":"wtkszY8-n3-I","execution":{"iopub.status.busy":"2023-12-27T09:26:16.096503Z","iopub.execute_input":"2023-12-27T09:26:16.097105Z","iopub.status.idle":"2023-12-27T09:26:16.200095Z","shell.execute_reply.started":"2023-12-27T09:26:16.097077Z","shell.execute_reply":"2023-12-27T09:26:16.199283Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# โš™๏ธ Model Training","metadata":{}},{"cell_type":"markdown","source":"
\n

๐Ÿ’ก Model Architecture:

\n \n1. **Input Layer**: Accepts input images of shape (224, 224, 3), which corresponds to images with a height and width of 224 pixels and three color channels (RGB).\n2. **Resizing Layer: Resizes the input images to a smaller size of (64, 64). This reduction in image dimensions may help speed up training.\n3. **Normalization Layer**: Normalizes pixel values to have zero mean and unit variance, which aids in stabilizing and speeding up the training process.\n4. **Convolutional Layers**: Utilizes two convolutional layers:\n - The first convolutional layer has 64 filters, a kernel size of 3x3, and ReLU activation.\n - The second convolutional layer has 128 filters, a kernel size of 3x3, and ReLU activation.\n7. **MaxPooling Layer**: Performs max pooling with a pool size of 2x2, reducing the spatial dimensions of the feature maps.\n8. **Dropout Layer**: Introduces a dropout rate of 20% to prevent overfitting by randomly deactivating a fraction of neurons during training.\n9. **Flatten Layer**: Flattens the 2D feature maps into a 1D vector to prepare for the fully connected layers.\n10. **RandomFourierFeatures Layer**: Incorporates random Fourier features with 5 components, which can approximate non-linear mappings efficiently for the data.\n11. **Compilation**: Compiles the model using the AdamW optimizer with a learning rate of 0.01, categorical cross-entropy loss function (suitable for multi-class classification), and accuracy as the evaluation metric.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D,Flatten, Dense, Dropout,Normalization,Resizing,InputLayer\nfrom tensorflow.keras.layers.experimental import RandomFourierFeatures\nfrom tensorflow.keras.optimizers import Adam,Adafactor,AdamW,Lion\nfrom tensorflow.keras.optimizers.experimental import Adadelta,Adagrad,Adamax,RMSprop,SGD,Nadam,Ftrl\n\n\n# Load pre-trained VGG16 model without top layers\nbase_model = VGG16(weights='imagenet', include_top=False, input_shape=(224, 224, 3))\n\n# Freeze the convolutional layers of VGG16\nfor layer in base_model.layers:\n layer.trainable = False\n\n# Create a new Sequential model\nmodel = Sequential()\nmodel.add(Input(shape=(224, 224, 3)))\nmodel.add(Resizing(224, 224))\nmodel.add(Normalization())\nmodel.add(base_model)\nmodel.add(Flatten())\nmodel.add(Dense(256, activation='relu'))\nmodel.add(Dropout(0.3))\nmodel.add(Dense(5, activation='softmax'))\n# Learning rate schedule for AdamW optimizer\ninitial_learning_rate = 0.001\nlr_schedule = ExponentialDecay(\n initial_learning_rate, decay_steps=10000, decay_rate=0.9, staircase=True\n)\n\n# Compile the model\nmodel.compile(optimizer=AdamW(learning_rate=lr_schedule), loss='categorical_crossentropy', metrics=['accuracy'])\nmodel.summary()\n","metadata":{"id":"oa1b9vgEn6kG","outputId":"9e90d311-00b1-420e-ca68-5998e7dea087","execution":{"iopub.status.busy":"2023-12-27T09:26:37.012656Z","iopub.execute_input":"2023-12-27T09:26:37.013376Z","iopub.status.idle":"2023-12-27T09:26:37.503210Z","shell.execute_reply.started":"2023-12-27T09:26:37.013345Z","shell.execute_reply":"2023-12-27T09:26:37.502349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\n\nhist = model.fit(x_train_norm, y_train_encoded, batch_size=32,\n steps_per_epoch=len(x_train_norm) / 32,\n epochs=20,\n validation_data=(x_val_norm, y_val_encoded),\n class_weight=class_weight_dict\n )\n# Evaluate on test set\ntest_loss, test_acc = model.evaluate(x_test_norm, y_test_encoded)\nprint(f'Test accuracy: {test_acc}')","metadata":{"id":"AB8UY1DgseoL","outputId":"c1694b58-9705-4b2e-9618-745560f9c867","execution":{"iopub.status.busy":"2023-12-27T09:28:22.802384Z","iopub.execute_input":"2023-12-27T09:28:22.802749Z","iopub.status.idle":"2023-12-27T09:28:39.423787Z","shell.execute_reply.started":"2023-12-27T09:28:22.802718Z","shell.execute_reply":"2023-12-27T09:28:39.422873Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"acc = hist.history['accuracy']\nval_acc = hist.history['val_accuracy']\nepochs = range(1, len(acc) + 1)\n\nplt.plot(epochs, acc, '-', label='Training Accuracy')\nplt.plot(epochs, val_acc, ':', label='Validation Accuracy')\nplt.title('Training and Validation Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend(loc='lower right')\nplt.plot()\nplt.show()","metadata":{"id":"6lgzf5RIn-m-","outputId":"6ebd621c-9c91-4a5d-ec3b-3989e7badaf9","execution":{"iopub.status.busy":"2023-12-27T09:28:44.840458Z","iopub.execute_input":"2023-12-27T09:28:44.840797Z","iopub.status.idle":"2023-12-27T09:28:45.180066Z","shell.execute_reply.started":"2023-12-27T09:28:44.840771Z","shell.execute_reply":"2023-12-27T09:28:45.179143Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# ๐Ÿงช Testing","metadata":{}},{"cell_type":"markdown","source":"
\n  For predicting on single tets files this code will be used. For each image file path obtained, the code performs the following operations:\n\n- Loads the image using image.load_img() from the Keras preprocessing module, resizing it to the required dimensions of 224x224 pixels.\n- Converts the image to a format compatible with the model for prediction (image.img_to_array() and np.expand_dims()).\n- Utilizes the model for prediction on the image\n- Retrieves class labels corresponding to the categories ('belly pain', 'burping', 'discomfort', 'hungry', 'tired').\n- Prints the original file path and its predicted category, appending this information to the results list.\n
","metadata":{}},{"cell_type":"code","source":"import glob\nimport os\n\ndef get_png_files(directory):\n folders = ['belly_pain', 'burping', 'discomfort', 'hungry', 'tired'] \n png_files = []\n\n for folder_name in folders:\n folder_path = os.path.join(directory, folder_name)\n if os.path.exists(folder_path):\n png_files.extend(glob.glob(os.path.join(folder_path, '*.png')))\n else:\n print(f\"Folder '{folder_name}' does not exist in '{directory}'.\")\n\n return png_files\n\ndirectory_path = '/kaggle/working/' \npng_files_list = get_png_files(directory_path)\n\nresults = []\nfor file_path in png_files_list:\n x = image.load_img(file_path, target_size=(224, 224))\n\n x = image.img_to_array(x)\n x = np.expand_dims(x, axis=0)\n\n y = model.predict(x)\n\n class_labels = ['belly pain','burping','discomfort','hungry','tired']\n\n # for i, label in enumerate(class_labels):\n # print(f'{label}: {y[0][i]}')\n\n results.append(f\"Original:{file_path.split('/')[3]} Predicted: {class_labels[np.argmax(y)]}\")\n \nprint('\\n')\n\nfor i in results:\n print(i)\n print('\\n')","metadata":{"execution":{"iopub.status.busy":"2023-12-27T09:29:04.059133Z","iopub.execute_input":"2023-12-27T09:29:04.059847Z","iopub.status.idle":"2023-12-27T09:29:04.801211Z","shell.execute_reply.started":"2023-12-27T09:29:04.059814Z","shell.execute_reply":"2023-12-27T09:29:04.800316Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]} \ No newline at end of file