目錄
首先來看下面 下麪這個錯誤:
這個問題是使用機器學習的多項式貝葉斯函數做文字預測時出現的, 拋開文字預測這個侷限,當使用機器學習函數進行模型構建與預測時就會出現類似的錯誤:ValueError: shapes (a,b) and (c,d) not aligned: b (dim 1) != c (dim 0)
這個錯誤是機器學習中的一個通病,錯誤中的a、b、c、d 實際數值可能會不同,請大家看清這個錯誤的樣子,有助於下文理解。
在進行實際問題分析之前,請允許我對問題背景進行簡單介紹:
我在程式中使用機器學習中的貝葉斯模型訓練文字數據,然後通過訓練好的模型進行對測試集文字類別的預測。訓練集與測試集的劃分採用的是ML中的train_test_split。模型不可能對文字數據進行fit,需要提取文字特徵,因此我在此使用的是TfidfVectorizer構建詞典,並計算所有文章的tfidf值作爲本文特徵進行fit,模型訓練過程無問題,而在predict過程中報錯。
問題分析:
錯誤出現在使用模型進行預測時,觀察上圖報錯的倒數第二行,即 return np.dot(a,b)
numpy的dot函數是進行矩陣乘法的函數,那我們瞭解若是想要兩個矩陣相乘有一定的要求,就是第一個矩陣的列數必須與第二個矩陣的行數相同,若計算 matrix(a*b) * matrix(c,d) ,則 b=c 纔可完成計算,即滿足一定的維度要求。
瞭解矩陣乘法的要求後這個問題也就迎刃而解了,那觀察上圖倒數第一行:
ValueError: shapes (2048,156891) and (276465,7) not aligned: 156891 (dim 1) != 276465 (dim 0)
提示的ValueError錯誤中提供了兩個shape的矩陣(也就是兩個維度的矩陣),matrix (2048 * 156891) 與matrix (276465 * 7)。通過上述的矩陣乘法的常識,我們初步可以瞭解錯誤是由矩陣維度不同引起的,而通過錯誤實際提供的矩陣維度正好驗證了我們的分析是正確的,即第一個矩陣的列的維度 156891 不等於 第二個矩陣行的維度 276465,正是錯誤中的 156891 (dim 1) != 276465 (dim 0)。
分析出了錯誤原因,還需瞭解這兩個矩陣到底是什麼,才能 纔能由根源解決問題。
matrix (2048 * 156891) 這個矩陣是模型使用predict函數進行預測所需要的數據,程式中的寫法爲
y_predict = mnb.predict(test_matrix)
此矩陣也就是 test_matrix 矩陣,此矩陣的行的維度 2048 是測試集的個數,就本問題來說是測試集文字的個數,矩陣的列的維度 156891 是 特徵的個數,也就是說每個文字都根據詞典選取了 156891 個特徵值。 此處若您還不是很清楚,我舉一個ML中非常經典的例子:鳶尾花數據集。鳶尾花數據集是列舉了4個特徵:花瓣的長度和寬度以及花萼的長度和寬度 。若你使用train_test_split函數分割的測試集爲30個,此時這個矩陣的維度即爲 30 * 4,30爲測試集的個數,4爲特徵的個數。
matrix (276465 * 7) 這個矩陣是模型使用fit函數進行訓練時所需要的數據,程式中的寫法爲
mnb.fit(train_matrix,y_train)
此矩陣也就是 train_matrix 矩陣,此矩陣的行的維度 276465 是訓練集選取的 特徵的個數 ,也就是說訓練集中每個文字都根據詞典選取了 276465 個特徵值,矩陣的列的維度 7 是類別的個數,本實驗中我定義了7個文字類別。在鳶尾花範例中此矩陣的行數爲選取特徵的個數 4個:花瓣的長度和寬度以及花萼的長度和寬度,列數爲類別的個數 3個:setosa、versicolor 或 virginica,此時這個矩陣的維度爲 4 * 3。
對兩個矩陣介紹後相信對錯誤的原因有了更爲深刻的理解,那麼你可能會問,爲什麼鳶尾花數據集進行訓練時一點問題都沒有?因爲鳶尾花數據集的特徵個數十分標準,是經過標準化處理的,即爲4個,在全部鳶尾花數據集中,每一個 個體的特徵都爲4個,因此在進行模型預測時不會出問題,因爲 第一個矩陣的列的維度 等於 第二個矩陣行的維度。
相必通過以上的介紹你也能理解模型是如何訓練的,即是通過矩陣乘法,將測試集樣本對映到不同的類別中,舉個例子,測試集個數爲10個,數據集選取特徵數爲1000個,類別爲5類,相信你也可以寫出預測的矩陣乘法了,即爲:matrix1(10 * 1000) * matrix2(1000 * 5) = matrix3(10 * 5)最終將10個數據分別對映到了5個類別上,通過矩陣乘法實現了維度降低並最終進行類別預測。
倘若鳶尾花數據集中的特徵不是十分標準,也就是沒有標準化這個概念,那麻煩就大了,假如鳶尾花訓練集均取4個特徵進行模型訓練,而測試集均取3個特徵作爲預測數據,則就會出現上述介紹的問題,將會出現下述錯誤。(假設10個測試集)
ValueError: shapes (10,3) and (4,3) not aligned: 3 (dim 1) != 4 (dim 0)
再倘若數據集特徵毫無規律,第一條數據可能具有3個特徵,第二條數據可能具有5個特徵 等等情況,毫無標準,則此數據集是有問題的,必須處理後才能 纔能進行使用,也就是要引出的標準化的概念!
鳶尾花數據集是ML官方提供的,因此無可挑剔。但我希望用這個簡單的數據集 幫助大家瞭解 ML模型是如何進行預測的,以及在模型訓練時對數據集的標準化要求,不僅對ValueError問題有個更爲全面的瞭解,同時也對其本質有了更爲深層的理解。
至此對宏觀上這類問題的分析已經十分清楚了,下面 下麪將介紹幾種解決方案,若不想瞭解此問題是如何發生在我所做的nlp問題中,可直接看下面 下麪的解決方案,若想要繼續瞭解,並對比自己的程式中是否會出現這種問題,請看下面 下麪的結合詳細問題的分析。
結合nlp的問題分析:
此問題是做nlp中十分經典的文字分類問題中發生的,在前面介紹過,文字特徵提取我使用了 TfidfVectorizer ,TfidfVectorizer是ML中文字特徵提取的工具之一,還有CountVectorizer、TfidfTransformer,但CountVectorizer + TfidfTransformer 的最終效果與TfidfVectorizer 處理的一樣,因此選擇TfidfVectorizer。
TfidfVectorizer 是通過詞袋法進行構建的,即構建出所有文章去除停用詞後的詞典,然後計算該篇文章詞的tfidf值,matrix (276465 * 7) 前面介紹的這個矩陣中行的維度即是選擇的特徵個數,也就意味着訓練文字構造的詞典長度爲 276465,而matrix (2048 * 156891) 中列的維度 與 276465不同,因此報錯。
在前面鳶尾花數據集中我特別強調標準化的概念,但在我們實際程式設計中,數據集並不是稱心如意,往往會有各種問題,不符合作爲模型等的輸入。
我出現的問題:在通過train_test_split函數對分詞並去除停用詞的數據集切分成x_train,x_test後分別調用TfidfVectorizer進行處理x_train與x_test,我將TfidfVectorizer的處理過程封裝如下:
def calTfidf(stopword,data):
tfvector = TfidfVectorizer(stop_words=stopword)
tfidf = tfvector.fit_transform(data)
return tfidf
首先初始化 TfidfVectorizer ,並定義去除停用詞,然後使用 TfidfVectorizer 的 fit_transform函數對數據進行訓練,我的呼叫即爲兩次呼叫,matrix_train = calTfidf(stopword,x_train) matrix_test = calTfidf(stopword,x_test)
那麼問題就出現了,兩次呼叫 fit_transform函數對數據進行處理,每一次均構造了一個詞袋,原本數據集中的文字長度就良莠不齊,選取的文字特徵更是難得統一,那麼呼叫兩次,構造這兩個詞袋的長度又怎麼會相同呢?就導致訓練集詞袋長度爲276465 ,測試集詞袋長度爲156891,造成了錯誤,最根本的原因就是數據集的標準化問題!
在問題分析中多次引出標準化問題,數據集是否標準化是此類錯誤的關鍵,那既然數據集不標準,我們的想法就是藉助手段使其標準,首先介紹一下人爲手段,在對數據集處理時,若發現哪個數據元素的維度與其他不同,那麼可以通過 補0 進行處理。與木桶原理相反,數據集的長度應取決與長度最長的數據元素,對其他長度小於它的數據元素均進行補0處理,當然補0並不是最好的,但一定是可行可使用的,下面 下麪我通過一個範例說明一下:
word = [
[0.2,0.4,0.2,0.1,0,0.4],
[0.3,0.5,0.2],
[0.3,0.5,0.2,0.4,0]
]
#cal max length
length = [len(item) for item in word]
maxlength = max(length)
#fill zero
for item in word:
if len(item) < maxlength:
item.extend([0.]*(maxlength-len(item)))
print(word)
假設word中每個列表元素即爲特徵值, 可以看到,每個列表長度都不相同,那麼可以首先獲取word中元素的最大長度,然後通過回圈,使用extend函數補0,補0的次數爲 最大的長度 - 此元素的長度。
但在對numpy.ndarray型別進行補0操作時十分困難,因此需要考慮下面 下麪的機器方法。
機器方法中將會使用sklearn庫中數據預處理常式fit_transform()和transform(),在上文提到了fit_transform(),使用fit_transform()進行對數據集的處理,TfidfVectorizer中呼叫fit_transform()是爲了構造詞典,並計算出每篇文章詞語的tfidf值。
fit_transform()的作用就是先擬合數據集,然後轉化數據集爲標準化形式。transform()的作用是通過找中心和縮放等實現標準化。字面上看兩個函數都是爲了實現標準化,但fit_transform 首先需要擬合數據集,進行數據歸一化處理,而transform 通過尋找fit_transform 歸一化處理後的中心實現標準化,也就是說transform 依賴 fit_transform的處理結果。
結合到實際ML中就是對x_train 進行 fit_transform,再對x_test 進行 transform!由於transform 依賴 fit_transform的處理結果,因此必須先進行 fit_transform,在進行transform。
本程式中的解決方案:
#get all articles tfidf matrix
def calTfidf(stopword,x_train,x_test):
tfvector = TfidfVectorizer(stop_words=stopword)
x_train_matrix = tfvector.fit_transform(x_train)
x_test_matrix = tfvector.transform(x_test)
return x_train_matrix,x_test_matrix
對x_train 進行 fit_transform,再對x_test 進行 transform 後訓練集與測試集均已標準化,使得矩陣相乘時列的維度與行的維度相同,成功的解決了問題。