決策樹算法推導&python應用

決策樹公式推導

(1)信息熵--用來度量樣本集合純度最常用的一種指標,定義如下:
Ent(D)=k=1Ypklog2pk(式1) \operatorname{Ent}(D)=-\sum_{k=1}^{\vert{\mathcal{Y}}\vert}p_k\log_2p_k\tag{式1}
其中,D={(x1,y1),(x2,y2),,(xm,ym)}D=\{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\}表示樣本集合,y|y|表示樣本類別總數,PkP_k表示第kk類樣本所佔的比例,而且滿足0Pk1k=1ypk=10\le{P_k}\le{1\cdot\sum_{k=1}^{|y|}p_k=1}。上式值越小,則純度越高。(樣本儘可能屬於同一類別,則“純度”更高)

[證]:證明0Ent(D)log2Y0\leq\operatorname{Ent}(D)\leq\log_{2}|\mathcal{Y}|
已知集合DD的信息熵的定義爲
Ent(D)=k=1Ypklog2pk(式2) \operatorname{Ent}(D)=-\sum_{k=1}^{|\mathcal{Y}|} p_{k} \log _{2} p_{k}\tag{式2}
其中,Y|\mathcal{Y}|表示樣本類別總數,pkp_k表示第kk類樣本所佔的比例,且0pk1,k=1npk=10 \leq p_k \leq 1,\sum_{k=1}^{n}p_k=1。如若令Y=n,pk=xk|\mathcal{Y}|=n,p_k=x_k,那麼信息熵Ent(D)\operatorname{Ent}(D)就可以看作一個nn元實值函數,也即
Ent(D)=f(x1,...,xn)=k=1nxklog2xk(式3) \operatorname{Ent}(D)=f(x_1,...,x_n)=-\sum_{k=1}^{n} x_{k} \log _{2} x_{k} \tag{式3}
其中,0xk1,k=1nxk=10 \leq x_k \leq 1,\sum_{k=1}^{n}x_k=1,下面考慮求該多元函數的最值。

最大值:

如果不考慮約束0xk10 \leq x_k \leq 1,僅考慮k=1nxk=1\sum_{k=1}^{n}x_k=1的話,對f(x1,...,xn)f(x_1,...,x_n)求最大值等價於如下最小化問題
mink=1nxklog2xk s.t. k=1nxk=1(式4) \begin{array}{ll}{ \operatorname{min}} & {\sum\limits_{k=1}^{n} x_{k} \log _{2} x_{k} } \\ {\text { s.t. }} & {\sum\limits_{k=1}^{n}x_k=1} \end{array}\tag{式4}
其中,y=xlogxy=x\log{x}函數圖像如下:
xlogx
這樣一來,在0xk10 \leq x_k \leq 1時,此問題爲凸優化問題,而對於凸優化問題來說,滿足KKT條件的點即爲最優解。由於此最小化問題僅含等式約束,那麼能令其拉格朗日函數的一階偏導數等於0的點即爲滿足KKT條件的點。根據拉格朗日乘子法可知,該優化問題的拉格朗日函數如下:
L(x1,...,xn,λ)=k=1nxklog2xk+λ(k=1nxk1)(式5) L(x_1,...,x_n,\lambda)=\sum_{k=1}^{n} x_{k} \log _{2} x_{k}+\lambda(\sum_{k=1}^{n}x_k-1)\tag{式5}
其中,λ\lambda爲拉格朗日乘子。對L(x1,...,xn,λ)L(x_1,...,x_n,\lambda)分別關於x1,...,xn,λx_1,...,x_n,\lambda求一階偏導數,並令偏導數等於0可得到下式:
L(x1,...,xn,λ)x1=x1[k=1nxklog2xk+λ(k=1nxk1)]=0=log2x1+x11x1ln2+λ=0=log2x1+1ln2+λ=0λ=log2x11ln2x2L(x1,...,xn,λ)x2=x2[k=1nxklog2xk+λ(k=1nxk1)]=0λ=log2x21ln2xnL(x1,...,xn,λ)xn=xn[k=1nxklog2xk+λ(k=1nxk1)]=0λ=log2xn1ln2λL(x1,...,xn,λ)λ=λ[k=1nxklog2xk+λ(k=1nxk1)]=0k=1nxk=1(式6) \begin{aligned} \cfrac{\partial L(x_1,...,x_n,\lambda)}{\partial x_1}&=\cfrac{\partial }{\partial x_1}\left[\sum_{k=1}^{n} x_{k} \log _{2} x_{k}+\lambda(\sum_{k=1}^{n}x_k-1)\right]=0\\ &=\log _{2} x_{1}+x_1\cdot \cfrac{1}{x_1\ln2}+\lambda=0 \\ &=\log _{2} x_{1}+\cfrac{1}{\ln2}+\lambda=0 \\ &\Rightarrow \lambda=-\log _{2} x_{1}-\cfrac{1}{\ln2}\\ 對x_2求偏導數有:\\ \cfrac{\partial L(x_1,...,x_n,\lambda)}{\partial x_2}&=\cfrac{\partial }{\partial x_2}\left[\sum_{k=1}^{n} x_{k} \log _{2} x_{k}+\lambda(\sum_{k=1}^{n}x_k-1)\right]=0\\ &\Rightarrow \lambda=-\log _{2} x_{2}-\cfrac{1}{\ln2}\\ \vdots\\ 對x_n求偏導數有:\\ \cfrac{\partial L(x_1,...,x_n,\lambda)}{\partial x_n}&=\cfrac{\partial }{\partial x_n}\left[\sum_{k=1}^{n} x_{k} \log _{2} x_{k}+\lambda(\sum_{k=1}^{n}x_k-1)\right]=0\\ &\Rightarrow \lambda=-\log _{2} x_{n}-\cfrac{1}{\ln2}\\ 對\lambda求偏導數有:\\ \cfrac{\partial L(x_1,...,x_n,\lambda)}{\partial \lambda}&=\cfrac{\partial }{\partial \lambda}\left[\sum_{k=1}^{n} x_{k} \log _{2} x_{k}+\lambda(\sum_{k=1}^{n}x_k-1)\right]=0\\ &\Rightarrow \sum_{k=1}^{n}x_k=1 實際上就等於約束條件。\\ \end{aligned}\tag{式6}
整理一下可得:
{λ=log2x11ln2=log2x21ln2=...=log2xn1ln2k=1nxk=1(式7) \left\{ \begin{array}{lr} \lambda=-\log _{2} x_{1}-\cfrac{1}{\ln2}=-\log _{2} x_{2}-\cfrac{1}{\ln2}=...=-\log _{2} x_{n}-\cfrac{1}{\ln2} \\ \sum\limits_{k=1}^{n}x_k=1 \end{array}\right.\tag{式7}

由以上兩個方程可以解得:
x1=x2=...=xn=1n(式8) x_1=x_2=...=x_n=\cfrac{1}{n}\tag{式8}

又因爲xkx_k還需滿足約束0xk10 \leq x_k \leq 1,這樣就得到01n10 \leq\cfrac{1}{n}\leq 1,所以x1=x2=...=xn=1nx_1=x_2=...=x_n=\cfrac{1}{n}是滿足所有約束的最優解,也即爲當前最小化問題的最小值點,同時也是f(x1,...,xn)f(x_1,...,x_n)的最大值點。將x1=x2=...=xn=1nx_1=x_2=...=x_n=\cfrac{1}{n}代入f(x1,...,xn)f(x_1,...,x_n)中可得:
f(1n,...,1n)=k=1n1nlog21n=n1nlog21n=log2n(式9) f(\cfrac{1}{n},...,\cfrac{1}{n})=-\sum_{k=1}^{n} \cfrac{1}{n} \log _{2} \cfrac{1}{n}=-n\cdot\cfrac{1}{n} \log _{2} \cfrac{1}{n}=\log _{2} n\tag{式9}
所以f(x1,...,xn)f(x_1,...,x_n)在滿足約束0xk1,k=1nxk=10 \leq x_k \leq 1,\sum_{k=1}^{n}x_k=1時的最大值爲log2n\log _{2} n

最小值:

如果不考慮約束k=1nxk=1\sum_{k=1}^{n}x_k=1,僅考慮0xk10 \leq x_k \leq 1f(x1,...,xn)f(x_1,...,x_n)可以看做是nn個互不相關的一元函數的加和,即:
f(x1,...,xn)=k=1ng(xk)(式10) f(x_1,...,x_n)=\sum_{k=1}^{n} g(x_k) \tag{式10}
其中,g(xk)=xklog2xk,0xk1g(x_k)=-x_{k} \log _{2} x_{k},0 \leq x_k \leq 1。那麼當g(x1),g(x2),...,g(xn)g(x_1),g(x_2),...,g(x_n)分別取到其最小值時,f(x1,...,xn)f(x_1,...,x_n)也就取到了最小值。所以接下來考慮分別求g(x1),g(x2),...,g(xn)g(x_1),g(x_2),...,g(x_n)各自的最小值,由於g(x1),g(x2),...,g(xn)g(x_1),g(x_2),...,g(x_n)的定義域和函數表達式均相同,所以只需求出g(x1)g(x_1)的最小值也就求出了g(x2),...,g(xn)g(x_2),...,g(x_n)的最小值。下面考慮求g(x1)g(x_1)的最小值,首先對g(x1)g(x_1)關於x1x_1求一階和二階導數:
g(x1)=d(x1log2x1)dx1=log2x1x11x1ln2=log2x11ln2(式11) g^{\prime}(x_1)=\cfrac{d(-x_{1} \log _{2} x_{1})}{d x_1}=-\log _{2} x_{1}-x_1\cdot \cfrac{1}{x_1\ln2}=-\log _{2} x_{1}-\cfrac{1}{\ln2}\tag{式11}
g(x1)=d(g(x1))dx1=d(log2x11ln2)dx1=1x1ln2(式12) g^{\prime\prime}(x_1)=\cfrac{d\left(g^{\prime}(x_1)\right)}{d x_1}=\cfrac{d\left(-\log _{2} x_{1}-\cfrac{1}{\ln2}\right)}{d x_1}=-\cfrac{1}{x_{1}\ln2}\tag{式12}
發現,當0xk10 \leq x_k \leq 1g(x1)=1x1ln2g^{\prime\prime}(x_1)=-\cfrac{1}{x_{1}\ln2}恆小於0,所以g(x1)g(x_1)是一個在其定義域範圍內開口向下的凹函數,那麼其最小值必然在邊界取,於是分別取x1=0x_1=0x1=1x_1=1,代入g(x1)g(x_1)可得:
g(0)=0log20=0(式13) g(0)=-0\log _{2} 0=0\tag{式13}
g(1)=1log21=0(式14) g(1)=-1\log _{2} 1=0\tag{式14}
所以,g(x1)g(x_1)的最小值爲0,同理可得g(x2),...,g(xn)g(x_2),...,g(x_n)的最小值也爲0,那麼f(x1,...,xn)f(x_1,...,x_n)的最小值此時也爲0。

但是,此時是不考慮約束k=1nxk=1\sum_{k=1}^{n}x_k=1,僅考慮0xk10 \leq x_k \leq 1時取到的最小值,若考慮約束k=1nxk=1\sum_{k=1}^{n}x_k=1的話,那麼f(x1,...,xn)f(x_1,...,x_n)的最小值一定大於等於0。如果令某個xk=1x_k=1,那麼根據約束k=1nxk=1\sum_{k=1}^{n}x_k=1可知x1=x2=...=xk1=xk+1=...=xn=0x_1=x_2=...=x_{k-1}=x_{k+1}=...=x_n=0,將其代入f(x1,...,xn)f(x_1,...,x_n)可得:
f(0,0,...,0,1,0,...,0)=0log200log20...0log201log210log20...0log20=0(式15) f(0,0,...,0,1,0,...,0)=-0 \log _{2}0-0 \log _{2}0...-0 \log _{2}0-1 \log _{2}1-0 \log _{2}0...-0 \log _{2}0=0 \\ \tag{式15}
所以xk=1,x1=x2=...=xk1=xk+1=...=xn=0x_k=1,x_1=x_2=...=x_{k-1}=x_{k+1}=...=x_n=0一定是f(x1,...,xn)f(x_1,...,x_n)在滿足約束k=1nxk=1\sum_{k=1}^{n}x_k=10xk10 \leq x_k \leq 1的條件下的最小值點,其最小值爲0。

綜上可知,當f(x1,...,xn)f(x_1,...,x_n)取到最大值時:x1=x2=...=xn=1nx_1=x_2=...=x_n=\cfrac{1}{n},此時樣本集合純度最低;當f(x1,...,xn)f(x_1,...,x_n)取到最小值時:xk=1,x1=x2=...=xk1=xk+1=...=xn=0x_k=1,x_1=x_2=...=x_{k-1}=x_{k+1}=...=x_n=0,此時樣本集合純度最高。

(2)條件熵–在已知樣本屬性aa的取值情況下,度量樣本集合純度的一種指標:
H(Da)=v=1VDvDEnt(Dv)(式16) H(D|a)=\sum_{v=1}^{V}\cfrac{|D^v|}{|D|}\operatorname{Ent}(D^v)\tag{式16}
其中,aa表示樣本的某個屬性,假定屬性aaVV個可能的取值{a1,a2,,aV}\{a^1,a^2,\cdots,a^V\},樣本集合DD中在屬性aa上取值爲ava^v的樣本記爲DvD^v,Ent(Dv)\operatorname{Ent}(D^v)爲樣本集合DvD^v的信息熵。H(Da)H(D|a)越小,純度越高。

1. ID3 決策樹

ID3決策樹----以信息增益爲準則來選擇劃分屬性的決策樹。信息增益定義如下:
Gain(D,a)=Ent(D)v=1VDvDEnt(Dv)=Ent(D)H(Da)(式17) \begin{aligned} \operatorname{Gain}(D,a) &=\operatorname{Ent}(D)-\sum_{v=1}^{V}\cfrac{|D^v|}{|D|}\operatorname{Ent}(D^v)\\ &=\operatorname{Ent}(D)-H(D|a) \end{aligned}\tag{式17}
選擇信息增益值最大的屬性作爲劃分屬性。因爲信息增益越大,則意味着使用該屬性來進行劃分所獲得的“純度提升”越大。

將(式17)進一步展開寫爲如下形式:
Gain(D,a)=Ent(D)v=1VDvDEnt(Dv)=Ent(D)v=1VDvD(k=1Ypklog2pk)=Ent(D)v=1VDvD(k=1YDkvDvlog2DkvDv)(式18) \begin{aligned} \operatorname{Gain}(D,a) &=\operatorname{Ent}(D)-\sum_{v=1}^{V}\cfrac{|D^v|}{|D|}\operatorname{Ent}(D^v)\\ &=\operatorname{Ent}(D)-\sum_{v=1}^{V}\cfrac{|D^v|}{|D|}\left(-\sum_{k=1}^{\mathcal{|Y|}}p_k\log_2p_k\right)\\ &=\operatorname{Ent}(D)-\sum_{v=1}^{V}\cfrac{|D^v|}{|D|}\left(-\sum_{k=1}^{\mathcal{|Y|}}{\cfrac{|D_k^v|}{|D^v|}}\log_2{\cfrac{|D_k^v|}{|D^v|}}\right)\tag{式18} \end{aligned}
其中,DkvD_k^v爲樣本集合DD中在屬性aa上取值爲ava^v且類別爲kk的樣本。

\Longrightarrow以信息增益爲劃分準則的ID3決策樹對可取值數目較多的屬性有所偏好。

2. C4.5決策樹

C4.5決策樹----以信息增益率爲準則來選擇劃分屬性的決策樹。信息增益率定義爲:
Gain-ratio(D,a)=Gain(D,a)IV(a)IV(a)=v=1VDvDlog2DvD(19) \operatorname{Gain-ratio}(D,a)=\cfrac{\operatorname{Gain}(D,a)}{\operatorname{IV}(a)}\\ 其中,\operatorname{IV}(a)=-\sum_{v=1}^{V}{\cfrac{|D^v|}{|D|}}\log_2{\cfrac{|D^v|}{|D|}}\tag{19}

增益率準則,對可取值數目較少的屬性有所偏好,因此C4.5算法並不是直接選擇增益率最大的劃分屬性,而是使用了一個啓發式;先從候選劃分屬性中找出信息增益高於平均水平的屬性,再從中選擇增益率最高的。

3. CART決策樹

CART決策樹----以基尼指數爲準則來選擇劃分屬性的決策樹。基尼值定義爲:
Gini(D)=k=1Ykkpkpk=k=1Ypkkkpk=k=1Ypk(1pk)=1k=1Ypk2(式20) \begin{aligned} \operatorname{Gini}(D)&=\sum_{k=1}^{|\mathcal{Y}|}\sum_{k^{\prime}\neq{k}}p_k^{\prime}p_k=\sum_{k=1}^{|\mathcal{Y}|}p_k\sum_{k^{\prime}\neq{k}}p_k^{\prime}\\ &=\sum_{k=1}^{|\mathcal{Y}|}p_k(1-p_k)=1-\sum_{k=1}^{|\mathcal{Y}|}p_{k}^2\tag{式20} \end{aligned}
基尼指數:
Gini-index(D,a)=v=1VDvDGini(Dv)(式21) \operatorname{Gini-index}(D,a)=\sum_{v=1}^{V}{\cfrac{|D^v|}{|D|}}\operatorname{Gini}(D^v)\tag{式21}
基尼值和基尼指數越小,樣本集合純度越高。

CART決策樹分類算法:
1.根據基尼指數公式找出基尼指數最小的屬性aa_*
2.計算屬性aa_*的所有可能取值的基尼值Gini(Dv)\operatorname{Gini}(D^v),並選擇基尼值最小的取值ava_*^v作爲劃分點。將集合DD劃分爲D1D_1D2D_2兩個集合(節點),其中D1D_1集合的樣本爲a=ava_*=a_*^v的樣本,D2D_2集合爲aava_*\neq{a_*^v}的樣本。
3.對集合D1,D2D_1,D_2重複步驟1和2,知道滿足條件。

4. 決策樹的構建

author by xiaoyao 這裏我使用酒的數據集來演示一下決策樹的構建。

# 導入libraries
import numpy as np
# 導入畫圖工具
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
# 導入tree模型和數據集加載工具
from sklearn import tree, datasets
# 導入數據集拆分工具
from sklearn.model_selection import train_test_split
wine = datasets.load_wine()
# 這裏只選取數據集的前兩個特徵
X = wine.data[:,:2]
y = wine.target
# 將數據集劃分爲訓練集個測試集
X_train, X_test, y_train, y_test = train_test_split(X, y)

# 忽略警告
import warnings
warnings.filterwarnings("ignore")
# 設定決策樹分類器最大深度爲1
clf = tree.DecisionTreeClassifier(max_depth=1)
# 擬合訓練數據集
clf.fit(X_train, y_train)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=1,
                       max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=None, splitter='best')
# 定義圖像中分區的顏色和散點的顏色
cmap_light = ListedColormap(["#FFAAAA", "#AAFFAA", "#AAAAFF"])
cmap_bold = ListedColormap(["#FF0000", "#00FF00", "#0000FF"])

# 分別用樣本的兩個特徵值創建圖像和橫軸、縱軸
x_min, x_max = X_train[:,0].min() - 1, X_train[:,0].max() + 1
y_min, y_max = X_train[:,1].min() - 1, X_train[:,1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, .02),np.arange(y_min, y_max, .02))
z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
# 給每個分類中的樣本分配不同的顏色
z = z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, z, cmap=cmap_light)

# 使用散點圖進行表示
plt.scatter(X[:,0], X[:,1], c=y, cmap=cmap_bold, edgecolor="k",s=20)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title("Classifier:(max_depth = 1)")
plt.show()

在這裏插入圖片描述

最大深度爲1時,分類器的表現不很好,下面加大深度

# 設定決策樹分類器最大深度爲3
clf2 = tree.DecisionTreeClassifier(max_depth=3)
# 擬合訓練數據集
clf2.fit(X_train, y_train)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=3,
                       max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=None, splitter='best')
# 定義圖像中分區的顏色和散點的顏色
cmap_light = ListedColormap(["#FFAAAA", "#AAFFAA", "#AAAAFF"])
cmap_bold = ListedColormap(["#FF0000", "#00FF00", "#0000FF"])

# 分別用樣本的兩個特徵值創建圖像和橫軸、縱軸
x_min, x_max = X_train[:,0].min() - 1, X_train[:,0].max() + 1
y_min, y_max = X_train[:,1].min() - 1, X_train[:,1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, .02),np.arange(y_min, y_max, .02))
z = clf2.predict(np.c_[xx.ravel(), yy.ravel()])
# 給每個分類中的樣本分配不同的顏色
z = z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, z, cmap=cmap_light)

# 使用散點圖進行表示
plt.scatter(X[:,0], X[:,1], c=y, cmap=cmap_bold, edgecolor="k",s=20)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title("Classifier:(max_depth = 3)")
plt.show()

在這裏插入圖片描述

此時,分類器就可以進行3個分類的識別,而且大部分的數據點都進入了正切的分類。接下來進一步調整深度。

# 設定決策樹分類器最大深度爲3
clf3 = tree.DecisionTreeClassifier(max_depth=5)
# 擬合訓練數據集
clf3.fit(X_train, y_train)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=5,
                       max_features=None, max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, presort=False,
                       random_state=None, splitter='best')
# 定義圖像中分區的顏色和散點的顏色
cmap_light = ListedColormap(["#FFAAAA", "#AAFFAA", "#AAAAFF"])
cmap_bold = ListedColormap(["#FF0000", "#00FF00", "#0000FF"])

# 分別用樣本的兩個特徵值創建圖像和橫軸、縱軸
x_min, x_max = X_train[:,0].min() - 1, X_train[:,0].max() + 1
y_min, y_max = X_train[:,1].min() - 1, X_train[:,1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, .02),np.arange(y_min, y_max, .02))
z = clf3.predict(np.c_[xx.ravel(), yy.ravel()])
# 給每個分類中的樣本分配不同的顏色
z = z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, z, cmap=cmap_light)

# 使用散點圖進行表示
plt.scatter(X[:,0], X[:,1], c=y, cmap=cmap_bold, edgecolor="k",s=20)
plt.xlim(xx.min(), xx.max())
plt.ylim(yy.min(), yy.max())
plt.title("Classifier:(max_depth = 3)")
plt.show()

在這裏插入圖片描述

發現,性能進一步提升了。接下來我使用graphviz這個library來展示這個過程。

安裝方式:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple graphviz

%pwd
'D:\\python code\\8messy'
# 導入graphviz工具包
import graphviz
# 導入決策樹中輸出graphviz的接口
from sklearn.tree import export_graphviz
# 選擇最大深度爲3的分類模型
export_graphviz(clf2, out_file="./wine.dot", class_names=wine.target_names,
               feature_names = wine.feature_names[:2], impurity=False,filled=True)
# 打開一個dot文件
with open('./wine.dot') as f:
    dot_graph = f.read()
# 顯示dot文件中的圖形
graphviz.Source(dot_graph)

在這裏插入圖片描述

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