[机器学习笔记#2] 一步步用 softmax Regression 实现手写字符识别

[机器学习笔记#1]从 Logistic Regression 讲讲分类器和机器学习
[机器学习笔记#3] 简明Lagrange multiplier(拉格朗日乘数)讲义
[资源分享]机器学习资料和常用工具汇总

上一笔记中我们讲了 logistic regression 的基本知识,实现了一个最简单的二分类器。这里再继续将其推广至更多类别的情形。

推广至多类别(multi-class)情形的 logistic regression 分类器被称作 softmax 分类器。这里我们研究的class 从原来的两种变成了C种,相应的logistic function便被如下的softmax function所取代:

$$P(y_i=c|x_i;W) = \frac{\exp(w_{c}^T x_i)}{\sum_{c'} \exp(w_{c'}^T x_i)}=\mu_{ic}$$
其中 $$W = \begin{bmatrix}w_0 ...w_c ... w_C\end{bmatrix} $$
可见权重W变成了C个权重的组合,每一个单独的子权重wc对应一个类别的判定。 类似的,我们定义损失函数如下: $$ L(W) = -log \prod_i^N \prod_c^C P(y_i=c|x_i;W)=\sum_i^N( -(\sum_c^C y_{ic} w_c^T x_i)+log{\sum_{c'}^C{\exp(w_{c'}^T x_i)}})$$
对其求导可得: $$\frac{\partial L(W)}{\partial w_c} =\sum_i^N (\frac{\exp(w_{c}^T x_i)}{\sum_{c'} \exp(w_{c'}^T x_i)}-y_{ic})x_i=\sum_i^N (\mu_{ic}-y_{ic})x_i$$
$$\frac{\partial L(W)}{\partial W} = \begin{bmatrix} \frac{\partial L(W)}{\partial w_0}...\frac{\partial L(W)}{\partial w_c} ... \frac{\partial L(W)}{\partial w_C}\end{bmatrix} $$

这次我们不再使用人工生成的数据作为实验对象,MNIST是一个经典的手写字符识别训练集,由机器学习领域的神人 Yann LeCun 提供,可在其主页下载,同时该页面还提供了大量测试数据以供比较学习。

该数据集包含60,000个训练数据,10,000个测试数据,由大量风格迥异,造型随机的手写数字0~9的黑白图片组成,图片已预先分割裁剪至28x28的尺寸,以下是一组样例:

[修改于 3 年前 - 2016-08-06 14:51:04]

来自 机器学习
2016-7-21 12:06:43
Cirno(作者)
1楼

在Python中使用MNIST较便捷的方法是安装MNIST工具包,在这里可以下载安装。

安装后便可如下直接导入训练数据和测试数据:

import matplotlib.pyplot as plt
import numpy as np
from mnist import MNIST
%matplotlib inline
mndata = MNIST('Path to your data/MNIST')
images, labels=mndata.load_training()
images_test, labels_test=mndata.load_testing()

计算Loss function,同样得解决指数函数数据溢出的问题:

def calculate_loss(w,x,y):
    theta=np.dot(np.transpose(w),x)
    ### first part
    l1=np.multiply(theta,y)
    ### second part
    thetaMax=np.max(theta,axis=0)
    theta-=thetaMax
    l2=np.log(np.sum(np.exp(theta),axis=0))
    ### sum together
    L=np.sum(-l1+l2+thetaMax)
    return L

计算导数:

def calculate_gradient(w,x_batch,y_batch):
    theta=np.dot(np.transpose(w),x_batch)
    theta-=np.max(theta,axis=0)
    mu=np.exp(theta)
    muSum=np.sum(mu,axis=0)
    mu=mu/muSum
    mu-=y_batch
    dL=np.dot(x_batch,np.transpose(mu))
    return dL

用SGD训练:

def train(x,y,batch_sz,lr,max_iter,loss_thresh):
    ### bias trick ###
    batch_sz=100
    d_len=x.shape[1]
    d_dim=x.shape[0]+1
    d_class=np.max(y)-np.min(y)+1
    x_b=np.concatenate((x,np.ones((1,d_len))),axis=0)
    yc=np.zeros((d_class,d_len))
    yc[y,np.arange(d_len)]=1;
    w=np.zeros((d_dim,d_class),dtype=np.float)
    
    Loss_old=0
    Loss=[]
    stepCnt=0
    ### Run SGD ###
    for iter in range(max_iter):
        ### sample a mini batch ###
        batch=np.arange(d_len)
        np.random.shuffle(batch)
        x_batch=x_b[:,batch[:batch_sz]]
        y_batch=yc[:,batch[:batch_sz]]
        ### update weight ###
        dL=calculate_gradient(w,x_batch,y_batch)
        w-=lr*dL
        ### record loss changes ###
        Loss.append(calculate_loss(w,x_b,yc))
        ### learning rate annealing ###
        stepCnt+=1
        if stepCnt==10:
            stepCnt=0
            lr*=0.8
        ### Check if converge ###
        if abs(Loss[-1]-Loss_old)<loss_thresh:
            break
        Loss_old=Loss[-1]
        
    return w,Loss

主函数:

x_train=np.transpose(np.matrix(images))
y_train=np.array(labels)
lr=0.5
batch_sz=50
Max_iter=200
loss_thresh=1e-3
w,Loss = train(x_train,y_train,batch_sz,lr,Max_iter,loss_thresh)
print w
plt.plot(Loss)

[修改于 3 年前 - 2016-07-22 09:15:50]

折叠评论
加载评论中,请稍候...
折叠评论
Cirno(作者)
2楼

Loss function的收敛情况如下:


得到的权重W是一个 785x10 的矩阵,其中10表示有10个不同的类别。

单独拿出其中某一行矩阵,拿掉最后一列bias,是一个784维的向量\(w_c\),即是对应于该行数字的预测权重。

这个权重在机器视觉上有何种意义呢?

下面变一个小魔术,如果你还记得我们的MNIST数据中的单个训练数据\(x_i\),是将一张28x28的数字图片,转换成一个784维的向量得到的。于是我们这里反过来把784维的\(w_c\)转换成28x28的图片,得到了以下的10张图片:


分别对应0~9这10个数字,从中依稀可以辨别出来。所以,这里分类字符的原理,其实可以看作是模板匹配(template matching),训练过程是为了从训练集中综合出最合适的数字模板。

[修改于 3 年前 - 2016-07-22 09:15:32]

折叠评论
加载评论中,请稍候...
折叠评论
Cirno(作者)
3楼

简要对测试集做了一下测试,以下补上prediction部分的代码:

def predict(w,x):
    score=np.dot(np.transpose(w),x)
    pred=np.argmax(score)
    return pred

随机抽取40个测试数据做了预测,这里仅定性说明softmax分类器训练结果的有效性,更加具体的测试数据以后更新。令人excited的一点是,虽然这个实验中我们只是用最简单的思路做了一些微小的工作,不加处理的对像素数据进行了regression,准确率已经比较可观了,这很大程度上可以解释为mnist数字,从分层理解的模型中来看,层次是很浅的,大约就是 像素->边缘->基本几何形状 这三层。



[修改于 3 年前 - 2016-07-22 09:50:04]

折叠评论
加载评论中,请稍候...
折叠评论
4楼
这个例子所用的算法,很接近传统OCR。但由于MNIST都是手写字体,字形变化很大,所以直接用784维向量而不加处理(比如卷积!)的话,loss func 的收敛情况其实不佳(从模板中8与9的相像程度来看的话)。

感觉楼主准备飙CNN了,期待。
折叠评论
加载评论中,请稍候...
折叠评论
Cirno(作者)
5楼
引用 novakon:
这个例子所用的算法,很接近传统OCR。但由于MNIST都是手写字体,字形变化很大,所以直接用784维向量而不加处理(比如卷积!)的话,loss func 的收敛情况其实不佳(从模板中8与9的相像程度来……
不急不急,NN 还没写呢。 另外回去看了一遍才发现9被我弄掉了,回头补上。
折叠评论
加载评论中,请稍候...
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{f.progress}}%
处理中..
上传失败,点击重试
{{f.name}}
空空如也~
(视频){{r.oname}}
{{selectedResourcesId.indexOf(r.rid) + 1}}
ID:{{user.uid}}
{{user.username}}
{{user.info.certsName}}
{{user.description}}
{{format("YYYY/MM/DD", user.toc)}}注册,{{fromNow(user.tlv)}}活动
{{submitted?"":"投诉"}}
请选择违规类型:
{{reason.description}}
支持的图片格式:jpg, jpeg, png