{"cells":[{"metadata":{},"cell_type":"markdown","source":"## Herbarium\n\nThis competition presents a few significant challenges:\n* Huge number of classes.\n* Imbalanced classes.\n* Very Large dataset that Kaggle kernels process slowly.\n\nThis is just a very simple kernel to help get folks started with loading and modifying the dataset.\n\nGood luck!"},{"metadata":{},"cell_type":"markdown","source":"## Imports"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport sklearn\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\n\nimport json\nfrom pandas.io.json import json_normalize\n\nimport wandb\n\nimport os\n\nfrom tensorflow.keras.applications import EfficientNetB0\n\nimport tensorflow as tf\nimport tensorflow.keras as keras\nfrom tensorflow.keras.layers import Input, Dense, Dropout, Conv2D, MaxPooling2D, BatchNormalization, Flatten\n\nimport math\nimport cv2\nfrom skimage.transform import resize\n\nimport seaborn as sns\n\nimport multiprocessing\n\nfrom glob import glob","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Weights and Biases for Personal Logging"},{"metadata":{"trusted":true},"cell_type":"code","source":"from kaggle_secrets import UserSecretsClient\n\nuser_secrets = UserSecretsClient()\nwandb_api = user_secrets.get_secret(\"wandb_api\")\nwandb_user = user_secrets.get_secret(\"wandb_user\")\n\nwandb.login(key = wandb_api)\ninit = wandb.init(project = 'herbarium')\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## GPU or TPU usage?"},{"metadata":{"trusted":true},"cell_type":"code","source":"USE_GPU = True\n\nif USE_GPU:\n    strategy = tf.distribute.MirroredStrategy()\n    print(f'Number of devices: {strategy.num_replicas_in_sync}')\n    print(tf.test.gpu_device_name())\n    print(f\"Number of accelerators: {strategy.num_replicas_in_sync}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"num_cores = multiprocessing.cpu_count()\nprint(f\"CPU Cores: {num_cores}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Metadata Read and Dataframe Conversion\n\nSee my post on converting JSON to CSV here: https://www.kaggle.com/c/herbarium-2021-fgvc8/discussion/225237\n\nI have added the csv to the kernel for easy loading and minimal processing time."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(\"../input/herbarium-traincsv/herb_train.csv\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# For Kaggle Pathfinding\nfilepath_prefix = \"../input/herbarium-2021-fgvc8/train/\"\ntrain_df['absolute_path'] = filepath_prefix + train_df.file_name\n\nget_base = os.path.basename\ntrain_df[\"filename_nopath\"] = train_df[\"file_name\"].apply(get_base)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## EDA - Quick Distribution Visualizations"},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.histplot(train_df[\"category_id\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.histplot(train_df[\"institution_id\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.histplot(train_df[\"height\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.histplot(train_df[\"width\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.histplot(train_df[\"family\"])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.histplot(train_df[\"order\"])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Reproducibility"},{"metadata":{"trusted":true},"cell_type":"code","source":"GLOBAL_SEED = 42\n\nnp.random.seed(GLOBAL_SEED)\ntf.random.set_seed(GLOBAL_SEED)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Tensorflow / Keras Starter"},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN_DIR = \"../input/herbarium-2021-fgvc8/train\"\nTEST_DIR =  \"../input/herbarium-2021-fgvc8/test\"\n\nBATCH_SIZE = 128\nSTAGES_PER_EPOCH = 256\nEPOCHS = 200\n\nIMG_HEIGHT = 224\nIMG_WIDTH = 224\nIMG_SIZE = (IMG_HEIGHT, IMG_WIDTH)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Splits"},{"metadata":{},"cell_type":"markdown","source":"#### Train Test Split"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Rename b/c it contains y.\n\nX_train, X_val, = train_test_split(train_df, test_size = 0.30,\n    stratify = train_df['name'], random_state = GLOBAL_SEED, shuffle = True\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(X_train.shape)\nprint(X_val.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"DO_SUBSET = True\n\nif DO_SUBSET:\n    n_samples = 20000\n    X_train = X_train.sample(n_samples)\n    X_val = X_val.sample(n_samples)\n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(X_train.shape)\nprint(X_val.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Make sure (subsample) train and val have same classes.\nThese are the names that are not in this validation split"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_names = set(X_train.name)\nvalidation_names = set(X_val.name)\nintersection_names = train_names.intersection(validation_names)\n\nlen(intersection_names)\n\n# Names in train but not in validation.\n\nnames_in_both = train_names.intersection(validation_names)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_keep_idx = X_train.isin(names_in_both)\nX_keep_idx.name.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_keep = X_train[X_keep_idx.name]\n\nprint(X_train.shape)\nprint(X_keep.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_val_keep_idx = X_val.isin(names_in_both)\nX_val_keep_idx.name.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_val_keep = X_val[X_val_keep_idx.name]\n\nprint(X_val.shape)\nprint(X_val_keep.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"X_val_keep.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"n_classes = X_keep.name.nunique()\nn_classes","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Image Dataset Generator Approach (Slower - Unused Right Now)"},{"metadata":{"trusted":true},"cell_type":"code","source":"rescale_value = 1/255.\n\ntrain_data_gen = keras.preprocessing.image.ImageDataGenerator(\n    rescale = rescale_value,\n    horizontal_flip = True,\n    rotation_range = 180,\n    shear_range = 30,\n    vertical_flip = True\n    \n    \n#    preprocessing_function = do_img_preprocessing_pipeline,\n#    featurewise_center = True\n)\n\nvalidation_data_gen = keras.preprocessing.image.ImageDataGenerator(\n    rescale = rescale_value,\n#    preprocessing_function = do_img_preprocessing_pipeline\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data_generator = train_data_gen.flow_from_dataframe(\n    dataframe = X_keep,\n    directory = None,\n    x_col = \"absolute_path\",\n    y_col = \"name\",\n    seed = GLOBAL_SEED,\n    batch_size = BATCH_SIZE,\n    shuffle = True,\n    class_mode = \"categorical\",\n    target_size = IMG_SIZE,\n    subset = \"training\",\n    validate_filenames=False\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"valid_data_generator = validation_data_gen.flow_from_dataframe(\n    dataframe = X_val_keep,\n    directory = None,\n    x_col = \"absolute_path\",\n    y_col = \"name\",\n    seed = GLOBAL_SEED,\n    batch_size = BATCH_SIZE,\n    shuffle = True,\n    class_mode = \"categorical\",\n    target_size = IMG_SIZE,\n    validate_filenames=False\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Faster Loading and Parallel Processing (In Process)"},{"metadata":{"trusted":true},"cell_type":"code","source":"def do_img_preprocessing_pipeline(image, label):\n    \n    image = tf.image.resize(image, IMG_SIZE)   # Resize\n    image = tf.cast(image, tf.float32) / 255.  # Recale\n    \n    # Flip\n    # Rotate\n    \n    return image, label","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class HerbariumBatchSequence(tf.keras.utils.Sequence):\n    \n    def __init__(self, x_set, y_set, \n                 batch_size,\n                 img_size = (224, 224),\n                 augment = False):\n        \"\"\"\n        `x_set` is list of paths to the images\n        `y_set` are the associated classes.\n\n        \"\"\"\n        \n        self.batch_size = batch_size\n        self.img_size = img_size\n        \n        self.x = x_set\n        self.y = y_set\n        \n        label_enc = LabelEncoder()\n        self.y = label_enc.fit_transform(self.y)\n        self.y = tf.keras.utils.to_categorical(self.y)\n    \n    def __len__(self):\n        \"\"\"Denotes the number of batches per epoch\"\"\"\n        return math.ceil(len(self.x) / self.batch_size)\n    \n    def __getitem__(self, idx):\n        \"\"\"Generate one batch of data\"\"\"\n        \n        first_id = idx * self.batch_size\n        last_id =  (idx + 1) * (self.batch_size)\n        \n        batch_x = self.x[first_id:last_id]\n        batch_y = self.y[first_id:last_id]\n        \n        output = np.array([\n            resize(cv2.imread(file_name), self.img_size)\n                   for file_name in batch_x]), np.array(batch_y)\n        \n        return output\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TrainGenerator = HerbariumBatchSequence(X_keep.absolute_path, \n                                        X_keep.name,\n                                        BATCH_SIZE)\n\nValidGenerator = HerbariumBatchSequence(X_val_keep.absolute_path, \n                                       X_val_keep.name,\n                                       BATCH_SIZE)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Custom Loss (in progress)"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Source: https://www.kaggle.com/guglielmocamporese/macro-f1-score-keras\n\nimport keras.backend as K\n\ndef macro_f1(y_true, y_pred):\n    y_pred = K.round(y_pred)\n    tp = K.sum(K.cast(y_true*y_pred, 'float'), axis=0)\n    # tn = K.sum(K.cast((1-y_true)*(1-y_pred), 'float'), axis=0)\n    fp = K.sum(K.cast((1-y_true)*y_pred, 'float'), axis=0)\n    fn = K.sum(K.cast(y_true*(1-y_pred), 'float'), axis=0)\n\n    p = tp / (tp + fp + K.epsilon())\n    r = tp / (tp + fn + K.epsilon())\n\n    f1 = 2*p*r / (p+r+K.epsilon())\n    f1 = tf.where(tf.is_nan(f1), tf.zeros_like(f1), f1)\n    return K.mean(f1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Efficientnet CNN\n\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"efficientnet = EfficientNetB0(include_top=True, \n                              weights=None, \n                              input_shape = (IMG_HEIGHT, IMG_WIDTH, 3),\n                              classes = n_classes\n)\n\n# efficientnet.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = efficientnet","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.compile(optimizer = 'adam',\n              loss = 'categorical_crossentropy',\n              metrics = 'accuracy')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#wandb_callback = wandb.keras.WandbCallback(log_weights=True)\n\nhistory = model.fit(TrainGenerator,\n                    steps_per_epoch = STAGES_PER_EPOCH,\n                    validation_data = ValidGenerator,\n                    workers = num_cores,\n                    epochs = 3,\n#                    callbacks=[wandb_callback]\n                   )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds = pd.DataFrame(model.predict(valid_data_generator))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds.head(15)","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}