深度学习——图像分类模型最简单的train.py

import os 
import sys
import json 
import torch
import torch.nn as nn
from torchvision import transforms, datasets 
import torch.optim as optim 
from tqdm import tqdm  
#from classic_models.alexnet import AlexNet
from classic_models.googlenet_v1 import  GoogLeNet

def main():
    # 判断可用设备
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

    # 注意改成自己的数据集路径
    data_path = "G:\\flower"
    assert os.path.exists(data_path), "{} path does not exist.".format(data_path) 

    # 数据预处理与增强
    """ 
    ToTensor()能够把灰度范围从0-255变换到0-1之间的张量.
    transform.Normalize()则把0-1变换到(-1,1). 具体地说, 对每个通道而言, Normalize执行以下操作: image=(image-mean)/std
    其中mean和std分别通过(0.5,0.5,0.5)和(0.5,0.5,0.5)进行指定。原来的0-1最小值0则变成(0-0.5)/0.5=-1; 而最大值1则变成(1-0.5)/0.5=1. 
    也就是一个均值为0, 方差为1的正态分布. 这样的数据输入格式可以使神经网络更快收敛。
    """
    data_transform = {
        "train": transforms.Compose([transforms.Resize(224),   # 将图片的短边缩放到224,图片的长边和短边的比值不变,即不能保证每张图片都是224*224大小,那么下一步的裁剪就有必要了
                                     transforms.CenterCrop(224),  # 由中心向两边进行裁剪,裁剪的尺寸为224*224
                                     transforms.ToTensor(),       # 可以将PIL和numpy格式的数据从[0,255]范围转换到[0,1] 。另外原始数据的shape是(H x W x C),这步后shape会变为(C x H x W)
                                     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]), # Normalize(mean, std, inplace=False),三通道中Normalize里面一般是Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),将上一步的数据范围由[0,1]转换为[-1,1]

        "val": transforms.Compose([transforms.Resize((224, 224)),  # val不需要任何数据增强
                                   transforms.ToTensor(),
                                   transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}


    # 使用ImageFlolder加载数据集中的图像,并使用指定的预处理操作来处理图像, ImageFlolder会同时返回图像和对应的标签。 (image path, class_index) tuples
    train_dataset = datasets.ImageFolder(root=os.path.join(data_path, "train"), transform=data_transform["train"]) # root:图片存储的根目录,即各类别文件夹所在目录的上一级目录。
    validate_dataset = datasets.ImageFolder(root=os.path.join(data_path, "val"), transform=data_transform["val"])  # transform:对图片进行预处理的操作(函数)。在data_transform中已经定义好
    train_num = len(train_dataset) # 计算train_dataset里面的图片个数
    val_num = len(validate_dataset) # 计算validate_dataset里面的图片个数

    # 使用class_to_idx给类别一个index,作为训练时的标签: {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
    flower_list = train_dataset.class_to_idx # class_to_idx是train_dataset里面的一个函数,返回一个字典,即flower_list是一个字典

    # 创建一个字典,存储index和类别的对应关系,在模型推理阶段会用到。
    cla_dict = dict((val, key) for key, val in flower_list.items())  # items()方法将字典里对应的一对键和值以元组的形式(键, 值),存储为所生成序列里的单个元素

    # 将字典写成一个json文件
    json_str = json.dumps(cla_dict, indent=4)  # json.dumps()是把python对象转换成json对象的一个过程,生成的是字符串。
    with open(os.path.join(data_path, 'class_indices.json') , 'w') as json_file:
        json_file.write(json_str)

    batch_size = 32 # batch_size大小,是超参,可调,如果模型跑不起来,尝试调小batch_size
 
    # 使用 DataLoader 将 ImageFloder 加载的数据集处理成批量(batch)加载模式
    train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True )
    validate_loader = torch.utils.data.DataLoader(validate_dataset, batch_size=4, shuffle=False ) # 注意,验证集不需要shuffle
    print("using {} images for training, {} images for validation.".format(train_num, val_num))
    
    # 实例化模型,并送进设备
    net = GoogLeNet(num_classes = 5) # 使用GoogleNet来定义网络模型,分类数为5
    #net = AlexNet(num_classes=5 )
    net.to(device)

    # 指定损失函数用于计算损失;指定优化器用于更新模型参数;指定训练迭代的轮数,训练权重的存储地址
    loss_function = nn.CrossEntropyLoss() # 交叉熵函数
    optimizer = optim.Adam(net.parameters(), lr=0.0002)
    epochs = 1
    save_path = os.path.abspath(os.path.join(os.getcwd(), './results/weights/alexnet'))  # os.getcwd()返回当前的文件目录,也就是后面的文件目录
    if not os.path.exists(save_path):    
        os.makedirs(save_path)       # 创建名为save_path的目录

    best_acc = 0.0 # 初始化验证集上最好的准确率,以便后面用该指标筛选模型最优参数。  
    for epoch in range(epochs):
        ############################################################## train ######################################################
        net.train() 
        acc_num = torch.zeros(1).to(device)    # 初始化,用于计算训练过程中预测正确的数量
        sample_num = 0                         # 初始化,用于记录当前迭代中,已经计算了多少个样本
        # tqdm是一个进度条显示器,可以在终端打印出现在的训练进度
        # train_loader:是需要迭代的对象,通常为列表或者生成器,其中包含训练数据。进度条会遍历该对象,并相应地更新进度。
        # file=sys.stdout:这个参数指定了进度条应该写入其输出的位置。在这种情况下,它被设置为sys.stdout,表示标准输出流(通常是控制台)。因此,进度条将显示在控制台中。
        # ncols=100:这个参数设置进度条的宽度,以字符为单位。在这里,它被设置为100个字符
        train_bar = tqdm(train_loader, file=sys.stdout, ncols=100)
        for data in train_bar :
            images, labels = data 
            sample_num += images.shape[0] #[32, 3, 224, 224]
            optimizer.zero_grad()         # 梯度初始化为零,把loss关于weight的导数变成0,避免梯度的叠加效应
            outputs = net(images.to(device)) # output_shape: [batch_size, num_classes]
            pred_class = torch.max(outputs, dim=1)[1] # torch.max 返回值是一个tuple,第一个元素是max值,第二个元素是max值的索引。
            acc_num += torch.eq(pred_class, labels.to(device)).sum()  # 是一个比较操作,它会将预测的类别(pred_class)和标签(labels)进行逐元素的比较,返回一个布尔类型的张量,表示对应位置上两个值是否相等。
                                                                      # sum() 是对布尔类型的张量进行求和操作,将所有为 True 的元素相加,得到一个标量值,表示预测正确的样本数量。
            loss = loss_function(outputs, labels.to(device)) # 求损失
            loss.backward() # 自动求导
            optimizer.step() # 梯度下降

            # print statistics 
            train_acc = acc_num.item() / sample_num 
            # .desc是进度条tqdm中的成员变量,作用是描述信息
            train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,  epochs, loss)

        # validate
        net.eval() #不启用 BatchNormalization 和 Dropout。此时pytorch会自动把BN和DropOut固定住,不会取平均,而是用训练好的值。不然的话,一旦test的batch_size过小,很容易就会因BN层导致模型performance损失较大;
        acc_num = 0.0  # accumulate accurate number per epoch
        with torch.no_grad(): 
            for val_data in validate_loader:
                val_images, val_labels = val_data
                outputs = net(val_images.to(device)) 
                predict_y = torch.max(outputs, dim=1)[1] 
                acc_num += torch.eq(predict_y, val_labels.to(device)).sum().item() 

        val_accurate = acc_num / val_num
        print('[epoch %d] train_loss: %.3f  train_acc: %.3f  val_accuracy: %.3f' %  (epoch + 1, loss, train_acc, val_accurate))   
        # 判断当前验证集的准确率是否是最大的,如果是,则更新之前保存的权重
        if val_accurate > best_acc:
            best_acc = val_accurate
            torch.save(net.state_dict(), os.path.join(save_path, "AlexNet.pth") ) # state_dict()返回一个包含了模型所有参数(权重和偏置)的字典。这个字典中的键是参数的名称,而对应的值则是该参数的张量。

        # 每次迭代后清空这些指标,重新计算 
        train_acc = 0.0
        val_accurate = 0.0

    print('Finished Training')

 
# if __name__ == '__main__':
#     main()
main()

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
深度学习是机器学习中的一种,它使用神经网络模型来解决复杂的模式识别和预测问题。Python是深度学习中最常用的编程语言之一。以下是一个简单的深度学习Python代码示例,用于识别手写数字: ``` # 导入必要的库 import keras from keras.datasets import mnist from keras.models import Sequential from keras.layers import Dense, Dropout from keras.optimizers import RMSprop # 加载MNIST数据集 (x_train, y_train), (x_test, y_test) = mnist.load_data() # 数据处理 x_train = x_train.reshape(60000, 784) x_test = x_test.reshape(10000, 784) x_train = x_train.astype('float32') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 y_train = keras.utils.to_categorical(y_train, 10) y_test = keras.utils.to_categorical(y_test, 10) # 建立模型 model = Sequential() model.add(Dense(512, activation='relu', input_shape=(784,))) model.add(Dropout(0.2)) model.add(Dense(512, activation='relu')) model.add(Dropout(0.2)) model.add(Dense(10, activation='softmax')) # 编译模型 model.compile(loss='categorical_crossentropy', optimizer=RMSprop(), metrics=['accuracy']) # 训练模型 history = model.fit(x_train, y_train, batch_size=128, epochs=20, verbose=1, validation_data=(x_test, y_test)) # 评估模型 score = model.evaluate(x_test, y_test, verbose=0) print('Test loss:', score) print('Test accuracy:', score) ``` 这个代码演示了一个基本的深度神经网络的训练过程。在这个例子中,我们使用Keras框架来构建、编译和训练一个包含两个隐藏层的神经网络,用于识别手写数字。通过这个代码,你可以学习到如何加载数据、预处理数据、构建模型、编译模型、训练模型和评估模型。当然,深度学习的应用十分广泛,可以用于图像识别、自然语言处理、音频处理等多个领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值