Python機器學習庫SKLearn:數據集轉換之特徵提取

特徵提取:http://scikit-learn.org/stable/modules/feature_extraction.html#feature-extraction

sklearn.feature_extraction模塊可以用於從由諸如文本和圖像的格式組成的數據集中提取機器學習算法支持的格式的特徵。

注意:特徵提取與特徵選擇非常不同:前者包括將任意數據(如文本或圖像)轉換爲可用於機器學習的數值特徵。 後者是應用於這些特徵的機器學習技術。

4.2.1  從詞典中加載特徵
類DictVectorizer可用於將表示爲標準Python dict對象列表的要素數組轉換爲scikit-learn估計量使用的NumPy / SciPy表示。雖然處理不是特別快,但是Python的dict具有使用方便,除了值的優勢外還有稀疏(沒有功能不需要存儲)和存儲特徵名稱。
DictVectorizer爲分類(也稱爲標稱,離散)特徵實現所謂的one-of-K或“one-hot(獨熱)”編碼。 分類特徵是“屬性值”對,其中值被限制到無序的離散可能性的列表(例如主題標識符,對象的類型,標籤,名稱...)。
在下面,“city”是分類屬性,而“temperature”是傳統的數字特徵:

measurements = [
    {'city': 'Dubai', 'temperature': 33.},
     {'city': 'London', 'temperature': 12.},
     {'city': 'San Fransisco', 'temperature': 18.},
 ]

from sklearn.feature_extraction import DictVectorizer
vec = DictVectorizer()

vec.fit_transform(measurements).toarray()
"""
輸出:
array([[  1.,   0.,   0.,  33.],
       [  0.,   1.,   0.,  12.],
       [  0.,   0.,   1.,  18.]])
"""
vec.get_feature_names()
"""
輸出:
['city=Dubai', 'city=London', 'city=San Fransisco', 'temperature']
"""

DictVectorizer也是自然語言處理模型中的訓練序列分類器的有用的表示變換,其通常通過提取圍繞感興趣的特定詞的特徵窗口來工作。例如,假設我們具有提取我們想要用作訓練序列分類器(例如塊)的互補標籤的部分語音(PoS)標籤的第一算法。 以下dict可以是在'貓坐在席子上'的句子“sat”周圍提取的這樣一個窗口。

pos_window = [
    {
       'word-2': 'the',
        'pos-2': 'DT',
        'word-1': 'cat',
        'pos-1': 'NN',
         'word+1': 'on',
         'pos+1': 'PP',
    },
     # 在實際應用中,將提取許多這樣的字典
]

該描述可以被矢量化爲適於饋送到分類器中的稀疏二維矩陣(可能在被管道化到文本之後。用於歸一化的TfidfTransformer):

vec = DictVectorizer()
pos_vectorized = vec.fit_transform(pos_window)
pos_vectorized
"""
輸出:
<1x6 sparse matrix of type '<class 'numpy.float64'>'
	with 6 stored elements in Compressed Sparse Row format>
"""
pos_vectorized.toarray()
"""
輸出:
array([[ 1.,  1.,  1.,  1.,  1.,  1.]])
"""
vec.get_feature_names()
"""
輸出:
['pos+1=PP', 'pos-1=NN', 'pos-2=DT', 'word+1=on', 'word-1=cat', 'word-2=the']
"""
你可以想象,如果一個文檔的每個單詞提取這樣的上下文,所得到的矩陣將非常寬(許多個獨熱特徵),大多數的零需要花費很多的內存和時間。 爲了使得到的數據結構能夠適應內存,DictVectorizer類默認使用scipy.sparse矩陣,而不是numpy.ndarray。

4.2.2 特徵散列
FeatureHasher類是一個高速,低內存的向量化器,使用一種稱爲特徵散列或“散列法”的技術。代替在訓練中構建訓練中遇到的特徵的哈希表,如向量化所做的那樣,FeatureHasher的實例將哈希函數應用於特徵以直接確定其在樣本矩陣中的列索引。結果是以犧牲可檢查性爲代價,提高速度和減少存儲器使用;哈希不記得輸入要素的樣子,沒有inverse_transform方法。
由於散列函數可能導致(不相關的)特徵之間的衝突,使用帶符號的散列函數,並且散列值的符號確定存儲在特徵的輸出矩陣中的值的符號。這樣,衝突可能抵消而不是累積誤差,並且任何輸出特徵的值的預期平均值爲零。如果non_negative = True傳遞給構造函數,則採用絕對值。這撤消了一些衝突處理,但允許輸出傳遞到估計器,如sklearn.naive_bayes.MultinomialNB或sklearn.feature_selection.chi2特徵選擇器,期望非負輸入。

FeatureHasher接受映射(如Python的dict及其在collections模塊中的變體),(feature,value)對或字符串,具體取決於構造函數參數input_type。 映射被視爲(feature,value)對的列表,而單個字符串的隱式值爲1,因此['feat1','feat2','feat3']被解釋爲[('feat1',1), 'feat2',1),('feat3',1)]。 如果單個特徵在樣本中出現多次,則相關值將被求和(因此('feat',2)和('feat',3.5)變爲('feat',5.5))。 FeatureHasher的輸出總是CSR格式中的scipy.sparse矩陣。
在文檔分類中可以使用特徵散列,但是與text.CountVectorizer不同,FeatureHasher不執行字分割或除Unicode到UTF-8編碼之外的任何其他預處理; 對於組合的tokenizer / hasher,請參閱下面的散列技巧的大文本語料庫的矢量化。

作爲示例,考慮需要從(token,part_of_speech)對提取的特徵的詞級自然語言處理任務。 可以使用Python生成器函數來提取特徵:

def token_features(token, part_of_speech):
    if token.isdigit():
        yield "numeric"
    else:
        yield "token={}".format(token.lower())
        yield "token,pos={},{}".format(token, part_of_speech)
    if token[0].isupper():
        yield "uppercase_initial"
    if token.isupper():
        yield "all_uppercase"
    yield "pos={}".format(part_of_speech)
    
#然後,要饋送到FeatureHasher.transform的raw_X可以使用以下來構造:
raw_X = (token_features(tok, pos_tagger(tok)) for tok in corpus)

#並且饋送到具有以下的散列:
hasher = FeatureHasher(input_type='string')
X = hasher.transform(raw_X)

得到一個scipy.sparse矩陣X.
注意使用生成器理解,其將惰性引入到特徵提取中:令牌僅在來自哈希的請求時處理。

4.2.2.1 實現細節
FeatureHasher使用MurmurHash3的帶符號32位變體。 結果(並且由於scipy.sparse的限制),支持的特徵的最大數目當前是2 ^ {31} - 1。Weinberger等人的散列技巧的原始公式 使用兩個單獨的散列函數h和\ xi來分別確定特徵的列索引和符號。 本實現在假定MurmurHash3的符號位與其它位無關的假設下工作。由於使用簡單模數將散列函數轉換爲列索引,因此建議使用2的冪作爲n_features參數; 否則這些要素將不會均勻地映射到列。

4.2.3文本特徵提取
4.2.3.1 詞袋錶示(詞袋模型)
文本分析是機器學習算法的主要應用領域。然而,原始數據,符號序列不能直接饋送到算法本身,因爲大多數人期望具有固定大小的數字特徵向量而不是具有可變長度的原始文本文檔。爲了解決這個問題,scikit-learn提供了用於從文本內容中提取數字特徵的最常用方法的實用程序,即:
令牌(tokenizing)化字符串併爲每個可能的令牌給出整數id,例如通過使用空格和標點符號作爲令牌分隔符。//單詞分割
計數(counting)每個文檔中標記的出現次數。//單詞計數
使用大多數樣本/文檔中出現的減少的重要性標記進行歸一化和加權(normalizing and weighting)。//歸一化/標準化

在該方案中,特徵和樣本定義如下:
每個單獨的標記出現頻率(標準化或不標準)被視爲特徵。
給定文檔的所有令牌頻率的向量被認爲是多元樣本。
因此,文檔語料庫可以由每個文檔具有一行和在語料庫中出現每個令牌(例如,詞)的列的矩陣表示。
我們將向量化稱爲將文本文檔的集合轉換爲數字特徵向量的一般過程。這種具體的策略(標記化,計數和歸一化)被稱爲詞袋或“n-gram袋”表示。文檔由單詞出現來描述,同時完全忽略文檔中的單詞的相對位置信息。

4.2.3.2 稀疏性
由於大多數文檔通常使用語料庫中非常小的子集的單詞,所以得到的矩陣將具有許多爲零(通常大於99%)的特徵值。例如,10,000個短文本文檔(例如電子郵件)的集合將使用具有總共100,000個唯一字詞的大小的詞彙表,而每個文檔將單獨使用100到1000個唯一字詞。爲了能夠在存儲器中存儲這樣的矩陣,但是爲了加速代數運算矩陣/向量,實現方式通常使用稀疏表示,例如在scipy.sparse包中可用的實現。

4.2.3.3。常用Vectorizer的使用
CountVectorizer在單個類中實現標記化和計數:

from sklearn.feature_extraction.text import CountVectorizer
此模型有很多參數,但是默認值是相當合理的(有關詳細信息,請參閱參考文檔):
vectorizer = CountVectorizer(min_df=1)
vectorizer                     
輸出:CountVectorizer(analyzer=...'word', binary=False, decode_error=...'strict',
        dtype=<... 'numpy.int64'>, encoding=...'utf-8', input=...'content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), preprocessor=None, stop_words=None,
        strip_accents=None, token_pattern=...'(?u)\\b\\w\\w+\\b',
        tokenizer=None, vocabulary=None)
讓我們使用它來標記和計數文本文檔的簡約語料庫的單詞出現:

corpus = [
     'This is the first document.',
     'This is the second second document.',
     'And the third one.',
     'Is this the first document?',
]
 X = vectorizer.fit_transform(corpus)
 X                              
輸出:<4x9 sparse matrix of type '<... 'numpy.int64'>'
    with 19 stored elements in Compressed Sparse ... format>
默認配置通過提取至少2個字母的單詞對字符串進行標記化。 可以明確請求執行此步驟的特定函數:

analyze = vectorizer.build_analyzer()
analyze("This is a text document to analyze.") == (
...     ['this', 'is', 'text', 'document', 'to', 'analyze'])
輸出:True
在擬合期間由分析器發現的每個項被分配與所得矩陣中的列對應的唯一整數索引。 這些列的解釋可以如下檢索:
vectorizer.get_feature_names() == (
     ['and', 'document', 'first', 'is', 'one',
      'second', 'the', 'third', 'this'])
輸出:True

X.toarray()           
輸出:array([[0, 1, 1, 1, 0, 0, 1, 0, 1],
       [0, 1, 0, 1, 0, 2, 1, 0, 1],
       [1, 0, 0, 0, 1, 0, 1, 1, 0],
       [0, 1, 1, 1, 0, 0, 1, 0, 1]]...)
從特徵名到列索引的逆轉換映射存儲在向量化器的vocabulary_屬性中:

vectorizer.vocabulary_.get('document')
輸出:1
因此,在未來調用變換方法時,在訓練語料庫中看不到的詞將被完全忽略:
vectorizer.transform(['Something completely new.']).toarray()
輸出:array([[0, 0, 0, 0, 0, 0, 0, 0, 0]]...)
注意,在先前的語料庫中,第一個和最後一個文檔具有完全相同的字,因此被編碼在相等的向量中。 特別是我們失去的信息,最後一個文件是一個疑問形式。 爲了保留一些本地排序信息,我們可以提取除了1克(單個詞)之外的2克詞:
bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),token_pattern=r'\b\w+\b', min_df=1)
analyze = bigram_vectorizer.build_analyzer()
analyze('Bi-grams are cool!') == (['bi', 'grams', 'are', 'cool', 'bi grams', 'grams are', 'are cool'])
輸出:True
因此,由該向量化器提取的詞彙更大,並且現在可以解決以局部定位模式編碼的模糊性:

X_2 = bigram_vectorizer.fit_transform(corpus).toarray()
X_2                        
輸出:array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
       [0, 0, 1, 0, 0, 1, 1, 0, 0, 2, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0],
       [1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],
       [0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1]]...)
特別是,“這是”的疑問形式只出現在最後一份文件中:

feature_index = bigram_vectorizer.vocabulary_.get('is this')
X_2[:, feature_index]     
輸出:array([0, 0, 0, 1]...)

4.2.3.4 Tf-idf項權重
在大文本語料庫中,一些單詞將非常存在(例如英語中的“the”,“a”,“is”),因此攜帶關於文檔的實際內容的非常少的有意義的信息。 如果我們直接將直接計數數據饋送到分類器,那些非常頻繁的術語將影響更稀有但更有趣的術語的頻率。爲了將計數特徵重新加權爲適合分類器使用的浮點值,使用tf-idf變換是非常常見的。

Tf表示術語頻率,而tf-idf表示術語 - 頻率乘以逆文檔頻率:

\text{tf-idf(t,d)}=\text{tf(t,d)} \times \text{idf(t)}

使用TfidfTransformer的默認設置,TfidfTransformer(norm ='l2',use_idf = True,smooth_idf = True,sublinear_tf = False)術語頻率,術語在給定文檔中出現的次數乘以idf分量, 其計算爲

\text{idf}(t) = log{\frac{1 + n_d}{1+\text{df}(d,t)}} + 1,

其中n_d是文檔的總數,並且\ text {df}(d,t)是包含項t的文檔的數目。 然後通過歐幾里得範數標準化所得的tf-idf載體:

v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v{_1}^2 +v{_2}^2 + \dots + v{_n}^2}}.

這最初是爲信息檢索開發的術語加權方案(作爲搜索引擎結果的排名函數),其也已經在文檔分類和聚類中被良好使用。

以下部分包含進一步的說明和示例,說明如何精確計算tf-idfs,以及如何在scikit-learn的TfidfTransformer和TfidfVectorizer中計算的tf-idfs與標準教科書符號略有不同,定義爲:


在TfidfTransformer和TfidfVectorizer中,smooth_idf = False,“1”計數被添加到idf而不是idf的分母:


這個規範化由TfidfTransformer類實現:

>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> transformer = TfidfTransformer(smooth_idf=False)
>>> transformer   
TfidfTransformer(norm=...'l2', smooth_idf=False, sublinear_tf=False,
                 use_idf=True)


讓我們以下面的計數爲例。 第一個詞是100%的時間,因此不是很有趣。 另外兩個功能只有在不到50%的時間,因此可能更有代表性的文件的內容:

>>> counts = [[3, 0, 1],
...           [2, 0, 0],
...           [3, 0, 0],
...           [4, 0, 0],
...           [3, 2, 0],
...           [3, 0, 2]]
...
>>> tfidf = transformer.fit_transform(counts)
>>> tfidf                         
<6x3 sparse matrix of type '<... 'numpy.float64'>'
    with 9 stored elements in Compressed Sparse ... format>

>>> tfidf.toarray()                        
array([[ 0.81940995,  0.        ,  0.57320793],
       [ 1.        ,  0.        ,  0.        ],
       [ 1.        ,  0.        ,  0.        ],
       [ 1.        ,  0.        ,  0.        ],
       [ 0.47330339,  0.88089948,  0.        ],
       [ 0.58149261,  0.        ,  0.81355169]])

每行被歸一化爲具有單位歐幾里德範數:


例如,我們可以在計數數組中計算第一個文檔中的第一項的tf-idf,如下所示:





現在,如果我們對文檔中的剩餘2項重複這個計算,我們得到:



raw tf-idfs的向量:


然後,應用歐幾里德(L2)範數,我們獲得以下文檔1的tf-idfs:



此外,默認參數smooth_idf = True爲分子和分母添加“1”,就好像看到一個額外的文檔包含集合中的每個術語一次,這樣可以防止零分割:


使用該修改,文檔1中的第三項的tf-idf改變爲1.8473:


L2歸一化的tf-idf變爲


>>> transformer = TfidfTransformer()
>>> transformer.fit_transform(counts).toarray()
array([[ 0.85151335,  0.        ,  0.52433293],
       [ 1.        ,  0.        ,  0.        ],
       [ 1.        ,  0.        ,  0.        ],
       [ 1.        ,  0.        ,  0.        ],
       [ 0.55422893,  0.83236428,  0.        ],
       [ 0.63035731,  0.        ,  0.77630514]])
由擬合方法調用計算的每個要素的權重存儲在模型屬性中:

>>> transformer.idf_                       
array([ 1. ...,  2.25...,  1.84...])

由於tf-idf通常用於文本特性,因此還有另一個類TfidfVectorizer將單個模型中的CountVectorizer和TfidfTransformer的所有選項組合在一起:

>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> vectorizer = TfidfVectorizer(min_df=1)
>>> vectorizer.fit_transform(corpus)
...                                
<4x9 sparse matrix of type '<... 'numpy.float64'>'
    with 19 stored elements in Compressed Sparse ... format>

雖然tf-idf標準化通常非常有用,但是可能存在二進制出現標記可能提供更好特徵的情況。 這可以通過使用CountVectorizer的二進制參數來實現。 特別地,一些估計器例如伯努利樸素貝葉斯顯式地建模離散佈爾隨機變量。 此外,非常短的文本可能具有噪聲tf-idf值,而二進制出現信息更穩定。
像往常一樣,調整特徵提取參數的最佳方式是使用交叉驗證的網格搜索。

4.2.3.5 解碼文本文件

文本由字符組成,但文件由字節組成。 這些字節表示根據某種編碼的字符。 要在Python中使用文本文件,它們的字節必須解碼爲一個稱爲Unicode的字符集。 常見的編碼是ASCII,Latin-1(西歐),KOI8-R(俄羅斯)和通用編碼UTF-8和UTF-16。 許多其他存在。

scikit-learn中的文本特徵提取器知道如何解碼文本文件,但是隻有當你告訴他們文件是什麼編碼時。CountVectorizer爲此採用編碼參數。對於現代文本文件,正確的編碼大概是UTF-8,因此是默認的(encoding =“utf-8”)。
如果你加載的文本實際上沒有使用UTF-8編碼,那麼你會得到一個UnicodeDecodeError。通過將decode_error參數設置爲“忽略”或“替換”,可以告訴矢量化器無關於解碼錯誤。有關更多詳細信息,請參閱Python函數bytes.decode的文檔(在Python提示符下鍵入help(bytes.decode))。
如果您在解碼文本時遇到問題,請嘗試以下操作:
找出文本的實際編碼是什麼。該文件可能帶有一個標題或README,告訴您編碼,或者可能有一些標準編碼,您可以根據文本來自哪裏。
您可以使用UNIX命令文件找出一般使用何種編碼。 Python chardet模塊有一個名爲chardetect.py的腳本,它將猜測特定的編碼,雖然你不能依賴它的猜測是正確的。
你可以嘗試UTF-8並忽略錯誤。您可以使用bytes.decode(errors ='replace')解碼字節字符串,用無意義的字符替換所有解碼錯誤,或在矢量化程序中設置decode_error ='replace'。這可能會損壞您的功能的有用性。
真實文本可以來自各種源,這些源可能已經使用了不同的編碼,或者甚至以與其編碼的編碼不同的編碼進行粗略解碼。這在從Web檢索的文本中很常見。 Python包ftfy可以自動分類出一些類的解碼錯誤,因此您可以嘗試將未知文本解碼爲latin-1,然後使用ftfy修復錯誤。
如果文本是編碼的雜亂,這很難排序(這是20新聞組數據集的情況),你可以回到一個簡單的單字節編碼,如拉丁-1。某些文本可能顯示不正確,但至少相同的字節序列將始終表示相同的功能。
例如,以下片段使用chardet(不隨scikit-learn一起提供,必須單獨安裝)來計算三個文本的編碼。 然後將文本向量化並打印學習的詞彙。 此處不顯示輸出。

>>> import chardet    
>>> text1 = b"Sei mir gegr\xc3\xbc\xc3\x9ft mein Sauerkraut"
>>> text2 = b"holdselig sind deine Ger\xfcche"
>>> text3 = b"\xff\xfeA\x00u\x00f\x00 \x00F\x00l\x00\xfc\x00g\x00e\x00l\x00n\x00 \x00d\x00e\x00s\x00 \x00G\x00e\x00s\x00a\x00n\x00g\x00e\x00s\x00,\x00 \x00H\x00e\x00r\x00z\x00l\x00i\x00e\x00b\x00c\x00h\x00e\x00n\x00,\x00 \x00t\x00r\x00a\x00g\x00 \x00i\x00c\x00h\x00 \x00d\x00i\x00c\x00h\x00 \x00f\x00o\x00r\x00t\x00"
>>> decoded = [x.decode(chardet.detect(x)['encoding'])
...            for x in (text1, text2, text3)]        
>>> v = CountVectorizer().fit(decoded).vocabulary_    
>>> for term in v: print(v)    

4.2.3.6 應用和示例

詞袋錶示是相當簡單,但在實踐中令人驚訝的有用。
特別是在監督設置中,它可以成功地與快速和可擴展的線性模型組合以訓練文檔分類器,例如:
           使用稀疏特徵的文本文檔的分類
在無監督設置中,它可以用於通過應用諸如K均值的聚類算法將類似文檔分組在一起:
使用k-means聚類文本文檔
最後,通過放鬆聚類的硬分配約束,例如通過使用非負矩陣分解(NMF或NNMF),可以發現語料庫的主要主題:
使用非負矩陣因子分解和潛在狄利克雷分配的主題提取

4.2.3.7 詞袋錶示的限制


4.2.4 圖像特徵提取

4.2.4.1 補丁提取

extract_patches_2d函數從存儲爲二維陣列的圖像或沿着第三軸的具有顏色信息的三維提取片段。 要從所有其修補程序重建圖像,請使用reconstruct_from_patches_2d。 例如,讓使用生成具有3個顏色通道的4×4像素圖片(例如,以RGB格式):

>>> import numpy as np
>>> from sklearn.feature_extraction import image

>>> one_image = np.arange(4 * 4 * 3).reshape((4, 4, 3))
>>> one_image[:, :, 0]  # R channel of a fake RGB picture
array([[ 0,  3,  6,  9],
       [12, 15, 18, 21],
       [24, 27, 30, 33],
       [36, 39, 42, 45]])

>>> patches = image.extract_patches_2d(one_image, (2, 2), max_patches=2,
...     random_state=0)
>>> patches.shape
(2, 2, 2, 3)
>>> patches[:, :, :, 0]
array([[[ 0,  3],
        [12, 15]],

       [[15, 18],
        [27, 30]]])
>>> patches = image.extract_patches_2d(one_image, (2, 2))
>>> patches.shape
(9, 2, 2, 3)
>>> patches[4, :, :, 0]
array([[15, 18],
       [27, 30]])
讓我們現在嘗試通過在重疊區域上求平均來從補片重建原始圖像:

>>> reconstructed = image.reconstruct_from_patches_2d(patches, (4, 4, 3))
>>> np.testing.assert_array_equal(one_image, reconstructed)
補丁提取器類的工作方式與extract_patches_2d相同,只是它支持多個圖像作爲輸入。 它被實現爲一個估計器,因此它可以在管道中使用。 看到:

>>> five_images = np.arange(5 * 4 * 4 * 3).reshape(5, 4, 4, 3)
>>> patches = image.PatchExtractor((2, 2)).transform(five_images)
>>> patches.shape
(45, 2, 2, 3)
4.2.4.2 圖像的連接圖

scikit-learn中的幾個估計器可以使用特徵或樣本之間的連接信息。 例如,Ward聚類(分層聚類)可以僅聚集圖像的相鄰像素,從而形成連續的區塊:


爲此,估計器使用“連通性”矩陣,給出哪些樣本被連接。
函數img_to_graph從2D或3D圖像返回這樣的矩陣。 類似地,grid_to_graph爲給定這些圖像的形狀的圖像構建連接矩陣。
這些矩陣可用於在使用連通性信息(例如Ward聚類(Hierarchical clustering))的估計器中強加連接性,但也建立預先計算的內核或相似性矩陣。

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