【线性回归——从简单构建到实现数据预测】
深度學習與神經網絡day03-線性回歸
- 一、簡單的線性回歸
- 1.1、數據集的構建
- 1.2、模型構建
- 1.3、損失函數
- 1.4、模型優化
- 1.5、模型訓練
- 1.6、模型評估
- 1.7、樣本數量和正則化系數的影響
- 二、多項式回歸
- 1.1、數據集的構建
- 1.2、模型構建
- 1.5、模型訓練
- 1.6、模型評估
- 三、嘗試封裝Runner類
- 四、基于線性回歸的波士頓房價預測
- 4.1數據集介紹
- 4.2數據清洗
- 4.2.1缺失值分析
- 4.2.2異常值處理
- 4.3數據集的劃分
- 4.4特征工程
- 4.5模型構建
- 4.6完善Runner類
- 4.7模型訓練
- 4.8 模型預測
- 后話:一些問題
一、簡單的線性回歸
1.1、數據集的構建
我們知道線性回歸要實現的方程式為:
我們通過一個簡單的函數linear_func來實現我們的一個簡單的線性回歸,并由此擴展到多個特征的線性回歸模型。
linear_func是一個可以構建構建輸入特征和輸出標簽的維度都為1的數據集函數。
function:接下來構造產生數據集的函數,函數隨機產生一個num_data行1列的隨機數據集,并由linear_func確定標簽值并添加噪聲值,從而使得數據集符合現實生活中并不那么完美的線性回歸。
import torchdef one_feature_data(func,random_x,num_data,noise=0.0,add_outlier=False,outlier_ratio=0.001):"""函數功能:根據給定的函數來生成簡單的數據集輸入:- func: 函數- random_x: x的取值范圍(list)- num_data: 生成的數據集個數- noise: 添加噪聲的大小,默認為0- add_outlier: 是否添加異常點,默認為False- outlier_ratio: 異常值占比輸出:- X: 特征數據,shape=[num_data,1]- Y: 標簽數據,shape=[num_data,1]"""#首先生成num_data個隨機數,范圍在random_x之間X = torch.rand(shape=[num_data])*(random_x[1]-random_x[0])+random_x[0]#產生YY=func(X)#生成高斯分布的標簽噪聲,使標簽不完美。#用np.random.normal實現num_data個噪聲點,0為均值,noise標準差的數據noise_Y = np.random.normal(0,noise,torch.tensor(num_data))Y = Y + noise_Y#查看異常值是否添加if add_outlier:#計算異常值個數outlier_num = int(num_data*outlier_ratio)#查看異常值個數是都為0if outlier_num!=0:#使用torch.randint生成在num_data個數下的outlier_num個異常值的位置outlier_id = torch.randint(num_data,shape=[outlier_num])#將選出的幾個異常位置的數據進行異常處理Y[outlier_id] = Y[outlier_id]*5return X,Yfunction:我們對上述的數據集進行訓練樣本和測試樣本的可視化,并對標準線進行繪圖,從而知道我們要擬合的標準線。
import matplotlib.pyplot as plt #設置參數 func = linear_func random_x = [-10,10] train_num = 100 #訓練樣本的數目 test_num = 50 #測試樣本的數目 noise = 2 #生成訓練集和測試集 X_train,Y_train = one_feature_data(func,random_x,num_data=train_num,noise=noise) print(X_train) print(Y_train) X_test,Y_test = one_feature_data(func,random_x,num_data=test_num,noise=noise) #生成一個大數據集 X_train_large,Y_train_large = one_feature_data(func,random_x,num_data=5000,noise=noise)#生成之前的標準線 X_underlying = torch.linspace(random_x[0],random_x[1],train_num) Y_underlying = linear_func(X_underlying) #數據的可視化 plt.scatter(X_train,Y_train,marker='*',facecolor="none",edgecolor='#e4007f',s=50,label="train_data") plt.scatter(X_test, Y_test, facecolor="none", edgecolor='#f19ec2', s=50, label="test data") plt.plot(X_underlying, Y_underlying, c='#000000', label=r"underlying distribution") plt.legend(fontsize='x-large') # 給圖像加圖例 plt.savefig('ml-vis.pdf') # 保存圖像到PDF文件中 plt.show()可視化:
1.2、模型構建
我們根據:Y=WX + b來構建最基本的式子:
#實現線性模型的通式,利用張量運算來實現 # X: tensor, shape=[N,D] # Y: tensor, shape=[N] # w: shape=[D,1] # b: shape=[1] Y = torch.matmul(X,w)+b接下來我們嘗試創建一個自己的Linear類來實現線性回歸迭代的實現。
創建如下模型:
我們對剛建立的預測模型進行最基本的測試,首先生成一個2行5列的隨機矩陣,然后傳入feature_size=5即5個特征的數據,從而對模型進行一次訓練得到預測模型,然后傳入這個2行5列的矩陣從而傳入得到2個預測值。
預測結果如下:
1.3、損失函數
回歸任務是對連續值的預測,希望模型能根據數據的特征輸出一個連續值作為預測值。因此回歸任務中常用的評估指標是均方誤差。
其中均方誤差沒有除2。
均方誤差 的定義為:
均方誤差的函數表示為:
我們采用一個簡單的樣例對該函數進行測試,確保函數的正確性。
#構造一個簡單的樣例對函數進行測試 y_label = torch.tensor([[-0.2],[4.9]],dtype=torch.float64) y_predict = torch.tensor([[1.3],[2.5]],dtype=torch.float64)error = mean_squared_error(y_label,y_predict).item() print("error:",error)測試結果:
1.4、模型優化
采用經驗風險最小化,線性回歸可以通過最小二乘法求出參數w和b的解析解。
最小二乘法 建立的最優解為:
建立模型優化器(最小二乘法)的函數如下:
1.5、模型訓練
我們已經建立好了模型,并且提到了損失函數,也有了模型優化器,接下來對我們已經建立好的模型進行求解。
#小數據集的模型訓練 input_size = 1 model = Linear(input_size) model = optimizer_lsm(model,X_train.reshape([-1,1]),Y_train.reshape([-1,1])) print("w_pred:",model.params['w'].item(), "b_pred: ", model.params['b'].item()) #求訓練集的誤差為 y_train_pred = model(X_train.reshape([-1,1])).squeeze() train_error = mean_squared_error(Y_train, y_train_pred).item() print("train error: ",train_error)訓練結果:
訓練結果:
1.6、模型評估
我們對用訓練集訓練過了的模型進行測試,來表達模型的性能,測試程序如下:
#小數據集進行評估 y_test_pred = model(X_test.reshape([-1,1])).squeeze() test_error = mean_squared_error(Y_test, y_test_pred).item() print("test error: ",test_error) #大數據集進行評估 y_test_pred_large = model_large(X_test.reshape([-1,1])).squeeze() test_error_large = mean_squared_error(Y_test, y_test_pred_large).item() print("test error large: ",test_error_large)評估結果:
1.7、樣本數量和正則化系數的影響
(1) 調整訓練數據的樣本數量,由 100 調整到 5000,觀察對模型性能的影響。
解: 當樣本數量較小時,容易受到個別點的擾動,對于不同的數據集,實驗結果產生誤差的可能性更大,模型性能較好。
當樣本數量較大時,不容易受到個別樣本的擾動,對于不同的數據集,實驗結果的誤差基本平衡,模型性能較好。
(2) 調整正則化系數,觀察對模型性能的影響。
解: 正則化系數過大,導致了參數被過度正則化,特征參數趨近于0,導致了欠擬合。
正則化系數過小,而訓練樣本的進一步加大,會使參數的數目增多,導致模型的過擬合。
正則化系數在一定范圍內調整時,會使模型的擬合效果編號
二、多項式回歸
1.1、數據集的構建
導入我們所需要的一些庫:
import math import torch from matplotlib import pyplot as plt import numpy as np由于是學習多項式回歸,所以在此我們假設我們要擬合的非線性函數為一個縮放后的sin函數。
# sin函數: sin(2 * pi * x) def sin(x):y = torch.sin(2 * math.pi * x)return y通線性回歸一樣的,我們生成數據:
def one_feature_data(func, random_x, num_data, noise=0.0, add_outlier=False, outlier_ratio=0.001):"""函數功能:根據給定的函數來生成簡單的數據集輸入:- func: 函數- random_x: x的取值范圍(list)- num_data: 生成的數據集個數- noise: 添加噪聲的大小,默認為0- add_outlier: 是否添加異常點,默認為False- outlier_ratio: 異常值占比輸出:- X: 特征數據,shape=[num_data,1]- Y: 標簽數據,shape=[num_data,1]"""# 首先生成num_data個隨機數,范圍在random_x之間X = torch.rand([num_data]) * (random_x[1] - random_x[0]) + random_x[0]# 產生YY = func(X)# 生成高斯分布的標簽噪聲,使標簽不完美。# 用np.random.normal實現num_data個噪聲點,0為均值,noise標準差的數據noise_Y = torch.tensor(np.random.normal(0, noise, torch.tensor(Y.shape[0])))Y = Y + noise_Y# 查看異常值是否添加if add_outlier:# 計算異常值個數outlier_num = int(num_data * outlier_ratio)# 查看異常值個數是都為0if outlier_num != 0:# 使用torch.randint生成在num_data個數下的outlier_num個異常值的位置outlier_id = torch.randint(num_data, [outlier_num])# 將選出的幾個異常位置的數據進行異常處理Y[outlier_id] = Y[outlier_id] * 5return X, Y# 生成數據 func = sin interval = (0, 1) train_num = 15 test_num = 10 noise = 0.5 # 0.1 X_train, y_train = one_feature_data(func=func, random_x=interval, num_data=train_num, noise=noise) X_test, y_test = one_feature_data(func=func, random_x=interval, num_data=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()我們對數據可視化,畫出標準的sinx圖,然后將訓練樣本可視化,從而確定數據點在標準線上下波動:
1.2、模型構建
我們實現多項式基函數polynomial_basis_function對原始特征x進行轉換。
多項式回歸如下:
我們建立如下函數來實現上式:
我們首先建立輸入序列的多項式轉化:
轉化結果圖:
1.5、模型訓練
轉化后的多項式,即變成了一特征屬性,我們可以采用線性回歸的方式來進行訓練。
plt.rcParams['figure.figsize'] = (12.0, 8.0)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()1.6、模型評估
通過均方誤差來衡量訓練誤差、測試誤差以及在沒有噪音的加入下sin函數值與多項式回歸值之間的誤差,更加真實地反映擬合結果。多項式分布階數從0到8進行遍歷。
# 訓練誤差和測試誤差 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_train, y_train_pred).item()training_errors.append(train_mse)test_mse = mean_squared_error(y_test, 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()評估結果:
通過觀察可視化結果,我們可以得到:
對于模型過擬合的情況,可以引入正則化方法,通過向誤差函數中添加一個懲罰項來避免系數傾向于較大的取值。
代碼如下:
評估結果:
三、嘗試封裝Runner類
Runner類的成員函數定義如下:
- __init__函數:實例化Runner類,需要傳入模型、損失函數、優化器和評價指標等;
- train函數:模型訓練,指定模型訓練需要的訓練集和驗證集;
- evaluate函數:通過對訓練好的模型進行評價,在驗證集或測試集上查看模型訓練效果;
- predict函數:選取一條數據對訓練好的模型進行預測;
- save_model函數:模型在訓練過程和訓練結束后需要進行保存;
- load_model函數:調用加載之前保存的模型。
包裝Runner類示意圖:
包裝后的Runner類:
class Runner():def __init__(self,model,model_loss,model_optimizer,model_evaluate):self.model = modelself.model_loss = model_lossself.model_optimizer = model_optimizerself.model_evaluate = model_evaluatedef train(self,Train_feature,Test_feature,**kwargs):self.Train_feature = Train_featurepassreturn #各個參數def evaluate(self,label,**kwargs):loss = self.model_loss(label,self.predict(self.Train_feature))# accuracy =passreturn #返回精度和誤差def predict(self,X,**kwargs):#用train 的各個參數來對輸入序列X進行預測,返回預測值passreturn #返回預測值def save_model(self,save_path):passdef load_model(self,model_path):pass四、基于線性回歸的波士頓房價預測
4.1數據集介紹
預覽一下我們的數據集的前五條數據,看看有沒有問題。
預覽圖:
4.2數據清洗
4.2.1缺失值分析
用isna().sum()各個屬性的統計缺失值個數。
data.isna().sum()分析下圖,說明此數據集不存在數據缺失的情況。
4.2.2異常值處理
通過箱線圖直觀的顯示數據分布,并觀測數據中的異常值。箱線圖一般由五個統計值組成:最大值、上四分位、中位數、下四分位和最小值。一般來說,觀測到的數據大于最大估計值或者小于最小估計值則判斷為異常值,其中:
箱線圖介紹:
查看數據集各個屬性的異常值并可視化的代碼:
import matplotlib.pyplot as plt # 可視化工具 import pandas as pd # 開源數據分析和操作工具# 利用pandas加載波士頓房價的數據集 data=pd.read_csv("C:\\boston_house_prices.csv") # 預覽前5行數據 data.head() # 查看各字段缺失值統計情況 data.isna().sum() # 箱線圖查看異常值分布 def boxplot(data, fig_name):# 繪制每個屬性的箱線圖data_col = list(data.columns)# 連續畫幾個圖片plt.figure(figsize=(5, 5), dpi=300)# 子圖調整plt.subplots_adjust(wspace=0.6)# 每個特征畫一個箱線圖for i, col_name in enumerate(data_col):plt.subplot(3, 5, i + 1)# 畫箱線圖plt.boxplot(data[col_name],showmeans=True,meanprops={"markersize": 1, "marker": "D", "markeredgecolor": "#C54680"}, # 均值的屬性medianprops={"color": "#946279"}, # 中位數線的屬性whiskerprops={"color": "#8E004D", "linewidth": 0.4, 'linestyle': "--"},flierprops={"markersize": 0.4},)# 圖名plt.title(col_name, fontdict={"size": 5}, pad=2)# y方向刻度plt.yticks(fontsize=4, rotation=90)plt.tick_params(pad=0.5)# x方向刻度plt.xticks([])plt.savefig(fig_name)plt.show()boxplot(data, 'ml-vis5.pdf')可視化結果:
由圖可以直觀的看出,數據中存在很多的異常值,即超出上下兩條杠的部分中的黑圓圈,我們將這些異常值認為是數據集中的“噪聲”,并將臨界值取代噪聲點,從而完成對數據集異常值的處理。
替換代碼:
取代后的箱線圖:
4.3數據集的劃分
將數據集劃分為兩份:訓練集和測試集,不包括驗證集。
使用下邊的代碼進行劃分:
4.4特征工程
為了消除綱量對數據特征之間影響,在模型訓練前,需要對特征數據進行歸一化處理,將數據縮放到[0, 1]區間內,使得不同特征之間具有可比性。
import torchX_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,dim=0) X_max = torch.max(X_train,dim=0) X_train = (X_train-X_min.values)/(X_max.values - X_min.values)X_test = (X_test-X_min.values)/(X_max.values - X_min.values)# 訓練集構造 train_dataset=(X_train,y_train) # 測試集構造 test_dataset=(X_test,y_test)4.5模型構建
首先,實例化一個對象。
關于nndl,我們可以不用這個包,可以用我們上邊自己寫的Linear類,從而進行一樣的操作,也能得到結果。
4.6完善Runner類
我們將之前只有一個外殼的Runner類補充,首先:
import torch.nn as nn mse_loss = nn.MSELoss()完整實現如下:
import torch.nn as nn import torch import os from nndl.op import Linear from nndl.opitimizer import optimizer_lsm # 模型實例化 input_size = 12 model=Linear(input_size) mse_loss = nn.MSELoss()class Runner(object):def __init__(self, model, optimizer, loss_fn, metric):# 優化器和損失函數為None,不再關注# 模型self.model = model# 評估指標self.metric = metric# 優化器self.optimizer = optimizerdef train(self, dataset, reg_lambda, model_dir):X, y = datasetself.optimizer(self.model, X, y, reg_lambda)# 保存模型self.save_model(model_dir)def evaluate(self, dataset, **kwargs):X, y = datasety_pred = self.model(X)result = self.metric(y_pred, y)return resultdef predict(self, X, **kwargs):return self.model(X)def save_model(self, model_dir):if not os.path.exists(model_dir):os.makedirs(model_dir)params_saved_path = os.path.join(model_dir, 'params.pdtensor')torch.save(model.params, params_saved_path)def load_model(self, model_dir):params_saved_path = os.path.join(model_dir, 'params.pdtensor')self.model.params = torch.load(params_saved_path)optimizer = optimizer_lsm runner = Runner(model, optimizer=optimizer,loss_fn=None, metric=mse_loss)4.7模型訓練
訓練代碼:
optimizer = optimizer_lsm runner = Runner(model, optimizer=optimizer,loss_fn=None, metric=mse_loss)# 模型保存文件夾 saved_dir = 'D:/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)訓練結果:
加載訓練好的模型參數,在測試集上得到模型的MSE指標。
4.8 模型預測
預測代碼
runner.load_model(saved_dir) pred = runner.predict(X_test[:1]) print("真實房價:",y_test[:1].item()) print("預測的房價:",pred.item())預測結果:
從輸出結果看,預測房價接近真實房價。
后話:一些問題
- 問題1:使用類實現機器學習模型的基本要素有什么優點?
解: 我自我感覺使用類實現機器學習模型的基本要素,可以讓我們以后對任何一個適用于該樣本的數據集能夠以更簡單的形式進行訓練,優化了后續的訓練代碼,并且以類封裝函數可以讓整體更加清晰,思路更加清楚,同時對于機器學習模型的整體把握也會好很多。 - 問題2:算子op、優化器opitimizer放在單獨的文件中,主程序在使用時調用該文件。這樣做有什么優點?
解: 能夠簡化操作,直接使用import引用文件,不需要再重新寫這些算子和優化器,從而不需要再進行更多的代碼編寫,省時省力。 - 問題3:線性回歸通常使用平方損失函數,能否使用交叉熵損失函數?為什么?
解: 如果要問為什么線性回歸要使用均方誤差,可以參考這篇博客:【線性回歸:為什么損失函數要使用均方誤差】,交叉熵損失的假設是誤差分布是二值分布,因此更適用于分類等離散屬性的問題,而均方誤差則假設數據的分布是正態分布,更加是用于連續屬性的誤差分析。
參考以下博客:
NNDL 實驗三 線性回歸
神經網絡與深度學習:案例與實踐
平方損失函數與交叉熵損失函數 & 回歸問題為何不使用交叉熵損失函數
總結
以上是生活随笔為你收集整理的【线性回归——从简单构建到实现数据预测】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 给ChatGLM2注入知识;阅文集团发布
- 下一篇: 博主有喜:拿了金奖和最佳创新奖