召回链路一般都是使用多路召回,例如文本召回,向量召回,KV召回等等。

1. 面向接口编程,而非实现

还是那句话,客户端并怎么不关心召回策略的具体实现,他只关心接口。通过这个接口它可以得到自己想要的召回结果。

class Recall {
public:
    explicit Recall(const std::string& name) : m_name(name) {}
    virtual ~Recall() = default;
    virtual void recall(int depth) const = 0;
private:
    std::string m_name;
};

我们定义一个召回策略的接口,后续所有策略都继承这个接口,其中m_name是策略的名称,recall是召回函数。

2. 实现接口

// 文本召回
class InvertRecall : public Recall {
public:
    explicit InvertRecall(const std::string& name) : Recall(name) {}
    void recall(int depth) const override {
        std::cout << std::string(depth, ' ') << "-" << name << std::endl;
    }
};

// 向量召回
class VectorRecall : public Recall {
public:
    explicit VectorRecall(const std::string& name) : Recall(name) {}
    void recall(int depth) const override {
        std::cout << std::string(depth, ' ') << "-" << name << std::endl;
    }
};

// KV召回
class KvRecall : public Recall {
public:
    explicit KvRecall(const std::string& name) : Recall(name) {}
    void recall(int depth) const override {
        std::cout << std::string(depth, ' ') << "-" << name << std::endl;
    }
};

3. 客户端调用

客户端调用也非常的方便,只要调用出自己想要的召回策略即可

Recall invertRecall = new InvertRecall("invert");
Recall vectorRecall = new VectorRecall("vector");
Recall kvRecall = new KvRecall("kv");

invertRecall->recall(0);
vectorRecall->recall(0);
kvRecall->recall(0);

增加一些策略

每一个召回策略都可以往下细分,例如向量召回又有FM召回和DSSM召回,FM召回呢又衍生了AFM和FFM,KV召回具体又有I2I召回,I2I召回又衍生了Item2vec和swingI2I算法等等。
我们之前所说的向量召回,文本召回,KV召回都是大类,这里面包含了更细粒度的分类和实现方法。

// 向量召回策略
Recall dssmRecall = new DSSMRecall("dssm");
Recall afmRecall = new AFMRecall("afm");
Recall ffmRecall = new FFMRecall("ffm");
// KV召回测录
Recall swingRecall = new SwingRecall("swing");
Recall item2vecRecall = new Item2VecRecall("item2vec");

// 倒排召回
Recall boolRecall = new BoolRecall("bool");


dssmRecall->recall(0);
afmRecall->recall(0);
ffmRecall->recall(0);
swingRecall->recall(0);
item2vecRecall->recall(0);
boolRecall->recall(0);

客户端需要调用所有的召回策略,每次新增都需要调用新的召回策略算法的recall方法。

5. 多态分组调用

一种极其简单的方法是增加一个数组,这可是多态常用的一种方法。

std::vector<Recall*> strategys{dssmRecall, afmRecall, ffmRecall, swingRecall, item2vecRecall, boolRecall};

for(auto strategy: strategys) {
    strategy->recall(0);
}

如果你愿意更进一步的话,还可以对这些召回策略进行分组,例如分为向量召回,文本召回,kv召回三个组,这样会更加清晰,想要增加什么哪个类的策略就放到哪个数组里即可

std::vector<Recall*> vectorStrategys{dssmRecall, afmRecall, ffmRecall};
std::vector<Recall*> invertStrategys{boolRecall};
std::vector<Recall*> kvStrategys{swingRecall, item2vecRecall};
for(auto strategy: vectorStrategys) {
    strategy->recall(0);
}
for(auto strategy: invertStrategys) {
    strategy->recall(0);
}
for(auto strategy: kvStrategys) {
    strategy->recall(0);
}

对客户端调用来说也更加清晰了。

6. 组合模式

用组合模式对上面进行进一步优化

// 召回接口
class Recall {
public:
    explicit Recall(const std::string& name) : m_name(name) {}
    virtual ~Recall() = default;
    virtual add(Recall* recall) {}
    virtual void recall(int depth) const = 0;
private:
    std::string m_name;
};

// 召回策略组
class RecallGroup : public Recall {
private:
    std::vector<Recall*> children;
public:
    explicit RecallGroup(const std::string& name) : RecallGroup(name) {}
    void add(Recall* recall) {
        children.push_back(recall);
    }
    void recall(int depth) const override {
        std::cout << std::string(depth, ' ') << "+" << name << std::endl;
        for (auto& child : children) {
            child->recall(depth + 4);
        }
    }
};

客户端调用

// 向量召回策略
Recall vectorGroup = new RecallGroup("vector");
Recall dssmRecall = new DSSMRecall("dssm");
vectorGroup.add(dssmRecall);
Recall fmGroup = new RecallGroup("fm");
Recall afmRecall = new AFMRecall("afm");
Recall ffmRecall = new FFMRecall("ffm");
fmGroup.add(afmRecall);
fmGroup.add(ffmRecall);
vectorGroup.add(fmGroup);

// KV召回测录
Recall kvGroup = new RecallGroup("kv");
Recall swingRecall = new SwingRecall("swing");
Recall item2vecRecall = new Item2VecRecall("item2vec");
kvGroup.add(swingRecall);
kvGroup.add(item2vecRecall);
// 倒排召回
Recall invertGroup = new RecallGroup("vector");
Recall boolRecall = new BoolRecall("bool");
invertGroup.add(boolRecall);

// 淘宝召回
Recall tbaoGroup = new RecallGroup("taobao");
tbaoGroup.add(vectorGroup);
tbaoGroup.add(kvGroup);
tbaoGroup.add(invertGroup);

for(auto task : tbaoGroup) {
	task->recall(0);
}

// 天猫召回
Recall tmaoGroup = new RecallGroup("taobao");
taobaoGroup.add(vectorGroup);

for(auto task : tmaoGroup) {
	task->recall(0);
}


// 闲鱼召回
Recall xianyuGroup = new RecallGroup("taobao");
xianyuGroup.add(swingRecall);
xianyuGroup.add(fmGroup);
for(auto task : xianyuGroup) {
	task->recall(0);
}

对于客户端来说,他只需要关注自己想要调用的策略即可,可以是单个策略,也可以是某一个策略组,更加的灵活了

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部