四、多變量線性回歸(Linear Regression with Multiple Variables)
- 4.1 多維特征
- 4.2 多變量梯度下降
- 4.3 梯度下降法實踐1-特征縮放
- 4.4 梯度下降法實踐2-學習率
- 4.5 特征和多項式回歸
- 4.6 正規方程
- 4.7 正規方程及不可逆性(可選)
五、Octave教程(Octave Tutorial)
- 5.1 基本操作
- 5.2 移動數據
- 5.3 計算數據
- 5.4 繪圖數據
- 5.5 控制語句:for,while,if語句
- 5.6 向量化
- 5.7 工作和提交的編程練習
第2周
四、多變量線性回歸(Linear Regression with Multiple Variables)
4.1 多維特征
參考視頻: 4 - 1 - Multiple Features (8 min).mkv
目前為止,我們探討了單變量/特征的回歸模型,現在我們對房價模型增加更多的特征,例如房間數樓層等,構成一個含有多個變量的模型,模型中的特征為\(\left( {x_{1}},{x_{1}},...,{x_{n}} \right)\)。
增添更多特征后,我們引入一系列新的注釋:
\(n\) 代表特征的數量
\({x^{\left( i \right)}}\)代表第 \(i\) 個訓練實例,是特征矩陣中的第i行,是一個向量(vector)。
比方說,上圖的
\({x}^{(2)}\text{=}\begin{bmatrix} 1416\\\ 3\\\ 2\\\ 40 \end{bmatrix}\),
\({x}_{j}^{\left( i \right)}\)代表特征矩陣中第 \(i\) 行的第 \(j\) 個特征,也就是第 \(i\) 個訓練實例的第 \(j\) 個特征。
如上圖的\(x_{2}^{\left( 2 \right)}=3,x_{3}^{\left( 2 \right)}=2\),
支持多變量的假設 \(h\) 表示為:\(h_{\theta}\left( x \right)={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}\),
這個公式中有\(n+1\)個參數和\(n\)個變量,為了使得公式能夠簡化一些,引入\(x_{0}=1\),則公式轉化為:\(h_{\theta} \left( x \right)={\theta_{0}}{x_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}\)
此時模型中的參數是一個\(n+1\)維的向量,任何一個訓練實例也都是\(n+1\)維的向量,特征矩陣X的維度是 \(m*(n+1)\)。 因此公式可以簡化為:\(h_{\theta} \left( x \right)={\theta^{T}}X\),其中上標T代表矩陣轉置。
4.2 多變量梯度下降
參考視頻: 4 - 2 - Gradient Descent for Multiple Variables (5 min).mkv
與單變量線性回歸類似,在多變量線性回歸中,我們也構建一個代價函數,則這個代價函數是所有建模誤差的平方和,即:\(J\left( {\theta_{0}},{\theta_{1}}...{\theta_{n}} \right)=\frac{1}{2m}\sum\limits_{i=1}^{m}{{{\left( h_{\theta} \left({x}^{\left( i \right)} \right)-{y}^{\left( i \right)} \right)}^{2}}}\) ,
其中:\(h_{\theta}\left( x \right)=\theta^{T}X={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}\) ,
我們的目標和單變量線性回歸問題中一樣,是要找出使得代價函數最小的一系列參數。
多變量線性回歸的批量梯度下降算法為:
即:
求導數后得到:
當\(n>=1\)時,
\({{\theta }_{0}}:={{\theta }_{0}}-a\frac{1}{m}\sum\limits_{i=1}^{m}{({{h}_{\theta }}({{x}^{(i)}})-{{y}^{(i)}})}x_{0}^{(i)}\)
\({{\theta }_{1}}:={{\theta }_{1}}-a\frac{1}{m}\sum\limits_{i=1}^{m}{({{h}_{\theta }}({{x}^{(i)}})-{{y}^{(i)}})}x_{1}^{(i)}\)
\({{\theta }_{2}}:={{\theta }_{2}}-a\frac{1}{m}\sum\limits_{i=1}^{m}{({{h}_{\theta }}({{x}^{(i)}})-{{y}^{(i)}})}x_{2}^{(i)}\)
我們開始隨機選擇一系列的參數值,計算所有的預測結果后,再給所有的參數一個新的值,如此循環直到收斂。
Python 代碼示例:
計算代價函數
\(J\left( \theta \right)=\frac{1}{2m}\sum\limits_{i=1}^{m}{{{\left( {h_{\theta}}\left( {x^{(i)}} \right)-{y^{(i)}} \right)}^{2}}}\)
其中:\({h_{\theta}}\left( x \right)={\theta^{T}}X={\theta_{0}}{x_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}\)
代碼:
def computeCost(X, y, theta):
inner = np.power(((X * theta.T) - y), 2)
return np.sum(inner) / (2 * len(X))
4.3 梯度下降法實踐1-特征縮放
參考視頻: 4 - 3 - Gradient Descent in Practice I - Feature Scaling (9 min).mkv
在我們面對多維特征問題的時候,我們要保證這些特征都具有相近的尺度,這將幫助梯度下降算法更快地收斂。
以房價問題為例,假設我們使用兩個特征,房屋的尺寸和房間的數量,尺寸的值為 0-2000平方英尺,而房間數量的值則是0-5,以兩個參數分別為橫縱坐標,繪制代價函數的等高線圖能,看出圖像會顯得很扁,梯度下降算法需要非常多次的迭代才能收斂。
解決的方法是嘗試將所有特征的尺度都盡量縮放到-1到1之間。如圖:
最簡單的方法是令:
其中 \({\mu_{n}}\)是平均值,\({s_{n}}\)是標准差。
4.4 梯度下降法實踐2-學習率
參考視頻: 4 - 4 - Gradient Descent in Practice II - Learning Rate (9 min).mkv
梯度下降算法收斂所需要的迭代次數根據模型的不同而不同,我們不能提前預知,我們可以繪制迭代次數和代價函數的圖表來觀測算法在何時趨於收斂。
也有一些自動測試是否收斂的方法,例如將代價函數的變化值與某個閥值(例如0.001)進行比較,但通常看上面這樣的圖表更好。
梯度下降算法的每次迭代受到學習率的影響,如果學習率\(\alpha\)過小,則達到收斂所需的迭代次數會非常高;如果學習率\(\alpha\)過大,每次迭代可能不會減小代價函數,可能會越過局部最小值導致無法收斂。
通常可以考慮嘗試些學習率:
\(\alpha\)=0.01,0.03,0.1,0.3,1,3,10
4.5 特征和多項式回歸
參考視頻: 4 - 5 - Features and Polynomial Regression (8 min).mkv
如房價預測問題,
\({x_{1}}\)=frontage(臨街寬度),\({x_{2}}=depth\)(縱向深度),\(x=frontage*depth=area\)(面積),則:\({h_{\theta}}\left( x \right)={\theta_{0}}+{\theta_{1}}x\)。
線性回歸並不適用於所有數據,有時我們需要曲線來適應我們的數據,比如一個二次方模型:\(h_{\theta}\left( x \right)={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}^2}\)
或者三次方模型: \(h_{\theta}\left( x \right)={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}^2}+{\theta_{3}}{x_{3}^3}\)
或者三次方模型:
通常我們需要先觀察數據然后再決定准備嘗試怎樣的模型。 另外,我們可以令:
從而將模型轉化為線性回歸模型。
根據函數圖形特性,我們還可以使:
\({{\text{h}}_{\theta}}(x)={{\theta }_{0}}\text{+}{{\theta }_{1}}(size)+{{\theta}_{1}}{{(size)}^{2}}\)
或者:
\({{\text{h}}_{\theta}}(x)={{\theta }_{0}}\text{+}{{\theta }_{1}}(size)+{{\theta }_{1}}\sqrt{size}\)
注:如果我們采用多項式回歸模型,在運行梯度下降算法前,特征縮放非常有必要。
4.6 正規方程
參考視頻: 4 - 6 - Normal Equation (16 min).mkv
到目前為止,我們都在使用梯度下降算法,但是對於某些線性回歸問題,正規方程方法是更好的解決方案。如:
正規方程是通過求解下面的方程來找出使得代價函數最小的參數的:\(\frac{\partial}{\partial{\theta_{j}}}J\left( {\theta_{j}} \right)=0\) 。
假設我們的訓練集特征矩陣為 \(X\)(包含了 \({{x}_{0}}=1\))並且我們的訓練集結果為向量 \(y\),則利用正規方程解出向量 \(\theta ={{\left( {X^T}X \right)}^{-1}}{X^{T}}y\) 。
上標T代表矩陣轉置,上標-1 代表矩陣的逆。設矩陣\(A={X^{T}}X\),則:\({{\left( {X^T}X \right)}^{-1}}={A^{-1}}\)
以下表示數據為例:
即:
運用正規方程方法求解參數:
在 Octave 中,正規方程寫作:
pinv(X'*X)*X'*y
注:對於那些不可逆的矩陣(通常是因為特征之間不獨立,如同時包含英尺為單位的尺寸和米為單位的尺寸兩個特征,也有可能是特征數量大於訓練集的數量),正規方程方法是不能用的。
梯度下降與正規方程的比較:
梯度下降 | 正規方程 |
---|---|
需要選擇學習率\(\alpha\) | 不需要 |
需要多次迭代 | 一次運算得出 |
當特征數量\(n\)大時也能較好適用 | 需要計算\({{\left( {{X}^{T}}X \right)}^{-1}}\) 如果特征數量n較大則運算代價大,因為矩陣逆的計算時間復雜度為\(O\left( {{n}^{3}} \right)\),通常來說當\(n\)小於10000 時還是可以接受的 |
適用於各種類型的模型 | 只適用於線性模型,不適合邏輯回歸模型等其他模型 |
總結一下,只要特征變量的數目並不大,標准方程是一個很好的計算參數$\theta $的替代方法。具體地說,只要特征變量數量小於一萬,我通常使用標准方程法,而不使用梯度下降法。
隨着我們要講的學習算法越來越復雜,例如,當我們講到分類算法,像邏輯回歸算法,我們會看到,
實際上對於那些算法,並不能使用標准方程法。對於那些更復雜的學習算法,我們將不得不仍然使用梯度下降法。因此,梯度下降法是一個非常有用的算法,可以用在有大量特征變量的線性回歸問題。或者我們以后在課程中,會講到的一些其他的算法,因為標准方程法不適合或者不能用在它們上。但對於這個特定的線性回歸模型,標准方程法是一個比梯度下降法更快的替代算法。所以,根據具體的問題,以及你的特征變量的數量,這兩種算法都是值得學習的。
正規方程的python實現:
import numpy as np
def normalEqn(X, y):
theta = np.linalg.inv(X.T@X)@X.T@y #X.T@X等價於X.T.dot(X)
return theta
4.7 正規方程及不可逆性(可選)
參考視頻: 4 - 7 - Normal Equation Noninvertibility (Optional) (6 min).mkv
在這段視頻中談談正規方程 ( normal equation ),以及它們的不可逆性。
由於這是一種較為深入的概念,並且總有人問我有關這方面的問題,因此,我想在這里來討論它,由於概念較為深入,所以對這段可選材料大家放輕松吧,也許你可能會深入地探索下去,並且會覺得理解以后會非常有用。但即使你沒有理解正規方程和線性回歸的關系,也沒有關系。
我們要講的問題如下:\(\theta ={{\left( {X^{T}}X \right)}^{-1}}{X^{T}}y\)
備注:本節最后我把推導過程寫下。
有些同學曾經問過我,當計算 \(\theta\)=inv(X'X ) X'y
,那對於矩陣\(X'X\)的結果是不可逆的情況咋辦呢?
如果你懂一點線性代數的知識,你或許會知道,有些矩陣可逆,而有些矩陣不可逆。我們稱那些不可逆矩陣為奇異或退化矩陣。
問題的重點在於\(X'X\)的不可逆的問題很少發生,在Octave里,如果你用它來實現\(\theta\)的計算,你將會得到一個正常的解。在Octave里,有兩個函數可以求解矩陣的逆,一個被稱為pinv()
,另一個是inv()
,這兩者之間的差異是些許計算過程上的,一個是所謂的偽逆,另一個被稱為逆。使用pinv() 函數可以展現數學上的過程,這將計算出\(\theta\)的值,即便矩陣\(X'X\)是不可逆的。
在pinv()
和 inv()
之間,又有哪些具體區別呢 ?
其中inv()
引入了先進的數值計算的概念。例如,在預測住房價格時,如果\({x_{1}}\)是以英尺為尺寸規格計算的房子,\({x_{2}}\)是以平方米為尺寸規格計算的房子,同時,你也知道1米等於3.28英尺 ( 四舍五入到兩位小數 ),這樣,你的這兩個特征值將始終滿足約束:\({x_{1}}={x_{2}}*{{\left( 3.28 \right)}^{2}}\)。
實際上,你可以用這樣的一個線性方程,來展示那兩個相關聯的特征值,矩陣X'X將是不可逆的。
第二個原因是,在你想用大量的特征值,嘗試實踐你的學習算法的時候,可能會導致矩陣\(X'X\)的結果是不可逆的。
具體地說,在\(m\)小於或等於n的時候,例如,有\(m\)等於10個的訓練樣本也有\(n\)等於100的特征數量。要找到適合的\((n +1)\) 維參數矢量\(\theta\),這將會變成一個101維的矢量,嘗試從10個訓練樣本中找到滿足101個參數的值,這工作可能會讓你花上一陣子時間,但這並不總是一個好主意。因為,正如我們所看到你只有10個樣本,以適應這100或101個參數,數據還是有些少。
稍后我們將看到,如何使用小數據樣本以得到這100或101個參數,通常,我們會使用一種叫做正則化的線性代數方法,通過刪除某些特征或者是使用某些技術,來解決當\(m\)比\(n\)小的時候的問題。即使你有一個相對較小的訓練集,也可使用很多的特征來找到很多合適的參數。
總之當你發現的矩陣\(X'X\)的結果是奇異矩陣,或者找到的其它矩陣是不可逆的,我會建議你這么做。
首先,看特征值里是否有一些多余的特征,像這些\({x_{1}}\)和\({x_{2}}\)是線性相關的,互為線性函數。同時,當有一些多余的特征時,可以刪除這兩個重復特征里的其中一個,無須兩個特征同時保留,將解決不可逆性的問題。因此,首先應該通過觀察所有特征檢查是否有多余的特征,如果有多余的就刪除掉,直到他們不再是多余的為止,如果特征數量實在太多,我會刪除些 用較少的特征來反映盡可能多內容,否則我會考慮使用正規化方法。
如果矩陣\(X'X\)是不可逆的,(通常來說,不會出現這種情況),如果在Octave里,可以用偽逆函數pinv()
來實現。這種使用不同的線性代數庫的方法被稱為偽逆。即使\(X'X\)的結果是不可逆的,但算法執行的流程是正確的。總之,出現不可逆矩陣的情況極少發生,所以在大多數實現線性回歸中,出現不可逆的問題不應該過多的關注\({X^{T}}X\)是不可逆的。
增加內容:
\(\theta ={{\left( {X^{T}}X \right)}^{-1}}{X^{T}}y\) 的推導過程:
\(J\left( \theta \right)=\frac{1}{2m}\sum\limits_{i=1}^{m}{{{\left( {h_{\theta}}\left( {x^{(i)}} \right)-{y^{(i)}} \right)}^{2}}}\)
其中:\({h_{\theta}}\left( x \right)={\theta^{T}}X={\theta_{0}}{x_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}\)
將向量表達形式轉為矩陣表達形式,則有\(J(\theta )=\frac{1}{2}{{\left( X\theta -y\right)}^{2}}\) ,其中\(X\)為\(m\)行\(n\)列的矩陣(\(m\)為樣本個數,\(n\)為特征個數),\(\theta\)為\(n\)行1列的矩陣,\(y\)為\(m\)行1列的矩陣,對\(J(\theta )\)進行如下變換
\(J(\theta )=\frac{1}{2}{{\left( X\theta -y\right)}^{T}}\left( X\theta -y \right)\)
\(=\frac{1}{2}\left( {{\theta }^{T}}{{X}^{T}}-{{y}^{T}} \right)\left(X\theta -y \right)\)
\(=\frac{1}{2}\left( {{\theta }^{T}}{{X}^{T}}X\theta -{{\theta}^{T}}{{X}^{T}}y-{{y}^{T}}X\theta -{{y}^{T}}y \right)\)
接下來對\(J(\theta )\)偏導,需要用到以下幾個矩陣的求導法則:
\(\frac{dAB}{dB}={{A}^{T}}\)
\(\frac{d{{X}^{T}}AX}{dX}=2AX\)
所以有:
\(\frac{\partial J\left( \theta \right)}{\partial \theta }=\frac{1}{2}\left(2{{X}^{T}}X\theta -{{X}^{T}}y -{{X}^{T}}y -0 \right)\)
\(={{X}^{T}}X\theta -{{X}^{T}}y\)
令\(\frac{\partial J\left( \theta \right)}{\partial \theta }=0\),
則有\(\theta ={{\left( {X^{T}}X \right)}^{-1}}{X^{T}}y\)
五、Octave教程(Octave Tutorial)
5.1 基本操作
參考視頻: 5 - 1 - Basic Operations (14 min).mkv
在這段視頻中,我將教你一種編程語言:Octave語言。你能夠用它來非常迅速地實現這門課中我們已經學過的,或者將要學的機器學習算法。
過去我一直嘗試用不同的編程語言來教授機器學習,包括C++、Java、Python、Numpy和Octave。我發現當使用像Octave這樣的高級語言時,學生能夠更快更好地學習並掌握這些算法。事實上,在硅谷,我經常看到進行大規模的機器學習項目的人,通常使用的程序語言就是Octave。
Octave是一種很好的原始語言(prototyping language),使用Octave你能快速地實現你的算法,剩下的事情,你只需要進行大規模的資源配置,你只用再花時間用C++或Java這些語言把算法重新實現就行了。開發項目的時間是很寶貴的,機器學習的時間也是很寶貴的。所以,如果你能讓你的學習算法在Octave上快速的實現,基本的想法實現以后,再用C++或者Java去改寫,這樣你就能節省出大量的時間。
據我所見,人們使用最多的用於機器學習的原始語言是Octave、MATLAB、Python、NumPy 和R。
Octave很好,因為它是開源的。當然MATLAB也很好,但它不是每個人都買得起的。貌似國內學生喜歡用收費的matlab,matlab功能要比Octave強大的多,網上有各種D版可以下載。這次機器學習課的作業也是用matlab的。如果你能夠使用MATLAB,你也可以在這門課里面使用。
如果你會Python、NumPy或者R語言,我也見過有人用 R的,據我所知,這些人不得不中途放棄了,因為這些語言在開發上比較慢,而且,因為這些語言如:Python、NumPy的語法相較於Octave來說,還是更麻煩一點。正因為這樣,所以我強烈建議不要用NumPy或者R來完整這門課的作業,我建議在這門課中用Octave來寫程序。
本視頻將快速地介紹一系列的命令,目標是迅速地展示,通過這一系列Octave的命令,讓你知道Octave能用來做什么。
啟動Octave:
現在打開Octave,這是Octave命令行。
現在讓我示范最基本的Octave代碼:
輸入5 + 6,然后得到11。
輸入3 – 2、5×8、1/2、2^6等等,得到相應答案。
這些都是基本的數學運算。
你也可以做邏輯運算,例如 12,計算結果為 false ( 假),這里的百分號命令表示注釋,12 計算結果為假,這里用0表示。
請注意,不等於符號的寫法是這個波浪線加上等於符號 ( ~= ),而不是等於感嘆號加等號( != ),這是和其他一些編程語言中不太一樣的地方。
讓我們看看邏輯運算 1 && 0,使用雙&符號表示邏輯與,1 && 0判斷為假,1和0的或運算 1 || 0,其計算結果為真。
還有異或運算 如XOR ( 1, 0 ),其返回值為1
從左向右寫着 Octave 324.x版本,是默認的Octave提示,它顯示了當前Octave的版本,以及相關的其它信息。
如果你不想看到那個提示,這里有一個隱藏的命令
輸入命令
現在命令提示已經變得簡化了。
接下來,我們將談到Octave的變量。
現在寫一個變量,對變量A賦值為3,並按下回車鍵,顯示變量A等於3。
如果你想分配一個變量,但不希望在屏幕上顯示結果,你可以在命令后加一個分號,可以抑制打印輸出,敲入回車后,不打印任何東西。
其中這句命令不打印任何東西。
現在舉一個字符串的例子:變量b等於"hi"。
C等於3大於等於1,所以,現在C變量的值是真。
如果你想打印出變量,或顯示一個變量,你可以像下面這么做:
設置A等於圓周率π,如果我要打印該值,那么只需鍵入A像這樣 就打印出來了。
對於更復雜的屏幕輸出,也可以用DISP命令顯示:
這是一種,舊風格的C語言語法,對於之前就學過C語言的同學來說,你可以使用這種基本的語法來將結果打印到屏幕。
例如 sprintf命令的六個小數:0.6%f ,a,這應該打印π的6位小數形式。
也有一些控制輸出長短格式的快捷命令:
下面,讓我們來看看向量和矩陣:
比方說 建立一個矩陣A
對A矩陣進行賦值
考慮到這是一個三行兩列的矩陣
你同樣可以用向量
建立向量V並賦值1 2 3,V是一個行向量,或者說是一個3 ( 列 )×1 ( 行 )
的向量,或者說,一行三列的矩陣。
如果我想,分配一個列向量,我可以寫“1;2;3”,現在便有了一個3 行 1 列
的向量,同時這是一個列向量。
下面是一些更為有用的符號,如:
V=1:0.1:2
這個該如何理解呢:這個集合V是一組值,從數值1開始,增量或說是步長為0.1,直到增加到2,按照這樣的方法對向量V操作,可以得到一個行向量,這是一個1行11列的矩陣,其矩陣的元素是1
1.1 1.2 1.3,依此類推,直到數值2。
我也可以建立一個集合V並用命令“1:6”進行賦值,這樣V就被賦值了1至6的六個整數。
這里還有一些其他的方法來生成矩陣
例如“ones(2, 3)
”,也可以用來生成矩陣:
元素都為2,兩行三列的矩陣,就可以使用這個命令:
你可以把這個方法當成一個生成矩陣的快速方法。
w為一個一行三列的零矩陣,一行三列的A矩陣里的元素全部是零:
還有很多的方式來生成矩陣。
如果我對W進行賦值,用Rand命令建立一個一行三列的矩陣,因為使用了Rand命令,則其一行三列的元素均為隨機值,如“rand(3,3)
”命令,這就生成了一個3×3的矩陣,並且其所有元素均為隨機。
數值介於0和1之間,所以,正是因為這一點,我們可以得到數值均勻介於0和1之間的元素。
如果,你知道什么是高斯隨機變量,或者,你知道什么是正態分布的隨機變量,你可以設置集合W,使其等於一個一行三列的N矩陣,並且,來自三個值,一個平均值為0的高斯分布,方差或者等於1的標准偏差。
還可以設置地更復雜:
並用hist命令繪制直方圖。
繪制單位矩陣:
如果對命令不清楚,建議用help命令:
以上講解的內容都是Octave的基本操作。希望你能通過上面的講解,自己練習一些矩陣、乘、加等操作,將這些操作在Octave中熟練運用。
在接下來的視頻中,將會涉及更多復雜的命令,並使用它們在Octave中對數據進行更多的操作。
5.2 移動數據
參考視頻: 5 - 2 - Moving Data Around (16 min).mkv
在這段關於 Octave的輔導課視頻中,我將開始介紹如何在 Octave 中移動數據。
如果你有一個機器學習問題,你怎樣把數據加載到 Octave 中?
怎樣把數據存入一個矩陣?
如何對矩陣進行相乘?
如何保存計算結果?
如何移動這些數據並用數據進行操作?
進入我的 Octave 窗口,
我鍵入 A,得到我們之前構建的矩陣 A,也就是用這個命令生成的:
A = [1 2; 3 4; 5 6]
這是一個3行2列的矩陣,Octave 中的 size() 命令返回矩陣的尺寸。
所以 size(A)
命令返回3 2
實際上,size() 命令返回的是一個 1×2 的矩陣,我們可以用 sz 來存放。
設置 sz = size(A)
因此 sz 就是一個1×2的矩陣,第一個元素是3,第二個元素是2。
所以如果鍵入 size(sz)
看看 sz 的尺寸,返回的是1 2,表示是一個1×2的矩陣,1 和 2分別表示矩陣sz的維度 。
你也可以鍵入 size(A, 1)
,將返回3,這個命令會返回A 矩陣的第一個元素,A矩陣的第一個維度的尺寸,也就是 A 矩陣的行數。
同樣,命令 size(A, 2)
,將返回2,也就是 A 矩陣的列數。
如果你有一個向量 v,假如 v = [1 2 3 4]
,然后鍵入length(v)
,這個命令將返回最大維度的大小,返回4。
你也可以鍵入 length(A),由於矩陣A是一個3×2的矩陣,因此最大的維度應該是3,因此該命令會返回3。
但通常我們還是對向量使用 length 命令,而不是對矩陣使用 length 命令,比如
length([1;2;3;4;5])
,返回5。
如何在系統中加載數據和尋找數據:
當我們打開 Octave 時,我們通常已經在一個默認路徑中,這個路徑是 Octave的安裝位置,pwd 命令可以顯示出Octave 當前所處路徑。
cd
命令,意思是改變路徑,我可以把路徑改為C:\Users\ang\Desktop,這樣當前目錄就變為了桌面。
如果鍵入 ls,ls 來自於一個 Unix 或者 Linux 命令,ls
命令將列出我桌面上的所有路徑。
事實上,我的桌面上有兩個文件:featuresX.dat 和priceY.dat,是兩個我想解決的機器學習問題。
featuresX
文件如這個窗口所示,是一個含有兩列數據的文件,其實就是我的房屋價格數據,數據集中有47行,第一個房子樣本,面積是2104平方英尺,有3個卧室,第二套房子面積為1600,有3個卧室等等。
priceY這個文件就是訓練集中的價格數據,所以 featuresX 和priceY
就是兩個存放數據的文檔,那么應該怎樣把數據讀入 Octave 呢?我們只需要鍵入featuresX.dat,這樣我將加載了 featuresX 文件。同樣地我可以加載priceY.dat。其實有好多種辦法可以完成,如果你把命令寫成字符串的形式
load('featureX.dat'),也是可以的,這跟剛才的命令效果是相同的,只不過是把文件名寫成了一個字符串的形式,現在文件名被存在一個字符串中。Octave中使用引號來表示字符串。
另外 who 命令,能顯示出 在我的 Octave工作空間中的所有變量
所以我可以鍵入featuresX 回車,來顯示 featuresX
這些就是存在里面的數據。
還可以鍵入 size(featuresX)
,得出的結果是 47 2,代表這是一個47×2的矩陣。
類似地,輸入 size(priceY)
,結果是 47
1,表示這是一個47維的向量,是一個列矩陣,存放的是訓練集中的所有價格 Y 的值。
who 函數能讓你看到當前工作空間中的所有變量,同樣還有另一個 whos
命令,能更詳細地進行查看。
同樣也列出我所有的變量,不僅如此,還列出了變量的維度。
double 意思是雙精度浮點型,這也就是說,這些數都是實數,是浮點數。
如果你想刪除某個變量,你可以使用 clear
命令,我們鍵入 clear featuresX
,然后再輸入 whos
命令,你會發現 featuresX 消失了。
另外,我們怎么儲存數據呢?
我們設變量 v= priceY(1:10)
這表示的是將向量 Y 的前10個元素存入 v 中。
假如我們想把它存入硬盤,那么用 save hello.mat v
命令,這個命令會將變量v存成一個叫 hello.mat 的文件,讓我們回車,現在我的桌面上就出現了一個新文件,名為hello.mat。
由於我的電腦里同時安裝了 MATLAB,所以這個圖標上面有 MATLAB的標識,因為操作系統把文件識別為 MATLAB文件。如果在你的電腦上圖標顯示的不一樣的話,也沒有關系。
現在我們清除所有變量,直接鍵入clear
,這樣將刪除工作空間中的所有變量,所以現在工作空間中啥都沒了。
但如果我載入 hello.mat 文件,我又重新讀取了變量 v,因為我之前把變量
v存入了hello.mat 文件中,所以我們剛才用 save
命令做了什么。這個命令把數據按照二進制形式儲存,或者說是更壓縮的二進制形式,因此,如果v
是很大的數據,那么壓縮幅度也更大,占用空間也更小。如果你想把數據存成一個人能看懂的形式,那么可以鍵入:
save hello.txt v -ascii
這樣就會把數據存成一個文本文檔,或者將數據的 ascii 碼存成文本文檔。
我鍵入了這個命令以后,我的桌面上就有了 hello.txt文件。如果打開它,我們可以發現這個文本文檔存放着我們的數據。
這就是讀取和儲存數據的方法。
接下來我們再來講講操作數據的方法:
假如 A 還是那個矩陣
跟剛才一樣還是那個 3×2 的矩陣,現在我們加上索引值,比如鍵入 A(3,2)
這將索引到A 矩陣的 (3,2) 元素。這就是我們通常書寫矩陣的形式,寫成 A 32,3和2分別表示矩陣的第三行和第二列對應的元素,因此也就對應 6。
我也可以鍵入A(2,:)
來返回第二行的所有元素,冒號表示該行或該列的所有元素。
類似地,如果我鍵入 A(:,2)
,這將返回 A 矩陣第二列的所有元素,這將得到 2 4 6。
這表示返回A 矩陣的第二列的所有元素。
你也可以在運算中使用這些較為復雜的索引。
我再給你展示幾個例子,可能你也不會經常使用,但我還是輸入給你看 A([1 3],:)
,這個命令意思是取 A 矩陣第一個索引值為1或3的元素,也就是說我取的是A矩陣的第一行和第三行的每一列,冒號表示的是取這兩行的每一列元素,即:
可能這些比較復雜一點的索引操作你會經常用到。
我們還能做什么呢?依然是 A 矩陣,A(:,2)
命令返回第二列。
你也可以為它賦值,我可以取 A 矩陣的第二列,然后將它賦值為10 11 12,我實際上是取出了 A 的第二列,然后把一個列向量[10;11;12]賦給了它,因此現在 A 矩陣的第一列還是 1 3 5,第二列就被替換為 10 11 12。
接下來一個操作,讓我們把 A 設為A = [A, [100, 101,102]]
,這樣做的結果是在原矩陣的右邊附加了一個新的列矩陣,就是把 A矩陣設置為原來的 A 矩陣再在右邊附上一個新添加的列矩陣。
最后,還有一個小技巧,如果你就輸入 A(:)
,這是一個很特別的語法結構,意思是把 A
中的所有元素放入一個單獨的列向量,這樣我們就得到了一個 9×1 的向量,這些元素都是
A 中的元素排列起來的。
再來幾個例子:
我還是把 A 重新設為 [1 2; 3 4; 5 6],我再設一個 B為[11 12; 13 14; 15 16],我可以新建一個矩陣 C,C = [A B],這個意思就是把這兩個矩陣直接連在一起,矩陣
A 在左邊,矩陣 B 在右邊,這樣組成了 C 矩陣,就是直接把 A 和 B 合起來。
我還可以設C = [A; B]
,這里的分號表示把分號后面的東西放到下面。所以,[A;B]
的作用依然還是把兩個矩陣放在一起,只不過現在是上下排列,所以現在 A 在上面 B在下面,C 就是一個 6×2 矩陣。
簡單地說,分號的意思就是換到下一行,所以 C 就包括上面的A,然后換行到下面,然后在下面放上一個 B。
另外順便說一下,這個[A B]
命令跟 [A, B]
是一樣的,這兩種寫法的結果是相同的。
通過以上這些操作,希望你現在掌握了怎樣構建矩陣,也希望我展示的這些命令能讓你很快地學會怎樣把矩陣放到一起,怎樣取出矩陣,並且把它們放到一起,組成更大的矩陣。
通過幾句簡單的代碼,Octave能夠很方便地很快速地幫助我們組合復雜的矩陣以及對數據進行移動。這就是移動數據這一節課。
我認為對你來講,最好的學習方法是,下課后復習一下我鍵入的這些代碼好好地看一看,從課程的網上把代碼的副本下載下來,重新好好看看這些副本,然后自己在Octave 中把這些命令重新輸一遍,慢慢開始學會使用這些命令。
當然,沒有必要把這些命令都記住,你也不可能記得住。你要做的就是,了解一下你可以用哪些命令,做哪些事。這樣在你今后需要編寫學習算法時,如果你要找到某個Octave中的命令,你可能回想起你之前在這里學到過,然后你就可以查找課程中提供的程序副本,這樣就能很輕松地找到你想使用的命令了。
5.3 計算數據
參考視頻: 5 - 3 - Computing on Data (13 min).mkv
現在,你已經學會了在Octave中如何加載或存儲數據,如何把數據存入矩陣等等。在這段視頻中,我將介紹如何對數據進行運算,稍后我們將使用這些運算操作來實現我們的學習算法。
這是我的 Octave窗口,我現在快速地初始化一些變量。比如設置A為一個3×2的矩陣,設置B為一個3 ×2矩陣,設置C為2 × 2矩陣。
我想算兩個矩陣的乘積,比如說 A × C,我只需鍵入A×C
,這是一個 3×2 矩陣乘以 2×2矩陣,得到這樣一個3×2矩陣。
你也可以對每一個元素,做運算 方法是做點乘運算A .\*B
,這么做Octave將矩陣 A中的每一個元素與矩陣 B 中的對應元素相乘
A .\* B
這里第一個元素1乘以11得到11,第二個元素2乘以12得到24,這就是兩個矩陣的元素位運算。通常來說,在Octave中點號一般用來表示元素位運算。
這里是一個矩陣A,這里我輸入A .\^ 2
,這將對矩陣A中每一個元素平方。
我們設V是一個向量,設V為 [1; 2; 3] 是列向量,你也可以輸入1 ./V
,得到每一個元素的倒數,所以這樣一來,就會分別算出 1/1 1/2 1/3。
矩陣也可以這樣操作,1 ./ A
得到A中每一個元素的倒數。
同樣地,這里的點號還是表示對每一個元素進行操作。
我們還可以進行求對數運算,也就是對每個元素進行求對數運算。
還有自然數e的冪次運算,就是以e為底,以這些元素為冪的運算。
我還可以用 abs來對 v 的每一個元素求絕對值,當然這里 v都是正數。我們換成另一個這樣對每個元素求絕對值,得到的結果就是這些非負的元素。還有–v,給出V中每個元素的相反數,這等價於 -1 乘以 v,一般就直接用 -v
就好了,其實就等於 -1*v。
還有一個技巧,比如說
我們想對v中的每個元素都加1,那么我們可以這么做,首先構造一個3行1列的1向量,然后把這個1向量跟原來的向量相加,因此
v 向量從[1 2 3] 增至 [2 3 4]。我用了一個,length(v)
命令,因此這樣一來,ones(length(v) ,1)
就相當於ones(3,1)
,然后我做的是v +ones(3,1)
,也就是將 v 的各元素都加上這些1,這樣就將 v 的每個元素增加了1。
另一種更簡單的方法是直接用 v+1
,v + 1
也就等於把 v 中的每一個元素都加上1。
現在,讓我們來談談更多的操作。
矩陣A 如果你想要求它的轉置,那么方法是用A’,將得出 A 的轉置矩陣。當然,如果我寫(A’)’
,也就是 A 轉置兩次,那么我又重新得到矩陣 A。
還有一些有用的函數,比如: a=[1 15 2 0.5]
,這是一個1行4列矩陣,val=max(a)
,這將返回A矩陣中的最大值15。
我還可以寫 [val, ind] =max(a)
,這將返回a矩陣中的最大值存入val,以及該值對應的索引,元素15對應的索引值為2
存入ind,所以 ind 等於2
特別注意一下,如果你用命令 max(A)
,A是一個矩陣的話,這樣做就是對每一列求最大值。
我們還是用這個例子,這個 a 矩陣a=[1 15 2 0.5]
,如果輸入a\<3
,這將進行逐元素的運算,所以元素小於3的返回1,否則返回0。
因此,返回[1 1 0 1]。也就是說,對a矩陣的每一個元素與3進行比較,然后根據每一個元素與3的大小關系,返回1和0表示真與假。
如果我寫 find(a\<3)
,這將告訴我a 中的哪些元素是小於3的。
設A = magic(3)
,magic 函數將返回一個矩陣,稱為魔方陣或幻方 (magic squares),它們具有以下這樣的數學性質:它們所有的行和列和對角線加起來都等於相同的值。
當然據我所知,這在機器學習里基本用不上,但我可以用這個方法很方便地生成一個3行3列的矩陣,而這個魔方矩陣這神奇的方形屏幕。每一行、每一列、每一個對角線三個數字加起來都是等於同一個數。
在其他有用的機器學習應用中,這個矩陣其實沒多大作用。
如果我輸入 [r,c] = find(A\>=7)
,這將找出所有A矩陣中大於等於7的元素,因此,r 和c分別表示行和列,這就表示,第一行第一列的元素大於等於7,第三行第二列的元素大於等於7,第二行第三列的元素大於等於7。
順便說一句,其實我從來都不去刻意記住這個 find 函數,到底是怎么用的,我只需要會用help 函數就可以了,每當我在使用這個函數,忘記怎么用的時候,我就可以用 help函數,鍵入 help find
來找到幫助文檔。
最后再講兩個內容,一個是求和函數,這是 a 矩陣:
鍵入 sum(a)
,就把 a 中所有元素加起來了。
如果我想把它們都乘起來,鍵入 prod(a)
,prod 意思是product(乘積),它將返回這四個元素的乘積。
floor(a)
是向下四舍五入,因此對於 a 中的元素0.5將被下舍入變成0。
還有 ceil(a)
,表示向上四舍五入,所以0.5將上舍入變為最接近的整數,也就是1。
鍵入 type(3)
,這通常得到一個3×3的矩陣,如果鍵入 max(rand(3),rand(3))
,這樣做的結果是返回兩個3×3的隨機矩陣,並且逐元素比較取最大值。
假如我輸入max(A,[],1)
,這樣做會得到每一列的最大值。
所以第一列的最大值就是8,第二列是9,第三列的最大值是7,這里的1表示取A矩陣第一個維度的最大值。
相對地,如果我鍵入max(A,[],2)
,這將得到每一行的最大值,所以,第一行的最大值是等於8,第二行最大值是7,第三行是9。
所以你可以用這個方法來求得每一行或每一列的最值,另外,你要知道,默認情況下max(A)
返回的是每一列的最大值,如果你想要找出整個矩陣A的最大值,你可以輸入max(max(A))
,或者你可以將A 矩陣轉成一個向量,然后鍵入 max(A(:))
,這樣做就是把 A 當做一個向量,並返回 A向量中的最大值。
最后,讓我們把 A設為一個9行9列的魔方陣,魔方陣具有的特性是每行每列和對角線的求和都是相等的。
這是一個9×9的魔方陣,我們來求一個 sum(A,1)
,這樣就得到每一列的總和,這也驗證了一個9×9的魔方陣確實每一列加起來都相等,都為369。
現在我們來求每一行的和,鍵入sum(A,2)
,這樣就得到了A 中每一行的和加起來還是369。
現在我們來算A 的對角線元素的和。我們現在構造一個9×9 的單位矩陣,
鍵入 eye(9)
設為I9
然后我們要用 A逐點乘以這個單位矩陣,除了對角線元素外,其他元素都會得到0。
鍵入sum(sum(A.\*eye(9))
這實際上是求得了,這個矩陣對角線元素的和確實是369。
你也可以求另一條對角線的和也是是369。
flipup/flipud 表示向上/向下翻轉。
同樣地,如果你想求這個矩陣的逆矩陣,鍵入
pinv(A)
,通常稱為偽逆矩陣,你就把它看成是矩陣 A 求逆,因此這就是 A
矩陣的逆矩陣。
設 temp = pinv(A)
,然后再用temp 乘以 A,這實際上得到的就是單位矩陣,對角線為1,其他元素為0。
如何對矩陣中的數字進行各種操作,在運行完某個學習算法之后,通常一件最有用的事情是看看你的結果,或者說讓你的結果可視化,在接下來的視頻中,我會非常迅速地告訴你,如何很快地畫圖,如何只用一兩行代碼,你就可以快速地可視化你的數據,這樣你就能更好地理解你使用的學習算法。
5.4 繪圖數據
參考視頻: 5 - 4 - Plotting Data (10 min).mkv
當開發學習算法時,往往幾個簡單的圖,可以讓你更好地理解算法的內容,並且可以完整地檢查下算法是否正常運行,是否達到了算法的目的。
例如在之前的視頻中,我談到了繪制成本函數\(J(\theta)\),可以幫助確認梯度下降算法是否收斂。通常情況下,繪制數據或學習算法所有輸出,也會啟發你如何改進你的學習算法。幸運的是,Octave有非常簡單的工具用來生成大量不同的圖。當我用學習算法時,我發現繪制數據、繪制學習算法等,往往是我獲得想法來改進算法的重要部分。在這段視頻中,我想告訴你一些Octave的工具來繪制和可視化你的數據。
我們先來快速生成一些數據用來繪圖。
如果我想繪制正弦函數,這是很容易的,我只需要輸入plot(t,y1)
,並回車,就出現了這個圖:
橫軸是t變量,縱軸是y1,也就是我們剛剛所輸出的正弦函數。
讓我們設置y2
Octave將會消除之前的正弦圖,並且用這個余弦圖來代替它,這里縱軸cos(x)從1開始,
如果我要同時表示正弦和余弦曲線。
我要做的就是,輸入:plot(t, y1)
,得到正弦函數,我使用函數hold on,hold on函數的功能是將新的圖像繪制在舊的之上
我現在繪制y2,輸入:plot(t, y2)
。
我要以不同的顏色繪制余弦函數,所以我在這里輸入帶引號的r繪制余弦函數,r表示所使用的顏色:plot(t,y2,’r’)
,再加上命令xlabel('time')
,
來標記X軸即水平軸,輸入ylabel('value')
,來標記垂直軸的值。
同時我也可以來標記我的兩條函數曲線,用這個命令 legend('sin','cos')
將這個圖例放在右上方,表示這兩條曲線表示的內容。最后輸入title('myplot')
,在圖像的頂部顯示這幅圖的標題。
如果你想保存這幅圖像,你輸入print –dpng 'myplot.png'
,png是一個圖像文件格式,如果你這樣做了,它可以讓你保存為一個文件。
Octave也可以保存為很多其他的格式,你可以鍵入help plot
。
最后如果你想,刪掉這個圖像,用命令close會讓這個圖像關掉。
Octave也可以讓你為圖像標號
你鍵入figure(1); plot(t, y1);
將顯示第一張圖,繪制了變量t y1。
鍵入figure(2); plot(t, y2);
將顯示第一張圖,繪制了變量t y2。
subplot命令,我們要使用subplot(1,2,1)
,它將圖像分為一個1*2的格子,也就是前兩個參數,然后它使用第一個格子,也就是最后一個參數1的意思。
我現在使用第一個格子,如果鍵入plot(t,y1)
,現在這個圖顯示在第一個格子。如果我鍵入subplot(1,2,2)
,那么我就要使用第二個格子,鍵入plot(t,y2)
;現在y2顯示在右邊,也就是第二個格子。
最后一個命令,你可以改變軸的刻度,比如改成[0.5 1 -1 1],輸入命令:axis([0.5 1 -1 1])
也就是設置了右邊圖的x軸和y軸的范圍。具體而言,它將右圖中的橫軸的范圍調整至0.5到1,豎軸的范圍為-1到1。
你不需要記住所有這些命令,如果你需要改變坐標軸,或者需要知道axis命令,你可以用Octave中用help命令了解細節。
最后,還有幾個命令。
Clf
(清除一幅圖像)。
讓我們設置A等於一個5×5的magic方陣:
我有時用一個巧妙的方法來可視化矩陣,也就是imagesc(A)命令,它將會繪制一個5*5的矩陣,一個5*5的彩色格圖,不同的顏色對應A矩陣中的不同值。
我還可以使用函數colorbar,讓我用一個更復雜的命令 imagesc(A),colorbar,colormap gray。這實際上是在同一時間運行三個命令:運行imagesc
,然后運行,colorbar
然后運行colormap gray
。
它生成了一個顏色圖像,一個灰度分布圖,並在右邊也加入一個顏色條。所以這個顏色條顯示不同深淺的顏色所對應的值。
你可以看到在不同的方格,它對應於一個不同的灰度。
輸入imagesc(magic(15)),colorbar,colormap gray
這將會是一幅15*15的magic方陣值的圖。
最后,總結一下這段視頻。你看到我所做的是使用逗號連接函數調用。如果我鍵入a=1,b=2,c=3然后按Enter鍵,其實這是將這三個命令同時執行,或者是將三個命令一個接一個執行,它將輸出所有這三個結果。
這很像a=1; b=2;c=3;如果我用分號來代替逗號,則沒有輸出出任何東西。
這里我們稱之為逗號連接的命令或函數調用。
用逗號連接是另一種Octave中更便捷的方式,將多條命令例如imagesc colorbar colormap,將這多條命令寫在同一行中。
現在你知道如何繪制Octave中不同的圖像,在下面的視頻中,我將告訴你怎樣在Octave中,寫控制語句,比如if
while for語句,並且定義和使用函數。
5.5 控制語句:for,while,if語句
參考視頻: 5 - 5 - Control Statements_ for, while, if statements (13 min).mkv
在這段視頻中,我想告訴你怎樣為你的 Octave 程序寫控制語句。諸如:"for" "while" "if" 這些語句,並且如何定義和使用方程。
我先告訴你如何使用 “for” 循環。
首先,我要將 v 值設為一個10行1列的零向量。
接着我要寫一個 “for" 循環,讓 i 等於 1 到 10,寫出來就是 i = 1:10。我要設 v(i)的值等於 2 的 i 次方,循環最后寫上“end”。
向量 v 的值就是這樣一個集合 2的一次方、2的二次方,依此類推。這就是我的 i 等於 1 到 10的語句結構,讓 i 遍歷 1 到 10的值。
另外,你還可以通過設置你的 indices (索引) 等於 1一直到10,來做到這一點。這時
indices 就是一個從1到10的序列。
你也可以寫 i = indices
,這實際上和我直接把 i 寫到 1 到 10 是一樣。你可以寫 disp(i)
,也能得到一樣的結果。所以 這就是一個 “for” 循環。
如果你對 “break” 和 “continue” 語句比較熟悉,Octave里也有 “break” 和 “continue”語句,你也可以在 Octave環境里使用那些循環語句。
但是首先讓我告訴你一個 while 循環是如何工作的:
這是什么意思呢:我讓 i 取值從 1 開始,然后我要讓 v(i) 等於 100,再讓 i 遞增 1,直到 i 大於 5停止。
現在來看一下結果,我現在已經取出了向量的前五個元素,把他們用100覆蓋掉,這就是一個while循環的句法結構。
現在我們來分析另外一個例子:
這里我將向你展示如何使用break語句。比方說 v(i) = 999,然后讓 i = i+1,當 i 等於6的時候 break (停止循環),結束 (end)。
當然這也是我們第一次使用一個 if 語句,所以我希望你們可以理解這個邏輯,讓 i 等於1 然后開始下面的增量循環,while語句重復設置 v(i) 等於999,不斷讓i增加,然后當 i 達到6,做一個中止循環的命令,盡管有while循環,語句也就此中止。所以最后的結果是取出向量 v 的前5個元素,並且把它們設置為999。
所以,這就是if 語句和 while 語句的句法結構。並且要注意要有end,上面的例子里第一個 end 結束的是 if
語句,第二個 end 結束的是 while 語句。
現在讓我告訴你使用 if-else 語句:
最后,提醒一件事:如果你需要退出 Octave,你可以鍵入exit
命令然后回車就會退出 Octave,或者命令quit
也可以。
最后,讓我們來說說函數 (functions),如何定義和調用函數。
我在桌面上存了一個預先定義的文件名為 “squarethisnumber.m”,這就是在 Octave 環境下定義的函數。
讓我們打開這個文件。請注意,我使用的是微軟的寫字板程序來打開這個文件,我只是想建議你,如果你也使用微軟的Windows系統,那么可以使用寫字板程序,而不是記事本來打開這些文件。如果你有別的什么文本編輯器也可以,記事本有時會把代碼的間距弄得很亂。如果你只有記事本程序,那也能用。我建議你用寫字板或者其他可以編輯函數的文本編輯器。
現在我們來說如何在 Octave 里定義函數:
這個文件只有三行:
第一行寫着 function y = squareThisNumber(x)
,這就告訴 Octave,我想返回一個 y值,我想返回一個值,並且返回的這個值將被存放於變量 y 里。另外,它告訴了Octave這個函數有一個參數,就是參數 x,還有定義的函數體,也就是 y 等於 x 的平方。
還有一種更高級的功能,這只是對那些知道“search path (搜索路徑)”這個術語的人使用的。所以如果你想要修改
Octave的搜索路徑,你可以把下面這部分作為一個進階知識,或者選學材料,僅適用於那些熟悉編程語言中搜索路徑概念的同學。
你可以使用addpath 命令添加路徑,添加路徑“C:\Users\ang\desktop”將該目錄添加到Octave的搜索路徑,這樣即使你跑到其他路徑底下,Octave依然知道會在 Users\ang\desktop目錄下尋找函數。這樣,即使我現在在不同的目錄下,它仍然知道在哪里可以找到“SquareThisNumber” 這個函數。
但是,如果你不熟悉搜索路徑的概念,不用擔心,只要確保在執行函數之前,先用 cd
命令設置到你函數所在的目錄下,實際上也是一樣的效果。
Octave
還有一個其他許多編程語言都沒有的概念,那就是它可以允許你定義一個函數,使得返回值是多個值或多個參數。這里就是一個例子,定義一個函數叫:
“SquareAndCubeThisNumber(x)
” (x的平方以及x的立方)
這說的就是函數返回值是兩個: y1 和 y2
接下來就是y1是被平方后的結果,y2是被立方后的結果,這就是說,函數會真的返回2個值。
有些同學可能會根據你使用的編程語言,比如你們可能熟悉的C或C++,通常情況下,認為作為函數返回值只能是一個值,但
Octave 的語法結構就不一樣,可以返回多個值。
如果我鍵入 [a,b] = SquareAndCubeThisNumber(5)
,然后,a 就等於25,b 就等於5的立方125。
所以說如果你需要定義一個函數並且返回多個值,這一點常常會帶來很多方便。
最后,我來給大家演示一下一個更復雜一點的函數的例子。
比方說,我有一個數據集,像這樣,數據點為[1,1], [2,2],[3,3],我想做的事是定義一個 Octave 函數來計算代價函數 \(J(\theta)\),就是計算不同 \(\theta\)值所對應的代價函數值\(J\)。
首先讓我們把數據放到 Octave 里,我把我的矩陣設置為X = [1 1; 1 2; 1 3];
請仔細看一下這個函數的定義,確保你明白了定義中的每一步。
現在當我在 Octave 里運行時,我鍵入$ J$ = costFunction$ J$ (X, y, theta),它就計算出 \(j\)等於0,這是因為如果我的數據集x 為 [1;2;3], y 也為 [1;2;3] 然后設置 \(\theta_0\) 等於0,\(\theta_1\)等於1,這給了我恰好45度的斜線,這條線是可以完美擬合我的數據集的。
而相反地,如果我設置theta 等於[0;0],那么這個假設就是0是所有的預測值,和剛才一樣,設置\(\theta_0\) = 0,\(\theta_1\)也等於0,然后我計算的代價函數,結果是2.333。實際上,他就等於1的平方,也就是第一個樣本的平方誤差,加上2的平方,加上3的平方,然后除以2m,也就是訓練樣本數的兩倍,這就是2.33。
因此這也反過來驗證了我們這里的函數,計算出了正確的代價函數。這些就是我們用簡單的訓練樣本嘗試的幾次試驗,這也可以作為我們對定義的代價函數\(J\)進行了完整性檢查。確實是可以計算出正確的代價函數的。至少基於這里的 X和 y是成立的。也就是我們這幾個簡單的訓練集,至少是成立的。
現在你知道如何在 Octave 環境下寫出正確的控制語句,比如 for 循環、while 循環和 if語句,以及如何定義和使用函數。
在接下來的Octave 教程視頻里,我會講解一下向量化,這是一種可以使你的 Octave程序運行非常快的思想。
5.6 向量化
參考視頻: 5 - 6 - Vectorization (14 min).mkv
在這段視頻中,我將介紹有關向量化的內容,無論你是用Octave,還是別的語言,比如MATLAB或者你正在用Python、NumPy 或 Java C C++,所有這些語言都具有各種線性代數庫,這些庫文件都是內置的,容易閱讀和獲取,他們通常寫得很好,已經經過高度優化,通常是數值計算方面的博士或者專業人士開發的。
而當你實現機器學習算法時,如果你能好好利用這些線性代數庫,或者數值線性代數庫,並聯合調用它們,而不是自己去做那些函數庫可以做的事情。如果是這樣的話,那么通常你會發現:首先,這樣更有效,也就是說運行速度更快,並且更好地利用你的計算機里可能有的一些並行硬件系統等等;其次,這也意味着你可以用更少的代碼來實現你需要的功能。因此,實現的方式更簡單,代碼出現問題的有可能性也就越小。
舉個具體的例子:與其自己寫代碼做矩陣乘法。如果你只在Octave中輸入a乘以b就是一個非常有效的兩個矩陣相乘的程序。有很多例子可以說明,如果你用合適的向量化方法來實現,你就會有一個簡單得多,也有效得多的代碼。
讓我們來看一些例子:這是一個常見的線性回歸假設函數:
如果你想要計算\(h_\theta(x)\) ,注意到右邊是求和,那么你可以自己計算j = 0 到 j = n 的和。但換另一種方式來想想,把 \(h_\theta(x)\) 看作\(\theta^Tx\),那么你就可以寫成兩個向量的內積,其中\(\theta\)就是\(\theta_0\)、\(\theta_1\)、\(\theta_2\),如果你有兩個特征量,如果 n = 2,並且如果你把 x 看作\(x_0\)、\(x_1\)、\(x_2\),這兩種思考角度,會給你兩種不同的實現方式。
比如說,這是未向量化的代碼實現方式:
計算\(h_\theta(x)\)是未向量化的,我們可能首先要初始化變量 prediction 的值為0.0,而這個變量prediction 的最終結果就是\(h_\theta(x)\),然后我要用一個 for 循環,j 取值 0 到n+1,變量prediction 每次就通過自身加上 theta(j) 乘以 x(j)更新值,這個就是算法的代碼實現。
順便我要提醒一下,這里的向量我用的下標是0,所以我有\(\theta_0\)、\(\theta_1\)、\(\theta_2\),但因為MATLAB的下標從1開始,在 MATLAB 中\(\theta_0\),我們可能會用 theta(1) 來表示,這第二個元素最后就會變成,theta(2) 而第三個元素,最終可能就用theta(3)表示,因為MATLAB中的下標從1開始,這就是為什么這里我的 for 循環,j 取值從 1 直到n+1,而不是從 0 到 n。這是一個未向量化的代碼實現方式,我們用一個 for 循環對 n 個元素進行加和。
作為比較,接下來是向量化的代碼實現:
你把x和\(\theta\)看做向量,而你只需要令變量prediction等於theta轉置乘以x,你就可以這樣計算。與其寫所有這些for循環的代碼,你只需要一行代碼,這行代碼就是利用 Octave 的高度優化的數值,線性代數算法來計算兩個向量\(\theta\)以及x的內積,這樣向量化的實現更簡單,它運行起來也將更加高效。這就是 Octave 所做的而向量化的方法,在其他編程語言中同樣可以實現。
讓我們來看一個C++ 的例子:
與此相反,使用較好的C++數值線性代數庫,你可以寫出像右邊這樣的代碼,因此取決於你的數值線性代數庫的內容。你只需要在C++中將兩個向量相乘,根據你所使用的數值和線性代數庫的使用細節的不同,你最終使用的代碼表達方式可能會有些許不同,但是通過一個庫來做內積,你可以得到一段更簡單、更有效的代碼。
現在,讓我們來看一個更為復雜的例子,這是線性回歸算法梯度下降的更新規則:
我們用這條規則對 j 等於 0、1、2等等的所有值,更新對象\(\theta_j\),我只是用\(\theta_0\)、\(\theta_1\)、\(\theta_2\)來寫方程,假設我們有兩個特征量,所以n等於2,這些都是我們需要對\(\theta_0\)、\(\theta_1\)、\(\theta_2\)進行更新,這些都應該是同步更新,我們用一個向量化的代碼實現,這里是和之前相同的三個方程,只不過寫得小一點而已。
你可以想象實現這三個方程的方式之一,就是用一個 for 循環,就是讓 j等於0、等於1、等於2,來更新\(\theta_j\)。但讓我們用向量化的方式來實現,看看我們是否能夠有一個更簡單的方法。基本上用三行代碼或者一個for 循環,一次實現這三個方程。讓我們來看看怎樣能用這三步,並將它們壓縮成一行向量化的代碼來實現。做法如下:
我打算把\(\theta\)看做一個向量,然后我用\(\theta\)-\(\alpha\) 乘以某個別的向量\(\delta\) 來更新\(\theta\)。
這里的 \(\delta\) 等於
讓我解釋一下是怎么回事:我要把\(\theta\)看作一個向量,有一個 n+1 維向量,\(\alpha\) 是一個實數,\(\delta\)在這里是一個向量。
所以這個減法運算是一個向量減法,因為 \(\alpha\) 乘以 δ是一個向量,所以\(\theta\)就是\(\theta\) - \(\alpha \delta\)得到的向量。
那么什么是向量 \(\delta\) 呢 ?
\(X^{(i)}\)是一個向量
你就會得到這些不同的式子,然后作加和。
實際上,在以前的一個小測驗,如果你要解這個方程,我們說過為了向量化這段代碼,我們會令u = 2v +5w
因此,我們說向量u等於2乘以向量v加上5乘以向量w。用這個例子說明,如何對不同的向量進行相加,這里的求和是同樣的道理。
這就是為什么我們能夠向量化地實現線性回歸。
所以,我希望步驟是有邏輯的。請務必看視頻,並且保證你確實能理解它。如果你實在不能理解它們數學上等價的原因,你就直接實現這個算法,也是能得到正確答案的。所以即使你沒有完全理解為何是等價的,如果只是實現這種算法,你仍然能實現線性回歸算法。如果你能弄清楚為什么這兩個步驟是等價的,那我希望你可以對向量化有一個更好的理解,如果你在實現線性回歸的時候,使用一個或兩個以上的特征量。
有時我們使用幾十或幾百個特征量來計算線性歸回,當你使用向量化地實現線性回歸,通常運行速度就會比你以前用你的for循環快的多,也就是自己寫代碼更新\(\theta_0\)、\(\theta_1\)、\(\theta_2\)。
因此使用向量化實現方式,你應該是能夠得到一個高效得多的線性回歸算法。而當你向量化我們將在之后的課程里面學到的算法,這會是一個很好的技巧,無論是對於Octave 或者一些其他的語言 如C++、Java 來讓你的代碼運行得更高效。
5.7 工作和提交的編程練習
參考視頻: 5 - 7 - Working on and Submitting Programming Exercises (4 min).mkv
在這段視頻中,我想很快地介紹一下這門課程做作業的流程,以及如何使用作業提交系統。這個提交系統可以即時檢驗你的機器學習程序答案是否正確。
在'ml-class-ex1'目錄中,我們提供了大量的文件,其中有一些需要由你自己來編輯,因此第一個文件應該符合編程練習中pdf文件的要求,其中一個我們要求你編寫的文件是warmUpExercise.m這個文件,這個文件只是為了確保你熟悉提交系統。
你需要做的就是提交一個5×5的矩陣,就是A = eye(5)
這將修改該函數以產生5×5的單位矩陣,現在warmUpExercise()這個方程就實現了返回5x5的單位矩陣,將它保存一下,所以我已經完成了作業的第一部分。
現在回到我的 Octave 窗口,現在來到我的目錄C:\Users\ang\Desktop\ml-class-ex1如果我想確保我已經實現了程序 像這樣輸入'warmUpExercise()'好了它返回了我們用剛才寫的代碼創建的一個5x5的單位矩陣
我現在可以按如下步驟提交代碼,我要在這里目錄下鍵入submit()。我要提交第一部分 所以我選擇輸入'1'。這時它問我的電子郵件地址,我們打開課程網站,輸入用戶名密碼。
按下回車鍵,它連接到服務器,並將其提交,然后它就會立刻告訴你:恭喜您!已成功完成作業1第1部分。這就確認了你已經做對了第一部分練習,如果你提交的答案不正確,那么它會給你一條消息,說明你沒有完全答對,您還可以繼續使用此提交密碼,也可以生成新密碼。你的密碼是否會顯示出來取決於你使用的操作系統。
這就是提交作業的方法,你完成家庭作業的時候,我希望你都能答對。