PostgreSQL码农集散地

90后技术哥哥“笑了”,65岁退休怕啥,上数字替身!微调后“我”又变聪明了

文中参考文档点击阅读原文打开, 同时推荐2个学习环境: 

1、懒人Docker镜像, 已打包200+插件:《最好的PostgreSQL学习镜像》

2、有web浏览器就能用的云起实验室: 《免费体验PolarDB开源数据库》

3、PolarDB开源数据库内核、最佳实践等学习图谱:  https://www.aliyun.com/database/openpolardb/activity 

关注公众号, 持续发布PostgreSQL、PolarDB、DuckDB等相关文章. 

微调后, 我的数字人变聪明了!90后,咱有数字替身,提前退休指日可待

背景

系列文章:

以前觉得AI笨笨的, 经过近期的折腾, 特别是在找到正确的微调姿势后, 突然发现它变聪明了. 第一次感受到AI带来的威胁.

不过有危就有机, 在出生率逐渐走低的背景下, 未来的劳动力、养老金来源都是巨大的挑战, 所以要搞AI嘛, 想象一下有个数字替身给你工作(哪怕是能给你降低50%工作量)是不是很爽? 所以说掌握先进生产力工具本身就像掌握信息差一样, 放在任何时代都是非常重要的. 企业不是要率先AI化, 而是要先将员工使用先进生产力工具的能力提升起来, 才能赢得未来. 我去高校给同学们分享的观点也非常明确, 这5点非常重要!   《德说-第227期, 致大学生的入学第一课, 如何从大一开始就为就业做正确的事情?》   : 

  • 行业/赛道
  • 思维模型
  • 专业能力及先进生产力工具
  • 影响力(连接)
  • 战绩

90后也许会是第一批享受数字替身的, 怕什么65岁退休.

AI的优势:

  • 能微调, 学习速度快
  • 不用休息
  • 触手可得, 现在一般的笔记本都能跑大模型. 或者调api. 总之使用门槛很低, 非常简单.

两种常用微调手段:

  • Prompt tuning 提示词微调(不需要训练), 本篇重点.
  • Fine tuning (需要训练), 下一篇介绍.

一、提示词微调之前

测试问题:

  • 请问PostgreSQL rum和gin索引的区别是什么?

先测试mistral:7b和gemma2:9b的输出如下. 都不知道rum是什么, 胡编乱造.

1 mistral:7b

rum (Rule-Based Optimizer) 和 GIN (Generalized Inverted Index) 在 PostgreSQL 中是两个相对独立的概念,但它们之间有密切的关联。

rum 是 PostgreSQL 优化器的一部分,负责根据查询并行度、表大小、统计信息等因素选择最佳执行计划(explain plan)。GIN 索引则是 PostgreSQL 提供的一种特殊索引类型,主要用于支持快速执行包含左边前缀匹配的查
询。

在 GIN 索引中,B-tree 索引结合了 GiST (Generic Search Tree) 索引的功能,其中 GiST 是用来处理多维区域查询和文本搜索的索引类型。GIN 索引主要使用 GIST 技术来实现在 B-tree 基础之上对多值属性执行快速范围
扫描。

GIN 索引最常见的应用场景是全文检索和区间查询,比如 SQL 语句中使用了 LIKE 操作符或者 BETWEEN 关键字。GIN 索引能够大大提高这类查询的性能,因为它可以在索引上快速地过滤出符合条件的记录。

总的来说,rum 是 PostgreSQL 优化器的一部分,负责选择执行计划;GIN 索引则是 PostgreSQL 提供的一种特殊索引类型,可以支持快速执行包含左边前缀匹配的查询。

2 gemma2:9b

在 PostgreSQL 中,GIN 和 RUM 都是用于处理文本搜索的特殊索引类型,但它们各有优缺点:

**GIN (Generalized INdex)**:

  • 优点:

    • 通用性强: 支持多种数据类型,包括字符串、数组、几何图形等。
    • 灵活度高: 可以自定义比较函数来实现特定类型的搜索逻辑。
    • 成熟稳定: 已经广泛使用且经过充分测试。
  • 缺点:

    • 性能较低: 对于大型文本数据集,GIN 的查询性能可能不如 RUM 高。
    • 内存占用大: 为了高效地查找匹配项,GIN 需要在内存中存储大量数据。

RUM (Regexp Unique Match):

  • 优点:

    • 高性能: 专为正则表达式搜索优化,对大型文本数据集具有较高的查询效率。
    • 低内存占用: RUM 使用更精简的数据结构,因此内存占用比 GIN 小。
  • 缺点:

    • 功能受限: 仅支持正则表达式搜索,不能用于其他类型数据的索引。
    • 灵活度低: 无法自定义比较函数,只能使用内置的正则表达式引擎。

总结:

  • 如果需要对多种数据类型进行搜索或自定义比较逻辑,可以选择 GIN。
  • 如果主要进行基于正则表达式的文本搜索,且需要高性能和低内存占用,可以选择 RUM。

选择建议:

  • 确定您的主要搜索需求是什么,是多类型数据搜索还是文本搜索?
  • 评估数据规模的大小,如果数据量很大,RUM 的高性能优势会更加明显。
  • 考虑内存资源的限制,RUM 的内存占用比 GIN 小。

希望以上解释能够帮助您理解 PostgreSQL rum和gin索引的区别!

二、Prompt tuning 上场

接下来用到我github这篇文章作为prompt, 看看回答有没有改观.

  • https://github.com/digoal/blog/tree/master/201907/20190706_01.md

提取文章标题作为quesion:

  • ## PostgreSQL rum 索引结构 - 比gin posting list|tree 的ctid(行号)多了addition info

提取文章的完整内容作为answer.

注意: 每种模型的提示参数不一样, 请参考对应的说明

  • https://www.promptingguide.ai/models/mistral-7b
  • https://apeatling.com/articles/part-2-building-your-training-data-for-fine-tuning/

mistral 提示参数

  • https://ollama.com/library/mistral
<s>[INST] 相关的问题1 [/INST] 答案1 </s>[INST] 请问PostgreSQL rum和gin索引的区别是什么? [/INST]  

gemma 提示参数

  • https://ollama.com/library/gemma2
<start_of_turn>user  
相关的问题1 <end_of_turn>  
<start_of_turn>model  
答案1 <end_of_turn>  
<start_of_turn>user  
请问PostgreSQL rum和gin索引的区别是什么? <end_of_turn>  
<start_of_turn>model  

1 mistral:7b 微调后

ollama run mistral:7b

输入prompt tunning + prompt

<s>[INST] ## PostgreSQL rum 索引结构 - 比gin posting list|tree 的ctid(行号)多了addition info [/INST] https://github.com/digoal/blog/tree/master/201907/20190706_01.md 内容贴到这里 </s>[INST] 请问PostgreSQL rum和gin索引的区别是什么? [/INST]  

PostgreSQL rum(Reverse-Index with Additional Information)索引和GIN(Generalized Inverted Index)索引的主要区别在于:

  1. 结构:GIN索引以TREE数据结构存储,查询时从根节点开始遍历到叶子节点的行号。RUM索引以倒排表的形式存储,每个单词或者短语对应一个条目,其中包含行号和附加信息(例如这一行包含了多少次这个单词/短语,或者
    时间戳等)。
  2. 功能:GIN索引支持的操作主要为包含、相交等,对于需要排序的操作(如ranking),需要额外的计算和扫描heap tuples。RUM索引则可以提供排序的结果,例如根据时间戳或者词频进行排序。
  3. 效率:由于GIN索引需要在运行时构建树,并且对于某些查询可能需要遍历整个树,因此可能会比RUM索引慢一点。RUM索引预先构建好了倒排表,使得查询速度更快。
  4. 应用:GIN索引主要用于全文搜索和统计分析,如对文本数据进行词频统计或者关键字匹配等。RUM索引可以用于时间序列数据的查询和排序,例如根据时间戳进行排序、统计、聚合等操作。
  5. 开源:RUM是PostgreSQL社区提供的一个扩展,需要手动安装和使用。GIN则是PostgreSQL内置的索引方案。

2 gemma2:9b 微调后

PostgreSQL RUM 和 GIN 索引的区别

GIN 和 RUM 都是 PostgreSQL 中用于全文检索的倒排索引,但它们在结构和功能方面存在一些关键区别:

1. 结构:

  • GIN (Generalized INdex): 存储索引字段 Entry 的TREE结构。每个Entry对应一个数据范围,末端是Posting list|tree, 存储对应的ctid(行号)。额外信息不存储。
  • RUM (Rank-aware Unique Matching): 在 Posting list|tree 的每一个 ctid 后面添加额外的属性信息(例如目标词个数、时间戳等)。这些额外的信息允许 RUM 进行更复杂的检索和排序操作。

2. 功能:

  • GIN: 擅长于包含、相交等简单的搜索操作,但对于 ranking 排序、phrase search 和基于其他字段的排序效率较低。
  • RUM: 支持更加丰富的检索功能,例如:
    • Ranking sort: 根据目标词个数或其他额外属性进行排名。
    • Phrase search: 支持根据词语顺序查找短语。
    • Timestamp based ordering: 支持根据时间戳排序结果。

3. 效率:

  • GIN: 对于简单的包含、相交查询,GIN 的效率较高。但对于 ranking 排序等操作,需要额外的 heap scan, 效率较低。
  • RUM: 通过在 Posting list|tree 中存储额外信息,可以避免额外的 heap scan,提高了排序和排名等操作的效率。

4. 应用场景:

  • GIN: 适用于简单的全文检索场景,例如查找包含特定词语的数据。
  • RUM: 适用于需要进行复杂检索和排序场景,例如搜索引擎、推荐系统等。

总而言之,选择合适的索引类型取决于具体的应用场景。如果只需要简单的包含、相交查询,可以使用 GIN 索引;如果需要进行排名、短语搜索或基于其他字段的排序,则建议使用 RUM 索引。

三、附

以下两个python脚本结果还不太对, 和ollama里面运行的结果有差别, 回头再看看啥原因.

mistral.py

import ollama    
import sys    

  def main():  

  # Get the file path from the command line argument    
  if len(sys.argv) < 3:    
    print("Usage: python script.py [prompt] [file1] [file2] ... ")    
    sys.exit(1)    

    """Reads files, extracts first line as promptN, remaining content as answerN, and formats the output."""

    prompt = sys.argv[1]  # Get the user-provided prompt  

    file_prompts_answers = []  
  for i in range(2, len(sys.argv)):  # Iterate over file paths starting from the second argument  
    try:  
      with open(sys.argv[i], 'r') as f:  
        promptN = f.readline().strip()  # Read and strip the first line  
        answerN = f.read().strip()  # Read the remaining content  
        file_prompts_answers.append((promptN, answerN))   
    except FileNotFoundError:  
      print(f"Error: File '{sys.argv[i]}' not found.")  

    output_string = f"<s>"

    for i, (promptN, answerN) in enumerate(file_prompts_answers):   
    output_string += f"[INST] {promptN} [/INST] {answerN} "

    output_string += f"</s>[INST] {prompt} [/INST]"

    output = ollama.generate(    
      model="mistral:latest",    
      prompt=output_string  
  )    

      print(output['response'])    

    if __name__ == "__main__":  
  main()  

gemma2.py

import ollama    
import sys    

  def main():  

  # Get the file path from the command line argument    
  if len(sys.argv) < 3:    
    print("Usage: python script.py [prompt] [file1] [file2] ... ")    
    sys.exit(1)    

    """Reads files, extracts first line as promptN, remaining content as answerN, and formats the output."""

    prompt = sys.argv[1]  # Get the user-provided prompt  

    file_prompts_answers = []  
  for i in range(2, len(sys.argv)):  # Iterate over file paths starting from the second argument  
    try:  
      with open(sys.argv[i], 'r') as f:  
        promptN = f.readline().strip()  # Read and strip the first line  
        answerN = f.read().strip()  # Read the remaining content  
        file_prompts_answers.append((promptN, answerN))   
    except FileNotFoundError:  
      print(f"Error: File '{sys.argv[i]}' not found.")  

    output_string = f""

    for i, (promptN, answerN) in enumerate(file_prompts_answers):   
    output_string += f"<start_of_turn>user\n{promptN} <end_of_turn>\n<start_of_turn>model\n{answerN} <end_of_turn>\n"

    output_string += f"<start_of_turn>model\n{prompt} <end_of_turn>\n"

    output = ollama.generate(    
      model="gemma2:9b",    
      prompt=output_string  
  )    

      print(output['response'])    

    if __name__ == "__main__":  
  main()  

参考

https://www.promptingguide.ai/models/mistral-7b

https://apeatling.com/articles/part-2-building-your-training-data-for-fine-tuning/

https://ollama.com/library/mistral

https://ollama.com/library/gemma2

本期彩蛋-配图为cuug陈老师,恭喜 CUUG获得PostgreSQL数据库认证与培训合作伙伴

Image
Image
Image

文章中的参考文档请点击阅读原文获得. 


欢迎关注我的github (https://github.com/digoal/blog) , 学习数据库不迷路.  

近期正在写公开课材料, 未来将通过视频号推出, 欢迎关注视频号:

Image