지난 글에서 선형 모델을 먼저 구현해 보겠다는 목표를 세웠었는데, 그 전에 해야할 일이 바로 행렬 연산에 관한 객체를 먼저 만드는 것입니다.

🖩 행렬의 연산

덧셈

행렬의 덧셈은 기본적으로 두 행렬의 행과 열이 같아야 가능합니다.

$$\begin{align*}\begin{pmatrix}1&2\\3&4\end{pmatrix}+\begin{pmatrix}5&6\\7&8\end{pmatrix}&=\begin{pmatrix}1+5&2+6\\3+7&4+8\end{pmatrix}\\&=\begin{pmatrix}6&8\\10&12\end{pmatrix}\end{align*}$$

하지만 선형 모델에서는 두 행렬의 덧셈 연산이 아닌, 행렬과 벡터1의 덧셈(Broadcasting) 연산이 필요합니다.

💡 벡터는 행렬의 특별한 경우로 볼 수 있다.

  • \(b=[b_1,b_2,b_3]\)
  • 세로로 쓰면 \(3 \times 1\) 열벡터, 가로로 쓰면 \(1 \times 3\) 행벡터

선형 모델에서의 덧셈은 가중치에 편향을 더하는 것인데, 행렬을 행벡터라고 생각하고 모든 행에 더해주면 됩니다.

$$\begin{align*}\begin{pmatrix}1&2\\3&4\end{pmatrix}+\begin{pmatrix}5&6\end{pmatrix}&=\begin{pmatrix}1+5&2+6\\3+5&4+6\end{pmatrix}\\&=\begin{pmatrix}6&8\\8&10\end{pmatrix}\end{align*}$$

곱셈

행렬의 곱셈은 전치행렬(좌승수)의 열 개수와 후치행렬(우승수)의 행 개수가 일치해야 곱할 수 있습니다.

$$\begin{align*}\begin{pmatrix}1&2&3\\4&5&6\end{pmatrix}\times\begin{pmatrix}a&d\\b&e\\c&f\end{pmatrix}&=\begin{pmatrix}1\times a+2\times b+3\times c&1\times d+2\times e+ 3\times f\\4\times a+5\times b+6\times c&4\times d+5\times e+6\times f\end{pmatrix}\\&=\begin{pmatrix}1a+2b+3c&1d+2e+3f\\4a+5b+6c&4d+5e+6f\end{pmatrix}\end{align*}$$

💡 \([\text{행 개수}, \text{열 개수}]\) 로 표현 한다면

\([\overset{\substack{\text{결과 행}\\\downarrow}}{2}\times \underset{\substack{\uparrow\\\text{좌승수 열}}}{3}] \times [\underset{\substack{\uparrow\\\text{우승수 행}}}{3}\times \overset{\substack{\text{결과 열}\\\downarrow}}{2}] = [2\times 2]\)

전치

전치 연산은 행을 열로, 열을 행으로 바꾸는 연산입니다.

$$\begin{pmatrix}a&b&c\\d&e&f\end{pmatrix}^T=\begin{pmatrix}a&d\\b&e\\c&f\end{pmatrix}$$

⌨️ 구현

⚠️ 아래 코드는 구현의 일부분 입니다. 여기를 참고해주세요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 행렬 덧셈
Matrix<T>& operator+(const Matrix<T>& mat) {
    if(_col != mat._col || _row != mat._row) {
        throw std::exception();
    }

    for(std::size_t i=0; i<_col*_row; i++) {
        _chunk[i] += mat._chunk[i];
    }

    return *this;
}
// 행렬 곱셈
Matrix<T> operator*(const Matrix<T>& mat) const {
    if(_col != mat._row) {
        throw std::exception();
    }

    Matrix<T> result(_row, mat._col);
    for(std::size_t row=0; row<_row; row++) {
        for(std::size_t col=0; col<mat._col; col++) {
            for(std::size_t x=0; x<_col; x++) { // 좌승수의 열이나 우승수의 행만큼 반복
                result(row, col) += (*this)(row, x) * mat(x, col);
            }
        }
    }

    return result;
}
// 행렬 전치
Matrix<T> transpose() const {
    if(_row == 0 && _col == 0) {
        throw std::exception();
    }
    
    Matrix<T> result(_col, _row);
    for(std::size_t row=0; row<_row; row++) {
        for(std::size_t col=0; col<_col; col++) {
            result(col, row) = (*this)(row, col);
        }
    }

    return result;
}

  1. 기초 선형대수에서는 벡터 공간의 원소를 벡터라고 함, 계산할 때는 벡터를 흔히 순서가 있는 성분들의 목록으로 표현함 ↩︎