AI备忘录 - Caffe
type: channel
link: https://www.aiuai.cn/tag/Caffe/
items: Caffe - 与 opencv4 相关问题解决
items: Caffe - solver.prototxt 参数配置
items: Caffe - 神经网络中的激活函数[转]
items: Caffe - 网络结构 prototxt 可视化
items: Caffe - Solver 网络优化方法
items: Caffe - 图片数据格式处理
items: Caffe - RoI Pooling 层
items: GPU优化 - Caffe 显存优化[译]
items: Caffe - 训练时Loss变为nan的原因[转]
items: Caffe - 基于 Python 创建LMDB/HDF5格式数据

Caffe - 与 opencv4 相关问题解决
type: item
date: 2020-10-08T12:33:00+08:00
description: Ubuntu20.04 编译 Caffe 时与 OpenCV4.X 的问题记录与解决.Caffe - Ubuntu 安装与测试实例及问题解决 - AIUAIUbuntu - OpenCV 源码安装与测试 - AIUAI参考：How to Install OpenCV on Ubuntu 20.04 - 2020.07.05参考：Install OpenCV 4.4.0 and Caffe on Ubuntu 20.04 for Python 3Ubuntu20.04 仓库安装 OpenCV:sudo apt install libopencv-dev python3-opencv查看版本：pkg-config --modversion opencv4 # 4.2.0问题1 - src/caffe/data_transformer.cpp:2:33: fatal error: opencv2/core/core.hpp原因：找不到头文件.解决方案：将 OpenCV 的头文件加入：原： INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial/ 新： INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial/ /usr/include/opencv4/问题2 - error: ‘CV_INTER_CUBIC’ was not declared in this scope问题：src/caffe/data_transformer.cpp:781:84: error: ‘CV_INTER_CUBIC’ was not declared in this scope cv::resize(crop_bufferM, cv_cropped_img, cv::Size(crop_size, crop_size), 0, 0, CV_INTER_CUBIC); ^~~~~~~~~~~~~~ src/caffe/data_transformer.cpp:781:84: note: suggested alternative: ‘SA_INTERRUPT’ cv::resize(crop_bufferM, cv_cropped_img, cv::Size(crop_size, crop_size), 0, 0, CV_INTER_CUBIC); ^~~~~~~~~~~~~~ SA_INTERRUPT src/caffe/data_transformer.cpp: In member function ‘void caffe::DataTransformer<Dtype>::Transform(const caffe::Datum&, const caffe::Datum&, caffe::Blob<Dtype>*, caffe::Blob<Dtype>*)’: src/caffe/data_transformer.cpp:954:51: error: ‘CV_INTER_NN’ was not declared in this scope cv::resize(M, M, cv::Size(width, height), 0, 0, CV_INTER_NN); ^~~~~~~~~~~ src/caffe/data_transformer.cpp:954:51: note: suggested alternative: ‘CV_INLINE’ cv::resize(M, M, cv::Size(width, height), 0, 0, CV_INTER_NN); ^~~~~~~~~~~ CV_INLINE make: *** [Makefile:592: .build_release/src/caffe/data_transformer.o] Error 1解决方案：在 src/caffe/data_transformer.cpp 中添加：include <opencv2/imgproc/imgproc_c.h>：#ifdef USE_OPENCV #include <opencv2/core/core.hpp> #include <opencv2/core/core.hpp> #include <opencv2/core/mat.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/imgproc/imgpr…

Caffe - solver.prototxt 参数配置
type: item
date: 2019-03-01T14:52:21+08:00
description: Caffe 进行模型训练，一般形式为：#!/usr/bin/env bash SOLVER=/path/to/solver.prototxt # GPUID=0 LOG=/path/to/train.log TOOLS=/path/to/caffe/build/tools echo "Starting to train" $TOOLS/caffe train -solver $SOLVER \ -weights /path/to/ptrtrained.caffemodel \ -gpu $GPUID 2>&1 | tee ${LOG}这里对 solver.prototxt 中的参数配置进行汇总说明. 具体可见 CAFFE_ROOT/src/caffe/proto/caffe.proto.net: "/path/to/train_val.prototxt" # test_iter： 每次测试的迭代次数， # 一般 test_iter*batch_size=所有的 test 样本数. # 一次测试可以覆盖全部的测试样本. test_iter: 1000 # test_interval： 训练时，每迭代 4000 次训练，进行一次测试. test_interval: 4000 # test_initialization： 训练开始时是否先进行一次测试. test_initialization: false # display： 每迭代 40 次训练，在屏幕上打印一次训练日志，如 loss 状态等. display: 40 # average_loss： 多次训练的 forward 的 loss，求平均值，并在屏幕上打印显示. # Display the loss averaged over the last average_loss iterations. # 相当于对 loss 值做了一个平滑. # 屏幕打印的当前训练 loss, 实际上是最近 20 次迭代的loss的平均值.不影响训练. average_loss: 40 # base_lr： 训练的基础学习率 lr,一般在 finetune 时，会设置较小的值. # 再根据特定的优化算法，对 lr 进行调整. # lr 过大，训练会难收敛； # lr 过小，训练收敛速度会比较慢. base_lr: 0.01 # lr_policy： 学习率 lr 的衰减策略 # lr_policy="fixed" # 训练过程中学习率lr保持不变. lr_policy: "step" # 与下面几个参数配合使用 # stepsize： 每 stepsize 次迭代，衰减学习率 stepsize: 320000 # gamma： 学习率 lr 衰减因子 gamma: 0.96 # momentum: 0.9 # weight_decay： 用于防止过拟合 weight_decay: 0.0002 # regularization_type：正则项，支持 L1 和 L2 正则化. # 由 weight_decay 决定. regularization_type = "L2" # clip_gradients：设置 clip_gradients>= 0 以对梯度参数进行剪枝， # 避免参数的真实 L2 参数值太大. clip_gradients: 60 # max_iter: 训练最大迭代次数，即网络何时停止训练. # 迭代次数少，可能收敛不了；迭代次数过多，可能过拟合或训练震荡. max_iter: 10000000 # iter_size: iter_size*batch_size=训练时的实际 batchsize. # 可以用于解决由于 GPU 显存不足，而导致训练 batchsize 受限的问题， # 相当于对 batch_size*iter_size 个样本进行 forward 后，才进行一次 backward. # 在单次 batchsize 受限时，可以做到很大的 batch. iter_size: 4 # snapshot：每 snapshot 次训练迭代，保存一次模型，相当于训练 checkpoints 保存. snapshot: 40000 # snapshot_prefix：训练中间模型的保存路径和前缀 snapshot_prefix: "/path/to/caffe_train" # snapshot_after_train：如果值为 false，则仅在训练完成后才保存一次模型. snapshot_after_train: true # type：优化算法 # 支持的有六种："SGD"，"AdaDelta"，"AdaGrad"，"Adam"，"Nesterov"，"RMSPr…

Caffe - 神经网络中的激活函数[转]
type: item
date: 2019-01-21T09:25:00+08:00
description: 原文1：理解神经网络的激活函数 - 2018.05.05原文2：神经网络的激活函数总结 - 2018.08.02原文3：反向传播算法推导-全连接神经网络 - 2018.07.06出处：SIGAI - 微信公众号1. 为什么需要激活函数从数学上看，神经网络是一个多层复合函数. 激活函数在很早以前就被引入，其作用是保证神经网络的非线性，因为线性函数无论怎样复合结果还是线性的. 假设神经网络的输入是 n 维向量 x，输出是 m 维向量 y，它实现了如下向量到向量的映射：$$ R^n \rightarrow R^m $$我们将这个函数记为：$$ y = h(x) $$除输入层之外，标准的前馈型神经网络第 $l$ 层实现的变换可以分为线性组合、激活函数两步. 在某些开源框架中，这两步可能会拆分成不同的层，以利于代码复用和灵活组合. 例如 Caffe 中线性组合由内积层 InnerProductLayer 类实现，激活函数由神经元层NeuronLayer 类实现. 神经网络第层的变换写成矩阵和向量形式为：$$ \begin{matrix} u^{(l)} = W^{(l)} x^{(l-1)} + b^{(l)} \\ x^{(l)} = f(u^{(l)}) \end{matrix} $$其中W是权重矩阵，b是偏置向量，u是临时结果，x是神经网络每一层的输出. 激活函数分别作用于向量u的每一个分量，产生一个向量输出x. 在正向传播阶段，反复用上面的公式进行计算，最后得到网络的输出. 对于一个3层的网络，整个映射可以写成：$$ h(x) = f(W^{(3)} f(W^{(2)} f(W^{(1)}x + b^{(1)}) + b^{(2)}) + b^{(3)}) $$这是一个3层的复合函数. 从这里可以清晰的看到，如果没有激活函数，整个函数将是一个线性函数：$$ W^{(3)} (W^{(2)} (W^{(1)}x + b^{(1)}) + b^{(2)}) + b^{(3)} $$因此对激活函数最基本的要求是必须是非线性的. 在早期，普遍使用的是sigmoid函数和tanh函数. sigmoid函数的计算公式为：$$ f(x) = \frac{1}{1 + exp(-x)} $$这个函数的图像为：tanh函数的计算公式为：$$ f(x) = \frac{1 - e^{-2x}}{1 + e^{-2x}} $$它的图像为：前者的值域为(0,1)，单调递增且有界；后者的值域为(-1,+1)，是一个中心对称的奇函数，同样也是单调递增且有界.2. 什么样的函数可以做激活函数前面已经说过，为保证非线性，激活函数必须为非线性函数，但仅仅具有非线性是不够的. 神经网络在本质上是一个复合函数，这会让我们思考一个问题：这个函数的建模能力有多强？即它能模拟什么样的目标函数？已经证明，只要激活函数选择得当，神经元个数足够多，使用3层即包含一个隐含层的神经网络就可以实现对任何一个从输入向量到输出向量的连续映射函数的逼近，这个结论称为万能逼近(universal approximation)定理. 万能逼近定理的表述为：如果$\varphi (x)$是一个非常数、有界、单调递增的连续函数，$I_m$ 是 m 维的单位立方体，$I_m$ 中的连续函数空间为 $C(I_m)$. 对于任意 $\epsilon > 0$以及函数$f \in C(I_m)$，存在整数 N，实数$v_i, b_i$，实向量$w_i \in R^m$，通过它们构造函数 $F(x)$作为函数 $f$ 的逼近：$$ F(\mathbf{x}) = \sum_{i=1}^N v_i \varphi (\mathbf{w}_i^T \mathbf{x} + b_i) $$对于任意的 $X \in I_m$ 满足：$$ |F(\mathbf{x}) - f(\mathbf{x})| < \epsilon $$万能逼近定理的直观解释是可以构造出上面这种形式的函数，逼近定义在单位立方体空间中的任何一个连续函数到任意指定的精度. 这个定理对激活函数的要求是必须非常数、有界、单调递增，并且连续. 文献[1]对使用 sigmoid 激活函数时的情况进行了证明：如果 $\sigma$ 是一个连续函数，并且满足下面条件：$$ \begin{matrix} lim_{x \rightarrow -\infty} \sigma(x) = 0 \\ lim_{x \rightarrow +\infty} \sigma(x) = 1 \end{matrix} $$则，函数族：$$ f(x) = \sum \alpha_i \sigma(\mathbf{w}_i^T \mathbf{x} + b_i) $$在空间 n 维…

Caffe - 网络结构 prototxt 可视化
type: item
date: 2018-12-01T16:01:55+08:00
description: Netscope 是对于 Caffe 网络结构可视化的很好的工具.Caffe 也有基于 Python 的网络结构可视化脚本，如：#!/usr/bin/env python #! --*-- coding:utf-8 --*-- """ Caffe prototxt 网络结构可视化 """ from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter from google.protobuf import text_format import sys sys.path.insert(0, '/path/to/caffe/python') import caffe import caffe.draw from caffe.proto import caffe_pb2 def parse_args(): """ 输入参数 """ parser = ArgumentParser(description=__doc__, formatter_class=ArgumentDefaultsHelpFormatter) # Caffe prototxt 网络定义文件 parser.add_argument('--prototxt', help='Input network prototxt file') # prototxt 训练或测试结构图 parser.add_argument('--phase', help='TRAIN, or TEST, or ALL', default="ALL") # 输出保存的网络结构图 parser.add_argument('--output_image', default='output_image.png', help='Output image file') # 网络结构的可视化方式，可选 'TB' 'LR'等. parser.add_argument('--vis_order', help='TB(top-bottom) or RL(right-left)', default='LR') # 可视化待学习网络层的学习率lr，multplier， # 如 Convolution, Deconvolution, InnerProduct 等网络层 parser.add_argument('--display_lrm', action='store_true', default=False) args = parser.parse_args() return args def main(): args = parse_args() print('[INFO] Prototxt %s' % args.prototxt) print('[INFO] Net phase %s' % args.phase) print('[INFO] Vis order %s' % args.vis_order) print('[INFO] Display lr and multplier %s' % args.display_lrm) print('[INFO] Output image %s' % args.output_image) assert args.phase in ['TRAIN', 'TEST', 'ALL'] net = caffe_pb2.NetParameter() text_format.Merge(open(args.prototxt).read(), net) print('[INFO] Drawing net to %s' % args.prototxt) caffe.draw.draw_net_to_file(net, args.output_image, args.vis_order, args.phase, args.display_lrm) print('[INFO] Done.') if __name__ == '__main__': main()输出结果如：

Caffe - Solver 网络优化方法
type: item
date: 2018-11-09T09:34:42+08:00
description: Caffe - SolverSolver 通过协调网络的前向推断(forward inference) 和后向梯度(backward gradients) 进行参数更新，以进行模型优化.网络学习可以分为：Solver 监督优化及参数更新和 Net 计算 loss 及梯度.Caffe 提供的 Solvers 有：Stochastic Gradient Descent (type: "SGD"),AdaDelta (type: "AdaDelta"),Adaptive Gradient (type: "AdaGrad"),Adam (type: "Adam"),Nesterov’s Accelerated Gradient (type: "Nesterov") andRMSprop (type: "RMSProp")Solver 的主要作用：[1] - 支持优化记录(bookkeeping)，并创建训练网络(learning)和测试网络(evaluation).[2] - 通过调用 forward/backward 进行迭代优化，并更新参数.[3] - 周期地评估(evaluation)测试网络.[4] - 保存优化过程中模型和优化器状态的快照.其中，对于每一次迭代，[1] - 调用 forward 计算网络的输出和 loss.[2] - 调用 backward 计算梯度.[3] - 根据优化算法，进行梯度计算，更新参数.[4] - 根据学习率，历史记录和优化方法更新优化器状态.Caffe Solvers 可以以 CPU 和 GPU 两种模式运行.Forward 计算：Backward 计算：1. 优化方法深度学习优化方法解决的问题是，最小化损失的一般问题.对于数据集 $D$，优化的目标是，对整个数据集 $|D|$ 求平均损失：$$ L(W) = \frac{1}{|D|} \sum _{i}^{|D|} f_W(X^{(i)}) + \lambda r(W) $$其中，$f_W(X^{(i)})$ 是对于数据样本 $X^{(i)}$ 的 损失值；$r(W)$ 是正则项，$\lambda$ 为其权重.在实际场景中，数据集样本总数 $|D | $ 会非常大，因此，在每次求解迭代中，一般采用小批量(mini-batch)数据 随机逼近目标 的方式，其中 $N << |D|$：$$ L(W) \approx \frac{1}{N} \sum _{i}^{N} f_W (X^{(i)}) + \lambda r(W) $$网络模型在 forward pass 中计算 $f_W$，在 backward pass 计算梯度 $\nabla f_W$.网络的参数更新 $\Delta W$ 是由求解器(solver) 根据误差梯度(error gradient) $\nabla f_W$，正则梯度 $\nabla r(W)$ 以及其它参数进行迭代更新.1.1 SGDSGD, Stochastic Gradient Descent，随机梯度下降法.SGD 是根据负梯度(negative gradient) $\nabla L(W)$和上次迭代的权重更新 $V_t$ 的线性组合来更新权重 $W$.学习率(learning rate) $\alpha$ 是负梯度 $\nabla L(W)$ 的权重；动量(momentum) $\mu$ 是上次权重更新 $V_t$ 的权重；记，计算的更新值为 $V_{t+1}$，在 $t + 1$ 次迭代的更新的权重为 $W_{t+1}$，在给定上次迭代的更新的权重为 $V_t$ 和当前权重 $W_t$ 时，其迭代公式为：$$ V_{t+1} = \mu V_t - \alpha \nabla L(W_t) $$$$ W_{t+1} = W_t + V_{t+1} $$学习率和动量设置的经验规则：深度学习中，推荐的 SGD 的参数设置是，初始化学习率为 $\alpha \approx 0.01 = 10 ^{-2}$，当损失函数减少逐渐变缓时，以一个常数因子(如，10)衰减，重复几次该过程.通常设置动量 $\mu = 0.9$ 等相似值. 通过在训练迭代时，平滑权重更新，动量能够使得 SGD 更平稳和快速.在 solver.prototxt 中的定义：base_lr: 0.01 # 学习率初始化值 lr_policy: "step" # 学习率策略，"steps" # 在每 stepsize 次迭代时， # 学习率以 gamma 因子衰减. gamma: 0.1 # 以 10 的因子衰减学习率， # 即，学习率乘以 gamma=0.1 stepsize:100000 # 每 100K 次迭代衰减学习率 max_iter:…

Caffe - 图片数据格式处理
type: item
date: 2018-06-04T16:09:00+08:00
description: Python 读取图片的库有很多， 如 opencv, skimage, PIL 等等， 但在送入 Caffe 网络前，需要的处理不大一样，不注意的情况下，容易弄混淆. 比如，通道转换，scale 范围，RGB次序等. Caffe 中彩色图片的颜色通道为 BGR, 像素值 scale 范围为 [0, 255].1. Caffe 自带的图片读取img = caffe.io.load_image(imagefile)采用的是 skimage.io.imread():def load_image(filename, color=True): """ 加载图片， Load an image converting from grayscale or alpha as needed. 参数： filename : 图片路径 color : boolean. 默认值 color=True， 表示 RGB 格式加载. color=False, 表示强度(intensity) 格式， 如果图片是灰度图. 返回值： image : an image with type np.float32 in range [0, 1] of size (H x W x 3) in RGB or of size (H x W x 1) in grayscale. """ img = skimage.img_as_float(skimage.io.imread(filename, as_grey=not color)).astype(np.float32) if img.ndim == 2: img = img[:, :, np.newaxis] if color: img = np.tile(img, (1, 1, 3)) elif img.shape[2] == 4: img = img[:, :, :3] return img可以看出， caffe.io.load_image() 基于 skimage.io.imread() 库读取图片， 得到的是 RGB 格式，像素值范围为 [0, 1] (float) 的. 因此，图片在送入网络前，采用以下处理：transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape}) transformer.set_transpose('data', (2, 0, 1)) # 如，由 227x227x3 变换为 3x227x227 transformer.set_mean('data', meandata) # 减均值 # transformer.set_mean('data', np.load(mean_file).mean(1).mean(1)) transformer.set_raw_scale('data', 255) # 像素值由 [0, 1] 缩放到 [0, 255] 范围 transformer.set_channel_swap('data', (2, 1, 0)) # 由 RGB 格式变换到 BGR 格式2. Opencv 图片读取import cv2 img = cv2.imread(imagefile) cv2.imread() 读取图片的格式是 BGR， 像素值范围为 [0, 255]. 相比于 caffe.io.load_image() 的图片读取，不需要再进行像素值范围缩放到 [0, 255], 通道变换也不需要再进行由 RGB 到 BGR 格式的转换. 即只需：transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape}) transformer.set_transpose('data', (2, 0, 1)) # 如，由 227x227x3 变换为 3x227x227 transformer.set_mean('data', meandata) # 减均值 # transformer.set_mean('data', np.load(mean_file).mean(1).mean(1))3. PIL 图片处理from PIL import Image img = Image.open(imagefile) PIL 对于 PNG, BMP, JPG 彩色图像格式的图片，Image.open() 均返回 RGB 图片. PIL 对于 PNG, BMP, JPG 灰度图像格式的图片，Image.open() 返回 L. 相比于 caffe.io.load_image() 的图片读取，不需要再进行像素值范围缩放到 [0, 255], 但是，…

Caffe - RoI Pooling 层
type: item
date: 2018-05-27T21:07:00+08:00
description: 目标检测中 RoI Pooling 层往往会被用到. 虽然 Mask R-CNN 提出了对像素更敏感的 RoI Align 层， 这里还是梳理下 RoI Pooling 层.1. caffe prototxt 中定义layer { name: "roi_pool5" type: "ROIPooling" bottom: "conv5" bottom: "rois" top: "pool5" roi_pooling_param { pooled_w: 6 pooled_h: 6 spatial_scale: 0.0625 # 1/16 } }2. caffe caffe.proto ROI Pooling 层参数说明ROI Pooling 层： optional ROIPoolingParameter roi_pooling_param = 43; message ROIPoolingParameter { // Pad, kernel size, and stride are all given as a single value for equal // dimensions in height and width or as Y, X pairs. optional uint32 pooled_h = 1 [default = 0]; // The pooled output height 池化后输出的 height optional uint32 pooled_w = 2 [default = 0]; // The pooled output width 池化后输出的 width // Multiplicative spatial scale factor to translate ROI coords from their // input scale to the scale used when pooling // 乘以空间缩放因子，以将 RoI 坐标由输入尺度转换到 pooling 时使用的尺度 optional float spatial_scale = 3 [default = 1]; }3. RoI Pooling 层作用原理及源码分析根据 prototxt 定义可以看出，roi_pool5 的输入有两个，[1] - bottom[0] 是 conv5 卷积层出来的 feature map，由于前面进行的 pool 层，conv5 的 feature map 的 height 和 width 分别是原图尺寸的 1/16. [2] - bottom[1] 是 rois blob， 其类似于一个 $num\_rois × 5$ 的二维矩阵，行数 num\_rosi 为 bottom[1]->num()，列数为 5，其定义为：input: "rois" input_shape { dim: 1 #to be changed on-the-fly to num ROIs dim: 5 #[batch_index, x1, y1, x2, y2] zero-based indexing }bottom_index 为每次在 bottom[0] 中的第一个维度的偏移量，[x1, y1, x2, y2] 是 feature map 中点的坐标.在 feature map 中，RoI Pooling层首先计算定义的 rois 在 conv feature map 上所映射的两个坐标，即，$x1 \times spatial\_scale, y1 \times spatial\_scale$ 和 $x2 \times spatial\_scale, y2 \times spatial\_scale$，其对应的点为 [top, left], [bottom, right]，即在 feature map 中确定一个区域. 对于确定的一个区域，进行 pooled\_h x pooled\_w 等分，这里是划分为 6x6=36 个相同大小的子区域, 区域大小为 bin\_h = roi_h / pooled\_h, bin\_w = roi\_w / pooled\_w；对于每个子区域，采用 max 操作找出对应 feature map 的最大值，即为输出 top blob 的对应值.对于 bottom[0] 的每个 channel 进行相同操作.roi_pool5 有一个输出 top[0]，其尺寸为 [bottom[1] -> num( ), bottom[0] -> channels, pooled\_h, pooled\_w]，其中，pooled_h 和 pooled_w 是固定定义的，其值这里为 6. 根据其源码 roi_p…

GPU优化 - Caffe 显存优化[译]
type: item
date: 2018-05-21T10:12:37+08:00
description: Caffe - GPU显存优化 原文 - Squeezing Memory out of Caffe memory multiloading Memory Optimization1. Caffe - memory multiloading 功能原文 - Squeezing Memory out of CaffeCaffe 的一个优化版本 - caffe-yjxiong. 测试了一下, ResNet101 相对于官方 caffe, 能够明显节省显存占用, batchsize 可以增加很多.显存优化的 Caffe 主要实现的功能: memory multiloading在深度网络训练和测试时,可以显著地节省内存.训练时,节省一半内存;测试时, 使用 95% 的内存.该内存优化的核心思想比较简单:如果有些消费者请求一些资源, 但他们并不需要同时请求所有的资源. 一种理想情况是, 只让他们在他们真正需要的时候再使用资源, 并在结束时返回资源. 在训练深度网络时, GPU显存资源是有限的. 资源消费者即是网络中的网络层(layers/operations), 在 GPU 保存着训练时的中间结果(intermediate results).例如, 采用标准 BP 算法训练 CNNs 时, 最主要的一种内存消耗是, 保存中间激活值和其梯度(也叫作敏感度sensitivities)( intermediate activation values and the gradients values w.r.t. intermediate activation).如果将一次 forward/backward 迭代看做一个周期(cycle), 那么一个重要发现是: 在整个周期中不是所有的值都需要保存的.具体来说, 对于单个网络层, 在 feed-forwad 过程中, 当得到该网络层的输出值后, 则网络层的输入激活值就没用了. 在 BP 过程中, 当得到网络层参数的梯度和输入敏感度(input sensitivities) 后, 则网络层的输入激活值和敏感度sensitivities 就没用了. 也就是说, 每个内存块只有在计算时才占用物理内存； 且, 不再使用时, 就可以释放物理内存.内存优化核心思想的实现可以共享存储资源并节省内存. 在 TensorFlow 或 Parrots 等深度框架中, 其主要是通过动态调度一个预先分配的内存池来实现的, 类似于 OS 对于物理 RAM 的调度(dynamically scheduling a pre-allocated memory pool as the OS does with the physical RAM). 但是, 在 Caffe 中, 如何实现呢? 由于 Caffe 具有完全不同的内存模型, 其每个内存块是由 OS/GPU 来自动分配的. 在内存池中实现类似功能是比较棘手的.相反地, 采用了一种替代方法. 假设网络结构是固定的(大部分情况都是这样), 在运行第一次迭代前, 来判断一个内存块的生命周期(life-cycle). 这里称之为 "dry-run", 即, 模拟计算流以寻找可以共享存储的 SyncedMem-s(Caffe 中的基本内存单元). 当找出 SyncedMem-s 后, 即可将其分配到单个内存插槽(single memory slot). 在实际训练和测试时, memory multiloading 的实现是将共享 SyncedMem-s 都由相同内存块来存储和读取的. 由于在 "dry-run" 中已经确定了依赖关系, multiloading 是不会损坏数据, 且能节省大量内存使用的.最后, 在实现 Caffe 的 memory multiloading 功能时, 发现的一个有意思的问题. Caffe 有一些网络层不进行计算, 只共享网络层的输入和输出 blobs(ShareData/ShareDiff). 这种情况在某些时候可能破坏 "dry-run" 处理所获得的依赖关系. 因此, 需要先让这些网络层告诉框架网络层所做的事情( first make these layers tell the framework that they doing this.) 在某些情况, 这些网络层是堆叠在一起以构建特定的结构. 由于会引入递归分享(recursive sharing, 一系列的 blobs 共享其激活值/梯度内存块), 导致问题更加棘手. 最终, 采用的方案是, 著名的 并查集(或不相交集合)数据结构( "union-find" (disjoint-set) data structure) 来使 memory multiloading 的实现更加干净和优雅.me…

Caffe - 训练时Loss变为nan的原因[转]
type: item
date: 2018-05-18T17:19:00+08:00
description: From 使用caffe训练时Loss变为nan的原因(https://mp.weixin.qq.com/s/LDaHvcwOnx80EKhP2sx2Yg)From Deep Learning 之 训练过程中出现NaN问题(http://blog.csdn.net/BVL10101111/article/details/76086344)1. 梯度爆炸原因：梯度变得非常大，使得学习过程难以继续现象：观察log，注意每一轮迭代后的loss。loss随着每轮迭代越来越大，最终超过了浮点型表示的范围，就变成了NaN。措施： 减小solver.prototxt中的base_lr，至少减小一个数量级。如果有多个loss layer，需要找出哪个损失层导致了梯度爆炸，并在train_val.prototxt中减小该层的loss_weight，而非是减小通用的base_lr.3设置clip gradient，用于限制过大的diff2. 不当的损失函数原因：有时候损失层中loss的计算可能导致NaN的出现。比如，给InfogainLoss层（信息熵损失）输入没有归一化的值，使用带有bug的自定义损失层等等。现象：观测训练产生的log时一开始并不能看到异常，loss也在逐步的降低，但突然之间NaN就出现了。措施：看看你是否能重现这个错误，在loss layer中加入一些输出以进行调试。示例：有一次我使用的loss归一化了batch中label错误的次数。如果某个label从未在batch中出现过，loss就会变成NaN。在这种情况下，可以用足够大的batch来尽量避免这个错误。3. 不当的输入原因：输入中就含有NaN。现象：每当学习的过程中碰到这个错误的输入，就会变成NaN。观察log的时候也许不能察觉任何异常，loss逐步的降低，但突然间就变成NaN了。措施：重整你的数据集，确保训练集和验证集里面没有损坏的图片。调试中你可以使用一个简单的网络来读取输入层，有一个缺省的loss，并过一遍所有输入，如果其中有错误的输入，这个缺省的层也会产生NaN。案例：有一次公司需要训练一个模型，把标注好的图片放在了七牛上，拉下来的时候发生了dns劫持，有一张图片被换成了淘宝的购物二维码，且这个二维码格式与原图的格式不符合，因此成为了一张“损坏”图片。每次训练遇到这个图片的时候就会产生NaN。良好的习惯是，你有一个检测性的网络，每次训练目标网络之前把所有的样本在这个检测性的网络里面过一遍，去掉非法值。4. 池化层中步长比核的尺寸大如下例所示，当池化层中stride > kernel的时候会在y中产生NaNlayer { name: "faulty_pooling" type: "Pooling" bottom: "x" top: "y" pooling_param { pool: AVE stride: 5 kernel: 3 } } 致谢：http://stackoverflow.com/questions/33962226/common-causes-of-NaNs-during-training一般来说，出现NaN有以下几种情况：1.如果在迭代的100轮以内，出现NaN，一般情况下的原因是因为你的学习率过高，需要降低学习率。可以不断降低学习率直至不出现NaN为止，一般来说低于现有学习率1-10倍即可。2.如果当前的网络是类似于RNN的循环神经网络的话，出现NaN可能是因为梯度爆炸的原因，一个有效的方式是增加“gradient clipping”（梯度截断来解决）3.可能用0作为了除数;4.可能0或者负数作为自然对数5.需要计算loss的数组越界（尤其是自己，自定义了一个新的网络，可能出现这种情况）6.在某些涉及指数计算，可能最后算得值为INF（无穷）（比如不做其他处理的softmax中分子分母需要计算exp（x），值过大，最后可能为INF/INF，得到NaN，此时你要确认你使用的softmax中在计算exp（x）做了相关处理（比如减去最大值等等））参考： http://russellsstewart.com/notes/0.html

Caffe - 基于 Python 创建LMDB/HDF5格式数据
type: item
date: 2018-05-18T17:04:00+08:00
description: Caffe 在图像分类模型的训练时, 效率起见, 未直接从图片列表读取图片, 训练数据往往是采用 LMDB 或 HDF5 格式.LMDB格式的优点：基于文件映射IO（memory-mapped），数据速率更好对大规模数据集更有效.HDF5的特点：易于读取类似于mat数据，但数据压缩性能更强需要全部读进内存里，故HDF5文件大小不能超过内存，可以分成多个HDF5文件，将HDF5子文件路径写入txt中.I/O速率不如LMDB.1. LMDB创建import lmdb import caffe lmdb_file = '/path/to/data_lmdb' N = 1000 # 准备 data 和 labels X = np.zeros((N, 3, 224, 224), dtype=np.uint8) # data y = np.zeros(N, dtype=np.int64) # labels env = lmdb.open(lmdb_file, map_size=int(1e12)) txn = env.begin(write=True) for i in range(N): datum = caffe.proto.caffe_pb2.Datum() datum.channels = X.shape[1] datum.height = X.shape[2] datum.width = X.shape[3] datum.data = X[i].tobytes() # or .tostring() if numpy < 1.9 datum.label = int(y[i]) # 以上五行也可以直接： datum = caffe.io.array_to_datum(data, label) str_id = '{:08}'.format(i) txn.put(str_id, datum.SerializeToString()) # in Python3 # txn.put(str_id.encode('ascii'), datum.SerializeToString())2. LMDB读取import numpy as np import lmdb import caffe env = lmdb.open('data_lmdb', readonly=True) txn = env.begin() lmdb_cursor = txn.cursor() datum = caffe.proto.caffe_pb2.Datum() for key, value in lmdb_cursor: print '{},{}'.format(key, value) datum.ParseFromString(value) flat_data = np.fromstring(datum.data, dtype=np.uint8) data = flat_data.reshape(datum.channels, datum.height, datum.width) # 或 data = caffe.io.datum_to_array(datum) labels = datum.label3. HDF5创建和读取W1:import h5py import numpy as np # 创建HDF5文件 imgsData = np.zeros((10,3,224,224)) # Images labels = range(10) # Labels f = h5py.File('HDF5_FILE.h5','w') # 创建一个h5文件 f['datas'] = imgsData # 写入Images数据 f['labels'] = labels # 写入Labels数据 f.close() # # 读取HDF5文件 f = h5py.File('HDF5_FILE.h5','r') # 打开h5文件 f_keys = f.keys() imgsData = f['datas'][:] labels = f['labels'][:] f.close() W2:import h5py datas = np.random.rand(100, 1000, 1000).astype('float32') labels = np.random.rand(1, 1000, 1000).astype('float32') # Create a new file f = h5py.File('data.h5', 'w') f.create_dataset('datas', data=datas) f.create_dataset('labels', data=labels)…