EdmondFrank's 时光足迹

この先は暗い夜道だけかもしれない それでも信じて進むんだ。星がその道を少しでも照らしてくれるのを。
或许前路永夜,即便如此我也要前进,因为星光即使微弱也会我为照亮前途。
——《四月は君の嘘》

Python大规模数据的处理技巧

目前在数据分析和挖掘领域内,最为热门的莫过于Python和R了,不过这两门语言一直因为不好处理大规模的数据而被人们调侃, 同时,hadoop和spark也因此应运而生。然而,其实Python在大规模的数据处理上也并非像传言所说的那么慢。甚者,其中也蕴含了 挺多的技巧让我们能够利用Python对大规模的数据进行分析计算。

下面就Python操作大规模数据时可能会遇到的问题,给出一些个人的见解。

问题一:大数据量的csv读入到内存中

问题分析:

当一个csv文件的数据量十分大时,例如,一个电商站点的一个月的流水帐单或交易记录,其中可能有高达几千万条至上亿条 记录文本。这样的文件对于一般性能的计算机来说,若是全部数据一次读入内存的存储就非常吃力了,甚至有崩溃的可能。

应对思路:

这时一个比较明智的方法就是将这些数据全部读入数据库之中,或者是根据我们的实际数据使用情况将大文件拆分成小块,然后 再按块读入。

简易示例:

1
2
3
4
5
6
7
8
9
10
11
12
#使用pandas包的最简示例
 chunker = pd.read_csv(PATH_LOAD, chunksize = CHUNK_SIZE)

#按列按需读取
  columns = ("date_time",  "user_id")
  chunks_train = pd.read_csv(filename, usecols = columns, chunksize = 100000)

#分块分行读取
for rawPiece in chunker_rawData:
  current_chunk_size = len(rawPiece.index)   #rawPiece 是dataframe
  for i in range(current_chunk_size ):
    timeFlag = timeShape(rawPiece.ix[i])   #获取第i行的数据

问题二:如何高效读取csv文件成python内部的list结构

问题分析:

当仅仅需要对外部大规模的csv做一些的简单的求和,求平均值,等简单的统计描述性分析时,使用pandas包显然是不明智的,因为pd 的读取中包含了各种抽象的转换操作,一但数据规模较大,性能是十分低下的。

应对思路:

这时应该直接采用python原生的IO读写操作,节省那些多此一举的转换操作。

简易示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#使用pandas包的方法,转换操作多,效率低下。
    userList = []
    content = pd.read_csv(filename)
    for i in range(len(content)):
        line = content.ix[i]['id']
        userList.append(line)

#直接使用原生操作读取外部数据,效率较高,但数据操作不及pandas方便
    userList = []
    f = open(filename)
    content = f.readlines()
    for line in content:
        line = line.replace('\n', '').split(',')
        userList.append(line)

问题三:数据结构之间合并

问题分析:

当你要对一组数据特征进行建模时,就要用到数据结构的合并功能了。 然而,对于大规模数据来说,任何的操作和合并如何采用的方法不当,其要浪费的时间都是十分致命的。

应对思路:

纵向的合并使用list并不好,因为需要去拆解list的每一个行元素,并用extend去拓展每一行的纵向元素 最好使用dataframe中的concat函数:c = pd.concat([a, b], axis = 1),当axis=0时表示合并行(以行为轴)

简易示例:

1
2
3
inx1 = DataFrame(np.random.randn(nSample_neg), columns = ['randVal'])
inx2 = DataFrame(range(nSample_neg), columns = ['inxVal'])
inx = pd.concat([inx1, inx2], axis = 1)

基于Redis的Bloomfilter

背景

url去重一直是大型分布式爬虫的主题,在一般规模比较大的的情景,去重需要考虑到两个点:

  1. 去重的数据量
  2. 去重的速度

并且,在一般情况下为了尽量降低去重对爬虫效率的影响一般选择在内存中去重。

  • 小数据:直接使用语言的逻辑判断及数据结构去重,如python的set,ruby的Set
  • 持续化去重: redis的set
  • 中型数据量去重:加密算法压缩url及长字符串在混合使用其他方法去重
  • 大型数据去重:使用bloomfilter(布隆过滤器)桉内存位去重

前言及原理简介

传统弊端

按照以往惯例来说,我们一般判断一个元素是否在一个集合内的通常做法是:先将所有元素保存下来, 然后通过比较判断它是否在集合之中。但是,这样的常规判断方法有一个很大的弊端就是,随着集合内的 元素个数变大,我们需要的空间和时间都呈线性增长,检索速度也越来越慢。

bloomfilter

而Bloom filter 采用的是哈希函数的方法,将一个元素表示为一个点并将他映射到一个长度为m的 阵列上。在检索时如果在阵列上发现对应的映射点为1时,那么这个元素在集合内,反之则不在集合内。

优点:

Bloom filter 优点在于它的插入和查询时间都是常数,另外它查询的元素不保存元素本身,具有良好的 安全性。

缺点:

最明显的一点是,当插入元素越多,查询元素被错判成“在集合内”的概率就越大。针对这个问题常用的 解决方法有:使用k个哈希函数来对应映射k个点,如果所对应的所有点都是1的话。那么元素在集合内, 如果任何一个有0的话,则元素不在集合内。另外,Bloom filter也不能删除一个元素,因为多个元素的哈希 的结果可能在bloom filter的阵列中都是占用同一个位。如果删除了一个比特位,可能会影响多个元素的检测。

简单图示

实现我们设我们的哈希函数有两个。

开始时集合内没有元素:

img

当来了一个元素a时,进行哈希计算再判断,当计算出对应的比特位上为0时,即a不在集合内,添加a的哈希值进去。

img

之后的元素,要判断是不是在集合内,也是同 a 一样的方法,只有对元素哈希后对应位置上都是 1 才认为这个元素在集合内

img

随着元素的插入,Bloom filter 中修改的值变多,出现误判的几率也随之变大,当新来一个元素时, 满足其在集合内的条件,即所有对应位都是1,这样就可能有两种情况, 一是这个元素就在集合内,没有发生误判;还有一种情况就是发生误判,出现了哈希碰撞,这个元素本不在集合内。

img

算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# encoding=utf-8

import redis
from hashlib import md5

class SimpleHash(object):
    def __init__(self, cap, seed):
        self.cap = cap
        self.seed = seed

    def hash(self, value):
        ret = 0
        for i in range(len(value)):
            ret += self.seed * ret + ord(value[i])
        return (self.cap - 1) & ret


class BloomFilter(object):
    def __init__(self, host='localhost', port=6379, db=0, blockNum=1, key='bloomfilter'):
        """
        :param host: the host of Redis
        :param port: the port of Redis
        :param db: witch db in Redis
        :param blockNum: one blockNum for about 90,000,000; if you have more strings for filtering, increase it.
        :param key: the key's name in Redis
        """
        self.server = redis.Redis(host=host, port=port, db=db)
        self.bit_size = 1 << 31  # Redis的String类型最大容量为512M,现使用256M
        self.seeds = [5, 7, 11, 13, 31, 37, 61]
        self.key = key
        self.blockNum = blockNum
        self.hashfunc = []
        for seed in self.seeds:
            self.hashfunc.append(SimpleHash(self.bit_size, seed))

    def isContains(self, str_input):
        if not str_input:
            return False
        m5 = md5()
        m5.update(str_input)
        str_input = m5.hexdigest()
        ret = True
        name = self.key + str(int(str_input[0:2], 16) % self.blockNum)
        for f in self.hashfunc:
            loc = f.hash(str_input)
            ret = ret & self.server.getbit(name, loc)
        return ret

    def insert(self, str_input):
        m5 = md5()
        m5.update(str_input)
        str_input = m5.hexdigest()
        name = self.key + str(int(str_input[0:2], 16) % self.blockNum)
        for f in self.hashfunc:
            loc = f.hash(str_input)
            self.server.setbit(name, loc, 1)


if __name__ == '__main__':
""" 第一次运行时会显示 not exists!,之后再运行会显示 exists! """
    bf = BloomFilter()
    if bf.isContains('http://www.baidu.com'):   # 判断字符串是否存在
        print 'exists!'
    else:
        print 'not exists!'
        bf.insert('http://www.baidu.com')

基本抽样和蓄水池抽样

前言

在阅读机器学习以及神经网络的相关资料中,我们总会时不时看见统计采样的身影。 看似简单的统计采样,在各种学习算法中发挥的强大的作用,一份好的样本,不但可以大幅度降低算法的计算量, 还能较为准确的代表整个样本空间,对算法的效率优化和整体的模型设计都有着非常大的意义。

基本采样方法的简介

单纯随机抽样(simple random sampling)

主要思想 :首先将整体编号,然后采用随机数的方法进行不放回性地抽取,并将所取得的数据组成新的样本。

  • 优点 :操作简单
  • 缺点 :数据量大时难以编号

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import random

def loaddata(filename):
    dataMat = []
    with open(filename) as fp:
        for line in fp.readlines():
            dataMat.append(line.strip().split('\t'))
    return dataMat

def simple_sampling(dataMat, num):
    try:
        samples = random.sample(dataMat, num)
        return samples
    except:
        print('sample larger than population')

系统抽样(systematic sampling)

主要思想 :先将总体分成n个部分,然后依次用相等间隔,从每一个部分中抽取出一个数据项组成观察样本。

  • 优点 :易于理解,样本涵盖范围广,有利于避免边缘化。
  • 缺点 :容易受总体的增减趋势影响。

代码实现

1
2
3
4
5
6
7
8
9
10
11
def loaddata(filename):
    dataMat = []
    with open(filename) as fp:
        for line in fp.readlines():
            dataMat.append(line.strip().split('\t'))
    return dataMat

def systematic_sampling(dataMat, num):
    k = int(len(dataMat)/num)
    samples = [random.sample(dataMat[i*k:(i+1)*k], 1) for i in range(num)]
    return samples

扩展

蓄水池抽样算法

蓄水池抽样是个很有趣的问题,这个问题的来源是关于等概率抽样的一种思考, 问题是,如何能在不知道总体对象数量(或者数量巨大)的情况下抽取k个对象, 使得每个对象被抽取到的概率相同。

原理 :考虑最终一定要抽取到k个对象,所以先任意抽出k个, 然后对剩下的对象分别以某种概率概率,使得最终每个对象被抽到的概率相同。

算法步骤

  • 输入:长度为N的数组L(N未知或者很大);输出:被等可能抽出的长度为k的数组l
  • 对输入L取前k个数组成的数组作为蓄水池
  • 对于L的第i(i=k+1,k+2,…,N)个数,任取r为0~i-1之间的整数,若r>k-1,则不进行替换,若r<=k-1,则用第i个数去替换蓄水池中第r个数
  • 遍历一遍L,取到的l中的每个元素都是以概率k/n等可能取到的

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import numpy as np
import matplotlib.pyplot as plt
def pool(L,k):
    arr = L[:k]
    for i,e in enumerate(L[k:]):
        r = np.random.randint(0,k+i+1)
        if r<=k-1:
            arr[r] = e
    return arr
def count(q,n):
    L = array("d")
    l1 = array("d")
    l2 = array("d")
    for e in q:
        L.append(e)
    for e in range(n):
        l1.append(L.count(e))
    for e in l1:
        l2.append(e/sum(l1))
    return l1,l2
if __name__ == '__main__':
    a = np.array([[i]*10000000 for i in range(3)])#生成等量的0,1,2
    a.shape = 1,-1
    L = a[0]
    k = 300000#设置要抽取的样本的数量,一般远小于总体数量
    p = pool(L, k)
    l1 = ['value=%d'% x for x in range(3)]
    plt.pie(count(p,3)[0],labels=l1,labeldistance=0.1,autopct='%1.2f%%')
    plt.title("Reservoir sampling")
    plt.show()

PCA(主成分分析)python实现

机器学习算法 PCA(主成分分析)python实现

背景

PCA(Principal Component Analysis),PCA的主要作用是降低数据集的维度,然后挑选出主要的特征。 PCA的主要思想是移动坐标轴,找到方差最大的方向上的特征值。

PCA算法是如何实现的?

简单来说,就是将数据从原始的空间中转换到新的特征空间中, 例如原始的空间是三维的(x,y,z),x、y、z分别是原始空间的三个基, 我们可以通过某种方法,用新的坐标系(a,b,c)来表示原始的数据, 那么a、b、c就是新的基,它们组成新的特征空间。在新的特征空间中,可能所有的数据在c上的投影都接近于0, 即可以忽略,那么我们就可以直接用(a,b)来表示数据,这样数据就从三维的(x,y,z)降到了二维的(a,b)。

基本步骤

  • 计算数据集的协方差矩阵
  • 计算协方差矩阵的特征值和特征向量
  • 保留最重要的n个特征

参考链接: http://deeplearning.stanford.edu/wiki/index.php/%E4%B8%BB%E6%88%90%E5%88%86%E5%88%86%E6%9E%90

什么是协方差矩阵?

其定义是:变量向量减去均值向量,然后乘以变量向量减去均值向量的转置再求均值。 例如x是变量,μ是均值,协方差矩阵等于E[(x-μ)(x-μ)t], 物理意义是这样的,例如x=(x1,x2,…,xi) 那么协方差矩阵的第m行n列的数为xm与xn的协方差,若m=n,则是xn的方差。 如果x的元素之间是独立的,那么协方差矩阵只有对角线是有值, 因为x独立的话对于m≠n的情况xm与xn的协方差为0。另外协方差矩阵是对称的。

算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
def pca(dataMat, topNfeat=9999999):
    meanVals = np.mean(dataMat, axis=0)
    #print(meanVals)
    meanRemoved = dataMat - meanVals #remove mean
    #print(meanRemoved)
    covMat = np.cov(meanRemoved, rowvar=0)
    eigvals,eigvects = np.linalg.eig(np.mat(covMat)) #计算协方差矩阵的特征值和特征向量
    eig_valind = np.argsort(eigvals)                    #sort, sort goes smallest to largest
    eig_valind = eig_valind[:-(topNfeat+1):-1]  #cut off unwanted dimensions
    red_eigvects = eigvects[:,eig_valind]          #reorganize eig vects largest to smallest
    low_datamat = meanRemoved * red_eigvects#transform data into new dimensions
    reconmat = (low_datamat * red_eigvects.T) + meanVals
    return low_datamat, reconmat

测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
学习时间  分数
9         39
15        56
25        93
14        61
10        50
18        75
0         32
16        85
5         42
19        70
16        66
20        80

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import numpy as np
import matplotlib.pyplot as plt
def loadDataSet(fileName, delim='\t'):
    fr = open(fileName)
    stringArr = [line.strip().split(delim) for line in fr.readlines()]
    datArr = [list(map(float,line)) for line in stringArr]
    #print(mat(datArr))
    fr.close()
    return np.mat(datArr)

def pca(dataMat, topNfeat=9999999):
    meanVals = np.mean(dataMat, axis=0)
    #print(meanVals)
    meanRemoved = dataMat - meanVals #remove mean
    #print(meanRemoved)
    covMat = np.cov(meanRemoved, rowvar=0)
    eigvals,eigvects = np.linalg.eig(np.mat(covMat)) #计算协方差矩阵的特征值和特征向量
    eig_valind = np.argsort(eigvals)                    #sort, sort goes smallest to largest
    eig_valind = eig_valind[:-(topNfeat+1):-1]  #cut off unwanted dimensions
    red_eigvects = eigvects[:,eig_valind]          #reorganize eig vects largest to smallest
    low_datamat = meanRemoved * red_eigvects#transform data into new dimensions
    reconmat = (low_datamat * red_eigvects.T) + meanVals
    return low_datamat, reconmat

def plotBestFit(dataSet1,dataSet2):
    dataArr1 = np.array(dataSet1)
    dataArr2 = np.array(dataSet2)
    n = np.shape(dataArr1)[0]
    n1=shape(dataArr2)[0]
    xcord1 = []; ycord1 = []
    xcord2 = []; ycord2 = []
    xcord3=[];ycord3=[]
    j=0
    for i in range(n):
        xcord1.append(dataArr1[i,0]); ycord1.append(dataArr1[i,1])
        xcord2.append(dataArr2[i,0]); ycord2.append(dataArr2[i,1])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
    ax.scatter(xcord2, ycord2, s=30, c='green')

    plt.xlabel('X1'); plt.ylabel('X2');
    plt.show()

if __name__=='__main__':
    mata=loadDataSet('score')
    a,b= pca(mata, 4)
    plotBestFit(a,b)