softmax实现(程序逐句讲解)
上一个博客已经讲了softmax理论部分,接下来我们就来做个实验,我们有一些手写字体图片(28*28),训练样本(train-images.idx3-ubyte里面的图像对应train-labels.idx1-ubyte)和测试样本(t10k-images.idx3-ubyte里面的图片对应t10k-labels.idx1-ubyte),我们用训练样本训练softmax模型,测试样本用来做测试。数据和下面讲解的程序下载地址:(这里)
我们首先展示下我们训练样本部分的图片和label:
1: images = loadMNISTImages('train-images.idx3-ubyte');%得到的images是一个784*60000的矩阵,意思是每一列是一幅28*28的图像展成了一列,
2: %一共有60000幅图像。
3: labels = loadMNISTLabels('train-labels.idx1-ubyte');
4: display_network(images(:,1:100)); % Show the first 100 images
5: disp(labels(1:10));
图片展示: 部分label:
我们发现lable是从0~9的,为了便于操作,我们最好转化成1~10(后面转换)。
下面我们进行训练,首先我们定义一些softmax模型常量:
1: inputSize = 28 * 28; % Size of input vector (MNIST images are 28x28)
2: inputSize =inputSize +1;% softmx的输入还要加上一维(x0=1),也是θj向量的维度
3: numClasses = 10; % Number of classes (MNIST images fall into 10 classes)
4: lambda = 1e-4; % Weight decay parameter
导入训练样本数据
1: images = loadMNISTImages('train-images.idx3-ubyte');%得到的images是一个784*60000的矩阵,意思是每一列是一
2: %幅28*28的图像展成了一列,一共有60000幅图像。
3: labels = loadMNISTLabels('train-labels.idx1-ubyte');
4: labels(labels==0) = 10; % 因为这里类别是1,2..k,从0开始的,所以这里把labels中的0映射成10
5:
6: inputData = images;
7: inputData = [ones(1,60000); inputData];%每个样本都要增加一个x0=1
初始化模型参数:
1: theta = 0.005 * randn(inputSize*numClasses, 1);
接下来也是最重要的一步就是:给定模型参数的情况下,求训练样本的softmax的cost function和梯度,即
1: [cost, grad] = softmax_regression_vec(theta,inputData ,labels,lambda );
接下来我们就要写softmax_regression_vec函数:
1: function [f,g] = softmax_regression_vec(theta, X,y,lambda )
2: %下面的n和inputSize指数据有多少维(包括新加的x0=1这一维),也是θj向量的维度
3: %这里y是1,2....到k,从1开始的
4: m=size(X,2);%X每一列是一个样本,m是指有m个样本
5: n=size(X,1); %n指代的前面说了
6: theta=reshape(theta, n, []); %也就是把theta设置成这样矩阵:有inputSize行也就是n行,每一列是一个θj,有k列。这样的θ矩阵跟前面理论部分的θ矩阵不一样,存在
%转置关系,为什么这样呢?这样这样的话在后面的reshape和矩阵A(:)这样的操作,方便,都是按列进行的,还原也方便。所以只好程序中出现的θ矩阵都是这样的,k列,跟理论部分的相反。
7: % initialize objective value and gradient.
8: f = 0;
9: g = zeros(size(theta));
10: h = theta'*X;%h是k行m列的矩阵,见图1.
图1
1: a = exp(h);
2: p = bsxfun(@rdivide,a,sum(a)); % sum(a)是一个行向量,每个元素是a矩阵的每一列的和。然后运用bsxfun(@rdivide,,)
3: %是a矩阵的第i列的每个元素除以 sum(a)向量的第i个元素。得到的p矩阵大小和图1一样,每个元素如图2.
图2
1: c = log(p); %然后我们取log的对数,c矩阵大小和图1一样,每个元素如图3
要注意取以e为底的对数,如果是其他的最后结果也正确,但是在下面梯度验证 %部分会有一个倍数关系,而不是相等。
图3
1: i = sub2ind(size(c), y',1:size(c,2)); %y',1:size(c,2)这两个向量必须同时是行向量或列向量
2: %因为我们接下来每一个样本xi对应的yi是几,就去找到p的每一列中,所对应的第几个元素就是要找的,如图4.首先使用sub2ind
3: %sub2ind: 在matlab中矩阵是按一列一列的存储的,比如A=[1 2 3;4 5 6]
4: %那么A(2)=4,A(3)=2...而这个函数作用就是比如 sub2ind(size(A),2,1)就是返回A的第2行第一列的元素存储的下标,因为
5: %A(2)=4,所以存储的下标是2,所以这里返回2.这里sub2ind(size(A),2,1)的2,1也可以换成向量[a1,a2..],[b1,b2..]但是注意
6: %这两个向量必须同时是行向量或列向量,而不能一个是行向量一个是列向量。所以返回的
7: %第一个元素是A的第a1行第b1列的元素存储的下标,返回的第,二个元素是A的第a2行第b2列的元素存储的下标...i是一个向量,c(i)得到的
8: %向量的每一个元素就是p中每一列你前面要找的的元素。
图4
1: values = c(i);
2: f = -(1/m)*sum(values)+ lambda/2 * sum(theta(:) .^ 2); %这个就是cost function
最后求梯度:
1: d = full(sparse(1:m,y,1)); %d为一个稀疏矩阵,有m行k列(k是类别的个数),这个矩阵的(1,y(1))、(2,y(2))
2: %....(m,y(m))位置都是1。
3: g = (1/m)*X*(p'-d)+ lambda * theta; %这个g和theta矩阵的结构一样。
4: g=g(:); % 再还原成向量的形式,这里(:)和reshape都是按列进行的,所以里面位置并没有改变。
解释下这几行:
我们想求梯度矩阵g,这里的g和θ=[θ1,θ2,…,θk]矩阵大小size一样(跟博客中的θ矩阵存在转置关系,之所有代码中这么做,是因为这样再把参数矩阵转成一个向量或转回去利用g(:)或reshape函数按列比较方面),是n行k列的矩阵。n是θj或一个样本xi(包括截距1这一维)的维度大小,k是类别个数。m是样本个数。
我们已经从上一篇博客知道我们g(u,v)的大小通过公式求得。
我们知道就是前面d矩阵(有m行k列) i 行v列的值,是前面p矩阵 (有m列k行)i 列 v行的值。
我们想用矢量编程来求g矩阵:
我们有样本X(代码中每一列是一个样本,也即X为n行m列),那么g = (1/m).*X*(p'-d)即是。比如,X的第 i 行乘以(p'-d)的第 j 列就是X(i,j)的值。(正是这种行向量乘以列向量是对应元素相乘再相加就完成了公式里的Σ,这也是矢量编程的核心)
其实cost function不含正则项部分我们还可以用-1/m*d(:)*c(:)来实现。
ok,现在我们这个函数写完了,我们想验证下,我们写的这个求导数或着说梯度的这个公式正确不正确,我们还是用之前博客提到的用求导公式来验证,因为你求softmax模型某个参数的导数跟你输入的数据是什么、多少都没有关系,所以我们这有用一些简单的随意写得数据和label,然后随意取一个参数进行验证是不是正确,这些程序在前面已经有了,就不进行讲解了。
1: % DEBUG = true; % Set DEBUG to true when debugging.
2: DEBUG = false;
3: if DEBUG
4: inputSize = 9;
5: inputData = randn(8, 100);
7: inputData = [ones(1,100);inputData];
8: labels = randi(10, 100, 1);%从[1,100]中随机生成一个100*1的列向量
10: end
12: % Randomly initialise theta
13: theta = 0.005 * randn(inputSize*numClasses, 1);
1: [cost, grad] = softmax_regression_vec(theta,inputData ,labels,lambda );
2:
3: if DEBUG
4: numGrad = computeNumericalGradient( @(theta) softmax_regression_vec(theta,inputData ,labels,lambda) ,theta);
5:
6: % Use this to visually compare the gradients side by side
7: disp([numGrad grad]);
8:
9: % Compare numerically computed gradients with those computed analytically
10: diff = norm(numGrad-grad)/norm(numGrad+grad);
11: disp(diff);
12: % The difference should be small.
13: % In our implementation, these values are usually less than 1e-7.
14:
15: % When your gradients are correct, congratulations!
16: end
然后我们看看检验结果(部分对比):
我们发现最终结果为 9.0915e-10,误差已经足够小了,说明我们的写得函数正确。
接下来,我们要通过迭代算法来最小化我们的cost function,我们仍然使用之前的minfunc 程序包来优化:
1: options.maxIter = 100; %迭代100次
2: softmaxModel = softmaxTrain(inputSize, numClasses, lambda, ...
3: inputData, labels, options);
4: %得到的softmaxModel是一个结构体。softmaxModel.optTheta是一个n行k列的参数矩阵,每一列是一个θj
1: function [softmaxModel] = softmaxTrain(inputSize, numClasses, lambda, inputData, labels, options)
2: % options (optional): options
3: % options.maxIter: number of iterations to train for
4: if ~exist('options', 'var')
5: options = struct;
6: end
7: if ~isfield(options, 'maxIter')
8: options.maxIter = 400;
9: end
10: % initialize parameters
11: theta = 0.005 * randn(numClasses* inputSize, 1);
12:
13: % Use minFunc to minimize the function
14: addpath minFunc/
15: options.Method = 'lbfgs'; % Here, we use L-BFGS to optimize our cost
16: % function. Generally, for minFunc to work, you
17: % need a function pointer with two outputs: the
18: % function value and the gradient. In our problem,
19: % softmaxCost.m satisfies this.
20: minFuncOptions.display = 'on';
21:
22: [softmaxOptTheta, cost] = minFunc( @(theta) softmax_regression_vec(theta,inputData,labels,lambda),theta, options);
23:
24: % Fold softmaxOptTheta into a nicer format
25: softmaxModel.optTheta = reshape(softmaxOptTheta,inputSize, numClasses );
26: softmaxModel.inputSize = inputSize;
27: softmaxModel.numClasses = numClasses;
28:
29: end
然后看下结果:
我们发现cost function 的值 收敛在0.2599附近。
接下来来预测我们的预测样本,看看识别的正确率是多少:
1: images = loadMNISTImages('t10k-images.idx3-ubyte');
2: labels = loadMNISTLabels('t10k-labels.idx1-ubyte');
3: labels(labels==0) = 10; % Remap 0 to 10
4:
5: inputData = images;
6: inputData = [ones(1,size(inputData,2)); inputData];%每个样本都要增加一个x0=1
7:
8: [pred] = softmaxPredict(softmaxModel, inputData);
9: acc = mean(labels(:) == pred(:));
10: fprintf('Accuracy: %0.3f%%\n', acc * 100);
11:
12: function [pred] = softmaxPredict(softmaxModel, data)
13:
14: theta = softmaxModel.optTheta; %theta是k列,n行的矩阵
15: pred = zeros(1, size(data, 2));
16: [~,pred]= max(theta'*data);%theta'*data这个矩阵如图5,某一个样本softmax最大值与这个矩阵某一列最大
17: %值是等价的,因为每一列除以同一个分母和不除是一样的,并且exp(.)是增函数,所以只求里面的最大值即可。
图5
最后看一下我们的正确识别率是多少:
(完)
参考:http://deeplearning.stanford.edu/wiki/index.php/Exercise:Softmax_Regression
softmax实现(程序逐句讲解)的更多相关文章
- 微信小程序——详细讲解页面传值(多种方法)
1.使用navigator的url带参传值 (1)在pageA页面有一个固定的值要传递到pageB页面,比如说一个固定的值user_id要传递给B <navigator url=".. ...
- 微信小程序入门讲解
微信小程序 注册 由于发文限制,请自行到微信公众平台注册 项目结构 project.config.json 配置文件(不需要动) app.json(用户配置) 路由pages window 整个程序样 ...
- MFC-01-Chapter01:Hello,MFC---1.3 第一个MFC程序(01)
#include <afxwin.h> class CMyApp : public CWinApp { public: virtual BOOL InitInstance(); }; cl ...
- 使用 Eclipse C/C++ Development Toolkit 开发应用程序
使用 Eclipse C/C++ Development Toolkit 开发应用程序 (转) 来自http://blog.csdn.net/favory/article/details/189080 ...
- 4 我们的第一个c#程序
1. 控制台应用程序. 在我们这个培训中主要使用控制台应用程序来讲解知识点和做练习. 什么是控制台程序? 控制台程序运行在dos窗口.没有可视化的界面.可以通过Dos窗口进入输入和输出显示 ...
- PID控制器的数字实现及C语法讲解
PID控制器的数字实现及C语法讲解 概述 为方便学习与交流,根据自己的理解与经验写了这份教程,有错误之处请各位读者予以指出,具体包含以下三部分内容: (1) PID数字化的推导过程(实质:微积分的近 ...
- PHP应用程序的安全性
无论在开发中,还是在面试时或者技术讨论时,安全性都是需要深入了解及掌握的. 目标 本教程目标是使您了解应该如何保护自己构建的 Web 应用程序.讲解如何防御最常见的安全威胁:SQL 注入.操纵 GET ...
- C语言入门(3)——对Hello World程序的解释
上篇我们写了一个最简单的程序.这个简单的程序包含了很多重要的内容.本篇我们通过这个最简单的Hello World程序逐一讲解C语言程序的一些特点. 打开Visual Studio 2013 通过菜单- ...
- 如何两周达到150行Java程序的能力--part 1
面向对象程序先导课是体系化面向对象课程的重要组成部分,其目标是帮助那些有一定C语言基础,但对面向对象概念陌生,基本没碰过Java编程的同学.该课程设计为暑期选修课,因为没有其他课程,我们设计为现场训练 ...
随机推荐
- Python简单实现邮件群发
Python简单实现邮件群发 import smtplib from email.mime.text import MIMEText from email.utils import formatadd ...
- rest_framework之访问频率控制
一 自定义频率控制类 class MyThrottle(): visitor_dic = {} def __init__(self): self.history = None def allow_r ...
- kotlin写的几个小例子
Kotlin-AdapterDemo kotlin语言下BaseAdapter,ArrayAdapter,SimpleAdapter,SimpleCursorAdapter四种适配器的示例 工具and ...
- Android学习九---OpenCV4android org.opencv.feature2d
不管是在识别,配准等应用中,提取图像的特征都是很关键的一环,提取特征是先找出图像的关键点(如角点,边缘点等),然后用描述子来描述这些点,最后整幅图像就可以表示成一个特征向量,特征向量就可以利用在后续识 ...
- jQuery Mobile 手动显示ajax加载器
在jquery mobile开发中,经常需要调用ajax方法,异步获取数据,如果异步获取数据方法由于网速等等的原因,会有一个反应时间,如果能在点击按钮后数据处理期间,给一个正在加载的提示,客户体验会更 ...
- 快速搭建vue脚手架
https://segmentfault.com/a/1190000011275993
- Memcached基础介绍
1.memcached是什么,有什么作用? )memcached是一个开源的.高性能的内存的缓存软件,从名称上看mem就是内存的意思,而cache就是缓存的意思. )memcached通过在事先规划好 ...
- 入门拾遗 day2
一.类和对象 对于Python,一切事物都是对象,对象基于类创建 学会查看帮助 type(类型名) 查看对象的类型dir(类型名) 查看类中提供的所有功能help(类型名) 查看类中所有详细的功能he ...
- SqlAlchemy基本
安装SQLAlchemy: $ easy_install sqlalchemy 数据库表是一个二维表,包含多行多列 [ ('1', 'Michael'), ('2', 'Bob'), ('3', 'A ...
- Python(迭代、三元表达式、列表生成、生成器、迭代器)
迭代 什么是迭代 1 重复 2 下次重复一定是基于上一次的结果而来 如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration). ...