目前一些模型API還沒有遷移到TF20中。 eg: CRF,Seq2Seq等
若是退回TF10,有些傷。
倒不如轉至Torch。
Pytorch的大部分思想和TF20大體類似。python
至於安裝,GPU我前面說過TF20。這裏不贅述。
官檔安裝:https://pytorch.org/get-started/locally/#start-locally網絡
本文幾乎通篇以代碼案例 和 註釋標註 的方式解釋API。(模型的訓練效果不作考慮。只看語法)
你若是懂Tensorflow2.0(Stable),那麼你看本文必定不費勁。
Torch和TF20 很像!!!
所以一些地方,我會列出 TF20 與 Torch的細節對比。app
import torch from torch import nn, optim from torchvision import datasets, transforms from torch.utils.data import DataLoader
data_preprocess = transforms.Compose([ # 頂預約數據處理函數,相似map()裏的函數句柄 transforms.Resize(28,28), # 變形 transforms.ToTensor(), # numpy 轉 Tensor ]) trian_dataset = datasets.MNIST( # TF20在keras.datasets中,未歸一化(0-255) '.', # 下載至當前目錄, (圖片0-1,已經被歸一化了) train=True, # train=True, 表明直接給你切出 訓練集 download=True, # True,若未下載,則先下載 transform=data_preprocess, # 指定數據預處理函數。第一行咱們指定的 ) test_dataset = datasets.MNIST( '.', train=False, # False表明測試集 # 就說下這裏, False表明 給你切出測試集 download=True, transform=data_preprocess, ) train = DataLoader( # 對應TF20中的 tf.data.Dataset對數據二次預處理(分批,亂序) trian_dataset, # 把上面第一次預處理的數據集 加載進來 batch_size=16, # mini-batch shuffle=True, # 亂序,加強模型泛化能力 ) test = DataLoader( test_dataset, batch_size=16, shuffle=True, )
# 模型定義部分 class MyModel(nn.Module): # TF20是 tk.models.Model def __init__(self): # TF20 也是 __init__() super().__init__() self.model = nn.Sequential( # tk.models.Sequential , 而且 TF裏面 須要加一個 [] nn.Linear(28*28, 256), # tk.layers.Dense(256) nn.ReLU(), # tk.layers.Relu() nn.Linear(256, 128), # tk.layers.Dense(128) nn.ReLU(), nn.Linear(128, 10), # tk.layers.Dense(10) ) def forward(self, x): # TF20是 __call__() x = x.view( x.size(0), 28*28 ) # x.view ==> tf.reshape x.size ==> x.shape[0] y_predict = self.model(x) return y_predict # -------------------------------華麗分割線--------------------------------- # 模型訓練部分 def main(): vis = visdom.Visdom() model = MyModel() loss_ = nn.CrossEntropyLoss() # 會將 y_predict自動加一層 softmax optimizer = optim.Adam(model.parameters()) # TF20: model.trainable_variables # visdom可視化 # 這步是初始化座標點,下面loss會用這個直接更新 vis.line( [0], # x座標 [0], # y座標 win='loss', # 窗口名稱 opts={'title': 'loss'}, # 窗口標題 ) for epoch in range(10): # epochs for step, (x, y_true) in enumerate(train): y_predict = model(x) loss = loss_(y_predict, y_true) optimizer.zero_grad() # 優化器清零 loss.backward() # 梯度計算 optimizer.step() # 梯度降低更新 tp.gradient(loss, variables)。 # 在上面的定義的基礎上更新追加畫點-連成線 vis.line( [loss.item()], [step], win='loss', update='append', # 追加畫點,而不是更新覆蓋 ) print(loss.item()) # .item() => 至關於 tensorflow 的 numpy() if epoch % 2 == 0: total_correct_samples = 0 # 用於記錄(預測正確的樣本的 總數量) total_samples = 0 # 用於記錄(樣本的 總數量) for x_test, y_test in test: y_pred = model(x_test) y_final_pred = y_pred.argmax(dim=1) # TF20的座標軸參數是 axis # 每一批是 batch_size=16,咱們要把它們都加在一塊兒 total_correct_samples += torch.eq(y_final_pred, y_test).float().sum().item() # 這裏提一下 eq() 和 equal() 的返回值的區別, 本身看,咱們一般用 eq # print( torch.equal( torch.Tensor([[1,2,3]]), torch.Tensor([[4,5,6]] ) ) ) #結果: False # print( torch.eq( torch.Tensor([[1,2,3]]), torch.Tensor([[4,5,6]] ) ) ) #結果: tensor([[0, 0, 0]], dtype=torch.uint8) per_sample = x_test.size(0) # 再說一次, size(0) 至關於TF xx.shape[0] # 獲取每批次樣本數量, 雖然咱們知道是 16 # 可是最後一個batch_size 可能不是16,因此要準確獲取。 total_samples += per_sample acc = total_correct_samples / total_samples print(f'epoch: {epoch}, loss: {loss}, acc: {acc}') # 測試部分 vis.line( [acc], [step], win='acc', update='append', # 追加畫點,而不是更新覆蓋 ) x, label = iter(test).next() target_predict = model(x).argmax(dim=1) # 畫出測試集圖片 viz.images(x, nrow=16, win="test_x", opts={'title': "test_x"}) vis.text( # 顯示預測標籤文本 str(target_predict.detach().numpy() ), win = 'target_predict', opts = {"title": target_predict} ) vis.text( # 顯示真值文本 str(label.detach().numpy() ), win = 'target_true', opts = {"target_true": target_predict} ) main()
安裝 pip install visdom 運行 python -m visdom.server (第一次可能會有點慢) # 語法和Tensorboard很像 使用 import visdom 見上代碼 vis.xxxxx
模塊導入和數據預處理部分和案例1的 MNIST如出一轍。
只要稍稍修改 datasets.MNIST ==> datasets.CIFAR10 便可, 簡單的不忍直視~~dom
模型定義部分:ide
class MyModel(nn.Module): # 舒適提示, 這是 Mmodule, 不是model def __init__(self): """ 先註明一下: TF中輸入圖片形狀爲 (樣本數, 高,寬,圖片通道) PyTorch中輸入圖片形狀爲 (樣本數, 圖片通道,高,寬) """ super().__init__() self.conv = nn.Sequential( # 再強調一遍,沒有 [] nn.Conv2d( in_channels=3, # 對應TF 圖片通道數(或者上一層通道) out_channels=8, # 對應TF filters, 卷積核數量 kernel_size=3, # 卷積核大小 stride=1, # 步長, TF 是 strides, 特別注意 padding=0, # no padding, 默認 ), nn.ReLU(), nn.MaxPool2d( kernel_size=3, # 滑動窗口大小 stride=None, # 默認爲None, 意爲和 kernel_size相同大小 ), nn.Conv2d( in_channels=8, # 對應TF 圖片通道數(或者上一層通道) out_channels=16, # 對應TF filters, 卷積核數量 kernel_size=3, # 卷積核大小 stride=1, # 步長, TF 是 strides, 特別注意 padding=0, # no padding, 默認 ), nn.ReLU(), nn.MaxPool2d( kernel_size=2, # 滑動窗口大小 stride=None, # 默認爲None, 意爲和 kernel_size相同大小 ), ) self.dense = nn.Sequential( nn.Linear(16*4*4, 128), # 對應TF Dense nn.Linear(128, 64), nn.Linear(64, 10), ) def forward(self, x): conv_output = self.conv(x) # (16, 16, 4.4) conv_output_reshape = conv_output.view(-1, 16*4*4) dense_output = self.dense(conv_output_reshape) return dense_output
模型訓練(模型調用+模型訓練的定義)函數
def main(): vis = visdom.Visdom() epochs = 100 device = torch.device('cuda') # 預約義 GPU 槽位(一會往裏面塞 模型和數據。) model = MyModel().to(device) # 模型轉爲 GPU 計算 # CrossEntropyLoss 會自動把下面的 dense_output ,也就是y_predict 加一層 softmax loss_ = nn.CrossEntropyLoss().to(device) optimizer = optim.Adam( model.parameters() ) for epoch in range(epochs): for step, (x_train, y_train) in enumerate(train): x_train, y_train = x_train.to(device), y_train.to(device) dense_output = model(x_train) loss = loss_(dense_output, y_train) optimizer.zero_grad() # 上一個例子提到過,梯度清零 loss.backward() # 反向傳播, 並將梯度累加到 optimizer中 optimizer.step() # 至關於作了 w = w - lr * 梯度 print(loss.item()) # item() 意思就是 tensor轉numpy,TF中的 API是 xx.numpy() sample_correct_numbers = 0 sample_total_numbers = 0 with torch.no_grad(): # 測試部分不須要計算梯度,所以能夠包裹在上下文中。 for x_test, y_test in test: x_test, y_test = x_test.to(device), y_test.to(device) # softmax 的 y_predict 與 y_test的 one-hot作交叉熵 y_predict = model(x_test).argmax(dim=1) sample_correct_numbers += torch.eq(y_predict, y_test).float().sum().item() sample_total_numbers += x_test.size(0) # 每批樣本的總數加在一塊兒 acc = sample_correct_numbers / sample_total_numbers print(acc) main()
上述結構說明:測試
1conv + (2+2+2+2)*2 + 1 fc = 18層 1conv + (3+4+6+3)*2 + 1 fc = 34層 1conv + (3+4+6+3)*3 + 1 fc = 50層 1conv + (3+4+23+3)*3 + 1 fc = 101層 1conv + (3+8+36+3)*3 + 1 fc = 152層
模塊導入優化
import cv2 import torch from torch import nn, optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import visdom import torch.nn.functional as F
數據導入預處理ui
data_preprocess = transforms.Compose([ transforms.Resize(32,32), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) train_dataset = datasets.CIFAR10( '.', train=True, download=True, transform=data_preprocess, ) test_dataset = datasets.CIFAR10( '.', train=False, # False表明測試集 download=True, transform=data_preprocess, ) train = DataLoader( train_dataset, batch_size=16, shuffle=True, ) test = DataLoader( test_dataset, batch_size=16, shuffle=True, )
基礎塊定義(BasicBlock):spa
class BasicBlock(nn.Module): """單個殘差塊 2個卷積+2個BN""" def __init__(self, input_channel, output_channel, stride=1): super().__init__() self.major = nn.Sequential( # 第一個Conv的步長爲指定步長,容許降採樣,容許輸出輸出通道不一致 nn.Conv2d(input_channel,output_channel,kernel_size=3,stride=stride, padding=1), nn.BatchNorm2d(output_channel), nn.ReLU(inplace=True), # 第二個Conv的步長爲定長1, 輸入輸出通道不變(緩衝輸出) nn.Conv2d(output_channel, output_channel, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(output_channel), # 第二個Conv就不用ReLU了, 由於一會須要和 x加在一塊兒,最後最一層大的Relu ) # 若輸入通道==輸出通道,且步長爲1,意味着圖片未被降採樣,則殘差網絡課直接爲普通網絡 self.shortcut = nn.Sequential() # 若輸入輸出通道不匹配,這時須要將圖片作一樣的變換,才能加在一塊兒。 if input_channel != output_channel or stride != 1: self.shortcut = nn.Sequential( nn.Conv2d( input_channel, output_channel, kernel_size=(1,1), stride = stride ), nn.BatchNorm2d(output_channel) ) def forward(self, x): major_out = self.major(x) # 主幹網絡的輸出 shotcut_out = self.shortcut(x) # 殘差網絡的輸出 # 上面這兩個網絡是平行的關係, 由於 它們的輸出不是鏈式的, 而是 都是一樣的 x。 # 拼接主幹網絡+殘差網絡,F 至關於TF20的 tf.nn 裏面單獨有各類 loss函數 return F.relu(major_out + shotcut_out) # 最後在拼接後的網絡外面加一層relu
ResNet+ResBlock定義:
class ResNet(nn.Module): def __init__(self, layers): # layers用來接受,用戶想要指定 ResNet的形狀 super().__init__() self.conv1 = nn.Sequential( nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(32), nn.ReLU(inplace=True), ) self.res_net = nn.Sequential( *self.ResBlock(32,64, layers[0],stride=2), # 16 *self.ResBlock(64,128, layers[1],stride=2), # 8 *self.ResBlock(128,256, layers[2],stride=2), # 4 *self.ResBlock(256,512, layers[3],stride=2), # 2 ) # 由於咱們一會須要展平,裏面填"通道*寬度*高度", "輸出通道" self.dense = nn.Linear(512 * 2 * 2, 10) def forward(self, x): out = self.conv1(x) out = self.res_net(out) out = out.view(x.size(0), -1)# 卷積展平操做 , torch中沒有flatten因此咱們就得手工 out = self.dense(out) return out def ResBlock(self, input_channel, output_channel, block_nums=2, stride=2): # 自定義規定,第一個block縮小的(對應通道翻倍),其他block大小不變 # 通道翻倍,步長*2,特徵減半 all_block = [BasicBlock(input_channel, output_channel,stride=stride)] for x in range(1,block_nums): all_block.append(BasicBlock(output_channel, output_channel,stride=1)) return all_block # resnet = ResNet(layers=[2,2,2,2]) # out = resnet(torch.randn(4,3,32,32)) # print(out.shape)
模型訓練:
def main(): vis = visdom.Visdom() epochs = 5 device = torch.device('cuda') model = ResNet(layers=[2,2,2,2]).to(device) # 會自動把下面的 dense_output ,也就是y_predict 加一層 softmax,y_true作one-hot loss_ = nn.CrossEntropyLoss().to(device) optimizer = optim.Adam( model.parameters(), lr=0.0001) for epoch in range(epochs): total_loss = 0.0 for step, (x_train, y_train) in enumerate(train): x_train, y_train = x_train.to(device), y_train.to(device) dense_output = model(x_train) loss = loss_(dense_output, y_train) optimizer.zero_grad() # 上一個例子提到過,梯度清零 loss.backward() # 反向傳播, 並將梯度累加到 optimizer中 optimizer.step() # 至關於作了 w = w - lr * 梯度 total_loss += loss.item() # item()就是 tensor轉numpy, TF中的 API是 xx.numpy() if step % 50 == 49: print('epoch:',epoch, 'loss:', total_loss / step) sample_correct_numbers = 0 sample_total_numbers = 0 with torch.no_grad(): # 測試部分不須要計算梯度,所以能夠包裹在上下文中。 for x_test, y_test in test: x_test, y_test = x_test.to(device), y_test.to(device) # softmax 的 y_predict 與 y_test的 one-hot作交叉熵 y_predict = model(x_test).argmax(dim=1) sample_correct_numbers += torch.eq(y_predict, y_test).float().sum().item() sample_total_numbers += x_test.size(0) # 每批樣本的總數加在一塊兒 acc = sample_correct_numbers / sample_total_numbers print(acc) torch.save(model, 'model.pkl') # 保存整個模型 main()
測試數據預處理(我隨便在網上下載下來的 1 張圖片):
# 這是Cifar-10數據的標準標籤 label = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck'] plane = cv2.imread('plane.jpg') # 我用的opencv plane = cv2.cvtColor(plane, cv2.COLOR_BGR2RGB) # opencv讀的數據格式是BGR,因此轉爲RGB plane = (plane - 127.5) / 127.5 # 二話不說,保持模型輸入數據的機率分佈,先作歸一化 plane = cv2.resize(plane, (32,32)) # 圖片縮小到32x32,和模型的輸入保持一致 plane = torch.Tensor(plane) # 轉換成 tensor plane = plane.view(1,32,32,3) # 增長一個維度 plane = plane.repeat(16,1,1,1) # 我就用一張圖片,爲了知足模型的形狀16,我複製了16次 plane = plane.permute([0,3,1,2]) # 雖然torch也有 像TF那樣的transpose,可是隻能操做2D device = torch.device('cuda') # 先定義一個cuda設備對象 plane = plane.to(device) # 咱們訓練集用的cuda, 因此預測數據也要轉爲cuda
正式輸入模型預測:
model = torch.load('model.pkl') # 讀取出 咱們訓練到最後整個模型 # 說明一下,若是你的預測是另外一個腳本中,class ResNet 的代碼定義部分也要複製過來 out = model(plane) # 預測結果,形狀爲[16,10] 16個樣本,10個預測機率, label_indexes = out.argmax(dim=1) # 取10個機率最大值的索引。 (1軸),形狀爲 [16,1] print(label_indexes) for i in label_indexes: # i爲每一個樣本預測的最大機率值 的 索引位置。 print(label[i]) # 拿着預測標籤的索引 去 真實標籤中找到真實標籤