Text2SQL系列:基于BERT微調(diào)+模板填充快速實現(xiàn)文本轉(zhuǎn)DSL查詢語句

關(guān)鍵詞:Text2SQL扁位,BERT

前言

Text2SQL是指將自然語言轉(zhuǎn)化為類SQL查詢語句匆背,使得用戶的查詢文本可以直接實現(xiàn)和數(shù)據(jù)庫交互,本文介紹一種以BERT為基礎(chǔ)模型墨技,通過模板填充來實現(xiàn)的Text2SQL算法和產(chǎn)品化。


內(nèi)容摘要

  • Text2SQL任務說明
  • 模板填充的思路
  • 條件列選擇子模型搭建(where col)
  • 條件合并類型子模型搭建(and挎狸,or)
  • 條件值匹配子模型搭建(col扣汪,value)
  • 排序條件子模型搭建
  • limit子模型搭建
  • 整體模型pipeline
  • 基于Streamlit快速產(chǎn)品化

Text2SQL任務說明

本文的任務是將用戶的查詢語言自動翻譯為ElasticSearch的DSL查詢語句,然后以DSL語句查詢ElasticSearch返回結(jié)果锨匆。業(yè)務場景為將上市公司的標簽存儲在ElasticSearch中崭别,通過自然語言挑選出對應的股票,實現(xiàn)“一句話選股”恐锣,該產(chǎn)品在市面上的APP已經(jīng)廣泛實現(xiàn)茅主,本文是對其的一個簡單功能復現(xiàn)。

市面上Text2SQL類似的產(chǎn)品

注意本文需要轉(zhuǎn)化的自然語言任務僅為挑選出企業(yè)侥蒙,并且展示出所需要的列暗膜,不設(shè)計復雜的聚合和聚合函數(shù),比如groupby鞭衩,max学搜,sum不在本次的考慮范圍內(nèi)。


模板填充的思路

模板填充生成查詢語句的思路類似于完型填空论衍,不論是SQL還是DSL查詢語句瑞佩,任何查詢都有固定部分,那把不固定的部分遮蔽掉坯台,單獨預測遮蔽掉的關(guān)鍵詞即可補全SQL炬丸,我們以ElasticSearch的DSL舉例,模板填充的形式如下

DSL模板填充

無底色部分代表固定的DSL形式蜒蕾,有底層部分代表可變的關(guān)鍵詞部分稠炬,不同的顏色代表不同的DSL模塊:

  • must:篩選條件的組合形式,must代表且咪啡,should代表或
  • term首启, range:篩選條件的比較類型,term是相等匹配撤摸,range是值大小比對
  • col:篩選條件用到的列毅桃,以及select需要的列
  • gte:比較符,gte代表大于等于
  • desc:排序類型准夷,desc代表降序
  • size:limit條件钥飞,代表最大輸出多少數(shù)據(jù)

因此模板填充的任務就是把可變的關(guān)鍵詞預測出來,從而拼接出最終的DSL語句衫嵌。


條件列選擇子模型搭建(where col)

第一步是要預測出條件列读宙,只有先預測出條件列,后續(xù)才能進一步完成列的條件匹配和值匹配楔绞。我們采用BERT的句子對的方式结闸,將每一列和用戶輸入的查詢語句拼接起來掖棉,采用[CLS]位置作為表征,預測二分類是否匹配膀估。

BERT句子對匹配預測

舉例如果有100個候選列幔亥,則一條查詢語句需要預測100條樣本,每條樣本是一個二分類察纯,最終可以預測出多個列或者一個列帕棉,也可以沒有列。


條件合并類型子模型搭建(and饼记,or)

條件合并類型預測是預測出有多個條件時的組合類型香伴,且,或具则,甚至沒有即纲,是一個三分類問題。只需要對原始的查詢語句使用BERT的[CLS]預測即可

BERT預測條件組合類型

條件值匹配子模型搭建(col博肋,value)

條件值匹配是一個三元組的預測低斋,涉及元素為col列,value值匪凡,和比較符膊畴,col列是條件列選擇子模型搭建(where col)該任務的輸出,及條件值匹配是條件列選擇的后續(xù)模型病游,由于col列已經(jīng)被上游任務預測出來了唇跨,所以本任務的目標是先抽取出value值,并且搭配比較符做匹配衬衬。
在此我們將col列分為real數(shù)值列和type類型枚舉列买猖,因為這兩種情況對應的value值和比較符都不一樣。

  • real數(shù)值列:需要通過配置提前獲得預測出的select列的類型為real滋尉,此時比較符設(shè)置為“>”玉控,“<”,“=”兼砖,“!=”奸远,“>=”既棺,“<=”一共六種情況讽挟,value值一定是數(shù)字,因此通過正則表達式加中文單位工具轉(zhuǎn)換可以抽取出來

一個使用Python腳本抽取輸入中的數(shù)字的案例如下

import re
import cn2an


def parse(text: str):
    res = []
    tmp = re.findall("[\d一二三四五六七八九十百千萬億零.]+", text)
    for t in tmp:
        value = ""
        try:
            value = cn2an.cn2an(t, "strict")
        except:
            try:
                value = cn2an.cn2an(t, "normal")
            except:
                try:
                    value = cn2an.cn2an(t, "smart")
                except:
                    pass
        if value:
            value = int(value) if int(value) == value else value
            res.append(value)
    return res

if __name__ == '__main__':
    print(parse("手里有三個蘋果丸冕,單價0.56元一個耽梅,總共一百2十三萬元"))

# 抽取結(jié)果 [3, 0.56, 1, 1230000]
  • type類型列:需要通過配置提前獲得預測出的select列的類型為type,此時value值為數(shù)據(jù)庫中該字段的枚舉值全集胖烛,比如該列為省份眼姐,則需要窮舉出所有的省份做匹配诅迷,比較符為"=","!="众旗。

基于以上約定需要對樣本進行構(gòu)造罢杉,即窮句出所有情況,以real列為例贡歧,所有select列和所有抽取出的value滩租,再疊加所有可能的比較符做二分類預測,同樣采用BERT的句子對做表征利朵,例如

構(gòu)造col律想,value匹配模型

排序條件子模型搭建

排序模型需要預測出order by的列,并且預測出是升序asc還是倒序desc绍弟,同樣通過配置提前預知需要排序的所有列技即,并且將其和升序和倒序拼接在一起輸入BERT進行句子對預測,例如

排序模型的BERT樣本輸入

同樣以[CLS]位置來預測二分類是否匹配樟遣。


limit子模型搭建

該模塊是排序條件模型的下游任務而叼,將條件值匹配抽取到的數(shù)字和排序條件列做窮舉,同樣采用BERT做句子對分類豹悬,模型示意圖如下

limit模型輸入

同樣以[CLS]位置來預測二分類是否匹配澈歉。


整體模型pipeline

基于以上方案一共有五個BERT模型,分別是where列預測屿衅,op組合條件預測埃难,select列和值的匹配,是否有排序條件涤久,limit值預測涡尘,五個模型的pipeline流程圖如下

整體預測pipeline

在最后一部需要整個5個模型的輸出改造成對應查詢語句,以ElasticSearch的DSL為例响迂,一個改造實現(xiàn)如下

    def parse_to_dsl(self, op_combine, match, order_op, limit):
        query = {
            "query": {
                "bool": {}
            },
            "_source": ["ent_name", "score"],
            "sort": [

            ]
        }
        if order_op:
            label_name = order_op[:-4]
            order_name = order_op[-4:]
            label_code = self.name_to_label_code[label_name]
            query["sort"].append({f"{label_code}": {"order": "desc" if order_name == "降序排名" else "asc"}})
            query["_source"].append(label_code)
        else:
            query["sort"].append({"score": {"order": "desc"}})
        if limit != -1:
            query["size"] = limit
        op_dsl = "must"
        if op_combine == "or":
            op_dsl = "should"
        query["query"]["bool"][op_dsl] = []
        sub_queries = {}
        for m in match:
            cond, op, val = re.split("(=|!=|>=|<=|>|<)", m)
            label_code = self.name_to_label_code[cond]
            query["_source"].append(label_code)
            c_type = self.cols_type[cond]
            # TODO 子查詢考抄,記錄命中數(shù)
            sub_query = {"query": {}}
            if c_type == "real" and op not in ("=", "!="):
                range_op = {"range": {f"{label_code}": {}}}
                if op == ">":
                    range_op["range"][f"{label_code}"]["gt"] = val
                elif op == ">=":
                    range_op["range"][f"{label_code}"]["gte"] = val
                elif op == "<=":
                    range_op["range"][f"{label_code}"]["lte"] = val
                elif op == "<":
                    range_op["range"][f"{label_code}"]["lt"] = val
                query["query"]["bool"][op_dsl].append(range_op)
                sub_query["query"] = range_op
            elif c_type in ("real", "type") and op == "=":
                term_op = {"term": {f"{label_code}": {"value": val}}}
                query["query"]["bool"][op_dsl].append(term_op)
                sub_query["query"] = term_op
            elif c_type in ("real", "type") and op == "!=":
                bool_op = {"bool": {"must_not": [{"term": {f"{label_code}": {"value": val}}}]}}
                query["query"]["bool"][op_dsl].append(bool_op)
                sub_query["query"] = bool_op
            sub_queries[m] = json.dumps(sub_query, ensure_ascii=False)
        return sub_queries, json.dumps(query, ensure_ascii=False)

本質(zhì)上先定義了一個基礎(chǔ)DSL模板,將要填充的槽留空蔗彤,基于模型的輸出解析改造成一個個子查詢川梅,再匯總為最終的DSL語句。


基于Streamlit快速產(chǎn)品化

以Streamlit框架為例然遏,快速不屬于個一句話選股的頁面功能贫途,實現(xiàn)如下

import streamlit as st
import pandas as pd


name = st.text_input('請輸入您的股票查詢條件', max_chars=100, help='最大長度為100字符')

if st.button('查詢'):
    parse_cond, df_info, order_op, limit = query(name)
    style = """
        <style>
        .box {
            border: 1px solid #ddd;
            padding: 6px 10px;
            margin-top: 10px;
            background-color: pink;
            color:#000;
            display:inline-block;
            margin-right:6px;
        }
        </style>
        """
    st.markdown(style, unsafe_allow_html=True)
    conds = []
    for k, v in parse_cond.items():
        conds.append("<span class='box'>{}</span>".format(k + "(" + str(v) + ")"))
    if order_op:
        conds.append("<span class='box'>{}</span>".format(order_op + f"前{limit}" if limit != -1 else order_op))
    st.markdown("".join(conds), unsafe_allow_html=True)
    hit = df_info[0]
    df = pd.DataFrame(df_info[1]).rename(columns=name_to_label_code)
    df.insert(0, '企業(yè)名稱', df.pop('企業(yè)名稱'))
    df.insert(1, '綜合指數(shù)', df.pop('綜合指數(shù)'))
    st.write("共計命中:{}條企業(yè)".format(hit))
    st.dataframe(df)

其中query中包裝了BERT模型的pipeline,其中streamlit彈出web應用如下

查詢頁面

如果檢索條件待侵,比如篩選出注冊資本金大于15億丢早,屬于廣東珠海的上市企業(yè),按照指數(shù)倒序排列,輸入后檢索結(jié)果如下

Text2SQL結(jié)果

Text2SQL不僅輸出了最終符合條件的企業(yè)怨酝,也統(tǒng)計出了相關(guān)的子條件傀缩,以及子條件命中的企業(yè)數(shù),最終符合要求的企業(yè)數(shù)农猬,從解析的條件來看赡艰,模型結(jié)果完全正確。

全文完畢斤葱。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞄摊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苦掘,更是在濱河造成了極大的恐慌换帜,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹤啡,死亡現(xiàn)場離奇詭異惯驼,居然都是意外死亡,警方通過查閱死者的電腦和手機递瑰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門祟牲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抖部,你說我怎么就攤上這事说贝。” “怎么了慎颗?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵乡恕,是天一觀的道長。 經(jīng)常有香客問我俯萎,道長傲宜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任夫啊,我火速辦了婚禮函卒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘撇眯。我一直安慰自己报嵌,他們只是感情好,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布熊榛。 她就那樣靜靜地躺著锚国,像睡著了一般。 火紅的嫁衣襯著肌膚如雪来候。 梳的紋絲不亂的頭發(fā)上跷叉,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天逸雹,我揣著相機與錄音营搅,去河邊找鬼云挟。 笑死,一個胖子當著我的面吹牛转质,可吹牛的內(nèi)容都是我干的园欣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼休蟹,長吁一口氣:“原來是場噩夢啊……” “哼沸枯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赂弓,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤绑榴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盈魁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翔怎,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年杨耙,在試婚紗的時候發(fā)現(xiàn)自己被綠了赤套。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡珊膜,死狀恐怖容握,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情车柠,我是刑警寧澤剔氏,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站竹祷,受9級特大地震影響介蛉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜溶褪,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一币旧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猿妈,春花似錦吹菱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俯抖,卻和暖如春输瓜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工尤揣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搔啊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓北戏,卻偏偏與公主長得像负芋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嗜愈,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容