搭建一个简单的Softmax分类器(基于CS231n)

来源:互联网 发布:淘宝老七 编辑:程序博客网 时间:2024/05/17 02:06
  • 生成数据集
  • 训练一个Softmax线性分类器
    • 初始化参数
    • 计算得分
    • 计算损失
    • 反向传播法(Backpropagation)计算梯度值
    • 参数更新
    • 完整代码


本文是基于Stanford CS231n的学习笔记,搭建一个简单的Softmax分类器。1

生成数据集

首先是生成一个不易线性分割的数据集,本文采用的例子是螺旋数据集(Spiral Dataset)

N = 100 # number of points per classD = 2 # dimensionalityK = 3 # number of classesX = np.zeros((N*K,D)) # data matrix (each row = single example)y = np.zeros(N*K, dtype='uint8') # class labelsfor j in xrange(K):  ix = range(N*j,N*(j+1))  r = np.linspace(0.0,1,N) # radius  t = np.linspace(j*4,(j+1)*4,N) + np.random.randn(N)*0.2 # theta  X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]  y[ix] = j# lets visualize the data:plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)plt.show()

通常情况我们需要预处理每个特征,进行Z-score标准化:
Z-score标准化有两个好处
1.是消除量纲对最终结果的影响,使不同变量具有可比性。比如两个人体重差10KG,身高差0.02M,在衡量两个人的差别时体重的差距会把身高的差距完全掩盖,标准化之后就不会有这样的问题。
2.均值为0,标准差为1

x=xμσ

其中σ是标准差,μ是样本均值
具体可参考2

但是这个数据集的特征已经很好的分布于[-1,1],所以我们跳过这个步骤

训练一个Softmax线性分类器

初始化参数

Softmax分类器是由一个线性的 Score Function 并使用 Cross-entropy Loss Function,这个线性分类器的参数由一个权重矩阵W和一个偏移向量b组成,首先我们初始化这些参数为随机数字:

# initialize parameters randomlyW = 0.01 * np.random.randn(D,K)b = np.zeros((1,K))

这里的D=2指的是数据集的维度,K=3指的是数据集包含的类别。

计算得分

# compute class scores for a linear classifierscores = np.dot(X, W) + b

在这个例子中我们有300个二维的点,所以计算得出的矩阵scores的大小为[300*3],表示的是300个点分别在3个类别的得分(蓝,红,黄)。

计算损失

分类器的第二个重要构成部分是损失函数,损失函数是一个可微的用来量化我们对于计算出的得分是否满意的函数。 我们希望每个点对应的正确的类别相比于其他类别取得一个更高的得分。在这个例子中我们会使用 Cross-entropy 损失函数,f表示的是每个点在3个类别的得分:

Li=logefyijefj

Softmax分类器解释f中的每个元素为该点对应三个类别的对数概率(未标准化)。 我们对其取幂得到概率(未标准化),然后再进行标准化得到概率。因此,log里面的表达式是正确类别的标准化后的概率
(fyi指的是该点在正确类别的对数概率)。
NOTE:这个公式的数值永远介于0到1之间,当正确类别的概率接近0时,损失值会接近正无穷。相反地,如果正确类别的概率接近于1,损失值会接近于0。所以,当Li很小时,正确类别的概率是很高的,当它很大时,正确类别的概率是很小的。

整个分类器的损失值是所有训练例子的损失值的平均值加上正则损失值。(正则损失值是为了防止过拟合)

L=1NiLidata loss+12λklW2k,lregularization loss

以下是代码

num_examples = X.shape[0]# get unnormalized probabilitiesexp_scores = np.exp(scores)# normalize them for each exampleprobs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

所以我们现在有了一个[300*3]的数组probs,描述了300个点分别对应3个类别的概率,现在我们可以求300个点分别对应的正确类别的概率的log值。

corect_logprobs = -np.log(probs[range(num_examples),y])

corectlogprobs是一个[300*1]矩阵,描述了300个点分别对应的正确类别的概率的log值。整个分类器的损失值将由这些log值加上正则损失值构成:

# compute the loss: average cross-entropy loss and regularizationdata_loss = np.sum(corect_logprobs)/num_examplesreg_loss = 0.5*reg*np.sum(W*W)loss = data_loss + reg_loss

这个代码中,正则强度值λ储存在reg中。用初始的随机参数值计算loss会得到,loss=1.1,也就是log(1.0/3)=1.1,因为用初始参数值计算出的每个类别的概率大概都是1/3。而我们接下来需要做的是尽可能降低loss值。

反向传播法(Backpropagation)计算梯度值

我们已经有了方法测量损失值(loss),现在我们要降低它。我们会采取梯度下降法。 首先我们引进一个中间变量p,一个代表概率的向量:

pk=efkjefj

则训练样本i的loss是
Li=log(pyi)

现在我们想要得到是fk的值应该怎样变化才能降低lossLi,即我们想求得Li/fk,利用复合函数求导可求得:

Lifk=pk1(yi=k)

这是一个很漂亮的结果,假设我们求得一个训练样本的概率值是 p = [0.2, 0.3, 0.5], 然后这个训练样本的正确类是第二类,则 df = [0.2, -0.7, 0.5]. 表示只有提升f中的第二个值才能降低loss. 以下为代码:

dscores = probsdscores[range(num_examples),y] -= 1dscores /= num_examples

最后,因为我们有 scores = np.dot(X, W) + b ,所以我们得知了dscores就可反向传播求得 db 和 dW 。

dW = np.dot(X.T, dscores)db = np.sum(dscores, axis=0, keepdims=True)dW += reg*W # don't forget the regularization gradient

参数更新

# perform a parameter updateW += -step_size * dWb += -step_size * db

完整代码

import numpy as npimport matplotlib.pyplot as plt# initialize parameters randomlyN = 100 # number of points per classD = 2 # dimensionalityK = 3 # number of classesX = np.zeros((N*K,D)) # data matrix (each row = single example)y = np.zeros(N*K, dtype='uint8') # class labelsfor j in xrange(K):  ix = range(N*j,N*(j+1))  r = np.linspace(0.0,1,N) # radius  t = np.linspace(j*4,(j+1)*4,N) + np.random.randn(N)*0.2 # theta  X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]  y[ix] = jplt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)D=2K=3W = 0.01 * np.random.randn(D,K)b = np.zeros((1,K))# some hyperparametersstep_size = 1e-0reg = 1e-3 # regularization strength# gradient descent loopnum_examples = X.shape[0]for i in xrange(200):  # evaluate class scores, [N x K]  scores = np.dot(X, W) + b   # compute the class probabilities  exp_scores = np.exp(scores)  probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) # [N x K]  # compute the loss: average cross-entropy loss and regularization  corect_logprobs = -np.log(probs[range(num_examples),y])  data_loss = np.sum(corect_logprobs)/num_examples  reg_loss = 0.5*reg*np.sum(W*W)  loss = data_loss + reg_loss  if i % 10 == 0:    print "iteration %d: loss %f" % (i, loss)  # compute the gradient on scores  dscores = probs  dscores[range(num_examples),y] -= 1  dscores /= num_examples  # backpropate the gradient to the parameters (W,b)  dW = np.dot(X.T, dscores)  db = np.sum(dscores, axis=0, keepdims=True)  dW += reg*W # regularization gradient  # perform a parameter update  W += -step_size * dW  b += -step_size * dbscores = np.dot(X, W) + bpredicted_class = np.argmax(scores, axis=1)print Wprint 'training accuracy: %.2f' % (np.mean(predicted_class == y))plt.show()

以下是Desicion Boundary的图,但是我不知道怎么画Desicion Bounday,有知道的朋友欢迎指导

关于本文涉及的更详细的SoftMax原理可参阅这里

原创粉丝点击