機器學習(迴歸十)——階段性總結

現在對迴歸做一下階段性總結。迴歸算法,總的來說就是這些,當然還有一些變種,基本上逃不過線性迴歸和邏輯迴歸這兩種。其實迴歸家族中還有比較有名的樹迴歸,這裏就先不介紹,因爲會涉及決策樹相關的內容,所以後面講到決策樹時再做介紹。(其實內容特別簡單)

總結

先做一下總結:

  • 線性模型一般用於迴歸問題,Logistic和Softmax模型一般用於分類問題
  • 求θ的主要方式是梯度下降算法,梯度下降算法是參數優化的重要手段,主要是SGD,適用於在線學習以及跳出局部極小值
  • Logistic/Softmax迴歸是實踐中解決分類問題的最重要的方法(邏輯迴歸和softmax,是實際工作中是解決性比較強的算法,計算的就是相關的概率,其他的(決策輸出)基本沒有這倆解釋性強,比如SVM。實際工作中如果要求解釋性比較強的分類算法,就可以用這兩種或決策樹。
  • 廣義線性模型對樣本要求不必要服從正態分佈、只需要服從指數分佈簇(二項分佈、泊松分佈、伯努利分佈、指數分佈等)即可;廣義線性模型的自變量可以是連續的也可以是離散的。
  • 在此期間介紹機器學習中主要用到的一種求解方法就是梯度下降。

再串一下前面幾篇博客的內容:

首先是對線性迴歸的理解,簡單來說就是找到這樣一條直線去擬合數據,求解時,最便捷的是最小二乘,也就是平方和損失函數,求解過程,就是對θ的解析。

解析的過程發現數據不是線性的,可進行多項式擴展,然後再用線性迴歸,這樣就可比較好的擬合了。多項式擴展會存在過擬合的問題,可以採用L1和L2正則。

不管是普通的線性迴歸還是L1或L2 正則,在求解時,最常用的是梯度下降。只要是函數是可以求導的,就能用梯度下降,哪怕不是全局凸函數也沒事,可以採用SGD,來跳過局部最優,而達到全局最優。當然機器學習領域遇到的基本都是凸函數。

最後面就是邏輯迴歸和softmax,邏輯迴歸是二分類,softmax是多分類。可以說他倆都是基於迴歸的思想來解決分類的問題。

在此還舉了一些例子,進行了代碼實現,對機器學習的流程有個直觀的理解:數據的讀取、處理、分割,特徵工程,接下來是模型的構建,模型效果的驗證,如果有超參,還有超參的選定,最後還有模型的持久化。

代碼案例

不寫個代碼,總感覺缺點什麼。下面就基於信貸數據進行用戶信貸分類,使用Logistic算法和KNN算法構建模型,並比較這兩大類算法的效果。(雖然KNN還沒介紹了,不過後續博客會對KNN進行詳細的介紹,這裏只是簡單的用了一下)

數據

數據的來源: Credit Approval Data Set

數據概要:
在這裏插入圖片描述
數據的屬性

在這裏插入圖片描述

翻譯過來,大致內容:

還是算了,着實不好翻譯……

看幾條具體數據
在這裏插入圖片描述

代碼

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import warnings
# 上面是機器學習庫,numpy之類的,下面是sklearn的
import sklearn
from sklearn.linear_model import LogisticRegressionCV
from sklearn.linear_model.coordinate_descent import ConvergenceWarning
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline #管道
from sklearn.neighbors import KNeighborsClassifier

## 設置字符集,防止中文亂碼
mpl.rcParams['font.sans-serif']=[u'simHei']
mpl.rcParams['axes.unicode_minus']=False
## 攔截異常
warnings.filterwarnings(action = 'ignore', category=ConvergenceWarning)

help(pd.read_csv)  

要讀取數據,先看一下這個函數

read_csv(filepath_or_buffer, sep=’,’, delimiter=None, header=‘infer’, names=None, index_col=None, usecols=None, squeeze=False, prefix=None, mangle_dupe_cols=True, dtype=None, engine=None, converters=None, true_values=None, false_values=None, skipinitialspace=False, skiprows=None, nrows=None, na_values=None, keep_default_na=True, na_filter=True, verbose=False, skip_blank_lines=True, parse_dates=False, infer_datetime_format=False, keep_date_col=False, date_parser=None, dayfirst=False, iterator=False, chunksize=None, compression=‘infer’, thousands=None, decimal=b’.’, lineterminator=None, quotechar=’"’, quoting=0, escapechar=None, comment=None, encoding=None, dialect=None, tupleize_cols=None, error_bad_lines=True, warn_bad_lines=True, skipfooter=0, doublequote=True, delim_whitespace=False, low_memory=True, memory_map=False, float_precision=None)

  • filepath_or_buffer:文件路徑或內存中的數據
  • sep:分隔符
  • header:頭部,默認是‘infer’,表示第一條
  • names:有頭部,列名就自動給

我們可以讀數據看一下。

在這裏插入圖片描述
發現沒有列,所以header就應該‘None’

pd.read_csv("datas/crx.data", header=None)

此時有了列號:
在這裏插入圖片描述
但是發現行列都是數字,所以可以加一下默認命名:

### 加載數據並對數據進行預處理
# 1. 加載數據
path = "datas/crx.data"
names = ['A1','A2','A3','A4','A5','A6','A7','A8',
         'A9','A10','A11','A12','A13','A14','A15','A16']
df = pd.read_csv(path, header=None, names=names)
print ("數據條數:", len(df))

# 2. 異常數據過濾
df = df.replace("?", np.nan).dropna(how='any')
print ("過濾後數據條數:", len(df))

df.head(5)

數據條數: 690
過濾後數據條數: 653

在這裏插入圖片描述
接下來看一下數據類型(必須是數值形,才支持,),以及是否有空值

df.info() # 看一下各個列的字符相關信息

<class ‘pandas.core.frame.DataFrame’>
Int64Index: 653 entries, 0 to 689
Data columns (total 16 columns):
A1 653 non-null object
A2 653 non-null object
A3 653 non-null float64
A4 653 non-null object
A5 653 non-null object
A6 653 non-null object
A7 653 non-null object
A8 653 non-null float64
A9 653 non-null object
A10 653 non-null object
A11 653 non-null int64
A12 653 non-null object
A13 653 non-null object
A14 653 non-null object
A15 653 non-null int64
A16 653 non-null object
dtypes: float64(2), int64(2), object(12)
memory usage: 86.7+ KB

# TODO: 有沒有其它便捷的代碼一次性查看所有的數據類型爲object的取值信息
df.A16.value_counts()

- 357
+ 296
Name: A16, dtype: int64

正表示審批通過,負表示不通過
這裏的數據處理是自己寫的,實際是有相關的API的

# 自定義的一個啞編碼實現方式:將v變量轉換成爲一個向量/list集合的形式
def parse(v, l):
    # v是一個字符串,需要進行轉換的數據
    # l是一個類別信息,其中v是其中的一個值
    return [1 if i == v else 0 for i in l]
# 定義一個處理每條數據的函數
def parseRecord(record):
    result = []
    ## 格式化數據,將離散數據轉換爲連續數據
    a1 = record['A1']
    for i in parse(a1, ('a', 'b')):
        result.append(i)
    
    result.append(float(record['A2']))
    result.append(float(record['A3']))
    
    # 將A4的信息轉換爲啞編碼的形式; 對於DataFrame中,原來一列的數據現在需要四列來進行表示
    a4 = record['A4']
    for i in parse(a4, ('u', 'y', 'l', 't')):
        result.append(i)
    
    a5 = record['A5']
    for i in parse(a5, ('g', 'p', 'gg')):
        result.append(i)
    
    a6 = record['A6']
    for i in parse(a6, ('c', 'd', 'cc', 'i', 'j', 'k', 'm', 'r', 'q', 'w', 'x', 'e', 'aa', 'ff')):
        result.append(i)
    
    a7 = record['A7']
    for i in parse(a7, ('v', 'h', 'bb', 'j', 'n', 'z', 'dd', 'ff', 'o')):
        result.append(i)
    
    result.append(float(record['A8']))
    
    a9 = record['A9']
    for i in parse(a9, ('t', 'f')):
        result.append(i)
        
    a10 = record['A10']
    for i in parse(a10, ('t', 'f')):
        result.append(i)
    
    result.append(float(record['A11']))
    
    a12 = record['A12']
    for i in parse(a12, ('t', 'f')):
        result.append(i)
        
    a13 = record['A13']
    for i in parse(a13, ('g', 'p', 's')):
        result.append(i)
    
    result.append(float(record['A14']))
    result.append(float(record['A15']))
    
    a16 = record['A16']
    if a16 == '+':
        result.append(1)
    else:
        result.append(0)
        
    return result
# 啞編碼實驗
print(parse('v', ['v', 'y', 'l']))
print(parse('y', ['v', 'y', 'l']))
print(parse('l', ['v', 'y', 'l']))

[1, 0, 0]
[0, 1, 0]
[0, 0, 1]

### 數據特徵處理(將數據轉換爲數值類型的)
new_names =  ['A1_0', 'A1_1',
              'A2','A3',
              'A4_0','A4_1','A4_2','A4_3', # 因爲需要對A4進行啞編碼操作,需要使用四列來表示一列的值
              'A5_0', 'A5_1', 'A5_2', 
              'A6_0', 'A6_1', 'A6_2', 'A6_3', 'A6_4', 'A6_5', 'A6_6', 'A6_7', 'A6_8', 'A6_9', 'A6_10', 'A6_11', 'A6_12', 'A6_13', 
              'A7_0', 'A7_1', 'A7_2', 'A7_3', 'A7_4', 'A7_5', 'A7_6', 'A7_7', 'A7_8', 
              'A8',
              'A9_0', 'A9_1' ,
              'A10_0', 'A10_1',
              'A11',
              'A12_0', 'A12_1',
              'A13_0', 'A13_1', 'A13_2',
              'A14','A15','A16']
datas = df.apply(lambda x: pd.Series(parseRecord(x), index = new_names), axis=1)
names = new_names

## 展示一下處理後的數據
datas.head(5)

在這裏插入圖片描述

datas.info()

<class ‘pandas.core.frame.DataFrame’>
Int64Index: 653 entries, 0 to 689
Data columns (total 48 columns):
A1_0 653 non-null float64
A1_1 653 non-null float64
A2 653 non-null float64
A3 653 non-null float64
A4_0 653 non-null float64
A4_1 653 non-null float64
A4_2 653 non-null float64
A4_3 653 non-null float64
A5_0 653 non-null float64
A5_1 653 non-null float64
A5_2 653 non-null float64
A6_0 653 non-null float64
A6_1 653 non-null float64
A6_2 653 non-null float64
A6_3 653 non-null float64
A6_4 653 non-null float64
A6_5 653 non-null float64
A6_6 653 non-null float64
A6_7 653 non-null float64
A6_8 653 non-null float64
A6_9 653 non-null float64
A6_10 653 non-null float64
A6_11 653 non-null float64
A6_12 653 non-null float64
A6_13 653 non-null float64
A7_0 653 non-null float64
A7_1 653 non-null float64
A7_2 653 non-null float64
A7_3 653 non-null float64
A7_4 653 non-null float64
A7_5 653 non-null float64
A7_6 653 non-null float64
A7_7 653 non-null float64
A7_8 653 non-null float64
A8 653 non-null float64
A9_0 653 non-null float64
A9_1 653 non-null float64
A10_0 653 non-null float64
A10_1 653 non-null float64
A11 653 non-null float64
A12_0 653 non-null float64
A12_1 653 non-null float64
A13_0 653 non-null float64
A13_1 653 non-null float64
A13_2 653 non-null float64
A14 653 non-null float64
A15 653 non-null float64
A16 653 non-null float64
dtypes: float64(48)
memory usage: 250.0 KB

## 數據分割
X = datas[names[0:-1]]
Y = datas[names[-1]]

X_train,X_test,Y_train,Y_test = train_test_split(X,Y,test_size=0.1,random_state=0)

X_train.describe().T

在這裏插入圖片描述

## 數據正則化操作(歸一化)
ss = StandardScaler()
## 模型訓練一定是在訓練集合上訓練的
X_train = ss.fit_transform(X_train) ## 訓練正則化模型,並將訓練數據歸一化操作
X_test = ss.transform(X_test) ## 使用訓練好的模型對測試數據進行歸一化操作

pd.DataFrame(X_train).describe().T

在這裏插入圖片描述

## Logistic算法模型構建
# LogisticRegression中,參數說明:
# penalty => 懲罰項方式,即使用何種方式進行正則化操作(可選: l1或者l2)
# C => 懲罰項係數,即L1或者L2正則化項中給定的那個λ係數(ppt上)
# LogisticRegressionCV中,參數說明:
# LogisticRegressionCV表示LogisticRegression進行交叉驗證選擇超參數(懲罰項係數C/λ)
# Cs => 表示懲罰項係數的可選範圍
lr = LogisticRegressionCV(Cs=np.logspace(-4,1,50), fit_intercept=True, penalty='l2', solver='lbfgs', tol=0.01, multi_class='ovr')
lr.fit(X_train, Y_train)

solver:‘lbfgs’擬牛頓法
邏輯迴歸在真正的二分類中,效果還是可以的,但它不適合多分類,雖然softmax可以做,但實際應用中,對於多分類很少用softmax。但有兩點需要注意:

  • softmax和其他多分類的求解方式很不一樣。其他多分類要構建很多個模型,而softmax只構建一個。
  • softmax屬於各類別的概率都算出來,以最大的爲標準,和深度學習最一個隱層的功能非常類似。所以深度學習最後一層是softmax
# Logistic算法效果輸出
lr_r = lr.score(X_train, Y_train)
print ("Logistic算法R值(訓練集上的準確率):", lr_r)
print ("Logistic算法稀疏化特徵比率:%.2f%%" % (np.mean(lr.coef_.ravel() == 0) * 100))
print ("Logistic算法參數:",lr.coef_)
print ("Logistic算法截距:",lr.intercept_)

Logistic算法R值(訓練集上的準確率): 0.8909710391822828
Logistic算法稀疏化特徵比率:2.13%
Logistic算法參數: [[-0.00507672 0.00507672 0.06367298 0.06284643 0.03997945 -0.04935683
0.06679464 0. 0.03997945 -0.04935683 0.06679464 0.00549457
-0.02646873 0.1057865 -0.10294239 -0.02346199 -0.05981958 -0.0131902
0.01148842 0.04690591 0.03631018 0.13996153 0.03858644 -0.02422956
-0.11817039 -0.00441403 0.08130739 -0.02489682 0.03130081 0.03567533
-0.01396069 -0.00769375 -0.10417126 -0.00379776 0.15834772 0.46892613
-0.46892613 0.16546747 -0.16546747 0.19117654 -0.01273762 0.01273762
0.01240825 -0.00744574 -0.0110668 -0.08907636 0.12989149]]
Logistic算法截距: [-0.25992008]

## Logistic算法預測(預測所屬類別)
lr_y_predict = lr.predict(X_test)
lr_y_predict

array([1., 1., 1., 0., 1., 1., 0., 1., 0., 1., 1., 0., 0., 1., 1., 0., 1.,
0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 1., 1., 0., 0., 1., 0., 0.,
1., 0., 0., 0., 0., 0., 1., 1., 0., 0., 0., 0., 1., 0., 0., 1., 1.,
0., 1., 0., 0., 1., 0., 1., 1., 1., 0., 1., 0., 1., 0., 1.])

## Logistic算法獲取概率值(就是Logistic算法計算出來的結果值)
y1 = lr.predict_proba(X_test)
y1

array([[0.22855228, 0.77144772],
[0.18260146, 0.81739854],
[0.15724948, 0.84275052],
[0.8701438 , 0.1298562 ],
[0.47949865, 0.52050135],
[0.05541409, 0.94458591],
[0.75895994, 0.24104006],
[0.21888948, 0.78111052],
[0.88715054, 0.11284946],

[0.87790067, 0.12209933],
[0.44764238, 0.55235762],
[0.86480515, 0.13519485],
[0.11153426, 0.88846574],
[0.82395841, 0.17604159],
[0.44200894, 0.55799106]])

## KNN算法構建
knn = KNeighborsClassifier(n_neighbors=20, algorithm='kd_tree', weights='distance')
knn.fit(X_train, Y_train)

KNeighborsClassifier(algorithm=‘kd_tree’, leaf_size=30, metric=‘minkowski’,
metric_params=None, n_jobs=1, n_neighbors=20, p=2,
weights=‘distance’)

## KNN算法效果輸出
knn_r = knn.score(X_train, Y_train)
print("Logistic算法訓練上R值(準確率):%.2f" % knn_r)

Logistic算法訓練上R值(準確率):1.00

## KNN算法預測
knn_y_predict = knn.predict(X_test)
knn_r_test = knn.score(X_test, Y_test)
print("Logistic算法訓練上R值(測試集上準確率):%.2f" % knn_r_test)

Logistic算法訓練上R值(測試集上準確率):0.83

## 結果圖像展示
## c. 圖表展示
x_len = range(len(X_test))
plt.figure(figsize=(14,7), facecolor='w')
plt.ylim(-0.1,1.1)
plt.plot(x_len, Y_test, 'ro',markersize = 6, zorder=3, label=u'真實值')
plt.plot(x_len, lr_y_predict, 'go', markersize = 10, zorder=2, label=u'Logis算法預測值,$R^2$=%.3f' % lr.score(X_test, Y_test))
plt.plot(x_len, knn_y_predict, 'yo', markersize = 16, zorder=1, label=u'KNN算法預測值,$R^2$=%.3f' % knn.score(X_test, Y_test))
plt.legend(loc = 'center right')
plt.xlabel(u'數據編號', fontsize=18)
plt.ylabel(u'是否審批(0表示通過,1表示通過)', fontsize=18)
plt.title(u'Logistic迴歸算法和KNN算法對數據進行分類比較', fontsize=20)
plt.show()

看一下效果:
在這裏插入圖片描述

最終: Logistic迴歸算法比KNN算法的結果好一些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章