機器學習算法與Python實踐之邏輯迴歸(Logistic Regression)

機器學習算法與Python實踐之(七)邏輯迴歸(Logistic Regression)

[email protected]

http://blog.csdn.net/zouxy09

   機器學習算法與Python實踐這個系列主要是參考《機器學習實戰》這本書。因爲自己想學習Python,然後也想對一些機器學習算法加深下了解,所以就想通過Python來實現幾個比較常用的機器學習算法。恰好遇見這本同樣定位的書籍,所以就參考這本書的過程來學習了。

   這節學習的是邏輯迴歸(Logistic Regression),也算進入了比較正統的機器學習算法。啥叫正統呢?我概念裏面機器學習算法一般是這樣一個步驟:

1)對於一個問題,我們用數學語言來描述它,然後建立一個模型,例如迴歸模型或者分類模型等來描述這個問題;

2)通過最大似然、最大後驗概率或者最小化分類誤差等等建立模型的代價函數,也就是一個最優化問題。找到最優化問題的解,也就是能擬合我們的數據的最好的模型參數;

3)然後我們需要求解這個代價函數,找到最優解。這求解也就分很多種情況了:

  a)如果這個優化函數存在解析解。例如我們求最值一般是對代價函數求導,找到導數爲0的點,也就是最大值或者最小值的地方了。如果代價函數能簡單求導,並且求導後爲0的式子存在解析解,那麼我們就可以直接得到最優的參數了。

  b)如果式子很難求導,例如函數裏面存在隱含的變量或者變量相互間存在耦合,也就互相依賴的情況。或者求導後式子得不到解釋解,例如未知參數的個數大於已知方程組的個數等。這時候我們就需要藉助迭代算法來一步一步找到最有解了。迭代是個很神奇的東西,它將遠大的目標(也就是找到最優的解,例如爬上山頂)記在心上,然後給自己定個短期目標(也就是每走一步,就離遠大的目標更近一點),腳踏實地,心無旁貸,像個蝸牛一樣,一步一步往上爬,支撐它的唯一信念是:只要我每一步都爬高一點,那麼積跬步,肯定能達到自己人生的巔峯,盡享山登絕頂我爲峯的豪邁與忘我。

   另外需要考慮的情況是,如果代價函數是凸函數,那麼就存在全局最優解,方圓五百里就只有一個山峯,那命中註定了,它就是你要找的唯一了。但如果是非凸的,那麼就會有很多局部最優的解,有一望無際的山峯,人的視野是偉大的也是渺小的,你不知道哪個山峯纔是最高的,可能你會被命運作弄,很無辜的陷入一個局部最優裏面,坐井觀天,以爲自己找到的就是最好的。沒想到山外有山,人外有人,光芒總在未知的遠處默默綻放。但也許命運眷戀善良的你,帶給你的總是最好的歸宿。也有很多不信命的人,覺得人定勝天的人,誓要找到最好的,否則不會罷休,永不向命運妥協,除非自己有一天累了,倒下了,也要靠剩下的一口氣,邁出一口氣能支撐的路程。好悲涼啊……哈哈。

    呃,不知道扯那去了,也不知道自己說的有沒有錯,有錯的話請大家不吝指正。那下面就進入正題吧。正如上面所述,邏輯迴歸就是這樣的一個過程:面對一個迴歸或者分類問題,建立代價函數,然後通過優化方法迭代求解出最優的模型參數,然後測試驗證我們這個求解的模型的好壞,冥冥人海,滾滾紅塵,我們是否找到了最適合的那個她。

一、邏輯迴歸(LogisticRegression)

   Logistic regression (邏輯迴歸)是當前業界比較常用的機器學習方法,用於估計某種事物的可能性。之前在經典之作《數學之美》中也看到了它用於廣告預測,也就是根據某廣告被用戶點擊的可能性,把最可能被用戶點擊的廣告擺在用戶能看到的地方,然後叫他“你點我啊!”用戶點了,你就有錢收了。這就是爲什麼我們的電腦現在廣告氾濫的原因了。

   還有類似的某用戶購買某商品的可能性,某病人患有某種疾病的可能性啊等等。這個世界是隨機的(當然了,人爲的確定性系統除外,但也有可能有噪聲或產生錯誤的結果,只是這個錯誤發生的可能性太小了,小到千萬年不遇,小到忽略不計而已),所以萬物的發生都可以用可能性或者機率(Odds)來表達。“機率”指的是某事物發生的可能性與不發生的可能性的比值。

   Logistic regression可以用來回歸,也可以用來分類,主要是二分類。還記得上幾節講的支持向量機SVM嗎?它就是個二分類的例如,它可以將兩個不同類別的樣本給分開,思想是找到最能區分它們的那個分類超平面。但當你給一個新的樣本給它,它能夠給你的只有一個答案,你這個樣本是正類還是負類。例如你問SVM,某個女生是否喜歡你,它只會回答你喜歡或者不喜歡。這對我們來說,顯得太粗魯了,要不希望,要不絕望,這都不利於身心健康。那如果它可以告訴我,她很喜歡、有一點喜歡、不怎麼喜歡或者一點都不喜歡,你想都不用想了等等,告訴你她有49%的機率喜歡你,總比直接說她不喜歡你,來得溫柔。而且還提供了額外的信息,她來到你的身邊你有多少希望,你得再努力多少倍,知己知彼百戰百勝,哈哈。Logistic regression就是這麼溫柔的,它給我們提供的就是你的這個樣本屬於正類的可能性是多少。

   還得來點數學。(更多的理解,請參閱參考文獻)假設我們的樣本是{x, y},y是0或者1,表示正類或者負類,x是我們的m維的樣本特徵向量。那麼這個樣本x屬於正類,也就是y=1的“概率”可以通過下面的邏輯函數來表示:



   這裏θ是模型參數,也就是迴歸係數,σ是sigmoid函數。實際上這個函數是由下面的對數機率(也就是x屬於正類的可能性和負類的可能性的比值的對數)變換得到的:



   換句話說,y也就是我們關係的變量,例如她喜不喜歡你,與多個自變量(因素)有關,例如你人品怎樣、車子是兩個輪的還是四個輪的、長得勝過潘安還是和犀利哥有得一拼、有千尺豪宅還是三寸茅廬等等,我們把這些因素表示爲x1, x2,…, xm。那這個女的怎樣考量這些因素呢?最快的方式就是把這些因素的得分都加起來,最後得到的和越大,就表示越喜歡。但每個人心裏其實都有一杆稱,每個人考慮的因素不同,蘿蔔青菜,各有所愛嘛。例如這個女生更看中你的人品,人品的權值是0.6,不看重你有沒有錢,沒錢了一起努力奮鬥,那麼有沒有錢的權值是0.001等等。我們將這些對應x1, x2,…, xm的權值叫做迴歸係數,表達爲θ1, θ2,…, θm。他們的加權和就是你的總得分了。請選擇你的心儀男生,非誠勿擾!哈哈。

   所以說上面的logistic迴歸就是一個線性分類模型,它與線性迴歸的不同點在於:爲了將線性迴歸輸出的很大範圍的數,例如從負無窮到正無窮,壓縮到0和1之間,這樣的輸出值表達爲“可能性”才能說服廣大民衆。當然了,把大值壓縮到這個範圍還有個很好的好處,就是可以消除特別冒尖的變量的影響(不知道理解的是否正確)。而實現這個偉大的功能其實就只需要平凡一舉,也就是在輸出加一個logistic函數。另外,對於二分類來說,可以簡單的認爲:如果樣本x屬於正類的概率大於0.5,那麼就判定它是正類,否則就是負類。實際上,SVM的類概率就是樣本到邊界的距離,這個活實際上就讓logistic regression給幹了。



   所以說,LogisticRegression 就是一個被logistic方程歸一化後的線性迴歸,僅此而已。

好了,關於LR的八卦就聊到這。歸入到正統的機器學習框架下,模型選好了,只是模型的參數θ還是未知的,我們需要用我們收集到的數據來訓練求解得到它。那我們下一步要做的事情就是建立代價函數了。

   LogisticRegression最基本的學習算法是最大似然。啥叫最大似然,可以看看我的另一篇博文“從最大似然到EM算法淺解”。

   假設我們有n個獨立的訓練樣本{(x1, y1) ,(x2, y2),…, (xn, yn)},y={0, 1}。那每一個觀察到的樣本(xi, yi)出現的概率是:



   上面爲什麼是這樣呢?當y=1的時候,後面那一項是不是沒有了,那就只剩下x屬於1類的概率,當y=0的時候,第一項是不是沒有了,那就只剩下後面那個x屬於0的概率(1減去x屬於1的概率)。所以不管y是0還是1,上面得到的數,都是(x, y)出現的概率。那我們的整個樣本集,也就是n個獨立的樣本出現的似然函數爲(因爲每個樣本都是獨立的,所以n個樣本出現的概率就是他們各自出現的概率相乘):



   那最大似然法就是求模型中使得似然函數最大的係數取值θ*。這個最大似然就是我們的代價函數(cost function)了。

   OK,那代價函數有了,我們下一步要做的就是優化求解了。我們先嚐試對上面的代價函數求導,看導數爲0的時候可不可以解出來,也就是有沒有解析解,有這個解的時候,就皆大歡喜了,一步到位。如果沒有就需要通過迭代了,耗時耗力。

   我們先變換下L(θ):取自然對數,然後化簡(不要看到一堆公式就害怕哦,很簡單的哦,只需要耐心一點點,自己動手推推就知道了。注:有xi的時候,表示它是第i個樣本,下面沒有做區分了,相信你的眼睛是雪亮的),得到:



   這時候,用L(θ)對θ求導,得到:



   然後我們令該導數爲0,你會很失望的發現,它無法解析求解。不信你就去嘗試一下。所以沒辦法了,只能藉助高大上的迭代來搞定了。這裏選用了經典的梯度下降算法。

二、優化求解

2.1、梯度下降(gradient descent)

    Gradient descent 又叫 steepest descent,是利用一階的梯度信息找到函數局部最優解的一種方法,也是機器學習裏面最簡單最常用的一種優化方法。它的思想很簡單,和我開篇說的那樣,要找最小值,我只需要每一步都往下走(也就是每一步都可以讓代價函數小一點),然後不斷的走,那肯定能走到最小值的地方,例如下圖所示:



  但,我同時也需要更快的到達最小值啊,怎麼辦呢?我們需要每一步都找下坡最快的地方,也就是每一步我走某個方向,都比走其他方法,要離最小值更近。而這個下坡最快的方向,就是梯度的負方向了。

對logistic Regression來說,梯度下降算法新鮮出爐,如下:

  其中,參數α叫學習率,就是每一步走多遠,這個參數蠻關鍵的。如果設置的太多,那麼很容易就在最優值附加徘徊,因爲你步伐太大了。例如要從廣州到上海,但是你的一步的距離就是廣州到北京那麼遠,沒有半步的說法,自己能邁那麼大步,是幸運呢?還是不幸呢?事物總有兩面性嘛,它帶來的好處是能很快的從遠離最優值的地方回到最優值附近,只是在最優值附近的時候,它有心無力了。但如果設置的太小,那收斂速度就太慢了,向蝸牛一樣,雖然會落在最優的點,但是這速度如果是猴年馬月,我們也沒這耐心啊。所以有的改進就是在這個學習率這個地方下刀子的。我開始迭代是,學習率大,慢慢的接近最優值的時候,我的學習率變小就可以了。所謂採兩者之精華啊!這個優化具體見2.3 。

   梯度下降算法的僞代碼如下:
#

初始化迴歸係數爲1

重複下面步驟直到收斂{

    計算整個數據集的梯度

    使用alpha x gradient來更新迴歸係數

}

返回迴歸係數值

#
  注:因爲本文中是求解的Logit迴歸的代價函數是似然函數,需要最大化似然函數。所以我們要用的是梯度上升算法。但因爲其和梯度下降的原理是一樣的,只是一個是找最大值,一個是找最小值。找最大值的方向就是梯度的方向,最小值的方向就是梯度的負方向。不影響我們的說明,所以當時自己就忘了改過來了,謝謝評論下面@wxltt的指出。另外,最大似然可以通過取負對數,轉化爲求最小值。代碼裏面的註釋也是有誤的,寫的代碼是梯度上升,註銷成了梯度下降,對大家造成的不便,希望大家海涵。

2.2、隨機梯度下降SGD (stochastic gradient descent)

  梯度下降算法在每次更新迴歸係數的時候都需要遍歷整個數據集(計算整個數據集的迴歸誤差),該方法對小數據集尚可。但當遇到有數十億樣本和成千上萬的特徵時,就有點力不從心了,它的計算複雜度太高。改進的方法是一次僅用一個樣本點(的迴歸誤差)來更新迴歸係數。這個方法叫隨機梯度下降算法。由於可以在新的樣本到來的時候對分類器進行增量的更新(假設我們已經在數據庫A上訓練好一個分類器h了,那新來一個樣本x。對非增量學習算法來說,我們需要把x和數據庫A混在一起,組成新的數據庫B,再重新訓練新的分類器。但對增量學習算法,我們只需要用新樣本x來更新已有分類器h的參數即可),所以它屬於在線學習算法。與在線學習相對應,一次處理整個數據集的叫“批處理”。

    隨機梯度下降算法的僞代碼如下:
#

初始化迴歸係數爲1

重複下面步驟直到收斂{

    對數據集中每個樣本

           計算該樣本的梯度

            使用alpha xgradient來更新迴歸係數

}

返回迴歸係數值

#

2.3、改進的隨機梯度下降

  評價一個優化算法的優劣主要是看它是否收斂,也就是說參數是否達到穩定值,是否還會不斷的變化?收斂速度是否快?



   上圖展示了隨機梯度下降算法在200次迭代中(請先看第三和第四節再回來看這裏。我們的數據庫有100個二維樣本,每個樣本都對係數調整一次,所以共有200*100=20000次調整)三個迴歸係數的變化過程。其中係數X2經過50次迭代就達到了穩定值。但係數X1和X0到100次迭代後穩定。而且可恨的是係數X1和X2還在很調皮的週期波動,迭代次數很大了,心還停不下來。產生這個現象的原因是存在一些無法正確分類的樣本點,也就是我們的數據集並非線性可分,但我們的logistic regression是線性分類模型,對非線性可分情況無能爲力。然而我們的優化程序並沒能意識到這些不正常的樣本點,還一視同仁的對待,調整係數去減少對這些樣本的分類誤差,從而導致了在每次迭代時引發係數的劇烈改變。對我們來說,我們期待算法能避免來回波動,從而快速穩定和收斂到某個值。

   對隨機梯度下降算法,我們做兩處改進來避免上述的波動問題:

1)在每次迭代時,調整更新步長alpha的值。隨着迭代的進行,alpha越來越小,這會緩解係數的高頻波動(也就是每次迭代係數改變得太大,跳的跨度太大)。當然了,爲了避免alpha隨着迭代不斷減小到接近於0(這時候,係數幾乎沒有調整,那麼迭代也沒有意義了),我們約束alpha一定大於一個稍微大點的常數項,具體見代碼。

2)每次迭代,改變樣本的優化順序。也就是隨機選擇樣本來更新迴歸係數。這樣做可以減少週期性的波動,因爲樣本順序的改變,使得每次迭代不再形成週期性。

   改進的隨機梯度下降算法的僞代碼如下:
#

初始化迴歸係數爲1

重複下面步驟直到收斂{

   對隨機遍歷的數據集中的每個樣本

          隨着迭代的逐漸進行,減小alpha的值

          計算該樣本的梯度

          使用alpha x gradient來更新迴歸係數

}

返回迴歸係數值

#
   比較原始的隨機梯度下降和改進後的梯度下降,可以看到兩點不同:

1)係數不再出現週期性波動。2)係數可以很快的穩定下來,也就是快速收斂。這裏只迭代了20次就收斂了。而上面的隨機梯度下降需要迭代200次才能穩定。

三、Python實現

  我使用的Python是2.7.5版本的。附加的庫有Numpy和Matplotlib。具體的安裝和配置見前面的博文。在代碼中已經有了比較詳細的註釋了。不知道有沒有錯誤的地方,如果有,還望大家指正(每次的運行結果都有可能不同)。裏面我寫了個可視化結果的函數,但只能在二維的數據上面使用。直接貼代碼:

logRegression.py

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片

###########################################

logRegression: Logistic Regression

Author : zouxy

Date : 2014-03-02

HomePage : http://blog.csdn.net/zouxy09

Email : [email protected]

###########################################

from numpy import *
import matplotlib.pyplot as plt
import time

calculate the sigmoid function

def sigmoid(inX):
return 1.0 / (1 + exp(-inX))

train a logistic regression model using some optional optimize algorithm

input: train_x is a mat datatype, each row stands for one sample

train_y is mat datatype too, each row is the corresponding label

opts is optimize option include step and maximum number of iterations

def trainLogRegres(train_x, train_y, opts):
# calculate training time
startTime = time.time()

numSamples, numFeatures = shape(train_x)  
alpha = opts['alpha']; maxIter = opts['maxIter']  
weights = ones((numFeatures, 1))  

# optimize through gradient descent algorilthm  
for k in range(maxIter):  
    if opts['optimizeType'] == 'gradDescent': # gradient descent algorilthm  
        output = sigmoid(train_x * weights)  
        error = train_y - output  
        weights = weights + alpha * train_x.transpose() * error  
    elif opts['optimizeType'] == 'stocGradDescent': # stochastic gradient descent  
        for i in range(numSamples):  
            output = sigmoid(train_x[i, :] * weights)  
            error = train_y[i, 0] - output  
            weights = weights + alpha * train_x[i, :].transpose() * error  
    elif opts['optimizeType'] == 'smoothStocGradDescent': # smooth stochastic gradient descent  
        # randomly select samples to optimize for reducing cycle fluctuations   
        dataIndex = range(numSamples)  
        for i in range(numSamples):  
            alpha = 4.0 / (1.0 + k + i) + 0.01  
            randIndex = int(random.uniform(0, len(dataIndex)))  
            output = sigmoid(train_x[randIndex, :] * weights)  
            error = train_y[randIndex, 0] - output  
            weights = weights + alpha * train_x[randIndex, :].transpose() * error  
            del(dataIndex[randIndex]) # during one interation, delete the optimized sample  
    else:  
        raise NameError('Not support optimize method type!')  


print 'Congratulations, training complete! Took %fs!' % (time.time() - startTime)  
return weights  

test your trained Logistic Regression model given test set

def testLogRegres(weights, test_x, test_y):
numSamples, numFeatures = shape(test_x)
matchCount = 0
for i in xrange(numSamples):
predict = sigmoid(test_x[i, :] * weights)[0, 0] > 0.5
if predict == bool(test_y[i, 0]):
matchCount += 1
accuracy = float(matchCount) / numSamples
return accuracy

show your trained logistic regression model only available with 2-D data

def showLogRegres(weights, train_x, train_y):
# notice: train_x and train_y is mat datatype
numSamples, numFeatures = shape(train_x)
if numFeatures != 3:
print “Sorry! I can not draw because the dimension of your data is not 2!”
return 1

# draw all samples  
for i in xrange(numSamples):  
    if int(train_y[i, 0]) == 0:  
        plt.plot(train_x[i, 1], train_x[i, 2], 'or')  
    elif int(train_y[i, 0]) == 1:  
        plt.plot(train_x[i, 1], train_x[i, 2], 'ob')  

# draw the classify line  
min_x = min(train_x[:, 1])[0, 0]  
max_x = max(train_x[:, 1])[0, 0]  
weights = weights.getA()  # convert mat to array  
y_min_x = float(-weights[0] - weights[1] * min_x) / weights[2]  
y_max_x = float(-weights[0] - weights[1] * max_x) / weights[2]  
plt.plot([min_x, max_x], [y_min_x, y_max_x], '-g')  
plt.xlabel('X1'); plt.ylabel('X2')  
plt.show()  

四、測試結果

    測試代碼:

test_logRegression.py

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片

###########################################

logRegression: Logistic Regression

Author : zouxy

Date : 2014-03-02

HomePage : http://blog.csdn.net/zouxy09

Email : [email protected]

###########################################

from numpy import *
import matplotlib.pyplot as plt
import time

def loadData():
train_x = []
train_y = []
fileIn = open(‘E:/Python/Machine Learning in Action/testSet.txt’)
for line in fileIn.readlines():
lineArr = line.strip().split()
train_x.append([1.0, float(lineArr[0]), float(lineArr[1])])
train_y.append(float(lineArr[2]))
return mat(train_x), mat(train_y).transpose()

step 1: load data

print “step 1: load data…”
train_x, train_y = loadData()
test_x = train_x; test_y = train_y

step 2: training…

print “step 2: training…”
opts = {‘alpha’: 0.01, ‘maxIter’: 20, ‘optimizeType’: ‘smoothStocGradDescent’}
optimalWeights = trainLogRegres(train_x, train_y, opts)

step 3: testing

print “step 3: testing…”
accuracy = testLogRegres(optimalWeights, test_x, test_y)

step 4: show the result

print “step 4: show the result…”
print ‘The classify accuracy is: %.3f%%’ % (accuracy * 100)
showLogRegres(optimalWeights, train_x, train_y)

    測試數據是二維的,共100個樣本。有2個類。如下:

testSet.txt

[python] view plaincopy在CODE上查看代碼片派生到我的代碼片
-0.017612 14.053064 0
-1.395634 4.662541 1
-0.752157 6.538620 0
-1.322371 7.152853 0
0.423363 11.054677 0
0.406704 7.067335 1
0.667394 12.741452 0
-2.460150 6.866805 1
0.569411 9.548755 0
-0.026632 10.427743 0
0.850433 6.920334 1
1.347183 13.175500 0
1.176813 3.167020 1
-1.781871 9.097953 0
-0.566606 5.749003 1
0.931635 1.589505 1
-0.024205 6.151823 1
-0.036453 2.690988 1
-0.196949 0.444165 1
1.014459 5.754399 1
1.985298 3.230619 1
-1.693453 -0.557540 1
-0.576525 11.778922 0
-0.346811 -1.678730 1
-2.124484 2.672471 1
1.217916 9.597015 0
-0.733928 9.098687 0
-3.642001 -1.618087 1
0.315985 3.523953 1
1.416614 9.619232 0
-0.386323 3.989286 1
0.556921 8.294984 1
1.224863 11.587360 0
-1.347803 -2.406051 1
1.196604 4.951851 1
0.275221 9.543647 0
0.470575 9.332488 0
-1.889567 9.542662 0
-1.527893 12.150579 0
-1.185247 11.309318 0
-0.445678 3.297303 1
1.042222 6.105155 1
-0.618787 10.320986 0
1.152083 0.548467 1
0.828534 2.676045 1
-1.237728 10.549033 0
-0.683565 -2.166125 1
0.229456 5.921938 1
-0.959885 11.555336 0
0.492911 10.993324 0
0.184992 8.721488 0
-0.355715 10.325976 0
-0.397822 8.058397 0
0.824839 13.730343 0
1.507278 5.027866 1
0.099671 6.835839 1
-0.344008 10.717485 0
1.785928 7.718645 1
-0.918801 11.560217 0
-0.364009 4.747300 1
-0.841722 4.119083 1
0.490426 1.960539 1
-0.007194 9.075792 0
0.356107 12.447863 0
0.342578 12.281162 0
-0.810823 -1.466018 1
2.530777 6.476801 1
1.296683 11.607559 0
0.475487 12.040035 0
-0.783277 11.009725 0
0.074798 11.023650 0
-1.337472 0.468339 1
-0.102781 13.763651 0
-0.147324 2.874846 1
0.518389 9.887035 0
1.015399 7.571882 0
-1.658086 -0.027255 1
1.319944 2.171228 1
2.056216 5.019981 1
-0.851633 4.375691 1
-1.510047 6.061992 0
-1.076637 -3.181888 1
1.821096 10.283990 0
3.010150 8.401766 1
-1.099458 1.688274 1
-0.834872 -1.733869 1
-0.846637 3.849075 1
1.400102 12.628781 0
1.752842 5.468166 1
0.078557 0.059736 1
0.089392 -0.715300 1
1.825662 12.693808 0
0.197445 9.744638 0
0.126117 0.922311 1
-0.679797 1.220530 1
0.677983 2.556666 1
0.761349 10.693862 0
-2.168791 0.143632 1
1.388610 9.341997 0
0.317029 14.739025 0
訓練結果:

   (a)梯度下降算法迭代500次。(b)隨機梯度下降算法迭代200次。(c)改進的隨機梯度下降算法迭代20次。(d)改進的隨機梯度下降算法迭代200次。

五、參考文獻

[1] Logisticregression (邏輯迴歸) 概述

[2] LogisticRegression 之基礎知識準備

[3] LogisticRegression

發佈了8 篇原創文章 · 獲贊 18 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章