{ "cells": [ { "cell_type": "code", "execution_count": 10, "id": "7d40bbdc", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "from numpy.linalg import norm\n", "from huggingface_hub import InferenceClient\n", "from sentence_transformers import SentenceTransformer\n", "import os\n", "from pypdf import PdfReader\n", "import re\n", "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", "from dotenv import load_dotenv\n", "from pathlib import Path\n", "\n", "dotenv_path = Path.cwd() / \".env\"\n", "load_dotenv(dotenv_path=dotenv_path, override=True)" ] }, { "cell_type": "code", "execution_count": 11, "id": "c2bfdd18", "metadata": {}, "outputs": [], "source": [ "HF_KEY = os.getenv(\"HF_TOKEN\")\n", "GITHUB_KEY = os.getenv(\"GITHUB_TOKEN\")" ] }, { "cell_type": "code", "execution_count": 4, "id": "a926808b", "metadata": {}, "outputs": [], "source": [ "# ── 1. READ PDF ───────────────────────────────────────────────────────────\n", "def read_pdf(path: str) -> tuple[str, int]:\n", " reader = PdfReader(path)\n", " full_text = \"\"\n", " for page in reader.pages:\n", " full_text += page.extract_text() + \"\\n\\n\" # double newline between pages\n", " return full_text, len(reader.pages)" ] }, { "cell_type": "code", "execution_count": 5, "id": "a62bd125", "metadata": {}, "outputs": [], "source": [ "# ── 2. CLEAN ──────────────────────────────────────────────────────────────\n", "def clean(text: str) -> str:\n", " text = re.sub(r'#{1,6}\\s*', '', text) # remove ## headings\n", " text = re.sub(r'\\*{1,2}(.*?)\\*{1,2}', r'\\1', text) # remove **bold**\n", " text = re.sub(r'\\n{3,}', '\\n\\n', text) # collapse excess newlines\n", " text = re.sub(r'[ \\t]+', ' ', text) # collapse spaces\n", " return text.strip()" ] }, { "cell_type": "code", "execution_count": 14, "id": "d571b53d", "metadata": {}, "outputs": [], "source": [ "import time\n", "hf_client = InferenceClient(provider=\"hf-inference\", api_key=HF_KEY)\n", "\n", "def embed_texts(texts: list[str], model_name) -> list[list[float]]:\n", " embeddings = []\n", " for text in texts:\n", " vector = np.array(hf_client.feature_extraction(\n", " text,\n", " model=model_name,\n", " ))\n", " # Fix shape if (1, 768) instead of (768,)\n", " if vector.ndim > 1:\n", " vector = vector.squeeze()\n", " vector = vector / norm(vector)\n", " embeddings.append(vector.tolist())\n", " time.sleep(0.1)\n", " return embeddings\n", "\n", "from azure.ai.inference import EmbeddingsClient\n", "from azure.core.credentials import AzureKeyCredential\n", "\n", "openai_embeddings_client = EmbeddingsClient(\n", " endpoint=\"https://models.github.ai/inference\",\n", " credential=AzureKeyCredential(GITHUB_KEY)\n", ")\n", "\n", "def embed_openai_large(texts: list[str]) -> list[list[float]]:\n", " embeddings = []\n", " for text in texts:\n", " response = openai_embeddings_client.embed(\n", " input=[text],\n", " model=\"openai/text-embedding-3-large\"\n", " )\n", " vector = np.array(response.data[0].embedding)\n", " # Normalize (OpenAI recommends normalizing for cosine similarity)\n", " vector = vector / np.linalg.norm(vector)\n", " embeddings.append(vector.tolist())\n", " time.sleep(0.1)\n", " return embeddings\n" ] }, { "cell_type": "code", "execution_count": 15, "id": "fd0959bf", "metadata": {}, "outputs": [], "source": [ "# ── 3. CHUNK with LangChain ───────────────────────────────────────────────\n", "def split_into_chunks(text: str) -> list[str]:\n", " text_splitter = RecursiveCharacterTextSplitter(\n", " chunk_size=1500,\n", " chunk_overlap=250,\n", " length_function=len, # بنعرف المودل على اسم الفانكشن الي بتطلع الطول\n", " separators=[\"\\n\\n\", \"\\n\", \".\", \"،\", \" \", \"\"]\n", " )\n", " chunks = text_splitter.split_text(text)\n", " return chunks" ] }, { "cell_type": "code", "execution_count": 16, "id": "7fccb8b2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Total real chunks: 4\n", "\n", "Example chunk:\n", "نظرة عامة \n", "البرنامج يؤهل الملتحقين به للتعامل مع البيانات الكبيرة وتحليلها إحصائيا، واستنباط المعرفة منها، مما يساعد في \n", "اتخاذ القرارات بطريقة دقيقة وعلمية. \n", "ويذكر أن علم البيانات والذكاء الاصطناعي يُعدُّ التخصص الأكثر تطوراً في العالم، خلال السنوات الأخيرة، وأن \n", "معظم الدول المتقدمة، وفي ظل الثورة ا\n" ] } ], "source": [ "# Step 1 — extract real chunks from your PDF\n", "raw_text, _ = read_pdf(\"Prototype_UCAS_Documents.pdf\")\n", "cleaned = clean(raw_text)\n", "real_chunks = split_into_chunks(cleaned)\n", "\n", "print(f\"Total real chunks: {len(real_chunks)}\")\n", "print(f\"\\nExample chunk:\\n{real_chunks[0][:300]}\")" ] }, { "cell_type": "code", "execution_count": 17, "id": "954c6681", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Chunck: نظرة عامة \n", "البرنامج يؤهل الملتحقين به للتعامل مع البيانات الكبيرة وتحليلها إحصائيا، واستنباط المعرفة منها، مما يساعد في \n", "اتخاذ القرارات بطريقة دقيقة وعلمية. \n", "ويذكر أن علم البيانات والذكاء الاصطناعي يُعدُّ التخصص الأكثر تطوراً في العالم، خلال السنوات الأخيرة، وأن \n", "معظم الدول المتقدمة، وفي ظل الثورة ا\n", "Chunck: الشرق الأوسط كالأردن والامارات والسعودية والبحرين. إضافة الى ان استخدام علم البينات والذكاء الاصطناعي \n", "في ارتفاع مملوس في كل من الأعمال التجارية خصوصا والعالم بشكل عام. \n", " \n", "فرص العمل \n", "يستطيع مختص علم البيانات والذكاء الاصطناعي أن يمارس عمله منفرداً أو ضمن فريق عمل سواء في قطاع \n", "خاص أو قطاع عام، وغالب\n", "Chunck: زوارنا الكرام .. طلبتنا الأعزاء \n", "نستعرض معكم حزمة المنح الدراسية والتسهيلات المقدمة للطلبة الجدد للعام الجامعي 2022-2023 م \n", " \n", " \n", "منحة طلبة الثانوية العامة المتفوقين \n", "• منحة دراسية 100% لكافة الاختصاصات \n", "للطلبة الحاصلين علي معدل الثانوية 95% فأكثر \n", "تستمر المنحة طيلة الدراسة بمعدل فصلي 95 % \n", "• منحة درا\n", "Chunck: منحة75% لمستفيدي الشؤون الاجتماعية للعديد من الاختصاصات \n", "• تستمر المنحة طيلة الدراسة بمعدل فصلي 75 % \n", " \n", " \n", "منحة70% لحفظة القرآن الكريم أو أحد الصحيحين \n", "تستمر المنحة طيلة الدراسة بمعدل فصلي 70 % \n", " \n", " \n", "منحة50% للطلبة الرياضيين وأصحاب الانجازات الرياضية \n", " \n", " \n", "منحة متفوقي الامتحان الشامل \n", "• منحة 50% فأكثر \n" ] } ], "source": [ "for chunck in real_chunks:\n", " print(f\"Chunck: {chunck[:300]}\")" ] }, { "cell_type": "code", "execution_count": 18, "id": "0c92a183", "metadata": {}, "outputs": [], "source": [ "test_pairs = [\n", " (real_chunks[0], \"query: هل يمكنك إخباري بشكل أكبر عن تخصص علم البيانات والذكاء الاصطناعي؟\", 1),\n", " (real_chunks[1], \"query: ما هي متطلبات القبول في البرنامج؟\", 0),\n", " (real_chunks[2], \"query: هل يوجد منح دراسية للطلاب المتفوقين؟\", 1),\n", " (real_chunks[0], \"query: ما هو موعد امتحانات نهاية الفصل؟\", 0),\n", "]" ] }, { "cell_type": "code", "execution_count": 19, "id": "9a344999", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "✅ query: هل يمكنك إخباري بشكل أكبر عن تخصص علم البيا\n", " Matryoshka : 0.6727\n", "\n", " E5-large : 0.8790\n", "\n", " OpenAI-3L : 0.5927\n", "\n", " BGE-M3 : 0.6547\n", "\n", "❌ query: ما هي متطلبات القبول في البرنامج؟\n", " Matryoshka : 0.0778\n", "\n", " E5-large : 0.7800\n", "\n", " OpenAI-3L : 0.2293\n", "\n", " BGE-M3 : 0.3870\n", "\n", "✅ query: هل يوجد منح دراسية للطلاب المتفوقين؟\n", " Matryoshka : 0.5872\n", "\n", " E5-large : 0.8364\n", "\n", " OpenAI-3L : 0.5409\n", "\n", " BGE-M3 : 0.6824\n", "\n", "❌ query: ما هو موعد امتحانات نهاية الفصل؟\n", " Matryoshka : -0.1104\n", "\n", " E5-large : 0.7353\n", "\n", " OpenAI-3L : 0.1508\n", "\n", " BGE-M3 : 0.3602\n", "\n" ] } ], "source": [ "def cosine_similarity(v1, v2):\n", " v1, v2 = np.array(v1), np.array(v2)\n", " return float(np.dot(v1, v2) / (norm(v1) * norm(v2)))\n", "\n", "results = []\n", "\n", "for chunk, query, label in test_pairs:\n", " # Model 1\n", " sim_matryoshka = cosine_similarity(\n", " embed_texts([chunk], \"Omartificial-Intelligence-Space/Arabic-Triplet-Matryoshka-V2\")[0],\n", " embed_texts([query], \"Omartificial-Intelligence-Space/Arabic-Triplet-Matryoshka-V2\")[0]\n", " )\n", " # Model 2\n", " sim_e5 = cosine_similarity(\n", " embed_texts([f\"passage: {chunk}\"], \"intfloat/multilingual-e5-large\")[0],\n", " embed_texts([f\"query: {query}\"], \"intfloat/multilingual-e5-large\")[0]\n", " )\n", " # Model 3\n", " sim_openai = cosine_similarity(\n", " embed_openai_large([chunk])[0], # OpenAI doesn't use passage/query prefixes\n", " embed_openai_large([query])[0]\n", " )\n", " # Model 4\n", " sim_bge_m3 = cosine_similarity(\n", " embed_texts([chunk], \"BAAI/bge-m3\")[0],\n", " embed_texts([query], \"BAAI/bge-m3\")[0]\n", " )\n", "\n", " results.append({\n", " \"query\": query,\n", " \"label\": label,\n", " \"matryoshka\": round(sim_matryoshka, 4),\n", " \"e5_large\": round(sim_e5, 4),\n", " })\n", " print(f\"{'✅' if label == 1 else '❌'} {query[:50]}\")\n", " print(f\" Matryoshka : {sim_matryoshka:.4f}\\n\")\n", " print(f\" E5-large : {sim_e5:.4f}\\n\")\n", " print(f\" OpenAI-3L : {sim_openai:.4f}\\n\")\n", " print(f\" BGE-M3 : {sim_bge_m3:.4f}\\n\")" ] }, { "cell_type": "code", "execution_count": null, "id": "16951616", "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "code", "execution_count": null, "id": "831e0654", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.11" } }, "nbformat": 4, "nbformat_minor": 5 }