So Tired !_! 逆水行舟, 不进则退!

19Aug/20

解决python3.7与openssl的低版本不兼容的问题

Posted by Nick Xu

安装了Python3.7之后,遇到的一个很麻烦的坑就是与系统自带的ssl版本不兼容, Python3.7需要的openssl的版本为1.0.2或者1.1.x,这个requirements在config Python3.7的时候使用“--with-ssl”选项会报出。

  1. Could not build the ssl module!
  2. 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)编译安装

  1. cd openssl-1.1.1g
  2. ./config --prefix=/usr/local/openssl no-zlib # 新版openssl将安装在/usr/local/openssl目录下
  3. make
  4. make install

这样的话,新版本的openssl已经安装完毕,但是需要进行一些环境配置才能够继续使用。

来到/usr/bin目录下,可以发现已经有一个openssl可执行文件,这个就是系统自带的低版本,这里我们对它进行一个备份。

(4)备份原openssl配置

备份是个好的习惯,至少在安装新版本出错的时候可以回滚旧版本继续使用。

  1. mv /usr/bin/openssl /usr/bin/openssl_bak # backup
  2. mv /usr/include/openssl/ /usr/include/openssl_bak

(5)备份好之后就可以为新版openssl配置软连接了

  1. # 将安装好的openssl的openssl命令软连到/usr/bin/openssl
  2. ln -s /usr/local/openssl/include/openssl /usr/include/openssl
  3. # 软链到升级后的libssl.so
  4. ln -s /usr/local/openssl/lib/libssl.so.1.1 /usr/local/lib64/libssl.so
  5. # 将安装好的openssl命令软连到/usr/bin/openssl
  6. ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl

(6)最后再修改下系统配置即可

  1. # 写入openssl库文件的搜索路径
  2. echo "/usr/local/openssl/lib" >> /etc/ld.so.conf # 需要root权限
  3. # 使修改后的/etc/ld.so.conf生效
  4. ldconfig -v

(7)验证安装结果

 

第二步:安装 LibreSSL

从 LibreSSL 官网 http://www.libressl.org/ 下载源代码。此处使用“libressl-2.8.2.tar.gz”。

 

以此执行以下命令安装:

#wget https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-2.8.2.tar.gz
#tar xzvf libressl-2.8.2.tar.gz
#cd libressl-2.8.2
#./config
#make
#make install

 

此时再验证 OpenSSL 版本,如下:

#openssl version
LibreSSL 2.8.2
Filed under: Linux, Python Comments Off
17Aug/20

pyenv 让 python 版本完美切换

Posted by Nick Xu

前言

我觉得如果使用 python 开发的话,还是在 unix/linux 的环境下吧,shell 工具的效率比 windows 高得多,尽管 windows 下也有 cmder 这种神器,而且现在 windows store 也很好的开发出了 linux 子系统,但是瑕疵非常多,unix/linux 才是完美的环境。本文用到的是 archlinux ,另外 debian/ubuntu、centos 也完全适用。mac 用户也可以参考,不过 homebrew 也提供了非常方便的安装方法,但是建议使用文本做法。

背景

python 版本比较多,2 和 3 相差非常大,很多项目需要跑在同一台服务器上,我们可以选择直接运行,也可以选择使用 docker。如果用 docker 那就不需要隔离环境了,如果要直接运行在服务器上,那就必须有隔离环境。比如有的项目使用 python 3.5,有的项目使用 python 3.7,此时我们可以借助 pyenv 帮助我们完美的隔离环境,让多个版本的 python 没有任何冲突,完美共存。

任务

使用 pyenv 和 pyenv-virtualenv ,在 linux 下完美隔离 python 各个版本

第 1 章:使用环境

请注意,接下来所有的操作都在 archlinux 下进行,本文不涉及 windows

清单

  1. git
  2. zsh 或者 bash
  3. pyenv
  4. pyenv-virtualenv

1、安装 git

在各大 linux 的发行版下安装 git 都非常简单,此处只展示部分示例

archlinux

sudo pacman -S git

debian/ubuntu

sudo apt-get install git

centos

sudo yum install git

2、开启终端

本文使用 zsh

3、安装 pyenv

说明:本文的所有安装都严格遵守官方文档,与官方文档完全保持一致。

git 地址:https://github.com/pyenv/pyenv

在你的终端中执行如下命令,安全无毒,请放心食用:

首先把项目克隆下来,放在家目录下的隐藏文件夹中:.pyenv

git clone https://github.com/pyenv/pyenv.git ~/.pyenv

然后配置环境变量

如果你使用 bash,就依次执行如下命令:

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc

echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc

echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bashrc

如果你使用 zsh,就依次执行如下命令:

echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc

echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc

echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.zshrc

echo 命令的含义是:将引号中内容写入某文件中
请注意,以上的三条 echo 命令的最后一条长长的命令,请你保证它引号中的内容处于 ~/.bashrc 或者 ~/.zshrc 的最底部。
因为在 pyenv 初始化期间会操作 path 环境变量,导致不可预测的行为。
查看文件的底部内容,可以使用 tail 命令,用法:tail ~/.bashrc 或者 tail ~/.zshrc,编辑文件可以使用 vim 或者 vscode

最后,在使用 pyenv 之前,重新初始化 shell 环境,执行如下命令

exec $SHELL

不执行该命令也是完全可以的,你可以关闭当前的终端窗口,重新启动一个就可以了。

此时,你已经完成了 pyenv 的安装了,你使用可以它的全部命令了,但是我建议你先别急着用,一口气装完 pyenv 的一个插件,那就是 pyenv-virtualenv

4、安装 pyenv-virtualenv

git 地址:https://github.com/pyenv/pyenv-virtualenv

把插件克隆在刚才已经安装完毕的 pyenv 的 plugins 文件夹中

git clone https://github.com/pyenv/pyenv-virtualenv.git $(pyenv root)/plugins/pyenv-virtualenv

然后配置环境变量

如果你使用 bash,就执行如下命令:

echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.bashrc

如果你使用 zsh,就执行如下命令:

echo 'eval "$(pyenv virtualenv-init -)"' >> ~/.zshrc

最后,在使用 pyenv 之前,重新初始化 shell 环境,执行如下命令

exec $SHELL

不执行该命令也是完全可以的,你可以关闭当前的终端窗口,重新启动一个就可以了。

到此,我们的所有重要安装已经全部完成了,可以开始体验了。

第 2 章:使用 pyenv

此处仅仅展示 pyenv 和 virtualenv 的日常用法

检查安装是否正确

检查 pyenv 的版本

pyenv version

查看 pyenv 已经托管了哪些 python 版本

pyenv versions

如果你看到了正常的版本信息,就说明可以了,如果看到了类似于 command not found 之类的,就说明安装失败了。

安装 3.6.6 版本的 python

pyenv install 3.6.6

这里有个问题,某些情况下会安装失败,报错就告诉你 Build failed

这个时候,pyenv 已经在它的 github wiki 里面为我们准备了一篇错误应对方案,原文地址 https://github.com/pyenv/pyenv/wiki

大意如下,只需要执行对应的命令即可:

archlinux 用户

sudo pacman -S base-devel openssl zlib

mac 用户

brew install openssl readline sqlite3 xz zlib

Ubuntu/Debian/Mint 用户

sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev

CentOS/Fedora <= 21 用户,请你保证已经安装了 xz 工具

sudo yum install gcc zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel libffi-devel

Fedora >= 22 用户,请你保证已经安装了 xz 工具

sudo dnf install -y gcc zlib-devel bzip2 bzip2-devel readline-devel sqlite sqlite-devel openssl-devel tk-devel

openSUSE 用户

zypper install gcc automake openssl-devel ncurses-devel readline-devel zlib-devel tk-devel

Solus 用户

sudo eopkg it -c system.devel

sudo eopkg install git gcc make zlib-devel bzip2-devel readline-devel sqlite3-devel openssl-devel tk-devel

Linuxbrew 用户

brew install bzip2 openssl readline sqlite xz

安装完这些补充的工具之后,再次执行:

pyenv install 3.6.6

就可以成功了,你可以不断的使用

pyenv versions

来查看被 pyenv 托管的 python 版本

而且你想装什么版本就装什么版本,想装几个装几个,都是完美共存,完美隔离,你可以在终端里输入

pyenv install

然后按下 tab 键,就可以看到所有可选的安装版本了

使用刚才安装的 python 3.6.6

首先我们需要明确一个概念,pyenv 和 pyenv-virtualenv 他们是如何协作的,你可以这么认为:

pyenv 托管 python 版本,virtualenv 使用 python 版本

好了,之前已经装好了版本,那么现在就来使用吧

第 1 步:创建虚拟环境

首先需要创建一个虚拟环境,执行命令:

pyenv virtualenv 3.6.6 my-env

它的格式就是这样固定的,最后一个是你自己想要的环境的名字,可以随便取。稍等片刻,你将会看到:

Looking in links: /tmp/tmp0eywgc7v
Requirement already satisfied: setuptools in /home/joit/.pyenv/versions/3.6.6/envs/my-env/lib/python3.6/site-packages (39.0.1)
Requirement already satisfied: pip in /home/joit/.pyenv/versions/3.6.6/envs/my-env/lib/python3.6/site-packages (10.0.1)

类似于这样的回显信息,说明环境已经创建成功了,它还告诉了你,该虚拟环境的绝对路径,如果你进去看了,你就会发现,所谓的虚拟环境,就是把 python 装在 pyenv 的安装目录的某个文件夹中,以供它自己调用。

第 2 步:激活虚拟环境

在任意目录下,执行命令:

pyenv activate my-env

你会发现,在你的终端里面,多了一个类似于 (my-env) 这样的一个东西,这时候你如果执行:

python --version

那就是 python 3.6.6 了

如果你执行:

pip --version

它会告诉你 pip 包安装的绝对路径,也是 pyenv 安装目录下的某个文件夹

如果你关掉了终端,那么下次启动你又得重新激活一次了,你可以使用如下命令:

首先 cd 到某一个目录,比如 ~/test

cd ~/test

然后在该目录下执行:

pyenv local my-env

你会发现已经被激活了,那么 local 命令和刚才有啥不同呢。如果你执行:

ls -al

你就会发现,在 ~/test 目录下,有个隐藏文件 .python-version,你可以看到这个文件里面,只写了一句话 my-env

这样你只要进入 ~/test 目录,就会自动激活虚拟环境

在虚拟环境下,你如果直接执行

python

就会进入到 python 的交互环境

如果你写了一个文件,名字叫做 app.py ,里面的内容只有一句代码:print(1)

然后执行:

python app.py

这时候,系统就会调用虚拟环境中的 python 解释器来执行这些代码了

第 3 章:更新 pyenv

由于我们是 git 克隆的,所以更新非常简单

cd ~/.pyenv 或者 cd $(pyenv root)
git pull

第 4 章:卸载 pyenv

由于 pyenv 把一切都放在 ~/.pyenv 下了,所以卸载很方便,两个步骤就行了

首先你需要删除环境变量

然后你需要执行:

rm -rf ~/.pyenv 或者 rm -rf $(pyenv root)

作者:JoiT
链接:https://www.jianshu.com/p/3e93311fe6cb
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Tagged as: , Comments Off
24Mar/20

pip下载库超时Connection to files.pythonhosted.org timed out问题解决(换源)

Posted by Nick Xu

1、国内镜像大全

阿里云 http://mirrors.aliyun.com/pypi/simple/
中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/
豆瓣(douban) http://pypi.douban.com/simple/
清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/
中国科学技术大学 http://pypi.mirrors.ustc.edu.cn/simple/

2、换源命令

临时:pip命令+ -i  +镜像地址。例如 pip install numpy -i https://mirrors.aliyun.com/pypi/simple/

永久:在更新到最新版本pip后,pip config set global.index-url+国内镜像地址,例如

pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/

Tagged as: , Comments Off
21Aug/19

mac解决安装mysql-python错误my_config.h

Posted by Nick Xu

brew install mysql
brew unlink mysql
brew install mysql-connector-c
sed -i -e 's/libs="$libs -l "/libs="$libs -lmysqlclient -lssl -lcrypto"/g' /usr/local/bin/mysql_config
pip install MySQL-python
brew unlink mysql-connector-c
brew link --overwrite mysql

出现下列错误:

ld: library not found for -lssl

解决:

sudo env LDFLAGS="-I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib" pip install MySQL-python

Filed under: Mac OS, Python Comments Off
10May/19

深度学习:Keras入门(一)之基础篇

Posted by Nick Xu

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)))

 

Tagged as: Comments Off
8May/19

小白学CNN以及Keras的速成

Posted by Nick Xu

一、为何要用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大学确实算是深度学习和人工智能领域非常牛逼的学校。

神经网络

废话不多说,开始学习我们的神经网络。

小白学CNN以及Keras的速成

这是一张脑神经的图片,神经网络的发明也是由此开始的,这就是所谓的一个神经元,上面有各种接受突触,然后通过一个脑神经来接受,最后得到输出的结果。

那么由这张脑神经图能够抽象出来的神经网络是什么呢?就是下面这个神经网络模型。

小白学CNN以及Keras的速成

这个怎么理解呢?就是输入一个向量,然后给向量的每一个元素分配一个权重,然后通过权重求和得到一个结果,然后将这个结果输入一个激活函数,得到最后的输出结果。

激活函数又是什么鬼?激活函数的出现是因为人脑的构造,人脑里面接受信息得到结果这个过程是非线性的,比如你看到一样东西,你不可能保留这个东西的全部特征,你会重点观察你感兴趣的地方,这就是非线性的,也就是说需要一个非线性变化将输入的结果变换为非线性的结果。现在常用的非线性函数就是Relu(x) = max(x, 0),就是将小于0的部分去掉,只保留大于0的部分。

这就是单元的输入和输出,将这些单元合在一起就是一个神经网络。

小白学CNN以及Keras的速成

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

小白学CNN以及Keras的速成

这里面的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以及Keras的速成

通过上面的图我们清楚地了解到卷积网络和一般网络结构上面的差别,也可以理解为卷积网络是立体的,而一般的网络结构是平面的。

卷积层

了解完了基本的结构之后,我们就要了解cnn最重要的一个部分,也是最为创新的一个部分,卷积层。首先用一张图片来比较一下卷积网络到底创新在什么地方。

小白学CNN以及Keras的速成

我们通过这个结构就可以清晰地看到卷积网络到底是怎么实现的。首先右边是传统的网络结构,在前面我们已经详细的解释过了。而左边的图片,我们首先看看图中最左边的结构,你肯定会好奇为什么是32x32x3的一块立体方块。这个32x32代表的是像素点,说白了也就是图片的大小,这个大小是你可以设置的,你可以设置为50x50,也可以是256x256,这都取决与图片的大小,那么3表示什么呢?3其实表示的是RGB的三个通道,RGB也是什么?RGB表示red,green,blue,这三种颜色的各种组合叠加可以形成各种各样的颜色,所以任何一张照片都可以用左边这种图形来表示。

那么中间这个小方块又表示什么呢?这个就是我们要重点讲的卷积。所谓的卷积,就是这种小方块,我们设置一个小方块的大小,但是这个小方块的厚度必须和左边的这个大方块的厚度是一样的,大方块每一个像素点由一个0到255的数字表示,这样我们就可以赋予小方块权重,比如我们取小方块的大小是3x3,我们要求厚度必须要和左边的大方块厚度一样,那么小方块的的大小就为3x3x3,我们就可以赋予其3x3x3个权重,然后我们就可以开始计算卷积的结果,将小方块从大方块的左上角开始,一个卷积小方块所覆盖的范围是3x3x3,然后我们将大方块中3x3x3的数字和小方块中的权重分别相乘相加,再加上一个偏差,就可以得到一个卷积的结果,可以抽象的写成Wx+b这种形式,这就是图上所显示的结果,然后我们可以设置小方块的滑动距离,每次滑动就可以形成一个卷积的计算结果,然后将整张大图片滑动覆盖之后就可以形成一层卷积的结果,我们看到图中的卷积结果是很厚的,也就是设置了很多层卷积。总结来说,每层卷积就是一个卷积核在图片上滑动求值,然后设置多个卷积核就可以形成多层的卷积层。

池化层

讲完卷积层,接下来就要讲一下池化层。为什么会有池化层的出现呢?是因为不断的做卷积,得到的中间结果会越来越厚,卷积就相当于提取图片中的特征,所以卷积层一般会设置得越来越厚,不然你就无法从前面的结果来提取更多的特征。这样就会导致中间的结果会越来越大,计算会越来越慢,所以提出了池化层。

所谓的池化层,就是将图片的大小缩小的一种处理方式。我们可以先看看下面的图片。

小白学CNN以及Keras的速成

通过这个图片,我们可以清楚地看到池化层是怎么处理的。池化层也是需要先设置一个窗口,但是这个小窗口的厚度是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

小白学CNN以及Keras的速成

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次,得到结果如下

小白学CNN以及Keras的速成

lenet.save('myletnet.h5')

可以保存训练好的模型

总结

OK, 这就是我们写的一个超级简单的Lenet,训练50次得到的训练准确率已经达到0.9939,测试准确率达到0.9852。

欢迎关注我的知乎专栏,深度炼丹

欢迎关注我的github主页

欢迎访问我的博客

这篇文章的代码都已传到了github上
SherlockLiao/lenet

Tagged as: , Comments Off
21Mar/19

ubuntu下用python控制usbrelay设备

Posted by Nick Xu

安装:
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时该参数无效

 

usbrelay-python

Filed under: Linux, Python Comments Off
29Jun/18

Upgrade to Python 2.7.9 on Ubuntu 14.04 LTS

Posted by Nick Xu

Upgrade to Python 2.7.9 on Ubuntu 14.04 LTS, and make your own .deb package for deployment

Need to run your web app with Python 2.7.9, but server environment uses Ubuntu 14.04 LTS? Don’t replace Python, leverage VirtualEnv instead!

April 5, 2015

I had this post hanging in my drafts on how I attempted to install a valid Python 2.7.9 runtime environment on Ubuntu 14.04 and make my own .deb package for easy re-deployment.

IMPORTANT This procedure isn’t complete as I had to shift focus elsewhere. I might rework this article to adjust what’s missing.

While I understand that Ubuntu 14.04 will remain using Python 2.7.6 internally, applications we run can be configured to use another python environment. Its what virtualenv is all about after all, isn’t it.

This post attempts to install, and make an installable .deb package of Python 2.7.9 and is meant to be used by web applications without touching the system’s python runtime.

Why not replacing internal Python version?

The reason is simple. If you replace internal Python version, other softwares within the OS will have broken dependencies.

I realized this while I wanted to upgrade the version and breaking an hard dependency I have on Salt Stack. Since many components within a given Ubuntu version relies on Python, it could break anything else. This is why I stopped working on the idea of replacing internally, but instead to configure VirtualEnv to use another version instead.

If you see procedures that shows you to replace telling you to use update-alternatives to replace python, don’t do it! Go instead learn how to run your own Python version in VirtualEnv.

Procedure

  1. Install build dependencies

    Those were the ones I ran last before a successful build on Ubuntu 14.04 LTS, if you aren’t using the same distribution, you might get a different list.

      apt-get install -y gcc-multilib g++-multilib libffi-dev libffi6 libffi6-dbg python-crypto python-mox3 python-pil python-ply libssl-dev zlib1g-dev libbz2-dev libexpat1-dev libbluetooth-dev libgdbm-dev dpkg-dev quilt autotools-dev libreadline-dev libtinfo-dev libncursesw5-dev tk-dev blt-dev libssl-dev zlib1g-dev libbz2-dev libexpat1-dev libbluetooth-dev libsqlite3-dev libgpm2 mime-support netbase net-tools bzip2
  2. Get Python sources and compile
    1. wget https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz
    2. tar xfz Python-2.7.9.tgz
    3. cd Python-2.7.9/
    4. ./configure --prefix /usr/local/lib/python2.7.9 --enable-ipv6
    5. make
    6. make install
  3. Test if the version works
    1. /usr/local/lib/python2.7.9/bin/python -V
    2. Python 2.7.9
  4. Then prepare package through FPM
    1. apt-get install -y ruby-dev gcc
    2. gem install fpm
    3. ...

Its basically about creating a .deb based on your new runtime setup. You can do that by using fpm(“Fabulous Package Manager”), I am using this technique in a post I published recently about installing a PHP library.

Incomplete scratchpad

But that’s as far as my notes goes for now. Sorry about that.

Setuptools

As per recommended in Setuptools instructions, we can run easy_install through a wget, like so;

 
  1. wget https://bootstrap.pypa.io/ez_setup.py -O - | /usr/local/lib/python2.7.9/bin/python
  2. /usr/local/lib/python2.7.9/bin/easy_install pip
  3. /usr/local/lib/python2.7.9/bin/pip install virtualenv

Then, create symbolic links

  1. ln -s /usr/local/lib/python2.7.9/bin/easy_install /usr/bin/easy_install
  2. ln -s /usr/local/lib/python2.7.9/bin/pip /usr/bin/pip

You can try if it worked

  1. pip list
  2. pip (6.0.8)
  3. setuptools (14.3)
  4. virtualenv (12.0.7)
Tagged as: , Comments Off
17Mar/16

install Python Imaging Library on mac

Posted by Nick Xu

# download
curl -O -L http://effbot.org/media/downloads/Imaging-1.1.7.tar.gz
# extract
tar -xzf Imaging-1.1.7.tar.gz
cd Imaging-1.1.7
# build and install
ln -s /usr/local/include/freetype2 /usr/local/include/freetype
xcode-select --install
python setup.py build
sudo python setup.py install
# or install it for just you without requiring admin permissions:
# python setup.py install --user
Tagged as: , Comments Off
23Dec/15

Python程序的性能分析指南

Posted by Nick Xu

虽然不是所有的Python程序都需要严格的性能分析,不过知道如何利用Python生态圈里的工具来分析性能,也是不错的。

分析一个程序的性能,总结下来就是要回答4个问题:

  1. 它运行的有多快?
  2. 它的瓶颈在哪?
  3. 它占用了多少内存?
  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  # millisecs
        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 #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     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 #    Mem usage  Increment   Line Contents
==============================================
     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:

1
pip install 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()   # this only shows objects that has been added or deleted since last show_growth() call
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。它看起来应该是这样:

backrefs

最下面的方框,里面用红色字母写出的是我们感兴趣的对象。我们可以看到它被变量x引用一次,被列表y引用三次。如果x是导致内存泄漏的对象,我们可以用这个方法来看为什么它没有通过追踪所有的引用而被自动释放。

来回顾一下,objgraph 能让我们:

  • 显示我们的python程序里占用内存最多的前N个对象
  • 显示在一段时间里被添加或删除的对象
  • 显示在我们的代码里对一个给定对象的所有引用

 

成就vs 精确

在前文中,我已经展示了如果使用几种工具来分析Python程序的性能。在有了这些工具和技术后,你应该能得到所需要的所有信息来追踪Python程序里大部分的内存泄漏和性能瓶颈。

跟很多其他的主题一样,进行一个性能分析意味着平衡和取舍。在不确定的时候,实现最简单的方案将是适合你目前需要的。

Tagged as: Comments Off
site
site