微软的 GraphRAG 实现最近获得了极大的关注。在上一篇博文中,我讨论了如何构建图形,并探讨了研究论文中强调的一些创新方面。从高层次来看,GraphRAG 库的输入是包含各种信息的源文件。这些文档使用大语言模型(LLM)进行处理,以提取文档中出现的实体及其关系的结构化信息。提取的结构化信息随后被用于构建知识图谱。
构建知识图谱后,GraphRAG 库将图谱算法(特别是莱顿社区检测算法)与 LLM 提示相结合,生成知识图谱中实体和关系社区的自然语言摘要。在这篇文章中,我们将从 GraphRAG 库中获取输出,将其存储在 Neo4j 中,然后使用 LangChain 和 LlamaIndex 协调框架直接从 Neo4j 设置检索器。您可以在 GitHub 上访问代码和 GraphRAG 输出[1],从而跳过 GraphRAG 提取过程。
1. 数据集
本博文中的数据集是查尔斯-狄更斯的《圣诞颂歌》,可通过古腾堡计划[2]免费获取。我们选择这本书作为源文件,是因为它在介绍性文件中得到了强调,使我们能够毫不费力地进行提取。
2. 图形构建
尽管可以跳过图形提取部分,但我们还是要谈谈我认为最重要的几个配置选项。例如,图提取可能会非常耗费Token和成本。因此,使用 gpt-4o-mini 这样相对便宜但性能良好的 LLM 测试提取是合理的。正如这篇博文所述,gpt-4-turbo 可以显著降低成本,同时保持良好的准确性。
GRAPHRAG_LLM_MODEL=gpt-4o-mini
最重要的配置是我们要提取的实体类型。默认情况下,会提取组织、人员、事件和地理信息。
GRAPHRAG_ENTITY_EXTRACTION_ENTITY_TYPES=organization,person,event,geo
这些默认实体类型可能对一本书很有用,但请务必根据您在特定用例中要处理的文档域对它们进行相应更改。另一个重要配置是最大提取值。作者发现,LLM 并不能一次提取所有可用信息,我们也分别进行了验证。
拾取配置允许 LLM 执行多次提取。在上图中,我们可以清楚地看到,在执行多次提取(拾取)时,我们能提取出更多的信息。多次提取需要大量令牌,因此使用 gpt-4o-mini 这样的廉价模型有助于降低成本。
GRAPHRAG_ENTITY_EXTRACTION_MAX_GLEANINGS=1
此外,默认情况下不提取索赔或协变量信息。您可以通过设置 GRAPHRAG_CLAIM_EXTRACTION_ENABLED 配置来启用它。
GRAPHRAG_CLAIM_EXTRACTION_ENABLED=False
译者注:其实这个在微软GraphRAG中没啥用,最后查询并没有使用。
并非所有结构化信息都能一次性提取出来,这似乎是一个经常出现的问题。因此,我们在这里也提供了拾取配置选项。同样有趣但我还没来得及深入研究的是提示调整部分。Prompt微调是可选的,但我们非常鼓励这样做,因为它可以提高准确性。配置设置完成后,我们就可以按照说明运行图形提取管道了,具体步骤如下。
提取管道会执行上图中所有的蓝色步骤。回顾我之前的博文,了解有关图构建和社区汇总的更多信息。微软GraphRAG 库的图提取管道的输出是一组 parquet 文件,如 Dulce 行动示例所示。这些 parquet 文件可以轻松导入 Neo4j 图形数据库,用于下游分析、可视化和检索。我们可以使用免费的云 Aura 实例,也可以建立本地 Neo4j 环境。我的朋友迈克尔-亨格(Michael Hunger)完成了将 parquet 文件导入 Neo4j 的大部分工作。在这篇博文中,我们将跳过导入的解释,但它包括从五六个 CSV 文件中导入和构建知识图谱。如果你想了解更多关于 CSV 导入的信息,可以查看 Neo4j Graph Academy 课程。导入代码和 GraphRAG 输出示例可在Jupyter 笔记本[3]上下载。导入完成后,我们可以打开 Neo4j 浏览器来验证和可视化导入的部分图表。
3. 图表分析
在开始实施检索器之前,我们先进行一个简单的图形分析,以熟悉提取的数据。我们首先要定义数据库连接和一个执行 Cypher 语句(图形数据库查询语言)并输出 Pandas DataFrame 的函数。
NEO4J_URI="bolt://localhost" NEO4J_USERNAME="neo4j" NEO4J_PASSWORD="password" driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USERNAME, NEO4J_PASSWORD)) def db_query(cypher: str, params: Dict[str, Any] = {}) -> pd.DataFrame: """Executes a Cypher statement and returns a DataFrame""" return driver.execute_query( cypher, parameters_=params, result_transformer_=Result.to_df )
在进行图形提取时,我们使用的块大小为 300。此后,作者将默认的块大小改为 1200。我们可以使用下面的Cypher语句来验证块大小。
db_query( "MATCH (n:__Chunk__) RETURN n.n_tokens as token_count, count(*) AS count" ) # token_count count # 300 230 # 155 1
230 块有 300 个Token,而最后一块只有 155 个Token。现在让我们检查一个实体及其描述的示例。
db_query( "MATCH (n:__Entity__) RETURN n.name AS name, n.description AS description LIMIT 1" )
古腾堡计划 "似乎在书中的某个地方有所描述,可能是在开头。我们可以观察到描述是如何捕捉到比实体名称更详细、更复杂的信息的,GraphRAG 论文引入了实体名称,以便从文本中保留更复杂、更细微的数据。我们也来看看示例关系。
db_query( "MATCH ()-[n:RELATED]->() RETURN n.description AS description LIMIT 5" )
微软GraphRAG 不仅仅能提取实体间的简单关系类型,还能捕捉详细的关系描述。与简单的关系类型相比,这种功能可以捕捉到更多细微的信息。我们还可以检查单个社区及其生成的描述。
db_query(""" MATCH (n:__Community__) RETURN n.title AS title, n.summary AS summary, n.full_content AS full_content LIMIT 1 """)
一个社区有标题、摘要和使用 LLM 生成的完整内容。我没有看到作者在检索时是使用完整内容还是只使用摘要,但我们可以在两者之间做出选择。我们可以观察到完整内容中的引文,这些引文指向信息来源的实体和关系。有趣的是,如果引文过长,LLM 有时会对其进行修剪,比如下面的例子。
[Data: Entities (11, 177); Relationships (25, 159, 20, 29, +more)]
没有办法扩展 "+more "符号,所以这是LLM处理长引文的一种有趣方法。现在我们来评估一些分布情况。首先,我们将检查从文本块中提取的实体计数的分布情况。
entity_df = db_query( """ MATCH (d:__Chunk__) RETURN count {(d)-[:HAS_ENTITY]->()} AS entity_count """ ) # Plot distribution plt.figure(figsize=(10, 6)) sns.histplot(entity_df['entity_count'], kde=True, bins=15, color='skyblue') plt.axvline(entity_df['entity_count'].mean(), color='red', linestyle='dashed', linewidth=1) plt.axvline(entity_df['entity_count'].median(), color='green', linestyle='dashed', linewidth=1) plt.xlabel('Entity Count', fontsize=12) plt.ylabel('Frequency', fontsize=12) plt.title('Distribution of Entity Count', fontsize=15) plt.legend({'Mean': entity_df['entity_count'].mean(), 'Median': entity_df['entity_count'].median()}) plt.show()
请记住,文本块有 300 个Token。因此,提取的实体数量相对较少,每个文本块平均约有三个实体。提取时没有进行任何拾取(单次提取)。如果我们增加拾取次数,看看分布情况会很有趣。接下来,我们将评估节点度分布。节点度是一个节点的关系数。
degree_dist_df = db_query( """ MATCH (e:__Entity__) RETURN count {(e)-[:RELATED]-()} AS node_degree """ ) # Calculate mean and median mean_degree = np.mean(degree_dist_df['node_degree']) percentiles = np.percentile(degree_dist_df['node_degree'], [25, 50, 75, 90]) # Create a histogram with a logarithmic scale plt.figure(figsize=(12, 6)) sns.histplot(degree_dist_df['node_degree'], bins=50, kde=False, color='blue') # Use a logarithmic scale for the x-axis plt.yscale('log') # Adding labels and title plt.xlabel('Node Degree') plt.ylabel('Count (log scale)') plt.title('Node Degree Distribution') # Add mean, median, and percentile lines plt.axvline(mean_degree, color='red', linestyle='dashed', linewidth=1, label=f'Mean: {mean_degree:.2f}') plt.axvline(percentiles[0], color='purple', linestyle='dashed', linewidth=1, label=f'25th Percentile: {percentiles[0]:.2f}') plt.axvline(percentiles[1], color='orange', linestyle='dashed', linewidth=1, label=f'50th Percentile: {percentiles[1]:.2f}') plt.axvline(percentiles[2], color='yellow', linestyle='dashed', linewidth=1, label=f'75th Percentile: {percentiles[2]:.2f}') plt.axvline(percentiles[3], color='brown', linestyle='dashed', linewidth=1, label=f'90th Percentile: {percentiles[3]:.2f}') # Add legend plt.legend() # Show the plot plt.show()
现实世界中的大多数网络都遵循幂律节点度分布,大多数节点的节点度相对较小,而一些重要节点的节点度很大。虽然我们的图很小,但节点度却遵循幂律。如果能确定哪个实体有 120 个关系(与 43% 的实体相连),那将会非常有趣。
db_query(""" MATCH (n:__Entity__) RETURN n.name AS name, count{(n)-[:RELATED]-()} AS degree ORDER BY degree DESC LIMIT 5""")
我们可以毫不犹豫地认为,Scrooge 就是这本书的主角。我还大胆猜测,Ebenezer Scrooge 和 Scrooge 其实是同一个实体,但由于微软GraphRAG 缺乏实体解析步骤,所以它们没有合并。这也表明,分析和清理数据是减少噪音信息的重要步骤,因为古腾堡计划有 13 种关系,尽管它们并不是图书故事的一部分。
最后,我们将检查每个等级的群落规模分布情况。
community_data = db_query(""" MATCH (n:__Community__) RETURN n.level AS level, count{(n)-[:IN_COMMUNITY]-()} AS members """) stats = community_data.groupby('level').agg( min_members=('members', 'min'), max_members=('members', 'max'), median_members=('members', 'median'), avg_members=('members', 'mean'), num_communities=('members', 'count'), total_members=('members', 'sum') ).reset_index() # Create box plot plt.figure(figsize=(10, 6)) sns.boxplot(x='level', y='members', data=community_data, palette='viridis') plt.xlabel('Level') plt.ylabel('Members') # Add statistical annotations for i in range(stats.shape[0]): level = stats['level'][i] max_val = stats['max_members'][i] text = (f"num: {stats['num_communities'][i]}\n" f"all_members: {stats['total_members'][i]}\n" f"min: {stats['min_members'][i]}\n" f"max: {stats['max_members'][i]}\n" f"med: {stats['median_members'][i]}\n" f"avg: {stats['avg_members'][i]:.2f}") plt.text(level, 85, text, horizontalalignment='center', fontsize=9) plt.show()
莱顿算法确定了三个级别的社区,其中级别越高的社区平均规模越大。不过,有些技术细节我并不清楚,因为如果检查 all_members 计数,就会发现每个级别的所有节点数都不一样,尽管理论上应该是一样的。另外,如果社区在更高级别上合并,为什么 0 级有 19 个社区,而 1 级有 22 个呢?作者在这里做了一些优化和技巧,我还没有时间详细探讨。
4. 实施检索
在本博文的最后一部分,我们将讨论GraphRAG 中指定的本地和全局检索器。检索器将与 LangChain 和 LlamaIndex 一起实现和集成。
4.1 本地检索
本地检索器首先使用矢量搜索来识别相关节点,然后收集链接信息并将其注入 LLM 提示。
虽然这个图看起来很复杂,但其实很容易实现。我们首先使用基于实体描述文本嵌入的向量相似性搜索来识别相关实体。一旦确定了相关实体,我们就可以遍历相关的文本块、关系、社区摘要等。在 LangChain 和 LlamaIndex 中,使用向量相似性搜索然后遍历整个图的模式可以通过检索查询功能轻松实现。
首先,我们需要配置向量索引。
`index_name = "entity" db_query( """ CREATE VECTOR INDEX """ + index_name + """ IF NOT EXISTS FOR (e:__Entity__) ON e.description_embedding OPTIONS {indexConfig: { `vector.dimensions`: 1536, `vector.similarity_function`: 'cosine' }} """ ) `
我们还将计算并存储社区权重,它被定义为社区中实体出现的不同文本块的数量。
`db_query( """ MATCH (n:`__Community__`)<-[:IN_COMMUNITY]-()<-[:HAS_ENTITY]-(c) WITH n, count(distinct c) AS chunkCount SET n.weight = chunkCount""" ) `
每个部分的候选项(文本单元、社区报告…)数量是可配置的。虽然原始实现中基于Token数的过滤略微复杂,但我们在此将其简化。我根据默认配置值开发了以下简化的顶级候选过滤值。
topChunks = 3 topCommunities = 3 topOutsideRels = 10 topInsideRels = 10 topEntities = 10
我们将从 LangChain 的实现开始。我们唯一需要定义的是检索查询(retrieval_query),它涉及的内容较多。
lc_retrieval_query = """ WITH collect(node) as nodes // Entity - Text Unit Mapping WITH collect { UNWIND nodes as n MATCH (n)<-[:HAS_ENTITY]->(c:__Chunk__) WITH c, count(distinct n) as freq RETURN c.text AS chunkText ORDER BY freq DESC LIMIT $topChunks } AS text_mapping, // Entity - Report Mapping collect { UNWIND nodes as n MATCH (n)-[:IN_COMMUNITY]->(c:__Community__) WITH c, c.rank as rank, c.weight AS weight RETURN c.summary ORDER BY rank, weight DESC LIMIT $topCommunities } AS report_mapping, // Outside Relationships collect { UNWIND nodes as n MATCH (n)-[r:RELATED]-(m) WHERE NOT m IN nodes RETURN r.description AS descriptionText ORDER BY r.rank, r.weight DESC LIMIT $topOutsideRels } as outsideRels, // Inside Relationships collect { UNWIND nodes as n MATCH (n)-[r:RELATED]-(m) WHERE m IN nodes RETURN r.description AS descriptionText ORDER BY r.rank, r.weight DESC LIMIT $topInsideRels } as insideRels, // Entities description collect { UNWIND nodes as n RETURN n.description AS descriptionText } as entities // We don't have covariates or claims here RETURN {Chunks: text_mapping, Reports: report_mapping, Relationships: outsideRels + insideRels, Entities: entities} AS text, 1.0 AS score, {} AS metadata """ lc_vector = Neo4jVector.from_existing_index( OpenAIEmbeddings(), url=NEO4J_URI, username=NEO4J_USERNAME, password=NEO4J_PASSWORD, index_name=index_name, retrieval_query=lc_retrieval_query )
该 Cypher 查询对一组节点执行多种分析操作,以提取和整理相关文本数据:
1.实体-文本单元映射:对于每个节点,查询会识别链接的文本块(__Chunk__
),根据与每个文本块相关的不同节点的数量对它们进行聚合,并根据频率对它们进行排序。最重要的文本块以 text_mapping
的形式返回。
2.实体-报告映射:对于每个节点,查询会找到相关的社区(__社区__
),并根据排名和权重返回排名靠前的社区摘要。
3.外部关系:本节提取相关实体(m
)不属于初始节点集的关系(RELATED
)描述。这些关系会进行排序,并仅限于最重要的外部关系。
4.内部关系:与外部关系类似,但这次只考虑两个实体都在初始节点集内的关系。
5.实体描述:只需收集初始集合中每个节点的描述。
最后,查询会将收集到的数据合并成一个结构化的结果,其中包括数据块、报告、内部和外部关系以及实体描述,还有一个默认分数和一个空元数据对象。您可以选择删除某些检索部分,以测试它们对结果的影响。
现在,你可以使用以下代码运行检索器:
docs = lc_vector.similarity_search( "What do you know about Cratchitt family?", k=topEntities, params={ "topChunks": topChunks, "topCommunities": topCommunities, "topOutsideRels": topOutsideRels, "topInsideRels": topInsideRels, }, ) # print(docs[0].page_content)
同样的检索模式也可以用 LlamaIndex 来实现。对于 LlamaIndex,我们首先需要为节点添加元数据,这样矢量索引才能正常工作。如果没有在相关节点中添加默认元数据,矢量索引将返回错误。
# https://github.com/run-llama/llama_index/blob/main/llama-index-core/llama_index/core/vector_stores/utils.py#L32 from llama_index.core.schema import TextNode from llama_index.core.vector_stores.utils import node_to_metadata_dict content = node_to_metadata_dict(TextNode(), remove_text=True, flat_metadata=False) db_query( """ MATCH (e:__Entity__) SET e += $content""", {"content": content}, )
同样,我们可以使用 LlamaIndex 中的 retrieval_query 功能来定义检索器。与 LangChain 不同的是,我们将使用 f-string 而不是查询参数来传递顶级候选过滤器参数。
retrieval_query = f""" WITH collect(node) as nodes // Entity - Text Unit Mapping WITH nodes, collect {{ UNWIND nodes as n MATCH (n)<-[:HAS_ENTITY]->(c:__Chunk__) WITH c, count(distinct n) as freq RETURN c.text AS chunkText ORDER BY freq DESC LIMIT {topChunks} }} AS text_mapping, // Entity - Report Mapping collect {{ UNWIND nodes as n MATCH (n)-[:IN_COMMUNITY]->(c:__Community__) WITH c, c.rank as rank, c.weight AS weight RETURN c.summary ORDER BY rank, weight DESC LIMIT {topCommunities} }} AS report_mapping, // Outside Relationships collect {{ UNWIND nodes as n MATCH (n)-[r:RELATED]-(m) WHERE NOT m IN nodes RETURN r.description AS descriptionText ORDER BY r.rank, r.weight DESC LIMIT {topOutsideRels} }} as outsideRels, // Inside Relationships collect {{ UNWIND nodes as n MATCH (n)-[r:RELATED]-(m) WHERE m IN nodes RETURN r.description AS descriptionText ORDER BY r.rank, r.weight DESC LIMIT {topInsideRels} }} as insideRels, // Entities description collect {{ UNWIND nodes as n RETURN n.description AS descriptionText }} as entities // We don't have covariates or claims here RETURN "Chunks:" + apoc.text.join(text_mapping, '|') + "\nReports: " + apoc.text.join(report_mapping,'|') + "\nRelationships: " + apoc.text.join(outsideRels + insideRels, '|') + "\nEntities: " + apoc.text.join(entities, "|") AS text, 1.0 AS score, nodes[0].id AS id, {{_node_type:nodes[0]._node_type, _node_content:nodes[0]._node_content}} AS metadata """
此外,返回方式也略有不同。我们需要将节点类型和内容作为元数据返回;否则,检索器就会崩溃。现在,我们只需实例化 Neo4j 向量存储,并将其用作查询引擎。现在我们可以测试 GraphRAG 本地检索器了。
response = loaded_index.query("What do you know about Scrooge?") print(response.response) #print(response.source_nodes[0].text) # Scrooge is an employee who is impacted by the generosity and festive spirit # of the Fezziwig family, particularly Mr. and Mrs. Fezziwig. He is involved # in the memorable Domestic Ball hosted by the Fezziwigs, which significantly # influences his life and contributes to the broader narrative of kindness # and community spirit.
4.2 全局检索器
全局检索器的结构稍微简单一些。它似乎是在指定的层次上遍历所有社区摘要,生成中间摘要,然后根据中间摘要生成最终回复。
我们必须事先确定要迭代哪个层次,这不是一个简单的决定,因为我们不知道哪个层次效果更好。层次越高,社区规模越大,但数量越少。这是我们在没有手动检查摘要的情况下所掌握的唯一信息。其他参数允许我们忽略低于等级或权重阈值的社区,但我们在这里不会使用这些参数。我们将使用 LangChain 实现全局检索器,并使用与 GraphRAG 论文中相同的map和reduce提示。由于系统提示非常长,我们将不在这里介绍,也不介绍链的构建。不过,所有代码都可以在Notebook[4]中找到。
def global_retriever(query: str, level: int, response_type: str = response_type) -> str: community_data = graph.query( """ MATCH (c:__Community__) WHERE c.level = $level RETURN c.full_content AS output """, params={"level": level}, ) intermediate_results = [] for community in tqdm(community_data, desc="Processing communities"): intermediate_response = map_chain.invoke( {"question": query, "context_data": community["output"]} ) intermediate_results.append(intermediate_response) final_response = reduce_chain.invoke( { "report_data": intermediate_results, "question": query, "response_type": response_type, } ) return final_response
现在让我们来测试一下。
print(global_retriever("What is the story about?", 2))
故事主要围绕着埃比尼泽-史古治展开,他是一个吝啬鬼,起初对生活抱有玩世不恭的态度,鄙视圣诞节。当他已故生意伙伴雅各布-马利的鬼魂拜访他时,他的转变开始了,随后出现了三个鬼魂,分别代表过去的圣诞节、现在的圣诞节和未来的圣诞节。这些遭遇促使史古治反思自己的生活和行为后果,最终使他接受了圣诞精神,经历了重大的个人成长[数据:报告(32, 17, 99, 86, +更多)]。### 雅各布-马利和鬼魂的作用 雅各布-马利的鬼魂起到了超自然催化剂的作用,他警告史古治三个鬼魂即将来访。每个鬼魂都会引导史古治进行一次自我发现之旅,说明他的选择所产生的影响以及同情心的重要性。鬼魂们向史古治揭示了他的行为不仅影响了他自己的生活,也影响了其他人的生活,尤其突出了救赎和相互关联的主题[数据:报告(86,17,99,+更多)]。### 史古治的关系与转变 史古治与克拉基特一家,尤其是鲍勃-克拉基特和他的儿子小添的关系,对他的转变至关重要。通过鬼魂呈现的幻象,史古治产生了同理心,这促使他采取实际行动,改善了克拉基特一家的境况。叙事强调了个人行为可以对社会产生深远影响,因为史古治新发现的慷慨在他的社区中培养了同情心和社会责任感[数据:报告(25、158、159、+更多)]。### 救赎与希望的主题 总的来说,这个故事是希望的永恒象征,突出了同情、自省和个人改变的潜力等主题。史古治从一个孤独的守财奴到一个仁慈的形象的转变过程说明,任何时候改变都为时不晚;小小的善举可以对个人和更广泛的社会产生重大的积极影响[数据:报告(32,102,126,148,158,159,+更多)]。总之,这个故事概括了圣诞节的变革力量和人与人之间联系的重要性,是一个关于救赎和一个人在节日期间对他人的影响的凄美叙事。
该响应相当长,而且很详尽,因为它符合全局检索器的要求,可以遍历指定层次上的所有社区。您可以测试如果改变社区的层次结构,响应会如何变化。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。