 We would really like to take our convolutional block out for a spin, so in order to do that, we need an appropriate data set. We need a one-dimensional data set, so one-dimensional array that has some kind of a pattern in it that's recognizable, but that might fall in a different location each time. This is what convolution is good at identifying. So we'll create one, a data set that's small enough that we can include it with cottonwood for testing, but that's complicated enough that it'll show whether our algorithm is working as we hope. We'll create a series of blips. So in the data directory, we'll create a module called dataloaderblips.py, and we want this to be a block. More specifically, we want it to be two blocks. We want to simulate having training data and having evaluation data. So we'll create two blocks, a training data block and an evaluation data block. Now for our purposes, we don't actually care. These will be exactly the same, but this will set up a good pattern that we can use later when we actually do have data that's segmented into training and evaluation or training and tuning and evaluation. So you can see here in our training data, in our evaluation data classes, these are both blocks, and they have the minimum signature. They each have a forward pass and a backward pass. They also have the Dunderstr method that returns a string that describes them briefly, and then they also have a Dunder init method that initializes them. This is all you need to create a block, to create something that can be connected to other things. I want to call out that both of the forward pass and the backward pass, they accept an argument. This is not because the data loader needs an argument, but because the structure will expect to be able to pass an argument to forward and backward pass functions of every block, even if it's empty, even if it's nonsense. So we need to include an argument there to be able to accept that if it should be passed. In our case, we won't need it, we won't use it. Here we rely on a get data sets function that returns two generators, a training data generator and an evaluation data generator. A generator is a Python function that is special. You can call next on it and it'll give you the next thing on the list that that generator has. So you can think of a generator, it's just like a tall stack, a deck of cards. Next just deals off the next one off the top. And so by having two generators that are get data sets function returns, we can collect the first one and use it to initialize our training data block and we can peel off the second one and use it to initialize our evaluation data block. And then on the forward pass, we go to that generator and we pull off the first example each time. So our next move is to actually write this get data sets function. See what it does. So here in get data sets, we'll take and kick the can down the road a little bit. We'll call a get blips function and these will be the actual data in our data set. And we'll just pull those into something called examples. This is a long list of possible data points. In this case, a one dimensional signal plus a label. We'll use this to create two generators, a training set generator and an evaluation set generator. In each of these, we set up an infinite loop while true, randomly choose some index from the length of this list of examples and then return the example that corresponds to that index. So in each case, we take our big bag of examples, we randomly pull one out, send a copy of it, put it back in the bag and then do that over and over again. The training set and the evaluation set are both pulling out of the same bag. So we're not going to expect to see any difference there. This is not what we're testing with this data set. Here we just want something that will put our convolutional block through its paces. And then we take these two functions that we created within the function and return the functions themselves, the training set and the evaluation set. And these then are what become the engines in our training data and evaluation data blocks that we just looked at. Now get blips. This is where we generate our actual fake data. So the way we do that is we seed our random number generator so that we'll get pseudo random results but the same ones every time we run it. So we initialize an empty list of blips. We specify that we want our signal to be 21 elements long but the blip that occurs will just be 7 elements long. So our entire signal will be 0 except for this little island of 7 elements that will be non-zero. And we'll generate 100 different ones of these for each flavor of blip. Now we defined our 4 different flavors. They'll each be named after a capital letter M, V, N and H. And these are very approximately named by the shape that they take if you plot them out on a line. So if you look, either each 7 numbers long, if you look at the M for instance, it jumps high with the first element 1 and then descends lower, lower down to 0.1 and then back up higher, 0.4.7, back up to 1 and then it'll drop down to 0 again because all the rest of the values are 0. So it kind of looks like the capital letter M sitting on a number line. V starts the numbers before and after are 0. It descends minus 0.1, minus 0.4 all the way down to minus 1 and then back up gradually on a ramp back up to 0. So it kind of looks like a V or a notch or a divot in the number line. The N starts at 0 and then drops to minus 0.7, jumps to 0.7, slowly descends down to minus 0.7, jumps back up to 0.7 and then it'll settle back to 0. And then H doesn't really look like an H at all. It has a peak up to 1, back down to 0. And then at the other end another peak down to minus 1 and then back to 0. So two isolated peaks in different directions. These are our four different flavors of blip. There are seven elements long and when we generate an example of a blip, we'll start with a full example full of zeros. So 21 zeros in a row. We'll randomly choose a location in the middle of those seven elements to be this particular flavor of blip. And then we return that. We make sure to modify it so that it's actually a two-dimensional array. So it's in the column direction all in one row. This is the format that the convolutional neural network will expect to see it in. Remember, channels are across rows and the signal is across columns. So this is a single channel signal. And then we set up for the number of examples of each flavor, we iterate through and generate one of each, an M, a V, an N, and an H. We generate a tuple, so a paired signal with a blip, and then the label, so just a string, capital letter M, V, N, and H. So that we know both the data and the label that should go with it. So each of these is an example. And then this set of blips, this list of blips is what gets returned and used to fuel the generators that then form the engines for our training data and evaluation data blocks. This means that each time we call forward pass on training data or evaluation data, what we get is in a two-dimensional array, one row by 21 columns with a blip somewhere in there of seven elements that are nonzero. And it'll be one of four flavors. And then it'll also come in a tuple with the label for what flavor that should be. M, V, N, or H. So we're getting two elements, a two-item tuple, each time we call forward pass. There's a little bit of code here at the bottom. This I found particularly useful when writing the data loader just to be able to test the results as we go. And I left it here because it's a good example of a simple end-to-end test. It doesn't absolutely guarantee that every tiny part works as intended, but it does show broadly that you can run it. You can look at the result. The result looks reasonable. If anything were badly broken, it would fail to run or produce very wild output. So this lets us verify that everything's working as it should be. And now we have data, a set of training and evaluation data blocks to pair with our convolutional neural network.