你能把美元價格和 “優(yōu)雅,細膩的單寧”,“成熟的醋栗香氣” 或 “醇厚的烤面包香氣” 聯(lián)系在一起嗎? 事實證明機器學習模型可以。 在這篇文章中,我將解釋如何使用 Keras(tf.keras)建立一個 Wide & Deep 網絡來預測其描述中的葡萄酒價格。對于那些剛接觸 Keras 的人來說,它是用于構建 ML 模型的更高級別的 TensorFlow API。 如果您想直接跳到代碼,可以在 GitHub 上找到它。 你也可以直接在瀏覽器中使用 Colab 進行零設置運行模型。
向 Francois,Josh 和 Yufeng 致敬,感謝他們對這篇文章的幫助和意見。
模型:Wide & Deep 與 Keras 結合
我最近一直在使用 Sequential Model API 構建了許多 Keras 模型(這里有一些例子),但我想嘗試使用 Functional API。Sequential API 是開始使用 Keras 的最佳方式,它可以讓你輕松地將模型定義為圖層堆棧。Functional API 則更加靈活,它最適合具有多個輸入或組合模型的模型。Functional API 的一個很好的實例是在 Keras 中執(zhí)行 wide and deep 網絡。關于學習 wide and deep 有很多很好的資源,因此我不會著重于細節(jié),但如果你有興趣了解更多,我推薦這篇文章。
在通過 wide & deep 網絡解決ML問題之前,你最好確保它非常適合您嘗試預測的內容。如果你有一個預測任務,輸入和輸出之間存在相對直接的關系,那么 wide 模型可能就足夠了。 Wide 模型是具有稀疏特征向量的模型,或具有大多數零值的向量。另一方面,已知多層深度網絡在諸如圖像或語音識別之類的任務上表現良好,其中輸入和輸出之間可能存在意外關系。如果你有一個可以從這兩個模型中受益的預測任務(推薦模型或帶有文本輸入的模型都是很好的例子),那么 wide & deep 可能是一個很好的選擇。在這種情況下,我分別嘗試了 Wide 模型和 Deep 模型,然后將它們組合在一起,結果發(fā)現 Wide & Deep 在一起的情況下精確度表現最好。讓我們深入了解一下吧。
數據集:預測葡萄酒的價格
我們將使用 Kaggle 葡萄酒數據集 this wine dataset 來查看:
我們可以從描述和品種中預測出一瓶葡萄酒的價格嗎?
這個問題非常適合 Wide & Deep 的學習,因為它涉及到文本輸入,并且葡萄酒的描述與其價格之間并沒有明顯的相關性。我們無法斬釘截鐵地說,描述中帶有 “果味” 一詞的葡萄酒更貴,或者有 “柔和的單寧” 描述的葡萄酒更便宜。此外,當我們將文本提供給模型時,有很多種方式來表示文本,兩者都可以導致不同類型的見解。有 Wide 表示(詞袋)和 Deep 表達(嵌入)兩種方式,將兩者結合起來可以讓我們從文本中提取更多的含義。這個數據集有很多不同的功能可能性,但我們只使用描述以及品種來進行相對簡化。以下是此數據集的示例輸入和預測:
輸入
描述: 馥郁的香草氣息從杯中升起,盡管是處于這個葡萄生長艱難的年份,果味也立即出現。 它的酸味和尖銳,帶著濃烈的草藥香,葡萄酒迅速成熟,果味,酸味,單寧,草本植物和香草味道的比例相當。 這款葡萄酒醇厚而緊實,它還很年輕,需要醒酒和/或更長時間的醒酒瓶才能展現出最佳效果。
品種: 黑皮諾
預測
價格?—?45美元
首先,以下是我們構建此模型所需的所有導入:
1import os
2import numpy as np
3import pandas as pd
5
6from sklearn.preprocessing import LabelEncoder
7
8from tensorflow import keras
9layers = keras.layers
10
11 # This code was tested with TensorFlow v1.7
12print("You have TensorFlow version", tf.__version__)
由于我們的模型輸出(預測)是數字的價格,我們將價格值直接提供給我們的模型進行訓練和評估。 GitHub 上提供了此模型的完整代碼。 在這里,我將重點介紹關鍵點。
首先,讓我們下載數據并將其轉換為 Pandas 數據結構:
1 !wget -q https://storage.googleapis.com/sara-cloud-ml/wine_data.csv
2data = pd.read_csv("wine_data.csv")
接下來,我們將其拆分為訓練和測試集并提取功能和標簽:
1train_size = int(len(data) * .8)
2
3# Train features
4description_train = data['description'][:train_size]
5variety_train = data['variety'][:train_size]
6
7# Train labels
8labels_train = data['price'][:train_size]
9
10# Test features
11description_test = data['description'][train_size:]
12variety_test = data['variety'][train_size:]
13
14# Test labels
15labels_test = data['price'][train_size:]
Part 1:Wide 模型
特征1:葡萄酒描述
為了創(chuàng)建我們的文本描述的 Wide 表示,我們將使用一個詞袋模型。 Here 有更多相關內容,但在這里我們快速回顧一下:一個詞袋模型會在模型的每個輸入中查找單詞的存在。你可以將每個輸入視為一袋 Scrabble 圖塊,其中每個圖塊包含一個單詞而不是一個字母。該模型沒有考慮描述中單詞的順序,僅考慮單詞的存在與否。
注:Here 鏈接
https://en.wikipedia.org/wiki/Bag-of-words_model
將一袋單詞模型的輸入想象為 Scrabble tiles,其中每個 tile 包含來自輸入的單詞(而不是字母)
我們不會去逐一查看數據集中每個描述里的每一個單詞,而是將我們的單詞數量限制在數據集中的前 12,000 個單詞中(不用擔心,還有一個用于創(chuàng)建此詞匯表的內置 Keras 實用程序)。這就被認為是 “Wide”,因為我們的模型對每個描述的輸入將是 12k 元素 Wide 的向量,其中 1 和 0 表示在特定描述中存在來自詞匯表的單詞。
Keras 有一些方便的文本預處理實用程序,我們將用它們將文本描述轉換成一個詞袋。使用詞袋模型,我們通常只希望在詞匯表中包含我們數據集中找到的總單詞的子集。 在這個例子中,我使用了 12,000 個單詞,但這是一個可以調整的超參數(嘗試一些值并查看數據集的最佳效果)。 我們可以使用 Keras Tokenizer 類創(chuàng)建我們的詞袋詞匯:
1vocab_size = 12000
2tokenize = keras.preprocessing.text.Tokenizer(num_words=vocab_size, char_level=False)
3tokenize.fit_on_texts(description_train) # only fit on train
然后我們將使用 texts_to_matrix 函數將每個描述轉換為詞袋向量:
1description_bow_train = tokenize.texts_to_matrix(description_train)
2description_bow_test = tokenize.texts_to_matrix(description_test)
特征2: 葡萄酒品種
在原始的 Kaggle 數據集中,共有 632 種葡萄酒品種。 為了讓我們的模型更容易提取模式,我做了一些預處理,只保留了前 40 個品種(約占原始數據集的 65%,總共 96k 大小的范例)。 我們將使用 Keras 實用程序將這些變量中的每一個轉換為整數表示,然后我們將為每個輸入創(chuàng)建 40 個元素寬度的的單熱矢量以指示變化:
1# Use sklearn utility to convert label strings to numbered index
2encoder = LabelEncoder()
3encoder.fit(variety_train)
4variety_train = encoder.transform(variety_train)
5variety_test = encoder.transform(variety_test)
6num_classes = np.max(variety_train) + 1
7
8# Convert labels to one hot
9variety_train = keras.utils.to_categorical(variety_train, num_classes)
10variety_test = keras.utils.to_categorical(variety_test, num_classes)
現在我們準備搭建 Wide 模型。
用 Keras functional API 搭建 Wide 模型
Keras 有兩個用于構建模型的 API:Sequential API 和 Functional API。 Functional API 為我們定義圖層的方式提供了更多的靈活性,并允許我們將多個特征輸入組合到一個圖層中。 當一切就緒,它還可以輕松地將我們的 Wide 模型和 Deep 模型組合二為一。 使用 Functional API,我們只使用幾行代碼就能輕松定義 Wide 模型。首先,我們將輸入層定義為 12k 元素向量(對于詞匯表中的每個單詞)。然后我們將它連接到我們的 Dense 輸出層以生成價格預測:
1bow_inputs = layers.Input(shape=(vocab_size,))
2variety_inputs = layers.Input(shape=(num_classes,))
3merged_layer = layers.concatenate([bow_inputs, variety_inputs])
4merged_layer = layers.Dense(256, activation='relu')(merged_layer)
5predictions = layers.Dense(1)(merged_layer)
6wide_model = Model(inputs=[bow_inputs, variety_inputs], outputs=predictions)
然后我們編譯模型,這樣就可以使用:
1wide_model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])
如果我們自己使用 Wide 模型,那么我們將使用 fit() 和 evaluate() 進行評估。由于我們稍后會將它與我們的 Deep 模型相結合,我們可以暫停訓練,直到兩個模型結合起來。現在是時候建立我們的 Deep 模型了!
Part 2:Deep 模型
為了創(chuàng)建葡萄酒描述的 Deep 表示,我們將其表示為嵌入。關于單詞嵌入有很多資源,簡而言之是它們提供了一種將單詞映射到向量的方法,以便相似的單詞在向量空間中更加靠近。
將描述表示為單詞嵌入
要將我們的文本描述轉換為嵌入層,我們首先需要將每個描述轉換為與詞匯表中每個單詞對應的整數向量。我們可以用方便的 Keras text_to_sequencesmethod 來做到這一點:
1train_embed = tokenize.texts_to_sequences(description_train)
2test_embed = tokenize.texts_to_sequences(description_test)
現在我們已經有了整數描述向量,我們需要確保它們的長度都相同,以便將它們輸入到我們的模型中。 Keras 也有一個方便的方法。我們將使用 pad_sequ 為了創(chuàng)建葡萄酒描述的深層表示,我們將其表示為嵌入。關于單詞嵌入有很多資源,但簡短的版本是它們提供了一種將單詞映射到向量的方法,以便相似的單詞在向量空間中更加接近。為每個描述向量添加零以使它們的長度相同(我使用 170 作為最大長度,這樣就沒有任何描述被縮短:
1max_seq_length = 170
2train_embed = keras.preprocessing.sequence.pad_sequences(train_embed, maxlen=max_seq_length)
3test_embed = keras.preprocessing.sequence.pad_sequences(test_embed, maxlen=max_seq_length)
將我們的描述轉換為長度相同的矢量,我們已準備好創(chuàng)建嵌入層并將其輸入 Deep 模型。
創(chuàng)建 Deep 模型
要創(chuàng)建嵌入層有兩種方法 - 我們可以使用預訓練嵌入的權重(有許多開源詞嵌入),或者我們可以從詞匯表中學習嵌入。最好先試驗這兩種方法,看看哪一個在數據集上表現更好。在這里,我們將使用學習嵌入。
首先,我們將定義 Deep 模型輸入的形狀。然后我們將它提供給嵌入層。 這里我使用的是 8 維的嵌入圖層(您可以嘗試調整嵌入圖層的維度)。嵌入層的輸出將是具有形狀的三維矢量:[批量大小,序列長度(在該示例中為 170),嵌入維度(在該示例中為 8)]。為了將我們的嵌入層連接到密集,完全連接的輸出層,我們需要先將其展平:
1deep_inputs = layers.Input(shape=(max_seq_length,))
2embedding = layers.Embedding(vocab_size, 8, 3input_length=max_seq_length)(deep_inputs)
embedding = layers.Flatten()(embedding)
一旦嵌入層變平,就可以將其輸入模型并進行編譯:
1embed_out = layers.Dense(1, activation='linear')(embedding)
2deep_model = Model(inputs=deep_inputs, outputs=embed_out)
3deep_model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])
Part 3:Wide 和 Deep
一旦我們定義了兩個模型,將它們組合起來就很容易。 我們只需要創(chuàng)建一個連接每個模型的輸出圖層,然后將它們合并到一個完全連接的 Dense 圖層中,最后定義一個組合模型,它將每個模型的輸入和輸出結合起來。顯然,由于每個模型都預測相同的事情(價格),因此每個模型的輸出或標簽都將是相同的。另請注意,由于我們的模型輸出是一個數值,我們不需要進行任何預處理 - 它已經是正確的格式:
1merged_out = layers.concatenate([wide_model.output, deep_model.output])
2merged_out = layers.Dense(1)(merged_out)
3combined_model = Model(wide_model.input + [deep_model.input], merged_out)
4combined_model.compile(loss='mse',optimizer='adam', metrics=['accuracy'])
有了這個,就該進行培訓和評估了。你可以嘗試最適合您的數據集的訓練時期和批量大小的訓練次數:
1# Training
2combined_model.fit([description_bow_train, variety_train] + [train_embed], labels_train, epochs=10, batch_size=128)
3
4# Evaluation
5combined_model.evaluate([description_bow_test, variety_test] + [test_embed], labels_test, batch_size=128)
在我們訓練模型中生成預測
到了最關鍵部分的時間了。了解我們的模型對之前從未見過的數據如何表現。 為此,我們可以在我們訓練的模型上調用 predict(),并將測試數據集傳遞給它(在以后的文章中我將介紹如何從純文本輸入中獲取預測):
1predictions = combined_model.predict([description_bow_test, variety_test] + [test_embed])
然后我們將預測與我們測試數據集中前 15 種葡萄酒的實際價格進行比較:
1for i in range(15):
2val = predictions[i]
3print(description_test[i])
4print(val[0], 'Actual: ', labels_test.iloc[i], ' ')
模型是怎么做的?我們來看看測試集中的三個例子:
馥郁的香草氣息從杯中升起,盡管是處于葡萄生長艱難的年份,果味也立即出現。它的酸味和尖銳,帶著濃烈的草藥香,葡萄酒迅速成熟,果味,酸味,單寧,草本植物和香草味道的比例相當。這款葡萄酒醇厚而緊實,它還很年輕,需要醒酒和/或更長時間的醒酒瓶才能展現出最佳效果。
預測價格: 46.233624 實際價格: 45.0
一款美味的日常酒。它是干型的,濃郁,足夠的漿果櫻桃香,包裹成光滑的質地。
預測價格: 9.694958 實際價格: 10.0
這是一款現代,圓潤,天鵝絨般的巴羅羅(來自 Monforte d'Alba),適合那些喜歡醇厚多汁的葡萄酒的人。香氣包含薰衣草,五香粉,肉桂,白巧克力和香草。 酸味漿果口味附帶著酸甜的口感和結實的單寧賦予了肯定和堅韌的口感。
預測價格: 41.028854 實際價格: 49.0
很不錯! 事實證明,葡萄酒的描述與其價格之間存在某種關系。我們可能無法本能地看到它,但我們的 ML 模型可以。
-
模型
+關注
關注
1文章
3172瀏覽量
48714 -
代碼
+關注
關注
30文章
4748瀏覽量
68356 -
keras
+關注
關注
2文章
20瀏覽量
6080
原文標題:用 Keras Functional API 和 TensorFLow 預測葡萄酒的價格
文章出處:【微信號:tensorflowers,微信公眾號:Tensorflowers】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論