{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\nimport warnings\nwarnings.filterwarnings('ignore')\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport librosa\nimport librosa.display\nimport matplotlib.pyplot as plt\nfrom datetime import datetime\n\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D, Input, BatchNormalization\nfrom tensorflow.keras import Model\n\nimport tensorflow.keras.backend as K\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\nimport IPython.display as ipd\nimport os\n#for dirname, _, filenames in os.walk('/kaggle/input'):\n    #for filename in filenames:\n        #print(os.path.join(dirname, filename))\n\n# You can write up to 5GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"#define some functions to convert different types to data to TFRecords\n\n#credit - stackoverflow, TF docs etc\n\ndef _bytes_feature(value):\n    \"\"\"Returns a bytes_list from a string / byte.\"\"\"\n    if isinstance(value, type(tf.constant(0))):\n        value = value.numpy() # BytesList won't unpack a string from an EagerTensor.\n    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))\n\ndef _float_feature(value):\n    \"\"\"Returns a float_list from a float / double.\"\"\"\n    return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))\n\ndef _int64_feature(value):\n    \"\"\"Returns an int64_list from a bool / enum / int / uint.\"\"\"\n    return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))\n\n\ndef _float2d_feature(value):\n    \"\"\"Returns an int64_list from a bool / enum / int / uint.\"\"\"\n    return tf.train.Feature(float_list=tf.train.FloatList(value=value.flatten()))\n\ndef _bytes_feature(value):\n    \"\"\"Returns a bytes_list from a string / byte.\"\"\"\n    if isinstance(value, type(tf.constant(0))): # if value ist tensor\n        value = value.numpy() # get value of tensor\n    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))\n\ndef serialize_array(array):\n    array = tf.io.serialize_tensor(array)\n    return array\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv('../input/birdsong-recognition/train.csv',)\nprint(train.shape)\ntrain['sampling_rate'] = train['sampling_rate'].apply(lambda sr: int(sr.split(' ')[0])).astype(np.uint16)\ntrain['sampling_rate'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#filter to only the longer duration clips so we can be sure we can take a standard size clip from the spectrogram\ntrain = train[train['duration'] > 6]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#set up an index corresponding to picking 2 birds at random\nselected = train.index[train['ebird_code'].isin(train['ebird_code'].value_counts().sort_values(ascending=False).index[0:2])]\nprint(train['ebird_code'][selected].value_counts())\nselected.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#replace with labels (0/1) for the selected 2 bird types\nlabels = {b:count for count,b in enumerate(train['ebird_code'][selected].value_counts().index)}\nprint(labels)\ntrain['new_label'] = train['ebird_code'].replace(labels)\ntrain['new_label'][selected].value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#using these 2 birds as an example, extract clips from teh spectrograms and append to a list\n#of clips and labels (so total 200 clips - one sample from each spectrogram)\n\nclip_list = []\nlabel_list = []\n\nfor idx in selected:\n    \n    #load the audio and take the spectrogram\n    c=train.loc[idx,'ebird_code']\n    f=train.loc[idx,'filename']\n    sr=train.loc[idx,'sampling_rate']\n    \n    audio_path = '/kaggle/input/birdsong-recognition/train_audio/' + c + '/' + f\n    \n    x , sr = librosa.load(audio_path, sr=sr)       \n    x = (x / np.abs(x).max()).astype('float32')\n    \n    X = librosa.stft(x) #create fourier transform spectrogram\n    Xdb = librosa.amplitude_to_db(abs(X)) \n    \n    Xdb = Xdb - Xdb.min()\n    Xdb = Xdb / Xdb.max()\n    \n    #as an example just taking one clip from the start of the spectrogram\n    clip = Xdb[:, 0:200].copy()\n    clip = (clip * 255).astype(np.uint8) #saving as 0-255, np.uint8, other formats possible\n    \n    label = train.loc[idx,'new_label']\n    \n    clip_list += [clip] #add clip to the list\n    label_list += [label] #add bird label to the list","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#shape of the extract samples is 1025 * 200 - would have taken a wider clip but some audios are very short and i\n#dont want to navigate around audio clips which are too short","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#pick some examples from the list of spectrogram clips and labels which has been created\n\nexamples = [0, 5, 10, 20, 110, 120, 130, 140]\nfig,axes=plt.subplots(nrows=1,ncols=8,figsize=(20,7))\n\nfor count,e in enumerate(examples):\n    axes[count].imshow(clip_list[e]) #plot the clip\n    axes[count].set_title(label_list[e]) #title is the LABEL (type of bird)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#split manually into train and validation data sets - for demo purposes\n#approach is not efficient and can be improved on but not important for a demo\n#TFRecords dont seem to have a convenient train/test/validation split function so preferring to do this now\n\nvalidation_index = [x for x in np.arange(70,100,1)] + [x for x in np.arange(170,200,1)] #take 30 from each bird type for validation\ntrain_index = [x for x in np.arange(0,200,1) if x not in validation_index] #everything else\n\nprint(len(train_index), len(validation_index))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#yes this is a very crude train/test split for data and labels just running through quickly...\ntrain_data = [clip_list[i] for i in train_index]\n\nvalidation_data = [clip_list[i] for i in validation_index]\n\ntrain_labels = [label_list[i] for i in train_index]\n\nvalidation_labels = [label_list[i] for i in validation_index]\n\nprint(len(train_data), len(validation_data), len(train_labels), len(validation_labels))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#write the TRAIN data to a TFRecords file\n#format is to feed through individual lines (Feature-Label) one at a time\n#more than 2 items can be added\n\nfile_path='train_clips_example1.tfrecords'\n    \nwith tf.io.TFRecordWriter(file_path) as writer:\n    for array, label in zip(train_data, train_labels): #this cycles through each line of the array-labels\n        serialized_array = serialize_array(array) #this turns the 2d numpy data into a 1d line of numbers\n        feature = {'b_feature': _bytes_feature(serialized_array), #1d line of numpy data converted to bytes\n                              'b_l' : _int64_feature(label)} #dtype selected for label\n        \n        example_message = tf.train.Example(features=tf.train.Features(feature=feature)) #the single line to be fed into the record\n        writer.write(example_message.SerializeToString()) #line is added","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#write the VALIDATION data to a TFRecords file\n#format is to feed through individual lines (Feature-Label) one at a time\n#more than 2 items can be added\n\nfile_path='validation_clips_example1.tfrecords' #change the file name\n    \nwith tf.io.TFRecordWriter(file_path) as writer:\n    for array, label in zip(validation_data, validation_labels): #just need to change the data feeds here\n        serialized_array = serialize_array(array) #other lines same as example above\n        feature = {'b_feature': _bytes_feature(serialized_array),\n                              'b_l' : _int64_feature(label)} \n        \n        example_message = tf.train.Example(features=tf.train.Features(feature=feature)) \n        writer.write(example_message.SerializeToString()) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#so the train and validation clips have now been saved to TFRecords\n\n#to load back again, need to start by inputting the file name\n#as i understand it, can put multiple file names into the list (as many as needed) - in this example we just have one\n\ntrain_filenames = ['train_clips_example1.tfrecords']\nvalidation_filenames = ['validation_clips_example1.tfrecords']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"BATCH_SIZE=5","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#first going to 'read' the validation data back - no data augmentation applied as this is validation\n\n# Read TFRecord file\n\n#instructions for array\ndef decode_image(image_data): #this gives instructions to decode the 2d numpy array and is used in the function below this\n    \n    feature = tf.io.parse_tensor(image_data, out_type=tf.uint8) #in this example the image is stored as 0-255 in np.uint8\n    \n    feature = tf.cast(feature, tf.float32) / 255.0  #divide by 255 and turn back into a float to get data 0-1 scaled\n    \n    feature = tf.reshape(feature, [1025, 200,1]) #reshape to original 2d format, from the 1d storage\n    \n    return feature\n\n#general instructions\ndef _parse_valid_element(element): #this gives instructions on how to read each line from the file\n    #dictionary of descriptions - the titles and data types MUST match the instructions for creating the record\n    parse_dic = {\n        'b_feature': tf.io.FixedLenFeature([], tf.string), # the 2d numpy array has been saved as a string\n        'b_l': tf.io.FixedLenFeature([], tf.int64) #the label has been saved as int64\n        }    \n    example_message = tf.io.parse_single_example(element, parse_dic) #this works by iteration - one example at a time\n    #the example message is the individual line which has the 2 components - b_feature and b_label1\n    \n    feature = decode_image(example_message['b_feature']) #this references the previous function to get back to a 2d numerical array\n    \n    b_label = example_message['b_l'] #this is the label y/n for event happening\n    \n    return feature, b_label #returns the 2d array and label\n\n\n#load up the VALIDATION DATA\nv_dataset = tf.data.TFRecordDataset(validation_filenames) #firstly, reference the list of filenames (previous code cell)\nvalid_dataset = v_dataset.map(_parse_valid_element) #map the input using the above functions\nvalid_dataset = valid_dataset.batch(BATCH_SIZE) #batch the data (not 100% sure if this is needed for validation data)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#now to read the TRAIN data back\n\n\n#instructions for array\ndef decode_augment_image(image_data): #THIS IS FOR TRAINING DATA\n    #i have included some clumsy attempts at augmentation - am sure others have much better versions of this\n    #chris deotte's notebook in petals starter comp for example \n    \n    #read the data back and reformat to original shape, as per validation data\n    feature = tf.io.parse_tensor(image_data, out_type=tf.uint8) #the image is stored as 0-255 in np.uint8    \n    feature = tf.cast(feature, tf.float32) / 255.0  #divide by 255 and turn into a float\n    feature = tf.reshape(feature, [1025, 200,1]) #reshape to 150 x 150\n    \n    #some crude data augmentation for train data - demo only, there are much better examples\n    feature = tf.image.random_flip_left_right(feature) \n    feature = tf.image.random_contrast(feature, 0.8, 1.2)\n    \n    return feature #return the numpy array\n\n#general instructions\ndef _parse_train_element(element):\n    #dictionary of descriptions - the titles and data types MUST match the instructions for creating the record\n    parse_dic = {\n        'b_feature': tf.io.FixedLenFeature([], tf.string), # Note that it is tf.string, not tf.float32\n        'b_l': tf.io.FixedLenFeature([], tf.int64)\n        }    \n    example_message = tf.io.parse_single_example(element, parse_dic) #this work by iteration - one example at a time      \n    #the example message is the individual line which has the 2 components - b_feature and b_label1\n    \n    feature = decode_augment_image(example_message['b_feature']) #for train data, may want a different function to include augmentation\n    \n    b_label = example_message['b_l'] #this is the label y/n for event happening\n    \n    return feature, b_label #returns the 2d array and label\n\n\n#load up the TRAINING data\nt_dataset = tf.data.TFRecordDataset(train_filenames)  #define the dataset scope in filenames\ntrain_dataset = t_dataset.map(_parse_train_element) #apply TRAIN mapping to the elements needed\n\n#note: my understanding of the shuffle buffer is that it is the range of samples over which shuffling will occur\n#larger number = more shuffling e.g. if shuffle = 10, samples will only be shuffled within indexes (0-10), (20-30) etc\ntrain_dataset = train_dataset.shuffle(100, reshuffle_each_iteration=True) \n\n#think this is needed to feed into the model\ntrain_dataset = train_dataset.batch(BATCH_SIZE)\n\n#think a call to prefetch() should also be added here when there is more data to speed things up","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#we can inspect some examples (note - as it doesn't preload into memory, have to iterate)\nfor raw_record in train_dataset.take(1):\n    print(repr(raw_record[1][0].numpy())) #think this shows the (label - [1] column) of the first record\n    \n    #print(repr(raw_record[0][0][0].numpy())) #think this would show the array - [0] column - of the first record\n    \n    plt.imshow(raw_record[0][0][:].numpy().reshape(1025,200)) #loaded image with imshow","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"K.clear_session()\n\nmodel = Sequential([\n    Conv2D(16, 2, padding='same', activation='relu', input_shape=(1025, 200, 1)),\n    MaxPooling2D(),\n    Dropout(0.5),\n    Conv2D(16, 4, padding='same', activation='relu'),\n    MaxPooling2D(),\n    Dropout(0.5),\n   Conv2D(16, 4, padding='same', activation='relu'),\n    MaxPooling2D(),\n    Dropout(0.5),\n    Flatten(),\n    Dense(30, activation='relu'),\n    Dropout(0.5),\n    Dense(2, activation='softmax')\n])\n\nmodel.compile(optimizer='adam',\n              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),\n              metrics=['accuracy'])\n\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print('model run starting')\nst = datetime.now()\nprint(datetime.now())\n\n#my understanding is that TF will automatically understand any dataset loaded as either\n#feature-labels\n#feature-labels-weights\n\n#batch size is already entered above & from dataset\n#can no longer set validation fraction of data using datasets - must be a separate dataset\nhistory = model.fit(train_dataset,verbose=1,epochs=30,validation_data=valid_dataset)\n\nprint('model run ending')\ned = datetime.now()\nprint(datetime.now())\nprint('time taken',ed-st)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}