19八/20
安装了Python3.7之后,遇到的一个很麻烦的坑就是与系统自带的ssl版本不兼容, Python3.7需要的openssl的版本为1.0.2
或者1.1.x,这个requirements在config Python3.7的时候使用“
--with-ssl”选项会报出。
-
Could not build the ssl module!
-
Python requires an OpenSSL 1.0.2 or 1.1 compatible libssl with X509_VERIFY_PARAM_set1_host().
Linux系统默认的openssl版本通常是:OpenSSL 1.0.1,可以使用命令查询系统的openssl版本:
openssl version

当前版本低于Python3.7的需求,会导致一些运行错误,比如pip3 install命令的失败,故需要进行openssl版本升级。

(1)下载较高版本的openssl
https://www.openssl.org/source/openssl-1.1.1g.tar.gz (2)解压安装包
tar -xvzf openssl-1.1.1g.tar.gz
(3)编译安装
-
-
./config --prefix=/usr/local/openssl no-zlib
-
-
这样的话,新版本的openssl已经安装完毕,但是需要进行一些环境配置才能够继续使用。
来到/usr/bin目录下,可以发现已经有一个openssl可执行文件,这个就是系统自带的低版本,这里我们对它进行一个备份。
(4)备份原openssl配置
备份是个好的习惯,至少在安装新版本出错的时候可以回滚旧版本继续使用。
-
mv /usr/bin/openssl /usr/bin/openssl_bak
-
mv /usr/include/openssl/ /usr/include/openssl_bak
(5)备份好之后就可以为新版openssl配置软连接了
-
-
ln -s /usr/local/openssl/include/openssl /usr/include/openssl
-
-
-
ln -s /usr/local/openssl/lib/libssl.so.1.1 /usr/local/lib64/libssl.so
-
-
-
ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl
(6)最后再修改下系统配置即可
-
-
echo "/usr/local/openssl/lib" >> /etc/ld.so.conf
-
-
-
(7)验证安装结果

第二步:安装 LibreSSL
从 LibreSSL 官网 http://www.libressl.org/ 下载源代码。此处使用“libressl-2.8.2.tar.gz”。
以此执行以下命令安装:
#wget https:
#tar xzvf libressl-2.8.2.tar.gz
#cd libressl-2.8.2
#./config
#make
#make install
此时再验证 OpenSSL 版本,如下:
#openssl version
LibreSSL 2.8.2
10五/19
1.关于Keras
1)简介
Keras是由纯python编写的基于theano/tensorflow的深度学习框架。
Keras是一个高层神经网络API,支持快速实验,能够把你的idea迅速转换为结果,如果有如下需求,可以优先选择Keras:
a)简易和快速的原型设计(keras具有高度模块化,极简,和可扩充特性)
b)支持CNN和RNN,或二者的结合
c)无缝CPU和GPU切换
2)设计原则
a)用户友好:Keras是为人类而不是天顶星人设计的API。用户的使用体验始终是我们考虑的首要和中心内容。Keras遵循减少认知困难的最佳实践:Keras提供一致而简洁的API, 能够极大减少一般应用下用户的工作量,同时,Keras提供清晰和具有实践意义的bug反馈。
b)模块性:模型可理解为一个层的序列或数据的运算图,完全可配置的模块可以用最少的代价自由组合在一起。具体而言,网络层、损失函数、优化器、初始化策略、激活函数、正则化方法都是独立的模块,你可以使用它们来构建自己的模型。
c)易扩展性:添加新模块超级容易,只需要仿照现有的模块编写新的类或函数即可。创建新模块的便利性使得Keras更适合于先进的研究工作。
d)与Python协作:Keras没有单独的模型配置文件类型(作为对比,caffe有),模型由python代码描述,使其更紧凑和更易debug,并提供了扩展的便利性。
2.Keras的模块结构

3.使用Keras搭建一个神经网络

4.主要概念
1)符号计算
Keras的底层库使用Theano或TensorFlow,这两个库也称为Keras的后端。无论是Theano还是TensorFlow,都是一个“符号式”的库。符号计算首先定义各种变量,然后建立一个“计算图”,计算图规定了各个变量之间的计算关系。
符号计算也叫数据流图,其过程如下(gif图不好打开,所以用了静态图,数据是按图中黑色带箭头的线流动的):

2)张量
张量(tensor),可以看作是向量、矩阵的自然推广,用来表示广泛的数据类型。张量的阶数也叫维度。
0阶张量,即标量,是一个数。
1阶张量,即向量,一组有序排列的数
2阶张量,即矩阵,一组向量有序的排列起来
3阶张量,即立方体,一组矩阵上下排列起来
4阶张量......
依次类推
重点:关于维度的理解
假如有一个10长度的列表,那么我们横向看有10个数字,也可以叫做10维度,纵向看只能看到1个数字,那么就叫1维度。注意这个区别有助于理解Keras或者神经网络中计算时出现的维度问题。
3)数据格式(data_format)
目前主要有两种方式来表示张量:
a) th模式或channels_first模式,Theano和caffe使用此模式。
b)tf模式或channels_last模式,TensorFlow使用此模式。
下面举例说明两种模式的区别:
对于100张RGB3通道的16×32(高为16宽为32)彩色图,
th表示方式:(100,3,16,32)
tf表示方式:(100,16,32,3)
唯一的区别就是表示通道个数3的位置不一样。
4)模型
Keras有两种类型的模型,序贯模型(Sequential)和函数式模型(Model),函数式模型应用更为广泛,序贯模型是函数式模型的一种特殊情况。
a)序贯模型(Sequential):单输入单输出,一条路通到底,层与层之间只有相邻关系,没有跨层连接。这种模型编译速度快,操作也比较简单
b)函数式模型(Model):多输入多输出,层与层之间任意连接。这种模型编译速度慢。
5.第一个示例
这里也采用介绍神经网络时常用的一个例子:手写数字的识别。
在写代码之前,基于这个例子介绍一些概念,方便大家理解。
PS:可能是版本差异的问题,官网中的参数和示例中的参数是不一样的,官网中给出的参数少,并且有些参数支持,有些不支持。所以此例子去掉了不支持的参数,并且只介绍本例中用到的参数。
1)Dense(500,input_shape=(784,))
a)Dense层属于网络层-->常用层中的一个层
b) 500表示输出的维度,完整的输出表示:(*,500):即输出任意个500维的数据流。但是在参数中只写维度就可以了,比较具体输出多少个是有输入确定的。换个说法,Dense的输出其实是个N×500的矩阵。
c)input_shape(784,) 表示输入维度是784(28×28,后面具体介绍为什么),完整的输入表示:(*,784):即输入N个784维度的数据
2)Activation('tanh')
a)Activation:激活层
b)'tanh' :激活函数
3)Dropout(0.5)
在训练过程中每次更新参数时随机断开一定百分比(rate)的输入神经元,防止过拟合。
4)数据集
数据集包括60000张28×28的训练集和10000张28×28的测试集及其对应的目标数字。如果完全按照上述数据格式表述,以tensorflow作为后端应该是(60000,28,28,3),因为示例中采用了mnist.load_data()获取数据集,所以已经判断使用了tensorflow作为后端,因此数据集就变成了(60000,28,28),那么input_shape(784,)应该是input_shape(28,28,)才对,但是在这个示例中这么写是不对的,需要转换成(60000,784),才可以。为什么需要转换呢?

如上图,训练集(60000,28,28)作为输入,就相当于一个立方体,而输入层从当前角度看就是一个平面,立方体的数据流怎么进入平面的输入层进行计算呢?所以需要进行黄色箭头所示的变换,然后才进入输入层进行后续计算。至于从28*28变换成784之后输入层如何处理,就不需要我们关心了。(喜欢钻研的同学可以去研究下源代码)。
并且,Keras中输入多为(nb_samples, input_dim)的形式:即(样本数量,输入维度)。
5)示例代码
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.optimizers import SGD
from keras.datasets import mnist
import numpy
'''
第一步:选择模型
'''
model = Sequential()
'''
第二步:构建网络层
'''
model.add(Dense(500,input_shape=(784,))) # 输入层,28*28=784
model.add(Activation('tanh')) # 激活函数是tanh
model.add(Dropout(0.5)) # 采用50%的dropout
model.add(Dense(500)) # 隐藏层节点500个
model.add(Activation('tanh'))
model.add(Dropout(0.5))
model.add(Dense(10)) # 输出结果是10个类别,所以维度是10
model.add(Activation('softmax')) # 最后一层用softmax作为激活函数
'''
第三步:编译
'''
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) # 优化函数,设定学习率(lr)等参数
model.compile(loss='categorical_crossentropy', optimizer=sgd, class_mode='categorical') # 使用交叉熵作为loss函数
'''
第四步:训练
.fit的一些参数
batch_size:对总的样本数进行分组,每组包含的样本数量
epochs :训练次数
shuffle:是否把数据随机打乱之后再进行训练
validation_split:拿出百分之多少用来做交叉验证
verbose:屏显模式 0:不输出 1:输出进度 2:输出每次的训练结果
'''
(X_train, y_train), (X_test, y_test) = mnist.load_data() # 使用Keras自带的mnist工具读取数据(第一次需要联网)
# 由于mist的输入数据维度是(num, 28, 28),这里需要把后面的维度直接拼起来变成784维
X_train = X_train.reshape(X_train.shape[0], X_train.shape[1] * X_train.shape[2])
X_test = X_test.reshape(X_test.shape[0], X_test.shape[1] * X_test.shape[2])
Y_train = (numpy.arange(10) == y_train[:, None]).astype(int)
Y_test = (numpy.arange(10) == y_test[:, None]).astype(int)
model.fit(X_train,Y_train,batch_size=200,epochs=50,shuffle=True,verbose=0,validation_split=0.3)
model.evaluate(X_test, Y_test, batch_size=200, verbose=0)
'''
第五步:输出
'''
print("test set")
scores = model.evaluate(X_test,Y_test,batch_size=200,verbose=0)
print("")
print("The test loss is %f" % scores)
result = model.predict(X_test,batch_size=200,verbose=0)
result_max = numpy.argmax(result, axis = 1)
test_max = numpy.argmax(Y_test, axis = 1)
result_bool = numpy.equal(result_max, test_max)
true_num = numpy.sum(result_bool)
print("")
print("The accuracy of the model is %f" % (true_num/len(result_bool)))
8五/19
一、为何要用Keras
如今在深度学习大火的时候,第三方工具也层出不穷,比较出名的有Tensorflow,Caffe,Theano,MXNet,在如此多的第三方框架中频繁的更换无疑是很低效的,只要你能够好好掌握其中一个框架,熟悉其原理,那么之后因为各种要求你想要更换框架也是很容易的。
那么sherlock用的是哪个框架呢?sherlock使用的是Google的开源框架Tensorflow,因为Google开源了tensorflow之后其社区非常活跃,而且版本更新也非常稳定,所以我就选择了这个框架。对于框架之争,在知乎上已经有很多人在撕逼了,这个就好比哪种编程语言好这个问题一样。对于我们来讲,选择一个稳定的框架,好好的学习deep learning才是重中之重,对于哪种框架更好的问题,我们学好之后自然有自己的见解,所以前期切忌在刷知乎听完大神撕逼之后频繁更换框架。
对于Tensorflow的安装,以及CPU和GPU版本,各种系统的安装网上已经有很多人详细的写过攻略了,可以自己去网上搜一搜,很容易就可以安装成功。
选择了Tensorflow之后,我们就可以愉快的开始我们的深度学习之旅了。去Tensorflow的中文社区,可以看到有一些新手教程,网上也有很多学习材料,推荐看看stanford大学cs224d的课件,http://cs224d.stanford.edu/lectures/CS224d-Lecture7.pdf, 很详细的介绍了tensorflow。然后你就可以写tensorflow的程序了。虽然说tensorflow已经是一个封装好的框架,但是你发现你写一个简单的神经网络也需要很多行才能够写完,这个时候,就有很多的第三方插架来帮助你写网络,也就是说你用tensorflow要写10行,第三方插架帮你封装了一个函数,就是把这10行集合在这个函数里面,那么你用1行,传入相同的参数,就能够达到10行相同的效果,如此简便并且节约时间,可以帮助很快的实现我们的想法。
Keras Documentation 就是Keras的官方文档,里面可以查阅所有的函数,并且可以在github上看他的开源代码,非常方便。安装也很简单,打开终端,输入pip install keras 就可以等待安装了。
下面就给一个简单的例子,来看一看Keras到底有多简单。
from keras.models import Sequential
model = Sequential()
引入sequential,这个就是一个空的网络结构,并且这个结构是一个顺序的序列,所以叫Sequential,Keras里面还有一些其他的网络结构。
from keras.layers import Dense, Activation
model.add(Dense(units=64, input_dim=100))
model.add(Activation('relu'))
model.add(Dense(units=10))
model.add(Activation('softmax'))
可以看到加入层很简单,只需要写.add,后面是要加的层的类型。
model.compile(loss='categorical_crossentropy',
optimizer='sgd',
metrics=['accuracy'])
一旦你写好了网络之后,就可以用compile编译整个网络,看参数设置有没有问题
model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.SGD(lr=0.01, momentum=0.9, nesterov=True))
你也可以自定义其中的优化函数,就像上面这样,’sgd’是Keras已经写好了一些默认参数的优化函数,你可以自己重新定义参数,得到一个优化函数。
model.fit(x_train,y_train,epochs=5,batch_size=32)
这个就像scikit-learn一样训练模型。
loss_and_metrics=model.evaluate(x_test,y_test,batch_size=128)
这个就是评估训练结果。
classes=model.predict(x_test,batch_size=128)
或者是通过predict进行预测。
看了上面的代码,相信很多熟悉scikit-learn的同学都很亲切,因为确实很简便,跟scikit-learn也有着类似的语法。
二、开始学习CNN
在理解CNN之前,我们有必要先理解一下什么是神经网络,这样才能开始了解更高级的卷积神经网络。
要学习神经网络当然有很多途径,网上不少的大牛写了很多攻略,有的推崇从理论到工程完成深度学习,有的希望从工程出发发现问题,解决问题。各种各样的方式都有不同的人去尝试,攻略也是一大推,这使得不少的小白直接倒在了选择材料的路上,一直在补先修知识,待到热情结束就放弃了学习,连卷积网络都不知道是什么,大大地打击了大家的学习热情。今天,sherlock在这里给大家推荐一个学习材料,保证你能够快速入门cnn,出去装逼也能够和别人聊几句。
这个材料是什么呢,就是大名鼎鼎的standford的cs231n这门课程。 CS231n Convolutional Neural Networks for Visual Recognition stanford大学确实算是深度学习和人工智能领域非常牛逼的学校。
神经网络
废话不多说,开始学习我们的神经网络。

这是一张脑神经的图片,神经网络的发明也是由此开始的,这就是所谓的一个神经元,上面有各种接受突触,然后通过一个脑神经来接受,最后得到输出的结果。
那么由这张脑神经图能够抽象出来的神经网络是什么呢?就是下面这个神经网络模型。

这个怎么理解呢?就是输入一个向量,然后给向量的每一个元素分配一个权重,然后通过权重求和得到一个结果,然后将这个结果输入一个激活函数,得到最后的输出结果。
激活函数又是什么鬼?激活函数的出现是因为人脑的构造,人脑里面接受信息得到结果这个过程是非线性的,比如你看到一样东西,你不可能保留这个东西的全部特征,你会重点观察你感兴趣的地方,这就是非线性的,也就是说需要一个非线性变化将输入的结果变换为非线性的结果。现在常用的非线性函数就是Relu(x) = max(x, 0),就是将小于0的部分去掉,只保留大于0的部分。
这就是单元的输入和输出,将这些单元合在一起就是一个神经网络。

这就是简单的一层网络,也可以由多层网络

这里面的input layer就是所谓的单个训练集的维数,将所有的训练集输入就可以开始训练一个神经网络。
Keras实现简单的神经网络
知道了神经网络的基本结构和原理,我们就可以开始使用keras去实现一个简单的神经网络。
import keras
from keras.models import Sequential
from keras.layers import Dense
import numpy as np
导入必要的package
x=np.array([[0,1,0],[0,0,1],[1,3,2],[3,2,1]])
y=np.array([0,0,1,1]).T
设定输入的x和y
simple_model=Sequential()
simple_model.add(Dense(5,input_shape=(x.shape[1],),activation='relu',name='layer1'))
simple_model.add(Dense(4,activation='relu',name='layer2'))
simple_model.add(Dense(1,activation='sigmoid',name='layer3'))
输入一个三层的神经网络,中间的hidden layer的元素个数是5和4,最后一层输出一个结果
simple_model.compile(optimizer='sgd',loss='mean_squared_error')
complie这个简单的模型
simple_model.fit(x,y,epochs=20000)
训练20000次模型
simple_model.predict(x[0:1])
可以预测一下第一个输入的x的结果与实际的是否相符。
上面就是一个简单三层网络的keras实现,接下来我们将正式进入Convolutional Neural Network
三、Convolutional Neural Network
前面给大家推荐了一门好课cs231n,本篇文章也是按照这个思路来的。
基本结构
首先解释一下什么是卷积,这个卷积当然不是数学上的卷积,这里的卷积其实表示的是一个三维的权重,这么解释起来可能不太理解,我们先看看卷积网络的基本结构。

通过上面的图我们清楚地了解到卷积网络和一般网络结构上面的差别,也可以理解为卷积网络是立体的,而一般的网络结构是平面的。
卷积层
了解完了基本的结构之后,我们就要了解cnn最重要的一个部分,也是最为创新的一个部分,卷积层。首先用一张图片来比较一下卷积网络到底创新在什么地方。

我们通过这个结构就可以清晰地看到卷积网络到底是怎么实现的。首先右边是传统的网络结构,在前面我们已经详细的解释过了。而左边的图片,我们首先看看图中最左边的结构,你肯定会好奇为什么是32x32x3的一块立体方块。这个32x32代表的是像素点,说白了也就是图片的大小,这个大小是你可以设置的,你可以设置为50x50,也可以是256x256,这都取决与图片的大小,那么3表示什么呢?3其实表示的是RGB的三个通道,RGB也是什么?RGB表示red,green,blue,这三种颜色的各种组合叠加可以形成各种各样的颜色,所以任何一张照片都可以用左边这种图形来表示。
那么中间这个小方块又表示什么呢?这个就是我们要重点讲的卷积。所谓的卷积,就是这种小方块,我们设置一个小方块的大小,但是这个小方块的厚度必须和左边的这个大方块的厚度是一样的,大方块每一个像素点由一个0到255的数字表示,这样我们就可以赋予小方块权重,比如我们取小方块的大小是3x3,我们要求厚度必须要和左边的大方块厚度一样,那么小方块的的大小就为3x3x3,我们就可以赋予其3x3x3个权重,然后我们就可以开始计算卷积的结果,将小方块从大方块的左上角开始,一个卷积小方块所覆盖的范围是3x3x3,然后我们将大方块中3x3x3的数字和小方块中的权重分别相乘相加,再加上一个偏差,就可以得到一个卷积的结果,可以抽象的写成Wx+b这种形式,这就是图上所显示的结果,然后我们可以设置小方块的滑动距离,每次滑动就可以形成一个卷积的计算结果,然后将整张大图片滑动覆盖之后就可以形成一层卷积的结果,我们看到图中的卷积结果是很厚的,也就是设置了很多层卷积。总结来说,每层卷积就是一个卷积核在图片上滑动求值,然后设置多个卷积核就可以形成多层的卷积层。
池化层
讲完卷积层,接下来就要讲一下池化层。为什么会有池化层的出现呢?是因为不断的做卷积,得到的中间结果会越来越厚,卷积就相当于提取图片中的特征,所以卷积层一般会设置得越来越厚,不然你就无法从前面的结果来提取更多的特征。这样就会导致中间的结果会越来越大,计算会越来越慢,所以提出了池化层。
所谓的池化层,就是将图片的大小缩小的一种处理方式。我们可以先看看下面的图片。

通过这个图片,我们可以清楚地看到池化层是怎么处理的。池化层也是需要先设置一个窗口,但是这个小窗口的厚度是1,而不再是前一层输出的结果的厚度。然后有两种处理方式,一种是取这个小窗口里面所有元素的最大值来代表这个小窗口,一种是取平均值,然后将小窗口滑动,在第二的位置再做同样的处理,上层网络输出方块的每一层做完之后就进入这个大方块的下一层做同样的操作,这个处理办法就可以让整个大方块的大小变小,可以看看上面的图片的左边。右边是一个简单的一层厚度,取最大值的例子。
实现Lenet
讲完了卷积网络的基本结构之后,你是不是已经迫不及待希望能够实现一个简单的神经网络了呢?卷积网络发展的特别迅速,最早是由Lecun提出来的,Lenet成为cnn的鼻祖,接下来他的学生Alex提出了层数更深的Alexnet,然后2013年又提出了VGGnet,有16层和19层两种,这些都只是在层数上面的加深,并没有什么其他的创新,而之后google提出了inception net在网络结构上实现了创新,提出了一种inception的机构,facebook ai 实验室又提出了resnet,残差网络,实现了150层的网络结构可训练化,这些我们之后会慢慢讲到。
接下来我们就来实现一下最简单的Lenet,使用mnist手写子体作为训练集。
import keras
from keras.datasets import mnist
(x_train, y_train), (x_test,y_test) =mnist.load_data()
导入必要的库和数据集
x_train=x_train.reshape(-1,28,28,1)
x_test=x_test.reshape(-1,28,28,1)
x_train=x_train/255.
x_test=x_test/255.
y_train=keras.utils.to_categorical(y_train)
y_test=keras.utils.to_categorical(y_test)
处理数据,让数据的shape是(28, 28, 1),然后label做一个one-hot encoding处理,比如类别是3,那么变成[0, 0, 1 ,0, 0, 0, 0, 0, 0, 0]。
from keras.layers import Conv2D,MaxPool2D,Dense,Flatten
from keras.models import Sequential
lenet=Sequential()
lenet.add(Conv2D(6,kernel_size=3,strides=1,padding='same',input_shape=(28, 28, 1)))
lenet.add(MaxPool2D(pool_size=2,strides=2))
lenet.add(Conv2D(16,kernel_size=5,strides=1,padding='valid'))
lenet.add(MaxPool2D(pool_size=2,strides=2))
lenet.add(Flatten())
lenet.add(Dense(120))
lenet.add(Dense(84))
lenet.add(Dense(10,activation='softmax'))
构建lenet

lenet.compile('sgd',loss='categorical_crossentropy',metrics=['accuracy'])
编译
lenet.fit(x_train,y_train,batch_size=64,epochs=50,validation_data=[x_test,y_test])
训练50次,得到结果如下

lenet.save('myletnet.h5')
可以保存训练好的模型
总结
OK, 这就是我们写的一个超级简单的Lenet,训练50次得到的训练准确率已经达到0.9939,测试准确率达到0.9852。
欢迎关注我的知乎专栏,深度炼丹
欢迎关注我的github主页
欢迎访问我的博客
这篇文章的代码都已传到了github上
SherlockLiao/lenet
21三/19
安装:
https://github.com/walac/pyusb
然后参考网友提供的图片内容:
0x21,0x09,0x300,0x0000,[0xff,0x01,0x00,0x00,0x00,0x00,0x00,0x00]
中括号里前2个是主要控制参数,
第1个参数, 0xff是打开1个开关,0xfe是打开所有开关,0xfd是关闭1个开关,0xfc是关闭所有开关
第2个参数, 0x01对应第几个开关,当第1个参数是0xfe和0xfc时该参数无效

23十二/15
虽然不是所有的Python程序都需要严格的性能分析,不过知道如何利用Python生态圈里的工具来分析性能,也是不错的。
分析一个程序的性能,总结下来就是要回答4个问题:
- 它运行的有多快?
- 它的瓶颈在哪?
- 它占用了多少内存?
- 哪里有内存泄漏?
接下来,我们会着手使用一些很棒的工具,来帮我们回答这些问题。
粗粒度的计算时间
我们先来用个很快的方法来给我们的代码计时:使用unix的一个很好的功能 time。
1
2
3
4
5
|
$ time python yourprogram.py
real 0m1.028s
user 0m0.001s
sys 0m0.003s
|
关于这3个测量值的具体含义可以看StackOverflow上的帖子,但是简要的说就是:
- real:代表实际花费的时间
- user::代表cpu花费在内核外的时间
- sys:代表cpu花费在内核以内的时间
通过把sys和user时间加起来可以获得cpu在你的程序上花费的时间。
如果sys和user加起来的时间比real时间要小很多,那么你可以猜想你的程序的大部分性能瓶颈应该是IO等待的问题。
用上下文管理器来细粒度的测量时间
我接下来要使用的技术就是让你的代码仪器化以让你获得细粒度的时间信息。这里是一个计时方法的代码片段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import time
class Timer( object ):
def __init__( self , verbose = False ):
self .verbose = verbose
def __enter__( self ):
self .start = time.time()
return self
def __exit__( self , * args):
self .end = time.time()
self .secs = self .end - self .start
self .msecs = self .secs * 1000
if self .verbose:
print 'elapsed time: %f ms' % self .msecs
|
为了使用它,将你想要测量时间的代码用Python关键字with和Timer上下文管理器包起来。它会在你的代码运行的时候开始计时,并且在执行结束的完成计时。
下面是一个使用它的代码片段:
1
2
3
4
5
6
7
8
9
10
11
|
from timer import Timer
from redis import Redis
rdb = Redis()
with Timer() as t:
rdb.lpush( "foo" , "bar" )
print "=> elasped lpush: %s s" % t.secs
with Timer as t:
rdb.lpop( "foo" )
print "=> elasped lpop: %s s" % t.secs
|
我会经常把这些计时器的输入记录进一个日志文件来让我知道程序的性能情况。
用分析器一行一行地计时和记录执行频率
Robert Kern有一个很棒的项目名叫 line_profiler。我经常会用它来测量我的脚本里每一行代码运行的有多快和运行频率。
为了用它,你需要通过pip来安装这个Python包:
1
|
$ pip install line_profiler
|
在你安装好这个模块之后,你就可以使用line_profiler模块和一个可执行脚本kernprof.py。
为了用这个工具,首先需要修改你的代码,在你想测量的函数上使用@profiler装饰器。不要担心,为了用这个装饰器你不需要导入任何其他的东西。Kernprof.py这个脚本可以在你的脚本运行的时候注入它的运行时。
Primes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@profile
def primes(n):
if n = = 2 :
return [ 2 ]
elif n< 2 :
return []
s = range ( 3 ,n + 1 , 2 )
mroot = n * * 0.5
half = (n + 1 ) / 2 - 1
i = 0
m = 3
while m < = mroot:
if s[i]:
j = (m * m - 3 ) / 2
s[j] = 0
while j<half:
s[j] = 0
j + = m
i = i + 1
m = 2 * i + 3
return [ 2 ] + [x for x in s if x]
primes( 100 )
.
|
一旦你在你的代码里使用了@profile装饰器,你就要用kernprof.py来运行你的脚本:
1
|
$ kernprof.py -l - v fib.py
|
-l这个选项是告诉kernprof将@profile装饰器注入到你的脚本的内建里,-v是告诉kernprof在脚本执行完之后立马显示计时信息。下面是运行测试脚本后得到的输出:
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
|
Wrote profile results to primes.py.lprof
Timer unit: 1e - 06 s
File : primes.py
Function: primes at line 2
Total time: 0.00019 s
Line
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2 @profile
3 def primes(n):
4 1 2 2.0 1.1 if n = = 2 :
5 return [ 2 ]
6 1 1 1.0 0.5 elif n< 2 :
7 return []
8 1 4 4.0 2.1 s = range ( 3 ,n + 1 , 2 )
9 1 10 10.0 5.3 mroot = n * * 0.5
10 1 2 2.0 1.1 half = (n + 1 ) / 2 - 1
11 1 1 1.0 0.5 i = 0
12 1 1 1.0 0.5 m = 3
13 5 7 1.4 3.7 while m < = mroot:
14 4 4 1.0 2.1 if s[i]:
15 3 4 1.3 2.1 j = (m * m - 3 ) / 2
16 3 4 1.3 2.1 s[j] = 0
17 31 31 1.0 16.3 while j<half:
18 28 28 1.0 14.7 s[j] = 0
19 28 29 1.0 15.3 j + = m
20 4 4 1.0 2.1 i = i + 1
21 4 4 1.0 2.1 m = 2 * i + 3
22 50 54 1.1 28.4 return [ 2 ] + [x for x in s if x]
.
|
在里面寻找花费时间比较长的行,有些地方在优化之后能带来极大的改进。
它用了多少内存?
现在,我们已经能很好的测量代码运行时间了,接下来就是分析代码用了多少内存了。幸运的是,Fabian Pedregosa已经完成了一个很好的memory_profiler,它模仿了Robert Kern的line_profile。
首先,用pip来安装它:
1
2
|
$ pip install - U memory_profiler
$ pip install psutil
|
(推荐安装psutils包,这是因为这能大大提升memory_profiler的性能)
跟line_profiler类似,memory_profiler需要用@profiler装饰器来装饰你感兴趣的函数,就像这样:
1
2
3
4
|
@profile
def primes(n):
...
...
|
用一下的命令来查看你的函数在运行时耗费的内存:
1
|
$ python - m memory_profiler primes.py
|
在代码运行完之后,你就应该能看到一下的输出:
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
|
Filename: primes.py
Line
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
2 @profile
3 7.9219 MB 0.0000 MB def primes(n):
4 7.9219 MB 0.0000 MB if n = = 2 :
5 return [ 2 ]
6 7.9219 MB 0.0000 MB elif n< 2 :
7 return []
8 7.9219 MB 0.0000 MB s = range ( 3 ,n + 1 , 2 )
9 7.9258 MB 0.0039 MB mroot = n * * 0.5
10 7.9258 MB 0.0000 MB half = (n + 1 ) / 2 - 1
11 7.9258 MB 0.0000 MB i = 0
12 7.9258 MB 0.0000 MB m = 3
13 7.9297 MB 0.0039 MB while m < = mroot:
14 7.9297 MB 0.0000 MB if s[i]:
15 7.9297 MB 0.0000 MB j = (m * m - 3 ) / 2
16 7.9258 MB - 0.0039 MB s[j] = 0
17 7.9297 MB 0.0039 MB while j<half:
18 7.9297 MB 0.0000 MB s[j] = 0
19 7.9297 MB 0.0000 MB j + = m
20 7.9297 MB 0.0000 MB i = i + 1
21 7.9297 MB 0.0000 MB m = 2 * i + 3
22 7.9297 MB 0.0000 MB return [ 2 ] + [x for x in s if x]
.
|
IPython里针对line_profiler和memory_profiler的快捷方式
Line_profiler和memory_profiler共有的特性是它们都在IPython里有快捷方式。你只需要在IPython里输入以下内容:
1
2
|
% load_ext memory_profiler
% load_ext line_profiler
|
完成这个步骤后,你就可以使用一个神奇的命令 %lprun 和 %mprun ,它们跟其对应的命令行的功能是类似的。主要的不同是在这里你不需要在你想测量的函数上面使用@profiler来装饰它。可以直接在IPython里像一下的样子了来运行它:
1
2
3
4
|
% load_ext In [ 1 ]: from primes import primes
In [ 2 ]: % mprun - f primes primes( 1000 )
In [ 3 ]: % lprun - f primes primes( 1000 / pre>
这个因为其不用修改你的代码,而能够节省你很多的时间和精力。
|
哪里有内存泄漏?
C Python解释器使用引用计数的方法来作为其内存管理的主要方法。这意味着虽有对象都包含一个计数器,如果增加了一个对这个对象的引用就加1,如果引用被删除就减1。当计数器的值变成0的时候,C Python解释器就知道这个对象不再被使用便会删除这个对象并且释放它占用的内存。 如果在你的程序里,尽管一个对象不再被使用了,但仍然保持对这个对象的引用,就会导致内存泄漏。 找到这些内存泄漏最快的方法就是使用一个很棒的工具,名叫objgraph,由Marius Gedminas写的。这个工具能让你看到内存里的对象数量,也能在你的代码里定位保持对这些对象的引用的地方。 首先是安装objgraph:
在它安装好之后,在你的代码里添加一段声明来调用debugger。
1
|
import pdb; pdb.set_trace()
|
哪些对象是最常见的?
在运行时,你可以通过运行它考察在你的代码里排前20最常见的对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
(pdb) import objgraph
(pdb) objgraph.show_most_common_types()
MyBigFatObject 20000
tuple 16938
function 4310
dict 2790
wrapper_descriptor 1181
builtin_function_or_method 934
weakref 764
list 634
method_descriptor 507
getset_descriptor 451
type 439
|
哪些对象被添加或者删除?
我们也可以及时看到在两点之间那些对象被添加或者删除了:
1
2
3
4
5
6
7
8
9
10
11
12
|
(pdb) import objgraph
(pdb) objgraph.show_growth()
.
.
.
(pdb) objgraph.show_growth()
traceback 4 + 2
KeyboardInterrupt 1 + 1
frame 24 + 1
list 667 + 1
tuple 16969 + 1
|
哪里引用了有漏洞的对象
顺着这条路继续,我们也能看到哪里有对任何指定对象的引用是被保持了的。我们以下面的程序为例:
1
2
3
|
x = [ 1 ]
y = [x, [x], { "a" :x}]
import pdb; pdb.set_trace()
|
为了看哪里有对于变量x的一个引用,运行objgraph.show_backref( )函数:
1
2
|
(pdb) import objgraph
(pdb) objgraph.show_backref([x], filename = "/tmp/backrefs.png" )
|
这个命令的输出应该是一个PNG图片,它的路径为 /tmp/backrefs.png。它看起来应该是这样:

最下面的方框,里面用红色字母写出的是我们感兴趣的对象。我们可以看到它被变量x引用一次,被列表y引用三次。如果x是导致内存泄漏的对象,我们可以用这个方法来看为什么它没有通过追踪所有的引用而被自动释放。
来回顾一下,objgraph 能让我们:
- 显示我们的python程序里占用内存最多的前N个对象
- 显示在一段时间里被添加或删除的对象
- 显示在我们的代码里对一个给定对象的所有引用
成就vs 精确
在前文中,我已经展示了如果使用几种工具来分析Python程序的性能。在有了这些工具和技术后,你应该能得到所需要的所有信息来追踪Python程序里大部分的内存泄漏和性能瓶颈。
跟很多其他的主题一样,进行一个性能分析意味着平衡和取舍。在不确定的时候,实现最简单的方案将是适合你目前需要的。