{"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":107837,"databundleVersionId":13072151,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pathlib, os, pandas as pd\n\n# Kaggle tự mount dữ liệu của cuộc thi vào thư mục này\nDATA_DIR = pathlib.Path(\"/kaggle/input/financial-chat-bot\")\nassert DATA_DIR.exists(), \"Chưa thấy thư mục competition – hãy kiểm tra lại Add-on Data\"\n\n# Xem nhanh các file bên trong\nlist(DATA_DIR.iterdir())\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-17T09:55:41.943661Z","iopub.execute_input":"2025-07-17T09:55:41.943854Z","iopub.status.idle":"2025-07-17T09:55:41.950340Z","shell.execute_reply.started":"2025-07-17T09:55:41.943837Z","shell.execute_reply":"2025-07-17T09:55:41.949426Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 2: Locate the test_question file and load it\nimport pandas as pd\n\n# Tìm tệp có tên bắt đầu bằng \"test_question\"\ntest_files = list(DATA_DIR.rglob(\"test_question.*\"))\nassert len(test_files) > 0, \"Không tìm thấy file test_question.* – kiểm tra lại dataset\"\ntest_path = test_files[0]          # nếu có nhiều, lấy file đầu tiên\nprint(\"Đang đọc:\", test_path)\n\n# Đọc tùy theo phần mở rộng\nif test_path.suffix in {\".xlsx\", \".xls\"}:\n    df_test = pd.read_excel(test_path)\nelse:  # giả sử .csv\n    df_test = pd.read_csv(test_path)\n\nprint(f\"Số câu hỏi: {len(df_test)}\")\ndisplay(df_test.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T09:57:11.381574Z","iopub.execute_input":"2025-07-17T09:57:11.382190Z","iopub.status.idle":"2025-07-17T09:57:11.954686Z","shell.execute_reply.started":"2025-07-17T09:57:11.382161Z","shell.execute_reply":"2025-07-17T09:57:11.953987Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 3: quick sanity-check và chuẩn hoá tên cột\n\n# 1) Thông tin tổng quát\ndf_test.info()\n\n# 2) Kiểm tra giá trị thiếu\nprint(\"\\nSố ô NaN theo cột:\")\nprint(df_test.isna().sum())\n\n# 3) Đổi tên cột 'inputs' → 'Question' (để thống nhất về sau)\nif 'inputs' in df_test.columns:\n    df_test = df_test.rename(columns={'inputs': 'Question'})\n\n# 4) Kiểm tra trùng ID (không nên có)\nduplicates = df_test.duplicated(subset=['ID']).sum()\nprint(f\"\\nSố ID trùng lặp: {duplicates}\")\nassert duplicates == 0, \"Có ID bị trùng – cần xử lý!\"\n\n# Hiển thị 5 dòng đầu sau khi đổi tên\ndisplay(df_test.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T09:58:01.396855Z","iopub.execute_input":"2025-07-17T09:58:01.397225Z","iopub.status.idle":"2025-07-17T09:58:01.424260Z","shell.execute_reply.started":"2025-07-17T09:58:01.397204Z","shell.execute_reply":"2025-07-17T09:58:01.423451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 4: tạo cột `output` tạm thời và xem trước\n\ndef dummy_answer(question: str) -> str:\n    \"\"\"\n    Placeholder trả lời mặc định.\n    Sau này bạn sẽ thay bằng hàm RAG/LLM thật.\n    \"\"\"\n    return \"Xin chào! Mình sẽ cập nhật câu trả lời chi tiết sớm.\"\n\n# Gán câu trả lời cho mọi hàng\ndf_test[\"output\"] = df_test[\"Question\"].apply(dummy_answer)\n\n# Kiểm tra 5 dòng đầu\ndisplay(df_test[[\"ID\", \"Question\", \"output\"]].head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T09:58:43.348093Z","iopub.execute_input":"2025-07-17T09:58:43.348576Z","iopub.status.idle":"2025-07-17T09:58:43.360881Z","shell.execute_reply.started":"2025-07-17T09:58:43.348553Z","shell.execute_reply":"2025-07-17T09:58:43.360269Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 5: lưu submission.csv và xem trước\n\nimport pathlib\n\nWORK_DIR = pathlib.Path(\"/kaggle/working\")\nWORK_DIR.mkdir(exist_ok=True)\n\nsub_path = WORK_DIR / \"submission.csv\"\ndf_test[[\"ID\", \"output\"]].to_csv(sub_path, index=False)\n\nprint(f\"✅ Đã ghi submission tới: {sub_path}\")\n\n# Xem 5 dòng đầu để chắc chắn\nimport pandas as pd\ndisplay(pd.read_csv(sub_path).head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T09:59:19.724852Z","iopub.execute_input":"2025-07-17T09:59:19.725520Z","iopub.status.idle":"2025-07-17T09:59:19.746672Z","shell.execute_reply.started":"2025-07-17T09:59:19.725494Z","shell.execute_reply":"2025-07-17T09:59:19.746005Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Bước 1 – Khám phá & chuẩn hóa dữ liệu test****** done \n","metadata":{}},{"cell_type":"markdown","source":"\n**Bước 2 – Chuẩn bị & làm sạch dữ liệu huấn luyện******\n","metadata":{}},{"cell_type":"code","source":"# Cell 6 – liệt kê cấu trúc thư mục news\nfrom pathlib import Path\nfrom collections import Counter\n\nNEWS_DIR = DATA_DIR / \"news\"\nassert NEWS_DIR.exists(), \"Thư mục news không tồn tại – kiểm tra lại!\"\n\n# Đếm số file theo phần mở rộng\next_counter = Counter(p.suffix.lower() for p in NEWS_DIR.rglob(\"*\") if p.is_file())\nprint(\"📄 Số file theo loại:\", ext_counter)\n\n# Hiển thị 10 file đầu tiên để hình dung\nsample_files = [str(p) for p in NEWS_DIR.rglob(\"*\") if p.is_file()][:10]\nfor f in sample_files:\n    print(\"•\", f)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T10:02:58.072409Z","iopub.execute_input":"2025-07-17T10:02:58.072917Z","iopub.status.idle":"2025-07-17T10:02:58.085032Z","shell.execute_reply.started":"2025-07-17T10:02:58.072888Z","shell.execute_reply":"2025-07-17T10:02:58.084473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 7 – đọc news.csv và xem cấu trúc\nimport pandas as pd\n\nnews_csv_path = NEWS_DIR / \"news.csv\"\nassert news_csv_path.exists(), f\"Không tìm thấy {news_csv_path}\"\n\ndf_news_raw = pd.read_csv(news_csv_path, on_bad_lines=\"skip\")\nprint(f\"🔹 Số dòng đọc được: {len(df_news_raw)}\")\ndisplay(df_news_raw.head())\n\n# Kiểm tra danh sách cột để ta chuẩn hoá ở bước kế tiếp\nprint(\"\\nCác cột hiện có:\", list(df_news_raw.columns))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T10:04:31.966216Z","iopub.execute_input":"2025-07-17T10:04:31.966792Z","iopub.status.idle":"2025-07-17T10:04:44.612940Z","shell.execute_reply.started":"2025-07-17T10:04:31.966769Z","shell.execute_reply":"2025-07-17T10:04:44.612251Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 8 – chọn cột cần thiết, làm sạch nội dung, xoá bản ghi quá ngắn / trùng\nimport re, unicodedata\nimport pandas as pd\n\n# 1) Giữ các cột quan trọng & đổi tên cho ngắn gọn\ndf_news = df_news_raw.rename(columns={\n    \"content\": \"content\",\n    \"source\":  \"url\",\n    \"news_date\": \"date\"\n})[[\"content\", \"url\", \"date\"]].copy()\n\n# 2) Hàm làm sạch nội dung\ndef clean_text(txt: str) -> str:\n    if pd.isna(txt): return \"\"\n    txt = str(txt)\n    txt = re.sub(r\"<[^>]+>\", \" \", txt)               # bỏ HTML tags\n    txt = unicodedata.normalize(\"NFKC\", txt)         # chuẩn Unicode\n    txt = re.sub(r\"\\s+\", \" \", txt).strip()           # gộp khoảng trắng\n    return txt\n\ndf_news[\"content\"] = df_news[\"content\"].apply(clean_text)\n\n# 3) Loại bỏ bản ghi content quá ngắn (< 30 ký tự) và trùng lặp\ndf_news = df_news[df_news[\"content\"].str.len() > 30]\ndf_news = df_news.drop_duplicates(subset=[\"content\"])\n\nprint(\"✅ Sau làm sạch: \", len(df_news), \"bài viết\")\ndisplay(df_news.head(3))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T10:12:56.011521Z","iopub.execute_input":"2025-07-17T10:12:56.011842Z","iopub.status.idle":"2025-07-17T10:13:24.183349Z","shell.execute_reply.started":"2025-07-17T10:12:56.011820Z","shell.execute_reply":"2025-07-17T10:13:24.182629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 9 – chia mỗi bài viết thành các chunk ~300 từ\nfrom nltk.tokenize import word_tokenize\nimport nltk, math\nfrom tqdm.auto import tqdm\nimport pandas as pd\n\n# đảm bảo tokenizer đã sẵn sàng\nnltk.download('punkt', quiet=True)\n\nCHUNK_SIZE = 300       # số từ trong 1 chunk\nCHUNK_OVERLAP = 50     # chồng lấn 50 từ để không mất ngữ cảnh\nMIN_WORDS   = 50       # bỏ chunk quá ngắn\n\ndef chunk_content(text: str) -> list[str]:\n    words = word_tokenize(text)\n    chunks = []\n    step = CHUNK_SIZE - CHUNK_OVERLAP\n    for i in range(0, len(words), step):\n        chunk_words = words[i:i + CHUNK_SIZE]\n        if len(chunk_words) < MIN_WORDS:\n            continue\n        chunks.append(\" \".join(chunk_words))\n    return chunks\n\nrecords = []\nfor _, row in tqdm(df_news.iterrows(), total=len(df_news)):\n    for chunk in chunk_content(row[\"content\"]):\n        records.append({\n            \"chunk\": chunk,\n            \"url\":   row[\"url\"],\n            \"date\":  row[\"date\"]\n        })\n\ndf_chunks = pd.DataFrame(records)\nprint(f\"✅ Tổng số chunk: {len(df_chunks)}\")\ndisplay(df_chunks.head(3))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T10:14:58.946357Z","iopub.execute_input":"2025-07-17T10:14:58.947237Z","iopub.status.idle":"2025-07-17T10:19:21.524397Z","shell.execute_reply.started":"2025-07-17T10:14:58.947210Z","shell.execute_reply":"2025-07-17T10:19:21.523789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 10 – ghi ra file parquet để tái sử dụng nhanh ở bước tiếp\nimport pathlib\n\nWORK_DIR = pathlib.Path(\"/kaggle/working\")\nclean_path  = WORK_DIR / \"news_clean.parquet\"\nchunk_path  = WORK_DIR / \"news_chunks.parquet\"\n\ndf_news.to_parquet(clean_path,  index=False)\ndf_chunks.to_parquet(chunk_path, index=False)\n\nprint(\"✅ Đã lưu:\")\nprint(\" •\", clean_path)\nprint(\" •\", chunk_path)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T10:21:26.380422Z","iopub.execute_input":"2025-07-17T10:21:26.381302Z","iopub.status.idle":"2025-07-17T10:21:34.353553Z","shell.execute_reply.started":"2025-07-17T10:21:26.381278Z","shell.execute_reply":"2025-07-17T10:21:34.352786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 11 (sửa) – cài thư viện, import torch, nạp dữ liệu chunk và load model embedding\n!pip install -qU sentence-transformers faiss-cpu  # chạy một lần; nếu đã cài rồi sẽ bỏ qua nhanh\n\nimport pathlib, pandas as pd, numpy as np, torch\nfrom sentence_transformers import SentenceTransformer\n\nWORK_DIR   = pathlib.Path(\"/kaggle/working\")\nchunk_path = WORK_DIR / \"news_chunks.parquet\"\nassert chunk_path.exists(), \"Không tìm thấy news_chunks.parquet – hãy chắc chắn đã chạy Cell 10\"\n\ndf_chunks = pd.read_parquet(chunk_path)\nprint(\"🔹 Số chunk nạp:\", len(df_chunks))\n\nEMB_MODEL_NAME = \"sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2\"\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nembedder = SentenceTransformer(EMB_MODEL_NAME, device=device)\n\nprint(f\"✅ Model loaded on {device}: {EMB_MODEL_NAME}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T10:26:11.508030Z","iopub.execute_input":"2025-07-17T10:26:11.508349Z","iopub.status.idle":"2025-07-17T10:26:29.936535Z","shell.execute_reply.started":"2025-07-17T10:26:11.508322Z","shell.execute_reply":"2025-07-17T10:26:29.935581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 12 – encode tất cả chunk thành vector và lưu thành tệp .npy\nimport numpy as np\nfrom tqdm.auto import tqdm\n\nBATCH_SIZE = 512          # điều chỉnh nếu GPU RAM hạn hẹp\nEMB_DIM    = embedder.get_sentence_embedding_dimension()\n\nall_embeddings = np.empty((len(df_chunks), EMB_DIM), dtype=\"float32\")\n\nfor start in tqdm(range(0, len(df_chunks), BATCH_SIZE)):\n    end   = min(start + BATCH_SIZE, len(df_chunks))\n    batch = df_chunks[\"chunk\"].iloc[start:end].tolist()\n    all_embeddings[start:end] = embedder.encode(\n        batch,\n        batch_size=BATCH_SIZE,\n        show_progress_bar=False,\n        normalize_embeddings=True,     # cosine-sim dùng norm=1\n        convert_to_numpy=True\n    )\n\n# Lưu xuống WORK_DIR để tái sử dụng\nemb_path = WORK_DIR / \"news_emb.npy\"\nnp.save(emb_path, all_embeddings)\n\nprint(\"✅ Đã tính xong embedding:\")\nprint(\" • Shape:\", all_embeddings.shape)\nprint(\" • Lưu tại:\", emb_path)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-17T10:27:42.986048Z","iopub.execute_input":"2025-07-17T10:27:42.986348Z"}},"outputs":[],"execution_count":null}]}