{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"2e7cf5dd-1e7a-407e-b575-a74b6579ca09","cell_type":"markdown","source":"# 擬似ラベル生成 — ゼロショット分類 (Approach A)\n\nモデル: `joeddav/xlm-roberta-large-xnli`  \n手法: NLIベースのゼロショット分類で、多言語Reportから12ラベルの信頼スコアを推定する\n\n## 対象ラベル\nACL, MCL, Medial Meniscus, Lateral Meniscus, Medial OA, Lateral OA, PF OA, Effusion, Synovitis, Baker's, Contusion, Fracture","metadata":{}},{"id":"47b1ff8c-5d96-4e7d-9e43-bb8cd39590bf","cell_type":"markdown","source":"## 0. インストール","metadata":{}},{"id":"ddb735c6-b0e1-417e-ba3f-e5473b15f33b","cell_type":"code","source":"!pip install transformers torch accelerate tqdm matplotlib scikit-learn -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:39:37.389857Z","iopub.execute_input":"2026-08-12T06:39:37.390155Z","iopub.status.idle":"2026-08-12T06:39:40.9685Z","shell.execute_reply.started":"2026-08-12T06:39:37.390132Z","shell.execute_reply":"2026-08-12T06:39:40.967623Z"}},"outputs":[],"execution_count":null},{"id":"62d60bfa-08c8-46cc-839e-1ee10fb723c2","cell_type":"markdown","source":"## 1. ライブラリのインポート","metadata":{}},{"id":"0da2eeb2-3ad7-4ff2-8805-1211fd6832ac","cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nfrom transformers import pipeline\nfrom tqdm.auto import tqdm\nimport warnings\nwarnings.filterwarnings('ignore')\n\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name(0)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:39:40.970487Z","iopub.execute_input":"2026-08-12T06:39:40.971029Z","iopub.status.idle":"2026-08-12T06:39:40.976774Z","shell.execute_reply.started":"2026-08-12T06:39:40.970999Z","shell.execute_reply":"2026-08-12T06:39:40.975881Z"}},"outputs":[],"execution_count":null},{"id":"939c6968-37c4-4be1-9802-d81fba6f2098","cell_type":"markdown","source":"## 2. データの読み込み","metadata":{}},{"id":"4b07a941-4bc2-4f4f-bb05-bdd4a5ef82a3","cell_type":"code","source":"DATA_DIR = \"/kaggle/input/competitions/rsna-knee-abnormality-detection\"  \n\ntrain = pd.read_csv(f\"{DATA_DIR}/train.csv\")\n\nLABELS = [\n    'ACL', 'MCL', 'Medial Meniscus', 'Lateral Meniscus',\n    'Medial OA', 'Lateral OA', 'PF OA', 'Effusion',\n    'Synovitis', \"Baker's\", 'Contusion', 'Fracture'\n]\n\n# ラベルなし症例のみを対象とする\nunlabeled = train[train[LABELS[0]].isna()].copy().reset_index(drop=True)\nlabeled   = train[train[LABELS[0]].notna()].copy().reset_index(drop=True)\n\nprint(f\"全症例数        : {len(train)}\")\nprint(f\"ラベルあり      : {len(labeled)}\")\nprint(f\"ラベルなし(対象): {len(unlabeled)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:39:40.977963Z","iopub.execute_input":"2026-08-12T06:39:40.978291Z","iopub.status.idle":"2026-08-12T06:39:41.086163Z","shell.execute_reply.started":"2026-08-12T06:39:40.97826Z","shell.execute_reply":"2026-08-12T06:39:41.085489Z"}},"outputs":[],"execution_count":null},{"id":"e5ec1500-e4c1-496c-825f-9ee57b2564dc","cell_type":"markdown","source":"## 3. ゼロショット分類器のロード\n\n`joeddav/xlm-roberta-large-xnli` は xlm-roberta-large を XNLI (多言語NLI) でファインチューニングしたモデル。  \n仮説文を英語で与えるだけで、多言語テキストに対してゼロショット分類ができる。","metadata":{}},{"id":"60caafde-8a4d-4527-a664-4c57993dd267","cell_type":"code","source":"MODEL_NAME = \"joeddav/xlm-roberta-large-xnli\"\n\ndevice = 0 if torch.cuda.is_available() else -1  # GPU=0, CPU=-1\n\nclassifier = pipeline(\n    \"zero-shot-classification\",\n    model=MODEL_NAME,\n    device=device,\n    torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,\n)\n\nprint(f\"モデルロード完了: {MODEL_NAME}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:39:41.088105Z","iopub.execute_input":"2026-08-12T06:39:41.088734Z","iopub.status.idle":"2026-08-12T06:40:09.305959Z","shell.execute_reply.started":"2026-08-12T06:39:41.0887Z","shell.execute_reply":"2026-08-12T06:40:09.30507Z"}},"outputs":[],"execution_count":null},{"id":"d0cf819f-0407-4f4b-86f4-c435aa5b5ee9","cell_type":"markdown","source":"## 4. ラベルごとの仮説文定義\n\nNLI の「含意 (entailment)」スコアを陽性確率として使う。  \n仮説文は英語で書いてOK（xlm-roberta-xnliは多言語対応）。","metadata":{}},{"id":"7f5db6aa-a20b-406c-a0ac-9a073827a243","cell_type":"code","source":"LABEL_HYPOTHESES = {\n    'ACL':              \"The anterior cruciate ligament is torn or injured.\",\n    'MCL':              \"The medial collateral ligament is torn or injured.\",\n    'Medial Meniscus':  \"There is a medial meniscus tear.\",\n    'Lateral Meniscus': \"There is a lateral meniscus tear.\",\n    'Medial OA':        \"There is medial compartment osteoarthritis.\",\n    'Lateral OA':       \"There is lateral compartment osteoarthritis.\",\n    'PF OA':            \"There is patellofemoral osteoarthritis.\",\n    'Effusion':         \"There is joint effusion or fluid in the knee.\",\n    'Synovitis':        \"There is synovitis or synovial inflammation.\",\n    \"Baker's\":          \"There is a Baker's cyst or popliteal cyst.\",\n    'Contusion':        \"There is a bone contusion or bone bruise.\",\n    'Fracture':         \"There is a fracture.\",\n}\n\nfor label, hyp in LABEL_HYPOTHESES.items():\n    print(f\"  [{label:<20}] → {hyp}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:40:09.307Z","iopub.execute_input":"2026-08-12T06:40:09.307602Z","iopub.status.idle":"2026-08-12T06:40:09.313686Z","shell.execute_reply.started":"2026-08-12T06:40:09.307569Z","shell.execute_reply":"2026-08-12T06:40:09.312887Z"}},"outputs":[],"execution_count":null},{"id":"d5ba1201-7a41-474b-81bb-f6112ad33061","cell_type":"markdown","source":"## 5. 動作確認（1件テスト）","metadata":{}},{"id":"c019dee4-c942-4717-bdfa-19b1f3e954f1","cell_type":"code","source":"sample_report = unlabeled['Report'].iloc[0]\nprint(\"== テストレポート ==\")\nprint(sample_report[:300], \"...\")\nprint()\n\ntest_label = 'ACL'\npos_hyp = LABEL_HYPOTHESES[test_label]\nneg_hyp = \"The anterior cruciate ligament is normal and intact.\"\n\nresult = classifier(\n    sample_report[:1800],\n    candidate_labels=[pos_hyp, neg_hyp],\n)\nprint(f\"== {test_label} 判定結果 ==\")\nfor lbl, score in zip(result['labels'], result['scores']):\n    print(f\"  スコア {score:.4f}  →  {lbl[:70]}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:40:09.314713Z","iopub.execute_input":"2026-08-12T06:40:09.315185Z","iopub.status.idle":"2026-08-12T06:40:09.870876Z","shell.execute_reply.started":"2026-08-12T06:40:09.315162Z","shell.execute_reply":"2026-08-12T06:40:09.870054Z"}},"outputs":[],"execution_count":null},{"id":"c9398af8-5195-47cc-84a2-559e96eb4e54","cell_type":"markdown","source":"## 6. 全データへの推論\n\n- 1件あたり12ラベル × NLI推論\n- GPU推奨（V100/A100で約1〜2時間 / 4000件）\n- `BATCH_SIZE` を VRAM に合わせて調整","metadata":{}},{"id":"2aea6f30-48e9-4f05-b365-8a60d40c20c0","cell_type":"code","source":"MAX_CHARS = 1800  # xlm-roberta 512トークン上限の文字数近似\nBATCH_SIZE = 8    # GPU VRAM に合わせて調整（VRAM 8GB→4, 16GB→8, 40GB→16）\n\ndef truncate_text(text, max_chars=MAX_CHARS):\n    if isinstance(text, str) and len(text) > max_chars:\n        return text[:max_chars]\n    return text if isinstance(text, str) else \"\"\n\nreports = unlabeled['Report'].apply(truncate_text).tolist()\nprint(f\"推論対象レポート数: {len(reports)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:40:09.871725Z","iopub.execute_input":"2026-08-12T06:40:09.871926Z","iopub.status.idle":"2026-08-12T06:40:09.880381Z","shell.execute_reply.started":"2026-08-12T06:40:09.871907Z","shell.execute_reply":"2026-08-12T06:40:09.879684Z"}},"outputs":[],"execution_count":null},{"id":"1be95784-3bf7-4780-8bfc-0444f6265fc4","cell_type":"code","source":"pseudo_scores = {}\n\nfor label, pos_hyp in LABEL_HYPOTHESES.items():\n    neg_hyp = f\"The knee has no {label.lower()} abnormality.\"\n    scores = []\n\n    for i in tqdm(range(0, len(reports), BATCH_SIZE), desc=f\"[{label}]\"):\n        batch = reports[i : i + BATCH_SIZE]\n        results = classifier(\n            batch,\n            candidate_labels=[pos_hyp, neg_hyp],\n            truncation=True,\n        )\n        if not isinstance(results, list):\n            results = [results]\n\n        for res in results:\n            # candidate_labels[0] = pos_hyp のスコア（entailment 確率）\n            pos_idx = res['labels'].index(pos_hyp)\n            scores.append(res['scores'][pos_idx])\n\n    pseudo_scores[label] = scores\n    print(f\"  -> mean={np.mean(scores):.3f}, max={np.max(scores):.3f}, min={np.min(scores):.3f}\")\n\nprint(\"\\n✅ 全ラベルの推論完了\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T06:40:09.881531Z","iopub.execute_input":"2026-08-12T06:40:09.881812Z","iopub.status.idle":"2026-08-12T07:11:11.816383Z","shell.execute_reply.started":"2026-08-12T06:40:09.881781Z","shell.execute_reply":"2026-08-12T07:11:11.815575Z"}},"outputs":[],"execution_count":null},{"id":"79dc1865-6d8f-49f5-9474-7b882a7af257","cell_type":"markdown","source":"## 7. 結果の整形と保存","metadata":{}},{"id":"ea76fbd4-b944-4052-9884-d153c9bdacdf","cell_type":"code","source":"pseudo_df = unlabeled[['StudyInstanceUID', 'Report']].copy()\n\nfor label in LABELS:\n    pseudo_df[label] = pseudo_scores[label]\n\nprint(\"== 疑似ラベル スコア統計 ==\")\npseudo_df[LABELS].describe().round(3)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:11:11.81756Z","iopub.execute_input":"2026-08-12T07:11:11.817875Z","iopub.status.idle":"2026-08-12T07:11:11.887666Z","shell.execute_reply.started":"2026-08-12T07:11:11.81785Z","shell.execute_reply":"2026-08-12T07:11:11.887044Z"}},"outputs":[],"execution_count":null},{"id":"bd3437a0-c13c-4017-8604-b32aff7585f3","cell_type":"code","source":"import matplotlib.pyplot as plt\n\nfig, axes = plt.subplots(3, 4, figsize=(16, 10))\naxes = axes.flatten()\n\nfor i, label in enumerate(LABELS):\n    axes[i].hist(pseudo_df[label], bins=30, color='steelblue', alpha=0.75, edgecolor='white')\n    axes[i].set_title(label, fontsize=11)\n    axes[i].set_xlabel('Confidence Score')\n    axes[i].set_ylabel('Count')\n    axes[i].axvline(0.5, color='red', linestyle='--', linewidth=1)\n\nplt.suptitle('Pseudo-Label Score Distributions (Zero-Shot XLM-RoBERTa-XNLI)', fontsize=13, y=1.02)\nplt.tight_layout()\nplt.savefig(\"/pseudo_label_score_dist.png\", dpi=150, bbox_inches='tight')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:26:56.414825Z","iopub.execute_input":"2026-08-12T07:26:56.415112Z","iopub.status.idle":"2026-08-12T07:26:59.707358Z","shell.execute_reply.started":"2026-08-12T07:26:56.415089Z","shell.execute_reply":"2026-08-12T07:26:59.706576Z"}},"outputs":[],"execution_count":null},{"id":"4ac49e93-48c3-476a-a440-3ffd53ab8716","cell_type":"code","source":"print(\"== 閾値0.5での陽性率 ==\")\nfor label in LABELS:\n    pos_rate = (pseudo_df[label] >= 0.5).mean()\n    print(f\"  {label:<20}: {pos_rate:.1%}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:27:01.916167Z","iopub.execute_input":"2026-08-12T07:27:01.916965Z","iopub.status.idle":"2026-08-12T07:27:01.925428Z","shell.execute_reply.started":"2026-08-12T07:27:01.916927Z","shell.execute_reply":"2026-08-12T07:27:01.924702Z"}},"outputs":[],"execution_count":null},{"id":"06d15114-0012-45f8-84d2-7b32d1d3019f","cell_type":"code","source":"# ① スコアそのまま（soft label 用）\nOUTPUT_SCORE = \"pseudo_label_scores.csv\"\npseudo_df.to_csv(OUTPUT_SCORE, index=False)\nprint(f\"✅ スコア保存: {OUTPUT_SCORE}\")\n\n# ② 信頼度フィルタ版（全ラベルのスコアが閾値の外側にある行のみ残す）\nTHRESHOLD_HIGH = 0.7  # これ以上 → 陽性と判定\nTHRESHOLD_LOW  = 0.3  # これ以下 → 陰性と判定\n\nconfident_mask = pseudo_df[LABELS].apply(\n    lambda col: (col >= THRESHOLD_HIGH) | (col <= THRESHOLD_LOW)\n).all(axis=1)\n\nbinary_df = pseudo_df.copy()\nfor label in LABELS:\n    binary_df[label] = (binary_df[label] >= 0.5).astype(int)\n\nconfident_binary_df = binary_df[confident_mask].copy()\n\nOUTPUT_BINARY = f\"pseudo_label_binary_confident.csv\"\nconfident_binary_df.to_csv(OUTPUT_BINARY, index=False)\nprint(f\"✅ 信頼度フィルタ済み保存: {OUTPUT_BINARY}\")\nprint(f\"   信頼できる件数: {len(confident_binary_df)} / {len(pseudo_df)} ({len(confident_binary_df)/len(pseudo_df):.1%})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:27:29.925062Z","iopub.execute_input":"2026-08-12T07:27:29.925679Z","iopub.status.idle":"2026-08-12T07:27:30.190086Z","shell.execute_reply.started":"2026-08-12T07:27:29.925649Z","shell.execute_reply":"2026-08-12T07:27:30.189297Z"}},"outputs":[],"execution_count":null},{"id":"058133e7-e1f2-4a02-8c53-835e1c5b70b6","cell_type":"markdown","source":"## 8. 検証：ラベルあり58件での AUC-ROC 評価","metadata":{}},{"id":"e8ee250c-82ab-4fc1-8c20-20890c56de8b","cell_type":"code","source":"from sklearn.metrics import roc_auc_score\n\nlabeled_reports = labeled['Report'].apply(truncate_text).tolist()\nval_scores = {}\n\nfor label, pos_hyp in LABEL_HYPOTHESES.items():\n    neg_hyp = f\"The knee has no {label.lower()} abnormality.\"\n    scores = []\n    for i in tqdm(range(0, len(labeled_reports), BATCH_SIZE), desc=f\"Val [{label}]\"):\n        batch = labeled_reports[i : i + BATCH_SIZE]\n        results = classifier(batch, candidate_labels=[pos_hyp, neg_hyp], truncation=True)\n        if not isinstance(results, list):\n            results = [results]\n        for res in results:\n            pos_idx = res['labels'].index(pos_hyp)\n            scores.append(res['scores'][pos_idx])\n    val_scores[label] = scores\n\nprint(\"\\n== 検証 AUC-ROC (ラベルあり58件) ==\")\naucs = []\nfor label in LABELS:\n    y_true  = labeled[label].values.astype(int)\n    y_score = val_scores[label]\n    try:\n        auc = roc_auc_score(y_true, y_score)\n        aucs.append(auc)\n        print(f\"  {label:<20}: AUC = {auc:.4f}\")\n    except Exception as e:\n        print(f\"  {label:<20}: AUC = N/A ({e})\")\n\nprint(f\"\\n  Macro-averaged AUC: {np.mean(aucs):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:27:36.875422Z","iopub.execute_input":"2026-08-12T07:27:36.875801Z","iopub.status.idle":"2026-08-12T07:28:04.657065Z","shell.execute_reply.started":"2026-08-12T07:27:36.875771Z","shell.execute_reply":"2026-08-12T07:28:04.656135Z"}},"outputs":[],"execution_count":null},{"id":"21ef284d-de27-4ecb-a6c8-63ae382466e5","cell_type":"markdown","source":"## 9. 次のステップ\n\n生成した疑似ラベルの活用方法:\n\n| ファイル | 用途 |\n|---|---|\n| `pseudo_label_scores.csv` | 連続スコアのまま → 画像モデルの soft label として利用 |\n| `pseudo_label_binary_confident.csv` | 二値ラベル（信頼度フィルタ済み） → Approach B の追加学習データ |\n\n**改善アイデア:**\n- ラベルごとに閾値を最適化（ROC曲線のYouden's J）\n- 仮説文をドメイン適応（医学用語を増やす）\n- Approach B（xlm-roberta-large + 58件でfine-tune）と組み合わせて精度向上","metadata":{}},{"id":"7e4f6ddc-0927-43fb-88fb-71ed37b14f11","cell_type":"code","source":"#Code by Abdul Basit https://www.kaggle.com/code/abdulbasitniazi/enetb7-explained-98-fine-tuning-eda\nimport glob\ntrain_series = glob.glob('../input/competitions/rsna-knee-abnormality-detection/train_series/**/**/*.dcm')\nprint(\"Total number of images: \", len(train_series))\n\ntrain_series = pd.Series(train_series)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:30:43.256929Z","iopub.execute_input":"2026-08-12T07:30:43.257328Z","iopub.status.idle":"2026-08-12T07:32:14.774372Z","shell.execute_reply.started":"2026-08-12T07:30:43.257299Z","shell.execute_reply":"2026-08-12T07:32:14.773441Z"}},"outputs":[],"execution_count":null},{"id":"955f6600-25d3-4dab-9508-abac75b8ec63","cell_type":"code","source":"#By Marco Vasquez E https://www.kaggle.com/code/marcovasquez/basic-eda-data-visualization/notebook\n\nfig=plt.figure(figsize=(15, 10))\ncolumns = 5; rows = 4\nfor i in range(1, columns*rows +1):\n    ds = pydicom.dcmread(train_series[i])#Original was dcmread(train_images_dir + train_images[i])\n    fig.add_subplot(rows, columns, i)\n    plt.imshow(ds.pixel_array, cmap=plt.cm.bone)\n    fig.add_subplot","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:32:14.776017Z","iopub.execute_input":"2026-08-12T07:32:14.776404Z","iopub.status.idle":"2026-08-12T07:32:14.789497Z","shell.execute_reply.started":"2026-08-12T07:32:14.776378Z","shell.execute_reply":"2026-08-12T07:32:14.788339Z"}},"outputs":[],"execution_count":null},{"id":"a2c1166f-f0fb-40cc-be49-dafb976c1001","cell_type":"code","source":"print(ds) # this is file type of image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:32:14.790328Z","iopub.status.idle":"2026-08-12T07:32:14.790667Z","shell.execute_reply.started":"2026-08-12T07:32:14.790461Z","shell.execute_reply":"2026-08-12T07:32:14.790476Z"}},"outputs":[],"execution_count":null},{"id":"9928bb80-564f-46ac-8264-9dc567810551","cell_type":"code","source":"im = ds.pixel_array\nprint(type(im))\nprint(im.dtype)\nprint(im.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:32:14.791757Z","iopub.status.idle":"2026-08-12T07:32:14.792033Z","shell.execute_reply.started":"2026-08-12T07:32:14.791879Z","shell.execute_reply":"2026-08-12T07:32:14.791891Z"}},"outputs":[],"execution_count":null},{"id":"5fad38b8-cccd-4964-884e-368d0c903f18","cell_type":"code","source":"#Marco Vasquez E https://www.kaggle.com/code/marcovasquez/basic-eda-data-visualization/notebook\n\nimport pylab\n\npylab.imshow(im, cmap=pylab.cm.gist_gray)\npylab.axis('on');","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-12T07:32:25.14938Z","iopub.execute_input":"2026-08-12T07:32:25.150019Z","iopub.status.idle":"2026-08-12T07:32:25.16035Z","shell.execute_reply.started":"2026-08-12T07:32:25.149988Z","shell.execute_reply":"2026-08-12T07:32:25.159157Z"}},"outputs":[],"execution_count":null},{"id":"e64f5339-aa9b-410e-bf81-c9b1ec18a0a2","cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}