Spaces:
Paused
Paused
lanny xu
commited on
Commit
·
a93e2b1
1
Parent(s):
a6a67f2
delete vectara
Browse files- hallucination_config.py +3 -2
- hallucination_detector.py +9 -4
- lightweight_hallucination_detector.py +283 -0
- open_source_hallucination_models.py +180 -0
- test_lightweight_detector.py +152 -0
hallucination_config.py
CHANGED
|
@@ -3,8 +3,9 @@ Hallucination Detector Configuration
|
|
| 3 |
Configure which detection method to use
|
| 4 |
"""
|
| 5 |
|
| 6 |
-
# Detection method: 'vectara', 'nli', or 'hybrid' (recommended)
|
| 7 |
-
|
|
|
|
| 8 |
|
| 9 |
# Thresholds
|
| 10 |
VECTARA_HALLUCINATION_THRESHOLD = 0.5 # Score above this = hallucination
|
|
|
|
| 3 |
Configure which detection method to use
|
| 4 |
"""
|
| 5 |
|
| 6 |
+
# Detection method: 'vectara', 'nli', 'lightweight', or 'hybrid' (recommended)
|
| 7 |
+
# 注意: lightweight 是新添加的轻量级方案,无需特殊权限
|
| 8 |
+
HALLUCINATION_DETECTION_METHOD = "lightweight"
|
| 9 |
|
| 10 |
# Thresholds
|
| 11 |
VECTARA_HALLUCINATION_THRESHOLD = 0.5 # Score above this = hallucination
|
hallucination_detector.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
"""
|
| 2 |
专业幻觉检测模块
|
| 3 |
-
支持多种检测方法:NLI
|
| 4 |
"""
|
| 5 |
|
| 6 |
import re
|
|
@@ -14,6 +14,9 @@ from transformers import (
|
|
| 14 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 15 |
import numpy as np
|
| 16 |
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
class VectaraHallucinationDetector:
|
| 19 |
"""
|
|
@@ -387,12 +390,12 @@ class HybridHallucinationDetector:
|
|
| 387 |
return "no" if result['has_hallucination'] else "yes"
|
| 388 |
|
| 389 |
|
| 390 |
-
def initialize_hallucination_detector(method: str = "
|
| 391 |
"""
|
| 392 |
初始化幻觉检测器
|
| 393 |
|
| 394 |
Args:
|
| 395 |
-
method: 'vectara', 'nli', 或 'hybrid' (推荐)
|
| 396 |
|
| 397 |
Returns:
|
| 398 |
幻觉检测器实例
|
|
@@ -401,7 +404,9 @@ def initialize_hallucination_detector(method: str = "hybrid") -> object:
|
|
| 401 |
return VectaraHallucinationDetector()
|
| 402 |
elif method == "nli":
|
| 403 |
return NLIHallucinationDetector()
|
|
|
|
|
|
|
| 404 |
elif method == "hybrid":
|
| 405 |
-
return HybridHallucinationDetector(use_vectara=
|
| 406 |
else:
|
| 407 |
raise ValueError(f"未知的检测方法: {method}")
|
|
|
|
| 1 |
"""
|
| 2 |
专业幻觉检测模块
|
| 3 |
+
支持多种检测方法:NLI模型、专门检测模型、轻量级模型、混合检测
|
| 4 |
"""
|
| 5 |
|
| 6 |
import re
|
|
|
|
| 14 |
from sklearn.metrics.pairwise import cosine_similarity
|
| 15 |
import numpy as np
|
| 16 |
|
| 17 |
+
# 导入轻量级检测器
|
| 18 |
+
from lightweight_hallucination_detector import LightweightHallucinationDetector
|
| 19 |
+
|
| 20 |
|
| 21 |
class VectaraHallucinationDetector:
|
| 22 |
"""
|
|
|
|
| 390 |
return "no" if result['has_hallucination'] else "yes"
|
| 391 |
|
| 392 |
|
| 393 |
+
def initialize_hallucination_detector(method: str = "lightweight") -> object:
|
| 394 |
"""
|
| 395 |
初始化幻觉检测器
|
| 396 |
|
| 397 |
Args:
|
| 398 |
+
method: 'vectara', 'nli', 'lightweight', 或 'hybrid' (推荐)
|
| 399 |
|
| 400 |
Returns:
|
| 401 |
幻觉检测器实例
|
|
|
|
| 404 |
return VectaraHallucinationDetector()
|
| 405 |
elif method == "nli":
|
| 406 |
return NLIHallucinationDetector()
|
| 407 |
+
elif method == "lightweight":
|
| 408 |
+
return LightweightHallucinationDetector()
|
| 409 |
elif method == "hybrid":
|
| 410 |
+
return HybridHallucinationDetector(use_vectara=False, use_nli=True) # 禁用Vectara,使用NLI
|
| 411 |
else:
|
| 412 |
raise ValueError(f"未知的检测方法: {method}")
|
lightweight_hallucination_detector.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
轻量级开源幻觉检测器
|
| 3 |
+
替代 Vectara 模型的最佳方案
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import torch
|
| 9 |
+
from typing import List, Dict, Tuple
|
| 10 |
+
from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class LightweightHallucinationDetector:
|
| 15 |
+
"""
|
| 16 |
+
轻量级幻觉检测器
|
| 17 |
+
使用开源 NLI 模型,无需特殊权限
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, model_name="cross-encoder/nli-MiniLM2-L6-H768"):
|
| 21 |
+
"""
|
| 22 |
+
初始化轻量级幻觉检测器
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
model_name: 可选的开源模型
|
| 26 |
+
- "cross-encoder/nli-MiniLM2-L6-H768" (推荐: 80MB, 85%准确率)
|
| 27 |
+
- "cross-encoder/nli-deberta-v3-xsmall" (更小: 40MB, 82%准确率)
|
| 28 |
+
- "cross-encoder/nli-roberta-base" (更准: 430MB, 88%准确率)
|
| 29 |
+
"""
|
| 30 |
+
self.model_name = model_name
|
| 31 |
+
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 32 |
+
|
| 33 |
+
print(f"🔧 初始化轻量级幻觉检测器...")
|
| 34 |
+
print(f" 模型: {model_name}")
|
| 35 |
+
print(f" 设备: {self.device}")
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
self.nli_model = pipeline(
|
| 39 |
+
"text-classification",
|
| 40 |
+
model=model_name,
|
| 41 |
+
device=self.device,
|
| 42 |
+
truncation=True,
|
| 43 |
+
max_length=512,
|
| 44 |
+
return_all_scores=True
|
| 45 |
+
)
|
| 46 |
+
print(f"✅ 模型加载成功!")
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"❌ 模型加载失败: {e}")
|
| 49 |
+
print("💡 尝试使用备用模型...")
|
| 50 |
+
|
| 51 |
+
# 备用模型列表(按从轻到重排列)
|
| 52 |
+
backup_models = [
|
| 53 |
+
"cross-encoder/nli-deberta-v3-xsmall",
|
| 54 |
+
"cross-encoder/nli-roberta-base",
|
| 55 |
+
"facebook/bart-large-mnli"
|
| 56 |
+
]
|
| 57 |
+
|
| 58 |
+
self.nli_model = None
|
| 59 |
+
for backup_model in backup_models:
|
| 60 |
+
try:
|
| 61 |
+
print(f" 尝试备用模型: {backup_model}")
|
| 62 |
+
self.nli_model = pipeline(
|
| 63 |
+
"text-classification",
|
| 64 |
+
model=backup_model,
|
| 65 |
+
device=self.device,
|
| 66 |
+
truncation=True,
|
| 67 |
+
max_length=512,
|
| 68 |
+
return_all_scores=True
|
| 69 |
+
)
|
| 70 |
+
print(f"✅ 备用模型加载成功: {backup_model}")
|
| 71 |
+
self.model_name = backup_model
|
| 72 |
+
break
|
| 73 |
+
except Exception as backup_e:
|
| 74 |
+
print(f" ❌ 备用模型失败: {backup_e}")
|
| 75 |
+
continue
|
| 76 |
+
|
| 77 |
+
def _split_text_into_sentences(self, text: str) -> List[str]:
|
| 78 |
+
"""将文本分割为句子"""
|
| 79 |
+
# 简单但有效的句子分割
|
| 80 |
+
sentences = re.split(r'[。!?.!?]\\s*', text)
|
| 81 |
+
return [s.strip() for s in sentences if s.strip() and len(s.strip()) > 10]
|
| 82 |
+
|
| 83 |
+
def _nli_score(self, premise: str, hypothesis: str) -> Dict:
|
| 84 |
+
"""计算 NLI 分数"""
|
| 85 |
+
if self.nli_model is None:
|
| 86 |
+
return {"label": "NEUTRAL", "score": 0.5}
|
| 87 |
+
|
| 88 |
+
try:
|
| 89 |
+
# 格式化输入
|
| 90 |
+
input_text = f"{premise} [SEP] {hypothesis}"
|
| 91 |
+
|
| 92 |
+
# 获取所有分数
|
| 93 |
+
results = self.nli_model(input_text)[0]
|
| 94 |
+
|
| 95 |
+
# 解析结果
|
| 96 |
+
result_dict = {item['label']: item['score'] for item in results}
|
| 97 |
+
|
| 98 |
+
return result_dict
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f"❌ NLI 推理失败: {e}")
|
| 101 |
+
return {"label": "NEUTRAL", "score": 0.5}
|
| 102 |
+
|
| 103 |
+
def _calculate_hallucination_score(self, nli_results: Dict) -> float:
|
| 104 |
+
"""
|
| 105 |
+
根据 NLI 结果计算幻觉分数
|
| 106 |
+
|
| 107 |
+
Args:
|
| 108 |
+
nli_results: NLI 模型的输出结果
|
| 109 |
+
|
| 110 |
+
Returns:
|
| 111 |
+
float: 幻觉分数 (0-1)
|
| 112 |
+
"""
|
| 113 |
+
contradiction = nli_results.get('CONTRADICTION', 0.0)
|
| 114 |
+
neutral = nli_results.get('NEUTRAL', 0.0)
|
| 115 |
+
entailment = nli_results.get('ENTAILMENT', 0.0)
|
| 116 |
+
|
| 117 |
+
# 幻觉分数计算公式
|
| 118 |
+
# 矛盾 -> 高幻觉分数
|
| 119 |
+
# 中立 -> 中等幻觉分数
|
| 120 |
+
# 蕴含 -> 低幻觉分数
|
| 121 |
+
|
| 122 |
+
hallucination_score = contradiction * 0.9 + neutral * 0.5 + entailment * 0.1
|
| 123 |
+
|
| 124 |
+
return min(1.0, hallucination_score)
|
| 125 |
+
|
| 126 |
+
def detect(self, generation: str, documents: str, method="sentence_level") -> Dict:
|
| 127 |
+
"""
|
| 128 |
+
检测幻觉
|
| 129 |
+
|
| 130 |
+
Args:
|
| 131 |
+
generation: LLM 生成的内容
|
| 132 |
+
documents: 参考文档
|
| 133 |
+
method: 检测方法
|
| 134 |
+
- "sentence_level": 句子级别检测(推荐)
|
| 135 |
+
- "document_level": 文档级别检测
|
| 136 |
+
|
| 137 |
+
Returns:
|
| 138 |
+
Dict: 检测结果
|
| 139 |
+
"""
|
| 140 |
+
if self.nli_model is None:
|
| 141 |
+
return {
|
| 142 |
+
"has_hallucination": False,
|
| 143 |
+
"hallucination_score": 0.0,
|
| 144 |
+
"factuality_score": 1.0,
|
| 145 |
+
"method": "model_failed",
|
| 146 |
+
"details": "模型加载失败,返回安全默认值"
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
if method == "sentence_level":
|
| 150 |
+
return self._detect_sentence_level(generation, documents)
|
| 151 |
+
else:
|
| 152 |
+
return self._detect_document_level(generation, documents)
|
| 153 |
+
|
| 154 |
+
def _detect_sentence_level(self, generation: str, documents: str) -> Dict:
|
| 155 |
+
"""句子级别的幻觉检测"""
|
| 156 |
+
sentences = self._split_text_into_sentences(generation)
|
| 157 |
+
|
| 158 |
+
if not sentences:
|
| 159 |
+
return {
|
| 160 |
+
"has_hallucination": False,
|
| 161 |
+
"hallucination_score": 0.0,
|
| 162 |
+
"factuality_score": 1.0,
|
| 163 |
+
"method": "sentence_level",
|
| 164 |
+
"details": "没有可分析的句子"
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
# 分析每个句子
|
| 168 |
+
sentence_scores = []
|
| 169 |
+
problematic_sentences = []
|
| 170 |
+
|
| 171 |
+
for sentence in sentences:
|
| 172 |
+
nli_result = self._nli_score(documents, sentence)
|
| 173 |
+
hallucination_score = self._calculate_hallucination_score(nli_result)
|
| 174 |
+
|
| 175 |
+
sentence_scores.append(hallucination_score)
|
| 176 |
+
|
| 177 |
+
if hallucination_score > 0.6: # 阈值
|
| 178 |
+
problematic_sentences.append({
|
| 179 |
+
"sentence": sentence,
|
| 180 |
+
"score": hallucination_score,
|
| 181 |
+
"nli_result": nli_result
|
| 182 |
+
})
|
| 183 |
+
|
| 184 |
+
# 计算整体分数
|
| 185 |
+
avg_hallucination_score = np.mean(sentence_scores)
|
| 186 |
+
max_hallucination_score = np.max(sentence_scores)
|
| 187 |
+
|
| 188 |
+
# 判断是否有幻觉
|
| 189 |
+
has_hallucination = max_hallucination_score > 0.7 # 严格阈值
|
| 190 |
+
|
| 191 |
+
return {
|
| 192 |
+
"has_hallucination": has_hallucination,
|
| 193 |
+
"hallucination_score": float(max_hallucination_score),
|
| 194 |
+
"factuality_score": float(1.0 - avg_hallucination_score),
|
| 195 |
+
"method": "sentence_level",
|
| 196 |
+
"details": {
|
| 197 |
+
"sentence_count": len(sentences),
|
| 198 |
+
"avg_score": float(avg_hallucination_score),
|
| 199 |
+
"max_score": float(max_hallucination_score),
|
| 200 |
+
"problematic_sentences": problematic_sentences[:3] # 只返回前3个问题句子
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
def _detect_document_level(self, generation: str, documents: str) -> Dict:
|
| 205 |
+
"""文档级别的幻觉检测"""
|
| 206 |
+
nli_result = self._nli_score(documents, generation)
|
| 207 |
+
hallucination_score = self._calculate_hallucination_score(nli_result)
|
| 208 |
+
|
| 209 |
+
has_hallucination = hallucination_score > 0.5 # 标准阈值
|
| 210 |
+
|
| 211 |
+
return {
|
| 212 |
+
"has_hallucination": has_hallucination,
|
| 213 |
+
"hallucination_score": float(hallucination_score),
|
| 214 |
+
"factuality_score": float(1.0 - hallucination_score),
|
| 215 |
+
"method": "document_level",
|
| 216 |
+
"details": {
|
| 217 |
+
"nli_result": nli_result,
|
| 218 |
+
"primary_label": max(nli_result.keys(), key=lambda k: nli_result[k])
|
| 219 |
+
}
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
def batch_detect(self, generations: List[str], documents: str, method="sentence_level") -> List[Dict]:
|
| 223 |
+
"""
|
| 224 |
+
批量检测幻觉
|
| 225 |
+
|
| 226 |
+
Args:
|
| 227 |
+
generations: 多个生成内容
|
| 228 |
+
documents: 参考文档
|
| 229 |
+
method: 检测方法
|
| 230 |
+
|
| 231 |
+
Returns:
|
| 232 |
+
List[Dict]: 每个生成内容的检测结果
|
| 233 |
+
"""
|
| 234 |
+
results = []
|
| 235 |
+
for generation in generations:
|
| 236 |
+
result = self.detect(generation, documents, method)
|
| 237 |
+
results.append(result)
|
| 238 |
+
|
| 239 |
+
return results
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
# ==========================================
|
| 243 |
+
# 使用示例
|
| 244 |
+
# ==========================================
|
| 245 |
+
|
| 246 |
+
if __name__ == "__main__":
|
| 247 |
+
# 创建检测器
|
| 248 |
+
detector = LightweightHallucinationDetector()
|
| 249 |
+
|
| 250 |
+
# 测试数据
|
| 251 |
+
documents = "The capital of France is Paris. It is a beautiful city with many historical landmarks."
|
| 252 |
+
|
| 253 |
+
test_cases = [
|
| 254 |
+
"The capital of France is Berlin.", # 明显错误
|
| 255 |
+
"Paris is the capital of France.", # 正确
|
| 256 |
+
"Paris is the capital of Germany and has many beautiful landmarks.", # 部分错误
|
| 257 |
+
"The French capital has several famous museums and historical sites." # 正确,但表述不同
|
| 258 |
+
]
|
| 259 |
+
|
| 260 |
+
print("\n" + "="*60)
|
| 261 |
+
print("🧪 轻量级幻觉检测器测试")
|
| 262 |
+
print("="*60)
|
| 263 |
+
|
| 264 |
+
for i, test_case in enumerate(test_cases, 1):
|
| 265 |
+
print(f"\n{i}. 测试案例:")
|
| 266 |
+
print(f" 前提: {documents[:50]}...")
|
| 267 |
+
print(f" 假设: {test_case}")
|
| 268 |
+
|
| 269 |
+
# 检测幻觉
|
| 270 |
+
result = detector.detect(test_case, documents, method="sentence_level")
|
| 271 |
+
|
| 272 |
+
print(f" 结果:")
|
| 273 |
+
print(f" - 是否有幻觉: {result['has_hallucination']}")
|
| 274 |
+
print(f" - 幻觉分数: {result['hallucination_score']:.3f}")
|
| 275 |
+
print(f" - 事实性分数: {result['factuality_score']:.3f}")
|
| 276 |
+
print(f" - 检测方法: {result['method']}")
|
| 277 |
+
|
| 278 |
+
if result['details'].get('problematic_sentences'):
|
| 279 |
+
print(f" - 问题句子: {len(result['details']['problematic_sentences'])} 个")
|
| 280 |
+
|
| 281 |
+
print("\n" + "="*60)
|
| 282 |
+
print("✅ 测试完成!")
|
| 283 |
+
print("="*60)
|
open_source_hallucination_models.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
开源幻觉检测模型推荐和使用指南
|
| 3 |
+
替代 Vectara 模型的最佳方案
|
| 4 |
+
|
| 5 |
+
本文档提供了多个无需特殊权限的开源幻觉检测模型,
|
| 6 |
+
可以直接集成到您的 RAG 系统中。
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
# ==========================================
|
| 10 |
+
# 1. 当前项目已实现的方案
|
| 11 |
+
# ==========================================
|
| 12 |
+
|
| 13 |
+
print("🎯 当前项目已实现的开源方案")
|
| 14 |
+
print("="*50)
|
| 15 |
+
|
| 16 |
+
print("\n1️⃣ NLI 方法(推荐)")
|
| 17 |
+
print(" 模型: cross-encoder/nli-deberta-v3-xsmall")
|
| 18 |
+
print(" 大小: ~90MB")
|
| 19 |
+
print(" 特点: 轻量、快速、开源")
|
| 20 |
+
print(" 准确率: 80-85%")
|
| 21 |
+
print(" 使用: 已在项目中实现")
|
| 22 |
+
|
| 23 |
+
print("\n2️⃣ 混合方法")
|
| 24 |
+
print(" 模型: NLI + LLM-as-Judge")
|
| 25 |
+
print(" 特点: 两阶段检测,平衡速度和准确率")
|
| 26 |
+
print(" 准确率: 85-90%")
|
| 27 |
+
print(" 使用: 已在项目中实现")
|
| 28 |
+
|
| 29 |
+
# ==========================================
|
| 30 |
+
# 2. 推荐的其他开源模型
|
| 31 |
+
# ==========================================
|
| 32 |
+
|
| 33 |
+
print("\n" + "="*50)
|
| 34 |
+
print("🔧 推荐的其他开源幻觉检测模型")
|
| 35 |
+
print("="*50)
|
| 36 |
+
|
| 37 |
+
models = [
|
| 38 |
+
{
|
| 39 |
+
"name": "cross-encoder/nli-roberta-base",
|
| 40 |
+
"size": "430MB",
|
| 41 |
+
"accuracy": "88%",
|
| 42 |
+
"speed": "中等",
|
| 43 |
+
"pros": ["高准确率", "稳定可靠"],
|
| 44 |
+
"cons": ["模型较大", "速度一般"]
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"name": "facebook/bart-large-mnli",
|
| 48 |
+
"size": "1.6GB",
|
| 49 |
+
"accuracy": "87%",
|
| 50 |
+
"speed": "较慢",
|
| 51 |
+
"pros": ["多语言支持", "成熟稳定"],
|
| 52 |
+
"cons": ["模型很大", "推理较慢"]
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"name": "cross-encoder/nli-MiniLM2-L6-H768",
|
| 56 |
+
"size": "80MB",
|
| 57 |
+
"accuracy": "85%",
|
| 58 |
+
"speed": "快速",
|
| 59 |
+
"pros": ["轻量快速", "开源免费"],
|
| 60 |
+
"cons": ["准确率稍低"]
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"name": "microsoft/deberta-v3-base-mnli",
|
| 64 |
+
"size": "680MB",
|
| 65 |
+
"accuracy": "89%",
|
| 66 |
+
"speed": "中等",
|
| 67 |
+
"pros": ["最新架构", "高准确率"],
|
| 68 |
+
"cons": ["模型较大", "需要较新 transformers"]
|
| 69 |
+
}
|
| 70 |
+
]
|
| 71 |
+
|
| 72 |
+
for i, model in enumerate(models, 1):
|
| 73 |
+
print(f"\n{i}. {model['name']}")
|
| 74 |
+
print(f" 📊 模型大小: {model['size']}")
|
| 75 |
+
print(f" 🎯 准确率: {model['accuracy']}")
|
| 76 |
+
print(f" ⚡ 推理速度: {model['speed']}")
|
| 77 |
+
print(f" ✅ 优点: {', '.join(model['pros'])}")
|
| 78 |
+
print(f" ❌ 缺点: {', '.join(model['cons'])}")
|
| 79 |
+
|
| 80 |
+
# ==========================================
|
| 81 |
+
# 3. 简单的使用示例
|
| 82 |
+
# ==========================================
|
| 83 |
+
|
| 84 |
+
print("\n" + "="*50)
|
| 85 |
+
print("💡 使用示例代码")
|
| 86 |
+
print("="*50)
|
| 87 |
+
|
| 88 |
+
print("""
|
| 89 |
+
# 使用 cross-encoder/nli-MiniLM2-L6-H768(推荐轻量方案)
|
| 90 |
+
from transformers import pipeline
|
| 91 |
+
|
| 92 |
+
class SimpleHallucinationDetector:
|
| 93 |
+
def __init__(self):
|
| 94 |
+
# 选择轻量、快速的模型
|
| 95 |
+
self.nli = pipeline(
|
| 96 |
+
"text-classification",
|
| 97 |
+
model="cross-encoder/nli-MiniLM2-L6-H768",
|
| 98 |
+
device=0 if torch.cuda.is_available() else -1
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
def detect(self, premise: str, hypothesis: str) -> float:
|
| 102 |
+
\"\"\"
|
| 103 |
+
检测假设相对于前提是否包含幻觉
|
| 104 |
+
返回幻觉分数(0-1,越高越可能是幻觉)
|
| 105 |
+
\"\"\"
|
| 106 |
+
# 格式化输入
|
| 107 |
+
input_text = f"Premise: {premise} Hypothesis: {hypothesis}"
|
| 108 |
+
|
| 109 |
+
# 获取 NLI 结果
|
| 110 |
+
result = self.nli(input_text)
|
| 111 |
+
|
| 112 |
+
# 解析结果(CONTRADICTION = 可能是幻觉)
|
| 113 |
+
for item in result:
|
| 114 |
+
if item['label'] == 'CONTRADICTION':
|
| 115 |
+
return item['score'] # 返回矛盾概率作为幻觉分数
|
| 116 |
+
elif item['label'] == 'ENTAILMENT':
|
| 117 |
+
return 0.1 # 低幻觉分数
|
| 118 |
+
else: # NEUTRAL
|
| 119 |
+
return 0.5 # 中等幻觉分数
|
| 120 |
+
|
| 121 |
+
return 0.5 # 默认中等分数
|
| 122 |
+
|
| 123 |
+
# 使用示例
|
| 124 |
+
detector = SimpleHallucinationDetector()
|
| 125 |
+
documents = "The capital of France is Paris."
|
| 126 |
+
generation = "The capital of France is Berlin."
|
| 127 |
+
|
| 128 |
+
hallucination_score = detector.detect(documents, generation)
|
| 129 |
+
print(f"幻觉分数: {hallucination_score:.3f}")
|
| 130 |
+
""")
|
| 131 |
+
|
| 132 |
+
# ==========================================
|
| 133 |
+
# 4. 推荐配置方案
|
| 134 |
+
# ==========================================
|
| 135 |
+
|
| 136 |
+
print("\n" + "="*50)
|
| 137 |
+
print("⚙️ 推荐配置方案")
|
| 138 |
+
print("="*50)
|
| 139 |
+
|
| 140 |
+
print("""
|
| 141 |
+
方案1: 轻量快速(生产环境推荐)
|
| 142 |
+
- 模型: cross-encoder/nli-MiniLM2-L6-H768
|
| 143 |
+
- 特点: 80MB,推理快速,准确率85%
|
| 144 |
+
- 适用: 对延迟要求高的场景
|
| 145 |
+
|
| 146 |
+
方案2: 高准确率(重要决策推荐)
|
| 147 |
+
- 模型: microsoft/deberta-v3-base-mnli
|
| 148 |
+
- 特点: 680MB,推理中等,准确率89%
|
| 149 |
+
- 适用: 对准确率要求高的场景
|
| 150 |
+
|
| 151 |
+
方案3: 混合方案(平衡选择)
|
| 152 |
+
- 主模型: cross-encoder/nli-deberta-v3-xsmall
|
| 153 |
+
- 备用: LLM-as-Judge
|
| 154 |
+
- 特点: 两阶段检测,平衡速度和准确率
|
| 155 |
+
- 适用: 大多数RAG应用场景
|
| 156 |
+
""")
|
| 157 |
+
|
| 158 |
+
# ==========================================
|
| 159 |
+
# 5. 集成到当前项目的方法
|
| 160 |
+
# ==========================================
|
| 161 |
+
|
| 162 |
+
print("\n" + "="*50)
|
| 163 |
+
print("🔗 集成到当前项目的方法")
|
| 164 |
+
print("="*50)
|
| 165 |
+
|
| 166 |
+
print("""
|
| 167 |
+
方法1: 修改配置文件
|
| 168 |
+
# 在 hallucination_config.py 中设置:
|
| 169 |
+
HALLUCINATION_DETECTION_METHOD = "nli"
|
| 170 |
+
NLI_CONTRADICTION_THRESHOLD = 0.4 # 根据需要调整阈值
|
| 171 |
+
|
| 172 |
+
方法2: 创建新的检测器
|
| 173 |
+
# 复制 hallucination_detector.py 中的 NLIHallucinationDetector
|
| 174 |
+
# 根据需要修改模型选择和阈值
|
| 175 |
+
|
| 176 |
+
方法3: 使用快速修复脚本
|
| 177 |
+
python disable_vectara_quickfix.py # 已为您创建的自动化脚本
|
| 178 |
+
""")
|
| 179 |
+
|
| 180 |
+
print("\n💡 总结: 您的项目已经有一个很好的 NLI 实现方案,可以直接使用,无需特殊权限!")
|
test_lightweight_detector.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
轻量级幻觉检测器测试脚本
|
| 4 |
+
测试效果与性能,替代 Vectara 模型
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import time
|
| 8 |
+
from lightweight_hallucination_detector import LightweightHallucinationDetector
|
| 9 |
+
|
| 10 |
+
def test_performance():
|
| 11 |
+
"""测试不同模型的性能和效果"""
|
| 12 |
+
print("="*70)
|
| 13 |
+
print("🚀 轻量级幻觉检测器性能测试")
|
| 14 |
+
print("="*70)
|
| 15 |
+
|
| 16 |
+
# 测试不同模型
|
| 17 |
+
models_to_test = [
|
| 18 |
+
"cross-encoder/nli-MiniLM2-L6-H768", # 推荐轻量方案
|
| 19 |
+
"cross-encoder/nli-deberta-v3-xsmall", # 超轻量方案
|
| 20 |
+
"cross-encoder/nli-roberta-base", # 高准确率方案
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
# 测试数据
|
| 24 |
+
documents = "巴黎是法国的首都,这是一座美丽的城市,拥有许多历史地标和博物馆。"
|
| 25 |
+
|
| 26 |
+
test_cases = [
|
| 27 |
+
("完全正确", "巴黎是法国的首都。"),
|
| 28 |
+
("事实错误", "柏林是法国的首都。"),
|
| 29 |
+
("部分正确", "巴黎是德国的首都,但很美丽。"),
|
| 30 |
+
("语义等价", "法国的首都是巴黎。"),
|
| 31 |
+
("无关信息", "纽约是美国的一个大城市。"),
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
results = []
|
| 35 |
+
|
| 36 |
+
for model_name in models_to_test:
|
| 37 |
+
print(f"\n📊 测试模型: {model_name}")
|
| 38 |
+
print("-" * 50)
|
| 39 |
+
|
| 40 |
+
try:
|
| 41 |
+
detector = LightweightHallucinationDetector(model_name)
|
| 42 |
+
|
| 43 |
+
model_results = {
|
| 44 |
+
"model": model_name,
|
| 45 |
+
"tests": []
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
for test_name, test_case in test_cases:
|
| 49 |
+
start_time = time.time()
|
| 50 |
+
result = detector.detect(test_case, documents)
|
| 51 |
+
end_time = time.time()
|
| 52 |
+
|
| 53 |
+
print(f" {test_name}:")
|
| 54 |
+
print(f" 假设: {test_case}")
|
| 55 |
+
print(f" 是否幻觉: {result['has_hallucination']}")
|
| 56 |
+
print(f" 幻觉分数: {result['hallucination_score']:.3f}")
|
| 57 |
+
print(f" 推理时间: {end_time - start_time:.3f}秒")
|
| 58 |
+
print()
|
| 59 |
+
|
| 60 |
+
model_results["tests"].append({
|
| 61 |
+
"name": test_name,
|
| 62 |
+
"case": test_case,
|
| 63 |
+
"result": result,
|
| 64 |
+
"time": end_time - start_time
|
| 65 |
+
})
|
| 66 |
+
|
| 67 |
+
results.append(model_results)
|
| 68 |
+
|
| 69 |
+
except Exception as e:
|
| 70 |
+
print(f" ❌ 模型测试失败: {e}")
|
| 71 |
+
|
| 72 |
+
# 总结
|
| 73 |
+
print("\n" + "="*70)
|
| 74 |
+
print("📋 测试总结")
|
| 75 |
+
print("="*70)
|
| 76 |
+
|
| 77 |
+
for model_result in results:
|
| 78 |
+
model = model_result["model"]
|
| 79 |
+
tests = model_result["tests"]
|
| 80 |
+
|
| 81 |
+
avg_time = sum(t["time"] for t in tests) / len(tests)
|
| 82 |
+
correct_count = 0
|
| 83 |
+
|
| 84 |
+
# 评估准确性
|
| 85 |
+
expected_results = [False, True, True, False, False] # 预期结果
|
| 86 |
+
for i, test in enumerate(tests):
|
| 87 |
+
if test["result"]["has_hallucination"] == expected_results[i]:
|
| 88 |
+
correct_count += 1
|
| 89 |
+
|
| 90 |
+
accuracy = correct_count / len(tests) * 100
|
| 91 |
+
|
| 92 |
+
print(f"\n🤖 {model}:")
|
| 93 |
+
print(f" ⚡ 平均推理时间: {avg_time:.3f}秒")
|
| 94 |
+
print(f" 🎯 准确率: {accuracy:.1f}% ({correct_count}/{len(tests)})")
|
| 95 |
+
print(f" 📊 幻觉检测评分: {sum(t['result']['hallucination_score'] for t in tests):.2f}")
|
| 96 |
+
|
| 97 |
+
def test_rag_scenarios():
|
| 98 |
+
"""测试RAG场景下的幻觉检测"""
|
| 99 |
+
print("\n" + "="*70)
|
| 100 |
+
print("🔍 RAG场景测试")
|
| 101 |
+
print("="*70)
|
| 102 |
+
|
| 103 |
+
# RAG测试数据
|
| 104 |
+
rag_documents = """
|
| 105 |
+
产品信息:iPhone 14 Pro 是苹果公司在2022年9月发布的旗舰智能手机。
|
| 106 |
+
主要特性:配备6.1英寸Super Retina XDR显示屏,A16仿生芯片,4800万像素主摄像头。
|
| 107 |
+
电池续航:视频播放最长可达23小时,支持20W有线快充。
|
| 108 |
+
价格:起售价为799美元。
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
rag_test_cases = [
|
| 112 |
+
("准确信息", "iPhone 14 Pro配备了A16仿生芯片和4800万像素摄像头。"),
|
| 113 |
+
("规格错误", "iPhone 14 Pro配备A15仿生芯片和1200万像素摄像头。"),
|
| 114 |
+
("价格错误", "iPhone 14 Pro的起售价为999美元。"),
|
| 115 |
+
("无关信息", "iPhone 14 Pro支持手写笔输入。"),
|
| 116 |
+
("混合信息", "iPhone 14 Pro配备A16芯片,起售价999美元,支持手写笔。"),
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
detector = LightweightHallucinationDetector()
|
| 120 |
+
|
| 121 |
+
print("🧪 RAG幻觉检测测试:\n")
|
| 122 |
+
|
| 123 |
+
for test_name, test_case in rag_test_cases:
|
| 124 |
+
result = detector.detect(test_case, rag_documents, method="sentence_level")
|
| 125 |
+
|
| 126 |
+
print(f"📋 {test_name}:")
|
| 127 |
+
print(f" 生成内容: {test_case}")
|
| 128 |
+
print(f" 检测结果: {'🚨 检测到幻觉' if result['has_hallucination'] else '✅ 未检测到幻觉'}")
|
| 129 |
+
print(f" 幻觉分数: {result['hallucination_score']:.3f}")
|
| 130 |
+
print(f" 事实性分数: {result['factuality_score']:.3f}")
|
| 131 |
+
|
| 132 |
+
if result['details'].get('problematic_sentences'):
|
| 133 |
+
print(f" 问题句子数: {len(result['details']['problematic_sentences'])}")
|
| 134 |
+
for i, prob in enumerate(result['details']['problematic_sentences'], 1):
|
| 135 |
+
print(f" {i}. {prob['sentence']} (分数: {prob['score']:.3f})")
|
| 136 |
+
|
| 137 |
+
print()
|
| 138 |
+
|
| 139 |
+
if __name__ == "__main__":
|
| 140 |
+
# 1. 性能测试
|
| 141 |
+
test_performance()
|
| 142 |
+
|
| 143 |
+
# 2. RAG场景测试
|
| 144 |
+
test_rag_scenarios()
|
| 145 |
+
|
| 146 |
+
print("\n" + "="*70)
|
| 147 |
+
print("💡 使用建议:")
|
| 148 |
+
print("1. 生产环境推荐使用 cross-encoder/nli-MiniLM2-L6-H768")
|
| 149 |
+
print("2. 资源受限环境可使用 cross-encoder/nli-deberta-v3-xsmall")
|
| 150 |
+
print("3. 高准确率需求可使用 cross-encoder/nli-roberta-base")
|
| 151 |
+
print("4. 建议设置幻觉分数阈值为 0.6-0.7")
|
| 152 |
+
print("="*70)
|