{"cells":[{"metadata":{},"cell_type":"markdown","source":"## Took help from this awesome notebook [https://www.kaggle.com/dipta007/birdsong-cnn-pytorch](http://) for learning purpose."},{"metadata":{},"cell_type":"markdown","source":"## Import all the libraries"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport os\nimport librosa # package for music and audio analysis\n\nfrom tqdm.notebook import tqdm, trange\n\nimport math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.preprocessing import LabelEncoder\n\nfrom csv import writer\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Declare the dataset path"},{"metadata":{"trusted":true},"cell_type":"code","source":"PATH = '../input/birdsong-recognition'\nIMG = '../input/birdsongspectrograms'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Data Preprocessing\n\n1. Load images and and use transformers from torchvision.\n2. Encode the class names from 0 to `number_of_classes`\n3. Create a new csv file named `train_test_data.csv` which will have 2 rows - `target` and `filepath`\n4. Split the dataset into train and test with `90:10` ratio\n5. Finally write a data generator function to get the batch_size of data"},{"metadata":{},"cell_type":"markdown","source":"## STEP 1: Load images and tranform them"},{"metadata":{"trusted":true},"cell_type":"code","source":"transformers = transforms.Compose([\n    transforms.RandomCrop((128, 512), pad_if_needed = True, padding_mode = \"reflect\"),\n    transforms.ToTensor(),\n    transforms.Normalize((0.5), (0.5))\n])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image(path, rescale = True, normalize = True):\n    image = Image.open(path)\n    image = transformers(image)\n    return image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## STEP 2: Encode the classes"},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv(os.path.sep.join([PATH,'train.csv']), skiprows = 0)\nle = LabelEncoder() # encode the\nle.fit(df['ebird_code'].to_numpy())\nlen(le.classes_) # shows the number of classes present in the csv file","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## STEP 3: Create a new csv file named `train_test_data.csv`"},{"metadata":{"trusted":true},"cell_type":"code","source":"def append_list_as_rows(file_name, list_of_elem):\n    with open(file_name, 'a+', newline = '') as write_obj:\n        csv_writer = writer(write_obj)\n        csv_writer.writerow(list_of_elem)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"csv_file_name = 'train_test_data.csv'\ndef remove_previous_csv_file():\n    try:\n        os.remove(csv_file_name)\n        print('[INFO] CSV file removed successfully')\n    except OSError as error:\n        print(f'[ERROR] {error}')\n        print(f'[INFO] {csv_file_name} cannot be removed')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"remove_previous_csv_file()\nheader = ['target', 'filepath']\nappend_list_as_rows(csv_file_name, header)\nfor index, row in tqdm(df.iterrows()):\n    bird = row['ebird_code']\n    audio = row['filename'].replace('.mp3', '.jpg')\n    filepath = f'{audio}'\n    \n    target = le.transform([bird])[0] # get the encoded class name\n    \n    if os.path.isfile(os.path.sep.join([IMG, filepath])):\n        append_list_as_rows(csv_file_name, [target, filepath])\n\nprint('[INFO] Complete writing to the csv file')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df2 = pd.read_csv(csv_file_name, skiprows = 0)\n\ndf2.head() # prints first five rows\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## STEP 4: Split the dataset into train and test"},{"metadata":{"trusted":true},"cell_type":"code","source":"VALIDATION_SIZE = 0.1\ndf2 = df2.sample(frac = 1).reset_index(drop = True)\n\ntotal_len = len(df2)\ntrain_size = int(total_len * (1.0 - VALIDATION_SIZE))\nval_size = int(total_len - train_size)\n\nprint(f'[INFO] Total Data: {total_len}, Train Data: {train_size}, Val Data: {val_size}')\n\ndef get_features(option):\n    data = None\n    if option == 'train':\n        data = df2[:train_size]\n    elif option == 'test':\n        data = df2[train_size:]\n    \n    for index, row in tqdm(data.iterrows()):\n        filepath = row['filepath']\n        spectrogram = load_image(os.path.sep.join([IMG, filepath]))\n        \n        yield spectrogram, row['target']\n\nprint(df2.head())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## STEP 5: Get the Batch Data"},{"metadata":{"trusted":true},"cell_type":"code","source":"BATCH_SIZE = 32\ndef get_batch(data_generator):\n    X, Y = [], []\n    cnt = 0\n    for x, y in data_generator:\n        X.append(x)\n        Y.append(y)\n        cnt += 1\n        if cnt >= BATCH_SIZE:\n            break\n    return torch.stack(X), torch.tensor(Y)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Define the Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\nprint(f'[INFO] Device: {device}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class model(nn.Module):\n    def __init__(self):\n        super(model, self).__init__()\n        self.conv1 = nn.Conv2d(1, 2, 3) # in_channels, out_channels, kernerl_size\n        self.conv2 = nn.Conv2d(2, 4, 3)\n        self.conv3 = nn.Conv2d(4, 8, 3)\n        \n        fn = 6944\n        self.fc1 = nn.Linear(fn, fn * 2) # in_features, out_features\n        self.fc2 = nn.Linear(fn * 2, fn)\n        self.fc3 = nn.Linear(fn, fn // 2)\n        self.output = nn.Linear(fn // 2, len(le.classes_))\n    \n    def forward(self, x):\n        x = F.relu(F.max_pool2d(self.conv1(x), 2))\n        x = F.relu(F.max_pool2d(self.conv2(x), 2))\n        x = F.relu(F.max_pool2d(self.conv3(x), 2))\n        \n        x = self.flatten(x)\n        \n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = F.relu(self.fc3(x))\n        \n        x = self.output(x)\n        \n        return x\n    \n    def flatten(self, x):\n        res = 1\n        for sz in x.size()[1:]:\n            res *= sz\n        return x.view(-1, res)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"LR = 0.0001\n\nnet = model().to(device)\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(net.parameters(), lr = LR)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Training"},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_number_of_correct_for_this_batch(y_pred, y):\n    y_pred = torch.nn.Softmax(dim = 1)(y_pred)\n    y_pred = torch.argmax(y_pred, dim = 1)\n    correct = torch.eq(y_pred, y).sum()\n    return correct.item()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"BEST_MODEL_PATH = 'best_model.pth'\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"EPOCHS = 40\nbest_loss = 1000000\npatience = 4\n\nfor epoch in range(EPOCHS):\n    # Training\n    net.train()\n    gen = get_features('train')\n    steps = math.ceil(train_size / BATCH_SIZE)\n    total_loss = 0\n    total_correct = 0\n    loop = tqdm(range(steps), total = steps)\n    \n    for i, _ in enumerate(loop):\n        X, Y = get_batch(gen)\n        X, Y = X.to(device), Y.to(device)\n        \n        # Forward Propagation\n        optimizer.zero_grad()\n        y_pred = net(X)\n        loss = criterion(y_pred, Y.view(-1))\n        total_loss += loss.item()\n        \n        # Backward Propagation\n        loss.backward()\n        optimizer.step()\n        \n        with torch.no_grad():\n            # Get Stats\n            correct = get_number_of_correct_for_this_batch(y_pred, Y)\n            total_correct += correct\n            \n            # Update Stats\n            loop.update(1)\n            loop.set_description(f'Epoch {epoch + 1}/{EPOCHS}')\n            loop.set_postfix(loss = loss.item(), acc = total_correct/((i + 1) * BATCH_SIZE))\n    \n    # Validation\n    with torch.no_grad():\n        net.eval()\n        gen = get_features('test')\n        steps = math.ceil(val_size / BATCH_SIZE)\n        total_loss = 0\n        total_correct = 0\n        loop = tqdm(range(steps), total = steps)\n        \n        for i, _ in enumerate(loop):\n            X, Y = get_batch(gen)\n            X, Y = X.to(device), Y.to(device)\n            \n            y_pred = net(X)\n            \n            loss = criterion(y_pred, Y.view(-1))\n            total_loss += loss.item()\n            \n            correct = get_number_of_correct_for_this_batch(y_pred, Y)\n            total_correct += correct\n            \n            loop.update(1)\n            loop.set_description(f'Epoch {epoch + 1}/{EPOCHS}')\n            loop.set_postfix(loss = loss.item(), acc = total_correct/((i + 1) * BATCH_SIZE))\n        \n        # Early Stopping\n        \n        if total_loss < best_loss:\n            best_loss = total_loss\n            patience = 4\n            torch.save(net, BEST_MODEL_PATH)\n        else:\n            patience -= 1\n        \n        if patience <= 0:\n            print(f'[INFO] Early Stopping at {epoch}')\n            break","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Testing"},{"metadata":{"trusted":true},"cell_type":"code","source":"net = torch.load(BEST_MODEL_PATH)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#from https://www.kaggle.com/daisukelab/creating-fat2019-preprocessed-data\ndef mono_to_color(X, mean = None, std = None, norm_max = None, norm_min = None, eps = 1e-6):\n    # Standardize\n    mean = mean or X.mean()\n    X = X - mean\n    \n    std = std or X.std()\n    Xstd = X / (std + eps)\n    _min, _max = Xstd.min(), Xstd.max()\n    norm_max = norm_max or _max\n    norm_min = norm_min or _min\n    \n    if (_max - _min) > eps:\n        # Normlize to [0, 255]\n        V = Xstd\n        V[V < norm_min] = norm_min\n        V[V > norm_max] = norm_max\n        V = 255 * (V - norm_min) / (norm_max - norm_min)\n        V = V.astype(np.unint8)\n    else:\n        # Just Zero\n        V = np.zeros_like(Xstd, dtype = np.unit8)\n    return V\n\ndef build_spectrogram(path, offset, duration):\n    y, sr = librosa.load(path, offset = offset, duration = duration)\n    total_secs = y.shape[0] / sr\n    M = librosa.feature.melspectrogram(y = y, sr = sr)\n    M = librosa.power_to_db(M)\n    M = mono_to_color(M)\n    \n    filename = path.split('/')[-1][:-4]\n    path = 'test.jpg'\n    cv2.imwrite(path, M, [int(cv2.IMWRITE_JPEG_QUALITY, 85)])\n    return path","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def make_prediction(x):\n    net.eval()\n    y_pred = net(x)\n    y_pred. nn.Softmax(dim = 1)(y_pred)\n    y_pred = torch.argmax(y_pred, dim = 1)\n    return le.inverse_transform(y_pred)[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# test_audio is only available while you submit the code"},{"metadata":{"trusted":true},"cell_type":"code","source":"TEST_FOLDER = '../input/birdsong-recognition/test_audio' # hidden folder\n\ntry:\n    preds = []\n    test = pd.read_csv(os.path.sep.join([PATH, 'test.csv']))\n    \n    for index, row in tqdm(test.iterrows()):\n        # Get test row information\n        site = row['site']\n        start_time = row['seconds']\n        row_id = row['row_id']\n        audio_id = row['audio_id']\n        \n        # Get the test sound clip\n        audio_file = os.path.sep.join([TEST_FOLDER, audio_id + '.mp3'])\n        if os.path.isfile(audio_file):\n            if site == 'site_1' or site == 'site_2':\n                path = build_spectrogram(audio_file, start_time, 5)\n                y = load_image(path)\n            else:\n                path = build_spectrogram(audio_file, 0, duration = None)\n                image = load_image(path)\n\n            # Make the predictions\n            pred = make_prediction(image)\n\n            # Store predictions\n            preds.append([row_id, pred])\n        else:\n            preds = pd.read_csv('../input/birdsong-recognition/sample_submission.csv')\n            break\nexcept Exception as e:\n    preds = pd.read_csv('../input/birdsong-recognition/sample_submission.csv')\n    print(f'[Reason] {e}')\n    \n# Convert to dataframe\npred_df = pd.DataFrame(preds, columns = ['row_id', 'birds'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Show first 5 predicitons"},{"metadata":{"trusted":true},"cell_type":"code","source":"pred_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Write to csv file for submission\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"pred_df.fillna('nocall', inplace = True) # fill the columns with nocall that are empty\npred_df.to_csv('submission.csv', index = False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Finally submit to the competition"},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}