{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        os.path.join(dirname, filename)\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:34:47.310370Z","iopub.execute_input":"2022-06-03T07:34:47.311083Z","iopub.status.idle":"2022-06-03T07:34:47.548628Z","shell.execute_reply.started":"2022-06-03T07:34:47.310974Z","shell.execute_reply":"2022-06-03T07:34:47.547433Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_meta = pd.read_csv(\"/kaggle/input/birdclef-2022/train_metadata.csv\")\nlabels = list(train_meta['primary_label'].unique()) #grabs the labels","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:34:49.833878Z","iopub.execute_input":"2022-06-03T07:34:49.834383Z","iopub.status.idle":"2022-06-03T07:34:49.963185Z","shell.execute_reply.started":"2022-06-03T07:34:49.834349Z","shell.execute_reply":"2022-06-03T07:34:49.962067Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Imports","metadata":{}},{"cell_type":"code","source":"import os\nimport json \nimport librosa\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nimport tensorflow as tf\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Flatten, Conv2D, MaxPooling2D\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras import optimizers,regularizers\nfrom tensorflow.keras.utils import plot_model, Sequence\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.applications import MobileNetV2\nfrom tensorflow.keras.layers import Input, Concatenate\nfrom tqdm import tqdm\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense,Dropout,Activation\nfrom tensorflow.keras.optimizers import Adam\nfrom sklearn import metrics","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:34:51.634177Z","iopub.execute_input":"2022-06-03T07:34:51.634599Z","iopub.status.idle":"2022-06-03T07:34:54.893091Z","shell.execute_reply.started":"2022-06-03T07:34:51.634566Z","shell.execute_reply":"2022-06-03T07:34:54.891802Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Preprocessing/Data Preparation**","metadata":{}},{"cell_type":"code","source":"import soundfile as sf\nimport os\n\ndef cutAudio(file_path, is_save):\n    # Load the file\n    filename = file_path.replace(\"/\", \"_\")\n    file_path = \"/kaggle/input/birdclef-2022/train_audio/\" + file_path\n    audio, sr = librosa.load(file_path)\n\n    # Get number of samples for 5 seconds\n    buffer = 5 * sr\n    totalSamples = len(audio)\n    samplesProcessed = 0\n    count = 1\n\n    audioSplit = []\n    audioFilenames = []\n    while samplesProcessed < totalSamples:\n        #check if the buffer is not exceeding total samples \n        if buffer > (totalSamples - samplesProcessed):\n            buffer = totalSamples - samplesProcessed\n\n        block = audio[samplesProcessed : (samplesProcessed + buffer)]\n        audioSplit.append(block)\n\n        # Write 5 second \n        if is_save == True:\n            outputFilename = \"/kaggle/working/each5s/split_\" + str(count) + \"_\" + filename\n            audioFilenames.append(outputFilename)\n            sf.write(outputFilename, block, sr)\n        count += 1\n        samplesProcessed += buffer\n    return audioSplit, sr, audioFilenames","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:34:57.594332Z","iopub.execute_input":"2022-06-03T07:34:57.595069Z","iopub.status.idle":"2022-06-03T07:34:57.604799Z","shell.execute_reply.started":"2022-06-03T07:34:57.595025Z","shell.execute_reply":"2022-06-03T07:34:57.603539Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def splitTrainAudio(_df):\n    data = []\n    #iterate through the rows\n    for index, row in tqdm(_df.iterrows()):\n        cutAudio(row[\"filename\"], True)\n        audio_lst, sr, filenames = cutAudio(row[\"filename\"], True)\n        #loop through audios\n        for idx, y in enumerate(audio_lst):\n            data.append([row[\"primary_label\"], row[\"filename\"], filenames[idx]])\n\n    data_df = pd.DataFrame(data, columns=['primary_label', 'original_filename', 'filename'])\n    data_df.to_csv(\"/kaggle/working/data_df.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:35:01.722159Z","iopub.execute_input":"2022-06-03T07:35:01.723040Z","iopub.status.idle":"2022-06-03T07:35:01.729838Z","shell.execute_reply.started":"2022-06-03T07:35:01.722996Z","shell.execute_reply":"2022-06-03T07:35:01.728875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_frames = []\nfor label in labels:\n    tmp_df = train_meta[train_meta[\"primary_label\"] == label].sample(n=1, replace=True).reset_index(drop=True)\n    data_frames.append(tmp_df)\nsample_df = pd.concat(data_frames).reset_index(drop=True)\nsample_df","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:35:08.731155Z","iopub.execute_input":"2022-06-03T07:35:08.731593Z","iopub.status.idle":"2022-06-03T07:35:09.051716Z","shell.execute_reply.started":"2022-06-03T07:35:08.731561Z","shell.execute_reply":"2022-06-03T07:35:09.050508Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#make a directory for the samples\n!mkdir -p \"/kaggle/working/each5s\"\nsplitTrainAudio(sample_df)","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:35:20.397346Z","iopub.execute_input":"2022-06-03T07:35:20.397815Z","iopub.status.idle":"2022-06-03T07:48:23.300226Z","shell.execute_reply.started":"2022-06-03T07:35:20.397776Z","shell.execute_reply":"2022-06-03T07:48:23.298521Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_df = pd.read_csv(\"/kaggle/working/data_df.csv\")\ndata_df","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:48:23.304762Z","iopub.execute_input":"2022-06-03T07:48:23.305157Z","iopub.status.idle":"2022-06-03T07:48:23.327110Z","shell.execute_reply.started":"2022-06-03T07:48:23.305123Z","shell.execute_reply":"2022-06-03T07:48:23.325785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"num_rows = 216\nnum_columns = 216\nnum_channels = 1\nn_mels = 512\n\n#grab features using mfcc\ndef extractFeatures(y, sr):\n    feat = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=num_rows, n_mels=n_mels)\n    if feat.shape[1] <= num_columns:\n        pad_width = num_columns - feat.shape[1]\n        feat = np.pad(feat, pad_width=((0,0),(0,pad_width)), mode='constant')\n    return feat","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:48:23.328499Z","iopub.execute_input":"2022-06-03T07:48:23.328939Z","iopub.status.idle":"2022-06-03T07:48:23.335418Z","shell.execute_reply.started":"2022-06-03T07:48:23.328902Z","shell.execute_reply":"2022-06-03T07:48:23.334449Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#data generator for training and testing data\nclass DataGenerator(Sequence):\n    #initial config\n    def __init__(self,\n                _X,\n                batch_size=32,\n                n_channels=1,\n                n_columns=470,\n                n_rows=120,\n                shuffle=True):\n        self.batch_size = batch_size\n        self.X = _X\n        self.n_channels = n_channels\n        self.n_columns = n_columns\n        self.n_rows = n_rows\n        self.shuffle = shuffle\n        self.img_indexes = np.arange(len(self.X))\n        self.on_epoch_end()\n    \n    #returns the number of batches per epoch\n    def __len__(self):\n        return int(np.floor(len(self.img_indexes) / self.batch_size))\n    \n    #generates a batch of data\n    def __getitem__(self, index):\n        indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]\n        list_IDs_temps = [self.img_indexes[k] for k in indexes]\n\n        # Generate data\n        X, y = self.__data_generation(list_IDs_temps)\n        return X, y\n    \n    #defines what happens after epoch, which is to update the indexes\n    def on_epoch_end(self):\n        self.indexes = np.arange(len(self.X))\n        if self.shuffle == True:\n            np.random.shuffle(self.indexes)\n    \n    #data generation function which returns the data and the labels and encodes them using keras to_categorical\n    def __data_generation(self, list_IDs_temps):\n        X = np.empty((self.batch_size, self.n_rows, self.n_columns))\n        y = np.empty((self.batch_size), dtype=int)\n        for i, ID in enumerate(list_IDs_temps):\n            file_path = self.X.iloc[ID][\"filename\"]\n            audio, sr = librosa.load(file_path)\n            feat = extractFeatures(audio, sr)\n            x_features = feat.tolist()\n            label = self.X.iloc[ID][\"target\"]\n            X[i] = np.array(x_features)\n            y[i] = label\n        X = X.reshape(X.shape[0], self.n_rows, self.n_columns, self.n_channels)\n        \n        return X, to_categorical(y, num_classes=len(labels))","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:48:23.337320Z","iopub.execute_input":"2022-06-03T07:48:23.337786Z","iopub.status.idle":"2022-06-03T07:48:23.352211Z","shell.execute_reply.started":"2022-06-03T07:48:23.337755Z","shell.execute_reply":"2022-06-03T07:48:23.351147Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#more config\nparams = dict(\n    batch_size=128,\n    n_rows=num_rows,\n    n_columns=num_columns,\n    n_channels=num_channels,\n)\nparams_train = dict(\n    shuffle=True,\n    **params\n)\nparams_valid = dict(\n    shuffle=False,\n    **params\n)","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:48:23.353996Z","iopub.execute_input":"2022-06-03T07:48:23.355249Z","iopub.status.idle":"2022-06-03T07:48:23.370077Z","shell.execute_reply.started":"2022-06-03T07:48:23.355206Z","shell.execute_reply":"2022-06-03T07:48:23.368892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n#plotting function to allow me to view what is happening with my model\ndef plot_his(history):\n    plt.figure(1, figsize = (15,8))\n    plt.subplot(221)\n    plt.plot(history.history['accuracy'])\n    plt.plot(history.history['val_accuracy'])\n    plt.title('model accuracy')\n    plt.ylabel('accuracy')\n    plt.xlabel('epoch')\n    plt.legend(['train', 'valid'])\n    plt.subplot(222)\n    plt.plot(history.history['loss'])\n    plt.plot(history.history['val_loss'])\n    plt.title('model loss')\n    plt.ylabel('loss')\n    plt.xlabel('epoch')\n    plt.legend(['train', 'valid'])\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:48:23.371402Z","iopub.execute_input":"2022-06-03T07:48:23.372294Z","iopub.status.idle":"2022-06-03T07:48:23.388486Z","shell.execute_reply.started":"2022-06-03T07:48:23.372253Z","shell.execute_reply":"2022-06-03T07:48:23.387235Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Convolutional Neural Network architecture \ndef create_cnn():\n    base_model = Sequential()\n    base_model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(num_rows, num_columns, num_channels)))\n    base_model.add(MaxPooling2D((2, 2)))\n    base_model.add(Dropout(0.2))\n    base_model.add(Conv2D(64, (3, 3), activation='relu'))\n    base_model.add(MaxPooling2D((2, 2)))\n    base_model.add(Dropout(0.2))\n    base_model.add(Conv2D(64, (3, 3), activation='relu'))\n    base_model.add(Flatten())\n    base_model.add(Dense(len(labels), activation='softmax', kernel_regularizer='l1'))\n    return base_model","metadata":{"execution":{"iopub.status.busy":"2022-06-03T10:08:16.899771Z","iopub.execute_input":"2022-06-03T10:08:16.902250Z","iopub.status.idle":"2022-06-03T10:08:16.914957Z","shell.execute_reply.started":"2022-06-03T10:08:16.902179Z","shell.execute_reply":"2022-06-03T10:08:16.913539Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_df['target'] = data_df['primary_label'].apply(lambda x: labels.index(x))\ndata_df","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:48:23.404379Z","iopub.execute_input":"2022-06-03T07:48:23.405175Z","iopub.status.idle":"2022-06-03T07:48:23.430417Z","shell.execute_reply.started":"2022-06-03T07:48:23.405139Z","shell.execute_reply":"2022-06-03T07:48:23.428653Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from keras import backend as K\n\n#equations for recall, precision and f1\ndef recall_m(y_true, y_pred):\n    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))\n    possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))\n    recall = true_positives / (possible_positives + K.epsilon())\n    return recall\n\ndef precision_m(y_true, y_pred):\n    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))\n    predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))\n    precision = true_positives / (predicted_positives + K.epsilon())\n    return precision\n\ndef f1_m(y_true, y_pred):\n    precision = precision_m(y_true, y_pred)\n    recall = recall_m(y_true, y_pred)\n    return 2*((precision*recall)/(precision+recall+K.epsilon()))","metadata":{"execution":{"iopub.status.busy":"2022-06-03T07:48:23.432008Z","iopub.execute_input":"2022-06-03T07:48:23.432503Z","iopub.status.idle":"2022-06-03T07:48:23.444027Z","shell.execute_reply.started":"2022-06-03T07:48:23.432455Z","shell.execute_reply":"2022-06-03T07:48:23.443019Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#function for training the model\ndef train_model(model, train_gen, val_gen):\n    checkpoint_model_path = \"/kaggle/working/customModel.h5\"\n    metric = \"val_accuracy\"\n    #compiling model with metrics as accuracy, f1, precision and recall to allow us to see what is happening per epoch\n    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy',f1_m,precision_m, recall_m])\n    num_epochs = 50\n    \n    #added a checkpointer to allow saving of weights at an interval, saving only when it achieves better scores\n    checkpointer = ModelCheckpoint(\n        filepath=checkpoint_model_path,\n        monitor=metric, verbose=1, save_best_only=True)\n    \n    #this stops the model from training when the metric being monitored is no longer improving\n    es_callback = EarlyStopping(monitor=metric, patience=5, verbose=1)\n    reduce_lr = ReduceLROnPlateau(monitor=metric, factor=0.3, patience=1, verbose=1, min_delta=0.0001, cooldown=1, min_lr=0.00001)\n    \n    #fit the model\n    history = model.fit(\n        train_gen,\n        epochs=num_epochs,\n        validation_data=val_gen,\n        callbacks=[checkpointer,es_callback,reduce_lr],\n        verbose=1\n    )\n    \n    #evaluate the model\n    loss, accuracy, f1_score, precision, recall = model.evaluate(val_gen, verbose=0)\n    \n    #print all the scores\n    print(\"Loss :\" + str(loss))\n    print(\"Accuracy :\" + str(accuracy))\n    print(\"F1 :\" + str(f1_score))\n    print(\"Precision :\" + str(precision))\n    print(\"Recall :\" + str(recall))\n    \n    #plot the model\n    plot_his(history)\n    \n    \n    return model","metadata":{"execution":{"iopub.status.busy":"2022-06-03T10:08:26.548711Z","iopub.execute_input":"2022-06-03T10:08:26.549128Z","iopub.status.idle":"2022-06-03T10:08:26.558926Z","shell.execute_reply.started":"2022-06-03T10:08:26.549094Z","shell.execute_reply":"2022-06-03T10:08:26.558274Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#using an 90:10 split between training:testing\nX_train, X_valid, _, _ = train_test_split(data_df, data_df[\"target\"], test_size=0.1, random_state=42)\ntrain_generator = DataGenerator(X_train, **params_train)\nvalid_generator = DataGenerator(X_valid, **params_valid)\ncnn_model = create_cnn()\ncnn_model = train_model(cnn_model, train_generator, valid_generator)","metadata":{"execution":{"iopub.status.busy":"2022-06-03T10:08:28.712013Z","iopub.execute_input":"2022-06-03T10:08:28.712461Z","iopub.status.idle":"2022-06-03T11:32:33.596726Z","shell.execute_reply.started":"2022-06-03T10:08:28.712423Z","shell.execute_reply":"2022-06-03T11:32:33.595938Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#grab all the unique labels\nlabels = list(data_df['primary_label'].unique())","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# **Submission**","metadata":{}},{"cell_type":"code","source":"test_path = \"/kaggle/input/birdclef-2022/test_soundscapes/\"\nfiles = [f.split('.')[0] for f in sorted(os.listdir(test_path))]\n\nbirds_path = \"/kaggle/input/birdclef-2022/scored_birds.json\"\nwith open(birds_path) as bf:\n    birds = json.load(bf)\n\ndata = []\nfor f in files:\n    file_path = test_path + f + '.ogg'\n    audio, sr = librosa.load(file_path)\n    # Get number of samples for 5 seconds; replace 5 by any number\n    buffer = 5 * sr\n    samples_total = len(audio)\n    samples_wrote = 0\n    counter = 1\n\n    while samples_wrote < samples_total:\n        #check if the buffer is not exceeding total samples \n        if buffer > (samples_total - samples_wrote):\n            buffer = samples_total - samples_wrote\n\n        block = audio[samples_wrote : (samples_wrote + buffer)]\n        feat = extractFeatures(block, sr)\n        x = feat.reshape(1, num_rows, num_columns, num_channels)\n        pred = cnn_model.predict(x)\n        label_index = np.argmax(pred,axis=1)[0]\n        \n        for b in birds:\n            segment_end = counter * 5   \n            row_id = f + '_' + b + '_' + str(segment_end)\n            target = False\n            if labels[label_index] == b:\n                target = True\n            data.append([row_id, target])\n        counter += 1\n        samples_wrote += buffer\n        \nsubmission_df = pd.DataFrame(data, columns=['row_id', 'target'])\nprint(submission_df.head())","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#removing the samples folder as it messes up finding the submission.csv\nimport shutil\nshutil.rmtree(os.getcwd() + '/each5s')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#saving on /kaggle/ and /kaggle/working to mitigate against the problem where the submission.csv cannot be found\nsubmission_df.to_csv(\"/kaggle/submission.csv\", index=False)\nsubmission_df.to_csv(\"/kaggle/working/submission.csv\", index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}