波士顿房价预测是一个经典案例,类似于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))