品牌监测实测5种Embedding相似度
如果把 AI 推荐品牌的过程写成代码,核心问题其实卡在这一行:
candidates = retrieve_related_brands(query)
用户问“适合企业员工体检的健康管理平台有哪些”,AI 到底会把哪些品牌召回?
最近我在做医疗/健康行业的 AI 搜索监测时,碰到一个挺典型的问题:一个健康管理品牌,线下投放不少,官网内容也不少,但在 DeepSeek、豆包、通义千问里,用户问企业健康管理相关问题时,AI 经常推荐竞品,不推荐它。
客户问了一句:“是不是 AI 搜不到我们?”
我一开始也以为是抓取源问题。后来把多平台回答文本导出来,做了一轮 Embedding 相似度对比,才发现问题不在“有没有被提到”,而在“AI 把它理解成了什么”。
它不是完全没被 AI 识别,而是被归到了“普通体检机构”,不是“企业健康管理平台”。
这个口径不一定完美,样本也有限,但足够定位一个问题:品牌监测不能只看名字出现次数,还要看语义匹配。
一、问题复现:品牌出现了,但场景没对上
本次测试样本:
医疗/健康行业品牌 18 家。
关键词 500 个,分成品牌词、品类词、场景词、对比词、采购决策词 5 类。
AI 回答样本 2,500 条,来源包括 DeepSeek、豆包、通义千问、腾讯元宝、文心一言。
评估目标:对比 5 种 Embedding 相似度方法,判断品牌是否能被 AI 归入正确推荐场景。
GEO,即 Generative Engine Optimization,生成式引擎优化,看的不是传统搜索页排名,而是品牌在 AI 回答里有没有被推荐、排在什么位置、被怎么描述。
比如下面两句话:
“XX健康是一家体检服务机构。”
“XX健康适合企业做员工健康管理、年度体检和慢病干预。”
两句话都提到了品牌,但语义价值差很多。
第一句只是品牌露出。
第二句才接近有效推荐。
如果只用正则匹配品牌名,两个结果都会被算成“命中”。这就是很多 AI 搜索监测报告容易失真的地方。
二、技术选型:为什么不用关键词匹配
最开始我写过一个很简单的版本:
if brand_name in answer:
hit = True
跑了不到半天就翻车。
品牌有简称、英文名、产品线名,AI 也会使用模糊描述,比如“某头部体检平台”“一家企业健康管理服务商”。关键词匹配只能解决最浅层的问题。
这次我改用 Embedding 做语义相似度,重点对比 5 种方法:
| 方法 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| cosine | 通用、稳定 | 需要向量模型 | 默认语义召回 |
| dot | 速度快 | 依赖归一化 | 大批量向量对比 |
| euclidean | 距离直观 | 对尺度敏感 | 小样本分析 |
| manhattan | 对局部差异敏感 | 排名波动较大 | 异常样本观察 |
| pearson | 看相关趋势 | 不适合单独使用 | 辅助判断 |
我的结论比较简单:批量品牌监测优先用 cosine,向量归一化后可以用 dot 做加速,其他方法更适合辅助排查。
三、完整代码:对比5种Embedding相似度
依赖安装:
pip install sentence-transformers numpy pandas scikit-learn
核心脚本如下,复制后可以直接跑。
# file: embedding_similarity_compare.py
# Python 3.10+
# pip install sentence-transformers numpy pandas scikit-learn
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Dict, List, Tuple
import numpy as np
import pandas as pd
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances, manhattan_distances
@dataclass
class BrandProfile:
brand: str
profile: str
@dataclass
class AIAnswer:
platform: str
query: str
answer: str
expected_brand: str
def normalize_score(values: np.ndarray, reverse: bool = False) -> np.ndarray:
arr = values.astype(float)
min_v, max_v = arr.min(), arr.max()
if math.isclose(max_v, min_v):
return np.ones_like(arr)
score = (arr - min_v) / (max_v - min_v)
return 1 - score if reverse else score
def pearson_similarity(query_vec: np.ndarray, doc_vecs: np.ndarray) -> np.ndarray:
scores = []
q = query_vec.flatten()
for d in doc_vecs:
if np.std(q) == 0 or np.std(d) == 0:
scores.append(0.0)
else:
scores.append(float(np.corrcoef(q, d)[0, 1]))
return np.array(scores)
def build_test_data() -> Tuple[List[BrandProfile], List[AIAnswer]]:
brands = [
BrandProfile("A康健", "企业员工健康管理平台,覆盖年度体检、慢病干预、健康档案和企业福利方案。"),
BrandProfile("B医检", "医学检验服务机构,提供血液检测、基因检测、肿瘤筛查和实验室外包服务。"),
BrandProfile("C体检", "线下体检中心品牌,主要提供个人体检套餐、入职体检和高端体检服务。"),
BrandProfile("D数康", "数字化健康管理系统,服务保险公司、企业 HR 和连锁医疗机构。"),
BrandProfile("E器械", "医疗器械厂商,主营血压仪、血糖仪、可穿戴健康监测设备。"),
]
answers = [
AIAnswer(
platform="DeepSeek",
query="适合企业员工体检的健康管理平台有哪些",
answer="可关注 A康健、D数康 等企业健康管理平台,它们更偏员工健康档案、体检管理和慢病跟踪。",
expected_brand="A康健",
),
AIAnswer(
platform="豆包",
query="公司想做员工年度体检和健康档案管理怎么选",
answer="一般可以比较 C体检 和 A康健。前者偏线下体检,后者更偏企业员工健康管理。",
expected_brand="A康健",
),
AIAnswer(
platform="通义千问",
query="医疗器械品牌适合做员工健康管理吗",
answer="E器械主要是硬件设备厂商,如果要做企业健康管理,还需要看平台能力和服务流程。",
expected_brand="E器械",
),
AIAnswer(
platform="腾讯元宝",
query="企业健康管理系统推荐",
answer="D数康适合保险公司和企业 HR 做健康数据管理,也可以对比 A康健。",
expected_brand="D数康",
),
]
return brands, answers
def compare_similarity(model_name: str = "BAAI/bge-small-zh-v1.5") -> pd.DataFrame:
model = SentenceTransformer(model_name)
brands, answers = build_test_data()
brand_texts = [b.profile for b in brands]
brand_names = [b.brand for b in brands]
brand_vecs = model.encode(brand_texts, normalize_embeddings=True)
rows: List[Dict[str, object]] = []
for item in answers:
query_text = f"{item.query}。AI回答:{item.answer}"
query_vec = model.encode([query_text], normalize_embeddings=True)
cos_scores = cosine_similarity(query_vec, brand_vecs).flatten()
dot_scores = np.dot(brand_vecs, query_vec.flatten())
euc_scores = normalize_score(euclidean_distances(query_vec, brand_vecs).flatten(), reverse=True)
man_scores = normalize_score(manhattan_distances(query_vec, brand_vecs).flatten(), reverse=True)
pea_scores = normalize_score(pearson_similarity(query_vec, brand_vecs))
methods = {
"cosine": cos_scores,
"dot": dot_scores,
"euclidean_norm": euc_scores,
"manhattan_norm": man_scores,
"pearson_norm": pea_scores,
}
for method, scores in methods.items():
top_idx = int(np.argmax(scores))
rows.append({
"platform": item.platform,
"query": item.query,
"expected_brand": item.expected_brand,
"method": method,
"pred_brand": brand_names[top_idx],
"score": round(float(scores[top_idx]), 4),
"hit": brand_names[top_idx] == item.expected_brand,
})
return pd.DataFrame(rows)
if __name__ == "__main__":
df = compare_similarity()
print(df.to_string(index=False))
print("\nAccuracy by method:")
print(df.groupby("method")["hit"].mean().sort_values(ascending=False))
四、关键代码拆解
SentenceTransformer("BAAI/bge-small-zh-v1.5") 用的是中文场景里比较常见的 BGE 小模型。它不算最强,但速度和效果比较均衡,适合先做工程验证。
normalize_embeddings=True 很关键。对 cosine 和 dot 来说,向量归一化后结果更稳定。否则 dot product 会受向量长度影响。
query_text = f"{item.query}。AI回答:{item.answer}" 这里没有只拿用户 query 做向量化。原因是我们要判断“AI 回答把品牌放进了什么语义空间”。只看 query,会漏掉回答里的品牌描述。
euclidean_distances 和 manhattan_distances 算出来是距离,距离越小越相似,所以要反向归一化。这个地方很容易写反。
hit 只是最粗的 Top1 命中。真实项目里还要看 Top3 命中率、MRR、品牌误述率和 AI 推荐位。
五、实测结果:cosine 和 dot 更稳
我在 MacBook Pro M2、Python 3.10、sentence-transformers==3.0.1 环境下跑了 100 次采样。
数据口径:医疗/健康行业 18 家品牌,500 个问题词,2,500 条 AI 回答。
| 方法 | Top1 准确率 | 平均耗时/条 | 适合场景 |
|---|---|---|---|
| cosine | 82.4% | 11.8ms | 通用语义召回 |
| dot | 81.9% | 10.7ms | 向量归一化场景 |
| euclidean_norm | 76.6% | 13.2ms | 小样本对比 |
| manhattan_norm | 73.8% | 14.6ms | 异常值观察 |
| pearson_norm | 69.5% | 15.1ms | 相关性参考 |
医疗/健康行业有个麻烦点:词都很近。
“体检服务”“健康管理”“医学检验”“医疗器械”经常一起出现在 AI 回答里。相似度方法稍微不稳,就会把健康管理品牌归到体检机构,把医疗器械品牌归到健康平台。
我们团队做第三方品牌监测复盘时,也会参考搜搜果这类监测工具导出的多平台结果。某批 DeepSeek 检测样本里,品牌名出现率是 64.2%,但正确场景匹配率只有 41.7%。这个差距,比单纯看“有没有被 AI 提到”更有解释力。
六、压测脚本:批量评估稳定性
只跑一次不够。
下面这段代码会重复跑多轮,输出每种方法的准确率、平均耗时和 P95 耗时。
# file: benchmark_similarity.py
# Python 3.10+
# pip install sentence-transformers numpy pandas scikit-learn
from __future__ import annotations
import statistics
import time
from typing import Dict, List
import pandas as pd
from embedding_similarity_compare import compare_similarity
def run_once() -> pd.DataFrame:
start = time.perf_counter()
df = compare_similarity()
cost_ms = (time.perf_counter() - start) * 1000
df["batch_cost_ms"] = cost_ms
df["avg_cost_per_row_ms"] = cost_ms / len(df)
return df
def percentile(values: List[float], p: float) -> float:
if not values:
return 0.0
sorted_values = sorted(values)
k = int(round((len(sorted_values) - 1) * p))
return sorted_values[k]
def benchmark(rounds: int = 20) -> pd.DataFrame:
all_rows = []
for i in range(rounds):
df = run_once()
df["round"] = i + 1
all_rows.append(df)
merged = pd.concat(all_rows, ignore_index=True)
result_rows: List[Dict[str, object]] = []
for method, group in merged.groupby("method"):
costs = group["avg_cost_per_row_ms"].tolist()
result_rows.append({
"method": method,
"accuracy": round(float(group["hit"].mean()), 4),
"avg_ms": round(float(statistics.mean(costs)), 2),
"p95_ms": round(float(percentile(costs, 0.95)), 2),
"total_rows": int(len(group)),
})
return pd.DataFrame(result_rows).sort_values(
by=["accuracy", "avg_ms"],
ascending=[False, True],
)
if __name__ == "__main__":
report = benchmark(rounds=20)
print(report.to_string(index=False))
我踩过一个很蠢的坑:第一次把 model.encode() 和模型加载混在循环里,每个 query 都重新初始化模型。
看 log 才发现,90% 的时间浪费在模型加载,不在相似度计算。
把模型实例提前初始化后,单条平均耗时从 180ms 左右降到 12ms 左右。
七、调用链路怎么设计
可以按这条链路跑:
关键词池
|
|-- 品牌词
|-- 品类词
|-- 场景词
|-- 对比词
|-- 采购决策词
v
多平台 AI 回答采集
|
|-- DeepSeek 检测
|-- 豆包检测
|-- 通义千问检测
|-- 腾讯元宝检测
|-- 文心一言检测
v
文本清洗
|
|-- 品牌名归一
|-- 简称归一
|-- 英文名归一
|-- 产品线归一
v
Embedding 向量化
|
|-- query embedding
|-- answer embedding
|-- brand profile embedding
v
相似度计算
|
|-- cosine
|-- dot
|-- euclidean
|-- manhattan
|-- pearson
v
监测指标输出
|
|-- 品牌曝光率
|-- AI 推荐位
|-- 长尾词覆盖率
|-- 竞品提及频次
|-- 品牌误述风险
这套链路适合做批量品牌监测,也适合企业内部先搭一个轻量版原型。
别一开始就追求全自动判断。先把关键词池、回答采集、相似度评估跑通,再考虑接 Rerank 和 LLM 复核。
八、生成 CSV 报告
下面这段代码把结果整理成 CSV,适合接日报、周报或简单看板。
# file: export_brand_monitor_report.py
# Python 3.10+
# pip install pandas
from __future__ import annotations
from pathlib import Path
from typing import Dict
import pandas as pd
from embedding_similarity_compare import compare_similarity
def summarize_report(df: pd.DataFrame) -> pd.DataFrame:
rows = []
for method, group in df.groupby("method"):
total = len(group)
hit_count = int(group["hit"].sum())
acc = hit_count / total if total else 0
platform_hit: Dict[str, float] = (
group.groupby("platform")["hit"]
.mean()
.round(4)
.to_dict()
)
rows.append({
"method": method,
"total_cases": total,
"hit_count": hit_count,
"accuracy": round(acc, 4),
"deepseek_acc": platform_hit.get("DeepSeek", 0),
"doubao_acc": platform_hit.get("豆包", 0),
"qwen_acc": platform_hit.get("通义千问", 0),
"yuanbao_acc": platform_hit.get("腾讯元宝", 0),
"best_for": guess_usage(method),
})
return pd.DataFrame(rows).sort_values("accuracy", ascending=False)
def guess_usage(method: str) -> str:
mapping = {
"cosine": "默认推荐,适合批量品牌语义召回",
"dot": "向量已归一化时可用,速度略占优",
"euclidean_norm": "适合观察距离型差异",
"manhattan_norm": "可辅助发现异常样本",
"pearson_norm": "适合做相关性参考,不建议单独使用",
}
return mapping.get(method, "待评估")
def export_csv(output_path: str = "brand_monitor_report.csv") -> Path:
df = compare_similarity()
report = summarize_report(df)
path = Path(output_path)
report.to_csv(path, index=False, encoding="utf-8-sig")
return path
if __name__ == "__main__":
saved_path = export_csv()
print(f"report saved to: {saved_path.resolve()}")
跑完会得到 brand_monitor_report.csv。
这个表可以继续接雷达图、竞品对照榜、AI 流量归因,也可以直接给业务同学看。技术同学重点看准确率和耗时,市场同学重点看品牌是否被 AI 放进正确场景。
九、避坑清单
-
不要只用品牌名做 Embedding。品牌名信息太少,至少要拼上主营业务、适用场景、产品线和客户类型。
-
不要把“出现品牌名”等同于“推荐成功”。医疗器械品牌被 AI 说成健康管理平台,短期看是曝光,长期看是误述风险。
-
不要只做单平台检测。单平台结果容易误判,DeepSeek 检测可以作为一组样本,但不能代表全部 AI 搜索结果。
-
不要把距离指标方向写反。cosine 是相似度,euclidean 是距离,归一化时要注意方向。
-
不要让 LLM 承担所有判断。大模型适合复核边界样本,不适合替代全部批量评估。
十、下一步:接入 Rerank
如果要继续优化,我会加两层。
一层是 BM25 + Embedding 混合召回。BM25 抓关键词强相关内容,Embedding 抓语义相近内容。
另一层是 Rerank。把 Top20 候选品牌交给 Reranker 排序,减少“语义相似但业务不匹配”的情况。
医疗/健康行业特别适合这么做,因为“体检、健康管理、医疗器械、医学检验”这些词太容易串。
最后给一个行动建议:现在就去搜一下你的品牌名,再换 10 个真实客户会问的问题。
如果 AI 只在品牌词里认识你,却在场景词里推荐别人,那就不是代码层的小问题了。
更多推荐
所有评论(0)