基于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,不欢迎转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/29473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络聚合通信测试--自动化测试脚本

一 网络聚合通信测试 以下测试用例为&#xff1a; 整集群测试&#xff0c;每节点进程数从2开始以2的幂次增加至满核心&#xff1b; 测试常见的通信聚合测试8个条目 二 测试前准备 待测节点已完成OS安装及基础配置待测节点已配置完IP&#xff08;若存在IB&#xff0c;则需要配置…

第二十二篇——香农第二定律(一):为什么你的网页总是打不开?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 看似在将知识&#xff0c;实际是在讲生活和所有&#xff1b;突破边界偶尔…

gitlab 获取指定分支下指定路径文件夹的解决方案

第一步&#xff1a; 获取 accessToken 及你的 项目 id &#xff1a; 获取 accessToken ,点击用户头像进入setting 按图示操作&#xff0c;第 3 步 填写你发起请求的域名。 获取项目 id , 简单粗暴方案 进入 你项目仓库页面后 直接 源码搜索 project_id&#xff0c; value 就…

【神经网络】基于CNN(卷积神经网络)构建猫狗分类模型

文章目录 解决问题数据集探索性数据分析数据预处理数据集分割数据预处理 构建模型并训练构建模型训练模型 结果分析与评估模型保存结果预测经验总结 解决问题 针对经典猫狗数据集&#xff0c;基于卷积神经网络&#xff0c;构建猫狗二元分类模型&#xff0c;使用数据集进行参数…

怎么提取视频中的音频?别错过这6个音频提取方法了!(全新)

您是否曾经发现过一个音乐很棒的视频&#xff0c;并想从视频中提取音频&#xff1f;如今&#xff0c;关于提取mp4视频中的音频需求越来越常见。例如&#xff0c;您可能想从mp4格式的电影中提取音频&#xff0c;将音乐用作手机铃声&#xff0c;或在自己的视频项目中使用视频中的…

[Qt] Qt Creator 编码警告:warning:C4819

Qt项目使用VC&#xff08;2019 64bit&#xff09;编译器出现此错误。 warning&#xff1a;C4819&#xff1a;该文件包含不能在当前代码页&#xff08;936&#xff09;中表示的字符。请将该文件保存为Unicode格式以防止数据丢失。(可能这个警告内容也会在Qt Creator 中乱码) 如…

Matlab只选取自己需要的数据画图

在Matlab作图的时候&#xff0c;经常会在同一个坐标系中作很多数据的图&#xff0c;如下图所示&#xff1a; 这就会导致不同数据所作的线会重叠在一起&#xff0c;不利于数据分析。如果只想对比几个数据的趋势&#xff0c;直接修改代码太过麻烦&#xff0c;可通过Matlab的绘图…

【C语言】数组参数和指针参数详解

在写代码的时候难免要把【数组】或者【指针】传给函数&#xff0c;那函数的参数该如何设计呢&#xff1f; 1 一维数组传参 #include <stdio.h> void test(int arr[])//ok? {} void test(int arr[10])//ok? {} void test(int* arr)//ok? {} void test2(int* arr[20])…

Java毕业设计 基于SSM助学贷款管理系统

Java毕业设计 基于SSM助学贷款管理系统 SSM 助学贷款管理系统 功能介绍 学生&#xff1a;登录 修改密码 学生信息 贷款项目信息 申请贷款 留言信息 公告 学校负责人&#xff1a;登录 修改密码 学生管理 学校负责人信息 贷款项目 贷款申请审批 留言信息 公告 银行负责人&…

Linux中nginx.conf如何配置【搬代码】

Nginx 是一个独立的软件。 它是一款高性能的 Web 服务器、反向代理服务器和负载均衡器等&#xff0c;具有强大的功能和广泛的应用场景。它通常需要单独进行安装和配置来发挥其作用。 下载网址&#xff1a;http://nginx.org/en/download.html nginx.conf写法&#xff1a; #配置…

鸿蒙实现金刚区效果

前言&#xff1a; DevEco Studio版本&#xff1a;4.0.0.600 所谓“金刚区"是位于APP功能入口的导航区域&#xff0c;通常以“图标文字”的宫格导航的形式出现。之所以叫“金刚区”&#xff0c;是因为该区域会随着业务目标的改变&#xff0c;展示不同的功能图标&#xff…

PostgreSQL源码分析——CREATE DATABASE

这里我们分析一下在PostgreSQL中创建数据库的源码&#xff0c;在分析源码之前&#xff0c;最好先阅读《PostgreSQL指南内幕探索》的第一章&#xff0c;数据库集簇、数据库和数据表&#xff0c;弄清其空间布局&#xff0c;理解PG中&#xff0c;数据库、表、元组是怎么布局的。通…

Collections.sort()方法总结

Collections.sort()方法总结 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们来探讨 Java 中的 Collections.sort() 方法。这个方法是 Java 集合框架中的…

C# —— 字符串的相关属性和方法

string 属于特殊的引用类型 字符串创建的三种方式 string s "路飞";//自变量定义方式 字符串当中如果要有特殊的符号,使用\进行转义 \t tab \n 换行 \r return键 s "123\rb\tc\nd,想在字符串当中展示引号,需要使用\"进行转义,想输入一个右斜杠\\,想显…

下载工程resources目录下的模板excel文件

一、添加依赖 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.1.0</version> </dependency> 二、编写接口 GetMapping("/downloadTemplate")public void downlo…

C++ 70 之 类模版中的成员函数,在类外实现

#include <iostream> #include <string> using namespace std;template<class T1, class T2> class Students10{ public:T1 m_name;T2 m_age;Students10(T1 name, T2 age); // 类内声明 类外实现// {// this->m_name name;// this->m_age …

CCAA质量管理【学习笔记】​​ 备考知识点笔记(六)质量改进系统方法与工具

第七节 质量改进系统方法与工具 1 质 量 改 进 方 法 概 述 可以说几乎每种质量管理领域的方法与工具都可以用于质量改进&#xff0c;但是一个组织在改进的整体推进中&#xff0c;往往不是采用单一的方法&#xff0c;会涉及多种改进的工具和手段&#xff0c;并依据一定的模式…

鸿蒙实现自定义Tabbar样式,显示数字红点提示

前言&#xff1a; DevEco Studio版本&#xff1a;4.0.0.600 Tabs的链接参考&#xff1a;OpenHarmony Tabs TabContent的链接参考&#xff1a;OpenHarmony TabContent 通过查看链接参考我们知道可以通过TabContent的tabBar来实现自定义TabBar样式&#xff08;CustomBuilder&…

CloudTopExam考试系统

前言 整个项目的都是自己从0到1完成的&#xff08;包括数据库设计&#xff09;。 这个项目耗费了自己的很多心血&#xff0c;尤其是数据库的设计&#xff08;中途推翻重做了好几次&#x1f494;&#xff09;。在做这个之前也看过很多类似的开源项目&#xff0c;相较于商用的产品…

第六节 未登录与登录分支设立

经常我们在设计中,经常会遇到多条件分支打开相关界面,下面重点基于一个控件判断对未登录与已登录分支跳转案例进行说明。 一、设置元件 注意:动态面板默认设置 二、设置隐藏面板 三、关联条件情形 1、设置触发事件的元件 2、启用情形 3、添加情形,增加面板中“未登录”为…