博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
稀疏向量计算优化小结
阅读量:2054 次
发布时间:2019-04-28

本文共 4400 字,大约阅读时间需要 14 分钟。

项目github地址:

欢迎大家star,留言,一起学习进步

在各种算法中,向量计算是最常用的一种操作之一。传统的向量计算,学过中学数学的同学也能明白怎么做。但在现在的大数据环境下,数据一般都会比较稀疏,因此稀疏向量的计算,跟普通向量计算,还是存在一些不同。

首先,我们定义两个向量:

A = [ x 1 , x 2 , ⋯   , x n ] A=[x_1, x_2,\cdots,x_n] A=[x1,x2,,xn]
B = [ y 1 , y 2 , ⋯   , y n ] B=[y_1, y_2,\cdots,y_n] B=[y1,y2,,yn]
定义A、B的点积为 A ∗ B A*B AB,要求 A ∗ B = ? A*B=? AB=?

最简单粗暴的方式

最直接的方式,当然就是按中学时候就学过的方法:

A ∗ B = x 1 ∗ y 1 + x 2 ∗ y 2 + ⋯ + x n ∗ y n A*B=x_1*y1 + x_2*y_2 + \cdots + x_n*y_n AB=x1y1+x2y2++xnyn
先不考虑乘法与加法的区别,也不考虑计算精度问题。如果按上述方式进行计算,总共进行了n次乘法,n-1次加法,总复杂度为2n-1。矩阵乘法的基本计算单元是向量之间的乘法,复杂度为 n 3 n^3 n3
在现在的大数据环境之下,n可能会很大,比如在计算广告,或者文本分类中,上百万维都是很正常的。而且这种向量都有一个特点,那就是很稀疏。如果没有很稀疏这个特点,那后面自然就无从谈起了。。。

第一种思路

对于稀疏向量,自然而然的可以想到按一下方式进行存储:

A : { < x 1 : l o c a t i o n 1 > , < x 2 : l o c a t i o n 2 > , ⋯   , < x i , l o c a t i o n i > } A:\{<x1:location1>,<x2:location2>,\cdots,<x_i,locationi>\} A:{
<
x1:location1>,<x2:location2>,,<xi,locationi>}
B : { < y 1 : l o c a t i o n 1 > , < y 2 : l o c a t i o n 2 > , ⋯   , < y j , l o c a t i o n j > } B:\{<y1:location1>,<y2:location2>,\cdots,<y_j,locationj>\} B:{
<
y1:location1>,<y2:location2>,,<yj,locationj>}
因为是稀疏向量,所以 i ≪ n , j ≪ n i \ll n,j \ll n in,jn

具体在计算A*B的时候,可以在向量A中循环,然后在向量B中进行二分查找。例如,在向量A中取出第一个非零元素,假设为 < x 1 , l o c a t i o n 1 > <x1,location1> <x1,location1>,在B中对location1进行二分。如果找到,计算乘积,如果找不到,自然为0.

那我们来估算一下算法的复杂度。在B中二分的复杂度为 l o g j logj logj,A的长度为 i i i,则这部分的总复杂度为 i l o g j ilogj ilogj,加法的最大情况为 m i n ( i , j ) − 1 min(i,j)-1 min(i,j)1,总的复杂度为 i l o g j + m i n ( i , j ) − 1 ilogj+min(i,j)-1 ilogj+min(i,j)1

继续优化

当然,如果我们知道 i i i , j j j 的大小,可以在小的向量上循环,在大的向量上二分,这样复杂度可以降低为 m i n ( i , j ) l o g ( m a x ( i , j ) ) + m i n ( i , j ) − 1 min(i,j)log(max(i,j))+min(i,j)-1 min(i,j)log(max(ij))+min(i,j)1

如果咱们不用二分查找,而是使用hash,则二分查找部分可以变为hash。假设hash的复杂度为1,那么总的复杂度为 2 m i n ( i , j ) 2min(i,j) 2min(i,j)。当然,我们忽略了创建hash的复杂度,以及hash碰撞的复杂度。
这样,总的复杂度就由最初的 2 n − 1 2n-1 2n1降到了 2 m i n ( i , j ) 2min(i,j) 2min(i,j)

并行

如果n特别特别大,比如凤巢系统动不动就是号称上亿维度。这样i,j也不会特别小。如果是两个矩阵相乘,咱们前面提到的,复杂度为 n 3 n^3 n3,这样就必须上并行计算了。搞数据的同学,对并行肯定不陌生,这里不再细述了。

代码验证

以上都是理论分析,为了验证实际中的运行效果,特意编写了一部分测试代码。测试代码如下

#!/usr/bin/env python#coding:utf-8'''Created on 2016年4月22日@author: lei.wang'''import time#二分查找def bin_search(num,list):    low = 0    high = len(list) - 1    while(low <= high):        middle = (low + high) / 2        if list[middle] > num:            high = middle - 1        elif list[middle] < num:            low = middle + 1        else:            return middle    return -1def t1():    all = 1000000    sparse_rate = 1000    vec_a = [0 for i in range(all)]    vec_b = [0 for i in range(all)]        list_none_zero = [sparse_rate*i for i in range(all / sparse_rate)]    for i in list_none_zero:        vec_a[i] = vec_b[i] = 1        sum = 0        #a,b分别不为0的位置    location_a = [i for i in range(0,all,sparse_rate)]    location_b = [i for i in range(0,all,sparse_rate)]        start = time.clock()    for i in location_a:        location = bin_search(i, location_b) #对应a不为0的位置,在b不为0的位置数组中查找是否存在        if location != -1:            sum += vec_a[i] * vec_b[location_b[location]] #如果存在,将结果相加    end = time.clock()            print "cost time is:",(end-start)    print "sum is:",sumdef t2():    all = 1000000    sparse_rate = 1000    vec_a = [0 for i in range(all)]    vec_b = [0 for i in range(all)]        list_of_none_zero = [sparse_rate*i for i in range(all / sparse_rate)]    for i in list_of_none_zero:        vec_a[i] = vec_b[i] = 1        sum = 0        start = time.clock()    for i in range(all):        sum += vec_a[i] * vec_b[i]    end = time.clock()        print "cost time is:",(end-start)    print "sum is:",sum           if __name__ == '__main__':    t1()    print    print    t2()

bin_search是自己实现的二分查找,t1方法是用上面说到的二分查找的方式,t2方法就是最简单的直接遍历相乘的方式。

在mac上运行以上代码,结果如下:

cost time is: 0.002319sum is: 1000cost time is: 0.123861sum is: 1000

可以看出,遍历的方式是二分查找的方式的54倍!按上述咱们的分析方式,遍历的方式应该是 2 ∗ 1 0 6 2*10^6 2106 的复杂度,二分查找的方式应该是 1 0 3 ∗ l o g 1000 10^3 * log1000 103log1000,即 1 0 4 10^4 104 左右的复杂度。二分查找的方式比遍历的方式应该要快100倍左右。根据咱们实验的结果来看,数量级上来说基本是差不多的。如果采取一些优化方式,比如用python自带的binset模块,应该会有更快的速度。

如果改变上述代码中的稀疏度,即改变sparse_rate的数值,例如将sparse_rate由1000改为10000,运行的结果如下:

cost time is: 0.000227sum is: 100cost time is: 0.118492sum is: 100

如果将sparse_rate改为100,运行的结果为:

cost time is: 0.034885sum is: 10000cost time is: 0.124176sum is: 10000

很容易看出来,对于遍历的方式来说,不管稀疏度为多少,耗时都是基本不变的。但是对于我们采用二分查找的方式来说,稀疏度越高,节省的计算资源,就越可观。

转载地址:http://fublf.baihongyu.com/

你可能感兴趣的文章
算法工程师 面经2019年5月
查看>>
搜索架构师 一面面经2019年6月
查看>>
稻草人手记
查看>>
第一次kaggle比赛 回顾篇
查看>>
leetcode 50. Pow(x, n)
查看>>
leetcode 130. Surrounded Regions
查看>>
【托业】【全真题库】TEST2-语法题
查看>>
博客文格式优化
查看>>
【托业】【新托业全真模拟】疑难语法题知识点总结(01~05)
查看>>
【SQL】group by 和order by 的区别。
查看>>
【F12】谷歌浏览器--前台效果可以在不访问服务器的前提下直接改样式看效果是否是预期值。...
查看>>
【Python】详解Python多线程Selenium跨浏览器测试
查看>>
Jmeter之参数化
查看>>
Shell 和Python的区别。
查看>>
Python 列表(list)、字典(dict)、字符串(string)常用基本操作小结
查看>>
Loadrunner之https协议录制回放报错如何解决?(九)
查看>>
python中xrange和range的异同
查看>>
列表、元组、集合、字典
查看>>
【Python】easygui小甲鱼
查看>>
【Python】关于Python多线程的一篇文章转载
查看>>