GitHub
https://github.com/calciferer/LearnKeras
简介
- 通过一个简单的
一元线性回归
来学习Keras的使用 - 需求:定义一个keras模型,让模型拟合这个回归方程
y = 2 x + 1 y=2x+1 y=2x+1
直接使用python代码实现
1. 生成模拟数据
def gen_data(self):
"""
生成x,y数据。这里拟合一个线性函数 y = 2 * x + 1
"""
# 生成训练集数据
# x从0到10,生成101个
x_train = np.linspace(0, 10, 101)
y_train = x_train * 2 + 1
self.x_train = x_train
self.y_train = y_train
return x_train, y_train
- 数据集为:一共101个数
– x:
– y:
– 这里x和y满足 y = 2 x + 1 y=2x+1 y=2x+1
– 用图像表示
– 生成模拟数据的时候,也可以给每个y加上一点随机数,这样看起来更接近真实的情况。
2. 使用梯度下降开始训练
- 定义要学习的函数 y ^ = W x + b \hat{y}=Wx+b y^=Wx+b ,只有W和b两个标量参数
- 定义
单个
样本(x,y)的损失函数,这里采用MSE均方误差
J ( W , b ) = ( y ^ − y ) 2 = ( W x + b − y ) 2 J(W,b)=(\hat{y}-y)^2=(Wx+b-y)^2 J(W,b)=(y^−y)2=(Wx+b−y)2 -
J
(
W
,
b
)
J(W,b)
J(W,b)分别对W和b求偏导数
∂ J ∂ W = 2 x ⋅ ( W x + b − y ) \frac{\partial{J}}{\partial{W}}=2x\cdot(Wx+b-y) ∂W∂J=2x⋅(Wx+b−y)
∂ J ∂ b = 2 ⋅ ( W x + b − y ) \frac{\partial{J}}{\partial{b}}=2\cdot(Wx+b-y) ∂b∂J=2⋅(Wx+b−y) - 更新参数公式(a是学习率)
W = W − a ∂ J ∂ W W = W-a\frac{\partial{J}}{\partial{W}} W=W−a∂W∂J
b = b − a ∂ J ∂ b b = b -a\frac{\partial{J}}{\partial{b}} b=b−a∂b∂J - 开始训练
def train(self):
alpha = 0.01 # 学习率
epoch = 1 # 轮次
# 初始化W,b
W = 5.0
b = 2.0
for step in range(epoch):
print(f"开始第{step + 1}个epoch")
batch_size = 101 # batch大小
W_grad_sum= 0.0 # 每个样本点对W偏导数的和
b_grad_sum = 0.0 # 每个样本点对b偏导数的和
total_loss = 0.0 # 总loss
for index, (x, y) in enumerate(zip(self.x_train, self.y_train)):
total_loss += (W * x + b - y) ** 2
W_grad_sum += 2 * x * (W * x + b - y)
b_grad_sum += 2 * (W * x + b - y)
if (index + 1) % batch_size == 0 or index == len(self.x_train) - 1:
print(f"经过第{int(index / batch_size) + 1}次迭代,当前index={index}")
W -= alpha * (W_grad_sum / batch_size) # 梯度和除以样本数
b -= alpha * (b_grad_sum / batch_size) # 梯度和除以样本数
print(f'loss:{total_loss / batch_size},W={W},b={b}')
W_grad_sum = 0.0
b_grad_sum = 0.0
total_loss = 0.0
# 训练完成
print(f"W:{W},b:{b}")
# 获取模型的预测值
y_pre = W * self.x_train + b
# 绘制train散点图
plt.plot(lr.x_train, lr.y_train, 'b.', markersize=1)
plt.plot(lr.x_train, y_pre, 'r-')
plt.show()
BatchSize的说明
– 在这个例子中,样本大小为101:
如果BatchSize设置为1,那么就属于SGD
随机梯度下降,取得每个样本都进行一次梯度下降,更新一次W, b
如果BatchSize设置为101,那么就属于BGD
批量梯度下降,每个epoch只进行一次梯度下降
如果BatchSize设置为1至101中的某个数,比如30,那么就属于MBGD(Mini-Batch)小批量梯度下降
,每经过指定的batchSize,进行一次梯度下降,每遍历batchSize个样本,称为一次iteration(迭代)
。- 设置BatchSize=30,epoch=1的训练结果
开始第1个epoch
经过第1次迭代,当前index=29
loss:35.365,W=4.7999,b=1.893
经过第2次迭代,当前index=59
loss:184.1637721688501,W=3.5695707699999994,b=1.6259489
经过第3次迭代,当前index=89
loss:153.60955990535018,W=1.710484948624333,b=1.37956387727
经过第4次迭代,当前index=100
loss:2.0640446086731177,W=1.8758650210476606,b=1.3969499574158581
W:1.8758650210476606,b:1.3969499574158581
使用Keras实现
- 使用keras后,4行代码就搞定了
model = Sequential()
# 添加一个全连接层,units=1表示该层有一个神经元,input_dim=1表示x_train中每个样本的维度为1,初始值W=5,b=2
model.add(Dense(units=1, input_dim=1, kernel_initializer=Constant(5), bias_initializer=Constant(2)))
# 定义优化算法为sgd:随机梯度下降,定义损失函数为mse:均方误差
model.compile(optimizer=SGD(learning_rate=0.01), loss='mse')
model.fit(x_train, y_train, epochs=1, batch_size=30, shuffle=False)
- 结果
Epoch 1/1
batch:0,para:[array([[5.]], dtype=float32), array([2.], dtype=float32)]
30/101 [=======>......................] - ETA: 0s - loss: 35.3650
batch:1,para:[array([[4.7999]], dtype=float32), array([1.893], dtype=float32)]
batch:2,para:[array([[3.569571]], dtype=float32), array([1.6259489], dtype=float32)]
batch:3,para:[array([[1.7104847]], dtype=float32), array([1.3795638], dtype=float32)]
101/101 [==============================] - 0s 654us/step - loss: 111.4463
W:[[2.1615217]],b:[1.4269804]
- 在batchSize=30时,结果
不同的原因
实际上,运行batch1,2,3后的结果是一样的
,keras在运行3个batch后,最后一个batch的大小为101-30*3=11,小于30,因此最后这11个样本被忽略了。但一般来说训练会设置多个epoch,和多个iteration,并且注意到model.fit
方法中有个shuffle
参数(它表示每次iteration后是否混洗数据集,默认是True),这样每个样本基本上都会被训练到,忽略这些对结果影响不大。 - 另外在进行随机梯度下降时,即BatchSize=1,epoch=1时得到的结果也有微小的差别
python的结果:W:2.07600474478807,b:0.236057629049279
keras的结果:W:[[2.0760052]],b:[0.23605807]
这是因为keras的浮点数据类型为float32
,python中默认为double