今天在做NLP意图识别测试的时候发现自己的FAISS识别的内容还没有余弦的高,感到很奇怪。代码如下
1、先加载向量模型
from sentence_transformers import SentenceTransformer
import os
os.environ['TRANSFORMERS_CACHE'] = 'D:/HuggingFace' # 可选,指定模型缓存路径
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com' # Hugging Face 镜像
# hf_hub_download.repo_url_prefix = "https://mirror.tuna.tsinghua.edu.cn/hugging-face-transformers"
# 加载预训练模型
model = SentenceTransformer('all-MiniLM-L6-v2',device="cpu") # 小型模型,速度快,精度高
2、定义测试数据
intents = {
"query_balance": ["查询余额", "余额查询", "查账户里的钱"],
"query_wheather": ["天气怎么样", "查询天气"],
"transfer_money": ["转账", "给别人汇款", "支付给对方"],
"open_account": ["开户", "我要开账户", "申请银行账号"],
"order_check": ["查订单", "订单查询", "订单状态"],
"query_bill": ["账单查询", "查询账单", "账单怎么样"],
"order_food": ["订餐", "我要订餐", "订餐服务"],
"query_express": ["查快递", "快递查询", "查询快递状态"],
"query_stock": ["查股票", "股票查询", "查询股票价格"],
"query_traffic": ["查交通", "交通查询", "查询交通状况"],
"query_news": ["查新闻", "新闻查询", "查询新闻"],
"query_calendar": ["查日历", "日历查询", "查询日历"],
"query_map": ["查地图", "地图查询", "查询地图"],
"query_movie": ["查电影", "电影查询", "查询电影"],
"query_music": ["查音乐", "音乐查询", "查询音乐"],
"query_joke": ["查笑话", "笑话查询", "查询笑话"],
"query_poem": ["查诗词", "诗词查询", "查询诗词"],
"query_translation": ["查翻译", "翻译查询", "查询翻译"],
"query_time": ["查时间", "时间查询", "查询时间"],
"order_hotel": ["订酒店", "我要订酒店", "酒店预订","定房间","定大号房间","还有房间吗"],
}
# 计算每个意图分类的短句向量
intent_embeddings = {}
for intent, phrases in intents.items():
intent_embeddings[intent] = model.encode(phrases)
3、使用传统的余弦计算距离
import numpy as np
from sentence_transformers import util
def find_best_intent(user_input):
user_embedding = model.encode(user_input)
best_intent, best_score = None, 0
for intent, embeddings in intent_embeddings.items():
# 计算与每个意图分类的最高相似度
scores = util.cos_sim(user_embedding, embeddings).max()
if scores > best_score:
best_intent, best_score = intent, scores
return best_intent, best_score
# 测试
user_input = "帮我查一下账户的余额"
intent, score = find_best_intent(user_input)
print(f"匹配意图: {intent}, 相似度: {score}")
没有问题匹配意图: open_account, 相似度: 0.7169514298439026
4、使用FAISS
# 使用向量搜索不行,因为向量搜索只能找到最近的向量,不能找到最相似的向量
import faiss
# 构建向量索引
index = faiss.IndexFlatL2(384) # 向量维度为 384
all_embeddings = []
intent_to_ids = {}
# 为每个意图分配 ID
id = 0
for intent, embeddings in intent_embeddings.items():
for embedding in embeddings:
all_embeddings.append(embedding)
intent_to_ids[id] = intent
id += 1
all_embeddings = np.array(all_embeddings).astype('float32')
# 添加到索引
index.add(all_embeddings)
# 用户输入匹配
def find_best_intent_faiss(user_input):
user_embedding = model.encode([user_input]).astype('float32')
distances, ids = index.search(user_embedding, 1) # 检索最近的一个向量
return intent_to_ids[ids[0][0]], distances[0][0]
# 测试
user_input = "我想查余额"
intent, distance = find_best_intent_faiss(user_input)
confidence = 1 - (distance/1.0)
print(f"匹配意图: {intent}, 距离: {distance} 相似度: {confidence}")
匹配意图: open_account, 距离: 3.0383210053788745e-13 相似度: 0.9999999999996961,这个没有问题
user_input = "我想查天气"
intent, distance = find_best_intent_faiss(user_input)
confidence = 1 - (distance/1.0)
print(f"匹配意图: {intent}, 距离: {distance} 相似度: {confidence}")
匹配意图: query_wheather, 距离: 0.2974719703197479 相似度: 0.7025280296802521,感觉也没有问题,下面的问题来了
user_input = "我要定一个房间"
intent, distance = find_best_intent_faiss(user_input)
confidence = 1-distance/1.0
print(f"向量匹配意图: {intent}, 距离: {distance} 相似度: {confidence}")
intent, score = find_best_intent(user_input)
print(f"最近距离匹配意图: {intent}, 相似度: {score}")
向量匹配意图: order_hotel, 距离: 0.49188053607940674 相似度: 0.5081194639205933 最近距离匹配意图: order_hotel, 相似度: 0.7540597915649414
只能查询AI信息
FAISS 默认使用的是 L2(欧几里得)距离,而余弦相似度更适合文本匹配任务,因此出现了这种相似度差异。
为什么 FAISS 的相似度较低?
-
默认 L2 距离计算方式
- FAISS 主要用于高维向量的最近邻搜索,默认使用 L2(欧几里得)距离。
- 由于 L2 距离受到向量长度的影响,可能导致不同长度的短句和长句的匹配效果不佳。
-
余弦相似度更适合文本
- 余弦相似度计算的是向量方向,而非长度,因此对语义匹配更稳定。
- 在 NLP 任务中,余弦相似度往往比 L2 距离更可靠。
5、使用 FAISS 余弦相似度
# 构建向量索引,Inner Product(内积),并归一化向量,使其等效于 余弦相似度
# 在NLP里面余弦相似度和内积相似度是等效的,计算的是两个向量的夹角
index = faiss.IndexFlatIP(384) # 向量维度为 384,
all_embeddings = []
intent_to_ids = {}
# 为每个意图分配 ID
id = 0
for intent, embeddings in intent_embeddings.items():
for embedding in embeddings:
all_embeddings.append(embedding)
intent_to_ids[id] = intent
id += 1
all_embeddings = np.array(all_embeddings).astype('float32')
# 添加到索引
faiss.normalize_L2(embeddings)
index.add(all_embeddings)
向量匹配意图: order_hotel, 距离: 0.7540597319602966 相似度: 0.24594026803970337 最近距离匹配意图: order_hotel, 相似度: 0.7540597915649414
考虑到后续的扩展性,因此后续整个使用FAISS