前言
在构建现代AI应用时,向量搜索已经成为一项核心技术,尤其是在处理语义理解、相似性匹配等任务时。LangGraph提供了强大的向量搜索功能,可以帮助我们有效地存储和搜索嵌入向量。本文将深入探讨LangGraph存储模块的向量搜索功能,通过实际示例展示如何实现语义相似性搜索。
什么是向量搜索?
向量搜索是一种基于向量空间模型的搜索技术,它将文本、图像等数据转换为高维向量,然后通过计算向量之间的相似度来找到最相似的项目。在AI应用中,向量搜索常用于:
- 语义搜索:根据文本的语义含义而非精确匹配进行搜索
- 相似内容推荐:推荐与用户兴趣相似的内容
- 重复内容检测:识别重复或高度相似的内容
- 图像和音频检索:基于内容而非元数据进行媒体检索
向量搜索的工作原理
向量搜索的基本原理如下:
- 文本或图像等数据被转换为高维向量
- 每个向量都代表了数据的语义信息
- 当用户输入查询时,查询也被转换为向量
- 存储中的所有向量与查询向量进行相似度计算
- 返回与查询最相似的向量对应的项目
向量搜索的数学原理
向量搜索的数学原理基于余弦相似度(cosine similarity)。余弦相似度的公式如下:
其中:
- 和 是两个向量
- 是向量的点积(dot product)
- 和 是向量的范数(magnitude)
余弦相似度的范围是[-1, 1],越接近1表示越相似。
示例功能概述
我们将创建两个向量搜索系统,全面展示向量搜索技术:
-
基础向量搜索示例:使用LangGraph的存储模块实现向量搜索
- 创建支持向量存储的内存实例
- 向存储中添加带嵌入向量的文档
- 手动计算向量相似度并执行搜索
-
专业向量数据库示例:使用Chroma向量数据库实现高效的向量搜索
- 创建Chroma客户端和集合
- 向集合中添加文档、嵌入向量和元数据
- 执行向量搜索和带元数据过滤的向量搜索
- 分析和显示搜索结果
完整代码实现
让我们先来看完整的代码实现,包括基础向量搜索和Chroma向量数据库两个示例:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""LangGraph 存储向量搜索操作示例
本示例展示了 LangGraph 存储模块的向量搜索功能,包括:
1. 创建支持向量搜索的存储实例
2. 向存储中添加带嵌入向量的项目
3. 使用Chroma向量数据库进行高效向量搜索
"""
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import Item
import numpy as np
import datetime
import chromadb
from chromadb.utils import embedding_functions
def vector_search_operations():
"""演示 LangGraph 存储的向量搜索操作"""
# 创建一个内存存储实例
# InMemoryStore 是一个基于内存的存储实现
store = InMemoryStore()
# 准备嵌入向量
# 在实际应用中,这些向量通常由嵌入模型生成
# 这里为了简化示例,我们手动创建一些向量
vector1 = np.array([0.1, 0.2, 0.3, 0.4, 0.5])
vector2 = np.array([0.2, 0.3, 0.4, 0.5, 0.6])
vector3 = np.array([0.8, 0.7, 0.6, 0.5, 0.4])
# 准备要存储的带向量的项目
# Item 对象的 value 字典中可以包含向量数据
# 通常我们将向量存储在名为 "embedding" 的键下
item1 = Item(
value={
"text": "人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。",
"category": "technology",
"embedding": vector1.tolist() # 将 numpy 数组转换为列表以存储
},
key="article_1",
namespace=("documents", "articles"),
created_at=datetime.datetime.now(),
updated_at=datetime.datetime.now()
)
item2 = Item(
value={
"text": "机器学习是人工智能的一个分支,它是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、计算复杂性理论等多门学科。",
"category": "technology",
"embedding": vector2.tolist()
},
key="article_2",
namespace=("documents", "articles"),
created_at=datetime.datetime.now(),
updated_at=datetime.datetime.now()
)
item3 = Item(
value={
"text": "自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。",
"category": "technology",
"embedding": vector3.tolist()
},
key="article_3",
namespace=("documents", "articles"),
created_at=datetime.datetime.now(),
updated_at=datetime.datetime.now()
)
# 执行 Put 操作,将带向量的项目添加到存储中
store.put(key=item1.key, value=item1.value, namespace=item1.namespace)
store.put(key=item2.key, value=item2.value, namespace=item2.namespace)
store.put(key=item3.key, value=item3.value, namespace=item3.namespace)
print("已成功添加 3 个带向量的项目到存储中")
# 准备查询向量
# 这个向量代表我们要搜索的内容的嵌入表示
query_vector = np.array([0.15, 0.25, 0.35, 0.45, 0.55]).tolist()
# 尝试执行向量搜索操作
print("尝试执行向量搜索...")
try:
# 获取所有存储的项目,然后手动执行向量相似性计算
# 这是一个替代方案,用于演示向量搜索的概念
print("我们直接获取所有项目并手动计算相似度:")
# 手动获取所有文章项目
article_1 = store.get(key='article_1', namespace=('documents', 'articles'))
article_2 = store.get(key='article_2', namespace=('documents', 'articles'))
article_3 = store.get(key='article_3', namespace=('documents', 'articles'))
articles = [article_1, article_2, article_3]
valid_articles = [article for article in articles if article is not None]
# 计算每个文章与查询向量的相似度
# 解释下向量的相似度的数学原理
# 向量的相似度可以用点积(dot product)来计算
# 点积的结果除以两个向量的范数(magnitude)的乘积,得到余弦相似度
# 余弦相似度的范围是[-1, 1],越接近1表示越相似
# 我们可以使用numpy的dot函数计算点积,linalg.norm函数计算范数
similarities = []
for article in valid_articles:
if 'embedding' in article.value:
item_vector = np.array(article.value['embedding'])
query_np_vector = np.array(query_vector)
similarity = np.dot(item_vector, query_np_vector) / (
np.linalg.norm(item_vector) * np.linalg.norm(query_np_vector)
) if np.linalg.norm(item_vector) > 0 and np.linalg.norm(query_np_vector) > 0 else 0
similarities.append((article, similarity))
# 按相似度排序并显示前2个结果
similarities.sort(key=lambda x: x[1], reverse=True)
print("向量搜索结果(最相似的 2 个项目):")
for i, (article, similarity) in enumerate(similarities[:2], 1):
print(f"{i}. 键: {article.key}, 相似度: {similarity:.4f}")
print(f" 文本: {article.value['text'][:100]}...")
except Exception as e:
print(f"执行向量搜索时出错: {e}")
def chroma_vector_search_example():
"""演示如何使用Chroma向量数据库进行向量搜索"""
print("=== 使用Chroma向量数据库进行向量搜索 ===")
try:
# 创建Chroma客户端和集合
print("初始化Chroma客户端...")
# 使用内存模式的Chroma(也可以指定持久化路径)
client = chromadb.Client()
# 创建一个集合,用于存储我们的文档和向量
collection = client.create_collection(
name="ai_articles",
metadata={"description": "AI相关文章的向量集合"}
)
# 准备文档数据
documents = [
"人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。",
"机器学习是人工智能的一个分支,它是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、计算复杂性理论等多门学科。",
"自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。"
]
# 为文档准备ID和元数据
ids = ["article_1", "article_2", "article_3"]
metadatas = [
{"category": "technology", "topic": "artificial_intelligence"},
{"category": "technology", "topic": "machine_learning"},
{"category": "technology", "topic": "nlp"}
]
# 生成嵌入向量
# 这里使用Chroma内置的sentence-transformers嵌入函数
# 在实际应用中会使用更专业的嵌入模型,比如OpenAi或者阿里云的嵌入模型
print("生成文档嵌入向量...")
default_ef = embedding_functions.DefaultEmbeddingFunction()
embeddings = default_ef(documents)
# 向Chroma添加文档、向量和元数据
print("向Chroma添加文档和向量...")
collection.add(
documents=documents,
embeddings=embeddings,
metadatas=metadatas,
ids=ids
)
print(f"已成功添加 {len(documents)} 个文档到Chroma向量数据库")
# 执行向量搜索
print("执行向量搜索...")
query = "什么是人工智能的分支学科?"
print(f"查询: {query}")
# 为查询生成嵌入向量
query_embedding = default_ef([query])[0]
# 搜索最相似的文档
results = collection.query(
query_embeddings=[query_embedding],
n_results=2, # 返回前2个最相似的结果
include=["documents", "distances", "metadatas"] # 包含额外信息
)
# 显示搜索结果
print("Chroma向量搜索结果(最相似的 2 个文档):")
for i in range(len(results["ids"][0])):
doc_id = results["ids"][0][i]
distance = results["distances"][0][i]
# Chroma返回的是距离,我们可以转换为相似度得分
similarity_score = 1 / (1 + distance) # 简单的距离转相似度公式
document = results["documents"][0][i]
metadata = results["metadatas"][0][i]
print(f"{i+1}. 文档ID: {doc_id}")
print(f" 相似度得分: {similarity_score:.4f}")
print(f" 文本: {document[:100]}...")
print(f" 元数据: {metadata}")
print()
# 演示带元数据过滤的向量搜索
print("执行带元数据过滤的向量搜索...")
filtered_results = collection.query(
query_embeddings=[query_embedding],
n_results=2,
where={"topic": {"$in": ["machine_learning", "nlp"]}} # 只搜索特定主题的文档
)
print("带元数据过滤的搜索结果:")
for i in range(len(filtered_results["ids"][0])):
doc_id = filtered_results["ids"][0][i]
document = filtered_results["documents"][0][i]
distance = filtered_results["distances"][0][i]
similarity_score = 1 / (1 + distance)
print(f"{i+1}. 文档ID: {doc_id}, 相似度: {similarity_score:.4f}")
print(f" 文本: {document[:100]}...")
except Exception as e:
print(f"使用Chroma时出错: {e}")
def main():
"""运行所有向量搜索示例"""
print("======= LangGraph 向量搜索功能示例 =======")
# 运行基础向量搜索示例
vector_search_operations()
# 运行Chroma向量数据库示例
chroma_vector_search_example()
print("======= 向量搜索示例运行完成 =======")
if __name__ == "__main__":
main()
代码解析
1. 导入必要的模块
from langgraph.store.memory import InMemoryStore
from langgraph.store.base import Item
import numpy as np
import datetime
import chromadb
from chromadb.utils import embedding_functions
在这个示例中,我们导入了:
InMemoryStore:LangGraph的基于内存的存储实现Item:表示存储中的一个项目numpy:用于向量计算datetime:用于处理时间戳chromadb:Chroma向量数据库客户端embedding_functions:Chroma的嵌入函数工具
2. 基础向量搜索实现
创建存储实例
# 创建一个内存存储实例
# InMemoryStore 是一个基于内存的存储实现
store = InMemoryStore()
这行代码创建了一个InMemoryStore实例,用于存储我们的文档和向量。
准备嵌入向量
# 准备嵌入向量
# 在实际应用中,这些向量通常由嵌入模型生成
# 这里为了简化示例,我们手动创建一些向量
vector1 = np.array([0.1, 0.2, 0.3, 0.4, 0.5])
vector2 = np.array([0.2, 0.3, 0.4, 0.5, 0.6])
vector3 = np.array([0.8, 0.7, 0.6, 0.5, 0.4])
在实际应用中,这些向量通常由嵌入模型(如OpenAI的text-embedding-ada-002、阿里的text-embedding、LangChain的嵌入模型等)生成。为了简化示例,我们手动创建了一些向量。
准备带向量的项目
item1 = Item(
value={
"text": "人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。",
"category": "technology",
"embedding": vector1.tolist() # 将numpy数组转换为列表以存储
},
key="article_1",
namespace=("documents", "articles"),
created_at=datetime.datetime.now(),
updated_at=datetime.datetime.now()
)
我们创建了包含文本内容、分类和嵌入向量的项目。注意,我们将numpy数组转换为列表(使用tolist()方法)以便于存储。
添加项目到存储
# 执行 Put 操作,将带向量的项目添加到存储中
store.put(key=item1.key, value=item1.value, namespace=item1.namespace)
store.put(key=item2.key, value=item2.value, namespace=item2.namespace)
store.put(key=item3.key, value=item3.value, namespace=item3.namespace)
我们使用put方法将带向量的项目添加到存储中,传入键、值和命名空间参数。
手动实现向量搜索
方便演示,我们手动实现了向量搜索的核心逻辑:
# 获取所有存储的项目,然后手动执行向量相似性计算
# 这是一个替代方案,用于演示向量搜索的概念
print("我们直接获取所有项目并手动计算相似度:")
# 手动获取所有文章项目
article_1 = store.get(key='article_1', namespace=('documents', 'articles'))
article_2 = store.get(key='article_2', namespace=('documents', 'articles'))
article_3 = store.get(key='article_3', namespace=('documents', 'articles'))
# 计算每个文章与查询向量的相似度
# 解释下向量的相似度的数学原理
# 向量的相似度可以用点积(dot product)来计算
# 点积的结果除以两个向量的范数(magnitude)的乘积,得到余弦相似度
# 余弦相似度的范围是[-1, 1],越接近1表示越相似
# 我们可以使用numpy的dot函数计算点积,linalg.norm函数计算范数
similarities = []
for article in valid_articles:
if 'embedding' in article.value:
item_vector = np.array(article.value['embedding'])
query_np_vector = np.array(query_vector)
similarity = np.dot(item_vector, query_np_vector) / (
np.linalg.norm(item_vector) * np.linalg.norm(query_np_vector)
) if np.linalg.norm(item_vector) > 0 and np.linalg.norm(query_np_vector) > 0 else 0
similarities.append((article, similarity))
# 按相似度排序并显示前2个结果
similarities.sort(key=lambda x: x[1], reverse=True)
这个实现包括三个关键步骤:获取所有文档、计算余弦相似度、按相似度排序。
3. Chroma向量数据库实现
创建Chroma客户端和集合
# 创建Chroma客户端和集合
print("初始化Chroma客户端...")
# 使用内存模式的Chroma(也可以指定持久化路径)
client = chromadb.Client()
# 创建一个集合,用于存储我们的文档和向量
collection = client.create_collection(
name="ai_articles",
metadata={"description": "AI相关文章的向量集合"}
)
这部分代码创建了一个Chroma客户端实例,并在其中创建了一个名为"ai_articles"的集合,用于存储我们的文档、向量和元数据。
准备文档数据
# 准备文档数据
documents = [
"人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。",
"机器学习是人工智能的一个分支,它是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、计算复杂性理论等多门学科。",
"自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。"
]
# 为文档准备ID和元数据
ids = ["article_1", "article_2", "article_3"]
metadatas = [
{"category": "technology", "topic": "artificial_intelligence"},
{"category": "technology", "topic": "machine_learning"},
{"category": "technology", "topic": "nlp"}
]
我们准备了文档内容、唯一标识符和元数据,用于在Chroma中存储和检索文档。
生成嵌入向量
# 生成嵌入向量
# 这里使用Chroma内置的sentence-transformers嵌入函数
# 在实际应用中,你可能会使用更专业的嵌入模型
print("生成文档嵌入向量...")
default_ef = embedding_functions.DefaultEmbeddingFunction()
embeddings = default_ef(documents)
我们使用Chroma内置的嵌入函数为文档生成向量表示。在实际应用中,你也可以使用其他嵌入模型。
向Chroma添加文档
# 向Chroma添加文档、向量和元数据
print("向Chroma添加文档和向量...")
collection.add(
documents=documents,
embeddings=embeddings,
metadatas=metadatas,
ids=ids
)
我们使用add方法将文档、向量、元数据和ID一起添加到Chroma集合中。
执行向量搜索
# 执行向量搜索
print("执行向量搜索...")
query = "什么是人工智能的分支学科?"
print(f"查询: {query}")
# 为查询生成嵌入向量
query_embedding = default_ef([query])[0]
# 搜索最相似的文档
results = collection.query(
query_embeddings=[query_embedding],
n_results=2, # 返回前2个最相似的结果
include=["documents", "distances", "metadatas"] # 包含额外信息
)
这部分代码执行了向量搜索操作:首先为查询文本生成嵌入向量,然后使用query方法在集合中搜索最相似的文档。我们指定返回前2个最相似的结果,并包含文档内容、距离和元数据信息。
带元数据过滤的向量搜索
# 演示带元数据过滤的向量搜索
print("执行带元数据过滤的向量搜索...")
filtered_results = collection.query(
query_embeddings=[query_embedding],
n_results=2,
where={"topic": {"$in": ["machine_learning", "nlp"]}} # 只搜索特定主题的文档
)
Chroma还支持带元数据过滤的向量搜索。在这个示例中,我们只搜索主题为"machine_learning"或"nlp"的文档,这展示了Chroma的高级功能。
item_vector = np.array(article.value['embedding'])
query_np_vector = np.array(query_vector)
if np.linalg.norm(item_vector) > 0 and np.linalg.norm(query_np_vector) > 0:
similarity = np.dot(item_vector, query_np_vector) / (
np.linalg.norm(item_vector) * np.linalg.norm(query_np_vector)
)
similarities.append((article, similarity))
# 3. 按相似度排序并显示结果
similarities.sort(key=lambda x: x[1], reverse=True)
这个实现包括三个关键步骤:获取所有文档、计算相似度、按相似度排序。
执行结果分析
执行上述代码,你会看到类似以下的输出:
======= LangGraph 向量搜索功能示例 =======
已成功添加 3 个带向量的项目到存储中
尝试执行向量搜索...
我们直接获取所有项目并手动计算相似度:
向量搜索结果(最相似的 2 个项目):
1. 键: article_2, 相似度: 0.9990
文本: 机器学习是人工智能的一个分支,它是一门多领域交叉学科,涉及概率论、统计学、
逼近论、凸分析、计算复杂性理论等多门学科。...
2. 键: article_1, 相似度: 0.9984
文本: 人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系
统的一门新的技术科学。...
=== 使用Chroma向量数据库进行向量搜索 ===
初始化Chroma客户端...
生成文档嵌入向量...
向Chroma添加文档和向量...
已成功添加 3 个文档到Chroma向量数据库
执行向量搜索...
查询: 什么是人工智能的分支学科?
Chroma向量搜索结果(最相似的 2 个文档):
1. 文档ID: article_1
相似度得分: 0.7244
文本: 人工智能是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系
统的一门新的技术科学。...
元数据: {'topic': 'artificial_intelligence', 'category': 'technology'}
2. 文档ID: article_3
相似度得分: 0.7203
文本: 自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人
与计算机之间用自然语言进行有效通信的各种理论和方法。...
元数据: {'topic': 'nlp', 'category': 'technology'}
执行带元数据过滤的向量搜索...
带元数据过滤的搜索结果:
1. 文档ID: article_3, 相似度: 0.7203
1. 文档ID: article_3, 相似度: 0.7203
文本: 自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人
与计算机之间用自然语言进行有效通信的各种理论和方法。...
2. 文档ID: article_2, 相似度: 0.6876
文本: 机器学习是人工智能的一个分支,它是一门多领域交叉学科,涉及概率论、统计学、逼近论
、凸分析、计算复杂性理论等多门学科。...
======= 向量搜索示例运行完成 =======
这个输出展示了两个向量搜索实现的结果:
基础向量搜索结果
- 语义相似性:搜索结果基于向量相似度而非关键词匹配(基于手工设置的向量值)
- 相似度排序:结果按相似度得分排序,最相似的项目排在前面
- 相关内容识别:能够识别与查询向量最相关的内容
Chroma向量数据库结果
- 专业向量搜索:Chroma提供了更专业、更高效的向量搜索功能
- 丰富的结果信息:返回了文档ID、相似度得分、文本内容和元数据
- 元数据过滤:成功演示了带元数据过滤的向量搜索功能,只返回符合特定条件的文档
- 结果解释性:提供了更多上下文信息,便于理解搜索结果
向量搜索的优势
通过上面的示例,我们可以看到向量搜索的几个主要优势:
- 语义理解:能够理解文本的语义含义,而不仅仅是关键词匹配
- 模糊匹配:即使查询与文档没有共同的关键词,也能找到相关内容
- 多模态支持:可以处理文本、图像、音频等多种数据类型
- 高效检索:对于大规模数据集,向量搜索通常比传统搜索方法更高效
- 个性化推荐:可以基于用户兴趣向量进行个性化推荐
优化
在实际应用中,你可以对我们的示例进行以下优化:
基础向量搜索优化
-
使用专业的嵌入模型:在实际应用中,使用高质量的嵌入模型(如OpenAI的text-embedding-ada-002、Hugging Face的嵌入模型等)来生成更准确的向量表示
from langchain_openai import OpenAIEmbeddings # 初始化嵌入模型 embeddings = OpenAIEmbeddings(model="text-embedding-ada-002") # 为文本生成嵌入向量 document_vector = embeddings.embed_query("要嵌入的文本") -
批量处理:对于大量文档,使用批量处理来提高效率
-
添加元数据过滤:结合元数据过滤和向量搜索来获得更精确的结果
# 在实际应用中,你可以结合元数据过滤和向量搜索 filtered_results = [] for article, similarity in similarities: if article.value.get('category') == 'technology': filtered_results.append((article, similarity))
实际应用场景
向量搜索在很多实际应用场景中都非常有用,特别是结合专业向量数据库如Chroma后,可以实现更复杂的功能:
基础应用场景
-
语义搜索引擎:构建能够理解用户查询意图的搜索引擎,超越传统关键词匹配
-
推荐系统:基于用户兴趣和内容相似性推荐相关内容,提高推荐的准确性和相关性
-
问答系统:快速找到与用户问题最相关的答案,提升问答系统的响应质量
-
图像检索:基于图像内容而非元数据进行检索,实现"以图搜图"功能
-
异常检测:识别与正常模式不同的异常数据,应用于金融 fraud detection、系统监控等领域
-
多模态检索:结合文本、图像、音频等多种模态数据进行联合检索
-
个性化搜索:根据用户历史行为和偏好,提供个性化的搜索结果
-
知识图谱增强:将向量搜索与知识图谱结合,提供更丰富的上下文信息
-
实时推荐系统:为大规模用户提供低延迟的实时推荐服务
-
跨语言检索:实现不同语言之间的内容检索,突破语言障碍
-
复杂过滤与聚合:结合元数据过滤和向量相似度,实现复杂的查询需求
-
智能内容组织:自动将相似内容分组和分类,便于内容管理和发现
总结
向量搜索是现代AI应用中的一项核心技术,它通过将数据转换为向量表示,然后计算向量之间的相似度来实现语义理解和相似性匹配。在本文中,我们通过两个完整的示例,全面展示了向量搜索的实现方式:
-
基础向量搜索实现:使用LangGraph的存储模块和numpy手动实现向量搜索,帮助我们理解向量搜索的核心原理和算法
-
专业向量数据库实现:使用Chroma向量数据库实现高效的向量搜索,展示了专业向量数据库的强大功能和优势
这两种实现方式各有优缺点:基础实现适合学习和小型项目,而专业向量数据库则更适合生产环境和大规模应用。通过本文的学习,你已经掌握了向量搜索的基本原理和实现方法,可以根据自己的需求选择合适的方案。
在实际应用中,向量搜索技术可以广泛应用于语义搜索、推荐系统、问答系统等多个领域,为AI应用提供强大的语义理解和相似性匹配能力。
如果你觉得这篇文章对你有帮助,请点赞、收藏并关注我,以获取更多关于LangGraph的教程和技巧!