深度学习中的优化算法之BGD
? ? ? 之前在https://blog.csdn.net/fengbingchun/article/details/75351323 介紹過梯度下降,常見的梯度下降有三種形式:BGD、SGD、MBGD,它們的不同之處在于我們使用多少數據來計算目標函數的梯度。
? ? ? 大多數深度學習算法都涉及某種形式的優化。優化指的是改變x以最小化或最大化某個函數f(x)的任務。我們通常以最小化f(x)指代大多數最優化問題。我們把要最小化或最大化的函數稱為目標函數(objective function)或準則(criterion)。當我們對其進行最小化時,我們也把它稱為成本函數(cost function)、損失函數(loss function)或誤差函數(error function)。
? ? ? 梯度下降是深度學習中一種常用的優化技術。梯度是函數的斜率。它衡量一個變量響應另一個變量的變化而變化的程度。在數學上,梯度下降是一個凸函數,其輸出是輸入的一組參數的偏導數。梯度越大,坡度越陡(the greater the gradient, the steeper the slope)。從初始值開始,迭代運行梯度下降以找到參數的最佳值,以找到給定成本函數的最小可能值。
? ? ? 梯度下降是一種優化算法,通常用于尋找深度學習算法中的權值及系數(weights or coefficients),如邏輯回歸。它的工作原理是讓模型對訓練數據進行預測,并使用預測中的error來更新模型從而減少error(It works by having the model make predictions on training data and using the error on the predictions to update the model in such a way as to reduce the error)。
? ? ? 該算法的目標是找到使模型在訓練數據集上的誤差最小化的模型參數(e.g. coefficients or weights)。它通過對模型進行更改,使其沿著誤差的梯度或斜率向下移動到最小誤差值來實現這一點。這使該算法獲得了"梯度下降"的名稱。
? ? ? 梯度下降是深度學習中非常流行的優化算法。它的目標是搜索目標函數或成本函數(objective function or cost function)的全局最小值。這只有在目標函數是凸函數時才有可能,這間接意味著該函數將是碗形的。在非凸函數的情況下,梯度下降會找到最近的最小值,這個函數的最小值稱為局部最小值。
? ? ? 梯度下降是一種一階優化算法。這意味著在更新參數時它只考慮函數的一階導數。我們的主要目標是在每次迭代中使梯度沿最陡斜率的方向行進,我們在與目標函數的梯度相反的方向上更新參數。
? ? ? 圖解說明:假設只有weight沒有bias。如果weight(w)的特定值的斜率>0,則表示我們在最優w*的右側,在這種情況下,更新將是負數,并且w將開始接近最優w*。但是,如果weight(w)的特定值的斜率<0,則更新將為正值,并將當前值增加到w以收斂到w*的最佳值。以下截圖來自于https://www.machinelearningman.com:重復該方法,直到成本函數收斂。
?? ? ? 在https://blog.csdn.net/fengbingchun/article/details/79370310 中有梯度下降應用于二分類的公式推導。
? ? ? BGD(Batch Gradient Descent):批量梯度下降,是梯度下降法最原始的形式,它計算訓練數據集中每個樣本的誤差(error),使用所有的樣本來進行參數更新。整個訓練數據集的一個周期(one cycle)稱為一個訓練epoch。BGD是在每個訓練epoch結束時才進行模型更新。
? ? ? 梯度下降是一種最小化目標函數的方法:θ為模型的參數,J(θ)為目標函數,以下截圖來自https://arxiv.org/pdf/1609.04747.pdf?
???????
?
? ? ? 批量梯度下降可以使用固定的學習率。同時處理整個訓練集,只有處理完整個訓練集才更新一次權值和偏置。
? ? ? 優點:
? ? ? (1).對模型的更新較小意味著這種形式的梯度下降比隨機梯度下降的計算效率更高。
? ? ? (2).降低的(decreased)的更新頻率會產生更穩定的誤差梯度(error gradient),在某些問題上可能產生更穩定的收斂。
? ? ? (3).預測誤差(prediction error)的計算和模型更新的分離使算法適用于基于并行處理的實現。
? ? ? 缺點:
? ? ? (1).更穩定的誤差梯度可能會導致模型過早收斂到一組不太理想的參數。
? ? ? (2).訓練結束時的更新需要在所有訓練樣本中累積預測誤差的額外復雜性(additional complexity)。
? ? ? (3).通常需要將整個訓練數據集加載到內存中以供算法使用。
? ? ? (4).對于大型數據集,模型更新以及訓練速度可能會變得非常慢。
? ? ? 以上內容主要參考:
? ? ? 1. https://arxiv.org/pdf/1609.04747.pdf
? ? ? 2.?https://machinelearningmastery.com/
? ? ? 3. https://www.machinelearningman.com
? ? ? 以下的測試代碼以https://blog.csdn.net/fengbingchun/article/details/79346691?中邏輯回歸實現的基礎上進行調整:
? ? ? logistic_regression2.hpp:
#ifndef FBC_SRC_NN_LOGISTIC_REGRESSION2_HPP_
#define FBC_SRC_NN_LOGISTIC_REGRESSION2_HPP_#include <vector>
#include <string>namespace ANN {enum class ActivationFunction {Sigmoid // logistic sigmoid function
};enum class LossFunction {MSE // Mean Square Error
};enum class Optimzation {BGD, // Batch_Gradient_DescentSGD, // Stochastic Gradient DescentMBGD // Mini-batch Gradient Descent
};template<typename T>
class LogisticRegression2 { // two categories
public:LogisticRegression2() = default;int init(const T* data, const T* labels, int train_num, int feature_length, T learning_rate = 0.00001, int epochs = 300);int train(const std::string& model);int load_model(const std::string& model);T predict(const T* data, int feature_length) const; // y = 1/(1+exp(-(wx+b)))private:int store_model(const std::string& model) const;T calculate_z(const std::vector<T>& feature) const; // z(i)=w^T*x(i)+bT calculate_cost_function() const;T calculate_activation_function(T value) const;T calculate_loss_function() const;T calculate_loss_function_derivative() const;T calculate_loss_function_derivative(unsigned int index) const;void calculate_gradient_descent();std::vector<std::vector<T>> x_; // training setstd::vector<T> y_; // ground truth labelsstd::vector<T> o_; // predict valueint epochs_ = 100; // epochsint m_ = 0; // train samples numint feature_length_ = 0; // weights lengthT alpha_ = (T)0.00001; // learning ratestd::vector<T> w_; // weightsT b_ = (T)0.; // thresholdActivationFunction activation_func_ = ActivationFunction::Sigmoid;LossFunction loss_func_ = LossFunction::MSE;Optimzation optim_ = Optimzation::BGD;
}; // class LogisticRegression2} // namespace ANN#endif // FBC_SRC_NN_LOGISTIC_REGRESSION2_HPP_
? ? ? logistic_regression2.cpp:
#include "logistic_regression2.hpp"
#include <fstream>
#include <algorithm>
#include <random>
#include <cmath>
#include "common.hpp"namespace ANN {template<typename T>
int LogisticRegression2<T>::init(const T* data, const T* labels, int train_num, int feature_length, T learning_rate, int epochs)
{if (train_num < 2) {fprintf(stderr, "logistic regression train samples num is too little: %d\n", train_num);return -1;}if (learning_rate <= 0) {fprintf(stderr, "learning rate must be greater 0: %f\n", learning_rate);return -1;}if (epochs <= 0) {fprintf(stderr, "number of epochs cannot be zero or a negative number: %d\n", epochs);return -1;}alpha_ = learning_rate;epochs_ = epochs;m_ = train_num;feature_length_ = feature_length;x_.resize(m_);y_.resize(m_);o_.resize(m_);for (int i = 0; i < m_; ++i) {const T* p = data + i * feature_length_;x_[i].resize(feature_length_);for (int j = 0; j < feature_length_; ++j) {x_[i][j] = p[j];}y_[i] = labels[i];}return 0;
}template<typename T>
int LogisticRegression2<T>::train(const std::string& model)
{CHECK(x_.size() == y_.size());w_.resize(feature_length_, (T)0.);generator_real_random_number(w_.data(), feature_length_, (T)-0.01f, (T)0.01f, true);generator_real_random_number(&b_, 1, (T)-0.01f, (T)0.01f);for (int iter = 0; iter < epochs_; ++iter) {calculate_gradient_descent();fprintf(stdout, "echoch: %d, cost function: %f\n", iter, calculate_cost_function());}CHECK(store_model(model) == 0);return 0;
}template<typename T>
int LogisticRegression2<T>::load_model(const std::string& model)
{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 length{ 0 };file.read((char*)&length, sizeof(length));w_.resize(length);feature_length_ = length;file.read((char*)w_.data(), sizeof(T)*w_.size());file.read((char*)&b_, sizeof(T));file.close();return 0;
}template<typename T>
T LogisticRegression2<T>::predict(const T* data, int feature_length) const
{CHECK(feature_length == feature_length_);T value{ (T)0. };for (int t = 0; t < feature_length_; ++t) {value += data[t] * w_[t];}value += b_;return (calculate_activation_function(value));
}template<typename T>
int LogisticRegression2<T>::store_model(const std::string& 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 length = w_.size();file.write((char*)&length, sizeof(length));file.write((char*)w_.data(), sizeof(T) * w_.size());file.write((char*)&b_, sizeof(T));file.close();return 0;
}template<typename T>
T LogisticRegression2<T>::calculate_z(const std::vector<T>& feature) const
{T z{ 0. };for (int i = 0; i < feature_length_; ++i) {z += w_[i] * feature[i];}z += b_;return z;
}template<typename T>
T LogisticRegression2<T>::calculate_cost_function() const
{/*// J+=-1/m([y(i)*loga(i)+(1-y(i))*log(1-a(i))])// Note: log0 is not definedT J{0.};for (int i = 0; i < m_; ++i)J += -(y_[i] * std::log(o_[i]) + (1 - y_[i]) * std::log(1 - o_[i]) );return J/m_;*/T J{0.};for (int i = 0; i < m_; ++i)J += 1./2*std::pow(y_[i] - o_[i], 2);return J/m_;
}template<typename T>
T LogisticRegression2<T>::calculate_activation_function(T value) const
{switch (activation_func_) {case ActivationFunction::Sigmoid:default: // Sigmoidreturn ((T)1 / ((T)1 + std::exp(-value))); // y = 1/(1+exp(-value))}
}template<typename T>
T LogisticRegression2<T>::calculate_loss_function() const
{switch (loss_func_) {case LossFunction::MSE:default: // MSET value = 0.;for (int i = 0; i < m_; ++i) {value += 1/2.*std::pow(y_[i] - o_[i], 2);}return value/m_;}
}template<typename T>
T LogisticRegression2<T>::calculate_loss_function_derivative() const
{switch (loss_func_) {case LossFunction::MSE:default: // MSET value = 0.;for (int i = 0; i < m_; ++i) {value += o_[i] - y_[i];}return value/m_;}
}template<typename T>
T LogisticRegression2<T>::calculate_loss_function_derivative(unsigned int index) const
{switch (loss_func_) {case LossFunction::MSE:default: // MSEreturn (o_[index] - y_[index]);}
}template<typename T>
void LogisticRegression2<T>::calculate_gradient_descent()
{switch (optim_) {case Optimzation::BGD:default: // BGDT db = (T)0.;std::vector<T> dw(feature_length_, (T)0.), z(m_, (T)0), dz(m_, (T)0);for (int i = 0; i < m_; ++i) {z[i] = calculate_z(x_[i]);o_[i] = calculate_activation_function(z[i]);dz[i] = calculate_loss_function_derivative(i);for (int j = 0; j < feature_length_; ++j) {dw[j] += x_[i][j] * dz[i]; // dw(i)+=x(i)(j)*dz(i)}db += dz[i]; // db+=dz(i)}for (int j = 0; j < feature_length_; ++j) {dw[j] /= m_;w_[j] -= alpha_ * dw[j];}b_ -= alpha_*(db/m_);}
}template class LogisticRegression2<float>;
template class LogisticRegression2<double>;} // namespace ANN
? ? ? test.cpp:
int test_logistic_regression2_train()
{
#ifdef _MSC_VERconst std::string image_path{ "E:/GitCode/NN_Test/data/images/digit/handwriting_0_and_1/" };
#elseconst std::string image_path{ "data/images/digit/handwriting_0_and_1/" };
#endifcv::Mat data, labels;for (int i = 1; i < 11; ++i) {const std::vector<std::string> label{ "0_", "1_" };for (const auto& value : label) {std::string name = std::to_string(i);name = image_path + value + name + ".jpg";cv::Mat image = cv::imread(name, 0);if (image.empty()) {fprintf(stderr, "read image fail: %s\n", name.c_str());return -1;}data.push_back(image.reshape(0, 1));}}data.convertTo(data, CV_32F);std::unique_ptr<float[]> tmp(new float[20]);for (int i = 0; i < 20; ++i) {if (i % 2 == 0) tmp[i] = 0.f;else tmp[i] = 1.f;}labels = cv::Mat(20, 1, CV_32FC1, tmp.get());ANN::LogisticRegression2<float> lr;const float learning_rate{ 0.00001f };const int iterations{ 1000 };int ret = lr.init((float*)data.data, (float*)labels.data, data.rows, data.cols);if (ret != 0) {fprintf(stderr, "logistic regression init fail: %d\n", ret);return -1;}#ifdef _MSC_VERconst std::string model{ "E:/GitCode/NN_Test/data/logistic_regression2.model" };
#elseconst std::string model{ "data/logistic_regression2.model" };
#endifret = lr.train(model);if (ret != 0) {fprintf(stderr, "logistic regression train fail: %d\n", ret);return -1;}return 0;
}int test_logistic_regression2_predict()
{
#ifdef _MSC_VERconst std::string image_path{ "E:/GitCode/NN_Test/data/images/digit/handwriting_0_and_1/" };
#elseconst std::string image_path{ "data/images/digit/handwriting_0_and_1/" };
#endifcv::Mat data, labels, result;for (int i = 11; i < 21; ++i) {const std::vector<std::string> label{ "0_", "1_" };for (const auto& value : label) {std::string name = std::to_string(i);name = image_path + value + name + ".jpg";cv::Mat image = cv::imread(name, 0);if (image.empty()) {fprintf(stderr, "read image fail: %s\n", name.c_str());return -1;}data.push_back(image.reshape(0, 1));}}data.convertTo(data, CV_32F);std::unique_ptr<int[]> tmp(new int[20]);for (int i = 0; i < 20; ++i) {if (i % 2 == 0) tmp[i] = 0;else tmp[i] = 1;}labels = cv::Mat(20, 1, CV_32SC1, tmp.get());CHECK(data.rows == labels.rows);#ifdef _MSC_VERconst std::string model{ "E:/GitCode/NN_Test/data/logistic_regression2.model" };
#elseconst std::string model{ "data/logistic_regression2.model" };
#endifANN::LogisticRegression2<float> lr;int ret = lr.load_model(model);if (ret != 0) {fprintf(stderr, "load logistic regression model fail: %d\n", ret);return -1;}for (int i = 0; i < data.rows; ++i) {float probability = lr.predict((float*)(data.row(i).data), data.cols);fprintf(stdout, "probability: %.6f, ", probability);if (probability > 0.5) fprintf(stdout, "predict result: 1, ");else fprintf(stdout, "predict result: 0, ");fprintf(stdout, "actual result: %d\n", ((int*)(labels.row(i).data))[0]);}return 0;
}
? ? ? train執行結果如下:
? ? ? predict執行結果如下:
? ? ? GitHub:?https://github.com/fengbingchun/NN_Test
總結
以上是生活随笔為你收集整理的深度学习中的优化算法之BGD的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网络数据包分析软件Wireshark简介
- 下一篇: 深度学习中的优化算法之MBGD