BERT_data train
·
这份代码是数据处理模块,它的核心任务非常明确:把硬盘上的 jiudian.txt 文本文件读取出来,经过清洗和分割,最终打包成 PyTorch 模型能直接“消化”的 DataLoader。
下面我为您逐行详细解释这段代码:
1. 导入库
python
编辑
from torch.utils.data import DataLoader, Dataset
from sklearn.model_selection import train_test_split
import torch
from torch.utils.data import DataLoader, Dataset:Dataset: 这是 PyTorch 的数据集抽象类。我们需要继承它来创建一个自定义的数据集类,告诉 PyTorch 怎么读取我们的数据。DataLoader: 这是一个迭代器。它负责把Dataset里的数据打包成一个个小批次(Batch),并支持多线程加载和数据打乱(Shuffle)。
from sklearn.model_selection import train_test_split:- 这是一个非常实用的工具函数。它可以自动把数据切分成训练集和验证集。
import torch:- 导入 PyTorch 库,主要用于将标签数据转换为张量(Tensor)。
2. 读取数据函数
python
编辑
def read_file(path):
data = []
label = []
def read_file(path):: 定义一个函数,参数path是数据文件的路径。data = []: 初始化一个空列表,用来存放文本内容(例如:“这家酒店真不错”)。label = []: 初始化一个空列表,用来存放标签(例如:“1”代表好评)。
python
编辑
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f):
with open(...): 以只读模式("r")打开文件,并指定编码为utf-8(防止中文乱码)。with语句会自动在读取结束后关闭文件。for i, line in enumerate(f):: 逐行读取文件。i: 行号索引(从 0 开始)。line: 当前行的文本内容。
python
编辑
if i == 0:
continue
- 跳过第一行。通常数据文件的第一行是表头(例如“label,text”),不是实际数据,所以跳过。
python
编辑
if i > 200 and i< 7500:
continue
- 数据筛选逻辑。这行代码的意思是:如果行号在 200 到 7500 之间,就跳过不读。
- 推测用途:这通常是为了快速调试代码。作者可能只想用前 200 条数据测试程序能不能跑通,或者只想用 7500 条之后的数据。在全量训练时,通常会注释掉这一行。
python
编辑
line = line.strip("\n")
line = line.split(",", 1)
line.strip("\n"): 去掉行末尾的换行符。line.split(",", 1): 按照逗号,分割字符串。1: 表示只分割一次。- 为什么要这样做? 假设文本内容是
"1,这,家,酒,店,真,好"。如果不限制分割次数,会被切成很多段。限制为 1 次,就能保证切分成[标签, 剩下的所有文本]两部分。
python
编辑
data.append(line[1])
label.append(line[0])
data.append(line[1]): 把分割后的第二部分(文本内容)加入data列表。label.append(line[0]): 把分割后的第一部分(标签)加入label列表。
python
编辑
print("读了%d的数据"%len(data))
return data, label
- 打印读取了多少条数据,并返回两个列表。
3. 自定义数据集类
python
编辑
class jdDataset(Dataset):
def __init__(self, data, label):
self.X = data
self.Y = torch.LongTensor([int(i) for i in label])
class jdDataset(Dataset):: 定义一个名为jdDataset的类,继承自 PyTorch 的Dataset。def __init__(self, data, label):: 初始化函数。self.X = data: 保存文本数据。self.Y = ...: 处理标签数据。[int(i) for i in label]: 把标签从字符串(如"1")转换成整数(如1)。torch.LongTensor(...): 将整数列表转换成 PyTorch 的长整型张量。- 注意:分类任务的标签(Target)必须是
Long类型,因为交叉熵损失函数需要它来索引类别。
python
编辑
def __getitem__(self, item):
return self.X[item], self.Y[item]
def __getitem__(self, item):: 这是一个魔术方法。当你通过索引访问数据(例如dataset[5])时,这个方法会被调用。return ...: 返回第item条数据的文本和对应的标签。
python
编辑
def __len__(self):
return len(self.Y)
def __len__(self):: 返回数据集的总大小。DataLoader需要知道总共有多少数据才能确定循环多少次。
4. 获取数据加载器
python
编辑
def get_data_loader(path, batchsize, val_size=0.2):
data, label = read_file(path)
def get_data_loader(...): 定义主函数。val_size=0.2: 默认验证集比例是 20%。
data, label = read_file(path): 调用上面的函数,读取原始数据。
python
编辑
train_x, val_x, train_y, val_y = train_test_split(data, label, test_size=val_size, shuffle=True, stratify=label)
train_test_split(...): 核心分割函数。data, label: 输入的文本和标签。test_size=val_size: 指定验证集占 20%。shuffle=True: 打乱数据。这很重要,防止模型学到数据的顺序(比如前100条全是好评)。stratify=label: 分层采样。这意味着训练集和验证集中,“好评”和“差评”的比例会保持一致(例如都是 1:1),防止出现训练集全是好评,验证集全是差评的情况。
python
编辑
train_set = jdDataset(train_x, train_y)
val_set = jdDataset(val_x, val_y)
- 将分割好的训练数据和验证数据,分别封装成我们刚才定义的
jdDataset对象。
python
编辑
train_loader = DataLoader(train_set, batchsize, shuffle=True)
val_loader = DataLoader(val_set, batchsize, shuffle=True)
DataLoader(...): 生成数据加载器。train_set: 传入数据集对象。batchsize: 每个批次取多少条数据(例如 16)。shuffle=True: 每个 epoch 开始时再次打乱数据顺序(增强模型泛化能力)。- 注意:通常验证集
val_loader的shuffle可以设为False,但这不影响结果,只是顺序不同而已。
python
编辑
return train_loader, val_loader
- 返回两个加载器,供主程序使用。
5. 测试代码
python
编辑
if __name__ == "__main__":
get_data_loader("../jiudian.txt", 2)
- 测试运行,指定文件路径和批次大小为 2。
这段代码是整个项目的核心“发动机”——训练与验证循环。它负责指挥模型如何利用数据进行学习,如何计算误差,以及如何根据验证集的表现来保存最好的模型。
下面我为您逐行详细解释:
1. 导入库
python
编辑
import torch
import time
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm
import torch: PyTorch 核心库。import time: 用于计算训练每个 epoch 花费的时间。import matplotlib.pyplot as plt: 绘图库,用于最后画出 Loss 和 Accuracy 的变化曲线。import numpy as np: 用于数值计算(如计算准确率时的求和)。from tqdm import tqdm: 一个进度条库。它可以在循环中显示一个动态的进度条,让你直观地看到训练进度。
2. 函数定义与参数解包
python
编辑
def train_val(para):
def train_val(para):: 定义训练函数,接收一个包含所有配置和对象的字典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']
- 这一大段代码是从字典
para中提取出之前定义好的变量,方便后续直接使用。model: 模型本体。train_loader/val_loader: 数据加载器。scheduler: 学习率调度器。optimizer: 优化器。loss: 损失函数。max_acc: 用于记录目前为止最好的验证集准确率,用于筛选最佳模型。val_epoch: 验证频率(每隔几个 epoch 验证一次)。
3. 初始化记录列表
python
编辑
plt_train_loss = []
plt_train_acc = []
plt_val_loss = []
plt_val_acc = []
val_rel = []
- 初始化 5 个空列表,用于在训练过程中记录数据,以便最后画图。
plt_train_loss/plt_train_acc: 记录每个 epoch 的训练集 Loss 和准确率。plt_val_loss/plt_val_acc: 记录每个 epoch 的验证集 Loss 和准确率。val_rel: 用于保存验证集的预测结果(虽然代码后面没怎么用到这个变量,可能是为了后续分析预留的)。
4. 训练循环 (Epoch Loop)
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
for i in range(epoch):: 开始循环,i代表当前的 epoch 轮数(从 0 到 4)。start_time = time.time(): 记录当前时间,用于计算这一轮花了多久。model.train(): 关键! 将模型设置为训练模式。这会开启 Dropout 和 BatchNorm 的训练行为。train_loss = 0.0...: 初始化当前 epoch 的累计 Loss 和准确率计数器。
5. 训练阶段 (Training Phase)
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() #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())
这是最核心的训练步骤,逐行解释:
for batch in tqdm(train_loader):: 遍历训练数据。tqdm会自动显示进度条。model.zero_grad(): 清空梯度。PyTorch 默认会累加梯度,所以每次迭代前必须手动清空。text, labels = ...: 从 batch 中取出文本和标签,并将标签移动到 GPU (device) 上。pred = model(text): 前向传播。将文本输入模型,得到预测结果。bat_loss = loss(pred, labels): 计算损失。比较预测结果pred和真实标签labels。bat_loss.backward(): 反向传播。计算梯度(即 Loss 对每个参数的导数)。optimizer.step(): 更新参数。优化器根据梯度更新模型的权重。scheduler.step(): 更新学习率。根据调度器策略调整学习率。optimizer.zero_grad(): 再次清空梯度(这是一种防御性写法,虽然上面已经清过一次了)。torch.nn.utils.clip_grad_norm_(...): 梯度裁剪。防止梯度爆炸(即梯度数值过大导致模型崩溃),将梯度限制在 1.0 以内。train_loss += bat_loss.item(): 累加当前的 Loss 值。.item()是取出 Tensor 中的数值。train_acc += ...: 计算并累加准确率。np.argmax(..., axis=1): 取出预测概率最大的类别的索引。== labels...: 与真实标签比较,返回 True/False。np.sum(...): 统计预测正确的数量。
6. 记录训练数据
python
编辑
plt_train_loss . append(train_loss/train_loader.dataset.__len__())
plt_train_acc.append(train_acc/train_loader.dataset.__len__())
- 将当前 epoch 的平均 Loss 和平均准确率加入列表。
- 注意:这里除以
dataset.__len__()是为了求平均值(虽然通常除以 batch 数量更常见,但除以总样本数也是一种归一化方式)。
7. 验证阶段 (Validation Phase)
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 i % val_epoch == 0:: 判断是否到了验证的轮次(这里val_epoch=1,所以每轮都验证)。model.eval(): 关键! 将模型设置为评估模式。这会关闭 Dropout,确保预测结果稳定。with torch.no_grad():: 停止梯度计算。验证时不需要更新模型,所以关闭梯度可以节省显存和计算资源。for batch in tqdm(val_loader):: 遍历验证集数据。val_pred = model(val_text): 前向传播得到预测结果。val_loss += ...: 累加验证集 Loss。val_acc += ...: 累加验证集预测正确的数量。
8. 保存最佳模型与打印日志
python
编辑
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 val_acc > max_acc:: 如果当前验证集的准确率超过了历史最高记录。torch.save(model, ...): 保存模型。将当前模型权重保存到硬盘。max_acc = val_acc: 更新最高准确率记录。plt_val_loss.append(...): 记录验证集的 Loss 和准确率到绘图列表。print(...): 打印当前轮次的状态信息,包括:- 轮次进度
[i/epoch] - 耗时
sec(s) - 训练集准确率和 Loss
- 验证集准确率和 Loss
- 轮次进度
9. 非验证轮次的处理
python
编辑
if i % 50 == 0:
torch.save(model, save_path+'-epoch:'+str(i)+ '-%.2f'%plt_val_acc[-1])
- 每隔 50 轮额外保存一次模型快照(虽然这里
epoch只有 5,所以这行代码实际上不会触发,除非训练很多轮)。
python
编辑
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])
)
else:: 如果这一轮不需要验证(即i % val_epoch != 0)。plt_val_loss.append(plt_val_loss[-1]): 为了保持绘图列表长度一致,复制上一次验证的结果填进去(这样画图时 x 轴不会错位)。print(...): 只打印训练集的信息。
10. 绘图 (Visualization)
python
编辑
plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title('loss')
plt.legend(['train', 'val'])
plt.show()
- 训练结束后,使用
matplotlib画出 Loss 曲线。 plt.plot(...): 绘制线条。plt.legend(...): 显示图例,区分训练 Loss 和验证 Loss。plt.show(): 显示图像。
python
编辑
plt.plot(plt_train_acc)
plt.plot(plt_val_acc)
plt.title('Accuracy')
plt.legend(['train', 'val'])
plt.savefig('acc.png')
plt.show()
- 同样的逻辑,画出准确率曲线。
plt.savefig('acc.png'): 将准确率曲线图保存为acc.png文件
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)