{"cells":[{"metadata":{},"cell_type":"markdown","source":"I've released [this crazingly fast (< 10 mins) kernel](https://www.kaggle.com/kneroma/inference-tpu-rfcx-audio-detection-fast) which uses a set of pre-computed MFCCs. \n\nThe problem is that those MFCCs are static, and if you change any params (**DURATION**, **STRIDE**, ...), you can no more use them. This is not fair. I will release my code that can help  you re-computing them whenever you need.\n\n* I use **joblib** to parallelize the computations, so it must require less than 1 hour to compute the MFCCs for the whole test dataset, and just 30 mins if STRIDE = DURATION\n* I directly use **soundfile** to read audios instead of **librosa** as soundfile is faster"},{"metadata":{},"cell_type":"markdown","source":"<h3><font color=\"blue\">Is this kernel useful for you ? Don't forget upvoting it, it really  motivates me in enhancing my work and sharing it with you :)</h3></font>"},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2020-12-27T08:05:00.353947Z","iopub.status.busy":"2020-12-27T08:05:00.351498Z","iopub.status.idle":"2020-12-27T08:05:03.624446Z","shell.execute_reply":"2020-12-27T08:05:03.62339Z"},"id":"kSCcqxf0c_O7","papermill":{"duration":3.295502,"end_time":"2020-12-27T08:05:03.624573","exception":false,"start_time":"2020-12-27T08:05:00.329071","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"import numpy as np\nimport librosa as lb\nimport soundfile as sf\nimport pandas as pd\nfrom pathlib import Path\n\nfrom tqdm.notebook import tqdm\n\n\nimport time","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"32000*10","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:03.664229Z","iopub.status.busy":"2020-12-27T08:05:03.663369Z","iopub.status.idle":"2020-12-27T08:05:03.665921Z","shell.execute_reply":"2020-12-27T08:05:03.665517Z"},"id":"rJhYZVIDc_O9","papermill":{"duration":0.024061,"end_time":"2020-12-27T08:05:03.666018","exception":false,"start_time":"2020-12-27T08:05:03.641957","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"NUM_CLASSES = 24\nSR = 32_000\nDURATION = 10\nSTRIDE = DURATION//2\n\nBATCH_START = 0\nBATCH_SIZE = 400\n\nNJOBS = 2\n\nTEST_AUDIO_ROOT = Path(\"../input/rfcx-species-audio-detection/train\")\nTEST_MFCC_SAVE_ROOT = Path(f\"train_mfcc_d{DURATION}_s{STRIDE}_sr{SR}_{BATCH_START:04d}_{BATCH_START+BATCH_SIZE:04d}\")\nTEST_MFCC_SAVE_ROOT.mkdir(exist_ok=True)","execution_count":null,"outputs":[]},{"metadata":{"id":"pbqOPUExc_O-","papermill":{"duration":0.016845,"end_time":"2020-12-27T08:05:03.699716","exception":false,"start_time":"2020-12-27T08:05:03.682871","status":"completed"},"tags":[],"trusted":true},"cell_type":"markdown","source":"(stacks,channels,mels,window/mel_hop_length)"},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:03.740251Z","iopub.status.busy":"2020-12-27T08:05:03.73975Z","iopub.status.idle":"2020-12-27T08:05:03.743651Z","shell.execute_reply":"2020-12-27T08:05:03.743234Z"},"id":"4fwNhdbJc_O-","papermill":{"duration":0.02684,"end_time":"2020-12-27T08:05:03.743735","exception":false,"start_time":"2020-12-27T08:05:03.716895","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"class MelSpecComputer:\n    def __init__(self, sr, n_mels, fmin, fmax):\n        self.sr = sr\n        self.n_mels = n_mels\n        self.fmin = fmin\n        self.fmax = fmax\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,\n        )\n\n        melspec = lb.power_to_db(melspec).astype(np.float32)\n        return melspec","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:03.791803Z","iopub.status.busy":"2020-12-27T08:05:03.790616Z","iopub.status.idle":"2020-12-27T08:05:03.793062Z","shell.execute_reply":"2020-12-27T08:05:03.793525Z"},"id":"Nk0haSTIc_O_","papermill":{"duration":0.033047,"end_time":"2020-12-27T08:05:03.79362","exception":false,"start_time":"2020-12-27T08:05:03.760573","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"def mono_to_color(X, eps=1e-6, mean=None, std=None):\n    X = np.stack([X, X, X], axis=-1)\n\n    # Standardize\n    mean = mean or X.mean()\n    std = std or X.std()\n    X = (X - mean) / (std + eps)\n\n    # Normalize to [0, 255]\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\n\ndef normalize(image, mean=None, std=None):\n    image = image / 255.0\n    if mean is not None and std is not None:\n        image = (image - mean) / std\n    return np.moveaxis(image, 2, 0).astype(np.float32)\n\n\ndef crop_or_pad(y, length,cut, sr, is_train=True):\n    if len(y) < length:\n        y = np.concatenate([y, np.zeros(length - len(y))])\n    elif len(y) > length:\n        if not is_train:\n            start = 0\n        else:\n            start = np.random.randint(cut,(len(y) - length))\n\n        y = y[start:start + length]\n\n    y = y.astype(np.float32, copy=False)\n\n    return y","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"cfg = {\n    'parse_params': {\n        'cut_time': 10,\n    },\n    'data_params': {\n        'sample_time': 6, # assert 60 % sample_time == 0\n        'spec_fmax': 24000.0,\n        'spec_fmin': 40.0,\n        'spec_mel': 384, \n        'mel_power': 2,\n        'img_shape': (384, 768)\n    },\n    'model_params': {\n        'batchsize_per_tpu': 18,\n        'iteration_per_epoch': 64,\n        'epoch': 18, \n        'arch': ResNet34,\n        'arch_preprocess': preprocess_input,\n        'freeze_to': 0,  # Freeze to backbone.layers[:freeze_to]. If None, all layers in the backbone will be freezed.\n        'loss': {\n            'fn': tfa.losses.SigmoidFocalCrossEntropy,\n            'params': {},\n        },\n        'optim': {\n            'fn': tfa.optimizers.RectifiedAdam,\n            'params': {'lr': 2e-3, 'total_steps': 18*64, 'warmup_proportion': 0.3, 'min_lr': 1e-6},\n        },\n        'mixup': True # False\n    }\n}"},{"metadata":{},"cell_type":"markdown","source":"@tf.function\ndef _cut_wav(x):\n    # random cut in training\n    cut_min = tf.random.uniform([], maxval=(CUT-TIME)*SR, dtype=tf.int32)\n    cut_max = cut_min + TIME * SR\n    cutwave = tf.reshape(x['audio_wav'][cut_min:cut_max], [TIME*SR])\n    y = {}\n    y.update(x)\n    y['audio_wav'] = cutwave\n    y['t_min'] = tf.maximum(0.0, x['t_min'] - tf.cast(cut_min, tf.float32) / SR)\n    y['t_max'] = tf.maximum(0.0, x['t_max'] - tf.cast(cut_min, tf.float32) / SR)\n    return y\n    \n@tf.function\ndef _cut_wav_val(x):\n    # center crop in validation\n    cut_min = (CUT-TIME)*SR // 2\n    cut_max = cut_min + TIME * SR\n    cutwave = tf.reshape(x['audio_wav'][cut_min:cut_max], [TIME*SR])\n    y = {}\n    y.update(x)\n    y['audio_wav'] = cutwave\n    y['t_min'] = tf.maximum(0.0, x['t_min'] - tf.cast(cut_min, tf.float32) / SR)\n    y['t_max'] = tf.maximum(0.0, x['t_max'] - tf.cast(cut_min, tf.float32) / SR)\n    return y\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"np.random.choice(range(0, 60*SR+SR*STRIDE-SR*DURATION, SR*STRIDE))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class RFCXTrainDataset:\n\n    def __init__(self, data, sr,duration= 10,cut =2, n_mels=128, fmin=0, fmax=None, num_classes=NUM_CLASSES, root=None):\n        ''' this is just for daving no need to save again the label  position will be read from the dataset'''\n            \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\n        self.num_classes = num_classes\n        self.duration = duration\n        \n        self.audio_length = self.duration*self.sr\n        self.cut_length = cut*self.sr\n        \n        self.root =  root or TEST_AUDIO_ROOT\n\n        self.mel_spec_computer = MelSpecComputer(sr=self.sr, n_mels=self.n_mels, fmin=self.fmin, fmax=self.fmax)\n        \n        self.res_type = \"kaiser_best\"\n\n\n    def __len__(self):\n        return len(self.data)\n    \n    def load(self, record):\n        y, _ = lb.load(self.root.joinpath(record).with_suffix(\".flac\").as_posix(), sr=self.sr, res_type=self.res_type)\n        return y\n    \n    def load2(self, record):\n        y, orig_sr = sf.read(self.root.joinpath(record).with_suffix(\".flac\").as_posix())\n        y = lb.resample(y, orig_sr=orig_sr, target_sr=self.sr, res_type=self.res_type)\n        return y\n    \n    def read_index(self, idx):\n        d = self.data.iloc[idx]\n        record = d[\"recording_id\"]\n        \n        y = self.load2(record)\n        \n        window = DURATION*self.sr\n        stride = STRIDE*self.sr\n            \n       # y = np.stack([y[i:i+window] for i in range(0, 60*self.sr+stride-window, stride)])\n\n        y = crop_or_pad(y, self.audio_length,self.cut_length, sr=self.sr)\n        \n        #choose random beginning to crop train\n       # t_min = np.random.choice(range(0, 60*self.sr+stride-window, stride))\n        \n        #y = y[t_min:t_min+window]\n        \n        \n        return y #, label\n            \n    def process(self, y):\n         image = self.mel_spec_computer(y) \n#         image = mono_to_color(image)\n#         image = normalize(image, mean=None, std=None)\n         return image\n\n    def __getitem__(self, idx):\n        \n        y  = self.read_index(idx)\n        \n        #y , label = self.read_index(idx)\n        \n        #image = np.stack([self.process(_y) for _y in y])\n        y = self.process(y)\n        \n        return y #,label\n    \n    def to_mfcc(self, idx):\n        record = self.data.iloc[idx][\"recording_id\"]\n        mfcc = self[idx]\n        \n        \n        np.save(TEST_MFCC_SAVE_ROOT.joinpath(record).with_suffix(\".npy\").as_posix(), mfcc)\n        \n       # np.save(TEST_MFCC_SAVE_ROOT.joinpath(record).with_suffix(\".npy\").as_posix(), mfcc)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"tp_dataset = RFCXTrainDataset(pd.read_csv('../input/rfcx-species-audio-detection/train_fp.csv'), sr=SR)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n\nx = tp_dataset[1]\nprint(x.shape)","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:03.882841Z","iopub.status.busy":"2020-12-27T08:05:03.879358Z","iopub.status.idle":"2020-12-27T08:05:03.884996Z","shell.execute_reply":"2020-12-27T08:05:03.885392Z"},"id":"Q2MieafPc_PB","papermill":{"duration":0.035536,"end_time":"2020-12-27T08:05:03.88549","exception":false,"start_time":"2020-12-27T08:05:03.849954","status":"completed"},"tags":[],"trusted":true},"cell_type":"markdown","source":"class RFCXDataset:\n\n    def __init__(self, data, sr, n_mels=128, fmin=0, fmax=None, num_classes=NUM_CLASSES, root=None):\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\n        self.num_classes = num_classes\n        self.duration = duration\n        self.audio_length = self.duration*self.sr\n        \n        self.root =  root or TEST_AUDIO_ROOT\n\n        self.mel_spec_computer = MelSpecComputer(sr=self.sr, n_mels=self.n_mels, fmin=self.fmin, fmax=self.fmax)\n        \n        self.res_type = \"kaiser_best\"\n\n\n    def __len__(self):\n        return len(self.data)\n    \n    def load(self, record):\n        y, _ = lb.load(self.root.joinpath(record).with_suffix(\".flac\").as_posix(), sr=self.sr, res_type=self.res_type)\n        return y\n    \n    def load2(self, record):\n        y, orig_sr = sf.read(self.root.joinpath(record).with_suffix(\".flac\").as_posix())\n        y = lb.resample(y, orig_sr=orig_sr, target_sr=self.sr, res_type=self.res_type)\n        return y\n    \n    def read_index(self, idx):\n        d = self.data.iloc[idx]\n        record = d[\"recording_id\"]\n        \n        y = self.load2(record)\n        \n        window = DURATION*self.sr\n        stride = STRIDE*self.sr\n            \n        y = np.stack([y[i:i+window] for i in range(0, 60*self.sr+stride-window, stride)])\n\n#         y = crop_or_pad(y, self.audio_length, sr=self.sr)\n        \n        return y\n            \n    def process(self, y):\n        melspec = self.mel_spec_computer(y) \n        image = mono_to_color(melspec)\n        image = normalize(image, mean=None, std=None)\n        return image\n\n    def __getitem__(self, idx):\n\n        y = self.read_index(idx)\n        \n        image = np.stack([self.process(_y) for _y in y])\n\n        return image\n    \n    def to_mfcc(self, idx):\n        record = self.data.iloc[idx][\"recording_id\"]\n        mfcc = self[idx]\n        \n        np.save(TEST_MFCC_SAVE_ROOT.joinpath(record).with_suffix(\".npy\").as_posix(), mfcc)"},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:03.923755Z","iopub.status.busy":"2020-12-27T08:05:03.923074Z","iopub.status.idle":"2020-12-27T08:05:03.925861Z","shell.execute_reply":"2020-12-27T08:05:03.925426Z"},"id":"xV9UJt8Mc_PC","papermill":{"duration":0.023364,"end_time":"2020-12-27T08:05:03.925939","exception":false,"start_time":"2020-12-27T08:05:03.902575","status":"completed"},"tags":[],"trusted":true},"cell_type":"code","source":"# def get_duration(audio_name, root=TEST_AUDIO_ROOT):\n#     return lb.get_duration(filename=root.joinpath(audio_name).with_suffix(\".flac\"))","execution_count":null,"outputs":[]},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:03.964403Z","iopub.status.busy":"2020-12-27T08:05:03.963877Z","iopub.status.idle":"2020-12-27T08:05:18.438105Z","shell.execute_reply":"2020-12-27T08:05:18.437604Z"},"id":"rVwlOrbxc_PD","outputId":"9b53ec99-634a-4b30-f9b5-75036184482b","papermill":{"duration":14.495358,"end_time":"2020-12-27T08:05:18.438221","exception":false,"start_time":"2020-12-27T08:05:03.942863","status":"completed"},"tags":[],"trusted":true},"cell_type":"markdown","source":"%%time\n\ndata = pd.DataFrame({\n    \"recording_id\": [path.stem for path in Path(TEST_AUDIO_ROOT).glob(\"*.flac\")],\n})\n\nprint(data.shape)\ndata.head()"},{"metadata":{"id":"x1WwgO0sc_PF","papermill":{"duration":0.018712,"end_time":"2020-12-27T08:05:18.74522","exception":false,"start_time":"2020-12-27T08:05:18.726508","status":"completed"},"tags":[],"trusted":true},"cell_type":"markdown","source":""},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:18.78594Z","iopub.status.busy":"2020-12-27T08:05:18.785444Z","iopub.status.idle":"2020-12-27T08:05:18.789579Z","shell.execute_reply":"2020-12-27T08:05:18.788896Z"},"id":"DajVt4q7c_PF","papermill":{"duration":0.026055,"end_time":"2020-12-27T08:05:18.789711","exception":false,"start_time":"2020-12-27T08:05:18.763656","status":"completed"},"tags":[],"trusted":true},"cell_type":"markdown","source":"ds = RFCXDataset(data=data, sr=SR)"},{"metadata":{"execution":{"iopub.execute_input":"2020-12-27T08:05:18.831385Z","iopub.status.busy":"2020-12-27T08:05:18.830615Z","iopub.status.idle":"2020-12-27T08:05:22.359206Z","shell.execute_reply":"2020-12-27T08:05:22.358244Z"},"id":"a4z6e5v8c_PF","outputId":"6bb5a3ef-c74e-4ded-d9f9-a861a2c0ae04","papermill":{"duration":3.551127,"end_time":"2020-12-27T08:05:22.359324","exception":false,"start_time":"2020-12-27T08:05:18.808197","status":"completed"},"tags":[],"trusted":true},"cell_type":"markdown","source":"%%time\n\nx = ds[1]\nprint(x.shape)\n#(11, 3, 128, 626)"},{"metadata":{"trusted":true},"cell_type":"markdown","source":"x.nbytes/(1024**2)"},{"metadata":{"trusted":true},"cell_type":"markdown","source":"# %timeit ds.to_mfcc(0)"},{"metadata":{"trusted":true},"cell_type":"markdown","source":""},{"metadata":{"trusted":true},"cell_type":"code","source":"import joblib\npool = joblib.Parallel(n_jobs=NJOBS)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"mapper = joblib.delayed(tp_dataset.to_mfcc)\ntasks = []\nfor idx in range(BATCH_START, max(BATCH_START + BATCH_SIZE, len(tp_dataset))):\n# for idx in range(25):\n    tasks.append(mapper(idx))\n    \nres = pool(tqdm(tasks))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}