假设我有一个数据框架,其中有几个数字变量和1个分类变量,有10000个类别。我使用Keras的神经网络来获取分类变量的嵌入矩阵。嵌入大小为50,所以Keras返回的矩阵的维数为 10002 x 50
. 额外的2行是未知类别,另一行我也不清楚--这是Keras工作的唯一方式,即。
model_i = keras.layers.Embedding(input_dim=num_categories+2, output_dim=embedding_size, input_length=1,
name=f'embedding_{cat_feature}')(input_i)
也就是在不使用 +2
它将无法工作。
所以,我有一个约12M行的训练集和约1M行的验证集.现在,我想到的重建嵌入的方法是。
NaN
列到数据框架i
在range(10002)(即类别数+2)中寻找对应的键值。i
在反转的字典中,如果它在字典中,使用pandas的 .loc
替换每一行(在这50 NaN
列)的值,对应于 i
(即分类变量等于类别名称,其中 i
编码的)与相应的行向量一起从 10002 x 50
矩阵。这个解决方案的问题是,它的效率很低。一位朋友告诉我另一种解决方案,它包括将分类变量转换为一热稀疏矩阵,维度为 12M x 10000
为训练集),然后使用矩阵乘法与嵌入矩阵,该矩阵的维数应该是 10000 x 50
从而获得 12M x 50
矩阵,然后我可以将其连接到我的原始数据框。这里的问题是。
num_categories + 2
矩阵Keras给我的是10000行,而不是)行。于是又出现了维度不匹配的情况。有谁知道更好的方法或者能解决第二种方法中的问题吗?我的最终目标是拥有一个包含所有变量的数据框架,其中包括 减去分类变量 而不是有另外50列的行向量,代表该分类变量的嵌入。
所以最终我找到了我帖子中提到的第二种方法的解决方案。使用稀疏矩阵可以避免在尝试对大数据(类别和或观测值)的矩阵进行乘法时可能出现的内存问题。我写了这个函数,它可以返回原始数据框,并附加所有所需的分类变量的嵌入向量。
def get_embeddings(model: keras.models.Model, cat_vars: List[str], df: pd.DataFrame,
dict: Dict[str, Dict[str, int]]) -> pd.DataFrame:
df_list: List[pd.DataFrame] = [df]
for var_name in cat_vars:
df_1vec: pd.DataFrame = df.loc[:, var_name]
enc = OneHotEncoder()
sparse_mat = enc.fit_transform(df_1vec.values.reshape(-1, 1))
sparse_mat = sparse.csr_matrix(sparse_mat, dtype='uint8')
orig_dict = dict[var_name]
match_to_arr = np.empty(
(sparse_mat.shape[1], model.get_layer(f'embedding_{var_name}').get_weights()[0].shape[1]))
match_to_arr[:] = np.nan
unknown_cat = model.get_layer(f'embedding_{var_name}').get_weights()[0].shape[0] - 1
for i, col in enumerate(tqdm.tqdm(enc.categories_[0])):
if col in orig_dict.keys():
val = orig_dict[col]
match_to_arr[i, :] = model.get_layer(f'embedding_{var_name}').get_weights()[0][val, :]
else:
match_to_arr[i, :] = (model.get_layer(f'embedding_{var_name}')
.get_weights()[0][unknown_cat, :])
a = sparse_mat.dot(match_to_arr)
a = pd.DataFrame(a, columns=[f'{var_name}_{i}' for i in range(1, match_to_arr.shape[1] + 1)])
df_list.append(a)
df_final = pd.concat(df_list, axis=1)
return df_final
dict
是一个字典的字典,即持有我事先编码的每个分类变量的字典,键是类别名称,值是整数。需要注意的是,每个类别的编码是用 num_values + 1
最后一个是为未知类别保留的。
基本上我所做的是询问每个类别值是否在字典中。如果它在字典中,我将临时数组中的相应行(所以如果这是第一个类别,那么第一行)分配给嵌入矩阵中的相应行,其中行号对应于类别名称被编码的值。i
th row)嵌入矩阵的最后一行,对应未知类别。
这就是我在评论中介绍的
df = pd.DataFrame({'int':np.random.uniform(0,1, 10),'cat':np.random.randint(0,333, 10)}) # cat are encoded
## define embedding model, you can also use multiple input source
inp = Input((1))
emb = Embedding(input_dim=10000+2, output_dim=50, name='embedding')(inp)
out = Dense(10)(emb)
model = Model(inp, out)
# model.compile(...)
# model.fit(...)
## get cat embeddings
extractor = Model(model.input, Flatten()(model.get_layer('embedding').output))
## concat embedding in the orgiginal df
df = pd.concat([df, pd.DataFrame(extractor.predict(df.cat.values))], axis=1)
df