食物分类项目学习(瞎写,巩固自用)
步骤1.读取数据
步骤2.要对图片先进行处理,图片大小转化(一般转变为3*224*224大小)
步骤3.通过数据模型
图片分类任务与回归任务相比,在模型中多了一些步骤,要对图片进行卷积运算,(3*224*224)->
(64*112*112)->(128*56*56)->(256*28*28)->(512*14*14)->(512*7*7)经过多次运算后,进行拉伸
x = x.view(x.size()[0], -1)
之后就可以理解为25088(512*7*7)个参数量的回归任务按回归的操作进行即可
具体流程、
1.建立数据集
class foodDataset(Dataset):
def __init__(self, path, mode):
self.mode = mode
if mode == "train":
self.transform = train_transform
else:
self.transform = val_transform
x, y = self.read_file(path)
self.X = x
self.Y = torch.LongTensor(y)
def __getitem__(self, item):
return self.transform(self.X[item]), self.Y[item]
def __len__(self):
return len(self.X)
def read_file(self, path):
for i in tqdm(range(11)):
file_dir = path + "/%02d" % i
file_list = os.listdir(file_dir)
xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8) # 多少图片, 图片大小, 图片层数(RGB),整数类型
yi = np.zeros(len(file_list)) # 存放标签
for j, each in enumerate(file_list): # 枚举函数实现返回列表中的序号和内容
img_path = file_dir + "/" + each
img = Image.open(img_path)
img = img.resize((HW, HW)) # 把图片大小变为224*224,改变尺寸
xi[j, ...] = img
yi[j] = i
if i == 0:
X = xi
Y = yi
else:
X = np.concatenate((X, xi), axis=0) # 在axis=1方向上(向下)拼接矩阵
Y = np.concatenate((Y, yi), axis=0)
print("read in img:%d" % len(Y))
return X, Y
在该类中除了必须的__init__,__gititem__,__len__外,额外定义了读取文件的函数,用于读取图片(以矩阵形式读取)
2.建立模型
class myModel(nn.Module):
def __init__(self , num_class):
super(myModel, self).__init__()
# 3*224*224-->512*7*7
self.layer1 = nn.Sequential( # 64*112*112
nn.Conv2d(3, 64, 3, 1, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.layer2 = nn.Sequential( # 128*56*56
nn.Conv2d(64, 128, 3, 1, 1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.layer3 = nn.Sequential( # 256*28*28
nn.Conv2d(128, 256, 3, 1, 1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.layer4 = nn.Sequential( # 512*14*14
nn.Conv2d(256, 512, 3, 1, 1),
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.pool =nn.MaxPool2d(2) #512*7*7
self.fc1 = nn.Linear(512*7*7, 1000)
self.relu =nn.ReLU()
self.fc2 =nn.Linear(1000,num_class)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.pool(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
分成了三个层方便书写,每个层中都进行了一次步长为1p,padding为1,卷积核大小为3的运算,层数每次加倍,然后是激活函数(relu)和最大值pooling,图片大小均除以2,最后进行回归操作(25088)->(1000)->(11)。
3.训练样本
训练过程都大同小异,用训练样本进行训练,验证样本进行验证,保存效果最好的模型。分类任务要保证准确率,因此引入了一个精准度的变量max_val_acc去衡量模型,而不是min_val_loss
def train_val(model, trainloader, valloader,optimizer, loss, epoch, device, save_path,no_label_loader, thres):
model = model.to(device) # 模型和数据 ,要在一个设备上。 cpu - gpu
plt_train_loss = []
plt_val_loss = []
plt_train_acc = []
plt_val_acc = []
max_val_acc = 0.0 # 记录训练验证loss 以及验证loss和结果
for i in range(epoch): # 训练epoch 轮
start_time = time.time() # 记录开始时间
model.train() # 模型设置为训练状态 结构
train_loss = 0.0
val_loss = 0.0
train_acc = 0.0
val_acc = 0.0
for data in trainloader: # 从训练集取一个batch的数据
optimizer.zero_grad() # 梯度清0
x, target = data[0].to(device), data[1].to(device) # 将数据放到设备上
pred = model(x) # 用模型预测数据
bat_loss = loss(pred, target) # 计算loss
bat_loss.backward() # 梯度回传, 反向传播。
optimizer.step() #用优化器更新模型。 轮到SGD出手了
train_loss += bat_loss.detach().cpu().item() #记录loss和
train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(),axis=1) == target.cpu().numpy()) # 记录一组样本中最大值所在的下标与目标对比,预测对则+1(注意从gpu上取下来)
plt_train_loss. append(train_loss/trainloader.dataset.__len__()) #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。
plt_train_acc.append(train_acc / trainloader.dataset.__len__()) # 记录acc到列表。注意是平均的acc ,因此要除以数据集长度。
model.eval() # 模型设置为验证状态
with torch.no_grad(): # 模型不再计算梯度
for data in valloader: # 从验证集取一个batch的数据
val_x , val_target = data[0].to(device), data[1].to(device) # 将数据放到设备上
val_pred = model(val_x) # 用模型预测数据
val_bat_loss = loss(val_pred, val_target) # 计算loss
val_loss += val_bat_loss.detach().cpu().item() # 计算loss
val_acc += np.sum(np.argmax(val_pred.detach().cpu().numpy(),axis=1) == val_target.cpu().numpy())
plt_val_loss.append(val_loss/valloader.dataset.__len__()) #记录loss到列表。注意是平均的loss ,因此要除以数据集长度。
plt_val_acc.append(val_acc / valloader.dataset.__len__()) # 记录acc到列表。注意是平均的acc ,因此要除以数据集长度。
if val_acc > max_val_acc:
torch.save(model, save_path) #如果loss比之前的最小值小, 说明模型更优, 保存这个模型
max_val_acc = val_acc
print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f Trainacc : %.6f | valLoss: %.6f valacc: %.6f' % \
(i, epoch, time.time()-start_time, plt_train_loss[-1], plt_train_acc[-1], plt_val_loss[-1], plt_val_acc[-1])
) #打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。
plt.plot(plt_train_loss) # 画图, 向图中放入训练loss数据
plt.plot(plt_val_loss) # 画图, 向图中放入训练loss数据
plt.title('loss') # 画图, 标题
plt.legend(['train', 'val']) # 画图, 图例
plt.show() # 画图, 展示
plt.plot(plt_train_acc) # 画图, 向图中放入训练loss数据
plt.plot(plt_val_acc) # 画图, 向图中放入训练loss数据
plt.title('acc') # 画图, 标题
plt.legend(['train', 'val']) # 画图, 图例
plt.show()
相关知识点
1.主要注意的是图片的维度变化和图片大小。
2.计算图片大小
(p1-q+padding*2)/b+1=p2
p1:原图片大小,q:卷积核大小,padding,b:步长,p2:新图片大小
pooling计算
图片大小长宽分别除以步长(一般为2)
计算卷积核大小
即新图片层数*卷积核大小**2