{"cells":[{"metadata":{},"cell_type":"markdown","source":"### At first Thanks to some awesome kernels\n1. https://www.kaggle.com/cwthompson/birdsong-making-a-prediction\n2. https://www.kaggle.com/parulpandey/eda-and-audio-processing-with-python\n3. https://www.kaggle.com/seriousran/mfcc-feature-extraction-for-sound-classification\n4. https://www.kaggle.com/hamditarek/audio-data-analysis-using-librosa\n\n### And some discussions\n1. https://www.kaggle.com/c/birdsong-recognition/discussion/158933\n2. https://www.kaggle.com/c/birdsong-recognition/discussion/158908\n3. https://www.kaggle.com/c/birdsong-recognition/discussion/159001\n4. https://www.kaggle.com/c/birdsong-recognition/discussion/159492\n\n## Last but not the Least, Spectrogram Dataset\n1. https://www.kaggle.com/ryches/birdsongspectrograms","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Import all the Libraries","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport librosa\n\nfrom tqdm.notebook import tqdm, trange\nimport subprocess\n\nimport math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torchvision import transforms\nfrom PIL import Image\n\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\nPATH = '../input/birdsong-recognition/'\nIMG = '../input/birdsongspectrograms/'\nos.listdir(PATH)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Data Preprocess","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"#### In this part, \n1. we first write a function to load image and run some transformers from torchvision.  \n2. After that, encode the class name from 0 to `number_of_classes`  \n3. Then make a new csv file named `train_vall.csv` which will have only 2 rows - `target` & `filepath`  \n4. Then from that CSV, we will split the whole dataset into 90% train data and 10% test data  \n5. Then we will write a data generator function to get the batch_size of data  ","execution_count":null},{"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])\n\ndef load_img(path, rescale=True, normalize=True):\n    img = Image.open(path)\n    img = transformers(img)\n    return img","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df = pd.read_csv(os.path.join(PATH, 'train.csv'), skiprows=0)\n\nfrom sklearn.preprocessing import LabelEncoder\nle = LabelEncoder()\nle.fit(df['ebird_code'].to_numpy())\n\ndf.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from csv import writer\ndef append_list_as_row(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":"DURATION = 10\n\nimport os\ntry: \n    os.remove(\"train_val.csv\")\n    print(\"removed successfully\") \nexcept OSError as error: \n    print(error) \n    print(\"File path can not be removed\") \n    \nheader = ['target', 'filepath']\nappend_list_as_row('train_val.csv', header)\n\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]\n    duration = row['duration']\n    \n    if os.path.isfile(f\"{IMG}{filepath}\"):\n        tmp = []\n        tmp.append(target)\n        tmp.append(filepath)\n\n        append_list_as_row('train_val.csv', tmp)\n    \n#     if duration > 10:\n#         now = load_clip(filepath, 0, DURATION)\n#         print(now, now.size())\n#         break","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"del df\nimport gc\ngc.collect()\n\ndf = pd.read_csv('train_val.csv', skiprows=0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"VALIDATION_SIZE = 0.1\n\ndf = df.sample(frac=1).reset_index(drop=True)\n\ntotal_len = len(df)\ntrain_sz = int(total_len * (1-VALIDATION_SIZE))\nval_sz = int(total_len - train_sz)\nprint(train_sz, total_len - train_sz, len(df[:train_sz]), len(df[train_sz:]))\n\n\ndef get_features(train):\n    data = None\n    if train:\n        data = df[:train_sz]\n    else:\n        data = df[train_sz:]\n\n    for index, row in tqdm(data.iterrows()):\n        filepath = row['filepath']\n        spectrogram = load_img(IMG + filepath)\n\n        yield spectrogram, row['target']\n    \ndf.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"BATCH_SIZE = 128\n\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        \n    return torch.stack(X), torch.tensor(Y)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Model","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n\nprint(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)\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)\n        self.fc2 = nn.Linear(fn*2, fn)\n        self.fc3 = nn.Linear(fn, fn//2)\n        self.output = nn.Linear(fn//2, 264)\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        x = self.flatten(x)\n        x = F.relu(self.fc1(x))\n        x = F.relu(self.fc2(x))\n        x = F.relu(self.fc3(x))\n        x = self.output(x)\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":"## Train","execution_count":null},{"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_now = torch.eq(y_pred, y).sum()\n    return correct_now.item()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"EPOCHS = 40\nBEST_MODEL_PATH = 'best_model.pth'\n\nbest_loss = 1000000\npatience = 4\n\nfor epoch in range(EPOCHS):\n    #### Training\n    net.train()\n    gen = get_features(True)\n    steps = math.ceil(train_sz / BATCH_SIZE)\n    total_loss = 0\n    total_correct = 0\n    loop = tqdm(range(steps), total=steps)\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_now = get_number_of_correct_for_this_batch(y_pred, Y)\n            total_correct += correct_now\n\n            # Update stats\n            loop.update(1)\n            loop.set_description('Epoch {}/{}'.format(epoch + 1, EPOCHS))\n            loop.set_postfix(loss=loss.item(), acc=total_correct/((i+1) * BATCH_SIZE))\n    \n    \n    #### Validation\n    with torch.no_grad():\n        net.eval()\n        gen = get_features(False)\n        steps = math.ceil(val_sz / BATCH_SIZE)\n        total_loss = 0\n        total_correct = 0\n        loop = tqdm(range(steps), total=steps)\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_now = get_number_of_correct_for_this_batch(y_pred, Y)\n            total_correct += correct_now\n\n            loop.update(1)\n            loop.set_description('Epoch {}/{}'.format(epoch + 1, EPOCHS))\n            loop.set_postfix(loss=loss.item(), acc=total_correct/((i+1) * BATCH_SIZE))\n\n        # Early Stopping\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\"Early stopping at {epoch}\")\n            break","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Test","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"best = torch.load(BEST_MODEL_PATH)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import librosa\nimport cv2\n#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    # Stack X as [X,X,X]\n#     X = np.stack([X, X, X], axis=-1)\n\n    # Standardize\n    mean = mean or X.mean()\n    X = X - mean\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    if (_max - _min) > eps:\n        # Normalize 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.uint8)\n    else:\n        # Just zero\n        V = np.zeros_like(Xstd, dtype=np.uint8)\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    best.eval()\n    y_pred = best(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":{"trusted":true},"cell_type":"code","source":"TEST_FOLDER='../input/birdsong-recognition/test_audio/'\n\ntry:\n    preds = []\n    test = pd.read_csv(os.path.join(PATH, 'test.csv'))\n    for index, row in test.iterrows():\n        # Get test row information\n        site = row['site']\n        start_time = row['seconds'] - 5\n        row_id = row['row_id']\n        audio_id = row['audio_id']\n\n        # Get the test sound clip\n        if site == 'site_1' or site == 'site_2':\n            path = build_spectrogram(TEST_FOLDER + audio_id + '.mp3', start_time, 5)\n            y = load_img(path)\n        else:\n            path = build_spectrogram(TEST_FOLDER + audio_id + '.mp3', 0, duration=None)\n            y = load_img(path)\n\n        # Make the prediction\n        pred = make_prediction(y, le, model)\n\n        # Store prediction\n        preds.append([row_id, pred])\nexcept Exception as e:\n    preds = pd.read_csv('../input/birdsong-recognition/sample_submission.csv')\n    print('why', e)\n        \n# print(preds)\npreds = pd.DataFrame(preds, columns=['row_id', 'birds'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds.fillna('nocall', inplace=True)\npreds.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Thanks for reading this notebook! If you found this notebook helpful, please give it an upvote. 💪","execution_count":null}],"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}