{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":13451,"databundleVersionId":1188070,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport json\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport pydicom\nfrom keras import layers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom keras.callbacks import Callback, ModelCheckpoint, EarlyStopping\nfrom keras.initializers import Constant\nfrom keras.models import Sequential\nfrom keras.optimizers import Adam\nfrom tensorflow.python.ops import array_ops\nfrom tqdm import tqdm\nfrom keras import backend as K\nimport tensorflow as tf\nimport keras\nfrom keras.applications import ResNet50\nfrom keras.models import Model, load_model\nfrom math import ceil, floor\nfrom sklearn.model_selection import ShuffleSplit\nfrom sklearn.metrics import log_loss\nfrom keras.layers import Dense, Flatten, Dropout, GlobalAveragePooling2D\nfrom keras.applications.resnet50 import preprocess_input","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:02:56.881914Z","iopub.execute_input":"2025-10-29T19:02:56.88222Z","iopub.status.idle":"2025-10-29T19:02:56.887782Z","shell.execute_reply.started":"2025-10-29T19:02:56.882198Z","shell.execute_reply":"2025-10-29T19:02:56.887044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.listdir('/kaggle/input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:03:09.27185Z","iopub.execute_input":"2025-10-29T19:03:09.272153Z","iopub.status.idle":"2025-10-29T19:03:09.285816Z","shell.execute_reply.started":"2025-10-29T19:03:09.272124Z","shell.execute_reply":"2025-10-29T19:03:09.285148Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Base_path = '/kaggle/input/rsna-intracranial-hemorrhage-detection/rsna-intracranial-hemorrhage-detection/'\ntrain_dir = 'stage_2_train/'\ntest_dir = 'stage_2_test/'\n\ntrain_csv = os.path.join(Base_path, 'stage_2_train.csv')\ntrain_df = pd.read_csv(train_csv)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:03:10.985394Z","iopub.execute_input":"2025-10-29T19:03:10.98563Z","iopub.status.idle":"2025-10-29T19:03:15.530341Z","shell.execute_reply.started":"2025-10-29T19:03:10.985613Z","shell.execute_reply":"2025-10-29T19:03:15.529548Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df['filename'] = train_df['ID'].apply(lambda st: \"ID_\" + st.split('_')[1] + \".png\")\ntrain_df['type'] =train_df['ID'].str.split('_').str[2]\n\nprint(train_df.shape)\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:03:19.399962Z","iopub.execute_input":"2025-10-29T19:03:19.400294Z","iopub.status.idle":"2025-10-29T19:03:31.163396Z","shell.execute_reply.started":"2025-10-29T19:03:19.400273Z","shell.execute_reply":"2025-10-29T19:03:31.162656Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"np.random.seed(2025)\nsample_files = np.random.choice(os.listdir(Base_path + train_dir),50000)\nsample_df = train_df[train_df.filename.apply(lambda x: x.replace('.png' , '.dcm')).isin(sample_files)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:07:57.101613Z","iopub.execute_input":"2025-10-29T19:07:57.10189Z","iopub.status.idle":"2025-10-29T19:08:10.683982Z","shell.execute_reply.started":"2025-10-29T19:07:57.101869Z","shell.execute_reply":"2025-10-29T19:08:10.683369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pivot_df = sample_df[['Label','filename','type']].drop_duplicates().pivot(index = 'filename' , columns = 'type' , values = 'Label').reset_index()\nprint(pivot_df.shape)\npivot_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:09:12.568324Z","iopub.execute_input":"2025-10-29T19:09:12.568727Z","iopub.status.idle":"2025-10-29T19:09:13.007942Z","shell.execute_reply.started":"2025-10-29T19:09:12.568701Z","shell.execute_reply":"2025-10-29T19:09:13.007207Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"validation_df = pivot_df.sample(int(len(pivot_df)*0.15))\nvalidation_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T19:09:38.384143Z","iopub.execute_input":"2025-10-29T19:09:38.384711Z","iopub.status.idle":"2025-10-29T19:09:38.39693Z","shell.execute_reply.started":"2025-10-29T19:09:38.384687Z","shell.execute_reply":"2025-10-29T19:09:38.396326Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true = []\nfor i in range(len(validation_df)):\n    y_true.append(validation_df.iloc[i,1])\n\nlen(y_true)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T07:41:54.689867Z","iopub.execute_input":"2025-10-08T07:41:54.690182Z","iopub.status.idle":"2025-10-08T07:41:54.814409Z","shell.execute_reply.started":"2025-10-08T07:41:54.690159Z","shell.execute_reply":"2025-10-08T07:41:54.813808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"full_true = []\nfor i in range(len(validation_df)):\n    for j in range(1,7):\n        full_true.append(validation_df.iloc[i,j])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T07:41:55.513519Z","iopub.execute_input":"2025-10-08T07:41:55.513821Z","iopub.status.idle":"2025-10-08T07:41:56.211055Z","shell.execute_reply.started":"2025-10-08T07:41:55.513787Z","shell.execute_reply":"2025-10-08T07:41:56.210219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"training_df = pivot_df[~(pivot_df.filename.isin(validation_df.filename))]\ntraining_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T07:41:56.612694Z","iopub.execute_input":"2025-10-08T07:41:56.613447Z","iopub.status.idle":"2025-10-08T07:41:56.639514Z","shell.execute_reply.started":"2025-10-08T07:41:56.613422Z","shell.execute_reply":"2025-10-08T07:41:56.638699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(training_df.head())\nprint(validation_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-07T16:05:20.411878Z","iopub.execute_input":"2025-10-07T16:05:20.412159Z","iopub.status.idle":"2025-10-07T16:05:20.419942Z","shell.execute_reply.started":"2025-10-07T16:05:20.41214Z","shell.execute_reply":"2025-10-07T16:05:20.419078Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_pixels_hu(scan): \n    image = np.stack([scan.pixel_array])\n    image = image.astype(np.int16) \n    \n    image[image == -2000] = 0\n    \n    intercept = scan.RescaleIntercept\n    slope = scan.RescaleSlope\n    \n    if slope != 1: \n        image = slope * image.astype(np.float64)\n        image = image.astype(np.int16)\n    \n    image += np.int16(intercept) \n    \n    return np.array(image, dtype=np.int16)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T07:42:03.81388Z","iopub.execute_input":"2025-10-08T07:42:03.814397Z","iopub.status.idle":"2025-10-08T07:42:03.819469Z","shell.execute_reply.started":"2025-10-08T07:42:03.814372Z","shell.execute_reply":"2025-10-08T07:42:03.818589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def apply_window(image, center, width):\n    image = image.copy()\n    min_value = center - width // 2\n    max_value = center + width // 2\n    image[image < min_value] = min_value\n    image[image > max_value] = max_value\n    return image\n\n\ndef apply_window_policy(image):\n\n    image1 = apply_window(image, 40, 80) # brain\n    image2 = apply_window(image, 80, 200) # subdural\n    image3 = apply_window(image, 40, 380) # bone\n    image1 = (image1 - 0) / 80\n    image2 = (image2 - (-20)) / 200\n    image3 = (image3 - (-150)) / 380\n    image = np.array([\n        image1 - image1.mean(),\n        image2 - image2.mean(),\n        image3 - image3.mean(),\n    ]).transpose(1,2,0)\n\n    return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T07:42:04.213189Z","iopub.execute_input":"2025-10-08T07:42:04.213443Z","iopub.status.idle":"2025-10-08T07:42:04.219282Z","shell.execute_reply.started":"2025-10-08T07:42:04.213423Z","shell.execute_reply":"2025-10-08T07:42:04.218421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def save_and_resize(filenames, load_dir):    \n    save_dir = '/kaggle/tmp/'\n    if not os.path.exists(save_dir):\n        os.makedirs(save_dir)\n\n    for filename in tqdm(filenames):\n        try:\n            path = load_dir + filename\n            new_path = save_dir + filename.replace('.dcm', '.png')\n            dcm = pydicom.dcmread(path)\n            image = get_pixels_hu(dcm)\n            image = apply_window_policy(image[0])\n            image -= image.min((0,1))\n            image = (255*image).astype(np.uint8)\n            image = cv2.resize(image, (224, 224)) #smaller\n            res = cv2.imwrite(new_path, image)\n            \n        except ValueError:\n            continue","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T07:42:13.607533Z","iopub.execute_input":"2025-10-08T07:42:13.608318Z","iopub.status.idle":"2025-10-08T07:42:13.613994Z","shell.execute_reply.started":"2025-10-08T07:42:13.608289Z","shell.execute_reply":"2025-10-08T07:42:13.613208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"save_and_resize(filenames=sample_files, load_dir=Base_path + train_dir)\n#save_and_resize(filenames=sample_test, load_dir=Base_path + test_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T07:42:13.971328Z","iopub.execute_input":"2025-10-08T07:42:13.971887Z","iopub.status.idle":"2025-10-08T08:11:45.622838Z","shell.execute_reply.started":"2025-10-08T07:42:13.971863Z","shell.execute_reply":"2025-10-08T08:11:45.622083Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_model():    \n    # Use ResNet50 instead of Xception\n    base_model = ResNet50(weights='imagenet', include_top=False, input_shape=(224,224,3))\n    \n    for layer in base_model.layers[-10:]:\n        layer.trainable = True\n        \n    # Add custom layers on top\n    x = base_model.output\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(0.15)(x)\n    y_pred = Dense(6, activation='sigmoid')(x)\n\n    return Model(inputs=base_model.input, outputs=y_pred)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T08:16:56.590574Z","iopub.execute_input":"2025-10-08T08:16:56.590872Z","iopub.status.idle":"2025-10-08T08:16:56.596443Z","shell.execute_reply.started":"2025-10-08T08:16:56.590843Z","shell.execute_reply":"2025-10-08T08:16:56.595649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"LR = 0.00005\nmodel = create_model()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T08:16:57.000685Z","iopub.execute_input":"2025-10-08T08:16:57.000945Z","iopub.status.idle":"2025-10-08T08:17:01.361767Z","shell.execute_reply.started":"2025-10-08T08:16:57.000918Z","shell.execute_reply":"2025-10-08T08:17:01.361224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 16 # had to revert back to 16 to have a comparaison point with the large model I ran locally \n\ndef create_datagen():\n    return ImageDataGenerator(\n        preprocessing_function=preprocess_input,\n        rotation_range=15,        # چرخش تصادفی ±15 درجه\n        width_shift_range=0.1,    # جابجایی افقی تا 10%\n        height_shift_range=0.1,   # جابجایی عمودی تا 10%\n        shear_range=0.1,          # برش زاویه‌ای\n        zoom_range=0.1,           # زوم تصادفی ±10%\n        horizontal_flip=True,     # قرینه‌سازی افقی\n        fill_mode='nearest'       # پر کردن پیکسل‌های خالی\n    )\n\ndef create_train_gen(datagen):\n    return datagen.flow_from_dataframe(\n        training_df, \n        directory='/kaggle/tmp/',\n        x_col='filename', \n        y_col=['any', 'epidural', 'intraparenchymal', \n               'intraventricular', 'subarachnoid', 'subdural'],\n        class_mode='raw',\n        target_size=(224, 224),\n        batch_size=BATCH_SIZE,\n        shuffle=True   # مهم برای train\n    )\n\ndef create_val_gen(datagen): \n    return datagen.flow_from_dataframe(\n        validation_df, \n        directory='/kaggle/tmp/',\n        x_col='filename', \n        y_col=['any', 'epidural', 'intraparenchymal', \n               'intraventricular', 'subarachnoid', 'subdural'],\n        class_mode='raw',\n        target_size=(224, 224),\n        batch_size=BATCH_SIZE,\n        shuffle=False  # val نباید shuffle بشه\n    )\n\n# Using augmented generator\ndata_generator = create_datagen()\ntrain_gen = create_train_gen(data_generator)\nval_gen = create_val_gen(ImageDataGenerator(preprocessing_function=preprocess_input))  ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T08:17:08.148971Z","iopub.execute_input":"2025-10-08T08:17:08.14921Z","iopub.status.idle":"2025-10-08T08:17:08.522873Z","shell.execute_reply.started":"2025-10-08T08:17:08.149193Z","shell.execute_reply":"2025-10-08T08:17:08.522161Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------ Imports مورد نیاز ------------------\nfrom sklearn.utils import class_weight\nimport numpy as np\nimport tensorflow as tf\nfrom keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\n\n# ------------------ محاسبه class weights ------------------\nlabel_cols = ['any', 'epidural', 'intraparenchymal',\n              'intraventricular', 'subarachnoid', 'subdural']\n\nclass_weights = {}\nfor i, col in enumerate(label_cols):\n    weights = class_weight.compute_class_weight(\n        class_weight='balanced',\n        classes=np.array([0, 1]),\n        y=training_df[col].values\n    )\n    class_weights[i] = weights[1]  # وزن کلاس مثبت\n\nprint(\"Class Weights (per-class positive weight):\", class_weights)\n\n# تبدیل به Tensor float32 برای استفاده در loss\nweights = np.array(list(class_weights.values()), dtype=np.float32)\nweights_tf = tf.convert_to_tensor(weights, dtype=tf.float32)  # shape (6,)\n\n# ------------------ تعریف Weighted Binary Crossentropy (element-wise) ------------------\ndef weighted_binary_crossentropy(y_true, y_pred):\n    \"\"\"\n    محاسبه BCE عنصری (shape = (batch, num_classes)) و ضرب در weights\n    y_true, y_pred: shape (batch, 6)\n    weights_tf: shape (6,)\n    خروجی: عدد اسکالر (میانگین weighted loss)\n    \"\"\"\n    # از K.backend.binary_crossentropy استفاده می‌کنیم تا خروجی shape=(batch,6) باشه\n    bce_elementwise = tf.keras.backend.binary_crossentropy(y_true, y_pred)  # shape (batch,6)\n\n    # اطمینان از float32\n    bce_elementwise = tf.cast(bce_elementwise, tf.float32)\n\n    # ضرب در وزن هر کلاس (broadcast: (batch,6) * (6,) => (batch,6))\n    weighted_bce = bce_elementwise * weights_tf[tf.newaxis, :]\n\n    # میانگین روی batch و کلاس‌ها\n    return tf.reduce_mean(weighted_bce)\n\n# ------------------ Compile مدل ------------------\nLR = 0.00005\nmodel.compile(\n    optimizer=Adam(learning_rate=LR),\n    loss=weighted_binary_crossentropy,\n    metrics=[\n        'accuracy',\n        tf.keras.metrics.AUC(name=\"auc\"),\n        tf.keras.metrics.Precision(name=\"precision\"),\n        tf.keras.metrics.Recall(name=\"recall\")\n    ]\n)\n\n# ------------------ Callbacks ------------------\ncheckpoint_path = \"best_model.h5\"\n\ncheckpoint = ModelCheckpoint(checkpoint_path, monitor=\"val_loss\",\n                             save_best_only=True, verbose=1)\n\nearlystop = EarlyStopping(monitor=\"val_loss\",\n                          patience=5,\n                          restore_best_weights=True,\n                          verbose=1)\n\nreduce_lr = ReduceLROnPlateau(monitor='val_loss',\n                              factor=0.5,\n                              patience=5,\n                              min_lr=1e-7,\n                              verbose=1)\n\n# ------------------ Training ------------------\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=100,\n    steps_per_epoch=len(train_gen),\n    validation_steps=len(val_gen),\n    callbacks=[checkpoint, earlystop, reduce_lr]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T08:17:19.406621Z","iopub.execute_input":"2025-10-08T08:17:19.407258Z","iopub.status.idle":"2025-10-08T10:34:43.891026Z","shell.execute_reply.started":"2025-10-08T08:17:19.407233Z","shell.execute_reply":"2025-10-08T10:34:43.890382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(14,5))\n\n# Plot loss\nplt.subplot(1,2,1)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Val Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.title('Loss over Epochs')\nplt.legend()\n\n# Plot accuracy\nplt.subplot(1,2,2)\nplt.plot(history.history['accuracy'], label='Train Acc')\nplt.plot(history.history['val_accuracy'], label='Val Acc')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.title('Accuracy over Epochs')\nplt.legend()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T10:41:36.280351Z","iopub.execute_input":"2025-10-08T10:41:36.28062Z","iopub.status.idle":"2025-10-08T10:41:36.675837Z","shell.execute_reply.started":"2025-10-08T10:41:36.280593Z","shell.execute_reply":"2025-10-08T10:41:36.67495Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import precision_score, recall_score, roc_auc_score, f1_score\nimport pandas as pd\nimport numpy as np\n\nlabel_cols = ['any', 'epidural', 'intraparenchymal',\n              'intraventricular', 'subarachnoid', 'subdural']\n\n# پیش‌بینی مدل روی validation generator\ny_pred = model.predict(val_gen)\n\n# y_true واقعی از validation_df\ny_true = validation_df[label_cols].values\n\n# Threshold 0.5 برای تبدیل به binary\ny_pred_binary = (y_pred > 0.5).astype(int)\n\n# ایجاد یک لیست برای ذخیره نتایج\nresults_per_class = []\n\nfor i, col in enumerate(label_cols):\n    precision = precision_score(y_true[:, i], y_pred_binary[:, i])\n    recall = recall_score(y_true[:, i], y_pred_binary[:, i])\n    f1 = f1_score(y_true[:, i], y_pred_binary[:, i])\n    try:\n        auc = roc_auc_score(y_true[:, i], y_pred[:, i])\n    except ValueError:\n        auc = np.nan  # اگر فقط یک کلاس وجود داشته باشد (مثلاً همه 0) AUC محاسبه نمی‌شود\n    results_per_class.append([col, precision, recall, f1, auc])\n\n# نمایش به صورت جدول\ndf_metrics = pd.DataFrame(results_per_class, columns=['Class', 'Precision', 'Recall', 'F1', 'AUC'])\nprint(df_metrics)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T10:45:46.718589Z","iopub.execute_input":"2025-10-08T10:45:46.719362Z","iopub.status.idle":"2025-10-08T10:46:10.21626Z","shell.execute_reply.started":"2025-10-08T10:45:46.719326Z","shell.execute_reply":"2025-10-08T10:46:10.215417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from keras.models import load_model\n\nbest_model = load_model(\"best_model.h5\", custom_objects={\"weighted_binary_crossentropy\": weighted_binary_crossentropy})\n\nresults = best_model.evaluate(val_gen)\nprint(dict(zip(best_model.metrics_names, results)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T10:57:53.701024Z","iopub.execute_input":"2025-10-08T10:57:53.701815Z","iopub.status.idle":"2025-10-08T10:58:27.942531Z","shell.execute_reply.started":"2025-10-08T10:57:53.701785Z","shell.execute_reply":"2025-10-08T10:58:27.941921Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import precision_recall_curve, roc_auc_score, f1_score\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\n\nlabel_cols = ['any', 'epidural', 'intraparenchymal',\n              'intraventricular', 'subarachnoid', 'subdural']\n\n# پیش‌بینی مدل روی validation generator\ny_pred = model.predict(val_gen)\ny_true = validation_df[label_cols].values\n\nbest_thresholds = {}\n\nplt.figure(figsize=(15,10))\n\nfor i, col in enumerate(label_cols):\n    precisions, recalls, thresholds = precision_recall_curve(y_true[:, i], y_pred[:, i])\n    \n    # محاسبه F1 برای هر threshold\n    f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-8)\n    \n    # پیدا کردن Threshold بهینه (F1-max)\n    best_idx = np.argmax(f1_scores)\n    best_thresh = thresholds[best_idx] if best_idx < len(thresholds) else 0.5\n    best_thresholds[col] = best_thresh\n    \n    print(f\"{col}: Best threshold = {best_thresh:.3f}, Max F1 = {f1_scores[best_idx]:.3f}\")\n    \n    # رسم PR curve\n    plt.plot(recalls, precisions, label=f'{col} (best_thresh={best_thresh:.2f})')\n\nplt.xlabel('Recall')\nplt.ylabel('Precision')\nplt.title('Precision-Recall Curve for Each Class')\nplt.legend()\nplt.grid(True)\nplt.show()\n\n# نمایش جدول thresholds\ndf_thresholds = pd.DataFrame(list(best_thresholds.items()), columns=['Class', 'Optimal Threshold'])\nprint(df_thresholds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T10:59:58.864049Z","iopub.execute_input":"2025-10-08T10:59:58.864345Z","iopub.status.idle":"2025-10-08T11:00:22.699281Z","shell.execute_reply.started":"2025-10-08T10:59:58.864323Z","shell.execute_reply":"2025-10-08T11:00:22.698505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ایجاد y_pred_binary با Thresholdهای بهینه\ny_pred_binary_opt = np.zeros_like(y_pred, dtype=int)\n\nfor i, col in enumerate(label_cols):\n    y_pred_binary_opt[:, i] = (y_pred[:, i] > best_thresholds[col]).astype(int)\n\nfrom sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score\n\nresults_per_class_opt = []\n\nfor i, col in enumerate(label_cols):\n    precision = precision_score(y_true[:, i], y_pred_binary_opt[:, i])\n    recall = recall_score(y_true[:, i], y_pred_binary_opt[:, i])\n    f1 = f1_score(y_true[:, i], y_pred_binary_opt[:, i])\n    auc = roc_auc_score(y_true[:, i], y_pred[:, i])\n    results_per_class_opt.append([col, precision, recall, f1, auc])\n\ndf_metrics_opt = pd.DataFrame(results_per_class_opt, \n                              columns=['Class', 'Precision', 'Recall', 'F1', 'AUC'])\nprint(df_metrics_opt)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-08T11:00:22.700475Z","iopub.execute_input":"2025-10-08T11:00:22.701144Z","iopub.status.idle":"2025-10-08T11:00:22.807501Z","shell.execute_reply.started":"2025-10-08T11:00:22.701108Z","shell.execute_reply":"2025-10-08T11:00:22.806829Z"}},"outputs":[],"execution_count":null}]}