将分类特征的嵌入提取回Python中的原始数据框

假设我有一个包含多个数值变量和一个具有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行的训练集和约有100万行的验证集。 现在,我想到的重新构造嵌入的方式是:

  1. 具有一个反向字典,其中数字值(以前编码为表示类别)作为键,而类别名称作为值
  2. 向数据框中添加50 NaN
  3. 对于范围(10002)(类别数+ 2)中的i,在反向字典中查找键i的对应值,如果它在字典中,则使用pandas .loc,替换与NaN的值相对应的每一行(在这50 i列中)(即,分类变量等于i的类别名称使用10002 x 50矩阵中的相应行向量进行编码。

此解决方案的问题在于效率极低。
一位朋友告诉我另一种解决方案,包括将分类变量转换为维度为12M x 10000的单热稀疏矩阵(对于训练集),然后对应具有维度{{ 1}}从而得到一个10000 x 50矩阵,然后可以将其连接到原始数据帧。这里的问题是:

  1. 它不适用于验证集,因为那里出现的类别数量与训练中出现的类别数量可能不同,因此维度不匹配。
  2. 即使在训练集上使用时,Keras给我的矩阵中也有10002(= {12M x 50)行,而不是10000。因此,尺寸也不匹配。

有人知道更好的方法还是可以解决第二种方法中的问题?
我的最终目标是拥有一个数据框,其中包含所有我的变量减去分类变量,相反,还有另外50列带有行向量的列,这些行向量表示该分类变量的嵌入。

milleralong 回答:将分类特征的嵌入提取回Python中的原始数据框

因此,最终我找到了帖子中提到的第二种方法的解决方案。使用稀疏矩阵可以避免在尝试将矩阵与大数据(类别和/或观察值)相乘时可能发生的内存问题。
我编写了此函数,该函数返回原始数据帧,并附加了所有所需的分类变量的嵌入矢量。

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行)。

,

这是我在评论中介绍的

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
本文链接:https://www.f2er.com/2332442.html

大家都在问