AlgoC++第五课:基于矩阵的算法实现

news2024/12/23 1:30:15

目录

  • 基于矩阵的算法实现
    • 前言
    • 1. 矩阵
    • 2. 矩阵求导推导
    • 3. 矩阵示例代码
      • 3.1 Matrix.hpp
      • 3.2 Matrix.cpp
      • 3.3 main.cpp
      • 3.4 拓展-cblas_sgemm
      • 3.5 拓展-LU分解
    • 4. 多元线性回归
    • 5. 多元逻辑回归
    • 6. 最小二乘法
    • 7. 岭回归(L2)
    • 8. 多元牛顿法
    • 9. 高斯牛顿法
    • 10. Levenberg-Marquardt(修正牛顿法/阻尼牛顿法)
    • 总结

基于矩阵的算法实现

前言

手写AI推出的全新面向AI算法的C++课程 Algo C++,链接。记录下个人学习笔记,仅供自己参考。

本次课程主要是讲解基于矩阵的算法实现

课程大纲可看下面的思维导图

在这里插入图片描述

1. 矩阵

先回忆下矩阵相关知识

定义矩阵乘法
{ a b d e } × { 1 3 2 4 } = { a 1 + b 2 a 3 + b 4 d 1 + e 2 d 3 + e 4 } \left\{\begin{array}{cc}a&b\\ d&e\end{array}\right\}\times\left\{\begin{array}{cc}1&3\\ 2&4\end{array}\right\}=\left\{\begin{array}{cc}a1+b2&a3+b4\\ d1+e2&d3+e4\end{array}\right\} {adbe}×{1234}={a1+b2d1+e2a3+b4d3+e4}
记法:C[r][c] = 乘加(A中取 r 行,B中取 c 列)

在这里插入图片描述

参考:https://www.cnblogs.com/ljy-endl/p/11411665.html

矩阵求导

对于 $ A\cdot B = C$ 定义 L L L 是关于 C C C 的损失函数

G = ∂ L ∂ C G = \dfrac{\partial L}{\partial C} G=CL 若直接 C C C A A A 求导,则 G G G 定义为 C C C 大小的全 1 矩阵,则有:
∂ L ∂ A = G ⋅ B T       ∂ L ∂ B = A T ⋅ G \dfrac{\partial L}{\partial A}=G\cdot B^T \ \ \ \ \ \dfrac{\partial L}{\partial B}=A^T \cdot G AL=GBT     BL=ATG
矩阵求导示例

import torch
import torch.nn as nn

X = nn.parameter.Parameter(torch.tensor([
    [1, 2],
    [2, 1],
    [0, 2]
], dtype=torch.float32))

theta = nn.parameter.Parameter(torch.tensor([
    [5, 1, 0],
    [2, 3, 1]
], dtype=torch.float32))

loss = (X @ theta).sum()
loss.backward()

print(f"Loss = {loss.item()}")
print(f"dloss / dX = \n{X.grad}")
print(f"dloss / dtheta = \n{theta.grad}")

print("================手动计算矩阵的导数===================")
G = torch.ones_like(X @ theta)
X_grad = G @ theta.data.T
theta_grad = X.data.T @ G
print(f"dloss / dX = \n{X_grad}")
print(f"dloss / dtheta = \n{theta_grad}")

输出结果如下:

Loss = 48.0
dloss / dX =
tensor([[6., 6.],
        [6., 6.],
        [6., 6.]])
dloss / dtheta =
tensor([[3., 3., 3.],
        [5., 5., 5.]])
================手动计算矩阵的导数===================
dloss / dX =
tensor([[6., 6.],
        [6., 6.],
        [6., 6.]])
dloss / dtheta =
tensor([[3., 3., 3.],
        [5., 5., 5.]])

通过上述验证可以看到利用推导的公式计算的结果和梯度反向传播的结果一致,说明我们推导的公式并无错误。

2. 矩阵求导推导

  1. 考虑矩阵乘法 A ⋅ B = C A \cdot B = C AB=C

  2. 考虑 Loss 函数 L = ∑ i m ∑ j n ( C i j − p ) 2 L = \sum^m_{i}\sum^n_{j}{(C_{ij} - p)^2} L=imjn(Cijp)2

  3. 考虑 C C C 的每一项导数 ▽ C i j = ∂ L ∂ C i j \triangledown C_{ij} = \frac{\partial L}{\partial C_{ij}} Cij=CijL

  4. 考虑 A B C ABC ABC 都为 2x2 矩阵时,定义 G G G L L L C C C 的导数
    A = [ a b c d ] B = [ e f g h ] C = [ i j k l ] G = ∂ L ∂ C = [ ∂ L ∂ i ∂ L ∂ j ∂ L ∂ k ∂ L ∂ l ] = [ w x y z ] A = \begin{bmatrix} a & b\\ c & d \end{bmatrix} \quad B = \begin{bmatrix} e & f \\ g & h \end{bmatrix} \quad C = \begin{bmatrix} i & j \\ k & l \end{bmatrix} \quad G = \frac{\partial L}{\partial C} = \begin{bmatrix} \frac{\partial L}{\partial i} & \frac{\partial L}{\partial j} \\ \frac{\partial L}{\partial k} & \frac{\partial L}{\partial l} \end{bmatrix} = \begin{bmatrix} w & x \\ y & z \end{bmatrix} A=[acbd]B=[egfh]C=[ikjl]G=CL=[iLkLjLlL]=[wyxz]

  5. 展开左边 A ⋅ B A \cdot B AB

C = [ i = a e + b g j = a f + b h k = c e + d g l = c f + d h ] C = \begin{bmatrix} i = ae + bg & j = af + bh\\ k = ce + dg & l = cf + dh \end{bmatrix} C=[i=ae+bgk=ce+dgj=af+bhl=cf+dh]

  1. L L L 对于每一个 A A A 的导数
    ▽ A i j = ∂ L ∂ A i j \triangledown A_{ij} = \frac{\partial L}{\partial A_{ij}} Aij=AijL

    ∂ L ∂ a = ∂ L ∂ i ∗ ∂ i ∂ a + ∂ L ∂ j ∗ ∂ j ∂ a ∂ L ∂ b = ∂ L ∂ i ∗ ∂ i ∂ b + ∂ L ∂ j ∗ ∂ j ∂ b ∂ L ∂ c = ∂ L ∂ k ∗ ∂ k ∂ c + ∂ L ∂ l ∗ ∂ l ∂ c ∂ L ∂ d = ∂ L ∂ k ∗ ∂ k ∂ d + ∂ L ∂ l ∗ ∂ l ∂ d \begin{aligned} \frac{\partial L}{\partial a} &= \frac{\partial L}{\partial i} * \frac{\partial i}{\partial a} + \frac{\partial L}{\partial j} * \frac{\partial j}{\partial a} \\ \frac{\partial L}{\partial b} &= \frac{\partial L}{\partial i} * \frac{\partial i}{\partial b} + \frac{\partial L}{\partial j} * \frac{\partial j}{\partial b} \\ \frac{\partial L}{\partial c} &= \frac{\partial L}{\partial k} * \frac{\partial k}{\partial c} + \frac{\partial L}{\partial l} * \frac{\partial l}{\partial c} \\ \frac{\partial L}{\partial d} &= \frac{\partial L}{\partial k} * \frac{\partial k}{\partial d} + \frac{\partial L}{\partial l} * \frac{\partial l}{\partial d} \end{aligned} aLbLcLdL=iLai+jLaj=iLbi+jLbj=kLck+lLcl=kLdk+lLdl

    ∂ L ∂ a = w e + x f ∂ L ∂ b = w g + x h ∂ L ∂ c = y e + z f ∂ L ∂ d = y g + z h \begin{aligned} \frac{\partial L}{\partial a} &= we + xf \\ \frac{\partial L}{\partial b} &= wg + xh \\ \frac{\partial L}{\partial c} &= ye + zf \\ \frac{\partial L}{\partial d} &= yg + zh \end{aligned} aLbLcLdL=we+xf=wg+xh=ye+zf=yg+zh

  2. 因此 A A A 的导数为

    ∂ L ∂ A = [ w e + x f w g + x h y e + z f y g + z h ] ∂ L ∂ A = [ w x y z ] [ e g f h ] \frac{\partial L}{\partial A} = \begin{bmatrix} we + xf & wg + xh\\ ye + zf & yg + zh \end{bmatrix} \quad \frac{\partial L}{\partial A} = \begin{bmatrix} w & x\\ y & z \end{bmatrix} \begin{bmatrix} e & g\\ f & h \end{bmatrix} AL=[we+xfye+zfwg+xhyg+zh]AL=[wyxz][efgh]

    ∂ L ∂ A = G ⋅ B T \frac{\partial L}{\partial A} = G \cdot B^T AL=GBT

  3. 同理 B B B 的导数为
    ∂ L ∂ e = w a + y c ∂ L ∂ f = x a + z c ∂ L ∂ g = w b + y d ∂ L ∂ h = x b + z d \begin{aligned} \frac{\partial L}{\partial e} &= wa + yc \\ \frac{\partial L}{\partial f} &= xa + zc \\ \frac{\partial L}{\partial g} &= wb + yd \\ \frac{\partial L}{\partial h} &= xb + zd \end{aligned} eLfLgLhL=wa+yc=xa+zc=wb+yd=xb+zd

    ∂ L ∂ B = [ w a + y c x a + z c w b + y d x b + z d ] ∂ L ∂ B = [ a c b d ] [ w x y z ] \frac{\partial L}{\partial B} = \begin{bmatrix} wa + yc & xa + zc\\ wb + yd & xb + zd \end{bmatrix} \quad \frac{\partial L}{\partial B} = \begin{bmatrix} a & c\\ b & d \end{bmatrix} \begin{bmatrix} w & x\\ y & z \end{bmatrix} BL=[wa+ycwb+ydxa+zcxb+zd]BL=[abcd][wyxz]

    ∂ L ∂ B = A T ⋅ G \frac{\partial L}{\partial B} = A^T \cdot G BL=ATG

3. 矩阵示例代码

3.1 Matrix.hpp

Matrix.hpp

#ifndef GEMM_HPP
#define GEMM_HPP

#include <vector>
#include <initializer_list>
#include <ostream>

/* 实现一个自定义的matrix类 */
class Matrix{
public:
    Matrix();
    Matrix(int rows, int cols, const std::initializer_list<float>& pdata={});

    /* 操作符重载,使得支持()操作 */
    const float& operator()(int irow, int icol)const {return data_[irow * cols_ + icol];}
    float& operator()(int irow, int icol){return data_[irow * cols_ + icol];}
    Matrix operator*(float value);
    Matrix operator-(const Matrix& other) const;
    int rows() const{return rows_;}
    int cols() const{return cols_;}
    Matrix view(int rows, int cols) const;
    Matrix power(float y) const;
    float reduce_sum() const;

    float* ptr() const{return (float*)data_.data();}
    Matrix gemm(const Matrix& other, bool at=false, bool bt=false, float alpha=1.0f, float beta=0.0f);
    Matrix inv();

private:
    int rows_ = 0;
    int cols_ = 0;
    std::vector<float> data_;
};

/* 全局操作符重载,使得能够被cout << m; */
std::ostream& operator << (std::ostream& out, const Matrix& m);

/* 对gemm的封装 */
Matrix gemm(const Matrix& a, bool ta, const Matrix& b, bool tb, float alpha, float beta);
Matrix inverse(const Matrix& a);

#endif // GEMM_HPP

Matrix.hpp 头文件中定义了一个矩阵类 Matrix,包含了矩阵的基本运算和一些高级的操作如:

  • 构造函数:可以通过指定矩阵的行数、列数和数据来构造矩阵。默认构造函数也被定义了,用来构空矩阵
  • () 操作符重载:使得支持类似数据的访问方式,如 M(0,0)
  • 矩阵乘法(gemm):Matrix 类实现了 GEMM 算法,可用于矩阵相乘。其中,at 和 bt 分别指示矩阵 A 和 B 是否要进行转置操作,alpha 和 beta 是系数。这个函数返回矩阵相乘的结果
  • 矩阵求逆(inv)
  • power:对矩阵的每个元素进行次幂运算
  • reduce_sum:计算矩阵所有元素的和
  • ptr:获取矩阵的数据指针

此外,Matrix.hpp 还包括了对于 Matrix 类的输出流操作符重载,使得矩阵可以被 cout 输出。

3.2 Matrix.cpp

Matrix.cpp


#include <vector>
#include <iostream>
#include <iomanip>
#include "openblas/cblas.h"
#include "openblas/lapacke.h"
#include "matrix.hpp"

Matrix::Matrix(){}
Matrix::Matrix(int rows, int cols, const std::initializer_list<float>& pdata){
    this->rows_ = rows;
    this->cols_ = cols;
    this->data_ = pdata;

    if(this->data_.size() < rows * cols)
        this->data_.resize(rows * cols);
}

Matrix Matrix::gemm(const Matrix& other, bool at, bool bt, float alpha, float beta){
    return ::gemm(*this, at, other, bt, alpha, beta);
}

Matrix Matrix::view(int rows, int cols) const{
    if(rows * cols != this->rows_ * this->cols_){
        printf("Invalid view to %d x %d\n", rows, cols);
        return Matrix();
    }
    Matrix newmat = *this;
    newmat.rows_ = rows;
    newmat.cols_ = cols;
    return newmat;
}

Matrix Matrix::operator-(const Matrix& other) const{
    Matrix output = *this;
    auto p0 = output.ptr();
    auto p1 = other.ptr();
    for(int i = 0; i < output.data_.size(); ++i)
        *p0++ -= *p1++;
    return output;
}

Matrix Matrix::power(float y) const{
    Matrix output = *this;
    auto p0 = output.ptr();
    for(int i = 0; i < output.data_.size(); ++i, ++p0)
        *p0 = std::pow(*p0, y);
    return output;
}

float Matrix::reduce_sum() const{
    auto p0 = this->ptr();
    float output = 0;
    for(int i = 0; i < this->data_.size(); ++i)
        output += *p0++;
    return output;
}

Matrix Matrix::inv(){
    return ::inverse(*this);
}

Matrix Matrix::operator*(float value){
    
    Matrix m = *this;
    for(int i = 0; i < data_.size(); ++i)
        m.data_[i] *= value;
    return m;
}

std::ostream& operator << (std::ostream& out, const Matrix& m){

    for(int i = 0; i < m.rows(); ++i){
        for(int j = 0; j < m.cols(); ++j){
            out << m(i, j) << "\t";
        }
        out << "\n";
    }
    return out;
}

Matrix gemm(const Matrix& a, bool ta, const Matrix& b, bool tb, float alpha, float beta){

    int a_elastic_rows = ta ? a.cols() : a.rows();   /* 如果转置,则维度转过来 */
    int a_elastic_cols = ta ? a.rows() : a.cols();   /* 如果转置,则维度转过来 */
    int b_elastic_rows = tb ? b.cols() : b.rows();   /* 如果转置,则维度转过来 */
    int b_elastic_cols = tb ? b.rows() : b.cols();   /* 如果转置,则维度转过来 */

    /* c是转置后维度的行和列 */
    Matrix c(a_elastic_rows, b_elastic_cols);

    int m = a_elastic_rows;
    int n = b_elastic_cols;
    int k = a_elastic_cols;
    int lda = a.cols();
    int ldb = b.cols();
    int ldc = c.cols();

    /* cblas的gemm调用风格,在以后也会存在 */
    cblas_sgemm(
        CblasRowMajor, ta ? CblasTrans : CblasNoTrans, tb ? CblasTrans : CblasNoTrans,
        m, n, k, alpha, a.ptr(), lda, b.ptr(), ldb, beta, c.ptr(), ldc
    );
    return c;
}

Matrix inverse(const Matrix& a){

    if(a.rows() != a.cols()){
        printf("Invalid to compute inverse matrix by %d x %d\n", a.rows(), a.cols());
        return Matrix();
    }

    Matrix output = a;
    int n = a.rows();
    int *ipiv = new int[n];

    /* LU分解 */
    int code = LAPACKE_sgetrf(LAPACK_COL_MAJOR, n, n, output.ptr(), n, ipiv);
    if(code == 0){
        /* 使用LU分解求解通用逆矩阵 */
        code = LAPACKE_sgetri(LAPACK_COL_MAJOR, n, output.ptr(), n, ipiv);
    }

    if(code != 0){
        printf("LAPACKE inverse matrix failed, code = %d\n", code);
        return Matrix();
    }

    delete[] ipiv;
    return output;
}

Matrix.cpp 比较重要的就是矩阵乘法和矩阵求逆,其中 gemm 函数实现了矩阵乘法,调用了 BLAS 库中的 cblas_sgemm 函数;inverse 函数实现了矩阵求逆,使用 LAPACK 库中的 sgetrfsgetri 函数进行了 LU 分解求解通用逆矩阵。

3.3 main.cpp

main.cpp


#include <stdarg.h>
#include <iostream>
#include <vector>
#include "matrix.hpp"

using namespace std;

namespace Application{

    namespace logger{

        #define INFO(...)  Application::logger::__printf(__FILE__, __LINE__, __VA_ARGS__)

        void __printf(const char* file, int line, const char* fmt, ...){

            va_list vl;
            va_start(vl, fmt);

            printf("\e[32m[%s:%d]:\e[0m ", file, line);
            vprintf(fmt, vl);
            printf("\n");
        }
    };

    struct Point{
        float x, y;

        Point(float x, float y):x(x), y(y){}
        Point() = default;
    };

    Matrix mygemm(const Matrix& a, const Matrix& b){

        Matrix c(a.rows(), b.cols());
        for(int i = 0; i < c.rows(); ++i){
            for(int j = 0; j < c.cols(); ++j){
                float summary = 0;
                for(int k = 0; k < a.cols(); ++k)
                    summary += a(i, k) * b(k, j);

                c(i, j) = summary;
            }
        }
        return c;
    }

    /* 求解仿射变换矩阵 */
    Matrix get_affine_transform(const vector<Point>& src, const vector<Point>& dst){

        //         P                M        Y
        // x1, y1, 1, 0, 0, 0      m00       x1
        // 0, 0, 0, x1, y1, 1      m01       y1
        // x2, y2, 1, 0, 0, 0      m02       x2
        // 0, 0, 0, x2, y2, 1      m10       y2
        // x3, y3, 1, 0, 0, 0      m11       x3
        // 0, 0, 0, x3, y3, 1      m12       y3
        // Y = PM
        // P.inv() @ Y = M
        
        if(src.size() != 3 || dst.size() != 3){
            printf("Invalid to compute affine transform, src.size = %d, dst.size = %d\n", src.size(), dst.size());
            return Matrix();
        }

        Matrix P(6, 6, {
            src[0].x, src[0].y, 1, 0, 0, 0,
            0, 0, 0, src[0].x, src[0].y, 1,
            src[1].x, src[1].y, 1, 0, 0, 0,
            0, 0, 0, src[1].x, src[1].y, 1,
            src[2].x, src[2].y, 1, 0, 0, 0,
            0, 0, 0, src[2].x, src[2].y, 1
        });

        Matrix Y(6, 1, {
            dst[0].x, dst[0].y, dst[1].x, dst[1].y, dst[2].x, dst[2].y
        });
        return P.inv().gemm(Y).view(2, 3);
    }

    void test_matrix(){

        Matrix a1(2, 3, {
            1, 2, 3,
            4, 5, 6
        });

        Matrix b1(3, 2,{
            3, 0,
            2, 1,
            0, 2
        });

        INFO("A1 @ B1 = ");
        std::cout << a1.gemm(b1) << std::endl;

        Matrix a2(3, 2, {
            1, 4,
            2, 5,
            3, 6
        });

        INFO("A2.T @ B1 =");
        std::cout << gemm(a2, true, b1, false, 1.0f, 0.0f) << std::endl;

        INFO("A1 @ B1 = ");
        std::cout << mygemm(a1, b1) << std::endl;

        INFO("a2 * 2 = ");
        std::cout << a2 * 2 << std::endl;

        Matrix c(2, 2, {
            1, 2, 
            3, 4
        });
        INFO("C.inv = ");
        std::cout << c.inv() << std::endl;
        std::cout << c.gemm(c.inv()) << std::endl;
    }

    void test_affine(){

        auto M = get_affine_transform({
            Point(0, 0), Point(10, 0), Point(10, 10)
        }, 
        {
            Point(20, 20), Point(100, 20), Point(100, 100)
        });

        INFO("Affine matrix = ");
        std::cout << M << std::endl;
    }

    /* 测试矩阵求导的过程 */
    void test_matrix_derivation(){

        /* loss = (X @ theta).sum() */

        Matrix X(
            3, 2, {
                1, 2, 
                2, 1, 
                0, 2
            }
        );

        Matrix theta(
            2, 3, {
                5, 1, 0,
                2, 3, 1
            }
        );

        auto loss = X.gemm(theta).reduce_sum();

        // G = dloss / d(X @ theta) = ones_like(X @ theta)
        // dloss/dX     = G @ theta.T
        // dloss/dtheta = X.T @ G
        INFO("Loss = %f", loss);

        Matrix G(3, 3, {1, 1, 1, 1, 1, 1, 1, 1, 1});

        INFO("dloss / dX = ");
        std::cout << G.gemm(theta, false, true);

        INFO("dloss / dtheta = ");
        std::cout << X.gemm(G, true);

    }

    int run(){
        test_matrix();
        test_affine();
        test_matrix_derivation();
        return 0;
    }
};

int main(){
    return Application::run();
}

main.cpp 主要演示了如何使用自定义的矩阵类 Matrix,并对其进行了简单的测试。

test_matrix 函数中,对自定义矩阵类 Matrix 进行了简单的测试,包括矩阵乘法、矩阵转置、自定义矩阵乘法、矩阵数乘、矩阵求逆等操作。

test_affine_transform 函数中,实现了对三个点的仿射变换矩阵的求解。P 矩阵是一个6行6列的矩阵,代表源点的位置,Y 矩阵式一个6行1列的矩阵,代表目标点的坐标,通过计算 P 的逆矩阵 P_inv,并与 Y 矩阵相乘即可得到6行1列的仿射变换矩阵 M,最后将其 view 成为2行3列返回。具体可以参考YOLOv5推理详解及预处理高性能实现

test_matrix_derivation 函数中,展示了矩阵求导的过程,即给定一个由 X 和 theta 计算得到的损失函数,求解其对 X 和 theta 的偏导数。

我们还在代码中定义了一个名为 INFO 的宏,它会调用 Application::logger::__printf 函数,其参数 __FILE__ 和 __LINE__ 是内置变量,分别代表当前代码所在的源文件和行号。__VA_ARGS__ 则是一个可变参数的占位符,表示可以传入不定数量的参数。使用这个宏可以输出带有文件名和行号的日志信息

__printf 函数使用了可变参数列表,通过调用 vprintf 函数来实现格式化输出(from chatGPT)

  • const char* file 和 int line 是两个参数,它们分别表示所在的文件名和行号;
  • const char* fmt 是格式化字符串,用于指定输出信息的格式;
  • … 代表可变参数列表,即后面可以跟上任意多个参数;
  • va_list vl 是可变参数列表的类型,它定义了一个指向参数列表的指针;
  • va_start 函数用于初始化参数列表,它将 vl 指向可变参数列表中的第一个参数;
  • printf("\e[32m[%s:%d]:\e[0m ", file, line) 用于输出带有颜色的文件名和行号信息,其中\e[32m 表示设置颜色为绿色,\e[0m 表示重置颜色
  • vprintf(fmt, vl) 用于输出格式化的信息,它根据 fmt 指定的格式和 vl 中的参数来生成输出;
  • printf(“\n”) 用于输出一个换行符,以结束这行日志信息的输出。

3.4 拓展-cblas_sgemm

cblas_sgemm 函数是 BLAS 库中的矩阵乘法函数,其参数如下:(from chatGPT)

void cblas_sgemm(
    const enum CBLAS_ORDER Order,
    const enum CBLAS_TRANSPOSE TransA,
    const enum CBLAS_TRANSPOSE TransB,
    const int M,
    const int N,
    const int K,
    const float alpha,
    const float *A,
    const int lda,
    const float *B,
    const int ldb,
    const float beta,
    float *C,
    const int ldc
);

其中各参数的含义如下:

  • Order:矩阵的存储顺序。CBLAS_ORDER 枚举类型,取值可以是 CblasRowMajor 或 CblasColMajor,分别表示按行存储和按列存储。
  • TransA:A 矩阵的转置情况。CBLAS_TRANSPOSE 枚举类型,取值可以是 CblasNoTrans(不转置)、CblasTrans(转置)或 CblasConjTrans(共轭转置)。
  • TransB:B 矩阵的转置情况。CBLAS_TRANSPOSE 枚举类型,取值可以是 CblasNoTrans(不转置)、CblasTrans(转置)或 CblasConjTrans(共轭转置)。
  • M:C 矩阵的行数。
  • N:C 矩阵的列数。
  • K:A 和 B 矩阵中共享的维度,即 A 矩阵的列数或 B 矩阵的行数。
  • alpha:乘法操作的系数,通常取值为1。
  • A:存储 A 矩阵的数组。
  • lda:A 矩阵每行的元素个数,通常为 A 矩阵的列数。
  • B:存储 B 矩阵的数组。
  • ldb:B 矩阵每行的元素个数,通常为 B 矩阵的列数。
  • beta:加法操作的系数,通常取值为 0。
  • C:存储结果 C 矩阵的数组。
  • ldc:C 矩阵每行的元素个数,通常为 C 矩阵的列数。

cblas_sgemm 函数会对 A、B、C 矩阵进行矩阵乘法运算,并将结果存储在 C 矩阵中。其中 A 矩阵的大小为 MxK,B 矩阵的大小为 KxN,C 矩阵的大小为 MxN

3.5 拓展-LU分解

LU 分解是一种将矩阵分解成下三角矩阵 L 和上三角矩阵 U 的方法,使得原矩阵可以表示为 LU 的乘积。利用 LU 分解,可以解线性方程组和计算矩阵的行列式、逆矩阵等操作。(from chatGPT)

对于一个 n × n n\times n n×n 的矩阵 A A A,可以通过 LU 分解得到:
A = L U A = LU A=LU
其中, L L L 是一个 n × n n\times n n×n 的下三角矩阵,而 U U U 是一个 n × n n\times n n×n 的上三角矩阵。具体来说,对于矩阵 A A A,可以通过高斯消元的方式得到 L L L U U U
[ a 11 a 12 a 13 … a 1 n a 21 a 22 a 23 … a 2 n a 31 a 32 a 33 … a 3 n ⋮ ⋮ ⋮ ⋱ ⋮ a n 1 a n 2 a n 3 … a n n ] = [ 1 l 21 1 l 31 l 32 1 ⋮ ⋮ ⋮ ⋱ l n 1 l n 2 l n 3 ⋯ 1 ] [ u 11 u 12 u 13 ⋯ u 1 n u 22 u 23 ⋯ u 2 n u 23 ⋯ u 3 n ⋱ ⋮ u n n ] \begin{bmatrix}a_{11}&a_{12}&a_{13}&\ldots&a_{1n}\\ a_{21}&a_{22}&a_{23}&\ldots&a_{2n}\\ a_{31}&a_{32}&a_{33}&\ldots&a_{3n}\\ \vdots&\vdots&\vdots&\ddots&\vdots\\ a_{n1}&a_{n2}&a_{n3}&\ldots&a_{nn}\end{bmatrix}= \begin{bmatrix}1\\l_{21}&1\\l_{31}&l_{32}&1\\\vdots&\vdots&\vdots&\ddots\\l_{n1}&l_{n2}&l_{n3}&\cdots&1\end{bmatrix} \begin{bmatrix}u_{11}&u_{12}&u_{13}&\cdots&u_{1n}\\ &u_{22}&u_{23}&\cdots&u_{2n}\\ &&u_{23}&\cdots&u_{3n}\\ &&&\ddots&\vdots\\ &&&&u_{nn}\end{bmatrix} a11a21a31an1a12a22a32an2a13a23a33an3a1na2na3nann = 1l21l31ln11l32ln21ln31 u11u12u22u13u23u23u1nu2nu3nunn
求解逆矩阵的方法就是利用 LU 分解得到的 L L L U U U 矩阵,通过下面的公式计算 A − 1 A^{-1} A1
A − 1 = U − 1 L − 1 A^{-1} = U^{-1}L^{-1} A1=U1L1
其中, L − 1 L^{-1} L1 是一个 n × n n\times n n×n 的下三角矩阵, U − 1 U^{-1} U1 是一个 n × n n\times n n×n 的上三角矩阵。

LAPACK库提供了一系列求解线性方程组和矩阵分解的函数。其中,LAPACK_sgetrf 函数用于进行 LU 分解,LAPACK_sgetri 函数用于计算矩阵的逆矩阵。

LAPACK_sgetrf 函数的参数:

  • order:表示矩阵数据的存储顺序,可以是LAPACK_ROW_MAJOR或者LAPACK_COL_MAJOR。
  • m:表示矩阵 A 的行数。
  • n:表示矩阵 A 的列数。
  • A:指向矩阵 A 的指针。
  • lda:表示矩阵 A 的行宽。
  • ipiv:指向一个长度为 min(m,n) 的整数数组,存储 LU 分解的行置换信息。

LAPACK_sgetrf 函数的返回值:

  • 如果返回值等于零,则表示操作成功完成。
  • 如果返回值小于零,则表示参数错误或某个 U(i,i) 为零,无法进行 LU 分解。
  • 如果返回值大于零,则表示 A 的前返回值列的 LU 分解出现奇异矩阵,无法求解。

LAPACK_sgetri 函数的参数:

  • order:表示矩阵数据的存储顺序,可以是LAPACK_ROW_MAJOR或者LAPACK_COL_MAJOR。
  • n:表示矩阵A的行数和列数。
  • A:指向矩阵A的指针。
  • lda:表示矩阵A的行宽。
  • ipiv:指向一个长度为n的整数数组,存储LU分解的行置换信息。

LAPACK_sgetri函数的返回值:

  • 如果返回值等于零,则表示操作成功完成。
  • 如果返回值小于零,则表示参数错误。
  • 如果返回值大于零,则表示某个 A(i,i) 为零,无法进行求解。

LAPACK_sgetrf 函数和 LAPACK_sgetri 函数的使用可以实现对逆矩阵的求解。具体来说,可以先使用LAPACK_sgetrf 函数进行 LU 分解,然后再使用 LAPACK_sgetri 函数对 LU 分解后的矩阵进行求逆。

4. 多元线性回归

多元线性回归模型如下:
h θ ( x ) = θ 0 x 0 + θ 1 x 1 + ⋯ + θ n x n = ∑ i = 0 n θ i x i = θ T X ( x 0 = 1 ) h_\theta(x)=\theta_0x_0+\theta_1x_1+\cdots+\theta_nx_n=\sum_{i=0}^n\theta_i x_i=\theta^T X(x_0=1) hθ(x)=θ0x0+θ1x1++θnxn=i=0nθixi=θTX(x0=1)
以我们之前讨论的房价预测的线性回归模型为例,特征有 x x x c o s ( x ) cos(x) cos(x) s i n ( x ) sin(x) sin(x)

X X X 是数据 n × 4 n\times 4 n×4 θ \theta θ 是参数 4 × 1 4\times 1 4×1 ,初始化为随机数
X = [ 1 x ( 1 ) s i n ( x ( 1 ) ) c o s ( x ( 1 ) ) ) 1 x ( 2 ) s i n ( x ( 2 ) ) c o s ( x ( 2 ) ) ⋯ 1 x ( n ) s i n ( x ( n ) ) c o s ( x ( n ) ) ) ]      θ = [ b i a s k i d e n t i t y k c o s k s i n ] = [ θ 1 θ 2 θ 3 θ 4 ] X=\left[\begin{array}{l l l l}{1}&{x^{(1)}}&{s i n(x^{(1)})}&{c o s(x^{(1)}))}\\ {1}&{x^{(2)}}&{s i n(x^{(2)})}&{c o s(x^{(2)})}\\ {}&{}&{\cdots}&{}\\ {1}&{x^{(n)}}&{s i n(x^{(n)})}&{c o s(x^{(n)}))}\\ \end{array}\right] \ \ \ \ \theta=\left[\begin{array}{c}{b i a s}\\ {k_{i d e n t i t y}}\\ {k_{c o s}}\\ {k_{s i n}}\\ \end{array}\right]=\left[\begin{array}{c}{\theta_{1}}\\ {\theta_{2}}\\ {\theta_{3}}\\ {\theta_{4}}\\ \end{array}\right] X= 111x(1)x(2)x(n)sin(x(1))sin(x(2))sin(x(n))cos(x(1)))cos(x(2))cos(x(n)))     θ= biaskidentitykcosksin = θ1θ2θ3θ4
对于预测值 P P P 等于:
P = [ p ( 1 ) p ( 2 ) ⋯ p ( n ) ] = X θ = [ 1 x ( 1 ) s i n ( x ( 1 ) ) c o s ( x ( 1 ) ) ) 1 x ( 2 ) s i n ( x ( 2 ) ) c o s ( x ( 2 ) ) ⋯ 1 x ( n ) s i n ( x ( n ) ) c o s ( x ( n ) ) ) ] [ b i a s k i d e n t i t y k c o s k s i n ] P={\left[\begin{array}{l}{p^{(1)}}\\ {p^{(2)}}\\ {\cdots}\\ {p^{(n)}}\end{array}\right]}=X\theta= \left[\begin{array}{l l l l}{1}&{x^{(1)}}&{s i n(x^{(1)})}&{c o s(x^{(1)}))}\\ {1}&{x^{(2)}}&{s i n(x^{(2)})}&{c o s(x^{(2)})}\\ {}&{}&{\cdots}&{}\\ {1}&{x^{(n)}}&{s i n(x^{(n)})}&{c o s(x^{(n)}))}\\ \end{array}\right] \left[\begin{array}{c}{b i a s}\\ {k_{identity}}\\ {k_{cos}}\\ {k_{sin}}\\ \end{array}\right] P= p(1)p(2)p(n) == 111x(1)x(2)x(n)sin(x(1))sin(x(2))sin(x(n))cos(x(1)))cos(x(2))cos(x(n))) biaskidentitykcosksin
对于真值,定义为 Y Y Y n × 1 n\times 1 n×1,则 Y Y Y 等于:
Y n x 1 = [ y ( 1 ) y ( 2 ) ⋯ y ( n ) ] Y_{n x1}=\left[\begin{array}{c}{{y^{(1)}}}\\ {{y^{(2)}}}\\ {{\cdots}}\\ {{y^{(n)}}}\end{array}\right] Ynx1= y(1)y(2)y(n)
对于 Loss 的计算,有如下:
L = 1 2 n ∑ i = 1 n ( p ( i ) − y ( i ) ) 2 L=\frac{1}{2n}\sum_{i=1}^n(p^{(i)}-y^{(i)})^2 L=2n1i=1n(p(i)y(i))2
根据前面的矩阵求导可得对于梯度的推导有:
∂ L ∂ P = 1 n ( P − Y ) ∂ L ∂ θ = 1 n X T ( P − Y ) \begin{aligned} &\frac{\partial L}{\partial P} =\frac{1}{n}(P-Y) \\ &\frac{\partial L}{\partial\theta} =\frac{1}{n}X^T(P-Y) \end{aligned} PL=n1(PY)θL=n1XT(PY)
示例代码如下

alpha = 0.01
n = len(X)
for i in range(100):
    L = 0.5 * ((X @ theta - Y)**2).sum() / n
    G = (X @ theta - Y) / n
    grad = X.T @ G
    theta = theta - alpha * grad

5. 多元逻辑回归

以我们之前讨论的居民幸福感的逻辑回归模型为例,特征有 area、distance

X X X 数据 n × 3 n\times 3 n×3 θ \theta θ 是参数 3 × 1 3\times 1 3×1,初始化为随机数
X = [ 1 a r e a ( 1 ) d i s t a n c e ( 1 ) 1 a r e a ( 2 ) d i s t a n c e ( 2 ) ⋯ 1 a r e a ( n ) d i s t a n c e ( n ) ]      θ = [ b i a s k a r e a k d i s t a n c e ] = [ θ 1 θ 2 θ 3 ] X=\left[\begin{array}{c c c}{{1}}&{{a r e a^{(1)}}}&{{d i s t a n c e^{(1)}}}\\ {{1}}&{{a r e a^{(2)}}}&{{d i s t a n c e^{(2)}}}\\ {{}}&{{}}&{{\cdots}}\\ {{1}}&{{a r e a^{(n)}}}&{{d i s t a n c e^{(n)}}}\end{array}\right] \ \ \ \ \theta=\left[\begin{array}{c}{b i a s}\\ {k_{area}}\\ {k_{distance}}\\ \end{array}\right]=\left[\begin{array}{c}{\theta_{1}}\\ {\theta_{2}}\\ {\theta_{3}}\\ \end{array}\right] X= 111area(1)area(2)area(n)distance(1)distance(2)distance(n)     θ= biaskareakdistance = θ1θ2θ3
对于预测值 P P P 等于:
P = [ p ( 1 ) p ( 2 ) ⋯ p ( n ) ] = X θ = [ 1 a r e a ( 1 ) d i s t a n c e ( 1 ) 1 a r e a ( 2 ) d i s t a n c e ( 2 ) ⋯ 1 a r e a ( n ) d i s t a n c e ( n ) ] [ b i a s k a r e a k d i s t a n c e ] P={\left[\begin{array}{l}{p^{(1)}}\\ {p^{(2)}}\\ {\cdots}\\ {p^{(n)}}\end{array}\right]}=X\theta= \left[\begin{array}{c c c}{{1}}&{{a r e a^{(1)}}}&{{d i s t a n c e^{(1)}}}\\ {{1}}&{{a r e a^{(2)}}}&{{d i s t a n c e^{(2)}}}\\ {{}}&{{}}&{{\cdots}}\\ {{1}}&{{a r e a^{(n)}}}&{{d i s t a n c e^{(n)}}}\end{array}\right] \left[\begin{array}{c}{b i a s}\\ {k_{area}}\\ {k_{distance}}\\ \end{array}\right] P= p(1)p(2)p(n) == 111area(1)area(2)area(n)distance(1)distance(2)distance(n) biaskareakdistance
对于真值,定义为 Y Y Y n × 1 n\times 1 n×1,则 Y Y Y 等于:
Y n x 1 = [ y ( 1 ) y ( 2 ) ⋯ y ( n ) ] Y_{n x1}=\left[\begin{array}{c}{{y^{(1)}}}\\ {{y^{(2)}}}\\ {{\cdots}}\\ {{y^{(n)}}}\end{array}\right] Ynx1= y(1)y(2)y(n)
H = s i g m o i d ( P ) H = sigmoid(P) H=sigmoid(P),对于 Loss的计算如下式:
L = − 1 2 n ∑ i = 1 n [ y ( i ) ⋅ l n ( h ( i ) ) + ( 1 − y ( i ) ) ⋅ l n ( 1 − h ( i ) ) ] L=-\frac{1}{2n}\sum_{i=1}^n[y^{(i)}\cdot ln(h^{(i)})+(1-y^{(i)})\cdot ln(1-h^{(i)})] L=2n1i=1n[y(i)ln(h(i))+(1y(i))ln(1h(i))]
此时,对于梯度的推导有:
∂ L ∂ P = 1 n ( H − Y ) ∂ L ∂ θ = 1 n X T ( H − Y ) \begin{aligned} &\frac{\partial L}{\partial P} =\frac{1}{n}(H-Y) \\ &\frac{\partial L}{\partial\theta} =\frac{1}{n}X^T(H-Y) \end{aligned} PL=n1(HY)θL=n1XT(HY)
示例代码如下

Python版:

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

alpha = 0.01
n = len(x)
for i in range(100):
    H = sigmoid(X @ theta)
    L = -(Y * np.log(H) + (1 - Y) * np.log(1 - H)).sum() / n
    
    G = (H - Y) / n
    grad = X.T @ G
    theta = theta - alpha * grad

C++版:

Matrix datas_matrix, label_matrix;
tie(datas_matrix, label_matrix) = statistics::datas_to_matrix(datas);

// Matrix theta = random::create_normal_distribution_matrix(3, 1);
Matrix theta(3, 1, {0, 0.1, 0.1});
int batch_size = datas.size();
float lr = 0.1;

for(int iter = 0; iter < 1000; ++iter){
    
    auto logistic = datas_matrix.gemm(theta).sigmoid();
    auto loss = -(logistic.log() * label_matrix + logistic.log_1subx() * label_matrix._1subx()).reduce.sum() / batch_size;
    
    auto G = (logistic - label_matrix) * (1.0f / batch_size);
    auto grad = datas_matrix.gemm(G, true);
    theta = theta - grad * lr;
    
    if(iter % 100 == 0)
        cout << "Iter " << iter <<", Loss: " << setprecision(3) << loss << endl;
}

6. 最小二乘法

向量模长的概念

对于向量 V V V 与自己做内积,等价于如下:
V T V = v 1 2 + v 2 2 + v 3 2 + . . . + v n 2 V^TV = v_1^2 + v_2^2 + v_3^2 +... + v_n^2 VTV=v12+v22+v32+...+vn2
而这个式子正好等于模长的平方,即:
V T V = ∣ ∣ V ∣ ∣ 2 2 = v 1 2 + v 2 2 + v 3 2 + . . . + v n 2 V^TV=||V||_2^2=v_1^2+v_2^2+v_3^2+...+v_n^2 VTV=∣∣V22=v12+v22+v32+...+vn2
对于 X m × n θ n × 1 = Y m × 1 X_{m\times n}\theta_{n\times 1}=Y_{m\times 1} Xm×nθn×1=Ym×1,已知 X X X Y Y Y 如何求解最佳 θ n × 1 \theta_{n\times 1} θn×1 使得 ∣ ∣ X θ − Y ∣ ∣ 2 2 ||X\theta-Y||_2^2 ∣∣Y22 最小,其中 m m m 是样本数量而 n n n 是特征维度
∣ ∣ X θ − Y ∣ ∣ 2 2 = ( X θ − Y ) T ( X θ − Y ) ∣ ||X\theta-Y||_2^2=(X\theta-Y)^T(X\theta-Y)| ∣∣Y22=(Y)T(Y)
定义优化的目标函数为:
L ( θ ) = 1 2 ( X θ − Y ) T ( X θ − Y ) L(\theta)=\frac{1}{2}(X\theta-Y)^{T}(X\theta-Y) L(θ)=21(Y)T(Y)

∂ L ( θ ) ∂ θ = X T ( X θ − Y ) = 0 \begin{aligned} & \\ &\frac{\partial L(\theta)}{\partial\theta}=X^T(X\theta-Y)=0 \end{aligned} θL(θ)=XT(Y)=0
推导得
X T X θ = X T Y θ = ( X T X ) − 1 X T Y \begin{aligned} &X^TX\theta=X^TY \\ &\theta=(X^TX)^{-1}X^TY \end{aligned} XT=XTYθ=(XTX)1XTY
相比于梯度下降法,最小二乘法需要求解逆矩阵,但是 X T X X^TX XTX 很容易出现不可逆现象。并且求解逆矩阵比较费时,所以两种方法各有优缺点。

示例代码如下

theta = np.linalg.inv(X.T @ X) @ X.T @ Y
theta

7. 岭回归(L2)

岭回归(即L2正则化的回归),相较于最小二乘法增加了正则化项,如下:
m i n i m i z e ∣ ∣ X θ − Y ∣ ∣ 2 2 + λ ∣ ∣ θ ∣ ∣ 2 2 minimize||X\theta-Y||_2^2+\lambda||\theta||_2^2 minimize∣∣Y22+λ∣∣θ22
得到求解公式:
θ = ( X T X + λ I ) − 1 X T Y \theta=(X^TX+\lambda I)^{-1}X^TY θ=(XTX+λI)1XTY
对于正则化项,可以理解为使得 X X X 在更多情况下可逆

示例代码如下

eps = 1e-5
theta = np.linalg.inv(X.T @ X + np.eye(X.shape[1]) * eps) @ X.T @ Y
theta

8. 多元牛顿法

还是之前那个问题,对于 X m × n θ n × 1 = Y m × 1 X_{m\times n}\theta_{n\times 1}=Y_{m\times 1} Xm×nθn×1=Ym×1,已知 X X X Y Y Y 如何求解最佳 θ n × 1 \theta_{n\times 1} θn×1 使得 ∣ ∣ X θ − Y ∣ ∣ 2 2 ||X\theta-Y||_2^2 ∣∣Y22 最小,其中 m m m 是样本数量而 n n n 是特征维度

定义目标函数如下:
L = 1 2 ∣ ∣ X θ − Y ∣ ∣ 2 2 L=\frac{1}{2}||X\theta-Y||_2^2 L=21∣∣Y22
根据海森矩阵更新 θ \theta θ
θ + = θ − H − 1 ∂ L ( θ ) ∂ θ \theta^+=\theta-H^{-1}\frac{\partial L(\theta)}{\partial\theta} θ+=θH1θL(θ)
其中 H H H L L L 对参数 θ \theta θ 的海森矩阵,定义为元素的二阶偏导数组成的方阵
H = [ ∂ 2 L ∂ θ 1 ∂ θ 1 ∂ 2 L ∂ θ 1 ∂ θ 2 ⋯ ∂ 2 L ∂ θ 1 ∂ θ n ∂ 2 L ∂ θ 2 ∂ θ 1 ∂ 2 L ∂ θ 2 ∂ θ 2 ⋯ ∂ 2 L ∂ θ 2 ∂ θ n ⋯ ⋯ ⋯ ⋯ ∂ 2 L ∂ θ n ∂ θ 1 ∂ 2 L ∂ θ n ∂ θ 2 ⋯ ∂ 2 L ∂ θ n ∂ θ n ] H=\left[\begin{array}{l l l l}{\frac{\partial^{2}L}{\partial\theta_{1}\partial\theta_{1}}}&{\frac{\partial^{2}L}{\partial\theta_{1}\partial\theta_{2}}}&{\cdots}&{\frac{\partial^{2}L}{\partial\theta_{1}\partial\theta_{n}}}\\ {\frac{\partial^{2}L}{\partial\theta_{2}\partial\theta_{1}}}&{\frac{\partial^{2}L}{\partial\theta_{2}\partial\theta_{2}}}&{\cdots}&{\frac{\partial^{2}L}{\partial\theta_{2}\partial\theta_{n}}}\\ {\cdots}&{\cdots}&{\cdots}&{\cdots}\\ {\frac{\partial^{2}L}{\partial\theta_{n}\partial\theta_{1}}}&{\frac{\partial^{2}L}{\partial\theta_{n}\partial\theta_{2}}}&{\cdots}&{\frac{\partial^{2}L}{\partial\theta_{n}\partial\theta_{n}}}\end{array}\right] H= θ1θ12Lθ2θ12Lθnθ12Lθ1θ22Lθ2θ22Lθnθ22Lθ1θn2Lθ2θn2Lθnθn2L
海森(hessian)矩阵代码如下:

def hessian(X, theta, Y):
    O = np.zeros((theta.shape[0], theta.shape[0]))
    for i in range(O.shape[0]):
        for j in range(O.shape[1]):
            for k in range(X.shape[0]):
                O[i, j] += X[k, i] * X[k, j]
    return O

# 或者

def hessian(X, theta, Y):
    return X.T @ X

梯度代码如下:

def gradient(X, theta, Y):
    g = X @ theta - Y
    return X.T @ g

for i in range(100):
    L = ((X @ theta - Y)**2).sum()
    theta = theta - np.linalg.inv(hessian(X, theta, Y)) @ gradient(X, theta, Y)

参考https://zhuanlan.zhihu.com/p/139159521

9. 高斯牛顿法

定义损失函数如下:
L = 1 2 ∣ ∣ X θ − Y ∣ ∣ 2 2 L=\frac{1}{2}||X\theta-Y||_2^2 L=21∣∣Y22
定义残差 r r r 为:
r = X θ − Y r = X\theta-Y r=Y
则有
L = 1 2 ∣ ∣ X θ − Y ∣ ∣ 2 2 = 1 2 ∣ ∣ r ∣ ∣ 2 2 L=\frac{1}{2}||X\theta-Y||_2^2=\frac{1}{2}||r||_2^2 L=21∣∣Y22=21∣∣r22
参考 r r r 可表示为:
r = [ r ( 1 ) r ( 2 ) … r ( m ) ] = [ x 1 ( 1 ) θ 1 + x 2 ( 1 ) θ 2 + . . . + x n ( 1 ) θ n − y ( 1 ) x 1 ( 2 ) θ 1 + x 2 ( 2 ) θ 2 + . . . + x n ( 2 ) θ n − y ( 2 ) … x 1 ( m ) θ 1 + x 2 ( m ) θ 2 + . . . + x n ( m ) θ n − y ( m ) ] r={\left[\begin{array}{l}{r^{(1)}}\\ {r^{(2)}}\\ {\ldots}\\ {r^{(m)}}\end{array}\right]}={\left[\begin{array}{l}{x_{1}^{(1)}\theta_{1}+x_{2}^{(1)}\theta_{2}+...+x_{n}^{(1)}\theta_{n}-y^{(1)}}\\ {x_{1}^{(2)}\theta_{1}+x_{2}^{(2)}\theta_{2}+...+x_{n}^{(2)}\theta_{n}-y^{(2)}}\\ {\ldots}\\ {x_{1}^{(m)}\theta_{1}+x_{2}^{(m)}\theta_{2}+...+x_{n}^{(m)}\theta_{n}-y^{(m)}}\\ \end{array}\right]} r= r(1)r(2)r(m) = x1(1)θ1+x2(1)θ2+...+xn(1)θny(1)x1(2)θ1+x2(2)θ2+...+xn(2)θny(2)x1(m)θ1+x2(m)θ2+...+xn(m)θny(m)
则残差 r r r 对于参数 θ \theta θ 的雅可比矩阵如下:
J ( r ( θ ) ) = [ ∂ r ( 1 ) ∂ θ 1 ∂ r ( 1 ) ∂ θ 2 ⋯ ∂ r ( 1 ) ∂ θ n ∂ r ( 2 ) ∂ θ 1 ∂ r ( 2 ) ∂ θ 2 ⋯ ∂ r ( 2 ) ∂ θ n ∂ r ( m ) ∂ θ 1 ∂ r ( m ) ∂ θ 2 ⋯ ∂ r ( m ) ∂ θ n ] J(r(\theta)) =\left[\begin{array}{c c c c}{\frac{\partial r^{(1)}}{\partial\theta_{1}}}&{\frac{\partial r^{(1)}}{\partial\theta_{2}}}&{\cdots}&{\frac{\partial r^{(1)}}{\partial\theta_{n}}}\\ {\frac{\partial r^{(2)}}{\partial\theta_{1}}}&{\frac{\partial r^{(2)}}{\partial\theta_{2}}}&{\cdots}&{\frac{\partial r^{(2)}}{\partial\theta_{n}}}\\ {}&{}&{}&{}\\ {\frac{\partial r^{(m)}}{\partial\theta_{1}}}&{\frac{\partial r^{(m)}}{\partial\theta_{2}}}&{\cdots}&{\frac{\partial r^{(m)}}{\partial\theta_{n}}}\\ \end{array}\right] J(r(θ))= θ1r(1)θ1r(2)θ1r(m)θ2r(1)θ2r(2)θ2r(m)θnr(1)θnr(2)θnr(m)
对于多元牛顿法的迭代公式:
θ + = θ − H − 1 ∂ L ( θ ) ∂ θ \theta^+=\theta-H^{-1}\frac{\partial L(\theta)}{\partial\theta} θ+=θH1θL(θ)
使用残差 r r r 对参数 θ \theta θ 的雅可比矩阵 J ( r ( θ ) ) J(r(\theta)) J(r(θ)) 近似目标函数对参数 θ \theta θ 的海森矩阵 H ( L ( θ ) ) H(L(\theta)) H(L(θ))
H ( L ( θ ) ) ≈ J ( r ( θ ) ) T J ( r ( θ ) ) H(L(\theta))\approx J(r(\theta))^T J(r(\theta)) H(L(θ))J(r(θ))TJ(r(θ))
最后得到迭代式:
θ + = θ − ( J T J ) − 1 J T r \theta^+=\theta - (J^T J)^{-1} J^T r θ+=θ(JTJ)1JTr
参考https://www.bilibili.com/video/av93296032

参考https://zhuanlan.zhihu.com/p/139159521

参考http://www.whudj.cn/?p=1122

10. Levenberg-Marquardt(修正牛顿法/阻尼牛顿法)

高斯牛顿法中, θ \theta θ 迭代式很像最小二乘法
θ + = θ − ( J T J ) − 1 J T r \theta^+=\theta - (J^T J)^{-1} J^T r θ+=θ(JTJ)1JTr
LM 修正法,引入了类似正则化项的 μ \mu μ,解决了 J T J J^TJ JTJ 的奇异问题。 μ \mu μ 也称之为阻尼系数
θ + = θ − ( J T J + μ I ) − 1 J T r \theta^+=\theta - (J^T J + \mu I)^{-1} J^T r θ+=θ(JTJ+μI)1JTr
示例代码如下

u = 0.00001
for i in range(100):
    r = X @ theta - Y
    L = (r**2).sum()
    J = jacobian(X, theta, Y)
    theta = theta - np.linalg.inv(J.T @ J + u * np.eye(J.shape[1])) @ J.T @ r

总结

本次课程学习了矩阵,以及矩阵求导推导及其代码实现,并基于矩阵介绍了常见的一些优化算法如多元牛顿法、高斯牛顿法、LM算法等,只是点到为止并没有对这些算法进行详细的分析,后续用到的时候再来补吧😂

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/455403.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Platforimio] LVGL +TFT_eSPI实现触摸功能

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; 本人持续分享更多关于电子通信专业内容以及嵌入式和单片机的知识&#xff0c;如果大家喜欢&#xff0c;别忘点个赞加个关注哦&#xff0c;让我们一起共同进步~ &#x…

Centos下环境变量

文章内容如下&#xff1a; 1&#xff09;什么是环境变量&#xff1b; 2&#xff09;如何通过程序获取环境变量&#xff1b; 3) 常识规律 一。环境变量的定义 环境变量就是指一段路径。 定义环境变量主要是为了方便的执行程序。添加环境变量的方法是export PATH$PATH:/A/B&…

医用IT隔离电源在医院特殊场所接地系统的应用

【摘要】我们国家大部分医院的临床救治和确诊都是利用了医疗电气类设备和医用的医疗仪器&#xff0c;因此这些地方的接地问题应该引起我们的高度的重视。IT系统主要是利用了中性点没有直接接地的方式&#xff0c;所以可以减少电压和电流&#xff0c;从而使人类触电的可能性小之…

搭建Serv-U FTP服务器共享文件外网远程访问「无公网IP」

文章目录 1. 前言2. 本地FTP搭建2.1 Serv-U下载和安装2.2 Serv-U共享网页测试2.3 Cpolar下载和安装 3. 本地FTP发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 转载自内网穿透工具的文章&#xff1a;使用Serv-U搭建FTP服务器并公网访问【内网穿透】 1. 前言…

Midjourney教程(三)——Prompt常用参数

Midjourney教程——Prompt常用参数 为了提升prompt的准确度与输入效率&#xff0c;让midjourney能够生成我们理想中的图片&#xff0c;我们需要学习一下prompt的常用参数 Version version版本号&#xff0c;midjourney支持多种模型&#xff0c;我们可以通过version参数来选择…

字典树(Trie/前缀树)

目录 字典树的概念 字典树的逻辑 字典树的实现 字典树小结 例题强化 字典树的概念 字典树&#xff08;Trie&#xff09;是一种空间换时间的数据结构&#xff0c;是一棵关于“字典”的树&#xff0c;主要用于统计、排序和保存大量的字符串。字典树是通过利用字符串的公共前…

广域通信网 - 流量控制(停等协议、滑动窗口协议)

文章目录 1 概述2 流量控制协议2.1 停等协议2.2 滑动窗口协议 1 概述 #mermaid-svg-c9cNIYsOvLpoO4AV {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-c9cNIYsOvLpoO4AV .error-icon{fill:#552222;}#mermaid-svg-c9c…

[MLIR] CodeGen Pipeline总结

参考资料&#xff1a; [MLIR] CodeGen Pipeline总结 - 知乎 (zhihu.com) 本文主要以 tensorflow 为例&#xff0c;介绍了其接入 MLIR 后的 CodeGen 过程&#xff0c;以及简要分析了一些现在常用的 CodeGen pipeline。本文是本人在结合博客(Codegen Dialect Overview - MLIR - L…

隐私计算,联邦学习

隐私计算&#xff08;“隐私保护计算” Privacy-Preserving Computation&#xff09; 隐私计算是一类技术方案&#xff0c;在处理和分析计算数据的过程中能保持数据不透明、不泄露、无法被计算方法以及其他非授权方获取。 数据方是指为执行隐私保护计算过程提供数据的组织或个…

泰国五一游玩儿攻略

泰国五一游玩儿攻略 2023年4月27日1. 机场2. 酒店和夜市 2023年4月28日2023年4月29日2023年4月30日2023年5月1日2023年5月2日2023年5月3日 2023年4月27日 1. 机场 1.1 海关资料准备&#xff1a; 往返机票&#xff08;去程返程都得有&#xff0c;每人单独打印自己的&#xff0…

多维时序 | MATLAB实现BO-CNN-GRU贝叶斯优化卷积门控循环单元多变量时间序列预测

多维时序 | MATLAB实现BO-CNN-GRU贝叶斯优化卷积门控循环单元多变量时间序列预测 目录 多维时序 | MATLAB实现BO-CNN-GRU贝叶斯优化卷积门控循环单元多变量时间序列预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 基于贝叶斯(bayes)优化卷积神经网络-门控循环…

开放式耳机有什么好处,分享几款高畅销的开放式耳机

开放式耳机是一种声音传导方式&#xff0c;主要通过颅骨、骨骼把声波传递到内耳&#xff0c;属于非入耳式的佩戴方式。相比传统入耳式耳机&#xff0c;开放式耳机不会堵塞耳道&#xff0c;使用时可以开放双耳&#xff0c;不影响与他人的正常交流。开放式耳机不会对耳朵产生任何…

RocketMQ整合代码

RocketMQ整合代码 一 构建Java基础环境 在maven项⽬中构建出RocketMQ消息示例的基础环境&#xff0c;即创建⽣产者程序和消费者程序。通过⽣产者和消费者了解RocketMQ操作消息的原⽣API 引⼊依赖 <dependencies><dependency><groupId>org.apache.rocketmq&…

HCIP——交换(更新中)

园区网架构 交换机实现了以下功能 无限的传输距离——识别&#xff0c;重写电信号&#xff08;帧&#xff09;保证信息完整彻底解决了冲突二层单播——MAC地址表提高端口密度 MAC 单播地址&#xff1a;MAC地址第一个字节第8位为0 组播地址&#xff1a;MAC地址第一个字节第8位…

我完全手写的Resnet50网络,终于把猫识别出来了

大家好啊&#xff0c;我是董董灿。 经常看我文章的同学&#xff0c;可能知道最近我在做一个小项目——《从零手写Resnet50实战》。 从零开始&#xff0c;用最简单的程序语言&#xff0c;不借用任何第三方库&#xff0c;完成Resnet50的所有算法实现和网络结构搭建&#xff0c;…

SOS大规模敏捷开发项目管理完整版(Scrum of Scrums)

Scrum of Scrums是轻量化的规模化敏捷管理模式&#xff0c;Leangoo领歌可以完美支持Scrum of Scrums多团队敏捷管理。 Scrum of Scrums的场景 Scrum of Scrums是指多个敏捷团队共同开发一个大型产品、项目或解决方案。Leangoo提供了多团队场景下的产品路线图规划、需求管理、…

2023首场亚马逊云科技行业峰会,医疗与生命科学年度盛会精彩先行

从实验室扩展到真实世界&#xff0c;从前沿技术探索到医疗生命科学行业的快速创新实践&#xff0c;亚马逊云科技不断地通过数字化助力医疗和生命科学的行业创新。由上海徐汇区科委指导&#xff0c;上海枫林集团作为支持单位&#xff0c;亚马逊云科技主办的2023亚马逊云科技医疗…

如何评估小程序开发费用:从项目规模到技术需求

作为一种越来越受欢迎的移动应用&#xff0c;小程序的开发费用是许多企业和个人考虑的重要因素之一。但是&#xff0c;要确定小程序开发费用并不是一件容易的事情&#xff0c;因为它涉及到多个因素&#xff0c;从项目规模到技术需求。 项目规模 小程序开发的费用通常与项目规…

docker-Dockerfile文件使用配置、自定义构建镜像、docker build

Dockerfile使用 docker build构建新的镜像参数解释 Dockerfile格式基础格式FROMCOPYADDRUNCMDENTRYPOINTENVARGVOLUMEEXPOSEWORKDIRUSERHEALTHCHECKONBUILDLABEL 命令摘要 docker build构建新的镜像 命令&#xff1a;docker build -t some-content-nginx . 参数解释 docker …

2023团体程序设计天梯赛--正式赛

L1-1 最好的文档 有一位软件工程师说过一句很有道理的话&#xff1a;“Good code is its own best documentation.”&#xff08;好代码本身就是最好的文档&#xff09;。本题就请你直接在屏幕上输出这句话。 输入格式&#xff1a; 本题没有输入。 输出格式&#xff1a; 在…