{"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":"markdown","source":"# A simple baseline of the pytorch version is built based on resnet34 of the timm library. Training and inference code is provided.\n\n\n# For the training code we use an online transformation, so the training is very slow. Pre-processing before training can speed up model training.\n\n# Many thanks to the notebooks below for the reference:\nhttps://www.kaggle.com/tattaka/birdclef2022-submission-baseline\n\nhttps://www.kaggle.com/myso1987/birdclef2022-pytorch-resnet34-starter-lb-0-50\n\netc.\n","metadata":{}},{"cell_type":"code","source":"!pip install ../input/timm-package/timm-0.4.12-py3-none-any.whl","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport json\nimport tqdm\nimport random\nimport shutil\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.optim as optim\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchaudio\nimport torchaudio.transforms as T\nfrom sklearn.model_selection import train_test_split, GroupKFold, StratifiedKFold, KFold\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import f1_score\nimport timm\nfrom torchaudio.transforms import MelSpectrogram, Resample\nimport re\nimport torch.nn.functional as F\nimport soundfile as sf\nimport glob\n\n\nclass Config:\n    seed = 2022 \n    num_classes = 152 \n    epochs = 21\n    batch_size = 48\n    n_fold = 5 \n    learning_rate = 3e-4 \n    img_size = 128 \n    print_freq = 100 \n    model_save_dir = './' \n    pretrained = True\n\nCFG = Config()\n\ndef seed_everything(seed: int):\n    random.seed(seed)\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True\n\n\ndef extract_call(data, call = 'call'):\n    try:\n        if re.search(data, call):\n            return \"True\"\n        else:\n            return \"False\"\n    except:\n        return \"False\"\n\ndef get_train_transforms():\n    return A.Compose([\n            A.Resize(CFG.img_size, CFG.img_size),\n            A.Normalize(mean=[0.485], std=[0.229], max_pixel_value=255.0, p=1.0), \n            ToTensorV2(p=1.0),\n        ], p=1.)\n        \ndef get_val_transforms():\n    return A.Compose([\n            A.Resize(CFG.img_size, CFG.img_size),\n            A.Normalize(mean=[0.485], std=[0.229], max_pixel_value=255.0, p=1.0),\n            ToTensorV2(p=1.0),\n        ], p=1.)\n\n    \nclass MyDataset(Dataset):\n    def __init__(self, image_paths=None, label_paths=None, transforms=None, mode='train'):\n        self.image_paths = image_paths\n        self.label_paths = label_paths\n        self.transforms = transforms\n        self.mode = mode\n        self.len = len(label_paths)\n        self.target_sample_rate = 32000\n        self.num_samples = 32000*5\n        self.mel_spectrogram = T.MelSpectrogram(sample_rate=self.target_sample_rate, n_fft=2048, win_length=None, hop_length=1024, center=True,\n                                       pad_mode=\"reflect\", power=2.0, norm='slaney', onesided=True, n_mels=128,\n                                       mel_scale=\"htk\", )\n        \n    def __getitem__(self, idx):\n        if self.mode == 'train':\n            audio, sample_rate = torchaudio.load(self.image_paths[idx])\n            audio = self.to_mono(audio)\n\n            if sample_rate != self.target_sample_rate:\n                resample = Resample(sample_rate, self.target_sample_rate)\n                audio = resample(audio)\n            if audio.shape[0] > self.num_samples:\n                audio = self.crop_audio(audio)\n            else:    \n                audio = self.pad_audio(audio)\n            mel =self.mel_spectrogram(audio)\n            mel = torchaudio.transforms.AmplitudeToDB()(mel)\n            mel = np.array(mel)\n            mel = self.scale_minmax(mel, 0, 255)\n            inputs = mel[:, :, np.newaxis] #[m,n]->[m,n,1]\n            labels = torch.tensor(self.label_paths[idx], dtype=torch.long)\n            augments = self.transforms(image=inputs)\n            inputs = augments['image']\n            return inputs, labels\n\n        elif self.mode == 'test':\n            SR = self.target_sample_rate\n            audio, sample_rate = torchaudio.load(self.image_paths)\n            audio = self.to_mono(audio)\n            sample = self.label_paths.loc[idx, :]  #test\n            row_id = sample.row_id \n            end_seconds = int(sample.seconds)\n            start_seconds = int(end_seconds - 5)\n            end_index = int(SR * (end_seconds + (60 - 5) / 2) + len(audio) // 3)\n            start_index = int(SR * (start_seconds - (60 - 5) / 2) + len(audio) // 3)\n            \n            audio = audio[start_index:end_index]\n            if sample_rate != self.target_sample_rate:\n                resample = Resample(sample_rate, self.target_sample_rate)\n                audio = resample(audio)\n            if audio.shape[0] > self.num_samples:\n                audio = self.crop_audio(audio)\n            else:    \n                audio = self.pad_audio(audio)\n            mel =self.mel_spectrogram(audio)\n            mel = torchaudio.transforms.AmplitudeToDB()(mel)\n            mel = np.array(mel)\n            mel = self.scale_minmax(mel, 0, 255)\n            inputs = mel[:, :, np.newaxis] \n            augments = self.transforms(image=inputs)\n            inputs = augments['image']\n            return inputs, row_id \n\n    def pad_audio(self, audio):\n        pad_length = self.num_samples - audio.shape[0]\n        last_dim_padding = (0, pad_length)\n        audio = F.pad(audio, last_dim_padding)\n        return audio\n        \n    def crop_audio(self, audio):\n        return audio[:self.num_samples]\n    \n    def to_mono(self, audio):\n        return torch.mean(audio, axis=0)\n    \n    def scale_minmax(self, X, min=0.0, max=1.0):\n        X_std = (X - X.min()) / (X.max() - X.min())\n        X_scaled = X_std * (max - min) + min\n        return X_scaled\n    \n    def __len__(self):\n        return self.len\n\n\n    \ndef train_model(model, criterion, optimizer, lr_scheduler=None, max_epoch=10):\n    total_iters = len(train_loader)\n    best_acc = 0\n    for epoch in range(1, max_epoch + 1):\n        losses = []\n        model.train()\n        for i, (inputs, labels) in enumerate(train_loader):\n            inputs, labels = inputs.to(device), labels.to(device)\n            out = model(inputs)\n            loss = criterion(out, labels)\n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n            losses.append(loss.item())\n            if CFG.print_freq > 0 and (i % CFG.print_freq == 0):\n                print(' Fold:{} Epoch:{}({}/{}) lr:{} loss:{}'.format(\n                    fold + 1, epoch, i, total_iters, optimizer.param_groups[-1]['lr'], loss.item()))\n        lr_scheduler.step()\n\n        # val\n        acc = val_model(model)\n        best_model_out_path = CFG.model_save_dir + \"/\" + 'fold_' + str(fold + 1) + '_best' + '.pth'\n        if acc > best_acc:\n            best_acc = acc\n            best_epoch = epoch\n            torch.save(model.state_dict(), best_model_out_path)\n            print(\"epoch/fold/lr/val_acc: {}/{}/{}/{}\".format(best_epoch, fold + 1, optimizer.param_groups[-1]['lr'],\n                                                              acc))\n    torch.save(model.state_dict(), CFG.model_save_dir + \"/\" + 'fold_' + str(fold + 1) + '_last' + '.pth')\n    return acc\n\n\ndef val_model(model):\n    model.eval()\n    correct = 0\n    total = 0\n    with torch.no_grad():\n        for inputs, labels in val_loader:\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs)\n            _, predicted = torch.max(outputs, dim=1)\n            correct += int((predicted == labels).sum())\n            total += inputs.shape[0]\n        acc = (correct / total)\n    return acc\n# =============================== model ========================\nclass MyModel(nn.Module):\n    def __init__(self, num_classes=2, pretrained=True):\n        super().__init__()\n        self.model = timm.create_model('resnet34', in_chans=1, pretrained=pretrained)\n        n_features = self.model.fc.in_features\n        self.model.fc = nn.Linear(n_features, num_classes)\n\n    def forward(self, x):\n        x = self.model(x)\n        return x\n\n\ndef prediction_for_clip(audio_path, test_df, models,  threshold=0.05):\n\n    test_dataset = MyDataset(audio_path, test_df, transforms=get_val_transforms(), mode='test')\n    test_loader = DataLoader(test_dataset, batch_size=1, shuffle=False)\n\n    prediction_dict = {}\n    for inputs, row_id in test_loader:\n#         print(row_id)\n        inputs = inputs.to(device)\n        with torch.no_grad():\n            probas = []\n            for model in models:\n                model.eval()\n                output = model(inputs)\n                probas.append(output.detach().cpu().numpy().reshape(-1))\n            probas = np.array(probas)\n            events = probas.mean(0) >= threshold\n        labels = np.argwhere(events).reshape(-1).tolist()\n        \n        print(labels)\n        if len(labels) == 0:\n            prediction_dict[str(row_id)] = \"nocall\"\n        else:\n            labels_str_list = list(map(lambda x: class_dict[x], labels))\n            label_string = \" \".join(labels_str_list)\n            prediction_dict[str(row_id)] = label_string\n    return prediction_dict\n\nif __name__ == \"__main__\":\n    device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n    seed_everything(CFG.seed)\n\n    #================================train==========================\n    root_path = \"../input/birdclef-2022/\"\n    input_path = root_path + '/train_audio/'\n    train_meta = pd.read_csv(root_path + 'train_metadata.csv')\n    for  i in range(len(train_meta)):\n        train_meta.loc[i, \"filename\"] = input_path + train_meta.loc[i, \"filename\"]\n\n    print(\"Length of data before call extraction : {}\".format(len(train_meta)))\n    train_meta[\"type\"] = train_meta[\"type\"].apply(extract_call)\n    train_meta = train_meta[train_meta[\"type\"] == \"True\"].reset_index(drop=True)\n    train_meta.drop(\"type\", 1, inplace = True)\n    print(\"Length of data after call extraction : {}\".format(len(train_meta)))\n\n    class_dict = dict()\n    for index, label in enumerate(train_meta.primary_label.unique()):\n        class_dict[index] = label\n        train_meta[\"primary_label\"].replace(label, index, inplace = True)\n    print(class_dict)\n\n    folds = KFold(n_splits=CFG.n_fold, shuffle=True, random_state=CFG.seed).split(range(len(train_meta['filename'])), range(len(train_meta['filename'])))  # multi-fold\n    for fold, (train_idx, val_idx) in enumerate(folds):\n        if fold >= 1:\n            break\n        print(f\"===============training fold_nth:{fold + 1}======================\")\n        print('train:', train_idx.shape)\n        print('val:', val_idx.shape)\n\n        train_dataset = MyDataset(train_meta.loc[train_idx, 'filename'].reset_index(drop=True), train_meta.loc[train_idx, 'primary_label'].reset_index(drop=True), get_train_transforms(), mode='train')\n        val_dataset = MyDataset(train_meta.loc[val_idx, 'filename'].reset_index(drop=True), train_meta.loc[val_idx, 'primary_label'].reset_index(drop=True), get_val_transforms(), mode='train')\n\n        train_loader = DataLoader(train_dataset, batch_size=CFG.batch_size, shuffle=True, num_workers=8)\n        val_loader = DataLoader(val_dataset, batch_size=CFG.batch_size, shuffle=False, num_workers=8)\n\n        model = MyModel(num_classes=CFG.num_classes, pretrained=CFG.pretrained).to(device)\n\n        optimizer = torch.optim.AdamW(model.parameters(), lr=CFG.learning_rate, weight_decay=1e-3)\n        scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=3, T_mult=2, eta_min=1e-5, last_epoch=-1)  \n        criterion = nn.CrossEntropyLoss().to(device) \n        # train model\n        acc = train_model(model, criterion, optimizer, scheduler, max_epoch=CFG.epochs)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}