它在数据持久化和容错性上表现非常好、支持混合搜索、支持水平扩展,同时又保持了轻量化。官方主打做AI时代的原生数据库,减少幻觉、数据泄漏和厂商绑定。
第一家,是一直以来在北美市场生态建设做的非常好的“松果”(Pinecone),产品做的非常好,拥有目前最强的产品生态和文档建设,CEO为前AWS&Yahoo的研发总监,年初刚刚发布了Serverless版本的新产品。你所知道的:Notion、微软、Shopify、Klarna(欧洲“支付宝”)、ClickUp、HubSpot、HelpScout、TaskUS、Disco、Frontier、GONG、Sixfold、Godaddy、InpharmD、YCombinator等等都是他们的客户。他们在去年“寒冬”中就募集了1亿美元,目前公司估值7.5亿美元。
另外一家,是和Pinecone同年建立公司,但是选择了开源商业化路线的Weaviate,在2023年公司人数只有65人(Pinecone一半)的情况下,保持非常高的发布和产品迭代频率,B轮融资5000万美元,目前估值2亿美元。Pinecone支持的ServerlessSaaS服务,Weaviate同样也是支持的。
不论是从以下哪个角度来看,它都值得我们来试着使用,尤其是你需要稳定不出错的私有化部署,并且不希望进行复杂的运维时:
好了,接下来,让我们来快速上手非常简单,数据相对安全可靠,并且程序本身不失轻量的向量数据库吧。
当然,如果你只想验证下向量数据库,了解下GTE模型的,也可以选择更小尺寸的GTESmall,相比较上面的大尺寸模型,精度并未下降太多(当然,有资源的情况下,还是建议跑大一些的模型,效果会更棒)。
但是,考虑到很多同学并不会使用向量化模型(EmbeddingModel),所以这里我们增加一个简单的验证环境,来展示下如何使用向量化模型。
当然,即使不使用Docker作为Python运行环境,我们也可以使用诸如Conda等支持环境隔离的方案,来运行我们的程序代码。
当Conda就绪后,只需要执行下面的代码,就能够获得一个完全干净的环境啦:
importtorch.nn.functionalasFfromtransformersimportAutoTokenizer,AutoModel#定义输入内容input_texts=["天气好热,哪里有卖冰棍的","今天好冷,该多穿两件","夏天","冬天"]#指定预训练模型,在线模型"thenlper/gte-base-zh",此处使用本地目录中的模型model_id="./thenlper/gte-small"#加载分词器和模型tokenizer=AutoTokenizer.from_pretrained(model_id)model=AutoModel.from_pretrained(model_id)#对输入内容进行分词、编码batch_dict=tokenizer(input_texts,max_length=512,padding=True,truncation=True,return_tensors='pt')#获取Embeddingsoutputs=model(**batch_dict)embeddings=outputs.last_hidden_state[:,0]#标准化embeddings,使用L2归一化,使其长度为1embeddings=F.normalize(embeddings,p=2,dim=1)#计算相似度,选择第一个元素,和除了第一个元素进行比较scores=(embeddings[:1]@embeddings[1:].T)*100#打印结果分数print(scores.tolist())将上面的代码保存为usage1.py,我们就可以准备运行模型啦。想要通过上面的代码运行模型,我们还需要安装两个基础的Python依赖:
pipinstalltorchtransformers当依赖安装完毕,我们可以执行pythonusage1.py,执行结果类似下面:
#pythonusage1.py[[80.17919921875,82.7370376586914,84.4228286743164]]如果你觉得上面的代码比较冗长,我们还可以使用SentenceTransformer的方式来运行模型:
fromsentence_transformersimportSentenceTransformerfromsentence_transformers.utilimportcos_sim#定义输入内容sentences=["天气好热,哪里有卖冰棍的","今天好冷,该多穿两件","夏天","冬天"]#指定预训练模型,在线模型"thenlper/gte-base-zh",此处使用本地目录中的模型model_id="./thenlper/gte-small"#加载SentenceTransformer模型model=SentenceTransformer(model_id)#获取向量embeddings=model.encode(sentences)#计算第一个句子(index0)和第二个句子(index1)的嵌入向量之间的余弦相似度,并打印结果print(cos_sim(embeddings[0],embeddings[1]))将上面的代码保存为usage2.py,然后我们完成必要的依赖安装:
pipinstallsentence_transformers然后,执行pythonusage2.py,我们就能够得到相似度结果了:
了解了通常情况下,我们如何使用向量模型,那么接下来,我们来看看如何使用Weaviate。
Weaviate和其他向量数据库产品或者用户量更大的支持向量功能的数据库不同的是,它的原生向量模块。
我们可以使用下面的配置,一键启动一个能够将我们输入的普通文本内容,自动转换为向量检索、相似度计算的向量数据库服务。
你可以选择Python、JavaScript(TS)、Java、Go、PHP、Ruby、C#等方式来完成Weaviate的调用。
#cdweaviate-quickstart#gomodtidygo:downloadinggopkg.in/check.v1v1.0.0-20201130134442-10cb98267c6cgo:downloadinggithub.com/kr/prettyv0.3.1go:downloadinggithub.com/rogpeppe/go-internalv1.11.0go:downloadinggithub.com/kr/textv0.2.0接着,执行gorunmain.go运行程序,程序将自动创建向量数据的索引,并查询我们输入的内容,获取最相近的计算结果,最终的输出类似下面这样:
在这里,再和大家展开聊几个技术细节吧。
在上面的Docker配置中,我们能够看到一个我定义的向量转换服务。
...t2v-transformers:image:soulteary/t2v-transformers:2024.06.27environment:#setto1toenableENABLE_CUDA:0ports:-9090:8080这个Docker服务中的soulteary/t2v-transformers:2024.06.27,包含了我们上文中提到的通义实验室团队的GTE模型。将GTE模型转换为一个可以被向量数据库调用的服务,其实也很简单。
Dockerfile中的内容是这样的,我们只需要将HuggingFace或ModelScope中下载的模型,在构建服务的时候,扔到/app/models/model中即可:
FROMsemitechnologies/transformers-inference:customCOPY./thenlper/gte-small/app/models/model然后,执行命令完成镜像的构建:
dockerbuild-tsoulteary/t2v-transformers:2024.06.27.最后,将服务的访问地址和启用模块名称,配置在weaviate容器服务的环境变量中即可。
在上面的代码中,有一段代码是关键:
objects:=make([]*models.Object,len(items))fori:=rangeitems{objects[i]=&models.Object{Class:"TraditionalFestival",Properties:map[string]any{"SolarTerms":items[i]["SolarTerms"],"Title":items[i]["Title"],"Author":items[i]["Author"],"Poem":items[i]["Poem"],"Description":items[i]["Description"],},}}batchRes,err:=client.Batch().ObjectsBatcher().WithObjects(objects...).Do(context.Background())在上面的代码中,我们将下面的数据按照“字段”进行了数据入库:
where:=filters.Where().WithPath([]string{"author"}).WithOperator(filters.Equal).WithValueString("陆游")result,err:=client.GraphQL().Get().WithClassName("TraditionalFestival").WithFields(fields...).WithNearText(nearText).WithLimit(2).WithWhere(where).Do(context.Background())再次执行代码,我们就可以得到过滤后的结果啦:
#gorunmain.goQueries[夏天吃瓜,冰棍也行吧]Time:16.928206ms{"data":{"Get":{"TraditionalFestival":[{"author":"陆游","description":"《月令七十二候集解》有载:“立,始建也。春气始而建立也。”在生生不息的春风中,一年的序幕由此开启。我们用一颗丰盈而善良的心,向着春意盎然的天地间走去。立春丨一候东风解冻,二候蜇虫始振,三候鱼陟负冰。","poem":"春盘春酒年年好,试戴银旛判醉倒。今朝一岁大家添,不是人间偏我老。","solarTerms":"立春","title":"木兰花·立春日作"}]}}}%向量数据落地存储的膨胀问题默认情况下,我们启动weaviate数据库后,一个空的数据库目录大概是112KB左右。
du-hsweaviate_data112Kweaviate_data而本文中,我们使用和建立向量索引的数据是12KB。
du-hstraditional-festival.json12Ktraditional-festival.json如果我们完成向量数据的索引,之前的数据库目录将膨胀至556KB:
du-hsweaviate_data556Kweaviate_data增长的数据量相比原始数据,膨胀了37倍之多,在做内容的向量索引的时候,我们需要进行数据的容量预估。(目前Weaviate使用的量化方案是PQ、BQ)
当然,解决这个问题的方法有很多,包括使用低维度一些的模型(本文使用的是512维的模型),或者不要对所有的内容都建立索引(本文对五个字段都进行了索引)。
--EOF
我们有一个小小的折腾群,里面聚集了一些喜欢折腾、彼此坦诚相待的小伙伴。