numpy實(shí)現(xiàn)一個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)(python3)

感知機(jī)(Perceptron)

一種簡(jiǎn)單的感知機(jī)結(jié)構(gòu)如下圖所示菜谣,由三個(gè)輸入節(jié)點(diǎn)和一個(gè)輸出節(jié)點(diǎn)構(gòu)成惭缰,三個(gè)輸入節(jié)點(diǎn)x1浪南,x2,x3分別代表一個(gè)輸入樣本x的三個(gè)特征值漱受;w1络凿,w2,w3分別代表三個(gè)特征值對(duì)應(yīng)的權(quán)重昂羡;b為偏置項(xiàng)絮记;輸出節(jié)點(diǎn)中的z和o分別代表線性變換后的輸出值和非線性變換后的輸出值。

image

\begin{cases}z = x_1*w_1+x_2*w_3+x_3*w_3+b\\o=f(z)\end{cases} \tag{1}

其中映射函數(shù)f為激活函數(shù)虐先,下面列幾個(gè)常見(jiàn)的激活函數(shù):

函數(shù)名 函數(shù)表達(dá)式 導(dǎo)數(shù)
sigmoid f(z)=\dfrac{1}{1+e^{-z}} f(z)[1-f(z)]
tanh f(z)=\dfrac{e^z-e^{-z}}{e^z+e^{-z}} 1-f(z)^2
softmax f(z)=\dfrac{e^{z_i}}{\sum_{j=0}^n e^{z_j}} 經(jīng)常用其構(gòu)成的
損失函數(shù)的導(dǎo)數(shù):
f(z_i)-t(i)~ [1]

神經(jīng)網(wǎng)絡(luò)(Neural Network)

神經(jīng)網(wǎng)絡(luò)基本結(jié)構(gòu)

神經(jīng)網(wǎng)絡(luò)與感知機(jī)類(lèi)似怨愤,但是它的節(jié)點(diǎn)更加復(fù)雜,下圖是一個(gè)含有1層隱藏層的神經(jīng)網(wǎng)絡(luò)蛹批,也是一種最簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò)撰洗,我們可以看到這個(gè)神經(jīng)網(wǎng)絡(luò)的輸入層有2個(gè)節(jié)點(diǎn),隱藏層有3個(gè)節(jié)點(diǎn)腐芍,輸出層有1個(gè)節(jié)點(diǎn)差导。我們可以認(rèn)為神經(jīng)網(wǎng)絡(luò)由多個(gè)感知機(jī)構(gòu)成。我們以下圖所示結(jié)構(gòu)為例猪勇,實(shí)現(xiàn)一個(gè)可以進(jìn)行數(shù)據(jù)分類(lèi)的神經(jīng)網(wǎng)絡(luò)设褐。


image

假設(shè)我們有N個(gè)樣本,對(duì)于每一個(gè)樣本來(lái)說(shuō)泣刹,都有兩個(gè)特征值助析,對(duì)于這樣的每一個(gè)樣本\textbf{x}(x_1,x_2)都滿足公式2,公式中帶小括號(hào)的上標(biāo)代表神經(jīng)網(wǎng)絡(luò)的層數(shù)椅您,w_{ij}為相鄰兩層兩個(gè)節(jié)點(diǎn)之間的權(quán)重系數(shù)外冀,其中的i代表前一層的第i個(gè)節(jié)點(diǎn),j代表后一層的第j個(gè)節(jié)點(diǎn)襟沮。

\begin{cases}z^{(1)}_{1} = x_1*w^{(1)}_{11}+x_2*w^{(1)}_{21}+b^{(1)}_1,~h_1=f(z^{(1)}_{1})\\z^{(1)}_2 = x_1*w^{(1)}_{22}+x_2*w^{(1)}_{22}+b^{(1)}_2,~h_2=f(z^{(1)}_2)\\z^{(1)}_3 = x_1*w^{(1)}_{13}+x_2*w^{(1)}_{23}+b^{(1)}_3 ,~h_3=f(z^1_3)\\z^{(2) }= h_1*w^{(2) }_{1}+ h_2*w^{(2) }_{2}+ h_3*w^{(2) }_{3}+b^{(2)},~o=f(z^{(2)})\end{cases}\tag{2}
我們可以用矩陣形式改寫(xiě)公式2:
\begin{cases}Z_1=X\cdot W_1+B_1\\ H=f(Z_1)\\ Z_2=H\cdot W_2+B_2\\ \hat Y=f(Z_2)\end{cases}\tag{3}

公式2中X_{[N\times2]}為輸入矩陣,B_{1~[N\times3]}為隱藏層偏置矩陣,W_{1~[2\times3]}為輸入層到隱藏層的權(quán)重矩陣开伏,W_{2~[3\times1]}為隱藏層到輸出層的權(quán)重矩陣膀跌,B_{2~[N\times1]}為輸出層偏置矩陣,\hat{Y}_{[N\times1]}為輸出矩陣(結(jié)果預(yù)測(cè)矩陣)固灵,Z_{1}捅伤,H矩陣維度為N\times3Z_{2}矩陣維度為N\times1巫玻。

神經(jīng)網(wǎng)絡(luò)損失函數(shù)

我們這里用改寫(xiě)的方差公式作為神經(jīng)網(wǎng)絡(luò)預(yù)測(cè)分類(lèi)結(jié)果的損失函數(shù)丛忆,正確的分類(lèi)結(jié)果矩陣記為Y_{[N\times1]}
func = \dfrac{1}{2N}*\sum_{i=1}^N{(\hat Y-Y)^2}\tag{4}
根據(jù)梯度下降法,我們需要求損失函數(shù)func的梯度仍秤,梯度下降法的實(shí)現(xiàn)可以看這里熄诡。損失函數(shù)可以表示為func=f(X,W_1,W_2,B_1,B_2)的形式(類(lèi)似地,Z_1=f(X,W_1,B_1)诗力,Z_2=f(Z_1,W_2,B_2))凰浮,由于W_1,W_2,B_1,B_2是我們需要訓(xùn)練的參數(shù),所以我們需要分別求func對(duì)W_1,W_2,B_1,B_2的梯度(這里涉及到矩陣的求導(dǎo)苇本,見(jiàn)附錄)袜茧。

\begin{cases} \dfrac{\partial func}{\partial W_2}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial W_2 } = \left( \dfrac{1}{N}*\sum_{i=1}^N{(\hat Y-Y)}\right)*\left(Z_1^T\cdot f'(Z_1,W_2,B_2) \right)\\ \\ \dfrac{\partial func}{\partial B_2}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial B_2 } = \left( \dfrac{1}{N}*\sum_{i=1}^N{(\hat Y-Y)}\right)* f'(Z_1,W_2,B_2) \\ \\ \dfrac{\partial func}{\partial W_1}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial Z_1 }*\dfrac{\partial Z_1}{\partial W_1 } = \left( \dfrac{1}{N}*\sum_{i=1}^N{(\hat Y-Y)}\right)*\left(f'(Z_1,W_2,B_2)\cdot W_2^T \right)*\left(X^T\cdot f'(X,W_1,B_1) \right)\\ \\ \dfrac{\partial func}{\partial B_1}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial Z_1 }*\dfrac{\partial Z_1}{\partial B_1 } = \left( \dfrac{1}{N}*\sum_{i=1}^N{(\hat Y-Y)}\right)*\left(f'(Z_1,W_2,B_2)\cdot W_2^T \right)*f'(X,W_1,B_1) \\ \end{cases}\tag{5}

根據(jù)梯度下降法,我們?cè)谇笸晏荻纫院蟀暾枰挛覀兊膮?shù)值笛厦,這里以W_1為例:
W_1 =W_1 - \eta*\dfrac{\partial func}{\partial W_1}\tag{6}
由公式6可以看出,W_1的梯度矩陣應(yīng)該與W_1維度相同俺夕,即\frac{\partial func}{\partial Z_2}_{[1\times1]}*\frac{\partial Z_2}{\partial Z_1 }_{[N\times1]\cdot[3\times1]}*\frac{\partial Z_1}{\partial W_1 }_{[2\times N]\cdot[N\times3]}W_{1~[2\times3]}維度相同裳凸,因此N應(yīng)該為1。所以我們?cè)诰幊虝r(shí)應(yīng)該一個(gè)樣本一個(gè)樣本的訓(xùn)練啥么,而不是N個(gè)樣本一起訓(xùn)練登舞。當(dāng)N=1時(shí),公式5可以簡(jiǎn)化為:
\begin{cases} \dfrac{\partial func}{\partial W_2}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial W_2 } = (\hat Y-Y)*\left(Z_1^T* f'(Z_1,W_2,B_2) \right)\\ \\ \dfrac{\partial func}{\partial B_2}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial B_2 } = (\hat Y-Y)* f'(Z_1,W_2,B_2) \\ \\ \dfrac{\partial func}{\partial W_1}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial Z_1 }*\dfrac{\partial Z_1}{\partial W_1 } = (\hat Y-Y)*\left(f'(Z_1,W_2,B_2)* W_2^T \right)*\left(X^T\cdot f'(X,W_1,B_1) \right)\\ \\ \dfrac{\partial func}{\partial B_1}=\dfrac{\partial func}{\partial Z_2}*\dfrac{\partial Z_2}{\partial Z_1 }*\dfrac{\partial Z_1}{\partial B_1 } = (\hat Y-Y)*\left(f'(Z_1,W_2,B_2)* W_2^T \right)*f'(X,W_1,B_1) \\ \end{cases}\tag{7}
按照上述思路進(jìn)行編程悬荣,這里隱藏層激活函數(shù)選擇sigmoid函數(shù)菠秒,輸出層激活函數(shù)選擇tanh函數(shù),得到分類(lèi)結(jié)果的錯(cuò)誤率為0.01~0.06氯迂,當(dāng)隱藏層和輸出層激活函數(shù)都選擇tanh函數(shù)時(shí)践叠,錯(cuò)誤率更低。下圖為錯(cuò)誤率為0.025時(shí)的分類(lèi)結(jié)果圖嚼蚀。我們可以看到圖中有5個(gè)數(shù)據(jù)點(diǎn)分類(lèi)錯(cuò)誤禁灼。

image

局限性

由于我們是一個(gè)樣本一個(gè)樣本訓(xùn)練的,所以我們得到的參數(shù)也是和這些樣本一一對(duì)應(yīng)的轿曙,因此這個(gè)模型無(wú)法畫(huà)出決策邊界弄捕,也無(wú)法預(yù)測(cè)新的數(shù)據(jù)僻孝,預(yù)測(cè)新的數(shù)據(jù)好像是應(yīng)該對(duì)訓(xùn)練好的參數(shù)進(jìn)行插值,但是我看別人沒(méi)有那么做的守谓,可能這樣不大好穿铆。

附錄

神經(jīng)網(wǎng)絡(luò)代碼

# -*- encoding=utf-8 -*-
__Author__ = "stubborn vegeta"

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from matplotlib.colors import ListedColormap

class neuralNetwork(object):
    def __init__(self, X, Y, inputLayer, outputLayer, hiddenLayer=3,learningRate=0.01, epochs=10):
        """
        learningRate:學(xué)習(xí)率
        epochs:訓(xùn)練次數(shù)
        inputLayer:輸入層節(jié)點(diǎn)數(shù)
        hiddenLayer:隱藏層節(jié)點(diǎn)數(shù)
        outputLayer:輸出層節(jié)點(diǎn)數(shù)
        """
        self.learningRate = learningRate
        self.epochs = epochs
        self.inputLayer = inputLayer
        self.hiddenLayer = hiddenLayer
        self.outputLayer = outputLayer
        self.X = X
        self.Y = Y
        self.lenX,_ = np.shape(self.X)
        s=np.random.seed(0)
        # W1:輸入層與隱藏層之間的權(quán)重;W2:隱藏層與輸出層之間的權(quán)重斋荞;B1:隱藏層各節(jié)點(diǎn)的偏置項(xiàng)荞雏;B2:輸出層各節(jié)點(diǎn)的偏置項(xiàng)
        self.W1 = np.array(np.random.random([self.inputLayer, self.hiddenLayer])*0.5)        #2*3
        self.B1 = np.array(np.random.random([self.lenX,self.hiddenLayer])*0.5)               #200*3
        self.W2 = np.array(np.random.random([self.hiddenLayer, self.outputLayer])*0.5)       #3*1
        self.B2 = np.array(np.random.random([self.lenX,self.outputLayer])*0.5)               #200*1

    def activationFunction(self, funcName:str, X):
        """
        激活函數(shù)
        sigmoid: 1/1+e^(-z)
        tanh: [e^z-e^(-z)]/[e^z+e^(-z)]
        softmax: e^zi/sum(e^j)
        """
        switch = {
                "sigmoid": 1/(1+np.exp(-X)),
                "tanh": np.tanh(X), 
                # "softmax": np.exp(X-np.max(X))/np.sum(np.exp(X-np.max(X)), axis=0)
                }
        return switch[funcName]

    def activationFunctionGrad(self, funcName:str, X):
        """
        激活函數(shù)的導(dǎo)數(shù)
        """
        switch = {
                "sigmoid": np.exp(-X)/(1+np.exp(-X))**2,
                "tanh": 1-(np.tanh(X)**2),
                # "softmax": np.exp(X-np.max(X))/np.sum(np.exp(X-np.max(X)), axis=0)
                }
        return switch[funcName]

    def train(self, funcNameH:str, funcNameO:str):
        """
        funcNameH: 隱藏層激活函數(shù)
        funcNameO: 輸出層激活函數(shù)
        """
        for i in range(0,self.epochs):
            j = np.random.randint(self.lenX)
            x = np.array([self.X[j]])
            y = np.array([self.Y[j]])
            b1 = np.array([self.B1[j]])
            b2 = np.array([self.B2[j]])
            # 前向傳播
            zHidden = x.dot(self.W1)+b1
            z1 = self.activationFunction(funcNameH, zHidden)  #1*3
            zOutput = z1.dot(self.W2)+b2
            z2 = self.activationFunction(funcNameO, zOutput)  #1*1 

            # 反向傳播
            dW2 = (z2-y)*(z1.T*self.activationFunctionGrad(funcNameO,zOutput))
            db2 = (z2-y)*self.activationFunctionGrad(funcNameO,zOutput)
            dW1 = (z2-y)*(self.activationFunctionGrad(funcNameO,zOutput)*self.W2.T)*(x.T.dot(self.activationFunctionGrad(funcNameH,zHidden)))
            db1 = (z2-y)*(self.activationFunctionGrad(funcNameO,zOutput)*self.W2.T)*self.activationFunctionGrad(funcNameH,zHidden)

            #更新參數(shù)
            self.W2 -= self.learningRate*dW2
            self.B2[j] -= self.learningRate*db2[0]
            self.W1 -= self.learningRate*dW1
            self.B1[j] -= self.learningRate*db1[0]
        return 0

    def predict(self, xNewData, funcNameH:str, funcNameO:str):
        X = xNewData                                         #200*2
        N,_ = np.shape(X)
        yPredict = []
        for j in range(0,N):    
            x = np.array([X[j]])
            b1 = np.array([self.B1[j]])
            b2 = np.array([self.B2[j]])
            # 前向傳播
            zHidden = x.dot(self.W1)+b1
            z1 = self.activationFunction(funcNameH, zHidden)  #1*3
            zOutput = z1.dot(self.W2)+b2
            z2 = self.activationFunction(funcNameO, zOutput)  #1*1 
            z2 = 1 if z2>0.5 else 0
            yPredict.append(z2)
        return yPredict,N


if __name__ == "__main__":
    X,Y = datasets.make_moons(200, noise=0.15)
    neural_network = neuralNetwork (X=X, Y=Y, learningRate=0.2, epochs=1000, inputLayer=2, hiddenLayer=3, outputLayer=1)
    funcNameH = "sigmoid"
    funcNameO = "tanh"
    neural_network.train(funcNameH=funcNameH,funcNameO=funcNameO)       
    yPredict,N = neural_network.predict(xNewData=X,funcNameH=funcNameH,funcNameO=funcNameO)
    print("錯(cuò)誤率:", sum((Y-yPredict)**2)/N)
    colormap = ListedColormap(['royalblue','forestgreen'])              # 用colormap中的顏色表示分類(lèi)結(jié)果
    plt.subplot(1,2,1)
    plt.scatter(X[:,0],X[:,1],s=40, c=Y, cmap=colormap)
    plt.xlabel("x")
    plt.ylabel("y")
    plt.title("Standard data")
    plt.subplot(1,2,2)
    plt.scatter(X[:,0],X[:,1],s=40, c=yPredict, cmap=colormap)
    plt.xlabel("x")
    plt.ylabel("y")
    plt.title("Predicted data")
    plt.show()

感知機(jī)結(jié)構(gòu)圖代碼

digraph network{
edge[fontname="Monaco"]
node[fontname="Monaco"]
rankdir=LR
b[shape=plaintext] 
x1->"z|o"[label=w1]
x2->"z|o"[label=w2]
x3->"z|o"[label=w3]
b->"z|o"
{rank=same;b;"z|o"}
}

神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)圖代碼

digraph network{
    edge[fontname="Monaco"]
    node[fontname="Monaco",shape=circle]
    rankdir=LR

    subgraph cluster_1{
        color = white
        fontname="Monaco"
        x1,x2;
        label = "Input Layer";
    }
    subgraph cluster_2{
        color = white
        fontname="Monaco"
        h3,h1,h2;
        label = "Hidden Layer";
    }
    subgraph cluster_3{
        // rank=same
        color = white
        fontname="Monaco"
        o;
        label = "Output Layer";
    }
    x1->h1
    x1->h2
    x1->h3
    x2->h1
    x2->h2
    x2->h3
    rank=same;h1;h2;h3
    h1->o
    h2->o
    h3->o       
}

矩陣求導(dǎo)公式

Y=A\cdot X~\Longrightarrow~ \dfrac{dY}{dX}=A^T Y=X\cdot A~\Longrightarrow~ \dfrac{dY}{dX}=A^T
Y=X^T\cdot A~\Longrightarrow~ \dfrac{dY}{dX}=A Y=A\cdot X~\Longrightarrow~ \dfrac{dY}{dX^T}=A
\dfrac{dX^T}{dX}=I \dfrac{dX}{dX^T}=I

  1. Softmax函數(shù)與交叉熵 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市平酿,隨后出現(xiàn)的幾起案子凤优,更是在濱河造成了極大的恐慌,老刑警劉巖蜈彼,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筑辨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡柳刮,警方通過(guò)查閱死者的電腦和手機(jī)挖垛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秉颗,“玉大人痢毒,你說(shuō)我怎么就攤上這事〔仙” “怎么了哪替?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)菇怀。 經(jīng)常有香客問(wèn)我凭舶,道長(zhǎng),這世上最難降的妖魔是什么爱沟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任帅霜,我火速辦了婚禮,結(jié)果婚禮上呼伸,老公的妹妹穿的比我還像新娘身冀。我一直安慰自己,他們只是感情好括享,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布搂根。 她就那樣靜靜地躺著,像睡著了一般铃辖。 火紅的嫁衣襯著肌膚如雪剩愧。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,682評(píng)論 1 312
  • 那天娇斩,我揣著相機(jī)與錄音仁卷,去河邊找鬼穴翩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锦积,可吹牛的內(nèi)容都是我干的藏否。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼充包,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了遥椿?” 一聲冷哼從身側(cè)響起基矮,我...
    開(kāi)封第一講書(shū)人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冠场,沒(méi)想到半個(gè)月后家浇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碴裙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年钢悲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舔株。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莺琳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出载慈,到底是詐尸還是另有隱情惭等,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布办铡,位于F島的核電站辞做,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏寡具。R本人自食惡果不足惜秤茅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望童叠。 院中可真熱鬧框喳,春花似錦、人聲如沸拯钻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粪般。三九已至拼余,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間亩歹,已是汗流浹背匙监。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工凡橱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人亭姥。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓稼钩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親达罗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坝撑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容