SoftMax原理及代码

softmax原理和代码理解:

softmax 个人理解就是一个M*N的矩阵(一个样本的特征)和一个N*K的矩阵相乘,得到M*K的矩阵,然后输入每一行的表示这一类的模型参数,然后计算出该样本属于每一类的概率。

我们知道logistic regression很适合做一些非线性方面的分类问题,不过它只适合处理二分类的问题,且在给出分类结果时还会给出结果的概率。那么如果需要用类似的方法(这里类似的方法指的是输出分类结果并且给出概率值)来处理多分类问题的话该怎么扩展呢?本次要讲的就是对logstic regression扩展的一种多分类器,softmax regression。

  在Logistic regression中,所学习的系统的程为:

   

  其对应的损失函数为:

   

  可以看出,给定一个样本,就输出一个概率值,该概率值表示的含义是这个样本属于类别’1’的概率,因为总共才有2个类别,所以另一个类别的概率直接用1减掉刚刚的结果即可。如果现在的假设是多分类问题,比如说总共有k个类别。在softmax regression中这时候的系统的方程为:

   

  其中的参数sidta不再是列向量,而是一个矩阵,矩阵的每一行可以看做是一个类别所对应分类器的参数,总共有k行。所以矩阵sidta可以写成下面的形式:

   

  此时,系统损失函数的方程为:

   

  其中的1{.}是一个指示性函数,即当大括号中的值为真时,该函数的结果就为1,否则其结果就为0。

  当然了,如果要用梯度下降法,牛顿法,或者L-BFGS法求得系统的参数的话,就必须求出损失函数的偏导函数,softmax regression中损失函数的偏导函数如下所示:

   

  注意公式中的是一个向量,表示的是针对第i个类别而求得的。所以上面的公式还只是一个类别的偏导公式,我们需要求出所有类别的偏导公式。表示的是损失函数对第j个类别的第l个参数的偏导。

  比较有趣的时,softmax regression中对参数的最优化求解不只一个,每当求得一个优化参数时,如果将这个参数的每一项都减掉同一个数,其得到的损失函数值也是一样的。这说明这个参数不是唯一解。用数学公式证明过程如下所示:

   

  那这个到底是什么原因呢?从宏观上可以这么理解,因为此时的损失函数不是严格非凸的,也就是说在局部最小值点附近是一个”平坦”的,所以在这个参数附近的值都是一样的了。那么怎样避免这个问题呢?其实加入规则项就可以解决(比如说,用牛顿法求解时,hession矩阵如果没有加入规则项,就有可能不是可逆的从而导致了刚才的情况,如果加入了规则项后该hession矩阵就不会不可逆了),加入规则项后的损失函数表达式如下:

   

  这个时候的偏导函数表达式如下所示:

   

  接下来剩下的问题就是用数学优化的方法来求解了,另外还可以从数学公式的角度去理解softmax regression是logistic regression的扩展。

  网页教程中还介绍了softmax regression和k binary classifiers之间的区别和使用条件。总结就这么一个要点:如果所需的分类类别之间是严格相互排斥的,也就是两种类别不能同时被一个样本占有,这时候应该使用softmax regression。反正,如果所需分类的类别之间允许某些重叠,这时候就应该使用binary classifiers了。

代码如下,有助于理解原理:

#include <iostream>
#include <math.h>
#include<algorithm> 
#include <functional> 
#include <string>
#include <cassert>
#include <vector>
using namespace std;
class LogisticRegression {
public:
	LogisticRegression(int inputSize, int k, int dataSize, int num_iters,double learningRate);
	~LogisticRegression();
	bool loadData(const string& filename);//加载数据
	void train();//训练函数
	void softmax(double* thetaX);//得到样本对应的属于某个类别的概率
	double predict(double* x);//预测函数
	double** getX();
	double** getY();
	void printX();
	void printY();
	void printTheta();
private:
	int inputSize;//输入特征数,不包括bias项
	int k;//类别数
	int dataSize;//样本数
	int num_iters;//迭代次数
	double **theta;//学习得到的权值参数
	double alpha;//学习速率
	double** x;//训练数据集
	double** y;//训练数据集对应的标号
};
softmax实现代码:

#include "LogisticRegression.h"
LogisticRegression::LogisticRegression(int in, int out,int size, int num_iters,double learningRate) {
	inputSize = in;
	k = out;
	alpha = learningRate;
	dataSize = size; 
	this->num_iters = num_iters;
	// initialize theta
	theta = new double*[k];
	for (int i = 0; i<k; i++) theta[i] = new double[inputSize];
	for (int i = 0; i<k; i++) {
		for (int j = 0; j<inputSize; j++) {
			theta[i][j] = 0;
		}
	}
	//initialize x
	x = new double*[dataSize];
	for (int i = 0; i<dataSize; i++) x[i] = new double[inputSize];
	for (int i = 0; i<dataSize; i++) {
		for (int j = 0; j<inputSize; j++) {
			x[i][j] = 0;
		}
	}
	//initialize y
	y = new double*[dataSize];
	for (int i = 0; i<dataSize; i++) y[i] = new double[k];
	for (int i = 0; i<dataSize; i++) {
		for (int j = 0; j<k; j++) {
			y[i][j] = 0;
		}
	}
}

LogisticRegression::~LogisticRegression() {
	for (int i = 0; i<k; i++) delete[] theta[i];
	delete[] theta;
	for (int i = 0; i < dataSize; i++)
	{
		delete[] x[i];
		delete[] y[i];
	}
	delete[] x;
	delete[] y;
}

void LogisticRegression::train() {
	for (int n = 0; n < num_iters; n++)
	{
		for (int s = 0; s < dataSize; s++)
		{
			double *py_x = new double[k];
			double *dy = new double[k];
			//1.求出theta*x
			for (int i = 0; i<k; i++) {
				py_x[i] = 0;
				for (int j = 0; j<inputSize; j++) {
					py_x[i] += theta[i][j] * x[s][j];
				}
			}
			//2.求出概率
			softmax(py_x);
			for (int i = 0; i<k; i++) {
				dy[i] = y[s][i] - py_x[i];//真实值与预测值的差异

				for (int j = 0; j<inputSize; j++) {
					theta[i][j] += alpha * dy[i] * x[s][j] / dataSize;
				}
			}
			delete[] py_x;
			delete[] dy;
		}
	}
}

void LogisticRegression::softmax(double *x) {
	double max = 0.0;
	double sum = 0.0;

	for (int i = 0; i<k; i++) if (max < x[i]) max = x[i];
	for (int i = 0; i<k; i++) {
		x[i] = exp(x[i] - max);
		sum += x[i];
	}

	for (int i = 0; i<k; i++) x[i] /= sum;
}


double LogisticRegression::predict(double *x) {
	double clsLabel;
	double* predictY = new double[k];
	for (int i = 0; i < k; i++) {
		predictY[i] = 0;
		for (int j = 0; j < inputSize; j++) {
			predictY[i] += theta[i][j] * x[j];
		}
	}
	softmax(predictY);
	double max = 0;
	for (int i = 0; i < k; i++)
	{
		if (predictY[i]>max) {
			clsLabel = i;
			max = predictY[i];
		}
	}
	return clsLabel;
}

double** LogisticRegression::getX()
{
	return x;
}
double** LogisticRegression::getY()
{
	return y;
}

bool LogisticRegression::loadData (const string& filename)
{
	const int M = 1024;
	char buf[M + 2];
	int i;
	vector<int> responses;
	FILE* f = fopen(filename.c_str(), "rt");
	if (!f)
	{
		cout << "Could not read the database " << filename << endl;
		return false;
	}
	int rowIndex = 0;
	for (;;)
	{
		char* ptr;
		if (!fgets(buf, M, f) || !strchr(buf, ','))// char *strchr(const char *s,char c):查找字符串s中首次出现字符c的位置
			break;
		y[rowIndex][buf[0] - 'A'] = 1;
		ptr = buf + 2;
		for (i = 0; i < inputSize; i++)
		{
			int n = 0;//存放sscanf当前已读取了的总字符数
			int m = 0;
			sscanf(ptr, "%d%n", &m, &n);//sscanf() - 从一个字符串中读进与指定格式相符的数据
			x[rowIndex][i] = m;
			ptr += n + 1;
		}
		rowIndex++;
		if (rowIndex >= dataSize) break;
		if (i < inputSize)
			break;
	}
	fclose(f);
	cout << "The database " << filename << " is loaded.\n";
	return true;
}

void LogisticRegression::printX()
{
	for (int i = 0; i<dataSize; i++) {
		for (int j = 0; j<inputSize; j++) {
			cout << x[i][j] << " ";
		}
		cout << endl;
	}
}
void LogisticRegression::printY()
{
	for (int i = 0; i<dataSize; i++) {
		for (int j = 0; j<k; j++) {
			cout << y[i][j] << " ";
		}
		cout << endl;
	}
}

void LogisticRegression::printTheta()
{
	for (int i = 0; i < k; i++) {
		for (int j = 0; j < inputSize; j++) {
			cout << theta[i][j] << " ";
		}
		cout << endl;
	}

}


转载的代码:http://blog.csdn.net/sq8912/article/details/45114439


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值