湖南大學人工智慧實驗三:分類演算法實驗
程式碼連結:人工智慧實驗
文章目錄
人工智慧實驗三:分類演算法實驗
一、實驗目的
-
鞏固4種基本的分類演算法的演算法思想:樸素貝葉斯演算法,決策樹演算法,人工神經網路,支援向量機;
-
能夠使用現有的分類器演算法程式碼進行分類操作
-
學習如何調節演算法的引數以提高分類效能;
二、實驗的硬體、軟體平臺
硬體:計算機
軟體:作業系統:WINDOWS/Linux
應用軟體:C, Java或者Matlab或python
三、實驗內容及步驟
利用現有的分類器演算法對文字資料集進行分類
實驗步驟:
-
瞭解文字資料集的情況並閱讀演算法程式碼說明文件;
-
編寫程式碼設計樸素貝葉斯演算法,決策樹演算法,人工神經網路,支援向量機等分類演算法,利用文字資料集中的訓練資料對演算法進行引數學習;
-
利用學習的分類器對測試資料集進行測試;
-
統計測試結果;
四、思考題:
- 如何在引數學習或者其他方面提高演算法的分類效能?
五、實驗報告要求
-
對各種演算法的原理進行說明;
-
對實驗過程進行描述;
-
附上完整的實驗程式碼,程式碼的詳細說明,統計實驗結果,對分類效能進行比較說明;
-
對演算法的時間空間複雜度進行比較分析。
六、實驗步驟
資料集讀取
總共給出了三個資料集:data集,predict集,test集,從名字可以看出,data集用於訓練,test集用於測試,predict集用於驗證演算法分類的效能。
所以通過pandas庫對資料集進行讀取:
data = pd.read_csv("C:/Users/a2783/Desktop/AI/Exp/Exp3/dataset.txt")
pred = pd.read_csv("C:/Users/a2783/Desktop/AI/Exp/Exp3/predict.txt")
test = pd.read_csv("C:/Users/a2783/Desktop/AI/Exp/Exp3/test.txt")
由於資料以文字形式儲存,需要轉換成對應的數值型別。這裡採用的方法是,對於每一個特徵,遍歷其所有的類別,將每一個類別賦予一個值。每個特徵值得範圍從1開始,最大即特徵對應的類別得種類數:
for i in data.columns[:-1]:
cnt = 1
feature = data[i]
feature = np.array(feature)
for j in np.unique(feature):
data.loc[data[i] == j, i] = cnt
pred.loc[pred[i] == j, i] = cnt
test.loc[test[i] == j, i] = cnt
cnt += 1
由於之後做分類時,要求標籤從0開始,所以對於Class_Values
的值從0開始:
cnt = 0
for j in np.unique(data[data.columns[-1]]):
data.loc[data[data.columns[-1]] == j, data.columns[-1]] = cnt
pred.loc[pred[data.columns[-1]] == j, pred.columns[-1]] = cnt
test.loc[test[data.columns[-1]] == j, test.columns[-1]] = cnt
cnt += 1
至此,資料處理完畢。
樸素貝葉斯
基礎理論
貝葉斯公式:
P
(
A
∣
B
)
=
P
(
A
)
P
(
B
∣
A
)
P
(
B
)
P(A|B) = \frac{P(A)P(B|A)}{P(B)}
P(A∣B)=P(B)P(A)P(B∣A)
其中,
P
(
B
)
P(B)
P(B)為歸一化因子,可以忽略,
P
(
A
)
P(A)
P(A)是先驗概率,
P
(
A
∣
B
)
P(A|B)
P(A∣B)是後驗概率,
P
(
B
∣
A
)
P(B|A)
P(B∣A)即似然。
但是根據貝葉斯公式,若要估計後驗概率,由於類條件概率為資料的所有屬性上的聯合概率,所有在樣本集中較難直接估計得等到。所以樸素貝葉斯演算法採用了屬性條件獨立性假設,假設所有屬性相互獨立,因此可得公式:
P
(
c
∣
x
)
=
P
(
c
)
P
(
c
∣
x
)
P
(
x
)
=
P
(
c
)
P
(
x
)
∏
i
=
1
d
P
(
x
i
∣
c
)
P(c|x)=\frac{P(c)P(c|x)}{P(x)} = \frac{P(c)}{P(x)}\prod_{i=1}^dP(x_i|c)
P(c∣x)=P(x)P(c)P(c∣x)=P(x)P(c)i=1∏dP(xi∣c)
所以,對於分類任務,可以由公式:
h
(
x
)
=
arg
max
c
∈
λ
P
(
c
)
∏
i
=
1
d
P
(
x
i
∣
c
)
h(x) = \arg\max_{c\in\lambda}P(c)\prod_{i=1}^{d}P(x_i|c)
h(x)=argc∈λmaxP(c)i=1∏dP(xi∣c)
來選擇最大的值作為當前樣本的分類。
對於樸素貝葉斯演算法,可以在資料集中直接估計得到類先驗概率以及條件概率
P
(
c
)
=
∣
D
c
∣
∣
D
∣
P
(
x
i
∣
c
)
=
∣
D
c
,
x
i
∣
∣
D
c
∣
P(c) = \frac{|D_c|}{|D|} \\ P(x_i|c) = \frac{|D_{c,x_i}|}{|D_c|}
P(c)=∣D∣∣Dc∣P(xi∣c)=∣Dc∣∣Dc,xi∣
如果屬性是連續屬性,那麼可以根據概率密度函式來計算得到條件概率,假設其服從高斯分佈,其
μ
c
,
i
\mu_{c,i}
μc,i和
σ
c
,
i
2
\sigma^2_{c,i}
σc,i2是第
c
c
c類樣本在第
i
i
i個屬性上的均值和方差。
拉普拉斯平滑
考慮到樣本集中的樣本並不是完全的,可能存在某個類的某個屬性沒有出現,所以採用拉普拉斯平滑,避免未出現的屬性攜帶的資訊被抹去。主要就是在求條件概率和先驗概率時,將分子加上
λ
\lambda
λ,相應的分母加上
∣
D
∣
∗
λ
|D|*\lambda
∣D∣∗λ。本次實驗中採用的是最簡單的採用
λ
=
1
\lambda=1
λ=1
P
(
c
)
=
∣
D
c
∣
+
1
∣
D
∣
+
N
P
(
x
i
∣
c
)
=
∣
D
c
,
x
i
∣
+
1
∣
D
c
∣
+
N
i
P(c) = \frac{|D_c| + 1}{|D| + N} \\ P(x_i|c) = \frac{|D_{c,x_i}| + 1}{|D_c|+N_i}
P(c)=∣D∣+N∣Dc∣+1P(xi∣c)=∣Dc∣+Ni∣Dc,xi∣+1
實驗程式碼
訓練
計算先驗概率:
for i in np.unique(np.array(y)):
self.prior[i] = (y.count(i) + self.L) / (len(y) + len(np.unique(np.array(y))))
計算條件概率:
for c in np.unique(np.array(y)):
D_c = Data.loc[Data[Data.columns[-1]] == c]
for x in feature:
for i in np.unique(np.array(Data[x])):
D_x = D_c.loc[D_c[x] == i]
before = str(x) + "," + str(i)
after = str(c)
key = before + "|" + after
self.P[key] = (len(D_x) + self.L) / (len(D_c) + len(np.unique(np.array(Data[x]))))
這裡儲存的方式都採用字典(Dict)的方式,對於條件概率,key使用字串形式表示,將其各個特徵的值轉換為字串並拼接來作為key。
整體程式碼:
def fit(self, Data: pd.DataFrame):
y = Data.iloc[:, -1]
y = list(y)
feature = Data.columns[:-1]
# priority
for i in np.unique(np.array(y)):
# 拉普拉斯平滑
self.prior[i] = (y.count(i) + self.L) / (len(y) + len(np.unique(np.array(y))))
# given
for c in np.unique(np.array(y)):
D_c = Data.loc[Data[Data.columns[-1]] == c]
for x in feature:
for i in np.unique(np.array(Data[x])):
D_x = D_c.loc[D_c[x] == i]
# 將屬性轉換為字串形式 作為key
before = str(x) + "," + str(i)
after = str(c)
key = before + "|" + after
# 拉普拉斯平滑
self.P[key] = (len(D_x) + self.L) / (len(D_c) +
len(np.unique(np.array(Data[x]))))
傳入的Data即訓練集
預測
具體的方式就是取出每一個待預測的樣本,根據樸素貝葉斯的公式,取出每個屬性的值進行連乘,最後選取概率最大的作為當前的預測標籤,最終返回
def pred(self, Data: pd.DataFrame) -> Tuple[List[str], List[str]]:
ans = []
acc = []
for _, val in Data.iterrows():
ret = None
mle = 0
for c in self.prior.keys():
pred = self.prior[c]
for idx in range(len(Data.columns[:-1])):
feature = Data.columns[idx]
cls = val[idx]
key = str(feature) + "," + str(cls) + "|" + str(c)
pred *= self.P[key]
if pred > mle:
mle = pred
ret = c
ans.append(ret)
if ret == val[-1]:
acc.append(1)
else:
acc.append(0)
return ans, acc
結果
在perdict集上正確率為:0.8095238095238095
決策樹
基礎理論
決策樹學習本質是從訓練資料集中歸納出一組分類規則,在每個結點進行分裂,將原本的資料集進行劃分,最終得到葉子結點,即當前資料的標籤,也就是之後預測的結果。基於樹結構的演算法都有一個比較好的特點:不需要進行特徵的歸一化,而且訓練速度較快。
不同的決策樹演算法採用不同的分裂依據。
ID3
ID3決策樹使用資訊增益來作為分裂依據,通過計算當前資料集中,資訊增益最大的特徵作為劃分依據,將資料集劃分為多個子資料集,作為當前節點的分支。
資訊熵:用來表示當前的樣本集合純度
E
n
t
(
D
)
=
−
∑
k
=
1
∣
λ
∣
p
k
l
o
g
2
p
k
Ent(D) = -\sum_{k=1}^{|\lambda|}p_klog_2p_k
Ent(D)=−k=1∑∣λ∣pklog2pk
p
k
p_k
pk為第
k
k
k類樣本所佔的比例
資訊增益:
G
a
i
n
(
D
,
a
)
=
E
n
t
(
D
)
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
E
n
t
(
D
v
)
Gain(D, a) = Ent(D) - \sum_{v=1}^V\frac{|D^v|}{|D|}Ent(D^v)
Gain(D,a)=Ent(D)−v=1∑V∣D∣∣Dv∣Ent(Dv)
其中,D為當前結點的資料集,v為其特徵
從而,對於每一個特徵計算資訊增益,選擇最大的特徵作為分類的依據,從而得到該節點的子結點,一直遞迴的執行,直到到達葉子節點:均屬於同一類樣本或沒有特徵可供選擇,對於後者,選取當前資料集中,包含最多樣本的類別作為當前葉子結點的值。
C4.5
C4.5演算法與ID3演算法相似,不過其採用資訊增益率而非資訊增益來選擇最優化分屬性。除此之外,還支援預剪枝以及後剪枝的方法。
資訊增益率
G
a
i
n
r
a
t
i
o
(
D
,
a
)
=
G
a
i
n
(
D
,
a
)
I
V
(
a
)
I
V
(
a
)
=
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
log
2
∣
D
v
∣
∣
D
∣
Gain_ratio(D, a)=\frac{Gain(D, a)}{IV(a)} \\ IV(a) = -\sum_{v=1}^V\frac{|D^v|}{|D|}\log_2\frac{|D^v|}{|D|}
Gainratio(D,a)=IV(a)Gain(D,a)IV(a)=−v=1∑V∣D∣∣Dv∣log2∣D∣∣Dv∣
I
V
(
a
)
IV(a)
IV(a)表示屬性
a
a
a的固有值。
資訊增益率對可取值數目較少的屬性有所偏好,所以對C4.5演算法進行改進,採用一個啟發式來進行選擇:從候選劃屬性中找到資訊增益高於平均水平的屬性,再從中選擇增益率最高的。
CART
CART則採用基尼指數來進行劃分屬性的選擇
基尼值:
G
i
n
i
(
D
)
=
∑
k
=
1
∣
λ
∣
∑
k
‘
≠
k
p
k
p
k
‘
=
1
−
∑
k
=
1
∣
λ
∣
p
k
2
Gini(D) = \sum_{k=1}^{|\lambda|}\sum_{k^`\ne k}p_kp_k^`=1-\sum_{k=1}^{|\lambda|}p_k^2
Gini(D)=k=1∑∣λ∣k‘=k∑pkpk‘=1−k=1∑∣λ∣pk2
基尼指數:
G
i
n
i
i
n
d
e
x
(
D
,
a
)
=
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
G
i
n
i
(
D
v
)
Gini_index(D, a) = \sum_{v=1}^V\frac{|D^v|}{|D|}Gini(D^v)
Giniindex(D,a)=v=1∑V∣D∣∣Dv∣Gini(Dv)
與前兩者不同,其選取的是基尼指數最小的屬性作為最優化分屬性,而且CART不僅可以用於分類,而且還可以用於迴歸。
除了上述所說的,決策樹多用於整合學習演算法,如隨機森林,GBDT等演算法,都有比較好的效果。
實驗程式碼
本次決策樹的實驗程式碼支援了上述三種構造的方法
ID3
def entropy(self, x: pd.DataFrame) -> float:
'''資訊熵'''
result = 0
size = x.size
for i in np.unique(np.array(x)):
p = x[x == i].size / size
result += p * np.log2(p)
return -1 * result
def cond_entropy(self, x: pd.DataFrame) -> float:
'''條件熵'''
result = 0
size = len(x)
feature = x.columns[0]
for i in np.unique(np.array(x.iloc[:, 0])):
D_i = x.loc[x[feature] == i]
result += len(D_i) / size * self.entropy(D_i.iloc[:, -1])
return result
def gain(self, x: pd.DataFrame) -> float:
'''資訊增益 ID3
x: 僅包含對應的特徵列與標籤列
'''
return self.entropy(np.array(x.iloc[:, -1])) - self.cond_entropy(x)
C4.5
def gain_ratio(self, x: pd.DataFrame) -> float:
'''增益率 C4.5
x: 僅包含對應的特徵列與標籤列
'''
return self.gain(x) / self.entropy(x.iloc[:, 0])
CART
def gini(self, x: pd.DataFrame) -> float:
'''基尼係數'''
result = 1.0
size = x.size
for i in np.unique(np.array(x)):
p = x[x == i].size / size
result -= p ** 2
return result
def gain_gini(self, x: pd.DataFrame) -> Tuple[float, str]:
'''基尼指數 CART
x: 僅包含對應的特徵列與標籤列
'''
min_gini = float("+inf")
kind = ""
feature = x.columns[0]
size = len(x)
for i in np.unique(np.array(x[feature])):
D_1 = x.loc[x[feature] == i].iloc[:, -1]
D_2 = x.loc[x[feature] != i].iloc[:, -1]
gini = D_1.size / size * self.gini(D_1) + D_2.size / size * self.gini(D_2)
if gini < min_gini:
min_gini = gini
kind = i
return min_gini, kind
以上,ID3和C4.5都返回的是計算得到的該特徵的資訊增益或增益率,而CART則是返回基尼指數以及最小的屬性值
在此之後,就可以實現決策樹的構造:
劃分依據
def get_max(self, x: pd.DataFrame) -> Tuple[str, List[pd.DataFrame]]:
'''計算劃分依據
x: 樣本集D
return: max_feature本次依據的特徵 dataset: 根據該特徵劃分出的資料集
'''
max_val = 0
max_feature = str
if self.method == "CART": # CART還要有當前的屬性
max_kind = str
max_val = float("+inf") # 由於選擇是最小的基尼指數,所以應當為極大值
for i in x.columns[:-1]: # 遍歷每一個特徵,選取最優劃分依據
A = pd.concat([x[i], x[x.columns[-1]]], axis=1)
val = float
if self.method == "ID3": # 根據不同的方法選擇得到不同的值
val = self.gain(A)
elif self.method == "C4.5":
val = self.gain_ratio(A)
elif self.method == "CART":
val, kind = self.gain_gini(A)
if val > max_val and self.method != "CART":
max_val = val
max_feature = i
elif val < max_val and self.method == "CART":
max_val = val
max_feature = i
max_kind = kind
dataset = [] # 劃分
if self.method != "CART": # 多叉樹
for i in np.unique(np.array(x[max_feature])):
D_i = x.loc[x[max_feature] == i]
dataset.append(D_i)
elif self.method == "CART": # 二叉樹
D_1 = x.loc[x[max_feature] == max_kind]
D_2 = x.loc[x[max_feature] != max_kind]
dataset.append(D_1)
dataset.append(D_2)
return max_feature, dataset
由於CART為二叉樹,並且劃分依據與其他的不同,所以在這為了適配三種方法,採用了特殊的判斷。最終,這個函式返回的是最優劃分屬性以及劃分後的資料集,使用List[pd.DataFrame]表示
建樹
def build(self, x: pd.DataFrame) -> Dict:
deep = copy.deepcopy(self.depth)
self.depth += 1
y = x.iloc[:, -1]
if len(np.unique(np.array(y))) == 1: # 葉子節點
self.depth -= 1
return y.iloc[0]
if len(x.columns) == 1: # 沒有屬性可供劃分
result = list(x.iloc[:, -1])
val, label = 0, str
for i in np.unique(np.array(result)): # 投票
if result.count(i) > val:
val = result.count(i)
label = i
return label
if deep > self.max_depth: # 到達最大深度 剪枝
result = list(x.iloc[:, -1])
val, label = 0, str
for i in np.unique(result):
if result.count(i) > val:
val = result.count(i)
label = i
return label
feature, dataset = self.get_max(x)
tree = {feature: {}}
if self.method != "CART": # 多叉樹構造,遍歷上述方法求得的dataset集
for i in range(len(dataset)):
data = dataset[i].copy()
kind = data[feature].iloc[0]
data.drop(feature, inplace=True, axis=1)
feature_ch = self.build(data)
tree[feature][kind] = feature_ch
elif self.method == "CART": # 二叉樹構造,只有該種屬性和其他
data_1 = dataset[0].copy()
data_2 = dataset[1].copy()
kind = data_1[feature].iloc[0]
data_1.drop(feature, inplace=True, axis=1)
data_2.drop(feature, inplace=True, axis=1)
feature_ch = self.build(data_1)
tree[feature][kind] = feature_ch
feature_ch = self.build(data_2)
tree[feature]["!" + kind] = feature_ch # 其他採用 "!"+屬性值 表示
self.depth -= 1
return tree
預測
def predict(self, pred: pd.DataFrame) -> List[str]:
ans = [] # 最終預測的結果
class_list = list(pred.iloc[:, -1]) # 葉子結點的屬性值
for _, data in pred.iterrows(): # 遍歷每一個測試樣例
key = [elem for elem in self.tree.keys()][0] # 取出當前樹的鍵值(劃分的屬性依據)
feature = data[key] # 得到預測的資料中該屬性的值
if self.method != "CART": # 不是CART決策時
class_val = self.tree[key][feature] # 得到該屬性的該值對應的子樹
elif self.method == "CART": # 是CART
try:
class_val = self.tree[key][feature] # 決策樹的屬性值與測試資料該屬性的值相匹配
except KeyError:
feature_notin = [elem for elem in self.tree[key].keys()][1] # 不匹配 得到另一枝
# 得到該屬性的另一值對應的子樹(CART為二叉樹)
class_val = self.tree[key][feature_notin]
while class_val not in class_list: # 當子樹的屬性不是葉子節點的屬性(當不是葉子節點時)
key = [elem for elem in class_val.keys()][0] # 重複上面的操作
feature = data[key]
if self.method != "CART":
class_val = class_val[key][feature]
elif self.method == "CART":
try:
class_val = class_val[key][feature]
except KeyError:
feature_notin = [elem for elem in class_val[key].keys()][1]
class_val = class_val[key][feature_notin]
ans.append(class_val) # 將本次預測的結果加入到ans中
return ans # 返回所有資料預測的結果
預測部分與樸素貝葉斯相似,都是返回預測的預測集結果。在預測的過程中,同樣的遍歷每一個樣例,取出當前樹的劃分依據,根據樣例的屬性來選擇分支,一直繼續直到到達葉子節點,此時,得到的就是該樣例的預測結果。
結果
在predict集上能夠得到完全的預測。
支援向量機
這一部分由於課本上並沒有深入的介紹,所以我也沒有具體去手動實現,因為SMO演算法的實現確實有難度。
理論基礎
對於樣本的分類,最基本的想法就是基於資料集D,在樣本空間找到一個劃分超平面,把不同類別的進行分開。而支援向量機,則是在該基礎上,找到兩個分類到達超平面的最大間隔最大,那麼這一個超平面則是要學習到的超平面。
根據超平面的方程:
ω
⃗
T
x
+
b
=
0
\vec\omega^Tx + b = 0
ωTx+b=0可見超平面可用
(
ω
⃗
,
b
)
(\vec\omega, b)
(ω,b)來表示,那麼樣本空間中任意點到超平面的距離即
r
=
ω
⃗
T
x
⃗
+
b
∣
∣
w
∣
∣
r = \frac{\vec\omega^T\vec{x}+b}{||w||}
r=∣∣w∣∣ωTx+b
考慮到目標是找到超平面,使得距離該超平面最近的樣本點最遠,所以有
y
(
x
)
=
m
i
n
1
∣
∣
ω
∣
∣
y
(
ω
T
x
+
b
)
y(x) = min\frac{1}{||\omega||}y(\omega^Tx+b)
y(x)=min∣∣ω∣∣1y(ωTx+b)
求解目標即:
arg
max
ω
,
b
y
(
x
)
即
arg
max
ω
,
b
(
min
i
1
∣
∣
ω
∣
∣
y
i
(
ω
T
x
i
+
b
)
)
\arg\max_{\omega, b}y(x) \\ 即\arg\max_{\omega, b}(\min_i\frac{1}{||\omega||}y_i(\omega^Tx_i+b))
argω,bmaxy(x)即argω,bmax(imin∣∣ω∣∣1yi(ωTxi+b))
如果可用完全正確分類,那麼有
{
ω
⃗
T
x
i
+
b
⩾
+
1
,
y
i
=
+
1
;
ω
⃗
T
x
i
+
b
⩽
−
1
,
y
i
=
−
1.
\begin{cases} \vec\omega^Tx_i+b\geqslant+1,\ y_i=+1;\\ \vec\omega^Tx_i+b\leqslant-1,\ y_i=-1. \end{cases}
{ωTxi+b⩾+1, yi=+1;ωTxi+b⩽−1, yi=−1.
在邊界上的樣本點即支援向量,兩個異類支援向量到超平面的距離和為
γ
=
2
∣
∣
ω
∣
∣
\gamma=\frac{2}{||\omega||}
γ=∣∣ω∣∣2,所以,目標即使其最大化,等價於
min
ω
,
b
1
2
∣
∣
ω
∣
∣
2
s
.
t
.
y
i
(
ω
⃗
T
x
i
+
b
)
⩾
1
,
i
=
1
,
2
,
…
…
,
m
\min_{\omega, b}\frac{1}{2}||\omega||^2 \\ s.t. \ y_i(\vec\omega^Tx_i+b)\geqslant1, i =1, 2, ……, m
ω,bmin21∣∣ω∣∣2s.t. yi(ωTxi+b)⩾1,i=1,2,……,m
得到了上述的式子,顯然是一個最優化問題,可以通過梯度下降等優化演算法來對其進行求解。
此處可以選擇損失函式為 h i n g e l o s s hinge\ loss hinge loss,對其使用梯度下降進行優化,從而得到最終的解。
另一種效率更高的方式即SMO演算法:引入拉格朗日乘子得到其對偶問題,並且根據上式的約束條件滿足KKT條件,可以使用SMO演算法來求解拉格朗日乘子,進而得到所求的 ω \omega ω和 b b b。
除此之外,還可以使用 K e r n e l T r i c k Kernel\ Trick Kernel Trick來將樣本點對映到高維空間,以解決線性不可分的問題。通過引入軟間隔,使得其允許接受一定程度得錯誤。
程式碼實現
這裡採用的是sklearn庫的SVC方法
svc = SVC()
svc.fit(x, y)
採用了預設的引數,即使用高斯核,軟間隔超引數C=1.0
結果
能夠做到 0.9206349206349206 的正確率
神經網路
理論基礎
簡單來說,每個神經網路必有一層輸入層,一層輸出層,中間的隱層可以自行設定。輸入層得大小即樣本的特徵數量,輸出層的大小為分類的類別數目。對於每一個樣本的輸入,首先經過前向傳播:簡單的線性函式累加,再經過啟用函式之後繼續傳遞到下一層,到達輸出層後計算誤差,將誤差進行反向傳播,並對引數 ω \omega ω進行優化。經過多次訓練,直到損失函式收斂,訓練結束。
預測部分則將資料輸入,得到每個輸出神經元的值,從中選取最大的值最為當前樣本的預測結果。
考慮上圖的神經網路,輸入為 x i x_i xi,經過前向傳播到達隱層,得到隱層結點的值: a i ( 1 ) = ω 01 T x a_i^{(1)} = \omega_{01}^Tx ai(1)=ω01Tx,經過一次啟用函式得到: b i ( 1 ) = g ( a i ( 1 ) ) b_i^{(1)}=g(a_i^{(1)}) bi(1)=g(ai(1)),再繼續傳播得到輸出的值: y i ^ = ω 12 T b \hat{y_i} = \omega_{12}^Tb yi^=ω12Tb(假設偏置 b i a s bias bias放入到輸入作為 ω i ( 0 ) \omega_i^{(0)} ωi(0),對應的輸入 x 0 = 1 x_0 = 1 x0=1)。
之後再將誤差反向傳播,首先得到輸出層的誤差: δ i ( 2 ) = y ^ − y \delta_i^{(2)}=\hat y-y δi(2)=y^−y,反向傳播得到隱層的誤差 δ i ( 1 ) = ω 12 T δ i ( 2 ) ⋅ g ′ ( b i ( 1 ) ) \delta_i^{(1)}=\omega_{12}^T\delta_i^{(2)}·g'(b_i^{(1)}) δi(1)=ω12Tδi(2)⋅g′(bi(1)),同時求得梯度為 Δ i j l = Δ i j l + a j ( l ) δ i ( l + 1 ) \Delta_{ij}^l=\Delta_{ij}^l+a_j^{(l)}\delta_i^{(l+1)} Δijl=Δijl+aj(l)δi(l+1),繼續對引數進行優化: ∂ ∂ ω i j ( l ) J ( ω ) = 1 m Δ i j l + λ ω i j ( l ) \frac{\partial}{\partial\omega_{ij}^{(l)}}J(\omega)=\frac{1}{m}\Delta_{ij}^l+\lambda\omega_{ij}^{(l)} ∂ωij(l)∂J(ω)=m1Δijl+λωij(l)
程式碼實現
採用了四層的神經網路,包含兩個隱層,啟用函式選擇ReLU啟用函式:
class NN(nn.Module):
def __init__(self, in_dim, hidden_1, hidden_2, out_dim):
super(NN, self).__init__()
self.hidden1 = nn.Linear(in_dim, hidden_1)
self.hidden2 = nn.Linear(hidden_1, hidden_2)
self.out = nn.Linear(hidden_2, out_dim)
def forward(self, x):
x = self.hidden1(x)
x = F.relu(x)
x = self.hidden2(x)
x = F.relu(x)
x = self.out(x)
x = F.relu(x)
return x
在訓練時,選擇的優化器為隨機梯度下降優化器,學習率預設為0.1,使用交叉熵作為損失函式,訓練輪數預設5000輪:
def __init__(self, in_dim, hidden_1, hidden_2, out_dim, lr=0.1, epochs=5000):
self.model = NN(in_dim, hidden_1, hidden_2, out_dim)
self.optimizer = optim.SGD(self.model.parameters(), lr=lr) # 隨機梯度下降優化器
self.criterion = nn.CrossEntropyLoss() # 交叉熵
self.epochs = epochs
if torch.cuda.is_available():
self.device = torch.device('cuda:0')
self.model.to(self.device)
self.criterion = self.criterion.cuda()
訓練:
def fit(self, data: pd.DataFrame, test: pd.DataFrame):
X_train = data.iloc[:, :-1]
y_train = data.iloc[:, -1]
X_test = test.iloc[:, :-1]
y_test = test.iloc[:, -1]
X_train = np.array(X_train, dtype=np.float32)
y_train = np.array(y_train, dtype=np.float32)
X_test = np.array(X_test, dtype=np.float32)
y_test = np.array(y_test, dtype=np.float32)
X_test = torch.from_numpy(X_test)
y_test = torch.from_numpy(y_test)
if torch.cuda.is_available():
X_test = X_test.to(self.device)
for epoch in range(self.epochs): # 每一輪訓練
x = torch.from_numpy(X_train)
y = torch.from_numpy(y_train)
if torch.cuda.is_available():
x = x.to(self.device)
y = y.to(self.device)
pred = self.model(x)
loss = self.criterion(pred, y.long()) # 計算loss
self.optimizer.zero_grad() # 優化
loss.backward() # 反向傳播
self.optimizer.step()
預測則是輸入資料,選擇最大的類別作為當前的分類:
def predict(self, pred: pd.DataFrame) -> float:
X_pred = pred.iloc[:, :-1]
y_pred = pred.iloc[:, -1]
X_pred = np.array(X_pred, dtype=np.float32)
y_pred = np.array(y_pred, dtype=np.float32)
x = torch.from_numpy(X_pred)
y = torch.from_numpy(y_pred)
if torch.cuda.is_available():
x = x.to(self.device)
predict = self.model(x)
predict = torch.max(predict, 1)[1] # 選擇最大的類別
if torch.cuda.is_available():
predict = predict.cpu()
pred_y = predict.data.numpy()
target_y = y.data.numpy()
acc = float((pred_y == target_y).astype(int).sum()) / float(target_y.size)
return acc
結果
每500次訓練進行loss的輸出以及準確率的檢視
最終預測結果為0.9682539682539683的準確率。若將輪數增大,準確率能夠逼近1.0
實驗結果
均為在perdict資料集上做預測的結果。
顯而易見的是,神經網路的分類方法能夠適用於任何資料集,在模型搭建好之後,只需要進行訓練,就可以得到不錯的結果,訓練的輪數越多,網路的層數越深,就能夠得到約準確的分類,但是帶來的時間開銷也是巨大的。
對於其他三種演算法,支援向量機的效能是很不錯的,泛化效能較強,而且是用於解決高維的問題;樸素貝葉斯較簡單,由於是有堅實的數學基礎所以其分類效率較穩定,但是需要直到其先驗概率才可以進行學習;決策樹的優點正如上述所講,不需要進行特徵歸一化,但是其容易忽略樣本特徵之間的相關性,而且容易過擬合。
思考題
對於引數的調整,支援向量機可以嘗試不同的核,或者減少其軟間隔係數,以使得分類效果更好,但是會降低其泛化效能;決策樹則可以嘗試不同的剪枝策略來提高其泛化能力;樸素貝葉斯則可以嘗試進行不同的平滑策略,除了拉普拉斯平滑還可以選擇lidstone平滑;而神經網路,則可以嘗試增加訓練輪數或者增大網路的深度來得到更好的分類結果,但是帶來的代價則是時間開銷的增大,此外還可以調整學習率來得到更好的訓練結果。
相關文章
- 實驗7.Vlan劃分實驗
- 實驗三驗收3
- 實驗三 數值積分(android)Android
- Java實驗二:類程式設計實驗Java程式設計
- 湖南大學人工智慧實驗二:Prolog程式設計求解圖搜尋問題人工智慧程式設計
- OPP實驗三
- OOP實驗三OOP
- 實驗三--測試
- 演算法實驗二演算法
- KNN演算法實驗KNN演算法
- 實驗5——類和物件物件
- 實驗--抽象類的使用抽象
- 實驗2 類與物件物件
- 實驗2 類和物件物件
- 實驗3 類和物件物件
- 資料結構實驗三:線性表綜合實驗資料結構
- 實驗三 類和物件 基礎程式設計2物件程式設計
- 實驗三 類和物件_基礎程式設計2物件程式設計
- Oracle恢復實驗(三)Oracle
- 計算機網路實驗三—— Cisco Packet Tracer 實驗計算機網路
- 實驗三結對專案
- 實驗三的專案分析
- 實驗三-軟體測試
- 實驗三:單元測試
- 實驗三——軟體測試
- 實驗三:軟體測試
- 實驗三 軟體測試
- 做實驗驗證MongoDB分頁的兩種方法MongoDB
- 實驗11.ACL實驗
- 資料結構實驗三 2024_樹與圖實驗資料結構
- 實驗七: 查詢演算法的實現演算法
- lumen驗證類 實現控制器場景驗證
- 實驗5.OSPF配置實驗
- 20個實驗之實驗11
- 20個實驗之實驗10
- 20個實驗之實驗16
- 30個實驗之實驗23
- 30個實驗之實驗25