线性回归介绍及分别使用最小二乘法和梯度下降法对线性回归C++实现
回歸:在這類任務中,計算機程序需要對給定輸入預測數值。為了解決這個任務,學習算法需要輸出函數f:Rn→R。除了返回結果的形式不一樣外,這類問題和分類問題是很像的。這類任務的一個示例是預測投保人的索賠金額(用于設置保險費),或者預測證券未來的價格。這類預測也用在算法交易中。
線性回歸解決回歸問題。換言之,我們的目標是建立一個系統,將向量x∈Rn作為輸入,預測標量y∈R作為輸出。線性回歸的輸出是其輸入的線性函數。另y’表示模型預測y應該取的值。我們定義輸出為:y’=wTx, 其中w∈Rn的參數(parameter)向量。
參數是控制系統行為的值。在這種情況下,wi是系數,會和特征xi相乘之后全部相加起來。我們可以將w看作是一組決定每個特征如何影響預測的權重(weight)。如果特征xi對應的權重wi是正的,那么特征的值增加,我們的預測值y’也會增加。如果特征xi對應的權重wi是負的,那么特征的值減少,我們的預測值y’也會減少。如果特征權重的大小很大,那么它對預測有很大的影響;如果特征權重的大小是零,那么它對預測沒有影響。
因此,我們可以定義任務T:通過輸出y’=wTx從x預測y。接下來我們需要定義性能度量P。
假設我們有m個輸入樣本組成的設計矩陣,我們不用它來訓練模型,而是評估模型性能如何。我們也有每個樣本對應的正確值y組成的回歸目標向量。因為這個數據集只是用來評估性能,我們稱之為測試集(test set)。我們將輸入的設計矩陣記作X(test),回歸目標向量記作y(test)。
度量模型性能的一種方法是計算模型在測試集上的均方誤差(mean squared error)。如果y’(test)表示模型在測試集上的預測值,那么均方誤差表示為:
直觀上,當y’(test)=y(test)時,我們會發現誤差降為0。我們也可以看到:
所以當預測值和目標值之間的歐幾里得距離增加時,誤差也會增加。
為了構建一個機器學習算法,我們需要設計一個算法,通過觀察訓練集(X(train),y(train))獲得經驗,減少MSEtest以改進權重w。一種直觀方式是最小化訓練集上的均方誤差,即MSEtrain。
最小化MSEtrain,我們可以簡單地求解其導數為0的情況:▽wMSEtrain=0
=> w=(X(train)TX(train))-1X(train)Ty(train)
上式給出解的系統方程被稱為正規方程(normal equation)。計算上式構成了一個簡單的機器學習算法。
線性回歸(linear regression)通常用來指稍微復雜一些,附加額外參數(截距項b)的模型。在這個模型中,y’=wTx+b。因此從參數到預測的映射仍是一個線性函數,而從特征到預測的映射是一個仿射函數。如此擴展到仿射函數意味著模型預測的曲線仍然看起來像是一條直線,只是這條直線沒必要經過原點。除了通過添加偏置參數b,我們還可以使用僅含權重的模型,但是x需要增加一項永遠為1的元素。對應于額外1的權重起到了偏置參數的作用。
截距項b通常被稱為仿射變換的偏置(bias)參數。這個術語的命名源自該變換的輸出在沒有任何輸入時會偏移b。
在統計學中,線性回歸(linear regression)是利用稱為線性回歸方程的最小二乘函數對一個或多個自變量和因變量之間關系進行建模的一種回歸分析。這種函數是一個或多個稱為回歸系數的模型參數的線性組合。只有一個自變量的情況稱為簡單回歸,大于一個自變量的情況叫多元回歸。
在線性回歸中,數據使用線性預測函數來建模,并且未知的模型參數也是通過數據來估計。這些模型被叫做線性模型。
線性回歸是回歸分析中第一種經過嚴格研究并在實際應用中廣泛使用的類型。這是因為線性依賴于其未知參數的模型比非線性依賴于其位置參數的模型更容易擬合,而且產生的估計的統計特性也更容易確定。
線性回歸有很多實際用途。分為以下兩大類:
(1)、如果目標是預測或者映射,線性回歸可以用來對觀測數據集和X的值擬合出一個預測模型。當完成這樣一個模型以后,對于一個新增的X值,在沒有給定與它相配對的y的情況下,可以用這個擬合過的模型預測出一個y值。
(2)、給定一個變量y和一些變量X1,...,Xp,這些變量有可能與y相關,線性回歸分析可以用來量化y與Xj之間相關性的強度,評估出與y不相關的Xj,并識別出哪些Xj的子集包含了關于y的冗余信息。
線性回歸模型經常用最小二乘逼近來擬合,但也可用別的方法來擬合。最小二乘逼近也可以用來擬合那些非線性的模型。因此,盡管”最小二乘法”和”線性模型”是緊密相連的,但他們是不能劃等號的。
理論模型:給一個隨機樣本(Yi,Xi1,…,Xip),i=1,…,n,一個線性回歸模型假設回歸子Yi和回歸量Xi1,…,Xip之間的關系是除了X的影響以外,還有其它的變數存在。我們加入一個誤差項εi(也是一個隨機變量)來捕獲除了Xi1,…,Xip之外任何對Yi的影響。所以一個多變量回歸模型表示為以下的形式:
Yi=β0+β1Xi1+β2Xi2+…+βpXip+εi,i=1,…,n.
其它的模型可能被認定成非線性模型。一個線性回歸模型不需要是自變量的線性函數。線性在這里表示Yi的條件均值在參數β里是線性的。例如:模型Yi=β1Xi+β2Xi2+εi在β1和β2里是線性的,但在Xi2里是非線性的,它是Xi的非線性函數。
數據和估計:區分隨機變量和這些變量的觀測值是很重要的。通常來說,觀測值或數據包括了n個值(yi,xi1,…,xip),i=1,…,n. 我們有p+1個參數β0,…, βp需要決定,為了估計這些參數,使用矩陣標記:Y=Xβ+ε.其中Y是一個包括了觀測值Y1,…,Yn的列向量,ε包括了未觀測的隨機成分ε1,…, εn以及回歸量的觀測值矩陣X。X通常包括一個常數項。如果X列之間存在線性相關,那么參數向量β就不能以最小二乘法估計除非β被限制,比如要求它的一些元素之和為0。
線性回歸是利用數理統計中回歸分析,來確定兩種或兩種以上變量間相互依賴的定量關系的一種統計分析方法。回歸分析中,只包括一個自變量和一個因變量,且二者的關系可用一條直線近似表示,這種回歸分析稱為一元線性回歸分析。如果回歸分析中包括兩個或兩個以上的自變量,且因變量和自變量之間是線性關系,則稱為多元線性回歸分析。
以上內容主要摘自:?《深度學習中文版》?和 ?維基百科
以下是參考網上一些code分別通過最小二乘法和梯度下降法實現的線性回歸C++測試代碼:
linear_regression.hpp:
#ifndef NN_LINEAR_REGRESSION_HPP_
#define NN_LINEAR_REGRESSION_HPP_#include <memory>
#include <string>
#include <ostream>namespace ANN {typedef enum {LEAST_SQUARES = 0,GRADIENT_DESCENT
} regression_method;template<typename T>
class LinearRegression {template<typename U>friend std::ostream& operator << (std::ostream& out, const LinearRegression<U>& lr);public:LinearRegression() = default;void set_regression_method(regression_method method);int init(const T* x, const T* y, int length);int train(const std::string& model, T learning_rate = 0, int iterations = 0);int load_model(const std::string& model) const;T predict(T x) const; // y = wx+bprivate:int gradient_descent();int least_squares();int store_model() const;regression_method method;std::unique_ptr<T[]> x, y;std::string model = "";int iterations = 1000;int length = 0;T learning_rate = 0.001f;T weight = 0;T bias = 0;
};} // namespace ANN#endif // NN_LINEAR_REGRESSION_HPP_
linear_regression.cpp:
#include "linear_regression.hpp"
#include <fstream>
#include <random>
#include <algorithm>
#include <numeric>namespace ANN {template<typename T>
void LinearRegression<T>::set_regression_method(regression_method method)
{this->method = method;
}template<typename T>
int LinearRegression<T>::init(const T* x, const T* y, int length)
{if (length < 3) {fprintf(stderr, "number of points must be greater than 2: %d\n", length);return -1;}this->x.reset(new T[length]);this->y.reset(new T[length]);for (int i = 0; i < length; ++i) {this->x[i] = x[i];this->y[i] = y[i];}this->length = length;return 0;
}template<typename T>
int LinearRegression<T>::train(const std::string& model, T learning_rate, int iterations)
{this->learning_rate = learning_rate;this->iterations = iterations;this->model = model;if (this->method == LEAST_SQUARES) {least_squares();} else if (this->method == GRADIENT_DESCENT) {gradient_descent();} else {fprintf(stderr, "invalid linear regression method\n");return -1;}return store_model();
}template<typename T>
int LinearRegression<T>::store_model() const
{std::ofstream file;file.open(model.c_str(), std::ios::binary);if (!file.is_open()) {fprintf(stderr, "open file fail: %s\n", model.c_str());return -1;}int m = method;file.write((char*)&m, sizeof(m));file.write((char*)&weight, sizeof(weight));file.write((char*)&bias, sizeof(bias));file.close();return 0;
}template<typename T>
int LinearRegression<T>::load_model(const std::string& model) const
{std::ifstream file;file.open(model.c_str(), std::ios::binary);if (!file.is_open()) {fprintf(stderr, "open file fail: %s\n", model.c_str());return -1;}int m{ -1 };file.read((char*)&m, sizeof(m)* 1);file.read((char*)&weight, sizeof(weight)* 1);file.read((char*)&bias, sizeof(bias)* 1);file.close();return 0;
}template<typename T>
T LinearRegression<T>::predict(T x) const
{return weight * x + bias;
}template<typename T>
int LinearRegression<T>::gradient_descent()
{std::random_device rd; std::mt19937 generator(rd());std::uniform_real_distribution<T> distribution(0, 0.5);weight = distribution(generator);;bias = distribution(generator);;int count{ 0 };std::unique_ptr<T[]> error(new T[length]), error_x(new T[length]);for (int i = 0; i < iterations; ++i) {for (int j = 0; j < length; ++j) {error[j] = weight * x[j] + bias - y[j];error_x[j] = error[j] * x[j];}T error_ = std::accumulate(error.get(), error.get() + length, (T)0) / length;T error_x_ = std::accumulate(error_x.get(), error_x.get() + length, (T)0) / length;// error = p(i) - y(i)// bias(i+1) = bias(i) - learning_rate*errorbias = bias - learning_rate * error_;// weight(i+1) = weight(i) - learning_rate*error*xweight = weight - learning_rate * error_x_;++count;if (count % 100 == 0)fprintf(stdout, "iteration %d\n", count);}return 0;
}template<typename T>
int LinearRegression<T>::least_squares()
{T sum_x{ 0 }, sum_y{ 0 }, sum_x_squared{ 0 }, sum_xy{ 0 };for (int i = 0; i < length; ++i) {sum_x += x[i];sum_y += y[i];sum_x_squared += x[i] * x[i];sum_xy += x[i] * y[i];}if (fabs(length * sum_x_squared - sum_x * sum_x) > DBL_EPSILON) {weight = (length * sum_xy - sum_y * sum_x) / (length * sum_x_squared - sum_x * sum_x); // slopebias = (sum_x_squared * sum_y - sum_x * sum_xy) / (length * sum_x_squared - sum_x * sum_x); // intercept} else {weight = 0;bias = 0;}return 0;
}template<typename T>
std::ostream& operator << (std::ostream& out, const LinearRegression<T>& lr)
{out << "result: y = " << lr.weight << "x + " << lr.bias;return out;
}template std::ostream& operator << (std::ostream& out, const LinearRegression<float>& lr);
template std::ostream& operator << (std::ostream& out, const LinearRegression<double>& lr);
template class LinearRegression<float>;
template class LinearRegression<double>;} // namespace ANN
funset.cpp:
#include "funset.hpp"
#include <iostream>
#include "perceptron.hpp"
#include "BP.hpp""
#include "CNN.hpp"
#include "linear_regression.hpp"
#include "common.hpp"
#include <opencv2/opencv.hpp>int test_linear_regression_train()
{std::vector<float> x{6.2f, 9.5f, 10.5f, 7.7f, 8.6f, 6.9f, 7.3f, 2.2f, 5.7f, 2.f,2.5f, 4.f, 5.4f, 2.2f, 7.2f, 12.2f, 5.6f, 9.f, 3.6f, 5.f,11.3f, 3.4f, 11.9f, 10.5f, 10.7f, 10.8f, 4.8f};std::vector<float> y{ 29.f, 44.f, 36.f, 37.f, 53.f, 18.f, 31.f, 14.f, 11.f, 11.f,22.f, 16.f, 27.f, 9.f, 29.f, 46.f, 23.f, 39.f, 15.f, 32.f,34.f, 17.f, 46.f, 42.f, 43.f, 34.f, 19.f };CHECK(x.size() == y.size());ANN::LinearRegression<float> lr;lr.set_regression_method(ANN::GRADIENT_DESCENT);lr.init(x.data(), y.data(), x.size());float learning_rate{ 0.001f };int iterations{ 1000 };std::string model{ "E:/GitCode/NN_Test/data/linear_regression.model" };int ret = lr.train(model, learning_rate, iterations);if (ret != 0) {fprintf(stderr, "train fail\n");return -1;}std::cout << lr << std::endl; // y = wx + breturn 0;
}
int test_linear_regression_predict()
{ANN::LinearRegression<float> lr;std::string model{ "E:/GitCode/NN_Test/data/linear_regression.model" };int ret = lr.load_model(model);if (ret != 0) {fprintf(stderr, "load model fail: %s\n", model.c_str());return -1;}float x = 13.8f;float result = lr.predict(x);fprintf(stdout, "input value: %f, result value: %f\n", x, result);return 0;
}
以下是調用梯度下降法得到的train結果:
以下是predict的結果:
以下是通過Python實現的畫曲線圖:黃線是梯度下降法的結果,綠線是最小二乘法的結果
Python代碼如下:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.lines import Line2Dx = [6.2, 9.5, 10.5, 7.7, 8.6, 6.9, 7.3, 2.2, 5.7, 2.,2.5, 4., 5.4, 2.2, 7.2, 12.2, 5.6, 9., 3.6, 5.,11.3, 3.4, 11.9, 10.5, 10.7, 10.8, 4.8]
y = [29., 44., 36., 37., 53., 18., 31., 14., 11., 11.,22., 16., 27., 9., 29., 46., 23., 39., 15., 32.,34., 17., 46., 42., 43., 34., 19.]fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('Linear Regression')
plt.xlabel('X')
plt.ylabel('Y')
ax.scatter(x,y,c = 'r',marker = 'o')line1 = [(0.0, 6.0506), (40, 137.5202)] # least squares
line2 = [(0, 1.68624), (40, 154.01864)] # gradient descent(line1_xs, line1_ys) = zip(*line1)
(line2_xs, line2_ys) = zip(*line2)ax.add_line(Line2D(line1_xs, line1_ys, linewidth=2, color='green')) # least squares
ax.add_line(Line2D(line2_xs, line2_ys, linewidth=2, color='yellow')) # gradient descentplt.show()
GitHub:? https://github.com/fengbingchun/NN_Test
總結
以上是生活随笔為你收集整理的线性回归介绍及分别使用最小二乘法和梯度下降法对线性回归C++实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pip、NumPy、Matplotlib
- 下一篇: C++11中std::initializ