Softmax回归也称多项或多类的Logistic回归,是Logistic回归在多分类问题上的推广。
对于这样的一个数据分布,我们希望有一种方法能够将这组数据分成四类。
很显然,我们用四根直线就可以将这组数据划分开。
softmax回归与Logistic回归的不同之处就是softmax回归要面对的是两类以上的分类问题,一般的认为,要进行的分类问题有几种类型,就需要绘制几条直线,每条直线的作用是“将某一类与其他所有类区分开①”。
①所描述的正是一种一对多余的思想。
一对其余
把多分类问题转换为C个“一对其余”的二分类问题。这样的方式需要C个判别函数,C指需要区分的类别的个数。其中第c个判别函数fc是将类别c的数据样本和其他所有不属于c的数据样本分开。
一对一
把多分类问题转换为C(C-1)/2个“一对一”的二分类问题。这种方式需要C(C-1)/2个判别函数,其中第(i,j)个判别函数是把类别i和类别j的数据样本分开。
argmax
argmax是一种改进的“一对其余”方式,同样需要C个判别函数。
这C个判别函数的通式是:
f c ( x : w c ) = w c T x + b c , c ∈ { 1 , ⋯ , C } f_c(x:w_c) = w^T_cx+b_c , c∈\lbrace1,\cdots,C\rbrace fc(x:wc)=wcTx+bc,c∈{1,⋯,C}
对于一个样本x,如果存在一个类别c,相对于其他所有类别 c ~ \tilde{c} c~( c ~ \tilde{c} c~≠c)有 f c ( x ; w c ) f_c(x;w_c) fc(x;wc)>KaTeX parse error: Got function '\tilde' with no arguments as subscript at position 3: f_\̲t̲i̲l̲d̲e̲{c}(x;w\tilde{c… ,那么x就是属于类别c的。
对于函数 f c ( x ; w c ) f_c(x;w_c) fc(x;wc),我们可以理解为它表示样本点 x i x_i xi关于这C条直线(C个判别函数)的距离判别。
我们先看一下三种判别方式能得到的区分效果。
可以看到,无论是“一对其余”还是“一对一”方式,都会有盲区。而且“argmax”方式对前面两种方式做了改进,使得这些盲区内的数据也可以被区分。
我们可以将argmax方式的判别函数看成是欧式距离的计算,显然,即使是“盲区”内的数据点,这些数据点关于直线的欧式距离是一定存在的,所以它一定是可以被划分的,所以在一定程度上解决了这个问题。
Logistic回归输出的是一个范围为[0,1]的概率值,是因为Logistic回归面对的二分类问题,一个范围在[0,1]内的概率值可以明确的区分输出的结果是0类还是1类。
然而在softmax回归中,多类问题使分类结果的表示变得困难起来。
所以会对输出结果y进行one-hot编码,进行编码后使得这个结果y_hot可以清晰的表示结果所属的类别。
我们直观的拿一个例子:
假设四分类问题中分类为1,2,3,4四类,分类y=1经过one-hot编码后,得到的结果是[1,0,0,0]。
是的,它是一个数组,或者说是一个向量。
那么同样的,分类y=2经过one-hot编码后得到的结果是[0,1,0,0],以此类推。
当然,在实际进行参数训练后得到的结果不可能是这么精确的值,我们知道分类问题得到的都是一些概率值。
所以我们得到的值可能是y1 = [0.1,0.2,0.5,0.2],y2 = [0.7,0.1,0.1,0.1]…
不过结果依然很显然了,y1表示这个数据更趋向于3类,y2表示的数据更趋向于1类。
与Logistic回归不同的是激活函数,softmax回归用到的激活函数是softmax。
softmax和sigmoid函数的目的都是将 w 1 ∗ x 1 + w 2 ∗ x 2 + b w_1*x_1+w_2*x_2+b w1∗x1+w2∗x2+b得到的值进行一个归一化,将值压缩到区间(0,1)内,这样便于所有的值进行比较。
引入所需库
import numpy as np
import matplotlib.pyplot as plt
随机生成数据集
np.random.seed(0)
Num=100
#0类别
x1 = np.random.normal(-3,1,size=(Num))
x2 = np.random.normal(-3,1,size=(Num))
y= np.zeros(Num)
data0 = np.array([x_1,x_2,y])
#1类别
x1 = np.random.normal(3,1,size=(Num))
x2 = np.random.normal(-3,1,size=(Num))
y= np.ones(Num)
data1 = np.array([x_1,x_2,y])
#2类别
x1 = np.random.normal(-3,1,size=(Num))
x2 = np.random.normal(3,1,size=(Num))
y= np.ones(Num)*2
data2 = np.array([x_1,x_2,y])
#3类别
x1 = np.random.normal(3,1,size=(Num))
x2 = np.random.normal(3,1,size=(Num))
y= np.ones(Num)*3
data3 = np.array([x_1,x_2,y])
data0 = data0.T
data1 = data1.T
data2 = data2.T
data3 = data3.T
查看数据分布
plt.scatter(data0[:,0],data0[:,1],marker="o")
plt.scatter(data1[:,0],data1[:,1],marker="+")
plt.scatter(data2[:,0],data2[:,1],marker="v")
plt.scatter(data3[:,0],data3[:,1],marker="s")
随机初始化权值与偏置
W = np.random.rand(4,2)
b = np.random.rand(4,1)
打乱数据集
All_data=np.concatenate((data0,data1,data2,data3))
np.random.shuffle(All_data)
查看初始化后判别函数的情况
x=np.arange(-5,5)
y1=(-W[0,0]*x-b[0])/W[0,1]
y2=(-W[1,0]*x-b[1])/W[1,1]
y3=(-W[2,0]*x-b[2])/W[2,1]
y4=(-W[3,0]*x-b[3])/W[3,1]
plt.scatter(data0[:,0],data0[:,1],marker="o")
plt.scatter(data1[:,0],data1[:,1],marker="+")
plt.scatter(data2[:,0],data2[:,1],marker="v")
plt.scatter(data0[:,0],data3[:,1],marker="s")
plt.plot(x,y1)
plt.plot(x,y2)
plt.plot(x,y3)
plt.plot(x,y4)
定义函数
#softmax(x)=e^x/sum(e^x)
def softmax_matrix(z):#当z不是一维时
exp = np.exp(z)
sum_exp = np.sum(np.exp(z),axis=1,keepdims=True)
return exp/sum_exp
def softmax_vector(z):#当z是一维时
return np.exp(z)/np.sum(np.exp(z))
#对Y进行one-hot编码
#temp = {0,1,2,3}
def one_hot(temp):
one_hot = np.zeros((len(temp),len(np.unique(temp))))
one_hot[np.arange(len(temp)),temp.astype(np.int).T]=1
return one_hot
# 计算 y_hat
def compute_y_hat(W,X,b):
return np.dot(X,W.T)+b.T
#计算交叉熵
def cross_entropy(y,y_hat):
loss = -(1/len(y))*np.sum(y*np.log(y_hat))
return loss
进行训练
#w = w + lr*grad
lr = 0.01
loss_list=[]
for i in range(1000):
#计算loss
y_hat = softmax_matrix(compute_y_hat(W,train_data_X,b))
y = one_hot(train_data_Y)
loss = cross_entropy(y,y_hat)
loss_list.append(loss)
#计算梯度
grad_w = (1/len(train_data_X))*(np.dot(train_data_X.T,(y-y_hat)).T)
grad_b = (1/len(train_data_X))*np.sum(y-y_hat)
#更新参数
W = W + lr*grad_w
b = b + lr*grad_b
# 输出
if i%100==1 :
print("i:%d , loss:%f"%(i,loss))
绘制训练后的分类情况
x=np.arange(-5,5)
y1=(-W[0,0]*x-b[0])/W[0,1]
y2=(-W[1,0]*x-b[1])/W[1,1]
y3=(-W[2,0]*x-b[2])/W[2,1]
y4=(-W[3,0]*x-b[3])/W[3,1]
plt.scatter(data0[:,0],data0[:,1],marker="o")
plt.scatter(data1[:,0],data1[:,1],marker="+")
plt.scatter(data2[:,0],data2[:,1],marker="v")
plt.scatter(data3[:,0],data3[:,1],marker="s")
plt.plot(x,y1)
plt.plot(x,y2)
plt.plot(x,y3)
plt.plot(x,y4)
查看loss值下降曲线
plt.plot(loss_list)