Why does it seem the noise() method is negatively biased?

edited November 2017 in Questions about Code

I just started reading Daniel Shiffman's Nature of code and going through his videos. I started playing with the Perlin noise method and it seems if you map it to -1,1 it has a distinct negative bias.

Below is the Random Walker code from the book altered to use Perlin noise for the movements. No matter how I fiddle with the time, it seems the walker always heads to the top left, i.e, it generally moves -x and -y. Is there some bug in this code or does the noise method have some sort of bias?

// Daniel Shiffman
// The Nature of Code
// http://www.shiffman.net/

Walker w;

void setup() {
  size(600,600);
  // Create a walker object
  w = new Walker();
  background(255);
}

void draw() {
  // Run the walker object
  w.step();
  w.render();
}
// A random walker object!
class Walker {
  float x,y,time,timeInc;


  Walker() {
    x = width/2;
    y = height/2;
    time = 0;
    timeInc = 20;

  }

  void render() {
    stroke(0);
    point(x,y);
  }

  // Randomly move to any neighboring pixel (or stay in the same spot)
  void step() {
    float stepX = noise(time);
//    stepX = stepX * 2 -1;
    stepX = map(stepX,0,1,-1,1);    
    time+=timeInc;
    float stepY = noise(time);
//    stepY =stepY *2 -1;
    stepY = map(stepY,0,1,-1,1);
    time+=timeInc;
    x += stepX;
    y += stepY;
    x = constrain(x,0,width-1);
    y = constrain(y,0,height-1);
  }
}

I'm thinking there's something I'm not seeing in the code because this short test shows no bias:

void setup(){
     for(int i = 0;i <20;i++){
          float x = (float)noise(i);
          x = map(x,0,1,-1,1);
          println(x);
     }
}

Answers

  • you are using steps of 20 in noise()? i thought it was meant to be tiny values.

    the reference:

    "Steps of 0.005-0.03 work best for most applications, but this will differ depending on use."

    (that said, you're not the first person to notice this. and it differs depending on platform and language, which isn't ideal)

  • I tried values from 0.001 to 1000 and it always floats to the top left. I even tried as Shiffman suggests using separate time values for timeX = 0and timeY=10000, and the same result, it floats to the top left. Now I have no idea if this is a problem in practical use, I was just curious if there was something I was overlooking in the code.

  • edited November 2017 Answer ✓

    @shawnlau --

    I believe there are a few things going on here.

    One is that Perlin noise output does not have a uniform distribution. See for example this discussion:

    https://stackoverflow.com/questions/9549851/uniform-distribution-from-a-fractal-perlin-noise-function-in-c-sharp

    Another is "Example 1.5 Perlin noise walker" from Shiffman's The Nature of Code doesn't actually depend on the distribution because it sets x = the new value (scaled). Because x is constantly reset, whether the aggregate is uniformly distributed doesn't matter -- the current value is always -1 to 1 (or whatever).

    http://natureofcode.com/book/introduction/

    void step() {
      // x- and y-location mapped from noise
      x = map(noise(tx), 0, 1, 0, width);
      y = map(noise(ty), 0, 1, 0, height); 
      // Move forward through “time.”
      tx += 0.01;
      ty += 0.01;
    }
    

    However it looks like your setup does depend on the distribution, because you are adding the latest value to x:

    x += stepX;
    y += stepY;
    

    Without a perfectly uniform distribution, this means you will drift over time.

    The easiest fix is to play back Perlin noise directly rather than add cumulative values sampled from it.

  • edited November 2017

    On my computer a long sample of Processing's Perlin noise always has an average bias between -0.02 and -0.08 -- so about 5% drift up / to the left.

    For any given seed and step the aggregate bias might be different (across a much wider range, a few are positive), but there is always a bias. Resetting the noise seed and randomizing step values in Processing's magic recommended range of 0.005-0.03 will consistently generate the average of averages -- that is, about -5% drift.

    If you want to make the adding method work (rather than just playing back noise directly), and you need to improve the centrality of your random walk, you could try this quick and dirty fix:

    1. Sample a large number of initial values (e.g. 400+) from a given seed (e.g. 1) at a small step value (e.g. 0.005) to determine the bias.

    2. Generate a random walk with that same seed and step, correcting each output value by the specific bias to approximately center the aggregate on 0.

    Here is the function:

    float seedBias( int seed, float stepSize, int reps ) {
      // String log = "";
      float x = 0;
      float total = 0;
      float bias = 0;
      noiseSeed(seed);
      for (int i = 0; i <reps; i++) {
        x = (float)noise(i * stepSize);
        x = map(x, 0, 1, -1, 1);
        total = total + x;
        // log = xs + (" " + x).substring(0, 4);
      }
      bias = total/(float)reps;
      return(bias);
    }
    

    Once you have the bias value, you would reset the noiseSeed and use it like this:

    x = noise(val)+bias;
    

    Note that pseudorandom number testing is extremely sensitive to the setup -- you may get unexpected results if you attempts to generate a single random step value, or to call randomSeed during draw, or to generate values during setup, et cetera.

  • edited November 2017

    That is pretty much what I wanted to find out. Its probably wise not to accumulate the results. In practical applications, the bias would probably not be noticed. But if it accumulates, then it becomes apparent. I'm not intersted in perfecting the Random Walker - noise version :) I was curious of what was going on and you provided a good explanation. Thanks. Your graphic was extremely helpful.

Sign In or Register to comment.