序言
Qwen-VL-Chat的Qlora微调有很多博主都讲的非常好,我也是参考的几个博主的文章之后,成功进行了微调。由于我是一个小白,在微调过程中,遇到了一些问题,然而许多博主并没有提及到这些问题,所以写这篇文章有两个目的:(1)记录自己的学习;(2)如果有向我一样刚接触这一块的小白,根据这篇文章可以更好,更方便的去动手微调。
情况说明:本微调教程是在AutoDL平台上租用容器实例进行的,如果有小伙伴是用自己的电脑进行本地微调,那么需要自行根据需求做出相应调整(我没试过,所以不做评价)。
建议及推荐:
- 显存至少需要10GB以上,低于10GB微调无法进行。
- python 3.8及以上版本
- pytorch 1.12及以上版本,推荐2.0及以上版本
- 建议使用CUDA 11.4及以上(GPU用户需考虑此选项)
目录
模型部署
1.1 下载Qwen-VL-chat模型
首先在AutoDL平台上租一台容器实例,我租用的配置如下图所示:
我下载模型用的是modelscope,官网为:魔搭社区。(使用modelscope下载速度更快)
# 创建虚拟环境 conda create --name qwenvl python=3.10 -y # 激活虚拟环境 source activate qwenvl # 下载modelscope工具,用于下载qwen-vl-chat模型 pip install modelscope # 下载qwen-vl-chat模型 # --cache_dir xxpathxx (xxpathxx可更换你的存储文件夹路径) modelscope download --model qwen/qwen-vl-chat --cache_dir '/root/autodl-tmp'
1.2 下载Qwen-VL-Chat所需要的依赖环境
pip install modelscope -U pip install transformers accelerate tiktoken -U pip install einops transformers_stream_generator -U pip install "pillow==9.*" -U pip install torchvision pip install matplotlib -U pip install bitsandbytes
1.3 使用Qwen-VL-Chat进行推理
准备一张图片,我们一起去试一下利用Qwen-Vl-Chat进行推理,看模型能不能跑起来。运行代码如下(modelscope上有两种调用方式,这里选取一种):
import os os.environ['CUDA_VISIBLE_DEVICES'] = '0' from modelscope import ( snapshot_download, AutoModelForCausalLM, AutoTokenizer, GenerationConfig, ) from transformers import BitsAndBytesConfig import torch #model_id = 'qwen/Qwen-VL-Chat' #revision = 'v1.1.0' #model_dir = snapshot_download(model_id, revision=revision) # model_dir为大模型路径,你可以根据自己的需求进行修改 model_dir = '/root/autodl-tmp/qwen/qwen-vl-chat' torch.manual_seed(1234) quantization_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.float16, bnb_4bit_quant_type='nf4', bnb_4bit_use_double_quant=True, llm_int8_skip_modules=['lm_head', 'attn_pool.attn']) tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained(model_dir, device_map="auto", trust_remote_code=True, fp16=True, quantization_config=quantization_config).eval() model.generation_config = GenerationConfig.from_pretrained(model_dir, trust_remote_code=True) query = tokenizer.from_list_format([ {'image': '/root/autodl-tmp/Data/image.jpg'}, {'text': '这是什么'}, ]) response, history = model.chat(tokenizer, query=query, history=None) print(response) response, history = model.chat(tokenizer, '输出检测框', history=history) print(response) image = tokenizer.draw_bbox_on_latest_picture(response, history) image.save('/root/autodl-tmp/Data/output_chat2.jpg')
正常运行该代码后,可以看到如下结果,说明模型跑通了,及Qwen-VL-Chat部署成功,及可以进行后续操作了。
数据准备
2.1 数据格式要求
你的图片数据集需要转换成Json数据形式,才能进行训练。Json数据格式如下所示:
[ { "id": "identity_0", "conversations": [ { "from": "user", "value": "Picture 1: <img>/root/autodl-tmp/check/ImageInput/部件数据/0001201905261055369600000110202000200000000000000000000000.jpg</img>\n描述下图片的主要对象以及他们的位置信息" }, { "from": "assistant", "value": "<ref>复合绝缘子</ref><box>(760,414),(844,892)</box> <ref>防震锤</ref><box>(341,814),(446,873)</box> <ref>防震锤</ref><box>(337,911),(441,971)</box> <ref>挂点金具</ref><box>(731,941),(805,1080)</box> <ref>均压环</ref><box>(737,873),(820,941)</box> <ref>均压环</ref><box>(784,371),(865,431)</box>" } ] }, { "id": "identity_1", "conversations": [ { "from": "user", "value": "Picture 2: <img>/root/autodl-tmp/check/ImageInput/部件数据/0001201905261057013250000120202000200000000000000000000000.jpg</img>\n描述下图片的主要对象以及他们的位置信息" }, { "from": "assistant", "value": "<ref>复合绝缘子</ref><box>(671,748),(768,1080)</box> <ref>复合绝缘子</ref><box>(844,735),(948,1080)</box> <ref>避雷器</ref><box>(529,522),(645,1072)</box> <ref>均压环</ref><box>(641,677),(769,787)</box> <ref>均压环</ref><box>(811,666),(940,773)</box>" } ] } ]
2.2 数据预处理
如果你准备的图谱数据集如下图所示,同时有.jpg图片和对应的.xml信息,那接下来我教您如何将数据集转换成所需要的json数据集。
将你的数据集,进行预处理,转换成符合要求的数据格式。转换代码如下:
import json import os import xml.etree.ElementTree as ET # 初始化JSON数据结构 all_data = [] # 指定XML文件所在的目录 xml_directory = '/root/autodl-tmp/Data/ImageInput/部件数据' i = 0 # 遍历目录下的所有XML文件 for filename in os.listdir(xml_directory): if filename.endswith('.xml'): # 构建完整的文件路径 file_path = os.path.join(xml_directory, filename) i = i + 1 # 解析XML文件 tree = ET.parse(file_path) root = tree.getroot() file_name = root.find('filename').text json_data = { "id": f"identity_{i - 1}", "conversations": [] } conversation1 = { "from": "user", "value": f"Picture {i}: <img>/root/autodl-tmp/Data/ImageInput/部件数据/{file_name}</img>\n描述下图片的主要对象以及他们的位置信息" } json_data['conversations'].append(conversation1) conversation = { "from": "assistant", "value": "" } for obj_elem in root.findall('object'): # 提取必要的数据 object_name = obj_elem.find('name').text bbox = obj_elem.find('bndbox') if bbox is not None: # 确保bndbox元素存在 xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) else: # 处理bndbox不存在的情况 print(f"Warning: No bounding box found in {filename}") continue a = f'<ref>{object_name}</ref><box>({xmin},{ymin}),({xmax},{ymax})</box>' # 创建单个conversation对象并添加到JSON数组中 if conversation["value"] == "": conversation["value"] = a else: conversation["value"] += " " + a json_data['conversations'].append(conversation) all_data.append(json_data) # 将JSON对象转换为字符串并保存到文件中 with open('/root/autodl-tmp/Data/ImageJsonData', 'w', encoding='utf-8') as f: json.dump(all_data, f, ensure_ascii=False, indent=2) print("转换完成,所有XML文件的数据已整合到json文件中。")
该代码中的注意事项:在代码处该三处位置,根据自己的需求进行修改。
(1)
(2)
(3)
正常运行该代码后,会有如下结果和json数据:
模型微调
3.1 Qwen-VL-Chat微调相关文件下载
Qwen-VL-Chat模型部署好,而且json数据集准备好之后,还需要下载一个用于模型微调的文件。
git clone https://github.com/QwenLM/Qwen-VL.git
如果使用git clone 失败或者报错,你可以直接进入该网站,下载ZIP文件,解压缩后导入Autodl中的容器实例。
文件内容如下:
3.2 Qwen-VL-Chat微调所需的依赖环境
# 在终端窗口输入指令,进入到刚刚下载得Qwen-VL文件 cd autodl-tmp/qwen/Qwen-VL # 根据requierment下载依赖环境 pip install -r requirements.txt pip install -r requirements_openai_api.txt pip install -r requirements_web_demo.txt pip install peft
3.3 Qwen-VL-Chat开始微调
在Qwen-VL文件夹下,创建.sh文件,命名为qlora.sh。并将下列指令复制到qlora.sh文件中。指令如下:
python3 /root/autodl-tmp/qwen/Qwen-VL/finetune.py --model_name_or_path /root/autodl-tmp/qwen/qwen-vl-chat --data_path /root/autodl-tmp/Data/ImageJsonData/data.json --bf16 True --fix_vit True --output_dir output_qwen --num_train_epochs 5 --per_device_train_batch_size 1 --per_device_eval_batch_size 1 --gradient_accumulation_steps 8 --evaluation_strategy "no" --save_strategy "steps" --save_steps 1000 --save_total_limit 10 --learning_rate 1e-5 --weight_decay 0.1 --adam_beta2 0.95 --warmup_ratio 0.01 --lr_scheduler_type "cosine" --logging_steps 1 --report_to "none" --model_max_length 600 --lazy_preprocess True --gradient_checkpointing true --use_lora
注意事项:该指令必须是一行,不能有手动换行。如下图片是为了方便大家查看。
然后在终端执行如下命令:
sh qlora.sh
如果出现权限问题,请自行查询解决方法。
正确运行开始微调时如图所示(如遇到报错,此步骤后有详细讲解,请参考):
如果遇到如下错误,可以根据下列操作进行解决。
错误一:运行 sh qlora.sh时,出现如下报错。且执行pip install deepspeed后同样报错。
解决方法:
# 添加下载源 conda config --add channels https://anaconda.org # 添加完之后在进行下载 pip install deepspeed
错误二:运行 sh qlora.sh时,出现如下错误。
解决方法:
(1)在1.1步骤中下载的Qwen-VL-Chat模型中,找到modeling_qwen.py文件。
(2)打开modeling_qwen.py文件,在代码654行处,作出如下修改。
3.4 将微调好的Qwen-VL-Chat与原模型进行合并
在微调好之后,需要将微调好的Qwen-VL-Chat模型与原大模型进行合并,合并之后就可以正常推理了。合并代码如下:
from peft import AutoPeftModelForCausalLM # 确保导入所需的模块 from modelscope import ( AutoTokenizer ) path_to_adapter = "/root/autodl-tmp/qwen/Qwen-VL/output_qwen" # 从预训练模型中加载自定义适配器模型 model = AutoPeftModelForCausalLM.from_pretrained( path_to_adapter, # 适配器的路径 device_map="auto", # 自动映射设备 trust_remote_code=True # 信任远程代码 ).eval() # 设置为评估模式 new_model_directory = "/root/autodl-tmp/qwen/New-Model-qwenvl" tokenizer = AutoTokenizer.from_pretrained( path_to_adapter, trust_remote_code=True, ) tokenizer.save_pretrained(new_model_directory) # 合并并卸载模型 merged_model = model.merge_and_unload() # 保存合并后的模型 merged_model.save_pretrained(new_model_directory, max_shard_size="2048MB", safe_serialization=True)
注意事项:
如若出现错误:AttributeError: 'QWenTokenizer' object has no attribute 'IMAGE_ST'
请参考以下链接:
3.5 对新Qwen-VL-Chat模型进行推理测试
推理测试的代码与1.3中的代码一致,只需将模型路径修改成微调后新模型的路径即可。测试输入的数据自己根据需求修改即可。
输出结果:
测试通过!!!!!!!!!!!!!
完结撒花!!!!!!!!!!!!!
参考文章:
通义千问(Qwen-VL)本地微调_qwen-vl 微调-CSDN博客
Qwen-VL Best Practice — swift 2.5.0.dev0 documentation
Qwen-VL大模型LoRA微调、融合及部署_qwen-vl微调-CSDN博客
通义千问-VL-Chat · 模型库 (modelscope.cn)
Update modeling_qwen.py · Qwen/Qwen-VL-Chat at e718765 (huggingface.co)