「这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战」。
先前我们考虑了单感知机训练算法的过程,现在我们完整的用代码实现一次。IRIS 数据集作为一个相当经典的分类数据集被许多的第三方库直接包含了。所以我们可以直接通过一些第三方库对数据进行加载,比如 scikit-learn 库。
我们可以使用它 dataset 模块中的 load_iris() 函数进行加载:
iris = load_iris()
X = np.c_[np.ones(100), iris.data[:100, 2:4]]
Y = iris.target[:100].reshape(100, 1)
Y[Y == 0] = -1
本身对于这个加载函数时可以使用解包获得数据和标签,不过保留 iris 变量也有好处,可以在后续用于获取一些额外的内容,例如标签对应的分类名称。
这里我们获取的是前100个数据,特征选取的是2,3列,而它对应的标签是 0 和 1 ,后续我们将使用符号函数 sign() 为此,需要将所有的 0 改成 -1 。
注意这里我们还让特征 X 额外添加了全为 1 的一列,这是为了像前文所述一般,将偏置 b 等价为与 1 相乘的权值,这样一并当作权值进行处理,无需再额外关注权值和偏置的区别。
由此我们将只有一个权值向量,我们可以直接将它初始化为全 1 的矩阵(随机初始化也一样)。并设定学习率为 0.1
W = np.array([[1],[1],[1]])
lr = 0.1
由于b和w统一考虑,以及设定了学习率,所以我们的训练函数,再稍微做些修改
def train(in_features, learn_rate, weight, label):
y_hat = np.sign(np.dot(in_features, weight))
error = label - y_hat
delta = learn_rate * (in_features.T.dot(error)) / in_features.shape[0]
return weight + delta
然后再考虑我们的训练流程,出于简单考虑,可以直接按顺序进行训练,所以我们可以实现一个数据生成器,用于每一轮的训练。
def data_iter(batch_size: int, features, labels):
indices = list(features.shape[0])
for i in range(0, features.shape[0], batch_size):
batch_indices = indices[i: min(i + batch_size, features.shape[0])]
yield features[batch_indices], labels[batch_indices]
对于训练而言,同样可以简单处理,一次对将整个数据集作为样本批量,尽管这不能算一个好的选择,但是感知机的特性是它绝对收敛,也就是可以接受的。
for epoch in range(1000):
for x, y in data_iter(100, X, Y):
W, y_hat = train(x, lr, W, y)
#if (y_hat == Y).all():
# 如果在整个样本集上都有该条件成立,那么就收敛了
如果有必要的话,我们可以在最后进行示意图的绘制,因为这个模型二维平面上画线,其两侧是数据。需要注意的点是其中直线的求解。它的斜率 k 和截距 b 需要我们手动求解。然后可以用它们求出两点用于绘图。散点图和折现图的叠加可以通过 zorder 关键字参数指定。