Noisy senses: measuring, noise and averaging
Real robots never know exactly where the wall is. Their sensors wobble. You'll build a distance sensor, add realistic wobble to it, and discover a simple trick that makes it more accurate: measure several times and average.
Start here
One measurement can be off by a lot. The average of many measurements is usually much closer to the truth.
The problem
Robo-Helper can now move. But it's driving blind: it only finds walls by bumping into them. It needs to look ahead and ask "how far is the wall in front of me?" And when it does, the answer is a little bit wrong every time, because real sensors are never perfect.
The idea in one picture
Ask five friends to guess how long a corridor is, in steps. One says 12, one says 16, one says 13, one says 15. Each guess is off. But some are too high and some are too low, so if you average them (14), the errors mostly cancel out. A robot does the same thing with its own sensor: it measures again and again, and averages.
Words to know
- Sensor
- A part that measures something about the world: distance, light, speed, sound.
- Noise
- Random wobble added to a measurement. Not a mistake you can fix, just how the world is.
- Random
- Unpredictable. Python's
randommodule gives you numbers you can't guess in advance. - Mean
- The average: add up all the values, divide by how many there are.
- Error
- How far a measurement is from the true value, ignoring whether it's too high or too low.
Going deeper: everything below is the full lesson. You'll need a calculator (or Python) for a few sums.
1. The problem
Robo-Helper is at (1, 1), facing east. How far can it drive before it hits the wall?
x: 0 1 2 3 4 5 6 7 8 9
y = 1 # R . . . . . . . #
Counting by eye: squares 2, 3, 4, 5, 6, 7 and 8 are free. That's 7. A robot can't "count by eye". It needs an algorithm, and you already wrote one in Lesson 0: look at the next square; if it's free, count it and look further.
But there's a second, sneakier problem. Real distance sensors (ultrasonic "bat ears", infrared, laser) don't return 7. They return 6.73, then 7.41, then 6.92… Every reading is slightly off. If the robot trusts one bad reading, it might think there's more room than there is, and crash. How do you get a trustworthy number out of an untrustworthy sensor?
2. First principles
Step 1: measuring, the perfect way
Start from the robot's square and step in one direction, one square at a time, counting free squares until the next one is a wall. The robot's own square doesn't count. It's your Lesson 0 loop, now on the real room, in any direction:
from (1, 1) facing E:
look at (2, 1): free → count 1
look at (3, 1): free → count 2
...
look at (8, 1): free → count 7
look at (9, 1): wall → stop. distance = 7
Facing N from (1, 1)? The very first square (1, 0) is a wall, so the answer is 0 and the loop body never runs.
Predict first: from (1, 1) facing S, what's the distance? Use the room picture from Lesson 1.
5. Going down from (1, 1): (1, 2), (1, 3), (1, 4), (1, 5), (1, 6) are all free, and (1, 7) is the bottom wall. The green block at (1, 5) isn't a wall, so it doesn't stop the beam in this simple world.
Step 2: adding noise, the realistic way
We'll pretend our sensor adds a random wobble to the true distance. The wobble is any number between −1 and +1, every value equally likely. Python does this with rng.uniform(-1, 1), where rng is a random number generator.
Here are eight real readings of the true distance 7 with noise 1.0 (from Python, seed 1):
6.27 7.69 7.53 6.51 6.99 6.90 7.30 7.58
Some are too low, some too high. The worst is off by 0.73. One more rule: a distance can't be negative. If the true distance is 0.2 and the wobble is −0.9, the sensor would say −0.7, which is nonsense. So we clip it: never return less than 0. Python's max(0.0, value) does that.
Step 3: averaging
The mean (average) of some numbers is their total divided by how many there are. Take the first four readings:
6.27 + 7.69 + 7.53 + 6.51 = 28.00
28.00 / 4 = 7.00 error = |7.00 - 7| = 0.00
Perfect! But that was lucky. The next four:
6.99 + 6.90 + 7.30 + 7.58 = 28.77
28.77 / 4 = 7.19 error = |7.19 - 7| = 0.19
Still better than the worst single reading in that group (7.58, off by 0.58). Why does averaging help? Because the wobble is just as likely to be up as down. When you add four wobbles together, the ups and downs partly cancel. It's like the friends guessing the corridor: the over-guessers and under-guessers balance each other.
Step 4: measuring "how wrong, usually"
One lucky or unlucky example proves nothing. To compare "1 reading" with "4 readings averaged" fairly, repeat the experiment many times (say 500) and average the errors. That number is the typical error. It answers: "if I trust this method, how far off am I, usually?"
For a single reading with noise 1.0, the error is equally likely to be any size from 0 to 1, so on average it's about 0.5. Here's what Python measured (real output of the homework file):
average of 1 readings -> typical error 0.503
average of 4 readings -> typical error 0.227
Predict first: 4 readings roughly halved the error. What will 16 readings give? And 64?
About 0.12 for 16, and about 0.06 for 64. The real output says 0.117 and 0.056. The pattern: four times as many readings halves the error. Not four times better, only two times. (In maths this is the "square root rule": the error shrinks like 1 ÷ √n. √4 = 2, √16 = 4, √64 = 8.)
That pattern matters for real robots. Averaging costs time: 64 readings at 40 per second is 1.6 seconds, and a moving robot can't wait that long. Engineers pick a balance: enough readings to be safe, few enough to react in time.
3. Play with it
Interactive: a noisy distance sensor
Click any floor square to put the robot there, then pick a direction. The true distance is worked out with the same loop as your distance_to_wall. Each reading adds the same noise as noisy_reading (uniform, clipped at 0). Change N and press Take a reading.
Dots: single readings. Red line: their average. Blue line: the truth.
Try: N = 1, press the button ten times and watch the red line jump. Then N = 16. Then N = 64. Then set noise to 2 and put the robot right next to a wall facing it. Why do the dots pile up at 0?
4. Homework
Open homework/lesson02_sensors.py. It imports your move from Lesson 1, so finish Lesson 1 first. If you haven't, the checker will say not written yet (Lesson 1: write this function). Write five functions in order.
Function 1: distance_to_wall(room, pos, direction)
What it does: counts the free squares between pos and the next wall in that direction. The robot's own square doesn't count.
Example: distance_to_wall(ROOM, (1, 1), "E") → 7, facing "N" → 0, facing "S" → 5.
Hint 1
It's the steps_to_wall loop from Lesson 0, except "is it the wall?" becomes is_free(room, p) and "x + 1" becomes move(p, direction).
Hint 2
Start by looking at the first square in front of the robot, not the robot's own square. Then loop while that square is free.
Hint 3
count = 0
p = one step from pos in direction
while p is free:
count goes up by 1
p = one more step in direction
return countFunction 2: noisy_reading(true_distance, noise, rng)
What it does: returns the true distance plus rng.uniform(-noise, noise), but never less than 0.
Example: with true distance 5 and noise 1.0, every result is between 4 and 6. With true distance 0.2 and noise 1.0, a result can be 0 but never negative.
Hint 1
Use the rng you're given, not random.uniform. The checker passes in a seeded generator so results are repeatable.
Hint 2
max(a, b) returns the bigger of two numbers. So max(0.0, x) is x unless x is negative, and then it's 0.
Hint 3
One line: return the max of 0.0 and (true distance + a uniform random number from −noise to +noise).
Function 3: average(values)
What it does: the mean of a list of numbers.
Example: average([1, 2, 3, 4]) → 2.5 (10 ÷ 4).
Hint 1
Total divided by how many.
Hint 2
Python has sum(list) and len(list) built in.
Hint 3
One line: return the sum of the values divided by the length of the values.
Function 4: smoothed_reading(true_distance, noise, n, rng)
What it does: takes n noisy readings and returns their average.
Example: with n = 4, it might take 6.27, 7.69, 7.53, 6.51 and return 7.0.
Hint 1
Build a list of n readings, then pass it to your average.
Hint 2
Start with an empty list and a for _ in range(n): loop. The _ means "I don't need the loop counter".
Hint 3
readings = []
repeat n times:
append noisy_reading(true_distance, noise, rng)
return average(readings)(Once that works, try writing it as one line with a list comprehension: [... for _ in range(n)].)
Function 5: average_error(true_distance, noise, n, trials, rng)
What it does: runs smoothed_reading trials times, works out the error of each one, abs(reading - true_distance), and returns the average error.
Example: with true distance 7, noise 1.0, n = 1 and 500 trials, you get about 0.5.
Hint 1
Same shape as smoothed_reading, but each item in your list is an error, not a reading.
Hint 2
Error means "how far off, ignoring direction", so use abs(). Without it, too-high and too-low errors cancel and you'd wrongly get nearly 0.
Hint 3
errors = []
repeat trials times:
r = smoothed_reading(true_distance, noise, n, rng)
append abs(r - true_distance)
return average(errors)Run it and see
cd homework
python3 lesson02_sensors.py # Windows: py lesson02_sensors.py
cd ..
Real output from the finished file:
True distance east from (1, 1): 7
average of 1 readings -> typical error 0.503
average of 4 readings -> typical error 0.227
average of 16 readings -> typical error 0.117
average of 64 readings -> typical error 0.056
Your numbers should be identical, because the program uses a fixed seed (random.Random(0)), as long as you call rng.uniform exactly once per reading.
Check it
python3 check.py 2 # Windows: py check.py 2
Lesson 2: lesson02_sensors.py
✓ distance_to_wall
✓ noisy_reading
✓ average
✓ averaging helps
4/4 passed 🎉 lesson complete!
The last check, "averaging helps", runs your average_error with 1 and with 16 readings and needs the error to drop by more than half. Two real ✗ messages you might meet:
✗ distance_to_wall: from (1, 1) facing E there are 7 free squares
✗ noisy_reading: never return a negative distance
The first usually means you counted the robot's own square (you'd get 8). The second means you forgot the max.
Stretch
Real sensors sometimes glitch badly: one reading in 20 is totally wrong (say, 0). Write glitchy_reading that does that, and see how much it ruins the average. Then write median(values) (sort the list, take the middle one) and compare. Which is more robust to glitches, mean or median? Robot engineers use both.
5. Real-world challenge: the corridor census
You'll be the noisy sensor, and see averaging work with your own eyes.
- Pick a distance you can measure exactly later: a hallway, the garden, the length of a long table. Don't measure it yet.
- Take 16 "readings". Pace it out in normal steps 16 times, writing down each count (half-steps allowed). Or ask 8 people to guess it in metres, twice each, without hearing each other's answers.
- Now measure it exactly with a tape measure, and convert your step counts to metres (measure one of your steps too). That's the "true distance".
- Work out the errors. For each single reading: |reading − true|. Then their average: that's the typical error for N = 1.
- Group them. Split your 16 readings into four groups of 4. Average each group, then find each group's error. Average those four errors: that's the typical error for N = 4. Finally, the average of all 16 against the truth.
- Record a little table: N = 1, N = 4, N = 16, and the typical error for each.
What it should teach you: the error usually shrinks as N grows, roughly halving from 1 to 4. If it doesn't shrink, look for a bias: maybe all your guesses were too low. Averaging cancels random wobble but it can't fix a sensor that's always wrong in the same direction. That difference (noise vs bias) is one of the most important ideas in all of measurement and AI.
6. Your Robo-Helper so far
Lesson 0 ✓ greets you, counts steps
Lesson 1 ✓ moves around the room without crashing into walls
Lesson 2 ✓ distance_to_wall: looks ahead before moving, and knows sensors need averaging
- Lesson 3 faces noise again: the braking measurements are noisy too, and learning a line through them is a smarter kind of averaging.
- In Lesson 4 the obstacle detector learns from noisy sensor readings of distance and speed, just like the ones you made today.
- In the Lesson 11 capstone,
RoboHelper.drive_tocalls yourdistance_to_wallbefore every step, and feeds it to Lesson 3'smax_safe_speedto choose how fast to drive.
7. Common mistakes
- Counting the robot's own square. You'd get 8 instead of 7. The checker says
from (1, 1) facing E there are 7 free squares. Look one step ahead before the loop. - Forgetting the clip at 0. The checker says
never return a negative distance. - Using
random.uniforminstead ofrng.uniform. It may pass, but your output won't match the numbers on this page, and results change every run. Always use therngyou're handed. - Forgetting
abs()inaverage_error. Positive and negative errors cancel, the "error" looks tiny for every N, and the checkaveraging should cut the typical error by more than halffails. - Lesson 1 not finished. Lesson 2 imports
move. You'll seenot written yet (Lesson 1: write this function)even on Lesson 2's check.
8. Check yourself
What's the difference between noise and a bias?
Noise is random wobble, sometimes up, sometimes down. Averaging cancels it. A bias pushes every reading the same way (say, always 0.3 too high). Averaging can't remove a bias. You fix it by calibrating against a known true value.
Predict: what does distance_to_wall(ROOM, (5, 3), "W") return?
1. (4, 3) is free, then (3, 3) is part of the inside wall #.
Predict: if you average 256 readings with noise 1.0, roughly what typical error do you expect?
About 0.03. 64 gave 0.056. 256 is four times as many, which halves the error again.
Why does average_error need many trials, not just one?
One trial can be lucky (like the four readings that averaged to exactly 7.00) or unlucky. Averaging over hundreds of trials gives the usual error, which is what you need to compare methods fairly.
Why might a fast robot not want to average 64 readings?
Each reading takes time. By the time it has 64, it may have moved a long way and the wall may be closer than its "accurate" answer says. Speed and accuracy pull against each other.
9. Go deeper (optional)
- LLM evals: decision models: the same problem in AI testing. A model's score on a small test wobbles, just like a sensor, and you need enough examples to trust it.
- Human–AI help levels: a robot that has to guess a person's goal from noisy, uncertain evidence.
- Free robotics courses: look for "probabilistic robotics" and "Kalman filter", the grown-up version of averaging over time.