基于langchain和向量库微调qwen2-1.5B学习行业专属知识

2024-06-18 19:12

本文主要是介绍基于langchain和向量库微调qwen2-1.5B学习行业专属知识,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        现在有大量的word文档数据需要让大模型学习形成专业业务领域的知识后对外提供问答服务,以下是基于前问Qwen2-1.5B模型进行langchain RAG向量搜索+大模型问答的实现过程。

第一步:原始数据处理

       原始word文档数据里面包含了封面、目录、概述、专业知识章节、表格、备注、附录等内容,还好没有图像,这些数据直接做分词向量化处理的时候因为上下文环境的问题会造成信息丢失,比如现在有一大段文字内容是关于如何制作面条的,关于制作面条的过程讲解的很细致,但是缺失了一个关键问题,就是这是关于中国面条的知识还是关于制作意大利面的知识呢?在做了向量化处理以后,如果用户的问题是如何制作意大利面,文字向量化处理的时候缺失了文章所属的专业领域分类信息就会造成数据检索失败或不准确,所以我这里是先将word文档内的内容按照段落转换成了excel格式,每一段数据一行,然后手工的对数据进行了一个概述补充,即补充了这些文字知识对应的是专业领域内的哪个领域的描述信息。

     以下是word文档转为excel的处理程序:

import pandas as pd
from docx import Document# 解析Word文档并保存段落到Excel文件
def parse_word_and_save_to_excel(word_file_path, excel_file_path, prefix):doc = Document(word_file_path)paragraphs = [prefix + p.text.strip() for p in doc.paragraphs if p.text.strip()]df = pd.DataFrame(paragraphs, columns=["paragraph"])df.to_excel(excel_file_path, index=False, engine='openpyxl')print(f"段落数据已保存到 {excel_file_path}")if __name__ == "__main__":word_file_path = "/data/xxx/doc/新加坡.docx"  # 请填写您的Word文档路径excel_file_path = "/data/xxx/新加坡段落数据.xlsx"  # 输出的Excel文件路径prefix = "在新加坡,"  # 前缀字符串parse_word_and_save_to_excel(word_file_path, excel_file_path, prefix)

  得到excel数据以后手动的对内容进行补充修改完善调整,涉及到表格数据的,涉及到列表内容的有可能需要完善修改一下,另外就是刚刚提到的,补充这段文字和业务领域的关联,删除没用的垃圾数据,如“目录”,“附录”等等没有用的数据。下一步就是产生问答对数据, 这里采用的方式是去调用其他的更高级的模型来生成问答对数据内容,正好赶上智谱清言API注册就给一千万token,所以这里使用的是chatglm4的模型去生成问答对数据,代码如下:

import json
import requests
import pandas as pd
from tqdm import tqdm# 设置HTTP代理
proxies = {"http": "http://x.1.2.3:1234","https": "http://x.1.2.3:1234"
}# API key 和 URL
api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  # 请填写您的API Key
api_url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"# 调用智谱AI API生成问答对
def generate_qa_pairs(paragraph, model="glm-4"):headers = {"Authorization": f"Bearer {api_key}","Content-Type": "application/json"}prompt = f"请基于以下段落生成尽可能多的问答对,并以 [{json.dumps({'q': '', 'a': ''})}] 结构化JSON格式返回:{paragraph}"data = {"model": model,"messages": [{"role": "user", "content": prompt}],"stream": False}response = requests.post(api_url, headers=headers, json=data, proxies=proxies)response.raise_for_status()return response.json()["choices"][0]["message"]["content"]# 保存结果为JSON文件
def save_to_json(data, output_file):with open(output_file, 'w', encoding='utf-8') as f:json.dump(data, f, ensure_ascii=False, indent=4)def process_paragraphs_from_excel(excel_file_path, output_file, start_percent, end_percent):df = pd.read_excel(excel_file_path, engine='openpyxl')paragraphs = df['paragraph'].tolist()total_paragraphs = len(paragraphs)start_index = int(total_paragraphs * start_percent / 100)end_index = int(total_paragraphs * end_percent / 100)selected_paragraphs = paragraphs[start_index:end_index]qa_pairs = []for paragraph in tqdm(selected_paragraphs, desc=f"Processing paragraphs {start_percent}% to {end_percent}%"):try:#去除中文双引号及英文双引号cleaned_paragraph = paragraph.replace('“', '').replace('”', '').replace('"', '')qa_content = generate_qa_pairs(cleaned_paragraph)qa_pairs.append({"paragraph": paragraph,"qa_pairs": json.loads(qa_content)})except requests.exceptions.RequestException as e:print(f"Error processing paragraph: {paragraph}\nError: {e}\nserver response:{qa_content}")except json.JSONDecodeError as e:print(f"Error decoding JSON for paragraph: {paragraph}\nError: {e}\nserver response:{qa_content}")save_to_json(qa_pairs, output_file)print(f"问答对已保存到 {output_file}")if __name__ == "__main__":excel_file_path = "/data/xxxxx/新加坡段落数据.xlsx"  # 输入的Excel文件路径output_json_file = "/data/xxxx/新加坡_qa_pairs.json"  # 输出的JSON文件路径start_percent = 0  # 起始百分比end_percent = 100  # 结束百分比process_paragraphs_from_excel(excel_file_path, output_json_file, start_percent, end_percent)

经过上面的步骤,word文档里面的知识就变成了问答对及上下文json数据,大概格式如下:

  [{"paragraph": "这里是上下文文字数据信息","qa_pairs": [{"q": "示例问题1?","a": "示例问题答案"},{"q": "示例问题2?","a": "示例问题答案"}]}
]

第二步:langchain+向量检索喂给大模型

    我这里是使用的Qwen2 1.5b模型,因为垃圾显卡只能跑这个模型了,另外是提前走向量搜索,大模型就做个总结也用不了多少算力。

先安装依赖

pip install flask flask-cors torch numpy transformers langchain langchain-community langchain-huggingface

 下面是程序代码:

from flask import Flask, request, jsonify
from flask_cors import CORS
import json
import os
import re
import torch
import numpy as np
from transformers import AutoModelForCausalLM, AutoTokenizer
from abc import ABC
from langchain.llms.base import LLM
from langchain.callbacks.manager import CallbackManagerForLLMRun
from langchain_community.vectorstores import FAISS
from langchain_community.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.docstore.document import Document
from langchain.prompts.prompt import PromptTemplate
from langchain.chains import RetrievalQA
from typing import Any, List, Mapping, Optional, Tuple# Flask应用初始化
app = Flask(__name__)
CORS(app)  # 允许跨域device = "cuda"  # 将模型加载到cuda设备上# 从指定路径加载模型和分词器
model_path = "/data/model/Qwen2-1.5B-Instruct/"
model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype="auto", device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_path)# 定义Qwen模型类,继承自LangChain的LLM基类
class Qwen(LLM, ABC):max_token: int = 10000  # 最大token数temperature: float = 0.01  # 温度参数,用于控制生成文本的多样性top_p = 0.9  # top-p采样参数history_len: int = 3  # 对话历史长度def __init__(self):super().__init__()@propertydef _llm_type(self) -> str:return "Qwen"@propertydef _history_len(self) -> int:return self.history_lendef set_history_len(self, history_len: int = 10) -> None:self.history_len = history_lendef _call(self,prompt: str,stop: Optional[List[str]] = None,run_manager: Optional[CallbackManagerForLLMRun] = None,) -> str:# 构造对话消息messages = [{"role": "system", "content": "你是一个非常专业的人工智能助手."},{"role": "user", "content": prompt}]# 将消息应用模板并生成文本text = tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True)# 将文本转换为模型输入model_inputs = tokenizer([text], return_tensors="pt").to(device)# 生成响应文本generated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512)# 获取生成的文本generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]return response@propertydef _identifying_params(self) -> Mapping[str, Any]:"""获取识别参数"""return {"max_token": self.max_token,"temperature": self.temperature,"top_p": self.top_p,"history_len": self.history_len}# 中文文本拆分器类,继承自CharacterTextSplitter
class ChineseTextSplitter(CharacterTextSplitter):def __init__(self, pdf: bool = False, **kwargs):super().__init__(**kwargs)self.pdf = pdfdef split_text(self, text: str) -> List[str]:if self.pdf:# 处理PDF文本格式text = re.sub(r"\n{3,}", "\n", text)text = re.sub('\s', ' ', text)text = text.replace("\n\n", "")# 定义句子分隔模式sent_sep_pattern = re.compile('([﹒﹔﹖﹗.。!?]["’”」』]{0,2}|(?=["‘“「『]{1,2}|$))')sent_list = []for ele in sent_sep_pattern.split(text):if sent_sep_pattern.match(ele) and sent_list:sent_list[-1] += eleelif ele:sent_list.append(ele)return sent_list# 加载文件并进行拆分
def load_file(filepath):loader = TextLoader(filepath, autodetect_encoding=True)textsplitter = ChineseTextSplitter(pdf=False)docs = loader.load_and_split(textsplitter)write_check_file(filepath, docs)return docs# 将拆分后的文档写入检查文件
def write_check_file(filepath, docs):folder_path = os.path.join(os.path.dirname(filepath), "tmp_files")if not os.path.exists(folder_path):os.makedirs(folder_path)fp = os.path.join(folder_path, 'load_file.txt')with open(fp, 'a+', encoding='utf-8') as fout:fout.write("filepath=%s,len=%s" % (filepath, len(docs)))fout.write('\n')for i in docs:fout.write(str(i))fout.write('\n')fout.close()# 将连续的列表分离成多个子列表
def separate_list(ls: List[int]) -> List[List[int]]:lists = []ls1 = [ls[0]]for i in range(1, len(ls)):if ls[i - 1] + 1 == ls[i]:ls1.append(ls[i])else:lists.append(ls1)ls1 = [ls[i]]lists.append(ls1)return lists# FAISS向量搜索包装类
class FAISSWrapper(FAISS):chunk_size = 250chunk_conent = Truescore_threshold = 0def similarity_search_with_score_by_vector(self, embedding: List[float], k: int = 4, filter=None,fetch_k: Optional[int] = None) -> List[Tuple[Document, float]]:if filter:embedding = [e for e, f in zip(embedding, filter) if f]if fetch_k is not None:k = fetch_kscores, indices = self.index.search(np.array([embedding], dtype=np.float32), k)docs = []id_set = set()store_len = len(self.index_to_docstore_id)for j, i in enumerate(indices[0]):if i == -1 or 0 < self.score_threshold < scores[0][j]:# 当返回的文档数量不足时会发生这种情况continue_id = self.index_to_docstore_id[i]doc = self.docstore.search(_id)if not self.chunk_conent:if not isinstance(doc, Document):raise ValueError(f"Could not find document for id {_id}, got {doc}")doc.metadata["score"] = int(scores[0][j])docs.append(doc)continueid_set.add(i)docs_len = len(doc.page_content)for k in range(1, max(i, store_len - i)):break_flag = Falsefor l in [i + k, i - k]:if 0 <= l < len(self.index_to_docstore_id):_id0 = self.index_to_docstore_id[l]doc0 = self.docstore.search(_id0)if docs_len + len(doc0.page_content) > self.chunk_size:break_flag = Truebreakelif doc0.metadata["source"] == doc.metadata["source"]:docs_len += len(doc0.page_content)id_set.add(l)if break_flag:breakif not self.chunk_conent:return docsif len(id_set) == 0 and self.score_threshold > 0:return []id_list = sorted(list(id_set))id_lists = separate_list(id_list)for id_seq in id_lists:for id in id_seq:if id == id_seq[0]:_id = self.index_to_docstore_id[id]doc = self.docstore.search(_id)else:_id0 = self.index_to_docstore_id[id]doc0 = self.docstore.search(_id0)doc.page_content += " " + doc0.page_contentif not isinstance(doc, Document):raise ValueError(f"Could not find document for id {_id}, got {doc}")doc_score = min([scores[0][id] for id in [indices[0].tolist().index(i) for i in id_seq if i in indices[0]]])doc.metadata["score"] = int(doc_score)docs.append((doc, doc_score))#print(docs)  #在这里可以看向量搜索结果,然后大模型根据这个结果去总结答案return docs# 加载QA数据
def load_qa_data(filepath):with open(filepath, 'r', encoding='utf-8') as f:data = json.load(f)return data# 获取文档的上下文信息
def get_context(document):return document.metadata.get("context_str", "No relevant information available.")@app.route('/query', methods=['POST'])
def handle_query():data = request.jsonquery = data.get("query")if not query:return jsonify({"error": "No query provided"}), 400# 使用检索到的上下文构造请求并调用模型response = qa.invoke({"query": query})return jsonify({"response": response})if __name__ == '__main__':# 加载QA数据qa_data_path = '/data/xxxxx/xxxx_qa_pairs.json'qa_data = load_qa_data(qa_data_path)# 处理每个QA对docs = []for item in qa_data:paragraph = item["paragraph"]for qa_pair in item["qa_pairs"]:question = qa_pair["q"]expected_answer = qa_pair["a"]docs.append(Document(page_content=paragraph, metadata={"source": paragraph,"context_str": paragraph,"question": question, "expected_answer": expected_answer}))# 嵌入模型名称EMBEDDING_MODEL = 'text2vec'PROMPT_TEMPLATE = """已知信息:{context_str} 根据上面的已知信息, respond to the user's question concisely and professionally. If an answer cannot be derived from it, say 'The question cannot be answered with the given information' or 'Not enough relevant information has been provided,' and do not include fabricated details in the answer. 问题是 {question}"""# 嵌入运行设备EMBEDDING_DEVICE = "cuda"# 从向量存储返回top-k文本块VECTOR_SEARCH_TOP_K = 5 #向量搜索结果太多的话可以改小点CHAIN_TYPE = 'stuff'embedding_model_dict = {"text2vec": "/data/model/text2vec-base-chinese/",}llm = Qwen()embeddings = HuggingFaceEmbeddings(model_name=embedding_model_dict[EMBEDDING_MODEL], model_kwargs={'device': EMBEDDING_DEVICE})# 初始化FAISS向量存储docsearch = FAISSWrapper.from_documents(docs, embeddings)prompt = PromptTemplate(template=PROMPT_TEMPLATE, input_variables=["context_str", "question"])chain_type_kwargs = {"prompt": prompt, "document_variable_name": "context_str"}qa = RetrievalQA.from_chain_type(llm=llm,chain_type=CHAIN_TYPE, retriever=docsearch.as_retriever(search_kwargs={"k": VECTOR_SEARCH_TOP_K}), chain_type_kwargs=chain_type_kwargs)app.run(host='0.0.0.0', port=5000)  # 启动Flask服务

上面的代码用到了text2vec,下载地址:

https://huggingface.co/shibing624/text2vec-base-chinese

千问大模型下载地址:

https://huggingface.co/Qwen/Qwen2-1.5B-Instruct

千问模型帮助文档资料:

https://qwen.readthedocs.io/en/latest/framework/Langchain.html

上面的代码的原始版本来源是(没法直接运行):

https://qwen.readthedocs.io/en/latest/_sources/framework/Langchain.rst.txt

运行程序以后可以通过postman调用,调用的测试数据样式可以是下面这样:

{"query": "你的问题内容"}

经过测试,我发现有时间回答的很好,好到你的问题是上下文数据的变种及涉及到计算才可以的都能回答的很好,有时候抽风到拿着问答对里面的问题去问大模型都不行,另外还有刚启动回答的不好,多问一会能稍微好点。

比如原始数据是你赚10000块钱的话需要缴税,你问说我每个月工资9999要不要缴税,大模型能回答不需要,再问我的月薪10001要不要缴税,大模型也能答对,注意这不是问答对里面的问题,就是大模型学习以后给出的答案,要是每次这样就挺好,可惜啊。

本文首发于http://blog.csdn.net/peihexian,不欢迎转载。

这篇关于基于langchain和向量库微调qwen2-1.5B学习行业专属知识的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1072860

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

sqlite3 相关知识

WAL 模式 VS 回滚模式 特性WAL 模式回滚模式(Rollback Journal)定义使用写前日志来记录变更。使用回滚日志来记录事务的所有修改。特点更高的并发性和性能;支持多读者和单写者。支持安全的事务回滚,但并发性较低。性能写入性能更好,尤其是读多写少的场景。写操作会造成较大的性能开销,尤其是在事务开始时。写入流程数据首先写入 WAL 文件,然后才从 WAL 刷新到主数据库。数据在开始

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/