線性回歸是機器學習的入門基礎。在機器學習中,主要分類兩類:回歸和分類。而線性回歸屬于回歸,雖然logistics回歸名字帶有回歸,其實這個模型完成的分類任務。簡單的了解回歸和分類,其實就是回歸的輸出是一個具體的數值,而回歸的輸出是一個特定的類别。
線性回歸:
- (1)
線性回歸和 logistic回歸算法原理推導
這裡的theta 是個矩陣,代表的是樣本特征的系數,X代表的是樣本的特征。
代表的是誤差項。這個誤差項獨立同分布的。服從标準正态分布。
- 獨立:這裡的樣本之間是沒有聯系的,也就是說一個樣本的資料是不會影響另一個樣本的資料的
- 同分布:因為這是按照同一套資料進行劃分的,也就是這套資料的使用場景是同一個場景。
- 高斯分布:照現實生活的情況來估計的,因為在一個現實生活中,誤差總是有正有負的,而且正負的比例是差不多的。是以服從誤差為0.方差為 的标準正态分布。
線性回歸和 logistic回歸算法原理推導
誤差為
的機率密度為
(2)
現在将(1)式帶入(2)式。得到
(3)
然後根據(3)式可以得到要求的似然函數:
(4)
這個式子的來源就是因為誤差項是獨立同分布的,是以機率密度相乘的話越大也就是代表參數選擇使得我們的預測值和真實值越接近。這個我是這麼了解的,因為對于标準正态分布,在均值處取得最大值,離均值越遠,值越小。而現實生活中那些特别離譜的值是很少存在的,是以隻要調整參數,使誤差項在均值附近就可以使
取得最大值。
因為乘法的求導是很麻煩的,是以利用對數似然函數。
(5)
展開後得到:
(6)
是以現在我們隻需要把
(最小二乘法) (7) 這個值最小就可以了。
也就是令其偏導數為0就可以了 得到:
。
這個求解算法雖然被證明也是對的,但是這完全不是機器學習的套路,機器學習的套路是用一堆現有的資料,然後告訴我怎麼樣,想讓我達到什麼樣的水準,然後我一點點的去學習。這就是用到了下面的梯度下降算法。(最小二乘法隻是個巧合,正好能用公式算出來,但是并不是所有的資料都能适用這種方法)
梯度,這裡其實就是數學上的對各個方向上的偏導數,當函數沿着梯度的方向改變變量的時候,函數的變化最快。但是按我的了解,是先有了變化最快的方向,然後人們給其起了個名字,把它叫做梯度。是以,當我們按着梯度的反方向更新參數的時候,就可以最快的達到最小值。
目标函數:
(8)
- 批量梯度下降: (9)
線性回歸和 logistic回歸算法原理推導 線性回歸和 logistic回歸算法原理推導 (10)線性回歸和 logistic回歸算法原理推導
這裡是用了所有的樣本一起進行了計算,然後為了達到最後的效果,隻需要進行疊代次數的修改。此方法計算的時間很慢,因為一次要計算所有的樣本值
- 随機梯度下降:
線性回歸和 logistic回歸算法原理推導
這裡是用了一個樣本進行參數的更新,但是這樣有個不好的效果,那就是會産生震蕩,因為資料裡總會有些噪聲。
- 小批量梯度下降:
線性回歸和 logistic回歸算法原理推導
這種辦法是每次采用一批量的資料進行資料的更新。也是使用最多的方法。
- logistic回歸
logistic是經典的二分類算法,在機器學習中通常被用來先用邏輯回歸進行分類,然後實在不行,再用複雜的分類算法進行分類。這樣比較符合人們正常的思維,舍難而取易者也。這裡的邏輯回歸雖然是二分類算法,但是也可以進行多分類,softmax函數就可以實作這樣的效果。softmax在神經網絡裡很常用。邏輯回歸的邊界是可以是非線性的,因為隻要資料是高階的,出來的邊界就可以是非線性的。
這裡要引入sigmoid函數:
(11)
代碼實作:
def sigmoid(x):
return 1/(1+np.exp(-x))
這個函數的定義域是(
),值域是 [0,1]。
在這裡我們做個映射
(12) 現線上性函數的輸出值變成了在區間0,1上的值了。
(12)
(13)
将兩者合在一起比較好些似然函數 得到:
(14)
似然函數:
(15)
對數似然:
(16)
按照機器學習的套路,是求目标函數的最小值。是以這裡改為求 -
.
這個函數就可以了解為損失函數,我們的目的就是更新這個函數,使其達到最大值。但是還是按照機器學習的套路來,加個負号求其最小值。代碼實作為:
#model :傳回預測值
def gfunc(x,t):
return sigmoid(np.dot(x,t))
print((gfunc(data_x,theta)).shape)
#cost : 根據參數計算損失
def cost(x,y):
s = np.multiply(y,np.log(x)) + np.multiply((1-y),np.log(1-x))
sum =-np.sum(s)/len(x)
return sum
下面是對其求偏導的過程:(公式實在是太難打了,就直接給個結果吧。)
是以最後的參數更新為:
(17)
代碼實作為:
def gradient(data,x,y,t):
c = (x - y).ravel()
for i in range(len(t)):
s = np.sum(np.multiply(c,data[:,i]))/len(x)
t[i] = t[i] - 0.001*s
for j in range(5000):
gradient(data_x,gfunc(data_x,theta),data_y,theta)
對于怎麼做分類,隻需要指定門檻值。因為最後輸出的值是個機率,預設的值是0.5。但是這個值是可以改變的,這時候要考慮到實際的需求,比如要考慮召回率高的時候,就可以适當的降低這個門檻值,但是此時帶來的也是将反例錯分類的代價。是以這個是要根據實際需要進行設定的。下面附錄整個Python實作邏輯回歸和梯度下降政策的選擇。(訓練資料沒有上傳)(環境:Python3.6+Spyder+Numpy+pandas+matplotlib)
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#首先進行資料的讀取
data = pd.read_csv('LogiReg_data.txt',header = None,names = ['num1','num2','class'])
#print(data) #列印資料
#首先增加偏置項
data.insert(0,'num0',1)
#拿到資料後,首先觀看一下資料的分布特征
class_1 = data[data['class'] == 1]
class_0 = data[data['class'] == 0]
print('class_1的樣本數量: %s' %(len(class_1))) #列印類别1的值
print('class_0的樣本數量: %s' %(len(class_0))) #列印類别0的值
#用圖顯示資料的分布情況
plt.figure()
plt.scatter(class_1['num1'],class_1['num2'],c = 'r',marker = 'o',label = 'class_1')
plt.scatter(class_0['num1'],class_0['num2'],c = 'g',marker = 'x',label = 'class_0')
plt.legend(loc = 'best')
plt.show()
#為了取資料友善,将其改變為ndarray格式的資料
data = np.array(data)
data_x = data[:,0:3]
data_y = data[:,3:4]
print(data_x[:5])
print(data_y[:5])
#decent : 更新參數
#accuracy : 計算精度
#定義sigmoid函數
def sigmoid(x):
return 1/(1+np.exp(-x))
theta = np.zeros([3,1])
print(theta)
#model :傳回預測值
def gfunc(x,t):
return sigmoid(np.dot(x,t))
print((gfunc(data_x,theta)).shape)
#cost : 根據參數計算損失
def cost(x,y):
s = np.multiply(y,np.log(x)) + np.multiply((1-y),np.log(1-x))
sum =-np.sum(s)/len(x)
return sum
#檢視一下沒有更新參數之前的函數損失值
print(cost(gfunc(data_x,theta),data_y))
#gradient: 計算每個參數的梯度方向
def gradient(data,x,y,t):
c = (x - y).ravel()
for i in range(len(t)):
s = np.sum(np.multiply(c,data[:,i]))/len(x)
t[i] = t[i] - 0.001*s
#for j in range(5000):
# gradient(data_x,gfunc(data_x,theta),data_y,theta)
co = np.zeros([2,1])
print(co)
index = 0
while(True):
gradient(data_x,gfunc(data_x,theta),data_y,theta)
index += 1
co[index%2,0] = cost(gfunc(data_x,theta),data_y)
if index % 2 == 0:
b = np.abs(co[0,0] - co[1,0])
if b<= 1e-6:
break
print(index)
print(co)
print(cost(gfunc(data_x,theta),data_y))
實驗結果就是當疊代執行109902時,損失值由之前的0.69降到了0.37。
真正實踐的時候,是不需要自己寫這些函數的,寫這些函數的目的主要是為了幫助了解内部的實作的原理。在真正使用的時候,我們所關注的是資料的處理。因為現實中的資料不是拿來就可以用的,如果不對資料進行人工處理,得到的結果肯定會不如意的,下面就為大家展示一下如果樣本類别資料不一緻的時候怎麼處理,以及處理後的對比效果。
#對于這個程式就是為了試驗對于樣本不統一的時候選擇不同的采樣方法對logistic回歸結果的影響。
#1:下采樣
#2:過采樣
本程式我選了下采樣,也就是使類别多的資料向類别少的資料方向進行靠攏
# -*- coding: utf-8 -*-
"""
Created on Fri Aug 10 15:39:32 2018
@author: Fighting_Hou
"""
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split,KFold
from sklearn.preprocessing import StandardScaler
import warnings
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import recall_score,accuracy_score
warnings.filterwarnings(action = 'ignore')
data = pd.read_csv('creditcard.csv',header = 0)
#過程為:
#首先檢視不同類别的資料個數
#然後選擇不同的政策對資料進行處理
#最後順便試驗了一下懲罰因子的影響
#将資料分為特征和标記資料
X = data.ix[:,data.columns != 'Class'] #這個取值的方法還是不錯的,想取哪列取哪列。
y = data.ix[:,data.columns == 'Class']
print(y.shape)
#對Amount資料進行均一化處理
scaler = StandardScaler().fit(X.loc[:,'Amount'].reshape(-1,1)) #對于歸一化 首先是fit一個模型
X['Amount_new'] = scaler.transform(X.loc[:,'Amount'].reshape(-1,1)) #然後調用模型的 fit_transform
X = X.drop(['Time','Amount'],axis = 1) #去除dataframe裡不想要的列 傳入list數組
#print(X.head())
#統計下不同類别的數量
positive_data = data[data.Class == 1] #這裡就是用到了 .列名。 很進階,竟然還可以用.屬性
negative_data = data[data.Class == 0]
print('正例的數量為: %s' % (len(positive_data)))
print('負例的數量為: %s' % (len(negative_data)))
#首先采用下采樣進行模型的訓練
positive_index =np.array( positive_data.index) #有必要 array一下 因為之後還要取值用
#print(positive_index)
negative_index = np.array(np.random.choice(negative_data.index,size = len(positive_data),replace = False))
print(len(negative_index))
connect = np.concatenate((positive_index,negative_index))
data_new = data.iloc[connect,:] #以後知道index後,就是用iloc這個函數取值了
print(data_new.shape)
X1 = data_new.ix[:,data_new.columns != 'Class']
y1 = data_new.ix[:,data_new.columns == 'Class']
print(y1.shape)
#交叉驗證
#首先将資料分為訓練集和測試集
x_train,x_test,y_train,y_test = train_test_split(X1,y1,test_size = 0.3,random_state = 1)
print(len(x_train))
print(len(x_test))
param = [0.01,0.1,1,10,100]
flod = KFold(n_splits=5,shuffle = False) #k折交叉驗證
for para in range(len(param)):
print('--------')
print('懲罰因子為: %s ' % (param[para]))
cost = []
for index_x,index_y in flod.split(x_train):
model = LogisticRegression(C = param[para],penalty = 'l1')
model.fit(x_train.iloc[index_x,:],y_train.iloc[index_x,:].values.ravel())
y_hat = model.predict(x_train.iloc[index_y,:])
cost.append(recall_score(y_hat,y_train.iloc[index_y,:].values))
print('inter %s的召回率為:%s ' % (para,recall_score(y_hat,y_train.iloc[index_y,:])))
print('平均 : %s' % (np.mean(cost)))
#### 展示一下不處理資料時的召回率 證明這個召回率是明顯低于 處理後的情況的
x_train,x_test,y_train,y_test = train_test_split(X,y,test_size = 0.3,random_state = 1)
model = LogisticRegression(C = 0.01,penalty = 'l1')
model.fit(x_train, y_train)
y_hat = model .predict(x_test)
print(recall_score(y_hat,y_test))
最後的輸出結果是: 使用處理後的資料的召回率(衡量算法好壞的一種方法)為97%
沒有使用資料處理的召回率為:85% 而且識别的錯誤的資料很多。