React 入门基础

React 入门基础

作为后端工程师快速入门React的简明指南。本文将用最简单的方式解释React核心概念,并通过实例展示如何构建基础的React应用。

1. React是什么?

React就像是前端的”类/对象”,它让我们可以把UI界面拆分成一个个可重用的组件。如果你熟悉后端开发:

  • React组件 ≈ 类
  • Props ≈ 方法参数
  • State ≈ 类的成员变量

2. 快速开始

2.1 创建项目

不需要复杂配置,一个命令即可创建项目:

1
2
3
4
5
6
# 创建新项目
npx create-react-app my-first-app
cd my-first-app

# 启动开发服务器
npm start

2.2 最简单的React组件

就像写一个简单的类一样:

1
2
3
4
5
6
7
8
9
10
11
// 函数组件(推荐用这种,更简单)
function Welcome() {
return <h1>你好,React</h1>;
}

// 类组件(类似传统面向对象写法)
class Welcome extends React.Component {
render() {
return <h1>你好,React</h1>;
}
}

3. 核心概念解析

3.1 组件与Props(参数传递)

就像函数接收参数一样,React组件接收props:

1
2
3
4
5
6
7
8
9
10
11
// 后端类比:
// function greeting(name) {
// return "Hello, " + name;
// }

function UserGreeting(props) {
return <h1>你好, {props.name}</h1>;
}

// 使用组件
<UserGreeting name="张三" />

3.2 State(状态管理)

类似于类的成员变量,但有特殊的更新方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useState } from 'react';

function Counter() {
// 声明状态变量,类似:
// private int count = 0;
const [count, setCount] = useState(0);

return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>
增加
</button>
</div>
);
}

3.3 简单的表单处理

类似后端接收表单数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function SimpleForm() {
const [message, setMessage] = useState('');

const handleSubmit = (e) => {
e.preventDefault();
// 这里可以发送到后端API
console.log('提交的消息:', message);
};

return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="submit">提交</button>
</form>
);
}

4. 实用示例:待办事项列表

一个完整的小应用示例,包含了核心概念的运用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { useState } from 'react';

function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');

// 添加待办事项
const addTodo = (e) => {
e.preventDefault();
if (input.trim() !== '') {
setTodos([...todos, input.trim()]);
setInput(''); // 清空输入
}
};

// 删除待办事项
const deleteTodo = (index) => {
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
};

return (
<div>
<h2>待办事项</h2>

{/* 添加表单 */}
<form onSubmit={addTodo}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="输入新待办事项"
/>
<button type="submit">添加</button>
</form>

{/* 列表显示 */}
<ul>
{todos.map((todo, index) => (
<li key={index}>
{todo}
<button onClick={() => deleteTodo(index)}>
删除
</button>
</li>
))}
</ul>
</div>
);
}

5. 开发工具推荐

  1. VS Code插件:

    • ES7+ React/Redux/React-Native snippets
    • Prettier - Code formatter
  2. 浏览器插件:

    • React Developer Tools(必装)

6. 调试技巧

  1. 使用console.log查看数据:
1
2
3
4
5
function App() {
const data = { name: "张三" };
console.log('数据:', data); // 在浏览器控制台查看
return <div>{data.name}</div>;
}
  1. React Developer Tools查看组件:
  • 在浏览器中右键 -> 检查
  • 切换到Components标签
  • 可以查看组件的props和state

总结

React的核心就是组件化思维:

  1. 把UI拆分成独立的、可重用的组件
  2. 通过props传递数据(类似函数参数)
  3. 使用state管理组件的内部状态(类似成员变量)
  4. 使用事件处理用户交互(类似后端的路由处理)

掌握这些基础概念,就能开始构建简单的React应用了。建议从小项目开始,逐步熟悉React的开发方式。

7. 进阶概念

7.1 生命周期与Hooks

类似于后端中对象的生命周期管理,React组件也有生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { useEffect } from 'react';

function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);

// 组件挂载和userId更新时执行
// 类似后端中的@PostConstruct或初始化方法
useEffect(() => {
async function fetchUser() {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
}
}
fetchUser();

// 清理函数,类似@PreDestroy
return () => {
// 清理工作,如取消请求
};
}, [userId]); // 依赖项数组,决定何时重新执行

if (error) return <div>错误: {error}</div>;
if (!user) return <div>加载中...</div>;
return <div>用户名: {user.name}</div>;
}

常用Hooks及其用途:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. useState - 状态管理
const [count, setCount] = useState(0);

// 2. useEffect - 副作用处理(API调用、订阅)
useEffect(() => {
document.title = `${count}次点击`;
}, [count]);

// 3. useContext - 上下文管理(类似依赖注入)
const theme = useContext(ThemeContext);

// 4. useRef - 引用DOM或保持值
const inputRef = useRef(null);

// 5. useMemo - 性能优化,缓存计算结果
const expensiveValue = useMemo(() => compute(a, b), [a, b]);

// 6. useCallback - 性能优化,缓存函数
const handleClick = useCallback(() => {
console.log(count);
}, [count]);

7.2 性能优化技巧

  1. 避免不必要的渲染:
1
2
3
4
5
6
7
import { memo } from 'react';

// 只有props变化时才重新渲染
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
// 复杂的渲染逻辑
return <div>{/* ... */}</div>;
});
  1. 大列表优化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { useState, useCallback } from 'react';
import { FixedSizeList } from 'react-window';

function VirtualizedList({ items }) {
const Row = useCallback(({ index, style }) => (
<div style={style}>
Item {items[index]}
</div>
), [items]);

return (
<FixedSizeList
height={400}
width={300}
itemCount={items.length}
itemSize={35}
>
{Row}
</FixedSizeList>
);
}

7.3 错误处理与边界

类似后端的全局异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// 记录错误到日志服务
logErrorToService(error, errorInfo);
}

render() {
if (this.state.hasError) {
return <h1>出错了!</h1>;
}
return this.props.children;
}
}

// 使用错误边界
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>

7.4 状态管理进阶

对于复杂应用,可以使用Context API(类似依赖注入):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { createContext, useContext, useReducer } from 'react';

// 创建上下文
const AppContext = createContext();

// 定义reducer(类似后端的状态机)
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}

// 提供者组件
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}

// 在组件中使用
function Counter() {
const { state, dispatch } = useContext(AppContext);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</div>
);
}

7.5 后端集成最佳实践

  1. API调用封装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// api.js
const BASE_URL = process.env.REACT_APP_API_BASE_URL;

export const api = {
async get(endpoint) {
const response = await fetch(`${BASE_URL}${endpoint}`, {
headers: {
'Authorization': `Bearer ${getToken()}`
}
});
if (!response.ok) throw new Error('API调用失败');
return response.json();
},

async post(endpoint, data) {
const response = await fetch(`${BASE_URL}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify(data)
});
if (!response.ok) throw new Error('API调用失败');
return response.json();
}
};

// 使用自定义Hook封装API调用
function useApi(endpoint) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
async function fetchData() {
try {
const result = await api.get(endpoint);
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData();
}, [endpoint]);

return { data, loading, error };
}
  1. WebSocket实时通信:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function ChatRoom() {
const [messages, setMessages] = useState([]);
const ws = useRef(null);

useEffect(() => {
// 建立WebSocket连接
ws.current = new WebSocket('ws://your-backend/chat');

ws.current.onmessage = (event) => {
const message = JSON.parse(event.data);
setMessages(prev => [...prev, message]);
};

return () => {
ws.current.close();
};
}, []);

const sendMessage = (text) => {
ws.current.send(JSON.stringify({ text }));
};

return (
<div>
{messages.map((msg, i) => (
<div key={i}>{msg.text}</div>
))}
<input onKeyPress={(e) => {
if (e.key === 'Enter') sendMessage(e.target.value);
}} />
</div>
);
}

7.6 部署注意事项

  1. 环境变量配置:
1
2
3
4
5
# .env.development
REACT_APP_API_URL=http://localhost:8080/api

# .env.production
REACT_APP_API_URL=https://api.production.com
  1. 构建优化:
1
2
3
4
5
6
{
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"build": "GENERATE_SOURCEMAP=false react-scripts build"
}
}

8. 视觉动效和交互体验

8.1 滚动动画(Scroll Animation)

你提到的那种随着滚动平滑变化的效果,专业术语叫”滚动动画”或”视差滚动”(Parallax Scrolling)。React可以通过以下几种方式实现:

  1. Intersection Observer API(推荐):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { useEffect, useRef } from 'react';

function ScrollAnimation() {
const elementRef = useRef(null);

useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
// 元素进入视口时添加动画类
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
}
});
},
{ threshold: 0.1 } // 元素出现10%就触发
);

if (elementRef.current) {
observer.observe(elementRef.current);
}

return () => observer.disconnect();
}, []);

return (
<div
ref={elementRef}
className="animate-section"
>
内容会淡入显示
</div>
);
}

// 配套的CSS
const styles = `
.animate-section {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease-out;
}

.fade-in {
opacity: 1;
transform: translateY(0);
}
`;
  1. 使用成熟的动画库:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { motion, useScroll } from "framer-motion";

function ParallaxSection() {
const { scrollYProgress } = useScroll();

return (
<motion.div
style={{
scale: scrollYProgress,
opacity: scrollYProgress
}}
>
随着滚动缩放和淡入
</motion.div>
);
}
  1. 视差滚动效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { useEffect, useState } from 'react';

function ParallaxBackground() {
const [scrollPosition, setScrollPosition] = useState(0);

useEffect(() => {
const handleScroll = () => {
setScrollPosition(window.pageYOffset);
};

window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);

return (
<div className="parallax-container">
<div
className="parallax-bg"
style={{
transform: `translateY(${scrollPosition * 0.5}px)`
}}
>
背景图片
</div>
<div className="content">
前景内容
</div>
</div>
);
}

// 配套的CSS
const styles = `
.parallax-container {
position: relative;
height: 100vh;
overflow: hidden;
}

.parallax-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 120%; /* 稍微大一点,确保滚动时看不到边界 */
background-image: url('your-image.jpg');
background-size: cover;
background-position: center;
will-change: transform; /* 性能优化 */
}

.content {
position: relative;
z-index: 1;
}
`;

8.2 常用动画库推荐

  1. Framer Motion:最流行的React动画库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { motion } from "framer-motion";

function AnimatedCard() {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
卡片内容
</motion.div>
);
}
  1. React Spring:物理引擎动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useSpring, animated } from 'react-spring';

function SpringAnimation() {
const props = useSpring({
from: { opacity: 0, transform: 'translate3d(0,-40px,0)' },
to: { opacity: 1, transform: 'translate3d(0,0,0)' }
});

return (
<animated.div style={props}>
弹簧动画效果
</animated.div>
);
}
  1. AOS (Animate On Scroll):专门用于滚动动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import AOS from 'aos';
import 'aos/dist/aos.css';

function ScrollEffects() {
useEffect(() => {
AOS.init({
duration: 1000,
once: true
});
}, []);

return (
<div
data-aos="fade-up"
data-aos-anchor-placement="top-center"
>
滚动时会向上淡入
</div>
);
}

8.3 性能优化提示

  1. 使用CSS transform和opacity进行动画,而不是改变位置和尺寸:
1
2
3
4
5
6
7
8
9
10
11
/* 好的做法 */
.smooth-animation {
transform: translateX(100px);
opacity: 0.5;
}

/* 避免的做法 */
.janky-animation {
left: 100px; /* 触发重排 */
width: 50%; /* 触发重排 */
}
  1. 使用will-change提示浏览器优化:
1
2
3
.parallax-element {
will-change: transform;
}
  1. 防抖动画触发:
1
2
3
4
5
6
7
8
9
10
11
12
import { debounce } from 'lodash';

function ScrollHandler() {
useEffect(() => {
const handleScroll = debounce(() => {
// 处理滚动动画
}, 16); // 约60fps

window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
}

总结

进阶概念主要围绕:

  1. 组件生命周期管理和Hooks的高级用法
  2. 性能优化(避免重渲染、虚拟列表等)
  3. 错误处理与监控
  4. 状态管理(Context API、Reducer)
  5. 后端集成(API封装、WebSocket)
  6. 部署与优化
  7. 滚动动画和交互效果实现

建议在掌握基础后,逐步深入这些进阶概念,它们对构建大型React应用至关重要。对于想要实现炫酷的滚动效果,可以从简单的Intersection Observer开始,然后逐步尝试更复杂的动画库和视差效果。

Faiss

Faiss

本文介绍Facebook AI开源的向量检索工具Faiss(Facebook AI Similarity Search),包括其基本概念、关键特性和主要使用场景。Faiss专门用于高效的相似性搜索和密集向量聚类,广泛应用于大规模向量检索任务中。

Multi-Vector检索简述

Multi-Vector检索是一种高效的向量搜索策略,一个常见的误解是认为它需要三维张量来存储多个向量,这会使索引变得复杂。实际上,Multi-Vector采用了更简单的方案:

  1. 存储设计:

    • 不使用三维张量 (n_docs, n_chunks, dimension)
    • 而是使用二维张量 (total_chunks, dimension),其中total_chunks = n_docs * n_chunks
    • 通过额外的doc_ids数组记录每个向量属于哪个文档
    • 这样可以直接使用Faiss的标准索引,无需特殊处理
  2. 检索流程:

    • 文档:先切分成多个chunk,每个chunk生成一个向量
    • 查询:同样切分成多个chunk,每个chunk生成一个向量
    • 检索:每个查询向量独立搜索,找到最相似的文档chunk
    • 结果:通过doc_ids合并同一文档的多个匹配结果,保留最高相似度
  3. 优势:

    • 实现简单:复用现有的向量索引能力
    • 存储高效:避免了三维张量的复杂性
    • 检索灵活:支持文档和查询包含不同数量的chunk
    • 效果更好:能捕获文档不同部分的语义信息

基本概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 初始化和基本使用示例
import faiss
import numpy as np

dimension = 128 # 向量维度
n_vectors = 1000 # 向量数量

# 创建随机向量数据
vectors = np.random.random((n_vectors, dimension)).astype('float32')

# 创建索引
index = faiss.IndexFlatL2(dimension) # L2距离的暴力搜索索引
index.add(vectors) # 添加向量到索引中

# 执行搜索
k = 5 # 返回最相似的k个结果
# shape为(1, dimension),表示1个查询向量,每个向量维度为dimension
# 如果要同时查询多个向量,可以用(n, dimension),n为查询向量数量
query_vector = np.random.random((1, dimension)).astype('float32')
distances, indices = index.search(query_vector, k)

主要索引类型

  1. IndexFlatL2:最基础的精确搜索索引

    • 计算真实的L2距离
    • 适用于小规模数据集
    • 无损失但计算成本高
  2. IndexIVFFlat:基于聚类的倒排索引

    • 先进行聚类,再在相关簇中搜索
    • 提供近似搜索,速度更快
    • 可以通过nprobe参数调整精度和速度的平衡
  3. IndexHNSWFlat:基于图的索引

    • 构建层次化的导航图
    • 非常快速的搜索性能
    • 内存占用较大
  4. IndexPQ:乘积量化索引

    • 通过向量压缩节省内存
    • 搜索速度快,但有一定精度损失
    • 适合大规模数据集

核心功能示例

1. IVF索引示例

1
2
3
4
5
6
7
8
9
10
11
12
# 创建IVF索引
nlist = 100 # 聚类中心数量
quantizer = faiss.IndexFlatL2(dimension)
index = faiss.IndexIVFFlat(quantizer, dimension, nlist)

# 训练索引
index.train(vectors)
index.add(vectors)

# 设置搜索参数
index.nprobe = 10 # 搜索时检查的聚类数量
distances, indices = index.search(query_vector, k)

2. 产品量化示例

1
2
3
4
5
6
7
8
9
10
11
# 创建PQ索引
n_bits = 8
n_subvectors = 16 # 将向量分成16个子向量
index = faiss.IndexPQ(dimension, n_subvectors, n_bits)

# 训练和添加向量
index.train(vectors)
index.add(vectors)

# 搜索
distances, indices = index.search(query_vector, k)

3. GPU加速示例

1
2
3
4
5
6
# 将索引转移到GPU
res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)

# 在GPU上执行搜索
distances, indices = gpu_index.search(query_vector, k)

4. Multi-Vector示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# 为文档创建多个向量表示的实际场景
dimension = 128
documents = [
# 文档1:包含3个文本块
[
"这是第一个文档的开头部分",
"这是文档中间的内容",
"这是文档的结尾部分"
],
# 文档2:包含2个文本块
[
"另一个文档的第一部分",
"另一个文档的第二部分"
]
]

# 假设这是文本转向量的函数(实际项目中使用embedding模型)
def text_to_vector(text):
# 这里用随机向量模拟,实际中使用embedding模型
return np.random.random(dimension).astype('float32')

# 为每个文档的每个文本块生成向量
doc_vectors = [] # 存储所有向量
doc_ids = [] # 存储向量对应的文档ID
for doc_id, doc in enumerate(documents):
for text_chunk in doc:
vec = text_to_vector(text_chunk)
doc_vectors.append(vec)
doc_ids.append(doc_id)

# 转换为numpy数组
doc_vectors = np.array(doc_vectors).astype('float32') # shape: (n_total_chunks, dimension)
doc_ids = np.array(doc_ids) # shape: (n_total_chunks,)

# 创建索引
index = faiss.IndexFlatL2(dimension)
index.add(doc_vectors)

# 搜索时的查询也可能需要分块处理
query = [
"查询的第一部分",
"查询的第二部分"
]

# 为查询的每个部分生成向量
query_vectors = []
for query_chunk in query:
vec = text_to_vector(query_chunk)
query_vectors.append(vec)
query_vectors = np.array(query_vectors).astype('float32') # shape: (n_query_chunks, dimension)

# 对每个查询向量进行搜索
k = 10
all_distances = []
all_indices = []
for query_vec in query_vectors:
# faiss需要shape为(1, dimension),因为每次search输入一个查询
distances, indices = index.search(query_vec.reshape(1, -1), k)
all_distances.append(distances[0])
all_indices.append(indices[0])

# 合并多个查询向量的结果
retrieved_docs = {}
for distances, indices in zip(all_distances, all_indices):
for dist, idx in zip(distances, indices):
doc_id = doc_ids[idx]
# 对于每个文档,保留最小距离(最相似)的结果
retrieved_docs = {}
for dist, idx in zip(distances[0], indices[0]):
doc_id = doc_ids[idx]
if doc_id not in retrieved_docs or dist < retrieved_docs[doc_id]:
retrieved_docs[doc_id] = dist

# 按距离排序获取最终的文档列表
final_results = sorted(retrieved_docs.items(), key=lambda x: x[1])[:5]

这个示例展示了如何:

  1. 为每个文档生成多个向量表示
  2. 维护向量和文档ID的对应关系
  3. 搜索后合并同一文档的多个向量结果
  4. 选择每个文档的最佳匹配分数

性能优化技巧

  1. 合适的索引选择

    • 数据量小:使用IndexFlatL2
    • 数据量中等:使用IndexIVFFlat
    • 数据量大:使用IndexHNSWFlat或IndexPQ
    • 内存受限:使用IndexPQ或组合索引
  2. 参数调优

    • IVF中的nlist:通常设置为sqrt(N),N为数据量
    • nprobe:增加可提高召回率,但会降低速度
    • HNSW的M参数:影响图的连接度,通常设为16-64
  3. 批量处理

    • 使用batch操作而不是单条查询
    • 合理设置batch size,避免内存溢出

实际应用场景

  1. 相似图像搜索

    • 将图像转换为特征向量
    • 构建高效的向量索引
    • 实现快速的相似图像检索
  2. 推荐系统

    • 用户和物品的向量表示
    • 快速找到相似用户或物品
    • 实时推荐计算
  3. 文本语义搜索

    • 存储文本的embedding向量
    • 支持语义相似度搜索
    • 集成到RAG系统中

总结

Faiss作为一个高性能的向量检索库,提供了多种索引类型和优化选项,能够满足不同规模和场景下的向量检索需求。通过合理选择索引类型和参数配置,可以在精度和性能之间找到最佳平衡点。在RAG系统中,Faiss通常用于存储和检索文档的向量表示,是实现高效语义搜索的关键组件。

Transformer术语解释

Transformer术语解释

本文针对后端开发者,解释Transformer中的关键术语和概念。

基础概念

1. 分词器(Tokenizer)

1
2
3
4
5
6
7
8
9
10
11
12
# 在后端开发中,我们经常这样分割字符串:
text = "hello world"
words = text.split(" ") # ["hello", "world"]

# 但在NLP中,分词更复杂:
text = "我爱编程"
tokens = tokenizer(text) # ["我", "爱", "编", "程"]

# 为什么要这样做?
# 1. 处理不同语言(中文需要字符级分割)
# 2. 处理特殊词(如URL、表情符号)
# 3. 控制词表大小(通过子词切分)

2. 词嵌入(Embedding)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 在后端开发中,我们用枚举表示类别:
class UserType(Enum):
ADMIN = 0
USER = 1

# 在NLP中,我们用向量表示词:
word_vectors = {
"我": [0.2, -0.5, 0.8, ...], # 512维向量
"爱": [0.7, 0.3, -0.4, ...],
"编程": [-0.1, 0.9, 0.2, ...]
}

# 为什么要用向量?
# 1. 捕捉词的语义(相似的词有相似的向量)
# 2. 可以计算相似度(通过向量点积或余弦相似度)
# 3. 神经网络需要数值输入

3. 编码器(Encoder)和解码器(Decoder)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 在后端开发中,我们经常编解码数据:
# JSON编解码
json_str = json.dumps(data) # 编码
data = json.loads(json_str) # 解码

# Base64编解码
encoded = base64.b64encode(data) # 编码
decoded = base64.b64decode(encoded) # 解码

# Transformer中的编解码:
class Translator:
def translate(self, text):
# 编码:理解输入文本
understanding = self.encoder(text)
# 理解 = {
# "主语": "天气",
# "谓语": "很好",
# "时态": "现在时"
# }

# 解码:生成目标语言
translation = self.decoder(understanding)
# 解码过程 = [
# "The",
# "The weather",
# "The weather is",
# "The weather is good"
# ]

return translation

数学概念

1. Softmax函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 在后端开发中,我们经常计算百分比:
total = sum(values)
percentages = [v/total for v in values]

# Softmax做类似的事,但保证结果更平滑:
def softmax(values):
exp_values = [math.exp(v) for v in values]
total = sum(exp_values)
return [exp/total for exp in exp_values]

# 例如:
scores = [1.0, 2.0, 0.5]
probs = softmax(scores) # [0.24, 0.65, 0.11]

# 特点:
# 1. 结果总和为1
# 2. 保持大小关系
# 3. 突出大的值

2. 层归一化(Layer Normalization)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 在后端处理数据时,我们可能这样标准化:
def normalize(values):
min_val = min(values)
max_val = max(values)
return [(v - min_val)/(max_val - min_val) for v in values]

# 层归一化类似,但用均值和方差:
def layer_norm(values, epsilon=1e-5):
mean = sum(values) / len(values)
variance = sum((x - mean) ** 2 for x in values) / len(values)
return [(x - mean) / math.sqrt(variance + epsilon) for x in values]

# 为什么要归一化?
# 1. 让训练更稳定
# 2. 防止数值太大或太小
# 3. 类似于数据库中的字段标准化

3. 向量点积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 在后端开发中,我们可能计算加权和:
def weighted_sum(values, weights):
return sum(v * w for v, w in zip(values, weights))

# 向量点积就是这样的加权和:
def dot_product(vec1, vec2):
return sum(v1 * v2 for v1, v2 in zip(vec1, vec2))

# 例如,计算两个词的相关度:
word1_vector = [0.2, 0.5, -0.1]
word2_vector = [0.3, 0.4, -0.2]
similarity = dot_product(word1_vector, word2_vector)

# 为什么用点积?
# 1. 可以衡量相似度
# 2. 计算效率高(可以用矩阵运算加速)
# 3. 捕捉向量方向的一致性

深度学习概念

1. 预训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 在后端开发中,我们用配置文件保存设置:
config = {
"db_host": "localhost",
"db_port": 5432,
"cache_size": 1000
}

# 预训练模型类似,但保存的是学习到的知识:
model = {
"vocabulary": {
"我": {"vector": [0.1, 0.2, ...], "freq": 10000},
"爱": {"vector": [0.3, -0.1, ...], "freq": 5000}
},
"weights": {
"layer1": numpy.array([[...], ...]),
"layer2": numpy.array([[...], ...])
}
}

# 为什么用预训练?
# 1. 复用已学习的知识(类似代码复用)
# 2. 节省训练时间和资源
# 3. 利用大规模数据的学习成果

2. 张量(Tensor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 在后端开发中,我们用不同维度的数据结构:
# 0维:标量
count = 42

# 1维:数组/列表
scores = [95, 88, 72]

# 2维:矩阵/表格
matrix = [
[1, 2, 3],
[4, 5, 6]
]

# 张量是更高维的推广:
# 3维张量(批次 x 序列长度 x 特征维度)
batch_data = [
# 第1个样本
[
[0.1, 0.2, 0.3], # 第1个词的特征
[0.4, 0.5, 0.6] # 第2个词的特征
],
# 第2个样本
[
[0.7, 0.8, 0.9],
[0.2, 0.3, 0.4]
]
]

# 为什么用张量?
# 1. 批量处理数据(提高效率)
# 2. 保存多维特征
# 3. GPU优化计算

总结

这些概念虽然来自机器学习领域,但都能找到后端开发中类似的概念:

  1. 分词 ≈ 字符串处理
  2. 词嵌入 ≈ 特征表示
  3. 编码器/解码器 ≈ 序列化/反序列化
  4. Softmax ≈ 百分比计算
  5. 层归一化 ≈ 数据标准化
  6. 张量 ≈ 多维数组

建议学习路线:

  1. 先理解基础概念(用已知的后端概念类比)
  2. 再学习数学概念(关注实际用途)
  3. 最后深入模型细节(按需了解)

Transformer

Transformer

本文从软件工程的角度介绍Transformer架构,通过类比和实例逐步深入理解其工作原理。

从软工角度理解Transformer

想象一个大型会议的同声传译系统:

  1. 源语言讲话 -> 翻译 -> 目标语言
  2. 翻译时需要理解整句话的上下文
  3. 多个翻译同时工作提高效率

Transformer就像这样一个系统:

  • Encoder:理解源语言(输入)
  • Decoder:生成目标语言(输出)
  • Attention机制:理解上下文关系

代码示例

以翻译”I love programming”为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 简化的Transformer使用示例
from transformers import AutoTokenizer, AutoModelForSeq2SeqTransformation

# 1. 加载预训练模型
tokenizer = AutoTokenizer.from_pretrained("t5-small")
model = AutoModelForSeq2SeqTransformation.from_pretrained("t5-small")

# 2. 准备输入
text = "translate English to Chinese: I love programming"
inputs = tokenizer(text, return_tensors="pt")

# 3. 生成翻译
outputs = model.generate(inputs.input_ids)
result = tokenizer.decode(outputs[0])
print(result) # 输出: "我爱编程"

核心概念解析

1. 分词(Tokenization)

类比:代码编译器的词法分析

1
2
3
4
5
# 编译器词法分析
"int main()" -> ["int", "main", "(", ")"]

# Transformer分词
"I love programming" -> ["▁I", "▁love", "▁progra", "mming"]

2. 注意力机制(Attention)

类似IDE中查找变量引用的功能,分析代码中各部分的关联关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 代码依赖分析示例
def analyze_dependencies(code_lines):
attention_scores = {}

# 示例代码行
code_lines = [
"user = User.objects.get(id=1)", # line 1
"orders = user.get_orders()", # line 2
"total = calculate_total(orders)", # line 3
"if total > 1000:", # line 4
" user.set_vip_status(True)" # line 5
]

# 分析每行代码与其他行的关联度(注意力分数)
for i, line in enumerate(code_lines):
scores = {}
for j, other_line in enumerate(code_lines):
# 检查变量复用
score = 0
if "user" in line and "user" in other_line:
score += 0.5
if "orders" in line and "orders" in other_line:
score += 0.5
if "total" in line and "total" in other_line:
score += 0.5
scores[j] = score

attention_scores[i] = scores

return attention_scores

# 分析结果示例:
# line 1 (user定义) 与 line 2和5最相关(使用了user变量)
# line 2 (orders定义) 与 line 3最相关(使用了orders变量)
# line 3和4 (total相关) 相互关联

这个例子展示了注意力机制的核心思想:

  1. 每行代码都会与其他所有行计算关联度
  2. 关联度基于变量的复用情况
  3. 最终每行代码都知道与其他行的关系强弱

Transformer中的注意力机制也是类似原理:

  1. 计算输入序列中每个词与其他词的关联度
  2. 关联度基于词向量的相似度
  3. 帮助模型理解词之间的依赖关系

3. 多头注意力(Multi-Head Attention)

类比代码审查时多人从不同角度并行Review:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 代码审查示例
class CodeReview:
def __init__(self, code):
self.code = code
self.reviewers = [
SecurityReviewer(), # 安全审查员
PerformanceReviewer(), # 性能审查员
StyleReviewer() # 代码风格审查员
]

def parallel_review(self):
review_results = []
# 每个审查员从自己专注的角度进行分析
for reviewer in self.reviewers:
results = reviewer.analyze(self.code)
review_results.append(results)

# 合并所有审查结果
final_report = self.combine_reviews(review_results)
return final_report

# 使用示例
code = """
def process_user_data(user_input):
data = json.loads(user_input) # 安全审查:需要输入验证
result = [] # 性能审查:考虑预分配大小
for item in data: # 风格审查:考虑使用列表推导式
result.append(item * 2)
return result
"""

review = CodeReview(code)
report = review.parallel_review()

这个例子展示了多头注意力的核心思想:

  1. 同一段代码被多个审查员并行分析
  2. 每个审查员关注不同的方面(安全/性能/风格)
  3. 最后将所有意见整合成完整报告

Transformer中的多头注意力也是类似原理:

  1. 同一输入序列被多个”注意力头”并行处理
  2. 每个”头”可以学习关注不同的模式(如语法关系、语义关联等)
  3. 最后将所有”头”的结果组合,得到更全面的理解

实现细节

1. 整体架构

让我们详细看看Transformer的各个组件是如何工作的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class MultiHeadAttention:
def __init__(self, num_heads=8, head_dim=64):
self.heads = num_heads
self.head_dim = head_dim
# 初始化查询(Q)、键(K)、值(V)的权重矩阵
self.W_q = np.random.randn(num_heads, head_dim) # 查询权重
self.W_k = np.random.randn(num_heads, head_dim) # 键权重
self.W_v = np.random.randn(num_heads, head_dim) # 值权重

def attention(self, query, key, value):
# 1. 计算注意力分数
# 类比:计算每个翻译专家对不同单词的关注程度
scores = np.dot(query, key.T) # Q和K的点积

# 2. 归一化分数(使用softmax)
# 类比:将关注度转换为百分比
attention_weights = self.softmax(scores)

# 3. 加权求和
# 类比:根据关注度获取最终理解
output = np.dot(attention_weights, value)
return output

def split_heads(self, x):
# 将输入分给多个注意力头处理
# 类比:将文本分配给多个翻译专家
batch_size = x.shape[0]
return x.reshape(batch_size, self.heads, -1, self.head_dim)

class TransformerLayer:
def __init__(self, dim=512, num_heads=8):
self.attention = MultiHeadAttention(num_heads, dim//num_heads)
self.feed_forward = self.create_ffn(dim)
self.norm1 = LayerNorm()
self.norm2 = LayerNorm()

def create_ffn(self, dim):
# 创建前馈神经网络
# 类比:每个翻译专家的个人思考过程
return {
'layer1': {
'weight': np.random.randn(dim, dim*4),
'bias': np.zeros(dim*4)
},
'layer2': {
'weight': np.random.randn(dim*4, dim),
'bias': np.zeros(dim)
}
}

def forward(self, x):
# 1. 自注意力层
# 类比:理解句子中词语之间的关系
attention_output = self.attention.forward(x)
x = self.norm1(x + attention_output) # 残差连接

# 2. 前馈网络
# 类比:深入思考和处理信息
ffn_output = self.feed_forward_pass(x)
x = self.norm2(x + ffn_output) # 残差连接
return x

class Transformer:
def __init__(self):
# 初始化编码器和解码器层
self.encoder_layers = [TransformerLayer() for _ in range(6)]
self.decoder_layers = [TransformerLayer() for _ in range(6)]
# 词嵌入层
self.embedding = self.create_embedding(vocab_size=50000, dim=512)

def translate(self, text):
"""翻译过程的详细步骤"""
# 1. 词嵌入
# 类比:将每个词转换为计算机理解的形式
tokens = self.tokenize(text) # "I love you" -> [24, 56, 789]
embedded = self.embed_tokens(tokens)

# 2. 位置编码
# 类比:为每个词添加位置信息
positions = self.add_position_encoding(embedded)

# 3. 编码器处理
# 类比:多个翻译专家反复理解原文
encoder_output = positions
for encoder in self.encoder_layers:
# attention的三个输入都是encoder_output
# 因为自注意力是分析同一段文本内部的关系
encoder_output = encoder.forward(encoder_output)

# 4. 解码器处理
# 类比:基于理解生成译文
decoder_output = self.decode(encoder_output)

# 5. 生成翻译
translation = self.generate_text(decoder_output)
return translation

# 使用示例
transformer = Transformer()
text = "The weather is good today"
translation = transformer.translate(text) # 天气很好

"""
核心组件说明:

1. MultiHeadAttention(多头注意力)
- 每个"头"都有自己的QKV权重矩阵
- 可以理解为多个翻译专家并行工作
- 最后合并所有专家的理解

2. TransformerLayer(Transformer层)
- 包含注意力机制和前馈网络
- 通过残差连接保留原始信息
- 使用层归一化确保数据分布稳定

3. Transformer(整体架构)
- 词嵌入:将词转换为向量
- 位置编码:添加位置信息
- 多层编码器和解码器:逐层加深理解
"""

核心机制解释:

  1. 注意力计算过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    def attention_example():
    # 以"I love you"为例
    words = ["I", "love", "you"]

    # 计算注意力分数(相关度)
    attention_matrix = {
    "I": {"I": 1.0, "love": 0.3, "you": 0.2},
    "love": {"I": 0.3, "you": 0.8, "love": 1.0},
    "you": {"I": 0.2, "love": 0.8, "you": 1.0}
    }

    # 注意力加权(得到每个位置的上下文表示)
    context = {
    "I": "主要关注自己,略微关注love",
    "love": "同时关注you和自己",
    "you": "主要关注love和自己"
    }

    return context
  2. 多头机制的作用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def multi_head_example():
    text = "The cat sits on the mat"
    heads = {
    "头1": "关注相邻词的关系", # cat和sits的关系
    "头2": "关注主谓关系", # cat和sits
    "头3": "关注介词关系", # sits和on
    "头4": "关注位置关系", # on和mat
    "头5": "关注冠词和名词的关系", # the和cat/mat
    "头6": "关注语义相关性", # cat和mat可能有相关性
    "头7": "关注句法结构", # 主语+谓语+介词短语
    "头8": "关注长距离依赖" # 第一个the和mat的关系
    }
    return heads
  3. 残差连接的意义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def residual_example():
    # 原始信息
    original = "The cat"

    # 经过注意力层
    attention_output = "关注cat和其他词的关系"

    # 残差连接:保留原始信息
    output = original + attention_output
    # 现在既有原始词义,又有上下文关系

    return output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

### 2. 详细工作流程

让我们通过一个具体的翻译示例来理解Transformer的工作流程:

```python
class DetailedTransformer:
def translate(self, text="The weather is good today"):
# 1. 输入处理
# 分词:将句子拆分成子词
tokens = ["The", "wea", "ther", "is", "good", "to", "day"]
# 位置编码:告诉模型每个词的位置
positions = [0, 1, 2, 3, 4, 5, 6]
# 词嵌入:将词转换为向量
embeddings = self.word_to_vector(tokens) # shape: [7, 512]

# 2. 编码器处理
# 第一层编码器
encoder_1 = {
"自注意力": {
"The": {"weather": 0.7, "good": 0.2}, # The主要关注weather
"weather": {"good": 0.6, "today": 0.3}, # weather关注good和today
"good": {"today": 0.8} # good强烈关注today
},
"前馈网络": "处理每个位置的特征"
}

# 第二层编码器(进一步提取特征)
encoder_2 = {
"自注意力": {
"The weather": 0.9, # 识别出主语
"is good today": 0.8 # 识别出谓语
}
}

# 3. 解码器处理
decoder_output = {
"生成过程": [
"天", # 首先生成"天"
"天气", # 基于上文生成"气"
"天气很", # 继续生成"很"
"天气很好" # 最后生成"好"
],
"注意力示例": {
"天": {"weather": 0.9}, # "天"主要关注"weather"
"气": {"weather": 0.8}, # "气"也主要关注"weather"
"很": {"is": 0.3, "good": 0.7}, # "很"关注"is"和"good"
"好": {"good": 0.9} # "好"主要关注"good"
}
}

return "天气很好"

"""
关键流程说明:
1. 输入处理
- 分词:将句子拆分成小单位,便于处理
- 位置编码:保留词序信息
- 词嵌入:将词转换为计算机可处理的向量

2. 编码器处理
- 自注意力:分析词之间的关联
- 多层处理:逐层提取更深层的特征
- 例如:识别出"The weather"是一个整体

3. 解码器处理
- 自注意力:确保生成的翻译连贯
- 编码器-解码器注意力:关注原文的相关部分
- 一次生成一个词,每次都参考前文
"""

这种设计的优势:

  1. 信息流动清晰:从输入到输出有明确的路径
  2. 特征提取逐层深入:像剥洋葱一样层层理解
  3. 注意力机制灵活:能够动态关注相关信息
  4. 并行计算高效:多头注意力可以并行处理

使用场景与优势

  1. 适用场景
  • 机器翻译
  • 文本生成
  • 代码补全
  • 问答系统
  1. 优势
  • 并行处理:类比多线程
  • 全局视野:类比全局变量作用域
  • 可扩展性:类比微服务架构

总结

Transformer是一个优雅的架构:

  1. 输入输出清晰(类比API设计)
  2. 模块化设计(类比软件工程最佳实践)
  3. 高效并行(类比现代软件架构)

后续学习建议:

  1. 先掌握使用方法
  2. 理解基本概念
  3. 逐步深入内部原理

RAG Retrieval Evaluation Methods

RAG Retrieval Evaluation Methods

本文介绍RAG(Retrieval-Augmented Generation)系统中检索效果的7种评估方法,使用彩虹和光合作用的示例详细说明每种方法的具体计算过程。
RAGAs 评估通常是 每一个gt 和 reference让大模型评估相关性,来计算结果。感觉上是比其它几个要更好一点,当然bag-of-words的recall也相当有用,比如五个文本就能全覆盖query。

评估数据集示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
rainbow_example = {
"query": "What causes rainbows?",
"ground_truth_refs": [
"Rainbows occur when sunlight is refracted through raindrops, splitting light into colors.",
"They appear opposite the sun and require water droplets in the atmosphere.",
"The colors of a rainbow range from red to violet in a specific order."
],
"retrieved_texts": [
"Sunlight bending through rain droplets creates rainbows with colorful bands.",
"Rainbows appear when water droplets and sunlight combine opposite the sun.",
"A rainbow shows colors from red to violet."
]
}

photosynthesis_example = {
"query": "Explain photosynthesis.",
"ground_truth_refs": [
"Photosynthesis is a process where plants convert carbon dioxide and water into glucose.",
"It requires sunlight and chlorophyll to capture energy.",
"Oxygen is released as a byproduct during photosynthesis."
],
"retrieved_texts": [
"Plants use sunlight and chlorophyll to turn CO2 and water into sugar.",
"Oxygen is produced when plants photosynthesize.",
"Photosynthesis transforms sunlight into chemical energy in plants."
]
}

1. 精确匹配评估 (Exact Match Evaluation)

精确匹配通过完全相等比较来评估检索结果。对于每个检索文本,与所有参考文本进行比较,只有完全相同才算匹配成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def exact_match_evaluation(ground_truth_refs, retrieved_texts):
matches = []
for retrieved in retrieved_texts:
# 检查每个检索文本是否与任何参考文本完全匹配(normalized先)
match_found = any(retrieved == ref for ref in ground_truth_refs)
matches.append(match_found)

# 计算准确率
accuracy = sum(matches) / len(retrieved_texts)
return accuracy

# 彩虹示例结果
rainbow_accuracy = exact_match_evaluation(
rainbow_example["ground_truth_refs"],
rainbow_example["retrieved_texts"]
) # 结果:0.0(没有完全相同的文本)

# 解释:虽然内容相似,但由于表达方式不同,没有一个检索结果与参考答案完全相同

2. 相似度评估 (Similarity Evaluation)

使用token-sort-ratio计算文本相似度,该方法会先对文本进行标记化和排序,再计算相似度,从而能够处理词序不同的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from rapidfuzz import fuzz

def similarity_evaluation(ground_truth_refs, retrieved_texts, threshold=80):
similarities = []
for retrieved in retrieved_texts:
# 计算与每个参考文本的相似度,取最高值
max_similarity = max(
fuzz.token_sort_ratio(retrieved, ref)
for ref in ground_truth_refs
)
similarities.append(max_similarity >= threshold)

# 计算高于阈值的比例
score = sum(similarities) / len(similarities)
return score, similarities

# 彩虹示例分析
score, details = similarity_evaluation(
rainbow_example["ground_truth_refs"],
rainbow_example["retrieved_texts"]
)
"""
结果:0.67(2/3的文本相似度超过阈值)
- "Sunlight bending..." vs "Rainbows occur when sunlight..." ≈ 85%
- "Rainbows appear when..." vs "They appear opposite..." ≈ 90%
- "A rainbow shows..." vs "The colors of a rainbow..." ≈ 75%
"""

3. 词袋模型评估 (Bag-of-Words Evaluation)

通过空格分词,计算词级别的召回率和精确率。对每个检索文本,与所有参考文本比较,取最佳匹配分数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def bow_evaluation(ground_truth_refs, retrieved_text):
# 对单个检索文本的评估
retrieved_words = set(retrieved_text.lower().split())

max_recall = 0
max_precision = 0

for ref in ground_truth_refs:
ref_words = set(ref.lower().split())
common_words = retrieved_words & ref_words

recall = len(common_words) / len(ref_words)
precision = len(common_words) / len(retrieved_words)

# 取最佳匹配分数
max_recall = max(max_recall, recall)
max_precision = max(max_precision, precision)

return max_recall, max_precision

# 光合作用示例分析
retrieved = photosynthesis_example["retrieved_texts"][0] # "Plants use sunlight..."
recall, precision = bow_evaluation(
photosynthesis_example["ground_truth_refs"],
retrieved
)
"""
分析第一条检索文本:
参考文本1对比:
- 共同词:plants, sunlight, water, into
- 参考词总数:11,共同词数:4,召回率=0.36
- 检索词总数:10,共同词数:4,精确率=0.40

参考文本2对比:
- 共同词:sunlight
- 参考词总数:8,共同词数:1,召回率=0.13
- 检索词总数:10,共同词数:1,精确率=0.10

参考文本3对比:
- 没有共同词
- 召回率=0,精确率=0

最终取最高分:
召回率=0.36,精确率=0.40
"""

4. Tiktoken评估

使用OpenAI的tiktoken分词器进行token级别评估。此方法考虑了LLM实际使用的分词方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import tiktoken

def tiktoken_evaluation(ground_truth_refs, retrieved_text):
# 初始化分词器
encoder = tiktoken.get_encoding("cl100k_base")

# 对检索文本进行分词
retrieved_tokens = set(encoder.encode(retrieved_text))

max_recall = 0
max_precision = 0

for ref in ground_truth_refs:
# 对参考文本进行分词
ref_tokens = set(encoder.encode(ref))
common_tokens = retrieved_tokens & ref_tokens

recall = len(common_tokens) / len(ref_tokens)
precision = len(common_tokens) / len(retrieved_tokens)

max_recall = max(max_recall, recall)
max_precision = max(max_precision, precision)

return max_recall, max_precision

# 彩虹示例分析
retrieved = rainbow_example["retrieved_texts"][0]
recall, precision = tiktoken_evaluation(
rainbow_example["ground_truth_refs"],
retrieved
)
"""
第一条检索文本的token分析:
"Sunlight bending through rain droplets creates rainbows with colorful bands."
↓ tiktoken分词
[sun, light, bend, ing, through, rain, drop, lets, creates, rainbow, s, with, color, ful, bands]

对比参考文本1:
"Rainbows occur when sunlight is refracted through raindrops, splitting light into colors."
↓ tiktoken分词
[Rainbow, s, occur, when, sun, light, is, refract, ed, through, rain, drop, s, split, ing, light, into, color, s]

共同tokens: {sun, light, through, rain, drop, s, color}
召回率 = 7/19 = 0.37
精确率 = 7/15 = 0.47
"""

5. RAGAS评估

使用LLM进行语义层面的评估,关注上下文的召回率和精确率。以下是RAGAS文档中对各个指标的官方定义:

Context Precision

Context Precision measures the proportion of relevant information in the retrieved context. It penalizes the retrieval of unnecessary information, even if it’s factually correct.

来源:https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/context_precision/

Context Recall

Context Recall evaluates whether all the information required to answer the question is present in the retrieved context.

来源:https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/context_recall/

Context Relevancy

Context Relevancy measures how relevant the retrieved context is to answer the given question.

来源:https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/context_relevancy/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from ragas.metrics import context_recall, context_precision
from ragas import evaluate

def ragas_evaluation(query, ground_truth_refs, retrieved_texts):
# 初始化评估器
evaluator = evaluate(
metrics=[context_recall(), context_precision()]
)

# 准备评估数据
eval_data = {
"question": [query],
"contexts": [retrieved_texts],
"ground_truths": [ground_truth_refs]
}

# 执行评估
scores = evaluator(eval_data)
return scores

# 光合作用示例评估
scores = ragas_evaluation(
photosynthesis_example["query"],
photosynthesis_example["ground_truth_refs"],
photosynthesis_example["retrieved_texts"]
)
"""
RAGAS会使用LLM分析:
1. 检索文本是否包含回答问题所需的关键信息
2. 检索文本的相关性和完整性
3. 返回context_recall和context_precision分数
4. 逐个比对 retrieved context 和 reference context
对每个 retrieved_contexts 里的 chunk,构建 prompt,询问 LLM:“这个 chunk 是否包含 reference context 中回答该 user_input 所需的重要信息?”
可以是简单的相关性判断,也可以细化为信息覆盖度。
LLM 给出相关性分数
LLM 输出:该检索 context 是否“命中”了 reference 的关键信息(通常是二元判断 relevant/not relevant 或数值分数)。
计算 precision
precision = 被判为 relevant 的 retrieved context 数 / 检索 context 总数


对于光合作用示例:
- 关键概念匹配:化学反应物、能量来源、产物都被覆盖
- 表达方式不同但语义相近
- 典型分数:context_recall=0.85, context_precision=0.90
"""

6. 嵌入相似度评估 (Embedding Similarity Evaluation)

使用预训练模型将文本转换为向量,通过余弦相似度评估语义相似性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from sentence_transformers import SentenceTransformer
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

def embedding_evaluation(ground_truth_refs, retrieved_texts, threshold=0.8):
# 加载模型
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

# 编码文本
ref_embeddings = model.encode(ground_truth_refs)
retrieved_embeddings = model.encode(retrieved_texts)

scores = []
for ret_emb in retrieved_embeddings:
# 计算与每个参考文本的相似度
similarities = cosine_similarity(
ret_emb.reshape(1, -1),
ref_embeddings
)[0]
# 取最高相似度
max_similarity = np.max(similarities)
scores.append(max_similarity >= threshold)

return sum(scores) / len(scores), scores

# 彩虹示例评估
score, details = embedding_evaluation(
rainbow_example["ground_truth_refs"],
rainbow_example["retrieved_texts"]
)
"""
详细分析:
1. "Sunlight bending..." vs 参考文本集:
- 与"Rainbows occur..." 相似度: 0.86
- 语义关键点匹配:光线折射、彩虹形成

2. "Rainbows appear..." vs 参考文本集:
- 与"They appear opposite..." 相似度: 0.92
- 语义关键点匹配:位置条件完全对应

3. "A rainbow shows..." vs 参考文本集:
- 与"The colors..." 相似度: 0.89
- 语义关键点匹配:颜色序列描述

最终分数:1.0(所有文本都找到了高相似度匹配)
"""

7. 重复内容评估 (Duplication Evaluation)

检测检索结果中的内容重复,确保返回的信息具有多样性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from rapidfuzz import fuzz

def duplication_evaluation(retrieved_texts, threshold=80):
duplicates = []

for i in range(len(retrieved_texts)):
for j in range(i + 1, len(retrieved_texts)):
similarity = fuzz.ratio(
retrieved_texts[i],
retrieved_texts[j]
)
if similarity > threshold:
duplicates.append({
'text1': retrieved_texts[i],
'text2': retrieved_texts[j],
'similarity': similarity
})

return duplicates

# 分析两个示例
rainbow_duplicates = duplication_evaluation(
rainbow_example["retrieved_texts"]
)
photosynthesis_duplicates = duplication_evaluation(
photosynthesis_example["retrieved_texts"]
)
"""
彩虹示例分析:
- 三段文本分别描述不同方面(形成原理/位置条件/颜色特征)
- 未检测到显著重复内容
- 重复检测分数都低于阈值

光合作用示例分析:
- 文本1和文本3在描述能量转换方面有部分重叠
- 相似度: 75%(低于阈值)
- 其他组合相似度更低

结论:两个示例的检索结果都具有良好的多样性
"""

总结

每种评估方法的特点和适用场景:

  1. 精确匹配:最严格,适用于需要精确答案的场景,但可能过于苛刻。
  2. 相似度评估:能处理表达差异,但保持语义相同的情况。
  3. 词袋模型:关注词级别匹配,忽略词序,适合评估主题相关性。
  4. Tiktoken评估:使用与LLM相同的分词方式,更准确反映模型理解。
  5. RAGAS评估:提供语义层面的深度评估,但依赖LLM服务。
  6. 嵌入相似度:能够捕捉深层语义关系,适合复杂文本比较。
  7. 重复检测:确保检索结果的多样性,避免冗余信息。

实际应用中,可以根据需求组合多种评估方法,以获得全面的评估结果。每种方法都提供了不同角度的洞察,帮助优化RAG系统的检索效果。

RAG Start MileStone

RAG Project Optimization

Current Status

The chat experience is not satisfactory.

Objective

Therefore, this project mainly needs to optimize the RAG experience.

OAuth and HTTP

用户代理与认证机制

大多数用户代理是浏览器(用作发送请求的代理)

认证机制

HTTP中定义了多种认证机制,用于保护资源并验证用户身份。以下是一些常见的认证机制:

  • 基本认证(Basic Authentication): 最简单的认证机制之一,用户提供用户名和密码。不建议在非加密的连接中使用,因为凭据以明文形式传输。

  • 摘要认证(Digest Authentication): 通过挑战和响应的方式工作。服务器发送一个挑战给客户端,客户端使用密码等信息计算响应,以证明其身份。

  • Bearer 认证: 用于OAuth 2.0授权的一种认证机制。在请求头中使用Bearer令牌,以证明请求的合法性。

更多认证机制信息请参考 Mozilla 开发者网络文档

OAuth 2.0

OAuth 2.0是一种开放标准,用于授权第三方应用访问用户资源,而无需分享用户凭据。其中一种常见的认证机制是Bearer Token认证,用于在OAuth 2.0授权流程中传递访问令牌。

了解更多OAuth 2.0的信息,请访问 OAuth 2.0 文档

链接资源

Build一场

发现build失败,根据报错找到文件;
某个文件所属的目录在package.json里找不到(和原来的连接目录不一致)
原来是npm install以后删除了该依赖,但是自动链接到了其他地方。
这个时候删除nodemodule,npm ci(持续集成)。

为项目加权限

  1. nestjs的passport不理解,其实是http的basic协议,默认传入的参数
  2. middleWare模块引用了A模块,而主模块引用middleware模块时会提示缺少A模块,因为加载时机,所以需要在主模块里也引入A模块;
  3. 只有浏览器默认会帮你把response里对cookie的要求配置好,而且要同域,那正常来说就带个token;
    无论是cookie——token
    还是返回个token设置有效时期
    还是给个api key设置个有效时期
    或者是 basic(简单传入用户名密码)验证;本质都差不多,不设置有效期就一样。

有关于export

export * from ‘[filePath]’
tsconfig.json中会将它自动和某个@开头的路径映射,方便引入

算是一个小的module概念

泛型的方式去读取 存储的key value值,方便取出特定的值来