{"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 numpy as np\nimport librosa as lb\nimport soundfile as sf\nimport pandas as pd\nimport joblib, json\nimport numpy as np\nimport librosa as lb\nimport soundfile as sf\nimport pandas as pd\nimport torch\nimport json\nimport copy\nimport joblib\n\nfrom soundfile import SoundFile\nfrom tqdm.notebook import tqdm\nfrom ast import literal_eval\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.models import efficientnet_b0\nfrom pathlib import Path\nfrom soundfile import SoundFile\nfrom pathlib import Path\nfrom tqdm.notebook import tqdm\nfrom sklearn.model_selection import KFold\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"NUM_CLASSES = 397\nSR = 32_000\nDURATION = 7 \nMAX_READ_SAMPLES = 5\n\nDATA_ROOT = Path(\"../input/birdclef-2021\")\nTRAIN_AUDIO_ROOT = Path(\"../input/birdclef-2021/train_short_audio\")\nTRAIN_AUDIO_IMAGES_SAVE_ROOT = Path(\"audio_images\")\nTRAIN_AUDIO_IMAGES_SAVE_ROOT.mkdir(exist_ok=True, parents=True)\n\nMODEL_ROOT = Path(\".\")\n\nTRAIN_BATCH_SIZE = 128\nTRAIN_NUM_WORKERS = 2\n\nVAL_BATCH_SIZE = 128\nVAL_NUM_WORKERS = 2\n\nTEST_AUDIO_ROOT = Path(\"../input/birdclef-2021/test_soundscapes\")\nSAMPLE_SUB_PATH = \"../input/birdclef-2021/sample_submission.csv\"\nTARGET_PATH = None\nif not len(list(TEST_AUDIO_ROOT.glob(\"*.ogg\"))):\n    TEST_AUDIO_ROOT = 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":"def 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    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_ROOT/\"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_ROOT/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":"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=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":{},"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, 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":"class AudioToImage:\n    def __init__(self, sr=SR, n_mels=128, fmin=0, fmax=None, duration=DURATION, 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 = MelSpecComputer(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 = mono_to_color(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] = crop_or_pad(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 = TRAIN_AUDIO_IMAGES_SAVE_ROOT/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(DURATION*0.666*SR))\nmapper = joblib.delayed(converter)\ntasks = [mapper(row) for row in df.itertuples(False)]\n\npool(tqdm(tasks))\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"MEL_PATHS = sorted(Path(\"./\").glob(\"rich_train_metadata.csv\"))\nTRAIN_LABEL_PATHS = sorted(Path(\"./\").glob(\"LABEL_IDS.json\"))\n\ndef get_df(mel_paths=MEL_PATHS, train_label_paths=TRAIN_LABEL_PATHS):\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=SR, is_train=True, num_classes=NUM_CLASSES, duration=DURATION):\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))[: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\n","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_model():\n    model = efficientnet_b0(pretrained=True)\n    model.classifier[1] = torch.nn.Linear(1280, NUM_CLASSES)\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":"nfold = 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":[]}]}