{"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":"from fastai.vision.all import *\nimport librosa as librosa","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-03-30T21:56:29.378651Z","iopub.execute_input":"2022-03-30T21:56:29.379059Z","iopub.status.idle":"2022-03-30T21:56:33.586259Z","shell.execute_reply.started":"2022-03-30T21:56:29.378934Z","shell.execute_reply":"2022-03-30T21:56:33.585057Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"meta = pd.read_csv('../input/birdclef-2022/train_metadata.csv')\nmeta.head(3)","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:56:39.144912Z","iopub.execute_input":"2022-03-30T21:56:39.145252Z","iopub.status.idle":"2022-03-30T21:56:39.286787Z","shell.execute_reply.started":"2022-03-30T21:56:39.145216Z","shell.execute_reply":"2022-03-30T21:56:39.285727Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"eBird = pd.read_csv('../input/birdclef-2022/eBird_Taxonomy_v2021.csv')\neBird.head(2)","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:56:53.633831Z","iopub.execute_input":"2022-03-30T21:56:53.634183Z","iopub.status.idle":"2022-03-30T21:56:53.725225Z","shell.execute_reply.started":"2022-03-30T21:56:53.634135Z","shell.execute_reply":"2022-03-30T21:56:53.724029Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**CARGANDO AUDIOS Y CREANDO ESPECTROGRAMAS**","metadata":{}},{"cell_type":"code","source":"# Copiado de https://www.kaggle.com/code/johnowhitaker/baseline-model/notebook\n# Fuentes: \n#    https://medium.com/analytics-vidhya/understanding-the-mel-spectrogram-fca2afa2ce53\n#    https://stackoverflow.com/questions/62584184/understanding-the-shape-of-spectrograms-and-n-mels\n#    https://librosa.org/doc/0.7.2/generated/librosa.feature.melspectrogram.html\n#    https://es.wikipedia.org/wiki/Escala_Mel\n# Explicación:\n#   Convertir un trozo de audio de 5s en una imagen que lo representa\n#     y = Audio time-series    sr = Sampling rate of y       \n#     n_fft = length of the FFT window    (FFT) -> fast Fourier transform ,algoritmo que descompone una onda en un conjunto de ondas seno y coseno\n#                                                  Este se aplica a fragmentos de longitud   n_FFT\n#     hop_length = Distancia entre los fragmentos. Si  hop_length = n_fft, la cantidad de framentos sería tamaño_tot / n_fft. Sin embargo, normal-\n#                                             -mente, los fragmentos se solapan entre ellos. Pudiendo ser quizas la distancia n_fft/2 por ejemplo.\n#     n_mels = Numero de separaciones (equidistantes ante la percepción humana), del espectro de frecuencia\n#     fmin y fmax = Máximo y Mínmo valor en Hz\n#\n#     power_to_db = Convert a power spectrogram (amplitude squared) to decibel (dB) units\ndef chunk_to_spec(chunk, SPEC_HEIGHT=64,SPEC_WIDTH=256, rate=32000, FMIN=200, FMAX=12500):\n    mel_spec = librosa.feature.melspectrogram(y=chunk, \n                                              sr=32000, \n                                              n_fft=1024, \n                                              hop_length=int(32000 * 5 / (SPEC_WIDTH - 1)), \n                                              n_mels=SPEC_HEIGHT, \n                                              fmin=FMIN, \n                                              fmax=FMAX)\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    return mel_spec_db","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:56:57.919507Z","iopub.execute_input":"2022-03-30T21:56:57.919831Z","iopub.status.idle":"2022-03-30T21:56:57.928316Z","shell.execute_reply.started":"2022-03-30T21:56:57.919797Z","shell.execute_reply":"2022-03-30T21:56:57.927248Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#  Cargamos los Peaks extraidos en el notebook auxiliar\ndf = pd.read_csv('../input/peak-identification-rubio/info_df.csv')\nprint(df.shape)\nprint(df.fn[0])\ndf.head(2)","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:57:01.205771Z","iopub.execute_input":"2022-03-30T21:57:01.206835Z","iopub.status.idle":"2022-03-30T21:57:01.310589Z","shell.execute_reply.started":"2022-03-30T21:57:01.206786Z","shell.execute_reply":"2022-03-30T21:57:01.309872Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#  Muestra como Cargar una fila del fichero de Picos y cortar el audio correspondiente y el primer pico de la lista\n#\n#  Fuentes:\n#      https://librosa.org/doc/main/generated/librosa.load.html\n#  Explicación:\n#      fn = file name,    l = length,    y = label(s?),    peaks = picos\n#      pandas.sample() = Devuelve una fila aleatoria, o una fila dada, o un porcentaje de filas dado .sample(frac = .25)\n#\n#      librosa.load(fn = (nombre del archivo de audio), sr=(sound rate), offset=(inicio de fragmento elegido), duration=(5 segundos))\n#          -> devueleve:   y = audio time-series,    sr = sound rate\n#\n#      Tiempo inicial del corte es:\n#         - en el caso de que el audio original sea menor de 5s -> ¿negativo?\n#                                                                    ¿BUG?\n#         - en el caso de que el pico elegido esté mas allá de los últimos 5s del audio original -> los ultimos 5s\n#         - si el pico elegido está antes de los ultimos 5s, 2.5s antes del pico comieza la rodaja. Si este inicio de rodaja es menor de 2.5, pues 2.5\n#           CREO QUE ESTO ES UN BUG DEL AUTOR YA QUE NO TIENE SENTIDO. CREO QUE QUIERE REFLEJAR QUE SI EL PTO INICAL DE LA RODAJA ES < 0 ENTONCES LA\n#           RODAJA COMIENZA EN CERO\n# \n#  Finalmente convierte el chunck a spectrograma y lo pinta\n\nfn, l, y, peaks = df.sample().values[0]\n#start_time = min(l/32000 - 5, max(float(peaks.split('#')[0])-2.5, 2.5)) # Tiempo inicial del corte\nstart_time = min(l/32000 - 5, max(float(peaks.split('#')[0])-2.5, 0)) # Correjido \ny, sr = librosa.load(fn, sr=32000, offset=start_time, duration=5)\n#plt.imshow(chunk_to_spec(y, SPEC_HEIGHT=128), cmap='inferno')\nprueba = chunk_to_spec(y, SPEC_HEIGHT=128)\nprueba","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:57:03.574190Z","iopub.execute_input":"2022-03-30T21:57:03.574963Z","iopub.status.idle":"2022-03-30T21:57:03.658524Z","shell.execute_reply.started":"2022-03-30T21:57:03.574909Z","shell.execute_reply":"2022-03-30T21:57:03.657446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tensor(prueba)","metadata":{"execution":{"iopub.status.busy":"2022-03-30T13:52:41.968336Z","iopub.execute_input":"2022-03-30T13:52:41.968817Z","iopub.status.idle":"2022-03-30T13:52:41.980317Z","shell.execute_reply.started":"2022-03-30T13:52:41.968768Z","shell.execute_reply":"2022-03-30T13:52:41.979165Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**IMPLEMENTACION CON LBRERIA FAST-AI**","metadata":{}},{"cell_type":"code","source":"# Fuente general: \n#     https://docs.fast.ai/tutorial.vision.html#Classifying-breeds\n# Otras fuentes:\n#     https://fastcore.fast.ai/basics.html#fastuple\n#     https://www.udacity.com/blog/2021/11/__init__-in-python-an-overview.html#:~:text=The%20__init__%20method%20is%20the%20Python%20equivalent%20of,is%20only%20used%20within%20classes.\n#     https://fastcore.fast.ai/basics.html#uniqueify\n#     pandas.iloc - https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.iloc.html\n#     torch.unsqueeze - https://stackoverflow.com/questions/57237352/what-does-unsqueeze-do-in-pytorch\n#     torch.cat - https://pytorch.org/docs/stable/generated/torch.cat.html\n#     https://fastcore.fast.ai/transform#Transform\n#     \n#     \n#     \n# Explicación:\n#   \n#   TitledImage = ???     ¿fastuple? -> \"tupla con funciones extendidas\" ( https://fastcore.fast.ai/basics.html#fastuple )\n#    \n#   ClipTransform = \n#      __init__ = funcion de creación del objeto  \n#           uniqueify --> \n#               deja una lista de las clases ordenada en self.vocab\n#               guarda clave y par \"indice:nombre\" -> (0:golondrina, 1:gaviota, 2:aguila)\n#\n#       encodes = SOLO SE EJECUTA CUANDO SE LE LLAMA.   En este caso solo se ejecuta cuando se llama DENTRO DE LA FUNCION \"TFMDLISTS\"\n#\n#            df.iloc[i].values -> coge la fila \"i\" de \"df\" extrae sus valores\n#            guarda f=nombreFichero,  l=legth,  label=label,  peaks=picos de la fila número \"y\"\n#            elije un número de pico -> ¿solo del 1 al 6?¿Para que elegimos 20?\n#            obtiene el tiempo de corte y saca el clip\n#            lo transforma a espectrograma -> ¿Por que con power_to_db?\n#            \n#            CAMBIOS INJUSTIFICADOS AL SPEC ->  ¿spec -= np.min(spec)?   ¿spec /= 80?\n#            \n#            ¿ torch.unsqueeze(tensor(spec), 0) ?\n#               tensor(spec) -> transforma la matriz multidimensional en un tensor \n#               torch.unsqueeze(x, 0) -> unsqueeze significa \"descomprimir\".  -> añade una dimension al tensor. -> si 0, lo añade en la primera \"capa\"  \n#                      si x = [1, 2, 3] (tensor de dim 2 ~ vector) -> x.unsqueeze(0) = [[1, 2, 3]]   \n#                              dim x0 = (3)  dim x' = (1,3) (dentro del 1er [] hay 1[], este con 3 elementos dentro)\n#               torch.cat -> Concatena tres veces el mismo tensor \"para sortear que las redes usan 3 canales rojo verde y azul\" -> ¿¿Muy ineficiente??\n#               return spec, self.o2i[label] -> Entiendo que: \n#                         devuelve la entrada \"spec\" que son los 3 tensores encadenados\n#                         Junto con \"y\", que es el numero de la clase a la ue peertenece. Lo obtiene buscado en la lista clave:valor \"self.o2i\"\n#\n#       decodes -> creo que es para tema de impementar funciones de visualizacion \".show\" propias de FastAI\n#             \n#\n#\n#\n         \nclass TitledImage(fastuple):\n    def show(self, ctx=None, **kwargs): show_titled_image(self, ctx=ctx, **kwargs)\n\nclass ClipTransform(ItemTransform):\n\n    def __init__(self, df):\n        self.df=df\n        self.vocab,self.o2i = uniqueify(df['label'], sort=True, bidir=True)\n        \n    def encodes(self, i, from_np=False):\n        f, l, label, peaks = df.iloc[i].values\n        clip_num = random.choice([0, 0, 0, 1, 1, 2, 3, 4, 5, 6]) # More prob chose big peak\n        start_time = min(l/32000 - 5, max(float(peaks.split('#')[clip_num])-2.5, 0))\n        y, sr = librosa.load(f, sr=32000, offset=start_time, duration=5)\n        spec = chunk_to_spec(y,SPEC_HEIGHT=112,SPEC_WIDTH=224) # ¿Por que usa esta funcion que lleva power_to_dB y no pcen?\n        #¿una vez detectado el pico es mejor pq no adultera tanto la informacion de la que aprendes, al ser mas homogénea?\n        spec -= np.min(spec) \n        spec /= 80 # np.max(spec) # Normalize\n        spec =  torch.unsqueeze(tensor(spec), 0)\n        spec = torch.cat([spec, spec, spec]) # Stack three channels to simulate RGB if using a pretrained model\n        return spec, self.o2i[label]\n    \n    def decodes(self, x):\n        return TitledImage(x[0],self.vocab[x[1]])\n   \n\n#   Fuentes:\n#     https://docs.fast.ai/data.load.html#DataLoader\n#     \n#     \n#   Mas allá de las clases: \n#      ¿se hace una copia de df en df_small?  --> Totalmente innecesario\n#      se crea clip_tfm que es un objeto de la clase ClipTransform con las reglas de transformacion de un audio en un solo clip\n#      train = 80% random del df\n#      Crea índices de train, y de las filas no incluidas entrain pero si en df (que es el indice de validación)\n#      \n#      Crea las transformaciones de los conjuntos de entrenamiento y validación\n#           train_tl= TfmdLists(train_idx, clip_tfm)   ->    le pasa la lista de audios(el índice en df), y el objeto que lo transforma (que ya contiene df)  \n#           valid_tl= TfmdLists(valid_idx, clip_tfm)   ->    Hace lo mismo con la validación\n#      \n#      Crea objeto DataLoader \"dls\" al que le mete la lista de parejas (imput, label) de training y las de validation\n#              train_tl y valid_tl  ->  Son ?? ¿datasets?\n#              bs = Batch Size -> nº de samples por que se cogen por cada calculo del gradiente\n#              dls = dls.cuda()  ->  para \"hacer uso de la gpu\"  -> mi gpu no tiene nucleos cuda ¿¿¿???\n#\n#\n \n\ndf_small = df\nclip_tfm = ClipTransform(df)\ntrain =  df_small.sample(frac=0.8)\ntrain_idx, valid_idx = list(train.index), df_small[~df_small.index.isin(train.index)].index\nprint('train and val size', len(train_idx), len(valid_idx))\ntrain_tl= TfmdLists(train_idx, clip_tfm)\nvalid_tl= TfmdLists(valid_idx, clip_tfm)\ndls = DataLoaders.from_dsets(train_tl, valid_tl, bs=16)\n\n\ndls = dls.cuda()\nxb, yb = dls.one_batch()\nprint(xb.shape)\ndls.show_batch(max_n=3)","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:57:09.545186Z","iopub.execute_input":"2022-03-30T21:57:09.545801Z","iopub.status.idle":"2022-03-30T21:57:12.463464Z","shell.execute_reply.started":"2022-03-30T21:57:09.545761Z","shell.execute_reply":"2022-03-30T21:57:12.462556Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Pruebas con tensores\nx = torch.tensor([1, 2, 3, 4])\nprint(x.shape)\nprint(x)\nprint(\"------\")\n\nx = x.unsqueeze(1)\nprint(x.shape)\nprint(x)\nx = x.squeeze(1)\nprint(x.shape)\nprint(x)\nx = x.unsqueeze(0)\nprint(x.shape)\nprint(x)","metadata":{"execution":{"iopub.status.busy":"2022-03-30T10:42:37.308124Z","iopub.status.idle":"2022-03-30T10:42:37.308479Z","shell.execute_reply.started":"2022-03-30T10:42:37.308330Z","shell.execute_reply":"2022-03-30T10:42:37.308346Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn = cnn_learner(dls, models.resnet18, loss_func=FocalLossFlat(), metrics=[accuracy], cbs=[ShowGraphCallback(), CSVLogger()])","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:57:38.325184Z","iopub.execute_input":"2022-03-30T21:57:38.326060Z","iopub.status.idle":"2022-03-30T21:57:40.249452Z","shell.execute_reply.started":"2022-03-30T21:57:38.326000Z","shell.execute_reply":"2022-03-30T21:57:40.248587Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn.unfreeze()\nlearn.fit_one_cycle(3, slice(1e-4, 1e-3))","metadata":{"execution":{"iopub.status.busy":"2022-03-30T21:57:42.065498Z","iopub.execute_input":"2022-03-30T21:57:42.066579Z","iopub.status.idle":"2022-03-30T23:35:20.123901Z","shell.execute_reply.started":"2022-03-30T21:57:42.066537Z","shell.execute_reply":"2022-03-30T23:35:20.122257Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn.recorder.plot_loss()\nplt.savefig('loss_plot.png')","metadata":{"execution":{"iopub.status.busy":"2022-03-31T00:44:25.605117Z","iopub.execute_input":"2022-03-31T00:44:25.605467Z","iopub.status.idle":"2022-03-31T00:44:25.698995Z","shell.execute_reply.started":"2022-03-31T00:44:25.605371Z","shell.execute_reply":"2022-03-31T00:44:25.697626Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn.save('stage-1')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn.remove_cb(CSVLogger) # Not pickleable\nlearn.export('baseline_3e.pkl')","metadata":{},"execution_count":null,"outputs":[]}]}