目录
VGG19实现
1.为数据打标签的generate_txt.py
2.对图像进行预处理的data_process.py
3.VGG19的网络构建代码net_VGG19.py
4.训练得到pth模型参数文件的get_pth_file.py
5.预测代码predict.py
6.预测VGG16与VGG19结果对比
VGG19实现
1.为数据打标签的generate_txt.py
这里的程序设计思想还是可以学一下的。
import os
from os import getcwd # 文件夹操作
# 写入数据集对应的文件夹
classes = ['cat','dog']
sets = ['train']
# 主程序执行
if __name__ == "__main__":
    wd = getcwd() # 获取当前工作目录
    # 说明:当前代码的目录关系是:sets-->subset(当前只有一个“train”)-->type_name(“train”下的关于类别的文件夹,比如“dog”)-->具体样本数据
    # 遍历sets中的每个文件夹,当前sets中只有"train"一个文件夹
    for subset in sets:
        list_file = open("cls_"+subset+".txt",'w')
        # 拿到每个子文件夹的目录
        path_subset = subset
        type_names = os.listdir(path_subset) # 拿到subset文件夹下的所有动物分类文件夹type_name,存到type_names列表中
        # 遍历subset中的每个文件夹type_name
        """
        它遍历名为type_names的列表中的每个元素。
        代码的目的是检查每个元素是否存在于名为classes的集合中。
        如果存在,代码会继续执行下一次循环,处理下一个元素;
        如果不存在,代码会跳过当前循环并继续执行下一次循环。
        """
        for type_name in type_names:
            if type_name not in classes:
                continue
            # 打标签
            type_id = classes.index(type_name) # 按type_name文件夹在classes文件夹中的索引,为type_name编号
            # 生成每个type_name文件夹的路径
            type_path = os.path.join(path_subset,type_name)
            photo_names = os.listdir(type_path) # 拿到type_name文件夹下的所有图片,组成一个列表phto_names
            # 处理每一张图片
            """
            这段代码的作用是遍历名为photos_name的列表中的每个元素,
            并根据文件名的扩展名来过滤文件。代码会判断文件的扩展名是否为.jpg、.png或.jpeg,
            如果不是这些扩展名之一,则跳过当前文件的处理。对于符合条件的文件,
            代码会将其写入到名为list_file的文件中,并写入文件的类别ID和路径信息。
            """
            for photo_name in photo_names:
                """
                这一行代码使用os.path.splitext()函数将文件名photo_name分成文件名部分和扩展名部分,
                并将扩展名赋值给变量postfix。下划线_表示不使用文件名部分,只关注扩展名。
                """
                _,postfit = os.path.splitext(photo_name) # #该函数用于分离文件名与拓展名
                # 如果拓展名不在如下的列表中,则跳过当前循环;如果在,则继续
                if postfit not in ['.jpg','.png','.jpeg']:
                    continue
                # 将文件的类别ID和完整路径信息写入到名为list_file的文件中
                photo_path = os.path.join(type_path,photo_name)
                # print(wd) # C:\Users\ZARD\PycharmProjects\pythonProject\AAA_FX\revise_VGG19
                # 如上可知,wd为该项目的路径
                list_file.write(str(type_id)+';'+'%s/%s'%(wd,photo_path))
                list_file.write('\n') # 这一行代码写入一个换行符,将下一个文件的记录写入到新的一行
        list_file.close() 
2.对图像进行预处理的data_process.py
对数据做一些基本操作,可根据实际需求进行更改。
import cv2
import numpy as np
import torch.utils.data as data
from PIL import  Image
def preprocess_input(x):
    x/=127.5
    x-=1.
    return x
def cvtColor(image):
    if len(np.shape(image))==3 and np.shape(image)[-2]==3:
        return image
    else:
        image=image.convert('RGB')
        return image
class DataGenerator(data.Dataset):
    def __init__(self,annotation_lines,inpt_shape,random=True):
        self.annotation_lines=annotation_lines
        self.input_shape=inpt_shape
        self.random=random
    def __len__(self):
        return len(self.annotation_lines)
    def __getitem__(self, index):
        annotation_path=self.annotation_lines[index].split(';')[1].split()[0]
        image=Image.open(annotation_path)
        image=self.get_random_data(image,self.input_shape,random=self.random)
        image=np.transpose(preprocess_input(np.array(image).astype(np.float32)),[2,0,1])
        y=int(self.annotation_lines[index].split(';')[0])
        return image,y
    def rand(self,a=0,b=1):
        return np.random.rand()*(b-a)+a
    def get_random_data(self,image,inpt_shape,jitter=.3,hue=.1,sat=1.5,val=1.5,random=True):
        image=cvtColor(image)
        iw,ih=image.size
        h,w=inpt_shape
        if not random:
            scale=min(w/iw,h/ih)
            nw=int(iw*scale)
            nh=int(ih*scale)
            dx=(w-nw)//2
            dy=(h-nh)//2
            image=image.resize((nw,nh),Image.BICUBIC)
            new_image=Image.new('RGB',(w,h),(128,128,128))
            new_image.paste(image,(dx,dy))
            image_data=np.array(new_image,np.float32)
            return image_data
        new_ar=w/h*self.rand(1-jitter,1+jitter)/self.rand(1-jitter,1+jitter)
        scale=self.rand(.75,1.25)
        if new_ar<1:
            nh=int(scale*h)
            nw=int(nh*new_ar)
        else:
            nw=int(scale*w)
            nh=int(nw/new_ar)
        image=image.resize((nw,nh),Image.BICUBIC)
        #将图像多余的部分加上灰条
        dx=int(self.rand(0,w-nw))
        dy=int(self.rand(0,h-nh))
        new_image=Image.new('RGB',(w,h),(128,128,128))
        new_image.paste(image,(dx,dy))
        image=new_image
        #翻转图像
        flip=self.rand()<.5
        if flip: image=image.transpose(Image.FLIP_LEFT_RIGHT)
        rotate=self.rand()<.5
        if rotate:
            angle=np.random.randint(-15,15)
            a,b=w/2,h/2
            M=cv2.getRotationMatrix2D((a,b),angle,1)
            image=cv2.warpAffine(np.array(image),M,(w,h),borderValue=[128,128,128])
        #色域扭曲
        hue=self.rand(-hue,hue)
        sat=self.rand(1,sat) if self.rand()<.5 else 1/self.rand(1,sat)
        val=self.rand(1,val) if self.rand()<.5 else 1/self.rand(1,val)
        x=cv2.cvtColor(np.array(image,np.float32)/255,cv2.COLOR_RGB2HSV)#颜色空间转换
        x[...,1]*=sat
        x[...,2]*=val
        x[x[:,:,0]>360,0]=360
        x[:,:,1:][x[:,:,1:]>1]=1
        x[x<0]=0
        image_data=cv2.cvtColor(x,cv2.COLOR_HSV2RGB)*255
        return image_data 
3.VGG19的网络构建代码net_VGG19.py
其实该代码可以直接去torch的官网下,而且如果想改动VGG网络结构,只需微调一下,就可以实现其代码了。
在torch官网能够下载到一些预训练的模型:
https://pytorch.org/vision/stable/models/vgg.html
比如如下的VGG16:

逐条解析该程序:
导包和下载网络权重:
 
 import torch
import torch.nn as nn
model_urls = {
    "vgg19":  "https://download.pytorch.org/models/vgg19-dcbb9e9d.pth",
}#权重下载网址,该地址在torch官网上可下载 
 
VGG网络的类:
class VGG(nn.Module): def __init__(self, features, num_classes = 1000, init_weights = True, dropout = 0.5): #继承 super(VGG,self).__init__() self.features = features self.avgpool = nn.AdaptiveAvgPool2d((7, 7)) # AdaptiveAvgPool2d使处于不同大小的图片也能进行分类 self.classifier = nn.Sequential( nn.Linear(512 * 7 * 7, 4096), nn.ReLU(True), nn.Dropout(p=dropout), nn.Linear(4096, 4096), nn.ReLU(True), nn.Dropout(p=dropout), # 完成4096的全连接 nn.Linear(4096, num_classes), #对 num_classes的分类 ) if init_weights: for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu") if m.bias is not None: nn.init.constant_(m.bias, 0) elif isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 1) nn.init.constant_(m.bias, 0) elif isinstance(m, nn.Linear): nn.init.normal_(m.weight, 0, 0.01) nn.init.constant_(m.bias, 0)  def forward(self, x): x = self.features(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.classifier(x) return x
网络结构设置:
def make_layers(cfg, batch_norm = False): # make_layers对输入的cfg进行循环 layers = [] in_channels = 3 for v in cfg: if v == "M": # 对cfg进行输入循环,取第一个v layers += [nn.MaxPool2d(kernel_size=2, stride=2)] # 把输入图像进行缩小 else: #v = cast(int, v) conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) if batch_norm: layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)] else: layers += [conv2d, nn.ReLU(inplace=True)] in_channels = v return nn.Sequential(*layers)   cfgs = { "VGG19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"], }# 这部分代码比较讲究,可参考B站视频,地址贴在下面了 def vgg19(pretrained=False, progress=True,num_classes=2): model = VGG(make_layers(cfgs["VGG19"])) if pretrained: from torch.hub import load_state_dict_from_url state_dict = load_state_dict_from_url(model_urls['vgg19'],model_dir='./model' ,progress=progress)#预训练模型地址 model.load_state_dict(state_dict) if num_classes != 1000: model.classifier = nn.Sequential( nn.Linear(512 * 7 * 7, 4096), nn.ReLU(True), nn.Dropout(p=0.5), # 随机删除一部分不合格 nn.Linear(4096, 4096), nn.ReLU(True), # 防止过拟合 nn.Dropout(p=0.5), nn.Linear(4096, num_classes), ) return model
if __name__ == '__main__': in_data = torch.ones(1, 3, 224, 224) net = vgg19(pretrained=False, progress=True, num_classes=2) out = net(in_data) print(out)
4.训练得到pth模型参数文件的get_pth_file.py
生成训练集和测试集:
'''数据集''' annotation_path = 'cls_train.txt' # 读取数据集生成的文件 with open(annotation_path,'r') as f: lines = f.readlines() # 拿到所有图片数据的地址,lines的数据类型是一个列表,其中存下了所有图片地址 #print(type(lines)) # <class 'list'> import numpy as np np.random.seed(10101) # 函数用于生成指定随机数 np.random.shuffle(lines) # 数据打乱 num_val = int(len(lines)*0.2) # 用做测试的数据数量 #print(num_val) #-->266 num_train = len(lines)-num_val # 训练的数据的数量 #输入图像大小 input_shape=[224,224] #导入图像大小 # 生成数据 from AAA_FX.revise_VGG19.data_process import DataGenerator  train_data = DataGenerator(lines[:num_train],input_shape,True) val_data = DataGenerator(lines[num_train:],input_shape,False)  val_len=len(val_data) print(val_len)#返回测试集长度
加载数据:
# 取黑盒子工具 """加载数据""" from torch.utils.data import DataLoader#工具取黑盒子,用函数来提取数据集中的数据(小批次) gen_train=DataLoader(train_data,batch_size=4)#训练集batch_size读取小样本,规定每次取多少样本 gen_test=DataLoader(val_data,batch_size=4)#测试集读取小样本
构建网络:
 
 '''构建网络'''
from net_VGG19 import vgg19
device=torch.device('cuda'if torch.cuda.is_available() else "cpu")#电脑主机的选择
net=vgg19(True, progress=True,num_classes=2)#定于分类的类别
net.to(device) 
 
 
选择优化器和学习率的调整方法:
'''选择优化器和学习率的调整方法''' lr=0.0001#定义学习率 optim=torch.optim.Adam(net.parameters(),lr=lr)#导入网络和学习率 sculer=torch.optim.lr_scheduler.StepLR(optim,step_size=1)#步长为1的读取
训练:
 
 '''训练'''
epochs=20#读取数据次数,每次读取顺序方式不同
for epoch in range(epochs):
    total_train=0 #定义总损失
    for data in gen_train:
        img,label=data
        with torch.no_grad():
            img =img.to(device)
            label=label.to(device)
        optim.zero_grad()
        output=net(img)
        train_loss=nn.CrossEntropyLoss()(output,label).to(device)
        train_loss.backward()#反向传播
        optim.step()#优化器更新
        total_train+=train_loss #损失相加
    sculer.step()
    total_test=0#总损失
    total_accuracy=0#总精度
    for data in gen_test:
        img,label =data #图片转数据
        with torch.no_grad():
            img=img.to(device)
            label=label.to(device)
            optim.zero_grad()#梯度清零
            out=net(img)#投入网络
            test_loss=nn.CrossEntropyLoss()(out,label).to(device)
            total_test+=test_loss#测试损失,无反向传播
            accuracy=((out.argmax(1)==label).sum()).clone().detach().cpu().numpy()#正确预测的总和比测试集的长度,即预测正确的精度
            total_accuracy+=accuracy
    print("训练集上的损失:{}".format(total_train))
    print("测试集上的损失:{}".format(total_test))
    print("测试集上的精度:{:.1%}".format(total_accuracy/val_len))#百分数精度,正确预测的总和比测试集的长度
    torch.save(net.state_dict(),"DogandCat{}.pth".format(epoch+1))
    print("模型已保存") 
 
 
5.预测代码predict.py
导入图像:
from PIL import Image test_pth='.\\train\cat\cat.6.jpg'#设置可以检测的图像 test=Image.open(test_pth)
处理图片:
'''处理图片''' from torchvision import transforms transform = transforms.Compose([transforms.Resize((224,224)),transforms.ToTensor()]) image=transform(test)
加载网络:
 
 '''加载网络'''
device=torch.device("cuda" if torch.cuda.is_available() else "cpu")#CPU与GPU的选择
net =vgg19()#输入网络
model=torch.load("./DogandCat8.pth",map_location=device)#已训练完成的结果权重输入
net.load_state_dict(model)#模型导入
net.eval()#设置为推测模式
image=torch.reshape(image,(1,3,224,224))#四维图形,RGB三个通
with torch.no_grad():
    out=net(image)
out=F.softmax(out,dim=1)#softmax 函数确定范围
out=out.data.cpu().numpy()
print(out)
a=int(out.argmax(1))#输出最大值位置
plt.figure()
list=['Cat','Dog']
plt.suptitle("Classes:{}:{:.1%}".format(list[a],out[0,a]))#输出最大概率的道路类型
plt.imshow(test)
plt.show() 
 
VGG16的道理是一样的,这里略。
6.预测VGG16与VGG19结果对比
VGG19的预测结果:

VGG16的预测结果:




















