pytorch の高度な学習 (5): ナニー レベルのニューラル ネットワーク転移学習アプリケーションの詳細な紹介、トレーニング済みモデルを必要なモデルに置き換える方法

コードリソースとデータセットリソースは、高度な学習 (4) でコードを使用し、一緒に食べることができます ~

Pytorch の高度な学習 (4): データ トレーニングに異なる分類モデルを使用する (alexnet、resnet、vgg など) - プログラマー募集

データセット: 花のデータセット、合計 5 つのカテゴリ。

  

目次

 1.事前トレーニング前のモデルトレーニング精度

1. CreateDataset.py は独自のデータセットを生成します

2.TrainModal.py トレーニング ニューラル ネットワーク

3. 走行結果 

4. 方法の改善: 転移学習を使用する 

2.転移学習

1. コンセプト

2. 目標

3. 具体的な手順

3.1 事前学習済みのネットワーク構造を表示する

3.2 fc 層の出力カテゴリの数を変更する

3.3 resnet18 のプレトレーニング パラメータをダウンロードする

3.4 プレトレーニング パラメータの fc 層パラメータを削除する

3.5 構築したモデルのフレームワークを表示する

3.6 独自のモデル パラメータを更新する

3.7 fcレイヤー以外のパラメータをフリーズする

3.8 損失関数とオプティマイザーを定義し、fc 層パラメーターの勾配を更新する 

3.9 モデルをロードして訓練する

3. 完全なコード


 1.事前トレーニング前のモデルトレーニング精度

1. CreateDataset.py は独自のデータセットを生成します

'''
生成训练集和测试集,保存在txt文件中
'''
##相当于模型的输入。后面做数据加载器dataload的时候从里面读他的数据
import os
import random#打乱数据用的

# 百分之60用来当训练集
train_ratio = 0.6

# 用来当测试集
test_ratio = 1-train_ratio

rootdata = r"data"  #数据的根目录

train_list, test_list = [],[]#读取里面每一类的类别
data_list = []

#生产train.txt和test.txt
class_flag = -1
for a,b,c in os.walk(rootdata):
    print(a)
    for i in range(len(c)):
        data_list.append(os.path.join(a,c[i]))

    for i in range(0,int(len(c)*train_ratio)):
        train_data = os.path.join(a, c[i])+'\t'+str(class_flag)+'\n'
        train_list.append(train_data)

    for i in range(int(len(c) * train_ratio),len(c)):
        test_data = os.path.join(a, c[i]) + '\t' + str(class_flag)+'\n'
        test_list.append(test_data)

    class_flag += 1

print(train_list)
random.shuffle(train_list)#打乱次序
random.shuffle(test_list)

with open('train.txt','w',encoding='UTF-8') as f:
    for train_img in train_list:
        f.write(str(train_img))

with open('test.txt','w',encoding='UTF-8') as f:
    for test_img in test_list:
        f.write(test_img)

2.TrainModal.py トレーニング ニューラル ネットワーク

resnet18 ニューラル ネットワークが使用され、トレーニング パラメーターは使用されず、エポック = 5 であり、現時点では精度は非常に低くなります。

'''
    加载pytorch自带的模型,从头训练自己的数据
'''
import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData

# 设置显卡型号为1
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '1'


from torchvision.models import alexnet  #最简单的模型
from torchvision.models import vgg11, vgg13, vgg16, vgg19   # VGG系列
from torchvision.models import resnet18, resnet34,resnet50, resnet101, resnet152    # ResNet系列
from torchvision.models import inception_v3     # Inception 系列

# 定义训练函数,需要
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    # 从数据加载器中读取batch(一次读取多少张,即批次数),X(图片数据),y(图片真实标签)。
    for batch, (X, y) in enumerate(dataloader):
        # 将数据存到显卡
        X, y = X.cuda(), y.cuda()

        # 得到预测的结果pred
        pred = model(X)

        # 计算预测的误差
        # print(pred,y)
        loss = loss_fn(pred, y)

        # 反向传播,更新模型参数
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 每训练10次,输出一次当前信息
        if batch % 10 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")


def test(dataloader, model):
    size = len(dataloader.dataset)
    # 将模型转为验证模式
    model.eval()
    # 初始化test_loss 和 correct, 用来统计每次的误差
    test_loss, correct = 0, 0
    # 测试时模型参数不用更新,所以no_gard()
    # 非训练, 推理期用到
    with torch.no_grad():
        # 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
        for X, y in dataloader:
            # 将数据转到GPU
            X, y = X.cuda(), y.cuda()
            # 将图片传入到模型当中就,得到预测的值pred
            pred = model(X)
            # 计算预测值pred和真实值y的差距
            test_loss += loss_fn(pred, y).item()
            # 统计预测正确的个数
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= size
    correct /= size
    print(f"correct = {correct}, Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")




if __name__=='__main__':
    batch_size = 8

    # # 给训练集和测试集分别创建一个数据集加载器
    train_data = LoadData("train.txt", True)
    valid_data = LoadData("test.txt", False)


    train_dataloader = DataLoader(dataset=train_data, num_workers=4, pin_memory=True, batch_size=batch_size, shuffle=True)
    test_dataloader = DataLoader(dataset=valid_data, num_workers=4, pin_memory=True, batch_size=batch_size)

    # 如果显卡可用,则用显卡进行训练
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Using {device} device")


    '''
        随着模型的加深,需要训练的模型参数量增加,相同的训练次数下模型训练准确率起来得更慢
    '''
   
    model = resnet18(weights=False, num_classes=5).to(device)    # 43.6%
 


    print(model)
    # 定义损失函数,计算相差多少,交叉熵,
    loss_fn = nn.CrossEntropyLoss()

    # 定义优化器,用来训练时候优化模型参数,随机梯度下降法
    optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)  # 初始学习率


    # 一共训练1次
    epochs = 5
    for t in range(epochs):
        print(f"Epoch {t+1}\n-------------------------------")
        time_start = time.time()
        train(train_dataloader, model, loss_fn, optimizer)
        time_end = time.time()
        print(f"train time: {(time_end-time_start)}")
        test(test_dataloader, model)
    print("Done!")

    # 保存训练好的模型
    torch.save(model.state_dict(), "model_resnet18.pth")
    print("Saved PyTorch Model Success!")

3. 走行結果 

5 エポックの正解率を考えると、事前トレーニング済みのモデルをトレーニングに使用しない場合、5 エポック トレーニング後の正解率は 57% です。

エポック 1
-------------------------------
トレーニング時間: 21.131392002105713
正しい = 0.48411322934719814、テスト エラー: 
 精度: 48.4%、平均損失: 0.163418 

エポック 2
-------------------------------
トレーニング時間: 17.17208242416382
正解 = 0.4927787406123628、テスト エラー: 
 精度: 49.3%、平均損失: 0.150533 

エポック 3
-------------------------------
トレーニング時間: 17.276950359344482
正解 = 0.5343731946851531、テスト エラー: 
 精度: 53.4%、平均損失: 0.138617 

エポック 4
-------------------------------
トレーニング時間: 16.926883220672607
正解 = 0.5557481224725592、テスト エラー: 
 精度: 55.6%、平均損失: 0.135120 

エポック 5
-------------------------------
トレーニング時間: 17.293556213378906
正解 = 0.5678798382437897、テスト エラー: 
精度: 56.8%、平均損失: 0.136653 

終わり!
保存された PyTorch モデルの成功!

4. 方法の改善: 転移学習を使用する 

        より高い精度をできるだけ早く達成したい場合は、インターネットでトレーニングされた事前トレーニング済みモデルのパラメーターを使用する必要がありますが、事前トレーニング済みモデルは 1000 カテゴリのデータセットを使用してトレーニングされるため、最後の全結合層 出力カテゴリは 1000 で、データセットのカテゴリ数は 5 であるため、全結合層のカテゴリ数を変更し、モデルを必要なフレームワークに変更する必要があります. これが転移学習です.

2.転移学習

1. コンセプト

転移学習(転移)

移行学習 - 一部のパラメーターを凍結し、完全に接続されたレイヤーを変更する

Pytorch が事前トレーニング済みモデルの一部を読み込み、一部のレイヤーをフリーズする_事前トレーニング済みモデルがバッチ正規化をフリーズする_csdn_1HAO のブログ - CSDN ブログ

        転移学習とは、タスクA用に開発したモデルを出発点として、タスクB用のモデルを開発する過程で再利用する機械学習手法です。

        ここで主に使用するのは、モデルベースの転送 (パラメーター ベースの TL)です。ソース ドメインとターゲット ドメインのパラメーター共有モデルを使用します。移行を実現するために、移行元ドメインと移行先ドメインの間で共有されるパラメーター情報を見つける方法を指します。この移行方法に必要な前提条件は次のとおりです。 ソース ドメインのデータとターゲット ドメインのデータは、いくつかのモデル パラメーターを共有できます。

2. 目標

事前トレーニング済みモデルのトレーニング パラメーターを使用しますが、元の全結合 (fc) 層によるカテゴリ 1000 出力を 5 に変更する必要があります。この例の転移学習には、主に 2 つの部分の変更が含まれます

  • ニューラル ネットワーク フレームワークの変更: resnet18 ニューラル ネットワークの fc 層の出力を 1000 から 5 に変更します。
  • トレーニング前のパラメーターの重みの変更: fc レイヤーの 1000 個の重みを削除し、新しくトレーニングした 5 つのパラメーターに置き換えます。

3. 具体的な手順

3.1 事前学習済みのネットワーク構造を表示する

  変更されていないフレームワーク pretrain_model の前に resnet18 ネットワークに名前を付けて出力すると、fc 層の出力カテゴリの数が 1000 であることがわかります。

pretrain_model = resnet18(weights=False) # 加载ResNet
print(pretrain_model)

3.2 fc 層の出力カテゴリの数を変更する

  •  pretrain_model の fc レイヤーへの入力アクセスは、num_ftrs でアクセスされます。
  • fc 層の出力層を 5 に置き換え、線形法を使用して入力層と出力層を線形に接続し、pretrain_model の fc 層に割り当てます。
  • pretrain_model のネットワーク構造を出力すると、out_features が表示され、5 に置き換えられます。
num_ftrs = pretrain_model.fc.in_features  # 获取全连接层的输入
    pretrain_model.fc = nn.Linear(num_ftrs, 5)  # 全连接层改为不同的输出,5
    print(pretrain_model)

3.3 resnet18 のプレトレーニング パラメータをダウンロードする

  • resnet の定義ページに入り、resnet 事前トレーニング モデル パラメーターのダウンロード URL を見つけることができます。

URL は: url="https://download.pytorch.org/models/resnet18-f37072fd.pth"
  • pth ファイルをダウンロードしたら、名前を resnet18_pretrain に変更し、プロジェクト ディレクトリに配置します。

  •  事前トレーニング済みパラメーター ファイルを読み込み、pretrained_dict という名前のディクショナリに保存し、出力してその内容を表示します。

    # 预先训练好的参数, 'https://download.pytorch.org/models/resnet18-5c106cde.pth'
    pretrained_dict = torch.load('./resnet18_pretrain.pth')
    # pretrained_dict = torch.load('./resnet34_pretrain.pth')
    print(pretrained_dict)
  • 事前トレーニング済みのモデルの重みとバイアスの項目が出力されていることがわかります.ここに fc レイヤーのパラメーターをマークしました.それぞれ 1000 個の重みとバイアスがあります.

  • しかし、fc レイヤーの重みとバイアスは必要ありません。なぜなら、出力する必要があるのは 5 だけだからです。したがって、fc レイヤーのパラメーターを破棄する必要があります。 Delete the elements of the pre-trained model with the same layer name as the current model but with different layer structure. これらは、置き換える必要があるパラメーターです

3.4 プレトレーニング パラメータの fc 層パラメータを削除する

pop を使用して、不要なパラメーターをポップアップ表示し、印刷して表示します。 

# # 弹出fc层的参数
    pretrained_dict.pop('fc.weight')
    pretrained_dict.pop('fc.bias')
print(pretrained_dict)

パラメータに fc 層のパラメータがないことがわかります。これは、後で自分で構築したニューラル ネットワーク pretrain_modelに適用するパラメータ です。

3.5 構築したモデルのフレームワークを表示する

独自の pretrain_model は fc 出力カテゴリの変更を完了しましたが、上記の事前トレーニング パラメーターが読み込まれていないため、この時点で独自のニューラル ネットワークのパラメーターは初期状態にあり、多くの0 と 1 があります。印刷して見る。

# 自己的模型参数变量,在开始时里面参数处于初始状态,所以很多0和1
    model_dict = pretrain_model.state_dict()
    print(model_dict)

 この時点で完了した作業は次のとおりです。

  1. Model_dict は pretrain_model の初期化パラメーターです. 彼の最後の全結合層のカテゴリの数を前面の 5 に変更したため、fc 層のパラメーターも 5 です。
  2. pretrained_dict は、ダウンロードした resnet の事前トレーニング パラメーターであり、最後の FC レイヤーの重みを削除しました。

3.6 独自のモデル パラメータを更新する

ここで、事前トレーニング済みモデルで現在のモデルと同じ形状のパラメーターを抽出し、それらを pretrained_dict 辞書に格納する必要があります。このうち、k はパラメーター名を表し、v はパラメーター値を表します。

# # 去除一些不需要的参数,加载预训练参数
    pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}

モデル パラメータが更新され、読み込まれます。 

# 模型参数列表进行参数更新,加载参数
    model_dict.update(pretrained_dict)

    # 改进过的预训练模型结构,加载刚刚的模型参数列表
    pretrain_model.load_state_dict(model_dict)
    print(pretrain_model)

 この操作の完了後、更新されていない最後の fc レイヤーのパラメーターを除いて、他の以前のレイヤーのパラメーターは事前トレーニング済みモデルのパラメーターに置き換えられます。

3.7 fcレイヤー以外のパラメータをフリーズする

最後の fc レイヤーのパラメーターを個別にトレーニングする必要があります。

以前にロードされたが現在は使用されていない他のレイヤーのパラメーターをフリーズする必要があります。

凍結方法:パラメータ名が fc.weights および fc.bias でない場合は、requires_grad を false に設定します. requires_grad は、パラメータを更新するかどうかを意味します. true の場合は、更新します. fc レイヤーを除くすべてのパラメーターをフリーズします。

# 将满足条件的参数的 requires_grad 属性设置为False
    for name, value in pretrain_model.named_parameters():
        if (name != 'fc.weight') and (name != 'fc.bias'):
            value.requires_grad = False

3.8 損失関数とオプティマイザーを定義し、fc 層パラメーターの勾配を更新する 

 フィルター関数は、モデル内で属性が requires_grad = True であるパラメーターを選択し、確率的勾配降下アルゴリズム SDG を使用して fc レイヤーのパラメーターを勾配で更新し、更新が必要なレイヤーのみを更新するようにオプティマイザーを制御します

    # filter 函数将模型中属性 requires_grad = True 的参数选出来
    params_conv = filter(lambda p: p.requires_grad, pretrain_model.parameters())  # 要更新的参数在parms_conv当中
  
# 定义损失函数,计算相差多少,交叉熵,  
loss_fn = nn.CrossEntropyLoss()
# 对参数进行更新
    optimizer = torch.optim.SGD(params_conv, lr=1e-3)  # 初始学习率
    

3.9 モデルをロードして訓練する

epoch=5 の場合、トレーニング結果は次のようになります。

 model = pretrain_model.to(device)

 # 一共训练5次
    epochs = 5
    for t in range(epochs):
        print(f"Epoch {t + 1}\n-------------------------------")
        train(train_dataloader, model, loss_fn, optimizer)
        test(test_dataloader, model)
    print("Done!")

    # 保存训练好的模型
    torch.save(model.state_dict(), "model_resnet34.pth")
    print("Saved PyTorch Model Success!")

epoch=5 の場合の正解率は 82.4% に達し、これは比較的高く、要件を満たしていることがわかります。

3. 完全なコード

'''
    加载pytorch自带的模型,从头训练自己的数据
'''
import time
import torch
from torch import nn
from torch.utils.data import DataLoader
from utils import LoadData

# 设置显卡型号为1
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '1'


from torchvision.models import alexnet  #最简单的模型
from torchvision.models import vgg11, vgg13, vgg16, vgg19   # VGG系列
from torchvision.models import resnet18, resnet34,resnet50, resnet101, resnet152    # ResNet系列
from torchvision.models import inception_v3     # Inception 系列

# 定义训练函数,需要
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    # 从数据加载器中读取batch(一次读取多少张,即批次数),X(图片数据),y(图片真实标签)。
    for batch, (X, y) in enumerate(dataloader):
        # 将数据存到显卡
        X, y = X.cuda(), y.cuda()

        # 得到预测的结果pred
        pred = model(X)

        # 计算预测的误差
        # print(pred,y)
        loss = loss_fn(pred, y)

        # 反向传播,更新模型参数
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 每训练10次,输出一次当前信息
        if batch % 10 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")


def test(dataloader, model):
    size = len(dataloader.dataset)
    # 将模型转为验证模式
    model.eval()
    # 初始化test_loss 和 correct, 用来统计每次的误差
    test_loss, correct = 0, 0
    # 测试时模型参数不用更新,所以no_gard()
    # 非训练, 推理期用到
    with torch.no_grad():
        # 加载数据加载器,得到里面的X(图片数据)和y(真实标签)
        for X, y in dataloader:
            # 将数据转到GPU
            X, y = X.cuda(), y.cuda()
            # 将图片传入到模型当中就,得到预测的值pred
            pred = model(X)
            # 计算预测值pred和真实值y的差距
            test_loss += loss_fn(pred, y).item()
            # 统计预测正确的个数
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= size
    correct /= size
    print(f"correct = {correct}, Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")


if __name__=='__main__':
    batch_size = 8

    # # 给训练集和测试集分别创建一个数据集加载器
    train_data = LoadData("train.txt", True)
    valid_data = LoadData("test.txt", False)


    train_dataloader = DataLoader(dataset=train_data, num_workers=4, pin_memory=True, batch_size=batch_size, shuffle=True)
    test_dataloader = DataLoader(dataset=valid_data, num_workers=4, pin_memory=True, batch_size=batch_size)

    # 如果显卡可用,则用显卡进行训练
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Using {device} device")


    '''        ResNet系列    '''
    pretrain_model = resnet18(weights=False)    # 43.6%
    num_ftrs = pretrain_model.fc.in_features  # 获取全连接层的输入
    pretrain_model.fc = nn.Linear(num_ftrs, 5)  # 全连接层改为不同的输出,5
    # print(pretrain_model)

    # 加载预训练模型
    pretrained_dict = torch.load('./resnet18_pretrain.pth')
    # 对模型进行输出
    # print(pretrained_dict)

    # # 删除预训练模型跟当前模型层名称相同,层结构却不同的元素;,弹出fc层的参数
    pretrained_dict.pop('fc.weight')
    pretrained_dict.pop('fc.bias')
    # print(pretrained_dict)

    # 自己的模型参数变量,在开始时里面参数处于初始状态,所以很多0和1
    model_dict = pretrain_model.state_dict()
    # print(model_dict)

    # 将预训练模型中与当前模型形状相同的参数提取出来,存储在pretrained_dict字典中。其中,k表示参数名,v表示参数值
    pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}

    # 模型参数列表进行参数更新,加载参数
    model_dict.update(pretrained_dict)

    # 改进过的预训练模型结构,加载刚刚的模型参数列表
    pretrain_model.load_state_dict(model_dict)
    # print(pretrain_model)

    '''
           冻结部分层
       '''
    # 将满足条件的参数的 requires_grad 属性设置为False
    for name, value in pretrain_model.named_parameters():
        if (name != 'fc.weight') and (name != 'fc.bias'):
            value.requires_grad = False


    # filter 函数将模型中属性 requires_grad = True 的参数选出来
    params_conv = filter(lambda p: p.requires_grad, pretrain_model.parameters())  # 要更新的参数在parms_conv当中

    # 定义损失函数,计算相差多少,交叉熵,
    loss_fn = nn.CrossEntropyLoss()
    # 对参数进行更新
    optimizer = torch.optim.SGD(params_conv, lr=1e-3)  # 初始学习率

    model = pretrain_model.to(device)

 # 一共训练5次
    epochs = 5
    for t in range(epochs):
        print(f"Epoch {t + 1}\n-------------------------------")
        train(train_dataloader, model, loss_fn, optimizer)
        test(test_dataloader, model)
    print("Done!")

    # 保存训练好的模型
    torch.save(model.state_dict(), "model_resnet34.pth")
    print("Saved PyTorch Model Success!")

おすすめ

転載: blog.csdn.net/weixin_45662399/article/details/130101161