Talk is cheap

Do something cool and make the world a better place.

0%

CUDA版本OpenCV安装

CUDA版本的OpenCV安装主要参考这里,写的已经很很详细了,不再赘述。

不过有两点需要注意:

  1. opencvopencv_contrib的版本可以下载最新的,比如4.5.4,cmake命令里注意替换版本号
  2. 一般我们不希望自己编译的版本安装在系统全局,因此,cmakeCMAKE_INSTALL_PREFIX参数需要更改,我的习惯是在home里创建libs保存自定义编译的库。

最终的cmake命令示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=~/libs/opencv-4.5.4-cuda/release \
-D WITH_TBB=ON \
-D ENABLE_FAST_MATH=1 \
-D CUDA_FAST_MATH=1 \
-D WITH_CUBLAS=1 \
-D WITH_CUDA=ON \
-D BUILD_opencv_cudacodec=OFF \
-D WITH_CUDNN=ON \
-D OPENCV_DNN_CUDA=ON \
-D CUDA_ARCH_BIN=6.1 \
-D WITH_V4L=ON \
-D WITH_QT=OFF \
-D WITH_OPENGL=ON \
-D WITH_GSTREAMER=ON \
-D OPENCV_GENERATE_PKGCONFIG=ON \
-D OPENCV_PC_FILE_NAME=opencv.pc \
-D OPENCV_ENABLE_NONFREE=ON \
-D OPENCV_PYTHON3_INSTALL_PATH=~/.virtualenvs/cv/lib/python3.8/site-packages \
-D PYTHON_EXECUTABLE=~/.virtualenvs/cv/bin/python \
-D OPENCV_EXTRA_MODULES_PATH=~/Downloads/opencv_contrib-4.5.4/modules \
-D INSTALL_PYTHON_EXAMPLES=OFF \
-D INSTALL_C_EXAMPLES=OFF \
-D BUILD_EXAMPLES=OFF ..

VS Code配置

首先参考官方文档配置C++支持,现在的问题就是,如何配置让vs code能自动识别我们自定义编译的opencv版本。

新建cmake项目,CMakeList.txt文件示例如下:

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 2.8)

set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS "-std=c++11 -O2")

find_package(OpenCV REQUIRED NO_DEFAULT_PATH PATHS "/home/hp/libs/opencv-4.5.4-cuda/release")

include_directories(${OpenCV_INCLUDE_DIRS})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include")
add_executable(test test.cpp)
target_link_libraries(test ${OpenCV_LIBS})

注意其中的find_package命令,添加了REQUIRED NO_DEFAULT_PATH参数和具体的PATHS参数。

路径识别与自动补全

vs code settings.json添加如下配置:

1
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json"

这样vs code就会使用cmake(编辑CMakeList文件vs code会自动生成)生成的`compile_commands.json‵文件,找到我们自定义配置的opencv路径。

Debug

经过上面的配置,还是无法一键debug,参考官方文档,配置launch.json文件的program参数,示例如下:

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
{
"version": "0.2.0",
"configurations": [
{
"name": "g++ - Build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${command:cmake.launchTargetPath}",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: g++ build active file",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}

至此,vs code就可以自动识别我们自定义编译的opencv,效果如下图: vscode

happy coding~

这个问题无外乎有三个难点:

  1. 什么是sum
  2. 什么是reduce
  3. 什么是维度(indices, 现在均改为了axis和numpy等包一致)

sum很简单,就是求和,那么问题就是2和3,让我们慢慢来讲。其实彻底讲清楚了这个问题,很多关于reduce,维度的问题都会恍然大悟。

0. 到底操作哪个维度??

sum这个操作完全可以泛化为任意函数,我们就以sum为例,来看看各种情况。 首先是一维(按照tensorflow的说法其实是0维,后面会说)就是这样:

1
2
a = 1
sum(a) = 1

那么看看二维的情况,为了看得更清楚,特意写成了矩阵的形式:

1
2
3
4
5
6
a = 
[
[1,2],
[3,4]
]
sum(a) = ???

仔细观察,那么问题来了,sum(a)到底应该是多少?有人说,当然是[3,7](“横着加”[[1+2],[3+4]]),有人说 不应该是[4,6](“竖着加”[[1+3],[2+4]])吗? 还有人或说,不应该是10(全加在一起)吗? 谁是对的? 都是对的。

所以,对于多维数组元素的相加,如果不指定“如何加”,结果是未定义的,之所以有些时候没有指定也可以得到结果,是因为不同的软件或框架有默认的行为。 对于tensorflow,默认行为是最后一种,也就是全加在一起。

1. 什么是维度?什么是轴(axis)?如何索引轴(axis)?

这是一个很大的问题,到底什么是维度呢?维基百科说:

维度,又称维数,是数学中独立参数的数目。在物理学和哲学的领域内,指独立的时空坐标的数目。 0维是一点,没有长度。 1维是线,只有长度。 2维是一个平面,是由长度和宽度(或曲线)形成面积。 3维是2维加上高度形成“体积面”。 虽然在一般人中习惯了整数维,但在分形中维度不一定是整数,可能会是一个非整的有理数或者无理数。

妈呀,好复杂,我只是想写个tensorflow代码呀。 那么,编程时,你就可以简单的认为:

维度是用来索引一个多维数组中某个具体数所需要最少的坐标。

把这句话多读几遍,我想你肯定会有所顿悟。这里之所以说第一个一维的例子时0维,是因为,一个数字根本不需要索引,因为就只有一个呀。 所有不同维度的形式如下:

1
2
3
4
5
0维,又称0维张量,数字,标量:1
1维,又称1维张量,数组,vector:[1,2,3]
2维,又称2维张量,矩阵,二维数组:[[1,2], [3,4]]
3维,又称3维张量,立方(cube),三维数组:[[[1,2], [3,4]], [[5,6], [7,8]]]
n维:你应该get到点了吧~

再多的维只不过是是把上一个维度当作自己的元素

1维的元素是标量,2维的元素是数组,3维度元素是矩阵。 从0维到3维,边看边念咒语维度是用来索引一个多维数组中某个具体数所需要最少的坐标。

在纸上写写看,想要精确定位一个数字,需要几个数字呢?比如三维数组,我们想要3这个数字,至少要3个数字定位,它的坐标是(0为索引起点):

1
[0, 1, 0]

好了,现在就能说了,什么是轴(axis),如何索引axis(代码中常用的变量名,后文就用axis代表轴)。 什么是axis,编程时,你就可以简单的认为: axis是多维数组每个维度的坐标。 同样,把这句话多读几遍,我想你一定有体悟。

还拿三维来说,数字3的坐标是[0, 1, 0],那么第一个数字0的axis是0,第二个数字1的axis是1,第三个数字0的axis是2。 让我们再看看我们是如何得到3这个数字的:

1
2
3
找到3所在的2维矩阵在这个3维立方的索引:0
找到3所在的1维数组在这个2维矩阵的索引:1
找到3这个数这个1维数组的索引:0

也就是说,对于[[[1,2], [3,4]], [[5,6], [7,8]]]这个3维情况,[[1,2],[[5,6]], [[3,4], [7,8]]这两个矩阵的axis是0,[1,2],[3,4],[5,6],[7,8] 这4个数组的axis是1,而1,2,3,4,5,6,7,8这8个数的axis是2。

越往里axis就越大,依次加1。

那么,对于三维的情况,令:

1
2
3
import tensorflow as tf
a = [[[1,2], [3,4]], [[5,6], [7,8]]]
tf.reduce_sum(a, axis=1)

应该输出:

1
[[4,6], [12,14]]

这就是处在axis=1的4个数组相加的结果,并reduce掉了一个维度。那么什么是reduce呢?

2. 什么是reduce

reduce这个词字面上来讲,大多称作“规约”,但这个词太专业了,以至于第一眼看不出来意思。我更倾向于解释为“塌缩”,这样就形象多了。 对一个n维的情况进行reduce,就是将执行操作的这个维度“塌缩”。还是上面tf.reduce_sum(a, axis=1)的例子,输出[[4,6], [12,14]]是二维, 显然是被“塌缩”了,塌缩的哪个维度呢?就是被操作的维度,第2个维度,也就是axis=1(0开始索引)。

tf.reduce_sum(a, axis=1)具体执行步骤如下:

  1. 找到a中axis=1的元素,也就是[1,2],[3,4],[5,6],[7,8]这4个数组
  2. 在axis=1的维度进行相加也就是[1,2]+[3,4]=[4,6][5,6]+[7,8]=[12,14]
  3. “塌缩”这一维度,也就是说“掉一层方括号”,得出[[4,6], [12,14]]

接下来是一个附加问题:

3. 什么是keepdims

也就是“不掉一层方括号”,tf.reduce_sum(a, axis=1, keepdims=True)得出[[[4, 6]], [[12, 14]]],可以看到还是三维。 这种尤其适合reduce完了要和别的同维元素相加的情况。

最后来做个练习吧,对于四维情况[[[[1,2], [3,4]]], [[[3,4], [5,6]]]],对axis=0,1,2,3分别在草稿纸上进行演算后,用tensorflow做个验证吧~

  1. $ uptime:输出最近1, 5, 15分钟的负载。
  2. $ dmesg tail:输出系统日志的最后10行,用于检查内核报警。
  3. $ vmstat 1:输出系统核心指标,其中r, free, (si, so), (us, sy, id, wa, st)分别代表等待CPU资源进程数,系统可用内存数,(交换区写入,读取),(用户,系统,空闲,IO等待,其他CPU时间),参数表示刷新间隔/秒。
  4. $ mpstat -P ALL 1:输出每个CPU的占用情况,一般若有一个CPU占用率特别高,则可能是一个单线程应用程序引起的。
  5. $ pidstat 1:输出进程的CPU占用率。
  6. $ iostat -xz 1:输出机器磁盘IO情况,参数-x表示输出额外信息,-z表示不输出非活动状态设备,数字表示刷新间隔。 其中r/s, w/s, rkB/s, wkB/s分别表示每秒读写次数和每秒读写数据量, await表示IO操作的平均等待时间,如果这个数值过大,可能是硬件设备遇到了瓶颈或者出现故障, avgqu-sz表示向设备发出的请求平均数量,大于1表示硬件可能已经饱和, %util表示设备利用率,这个数值表示设备的繁忙程度,经验值是如果超过60,可能会影响IO性能,100%表示已经饱和。
  7. $ free -m:输出系统内存的使用情况,参数-m表示按照兆字节显示。
  8. $ sar -n DEV 1:输出网络设备的吞吐率,参数-n详见man手册,数字表示刷新间隔,下同。
  9. $ sar -n TCP,ETCP 1:输出TCP连接状态。
  10. $ top/htop:输出系统负载总览,建议使用htop,输出更加形象。

此博文为原文的缩水精简版,备忘记录于此。

最近看自然语言处理词频统计的时候,看到的一种方法,小记于此。

伪计数可以看做是一种平滑,由拉普拉斯提出,也称为拉普拉斯平滑。伪计数估计量的具体公式是:

其中是估计量,是观测到的事件分类,是平滑参数(有些学者认为应该是1,不过一般都比较小,比如取0.5),是观测事件总数,是观测值的可能分类,例如二分类问题中

那么这个公式是什么意思呢?具体地说,为什么要这么规定这种平滑?那是因为这种平滑的好处是它的值在经验估计:与均匀分布概率之间。也就是很好地完成了从事实发生概率到随机发生概率的平滑任务。

再具体一点是什么意思呢?那么我觉得就要从它的来历说起了。历史上,拉普拉斯试图估计明天太阳升起的可能性的时候,提出了这个概念。 大家都知道,太阳当然会升起啊!不升起来的概率(或者说观测到不升起来的次数)为0。等等,那可不一定,至少拉普拉斯不这么认为, 他甚至认为日出的前一秒,也不能确定能不能观测到太阳升起。当然,这不是抬杠,仔细想想确实是这个道理,毕竟谁也不知道太阳会不会被一个极小概率的事件毁灭。

那么,这个概率到底是多少?这个概率怎么计算出来既可以满足事实(姑且叫做“事实概率”),又可以满足幻想(姑且叫做“幻想概率”)。答案是:平滑。

比如,从我开始观测,一共是次观测,其中有:

的集合可以知道, 那么很接近事实(事实概率为0),但又有那么点发生幻想的几率(幻想概率为0.01)。 随着观测的进行,越来越大, 而却没什么长进,那么幻想概率会越来越小但又不是不会发生。

可以看出,伪计数很好的实践了“虽然我观测没发生,但是也不否认发生的可能性,只是发生的可能性随着我观测我觉得越来越小而已”这一贝叶斯派的思想。其实,这就是一种从贝叶斯角度看概率的实例。

参考资料:Additive smoothing

想把这个写成一个系列,“深度学习算法在写paper时的应用过程——从数据到对比实验结果”,但是填坑太累,先分开写吧,最后如果能完成就整理成系列。

最近有篇关于轴承故障诊断的文章要用到轴承数据,就找到了西储大学这个轴承的公开数据集。 关于数据集本身我就不做过多的描述了,主页说得已经很清楚。

但是问题来了,这个数据集没有提供压缩文件下载!而是用表格的形式列出的很多数据文件的集合,而且还是.mat文件,也就是MATLAB文件。 更不方便的一点是文件名都是数字,果手动下载每个文件,需要:

  1. 建立不同采样频率(12k&48k)的文件夹
  2. 分别下载每个分类下的每个文件
  3. 3.对每个文件重新命名

这可不是个轻松加愉快的事情,本着“需要手工机械重复超过3分钟的工作应该尝试使用自动化脚本实现”这一不知道哪里看来的感觉很有道理的信念,就开始考虑如何自动化实现了。 其实最开始思路很简单,程序需要完成至少以下内容:

  1. 爬取网页
  2. 分析网页,获得数据的大分类(Normal&12k&48k)category,每个单个文件在表格中的名称&url(例如IR007_0以及它的地址)filename & url
  3. 根据2获取的category建立文件夹,通过url下载,并重命名为filename

整个脚本实现起来应该不难,第1, 2部分只是一个简单的获取网页+内容解析,最具有挑战性的地方在于第3步,因为可能要用多线程下载。 灵机一动,多线程下载不是有工具嘛,wget,aria2,不能放着这么好用的工具不用。 尤其是aria2,多线程下载就是一个参数的事。 因此,第3个步骤改为:

根据2获取的category建立文件夹,并将urlfilename保存位aria2下载列表格式。 这样,就可以直接用shell脚本来遍历多线程下载了。

分析就到这里,程序也很清晰了,源代码在这里

其中有一个Python文件和两个shell script,Python文件是用来获取filenameurl等信息并生成aria2下载列表url.txt的, shell script是用来自动化遍历子文件夹下载url.txt的。

其中url.txt的格式参见aria2的官方文档

Traditional CS:

  1. CS110: Principles of Computer Systems
  2. CS161: Design and Analysis of Algorithms
  3. CS106A: Programming Methodology
  4. CS97SI: Introduction to Programming Contests
  5. CS143: Compilers

AI/ML:

  1. CS221: Artificial Intelligence: Principles and Techniques
  2. CS229: Machine Learning
  3. CS231n: Convolutional Neural Networks for Visual Recognition
  4. CS224n: Natural Language Processing with Deep Learning
  5. CS294: Deep Reinforcement Learning
  6. CS 228: Probabilistic Graphical Models

GPU Programming:

  1. CS179: (Caltech)

  1. 基础知识
    • 线性代数
    • 矩阵理论
    • 概率论
    • 随机过程
    • 图论
    • 数值分析
    • 最优化理论
    • 机器学习
    • 数据挖掘
  2. 平台
    • Linux
  3. 语言
    • Python
    • Linux shell
  4. 基础库
    • numpy
    • pandas
    • sklearn
    • matplotlib or seaborn
  5. 框架
    • TensorFlow
    • Pytorch
    • Keras
  6. 常用模型
    • CNN
    • RNN(LSTM)
    • GAN
  7. 常用架构(包括transfer learning的方法)
    • VGG
    • Inception
  8. 竞赛平台
    • Kaggle
  9. 常用技巧
    • 爬虫
    • 数据清洗
    • 假设与推断

突然想起来本科的日子,参加几个社团,没事打打球,在工作室,午后慵懒地靠椅上(中间还垫了个睡枕:-)),喝着咖啡悠闲地写自己想要构建的项目代码, 总有学不完的新知识,晚上翻窗户出去吃夜宵,聊天……..真是充实又幸福。

现在每天赶文章,想idea,文献综述,写为了完成任务的代码,做ppt,汇报,教研室-食堂-宿舍循环,接触到的人,圈子不超过20,充实是充实了, 但是却没了幸福感,难道以后也是这样吗?果然都说本科是最快乐的时光,一点不假。 Good old days.

VGG pretrained模型地址 下载地址 不过我用的这个beta版本不太一样,但是分析过程是一样的

今天在玩Stanford CS20SI公开课里的一个神经网络style_transfer小模型的时候,用到了vgg pretrained模型,然而,代码里获取weight和bias 是这么写的:

1
2
W = vgg_layers[0][layer][0][0][2][0][0]
b = vgg_layers[0][layer][0][0][2][0][1]

摔!这也没注释,完全看不懂啊! 虽然可以查到vgg的模型信息(下面有个layer configuration的SVG图),可是对应不上这些索引啊! 于是把模型下载下来,在ipython里一步一步解析:

1
2
import scipy.io
vgg = scipy.io.loadmat('imagenet-vgg-verydeep-19.mat')

接下来就是探索格式了。

首先,看一下导入后的模型类型: type(vgg),嗯,不出所料,是个dictscipy.io.loadmat的官方文档也有说明,返回类型是个dict。

然后就是看一下有哪些key啦:vgg.keys(),输出有dict_keys(['__header__', '__version__', '__globals__', 'layers', 'classes', 'normalization'])

看来前三项是meta信息,后面的三样东西是我们需要的,看名字应该分别是层参数,分类信息,正则化参数(像素平均值)。

那么,先看看layers层: layers = vgg['layers'],输出有一大坨,不过看头部足以分析:

1
array([[array([[(array([[array([[[[0.39416704, -0.08419707, -0.03631314, ...

顶级array有两个[[所以是两维,每一个维数的元素是array,array内部还有维数,因此可以看出,想要索引出元素确实需要很多index,那么看一下shape:layers.shape,输出是 (1, 43)

说明虽然有两维,但是第一维是”虚的”,也就是只有一个元素,我们index进去:layers = layers[0]

根据模型可以知道,这43个元素其实就是对应模型的43层信息(conv1_1, relu, conv1_2…), 那么看一层就足以,而且我们现在得到了一个有用的index,那就是layer:layers[layer]

其中layer是layer参数,范围0-42,我们选0看看:layer = layers[0],从输出可以看出尾部有dtype信息,这个比较重要,记录了元素的标签:

1
dtype=[('weights', 'O'), ('pad', 'O'), ('type', 'O'), ('name', 'O'), ('stride', 'O')]

可以看出顶层的array有5个元素,分别是weight(含有bias),pad(填充元素,无用),type, name, stride信息。 然后继续看一下shape信息, layer.shape输出是:(1, 1)。说明虽然有两维但是这两维都是”虚的”,也就是只有一个元素,我们直接两次index进去, 应该会得到一个包含5中信息的array,layer = layer[0][0],现在layer已经是”裸”的了,直接看看len,len(layer)输出是5果然不出所料, 那么已经很清楚了,对应上面的dtype说明,很容易得到weight, bias之类信息,比如layer的name,是第4个(从0开始索引),那么:name = layer[3] 输出是array(['conv1_1'], dtype='<U7'),果然拿到了name信息,但是这是一个array,想拿到字符串还要再index进去:name = name[0]。 这次输出就是一个字符串元素了:’conv1_1’,看来解析已经成功了。 其他如法炮制,例如weight(含有bias):weight = layer[0],看一下weight的shape:weight.shape,输出是: (1, 2), 再次说明第一维是”虚的”,index进去:weight = weight[0]。注意,这里的weight是广义的weight,也就是包含通常我们说的weight和bias, 所以,分别拿到这两个元素还要再解开一次:

1
weight, bias = weight

至此,已经完成了第0层的name, weight, bias解析,那么其他层也一样,改变layer的index即可,但是要注意的是,这是conv层的解析,relu层,fc层有所不同,不过原理一样,在ipython里探索即可。

所以,根据上面总结出layer的name的index顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
name = vgg
# 字典key
['layers']
# 去掉"虚"的维,相当于np.squeeze
[0]
# 层索引
[layer]
# 连续两次去掉虚的维度
[0][0]
# weigh, pad, type, name, stride5类信息的索引,从0开始
[3]
# 去掉"虚"的维,相当于np.squeeze
[0]

weight,bias的index顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
weight, bias = vgg
# 字典key
['layers']
# 去掉"虚"的维,相当于np.squeeze
[0]
# 层索引
[layer]
# 连续两次去掉虚的维度
[0][0]
# weigh, pad, type, name, stride5类信息的索引,从0开始
[0]
# 去掉"虚"的维,相当于np.squeeze
[0]