用Keras Functional API和TensorFlow预测葡萄酒的价格
数据集:预测葡萄酒的价格
我们将使用Kaggle的这款葡萄酒数据集(https://www.kaggle.com/zynicide/wine-reviews/data)来查看:
我们能否从其描述和品种中预测一瓶葡萄酒的价格?
这个问题非常适合广泛深入的学习,因为它涉及文本输入,葡萄酒的描述和价格之间没有明显的相关性。我们不能确定地说葡萄酒中描述的“fruity”一词更昂贵,或者“soft tannins”的葡萄酒更便宜。另外,当我们将文本提供给我们的模型时,有多种方式来表示文本,并且都可以导致不同类型的见解。有广泛的表达(词袋)和深层的(嵌入),并且两者结合可以让我们从文本中提取更多的意义。这个数据集有很多不同的功能可能性,但我们只会使用描述和多样性来保持相对简单。以下是来自此数据集的示例输入和预测:
输入
产品简介:强烈的香草气味从玻璃上升起,但水果即使在这个难以酿制的年份中也立即出现。它酸甜而浓郁,带有强烈的草药成分,葡萄酒以水果,酸,单宁,香草和香草等比例快速聚焦。这款葡萄酒坚固而紧密,仍然非常年轻,需要倾析和/或进一步的瓶龄来展现其最佳状态。
品种:黑比诺(Pinot Noir)
预测
价格 - $ 45
以下是我们构建此模型所需的所有导入:
import os
import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.preprocessing import LabelEncoder
from tensorflow import keras
layers = keras.layers
# This code was tested with TensorFlow v1.7
print("You have TensorFlow version", tf.__version__)
由于我们模型的输出(预测)是一个价格数字,我们将直接将价格数据提供给我们的培训和评估模型。首先,让我们下载数据并将其转换为Pandas数据框:
!wget -q https://storage.googleapis.com/sara-cloud-ml/wine_data.csv
data = pd.read_csv("wine_data.csv")
接下来,我们将它分解为一个训练和测试集并提取特征和标签:
train_size = int(len(data) * .8)
# Train features
description_train = data['description'][:train_size]
variety_train = data['variety'][:train_size]
# Train labels
labels_train = data['price'][:train_size]
# Test features
description_test = data['description'][train_size:]
variety_test = data['variety'][train_size:]
# Test labels
labels_test = data['price'][train_size:]
第1部分: the wide model
特点1:葡萄酒的描述
为了创建我们的文本描述的广泛表示,我们将使用a bag of words模型。快速复习:你可以把每一个输入看作是一个拼字游戏包,每一块都包含一个单词而不是一个字母。模型没有考虑描述中单词的顺序,只是单词的存在或不存在。
我们不会在数据集中查看每个描述中的每个单词,而是将我们的单词包限制在数据集中的前12,000个单词(不用担心,有一个用于创建此词汇表的内置Keras实用程序)。这被认为是“wide”的,因为对于每个描述我们模型的输入将是一个12k宽的向量,1s和0s表示在特定描述中我们的词汇表中存在单词。
Keras有一些方便的文本预处理实用程序,我们将用它将文本描述转换为一包文字。通过一包单词模型,我们通常只希望在词汇表中包含我们数据集中找到的全部单词的一个子集。在这个例子中,我使用了12,000个单词,但是这是一个可以调整的超参数(尝试一些值并查看对数据集最有效的方法)。我们可以使用Keras Tokenizer类创建我们的单词词汇包:
vocab_size = 12000
tokenize = keras.preprocessing.text.Tokenizer(num_words = vocab_size,char_level = False)
tokenize.fit_on_texts(description_train)#仅适用于训练
然后,我们将使用该texts_to_matrix函数将每个描述转换为一个单词矢量包:
description_bow_train = tokenize.texts_to_matrix(description_train)
description_bow_test = tokenize.texts_to_matrix(description_test)
特色2:葡萄酒品种
在原始的Kaggle数据集中,共有632种葡萄酒品种。为了使我们的模型更容易提取模式,我做了一些预处理,只保留前40个品种(原始数据集的65%左右,或总共96个示例)。我们将使用Keras实用程序将这些变体中的每一个转换为整数表示形式,然后我们将为每个输入创建40个元素的单热矢量以指示变体:
# Use sklearn utility to convert label strings to numbered index
encoder = LabelEncoder()
encoder.fit(variety_train)
variety_train = encoder.transform(variety_train)
variety_test = encoder.transform(variety_test)
num_classes = np.max(variety_train) + 1
# Convert labels to one hot
variety_train = keras.utils.to_categorical(variety_train, num_classes)
variety_test = keras.utils.to_categorical(variety_test, num_classes)
使用Keras功能API构建the wide model
Keras有两个用于构建模型的API:Sequential API和Functional API。功能API为我们定义图层提供了更多的灵活性,并让我们将多个功能输入组合到一个图层中。当我们准备好时,它还可以很容易地将我们的广泛和深度模型结合到一个模型中。使用Functional API,我们可以用几行代码来定义我们的宽泛模型。首先,我们将输入层定义为12k元素矢量(用于词汇表中的每个单词)。然后我们将它连接到我们的密集输出层来生成价格预测:
bow_inputs = layers.Input(shape=(vocab_size,))
variety_inputs = layers.Input(shape=(num_classes,))
merged_layer = layers.concatenate([bow_inputs, variety_inputs])
merged_layer = layers.Dense(256, activation='relu')(merged_layer)
predictions = layers.Dense(1)(merged_layer)
wide_model = Model(inputs=[bow_inputs, variety_inputs], outputs=predictions)
然后,我们将编译模型
wide_model.compile(loss = ' mse ',optimizer = ' adam ',metrics = [ ' accuracy ' ])
第2部分:深层模型
为了深入描述葡萄酒的描述,我们将其作为嵌入代表它。词嵌入(word embeddings)有很多资源,但简短的版本是,它们提供了一种将单词映射到矢量的方法,以便类似单词在矢量空间中更接近。
将描述表示为词嵌入
为了将我们的文本描述转换为嵌入层,我们首先需要将每个描述转换为与我们词汇表中每个单词对应的整数向量。我们可以用方便的Keras texts_to_sequences方法做到这一点:
train_embed = tokenize.texts_to_sequences(description_train)
test_embed = tokenize.texts_to_sequences(description_test)
现在我们已经有了integerized描述向量,我们需要确保它们长度相同,以将它们馈送到我们的模型中。凯拉斯也有一个方便的方法。我们将使用pad_sequences为每个描述向量添加零,以使它们的长度都相同(我使用170作为最大长度,这样就不会缩短描述):
max_seq_length = 170
train_embed = keras.preprocessing.sequence.pad_sequences(train_embed,maxlen = max_seq_length)
test_embed = keras.preprocessing.sequence.pad_sequences(test_embed,maxlen = max_seq_length)
将我们的描述转换为长度相同的矢量后,我们就可以创建我们的嵌入图层并将其输入到深层模型中。
建立深层模型
有两种方法可以创建嵌入层 - 我们可以使用预先训练好的嵌入(有许多开源词嵌入)的权重,或者我们可以从我们的词汇表中学习嵌入。最好对两者进行试验,看看哪一个在数据集上表现更好。这里我们将使用学习的嵌入。
首先,我们将定义输入到深模型的形状。然后我们将它输入到嵌入层。在这里,我使用了一个8维的嵌入图层(您可以尝试调整嵌入图层的维度)。嵌入层的输出将是具有以下形状的三维向量:[批量大小,序列长度(本例中为170),嵌入维数(本例中为8)]。为了将我们的嵌入层连接到密集,全连接的输出层,我们需要首先将其平坦化:
deep_inputs = layers.Input(shape=(max_seq_length,))
embedding = layers.Embedding(vocab_size, 8, input_length=max_seq_length)(deep_inputs)
embedding = layers.Flatten()(embedding)
一旦嵌入层变平,就可以将其填充到模型中并进行编译:
embed_out = layers.Dense(1, activation='linear')(embedding)
deep_model = Model(inputs=deep_inputs, outputs=embed_out)
deep_model.compile(loss='mse', optimizer='adam', metrics=['accuracy'])
第3部分:wide and deep
一旦我们定义了两个模型,将它们结合起来很简单。我们只需创建一个连接每个模型输出的图层,然后将它们合并为一个完全连接的Dense图层,最后定义一个组合模型,将每个模型的输入和输出相结合。显然,由于每个模型都预测同一个东西(价格),所以每个模型的输出或标签都是相同的。还要注意,由于我们模型的输出是一个数值,我们不需要做任何预处理 - 它已经以正确的格式显示出来了:
merged_out = layers.concatenate([wide_model.output,deep_model.output])
merged_out = layers.Dense(1)(merged_out)
combined_model = Model(wide_model.input + [deep_model.input],merged_out)
combined_model.compile(loss = ' mse ',optimizer = ' adam ',metrics = [ ' accuracy ' ])
这样,就可以开展训练和评估了。您可以尝试使用最适合数据集的训练时期和批次大小数量:
# Training
combined_model.fit([description_bow_train, variety_train] + [train_embed], labels_train, epochs=10, batch_size=128)
# Evaluation
combined_model.evaluate([description_bow_test, variety_test] + [test_embed], labels_test, batch_size=128)
在我们的训练模型上产生预测
时间是最重要的部分 - 看看我们的模型如何执行它以前从未见过的数据。为此,我们可以调用predict()我们训练好的模型,并将它传递给我们的测试数据集:
predictions = combined_model.predict([description_bow_test,variety_test] + [test_embed])
然后,我们将比较预测与我们测试数据集中前15个葡萄酒的实际值:
for i in range(15):
val = predictions[i]
print(description_test[i])
print(val[0], 'Actual: ', labels_test.iloc[i], '')
我们来看看我们的测试集中的三个例子:
Powerful vanilla scents rise from the glass, but the fruit, even in this difficult vintage, comes out immediately. It's tart and sharp, with a strong herbal component, and the wine snaps into focus quickly with fruit, acid, tannin, herb and vanilla in equal proportion. Firm and tight, still quite young, this wine needs decanting and/or further bottle age to show its best.
Predicted: 46.233624 Actual: 45.0
A good everyday wine. It's dry, full-bodied and has enough berry-cherry flavors to get by, wrapped into a smooth texture.
Predicted: 9.694958 Actual: 10.0
Here's a modern, round and velvety Barolo (from Monforte d'Alba) that will appeal to those who love a thick and juicy style of wine. The aromas include lavender, allspice, cinnamon, white chocolate and vanilla. Tart berry flavors backed by crisp acidity and firm tannins give the mouthfeel determination and grit.
Predicted: 41.028854 Actual: 49.0
事实证明,葡萄酒的描述和价格之间有一些关系。我们可能无法直观地看到它,但我们的ML模型可以。