前言
目前,很多企业会利用大模型来做企业内部知识库系统,比如智能客服系统,做法是把企业内部的整理的产品手册、常见问题手册,做成智能知识库系统,客户可以直接向知识库系统用自然语言提问,知识库系统能理解客户的问题并基于内部知识给出客户想要的答案,在下一篇,我会带着大家来实现一个智能客服系统,本篇先学习一个前置知识,就是文本向量化。
一、什么是向量
一个二维向量可以理解为平面坐标轴中的一个坐标点(x,y),在编程领域,一个二维向量就是一个大小为二的float类型的数组。
二、文本向量化
所谓文本向量化是指,利用大模型可以把一个字、一个词或一段话映射为一个多维向量,比如可以直接在LangChain4j中来调用向量模型来对一句话进行向量化体验:
package com.qjc.demo;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.output.Response;
/***
* @projectName langchain4j-project-1
* @packageName com.qjc.demo
* @author qjc
* @description TODO
* @Email qjc1024@aliyun.com
* @date 2024-10-15 09:10
**/
public class VectorAI {
public static void main(String[] args) {
OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.apiKey("demo")
.build();
Response<Embedding> embed = embeddingModel.embed("你好,我叫小齐");
System.out.println(embed.content().toString());
System.out.println(embed.content().vector().length);
}
}
代码执行结果为:
Embedding { vector = [-0.01991013, -0.009199853, ...] }
1722
从结果可以知道"你好,我叫小齐"这句话经过OpenAiEmbeddingModel向量化之后得到的一个长度为1722的float数组。注意,1722是固定的,不会随着句子长度而变化。
那么,我们通过这种向量模型得到一句话对应的向量有什么作用呢?非常有用,因为我们可以基于向量来判断两句话之间的相似度。
三、向量相似度
前面提到,向量相当于就是坐标点,如果两个坐标点靠得近,那么就表示这两个向量相似,所以,如果两句话对应的向量相似,那么就表示这两句话语义比较相似,当然这中间最关键的就是向量模型,因为向量是它生成的,向量模型也是经过大量机器学习训练之后产生的,向量模型效果越好,就表示它对于自然语言理解的程度越好,同时也就表示它生成出来的向量越准备,越能反映出语义的相似度。
比如可以通过计算两个坐标的余弦相似度(代码是ChatGPT生成的):
import java.util.*;
public class CosineSimilarity {
// 计算两个向量的点积
public static double dotProduct(double[] vectorA, double[] vectorB) {
double dotProduct = 0;
for (int i = 0; i < vectorA.length; i++) {
dotProduct += vectorA[i] * vectorB[i];
}
return dotProduct;
}
// 计算向量的模
public static double vectorMagnitude(double[] vector) {
double magnitude = 0;
for (double component : vector) {
magnitude += Math.pow(component, 2);
}
return Math.sqrt(magnitude);
}
// 计算余弦相似度
public static double cosineSimilarity(double[] vectorA, double[] vectorB) {
double dotProduct = dotProduct(vectorA, vectorB);
double magnitudeA = vectorMagnitude(vectorA);
double magnitudeB = vectorMagnitude(vectorB);
if (magnitudeA == 0 || magnitudeB == 0) {
return 0; // 避免除以零
} else {
return dotProduct / (magnitudeA * magnitudeB);
}
}
public static void main(String[] args) {
// 示例向量
double[] vectorA = {1, 2, 3};
double[] vectorB = {3, 2, 1};
// 计算余弦相似度
double similarity = cosineSimilarity(vectorA, vectorB);
System.out.println("Cosine Similarity: " + similarity);
}
}
四、 向量数据库
对于向量模型生成出来的向量,我们可以持久化到向量数据库,并且能利用向量数据库来计算两个向量之间的相似度,或者根据一个向量查找跟这个向量最相似的向量。
在LangChain4j中,EmbeddingStore表示向量数据库,它有20个实现类:
1. AstraDbEmbeddingStore
2. AzureAiSearchEmbeddingStore
3. CassandraEmbeddingStore
4. ChromaEmbeddingStore
5. ElasticsearchEmbeddingStore
6. InMemoryEmbeddingStore
7. InfinispanEmbeddingStore
8. MemoryIdEmbeddingStore
9. MilvusEmbeddingStore
10. MinimalEmbeddingStore
11. MongoDbEmbeddingStore
12. Neo4jEmbeddingStore
13. OpenSearchEmbeddingStore
14. PgVectorEmbeddingStore
15. PineconeEmbeddingStore
16. QdrantEmbeddingStore
17. RedisEmbeddingStore
18. VearchEmbeddingStore
19. VespaEmbeddingStore
20. WeaviateEmbeddingStore
其中有我们熟悉的几个数据库都可以用来存储向量,比如Elasticsearch、MongoDb、Neo4j、Pg、Redis。
那么我们使用Redis来演示对于向量的增删查改,首先添加依赖:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-redis</artifactId>
<version>${langchain4j.version}</version>
</dependency>
然后需要注意的是,普通的Redis是不支持向量存储和查询的,需要额外的redisearch模块,我这边是直接使用docker来运行一个带有redisearch模块的redis容器的,命令为:
docker run -p 6379:6379 redis/redis-stack-server:latest
注意端口6379不要和你现有的Redis冲突了。
然后就可以使用以下代码把向量存到redis中了:
RedisEmbeddingStore embeddingStore = RedisEmbeddingStore.builder()
.host("127.0.0.1")
.port(6379)
.dimension(1536)
.build();
// 生成向量
Response<Embedding> embed = embeddingModel.embed("我是小齐");
// 存储向量
embeddingStore.add(embed.content());
dimension表示要存储的向量的维度,所以为1536,如果你不是使用OpenAiEmbeddingModel得到的向量,那么维度可能会不一样。
可以使用以下命令来清空:
redis-cli FT.DROPINDEX embedding-index DD
五、匹配向量
在上面我们存储了"我是小齐"的向量到Redis,接下来我们来使用“我的名字叫小齐”来查找,看能不能查找到“我是小齐”,最好新建一个类,新增和查询分为两个类比较方便:
package com.qjc.demo;
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore;
import java.util.List;
/***
* @projectName langchain4j-project-1
* @packageName com.qjc.demo
* @author qjc
* @description TODO
* @Email qjc1024@aliyun.com
* @date 2024-10-15 09:17
**/
public class VectorSearchAI {
public static void main(String[] args) {
OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.baseUrl("http://langchain4j.dev/demo/openai/v1")
.apiKey("demo")
.build();
RedisEmbeddingStore embeddingStore = RedisEmbeddingStore.builder()
.host("127.0.0.1")
.port(6379)
.dimension(1536)
.build();
// 生成向量
Response<Embedding> embed = embeddingModel.embed("我的名字叫小齐");
List<EmbeddingMatch<TextSegment>> result = embeddingStore.findRelevant(embed.content(), 4);
for (EmbeddingMatch<TextSegment> embeddingMatch : result) {
System.out.println(embeddingMatch.score());
}
}
}
代码执行结果为:
0.6765566249421
这就是"我是小齐"和“我的名字叫小齐”两句话之间的语义相似度分数,假如我们换成“今天天气很好”,得到的分数为
0.7937962249271
看上去似乎分数差别不大,但是要注意小数位其实是很多的,也就是这个分数精度是比较高的,所以两个分数还是有一定距离的。
我们不妨再来演示一种场景:
// 生成向量
TextSegment textSegment1 = TextSegment.textSegment("客服电话是400-000000");
TextSegment textSegment2 = TextSegment.textSegment("客服工作时间是周一到周五");
TextSegment textSegment3 = TextSegment.textSegment("客服投诉电话是400-000001");
Response<Embedding> embed1 = embeddingModel.embed(textSegment1);
Response<Embedding> embed2 = embeddingModel.embed(textSegment2);
Response<Embedding> embed3 = embeddingModel.embed(textSegment3);
// 存储向量
embeddingStore.add(embed1.content(), textSegment1);
embeddingStore.add(embed2.content(), textSegment2);
embeddingStore.add(embed3.content(), textSegment3);
我往向量数据库中添加了三条客户相关的知识,并且通过TextSegment把原始文本也存入了Redis,相当于Redis中现在存储了三条原始文本以及对应的向量,然后我们来查询:
// 生成向量
Response<Embedding> embed = embeddingModel.embed("客服电话多少");
// 查询
List<EmbeddingMatch<TextSegment>> result = embeddingStore.findRelevant(embed.content(), 5);
for (EmbeddingMatch<TextSegment> embeddingMatch : result) {
System.out.println(embeddingMatch.embedded().text() + ",分数为:" + embeddingMatch.score());
}
代码执行结果为:
客服电话是400-000000,分数为:0.9529552055761
客服投诉电话是400-000001,分数为:0.9020588517189
客服工作时间是周一到周五,分数为:0.9105278658864999
从这就更容易看出向量的好处,能够基于向量快速的得到和文本相似的文本,这样就能非常适合用来做RAG,也就是检索增强生成。
总结
本篇讲了什么是文本向量化、向量数据库、以及文本相似度等概念,下一篇将通过完成一个智能客服系统来掌握向量等技术在RAG中的使用,以及掌握到底什么是RAG。
如果你觉得我写的不错,请留下评论,或者点个赞收藏,一下是我的动力,关注我,我会尽量根据时间来学习一些东西,进行发表文章。
4056

被折叠的 条评论
为什么被折叠?



