感知機
2018/12/17 代碼結構更新,詳見https://github.com/bBobxx/statistical-learning
前言
最近學習了c++,俗話說‘光說不練假把式’,所以決定用c++將《統計學習方法》里面的經典模型全部實現一下,代碼在這里,請大家多多指教。
感知機雖然簡單,但是他可以為學習其他模型提供基礎,現在先簡單回顧一下基礎知識。
感知機模型
首先,感知機是用來分類的模型,上圖就是簡單的感知機模型,其中\(f\) 我們一般取符號函數
所以感知機的數學形式就是
其中w和x都是n維的向量。當n為2時,\(sign\)里面的公式有沒有特別熟悉?就是直線的公式,n>2就是超平面,用一下課本里面的圖就是如下圖
這就是分類的根據,必須要注意,感知機只能分離線性可分數據,非線性的不行。
感知機學習策略
提到學習就不得不提到梯度下降算法。感知機的學習策略就是隨機梯度下降算法。
具體的在書中講的很詳細,我這里就不贅述了,直接看學習算法吧:
(1) 選取初值w,b。
(2) 選取一組訓練數據(x, y)。
(3) 如果\(y(wx+b)\leq0\),則
(4)轉至(2)直到沒有誤分類點。
c++實現感知機
代碼結構
實現
首先我有一個基類Base,為了以后的算法繼承用的,它包含一個run()的純虛函數,這樣以后就可以在main里面實現多態。
我的數據都存儲在私有成員里:
std::vector<std::vector<double>> inData;//從文件都的數據
std::vector<std::vector<double>> trainData;//分割后的訓練數據,里面包含真值
std::vector<std::vector<double>> testData;
unsigned long indim = 0;
std::vector<double> w;
double b;
std::vector<std::vector<double>> trainDataF;//真正的訓練數據,特征
std::vector<std::vector<double>> testDataF;
std::vector<double> trainDataGT;//真值
std::vector<double> testDataGT;
在main函數里只需要調用每個模型的run()方法,聲明的是基類指針:
int main() {
Base* obj = new Perceptron();
obj->run();
delete obj;
return 0;
}
第一步,讀取數據並分割。這里用的vector存儲。
getData("../data/perceptrondata.txt");
splitData(0.6);//below is split data , and store it in trainData, testData
第二步初始化
std::vector<double> init = {1.0,1.0,1.0};
initialize(init);
第三步進行訓練。
在訓練時,函數調用順序如下:
-
調用computeGradient,進行梯度的計算。對於滿足\(y(wx+b)>0\)的數據我們把梯度設為0。
std::pair<std::vector<double>, double> Perceptron::computeGradient(const std::vector<double>& inputData, const double& groundTruth) { double lossVal = loss(inputData, groundTruth); std::vector<double> w; double b; if (lossVal > 0.0) { for(auto indata:inputData) { w.push_back(indata*groundTruth); } b = groundTruth; } else{ for(auto indata:inputData) { w.push_back(0.0); } b = 0.0; } return std::pair<std::vector<double>, double>(w, b);//here, for understandable, we use pair to represent w and b. //you also could return a vector which contains w and b. }
在調用computeGradient時又調用了loss,即計算\(-y(wx+b)\),loss里調用了inference,用來計算\(wx+b\),看起來有點多余對吧,inference函數存在的目的是為了后面預測時候用的。
double Perceptron::loss(const std::vector<double>& inputData, const double& groundTruth){ double loss = -1.0 * groundTruth * inference(inputData); std::cout<<"loss is "<< loss <<std::endl; return loss; }
double Perceptron::inference(const std::vector
//just compute wx+b , for compute loss and predict.
if (inputData.size()!=indim){
std::cout<<"input dimension is incorrect. "<<std::endl;
throw inputData.size();
}
double sum_tem = 0.0;
sum_tem = inputData * w;
sum_tem += b;
return sum_tem;
}
- 根據計算的梯度更新w, b
```c++
void Perceptron::train(const int & step, const float & lr) {
int count = 0;
createFeatureGt();
for(int i=0; i<step; ++i){
if (count==trainDataF.size()-1)
count = 0;
count++;
std::vector<double> inputData = trainDataF[count];
double groundTruth = trainDataGT[count];
auto grad = computeGradient(inputData, groundTruth);
auto grad_w = grad.first;
double grad_b = grad.second;
for (int j=0; j<indim;++j){//這里更新參數
w[j] += lr * (grad_w[j]);
}
b += lr * (grad_b);
}
}
- 預測用的數據也是之前就分割好的,注意這里的參數始終存在
std::vector<double> paraData;
進行預測的代碼
int Perceptron::predict(const std::vector<double>& inputData, const double& GT) {
double out = inference(inputData);
std::cout<<"The right class is "<<GT<<std::endl;
if(out>=0.0){
std::cout<<"The predict class is 1"<<std::endl;
return 1;
}
else{
std::cout<<"The right class is -1"<<std::endl;
return -1;
}