召回链路一般都是使用多路召回,例如文本召回,向量召回,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);
}
对于客户端来说,他只需要关注自己想要调用的策略即可,可以是单个策略,也可以是某一个策略组,更加的灵活了