{"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"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":25954,"databundleVersionId":2091745,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport torch\nimport librosa as lb\nimport soundfile as sf\nimport json\nimport copy\nimport joblib\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm.notebook import tqdm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from pathlib import Path\n\nnum_labels = 397\nFD = 32_000\nm_dur = 7 \nm_max_read_samples = 5\n\ndata_path = Path(\"../input/birdclef-2021\")\ntrain_audio_path = Path(\"../input/birdclef-2021/train_short_audio\")\npath_to_save_train_audio_imgs = Path(\"audio_images\")\npath_to_save_train_audio_imgs.mkdir(exist_ok=True, parents=True)\n\ntest_audio_path = Path(\"../input/birdclef-2021/test_soundscapes\")\nsample_sub_path = \"../input/birdclef-2021/sample_submission.csv\"\ntarget_path = None\nif not len(list(test_audio_path.glob(\"*.ogg\"))):\n    test_audio_path = Path(\"../input/birdclef-2021/train_soundscapes\")\n    sample_sub_path = None\n    target_path = Path(\"../input/birdclef-2021/train_soundscape_labels.csv\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(device)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from soundfile import SoundFile\n\ndef get_audio_info(filepath):\n    \"\"\"Get some properties from  an audio file\"\"\"\n    with SoundFile(filepath) as f:\n        sr = f.samplerate\n        frames = f.frames\n        duration = float(frames)/sr\n        \n    return {\"frames\": frames, \"sr\": sr, \"duration\": duration}","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def make_df():\n    \n    df = pd.read_csv(data_path/\"train_metadata.csv\")\n\n    LABEL_IDS = {label: label_id for label_id, label in enumerate(sorted(df[\"primary_label\"].unique()))}\n    \n    df[\"label_id\"] = df[\"primary_label\"].map(LABEL_IDS)\n\n    df[\"filepath\"] = [str(train_audio_path/primary_label/filename) for primary_label,filename in zip(df.primary_label, df.filename) ]\n\n    pool = joblib.Parallel(4)\n    mapper = joblib.delayed(get_audio_info)\n    tasks = [mapper(filepath) for filepath in df.filepath]\n\n    df = pd.concat([df, pd.DataFrame(pool(tqdm(tasks)))], axis=1, sort=False)\n\n    return LABEL_IDS, df","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LABEL_IDS, df = make_df()\n\ndf.to_csv(\"rich_train_metadata.csv\", index=True)\nwith open(\"LABEL_IDS.json\", \"w\") as f:\n    json.dump(LABEL_IDS, f)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def resize(y, length, is_train=True, start=None):\n    if len(y) < length:\n        y = np.concatenate([y, np.zeros(length - len(y))])\n        \n        n_repeats = length // len(y)\n        epsilon = length % len(y)\n        \n        y = np.concatenate([y]*n_repeats + [y[:epsilon]])\n        \n    elif len(y) > length:\n        if not is_train:\n            start = start or 0\n        else:\n            start = start or np.random.randint(len(y) - length)\n\n        y = y[start:start + length]\n\n    return y","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def colorize(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","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class CalcMelSpec:\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=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        \n        return melspec","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class AudioToImage:\n    def __init__(self, sr=FD, n_mels=128, fmin=0, fmax=None, duration=m_dur, step=None, res_type=\"kaiser_fast\", resample=True):\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 = CalcMelSpec(sr=self.sr, n_mels=self.n_mels, fmin=self.fmin,\n                                                 fmax=self.fmax)\n        \n    def audio_to_image(self, audio):\n        melspec = self.mel_spec_computer(audio) \n        image = colorize(melspec)\n        return image\n\n    def __call__(self, row, save=True):\n      audio, orig_sr = sf.read(row.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 = [audio[i:i+self.audio_length] for i in range(0, max(1, len(audio) - self.audio_length + 1), self.step)]\n      audios[-1] = resize(audios[-1] , length=self.audio_length)\n      images = [self.audio_to_image(audio) for audio in audios]\n      images = np.stack(images)\n        \n      if save:\n        path = path_to_save_train_audio_imgs/f\"{row.primary_label}/{row.filename}.npy\"\n        path.parent.mkdir(exist_ok=True, parents=True)\n        np.save(str(path), images)\n      else:\n        return  row.filename, images","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pool = joblib.Parallel(2)\n\nconverter = AudioToImage(step=int(m_dur*0.666*FD))\nmapper = joblib.delayed(converter)\ntasks = [mapper(row) for row in df.itertuples(False)]\n\npool(tqdm(tasks))","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mel_paths = sorted(Path(\"./\").glob(\"rich_train_metadata.csv\"))\ntrain_labels_path = sorted(Path(\"./\").glob(\"LABEL_IDS.json\"))","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from ast import literal_eval\n\ndef get_df(mel_paths=mel_paths, train_label_paths=train_labels_path):\n  df = None\n  LABEL_IDS = {}\n    \n  for file_path in mel_paths:\n    temp = pd.read_csv(str(file_path), index_col=0)\n    temp[\"impath\"] = temp.apply(lambda row: file_path.parent/\"audio_images/{}/{}.npy\".format(row.primary_label, row.filename), axis=1) \n    df = temp if df is None else df.append(temp)\n    \n  df[\"secondary_labels\"] = df[\"secondary_labels\"].apply(literal_eval)\n\n  for file_path in train_label_paths:\n    with open(str(file_path)) as f:\n      LABEL_IDS.update(json.load(f))\n\n  return LABEL_IDS, df","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LABEL_IDS, df = get_df()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class BirdClefDataset(Dataset):\n\n    def __init__(self, df, sr=FD, is_train=True, num_classes=num_labels, duration=m_dur):\n        \n        self.df = df\n        self.sr = sr\n        self.is_train = is_train\n        self.num_classes = num_classes\n        self.duration = duration\n        self.audio_length = self.duration*self.sr\n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        image = np.load(str(row.impath))[:m_max_read_samples]\n\n        image = image[np.random.choice(len(image))]\n        image = image.astype(\"float32\", copy=False) / 255.0\n        image = np.stack([image, image, image])\n        \n        t = row.label_id\n        \n        return image, t","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torchvision.models import efficientnet_b0\n\ndef get_model():\n    model = efficientnet_b0(pretrained=True)\n    model.classifier[1] = torch.nn.Linear(1280, num_labels)\n    model.to(device);\n    return model","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learning_rate = 2e-4\nepochs = 10\nloss_fn = torch.nn.CrossEntropyLoss()\n\ndef train(model, loss_fn, train_loader, valid_loader, epochs, optimizer, scheduler):\n    best_model_wts = copy.deepcopy(model.state_dict())\n    best_acc = 0.0\n    train_losses = []\n    valid_losses = []\n    \n    for epoch in tqdm(range(1,epochs+1)):\n        model.train()\n        batch_losses=[]\n        for _, data in enumerate(train_loader):\n            x, y = data\n            optimizer.zero_grad()\n            x = x.to(device, dtype=torch.float32)\n            y = y.to(device, dtype=torch.long)\n            y_hat = model(x)\n            loss = loss_fn(y_hat, y)\n            loss.backward()\n            batch_losses.append(loss.item())\n            optimizer.step()\n        train_losses.append(batch_losses)\n\n        model.eval()\n        batch_losses=[]\n        trace_y = []\n        trace_yhat = []\n        \n        for _, data in enumerate(valid_loader):\n            x, y = data\n            x = x.to(device, dtype=torch.float32)\n            y = y.to(device, dtype=torch.long)\n            y_hat = model(x)\n            loss = loss_fn(y_hat, y)\n            trace_y.append(y.cpu().detach().numpy())\n            trace_yhat.append(y_hat.cpu().detach().numpy())      \n            batch_losses.append(loss.item())\n        valid_losses.append(batch_losses)\n        trace_y = np.concatenate(trace_y)\n        trace_yhat = np.concatenate(trace_yhat)\n        accuracy = np.mean(trace_yhat.argmax(axis=1)==trace_y)\n        \n        print(\"epoch = %d, train_loss = %.5f, val_loss = %.5f, val_accuracy = %.5f\" % (epoch, np.mean(train_losses[-1]), np.mean(valid_losses[-1]), accuracy))\n\n        scheduler.step(np.mean(valid_losses[-1]))\n        if accuracy > best_acc:\n            best_acc = accuracy\n            best_model_wts = copy.deepcopy(model.state_dict())\n\n    model.load_state_dict(best_model_wts)\n    return model","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import KFold\n\nnfold = 5\nskf = KFold(n_splits=nfold, shuffle=True, random_state=32)\n\nfor fold_id, (train_index, val_index) in enumerate(skf.split(df)):\n    print(\"Fold\", fold_id)\n    train_data = df.loc[train_index]\n    val_data = df.loc[val_index]\n\n    train_set = BirdClefDataset(train_data)\n    valid_set = BirdClefDataset(val_data, \"valid\")\n    train_loader = DataLoader(train_set, batch_size=64, shuffle=True, drop_last=True)\n    valid_loader = DataLoader(valid_set, batch_size=64, shuffle=True, drop_last=True)\n\n    model = get_model()\n    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', patience=3)\n    model = train(model, loss_fn, train_loader, valid_loader, epochs, optimizer, scheduler)\n    torch.save(model.state_dict(), \"../working/model\" + str(fold_id) + \".pt\")\n    \n    del train_data, val_data, train_loader, valid_loader, model, train_set, valid_set","metadata":{},"execution_count":null,"outputs":[]}]}