{"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":"# Notes","metadata":{"papermill":{"duration":0.04953,"end_time":"2021-02-16T15:59:39.560117","exception":false,"start_time":"2021-02-16T15:59:39.510587","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"In this kenel, I'm going to use a classical **ResneSt50** for bird identification.","metadata":{"papermill":{"duration":0.04754,"end_time":"2021-02-16T15:59:39.092567","exception":false,"start_time":"2021-02-16T15:59:39.045027","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"* The inference is based on these [resnest50 weights](https://www.kaggle.com/kneroma/kkiller-birdclef-models-public). Please, don't forget upvoting the dataset to make it more visible for others\n* The inference pipeline is optimized as much as I can in order to reduce execution time","metadata":{"papermill":{"duration":0.04461,"end_time":"2021-02-16T15:59:39.651121","exception":false,"start_time":"2021-02-16T15:59:39.606511","status":"completed"},"tags":[]}},{"cell_type":"markdown","source":"**If the only thing you wan't to change is `hyperparams`, please consider commenting instead of spamming with stupid forks !**","metadata":{}},{"cell_type":"markdown","source":"<h2><font color=\"blue\">If you find this work useful, please don't forget upvoting :)</font></h2>","metadata":{"papermill":{"duration":0.052753,"end_time":"2021-02-16T15:59:39.926669","exception":false,"start_time":"2021-02-16T15:59:39.873916","status":"completed"},"tags":[]}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!nvidia-smi","metadata":{"id":"ikMF-nQSdTbF","outputId":"041c6767-86ab-46e4-9b22-a4840121ecca","papermill":{"duration":1.301859,"end_time":"2021-02-16T15:59:41.363841","exception":false,"start_time":"2021-02-16T15:59:40.061982","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"try:\n    import resnest\nexcept ModuleNotFoundError:\n    !pip install -q \"../input/resnest50-fast-package/resnest-0.0.6b20200701/resnest\"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport librosa as lb\nimport soundfile as sf\nimport pandas as pd\nimport cv2\nfrom pathlib import Path\nimport re\n\nimport torch\nfrom torch import nn\nfrom  torch.utils.data import Dataset, DataLoader\n\nfrom tqdm.notebook import tqdm\n\nimport time\nfrom resnest.torch import resnest50","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","id":"kSCcqxf0c_O7","papermill":{"duration":4.153186,"end_time":"2021-02-16T15:59:57.894303","exception":false,"start_time":"2021-02-16T15:59:53.741117","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"papermill":{"duration":0.046502,"end_time":"2021-02-16T15:59:59.284533","exception":false,"start_time":"2021-02-16T15:59:59.238031","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Configs","metadata":{"papermill":{"duration":0.046996,"end_time":"2021-02-16T15:59:59.377834","exception":false,"start_time":"2021-02-16T15:59:59.330838","status":"completed"},"tags":[]}},{"cell_type":"code","source":"NUM_CLASSES = 397\nSR = 32_000\nDURATION = 5\nTHRESH = 0.23\n\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"DEVICE:\", DEVICE)\n\nTEST_AUDIO_ROOT = Path(\"../input/birdclef-2021/test_soundscapes\")\nSAMPLE_SUB_PATH = \"../input/birdclef-2021/sample_submission.csv\"\nTARGET_PATH = None\n    \nif not len(list(TEST_AUDIO_ROOT.glob(\"*.ogg\"))):\n    # runs on sync run\n    TEST_AUDIO_ROOT = Path(\"../input/birdclef-2021/train_soundscapes\")\n    SAMPLE_SUB_PATH = None\n    # SAMPLE_SUB_PATH = \"../input/birdclef-2021/sample_submission.csv\"\n    TARGET_PATH = Path(\"../input/birdclef-2021/train_soundscape_labels.csv\")","metadata":{"id":"rJhYZVIDc_O9","papermill":{"duration":0.440614,"end_time":"2021-02-16T15:59:59.86408","exception":false,"start_time":"2021-02-16T15:59:59.423466","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data","metadata":{"papermill":{"duration":0.046026,"end_time":"2021-02-16T16:00:00.054107","exception":false,"start_time":"2021-02-16T16:00:00.008081","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class MelSpecComputer:\n    def __init__(self, sr, n_mels, fmin, fmax, **kwargs):\n        self.sr = sr\n        self.n_mels = n_mels\n        self.fmin = fmin\n        self.fmax = fmax\n        kwargs[\"n_fft\"] = kwargs.get(\"n_fft\", self.sr//10)\n        kwargs[\"hop_length\"] = kwargs.get(\"hop_length\", self.sr//(10*4))\n        self.kwargs = kwargs\n\n    def __call__(self, y):\n\n        melspec = lb.feature.melspectrogram(\n            y, sr=self.sr, n_mels=self.n_mels, fmin=self.fmin, fmax=self.fmax, **self.kwargs,\n        )\n\n        melspec = lb.power_to_db(melspec).astype(np.float32)\n        return melspec","metadata":{"id":"4fwNhdbJc_O-","papermill":{"duration":0.056975,"end_time":"2021-02-16T16:00:00.157168","exception":false,"start_time":"2021-02-16T16:00:00.100193","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def mono_to_color(X, eps=1e-6, mean=None, std=None):\n    mean = mean or X.mean()\n    std = std or X.std()\n    X = (X - mean) / (std + eps)\n    \n    _min, _max = X.min(), X.max()\n\n    if (_max - _min) > eps:\n        V = np.clip(X, _min, _max)\n        V = 255 * (V - _min) / (_max - _min)\n        V = V.astype(np.uint8)\n    else:\n        V = np.zeros_like(X, dtype=np.uint8)\n\n    return V\n\ndef crop_or_pad(y, length):\n    if len(y) < length:\n        y = np.concatenate([y, length - np.zeros(len(y))])\n    elif len(y) > length:\n        y = y[:length]\n    return y","metadata":{"id":"Nk0haSTIc_O_","papermill":{"duration":0.065406,"end_time":"2021-02-16T16:00:00.271104","exception":false,"start_time":"2021-02-16T16:00:00.205698","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class BirdCLEFDataset(Dataset):\n    def __init__(self, data, sr=SR, n_mels=128, fmin=0, fmax=None, duration=DURATION, step=None, res_type=\"kaiser_fast\", resample=True):\n        \n        self.data = data\n        \n        self.sr = sr\n        self.n_mels = n_mels\n        self.fmin = fmin\n        self.fmax = fmax or self.sr//2\n\n        self.duration = duration\n        self.audio_length = self.duration*self.sr\n        self.step = step or self.audio_length\n        \n        self.res_type = res_type\n        self.resample = resample\n\n        self.mel_spec_computer = MelSpecComputer(sr=self.sr, n_mels=self.n_mels, fmin=self.fmin,\n                                                 fmax=self.fmax)\n    def __len__(self):\n        return len(self.data)\n    \n    @staticmethod\n    def normalize(image):\n        image = image.astype(\"float32\", copy=False) / 255.0\n        image = np.stack([image, image, image])\n        return image\n    \n    def audio_to_image(self, audio):\n        melspec = self.mel_spec_computer(audio) \n        image = mono_to_color(melspec)\n        image = self.normalize(image)\n        return image\n\n    def read_file(self, filepath):\n        audio, orig_sr = sf.read(filepath, dtype=\"float32\")\n\n        if self.resample and orig_sr != self.sr:\n            audio = lb.resample(audio, orig_sr, self.sr, res_type=self.res_type)\n          \n        audios = []\n        for i in range(self.audio_length, len(audio) + self.step, self.step):\n            start = max(0, i - self.audio_length)\n            end = start + self.audio_length\n            audios.append(audio[start:end])\n            \n        if len(audios[-1]) < self.audio_length:\n            audios = audios[:-1]\n            \n        images = [self.audio_to_image(audio) for audio in audios]\n        images = np.stack(images)\n        \n        return images\n    \n        \n    def __getitem__(self, idx):\n        return self.read_file(self.data.loc[idx, \"filepath\"])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data = pd.DataFrame(\n     [(path.stem, *path.stem.split(\"_\"), path) for path in Path(TEST_AUDIO_ROOT).glob(\"*.ogg\")],\n    columns = [\"filename\", \"id\", \"site\", \"date\", \"filepath\"]\n)\nprint(data.shape)\ndata.head()","metadata":{"id":"rVwlOrbxc_PD","outputId":"9b53ec99-634a-4b30-f9b5-75036184482b","papermill":{"duration":0.162256,"end_time":"2021-02-16T16:00:01.102152","exception":false,"start_time":"2021-02-16T16:00:00.939896","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train = pd.read_csv(\"../input/birdclef-2021/train_metadata.csv\")\n\nLABEL_IDS = {label: label_id for label_id,label in enumerate(sorted(df_train[\"primary_label\"].unique()))}\nINV_LABEL_IDS = {val: key for key,val in LABEL_IDS.items()}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inference","metadata":{"id":"BNsivZZZc_PG","papermill":{"duration":0.048675,"end_time":"2021-02-16T16:00:08.424781","exception":false,"start_time":"2021-02-16T16:00:08.376106","status":"completed"},"tags":[]}},{"cell_type":"code","source":"test_data = BirdCLEFDataset(data=data)\nlen(test_data), test_data[0].shape","metadata":{"id":"AzlOErOmc_PH","papermill":{"duration":0.058312,"end_time":"2021-02-16T16:00:08.531199","exception":false,"start_time":"2021-02-16T16:00:08.472887","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"papermill":{"duration":0.048931,"end_time":"2021-02-16T16:00:14.351476","exception":false,"start_time":"2021-02-16T16:00:14.302545","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_net(checkpoint_path, num_classes=NUM_CLASSES):\n    net = resnest50(pretrained=False)\n    net.fc = nn.Linear(net.fc.in_features, num_classes)\n    dummy_device = torch.device(\"cpu\")\n    d = torch.load(checkpoint_path, map_location=dummy_device)\n    for key in list(d.keys()):\n        d[key.replace(\"model.\", \"\")] = d.pop(key)\n    net.load_state_dict(d)\n    net = net.to(DEVICE)\n    net = net.eval()\n    return net","metadata":{"papermill":{"duration":0.05945,"end_time":"2021-02-16T16:00:14.565804","exception":false,"start_time":"2021-02-16T16:00:14.506354","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ncheckpoint_paths = [\n    Path(\"../input/k2-baselines/secondl1-birdclef_resnest50_fold0_epoch_08_f1_val_07132_20210523095829.pth\"),\n]\n\n\nnets = [\n        load_net(checkpoint_path.as_posix()) for checkpoint_path in checkpoint_paths\n]","metadata":{"id":"ayVLRTzLc_PI","papermill":{"duration":14.506299,"end_time":"2021-02-16T16:00:30.268614","exception":false,"start_time":"2021-02-16T16:00:15.762315","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"papermill":{"duration":0.051509,"end_time":"2021-02-16T16:00:30.373444","exception":false,"start_time":"2021-02-16T16:00:30.321935","status":"completed"},"tags":[]},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"@torch.no_grad()\ndef get_thresh_preds(out, thresh=None):\n    thresh = thresh or THRESH\n    o = (-out).argsort(1)\n    npreds = (out > thresh).sum(1)\n    preds = []\n    for oo, npred in zip(o, npreds):\n        preds.append(oo[:npred].cpu().numpy().tolist())\n    return preds","metadata":{"papermill":{"duration":0.059925,"end_time":"2021-02-16T16:00:30.912547","exception":false,"start_time":"2021-02-16T16:00:30.852622","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_bird_names(preds):\n    bird_names = []\n    for pred in preds:\n        if not pred:\n            bird_names.append(\"nocall\")\n        else:\n            bird_names.append(\" \".join([INV_LABEL_IDS[bird_id] for bird_id in pred]))\n    return bird_names","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def predict(nets, test_data, names=True):\n    preds = []\n    with torch.no_grad():\n        for idx in  tqdm(list(range(len(test_data)))):\n            xb = torch.from_numpy(test_data[idx]).to(DEVICE)\n            pred = 0.\n            for net in nets:\n                o = net(xb)\n                o = torch.sigmoid(o)\n\n                pred += o\n\n            pred /= len(nets)\n            \n            if names:\n                pred = get_bird_names(get_thresh_preds(pred))\n\n            preds.append(pred)\n    return preds","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred_probas = predict(nets, test_data, names=False)\nprint(len(pred_probas))","metadata":{"papermill":{"duration":7466.932385,"end_time":"2021-02-16T18:04:58.116349","exception":false,"start_time":"2021-02-16T16:00:31.183964","status":"completed"},"tags":[],"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"preds = [get_bird_names(get_thresh_preds(pred, thresh=THRESH)) for pred in pred_probas]\n# preds[:2]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def preds_as_df(data, preds):\n    sub = {\n        \"row_id\": [],\n        \"birds\": [],\n    }\n    \n    for row, pred in zip(data.itertuples(False), preds):\n        row_id = [f\"{row.id}_{row.site}_{5*i}\" for i in range(1, len(pred)+1)]\n        sub[\"birds\"] += pred\n        sub[\"row_id\"] += row_id\n        \n    sub = pd.DataFrame(sub)\n    \n    if SAMPLE_SUB_PATH:\n        sample_sub = pd.read_csv(SAMPLE_SUB_PATH, usecols=[\"row_id\"])\n        sub = sample_sub.merge(sub, on=\"row_id\", how=\"left\")\n        sub[\"birds\"] = sub[\"birds\"].fillna(\"nocall\")\n    return sub","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub = preds_as_df(data, preds)\nprint(sub.shape)\nsub","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Small validation","metadata":{}},{"cell_type":"code","source":"def get_metrics(s_true, s_pred):\n    s_true = set(s_true.split())\n    s_pred = set(s_pred.split())\n    n, n_true, n_pred = len(s_true.intersection(s_pred)), len(s_true), len(s_pred)\n    \n    prec = n/n_pred\n    rec = n/n_true\n    f1 = 2*prec*rec/(prec + rec) if prec + rec else 0\n    \n    return {\"f1\": f1, \"prec\": prec, \"rec\": rec, \"n_label\": n_true, \"n_pred\": n_pred, \"n_match\": n}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if TARGET_PATH:\n    sub_target = pd.read_csv(TARGET_PATH)\n    sub_target = sub_target.merge(sub, how=\"left\", on=\"row_id\")\n    \n    print(sub_target[\"birds_x\"].notnull().sum(), sub_target[\"birds_x\"].notnull().sum())\n    assert sub_target[\"birds_x\"].notnull().all()\n    assert sub_target[\"birds_y\"].notnull().all()\n    \n    df_metrics = pd.DataFrame([get_metrics(s_true, s_pred) for s_true, s_pred in zip(sub_target.birds_x, sub_target.birds_y)])\n\n    print(f\"THRESH: {THRESH}\")\n    print('=== standard matches ===')\n    print(df_metrics.mean())\n    \n    birds_only = sub_target[(sub_target['birds_x'] != 'nocall')]\n\n    bo_metrics = pd.DataFrame([get_metrics(s_true, s_pred) for s_true, s_pred in zip(birds_only.birds_x, birds_only.birds_y)])\n    print('=== bird matches ===')\n    print(bo_metrics.mean())\n    \n    nocall_only = sub_target[(sub_target['birds_x'] == 'nocall')]\n    nocall_metrics = pd.DataFrame([get_metrics(s_true, s_pred) for s_true, s_pred in zip(nocall_only.birds_x, nocall_only.birds_y)])\n    print('=== nocall matches ===')\n    print(nocall_metrics.mean())","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}