項(xiàng)目背景
原本業(yè)務(wù)內(nèi)容是比較常見的判定業(yè)務(wù)漆弄,即輸入為某個(gè)實(shí)體有一定誤差的測量信息和相關(guān)參考信息睦裳,輸出為其應(yīng)當(dāng)歸屬的實(shí)體。套用一個(gè)簡單場景就是輸入一篇未署名文章撼唾,根據(jù)文風(fēng)歸屬到庫中已存在的作者名下廉邑,抑或是歸屬到一個(gè)新建的匿名作者名下。
問題的難點(diǎn)在于:
- 分類實(shí)體數(shù)量較多,在百萬量級
- 分類數(shù)量不確定蛛蒙,且在動(dòng)態(tài)變化糙箍,即有新增和過期
- 測量存在誤差
- 場景較多
評價(jià)標(biāo)準(zhǔn):
- 歸屬要準(zhǔn)確(作者名下文章不要錯(cuò))
- 少遺漏(文章能盡量找到作者)
- 避免錯(cuò)誤創(chuàng)建(同一作者不要?jiǎng)?chuàng)建多個(gè)實(shí)體)
原解決方案也比較傳統(tǒng),即制定了一套策略牵祟,將策略組合為一顆決策樹結(jié)構(gòu)深夯,邏輯較重的同時(shí)需要很多先驗(yàn)知識。套用場景即根據(jù)常用的人稱代詞课舍、語法結(jié)構(gòu)來判斷是否屬于同一作者塌西。
隨著準(zhǔn)確率要求不斷提高,策略組合的調(diào)整和維護(hù)愈發(fā)困難筝尾,主要原因在于:
- 策略不斷增加捡需,維護(hù)難度提升
- 為兼容誤差打了不少補(bǔ)丁,維護(hù)難度提升
- 閾值筹淫、策略調(diào)整僅憑經(jīng)驗(yàn)站辉,每次調(diào)整需要回歸驗(yàn)證的數(shù)據(jù)較多
- 場景較多,要針對每個(gè)場景制定對應(yīng)的優(yōu)化策略的工作量較大
總之一句話损姜,可預(yù)見的未來策略會繼續(xù)膨脹饰剥,維護(hù)難度也會進(jìn)一步提升。意識到技術(shù)風(fēng)險(xiǎn)后摧阅,組內(nèi)討論認(rèn)為如果使用機(jī)器學(xué)習(xí)方案一方面可降低解決方案的復(fù)雜度(降低維護(hù)成本)汰蓉,另一方面利于后期場景擴(kuò)展和能力遷移。至少也應(yīng)該能做到模型解決通用場景棒卷,在此基礎(chǔ)上再定制優(yōu)化策略(降低開發(fā)成本)顾孽。
本次幸運(yùn)地被選中承擔(dān)調(diào)研工作,盡管之前沒有使用模型解決問題的經(jīng)驗(yàn)(純小白)若厚,但剛好有個(gè)拓寬技術(shù)面的機(jī)會肯定是不能放過的。于是現(xiàn)學(xué)現(xiàn)賣記錄一下趟坑過程灾常,也希望能給其他有興趣的非算法攻城獅們一點(diǎn)信心肃晚,其實(shí)(浮于表面的)模型應(yīng)用并沒有通常大家認(rèn)為的那么難。
前期準(zhǔn)備
問題定義
問題的本質(zhì)是匹配吧碾,即目標(biāo)比較兩個(gè)向量的距離落剪。有很多方式可以實(shí)現(xiàn)這個(gè)比較呢堰,比如
- 分類:根據(jù)標(biāo)注好的各類別數(shù)據(jù)參與訓(xùn)練鞋拟,預(yù)測新數(shù)據(jù)屬于哪一原定分類
- 聚類:把相似數(shù)據(jù)聚合為一類,最終得到多個(gè)分類
- 回歸:多用于根據(jù)標(biāo)注好的數(shù)據(jù)和原預(yù)測值獲得一個(gè)擬合函數(shù),預(yù)測新數(shù)據(jù)的值
模型選擇
聚類模型
思路:特征聚為一類說明向量距離接近萝毛,屬于同一分類。
使用聚類模型則似乎不利于實(shí)現(xiàn)在線預(yù)測(每次預(yù)測都要聚類歉胶,投入較高),且常用的聚類模型存在一些限制條件,如:
- K-means等,需要預(yù)設(shè)類簇個(gè)數(shù)K,不適合本業(yè)務(wù)場景
- DBScan等即硼,需要一些先驗(yàn)知識如向量間的距離閾值等层皱,在本場景中較難確定
- 聚類模型很難支持百萬量級類簇?cái)?shù)量的聚類
回歸模型
實(shí)在不知如何將向量匹配問題轉(zhuǎn)換為回歸問題瓮增,所以干脆沒怎么嘗試凡资。
多分類模型
思路:認(rèn)為每個(gè)應(yīng)當(dāng)歸屬同一實(shí)體的數(shù)據(jù)屬于一個(gè)分類,為所有特征計(jì)算分類伞访。
多分類本質(zhì)上是為每個(gè)類訓(xùn)練了一個(gè)分類器掂骏,但訓(xùn)練上百萬個(gè)分類器顯然也并不合理弟灼,放棄方案级解。
二分類模型
通過研究某屆天池競賽發(fā)現(xiàn),直接計(jì)算兩個(gè)特征向量的“差異”并根據(jù)最終是否匹配進(jìn)行標(biāo)注缴挖,是個(gè)比較合理的思路同蜻,最終便選擇了二分類模型棚点。
舉例來說是這樣:
x_diff | y_diff | match |
---|---|---|
0.1 | 0.1 | true |
0.9 | 0.9 | false |
簡而言之,原本決策引擎使用的是多個(gè)isMatch(v1, v2, threshold)
規(guī)則組成的規(guī)則組默责,滿足一組特定規(guī)則的就認(rèn)為是同一類贱鼻;而在使用機(jī)器學(xué)習(xí)模型后舱呻,使用的是[a1-a2, b1-b2, ... , x1-x2]
向量映射在高維空間的結(jié)果,由模型劃線分類蕊程。
特征工程
基礎(chǔ)數(shù)據(jù)
基礎(chǔ)屬性數(shù)據(jù)盡量選擇與結(jié)果有相關(guān)性的攘烛,避免為明顯毫無關(guān)聯(lián)的屬性計(jì)算特征參與訓(xùn)練扎即。
特征計(jì)算
如前所述遏插,最終特征的形式是輸入與候選間同一屬性維度的diff缸榄,最終形成的向量則是N個(gè)維度diff的結(jié)果。
特殊處理
- 首先明確泛释,機(jī)器學(xué)習(xí)模型并不清楚特征各個(gè)維度之間的聯(lián)系,也不知道特征維度的diff如何計(jì)算温算,所以需要做一些處理怜校;
- 其次,可以做一些特殊處理使得基礎(chǔ)特征與結(jié)果關(guān)聯(lián)性更強(qiáng)注竿,比如將x茄茁、y坐標(biāo)轉(zhuǎn)為二維坐標(biāo)魂贬,使得最終的diff結(jié)果由x_diff、y_diff轉(zhuǎn)化為(x, y)間的歐式距離
通過將一些非數(shù)值類特征通過特殊手段處理映射為(連續(xù)的)數(shù)值特征裙顽,對模型訓(xùn)練結(jié)果會有明顯幫助:
- 文本類:可以嘗試Levenshtein距離(編輯距離)
- 枚舉類:可嘗試One-Hot編碼距離
- 距離類:可嘗試余弦相似度付燥、曼哈頓距離等
具體處理方式還是依業(yè)務(wù)而定,并非任何維度特征都應(yīng)當(dāng)處理愈犹。
數(shù)據(jù)準(zhǔn)備
總數(shù)據(jù)量
通過特征工程計(jì)算的10w+特征向量键科。
打標(biāo)
二分類模型需要將數(shù)據(jù)分為兩個(gè)類,如0類和1類漩怎,或a類和b類等勋颖,分類標(biāo)簽需要體現(xiàn)在訓(xùn)練時(shí)的特征數(shù)據(jù)中,也就是說訓(xùn)練數(shù)據(jù)實(shí)際上是特征向量+分類標(biāo)簽
勋锤。
本業(yè)務(wù)中的分類就是兩者是否匹配饭玲。套用場景即每篇文章提供多個(gè)候選作者,與其真正的作者屬性diff形成的特征向量打標(biāo)將會是a叁执,而與其他非真正作者形成的特征向量打標(biāo)將為b茄厘。
鑒于原決策模型準(zhǔn)確率9x%左右,索性直接使用了原本業(yè)務(wù)決策模型輸出的結(jié)果作為分類標(biāo)簽谈宛,當(dāng)然使用人工打標(biāo)結(jié)果效果是更好的次哈,盡管人工打標(biāo)準(zhǔn)確率也不一定是100%。
預(yù)處理
- 歸一化
- 避免單一維度影響過大入挣,將每個(gè)維度的值映射到0~1的區(qū)間內(nèi)
- 計(jì)算方式:
normalized_value = (value - min_value) / (max_value - min_value)
建模流程
建模
說明
本流程使用阿里云PAI平臺搭建亿乳,省去搭環(huán)境的痛苦。當(dāng)然径筏,這個(gè)流程平臺無關(guān)葛假,完全也可以自行搭建。
讀數(shù)據(jù)表
打標(biāo)后的特征數(shù)據(jù)輸入滋恬。
預(yù)處理腳本
對某些稀疏矩陣類型的特征進(jìn)行預(yù)處理聊训,轉(zhuǎn)為標(biāo)準(zhǔn)順序格式。即把Java中的Map結(jié)構(gòu)按照擬定的順序轉(zhuǎn)為一維數(shù)組恢氯,由于特征較多带斑,這個(gè)過程使用了代碼生成腳本。
類型轉(zhuǎn)換
某些特征值需要由非數(shù)值類型轉(zhuǎn)為數(shù)值類型勋拟,另外可以做一些缺失值填充。
全表統(tǒng)計(jì)
統(tǒng)計(jì)數(shù)據(jù)表的各項(xiàng)信息敢靡,包括最大最小值啸胧、方差等幔虏。用于在數(shù)據(jù)預(yù)處理時(shí)參考想括,如歸一化計(jì)算用到的每個(gè)維度的最大值最小值烙博。
訓(xùn)練時(shí)的預(yù)測中這不是必須的步驟习勤,輸出的數(shù)據(jù)主要用于脫離平臺使用图毕。
數(shù)據(jù)歸一化
將每個(gè)維度的值映射至0~1。
數(shù)據(jù)拆分
將輸入的全量特征數(shù)據(jù)隨機(jī)拆分成兩個(gè)部分:
- 訓(xùn)練數(shù)據(jù)
- 用于訓(xùn)練模型囤官,理論上訓(xùn)練數(shù)據(jù)越多蛤虐,訓(xùn)練好的模型預(yù)測越穩(wěn)定和準(zhǔn)確
- 預(yù)測數(shù)據(jù)
- 用于給訓(xùn)練完的模型進(jìn)行預(yù)測測試驳庭,模型對預(yù)測數(shù)據(jù)的預(yù)測結(jié)果將成為模型評估的依據(jù)
在這里可以把訓(xùn)練數(shù)據(jù)與預(yù)測數(shù)據(jù)按7:3或8:2拆分饲常。
XGBoost二分類
XGBoost是基于梯度提升樹(GBDT)算法的模型贝淤,由華人大牛陳天奇博士團(tuán)隊(duì)提出。
GBDT的思想可以用一個(gè)通俗的例子解釋朽基,假如有個(gè)人30歲离陶,我們首先用20歲去擬合招刨,發(fā)現(xiàn)損失有10歲,這時(shí)我們用6歲去擬合剩下的損失,發(fā)現(xiàn)差距還有4歲学密,第三輪我們用3歲擬合剩下的差距传藏,差距就只有一歲了毯侦。如果我們的迭代輪數(shù)還沒有完,可以繼續(xù)迭代下面试幽,每一輪迭代,擬合的歲數(shù)誤差都會減小济榨。
——參考資料
模型預(yù)測
將預(yù)測數(shù)據(jù)輸入訓(xùn)練好的模型預(yù)測绿映,結(jié)果用于模型效果評估叉弦。
模型導(dǎo)出
將訓(xùn)練好的模型導(dǎo)出為pmml格式文件。
混淆矩陣/二分類評估
用于評估模型效果钝诚。
模型評估
混淆矩陣
結(jié)果
說明
預(yù)測Positive | 預(yù)測Negative | |
---|---|---|
真值Positive | True Positive,該P(yáng)的P了拧略,正確 | False Negative垫蛆,該P(yáng)的N了,錯(cuò)誤 |
真值Negative | False Positive袱饭,該N的P了虑乖,錯(cuò)誤 | True Negative疹味,該N的N了糙捺,正確 |
準(zhǔn)確率
ACC = (TP + TN) / (TP + FN + FP + TN)
即整體準(zhǔn)確率洪灯,所有預(yù)測正確的 / 總預(yù)測量婴渡。
精確率
PPV = TP / (TP + FP)
即該P(yáng)也正確預(yù)測為P的準(zhǔn)確率。也可以計(jì)算N的精確率哄尔。
召回率
TPR = TP / (TP + FN)
即P預(yù)測正確的占真值P的比例柠并。也可計(jì)算N的鸣戴。
總的來說準(zhǔn)確率可以看出綜合分類能力,而精確率和召回率可以看出其中一個(gè)分類的預(yù)測能力入偷。
二分類評估
二分類評估結(jié)果通常是計(jì)算得到ROC/K-S曲線等。
簡單地說丙曙,ROC曲線越靠左上角/AUC越大/F1 score越大/KS越大說明模型效果越好。
特征重要性
XGBoost模型對特征重要性進(jìn)行了評估拆挥,對于貢獻(xiàn)度非常小的特征維度在訓(xùn)練過程中舍棄掉了惰瓜。
同時(shí)备禀,訓(xùn)練好的模型可以輸出特征重要性排序,也就是說可以根據(jù)特征重要性進(jìn)行針對性的優(yōu)化另患。例如租冠,某維度重要特征由某服務(wù)計(jì)算得來顽爹,那么提升這個(gè)服務(wù)能力將比提高其他能力對結(jié)果的影響更大。
服務(wù)集成
模型導(dǎo)出
訓(xùn)練好的模型可導(dǎo)出為標(biāo)準(zhǔn)的pmml格式文件涉馅。
pmml格式是數(shù)據(jù)挖掘的通用規(guī)范格式,pmml文件其實(shí)就是一個(gè)很長的xml桥爽,包含用到的特征及特征間的關(guān)系跪楞。通過pmml文件可以加載訓(xùn)練的模型并執(zhí)行預(yù)測缕碎,也就是說pmml是一個(gè)“類代碼”,用于生成可運(yùn)行的“實(shí)例”寨典。
集成至服務(wù)
可以簡單通過pmml-evaluator
包加載pmml文件:
<!-- 依賴 -->
<dependency>
<groupId>org.jpmml</groupId>
<artifactId>pmml-evaluator</artifactId>
<version>1.5.9</version>
</dependency>
/* 集成 */
@Service
public class PmmlDemo {
/**
* 模型pmml文件路徑
*/
private static final String MODEL_PMML_PATH = "/model/gbdt_model_20210106.pmml";
/**
* 模型
*/
private Evaluator model;
/**
* 參數(shù)列表
*/
private List<InputField> paramFields;
/**
* Positive目標(biāo)分類,同訓(xùn)練數(shù)據(jù)打標(biāo)中的Positive分類
*/
private static final Object TARGET_CATEGORY = "0";
@PostConstruct
public void init() throws IOException, JAXBException, SAXException {
model = buildEvaluator();
paramFields = model.getInputFields();
}
/**
* 加載模型
* pmml-evaluator 1.5.x版本的使用方式與1.4略有不同
*/
private static Evaluator buildEvaluator() throws JAXBException, SAXException, IOException {
InputStream inputStream = PmmlDemo.class.getResourceAsStream(MODEL_PMML_PATH);
PMML pmml = PMMLUtil.unmarshal(inputStream);
inputStream.close();
ModelEvaluatorBuilder evaluatorBuilder = new ModelEvaluatorBuilder(pmml, (String)null)
.setModelEvaluatorFactory(ModelEvaluatorFactory.newInstance())
.setValueFactoryFactory(ValueFactoryFactory.newInstance());
return evaluatorBuilder.build();
}
/**
* 模型預(yù)測
*/
public Double getPredictScore(BizFeature feature) throws InvocationTargetException, IllegalAccessException {
if (feature == null) {
throw new NullPointerException();
}
// 讀取feature數(shù)據(jù)
Map<String, Object> fieldMap = featureToMap(feature);
// 填充模型輸入
Map<FieldName, FieldValue> params = fillParams(fieldMap);
// 預(yù)測
ProbabilityDistribution result = predict(params);
if (result == null) {
return null;
}
return result.getProbability(TARGET_CATEGORY);
}
/**
* 通過反射把業(yè)務(wù)特征BO屬性轉(zhuǎn)為map結(jié)構(gòu)
* 包括數(shù)據(jù)的預(yù)處理
*/
private static Map<String, Object> featureToMap(BizFeature feature) throws InvocationTargetException, IllegalAccessException {
Map<String, Object> output = Maps.newHashMapWithExpectedSize(512);
Method[] methods = BizFeature.class.getDeclaredMethods();
for (Method method : methods) {
String key = method.getName();
if (!key.startsWith("get")) {
continue;
}
key = key.toLowerCase();
if (key.contains("bizid") || key.contains("entityid") || key.contains("label")) {
continue;
}
Object value = method.invoke(feature);
key = key.substring(3);
put(output, key, value);
}
return output;
}
/**
* 數(shù)據(jù)預(yù)處理
* 這里用到歸一化
*/
private static void put(Map<String, Object> outputMap, String key, Object value) {
if (value instanceof Integer) {
outputMap.put(key, BizFeatureNormalizationHelper.normalization(key, (Integer)value));
} else if (value instanceof Double) {
outputMap.put(key, BizFeatureNormalizationHelper.normalization(key, (Double)value));
}
}
/**
* 根據(jù)模型需要的特征寇仓,提取對應(yīng)的業(yè)務(wù)特征值進(jìn)行填充
*/
private Map<FieldName, FieldValue> fillParams(Map<String, Object> map) {
Map<FieldName, FieldValue> params = Maps.newHashMap();
for (InputField inputField : paramFields) {
FieldName inputFieldName = inputField.getName();
Object rawValue = map.get(inputFieldName.getValue());
FieldValue inputFieldValue = inputField.prepare(rawValue);
params.put(inputFieldName, inputFieldValue);
}
return params;
}
/**
* 預(yù)測似乎是非線程安全的躺枕?使用synchronized
*/
private synchronized ProbabilityDistribution predict(Map<FieldName, FieldValue> arguments) {
Map<FieldName, ?> results = model.evaluate(arguments);
List<TargetField> targetFields = model.getTargetFields();
if (CollectionUtils.isEmpty(targetFields)) {
return null;
}
TargetField targetField = targetFields.get(0);
FieldName targetFieldName = targetField.getName();
return (ProbabilityDistribution)results.get(targetFieldName);
}
}
注意:pmml-evaluator
包1.5以前的版本模型加載方式和1.5.x版本方式不同。
在線預(yù)測
預(yù)測結(jié)果會輸出每個(gè)分類的0~1打分危彩,在二分類中兩個(gè)分類的結(jié)果是互補(bǔ)的,如對a分類預(yù)測分0.3梧奢,則對b分類預(yù)測分就是0.7惦蚊。
通過分類效果評估步驟可以確定一個(gè)合理的預(yù)測分閾值蹦锋,即控制是以0.4分界還是0.6分界兆沙,小于這個(gè)閾值的為a分類,否則為b分類莉掂。
在線預(yù)測結(jié)果跟模型訓(xùn)練階段同一條數(shù)據(jù)的預(yù)測結(jié)果可能有細(xì)微不同葛圃,因?yàn)閿?shù)據(jù)預(yù)處理階段使用的統(tǒng)計(jì)信息不完全相同。本業(yè)務(wù)訓(xùn)練時(shí)使用的歸一化統(tǒng)計(jì)信息和在線預(yù)測時(shí)的略有不同憎妙。
補(bǔ)充
在淌過這條河以后發(fā)現(xiàn)库正,如果并不深究而是淺顯地使用機(jī)器學(xué)習(xí)模型解決問題的話,確實(shí)沒有想象中那么難厘唾。希望這篇簡短的記錄能夠幫助更多人更容易地切換思路褥符,在工具包里添加一項(xiàng)新的利器。
另外抚垃,Java/Python也有現(xiàn)成工具可以直接訓(xùn)練模型喷楣,導(dǎo)出為pmml文件趟大,當(dāng)然平臺會更方便一些就是了。
踩坑經(jīng)歷
- 一定要定義好問題抡蛙,這可能是小白嘗試機(jī)器學(xué)習(xí)最難的一步(淚)
- 二分類中樣本數(shù)量最好相近护昧,否則可能會導(dǎo)致模型對某個(gè)分類過擬合
- 集成pmml后需要做與訓(xùn)練模型時(shí)相同的數(shù)據(jù)預(yù)處理
參考文檔
機(jī)器學(xué)習(xí)-回歸問題(Regression) - 知乎
機(jī)器學(xué)習(xí)系列(七)——分類問題(classification)zxhohai的博客-CSDN博客分類問題
分類器評估指標(biāo)——混淆矩陣 ROC AUC KS AR PSI Lift Gain_snowdroptulip的博客-CSDN博客