{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":114094,"databundleVersionId":13610510,"isSourceIdPinned":false}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"1064ba29","cell_type":"markdown","source":"# ELT 579 — Rodada CRISP-DM para detecção de objetos com YOLO\n## Estudo de caso: *Grocery Items: Multi-class Object Detection*\n\n**Professor:** Rodolpho V. A. Neves\n\nA proposta deste notebook é mostrar uma iteração completa do CRISP-DM aplicada a um problema de detecção de objeto a partir de dados sintéticos para dados reais:\n\n- treinamento em dados sintéticos;\n- validação em dados reais e sintéticos;\n- submissão final em um ambiente competitivo.","metadata":{}},{"id":"82ed8227","cell_type":"markdown","source":"### Objetivos de aprendizagem\n\nAo final desta aula, você deve ser capaz de:\n\n1. enquadrar um problema de detecção de objetos dentro do **CRISP-DM**;\n2. distinguir o papel de dados sintéticos e dados reais em um cenário de generalização;\n3. auditar um dataset no formato YOLO;\n4. treinar um detector baseline com hiperparâmetros reprodutíveis;\n5. interpretar métricas de validação com cautela quando existe **gap entre domínios**;\n6. gerar um `submission.csv` coerente com a competição;\n7. fechar uma rodada completa de experimento.\n","metadata":{}},{"id":"ae80e139","cell_type":"markdown","source":"## Mapa da aula no CRISP-DM\n\n| Fase | Pergunta central | Produto da fase |\n|---|---|---|\n| 1. Business Understanding | O que a competição quer medir? | definição do problema e do artefato final |\n| 2. Data Understanding | Como são os dados sintéticos, reais e as anotações? | leitura do dataset |\n| 3. Data Preparation | Como preparar caminhos, dados e parâmetros? | `data.yaml` e baseline definido |\n| 4. Modeling | Como treinar e refinar o modelo? | pesos treinados e histórico de treinamento |\n| 5. Evaluation | O modelo realmente generaliza? | métricas locais e inspeção visual |\n| 6. Deployment | Como transformar o modelo em submissão? | submissão de um arquivo `submission.csv` na competição do Kaggle |","metadata":{}},{"id":"49194a93","cell_type":"markdown","source":"## Requisitos do ambiente\n\nEste notebook foi pensado para rodar preferencialmente com GPU.\n\nBibliotecas principais:\n- `ultralytics`\n- `torch`\n- `opencv-python`\n- `pyyaml`\n- `matplotlib`\n- `pandas`\n\nEstrutura esperada da competição:\n\n```text\ncompetition_root/\n├── data/\n│   ├── train/\n│   │   ├── images/\n│   │   └── labels/\n│   ├── val/\n│   │   ├── images/\n│   │   └── labels/\n│   └── test/\n│       └── images/\n├── sample_submission.csv\n└── ...\n```\n","metadata":{}},{"id":"c3b3d91f","cell_type":"markdown","source":"## 0. Preparação do ambiente","metadata":{}},{"id":"de6d66bd","cell_type":"code","source":"# Descomente se precisar instalar as dependências no ambiente atual.\n%pip install -q ultralytics pyyaml opencv-python-headless pandas matplotlib numpy torch gdown\nprint(\"Bibliotecas instaladas com sucesso!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"05562388","cell_type":"markdown","source":"### 0.1 Imports e configuração inicial","metadata":{}},{"id":"4ac45846","cell_type":"code","source":"from __future__ import annotations\n\nimport math\nimport random\nfrom pathlib import Path\nimport os, sys, csv\n\nimport gdown\nimport zipfile\n\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport torch\nimport yaml\nfrom ultralytics import YOLO\n\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"mps\" if torch.backends.mps.is_available() else \"cpu\"\ncompetition_root = Path.cwd()\n\nplt.rcParams[\"figure.figsize\"] = (10, 6)\nplt.rcParams[\"axes.grid\"] = True\n\nprint(\"DEVICE =\", DEVICE)\nprint(\"Diretório atual =\", competition_root)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"821f4247","cell_type":"markdown","source":"### 0.2 Parâmetros dos experimentos","metadata":{}},{"id":"cf3a5f3a","cell_type":"code","source":"# Configuração principal do baseline\nweights = \"yolo26m.pt\"\nepochs = 60\nbatch = 16\nmosaic = 0.3\nmixup = 0.1\noptimizer = \"Adam\"\nmomentum = 0.8\nlr0 = 0.0001\nlrf = 0.0001\namp = False\nimgsz = 640\n\nproject = \"multi-grocery-kaggle\"\nbaseline_name = \"baseline\"\nrefined_name = \"refinement\"\n\nsave_dir = competition_root / \"runs\" / \"detect\" / project \n\nprint(\"weights =\", weights)\nprint(\"project =\", project)\nprint(\"baseline_name =\", baseline_name)\nprint(\"refined_name =\", refined_name)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"717230c5","cell_type":"markdown","source":"### 0.3 Construção do `yolo_params.yaml` para configuração dos dados","metadata":{}},{"id":"4a7eac52","cell_type":"code","source":"# depois de adicionar a fonte de dados ao notebook:\n## pastas do banco de dados no Kaggle\ntrain_images = Path(\"/kaggle/input/competitions/grocery-items-multi-class-object-detection/train/train/images\")\ntrain_labels = train_images.parent / \"labels\"\n\nval_images = Path(\"/kaggle/input/competitions/grocery-items-multi-class-object-detection/val/val/images\")\nval_labels = val_images.parent / \"labels\"\n\ntest_images = Path(\"/kaggle/input/competitions/grocery-items-multi-class-object-detection/testImages/testImages/images\")\n\n\n## construcao do yolo_params.yaml\nyolo_params = dict({\n        \"train\": train_images,\n        \"val\": val_images,\n        \"test\": test_images,\n        \"nc\": 3,\n        \"names\": ['cheerios', 'soup', 'candle']\n    })\n\nfor split_name in [\"train\", \"val\", \"test\"]:\n    if split_name in yolo_params and yolo_params[split_name]:\n        split_path = Path(yolo_params[split_name])\n        if split_path.is_absolute():\n            yolo_params[split_name] = str(split_path)\n        else:\n            yolo_params[split_name] = str((competition_root / split_path).resolve())\n\nyolo_params_path = Path.cwd() / \"yolo_params.yaml\"\nwith open(yolo_params_path, \"w\", encoding=\"utf-8\") as f:\n    yaml.safe_dump(yolo_params, f, sort_keys=False, allow_unicode=True)\n\nprint(\"yolo_params_path =\", yolo_params_path)\nyolo_params","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"86dc6018","cell_type":"markdown","source":"### 0.5 Estrutura de caminhos usada ao longo do notebook","metadata":{}},{"id":"3d925e18","cell_type":"code","source":"paths_df = pd.DataFrame([\n    {\"item\": \"train_images\", \"path\": str(train_images), \"exists\": train_images.exists()},\n    {\"item\": \"train_labels\", \"path\": str(train_labels), \"exists\": train_labels.exists()},\n    {\"item\": \"val_images\", \"path\": str(val_images), \"exists\": val_images.exists()},\n    {\"item\": \"val_labels\", \"path\": str(val_labels), \"exists\": val_labels.exists()},\n    {\"item\": \"test_images\", \"path\": str(test_images), \"exists\": test_images.exists()},\n])\n\npaths_df","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"caca9f92","cell_type":"markdown","source":"# Fase 1 e 2 — Business Understanding + Data Understanding","metadata":{}},{"id":"bdc323fa","cell_type":"markdown","source":"## 1. Formulação do problema\n\nA tarefa é:\n\n> treinar um detector em imagens sintéticas e verificar se ele continua útil em imagens reais.","metadata":{}},{"id":"cd0d02f3","cell_type":"markdown","source":"## 2. Estrutura dos splits","metadata":{}},{"id":"8b9dbb85","cell_type":"code","source":"image_extensions = {\".jpg\", \".jpeg\", \".png\", \".bmp\", \".webp\"}\n\ntrain_image_files = sorted([p for p in train_images.iterdir() if p.suffix.lower() in image_extensions]) if train_images.exists() else []\nval_image_files = sorted([p for p in val_images.iterdir() if p.suffix.lower() in image_extensions]) if val_images.exists() else []\ntest_image_files = sorted([p for p in test_images.iterdir() if p.suffix.lower() in image_extensions]) if test_images.exists() else []\n\ntrain_label_files = sorted(train_labels.glob(\"*.txt\")) if train_labels.exists() else []\nval_label_files = sorted(val_labels.glob(\"*.txt\")) if val_labels.exists() else []\n\ntrain_image_stems = {p.stem for p in train_image_files}\nval_image_stems = {p.stem for p in val_image_files}\ntrain_label_stems = {p.stem for p in train_label_files}\nval_label_stems = {p.stem for p in val_label_files}\n\nsummary_df = pd.DataFrame([\n    {\n        \"split\": \"train\",\n        \"images_dir\": str(train_images),\n        \"labels_dir\": str(train_labels),\n        \"n_images\": len(train_image_files),\n        \"n_labels\": len(train_label_files),\n        \"images_without_label\": len(train_image_stems - train_label_stems),\n        \"labels_without_image\": len(train_label_stems - train_image_stems),\n    },\n    {\n        \"split\": \"val\",\n        \"images_dir\": str(val_images),\n        \"labels_dir\": str(val_labels),\n        \"n_images\": len(val_image_files),\n        \"n_labels\": len(val_label_files),\n        \"images_without_label\": len(val_image_stems - val_label_stems),\n        \"labels_without_image\": len(val_label_stems - val_image_stems),\n    },\n    {\n        \"split\": \"test\",\n        \"images_dir\": str(test_images),\n        \"labels_dir\": None,\n        \"n_images\": len(test_image_files),\n        \"n_labels\": None,\n        \"images_without_label\": None,\n        \"labels_without_image\": None,\n    },\n])\n\nsummary_df","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"f44c97ca","cell_type":"markdown","source":"## 3. Conteúdo do `yolo_params.yaml` utilizado na rodada","metadata":{}},{"id":"a6f034e5","cell_type":"code","source":"pd.Series(yolo_params)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"17fe7011","cell_type":"markdown","source":"## 4. Inspeção visual rápida dos dados","metadata":{}},{"id":"b45bb9fa","cell_type":"code","source":"n_show = 6\n\ntrain_preview = train_image_files if len(train_image_files) <= n_show else random.sample(train_image_files, n_show)\nval_preview = val_image_files if len(val_image_files) <= n_show else random.sample(val_image_files, n_show)\ntest_preview = test_image_files if len(test_image_files) <= n_show else random.sample(test_image_files, n_show)\n\nfor preview_list, title in [\n    (train_preview, \"Amostras do treino sintético\"),\n    (val_preview, \"Amostras da validação\"),\n    (test_preview, \"Amostras do teste\"),\n]:\n    if len(preview_list) == 0:\n        print(f\"Nenhuma imagem encontrada para: {title}\")\n        continue\n\n    cols = 3\n    rows = math.ceil(len(preview_list) / cols)\n\n    fig, axes = plt.subplots(rows, cols, figsize=(16, 5 * rows))\n    axes = np.array(axes).reshape(-1)\n\n    for ax in axes:\n        ax.axis(\"off\")\n\n    for ax, img_path in zip(axes, preview_list):\n        img = cv2.imread(str(img_path))\n        if img is None:\n            ax.set_title(f\"Falha ao ler {img_path.name}\", fontsize=9)\n            continue\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        ax.imshow(img)\n        ax.set_title(img_path.name, fontsize=9)\n        ax.axis(\"off\")\n\n    fig.suptitle(title, fontsize=14)\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"b80d04d7","cell_type":"markdown","source":"## 5. Visualização das anotações YOLO no treino","metadata":{}},{"id":"ab3620a4","cell_type":"code","source":"annotated_preview = train_image_files if len(train_image_files) <= 4 else random.sample(train_image_files, 4)\n\nif len(annotated_preview) == 0:\n    print(\"Nenhuma imagem disponível.\")\nelse:\n    fig, axes = plt.subplots(1, len(annotated_preview), figsize=(5 * len(annotated_preview), 5))\n    if len(annotated_preview) == 1:\n        axes = [axes]\n\n    for ax, img_path in zip(axes, annotated_preview):\n        label_path = train_labels / f\"{img_path.stem}.txt\"\n        img = cv2.imread(str(img_path))\n\n        if img is None:\n            ax.set_title(f\"Falha ao ler {img_path.name}\", fontsize=9)\n            ax.axis(\"off\")\n            continue\n\n        h, w = img.shape[:2]\n\n        if label_path.exists():\n            for line in label_path.read_text(encoding=\"utf-8\").splitlines():\n                line = line.strip()\n                if not line:\n                    continue\n                tokens = line.split()\n                try:\n                    values = [float(x) for x in tokens]\n                except ValueError:\n                    continue\n\n                if len(values) < 5:\n                    continue\n\n                cls_id, x_center, y_center, bw, bh = values[:5]\n\n                x1 = int((x_center - bw / 2) * w)\n                y1 = int((y_center - bh / 2) * h)\n                x2 = int((x_center + bw / 2) * w)\n                y2 = int((y_center + bh / 2) * h)\n\n                cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)\n                cv2.putText(\n                    img,\n                    f\"class={int(cls_id)}\",\n                    (x1, max(y1 - 8, 0)),\n                    cv2.FONT_HERSHEY_SIMPLEX,\n                    0.6,\n                    (0, 255, 0),\n                    2\n                )\n\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        ax.imshow(img)\n        ax.set_title(img_path.name, fontsize=9)\n        ax.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"a423bd0b","cell_type":"markdown","source":"## 6. Estatísticas das bounding boxes","metadata":{}},{"id":"4da36660","cell_type":"code","source":"train_bbox_rows = []\n\nfor img_path in train_image_files:\n    label_path = train_labels / f\"{img_path.stem}.txt\"\n    img = cv2.imread(str(img_path))\n    if img is None:\n        continue\n\n    h, w = img.shape[:2]\n\n    if label_path.exists():\n        for line in label_path.read_text(encoding=\"utf-8\").splitlines():\n            line = line.strip()\n            if not line:\n                continue\n            tokens = line.split()\n            try:\n                values = [float(x) for x in tokens]\n            except ValueError:\n                continue\n\n            if len(values) < 5:\n                continue\n\n            cls_id, x_center, y_center, bw, bh = values[:5]\n            train_bbox_rows.append({\n                \"image_name\": img_path.name,\n                \"width_px\": w,\n                \"height_px\": h,\n                \"cls_id\": int(cls_id),\n                \"x_center\": x_center,\n                \"y_center\": y_center,\n                \"bbox_w\": bw,\n                \"bbox_h\": bh,\n                \"bbox_area\": bw * bh,\n                \"bbox_aspect_ratio\": bw / bh if bh > 0 else np.nan,\n            })\n\ntrain_bbox_df = pd.DataFrame(train_bbox_rows)\nprint(\"Quantidade de caixas no treino =\", len(train_bbox_df))\ntrain_bbox_df.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"6fd430b5","cell_type":"code","source":"if not train_bbox_df.empty:\n    fig, axes = plt.subplots(1, 3, figsize=(18, 4))\n\n    axes[0].hist(train_bbox_df[\"bbox_area\"], bins=30)\n    axes[0].set_title(\"Área relativa das caixas\")\n\n    axes[1].hist(train_bbox_df[\"bbox_w\"], bins=30)\n    axes[1].set_title(\"Largura relativa\")\n\n    axes[2].hist(train_bbox_df[\"bbox_h\"], bins=30)\n    axes[2].set_title(\"Altura relativa\")\n\n    plt.tight_layout()\n    plt.show()\n\n    fig, axes = plt.subplots(1, 2, figsize=(14, 4))\n\n    axes[0].hist(train_bbox_df[\"bbox_aspect_ratio\"].dropna(), bins=30)\n    axes[0].set_title(\"Razão de aspecto\")\n\n    axes[1].hist(train_bbox_df[\"cls_id\"], bins=np.arange(train_bbox_df[\"cls_id\"].max() + 2) - 0.5)\n    axes[1].set_title(\"Distribuição de classes\")\n\n    plt.tight_layout()\n    plt.show()\nelse:\n    print(\"Nenhuma bounding box foi encontrada no treino.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"5187494e","cell_type":"markdown","source":"## 7. Estatísticas de tamanho das imagens","metadata":{}},{"id":"446b1e23","cell_type":"code","source":"train_size_rows = []\nfor img_path in train_image_files:\n    img = cv2.imread(str(img_path))\n    if img is None:\n        continue\n    h, w = img.shape[:2]\n    train_size_rows.append({\"image_name\": img_path.name, \"width_px\": w, \"height_px\": h, \"aspect_ratio\": w / h if h > 0 else np.nan})\n\nval_size_rows = []\nfor img_path in val_image_files:\n    img = cv2.imread(str(img_path))\n    if img is None:\n        continue\n    h, w = img.shape[:2]\n    val_size_rows.append({\"image_name\": img_path.name, \"width_px\": w, \"height_px\": h, \"aspect_ratio\": w / h if h > 0 else np.nan})\n\ntest_size_rows = []\nfor img_path in test_image_files:\n    img = cv2.imread(str(img_path))\n    if img is None:\n        continue\n    h, w = img.shape[:2]\n    test_size_rows.append({\"image_name\": img_path.name, \"width_px\": w, \"height_px\": h, \"aspect_ratio\": w / h if h > 0 else np.nan})\n\ntrain_sizes_df = pd.DataFrame(train_size_rows)\nval_sizes_df = pd.DataFrame(val_size_rows)\ntest_sizes_df = pd.DataFrame(test_size_rows)\n\ndisplay(train_sizes_df.describe(include=\"all\"))\ndisplay(val_sizes_df.describe(include=\"all\"))\ndisplay(test_sizes_df.describe(include=\"all\"))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"901e2bb8","cell_type":"markdown","source":"# Fase 3 — Data Preparation","metadata":{}},{"id":"63b8164a","cell_type":"markdown","source":"## 9. Verificação de consistência entre imagens e labels","metadata":{}},{"id":"7665b78b","cell_type":"code","source":"consistency_df = pd.DataFrame([\n    {\n        \"split\": \"train\",\n        \"n_images\": len(train_image_files),\n        \"n_labels\": len(train_label_files),\n        \"images_without_label\": len(train_image_stems - train_label_stems),\n        \"labels_without_image\": len(train_label_stems - train_image_stems)\n    },\n    {\n        \"split\": \"val\",\n        \"n_images\": len(val_image_files),\n        \"n_labels\": len(val_label_files),\n        \"images_without_label\": len(val_image_stems - val_label_stems),\n        \"labels_without_image\": len(val_label_stems - val_image_stems),\n    },\n])\n\nconsistency_df","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"9d848315","cell_type":"markdown","source":"## 10. Hipóteses da primeira rodada\n\nNesta rodada, vamos trabalhar com hipóteses simples e explícitas:\n\n1. um baseline com `yolo26n` é suficiente para a primeira iteração;\n2. os dados sintéticos contêm informação útil, mas não capturam todo o domínio real;\n3. a validação visual em imagens reais é indispensável;\n4. um limiar de confiança mais baixo na inferência pode evitar perda de detecções no teste, ao custo de mais falsos positivos;\n5. a primeira rodada serve para estabelecer um **ponto de comparação**, não para “ganhar a competição”.\n","metadata":{}},{"id":"7702312f","cell_type":"markdown","source":"## 11. Definição do baseline","metadata":{}},{"id":"09c7f3aa","cell_type":"code","source":"baseline_params = {\n    \"data\": str(yolo_params_path), \n    \"epochs\": epochs,\n    \"batch\": batch,\n    \"device\": DEVICE,\n    \"mosaic\": mosaic,\n    \"mixup\": mixup,\n    \"optimizer\": optimizer,\n    \"lr0\": lr0,\n    \"lrf\": lrf,\n    \"momentum\": momentum,\n    \"val\": True,\n    \"imgsz\": imgsz,\n    \"project\": project,\n    \"name\": baseline_name,\n    \"exist_ok\": True,\n    \"plots\": True,\n    \"seed\": SEED,\n    \"save_dir\": str(save_dir / baseline_name),}\n\npd.Series(baseline_params)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"a472287f","cell_type":"markdown","source":"# Fase 4 — Modelagem","metadata":{}},{"id":"11a6d276","cell_type":"markdown","source":"## 13. Treinamento do modelo baseline","metadata":{}},{"id":"6bd3be91","cell_type":"code","source":"RUN_BASELINE = True\n\nif RUN_BASELINE:\n    model_baseline_training = YOLO(weights)\n    baseline_results = model_baseline_training.train(**baseline_params)\nelse:\n    baseline_results = None\n    print(\"Treinamento baseline não executado. Defina RUN_BASELINE=True para rodar.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"421f9674","cell_type":"markdown","source":"## 14. Leitura do histórico do ajuste do modelo baseline","metadata":{}},{"id":"434ee1df","cell_type":"code","source":"baseline_run_dir = Path(\"runs/detect/\" + project) /  baseline_name\nbaseline_results_csv = baseline_run_dir / \"results.csv\"\n\nprint(\"baseline_run_dir =\", baseline_run_dir.resolve())\nprint(\"results.csv existe?\", baseline_results_csv.exists())\n\nif baseline_results_csv.exists():\n    baseline_history = pd.read_csv(baseline_results_csv)\n    display(baseline_history.tail())\nelse:\n    baseline_history = None\n    print(\"results.csv ainda não disponível.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"43d6c3ab","cell_type":"code","source":"if baseline_history is not None:\n    metric_cols = [c for c in baseline_history.columns if not c.startswith(\"epoch\")]\n\n    if len(metric_cols) == 0:\n        print(\"Nenhuma métrica encontrada em results.csv\")\n    else:\n        n_cols = 3\n        n_rows = math.ceil(len(metric_cols) / n_cols)\n\n        fig, axes = plt.subplots(n_rows, n_cols, figsize=(18, 4 * n_rows))\n        axes = np.array(axes).reshape(-1)\n\n        for ax, col in zip(axes, metric_cols):\n            ax.plot(baseline_history.index + 1, baseline_history[col])\n            ax.set_title(col)\n            ax.set_xlabel(\"Época\")\n\n        for ax in axes[len(metric_cols):]:\n            ax.axis(\"off\")\n\n        fig.suptitle(\"Curvas de treinamento — baseline\", fontsize=15)\n        plt.tight_layout()\n        plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"0e6d11e4","cell_type":"markdown","source":"## 15. Carregar o melhor modelo da rodada","metadata":{}},{"id":"1130cd61","cell_type":"code","source":"baseline_best_path = baseline_run_dir / \"weights\" / \"best.pt\"\n\nif baseline_best_path.exists():\n    baseline_model = YOLO(str(baseline_best_path))\n    print(\"Modelo baseline carregado com sucesso.\")\nelse:\n    baseline_model = None\n    print(\"Pesos baseline não encontrados.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"65a30560","cell_type":"markdown","source":"## 16. Refinamento opcional\n\nA segunda rodada existe para ilustrar que o CRISP-DM é iterativo.  \nA ideia não é ajustar aleatoriamente; é alterar poucas variáveis com hipótese explícita.\n","metadata":{}},{"id":"8623ba60","cell_type":"code","source":"RUN_REFINEMENT = True\n\nrefined_params = {\n    \"data\": str(yolo_params_path),\n    \"epochs\": epochs*2,\n    \"batch\": batch,\n    \"device\": DEVICE,\n    \"mosaic\": mosaic,\n    \"mixup\": mixup,\n    \"optimizer\": optimizer,\n    \"lr0\": lr0,\n    \"lrf\": lrf,\n    \"momentum\": momentum,\n    \"val\": True,\n    \"amp\": amp,\n    \"imgsz\": 768,\n    \"project\": project,\n    \"name\": refined_name,\n    \"exist_ok\": True,\n    \"plots\": True,\n    \"seed\": SEED,\n    \"save_dir\": str(save_dir / refined_name),\n}\n\npd.Series(refined_params)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"b31d9caa","cell_type":"code","source":"if RUN_REFINEMENT:\n    model_refined_training = YOLO(weights)\n    refined_results = model_refined_training.train(**refined_params)\nelse:\n    refined_results = None\n    print(\"Refinamento não executado. Defina RUN_REFINEMENT=True para rodar.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"a141a21a","cell_type":"markdown","source":"## 17. Comparação entre rodadas de treinamento","metadata":{}},{"id":"de2857ff","cell_type":"code","source":"comparison_rows = []\n\nfor run_dir in [Path(\"runs/detect/\" + project) /  baseline_name, Path(\"runs/detect/\" + project) / refined_name]:\n    csv_path = run_dir / \"results.csv\"\n    if not csv_path.exists():\n        continue\n\n    df = pd.read_csv(csv_path)\n    if len(df) == 0:\n        continue\n\n    row = {\"run_dir\": str(run_dir)}\n    row.update(df.iloc[-1].to_dict())\n    comparison_rows.append(row)\n\ncomparison_df = pd.DataFrame(comparison_rows)\ncomparison_df","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"01a85666","cell_type":"markdown","source":"# Fase 5 — Avaliação","metadata":{}},{"id":"e5fdfb2c","cell_type":"markdown","source":"## 18. Escolha da rodada para avaliação e submissão","metadata":{}},{"id":"f380b510","cell_type":"code","source":"refined_best_path = Path(\"runs/detect/\" + project) / refined_name / \"weights\" / \"best.pt\"\n\nprint(\"Rodada selecionada =\", refined_best_path)\n\nif not refined_best_path.exists():\n    raise FileNotFoundError(f\"Pesos não encontrados: {refined_best_path}\")\n\nchosen_refined_model = YOLO(str(refined_best_path))\nprint(\"Modelo carregado para avaliação.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"456c623e","cell_type":"markdown","source":"## 19. Avaliação qualitativa em imagens reais de validação","metadata":{}},{"id":"56ae98dc","cell_type":"code","source":"val_preview = val_image_files if len(val_image_files) <= 6 else random.sample(val_image_files, 6)\n\nif len(val_preview) == 0:\n    print(\"Lista de imagens de validação vazia.\")\nelse:\n    cols = 3\n    rows = math.ceil(len(val_preview) / cols)\n\n    fig, axes = plt.subplots(rows, cols, figsize=(16, 5 * rows))\n    axes = np.array(axes).reshape(-1)\n\n    for ax in axes:\n        ax.axis(\"off\")\n\n    for ax, img_path in zip(axes, val_preview):\n        print(f\"Processando {img_path}\")\n        result = chosen_refined_model.predict(str(img_path), conf=0.15, verbose=False)[0]\n        plotted = result.plot()\n        plotted = cv2.cvtColor(plotted, cv2.COLOR_BGR2RGB)\n\n        ax.imshow(plotted)\n        ax.set_title(img_path.name, fontsize=9)\n        ax.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"ec1458b0","cell_type":"markdown","source":"## 21. Interpretação das métricas e das imagens\n\nPerguntas para orientar a discussão:\n\n1. o modelo detecta o objeto, mas localiza mal a caixa?\n2. existem falsos positivos recorrentes em certos tipos de fundo?\n3. há perda de detecção quando o objeto muda de escala, orientação ou iluminação?\n4. a inspeção visual parece coerente com a métrica local?\n5. essa rodada parece madura o suficiente para ser enviada ao leaderboard?\n","metadata":{}},{"id":"f82e6555","cell_type":"markdown","source":"# Fase 6 — Deployment","metadata":{}},{"id":"2899892b","cell_type":"markdown","source":"## 22. Geração de arquivos `.txt` de predição no conjunto de teste","metadata":{}},{"id":"bf862dd8","cell_type":"code","source":"predictions_root = Path(\"predictions_txt\")\npredictions_labels_dir = predictions_root / \"labels\"\npredictions_root.mkdir(exist_ok=True, parents=True)\npredictions_labels_dir.mkdir(exist_ok=True, parents=True)\n\nfor img_path in test_image_files:\n    result = chosen_refined_model.predict(str(img_path), conf=0.015, verbose=False)[0]\n    img_h, img_w = result.orig_shape\n\n    output_txt = predictions_labels_dir / f\"{img_path.stem}.txt\"\n\n    with open(output_txt, \"w\", encoding=\"utf-8\") as f:\n        for box in result.boxes:\n            cls_id = int(box.cls.item())\n            conf_score = float(box.conf.item())\n            x_center = float(box.xywh[0][0].item()) / img_w\n            y_center = float(box.xywh[0][1].item()) / img_h\n            width = float(box.xywh[0][2].item()) / img_w\n            height = float(box.xywh[0][3].item()) / img_h\n\n            f.write(\n                f\"{cls_id} {conf_score:.6f} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\\n\"\n            )\n\nprint(\"Predições salvas em:\", predictions_labels_dir.resolve())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"09970499","cell_type":"markdown","source":"## 23. Inspeção rápida dos arquivos de predição","metadata":{}},{"id":"c27a767b","cell_type":"code","source":"pred_txt_files = sorted(predictions_labels_dir.glob(\"*.txt\"))\nprint(\"Quantidade de arquivos de predição:\", len(pred_txt_files))\n\nfor txt_file in pred_txt_files[:5]:\n    print(\"=\" * 80)\n    print(txt_file.name)\n    print(txt_file.read_text(encoding=\"utf-8\")[:500])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"0a642749","cell_type":"markdown","source":"## 24. Construção do `submission.csv`","metadata":{}},{"id":"5a713726","cell_type":"code","source":"def predictions_to_csv(\n    preds_folder: str = \"predictions/labels\", \n    output_csv: str = \"submission.csv\", \n    test_images_folder: str = \"testImages/images\",\n    allowed_extensions: tuple = (\".jpg\", \".png\", \".jpeg\")\n    ):\n    \"\"\"\n    Convert YOLO prediction files to Kaggle submission CSV format\n    with strict validation.\n    \"\"\"\n    # Validate inno boxputs\n    preds_path = Path(preds_folder)\n    if not preds_path.exists():\n        print(f\"ERROR: Prediction folder '{preds_folder}' does not exist\")\n        sys.exit(1)\n\n    # Get test image IDs (without extensions)\n    test_images_path = Path(test_images_folder)\n    if not test_images_path.exists():\n        print(f\"ERROR: Test images folder '{test_images_folder}' not found\")\n        sys.exit(1)\n        \n    test_images = {\n        p.stem: True \n        for p in test_images_path.glob(\"*\") \n        if p.suffix.lower() in allowed_extensions\n    }\n    print(f\"Found {len(test_images)} test images\")\n\n    # Collect predictions with validation\n    predictions = []\n    error_count = 0\n    \n    for txt_file in preds_path.glob(\"*.txt\"):\n        image_id = txt_file.stem\n        \n        # Validate image_id\n        if image_id not in test_images:\n            print(f\"Skipping non-test image prediction: {txt_file.name}\")\n            continue\n            \n        with open(txt_file, \"r\") as f:\n            valid_lines = []\n            for line_num, line in enumerate(f, 1):\n                line = line.strip()\n                if not line:\n                    continue\n                            \n                parts = line.split()\n                if len(parts) != 6:\n                    print(f\"Invalid prediction in {txt_file.name} line {line_num}: {line}\")\n                    error_count += 1\n                    continue\n                            \n                try:\n                    [float(x) for x in parts]\n                    valid_lines.append(line)\n                except ValueError:\n                    print(f\"Non-numeric values in {txt_file.name} line {line_num}: {line}\")\n                    error_count += 1\n                    continue\n\n        flat_pred = \" \".join(\" \".join(line.split()) for line in valid_lines)\n        pred_str = flat_pred if flat_pred else \"no box\"\n        predictions.append({\"image_id\": image_id, \"prediction_string\": pred_str})\n\n\n    # Create submission dataframe\n    submission_df = pd.DataFrame({\"image_id\": list(test_images.keys())})\n    \n    if predictions:\n        preds_df = pd.DataFrame(predictions)\n        final_df = submission_df.merge(preds_df, on=\"image_id\", how=\"left\").fillna(\"no boxes\")\n    else:\n        final_df = submission_df\n        final_df[\"prediction_string\"] = \"no boxes\"\n\n    # Save with CSV quoting rules\n    final_df.to_csv(output_csv, index=False, quoting=csv.QUOTE_NONNUMERIC)\n    \n    print(f\"\\n Success! Submission saved to {output_csv}\")\n    print(f\"   Total predictions: {len(predictions)}\")\n    print(f\"   Validation errors: {error_count}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"ebf91c62","cell_type":"code","source":"predictions_to_csv(\n    preds_folder=str(predictions_labels_dir), \n    output_csv=\"submission.csv\", \n    test_images_folder=str(test_images),\n    allowed_extensions=(\".jpg\", \".png\", \".jpeg\")\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"5cbb479a","cell_type":"markdown","source":"## 25. Verificações finais antes da submissão","metadata":{}},{"id":"0de439a8","cell_type":"code","source":"submission_df = pd.read_csv(\"submission.csv\")\nprint(\"Shape da submissão:\", submission_df.shape)\nprint(\"Colunas:\", list(submission_df.columns))\nprint(\"Linhas sem detecção ('no boxes'):\", (submission_df.iloc[:, 1] == \"no boxes\").sum())\ndisplay(submission_df.head())","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"367e3d4f","cell_type":"markdown","source":"## Encerramento da rodada CRISP-DM\n\nNesta iteração, você:\n\n- entendeu a natureza do problema;\n- inspecionou os dados e o gap entre domínios;\n- preparou um `yolo_params.yaml` operacional;\n- treinou um baseline com YOLO;\n- avaliou quantitativamente e qualitativamente;\n- gerou predições e um `submission.csv`.\n\n### O que deve vir na próxima rodada\n1. revisar a escolha do modelo-base (optar por tamanhos diferentes);\n2. testar mais de uma configuração de `imgsz`;\n3. investigar o efeito de augmentations sobre generalização real;\n4. comparar score local com score do leaderboard;\n5. discutir explicitamente por que a validação local pode falhar como estimador do desempenho real.","metadata":{}}]}