分类实战:对图片进行分类
其中带标签的训练数据共有11类,每类280个,不带标签的训练数据共6786个,不带标签的数据需要用到半监督学习的方法。
上半是对带标签的数据进行训练验证,下半则主要介绍半监督学习及代码复盘
1.数据处理
(1)数据增广,通过对现有的数据样本进行变换,生成更多数据样本,以提高模型泛化能力,减少过拟合风险。
#训练集数据变换
train_transform = transforms.Compose(
[
transforms.ToPILImage(), #将数据转换为PIL Image对象,以便对其进行操作
transforms.RandomResizedCrop(224), #随机放大裁剪,并调整到224*224
transforms.RandomRotation(50), #50度以内随机旋转
transforms.ToTensor() #将PIL Image对象转换为tensor,即模型接受的输入
]
)
#验证集数据变换 (验证和测试需要用原图,故不做变换)
val_transform = transforms.Compose(
[
transforms.ToTensor()
]
)
(2)数据集类,类中初始化、获取项目和长度三个函数与回归实战大体相似,但因为分类图片存储在文件夹中,不能使用csv模块读写,故另外定义一个读文件函数。
HW = 224
class food_Dataset(Dataset):
def __init__(self, path, mode="train"):
self.mode = mode
self.X, self.Y = self.read_file(path) #调用读文件函数
self.Y = torch.LongTensor(self.Y) #标签转为长整型
if mode == "train":
self.transform = train_transform #数据增广,允许图片进行变换
else:
self.transform = val_transform
def read_file(self, path):
for i in tqdm(range(11)): # 标签数据被分成11个文件夹,tqdm用于显示进度
file_dir = path + "/%02d" % i # %02d,保留两位整数
file_list = os.listdir(file_dir) # 列出文件夹下所有的文件名字
xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8) #某类的所有图片
yi = np.zeros(len(file_list), dtype=np.uint8) #某类图片对应的标签
for j, img_name in enumerate(file_list):
img_path = os.path.join(file_dir, img_name)
#合并目录路径和图片名字得到图片路径
img = Image.open(img_path) # 读取图片
img = img.resize((HW, HW)) # 调整图片大小 512*512 --> 224*224
xi[j, ...] = img #存入图像
yi[j] = i #存入标签
if i == 0: #如果是第一次则直接赋值,否则拼接以合并所有图像与标签
X = xi
Y = yi
else:
X = np.concatenate((X, xi), axis=0) #axis=0代表新内容放到下一行
Y = np.concatenate((Y, yi), axis=0)
print("读到了%d个数据" % len(Y)) #显示一共读到了多少数据
return X, Y
#读文件函数接收文件路径参数,返回所有读取并调整大小后的图像数据及其对应的标签
def __getitem__(self, item):
return self.transform(self.X[item]), self.Y[item]
def __len__(self):
return len(self.X)
#实例化
train_set = food_Dataset(train_path, "train")
val_set = food_Dataset(val_path, "val")
2.定义模型(三种方法)
(1)自定义。
class myModel(nn.Module):
def __init__(self, num_class): #num_class为图片类别数
super(myModel, self).__init__()
#3*224*224 ->512*7*7
#layer0到layer1为四个卷积层序列
self.layer0 = nn.Sequential(
nn.Conv2d(3, 64, 3, 1, 1), #卷积层 3*224*224->64*224*224
#(输入为3,输出为64,卷积核为3*3,步长1,填充1(保证输出与输入尺寸一致,根据卷积核大小定)
nn.BatchNorm2d(64), #批量归一化层(标准化处理,加速训练过程)
nn.ReLU(), #激活函数
nn.MaxPool2d(2), #最大池化层,池化窗口2*2 64*224*224->64*112*112
)
self.layer1 = nn.Sequential(
nn.Conv2d(64, 128, 3, 1, 1), # 64*112*112->128*112*112
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(2), # 128*112*112->128*56*56
)
self.layer2 = nn.Sequential(
nn.Conv2d(128, 256, 3, 1, 1), # 128*56*56->256*56*56
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(2), # 256*56*56->256*28*28
)
self.layer3 = nn.Sequential(
nn.Conv2d(256, 512, 3, 1, 1), # 256*28*28->512*28*28
nn.BatchNorm2d(512),
nn.ReLU(),
nn.MaxPool2d(2), # 512*28*28->512*14*14
)
self.pool = nn.MaxPool2d(2) #额外池化层 512*14*14->512*7*7(参数量25088)
self.fc1 = nn.Linear(25088, 1000) #第一个全连接层 25088-->1000
self.relu = nn.ReLU()
self.fc2 = nn.Linear(1000, num_class) #第二个全连接层 1000-->num_class
def forward(self, x):
x = self.layer0(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(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
#实例化
model = myModel(11)
其中每个卷积层也可如下,不过前向传播会稍繁复。
self.conv1 = nn.Conv2d(3, 64, 3, 1, 1)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU()
self.pool1 = nn.MaxPool2d(2)
(2)从其他文件中调用,比如之前写过的可以直接拿来用。
from model_utils.model import initialize_model
#引用model_utils文件夹下的model.py文件中定义的initialize_model模型模块
model, _ = initialize_model("resnet18", 11, use_pretrained=True)
#调用并传入相关参数,具体看函数定义
(3)从模块里调用。
from torchvision.models import resnet18 #引入ResNet-18卷积神经网络模型
model = resnet18(pretrained=True) #实例化,True用架构和参数,false只用架构
in_fetures = model.fc.in_features #获取原输入特征
model.fc = nn.Linear(in_fetures, 11) #建立新的全连接层,接受原来的输入,但调整输出为所需
3.训练和验证
与回归实战类似,区别在于用最大准确率代替了最小损失,初始化为0,若验证准确率大于最大准确率则更新模型。同时记录了每轮模型在训练集和验证集上的准确率,进行可视化。
在训练集上的准确次数计算如下:
train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())
#argmax指出最大值下标, 最大值下标与标签比较得到预测正确数量
5.配置及调用
本次实战使用了新的优化器AdamW,它与SGD有两个区别,一是SGD主要计算某点的梯度,而AdamW需要综合该点与该点以前的梯度;二是,可以自动调整学习率,更不容易出现梯度爆炸。
总体而言AdamW能够提供比SGD更稳定的训练过程。
另外也使用了新的损失计算函数--交叉熵,用于处理本次实战面对的分类问题。
所得结果如下:
标签:512,nn,--,self,李哥,64,model,224,复盘 From: https://blog.csdn.net/m0_69136216/article/details/145213829