---
# System prepended metadata

title: 淺入淺出瞭解機器學習_02_Linear Regression(下)
tags: [淺入淺出瞭解機器學習]

---

# 淺入淺出瞭解機器學習_02_Linear Regression(下)
###### tags: `淺入淺出瞭解機器學習`

## 多變量性迴歸
在上一篇文章中已經說明單變量的線性迴歸求解過程，這邊我們將這個過程擴展到多變量，並加回偏差項，也就是bias。

先將上一篇文章中已知的數學式寫出來，比較方便回憶：
* hypothesis $h_\theta(x)=\theta_1 x$
* loss function $\dfrac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2$

可以看到目前的hypothesis就只有一個參數，讓我們加入更多的參數，因為實務上通常擁有的特徵數都是不少的：
* new hypothesis，$h_\theta(x) = \theta_1 x_1 + \theta_2 x_2 + \theta_3 x_3 + ... + \theta_n x_n + \theta_0 x_0$
    * 其中$x_0 = 1$，原本我們有$n$個特徵與$n$個參數，但因為加入bias，$\theta_0$，因此參數的維度變成$n+1$，所以我們加入一個相對應的$x_0=1$就可以讓兩者維度相同，這麼做的好處在於可以批次處理，只需要利用內積就可以一次求解，而不需要做那麼多的加法

以內積來一次性的計算，調整公式如下：
* $h_\theta(x) = \theta^T \cdot x$
    * 假設，$\theta$表示所有的參數，以$x$表示所有的特徵，在兩者維度皆為$n+1$的情況下就可以調整為上面的數學式，非常優美、簡潔

看起來似乎不一樣，但其實以向量的角度來看的話，基本是沒有什麼變化。現在，我們的loss function就變成：
* $L(\theta) = \dfrac{1}{2m} \sum_{i=1}^m (h_\theta(x^{(i)}) - y^{(i)})^2$
    * 其中$h_\theta(x) = \theta^T \cdot x$

加入更多的參數之後，我們的參數更新就變成如下：
* $\theta_j := \theta_j - \alpha\dfrac{\partial L}{\partial \theta_j}$
    * 下標$j$代表第$j$個參數

其實加入再多的參數對於求偏微分沒有太大的差異，因為微分只會對相對應的參數有影響，其它的微了之後就不見，因此：
* $\forall \theta, \space \theta_j := \theta_j - \alpha \cdot \space \dfrac{1}{2m} \cdot 2 \sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)}) x^{(i)}_j$
    * $\forall \theta$表示對所有的$\theta$，$\forall$表示"for all"

記得在上一篇文章提過的部份，更新這個動作必需等到所有的參數通通計算過梯度之後才可以做，如果你一邊計算一邊更新的話，那後面的參數更新通通都會是異常的，這點非常重要。也就是，
```python=
# 先寫入暫存
tmp_theta_1 = theta_1 - gradient_1
tmp_theta_2 = theta_2 - gradient_2

# 全部計算之後才可以更新
theta_1 = tmp_theta_1
theta_2 = tmp_theta_2
```

如果你是用tensorflow的話，作法也是雷同：
```python
import tensorflow as tf

# 注意到輸入維度與參數維度的變化
X = tf.constant([[1., 4.], [2., 5.], [3., 6.]]) # input
y = tf.constant([[1.], [2.], [3.]]) # output
w = tf.Variable(initial_value=[[0.], [0.]]) # theta
# 計算gradient
with tf.GradientTape() as tape: 
    # 定義loss function
    L = tf.reduce_sum(tf.square(tf.matmul(X, w) - y)) / (X.shape[0] * 2)

# 計算L(w, b)關於w, b的偏導數
w_grad = tape.gradient(L, [w])   

print(L, [w.numpy() for w in w_grad])
>>> tf.Tensor(2.3333333, shape=(), dtype=float32) [array([[ -4.666667],
       [-10.666667]], dtype=float32)]
```

第一個特徵值的梯度與上一個案例一樣不變，因此得到相同的值，第二個特徵值我們可以怒算一波：
* $\dfrac{1}{3}*((0-1)*4 + (0-2)*5) + (0-3)*6)=\dfrac{1}{3}*-32=-10.67$

第一次迭代所計算的loss會跟上一次一樣只是單純因為初始化參數為0，因此怎麼算結果都會是一樣。

## 預測結果

假設，我們就是只訓練一個迭代，然後得到剛剛所計算出來的系數，這意味著我們的hypothesis現在是長這樣子，$h_\theta(x) = -4.67 \cdot x_1 - 10.67 \cdot x_2$。你只需要帶入你想預測的資料，也許我們想看看$[1, 4]$這組輸入現在會得到什麼結果，那只要帶入相對應的位置即可，也就是$-4.67 - 42.68 = -47.35$，好像爛的有點over，不過相信我，多訓練幾次迭代就會收斂的。

下面給出迭代執行更新的範例：
```python
X = tf.constant([[1., 4.], [2., 5.], [3., 6.]]) # input
y = tf.constant([[1.], [2.], [3.]]) # output
w = tf.Variable(initial_value=[[0.], [0.]]) # theta
variables = [w]
# 定義最佳化方式
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
# 迭代處理
for i in range(10):
    with tf.GradientTape() as tape:        
        print(f'iteration: {i}')
        L = tf.reduce_sum(tf.square(tf.matmul(X, w) - y)) / (X.shape[0] * 2)
        # 計算L(w, b)關於w, b的偏導數
        w_grad = tape.gradient(L, [w])        
        print(f'loss: {L.numpy()}, gradient: {w_grad}')
        # 更新參數
        optimizer.apply_gradients(grads_and_vars=zip(w_grad, variables))
        print(f'after training theta: {w.numpy()}')   
```

## 結論
直觀來看，特徵再多，只要向量化就可以批次處理，因此整體求解並沒有太大的變化。有一件事一直到這邊才說明，關於線性迴歸的loss function又稱為Mean Square Error，均方誤差，這不難理解，我們計算實際資料與預測資料之間的距離，然後計算平方取平均。而這個loss function或許並不適用於其它的演算法，因為每個演算法都可以有屬於它自己的loss function，當然可以自創，只是在你用自創的loss function之後，線性迴歸可能就不是大家所知道的線性迴歸。

另外，最佳化的方式有很多，在深度學習的領域中有著眾多最佳化的方式，sgd、momentum、adam、rmsprop以及最近非常火熱的LookAhead、Ranger、Ranger LARS..沒有絕對，只有不斷嚐試。

知道線性迴歸是怎麼一回事之後我們就可以瞭解下一個演算法，也就是Logistic Regression。
## 參考
吳恩達老師機器學習課程
李宏毅老師機器學習課程
Sebastian Raschka機器學習