farandal/csirt-img-ml-model
GitHub: farandal/csirt-img-ml-model
基于CLIP视觉语义和FAISS相似度检索的欺诈图像风险评估模型,用于钓鱼邮件、恶意网站截图的快速比对与威胁溯源。
Stars: 0 | Forks: 1
# CSIRT 欺诈图像 ML 模型
针对智利政府 CSIRT(计算机安全事件响应团队)发布的钓鱼邮件、欺诈网站和恶意 SMS 消息图像,基于相似度的欺诈风险评分器。
给定一张图像,该模型会返回一个**欺诈风险评分(0–1)**,以及 CSIRT 数据集中最匹配的已知欺诈案例的**完整事件元数据**。
## 目录
1. [项目概述](#1-project-overview)
2. [数据集](#2-dataset)
3. [技术栈](#3-technology-stack)
4. [ML 架构](#4-ml-architecture)
5. [分步指南:数据集准备](#5-step-by-step-dataset-preparation)
6. [分步指南:训练(构建索引)](#6-step-by-step-training-index-building)
7. [分步指南:预测](#7-step-by-step-prediction)
8. [产物](#8-artifacts)
9. [风险评分解释](#9-risk-score-interpretation)
10. [快速开始](#10-quick-start)
11. [编程 API](#11-programmatic-api)
12. [项目结构](#12-project-structure)
13. [设计决策与局限性](#13-design-decisions-and-limitations)
## 1. 项目概述
该模型旨在为网络安全分析师识别欺诈图像提供支持。该系统没有从头训练二分类器(这需要标注的负样本),而是采用**基于检索的检测**:将每张输入图像与包含 3,764 个已确认欺诈截图的参考库进行比较。图像与已知欺诈在视觉上越相似,其得分就越高。
这种方法具有几个实际优势:
- **无需负标签** — 整个数据集完全由来自 CSIRT 官方警报的已确认欺诈案例组成。
- **设计上即可解释** — 每个评分都有具体匹配的事件作为支撑,并提供警报代码、IoC 指标和源 PDF。
- **无需重新训练神经网络即可更新** — 添加新的欺诈案例只需嵌入新图像并将其插入索引中。
- **零样本泛化** — CLIP 的预训练语义理解能力使模型能够检测在视觉上类似于已知活动的新欺诈模式,即使具体内容有所不同。
## 2. 数据集
### 来源
2019 年至 2024 年间由 **CSIRT del Gobierno de Chile** 发布的 256 份每周网络安全公报(PDF),从 `csirt.gob.cl` 下载。
### 提取过程
脚本 `extract_fraud_images.py`(使用 **PyMuPDF / fitz**)逐页处理每个 PDF:
1. **页眉检测** — 顶部 30pt 内具有全宽横幅的页面被识别为封面并被跳过。
2. **Logo 检测** — 排除 CSIRT logo(始终位于右上角象限,x > 页面宽度的 60%,y < 220pt)。
3. **事件表检测** — 只有右列(x > 200pt)包含字符串 `"Alerta de seguridad"` 的页面才会被视为欺诈事件页面。这过滤掉了摘要页、仅包含 IoC 的页面和建议部分。
4. **图像提取** — 对于每个符合条件的页面,通过 `fitz.Document.extract_image()` 提取嵌入的图像。
5. **文本关联** — 提取每张图像右侧的事件表文本,使用位置块分析,并通过 y 坐标位置接近度与图像进行匹配。
### 数据集统计
| 指标 | 值 |
|---|---|
| 源 PDF 数量 | 256 |
| 包含嵌入欺诈图像的 PDF | 212 |
| 图像/文本对总计 | **3,764** |
| JPEG 图像 | 2,935 |
| PNG 图像 | 829 |
| 事件类别 — 欺诈 (Fraude) | 2,760 |
| 事件类别 — 漏洞 (Vulnerabilidad) | 982 |
| 事件类别 — 入侵尝试 (Intentos de Intrusión) | 19 |
| 事件类型 — 身份伪造 (Falsificación de Identidad) | 1,286 |
| 事件类型 — 钓鱼 (Phishing) | 997 |
| 事件类型 — 开放系统/软件 (Sistema/Software Abierto) | 982 |
| 事件类型 — 恶意软件 (Malware) | 347 |
| 事件类型 — 直接欺诈 (Fraude directo) | 105 |
### 配对文本文件
每张图像 `X.jpeg` 都有一个对应的 `X.txt` 文件,其中包含从 PDF 中图像旁边的表格中提取的结构化事件元数据:
```
Source: 13BCS22-000178-01.pdf | Page: 3 | Image: 13BCS22-000178-01_p03_01 (736x376px)
------------------------------------------------------------
CSIRT alerta campaña de phishing que suplanta al SII
Alerta de seguridad cibernética
2CMV22-00349-01
Clase de alerta
Fraude
Tipo de incidente
Malware
Nivel de riesgo
Alto
TLP
Blanco
Fecha de lanzamiento original
23 de septiembre de 2022
Última revisión
23 de septiembre de 2022
Indicadores de compromiso
Asunto
FACTURA – ERICA AMALIA , Notificacion Giro Folio ...
Correo de salida
dukcapiltapinkab@server.tapinkab.go.id
SHA256
cc83ecc8da9069f2e3be95cee116d722163a3d10...
```
## 3. 技术栈
| 组件 | 库 | 版本 | 作用 |
|---|---|---|---|
| 图像编码器 | **open-clip-torch** | 3.3.0 | CLIP ViT-B/32 — 将图像转换为 512 维语义向量 |
| 相似度索引 | **FAISS** (faiss-cpu) | 1.7.4 | 精确的余弦相似度最近邻搜索 |
| 深度学习运行时 | **PyTorch** | 2.2.2 | 张量操作,模型推理 |
| 图像加载 | **Pillow** | 10.x | 打开 JPEG/PNG 欺诈截图 |
| 数值计算 | **NumPy** | 1.26.x | 嵌入矩阵操作 |
| PDF 解析 | **PyMuPDF (fitz)** | 1.22.x | 从 PDF 提取数据集 |
| 进度条显示 | **tqdm** | 4.x | 训练进度条 |
| Python | **CPython** | 3.9 | 运行时 |
### 为什么选择 CLIP?
CLIP(对比语言-图像预训练,OpenAI 2021)使用从互联网抓取的 4 亿个图像-文本对进行了训练。其视觉编码器学习的是**语义**图像表示,而不是低层级的像素统计。这对欺诈检测至关重要,因为:
- 两封冒充同一家银行的钓鱼电子邮件在视觉上看起来很相似,即使像素内容不同(字体不同、按钮颜色不同、截图裁剪不同)。
- CLIP 知道伪造的 SII(税务局)登录页面在语义上与其他伪造的登录页面相关,即使它们来自不同的活动。
- 它可以泛化到与已知案例具有视觉结构的、未见过的欺诈模式。
### 为什么选择 FAISS IndexFlatIP?
`IndexFlatIP` 执行**精确内积搜索**。当嵌入向量经过 L2 归一化(单位范数)时,内积等于余弦相似度。这提供了一个在 [0, 1] 之间的分数,可以直接解释为语义相似度。没有使用任何近似——每次查询都会与所有 3,764 个向量进行比较,以确保最大准确性。在这个数据集规模下(3,764 × 512 维 float32 = 7.7 MB),精确搜索是瞬间完成的。
## 4. ML 架构
```
Query Image
│
▼
┌───────────────────────────────────┐
│ CLIP Visual Encoder │
│ (ViT-B/32-quickgelu, OpenAI) │
│ │
│ 1. Resize → 224×224 px │
│ 2. Normalise pixels │
│ 3. Patch tokenisation (32×32) │
│ 4. 12-layer Vision Transformer │
│ 5. Project → 512-dim vector │
│ 6. L2-normalise │
└───────────────┬───────────────────┘
│ query embedding (512-dim, unit norm)
▼
┌───────────────────────────────────┐
│ FAISS IndexFlatIP │
│ (3,764 reference embeddings) │
│ │
│ cosine_sim(query, ref_i) │
│ = query · ref_i │
│ (both unit-norm vectors) │
│ │
│ Returns top-k (default 5) │
│ most similar reference images │
└───────────────┬───────────────────┘
│ [(sim_1, idx_1), …, (sim_k, idx_k)]
▼
┌───────────────────────────────────┐
│ Fraud Scorer │
│ │
│ fraud_score = mean(sim_1…sim_k) │
│ risk_level = threshold(score) │
│ │
│ For each match idx_i: │
│ metadata[idx_i] → incident │
│ data from the paired .txt file │
└───────────────────────────────────┘
│
▼
FraudResult
fraud_score : 0.0 – 1.0
risk_level : MINIMAL / LOW / MEDIUM / HIGH / CRITICAL
top_matches : [{similarity, incident_title, alert_code,
clase, tipo, ioc_data, ref_image_path}]
```
## 5. 分步指南:数据集准备
### 第 1 步 — 打开每个 PDF
PyMuPDF 打开 `dataset/csirt_pdf/` 中的每个 PDF(256 个文件)。每个页面会被独立检查。
### 第 2 步 — 验证页面
仅当右列(`x0 > 200pt` 的文本块)包含字符串 `"Alerta de seguridad"` 时,才会处理该页面。此字符串仅出现在 CSIRT 事件表标题中。没有此字符串的页面(摘要仪表板、仅 IoC 列表、建议部分)将被完全跳过。
```
right_column_blocks = [b for b in page.get_text("blocks") if b.x0 > 200]
boundary_ys = [b.y0 for b in right_column_blocks if "Alerta de seguridad" in b.text]
if not boundary_ys:
skip page
```
### 第 3 步 — 提取图像
对于合格页面上的每个图像对象,会应用三个空间过滤器:
| 过滤器 | 条件 | 原因 |
|---|---|---|
| 页眉 | `rect.y0 < 30pt 且 rect.width > 页面宽度的 45%` | 顶部的全宽横幅 |
| Logo | `rect.x0 > 页面宽度的 60% 且 rect.y0 < 220pt` | 右上角的 CSIRT logo |
| 极小 | `rect.width < 40pt 或 rect.height < 30pt` | 不可见的填充/装饰性像素 |
通过所有三个过滤器的图像将通过 `fitz.Document.extract_image()` 提取为原始字节。
### 第 4 步 — 关联事件文本
每个合格的页面可能包含一个或两个事件,每个事件左侧带有一个截图,右侧带有一个元数据表。每张图像对应的文本通过以下方式找到:
1. 收集所有按 `y0`(从上到下)排序的右列文本块。
2. 识别事件边界:包含 `"Alerta de seguridad"` 的块定义了每个事件的开始位置。
3. 从该边界开始收集所有右列文本,直到下一个事件开始。
### 第 5 步 — 写入输出
对于每张提取的图像,都会将一对文件写入 `dataset/csirt_extracted/{pdf_stem}/`:
- `{pdf_stem}_p{page:02d}_{idx:02d}.jpeg` — 欺诈截图
- `{pdf_stem}_p{page:02d}_{idx:02d}.txt` — 关联的事件元数据
## 6. 分步指南:训练(构建索引)
在此语境下,训练是指**构建参考嵌入索引**。没有被优化的可学习参数。CLIP 的权重是冻结的预训练权重;发生变化的是存储在 FAISS 中的欺诈图像表示索引。
### 第 1 步 — 发现图像/文本对
```
pairs = [(img, img.with_suffix(".txt"))
for img in dataset_dir.rglob("*")
if img.suffix in IMAGE_EXTS and img.with_suffix(".txt").exists()]
# 结果:3,764 对
```
### 第 2 步 — 加载 CLIP 模型
```
model, _, preprocess = open_clip.create_model_and_transforms(
"ViT-B-32-quickgelu", pretrained="openai"
)
model.eval()
```
`ViT-B-32-quickgelu` 使用的 QuickGELU 激活函数与 OpenAI 原始权重完全匹配。标准的 `ViT-B-32` 标签具有激活不匹配的问题,会导致 macOS 上出现数值不稳定。该模型永远不会被置于训练模式(从未调用 `model.train()`),也不会计算任何梯度。
### 第 3 步 — 以批大小为 32 嵌入图像
```
for batch in chunks(image_paths, 32):
# 1. Load each image as PIL RGB
images = [Image.open(p).convert("RGB") for p in batch]
# 2. Apply CLIP preprocessing:
# - Resize shortest side to 224px (bicubic interpolation)
# - Centre-crop to 224×224
# - Normalise channels:
# mean = (0.48145466, 0.4578275, 0.40821073)
# std = (0.26862954, 0.26130258, 0.27577711)
tensors = torch.stack([preprocess(img) for img in images]) # (B, 3, 224, 224)
# 3. Forward pass through Vision Transformer — no gradient tracking
with torch.no_grad():
features = model.encode_image(tensors) # (B, 512)
# 4. L2-normalise so that inner product == cosine similarity
features = features / features.norm(dim=-1, keepdim=True)
embeddings.append(features.numpy()) # (B, 512) float32
```
**Vision Transformer 内部结构 (ViT-B/32):**
| 属性 | 值 |
|---|---|
| 输入分辨率 | 224 × 224 RGB |
| Patch 大小 | 32 × 32 px |
| 序列长度 | 49 个 Patch + 1 个 CLS token = 50 个 token |
| 嵌入维度 | 768 |
| Transformer 深度 | 12 层 |
| 注意力头数 | 12 |
| 输出投影 | 768 → 512 维 |
| 总参数量 | ~8700 万 |
### 第 4 步 — 构建 FAISS 索引
```
dim = 512
index = faiss.IndexFlatIP(dim) # exact inner-product (= cosine for unit vectors)
index.add(embeddings_matrix) # shape: (3764, 512), dtype: float32
```
`IndexFlatIP` 逐字存储每个向量,并在查询时执行暴力内积搜索。对于 3,764 个 512 维的向量,在 CPU 上单次查询耗时不到 1 毫秒。
### 第 5 步 — 解析并存储元数据
每个 `.txt` 文件都被解析为 `IncidentMetadata` 数据类:
```
@dataclass
class IncidentMetadata:
source_pdf: str # "13BCS22-000178-01.pdf"
page: int # page number within the source PDF
image_path: str # absolute path to the .jpeg / .png file
txt_path: str # absolute path to this .txt file
title: str # incident headline from the bulletin
alert_code: str # "2CMV22-00349-01"
clase_de_alerta: str # "Fraude" / "Vulnerabilidad" / …
tipo_de_incidente: str # "Phishing" / "Malware" / "Falsificación de Identidad" / …
nivel_de_riesgo: str # "Alto" / "Medio" / "Bajo"
tlp: str # "Blanco" / "Verde" / …
fecha_lanzamiento: str # original publication date
ultima_revision: str # last revision date
indicadores: str # full IoC block: URLs, IPs, SHA-256 hashes, email metadata
```
3,764 条元数据记录被序列化为一个 JSON 数组,其排序与 FAISS 索引完全一致 — `metadata[i]` 始终对应第 `i` 行的嵌入向量。
### 第 6 步 — 保存产物
```
fraud_model/artifacts/
├── index.faiss 7.7 MB FAISS index (3764 × 512 float32 vectors)
├── metadata.json 3.0 MB Parsed incident metadata (JSON array, 3764 objects)
└── embeddings.npy 7.7 MB Raw NumPy array backup (shape: 3764 × 512)
```
总产物占用空间:**18.4 MB**
## 7. 分步指南:预测
### 第 1 步 — 加载产物
在首次实例化时,`FraudDetector` 会加载 FAISS 索引、元数据 JSON,并初始化 CLIP 嵌入器。后续调用将重用已加载的对象。
```
detector = FraudDetector() # default: fraud_model/artifacts/
detector = FraudDetector(artifacts_dir="/path/to/artifacts", top_k=3)
```
### 第 2 步 — 嵌入查询图像
查询图像将经历与训练期间使用的**完全相同的预处理流水线**(调整大小 → 中心裁剪至 224×224 → 通道归一化 → ViT-B/32 前向传播 → L2 归一化),生成一个 512 维的单位向量。
在训练和推理期间接受相同的预处理是必不可少的:FAISS 索引是使用此确切变换计算的嵌入构建的,因此任何偏差都会产生错误的相似度分数。
### 第 3 步 — 最近邻搜索
```
# query_embedding:形状 (1, 512),单位范数
similarities, indices = index.search(query_embedding, k=5)
# similarities:形状 (1, 5) — 余弦相似度值,按最高值优先排序
# indices:形状 (1, 5) — 元数据数组的行索引
```
由于查询向量和参考向量均进行了 L2 归一化,内积等于余弦相似度。值为 1.0 表示查询与已知的欺诈图像(在嵌入空间中)完全相同;值大于 ~0.65 表示与某个欺诈活动在视觉上具有高度相似性。
### 第 4 步 — 计算欺诈分数
```
fraud_score = mean(similarities[0]) # average of top-5 cosine similarities
fraud_score = clamp(fraud_score, 0.0, 1.0)
```
取前 5 个的**平均值**而不是最大值,使得分数更加鲁棒 — 单个偶然的高相似度不能单独将分数膨胀到危险水平。
### 第 5 步 — 分配风险级别
| 分数阈值 | 风险级别 | 解释 |
|---|---|---|
| ≥ 0.80 | **严重 (CRITICAL | 与已知欺诈图像几乎完全相同 |
| ≥ 0.65 | **高 (HIGH)** | 与欺诈活动在视觉上具有高度相似性 |
| ≥ 0.50 | **中 (MEDIUM)** | 与欺诈模式有明显相似之处 |
| ≥ 0.35 | **低 (LOW)** | 存在微弱相似性;需要人工审查 |
| < 0.35 | **极低 (MINIMAL)** | 与所有已知欺诈在视觉上均不相似 |
### 第 6 步 — 返回丰富后的结果
```
FraudResult(
fraud_score = 0.909,
risk_level = "CRITICAL",
top_matches = [
FraudMatch(
rank = 1,
similarity = 1.000,
image_path = "dataset/csirt_extracted/13BCS22-000178-01/…jpeg",
incident = {
"title": "CSIRT alerta campaña de phishing que suplanta al SII",
"alert_code": "2CMV22-00349-01",
"clase_de_alerta": "Fraude",
"tipo_de_incidente": "Malware",
"nivel_de_riesgo": "Alto",
"fecha_lanzamiento": "23 de septiembre de 2022",
"indicadores": "Asunto\n✅ FACTURA – ERICA AMALIA …\nSHA256\ncc83ecc8…",
…
}
),
…
]
)
```
## 8. 产物
运行 `scripts/train.py` 后,三个文件将被写入 `fraud_model/artifacts/`:
**`index.faiss` (7.7 MB)**
二进制 FAISS 索引。包含所有 3,764 个经过 L2 归一化的 512 维 float32 嵌入向量。通过 `faiss.read_index()` 加载。支持亚毫秒级的精确最近邻查询。
**`metadata.json` (3.0 MB)**
包含 3,764 个对象的 JSON 数组。元素 `i` 对应 FAISS 索引中第 `i` 行的嵌入向量。包含 `IncidentMetadata` 的所有字段,包括完整的 IoC 块。
**`embeddings.npy` (7.7 MB)**
形状为 `(3764, 512)`、数据类型为 `float32` 的 NumPy 数组。嵌入矩阵的备份;可用于离线分析、聚类或使用不同的 FAISS 设置重建索引而无需重新运行 CLIP。
## 9. 风险评分解释
欺诈评分是 **CLIP 嵌入空间中的余弦相似度**,而不是经过校准的概率。
- **1.00** — 查询图像与索引中的欺诈图像完全相同(或几乎相同)。当查询的图像本身属于训练集的一部分时会发生这种情况。
- **0.80–0.95** — 同一欺诈活动:被冒充的同一家银行、相同的 SMS 模板、存在轻微视觉差异的相同钓鱼工具包。
- **0.65–0.79** — 相关活动:目标品牌或变体模板不同,但与已知活动共享视觉结构。
- **0.50–0.64** — 结构性相似:布局、配色方案或 UI 元素与欺诈模式相似,但内容存在分歧。
- **< 0.50** — 可能是新型欺诈类型或合法图像。对于真正的钓鱼内容,在纯欺诈图像库中低于 0.35 的分数并不常见。
## 10. 快速开始
### 设置
```
cd csirt-img-ml-model
python3.9 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### 构建数据集(如果尚未完成)
```
# 需要 PyMuPDF:pip install pymupdf
python scripts/extract_fraud_images.py
# 输入:dataset/csirt_pdf/(256 个 PDF)
# 输出:dataset/csirt_extracted/(3,764 对 image+txt)
```
### 训练 — 构建 FAISS 索引
```
python scripts/train.py
# 可选:
# --dataset dataset/csirt_extracted (默认)
# --artifacts fraud_model/artifacts (默认)
# --batch-size 32 (每个 CLIP 批次的图像数量)
# # 运行时间:在 CPU 上约 9 分钟。CLIP 模型(约 340 MB)会在首次运行时自动下载。
```
### 预测
```
# 格式化终端输出
python scripts/predict.py path/to/suspicious_image.jpg
# 限制为前 3 个匹配项
python scripts/predict.py path/to/image.jpg --top-k 3
# 机器可读 JSON
python scripts/predict.py path/to/image.jpg --json
```
## 11. 编程 API
```
from fraud_model import FraudDetector
# 加载一次,即可重复用于多次查询
detector = FraudDetector()
# 或使用自定义路径:
# detector = FraudDetector(artifacts_dir="fraud_model/artifacts", top_k=3)
# 为图像评分 — 接受文件路径 (str/Path) 或 PIL.Image
result = detector.score("suspicious.jpg")
# 顶级结果
print(result.fraud_score) # float 0.0–1.0, e.g. 0.871
print(result.risk_level) # str, e.g. "HIGH"
# 最近邻匹配
for match in result.top_matches:
print(match.rank) # 1, 2, 3, …
print(match.similarity) # e.g. 0.924
print(match.incident["title"]) # "CSIRT alerta campaña de phishing…"
print(match.incident["alert_code"]) # "2CMV22-00349-01"
print(match.incident["clase_de_alerta"]) # "Fraude"
print(match.incident["tipo_de_incidente"]) # "Phishing"
print(match.incident["nivel_de_riesgo"]) # "Alto"
print(match.incident["fecha_lanzamiento"]) # "23 de septiembre de 2022"
print(match.incident["indicadores"]) # full IoC block (URLs, IPs, hashes)
print(match.incident["image_path"]) # path to the reference image
# 序列化为 dict / JSON
import json
print(json.dumps(result.to_dict(), ensure_ascii=False, indent=2))
```
## 12. 项目结构
```
csirt-img-ml-model/
│
├── dataset/
│ ├── csirt_extracted/ # 3,764 image+txt pairs in 212 subdirectories
│ │ ├── 13BCS20-00054-01/
│ │ │ ├── 13BCS20-00054-01_p03_01.jpeg
│ │ │ ├── 13BCS20-00054-01_p03_01.txt
│ │ │ └── …
│ │ └── …
│ ├── csirt_pdf/ # 256 original CSIRT bulletin PDFs
│ └── csirt_pdf_compressed/ # 256 compressed PDF copies
│
├── fraud_model/ # installable Python package
│ ├── __init__.py # public API: FraudDetector, FraudResult, parse_txt
│ ├── embedder.py # FraudImageEmbedder — CLIP ViT-B/32 wrapper
│ ├── metadata.py # parse_txt() — .txt parser → IncidentMetadata
│ ├── scorer.py # FraudDetector — loads index, scores images
│ └── artifacts/ # generated by train.py
│ ├── index.faiss # FAISS similarity index (7.7 MB)
│ ├── metadata.json # incident metadata JSON array (3.0 MB)
│ └── embeddings.npy # raw embedding matrix backup (7.7 MB)
│
├── scripts/
│ ├── compress_pdf.py # Single-PDF compression utility
│ ├── compress_pdfs.py # Batch PDF compression utility
│ ├── extract_fraud_images.py # Step 1: extract images+txt from PDFs
│ ├── train.py # Step 2: build FAISS index from extracted images
│ └── predict.py # Step 3: CLI to score a query image
├── requirements.txt # Python dependencies
└── README.md # this file
```
## 13. 设计决策与局限性
**无负样本。** 该模型无法从其训练数据中获得“绝对不是欺诈”的概念。分数是相对于欺诈库的,而不是绝对概率。在视觉上类似于 CSIRT 文档页面的合法图像得分将高于不相关的图像。在生产环境中,应结合包含合法图像的保留集来校准阈值。
**语言无关性。** CLIP 处理的是视觉特征,而不是 OCR 文本。任何语言的欺诈内容都会受到同等评估,只要其视觉结构类似于已知模式。
**冻结的编码器。** CLIP 权重从未在 CSIRT 数据上进行微调。在没有任何负样本的 3,764 张图像上进行微调会导致过拟合。我们更倾向于使用 CLIP 的零样本泛化能力。
**精确搜索。** `IndexFlatIP` 是精确的(没有近似)。对于这种语料库大小,这是最优的 — 查询时间不到 1 毫秒。如果语料库增长到超过约 100 万张图像,请考虑切换到 `IndexIVFFlat` 或 `IndexHNSWFlat`,以实现亚线性查询时间,但这会略微降低准确性。
**无需完全重新训练即可添加新的欺诈案例。** 新确认的欺诈图像可以增量添加到索引中:
```
from fraud_model.embedder import FraudImageEmbedder
from fraud_model.metadata import parse_txt
import faiss, json, numpy as np
embedder = FraudImageEmbedder()
index = faiss.read_index("fraud_model/artifacts/index.faiss")
metadata = json.load(open("fraud_model/artifacts/metadata.json"))
# 嵌入并添加新图像
new_embeddings = embedder.embed_batch(new_image_paths) # (N, 512)
index.add(new_embeddings)
faiss.write_index(index, "fraud_model/artifacts/index.faiss")
# 追加 metadata
for txt_path in new_txt_paths:
metadata.append(parse_txt(txt_path).to_dict())
json.dump(metadata, open("fraud_model/artifacts/metadata.json", "w"),
ensure_ascii=False, indent=2)
```
**macOS BLAS 冲突。** 在 macOS(Intel)上,PyTorch 和 FAISS 均链接到 AVX2 BLAS 例程。如果在 PyTorch 之前导入 FAISS,相互竞争的初始化会导致 SIGSEGV。此包中的所有模块都在 `faiss` 之前导入 `torch` 以防止此问题。
标签:Apex, CSIRT, 元数据提取, 凭据扫描, 反欺诈, 可解释AI, 向量检索, 图像分类, 威胁情报, 开发者工具, 异常检测, 恶意短信检测, 搜索语句(dork), 机器学习, 欺诈预防, 深度学习, 特征嵌入, 相似度检索, 网络安全, 网络钓鱼检测, 计算机视觉, 进程保护, 逆向工具, 钓鱼网站识别, 隐私保护, 零样本学习