Finger's Blog


  • 首页

  • 归档

  • 标签

  • 分类

  • 关于

  • 公益404

  • 搜索

用Numpy求矩阵的逆

发表于 2018-06-24 | 分类于 Numpy | | 阅读次数:
| 字数统计:

定义

一个n阶方阵A称为可逆的,或非奇异的,如果存在一个n阶方阵B,使得

AB = BA = E

并称B是A的一个逆矩阵。A的逆矩阵记作A^-1。

百度逆矩阵

特性

若|A|≠0,则矩阵A可逆,且

其中,A*为矩阵A的伴随矩阵。

求解

假定矩阵A为:

根据公式:

因A可逆,则有A的伴随矩阵特性 A* = A^T

根据三阶行列式特性(对角线法)|A| = (1 x 1) - (-1 x 1)

代入公式:

用Numpy求矩阵的逆

numpy.linalg中的inv()函数就是用来求矩阵的逆。

1
2
3
4
5
6
7
8
>>> A = np.mat("1 -1; 1 1")
>>> A
matrix([[ 1, -1],
[ 1, 1]])
>>> inverse = np.linalg.inv(A)
>>> inverse
matrix([[ 0.5, 0.5],
[-0.5, 0.5]])

感觉是不是很简单。

用Numpy解线性方程组

发表于 2018-06-24 | 分类于 Numpy | | 阅读次数:
| 字数统计:

矩阵可以通过线性方式把一个向量变换成另一个向量,因此从数值计算的角度看,
这种操作对应于一个线性方程组。Numpy.linalg中的 solve() 子例程可以求解类似 Ax=b 这种形式的线性方程组,其中A是一个矩阵,b是一维或者二维数组,二x是未知量。下面介绍如何使用 dot() 函数来计算两个浮点型数组的点积(dot product)。

这里举例说明解线性方程组的过程,具体步骤如下所示:

1、创建矩阵A和数组b

1
2
3
4
5
6
7
8
9
>>> import numpy as np
>>> A = np.mat("1 -2 1; 0 2 -8; -4 5 9") # mat函数可以将目标数据的类型转换为矩阵(matrix)
>>> A
matrix([[ 1, -2, 1],
[ 0, 2, -8],
[-4, 5, 9]])
>>> b = np.array([0, 8, -9])
>>> b
array([ 0, 8, -9])

2、调用solve()函数

1
2
3
>>> x = np.linalg.solve(A, b)
>>> x
array([29., 16., 3.])

如上述所示,得出该线性方程组的解为:[29., 16., 3.]

3、利用dot()函数进行验算

1
2
>>> np.dot(A, x)
matrix([[ 0., 8., -9.]])

深度学习矩阵运算的概念和代码实现(Numpy)

发表于 2018-06-10 | 分类于 Python | | 阅读次数:
| 字数统计:

本文从向量的概念与运算扩展到矩阵运算的概念与代码实现,对机器学习或者是深度学习的入门者提供最基础,也是最实用的教程指导,为以后的机器学习模型开发打下基础。

在我们学习机器学习时,常常遇到需要使用矩阵提高计算效率的时候。如在使用批量梯度下降迭代求最优解时,正规方程会采用更简洁的矩阵形式提供权重的解析解法。而如果不了解矩阵的运算法则及意义,甚至我们都很难去理解一些如矩阵因子分解法和反向传播算法之类的基本概念。同时由于特征和权重都以向量储存,那如果我们不了解矩阵运算,代码实现将变得十分艰难。

1、什么是线性代数?

在深度学习中,线性代数是一个强大的数学工具箱,它提供同时计算多维数组的方法。线性代数不仅会提供如同向量和矩阵那样的结构来储存这些数字,还会提供矩阵的加,减,乘,除和其他运算规则。

2、线性代数为什么如此实用?

线性代数将复杂问题转变为简单,直观和高效的计算问题。下面的例子可以表明实现同样的功能,线性代数的代码表达是如何的简洁与美观。

1
2
3
4
5
6
7
8
9
# Multiply two arrays
>>> x = [1, 2, 3]
>>> y = [2, 3, 4]
>>> product = []
>>> for i in range(len(x)):
... product.append(x[i] * y[i])
...
>>> product
[2, 6, 12]
1
2
3
4
5
6
# Linear algebra version
>>> import numpy as np
>>> x = np.array([1,2,3])
>>> y = np.array([2,3,4])
>>> x * y
array([ 2, 6, 12])

3、线性代数怎样应用到深度学习?

神经网络将权重储存在矩阵当中。而线性代数特别是在GPU上,可以对矩阵进行简单迅捷的计算处理。实际上,GPU的设计就是源于向量和矩阵计算处理的基本概念。像素块阵列构成,视频游戏使用巨量,连续展开的矩阵生成引人注目的游戏体验是一样的.GPU会并行地操作整个矩阵里元素,而不是一个接一个地处理。

4、向量

向量由数字或其它项组成的一维阵列。在几何学中,向量储存了空间中一个点潜在的改变方向。向量[3,-2]也就代表着原点向(3,-2)这一点运动的趋向。若向量所具有的维度超过一维,那么就称之为矩阵。

4.1、向量的符号表示

有很多符号方式都能表示向量,下面是在本篇文章中你可能会遇到的:

4.2、几何学中的向量

向量一般表征着一个点的运动,一个向量同时储存其潜在变化的方向和大小。如下图所示,在平面空间中画出了向量[-2,5],因为向量只储存了方向和大小,那么平移并不会改变向量的值,所以所有平移的向量(方向和大小不变)都是相等的。

4.3、标量运算

标量运算即为向量和数字间的运算,向量与数的运算就是向量内每一个元素与这一个数进行相应的运算如下图的一个标量运算:

4.4、向量间运算

在向量间的运算中,对应位置的值可以组合而产生一个新向量。第一个向量的第我个值只与第二个向量的第我个值相匹配。这也就意味着向量之间的维度必须相等才能进行运算。下图表明向量之间的加减法是对应元素之间的加减,代码表明了向量之间的加减和除法。

1
2
3
4
5
6
7
8
>>> x = np.array([1,2,3])
>>> y = np.array([2,3,4])
>>> x + y
array([3, 5, 7])
>>> x - y
array([-1, -1, -1])
>>> x / y
array([0.5 , 0.66666667, 0.75 ])

在Numpy中,如果向量是一维的,那么他就能看作是一个标量,与其他多维向量的运算就相当于一个数。

4.5、向量乘法

向量的乘法有两种类型:一种是点积,另一种是哈达玛积(Hadamard)。

4.5.1、点积

两个向量的点积结果是一个标量。向量和矩阵(矩阵乘法)的点积在深度学习中是最重要的运算之一。

4.5.2、哈达玛积(Hadamard)

哈达玛积是元素之间的乘积,并得出一个向量。从下图可以看出来阿达玛积就将是向量对应元素相乘。

1
2
3
4
>>> x = np.array([1,2,3])
>>> y = np.array([4,5,6])
>>> x * y
array([ 4, 10, 18])

4.6、向量场

向量场展示了如果我们运用一个向量函数(如向量加法或乘法等),其中任意点(X,Y)会有什么样的运动倾向。在空间中给定一点,向量场就是我们使用的向量运算在该点的方向和大小。

该向量场很有意思,因为根据不同的出发点,其都有不同的方向。出现这种情况是因为在该向量场中,向量背后储存的项不是一个5或2那样的实数,它是2倍或X对于图表中的每一个点,我们将坐标轴变换为2x或x ^ 2,然后将起始点画一个箭头到新的坐标点,这样就制成了上图机器学习算法(如梯度下降算法)的可视化十分重要。

5、矩阵

矩阵就是一个由数字或其它项组成的表格,只不过是该表格会有特定的加法,减法和乘法规则。

5.1、矩阵的阶

我们描述矩阵的维度由阶来表达:即行数×列数(如3×2)阶矩阵。

1
2
3
4
5
a = np.array([
[1,2,3],
[4,5,6]
])
a.shape == (2,3)
1
2
3
4
b = np.array([
[1,2,3]
])
b.shape == (1,3)

5.2、矩阵的标量运算

矩阵的标量运算和向量的标量运算是一样的。可以简单地将标量和矩阵中的每一个元素做运算处理(如加,减,乘,除等)。

1
2
3
4
5
>>> a = np.array([[2, 3], [2, 3], [2, 3]])
>>> a + 1
array([[3, 4],
[3, 4],
[3, 4]])

5.3、矩阵间的运算

为了能进行加减运算,两个矩阵的阶必须相等。然后我们可以对两个矩阵相应的元素进行运算处理。如下图就是两阶方阵的加法。

1
2
3
4
5
6
7
8
a = np.array([
[1,2],
[3,4]
])
b = np.array([
[1,2],
[3,4]
])
1
2
3
a + b
[[2, 4],
[6, 8]]
1
2
3
a — b
[[0, 0],
[0, 0]]

在Numpy中,矩阵之间运算所需要的阶相等可以通过一个称之为广播的机制变得不那么严格。如果两个矩阵相应的阶(行数×列数)满足下面两个要求,那么它们就是可以进行运算的:

  • 两个矩阵的阶相等
  • 矩阵的阶有一个维度是1
1
2
3
4
5
6
7
8
9
10
11
a = np.array([
[1],
[2]
])
b = np.array([
[3,4],
[5,6]
])
c = np.array([
[1,2]
])
1
2
3
4
5
6
# Same no. of rows
# Different no. of columns
# but a has one column so this works
a * b
[[ 3, 4],
[10, 12]]
1
2
3
4
5
6
# Same no. of columns
# Different no. of rows
# but c has one row so this works
b * c
[[ 3, 8],
[5, 12]]
1
2
3
4
5
6
7
# Different no. of columns
# Different no. of rows
# but both a and c meet the
# size 1 requirement rule
a + c
[[2, 3],
[3, 4]]

而在高维(三维或四维等)矩阵的情况下,矩阵间运算更有意思,不过在深度学习里并不常见。

5.4、矩阵Hadamard乘积

Hadamard乘积同样是矩阵间的运算,即两个矩阵间相同位置的元素相互乘积。

1
2
3
4
5
6
a = np.array(
[[2,3],
[2,3]])
b = np.array(
[[3,4],
[5,6]])
1
2
3
4
# Uses python's multiply operator
a * b
[[ 6, 12],
[10, 18]]

在Numpy中,矩阵和向量的Hadamard乘要只需要两个矩阵满足广播机制的要求就行。

5.5、矩阵的转置

把矩阵A的行和列互相交换所产生的矩阵称为A的转置矩阵,这一过程称为矩阵的转置。

神经网络在处理不同大小的权重或输入矩阵时,经常出现矩阵的阶不符合矩阵乘法的要求。矩阵的转置通过将矩阵旋转一下以满足矩阵乘法所需要的维度要求。下面,我们可以通过两步完成矩阵的转置。

  • 旋转矩阵90度
  • 将每一行的元素都反向写一遍

以下我们将矩阵M转置为矩阵T

1
2
3
a = np.array([
[1, 2],
[3, 4]])
1
2
3
a.T
[[1, 3],
[2, 4]]

矩阵的转置满足以下运算规律:

5.6、矩阵乘法

矩阵乘法是由一组乘法法则组成,他们共同作用以乘得一个新矩阵。

5.6.1、规则

并不是所有矩阵都能进行矩阵乘法运算的,如果两个矩阵能相乘,那么它需要满足以下条件:

  • 第一个矩阵列的数量必须等于第二个矩阵行的数量
  • m × n 阶矩阵左乘n×k阶矩阵的结果是 m × k 阶矩阵。新得出来矩阵就等于第一矩阵的行数 × 第二矩阵的列数。
5.6.2、步骤

矩阵乘法的步骤和向量点积的过程是相似的,它们都是由对应位置的元素进行乘积并相加而得出。第一个矩阵每一行的维度和第二个矩阵每一列的维度相等,所以第一个矩阵第i行元素与第二个矩阵第j列对应元素的乘积和就等于新矩阵的第i行第j列的元素值。在下图中,A矩阵左乘B矩阵得到C矩阵A矩阵行向量与B矩阵列向量点积就等于C矩阵的元素,具体可以通过下图C矩阵内部元素的构成来了解。

A矩阵行向量与B矩阵列向量b1的点积,即下图所示:

下面是另一个矩阵的乘积:

矩阵乘法是不可交换的(即AB≠BA)。因为不可能预期在改变向量的部分后还能得到相同的结果,而且第一个矩阵的列数必须要和第二个矩阵的行数相同,也可以看出为什么矩阵相乘的顺序会影响其结果。虽然矩阵乘法是人为的规则,但它确实大大简化了计算的表达,可以将巨大的计算量很简洁地表达出来,这一点对机器学习算法的开发和使用有重要的作用。

最后你可以用以下案例检测一下是否你已经掌握了矩阵运算的基本原理:

下面矩阵乘法的阶是多少?

下面矩阵的乘法的阶是多少?

下面矩阵的乘法是多少?

1
2
[2*5 + 3*3, 2*4 + 3*5]
[1*5 + 4*3, 1*4 + 4*5]

下面矩阵的乘法是多少?

1
2
[3*1, 3*2, 3*3]
[5*1, 5*2, 5*3]

下面矩阵的乘法是多少?

1
[1*4 + 2*5 + 3*6]

5.6.3、使用Numpy进行矩阵乘法运算

在Numpy中,np.dot(a,b)函数可以进行向量和矩阵点积。并且该函数还有许多有意思的特征, 所以我建议你在使用该函数前先看看该函数的用法:查看文档

1
2
3
4
a = np.array([
[1, 2]
])
a.shape == (1,2)
1
2
3
4
5
b = np.array([
[3, 4],
[5, 6]
])
b.shape == (2,2)
1
2
3
4
# Multiply
mm = np.dot(a,b)
mm == [13, 16]
mm.shape == (1,2)

深度学习通常会有巨大的计算量。从最开始的特征输入,我们会使用一个个高维向量将特征输入到神经网络中,而每一层的权重作为列向量组成一个权重矩阵。每一层的正向传播都需要使用矩阵乘法进行计算,而反向传播更需要理解矩阵运算才能对其运行原理有一个较为深入的理解。本文是矩阵运算的基础性文章,其不仅对概念的理解很是重要,同时在新手开始学着搭建机器学习系统时更为有用,因为矩阵运算的代码在实际操作中是我们看懂一段代码或写出一段代码的基础。并且采用矩阵运算代码实现也远比采用循环语句或条件语句代码实现的算法要简洁易读得多。

原文链接:https://www.jiqizhixin.com/articles/2017-08-07-2

英文链接:https://towardsdatascience.com/linear-algebra-cheat-sheet-for-deep-learning-cd67aba4526c

用Python实现一个大数据搜索引擎

发表于 2018-06-09 | 分类于 SearchEngine | | 阅读次数:
| 字数统计:

本文转自:https://my.oschina.net/taogang/blog/1579204

搜索是大数据领域里常见的需求。Splunk和ELK分别是该领域在非开源和开源领域里的领导者。本文利用很少的Python代码实现了一个基本的数据搜索功能,试图让大家理解大数据搜索的基本原理。

1、布隆过滤器(Bloom Filter)

第一步我们先要实现一个布隆过滤器

布隆过滤器是大数据领域的一个常见算法,它的目的是过滤掉那些不是目标的元素。也就是说如果一个要搜索的词并不存在与我的数据中,那么它可以以很快的速度返回目标不存在。

让我们看看以下布隆过滤器的代码:

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
class Bloomfilter(object):
"""
A Bloom filter is a probabilistic data-structure that trades space for accuracy
when determining if a value is in a set. It can tell you if a value was possibly
added, or if it was definitely not added, but it can't tell you for certain that
it was added.
"""
def __init__(self, size):
"""Setup the BF with the appropriate size"""
self.values = [False] * size
self.size = size
def hash_value(self, value):
"""Hash the value provided and scale it to fit the BF size"""
return hash(value) % self.size
def add_value(self, value):
"""Add a value to the BF"""
h = self.hash_value(value)
self.values[h] = True
def might_contain(self, value):
"""Check if the value might be in the BF"""
h = self.hash_value(value)
return self.values[h]
def print_contents(self):
"""Dump the contents of the BF for debugging purposes"""
print(self.values)
  • 基本的数据结构是个数组(实际上是个位图,用1/0来记录数据是否存在),初始化是没有任何内容,所以全部置False。实际的使用当中,该数组的长度是非常大的,以保证效率。
  • 利用哈希算法来决定数据应该存在哪一位,也就是数组的索引
  • 当一个数据被加入到布隆过滤器的时候,计算它的哈希值然后把相应的位置为True
  • 当检查一个数据是否已经存在或者说被索引过的时候,只要检查对应的哈希值所在的位的True/Fasle

看到这里,大家应该可以看出,如果布隆过滤器返回False,那么数据一定是没有索引过的,然而如果返回True,那也不能说数据一定就已经被索引过。在搜索过程中使用布隆过滤器可以使得很多没有命中的搜索提前返回来提高效率。

我们看看这段 code是如何运行的:

1
2
3
4
5
6
7
8
9
10
bf = Bloomfilter(10)
bf.add_value('dog')
bf.add_value('fish')
bf.add_value('cat')
bf.print_contents()
bf.add_value('bird')
bf.print_contents()
# Note: contents are unchanged after adding bird - it collides
for term in ['dog', 'fish', 'cat', 'bird', 'duck', 'emu']:
print('{}: {} {}'.format(term, bf.hash_value(term), bf.might_contain(term)))
1
2
3
4
5
6
7
8
[False, False, False, False, True, True, False, False, False, True]
[False, False, False, False, True, True, False, False, False, True]
dog: 5 True
fish: 4 True
cat: 9 True
bird: 9 True
duck: 5 True
emu: 8 False
1.1、首先创建了一个容量为10的的布隆过滤器

1.2、然后分别加入 ‘dog’,‘fish’,‘cat’三个对象,这时的布隆过滤器的内容如下:

1.3、然后加入‘bird’对象,布隆过滤器的内容并没有改变,因为‘bird’和‘fish’恰好拥有相同的哈希。

1.4、最后我们检查一堆对象(’dog’, ‘fish’, ‘cat’, ‘bird’, ‘duck’, ‘emu’)是不是已经被索引了。结果发现‘duck’返回True,2而‘emu’返回False。因为‘duck’的哈希恰好和‘dog’是一样的。

2、分词

下面一步我们要实现分词。 分词的目的是要把我们的文本数据分割成可搜索的最小单元,也就是词。这里我们主要针对英语,因为中文的分词涉及到自然语言处理,比较复杂,而英文基本只要用标点符号就好了。

下面我们看看分词的代码:

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
def major_segments(s):
"""
Perform major segmenting on a string. Split the string by all of the major
breaks, and return the set of everything found. The breaks in this implementation
are single characters, but in Splunk proper they can be multiple characters.
A set is used because ordering doesn't matter, and duplicates are bad.
"""
major_breaks = ' '
last = -1
results = set()
# enumerate() will give us (0, s[0]), (1, s[1]), ...
for idx, ch in enumerate(s):
if ch in major_breaks:
segment = s[last+1:idx]
results.add(segment)
last = idx
# The last character may not be a break so always capture
# the last segment (which may end up being "", but yolo)
segment = s[last+1:]
results.add(segment)
return results
2.1、主要分割

主要分割使用空格来分词,实际的分词逻辑中,还会有其它的分隔符。例如Splunk的缺省分割符包括以下这些,用户也可以定义自己的分割符。

] < > ( ) { } | ! ; , ‘ “ * \n \r \s \t & ? + %21 %26 %2526 %3B %7C %20 %2B %3D – %2520 %5D %5B %3A %0A %2C %28 %29

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
def minor_segments(s):
"""
Perform minor segmenting on a string. This is like major
segmenting, except it also captures from the start of the
input to each break.
"""
minor_breaks = '_.'
last = -1
results = set()
for idx, ch in enumerate(s):
if ch in minor_breaks:
segment = s[last+1:idx]
results.add(segment)
segment = s[:idx]
results.add(segment)
last = idx
segment = s[last+1:]
results.add(segment)
results.add(s)
return results
2.2、次要分割

次要分割和主要分割的逻辑类似,只是还会把从开始部分到当前分割的结果加入。例如“1.2.3.4”的次要分割会有1,2,3,4,1.2,1.2.3

1
2
3
4
5
6
7
def segments(event):
"""Simple wrapper around major_segments / minor_segments"""
results = set()
for major in major_segments(event):
for minor in minor_segments(major):
results.add(minor)
return results

分词的逻辑就是对文本先进行主要分割,对每一个主要分割在进行次要分割。然后把所有分出来的词返回。

我们看看这段 code是如何运行的:

1
2
for term in segments('src_ip = 1.2.3.4'):
print(term)

结果:

1
2
3
4
5
6
7
8
9
10
11
src
1.2
1.2.3.4
src_ip
3
1
1.2.3
ip
2
=
4

3、搜索

好了,有个分词和布隆过滤器这两个利器的支撑后,我们就可以来实现搜索的功能了。

上代码:

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
class Splunk(object):
def __init__(self):
self.bf = Bloomfilter(64)
self.terms = {} # Dictionary of term to set of events
self.events = []
def add_event(self, event):
"""Adds an event to this object"""
# Generate a unique ID for the event, and save it
event_id = len(self.events)
self.events.append(event)
# Add each term to the bloomfilter, and track the event by each term
for term in segments(event):
self.bf.add_value(term)
if term not in self.terms:
self.terms[term] = set()
self.terms[term].add(event_id)
def search(self, term):
"""Search for a single term, and yield all the events that contain it"""
# In Splunk this runs in O(1), and is likely to be in filesystem cache (memory)
if not self.bf.might_contain(term):
return
# In Splunk this probably runs in O(log N) where N is the number of terms in the tsidx
if term not in self.terms:
return
for event_id in sorted(self.terms[term]):
yield self.events[event_id]
  • Splunk代表一个拥有搜索功能的索引集合
  • 每一个集合中包含一个布隆过滤器,一个倒排词表(字典),和一个存储所有事件的数组
  • 当一个事件被加入到索引的时候,会做以下的逻辑
    • 为每一个事件生成一个unqie id,这里就是序号
    • 对事件进行分词,把每一个词加入到倒排词表,也就是每一个词对应的事件的id的映射结构,注意,一个词可能对应多个事件,所以倒排表的的值是一个Set。倒排表是绝大部分搜索引擎的核心功能。
  • 当一个词被搜索的时候,会做以下的逻辑
    • 检查布隆过滤器,如果为假,直接返回
    • 检查词表,如果被搜索单词不在词表中,直接返回
    • 在倒排表中找到所有对应的事件id,然后返回事件的内容

我们运行下看看吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
s = Splunk()
s.add_event('src_ip = 1.2.3.4')
s.add_event('src_ip = 5.6.7.8')
s.add_event('dst_ip = 1.2.3.4')
for event in s.search('1.2.3.4'):
print(event)
print('-')
for event in s.search('src_ip'):
print(event)
print('-')
for event in s.search('ip'):
print(event)
1
2
3
4
5
6
7
8
9
src_ip = 1.2.3.4
dst_ip = 1.2.3.4
-
src_ip = 1.2.3.4
src_ip = 5.6.7.8
-
src_ip = 1.2.3.4
src_ip = 5.6.7.8
dst_ip = 1.2.3.4

是不是很赞!

4、更复杂的搜索

更进一步,在搜索过程中,我们想用And和Or来实现更复杂的搜索逻辑。

上代码:

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
class SplunkM(object):
def __init__(self):
self.bf = Bloomfilter(64)
self.terms = {} # Dictionary of term to set of events
self.events = []
def add_event(self, event):
"""Adds an event to this object"""
# Generate a unique ID for the event, and save it
event_id = len(self.events)
self.events.append(event)
# Add each term to the bloomfilter, and track the event by each term
for term in segments(event):
self.bf.add_value(term)
if term not in self.terms:
self.terms[term] = set()
self.terms[term].add(event_id)
def search_all(self, terms):
"""Search for an AND of all terms"""
# Start with the universe of all events...
results = set(range(len(self.events)))
for term in terms:
# If a term isn't present at all then we can stop looking
if not self.bf.might_contain(term):
return
if term not in self.terms:
return
# Drop events that don't match from our results
results = results.intersection(self.terms[term])
for event_id in sorted(results):
yield self.events[event_id]
def search_any(self, terms):
"""Search for an OR of all terms"""
results = set()
for term in terms:
# If a term isn't present, we skip it, but don't stop
if not self.bf.might_contain(term):
continue
if term not in self.terms:
continue
# Add these events to our results
results = results.union(self.terms[term])
for event_id in sorted(results):
yield self.events[event_id]

利用Python集合的intersection和union操作,可以很方便的支持And(求交集)和Or(求合集)的操作。

运行结果如下:

1
2
3
4
5
6
7
8
9
10
s = SplunkM()
s.add_event('src_ip = 1.2.3.4')
s.add_event('src_ip = 5.6.7.8')
s.add_event('dst_ip = 1.2.3.4')
for event in s.search_all(['src_ip', '5.6']):
print(event)
print('-')
for event in s.search_any(['src_ip', 'dst_ip']):
print(event)
1
2
3
4
5
src_ip = 5.6.7.8
-
src_ip = 1.2.3.4
src_ip = 5.6.7.8
dst_ip = 1.2.3.4

完整源码地址

5、总结

以上的代码只是为了说明大数据搜索的基本原理,包括布隆过滤器,分词和倒排表。如果大家真的想要利用这代码来实现真正的搜索功能,还差的太远。所有的内容来自于Splunk Conf2017。大家如果有兴趣可以去看网上的视频。

  • 文档
  • 视频

Python中的10个隐藏特性

发表于 2018-05-24 | 分类于 Python | | 阅读次数:
| 字数统计:

1、函数参数解包(unpack)

1
2
3
4
5
6
7
8
9
>>> def foo(x, y): print(x, y)
...
>>> alist = [1,2]
>>> adict = {'x': 1, 'y': 2}
>>> foo(*alist)
1 2
>>> foo(**adict)
1 2
>>>

2、python3中的元组unpack

1
2
3
4
5
6
7
8
>>> a, b, *rest = range(10)
>>> a
0
>>> b
1
>>> rest
[2, 3, 4, 5, 6, 7, 8, 9]
>>>

也可以取最后一个:

1
2
3
4
5
6
7
8
9
>>> first, second, *rest, last = range(10)
>>> first
0
>>> second
1
>>> last
9
>>> rest
[2, 3, 4, 5, 6, 7, 8]

3、链式比较操作符

1
2
3
4
5
>>> 1 < x < 2
False
>>> 4 > x >= 3
True
>>>

4、列表切片操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 按步长2取列表数据
>>> a = [1,2,3,4,5]
>>> a[::2]
[1, 3, 5]
# 用步长-1来反转列表
>>> a[::-1]
[5, 4, 3, 2, 1]
# 用切片来删除列表的某一段
>>> a = [1,2,3,4,5]
>>> a[1:3] = []
>>> a
[1, 4, 5]
# 也可以用 del a[1:3]
>>> a = [1,2,3,4,5]
>>> del a[1:3]
>>> a
[1, 4, 5]

5、嵌套的列表推导式

1
2
>>> [(i, j) for i in range(3) for j in range(i)]
[(1, 0), (2, 0), (2, 1)]

6、字典里的无限递归

1
2
3
4
5
>>> a, b = {}, {}
>>> a['b'] = b
>>> b['a'] = a
>>> a
{'b': {'a': {...}}}

当然列表也可以

1
2
3
4
5
>>> a, b = [], []
>>> a.append(b)
>>> b.append(a)
>>> a
[[[...]]]

7、下划线”_”

1
2
3
4
5
6
# _ 在Python解析器上返回上一次的值
>>> 1 + 1
2
>>> _
2

另外 Python中的”_”也经常用在未使用的变量命名

1
2
>>> pos = (1, 2)
>>> x, _ = pos

8、注意函数的默认参数

1
2
3
4
5
6
7
8
>>> def foo(x=[]):
... x.append(1)
... print x
...
>>> foo()
[1]
>>> foo()
[1, 1]

更安全的做法:

1
2
3
4
5
6
7
8
9
10
11
>>> def foo(x=None):
... if x is None:
... x = []
... x.append(1)
... print x
...
>>> foo()
[1]
>>> foo()
[1]
>>>

9、另一种字符串连接

1
2
3
>>> name = "Hello" "World"
>>> name
'HelloWorld'

连接多行:

1
2
3
4
>>> name = "Hello" \
... "World"
>>> name
'HelloWorld'

10、不能访问到的属性

1
2
3
4
5
6
7
8
9
>>> class Foo(object): pass
...
>>> obj = Foo()
>>> setattr(o, 'hello world', 1)
>>> o.hello world
File "<stdin>", line 1
o.hello world
^
SyntaxError: invalid syntax

不过,能用 setattr 设置属性,就可以用 getattr 取出

1
2
>>> getattr(o, 'hello world')
1

Python3.6中那些很酷的特性

发表于 2018-05-22 | 分类于 Python | | 阅读次数:
| 字数统计:

可读性更强的数字字面值

Python代码在可读性上做到了极致,被称为是可执行伪代码。然而,它还在不断地改进,比如这个可读性更好的数字字面值语法,就是方便程序员能以一种 “for humans ” 的方式阅读和理解数字。你现在可以给数字添加下划线,并按照你喜欢的方式对它们进行分组 。这对于二进制或十六进制数字或者是大数来说非常方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> six_figures = 100_000
>>> six_figures
100000
>>> a = 10_00_0
>>> a
10000
>>> error = 0xbad_c0ffee
>>> error
50159747054
>>> flags = 0b_0111_0101_0001_0101
>>> flags
29973

请记住,这种改变只是语法层面上的变化,是一种在源代码中以不同方式表示数字文字的方法而已。在虚拟机编译成字节码的时候不会有任何变化,你可以在 PEP515 中了解到关于它的更多信息。

新的字符串格式化方法

对字符串格式化操作有两种常用的方法,第一个是使用 “%” 操作符,第二个是使用 format 函数。

“%” 操作符

1
2
3
>>> s = "%s is %d" % ('two', 2)
>>> s
'two is 2'

format 函数

1
2
3
>>> s = "{fruit} is {color}".format(fruit='apple', color='red')
>>> s
'apple is red'

显然,format 函数要比 % 操作符的可读性要好,在Python 3.6 增加了第三种格式化字符串方法,称为 Formatted String Literals ,简称 f字符串。

1
2
3
>>> name = 'Bob'
>>> f'Hello, {name}!'
'Hello, Bob!'

你还可以在字符串内使用嵌入式的 Python 表达式,例如:

1
2
3
4
>>> a = 5
>>> b = 10
>>> f'Five plus ten is {a + b} and not {2 * (a + b)}.'
'Five plus ten is 15 and not 30.'

这个看起来很酷,其实这种操作在模版引擎中早就有这样的特性存在,只不过因为用的人多了,就引入到了语言标准中。

除了这些,还可以操作数字

1
2
3
4
5
6
7
8
9
10
11
12
13
# 精度
>>> PI = 3.141592653
>>> f"Pi is {PI:.2f}"
>>> 'Pi is 3.14'
>>> error = 50159747054
#以16进制格式化
>>> f'Programmer Error: {error:#x}'
'Programmer Error: 0xbadc0ffee'
#以二进制格式化
>>> f'Programmer Error: {error:#b}'
'Programmer Error: 0b101110101101110000001111111111101110'

你可以在 PEP498 中了解更多信息

变量注释

“动态语言一时爽,代码重构火葬场”,虽有危言耸听嫌疑,但的确因为动态语言的灵活性也带来代码维护困难的麻烦,我们不得不通过文档注释来对参数进行说明,而有时又因为业务需求的变更导致代码修改后没有同步文档注释造成实际代码和文档不一致的情况,如果能像静态语言一样,让程序员在语法层面就是就被限制在规则范围内做事,就不会出问题了。所以,像Java这样的语言做工程项目是有优势的。

从Python 3.5开始,可以将类型注解添加到函数和方法中:

1
2
>>> def my_add(a: int, b: int) -> int:
... return a + b

这个函数表示,a 和 b 两个参数必须是 int 类型,函数的返回值也是 int。

在语义方面没有任何改变—CPython解释器只是将类型记录为类型注释,但不做任何方式类型检查。类型检查纯粹是可选的,你需要一个像Mypy这样的工具来帮助你。

可以在PEP 526中了解更多关于这一变化的信息。

当然,这个版本不止这么一点点变化,还有:

  • 异步生成器的语法
  • 异步推导式语法
  • 更快的字典结构,内存减少20%到25%

英文原文:https://dbader.org/blog/cool-new-features-in-python-3-6

Python Dictionary and Set comprehensions

发表于 2018-05-21 | 分类于 Python | | 阅读次数:
| 字数统计:

大多数Python程序员都知道并使用列表推导。对于不熟悉这个概念的人来说,列表推导其实是一种更简短、更简洁的创建列表的方式。

1
2
3
4
>>> some_list = [1, 2, 3, 4, 5]
>>> another_list = [ x + 1 for x in some_list ]
>>> another_list
[2, 3, 4, 5, 6]

而从Python 3.1开始(也反向地移植到了Python 2.7), 我们可以用同样的方式创建集合(Set)和字典(Dict):

使用推导的方式,创建一个集合

1
2
3
4
5
>>> # Set Comprehensions
>>> some_list = [1, 2, 3, 4, 5, 2, 5, 1, 4, 8]
>>> even_set = { x for x in some_list if x % 2 == 0 }
>>> even_set
set([8, 2, 4])

使用推导的方式,创建一个Dict

1
2
3
4
>>> # Dict Comprehensions
>>> d = { x: x % 2 == 0 for x in range(1, 11) }
>>> d
{1: False, 2: True, 3: False, 4: True, 5: False, 6: True, 7: False, 8: True, 9: False, 10: True}

另一个创建集合的方式:

1
2
3
>>> my_set = {1, 2, 1, 2, 3, 4}
>>> my_set
set([1, 2, 3, 4])

没有使用到内建的 set方法, 直接用大括号{}创建。

Python 元类(Metaclasses)

发表于 2018-05-19 | 分类于 Python | | 阅读次数:
| 字数统计:

Python 元类(Metaclasses)

1、Type and Class

在Python3中,所有类都是新式类(New-Style)。因此,类的类型和它的实例类型是可以互换的。在Python中,所有的东西都是object。类也是一个object。因此类一定有一个type。
例如:

1
2
3
4
5
6
7
>>> class Foo: pass
...
>>> x = Foo()
>>> type(x)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'>

如上所示:x的类型是Foo,但是Foo的类型,类的本身就是type。
我们熟悉的内置类的类型也是type:

1
2
3
4
5
6
7
8
>>> for t in int, float, dict, list, tuple:
... print(type(t))
...
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>

type本身的类型也是type

1
2
3
>>> type(type)
<class 'type'>
>>>

type是一个元类,其中类是实例。就像普通对象是类的实例,Python中的任何新式类,以及Python 3中的任何类,都是类的元类实例。

还是上述的例子:

1
2
3
4
5
6
7
>>> class Foo: pass
...
>>> x = Foo()
>>> type(x)
<class '__main__.Foo'>
>>> type(Foo)
<class 'type'>
  • x是类Foo的实例
  • Foo 是type元类的实例
  • type也是元类的一个实例,因此它是它自己的一个实例。

2、如何动态创建类

内置类型()函数在传递一个参数时,返回对象的类型。对于新式类,通常与对象的class属性相同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> type(3)
<class 'int'>
>>> type(['foo', 'bar', 'baz'])
<class 'list'>
>>> t = (1, 2, 3, 4, 5)
>>> type(t)
<class 'tuple'>
>>> class Foo:
... pass
...
>>> type(Foo())
<class '__main__.Foo'>

我们还可以使用三个参数的type(, , )方法来动态创建一个新类:

  • 指定了类名。这成为该类的name属性。
  • 指定了类继承的基类的一个元组。这成为该类的bases属性。
  • 指定包含类主体定义的名称空间词典。这成为该类的dict属性。
    以这种方式调用类型()将创建类型元类的一个新实例。换句话说,它动态创建一个新类。
Example 1 最基本的使用
1
2
3
4
>>> Foo = type('Foo', (), {})
>>> x = Foo()
>>> x
<__main__.Foo object at 0x102b563c8>

上述通过type()动态创建类和下面代码创建类是一样的。

1
2
3
4
5
6
>>> class Foo:
... pass
...
>>> x = Foo()
>>> x
<__main__.Foo object at 0x102b56400>

最佳实战:动态创建类有一个很大好处。比如当你数据库表是采用分表设计。
如:

1
2
3
4
5
table_1
table_2
table_3
...
table_N

这个时候你要根据表来创建对应的模型。你会发现table结构是一致,只是表名不同。
那么最好的方式就是通过type()元类动态创建模型type(‘table_’+id, (), {})。

Example 2 继承
1
2
3
4
5
6
7
8
9
>>> Bar = type('Bar', (Foo,), dict(attr=100))
>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)
1
2
3
4
5
6
7
8
9
10
11
>>> class Bar(Foo):
... attr = 100
...
>>> x = Bar()
>>> x.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)
Example 3 默认属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> Foo = type(
... 'Foo',
... (),
... {
... 'attr': 100,
... 'attr_val': lambda x : x.attr
... }
... )
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100
1
2
3
4
5
6
7
8
9
10
11
>>> class Foo:
... attr = 100
... def attr_val(self):
... return self.attr
...
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100

Examle 4 默认属性引用外部函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> def f(obj):
... print('attr =', obj.attr)
...
>>> Foo = type(
... 'Foo',
... (),
... {
... 'attr': 100,
... 'attr_val': f
... }
... )
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
attr = 100
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def f(obj):
... print('attr =', obj.attr)
...
>>> class Foo:
... attr = 100
... attr_val = f
...
>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
attr = 100

参考文献

1、https://realpython.com/python-metaclasses/#type-and-class

Python Old-Style Vs. New-Style Classes

发表于 2018-05-18 | 分类于 Python | | 阅读次数:
| 字数统计:

Python Old-Style Vs. New-Style Classes

在Python中,类有两种方式定义,一种是非正式的,我们称为旧式的或者叫经典的(Old-Style),另一种是正式的(New-Style)。

换一种说法,对于继承自object的类,则称为New-Style类。未继承object的类称为Old-Style类。

在Python2.7中New-Style和Old-Style类存在一些区别。

Old-Style Classes

对于Old-Style类的实例,它的类和类型并不相同。Old-Style类的实例是一个内置类型,而类型是一个实例。例如:obj是一个Old-Style的实例。那么obj.class是一个内置类,而type(obj)是一个实例。

1
2
3
4
5
6
7
>>> class Foo: pass
...
>>> obj = Foo()
>>> obj.__class__
<class __main__.Foo at 0x103faba10>
>>> type(obj)
<type 'instance'>

New-Style Classes

New-Style类统一了类和类型的概念。例如:obj是一个New-Style的实例,obj.class和type(obj)相同:

1
2
3
4
5
6
7
>>> class Foo(object): pass
...
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
1
2
3
4
5
6
7
8
9
>>> n = 10
>>> d = {'x': 1, 'y': 2}
>>> x = Foo()
>>> for obj in (n, d, x):
... print(type(obj) is obj.__class__)
...
True
True
True

在 Python 2.7中New-Style类和Old-Style类在多继承方面也会有差异:

Old-Style Classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A:
def foo(self):
print('called A.foo()')
class B(A):
pass
class C(A):
def foo(self):
print('called C.foo()')
class D(B, C):
pass
if __name__ == '__main__':
d = D()
d.foo()
1
Out: called A.foo()

New-Style Classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A(object):
def foo(self):
print('called A.foo()')
class B(A):
pass
class C(A):
def foo(self):
print('called C.foo()')
class D(B, C):
pass
if __name__ == '__main__':
d = D()
d.foo()
1
Out: called C.foo()

B、C 是 A 的子类,D 多继承了 B、C 两个类,其中 C 重写了 A 中的 foo() 方法。

如果 A 是经典类,当调用 D 的实例的 foo() 方法时,Python 会按照深度优先的方法去搜索 foo(),路径是 B-A-C ,执行的是 A 中的 foo() ;

如果 A 是新式类,当调用 D 的实例的 foo() 方法时,Python 会按照广度优先的方法去搜索 foo(),路径是 B-C-A ,执行的是 C 中的 foo() 。

因为 D 是直接继承 C 的,从逻辑上说,执行 C 中的 foo() 更加合理,因此新式类对多继承的处理更为合乎逻辑。

注意: 在 Python 3.x 中的新式类貌似已经兼容了经典类,无论 A 是否继承 object 类, D 实例中的 foo() 都会执行 C 中的 foo() 。但是在 Python 2.7 中这种差异仍然存在,因此还是推荐使用新式类,要继承 object 类。

参考文献

1、https://realpython.com/python-metaclasses/#old-style-vs-new-style-classes

2、https://www.zhihu.com/question/19754936/answer/202650790

Python类的__dict__无法更新

发表于 2018-05-17 | 分类于 Python | | 阅读次数:
| 字数统计:

Python类的dict无法更新?

测试环境: Python2.7

1
2
3
4
5
6
>>> class O(object): pass
...
>>> O.__dict__['name'] = 'Finger'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'dictproxy' object does not support item assignment

答:是的, class的__dict__是只读的

1
2
3
4
5
>>> O.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'O' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'O' objects>, '__doc__': None})
>>> O.__dict__.items()
[('__dict__', <attribute '__dict__' of 'O' objects>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'O' objects>), ('__doc__', None)]

可以看到O.__dict__ 是一个dictproxy对象,而不是一个dict。

1
2
>>> dir(O.__dict__)
['__class__', '__cmp__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'copy', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'values']

我们dir(O.__dict__),发现它没有__setitem__方法

那么我们怎么改类设置属性呢?可以用setattr

1
2
3
4
>>> setattr(O, 'name', 'Finger')
>>> O.name
'Finger'
>>>

可能在测试的过程中你会发现,如果你采用Python经典继承(Old-Style Classes)看到的结果却是这样的:

1
2
3
4
5
>>> class O: pass
...
>>> O.__dict__['name'] = 'Finger'
>>> O.name
'Finger'

关于Python的Old-Style和New-Style Classes下次再探讨

注意:在Python3.x中不管是Old-Style还是New-Style Classes都是不允许对类的__dict__进行修改。因为不管哪种继承方式,它们都是mappingproxy对象

1
2
3
4
5
6
7
8
9
10
11
12
>>> class O: pass
...
>>> O.__dict__['name'] = 'Finger'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> class O(object): pass
...
>>> O.__dict__['name'] = 'Finger'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
1234

Finger Chou

38 日志
13 分类
28 标签
RSS
GitHub E-Mail
© 2016 — 2019 Finger.Chou
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4