{"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":"# Fastaudio starter kit","metadata":{}},{"cell_type":"markdown","source":"This notebook tries to give you the basic steps to compete in the Rainforest Connection Species Audio Detection competition using fastaudio.\n\n[Fastaudio](https://github.com/fastaudio/fastaudio) is an community contributed module for building audio machine learning applications on top of fastai 2. ","metadata":{}},{"cell_type":"markdown","source":"Let's start updating the pytorch version and installing it","metadata":{}},{"cell_type":"code","source":"!pip install --upgrade fastaudio","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"After installing fastaudio, restart the env before importing the library\n\n![image.png](https://i.imgur.com/xlAOnbW.png)","metadata":{}},{"cell_type":"markdown","source":"# Imports and initial data exploration","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nfrom fastaudio.all import *\nfrom fastai.vision.all import *","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = Path(\"../input/rfcx-species-audio-detection\")\npath.ls()","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:17:34.786299Z","iopub.execute_input":"2021-05-25T22:17:34.786700Z","iopub.status.idle":"2021-05-25T22:17:34.795784Z","shell.execute_reply.started":"2021-05-25T22:17:34.786660Z","shell.execute_reply":"2021-05-25T22:17:34.794769Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_path = path / 'train'\ntest_path = path / 'test'","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:17:34.991547Z","iopub.execute_input":"2021-05-25T22:17:34.992063Z","iopub.status.idle":"2021-05-25T22:17:34.996588Z","shell.execute_reply.started":"2021-05-25T22:17:34.992021Z","shell.execute_reply":"2021-05-25T22:17:34.995382Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's open a training file and visualize/hear it","metadata":{}},{"cell_type":"code","source":"train_files = get_audio_files(train_path)\naudio = AudioTensor.create(train_files[0])\naudio.show();","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:17:41.499153Z","iopub.execute_input":"2021-05-25T22:17:41.499494Z","iopub.status.idle":"2021-05-25T22:17:48.429419Z","shell.execute_reply.started":"2021-05-25T22:17:41.499460Z","shell.execute_reply":"2021-05-25T22:17:48.428526Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Processing dataframes","metadata":{}},{"cell_type":"markdown","source":"We will drop all the columns that are not recording_id or species_id.","metadata":{}},{"cell_type":"code","source":"df_train_tp = pd.read_csv(path / 'train_tp.csv')\ndf_train_tp[\"recording_id\"] = df_train_tp[\"recording_id\"].map(lambda x: \"train/\"+x)\ndf_train_tp.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:17:56.546496Z","iopub.execute_input":"2021-05-25T22:17:56.546826Z","iopub.status.idle":"2021-05-25T22:17:56.580408Z","shell.execute_reply.started":"2021-05-25T22:17:56.546793Z","shell.execute_reply":"2021-05-25T22:17:56.579753Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_train_tp = df_train_tp.drop(['t_min', 't_max', 'f_min', 'f_max', 'songtype_id'], axis=1)\ndf_train_tp['species_id'] = df_train_tp['species_id'].astype(str)\ndf_train_tp.head()","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:17:57.607960Z","iopub.execute_input":"2021-05-25T22:17:57.608290Z","iopub.status.idle":"2021-05-25T22:17:57.624352Z","shell.execute_reply.started":"2021-05-25T22:17:57.608255Z","shell.execute_reply":"2021-05-25T22:17:57.623637Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There are multiple lines with the same recording_id but different species_id. Now, we will group them and concat the species_id separated by commas `,`","metadata":{}},{"cell_type":"code","source":"# https://stackoverflow.com/questions/27298178/concatenate-strings-from-several-rows-using-pandas-groupby\ndf_train_tp['species_id'] = df_train_tp.groupby('recording_id')['species_id'].transform(\",\".join)\ndf_train_tp = df_train_tp.reset_index()","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:17:58.482449Z","iopub.execute_input":"2021-05-25T22:17:58.482777Z","iopub.status.idle":"2021-05-25T22:17:58.727987Z","shell.execute_reply.started":"2021-05-25T22:17:58.482745Z","shell.execute_reply":"2021-05-25T22:17:58.727253Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Building the dataloaders","metadata":{}},{"cell_type":"markdown","source":"First we will build [datablocks](https://docs.fast.ai/tutorial.datablock.html), that are a general way to specify how to load our data. Then, using this blocks, the train and validation dataloaders will be created. ","metadata":{}},{"cell_type":"code","source":"# AudioToSpec is a Transform from fastaudio that runs on the GPU \n# (notice that it's passed as a a batch_tfms)\n# Also, there's multiple AudioConfig's ready to use with parameters that can be easily adjusted.\n\naudio_to_spec = AudioToSpec.from_cfg(AudioConfig.BasicMelSpectrogram(n_fft=512))\n\n# Adding some data augmentation\ndata_augmentation = [AddNoise(color=NoiseColor.White, noise_level=0.1), SignalShifter(max_pct=0.3)]\n\nblocks = DataBlock(blocks=(AudioBlock, MultiCategoryBlock),\n                  get_x = ColReader('recording_id', pref=str(path.resolve())+\"/\", suff='.flac'),\n                  get_y = ColReader('species_id', label_delim=','),\n                  item_tfms = data_augmentation,\n                  batch_tfms = audio_to_spec,\n                  splitter=RandomSplitter(valid_pct=0.2, seed=42)\n                  )","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:18:07.750801Z","iopub.execute_input":"2021-05-25T22:18:07.751120Z","iopub.status.idle":"2021-05-25T22:18:07.760552Z","shell.execute_reply.started":"2021-05-25T22:18:07.751089Z","shell.execute_reply":"2021-05-25T22:18:07.759675Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Creating the dataloaders\ndls = blocks.dataloaders(df_train_tp, bs=24)","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:18:12.767830Z","iopub.execute_input":"2021-05-25T22:18:12.768151Z","iopub.status.idle":"2021-05-25T22:18:15.744084Z","shell.execute_reply.started":"2021-05-25T22:18:12.768120Z","shell.execute_reply":"2021-05-25T22:18:15.743332Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now let's vizualize one batch of data","metadata":{}},{"cell_type":"code","source":"dls.show_batch(ncols=3, nrows=2, figsize=(20, 10))","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:18:17.934277Z","iopub.execute_input":"2021-05-25T22:18:17.934618Z","iopub.status.idle":"2021-05-25T22:18:24.974457Z","shell.execute_reply.started":"2021-05-25T22:18:17.934584Z","shell.execute_reply":"2021-05-25T22:18:24.973648Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Learner and training","metadata":{}},{"cell_type":"markdown","source":"The model used is based on the torchvision resnet18, with the only modification that the input should have 1 channel, because that's what our spectrograms have.\n\nHere you can use all the standard computer vision tricks, in fact the cnn_learner comes from the fastai.vision module","metadata":{}},{"cell_type":"code","source":"learner = cnn_learner(dls, resnet18, n_in=1)","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:18:44.116663Z","iopub.execute_input":"2021-05-25T22:18:44.116994Z","iopub.status.idle":"2021-05-25T22:18:44.420001Z","shell.execute_reply.started":"2021-05-25T22:18:44.116962Z","shell.execute_reply":"2021-05-25T22:18:44.419111Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learner.lr_find()","metadata":{"execution":{"iopub.status.busy":"2021-05-25T22:18:46.875064Z","iopub.execute_input":"2021-05-25T22:18:46.875430Z","iopub.status.idle":"2021-05-25T22:23:22.638886Z","shell.execute_reply.started":"2021-05-25T22:18:46.875384Z","shell.execute_reply":"2021-05-25T22:23:22.638116Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Here you have a lot of room to experiment. As this is a starter kit, only the baseline `.fine_tune(...)` is used, but it's recommended to train for some epochs (with `.fit_one_cycle(...)`), unfreeze the model, and continue training.","metadata":{}},{"cell_type":"code","source":"learner.fine_tune(10, base_lr=5e-2)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learner.recorder.plot_loss()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Creating submission file","metadata":{}},{"cell_type":"code","source":"submission_df = pd.read_csv(path / 'sample_submission.csv')\nsubmission_df[\"recording_id\"] = submission_df[\"recording_id\"].map(lambda x: \"test/\"+x)\nsubmission_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Easily create test dataloader and get the predictions\ntest_dl = dls.test_dl(submission_df)\npreds = learner.get_preds(dl = test_dl)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"preds[0].shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Copy the predictions into the submission dataframe\nsubmission_df.iloc[:, 1:] = preds[0]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# It's ready to submit\nsubmission_df[\"recording_id\"] = submission_df[\"recording_id\"].map(lambda x: x.split(\"/\")[1])\nsubmission_df","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission_df.to_csv('submission.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}