NNDL 实验三 线性回归
文章目錄
- NNDL 實驗三 線性回歸
- 2.2 線性回歸
- 2.2.1 數據集構建
- 2.2.2 模型構建
- 2.2.3 損失函數
- 2.2.4 模型優化
- 2.2.5 模型訓練
- 2.2.6 模型評估
- 2.2.7 樣本數量 & 正則化系數
- 2.3 多項式回歸
- 2.3.1 數據集構建
- 2.3.2 模型構建
- 2.3.3 模型訓練
- 2.3.4 模型評估
- 2.4 Runner類介紹
- 2.5 基于線性回歸的波士頓房價預測
- 2.5.1 數據處理
- 2.5.1.1 數據清洗
- 2.5.1.2 數據集劃分
- 2.5.1.3特征工程
- 2.5.2 模型構建
- 2.5.3 完善Runner類
- 2.5.4 模型訓練
- 2.5.5 模型測試
- 2.5.6 模型預測
NNDL 實驗三 線性回歸
使用pytorch實現
2.2 線性回歸
2.2.1 數據集構建
構造一個小的回歸數據集:
生成 150 個帶噪音的樣本,其中 100 個訓練樣本,50 個測試樣本,并打印出訓練數據的可視化分布。
代碼:
輸出結果:
2.2.2 模型構建
import torch torch.seed() # 設置隨機種子 class Op(object):def __init__(self):passdef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):raise NotImplementedErrordef backward(self, inputs):raise NotImplementedError # 線性算子 class Linear(Op):def __init__(self, input_size):"""輸入:- input_size:模型要處理的數據特征向量長度"""self.input_size = input_size# 模型參數self.params = {}self.params['w'] = torch.randn(self.input_size, 1)self.params['b'] = torch.zeros([1])def __call__(self, X):return self.forward(X)# 前向函數def forward(self, X):N, D = X.shapeif self.input_size == 0:return torch.full(shape=[N, 1], fill_value=self.params['b'])assert D == self.input_size # 輸入數據維度合法性驗證# 使用torch.matmul計算兩個tensor的乘積y_pred = torch.matmul(X, self.params['w']) + self.params['b']return y_pred# 注意這里我們為了和后面章節統一,這里的X矩陣是由N個x向量的轉置拼接成的,與原教材行向量表示方式不一致 input_size = 3 N = 2 X = torch.randn(N,input_size) # 生成2個維度為3的數據 model = Linear(input_size) y_pred = model(X) print("y_pred:", y_pred) # 輸出結果的個數也是2個y_pred: tensor([[ 0.7946],
[-3.1321]])
2.2.3 損失函數
回歸任務中常用的評估指標是均方誤差
均方誤差(mean-square error, MSE)是反映估計量與被估計量之間差異程度的一種度量。
思考:沒有除2合理么?談談自己的看法,寫到實驗報告。
合理,2只是一個常數,它不會影響均方誤差的準確性。
2.2.4 模型優化
經驗風險 ( Empirical Risk ),即在訓練集上的平均損失。
#模型優化 def optimizer_lsm(model, X, y, reg_lambda=0):"""輸入:- model: 模型- X: tensor, 特征數據,shape=[N,D]- y: tensor,標簽數據,shape=[N]- reg_lambda: float, 正則化系數,默認為0輸出:- model: 優化好的模型"""N, D = X.shape# 對輸入特征數據所有特征向量求平均x_bar_tran = torch.mean(X, axis=0).T# 求標簽的均值,shape=[1]y_bar = torch.mean(y)# paddle.subtract通過廣播的方式實現矩陣減向量x_sub = torch.subtract(X, x_bar_tran)# 使用paddle.all判斷輸入tensor是否全0if torch.all(x_sub == 0):model.params['b'] = y_barmodel.params['w'] = torch.zeros(shape=[D])return model# paddle.inverse求方陣的逆tmp = torch.inverse( torch.matmul(x_sub.T, x_sub) +reg_lambda * torch.eye(num_rows=(D)))w = torch.matmul(torch.matmul(tmp, x_sub.T), (y - y_bar))b = y_bar - torch.matmul(x_bar_tran, w)model.params['b'] = bmodel.params['w'] = torch.squeeze(w, axis=-1)return model思考1. 為什么省略1/N了不影響效果?
它是個系數,對于求解優化的問題,它不會造成優化結果的改變。
思考 2. 什么是最小二乘法( Least Square Method , LSM )
最小二乘法主要是通過最小化誤差的平方和尋找數據的最佳函數匹配。
2.2.5 模型訓練
在準備了數據、模型、損失函數和參數學習的實現之后,我們開始模型的訓練。在回歸任務中,模型的評價指標和損失函數一致,都為均方誤差。
通過上文實現的線性回歸類來擬合訓練數據,并輸出模型在訓練集上的損失。
w_pred: 1.2005535364151 b_pred: 0.6063849329948425
train error: 5.124952793121338
2.2.6 模型評估
下面用訓練好的模型預測一下測試集的標簽,并計算在測試集上的損失。
y_test_pred = model(X_test.reshape([-1,1])).squeeze() test_error = mean_squared_error(y_true=y_test, y_pred=y_test_pred).item() print("test error: ",test_error)test error: 3.3286447525024414
2.2.7 樣本數量 & 正則化系數
1.調整訓練數據的樣本數量,由 100 調整到 5000,觀察對模型性能的影響。
train error: 4.095644950866699
2.調整正則化系數,觀察對模型性能的影響。
train error: 4.089155673980713
2.3 多項式回歸
2.3.1 數據集構建
import math import torch import matplotlib.pyplot as plt import numpy as np # sin函數: sin(2 * pi * x) def sin(x):y =torch.sin(2 * math.pi * x)return y def create_toy_data(func, interval, sample_num, noise = 0.0, add_outlier = False, outlier_ratio = 0.001):# 均勻采樣# 使用torch.rand在生成sample_num個隨機數X = torch.rand(size = [sample_num]) * (interval[1]-interval[0]) + interval[0]y = func(X)# 生成高斯分布的標簽噪聲# 使用torch.normal生成0均值,noise標準差的數據epsilon = torch.tensor(np.random.normal(0,noise,size=y.shape[0]))y = y + epsilonif add_outlier: # 生成額外的異常點outlier_num = int(len(y)*outlier_ratio)if outlier_num != 0:# 使用torch.randint生成服從均勻分布的、范圍在[0, len(y))的隨機Tensoroutlier_idx = torch.rand(len(y),shape = [outlier_num])y[outlier_idx] = y[outlier_idx] * 5return X, y func = sin interval = (0,1) train_num = 15 test_num = 10 noise = 0.5 #0.1 X_train, y_train = create_toy_data(func=func, interval=interval, sample_num=train_num, noise = noise) X_test, y_test = create_toy_data(func=func, interval=interval, sample_num=test_num, noise = noise)X_underlying = torch.linspace(interval[0],interval[1],steps=100) y_underlying = sin(X_underlying)# 繪制圖像 plt.rcParams['figure.figsize'] = (8.0, 6.0) plt.scatter(X_train, y_train, facecolor="none", edgecolor='#e4007f', s=50, label="train data") #plt.scatter(X_test, y_test, facecolor="none", edgecolor="r", s=50, label="test data") plt.plot(X_underlying, y_underlying, c='#000000', label=r"$\sin(2\pi x)$") plt.legend(fontsize='x-large') plt.savefig('ml-vis2.pdf') plt.show()2.3.2 模型構建
套用求解線性回歸參數的方法來求解多項式回歸參數
import torch # 多項式轉換 def polynomial_basis_function(x, degree=2):if degree == 0:return torch.ones(shape=x.shape, dtype=torch.loat32)x_tmp = xx_result = x_tmpfor i in range(2, degree + 1):x_tmp = torch.multiply(x_tmp, x) # 逐元素相乘x_result = torch.concat((x_result, x_tmp), axis=-1)return x_result# 簡單測試 data = [[2], [3], [4]] X = torch.as_tensor(data=data, dtype=torch.float32) degree = 3 transformed_X = polynomial_basis_function(X, degree=degree) print("轉換前:", X) print("階數為", degree, "轉換后:", transformed_X)轉換前: tensor([[2.],
[3.],
[4.]])
階數為 3 轉換后: tensor([[ 2., 4., 8.],
[ 3., 9., 27.],
[ 4., 16., 64.]])
2.3.3 模型訓練
對于多項式回歸,可以同樣使用前面線性回歸中定義的LinearRegression算子、訓練函數train、均方誤差函數mean_squared_error。
for i, degree in enumerate([0, 1, 3, 8]): # []中為多項式的階數model = Linear(degree)X_train_transformed = polynomial_basis_function(X_train.reshape([-1, 1]), degree)X_underlying_transformed = polynomial_basis_function(X_underlying.reshape([-1, 1]), degree)model = optimizer_lsm(model, X_train_transformed, y_train.reshape([-1, 1])) # 擬合得到參數y_underlying_pred = model(X_underlying_transformed).squeeze()print(model.params)# 繪制圖像plt.subplot(2, 2, i + 1)plt.scatter(X_train, y_train, facecolor="none", edgecolor='#e4007f', s=50, label="train data")plt.plot(X_underlying, y_underlying, c='#000000', label=r"$\sin(2\pi x)$")plt.plot(X_underlying, y_underlying_pred, c='#f19ec2', label="predicted function")plt.ylim(-2, 1.5)plt.annotate("M={}".format(degree), xy=(0.95, -1.4))# plt.legend(bbox_to_anchor=(1.05, 0.64), loc=2, borderaxespad=0.) plt.legend(loc='lower left', fontsize='x-large') plt.savefig('ml-vis3.pdf') plt.show()2.3.4 模型評估
# 訓練誤差和測試誤差 training_errors = [] test_errors = [] distribution_errors = []# 遍歷多項式階數 for i in range(9):model = Linear(i)X_train_transformed = polynomial_basis_function(X_train.reshape([-1, 1]), i)X_test_transformed = polynomial_basis_function(X_test.reshape([-1, 1]), i)X_underlying_transformed = polynomial_basis_function(X_underlying.reshape([-1, 1]), i)optimizer_lsm(model, X_train_transformed, y_train.reshape([-1, 1]))y_train_pred = model(X_train_transformed).squeeze()y_test_pred = model(X_test_transformed).squeeze()y_underlying_pred = model(X_underlying_transformed).squeeze()train_mse = mean_squared_error(y_true=y_train, y_pred=y_train_pred).item()training_errors.append(train_mse)test_mse = mean_squared_error(y_true=y_test, y_pred=y_test_pred).item()test_errors.append(test_mse)# distribution_mse = mean_squared_error(y_true=y_underlying, y_pred=y_underlying_pred).item()# distribution_errors.append(distribution_mse)print("train errors: \n", training_errors) print("test errors: \n", test_errors) # print ("distribution errors: \n", distribution_errors)# 繪制圖片 plt.rcParams['figure.figsize'] = (8.0, 6.0) plt.plot(training_errors, '-.', mfc="none", mec='#e4007f', ms=10, c='#e4007f', label="Training") plt.plot(test_errors, '--', mfc="none", mec='#f19ec2', ms=10, c='#f19ec2', label="Test") # plt.plot(distribution_errors, '-', mfc="none", mec="#3D3D3F", ms=10, c="#3D3D3F", label="Distribution") plt.legend(fontsize='x-large') plt.xlabel("degree") plt.ylabel("MSE") plt.savefig('ml-mse-error.pdf') plt.show()對于模型過擬合的情況,可以引入正則化方法.
degree = 8 # 多項式階數 reg_lambda = 0.0001 # 正則化系數X_train_transformed = polynomial_basis_function(X_train.reshape([-1,1]), degree) X_test_transformed = polynomial_basis_function(X_test.reshape([-1,1]), degree) X_underlying_transformed = polynomial_basis_function(X_underlying.reshape([-1,1]), degree)model = Linear(degree) optimizer_lsm(model,X_train_transformed,y_train.reshape([-1,1]))y_test_pred=model(X_test_transformed).squeeze() y_underlying_pred=model(X_underlying_transformed).squeeze()model_reg = Linear(degree) optimizer_lsm(model_reg,X_train_transformed,y_train.reshape([-1,1]),reg_lambda=reg_lambda)y_test_pred_reg=model_reg(X_test_transformed).squeeze() y_underlying_pred_reg=model_reg(X_underlying_transformed).squeeze()mse = mean_squared_error(y_true = y_test, y_pred = y_test_pred).item() print("mse:",mse) mes_reg = mean_squared_error(y_true = y_test, y_pred = y_test_pred_reg).item() print("mse_with_l2_reg:",mes_reg)# 繪制圖像 plt.scatter(X_train, y_train, facecolor="none", edgecolor="#e4007f", s=50, label="train data") plt.plot(X_underlying, y_underlying, c='#000000', label=r"$\sin(2\pi x)$") plt.plot(X_underlying, y_underlying_pred, c='#e4007f', linestyle="--", label="$deg. = 8$") plt.plot(X_underlying, y_underlying_pred_reg, c='#f19ec2', linestyle="-.", label="$deg. = 8, \ell_2 reg$") plt.ylim(-1.5, 1.5) plt.annotate("lambda={}".format(reg_lambda), xy=(0.82, -1.4)) plt.legend(fontsize='large') plt.savefig('ml-vis4.pdf') plt.show()2.4 Runner類介紹
機器學習方法流程包括數據集構建、模型構建、損失函數定義、優化器、模型訓練、模型評價、模型預測等環節。
為了更方便地將上述環節規范化,我們將機器學習模型的基本要素封裝成一個Runner類。
除上述提到的要素外,再加上模型保存、模型加載等功能。
Runner類的成員函數定義如下:
__init__函數:實例化Runner類,需要傳入模型、損失函數、優化器和評價指標等;
train函數:模型訓練,指定模型訓練需要的訓練集和驗證集;
evaluate函數:通過對訓練好的模型進行評價,在驗證集或測試集上查看模型訓練效果;
predict函數:選取一條數據對訓練好的模型進行預測;
save_model函數:模型在訓練過程和訓練結束后需要進行保存;
load_model函數:調用加載之前保存的模型。
2.5 基于線性回歸的波士頓房價預測
使用線性回歸來對馬薩諸塞州波士頓郊區的房屋進行預測。
實驗流程主要包含如下5個步驟:
數據處理:包括數據清洗(缺失值和異常值處理)、數據集劃分,以便數據可以被模型正常讀取,并具有良好的泛化性;
模型構建:定義線性回歸模型類;
訓練配置:訓練相關的一些配置,如:優化算法、評價指標等;
組裝訓練框架Runner:Runner用于管理模型訓練和測試過程;
模型訓練和測試:利用Runner進行模型訓練和測試。
2.5.1 數據處理
2.5.1.1 數據清洗
import pandas as pd # 開源數據分析和操作工具# 利用pandas加載波士頓房價的數據集 data=pd.read_csv(r"C:\Users\320\Documents\Tencent Files\1377916621\FileRecv/boston_house_prices.csv") # 查看各字段缺失值統計情況 print(data.isna().sum())從輸出結果看,波士頓房價預測數據集中不存在缺失值的情況。
異常值處理:通過箱線圖直觀的顯示數據分布,并觀測數據中的異常值。箱線圖一般由五個統計值組成:最大值、上四分位、中位數、下四分位和最小值。一般來說,觀測到的數據大于最大估計值或者小于最小估計值則判斷為異常值,其中:
最大估計值=上四分位+1.5?(上四分位?下四分位)
最小估計值=下四分位?1.5?(上四分位?下四分位)
使用四分位值篩選出箱線圖中分布的異常值,并將這些數據視為噪聲,其將被臨界值取代,代碼實現如下:
# 四分位處理異常值 num_features=data.select_dtypes(exclude=['object','bool']).columns.tolist()for feature in num_features:if feature =='CHAS':continueQ1 = data[feature].quantile(q=0.25) # 下四分位Q3 = data[feature].quantile(q=0.75) # 上四分位IQR = Q3-Q1 top = Q3+1.5*IQR # 最大估計值bot = Q1-1.5*IQR # 最小估計值values=data[feature].valuesvalues[values > top] = top # 臨界值取代噪聲values[values < bot] = bot # 臨界值取代噪聲data[feature] = values.astype(data[feature].dtypes)# 再次查看箱線圖,異常值已被臨界值替換(數據量較多或本身異常值較少時,箱線圖展示會不容易體現出來) boxplot(data, 'ml-vis6.pdf')2.5.1.2 數據集劃分
import torch torch.seed() # 劃分訓練集和測試集 def train_test_split(X, y, train_percent=0.8):n = len(X)shuffled_indices = torch.randperm(n) # 返回一個數值在0到n-1、隨機排列的1-D Tensortrain_set_size = int(n*train_percent)train_indices = shuffled_indices[:train_set_size]test_indices = shuffled_indices[train_set_size:]X = X.valuesy = y.valuesX_train=X[train_indices]y_train = y[train_indices]X_test = X[test_indices]y_test = y[test_indices]return X_train, X_test, y_train, y_test X = data.drop(['MEDV'], axis=1) y = data['MEDV'] X_train, X_test, y_train, y_test = train_test_split(X,y)# X_train每一行是個樣本,shape[N,D]2.5.1.3特征工程
import torch X_train = torch.tensor(X_train ,dtype=torch.float32) X_test = torch.tensor(X_test ,dtype=torch.float32) y_train = torch.tensor(y_train ,dtype=torch.float32) y_test = torch.tensor(y_test ,dtype=torch.float32) X_min = torch.min(X_train) X_max = torch.max(X_train) X_train = (X_trai n -X_min ) /(X_ma x -X_min) X_test = (X_tes t -X_min ) /(X_ma x -X_min) # 訓練集構造 train_datase t =(X_train ,y_train) # 測試集構造 test_datase t =(X_test ,y_test) print(train_dataset) print(test_dataset)2.5.2 模型構建
from nndl.op import Linear # 模型實例化 input_size = 12 model=Linear(input_size) # nndl.op import torch torch.seed() # 設置隨機種子 class Op(object):def __init__(self):passdef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):raise NotImplementedErrordef backward(self, inputs):raise NotImplementedError # 線性算子 class Linear(Op):def __init__(self, input_size):self.input_size = input_size# 模型參數self.params = {}self.params['w'] = torch.randn(self.input_size, 1)self.params['b'] = torch.zeros([1])def __call__(self, X):return self.forward(X)# 前向函數def forward(self, X):N, D = X.shapeif self.input_size == 0:return torch.full(shape=[N, 1], fill_value=self.params['b'])assert D == self.input_size # 輸入數據維度合法性驗證# 使用torch.matmul計算兩個tensor的乘積y_pred = torch.matmul(X, self.params['w']) + self.params['b']sreturn y_pred2.5.3 完善Runner類
模型定義好后,圍繞模型需要配置損失函數、優化器、評估、測試等信息,以及模型相關的一些其他信息(如模型存儲路徑等)。
在本章中使用的Runner類為V1版本。其中訓練過程通過直接求解解析解的方式得到模型參數,沒有模型優化及計算損失函數過程,模型訓練結束后保存模型參數。
訓練配置中定義:
訓練環境,如GPU還是CPU,本案例不涉及;
優化器,本案例不涉及;
損失函數,本案例通過平方損失函數得到模型參數的解析解;
評估指標,本案例利用MSE評估模型效果。
在測試集上使用MSE對模型性能進行評估。
2.5.4 模型訓練
在組裝完成Runner之后,我們將開始進行模型訓練、評估和測試。首先,我們先實例化Runner,然后開始進行裝配訓練環境,接下來就可以開始訓練了,相關代碼如下:
# 模型保存文件夾 saved_dir = '/home/aistudio/work/models'# 啟動訓練 runner.train(train_dataset,reg_lambda=0,model_dir=saved_dir)打印出訓練得到的權重:
columns_list = data.columns.to_list() weights = runner.model.params['w'].tolist() b = runner.model.params['b'].item()for i in range(len(weights)):print(columns_list[i],"weight:",weights[i])print("b:",b)CRIM weight: -545.2598266601562
ZN weight: 22.065792083740234
INDUS weight: -8.44003677368164
CHAS weight: 1530.1904296875
NOX weight: -8955.4931640625
RM weight: 2347.146484375
AGE weight: -5.644048690795898
DIS weight: -707.4413452148438
RAD weight: 271.2647705078125
TAX weight: -6.599415302276611
PTRATIO weight: -592.3955688476562
LSTAT weight: -313.0545654296875
b: 35.074371337890625
2.5.5 模型測試
# 加載模型權重 runner.load_model(saved_dir) mse = runner.evaluate(test_dataset) print('MSE:', mse.item())MSE: 14.380061149597168
2.5.6 模型預測
runner.load_model(saved_dir) pred = runner.predict(X_test[:1]) print("真實房價:",y_test[:1].item()) print("預測的房價:",pred.item())真實房價: 10.899999618530273
預測的房價: 15.171201705932617
問題1:使用類實現機器學習模型的基本要素有什么優點?
(1)可以提高程序的效率。
(2)易維護:如果需要改變需求,那么也只需要在局部模塊進行維護,大大降低了成本。
(3)易擴展:系統更靈活、更容易擴展,而且成本較低。
問題2:算子op、優化器opitimizer放在單獨的文件中,主程序在使用時調用該文件。這樣做有什么優點?
簡潔明了,需要修改時,只需要改其中一部分,不需要進行全篇改動。
問題3:線性回歸通常使用平方損失函數,能否使用交叉熵損失函數?為什么?
不能,平方損失函數看重每一個輸出結果,交叉熵損失函數只看重正確的結果。因此平方損失函數還和錯誤結果有關,所以平方損失函數會使正確的分類變大,還會讓錯誤分類都變得更加平均,但實際中后面得調整是沒必要的,但是對于回歸問題這樣的考慮是比較重要得,因而回歸問題上使用交叉熵不合適。
總結
以上是生活随笔為你收集整理的NNDL 实验三 线性回归的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 几款引擎比较:BigWorld,Unre
- 下一篇: 电子商务模块实训