1. 数据
1.数据读取
torch.utils.data.Dataset
2.数据变换
torchvision.transforms
3.数据增强
mixup cutout
数据格式的统一
划分训练集和测试集
读入数据是按批的.每次读入一个批次的数据,放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。
参数
- batch size
batch_size = 16
# 批次的大小
- 初始学习率(初始)
lr = 1e-4
# 优化器的学习率
- 训练次数(max_epochs)
max_epochs = 100
- GPU配置
# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
数据读入
- 通过Dataset+DataLoader的方式完成,Dataset定义好数据的格式和数据变换形式,DataLoader用iterative的方式不断读入批次数据。
# 以cifar10数据集为例给出构建Dataset类的方式
import torch
from torchvision import datasets
from torch.utils.data import DataLoader
train_data = datasets.ImageFolder(train_path, transform=data_transform)
val_data = datasets.ImageFolder(val_path, transform=data_transform)
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
# num_workers:有多少个进程用于读取数据
# shuffle:是否将读入的数据打乱
# drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练
# 其中图片存放在一个文件夹,另外有一个csv文件给出了图片名称对应的标签。这种情况下需要自己来定义Dataset类
class MyDataset(Dataset):
def __init__(self, data_dir, info_csv, image_list, transform=None):
"""
Args:
data_dir: path to image directory.
info_csv: path to the csv file containing image indexes
with corresponding labels.
image_list: path to the txt file contains image names to training/validation set
transform: optional transform to be applied on a sample.
"""
label_info = pd.read_csv(info_csv)
image_file = open(image_list).readlines()
self.data_dir = data_dir
self.image_file = image_file
self.label_info = label_info
self.transform = transform
def __getitem__(self, index):
"""
Args:
index: the index of item
Returns:
image and its labels
"""
image_name = self.image_file[index].strip('\n')
raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
label = raw_label.iloc[:,0]
image_name = os.path.join(self.data_dir, image_name)
image = Image.open(image_name).convert('RGB')
if self.transform is not None:
image = self.transform(image)
return image, label
def __len__(self):
return len(self.image_file)
2. 模型
# AlexNet
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
net = AlexNet()
print(net)
3. 优化器和损失函数选择
优化器
优化器是根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值,使得模型输出更加接近真实标签。
optimizer:SGD/
class Optimizer(object):
def __init__(self, params, defaults):
self.defaults = defaults
self.state = defaultdict(dict)
self.param_groups = []
defaults
:存储的是优化器的超参数state
:参数的缓存param_groups
:管理的参数组,是一个list
'''
optimizer在一个神经网络的epoch中需要实现下面两个步骤:
1.梯度置零
2.梯度更新
'''
optimizer = torch.optim.SGD(net.parameters(), lr=1e-5)
for epoch in range(EPOCH):
...
optimizer.zero_grad() #梯度置零
loss = ... #计算loss
loss.backward() #BP反向传播
optimizer.step() #梯度更新
损失函数
- 损失函数:模型的负反馈
loss
对应参数学者
4. 训练
1.sklearn kfols:
for kfold:
for i in range(num_epochs):
for image.labels in dataloader:
xxx
model.train() # 训练状态
model.eval() # 验证/测试状态
def train(epoch):
model.train()
train_loss = 0
for data, label in train_loader:
# 将数据放到GPU上用于后续计算
data, label = data.cuda(), label.cuda()
# 开始用当前批次数据做训练时,应当先将优化器的梯度置零
optimizer.zero_grad()
# 将data送入模型中训练
output = model(data)
# 根据预先定义的criterion计算损失函数
loss = criterion(label, output)
# 将loss反向传播回网络
loss.backward()
# 使用优化器更新模型参数
optimizer.step()
train_loss += loss.item()*data.size(0)
train_loss = train_loss/len(train_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
验证:
- 需要预先设置torch.no_grad,以及将model调至eval模式
- 不需要将优化器的梯度置零
- 不需要将loss反向回传到网络
- 不需要更新optimizer
def val(epoch):
model.eval()
val_loss = 0
with torch.no_grad():
for data, label in val_loader:
# 将数据放到GPU上用于后续计算
data, label = data.cuda(), label.cuda()
# 将data送入模型中训练
output = model(data)
preds = torch.argmax(output, 1)
# 根据预先定义的criterion计算损失函数
loss = criterion(output, label)
val_loss += loss.item()*data.size(0)
running_accu += torch.sum(preds == label.data)
val_loss = val_loss/len(val_loader.dataset)
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))