多类别分类
一、模型建立
对于二元分类的数据模型如下;
使用两种符号表示两个不同的数据集。
对于二元分类,上一篇博客已经给出分析:二元分类原理分析,及代码实现过程
运用逻辑回归,很好地实现了分类的功能。
对于一个多分类问题,以三元分类为例,建立数据模型如下,
使用三种符号,表示三种不同类别的数据集。
现在已经知道如何进行二元分类,可以使用逻辑回归,对于直线,同时可以将数据集一分为二为正类和负类。用一对多的分类思想,我们可以将其用在多类分类问题上。有时这个方法也被称为"一对余"(one-vs-rest)方法。
二、一对多分类方法(one-vs-all)
以三元分类为例,有一个训练集,好比上图表示的有3 个类别,我们用三角形表示 ? = 1,方框表示? = 2,叉叉表示 ? = 3。通过使用一个训练集,将其分成3 个二元分类问题。
先从用三角形代表的类别1 开始,实际上可以创建一个,新的"伪"训练集,类型2 和类型3 定为负类,类型1 设定为正类,创建一个新的训练集,如下图所示的那样,要拟合出一个合适的分类器。
这里的三角形是正样本,而圆形代表负样本。可以这样想,设置三角形的值为1,圆形的值为0,下面来训练一个标准的逻辑回归分类器,这样我们就得到一个正边界。为了能实现这样的转变,我们将多个类中的一个类标记为正向类(? = 1),然后将其他所有类都标记为负向类,这个模型记作
h
θ
1
(
x
)
h_{\theta1}(x)
hθ1(x);接着,类似地选择另一个类标记为正向类(? = 2),再将其它类都标记为负向类,将这个模型记作
h
θ
2
(
x
)
h_{\theta2}(x)
hθ2(x);依此类推。
最后得到一系列的模型简记为:
最后,在需要做预测时,将所有的分类机都运行一遍,然后对每一个输入变量,都选择最高可能性的输出变量。
三、分类器实现
以数字字迹识别为例,讨论多类别分类问题。
1,加载数据集(Dateset),可视化
首先,加载数据集。这里的数据为MATLAB的格式,所以要使用SciPy.io的loadmat函数。
def load_data(path):
data = loadmat(path)
X = data['X']
y = data['y']
return X,y
X, y = load_data('ex3data1.mat')
print(np.unique(y)) # 看下有几类标签
# [ 1 2 3 4 5 6 7 8 9 10]
X.shape, y.shape
# ((5000, 400), (5000, 1))
控制台输出数据中的类别
其中有5000个训练样本,每个样本是20*20像素的数字的灰度图像。每个像素代表一个浮点数,表示该位置的灰度强度。20×20的像素网格被展开成一个400维的向量。在我们的数据矩阵X中,每一个样本都变成了一行,这给了我们一个5000×400矩阵X,每一行都是一个手写数字图像的训练样本。
由Y数组,查看得到数据集分为,[1,2,3,4,5,6,7,8,9,10]十个类别。
随机查看1个训练样本,查看100个训练样本。
def plot_an_image(X):
"""
随机打印一个数字
"""
pick_one = np.random.randint(0, 5000)
image = X[pick_one, :]
fig, ax = plt.subplots(figsize=(1, 1))
ax.matshow(image.reshape((20, 20)), cmap='gray_r')
plt.xticks([]) # 去除刻度,美观
plt.yticks([])
plt.show()
print('this should be {}'.format(y[pick_one]))
def plot_100_image(X):
"""
随机画100个数字
"""
sample_idx = np.random.choice(np.arange(X.shape[0]), 100) # 随机选100个样本
sample_images = X[sample_idx, :] # (100,400)
fig, ax_array = plt.subplots(nrows=10, ncols=10, sharey=True, sharex=True, figsize=(8, 8))
for row in range(10):
for column in range(10):
ax_array[row, column].matshow(sample_images[10 * row + column].reshape((20, 20)),
cmap='gray_r')
plt.xticks([])
plt.yticks([])
plt.show()
2,向量化逻辑回归
使用多个one-vs-all(一对多)logistic回归模型来构建一个多类别分类器。由于有10个类,需要训练10个独立的分类器。为了提高训练效率,重要的是向量化。
2.1向量化正则化的代价函数
正则化的logistic回归的代价函数是:
J
(
θ
)
=
−
1
m
[
∑
i
=
1
m
y
(
i
)
log
(
h
θ
(
x
(
i
)
)
−
(
1
−
y
(
i
)
)
log
(
1
−
h
θ
(
x
(
i
)
)
)
]
+
λ
2
m
∑
j
=
1
n
θ
j
2
J(\theta)=-\frac{1}{m}[\sum_{i=1}^{m}y^{(i)}\log(h_\theta(x^{(i)})-(1-y^{(i)})\log(1-h_\theta(x^{(i)}))]+\frac{\lambda}{2m}\sum_{j=1}^{n}\theta_j^2
J(θ)=−m1[∑i=1my(i)log(hθ(x(i))−(1−y(i))log(1−hθ(x(i)))]+2mλ∑j=1nθj2
对于每个样本
i
i
i要计算,
h
θ
(
x
(
i
)
)
h_\theta(x^{(i)})
hθ(x(i)),
h
θ
(
x
(
i
)
)
=
g
(
θ
T
x
(
i
)
)
h_\theta(x^{(i)})=g(\theta^Tx^{(i)})
hθ(x(i))=g(θTx(i)),
g
(
z
)
g(z)
g(z)是sigmoid函数
事实上我们可以对所有的样本用矩阵乘法来快速的计算。
定义X,与
θ
\theta
θ
通过计算X
θ
\theta
θ
在最后一个等式中,我们用到了一个定理,如果
a
a
a和
b
b
b都是向量,那么
a
T
b
=
b
T
a
a^Tb=b^Ta
aTb=bTa 这样我们就可以用一行代码计算出所有的样本。
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def regularized_cost(theta, X, y, l):
"""
don't penalize theta_0
args:
X: feature matrix, (m, n+1) # 插入了x0=1
y: target vector, (m, )
l: lambda constant for regularization
"""
thetaReg = theta[1:]
first = (-y*np.log(sigmoid(X@theta))) + (y-1)*np.log(1-sigmoid(X@theta))
reg = (thetaReg@thetaReg)*l / (2*len(X))
return np.mean(first) + reg
2.2向量化梯度
未正则化逻辑回归代价函数的梯度为:
∂
∂
/
θ
j
J
(
θ
)
=
−
1
m
∑
i
=
1
m
(
h
θ
(
x
(
i
)
)
−
y
(
i
)
)
x
j
(
i
)
\frac{\partial }{\partial /\theta_j}J(\theta)=-\frac{1}{m}\sum_{i=1}^{m} (h_\theta(x^{(i)})-y^{(i)})x_j^{(i)}
∂/θj∂J(θ)=−m1∑i=1m(hθ(x(i))−y(i))xj(i)
使用向量化的方法表示
其中,
注意,
h
θ
(
x
(
i
)
)
−
y
(
i
)
h_\theta(x^{(i)})-y^{(i)}
hθ(x(i))−y(i)是一个标量,令
β
i
=
h
θ
(
x
(
i
)
)
−
y
(
i
)
\beta_i=h_\theta(x^{(i)})-y^{(i)}
βi=hθ(x(i))−y(i)
得到:
正则化后的梯度下降算法表示如下,不惩罚
θ
0
\theta_0
θ0
代码实现
def regularized_gradient(theta, X, y, l):
"""
don't penalize theta_0
args:
l: lambda constant
return:
a vector of gradient
"""
thetaReg = theta[1:]
first = (1 / len(X)) * X.T @ (sigmoid(X @ theta) - y)
# 这里人为插入一维0,使得对theta_0不惩罚,方便计算
reg = np.concatenate([np.array([0]), (l / len(X)) * thetaReg])
return first + reg
3,一对多分类器(one-vs-all)
这部分通过训练多个正则化logistic回归分类器实现一对多分类,每个分类器对应数据集中K类中的一个。
对于这个任务,我们有10个可能的类,并且由于logistic回归只能一次在2个类之间进行分类,每个分类器在“类别 i”和“不是 i”之间决定。 我们将把分类器训练包含在一个函数中,该函数计算10个分类器中的每个分类器的最终权重,并将权重返回shape为(k, (n+1))数组,其中 n 是参数数量。
from scipy.optimize import minimize
def one_vs_all(X, y, l, K):
"""generalized logistic regression
args:
X: feature matrix, (m, n+1) # with incercept x0=1
y: target vector, (m, )
l: lambda constant for regularization
K: numbel of labels
return: trained parameters
"""
all_theta = np.zeros((K, X.shape[1])) # (10, 401)
for i in range(1, K+1):
theta = np.zeros(X.shape[1])
y_i = np.array([1 if label == i else 0 for label in y])
ret = minimize(fun=regularized_cost, x0=theta, args=(X, y_i, l), method='TNC',
jac=regularized_gradient, options={'disp': True})
all_theta[i-1,:] = ret.x
return all_theta
这里需要注意的几点:首先,我们为X添加了一列常数项 1 ,以计算截距项(常数项)。 其次,我们将y从类标签转换为每个分类器的二进制值(要么是类i,要么不是类i)。 最后,我们使用SciPy的较新优化API来最小化每个分类器的代价函数。 如果指定的话,API将采用目标函数,初始参数集,优化方法和jacobian(渐变)函数。 然后将优化程序找到的参数分配给参数数组。
4,One-vs-all Prediction
在训练多类别分类器之后,现在可以用它来预测给定图像中包含的数字。对于每个输入,您应该使用以下方法计算它属于每个类的“概率” 训练的Logistic回归分类器。One-vs-all预测函数将选择相应的逻辑回归分类器输出概率最高的类和ret。 将类标签(1、2、…或K)作为输入示例的预测。
实现向量化代码的一个更具挑战性的部分是正确地写入所有的矩阵,保证维度正确。
def predict_all(X, all_theta):
# compute the class probability for each class on each training instance
h = sigmoid(X @ all_theta.T) # 注意的这里的all_theta需要转置
# create array of the index with the maximum probability
# Returns the indices of the maximum values along an axis.
h_argmax = np.argmax(h, axis=1)
# because our array was zero-indexed we need to add one for the true label prediction
h_argmax = h_argmax + 1
return h_argmax
这里的h共5000行,10列,每行代表一个样本,每列是预测对应数字的概率。我们取概率最大对应的index加1就是我们分类器最终预测出来的类别。返回的h_argmax是一个array,包含5000个样本对应的预测值。
raw_X, raw_y = load_data('ex3data1.mat')
X = np.insert(raw_X, 0, 1, axis=1) # (5000, 401)
y = raw_y.flatten() # 这里消除了一个维度,方便后面的计算 or .reshape(-1) (5000,)
all_theta = one_vs_all(X, y, 1, 10)
all_theta # 每一行是一个分类器的一组参数
y_pred = predict_all(X, all_theta)
accuracy = np.mean(y_pred == y)
print ('accuracy = {0}%'.format(accuracy * 100))
通过预测的y值与实际y值进行比对,得出结果在这次练习中的多类别分类器的准确率是0.9446。