1. 線性迴歸
1.1 線性模型
當輸入包含d個特徵,預測結果表示為:
記x為樣本的特徵向量,w為權重向量,上式可表示為:
對於含有n個樣本的資料集,可用X來表示n個樣本的特徵集合,其中行代表樣本,列代表特徵,那麼預測值可用矩陣乘法表示為:
給定訓練資料特徵X和對應的已知標籤y,線性迴歸的⽬標是找到⼀組權重向量w和偏置b:當給定從X的同分布中取樣的新樣本特徵時,這組權重向量和偏置能夠使得新樣本預測標籤的誤差儘可能小。
1.2 損失函式(loss function)
損失函式又稱代價函式(cost function),通常用其來度量目標的實際值和預測值之間的誤差。在迴歸問題中,常用的損失函式為平方誤差函式:
我們的目標便是求得最小化損失函式下引數w和b的值:
求解上式,一般有以下兩種方式:
1> 正規方程(解析解)
2> 梯度下降(gradient descent)
(1)初始化模型引數的值,如隨機初始化;
(2)從資料集中隨機抽取小批量樣本且在負梯度的方向上更新引數,並不斷迭代這一步驟。
上式中:n表示每個小批量中的樣本數,也稱批量大小(batch size)、α表示學習率(learning rate),n和α的值需要手動預先指定,而不是模型訓練得到的,這類引數稱為超引數(hyperparameter),選擇超引數的過程稱為調參(hyperparameter tuning)。
梯度下降和正規方程比較:
1.3 向量化加速
為了加快模型訓練速度,可以採用向量化計算的方式,這通常會帶來數量級的加速。下邊用程式碼簡單對比測試下向量化計算的加速效果。
import math import time import numpy as np import torch from d2l import torch as d2l # a、b是全為1的10000維向量 n = 10000 a = torch.ones(n) b = torch.ones(n) class Timer: def __init__(self): """記錄多次執行時間""" self.tik = None self.times = [] self.start() def start(self): """啟動計時器""" self.tik = time.time() def stop(self): """停止計時器並將時間記錄在列表中""" self.times.append(time.time() - self.tik) return self.times[-1] def avg(self): """返回平均時間""" return sum(self.times) / len(self.times) def sum(self): """返回總時間""" return sum(self.times) def cumsum(self): """返回總時間""" return np.array(self.times).cumsum().tolist() c = torch.zeros(n) timer = Timer() for i in range(n): c[i] = a[i] + b[i] print(f'{timer.stop():.5f} sec') timer.start() d = a + b print(f'{timer.stop():.5f} sec')
程式碼執行結果如下,可見向量化程式碼確實極大的提高了計算速度。
注:這裡向量化計算d=a+b的時間不知道為什麼統計出來是0,可能是跟電腦的計時器精度有關。
2. 從零實現線性迴歸
線性迴歸的實現過程可以簡單總結為以下幾個步驟:
(1)讀取資料(或構造資料),轉換成需要的格式和型別,並生成標籤 ;
(2)定義初始化模型引數、定義模型、定義損失函式、定義優化演算法;
(3)使用優化演算法訓練模型。
import random import torch import numpy as np from matplotlib import pyplot as plt from d2l import torch as d2l # 構造資料集 def synthetic_data(w, b, num_examples): """生成 y = Xw + b + 噪聲。""" # 均值為0,方差為1的隨機數,行數為樣本數,列數是w的長度(行代表樣本,列代表特徵) X = torch.normal(0, 1, (num_examples, len(w))) # pytorch較新版本 # X = torch.tensor(np.random.normal(0, 1, (num_examples, len(w))), dtype=torch.float32) # pytorch1.1.0版本 y = torch.matmul(X, w) + b # 均值為0,方差為1的隨機數,噪聲項。 y += torch.normal(0, 0.01, y.shape) # pytorch較新版本 # y += torch.tensor(np.random.normal(0, 0.01, y.shape), dtype=torch.float32) # pytorch1.1.0版本 return X, y.reshape((-1, 1)) true_w = torch.tensor([2, -3.4]) true_b = 4.2 features, labels = synthetic_data(true_w, true_b, 1000) print('features:', features[0], '\nlabel:', labels[0]) d2l.set_figsize() d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1) # 生成一個data_iter函式,該函式接收批量大小、特徵矩陣和標籤向量作為輸入,生成大小為batch_size的小批量 def data_iter(batch_size, features, labels): num_examples = len(features) indices = list(range(num_examples)) # 這些樣本是隨機讀取的,沒有特定的順序 random.shuffle(indices) for i in range(0, num_examples, batch_size): batch_indices = torch.tensor(indices[i:min(i+batch_size, num_examples)]) yield features[batch_indices], labels[batch_indices] batch_size = 10 for X, y in data_iter(batch_size, features, labels): print(X, '\n', y) break # 定義初始化模型引數 w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True) # pytorch較新版本 # w = torch.autograd.Variable(torch.tensor(np.random.normal(0, 0.01, size=(2, 1)), # dtype=torch.float32), requires_grad=True) # pytorch1.1.0版本 b = torch.zeros(1, requires_grad=True) # 定義模型 def linreg(X, w, b): """線性迴歸模型。""" return torch.matmul(X, w) + b # 定義損失函式 def squared_loss(y_hat, y): """均方損失。""" return (y_hat - y.reshape(y_hat.shape))**2 / 2 # 定義優化演算法 def sgd(params, lr, batch_size): """小批量隨機梯度下降""" with torch.no_grad(): for param in params: param -= lr * param.grad / batch_size param.grad.zero_() # 訓練過程 lr = 0.03 num_epochs = 3 net = linreg loss = squared_loss for epoch in range(num_epochs): for X, y in data_iter(batch_size, features, labels): l = loss(net(X, w, b), y) # X和y的小批量損失 # 因為l形狀是(batch_size, 1),而不是一個標量。l中的所有元素被加到一起並以此來計算關於[w, b]的梯度 l.sum().backward() sgd([w, b], lr, batch_size) # 使用引數的梯度更新引數 with torch.no_grad(): train_l = loss(net(features, w, b), labels) print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}') print(f'w的估計誤差:{true_w - w.reshape(true_w.shape)}') print(f'b的估計誤差:{true_b - b}')
3. 使用深度學習框架(PyTorch)實現線性迴歸
使用PyTorch封裝的高階API可以快速高效的實現線性迴歸
import numpy as np import torch from torch import nn # 'nn'是神經網路的縮寫 from torch.utils import data from d2l import torch as d2l # 構造資料集 def synthetic_data(w, b, num_examples): """生成 y = Xw + b + 噪聲。""" # 均值為0,方差為1的隨機數,行數為樣本數,列數是w的長度(行代表樣本,列代表特徵) X = torch.normal(0, 1, (num_examples, len(w))) # pytorch較新版本 # X = torch.tensor(np.random.normal(0, 1, (num_examples, len(w))), dtype=torch.float32) # pytorch1.1.0版本 y = torch.matmul(X, w) + b # 均值為0,方差為1的隨機數,噪聲項。 y += torch.normal(0, 0.01, y.shape) # pytorch較新版本 # y += torch.tensor(np.random.normal(0, 0.01, y.shape), dtype=torch.float32) # pytorch1.1.0版本 return X, y.reshape((-1, 1)) true_w = torch.tensor([2, -3.4]) true_b = 4.2 features, labels = synthetic_data(true_w, true_b, 1000) d2l.set_figsize() d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1) # 呼叫框架中現有的API來讀取資料 def load_array(data_arrays, batch_size, is_train=True): """構造一個PyTorch資料迭代器""" dataset = data.TensorDataset(*data_arrays) return data.DataLoader(dataset, batch_size, shuffle=is_train) batch_size = 10 data_iter = load_array((features, labels), batch_size) print(next(iter(data_iter))) # 使用框架預定義好的層 net = nn.Sequential(nn.Linear(2, 1)) # 初始化模型引數(等價於前邊手動實現w、b以及network的方式) net[0].weight.data.normal_(0, 0.01) # 使用正態分佈替換掉w的值 net[0].bias.data.fill_(0) # 計算均方誤差使用MSELoss類,也稱為平方L2範數 loss = nn.MSELoss() # 例項化SGD例項 trainer = torch.optim.SGD(net.parameters(), lr=0.03) # 訓練 num_epochs = 3 # 迭代三個週期 for epoch in range(num_epochs): for X, y in data_iter: l = loss(net(X), y) trainer.zero_grad() # 優化器,先將梯度清零 l.backward() trainer.step() # 模型更新 l = loss(net(features), labels) print(f'epoch {epoch + 1}, loss {l:f}') w = net[0].weight.data print('w的估計誤差:', true_w - w.reshape(true_w.shape)) b = net[0].bias.data print('b的估計誤差:', true_b - b)
4. 報錯總結
1. torch.normal()報錯,這個是由於PyTorch版本問題,torch.normal()函式的引數形式和用法有所變化。
要生成均值為0且方差為1的隨機數,pytorch1.1.0和pytorch1.9.0可以分別採用以下形式:
# pytorch1.9.0 X = torch.normal(0, 1, (num_examples, len(w))) # pytorch1.1.0(也適用於高版本) X = torch.tensor(np.random.normal(0, 1, (num_examples, len(w))), dtype=torch.float32)
2. d2l庫安裝報錯。這個我在公司電腦上直接一行pip install d2l成功安裝,回家換自己電腦,各種報錯。解決之後發現大多都是找不到安裝源、缺少相關庫或者庫版本不相容的問題。
安裝方式:conda install d2l 或 pip install d2l。網速太慢下不下來可以選擇國內源映象:
pip install d2l -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
國內常用源映象:
# 清華:https://pypi.tuna.tsinghua.edu.cn/simple # 阿里雲:http://mirrors.aliyun.com/pypi/simple/ # 中國科技大學 https://pypi.mirrors.ustc.edu.cn/simple/ # 華中理工大學:http://pypi.hustunique.com/ # 山東理工大學:http://pypi.sdutlinux.org/ # 豆瓣:http://pypi.douban.com/simple/
需要注意的是:有時候使用conda install d2l命令無法下載,改為pip 命令後即可下載成功。這是因為有些包只能通過pip安裝。Anaconda提供超過1,500個軟體包,包括最流行的資料科學、機器學習和AI框架,這與PyPI上提供的150,000多個軟體包相比,只是一小部分。
Python官方安裝whl包和tar.gz包安裝方法:
安裝whl包:pip install wheel,pip install xxx.whl
安裝tar.gz包:cd到解壓後路徑,python setup.py install
參考資料
[1] Python錯誤筆記(2)之Pytorch的torch.normal()函式
[2] 動手學深度學習 李沐