
DeepSeek-VL2微调广告文案撰写案例
在本节中,我们将踏入广告文案撰写的实战领地。在此之前,我们已经深入探讨了DeepSeek-VL2微调技术中所采纳的LoRA方法,以及与之紧密相关的库包PEFT。这些尖端工具与技术,为我们的文案创作提供了强大的支持,使我们能更精准地捕捉目标受众的心理与需求。在数字化浪潮汹涌的今天,如何运用这些科技利器,打造出既富有创意又极具针对性的广告文案,将是我们探索的重点。接下来,我们将携手LoRA与PEFT,
《DeepSeek大模型高性能核心技术与多模态融合开发(人工智能技术丛书)》(王晓华)【摘要 书评 试读】- 京东图书
在本节中,我们将踏入广告文案撰写的实战领地。在此之前,我们已经深入探讨了DeepSeek-VL2微调技术中所采纳的LoRA方法,以及与之紧密相关的库包PEFT。这些尖端工具与技术,为我们的文案创作提供了强大的支持,使我们能更精准地捕捉目标受众的心理与需求。
在数字化浪潮汹涌的今天,如何运用这些科技利器,打造出既富有创意又极具针对性的广告文案,将是我们探索的重点。接下来,我们将携手LoRA与PEFT,开启广告文案撰写的新篇章,书写属于DeepSeek-VL2的精彩故事。
8.3.1 数据的准备
在本小节中,作者提供了一份基于广告文案提示词生成文案的数据集,如下所示:
{"content": "类型#裤*版型#宽松*风格#性感*图案#线条*裤型#阔腿裤", "summary": "宽松的阔腿裤这两年真的吸粉不少,明星时尚达人的心头爱。毕竟好穿时尚,谁都能穿出腿长2米的效果宽松的裤腿,当然是遮肉小能手啊。上身随性自然不拘束,面料亲肤舒适贴身体验感棒棒哒。系带部分增加设计看点,还让单品的设计感更强。腿部线条若隐若现的,性感撩人。颜色敲温柔的,与裤子本身所呈现的风格有点反差萌。"}
{"content": "类型#裙*风格#简约*图案#条纹*图案#线条*图案#撞色*裙型#鱼尾裙*裙袖长#无袖", "summary": "圆形领口修饰脖颈线条,适合各种脸型,耐看有气质。无袖设计,尤显清凉,简约横条纹装饰,使得整身人鱼造型更为生动立体。加之撞色的鱼尾下摆,深邃富有诗意。收腰包臀,修饰女性身体曲线,结合别出心裁的鱼尾裙摆设计,勾勒出自然流畅的身体轮廓,展现了婀娜多姿的迷人姿态。"}
{"content": "类型#上衣*版型#宽松*颜色#粉红色*图案#字母*图案#文字*图案#线条*衣样式#卫衣*衣款式#不规则", "summary": "宽松的卫衣版型包裹着整个身材,宽大的衣身与身材形成鲜明的对比描绘出纤瘦的身形。下摆与袖口的不规则剪裁设计,彰显出时尚前卫的形态。被剪裁过的样式呈现出布条状自然地垂坠下来,别具有一番设计感。线条分明的字母样式有着花式的外观,棱角分明加上具有少女元气的枣红色十分有年轻活力感。粉红色的衣身把肌肤衬托得很白嫩又健康。"}
……
{"content": "类型#裙*版型#宽松*材质#雪纺*风格#清新*裙型#a字*裙长#连衣裙", "summary": "踩着轻盈的步伐享受在午后的和煦风中,让放松与惬意感为你免去一身的压力与束缚,仿佛要将灵魂也寄托在随风摇曳的雪纺连衣裙上,吐露出<UNK>微妙而又浪漫的清新之意。宽松的a字版型除了能够带来足够的空间,也能以上窄下宽的方式强化立体层次,携带出自然优雅的曼妙体验。"}
其中content是提示词部分,而summary则是生成的文案。对于这个数据集,我们需要完成数据的读取操作,代码如下所示:
import torch,json
from transformers import AutoModelForCausalLM
from tqdm import tqdm
from deepseek_vl2.models import DeepseekVLV2Processor, DeepseekVLV2ForCausalLM
from deepseek_vl2.utils.io import load_pil_images
# specify the path to the model
model_path = "deepseek-ai/deepseek-vl2-tiny"
vl_chat_processor = DeepseekVLV2Processor.from_pretrained(model_path)
tokenizer = vl_chat_processor.tokenizer
file_path = "../lora_dataset/AdvertiseGen/train_small.json"
conversations = []
with open(file_path, 'r', encoding='utf-8') as file:
for line in file:
# 尝试解析每一行作为独立的JSON对象
data = json.loads(line)
content = data['content']
summary = data['summary']
if len(content) < 144 and len(summary) < 144:
conversation = {"role": "<|User|>", "content": content}, {"role": "<|Assistant|>", "content": summary + "<|end▁of▁sentence|>"}
conversations.append(conversation)
上面代码实现了数据集的读取。其中需要注意,为了模型训练的迅捷性,我们定义了文案长度为144,而重构的文本也保证了其符合原始的DeepSeek模型生成方式,并且在结尾处显式地添加了结束符"<|end▁of▁sentence|>"。
接下来,为了适配模型的训练,我们实现了Dataset与Datacollect类,代码如下所示:
class DataCollator:
def __init__(self, tokenizer):
self.tokenizer = tokenizer
self.padding_value = self.pad_token_id = tokenizer.eos_token_id
self.bos_token_id=tokenizer.bos_token_id
self.eos_token_id=tokenizer.eos_token_id
print(self.padding_value,self.bos_token_id,self.eos_token_id)
def __call__(self, instances):
input_ids ,labels = tuple([instance[key] for instance in instances] for key in ("input_ids", "labels"))
input_ids = torch.nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=self.padding_value)
labels = torch.nn.utils.rnn.pad_sequence(labels, batch_first=True, padding_value=-100)
attention_mask = input_ids.ne(self.padding_value)
return input_ids, attention_mask, labels
import torch
from torch.utils.data import Dataset
class LoraDataset(Dataset):
def __init__(self, conversations):
super(LoraDataset, self).__init__()
self.conversations = conversations
DataCollator类是一个用于数据整理的辅助类,它主要用于将一批实例(instances)整理成模型训练所需的格式。在初始化时,它接收一个tokenizer对象,并从中获取填充值(padding_value)、开始符号ID(bos_token_id)和结束符号ID(eos_token_id)。这些值在后续的数据处理中会被用到。当调用__call__方法时,DataCollator会接收一批实例,提取出其中的input_ids和labels,然后使用torch.nn.utils.rnn.pad_sequence方法对它们进行填充,使它们具有相同的长度,以便批量处理。同时,它还会生成一个attention_mask,用于指示哪些位置是填充的,哪些位置是有效的输入。最终,DataCollator返回处理后的input_ids、attention_mask和labels。
def __len__(self):
return len(self.conversations)
def __getitem__(self, idx):
conversation = self.conversations[idx]
pil_images = load_pil_images(conversation)
prepare_inputs = vl_chat_processor(
conversations=conversation,
images=pil_images,
force_batchify=True,
system_prompt=""
)
input_ids = prepare_inputs.input_ids
labels = prepare_inputs.labels
return dict(input_ids=input_ids[0], labels=labels[0])
LoraDataset类是一个继承自torch.utils.data.Dataset的自定义数据集类,用于加载和处理对话数据。在初始化时,它接收一个conversations列表,该列表包含了所有的对话数据。__len__方法返回数据集的大小,即对话的数量。__getitem__方法则根据索引idx从conversations列表中获取对应的对话,并通过一系列处理(如加载图片、准备输入等)将其转换成模型所需的输入格式。具体来说,它会调用load_pil_images函数加载对话中的图片,然后使用vl_chat_processor处理对话和图片,生成input_ids和labels。最后,它将input_ids和labels的第一个元素(假设每个对话只对应一个输入和一个标签)打包成一个字典并返回,以便后续的数据加载和模型训练。
8.3.2 微调模型的训练
接下来,我们需要完成基于DeepSeek-VL2的微调模型训练。前面已经讲解了PEFT的使用以及LoRA的原理,这里我们只需要基于这些经典方法完成模型搭建并开始训练,代码如下所示:
import torch
from transformers import AutoModelForCausalLM
from tqdm import tqdm
from deepseek_vl2.models import DeepseekVLV2Processor, DeepseekVLV2ForCausalLM
model_path = "deepseek-ai/deepseek-vl2-tiny"
vl_chat_processor: DeepseekVLV2Processor = DeepseekVLV2Processor.from_pretrained(model_path)
tokenizer = vl_chat_processor.tokenizer
from peft import LoraConfig,TaskType,get_peft_model
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM, # # 模型类型需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
target_modules = ["qkv","q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
inference_mode = False, # False:训练模式 True:推理模式
r = 8, # LoRA 秩
lora_alpha = 32, # LoRA alaph,具体作用参见 LoRA 原理
lora_dropout = 0.1, # Dropout 比例
)
with torch.no_grad():
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True)
model = model.to(torch.bfloat16).cuda()
# 使用get_peft_model函数对模型进行LoRA微调
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
# 定义批次大小和学习率
BATCH_SIZE = 12
LEARNING_RATE = 2e-5
import get_dataset
from torch.utils.data import DataLoader, Dataset
train_dataset = get_dataset.LoraDataset(get_dataset.conversations)
collate_fn = get_dataset.DataCollator(tokenizer)
# 创建一个数据加载器对象,设定批次大小、是否打乱数据、以及数据的整合方式等
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE,shuffle=True,collate_fn=collate_fn)
# 定义损失函数为交叉熵损失函数,忽略标签为-100的部分
loss_fun = torch.nn.CrossEntropyLoss(ignore_index=-100)
# 使用AdamW优化器,对模型参数进行优化,设定学习率等参数
optimizer = torch.optim.AdamW(model.parameters(), lr = LEARNING_RATE)
# 定义学习率调度器,使用余弦退火方式调整学习率,设定最大迭代次数、最小学习率等参数
lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_max = 2400,eta_min=2e-6,last_epoch=-1)
# 开始进行2个epoch的训练
for epoch in range(24):
# 使用tqdm创建进度条
pbar = tqdm(train_loader,total=len(train_loader))
for inps,attn_mask,labs in pbar:
inps = inps.cuda()
attn_mask = attn_mask.cuda()
labs = labs.cuda()
output_dict = model(input_ids = inps,attention_mask = attn_mask,use_cache = False,labels=labs)
loss = (output_dict["loss"])
#logits = output_dict["logits"] #torch.Size([4, 18, 129280])
loss.backward() # 对损失值进行反向传播,计算模型参数的梯度
optimizer.step() # 使用优化器更新模型的参数
lr_scheduler.step() # 更新学习率
# 设置进度条的描述,显示当前轮数、训练损失和学习率
pbar.set_description(
f"epoch:{epoch + 1}, train_loss:{loss.item():.5f}, lr:{lr_scheduler.get_last_lr()[0] * 1000:.5f}")
# 保存训练好的模型参数
model.save_pretrained("./lora_saver/lora_query_key_value")
首先,代码通过import语句引入了所需的库和模块,包括torch、transformers中的AutoModelForCausalLM、tqdm(用于进度条显示),以及DeepSeek-VL2中的模型和处理器。接着,指定了模型路径,并使用该路径加载了DeepseekVLV2Processor,从中获取了tokenizer。然后,配置了LoRA微调的相关参数,包括任务类型、目标模块、推理模式、LoRA秩、LoRA alpha和LoRA dropout比例。在torch.no_grad()上下文中,加载了预训练模型,并将其转换为bfloat16格式并移至CUDA设备。最后,使用get_peft_model函数对模型进行LoRA微调,并打印了可训练的参数。
接下来,代码通过自定义的get_dataset模块加载了训练数据集,并使用DeepseekVL2Processor的tokenizer初始化了数据整理函数collate_fn。然后,创建了一个DataLoader对象,用于批量加载训练数据,同时设置了批次大小、数据打乱和整合方式。此外,定义了交叉熵损失函数(忽略标签为-100的部分),并使用AdamW优化器对模型参数进行优化,设置了学习率。最后,配置了学习率调度器,采用余弦退火方式调整学习率,并设置了最大迭代次数和最小学习率等参数。
在代码的训练部分,开始了24个epoch的训练过程。在每个epoch中,使用tqdm创建了进度条,用于显示训练进度。在每次迭代中,将输入数据、注意力掩码和标签移至CUDA设备,并通过模型前向传播计算损失。然后,对损失值进行反向传播,计算模型参数的梯度,并使用优化器更新模型参数。同时,更新学习率,并在进度条中显示当前轮数、训练损失和学习率。最后,在每个epoch结束时,把训练好的模型参数保存到指定路径。
更多推荐
所有评论(0)