直接搭建網絡必須與torchvision自帶的網絡的權重也就是pth文件的結構、尺寸和變量命名完全一致,否則無法加載權重文件。
此時可比較2個字典逐一加載,詳見
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import torch import torchvision import cv2 as cv from utils.utils import letter_box from model.backbone import ResNet18 model1 = ResNet18( 1 ) model2 = torchvision.models.resnet18(progress = False ) fc = model2.fc model2.fc = torch.nn.Linear( 512 , 1 ) # print(model) model_dict1 = model1.state_dict() model_dict2 = torch.load( 'resnet18.pth' ) model_list1 = list (model_dict1.keys()) model_list2 = list (model_dict2.keys()) len1 = len (model_list1) len2 = len (model_list2) minlen = min (len1, len2) for n in range (minlen): if model_dict1[model_list1[n]].shape ! = model_dict2[model_list2[n]].shape: continue model_dict1[model_list1[n]] = model_dict2[model_list2[n]] model1.load_state_dict(model_dict1) missing, unspected = model2.load_state_dict(model_dict2) image = cv.imread( 'zhn1.jpg' ) image = letter_box(image, 224 ) image = image[:, :, :: - 1 ].transpose( 2 , 0 , 1 ) print ( 'Network loading complete.' ) model1. eval () model2. eval () with torch.no_grad(): image = torch.tensor(image / 256 , dtype = torch.float32).unsqueeze( 0 ) predict1 = model1(image) predict2 = model2(image) print ( 'finished' ) # torch.save(model.state_dict(), 'resnet18.pth') |
以上為全部程序,最終可測試原模型與加載了自帶權重的自定義模型的輸出是否相等。
補充:使用Pytorch搭建ResNet分類網絡并基于遷移學習訓練
如果stride=1,padding=1
卷積處理是不會改變特征矩陣的高和寬
使用BN層時
卷積中的參數bias置為False(有無偏置BN層的輸出都相同),BN層放在conv層和relu層的中間
復習BN層:
Batch Norm 層是對每層數據歸一化后再進行線性變換改善數據分布, 其中的線性變換是可學習的.
Batch Norm優點:減輕過擬合;改善梯度傳播(權重不會過高或過低)容許較高的學習率,能夠提高訓練速度。減輕對初始化權重的強依賴,使得數據分布在激活函數的非飽和區域,一定程度上解決梯度消失問題。作為一種正則化的方式,在某種程度上減少對dropout的使用。
Batch Norm層擺放位置:在激活層(如 ReLU )之前還是之后,沒有一個統一的定論。
BN層與 Dropout 合作:Batch Norm的提出使得dropout的使用減少,但是Batch Norm不能完全取代dropout,保留較小的dropout率,如0.2可能效果更佳。
為什么要先normalize再通過γ,β線性變換恢復接近原來的樣子,這不是多此一舉嗎?
在一定條件下可以糾正原始數據的分布(方差,均值變為新值γ,β),當原始數據分布足夠好時就是恒等映射,不改變分布。如果不做BN,方差和均值對前面網絡的參數有復雜的關聯依賴,具有復雜的非線性。在新參數 γH′ + β 中僅由 γ,β 確定,與前邊網絡的參數無關,因此新參數很容易通過梯度下降來學習,能夠學習到較好的分布。
遷移學習導入權重和下載權重:
1
2
3
4
5
6
7
|
import torchvision.models.resnet #ctrl+鼠標左鍵點擊即可下載權重 net = resnet34() #一開始不能設置全連接層的輸出種類為自己想要的,必須先將模型參數載入,再修改全連接層 # 官方提供載入預訓練模型的方法 model_weight_path = "./resnet34-pre.pth" #權重路徑 missing_keys, unexpected_keys = net.load_state_dict(torch.load(model_weight_path), strict = False ) #載入模型權重 inchannel = net.fc.in_features net.fc = nn.Linear(inchannel, 5 ) #重新確定全連接層 |
完整代碼:
model部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
import torch.nn as nn import torch class BasicBlock(nn.Module): #對應18層和34層所對應的殘差結構(既要有實線殘差結構功能,也要有虛線殘差結構功能) expansion = 1 #殘差結構主分支上的三個卷積層是否相同,相同為1,第三層是一二層四倍則為4 def __init__( self , in_channel, out_channel, stride = 1 , downsample = None ): #downsample代表虛線殘差結構選項 super (BasicBlock, self ).__init__() self .conv1 = nn.Conv2d(in_channels = in_channel, out_channels = out_channel, kernel_size = 3 , stride = stride, padding = 1 , bias = False ) self .bn1 = nn.BatchNorm2d(out_channel) self .relu = nn.ReLU() self .conv2 = nn.Conv2d(in_channels = out_channel, out_channels = out_channel, kernel_size = 3 , stride = 1 , padding = 1 , bias = False ) self .bn2 = nn.BatchNorm2d(out_channel) self .downsample = downsample def forward( self , x): identity = x if self .downsample is not None : identity = self .downsample(x) #得到捷徑分支的輸出 out = self .conv1(x) out = self .bn1(out) out = self .relu(out) out = self .conv2(out) out = self .bn2(out) out + = identity out = self .relu(out) return out #得到殘差結構的最終輸出 class Bottleneck(nn.Module): #對應50層、101層和152層所對應的殘差結構 expansion = 4 #第三層卷積核個數是第一層和第二層的四倍 def __init__( self , in_channel, out_channel, stride = 1 , downsample = None ): super (Bottleneck, self ).__init__() self .conv1 = nn.Conv2d(in_channels = in_channel, out_channels = out_channel, kernel_size = 1 , stride = 1 , bias = False ) self .bn1 = nn.BatchNorm2d(out_channel) self .conv2 = nn.Conv2d(in_channels = out_channel, out_channels = out_channel, kernel_size = 3 , stride = stride, bias = False , padding = 1 ) self .bn2 = nn.BatchNorm2d(out_channel) self .conv3 = nn.Conv2d(in_channels = out_channel, out_channels = out_channel * self .expansion, kernel_size = 1 , stride = 1 , bias = False ) self .bn3 = nn.BatchNorm2d(out_channel * self .expansion) self .relu = nn.ReLU(inplace = True ) self .downsample = downsample def forward( self , x): identity = x if self .downsample is not None : identity = self .downsample(x) out = self .conv1(x) out = self .bn1(out) out = self .relu(out) out = self .conv2(out) out = self .bn2(out) out = self .relu(out) out = self .conv3(out) out = self .bn3(out) out + = identity out = self .relu(out) return out class ResNet(nn.Module): #定義整個網絡的框架部分 #blocks_num是殘差結構的數目,是一個列表參數,block對應哪個殘差模塊 def __init__( self , block, blocks_num, num_classes = 1000 , include_top = True ): super (ResNet, self ).__init__() self .include_top = include_top self .in_channel = 64 #通過第一個池化層后所得到的特征矩陣的深度 self .conv1 = nn.Conv2d( 3 , self .in_channel, kernel_size = 7 , stride = 2 , padding = 3 , bias = False ) self .bn1 = nn.BatchNorm2d( self .in_channel) self .relu = nn.ReLU(inplace = True ) self .maxpool = nn.MaxPool2d(kernel_size = 3 , stride = 2 , padding = 1 ) self .layer1 = self ._make_layer(block, 64 , blocks_num[ 0 ]) self .layer2 = self ._make_layer(block, 128 , blocks_num[ 1 ], stride = 2 ) self .layer3 = self ._make_layer(block, 256 , blocks_num[ 2 ], stride = 2 ) self .layer4 = self ._make_layer(block, 512 , blocks_num[ 3 ], stride = 2 ) if self .include_top: self .avgpool = nn.AdaptiveAvgPool2d(( 1 , 1 )) # output size = (1, 1) self .fc = nn.Linear( 512 * block.expansion, num_classes) for m in self .modules(): if isinstance (m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode = 'fan_out' , nonlinearity = 'relu' ) def _make_layer( self , block, channel, block_num, stride = 1 ): #channel:殘差結構中,第一個卷積層所使用的卷積核的個數 downsample = None if stride ! = 1 or self .in_channel ! = channel * block.expansion: #18層和34層會直接跳過這個if語句 downsample = nn.Sequential( nn.Conv2d( self .in_channel, channel * block.expansion, kernel_size = 1 , stride = stride, bias = False ), nn.BatchNorm2d(channel * block.expansion)) layers = [] layers.append(block( self .in_channel, channel, downsample = downsample, stride = stride)) self .in_channel = channel * block.expansion for _ in range ( 1 , block_num): layers.append(block( self .in_channel, channel)) return nn.Sequential( * layers) def forward( self , x): x = self .conv1(x) x = self .bn1(x) x = self .relu(x) x = self .maxpool(x) x = self .layer1(x) x = self .layer2(x) x = self .layer3(x) x = self .layer4(x) if self .include_top: #默認是true x = self .avgpool(x) x = torch.flatten(x, 1 ) x = self .fc(x) return x def resnet34(num_classes = 1000 , include_top = True ): return ResNet(BasicBlock, [ 3 , 4 , 6 , 3 ], num_classes = num_classes, include_top = include_top) def resnet101(num_classes = 1000 , include_top = True ): return ResNet(Bottleneck, [ 3 , 4 , 23 , 3 ], num_classes = num_classes, include_top = include_top) |
訓練部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
import torch import torch.nn as nn from torchvision import transforms, datasets import json import matplotlib.pyplot as plt import os import torch.optim as optim from model import resnet34, resnet101 import torchvision.models.resnet #ctrl+鼠標左鍵點擊即可下載權重 device = torch.device( "cuda:0" if torch.cuda.is_available() else "cpu" ) print (device) data_transform = { "train" : transforms.Compose([transforms.RandomResizedCrop( 224 ), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([ 0.485 , 0.456 , 0.406 ], [ 0.229 , 0.224 , 0.225 ])]), #和官網初始化方法保持一致 "val" : transforms.Compose([transforms.Resize( 256 ), transforms.CenterCrop( 224 ), transforms.ToTensor(), transforms.Normalize([ 0.485 , 0.456 , 0.406 ], [ 0.229 , 0.224 , 0.225 ])])} data_root = os.path.abspath(os.path.join(os.getcwd(), "../.." )) # get data root path image_path = data_root + "/data_set/flower_data/" # flower data set path train_dataset = datasets.ImageFolder(root = image_path + "train" , transform = data_transform[ "train" ]) train_num = len (train_dataset) # {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4} flower_list = train_dataset.class_to_idx cla_dict = dict ((val, key) for key, val in flower_list.items()) # write dict into json file json_str = json.dumps(cla_dict, indent = 4 ) with open ( 'class_indices.json' , 'w' ) as json_file: json_file.write(json_str) batch_size = 16 train_loader = torch.utils.data.DataLoader(train_dataset, batch_size = batch_size, shuffle = True , num_workers = 0 ) validate_dataset = datasets.ImageFolder(root = image_path + "val" , transform = data_transform[ "val" ]) val_num = len (validate_dataset) validate_loader = torch.utils.data.DataLoader(validate_dataset, batch_size = batch_size, shuffle = False , num_workers = 0 ) net = resnet34() #一開始不能設置全連接層的輸出種類為自己想要的,必須先將模型參數載入,再修改全連接層 # 官方提供載入預訓練模型的方法 model_weight_path = "./resnet34-pre.pth" #權重路徑 missing_keys, unexpected_keys = net.load_state_dict(torch.load(model_weight_path), strict = False ) #載入模型權重 inchannel = net.fc.in_features net.fc = nn.Linear(inchannel, 5 ) #重新確定全連接層 net.to(device) loss_function = nn.CrossEntropyLoss() optimizer = optim.Adam(net.parameters(), lr = 0.0001 ) best_acc = 0.0 save_path = './resNet34.pth' for epoch in range ( 3 ): # train net.train() #控制BN層狀態 running_loss = 0.0 for step, data in enumerate (train_loader, start = 0 ): images, labels = data optimizer.zero_grad() logits = net(images.to(device)) loss = loss_function(logits, labels.to(device)) loss.backward() optimizer.step() # print statistics running_loss + = loss.item() # print train process rate = (step + 1 ) / len (train_loader) a = "*" * int (rate * 50 ) b = "." * int (( 1 - rate) * 50 ) print ( "\rtrain loss: {:^3.0f}%[{}->{}]{:.4f}" . format ( int (rate * 100 ), a, b, loss), end = "") print () # validate net. eval () #控制BN層狀態 acc = 0.0 # accumulate accurate number / epoch with torch.no_grad(): for val_data in validate_loader: val_images, val_labels = val_data outputs = net(val_images.to(device)) # eval model only have last output layer # loss = loss_function(outputs, test_labels) predict_y = torch. max (outputs, dim = 1 )[ 1 ] acc + = (predict_y = = val_labels.to(device)). sum ().item() val_accurate = acc / val_num if val_accurate > best_acc: best_acc = val_accurate torch.save(net.state_dict(), save_path) print ( '[epoch %d] train_loss: %.3f test_accuracy: %.3f' % (epoch + 1 , running_loss / step, val_accurate)) print ( 'Finished Training' ) |
預測部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
import torch from model import resnet34 from PIL import Image from torchvision import transforms import matplotlib.pyplot as plt import json device = torch.device( "cuda:0" if torch.cuda.is_available() else "cpu" ) data_transform = transforms.Compose( [transforms.Resize( 256 ), transforms.CenterCrop( 224 ), transforms.ToTensor(), transforms.Normalize([ 0.485 , 0.456 , 0.406 ], [ 0.229 , 0.224 , 0.225 ])]) #采用和訓練方法一樣的標準化處理 # load image img = Image. open ( "../aa.jpg" ) plt.imshow(img) # [N, C, H, W] img = data_transform(img) # expand batch dimension img = torch.unsqueeze(img, dim = 0 ) # read class_indict try : json_file = open ( './class_indices.json' , 'r' ) class_indict = json.load(json_file) except Exception as e: print (e) exit( - 1 ) # create model model = resnet34(num_classes = 5 ) # load model weights model_weight_path = "./resNet34.pth" model.load_state_dict(torch.load(model_weight_path, map_location = device)) #載入訓練好的模型參數 model. eval () #使用eval()模式 with torch.no_grad(): #不跟蹤損失梯度 # predict class output = torch.squeeze(model(img)) #壓縮batch維度 predict = torch.softmax(output, dim = 0 ) #通過softmax得到概率分布 predict_cla = torch.argmax(predict).numpy() #尋找最大值所對應的索引 print (class_indict[ str (predict_cla)], predict[predict_cla].numpy()) #打印類別信息和概率 plt.show() |
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/qq_34288751/article/details/114163057