{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Acknowledgement :\n* This is a forked kernel. I forked it from [@shams1](https://www.kaggle.com/shams1/audio-data-analysis-using-librosa) who in turn forked it from [@hamditarek](https://www.kaggle.com/hamditarek/audio-data-analysis-using-librosa). \n* Well, I worked on EDA part with more details. I am new to work with audio data. This is the first time I am trying it. I acknowledge tons of thanks to [@hamditarek](https://www.kaggle.com/hamditarek) for sharing his works using Librosa.\n* I also learnt a lot from [this kernel](https://www.kaggle.com/jaseemck/audio-processing-using-librosa-for-beginners/comments) made by [@jaseemck](https://www.kaggle.com/jaseemck). Many thanks to him as well for sharing his work."},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install librosa","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os\nimport warnings\nwarnings.filterwarnings(\"ignore\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Loading and Playing an Audio File:\nLoading with librosa returns an audio file as a numpy array with a default sampling rate (sr) of 22 Kilo Hz mono. We can change this default frequency by resampling at 85 Kilo Hz (did it here for Vesspa in the end, you will notice the **difference in pitch**). Thereafter, using IPython.display.Audio we can play the audio in your jupyter notebook."},{"metadata":{"trusted":true},"cell_type":"code","source":"import librosa\naudio_data_ameavo = '../input/birdsong-recognition/train_audio/ameavo/XC133080.mp3' #take any sample input .mp3\naudio_data_mouchi = '../input/birdsong-recognition/train_audio/mouchi/XC109939.mp3'\naudio_data_nutwoo = '../input/birdsong-recognition/train_audio/nutwoo/XC109667.mp3'\naudio_data_pygnut = '../input/birdsong-recognition/train_audio/pygnut/XC11739.mp3'\naudio_data_vesspa = '../input/birdsong-recognition/train_audio/vesspa/XC102960.mp3'\naudio_data_yetvir = '../input/birdsong-recognition/train_audio/yetvir/XC120867.mp3'\nx_ameavo , sr = librosa.load(audio_data_ameavo)\nx_mouchi , sr = librosa.load(audio_data_mouchi)\nx_nutwoo , sr = librosa.load(audio_data_nutwoo)\nx_pygnut , sr = librosa.load(audio_data_pygnut)\nx_vesspa , sr = librosa.load(audio_data_vesspa)\nx_yetvir , sr = librosa.load(audio_data_yetvir)\n\nprint(type(x_ameavo), type(sr))\nprint(\"Ameavo :\", x_ameavo.shape, sr)\nprint(\"Mouchi :\", x_mouchi.shape, sr)\nprint(\"Nutwoo :\", x_nutwoo.shape, sr)\nprint(\"Pygnut :\", x_pygnut.shape, sr)\nprint(\"Vesspa :\", x_vesspa.shape, sr)\nprint(\"Yetvir :\", x_yetvir.shape, sr)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"librosa.load(audio_data_ameavo)\nlibrosa.load(audio_data_mouchi)\nlibrosa.load(audio_data_nutwoo)\nlibrosa.load(audio_data_pygnut)\nlibrosa.load(audio_data_vesspa)\nlibrosa.load(audio_data_yetvir)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import IPython.display as ipd\nprint(\"Song of Ameavo :\")\nipd.Audio(audio_data_ameavo)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Song of Mouchi :\")\nipd.Audio(audio_data_mouchi)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Song of Nutwoo :\")\nipd.Audio(audio_data_nutwoo)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Song of Pygnut :\")\nipd.Audio(audio_data_pygnut)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Song of Vesspa :\")\nipd.Audio(audio_data_vesspa)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Song of Yetvir :\")\nipd.Audio(audio_data_yetvir)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Changing default frequency\nlibrosa.load(audio_data_vesspa, sr=85000)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"ipd.Audio(audio_data_vesspa)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Spectral Representations\n\n* Short-time Fourier transform (STFT) underlies most analysis. \n* librosa.stft returns a complex matrix D. \n* D[f, t] is the fast Fourier transform (FFT) value at frequency f, time (frame) t.\n* librosa.stft() converts data into short term Fourier transform. [STFT](https://www.youtube.com/watch?v=g1_wcbGUcDY) converts signals so that we can know the amplitude of the given frequency at a given time. Using STFT we can determine the amplitude of various frequencies playing at a given time of an audio signal."},{"metadata":{"trusted":true},"cell_type":"code","source":"D_ameavo = librosa.stft(x_ameavo)\nD_mouchi = librosa.stft(x_mouchi)\nD_nutwoo = librosa.stft(x_nutwoo)\nD_pygnut = librosa.stft(x_pygnut)\nD_vesspa = librosa.stft(x_vesspa)\nD_yetvir = librosa.stft(x_yetvir)\nprint(\"For Ameavo :\", D_ameavo.shape, D_ameavo.dtype)\nprint(\"For Mouchi :\", D_mouchi.shape, D_mouchi.dtype)\nprint(\"For Nutwoo :\", D_nutwoo.shape, D_nutwoo.dtype)\nprint(\"For Pygnut :\", D_pygnut.shape, D_pygnut.dtype)\nprint(\"For Vesspa :\", D_vesspa.shape, D_vesspa.dtype)\nprint(\"For Yetvir :\", D_yetvir.shape, D_yetvir.dtype)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"For observation, since we changed the frequency of Vesspa's sound wave from default 22 KHz to 85 KHz, we can see the reflection of that in the shape of D matrix. It has the highest dimension of columns in its 2-dim STFT array (4460).\n\nOften, we only care about the magnitude. D contains both magnitude S and phase $\\phi$.\n\n$$\nD_{ft} = S_{ft} \\exp\\left(j \\phi_{ft}\\right)\n$$"},{"metadata":{"trusted":true},"cell_type":"code","source":"S, phase = librosa.magphase(D_ameavo)\nprint(S.dtype, phase.dtype, np.allclose(D_ameavo, S * phase))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Constant-Q Transforms\nThe CQT gives a logarithmically spaced frequency basis. This representation is more natural for many analysis tasks."},{"metadata":{"trusted":true},"cell_type":"code","source":"C = librosa.cqt(x_ameavo, sr=sr)\nprint(C.shape, C.dtype)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Visualizing Audio:\n\nWe can plot the audio array using librosa.display.waveplot."},{"metadata":{"trusted":true},"cell_type":"code","source":"%matplotlib inline\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nplt.style.use('ggplot')\nimport librosa.display\n\nfig, ax = plt.subplots(6, figsize = (16, 12))\nfig.suptitle('Sound Waves', fontsize=14)\nlibrosa.display.waveplot(x_ameavo, sr = sr, ax=ax[0])\nax[0].set_ylabel('Ameavo')\nlibrosa.display.waveplot(x_mouchi, sr = sr, ax=ax[1])\nax[1].set_ylabel('Mouchi')\nlibrosa.display.waveplot(x_nutwoo, sr = sr, ax=ax[2])\nax[2].set_ylabel('Nutwoo')\nlibrosa.display.waveplot(x_pygnut, sr = sr, ax=ax[3])\nax[3].set_ylabel('Pygnut')\nlibrosa.display.waveplot(x_vesspa, sr = sr, ax=ax[4])\nax[4].set_ylabel('Vesspa')\nlibrosa.display.waveplot(x_yetvir, sr = sr, ax=ax[5])\nax[5].set_ylabel('Yetvir')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Spectrogram\n* A spectrogram is a visual way of representing the signal strength (loudness) over time at various frequencies present in a particular waveform. Not only one can see whether there is more or less energy at a point (for e.g. 2 Hz vs 10 Hz), but also one can visualize how energy levels are varying over time.\n\n* A spectrogram is usually depicted as a [heat map](https://en.wikipedia.org/wiki/Heat_map), i.e., as an image with the intensity shown by varying the color or brightness.\n\n* We can display a spectrogram using librosa.display.specshow.\n\n* As we mentioned earlier, librosa.stft() converts data into short term Fourier transform. [STFT](https://www.youtube.com/watch?v=g1_wcbGUcDY) converts signals so that we can know the amplitude of the given frequency at a given time. Using STFT we can determine the amplitude of various frequencies playing at a given time of an audio signal.\n\n* The vertical axis shows frequencies (from 0 to 10kHz), and the horizontal axis shows the time of the clip."},{"metadata":{"trusted":true},"cell_type":"code","source":"X_ameavo = librosa.stft(x_ameavo)\nX_mouchi = librosa.stft(x_mouchi)\nX_nutwoo = librosa.stft(x_nutwoo)\nX_pygnut = librosa.stft(x_pygnut)\nX_vesspa = librosa.stft(x_vesspa)\nX_yetvir = librosa.stft(x_yetvir)\n\nXdb_ameavo = librosa.amplitude_to_db(abs(X_ameavo))\nXdb_mouchi = librosa.amplitude_to_db(abs(X_mouchi))\nXdb_nutwoo = librosa.amplitude_to_db(abs(X_nutwoo))\nXdb_pygnut = librosa.amplitude_to_db(abs(X_pygnut))\nXdb_vesspa = librosa.amplitude_to_db(abs(X_vesspa))\nXdb_yetvir = librosa.amplitude_to_db(abs(X_yetvir))\n\nfig, ax = plt.subplots(2, 3, figsize=(16, 10))\nfig.suptitle('Spectrogram', fontsize=12)\n\nlibrosa.display.specshow(Xdb_ameavo, sr = sr, x_axis = 'time', y_axis = 'hz', ax=ax[0, 0])\nax[0, 0].set_title('Ameavo', fontsize = 12)\nlibrosa.display.specshow(Xdb_mouchi, sr = sr, x_axis = 'time', y_axis = 'hz', ax=ax[0, 1])\nax[0, 1].set_title('Mouchi', fontsize = 12)\nlibrosa.display.specshow(Xdb_nutwoo, sr = sr, x_axis = 'time', y_axis = 'hz', ax=ax[0, 2])\nax[0, 2].set_title('Nutwoo', fontsize = 12)\nlibrosa.display.specshow(Xdb_pygnut, sr = sr, x_axis = 'time', y_axis = 'hz', ax=ax[1, 0])\nax[1, 0].set_title('Pygnut', fontsize = 12)\nlibrosa.display.specshow(Xdb_vesspa, sr = sr, x_axis = 'time', y_axis = 'hz', ax=ax[1, 1])\nax[1, 1].set_title('Vesspa', fontsize = 12)\nlibrosa.display.specshow(Xdb_yetvir, sr = sr, x_axis = 'time', y_axis = 'hz', ax=ax[1, 2])\nax[1, 2].set_title('Yetvir', fontsize = 12)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Since we see that all action is taking place at the bottom of the spectrum, we can convert the frequency axis to a **logarithmic** one."},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots(2, 3, figsize=(16, 10))\nfig.suptitle('Spectrogram with Log Values of Frequency', fontsize=12)\n\nlibrosa.display.specshow(Xdb_ameavo, sr=sr, x_axis='time', y_axis='log', ax=ax[0, 0])\nax[0, 0].set_title('Ameavo', fontsize = 12)\nlibrosa.display.specshow(Xdb_mouchi, sr=sr, x_axis='time', y_axis='log', ax=ax[0, 1])\nax[0, 1].set_title('Mouchi', fontsize = 12)\nlibrosa.display.specshow(Xdb_nutwoo, sr=sr, x_axis='time', y_axis='log', ax=ax[0, 2])\nax[0, 2].set_title('Nutwoo', fontsize = 12)\nlibrosa.display.specshow(Xdb_pygnut, sr=sr, x_axis='time', y_axis='log', ax=ax[1, 0])\nax[1, 0].set_title('Pygnut', fontsize = 12)\nlibrosa.display.specshow(Xdb_vesspa, sr=sr, x_axis='time', y_axis='log', ax=ax[1, 1])\nax[1, 1].set_title('Vesspa', fontsize = 12)\nlibrosa.display.specshow(Xdb_yetvir, sr=sr, x_axis='time', y_axis='log', ax=ax[1, 2])\nax[1, 2].set_title('Yetvir', fontsize = 12)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Creating an Audio Signal:"},{"metadata":{"trusted":true},"cell_type":"code","source":"sr = 22000 # sample rate\nT = 5.0    # seconds\nt = np.linspace(0, T, int(T*sr), endpoint=False) # time variable\nx = 0.5*np.sin(2*np.pi*220*t) # pure sine wave at 220 Hz\n#Playing the audio\nipd.Audio(x, rate=sr) \n#Saving the audio\nlibrosa.output.write_wav('tone_220.wav', x, sr)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import sklearn\nspectral_centroids = librosa.feature.spectral_centroid(x, sr=sr)[0]\nspectral_centroids.shape\n# Computing the time variable for visualization\nplt.figure(figsize=(14, 6))\nframes = range(len(spectral_centroids))\nt = librosa.frames_to_time(frames)\n# Normalising the spectral centroid for visualisation\ndef normalize(x, axis=0):\n    return sklearn.preprocessing.minmax_scale(x, axis=axis)\n#Plotting the Spectral Centroid along the waveform\nlibrosa.display.waveplot(x, sr=sr, alpha=0.3)\nplt.plot(t, normalize(spectral_centroids), linewidth=1.5, color='r')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"librosa.feature.spectral_centroid returns an array with columns equal to a number of frames present in the audio sample."},{"metadata":{},"cell_type":"markdown","source":"# Spectral Rolloff\n* It is a measure of the shape of the signal. It represents the frequency at which high frequencies decline to 0. To obtain it, we have to calculate the fraction of bins in the power spectrum where 85% of its power is at lower frequencies.\n\n* librosa.feature.spectral_rolloff computes the rolloff frequency for each frame in an audio signal."},{"metadata":{"trusted":true},"cell_type":"code","source":"spectral_rolloff = librosa.feature.spectral_rolloff(x+0.01, sr=sr)[0]\nplt.figure(figsize=(12, 5))\nlibrosa.display.waveplot(x, sr=sr, alpha=0.3)\nplt.plot(t, normalize(spectral_rolloff), linewidth=1.5, color='r')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Spectral Bandwidth\n\nThe spectral bandwidth is defined as the width of the band of light at one-half the peak maximum (or full width at half maximum [FWHM]) and is represented by the two vertical red lines and λSB on the wavelength axis."},{"metadata":{"trusted":true},"cell_type":"code","source":"spectral_bandwidth_2 = librosa.feature.spectral_bandwidth(x+0.01, sr=sr)[0]\nspectral_bandwidth_3 = librosa.feature.spectral_bandwidth(x+0.01, sr=sr, p=3)[0]\nspectral_bandwidth_4 = librosa.feature.spectral_bandwidth(x+0.01, sr=sr, p=4)[0]\nspectral_bandwidth_5 = librosa.feature.spectral_bandwidth(x+0.01, sr=sr, p=5)[0]\nplt.figure(figsize=(12, 6))\nlibrosa.display.waveplot(x, sr=sr, alpha=0.3)\nplt.plot(t, normalize(spectral_bandwidth_2), linewidth=1.5, color='r')\nplt.plot(t, normalize(spectral_bandwidth_3), linewidth=1.5, color='g')\nplt.plot(t, normalize(spectral_bandwidth_4), linewidth=1.5, color='y')\nplt.plot(t, normalize(spectral_bandwidth_5), linewidth=1.5, color='b')\nplt.legend(('p = 2', 'p = 3', 'p = 4', 'p = 5'))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Zoom-In Waveplots for Different Birds:\n\nLet is deep-dive and zoom-in the waveplots of different birds to see the difference in pattern minutely."},{"metadata":{"trusted":true},"cell_type":"code","source":"#Plot the signal for Ameavo:\nplt.figure(figsize=(14, 5))\nlibrosa.display.waveplot(x_ameavo, sr=sr)\nplt.title('Normal Waveplot for Ameavo')\n# Zooming in\nn0 = 8800\nn1 = 9000\nplt.figure(figsize=(14, 6))\nplt.plot(x_ameavo[n0:n1], linewidth=1.5)\nplt.grid()\nplt.title('Zoomed-In Waveplot for Ameavo')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Plot the signal for Mouchi:\nplt.figure(figsize=(14, 5))\nlibrosa.display.waveplot(x_mouchi, sr=sr)\nplt.title('Normal Waveplot for Mouchi')\n# Zooming in\nn0 = 8800\nn1 = 9000\nplt.figure(figsize=(14, 6))\nplt.plot(x_mouchi[n0:n1], linewidth=1.5)\nplt.grid()\nplt.title('Zoomed-In Waveplot for Mouchi')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Plot the signal for Nutwoo:\nplt.figure(figsize=(14, 5))\nlibrosa.display.waveplot(x_nutwoo, sr=sr)\nplt.title('Normal Waveplot for Nutwoo')\n# Zooming in\nn0 = 8800\nn1 = 9000\nplt.figure(figsize=(14, 6))\nplt.plot(x_nutwoo[n0:n1], linewidth=1.5)\nplt.grid()\nplt.title('Zoomed-In Waveplot for Nutwoo')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Plot the signal for Pygnut:\nplt.figure(figsize=(14, 5))\nlibrosa.display.waveplot(x_pygnut, sr=sr)\nplt.title('Normal Waveplot for Pygnut')\n# Zooming in\nn0 = 8800\nn1 = 9000\nplt.figure(figsize=(14, 6))\nplt.plot(x_pygnut[n0:n1], linewidth=1.5)\nplt.grid()\nplt.title('Zoomed-In Waveplot for Pygnut')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Note :** For observation, Pygnut has the waveplot on the positive side only. We will see an interesting observation in the no. of zero crossings later."},{"metadata":{"trusted":true},"cell_type":"code","source":"#Plot the signal for Vesspa:\nplt.figure(figsize=(14, 5))\nlibrosa.display.waveplot(x_vesspa, sr=sr)\nplt.title('Normal Waveplot for Vesspa')\n# Zooming in\nn0 = 8800\nn1 = 9000\nplt.figure(figsize=(14, 6))\nplt.plot(x_vesspa[n0:n1], linewidth=1.5)\nplt.grid()\nplt.title('Zoomed-In Waveplot for Vesspa')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Plot the signal for Yetvir:\nplt.figure(figsize=(14, 5))\nlibrosa.display.waveplot(x_yetvir, sr=sr)\nplt.title('Normal Waveplot for Yetvir')\n# Zooming in\nn0 = 8800\nn1 = 9000\nplt.figure(figsize=(14, 6))\nplt.plot(x_yetvir[n0:n1], linewidth=1.5)\nplt.grid()\nplt.title('Zoomed-In Waveplot for Yetvir')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## How many Zero Crossings?\n\nThe zero crossing rate is the rate of sign-changes along a signal, i.e., the rate at which the signal changes from positive to negative or back. This feature has been used heavily in both speech recognition and music information retrieval (Ref: [Towards Data Science Post](https://towardsdatascience.com/extract-features-of-music-75a3f9bc265d#:~:text=The%20zero%20crossing%20rate%20is,recognition%20and%20music%20information%20retrieval.))."},{"metadata":{"trusted":true},"cell_type":"code","source":"zero_crossings_ameavo = librosa.zero_crossings(x_ameavo[n0:n1], pad=False)\nzero_crossings_mouchi = librosa.zero_crossings(x_mouchi[n0:n1], pad=False)\nzero_crossings_nutwoo = librosa.zero_crossings(x_nutwoo[n0:n1], pad=False)\nzero_crossings_pygnut = librosa.zero_crossings(x_pygnut[n0:n1], pad=False)\nzero_crossings_vesspa = librosa.zero_crossings(x_vesspa[n0:n1], pad=False)\nzero_crossings_yetvir = librosa.zero_crossings(x_yetvir[n0:n1], pad=False)\n\nprint(\"No. of Zero Crossings :\")\nprint('For Ameavo :', sum(zero_crossings_ameavo))\nprint('For Mouchi :', sum(zero_crossings_mouchi))\nprint('For Nutwoo :', sum(zero_crossings_nutwoo))\nprint('For Pygnut :', sum(zero_crossings_pygnut))\nprint('For Vesspa :', sum(zero_crossings_vesspa))\nprint('For Yetvir :', sum(zero_crossings_yetvir))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Observation :** For Pygnut, the no. of zero-crossing is '0'. It is evident from the zoomed-in waveplot above where we can see the curve on the positive side of X-axis only."},{"metadata":{},"cell_type":"markdown","source":"# Mel-Frequency Cepstral Coefficients(MFCCs)\n\nIn sound processing, the mel-frequency cepstrum (MFC) is a representation of the short-term power spectrum of a sound, based on a linear cosine transform of a log power spectrum on a nonlinear mel scale of frequency. Mel-frequency cepstral coefficients (MFCCs) are coefficients that collectively make up an MFC. (Ref: [Wiki](https://en.wikipedia.org/wiki/Mel-frequency_cepstrum))"},{"metadata":{"trusted":true},"cell_type":"code","source":"fs=15\nmfccs_ameavo = librosa.feature.mfcc(x_ameavo, sr=fs)\nmfccs_mouchi = librosa.feature.mfcc(x_mouchi, sr=fs)\nmfccs_nutwoo = librosa.feature.mfcc(x_nutwoo, sr=fs)\nmfccs_pygnut = librosa.feature.mfcc(x_pygnut, sr=fs)\nmfccs_vesspa = librosa.feature.mfcc(x_vesspa, sr=fs)\nmfccs_yetvir = librosa.feature.mfcc(x_yetvir, sr=fs)\n\nprint(\"MFCC shapes :\")\nprint(\"Ameavo :\", mfccs_ameavo.shape)\nprint(\"Mouchi :\", mfccs_mouchi.shape)\nprint(\"Nutwoo :\", mfccs_nutwoo.shape)\nprint(\"Pygnut :\", mfccs_pygnut.shape)\nprint(\"Vesspa :\", mfccs_vesspa.shape)\nprint(\"Yetvir :\", mfccs_yetvir.shape)\n\n#Displaying  the MFCCs:\nfig, ax = plt.subplots(6, figsize = (16, 14))\nfig.suptitle('MFCCs Display', fontsize=14)\nlibrosa.display.specshow(mfccs_ameavo, sr=sr, x_axis='time', cmap='spring', ax=ax[0])\nax[0].set_ylabel('Ameavo')\nlibrosa.display.specshow(mfccs_mouchi, sr=sr, x_axis='time', cmap='spring', ax=ax[1])\nax[1].set_ylabel('Mouchi')\nlibrosa.display.specshow(mfccs_nutwoo, sr=sr, x_axis='time', cmap='spring', ax=ax[2])\nax[2].set_ylabel('Nutwoo')\nlibrosa.display.specshow(mfccs_pygnut, sr=sr, x_axis='time', cmap='spring', ax=ax[3])\nax[3].set_ylabel('Pygnut')\nlibrosa.display.specshow(mfccs_vesspa, sr=sr, x_axis='time', cmap='spring', ax=ax[4])\nax[4].set_ylabel('Vesspa')\nlibrosa.display.specshow(mfccs_yetvir, sr=sr, x_axis='time', cmap='spring', ax=ax[5])\nax[5].set_ylabel('Yetvir')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Chroma Feature\n\nIn music, the term \"chroma feature\" or \"chromagram\" closely relates to the 12 different pitch classes. Chroma-based features, which are also referred to as \"pitch class profiles\", are a powerful tool for analyzing music whose pitches can be meaningfully categorized (often into twelve categories) and whose tuning approximates to the equal-tempered scale. One main property of chroma features is that they capture harmonic and melodic characteristics of music, while being robust to changes in timbre and instrumentation. (Ref: [Wiki](https://en.wikipedia.org/wiki/Chroma_feature))\n\nHere, we have experimented with a hop-length of 12."},{"metadata":{"trusted":true},"cell_type":"code","source":"hop_length=12\nchromagram_ameavo = librosa.feature.chroma_stft(x_ameavo, sr=sr, hop_length=hop_length)\nchromagram_mouchi = librosa.feature.chroma_stft(x_mouchi, sr=sr, hop_length=hop_length)\nchromagram_nutwoo = librosa.feature.chroma_stft(x_nutwoo, sr=sr, hop_length=hop_length)\nchromagram_pygnut = librosa.feature.chroma_stft(x_pygnut, sr=sr, hop_length=hop_length)\nchromagram_vesspa = librosa.feature.chroma_stft(x_vesspa, sr=sr, hop_length=hop_length)\nchromagram_yetvir = librosa.feature.chroma_stft(x_yetvir, sr=sr, hop_length=hop_length)\n\nfig, ax = plt.subplots(6, figsize = (16, 14))\nfig.suptitle('Chromagrams Display', fontsize=14)\nlibrosa.display.specshow(chromagram_ameavo, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='spring', ax=ax[0])\nax[0].set_ylabel('Ameavo')\nlibrosa.display.specshow(chromagram_mouchi, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='spring', ax=ax[1])\nax[1].set_ylabel('Mouchi')\nlibrosa.display.specshow(chromagram_nutwoo, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='spring', ax=ax[2])\nax[2].set_ylabel('Nutwoo')\nlibrosa.display.specshow(chromagram_pygnut, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='spring', ax=ax[3])\nax[3].set_ylabel('Pygnut')\nlibrosa.display.specshow(chromagram_vesspa, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='spring', ax=ax[4])\nax[4].set_ylabel('Vesspa')\nlibrosa.display.specshow(chromagram_yetvir, x_axis='time', y_axis='chroma', hop_length=hop_length, cmap='spring', ax=ax[5])\nax[5].set_ylabel('Yetvir')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Resnet Model\n\n**Acknowledgement :** For the model, please refer to the original kernel [inference, PyTorch Birdcall ResNet Baseline](https://www.kaggle.com/hidehisaarai1213/inference-pytorch-birdcall-resnet-baseline) by [Hidehisa](https://www.kaggle.com/hidehisaarai1213) and the fine tuned kernel: [resnet-vers.](https://www.kaggle.com/manavtrivedi/resnet-vers/notebook) by [Manav](https://www.kaggle.com/manavtrivedi)"},{"metadata":{"trusted":true},"cell_type":"code","source":"import cv2\nimport audioread\nimport logging\nimport os\nimport random\nimport time\nimport warnings\n\nimport librosa\nimport numpy as np\nimport pandas as pd\nimport soundfile as sf\nimport torch\nimport torch.nn as nn\nimport torch.cuda\nimport torch.nn.functional as F\nimport torch.utils.data as data\n\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom fastprogress import progress_bar\nfrom sklearn.metrics import f1_score\nfrom torchvision import models","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def set_seed(seed: int = 12345):\n    random.seed(seed)\n    np.random.seed(seed)\n    os.environ[\"PYTHONHASHSEED\"] = str(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)  # type: ignore\n    torch.backends.cudnn.deterministic = True  # type: ignore\n    torch.backends.cudnn.benchmark = True  # type: ignore\n    \n    \ndef get_logger(out_file=None):\n    logger = logging.getLogger()\n    formatter = logging.Formatter(\"%(asctime)s - %(levelname)s - %(message)s\")\n    logger.handlers = []\n    logger.setLevel(logging.INFO)\n\n    handler = logging.StreamHandler()\n    handler.setFormatter(formatter)\n    handler.setLevel(logging.INFO)\n    logger.addHandler(handler)\n\n    if out_file is not None:\n        fh = logging.FileHandler(out_file)\n        fh.setFormatter(formatter)\n        fh.setLevel(logging.INFO)\n        logger.addHandler(fh)\n    logger.info(\"logger set up\")\n    return logger\n    \n    \n@contextmanager\ndef timer(name: str, logger: Optional[logging.Logger] = None):\n    t0 = time.time()\n    msg = f\"[{name}] start\"\n    if logger is None:\n        print(msg)\n    else:\n        logger.info(msg)\n    yield\n\n    msg = f\"[{name}] done in {time.time() - t0:.2f} s\"\n    if logger is None:\n        print(msg)\n    else:\n        logger.info(msg)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"logger = get_logger(\"main.log\")\nset_seed(12345)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TARGET_SR = 36000\nTEST = Path(\"../input/birdsong-recognition/test_audio\").exists()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"if TEST:\n    DATA_DIR = Path(\"../input/birdsong-recognition/\")\nelse:\n    # dataset created by @shonenkov, thanks!\n    DATA_DIR = Path(\"../input/birdcall-check/\")\n    \n\ntest = pd.read_csv(DATA_DIR / \"test.csv\")\ntest_audio = DATA_DIR / \"test_audio\"\n\n\ntest.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub = pd.read_csv(\"../input/birdsong-recognition/sample_submission.csv\")\nsub.to_csv(\"submission.csv\", index=False)  #will be overwritten","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class ResNet(nn.Module):\n    def __init__(self, base_model_name: str, pretrained=False,\n                 num_classes=264):\n        super().__init__()\n        base_model = models.__getattribute__(base_model_name)(\n            pretrained=pretrained)\n        layers = list(base_model.children())[:-2]\n        layers.append(nn.AdaptiveMaxPool2d(1))\n        self.encoder = nn.Sequential(*layers)\n\n        in_features = base_model.fc.in_features\n\n        self.classifier = nn.Sequential(\n            nn.Linear(in_features, 1024), nn.ReLU(), nn.Dropout(p=0.25),\n            nn.Linear(1024, 1024), nn.ReLU(), nn.Dropout(p=0.25),\n            nn.Linear(1024, num_classes))\n\n    def forward(self, x):\n        batch_size = x.size(0)\n        x = self.encoder(x).view(batch_size, -1)\n        x = self.classifier(x)\n        multiclass_proba = F.softmax(x, dim=1)\n        multilabel_proba = F.sigmoid(x)\n        return {\n            \"logits\": x,\n            \"multiclass_proba\": multiclass_proba,\n            \"multilabel_proba\": multilabel_proba\n        }","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model_config = {\n    \"base_model_name\": \"resnet50\",\n    \"pretrained\": False,\n    \"num_classes\": 264\n}\n\nmelspectrogram_parameters = {\n    \"n_mels\": 128,\n    \"fmin\": 20,\n    \"fmax\": 20000\n}\n\nweights_path = \"../input/birdcall-resnet50-init-weights/best.pth\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"BIRD_CODE = {\n    'aldfly': 0, 'ameavo': 1, 'amebit': 2, 'amecro': 3, 'amegfi': 4,\n    'amekes': 5, 'amepip': 6, 'amered': 7, 'amerob': 8, 'amewig': 9,\n    'amewoo': 10, 'amtspa': 11, 'annhum': 12, 'astfly': 13, 'baisan': 14,\n    'baleag': 15, 'balori': 16, 'banswa': 17, 'barswa': 18, 'bawwar': 19,\n    'belkin1': 20, 'belspa2': 21, 'bewwre': 22, 'bkbcuc': 23, 'bkbmag1': 24,\n    'bkbwar': 25, 'bkcchi': 26, 'bkchum': 27, 'bkhgro': 28, 'bkpwar': 29,\n    'bktspa': 30, 'blkpho': 31, 'blugrb1': 32, 'blujay': 33, 'bnhcow': 34,\n    'boboli': 35, 'bongul': 36, 'brdowl': 37, 'brebla': 38, 'brespa': 39,\n    'brncre': 40, 'brnthr': 41, 'brthum': 42, 'brwhaw': 43, 'btbwar': 44,\n    'btnwar': 45, 'btywar': 46, 'buffle': 47, 'buggna': 48, 'buhvir': 49,\n    'bulori': 50, 'bushti': 51, 'buwtea': 52, 'buwwar': 53, 'cacwre': 54,\n    'calgul': 55, 'calqua': 56, 'camwar': 57, 'cangoo': 58, 'canwar': 59,\n    'canwre': 60, 'carwre': 61, 'casfin': 62, 'caster1': 63, 'casvir': 64,\n    'cedwax': 65, 'chispa': 66, 'chiswi': 67, 'chswar': 68, 'chukar': 69,\n    'clanut': 70, 'cliswa': 71, 'comgol': 72, 'comgra': 73, 'comloo': 74,\n    'commer': 75, 'comnig': 76, 'comrav': 77, 'comred': 78, 'comter': 79,\n    'comyel': 80, 'coohaw': 81, 'coshum': 82, 'cowscj1': 83, 'daejun': 84,\n    'doccor': 85, 'dowwoo': 86, 'dusfly': 87, 'eargre': 88, 'easblu': 89,\n    'easkin': 90, 'easmea': 91, 'easpho': 92, 'eastow': 93, 'eawpew': 94,\n    'eucdov': 95, 'eursta': 96, 'evegro': 97, 'fiespa': 98, 'fiscro': 99,\n    'foxspa': 100, 'gadwal': 101, 'gcrfin': 102, 'gnttow': 103, 'gnwtea': 104,\n    'gockin': 105, 'gocspa': 106, 'goleag': 107, 'grbher3': 108, 'grcfly': 109,\n    'greegr': 110, 'greroa': 111, 'greyel': 112, 'grhowl': 113, 'grnher': 114,\n    'grtgra': 115, 'grycat': 116, 'gryfly': 117, 'haiwoo': 118, 'hamfly': 119,\n    'hergul': 120, 'herthr': 121, 'hoomer': 122, 'hoowar': 123, 'horgre': 124,\n    'horlar': 125, 'houfin': 126, 'houspa': 127, 'houwre': 128, 'indbun': 129,\n    'juntit1': 130, 'killde': 131, 'labwoo': 132, 'larspa': 133, 'lazbun': 134,\n    'leabit': 135, 'leafly': 136, 'leasan': 137, 'lecthr': 138, 'lesgol': 139,\n    'lesnig': 140, 'lesyel': 141, 'lewwoo': 142, 'linspa': 143, 'lobcur': 144,\n    'lobdow': 145, 'logshr': 146, 'lotduc': 147, 'louwat': 148, 'macwar': 149,\n    'magwar': 150, 'mallar3': 151, 'marwre': 152, 'merlin': 153, 'moublu': 154,\n    'mouchi': 155, 'moudov': 156, 'norcar': 157, 'norfli': 158, 'norhar2': 159,\n    'normoc': 160, 'norpar': 161, 'norpin': 162, 'norsho': 163, 'norwat': 164,\n    'nrwswa': 165, 'nutwoo': 166, 'olsfly': 167, 'orcwar': 168, 'osprey': 169,\n    'ovenbi1': 170, 'palwar': 171, 'pasfly': 172, 'pecsan': 173, 'perfal': 174,\n    'phaino': 175, 'pibgre': 176, 'pilwoo': 177, 'pingro': 178, 'pinjay': 179,\n    'pinsis': 180, 'pinwar': 181, 'plsvir': 182, 'prawar': 183, 'purfin': 184,\n    'pygnut': 185, 'rebmer': 186, 'rebnut': 187, 'rebsap': 188, 'rebwoo': 189,\n    'redcro': 190, 'redhea': 191, 'reevir1': 192, 'renpha': 193, 'reshaw': 194,\n    'rethaw': 195, 'rewbla': 196, 'ribgul': 197, 'rinduc': 198, 'robgro': 199,\n    'rocpig': 200, 'rocwre': 201, 'rthhum': 202, 'ruckin': 203, 'rudduc': 204,\n    'rufgro': 205, 'rufhum': 206, 'rusbla': 207, 'sagspa1': 208, 'sagthr': 209,\n    'savspa': 210, 'saypho': 211, 'scatan': 212, 'scoori': 213, 'semplo': 214,\n    'semsan': 215, 'sheowl': 216, 'shshaw': 217, 'snobun': 218, 'snogoo': 219,\n    'solsan': 220, 'sonspa': 221, 'sora': 222, 'sposan': 223, 'spotow': 224,\n    'stejay': 225, 'swahaw': 226, 'swaspa': 227, 'swathr': 228, 'treswa': 229,\n    'truswa': 230, 'tuftit': 231, 'tunswa': 232, 'veery': 233, 'vesspa': 234,\n    'vigswa': 235, 'warvir': 236, 'wesblu': 237, 'wesgre': 238, 'weskin': 239,\n    'wesmea': 240, 'wessan': 241, 'westan': 242, 'wewpew': 243, 'whbnut': 244,\n    'whcspa': 245, 'whfibi': 246, 'whtspa': 247, 'whtswi': 248, 'wilfly': 249,\n    'wilsni1': 250, 'wiltur': 251, 'winwre3': 252, 'wlswar': 253, 'wooduc': 254,\n    'wooscj2': 255, 'woothr': 256, 'y00475': 257, 'yebfly': 258, 'yebsap': 259,\n    'yehbla': 260, 'yelwar': 261, 'yerwar': 262, 'yetvir': 263\n}\n\nINV_BIRD_CODE = {v: k for k, v in BIRD_CODE.items()}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def mono_to_color(X: np.ndarray,\n                  mean=None,\n                  std=None,\n                  norm_max=None,\n                  norm_min=None,\n                  eps=1e-7):\n    \"\"\"\n    Code from https://www.kaggle.com/daisukelab/creating-fat2019-preprocessed-data\n    \"\"\"\n    # Stack X as [X,X,X]\n    X = np.stack([X, X, X], axis=-1)\n\n    # Standardize\n    mean = mean or X.mean()\n    X = X - mean\n    std = std or X.std()\n    Xstd = X / (std + eps)\n    _min, _max = Xstd.min(), Xstd.max()\n    norm_max = norm_max or _max\n    norm_min = norm_min or _min\n    if (_max - _min) > eps:\n        # Normalize to [0, 255]\n        V = Xstd\n        V[V < norm_min] = norm_min\n        V[V > norm_max] = norm_max\n        V = 255 * (V - norm_min) / (norm_max - norm_min)\n        V = V.astype(np.uint8)\n    else:\n        # Just zero\n        V = np.zeros_like(Xstd, dtype=np.uint8)\n    return V\n\n\nclass TestDataset(data.Dataset):\n    def __init__(self, df: pd.DataFrame, clip: np.ndarray,\n                 img_size=224, melspectrogram_parameters={}):\n        self.df = df\n        self.clip = clip\n        self.img_size = img_size\n        self.melspectrogram_parameters = melspectrogram_parameters\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx: int):\n        SR = 36000\n        sample = self.df.loc[idx, :]\n        site = sample.site\n        row_id = sample.row_id\n        \n        if site == \"site_3\":\n            y = self.clip.astype(np.float32)\n            len_y = len(y)\n            start = 0\n            end = SR * 5\n            images = []\n            while len_y > start:\n                y_batch = y[start:end].astype(np.float32)\n                if len(y_batch) != (SR * 5):\n                    break\n                start = end\n                end = end + SR * 5\n                \n                melspec = librosa.feature.melspectrogram(y_batch,\n                                                         sr=SR,\n                                                         **self.melspectrogram_parameters)\n                melspec = librosa.power_to_db(melspec).astype(np.float32)\n                image = mono_to_color(melspec)\n                height, width, _ = image.shape\n                image = cv2.resize(image, (int(width * self.img_size / height), self.img_size))\n                image = np.moveaxis(image, 2, 0)\n                image = (image / 255.0).astype(np.float32)\n                images.append(image)\n            images = np.asarray(images)\n            return images, row_id, site\n        else:\n            end_seconds = int(sample.seconds)\n            start_seconds = int(end_seconds - 5)\n            \n            start_index = SR * start_seconds\n            end_index = SR * end_seconds\n            \n            y = self.clip[start_index:end_index].astype(np.float32)\n\n            melspec = librosa.feature.melspectrogram(y, sr=SR, **self.melspectrogram_parameters)\n            melspec = librosa.power_to_db(melspec).astype(np.float32)\n\n            image = mono_to_color(melspec)\n            height, width, _ = image.shape\n            image = cv2.resize(image, (int(width * self.img_size / height), self.img_size))\n            image = np.moveaxis(image, 2, 0)\n            image = (image / 255.0).astype(np.float32)\n\n            return image, row_id, site","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_model(config: dict, weights_path: str):\n    model = ResNet(**config)\n    checkpoint = torch.load(weights_path)\n    model.load_state_dict(checkpoint[\"model_state_dict\"])\n    device = torch.device(\"cuda\")\n    model.to(device)\n    model.eval()\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def prediction_for_clip(test_df: pd.DataFrame, \n                        clip: np.ndarray, \n                        model: ResNet, \n                        mel_params: dict, \n                        threshold=0.60):\n\n    dataset = TestDataset(df=test_df, \n                          clip=clip,\n                          img_size=224,\n                          melspectrogram_parameters=mel_params)\n    loader = data.DataLoader(dataset, batch_size=1, shuffle=False)\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    \n    model.eval()\n    prediction_dict = {}\n    for image, row_id, site in progress_bar(loader):\n        site = site[0]\n        row_id = row_id[0]\n        if site in {\"site_1\", \"site_2\"}:\n            image = image.to(device)\n\n            with torch.no_grad():\n                prediction = model(image)\n                proba = prediction[\"multilabel_proba\"].detach().cpu().numpy().reshape(-1)\n\n            events = proba >= threshold\n            labels = np.argwhere(events).reshape(-1).tolist()\n\n        else:\n            # to avoid prediction on large batch\n            image = image.squeeze(0)\n            batch_size = 16\n            whole_size = image.size(0)\n            if whole_size % batch_size == 0:\n                n_iter = whole_size // batch_size\n            else:\n                n_iter = whole_size // batch_size + 1\n                \n            all_events = set()\n            for batch_i in range(n_iter):\n                batch = image[batch_i * batch_size:(batch_i + 1) * batch_size]\n                if batch.ndim == 3:\n                    batch = batch.unsqueeze(0)\n\n                batch = batch.to(device)\n                with torch.no_grad():\n                    prediction = model(batch)\n                    proba = prediction[\"multilabel_proba\"].detach().cpu().numpy()\n                    \n                events = proba >= threshold\n                for i in range(len(events)):\n                    event = events[i, :]\n                    labels = np.argwhere(event).reshape(-1).tolist()\n                    for label in labels:\n                        all_events.add(label)\n                        \n            labels = list(all_events)\n        if len(labels) == 0:\n            prediction_dict[row_id] = \"nocall\"\n        else:\n            labels_str_list = list(map(lambda x: INV_BIRD_CODE[x], labels))\n            label_string = \" \".join(labels_str_list)\n            prediction_dict[row_id] = label_string\n    return prediction_dict","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def prediction(test_df: pd.DataFrame,\n               test_audio: Path,\n               model_config: dict,\n               mel_params: dict,\n               weights_path: str,\n               threshold = 0.75):\n    model = get_model(model_config, weights_path)\n    unique_audio_id = test_df.audio_id.unique()\n\n    warnings.filterwarnings(\"ignore\")\n    prediction_dfs = []\n    for audio_id in unique_audio_id:\n        with timer(f\"Loading {audio_id}\", logger):\n            clip, _ = librosa.load(test_audio / (audio_id + \".mp3\"),\n                                   sr=TARGET_SR,\n                                   mono=True,\n                                   res_type=\"kaiser_fast\")\n        \n        test_df_for_audio_id = test_df.query(\n            f\"audio_id == '{audio_id}'\").reset_index(drop=True)\n        with timer(f\"Prediction on {audio_id}\", logger):\n            prediction_dict = prediction_for_clip(test_df_for_audio_id,\n                                                  clip=clip,\n                                                  model=model,\n                                                  mel_params=mel_params,\n                                                  threshold=threshold)\n        row_id = list(prediction_dict.keys())\n        birds = list(prediction_dict.values())\n        prediction_df = pd.DataFrame({\n            \"row_id\": row_id,\n            \"birds\": birds\n        })\n        prediction_dfs.append(prediction_df)\n    \n    prediction_df = pd.concat(prediction_dfs, axis=0, sort=False).reset_index(drop=True)\n    return prediction_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission = prediction(test_df=test,\n                        test_audio=test_audio,\n                        model_config=model_config,\n                        mel_params=melspectrogram_parameters,\n                        weights_path=weights_path,\n                        threshold=0.99)\nsubmission.to_csv(\"submission.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"submission successful\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"If you have found this kernel useful and are re-forking it, would appreciate if you **upvote** it as well! Thanks."},{"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}