解決pytorch 模型復制的一些問題
直接使用
model2=model1
會出現當更新model2時,model1的權重也會更新,這和自己的初始目的不同。
經評論指出可以使用:
model2=copy.deepcopy(model1)
來實現深拷貝,手上沒有pytorch環境,具體還沒測試過,誰測試過可以和我說下有沒有用。
原方法:
所有要使用模型復制可以使用如下方法。
torch.save(model, "net_params.pkl") model5=Cnn(3,10) model5=torch.load('net_params.pkl')
這樣編寫不會影響原始模型的權重
補充:pytorch模型訓練流程中遇到的一些坑(持續更新)
要訓練一個模型,主要分成幾個部分,如下。
數據預處理
入門的話肯定是拿 MNIST 手寫數據集先練習。
pytorch 中有幫助我們制作數據生成器的模塊,其中有 Dataset、TensorDataset、DataLoader 等類可以來創建數據入口。
之前在 tensorflow 中可以用 dataset.from_generator() 的形式,pytorch 中也類似,目前我瞭解到的有兩種方法可以實現。
第一種就繼承 pytorch 定義的 dataset,改寫其中的方法即可。如下,就獲得瞭一個 DataLoader 生成器。
class MyDataset(Dataset): def __init__(self, data, labels): self.data = data self.labels = labels def __getitem__(self, index): return self.data[index], self.labels[index] def __len__(self): return len(self.labels) train_dataset = MyDataset(train_data, train_label) train_loader = DataLoader(dataset = train_dataset, batch_size = 1, shuffle = True)
第二種就是轉換,先把我們準備好的數據轉化成 pytorch 的變量(或者是 Tensor),然後傳入 TensorDataset,再構造 DataLoader。
X = torch.from_numpy(train_data).float() Y = torch.from_numpy(train_label).float() train_dataset = TensorDataset(X, Y) train_loader = DataLoader(dataset = train_dataset, batch_size = 1, shuffle = True) #num_workers = 2)
模型定義
class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 3) self.conv2 = nn.Conv2d(6 ,16, 3) self.fc1 = nn.Linear(400, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): relu = F.relu(self.conv1(x)) x = F.max_pool2d(relu, (2, 2)) x = F.max_pool2d(F.relu(self.conv2(x)), 2) x = x.view(-1, self.num_flat_features(x)) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x def num_flat_features(self, x): size = x.size()[1:] #除瞭batch_size之外的維度 num_features = 1 for s in size: num_features *= s return num_features
訓練模型那麼肯定要先定義一個網絡結構,如上定義一個前向傳播網絡。裡面包含瞭卷積層、全連接層、最大池化層和 relu 非線性激活層(名字我自己取的)以及一個 view 展開,把一個多維的特征圖平展成一維的。
其中nn.Conv2d(in_channels, out_channels, kernel_size),第一個參數是輸入的深度,第二是輸出的深度,第三是卷積核的尺寸。
F.max_pool2d(input, (pool_size, pool_size)),第二個參數是池話
nn.Linear(in_features, out_features)
x.view是平展的操作,不過實際上相當於 numpy 的 reshape,需要計算轉換後的尺寸。
損失函數定義
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
模型定義完之後,意味著給出輸入,就可以得到輸出的結果。那麼就來比較 outputs 和 targets 之間的區別,那麼就需要用到損失函數來描述。
訓練網絡
for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 2000 == 1999: # print every 2000 mini-batches print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss = 0.0 print('Finished Training')
以上的代碼是官方教程中給出來的,我們要做的就是學習他的思路。
1.首先是 epoch 的數量為 2,每個 epoch 都會歷遍一次整個訓練集。在每個 epoch 內累積統計 running_loss,每 2000 個 batch 數據計算一次損失的平均值,然後 print 再重新將 running_loss 置為 0。
2.然後分 mini-batch 進行訓練,在每個計算每個 mini-batch 的損失之前,都會將優化器 optimizer 中的梯度清空,防止不同 mini-batch 的梯度被累加到一起。更新分成兩步:第一步計算損失函數,然後把總的損失分配到各個層中,即 loss.backward(),然後就使用優化器更新權重,即 optimizer.step()。
保存模型
PATH = '...' torch.save(net.state_dict(), PATH)
爬坑總結
總的來說流程就是上面那幾步,但自己做的時候就遇到瞭挺多問題,最主要是對於其中張量傳播過程中的要求不清楚,導致出瞭不少錯誤。
首先是輸入的數據,pytorch 默認圖片的 batch 數據的結構是(BATCH_SIZE, CHANNELS, IMG_H, IMG_W),所以要在生成數據時做一些調整,滿足這種 BCHW 的規則。
會經常出現一些某個矩陣或者張量要求的數據,例如 “RuntimeError: Expected object of scalar type Double but got scalar type Float for argument #2 ‘mat2’” 等錯誤信息。
可以使用 x.double(),y.float(),z.long() 等方式轉換成他要求的格式。
RuntimeError: multi-target not supported。這個錯誤出現在損失函數那個地方,對於分類問題肯定是優先考慮交叉熵。
criterion = nn.CrossEntropyLoss() loss = criterion(outputs, labels.long())#報錯的地方
當我batch-size=1時這個地方不會報錯,但是當batch-size>1時就會報錯。
查瞭別人的代碼,大傢基本都是和官方教程裡面寫的一樣,使用官方的 mnist 數據接口,代碼如下。一開始我是不願意的,因為那樣子意味著可能數據格式被封裝起來看不見,但是自己折騰成本比較高,所以還是試瞭,真香!
train_dataset = datasets.MNIST(root='./data/', train=True, transform=transforms.ToTensor(), download=True) train_loader = DataLoader(dataset = train_dataset, batch_size = 4, shuffle = True)
打印瞭一下從生成器中獲得數據,看一下 size,發現果然和我自己寫的不同。當 batch_size=4 時,數據 data.size() 都是4*1*28*28,這個是相同的;但是 labels.size() 是不同的,我寫的是 one_hot 向量所以是 4*10,但它的是 4。
直接打印 labels 看看,果然,是單個指,例如 tensor([3, 2, 6, 2]) 這樣。
不過模型的 outputs 依然是 4*10,看來是 nn.CrossEntropyLoss() 這個函數自己會做計算,所以他才會報錯說 multi-target not supported,因為 lables.size() 不對,原本隻有一個數字,但現在是10個數字,相當於被分配瞭10個屬性,自然就報錯啦。
所以稍微修改瞭自己寫的生成器之後,就沒問題瞭。
不過,如果想要更自由的調用數據,還是需要對對象進行一些方法的重載,使用 pytoch 定義的 DataLoader,用 enumerate,就會把所有的數據歷遍一次,如果使用 iter() 得到一個可迭代對象之後 next(),並不可以像 tensorflow 那樣子生成訓練數據。
例如說,如果使用如上的形式,DataLoader 得到的是一個生成器,python 中的生成器對象主要有 __next__ 和 __iter__ 等魔術方法決定。
__iter__ 方法使得實例可以如下調用,可以得到一個可迭代對象,iterable,但是如果不加也沒關系,因為更重要的是 __next__ 類方法。
如下自己寫瞭 __next__ 方法之後就可以看到,原本會出現越界的現象不見瞭,可以循環的歷遍數據,當然也可以想被註釋的那部分一樣,拋出 StopIteration 來終止。
a = A() a_iter = iter(a) class A(): def __init__(self): self.list = [1,2,3] self.index = 0 #def __getitem__(self, index): # return self.list[i] #def __iter__(self): # return self def __next__(self): #for i in range(): if self.index >= len(self.list): #raise StopIteration self.index = self.index%len(self.list) result = self.list[self.index] self.index += 1 return result b = A() for i in range(20): print(next(b))
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。
推薦閱讀:
- pytorch加載自己的圖片數據集的2種方法詳解
- Pytorch相關知識介紹與應用
- Pytorch實現圖像識別之數字識別(附詳細註釋)
- PyTorch 遷移學習實踐(幾分鐘即可訓練好自己的模型)
- 基於PyTorch實現一個簡單的CNN圖像分類器