R&S | 手把手搞推薦[3]:數(shù)據(jù)集存取思路

上一期講到我們進(jìn)行一整套特征工程,然而可怕的是,事實(shí)上這樣存儲(chǔ)的訓(xùn)練集數(shù)據(jù)體積無敵大,這樣的數(shù)據(jù)在計(jì)算圖中將遇到內(nèi)存不足的關(guān)鍵問題,給大家看看我在讀取數(shù)據(jù)的時(shí)候的壯觀景象:

內(nèi)存爆炸

因此,在進(jìn)行LR之前,我還是冒著被大家打的風(fēng)險(xiǎn),寫一篇挽尊之作,教大家設(shè)計(jì)更合適的方法存取數(shù)據(jù)集。

簡單說思路

口述思路

現(xiàn)在的當(dāng)務(wù)之急是找到一個(gè)合適的方式壓縮存儲(chǔ)空間,從而保證空間復(fù)雜度較低,然而在更多現(xiàn)實(shí)場景,其實(shí)還會(huì)涉及很多問題,因此在這里,我統(tǒng)一講講如何設(shè)計(jì)一個(gè)好的方式進(jìn)行數(shù)據(jù)集的存取,說白了就是要思考這幾個(gè)問題:

  • 用什么存?純文本?CSV?SQL?excel?甚至是其他更新更多樣化的操作
  • 用什么結(jié)構(gòu)存?

那么,在進(jìn)行選擇的時(shí)候,實(shí)際上要考慮的是這幾個(gè)問題:

  • 安全性。存取安全,一定級別下防止泄露。
  • 完整性。不會(huì)存在數(shù)據(jù)出錯(cuò)。
  • 高效性。存取的復(fù)雜度要在可控范圍內(nèi)。

實(shí)際問題

在我們的這個(gè)問題下,安全性不要求;完整性上述方案基本能保證,所以也沒問題;問題就在于高效性,現(xiàn)在這個(gè)是我們目前面臨的重大問題,所以我們要好好處理,保證數(shù)據(jù)不失真的情況下去處理。

壓縮的核心在于略去不必要的信息,或者用更簡單的方式來描述更多的信息,例如條件允許的情況下二元數(shù)組可以用一元數(shù)組+函數(shù)的方式存儲(chǔ),那么在此處,我們先看看數(shù)據(jù)。

  • 我們的數(shù)據(jù)是一套o(hù)ne-hot數(shù)據(jù)
  • one-hot數(shù)據(jù)本身是一種稀疏矩陣的結(jié)構(gòu)
  • 稀疏矩陣中含有大量的0,僅有少部分非0

因此,我們可以用稀疏矩陣的方式進(jìn)行存取,只記住非零位置下的值,其他位置為0即可。

稀疏矩陣存取

稀疏矩陣的有關(guān)理論在此處不贅述,可以自行查閱,此處給出一種我最終選擇的方案,用CSR格式,技術(shù)方案是用scipy.sparse。

簡單說說CSR,CSR格式實(shí)際上就是用一個(gè)三元組數(shù)組來表示這個(gè)稀疏矩陣,三元組分別表示(col,row,data),即行,列,數(shù)值,非零的位置的數(shù)值得以保留,然后其他位置都是0。

存儲(chǔ)

首先來看,用舊方案和新方案的數(shù)據(jù)結(jié)構(gòu):

舊數(shù)據(jù):純onehot,每行接近1w個(gè)數(shù)據(jù)
新數(shù)據(jù):分為兩塊存儲(chǔ),X部分用稀疏矩陣,然后用npz存儲(chǔ),Y部分用one-hot存儲(chǔ)。
0.0 0.0 1.0 0.0 0.0 23,1.0 3737,1.0 3750,1.0 3757,1.0 6249,1.0 9801,1.0 9806,1.0 9819,1.0

存儲(chǔ)完,體積只有59M,這樣讀取到內(nèi)存的體積也會(huì)小很多,甚至整塊都讀進(jìn)去都沒問題。

這里會(huì)用到稀疏矩陣的2個(gè)函數(shù),此處導(dǎo)入:

from scipy.sparse import csr_matrix, save_npz

下面來看看怎么實(shí)現(xiàn)的,下面是根據(jù)之前上游合并好的數(shù)據(jù),利用加載得到的oh_encoder,分別進(jìn)行轉(zhuǎn)化,分為兩塊輸出,一方面是x特征數(shù)據(jù)矩陣,另一方面是y標(biāo)簽one-hot矩陣。

def gen_res(source_data, oh_encoder):
    col_all = []
    row_all = []
    data_all = []
    idx = 0
    y_res = []
    with open(source_data, encoding="utf8") as f:
        for line in f:
            if idx == 0:
                idx = 1
                continue
            ll = line.strip().split("::")
            ll = line.strip().split("::")
            data_item = []
            scores_item = []
            scores_item = scores_item + oh_encoder["scores"].transform([[ll[0]]])[0].tolist()
            data_item = data_item + oh_encoder["movie_id"].transform([[ll[1]]])[0].tolist()
            data_item = data_item + oh_encoder["movie_year"].transform([[get_year(ll[2])]])[0].tolist()
            data_item = data_item + get_movie_type_oh(ll[3], oh_encoder["movie_type"]).tolist()
            data_item = data_item + oh_encoder["user_id"].transform([[ll[4]]])[0].tolist()
            data_item = data_item + oh_encoder["user_gentle"].transform([[ll[5]]])[0].tolist()
            data_item = data_item + oh_encoder["user_age"].transform([[ll[6]]])[0].tolist()
            data_item = data_item + oh_encoder["user_occupation"].transform([[ll[7]]])[0].tolist()
            # Y處理
            y_res.append(scores_item)
            # X處理
            col, data = sparse_list(data_item)
            col_all = col_all + col
            row_all = row_all + [idx - 1 for item in range(len(col))]
            data_all = data_all + data
            idx = idx + 1
            if idx % 10000 == 0:
                print("generating %s data items" % (idx))
                # break
    x_res = csr_matrix((data_all, (row_all, col_all)), shape=(max(row_all) + 1, 9831))
    return x_res, y_res

總結(jié)這么幾個(gè)點(diǎn):

  • scores數(shù)據(jù)我還是保留著one-hot,存儲(chǔ)差別不是很大,后面的代碼也不用改太多,條件允許的話偷個(gè)懶吧
  • 其他的onehot數(shù)據(jù),我是先用transform計(jì)算出來,進(jìn)行組合,然后再用sparse_list轉(zhuǎn)化,此處,只需要抽取col和data,存儲(chǔ)即可(row因?yàn)闆]必要存,行數(shù)后面取可以算出來)
  • csr_matrix是一個(gè)轉(zhuǎn)化為稀疏矩陣的函數(shù)
  • 9831是除了特征維度,此處規(guī)范化,避免特征出現(xiàn)個(gè)數(shù)不足的現(xiàn)象
  • 有關(guān)稀疏函數(shù)在這塊的應(yīng)用,非常建議大家多看看API文檔,傳送門在這里:https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html
  • 這塊的時(shí)間其實(shí)挺長的,畢竟涉及大量的處理,有條件的各位可以考慮用mapreduce分布式嘗試

下面給出sparse_list的定義,這塊的任務(wù)是對原來的向量,轉(zhuǎn)為用[col,data]存儲(chǔ)的形式:

def sparse_list(array_get):
    col = []
    data = []
    for idx in range(len(array_get)):
        if array_get[idx] != 0:
            col.append(idx)
            data.append(array_get[idx])
    return col, data

在gen_res得到結(jié)果后,就可以進(jìn)行進(jìn)一步存儲(chǔ),下面給出一個(gè)訓(xùn)練數(shù)據(jù)方面的例子。

# 訓(xùn)練數(shù)據(jù)生成
print("generating training data")
x_train, y_train = gen_res(TRAIN_DATA_PATH, oh_encoder)
with open(GEN_DATA_TRAIN_Y_PATH, "w", encoding='utf8') as f:
    for item in y_train:
        f.write("%s\n" % (",".join([str(i) for i in item])))
save_npz(GEN_DATA_TRAIN_X_PATH, x_train)
print("training data generation done")

大寫基本是寫死的路徑和參數(shù),oh_encoder是使用的轉(zhuǎn)化器,save_npz是存儲(chǔ)稀疏矩陣的函數(shù)。

讀取

這塊本來是在后續(xù)機(jī)器學(xué)習(xí)模型那集才會(huì)說的,此處為了內(nèi)容完善寫出來提早放出來~下一集這個(gè)函數(shù)就不單獨(dú)放出來,直接調(diào)用啦。

首先是看這里需要的重要包。

from scipy.sparse import load_npz

load_npz是對應(yīng)稀疏函數(shù)的加載包。

def load_dataset(x_path, y_path):
    y_ = []
    y_oh = []
    idx = 0
    with open(y_path, encoding="utf8") as f:
        for line in f:
            ll = line.strip().split(",")
            y_item = max([idx * int(float(ll[idx])) for idx in range(5)]) + 1
            y_oh_item = [int(float(item)) for item in ll[:5]]
            y_.append(y_item)
            y_oh.append(y_oh_item)
            idx = idx + 1
            if idx % 10000 == 0:
                print("loading %s" % idx)
    x_ = load_npz(x_path)
    return x_, y_, y_oh

同樣簡單粗暴地給關(guān)鍵點(diǎn)吧

  • 此處要用scipy給的數(shù)據(jù)結(jié)構(gòu)來存
    • 原因是scipy.sparse這個(gè)數(shù)據(jù)類型能夠直接放在sklearn下的機(jī)器學(xué)習(xí)模型訓(xùn)練輸入中
    • 存取比較直接,不用關(guān)心內(nèi)部邏輯,復(fù)雜度一般不會(huì)太高
  • x和y分別取出分別放好,畢竟后續(xù)訓(xùn)練的時(shí)候也是要分開放入的
  • 此處的輸出我分了三個(gè),作為稀疏矩陣的特征,數(shù)值型的y,和one-hot的y,日后結(jié)果評估會(huì)有用。

小結(jié)

算法從來不止是機(jī)器學(xué)習(xí),而是對整個(gè)項(xiàng)目流程的把握,根據(jù)特定目標(biāo)做好規(guī)劃,一整個(gè)項(xiàng)目才得以完成。本文主要以movielens為例談?wù)摂?shù)據(jù)集存取策略的問題,是特征工程后確定數(shù)據(jù)存儲(chǔ)格式、存取方式的重要一步,合理高效的存取策略會(huì)令整個(gè)系統(tǒng)在進(jìn)行模型計(jì)算時(shí)更加高效,省時(shí)省空間。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容