【博客文章2025】Oracle Database 23ai:AI Vector Search笔记与实验7---利用自编的Embedding函数实现中文相似性查询
1. 准备数据:
本博客使用这两篇博客中的实验环境。加上这篇原创的,由朋友于2006年创作的(从未在互联网上流传的)小游记《老荒游福州》fz.pdf,并将其加载到t1表中:
exec pack_blob.insert_blob(18,'DIR1','fz.pdf'); |
2. 编写存储过程完成数据向量化:
创建一个新表来存储非结构化的数据chunks和相关的嵌入向量。这个表将引入一个叫做vector类型的列:
create table t1chunksollama (doc_id number, chunk_id number, chunk_data clob, chunk_embedding vector);
|
首先插入数据(使用insert语句从t1表的data列读取PDF文档,转换这些PDF文档为文本),然后将文本分块成数据chunks,最后为每一个数据chunk使用《AI Vector Search笔记与实验6》中编写的通用函数为库内数据生成向量。 这些事情要通过编写一个存储过程来完成:dbms_vector_chain包中的utl_to_text将PDF数据转换成文本;utl_to_chunks将文本分块;func_text_input_ollama为每一个文本分块生成嵌入向量: create or replace procedure proc_ins_t1chunksollama(p_id number)
is
begin
insert into hr.t1chunksollama
SELECT D.id id,
JSON_VALUE(C.column_value, '$.chunk_id') AS id,
JSON_VALUE(C.column_value, '$.chunk_data') AS txt,
func_text_input_ollama(
replace(
replace(
replace(
replace(
replace(
replace(
replace(
replace(
replace(
JSON_VALUE(C.column_value, '$.chunk_data'),chr(10),' '
),chr(34),' '
),chr(39),' '
),chr(123),' '
),chr(125),' '
),chr(92),' '
),chr(47),' '
),chr(91),' '
),chr(93),' '
)
)
FROM hr.t1 D,
dbms_vector_chain.utl_to_chunks(dbms_vector_chain.utl_to_text(D.data),
JSON('{ "by": "words",
"split": "recursively",
"language" : "simplified chinese",
"normalize": "all"
}'
)
) C
where D.id=p_id;
commit;
end;
/ |
为了保证生成向量成功,必需逐层把文本块中的chr(10)、chr(34)、chr(39)、chr(123)、chr(125)、chr(92)、chr(47)、chr(91)和chr(93)替换成' '。以下给出这些ASCII字符对照表:
chr(10) |
'\n' (new line)
|
chr(34)
|
"
|
chr(39)
|
'
|
chr(123)
|
{
|
chr(125)
|
}
|
chr(92)
|
\ '\\'
|
chr(47)
|
/
|
chr(91)
|
[
|
chr(93)
|
]
|
这些字符都是JSON保留字,所以必需被替换成空格。 另外请注意:在utl_to_chunks进行数据分块时,传递的JSON参数是: "language" : "simplified chinese",这是因为t1表中的PDF都是中文文本。而func_text_input_ollama利用Ollama的bge-m3大语言模型支持中文向量化。
3. 利用自编的Embedding函数生成嵌入向量: 执行本文第2节中编写的过程proc_ins_t1chunksollama:
exec proc_ins_t1chunksollama(18);
|
重复以上步骤,再向t1表(之后用func_text_input_ollama)加载大量其他pdf数据并向量化它们,以避免案例的个例化和不具代表性:
select count(*) from t1chunksollama;
COUNT(*) 51883
|
select * from t1chunksollama where chunk_embedding is null; 无结果集 |
4. 利用自编的Embedding函数实现中文相似性查询:
4.1 普通的字符模糊查询不返回任何结果:
select doc_id, chunk_data from hr.t1chunksollama where chunk_data like '%老荒游福州的所有景点%'; 无结果集 |
4.2 创建“内存中邻居图向量索引”: 鉴于t1chunksollama中有5万多条向量数据,为了加速查询,我们来创建“内存中邻居图向量索引”:create vector index t1_hnsw_idx on t1chunksollama(chunk_embedding) organization inmemory neighbor graph distance COSINE with target accuracy 95 ; |
注意:创建了“内存中邻居图向量索引”的表是无法再进行DML操作的。
BEGIN proc_ins_t1chunksollama(19); END;
* 第 1 行出现错误: ORA-51928: 不支持对具有内存中邻居图向量索引的表使用数据操纵语言 (DML)。 ORA-06512: 在 "HR.PROC_INS_T1CHUNKSOLLAMA", line 5 ORA-06512: 在 line 1 帮助:https://docs.oracle.com/error-help/db/ora-51928/ |
如果要再进行DML操作,那么需要先删除“内存中邻居图向量索引”:
select doc_id, chunk_data from hr.t1chunksollama order by vector_distance(chunk_embedding , func_text_input_ollama('老荒游福州的所有景点'), cosine) fetch first 25 rows only;
DOC_ID | CHUNK_DATA |
---|
18 | 老荒游福州
(2006.1.29
-
2.3)
大年初一:北京-福州,华林寺-西湖-福建省博物馆-三坊七巷
狗年的第一顿饭是在海航的飞机上吃了一盒饺子。中午,从机场 | 18 | 纪念馆没有游客留言簿。但我真想说一句:
福州出了个林则徐!
下午还去了两座庙
-
归元寺和地藏寺,都是免费。归元寺里有一
尊古代铁佛,约40吨重,也不知何人所铸。来寺进香的主要是本地 | 18 | 大年初五:白马双溪,福州:林则徐纪念馆-归元寺-地藏寺
今天经历了三件讨厌事,对福建的印象有跌幅。
第一件:从青云山回永泰县城,摩的司机开价要20元。到达后, | 18 | 票五元,看来福州不是典型的旅游城市,不以门票宰客。公园旁边是
船政学堂和造船厂故址。1866年,闽浙总督左宗棠奏请在福州办船
政。船政初始,左宗棠调任陕甘总督,赴任前,力荐沈葆桢总理船政 | 18 | 回到宾馆用温泉砸了砸。想到福州小吃甜、油、过分细腻,不利
于体能恢复。在胃的建议下,去找回民吃了两碗拉面。晚上逛了台江
路步行街和榕城古街,没啥意思,这种市面,全国都大同小异。我出 | 18 | 乘大巴到福州市区,入住于山宾馆。于山宾馆位于福州的黄金地段,
背依于山,面朝五一广场,闹中取静。我选择的标间位于最高的四层,
朝山,可以洗温泉,观瀑布。过年吗,奢侈一把,195元。 | 18 | 淡清静。要是在福州长住,我还真打算带上几本闲书,入此山门挂单
几日,往自己的心里走一遭。
晚饭吃了传说中的沙县小吃,原料和味道都平常,可取之处就是
便宜。
大年初六:于山,福州-北京 | 18 | 不如人,迂腐颓势的文化未尝不也是败因之一。
唏嘘之后,回到福州,于天色将晚之时登上乌山。福州小山多,
市民好福气。老年人爬山,找个石桌打麻将;年轻人爬山,找个石凳 | 18 | 寺内著名的素面现称"平安发财面"。我见用料普通,又不是非要发
财不可,就下山去马尾了。好在于山宾馆的早餐有各式福州小吃,我
塞得很饱。
一直以为马尾是海港,到了才发现原来还没出闽江。罗星公园门 | 18 | 谈恋爱。我饿了,下山觅食,吃了雪菜光饼、花生汤、竹筒蛋羹扇贝
等等。
大年初三:闽侯十八重溪-金山寺
一早去客运西站乘中巴到闽侯南屿,下车一打听,才知十八重溪
在下一个镇
- | 18 | 生活的人,不仅字写得极好,还爱研究水果,著有《荔枝谱》。品茶
也很在行,著有《茶录》。老蔡还是一位造桥专家,首创了流线型桥
墩,即科学,又美观。
福州是林则徐的出生地,是左宗棠客死的他乡。关于林则徐,我 | 18 | 定和其他三人拼车打的回福州。在福州西站下车后,司机立刻开车狂
奔,我立刻意识到他找给我的是一张五十的假币。他完全是故意的,
真想薅丫头发往车上撞。
第三件:本来觉得于山宾馆服务很好,但此番回来,住了188的 | 18 | 我面朝瀑布喊:"有没有女鬼,有没有女鬼?"
自然,保持她一贯的沉默,因为她从不承诺。
沐浴完毕,唱着山歌归去。由于景区封闭,沿公路走了很久,才
打到摩的。至南通,换乘客车回福州西站。又去了距此地不远江中的 | 18 | 大年初二:鼓山白云洞-涌泉寺-马尾罗星公园-乌山
听说白云洞山高路远,就去看看。在咨询了公交售票员之后,于
下院前两站的远洋下车,花四块钱打摩的,至一村口。司机指着公厕 | 18 | 的白马山庄。春节期间,标间涨到198一天。我开了一间,条件很一
般,所幸热水器很大。仔细研究了一下门票:四个景区,每个单独售
票25,学生票15;任意两个景区联票40。我后来花55元游遍了四 | 18 | 金山寺。
木船摆渡过去,加门票共收费两元。金山寺极小,置于江中石上,
惟一僧、一塔、一樟、一榕,两进院落而已。前院供妈祖,后院供西
方三圣。暮霭沉沉,闽江东去,逝者如斯。 | 18 | 是到了郊县,要格外防备存心使坏的出租和摩的。我想,如果带着一
个家庭生活,福州实在是个宜居的城市。但如果是一个人吃饱就全家
不饿了,住在福州,或许会感到不够痛快。 | 18 | 来他们是四川来福建打工的,相约到此旅游。我发现,凡是游客相对
稀少的景区,人与人往往相当友善。
两位好人去追赶他们的同伴,我独自上行,逐一欣赏每一出瀑布。 | 9 | 乃衮宦游憩之地,创有凉亭,雕栏画栋,极其华丽。壁间悬⼤家名笔,⼏上列稀世奇珍,佳联掇画,⽿⽬繁华,⼤额标题古今坟典,诚⼈间之蓬
岛,凡世之⼴寒也。⽣每与端游玩其间,或题咏,或琴棋,留连光景,取乐不⼀。 | 18 | 事务。沈葆桢是林则徐的女婿,他后来赶走过侵台的日人,还对台湾
的开发贡献很大。
公园面朝马尾海战战场。我登上罗星塔观望,以江面作为战场,
过于狭窄,不利于进攻的一方。况且,福建水师的三艘军舰战前已经 | 18 | 参照一张简易的地图,步行经西湖公园去省博物馆。西湖岸上游
人如织,博物馆里倒颇清静。就布展来说,淡化了阶级斗争,突出了
传统文化。闽籍的历史名人中,蔡襄给我印象较深。因为他是个热爱 | 18 | 却要40元,说是返程费用。我说,那你应该事先和我讲清楚,这是
你的不对了。念十多公里路也不近,又给了他10元。
第二件:此事最为可恶。因永泰至福州的客车排队数百人,我决 | 12 | 去。那个森林太奇怪了,人一走进似乎被下了迷药,脑袋都昏沉起来。
"
"
我走过两次,但走到一半,就迷路了。上次跟育走过一次,结果转到樱花林这边来了。我们明明没有转弯。
"
我想起上次的 | 18 | 翻山经小鼓、大鼓、观音掌,下行很远至涌泉寺。鼓山各景点相
距甚远,显得支离破碎。涌泉寺规模不小,咋说也比少林寺大,门票
三元。建寺虽早,但造像的配置和模样皆为近制。烧香祈福的人很多。 | 12 | 感觉整个人是在一个立体感十足的空间走着,入眼的除了乾枯的樱花树还是樱花树,似乎整个树林都被樱花树埋没了。 |
|
基本上,结果集中所有查出的相似性文本都出自18号文档(小游记《老荒游福州》),结果非常令人满意。 以上代码中的“vector_distance(chunk_embedding , func_text_input_ollama('老荒游福州的所有景点'), cosine)”,代表两个向量之间距离比较的依据是“cosine”,这是默认值。其他的比较依据还有(使用不同的比较依据,结果集会略有不同): DOT:计算两个向量的负向点积。 EUCLIDEAN:也称为L2距离,计算两个向量之间的欧几里德距离。 EUCLIDEAN_SQUARED:也称为L2_SQUARED,是没有取平方根的EUCLIDEAN。 HAMMING:通过计算两个向量之间维度数目的不同,来计算距离。 JACCARD:计算JACCARD距离。查询中使用的两个向量必须是二进制向量。 |