This project implements a Retrieval-Augmented Generation (RAG) system for matching ESG (Environmental, Social, and Governance) report segments to SASB (Sustainability Accounting Standards Board) metrics. Given a page from an ESG report and a corresponding SASB standard, the system identifies relevant text regions and predicts their bounding boxes along with the matching SASB metric codes.
- Input: ESG report PDF page + SASB report (metric definitions)
- Output: Predicted bounding boxes and corresponding SASB metric codes
- Evaluation Metric: F1 Score (IoU threshold = 0.5)
- Goal: F1 > 0.2
IR/
├── main.py # 🚀 Main entry point (end-to-end pipeline)
├── config.py # ⚙️ Configuration management
├── data/
│ ├── train.json # Training data (151 samples)
│ ├── test.json # Test data (72 samples)
│ ├── train_solution.csv # Ground truth labels
│ ├── reports/ # ESG report PDFs (15 companies)
│ ├── sasb/ # SASB standard PDFs & markdown (7 industries)
│ └── processed/
│ ├── esg_segments_v2.jsonl # Extracted ESG segments (OCR)
│ ├── esg_segments_v4.jsonl # Extracted ESG segments (PyMuPDF)
│ ├── sasb_metrics.json # Parsed SASB metrics
│ └── sasb_metrics_translated.json
├── scripts/
│ ├── 01_build_esg_segments_v2.py # Extract ESG segments from PDFs
│ ├── 02_build_sasb_metrics.py # Parse SASB metrics from markdown
│ ├── 03_make_train_solution_csv.py # Generate solution CSV
│ ├── 04_rag_baseline_infer_v64_selective_merge.py # Standalone inference
│ ├── ensemble_union.py # 🎯 Ensemble: union multiple predictions
│ ├── score.py # Evaluation script
│ └── analyze_*.py # Analysis scripts
├── results/ # Inference results & submissions
│ ├── test_full_union.csv # ⭐ Best submission (Private=0.2000)
│ ├── test_v64_selective_merge_gemini3.csv # Gemini3 predictions
│ ├── test_deepseek_th060_llm8.csv # DeepSeek predictions
│ └── test_gemma3_th060_llm8.csv # Gemma3 predictions
├── FINAL_SUMMARY.md # 📊 Experiment summary
└── README.md
# Python 3.8+
pip install torch transformers sentence-transformers numpy pandas tqdm openai
# Optional: For Gemini API support
pip install google-generativeaiRequired Models:
BAAI/bge-m3- Bi-encoder for semantic retrievalBAAI/bge-reranker-v2-m3- Cross-encoder for reranking (optional but recommended)
LLM Options (choose one):
- Ollama (default): Remote/Local LLM server (e.g.,
minimax-m2:cloud,qwen3:8b) - Gemini API: Google's Gemini (
gemini-2.5-pro)
The easiest way to run the system is using main.py:
cd IR
# Run full pipeline (preprocess + inference + evaluate)
python main.py --mode train --full
# Run only inference on test set
python main.py --mode test
# Run only preprocessing
python main.py --preprocess
# Run only evaluation
python main.py --evaluate results/train_v64_selective_merge.csv
# Show current configuration
python main.py --show-config
# Override parameters
python main.py --mode train --score-threshold 0.55 --llm-threshold 8
# Use Gemini API instead of Ollama
export GEMINI_API_KEY="your-api-key-here"
python main.py --mode train --llm-provider geminiStep 1: Data Preprocessing
1.1 Extract ESG Segments
Extract text segments from ESG report PDFs:
cd IR
python scripts/01_build_esg_segments_v2.pyThis reads content_list.json files (from OCR tools like MinerU) and outputs data/processed/esg_segments_v2.jsonl.
1.2 Parse SASB Metrics
Parse SASB standard documents to extract metrics:
python scripts/02_build_sasb_metrics.pyThis reads SASB markdown files from data/sasb/ and outputs data/processed/sasb_metrics.json.
1.3 Generate Solution CSV (Optional)
python scripts/03_make_train_solution_csv.pyTraining Set:
python scripts/04_rag_baseline_infer_v64_selective_merge.py --mode trainTest Set:
python scripts/04_rag_baseline_infer_v64_selective_merge.py --mode testResults will be saved to results/train_v64_selective_merge.csv or results/test_v64_selective_merge.csv.
python scripts/score.py data/train_solution.csv results/train_v64_selective_merge.csvOutput example:
F1 Score: 0.1618
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 1. Segment │ │ 2. Bi-encoder │ │ 3. BM25 │
│ Extraction │ ──► │ Retrieval │ ──► │ Hybrid │
│ (OCR/PyMuPDF) │ │ (bge-m3) │ │ Search │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐ ┌──────────────────┐
│ 4. Cross-Encoder│ │ 5. LLM │
│ Reranking │ ──► │ Scoring │
│ (bge-reranker) │ │ (Top-3 only) │
└──────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 8. Output │ │ 7. BBox │ │ 6. Candidate │
│ Results │ ◄── │ Merging │ ◄── │ Selection │
│ (CSV format) │ │ (Selective) │ │ (LLM score ≥7) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Key Components:
- Segment Extraction: Extract text segments with bounding boxes from ESG PDFs
- Bi-encoder Retrieval: Use
bge-m3to compute semantic similarity - BM25 Hybrid Search: Combine lexical and semantic matching
- Cross-Encoder Reranking (NEW): Use
bge-reranker-v2-m3to filter candidates before LLM - LLM Scoring: Use LLM to score relevance (1-10) on Top-3 candidates only
- Selective BBox Merging: Intelligently merge adjacent segments
- Output: Generate predictions in CSV format
All parameters are centralized in config.py for easy tuning:
| Parameter | Value | Description |
|---|---|---|
score_threshold |
0.52 | Bi-encoder similarity threshold |
confidence_threshold |
7 | LLM score threshold (1-10) |
use_reranker |
True | Enable cross-encoder reranking |
reranker_top_k |
5 | Keep top-K candidates after reranking |
top_k_candidates_per_metric |
3 | Max segments per metric sent to LLM |
horizontal_spread_threshold |
200 | Pixels threshold for bbox merging |
max_merged_ratio |
0.50 | Maximum merged bbox size (% of page) |
bm25_boost_value |
0.03 | BM25 score boost |
Configuration Files:
config.py- All configurable parameters (paths, models, thresholds)- Modify
config.pydirectly or use command-line overrides withmain.py
| Version | Train F1 | Test Public | Test Private | Strategy |
|---|---|---|---|---|
| v22 | 0.1006 | - | - | Bi-encoder only |
| v35 | 0.1127 | - | - | + LLM reranking |
| v55 | 0.1387 | - | - | + BM25 hybrid |
| v64 | 0.1618 | 0.1904 | 0.1250 | + Selective merge (Gemini3) |
| Ensemble | - | 0.1923 | 0.2000 ⭐ | Multi-model union |
The best performing submission was achieved using multi-model ensemble:
# Generate predictions from multiple LLMs
python main.py --mode test --llm-provider gemini # Gemini3 Pro
python main.py --mode test --llm-model deepseek-r1:14b # DeepSeek R1
python main.py --mode test --llm-model gemma3:12b --llm-threshold 8 # Gemma3
# Combine predictions using union strategy
python scripts/ensemble_union.py \
results/test_v64_selective_merge_gemini3.csv \
results/test_deepseek_th060_llm8.csv \
results/test_gemma3_th060_llm8.csv \
--output results/test_full_union.csv --statsEnsemble Union Script Usage:
# Basic usage
python scripts/ensemble_union.py file1.csv file2.csv --output union.csv
# With statistics
python scripts/ensemble_union.py file1.csv file2.csv file3.csv --output union.csv --stats
# Priority: first file's prediction takes precedence
python scripts/ensemble_union.py \
results/gemini3_predictions.csv \
results/deepseek_predictions.csv \
--output results/ensemble.csvInput (train.json):
{
"id": "003",
"page": 169,
"label": ["78.14,238.71,482.26,349.60:TC-SC-320a.1"],
"company": "tsmc",
"esg_report": "TSMC_台積電.pdf",
"sasb_report": "SASB-TC-SC.pdf"
}Output (CSV):
ID,TARGET
003,78.14,238.71,482.26,349.60:TC-SC-320a.1
004,NONE本專案實作了一個 檢索增強生成 (RAG) 系統,用於將 ESG(環境、社會、治理)報告段落匹配到 SASB(可持續會計準則委員會)指標。給定 ESG 報告的特定頁面和對應的 SASB 標準,系統會識別相關文字區域,並預測其邊界框 (bounding box) 及對應的 SASB 指標代碼。
- 輸入:ESG 報告 PDF 頁面 + SASB 報告(指標定義)
- 輸出:預測的邊界框和對應的 SASB 指標代碼
- 評估指標:F1 Score(IoU 閾值 = 0.5)
- 目標:F1 > 0.2
IR/
├── main.py # 🚀 主程式入口(端到端流程)
├── config.py # ⚙️ 配置管理
├── data/
│ ├── train.json # 訓練資料(151 筆)
│ ├── test.json # 測試資料(72 筆)
│ ├── train_solution.csv # 標準答案
│ ├── reports/ # ESG 報告 PDF(15 家公司)
│ ├── sasb/ # SASB 標準 PDF 和 markdown(7 個產業)
│ └── processed/
│ ├── esg_segments_v2.jsonl # 提取的 ESG 段落(OCR 版本)
│ ├── esg_segments_v4.jsonl # 提取的 ESG 段落(PyMuPDF 版本)
│ ├── sasb_metrics.json # 解析的 SASB 指標
│ └── sasb_metrics_translated.json
├── scripts/
│ ├── 01_build_esg_segments_v2.py # 從 PDF 提取 ESG 段落
│ ├── 02_build_sasb_metrics.py # 從 markdown 解析 SASB 指標
│ ├── 03_make_train_solution_csv.py # 產生答案 CSV
│ ├── 04_rag_baseline_infer_v64_selective_merge.py # 獨立推理腳本
│ ├── ensemble_union.py # 🎯 Ensemble:合併多模型預測
│ ├── score.py # 評分腳本
│ └── analyze_*.py # 分析腳本
├── results/ # 推理結果與提交檔案
│ ├── test_full_union.csv # ⭐ 最佳提交 (Private=0.2000)
│ ├── test_v64_selective_merge_gemini3.csv # Gemini3 預測
│ ├── test_deepseek_th060_llm8.csv # DeepSeek 預測
│ └── test_gemma3_th060_llm8.csv # Gemma3 預測
├── FINAL_SUMMARY.md # 📊 實驗總結
└── README.md
# Python 3.8+
pip install torch transformers sentence-transformers numpy pandas tqdm openai
# 可選:支援 Gemini API
pip install google-generativeai必要模型:
BAAI/bge-m3- 用於語義檢索的 Bi-encoderBAAI/bge-reranker-v2-m3- 用於重排序的 Cross-encoder(可選但推薦)
LLM 選項(選擇其一):
- Ollama(預設):遠端/本地 LLM 服務(如
minimax-m2:cloud、qwen3:8b) - Gemini API:Google 的 Gemini(
gemini-2.5-pro)
最簡單的方式是使用 main.py:
cd IR
# 執行完整流程(預處理 + 推理 + 評估)
python main.py --mode train --full
# 僅在測試集上執行推理
python main.py --mode test
# 僅執行預處理
python main.py --preprocess
# 僅執行評估
python main.py --evaluate results/train_v64_selective_merge.csv
# 顯示當前配置
python main.py --show-config
# 覆蓋參數
python main.py --mode train --score-threshold 0.55 --llm-threshold 8
# 使用 Gemini API(而非 Ollama)
export GEMINI_API_KEY="your-api-key-here"
python main.py --mode train --llm-provider gemini1.1 提取 ESG 段落
從 ESG 報告 PDF 中提取文字段落:
cd IR
python scripts/01_build_esg_segments_v2.py這會讀取 content_list.json 檔案(由 MinerU 等 OCR 工具產生),並輸出 data/processed/esg_segments_v2.jsonl。
1.2 解析 SASB 指標
解析 SASB 標準文件以提取指標:
python scripts/02_build_sasb_metrics.py這會讀取 data/sasb/ 中的 SASB markdown 檔案,並輸出 data/processed/sasb_metrics.json。
1.3 產生答案 CSV(可選)
python scripts/03_make_train_solution_csv.py訓練集:
python scripts/04_rag_baseline_infer_v64_selective_merge.py --mode train測試集:
python scripts/04_rag_baseline_infer_v64_selective_merge.py --mode test結果將儲存至 results/train_v64_selective_merge.csv 或 results/test_v64_selective_merge.csv。
python scripts/score.py data/train_solution.csv results/train_v64_selective_merge.csv輸出範例:
F1 Score: 0.1618
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 1. 段落提取 │ │ 2. Bi-encoder │ │ 3. BM25 │
│ (OCR/PyMuPDF) │ ──► │ 檢索 │ ──► │ 混合搜尋 │
│ │ │ (bge-m3) │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 6. 輸出結果 │ │ 5. Bbox │ │ 4. LLM │
│ (CSV 格式) │ ◄── │ 合併 │ ◄── │ 重排序 │
│ │ │ (選擇性合併) │ │ (Qwen2.5-7B) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
關鍵組件:
- 段落提取:從 ESG PDF 中提取帶有邊界框的文字段落
- Bi-encoder 檢索:使用
bge-m3計算語義相似度 - BM25 混合搜尋:結合詞彙匹配和語義匹配
- Cross-Encoder 重排序(新增):使用
bge-reranker-v2-m3在 LLM 前過濾候選 - LLM 評分:僅對 Top-3 候選進行評分(1-10 分)
- 選擇性 Bbox 合併:智慧合併相鄰段落
- 輸出:產生 CSV 格式的預測結果
所有參數都集中在 config.py 中,方便調整:
| 參數 | 數值 | 說明 |
|---|---|---|
score_threshold |
0.52 | Bi-encoder 相似度閾值 |
confidence_threshold |
7 | LLM 分數閾值(1-10) |
use_reranker |
True | 啟用 Cross-encoder 重排序 |
reranker_top_k |
5 | Reranker 後保留的候選數 |
top_k_candidates_per_metric |
3 | 每個指標最多送幾個給 LLM |
horizontal_spread_threshold |
200 | Bbox 合併的像素閾值 |
max_merged_ratio |
0.50 | 合併後 bbox 最大尺寸(頁面百分比) |
bm25_boost_value |
0.03 | BM25 分數加成 |
配置檔案:
config.py- 所有可配置參數(路徑、模型、閾值)- 可直接修改
config.py或使用main.py的命令列參數覆蓋
| 版本 | Train F1 | Test Public | Test Private | 策略 |
|---|---|---|---|---|
| v22 | 0.1006 | - | - | 僅 Bi-encoder |
| v35 | 0.1127 | - | - | + LLM 重排序 |
| v55 | 0.1387 | - | - | + BM25 混合 |
| v64 | 0.1618 | 0.1904 | 0.1250 | + 選擇性合併 (Gemini3) |
| Ensemble | - | 0.1923 | 0.2000 ⭐ | 多模型聯集 |
最佳提交成績是透過 多模型 Ensemble 達成的:
# 從多個 LLM 產生預測
python main.py --mode test --llm-provider gemini # Gemini3 Pro
python main.py --mode test --llm-model deepseek-r1:14b # DeepSeek R1
python main.py --mode test --llm-model gemma3:12b --llm-threshold 8 # Gemma3
# 使用 union 策略合併預測
python scripts/ensemble_union.py \
results/test_v64_selective_merge_gemini3.csv \
results/test_deepseek_th060_llm8.csv \
results/test_gemma3_th060_llm8.csv \
--output results/test_full_union.csv --statsEnsemble Union 腳本使用方式:
# 基本使用
python scripts/ensemble_union.py file1.csv file2.csv --output union.csv
# 顯示統計資訊
python scripts/ensemble_union.py file1.csv file2.csv file3.csv --output union.csv --stats
# 優先順序:第一個檔案的預測優先
python scripts/ensemble_union.py \
results/gemini3_predictions.csv \
results/deepseek_predictions.csv \
--output results/ensemble.csv輸入(train.json):
{
"id": "003",
"page": 169,
"label": ["78.14,238.71,482.26,349.60:TC-SC-320a.1"],
"company": "tsmc",
"esg_report": "TSMC_台積電.pdf",
"sasb_report": "SASB-TC-SC.pdf"
}輸出(CSV):
ID,TARGET
003,78.14,238.71,482.26,349.60:TC-SC-320a.1
004,NONE| 代碼前綴 | 產業 |
|---|---|
| TC-SC | 半導體 (Semiconductors) |
| TC-HW | 硬體 (Hardware) |
| FN-CB | 商業銀行 (Commercial Banks) |
| EM-RM | 石油與天然氣精煉 (Oil & Gas Refining) |
| HC-BP | 生物製藥 (Biotechnology & Pharmaceuticals) |
| HC-MS | 醫療設備 (Medical Equipment & Supplies) |
| IF-GU | 天然氣公用事業 (Gas Utilities) |
Q1:模型載入失敗?
# 確保有足夠的 GPU 記憶體(建議 16GB+)
# 或修改腳本使用 CPUQ2:找不到 content_list.json?
# 需要先用 MinerU 等 OCR 工具處理 PDF
# 或使用 PyMuPDF 版本的 segment 提取腳本Q3:如何調整閾值?
# 在 04_rag_baseline_infer_v64_selective_merge.py 中修改:
SCORE_THRESHOLD = 0.52 # 提高會減少誤報,降低會減少漏報
LLM_CONFIDENCE_THRESHOLD = 7 # 提高會更嚴格MIT License
IRIE Final Project - Fall 2024