{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":16880,"databundleVersionId":858837}],"dockerImageVersionId":29845,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# --- 1. PURE TENSORFLOW & NATIVE LIBRARIES (ZERO INSTALLATIONS) ---\nimport os\nimport cv2 \nimport json\nimport numpy as np\nimport pandas as pd\nfrom tqdm.notebook import tqdm\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom scipy.io import wavfile\nfrom scipy import signal\n\n# TensorFlow & Keras\nimport tensorflow as tf\nfrom tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense, Dropout, LSTM, TimeDistributed, Concatenate\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, roc_auc_score, accuracy_score\n\nprint(f\"✅ TensorFlow Version: {tf.__version__}\")\n\n# إعداد الـ GPU لمنع انهيار الذاكرة\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    try:\n        for gpu in gpus:\n            tf.config.experimental.set_memory_growth(gpu, True)\n        print(f\"✅ GPU Available: {gpus[0].name}\")\n    except RuntimeError as e:\n        print(e)\nelse:\n    print(\"⚠️ Running on CPU\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 2. LIGHTNING FAST EXTRACTION (NATIVE OPENCV & SCIPY) ---\nDATA_DIR = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nMETADATA_PATH = os.path.join(DATA_DIR, 'metadata.json')\n\nFACES_DIR = './tf_dataset/faces/'\nAUDIO_DIR = './tf_dataset/audio/'\nos.makedirs(FACES_DIR, exist_ok=True)\nos.makedirs(AUDIO_DIR, exist_ok=True)\n\nwith open(METADATA_PATH, 'r') as f:\n    metadata = json.load(f)\ndf = pd.DataFrame([{'video_id': k, 'label': 1 if v['label']=='FAKE' else 0} for k, v in metadata.items()])\ndf = df[df['video_id'].isin(os.listdir(DATA_DIR))].reset_index(drop=True)\n\nSEQ_LENGTH = 30 # 30 فريم للفيديو\nIMG_SIZE = 128  \n\n# استخدام كاشف الوجوه المدمج أساساً في OpenCV (سريع جداً وموجود مسبقاً)\nface_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')\n\ndef extract_features(video_id):\n    video_path = os.path.join(DATA_DIR, video_id)\n    vid_name = video_id.split('.')[0]\n    \n    # 1. استخراج الصوت بالطريقة الأصلية (Scipy)\n    audio_path = os.path.join(AUDIO_DIR, f\"{vid_name}.npy\")\n    if not os.path.exists(audio_path):\n        temp_wav = f\"temp_{vid_name}.wav\"\n        # استخراج الصوت باستخدام ffmpeg المدمج في لينكس\n        os.system(f\"ffmpeg -i {video_path} -vn -acodec pcm_s16le -ar 16000 -ac 1 {temp_wav} -y -loglevel quiet\")\n        try:\n            sample_rate, samples = wavfile.read(temp_wav)\n            # استخراج الطيف الصوتي بدون librosa\n            frequencies, times, spectrogram = signal.spectrogram(samples, sample_rate)\n            spectrogram = np.log(spectrogram + 1e-10) # Log scale\n            \n            # توحيد الحجم (128x312)\n            spec_resized = cv2.resize(spectrogram, (312, 128))\n            np.save(audio_path, spec_resized)\n        except:\n            np.save(audio_path, np.zeros((128, 312)))\n        if os.path.exists(temp_wav): os.remove(temp_wav)\n\n    # 2. استخراج الوجوه (سريع جداً)\n    vid_faces_dir = os.path.join(FACES_DIR, vid_name)\n    if not os.path.exists(vid_faces_dir):\n        os.makedirs(vid_faces_dir)\n        cap = cv2.VideoCapture(video_path)\n        frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n        if frame_count > 0:\n            indices = np.linspace(0, frame_count - 1, SEQ_LENGTH, dtype=int)\n            for k, i in enumerate(indices):\n                cap.set(cv2.CAP_PROP_POS_FRAMES, i)\n                ret, frame = cap.read()\n                if not ret: continue\n                \n                gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)\n                faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))\n                \n                if len(faces) > 0:\n                    x, y, w, h = faces[0] # أخذ أول وجه\n                    face_crop = frame[y:y+h, x:x+w]\n                    face_resized = cv2.resize(face_crop, (IMG_SIZE, IMG_SIZE))\n                    cv2.imwrite(os.path.join(vid_faces_dir, f\"frame_{k:02d}.jpg\"), face_resized)\n        cap.release()\n\nprint(\"⏳ Extracting Features (Ultra-Fast Mode)...\")\nfor _, row in tqdm(df.iterrows(), total=len(df)):\n    extract_features(row['video_id'])\nprint(\"✅ Done!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 2. LIGHTNING FAST EXTRACTION (NATIVE OPENCV & SCIPY) ---\nDATA_DIR = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nMETADATA_PATH = os.path.join(DATA_DIR, 'metadata.json')\n\nFACES_DIR = './tf_dataset/faces/'\nAUDIO_DIR = './tf_dataset/audio/'\nos.makedirs(FACES_DIR, exist_ok=True)\nos.makedirs(AUDIO_DIR, exist_ok=True)\n\nwith open(METADATA_PATH, 'r') as f:\n    metadata = json.load(f)\ndf = pd.DataFrame([{'video_id': k, 'label': 1 if v['label']=='FAKE' else 0} for k, v in metadata.items()])\ndf = df[df['video_id'].isin(os.listdir(DATA_DIR))].reset_index(drop=True)\n\nSEQ_LENGTH = 30 # 30 فريم للفيديو\nIMG_SIZE = 128  \n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\n\nDATA_DIR = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nMETADATA_PATH = os.path.join(DATA_DIR, 'metadata.json')\nFACES_DIR = './tf_dataset/faces/'\n\n# 1. قراءة كل الفيديوهات الأصلية (400 فيديو)\nwith open(METADATA_PATH, 'r') as f:\n    metadata = json.load(f)\n\n# مجموعة تحتوي على أسماء كل الفيديوهات بصيغة mp4\nall_videos = set(metadata.keys()) \n\n# 2. قراءة الفيديوهات التي تم استخراج الوجوه منها بنجاح\n# نضيف لها .mp4 لكي تتطابق مع الأسماء الأصلية\nif os.path.exists(FACES_DIR):\n    extracted_videos = set([f\"{folder}.mp4\" for folder in os.listdir(FACES_DIR)])\nelse:\n    extracted_videos = set()\n\n# 3. إيجاد الفيديوهات المفقودة (عملية طرح بسيطة)\nmissing_videos = all_videos - extracted_videos\n\n# 4. طباعة النتائج\nprint(f\"📊 Total original videos: {len(all_videos)}\")\nprint(f\"✅ Successfully extracted: {len(extracted_videos)}\")\nprint(f\"❌ Missing videos: {len(missing_videos)}\\n\")\n\nprint(\"⚠️ قائمة الفيديوهات الـ 19 التي لم يتم اكتشاف وجوه فيها:\")\nprint(\"-\" * 50)\nfor idx, vid in enumerate(sorted(missing_videos), 1):\n    label = metadata[vid]['label']\n    print(f\"{idx}. {vid}  --->  (Label: {label})\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 4. VIDEO MODEL WITH ATTENTION POOLING ---\nfrom tensorflow.keras.layers import Multiply, Lambda\nimport tensorflow.keras.backend as K\n\ndef build_attention_video_model():\n    video_input = Input(shape=(SEQ_LENGTH, IMG_SIZE, IMG_SIZE, 3), name='video_input')\n    \n    # 1. Base CNN (MobileNetV2)\n    base_cnn = tf.keras.applications.MobileNetV2(\n        include_top=False, \n        weights='imagenet', \n        input_shape=(IMG_SIZE, IMG_SIZE, 3)\n    )\n    \n    # فك تجميد آخر 30 طبقة\n    base_cnn.trainable = True\n    for layer in base_cnn.layers[:-30]:\n        layer.trainable = False\n\n    # تمرير الفريمات واستخراج الميزات\n    x = TimeDistributed(base_cnn)(video_input)\n    x = TimeDistributed(GlobalAveragePooling2D())(x) # الآن شكل x هو (Batch, 10, 1280)\n    \n    # 🌟 2. طبقة الـ Attention Pooling (السر هنا) 🌟\n    # أ. حساب أهمية كل إطار (Frame)\n    attention_scores = Dense(128, activation='tanh')(x)\n    attention_scores = Dense(1, activation='linear')(attention_scores) \n    \n    # ب. تحويل الأهمية إلى نسب مئوية (Softmax) بحيث يكون مجموعها 100%\n    attention_weights = tf.keras.layers.Softmax(axis=1, name='attention_weights')(attention_scores)\n    \n    # ج. ضرب الميزات بنسب الأهمية (إعطاء التركيز للإطار المشوه)\n    weighted_features = Multiply()([x, attention_weights])\n    \n    # د. جمع الميزات الموزونة (بدلاً من المتوسط العادي)\n    x = tf.reduce_sum(weighted_features, axis=1)\n\n    # 3. Classifier\n    x = Dropout(0.5)(x) \n    x = Dense(64, activation='relu')(x)\n    x = BatchNormalization()(x)\n    x = Dropout(0.4)(x)\n    \n    output = Dense(1, activation='sigmoid', name='final_output')(x)\n\n    model = Model(inputs=video_input, outputs=output)\n    return model\n\nmodel = build_attention_video_model()\n\n# سرعة تعلم منخفضة لضمان استقرار أوزان الـ Attention\nmodel.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n              loss='binary_crossentropy',\n              metrics=['accuracy'])\n\nprint(\"✅ Attention Video Model Ready!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 3. ULTRA-FAST TF.DATA PIPELINE (FIXED FOR TF 2.1) ---\nAUTOTUNE = tf.data.experimental.AUTOTUNE \n\ndef process_path(file_path, label):\n    img = tf.io.read_file(file_path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.cast(img, tf.float32) # تحويل الأرقام إلى فواصل عشرية\n    img = tf.image.resize(img, [IMG_SIZE, IMG_SIZE])\n    \n    # 🔥 نموذج MobileNetV2 يتطلب أن تكون البيكسلات بين -1 و 1\n    img = (img / 127.5) - 1.0 \n    \n    return img, label\n\ndef configure_for_performance(ds, shuffle=False):\n    if shuffle:\n        ds = ds.shuffle(buffer_size=1000)\n    ds = ds.batch(BATCH_SIZE)\n    ds = ds.prefetch(buffer_size=AUTOTUNE) # تحميل الدفعة التالية بينما الـ GPU يتدرب\n    return ds\n\ntrain_ds = tf.data.Dataset.from_tensor_slices((train_df['image_path'].values, train_df['label'].values))\ntrain_ds = train_ds.map(process_path, num_parallel_calls=AUTOTUNE)\ntrain_ds = configure_for_performance(train_ds, shuffle=True)\n\nval_ds = tf.data.Dataset.from_tensor_slices((val_df['image_path'].values, val_df['label'].values))\nval_ds = val_ds.map(process_path, num_parallel_calls=AUTOTUNE)\nval_ds = configure_for_performance(val_ds)\n\n\n# --- 4. FAST MOBILENETV2 MODEL ---\ndef build_fast_model():\n    # 🔥 استخدام MobileNetV2 السريع والخفيف المتوفر في TF 2.1\n    base_model = tf.keras.applications.MobileNetV2(\n        include_top=False, \n        weights='imagenet', \n        input_shape=(IMG_SIZE, IMG_SIZE, 3)\n    )\n    \n    # فك تجميد آخر 30 طبقة لتتعلم ميزات التزييف\n    base_model.trainable = True\n    for layer in base_model.layers[:-30]:\n        layer.trainable = False\n\n    inputs = Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n    \n    # training=False مهم جداً لطبقات BatchNormalization\n    x = base_model(inputs, training=False)\n    \n    x = GlobalAveragePooling2D()(x)\n    x = BatchNormalization()(x)\n    x = Dropout(0.4)(x)\n    \n    x = Dense(128, activation='relu')(x)\n    x = Dropout(0.3)(x)\n    \n    outputs = Dense(1, activation='sigmoid')(x)\n    \n    model = Model(inputs, outputs)\n    return model\n\nmodel = build_fast_model()\n\n# سرعة تعلم مناسبة\nmodel.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n              loss='binary_crossentropy',\n              metrics=['accuracy', tf.keras.metrics.AUC(name='auc')])\n\nprint(\"✅ MobileNetV2 Model Ready!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nFACES_DIR = '/kaggle/working/tf_dataset/faces/' # أو './tf_dataset/faces/' حسب النسخة التي تعمل عليها\nAUDIO_DIR = '/kaggle/working/tf_dataset/audio/' # أو './tf_dataset/audio/'\n\nfaces_count = len(os.listdir(FACES_DIR))\naudio_count = len(os.listdir(AUDIO_DIR))\n\nprint(f\"📁 عدد مجلدات الوجوه المستخرجة: {faces_count} فيديو\")\nprint(f\"🎵 عدد ملفات الصوت المستخرجة: {audio_count} ملف\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 3. DATA DISTRIBUTION ANALYSIS ---\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# حساب عدد الفيديوهات في كل فئة\ntotal_counts = df['label'].value_counts()\nreal_count = total_counts.get(0, 0)\nfake_count = total_counts.get(1, 0)\n\nprint(\"📊 إحصائيات مجموعة البيانات (Dataset Statistics):\")\nprint(f\"🔸 إجمالي الفيديوهات المعالجة: {len(df)}\")\nprint(f\"🟢 الفيديوهات الحقيقية (REAL - 0): {real_count}\")\nprint(f\"🔴 الفيديوهات المزيفة (FAKE - 1): {fake_count}\")\n\n# حساب النسب المئوية\nreal_ratio = (real_count / len(df)) * 100\nfake_ratio = (fake_count / len(df)) * 100\nprint(f\"⚖️ نسبة الحقيقي: {real_ratio:.1f}% | نسبة المزيف: {fake_ratio:.1f}%\")\n\n# رسم مخطط بياني احترافي للتقرير\nplt.figure(figsize=(8, 5))\n# استخدمنا ألوان تعبر عن الحالة (أحمر للمزيف، أخضر للحقيقي)\nax = sns.countplot(data=df, x=df['label'].map({0: 'REAL', 1: 'FAKE'}), palette=['#e74c3c', '#2ecc71'])\n\n# إضافة الأرقام فوق كل عمود في الرسم\nfor p in ax.patches:\n    ax.annotate(f'{int(p.get_height())}', \n                (p.get_x() + p.get_width() / 2., p.get_height()),\n                ha='center', va='baseline', fontsize=12, fontweight='bold',\n                color='black', xytext=(0, 5), textcoords='offset points')\n\nplt.title('Data Distribution: REAL vs FAKE', fontsize=14, fontweight='bold')\nplt.xlabel('Category', fontsize=12)\nplt.ylabel('Number of Videos', fontsize=12)\nplt.ylim(0, max(real_count, fake_count) + 50) # رفع السقف قليلاً ليظهر الرقم بوضوح\nplt.grid(axis='y', linestyle='--', alpha=0.7)\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.layers import Input, Dense, Dropout, GlobalAveragePooling2D, GlobalAveragePooling1D, GlobalMaxPooling1D, TimeDistributed, Concatenate, Lambda, GaussianNoise\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.applications import Xception \nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping\nfrom tensorflow.keras import regularizers\n\ndef build_finetuned_video_model(seq_length=30, img_size=128):\n    video_input = Input(shape=(seq_length, img_size, img_size, 3))\n    \n    # 1. تهيئة الألوان وإضافة ضوضاء قوية لمنع الحفظ (Overfitting)\n    x = Lambda(lambda img: (img / 127.5) - 1.0)(video_input)\n    x = TimeDistributed(GaussianNoise(0.05))(x) \n    \n    # 2. استدعاء Xception\n    base_cnn = Xception(weights='imagenet', include_top=False, input_shape=(img_size, img_size, 3))\n    \n    # 🚨 السر الأول (Fine-tuning): تجميد معظم النموذج وفك آخر 15 طبقة فقط!\n    base_cnn.trainable = True\n    for layer in base_cnn.layers[:-15]: \n        layer.trainable = False\n        \n    encoded_frames = TimeDistributed(base_cnn)(x)\n    encoded_frames = TimeDistributed(GlobalAveragePooling2D())(encoded_frames)\n    \n    # 🚨 السر الثاني (الاستغناء عن GRU المعقد):\n    # التزييف المستمر نلتقطه بالمتوسط، والخطأ المفاجئ في فريم واحد نلتقطه بالـ Max\n    avg_pool = GlobalAveragePooling1D()(encoded_frames)\n    max_pool = GlobalMaxPooling1D()(encoded_frames)\n    temporal_features = Concatenate()([avg_pool, max_pool])\n    \n    # 🚨 السر الثالث (L2 Regularization): قيود رياضية تمنع النموذج من حفظ الـ 320 فيديو\n    x = Dense(128, activation='relu', kernel_regularizer=regularizers.l2(0.01))(temporal_features)\n    x = Dropout(0.5)(x)\n    x = Dense(64, activation='relu', kernel_regularizer=regularizers.l2(0.01))(x)\n    x = Dropout(0.5)(x)\n    \n    output = Dense(1, activation='sigmoid')(x)\n    \n    model = Model(inputs=video_input, outputs=output)\n    \n    # تقليل سرعة التعلم جداً لأننا نعدل أوزان حساسة (Fine-Tuning)\n    optimizer = tf.keras.optimizers.Adam(learning_rate=5e-5)\n    \n    model.compile(optimizer=optimizer, \n                  loss='binary_crossentropy', \n                  metrics=['accuracy', tf.keras.metrics.AUC(name='auc')])\n    return model\n\nmodel_finetuned = build_finetuned_video_model(seq_length=30, img_size=128)\nmodel_finetuned.summary()\n\n# ==========================================\n# 3. إعدادات التدريب الجديدة\n# ==========================================\ncallbacks = [\n    ModelCheckpoint(\"best_video_deepfake_finetuned.h5\", monitor=\"val_auc\", mode=\"max\", save_best_only=True, verbose=1),\n    ReduceLROnPlateau(monitor=\"val_auc\", mode=\"max\", factor=0.5, patience=2, min_lr=1e-6, verbose=1),\n    EarlyStopping(monitor=\"val_auc\", mode=\"max\", patience=7, restore_best_weights=True)\n]\n\nprint(\"🔥 Starting FINETUNED Video-Level Training...\")\nhistory = model_finetuned.fit(\n    x=X_video, \n    y=y_labels,\n    validation_split=0.2, \n    epochs=40,\n    batch_size=4, \n    class_weight=class_weights, \n    callbacks=callbacks\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 1. ADVANCED DATA GENERATOR & SETUP ---\nimport os\nimport cv2 \nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.utils.class_weight import compute_class_weight\n\nimport tensorflow as tf\nfrom tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense, Dropout, LSTM, TimeDistributed, Concatenate, BatchNormalization, Layer\nfrom tensorflow.keras.models import Model\nimport tensorflow.keras.backend as K\n\nprint(f\"✅ TensorFlow Version: {tf.__version__}\")\n\n# جلب البيانات\nDATA_DIR = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nimport json\nwith open(os.path.join(DATA_DIR, 'metadata.json'), 'r') as f:\n    metadata = json.load(f)\ndf = pd.DataFrame([{'video_id': k, 'label': 1 if v['label']=='FAKE' else 0} for k, v in metadata.items()])\ndf = df[df['video_id'].isin(os.listdir(DATA_DIR))].reset_index(drop=True)\n\n# تقسيم البيانات\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['label'])\n\n# حساب أوزان الفئات (Class Weights) لحل مشكلة عدم التوازن\nweights = compute_class_weight('balanced', classes=np.unique(train_df['label']), y=train_df['label'])\nclass_weights = dict(enumerate(weights))\nprint(f\"⚖️ Class Weights: {class_weights}\")\n\n# --- Keras Sequence Generator متطور (Thread-Safe & Memory Efficient) ---\nclass AdvancedDeepfakeGenerator(tf.keras.utils.Sequence):\n    def __init__(self, dataframe, batch_size=4, seq_length=30, img_size=128, shuffle=True):\n        self.df = dataframe.reset_index(drop=True)\n        self.batch_size = batch_size\n        self.seq_length = seq_length\n        self.img_size = img_size\n        self.shuffle = shuffle\n        self.indices = np.arange(len(self.df))\n        if self.shuffle: np.random.shuffle(self.indices)\n\n    def __len__(self):\n        return int(np.floor(len(self.df) / self.batch_size))\n\n    def on_epoch_end(self):\n        if self.shuffle: np.random.shuffle(self.indices)\n\n    def __getitem__(self, index):\n        batch_indices = self.indices[index * self.batch_size:(index + 1) * self.batch_size]\n        \n        X_video = np.zeros((self.batch_size, self.seq_length, self.img_size, self.img_size, 3), dtype=np.float32)\n        X_audio = np.zeros((self.batch_size, 128, 312, 1), dtype=np.float32)\n        y = np.zeros((self.batch_size,), dtype=np.float32)\n\n        for i, idx in enumerate(batch_indices):\n            row = self.df.iloc[idx]\n            vid_name = row['video_id'].split('.')[0]\n            \n            # 1. Video Sequence\n            vid_dir = os.path.join('./tf_dataset/faces/', vid_name)\n            if os.path.exists(vid_dir):\n                frames = sorted([f for f in os.listdir(vid_dir) if f.endswith('.jpg')])\n                for j, frame_name in enumerate(frames[:self.seq_length]):\n                    img = cv2.imread(os.path.join(vid_dir, frame_name))\n                    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) / 255.0\n                    X_video[i, j, :, :, :] = img\n            \n            # 2. Audio Spectrogram\n            audio_path = os.path.join('./tf_dataset/audio/', f\"{vid_name}.npy\")\n            if os.path.exists(audio_path):\n                audio_data = np.load(audio_path)\n                max_val = np.max(np.abs(audio_data))\n                X_audio[i, :, :, 0] = (audio_data / max_val) if max_val > 0 else audio_data\n\n            y[i] = row['label']\n\n        return [X_video, X_audio], y\n\nBATCH_SIZE = 4\ntrain_gen = AdvancedDeepfakeGenerator(train_df, batch_size=BATCH_SIZE, shuffle=True)\nval_gen = AdvancedDeepfakeGenerator(val_df, batch_size=BATCH_SIZE, shuffle=False)\nprint(\"✅ Generators Initialized Successfully!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- SIMPLIFIED MODEL WITH BUILT-IN ATTENTION ---\nimport tensorflow as tf\nfrom tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense, Dropout, LSTM, TimeDistributed, Concatenate, BatchNormalization, Attention, GlobalAveragePooling1D\nfrom tensorflow.keras.models import Model\n\ndef build_simple_attention_model():\n    # -----------------------------------------------------\n    # 🌟 الفرع الأول: الفيديو الزمني\n    # -----------------------------------------------------\n    video_input = Input(shape=(30, 128, 128, 3), name=\"video_input\")\n    \n    x = TimeDistributed(Conv2D(32, (3, 3), activation='relu', padding='same'))(video_input)\n    x = TimeDistributed(MaxPooling2D((2, 2)))(x)\n    \n    x = TimeDistributed(Conv2D(64, (3, 3), activation='relu', padding='same'))(x)\n    x = TimeDistributed(MaxPooling2D((2, 2)))(x)\n    \n    x = TimeDistributed(Conv2D(128, (3, 3), activation='relu', padding='same'))(x)\n    x = TimeDistributed(MaxPooling2D((2, 2)))(x)\n    \n    x = TimeDistributed(Flatten())(x)\n    \n    # الـ LSTM يخرج التتابع الكامل (30 خطوة زمنية)\n    lstm_out = LSTM(128, return_sequences=True, name=\"temporal_lstm\")(x)\n    \n    # 🌟 استخدام طبقة الانتباه الجاهزة من TensorFlow (Self-Attention)\n    # نمرر مخرجات الـ LSTM لنفسها (Query and Value) لتركز على الفريمات الأهم\n    attention_out = Attention(name=\"tf_builtin_attention\")([lstm_out, lstm_out])\n    \n    # تجميع الفريمات بعد الانتباه في متجه واحد\n    video_features = GlobalAveragePooling1D()(attention_out)\n\n    # -----------------------------------------------------\n    # 🎵 الفرع الثاني: تحليل الصوت\n    # -----------------------------------------------------\n    audio_input = Input(shape=(128, 312, 1), name=\"audio_input\")\n    \n    y = Conv2D(32, (3, 3), activation='relu', padding='same')(audio_input)\n    y = MaxPooling2D((2, 2))(y)\n    \n    y = Conv2D(64, (3, 3), activation='relu', padding='same')(y)\n    y = MaxPooling2D((2, 2))(y)\n    \n    y = Flatten()(y)\n    audio_features = Dense(128, activation='relu')(y)\n\n    # -----------------------------------------------------\n    # 🤝 الفرع الثالث: الدمج والقرار\n    # -----------------------------------------------------\n    merged = Concatenate(name=\"fusion_layer\")([video_features, audio_features])\n    \n    z = Dense(128, activation='relu')(merged)\n    z = Dropout(0.5)(z)\n    output = Dense(1, activation='sigmoid', name=\"prediction\")(z)\n\n    model = Model(inputs=[video_input, audio_input], outputs=output)\n    return model\n\n# إنشاء النموذج\nmodel = build_simple_attention_model()\n\n# تجميع النموذج\nmodel.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001), \n              loss='binary_crossentropy', \n              metrics=['accuracy'])\n\nprint(\"✅ Simplified Model with Built-in Attention Created!\")\nmodel.summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 4. TRAINING WITH CALLBACKS & CLASS WEIGHTS ---\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping\n\n# 1. حفظ أفضل موديل فقط\ncheckpoint = ModelCheckpoint(\n    'best_attention_multimodal.h5', \n    monitor='val_accuracy', \n    save_best_only=True, \n    mode='max', \n    verbose=1\n)\n\n# 2. تقليل سرعة التعلم إذا توقف الموديل عن التحسن\nreduce_lr = ReduceLROnPlateau(\n    monitor='val_loss', \n    factor=0.5, \n    patience=2, \n    verbose=1,\n    min_lr=1e-6\n)\n\n# 3. إيقاف التدريب مبكراً إذا بدأ النموذج في الحفظ الأعمى (Overfitting)\nearly_stop = EarlyStopping(\n    monitor='val_loss',\n    patience=4,\n    restore_best_weights=True,\n    verbose=1\n)\n\nprint(\"🚀 Starting Advanced Training Process...\")\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=15,\n    class_weight=class_weights, # تمرير أوزان الفئات هنا\n    callbacks=[checkpoint, reduce_lr, early_stop]\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 6. EVALUATION & CONFUSION MATRIX ---\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import confusion_matrix, roc_auc_score, classification_report\n\n# 1. رسم منحنيات التعلم\nfig, ax = plt.subplots(1, 2, figsize=(14, 5))\nax[0].plot(history.history['loss'], label='Train Loss', color='blue')\nax[0].plot(history.history['val_loss'], label='Val Loss', color='red')\nax[0].set_title('Loss Over Epochs')\nax[0].legend()\n\nax[1].plot(history.history['accuracy'], label='Train Acc', color='blue')\nax[1].plot(history.history['val_accuracy'], label='Val Acc', color='red')\nax[1].set_title('Accuracy Over Epochs')\nax[1].legend()\nplt.show()\n\n# 2. تحميل أفضل وزن وصل له النموذج\nmodel.load_weights('best_attention_multimodal.h5')\n\n# 3. اختبار النموذج على بيانات الاختبار (Validation Data)\nprint(\"🔍 Evaluating Model on Validation Data...\")\nall_preds, all_labels = [], []\nfor i in range(len(val_gen)):\n    (X_vid, X_aud), y_true = val_gen[i]\n    preds = model.predict([X_vid, X_aud], verbose=0)\n    all_preds.extend(preds.flatten())\n    all_labels.extend(y_true)\n\nbinary_preds = (np.array(all_preds) > 0.5).astype(int)\n\n# 4. طباعة التقرير الشامل (أهم شيء للتقرير)\nprint(\"\\n📑 Classification Report:\")\nprint(classification_report(all_labels, binary_preds, target_names=['REAL (0)', 'FAKE (1)']))\n\n# 5. رسم مصفوفة الارتباك (Confusion Matrix)\ncm = confusion_matrix(all_labels, binary_preds)\nplt.figure(figsize=(6,5))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=['Predicted REAL', 'Predicted FAKE'], \n            yticklabels=['Actual REAL', 'Actual FAKE'])\nplt.title('Confusion Matrix - Attention Multi-Modal')\nplt.show()\n\n# 6. حساب الـ ROC-AUC\ntry:\n    auc = roc_auc_score(all_labels, all_preds)\n    print(f\"🌟 Final ROC-AUC Score: {auc:.4f}\")\nexcept:\n    pass","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 2. EXTRACT TIME-SERIES FRAMES & AUDIO ---\nfrom mtcnn import MTCNN\nfrom PIL import Image\n\nDATA_DIR = '/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/'\nMETADATA_PATH = os.path.join(DATA_DIR, 'metadata.json')\n\nFACES_DIR = './tf_dataset/faces/'\nAUDIO_DIR = './tf_dataset/audio/'\nos.makedirs(FACES_DIR, exist_ok=True)\nos.makedirs(AUDIO_DIR, exist_ok=True)\n\nwith open(METADATA_PATH, 'r') as f:\n    metadata = json.load(f)\ndf = pd.DataFrame([{'video_id': k, 'label': 1 if v['label']=='FAKE' else 0} for k, v in metadata.items()])\ndf = df[df['video_id'].isin(os.listdir(DATA_DIR))].reset_index(drop=True)\n\nSEQ_LENGTH = 30 \nIMG_SIZE = 128  \n\n# تهيئة كاشف الوجوه الخاص بـ TensorFlow\ndetector = MTCNN()\n\ndef extract_features(video_id):\n    video_path = os.path.join(DATA_DIR, video_id)\n    vid_name = video_id.split('.')[0]\n    \n    # 1. استخراج الصوت (Audio Spectrogram)\n    audio_path = os.path.join(AUDIO_DIR, f\"{vid_name}.npy\")\n    if not os.path.exists(audio_path):\n        temp_wav = f\"temp_{vid_name}.wav\"\n        os.system(f\"ffmpeg -i {video_path} -q:a 0 -map a {temp_wav} -y -v quiet\")\n        try:\n            y, sr = librosa.load(temp_wav, sr=16000, duration=10.0)\n            mel = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=128)\n            mel_db = librosa.power_to_db(mel, ref=np.max)\n            if mel_db.shape[1] < 312:\n                mel_db = np.pad(mel_db, ((0,0), (0, 312 - mel_db.shape[1])))\n            else:\n                mel_db = mel_db[:, :312]\n            np.save(audio_path, mel_db)\n        except:\n            np.save(audio_path, np.zeros((128, 312)))\n        if os.path.exists(temp_wav): os.remove(temp_wav)\n\n    # 2. استخراج الفريمات (Video Sequence)\n    vid_faces_dir = os.path.join(FACES_DIR, vid_name)\n    if not os.path.exists(vid_faces_dir):\n        os.makedirs(vid_faces_dir)\n        cap = cv2.VideoCapture(video_path)\n        frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n        if frame_count > 0:\n            indices = np.linspace(0, frame_count - 1, SEQ_LENGTH, dtype=int)\n            for k, i in enumerate(indices):\n                cap.set(cv2.CAP_PROP_POS_FRAMES, i)\n                ret, frame = cap.read()\n                if not ret: continue\n                \n                # تحويل الألوان وتمريرها للمكتشف\n                img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n                faces = detector.detect_faces(img_rgb)\n                \n                if faces:\n                    # أخذ الوجه الأول (صاحب أعلى نسبة ثقة)\n                    x, y, w, h = faces[0]['box']\n                    \n                    # التأكد من أن الإحداثيات داخل الصورة\n                    x1, y1 = max(0, x), max(0, y)\n                    x2, y2 = min(img_rgb.shape[1], x1 + w), min(img_rgb.shape[0], y1 + h)\n                    \n                    if x2 > x1 and y2 > y1:\n                        face_crop = img_rgb[y1:y2, x1:x2]\n                        face_pil = Image.fromarray(face_crop).resize((IMG_SIZE, IMG_SIZE), Image.LANCZOS)\n                        face_pil.save(os.path.join(vid_faces_dir, f\"frame_{k:02d}.jpg\"))\n        cap.release()\n\nprint(\"⏳ Extracting Spatio-Temporal and Audio features...\")\nfor _, row in tqdm(df.iterrows(), total=len(df)):\n    extract_features(row['video_id'])\nprint(\"✅ Done!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install facenet-pytorch==2.5.2","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport torch\nimport json\nimport numpy as np\nfrom facenet_pytorch import MTCNN\nfrom PIL import Image\nfrom tqdm import tqdm # مكتبة شريط التقدم لمعرفة وقت الانتهاء\n\n# 1. إعداد كرت الشاشة والمكتشف\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(f\"Running on device: {device}\")\nmtcnn = MTCNN(margin=40, keep_all=False, select_largest=True, post_process=False, device=device)\n\n# 2. تحديد مسارات الملفات\nVIDEO_DIR = \"/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/\"\nMETADATA_PATH = os.path.join(VIDEO_DIR, \"metadata.json\")\n\n# مسار الحفظ في مساحة عمل كاغل\nOUTPUT_DIR = \"/kaggle/working/extracted_faces\"\nREAL_DIR = os.path.join(OUTPUT_DIR, \"REAL\")\nFAKE_DIR = os.path.join(OUTPUT_DIR, \"FAKE\")\n\n# إنشاء المجلدات إذا لم تكن موجودة\nos.makedirs(REAL_DIR, exist_ok=True)\nos.makedirs(FAKE_DIR, exist_ok=True)\n\n# 3. قراءة ملف التصنيفات (Labels)\nwith open(METADATA_PATH, 'r') as f:\n    metadata = json.load(f)\n\n# دالة الاستخراج (تم تحديثها لإخفاء التحذير الوردي)\ndef extract_faces_from_video(video_path, num_frames=15):\n    cap = cv2.VideoCapture(video_path)\n    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n    \n    if total_frames == 0:\n        return []\n\n    frame_indices = np.linspace(0, total_frames - 1, num_frames, dtype=int)\n    frames_list = []\n    \n    for idx in frame_indices:\n        cap.set(cv2.CAP_PROP_POS_FRAMES, idx)\n        ret, frame = cap.read()\n        if ret:\n            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n            frames_list.append(Image.fromarray(frame))\n            \n    cap.release()\n    \n    if len(frames_list) == 0:\n        return []\n\n    faces = mtcnn(frames_list)\n    valid_faces = []\n    for face in faces:\n        if face is not None:\n            import torch.nn.functional as F\n            # تم إضافة align_corners=False لإخفاء التحذير\n            face_resized = F.interpolate(face.unsqueeze(0), size=(224, 224), mode='bilinear', align_corners=False).squeeze(0)\n            face_np = face_resized.permute(1, 2, 0).byte().cpu().numpy()\n            valid_faces.append(face_np)\n            \n    return valid_faces\n\n# ================= 4. بدء المعالجة الشاملة =================\n# جلب كل الفيديوهات التي تنتهي بـ .mp4\nall_videos = [f for f in os.listdir(VIDEO_DIR) if f.endswith('.mp4')]\n\nprint(f\"تم العثور على {len(all_videos)} فيديو. جاري بدء استخراج الوجوه...\")\n\n# حلقة المرور على الفيديوهات مع شريط تقدم\nfor video_name in tqdm(all_videos, desc=\"Processing Videos\"):\n    video_path = os.path.join(VIDEO_DIR, video_name)\n    \n    # معرفة هل هو حقيقي أم مزيف من الـ json\n    try:\n        label = metadata[video_name]['label']\n    except KeyError:\n        continue # تخطي الفيديو إذا لم يكن له تصنيف\n        \n    # استخراج الوجوه\n    extracted_faces = extract_faces_from_video(video_path, num_frames=15)\n    \n    # تحديد مجلد الحفظ بناءً على النوع\n    save_folder = REAL_DIR if label == \"REAL\" else FAKE_DIR\n    \n    # حفظ كل وجه كصورة مستقلة\n    # أزلنا امتداد .mp4 من اسم الفيديو واستبدلناه بـ .jpg\n    base_name = video_name.replace(\".mp4\", \"\") \n    \n    for i, face_array in enumerate(extracted_faces):\n        img = Image.fromarray(face_array)\n        img_name = f\"{base_name}_frame_{i}.jpg\"\n        img_path = os.path.join(save_folder, img_name)\n        img.save(img_path)\n\nprint(\"\\n🎉 تمت العملية بنجاح! جميع الوجوه جاهزة الآن في مجلد /kaggle/working/extracted_faces\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install timm # تثبيت مكتبة النماذج الجاهزة\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nimport os\nimport glob\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nimport timm\nfrom tqdm import tqdm\n\n# ================= 1. الإعدادات الأساسية =================\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nBATCH_SIZE = 32\nEPOCHS = 3 # يمكنك زيادتها لاحقاً إلى 5 أو 10 للحصول على دقة أعلى\nLEARNING_RATE = 1e-4\n\n# جمع مسارات الصور التي استخرجناها\nREAL_DIR = \"/kaggle/working/extracted_faces/REAL/*.jpg\"\nFAKE_DIR = \"/kaggle/working/extracted_faces/FAKE/*.jpg\"\n\nreal_images = glob.glob(REAL_DIR)\nfake_images = glob.glob(FAKE_DIR)\n\nprint(f\"عدد الصور الحقيقية: {len(real_images)}\")\nprint(f\"عدد الصور المزيفة: {len(fake_images)}\")\n\n# إعطاء تسميات (0 للحقيقي، و 1 للمزيف)\nall_images = real_images + fake_images\nall_labels = [0] * len(real_images) + [1] * len(fake_images)\n\n# تقسيم البيانات (80% للتدريب، 20% للاختبار لمعرفة الدقة الحقيقية)\ntrain_imgs, val_imgs, train_labels, val_labels = train_test_split(\n    all_images, all_labels, test_size=0.2, random_state=42, stratify=all_labels\n)\n\n# ================= 2. تجهيز البيانات (Data Augmentation) =================\n# إضافة تأثيرات للتدريب لكي يصبح النموذج قوياً ولا يحفظ الصور فقط\ntrain_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(), # قلب الصورة أفقياً\n    transforms.ColorJitter(brightness=0.2, contrast=0.2), # تغيير الإضاءة\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # توحيد الألوان\n])\n\n# صور الاختبار لا نضيف لها تأثيرات، فقط ضبط الحجم\nval_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nclass DeepfakeDataset(Dataset):\n    def __init__(self, image_paths, labels, transform):\n        self.image_paths = image_paths\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        image = Image.open(img_path).convert(\"RGB\")\n        label = torch.tensor(self.labels[idx], dtype=torch.float32)\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        return image, label\n\n# إنشاء قنوات تحميل البيانات\ntrain_dataset = DeepfakeDataset(train_imgs, train_labels, train_transforms)\nval_dataset = DeepfakeDataset(val_imgs, val_labels, val_transforms)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\n# ================= 3. بناء النموذج (EfficientNet-B3) =================\nprint(\"\\nجاري تحميل نموذج EfficientNet-B3...\")\n# نستخدم نموذج مدرب مسبقاً على ملايين الصور (pretrained=True) لنستفيد من ذكائه\nmodel = timm.create_model('efficientnet_b3', pretrained=True, num_classes=1)\nmodel = model.to(DEVICE)\n\n# دالة حساب الخطأ (Loss) والمحسن (Optimizer)\ncriterion = nn.BCEWithLogitsLoss() # ممتازة لمشاكل التصنيف الثنائي (مزيف/حقيقي)\noptimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\n# ================= 4. حلقة التدريب (Training Loop) =================\nprint(\"🚀 بدء التدريب...\\n\")\n\nfor epoch in range(EPOCHS):\n    model.train()\n    running_loss = 0.0\n    correct_train = 0\n    total_train = 0\n    \n    # تدريب النموذج على دفعات\n    loop = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Train]\")\n    for images, labels in loop:\n        images, labels = images.to(DEVICE), labels.to(DEVICE)\n        \n        optimizer.zero_grad()\n        outputs = model(images).squeeze(1) # النتيجة التي توقعها النموذج\n        \n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item()\n        \n        # حساب الدقة\n        predictions = (torch.sigmoid(outputs) >= 0.5).float()\n        correct_train += (predictions == labels).sum().item()\n        total_train += labels.size(0)\n        \n        loop.set_postfix(loss=loss.item())\n\n    train_acc = correct_train / total_train\n    \n    # ================= 5. تقييم النموذج على صور لم يرها (Validation) =================\n    model.eval()\n    correct_val = 0\n    total_val = 0\n    val_loss = 0.0\n    \n    with torch.no_grad(): # إيقاف التعلم هنا لأننا نختبره فقط\n        for images, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Valid]\"):\n            images, labels = images.to(DEVICE), labels.to(DEVICE)\n            outputs = model(images).squeeze(1)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item()\n            \n            predictions = (torch.sigmoid(outputs) >= 0.5).float()\n            correct_val += (predictions == labels).sum().item()\n            total_val += labels.size(0)\n            \n    val_acc = correct_val / total_val\n    \n    print(f\"📊 النتائج بعد Epoch {epoch+1}:\")\n    print(f\"Loss التدريب: {running_loss/len(train_loader):.4f} | دقة التدريب: {train_acc*100:.2f}%\")\n    print(f\"Loss الاختبار: {val_loss/len(val_loader):.4f} | دقة الاختبار: {val_acc*100:.2f}%\\n\")\n\n# حفظ النموذج لاستخدامه لاحقاً على الفيديوهات\ntorch.save(model.state_dict(), \"/kaggle/working/deepfake_efficientnet_model.pth\")\nprint(\"✅ تم حفظ النموذج بنجاح!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import cv2\nimport torch\nimport numpy as np\nimport timm\nfrom facenet_pytorch import MTCNN\nfrom PIL import Image\nfrom torchvision import transforms\n\n# 1. الإعدادات الأساسية\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n# 2. تجهيز مكتشف الوجوه (نفس الذي استخدمناه مسبقاً)\nmtcnn = MTCNN(margin=40, keep_all=False, select_largest=True, post_process=False, device=DEVICE)\n\n# 3. تحميل النموذج الذي قمنا بتدريبه للتو\nprint(\"جاري تحميل العقل الذكي...\")\nmodel = timm.create_model('efficientnet_b3', pretrained=False, num_classes=1)\n# نحمل الأوزان التي تدربت وحصلت على دقة 92%\nmodel.load_state_dict(torch.load(\"/kaggle/working/deepfake_efficientnet_model.pth\"))\nmodel = model.to(DEVICE)\nmodel.eval() # وضع الاختبار (مهم جداً)\n\n# 4. نفس تأثيرات الاختبار التي استخدمناها\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# 5. الدالة السحرية لفحص الفيديو بالكامل\ndef predict_video(video_path, num_frames=15):\n    # أ. استخراج الوجوه من الفيديو (نفس الكود السابق)\n    cap = cv2.VideoCapture(video_path)\n    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n    if total_frames == 0:\n        return None\n    \n    frame_indices = np.linspace(0, total_frames - 1, num_frames, dtype=int)\n    frames_list = []\n    \n    for idx in frame_indices:\n        cap.set(cv2.CAP_PROP_POS_FRAMES, idx)\n        ret, frame = cap.read()\n        if ret:\n            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n            frames_list.append(Image.fromarray(frame))\n    cap.release()\n    \n    if not frames_list:\n        return None\n\n    faces = mtcnn(frames_list)\n    \n    # ب. فحص الوجوه بالنموذج\n    face_probabilities = [] # سنخزن هنا نسبة التزييف لكل وجه\n    \n    with torch.no_grad():\n        for face in faces:\n            if face is not None:\n                import torch.nn.functional as F\n                face_resized = F.interpolate(face.unsqueeze(0), size=(224, 224), mode='bilinear', align_corners=False).squeeze(0)\n                face_np = face_resized.permute(1, 2, 0).byte().cpu().numpy()\n                \n                # تحويل الوجه ليناسب النموذج\n                face_pil = Image.fromarray(face_np)\n                face_tensor = transform(face_pil).unsqueeze(0).to(DEVICE)\n                \n                # التوقع (0 يعني حقيقي، 1 يعني مزيف)\n                output = model(face_tensor)\n                probability = torch.sigmoid(output).item()\n                face_probabilities.append(probability)\n                \n    if not face_probabilities:\n        return None\n        \n    # ج. حساب المتوسط لجميع الإطارات (النتيجة النهائية للفيديو)\n    video_fake_prob = sum(face_probabilities) / len(face_probabilities)\n    return video_fake_prob\n\n# ================= تجربة النظام =================\n# ضع هنا مسار أي فيديو من الداتاسيت (مثلا فيديو مزيف لترى كيف سيكتشفه)\nTEST_VIDEO_PATH = \"/kaggle/input/competitions/deepfake-detection-challenge/test_videos/aassnaulhq.mp4\" \n\nprint(f\"\\nجاري فحص الفيديو: {TEST_VIDEO_PATH.split('/')[-1]}\")\nfake_probability = predict_video(TEST_VIDEO_PATH)\n\nif fake_probability is not None:\n    print(\"-\" * 40)\n    print(f\"احتمالية أن الفيديو مزيف: {fake_probability * 100:.2f}%\")\n    \n    if fake_probability >= 0.5:\n        print(\"🚨 النتيجة النهائية: الفيديـــو مزيـــف (FAKE) 🚨\")\n    else:\n        print(\"✅ النتيجة النهائية: الفيديـــو حقيقــي (REAL) ✅\")\n    print(\"-\" * 40)\nelse:\n    print(\"لم يتم اكتشاف وجوه في هذا الفيديو.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T10:31:23.259617Z","iopub.execute_input":"2026-05-27T10:31:23.260202Z","iopub.status.idle":"2026-05-27T10:31:33.017214Z","shell.execute_reply.started":"2026-05-27T10:31:23.259955Z","shell.execute_reply":"2026-05-27T10:31:33.016497Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\nimport os\n\n# مسار مجلد الفيديوهات وملف الإجابات الصحيحة\nVIDEO_DIR = \"/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/\"\nMETADATA_PATH = os.path.join(VIDEO_DIR, \"metadata.json\")\n\n# قراءة ملف الإجابات\nwith open(METADATA_PATH, 'r') as f:\n    metadata = json.load(f)\n\n# البحث عن فيديو حقيقي وفيديو مزيف من الملف للتحقق\nreal_video_name = None\nfake_video_name = None\n\nfor video_name, info in metadata.items():\n    if info['label'] == 'REAL' and real_video_name is None:\n        real_video_name = video_name\n    elif info['label'] == 'FAKE' and fake_video_name is None:\n        fake_video_name = video_name\n        \n    if real_video_name and fake_video_name:\n        break # وجدنا واحد من كل نوع، نوقف البحث\n\nprint(\"✅ تم جلب فيديوهات التحقق من ملف الإجابات الرسمية (metadata.json)\\n\")\n\n# ================= 1. فحص الفيديو الحقيقي =================\nprint(f\"🎬 جاري فحص الفيديو الحقيقي: {real_video_name}\")\nreal_path = os.path.join(VIDEO_DIR, real_video_name)\nprob_real = predict_video(real_path)\n\nif prob_real is not None:\n    prediction = \"FAKE 🚨\" if prob_real >= 0.5 else \"REAL ✅\"\n    print(f\"الإجابة الرسمية من Kaggle: REAL ✅\")\n    print(f\"توقع العقل الاصطناعي الخاص بك: {prediction} (نسبة التزييف: {prob_real*100:.2f}%)\")\n    if prediction == \"REAL ✅\":\n        print(\"🎯 النتيجة: تطابق مذهل! النموذج اكتشف أنه حقيقي.\")\nelse:\n    print(\"لم يتم العثور على وجوه.\")\n\nprint(\"\\n\" + \"=\"*50 + \"\\n\")\n\n# ================= 2. فحص الفيديو المزيف =================\nprint(f\"🎬 جاري فحص الفيديو المزيف: {fake_video_name}\")\nfake_path = os.path.join(VIDEO_DIR, fake_video_name)\nprob_fake = predict_video(fake_path)\n\nif prob_fake is not None:\n    prediction = \"FAKE 🚨\" if prob_fake >= 0.5 else \"REAL ✅\"\n    print(f\"الإجابة الرسمية من Kaggle: FAKE 🚨\")\n    print(f\"توقع العقل الاصطناعي الخاص بك: {prediction} (نسبة التزييف: {prob_fake*100:.2f}%)\")\n    if prediction == \"FAKE 🚨\":\n        print(\"🎯 النتيجة: تطابق مذهل! النموذج اكتشف التزييف بنجاح.\")\nelse:\n    print(\"لم يتم العثور على وجوه.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T10:35:24.275757Z","iopub.execute_input":"2026-05-27T10:35:24.276049Z","iopub.status.idle":"2026-05-27T10:35:43.459231Z","shell.execute_reply.started":"2026-05-27T10:35:24.276007Z","shell.execute_reply":"2026-05-27T10:35:43.458095Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# مسار مجلد فيديوهات الاختبار (التي لم يرها النموذج أبداً)\nTEST_DIR = \"/kaggle/input/competitions/deepfake-detection-challenge/test_videos/\"\n\n# جلب أسماء الفيديوهات في المجلد\ntest_videos = [f for f in os.listdir(TEST_DIR) if f.endswith('.mp4')]\n\nif len(test_videos) > 0:\n    # سنأخذ أول فيديو كمثال\n    blind_video = test_videos[0] \n    blind_path = os.path.join(TEST_DIR, blind_video)\n    \n    print(\"=\"*50)\n    print(f\"🕵️‍♂️ فحص فيديو مجهول تماماً (Test Set): {blind_video}\")\n    print(\"=\"*50)\n    \n    prob_blind = predict_video(blind_path)\n    \n    if prob_blind is not None:\n        prediction = \"FAKE 🚨 (مزيف)\" if prob_blind >= 0.5 else \"REAL ✅ (حقيقي)\"\n        print(f\"النتيجة النهائية للعقل الاصطناعي: {prediction}\")\n        print(f\"نسبة التزييف: {prob_blind*100:.2f}%\")\n        \n        # يمكنك مشاهدة الفيديو بعينك في Kaggle للتأكد من منطقية النتيجة\n        print(\"\\n(ملاحظة: بما أن Kaggle تخفي الإجابة الرسمية لهذا المجلد، يمكنك تحميل الفيديو ومشاهدته بعينك لتقييم ذكاء النموذج!)\")\n    else:\n        print(\"لم يتم العثور على وجوه في الفيديو.\")\nelse:\n    print(\"لا يوجد فيديوهات في مجلد test_videos.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T10:38:22.372989Z","iopub.execute_input":"2026-05-27T10:38:22.373268Z","iopub.status.idle":"2026-05-27T10:38:29.789035Z","shell.execute_reply.started":"2026-05-27T10:38:22.373229Z","shell.execute_reply":"2026-05-27T10:38:29.788055Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nimport glob\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nimport timm\nfrom tqdm import tqdm\n\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nBATCH_SIZE = 4  \nEPOCHS = 10 # ⬅️ زدنا عدد الدورات ليأخذ وقته في الفهم\nLEARNING_RATE = 1e-4\nFRAMES_PER_VIDEO = 15\n\nMETADATA_PATH = \"/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/metadata.json\"\nREAL_DIR = \"/kaggle/working/extracted_faces/REAL/\"\nFAKE_DIR = \"/kaggle/working/extracted_faces/FAKE/\"\n\nwith open(METADATA_PATH, 'r') as f:\n    metadata = json.load(f)\n\nvalid_videos = []\nlabels = []\n\nfor video_name, info in metadata.items():\n    base_name = video_name.replace('.mp4', '')\n    real_frames = glob.glob(f\"{REAL_DIR}{base_name}_frame_*.jpg\")\n    fake_frames = glob.glob(f\"{FAKE_DIR}{base_name}_frame_*.jpg\")\n    \n    if len(real_frames) == FRAMES_PER_VIDEO or len(fake_frames) == FRAMES_PER_VIDEO:\n        valid_videos.append(video_name)\n        labels.append(0 if info['label'] == 'REAL' else 1)\n\ntrain_vids, val_vids, train_labels, val_labels = train_test_split(\n    valid_videos, labels, test_size=0.2, random_state=42, stratify=labels\n)\n\n# ================= 1. تحديث التأثيرات (السحر هنا) =================\n# إضافة تأثيرات للتدريب لمنع الحفظ (Overfitting)\ntrain_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(p=0.5), # قلب الوجه\n    transforms.ColorJitter(brightness=0.15, contrast=0.15), # تغيير طفيف في الإضاءة\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\n# بيانات التحقق تبقى بدون تأثيرات ليكون الاختبار حقيقياً\nval_transforms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nclass VideoAttentionDataset(Dataset):\n    def __init__(self, video_names, labels, metadata, transform):\n        self.video_names = video_names\n        self.labels = labels\n        self.metadata = metadata\n        self.transform = transform # ⬅️ استخدام التأثيرات الجديدة\n\n    def __len__(self):\n        return len(self.video_names)\n\n    def __getitem__(self, idx):\n        video_name = self.video_names[idx]\n        label = torch.tensor(self.labels[idx], dtype=torch.float32)\n        base_name = video_name.replace('.mp4', '')\n        \n        folder = REAL_DIR if self.metadata[video_name]['label'] == 'REAL' else FAKE_DIR\n        \n        frames = []\n        for i in range(FRAMES_PER_VIDEO):\n            img_path = os.path.join(folder, f\"{base_name}_frame_{i}.jpg\")\n            image = Image.open(img_path).convert(\"RGB\")\n            frames.append(self.transform(image))\n            \n        video_tensor = torch.stack(frames) \n        return video_tensor, label\n\n# تمرير التأثيرات الصحيحة\ntrain_dataset = VideoAttentionDataset(train_vids, train_labels, metadata, train_transforms)\nval_dataset = VideoAttentionDataset(val_vids, val_labels, metadata, val_transforms)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n\n# ================= 2. نفس العقل الذكي =================\nclass DeepfakeAttentionModel(nn.Module):\n    def __init__(self, hidden_dim=256):\n        super(DeepfakeAttentionModel, self).__init__()\n        self.backbone = timm.create_model('efficientnet_b0', pretrained=True, num_classes=0)\n        feature_dim = self.backbone.num_features\n        self.lstm = nn.LSTM(feature_dim, hidden_dim, batch_first=True, bidirectional=True)\n        self.attention_layer = nn.Linear(hidden_dim * 2, 1)\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(hidden_dim * 2, 64),\n            nn.ReLU(),\n            nn.Dropout(0.5), # ⬅️ زيادة الـ Dropout قليلاً لتقوية النموذج\n            nn.Linear(64, 1)\n        )\n\n    def forward(self, x):\n        batch_size, seq_len, c, h, w = x.size()\n        x = x.view(batch_size * seq_len, c, h, w)\n        features = self.backbone(x)\n        features = features.view(batch_size, seq_len, -1)\n        \n        lstm_out, _ = self.lstm(features)\n        \n        attention_weights = self.attention_layer(lstm_out)\n        attention_weights = torch.softmax(attention_weights, dim=1)\n        context_vector = torch.sum(attention_weights * lstm_out, dim=1)\n        \n        out = self.classifier(context_vector)\n        return out.squeeze(1)\n\nmodel = DeepfakeAttentionModel().to(DEVICE)\n\n# ================= 3. إضافة الموازنة ومحفز التعلم =================\n# حساب وزن الأخطاء (لأن الفيديوهات المزيفة أكثر من الحقيقية)\n# هذا يجعل النموذج يهتم أكثر باكتشاف الفيديو الحقيقي بدلاً من توقع \"مزيف\" دائماً\npos_weight = torch.tensor([4.0]).to(DEVICE) \ncriterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n\noptimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\n# ⬅️ السحر هنا: محفز يقلل سرعة التعلم تدريجياً لزيادة الدقة\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n\nprint(\"🚀 بدء التدريب لكسر حاجز 90%...\\n\")\n\nfor epoch in range(EPOCHS):\n    model.train()\n    running_loss = 0.0\n    correct_train, total_train = 0, 0\n    \n    loop = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Train]\")\n    for videos, labels in loop:\n        videos, labels = videos.to(DEVICE), labels.to(DEVICE)\n        \n        optimizer.zero_grad()\n        outputs = model(videos)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item()\n        predictions = (torch.sigmoid(outputs) >= 0.5).float()\n        correct_train += (predictions == labels).sum().item()\n        total_train += labels.size(0)\n        \n        # عرض معدل التعلم الحالي\n        current_lr = optimizer.param_groups[0]['lr']\n        loop.set_postfix(loss=loss.item(), lr=f\"{current_lr:.6f}\")\n\n    # تحديث سرعة التعلم\n    scheduler.step()\n\n    model.eval()\n    val_loss = 0.0\n    correct_val, total_val = 0, 0\n    \n    with torch.no_grad():\n        for videos, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Valid]\"):\n            videos, labels = videos.to(DEVICE), labels.to(DEVICE)\n            outputs = model(videos)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item()\n            \n            predictions = (torch.sigmoid(outputs) >= 0.5).float()\n            correct_val += (predictions == labels).sum().item()\n            total_val += labels.size(0)\n            \n    val_acc = correct_val / total_val\n    print(f\"📊 نتائج Epoch {epoch+1}:\")\n    print(f\"دقة التدريب: {(correct_train/total_train)*100:.2f}% | دقة التحقق (النهائية): {val_acc*100:.2f}%\\n\")\n\n# حفظ هذا الموديل الاحترافي\ntorch.save(model.state_dict(), \"/kaggle/working/deepfake_attention_model_90plus.pth\")\nprint(\"✅ تم حفظ النموذج الفائق بنجاح!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-27T10:43:14.208865Z","iopub.execute_input":"2026-05-27T10:43:14.209177Z","iopub.status.idle":"2026-05-27T10:46:23.464874Z","shell.execute_reply.started":"2026-05-27T10:43:14.209132Z","shell.execute_reply":"2026-05-27T10:46:23.463855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install torchvision==0.11.2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-01T23:18:56.266995Z","iopub.execute_input":"2026-06-01T23:18:56.267266Z","execution_failed":"2026-06-01T23:48:03.087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q facenet-pytorch==2.5.2 timm\n\nimport os\nimport json\nimport glob\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport numpy as np\nfrom facenet_pytorch import MTCNN\nimport timm\nfrom sklearn.model_selection import train_test_split\nfrom tqdm.auto import tqdm\n\n# Filter warnings for cleaner output\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-02T11:30:18.928839Z","iopub.execute_input":"2026-06-02T11:30:18.929250Z","iopub.status.idle":"2026-06-02T11:31:35.284483Z","shell.execute_reply.started":"2026-06-02T11:30:18.929175Z","shell.execute_reply":"2026-06-02T11:31:35.283200Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Device configuration\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Active Device: {DEVICE}\")\n\n# Path configurations\nVIDEO_DIR = \"/kaggle/input/competitions/deepfake-detection-challenge/train_sample_videos/\"\nMETADATA_PATH = os.path.join(VIDEO_DIR, \"metadata.json\")\n\nOUTPUT_DIR = \"/kaggle/input/notebooks/alisakour/notebook8e19260cc0/extracted_faces\"\nREAL_DIR = os.path.join(OUTPUT_DIR, \"REAL\")\nFAKE_DIR = os.path.join(OUTPUT_DIR, \"FAKE\")\n\nos.makedirs(REAL_DIR, exist_ok=True)\nos.makedirs(FAKE_DIR, exist_ok=True)\n\n# Hyperparameters\nNUM_FRAMES = 15\nBATCH_SIZE = 4\nEPOCHS = 10\nLEARNING_RATE = 1e-4","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-02T11:32:25.776942Z","iopub.execute_input":"2026-06-02T11:32:25.777540Z","iopub.status.idle":"2026-06-02T11:32:25.854288Z","shell.execute_reply.started":"2026-06-02T11:32:25.777285Z","shell.execute_reply":"2026-06-02T11:32:25.852927Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_faces_from_video(video_path, mtcnn_model, num_frames):\n    cap = cv2.VideoCapture(video_path)\n    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n    \n    if total_frames == 0:\n        cap.release()\n        return []\n\n    frame_indices = np.linspace(0, total_frames - 1, num_frames, dtype=int)\n    frames_list = []\n    \n    for idx in frame_indices:\n        cap.set(cv2.CAP_PROP_POS_FRAMES, idx)\n        ret, frame = cap.read()\n        if ret:\n            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n            frames_list.append(Image.fromarray(frame))\n            \n    cap.release()\n    \n    if not frames_list:\n        return []\n\n    faces = mtcnn_model(frames_list)\n    valid_faces = []\n    \n    for face in faces:\n        if face is not None:\n            face_resized = F.interpolate(\n                face.unsqueeze(0), size=(224, 224), mode='bilinear', align_corners=False\n            ).squeeze(0)\n            face_np = face_resized.permute(1, 2, 0).byte().cpu().numpy()\n            valid_faces.append(face_np)\n            \n    return valid_faces\n\ndef process_and_save_videos():\n    mtcnn = MTCNN(margin=40, keep_all=False, select_largest=True, post_process=False, device=DEVICE)\n    \n    with open(METADATA_PATH, 'r') as f:\n        metadata = json.load(f)\n        \n    videos = [f for f in os.listdir(VIDEO_DIR) if f.endswith('.mp4')]\n    \n    for video_name in tqdm(videos, desc=\"Extracting Faces\"):\n        if video_name not in metadata:\n            continue\n            \n        label = metadata[video_name]['label']\n        video_path = os.path.join(VIDEO_DIR, video_name)\n        \n        extracted_faces = extract_faces_from_video(video_path, mtcnn, NUM_FRAMES)\n        \n        # Ensure structural integrity (skip if face was not found in all frames)\n        if len(extracted_faces) != NUM_FRAMES:\n            continue\n            \n        save_folder = REAL_DIR if label == \"REAL\" else FAKE_DIR\n        base_name = video_name.replace(\".mp4\", \"\")\n        \n        for i, face_arr in enumerate(extracted_faces):\n            img = Image.fromarray(face_arr)\n            img.save(os.path.join(save_folder, f\"{base_name}_frame_{i}.jpg\"))\n\n# Run extraction (Execute this only once)\nprocess_and_save_videos()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SpatialTemporalDataset(Dataset):\n    def __init__(self, video_names, labels, metadata, transform, frames_per_video):\n        self.video_names = video_names\n        self.labels = labels\n        self.metadata = metadata\n        self.transform = transform\n        self.frames_per_video = frames_per_video\n\n    def __len__(self):\n        return len(self.video_names)\n\n    def __getitem__(self, idx):\n        video_name = self.video_names[idx]\n        label = torch.tensor(self.labels[idx], dtype=torch.float32)\n        base_name = video_name.replace('.mp4', '')\n        \n        folder = REAL_DIR if self.metadata[video_name]['label'] == 'REAL' else FAKE_DIR\n        \n        frames = []\n        for i in range(self.frames_per_video):\n            img_path = os.path.join(folder, f\"{base_name}_frame_{i}.jpg\")\n            image = Image.open(img_path).convert(\"RGB\")\n            frames.append(self.transform(image))\n            \n        video_tensor = torch.stack(frames) \n        return video_tensor, label\n\ndef get_dataloaders():\n    with open(METADATA_PATH, 'r') as f:\n        metadata = json.load(f)\n        \n    valid_videos = []\n    labels = []\n    \n    for video_name, info in metadata.items():\n        base_name = video_name.replace('.mp4', '')\n        real_frames = glob.glob(os.path.join(REAL_DIR, f\"{base_name}_frame_*.jpg\"))\n        fake_frames = glob.glob(os.path.join(FAKE_DIR, f\"{base_name}_frame_*.jpg\"))\n        \n        if len(real_frames) == NUM_FRAMES or len(fake_frames) == NUM_FRAMES:\n            valid_videos.append(video_name)\n            labels.append(0 if info['label'] == 'REAL' else 1)\n            \n    train_vids, val_vids, train_labels, val_labels = train_test_split(\n        valid_videos, labels, test_size=0.2, random_state=42, stratify=labels\n    )\n    \n    train_transforms = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.RandomHorizontalFlip(p=0.5),\n        transforms.ColorJitter(brightness=0.15, contrast=0.15),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n    \n    val_transforms = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n    ])\n    \n    train_dataset = SpatialTemporalDataset(train_vids, train_labels, metadata, train_transforms, NUM_FRAMES)\n    val_dataset = SpatialTemporalDataset(val_vids, val_labels, metadata, val_transforms, NUM_FRAMES)\n    \n    train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\n    val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n    \n    return train_loader, val_loader\n\ntrain_loader, val_loader = get_dataloaders()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-01T23:24:08.145490Z","iopub.execute_input":"2026-06-01T23:24:08.145769Z","iopub.status.idle":"2026-06-01T23:24:11.601727Z","shell.execute_reply.started":"2026-06-01T23:24:08.145732Z","shell.execute_reply":"2026-06-01T23:24:11.600842Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport torch\n\ndef visualize_balanced_samples(dataloader, num_real=2, num_fake=2):\n    real_samples = []\n    fake_samples = []\n    \n    # Iterate through the dataloader until we collect the required samples\n    for videos, labels in dataloader:\n        for i in range(videos.size(0)):\n            vid = videos[i]\n            lbl = labels[i].item()\n            \n            if lbl == 0.0 and len(real_samples) < num_real:\n                real_samples.append((vid, lbl))\n            elif lbl == 1.0 and len(fake_samples) < num_fake:\n                fake_samples.append((vid, lbl))\n                \n            # Break early if we have collected enough samples for both classes\n            if len(real_samples) == num_real and len(fake_samples) == num_fake:\n                break\n        if len(real_samples) == num_real and len(fake_samples) == num_fake:\n            break\n            \n    # Combine collected samples\n    all_samples = real_samples + fake_samples\n    total_samples = len(all_samples)\n    \n    if total_samples == 0:\n        return\n        \n    fig, axes = plt.subplots(1, total_samples, figsize=(15, 5))\n    if total_samples == 1:\n        axes = [axes]\n        \n    for i, ax in enumerate(axes):\n        vid, lbl = all_samples[i]\n        \n        # Extract the middle frame from the sequence for visualization\n        mid_frame_idx = vid.size(0) // 2\n        img_tensor = vid[mid_frame_idx]\n        \n        # Denormalize the image tensor\n        img = img_tensor.numpy().transpose((1, 2, 0))\n        mean = np.array([0.485, 0.456, 0.406])\n        std = np.array([0.229, 0.224, 0.225])\n        img = std * img + mean\n        img = np.clip(img, 0, 1)\n        \n        # Set label\n        class_name = \"FAKE\" if lbl == 1.0 else \"REAL\"\n        \n        ax.imshow(img)\n        ax.set_title(f\"Label: {class_name}\", fontsize=12, fontweight='bold')\n        ax.axis('off')\n        \n    plt.tight_layout()\n    plt.show()\n\n# Execute balanced visualization\nvisualize_balanced_samples(train_loader, num_real=2, num_fake=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-01T23:33:50.972061Z","iopub.execute_input":"2026-06-01T23:33:50.972345Z","iopub.status.idle":"2026-06-01T23:33:52.711153Z","shell.execute_reply.started":"2026-06-01T23:33:50.972302Z","shell.execute_reply":"2026-06-01T23:33:52.710322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CustomCNN(nn.Module):\n    def __init__(self):\n        super(CustomCNN, self).__init__()\n        \n        self.features = nn.Sequential(\n            # Block 1: (3, 224, 224) -> (32, 112, 112)\n            nn.Conv2d(3, 32, kernel_size=3, padding=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            # Block 2: (32, 112, 112) -> (64, 56, 56)\n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.BatchNorm2d(64),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            # Block 3: (64, 56, 56) -> (128, 28, 28)\n            nn.Conv2d(64, 128, kernel_size=3, padding=1),\n            nn.BatchNorm2d(128),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            # Block 4: (128, 28, 28) -> (256, 14, 14)\n            nn.Conv2d(128, 256, kernel_size=3, padding=1),\n            nn.BatchNorm2d(256),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(kernel_size=2, stride=2),\n            \n            # Block 5: (256, 14, 14) -> (512, 7, 7) -> (512, 1, 1)\n            nn.Conv2d(256, 512, kernel_size=3, padding=1),\n            nn.BatchNorm2d(512),\n            nn.ReLU(inplace=True),\n            nn.AdaptiveAvgPool2d((1, 1))\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        return x.view(x.size(0), -1)\n\nclass CustomSpatialTemporalModel(nn.Module):\n    def __init__(self, hidden_dim=256, dropout_rate=0.5):\n        super(CustomSpatialTemporalModel, self).__init__()\n        \n        self.backbone = CustomCNN()\n        feature_dim = 512 \n        \n        self.lstm = nn.LSTM(feature_dim, hidden_dim, batch_first=True, bidirectional=True)\n        self.attention = nn.Linear(hidden_dim * 2, 1)\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(hidden_dim * 2, 64),\n            nn.ReLU(),\n            nn.Dropout(dropout_rate),\n            nn.Linear(64, 1)\n        )\n\n    def forward(self, x):\n        batch_size, seq_len, c, h, w = x.size()\n        \n        # Spatial Processing\n        x = x.view(batch_size * seq_len, c, h, w)\n        features = self.backbone(x)\n        \n        # Temporal Processing\n        features = features.view(batch_size, seq_len, -1)\n        lstm_out, _ = self.lstm(features)\n        \n        # Attention Calculation\n        attention_weights = self.attention(lstm_out)\n        attention_weights = torch.softmax(attention_weights, dim=1)\n        context_vector = torch.sum(attention_weights * lstm_out, dim=1)\n        \n        # Final Classification\n        logits = self.classifier(context_vector)\n        return logits.squeeze(1)\n\n# Initialize the model and move it to the configured device\nmodel = CustomSpatialTemporalModel().to(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-01T23:24:16.358221Z","iopub.execute_input":"2026-06-01T23:24:16.358590Z","iopub.status.idle":"2026-06-01T23:24:19.265312Z","shell.execute_reply.started":"2026-06-01T23:24:16.358519Z","shell.execute_reply":"2026-06-01T23:24:19.264416Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SpatialTemporalAttentionModel(nn.Module):\n    def __init__(self, hidden_dim=256, dropout_rate=0.5):\n        super(SpatialTemporalAttentionModel, self).__init__()\n        \n        self.backbone = timm.create_model('efficientnet_b0', pretrained=True, num_classes=0)\n        feature_dim = self.backbone.num_features\n        \n        self.lstm = nn.LSTM(feature_dim, hidden_dim, batch_first=True, bidirectional=True)\n        self.attention = nn.Linear(hidden_dim * 2, 1)\n        \n        self.classifier = nn.Sequential(\n            nn.Linear(hidden_dim * 2, 64),\n            nn.ReLU(),\n            nn.Dropout(dropout_rate),\n            nn.Linear(64, 1)\n        )\n\n    def forward(self, x):\n        batch_size, seq_len, c, h, w = x.size()\n        \n        # Spatial Processing\n        x = x.view(batch_size * seq_len, c, h, w)\n        features = self.backbone(x)\n        \n        # Temporal Processing\n        features = features.view(batch_size, seq_len, -1)\n        lstm_out, _ = self.lstm(features)\n        \n        # Attention Calculation\n        attention_weights = self.attention(lstm_out)\n        attention_weights = torch.softmax(attention_weights, dim=1)\n        context_vector = torch.sum(attention_weights * lstm_out, dim=1)\n        \n        # Final Classification\n        logits = self.classifier(context_vector)\n        return logits.squeeze(1)\n\nmodel = SpatialTemporalAttentionModel().to(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-01T23:07:38.508369Z","iopub.execute_input":"2026-06-01T23:07:38.508892Z","iopub.status.idle":"2026-06-01T23:07:38.994126Z","shell.execute_reply.started":"2026-06-01T23:07:38.508648Z","shell.execute_reply":"2026-06-01T23:07:38.993517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_and_evaluate(model, train_loader, val_loader, epochs, learning_rate):\n    pos_weight = torch.tensor([4.0]).to(DEVICE)\n    criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)\n    \n    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)\n    \n    history = {'train_loss': [], 'val_loss': [], 'train_acc': [], 'val_acc': []}\n    best_val_acc = 0.0\n    \n    for epoch in range(epochs):\n        # Training Phase\n        model.train()\n        running_loss = 0.0\n        correct_train, total_train = 0, 0\n        \n        train_loop = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{epochs} [Train]\")\n        for videos, labels in train_loop:\n            videos, labels = videos.to(DEVICE), labels.to(DEVICE)\n            \n            optimizer.zero_grad()\n            outputs = model(videos)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item()\n            predictions = (torch.sigmoid(outputs) >= 0.5).float()\n            correct_train += (predictions == labels).sum().item()\n            total_train += labels.size(0)\n            \n            train_loop.set_postfix(loss=f\"{loss.item():.4f}\")\n            \n        scheduler.step()\n        train_acc = correct_train / total_train\n        train_loss = running_loss / len(train_loader)\n        \n        # Validation Phase\n        model.eval()\n        val_loss = 0.0\n        correct_val, total_val = 0, 0\n        \n        val_loop = tqdm(val_loader, desc=f\"Epoch {epoch+1}/{epochs} [Valid]\")\n        with torch.no_grad():\n            for videos, labels in val_loop:\n                videos, labels = videos.to(DEVICE), labels.to(DEVICE)\n                outputs = model(videos)\n                loss = criterion(outputs, labels)\n                \n                val_loss += loss.item()\n                predictions = (torch.sigmoid(outputs) >= 0.5).float()\n                correct_val += (predictions == labels).sum().item()\n                total_val += labels.size(0)\n                \n        val_acc = correct_val / total_val\n        val_loss_avg = val_loss / len(val_loader)\n        \n        # Save metrics to history\n        history['train_loss'].append(train_loss)\n        history['val_loss'].append(val_loss_avg)\n        history['train_acc'].append(train_acc)\n        history['val_acc'].append(val_acc)\n        \n        print(f\"Epoch [{epoch+1}/{epochs}] | \"\n              f\"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | \"\n              f\"Val Loss: {val_loss_avg:.4f}, Val Acc: {val_acc:.4f}\")\n        \n        if val_acc > best_val_acc:\n            best_val_acc = val_acc\n            torch.save(model.state_dict(), \"/kaggle/working/best_custom_model.pth\")\n            \n    print(\"Training process finalized.\")\n    return history\n\n# Execute Training Pipeline and capture history\ntraining_history = train_and_evaluate(model, train_loader, val_loader, EPOCHS, LEARNING_RATE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-01T23:34:14.657643Z","iopub.execute_input":"2026-06-01T23:34:14.657916Z","iopub.status.idle":"2026-06-01T23:35:28.572667Z","shell.execute_reply.started":"2026-06-01T23:34:14.657877Z","shell.execute_reply":"2026-06-01T23:35:28.571388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_learning_curves(history):\n    epochs = range(1, len(history['train_loss']) + 1)\n    \n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 5))\n    \n    # Plot Loss Curve\n    ax1.plot(epochs, history['train_loss'], 'b-', label='Training Loss', marker='o')\n    ax1.plot(epochs, history['val_loss'], 'r-', label='Validation Loss', marker='o')\n    ax1.set_title('Training and Validation Loss', fontsize=14)\n    ax1.set_xlabel('Epochs', fontsize=12)\n    ax1.set_ylabel('Loss', fontsize=12)\n    ax1.grid(True, linestyle='--', alpha=0.7)\n    ax1.legend(loc='upper right')\n    \n    # Plot Accuracy Curve\n    ax2.plot(epochs, history['train_acc'], 'b-', label='Training Accuracy', marker='o')\n    ax2.plot(epochs, history['val_acc'], 'r-', label='Validation Accuracy', marker='o')\n    ax2.set_title('Training and Validation Accuracy', fontsize=14)\n    ax2.set_xlabel('Epochs', fontsize=12)\n    ax2.set_ylabel('Accuracy', fontsize=12)\n    ax2.grid(True, linestyle='--', alpha=0.7)\n    ax2.legend(loc='lower right')\n    \n    plt.tight_layout()\n    plt.show()\n\n# Render the plots\nplot_learning_curves(training_history)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-01T23:35:33.513330Z","iopub.execute_input":"2026-06-01T23:35:33.513694Z","iopub.status.idle":"2026-06-01T23:35:33.532673Z","shell.execute_reply.started":"2026-06-01T23:35:33.513621Z","shell.execute_reply":"2026-06-01T23:35:33.531714Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install timm -q\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-29T17:26:48.421483Z","iopub.execute_input":"2026-06-29T17:26:48.421798Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix, classification_report, roc_auc_score\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport numpy as np\nimport timm\n\n# Configuration parameters\nDEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'\nBATCH_SIZE = 32\nEPOCHS_CNN = 10\nEPOCHS_VIT = 5\nLEARNING_RATE = 1e-4\nIMG_SIZE = 224\n\nprint(\"Environment Setup Complete.\")\nprint(f\"Using Device: {DEVICE}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}}]}