{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":3004,"databundleVersionId":861823},{"sourceType":"datasetVersion","sourceId":13698096,"datasetId":8693399,"databundleVersionId":14441996}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Importações\nimport numpy as np                                                       # Importa a biblioteca numpy para lidar com vetores, matrizes e etc.\nimport tensorflow as tf                                                  # Importa a biblioteca tensorflow para geração de modelos de IA\nfrom tensorflow import keras                                             # Importa a biblioteca keras para geração de modelos de IA\nfrom tensorflow.keras import layers                                      # Importa a biblioteca layers para simplificar o uso de camadas dos modelos de IA\nfrom tensorflow.keras.layers import Dense, Input                         # Importa a biblioteca dense e Input para simplificar o uso de camadas dos modelos de IA\nfrom tensorflow.keras.models import Sequential, Model                    # Importa a biblioteca sequential para simplificar o uso de modelos de IA\nfrom tensorflow.keras.optimizers import Adam                             # Importa a biblioteca Adam dos otimizadores para poder ajustar a taxa de aprendizado\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.preprocessing import MinMaxScaler\nfrom tensorflow.keras import layers, callbacks\nimport matplotlib.pyplot as plt\nfrom IPython.display import clear_output\nimport tensorflow as tf\nimport os\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nfrom tensorflow.keras.preprocessing import image\nfrom tensorflow.keras.preprocessing import image_dataset_from_directory","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:13:57.775674Z","iopub.execute_input":"2025-11-11T20:13:57.775935Z","iopub.status.idle":"2025-11-11T20:13:57.782161Z","shell.execute_reply.started":"2025-11-11T20:13:57.775918Z","shell.execute_reply":"2025-11-11T20:13:57.781227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -------------------------------\n# CONFIGURAÇÕES DO DATASET\n# -------------------------------\ndataset_path = \"/kaggle/input/gnomos-tucanos-e-outras-coisas-aleatrias/Dataset _v2/Dataset _v2\"\nimg_height = 320\nimg_width = 240\nbatch_size = 32\nnum_classes=2\nchannels=3\n\n# -------------------------------\n# CARREGAR DATASET\n# -------------------------------\ntrain_ds = tf.keras.utils.image_dataset_from_directory(\n    dataset_path,\n    validation_split=0.2,         # separa 20% para validação\n    subset=\"training\",\n    seed=42,\n    image_size=(img_height, img_width),\n    batch_size=batch_size,\n    label_mode=\"categorical\"\n)\n\nval_ds = tf.keras.utils.image_dataset_from_directory(\n    dataset_path,\n    validation_split=0.2,\n    subset=\"validation\",\n    seed=42,\n    image_size=(img_height, img_width),\n    batch_size=batch_size,\n    label_mode=\"categorical\"\n)\n\nclass_names = train_ds.class_names\nprint(\"Classes detectadas:\", class_names)\n\n# Melhora o desempenho (prefetch otimiza o pipeline)\ntrain_ds = train_ds.prefetch(buffer_size=tf.data.AUTOTUNE)\nval_ds = val_ds.prefetch(buffer_size=tf.data.AUTOTUNE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T19:52:16.797362Z","iopub.execute_input":"2025-11-11T19:52:16.797651Z","iopub.status.idle":"2025-11-11T19:52:16.981532Z","shell.execute_reply.started":"2025-11-11T19:52:16.797627Z","shell.execute_reply":"2025-11-11T19:52:16.980760Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Criação do modelo: TinyCNN (custom)\nmodel = keras.Sequential([\n    keras.layers.Rescaling(1./255, input_shape=(img_height, img_width, channels)),\n\n    keras.layers.SeparableConv2D(8, (3,3), activation='relu'),\n    keras.layers.BatchNormalization(),\n    keras.layers.MaxPooling2D(2),\n\n    keras.layers.SeparableConv2D(16, (3,3), activation='relu'),\n    keras.layers.BatchNormalization(),\n    keras.layers.MaxPooling2D(2),\n\n    keras.layers.GlobalAveragePooling2D(),\n\n    keras.layers.Dense(num_classes, activation='softmax')\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T19:52:16.982442Z","iopub.execute_input":"2025-11-11T19:52:16.982692Z","iopub.status.idle":"2025-11-11T19:52:17.064440Z","shell.execute_reply.started":"2025-11-11T19:52:16.982672Z","shell.execute_reply":"2025-11-11T19:52:17.063532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Diretório de checkpoints (pasta padrão do Kaggle)\ncheckpoint_dir = \"/kaggle/working/checkpoints\"\nos.makedirs(checkpoint_dir, exist_ok=True)\n\n# Callback: salva o melhor modelo\nbest_model_callback = callbacks.ModelCheckpoint(\n    filepath=os.path.join(checkpoint_dir, \"melhor_modelo.keras\"),\n    monitor='val_loss',\n    save_best_only=True,\n    verbose=1\n)\n\nclass LiveLossPlot(tf.keras.callbacks.Callback):\n    \"\"\"\n    Callback para plotar loss e val_loss durante o treinamento.\n    Mostra dois gráficos:\n        1. Todas as épocas.\n        2. Últimas 10 épocas.\n    \"\"\"\n    def __init__(self):\n        super().__init__()\n        self.losses = []\n        self.val_losses = []\n\n    def on_epoch_end(self, epoch, logs=None):\n        logs = logs or {}\n        self.losses.append(logs.get('loss'))\n        self.val_losses.append(logs.get('val_loss'))\n\n        # Limpa a saída anterior\n        clear_output(wait=True)\n        self._plot_losses()\n\n    def _plot_losses(self):\n        epochs = range(1, len(self.losses) + 1)\n        plt.figure(figsize=(12, 5))\n\n        # --- Todas as épocas ---\n        plt.subplot(1, 2, 1)\n        plt.plot(epochs, self.losses, label='Training Loss', marker='o')\n        if any(self.val_losses):\n            plt.plot(epochs, self.val_losses, label='Validation Loss', marker='s')\n        plt.title('Loss vs Validation Loss (todas as épocas)')\n        plt.xlabel('Épocas')\n        plt.ylabel('Loss')\n        plt.legend()\n        plt.grid(True, linestyle='--', alpha=0.6)\n\n        # --- Últimas 10 épocas ---\n        plt.subplot(1, 2, 2)\n        last_n = 10 if len(epochs) > 10 else len(epochs)\n        plt.plot(epochs[-last_n:], self.losses[-last_n:], label='Training Loss (últimas)', marker='o')\n        if any(self.val_losses):\n            plt.plot(epochs[-last_n:], self.val_losses[-last_n:], label='Validation Loss (últimas)', marker='s')\n        plt.title(f'Últimas {last_n} Épocas')\n        plt.xlabel('Épocas')\n        plt.ylabel('Loss')\n        plt.legend()\n        plt.grid(True, linestyle='--', alpha=0.6)\n\n        plt.tight_layout()\n        plt.show()\n\n\n# Callback customizado: salva a cada 20 épocas\nclass EpochCheckpoint(callbacks.Callback):\n    def __init__(self, save_dir, interval=1):\n        super().__init__()\n        self.save_dir = save_dir\n        self.interval = interval\n        os.makedirs(save_dir, exist_ok=True)\n\n    def on_epoch_end(self, epoch, logs=None):\n        if (epoch + 1) % self.interval == 0:\n            path = os.path.join(self.save_dir, f\"modelo_epoca_{epoch+1:03d}.keras\")\n            self.model.save(path)\n            print(f\"\\n✅ Checkpoint salvo: {path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T19:52:17.065297Z","iopub.execute_input":"2025-11-11T19:52:17.065557Z","iopub.status.idle":"2025-11-11T19:52:17.076763Z","shell.execute_reply.started":"2025-11-11T19:52:17.065537Z","shell.execute_reply":"2025-11-11T19:52:17.075597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"live_plot = LiveLossPlot()\n\nepoch_checkpoint = EpochCheckpoint(save_dir=checkpoint_dir, interval=20)\n\n# Compilar\nmodel.compile(optimizer=Adam(learning_rate=1e-4), loss='categorical_crossentropy', metrics=['categorical_crossentropy'])\n\n# Treinamento\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=1000,\n    batch_size=64,\n    callbacks=[best_model_callback, epoch_checkpoint, live_plot],\n    verbose=1\n)\n\n# Após o treinamento, salvar modelo final\nfinal_model_path = os.path.join(checkpoint_dir, \"modelo_final.keras\")\nmodel.save(final_model_path)\nprint(f\"\\n🏁 Treinamento concluído. Modelo final salvo em: {final_model_path}\")\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T19:52:17.078873Z","iopub.execute_input":"2025-11-11T19:52:17.079139Z","iopub.status.idle":"2025-11-11T20:10:08.615922Z","shell.execute_reply.started":"2025-11-11T19:52:17.079118Z","shell.execute_reply":"2025-11-11T20:10:08.614723Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verificar a curva de treino e de validação\n\nplt.plot(history.history[\"loss\"], label=\"Treino\")\nplt.plot(history.history[\"val_loss\"], label=\"Validação\")\nplt.xlabel(\"Época\")\nplt.ylabel(\"Loss (Cross Entrophy)\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:10:08.616819Z","iopub.execute_input":"2025-11-11T20:10:08.617093Z","iopub.status.idle":"2025-11-11T20:10:08.745396Z","shell.execute_reply.started":"2025-11-11T20:10:08.617067Z","shell.execute_reply":"2025-11-11T20:10:08.744382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Função para teste da inferência\ndef predict_image(model, img_path, threshold=0.6):\n    img = image.load_img(img_path, target_size=(img_height, img_width))\n    img_array = image.img_to_array(img)\n    img_array = np.expand_dims(img_array, axis=0)\n\n    pred = model.predict(img_array)[0]\n    class_index = np.argmax(pred)\n    confidence = pred[class_index]\n\n    print(f\" confidence: {confidence}, threshhold: {threshold} - Gnomo: {pred[0]}, Tucano: {pred[1]}\")\n    \n    if confidence < threshold:\n        return f\"Outro\"\n\n    return f\"{class_names[class_index]}\"\n\nimg_path = \"/kaggle/input/gnomos-tucanos-e-outras-coisas-aleatrias/Dataset _v2/Dataset _v2/Tucano/20251105_221937.png\"\npredict_image(model, img_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:21:26.028449Z","iopub.execute_input":"2025-11-11T20:21:26.028768Z","iopub.status.idle":"2025-11-11T20:21:26.121236Z","shell.execute_reply.started":"2025-11-11T20:21:26.028753Z","shell.execute_reply":"2025-11-11T20:21:26.120599Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Converter para TFLite\nconverter = tf.lite.TFLiteConverter.from_keras_model(model)             # Cria um conversor a partir de um modelo já treinado\nconverter.optimizations = [tf.lite.Optimize.DEFAULT]                    # Define que o conversor deve aplicar otimizações padrão\nconverter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS]  # Especifica que apenas operações internas do TensorFlow Lite com precisão float32\nconverted = converter.convert()                                         # Realiza a conversão do modelo para o formato .tflite","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:10:08.752904Z","iopub.status.idle":"2025-11-11T20:10:08.753129Z","shell.execute_reply.started":"2025-11-11T20:10:08.753029Z","shell.execute_reply":"2025-11-11T20:10:08.753038Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs(\"/kaggle/working/to_tflite/\", exist_ok=True)\n\nopen(\"ImageReckon.tflite\", \"wb\").write(converted)                           # Salva o modelo convertido (em formato TFLite) em um arquivo binário chamado \"ImageReckon.tflite\"\nbasic_model_size = os.path.getsize(\"ImageReckon.tflite\")                    # Obtém o tamanho do arquivo gerado em bytes e armazena na variável 'basic_model_size'\n\n!echo \"extern const unsigned char model[];\"       > /kaggle/working/to_tflite/model.h    # Cria o arquivo 'model.h' com a declaração externa e constante do array que conterá o modelo\n!echo \"extern const unsigned int model_len;\"      >> /kaggle/working/to_tflite/model.h   # Adiciona ao arquivo 'model.h' a declaração externa e constante do tamanho do modelo\n!echo \"#include \\\"model.h\\\"\"                      > /kaggle/working/to_tflite/model.cc   # Cria o arquivo 'model.cc' com o include do modelo\n!echo \"\"                                          >> /kaggle/working/to_tflite/model.cc  # Cria uma linha em branco\n!echo \"const unsigned char model[] = {\"           >> /kaggle/working/to_tflite/model.cc  # Adiciona ao arquivo 'model.cc' a definição inicial do array que armazenará os bytes do modelo\n!cat ImageReckon.tflite | xxd -i                      >> /kaggle/working/to_tflite/model.cc  # Converte o conteúdo binário de \"ImageReckon\" em array de bytes (formato C) e concatena ao .cc\n!echo \"};\"                                        >> /kaggle/working/to_tflite/model.cc  # Finaliza a definição do array 'model[]' com um ponto e vírgula\n!echo -n \"const unsigned int model_len = \"        >> /kaggle/working/to_tflite/model.cc  # Inicia a definição de uma variável que armazenará o tamanho do array 'model[]'\n!echo -n \"$basic_model_size\"                      >> /kaggle/working/to_tflite/model.cc  # Escreve o valor da variável de tamanho (que está no ambiente) no arquivo .cc\n!echo \";\"                                         >> /kaggle/working/to_tflite/model.cc  # Finaliza a linha de definição da variável com um ponto e vírgula\nmodel_cc_size = os.path.getsize(\"/kaggle/working/to_tflite/model.cc\")                    # Obtém o tamanho final do arquivo \"model.cc\" (em bytes) após inserir tudo\nprint(f\"cc file, model.cc, is {model_cc_size:,} bytes.\")                # Imprime o tamanho final do arquivo .cc formatado com separador de milhar","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-11T20:10:08.754002Z","iopub.status.idle":"2025-11-11T20:10:08.754262Z","shell.execute_reply.started":"2025-11-11T20:10:08.754135Z","shell.execute_reply":"2025-11-11T20:10:08.754146Z"}},"outputs":[],"execution_count":null}]}