当前位置: 首页 > news >正文

通义千问1.5-1.8B-Chat-GPTQ-Int4 使用CSDN博客文章进行领域知识微调

通义千问1.5-1.8B-Chat-GPTQ-Int4 使用CSDN博客文章进行领域知识微调

想让大模型更懂你的专业领域吗?比如,你想让它精通后端开发、前端框架或者某个特定的技术栈,回答得更专业、更贴切。今天,我们就来手把手教你一个实用技巧:利用技术社区里海量的公开博客文章,为你自己的通义千问模型“开小灶”,进行领域知识微调。

这个过程就像给模型做一次定向的知识补充。我们以CSDN上的技术博客为例,从如何获取和清洗这些文章数据,到编写微调脚本,最后在云端GPU上完成训练,带你走完全程。即使你之前没怎么接触过模型微调,跟着步骤走,也能让你的模型变得更“懂行”。

1. 准备工作:理解我们要做什么

在开始动手之前,我们先花几分钟,把整个事情的脉络理清楚。这能帮你更好地理解每一步的目的,而不是机械地敲命令。

简单来说,大模型就像一位知识渊博但领域宽泛的“通才”。它知道很多,但对某个非常垂直、非常新的技术细节,可能就不如天天泡在相关论坛里的“专家”了。我们的目标,就是通过微调,让这位“通才”在你关心的领域,变得更像“专家”。

微调不是从头训练一个模型,那需要海量数据和算力。我们是在一个已经训练好的、能力很强的基座模型(比如通义千问1.5-1.8B-Chat)基础上,用一批新的、高质量的领域数据(比如CSDN上关于“Spring Cloud”的精品文章)对它进行“再教育”。模型会调整它内部的一部分参数,从而学会这些新知识,并在回答相关问题时,能调用这些知识,给出更专业的回复。

我们这次会使用一种叫LoRA(Low-Rank Adaptation)的高效微调方法。它的好处是训练速度快,需要的显存少,而且只会新增一小部分参数,不会改动原始模型的大部分“记忆”。训练完成后,你可以很方便地加载或卸载这个“知识扩展包”,非常灵活。

整个流程可以拆解成几个关键步骤:找数据、洗数据、准备训练脚本、跑训练、测试效果。下面我们就一步步来。

2. 第一步:获取与清洗领域数据

数据是微调的“粮食”,粮食的质量直接决定模型“学习”的效果。我们的目标是获取CSDN上特定主题的高质量技术文章。

2.1 数据获取思路

完全手动复制粘贴显然不现实。我们可以通过编写简单的爬虫脚本来批量获取。这里需要特别注意,任何数据获取行为都必须遵守网站的服务条款和robots.txt协议,尊重版权,仅用于个人学习与研究,且不应对目标网站造成访问压力。

一个常见的思路是,通过CSDN的搜索功能,找到特定关键词下的文章列表页,然后解析列表页获取文章链接,最后再逐个访问文章详情页抓取标题和正文内容。

下面是一个高度简化的、用于演示原理的示例。在实际操作中,你需要处理反爬机制、分页、动态加载等问题,并确保请求频率友好。

import requests from bs4 import BeautifulSoup import time import json def fetch_article_links(keyword, max_pages=3): """获取文章链接列表(示例,需根据实际网页结构调整)""" base_url = f"https://so.csdn.net/so/search?q={keyword}" links = [] for page in range(1, max_pages + 1): # 注意:实际URL结构可能不同,这里仅为示例 url = f"{base_url}&t=blog&p={page}" try: resp = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}) soup = BeautifulSoup(resp.text, 'html.parser') # 这里需要根据实际的HTML结构找到文章链接的标签 # 例如,可能是 class='main-item' 下的 'a' 标签 for item in soup.find_all('a', class_='some-article-class'): # 请替换为真实class href = item.get('href') if href and 'article/details' in href: links.append(href) time.sleep(1) # 礼貌性延迟,避免请求过快 except Exception as e: print(f"获取第{page}页失败: {e}") return links def fetch_article_content(article_url): """获取单篇文章的标题和正文""" try: resp = requests.get(article_url, headers={'User-Agent': 'Mozilla/5.0'}) soup = BeautifulSoup(resp.text, 'html.parser') # 提取标题,需要根据实际页面结构调整选择器 title_tag = soup.find('h1', class_='title-article') # 常见标题class title = title_tag.text.strip() if title_tag else "No Title" # 提取正文,CSDN文章正文通常在 id='article_content' 或 class='blog-content-box' 的div里 content_div = soup.find('div', id='article_content') or soup.find('div', class_='blog-content-box') if content_div: # 清理正文中的脚本、样式等无关标签 for tag in content_div(['script', 'style', 'aside', 'nav']): tag.decompose() content = content_div.get_text(separator='\n', strip=True) else: content = "" return {'title': title, 'content': content, 'url': article_url} except Exception as e: print(f"抓取文章 {article_url} 失败: {e}") return None # 示例:抓取“Docker”相关的文章 if __name__ == '__main__': keyword = "Docker" print(f"开始抓取关键词 '{keyword}' 相关的文章链接...") article_links = fetch_article_links(keyword, max_pages=2) # 少量测试 print(f"找到 {len(article_links)} 篇文章链接。") articles = [] for link in article_links: print(f"正在抓取: {link}") article_data = fetch_article_content(link) if article_data and article_data['content']: articles.append(article_data) time.sleep(1.5) # 增加延迟,保持友好 # 保存为JSON文件 with open(f'csdn_articles_{keyword}.json', 'w', encoding='utf-8') as f: json.dump(articles, f, ensure_ascii=False, indent=2) print(f"数据已保存至 csdn_articles_{keyword}.json, 共 {len(articles)} 篇。")

重要提示:上述代码仅为演示逻辑,CSDN的页面结构可能随时变化,你需要使用浏览器的开发者工具(F12)检查元素,更新标签选择器。务必遵守网站规定,控制抓取速度和数量。

2.2 数据清洗与格式化

抓取下来的原始文本通常包含很多“噪音”,比如广告、版权声明、无关的推荐链接、代码注释中的乱码等。我们需要清洗这些数据,并将其转换成模型训练需要的格式。

对于通义千问Chat模型,通常的微调数据格式是包含“instruction”(指令)、“input”(输入)和“output”(输出)的对话格式。我们可以将博客文章内容巧妙地构造成这种格式。

import json import re def clean_text(text): """基础文本清洗""" if not text: return "" # 移除多余的空白字符(包括换行符和空格) text = re.sub(r'\s+', ' ', text) # 移除常见的无关短语(根据实际情况扩充) noise_phrases = [ '版权声明:本文为博主原创文章', '遵循 CC 4.0 BY-SA 版权协议', '转载请附上原文出处链接', '如有侵权,请联系删除', '感谢您的阅读', '如果觉得有帮助,请点个赞' ] for phrase in noise_phrases: text = text.replace(phrase, '') return text.strip() def format_to_conversation(article_data): """将单篇文章数据格式化为对话样本""" title = article_data.get('title', '') content = clean_text(article_data.get('content', '')) # 如果内容太短,可能质量不高,可以过滤掉 if len(content) < 300: return None # 构建一个简单的指令-输出对 # 指令:要求模型根据标题生成或总结内容 # 输入:标题(可选) # 输出:文章正文 formatted_sample = { "instruction": "请根据以下技术主题,生成一篇详细的技术博客文章。", "input": title, "output": content } # 另一种常见格式是问答对,可以从文章中提取或生成Q&A # 例如,将小标题作为问题,后续段落作为答案(这里简化处理) return formatted_sample # 加载并处理抓取的数据 input_file = 'csdn_articles_Docker.json' output_file = 'cleaned_training_data.jsonl' formatted_samples = [] with open(input_file, 'r', encoding='utf-8') as f: raw_articles = json.load(f) for article in raw_articles: sample = format_to_conversation(article) if sample: formatted_samples.append(sample) # 保存为JSONL格式(每行一个JSON对象),这是许多训练工具支持的格式 with open(output_file, 'w', encoding='utf-8') as f: for sample in formatted_samples: f.write(json.dumps(sample, ensure_ascii=False) + '\n') print(f"数据清洗与格式化完成。共生成 {len(formatted_samples)} 条训练样本,已保存至 {output_file}")

清洗后,你就得到了一份干净的、格式规范的训练数据集。数据质量比数量更重要,几百篇高质量文章的效果可能优于几千篇低质量文章。

3. 第二步:准备微调环境与脚本

有了数据,我们接下来准备训练环境。本地训练大模型对显卡要求很高,因此我们选择在云端的GPU平台上进行。这里以星图GPU平台为例,它提供了预置的环境和资源,能省去很多环境配置的麻烦。

3.1 环境与模型准备

首先,你需要一个支持PyTorch和深度学习库的环境。星图平台的镜像通常已经配置好了基础环境。我们主要需要安装一些特定的微调库。

我们将使用transformersdatasetspeft(Parameter-Efficient Fine-Tuning)库。peft库包含了LoRA等高效微调方法的实现。

# 在星图平台的Notebook或终端中,通常基础环境已具备 # 可能需要安装或更新以下库 pip install transformers datasets accelerate peft -U pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 根据CUDA版本选择

接下来,下载我们要微调的基础模型:通义千问1.5-1.8B-Chat的GPTQ-Int4量化版本。量化模型体积更小,加载和推理更快,适合在资源有限的情况下进行微调。

from transformers import AutoTokenizer, AutoModelForCausalLM model_name_or_path = "Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4" # 模型在Hugging Face上的名称 # 加载tokenizer和模型 tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) model = AutoModelForCausalLM.from_pretrained( model_name_or_path, device_map="auto", # 自动分配到可用设备(GPU) trust_remote_code=True # 通义千问模型需要此参数 ) print("模型与分词器加载完毕。")

3.2 编写LoRA微调脚本

现在,我们来编写核心的训练脚本。我们会使用peft库来配置LoRA参数,并使用transformersTrainerAPI来管理训练过程。

创建一个名为train_lora.py的脚本文件:

import json from datasets import Dataset from transformers import ( AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer ) from peft import LoraConfig, TaskType, get_peft_model import torch # 1. 加载模型和分词器 model_name_or_path = "Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4" tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True) # 设置padding token(如果tokenizer没有) if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token model = AutoModelForCausalLM.from_pretrained( model_name_or_path, device_map="auto", torch_dtype=torch.float16, # 使用半精度浮点数节省显存 trust_remote_code=True ) # 2. 加载并预处理数据集 def load_and_preprocess_data(file_path): data = [] with open(file_path, 'r', encoding='utf-8') as f: for line in f: data.append(json.loads(line)) def preprocess_function(examples): # 构建模型输入的文本格式。通义千问Chat格式通常为: # <|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{user_message}<|im_end|>\n<|im_start|>assistant\n{assistant_message}<|im_end|> texts = [] for inst, inp, outp in zip(examples['instruction'], examples['input'], examples['output']): # 如果input不为空,则与instruction拼接作为用户输入 user_msg = f"{inst}\n{inp}" if inp else inst # 构建完整对话文本 message = [ {"role": "system", "content": "你是一个技术专家,请根据用户要求生成专业的技术内容。"}, {"role": "user", "content": user_msg}, {"role": "assistant", "content": outp} ] text = tokenizer.apply_chat_template(message, tokenize=False, add_generation_prompt=False) texts.append(text) return {"text": texts} dataset = Dataset.from_list(data) tokenized_dataset = dataset.map( preprocess_function, batched=True, remove_columns=dataset.column_names ) return tokenized_dataset train_dataset = load_and_preprocess_data("cleaned_training_data.jsonl") print(f"训练数据集加载完成,样本数:{len(train_dataset)}") # 3. 配置LoRA lora_config = LoraConfig( task_type=TaskType.CAUSAL_LM, # 因果语言模型任务 r=8, # LoRA的秩(rank),影响参数量,通常8或16 lora_alpha=32, # 缩放参数 lora_dropout=0.1, # Dropout概率 target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 对Transformer的注意力模块应用LoRA bias="none" ) model = get_peft_model(model, lora_config) model.print_trainable_parameters() # 打印可训练参数量,应该只占原模型很小一部分 # 4. 配置训练参数 training_args = TrainingArguments( output_dir="./qwen1.5-1.8b-csdn-lora", # 输出目录 num_train_epochs=3, # 训练轮数,根据数据集大小调整 per_device_train_batch_size=4, # 每个GPU的批次大小,根据显存调整 gradient_accumulation_steps=4, # 梯度累积步数,模拟更大批次 warmup_steps=100, # 学习率预热步数 logging_steps=50, # 每多少步打印一次日志 save_steps=500, # 每多少步保存一次检查点 save_total_limit=2, # 最多保存的检查点数量 learning_rate=2e-4, # 学习率,LoRA通常可以设大一点 fp16=True, # 使用混合精度训练,节省显存并加速 push_to_hub=False, # 不上传到Hugging Face Hub report_to="none", # 不报告到其他平台 remove_unused_columns=False, ) # 5. 创建Trainer并开始训练 trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, tokenizer=tokenizer, data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True), ) print("开始训练...") trainer.train() print("训练完成!") # 6. 保存LoRA适配器权重 model.save_pretrained("./qwen1.5-1.8b-csdn-lora-final") print("LoRA权重已保存。")

这个脚本涵盖了从数据加载、LoRA配置到训练启动的全过程。你需要根据自己实际的GPU显存大小,调整per_device_train_batch_sizegradient_accumulation_steps参数。

4. 第三步:在星图GPU平台运行训练

脚本准备好了,我们把它放到云端GPU上运行。

  1. 上传资源:将你的cleaned_training_data.jsonl数据集和train_lora.py训练脚本,上传到星图GPU平台为你分配的工作空间(比如通过JupyterLab的文件上传功能)。
  2. 调整参数(可选):根据你的数据集大小和GPU型号(比如V100 16GB, A100等),你可能需要回头调整脚本中的num_train_epochsper_device_train_batch_size等参数。显存不够就调小批次大小或增加梯度累积步数。
  3. 启动训练:在平台的终端(Terminal)中,运行以下命令:
cd /your/work/path # 进入你的工作目录 python train_lora.py

训练开始后,终端会输出损失值(loss)等日志信息。看到loss值稳步下降并逐渐趋于平稳,通常意味着训练正在有效进行。整个过程可能需要几十分钟到几小时,取决于数据量、epoch数和GPU性能。

训练完成后,会在指定的输出目录(./qwen1.5-1.8b-csdn-lora-final)生成保存的LoRA权重文件(通常是adapter_model.binadapter_config.json)。

5. 第四步:测试与使用微调后的模型

训练结束,我们来验证一下效果。编写一个简单的测试脚本,加载基础模型和我们刚训练好的LoRA权重,进行对话测试。

创建一个test_model.py文件:

from transformers import AutoTokenizer, AutoModelForCausalLM from peft import PeftModel import torch # 加载基础模型和分词器 base_model_name = "Qwen/Qwen1.5-1.8B-Chat-GPTQ-Int4" tokenizer = AutoTokenizer.from_pretrained(base_model_name, trust_remote_code=True) base_model = AutoModelForCausalLM.from_pretrained( base_model_name, device_map="auto", torch_dtype=torch.float16, trust_remote_code=True ) # 加载LoRA权重 lora_model_path = "./qwen1.5-1.8b-csdn-lora-final" model = PeftModel.from_pretrained(base_model, lora_model_path) model = model.merge_and_unload() # 可选:将LoRA权重合并到基础模型中,方便后续单独部署 model.eval() # 定义对话函数 def chat_with_model(user_input): messages = [ {"role": "system", "content": "你是一个精通Docker容器技术的专家。"}, # 可以根据微调领域修改 {"role": "user", "content": user_input} ] text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = tokenizer(text, return_tensors="pt").to(model.device) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=512, # 生成的最大长度 do_sample=True, # 使用采样 temperature=0.8, # 温度参数,控制随机性 top_p=0.9 # 核采样参数 ) response = tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True) return response # 进行测试 test_questions = [ "请详细解释Dockerfile中COPY和ADD指令的区别。", "如何优化Docker镜像的体积?", "Docker容器和虚拟机的主要区别是什么?" ] for q in test_questions: print(f"用户: {q}") answer = chat_with_model(q) print(f"模型: {answer}\n{'-'*50}")

运行这个测试脚本,观察模型的回答。与微调前相比,你应该能感觉到,在微调数据涉及的领域(比如Docker),模型的回答更具体、更专业,可能还会使用一些领域内的特定术语和最佳实践,更像一个技术博客作者的口吻。

6. 一些经验与总结

走完这一遍,你应该已经成功用CSDN博客数据给通义千问模型做了一次领域知识微调。回顾一下,整个过程的关键在于数据的质量、LoRA参数的合理配置以及训练资源的有效利用。

数据方面,清洗和格式化比单纯追求数量更重要。尽量选择那些结构清晰、内容详实、代码规范的优质博文。在构造训练样本时,多思考如何让“指令-输出”对更贴合你希望模型学会的任务,比如直接总结、问答、或者代码生成。

训练过程中,如果遇到显存不足(OOM)的错误,首要任务是减小per_device_train_batch_size,或者尝试使用梯度检查点(gradient_checkpointing=True)等技巧。学习率、训练轮数这些超参数,可以根据loss曲线进行微调,避免欠拟合或过拟合。

最后,这个微调出来的模型,可以看作是你的“领域增强版”通义千问。你可以把它用于智能技术客服、代码助手、文档摘要等场景。这个方法具有很强的扩展性,不只是CSDN,任何高质量的、结构化的领域文本(如技术文档、论文、论坛精华帖)都可以作为“粮食”来喂养模型,让它持续进化,更贴合你的专属需求。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

http://www.jsqmd.com/news/456288/

相关文章:

  • Hotkey Detective:终结热键劫持的系统级诊断方案
  • 移动端人脸识别应用:Retinaface+CurricularFace轻量化部署
  • ARM Cortex-M4 DSP库实战:从CMSIS下载到Keil配置全流程(附避坑指南)
  • STM32嵌入式系统调用Hunyuan-MT 7B:边缘设备翻译方案探索
  • 智能文献解析:Zotero Reference提升学术效率的技术实践
  • SUPER COLORIZER 应对复杂场景:如何处理带有大量细节和纹理的黑白照片
  • DeOldify在影视制作中的潜力展示:为经典黑白电影片段上色
  • SIM卡区域限制突破工具:Nrfr的技术实现与场景化应用
  • 手把手教你用E2PROM 2816搭建微程序控制器(附完整实验步骤)
  • Windows Defender 深度管理指南:从禁用到完全移除的系统化方案
  • Ostrakon-VL-8B Android应用开发:离线与云端混合模式实现
  • DAMO-YOLO应用落地:医疗影像辅助标注系统中的目标定位实践
  • 语义分析神器BGE-M3:快速部署,轻松验证知识库检索准确性
  • Megatron vs DeepSpeed:如何根据你的GPU和模型规模选择最佳训练框架?
  • Flyway迁移脚本命名规范详解:从V1到R__的避坑指南与团队协作实践
  • 5分钟解决数组可视化难题!NPYViewer让NumPy数据直观呈现
  • 3分钟突破90帧:WaveTools游戏优化工具让旧电脑焕发新生
  • TwinCAT3运动控制实战:5步搞定电子齿轮与凸轮同步(基于AX5000驱动器)
  • MinerU部署教程(K8s集群版):Helm Chart一键部署,支持水平扩缩容
  • Nano-Banana异常处理指南:常见错误与解决方案
  • Verdi+DC综合实战:用Python脚本自动提取模块面积并生成可视化Excel报告
  • 车载C++以太网协议栈开发必踩的5个致命陷阱:AUTOSAR CP/Adaptive实测数据曝光,第3个90%工程师仍在犯
  • Chandra OCR新手入门:5分钟本地部署,一键识别表格/手写/公式
  • 从零开始搭建Dante靶场:手把手教你复现AD域内网渗透实战(含避坑指南)
  • MQ-2烟雾传感器模块驱动移植与数据读取实战(基于立创开发板R7FA6E2BB3CNE)
  • 从立创天猛星到地阔星:基于MSPM0G3507与STM32F103的PID电机控制项目复刻与移植实战
  • CHORD-X生成报告的多维度质量评估体系构建与可视化
  • 告别兼容性问题!手把手教你用虹科Media Converter连接不同车载以太网接口(含MATEnet/HMTD实战案例)
  • 告别反复格式化!用Ventoy制作2025年终极启动盘,Windows/Linux/macOS一网打尽
  • 地奇星GPT定时器实战:从500Hz方波到10kHz PWM输出的FSP配置与编程详解