手把手教你做最好的電商搜索引擎(1)類目預測

NO IMAGE

手把手教你做最好的電商搜索引擎(1) – 類目預測

引言

電商已經在當今我們的生活中扮演中舉足輕重的角色,而搜索引擎作為電商系統的主要流量入口,搜索的體驗對整個系統起到至關重要的作用,如何優化搜索,讓用戶搜的更好,更快,已經是一個成熟的電商系統的必修課了。

搜索的核心和本質

什麼是好的電商搜索引擎?

一個優秀的電商搜索引擎的本質其實就是理解用戶的需求,幫助用戶快速找到想要的商品,並達成交易。

那麼,搜索引擎是如何理解用戶的意圖的呢?常見的方法有3種:

1.基於詞典以及模版的規則方法

人工給用戶的搜索詞分類,比如用戶搜索iphone,人工歸類到“手機”分類。這種處理方式精準有效,對付一些熱門的商品/特殊名詞非常有效,但是隨著用戶越來越多,關鍵詞,包括長尾詞,越來越多越複雜,靠人工已經沒辦法處理那麼大的工作量。

2.基於用戶行為的統計方法

根據用戶的行為,比如用戶搜蘋果,大部分點了手機、少數人點了水果,根據統計就可以得出蘋果的類目依次是:手機 > 水果。
這種方法依賴於用戶行為,而且和方法1一樣,遇到複雜的詞,就很難命中。特別是中文這種博大精深的字順序錯了點點也基本不會影響別人理解的語言。

3.基於機器學習模型來對用戶的意圖進行判別

隨著人工智能的發展壯大,NLP (Natural Language Processing) ,自然語言處理成為AI時代最重要的組成部分。這種主要是通過機器學習及深度學習的方式,對已標註好的領域的語料進行訓練學習,得到一個意圖識別的模型。利用該模型,當再輸入一個測試集時,它能快速地預測出該語料對應的分類,並提供對應的置信度。使用這種方式的一個好處就是,在語料不斷豐富後,模型的準確度會不斷提升。

今天我們主要聊一聊第3種方式。

搜索詞處理步驟

電商搜索引擎拿到一個用戶的搜索關鍵詞,一般需要進行以下幾個處理步驟:

1.文本歸一

手把手教你做最好的電商搜索引擎(1)類目預測

常見的操作有:

(1).去除停用詞,如:用戶不小心輸入的特殊符號、標點符號

(2).大小寫統一,如:Nike/nike,iphone xr/IPHONE Xr

(3).不同語言轉換,如:iphone/蘋果手機,阿迪達斯/adidas

2.文本糾錯,如:iphoe => iphone

3.分詞,如:男士運動衛衣連帽李寧 => 男士 運動 衛衣 連帽 李寧

4.意圖識別/中心詞識別,如:

“男士運動衛衣連帽李寧”,分詞結果:“男士 運動 衛衣 連帽 李寧”

識別結果:

人群:男士
品類:衛衣
品類修飾詞: 運動 連帽
品牌: 李寧

5.類目預測/文本分類,如:

男士運動衛衣連帽李寧 => 運動服

睡衣女秋冬 => 女士內衣/家居服

作為NLP領域最經典的使用場景之一,文本分類積累了許多的實現方法,如Facebook開源的FastText、卷積神經網絡(CNN)和循環神經網絡(RNN)等,這裡我們主要看一下基於深度學習的文本分類。

原理我們就不細說了,今天我們主要使用FastText來實踐如何進行類目預測。

Let’s do it!

FastText類目預測實踐

系統架構

手把手教你做最好的電商搜索引擎(1)類目預測

數據準備

接下來我們開始收集數據吧。

眾所周知,機器學習的一大難題,就是收集大量標註好的樣本,要保證樣本容易處理,更新及時,覆蓋全面,不是一件容易的事。

因為我們系統的主要商品來源是淘寶,所以我以淘寶商品為例,類目就使用淘寶的一級類目,文本數據就使用淘寶的商品標題。

差不多是這樣的:

類目標題
美容護膚/美體/精油正品護膚 蘭蔻水份緣舒緩柔膚者哩50ml 中樣超水妍保溼啫喱凝露水

我們需要一個分詞工具,分詞有很多工具,比如阿里雲、騰訊雲等第三方服務,也可以使用結巴分詞等開源工具,這裡我們使用結巴分詞來自建一個http分詞接口。

$ npm init
$ npm install nodejieba --save
$ vim fenci.js

fenci.js內容如下:


"use strict";
(function () {
const queryString = require('querystring'),
http = require('http'),
nodejieba = require("nodejieba")
const port = process.env.PORT || 8800
const host = process.env.HOST || '0.0.0.0'
const requestHandler = (request, response) => {
let query = request.url.split('?')[1]
let queryObj = queryString.parse(query)
let result = nodejieba.cut(queryObj.text, true)
let res = {
rs: result
}
response.setHeader('Content-Type', 'application/json')
response.end( JSON.stringify(res, null, 2) )
}
const server = http.createServer(requestHandler);
server.listen(port, host, (error) => {
if (error) {
console.error(error);
}
console.log(`server is listening on ${port}`)
})
}).call(this)

使用pm2運行:

$ pm2 start fenci.js

驗證分詞接口(注意參數要urlencode一次):

$ curl 'http://127.0.0.1:8800/?text=%E4%BF%9D%E6%9A%96%E5%86%85%E8%A1%A3' 

得到如下輸出,表示接口已經正常工作了:

{
"rs": [
"保暖",
"內衣"
]
}

那上面的商品標題差不多就變成了這樣:

正品 護膚 蘭蔻 水份 緣 舒緩 柔膚者 哩 50 ml 中樣 超水妍 保溼 啫喱 凝 露水

分詞不準確的話,我們可以收集一些電商常用的品牌詞和術語等等,保存到分詞用戶自定義詞庫(user.dict.utf8文件)中,並重啟分詞服務即可。

文本樣本庫

為了方便管理,我們把樣本庫存到mysql中,讓我們建一個mysql表:

CREATE TABLE `tb_text_train` (
`item_id` bigint(20) NOT NULL COMMENT '淘寶商品ID',
`title` varchar(255) DEFAULT NULL COMMENT '淘寶商品標題',
`level_one_category_name` varchar(255) DEFAULT NULL COMMENT '淘寶商品類目',
`title_split` varchar(1000) DEFAULT NULL COMMENT '標題分詞',
`done` tinyint(1) DEFAULT '0' COMMENT '樣本已處理為1,未處理為0',
`updatetime` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`item_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

這樣我們就可以採用定時腳本,定期把庫裡的商品標題處理完(去除停用詞,分詞),同步到樣本庫。也可以做一個後臺,在後臺人工處理標註。

待訓練文本處理

文本標註完,我們可以使用sql語句來導出已標註好的數據,按照想要的格式導出為一個data.txt文件,格式如下:

__label__傳統滋補營養品 茯薏芷溼膏 茯清溼 茶芷溼 茶
__label__男裝 秋季 褲子 男 休閒 長褲 韓版 潮流 學生 寬鬆 束腳 褲百搭 運動褲 男 九分褲
__label__住宅傢俱 大理石 面 功夫茶 茶几 簡約 現代中式 多功能 泡茶 一體桌 客廳 辦公室 喝茶
__label__服飾配件/皮帶/帽子/圍巾 蝴蝶結 禁慾 帽子 夏天 遮陽 防晒 漁夫帽 出遊 海邊 度假 日系 文藝 女 草帽
__label__女鞋 秋冬 真皮 保暖 豆豆 鞋 平底 休閒女鞋 大碼 媽媽 鞋 孕婦 白色 護士 棉鞋 女
__label__居家日用 超聲波 驅蚊器 家用 滅蚊 神器 智能 電子 驅蚊 驅蟲 器 室內 驅鼠滅 蒼蠅 蟑螂
__label__女士內衣/男士內衣/家居服 男士 平角 內褲 純棉 中老年 爸爸 平角 褲 全棉 褲衩 寬鬆 老人 加肥加大 褲頭

格式說明,一行一個:

__label__分類名 已經處理好的標題字符串

然後我們把整個數據集拆分為2部分,90%做為訓練集,10%做為測試集,代碼如下:

import pandas as pd
import numpy as np
# 語料數據集存放路徑
data_path = "/usr/local/webdata/fastText/"
# 讀取語料數據集文本文件
train = pd.read_csv(data_path+"data.txt", header=0, sep='\r\n', engine='python')
ts =  train.shape
# 打亂數據集,拆分為訓練集train.txt和測試集test.txt
df = pd.DataFrame(train)
new_train = df.reindex(np.random.permutation(df.index))
# 訓練集和測試集比例為9:1
indice_90_percent = int((ts[0]/100.0)* 90)
# 打散到2個文件中
new_train[:indice_90_percent].to_csv(data_path+'train.txt',index=False)
new_train[indice_90_percent:].to_csv(data_path+'test.txt',index=False)

使用fastText訓練

1.安裝fasttext:

fasttext安裝非常簡單:

$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ make
$ pip install .

2.使用fasttext訓練:

使用命令行運行訓練:

$ ./fasttext supervised -input train.txt -output model -label __label__ -epoch 50 -wordNgrams 3 -dim 100 -lr 0.5 -loss hs

或者寫python程序調用fasttext模塊:

import fasttext
model = fasttext.train_supervised(input="model",
lr=0.5,
epoch=100,
wordNgrams=3,
dim=100,
loss='hs')

模型訓練結束後,會生成模型文件model.bin, 文本向量文件model.vec。

3.驗證數據集

訓練的差不多了,讓我們用測試集來檢查一下,是騾子是馬拉出來遛遛。

$ ./fasttext test model.bin test.txt

會出現以下的結果:

N   1892209
[email protected] 0.982
[email protected] 0.982

恩,看起來不錯。

我們用實際的用戶搜索詞來試一下吧, 比如剛才我們分詞“保暖內衣”,得到的分詞結果是“保暖 內衣”,那麼提交給fasttext預測就是這樣的:

$ echo '保暖 內衣' | ./fasttext predict model.bin -

稍等片刻,就可以得到這樣的結果:

__label__女士內衣/男士內衣/家居服

bingo,預測非常完美!

Tips:
默認生成的model.bin文件會很大,可以使用quantize命令來壓縮模型文件:

$ ./fasttext quantize -output model

會生成體積大幅減少的model.ftz文件,測試下來效果很顯著,比如我這裡由4.7G變成了616MB,很棒~~

4.7G    model.bin
616M    model.ftz
60K     model.o
6.1G    model.vec

使用方式和model.bin一樣,如:

$ ./fasttext test model.ftz test.txt
$ echo '保暖 內衣' | ./fasttext predict model.ftz -

4.提供web服務

為了更方便地使用預測服務,我們把預測功能也做成一個http接口,還是使用nodejs。

$ npm install fasttext.js --save
$ vim predict.js

predict.js內容如下:


"use strict";
(function () {
const queryString = require('querystring'),
FastText = require('fasttext.js'),
http = require('http')
const port = process.env.PORT || 8801
const host = process.env.HOST || '0.0.0.0'
const fastText = new FastText({
loadModel: '/usr/local/webdata/fastText/model.ftz'
})
const requestHandler = (request, response) => {
let query = request.url.split('?')[1]
let queryObj = queryString.parse(query)
fastText.predict(queryObj.text)
.then(labels => {
let res = {
predict: labels
}
response.setHeader('Content-Type', 'application/json')
response.end( JSON.stringify(res, null, 2) )
})
.catch(error => {
console.error("predict error", error)
})
}
const server = http.createServer(requestHandler)
fastText.load()
.then(done => {
console.log("model loaded")
server.listen(port, host, (error) => {
if (error) {
console.error(error)
}
console.log(`server is listening on ${port}`)
})
})
.catch(error => {
console.error("load error", error)
});
}).call(this)

同樣,我們使用pm2來啟動服務:

$ pm2 start predict.js

驗證一下效果(注意參數要先用上面的分詞接口分詞,再urlencode一次):

$ curl 'http://127.0.0.1:8801/?text=%E4%BF%9D%E6%9A%96%20%E5%86%85%E8%A1%A3'

得到這樣的結果:

{
"predict": [
{
"label": "女士內衣/男士內衣/家居服",
"score": "1.00005"
},
{
"label": "童裝/嬰兒裝/親子裝",
"score": "0.0543005"
}
]
}

好像效果很不錯哦,選用score值最大的基本上就滿足需求了。

總結

經過上面簡單的幾步,我們已經成功搭建了一套淘寶商品分類預測服務,包含分詞系統、分類預測系統。把這些服務集成到我們的搜索功能中,我們的搜索就更準了,用戶就買得更開心了。

代碼請見我的github

參考

FastText
fasttext.js
結巴中文分詞

相關文章

2020年的前端資源

再見2019展望未來|年度徵文

JS高級之手寫一個Promise,Generator,async和await【近1W字】

值得期待的JavaScript新特性