{"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":"import os\nimport glob\nfrom pathlib import Path\nimport pandas as pd\nimport torch\nfrom fastprogress import progress_bar\nimport numpy as np\nimport warnings\nfrom collections import defaultdict\nfrom collections import Counter","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:53.288187Z","iopub.execute_input":"2021-06-01T17:17:53.288481Z","iopub.status.idle":"2021-06-01T17:17:54.540326Z","shell.execute_reply.started":"2021-06-01T17:17:53.288451Z","shell.execute_reply":"2021-06-01T17:17:54.539379Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"torch.manual_seed(42)\ntorch.backends.cudnn.deterministic = True\ntorch.backends.cudnn.benchmark = False\nnp.random.seed(42)","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:54.543302Z","iopub.execute_input":"2021-06-01T17:17:54.543718Z","iopub.status.idle":"2021-06-01T17:17:54.553368Z","shell.execute_reply.started":"2021-06-01T17:17:54.543676Z","shell.execute_reply":"2021-06-01T17:17:54.552202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 1. Prelim","metadata":{}},{"cell_type":"code","source":"ROOT = Path.cwd().parent\nINPUT_ROOT = ROOT / \"input\"\nRAW_DATA = INPUT_ROOT / \"birdclef-2021\"\n\nif(os.path.isdir(INPUT_ROOT / \"birdclef-2021\" / \"testsoundscapes\")):\n    TEST_AUDIO_DIR = INPUT_ROOT / \"birdclef-2021\" / \"testsoundscapes\"\n    test = pd.read_csv(INPUT_ROOT / \"birdclef-2021\" / \"test.csv\")\nelse:\n    TEST_AUDIO_DIR = INPUT_ROOT / \"birdclef-2021\" / \"train_soundscapes/\"\n    test = pd.read_csv(INPUT_ROOT / \"birdclef-2021\" / \"train_soundscape_labels.csv\")\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:54.556376Z","iopub.execute_input":"2021-06-01T17:17:54.556754Z","iopub.status.idle":"2021-06-01T17:17:54.650101Z","shell.execute_reply.started":"2021-06-01T17:17:54.556725Z","shell.execute_reply":"2021-06-01T17:17:54.648889Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"BIRD_CODE = {\n    'acafly': 0, 'acowoo': 1, 'aldfly': 2, 'ameavo': 3, 'amecro': 4, 'amegfi': 5,\n    'amekes': 6, 'amepip': 7, 'amered': 8, 'amerob': 9, 'amewig': 10,\n    'amtspa': 11, 'andsol1': 12, 'annhum': 13, 'astfly': 14, 'azaspi1': 15,\n    'babwar': 16, 'baleag': 17, 'balori': 18, 'banana': 19, 'banswa': 20,\n    'banwre1': 21, 'barant1': 22, 'barswa': 23, 'batpig1': 24, 'bawswa1': 25,\n    'bawwar': 26, 'baywre1': 27, 'bbwduc': 28, 'bcnher': 29, 'belkin1': 30,\n    'belvir': 31, 'bewwre': 32, 'bkbmag1': 33, 'bkbplo': 34, 'bkbwar': 35,\n    'bkcchi': 36, 'bkhgro': 37, 'bkmtou1': 38, 'bknsti': 39, 'blbgra1': 40,\n    'blbthr1': 41, 'blcjay1': 42, 'blctan1': 43, 'blhpar1': 44, 'blkpho': 45,\n    'blsspa1': 46, 'blugrb1': 47, 'blujay': 48, 'bncfly': 49, 'bnhcow': 50,\n    'bobfly1': 51, 'bongul': 52, 'botgra': 53, 'brbmot1': 54, 'brbsol1': 55,\n    'brcvir1': 56, 'brebla': 57, 'brncre': 58, 'brnjay': 59, 'brnthr': 60,\n    'brratt1': 61, 'brwhaw': 62, 'brwpar1': 63, 'btbwar': 64, 'btnwar': 65,\n    'btywar': 66, 'bucmot2': 67, 'buggna': 68, 'bugtan': 69, 'buhvir': 70,\n    'bulori': 71, 'burwar1': 72, 'bushti': 73, 'butsal1': 74, 'buwtea': 75,\n    'cacgoo1': 76, 'cacwre': 77, 'calqua': 78, 'caltow': 79, 'cangoo': 80,\n    'canwar': 81, 'carchi': 82, 'carwre': 83, 'casfin': 84, 'caskin': 85,\n    'caster1': 86, 'casvir': 87, 'categr': 88, 'ccbfin': 89, 'cedwax': 90,\n    'chbant1': 91, 'chbchi': 92, 'chbwre1': 93, 'chcant2': 94, 'chispa': 95,\n    'chswar': 96, 'cinfly2': 97, 'clanut': 98, 'clcrob': 99, 'cliswa': 100,\n    'cobtan1': 101, 'cocwoo1': 102, 'cogdov': 103, 'colcha1': 104, 'coltro1': 105,\n    'comgol': 106, 'comgra': 107, 'comloo': 108, 'commer': 109, 'compau': 110,\n    'compot1': 111, 'comrav': 112, 'comyel': 113, 'coohaw': 114, 'cotfly1': 115,\n    'cowscj1': 116, 'cregua1': 117, 'creoro1': 118, 'crfpar': 119, 'cubthr': 120,\n    'daejun': 121, 'dowwoo': 122, 'ducfly': 123, 'dusfly': 124, 'easblu': 125,\n    'easkin': 126, 'easmea': 127, 'easpho': 128, 'eastow': 129, 'eawpew': 130,\n    'eletro': 131, 'eucdov': 132, 'eursta': 133, 'fepowl': 134, 'fiespa': 135,\n    'flrtan1': 136, 'foxspa': 137, 'gadwal': 138, 'gamqua': 139, 'gartro1': 140,\n    'gbbgul': 141, 'gbwwre1': 142, 'gcrwar': 143, 'gilwoo': 144, 'gnttow': 145,\n    'gnwtea': 146, 'gocfly1': 147, 'gockin': 148, 'gocspa': 149, 'goftyr1': 150,\n    'gohque1': 151, 'goowoo1': 152, 'grasal1': 153, 'grbani': 154, 'grbher3': 155,\n    'grcfly': 156, 'greegr': 157, 'grekis': 158, 'grepew': 159, 'grethr1': 160,\n    'gretin1': 161, 'greyel': 162, 'grhcha1': 163, 'grhowl': 164, 'grnher': 165,\n    'grnjay': 166, 'grtgra': 167, 'grycat': 168, 'gryhaw2': 169, 'gwfgoo': 170,\n    'haiwoo': 171, 'heptan': 172, 'hergul': 173, 'herthr': 174, 'herwar': 175,\n    'higmot1': 176, 'hofwoo1': 177, 'houfin': 178, 'houspa': 179, 'houwre': 180,\n    'hutvir': 181, 'incdov': 182, 'indbun': 183, 'kebtou1': 184, 'killde': 185,\n    'labwoo': 186, 'larspa': 187, 'laufal1': 188, 'laugul': 189, 'lazbun': 190,\n    'leafly': 191, 'leasan': 192, 'lesgol': 193, 'lesgre1': 194, 'lesvio1': 195,\n    'linspa': 196, 'linwoo1': 197, 'littin1': 198, 'lobdow': 199, 'lobgna5': 200,\n    'logshr': 201, 'lotduc': 202, 'lotman1': 203, 'lucwar': 204, 'macwar': 205,\n    'magwar': 206, 'mallar3': 207, 'marwre': 208, 'mastro1': 209, 'meapar': 210,\n    'melbla1': 211, 'monoro1': 212, 'mouchi': 213, 'moudov': 214, 'mouela1': 215,\n    'mouqua': 216, 'mouwar': 217, 'mutswa': 218, 'naswar': 219, 'norcar': 220,\n    'norfli': 221, 'normoc': 222, 'norpar': 223, 'norsho': 224, 'norwat': 225,\n    'nrwswa': 226, 'nutwoo': 227, 'oaktit': 228, 'obnthr1': 229, 'ocbfly1': 230,\n    'oliwoo1': 231, 'olsfly': 232, 'orbeup1': 233, 'orbspa1': 234, 'orcpar': 235,\n    'orcwar': 236, 'orfpar': 237, 'osprey': 238, 'ovenbi1': 239, 'pabspi1': 240,\n    'paltan1': 241, 'palwar': 242, 'pasfly': 243, 'pavpig2': 244, 'phivir': 245,\n    'pibgre': 246, 'pilwoo': 247, 'pinsis': 248, 'pirfly1': 249, 'plawre1': 250,\n    'plaxen1': 251, 'plsvir': 252, 'plupig2': 253, 'prowar': 254, 'purfin': 255,\n    'purgal2': 256, 'putfru1': 257, 'pygnut': 258, 'rawwre1': 259, 'rcatan1': 260,\n    'rebnut': 261, 'rebsap': 262, 'rebwoo': 263, 'redcro': 264, 'reevir1': 265,\n    'rehbar1': 266, 'relpar': 267, 'reshaw': 268, 'rethaw': 269, 'rewbla': 270,\n    'ribgul': 271, 'rinkin1': 272, 'roahaw': 273, 'robgro': 274, 'rocpig': 275,\n    'rotbec': 276, 'royter1': 277, 'rthhum': 278, 'rtlhum': 279, 'ruboro1': 280,\n    'rubpep1': 281, 'rubrob': 282, 'rubwre1': 283, 'ruckin': 284, 'rucspa1': 285,\n    'rucwar': 286, 'rucwar1': 287, 'rudpig': 288, 'rudtur': 289, 'rufhum': 290,\n    'rugdov': 291, 'rumfly1': 292, 'runwre1': 293, 'rutjac1': 294, 'saffin': 295,\n    'sancra': 296, 'sander': 297, 'savspa': 298, 'saypho': 299, 'scamac1': 300,\n    'scatan': 301, 'scbwre1': 302, 'scptyr1': 303, 'scrtan1': 304, 'semplo': 305,\n    'shicow': 306, 'sibtan2': 307, 'sinwre1': 308, 'sltred': 309, 'smbani': 310,\n    'snogoo': 311, 'sobtyr1': 312, 'socfly1': 313, 'solsan': 314, 'sonspa': 315,\n    'soulap1': 316, 'sposan': 317, 'spotow': 318, 'spvear1': 319, 'squcuc1': 320,\n    'stbori': 321, 'stejay': 322, 'sthant1': 323, 'sthwoo1': 324, 'strcuc1': 325,\n    'strfly1': 326, 'strsal1': 327, 'stvhum2': 328, 'subfly': 329, 'sumtan': 330,\n    'swaspa': 331, 'swathr': 332, 'tenwar': 333, 'thbeup1': 334, 'thbkin': 335,\n    'thswar1': 336, 'towsol': 337, 'treswa': 338, 'trogna1': 339, 'trokin': 340,\n    'tromoc': 341, 'tropar': 342, 'tropew1': 343, 'tuftit': 344, 'tunswa': 345,\n    'veery': 346, 'verdin': 347, 'vigswa': 348, 'warvir': 349, 'wbwwre1': 350,\n    'webwoo1': 351, 'wegspa1': 352, 'wesant1': 353, 'wesblu': 354, 'weskin': 355,\n    'wesmea': 356, 'westan': 357, 'wewpew': 358, 'whbman1': 359, 'whbnut': 360,\n    'whcpar': 361, 'whcsee1': 362, 'whcspa': 363, 'whevir': 364, 'whfpar1': 365,\n    'whimbr': 366, 'whiwre1': 367, 'whtdov': 368, 'whtspa': 369, 'whwbec1': 370,\n    'whwdov': 371, 'wilfly': 372, 'willet1': 373, 'wilsni1': 374, 'wiltur': 375,\n    'wlswar': 376, 'wooduc': 377, 'woothr': 378, 'wrenti': 379, 'y00475': 380,\n    'yebcha': 381, 'yebela1': 382, 'yebfly': 383, 'yebori1': 384, 'yebsap': 385,\n    'yebsee1': 386, 'yefgra1': 387, 'yegvir': 388, 'yehbla': 389, 'yehcar1': 390,\n    'yelgro': 391, 'yelwar': 392, 'yeofly1': 393, 'yerwar': 394, 'yeteup1': 395,\n    'yetvir': 396\n}\n\nINV_BIRD_CODE = {v: k for k, v in BIRD_CODE.items()}","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:54.651763Z","iopub.execute_input":"2021-06-01T17:17:54.652191Z","iopub.status.idle":"2021-06-01T17:17:54.706800Z","shell.execute_reply.started":"2021-06-01T17:17:54.652149Z","shell.execute_reply":"2021-06-01T17:17:54.705711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 2. Models","metadata":{}},{"cell_type":"markdown","source":"## 2.1 Audio Utils","metadata":{}},{"cell_type":"code","source":"'''\nISC License\nCopyright (c) 2013--2017, librosa development team.\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n'''\n\nimport torch.nn as nn\nimport numpy as np\nimport torch\nimport librosa\nimport torch.nn.functional as F\nclass DFTBase(nn.Module):\n    def __init__(self):\n        \"\"\"Base class for DFT and IDFT matrix\"\"\"\n        super(DFTBase, self).__init__()\n\n    def dft_matrix(self, n):\n        (x, y) = np.meshgrid(np.arange(n), np.arange(n))\n        omega = np.exp(-2 * np.pi * 1j / n)\n        W = np.power(omega, x * y)\n        return W\n\n    def idft_matrix(self, n):\n        (x, y) = np.meshgrid(np.arange(n), np.arange(n))\n        omega = np.exp(2 * np.pi * 1j / n)\n        W = np.power(omega, x * y)\n        return W\n    \n    \nclass STFT(DFTBase):\n    def __init__(self, n_fft=2048, hop_length=None, win_length=None, \n        window='hann', center=True, pad_mode='reflect', freeze_parameters=True):\n        \"\"\"Implementation of STFT with Conv1d. The function has the same output \n        of librosa.core.stft\n        \"\"\"\n        super(STFT, self).__init__()\n\n        assert pad_mode in ['constant', 'reflect']\n\n        self.n_fft = n_fft\n        self.center = center\n        self.pad_mode = pad_mode\n\n        # By default, use the entire frame\n        if win_length is None:\n            win_length = n_fft\n\n        # Set the default hop, if it's not already specified\n        if hop_length is None:\n            hop_length = int(win_length // 4)\n\n        fft_window = librosa.filters.get_window(window, win_length, fftbins=True)\n\n        # Pad the window out to n_fft size\n        fft_window = librosa.util.pad_center(fft_window, n_fft)\n\n        # DFT & IDFT matrix\n        self.W = self.dft_matrix(n_fft)\n\n        out_channels = n_fft // 2 + 1\n\n        self.conv_real = nn.Conv1d(in_channels=1, out_channels=out_channels, \n            kernel_size=n_fft, stride=hop_length, padding=0, dilation=1, \n            groups=1, bias=False)\n\n        self.conv_imag = nn.Conv1d(in_channels=1, out_channels=out_channels, \n            kernel_size=n_fft, stride=hop_length, padding=0, dilation=1, \n            groups=1, bias=False)\n\n        self.conv_real.weight.data = torch.Tensor(\n            np.real(self.W[:, 0 : out_channels] * fft_window[:, None]).T)[:, None, :]\n        # (n_fft // 2 + 1, 1, n_fft)\n\n        self.conv_imag.weight.data = torch.Tensor(\n            np.imag(self.W[:, 0 : out_channels] * fft_window[:, None]).T)[:, None, :]\n        # (n_fft // 2 + 1, 1, n_fft)\n\n        if freeze_parameters:\n            for param in self.parameters():\n                param.requires_grad = False\n\n    def forward(self, input):\n        \"\"\"input: (batch_size, data_length)\n        Returns:\n          real: (batch_size, n_fft // 2 + 1, time_steps)\n          imag: (batch_size, n_fft // 2 + 1, time_steps)\n        \"\"\"\n\n        x = input[:, None, :]   # (batch_size, channels_num, data_length)\n\n        if self.center:\n            x = F.pad(x, pad=(self.n_fft // 2, self.n_fft // 2), mode=self.pad_mode)\n\n        real = self.conv_real(x)\n        imag = self.conv_imag(x)\n        # (batch_size, n_fft // 2 + 1, time_steps)\n\n        real = real[:, None, :, :].transpose(2, 3)\n        imag = imag[:, None, :, :].transpose(2, 3)\n        # (batch_size, 1, time_steps, n_fft // 2 + 1)\n\n        return real, imag\n    \n    \nclass Spectrogram(nn.Module):\n    def __init__(self, n_fft=2048, hop_length=None, win_length=None, \n        window='hann', center=True, pad_mode='reflect', power=2.0, \n        freeze_parameters=True):\n        \"\"\"Calculate spectrogram using pytorch. The STFT is implemented with \n        Conv1d. The function has the same output of librosa.core.stft\n        \"\"\"\n        super(Spectrogram, self).__init__()\n\n        self.power = power\n\n        self.stft = STFT(n_fft=n_fft, hop_length=hop_length, \n            win_length=win_length, window=window, center=center, \n            pad_mode=pad_mode, freeze_parameters=True)\n\n    def forward(self, input):\n        \"\"\"input: (batch_size, 1, time_steps, n_fft // 2 + 1)\n        Returns:\n          spectrogram: (batch_size, 1, time_steps, n_fft // 2 + 1)\n        \"\"\"\n\n        (real, imag) = self.stft.forward(input)\n        # (batch_size, n_fft // 2 + 1, time_steps)\n\n        spectrogram = real ** 2 + imag ** 2\n\n        if self.power == 2.0:\n            pass\n        else:\n            spectrogram = spectrogram ** (power / 2.0)\n\n        return spectrogram\n\n    \nclass LogmelFilterBank(nn.Module):\n    def __init__(self, sr=32000, n_fft=2048, n_mels=64, fmin=50, fmax=14000, is_log=True, \n        ref=1.0, amin=1e-10, top_db=80.0, freeze_parameters=True):\n        \"\"\"Calculate logmel spectrogram using pytorch. The mel filter bank is \n        the pytorch implementation of as librosa.filters.mel \n        \"\"\"\n        super(LogmelFilterBank, self).__init__()\n\n        self.is_log = is_log\n        self.ref = ref\n        self.amin = amin\n        self.top_db = top_db\n\n        self.melW = librosa.filters.mel(sr=sr, n_fft=n_fft, n_mels=n_mels,\n            fmin=fmin, fmax=fmax).T\n        # (n_fft // 2 + 1, mel_bins)\n\n        self.melW = nn.Parameter(torch.Tensor(self.melW))\n\n        if freeze_parameters:\n            for param in self.parameters():\n                param.requires_grad = False\n\n    def forward(self, input):\n        \"\"\"input: (batch_size, channels, time_steps)\n        \n        Output: (batch_size, time_steps, mel_bins)\n        \"\"\"\n\n        # Mel spectrogram\n        mel_spectrogram = torch.matmul(input, self.melW)\n\n        # Logmel spectrogram\n        if self.is_log:\n            output = self.power_to_db(mel_spectrogram)\n        else:\n            output = mel_spectrogram\n\n        return output\n\n\n    def power_to_db(self, input):\n        \"\"\"Power to db, this function is the pytorch implementation of \n        librosa.core.power_to_lb\n        \"\"\"\n        ref_value = self.ref\n        log_spec = 10.0 * torch.log10(torch.clamp(input, min=self.amin, max=np.inf))\n        log_spec -= 10.0 * np.log10(np.maximum(self.amin, ref_value))\n\n        if self.top_db is not None:\n            if self.top_db < 0:\n                raise ParameterError('top_db must be non-negative')\n            log_spec = torch.clamp(log_spec, min=log_spec.max().item() - self.top_db, max=np.inf)\n\n        return log_spec\n\n\nclass DropStripes(nn.Module):\n    def __init__(self, dim, drop_width, stripes_num):\n        \"\"\"Drop stripes. \n        Args:\n          dim: int, dimension along which to drop\n          drop_width: int, maximum width of stripes to drop\n          stripes_num: int, how many stripes to drop\n        \"\"\"\n        super(DropStripes, self).__init__()\n\n        assert dim in [2, 3]    # dim 2: time; dim 3: frequency\n\n        self.dim = dim\n        self.drop_width = drop_width\n        self.stripes_num = stripes_num\n\n    def forward(self, input):\n        \"\"\"input: (batch_size, channels, time_steps, freq_bins)\"\"\"\n\n        assert input.ndimension() == 4\n\n        if self.training is False:\n            return input\n\n        else:\n            batch_size = input.shape[0]\n            total_width = input.shape[self.dim]\n\n            for n in range(batch_size):\n                self.transform_slice(input[n], total_width)\n\n            return input\n\n\n    def transform_slice(self, e, total_width):\n        \"\"\"e: (channels, time_steps, freq_bins)\"\"\"\n\n        for _ in range(self.stripes_num):\n            distance = torch.randint(low=0, high=self.drop_width, size=(1,))[0]\n            bgn = torch.randint(low=0, high=total_width - distance, size=(1,))[0]\n\n            if self.dim == 2:\n                e[:, bgn : bgn + distance, :] = 0\n            elif self.dim == 3:\n                e[:, :, bgn : bgn + distance] = 0\n\n\nclass SpecAugmentation(nn.Module):\n    def __init__(self, time_drop_width, time_stripes_num, freq_drop_width, \n        freq_stripes_num):\n        \"\"\"Spec augmetation. \n        [ref] Park, D.S., Chan, W., Zhang, Y., Chiu, C.C., Zoph, B., Cubuk, E.D. \n        and Le, Q.V., 2019. Specaugment: A simple data augmentation method \n        for automatic speech recognition. arXiv preprint arXiv:1904.08779.\n        Args:\n          time_drop_width: int\n          time_stripes_num: int\n          freq_drop_width: int\n          freq_stripes_num: int\n        \"\"\"\n\n        super(SpecAugmentation, self).__init__()\n\n        self.time_dropper = DropStripes(dim=2, drop_width=time_drop_width, \n            stripes_num=time_stripes_num)\n\n        self.freq_dropper = DropStripes(dim=3, drop_width=freq_drop_width, \n            stripes_num=freq_stripes_num)\n\n    def forward(self, input):\n        x = self.time_dropper(input)\n        x = self.freq_dropper(x)\n        return x","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:54.712219Z","iopub.execute_input":"2021-06-01T17:17:54.712592Z","iopub.status.idle":"2021-06-01T17:17:56.831897Z","shell.execute_reply.started":"2021-06-01T17:17:54.712563Z","shell.execute_reply":"2021-06-01T17:17:56.830588Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2.2 PANN Models","metadata":{}},{"cell_type":"code","source":"'''\nThe MIT License\n  \nCopyright (c) 2018-2020 Qiuqiang Kong\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n'''\n\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision.models as models\n\ndef init_layer(layer):\n    nn.init.xavier_uniform_(layer.weight)\n\n    if hasattr(layer, \"bias\"):\n        if layer.bias is not None:\n            layer.bias.data.fill_(0.)\n\n\ndef init_bn(bn):\n    bn.bias.data.fill_(0.)\n    bn.weight.data.fill_(1.0)\n\n\ndef interpolate(x: torch.Tensor, ratio: int):\n    \"\"\"Interpolate data in time domain. This is used to compensate the\n    resolution reduction in downsampling of a CNN.\n\n    Args:\n      x: (batch_size, time_steps, classes_num)\n      ratio: int, ratio to interpolate\n    Returns:\n      upsampled: (batch_size, time_steps * ratio, classes_num)\n    \"\"\"\n    (batch_size, time_steps, classes_num) = x.shape\n    upsampled = x[:, :, None, :].repeat(1, 1, ratio, 1)\n    upsampled = upsampled.reshape(batch_size, time_steps * ratio, classes_num)\n    return upsampled\n\n\ndef pad_framewise_output(framewise_output: torch.Tensor, frames_num: int):\n    \"\"\"Pad framewise_output to the same length as input frames. The pad value\n    is the same as the value of the last frame.\n    Args:\n      framewise_output: (batch_size, frames_num, classes_num)\n      frames_num: int, number of frames to pad\n    Outputs:\n      output: (batch_size, frames_num, classes_num)\n    \"\"\"\n    pad = framewise_output[:, -1:, :].repeat(\n        1, frames_num - framewise_output.shape[1], 1)\n    \"\"\"tensor for padding\"\"\"\n\n    output = torch.cat((framewise_output, pad), dim=1)\n    \"\"\"(batch_size, frames_num, classes_num)\"\"\"\n\n    return output\n\n\nclass ConvBlock(nn.Module):\n    def __init__(self, in_channels: int, out_channels: int):\n        super().__init__()\n\n        self.conv1 = nn.Conv2d(\n            in_channels=in_channels,\n            out_channels=out_channels,\n            kernel_size=(3, 3),\n            stride=(1, 1),\n            padding=(1, 1),\n            bias=False)\n\n        self.conv2 = nn.Conv2d(\n            in_channels=out_channels,\n            out_channels=out_channels,\n            kernel_size=(3, 3),\n            stride=(1, 1),\n            padding=(1, 1),\n            bias=False)\n\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n\n        self.init_weight()\n\n    def init_weight(self):\n        init_layer(self.conv1)\n        init_layer(self.conv2)\n        init_bn(self.bn1)\n        init_bn(self.bn2)\n\n    def forward(self, input, pool_size=(2, 2), pool_type='avg'):\n\n        x = input\n        x = F.relu_(self.bn1(self.conv1(x)))\n        x = F.relu_(self.bn2(self.conv2(x)))\n        if pool_type == 'max':\n            x = F.max_pool2d(x, kernel_size=pool_size)\n        elif pool_type == 'avg':\n            x = F.avg_pool2d(x, kernel_size=pool_size)\n        elif pool_type == 'avg+max':\n            x1 = F.avg_pool2d(x, kernel_size=pool_size)\n            x2 = F.max_pool2d(x, kernel_size=pool_size)\n            x = x1 + x2\n        else:\n            raise Exception('Incorrect argument!')\n\n        return x\n\n\nclass AttBlock(nn.Module):\n    def __init__(self,\n                 in_features: int,\n                 out_features: int,\n                 activation=\"linear\",\n                 temperature=1.0):\n        super().__init__()\n\n        self.activation = activation\n        self.temperature = temperature\n        self.att = nn.Conv1d(\n            in_channels=in_features,\n            out_channels=out_features,\n            kernel_size=1,\n            stride=1,\n            padding=0,\n            bias=True)\n        self.cla = nn.Conv1d(\n            in_channels=in_features,\n            out_channels=out_features,\n            kernel_size=1,\n            stride=1,\n            padding=0,\n            bias=True)\n\n        self.bn_att = nn.BatchNorm1d(out_features)\n        self.init_weights()\n\n    def init_weights(self):\n        init_layer(self.att)\n        init_layer(self.cla)\n        init_bn(self.bn_att)\n\n    def forward(self, x):\n        # x: (n_samples, n_in, n_time)\n        norm_att = torch.softmax(torch.tanh(self.att(x)), dim=-1)\n        cla = self.nonlinear_transform(self.cla(x))\n        x = torch.sum(norm_att * cla, dim=2)\n        return x, norm_att, cla\n\n    def nonlinear_transform(self, x):\n        if self.activation == 'linear':\n            return x\n        elif self.activation == 'sigmoid':\n            return torch.sigmoid(x)\n        \nclass PANNsDense121Att(nn.Module):\n    def __init__(self, sample_rate: int, window_size: int, hop_size: int,\n                 mel_bins: int, fmin: int, fmax: int, classes_num: int, apply_aug: bool, top_db=None):\n        super().__init__()\n        \n        window = 'hann'\n        center = True\n        pad_mode = 'reflect'\n        ref = 1.0\n        amin = 1e-10\n        self.interpolate_ratio = 32  # Downsampled ratio\n        self.apply_aug = apply_aug\n\n        # Spectrogram extractor\n        self.spectrogram_extractor = Spectrogram(\n            n_fft=window_size,\n            hop_length=hop_size,\n            win_length=window_size,\n            window=window,\n            center=center,\n            pad_mode=pad_mode,\n            freeze_parameters=True)\n\n        # Logmel feature extractor\n        self.logmel_extractor = LogmelFilterBank(\n            sr=sample_rate,\n            n_fft=window_size,\n            n_mels=mel_bins,\n            fmin=fmin,\n            fmax=fmax,\n            ref=ref,\n            amin=amin,\n            top_db=top_db,\n            freeze_parameters=True)\n\n        # Spec augmenter\n        self.spec_augmenter = SpecAugmentation(\n            time_drop_width=64,\n            time_stripes_num=2,\n            freq_drop_width=8,\n            freq_stripes_num=2)\n\n        self.bn0 = nn.BatchNorm2d(mel_bins)\n\n        self.fc1 = nn.Linear(1024, 1024, bias=True)\n        self.att_block = AttBlock(1024, classes_num, activation='sigmoid')\n\n\n        self.densenet_features = models.densenet121(pretrained=False).features\n\n        self.init_weight()\n\n    def init_weight(self):\n        init_bn(self.bn0)\n        init_layer(self.fc1)\n        \n    def cnn_feature_extractor(self, x):\n        x = self.densenet_features(x)\n        return x\n    \n    def preprocess(self, input_x, mixup_lambda=None):\n\n        x = self.spectrogram_extractor(input_x)  # (batch_size, 1, time_steps, freq_bins)\n        x = self.logmel_extractor(x)  # (batch_size, 1, time_steps, mel_bins)\n\n        frames_num = x.shape[2]\n\n        x = x.transpose(1, 3)\n        x = self.bn0(x)\n        x = x.transpose(1, 3)\n\n        if self.apply_aug:\n            x = self.spec_augmenter(x)\n\n        return x, frames_num\n        \n\n    def forward(self, input_data):\n        input_x, mixup_lambda = input_data\n        \"\"\"\n        Input: (batch_size, data_length)\"\"\"\n        b, c, s = input_x.shape\n        input_x = input_x.reshape(b*c, s)\n        x, frames_num = self.preprocess(input_x, mixup_lambda=mixup_lambda)\n        if mixup_lambda is not None:\n            b = (b*c)//2\n            c = 1\n        # Output shape (batch size, channels, time, frequency)\n        x = x.expand(x.shape[0], 3, x.shape[2], x.shape[3])\n        x = self.cnn_feature_extractor(x)\n        \n        # Aggregate in frequency axis\n        x = torch.mean(x, dim=3)\n\n        x1 = F.max_pool1d(x, kernel_size=3, stride=1, padding=1)\n        x2 = F.avg_pool1d(x, kernel_size=3, stride=1, padding=1)\n        x = x1 + x2\n\n        x = F.dropout(x, p=0.5, training=self.training)\n        x = x.transpose(1, 2)\n        x = F.relu_(self.fc1(x))\n        x = x.transpose(1, 2)\n        x = F.dropout(x, p=0.5, training=self.training)\n\n        (clipwise_output, norm_att, segmentwise_output) = self.att_block(x)\n        segmentwise_output = segmentwise_output.transpose(1, 2)\n\n        # Get framewise output\n        framewise_output = interpolate(segmentwise_output,\n                                       self.interpolate_ratio)\n        framewise_output = pad_framewise_output(framewise_output, frames_num)\n        frame_shape =  framewise_output.shape\n        clip_shape = clipwise_output.shape\n        output_dict = {\n            'framewise_output': framewise_output.reshape(b, c, frame_shape[1],frame_shape[2]),\n            'clipwise_output': clipwise_output.reshape(b, c, clip_shape[1]),\n        }\n\n        return output_dict","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:56.836508Z","iopub.execute_input":"2021-06-01T17:17:56.836893Z","iopub.status.idle":"2021-06-01T17:17:57.375885Z","shell.execute_reply.started":"2021-06-01T17:17:56.836844Z","shell.execute_reply":"2021-06-01T17:17:57.375035Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2.3 Model Utils","metadata":{}},{"cell_type":"code","source":"def get_model(ModelClass: object, config: dict, weights_path: str):\n    model = ModelClass(**config)\n    checkpoint = torch.load(weights_path, map_location='cpu')\n    model.load_state_dict(checkpoint[\"model\"])\n    model.to(device)\n    model.eval()\n    return model","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:57.377460Z","iopub.execute_input":"2021-06-01T17:17:57.377876Z","iopub.status.idle":"2021-06-01T17:17:57.387581Z","shell.execute_reply.started":"2021-06-01T17:17:57.377819Z","shell.execute_reply":"2021-06-01T17:17:57.385462Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 3. Model Parameters","metadata":{}},{"cell_type":"code","source":"list_of_models = [\n    {\n        \"model_class\": PANNsDense121Att,\n        \"config\": {\n            \"sample_rate\": 32000,\n            \"window_size\": 1024,\n            \"hop_size\": 320,\n            \"mel_bins\": 64,\n            \"fmin\": 50,\n            \"fmax\": 14000,\n            \"classes_num\": len(BIRD_CODE),\n            \"apply_aug\": True,\n            \"top_db\": None\n        },\n        \"weights_path\": \"../input/birdclef-2021-training/saved_models/example_config/example_config_checkpoint_2_score=0.0107.pt\",\n        \"clip_threshold\": 0.1, # 0.3,\n        \"threshold\": 0.1 #0.3\n    }\n]\nPERIOD = 30\nSR = 32000\n# how many models are neccessary for a successful detection\nvote_lim = 1 # 4\nTTA = 10","metadata":{"execution":{"iopub.status.busy":"2021-06-01T17:17:57.390499Z","iopub.execute_input":"2021-06-01T17:17:57.391340Z","iopub.status.idle":"2021-06-01T17:17:57.400535Z","shell.execute_reply.started":"2021-06-01T17:17:57.391269Z","shell.execute_reply":"2021-06-01T17:17:57.398923Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for lm in list_of_models:\n    lm[\"model\"] = get_model(lm[\"model_class\"], lm[\"config\"], lm[\"weights_path\"])","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:17:57.402931Z","iopub.execute_input":"2021-06-01T17:17:57.403578Z","iopub.status.idle":"2021-06-01T17:18:05.160200Z","shell.execute_reply.started":"2021-06-01T17:17:57.403532Z","shell.execute_reply":"2021-06-01T17:18:05.159230Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 4. Predictions","metadata":{}},{"cell_type":"code","source":"def prediction_for_clip(test_df: pd.DataFrame,\n                        clip: np.ndarray, \n                        model,\n                        threshold,\n                       clip_threshold):\n\n    audios = []\n    y = clip.astype(np.float32)\n    len_y = len(y)\n    start = 0\n    end = PERIOD * SR\n    while True:\n        y_batch = y[start:end].astype(np.float32)\n        if len(y_batch) != PERIOD * SR:\n            y_pad = np.zeros(PERIOD * SR, dtype=np.float32)\n            y_pad[:len(y_batch)] = y_batch\n            audios.append(y_pad)\n            break\n        start = end\n        end += PERIOD * SR\n        audios.append(y_batch)\n        \n    array = np.asarray(audios)\n    tensors = torch.from_numpy(array)\n    \n    model.eval()\n    estimated_event_list = []\n    global_time = 0.0\n    site = test_df[\"site\"].values[0]\n    audio_id = test_df[\"audio_id\"].values[0]\n    for image in tensors:\n        image = image.unsqueeze(0).unsqueeze(0)\n        image = image.expand(image.shape[0], TTA, image.shape[2])\n        image = image.to(device)\n        \n        with torch.no_grad():\n            prediction = model((image, None))\n            framewise_outputs = prediction[\"framewise_output\"].detach(\n                ).cpu().numpy()[0].mean(axis=0)\n            clipwise_outputs = prediction[\"clipwise_output\"].detach(\n                ).cpu().numpy()[0].mean(axis=0)\n                \n        thresholded = framewise_outputs >= threshold\n        \n        clip_thresholded = clipwise_outputs >= clip_threshold\n        clip_indices = np.argwhere(clip_thresholded).reshape(-1)\n        clip_codes = []\n        for ci in clip_indices:\n            clip_codes.append(INV_BIRD_CODE[ci])\n            \n        for target_idx in range(thresholded.shape[1]):\n            if thresholded[:, target_idx].mean() == 0:\n                pass\n            else:\n                detected = np.argwhere(thresholded[:, target_idx]).reshape(-1)\n                head_idx = 0\n                tail_idx = 0\n                while True:\n                    if (tail_idx + 1 == len(detected)) or (\n                            detected[tail_idx + 1] - \n                            detected[tail_idx] != 1):\n                        onset = 0.01 * detected[\n                            head_idx] + global_time\n                        offset = 0.01 * detected[\n                            tail_idx] + global_time\n                        onset_idx = detected[head_idx]\n                        offset_idx = detected[tail_idx]\n                        max_confidence = framewise_outputs[\n                            onset_idx:offset_idx, target_idx].max()\n                        mean_confidence = framewise_outputs[\n                            onset_idx:offset_idx, target_idx].mean()\n                        if INV_BIRD_CODE[target_idx] in clip_codes:\n                            estimated_event = {\n                                \"site\": site,\n                                \"audio_id\": audio_id,\n                                \"primary_label\": INV_BIRD_CODE[target_idx],\n                                \"clip_codes\": clip_codes,\n                                \"onset\": onset,\n                                \"offset\": offset,\n                                \"max_confidence\": max_confidence,\n                                \"mean_confidence\": mean_confidence\n                            }\n                            estimated_event_list.append(estimated_event)\n                        head_idx = tail_idx + 1\n                        tail_idx = tail_idx + 1\n                        if head_idx >= len(detected):\n                            break\n                    else:\n                        tail_idx += 1\n        global_time += PERIOD\n        \n    prediction_df = pd.DataFrame(estimated_event_list)\n    return prediction_df\n\ndef prediction(test_df: pd.DataFrame,\n               test_audio: Path,\n               list_of_model_details):\n    test_df['audio_site_id'] = test_df['audio_id'].map(str) + '_' + test_df['site'].map(str)\n    unique_audio_site_id = test_df.audio_site_id.unique()\n\n    warnings.filterwarnings(\"ignore\")\n    prediction_dfs_dict = defaultdict(list)\n    for audio_site_id in progress_bar(unique_audio_site_id):\n        search_pattern = os.path.join(test_audio, audio_site_id + '_*.ogg')\n        audio_id = int(audio_site_id.split('_')[0])\n        audio_filenames = glob.glob(search_pattern)\n        if(len(audio_filenames) == 0):\n            continue\n        \n        audio_filename = audio_filenames[0]\n        clip, _ = librosa.load(audio_filename, sr=SR, mono=True, res_type=\"kaiser_fast\")\n        \n        test_df_for_audio_id = test_df.query(f\"audio_id == '{audio_id}'\").reset_index(drop=True)\n        for i, model_details in enumerate(list_of_model_details):\n            prediction_df = prediction_for_clip(test_df_for_audio_id,\n                                                clip=clip,\n                                                model=model_details[\"model\"],\n                                                threshold=model_details[\"threshold\"],\n                                                clip_threshold=model_details[\"clip_threshold\"]\n                                               )\n            prediction_dfs_dict[i].append(prediction_df)\n    list_of_prediction_df = []\n    for key, prediction_dfs in prediction_dfs_dict.items():\n        prediction_df = pd.concat(prediction_dfs, axis=0, sort=False).reset_index(drop=True)\n        list_of_prediction_df.append(prediction_df)\n    return list_of_prediction_df\n","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:18:05.164362Z","iopub.execute_input":"2021-06-01T17:18:05.164701Z","iopub.status.idle":"2021-06-01T17:18:05.200663Z","shell.execute_reply.started":"2021-06-01T17:18:05.164655Z","shell.execute_reply":"2021-06-01T17:18:05.199452Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"list_of_prediction_df = prediction(test_df=test,\n                           test_audio=TEST_AUDIO_DIR,\n                           list_of_model_details=list_of_models)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2021-06-01T17:18:05.204350Z","iopub.execute_input":"2021-06-01T17:18:05.204762Z","iopub.status.idle":"2021-06-01T17:19:38.087301Z","shell.execute_reply.started":"2021-06-01T17:18:05.204733Z","shell.execute_reply":"2021-06-01T17:19:38.086279Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 5. Post Process","metadata":{}},{"cell_type":"code","source":"def get_post_post_process_predictions(prediction_df):\n    labels = {}\n    for audio_id, sub_df in progress_bar(prediction_df.groupby(\"audio_id\")):\n        events = sub_df[[\"primary_label\", \"onset\", \"offset\", \"max_confidence\", \"site\"]].values\n        n_events = len(events)\n\n        site = events[0][4]\n        for i in range(n_events):\n            event = events[i][0]\n            onset = events[i][1]\n            offset = events[i][2]\n            \n            start_section = int((onset // 5) * 5) + 5\n            end_section = int((offset // 5) * 5) + 5\n            cur_section = start_section\n\n            row_id = f\"{audio_id}_{site}_{start_section}\" #  f\"{site}_{audio_id}_{start_section}\"\n            if labels.get(row_id) is not None:\n                labels[row_id].add(event)\n            else:\n                labels[row_id] = set()\n                labels[row_id].add(event)\n\n            while cur_section != end_section:\n                cur_section += 5\n                row_id = f\"{audio_id}_{site}_{cur_section}\" # f\"{site}_{audio_id}_{cur_section}\"\n                if labels.get(row_id) is not None:\n                    labels[row_id].add(event)\n                else:\n                    labels[row_id] = set()\n                    labels[row_id].add(event)\n\n\n    for key in labels:\n        labels[key] = \" \".join(sorted(list(labels[key])))\n\n\n    row_ids = list(labels.keys())\n    birds = list(labels.values())\n    post_processed = pd.DataFrame({\n        \"row_id\": row_ids,\n        \"birds\": birds\n    })\n    return post_processed","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-06-01T17:19:38.089157Z","iopub.execute_input":"2021-06-01T17:19:38.089555Z","iopub.status.idle":"2021-06-01T17:19:38.106416Z","shell.execute_reply.started":"2021-06-01T17:19:38.089512Z","shell.execute_reply":"2021-06-01T17:19:38.104943Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"all_row_id = test[[\"row_id\"]]\nlist_of_submissions = []\nfor prediction_df in list_of_prediction_df:\n    post_processed = get_post_post_process_predictions(prediction_df)\n    submission = post_processed.fillna(\"nocall\")\n    submission = submission.set_index('row_id')\n    list_of_submissions.append(submission)\n\nlist_of_submissions","metadata":{"execution":{"iopub.status.busy":"2021-06-01T17:19:38.107994Z","iopub.execute_input":"2021-06-01T17:19:38.108537Z","iopub.status.idle":"2021-06-01T17:19:38.163504Z","shell.execute_reply.started":"2021-06-01T17:19:38.108487Z","shell.execute_reply":"2021-06-01T17:19:38.162185Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"list_all_of_row_ids = []\nfor sub_x in list_of_submissions:\n    list_all_of_row_ids+= list(sub_x.index.values)\nlist_all_of_row_ids = list(set(list_all_of_row_ids))","metadata":{"execution":{"iopub.status.busy":"2021-06-01T17:19:38.165681Z","iopub.execute_input":"2021-06-01T17:19:38.166104Z","iopub.status.idle":"2021-06-01T17:19:38.172537Z","shell.execute_reply.started":"2021-06-01T17:19:38.166064Z","shell.execute_reply":"2021-06-01T17:19:38.171011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 6. Ensemble","metadata":{}},{"cell_type":"code","source":"final_submission = []\nfor row_id in list_all_of_row_ids:\n    birds = []\n    for sub in list_of_submissions:\n        if row_id in sub.index:\n            birds.extend(sub.loc[row_id].birds.split(\" \"))\n    birds = [x for x in birds if \"nocall\" != x and \"\" != x]\n    count_birds = Counter(birds)\n    final_birds = []\n    for key, value in count_birds.items():\n        if value >= vote_lim:\n            final_birds.append(key)\n    if len(final_birds)>0:\n        row_data = {\n            \"row_id\": row_id,\n            \"birds\": \" \".join(sorted(final_birds))\n        }\n    else:\n        row_data = {\n            \"row_id\": row_id,\n            \"birds\": \"nocall\"\n        }\n    final_submission.append(row_data)\n\nsite_3_data = defaultdict(list)\nfor row in final_submission:\n    if \"site_3\" in row[\"row_id\"]:\n        final_row_id = \"_\".join(row[\"row_id\"].split(\"_\")[0:-1])\n        birds = row[\"birds\"].split(\" \")\n        birds = [x for x in birds if \"nocall\" != x and \"\" != x]\n        site_3_data[final_row_id].extend(birds)\n        \nfor key, value in site_3_data.items():\n    count_birds = Counter(value)\n    final_birds = []\n    for k, v in count_birds.items():\n        if v >= vote_lim:\n            final_birds.append(k)\n    if len(final_birds)>0:\n        row_data = {\n            \"row_id\": key,\n            \"birds\": \" \".join(sorted(final_birds))\n        }\n    else:\n        row_data = {\n            \"row_id\": key,\n            \"birds\": \"nocall\"\n        }\n    final_submission.append(row_data)\n\nsubmission = pd.DataFrame(final_submission)\nsubmission = all_row_id.merge(submission, on=\"row_id\", how=\"left\")\nsubmission = submission.fillna(\"nocall\")","metadata":{"execution":{"iopub.status.busy":"2021-06-01T17:19:38.174887Z","iopub.execute_input":"2021-06-01T17:19:38.175807Z","iopub.status.idle":"2021-06-01T17:19:38.234327Z","shell.execute_reply.started":"2021-06-01T17:19:38.175720Z","shell.execute_reply":"2021-06-01T17:19:38.233432Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.head(500)","metadata":{"execution":{"iopub.status.busy":"2021-06-01T17:19:38.236836Z","iopub.execute_input":"2021-06-01T17:19:38.237513Z","iopub.status.idle":"2021-06-01T17:19:38.256050Z","shell.execute_reply.started":"2021-06-01T17:19:38.237471Z","shell.execute_reply":"2021-06-01T17:19:38.255260Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv(\"submission.csv\", index=False)","metadata":{"execution":{"iopub.status.busy":"2021-06-01T17:19:38.257483Z","iopub.execute_input":"2021-06-01T17:19:38.258020Z","iopub.status.idle":"2021-06-01T17:19:38.770543Z","shell.execute_reply.started":"2021-06-01T17:19:38.257987Z","shell.execute_reply":"2021-06-01T17:19:38.769664Z"},"trusted":true},"execution_count":null,"outputs":[]}]}