近几年科技领域很流行大数据、机器学习、深度学习之类的字眼,其实,这些东西原理很早的时候就有了,只不过当时不叫这些名词,等我们慢慢学习就会发现,这些玩意儿结合了高数、概率统计学、矩阵论、线性代数等,然后以代码实现来解决实际问题。深度学习可以认为被包含在机器学习之中。至于目前他们的应用和研究领域,包括图像识别、NLP(自然语言处理)等等,大家自行查阅。废话不多说,现在我从一个最简单的模型向大家展示这类玩意儿怎么玩起来
1.感知机
为了了解什么是感知机,我们需要先理解一下几点:
第一点:感知机是二类分类的线性分类模型(为了新手理解,还是多说两句,什么叫做二类分类,从程序的角度,感知机的输入是某个事物,而它的输出只有两种状态,从现实的角度,我们把具体的人这个事物输入给感知机要她判别性别,最后感知机只能输出两种结果:“男人”或“女人”,不会有第三种~~,如果我们需要有第三种结果,则这个问题不能用感知机来解决(有其他办法)。至于什么叫做线性,接下来会讲到)
第二点:把某个事物输入到感知机模型的理解。(太抽象了吧,映射到程序是怎样的啊)这种输入其实有很多种形式,我举个最简单的例子帮助读者理解。对于某个特定的人,比如我,身高2.0米,体重150,帅气程度100分(满分100) 可爱程度0(满分100),数据提取出来 (2.0 150 100 0) 其中的每一个标识都称之为一个特征点,然后我们就定义一个向量 a = (2.0,150,100,0)来表示“我”这个人,所以输入就是向量a(特征向量)。(ps:这只是最基础的一种形式,不局限于此)
第三点:“学习”的简单理解。机器学习、深度学习中都有学习二字,此二字代表什么意思?直观理解,就是我们从已知信息中学习其中的规律规则,从而预测未知。还是以上述例子阐述。现在我们碰到这样的一个问题:我有1000条人的信息,现在我在路上看到一个背影,目测身高165,体重90、帅气程度30,可爱程度80,想知道他(她)是男是女。这种情况就可以考虑采用感知机处理类似问题。(埋下铺垫,实际中的应用场景很有限)。我们提取1000个人信息中的五个特征点(身高、体重、帅气程度、可爱程度,性别)转化成1000个特征向量,1000个标记向量(只有性别,男用+1表示,女用-1表示,对应于特征向量)作为感知机的输入进行训练,让其学习其中的关联,得到一个训练后的模型,当输入向量(1.65,90,30,80)后,感知机就可以根据训练好的模型预测出最后的结果(+1 或 -1)
第四点:我们从数学的角度深入推敲这个问题的实际转化(其实也就是一个数学建模的过程)
可以理解如下
人 特征向量 标记 序号
我 (2.0,150,100,0) +1(男) 已知 1
夏目 (1.78,135,80,20) +1 ( 男) 已知 2
鲁路修 (1.80,140,99,1) +1(男) 已知 3
魔女cc (1.63,88,10,99) -1 (女) 已知 4
.... ... ... ...
路人 (1.65,90,20,90) ?(要求解的量)
那么,建模开始
第五点:"学习"的数学表达式直观理解(以下以感知机为例,其实很多类型神经网络都类似)
通过上述建模,现在我们的问题变成了如何调整系数W(w0,w1,w2.... )和b,(注意,此处的系数W是向量),使得模型f(X) = sign(W*X+b)具有更好的表达能力。此处好的表达能力指的什么?还是举上述列子,对于已知的1000个特征向量,输入模型f计算后得到的结果尽可能和对应标签一致(其实也就是一个拟合过程),然后我们再输入表示刚才路人的特征向量,经过f计算的结果就是对于此路人的性别预测结果。
那么,现在问题变成了如何调整系数W和b,使得f(X)学习到的表达能力最好。这里扯起来又可以写几篇文章,我们以后一步步展开,目前我只做陈述,有兴趣的同学可以查看我的csdn博客(代价函数 )。目前咱们暂且要记住,如何调整系数W和b,使得f(X)学习到的表达能力最好问题,可以转换为调整模型参数W,b,使得代价函数最小。代价函数有许多定义形式,此处我们依照李航《统计学习方法》P26 P27(大家有兴趣的可以看看,其中有cost代价函数的推导过程。此处我直接给出
其中M表示误分类点的集合 ,xi表示第i个样本(第i个已知信息的特征向量),yi对应+1/-1
第六点:“学习算法”
怎样使得代价函数最小(其实变成了一个纯高数问题),对于L(W,b),xi和yi已知,W和b是变量,L是一个多元方程,求W和b,使得L为最小值,采用梯度下降算法(ps:高数中有一个梯度的概念,大家需要回顾,可以参考机器学习-梯度下降法实现线性回归 )
最后的步骤如下:
上述训练集中xi可以是多维的,即训练集T = {(x11,x12,x13,x14,...,x1n,y1),(x21,x22,x23,x24,...,x2n,y2,...,(xN1,xN2,xN3,xN4,...,xNn,yN))
读者要特别注意的几点:上述文字只是帮助初学者更容易上手理解,没有涉及推导过程,《统计学习方法》李航著 第一二章一定要看看,学会手工推算,损失函数、梯度下降都要分别查阅文献和推导
读到这估计很多人已经迷糊了,但最大的问题在于,为什么建模的时候使用多元线性方程,难道多元自变量和结果之间的关系一定是线性的吗?(如果读到这都没想过这个问题,那~~)当然不一定,怎么可能,而且绝大部分不是线性可分的,所以,大家要记住,单个感知机的表达能力很弱。此处可以联想一下,如果在二维坐标中有很多离散的红蓝点,我们有多少概率可以用一条线就将其划分开,在三维坐标中又有多少概率可以用平面将其划分开,依次类推到n维。既然单个感知机的能力这么挫,我为什么要开篇讲它? 因为单个感知机可以看成组成神经网络的神经元的一部分,以后详解。
2.代码实例
扯了半天不写代码和咸鱼又有什么区别
代码下载(整个项目打包了,安装vs2013 ,直接双击.sln即可调试查看)
如果我们用上述代码学习训练无法线性可分的数据集,就会导致W,b无法收敛,陷入死循环,若一定要用感知机训练,就只能手工设置循环次数,但这种方式最后会导致训练效果不好,预测的正确率就会大幅降低。以后会更新更好的神经网络模型来解决这个问题
以统计学习方法P29页题目为例
#include<iostream>
#include<vector>
#include<string>
#include<fstream>
using namespace std;
typedef vectorfeatureVct;
typedef int label;
class Perceptron;
void printRes(Perceptron pp);
class Perceptron
{private:
vectorFtSet; //特征向量数据集vectorlabelSet; //二分类label数据集 +1或-1
double LearnRate; //学习率
featureVct w; //感知机模型参数
double bias; //偏置项
int dimension; //特征向量维度
int count;
public:
Perceptron(int dimension,featureVct w,double bias = 0.0,double learnRate = 1);
int GetCount() const; //获取迭代次数
const featureVct& GetW() const ; //获取模型参数
void SetW(const featureVct& w); //设置模型参数
double GetBias() const; //获取偏置项
void SetBias(double bias); //设置骗置项
bool ReadSource(const string& filePath); //读取数据源
void Train(); //训练模型,得到感知机模型
public:
double VectorDotProduct(const featureVct& f1, const featureVct& f2); //向量点积运算)
featureVct VectorScalarMulti(double num, const featureVct f); //向量数乘运算
featureVct VectorAdd(const featureVct& f1, const featureVct& f2); //向量加法运算
};
const featureVct& Perceptron::GetW() const
{
return w;
}
void Perceptron::SetW(const featureVct& w)
{
this->w = w;
}
double Perceptron::GetBias() const
{
return bias;
}
void Perceptron::SetBias(double bias)
{
this->bias = bias;
}
/*文件格式:每行表示一个样本点,特征值之间用空格隔开,最后一列存储类别信息1或-1*/
bool Perceptron::ReadSource(const string& filePath)
{
ifstream file(filePath);
if (!file)
return false;
while (!file.eof())
{
featureVct dataTmp;
double tmp;
for (int i = 0; i < dimension; ++i)
{
file >> tmp;
dataTmp.push_back(tmp);
}
FtSet.push_back(dataTmp);
label labelData;
file >> labelData;
labelSet.push_back(labelData);
}
return true;
}
void Perceptron::Train()
{
//感知机训练过程
int flag = true;
while (flag)
{
for (int i = 0; i < FtSet.size(); i++)
{
flag = false;
if (labelSet[i] * (VectorDotProduct(w,FtSet[i]) + bias) <= 0)
{
//此处的打印只是为了更直观的给大家展示学习过程,工程中最后不要直接在类中打印信息
printRes(*this);
flag = true;
w = VectorAdd(w, VectorScalarMulti(labelSet[i]*LearnRate,FtSet[i]));
bias += LearnRate * labelSet[i];
++count;
break;
}
}
//此处的打印只是为了更直观的给大家展示学习过程,工程中最后不要直接在类中打印信息
if(!flag) printRes(*this);
}
}
Perceptron::Perceptron(int dimension, featureVct w, double bias, double learnRate)
{
this->dimension = dimension;
this->bias = bias;
this->LearnRate = learnRate;
this->w = w;
count = 0;
}
double Perceptron::VectorDotProduct(const featureVct& f1, const featureVct& f2)
{
double sum = 0.0;
for (int i = 0; i != f1.size(); ++i)
{
sum += f1[i] * f2[i];
}
return sum;
}
featureVct Perceptron::VectorScalarMulti(double num, const featureVct f)
{
featureVct tmp;
for (int i = 0; i != f.size(); ++i)
{
tmp.push_back(num*f[i]);
}
return tmp;
}
featureVct Perceptron::VectorAdd(const featureVct& f1, const featureVct& f2)
{
featureVct tmp(0);
for (int i = 0; i != f1.size(); ++i)
{
tmp.push_back(f1[i] + f2[i]);
}
return tmp;
}
int Perceptron::GetCount() const
{
return count;
}
void printRes(Perceptron pp)
{
cout << "迭代次数:" << pp.GetCount() << endl;
cout << "w:";
featureVct tmp = pp.GetW();
for (int i = 0; i < tmp.size() - 1; ++i)
{
cout << tmp[i] << " ";
}
cout << tmp[tmp.size() - 1] << endl;
cout << "bias:";
cout << pp.GetBias() << endl;
cout << "---------------------------" << endl;
}
int main()
{
featureVct w;
w.push_back(0.0); //设置w初始化参数
w.push_back(0.0);
Perceptron pp(2, w); //创建特征向量为2维的感知机对象
if (!pp.ReadSource("sun.txt"))
{
cout << "读取文件失败";
exit(-1);
}
pp.Train(); //训练得到感知机模型
}