import numpy as np
from sklearn import datasets, linear_model
import matplotlib.pyplot as plt
class Config:
nn_input_dim = 2
nn_output_dim = 2
epsilon = 0.01
reg_lambda = 0.01
Config 类定义了神经网络的一些配置参数。以下是每个参数的解释:
nn_input_dim: 这个参数表示神经网络的输入层的维度。
nn_output_dim: 这个参数表示神经网络的输出层的维度。
epsilon: 梯度下降的学习率。
reg_lambda: 正则化强度。正则化是一种防止过拟合的技术,它通过在损失函数中添加一个额外的
项来惩罚过于复杂的模型。正则化强度决定了这个惩罚项的权重。较大的正则化强度意味着模型更
倾向于简单,较小的正则化强度意味着模型可能更复杂。选择合适的正则化强度同样非常重要。
def generate_data():
np.random.seed(0)
X, y = datasets.make_moons(200, noise=0.20)
return X, y
函数首先设置随机数生成器的种子为0,确保每次运行时生成的数据集都相同。接下来,使用
datasets.make_moons
方法生成一个具有两个半月形状(moons)的数据集。这个方法可以生成一
个具有两个半月形状分布的非线性可分的数据集,通常用于测试分类器的性能。
np.random.seed(0)
: 设置随机数生成器的种子为0。这样可以确保每次生成的数据集相同,有
助于实验的可重复性。
datasets.make_moons(200, noise=0.20)
: 使用make_moons
方法生成一个包含200个数据点的
数据集。其中,noise
参数设置为0.20,表示在原始的半月形状上添加一定程度的噪声。这会使数
据集的边界更加复杂。
其中X
是一个形状为(200, 2)的数组,代表200个数据点的坐标;y
是一个形状为(200,)的数组,
代表每个数据点对应的类别标签(0或1)。
def visualize(X, y, model):
# plt.scatter(X[:, 0], X[:, 1], s=40, c=y, cmap=plt.cm.Spectral)
# plt.show()
plot_decision_boundary(lambda x:predict(model,x), X, y)
def plot_decision_boundary(pred_func, X, y):
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
h = 0.01
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
plt.title("Neural Network")
plt.show()
x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
和 y_min, y_max =
X[:,1].min() - .5, X[:, 1].max() + .5
: 这两行代码确定了x坐标和y坐标的最小值和最大值,并
在这个范围外额外添加了0.5的边距。这样做是为了在绘制决策边界时,可以超出数据点的最小和
最大坐标,使图像更加美观。
h = 0.01
: 这行代码设置了生成网格点的间隔为0.01。
np.meshgrid
函数生成成了一个在指定范围内、间隔为h
的网格点。
pred_func
使用预测函数pred_func
对网格点进行预测。
np.c_[xx.ravel(), yy.ravel()]
将网格点从网格形式转换为[x_coordinate,
y_coordinate]
的列表形式,然后应用预测函数。
Z.reshape(xx.shape)
: 这行代码将预测结果从列表形式转换回网格形式,以便于后面的
绘制操作。
plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
: 这行代码绘制了决策边界。
plt.contourf
是一个用于绘制等高线图的函数,这里用它来表示预测结果的变化趋势,由此可以观
察模型的决策边界。
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
: 这行代码绘制了数据点。
与之前的visualize
函数中相同,X[:, 0]
和X[:, 1]
表示数据点的x和y坐标,c=y
表示点的颜色由类
别标签y
确定,cmap=plt.cm.Spectral
表示使用Spectral颜色映射。
def calculate_loss(model, X, y):
num_examples = len(X) # training set size
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
# Forward propagation to calculate our predictions
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
# Calculating the loss
corect_logprobs = -np.log(probs[range(num_examples), y])
data_loss = np.sum(corect_logprobs)
# Add regulatization term to loss (optional)
data_loss += Config.reg_lambda / 2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
return 1. / num_examples * data_loss
num_examples = len(X)
: 计算训练集的大小(数据点的数量)。
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
: 从输入的模型
参数中提取权重矩阵W1
、W2
和偏置项b1
、b2
。
z1 = X.dot(W1) + b1
和 a1 = np.tanh(z1)
: 执行前向传播的第一层。X.dot(W1)
执行输入层
到隐藏层的加权求和,然后加上偏置项b1
。将结果z1
传递给激活函数tanh
,得到隐藏层的激活值
a1
。
z2 = a1.dot(W2) + b2
和 exp_scores = np.exp(z2)
: 前向传播的第二层。a1.dot(W2)
执行隐
藏层到输出层的加权求和,然后加上偏置项b2
。将结果z2
传递给softmax
激活函数之前,先计算z2
中每个元素的指数值。
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
: 将上一步得到的指数
值归一化为概率值。这实际上是应用softmax
激活函数的最后一步,使得probs
中的每一行和为1。
corect_logprobs = -np.log(probs[range(num_examples), y])
和 data_loss =
np.sum(corect_logprobs)
: 计算模型在每个数据点上产生的交叉熵损失。首先计算每个数据点对应
正确类别的概率的负对数(即,交叉熵损失),然后将这些损失相加,得到整个数据集的总损失。
data_loss += Config.reg_lambda / 2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
: 添加了一个正则化项。正则化项是可选的,用于防止模型过拟合。这里
采用的是L2正则化,将权重矩阵W1
和W2
的平方和乘以正则化系数Config.reg_lambda
,再除以2,
然后加到data_loss
中。
最后,计算平均损失并返回。将总损失除以训练集的大小,得到每个数据点上的平均损失。
def predict(model, x):
W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
# Forward propagation
z1 = x.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
return np.argmax(probs, axis=1)
从输入的模型参数中提取权重矩阵W1
、W2
和偏置项b1
、b2
。
z1 = x.dot(W1) + b1
和 a1 = np.tanh(z1)
: 执行前向传播的第一层。x.dot(W1)
执行输入层
到隐藏层的加权求和,然后加上偏置项b1
。将结果z1
传递给激活函数tanh
,得到隐藏层激活值a1
。
z2 = a1.dot(W2) + b2
和 exp_scores = np.exp(z2)
: 执行前向传播的第二层。a1.dot(W2)
执
行隐藏层到输出层的加权求和,然后加上偏置项b2
。将结果z2
传递给softmax
激活函数之前,先计
算z2
中每个元素的指数值。
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
: 这行代码将上一步得
到的指数值归一化为概率值。这实际上是应用softmax
激活函数的最后一步,使得probs
中的每一行
的和为1。
最后,这行代码返回每一行概率值中最大值的索引,即预测的类别标签。np.argmax
函数在指
定轴(这里是axis=1
,即横轴)上找到最大值的索引。
def build_model(X, y, nn_hdim, num_passes=20000, print_loss=False):
# Initialize the parameters to random values. We need to learn these.
num_examples = len(X)
np.random.seed(0)
W1 = np.random.randn(Config.nn_input_dim, nn_hdim) / np.sqrt(Config.nn_input_dim)
b1 = np.zeros((1, nn_hdim))
W2 = np.random.randn(nn_hdim, Config.nn_output_dim) / np.sqrt(nn_hdim)
b2 = np.zeros((1, Config.nn_output_dim))
# This is what we return at the end
model = {}
# Gradient descent. For each batch...
for i in range(0, num_passes):
# Forward propagation
z1 = X.dot(W1) + b1
a1 = np.tanh(z1)
z2 = a1.dot(W2) + b2
exp_scores = np.exp(z2)
probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
# Backpropagation will be teached in the next lession
delta3 = probs
delta3[range(num_examples), y] -= 1
dW2 = (a1.T).dot(delta3)
db2 = np.sum(delta3, axis=0, keepdims=True)
delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
dW1 = np.dot(X.T, delta2)
db1 = np.sum(delta2, axis=0)
# Add regularization terms (b1 and b2 don't have regularization terms)
dW2 += Config.reg_lambda * W2
dW1 += Config.reg_lambda * W1
# Gradient descent parameter update
W1 += -Config.epsilon * dW1
b1 += -Config.epsilon * db1
W2 += -Config.epsilon * dW2
b2 += -Config.epsilon * db2
# Assign new parameters to the model
model = {'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}
# Optionally print the loss.
# This is expensive because it uses the whole dataset, so we don't want to do it too often.
if print_loss and i % 1000 == 0:
print("Loss after iteration %i: %f" % (i, calculate_loss(model, X, y)))
return model
函数开始时,初始化权重矩阵W1
、W2
和偏置项b1
、b2
。权重矩阵采用随机值初始化,除以输
入维度和隐藏层节点数的平方根,以减小初始值规模。偏置项初始化为零。
创建模型字典:创建一个空字典model
,用于存储模型参数。在梯度下降过程中,我们会不断
更新这些参数。
梯度下降:通过num_passes
次迭代执行梯度下降算法。在每次迭代中,我们进行以下步骤:
前向传播:计算输入层到隐藏层和隐藏层到输出层的加权求和,应用激活函数,并计算概
率。反向传播:计算梯度(偏导数),以便更新权重矩阵和偏置项。这里的反向传播算法用于计算
损失函数关于模型参数的梯度。
添加正则化项:将权重矩阵W1
和W2
的正则化项添加到梯度中(偏置项没有正则化项)。
梯度下降参数更新:使用梯度更新权重矩阵和偏置项。乘以学习率(Config.epsilon
),然
后从当前参数值中减去梯度。将更新后的参数赋值给模型字典。
若print_loss
参数为True
,则每1000次迭代打印一次损失值。这个操作相对昂贵,因为它需
要计算整个数据集上的损失值。返回训练好的模型。
def main():
X, y = generate_data()
model = build_model(X, y, 3, print_loss=True)
visualize(X, y, model)
if __name__ == "__main__":
main()