节前,我们星球组织了一场算法岗技术&面试讨论会,邀请了一些互联网大厂朋友、参加社招和校招面试的同学.
针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。
合集:
《大模型面试宝典》(2024版) 正式发布!
大型语言模型以其文本生成能力而闻名。在预训练期间,它们接受了数百万个标记的训练。这将有助于大型语言模型理解英文文本并在生成期间生成有意义的完整标记。
自然语言处理中的另一个常见任务是序列分类任务。在此,我们将给定的序列分类为不同的类别。这可以通过 prompt 使用大型语言模型简单地完成,但这可能只是有时有效。
我们可以调整大型语言模型以针对给定的输入为每个类别输出一组概率。本指南将展示如何训练此类 LLM 并使用微调的 Llama 3 模型。
在本指南中,我们将在 Kaggle 中工作。第一步是下载必要的库,我们需要这些库来微调Llama 3进行序列分类。让我们运行以下代码:
!pipinstall-qtransformersacceleratetrlbitsandbytesdatasetsevaluatehuggingface-cli
!pipinstall-qpeftscikit-learn
我们首先下载以下库:
之后,尝试登录 HuggingFace 中心。为此,我们将使用 huggingface-cli 工具。此代码如下所示:
!huggingface-cli login --token $YOUR_HF_TOKEN
在这里,我们使用附加的 – token 选项调用 huggingface-cli 的登录选项。在这里,我们提供 HuggingFace 令牌以登录 HuggingFace。要获取 HuggingFace 令牌,请转到此链接。如下图所示,您可以通过单击“新令牌”或使用现有令牌来创建访问令牌。只需复制该令牌并将其粘贴到 YOUR_HF_TOKEN 位置即可。
接下来,我们加载数据集进行训练。为此,我们使用以下代码:
fromdatasetsimportload_dataset
dataset=load_dataset("ag_news")
运行此命令将把 ag_news 数据集下载到 dataset 变量中。ag_news 数据集如下图所示:
这是一个新闻分类数据集。新闻分为不同的类别,例如世界、体育、商业和科学/技术。现在,让我们看看每个类别的示例数量是否相等,或者是否存在类别不平衡。
importpandasaspd
df=pd.DataFrame(dataset['train'])
df.label.value_counts(normalize=True)
运行此代码会产生以下输出。我们可以检查所有 4 个标签是否具有相同的比例,这意味着每个类别在数据集中都有相同数量的示例。数据集很大,所以我们只需要其中的一部分。因此,我们使用以下代码从此数据框中抽取一些数据:
#Splittingthedataframeinto4separatedataframesbasedonthelabels
label_1_df=df[df['label']==0]
label_2_df=df[df['label']==1]
label_3_df=df[df['label']==2]
label_4_df=df[df['label']==3]
#Shuffleeachlabeldataframe
label_1_df=label_1_df.sample(frac=1).reset_index(drop=True)
label_2_df=label_2_df.sample(frac=1).reset_index(drop=True)
label_3_df=label_3_df.sample(frac=1).reset_index(drop=True)
label_4_df=label_4_df.sample(frac=1).reset_index(drop=True)
#Splittingeachlabeldataframeintotrain,test,andvalidationsets
label_1_train=label_1_df.iloc[:2000]
label_1_test=label_1_df.iloc[2000:2500]
label_1_val=label_1_df.iloc[2500:3000]
label_2_train=label_2_df.iloc[:2000]
label_2_test=label_2_df.iloc[2000:2500]
label_2_val=label_2_df.iloc[2500:3000]
label_3_train=label_3_df.iloc[:2000]
label_3_test=label_3_df.iloc[2000:2500]
label_3_val=label_3_df.iloc[2500:3000]
label_4_train=label_4_df.iloc[:2000]
label_4_test=label_4_df.iloc[2000:2500]
label_4_val=label_4_df.iloc[2500:3000]
#Concatenatingthesplitsbacktogether
train_df=pd.concat([label_1_train,label_2_train,label_3_train,label_4_train])
test_df=pd.concat([label_1_test,label_2_test,label_3_test,label_4_test])
val_df=pd.concat([label_1_val,label_2_val,label_3_val,label_4_val])
#Shufflethedataframestoensurerandomness
train_df=train_df.sample(frac=1).reset_index(drop=True)
test_df=test_df.sample(frac=1).reset_index(drop=True)
val_df=val_df.sample(frac=1).reset_index(drop=True)
为了确认,让我们检查训练数据框中每个标签的值计数。代码如下:
train_df.label.value_counts()
因此,我们可以检查训练数据框是否对四个标签都具有相同的示例。在将它们发送到训练之前,我们需要将这些 Pandas DataFrames 转换为 HuggingFace 训练库接受的 DatasetDict。为此,我们使用以下代码:
fromdatasetsimportDatasetDict,Dataset
#ConvertingpandasDataFramesintoHuggingFaceDatasetobjects:
dataset_train=Dataset.from_pandas(train_df)
dataset_val=Dataset.from_pandas(val_df)
dataset_test=Dataset.from_pandas(test_df)
#CombinethemintoasingleDatasetDict
dataset=DatasetDict({
'train':dataset_train,
'val':dataset_val,
'test':dataset_test
})
dataset
从输出中我们可以看到,DatasetDict 包含 3 个数据集,分别是训练、测试和验证数据集。其中每个数据集仅包含 2 列:一列是文本,另一列是标签。
在这里,在我们的数据集中,每个类别的比例是相同的。在实际情况中,这可能只是有时是正确的。因此,当类别不平衡时,我们需要采取适当的措施,以便 LLM 不会更加重视包含更多示例的标签。为此,我们计算类别权重。
类别权重告诉我们必须赋予每个类别多大的重要性;类别权重越大,该类别的重要性就越大。如果我们的数据集不平衡,我们可以为标签提供更多的类别权重,使用更少的示例,从而赋予它更大的重要性。为了获得这些类别权重,我们可以取数据集中类别标签(值计数)比例的倒数。此代码如下:
importtorch
class_weights=(1/train_df.label.value_counts(normalize=True).sort_index()).tolist()
class_weights=torch.tensor(class_weights)
class_weights=class_weights/class_weights.sum()
class_weights
从输出中我们可以看到,所有类别的类别权重都是相等的;这是因为所有类别都有相同数量的示例。
在本节中,我们将下载并准备模型进行训练。首先是下载模型。我们无法使用完整模型,因为我们正在处理一个小型 GPU;因此,我们将对其进行量化。此代码如下:
fromtransformersimportBitsAndBytesConfig,AutoModelForSequenceClassification
quantization_config=BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type='nf4',
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
model_name="meta-llama/Meta-Llama-3-8B"
model=AutoModelForSequenceClassification.from_pretrained(
model_name,
quantization_config=quantization_config,
num_labels=4,
device_map='auto'
)
因此,运行上述代码将从 HuggingFace 中心下载 Llama 3 8B 大型语言模型,根据我们为其提供的 quantization_config 对其进行量化,然后将 LLM 的输出头替换为具有 4 个神经元的线性头作为输出,并将模型推送到 GPU。接下来,我们将为模型创建一个 LoRA 配置,以仅训练一部分参数。此代码如下:
frompeftimportLoraConfig,prepare_model_for_kbit_training,get_peft_model
lora_config=LoraConfig(
r=16,
lora_alpha=8,
target_modules=['q_proj','k_proj','v_proj','o_proj'],
lora_dropout=0.05,
bias='none',
task_type='SEQ_CLS'
)
model=prepare_model_for_kbit_training(model)
model=get_peft_model(model,lora_config)
运行此程序后,get_peft_model 将采用模型并通过包装模型和 LoRA 配置来准备使用PEFT 方法(如本例中的LoRA)进行训练。
在本节中,我们将在模型训练之前在测试数据上测试 Llama 3 模型。为此,我们将首先下载标记器。此代码如下:
fromtransformersimportAutoTokenizer
model_name="meta-llama/Meta-Llama-3-8B"
tokenizer=AutoTokenizer.from_pretrained(model_name,add_prefix_space=True)
tokenizer.pad_token_id=tokenizer.eos_token_id
tokenizer.pad_token=tokenizer.eos_token
model.config.pad_token_id=tokenizer.pad_token_id
model.config.use_cache=False
model.config.pretraining_tp=1
接下来,我们甚至通过将模型的 pad token ID 设置为 tokenizer 的 pad token ID 来编辑模型配置,并且不使用缓存。现在,我们将测试数据提供给模型并收集输出:
sentences=test_df.text.tolist()
batch_size=32
all_outputs=[]
foriinrange(0,len(sentences),batch_size):
batch_sentences=sentences[i:i+batch_size]
inputs=tokenizer(batch_sentences,return_tensors="pt",
padding=True,truncation=True,max_length=512)
inputs={k:v.to('cuda'iftorch.cuda.is_available()else'cpu')fork,vininputs.items()}
withtorch.no_grad():
outputs=model(**inputs)
all_outputs.append(outputs['logits'])
final_outputs=torch.cat(all_outputs,dim=0)
test_df['predictions']=final_outputs.argmax(axis=1).cpu().numpy()
运行此代码会将模型结果存储在一个变量中。我们将这些预测添加到新列中的测试 DataFrame 中。我们取每个输出的 argmax;这为我们提供了 final_outputs 列表中每个输出概率最高的标签。现在,我们需要评估 LLM 生成的输出,我们可以通过以下代码进行评估:
fromsklearn.metricsimportaccuracy_score,confusion_matrix
fromsklearn.metricsimportbalanced_accuracy_score,classification_report
defget_metrics_result(test_df):
y_test=test_df.label
y_pred=test_df.predictions
print("ClassificationReport:")
print(classification_report(y_test,y_pred))
print("BalancedAccuracyScore:",balanced_accuracy_score(y_test,y_pred))
print("AccuracyScore:",accuracy_score(y_test,y_pred))
get_metrics_result(test_df)
运行该程序产生了以下结果。我们可以看到,我们的准确率为 0.23,这非常低。该模型的准确率、召回率和 f1 分数也非常低;它们甚至没有达到 50% 以上的百分比。在训练模型后对它们进行测试将让我们了解模型的训练效果。
在开始训练之前,我们需要对数据进行预处理,然后再将其发送到模型。为此,我们使用以下代码:
defdata_preprocesing(row):
returntokenizer(row['text'],truncation=True,max_length=512)
tokenized_data=dataset.map(data_preprocesing,batched=True,
remove_columns=['text'])
tokenized_data.set_format("torch")
现在,datasetdict 中的每个数据集都包含三个特征/列,即标签、input_id 和 Attention_masks。使用上述预处理函数为每个文本生成 input_ids 和 Attention_masks。在训练时,我们需要一个数据整理器来批量处理数据。为此,我们使用以下代码:
fromtransformersimportDataCollatorWithPadding
collate_fn=DataCollatorWithPadding(tokenizer=tokenizer)
这将确保批处理中的所有输入具有相同的长度,这对于加快训练速度至关重要。因此,我们使用特殊标记(如填充标记)将输入统一填充到最长序列长度,从而允许同时进行批处理。
在开始训练之前,我们需要一个误差度量来评估它。大型语言模型的默认误差度量是负对数似然损失。但在这里,因为我们正在修改 LLM 以使其成为序列分类工具,所以我们需要重新定义在训练时测试模型所需的误差度量:
defcompute_metrics(evaluations):
predictions,labels=evaluations
predictions=np.argmax(predictions,axis=1)
return{'balanced_accuracy':balanced_accuracy_score(predictions,labels),
'accuracy':accuracy_score(predictions,labels)}
因为我们要使用自定义指标,所以我们甚至定义了一个自定义训练器来训练我们的 LLM,这是必要的,因为我们在这里使用类权重。为此,代码将是
classCustomTrainer(Trainer):
def__init__(self,*args,class_weights=None,**kwargs):
super().__init__(*args,**kwargs)
ifclass_weightsisnotNone:
self.class_weights=torch.tensor(class_weights,
dtype=torch.float32).to(self.args.device)
else:
self.class_weights=None
defcompute_loss(self,model,inputs,return_outputs=False):
labels=inputs.pop("labels").long()
outputs=model(**inputs)
logits=outputs.get('logits')
ifself.class_weightsisnotNone:
loss=F.cross_entropy(logits,labels,weight=self.class_weights)
else:
loss=F.cross_entropy(logits,labels)
return(loss,outputs)ifreturn_outputselseloss
现在,我们将定义我们的训练参数。代码如下
training_args=TrainiAgrumentsngArguments(
output_dir='sentiment_classification',
learning_rate=1e-4,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
num_train_epochs=1,
logging_steps=1,
weight_decay=0.01,
evaluation_strategy='epoch',
save_strategy='epoch',
load_best_model_at_end=True,
report_to="none"
)
这将创建我们的 TrainingArguments 对象。现在,我们准备将其传递给我们创建的 Trainer。此代码如下
trainer=CustomTrainer(
model=model,
args=training_args,
train_dataset=tokenized_datasets['train'],
eval_dataset=tokenized_datasets['val'],
tokenizer=tokenizer,
data_collator=collate_fn,
compute_metrics=compute_metrics,
class_weights=class_weights,
)
train_result=trainer.train()
现在我们已经创建了训练器对象。现在我们调用训练器对象的 .train() 函数来启动训练过程,并将结果存储在 train_result 中
现在,让我们尝试进行评估,在测试数据上测试新训练的模型:
defgenerate_predictions(model,df_test):
sentences=df_test.text.tolist()
batch_size=32
all_outputs=[]
foriinrange(0,len(sentences),batch_size):
batch_sentences=sentences[i:i+batch_size]
inputs=tokenizer(batch_sentences,return_tensors="pt",
padding=True,truncation=True,max_length=512)
inputs={k:v.to('cuda'iftorch.cuda.is_available()else'cpu')
fork,vininputs.items()}
withtorch.no_grad():
outputs=model(**inputs)
all_outputs.append(outputs['logits'])
final_outputs=torch.cat(all_outputs,dim=0)
df_test['predictions']=final_outputs.argmax(axis=1).cpu().numpy()
generate_predictions(model,test_df)
get_performance_metrics(test_df)
运行此代码会产生以下结果。我们看到模型的准确率有了很大的提升。其他指标,如精度、召回率和 f1 分数也从初始值有所增加。总体准确率从训练前的 0.23 提高到训练后的 0.93,即训练后模型提高了 0.7,即 70%。由此,我们可以了解到,大型语言模型非常适合用作序列分类器
总之,对大型语言模型Llama 3进行序列分类的微调涉及几个详细步骤,从准备数据集到量化模型以在有限的硬件上进行高效训练。通过利用 HuggingFace 的各种库并实施 Prompt Engineering 和 LoRA 配置等技术,可以有效地训练这些模型以完成新闻分类等特定任务。本指南演示了从初始设置和数据预处理到模型训练和评估的整个过程,突出了 LLM 在自然语言处理任务中的多功能性和强大功能。
(完)
| 欢迎光临 链载Ai (https://www.lianzai.com/) | Powered by Discuz! X3.5 |