分块或文本拆分是一种将大块文本分解为小块的方法。例如,您可以将一篇文章分成多个段落,将一个段落分成多个句子,甚至将单词分成多个字符。

在人类层面上,无论您是学习新东西、组织数据还是尝试理解复杂概念,这种技术都更容易处理和记住信息。例如,在教育中,教师使用分块来协助学生通过将课程分解成部分来吸收和保留复杂的信息。
不过,在编程层面上,分块协助程序员进行文本分析、人工智能、软件开发、RAG等……例如,在人工智能中,特别是在检索增强生成(RAG)中,分块对于处理大型数据集至关重大。它允许人工智能快速检索特定信息,从而获得更快、更准确的结果。
一种需要分块的技术
ChatGPT和Gemini等生成性人工智能模型的一个主要局限性是幻觉。当你问它一些事情时,它会回答一些错误或不相关的事情。例如:

如您所见,它对问题的回答不正确,尽管很明显它不包含任何“m”。然后,当我问它是否确定时,它意识到它回复不正确,并修复了它。
如果您使用GPT-4而不是GPT-3.5,它会正确回答这个问题。不过,这并不意味着它不会给其他复杂问题的答案产生幻觉。

检索增强生成(RAG)来了,这是一种通过从外部来源获取实际来提高生成AI模型的准确性和可靠性的技术。
你通过给人工智能模型一个像论文一样的外部资源,然后问它与之相关的问题来做到这一点。然后,这个人工智能模型将分析这段文字,如果它没有从文本中找到你问题的答案,它会告知你它没有答案,而不是产生幻觉并给你一个错误的回答。

为了有效地做到这一点,RAG将外部文本块放入一个由较小块组成的数据库中,这改善了对相关信息的搜索,并将其包含在其响应中。这不仅提高了生成内容的准确性,还使人工智能在获取必要数据方面更加高效和快速。
这是分块超级重大用途的一个例子。分块方法越好,RAG程序的响应和工作流程就越好。
分块方法
根据程序任务的不同,分块可以以不同的方式应用。以下是不同分块方法的概述:
- 按字符:将文本分解为单个字符,这对于需要深度和粒度文本分析的任务超级有用。
- 按字符+ SimplerLLM:除了保留句子结构,获得更好和更有意义的片段外,还逐个字符分块文本。(在SimplerLLM库中可用)
- 通过令牌:文本被分割成令牌,例如单词或子词,一般用于自然语言处理来分析文本。
- 按段落:如名称所述,它逐段分段文本,这对维护文本结构很有用。
- 递归分块:它涉及反复将数据分解为更小的分块,一般用于分层数据结构。
- 语义分块:它根据意义而不是结构元素对文本进行分组,这对需要理解数据上下文的任务至关重大。
- 代理分块:这种类型的侧重于根据所涉及的代理(如人员或组织)识别和分组文本,这在信息提取中很有用。
目前来说说我们的主要想法
语义分块是如何工作的?
语义分块背后的主要想法是根据分块在意义上的类似程度来拆分给定的文本。
这种类似性是通过将给定文本分块成句子,然后将所有这些基于文本的块转换为矢量嵌入并计算这些块之间的余弦类似性来计算的。
在那之后,我们初始化一个阈值,例如0.8,每当2个连续段之间的余弦类似性大于此时,就会在那里进行拆分。因此,在这次拆分之前,一切都将是一个大块,之后是另一个大块,以此为由此,直到我们得到所有的大块。
理论示例:

正如你在上面看到的,假设我们把句子1和2变成了向量,检查了它们之间的余弦类似性,发现它是0.85。由于我们的阈值为0.8,这低于我们得到的结果,所以我们在那里分块。如果我们选择阈值为0.9,则不会完成任何块。

目前,假设我们将句子2和3转换为向量,检查了它们的余弦类似性,发现它是0.3。因此,那里不会进行分块,由于我们的阈值是0.8,这比我们得到的结果要大。
请记住,上述示例只是理论上的,只是为了简化想法。
我的原型
我根据上面解释的算法开发了一个语义分块器,并进行了一些调整。
我没有获得相邻句子之间的余弦类似性,而是将每个句子与之前的句子和之后的句子结合起来。然后,我计算了组合句子的余弦类似性。
因此,我目前不是计算句子2和3之间的类似性,而是在合并的句子1、2、3和2,3,4之间计算它。
为什么要这样做?当只用句子分块时,你可能会得到2个句子,列如“这并不像看起来那么容易”和“你知道吗?”。当你把这些句子变成向量时,它们之间的余弦类似性不会接近;不过,在句子结构和含义方面,它们超级接近。因此,当将它们组合在一起并与其他组进行比较时,通过将它们全部转换为一个向量,会产生更好的结果,因此,如果被比较的组在意义上的确 接近,则会获得较低的余弦类似性。
无论如何,回到我们的原型:
进口重新
进口openai
导入numpy为np
从 sklearn.metrics.pairwise import cosine_similarity
def chunk_text(文本):
#将输入文本拆分为单个句子。
single_sentences_list = _split_sentences(文本)
# 组合相邻的句子,在每个句子周围形成一个上下文窗口。
combined_sentences = _combine_sentences(single_sentences_list)
#使用神经网络模型将组合句子转换为向量表明。
嵌入 = convert_to_vector(combined_sentences)
#计算连续组合句子嵌入之间的余弦距离,以测量类似性。
距离 = _calculate_cosine_distances(嵌入)
#根据所有距离的第80个百分位数确定识别断点的阈值距离。
断点_百分比_阈值 = 80
breakpoint_distance_threshold = np.percentile(距离,breakpoint_percentile_threshold)
#找到距离超过计算阈值的所有指数,表明潜在的块断点。
indices_above_thresh = [i for i, distance in enumerate(distances) if distance > breakpoint_distance_threshold]
#初始化块列表和变量,以跟踪下一个块的开始。
大块 = []
start_index = 0
#循环识别的断点,并相应地创建块。
对于index_above_thresh中的索引:
chunk = ' '.join(single_sentences_list[start_index:index+1])
chunks.append(块)
start_index = 索引 + 1
#如果最后一个断点后还剩下任何句子,请将它们添加为最后一个块。
if start_index < len(single_sentences_list):
chunk = ' '.join(single_sentences_list[start_index:])
chunks.append(块)
#返回文本块列表。
返回块
def _split_sentences(文本):
#使用正则表达式将文本拆分为基于标点符号和空格的句子。
句子 = re.split(r'(?<=[.?!])s+',文本)
返回句子
def _combine_sentences(句子):
#通过将每个句子与上一个和下一个句子结合起来来创建一个缓冲区,以提供更广泛的上下文。
combined_sentences = []
对于i在范围内(len(句子)):
combined_sentence = 句子[i]
如果i > 0:
combined_sentence = sentences[i-1] + ' ' + combined_sentence
if i < len(句子) - 1:
combined_sentence += ' ' + 句子[i+1]
combined_sentences.append(combined_sentence)
返回组合_句子
def convert_to_vector(文本):
#尝试使用预训练模型为文本列表生成嵌入,并处理任何异常。
尝试:
响应 = openai.embeddings.create(
输入=文本,
model="text-embedding-3-small"
)
embeddings = np.array([响应.data中项目的项目嵌入])
返回嵌入
例外情况除外:
print("发生错误:", e)
return np.array([]) # 在出现错误时返回一个空数组
def _calculate_cosine_distances(嵌入):
#计算连续嵌入之间的余弦距离(1-余弦类似性)。
距离 = []
对于范围内的i(len(嵌入)-1):
similarity = cosine_similarity([embeddings[i]], [embeddings[i + 1]])[0][0]
距离 = 1 - 类似性
距离.append(距离)
返回距离
#主要部分
text = """ Your_Input_Text"""
chunks = chunk_text(文本)
print("Chunks:", chunks)
代码超级简单;它经历了我上面提到的所有步骤。
在主要部分,您调用chunk_text函数,该函数获取您的输入文本,然后从中调用所有其他相应的函数。
滚动到顶部,您会看到,第一,它调用_split_senteces函数将输入文本拆分为句子,然后_combine_sentences生成3个句子的组,即每个句子及其相邻句子;之后,所有这些分组的句子都使用OpenAI的嵌入模型转换为向量嵌入,然后在所有相邻的句子组之间计算余弦类似性,最后,每当有大于我初始化到95的百分位阈值时,就会完成一个块。
尝试通过更改百分位值阈值来尝试代码,并查看块的差异。你会意识到,阈值越低,你得到的块就越多。但为什么呢?
当阈值低时,2个文本必须彼此超级类似,才能获得低余弦类似性,以通过与阈值的比较,而不是被分块。否则,文本不会被分块,由于当然,并非所有文本都超级类似。
执行代码
要运行代码,您必须创建一个包含OpenAI API密钥的.env文件,如下:

有语义分块的图书馆
如果您是不喜爱自己做这些细节的类型,并且您更喜爱现成的工具和函数,您始终可以使用集成了语义分块的知名库;以下是一些:
LangChain:LangChain是一个开源库,旨在促进语言模型应用程序的构建。它提供语义分块功能,可以增强自然语言的理解。
Llama Index:Llama Index是一个专门的库,为大型语言模型提供高效的索引和检索功能。它集成了语义分块,以提高搜索精度和相关性。
SimplerLLM:即将推出……我很快就会在SimplerLLM中添加更多分块函数。敬请关注!
这些库的方法和允许的定制水平各不一样,因此选择使用哪个库可能取决于您项目的具体要求。在我看来,最好的方法是将上面的原型编辑成您自己的喜好!
让我们把结果可视化
让我们尝试我们构建的语义分块器,并可视化我们得到的结果。以下是使用SimplentLLM阅读博客文章并使用matplotlib.pyplot绘制余弦类似性的附加代码:
从 SimplerLLM.tools.generic_loader import load_content
def plot_cosine_similarities(文本):
句子 = _split_sentences(文本)
combined_sentences = _combine_sentences(句子)
嵌入 = convert_to_vector(combined_sentences)
类似性 = calculate_cosine_similarities(嵌入)
plt.figure(figsize=(10,5))
# 绘制连接所有点的蓝色线
plt.plot(类似性,marker='o',linestyle='-',color='blue',label='Cosine类似性')
#覆盖类似性为0.95或更高的红点
对于i,枚举的类似性(类似性):
如果类似性>= 0.95:
plt.plot(i,类似性,标记=“o”,颜色=“红色”)
plt.title('连续句子之间的Cosine类似性')
plt.xlabel('句子对指数')
plt.ylabel('Cosine类似性')
plt.grid(真)
plt.legend()
plt.show()
# 用法示例:
load = load_content("https://learnwithhasan.com/how-to-build-a-semantic-plagiarism-detector/")
文本=加载。内容
plot_cosine_similarities(文本)
如您所见,我正在从网站上加载一篇博客文章,计算余弦类似性,并绘制结果:

超级酷,是吧?
如图所示,每个大于0.95(阈值)的余弦类似性都用红色显示,这就是所有分块发生的地方。因此,2个彩色点之间的一切都是一块。
尝试使用不同的输出,看看图表的外观,并与我们分享您的结果!



