一、Data.py
python
# data负责产生两个dataloader
from torch.utils.data import DataLoader, Dataset
from sklearn.model_selection import train_test_split #给X,Y 和分割比例, 分割出来一个训练集和验证机的X, Y
import torch
def read_file(path):
data = []
label = []
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f):
if i == 0:
continue
if i > 200 and i< 7500:
continue
line = line.strip("\n")
line = line.split(",", 1) #把这句话,按照,分割, 1表示分割次数
data.append(line[1])
label.append(line[0])
print("读了%d的数据"%len(data))
return data, label
# file = "../jiudian.txt"
# read_file(file)
class jdDataset(Dataset):
def __init__(self, data, label):
self.X = data
self.Y = torch.LongTensor([int(i) for i in label])
def __getitem__(self, item):
return self.X[item], self.Y[item]
def __len__(self):
return len(self.Y)
def get_data_loader(path, batchsize, val_size=0.2): #读入数据,分割数据。
data, label = read_file(path)
train_x, val_x, train_y, val_y = train_test_split(data, label, test_size=val_size, shuffle=True, stratify=label)
train_set = jdDataset(train_x, train_y)
val_set = jdDataset(val_x, val_y)
train_loader = DataLoader(train_set, batchsize, shuffle=True)
val_loader = DataLoader(val_set, batchsize, shuffle=True)
return train_loader, val_loader
if __name__ == "__main__":
get_data_loader("../jiudian.txt", 2)
代码逐行解释
导入必要的库
python
from torch.utils.data import DataLoader, Dataset
from sklearn.model_selection import train_test_split # 给X, Y 和分割比例, 分割出来一个训练集和验证集的X, Y
import torch
DataLoader
和Dataset
:来自torch.utils.data
,用于创建数据加载器和自定义数据集。train_test_split
:来自sklearn.model_selection
,用于将数据集划分为训练集和验证集。torch
:PyTorch 的核心库,用于深度学习模型的构建和训练。
定义读取文件的函数
python
def read_file(path):
data = []
label = []
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f):
if i == 0:
continue
if i > 200 and i < 7500:
continue
line = line.strip("\n")
line = line.split(",", 1) # 把这句话,按照逗号分割, 1表示分割次数
data.append(line[1])
label.append(line[0])
print("读了%d的数据" % len(data))
return data, label
read_file
函数 :- 打开指定路径的文件并读取每一行。
- 跳过第一行(假设是表头)。
- 忽略第201到7499行的数据。
- 使用
strip("\n")
去除每行末尾的换行符。 - 使用
split(",", 1)
按照第一个逗号分割每行数据,生成包含两个元素的列表。 - 将分割后的第二个元素(即文本数据)添加到
data
列表中,第一个元素(即标签)添加到label
列表中。 - 打印读取的数据条数,并返回
data
和label
。
自定义数据集类
python
class jdDataset(Dataset):
def __init__(self, data, label):
self.X = data
self.Y = torch.LongTensor([int(i) for i in label])
def __getitem__(self, item):
return self.X[item], self.Y[item]
def __len__(self):
return len(self.Y)
jdDataset
类 :- 继承自
torch.utils.data.Dataset
。 - 在
__init__
方法中初始化self.X
和self.Y
,其中self.Y
是将标签转换为torch.LongTensor
类型。 __getitem__
方法用于根据索引获取数据项,返回对应的文本数据和标签。__len__
方法返回数据集的长度。
- 继承自
生成数据加载器
python
def get_data_loader(path, batchsize, val_size=0.2): # 读入数据,分割数据
data, label = read_file(path)
train_x, val_x, train_y, val_y = train_test_split(data, label, test_size=val_size, shuffle=True, stratify=label)
train_set = jdDataset(train_x, train_y)
val_set = jdDataset(val_x, val_y)
train_loader = DataLoader(train_set, batchsize, shuffle=True)
val_loader = DataLoader(val_set, batchsize, shuffle=True)
return train_loader, val_loader
get_data_loader
函数 :- 调用
read_file
函数读取数据并获得data
和label
。 - 使用
train_test_split
函数将数据集划分为训练集和验证集。参数包括:test_size
: 验证集的比例,默认为0.2。shuffle
: 是否在分割前打乱数据,默认为True
。stratify
: 根据标签进行分层抽样,确保训练集和验证集中的标签分布一致。
- 创建
jdDataset
实例train_set
和val_set
。 - 使用
DataLoader
创建训练集和验证集的数据加载器。训练集的数据加载器设置为shuffle=True
,以便每次迭代时打乱数据顺序;验证集的数据加载器也设置为shuffle=True
,但在实际应用中通常不需要打乱验证集的数据顺序。 - 返回训练集和验证集的数据加载器。
- 调用
主程序入口
python
if __name__ == "__main__":
get_data_loader("../jiudian.txt", 2)
- 主程序入口 :
- 当脚本作为主程序运行时,调用
get_data_loader
函数读取路径为"../jiudian.txt"
的文件,并生成批量大小为2的数据加载器。
- 当脚本作为主程序运行时,调用
详细步骤说明
-
读取文件:
read_file
函数负责从指定路径读取文件内容,并跳过某些特定行(如第一行和第201到7499行),然后将剩余的有效行按逗号分割为标签和文本数据,分别存储在data
和label
列表中。
-
创建自定义数据集:
jdDataset
类继承自Dataset
,实现了__getitem__
和__len__
方法。它将原始数据和标签封装成一个可被DataLoader
使用的对象。
-
数据集划分:
get_data_loader
函数使用train_test_split
将数据集划分为训练集和验证集,并创建相应的jdDataset
实例。
-
创建数据加载器:
- 使用
DataLoader
创建训练集和验证集的数据加载器。训练集的数据加载器设置了shuffle=True
,以便每次迭代时打乱数据顺序;验证集的数据加载器也设置了shuffle=True
,但通常不需要打乱验证集的数据顺序。
- 使用
示例输出
假设 "../jiudian.txt"
文件的内容如下:
id,label,text
1,0,这是一家不错的酒店。
2,1,房间太小了。
...
执行 get_data_loader("../jiudian.txt", 2)
后,程序将读取文件内容,忽略第一行和第201到7499行的数据,然后将剩余的数据按逗号分割为标签和文本数据,并将其划分为训练集和验证集,最后创建对应的数据加载器。
总结
这段代码的主要功能是从文件中读取数据,将其划分为训练集和验证集,并创建相应的数据加载器。通过这种方式,可以方便地将数据提供给深度学习模型进行训练和验证。
二、Model.py
python
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer, BertConfig
class myBertModel(nn.Module):
def __init__(self, bert_path, num_class, device):
super(myBertModel, self).__init__()
self.bert = BertModel.from_pretrained(bert_path)
# config = BertConfig.from_pretrained(bert_path)
# self.bert = BertModel(config)
self.device = device
self.cls_head = nn.Linear(768, num_class)
self.tokenizer = BertTokenizer.from_pretrained(bert_path)
def forward(self, text):
input = self.tokenizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=128)
input_ids = input["input_ids"].to(self.device)
token_type_ids = input['token_type_ids'].to(self.device)
attention_mask = input['attention_mask'].to(self.device)
sequence_out, pooler_out = self.bert(input_ids=input_ids,
token_type_ids=token_type_ids,
attention_mask=attention_mask,
return_dict=False) #return_dict
pred = self.cls_head(pooler_out)
return pred
if __name__ == "__main__":
model = myBertModel("../bert-base-chinese", 2)
pred = model("今天天气真好")
代码逐行解释
导入必要的库
python
import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer, BertConfig
torch
和torch.nn
:PyTorch的核心库,用于构建神经网络。BertModel
,BertTokenizer
,BertConfig
:来自transformers
库,分别用于加载预训练的BERT模型、BERT分词器和BERT配置。
定义自定义BERT模型类
python
class myBertModel(nn.Module):
def __init__(self, bert_path, num_class, device):
super(myBertModel, self).__init__()
self.bert = BertModel.from_pretrained(bert_path)
self.device = device
self.cls_head = nn.Linear(768, num_class)
self.tokenizer = BertTokenizer.from_pretrained(bert_path)
__init__
方法 :- 加载预训练的BERT模型。
- 初始化设备(
device
),可以是CPU或GPU。 - 定义分类头(
cls_head
),将BERT输出的768维向量映射到指定的类别数。 - 加载BERT的分词器(
tokenizer
),用于将输入文本转换为BERT模型所需的输入格式。
python
def forward(self, text):
input = self.tokenizer(text, return_tensors="pt", truncation=True, padding="max_length", max_length=128)
input_ids = input["input_ids"].to(self.device)
token_type_ids = input['token_type_ids'].to(self.device)
attention_mask = input['attention_mask'].to(self.device)
sequence_out, pooler_out = self.bert(input_ids=input_ids,
token_type_ids=token_type_ids,
attention_mask=attention_mask,
return_dict=False) #return_dict
pred = self.cls_head(pooler_out)
return pred
forward
方法 :- 使用
tokenizer
将输入文本转换为BERT模型所需的输入张量(input_ids
,token_type_ids
,attention_mask
)。 - 将这些张量移动到指定的设备(CPU或GPU)上。
- 调用
self.bert
进行前向传播,得到序列输出(sequence_out
)和池化输出(pooler_out
)。 - 使用分类头(
cls_head
)对池化输出进行线性变换,得到最终的预测结果(pred
)。
- 使用
主程序入口
python
if __name__ == "__main__":
model = myBertModel("../bert-base-chinese", 2, "cpu")
pred = model("今天天气真好")
print(pred)
- 主程序入口 :
- 创建
myBertModel
实例,指定预训练BERT模型路径、类别数和设备。 - 调用模型进行前向传播,传入一条测试文本
"今天天气真好"
,并打印预测结果。
- 创建
三、train.py
python
import torch
import time
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
def train_val(para):
########################################################
model = para['model']
train_loader =para['train_loader']
val_loader = para['val_loader']
scheduler = para['scheduler']
optimizer = para['optimizer']
loss = para['loss']
epoch = para['epoch']
device = para['device']
save_path = para['save_path']
max_acc = para['max_acc']
val_epoch = para['val_epoch']
#################################################
plt_train_loss = []
plt_train_acc = []
plt_val_loss = []
plt_val_acc = []
val_rel = []
for i in range(epoch):
start_time = time.time()
model.train()
train_loss = 0.0
train_acc = 0.0
val_acc = 0.0
val_loss = 0.0
for batch in tqdm(train_loader):
model.zero_grad()
text, labels = batch[0], batch[1].to(device)
pred = model(text)
bat_loss = loss(pred, labels)
bat_loss.backward()
optimizer.step()
scheduler.step() #scheduler 调整学习率
optimizer.zero_grad()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) #梯度裁切
train_loss += bat_loss.item() #.detach 表示去掉梯度
train_acc += np.sum(np.argmax(pred.cpu().data.numpy(),axis=1)== labels.cpu().numpy())
plt_train_loss . append(train_loss/train_loader.dataset.__len__())
plt_train_acc.append(train_acc/train_loader.dataset.__len__())
if i % val_epoch == 0:
model.eval()
with torch.no_grad():
for batch in tqdm(val_loader):
val_text, val_labels = batch[0], batch[1].to(device)
val_pred = model(val_text)
val_bat_loss = loss(val_pred, val_labels)
val_loss += val_bat_loss.cpu().item()
val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_labels.cpu().numpy())
val_rel.append(val_pred)
if val_acc > max_acc:
torch.save(model, save_path+str(epoch)+"ckpt")
max_acc = val_acc
plt_val_loss.append(val_loss/val_loader.dataset.__len__())
plt_val_acc.append(val_acc/val_loader.dataset.__len__())
print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | valAcc: %3.6f valLoss: %3.6f ' % \
(i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1])
)
if i % 50 == 0:
torch.save(model, save_path+'-epoch:'+str(i)+ '-%.2f'%plt_val_acc[-1])
else:
plt_val_loss.append(plt_val_loss[-1])
plt_val_acc.append(plt_val_acc[-1])
print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f ' % \
(i, epoch, time.time()-start_time, plt_train_acc[-1], plt_train_loss[-1])
)
plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title('loss')
plt.legend(['train', 'val'])
plt.show()
plt.plot(plt_train_acc)
plt.plot(plt_val_acc)
plt.title('Accuracy')
plt.legend(['train', 'val'])
plt.savefig('acc.png')
plt.show()
这段代码实现了一个完整的训练和验证循环,用于训练一个深度学习模型。它包括训练过程中的损失和准确率记录、模型保存、学习率调整以及结果的可视化。
代码逐行解释
导入必要的库
python
import torch
import time
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
torch
:PyTorch的核心库,用于构建和训练神经网络。time
:用于记录训练时间。matplotlib.pyplot
:用于绘制训练和验证的损失和准确率曲线。numpy
:用于数值计算,特别是处理预测结果和标签的比较。tqdm
:用于显示进度条,方便监控训练过程。
定义训练和验证函数
python
def train_val(para):
train_val
函数 :接受一个包含所有必要参数的字典para
,并在其中进行训练和验证。
参数解析
python
model = para['model']
train_loader = para['train_loader']
val_loader = para['val_loader']
scheduler = para['scheduler']
optimizer = para['optimizer']
loss = para['loss']
epoch = para['epoch']
device = para['device']
save_path = para['save_path']
max_acc = para['max_acc']
val_epoch = para['val_epoch']
model
:要训练的模型。train_loader
和val_loader
:训练集和验证集的数据加载器。scheduler
:学习率调度器。optimizer
:优化器(如Adam、SGD等)。loss
:损失函数(如交叉熵损失)。epoch
:训练的总轮数。device
:设备(CPU或GPU)。save_path
:模型保存路径。max_acc
:当前最高的验证准确率。val_epoch
:每隔多少个epoch进行一次验证。
初始化记录变量
python
plt_train_loss = []
plt_train_acc = []
plt_val_loss = []
plt_val_acc = []
val_rel = []
plt_train_loss
和plt_train_acc
:记录每轮训练的损失和准确率。plt_val_loss
和plt_val_acc
:记录每轮验证的损失和准确率。val_rel
:用于存储验证集的预测结果(未在后续代码中使用)。
训练和验证循环
python
for i in range(epoch):
start_time = time.time()
model.train()
train_loss = 0.0
train_acc = 0.0
val_acc = 0.0
val_loss = 0.0
- 初始化每轮的损失和准确率:在每个epoch开始时,重置这些变量。
训练阶段
python
for batch in tqdm(train_loader):
model.zero_grad()
text, labels = batch[0], batch[1].to(device)
pred = model(text)
bat_loss = loss(pred, labels)
bat_loss.backward()
optimizer.step()
scheduler.step() # 调整学习率
optimizer.zero_grad()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) # 梯度裁切
train_loss += bat_loss.item()
train_acc += np.sum(np.argmax(pred.cpu().data.numpy(), axis=1) == labels.cpu().numpy())
tqdm(train_loader)
:显示训练进度条。model.zero_grad()
:清空之前的梯度。text, labels = batch[0], batch[1].to(device)
:将数据和标签移动到指定设备。pred = model(text)
:前向传播得到预测结果。bat_loss = loss(pred, labels)
:计算损失。bat_loss.backward()
:反向传播计算梯度。optimizer.step()
:更新模型参数。scheduler.step()
:调整学习率(根据调度器)。optimizer.zero_grad()
:清空梯度(通常在zero_grad()
之后不需要再次调用)。torch.nn.utils.clip_grad_norm_
:梯度裁切,防止梯度过大导致训练不稳定。train_loss += bat_loss.item()
:累加当前批次的损失。train_acc += np.sum(np.argmax(pred.cpu().data.numpy(), axis=1) == labels.cpu().numpy())
:计算当前批次的准确率。
计算平均损失和准确率并记录
python
plt_train_loss.append(train_loss / len(train_loader.dataset))
plt_train_acc.append(train_acc / len(train_loader.dataset))
plt_train_loss.append(...)
:记录每轮训练的平均损失。plt_train_acc.append(...)
:记录每轮训练的平均准确率。
验证阶段
python
if i % val_epoch == 0:
model.eval()
with torch.no_grad():
for batch in tqdm(val_loader):
val_text, val_labels = batch[0], batch[1].to(device)
val_pred = model(val_text)
val_bat_loss = loss(val_pred, val_labels)
val_loss += val_bat_loss.cpu().item()
val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_labels.cpu().numpy())
val_rel.append(val_pred)
if val_acc > max_acc:
torch.save(model, save_path + str(epoch) + "ckpt")
max_acc = val_acc
plt_val_loss.append(val_loss / len(val_loader.dataset))
plt_val_acc.append(val_acc / len(val_loader.dataset))
print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f | valAcc: %3.6f valLoss: %3.6f' % \
(i, epoch, time.time() - start_time, plt_train_acc[-1], plt_train_loss[-1], plt_val_acc[-1], plt_val_loss[-1])
)
if i % 50 == 0:
torch.save(model, save_path + '-epoch:' + str(i) + '-%.2f' % plt_val_acc[-1])
else:
plt_val_loss.append(plt_val_loss[-1])
plt_val_acc.append(plt_val_acc[-1])
print('[%03d/%03d] %2.2f sec(s) TrainAcc : %3.6f TrainLoss : %3.6f' % \
(i, epoch, time.time() - start_time, plt_train_acc[-1], plt_train_loss[-1])
)
if i % val_epoch == 0:
:每隔val_epoch
轮进行一次验证。model.eval()
:设置模型为评估模式,关闭dropout等训练特定操作。with torch.no_grad():
:禁用梯度计算,节省内存并加速推理。val_text, val_labels = batch[0], batch[1].to(device)
:将验证数据和标签移动到指定设备。val_pred = model(val_text)
:前向传播得到验证预测结果。val_bat_loss = loss(val_pred, val_labels)
:计算验证损失。val_loss += val_bat_loss.cpu().item()
:累加验证损失。val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == val_labels.cpu().numpy())
:计算验证准确率。if val_acc > max_acc:
:如果当前验证准确率超过历史最高值,则保存模型。plt_val_loss.append(...)
和plt_val_acc.append(...)
:记录每轮验证的平均损失和准确率。print(...)
:打印当前轮次的训练和验证信息。if i % 50 == 0:
:每隔50轮保存一次模型。
绘制训练和验证曲线
python
plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title('loss')
plt.legend(['train', 'val'])
plt.show()
plt.plot(plt_train_acc)
plt.plot(plt_val_acc)
plt.title('Accuracy')
plt.legend(['train', 'val'])
plt.savefig('acc.png')
plt.show()
plt.plot(...)
:绘制训练和验证的损失和准确率曲线。plt.title(...)
:设置图表标题。plt.legend(...)
:添加图例。plt.show()
:显示图表。plt.savefig('acc.png')
:保存准确率图表为PNG文件。