【深度学习系列】PaddlePaddle之数据预处理
2017-11-08 13:15
267 查看
上篇文章讲了卷积神经网络的基本知识,本来这篇文章准备继续深入讲CNN的相关知识和手写CNN,但是有很多同学跟我发邮件或私信问我关于PaddlePaddle如何读取数据、做数据预处理相关的内容。网上看的很多教程都是几个常见的例子,数据集不需要自己准备,所以不需要关心,但是实际做项目的时候做数据预处理感觉一头雾水,所以我就写一篇文章汇总一下,讲讲如何用PaddlePaddle做数据预处理。
PaddlePaddle的基本数据格式
根据官网的资料,总结出PaddlePaddle支持多种不同的数据格式,包括四种数据类型和三种序列格式:
四种数据类型:
dense_vector:稠密的浮点数向量。
sparse_binary_vector:稀疏的二值向量,即大部分值为0,但有值的地方必须为1。
sparse_float_vector:稀疏的向量,即大部分值为0,但有值的部分可以是任何浮点数。
integer:整型格式
api如下:
说明:稠密向量,输入特征是一个稠密的浮点向量。举个例子,手写数字识别里的输入图片是28*28的像素,Paddle的神经网络的输入应该是一个784维的稠密向量。
参数:
dim(int) 向量维度
seq_type(int)输入的序列格式
返回类型:InputType
说明:稀疏的二值向量。输入特征是一个稀疏向量,这个向量的每个元素要么是0,要么是1
参数:同上
返回类型:同上
说明:稀疏向量,向量里大多数元素是0,其他的值可以是任意的浮点值
参数:同上
返回类型:同上
说明:整型格式
参数:
seq_type(int):输入的序列格式
value_range(int):每个元素的范围
返回类型:InputType
三种序列格式:
SequenceType.NO_SEQUENCE:不是一条序列
SequenceType.SEQUENCE:是一条时间序列
SequenceType.SUB_SEQUENCE: 是一条时间序列,且序列的每一个元素还是一个时间序列。
api如下:
说明:稠密向量的序列格式
参数:dim(int):稠密向量的维度
返回类型:InputType
说明:稀疏的二值向量序列。每个序列里的元素要么是0要么是1
参数:dim(int):稀疏向量的维度
返回类型:InputType
说明:稀疏的向量序列。每个序列里的元素要么是0要么是1
参数:
dim(int):稀疏向量的维度
seq_type(int):输入的序列格式
返回类型:InputType
说明:稀疏的向量序列,向量里大多数元素是0,其他的值可以是任意的浮点值
参数:
dim(int):稀疏向量的维度
seq_type(int):输入的序列格式
返回类型:InputType
说明:value_range(int):每个元素的范围
不同的数据类型和序列模式返回的格式不同,如下表:
View Code
reader decorator
如果想要读取同时读取两部分的数据,那么可以定义两个reader,合并后对其进行shuffle。如我想读取所有用户对比车系的数据和浏览车系的数据,可以定义两个reader,分别为contrast()和view(),然后通过预定义的reader decorator缓存并组合这些数据,在对合并后的数据进行乱序操作。源码见decorator.py
这样有一个很大的好处,就是组合特征来训练变得更容易了!传统的跑模型的方法是,确定label和feature,尽可能多的找合适的feature扔到模型里去训练,这样我们就需要做一张大表,训练完后我们可以分析某些特征的重要性然后重新增加或减少一些feature来进行训练,这样我们有需要对原来的label-feature表进行修改,如果数据量小没啥影响,就是麻烦点,但是数据量大的话需要每一次增加feature,和主键、label来join的操作都会很耗时,如果采取这种方式的话,我们可以对某些同一类的特征做成一张表,数据存放的地址存为一个变量名,每次跑模型的时候想选取几类特征,就创建几个reader,用reader decorator 组合起来,最后再shuffle灌倒模型里去训练。这!样!是!不!是!很!方!便!
如果没理解,我举一个实例,假设我们要预测用户是否会买车,label是买车 or 不买车,feature有浏览车系、对比车系、关注车系的功能偏好等等20个,传统的思维是做成这样一张表:
如果想要减少feature_2,看看feature_2对模型的准确率影响是否很大,那么我们需要在这张表里去掉这一列,想要增加一个feature的话,也需要在feature里增加一列,如果用reador decorator的话,我们可以这样做数据集:
把相同类型的feature放在一起,不用频繁的join减少时间,一共做四个表,创建4个reador:
如果新发现了一个特征,想尝试这个特征对模型提高准确率有没有用,可以再单独把这个特征数据提取出来,再增加一个reader,用reader decorator组合起来,shuffle后放入模型里跑就行了。
PaddlePaddle的数据预处理实例
还是以手写数字为例,对数据进行处理后并划分train和test,只需要4步即可:
指定数据地址
2.创建reader creator
3.创建训练集和测试集
4.下载数据并转换成相应格式
如果想换成自己的训练数据,只需要按照步骤改成自己的数据地址,创建相应的reader creator(或者reader decorator)即可。
这是图像的例子,如果我们想训练一个文本模型,做一个情感分析,这个时候如何处理数据呢?步骤也很简单。
假设我们有一堆数据,每一行为一条样本,以
现在开始做数据预处理
1.创建reader
返回类型为:
2.组合读取方式
完整的代码如下(加上了划分train和test部分):
总结
这篇文章主要讲了在paddlepaddle里如何加载自己的数据集,转换成相应的格式,并划分train和test。我们在使用一个框架的时候通常会先去跑几个简单的demo,但是如果不用常见的demo的数据,自己做一个实际的项目,完整的跑通一个模型,这才代表我们掌握了这个框架的基本应用知识。跑一个模型第一步就是数据预处理,在paddlepaddle里,提供的方式非常简单,但是有很多优点:
shuffle数据非常方便
可以将数据组合成batch训练
可以利用reader decorator来组合多个reader,提高组合特征运行模型的效率
可以多线程读取数据
而我之前使用过mxnet来训练车牌识别的模型,50w的图片数据想要一次训练是非常慢的,这样的话就有两个解决方法:一是批量训练,这一点大多数的框架都会有, 二是转换成mxnet特有的rec格式,提高读取效率,可以通过im2rec.py将图片转换,比较麻烦,如果是tesnorflow,也有相对应的特定格式tfrecord,这几种方式各有优劣,从易用性上,paddlepaddle是比较简单的。
这篇文章没有与上篇衔接起来,因为看到有好几封邮件都有问怎么自己加载数据训练,所以就决定插入一节先把这个写了。下篇文章我们接着讲CNN的进阶知识。下周见^_^!
参考文章:
1.官网说明:http://doc.paddlepaddle.org/develop/doc_cn/getstarted/concepts/use_concepts_cn.html
PaddlePaddle的基本数据格式
根据官网的资料,总结出PaddlePaddle支持多种不同的数据格式,包括四种数据类型和三种序列格式:
四种数据类型:
dense_vector:稠密的浮点数向量。
sparse_binary_vector:稀疏的二值向量,即大部分值为0,但有值的地方必须为1。
sparse_float_vector:稀疏的向量,即大部分值为0,但有值的部分可以是任何浮点数。
integer:整型格式
api如下:
paddle.v2.data_type.
dense_vector(dim, seq_type=0)
说明:稠密向量,输入特征是一个稠密的浮点向量。举个例子,手写数字识别里的输入图片是28*28的像素,Paddle的神经网络的输入应该是一个784维的稠密向量。
参数:
dim(int) 向量维度
seq_type(int)输入的序列格式
返回类型:InputType
paddle.v2.data_type.
sparse_binary_vector(dim, seq_type=0)
说明:稀疏的二值向量。输入特征是一个稀疏向量,这个向量的每个元素要么是0,要么是1
参数:同上
返回类型:同上
paddle.v2.data_type.
sparse_vector(dim, seq_type=0)
说明:稀疏向量,向量里大多数元素是0,其他的值可以是任意的浮点值
参数:同上
返回类型:同上
paddle.v2.data_type.
integer_value(value_range, seq_type=0)
说明:整型格式
参数:
seq_type(int):输入的序列格式
value_range(int):每个元素的范围
返回类型:InputType
三种序列格式:
SequenceType.NO_SEQUENCE:不是一条序列
SequenceType.SEQUENCE:是一条时间序列
SequenceType.SUB_SEQUENCE: 是一条时间序列,且序列的每一个元素还是一个时间序列。
api如下:
paddle.v2.data_type.dense_vector_sequence(dim, seq_type=0)
说明:稠密向量的序列格式
参数:dim(int):稠密向量的维度
返回类型:InputType
paddle.v2.data_type.sparse_binary_vector_sequence(dim, seq_type=0)
说明:稀疏的二值向量序列。每个序列里的元素要么是0要么是1
参数:dim(int):稀疏向量的维度
返回类型:InputType
paddle.v2.data_type.sparse_non_value_slot(dim, seq_type=0)
说明:稀疏的向量序列。每个序列里的元素要么是0要么是1
参数:
dim(int):稀疏向量的维度
seq_type(int):输入的序列格式
返回类型:InputType
paddle.v2.data_type.sparse_value_slot(dim, seq_type=0)
说明:稀疏的向量序列,向量里大多数元素是0,其他的值可以是任意的浮点值
参数:
dim(int):稀疏向量的维度
seq_type(int):输入的序列格式
返回类型:InputType
paddle.v2.data_type.integer_value_sequence(value_range, seq_type=0)
说明:value_range(int):每个元素的范围
不同的数据类型和序列模式返回的格式不同,如下表:
1 __all__ = ['np_array', 'text_file', "cloud_reader"] 2 3 4 def np_array(x): 5 """ 6 Creates a reader that yields elements of x, if it is a 7 numpy vector. Or rows of x, if it is a numpy matrix. 8 Or any sub-hyperplane indexed by the highest dimension. 9 :param x: the numpy array to create reader from. 10 :returns: data reader created from x. 11 """ 12 13 def reader(): 14 if x.ndim < 1: 15 yield x 16 17 for e in x: 18 yield e 19 20 return reader 21 22 23 def text_file(path): 24 """ 25 Creates a data reader that outputs text line by line from given text file. 26 Trailing new line ('\\\\n') of each line will be removed. 27 :path: path of the text file. 28 :returns: data reader of text file 29 """ 30 31 def reader(): 32 f = open(path, "r") 33 for l in f: 34 yield l.rstrip('\n') 35 f.close() 36 37 return reader 38 39 40 def recordio(paths, buf_size=100): 41 """ 42 Creates a data reader from given RecordIO file paths separated by ",", 43 glob pattern is supported. 44 :path: path of recordio files, can be a string or a string list. 45 :returns: data reader of recordio files. 46 """ 47 48 import recordio as rec 49 import paddle.v2.reader.decorator as dec 50 import cPickle as pickle 51 52 def reader(): 53 if isinstance(paths, basestring): 54 path = paths 55 else: 56 path = ",".join(paths) 57 f = rec.reader(path) 58 while True: 59 r = f.read() 60 if r is None: 61 break 62 yield pickle.loads(r) 63 f.close() 64 65 return dec.buffered(reader, buf_size) 66 67 68 pass_num = 0 69 70 71 def cloud_reader(paths, etcd_endpoints, timeout_sec=5, buf_size=64): 72 """ 73 Create a data reader that yield a record one by one from 74 the paths: 75 :paths: path of recordio files, can be a string or a string list. 76 :etcd_endpoints: the endpoints for etcd cluster 77 :returns: data reader of recordio files. 78 .. code-block:: python 79 from paddle.v2.reader.creator import cloud_reader 80 etcd_endpoints = "http://127.0.0.1:2379" 81 trainer.train.( 82 reader=cloud_reader(["/work/dataset/uci_housing/uci_housing*"], etcd_endpoints), 83 ) 84 """ 85 import os 86 import cPickle as pickle 87 import paddle.v2.master as master 88 c = master.client(etcd_endpoints, timeout_sec, buf_size) 89 90 if isinstance(paths, basestring): 91 path = [paths] 92 else: 93 path = paths 94 c.set_dataset(path) 95 96 def reader(): 97 global pass_num 98 c.paddle_start_get_records(pass_num) 99 pass_num += 1 100 101 while True: 102 r, e = c.next_record() 103 if not r: 104 if e != -2: 105 print "get record error: ", e 106 break 107 yield pickle.loads(r) 108 109 return reader
View Code
reader decorator
如果想要读取同时读取两部分的数据,那么可以定义两个reader,合并后对其进行shuffle。如我想读取所有用户对比车系的数据和浏览车系的数据,可以定义两个reader,分别为contrast()和view(),然后通过预定义的reader decorator缓存并组合这些数据,在对合并后的数据进行乱序操作。源码见decorator.py
data = paddle.reader.shuffle( paddle.reader.compose( paddle.reader(contradt(contrast_path),buf_size = 100), paddle.reader(view(view_path),buf_size = 200), 500)
这样有一个很大的好处,就是组合特征来训练变得更容易了!传统的跑模型的方法是,确定label和feature,尽可能多的找合适的feature扔到模型里去训练,这样我们就需要做一张大表,训练完后我们可以分析某些特征的重要性然后重新增加或减少一些feature来进行训练,这样我们有需要对原来的label-feature表进行修改,如果数据量小没啥影响,就是麻烦点,但是数据量大的话需要每一次增加feature,和主键、label来join的操作都会很耗时,如果采取这种方式的话,我们可以对某些同一类的特征做成一张表,数据存放的地址存为一个变量名,每次跑模型的时候想选取几类特征,就创建几个reader,用reader decorator 组合起来,最后再shuffle灌倒模型里去训练。这!样!是!不!是!很!方!便!
如果没理解,我举一个实例,假设我们要预测用户是否会买车,label是买车 or 不买车,feature有浏览车系、对比车系、关注车系的功能偏好等等20个,传统的思维是做成这样一张表:
如果想要减少feature_2,看看feature_2对模型的准确率影响是否很大,那么我们需要在这张表里去掉这一列,想要增加一个feature的话,也需要在feature里增加一列,如果用reador decorator的话,我们可以这样做数据集:
把相同类型的feature放在一起,不用频繁的join减少时间,一共做四个表,创建4个reador:
data = paddle.reader.shuffle( paddle.reader.compose( paddle.reader(table1(table1_path),buf_size = 100), paddle.reader(table2(table2_path),buf_size = 100), paddle.reader(table3(table3_path),buf_size = 100), paddle.reader(table4(table4_path),buf_size = 100), 500)
如果新发现了一个特征,想尝试这个特征对模型提高准确率有没有用,可以再单独把这个特征数据提取出来,再增加一个reader,用reader decorator组合起来,shuffle后放入模型里跑就行了。
PaddlePaddle的数据预处理实例
还是以手写数字为例,对数据进行处理后并划分train和test,只需要4步即可:
指定数据地址
1 import paddle.v2.dataset.common 2 import subprocess 3 import numpy 4 import platform 5 __all__ = ['train', 'test', 'convert'] 6 7 URL_PREFIX = 'http://yann.lecun.com/exdb/mnist/' 8 TEST_IMAGE_URL = URL_PREFIX + 't10k-images-idx3-ubyte.gz' 9 TEST_IMAGE_MD5 = '9fb629c4189551a2d022fa330f9573f3' 10 TEST_LABEL_URL = URL_PREFIX + 't10k-labels-idx1-ubyte.gz' 11 TEST_LABEL_MD5 = 'ec29112dd5afa0611ce80d1b7f02629c' 12 TRAIN_IMAGE_URL = URL_PREFIX + 'train-images-idx3-ubyte.gz' 13 TRAIN_IMAGE_MD5 = 'f68b3c2dcbeaaa9fbdd348bbdeb94873' 14 TRAIN_LABEL_URL = URL_PREFIX + 'train-labels-idx1-ubyte.gz' 15 TRAIN_LABEL_MD5 = 'd53e105ee54ea40749a09fcbcd1e9432'
2.创建reader creator
1 def reader_creator(image_filename, label_filename, buffer_size): 2 # 创建一个reader 3 def reader(): 4 if platform.system() == 'Darwin': 5 zcat_cmd = 'gzcat' 6 elif platform.system() == 'Linux': 7 zcat_cmd = 'zcat' 8 else: 9 raise NotImplementedError() 10 11 m = subprocess.Popen([zcat_cmd, image_filename], stdout=subprocess.PIPE) 12 m.stdout.read(16) 13 14 l = subprocess.Popen([zcat_cmd, label_filename], stdout=subprocess.PIPE) 15 l.stdout.read(8) 16 17 try: # reader could be break. 18 while True: 19 labels = numpy.fromfile( 20 l.stdout, 'ubyte', count=buffer_size).astype("int") 21 22 if labels.size != buffer_size: 23 break # numpy.fromfile returns empty slice after EOF. 24 25 images = numpy.fromfile( 26 m.stdout, 'ubyte', count=buffer_size * 28 * 28).reshape( 27 (buffer_size, 28 * 28)).astype('float32') 28 29 images = images / 255.0 * 2.0 - 1.0 30 31 for i in xrange(buffer_size): 32 yield images[i, :], int(labels[i]) 33 finally: 34 m.terminate() 35 l.terminate() 36 37 return reader
3.创建训练集和测试集
1 def train(): 2 """ 3 创建mnsit的训练集 reader creator 4 返回一个reador creator,每个reader里的样本都是图片的像素值,在区间[0,1]内,label为0~9 5 返回:training reader creator 6 """ 7 return reader_creator( 8 paddle.v2.dataset.common.download(TRAIN_IMAGE_URL, 'mnist', 9 TRAIN_IMAGE_MD5), 10 paddle.v2.dataset.common.download(TRAIN_LABEL_URL, 'mnist', 11 TRAIN_LABEL_MD5), 100) 12 13 14 def test(): 15 """ 16 创建mnsit的测试集 reader creator 17 返回一个reador creator,每个reader里的样本都是图片的像素值,在区间[0,1]内,label为0~9 18 返回:testreader creator 19 """ 20 return reader_creator( 21 paddle.v2.dataset.common.download(TEST_IMAGE_URL, 'mnist', 22 TEST_IMAGE_MD5), 23 paddle.v2.dataset.common.download(TEST_LABEL_URL, 'mnist', 24 TEST_LABEL_MD5), 100)
4.下载数据并转换成相应格式
1 def fetch(): 2 paddle.v2.dataset.common.download(TRAIN_IMAGE_URL, 'mnist', TRAIN_IMAGE_MD5) 3 paddle.v2.dataset.common.download(TRAIN_LABEL_URL, 'mnist', TRAIN_LABEL_MD5) 4 paddle.v2.dataset.common.download(TEST_IMAGE_URL, 'mnist', TEST_IMAGE_MD5) 5 paddle.v2.dataset.common.download(TEST_LABEL_URL, 'mnist', TRAIN_LABEL_MD5) 6 7 8 def convert(path): 9 """ 10 将数据格式转换为 recordio format 11 """ 12 paddle.v2.dataset.common.convert(path, train(), 1000, "minist_train") 13 paddle.v2.dataset.common.convert(path, test(), 1000, "minist_test")
如果想换成自己的训练数据,只需要按照步骤改成自己的数据地址,创建相应的reader creator(或者reader decorator)即可。
这是图像的例子,如果我们想训练一个文本模型,做一个情感分析,这个时候如何处理数据呢?步骤也很简单。
假设我们有一堆数据,每一行为一条样本,以
\t分隔,第一列是类别标签,第二列是输入文本的内容,文本内容中的词语以空格分隔。以下是两条示例数据:
positive 今天终于试了自己理想的车 外观太骚气了 而且中控也很棒 negative 这台车好贵 而且还费油 性价比太低了
现在开始做数据预处理
1.创建reader
1 def train_reader(data_dir, word_dict, label_dict): 2 def reader(): 3 UNK_ID = word_dict["<UNK>"] 4 word_col = 0 5 lbl_col = 1 6 7 for file_name in os.listdir(data_dir): 8 with open(os.path.join(data_dir, file_name), "r") as f: 9 for line in f: 10 line_split = line.strip().split("\t") 11 word_ids = [ 12 word_dict.get(w, UNK_ID) 13 for w in line_split[word_col].split() 14 ] 15 yield word_ids, label_dict[line_split[lbl_col]] 16 17 return reader
返回类型为:
paddle.data_type.integer_value_sequence(词语在字典的序号)和
paddle.data_type.integer_value(类别标签)
2.组合读取方式
1 train_reader = paddle.batch( 2 paddle.reader.shuffle( 3 reader.train_reader(train_data_dir, word_dict, lbl_dict), 4 buf_size=1000), 5 batch_size=batch_size)
完整的代码如下(加上了划分train和test部分):
1 import os 2 3 4 def train_reader(data_dir, word_dict, label_dict): 5 """ 6 创建训练数据reader 7 :param data_dir: 数据地址. 8 :type data_dir: str 9 :param word_dict: 词典地址, 10 词典里必须有 "UNK" . 11 :type word_dict:python dict 12 :param label_dict: label 字典的地址 13 :type label_dict: Python dict 14 """ 15 16 def reader(): 17 UNK_ID = word_dict["<UNK>"] 18 word_col = 1 19 lbl_col = 0 20 21 for file_name in os.listdir(data_dir): 22 with open(os.path.join(data_dir, file_name), "r") as f: 23 for line in f: 24 line_split = line.strip().split("\t") 25 word_ids = [ 26 word_dict.get(w, UNK_ID) 27 for w in line_split[word_col].split() 28 ] 29 yield word_ids, label_dict[line_split[lbl_col]] 30 31 return reader 32 33 34 def test_reader(data_dir, word_dict): 35 """ 36 创建测试数据reader 37 :param data_dir: 数据地址. 38 :type data_dir: str 39 :param word_dict: 词典地址, 40 词典里必须有 "UNK" . 41 :type word_dict:python dict 42 """ 43 44 def reader(): 45 UNK_ID = word_dict["<UNK>"] 46 word_col = 1 47 48 for file_name in os.listdir(data_dir): 49 with open(os.path.join(data_dir, file_name), "r") as f: 50 for line in f: 51 line_split = line.strip().split("\t") 52 if len(line_split) < word_col: continue 53 word_ids = [ 54 word_dict.get(w, UNK_ID) 55 for w in line_split[word_col].split() 56 ] 57 yield word_ids, line_split[word_col] 58 59 return reader
总结
这篇文章主要讲了在paddlepaddle里如何加载自己的数据集,转换成相应的格式,并划分train和test。我们在使用一个框架的时候通常会先去跑几个简单的demo,但是如果不用常见的demo的数据,自己做一个实际的项目,完整的跑通一个模型,这才代表我们掌握了这个框架的基本应用知识。跑一个模型第一步就是数据预处理,在paddlepaddle里,提供的方式非常简单,但是有很多优点:
shuffle数据非常方便
可以将数据组合成batch训练
可以利用reader decorator来组合多个reader,提高组合特征运行模型的效率
可以多线程读取数据
而我之前使用过mxnet来训练车牌识别的模型,50w的图片数据想要一次训练是非常慢的,这样的话就有两个解决方法:一是批量训练,这一点大多数的框架都会有, 二是转换成mxnet特有的rec格式,提高读取效率,可以通过im2rec.py将图片转换,比较麻烦,如果是tesnorflow,也有相对应的特定格式tfrecord,这几种方式各有优劣,从易用性上,paddlepaddle是比较简单的。
这篇文章没有与上篇衔接起来,因为看到有好几封邮件都有问怎么自己加载数据训练,所以就决定插入一节先把这个写了。下篇文章我们接着讲CNN的进阶知识。下周见^_^!
参考文章:
1.官网说明:http://doc.paddlepaddle.org/develop/doc_cn/getstarted/concepts/use_concepts_cn.html
相关文章推荐
- 【深度学习系列】PaddlePaddle垃圾邮件处理实战(二)
- 【深度学习系列】PaddlePaddle垃圾邮件处理实战(一)
- 4 关于数据仓库维度数据处理的方法探究系列——缓慢变化维处理——覆盖方式
- 【Web API系列教程】3.4 — 实战:处理数据(处理实体关系)
- curl数据采集系列之正则处理函数get_matches
- 【云星数据---Apache Flink实战系列(精品版)】:Apache Flink实战基础002--flink特性:流处理特性介绍
- 串口通讯系列五之串口数据接收处理
- 【Web API系列教程】3.3 — 实战:处理数据(建立数据库)
- Scrapy爬虫系列笔记之六:使用item以及对得到的数据进行存储以及处理_by_书訢
- 云星数据---Apache Flink实战系列(精品版)】:Flink流处理API详解与编程实战004-Flink基于流的window操作002
- 云星数据---Apache Flink实战系列(精品版)】:Flink流处理API详解与编程实战011-DataStream与MySql自定义sink和source(Java版)003
- 大数据处理系列之(二)系统过载保护
- 5 关于数据仓库维度数据处理的方法探究系列——缓慢变化维处理——全历史记录
- Razor Web Page学习系列- 用Json处理json数据
- 快速入门系列--WCF--04元数据和异常处理
- Scott的ASP.net MVC框架系列文章之四: 处理表单数据
- 云星数据---Apache Flink实战系列(精品版)】:Flink流处理API详解与编程实战012-Flink在流处理中常见的sink和source001
- WCF+Ef实战系列二:EF实体的构建及数据业务层的处理
- 5 关于数据仓库维度数据处理的方法探究系列——缓慢变化维处理——全历史记录
- AR Drone系列之:使用ROS catkin创建package并使用cv_bridge实现对ar drone摄像头数据的处理