声明:图片均来自网络,若有侵权,联系本人后立即删除
1. 定义
-
假设输入空间(特征空间)是 χ ⊆ R n \chi \subseteq\mathsf{R^n} χ⊆Rn,输出空间是 γ = { + 1 , − 1 } \gamma =\{+1,-1\} γ={+1,−1}。输入我们 x ∈ χ x\in\chi x∈χ表示实例的特征向量,对应于输入空间(特征空间)的点:输出 y ∈ γ y\in\gamma y∈γ表示实例的类别。由输入空间到输出空间的如下函数:
f ( x ) = s i g n ( w ⋅ x + b ) f(x) = sign(w·x +b) f(x)=sign(w⋅x+b)
称为感知机。其中,
s i g n ( x ) = { + 1 , x ≥ 0 − 1 , x < 0 sign(x) = \begin{cases} +1,\quad x\ge0 \\ -1, \quad x<0 \end{cases} sign(x)={+1,x≥0−1,x<0 -
感知机模型对应于输入空间(特征空间)中的分离超平面 w ⋅ x + b = 0 w·x+b=0 w⋅x+b=0。
2. 感知机学习算法
- 输入:训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T = \{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)},其中 x i ∈ χ = R n x_i\in\chi=\mathsf{R^n} xi∈χ=Rn, y i ∈ γ = { − 1 , + 1 } y_i\in\gamma=\{-1,+1\} yi∈γ={−1,+1}, i = 1 , 2 , . . . , N \ i=1,2,...,N i=1,2,...,N; 学习率 η ( 0 < η ≤ 1 ) \eta(0<\eta\le1) η(0<η≤1);
- 输出: w , b w,b w,b:感知机模型 f ( x ) = s i g n ( w ⋅ x + b ) f(x)=sign(w·x+b) f(x)=sign(w⋅x+b)。
- 选取初值 w 0 , b 0 w_0,b_0 w0,b0。
- 在训练集中选取数据 ( x i , y i ) (x_i,y_i) (xi,yi)
- 如果 y i ( w ⋅ x i + b ) ≤ 0 y_i(w·x_i +b)\le0 yi(w⋅xi+b)≤0
w ← w + η y i x i b ← b + η y i w\gets w + \eta y_i x_i\\ b\gets b+ \eta y_i \quad\ w←w+ηyixib←b+ηyi- 跳至(2),直至训练集中没有误分类点。
注意:
- 该算法采用的随机梯度下降法(SGD, stochastic gradient descent)。经本人实现发现,如果采用批梯度下降(mini-batch GD),该算法的时间效率和精确度都会下降,所以个人建议还是用SGD更加靠谱。
- 感知机学习的策略是极小化损失函数:
min
w
,
b
L
(
w
,
b
)
=
−
∑
x
i
∈
M
y
i
(
w
⋅
x
i
+
b
)
\mathop{\min_{w,b}}L(w,b)=-\sum_{x_i\in M} y_i(w·x_i+b)
w,bminL(w,b)=−xi∈M∑yi(w⋅xi+b)
损失函数对应于误分类点到分离超平面的总距离。
3. 数据集介绍
关于数据集,参考的是wds2006sdo写的感知机实现的博文中的MINST数据集。MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片:
由于我们上述描述的感知机只能进行二分类,所以用该数据集中专门为了训练二分类问题的数据集来训练我们的感知机模型。
4. 特征
为了训练我们的感知机,首先要确定样本的特征,对于该问题,我们既可以用整张图像作为特征,也可以用图像的HOG特征来进行训练。具体的图像的HOG特征参考的是李航《统计学习方法》第二章——用Python实现感知器模型(MNIST数据集)。
具体的提取HOG特征的代码会在下面呈现,除此之外我们还要一个hog.xml文件来保存hog的配置信息,如下所示:
<?xml version="1.0"?>
<opencv_storage>
<hog type_id="opencv-object-detector-hog">
<winSize>
28 28</winSize>
<blockSize>
14 14</blockSize>
<blockStride>
7 7</blockStride>
<cellSize>
7 7</cellSize>
<nbins>9</nbins>
<derivAperture>1</derivAperture>
<winSigma>4.</winSigma>
<histogramNormType>0</histogramNormType>
<L2HysThreshold>2.0000000000000001e-001</L2HysThreshold>
<gammaCorrection>1</gammaCorrection>
<nlevels>64</nlevels></hog>
</opencv_storage>
5.代码实现
这里代码的基本框架也是参考的wds2006sdo,但是其中核心代码是自己写的或者在原来的基础上做了些改动。
代码用python实现。
import pandas as pd
import numpy as np
import cv2
import time
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
class Perceptron(object):
def __init__(self, learning_step=0.00001, max_iteration=5000, max_correct_count = 10000):
self.learning_step = learning_step
self.max_iteration = max_iteration
self.max_correct_count = max_correct_count
def train(self, features, labels):
# 初始化参数为0
labels_ = labels * 2 - 1
self.w = np.array([0.0] * features.shape[1])
self.b = 0.0
time = 0
correct_count = 0
while time < self.max_iteration:
time += 1
rand_idx = np.random.randint(0, len(labels))
img = features[rand_idx]
# 这里是min_batch GD的实现
# batch_idx = np.random.randint(0, features.shape[0], size=(512,))
# batch_features = features[batch_idx]
# batch_labels = labels_[batch_idx]
# y = np.sum(batch_features * self.w, axis=1) + self.b
# idx = np.where(y*batch_labels.reshape(-1, 1) <= 0)[0]
# delta_w = np.sum(batch_features[idx] * batch_labels[idx].reshape(-1, 1), axis=0)
# delta_b = np.sum(batch_labels[idx])
y = np.sum(img * self.w) + self.b
if y * labels_[rand_idx] > 0:
correct_count += 1
if correct_count > self.max_correct_count:
break
else:
continue
delta_w = features[rand_idx] * labels_[rand_idx]
delta_b = labels_[rand_idx]
delta = np.sum(np.abs(delta_w)) + np.abs(delta_b)
# 梯度下降更新参数
self.w = self.w + self.learning_step * delta_w
self.b = self.b + self.learning_step * delta_b
if time % int(self.max_iteration / 5) == 0:
print("Training iterations:", str(time))
def predict_(self, x):
y = np.sum(self.w * x) + self.b
return int(y > 0)
def predict(self, features):
labels = []
for feature in features:
labels.append(self.predict_(feature))
return labels
def get_hog_features(trainset):
features = []
hog = cv2.HOGDescriptor('../hog.xml')
for img in trainset:
img = np.reshape(img, (28, 28))
cv_img = img.astype(np.uint8)
hog_feature = hog.compute(cv_img)
features.append(hog_feature)
features = np.array(features)
features_np = np.reshape(features, (-1, 324))
return features_np
if __name__ == '__main__':
print("---start load data----")
time_1 = time.time()
raw_data = pd.read_csv('../data/train_binary.csv', header=0)
data = raw_data.values
imgs = data[:, 1:]
imgs_hog = get_hog_features(imgs)
labels = data[:, 0]
train_features, test_features, train_labels, test_labels = train_test_split(
imgs_hog, labels, test_size=0.33, random_state=23323)
time_2 = time.time()
print("read data cost:", time_2 - time_1, 'second', '\n')
print('----start training----')
p = Perceptron()
p.train(train_features, train_labels)
time_3 = time.time()
print("training cost:", time_3 - time_2, 'second', '\n')
print('----start predicting----')
test_predict = p.predict(test_features)
time_4 = time.time()
print("predicting cost", time_4 - time_3, 'second', '\n')
score = accuracy_score(test_labels, test_predict)
print('The accuracy score is: ', score)
6. 代码运行结果
在macbook pro上的运行结果如下:
代码实现需要注意的点:
- def train()中定义的correct_count变量是为了让模型能正确估计大部分数据时停下,如果不加这个变量,运行时间会更长,而且准确率会降低。
- def train()中定义的time是为了让训练足够久之后能够终止。
- 前面也说过,经过亲测,SGD不仅在时间上,而且在准确度上都完胜mini-batch GD,所以推荐用SGD。
参考文献
[1] 《统计学习方法》 李航
[2] wds2006sdo的博客