動手學深度學習(一) 線性回歸
- 2020 年 2 月 24 日
- 筆記
線性回歸
主要內容包括:
- 線性回歸的基本要素
- 線性回歸模型從零開始的實現
- 線性回歸模型使用pytorch的簡潔實現
線性回歸的基本要素
模型
為了簡單起見,這裡我們假設價格只取決於房屋狀況的兩個因素,即面積(平方米)和房齡(年)。接下來我們希望探索價格與這兩個因素的具體關係。線性回歸假設輸出與各個輸入之間是線性關係:
數據集
我們通常收集一系列的真實數據,例如多棟房屋的真實售出價格和它們對應的面積和房齡。我們希望在這個數據上面尋找模型參數來使模型的預測價格與真實價格的誤差最小。在機器學習術語里,該數據集被稱為訓練數據集(training data set)或訓練集(training set),一棟房屋被稱為一個樣本(sample),其真實售出價格叫作標籤(label),用來預測標籤的兩個因素叫作特徵(feature)。特徵用來表徵樣本的特點。
損失函數
在模型訓練中,我們需要衡量價格預測值與真實值之間的誤差。通常我們會選取一個非負數作為誤差,且數值越小表示誤差越小。一個常用的選擇是平方函數。 它在評估索引為
的樣本誤差的表達式為
優化函數 - 隨機梯度下降
當模型和損失函數形式較為簡單時,上面的誤差最小化問題的解可以直接用公式表達出來。這類解叫作解析解(analytical solution)。本節使用的線性回歸和平方誤差剛好屬於這個範疇。然而,大多數深度學習模型並沒有解析解,只能通過優化演算法有限次迭代模型參數來儘可能降低損失函數的值。這類解叫作數值解(numerical solution)。
在求數值解的優化演算法中,小批量隨機梯度下降(mini-batch stochastic gradient descent)在深度學習中被廣泛使用。它的演算法很簡單:先選取一組模型參數的初始值,如隨機選取;接下來對參數進行多次迭代,使每次迭代都可能降低損失函數的值。在每次迭代中,先隨機均勻取樣一個由固定數目訓練數據樣本所組成的小批量(mini-batch)
,然後求小批量中數據樣本的平均損失有關模型參數的導數(梯度),最後用此結果與預先設定的一個正數的乘積作為模型參數在本次迭代的減小量。
學習率:
代表在每次優化中,能夠學習的步長的大小 批量大小:
是小批量計算中的批量大小batch size
總結一下,優化函數的有以下兩個步驟:
- (i)初始化模型參數,一般來說使用隨機初始化;
- (ii)我們在數據上迭代多次,通過在負梯度方向移動參數來更新每個參數。
矢量計算
在模型訓練或預測時,我們常常會同時處理多個數據樣本並用到矢量計算。在介紹線性回歸的矢量計算表達式之前,讓我們先考慮對兩個向量相加的兩種方法。
- 向量相加的一種方法是,將這兩個向量按元素逐一做標量加法。
- 向量相加的另一種方法是,將這兩個向量直接做矢量加法。
import torch import time # init variable a, b as 1000 dimension vector n = 1000 a = torch.ones(n) b = torch.ones(n)
# define a timer class to record time class Timer(object): """Record multiple running times.""" def __init__(self): self.times = [] self.start() def start(self): # start the timer self.start_time = time.time() def stop(self): # stop the timer and record time into a list self.times.append(time.time() - self.start_time) return self.times[-1] def avg(self): # calculate the average and return return sum(self.times)/len(self.times) def sum(self): # return the sum of recorded time return sum(self.times)
現在我們可以來測試了。首先將兩個向量使用for循環按元素逐一做標量加法。
timer = Timer() c = torch.zeros(n) for i in range(n): c[i] = a[i] + b[i] '%.5f sec' % timer.stop()
另外是使用torch來將兩個向量直接做矢量加法:
timer.start() d = a + b '%.5f sec' % timer.stop()
結果很明顯,後者比前者運算速度更快。因此,我們應該儘可能採用矢量計算,以提升計算效率。
線性回歸模型從零開始的實現
# import packages and modules %matplotlib inline import torch from IPython import display from matplotlib import pyplot as plt import numpy as np import random print(torch.__version__)
生成數據集
使用線性模型來生成數據集,生成一個1000個樣本的數據集,下面是用來生成數據的線性關係:
# set input feature number num_inputs = 2 # set example number num_examples = 1000 # set true weight and bias in order to generate corresponded label true_w = [2, -3.4] true_b = 4.2 features = torch.randn(num_examples, num_inputs, dtype=torch.float32) labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float32)
使用影像來展示生成的數據
plt.scatter(features[:, 1].numpy(), labels.numpy(), 1);
讀取數據集
def data_iter(batch_size, features, labels): num_examples = len(features) indices = list(range(num_examples)) random.shuffle(indices) # random read 10 samples for i in range(0, num_examples, batch_size): j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # the last time may be not enough for a whole batch yield features.index_select(0, j), labels.index_select(0, j)
batch_size = 10 for X, y in data_iter(batch_size, features, labels): print(X, 'n', y) break
初始化模型參數
w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float32) b = torch.zeros(1, dtype=torch.float32) w.requires_grad_(requires_grad=True) b.requires_grad_(requires_grad=True)
定義模型
定義用來訓練參數的訓練模型:
def linreg(X, w, b): return torch.mm(X, w) + b
定義損失函數
我們使用的是均方誤差損失函數:
def squared_loss(y_hat, y): return (y_hat - y.view(y_hat.size())) ** 2 / 2
定義優化函數
在這裡優化函數使用的是小批量隨機梯度下降:
def sgd(params, lr, batch_size): for param in params: param.data -= lr * param.grad / batch_size # ues .data to operate param without gradient track
訓練
當數據集、模型、損失函數和優化函數定義完了之後就可來準備進行模型的訓練了。
# super parameters init lr = 0.03 num_epochs = 5 net = linreg loss = squared_loss # training for epoch in range(num_epochs): # training repeats num_epochs times # in each epoch, all the samples in dataset will be used once # X is the feature and y is the label of a batch sample for X, y in data_iter(batch_size, features, labels): l = loss(net(X, w, b), y).sum() # calculate the gradient of batch sample loss l.backward() # using small batch random gradient descent to iter model parameters sgd([w, b], lr, batch_size) # reset parameter gradient w.grad.data.zero_() b.grad.data.zero_() train_l = loss(net(features, w, b), labels) print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))w, true_w, b, true_b
線性回歸模型使用pytorch的簡潔實現
import torch from torch import nn import numpy as np torch.manual_seed(1) print(torch.__version__) torch.set_default_tensor_type('torch.FloatTensor')生成數據集
在這裡生成數據集跟從零開始的實現中是完全一樣的。
num_inputs = 2 num_examples = 1000 true_w = [2, -3.4] true_b = 4.2 features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float) labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)
讀取數據集
import torch.utils.data as Data batch_size = 10 # combine featues and labels of dataset dataset = Data.TensorDataset(features, labels) # put dataset into DataLoader data_iter = Data.DataLoader( dataset=dataset, # torch TensorDataset format batch_size=batch_size, # mini batch size shuffle=True, # whether shuffle the data or not num_workers=2, # read data in multithreading )
for X, y in data_iter: print(X, 'n', y) break
定義模型
class LinearNet(nn.Module): def __init__(self, n_feature): super(LinearNet, self).__init__() # call father function to init self.linear = nn.Linear(n_feature, 1) # function prototype: `torch.nn.Linear(in_features, out_features, bias=True)` def forward(self, x): y = self.linear(x) return y net = LinearNet(num_inputs) print(net)
# ways to init a multilayer network # method one net = nn.Sequential( nn.Linear(num_inputs, 1) # other layers can be added here ) # method two net = nn.Sequential() net.add_module('linear', nn.Linear(num_inputs, 1)) # net.add_module ...... # method three from collections import OrderedDict net = nn.Sequential(OrderedDict([ ('linear', nn.Linear(num_inputs, 1)) # ...... ])) print(net) print(net[0])初始化模型參數
from torch.nn import init init.normal_(net[0].weight, mean=0.0, std=0.01) init.constant_(net[0].bias, val=0.0) # or you can use `net[0].bias.data.fill_(0)` to modify it directly
for param in net.parameters(): print(param)
定義損失函數
loss = nn.MSELoss() # nn built-in squared loss function # function prototype: `torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')`
定義優化函數
import torch.optim as optim optimizer = optim.SGD(net.parameters(), lr=0.03) # built-in random gradient descent function print(optimizer) # function prototype: `torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)`
訓練
num_epochs = 3 for epoch in range(1, num_epochs + 1): for X, y in data_iter: output = net(X) l = loss(output, y.view(-1, 1)) optimizer.zero_grad() # reset gradient, equal to net.zero_grad() l.backward() optimizer.step() print('epoch %d, loss: %f' % (epoch, l.item()))# result comparision dense = net[0] print(true_w, dense.weight.data) print(true_b, dense.bias.data)
兩種實現方式的比較
- 從零開始的實現(推薦用來學習) 能夠更好的理解模型和神經網路底層的原理
- 使用pytorch的簡潔實現 能夠更加快速地完成模型的設計與實現
