---
# System prepended metadata

title: 監督式學習實作

---

>[name=澤]本來想做個gpt的二元分類問題，但我找不到現成的資料，我們就拿horse colic這個資料集實作吧@@


那確定好我們的任務後，首先幫大家回顧一下機器學習的步驟:
![範例圖](https://hackmd.io/_uploads/HyBhW_cQC.jpg)

### step1:資料收集&介紹
[資料集連結](https://archive.ics.uci.edu/dataset/47/horse+colic)
該資料集具有28個屬性，並且有30%的缺失值，共有368筆資料，其中包含馬的年齡、呼吸頻率、體溫等，如下圖，那我們就來預測這個馬是否還存活吧(即屬性23(outcome))
![image](https://hackmd.io/_uploads/rJXF9NcmR.png =60%x)


### step2: 資料清理
既然得知我們想要預測的屬性，首先我們看一下attruible，哪些資料是需要的呢?那些屬性是和馬的生死無關的?
![image](https://hackmd.io/_uploads/H1rSBJimC.png =50%x)

觀察後我們發現屬性 3(hospital_number)、 25(lesion_site) 、26(lesion_type)、27(lesion_subtype)、28(cp_data)是不相關的，因此先將這些屬性刪除吧!
```pyhton=
# load train/test data
data = np.genfromtxt(fname='/content/drive/MyDrive/horse-colic/horse-colic.data',delimiter=' ',dtype=float)
data_test = np.genfromtxt(fname='/content/drive/MyDrive/horse-colic/horse-colic.test',delimiter=' ',dtype=float)


# data pre-processing

#change nd.array to dataframe,and give it index

train = pd.DataFrame(data,columns=range(1,29),index=range(1,301))
test = pd.DataFrame(data_test,columns=range(1,29),index=range(1,69))

# drop the Attributes 3 25 26 27 28,(index is begin 1)
train = train.drop(columns=[3, 25, 26, 27, 28])
test = test.drop(columns=[3, 25, 26, 27, 28])

```
首先我將資料從nd.array轉成資料表，並且給予其標籤，阿記得python的range並不包含其最後一個數字(stop)，所以我才寫,columns=range(1,29),index=range(1,301)
>[name=澤]其實我有點懶得切測試訓練資料，所以直接套現成的，但如果想切很推薦使用sklearn提供的train_test_split，非常好用!

[切割套件連結請點我](https://scikit-learn.org/0.19/modules/generated/sklearn.model_selection.train_test_split.html)

不過接下來我們發現，outcome這個屬性有三種，1,2,3，因為他將安樂死獨立出來，因此我們須將3(安樂死)替換成2(死亡)，以下是程式碼:
``` python=
# change label euthanized with died
for index, row in train.iterrows():
    if row[23] == 3:
        train.loc[index, 23] = 2
for index, row in test.iterrows():
  if row[23] == 3:
      train.loc[index, 23] = 2
```
>[name=澤]iterrows是迭代器，他會遍歷每個元素

你以為到這裡資料清洗就結束了嗎?錯!!!還有缺失值要補上呢!不過也有一些feature selection的套件'可以使用，可以自動選擇那些特徵是不用的
[feature selection](https://scikit-learn.org/stable/modules/feature_selection.html)

那該如何補上缺失值呢?，這裡我們也使用sklearn套件的[KNNImputer](https://scikit-learn.org/stable/modules/generated/sklearn.impute.KNNImputer.html)
```python=
# NA fill in,because model can not access NA
imputer = KNNImputer(n_neighbors = 3, metric='nan_euclidean')
train = imputer.fit_transform(train)
test = imputer.transform(test)

# make the label(or target),so use y
y_train = train[:,22]
y_test = test[:,22]
print('after change label euthanized to died')
print(y_train)

```
![image](https://hackmd.io/_uploads/HyTYkeo7R.png)


>nan_euclidean你可以想成找缺失值附近的鄰居，就是歐基里德距離拉，y_train就是預測目標拉


### step3: 選擇模型
我們選擇四種模型來挑戰這個任務，首先登場的是我們一號選手KNN

#### KNN

該模型又稱為最近鄰居法，簡單來說有以下步驟
* 1.決定k值(鄰居數量)
* 2.決定距離計算方法(歐基里德、明可夫斯基、曼哈頓距離等)
* 3.選擇你想加入哪一群

聽起來很簡單吧，我覺得他是相對直觀的模型

一樣連結在這，這裡先不細講參數部分[sklearn-KNN](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html)
```python=
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score


model = KNeighborsClassifier(n_neighbors=3, weights='distance', algorithm='auto', leaf_size=20, p=2)

model.fit(train,y_train)

pred = model.predict(test)

acc = accuracy_score(y_test, pred)
print(f"Accuracy: {acc}")
# Accuracy: 0.7352941176470589
```



#### 邏輯迴歸（Logistic Regression）
這個我們介紹過拉，就不贅述了，簡單來說他會記錄每個觀測值的條件機率，並將其相加以產生預測機率。對於二元分類，小於 0.5 的機率將預測 0，而大於 0 的機率將預測 1，我們之前有介紹過sigmod函數，就是用它來做轉換。

![image](https://hackmd.io/_uploads/Hkzqqfo7C.png =50%x)
[圖片來源](https://ithelp.ithome.com.tw/m/articles/10269006)

[點我看Logistic Regression](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html)
```python=
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

model = LogisticRegression().fit(train, y_train)
pred = model.predict(test)

acc = accuracy_score(y_test, pred)

print(f"Accuracy: {acc}")
#Accuracy: 1.0
```

#### SVM(支持向量機)
它的基礎概念滿簡單的，找到一條決策邊界，讓不同類別被完美分開，你也可以透過核技巧去將資料投射到更高維度的空間，透過多維度的投影，將原本在二維空間中不可分的點到了高維空間就可以分開了。
![image](https://hackmd.io/_uploads/rk6j1fiXR.png =50%x)
>[name=澤]其實SVM可以玩的東西滿多的，比如用多項式核和RBF核等，那連結我一樣放這大家可以看看~

[SVM](https://scikit-learn.org/stable/modules/svm.html)
這邊我們就寫個簡單的模型玩玩就好啦~
```python=
from sklearn import svm
from sklearn.metrics import accuracy_score

# Radial Basis Function 高斯轉換
model = svm.SVC(kernel="rbf", gamma="auto",decision_function_shape='ovo')
model.fit(train, y_train)
pred = model.predict(test)

acc = accuracy_score(y_test, pred)

print(f"Accuracy: {acc}")
#Accuracy: 0.6911764705882353
```



#### random forest(隨機森林)
許多不同的決策樹所組成的森林，這種方法又稱為Ensemble Method(集成方法)，透過多棵決策樹投票表決最終的類別，而隨機森林的隨機是先隨機抽n筆資料，這些資料室可以被隨機抽取的，就是 Bootstrap，而在 sklearn 中，最多隨機選取 log2N 個特徵。

![image](https://hackmd.io/_uploads/SJSx9fi7A.png =50%x)

[圖片來源](https://ithelp.ithome.com.tw/m/articles/10272586)

[點我看random forest](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html)
```python=
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

model = RandomForestClassifier(n_estimators=100, criterion = 'gini',max_depth=10)

model.fit(train, y_train)
pred = model.predict(test)

acc = accuracy_score(y_test, pred)

print(f"Accuracy: {acc}")

#Accuracy: 1.0
```
### step4: 調整參數
好啦，接下來講一下調整參數，剛剛看了那麼多模型，但都不確定裡面的參數定義是啥對吧，那有沒有一種方式可以幫我們直接找出最好的參數勒?

>[name=澤]接下來我們以svm模型的參數使用GridSearchCV和Pipeline找出最好參數



[pipline](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html)

[GridSearchCV](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html)

首先我們先觀察下有什麼參數
![image](https://hackmd.io/_uploads/rk4MmZaX0.png)

你可以看到大致上有這些：
 
C：float，預設1.0，錯誤項的懲罰係數，svm中正規化參數，C值越大，分類錯誤懲罰越嚴厲，但也可能導致overfitting，訓練資料上表現好，但測試集不佳，反之C越小，模型能容忍一些錯誤，泛化能力可能更強，但也可能導致underfitting。

kernel:有linear(線性核參數), poly(多項式核參數), rbf(高斯核/預設), sigmoid(sigmod核函數),precomputed(核矩陣)，當然你也可以給自定義的核函數計算核矩陣。

degree:int，預設為3，不可為負數，該參數僅能用於poly，代表其的階數。

gamma:有scale(預設)、auto或浮點數，只對rbf(高斯核/預設),sigmoid(sigmod核函數),precomputed(核矩陣)有用，主要是對低維度樣本進行高度映射(決定數據到高維空間的曲率)，較小的gamms可使決策邊界更加平滑，有助於減少模型複雜度，避免overfitting，但可能數據的部分特徵無法捕捉，請看下圖:
![image](https://hackmd.io/_uploads/Hku3Ow1V0.png =80%x)
[圖片連結](https://blog.csdn.net/bigFatCat_Tom/article/details/93790753)

coef0:預設 0.0，核函數中的常數項，只對poly、sigmoid有用。
![image](https://hackmd.io/_uploads/ryTxsw1EA.png)

shrinking:bool，預設true，是否採用啟發式收縮。

probability:bool，預設true，是否使用概率估計，不過一定要在fit()前使用。

tol:float, default=1e-3，停止訓練誤差精度。

cache_size:訓練所需內存，預設200MB。

class_weight:balanced、default=None，給每個類別設置不同的懲罰參數C，沒有給的話則給所以類別1。

verbose:bool, default=False，是否啟用詳細輸出，不過啟用該設置可能導致poly沒法正常工作。

max_iter:int，預設-1，最大迭代次數，-1代表不限制。

>還有一些但我就不介紹完，自己去看吧~

好的，那我現在想搭配GridSearchCV和pipline找出最佳參數，該怎麼做呢?

```python=
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
  

# 設置 Pipeline
pipe = Pipeline([('scaler', StandardScaler()), ('svc', SVC())])
# scaler可以參考https://scikit-learn.org/stable/modules/preprocessing.html StandardScaler是平均&變異數標準化
# 設置參數
param_grid = {
    'svc__C': [0.1, 1, 10, 100],
    'svc__kernel': ['linear', 'poly', 'rbf', 'sigmoid'],
    'svc__gamma': ['scale', 'auto'],
    'svc__degree': [2, 3, 4],  # 只在 kernel 為 'poly' 時有效
    'svc__coef0': [0.0, 0.1, 0.5]  # 只在 kernel 為 'poly' 或 'sigmoid' 時有效
}

# 設置 GridSearchCV cv是交叉驗證次數，scoring是評估標準，n_jobs示使用全部可用的CPU核心，verbose設置日誌信息
grid_search = GridSearchCV(pipe, param_grid, cv=5, scoring='accuracy', n_jobs=-1, verbose=2)

# 進行參數搜尋
grid_search.fit(train, y_train)

# 最佳參數和準確率
best_model = grid_search.best_estimator_
pred = best_model.predict(test)
acc = accuracy_score(y_test, pred)

print(f"Best Parameters: {grid_search.best_params_}")
print(f"Accuracy: {acc}")

```
結果如下:
![image](https://hackmd.io/_uploads/Sk4bXu14C.png)

### step5: 分析、預測結果 
分析的方式很多，我們就舉AUC跟F1_score吧!
```python=

from sklearn.preprocessing import LabelEncoder

# 重新編碼標籤，本來是1 2 ，更改一下
le = LabelEncoder()
y_train_encoded = le.fit_transform(y_train)
y_test_encoded = le.transform(y_test)

# 計算 F1-score
f1 = f1_score(y_test_encoded, pred, average='weighted')


auc = roc_auc_score(y_test_encoded, best_model.decision_function(test))

# 混淆矩陣和分類報告
conf_matrix = confusion_matrix(y_test_encoded, pred)
class_report = classification_report(y_test_encoded, pred)

# 輸出結果
print(f"F1 Score: {f1}")
print(f"AUC: {auc}")

print(f"Classification Report:\n{class_report}")

# 繪製 ROC 曲線，僅當有兩個類別時
fpr, tpr, _ = roc_curve(y_test_encoded, best_model.decision_function(test))
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (area = {auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC)')
plt.legend(loc="lower right")
plt.show()


```

![image](https://hackmd.io/_uploads/BylJEtJNR.png =80%x)

想要完整的程式碼嗎?去吧，我把程式碼都放這: 
[自主學習_監督式學習範例](https://colab.research.google.com/drive/1WoWMkGJFfhcyZ1CrjL8SHI4q4hMvffJp#scrollTo=row6BWY0H5L3)


### 參考連結:
[核模型 - 支持向量機 (SVM)](https://ithelp.ithome.com.tw/m/articles/10270447)

[ML入門（十一）支援向量機](https://medium.com/chung-yi/ml%E5%85%A5%E9%96%80-%E5%8D%81%E4%B8%80-%E6%94%AF%E6%8F%B4%E5%90%91%E9%87%8F%E6%A9%9F-support-vector-machine-svm-c8c1bb1c970f)

[random forest](https://medium.com/chung-yi/ml%E5%85%A5%E9%96%80-%E5%8D%81%E4%B8%83-%E9%9A%A8%E6%A9%9F%E6%A3%AE%E6%9E%97-random-forest-6afc24871857)

[多棵決策樹更厲害：隨機森林 (Random forest)](https://ithelp.ithome.com.tw/m/articles/10272586)
[SVM參數介紹](https://blog.csdn.net/github_39261590/article/details/75009069)