阅读视图
Langchain.js | Document Transformer👈| 文本这样转变😱
前言
书接上文 , 参考官方文档学习了 Ducument loader , 他的作用是加载不同的数据源 , 并且探讨了一些设计思想 , 这一次 ,还是这张图 , 但是学习的主题变了 , 我今天想学习 : 在加载数据之后 , 向量化之前 , 进行的文本转化处理, 这其实是文本转化器(Document Transformer)在作祟 , 对应图中红色方框的阶段 ~
同样 , 还是从宏观入手,文档转化主要有以下工作 :
-
文本分割
- 完整文档内容 分割为 chunk(文本块)
-
文本元数据提取
- 提取核心数据
-
文本翻译
- 全球化下 , 处理多语言文档已经成为常态
-
生成文本问答
- 向量储存的知识库中 , 通常以 QA 对出现 , 我们主要是在向量化之前转化为问答的形式 , 利于向量化过程中 , 增强相关性 , 聚焦核心要点
上面的四个步骤中 , 文本分割 是向量化之前的处理, 后面的三个步骤主要是在向量化前起优化作用
在 python 中提供 DocTran 库 , 用于上述文本元数据提取、文本翻译、生成文本为问答 ,因为 DocTran
- 提供 DoctranPropertyExtractor 进行属性提取
- 提供DoctranTextTranslator 进行文档翻译
- 提供DoctranQATransformer 进行生成文本问答
参考自官网 : python.langchain.ac.cn/docs/integr…
但在 langchain.js 中 , 我简单搜索了下官网 , 没有找到集成的 DocTran库 ,
有时间再去 langchain 的 github 上看看 , 地址如下 : github.com/langchain-a…
如果还没有 , 或许我们可以自己拓展
我就不看了 , 学校放假🤡 , 我要去愉快的玩耍一下 , 之后有时间再来研究研究 , 这篇文章主要总结我分割文本的经验 ~
文本分割
思考 :
-
为什么要文本分割 ?
-
怎么分割 ?
- 随便分吗 ? 肯定不是 🤡👈
为什么要文本分割 ?
主要有以下原因 :
-
处理非统一文档长度
现实世界的文档集合通常包含不同大小的文本。分割确保对所有文档进行一致的处理。 -
克服模型限制
许多嵌入模型和语言模型都有最大输入大小限制。分割使我们能够处理否则会超出这些限制的文档。 -
提升表示质量
对于较长的文档,嵌入或其他表示的质量可能会随着尝试捕捉过多信息而下降。拆分可以导致每个部分的表示更加专注和准确。 -
增强检索精度
在信息检索系统中,拆分可以提高搜索结果的粒度,允许更精确地匹配查询到相关文档部分。 -
优化计算资源
使用更小的文本块可以提高内存效率,并允许更好地并行化处理任务。
怎么分割 ?
关键概念
-
Chunk Size
- 块大小:末端块长度(以字符为单位)
-
Chunk Overlap
- 块重叠:连续块共享的重叠或交叉量
如何理解 ?
比如我们对《少年中国说》这篇文章进行分割
我们使用官网自带的ChunkViz v0.1 (块可视化工具)
上传文章后 , 选择下面的分割标准
文本分割如下 :
上面发生了什么?
上面其实就是使用 langchain.js 提供的RecursiveCharacterTextSplitter(递归字符文本分割) , 并且设置
- Chunk size
- Chunk overlap
这两个的参数大小
比如我上面设置 Chunk size 大小为 5 , 那么基本每个颜色块都是 5 个字符
由于可视化工具 Chunk overlap(块重叠) 无法展示 , 所以块与块之间没有重复的字段
我举个例子 : 一段分块是"日本人之称我中国也,一则曰老大帝国,再则曰老大帝国。" ,如果有重叠块可能会出现"再则曰老大帝国。是语也,盖袭译欧西人之言也。" , 如加粗的文字就是重叠部分 .
需要重叠块的原因是 , 两个块之间 , 需要有语义的联系 , 使得块向量化的时候 , 知道块与块之间的联系
以上便是分割的原理 , 以下使用代码实现
RecursiveCharacterTextSplitter
默认的分隔符列表是 ["\n\n", "\n", " ", ""] , 即分割的顺序是 : 段落("\n\n") => 句子("\n") =>单词(" ")
使用加载器加载 data 目录下的《少年中国说为 Document 对象 , 使用RecursiveCharacterTextSplitter 切割这个对象 , 代码如下
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { TextLoader } from "langchain/document_loaders/fs/text";
const loader = new TextLoader("data/少年中国说.txt");
const docs = await loader.load();
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 64,
chunkOverlap: 0,
});
const splitDocs = await splitter.splitDocuments(docs);
console.log(splitDocs);
部分输出如下图 :
How to split code
上面说到 , RecursiveCharacterTextSplitter 默认按照["\n\n", "\n", " ", ""] 分割 , 其实还包含预构建的分隔符列表,这些分隔符对于在特定编程语言中分割文本非常有用。
支持的语言有
"html" | "cpp" | "go" | "java" | "js" | "php" | "proto" | "python" | "rst" | "ruby" | "rust" | "scala" | "swift" | "markdown" | "latex" | "sol"
举两个例子就知道了~
Markdown
这里是一个使用 Markdown 分隔符进行分割的示例:
const markdownText = `
# 🦜️🔗 LangChain
⚡ Building applications with LLMs through composability ⚡
## Quick Install
# Hopefully this code block isn't split
pip install langchain
`;
const mdSplitter = RecursiveCharacterTextSplitter.fromLanguage("markdown", {
chunkSize: 60,
chunkOverlap: 0,
});
const mdDocs = await mdSplitter.createDocuments([markdownText]);
mdDocs;
效果如下 :
[
Document {
pageContent: "# 🦜️🔗 LangChain",
metadata: { loc: { lines: { from: 2, to: 2 } } }
},
Document {
pageContent: "⚡ Building applications with LLMs through composability ⚡",
metadata: { loc: { lines: { from: 4, to: 4 } } }
},
Document {
pageContent: "## Quick Install",
metadata: { loc: { lines: { from: 6, to: 6 } } }
},
Document {
pageContent: "```bash\n# Hopefully this code block isn't split",
metadata: { loc: { lines: { from: 8, to: 9 } } }
},
Document {
pageContent: "pip install langchain",
metadata: { loc: { lines: { from: 10, to: 10 } } }
},
Document {
pageContent: "```",
metadata: { loc: { lines: { from: 11, to: 11 } } }
},
Document {
pageContent: "As an open-source project in a rapidly developing field, we",
metadata: { loc: { lines: { from: 13, to: 13 } } }
},
Document {
pageContent: "are extremely open to contributions.",
metadata: { loc: { lines: { from: 13, to: 13 } } }
}
]
JavaScript
const JS_CODE = `
function helloWorld() {
console.log("Hello, World!");
}
// Call the function
helloWorld();
`;
const jsSplitter = RecursiveCharacterTextSplitter.fromLanguage("js", {
chunkSize: 60,
chunkOverlap: 0,
});
const jsDocs = await jsSplitter.createDocuments([JS_CODE]);
jsDocs;
[
Document {
pageContent: 'function helloWorld() {\n console.log("Hello, World!");\n}',
metadata: { loc: { lines: { from: 2, to: 4 } } }
},
Document {
pageContent: "// Call the function\nhelloWorld();",
metadata: { loc: { lines: { from: 6, to: 7 } } }
}
]
参考 : js.langchain.com/docs/how_to…
总结
学习文本分割的处理思想和方法 , 为后续向量化做准备 ~
可以在本次学习上加餐: token