OPF time encoder parameter questions


#1

For the model params, are ‘n’ and ‘w’ really equal to just 29 and 21? In the params file it says that for the scalar encoder, though I thought that ‘n’ had to be many times larger than w?

Also where can I find the ‘n’ and ‘w’ values for the time of day and weekend params? There are a couple of ‘21’ values there but they paired with ‘6.09’ and ‘1’, which seem strange. I’m trying to replicate NuPIC’s results with my own implementation and want everything to line up as closely as possible. Thanks!!

Here’s the file I’m referring to:
https://pastebin.com/9p3NVxdh


About hotgym demo
#2

I’m not exactly sure where that snippet comes from, but I believe those values are forced into integers. The probably have decimals because they were produced by swarming years ago honestly.

Remember that this encoder is going to play a part in a larger encoding. To know if w is too large within n, you really need to know the combined n and how much space the sub-encoding takes up. :thinking:

Also, check out this cyclic encoder display I’m working on (not sure how long that link will be valid). In this viz, n = bits and w = range. I’m going to try and explain the concept of buckets with this visual soon.


#3

Thanks @rhyolight

The code snippet comes from rec_center_hourly_model_params.py file in the ‘…hotgym/anomaly/one_gym/model_params’ folder.

So can I interpret the ‘21’ and ‘6.09’ values for ‘timestamp_timeOfDay’ as n and w? And the 21’ and ‘1’ values for ‘timestamp_weekend’ as n and w? If this were the case, then the total n = 29+21+21 (71) and total w = 21+6+1 (28)? Does this sound plausible?


#4

Those values are not exactly n and w. Yes, the 21 is the number of bits the encoder produces, which is n. But 6.09 is actually the number of buckets, or unique values the encoder can recognize.

If you look at the same parameter set you showed, there is a weekend encoder in there with values [21, 1] which means 21 bits out, and 2 buckets (0 & 1). You only need two values to represent weekend or not.

Watch this space, I’m going to have a whole section on cyclic encoders and how they can be used both for discrete categorical encoding or continuous scalar encoding.

EDIT: See correction below


#5

Ok that helps! So for the timeOfDay encoder ~6 distinct values over 21 bits would mean a w around 4, and for the weekend encoder 2 distinct values over 21 bits would mean a w around 10. These two n values of 21 plus the 29 from the scalar would mean a total n of 71.

Even with this concatenation, isn’t it still the case that the scalar values are totally capped within 29 of the bits? It just seems like this crams the whole scalar range into a really tight space, since the min value would be encoded by bits 1-21 and the max by bits 9-29. Doesn’t this effectively create too few buckets with too much overlap between them? Wouldn’t all mid-range values look nearly identical to the SP?


#6

According to NuPIC API documentation ‘21’ in timeOfDay and weekend encoders is not ‘n’, but width ‘w’:

Parameters:

  • timeOfDay -
    (int | tuple) Time of day, where midnight = 0, units = hour.

    • (int) width of attribute: default radius = 4 hours
    • (tuple) timeOfDay[0] = width; timeOfDay[1] = radius

The same you can see from the code. In nupic/encoders/date.py:

215     self.timeOfDayEncoder = None
216     if timeOfDay != 0:
217       # Value is time of day in hours
218       # Radius = 4 hours, e.g. morning, afternoon, evening, early night,
219       #  late night, etc.
220       if hasattr(timeOfDay, "__getitem__"):
221         w = timeOfDay[0]
222         radius = timeOfDay[1]
223       else:
224         w = timeOfDay
225         radius = 4
226       self.timeOfDayEncoder = ScalarEncoder(w = w, minval=0, maxval=24,
227                               periodic=True, radius=radius, name="time of day", forced=forced)
228       self.timeOfDayOffset = self.width
229       self.width += self.timeOfDayEncoder.getWidth()
230       self.description.append(("time of day", self.timeOfDayOffset))
231       self.encoders.append(("time of day", self.timeOfDayEncoder, self.timeOfDayOffset))

‘w’ is initialized as the first element of timeOfDay tuple:

221         w = timeOfDay[0]

Then it’s passed to initialize ScalarEncoder:

226       self.timeOfDayEncoder = ScalarEncoder(w = w, minval=0, maxval=24,
227                               periodic=True, radius=radius, name="time of day", forced=forced)

Later ‘n’ is calculated in init function of ScalarEncoder _initEncoder():

268     if n != 0:
        ...

287     else:
        ...
305         nfloat = self.w * (self.range / self.radius) + 2 * self.padding
306         self.n = int(math.ceil(nfloat))

Note: ‘n’ is 0, so ‘else’ path is taken.

So, in ‘timeOfDay’: ( 21, 6.090344152692538):

‘21’ (width) is a number of bits that are set to encode a single time value.
‘6.090344152692538’ (radius) is a distance between inputs which have non-overlapping representations, e.g.:

0h
0111111111111111111111000…
6.09h
0000000000000000000000111… (21set bits)

Here 0h and 6.09h are completely different inputs from encoder point of view, since there’s no even 1 bit of overlapping.

Another way to think about this is a proportion. If:

w [bits, 21] -> radius [time, 6.09hours]
n [bits] -> range [time, 24hours]

then:

n = w * range / radius

This is what you see in line 305 above. (There’s also padding there, but it’s a different story)

@rhyolight Please correct me if I’m wrong.


#7

Thanks for that. I have always found this confusing, even after writing my own date encoder in javascript.