智能風(fēng)控決策引擎系統(tǒng)可落地實現(xiàn)方案(二)決策流實現(xiàn)

內(nèi)容轉(zhuǎn)自微信公眾號植榕,技術(shù)時光幻馁,關(guān)注第一時間獲取最新文章

I.決策流介紹

在上一篇文章中脓钾,我們實現(xiàn)了一個完整的規(guī)則集解析過程售睹,實現(xiàn)了規(guī)則引擎。具體請參考上一篇文章:(一)規(guī)則引擎實現(xiàn)惭笑。

實際風(fēng)控需求通常不會只有一組規(guī)則侣姆,會對不同的規(guī)則、規(guī)則集進行編排沉噩,還會出現(xiàn)分支流程捺宗,子流程,形成一個更復(fù)雜風(fēng)控流程川蒙,我們叫決策流蚜厉,決策流的解析就是實現(xiàn)一個流程引擎。

流程引擎也叫工作流引擎畜眨,有很多開源實現(xiàn)昼牛,比較出名的是java的Activiti,jBPM5康聂。這里我們從業(yè)務(wù)需求分析贰健,抽象建模并自己實現(xiàn)流程解析過程。

II.決策流抽象建模

先看一個決策流長什么樣恬汁?

image

這是一個比較簡單的決策流伶椿,它由兩個規(guī)則集順序編排,并有起始和結(jié)束,是符合BPMN規(guī)范的脊另。

BPMN是什么导狡?Business Process Diagram(BPM)是指一個業(yè)務(wù)流程圖,“N”是Notation符號偎痛,BPMN業(yè)務(wù)流程建模符號旱捧,是由OMG組織維護的一套業(yè)務(wù)流程建模標(biāo)準(zhǔn)。這里我們關(guān)注和使用其中流對象(Flow)相關(guān)定義及元素踩麦。具體如下圖:

image

1. 簡單決策流實現(xiàn)

流對象中枚赡,選取事件Event中開始Start Event作為決策流的開始節(jié)點,結(jié)束End Event作為決策流的結(jié)束節(jié)點靖榕,一個規(guī)則集作為一個活動Activity标锄,后面決策樹、決策矩陣等決策節(jié)點也是活動Activity的一種類型茁计,流向Flow作為決策流的編排執(zhí)行順序料皇,由此一個簡單的決策流就用BPMN規(guī)范做了抽象。

image

數(shù)據(jù)結(jié)構(gòu)上星压,決策流節(jié)點使用單向線性鏈表践剂,每個節(jié)點持有下一節(jié)點指針。

代碼實現(xiàn)上娜膘,使用pipline架構(gòu)作為流程解析逊脯,前一個節(jié)點解析結(jié)果(out/sink)作為下一個節(jié)點的輸入(in/source),每個節(jié)點封裝解析算法(parse)竣贪,節(jié)點的執(zhí)行結(jié)果統(tǒng)一存儲在上下文中军洼,直到全部節(jié)點執(zhí)行完成或中斷退出。

用DSL表述演怎,將決策流抽象成Workflow結(jié)構(gòu)體匕争,由多個Node組成,具體yaml語法表達如下:

workflow:
  - node:
    node_name: start_1
    category: start
    next_node_name: ruleset_1
    next_category: ruleset
  - node:
    node_name: ruleset_1
    category: ruleset
    next_node_name: ruleset_2
    next_category: ruleset
  - node:
    node_name: ruleset_2
    category: ruleset
    next_node_name: end_1
    next_category: end
  - node:
    node_name: end_1
    category: end
    next_node_name: ""
    next_category: ""

Node結(jié)構(gòu)體如下:

type Node struct {
    NodeName     string `yaml:"node_name"`
    Category     string `yaml:"category"`
    NextNodeName string `yaml:"next_node_name"`
    NextCategory string `yaml:"next_category"`
}

規(guī)則集DSL抽象和上一篇一樣爷耀,將多個Ruleset組成Rulesets數(shù)組甘桑,用ruleset_name進行區(qū)分。

rulesets:
  - ruleset_name: ruleset_1
    ruleset_category: internal
    rules:
    #...
  - ruleset_name: ruleset_2
    ruleset_category: external
    rules:
    #...

此時完整DSL結(jié)構(gòu)體歹叮,包括Workflow代表Node節(jié)點數(shù)組跑杭,Rulesets代表規(guī)則集Ruleset數(shù)組。

type Dsl struct {
    Workflow     []Node        `yaml:"workflow,flow"`
    Rulesets     []Ruleset     `yaml:"rulesets,flow"`
}

決策流解析過程:

  1. 先找到start節(jié)點咆耿,執(zhí)行解析

  2. 循環(huán)節(jié)點下一指針德谅,執(zhí)行每個節(jié)點parse()直到下一指針為空或中斷退出

  3. 返回決策結(jié)果


//define result struct
type DslResult struct {
    NextNodeName string
    NextCategory string
    Decision     interface{}
    Track        []string
}
//dsl flow parse
func (dsl *Dsl) Parse() DslResult {
    log.Println("dsl parse start...")
    if len(dsl.Workflow) == 0 { 
        panic("dsl workflow is empty")
    }   
    var result = new(DslResult)
    //from start node
    firstNode := dsl.FindStartNode()
    dsl.gotoNextNode(firstNode.NodeName, firstNode.Category, result)

    //loop parse node and go to next node
    for !isBreakDecision(result.Decision) && result.NextNodeName != "" {
        dsl.gotoNextNode(result.NextNodeName, result.NextCategory, result)
    }   
    log.Println("dsl parse end.")
    return result
}
//parse node and find next
func (dsl *Dsl) gotoNextNode(nodeName string, category string, result *DslResult) {
    //find current node from workflow
    node := dsl.FindNode(nodeName)
    if node == "" {
        return
    }
    result.Track = append(result.Track, nodeName)
    //default
    result.NextNodeName = node.NextNodeName
    result.NextCategory = node.NextCategory
    result.Decision = nil
    //parse different category node
    switch category {
    case configs.START:
        return
    case configs.RULESET:
        ruleset := dsl.FindRuleset(node.NodeName)
        result.Decision = ruleset.parse()
    case configs.END:
        result.NextNodeName = ""
        result.NextCategory = ""
    }
}

2.增加決策流分支

分支流程需要增加網(wǎng)關(guān)Gateway節(jié)點,網(wǎng)關(guān)又分為并行網(wǎng)關(guān)萨螺、排它網(wǎng)關(guān)窄做、包容網(wǎng)關(guān)宅荤,而排它網(wǎng)關(guān)更符合風(fēng)控場景業(yè)務(wù)語義,即一個決策流只能走一個分支浸策。

image

決策流滿足條件1時走第一條分支,經(jīng)過規(guī)則集A惹盼,滿足條件2時走規(guī)則集B的分支庸汗。

2.1 數(shù)據(jù)結(jié)構(gòu)選擇

增加了分支流程,線性鏈表結(jié)構(gòu)是否仍適用手报?樹結(jié)構(gòu)蚯舱,圖結(jié)構(gòu),鏈表結(jié)構(gòu)如何選擇掩蛤?

首選考慮樹結(jié)構(gòu)枉昏,可能是N叉樹,也可能是無分支的線性結(jié)構(gòu)揍鸟,且是有向的兄裂,因此樹結(jié)構(gòu)并不合適。

進一步考慮有向圖結(jié)構(gòu)(如有向無環(huán)圖DAG)阳藻,圖的解析主要是進行深度或廣度遍歷晰奖,可以執(zhí)行每條分支流程,因此圖的遍歷更適合做并行網(wǎng)關(guān)腥泥,而決策流是排它網(wǎng)關(guān)語義匾南,同時有且只有一個分支滿足執(zhí)行條件,使用圖結(jié)構(gòu)并不合適蛔外,一次決策的執(zhí)行流程蛆楞,更像一個鏈?zhǔn)搅鞒蹋m合單向鏈表結(jié)構(gòu)夹厌。

image

2.2 條件網(wǎng)關(guān)實現(xiàn)

條件網(wǎng)關(guān)功能:滿足某個分支線上的條件即走該分支豹爹。

條件網(wǎng)關(guān)解析:循環(huán)執(zhí)行每個分支的條件表達式,并選擇結(jié)果為true的第一個分支尊流,決策結(jié)果即為決策流下一步要走的分支名帅戒。這里也注意,一般情況不允許配置兩個分支條件有重合崖技,可以同時滿足的情況逻住。


//conditinal gateway node
type Conditional struct {
    ConditionalName string   `yaml:"conditional_name"`
    Depends         []string `yaml:"depends"`
    Branchs         []Branch `yaml:"branchs,flow"`
}
//branch in conditional
type Branch struct {
    BranchName string      `yaml:"branch_name"`
    Conditions []Condition `yaml:"conditions"`
    Logic      string      `yaml:"logic"`
    Decision   string      `yaml:"decision"`
}
//conditional gateway parse
func (conditional *Conditional) parse() string {
    depends := internal.GetFeatures(conditional.Depends) //need to check
    for _, branch := range conditional.Branchs {         //loop all the branch
        var conditionRs = make([]bool, 0)
        for _, condition := range branch.Conditions {
            if data, ok := depends[condition.Feature]; ok {
                rs, _ := operator.Compare(condition.Operator, data, condition.Value)
                conditionRs = append(conditionRs, rs)
            } else { //get feature fail
                continue //can modify according scene
            }
        }
        logicRs, _ := operator.Boolean(conditionRs, branch.Logic)
        if logicRs { //if true, choose the branch and break
            return branch.Decision
        } else {
            continue
        }
    }
    return "" //can't find any branch
}

現(xiàn)在把條件網(wǎng)關(guān)加入workflow,即構(gòu)造了完整DSL語法迎献。


workflow:
  - node:
    node_name: start_1
    category: start
    next_node_name: conditional_1
    next_category: conditional
  - node:
    node_name: conditional_1
    category: conditional
    next_node_name: ""
    next_category: ""
  - node:
    node_name: ruleset_1
    node_category: ruleset
    next_node_name: end_1
    next_category: end 
  - node:
    node_name: ruleset_2
    node_category: ruleset
    next_node_name: end_2
    next_category: end 
  - node:
    node_name: end_1
    node_category: end 
    next_node_name: ""
    next_category: ""
  - node:
    node_name: end_2
    node_category: end
    next_node_name: ""
    next_category: ""

決策流執(zhí)行過程需要加上對條件網(wǎng)關(guān)conditional的執(zhí)行解析瞎访。

func (dsl *Dsl) gotoNextNode(nodeName string, category string, result *DslResult) {
  //...
  switch category {
  //...
  case configs.CONDITIONAL:
        conditional := dsl.FindConditional(node.NodeName)
        rs := conditional.parse()
        if rs == nil { //not match any branch, error
            result.NextNodeName = ""
            log.Println(node.NodeName, "not match any branch")
        } else {
            result.NextNodeName = rs.(string)
            result.NextCategory = dsl.FindNode(rs.(string)).Category
        }
  }
}

最后決策流執(zhí)行情況如下:

image
image

3. 分流網(wǎng)關(guān)實現(xiàn)

風(fēng)控工作中,需要不斷對規(guī)則策略和模型進行迭代優(yōu)化吁恍,新的規(guī)則模型效果需要經(jīng)過實驗證明扒秸,與原規(guī)則模型進行效果比對播演,選擇較優(yōu)的規(guī)則模型使用,這種叫冠軍/挑戰(zhàn)者試驗伴奥,原規(guī)則模型分支叫冠軍写烤,新增分支叫挑戰(zhàn)者,也有叫A/B Test拾徙。

這里使用分流網(wǎng)關(guān)來實現(xiàn)ABTest洲炊,決策流配置如下:

image
abtests:
  - abtest:
    abtest_name: abtest_1    
    branchs:
    - branch:
      branch_name: branch_1
      percent: 45
      decision: ruleset_1
    - branch:
      branch_name: branch_2
      percent: 55
      decision: ruleset_2

代碼實現(xiàn)上,45%的流量分給第一分支尼啡,55%流量分給第二分支暂衡,這里實現(xiàn)一個0-99隨機數(shù),獲取結(jié)果大于45崖瞭,走分支2狂巢,否則走分支1。

rand.Seed(time.Now().UnixNano())
 rs := rand.Intn(99)

具體實現(xiàn)上仍有幾個問題需要考慮:

  • 流量配比可能為小數(shù)书聚。

  • 流量配比之和應(yīng)該為100唧领,可在發(fā)布時校驗。

  • 隨機種子的設(shè)置雌续,如果希望隨機數(shù)與用戶id和實驗名相關(guān)疹吃,需要取其hash來設(shè)置種子seed。

  • 實驗及分流可用redis/mysql固化西雀,下一次直接使用(有些業(yè)務(wù)場景需求)萨驶。

4. 復(fù)雜決策流實現(xiàn)

目前實現(xiàn)了開始、結(jié)束艇肴、規(guī)則集腔呜、條件網(wǎng)關(guān)、分流網(wǎng)關(guān)再悼,通過組合幾種節(jié)點核畴,即可實現(xiàn)了大多數(shù)風(fēng)控場景下的決策流配置。

image

III.問題與思考

****1. ****配置****決策流是否匯合****

image
image

第一種配置會省事一些冲九,第二種對分支表述會更清晰些谤草,如果針對更復(fù)雜的流,有匯合邏輯會更復(fù)雜且易出錯莺奸。

在實際執(zhí)行解析過程中丑孩,由于鏈?zhǔn)浇馕觯暗拇a可支持上述兩種方式都正常解析灭贷。Workflow配置一個end節(jié)點還是兩個end節(jié)點温学,之前是按兩個結(jié)束節(jié)點表述,這里可改成一個end節(jié)點:

workflow:
  #...
  - node:
    node_name: ruleset_3
    node_category: ruleset
    next_node_name: end_1
    next_category: end 
  - node:
    node_name: ruleset_4
    node_category: ruleset
    next_node_name: end_1
    next_category: end 
  - node:
    node_name: end_1
    node_category: end 
    next_node_name: ""
    next_category: ""

2.決策流管理后臺

工作中甚疟,為了讓風(fēng)控分析師更方便的配置決策流仗岖,還需要配套一個可視化交互后臺逃延,所有節(jié)點以組件拖拉拽的方式提供服務(wù),這里分享一個開源前端庫轧拄。

https://bpmn.io

image

通過該組件揽祥,可以生成一個XML格式文件,我們對其進行解析檩电,然后轉(zhuǎn)化為我們需要的Yaml格式DSL文件盔然。

3.獲取依賴特征數(shù)據(jù)

決策流執(zhí)行和規(guī)則執(zhí)行都離不開特征數(shù)據(jù)的支持,而特征數(shù)據(jù)何時加載會對解析效率有不同影響是嗜。一般有兩種方式:

  • 決策流執(zhí)行前將數(shù)據(jù)全部獲取

  • 邊解析決策流邊加載特征

image

一些商業(yè)決策引擎就需要先將所有數(shù)據(jù)特征加工好,統(tǒng)一推給決策引擎挺尾,決策引擎給出決策結(jié)果鹅搪。這種方式主要缺點:決策請求數(shù)據(jù)不同,可能執(zhí)行第一個規(guī)則(集)即命中拒絕中斷遭铺,剩余規(guī)則(集)依賴的特征就沒必要加載丽柿,如果這部分特征來自三方收費數(shù)據(jù)源,會導(dǎo)致數(shù)據(jù)成本浪費魂挂,所以按需加載更符合一般業(yè)務(wù)場景甫题。

實際風(fēng)控場景,會拆分內(nèi)部數(shù)據(jù)規(guī)則集和外部數(shù)據(jù)規(guī)則集涂召,決策先使用內(nèi)部數(shù)據(jù)規(guī)則集坠非,如命中拒絕即退出不再執(zhí)行外部數(shù)據(jù)規(guī)則集。更精細化控制果正,可將外部數(shù)據(jù)規(guī)則集中每條規(guī)則做優(yōu)先級排序炎码,規(guī)則集執(zhí)行策略按命中即退出模式,這樣可進一步控制外部數(shù)據(jù)成本秋泳。

4.關(guān)于決策流退出

決策流以pipline方式依次執(zhí)行,引起決策流退出的情況可能有如下幾種:

  • 決策流執(zhí)行到end節(jié)點,正常結(jié)束退出亚情。

  • 決策流執(zhí)行規(guī)則集結(jié)果為拒絕昙沦,根據(jù)業(yè)務(wù)語義中斷退出(isBreakDecision代碼進行控制),這里是隱式的進行了退出卓起,也有配置結(jié)束節(jié)點顯示退出的做法和敬,但每個規(guī)則集配置一個并不方便。

  • 決策流執(zhí)行條件網(wǎng)關(guān)未匹配到任何分支戏阅,或決策流在獲取依賴特征時因系統(tǒng)問題導(dǎo)致失敗概龄,這時候觸發(fā)異常退出情況,需要進行監(jiān)控和報警處理饲握。

5.架構(gòu)部署

將決策引擎部署成一個web服務(wù)私杜,使用gin或net/http即可輕松搭建一個api服務(wù)蚕键,業(yè)務(wù)場景在需要風(fēng)控時調(diào)用決策引擎系統(tǒng),獲取決策結(jié)果數(shù)據(jù)衰粹。

決策引擎在啟動時加載DSL到內(nèi)存锣光,當(dāng)有變更發(fā)布時更新內(nèi)存DSL,關(guān)于如何熱發(fā)布后續(xù)文章會進一步分析铝耻。

數(shù)據(jù)返回一般可以包括:結(jié)果數(shù)據(jù)誊爹,拒絕還是通過;過程數(shù)據(jù)瓢捉,命中規(guī)則频丘、規(guī)則集情況,執(zhí)行路徑泡态,執(zhí)行分支搂漠,以及每個特征值情況,用于后期分析使用某弦。結(jié)果數(shù)據(jù)可落地mysql數(shù)據(jù)庫桐汤,較大的數(shù)據(jù)源數(shù)據(jù)可存儲hbase,或通過log靶壮、kafka等方式異步存儲怔毛。

決策引擎依賴的特征數(shù)據(jù)可從特征引擎獲取,特征引擎進行數(shù)據(jù)庫腾降、數(shù)據(jù)源的對接以及特征衍生加工拣度,有些公司會進一步拆分成(前置)數(shù)據(jù)平臺和特征加工平臺兩個系統(tǒng)。

具體架構(gòu)圖如下:

image

執(zhí)行引擎流程時序圖:

image

6.引入模型及模型引擎

使用規(guī)則策略做風(fēng)控螃壤,通常只能決策是否通過蜡娶,適用于反欺詐,而綜合評估用戶風(fēng)險等級映穗,對用戶進行信用評級窖张,通過風(fēng)險定價可進一步提升風(fēng)控能力。隨著大數(shù)據(jù)技術(shù)的發(fā)展蚁滋,對數(shù)據(jù)的深度挖掘并建立機器學(xué)習(xí)宿接、深度學(xué)習(xí)模型,成為更重要的風(fēng)控手段辕录。通過模型結(jié)果睦霎,可更精細化的進行風(fēng)險評估。下一篇走诞,我們將介紹模型相關(guān)開發(fā)和技術(shù)實現(xiàn)副女。

內(nèi)容轉(zhuǎn)自微信公眾號,技術(shù)時光 techyears蚣旱,關(guān)注第一時間獲取最新文章

文章相關(guān)代碼實現(xiàn):
https://github.com/skyhackvip/risk_engine

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碑幅,一起剝皮案震驚了整個濱河市戴陡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沟涨,老刑警劉巖恤批,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異裹赴,居然都是意外死亡喜庞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門棋返,熙熙樓的掌柜王于貴愁眉苦臉地迎上來延都,“玉大人,你說我怎么就攤上這事睛竣∥浚” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵酵颁,是天一觀的道長。 經(jīng)常有香客問我月帝,道長躏惋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任嚷辅,我火速辦了婚禮簿姨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘簸搞。我一直安慰自己扁位,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布趁俊。 她就那樣靜靜地躺著域仇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寺擂。 梳的紋絲不亂的頭發(fā)上暇务,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音怔软,去河邊找鬼垦细。 笑死,一個胖子當(dāng)著我的面吹牛挡逼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘱能,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼憔涉,長吁一口氣:“原來是場噩夢啊……” “哼析苫!你這毒婦竟也來了衩侥?” 一聲冷哼從身側(cè)響起茫死,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤峦萎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后被环,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體详幽,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡唇聘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年剥险,在試婚紗的時候發(fā)現(xiàn)自己被綠了宪肖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匈庭。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡阱持,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒜绽,到底是詐尸還是另有隱情桶现,我是刑警寧澤相赁,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蛆挫,受9級特大地震影響废境,放射性物質(zhì)發(fā)生泄漏毡咏。R本人自食惡果不足惜堵泽,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一纹安、第九天 我趴在偏房一處隱蔽的房頂上張望光督。 院中可真熱鬧,春花似錦、人聲如沸努隙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工绍坝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留椎咧,地道東北人拗踢。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓君纫,卻偏偏與公主長得像叉庐,于是被迫代替她去往敵國和親肢执。 傳聞我的和親對象是個殘疾皇子临庇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355