Skip to content

Commit

Permalink
Update the random jitter range to be mathematically sound.
Browse files Browse the repository at this point in the history
  • Loading branch information
liuliu committed Jan 3, 2019
1 parent 2c5273a commit c5e7f94
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 8 deletions.
6 changes: 5 additions & 1 deletion doc/nnc-df.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ Use Dataframe with Addons

The raw dataframe API (i.e. :cpp:func:`ccv_cnnp_dataframe_new`, :cpp:func:`ccv_cnnp_dataframe_map` and :cpp:func:`ccv_cnnp_dataframe_reduce_new`) are hard to use. Unlike helper functions such as :cpp:func:`CPU_TENSOR_NCHW` in ``ccv_nnc_easy.h``, there is no helper functions to fill in the :cpp:class:`ccv_cnnp_column_data_t`. This is intentional. You should interact dataframe with its addons API instead.

The addons API are implemented with the raw dataframe API. It provides the ability to load data from :cpp:class:`ccv_array_t`.
The addons API are implemented with the raw dataframe API. It provides the ability to load data from :cpp:class:`ccv_array_t` (in the future, from CSV file and SQLite tables as well). Being able to iterate through a :cpp:class:`ccv_array_t` doesn't do much (you can do the same by iterating with for loop). The power comes from combining multiple operations on top of that. For example, if you have a :cpp:class:`ccv_array_t` of file names, you can use :cpp:func:`ccv_cnnp_dataframe_read_image` to load these files as images. Certain image jitter can be applied with :cpp:func:`ccv_cnnp_dataframe_image_random_jitter`. Finally, you can batch these images into tensors with :cpp:func:`ccv_cnnp_dataframe_batching_new`.

When you iterate through the newly created dataframe, you can get the batched tensor one by one. More importantly, using dataframe also supports stream contexts. When you prefetch on one stream context, it is done asynchronously on that stream context. Rather than using a IO thread for data loader, this is NNC's way of supporting asynchronous data loading v.s. training.

Fundamentally, using dataframe with addons enables you to express computations independent of the actual execution. The actual execution (such as reading from disk, applying image random jitter or copying to GPU) only happens when you prefetch or iterate through it. This enables the powerful abstraction to load, manipulate and feed data into NNC's training process.
19 changes: 15 additions & 4 deletions lib/nnc/ccv_cnnp_dataframe_addons.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ static void _ccv_cnnp_image_lighting(ccv_dense_matrix_t* image, const float alph
}
}

static float _ccv_cnnp_random_logexp(sfmt_t* const sfmt, const float jitter)
{
// We want to get something around logarithmic scale, thus, 0 is no good, and infinity is no good. 1 is the same.
// jitter is some turbulence we want around 1. We want the range range to be around [1 / (1 + jitter), 1 + jitter]
// but the distribution is not uniform (50% fall under 1, and 50% fall above 1). The way to do this is to first
// get to logarithmic range, doing a uniform sampling, and then convert back.
double log_jitter_limit = log(1 + jitter);
double log_random_jitter = sfmt_genrand_real1(sfmt) * 2 * log_jitter_limit - log_jitter_limit;
return (float)exp(log_random_jitter); // Convert it back to exponential form.
}

static void _ccv_cnnp_image_manip(ccv_dense_matrix_t* image, const ccv_cnnp_random_jitter_t random_jitter, sfmt_t* const sfmt)
{
assert(sfmt && CCV_GET_CHANNEL(image->type) == CCV_C3);
Expand All @@ -174,19 +185,19 @@ static void _ccv_cnnp_image_manip(ccv_dense_matrix_t* image, const ccv_cnnp_rand
if (random_jitter.brightness == 0)
break;
// introduce some brightness changes to the original image
ccv_scale(image, (ccv_matrix_t**)&image, 0, sfmt_genrand_real1(sfmt) * random_jitter.brightness * 2 + (1 - random_jitter.brightness));
ccv_scale(image, (ccv_matrix_t**)&image, 0, _ccv_cnnp_random_logexp(sfmt, random_jitter.brightness));
break;
case 1:
// introduce some saturation changes to the original image
if (random_jitter.saturation == 0)
break;
ccv_saturation(image, &image, 0, sfmt_genrand_real1(sfmt) * random_jitter.saturation * 2 + (1 - random_jitter.saturation));
ccv_saturation(image, &image, 0, _ccv_cnnp_random_logexp(sfmt, random_jitter.saturation));
break;
case 2:
// introduce some contrast changes to the original image
if (random_jitter.contrast == 0)
break;
ccv_contrast(image, &image, 0, sfmt_genrand_real1(sfmt) * random_jitter.contrast * 2 + (1 - random_jitter.contrast));
ccv_contrast(image, &image, 0, _ccv_cnnp_random_logexp(sfmt, random_jitter.contrast));
break;
case 3:
if (random_jitter.lighting == 0)
Expand Down Expand Up @@ -228,7 +239,7 @@ static void _ccv_cnnp_random_jitter(void*** const column_data, const int column_
int resize_cols = ccv_max(resize, (int)(input->cols * (float)resize / input->rows + 0.5));
if (random_jitter.aspect_ratio > 0)
{
const float aspect_ratio = sqrtf((random_jitter.aspect_ratio + (1 - 1 / (1 + random_jitter.aspect_ratio))) * sfmt_genrand_real1(&sfmt[i]) + 1 / (1 + random_jitter.aspect_ratio));
const float aspect_ratio = sqrtf(_ccv_cnnp_random_logexp(&sfmt[i], random_jitter.aspect_ratio));
resize_rows = (int)(resize_rows * aspect_ratio + 0.5);
resize_cols = (int)(resize_cols / aspect_ratio + 0.5);
}
Expand Down
6 changes: 3 additions & 3 deletions lib/nnc/ccv_nnc.h
Original file line number Diff line number Diff line change
Expand Up @@ -2269,9 +2269,9 @@ CCV_WARN_UNUSED(int) ccv_cnnp_dataframe_read_image(ccv_cnnp_dataframe_t* const d
* The structure to describe how to apply random jitter to the image.
*/
typedef struct {
float contrast; /**< The random contrast, the final contrast will be 1 - contrast + random_unit * contrast * 2 */
float saturation; /**< The saturation, the final saturation will be 1 - saturation + random_unit * saturation * 2 */
float brightness; /**< The brightness, the final brightness will be 1 - brightness + random_unit * brightness * 2 */
float contrast; /**< The random contrast, the final contrast will be [1 / (1 + contrast), 1 + contrast] */
float saturation; /**< The saturation, the final saturation will be [1 / (1 + saturation), 1 + saturation] */
float brightness; /**< The brightness, the final brightness will be between [1 / (1 + brightness), 1 + brightness] */
float lighting; /**< AlexNet style PCA based image jitter */
float aspect_ratio; /**< Stretch aspect ratio between [1 / (1 + asepct_ratio), 1 + aspect_ratio] */
int symmetric; /**< Apply random flip on x-axis (around y-axis */
Expand Down
Binary file modified test/unit/nnc/data/nature.random-jitter.bin
Binary file not shown.

0 comments on commit c5e7f94

Please sign in to comment.