Logistic Regression
1.1 Visualizing the data
我們的目標是根據ex2data1裏面的數據,把被接受入學的數據和被拒絕入學的數據都標註在一張座標圖上。
爲了表示區分,接受入學的樣本在圖上用“黑色小十字”來表示,而拒絕入學的樣本在圖上用“黃色小圓點”來表示。
如果要做這個事情,第一步就是要把混雜在一起的數據分成兩堆。y=1的爲一堆,y=0的爲一堆。
難點1 find函數
我能夠猜到 pos = find(y ==1) 是將y向量中等於1那些值所在的位置記錄下來放到一個向量中,這個向量就是pos。這裏主要是瞭解find這個函數的用法
在document裏面有很多find函數,就是這個Find indices of nonzero elements.
其中有一個 Elements Equal to Specific Values用法。難點2 矩陣切片的再認識
X(pos,1)這個我也能猜出來是,它表示的是X這個矩陣中第一列中的行序數在pos向量中的那些值。我的疑問是,這個矩陣切片操作還可以這麼表示,一個變量用向量,另一個變量用常數。仔細想想其實之前也用過,比如X = data(:,[1,2])中逗號前的部分“:”其實就是一種向量的表示方法,也就是說作爲矩陣定位的變量是可以用向量來表示的;而逗號後面的部分“[1,2]”其實是第一列和第二列構成的矩陣,從這個表示方法來推測,其實1,2這些所謂的常數,其實應該是代表列位置的索引。總結一下,其實一個矩陣A的切片方式A(X,Y),X表示的行的部分,X可以由表示行的那些索引構成的向量,Y表示列的部分,可以由表示列的索引構成的向量。對於這個例子來說我還可以構造一個矩陣 s = data([1,15,25],[1,3])
搞清了這兩點,我們就可以畫圖了,參考第二週作業筆記中對於畫圖函數的理解。以在座標軸上標註出“被接受”的樣本爲例,最重要的是兩個向量,一個向量是被接受樣本的x座標向量,另一個是被接受樣本的y座標向量,x座標向量可以用X(pos,1)表示,y座標向量可以用y(pos,2),在加上一些圖形屬性的參數就是
plot(X(pos, 1), X(pos, 2), ‘k+’,’LineWidth’, 2, ‘MarkerSize’, 7);
同理“被拒絕”樣本的x,y座標軸向量爲X(neg, 1), X(neg, 2),繪圖函數爲:
plot(X(neg, 1), X(neg, 2), ‘ko’, ‘MarkerFaceColor’, ‘y’, ‘MarkerSize’, 7);
1.2 Implementation
1.2.1 sigmoid function
其實這個函數非常簡單,只是有兩點要注意
1. 指數函數的表示方法
如果是以e爲底的那麼就用 exp(x)來表示,如果是以其它自然數爲底的比如說
2. 向量形式的實現
這個sigmoid函數是以向量的形式來實現的,在用1除以1+e^z的時候要用“./”而不是普通除,這個如果不是看人家的代碼還真不容易發現。
所謂向量化可以這麼理解:我需要對向量中的每個元素進行同一種運算。
比如以sigmoid函數作爲例子,假設待計算的向量爲
引入向量運算的理由是經常有大量的數據經常用同一個函數進行運算,按照一般程序的處理方法,就得用循環語句了。如果能把接受同樣過程處理的數據打包批量進行處理就好了,這個就是向量化的思想。
Matlab裏面大多數函數都已經實現了向量化,我們在實現函數的時候也要儘可能的實現向量化,具體來說就是如果可以用Matlab提供現有運算工具就能實現對向量(或者矩陣)中每個元素的處理就可以直接用先有工具的組合就行了。但是如果沒有,那就只能寫循環語句了。以這個sigmoid函數爲例,待處理數據初始的狀態爲
這裏面還涉及到一個知識點,就是矩陣和標量之間的運算,標量和矩陣之間的運算相當於標量和矩陣中的每一個元素進行運算,對於除法來說,標量在左邊和在右邊是完全不一樣的,你可以做一個實驗,構造一個矩陣A,之後用2除以A和A除以2,結果是不同的,這點很容易知道,但是如果是2除以A要用 “2 ./ A” A除以2用“A ./ 2”或者“A / 2 ”都可以。其它的乘法以及加減法加點或者不加點運算效果都是一樣的。只有除法要注意。
1.2.2 Cost function and gradient
這塊還是要用向量的方式實現,可以用第二週發現的方法,我們用幾個小的觀察一下規律嘛(有點像高中學數列的時候)
1. 關於cost function的實現,我自己沒想出來向量形式的實現方式,只能用循環的方法實現(好Low啊),後來看了網友的答案得到啓發。我來推導一下這個是怎麼實現的。
首先還是用一個小數來把問題具體化,設m=3,n=2(三個樣本,兩個特徵)。
係數矩陣
參數向量爲
如果用樣本矩陣來表示係數矩陣就應該是
其實這個形式就跟CS229-notes1裏面page10的那個係數矩陣表達方法一樣的。我爲什麼要寫一遍係數矩陣,是因爲後面要劃歸爲向量形式要對這種形式比較熟悉。
接下來我們把
我們把
這部分就是
這部分其實就是向量
向量
而
* 方法一:把
其實這就看出來了,就是向量
* 方法二:因此向量
也就相當於向量
而向量
因此
由此我們可以得到
同理
J = -1 / m * (dot(y,log(sigmoid(X * theta))) + dot((1 - y ),log(1 - sigmoid(X * theta))));
另外我還發現不知道是效率高咋滴,大家都不喜歡用向量內積的方式,而是先用點乘的方式之後在用sum把一個向量裏面的所有分量相加
J = -1 / m * sum (y .* log(sigmoid(X * theta)) + (1 - y ) .* log(1 - sigmoid(X * theta)));
2. 求梯度,其實梯度就是參數迭代公式中的偏導部分,爲啥這個叫做梯度,其實這個是斜率,參數要按照一個角度下降,控制這個角度多大的就是這個梯度的大小。
參數收斂公式:
其實這個公式沒什麼牛逼的地方,思想很簡單就是把
我們依然用小數來具體化,設m = 3 ,n=2,j = 1,則
這種多個乘積相加的形式應該本能的反映出這是向量點積於是,可以分成
所以
於是全部參數的梯度一次性求出就是
看了一下答案,發現
所以
而
(PS:這裏爲了方便把1/3略去了)
所以這個推導還不是很容易的。
grad = 1 / m * (X' * (sigmoid(X * theta)-y));
這兩行代碼用了兩天,平均一天一行代碼(囧)
1.2.3 Learning parameters using fminunc
這個不需要我們自己實現,需要注意的是,這個使用的套路是,我們實現一個函數,這個函數再作爲參數傳遞給優化的函數
1.2.3.1 plotDecisionBoundary.m
1.2.3.1.1 contour函數的使用方法
這個分成兩種情況,第一種情況是係數矩陣的列小於3,也就是特徵只有兩個的情況,特徵如果只有兩個的情況下,決策邊界就是平面圖形上的一條直線,畫圖的方法比較容易直接看程序吧。
當係數矩陣的列大於3的時候,畫圖利用的是輪廓圖的方法。爲了便於直觀的思考,我們這裏只考慮特徵值有三個的情況,也就是決策邊界圖形是一個平面(x+y+z=0是一個平面)
要搞清contour()這個函數是怎麼用的,就要首先搞清在Matlab裏面三維圖形是怎麼畫的
假設Matlab裏面有一個三維繪製函數draw3D,那麼根據二維圖像的繪製方式這個函數只要接受三個參數就可以了,draw3D(x,y,z),第一個參數x是座標在x軸方向上的分量構成的向量,第二個參數是座標在y軸上的分量構成的向量,第三個參數z是座標在z軸上的分量構成的向量。因爲三維空間上的一個點就是(x,y,z),如果這三個分量都確定了,這個點就確認了,如果函數的參數是這樣設置的,那麼函數使用起來就特別直觀,而實際上Matlab裏面的3D繪圖函數上是接收矩陣作爲參數的,不知道爲啥弄得那麼複雜。在Matlab裏最基礎的繪製3D圖形的函數叫做mesh,不叫draw3D。
以mesh()爲例,它的參數有這樣三種情況
1. 如果x,y,z是同型的二維矩陣,那麼這三個矩陣中處於相同位置的值,構成了一個座標點,比如x(1,1),y(1,1),z(1,1)構成了屬於圖形上的一個點,但是如果x(1,1),y(1,1),z(1,2),這三個值構成的座標點雖然是一個點,但是這個點不在圖形上。
2. 如果只接受一個參數,即mesh(z),那麼z必須是一個二維的矩陣,那麼在這種情況下,這個矩陣如何映射到圖上的點呢?從直覺上我們能感覺x,y的座標可能會跟矩陣的行方向索引以及列方向所以相關,最自然的感覺是對於Z矩陣中的某一個元素Z(i,j),Z(i,j)的值作爲z軸方向的分量,行方向上的索引i作爲x軸方向上的分量,列方向上的索引j作爲y軸方向上的分量,即(i,j,Z(i,j))是圖形上的一個點。實際情況是,z(i,j)作爲z軸方向上的分量沒問題,而x,y其實正好和想象中的相反,我們設Z是一個m x n 的矩陣,首先X方向的範圍是 1-n,Y方向的範圍是1-m ,而且對於矩陣Z中的任意一個元素z(i,j),行方向的索引i是座標點在y軸方向上的分量,而列方向上的索引j是x軸方向上的索引,即(j,i,Z(i,j))是圖形上的一個點。這種感覺用這張圖表示比較形象。
我們可以用這麼一個矩陣做一個測試
zz =
8 8 9
8 8 9
7 7 7
7 7 6
利用mesh(ZZ)得到的爲
3. 如果x是一個向量,y也是一個向量,z是一個矩陣,那麼這兩個向量和一個矩陣通過何種方式組合成一個圖像上的點呢,如果理解了上一點,也就是隻接受一個矩陣作爲參數的情況,那麼這中情況也比較好理解。在上一種情況中,咱們直接說結論吧,對於矩陣中的任意一個點Z(i,j)來說,它對應的座標爲(j , i , Z( i , j )),也就是拿矩陣的列方向索引爲x座標軸的分量,拿矩陣的行方向索引作爲y座標軸的分量。但是如果這裏增加了X和Y向量的話,矩陣的行列方向的索引就不能直接作爲座標的分量了,而應該作爲X向量和Y向量的索引,根據這個索引得到向量中的某一個值,這個值作爲座標裏的一個分量。也就是多了一層映射。具體來說就是Z(i,j),用列方向索引j,在X向量找第j個分量(即X(j))作爲座標x軸方向上的分量;用行方向上的索引i在Y向量找第i個分量(即Y(i))作爲座標在y軸方向的的分量。總結一下,如果給了三個參數X向量,Y向量,Z矩陣,那麼對於矩陣中的任何一個點Z(i,j),( X(j) , Y(i) , Z( i , j ) )是圖形上的一個點。注意正是由於這種關係,那麼X向量的長度要和Z的列的數量一樣,Y向量的長度要和Z的行的數量一樣。
這個 x,y,z上存在這樣的映射關係,length(x)=n,length(y)=m,z是一個m x n的二維矩陣(即 [m,n]=size(z)),那麼(x[j],y[i],z[i,j])構成的點在圖形上
mesh是以網格的方式來作圖,surf就在網格上鋪了一層表面。
瞭解了三維作圖的方法,等高線就容易理解了,等高線的圖形你可以理解爲做出三維的圖形之後,系統自動的給你截取等高線,所以contour和mesh使用參數的方法一樣。
爲什麼這裏用到contour函數,這是因爲,對於多特徵(超過2個)的分類問題,決策邊界不再是一個直線而是一個立體,或者超維的立體,這個時候我們想在二維的平面上表現出決策邊界只能在Z=0所在的平面上截出這個超維圖像的邊界。
1.2.3.1.2 meshgrid函數的使用方法
在實際的繪圖過程中,我們一般按照什麼步驟去繪圖呢,類似於二維的圖像繪製,最方便的方法是構造X矩陣,Y矩陣,之後將X、Y帶入到Z = f(X,Y)的式子中去得到Z矩陣(注意這個式子一定要是向量兼容的形式)。之後把X,Y,Z帶入到mesh(X,Y,Z)就可以了。
而構造X,Y矩陣最方便的方法是用meshgrid函數。meshgrid,有兩種用法,第一種是[X,Y] = meshgrid(gv),第二種是[X,Y] = meshgrid(xgv,ygv)。
1. 先說第一種,[X,Y] = meshgrid(gv),它的作用是
* 把gv這個向量按行擴展成一個length(gv) x length(gv)的矩陣之後之後賦值給X
* 把X這個矩陣轉置賦值給Y。
2. 再說第二種,[X,Y] = meshgrid(xgv,ygv),它的構造的方法是:
* 設 xgv和ygv都是行向量,且m = length(xgv),n = length(xgv)
* 生成的矩陣無論是X還是Y,都是 n x m型的矩陣
* if m>n : a 按行擴展到n行構成一個矩陣賦給X,b先轉置成列向量按列擴展到m列構成一個矩陣賦值給Y
* if m
1.2.3.1.3 如果不用meshgrid函數,也就是採用mesh(xgv,ygv,ZZ)的方法
在這兩次作業中的代碼中(ex1.m以及plotDecisionBoundary.m),沒有采用meshgrid的方法,而是採用這樣的方法:
1. 分別生成兩個X,Y向量,之後再生成一個length(X) x length(Y)的全零矩陣Z
2. 填充Z矩陣,填充的方法是:從順序上是Z的第一行開始從左到右一行一行的填充,填充的內容對於Z(i,j)來說是用x(i),y(j)帶入Z(x,y)來得到。用代碼上來表示就是
for i = 1 : length(X)
for j = 1 : length(Y)
Z(i,j) = f(X(i) , Y(j))
3. 將Z矩陣轉置(這步還是挺聰明的)
4. 講X,Y,Z代入mesh(X,Y,Z)
第3步還是相當的聰明的
1.2.3.2 mapFeature.m
雖然繪圖裏用到這個函數,但是這個步驟還沒用到,正則化部分裏會用到,所以在下面展開說
1.2.4 Evaluating logistic regression
這部分的主要任務是完成predict.m這個程序,這個程序沒有什麼太多難的。
主要要注意的還是find的用法,比如調用sigmoid函數的結果向量爲S,那麼在S中找到值大於等於0.5的那些分量所在的位置,把它記錄下來並且保存在一個向量中,pos = find(S>=0.5)
之後將P這個零向量中相應的位置賦值爲1 p(pos) =1。我一開始驚訝於,原來還可以這麼用,其實pos = find(S>=0.5),p(pos) =1 也是向量的運算,S>=0.5其實是對S中的每一個分量都執行這個操作。p(pos) = 1可以看成pos中的每一個分量i取出來帶到p向量裏面p(i) =1
Regularized logistic regression
2.1 Visualizing the data
這個沒有要實現的,直接執行就行
2.2 Feature mapping
- 爲什麼要引入featuremapping這個函數,主要目的是由於決策邊界不能由直線來表示,於是我們可以通過在現有的特徵基礎上進行組合的方法生成一些新的特徵的方法來使我們的擬合曲線變成比較複雜的形態。具體來說就是兩個特徵x1,x2,我們可以通過對其多次的重複乘得到一個新的feature。比如我要構造一個6次方的特徵,可以有
x16,x15x2,x14x22,x13x23,x12x24,x1x25,x26 這7個組合,其實x16 可以看成6個x1 相乘,x15x2 可以看成5個x1 和一個x2 相乘,到底有幾種組合,這是一個排列組合的問題,這個不太擅長就先跳過去啦,有誰比較擅長可以教教我。如果從0次開始一直到6次的所有組合都加起來一共有28個。也就是如果我們有兩個features,如果我們要映射到6次的時候,一共會構造出28個features。這個就是mapfeature的思想。 再說一下代碼中技巧
在代碼中有一個end+1的用法,如果我們對一個矩陣A進行操作的時候,end代表A最後一行的索引數。
可以參考這個文檔由於mapfeature構造了28個特徵,那麼
theta 也得相應的增加,也得有28個。
2.3 Cost function and gradient
總的來說如果是搞定了costFunction.m的內容的這個並不是很難,但是我也踏了一些坑,我先說一下自己的思路:
1. 計算cost
目標是寫出公式
在計算
t = theta(2:size(theta,1));
其實有更加優雅的方法
t = theta(2:end)
於是後面的部分用代碼表示就是:
lambda / ( 2 * m ) * sum(t .* t)
其實還有更優雅的表達方法
lambda / ( 2 * m ) * t' * t
這裏我還踩了一個坑,因爲
ps:pow2()不是計算平方的公式是計算以2爲底的指數的公式
2. 計算偏導數
這塊我就是按照題目裏的提示分成兩部分做的,先計算
grad = [g1 ; g2]
注意因爲grad是列向量,所以g1和g2中間用得是分號。
3. 更加優雅的思路
這是我見過最聰明的方法了。因爲其實在正則化的過程中,其實
那麼偏導數這部分計算公式都可以不變
思路是這樣的
注意觀察這個公式,這個公式可以分成兩個部分,前一部分和普通的代價函數計算一樣,後一部分是參與正則化的參數。那麼我們可以建立兩個參數向量第一個參數向量包含從
本質上,我們梯度下降的核心公式是這個:
其中,
我們依然用一個小的數來具體化,設n=2,則參數有三個需要確定
而
grad = ( X' * (sigmoid(X*theta) - y ) )/ m + lambda/m * theta_1 ;
我之前的思路就相當於把這個公式:
分開計算,在計算第一個公式的時候相當於用到了每個樣本的第一個分量
而其實通過上面的推導步驟可以發現其實上面的條件公式也是可以整合成一個公式的,只要弄出兩個