├── .gitignore ├── python ├── caffe_helper │ ├── layers │ │ ├── __init__.py │ │ ├── vision_layers.py │ │ ├── common_layers.py │ │ ├── loss_layers.py │ │ └── data_layers.py │ ├── obsolete.py │ ├── rand_seed.py │ ├── theano_util.py │ ├── visualize.py │ ├── __init__.py │ ├── tools.py │ └── proto_creator.py └── test │ ├── test_data_layers.py │ ├── test_vision_layers.py │ ├── conftest.py │ ├── test_loss_layers.py │ └── test_common_layers.py ├── model_templates ├── image_data_layer.macro ├── scalar_data_layer.macro ├── sample_vgg16.prototxt.jinja2 ├── sample.prototxt.jinja2 ├── upsampling.macro ├── bvlc_cnn.macro ├── vgg_cnn.macro ├── data_layer.macro ├── sample2.prototxt.jinja2 ├── bvlc_fcs.macro ├── solver.prototxt.jinja2 ├── sample3.prototxt.jinja2 ├── prelu_fc_unit.macro ├── bvlc_convolutions.macro ├── vgg_convs.macro ├── fc_unit.macro ├── vgg_conv_unit.macro ├── vgg_prelu_conv_unit.macro └── conv_unit.macro ├── README.md └── script └── caffex.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *~ 4 | sandbox/ -------------------------------------------------------------------------------- /python/caffe_helper/layers/__init__.py: -------------------------------------------------------------------------------- 1 | import data_layers 2 | -------------------------------------------------------------------------------- /python/caffe_helper/obsolete.py: -------------------------------------------------------------------------------- 1 | 2 | def get_iter_from_path(pathi): 3 | return int(pathi.split('_')[-1].split('.')[0]) 4 | -------------------------------------------------------------------------------- /python/caffe_helper/rand_seed.py: -------------------------------------------------------------------------------- 1 | # rand_seed.py 2 | import os 3 | 4 | envseed = os.environ.get('TNARIHI_CAFFE_HELPER_SEED', None) 5 | if envseed is not None: 6 | if envseed == 'rand': 7 | import time 8 | seed = int(time.time()) 9 | del time 10 | else: 11 | seed = int(envseed) 12 | else: 13 | seed = None 14 | -------------------------------------------------------------------------------- /model_templates/image_data_layer.macro: -------------------------------------------------------------------------------- 1 | {% macro image_data_layer(name, phase_train=true, param_str='{}') %} 2 | layer { 3 | name: "{{name}}" 4 | type: "Python" 5 | top: "{{name}}" 6 | python_param { 7 | module: "caffe_helper.layers.data_layers" 8 | layer: "ImageDataLayer" 9 | param_str: "{{param_str}}" 10 | } 11 | include: { phase: {% if phase_train %}TRAIN{%else%}TEST{%endif%} } 12 | } 13 | {% endmacro %} 14 | -------------------------------------------------------------------------------- /model_templates/scalar_data_layer.macro: -------------------------------------------------------------------------------- 1 | {% macro scalar_data_layer(name, phase_train=true, param_str) %} 2 | layer { 3 | name: "{{name}}" 4 | type: "Python" 5 | top: "{{name}}" 6 | python_param { 7 | module: "caffe_helper.layers.data_layers" 8 | layer: "ScalarDataLayer" 9 | param_str: "{{param_str}}" 10 | } 11 | include: { phase: {% if phase_train %}TRAIN{%else%}TEST{%endif%} } 12 | } 13 | {% endmacro %} 14 | -------------------------------------------------------------------------------- /model_templates/sample_vgg16.prototxt.jinja2: -------------------------------------------------------------------------------- 1 | {% from 'vgg_convs.macro' import vgg_convs %} 2 | {% from 'bvlc_fcs.macro' import bvlc_fcs %} 3 | {% from 'fc_unit.macro' import fc_unit %} 4 | name: "sample_vgg16" 5 | 6 | input: "data" 7 | input_dim: 10 8 | input_dim: 3 9 | input_dim: 224 10 | input_dim: 224 11 | 12 | 13 | {{vgg_convs('data')}} 14 | {{bvlc_fcs('conv5-pool')}} 15 | {{ 16 | fc_unit('fc7', 'fc8', num=1000, 17 | w_filler={'type': '"gaussian"', 'std': 0.01}, 18 | nonlin_type='Softmax') 19 | }} -------------------------------------------------------------------------------- /model_templates/sample.prototxt.jinja2: -------------------------------------------------------------------------------- 1 | {% from 'bvlc_convolutions.macro' import bvlc_convolutions %} 2 | {% from 'bvlc_fcs.macro' import bvlc_fcs %} 3 | {% from 'fc_unit.macro' import fc_unit %} 4 | name: "bvlc_sample" 5 | 6 | input: "data" 7 | input_dim: 10 8 | input_dim: 3 9 | input_dim: 227 10 | input_dim: 227 11 | 12 | {{bvlc_convolutions('data')}} 13 | {{bvlc_fcs('conv5-pool')}} 14 | {{ 15 | fc_unit('fc7', 'fc8', num=1000, 16 | w_filler={'type': '"gaussian"', 'std': 0.01}, 17 | nonlin_type='Softmax') 18 | }} 19 | -------------------------------------------------------------------------------- /model_templates/upsampling.macro: -------------------------------------------------------------------------------- 1 | {% macro upsampling(name, bottom, top, factor, channels) %} 2 | layer { 3 | name: "{{name}}", type: "Deconvolution" 4 | bottom: "{{bottom}}" top: "{{top}}" 5 | convolution_param { 6 | kernel_size: {{(2 * factor - factor % 2)|int}} stride: {{factor|int}} 7 | num_output: {{channels}} group: {{channels}} 8 | pad: {{((factor - 1) / 2.0)|round(method='ceil')|int}} 9 | weight_filler: { type: "bilinear_upsampling" } bias_term: false 10 | } 11 | param { lr_mult: 0 decay_mult: 0 } 12 | } 13 | {% endmacro %} 14 | -------------------------------------------------------------------------------- /model_templates/bvlc_cnn.macro: -------------------------------------------------------------------------------- 1 | {% from 'bvlc_convolutions.macro' import bvlc_convolutions %} 2 | {% from 'bvlc_fcs.macro' import bvlc_fcs %} 3 | {% from 'fc_unit.macro' import fc_unit %} 4 | {% macro bvlc_cnn(input_name, prefix='', param_prefix='', no_fc8=false) %} 5 | {{bvlc_convolutions(input_name, prefix, param_prefix)}} 6 | {{bvlc_fcs(prefix + 'conv5-pool', prefix, param_prefix)}} 7 | {% if not no_fc8 %}{{ 8 | fc_unit('fc7', 'fc8', num=1000, 9 | w_filler={'type': '"gaussian"', 'std': 0.01}, 10 | b_filler={'type': '"constant"', 'value': 0}, 11 | nonlin_type='Softmax') 12 | }}{% endif %} 13 | {% endmacro %} 14 | -------------------------------------------------------------------------------- /model_templates/vgg_cnn.macro: -------------------------------------------------------------------------------- 1 | {% from 'vgg_convs.macro' import vgg_convs %} 2 | {% from 'bvlc_fcs.macro' import bvlc_fcs %} 3 | {% from 'fc_unit.macro' import fc_unit %} 4 | {% macro vgg_cnn(input_name, prefix='', param_prefix='', no_fc8=false, 5 | fc_lr=[1, 1]) %} 6 | {{vgg_convs(input_name, prefix, param_prefix)}} 7 | {{bvlc_fcs(prefix + 'conv5-pool', prefix, param_prefix, lr=fc_lr)}} 8 | {% if not no_fc8 %}{{ 9 | fc_unit('fc7', 'fc8', num=1000, 10 | w_filler={'type': '"gaussian"', 'std': 0.01}, 11 | b_filler={'type': '"constant"', 'value': 0}, 12 | nonlin_type='Softmax') 13 | }}{% endif %} 14 | {% endmacro %} 15 | -------------------------------------------------------------------------------- /model_templates/data_layer.macro: -------------------------------------------------------------------------------- 1 | {% macro data_layer(source, batch_size, prefix, phase_train=true, 2 | backend='LMDB', crop_size=none, mirror=false, mean_str=none) 3 | %} 4 | layer { 5 | name: "{{prefix}}data" 6 | type: "Data" 7 | top: "{{prefix}}data" 8 | top: "{{prefix}}label" 9 | data_param { 10 | source: "{{source}}" 11 | backend: {{backend}} 12 | batch_size: {{batch_size}} 13 | } 14 | transform_param { 15 | {% if crop_size is not none %}crop_size: {{crop_size}}{%endif%} 16 | {% if mean_str is none %} 17 | mean_value: 104 18 | mean_value: 117 19 | mean_value: 123 20 | {% else %} 21 | {{mean_str}} 22 | {% endif %} 23 | mirror: {{mirror|bool2str}} 24 | } 25 | include: { phase: {% if phase_train %}TRAIN{%else%}TEST{%endif%} } 26 | } 27 | {% endmacro %} 28 | -------------------------------------------------------------------------------- /python/caffe_helper/theano_util.py: -------------------------------------------------------------------------------- 1 | 2 | theano_initialized = False 3 | 4 | def init_theano(): 5 | """Initialize Theano for Caffe 6 | """ 7 | global theano_initialized 8 | if theano_initialized: 9 | return 10 | import caffe 11 | from theano.sandbox.cuda import use 12 | assert caffe.check_mode_gpu() 13 | use('gpu%d' % caffe.get_device()) 14 | theano_initialized = True 15 | 16 | def blob_to_CudaNdArray(b, diff=False): 17 | from theano.sandbox import cuda 18 | data_ptr = long(b.gpu_data_ptr) 19 | diff_ptr = long(b.gpu_diff_ptr) 20 | strides = tuple() 21 | if len(b.shape) > 0: 22 | strides = [1] 23 | for i in b.shape[::-1][:-1]: 24 | strides.append(strides[-1]*i) 25 | strides = tuple(strides[::-1]) 26 | return cuda.from_gpu_pointer(data_ptr, b.shape, strides, b), \ 27 | cuda.from_gpu_pointer(diff_ptr, b.shape, strides, b) 28 | -------------------------------------------------------------------------------- /model_templates/sample2.prototxt.jinja2: -------------------------------------------------------------------------------- 1 | {% from 'bvlc_convolutions.macro' import bvlc_convolutions %} 2 | {% from 'bvlc_fcs.macro' import bvlc_fcs %} 3 | {% from 'fc_unit.macro' import fc_unit %} 4 | name: "bvlc_sample" 5 | 6 | input: "data" 7 | input_dim: 10 8 | input_dim: 3 9 | input_dim: 227 10 | input_dim: 227 11 | 12 | {% macro bvlc_cnn(input_name, prefix='', param_prefix='') %} 13 | {{bvlc_convolutions(input_name, prefix, param_prefix)}} 14 | {{bvlc_fcs(prefix + 'conv5-pool', prefix, param_prefix)}} 15 | {% endmacro %} 16 | {{bvlc_cnn('data')}} 17 | {{bvlc_cnn('data', 'p_')}} 18 | layer { 19 | name: "sub" 20 | bottom: "fc7" 21 | bottom: "p_fc7" 22 | top: "sub" 23 | type: "Eltwise" 24 | eltwise_param { 25 | operation: SUM 26 | coeff: 1 27 | coeff: 1 28 | } 29 | } 30 | {{ 31 | fc_unit('sub', 'sub-fc8', num=1000, 32 | w_filler={'type': '"gaussian"', 'std': 0.01}, 33 | nonlin_type=none) 34 | }} 35 | -------------------------------------------------------------------------------- /model_templates/bvlc_fcs.macro: -------------------------------------------------------------------------------- 1 | {% from 'fc_unit.macro' import fc_unit %} 2 | 3 | {% macro bvlc_fcs( 4 | input_name, 5 | prefix='', 6 | param_prefix='', 7 | lr=[1, 1], 8 | wd=[1, 1], 9 | num=[4096, 4096], 10 | w_std=[0.005,0.005], 11 | b_value=[1, 1], 12 | nonlin_type=['ReLU', 'ReLU'], 13 | dropout_ratio=[0.5, 0.5], 14 | start=6, last=7) 15 | 16 | %} 17 | {% for j in range(start-1, last) %} 18 | {% set i = j - (start-1) %} 19 | {% set fc_name = 'fc%d'|format(j+1) %} 20 | {% set prev_fc_name = 'fc%d'|format(j) %} 21 | {% if i == 0 %}{% set input = input_name %} 22 | {% else %}{% set input = prefix + prev_fc_name %} 23 | {% endif %} 24 | {{ 25 | fc_unit( 26 | input, prefix + fc_name, 27 | lr_w=lr[i]*1, lr_b=lr[i]*2, wd_w=wd[i], 28 | num=num[i], 29 | w_filler={"type": '"gaussian"', "std": w_std[i]}, 30 | b_filler={"type": '"constant"', "value": b_value[i]}, 31 | param_name=param_prefix + fc_name, nonlin_type=nonlin_type[i], 32 | dropout_ratio=dropout_ratio[i]) 33 | }} 34 | {% endfor %} 35 | {% endmacro %} 36 | -------------------------------------------------------------------------------- /model_templates/solver.prototxt.jinja2: -------------------------------------------------------------------------------- 1 | {# 2 | params: net, snapshot_prefix, max_iter 3 | optional params: 4 | test_iter=200, test_interval=1000, base_lr=0.001, display=50, 5 | momentum=0.9, weight_decay=0.000001, snapshot=1000, debug_info=false 6 | #} 7 | net: "{{net}}" 8 | {% if test_iter|d(none) is not none %}test_iter: {{ test_iter }}{%endif%} 9 | {% if test_interval|d(none) is not none %}test_interval: {{ test_interval }}{%endif%} 10 | base_lr: {{ base_lr|d(0.001) }} 11 | display: {{ display|d(50) }} 12 | average_loss: {{ display|d(50) }} 13 | max_iter: {{ max_iter }} 14 | momentum: {{ momentum|d(0.9) }} 15 | weight_decay: {{ weight_decay|d(0.000001) }} 16 | snapshot: {{ snapshot|d(1000) }} 17 | snapshot_prefix: "{{snapshot_prefix}}" 18 | lr_policy: "{{lr_policy|d("fixed")}}" 19 | power: {{power|d(0.5)}} 20 | {% if debug_info|d(false) %}debug_info: true{% endif %} 21 | {% set accum_grad = accum_grad|d(1) %} 22 | {% if accum_grad > 1 %}iter_size: {{ accum_grad }}{% endif %} 23 | {% if share_blobs|d(true) %}share_blobs: true{%endif%} 24 | {% if force_cpu_momentum|d(false) %}force_cpu_momentum: true{% endif %} 25 | -------------------------------------------------------------------------------- /python/caffe_helper/visualize.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def blob_to_tile(blob, padsize=1, padval=0): 5 | """ 6 | take an array of shape (n, channels, height, width) 7 | and visualize each (height, width) thing in a grid of size approx. sqrt(n) by sqrt(n) 8 | """ 9 | assert(blob.ndim == 4) 10 | if blob.shape[1] != 3: 11 | blob = blob.reshape((-1, 1) + blob.shape[2:]) 12 | blob = blob.transpose(0, 2, 3, 1) 13 | 14 | blob = blob - blob.min() # copy 15 | blob /= blob.max() 16 | 17 | # force the number of filters to be square 18 | n = int(np.ceil(np.sqrt(blob.shape[0]))) 19 | padding = ( 20 | (0, n ** 2 - blob.shape[0]), 21 | (0, padsize), 22 | (0, padsize) 23 | ) + ((0, 0),) * (blob.ndim - 3) 24 | blob = np.pad( 25 | blob, padding, mode='constant', constant_values=(padval, padval)) 26 | 27 | # tile the filters into an image 28 | blob = blob.reshape( 29 | (n, n) 30 | + blob.shape[1:] 31 | ).transpose((0, 2, 1, 3) + tuple(range(4, blob.ndim + 1))) 32 | blob = blob.reshape( 33 | (n * blob.shape[1], n * blob.shape[3]) + blob.shape[4:]) 34 | if blob.shape[2] == 1: 35 | return blob.reshape((blob.shape[:2])) 36 | return blob 37 | -------------------------------------------------------------------------------- /model_templates/sample3.prototxt.jinja2: -------------------------------------------------------------------------------- 1 | {% from 'conv_unit.macro' import conv_unit %} 2 | name: "bvlc_sample" 3 | 4 | input: "data" 5 | input_dim: 10 6 | input_dim: 3 7 | input_dim: 227 8 | input_dim: 227 9 | 10 | {% set input = 'data' %} 11 | {% set filtered = 'data' %} 12 | {% for i in range(5) %} 13 | {% set conv_name = 'conv%d'|format(i+1) %} 14 | {% set nin_name = 'nin%d'|format(i+1) %} 15 | {% set nin2_name = 'nin2-%d'|format(i+1) %} 16 | {% set filtered_name = 'sub%d'|format(i+1) %} 17 | {{ 18 | conv_unit(input, conv_name, 19 | channels=64, kernel=5, pad=2, 20 | param_name='conv', 21 | no_pool=true, no_norm=true) 22 | }} 23 | {{ 24 | conv_unit(conv_name, nin_name, 25 | channels=32, kernel=1, group=2, 26 | param_name='nin', 27 | no_pool=true, no_norm=true) 28 | }} 29 | {{ 30 | conv_unit(nin_name, nin2_name, 31 | channels=3, kernel=1, 32 | param_name='nin2', 33 | no_pool=true, no_norm=true, nonlin_type=none) 34 | }} 35 | layer { 36 | name: "{{filtered_name}}" 37 | type: "Eltwise" 38 | bottom: "{{nin2_name}}" 39 | bottom: "{{filtered}}" 40 | top: "{{filtered_name}}" 41 | eltwise_param { 42 | operation: SUM 43 | coeff: 1 44 | coeff: 1 45 | } 46 | } 47 | {% set input = filtered_name %} 48 | {% set filtered = filtered_name %} 49 | {% endfor %} 50 | -------------------------------------------------------------------------------- /model_templates/prelu_fc_unit.macro: -------------------------------------------------------------------------------- 1 | {% macro prelu_fc_unit( 2 | input_name, unit_name, 3 | lr_w=1, lr_b=1, wd_w=1, wd_b=0, 4 | w_std=0.01, 5 | num=64, 6 | bias_term=true, 7 | param_name=none, nonlin_type='PReLU', 8 | dropout_ratio=-1) 9 | %} 10 | {% set fc_name = unit_name %} 11 | {% set nonlin_name = unit_name + '-nonlin' %} 12 | {% set drop_name = unit_name + '-drop' %} 13 | layer { 14 | name: "{{fc_name}}" 15 | type: "InnerProduct" 16 | bottom: "{{input_name}}" 17 | top: "{{fc_name}}" 18 | param { 19 | lr_mult: {{ lr_w }} 20 | decay_mult: {{ wd_w }} 21 | {% if param_name is not none %}name: "{{ param_name }}_w"{%endif%} 22 | } 23 | param { 24 | lr_mult: {{ lr_b }} 25 | decay_mult: {{ wd_b }} 26 | {% if param_name is not none %}name: "{{ param_name }}_b"{%endif%} 27 | } 28 | inner_product_param { 29 | num_output: {{num}} 30 | weight_filler { 31 | type: "gaussian" 32 | std: {{w_std}} 33 | } 34 | bias_term: {{bias_term|bool2str}} 35 | } 36 | } 37 | {% if nonlin_type is not none %} 38 | layer { 39 | name: "{{nonlin_name}}" 40 | type: "{{nonlin_type}}" 41 | bottom: "{{fc_name}}" 42 | top: "{{fc_name}}" 43 | } 44 | {% endif %} 45 | {% if dropout_ratio > 0 %} 46 | layer { 47 | name: "{{drop_name}}" 48 | type: "Dropout" 49 | bottom: "{{fc_name}}" 50 | top: "{{fc_name}}" 51 | dropout_param { 52 | dropout_ratio: {{dropout_ratio}} 53 | } 54 | } 55 | {% endif %} 56 | {% endmacro %} -------------------------------------------------------------------------------- /model_templates/bvlc_convolutions.macro: -------------------------------------------------------------------------------- 1 | {% from 'conv_unit.macro' import conv_unit %} 2 | 3 | {% macro bvlc_convolutions( 4 | input_name, 5 | prefix='', 6 | param_prefix='', 7 | lr=[1, 1, 1, 1, 1], 8 | wd=[1, 1, 1, 1, 1], 9 | channels=[96, 256, 384, 384, 256], 10 | kernel=[11, 5, 3, 3, 3], 11 | stride=[4, 1, 1, 1, 1], 12 | pad=[0, 2, 1, 1, 1], 13 | group=[1, 2, 1, 2, 2], 14 | w_std=[0.01,0.01,0.01,0.01,0.01], 15 | b_value=[0, 1, 0, 0, 1], 16 | pool_kernel=[3, 3, 0, 0, 3], 17 | pool_stride=[2, 2, 0, 0, 2], 18 | no_pool=[false, false, true, true, false], 19 | no_norm=[false, false, true, true, true], 20 | start=1, last=5) 21 | %} 22 | {% for j in range(start-1, last) %} 23 | {% set i = j - (start-1) %} 24 | {% set conv_name = 'conv%d'|format(j+1) %} 25 | {% set prev_conv_name = 'conv%d'|format(j) %} 26 | {% if i == 0 %}{% set input = input_name %} 27 | {% elif no_norm[i-1] and no_pool[i-1] %}{% set input = prefix + prev_conv_name %} 28 | {% elif no_norm[i-1] %}{% set input = prefix + prev_conv_name + '-pool' %} 29 | {% else %}{% set input = prefix + prev_conv_name + '-norm' %} 30 | {% endif %} 31 | {{ 32 | conv_unit( 33 | input, prefix + conv_name, 34 | lr_w=lr[i]*1, lr_b=lr[i]*2, wd_w=wd[i], 35 | channels=channels[i], kernel=kernel[i], stride=stride[i], 36 | group=group[i], pad=pad[i], 37 | w_filler={"type": '"gaussian"', "std": w_std[i]}, 38 | b_filler={"type": '"constant"', "value": b_value[i]}, 39 | param_name=param_prefix + conv_name, 40 | pool_kernel=pool_kernel[i], pool_stride=pool_stride[i], 41 | no_pool=no_pool[i], no_norm=no_norm[i]) 42 | }} 43 | {% endfor %} 44 | {% endmacro %} 45 | -------------------------------------------------------------------------------- /model_templates/vgg_convs.macro: -------------------------------------------------------------------------------- 1 | {% from 'vgg_conv_unit.macro' import vgg_conv_unit %} 2 | 3 | {% macro vgg_convs( 4 | input_name, 5 | prefix='', 6 | param_prefix='', 7 | lr=[1, 1, 1, 1, 1], 8 | wd=[1, 1, 1, 1, 1], 9 | channels=[64, 128, 256, 512, 512], 10 | kernel=[3, 3, 3, 3, 3], 11 | stride=[1, 1, 1, 1, 1], 12 | pad=[1, 1, 1, 1, 1], 13 | group=[1, 1, 1, 1, 1], 14 | w_std=[0.01,0.01,0.01,0.01,0.01], 15 | b_value=[0, 0, 0, 0, 0], 16 | pool_kernel=[2, 2, 2, 2, 2], 17 | pool_stride=[2, 2, 2, 2, 2], 18 | num_conv=[2, 2, 3, 3, 3], 19 | nonlin_type=['ReLU', 'ReLU', 'ReLU', 'ReLU', 'ReLU'], 20 | pool_type=['MAX', 'MAX', 'MAX', 'MAX', 'MAX'], 21 | start=1, last=5) 22 | %} 23 | {% for j in range(start-1, last) %} 24 | {% set i = j - (start-1) %} 25 | {% set conv_name = 'conv%d'|format(j+1) %} 26 | {% set prev_conv_name = 'conv%d'|format(j) %} 27 | {% if i == 0 %} 28 | {% set input = input_name %} 29 | {% elif pool_type[i-1] is none %} 30 | {% set input = prefix + prev_conv_name + '_%d'|format(num_conv[i-1]) %} 31 | {% else %} 32 | {% set input = prefix + prev_conv_name + '-pool' %} 33 | {% endif %} 34 | {{ 35 | vgg_conv_unit( 36 | input, prefix + conv_name, 37 | lr_w=lr[i]*1, lr_b=lr[i]*2, wd_w=wd[i], 38 | channels=channels[i], kernel=kernel[i], stride=stride[i], 39 | group=group[i], pad=pad[i], 40 | w_filler={"type": '"gaussian"', "std": w_std[i]}, 41 | b_filler={"type": '"constant"', "value": b_value[i]}, 42 | param_prefix=param_prefix + conv_name, 43 | pool_kernel=pool_kernel[i], pool_stride=pool_stride[i], 44 | num_conv=num_conv[i], 45 | pool_type=pool_type[i], nonlin_type=nonlin_type[i]) 46 | }} 47 | {% endfor %} 48 | {% endmacro %} 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Caffe helper 2 | 3 | This repositry provides helper tools and additional layers for BVLC/caffe. 4 | Basically it is developed for my own use, so documentation is quite poor and 5 | codes are not so clean. 6 | 7 | ## Set up 8 | 9 | ### @tnarihi's Caffe fork 10 | 11 | This helper tools require to use @tnarihi's branch of Caffe. You can get it 12 | by running: 13 | 14 | ```shell 15 | git clone git://github.com:tnarihi/caffe.git 16 | cd caffe 17 | git checkout -b future origin/future 18 | ``` 19 | 20 | Once you get it, you will follow the installation of python dependencies in the 21 | below section, then you can install it as you do to install the original Caffe. 22 | 23 | ### Python dependencies. 24 | 25 | Some Python layers of caffe require additional dependencies. 26 | 27 | [Here on Gist](https://gist.github.com/tnarihi/cf9154357500de8b051b) is a 28 | script which installs the dependencies of this tools. This will install 29 | Miniconda Python distribution in your home directory, and install the 30 | dependencies. If you already have Anaconda/Miniconda, you might be able to skip 31 | the python installation step in the script. I've confirmed this works on Ubuntu 32 | 12.04/14.04. I am installing OpenCV using conda but I recommend you to install 33 | OpenCV from source because there would be some library conflicts. There might 34 | be some non-Python dependencies needed to be installed. 35 | 36 | ### Setting paths 37 | 38 | To use caffe and tnarihi-caffe-helper, you should set some environmental vars. 39 | If you work on bash: 40 | 41 | ```shell 42 | echo "export CAFFE_ROOT=" >> ~/.bashrc 43 | echo "export PYTHONPATH=/python:/python":$PYTHONPATH >> ~/.bashrc 44 | ``` 45 | 46 | ### Test everythinig works 47 | After installing them, run py.test at python folder. 48 | 49 | ```shell 50 | cd /python 51 | py.test 52 | ``` 53 | -------------------------------------------------------------------------------- /model_templates/fc_unit.macro: -------------------------------------------------------------------------------- 1 | {% macro fc_unit( 2 | input_name, unit_name, 3 | lr_w=1, lr_b=2, wd_w=1, wd_b=0, 4 | num=64, 5 | w_filler={"type": '"gaussian"', "std": 0.005}, 6 | b_filler={"type": '"constant"', "value": 1}, 7 | bias_term=true, 8 | param_name=none, nonlin_type='ReLU', 9 | dropout_ratio=-1, prelu_lr_mult=1) 10 | %} 11 | {% set fc_name = unit_name %} 12 | {% set nonlin_name = unit_name + '-nonlin' %} 13 | {% set drop_name = unit_name + '-drop' %} 14 | layer { 15 | name: "{{fc_name}}" 16 | type: "InnerProduct" 17 | bottom: "{{input_name}}" 18 | top: "{{fc_name}}" 19 | param { 20 | lr_mult: {{ lr_w }} 21 | decay_mult: {{ wd_w }} 22 | {% if param_name is not none %}name: "{{ param_name }}_w"{%endif%} 23 | } 24 | param { 25 | lr_mult: {{ lr_b }} 26 | decay_mult: {{ wd_b }} 27 | {% if param_name is not none %}name: "{{ param_name }}_b"{%endif%} 28 | } 29 | inner_product_param { 30 | num_output: {{num}} 31 | weight_filler { 32 | {% for key, val in w_filler.iteritems() %}{{ key }}: {{ val }} 33 | {% endfor %} 34 | } 35 | {% if bias_term %} 36 | bias_filler { 37 | {% for key, val in b_filler.iteritems() %}{{ key }}: {{ val }} 38 | {% endfor %} 39 | } 40 | bias_term: {{bias_term|bool2str}} 41 | {% endif %} 42 | } 43 | } 44 | {% if nonlin_type is not none %} 45 | layer { 46 | name: "{{nonlin_name}}" 47 | type: "{{nonlin_type}}" 48 | bottom: "{{fc_name}}" 49 | top: "{{fc_name}}" 50 | {% if nonlin_type == 'PReLU' %} 51 | param { 52 | decay_mult: 0 53 | lr_mult: {{prelu_lr_mult}} 54 | {% if param_name is not none %}name: "{{param_name}}_prelu"{%endif%} 55 | } 56 | {% endif %} 57 | } 58 | {% endif %} 59 | {% if dropout_ratio > 0 %} 60 | layer { 61 | name: "{{drop_name}}" 62 | type: "Dropout" 63 | bottom: "{{fc_name}}" 64 | top: "{{fc_name}}" 65 | dropout_param { 66 | dropout_ratio: {{dropout_ratio}} 67 | } 68 | } 69 | {% endif %} 70 | {% endmacro %} -------------------------------------------------------------------------------- /python/test/test_data_layers.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import caffe 4 | from caffe.proto import caffe_pb2 5 | 6 | 7 | def test_hdf5_layer(tmpdir): 8 | td = tmpdir.mkdir('test_hdf5_layer') 9 | path_h5 = td.join('h5layer.in.h5').ensure().strpath 10 | n_sample = 5 11 | names = ['data%02d' % i for i in xrange(n_sample)] 12 | blob_name = 'blob1' 13 | batch_size = 2 14 | blob_shape = (1, 2, 3) 15 | data = np.random.rand(*((n_sample,) + blob_shape)) 16 | import h5py 17 | with h5py.File(path_h5, 'w') as hd: 18 | for i, name in enumerate(names): 19 | hd.create_group(name) 20 | hd[name][blob_name] = data[i] 21 | import csv 22 | lpath_source = td.join('source.txt').ensure() 23 | csv.writer(lpath_source.open('w')).writerows(map(lambda x: [x], names)) 24 | t = caffe.Blob([]) 25 | bottom = [] 26 | top = [t] 27 | # Create Layer 28 | lp = caffe_pb2.LayerParameter() 29 | lp.type = "Python" 30 | lp.python_param.module = "caffe_helper.layers.data_layers" 31 | lp.python_param.layer = "HDF5Layer" 32 | lp.python_param.param_str = str(dict( 33 | batch_size=batch_size, source=lpath_source.strpath, path_h5=path_h5, 34 | column_id=0, blob_name='blob1')) 35 | layer = caffe.create_layer(lp) 36 | layer.SetUp(bottom, top) 37 | j = 0 38 | for i in xrange(3): 39 | layer.Reshape(bottom, top) 40 | layer.Forward(bottom, top) 41 | assert top[0].shape == (batch_size,) + blob_shape 42 | for blob in top[0].data: 43 | j %= n_sample 44 | assert np.all(blob == data[j].astype(np.float32)) 45 | j += 1 46 | 47 | # Shuffle: Values are not checked here so far. 48 | lp.python_param.param_str = str(dict( 49 | batch_size=batch_size, source=lpath_source.strpath, path_h5=path_h5, 50 | column_id=0, blob_name='blob1', shuffle=True, random_seed=313)) 51 | layer = caffe.create_layer(lp) 52 | layer.SetUp(bottom, top) 53 | for i in xrange(3): 54 | layer.Reshape(bottom, top) 55 | layer.Forward(bottom, top) 56 | assert top[0].shape == (batch_size,) + blob_shape 57 | -------------------------------------------------------------------------------- /python/test/test_vision_layers.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import caffe 4 | from caffe.proto import caffe_pb2 5 | from caffe.gradient_check_util import GradientChecker 6 | 7 | 8 | def test_graident_4_layer(): 9 | ba, c, h, w = (2, 3, 4, 4) 10 | b = caffe.Blob([ba, c, h, w]) 11 | t = caffe.Blob([]) 12 | bottom = [b] 13 | top = [t] 14 | # Create Layer 15 | lp = caffe_pb2.LayerParameter() 16 | lp.type = "Python" 17 | lp.python_param.module = "caffe_helper.layers.vision_layers" 18 | lp.python_param.layer = "Gradient4Layer" 19 | layer = caffe.create_layer(lp) 20 | layer.SetUp(bottom, top) 21 | rng = np.random.RandomState(313) 22 | b.data[...] = rng.randn(*b.shape) 23 | layer.Reshape(bottom, top) 24 | layer.Forward(bottom, top) 25 | assert top[0].shape == (ba, 2, c, h, w) 26 | assert np.all( 27 | top[0].data[:, 0, :, :, :-1] == 28 | bottom[0].data[..., :, :-1] - bottom[0].data[..., :, 1:]) 29 | assert np.all( 30 | top[0].data[:, 1, :, :-1, :] == 31 | bottom[0].data[..., :-1, :] - bottom[0].data[..., 1:, :]) 32 | checker = GradientChecker(1e-2, 1e-4) 33 | checker.check_gradient_exhaustive( 34 | layer, bottom, top) 35 | 36 | 37 | def test_morphology_layer(): 38 | ba, c, h, w = (2, 3, 4, 4) 39 | b = caffe.Blob([ba, c, h, w]) 40 | t = caffe.Blob([]) 41 | bottom = [b] 42 | top = [t] 43 | # Create Layer 44 | lp = caffe_pb2.LayerParameter() 45 | lp.type = "Python" 46 | lp.python_param.module = "caffe_helper.layers.vision_layers" 47 | lp.python_param.layer = "MorphologyLayer" 48 | lp.python_param.param_str = str(dict(op='erode', kernel='4nn')) 49 | layer = caffe.create_layer(lp) 50 | layer.SetUp(bottom, top) 51 | b.data[1, ...] = [[ 52 | [0, 1, 1, 0], 53 | [1, 1, 1, 1], 54 | [1, 1, 1, 1], 55 | [0, 1, 1, 0], 56 | ]] 57 | layer.Reshape(bottom, top) 58 | layer.Forward(bottom, top) 59 | assert np.all( 60 | top[0].data[1, ...] == [[ 61 | [0, 0, 0, 0], 62 | [0, 1, 1, 0], 63 | [0, 1, 1, 0], 64 | [0, 0, 0, 0], 65 | ]]) 66 | -------------------------------------------------------------------------------- /python/caffe_helper/__init__.py: -------------------------------------------------------------------------------- 1 | def __setup(): 2 | """Caffe helper set up function""" 3 | import os 4 | import sys 5 | from jinja2 import Environment, FileSystemLoader 6 | 7 | def j2filter_slice_list(value, slices): 8 | try: 9 | return [value[i] for i in slices] 10 | except TypeError: 11 | return value[:slices] 12 | 13 | def j2filter_bool2str(value): 14 | if value: 15 | return 'true' 16 | else: 17 | return 'false' 18 | 19 | def j2filter_to_int(value): 20 | return int(value) 21 | 22 | j2filters = { 23 | 'max': max, 24 | 'min': min, 25 | 'slice_list': j2filter_slice_list, 26 | 'bool2str': j2filter_bool2str, 27 | 'to_int': j2filter_to_int, 28 | } 29 | pj = os.path.join 30 | caffe_root = os.getenv("CAFFE_ROOT") 31 | if caffe_root is None: 32 | raise ValueError( 33 | "Before calling this module, you should set `CAFFE_ROOT` as " 34 | "your environmental variable.") 35 | caffe_bin = pj(caffe_root, 'build/tools/caffe') 36 | dir_log = pj(caffe_root, 'tmp', 'log') 37 | if not os.path.isdir(dir_log): 38 | os.makedirs(dir_log) 39 | # Setting path to Caffe python 40 | sys.path.append(pj(caffe_root, 'python')) 41 | 42 | # Setting up jinja2 43 | path_jinja2 = os.path.abspath( 44 | pj(os.path.dirname(__file__), '..', '..', 'model_templates') 45 | ) 46 | env = Environment(extensions=['jinja2.ext.do']) 47 | env.filters.update(j2filters) 48 | env.loader = FileSystemLoader(path_jinja2) 49 | dir_proto_out = pj(caffe_root, 'tmp', 'prototxt') 50 | dir_template = path_jinja2 51 | if not os.path.isdir(dir_proto_out): 52 | os.makedirs(dir_proto_out) 53 | return caffe_root, caffe_bin, dir_log, env, dir_proto_out, dir_template, 54 | 55 | 56 | caffe_root, caffe_bin, dir_log, j2env, dir_proto_out, dir_template = __setup() 57 | verbose = True 58 | 59 | 60 | def set_dir_log(dir_log_): 61 | global dir_log 62 | dir_log = dir_log_ 63 | 64 | 65 | def set_verbose(verbose_): 66 | global verbose 67 | verbose = verbose_ 68 | 69 | def add_jinja2_path(p): 70 | from os.path import abspath 71 | from jinja2 import FileSystemLoader, ChoiceLoader 72 | global j2env 73 | j2env.loader = ChoiceLoader([j2env.loader, FileSystemLoader(abspath(p))]) 74 | 75 | import caffe_helper.tools 76 | import caffe_helper.visualize 77 | -------------------------------------------------------------------------------- /model_templates/vgg_conv_unit.macro: -------------------------------------------------------------------------------- 1 | {% macro vgg_conv_unit( 2 | input_name, unit_prefix, 3 | lr_w=1, lr_b=2, wd_w=1, wd_b=0, 4 | channels=64, kernel=3, pad=1, stride=none, group=none, 5 | w_filler={"type": '"gaussian"', "std": 0.01}, 6 | b_filler={"type": '"constant"', "value": 0}, 7 | param_prefix=none, 8 | num_conv=2, 9 | pool_kernel=2, pool_stride=2, 10 | nonlin_type='ReLU', pool_type="MAX") 11 | %} 12 | {# Convolutions-relu loop #} 13 | {% for cid in range(num_conv) %} 14 | {% set conv_name = unit_prefix + '_%d'|format(cid+1) %} 15 | {% set nonlin_name = conv_name + '-nonlin' %} 16 | {% if cid == 0 %} 17 | {% set bottom = input_name %} 18 | {% else %} 19 | {% set bottom = unit_prefix + '_%d'|format(cid) %} 20 | {% endif %} 21 | {% if param_prefix is not none %} 22 | {% set param_name = param_prefix + '_%d'|format(cid+1) %} 23 | {% endif %} 24 | layer { 25 | name: "{{conv_name}}"{# e.g. conv1_2#} 26 | type: "Convolution" 27 | bottom: "{{ bottom }}"{# e.g. data/conv1_1#} 28 | top: "{{ conv_name }}" 29 | param { 30 | lr_mult: {{ lr_w }} 31 | decay_mult: {{ wd_w }} 32 | {% if param_name is not none %}name: "{{ param_name }}_w"{%endif%} 33 | } 34 | param { 35 | lr_mult: {{ lr_b }} 36 | decay_mult: {{ wd_b }} 37 | {% if param_name is not none %}name: "{{ param_name }}_b"{%endif%} 38 | } 39 | convolution_param { 40 | num_output: {{ channels }} 41 | kernel_size: {{ kernel }} 42 | {% if stride is not none %}stride: {{ stride }}{% endif %} 43 | {% if group is not none %}group: {{ group }}{% endif %} 44 | {% if pad is not none %}pad: {{ pad }}{% endif %} 45 | weight_filler { 46 | {% for key, val in w_filler.iteritems() %}{{ key }}: {{ val }} 47 | {% endfor %} 48 | } 49 | bias_filler { 50 | {% for key, val in b_filler.iteritems() %}{{ key }}: {{ val }} 51 | {% endfor %} 52 | } 53 | } 54 | } 55 | {% if nonlin_type is not none %} 56 | layer { 57 | name: "{{ nonlin_name }}" 58 | type: "{{nonlin_type}}" 59 | bottom: "{{ conv_name }}" 60 | top: "{{ conv_name }}" 61 | } 62 | {% endif %} 63 | {% endfor %} 64 | {% if not no_pool %} 65 | {% set pool_name = unit_prefix + '-pool' %} 66 | layer { 67 | name: "{{pool_name}}" 68 | type: "Pooling" 69 | bottom: "{{ unit_prefix + '_%d'|format(num_conv) }}" 70 | top: "{{ pool_name }}" 71 | pooling_param { 72 | pool: {{pool_type}} 73 | kernel_size: {{ pool_kernel }} 74 | stride: {{ pool_stride }} 75 | } 76 | } 77 | {% endif %} 78 | {% endmacro %} -------------------------------------------------------------------------------- /python/test/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import numpy as np 4 | import caffe 5 | 6 | 7 | def pytest_addoption(parser): 8 | parser.addoption("--caffe-cpu", action="store_true", 9 | dest='caffe_cpu', 10 | help="Use cpu instead of gpu") 11 | parser.addoption("--caffe-gpu", action="store", default=0, 12 | dest='caffe_gpu', type='int', 13 | help="Specify gpu device id") 14 | 15 | 16 | def get_name(request): 17 | """ 18 | try: 19 | name = request.function.__name__ 20 | except: 21 | try: 22 | name = request.cls.__name__ 23 | except: 24 | try: 25 | name = request.module.__name__ 26 | except: 27 | name = 'session' 28 | return name 29 | """ 30 | return request.fixturename 31 | 32 | 33 | def set_caffe_dev(request): 34 | name = get_name(request) 35 | if request.config.getoption('caffe_cpu'): 36 | caffe.set_mode_cpu() 37 | print '"%s" run in cpu' % name 38 | return 39 | device_id = request.config.getoption('caffe_gpu') 40 | caffe.set_mode_gpu() 41 | caffe.set_device(device_id) 42 | print '"%s" run in gpu %d' % (name, device_id) 43 | 44 | """ 45 | @pytest.fixture(scope="session", autouse=True) 46 | def session_dev(request): 47 | set_caffe_dev(request) 48 | 49 | 50 | @pytest.fixture(scope="module", autouse=True) 51 | def module_dev(request): 52 | set_caffe_dev(request) 53 | 54 | 55 | @pytest.fixture(scope="class", autouse=True) 56 | def class_dev(request): 57 | set_caffe_dev(request) 58 | """ 59 | 60 | 61 | @pytest.fixture(scope="function", autouse=True) 62 | def function_dev(request): 63 | set_caffe_dev(request) 64 | 65 | 66 | # Blob FloatingPointError 67 | @pytest.fixture(scope="session") 68 | def blob_4_2322(request): 69 | print "Call:", request.fixturename 70 | shape = [2, 3, 2, 2] 71 | return [caffe.Blob(shape) for i in xrange(4)] 72 | 73 | 74 | @pytest.fixture(scope="function") 75 | def blob_4_2322_init(request, blob_4_2322): 76 | print "Call:", request.fixturename 77 | pred, label, mask, top = blob_4_2322 78 | shape = pred.shape 79 | rng = np.random.RandomState(313) 80 | pred.data[...] = rng.rand(*shape) + 0.01 # > 0 81 | label.data[...] = rng.rand(*shape) + 0.01 # > 0 82 | mask.data[...] = rng.rand(*shape) > 0.2 # 80% and avoid 0 div 83 | mask.data[mask.data.reshape(mask.shape[0], -1).sum(1) == 0, 0, 0, 0] = 1 84 | return pred, label, mask, top 85 | -------------------------------------------------------------------------------- /model_templates/vgg_prelu_conv_unit.macro: -------------------------------------------------------------------------------- 1 | {% macro vgg_prelu_conv_unit( 2 | input_name, unit_prefix, 3 | lr_w=1, lr_b=1, wd_w=1, wd_b=0, 4 | channels=64, kernel=3, pad=1, stride=none, group=none, 5 | param_prefix=none, 6 | num_conv=2, 7 | pool_kernel=2, pool_stride=2, 8 | nonlin_type='PReLU', pool_type="MAX") 9 | %} 10 | {# Convolutions-relu loop #} 11 | {% for cid in range(num_conv) %} 12 | {% set conv_name = unit_prefix + '_%d'|format(cid+1) %} 13 | {% set nonlin_name = conv_name + '-nonlin' %} 14 | {% if cid == 0 %} 15 | {% set bottom = input_name %} 16 | {% else %} 17 | {% set bottom = unit_prefix + '_%d'|format(cid) %} 18 | {% endif %} 19 | {% if param_prefix is not none %} 20 | {% set param_name = param_prefix + '_%d'|format(cid+1) %} 21 | {% endif %} 22 | layer { 23 | name: "{{conv_name}}"{# e.g. conv1_2#} 24 | type: "Convolution" 25 | bottom: "{{ bottom }}"{# e.g. data/conv1_1#} 26 | top: "{{ conv_name }}" 27 | param { 28 | lr_mult: {{ lr_w }} 29 | decay_mult: {{ wd_w }} 30 | {% if param_name is not none %}name: "{{ param_name }}_w"{%endif%} 31 | } 32 | param { 33 | lr_mult: {{ lr_b }} 34 | decay_mult: {{ wd_b }} 35 | {% if param_name is not none %}name: "{{ param_name }}_b"{%endif%} 36 | } 37 | convolution_param { 38 | num_output: {{ channels }} 39 | kernel_size: {{ kernel }} 40 | {% if stride is not none %}stride: {{ stride }}{% endif %} 41 | {% if group is not none %}group: {{ group }}{% endif %} 42 | {% if pad is not none %}pad: {{ pad }}{% endif %} 43 | weight_filler { 44 | type: "gaussian" 45 | {% if nonlin_type == 'PReLU' %} 46 | std: {{(2.0 / ((kernel**2) * channels * (1.0 + 0.25**2)))**0.5}} 47 | {% elif nonlin_type == 'ReLU' %} 48 | std: {{(2.0 / ((kernel**2) * channels))**0.5}} 49 | {% else %} 50 | # Xavier 51 | std: {{(1.0 / ((kernel**2) * channels))**0.5}} 52 | {% endif %} 53 | } 54 | } 55 | } 56 | {% if nonlin_type is not none %} 57 | layer { 58 | name: "{{ nonlin_name }}" 59 | type: "{{nonlin_type}}" 60 | bottom: "{{ conv_name }}" 61 | top: "{{ conv_name }}" 62 | {%if nonlin_type == 'PReLU' %} 63 | param { 64 | {% if param_name is not none %}name: "{{param_name}}_prelu"{%endif%} 65 | decay_mult: 0 66 | } 67 | {%endif%} 68 | } 69 | {% endif %} 70 | {% endfor %} 71 | {% if not no_pool %} 72 | {% set pool_name = unit_prefix + '-pool' %} 73 | layer { 74 | name: "{{pool_name}}" 75 | type: "Pooling" 76 | bottom: "{{ unit_prefix + '_%d'|format(num_conv) }}" 77 | top: "{{ pool_name }}" 78 | pooling_param { 79 | pool: {{pool_type}} 80 | kernel_size: {{ pool_kernel }} 81 | stride: {{ pool_stride }} 82 | } 83 | } 84 | {% endif %} 85 | {% endmacro %} -------------------------------------------------------------------------------- /model_templates/conv_unit.macro: -------------------------------------------------------------------------------- 1 | {% macro conv_unit( 2 | input_name, unit_name, 3 | lr_w=1, lr_b=2, wd_w=1, wd_b=0, 4 | channels=64, kernel=5, stride=none, group=none, pad=none, 5 | w_filler={"type": '"gaussian"', "std": 0.01}, 6 | b_filler={"type": '"constant"', "value": 0}, 7 | param_name=none, 8 | pool_kernel=3, pool_stride=2, 9 | nonlin_type='ReLU', no_pool=false, no_norm=false, 10 | prelu_lr_mult=1, dropout_ratio=-1) 11 | %} 12 | {% set conv_name = unit_name %} 13 | {% set nonlin_name = unit_name + '-nonlin' %} 14 | {% set drop_name = unit_name + '-drop' %} 15 | {% set pool_name = unit_name + '-pool' %} 16 | {% set norm_name = unit_name + '-norm' %} 17 | layer { 18 | name: "{{ conv_name }}" 19 | type: "Convolution" 20 | bottom: "{{ input_name }}" 21 | top: "{{ conv_name }}" 22 | param { 23 | lr_mult: {{ lr_w }} 24 | decay_mult: {{ wd_w }} 25 | {% if param_name is not none %}name: "{{ param_name }}_w"{%endif%} 26 | } 27 | param { 28 | lr_mult: {{ lr_b }} 29 | decay_mult: {{ wd_b }} 30 | {% if param_name is not none %}name: "{{ param_name }}_b"{%endif%} 31 | } 32 | convolution_param { 33 | num_output: {{ channels }} 34 | kernel_size: {{ kernel }} 35 | {% if stride is not none %}stride: {{ stride }}{% endif %} 36 | {% if group is not none %}group: {{ group }}{% endif %} 37 | {% if pad is not none %}pad: {{ pad }}{% endif %} 38 | weight_filler { 39 | {% for key, val in w_filler.iteritems() %}{{ key }}: {{ val }} 40 | {% endfor %} 41 | } 42 | bias_filler { 43 | {% for key, val in b_filler.iteritems() %}{{ key }}: {{ val }} 44 | {% endfor %} 45 | } 46 | } 47 | } 48 | {% if nonlin_type is not none %} 49 | layer { 50 | name: "{{ nonlin_name }}" 51 | type: "{{nonlin_type}}" 52 | bottom: "{{ conv_name }}" 53 | top: "{{ conv_name }}" 54 | {% if nonlin_type == 'PReLU' %} 55 | param { 56 | decay_mult: 0 57 | lr_mult: {{prelu_lr_mult}} 58 | {% if param_name is not none %}name: "{{ param_name }}_prelu"{%endif%} 59 | } 60 | {% endif %} 61 | } 62 | {% endif %} 63 | {% if dropout_ratio > 0%} 64 | layer { 65 | name: "{{drop_name}}" 66 | type: "Dropout" 67 | bottom: "{{conv_name}}" 68 | top: "{{conv_name}}" 69 | dropout_param { 70 | dropout_ratio: {{dropout_ratio}} 71 | } 72 | } 73 | {% endif %} 74 | {% if not no_pool %} 75 | layer { 76 | name: "{{ pool_name }}" 77 | type: "Pooling" 78 | bottom: "{{ conv_name }}" 79 | top: "{{ pool_name }}" 80 | pooling_param { 81 | pool: MAX 82 | kernel_size: {{ pool_kernel }} 83 | stride: {{ pool_stride }} 84 | } 85 | } 86 | {% endif %} 87 | {% if not no_norm %} 88 | layer { 89 | name: "{{ norm_name }}" 90 | type: "LRN" 91 | bottom: "{{ pool_name }}" 92 | top: "{{ norm_name }}" 93 | lrn_param { 94 | local_size: 5 95 | alpha: 0.0001 96 | beta: 0.75 97 | } 98 | } 99 | {% endif %} 100 | {% endmacro %} -------------------------------------------------------------------------------- /python/caffe_helper/layers/vision_layers.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from caffe import Layer 4 | 5 | def _force_c_order_array(x): 6 | if x.flags.c_contiguous: 7 | return x 8 | return x.copy(order='c') 9 | 10 | class DownSamplingLayer(Layer): 11 | 12 | def setup(self, bottom, top): 13 | param = eval(self.param_str_) 14 | self.factor_ = param['factor'] 15 | self.reshape(bottom, top) 16 | 17 | def reshape(self, bottom, top): 18 | top[0].reshape( 19 | *(bottom[0].data.shape[:2] 20 | + (bottom[0].data.shape[2] / self.factor_, 21 | bottom[0].data.shape[3] / self.factor_))) 22 | 23 | def forward(self, bottom, top): 24 | top[0].data[...] = bottom[0].data[:, :, ::self.factor_, ::self.factor_] 25 | 26 | def backward(self, top, propagate_down, bottom): 27 | if propagate_down[0]: 28 | bottom[0].diff[:, :, ::self.factor_, ::self.factor_] = top[0].diff 29 | 30 | 31 | class Gradient4Layer(Layer): 32 | 33 | """4-connected neighborhood gradient 34 | bottom: (B, C, H, W) 35 | top: (B, 2, C, H, W) 36 | 37 | Gradients for x-axis are stacked to 0-th index in 2nd axis, gradients for 38 | y-axis are stacked to 1st index. 39 | """ 40 | 41 | def setup(self, bottom, top): 42 | self.reshape(bottom, top) 43 | 44 | def reshape(self, bottom, top): 45 | assert len(bottom) == 1 46 | assert len(top) == 1 47 | assert len(bottom[0].shape) == 4 48 | b, c, h, w = bottom[0].shape 49 | top[0].reshape(b, 2, c, h, w) 50 | 51 | def forward(self, bottom, top): 52 | top[0].data[:, 0, :, :, :-1] = bottom[0].data[..., :, :-1] - \ 53 | bottom[0].data[..., :, 1:] 54 | top[0].data[:, 1, :, :-1, :] = bottom[0].data[..., :-1, :] - \ 55 | bottom[0].data[..., 1:, :] 56 | 57 | def backward(self, top, propagate_down, bottom): 58 | if not propagate_down[0]: 59 | return 60 | bottom[0].diff[...] = 0 61 | bottom[0].diff[..., :, :-1] += top[0].diff[:, 0, :, :, :-1] 62 | bottom[0].diff[..., :, 1:] -= top[0].diff[:, 0, :, :, :-1] 63 | bottom[0].diff[..., :-1, :] += top[0].diff[:, 1, :, :-1, :] 64 | bottom[0].diff[..., 1:, :] -= top[0].diff[:, 1, :, :-1, :] 65 | 66 | 67 | class MorphologyLayer(Layer): 68 | 69 | """Morphological operation using cv2 as a backend. 70 | Only supports dimension in [1, 3] at axis=1. 71 | 72 | parameters 73 | ---------- 74 | 75 | op : "BLACKHAT", "CROSS", "ELLIPSE", "GRADIENT", "RECT", 76 | "CLOSE", "DILATE", "ERODE", "OPEN", "TOPHAT" which come from `cv2.MORPH_*`. 77 | 78 | kernel: "4nn" is only supported so far 79 | 80 | """ 81 | 82 | def setup(self, bottom, top): 83 | param = eval(self.param_str_) 84 | self.op_ = param['op'].upper() 85 | kernel_ = param['kernel'] 86 | if kernel_ == '4nn': 87 | self.kernel_ = np.array( 88 | [[0, 1, 0], [1, 1, 1], [0, 1, 0]], np.uint8) 89 | else: 90 | ValueError 91 | self.reshape(bottom, top) 92 | 93 | def reshape(self, bottom, top): 94 | assert len(bottom) == 1 95 | assert len(top) == 1 96 | assert len(bottom[0].shape) == 4 97 | assert bottom[0].shape[1] == 3 or bottom[0].shape[1] == 1 98 | top[0].reshape(*bottom[0].shape) 99 | 100 | def forward(self, bottom, top): 101 | import cv2 102 | for i, img in enumerate(bottom[0].data.transpose(0, 2, 3, 1)): 103 | imgo = cv2.morphologyEx( 104 | _force_c_order_array(img), getattr(cv2, "MORPH_" + self.op_), self.kernel_) 105 | top[0].data[i, ...] = imgo.transpose(2, 0, 1) 106 | 107 | def backward(self, top, propagate_down, bottom): 108 | raise NotImplementedError( 109 | "%s does not support backward pass." % self.__class__) 110 | -------------------------------------------------------------------------------- /python/caffe_helper/tools.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | 4 | from .proto_creator import * 5 | from .obsolete import * 6 | 7 | 8 | def save_output_hdf5(blobs, proto_path, model_path, path_out, path_names, 9 | h5mode='a', name_column=0, gpu=0, phase=None): 10 | import csv 11 | import caffe 12 | from h5py import File as H5File 13 | if phase is None: 14 | phase = caffe.TEST 15 | try: 16 | os.makedirs(os.path.dirname(path_out)) 17 | except: 18 | pass 19 | names = map(lambda x: x[name_column], csv.reader(open(path_names, 'r'))) 20 | with H5File(path_out, h5mode) as h5d: 21 | if gpu < 0: 22 | caffe.set_mode_cpu() 23 | else: 24 | caffe.set_mode_gpu() 25 | caffe.set_device(gpu) 26 | net = caffe.Net(proto_path, model_path, phase) 27 | i = 0 28 | while True: 29 | ret = net.forward(blobs=blobs) 30 | for s in xrange(ret[blobs[0]].shape[0]): 31 | if len(names) == i: 32 | return 33 | try: 34 | h5d.create_group(names[i]) 35 | except ValueError: 36 | pass 37 | for b in blobs: 38 | try: 39 | h5d[names[i]][b] = ret[b][s].copy() 40 | except (ValueError, RuntimeError): 41 | del h5d[names[i]][b] 42 | h5d[names[i]][b] = ret[b][s].copy() 43 | i += 1 44 | 45 | 46 | def draw_net_from_prototxt(path_proto, rankdir='LR', **proto_kw): 47 | """""" 48 | if path_proto.endswith('.jinja2'): 49 | path_proto = convert_prototxt_template(path_proto, **proto_kw) 50 | from google.protobuf import text_format 51 | import caffe 52 | import caffe.draw 53 | from caffe.proto import caffe_pb2 54 | from PIL import Image 55 | from StringIO import StringIO 56 | net = caffe_pb2.NetParameter() 57 | text_format.Merge(open(path_proto).read(), net) 58 | png = caffe.draw.draw_net(net, rankdir, ext='png') 59 | img = np.asarray(Image.open(StringIO(png))) 60 | return img 61 | 62 | 63 | train_command_base = """ 64 | #! /usr/bin/env python 65 | # BEGIN EMBEDDED 66 | path_pkl = "{path_pkl}" 67 | weights = "{weights}" 68 | default_cbatch = {cbatch} 69 | # END ENBEDDED 70 | 71 | import cPickle 72 | import subprocess 73 | from optparse import OptionParser 74 | 75 | usage = 'Usage: %prog [options] gpuid' 76 | parser = OptionParser(usage=usage) 77 | parser.add_option( 78 | '-b', '--batch', dest='cbatch', type='int', default=default_cbatch, 79 | help='Computational batch size', 80 | ) 81 | 82 | options, args = parser.parse_args() 83 | if len(args) != 1: 84 | parser.error("GPU id must be specified.") 85 | gpu = int(args[0]) 86 | 87 | with open(path_pkl, 'rb') as fd: 88 | pc, spc, prefix = cPickle.load(fd) 89 | if options.cbatch is not None: 90 | cbatch_old = pc.kw['batch_size'] 91 | pc.update(batch_size=options.cbatch) 92 | iter_size = spc.kw['accum_grad'] 93 | lbatch = cbatch_old * iter_size 94 | assert lbatch % options.cbatch == 0 95 | spc.update(accum_grad=lbatch/options.cbatch) 96 | net_proto = pc.create(prefix_proto=prefix) 97 | subprocess.call('bash -c "' + spc.train_command(net_proto, weights, gpu=gpu) + '"', shell=True) 98 | """ 99 | def create_train_command(pc, spc, params, epochs, batch, prefix, num_train, weights): 100 | pc = pc.copy_and_update(params=params) 101 | cbatch = pc.kw['batch_size'] 102 | spc = spc.copy_and_update( 103 | snapshot_prefix=pc.get_path_proto_base(prefix), 104 | accum_grad=batch/cbatch, 105 | max_iter=epochs * num_train / batch + 1,) 106 | path_pkl = spc.get_path_proto_base() + '.pkl' 107 | path_py = spc.get_path_proto_base() + '.train.py' 108 | with open(path_pkl, 'wb') as fd: 109 | s = cPickle.dump([pc, spc, prefix], fd) 110 | with open(path_py, 'w') as fd: 111 | print >> fd, train_command_base.format(path_pkl=path_pkl, weights=weights, cbatch=cbatch) 112 | print 'python', path_py 113 | 114 | def create_test_command( 115 | blobs, pc, prefix, model_path, path_out, path_names, 116 | h5mode='a', name_column=0, gpu=0, phase=None, 117 | params=None 118 | ): 119 | if params is not None: 120 | pc = pc.copy_and_update(params=params) 121 | path_pkl = pc.get_path_proto_base(prefix) + '.pkl' 122 | path_py = pc.get_path_proto_base(prefix) + '.test.py' 123 | with open(path_pkl, 'wb') as fd: 124 | s = cPickle.dump([pc, spc, prefix], fd) 125 | with open(path_py, 'w') as fd: 126 | print >> fd, train_command_base.format(path_pkl=path_pkl, weights=weights, cbatch=cbatch) 127 | print 'python', path_py 128 | -------------------------------------------------------------------------------- /script/caffex.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import glob 3 | import re 4 | import subprocess 5 | import sys 6 | from os.path import expandvars 7 | import signal 8 | import time 9 | 10 | 11 | class GracefulKiller: 12 | 13 | """Stack overflow : http://goo.gl/mdh4hP""" 14 | 15 | def __init__(self): 16 | print("killer init") 17 | self.kill_now = False 18 | # signal.signal(signal.SIGINT, self.exit_gracefully) 19 | signal.signal(signal.SIGTERM, self.exit_gracefully) 20 | 21 | def exit_gracefully(self, signum, frame): 22 | print("Signal handler called") 23 | self.kill_now = True 24 | 25 | 26 | class Tee(object): 27 | 28 | def __init__(self, *files): 29 | self.files = files 30 | 31 | def write(self, obj): 32 | for f in self.files: 33 | f.write(obj) 34 | f.flush() # If you want the output to be visible immediately 35 | 36 | def flush(self): 37 | for f in self.files: 38 | f.flush() 39 | 40 | 41 | def parse_args(): 42 | import argparse 43 | 44 | parser = argparse.ArgumentParser() 45 | parser.add_argument('mode') 46 | parser.add_argument('--weights') 47 | parser.add_argument('--solver') 48 | parser.add_argument('--gpu', type=int) 49 | parser.add_argument('--log') 50 | parser.add_argument('--max_iter', type=int) 51 | parser.add_argument('--step_size', type=int) 52 | parser.add_argument('--warn_step_second', type=int, default=25) 53 | parser.add_argument('--time_limit', type=int) 54 | args = parser.parse_args() 55 | assert args.mode == 'train', 'only train supported' 56 | return args 57 | 58 | 59 | def get_solver(args): 60 | from caffe.proto.caffe_pb2 import SolverParameter 61 | from google.protobuf.text_format import Merge 62 | 63 | solver = SolverParameter() 64 | Merge(open(args.solver, 'r').read(), solver) 65 | return solver 66 | 67 | 68 | def get_iter_reached(args, solver): 69 | 70 | models = glob.glob(solver.snapshot_prefix + '_iter_*.solverstate') 71 | r = re.compile(r"^.+_iter_([0-9]+)\.solverstate$") 72 | iter_reached = 0 73 | for model in models: 74 | m = r.match(model) 75 | itr = int(m.groups()[0]) 76 | iter_reached = max(itr, iter_reached) 77 | return iter_reached 78 | 79 | 80 | def create_command(args, solver, iter_reached): 81 | 82 | cmd = [expandvars('$CAFFE_ROOT/build/tools/caffe'), args.mode] 83 | cmd += ['-solver', args.solver] 84 | if iter_reached == 0: 85 | if args.weights is not None: 86 | cmd += ['-weights', args.weights] 87 | else: 88 | cmd += ['-snapshot', 89 | solver.snapshot_prefix + 90 | '_iter_{}.solverstate'.format(iter_reached)] 91 | if args.gpu is not None: 92 | cmd += ['-gpu', str(args.gpu)] 93 | return cmd 94 | 95 | 96 | def train(args, solver, iter_reached): 97 | 98 | import caffe 99 | 100 | if args.gpu is not None: 101 | caffe.set_mode_gpu() 102 | caffe.set_device(args.gpu) 103 | max_iter = solver.max_iter 104 | if args.max_iter is not None: 105 | max_iter = args.max_iter 106 | step_size = 1 107 | if args.step_size is not None: 108 | step_size = args.step_size 109 | 110 | print('init solver') 111 | s = caffe.SGDSolver(args.solver) 112 | 113 | # Resume from solverstate or weights 114 | if iter_reached == 0: 115 | if args.weights is not None: 116 | s.net.copy_from(args.weights) 117 | else: 118 | s.restore(str(solver.snapshot_prefix 119 | + '_iter_{}.solverstate'.format(iter_reached))) 120 | 121 | # killer = GracefulKiller() # Register signal handler 122 | 123 | stime_all = time.time() 124 | while s.iter < max_iter: 125 | print(s.iter) 126 | stime = time.time() 127 | s.step(step_size) 128 | time_elapsed = time.time() - stime 129 | if time_elapsed > 25: 130 | import warnings 131 | warnings.warn( 132 | "solver.step(step_size={}) takes {} sec. It might not be able " 133 | "to take a snapshot even when SIGINT/SIGTERM is " 134 | "received.".format(step_size, time_elapsed), RuntimeWarning) 135 | """ 136 | if killer.kill_now: 137 | print("SIGTERM/SIGINT received: taking snapshot...") 138 | s.snapshot() 139 | print("Snapshot done. System killing process...") 140 | sys.exit(0) 141 | """ 142 | if args.time_limit is not None and \ 143 | time.time() - stime_all > args.time_limit: 144 | print("Reached to time limit: taking snapshot...") 145 | s.snapshot() 146 | sys.exit(0) 147 | else: 148 | print("Reached to max_iter: taking snapshot...") 149 | s.snapshot() 150 | print ("Training done.") 151 | 152 | 153 | def main(): 154 | args = parse_args() 155 | solver = get_solver(args) 156 | iter_reached = get_iter_reached(args, solver) 157 | 158 | if iter_reached >= solver.max_iter - 1: 159 | print('caffex.py DONE.') 160 | return 161 | 162 | train(args, solver, iter_reached) 163 | 164 | """ 165 | cmd = create_command(args, solver, iter_reached) 166 | print('run:', ' '.join(cmd)) 167 | if args.log is not None: 168 | subprocess.call(' '.join(cmd) + '|&tee -a {}'.format(args.log), shell=True) 169 | else: 170 | subprocess.call(' '.join(cmd), shell=True) 171 | """ 172 | 173 | main() 174 | -------------------------------------------------------------------------------- /python/test/test_loss_layers.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import pytest 4 | 5 | import caffe 6 | from caffe.proto import caffe_pb2 7 | from caffe.gradient_check_util import GradientChecker 8 | 9 | 10 | @pytest.fixture(scope="module") 11 | def sil2_loss_layer(request, blob_4_2322): 12 | if request.config.getoption('caffe_cpu'): 13 | raise pytest.skip("ScaleInvariantL2LossLayer requires GPU") 14 | print "Call:", request.fixturename 15 | # Create blobs 16 | pred, label, mask, top = blob_4_2322 17 | bottom = [pred, label, mask] 18 | top = [top] 19 | lam = 0.5 20 | # Create Layer 21 | lp = caffe_pb2.LayerParameter() 22 | lp.type = "Python" 23 | lp.python_param.module = "caffe_helper.layers.loss_layers" 24 | lp.python_param.layer = "ScaleInvariantL2LossLayer" 25 | lp.python_param.param_str = str({'lambda': lam}) 26 | # caffe.set_mode_gpu() 27 | # caffe.set_device(0) 28 | layer = caffe.create_layer(lp) 29 | layer.SetUp(bottom, top) 30 | return layer 31 | 32 | 33 | def test_sil2_loss_layer_forward(sil2_loss_layer, blob_4_2322_init): 34 | layer = sil2_loss_layer 35 | pred, label, mask, top = blob_4_2322_init 36 | bottom = [pred, label, mask] 37 | top = [top] 38 | layer.Reshape(bottom, top) 39 | layer.Forward(bottom, top) 40 | pred_data = pred.data.reshape(pred.shape[0], -1) 41 | label_data = label.data.reshape(pred.shape[0], -1) 42 | mask_data = mask.data.reshape(pred.shape[0], -1) 43 | diff = (pred_data - label_data) * mask_data 44 | diff_sum = diff.sum(1) 45 | diff2_sum = (diff ** 2).sum(1) 46 | mask_sum = mask_data.sum(1) 47 | term1 = (diff2_sum / mask_sum).mean() 48 | term2 = ((diff_sum ** 2) / (mask_sum ** 2)).mean() 49 | loss = term1 - layer.lambda_ * term2 50 | assert np.isclose(top[0].data, loss) 51 | 52 | 53 | def test_sil2_loss_layer_backward(sil2_loss_layer, blob_4_2322_init): 54 | layer = sil2_loss_layer 55 | pred, label, mask, top = blob_4_2322_init 56 | bottom = [pred, label, mask] 57 | top = [top] 58 | checker = GradientChecker(1e-2, 1e-2) 59 | checker.check_gradient_exhaustive( 60 | layer, bottom, top, check_bottom=[0, 1]) 61 | 62 | 63 | def test_dssim_layer(request): 64 | if request.config.getoption('caffe_cpu'): 65 | raise pytest.skip("DSSIMLayer requires GPU") 66 | x, y = np.ogrid[:5, :5] 67 | img1 = np.sin(x / 5.0 * np.pi) * np.cos(y / 5.0 * np.pi) 68 | img1 = np.repeat(img1[..., np.newaxis], 3, 2) 69 | img1 = (img1 - img1.min()) / (img1.max() - img1.min()) 70 | rng = np.random.RandomState(313) 71 | img2 = img1 + rng.randn(*img1.shape) * 0.2 72 | img2[img2 > 1] = 1 73 | img2[img2 < 0] = 0 74 | bottom = [caffe.Blob([]), caffe.Blob([])] 75 | top = [caffe.Blob([])] 76 | img1 = img1.transpose(2, 0, 1) 77 | img2 = img2.transpose(2, 0, 1) 78 | bottom[0].reshape(*((1,) + img1.shape)) 79 | bottom[1].reshape(*((1,) + img2.shape)) 80 | # Create Layer 81 | lp = caffe_pb2.LayerParameter() 82 | lp.type = "Python" 83 | lp.python_param.module = "caffe_helper.layers.loss_layers" 84 | lp.python_param.layer = "DSSIMLayer" 85 | lp.python_param.param_str = str({'hsize': 3}) 86 | layer = caffe.create_layer(lp) 87 | layer.SetUp(bottom, top) 88 | bottom[0].data[...] = img1[np.newaxis] 89 | bottom[1].data[...] = img2[np.newaxis] 90 | checker = GradientChecker(1e-3, 1e-2) 91 | checker.check_gradient_exhaustive( 92 | layer, bottom, top, check_bottom=[0, 1]) 93 | 94 | 95 | def test_logit_loss_layer(request): 96 | if request.config.getoption('caffe_cpu'): 97 | raise pytest.skip("LogitLossLayer requires GPU") 98 | # data 99 | y = np.random.rand(2, 3, 4, 5) 100 | t = (np.random.rand(2, 3, 4, 5) > 0.5).astype(np.float)\ 101 | - (np.random.rand(2, 3, 4, 5) > 0.5).astype(np.float) 102 | # setting up blobs 103 | bottom = [caffe.Blob([]), caffe.Blob([])] 104 | top = [caffe.Blob([])] 105 | bottom[0].reshape(*y.shape) 106 | bottom[1].reshape(*t.shape) 107 | bottom[0].data[...] = y 108 | bottom[1].data[...] = t 109 | # Create Layer 110 | lp = caffe_pb2.LayerParameter() 111 | lp.type = "Python" 112 | lp.python_param.module = "caffe_helper.layers.loss_layers" 113 | lp.python_param.layer = "LogitLossLayer" 114 | layer = caffe.create_layer(lp) 115 | layer.SetUp(bottom, top) 116 | layer.Reshape(bottom, top) 117 | layer.Forward(bottom, top) 118 | # reference computation 119 | l = np.sum(np.abs(t) * np.log(1 + np.exp(-y * t))) / np.sum(np.abs(t)) 120 | assert np.isclose(top[0].data, l) 121 | checker = GradientChecker(1e-3, 1e-2) 122 | checker.check_gradient_exhaustive( 123 | layer, bottom, top, check_bottom=[0]) 124 | 125 | 126 | def test_crossent_layer(request): 127 | if request.config.getoption('caffe_cpu'): 128 | raise pytest.skip("crossentropyLossLayer requires GPU") 129 | pred = caffe.Blob((5, 8)) 130 | label = caffe.Blob((5, 1)) 131 | loss = caffe.Blob([]) 132 | bottom = [pred, label] 133 | top = [loss] 134 | 135 | # Fill 136 | rng = np.random.RandomState(313) 137 | pred.data[...] = rng.rand(*pred.shape) + 0.1 138 | label.data[...] = rng.randint(0, 8, label.shape) 139 | pred.data[...] = pred.data / pred.data.sum(axis=1, keepdims=True) 140 | # Create Layer 141 | lp = caffe_pb2.LayerParameter() 142 | lp.type = "Python" 143 | lp.python_param.module = "caffe_helper.layers.loss_layers" 144 | lp.python_param.layer = "CrossEntropyLossLayer" 145 | layer = caffe.create_layer(lp) 146 | layer.SetUp(bottom, top) 147 | layer.Reshape(bottom, top) 148 | layer.Forward(bottom, top) 149 | ref = -np.mean( 150 | np.log( 151 | np.maximum(np.finfo(np.float32).tiny, pred.data) 152 | ).reshape(pred.shape[0], -1)[ 153 | np.arange(pred.shape[0]), label.data.astype('int32')] 154 | ) 155 | assert np.isclose(ref, loss.data) 156 | checker = GradientChecker(1e-3, 1e-2) 157 | checker.check_gradient_exhaustive( 158 | layer, bottom, top, check_bottom=[0]) 159 | -------------------------------------------------------------------------------- /python/caffe_helper/proto_creator.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from os.path import join, dirname, abspath 3 | import caffe_helper as ch 4 | 5 | 6 | class ProtoCreator(object): 7 | 8 | def __init__(self, proto_base, params=None, **kw): 9 | self._params = None 10 | if params is not None: 11 | self._params = params.copy() 12 | self._kw = kw.copy() 13 | self._proto_base = proto_base 14 | 15 | def get_param_str(self): 16 | if self._params is not None: 17 | return dict_stringify(self._params) 18 | return '' 19 | 20 | def get_path_proto_base(self, prefix_proto): 21 | path_proto = prefix_proto 22 | if self._params is not None: 23 | path_proto += '+' + dict_stringify(self._params) 24 | return path_proto 25 | 26 | def create(self, path_proto=None, prefix_proto=None): 27 | assert path_proto is None or prefix_proto is None, \ 28 | "Both of path_proto and prefix cannot be set." 29 | if prefix_proto is not None: 30 | path_proto = self.get_path_proto_base(prefix_proto) 31 | path_proto += '.prototxt' 32 | kw = self._kw 33 | if self._params is not None: 34 | kw = dict(self._params, **kw) 35 | return convert_prototxt_template( 36 | self.proto_base, path_proto=path_proto, **kw) 37 | 38 | def copy(self): 39 | return self.__class__( 40 | proto_base=self.proto_base, params=self._params, **self._kw) 41 | 42 | def update(self, params=None, **kw): 43 | if params is not None: 44 | if self._params is None: 45 | self._params = params.copy() 46 | else: 47 | self._params.update(params) 48 | self._kw.update(**kw) 49 | 50 | def copy_and_update(self, proto_base=None, params=None, **kw): 51 | cp = self.copy() 52 | if proto_base is not None: 53 | cp.proto_base = proto_base 54 | cp.update(params=params, **kw) 55 | return cp 56 | 57 | @property 58 | def proto_base(self): 59 | return self._proto_base 60 | 61 | @proto_base.setter 62 | def proto_base(self, value): 63 | self._proto_base = value 64 | 65 | @property 66 | def kw(self): 67 | return self._kw 68 | 69 | @property 70 | def params(self): 71 | return self._params 72 | 73 | 74 | class SolverProtoCreator(ProtoCreator): 75 | SOLVER_PROTO_BASE = join(ch.dir_template, 'solver.prototxt.jinja2') 76 | PARAMS = { 77 | 'base_lr': 0.001, 78 | 'momentum': 0.9, 79 | 'lr_policy': 'poly', 'power': 0.5, 80 | } 81 | 82 | def __init__( 83 | self, 84 | snapshot_prefix, max_iter, test_iter=None, 85 | test_interval=None, base_lr=0.001, 86 | display=50, momentum=0.9, 87 | weight_decay=1e-6, snapshot=100, debug_info=False, 88 | accum_grad=1, share_blobs=True, 89 | force_cpu_momentum=False, lr_policy="fixed", power=0.5, 90 | proto_base=None, net=None, params=None): 91 | del proto_base 92 | del net 93 | del params 94 | kw = locals().copy() 95 | del kw['self'] 96 | proto_base = SolverProtoCreator.SOLVER_PROTO_BASE 97 | super(SolverProtoCreator, self).__init__(proto_base, **kw) 98 | 99 | @ProtoCreator.proto_base.setter 100 | def proto_base(self, value): 101 | raise AttributeError("Assignment of `proto_base` is not allowed.") 102 | 103 | def get_param_str(self): 104 | params = {} 105 | for k, v in self.PARAMS.iteritems(): 106 | if self._kw[k] != v: 107 | params[k] = self._kw[k] 108 | if params: 109 | return dict_stringify(params) 110 | return '' 111 | def get_path_proto_base(self): 112 | path_proto = self._kw['snapshot_prefix'] 113 | param_str = self.get_param_str() 114 | if param_str: 115 | path_proto += '+' + param_str 116 | return path_proto 117 | 118 | def create(self, net, path_proto=None): 119 | if path_proto is None: 120 | path_proto = self.get_path_proto_base() 121 | path_proto += '.solver.prototxt' 122 | self._kw.update({'net': net}) 123 | return super(SolverProtoCreator, self).create(path_proto) 124 | 125 | def model_path(self, iter_=None): 126 | if iter_ is None: 127 | iter_ = self._kw['max_iter'] + 1 128 | return self._kw['snapshot_prefix'] + '_iter_%d.caffemodel' % (iter_) 129 | 130 | def train_command(self, net_proto, weights=None, snapshot=None, gpu=0): 131 | com = ["%s/build/tools/caffe train" % ch.caffe_root, 132 | "-solver %s" % self.create(net_proto)] 133 | if weights is not None: 134 | com += ["-weights %s" % weights] 135 | if snapshot is not None: 136 | com += ["-snapshot %s" % snapshot] 137 | if gpu >= 0: 138 | com += ["-gpu %d" % gpu] 139 | return ' '.join( 140 | com + ['|& tee %s' % (self._kw['snapshot_prefix'] + '.log')]) 141 | 142 | def train_command_caffex(self, net_proto, weights=None, gpu=0): 143 | com = ["python %s/script/caffex.py train" % abspath( 144 | join(dirname(ch.__file__), '..', '..')), 145 | "--solver %s" % self.create(net_proto)] 146 | if weights is not None: 147 | com += ["--weights %s" % weights] 148 | if gpu >= 0: 149 | com += ["--gpu %d" % gpu] 150 | com += ['--log %s' % (self._kw['snapshot_prefix'] + '.log')] 151 | return ' '.join(com) 152 | 153 | 154 | def convert_prototxt_template(path_template, path_proto=None, **proto_kw): 155 | """""" 156 | if path_proto is None: 157 | m = hashlib.md5(open(path_template, 'rb').read()) 158 | if proto_kw: 159 | m.update(str(proto_kw)) 160 | path_proto = join(ch.dir_proto_out, m.hexdigest() + '.prototxt') 161 | with open(path_proto, 'w') as fd: 162 | tmpl = ch.j2env.from_string(open(path_template).read()) 163 | print >> fd, tmpl.render(**proto_kw) 164 | return path_proto 165 | 166 | 167 | def get_solver_prototxt(net, 168 | snapshot_prefix, max_iter, test_iter=200, 169 | test_interval=100, base_lr=0.001, 170 | display=50, momentum=0.9, 171 | weight_decay=1e-6, snapshot=100, debug_info=False, 172 | accum_grad=1, share_blobs=True, 173 | force_cpu_momentum=False, lr_policy="fixed", power=0.5, 174 | path_proto=None): 175 | """ 176 | """ 177 | kw = locals().copy() 178 | path_solver = convert_prototxt_template( 179 | join(ch.dir_template, 'solver.prototxt.jinja2'), **kw) 180 | return path_solver 181 | 182 | 183 | def dict_stringify(net_params): 184 | return '+'.join( 185 | map( 186 | lambda x: '='.join( 187 | ( 188 | str(x[0]).replace(' ', '_'), 189 | str(x[1]).replace(' ', '_')\ 190 | .replace(',', '')\ 191 | .replace('[', '')\ 192 | .replace(']', ''))), 193 | sorted(net_params.iteritems())) 194 | ) 195 | -------------------------------------------------------------------------------- /python/test/test_common_layers.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | 4 | import pytest 5 | 6 | import caffe 7 | from caffe.proto import caffe_pb2 8 | from caffe.gradient_check_util import GradientChecker 9 | 10 | 11 | @pytest.fixture(scope="function") 12 | def blob_inplace_init(blob_4_2322): 13 | b, _, _, t = blob_4_2322 14 | t.reshape(*b.shape) 15 | return [t], [t] 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def reshape_layer(blob_4_2322): 20 | b, _, _, t = blob_4_2322 21 | t.reshape(*b.shape) 22 | bottom = [t] 23 | top = [t] 24 | reshape = (2, 12) 25 | # Create Layer 26 | lp = caffe_pb2.LayerParameter() 27 | lp.type = "Python" 28 | lp.python_param.module = "caffe_helper.layers.common_layers" 29 | lp.python_param.layer = "ReshapeLayer" 30 | lp.python_param.param_str = str({'shape': reshape}) 31 | layer = caffe.create_layer(lp) 32 | layer.SetUp(bottom, top) 33 | return layer 34 | 35 | 36 | def test_reshape_layer_forward(reshape_layer, blob_inplace_init): 37 | layer = reshape_layer 38 | bottom, top = blob_inplace_init 39 | bak = bottom[0].data.copy() 40 | layer.Reshape(bottom, top) 41 | layer.Forward(bottom, top) 42 | assert bottom[0].shape == top[0].shape 43 | assert top[0].shape == layer.shape_ 44 | assert np.all(bottom[0].data.flat == bak.flat) 45 | 46 | 47 | def test_reshape_layer_backward(reshape_layer, blob_inplace_init): 48 | layer = reshape_layer 49 | bottom, top = blob_inplace_init 50 | bak = top[0].diff.copy() 51 | layer.Reshape(bottom, top) 52 | layer.Forward(bottom, top) 53 | layer.Backward(top, [True], bottom) 54 | assert bak.shape == bottom[0].shape 55 | assert bak.shape == top[0].shape 56 | assert np.all(bottom[0].diff.flat == bak.flat) 57 | 58 | 59 | @pytest.fixture(scope="module", 60 | params=[(False, False), (False, True), 61 | (True, False), (True, True)]) 62 | def matrix_mult_layer(request): 63 | if request.config.getoption('caffe_cpu'): 64 | raise pytest.skip("MatrixMultLayer requires GPU") 65 | m1 = caffe.Blob((2, 4, 2)) 66 | m2 = caffe.Blob((2, 2, 3)) 67 | t = caffe.Blob([]) 68 | t1, t2 = request.param 69 | if t1: 70 | s = m1.shape 71 | m1.reshape(s[0], s[2], s[1]) 72 | if t2: 73 | s = m2.shape 74 | m2.reshape(s[0], s[2], s[1]) 75 | rng = np.random.RandomState(313) 76 | m1.data[...] = rng.randn(*m1.shape) 77 | m2.data[...] = rng.randn(*m2.shape) 78 | bottom = [m1, m2] 79 | top = [t] 80 | # Create Layer 81 | lp = caffe_pb2.LayerParameter() 82 | lp.type = "Python" 83 | lp.python_param.module = "caffe_helper.layers.common_layers" 84 | lp.python_param.layer = "MatrixMultLayer" 85 | lp.python_param.param_str = str({'t1': t1, 't2': t2}) 86 | layer = caffe.create_layer(lp) 87 | layer.SetUp(bottom, top) 88 | return layer, bottom, top, request.param 89 | 90 | 91 | def test_matrix_mult_layer_forward(matrix_mult_layer): 92 | layer, bottom, top, param = matrix_mult_layer 93 | m1, m2 = bottom 94 | m1, m2 = m1.data.copy(), m2.data.copy() 95 | mo = np.zeros(top[0].shape, np.float32) 96 | for b in xrange(bottom[0].shape[0]): 97 | x, y = m1[b], m2[b] 98 | if param[0]: 99 | x = x.T 100 | if param[1]: 101 | y = y.T 102 | mo[b][...] = np.dot(x, y) 103 | layer.Reshape(bottom, top) 104 | layer.Forward(bottom, top) 105 | assert np.allclose(mo, top[0].data) 106 | 107 | 108 | def test_matrix_mult_layer_backward(matrix_mult_layer): 109 | layer, bottom, top, _ = matrix_mult_layer 110 | checker = GradientChecker(1e-3, 1e-2) 111 | checker.check_gradient_exhaustive( 112 | layer, bottom, top) 113 | 114 | 115 | def test_parameter_layer(): 116 | t = caffe.Blob([]) 117 | bottom = [] 118 | top = [t] 119 | # Create Layer 120 | lp = caffe_pb2.LayerParameter() 121 | lp.type = "Python" 122 | lp.python_param.module = "caffe_helper.layers.common_layers" 123 | lp.python_param.layer = "ParameterLayer" 124 | lp.python_param.param_str = str(dict( 125 | shape=(2, 3, 2, 2), 126 | filler="lambda shape, rng: rng.randn(*shape) * 0.01")) 127 | layer = caffe.create_layer(lp) 128 | layer.SetUp(bottom, top) 129 | assert len(layer.blobs) == 1 130 | assert layer.blobs[0].shape == (2, 3, 2, 2) 131 | param_copy = layer.blobs[0].data.copy() 132 | layer.Forward(bottom, top) 133 | assert np.allclose(top[0].data, param_copy) 134 | checker = GradientChecker(1e-3, 1e-5) 135 | checker.check_gradient_exhaustive( 136 | layer, bottom, top) 137 | 138 | 139 | def test_reduction_layer_mean(blob_4_2322): 140 | b, _, _, t = blob_4_2322 141 | bottom = [b] 142 | top = [t] 143 | # Create Layer 144 | lp = caffe_pb2.LayerParameter() 145 | lp.type = "Python" 146 | lp.python_param.module = "caffe_helper.layers.common_layers" 147 | lp.python_param.layer = "ReductionLayer" 148 | lp.python_param.param_str = str({'axis': 1, 'op': 'mean'}) 149 | layer = caffe.create_layer(lp) 150 | layer.SetUp(bottom, top) 151 | rng = np.random.RandomState(313) 152 | b.data[...] = rng.randn(*b.shape) 153 | layer.Reshape(bottom, top) 154 | layer.Forward(bottom, top) 155 | assert np.all(b.data.mean(layer.axis_).reshape(t.shape) == t.data) 156 | checker = GradientChecker(1e-2, 1e-4) 157 | checker.check_gradient_exhaustive( 158 | layer, bottom, top) 159 | 160 | 161 | def test_reduction_layer_sum(blob_4_2322): 162 | b, _, _, t = blob_4_2322 163 | bottom = [b] 164 | top = [t] 165 | # Create Layer 166 | lp = caffe_pb2.LayerParameter() 167 | lp.type = "Python" 168 | lp.python_param.module = "caffe_helper.layers.common_layers" 169 | lp.python_param.layer = "ReductionLayer" 170 | lp.python_param.param_str = str({'axis': 1, 'op': 'sum'}) 171 | layer = caffe.create_layer(lp) 172 | layer.SetUp(bottom, top) 173 | rng = np.random.RandomState(313) 174 | b.data[...] = rng.randn(*b.shape) 175 | layer.Reshape(bottom, top) 176 | layer.Forward(bottom, top) 177 | assert np.all(b.data.sum(layer.axis_, keepdims=True) == t.data) 178 | checker = GradientChecker(1e-2, 1e-4) 179 | checker.check_gradient_exhaustive( 180 | layer, bottom, top) 181 | 182 | 183 | @pytest.fixture(scope="module", 184 | params=itertools.product( 185 | [None, 1, 2, 3], [1, 2])) 186 | def lpnorm_params(request): 187 | return request.param 188 | 189 | 190 | def test_lp_normalization_layer(blob_4_2322, lpnorm_params): 191 | axis, p = lpnorm_params 192 | b, _, _, t = blob_4_2322 193 | bottom = [b] 194 | top = [t] 195 | # Create Layer 196 | lp = caffe_pb2.LayerParameter() 197 | lp.type = "Python" 198 | lp.python_param.module = "caffe_helper.layers.common_layers" 199 | lp.python_param.layer = "LpNormalizationLayer" 200 | lp.python_param.param_str = str({'axis': axis, 'p': p}) 201 | layer = caffe.create_layer(lp) 202 | layer.SetUp(bottom, top) 203 | rng = np.random.RandomState(313) 204 | b.data[...] = rng.rand(*b.shape) 205 | layer.Reshape(bottom, top) 206 | layer.Forward(bottom, top) 207 | if axis is None: 208 | axis = tuple(range(1, len(bottom[0].shape))) 209 | test_top = b.data / ((b.data**p).sum(axis, keepdims=True) ** (1./p)) 210 | assert np.allclose(test_top, t.data) 211 | checker = GradientChecker(1e-3, 1e-2) 212 | checker.check_gradient_exhaustive( 213 | layer, bottom, top) 214 | 215 | 216 | def test_slice_by_array_layer(blob_4_2322, tmpdir): 217 | path_indexes = tmpdir.join('indexes.mat').strpath 218 | from scipy.io import savemat 219 | indexes = np.array([2, 0]) 220 | savemat(path_indexes, {'indexes': indexes}) 221 | b, _, _, t = blob_4_2322 222 | bottom = [b] 223 | top = [t] 224 | # Create Layer 225 | lp = caffe_pb2.LayerParameter() 226 | lp.type = "Python" 227 | lp.python_param.module = "caffe_helper.layers.common_layers" 228 | lp.python_param.layer = "SliceByArrayLayer" 229 | lp.python_param.param_str = str( 230 | {'path_mat': path_indexes, 'key': 'indexes'}) 231 | layer = caffe.create_layer(lp) 232 | layer.SetUp(bottom, top) 233 | rng = np.random.RandomState(313) 234 | b.data[...] = rng.randn(*b.shape) 235 | layer.Reshape(bottom, top) 236 | layer.Forward(bottom, top) 237 | assert np.all(top[0].data == bottom[0].data[:, indexes, ...]) 238 | checker = GradientChecker(1e-2, 1e-5) 239 | checker.check_gradient_exhaustive( 240 | layer, bottom, top) 241 | 242 | 243 | def test_broadcast_layer(): 244 | ba, c, h, w = [2, 1, 3, 4] 245 | b, t = caffe.Blob([ba, c, h, w]), caffe.Blob([]) 246 | bottom = [b] 247 | top = [t] 248 | # Create Layer 249 | lp = caffe_pb2.LayerParameter() 250 | lp.type = "Python" 251 | lp.python_param.module = "caffe_helper.layers.common_layers" 252 | lp.python_param.layer = "BroadcastLayer" 253 | lp.python_param.param_str = str( 254 | {'axis': 1, 'num': 3}) 255 | layer = caffe.create_layer(lp) 256 | layer.SetUp(bottom, top) 257 | rng = np.random.RandomState(313) 258 | b.data[...] = rng.randn(*b.shape) 259 | layer.Reshape(bottom, top) 260 | layer.Forward(bottom, top) 261 | assert t.shape == (ba, 3, h, w) 262 | for i in xrange(3): 263 | assert np.all(b.data == t.data[:, i:i + 1]) 264 | checker = GradientChecker(1e-2, 1e-5) 265 | checker.check_gradient_exhaustive( 266 | layer, bottom, top) 267 | 268 | 269 | def test_tile_layer(): 270 | ba, c, h, w = [2, 3, 3, 4] 271 | b, t = caffe.Blob([ba, c, h, w]), caffe.Blob([]) 272 | bottom = [b] 273 | top = [t] 274 | # Create Layer 275 | lp = caffe_pb2.LayerParameter() 276 | lp.type = "Python" 277 | lp.python_param.module = "caffe_helper.layers.common_layers" 278 | lp.python_param.layer = "TileLayer" 279 | axis = 1 280 | num = 5 281 | lp.python_param.param_str = str( 282 | {'axis': axis, 'num': num}) 283 | layer = caffe.create_layer(lp) 284 | layer.SetUp(bottom, top) 285 | rng = np.random.RandomState(313) 286 | b.data[...] = rng.randn(*b.shape) 287 | layer.Reshape(bottom, top) 288 | layer.Forward(bottom, top) 289 | assert t.shape == (ba, c * num, h, w) 290 | reps = [1 for _ in t.shape] 291 | reps[axis] = num 292 | assert np.all(np.tile(b.data, reps) == t.data) 293 | checker = GradientChecker(1e-2, 1e-5) 294 | checker.check_gradient_exhaustive( 295 | layer, bottom, top) 296 | 297 | 298 | def test_axpb_layer(blob_4_2322): 299 | b, _, _, t = blob_4_2322 300 | bottom = [b] 301 | top = [t] 302 | # Create Layer 303 | va = 0.7 304 | vb = -0.3 305 | lp = caffe_pb2.LayerParameter() 306 | lp.type = "Python" 307 | lp.python_param.module = "caffe_helper.layers.common_layers" 308 | lp.python_param.layer = "AXPBLayer" 309 | lp.python_param.param_str = str({'a': va, 'b': vb}) 310 | layer = caffe.create_layer(lp) 311 | layer.SetUp(bottom, top) 312 | rng = np.random.RandomState(313) 313 | b.data[...] = rng.randn(*b.shape) 314 | layer.Reshape(bottom, top) 315 | layer.Forward(bottom, top) 316 | assert np.all(va * b.data + vb == t.data) 317 | checker = GradientChecker(1e-3, 1e-2) 318 | checker.check_gradient_exhaustive( 319 | layer, bottom, top) 320 | -------------------------------------------------------------------------------- /python/caffe_helper/layers/common_layers.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from pycuda.elementwise import ElementwiseKernel 3 | 4 | import caffe 5 | from caffe import Layer 6 | import caffe.pycuda_util as pu 7 | 8 | from vision_layers import DownSamplingLayer # For backward compatibility 9 | 10 | 11 | class ReshapeLayer(Layer): 12 | 13 | """Reshape 14 | 15 | You should specify indentical blobs for bottom and top 16 | """ 17 | 18 | def setup(self, bottom, top): 19 | param = eval(self.param_str_) 20 | self.shape_ = param['shape'] 21 | for i, s in enumerate(self.shape_): 22 | if i != 0 and s < 0: 23 | raise ValueError( 24 | "-1 is only allowed at 1st axis: %s" % str(self.shape_)) 25 | if self.shape_[0] < 0: 26 | assert np.prod(self.shape_[1:]) == np.prod(bottom[0].shape[1:]) 27 | else: 28 | assert np.prod(self.shape_) == np.prod(bottom[0].shape) 29 | self.reshape(bottom, top) 30 | 31 | def reshape(self, bottom, top): 32 | self.bottom_shape_ = bottom[0].shape 33 | if self.shape_[0] < 0: 34 | top[0].reshape(self.bottom_shape_[0], *self.shape_[1:]) 35 | else: 36 | top[0].reshape(*self.shape_) 37 | 38 | def forward(self, bottom, top): 39 | pass 40 | 41 | def backward(self, top, propagate_down, bottom): 42 | if propagate_down[0]: 43 | bottom[0].reshape(*self.bottom_shape_) 44 | 45 | 46 | class LogLayer(Layer): 47 | 48 | def setup(self, bottom, top): 49 | param = eval(self.param_str_) 50 | self.offset_ = param['offset'] 51 | self.reshape(bottom, top) 52 | self.k_log_ = ElementwiseKernel( 53 | "float *bottom, float *top, float offset", 54 | "top[i] = log(bottom[i] + offset)", 'elemwise_log') 55 | 56 | def reshape(self, bottom, top): 57 | top[0].reshape(*bottom[0].shape) 58 | 59 | def forward(self, bottom, top): 60 | with pu.caffe_cuda_context(): 61 | self.k_log_( 62 | bottom[0].data_as_pycuda_gpuarray(), 63 | top[0].data_as_pycuda_gpuarray(), 64 | np.float32(self.offset_)) 65 | 66 | 67 | def blas_trans(t): 68 | return 'T' if t else 'N' 69 | 70 | 71 | class MatrixMultLayer(Layer): 72 | 73 | def _check_shape(self, bottom, top): 74 | assert len(bottom) == 2 75 | assert len(top) == 1 76 | assert bottom[0].shape[0] == bottom[1].shape[0] 77 | r1, c1 = bottom[0].shape[1:] 78 | r2, c2 = bottom[1].shape[1:] 79 | if self.t1_: 80 | r1, c1 = c1, r1 81 | if self.t2_: 82 | r2, c2 = c2, r2 83 | assert c1 == r2 84 | self.outshape_ = r1, c2 85 | 86 | def setup(self, bottom, top): 87 | param = eval(self.param_str_) 88 | self.t1_ = param.get('t1', False) 89 | self.t2_ = param.get('t2', False) 90 | self.reshape(bottom, top) 91 | 92 | def reshape(self, bottom, top): 93 | self._check_shape(bottom, top) 94 | batch_size = bottom[0].shape[0] 95 | shape = (batch_size,) + self.outshape_ 96 | top[0].reshape(*shape) 97 | 98 | def forward(self, bottom, top): 99 | with pu.caffe_cuda_context(): 100 | h = caffe.cublas_handle() 101 | import scikits.cuda.linalg as linalg 102 | mat1 = bottom[0].data_as_pycuda_gpuarray() 103 | mat2 = bottom[1].data_as_pycuda_gpuarray() 104 | mato = top[0].data_as_pycuda_gpuarray() 105 | for b in xrange(bottom[0].shape[0]): 106 | linalg.dot(mat1[b], mat2[b], 107 | transa=blas_trans(self.t1_), 108 | transb=blas_trans(self.t2_), 109 | handle=h, out=mato[b]) 110 | 111 | def backward(self, top, propagate_down, bottom): 112 | with pu.caffe_cuda_context(): 113 | h = caffe.cublas_handle() 114 | import scikits.cuda.linalg as linalg 115 | top_diff = top[0].diff_as_pycuda_gpuarray() 116 | ts = [self.t1_, self.t2_] 117 | for i in xrange(len(bottom)): 118 | if not propagate_down[i]: 119 | continue 120 | diff = bottom[i].diff_as_pycuda_gpuarray() 121 | data = bottom[(i + 1) % 2].data_as_pycuda_gpuarray() 122 | # Belew 3 conditions are complicated and might be hard to 123 | # understand. 124 | swap = ts[i] ^ bool(i) 125 | t1 = ts[i] 126 | t2 = (not t1) ^ ts[(i + 1) % 2] 127 | for b in xrange(bottom[0].shape[0]): 128 | x = top_diff[b] 129 | y = data[b] 130 | t1_, t2_ = t1, t2 131 | if swap: 132 | x, y = y, x 133 | t1_, t2_ = t2_, t1_ 134 | linalg.dot(x, y, 135 | transa=blas_trans(t1_), transb=blas_trans(t2_), 136 | handle=h, out=diff[b]) 137 | 138 | 139 | class ParameterLayer(Layer): 140 | 141 | """ 142 | ParameterLayer is holding a parameter blob and feeds the data of the blob 143 | to the top blob directly. Note this always accumulates param grad, so it 144 | needs accum-grad branch. 145 | """ 146 | 147 | def setup(self, bottom, top): 148 | param = eval(self.param_str_) 149 | self.shape_ = param['shape'] 150 | assert len(bottom) == 0 151 | assert len(top) == 1 152 | self.reshape(bottom, top) 153 | 154 | # Initialize parameter 155 | if len(self.blobs) > 0: 156 | assert self.blobs[0].shape == self.shape_ 157 | return 158 | seed = param.get('seed', 313) 159 | rng = np.random.RandomState(seed) 160 | # filler must be a form of lambda shape, rng: 161 | filler = eval(param['filler']) 162 | self.blobs.append(caffe.Blob(self.shape_)) 163 | self.blobs[0].data[...] = filler(top[0].data.shape, rng) 164 | 165 | def reshape(self, bottom, top): 166 | top[0].reshape(*self.shape_) 167 | 168 | def forward(self, bottom, top): 169 | top[0].data[...] = self.blobs[0].data 170 | 171 | def backward(self, top, propagate_down, bottom): 172 | self.blobs[0].diff[...] += top[0].diff 173 | 174 | 175 | class ReductionLayer(Layer): 176 | 177 | """ 178 | Parameters 179 | ---------- 180 | 181 | :axis: Axis to be reduced 182 | :op: Operation of reduction. "mean" is only supported so far. 183 | """ 184 | 185 | def setup(self, bottom, top): 186 | param = eval(self.param_str_) 187 | self.axis_ = param['axis'] 188 | self.op_ = param['op'] 189 | if self.op_ not in ['mean', 'sum']: 190 | raise ValueError("Unsupported op type: %s" % self.op_) 191 | self.reshape(bottom, top) 192 | 193 | def reshape(self, bottom, top): 194 | assert len(bottom) == 1 195 | assert len(top) == 1 196 | assert len(bottom[0].shape) >= self.axis_ 197 | shape = list(bottom[0].shape) 198 | shape[self.axis_] = 1 199 | top[0].reshape(*shape) 200 | 201 | def forward(self, bottom, top): 202 | if self.op_ == 'mean': 203 | top[0].data[...] = \ 204 | bottom[0].data.mean(self.axis_, keepdims=True) 205 | elif self.op_ == 'sum': 206 | top[0].data[...] = \ 207 | bottom[0].data.sum(self.axis_, keepdims=True) 208 | else: 209 | raise ValueError("Unsupported op type: %s" % self.op_) 210 | 211 | def backward(self, top, propagate_down, bottom): 212 | if not propagate_down[0]: 213 | return 214 | if self.op_ == 'mean': 215 | bottom[0].diff[...] = top[0].diff / bottom[0].shape[self.axis_] 216 | elif self.op_ == 'sum': 217 | bottom[0].diff[...] = top[0].diff 218 | else: 219 | raise ValueError("Unsupported op type: %s" % self.op_) 220 | 221 | 222 | class LpNormalizationLayer(Layer): 223 | 224 | """ 225 | Parameters 226 | ---------- 227 | 228 | :axis: Axis to be normalized. None means normalize over all dimensions. 229 | :p: if p=1, no abs L1 normalization. if p=2, l2 normalization 230 | 231 | Forward 232 | ------- 233 | z_i = x_i / (\sum_j x_j^p)^{1/p} 234 | """ 235 | 236 | def build_theano_functions(self, bottom, top): 237 | # building Theano functions 238 | from caffe_helper.theano_util import init_theano 239 | init_theano() 240 | 241 | import theano as tn 242 | import theano.tensor as T 243 | p = np.float32(self.p_) 244 | axis = self.axis_ 245 | if axis is None: 246 | axis = tuple(range(1, len(bottom[0].shape))) 247 | 248 | # blob to CudaNdArray 249 | # Forward pass 250 | Tensor = T.TensorType('float32', [False] * len(bottom[0].shape)) 251 | s_x = Tensor('x') # bottom data 252 | s_dz = Tensor('dz') # top diff 253 | s_z = s_x * ( 254 | (s_x**p).sum(axis, keepdims=True)**(np.float32(-1./p))) 255 | # See http://goo.gl/wIVRsP for `tn.Out(x, borrow=True)` 256 | self.f_forward = tn.function([s_x], tn.Out(s_z, borrow=True)) 257 | 258 | # Backward pass 259 | s_l = (s_dz * s_z).sum() 260 | s_grad = tn.grad(s_l, wrt=s_x) 261 | self.f_backward = tn.function([s_x, s_dz], tn.Out(s_grad, borrow=True)) 262 | 263 | def setup(self, bottom, top): 264 | param = eval(self.param_str_) 265 | self.axis_ = param.get('axis', None) 266 | self.p_ = param.get('p', 1) 267 | self.reshape(bottom, top) 268 | self.build_theano_functions(bottom, top) 269 | 270 | def reshape(self, bottom, top): 271 | assert len(bottom) == 1 272 | assert len(top) == 1 273 | if self.axis_ is not None: 274 | assert len(bottom[0].shape) >= self.axis_ 275 | top[0].reshape(*bottom[0].shape) 276 | 277 | def forward(self, bottom, top): 278 | from caffe_helper.theano_util import blob_to_CudaNdArray 279 | b, _ = blob_to_CudaNdArray(bottom[0]) 280 | t, _ = blob_to_CudaNdArray(top[0]) 281 | t[...] = self.f_forward(b) 282 | 283 | def backward(self, top, propagate_down, bottom): 284 | from caffe_helper.theano_util import blob_to_CudaNdArray 285 | if not propagate_down[0]: 286 | return 287 | b, bdiff = blob_to_CudaNdArray(bottom[0]) 288 | _, tdiff = blob_to_CudaNdArray(top[0]) 289 | bdiff[...] = self.f_backward(b, tdiff) 290 | 291 | 292 | class SliceByArrayLayer(Layer): 293 | 294 | """ 295 | Slicing 1st axis with an integer array 296 | """ 297 | 298 | def setup(self, bottom, top): 299 | from scipy.io import loadmat 300 | param = eval(self.param_str_) 301 | self.indexes_ = loadmat(param['path_mat'])[param['key']].flatten() 302 | assert np.unique(self.indexes_).size == self.indexes_.size, \ 303 | 'Indexes must be unique each other.' 304 | self.axis_ = param.get('axis', 1) 305 | assert self.axis_ == 1, 'Now only axis=1 is supported.' 306 | self.reshape(bottom, top) 307 | 308 | def reshape(self, bottom, top): 309 | assert len(bottom) == 1 310 | assert len(top) == 1 311 | assert len(bottom[0].shape) > self.axis_ 312 | shape = list(bottom[0].shape) 313 | shape[self.axis_] = self.indexes_.size 314 | top[0].reshape(*shape) 315 | 316 | def forward(self, bottom, top): 317 | top[0].data[...] = bottom[0].data[:, self.indexes_, ...] 318 | 319 | def backward(self, top, propagate_down, bottom): 320 | if not propagate_down[0]: 321 | return 322 | bottom[0].diff[...] = 0 323 | bottom[0].diff[:, self.indexes_, ...] = top[0].diff 324 | 325 | 326 | class BroadcastLayer(Layer): 327 | 328 | def setup(self, bottom, top): 329 | param = eval(self.param_str_) 330 | self.axis_ = param['axis'] 331 | self.num_ = param['num'] 332 | self.reshape(bottom, top) 333 | 334 | def reshape(self, bottom, top): 335 | assert len(bottom) == 1 336 | assert len(top) == 1 337 | assert bottom[0].shape[self.axis_] == 1 338 | shape = list(bottom[0].shape) 339 | shape[self.axis_] = self.num_ 340 | top[0].reshape(*shape) 341 | 342 | def forward(self, bottom, top): 343 | top[0].data[...] = bottom[0].data 344 | 345 | def backward(self, top, propagate_down, bottom): 346 | if not propagate_down[0]: 347 | return 348 | bottom[0].diff[...] = top[0].diff.sum(self.axis_, keepdims=True) 349 | 350 | 351 | class TileLayer(Layer): 352 | 353 | def setup(self, bottom, top): 354 | param = eval(self.param_str_) 355 | self.axis_ = param['axis'] 356 | self.num_ = param['num'] 357 | self.reshape(bottom, top) 358 | 359 | def reshape(self, bottom, top): 360 | assert len(bottom) == 1 361 | assert len(top) == 1 362 | shape = list(bottom[0].shape) 363 | shape[self.axis_] *= self.num_ 364 | top[0].reshape(*shape) 365 | 366 | def forward(self, bottom, top): 367 | reps = [1 for _ in bottom[0].shape] 368 | reps[self.axis_] = self.num_ 369 | top[0].data[...] = np.tile(bottom[0].data, reps) 370 | 371 | def backward(self, top, propagate_down, bottom): 372 | if not propagate_down[0]: 373 | return 374 | shape = bottom[0].shape 375 | shape2 = shape[:self.axis_] + (self.num_,) + shape[self.axis_:] 376 | bottom[0].diff[...] = top[0].diff.reshape(shape2).sum(self.axis_) 377 | 378 | 379 | class AXPBLayer(Layer): 380 | 381 | def setup(self, bottom, top): 382 | param = eval(self.param_str_) 383 | self.a_ = param.get('a', 1.0) 384 | self.b_ = param.get('b', 0.0) 385 | 386 | # Initialize parameters 387 | if len(self.blobs) > 0: 388 | assert len(self.blobs) == 1 389 | assert self.blobs[0].shape == (2,) 390 | return 391 | self.blobs.append(caffe.Blob([2, ])) 392 | self.blobs[0].data[...] = [self.a_, self.b_] 393 | self.reshape(bottom, top) 394 | 395 | def reshape(self, bottom, top): 396 | assert len(bottom) == 1 397 | assert len(top) == 1 398 | top[0].reshape(*bottom[0].shape) 399 | 400 | def forward(self, bottom, top): 401 | top[0].data[...] = self.blobs[0].data[0] * bottom[0].data \ 402 | + self.blobs[0].data[1] 403 | 404 | def backward(self, top, propagate_down, bottom): 405 | # Propagate to param 406 | self.blobs[0].diff[0] += np.sum(bottom[0].data * top[0].diff) 407 | self.blobs[0].diff[1] += np.sum(top[0].diff) 408 | if not propagate_down[0]: 409 | return 410 | bottom[0].diff[...] = self.blobs[0].data[0] * top[0].diff 411 | -------------------------------------------------------------------------------- /python/caffe_helper/layers/loss_layers.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import pycuda.gpuarray as gpuarray 4 | from pycuda.compiler import SourceModule 5 | from pycuda.reduction import ReductionKernel 6 | from pycuda.elementwise import ElementwiseKernel 7 | 8 | import caffe 9 | from caffe import Layer 10 | import caffe.pycuda_util as pu 11 | 12 | dtype = np.float32 13 | 14 | 15 | class ScaleInvariantL2LossLayer(Layer): 16 | 17 | """Scale Invariant L2 Loss which is described in NYU-depth paper. 18 | You must specify loss_weight in LayerParameter. 19 | """ 20 | 21 | def setup(self, bottom, top): 22 | assert len(bottom) == 3 23 | assert len(top) == 1 24 | # parameter 25 | param = eval(self.param_str_) 26 | self.lambda_ = param['lambda'] 27 | self.clip_gradient_ = param.get('clip_gradient', None) 28 | # Create CUDA function 29 | with pu.caffe_cuda_context(): 30 | self.k_masked_diff_ = ElementwiseKernel( 31 | "float *diff, float *pred, float *label, float *mask", 32 | "diff[i] = (pred[i] - label[i]) * mask[i]", 'masked_diff') 33 | self.k_squared_ = ElementwiseKernel( 34 | "float *diff, float *diff2", 35 | "diff2[i] = diff[i] * diff[i]", 'squared') 36 | self.k_ensure_mask_sum_ = ElementwiseKernel( 37 | "float *mask_sum", 38 | "mask_sum[i] = max(mask_sum[i], 1.0f)", 'ensure_mask_sum') 39 | if self.clip_gradient_ is not None: 40 | self.k_clip_gradient = ElementwiseKernel( 41 | "float *diff", 42 | "diff[i] = fmaxf(-{0}, fminf(diff[i], {0}))".format( 43 | self.clip_gradient_), 44 | 'clip_gradient') 45 | # This should be computed more faster by cublasSdot 46 | self.k_sum_ = ReductionKernel( 47 | dtype, neutral="0", 48 | reduce_expr="a+b", map_expr="d[i]", 49 | arguments="float *d") 50 | self.k_squred_sum_ = ReductionKernel( 51 | dtype, neutral="0", 52 | reduce_expr="a+b", map_expr="d[i] * d[i]", 53 | arguments="float *d") 54 | self.k_div_sum_ = ReductionKernel( 55 | dtype, neutral="0", 56 | reduce_expr="a+b", 57 | map_expr="d[i] / m[i]", 58 | arguments="float *d, float *m") 59 | self.k_div_squared_sum_ = ReductionKernel( 60 | dtype, neutral="0", 61 | reduce_expr="a+b", 62 | map_expr="d[i] * d[i] / (m[i] * m[i])", 63 | arguments="float *d, float *m") 64 | func_backward = SourceModule( 65 | """ 66 | #include 67 | __global__ void backward(float *pred, float *label, float *mask, 68 | float *diff_sum, float *mask_sum, int count, int stride, int sgn, 69 | int batch_size, float lambda, float loss_weight, float *diff) { 70 | CUDA_KERNEL_LOOP(i, count) { 71 | diff[i] = loss_weight * mask[i] * 2.0f * sgn / mask_sum[i / stride] 72 | / batch_size * ((pred[i] - label[i]) 73 | - lambda / mask_sum[i / stride] * diff_sum[i / stride]); 74 | } 75 | } 76 | """, include_dirs=pu.caffe_include_dirs).get_function("backward") 77 | func_backward.prepare("PPPPPiiiiffP") 78 | 79 | def _func_backward(pred, label, mask, ds, ms, sgn, loss_weight, 80 | diff): 81 | bg = pu.block_and_grid(pred.size) 82 | batch_size = pred.shape[0] 83 | count = pred.size 84 | stride = pred.size / pred.shape[0] 85 | func_backward.prepared_call( 86 | bg['grid'], bg['block'], 87 | pred.gpudata, label.gpudata, mask.gpudata, ds.gpudata, 88 | ms.gpudata, count, stride, sgn, batch_size, 89 | self.lambda_, loss_weight, 90 | diff.gpudata) 91 | self.k_backward_ = _func_backward 92 | self.batch_size_ = 0 93 | self.dim_ = 0 94 | self.reshape(bottom, top) 95 | 96 | def reshape(self, bottom, top): 97 | with pu.caffe_cuda_context(): 98 | 99 | batch_size = bottom[0].shape[0] 100 | if self.batch_size_ != batch_size: 101 | self.batch_size_ = batch_size 102 | self.diff_sum_ = gpuarray.zeros((batch_size, 1), dtype) 103 | self.diff2_sum_ = gpuarray.zeros((batch_size, 1), dtype) 104 | self.mask_sum_ = gpuarray.zeros((batch_size, 1), dtype) 105 | dim = int(np.prod(bottom[0].shape[1:])) 106 | if self.dim_ != dim: 107 | self.dim_ = dim 108 | self.multipier_sum_ = gpuarray.zeros((dim, 1), dtype) 109 | self.multipier_sum_.fill(dtype(1.0)) 110 | top[0].reshape() 111 | 112 | def forward(self, bottom, top): 113 | """ 114 | 115 | """ 116 | with pu.caffe_cuda_context(): 117 | h = caffe.cublas_handle() 118 | batch_size = bottom[0].shape[0] 119 | dim = bottom[0].count / bottom[0].shape[0] 120 | pred = bottom[0].data_as_pycuda_gpuarray() 121 | label = bottom[1].data_as_pycuda_gpuarray() 122 | mask = bottom[2].data_as_pycuda_gpuarray() 123 | # Use bottom[0,1].diff as temporary buffer 124 | diff = bottom[0].diff_as_pycuda_gpuarray() 125 | diff2 = bottom[1].diff_as_pycuda_gpuarray() 126 | # Compute diff 127 | self.k_masked_diff_(diff, pred, label, mask) 128 | self.k_squared_(diff, diff2) 129 | import scikits.cuda.linalg as linalg 130 | # This needs scikits.cuda 0.5.0a3 or later 131 | # (sudo) pip install scikits.cuda=>0.5.0a3 132 | linalg.dot(diff.reshape(batch_size, dim), self.multipier_sum_, 133 | handle=h, out=self.diff_sum_) 134 | linalg.dot(diff2.reshape(batch_size, dim), self.multipier_sum_, 135 | handle=h, out=self.diff2_sum_) 136 | linalg.dot(mask.reshape(batch_size, dim), self.multipier_sum_, 137 | handle=h, out=self.mask_sum_) 138 | self.k_ensure_mask_sum_(self.mask_sum_) 139 | term1 = self.k_div_sum_(self.diff2_sum_, self.mask_sum_) 140 | term2 = self.k_div_squared_sum_(self.diff_sum_, self.mask_sum_) 141 | top[0].data[...] = (term1.get() - self.lambda_ * term2.get()) \ 142 | / batch_size 143 | 144 | def backward(self, top, propagate_down, bottom): 145 | """ 146 | Compute @f$\frac{\partial {\cal L}}{\partial y_bi}=\frac{\partial {\cal L}}{\partial d_i} \frac{\partial d_i} {\partial y_bi}@f$. 147 | @f$\frac{\partial {\cal L}}{\partial d_i}=\frac{2}{n}d_i' \left(d_i - \frac{\lambda}{n}\sum_j d_j\right). 148 | """ 149 | with pu.caffe_cuda_context(): 150 | pred = bottom[0].data_as_pycuda_gpuarray() 151 | label = bottom[1].data_as_pycuda_gpuarray() 152 | mask = bottom[2].data_as_pycuda_gpuarray() 153 | for i in xrange(len(bottom) - 1): 154 | if propagate_down[i]: 155 | diff = bottom[i].diff_as_pycuda_gpuarray() 156 | sgn = 1 if i == 0 else - 1 157 | self.k_backward_( 158 | pred, label, mask, self.diff_sum_, self.mask_sum_, sgn, 159 | top[0].diff, diff) 160 | if self.clip_gradient_ is not None: 161 | self.k_clip_gradient(diff) 162 | 163 | 164 | class DSSIMLayer(Layer): 165 | 166 | def setup(self, bottom, top): 167 | from caffe_helper.theano_util import init_theano 168 | init_theano() 169 | 170 | import theano as tn 171 | import theano.tensor as T 172 | from theano.tensor.signal.conv import conv2d 173 | assert len(bottom) >= 2 174 | assert len(bottom) <= 3 175 | assert len(top) == 1 176 | # parameter 177 | self.K_ = [0.01, 0.03] 178 | self.L_ = 1.0 179 | param = eval(self.param_str_) 180 | self.hsize_ = param.get('hsize', 11) 181 | self.sigma_ = param.get('sigma', 1.5) 182 | assert self.hsize_ % 2 == 1 183 | hsize = self.hsize_ 184 | sigma = self.sigma_ 185 | C1 = (self.K_[0] * self.L_) ** 2 186 | C2 = (self.K_[1] * self.L_) ** 2 187 | # Creating gaussian filter 188 | x = np.exp(-0.5 * ((np.arange(hsize) - int(hsize / 2)) ** 2) / 189 | (sigma ** 2)) 190 | filt = x.reshape(-1, 1) * x.reshape(1, -1) 191 | filt /= filt.sum() 192 | 193 | # Build a Theano function which computes SSIM and its gradients wrt two 194 | # images 195 | simg1_in = T.ftensor3() 196 | simg2_in = T.ftensor3() 197 | 198 | if len(bottom) > 2: 199 | smask = T.ftensor3() 200 | sk = T.sum(simg1_in * simg2_in * smask) \ 201 | / T.sum(simg1_in * simg1_in * smask) 202 | simg1 = sk * simg1_in * smask 203 | simg2 = simg2_in * smask 204 | else: 205 | sk = T.sum(simg1_in * simg2_in) \ 206 | / T.sum(simg1_in * simg1_in) 207 | simg1 = sk * simg1_in 208 | simg2 = simg2_in 209 | sfilt = tn.shared(filt.astype(np.float32)) 210 | smu1 = conv2d(simg1, sfilt) 211 | smu2 = conv2d(simg2, sfilt) 212 | smu1_sq = smu1 * smu1 213 | smu2_sq = smu2 * smu2 214 | smu1_mu2 = smu1 * smu2 215 | ssig1_sq = conv2d(simg1 * simg1, sfilt) - smu1_sq 216 | ssig2_sq = conv2d(simg2 * simg2, sfilt) - smu2_sq 217 | ssig12 = conv2d(simg1 * simg2, sfilt) - smu1_mu2 218 | sssim = ( 219 | ((2 * smu1_mu2 + C1) * (2 * ssig12 + C2)) 220 | / ((smu1_sq + smu2_sq + C1) * (ssig1_sq + ssig2_sq + C2)) 221 | ).mean() 222 | sdssim = (1 - sssim) / 2 223 | gimg1, gimg2 = tn.grad(sdssim, [simg1_in, simg2_in]) 224 | if len(bottom) > 2: 225 | self.fdssim_with_grad = tn.function( 226 | [simg1_in, simg2_in, smask], [sdssim, gimg1, gimg2]) 227 | else: 228 | self.fdssim_with_grad = tn.function( 229 | [simg1_in, simg2_in], [sdssim, gimg1, gimg2]) 230 | 231 | def reshape(self, bottom, top): 232 | assert bottom[0].shape == bottom[1].shape 233 | assert len(bottom[0].shape) == 4 234 | top[0].reshape() 235 | 236 | def forward(self, bottom, top): 237 | dssim = np.float64(0.0) 238 | for i in xrange(bottom[0].shape[0]): 239 | if len(bottom) > 2: 240 | s, g1, g2 = self.fdssim_with_grad( 241 | bottom[0].data[i], bottom[1].data[i], bottom[2].data[i]) 242 | else: 243 | s, g1, g2 = self.fdssim_with_grad( 244 | bottom[0].data[i], bottom[1].data[i]) 245 | dssim += s 246 | bottom[0].diff[i] = g1 / bottom[0].shape[0] 247 | bottom[1].diff[i] = g2 / bottom[1].shape[0] 248 | top[0].data[...] = dssim / bottom[0].shape[0] 249 | 250 | def backward(self, top, propagate_down, bottom): 251 | bottom[0].diff[...] *= top[0].diff 252 | bottom[1].diff[...] *= top[0].diff 253 | 254 | 255 | class LogitLossLayer(Layer): 256 | 257 | """ 258 | bottoms: 259 | y : (N x ....) in R, scores 260 | t : (N x ....) in [-1, 0, 1], targets, target 0 ignoring the data 261 | tops: 262 | l : (0) loss 263 | """ 264 | 265 | def setup(self, bottom, top): 266 | from caffe_helper.theano_util import init_theano 267 | init_theano() 268 | 269 | import theano as tn 270 | import theano.tensor as T 271 | assert len(bottom) == 2 272 | assert len(top) == 1 273 | s_y = T.matrix('y') # y in [-inf, inf] 274 | s_t = T.matrix('t') # t in {-1, 0, 1} where 0 is ignored 275 | s_dloss = T.scalar('dloss') 276 | # Forward 277 | # s_loss = T.mean(abs(s_t) * T.log1p(T.exp(-s_y * s_t))) # unstable 278 | s_loss = -T.sum( 279 | abs(s_t) * ( 280 | s_y * ((s_t >= 0) - (s_y >= 0)) - T.log1p(T.exp(-abs(s_y)))))\ 281 | / T.maximum(T.sum(abs(s_t)), 1) 282 | # Backward 283 | s_p = 1 / (1 + T.exp(-s_y)) 284 | s_dy = s_dloss * abs(s_t) * (s_p - (s_t >= 0)) / \ 285 | T.maximum(T.sum(abs(s_t)), 1) 286 | 287 | def _o(s): 288 | return tn.Out(s, borrow=True) 289 | self.tn_forward = tn.function([s_y, s_t], s_loss) 290 | self.tn_backward = tn.function([s_y, s_t, s_dloss], _o(s_dy)) 291 | 292 | def reshape(self, bottom, top): 293 | assert bottom[0].shape == bottom[1].shape, \ 294 | "{} == {}".format(tuple(bottom[0].shape), tuple(bottom[1].shape)) 295 | top[0].reshape() 296 | 297 | def forward(self, bottom, top): 298 | from caffe_helper.theano_util import blob_to_CudaNdArray 299 | y, _ = blob_to_CudaNdArray(bottom[0]) 300 | t, _ = blob_to_CudaNdArray(bottom[1]) 301 | l, _ = blob_to_CudaNdArray(top[0]) 302 | s = (y.shape[0], int(np.prod(y.shape[1:]))) 303 | l[...] = self.tn_forward(y.reshape(s), t.reshape(s)) 304 | 305 | def backward(self, top, propagate_down, bottom): 306 | from caffe_helper.theano_util import blob_to_CudaNdArray 307 | y, dy = blob_to_CudaNdArray(bottom[0]) 308 | t, _ = blob_to_CudaNdArray(bottom[1]) 309 | _, dl = blob_to_CudaNdArray(top[0]) 310 | s = (y.shape[0], int(np.prod(y.shape[1:]))) 311 | dy[...] = self.tn_backward( 312 | y.reshape(s), t.reshape(s), dl).reshape(dy.shape) 313 | 314 | 315 | class BinaryAccuracyLayer(Layer): 316 | 317 | """ 318 | bottoms: 319 | y : (N x ....) in R, scores 320 | t : (N x ....) in [-1, 0, 1], targets, target 0 ignoring the data 321 | tops: 322 | l : (0) loss 323 | """ 324 | 325 | def setup(self, bottom, top): 326 | from caffe_helper.theano_util import init_theano 327 | init_theano() 328 | 329 | import theano as tn 330 | import theano.tensor as T 331 | assert len(bottom) == 2 332 | assert len(top) == 1 333 | s_y = T.matrix('y') # y in [-inf, inf] 334 | s_t = T.matrix('t') # t in {-1, 0, 1} where 0 is ignored 335 | # Forward 336 | s_loss = T.sum(abs(s_t) * T.eq((s_y >= 0), (s_t >= 0))) \ 337 | / T.maximum(T.sum(abs(s_t)), 1) 338 | 339 | def _o(s): 340 | return tn.Out(s, borrow=True) 341 | self.tn_forward = tn.function([s_y, s_t], _o(s_loss)) 342 | 343 | def reshape(self, bottom, top): 344 | assert bottom[0].shape == bottom[1].shape, \ 345 | "{} == {}".format(tuple(bottom[0].shape), tuple(bottom[1].shape)) 346 | top[0].reshape() 347 | 348 | def forward(self, bottom, top): 349 | from caffe_helper.theano_util import blob_to_CudaNdArray 350 | y, _ = blob_to_CudaNdArray(bottom[0]) 351 | t, _ = blob_to_CudaNdArray(bottom[1]) 352 | l, _ = blob_to_CudaNdArray(top[0]) 353 | s = (y.shape[0], int(np.prod(y.shape[1:]))) 354 | l[...] = self.tn_forward(y.reshape(s), t.reshape(s)) 355 | 356 | def backward(self, top, propagate_down, bottom): 357 | pass 358 | 359 | 360 | class CrossEntropyLossLayer(Layer): 361 | 362 | """Unlike SoftmaxLoss Layer, this layer assumes the input is already 363 | normalized probability""" 364 | 365 | def setup(self, bottom, top): 366 | self.reshape(bottom, top) 367 | from caffe_helper.theano_util import init_theano 368 | init_theano() 369 | 370 | import theano as tn 371 | import theano.tensor as T 372 | shape1 = bottom[0].shape # prediction 373 | shape2 = bottom[1].shape # label 374 | s_p = T.TensorType('float32', [False] * len(shape1))('p') 375 | s_t = T.TensorType('float32', [False] * len(shape2))('t') 376 | 377 | # Forward pass 378 | FLTMIN = np.finfo(np.float32).tiny 379 | s_l = -T.mean( 380 | T.log(T.maximum(FLTMIN, s_p.flatten(2)))[ 381 | T.arange(s_t.shape[0]), T.cast(s_t, 'int32')] 382 | ) 383 | self.f_forward = tn.function( 384 | [s_p, s_t], tn.Out(s_l, borrow=True)) 385 | 386 | # Backward pass 387 | s_dz = T.fscalar('dz') 388 | sg_p = tn.grad(s_dz * s_l, wrt=s_p) 389 | self.f_backward = tn.function( 390 | [s_p, s_t, s_dz], tn.Out(sg_p, borrow=True)) 391 | 392 | def reshape(self, bottom, top): 393 | assert len(bottom) == 2 394 | assert len(top) == 1 395 | top[0].reshape() 396 | 397 | def forward(self, bottom, top): 398 | from caffe_helper.theano_util import blob_to_CudaNdArray 399 | p, _ = blob_to_CudaNdArray(bottom[0]) 400 | t, _ = blob_to_CudaNdArray(bottom[1]) 401 | l, _ = blob_to_CudaNdArray(top[0]) 402 | l[...] = self.f_forward(p, t) 403 | 404 | def backward(self, top, propagate_down, bottom): 405 | if not propagate_down[0]: 406 | return 407 | assert not propagate_down[1] 408 | 409 | from caffe_helper.theano_util import blob_to_CudaNdArray 410 | p, dp = blob_to_CudaNdArray(bottom[0]) 411 | t, _ = blob_to_CudaNdArray(bottom[1]) 412 | l, dz = blob_to_CudaNdArray(top[0]) 413 | dp[...] = self.f_backward(p, t, dz) 414 | -------------------------------------------------------------------------------- /python/caffe_helper/layers/data_layers.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor 2 | import csv 3 | from logging import getLogger, StreamHandler, DEBUG, INFO 4 | 5 | import os 6 | from os.path import join, expandvars, expanduser 7 | import time 8 | 9 | import numpy as np 10 | 11 | import cv2 12 | from caffe import Layer 13 | from caffe_helper.rand_seed import seed as ENV_SEED 14 | 15 | 16 | def pad_with_tblr(img, t, b, l, r, 17 | border_mode=cv2.BORDER_CONSTANT, border_value=0): 18 | if border_mode == cv2.BORDER_CONSTANT: 19 | h, w = img.shape[:2] 20 | hh, ww = h + t + b, w + l + r 21 | ret = np.ones((hh, ww) + img.shape[2:], img.dtype) * border_value 22 | ret[t:t+h, l:l+w, ...] = img 23 | return ret 24 | return cv2.copyMakeBorder(img, t, b, l, r, 25 | borderType=border_mode) 26 | 27 | 28 | def pad_with_min_shape(img, min_shape, border_mode=cv2.BORDER_CONSTANT, 29 | border_value=0): 30 | """""" 31 | hh, ww = min_shape 32 | h, w = img.shape[:2] 33 | if hh <= h and ww <= w: 34 | return img 35 | hh, ww = (max(hh, h), max(ww, w)) 36 | t = int((hh - h) // 2) 37 | b = t + int((hh - h) % 2) 38 | l = int((ww - w) // 2) 39 | r = l + int((ww - w) % 2) 40 | if border_mode == cv2.BORDER_CONSTANT: 41 | ret = np.ones((hh, ww) + img.shape[2:], img.dtype) * border_value 42 | ret[t:t+h, l:l+w, ...] = img 43 | return ret 44 | return cv2.copyMakeBorder(img, t, b, l, r, border_mode, border_value) 45 | 46 | 47 | class ImageTransformer(object): 48 | 49 | logger = getLogger('ImageTransformer') 50 | handler = StreamHandler() 51 | handler.setLevel(INFO) 52 | logger.setLevel(INFO) 53 | logger.addHandler(handler) 54 | 55 | @staticmethod 56 | def get_params( 57 | height=-1, width=-1, crop_size=None, pad=0, mirror=False, 58 | scale=None, mean_value=None, color=True, channel_swap=None, 59 | random_crop=False, random_seed=313): 60 | """""" 61 | return locals() 62 | 63 | def __init__(self, param): 64 | self.random_seed_ = param.get('random_seed', 313) 65 | if ENV_SEED is not None: 66 | self.random_seed_ = ENV_SEED 67 | self.mirror_ = param.get('mirror', False) 68 | self.crop_size_ = param.get('crop_size', None) 69 | self.random_crop_ = param.get('random_crop', False) 70 | self.random_scale_ = param.get('random_scale', None) 71 | self.random_noise_ = param.get('random_noise', None) 72 | self.random_rotation_ = param.get('random_rotation', None) 73 | self.mean_value_ = param.get('mean_value', None) 74 | self.scale_ = param.get('scale', None) 75 | self.color_ = param.get('color', True) 76 | self.pad_ = param.get('pad', 0) 77 | self.minshape_ = param.get('minshape', 0) 78 | self.height_ = param.get('height', -1) 79 | self.width_ = param.get('width', -1) 80 | self.channel_swap_ = param.get('channel_swap', None) 81 | self.border_mode_ = getattr( 82 | cv2, param.get('border_mode', 'BORDER_CONSTANT')) 83 | self.border_value_ = param.get('border_value', 0) 84 | self.float_ = param.get('float', True) 85 | self.rng_mirror_ = np.random.RandomState(self.random_seed_) 86 | self.rng_crop_ = np.random.RandomState(self.random_seed_ + 1) 87 | self.rng_scale_ = np.random.RandomState(self.random_seed_ + 2) 88 | self.rng_noise_ = np.random.RandomState(self.random_seed_ + 3) 89 | self.rng_rotation_ = np.random.RandomState(self.random_seed_ + 4) 90 | self.check_params() 91 | self.logger.info( 92 | (os.linesep + ' ').join( 93 | map(lambda kv: "%s = %s" % (kv[0], kv[1]), 94 | filter(lambda kv: kv[0] in ( 95 | 'random_seed_', 'mirror_', 'crop_size_', 'mean_value_', 96 | 'scale_', 'color_', 'pad_', 'height_', 'width_', 97 | 'channel_swap_', 'random_crop_', 'random_scale_', 98 | 'random_noise_', 'random_rotation_',), 99 | self.__dict__.iteritems())))) 100 | c = 3 if self.color_ else 1 101 | if self.crop_size_ is None: 102 | h = self.height_ 103 | w = self.width_ 104 | else: 105 | try: 106 | h, w = self.crop_size_ 107 | except: 108 | h = w = self.crop_size_ 109 | self.out_shape_ = (c, h, w) 110 | 111 | def check_params(self): 112 | """Check if parameters are set properly.""" 113 | assert self.random_noise_ is None or \ 114 | len(self.random_noise) == 2 115 | if self.random_rotation_ == 0: 116 | self.random_rotation_ = None 117 | if self.random_scale_ == (1, 1): 118 | self.random_scale_ = None 119 | 120 | @property 121 | def out_shape(self): 122 | return self.out_shape_ 123 | 124 | def transform(self, img): 125 | """""" 126 | rng_crop = self.rng_crop_ 127 | rng_mirror = self.rng_mirror_ 128 | border_mode = self.border_mode_ 129 | 130 | ts = time.clock() 131 | if self.height_ > 0 and self.width_ > 0: 132 | img = cv2.resize(img, (self.width_, self.height_)) 133 | self.logger.debug("transform resize") 134 | # PAD 135 | if self.pad_: 136 | try: 137 | to, bo, le, ri = self.pad_ 138 | except: 139 | try: 140 | tobo, leri = self.pad_ 141 | to = bo = tobo 142 | le = ri = leri 143 | except: 144 | to = bo = le = ri = self.pad_ 145 | pad_with_tblr(img, to, bo, le, ri, 146 | self.border_mode_, self.border_value_) 147 | self.logger.debug("transform pad") 148 | if self.minshape_ is not None: 149 | img = pad_with_min_shape(img, self.minshape_, 150 | self.border_mode_, self.border_value_) 151 | 152 | # ROTATE and/or ROTATE 153 | if self.random_rotation_ is not None or self.random_scale_ is not None: 154 | center = tuple(np.array(img.shape[1::-1])/2.) 155 | degree = 0 156 | scale_size = 1.0 157 | if self.random_rotation_ is not None: 158 | degree = self.rng_rotation_.uniform( 159 | -self.random_rotation_, self.random_rotation_) 160 | if self.random_scale_ is not None: 161 | scale_size = self.rng_scale_.uniform(*self.random_scale_) 162 | rmat = cv2.getRotationMatrix2D(center, degree, scale_size) 163 | img = cv2.warpAffine( 164 | img, rmat, img.shape[1::-1], borderMode=border_mode) 165 | # CROP 166 | if self.crop_size_ is not None: 167 | try: 168 | hcrop, wcrop = self.crop_size_ 169 | except: 170 | hcrop = wcrop = self.crop_size_ 171 | h, w = img.shape[:2] 172 | if h != hcrop or w != wcrop: 173 | if self.random_crop_: 174 | hoff = rng_crop.randint(0, h - hcrop + 1) 175 | woff = rng_crop.randint(0, w - wcrop + 1) 176 | self.logger.debug( 177 | "transform crop random (%d, %d)" % (hoff, woff)) 178 | else: 179 | hoff = (h - hcrop) / 2 180 | woff = (w - wcrop) / 2 181 | self.logger.debug("transform crop") 182 | img = img[hoff:hoff + hcrop, woff:woff + wcrop] 183 | # MIRROR 184 | if self.mirror_: 185 | if rng_mirror.randint(0, 2): 186 | img = img[:, ::-1] 187 | self.logger.debug("transform mirror") 188 | # COLOR 189 | if not self.color_: 190 | if img.ndim == 3: 191 | img = img.mean(2) 192 | img = img[..., np.newaxis] 193 | self.logger.debug("transform color") 194 | # FLOAT 195 | if self.float_: 196 | img = img.astype('float32') 197 | # SUBTRACT 198 | if self.mean_value_ is not None: 199 | if len(self.mean_value_) in (1, 3): 200 | img -= self.mean_value_ 201 | self.logger.debug("transform mean") 202 | else: 203 | raise ValueError("mean_value is invalid") 204 | # SCALE INTENSITY 205 | if self.scale_ is not None: 206 | img *= self.scale_ 207 | self.logger.debug("transform scale intensity") 208 | # CHANNEL SWAP 209 | if self.channel_swap_: 210 | img = img[:, :, self.channel_swap_] 211 | self.logger.debug("transform channel swap") 212 | # TRANSPOSE 213 | img = img.transpose(2, 0, 1) 214 | # Time 215 | self.logger.debug( 216 | 'transform takes {} ms'.format(1000 * (time.clock() - ts))) 217 | return img 218 | 219 | 220 | class BaseDataLayer(Layer): 221 | 222 | def setup(self, bottom, top): 223 | param = eval(self.param_str_) 224 | self.batch_size_ = param['batch_size'] 225 | self.data_setup(bottom, top) 226 | top[0].reshape(*self.data_.shape) 227 | self.executor_ = ThreadPoolExecutor(max_workers=1) 228 | self.thread_ = self.executor_.submit(self.internal_thread_entry) 229 | 230 | def reshape(self, bottom, top): 231 | pass 232 | 233 | def forward(self, bottom, top): 234 | self.thread_.result() 235 | top[0].reshape(*self.data_.shape) 236 | top[0].data[...] = self.data_ 237 | self.thread_ = self.executor_.submit(self.internal_thread_entry) 238 | 239 | def data_setup(self, bottom, top): 240 | raise NotImplementedError() 241 | 242 | def internal_thread_entry(self): 243 | raise NotImplementedError() 244 | 245 | def __del__(self): 246 | self.thread_.result() 247 | self.executor_.shutdown() 248 | super(self.__class__, self).__del__() 249 | 250 | 251 | class ImageDataLayer(BaseDataLayer): 252 | 253 | logger = getLogger('ImageDataLayer') 254 | handler = StreamHandler() 255 | handler.setLevel(INFO) 256 | logger.setLevel(INFO) 257 | logger.addHandler(handler) 258 | 259 | @staticmethod 260 | def get_params( 261 | source, column_id=0, root='', shuffle=False, num_thread=8, 262 | height=-1, width=-1, crop_size=None, pad=0, mirror=False, 263 | scale=None, mean_value=None, color=True, channel_swap=None, 264 | random_crop=False, random_seed=313): 265 | """""" 266 | return locals() 267 | 268 | def _read_image(self, path_img): 269 | img = cv2.imread(path_img) 270 | if img is None: 271 | raise ValueError("File not exists or corrupted: %s" % path_img) 272 | return img 273 | 274 | def _read_and_transform_image(self, path_img): 275 | img = self._read_image(path_img) 276 | return self.transformer_.transform(img) 277 | 278 | def data_setup(self, bottom, top): 279 | param = eval(self.param_str_) 280 | self.source_ = param['source'] 281 | self.column_id_ = param.get('column_id_', 0) 282 | self.root_ = param.get('root', '') 283 | self.shuffle_ = param.get('shuffle', False) 284 | self.random_seed_ = param.get('random_seed', 313) 285 | if ENV_SEED is not None: 286 | self.random_seed_ = ENV_SEED 287 | self.num_thread_ = param.get('num_thread', 8) 288 | self.param = param 289 | with open(self.source_, 'r') as fd: 290 | self.lines_ = filter( 291 | lambda x: x, 292 | map(lambda x: x[self.column_id_].strip(), csv.reader(fd))) 293 | if not len(self.lines_): 294 | raise ValueError("Dataset is empty.") 295 | self.indexes_ = np.arange(len(self.lines_)) 296 | self.at_ = 0 297 | if self.shuffle_: 298 | self.rng_ = np.random.RandomState(self.random_seed_) 299 | self.rng_.shuffle(self.indexes_) 300 | self.transformer_ = ImageTransformer(param) 301 | self.data_ = np.zeros( 302 | (self.batch_size_,) + self.transformer_.out_shape, 'float32') 303 | 304 | def internal_thread_entry(self): 305 | # Batch images 306 | tsw = time.clock() 307 | images = () 308 | for i in xrange(self.batch_size_): 309 | try: 310 | index = self.indexes_[self.at_] 311 | self.at_ += 1 312 | except IndexError: 313 | if self.shuffle_: 314 | self.rng_.shuffle(self.indexes_) 315 | self.at_ = 0 316 | index = self.indexes_[self.at_] 317 | self.at_ += 1 318 | images += self.root_ + self.lines_[index], 319 | # Load images in parallel 320 | for index, img in enumerate(images): 321 | self.data_[index] = self._read_and_transform_image(img) 322 | self.logger.debug( 323 | 'read a batch takes {} ms'.format(1000 * (time.clock() - tsw))) 324 | 325 | 326 | class MatNcImageDataLayer(ImageDataLayer): 327 | 328 | """Image provider from Matlab .mat n-channel image files. You must specify 329 | `key` option where you will get the data from the matlab file, accessing 330 | by: 331 | 332 | ``` 333 | img = loadmat(file)[key] 334 | 335 | ``` 336 | 337 | This layer is intended to be used for loading n-channel images rather than 338 | RGB, RGBA or grayscale images. 339 | """ 340 | 341 | def _read_image(self, path_img): 342 | from scipy.io import loadmat 343 | return loadmat(path_img)[self.key_] 344 | 345 | def data_setup(self, bottom, top): 346 | super(MatNcImageDataLayer, self).data_setup(bottom, top) 347 | param = eval(self.param_str_) 348 | self.key_ = param['key'] 349 | from scipy.io import loadmat 350 | l = loadmat(self.root_ + self.lines_[0])[self.key_] 351 | self.data_ = np.zeros(( 352 | self.batch_size_, l.shape[-1],) + 353 | self.transformer_.out_shape[1:], dtype='float32') 354 | 355 | 356 | class HDF5Layer(BaseDataLayer): 357 | 358 | def data_setup(self, bottom, top): 359 | import h5py 360 | param = eval(self.param_str_) 361 | self.source_ = param['source'] 362 | self.path_h5_ = param['path_h5'] 363 | self.column_id_ = param.get('column_id_', 0) 364 | self.shuffle_ = param.get('shuffle', False) 365 | self.random_seed_ = param.get('random_seed', 313) 366 | if ENV_SEED is not None: 367 | self.random_seed_ = ENV_SEED 368 | self.blob_name_ = param['blob_name'] 369 | 370 | with open(self.source_, 'r') as fd: 371 | self.lines_ = filter( 372 | lambda x: x, 373 | map(lambda x: x[self.column_id_].strip(), csv.reader(fd))) 374 | if not len(self.lines_): 375 | raise ValueError("Dataset is empty.") 376 | self.indexes_ = np.arange(len(self.lines_)) 377 | self.at_ = 0 378 | if self.shuffle_: 379 | self.rng_ = np.random.RandomState(self.random_seed_) 380 | self.rng_.shuffle(self.indexes_) 381 | self.hd_ = h5py.File(self.path_h5_, 'r') 382 | self.data_ = np.zeros( 383 | (self.batch_size_,) 384 | + self.hd_[self.lines_[0]][self.blob_name_].shape, 'float32') 385 | 386 | def __del__(self): 387 | self.hd_.close() 388 | super(HDF5Layer, self).__del__() 389 | 390 | def internal_thread_entry(self): 391 | # Batch images 392 | for i in xrange(self.batch_size_): 393 | try: 394 | index = self.indexes_[self.at_] 395 | self.at_ += 1 396 | except IndexError: 397 | if self.shuffle_: 398 | self.rng_.shuffle(self.indexes_) 399 | self.at_ = 0 400 | index = self.indexes_[self.at_] 401 | self.at_ += 1 402 | self.data_[i] = self.hd_[self.lines_[index]][self.blob_name_].value 403 | 404 | 405 | class ScalarDataLayer(BaseDataLayer): 406 | logger = getLogger('ScalarDataLayer') 407 | handler = StreamHandler() 408 | handler.setLevel(INFO) 409 | logger.setLevel(INFO) 410 | logger.addHandler(handler) 411 | 412 | @staticmethod 413 | def get_params( 414 | source, column_id=0, shuffle=False, random_seed=313): 415 | """""" 416 | return locals() 417 | 418 | def data_setup(self, bottom, top): 419 | param = eval(self.param_str_) 420 | self.source_ = param['source'] 421 | self.column_id_ = param.get('column_id', 0) 422 | self.shuffle_ = param.get('shuffle', False) 423 | self.random_seed_ = param.get('random_seed', 313) 424 | if ENV_SEED is not None: 425 | self.random_seed_ = ENV_SEED 426 | with open(self.source_, 'r') as fd: 427 | self.values_ = filter( 428 | lambda x: x, 429 | map(lambda x: x[self.column_id_].strip(), csv.reader(fd))) 430 | self.values_ = np.array(map(float, self.values_), dtype='float32') 431 | if self.values_.size == 0: 432 | raise ValueError("Dataset is empty.") 433 | self.indexes_ = np.arange(self.values_.size) 434 | self.at_ = 0 435 | if self.shuffle_: 436 | self.rng_ = np.random.RandomState(self.random_seed_) 437 | self.rng_.shuffle(self.indexes_) 438 | self.data_ = np.zeros( 439 | (self.batch_size_,) + (1,) * 3, 'float32') 440 | 441 | def internal_thread_entry(self): 442 | indexes = self.indexes_[self.at_:self.at_ + self.batch_size_] 443 | self.at_ += self.batch_size_ 444 | if indexes.shape[0] != self.batch_size_: 445 | if self.shuffle_: 446 | self.rng_.shuffle(self.indexes_) 447 | res = self.batch_size_ - indexes.shape[0] 448 | indexes2 = self.indexes_[:res] 449 | indexes = np.concatenate((indexes, indexes2)) 450 | self.at_ = res 451 | self.data_[:, 0, 0, 0] = self.values_[indexes] 452 | --------------------------------------------------------------------------------