{"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":"!pip install mllibs","metadata":{"execution":{"iopub.status.busy":"2022-08-12T19:03:23.233222Z","iopub.execute_input":"2022-08-12T19:03:23.233883Z","iopub.status.idle":"2022-08-12T19:03:31.104431Z","shell.execute_reply.started":"2022-08-12T19:03:23.233745Z","shell.execute_reply":"2022-08-12T19:03:31.103706Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from IPython.core.display import display, HTML, Javascript\n\ncolor_map = ['#FFFFFF','#FF5733']\n\nprompt = color_map[-1]\nmain_color = color_map[0]\nstrong_main_color = color_map[1]\ncustom_colors = [strong_main_color, main_color]\n\ncss_file = '''\ndiv #notebook {\nbackground-color: white;\nline-height: 20px;\n}\n\n#notebook-container {\n%s\nmargin-top: 2em;\npadding-top: 2em;\nborder-top: 4px solid %s;\n-webkit-box-shadow: 0px 0px 8px 2px rgba(224, 212, 226, 0.5);\n    box-shadow: 0px 0px 8px 2px rgba(224, 212, 226, 0.5);\n}\n\ndiv .input {\nmargin-bottom: 1em;\n}\n\n.rendered_html h1, .rendered_html h2, .rendered_html h3, .rendered_html h4, .rendered_html h5, .rendered_html h6 {\ncolor: %s;\nfont-weight: 600;\n}\n\ndiv.input_area {\nborder: none;\n    background-color: %s;\n    border-top: 2px solid %s;\n}\n\ndiv.input_prompt {\ncolor: %s;\n}\n\ndiv.output_prompt {\ncolor: %s; \n}\n\ndiv.cell.selected:before, div.cell.selected.jupyter-soft-selected:before {\nbackground: %s;\n}\n\ndiv.cell.selected, div.cell.selected.jupyter-soft-selected {\n    border-color: %s;\n}\n\n.edit_mode div.cell.selected:before {\nbackground: %s;\n}\n\n.edit_mode div.cell.selected {\nborder-color: %s;\n\n}\n'''\n\ndef to_rgb(h): \n    return tuple(int(h[i:i+2], 16) for i in [0, 2, 4])\n\nmain_color_rgba = 'rgba(%s, %s, %s, 0.1)' % (to_rgb(main_color[1:]))\nopen('notebook.css', 'w').write(css_file % ('width: 95%;', main_color, main_color, main_color_rgba, \n                                            main_color,  main_color, prompt, main_color, main_color, \n                                            main_color, main_color))\n\ndef nb(): \n    return HTML(\"<style>\" + open(\"notebook.css\", \"r\").read() + \"</style>\")\nnb()","metadata":{"_kg_hide-input":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"''' GLOBAL CONFIGURATION CLASS '''\n\nclass cfg:\n\n    local = False\n    # Generate Subset\n    kfold = 4       # Number of kfolds to be used\n    pw = 2   # Tunable constant that affects spectogram output\n    offset = None # Offset input signal\n    seed = 1337      # random seed id\n    sr = 32000        # librosa sample rate input\n    sl = 5 # seconds   \n    sshape = (48*2,128*2) # height x width of spectogram images\n    fmin = 500      # spectrum min frequency\n    fmax = 12500    # spectrum max frequency\n    n_epoch = 100   # training epochs\n    cutoff = 15     # 3 sample spectogram (training) \n    hop_len = int(sl*sr / (sshape[1] - 1))\n    nfft = 1024\n    model_bins = 20  # split signal into bins","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"''' IMPORT MODULES & HELPER FUNCTIONS '''\n\nimport os, random\nimport numpy as np\nimport math\nfrom PIL import Image\nimport copy\nimport shutil\nfrom tqdm import tqdm,tqdm_notebook\nimport pandas as pd\nimport librosa\nfrom sklearn.utils import shuffle\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport librosa.display\nimport IPython.display as ipd\nfrom plotly.subplots import make_subplots\nimport plotly.graph_objects as go\nimport plotly.express as px\nfrom scipy.signal import find_peaks\nfrom collections import Counter\nfrom mllibs.kriging_regressor import Kriging\nfrom mllibs.gp_regressor import GPR\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n    \n''' HELPER FUNCTION '''\nlst_col = ['#B1D784','#2E8486','#004379','#032B52','#EAEA8A']\n\n''' Display List '''\n# display list neatly\n# https://stackoverflow.com/questions/1524126/how-to-print-a-list-more-nicely\ndef list_columns(obj, cols=4, columnwise=True, gap=4):\n    sobj = [str(item) for item in obj]\n    if cols > len(sobj): cols = len(sobj)\n    max_len = max([len(item) for item in sobj])\n    if columnwise: cols = int(math.ceil(float(len(sobj)) / float(cols)))\n    plist = [sobj[i: i+cols] for i in range(0, len(sobj), cols)]\n    if columnwise:\n        if not len(plist[-1]) == cols:\n            plist[-1].extend(['']*(len(sobj) - len(plist[-1])))\n        plist = zip(*plist)\n    printer = '\\n'.join([\n        ''.join([c.ljust(max_len + gap) for c in p])\n        for p in plist])\n    print (printer)\n\n''' Split Signal into Segments'''\n# split audio signal into chunks\ndef split_signal(sig):\n    sig_splits = []\n    for i in range(0, len(sig), int(cfg.sl * cfg.sr)):\n        split = sig[i:i + int(cfg.sl * cfg.sr)]\n        if len(split) < int(cfg.sl * cfg.sr):\n            break\n        sig_splits.append(split)\n    \n    return sig_splits\n\n''' Pixel to Frequency '''\n# get spectogram frequency value\ndef pxtohz(y_mel_index, sr=cfg.sr, n_fft = 1024, printSummary = True):\n    def find_nearest(a, a0):\n        idx = np.abs(a - a0).argmin()\n        return a.flat[idx]\n\n    hz_scale = librosa.core.fft_frequencies(sr=sr, n_fft=n_fft)\n    mel_scale = librosa.core.mel_frequencies(n_mels=cfg.sshape[0], \n                                               fmin=cfg.fmin, fmax=cfg.fmax, htk=False)\n    y_hz = mel_scale[int(y_mel_index)] \n    y_hz_nearest = find_nearest(hz_scale, y_hz)\n    y_hz_index = list(hz_scale).index(y_hz_nearest)\n    return y_hz\n\n''' Split DataFrame into Parts by index '''\n# split dataframe into chunks \ndef split_dataframe(df, chunk_size = 10000): \n    chunks = list()\n    num_chunks = len(df) // chunk_size + 1\n    for i in range(num_chunks):\n        chunks.append(df[i*chunk_size:(i+1)*chunk_size])\n    return chunks\n\n''' LOCAL DIRECTORIES '''\n\n# Work from Main Directory\nBASE_DIR = '.\\\\kaggle\\\\input\\\\birdclef-2021\\\\'\nDIR_SPEC_IN = '.\\\\kaggle\\\\input\\\\birdclef-2021\\\\train_short_audio\\\\'\n\n# spectogram options\nDIR_SPEC_OUT = '.\\\\kaggle\\\\working\\\\train_96_256_rat4\\\\'\n# DIR_SPEC_OUT = '.\\\\kaggle\\\\working\\\\train_96_256\\\\'\n# DIR_SPEC_OUT = '.\\\\kaggle\\\\working\\\\train_48_128\\\\'\n\nCSV_IN_TRAIN = '.\\\\kaggle\\\\input\\\\birdclef-2021\\\\train_metadata.csv'\nDIR_WEIGHTS = '.\\\\kaggle\\\\working\\\\weights\\\\'\nDIR_KFOLDS = '.\\\\kaggle\\\\working\\\\kfolds\\\\'\nINITIAL_CONDITION = '.\\\\kaggle\\\\working\\\\model_t0.h5'\n\nif(cfg.local is False):\n    os.chdir('../..')\n    BASE_DIR = BASE_DIR.replace('\\\\','/')\n    DIR_SPEC_IN = DIR_SPEC_IN.replace('\\\\','/')\n    DIR_SPEC_OUT = DIR_SPEC_OUT.replace('\\\\','/')\n    CSV_IN_TRAIN = CSV_IN_TRAIN.replace('\\\\','/')\n    DIR_WEIGHTS = DIR_WEIGHTS.replace('\\\\','/')\n    DIR_KFOLDS = DIR_KFOLDS.replace('\\\\','/')\n    INITIAL_CONDITION = INITIAL_CONDITION.replace('\\\\','/')\n    \nprint(f'cwd: {os.getcwd()}')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-02-17T16:12:03.45549Z","iopub.execute_input":"2022-02-17T16:12:03.455854Z","iopub.status.idle":"2022-02-17T16:12:07.344702Z","shell.execute_reply.started":"2022-02-17T16:12:03.455823Z","shell.execute_reply":"2022-02-17T16:12:07.343272Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"![](https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/8cc1eeaa-4046-4c4a-ae93-93d656f68688/dezjm4r-cf8da152-cbcf-4895-979a-a383a803133f.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzhjYzFlZWFhLTQwNDYtNGM0YS1hZTkzLTkzZDY1NmY2ODY4OFwvZGV6am00ci1jZjhkYTE1Mi1jYmNmLTQ4OTUtOTc5YS1hMzgzYTgwMzEzM2YuanBnIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.sBJ14rD9a6oyBk-T9ay8zS9HSQNBv8DHUOm0Z3caaRk)\n\n# <b><span style='color:#F1C40F'>1 |</span> INTRODUCTION</b>\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.1 | TONAL PEAK FREQUENCY IDENTIFICATION </b></p>\n</div>\n\n- In this notebook, given as <b>set/group</b> of labeled recordings , we'll be looking at a <b>model based approach</b> to determine the frequency at which a bird specie of interest tends to call for a primary specie.\n- The approach requires the identification of noise within the spectogram, to do that, we will be utilising maximum tonal sound peaks to create a broadband natured model.\n- Noise generally can be divided into <b>broadband noise</b> (general noise level) & <b>tonal noises</b> (peaks at specific frequency bins). They don't have precise definitions, but <b>broadband</b> noises can be abstractly defined as the general noise level in an environement coming from various locations, creating a broad frequency range noise relation to output noise level. <b>Tonal</b> noise sources tend be associated to very clearly distinguishible noise peaks at specific frequencies ( or over a small frequency range ). \n- When we look at a spectogram, each bird specie tends to create quite a repetitive collection of freq vs time structures, usually across a specific frequency range, usually it's a combination of tonal peaks that make up an entire bird call. \n- In this approach, the two terms are used even looser, since there is a time element to this model from the STFT, which can be useful in a variety of scenarios.\n- The tonal peak frequency identification approach relies on the assumption that the more data is fed into the system, the more precise the result should get, as occasional secondary birds & other noises should eventually start to show more dissipative distribution in the entire subset that is analysed.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.2 | SOUND EVENT DETECTION (SED) </b></p>\n</div>\n\n**Sound Event Detection | [@paperswithcode](https://paperswithcode.com/task/sound-event-detection])**\n\n> Sound Event Detection (SED) is the task of recognizing the sound events and their respective temporal start and end time in a recording. Sound events in real life do not always occur in isolation, but tend to considerably overlap with each other. Recognizing such overlapping sound events is referred as polyphonic SED.\n\n- There are various way to detect the presence of tonal peaks (usually what we are interested) within a spectogram, in fact you can simply do it by dividing the audio clip into segments & doing an FFT for each segment, followed by a comparison of correlation between each segment FFT functions. STFT conversion already introduces this time component, which is very handy and exactly what we'll use to create a model that will be used in SED.\n- These spectograms ( obtained via STFT ) go much further and can usually contain quite a lot information (relevant and irrelevant). They capture various noise sources not even associated with the primary specie which it was weakly labeled.\n- With a time domain component (as opposed to standard FFT), creating <b>a model containing a time element</b> can be quite handy over of a simple FFT overall since call time is a critical component in a call noise structure.\n- When attempting Sound Event Detection (SED) in the spectogram, we'll probably run into some logistical issues of how to actually identify these tonal noise sources; determining peak locations was the thing immediately come to mind.\n- In this problem, the peak cut off threshold and the tonal peak's relativeness will depend on a constructed model, which will act to recreate the general sound level noise curve, having a time dependency as well, which can be more useful than a similar one constructed from FFT as opposed to STFT.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.3 | ENSEMBLED BROADBAND MODEL </b></p>\n</div>\n\n- The STFT based broadband model has applications which go slightly outside the scope of the current notebook, its purpose in this problem is outlined below.\n- Using data from STFT, the constructed model serves a few purpose;\n  - (1) It is used for creating <b>an ensemble model from all individual time bin models</b> that oscillates less, thus creating less peaks when combined with scipy's peak identificaton module\n  - (2) It is also useful for identifying how cluttered a particular frequency is with tonal peaks, this way we can easily identify constanly occuring noises, such as insects, bird groups & build a collection of unique bird calls, when combined with a simple correlation evaluation for all functions\n \n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.4 | DETERMINING FREQUENCY CUTOFF FOR SPECTORGRAM </b></p>\n</div>\n \n- When we want to use spectograms as CNN inputs, ultimately several key variables come into play: <b>minimum</b> & <b>maximum</b> frequencies determine the cutoff frequency points in the spectogram.\n- We need to determine which frequency range combination & figure sizes to set in the created spectogram, before feeding it into the CNN model.\n- As birds don't tend to call in the entire frequency range, we could utilise a specie specific tonal peak occurence library in order to determine the cutoff frequencies, since they can contain more specific information about the call freq/time structure, as opposed to a zoomed out one, which will naturally lose some detail.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.5 | NOTEBOOK WORKFLOW </b></p>\n</div>\n\n- We aim to ustilise a broadband model, which exhibits minor fluctuation tendencies to recreate tonal peaks. From these peaks, we utilise scipy's peak detection module and save the pixel index at which a peak was found, we repeat this process for every short audio recording saving all the freq/pixel indicies at which model peaks were constructed, thus creating a one dimensional peak map for each specie, which will tend to tell us at which frequencies tonal peaks tend to occur for a given specie.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>1.6 | APPLICATION OF BROADBAND MODEL</b></p>\n</div>\n\n- <b>For the creation of a detailed spectrum that will capture the frequency/time domain call signature (our focus here)</b>\n- For pre-process short audio & soundscape detail investigation, \n- For image augmentation including time & frequency filtering. ","metadata":{}},{"cell_type":"markdown","source":"# <b><span style='color:#F1C40F'>2 |</span> METHOD</b>\n\n- The STFT based broadband model is quite straightforward, and there probably is nothing unique about it that I'm aware of.\n- I just thought I'd share the model I couple with the CNN approach for the analyses of both short audio & soundscape data. \n- I simply will share one of its applications (which is just to count the peak index occurence count) outlined in the introduction. I tend to analyse the whole batch of weakly labeled subset data belonging to one specie.\n- Looping over all desired audio files of a subset of interest to us (a particular primary label subset):\n> - First, we load an audio recording that we wish to convert to  desired to be used as inputs for CNN models. \n> - The audio is then split into segments that will define the spectogram time domain limits. Usually we would start with the entire frequency range [0,12.5kHz] and split the recording into a 5 second chunks, creating a time & frequency domain relation.\n> - For reference, we find the maximum dB value in the entire frequency range, <b>this model will define the peaks of the tonal noises and will always be the maximum.</b>\n> - The spectogram is then divided into <b>time bins</b>, <b>cfg.model_bins</b> & for each time bin, the maximum value for each frequency is determined.\n> - A <b>model for each time bin</b> is created and a simple <b>enemble of all time segments is constructed</b>, this should always create a model that is lower in dB level than the global peak model mentioned earlier. There are certain cases where this is not the case, usually an indicator that there exist an anomaly in the structure of the curve (as shown in the example below).\n> - The <b>peaks of the model</b> are then found using <b>scipy's find_peaks module</b>, stored into a global list & the <b>Counter</b> module counts all list entries.\n> - The results are subsequently plotted for each pixel value. The corresponding frequency values can be extracted using the function <b>pxtohz</b>.","metadata":{}},{"cell_type":"markdown","source":"# <b><span style='color:#F1C40F'>3 |</span> MAIN CLASS</b>\n\n### <b><span style='color:#F1C40F'>OVERVIEW</span></b>\n\n- It is always useful to keep code clean and work with classes as much as possible when working on any project.\n- I've created a simplified class, with operations associated with getting data from <b>birdclef-2021</b> data.\n- I'll be making continuous references to this class and its instantiation is required to load relevant data.\n\n### <b><span style='color:#F1C40F'>CLASS CONTENT</span></b>\n\n- Reading of Training <b>shot audio</b> & <b>training soundscape</b> related CSV files are both required to get the relevant subset of data used here, which is achieved straight after instantiation.\n- <b>get_short_labels</b> is useful to get all possible <b>primary_labels</b>, which is an important data feature in this competition.\n\n### <b><span style='color:#F1C40F'>CREATING SUBSETS OF DATA</span></b>\n\n- I'm quite fond of using the primary label <b>rugdov (Ruddy Ground Dove)</b> for the example for the earlir outlined reason.\n- In this dataset, there are only 66 recordings of this primary specie.","metadata":{}},{"cell_type":"code","source":"class get_subset:\n\n    def __init__(self):\n\n        ''' 1. SHORT TRAINING FILES '''\n        self.__SHORTAUDIO__ = DIR_SPEC_IN\n        # main short audio info CSV file\n        self.pd_short_audio = pd.read_csv(BASE_DIR+'train_metadata.csv')\n        if(cfg.local is False):\n            self.pd_short_audio['path'] = self.__SHORTAUDIO__ + \"/\" + self.pd_short_audio['primary_label'] + '/' + self.pd_short_audio['filename']\n        else:\n            self.pd_short_audio['path'] = self.__SHORTAUDIO__ + \"\\\\\" + self.pd_short_audio['primary_label'] + '\\\\' + self.pd_short_audio['filename']\n        \n        ''' 2. TRAINING SOUNDSCAPE FILES '''\n        self.__SO_PATH_TR__ = BASE_DIR+'.\\\\train_soundscapes\\\\'  # path to train soundcape files\n        self.__SO_PATH_TE__ = BASE_DIR+'.\\\\test_soundscapes\\\\'  # path to test soundcape files\n        if(cfg.local is False):\n            self.__SO_PATH_TR__ = self.__SO_PATH_TR__.replace('\\\\','/')\n            self.__SO_PATH_TE__ = self.__SO_PATH_TE__.replace('\\\\','/')\n        \n        # main soundscape info CSV file (shows interval labels)\n        path_soundscape_audio = BASE_DIR+'train_soundscape_labels.csv'   # read soundscape related CSV\n        self.pd_scape = pd.read_csv(path_soundscape_audio)\n\n        # list of filest to soundscape .ogg\n        lst_sounds = os.listdir(self.__SO_PATH_TR__)\n        self.PATH_SCAPE = [self.__SO_PATH_TR__ + i for i in lst_sounds]\n        self.PATH_SCAPE.sort()\n\n    ''' GET ALL LABELS AVAILABLE '''\n    # display all available classes in dataset\n    def get_short_labels(self):\n        primary_labels = self.pd_short_audio.primary_label.unique()\n        primary_labels.sort()\n        return primary_labels\n    \n    # get a sample row of a primary_label\n    def prim_lookup(self,prim_id):\n        tdf = self.pd_short_audio[self.pd_short_audio['primary_label'] == prim_id]\n        return tdf.sample(1,random_state=24)\n\n    ''' 3. GET VARIOUS SUBSETS OF DATA '''\n    # get various subsets of dataframe\n    def get_bird_subset(self,name='acafly'):\n        return self.pd_short_audio[self.pd_short_audio['primary_label'] == name].copy().reset_index()\n    # get rating subset\n    def get_rating_subset(self,rating=2):\n        return self.pd_short_audio[self.pd_short_audio['rating'] == rating].copy().reset_index()\n    # get bird & rating subset\n    def get_bird_rating(self,name='acafly',rating=4):\n        return self.pd_short_audio[(self.pd_short_audio['primary_label'] == name)&(self.pd_short_audio['rating'] == rating)].copy().reset_index()\n    # # show name of primary label\n    def primary_to_common(self,primary='cangoo'):\n        specie = self.pd_short_audio[self.pd_short_audio['primary_label'] == primary].sample(1)\n        return specie\n    \n    ''' 4. FIND DATA VIA XENO IDENTIFIER '''\n    # You might want to quickly find data based on unique Xeno identifier eg. XC544318\n    \n    # get the entire row info\n    def id_lookup(self,record_id):\n        row = self.pd_short_audio[self.pd_short_audio['filename'] == record_id + '.ogg']\n        display(row)\n    # get the pathway to the recording\n    def id_path(self,record_id):\n        row = self.pd_short_audio[self.pd_short_audio['filename'] == record_id + '.ogg']\n        path = row['path'].values[0]\n        return path\n\n# Instantiate Main Dataset Class\ndata = get_subset() # Instantiate Main Dataset Class\n# data.get_short_labels() # Show all available classes\nsubset = data.get_bird_subset('rugdov') # Pick 1 Class\ndisplay(subset.head(1))\nsubset_path = subset['path'].tolist() # Define Series list\nprint(f'Remaining Subset: {subset.shape}') ","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:07.346588Z","iopub.execute_input":"2022-02-17T16:12:07.346901Z","iopub.status.idle":"2022-02-17T16:12:08.006938Z","shell.execute_reply.started":"2022-02-17T16:12:07.346869Z","shell.execute_reply":"2022-02-17T16:12:08.0057Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1C40F'>4 |</span> BROADBAND MODEL - UNIVERSAL KRIGING</b>\n\n### <b><span style='color:#F1C40F'>OVERVIEW</span></b>\n\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">Kriging</mark>**, popular in geospatial interpolation & optimisation is an ensemble model consisting of **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">Gaussian Process Regression</mark>** and typically **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">Polynomial Regression</mark>**, \n- Such a combination is quite suitable for the current problem since we want to have a model that doesn't overfit, nor underfit.\n- Kriging is a very <b>accurate interpolation approach</b> & has a tendency to overfit (defeating the purpose of making a broadband model) when its hyperparameters are optimised, so we will be using <b>self defined hyperparameters</b> & focusing only on the variation of only one (<b>theta</b>) when needed, whilst keeping the other two fixed.\n- If you are interested in other applications of Kriging, I've also used it to estimate temperature in defined regions in another notebook; **[Geospatial Data Visualisation](https://www.kaggle.com/shtrausslearning/geospatial-data-visualisation)**\n- The class is written utiling sklearn's structure for custom classes that can  be integrated.\n\n### <b><span style='color:#F1C40F'>HOW THE MODEL IS USED</span></b>\n\n- In our problem, we will use this model in an attempt to differentiate between <b>broadband</b> & <b>tonal</b> noise sources in audio recordings.\n- We will be creating a kriging model <b>for every time bin segment</b> we defined in our 5 second cut segment using the <b>global cfg.model_bins parameter</b>.\n- The assumption is that <b>by creating an ensemble of all individual time segments</b>, the <b>model should mainly capture the broadband noise level</b>, variations relative to the peak values is an indicator of how much activity occurs in each timebin segment, we can easily use this to  detect event/non event cases.\n- There are quite a lot of things such a model can tell us not covered in the scope of this notebook. I can just name one on the top of my head; for example if capturing the entire frequency and time range, highly similar model and global peaks indicate the presence of constantly occuring noise, which are usually non bird related, such as insect etc and you may want to be aware of it.\n\n### <b><span style='color:#F1C40F'>MODEL THEORY</span></b>\n\n<p>The Kriging model is built on the assumption that the data <em>Y</em> obey a Gaussian process with an assumed form for the mean function and the covariance between data points: </p>\n<p class=\"formulaDsp\">\n\\[ Y = N(m(\\vec{x}), K(\\vec{x},\\vec{x}))\\]\n</p>\n<p> where \\(m(\\vec{x})\\) is the mean function and \\(K(\\vec{x},\\vec{x})\\) represents the covariance between function values. For this work, a regression mean function is assumed. Using this form, the mean function has the following form: </p>\n<p class=\"formulaDsp\">\n\\[ m(x) = h^{T}(\\vec{x}) \\beta \\]\n</p>\n<p> where \\(h^{T}(\\vec{x})\\) represents a column vector containing the basis functions of the basis evaluated at the points \\(\\vec{x}\\). The regression parameters \\(\\beta\\) are treated as part of the Kriging model and are determined while constructioning the model. Using this form of the mean function yields a Universal Kriging model. The case of a (polyorder=0) regression (where the vector \\(h(\\vec{x})\\) reduces to unity) is referred to as <b>Ordinary Kriging</b> and is also covered by this functional form. The assumption of a vague prior on the regression parameters gives the following closed form for the parameters: </p>\n<p class=\"formulaDsp\">\n\\[ \\beta=(H K^{-1} H^{T})^{-1} H^T K^{-1} Y = A^{-1} H^T K^{-1} Y \\]\n</p>\n<p> where \\(K\\) is the covariance matrix between the training data. For a Kriging model, the covariance between function values is assumed to be only a function of the distance between points. The multi-dimension covariance function is constructed using a tensor product of one dimension functions. The multi-dimension covariance is calculated in <b>fit()</b>, which calls the static method <b>covfn</b>. The elements in ths covariance matrix are given as: </p>\n<p class=\"formulaDsp\">\n\\[ K_{i,j} = cov(y_{i},y_{j}) = \\sigma^{2} k(\\vec{X}_{i},\\vec{X}_{j}; \\theta) + \\sigma^{2}_{n} \\delta_{i,j} \\]\n</p>\n<p> The parameters \\(\\sigma\\) and \\(\\theta\\) (and in some cases \\(\\sigma_{n}\\)) are denoted as hyperparameters and are determined maximizing the likelihood equation for the Kriging model. This likelihood gives the probability that a Gaussian process with specified hyperparamters describes the training data <em>X</em> and <em>Y</em>. By picking the hyperparameters that maximize this probability, a Kriging model that best describes the data can be constructed. The hyperparameters are determined based on the <b>likelihood equation</b> for a gaussian process with a vague prior on the regression parameters. This likelihood is computed in function <b>llhobj</b> which uses the Scipy Module, <b>minimize</b>. Using this equation, optimization is used to determine all of the parameters, including the covariance magnitude \\(\\sigma\\) and noise \\(\\sigma_{n}\\). This way of determining hyperparameters should be used when the noise level of the function needs to be fitted. <br/>\n With the regression and covariance parameters determined, the final processed data can be constructed using the inverse of the covariance matrix. To make predictions from the Kriging model, the following vector is required: </p>\n<p class=\"formulaDsp\">\n\\[ V = K^{-1} (Y - H^{T} \\beta) \\]\n</p>\n<p> where \\( K \\) is the covariance matrix, the product \\(H^{T} \\beta\\) represents the mean function evaluated at the training points and \\(Y\\) represents the function values at the training points. Using this processed data, the regression parameters and covariance parameters & predictions can be made.\n\n<br>\n    \n<tr><td class=\"mdescLeft\">&#160;</td><td class=\"mdescRight\">Model predictions throughout the domain are determined by sampling from the conditional distribution \\(y_* | \\vec{X},Y\\) using the covariance between points in the domain where \\(\\vec{X},Y\\) are the input and output training data. The posterior mean predictions for an explicit mean are given by the formula: </p>\n<p class=\"formulaDsp\">\n\\[ y(\\vec{x}_{*}) | \\vec{X},Y,m(x) = m(\\vec{x}_{*}) + k_*^T K^{-1} (Y-m(\\vec{x}_{*})) \\]\n</p>\n<p> where \\(k_{*}^{T}\\) represents the covariance between the test point, \\(\\vec{x}_{*}\\), and the training points \\(\\vec{X}\\) (a row vector of length ntot). <br/>\n For a regression mean function, the function predictions take the form of: </p>\n<p class=\"formulaDsp\">\n\\[ y(\\vec{x}_{*}) | \\vec{X},Y,\\beta = h^{T}(\\vec{x}_{*}) \\beta + k_*^T K^{-1} (Y-H^{T} \\beta) \\]\n</p>\n<p> The regression parameters \\(\\beta\\) and the hyperparamters in the covariance function are supplied by <b>fit()</b>. Using only this data, function predictions can be made; however, the construction and inverse of the covariance matrix can make the function predictions expensive. Because this matrix is inverted during the construction of the Kriging model, this work can be re-used for function predictions. Defining the processed data \\(V\\) as: </p>\n<p class=\"formulaDsp\">\n\\[ V = K^{-1} (Y - H^{T} \\beta) \\]\n</p>\n<p> the function predictions are given by: </p>\n<p class=\"formulaDsp\">\n\\[ y(\\vec{x}_{*}) | \\vec{X},Y,\\beta = h^{T}(\\vec{x}_{*}) \\beta + k_*^T V \\]\n</p>","metadata":{}},{"cell_type":"code","source":"from sklearn.base import BaseEstimator,RegressorMixin\nfrom numpy.linalg import cholesky, det, lstsq, inv, pinv\nfrom scipy.optimize import minimize\nimport numpy as np\nfrom sklearn.preprocessing import PolynomialFeatures\npi = 4.0*np.arctan(1.0)\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n# Universal Kriging Model (Polynomial Regression + Full Gaussian Process Regression Model)\n# Commonly used Ensemble Approach for geospatial interpolation and \n\nclass Kriging(BaseEstimator,RegressorMixin):\n    \n    def __init__(self,kernel='rbf',theta=10.0,sigma=10.0,sigma_n=1,opt=True,polyorder=2):\n        self.theta = theta\n        self.sigma = sigma\n        self.sigma_n = sigma_n\n        self.opt = opt\n        self.polyorder = polyorder \n        Kriging.kernel = kernel \n\n    ''' local covariance functions '''\n    @staticmethod\n    def covfn(X0,X1,theta=1.0,sigma=1.0):\n\n        ''' Radial Basis Covariance Function '''\n        if(Kriging.kernel == 'rbf'):\n            r = np.sum(X0**2,1).reshape(-1,1) + np.sum(X1**2,1) - 2 * np.dot(X0,X1.T)\n            return sigma**2 * np.exp(-0.5/theta**2*r)\n\n        ''' Matern Covariance Class of Funtions '''\n        if(Kriging.kernel == 'matern'):\n            lid=1\n            r = np.sum(X0**2,1)[:,None] + np.sum(X1**2,1) - 2 * np.dot(X0,X1.T)\n            if(lid==1):\n                return sigma**2 * np.exp(-r/theta)\n            elif(lid==2):\n                ratio = r/theta\n                v1 = (1.0+np.sqrt(3)*ratio)\n                v2 = np.exp(-np.sqrt(3)*ratio)\n                return sigma**2*v1*v2\n            elif(lid==3):\n                ratio = r/theta\n                v1 = (1.0+np.sqrt(5)*ratio+(5.0/3.0)*ratio**2)\n                v2 = np.exp(-np.sqrt(5)*ratio)\n                return sigma**2*v1*v2\n        else:\n            print('Covariance Function not defined')\n            \n    ''' Train the Model'''\n    def fit(self,X,y):\n        \n        ''' Working w/ numpy matrices'''\n        if(type(X) is np.ndarray):\n            self.X = X;self.y = y\n        else:\n            self.X = X.values; self.y = y.values\n        self.ntot,ndim = self.X.shape\n        \n        # Collocation Matrix\n        self.poly = PolynomialFeatures(self.polyorder)\n        self.H = self.poly.fit_transform(self.X)\n        \n        ''' Optimisation Objective Function '''\n        # Optimisation of hyperparameters via the objective funciton\n        def llhobj(X,y,noise):\n            \n            # Simplified Variant\n            def llh_dir(hypers):\n                K = self.covfn(X,X,theta=hypers[0],sigma=hypers[1]) + noise**2 * np.eye(self.ntot)\n                return 0.5 * np.log(det(K)) + \\\n                    0.5 * y.T.dot(inv(K).dot(y)).ravel()[0] + 0.5 * self.ntot * np.log(2*pi)\n\n            # Full Likelihood Equation\n            def nll_full(hypers):\n                K = self.covfn(X,X,theta=hypers[0],sigma=hypers[1]) + noise**2 * np.eye(self.ntot)\n                L = cholesky(K)\n                return np.sum(np.log(np.diagonal(L))) + \\\n                    0.5 * y.T.dot(lstsq(L.T, lstsq(L,y)[0])[0]) + \\\n                    0.5 * self.ntot * np.log(2*pi)\n            \n            return llh_dir # return one of the two, simplified variant doesn't always work well\n        \n        ''' Update hyperparameters based on set objective function '''\n        if(self.opt==True):\n            # define the objective funciton\n            objfn = llhobj(self.X,self.y,self.sigma_n)\n            # search for the optimal hyperparameters based on given relation\n            res = minimize(fun=objfn,x0=[1,1],\n                           method='Nelder-Mead',tol=1e-6)\n            self.theta,self.sigma = res.x # update the hyperparameters to \n\n        self.HT = self.H.T\n        self.Kmat = self.covfn(self.X,self.X,self.theta,self.sigma) \\\n                  + self.sigma_n**2 * np.eye(self.ntot) # Covariance Matrix (Train/Train)\n        self.IKmat = pinv(self.Kmat) # Pseudo Matrix Inversion (More Stable)\n\n        self.HK = np.dot(self.HT,self.IKmat) # HK^-1\n        HKH = np.dot(self.HK,self.H)     # HK^-1HT\n        self.A = inv(HKH)             # Variance-Covariance Weighted LS Matrix\n\n        self.W = np.dot(self.IKmat,self.y)\n        Q = np.dot(self.HT,self.W)\n        self.beta = np.dot(self.A,Q)               # Regression coefficients\n        self.V = self.W - np.dot(self.IKmat,self.H).dot(self.beta) # K^{-1} (Y - H^{T} * beta)\n        \n        return self  # return class & use w/ predict()\n\n    ''' Posterior Prediction;  '''\n    # Make a prediction based on what the model has learned \n    def predict(self,Xm):\n        \n        ''' Working w/ numpy matrices'''\n        if(type(Xm) is np.ndarray):\n            self.Xm = Xm\n        else:\n            self.Xm = Xm.values\n        self.mtot,ndim = self.Xm.shape\n        \n        self.Hm = self.poly.fit_transform(self.Xm) # Collocation Matrix\n        self.Kmat = self.covfn(self.X,self.Xm,self.theta,self.sigma) # Covariance Matrix (Train/Test)\n        yreg = np.dot(self.Hm,self.beta)               # Mean Prediction based on Regression\n        ykr = np.dot(self.Kmat.T,self.V)              # posterior mean predictions for an explicit mean \n\n        return yreg + ykr","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:08.011877Z","iopub.execute_input":"2022-02-17T16:12:08.012168Z","iopub.status.idle":"2022-02-17T16:12:08.045201Z","shell.execute_reply.started":"2022-02-17T16:12:08.01214Z","shell.execute_reply":"2022-02-17T16:12:08.043684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1C40F'>5 |</span> CREATING SPECTOGRAMS</b>\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>5.1 | OVERVIEW</b></p>\n</div>\n\n- <b>get_spectograms</b> is an extended function of the one posted another notebook **[[Keras, Inference] BirdCLEF2021 starter](https://www.kaggle.com/shtrausslearning/keras-inference-birdclef2021-starter)**\n- The function is used for creating a spectogram for different audio chunks of the split input audio signal.\n- I've included several things in the function that can be useful to visualise during general EDA & we will look through one recording that is split into several segments (all contents for one case only.\n\n**<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">Function Arguments</mark>**\n\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">filepath</mark>** : string containg path to audio <br>\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">primary_label</mark>** : desired output name <br>\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">output_dir</mark>** : directory in which the spectograms are saved <br>\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">save_id</mark>** : Save spectogram output or not <br>\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">audio_id</mark>** : display segment audio recording <br>\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">select_id</mark>** : training / soundscape data is looked at ( soundscape just adds label ) <br>\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">plot_id</mark>** : output general plots ( those displayed in the next example )\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">store_id</mark>** : save peak values to global loop ( when actually looping through all audios )\n- **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">height</mark>** : plotly figure height","metadata":{}},{"cell_type":"code","source":"def get_spectrograms(filepath=None,primary_label=None, output_dir=None,save_id=False,\n                     audio_id=False,select_id='train',plot_id=False,store_id=False,height=400):\n\n    # duration is set from global variable\n    sig, rate = librosa.load(filepath, sr=cfg.sr, offset=cfg.offset, duration=cfg.cutoff) # read audio data\n    sig_splits = split_signal(sig) # split the signal into parts        \n    fig = make_subplots(rows=3, cols=len(sig_splits))\n    \n    # Extract mel spectrograms for each audio chunk\n    s_cnt = 0; path_all = []; jj=0; kk= cfg.offset; lst_peak_model = []\n    for chunk in sig_splits:\n        \n        kk=+ cfg.offset + cfg.sl\n        \n        # Play Audio\n        if(audio_id):\n            print(f'Audio ID: {kk}')\n            display(ipd.display(ipd.Audio(data=chunk, rate=cfg.sr)))\n            \n        kk+=5; jj+=1;\n        mel = librosa.feature.melspectrogram(chunk,sr=cfg.sr,\n                                             fmin=cfg.fmin, \n                                             fmax=cfg.fmax,\n                                             n_mels=cfg.sshape[0],\n                                             n_fft = cfg.nfft) \n        mel_spec = librosa.power_to_db(mel**cfg.pw, ref=np.min)\n        mel_spec_disp = mel_spec.copy()\n        mel_spec_disp -= mel_spec_disp.min(); mel_spec_disp /= mel_spec_disp.max() # numpy format\n        if(plot_id):\n            fig.add_trace(go.Heatmap(z=mel_spec,colorscale='viridis',showscale=False),1,jj)\n        \n        # add sandscape results annotation\n        if(select_id is 'soundscape'):\n            data = get_subset()\n            record_id = filepath.split('_')[1].split('/')[1]\n            get_birds = data.pd_scape[data.pd_scape['audio_id']==int(record_id)][['seconds','birds']]\n            get_birds_v= get_birds[get_birds['seconds'] == kk]['birds'].values[0]\n        \n        '''Get Mel Max/Min/Mean Values (TIME)'''\n        ldf = pd.DataFrame(mel_spec)\n        maxst = ldf.describe().loc['max',:]\n        if(plot_id):\n            fig.add_trace(go.Scatter(y=maxst,line=dict(color=lst_col[0])),2,jj) # chunk signal data\n\n        '''Get Mel Max/Min/Mean Values (FREQUENCY)'''\n        ldf = pd.DataFrame(mel_spec.T)\n        maxsf = ldf.describe().loc['max',:]\n        if(plot_id):\n            fig.add_trace(go.Scatter(y=maxsf,line = dict(color=lst_col[1]),name='spect-max'),3,jj)\n    \n        ''' TIME BIN MODEL PREDICTION  '''\n        # Split the data into time bins, splitting data into bins -> broadband model attempt\n\n        lst_df = split_dataframe(ldf,chunk_size=cfg.model_bins)        \n        lst_ensemble = []; ym = 0; ii=0\n        for tdf in lst_df:\n            ii+=1; maxs = tdf.describe().loc['max',:]  # maximum value in frequency band (plot)\n            model = Kriging(opt=False,theta=4)\n            model.fit(X=tdf.T.index[:,None],y=maxs)\n            Xm = np.arange(0,maxsf.shape[0],1)[:,None]\n            ym+= model.predict(Xm)\n            y_ens = ym/float(ii)  # ensemble model\n        if(plot_id):\n            fig.add_trace(go.Scatter(y=y_ens,line = dict(color=lst_col[0]),name='spect-bin-model'),3,jj)\n        \n        ''' Find Peaks in Data '''\n        # find peaks in bin model \n        peaks, _ = find_peaks(y_ens, height=0) # find peaks in ensemble model\n        if(store_id):\n            glst_peak_model.extend(peaks)      \n        if(plot_id):\n            fig.add_trace(go.Scatter(x=peaks,y=y_ens[peaks],mode='markers',marker=dict(color='black')),3,jj)\n    \n        ''' (SAVE) MELSPECTOGRAM '''\n        if(save_id):\n            mel_spec -= mel_spec.min(); mel_spec /= mel_spec.max() # numpy format\n            save_dir = os.path.join(output_dir, primary_label)\n            if not os.path.exists(save_dir): os.makedirs(save_dir)\n            fname = filepath.rsplit(os.sep, 1)[-1].rsplit('.', 1)[0] + '_' + str(s_cnt) + '.png'\n            save_path = os.path.join(save_dir,fname)\n            im = Image.fromarray(mel_spec * 255.0).convert(\"L\")\n            im.save(save_path)\n        \n            # add filepath to list\n            path_all.append(save_path)\n            s_cnt += 1\n    \n    if(plot_id):\n        fig.update_layout(margin=dict(l=0, r=0, t=30, b=0),height=height,showlegend=False)\n        fig.update_layout(template='plotly_white',font=dict(family='sans-serif',size=14))\n        fig.show()\n        \n#     if(plot_id):\n#         fig.update_layout(margin=dict(l=0, r=0, t=30, b=0),height=height,coloraxis_showscale=False,showlegend=False)\n#         fig.update_layout(template='plotly_white',font=dict(family='sans-serif',size=14))\n#         fig.show()\n        \n    return path_all # return list of pathways to created spectograms","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:08.047178Z","iopub.execute_input":"2022-02-17T16:12:08.047654Z","iopub.status.idle":"2022-02-17T16:12:08.077008Z","shell.execute_reply.started":"2022-02-17T16:12:08.047603Z","shell.execute_reply":"2022-02-17T16:12:08.075804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>5.2 | SAMPLE VISUALISATION CASE</b></p>\n</div>\n\n- Let's choose one particular audio recording that outlines some particular interesting things; <b>subset_path[5]</b>\n- We'll limit outselves to one recording that is longer than 20 seconds, however we'll use <b>cfg.cutoff = 20</b> to limit ourselves to four chunks of spectogram data.\n- This particular one is voted to be a rather clean recording <b>rating = 4</b> by [Xeno Canto](https://www.xeno-canto.org/) members.\n- For the spectogram image output, we'll use <b>(96,256) px</b> & a desired range of <b>(0,12.5) kHz frequency range</b>, which is what you might start off with.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>5.3 | OUTPUT IMAGES</b></p>\n</div>\n\n- We'll output, in order:\n  - 5 second chunk mel spectogram images (vertical & horizontal axis represent <b>px equivalent</b> of <b>time</b> & <b>frequency</b> domains.)\n  - <b>Time Domain</b> | Maximum signal chunk dB value at each time segment (<b>vertical (dB)</b> & <b>horizontal (time)</b>)\n  - <b>Frequeny Domain</b> | Maximum signal chunk dB value at each frequency (<b>vertical (dB)</b> & <b>horizontal(frequency)</b>)\n    - <b>dark green</b> : global spectogram maximum value at each frequency \n    - <b>light green</b> : local bin maximum value at each frequency \n    - <b>dots</b> : scipy module evaluated model peak values\n  \n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>5.4 | SOME OVERSERVATIONS</b></p>\n</div>\n\n- We can see some <b>repetitive tonal peaks</b> at the bottom half of the spectogram. Likely our <b>primary_label</b>, since most chunks contain similar patterns.\n- The more such tonal peaks there are the more the model moves towards the maximum spectogram value, as seeen from images (1,2 & 4) which have different numbers of calls.\n- <b>Frequency masking</b> is present in some recordings, being already tampered with to filter out high frequency noise, such a insect sounds. \n- Such constant line sounds are all over the place, not only at costant frequency, but at constant times as well, as a result its useful study the spectogram as much as possible.\n- The scipy module finds <b>four peaks in total at around 14 px</b>, you can use the function <b>pxtohz</b> to get the frequency equivalent.\n\n<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>5.5 | MODELS OR FASTER METHODS</b></p>\n</div>\n\n- From the results below, you can see that the model generates much lower number of peaks compared to that of the maximum peak value (dark green).\n- Inevitably, you will start to contribute more peaks to the overall count and potentially have multiple options instead of one (say if you were using a threshold to obtain the most common bird frequency), the overall confidence of having more peaks to choose from\n- There is a slight issue surrounding model hyperparameters, how do you know which ones to choose? I think this can come down to simply understanding how each hyperparameter affects the model, and going off that knowledge. Ultimately there are scenarios in which we will get False results, so coming up with a method that will tune them will definitely be useful!","metadata":{}},{"cell_type":"code","source":"display(subset[5:6])","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:08.078209Z","iopub.execute_input":"2022-02-17T16:12:08.078618Z","iopub.status.idle":"2022-02-17T16:12:08.110168Z","shell.execute_reply.started":"2022-02-17T16:12:08.078576Z","shell.execute_reply":"2022-02-17T16:12:08.10937Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cfg.sshape = (48*2,128*2) # define shape of the spectogram \ncfg.pw = 1; cfg.cutoff = 20; cfg.offset = 0  # defined power of spectogram, read cutoff & read starting location\ncfg.fmin = 0; cfg.fmax = 12500  # minimum and maximum frequency of the spectogram\nout = get_spectrograms(filepath=subset_path[5],primary_label='temp',output_dir=DIR_SPEC_OUT,\n                       save_id=False,audio_id=False,select_id='short',plot_id=True,store_id=False,height=500)","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:08.11111Z","iopub.execute_input":"2022-02-17T16:12:08.111456Z","iopub.status.idle":"2022-02-17T16:12:30.400496Z","shell.execute_reply.started":"2022-02-17T16:12:08.111425Z","shell.execute_reply":"2022-02-17T16:12:30.399287Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<div style=\"color:white;display:fill;border-radius:8px;\n            background-color:#323232;font-size:150%;\n            font-family:Nexa;letter-spacing:0.5px\">\n    <p style=\"padding: 8px;color:white;\"><b>5.6 | MISSING POTENTIAL BIRD CALLS</b></p>\n</div>\n\n- 57,55 px show another peak, perhaps this is the <b>primary label?</b>, we clearly have a case of non specified <b>secondary labels</b> and the CNN model might start to pay attention to such secondary calls in the recordings.\n- Some segments have <b>considerably higher broadband noise levels</b> & less noticable visible peaks (img. 3), which from my observations is similar to the soundscape recording environments.\n- A realistic scenario would be that the peak would be completely missed in a soundcape environment when using the previously defined frequency range because the <b>bird may be quite far away</b> from the microphone and the call would be extremly faint on the spectogram that we are using. \n- One option to counter this is to redefine the frequency range and reduce the image size, whilst focusing on one part of the spectogram, which we know is the primary label.\n- If we had the knowledge that this bird does call at such a small frequency range, we can redefine the window (0,1) kHz & we can note that the peak stands out a little more compared to the previous example, of course ","metadata":{}},{"cell_type":"code","source":"cfg.sshape = (48,128) # define shape of the spectogram \ncfg.pw = 1; cfg.cutoff = 20; cfg.offset = 0  # defined power of spectogram, read cutoff & read starting location\ncfg.fmin = 0; cfg.fmax = 1000  # minimum and maximum frequency of the spectogram\nout = get_spectrograms(filepath=subset_path[5],primary_label='temp',output_dir=DIR_SPEC_OUT,\n                       save_id=False,audio_id=False,select_id='short',plot_id=True,store_id=False,height=500)","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:30.403278Z","iopub.execute_input":"2022-02-17T16:12:30.40377Z","iopub.status.idle":"2022-02-17T16:12:44.415687Z","shell.execute_reply.started":"2022-02-17T16:12:30.403719Z","shell.execute_reply":"2022-02-17T16:12:44.414715Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1C40F'>6 |</span> FINDING THE MOST COMMON CALL FREQUENCY</b>\n\n- As outlined in the introduction, we are most interested in **inspecting all of the available data** to us for one particular bird specie.\n- We'll be investigating the audio recordings of the **<mark style=\"background-color:#FFC300;color:white;border-radius:5px;opacity:0.7\">rugdov (Ruddy Ground Dove)</mark>**, setting the cutoff to 60 seconds per audio recording; <b>cfg.cutoff = 60</b>\n- We have about 60 entries:\n> And let's **loop through all audio recordings** & **store the peak location** results into the global list <b>glst_peak_model</b> & then simply use the counter module; <b>Counter</b> to count all the unique entries.","metadata":{}},{"cell_type":"code","source":"''' Plot Counter Values'''\ndef pxcounter(Counter):\n    fig = px.bar(x=Counter.keys(),y=Counter.values(),color=Counter.values(),color_continuous_scale  ='viridis')\n    fig.update_layout(margin=dict(l=30, r=30, t=70, b=30),height=300,coloraxis_showscale=False,showlegend=False)\n    fig.update_layout(template='plotly_white',font=dict(family='sans-serif',size=14))\n    fig.update_layout(title=f'<b>MODEL PEAK COUNT</b> | FOR DIFFERENT PIXEL/FREQUENCY VALUES',\n                      font=dict(family='sans-serif',size=12))\n    fig.show()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2022-02-17T16:12:44.417281Z","iopub.execute_input":"2022-02-17T16:12:44.417812Z","iopub.status.idle":"2022-02-17T16:12:44.426802Z","shell.execute_reply.started":"2022-02-17T16:12:44.417774Z","shell.execute_reply":"2022-02-17T16:12:44.425534Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cfg.sshape = (48*2,128*2)\ncfg.pw = 1; cfg.cutoff = 60; cfg.offset = 0\ncfg.fmin = 0; cfg.fmax = 12500; cfg.model_bins = 20\n\nglst_peak_model = []\nwith tqdm_notebook(total=60) as pbar:\n    for recording in range(0,60):\n        pbar.update(1)\n        out = get_spectrograms(filepath=subset_path[recording],\n                               primary_label='temp',\n                               output_dir=DIR_SPEC_OUT,\n                               save_id=False,\n                               audio_id=False,\n                               select_id='short',\n                               plot_id=False,\n                               store_id=True,\n                               height=450)","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:44.428646Z","iopub.execute_input":"2022-02-17T16:12:44.429122Z","iopub.status.idle":"2022-02-17T16:12:58.980419Z","shell.execute_reply.started":"2022-02-17T16:12:44.429078Z","shell.execute_reply":"2022-02-17T16:12:58.978208Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"glo_count = Counter(glst_peak_model)\npxcounter(glo_count) # y axis represents the peak occurence count, x axis represents the vertical pixel value","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:58.981349Z","iopub.status.idle":"2022-02-17T16:12:58.981804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <b><span style='color:#F1C40F'>7 |</span> OTHER APPLICATIONS</b>\n\n### <b><span style='color:#F1C40F'>FREQUENCY/TIME MASKING</span></b>\n- There are quite a lot of applications for such a model based broadband estimation approach, I will not outline them all since they aren't directly related to the application to neural networks.\n- I'll instead focus my attention on audio <b>augmentations</b> which are commonly used in audio CNN applications; <b>frequency</b> & <b>time masking</b> & can be quite useful to create more generalised CNN models given that PyTorch is quite popular. An example implementation of PyTorch augmentations can be found in the notebook; [Histopathologic Cancer Detection w/ Pytorch](https://www.kaggle.com/shtrausslearning/binary-cancer-image-classification-w-pytorch) in Section <b>4. Transforming the Data</b>.\n- By creating a library that indicates at which frequency birds tend to call, you can utilise these functions below & modify them to prevent the augmentation from randomly cutting out the frequency bin which we identified to be the most common to that bird.\n- I'm sure you can think of some other applications for frequency & time masking as well, i've added the functions below which should get you started.","metadata":{}},{"cell_type":"code","source":"''' FREQUENCY MASKING '''\n# transform.Compose([hzmask(max_width=5, \n#                           use_mean=False)])\nclass hzmask(object):\n\n    def __init__(self, max_width, use_mean=True):\n        self.max_width = max_width\n        self.use_mean = use_mean\n\n    def __call__(self, tensor):\n        start = random.randrange(0, tensor.shape[2])\n        end = start + random.randrange(1, self.max_width)\n        if self.use_mean:\n            tensor[:, start:end, :] = tensor.mean()\n        else:\n            tensor[:, start:end, :] = 0\n        return tensor\n\n    def __repr__(self):\n        format_string = self.__class__.__name__ + \"(max_width=\"\n        format_string += str(self.max_width) + \")\"\n        format_string += 'use_mean=' + (str(self.use_mean) + ')')\n\n        return format_string\n    \n''' TIME MASKING '''\n# transform.Compose([tmask(max_width=5, \n#                          use_mean=False)])\nclass tmask(object):\n\n    def __init__(self, max_width, use_mean=True):\n        self.max_width = max_width\n        self.use_mean = use_mean\n\n    def __call__(self, tensor):\n        start = random.randrange(0, tensor.shape[1])\n        end = start + random.randrange(0, self.max_width)\n        if self.use_mean:\n            tensor[:, :, start:end] = tensor.mean()\n        else:\n            tensor[:, :, start:end] = 0\n        return tensor\n\n    def __repr__(self):\n        format_string = self.__class__.__name__ + \"(max_width=\"\n        format_string += str(self.max_width) + \")\"\n        format_string += 'use_mean=' + (str(self.use_mean) + ')')\n        return format_string","metadata":{"execution":{"iopub.status.busy":"2022-02-17T16:12:58.982848Z","iopub.status.idle":"2022-02-17T16:12:58.983304Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### <b><span style='color:#F1C40F'>OTHER ALTERNATIVES</span></b>\n\n- The utilisation of a model is mainly down to me wanting something that will differentiate <b>broadband</b> & <b>tonal</b> noises sources in the spectogram & use it for the analyses of spectogram data. \n- It could well be that simply using the maximum value in each bin can be more benefitial from the point of execution time. The fact that I'm using my own model class also doesn't help since it's not optmised for speed, although for some reason I didn't notice any difference between catboost, which is probably down to the fact that I manually select hyperparameters.\n- If you have any suggestions for improvements or questions let me know.","metadata":{}}]}