3制造数据集_R&S | 手把手搞推荐[3]:数据集存取

上一期讲到我们进行一整套特征工程,然而可怕的是,在今天看数据时,训练集数据体积无敌大,这样的数据在计算图中将遇到内存不足的关键问题,给大家看看我在读取数据的时候的壮观景象:

9a001486e37d51a7484c702fbb1c2dcf.png

因此,在进行LR之前,首先教大家设计更合适的方法存取数据集。

简单说思路

口述思路

现在的当务之急是找到一个合适的方式压缩存储空间,从而保证空间复杂度较低,然而在更多现实场景,其实还会涉及很多问题,因此在这里,我统一讲讲如何设计一个好的方式进行数据集的存取,说白了就是要思考这几个问题:

  • 用什么存?纯文本?CSV?SQL?excel?甚至是其他更新更多样化的操作
  • 用什么结构存?

那么,在进行选择的时候,实际上要考虑的是这几个问题:

  • 安全性。存取安全,一定级别下防止泄露。
  • 完整性。不会存在数据出错。
  • 高效性。存取的时间和空间复杂度要在可控范围内。

实际问题

在我们的这个问题下,安全性不要求,我都给你们开源了,那其实安全性没啥;完整性上述方案基本能保证,所以也没问题;问题就在于高效性,现在这个是我们目前面临的重大问题,所以我们要好好处理,具体的处理是先看看现在的数据。

压缩的核心在于略去不必要的信息,或者用更简单的方式来描述更多的信息,例如条件允许的情况下二元数组可以用一元数组+函数的方式存储,那么在此处,我们先看看数据。

  • 我们的数据是一套one-hot数据
  • one-hot数据本身是一种稀疏矩阵的结构
  • 稀疏矩阵中含有大量的0,仅有少部分非0

因此,我们可以用稀疏矩阵的方式进行存取,只记住非零位置下的值,其他位置为0即可。

稀疏矩阵存取

稀疏矩阵的有关理论在此处就不赘述啦,可以自行查阅,此处给出一种我最终选择的方案,用CSR格式,技术方案是用scipy.sparse。

简单说说CSR,CSR格式实际上就是用一个三元组数组来表示这个稀疏矩阵,三元组分别表示(col,row,data),即行,列,数值,非零的位置的数值得以保留,然后其他位置都是0。

存储

首先来看,用旧方案和新方案的数据结构:

旧数据:纯onehot,每行接近1w个数据
新数据:分为两块存储,X部分用稀疏矩阵,然后用npz存储,Y部分用one-hot存储。
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

存储完,体积只有59M,这样读取到内存的体积也会小很多。

这里会用到稀疏矩阵的2个函数,此处导入:

from scipy.sparse import csr_matrix, save_npz

下面来看看怎么实现的,下面是根据之前上游合并好的数据,利用加载得到的oh_encoder,分别进行转化,分为两块输出,一方面是x特征数据矩阵,另一方面是y标签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

总结这么几个点:

  • scores数据我还是保留着one-hot,存储差别不是很大,后面的代码也不用改太多,条件允许的话偷个懒吧
  • 其他的onehot数据,我是先用transform计算出来,进行组合,然后再用sparse_list转化,此处,只需要抽取col和data,存储即可(row因为没必要存,行数后面取可以算出来)
  • csr_matrix是一个转化为稀疏矩阵的函数
  • 9831是除了特征维度,此处规范化,避免特征出现个数不足的现象
  • 有关稀疏函数在这块的应用,非常建议大家多看看API文档,传送门在这里:https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html
  • 这块的时间其实挺长的,毕竟涉及大量的处理,有条件的同学可以考虑用mapreduce分布式尝试

下面给出sparse_list的定义,这块的任务是对原来的向量,转为用[col,data]存储的形式:

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得到结果后,就可以进行进一步存储,下面给出一个训练数据方面的例子。

# 训练数据生成
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("%sn" % (",".join([str(i) for i in item])))
save_npz(GEN_DATA_TRAIN_X_PATH, x_train)
print("training data generation done")

大写基本是写死的路径和参数,oh_encoder是使用的转化器,save_npz是存储稀疏矩阵的函数。

读取

这块本来是在后续机器学习模型那集才会说的,此处为了内容完善写出来提早放出来~下一集这个函数就不单独放出来,直接调用啦。

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

from scipy.sparse import load_npz

load_npz是对应稀疏函数的加载包。

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

同样简单粗暴地给关键点吧

  • 此处要用scipy给的数据结构来存
  • 原因是scipy.sparse这个数据类型能够直接放在sklearn下的机器学习模型训练输入中
  • 存取比较直接,不用关心内部逻辑,复杂度一般不会太高
  • x和y分别取出分别放好,毕竟后续训练的时候也是要分开放入的
  • 此处的输出我分了三个,作为稀疏矩阵的特征,数值型的y,和one-hot的y,日后结果评估会有用。

小结

这块的内容不是很多,就是想补充自己挖的坑,这里还有一个补充建议,当做是给读到这里的人的彩蛋吧。

虽然说调包侠非常不受待见,但是在实践中能调包尽量调包,毕竟包是别人写的比较好的程序,大部分情况下性能会比你自己造轮子高,同时也十分方便,何乐而不为?只会调包和善于调包是两个不同的概念哈。

从来就没有简单的事情,认真思考吸取经验,才会让自己进一步成长。

补充

近期有一份关于我的采访,非常感谢豪哥给我的机会,说大神距离可能还有很远,希望能和大家多交流沟通:

书豪:书豪采访记(三)算法大神曾冠荣​zhuanlan.zhihu.com
f4d61a53bf7284f12cca22f697524337.png

另外有些我写的有关文章,有兴趣也可以点击进去看看,如果觉得不错欢迎给个赞和感谢!

R&S | 手把手搞推荐[0]:推荐入门小结

R&S | 手把手搞推荐[1]:数据探索

R&S 手把手搞推荐[2]:特征工程指南

R&S | 爱奇艺搜索启发

我的NLP学习之路

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值