Logistic迴歸之梯度上升優化演算法(四)
Logistic迴歸之梯度上升優化演算法(四)
從疝氣病症狀預測病馬的死亡率
1、實戰背景
我們使用Logistic迴歸來預測患疝氣病的馬的存活問題。原始資料集點選這裡下載。資料中一個包含了368個樣本和28個特徵。這種病不一定源自馬的腸胃問題,其他問題也可能引發疝氣病。該資料集中包含了醫院檢測疝氣病的一些指標,有的指標比較主觀,有的指標難以測量。例如馬的疼痛級別。另外需要說明的是,除了部分指標主觀和難以測量外,該資料集有30%的值是缺失的。所以我們得預先對資料集進行處理,再利用Logistic迴歸和隨機梯度上升演算法來預測病馬的死亡率。
2、準備資料
假設有100個樣本和20個特徵,這些資料都是機器手機回來的。若機器上的某個感測器損壞導致一個特徵無效時該怎麼辦?此時是否要扔掉整個資料?這種情況下,另外19個特徵怎麼辦?是否還可用?答案是肯定的。因為有的資料相當昂貴,扔掉和重新獲取都是不可取的,下面給出了一些處理方法。
- 使用可用特徵的均值來填補缺失值
- 使用特殊值來填補缺失值,如-1
- 忽略有缺失值的樣本
- 使用相似樣本的均值填補缺失值
- 使用另外的機器學習演算法預測缺失值
現在我們要進行資料預處理。有兩件事,一、所有的缺失值必須用一個實數值;唉替換,因為我們使用的Numpy資料型別不允許包含缺失值。這裡選擇實數0來替換所有缺失值。這樣做的好處在於更新時不會影響迴歸係數的值,如果選擇實數0來替換缺失值,那麼dataMatrix的某特徵對應值為0,那麼該特徵的係數將不做更新。所有以0代替缺失值在Logistic迴歸中可以滿足這個要求。二、如果在測試資料集中發現一條資料的類別標籤已經缺失,那麼我們的簡單做法是將該條資料丟棄,這是因為類別標籤與特徵不同,很難確定採用某個合適的值來替換。採用Logistic迴歸進行分析時這類做法是合理的。現在我沒有一個乾淨可用的資料集和一個不錯的優化演算法,將二者結合,預測馬的生死問題。
乾淨資料集下載地址:https://github.com/Jack-Cherish/Machine-Learning/tree/master/Logistic
3、使用Python構建Logistic迴歸分類器
使用訓練集的每個特徵用於最優化演算法中得到迴歸係數。把測試集的特徵乘以迴歸係數並求和放入Sigmoid函式中。如果返回值大於0.5,預測標籤為1,否則為0.
import numpy as np import random ''' 函式說明:sigmoid函式 Parameters: inX - 資料 Retruns: sigmoid函式 ''' def sigmoid(inX): return (1.0/(1+np.exp(-inX))) ''' 函式說明:改進的隨機梯度上升演算法 Parameters: dataMatrix - 資料陣列 classLabels - 資料標籤 numIter - 迭代次數 Returns: weights - 求得的迴歸係數陣列(最優引數) ''' def stocGradAscent1(dataMatrix,classLabels,numIter=150): m,n = np.shape(dataMatrix)#返回dataMatrix大小 weights = np.ones(n)#初始化迴歸係數 for j in range(numIter): dataIndex = list(range(m)) for i in range(m): alpha = 4/(1.0+j+i)+0.01 #降低alpha的大小,每次減小1/(j+i) randIndex = int(random.uniform(0,len(dataIndex)))#隨機選取樣本 h = sigmoid(sum(dataMatrix[randIndex] * weights)) error = classLabels[randIndex] - h weights = weights + alpha * error* dataMatrix[randIndex] del(dataIndex[randIndex]) print(weights) return weights ''' 函式說明:使用Python寫得Logistic分類器做預測 Parameters: None Returns: None ''' def colicTest(): frTrain = open('horseColicTraining.txt')#開啟訓練集 frTest = open('horseColicTest.txt')#開啟測試集 trainingSet = [] trainingLabels = [] for line in frTrain.readlines(): currLine = line.strip().split('\t')#strip()為空是,split以'\t'分割字串 # print(currLine) lineArr = [] for i in range(len(currLine) - 1): lineArr.append(float(currLine[i])) trainingSet.append(lineArr)#處理成一個二維列表 # print(trainingSet) trainingLabels.append(float(currLine[-1])) #存放標籤列表 trainweights = stocGradAscent1(np.array(trainingSet),trainingLabels,500)#使用改進的隨機梯度上升演算法 errorCount = 0 numTestVec = 0.0 for line in frTest.readlines(): numTestVec += 1.0 currLine = line.strip().split('\t') lineArr = [] for i in range(len(currLine) - 1): lineArr.append(float(currLine[i])) if int(classifyVector(np.array(lineArr),trainweights)) != int(currLine[-1]): errorCount +=1 errorRate = (float(errorCount)/numTestVec) * 100 print('測試集錯誤率為: %.2f%%' %errorRate) ''' 函式說明:分類函式 Parameters: inX - 特徵向量 weights - 迴歸係數 Returns: 分類結果 ''' def classifyVector(inX , weights): prob = sigmoid(sum(inX * weights)) if prob > 0.5: return 1.0 else: return 0.0 if __name__ == '__main__': np.set_printoptions(suppress=True) # 關閉科學技術法 colicTest()
執行結果如下:
結果中有個警告,意思是計算的資料結果益處。這個警告對我們的實驗雖然沒什麼影響,如果要改的話可以用如下程式碼:
def sigmoid(inX):
if inX >= 0:
return 1.0 / (1 + np.exp(-inX))
else:
return np.exp(inX) / (1 + np.exp(inX))
# return (1.0/(1+np.exp(-inX)))
程式碼出處:https://blog.csdn.net/cckchina/article/details/79915181。文中也沒有說明原因,如果有知道為什麼的還請在評論中說一下,謝謝。迴歸正題,我們的演算法錯誤率比較高。主要原因是我們的資料集本身就小,而且有很多的資料缺失。因此在面對資料量小的情況下我們可以試試梯度上升演算法。
import numpy as np
import random
'''
函式說明:梯度上升法
Parameters:
dataMatIn - 資料集
classLabels - 資料標籤
Returns:
weights.getA() - 求得的權重陣列
'''
def gradAscent(dataMatIn, classLabels):
dataMatrix = np.mat(dataMatIn) # 轉換成numpy的mat
labelMat = np.mat(classLabels).transpose() # 轉換成numpy的mat,並進行轉置
m, n = np.shape(dataMatrix) # 返回dataMatrix的大小。m為行數,n為列數。
alpha = 0.01 # 移動步長,也就是學習速率,控制更新的幅度。
maxCycles = 500 # 最大迭代次數
weights = np.ones((n, 1))
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) # 梯度上升向量化公式
error = labelMat - h
weights = weights + alpha * dataMatrix.transpose() * error
return weights.getA() # 將矩陣轉換為陣列,並返回
'''
函式說明:sigmoid函式
Parameters:
inX - 資料
Retruns:
sigmoid函式
'''
def sigmoid(inX):
# if inX >= 0:
# return 1.0 / (1 + np.exp(-inX))
# else:
# return np.exp(inX) / (1 + np.exp(inX))
return 1.0/(1+np.exp(-inX))
'''
函式說明:改進的隨機梯度上升演算法
Parameters:
dataMatrix - 資料陣列
classLabels - 資料標籤
numIter - 迭代次數
Returns:
weights - 求得的迴歸係數陣列(最優引數)
'''
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
m,n = np.shape(dataMatrix)#返回dataMatrix大小
weights = np.ones(n)#初始化迴歸係數
for j in range(numIter):
dataIndex = list(range(m))
for i in range(m):
alpha = 4/(1.0+j+i)+0.01 #降低alpha的大小,每次減小1/(j+i)
randIndex = int(random.uniform(0,len(dataIndex)))#隨機選取樣本
h = sigmoid(sum(dataMatrix[randIndex] * weights))
error = classLabels[randIndex] - h
weights = weights + alpha * error* dataMatrix[randIndex]
del(dataIndex[randIndex])
# print(weights)
return weights
'''
函式說明:使用Python寫得Logistic分類器做預測
Parameters:
None
Returns:
None
'''
def colicTest():
frTrain = open('horseColicTraining.txt')#開啟訓練集
frTest = open('horseColicTest.txt')#開啟測試集
trainingSet = []
trainingLabels = []
for line in frTrain.readlines():
currLine = line.strip().split('\t')#strip()為空是,split以'\t'分割字串
# print(currLine)
lineArr = []
for i in range(len(currLine) - 1):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)#處理成一個二維列表
# print(trainingSet)
trainingLabels.append(float(currLine[-1])) #存放標籤列表
trainweights = gradAscent(np.array(trainingSet),trainingLabels)#使用改進的隨機梯度上升演算法
errorCount = 0
numTestVec = 0.0
for line in frTest.readlines():
numTestVec += 1.0
currLine = line.strip().split('\t')
lineArr = []
for i in range(len(currLine) - 1):
lineArr.append(float(currLine[i]))
if int(classifyVector(np.array(lineArr),trainweights[:,0])) != int(currLine[-1]):
errorCount +=1
errorRate = (float(errorCount)/numTestVec) * 100
print('測試集錯誤率為: %.2f%%' %errorRate)
'''
函式說明:分類函式
Parameters:
inX - 特徵向量
weights - 迴歸係數
Returns:
分類結果
'''
def classifyVector(inX , weights):
prob = sigmoid(sum(inX * weights))
if prob > 0.5:
return 1.0
else:
return 0.0
if __name__ == '__main__':
np.set_printoptions(suppress=True) # 關閉科學技術法
colicTest()
執行結果如下:
錯誤率有所降低。
- 資料集較大時,使用隨機梯度上升發
- 資料集較小是,使用梯度上升演算法
總結
Logistic迴歸的有點在於容易實現,好理解,計算代價不高,速度很快,儲存資源低。缺點在於容易欠擬合,分類經度可能不高。
參考文獻:
- 《機器學習實戰》第五章內容
- Jack Cui部落格:https://cuijiahua.com/blog/2017/11/ml_7_logistic_2.html