在Python中提取分类特征的嵌入回到原始数据框架中去

问题描述 投票:1回答:1

假设我有一个数据框架,其中有几个数字变量和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行的验证集.现在,我想到的重建嵌入的方法是。

  1. 有一个反转的字典,以数值(之前的编码代表类别)为键,类别名称为值。
  2. 加50 NaN 列到数据框架
  3. 对于 i 在range(10002)(即类别数+2)中寻找对应的键值。i 在反转的字典中,如果它在字典中,使用pandas的 .loc替换每一行(在这50 NaN 列)的值,对应于 i (即分类变量等于类别名称,其中 i 编码的)与相应的行向量一起从 10002 x 50 矩阵。

这个解决方案的问题是,它的效率很低。一位朋友告诉我另一种解决方案,它包括将分类变量转换为一热稀疏矩阵,维度为 12M x 10000 为训练集),然后使用矩阵乘法与嵌入矩阵,该矩阵的维数应该是 10000 x 50 从而获得 12M x 50 矩阵,然后我可以将其连接到我的原始数据框。这里的问题是。

  1. 在验证集上无法使用 因为出现在验证集上的类别数量可能与训练集不同 所以维度不匹配。
  2. 即使用在训练集上,我的数据也有10002(=)num_categories + 2矩阵Keras给我的是10000行,而不是)行。于是又出现了维度不匹配的情况。

有谁知道更好的方法或者能解决第二种方法中的问题吗?我的最终目标是拥有一个包含所有变量的数据框架,其中包括 减去分类变量 而不是有另外50列的行向量,代表该分类变量的嵌入。

python keras neural-network categorical-data embedding
1个回答
1
投票

所以最终我找到了我帖子中提到的第二种方法的解决方案。使用稀疏矩阵可以避免在尝试对大数据(类别和或观测值)的矩阵进行乘法时可能出现的内存问题。我写了这个函数,它可以返回原始数据框,并附加所有所需的分类变量的嵌入向量。

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 最后一个是为未知类别保留的。

基本上我所做的是询问每个类别值是否在字典中。如果它在字典中,我将临时数组中的相应行(所以如果这是第一个类别,那么第一行)分配给嵌入矩阵中的相应行,其中行号对应于类别名称被编码的值。ith row)嵌入矩阵的最后一行,对应未知类别。


0
投票

这就是我在评论中介绍的

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
© www.soinside.com 2019 - 2024. All rights reserved.