作者 | CDA数据分析师
像Keras中的机器学习和深度学习模型一样,要求所有输入和输出变量均为数字。
这意味着,如果你的数据包含分类数据,则必须先将其编码为数字,然后才能拟合和评估模型。
两种最流行的技术是整数编码和一种热编码,尽管一种称为学习嵌入的较新技术可能在这两种方法之间提供了有用的中间立场。
在本教程中,您将发现在Keras中开发神经网络模型时如何编码分类数据。
完成本教程后,您将知道:
让我们开始吧。
本教程分为五个部分。分别是:
类别变量是其值采用标签值的变量。
例如,变量可以是“ color ”,并且可以取值“ red ”,“ green ”和“ blue”。
有时,分类数据可能在类别之间具有排序的关系,例如“ 第一 ”,“ 第二 ”和“ 第三”。这种类型的分类数据称为序数,并且其他排序信息可能很有用。
机器学习算法和深度学习神经网络要求输入和输出变量是数字。
这意味着必须先将分类数据编码为数字,然后才能使用它来拟合和评估模型。
有多种编码分类变量以进行建模的方法,尽管最常见的三种方法如下:
我们将仔细研究如何使用以下每种方法对分类数据进行编码,以在Keras中训练深度学习神经网络。
作为本教程的基础,我们将使用自1980年代以来在机器学习中广泛研究的所谓“ 乳腺癌”数据集。
该数据集将乳腺癌患者数据分类为癌症复发或无复发。有286个示例和9个输入变量。这是一个二进制分类问题。
该数据集上合理的分类准确性得分在68%到73%之间。我们将针对该区域,但请注意,本教程中的模型并未经过优化:它们旨在演示编码方案。
查看数据,我们可以看到所有九个输入变量都是分类的。
具体来说,所有变量都用引号引起来;有些是序数,有些不是。
'40-49','premeno','15-19','0-2','yes','3','right','left_up','no','recurrence-events'
'50-59','ge40','15-19','0-2','no','1','right','central','no','no-recurrence-events'
'50-59','ge40','35-39','0-2','no','2','left','left_low','no','recurrence-events'
'40-49','premeno','35-39','0-2','yes','3','right','left_low','yes','no-recurrence-events'
'40-49','premeno','30-34','3-5','yes','2','left','right_up','no','recurrence-events'
我们可以使用Pandas库将该数据集加载到内存中。
# load the dataset as a pandas DataFrame
data = read_csv(filename, header=None)
# retrieve numpy array
dataset = data.values
加载后,我们可以将列分为输入(X)和输出(y)进行建模。
# split into input (X) and output (y) variables
X = dataset[:, :-1]
y = dataset[:,-1]
最后,我们可以将输入数据中的所有字段都强制为字符串,以防万一熊猫试图将某些字段自动映射为数字(确实如此)。
我们还可以将输出变量整形为一列(例如2D形状)。
# format all fields as string
X = X.astype(str)
# reshape target to be a 2d array
y = y.reshape((len(y), 1))
我们可以将所有这些结合到一个有用的功能中,以备后用。
# load the dataset
def load_dataset(filename):
# load the dataset as a pandas DataFrame
data = read_csv(filename, header=None)
# retrieve numpy array
dataset = data.values
# split into input (X) and output (y) variables
X = dataset[:, :-1]
y = dataset[:,-1]
# format all fields as string
X = X.astype(str)
# reshape target to be a 2d array
y = y.reshape((len(y), 1))
return X, y
加载后,我们可以将数据分为训练集和测试集,以便我们可以拟合和评估深度学习模型。
我们将使用scikit-learn中的train_test_split()函数,并将67%的数据用于训练,将33%的数据用于测试。
# load the dataset
X, y = load_dataset('breast-cancer.csv')
# split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
将所有这些元素结合在一起,下面列出了加载,拆分和汇总原始分类数据集的完整示例。
# load and summarize the dataset
from pandas import read_csv
from sklearn.model_selection import train_test_split
# load the dataset
def load_dataset(filename):
# load the dataset as a pandas DataFrame
data = read_csv(filename, header=None)
# retrieve numpy array
dataset = data.values
# split into input (X) and output (y) variables
X = dataset[:, :-1]
y = dataset[:,-1]
# format all fields as string
X = X.astype(str)
# reshape target to be a 2d array
y = y.reshape((len(y), 1))
return X, y
# load the dataset
X, y = load_dataset('breast-cancer.csv')
# split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
# summarize
print('Train', X_train.shape, y_train.shape)
print('Test', X_test.shape, y_test.shape)
运行示例将报告训练和测试集的输入和输出元素的大小。
我们可以看到,我们有191个示例用于培训,而95个用于测试。
Train (191, 9) (191, 1)
Test (95, 9) (95, 1)
既然我们已经熟悉了数据集,那么让我们看一下如何对它进行编码以进行建模。
顺序编码涉及将每个唯一标签映射到整数值。
这样,有时将其简称为整数编码。
这种类型的编码实际上仅在类别之间存在已知关系时才适用。
数据集中的某些变量确实存在这种关系,理想情况下,在准备数据时应利用此关系。
在这种情况下,我们将忽略任何可能存在的序数关系,并假定所有变量都是类别变量。至少将序数编码用作其他编码方案的参考点仍然会有所帮助。
我们可以使用scikit-learn的scikit-learn将每个变量编码为整数。这是一个灵活的类,并且允许将类别的顺序指定为参数(如果已知这样的顺序)。
注意:我将作为练习来更新以下示例,以尝试为具有自然顺序的变量指定顺序,并查看其是否对模型性能产生影响。
对变量进行编码的最佳实践是使编码适合训练数据集,然后将其应用于训练和测试数据集。
下面的函数prepare_inputs(),获取训练和测试集的输入数据,并使用序数编码对其进行编码。
# prepare input data
def prepare_inputs(X_train, X_test):
oe = OrdinalEncoder()
oe.fit(X_train)
X_train_enc = oe.transform(X_train)
X_test_enc = oe.transform(X_test)
return X_train_enc, X_test_enc
我们还需要准备目标变量。
这是一个二进制分类问题,因此我们需要将两个类标签映射到0和1。
这是一种序数编码,scikit-learn提供了为此专门设计的LabelEncoder类。尽管LabelEncoder设计用于编码单个变量,但我们可以轻松使用OrdinalEncoder并获得相同的结果。
在*()prepare_targets*整数编码的训练集和测试集的输出数据。
# prepare target
def prepare_targets(y_train, y_test):
le = LabelEncoder()
le.fit(y_train)
y_train_enc = le.transform(y_train)
y_test_enc = le.transform(y_test)
return y_train_enc, y_test_enc
我们可以调用这些函数来准备我们的数据。
# prepare input data
X_train_enc, X_test_enc = prepare_inputs(X_train, X_test)
# prepare output data
y_train_enc, y_test_enc = prepare_targets(y_train, y_test)
现在我们可以定义一个神经网络模型。
在所有这些示例中,我们将使用相同的通用模型。具体来说,是一种多层感知器(MLP)神经网络,其中的一个隐藏层具有10个节点,而输出层中的一个节点用于进行二进制分类。
无需赘述,下面的代码定义了模型,将其拟合在训练数据集上,然后在测试数据集上对其进行了评估。
# define the model
model = Sequential()
model.add(Dense(10, input_dim=X_train_enc.shape[1], activation='relu', kernel_initializer='he_normal'))
model.add(Dense(1, activation='sigmoid'))
# compile the keras model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit the keras model on the dataset
model.fit(X_train_enc, y_train_enc, epochs=100, batch_size=16, verbose=2)
# evaluate the keras model
_, accuracy = model.evaluate(X_test_enc, y_test_enc, verbose=0)
print('Accuracy: %.2f' % (accuracy*100))
综合所有这些,下面列出了使用序数编码准备数据并拟合和评估数据上的神经网络的完整示例。
# example of ordinal encoding for a neural network
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OrdinalEncoder
from keras.models import Sequential
from keras.layers import Dense
# load the dataset
def load_dataset(filename):
# load the dataset as a pandas DataFrame
data = read_csv(filename, header=None)
# retrieve numpy array
dataset = data.values
# split into input (X) and output (y) variables
X = dataset[:, :-1]
y = dataset[:,-1]
# format all fields as string
X = X.astype(str)
# reshape target to be a 2d array
y = y.reshape((len(y), 1))
return X, y
# prepare input data
def prepare_inputs(X_train, X_test):
oe = OrdinalEncoder()
oe.fit(X_train)
X_train_enc = oe.transform(X_train)
X_test_enc = oe.transform(X_test)
return X_train_enc, X_test_enc
# prepare target
def prepare_targets(y_train, y_test):
le = LabelEncoder()
le.fit(y_train)
y_train_enc = le.transform(y_train)
y_test_enc = le.transform(y_test)
return y_train_enc, y_test_enc
# load the dataset
X, y = load_dataset('breast-cancer.csv')
# split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
# prepare input data
X_train_enc, X_test_enc = prepare_inputs(X_train, X_test)
# prepare output data
y_train_enc, y_test_enc = prepare_targets(y_train, y_test)
# define the model
model = Sequential()
model.add(Dense(10, input_dim=X_train_enc.shape[1], activation='relu', kernel_initializer='he_normal'))
model.add(Dense(1, activation='sigmoid'))
# compile the keras model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit the keras model on the dataset
model.fit(X_train_enc, y_train_enc, epochs=100, batch_size=16, verbose=2)
# evaluate the keras model
_, accuracy = model.evaluate(X_test_enc, y_test_enc, verbose=0)
print('Accuracy: %.2f' % (accuracy*100))
在任何现代硬件(无需GPU)上运行示例,只需几秒钟即可使模型适应模型。
在每个训练时期结束时报告模型的损失和准确性,最后报告测试数据集上模型的准确性。
鉴于学习算法的随机性,您的具体结果会有所不同。尝试运行该示例几次。
在这种情况下,我们可以看到该模型在测试数据集上达到了约70%的精度。
不错,因为只有某些输入变量存在序数关系,对于某些输入变量才存在序数关系,因此在编码中不遵循序数关系。
...
Epoch 95/100
- 0s - loss: 0.5349 - acc: 0.7696
Epoch 96/100
- 0s - loss: 0.5330 - acc: 0.7539
Epoch 97/100
- 0s - loss: 0.5316 - acc: 0.7592
Epoch 98/100
- 0s - loss: 0.5302 - acc: 0.7696
Epoch 99/100
- 0s - loss: 0.5291 - acc: 0.7644
Epoch 100/100
- 0s - loss: 0.5277 - acc: 0.7644
Accuracy: 70.53
这为处理分类数据提供了一个很好的起点。
更好和更通用的方法是使用一种热编码。
一种热编码适用于类别之间不存在关系的类别数据。
它涉及用二进制向量表示每个分类变量,该二进制向量的每个唯一标签都有一个元素,并用1标记所有类标签,所有其他元素标记为0。
例如,如果我们的变量是“ color ”,标签是“ red ”,“ green ”和“ blue ”,我们将这些标签中的每一个编码为三元素二进制矢量,如下所示:
然后,数据集中的每个标签将替换为一个向量(一列变为三列)。对所有类别变量都进行了此操作,因此在乳腺癌数据集的情况下,我们的9个输入变量或列变为43。
scikit-learn库提供了OneHotEncoder,可以自动对一个或多个变量进行热编码。
下面的prepare_inputs()函数为上一节中的示例提供了一个嵌入式替换函数。代替使用OrdinalEncoder,它使用OneHotEncoder。
# prepare input data
def prepare_inputs(X_train, X_test):
ohe = OneHotEncoder()
ohe.fit(X_train)
X_train_enc = ohe.transform(X_train)
X_test_enc = ohe.transform(X_test)
return X_train_enc, X_test_enc
结合在一起,下面列出了一个热编码乳腺癌分类数据集并使用神经网络进行建模的完整示例。
# example of one hot encoding for a neural network
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from keras.models import Sequential
from keras.layers import Dense
# load the dataset
def load_dataset(filename):
# load the dataset as a pandas DataFrame
data = read_csv(filename, header=None)
# retrieve numpy array
dataset = data.values
# split into input (X) and output (y) variables
X = dataset[:, :-1]
y = dataset[:,-1]
# format all fields as string
X = X.astype(str)
# reshape target to be a 2d array
y = y.reshape((len(y), 1))
return X, y
# prepare input data
def prepare_inputs(X_train, X_test):
ohe = OneHotEncoder()
ohe.fit(X_train)
X_train_enc = ohe.transform(X_train)
X_test_enc = ohe.transform(X_test)
return X_train_enc, X_test_enc
# prepare target
def prepare_targets(y_train, y_test):
le = LabelEncoder()
le.fit(y_train)
y_train_enc = le.transform(y_train)
y_test_enc = le.transform(y_test)
return y_train_enc, y_test_enc
# load the dataset
X, y = load_dataset('breast-cancer.csv')
# split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
# prepare input data
X_train_enc, X_test_enc = prepare_inputs(X_train, X_test)
# prepare output data
y_train_enc, y_test_enc = prepare_targets(y_train, y_test)
# define the model
model = Sequential()
model.add(Dense(10, input_dim=X_train_enc.shape[1], activation='relu', kernel_initializer='he_normal'))
model.add(Dense(1, activation='sigmoid'))
# compile the keras model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit the keras model on the dataset
model.fit(X_train_enc, y_train_enc, epochs=100, batch_size=16, verbose=2)
# evaluate the keras model
_, accuracy = model.evaluate(X_test_enc, y_test_enc, verbose=0)
print('Accuracy: %.2f' % (accuracy*100))
该示例对输入的分类数据进行热编码,并像上一节中一样对目标变量进行标签编码。然后将相同的神经网络模型拟合到准备的数据集上。
鉴于学习算法的随机性,您的具体结果会有所不同。尝试运行该示例几次。
在这种情况下,该模型的性能相当好,达到约72%的准确度,与上一节中看到的相近。
比较公平的比较是将每个配置运行10或30次,并使用平均精度比较性能。回想一下,在本教程中,我们更专注于如何对分类数据进行编码,而不是在此特定数据集上获得最佳分数。
...
Epoch 95/100
- 0s - loss: 0.3837 - acc: 0.8272
Epoch 96/100
- 0s - loss: 0.3823 - acc: 0.8325
Epoch 97/100
- 0s - loss: 0.3814 - acc: 0.8325
Epoch 98/100
- 0s - loss: 0.3795 - acc: 0.8325
Epoch 99/100
- 0s - loss: 0.3788 - acc: 0.8325
Epoch 100/100
- 0s - loss: 0.3773 - acc: 0.8325
Accuracy: 72.63
序数和一种热编码可能是两种最受欢迎的方法。
一种较新的技术类似于一种热编码,被设计用于神经网络,称为学习嵌入。
学习的嵌入或简称为“ 嵌入 ”是分类数据的分布式表示。
每个类别都映射到一个不同的向量,并且在训练神经网络时可以调整或学习向量的属性。向量空间提供类别的投影,从而允许那些接近或相关的类别自然地聚在一起。
通过允许从数据中学习任何顺序的关系,这既提供了顺序关系的好处,又提供了为每个类别提供矢量表示的一种热编码。与一种热编码不同,输入向量不是稀疏的(没有很多零)。缺点是它需要学习作为模型的一部分,并需要创建更多的输入变量(列)。
该技术最初是为提供单词的分布式表示而开发的,例如允许相似的单词具有相似的矢量表示。这样,该技术通常被称为单词嵌入,并且在文本数据的情况下,已经开发了算法来学习独立于神经网络的表示。
使用嵌入的另一个好处是,每个类别映射到的学习向量可以适合具有中等技能的模型,但是可以提取这些向量并将其通常用作一系列不同模型上类别的输入,并且应用程序。即,它们可以被学习和重用。
可以通过嵌入层在Keras中使用嵌入。
每个类别变量都需要一个嵌入层,并且尽管类别之间没有任何关系,但是嵌入期望类别是按序编码的。
每次嵌入还需要用于分布式表示(向量空间)的维数。在自然语言应用程序中,通常使用50、100或300个尺寸。对于我们的小示例,我们将维数固定为10,但这是任意的;您应该尝试其他值。
首先,我们可以使用序数编码来准备输入数据。
我们将开发的模型将为每个输入变量分别嵌入一个。因此,该模型将采用九个不同的输入数据集。这样,我们将分别使用LabelEncoder拆分输入变量和序数编码(整数编码),并返回单独准备的训练和测试输入数据集的列表。
下面的*prepare_inputs()*函数实现了此功能,枚举每个输入变量,使用最佳实践对每个变量进行正确的整数编码,并返回已编码的训练变量和测试变量(或单变量数据集)的列表,这些列表可稍后用作我们模型的输入。
...
Epoch 95/100
- 0s - loss: 0.3837 - acc: 0.8272
Epoch 96/100
- 0s - loss: 0.3823 - acc: 0.8325
Epoch 97/100
- 0s - loss: 0.3814 - acc: 0.8325
Epoch 98/100
- 0s - loss: 0.3795 - acc: 0.8325
Epoch 99/100
- 0s - loss: 0.3788 - acc: 0.8325
Epoch 100/100
- 0s - loss: 0.3773 - acc: 0.8325
Accuracy: 72.63
现在我们可以构建模型。
在这种情况下,我们必须以不同的方式构造模型,因为我们将有九个输入层,其中有九个嵌入,在将它们的输出(九个不同的10元素向量)作为输入传递给密集层之前,其输出(九个不同的10元素向量)必须串联在一起。
我们可以使用功能性的Keras API实现此目的。
首先,我们可以枚举每个变量并构造一个输入层,并将其连接到嵌入层,并将这两个层存储在列表中。定义模型时,我们需要引用所有输入层,并且需要引用每个嵌入层以将它们集中到合并层。
...
# prepare each input head
in_layers = list()
em_layers = list()
for i in range(len(X_train_enc)):
# calculate the number of unique inputs
n_labels = len(unique(X_train_enc[i]))
# define input layer
in_layer = Input(shape=(1,))
# define embedding layer
em_layer = Embedding(n_labels, 10)(in_layer)
# store layers
in_layers.append(in_layer)
em_layers.append(em_layer)
然后,我们可以合并所有嵌入层,定义隐藏层和输出层,然后定义模型。
...
# concat all embeddings
merge = concatenate(em_layers)
dense = Dense(10, activation='relu', kernel_initializer='he_normal')(merge)
output = Dense(1, activation='sigmoid')(dense)
model = Model(inputs=in_layers, outputs=output)
当使用具有多个输入的模型时,我们将需要指定一个列表,每个输入具有一个数据集,例如,对于我们的数据集,九个数组的列表每个数组每一列。幸运的是,这是我们从*prepare_inputs()*函数返回的格式。
因此,拟合和评估模型的过程与上一部分类似。
另外,我们将通过调用plot_model()函数来绘制模型并将其保存到文件中。这需要安装pygraphviz和pydot,这在某些系统上可能会很麻烦。如果遇到问题,请注释掉import语句并调用plot_model()。
...
# compile the keras model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# plot graph
plot_model(model, show_shapes=True, to_file='embeddings.png')
# fit the keras model on the dataset
model.fit(X_train_enc, y_train_enc, epochs=20, batch_size=16, verbose=2)
# evaluate the keras model
_, accuracy = model.evaluate(X_test_enc, y_test_enc, verbose=0)
print('Accuracy: %.2f' % (accuracy*100))
综上所述,下面列出了为多输入层模型中的每个分类输入变量使用单独的嵌入的完整示例。
# example of learned embedding encoding for a neural network
from numpy import unique
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers import Embedding
from keras.layers.merge import concatenate
from keras.utils import plot_model
# load the dataset
def load_dataset(filename):
# load the dataset as a pandas DataFrame
data = read_csv(filename, header=None)
# retrieve numpy array
dataset = data.values
# split into input (X) and output (y) variables
X = dataset[:, :-1]
y = dataset[:,-1]
# format all fields as string
X = X.astype(str)
# reshape target to be a 2d array
y = y.reshape((len(y), 1))
return X, y
# prepare input data
def prepare_inputs(X_train, X_test):
X_train_enc, X_test_enc = list(), list()
# label encode each column
for i in range(X_train.shape[1]):
le = LabelEncoder()
le.fit(X_train[:, i])
# encode
train_enc = le.transform(X_train[:, i])
test_enc = le.transform(X_test[:, i])
# store
X_train_enc.append(train_enc)
X_test_enc.append(test_enc)
return X_train_enc, X_test_enc
# prepare target
def prepare_targets(y_train, y_test):
le = LabelEncoder()
le.fit(y_train)
y_train_enc = le.transform(y_train)
y_test_enc = le.transform(y_test)
return y_train_enc, y_test_enc
# load the dataset
X, y = load_dataset('breast-cancer.csv')
# split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
# prepare input data
X_train_enc, X_test_enc = prepare_inputs(X_train, X_test)
# prepare output data
y_train_enc, y_test_enc = prepare_targets(y_train, y_test)
# make output 3d
y_train_enc = y_train_enc.reshape((len(y_train_enc), 1, 1))
y_test_enc = y_test_enc.reshape((len(y_test_enc), 1, 1))
# prepare each input head
in_layers = list()
em_layers = list()
for i in range(len(X_train_enc)):
# calculate the number of unique inputs
n_labels = len(unique(X_train_enc[i]))
# define input layer
in_layer = Input(shape=(1,))
# define embedding layer
em_layer = Embedding(n_labels, 10)(in_layer)
# store layers
in_layers.append(in_layer)
em_layers.append(em_layer)
# concat all embeddings
merge = concatenate(em_layers)
dense = Dense(10, activation='relu', kernel_initializer='he_normal')(merge)
output = Dense(1, activation='sigmoid')(dense)
model = Model(inputs=in_layers, outputs=output)
# compile the keras model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# plot graph
plot_model(model, show_shapes=True, to_file='embeddings.png')
# fit the keras model on the dataset
model.fit(X_train_enc, y_train_enc, epochs=20, batch_size=16, verbose=2)
# evaluate the keras model
_, accuracy = model.evaluate(X_test_enc, y_test_enc, verbose=0)
print('Accuracy: %.2f' % (accuracy*100))
运行示例将如上所述准备数据,拟合模型并报告性能。
鉴于学习算法的随机性,您的具体结果会有所不同。尝试运行该示例几次。
在这种情况下,该模型的性能相当好,与上一节中针对一种热编码的代码相匹配。
由于在一个熟练的模型中对学习的向量进行了训练,因此可以保存它们,并将其用作对相同数据进行操作的其他模型中这些变量的一般表示。探索这种编码的有用且令人信服的理由。
...
Epoch 15/20
- 0s - loss: 0.4891 - acc: 0.7696
Epoch 16/20
- 0s - loss: 0.4845 - acc: 0.7749
Epoch 17/20
- 0s - loss: 0.4783 - acc: 0.7749
Epoch 18/20
- 0s - loss: 0.4763 - acc: 0.7906
Epoch 19/20
- 0s - loss: 0.4696 - acc: 0.7906
Epoch 20/20
- 0s - loss: 0.4660 - acc: 0.7958
Accuracy: 72.63
为了确认我们对模型的理解,将创建一个图并将其保存到当前工作目录中的embeddings.png文件中。
该图显示了九个输入,每个输入映射到一个10元素向量,这意味着模型的实际输入是90元素向量。