# EmbeddingVariable进阶功能：特征准入
## 功能介绍
通过观察发现，当有些特征的频率过低时，对模型的训练效果不会有帮助，还会造成内存浪费以及过拟合的问题。因此需要特征准入功能来过滤掉频率过低的特征。
目前我们支持了两种特征准入的方式：基于Counter的特征准入和基于Bloom Filter的特征准入：

- **基于Counter的特征准入**：基于Counter的准入会记录每个特征在前向中被访问的次数，只有当统计的次出超过准入值的特征才会给分配embedding vector并且在后向中被更新。这种方法的好处子在于会精确的统计每个特征的次数，同时获取频次的查询可以跟查询embedding vector同时完成，因此相比不使用特征准入的时候几乎不会带来额外的时间开销。缺点则是为了减少查询的次数，即使对于不准入的特征，也需要记录对应特征所有的metadata，在准入比例较低的时候相比使用Bloom Filter的方法会有较多额外内存开销。
- **基于Bloom Filter的准入**：基于Bloom Filter的准入是基于Counter Bloom Filter实现的，这种方法的优点是在准入比例较低的情况下，可以比较大地减少内存的使用量。缺点是由于需要多次hash与查询，会带来比较明显的时间开销，同时在准入比例较高的情况下，Blomm filter数据结构带来的内存开销也比较大。
- **未准入特征初始化**：当用户开启准入功能，特征尚未达到准入条件，此时查询到Embedding值为InitializerOption中设置的default_value_no_permission，默认值为0.0。

## 使用方法

用户可以参考下面的方法使用特征准入功能

```python
#使用CBF-based feature filter
filter_option = tf.CBFFilter(filter_freq=3,
                                         max_element_size = 2**30,
                                         false_positive_probability = 0.01,
                                         counter_type=dtypes.int64)
#使用Counter-based feature filter
filter_option = tf.CounterFilter(filter_freq=3)

ev_opt = tf.EmbeddingVariableOption(filter_option=filter_option)
#通过get_embedding_variable接口使用
emb_var = get_embedding_variable("var", embedding_dim = 16, ev_option=ev_opt)

#通过sparse_column_with_embedding接口使用
from tensorflow.contrib.layers.python.layers import feature_column
emb_var = feature_column.sparse_column_wth_embedding("var", ev_option=ev_opt)

emb_var = tf.feature_column.categorical_column_with_embedding("var", ev_option=ev_opt)
```
下面是特征准入接口的定义：
```python
@tf_export(v1=["CounterFilter"])
class CounterFilter(object):
  def __init__(self, filter_freq = 0):
    self.filter_freq = filter_freq
    
@tf_export(v1=["CBFFilter"])
class CBFFilter(object):
  def __init__(self,
               filter_freq = 0,
               max_element_size = 0,
               false_positive_probability = -1.0,
               counter_type = dtypes.uint64)
```
**参数解释**：

- `filter_freq`：这个参数两种filter都有，表示特征的准入值。
- `max_element_size`：特征的数量
- `false_positive_probability`：允许的错误率
- `counter_type`：统计频次的数据类型

BloomFilter的准入参数设置可以参考下面的表，其中m是`bloom filter`的长度，n是`max_element_size`, k是`hash function`的数量，表中的数值是`false_positive_probability`：

![img_1.png](Embedding-Variable/img_1.png)

**功能的开关**：如果构造`EmbeddingVariableOption`对象的时候，如果不传入`CounterFilter`或`CBFFilter`或`filter_freq`设置为0则功能关闭。

**ckpt相关**：对于checkpoint功能，当使用`tf.train.saver`时，无论特征是否准入，都会将其id与频次信息记录在ckpt中，未准入特征的embedding值则不会被保存到ckpt中。在load checkpoint的时候，对于ckpt中未准入的特征，通过比较其频次与filter阈值大小来确定在新一轮训练中是否准入；对于ckpt中已经准入的特征，无论ckpt中的特征频次是否超过了filter阈值，都认为其在新一轮训练中是已经准入的特征。同时ckpt支持向前兼容，即可以读取没有conuter记录的ckpt。目前不支持incremental ckpt。

**关于filter_freq的设置**：目前还需要用户自己根据数据配置。

**特征准入与Embedding多级存储**：由于基于BloomFilter的特征准入功能与Embedding多级存储功能基于不同的计数组件统计特征的频次，同时打开两个功能将导致计数功能出现错误，因此目前无法同时使用基于BloomFilter的特征准入与Embedding多级存储功能。

**收集未准入特征的信息**：

用户如果想要获取一些未准入特征的信息，例如有哪些特征未准入，他们的被访问频次分别是多少，用户可以通过读取ckpt中的内容来获得这些信息。读取checkpoint中内容的方法如下：
```python
from tensorflow.contrib.framework.python.framework import checkpoint_utils
for name, shape in checkpoint_utils.list_variables("xxxxx.ckpt"):
      print('loading... ', name, shape, checkpoint_utils.load_variable("xxxxx.ckpt", name))
```

执行上述代码可以得到如下结果：
![img_2.png](Embedding-Variable/img_2.jpg)
对于EV，在存入ckpt时会被分成9个部分保存，假设EV的名字是var，那么将在ckpt中看到：

- `var-keys`:已准入特征的id
- `var-values`:已准入特征的embedding
- `var-freqs`:已准入特征被查询的次数
- `var-versions`:已准入特征最近一次被更新的global step
- `var-keys_filtered`:未准入特征的id
- `var-freqs_filtered`:未准入特征被查询的频次
- `var-versions_filtered`:未准入特征最近一次被更新的global step
- `var-partition_offset`:用于恢复已准入特征的参数
- `var-partition_filter_offset`:用于恢复未准入特征的参数

用户通过读取filtered相关的参数就可以收集未准入特征的信息。

**是否保存未准入特征的相关信息**

用户有时在保存ckpt时不需要保存未准入特征的信息（例如未准入特征的信息在推理时是不需要的，因此可以不保存）。用户可以通过配置`TF_EV_SAVE_FILTERED_FEATURES`环境变量为`False`来设置不保存未准入特征的相关信息，从而减少checkpoint的大小。

**去掉checkpoint中未准入特征的相关信息**

对于即将上线服务的模型，checkpoint中未准入特征的信息是无用的，因此DeepRec提供了工具可以去除checkpoint中未准入特征的信息。在编译安装完DeepRec后，用户可以通过如下命令使用工具：
```bash
python Deeprec/tensorflow/python/tools/shrink_ckpt_with_filtered_features.py --input_checkpoint /root/code/model.ckpt --output_checkpoint /root/shrink.ckpt
```
该工具具有如下参数：

- `input_checkpoint`:输入checkpoint的完整路径，必填
- `output_checkpoint`:生成checkpoint的完整路径，必填
