深度學習第四篇--- Pytorch 線性回歸模型-波士頓房價預測

波士頓房價預測是一個經典案例,類似于XX語言的Hello World。本文我們學習這個案例,體會深度學習的過程。波斯頓房價可能受影響的因素一共有下面13個,如下圖所示。

我們期望用這個13個因素構建一個模型,實現對房價的預測。對于預測問題,根據預測值的輸出類型是否連續,分為回歸任務和和分類任務,因為房價的預測是一個連續值,所以房價的預測是一個回歸任務,因此我們使用線性回歸模型來解決這個問題,則公式如下:


其中w是各個因素的權重,b是偏置,最終我們經過多輪的訓練,求解出每個w和每個b的值,能夠擬合所有數據,那整個項目就成了。為了證明我們擬合的好不好,還需要定義一個損失函數。


訓練集數據:
https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data

模型的構建和訓練分成5個部分
● 讀取數據集,進行一些數據預處理
● 模型的設計
● 訓練的配置,設定優化算法,即梯度下降算法
● 循環調用模型的訓練過程,主要有前向計算,定義損失函數,反向傳播三個步驟
● 模型的保存、推理

1 數據集加載

1.1 讀取

def load_boston_house_data():
   print("加載數據")
   data = []
   ff = open("../data/boston_house_data").readlines()  # 將數據的每一列都讀取出來
   for item in ff:
       out = re.sub(r"\s{2,}", " ", item).strip()
       print(out)
       data.append(out.split(" "))
   data = np.array(data).astype(np.float64)  # 將數據進行類型轉換為float
   print(data.shape)
   return data


if __name__ == '__main__':
   data = load_boston_house_data()

讀取出來是共計506行,每行14列,其中前13列代表了(表格中影響房價的13特征X,分別對應 [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]),最后一列是房價Y


1.2 訓練集和測試集劃分

通過打印訓練集的形狀,可以發現共有506個樣本,每個樣本含有13個特征和1個預測值。在本案例中,我們將80%的數據用作訓練集,20%用作測試集,實現代碼如下。506*0.8 = 405

def load_boston_house_data():
   print("加載數據")
   data = []
   ff = open("../data/boston_house_data").readlines()  # 將數據的每一列都讀取出來
   for item in ff:
       out = re.sub(r"\s{2,}", " ", item).strip()
       # print(out)
       data.append(out.split(" "))
   data = np.array(data).astype(np.float64)  # 將數據進行類型轉換為float
   print('data.shape={},data[0]={},shape={}'.format(data.shape, data[0], data[0].shape))
   # 80%為訓練集,20%為測試集
   ratio = 0.8
   offset = int(data.shape[0] * ratio)
   training_data = data[:offset]
   test_data = data[offset:]
   print('training_data.shape={},test_data.shape={}'.format(training_data.shape, test_data.shape))
   return training_data, test_data


if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   print('x.shape={},x[0]='.format(x.shape), x[0])
   print('y.shape={},y[0]='.format(y.shape), y[0])

將train_data數據劃分為特征數據x和目標數據y。
● train_data[:, :-1]表示取train_data數組的所有行(維度為:),以及所有列除了最后一列(維度為:-1),即取除了最后一列外的所有列。這樣得到的就是特征數據x,它是一個二維數組。
● train_data[:, -1:]表示取train_data數組的所有行(維度為:),以及只取最后一列(維度為-1:),即只取最后一列的數據。這樣得到的就是目標數據y,它是一個二維數組。

2 模型設計

2.1 單樣本理解

我們的公式是:


在不考慮求和的情況下,對于一個單樣本來說,y = wx + b。對于上面樣本x_train_data[0]、y_train_data[0] 如下:
x_train_data[0] [6.320e-03 1.800e+01 2.310e+00 0.000e+00 5.380e-01 6.575e+00 6.520e+01
4.090e+00 1.000e+00 2.960e+02 1.530e+01 3.969e+02 4.980e+00]
y_train_data[0] 24.0
現在我們只要求出一個w 和 b 使得上面 w
x + b = 24.0就可以了。由于x可以看成1 * 13的矩陣,若滿足矩陣乘法,那么w需要有13行,矩陣的形狀是13*1,我們隨機生成一個 w如下:

w = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, -0.1, -0.2, -0.3, -0.4, 0.0]
w = np.array(w).reshape([13, 1]) # 改變形狀

然后我們可以設置b =2,完整的代碼如下,使用公式 z = w *x +b ,計算出t的值

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   print('x.shape={},x[0]='.format(x.shape), x[0])
   print('y.shape={},y[0]='.format(y.shape), y[0])

   x0 = x[0]
   print('x[0]={}'.format(x0))
   w0 = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, -0.1, -0.2, -0.3, -0.4, 0.0]
   w0 = np.array(w0).reshape([13, 1])
   print("w={}".format(w0))
   b = 2
   z = np.dot(x[0], w0) + b
   print("w * x0 + t = {}".format(z))

計算出z等于-163,因為我們的w和b是隨機設置的,所以和準確值24相比,差距比較大。根據以上的代碼,我們定義一個網絡類,并且在main函數中調用。

class Network(object):
   def __init__(self, num_of_weights):
       # 隨機產生w的初始值 為了保持程序每次運行結果的一致性,此處設置固定的隨機數種子
       np.random.seed(0)
       self.w = np.random.randn(num_of_weights, 1)
       print("self.w={}".format(self.w))
       self.b = 2

   def forward(self, x):
       z = np.dot(x, self.w) + self.b
       return z
       
if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   net = Network(13)
   z = net.forward(x0)
   print("w * x0 + t = {}".format(z))

2.2 定義損失函數

可以看到我們上面隨機生成的w,離真實值差距都比較大,這節我們需要定一個損失函數,來衡量模型的好壞,也就是衡量參數w和b的好壞。我們的損失函數如下:



現在先用這個公式計算損失,至于為什么是這樣的函數,后面在說明,現在我們的Net類中添加loss函數

class Network(object):
   def __init__(self, num_of_weights):
       # 隨機產生w的初始值 為了保持程序每次運行結果的一致性,此處設置固定的隨機數種子
       np.random.seed(0)
       self.w = np.random.randn(num_of_weights, 1)
       print("self.w={}".format(self.w))
       self.b = 2

   def forward(self, x):
       z = np.dot(x, self.w) + self.b
       return z

   def loss(self, z, y):
       temp_l = (z - y) * (z - y)
       cost = np.mean(temp_l)
       return cost

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   y0 = y[0]
   net = Network(13)
   z = net.forward(x0)
   loss = net.loss(z, y)
   print("w * x0 + t = {},loss = {}".format(z, loss))

3 模型訓練

3.1 梯度下降算法含義

模型訓練的過程,也就是求解w和b的過程,我們需要求到一個w和b,使用loss最小。對于上面定義的損失函數,我們先看一個一元二次方程,它就一個開口向上的拋物線。我們要求P0處的斜率,就是求x值=x0的倒數。

我們要求函數值loss最小,什么時候曲線值最小呢,當倒數為0的時候,值最小。對于上面的損失函數來說:他們的偏導數為0即可:


但是想直接求解到w和b的值是比較困難的。在現實中存在大量的函數正向求解容易,但反向求解較難,被稱為單向函數。比如有了一個鑰匙X,打開鎖Y很容易,但是給你一把鎖Y,配出他的鑰匙X很難。
因此,有一種方法論被提出來:把拋物線想象成山坡,我們要從P點出發到山坡的底部。這種情況特別類似于一位想從山峰走到坡谷的盲人,他看不見坡谷在哪(無法逆向求解出Loss導數為0時的參數值),但可以伸腳探索身邊的坡度(當前點的導數值,也稱為梯度)。那么,求解Loss函數最小值可以這樣實現:從當前的參數取值,一步步的按照下坡的方向下降,直到走到最低點。這種方法形象的稱它為“盲人下坡法”。更正式的說法叫“梯度下降法”。



我們可以先看了loss只隨著W1和W2兩個參數變化的過程,其他的W3....W13都被固定下來,用以下代碼畫出loss的變化曲線。

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   y0 = y[0]

   net = Network(13)
   losses = []
   # 只畫出參數w1和w2在區間[-160, 160]的曲線部分,以及包含損失函數的極值
   w1 = np.arange(-160.0, 160.0, 1.0)
   w2 = np.arange(-160.0, 160.0, 1.0)
   losses = np.zeros([len(w1), len(w2)]) # 320*320的全0矩陣
   # 計算設定區域內每個參數取值所對應的Loss
   for i in range(len(w1)):
       for j in range(len(w2)):
           net.w[1] = w1[i]
           net.w[2] = w2[j]
           z = net.forward(x)
           loss = net.loss(z, y)
           losses[i, j] = loss

   fig = plt.figure()
   ax = Axes3D(fig)

   w1, w2 = np.meshgrid(w1, w2)

   ax.plot_surface(w1, w2, losses, rstride=1, cstride=1, cmap='rainbow')
   plt.show()

我們可以看到,在三維的空間中,很直觀的看到有山坡的低點。想著盲人從山坡感覺著地面的走勢,可以走到山底。

3.2 梯度下降算法推導

既然是求損失函數的偏導數,那么可以定義出來:



考慮只有一個樣本i的情況下:


因為求偏到之后,會有一個2出來,所以這里乘以1/2,方便計算。那么Z真實 = w*x +b代入公式即為:


所以計算出L損失為:



計算偏導數為:



我們選擇一個樣本,比如w0的梯度來計算
if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   y0 = y[0]

   net = Network(13)
   z0 = net.forward(x0)

   gradient_w0 = (z0 - y0) * x0[0]
   print('x0 {}, shape {}'.format(x0, x0.shape))
   print('y0 {}, shape {}'.format(y0, y0.shape))
   print('z0 {}, shape {}'.format(z0, z0.shape))
   print('gradient_w0 {}'.format(gradient_w0))

   # 計算出第一個樣本所有w的梯度
   for index in range(13):
       gradient_w = (z0 - y0) * x0[index]
       print("gradient_w{}={}".format(index, gradient_w))

根據如上公式我們可以把w1......w13的梯度都計算出來。

3.3 使用NumPy進行梯度計算

NumPy中的矩陣是可以直接做運算的,假設我們要計算第一個樣本所有的梯度。


if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   y0 = y[0]

   net = Network(13)
   z0 = net.forward(x0)

   gradient_w = (z0 - y0) * x0
   print('gradient_w {}'.format(gradient_w))

可以看到和上面一個個計算結果是一樣的,所以計算第一個和第二個樣本的梯度變的很簡單,如下:

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   y0 = y[0]

   net = Network(13)
   z0 = net.forward(x0)

   gradient_w0 = (z0 - y0) * x0
   print('gradient_w0 {}'.format(gradient_w0))

   x1 = x[1]
   y1 = y[1]
   z1 = net.forward(x1)
   gradient_w1 = (z1 - y1) * x1
   print('gradient_w1 {}'.format(gradient_w1))

   x2 = x[2]
   y2 = y[2]
   z2 = net.forward(x2)
   gradient_w2 = (z2 - y2) * x2
   print('gradient_w2 {}'.format(gradient_w2))

上面三個樣本是分別求出梯度的,能不能一起求出呢,也是可以的。

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   y0 = y[0]

   net = Network(13)
   # 注意這里是一次取出3個樣本的數據,不是取出第3個樣本
   x3samples = x[0:3]
   y3samples = y[0:3]
   z3samples = net.forward(x3samples)
   gradient_w = (z3samples - y3samples) * x3samples
   print("w={}".format(gradient_w))

所以,我們根據上面的思路,可以求出所有的樣本的梯度:

def gradient(self, x, y):
   z = self.forward(x)
   y = y.reshape((len(y), 1))
   gradient_w = (z - y) * x
   gradient_w = np.mean(gradient_w, axis=0)
   gradient_w = gradient_w[:, np.newaxis]


z = net.forward(x_train)
loss = net.loss_fun(z, y_train)
gradient_w = net.gradient(x_train, y_train)
print('gradient_w {}, shape {}'.format(gradient_w, gradient_w.shape))

考慮到上面gradient_w的每一行代表了一個樣本對梯度的貢獻。根據梯度的計算公式,總梯度是對每個樣本對梯度貢獻的平均值,我們需要mean處理一下。


   def gradient(self, x, y):
       z = self.forward(x)
       gradient_w = (z - y) * x
       gradient_w = np.mean(gradient_w, axis=0)
       print('gradient_w={},gradient_w shape {}'.format(gradient_w, gradient_w.shape))
       gradient_w = gradient_w[:, np.newaxis]
       print('gradient_w={},gradient_w shape {}'.format(gradient_w, gradient_w.shape))
       return gradient_w

np.mean(gradient_w, axis=0)表示對梯度向量gradient_w按列進行求平均值。這樣,將梯度向量從一維數組變為一個標量值,即得到了平均梯度。

[:, np.newaxis]用于改變數組的形狀。gradient_w[:, np.newaxis]表示將平均梯度gradient_w轉換為一個二維數組。其中[:, np.newaxis]的作用是在每個元素的維度上增加一個新的維度,從而將一維數組轉換為列向量。

將梯度向量進行平均,可以減少梯度的方差,使參數更新更加穩定。而將平均梯度轉換為列向量,則是為了與模型參數的形狀相匹配,從而實現參數的正確更新。

同時根據公式,可以把偏執b寫出來

gradient_b = (z - y)
gradient_b = np.mean(gradient_b)

3.4 梯度反方向移動

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   x0 = x[0]
   y0 = y[0]

   net = Network(13)
   # 設置[w1, w2] = [-100., -100.]
   net.w[1] = -100.0
   net.w[2] = -100.0

   z = net.forward(x)
   loss = net.loss(z, y)
   gradient_w, gradient_b = net.gradient(x, y)
   gradient_w1 = gradient_w[1][0]
   gradient_w2 = gradient_w[2][0]
   print('第一次 point {}, loss {}'.format([net.w[1][0], net.w[2][0]], loss))
   print('第一次 gradient {}'.format([gradient_w1, gradient_w2]))

   # 在[w1, w2]平面上,沿著梯度的反方向移動到下一個點P1,移動10次
   for index in range(10):
       # 定義移動步長 step_size
       step_size = 0.1
       # 更新參數w5和w9
       net.w[1] = net.w[1] - step_size * gradient_w1
       net.w[2] = net.w[2] - step_size * gradient_w2
       # 重新計算z
       z = net.forward(x)
       loss = net.loss(z, y)
       gradient_w, gradient_b = net.gradient(x, y)
       gradient_w1 = gradient_w[1][0]
       gradient_w2 = gradient_w[2][0]
       print('point {}, loss {}'.format([net.w[1][0], net.w[2][0]], loss))
       print('gradient {}'.format([gradient_w1, gradient_w2]))

我們看到loss變化的太快了,而且不是呈現下降趨勢,后面的值有稍微變大,畫圖出來就是打勾了,也就是我們俗稱的不收斂,所以我們要把我們step值,即每次移動的步長(學習率)調整的小一些,從0.1調整成0.001,圖像是符合預期的


3.5 數據歸一化處理

對于上面其實還不行,我們將訓練調整到100次,畫圖圖像。


可以看到 loss值到20輪之后,就下降不下去了,這樣的模型是不行的。我們在數據處理的時候,還忽視了重要的一個步驟,就是數據特征輸入歸一化后。

def load_boston_house_data():
   print("加載數據")
   data = []
   ff = open("../data/boston_house_data").readlines()  # 將數據的每一列都讀取出來
   for item in ff:
       out = re.sub(r"\s{2,}", " ", item).strip()
       # print(out)
       data.append(out.split(" "))
   data = np.array(data).astype(np.float64)  # 將數據進行類型轉換為float
   print('data.shape={},data[0]={},shape={}'.format(data.shape, data[0], data[0].shape))
   # 80%為訓練集,20%為測試集
   ratio = 0.8
   offset = int(data.shape[0] * ratio)
   training_data = data[:offset]
   # 針對training_data 做歸一化處理
   # 計算訓練集的最大值,最小值
   maximums, minimums = training_data.max(axis=0), \
       training_data.min(axis=0)

   # 對數據進行歸一化處理
   for i in range(13):
       data[:, i] = (data[:, i] - minimums[i]) / (maximums[i] - minimums[i])

   test_data = data[offset:]
   print('training_data.shape={},test_data.shape={}'.format(training_data.shape, test_data.shape))
   return training_data, test_data

歸一化處理常用于預處理階段,將數據縮放到一個固定的范圍內。
maximums, minimums = training_data.max(axis=0), training_data.min(axis=0)計算訓練集的每個特征列的最大值和最小值。這里使用max(axis=0)和min(axis=0)來分別計算每列的最大值和最小值,得到一個包含每個特征列最大和最小值的一維數組。

for i in range(13):遍歷數據的每個特征列。(data[:, i] - minimums[i]) / (maximums[i] - minimums[i])進行歸一化處理。對于每個特征列,將該列中的每個數據減去最小值,然后除以最大值和最小值的差。這樣可以將每個數據映射到0和1之間的范圍。data[:, i]表示取數據矩陣data的第i列。

通過將數據進行歸一化處理,可以避免特征之間的尺度差異對機器學習算法的影響。不同的特征往往具有不同的取值范圍,而歸一化可以將所有特征縮放到相同的尺度上,使得特征之間具有可比性,并且有助于算法更好地收斂和準確性能。
我們在看下歸一化之后的圖像,loss值下降的還是挺明顯的。

3.6 將訓練擴到所有的樣本

class Network(object):
   def __init__(self, num_of_weights):
       # 隨機產生w的初始值 為了保持程序每次運行結果的一致性,此處設置固定的隨機數種子
       np.random.seed(0)
       self.w = np.random.randn(num_of_weights, 1)
       self.b = 2

   def forward(self, x):
       z = np.dot(x, self.w) + self.b
       return z

   def loss(self, z, y):
       temp_l = (z - y) * (z - y)
       cost = np.mean(temp_l)
       return cost

   def update(self, gradient_w, gradient_b, eta=0.01):
       self.w = self.w - eta * gradient_w
       self.b = self.b - eta * gradient_b

   def train(self, x, y, iterations=100, eta=0.01):
       losses = []
       for i in range(iterations):
           z = self.forward(x)
           L = self.loss(z, y)
           gradient_w, gradient_b = self.gradient(x, y)
           self.update(gradient_w, gradient_b, eta)
           losses.append(L)
           if (i + 1) % 10 == 0:
               print('iter {}, loss {}'.format(i, L))
       return losses

   def gradient(self, x, y):
       z = self.forward(x)
       gradient_w = (z - y) * x
       gradient_w = np.mean(gradient_w, axis=0)
       # print('gradient_w={},gradient_w shape {}'.format(gradient_w, gradient_w.shape))
       gradient_w = gradient_w[:, np.newaxis]
       # print('gradient_w={},gradient_w shape {}'.format(gradient_w, gradient_w.shape))
       gradient_b = (z - y)
       gradient_b = np.mean(gradient_b)
       return gradient_w, gradient_b


if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   x = train_data[:, :-1]
   y = train_data[:, -1:]
   # 創建網絡
   net = Network(13)
   num_iterations = 1000
   # 啟動訓練
   losses = net.train(x, y, iterations=num_iterations, eta=0.01)

   # 畫出損失函數的變化趨勢
   plot_x = np.arange(num_iterations)
   plot_y = np.array(losses)
   plt.plot(plot_x, plot_y)
   plt.show()

loss值變得更低了。

3.7 隨機梯度下降法

每次損失函數和梯度計算都是基于數據集中的全量數據。對于波士頓房價預測任務數據集而言,樣本數比較少,只有404個。但在實際問題中,數據集往往非常大,如果每次都使用全量數據進行計算,效率非常低。

一個合理的解決方案是每次從總的數據集中隨機抽取出小部分數據來代表整體,基于這部分數據計算梯度和損失來更新參數,這種方法被稱作隨機梯度下降法(Stochastic Gradient Descent,SGD),核心概念如下:
minibatch:每次迭代時抽取出來的一批數據被稱為一個minibatch。
batch size:每個minibatch所包含的樣本數目稱為batch size。
Epoch:當程序迭代的時候,按minibatch逐漸抽取出樣本,當把整個數據集都遍歷到了的時候,則完成了一輪訓練,也叫一個Epoch(輪次)。啟動訓練時,可以將訓練的輪數num_epochs和batch_size作為參數傳入。


if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   print("train_data.shape=", train_data.shape)

   #暫定batch_size = 10
   # 第1個batch
   train_data1 = train_data[0:10]
   # 第2個batch
   train_data2 = train_data[10:20]
   # 第3個batch
   train_data3 = train_data[20:30]

   net = Network(13)
   x = train_data1[:, :-1]
   y = train_data1[:, -1:]
   loss = net.train(x, y, iterations=1, eta=0.01)
   print("train_data1 loss", loss)
   x = train_data2[:, :-1]
   y = train_data2[:, -1:]
   loss = net.train(x, y, iterations=1, eta=0.01)
   print("train_data2 loss", loss)
   x = train_data3[:, :-1]
   y = train_data3[:, -1:]
   loss = net.train(x, y, iterations=1, eta=0.01)
   print("train_data3 loss", loss)

按此方法不斷的取出新的minibatch,將train_data分成大小為batch size=10的多個minibatch

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   print("train_data.shape=", train_data.shape)

   batch_size = 10
   n = len(train_data)
   mini_batches = [train_data[k:k + batch_size] for k in range(0, n, batch_size)]
   print('total number of mini_batches is ', len(mini_batches))
   print('first mini_batch shape ', mini_batches[0].shape)
   print('last mini_batch shape ', mini_batches[-1].shape)


404條數據,共計41個batch,最后一個batch中數據有4條。
通過大量實驗發現,模型對最后出現的數據印象更加深刻。訓練數據導入后,越接近模型訓練結束,最后幾個批次數據對模型參數的影響越大。為了避免模型記憶影響訓練效果,需要進行樣本亂序操作。

# 打亂樣本順序
np.random.shuffle(train_data)

全部的訓練代碼如下:

class Network(object):
   def __init__(self, num_of_weights):
       # 隨機產生w的初始值 為了保持程序每次運行結果的一致性,此處設置固定的隨機數種子
       np.random.seed(0)
       self.w = np.random.randn(num_of_weights, 1)
       self.b = 2

   def forward(self, x):
       z = np.dot(x, self.w) + self.b
       return z

   def loss(self, z, y):
       temp_l = (z - y) * (z - y)
       cost = np.mean(temp_l)
       return cost

   def update(self, gradient_w, gradient_b, eta=0.01):
       self.w = self.w - eta * gradient_w
       self.b = self.b - eta * gradient_b

   def train(self, training_data, num_epochs, batch_size=10, eta=0.01):
       n = len(training_data)
       losses = []
       # 第一層循環,確定訓練多少個epoch
       for epoch_id in range(num_epochs):
           # 在每輪迭代開始之前,將訓練數據的順序隨機打亂
           np.random.shuffle(training_data)
           # 將訓練數據進行拆分,每個mini_batch包含batch_size條的數據
           mini_batches = [training_data[k:k + batch_size] for k in range(0, n, batch_size)]
           # 第二層循環,每個batch中的多條數據送給模型
           for iter_id, mini_batch in enumerate(mini_batches):
               # print(self.w.shape)
               # print(self.b)
               x = mini_batch[:, :-1]
               y = mini_batch[:, -1:]
               a = self.forward(x)
               loss = self.loss(a, y)
               gradient_w, gradient_b = self.gradient(x, y)
               self.update(gradient_w, gradient_b, eta)
               losses.append(loss)
               print('Epoch {:3d} / iter {:3d}, loss = {:.4f}'.
                     format(epoch_id, iter_id, loss))

       return losses

   def gradient(self, x, y):
       z = self.forward(x)
       gradient_w = (z - y) * x
       gradient_w = np.mean(gradient_w, axis=0)
       # print('gradient_w={},gradient_w shape {}'.format(gradient_w, gradient_w.shape))
       gradient_w = gradient_w[:, np.newaxis]
       # print('gradient_w={},gradient_w shape {}'.format(gradient_w, gradient_w.shape))
       gradient_b = (z - y)
       gradient_b = np.mean(gradient_b)
       return gradient_w, gradient_b


if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   print("train_data.shape=", train_data.shape)

   # 創建網絡
   net = Network(13)
   # 啟動訓練
   losses = net.train(train_data, num_epochs=50, batch_size=100, eta=0.1)

   # 畫出損失函數的變化趨勢
   plot_x = np.arange(len(losses))
   plot_y = np.array(losses)
   plt.plot(plot_x, plot_y)
   plt.show()

第一層循環,代表樣本要被訓練epoch次。第二層的循環,代表每個epoch中的樣本被拆分成多個批次送到網絡結構中,成為迭代。最后觀察一下結果。可以看到有一定的尖刺產生,但是總體的趨勢是下降的。

使用pytorch訓練模型

4.1 訓練步驟

使用pytorch訓練模型一般有如下關鍵的4步驟。


首先定義Net類,繼承torch.nn.Module,添加一個線性層

class Net(torch.nn.Module):
   def __init__(self, n_feature, n_output):
       super(Net, self).__init__()
       self.predict = torch.nn.Linear(n_feature, n_output)

   def forward(self, x):
       out = self.predict(x)
       return out

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   print("train_data.shape=", train_data.shape)

   X_train = train_data[:, :-1]
   Y_train = train_data[:, -1:]
   net = Net(13, 1)
   # loss
   # 采用均方損失
   loss_func = torch.nn.MSELoss()

   # optimizer
   optimizer = torch.optim.SGD(net.parameters(), lr=0.0001)
   losses = []
   for i in range(4000):
       x_data = torch.tensor(X_train, dtype=torch.float32)
       y_data = torch.tensor(Y_train, dtype=torch.float32)
       pred = net.forward(x_data)
       pred = torch.squeeze(pred)
       loss = loss_func(pred, y_data)
       losses.append(loss)

       optimizer.zero_grad()
       loss.backward()
       optimizer.step()

       # 打印迭代次數和loss的變化
       print("ite:{}, loss_train:{}".format(i, loss))
       print(pred[0:10])
       print(y_data[0:10])
   # 畫出損失函數的變化趨勢
   plot_x = np.arange(len(losses))
   plot_y = np.array(losses)
   plt.plot(plot_x, plot_y)
   plt.show()

4.2 模型的保存與加載

   torch.save(net, "model.bin")
   net = torch.load("model.bin")

4.3 推理

if __name__ == '__main__':
   train_data, test_data = load_boston_house_data()
   print("train_data.shape=", train_data.shape)
   net = Net(13, 1)
   net.train(train_data)
   torch.save(net, "model.bin")

   # 推理
   X_test = test_data[:, :-1]
   Y_test = test_data[:, -1:]
   net = torch.load("model.bin")
   loss_func = torch.nn.MSELoss()
   # test
   x_data = torch.tensor(X_test, dtype=torch.float32)
   y_data = torch.tensor(Y_test, dtype=torch.float32)
   pred = net.forward(x_data)
   pred = torch.squeeze(pred)
   loss_test = loss_func(pred, y_data) * 0.001
   print("test loss_test:{}".format(loss_test))
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,156評論 6 529
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,866評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 174,880評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,398評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,202評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,743評論 1 320
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,822評論 3 438
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 41,962評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,476評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,444評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,579評論 1 365
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,129評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,840評論 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,231評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,487評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,177評論 3 388
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,568評論 2 370

推薦閱讀更多精彩內容