我爱编程

深度学习开篇-感知机(perceptron)原理和实现

2017-12-03  本文已影响0人  孙小五哥哥

近几年科技领域很流行大数据、机器学习、深度学习之类的字眼,其实,这些东西原理很早的时候就有了,只不过当时不叫这些名词,等我们慢慢学习就会发现,这些玩意儿结合了高数、概率统计学、矩阵论、线性代数等,然后以代码实现来解决实际问题。深度学习可以认为被包含在机器学习之中。至于目前他们的应用和研究领域,包括图像识别、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即可调试查看)

感知机简单实现c++

如果我们用上述代码学习训练无法线性可分的数据集,就会导致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();  //训练得到感知机模型

}

上一篇下一篇

猜你喜欢

热点阅读