波士頓房價預測是一個經典案例,類似于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 使得上面 wx + 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))