The goal of this project was to predict the next term of any given number sequence by using data from OEIS. This was done using a combination of machine learning and graphing methods to create functions that would tell you what the next term is most likely going to be.
Importing OEIS Data
Importing OEIS Data
The On-Line Encyclopedia of Integer Sequences, or OEIS, is a tool that many mathematicians and other scientists use find sequences that have been discovered. OEIS contains over 300,000 number sequences.
Imports data from https://oeis.org/wiki/Download
In[]:=
Deletes the front part about licensing and splits it into sequences with their A-number:
In[]:=
list=StringSplit[Drop[StringSplit[data,"\n"],4]];
Splits that into a list that has the sequence’s A-number and the sequence:
In[]:=
stringList={#[[1]],Drop[#,1]}&/@list;
Turns the strings into integers:
In[]:=
sequencesList={#[[1]],ToExpression[Flatten[StringSplit[#[[2]],","]]]}&/@stringList;
Turns the lists into associations:
In[]:=
sequencesAssoc=<|#[[1]]->#[[2]]&/@sequencesList|>;
Makes the association into a Hash Table:
In[]:=
sequencesData=CreateDataStructure["HashTable",sequencesAssoc];
Getting all the sequences
In[]:=
sequence=sequencesData["Values"];
Restricting the Data
Restricting the Data
From many tests, it was determined that sequences that contained less than 10 terms, had negative numbers, and had terms larger than 100 caused many errors. So I decided to remove them from the dataset. For some of the models, the sequences had to be non-decreasing as well as the other restrictions.
Creates a limit so that the terms stay smaller than 100 and are all positive:
In[]:=
boundary[x_]:=AllTrue[x,(0<=#<=100)&]
Selects all sequences that follow this limit:
In[]:=
sequence2=Select[sequence,boundary[#]&&Length[#]>=11&];
For some of the models, the sequences had to be non-decreasing as well as the other restrictions.
Creates limit so that all the sequences have terms that are not decreasing
increasing[x_]:=AllTrue[Table[(x[[n]]<=x[[n+1]]),{n,Range[1,Length[x]-1]}],TrueQ]
Selects all the sequences that follow that
In[]:=
sequence3=Select[sequence,boundary[#]&&Length[#]>=11&&increasing[#]&];
Using Classify
Using Classify
Classify is the first thing I tried. However, I discovered that Classify isn't reliable with this kind of input, a list of varying lengths that is order-dependent, due to it being inaccurate quite often. I adjusted the input with 2 different methods. The first way I tried was by using strings.
I first made the sequences into a strings with terms, separated by spaces, that would be associated with the next term in the sequence. For example, {"0"->"1","0 1"->"0",....}:
In[]:=
classifyTrainingSet1=Flatten[Table[StringDelete[ToString[Take[#,n]],{"{","}",","}]->Part[#,n+1],{n,Range[10]}]&/@sequence2];
Then turned that into a Classify function:
In[]:=
classifyMethod1=Classify[classifyTrainingSet1,Method->"NeuralNetwork"]
In[]:=
However when tested, the output remained 1 for almost every sequence I tested it with. As shown below:
In[]:=
Round[classifyMethod1["0 1 2 3"]]
Out[]=
1
This showed that using classify with this method wasn't useful. So then I tried using lists instead of strings.
I first made the sequences into a group of terms that would be associated with the next term in the sequence. This was achieved by using zeros as a filler to make sure that all of the lengths the same. For example, {{0,0,0,0,0,0,0,0,0,0}->1,{0,0,0,0,0,0,0,0,0,1}->0,...}:
In[]:=
classifyTrainingSet2=Flatten[Table[Flatten[Prepend[Take[#,n],Table[0*x,{x,10-n}]]]->Part[#,n+1],{n,Range[1,10]}]&/@sequence2];
Then making the data into a Classify function:
In[]:=
classifyMethod2=Classify[classifyTrainingSet2,Method->"NeuralNetwork"]
In[]:=
Due to the fillers, typing the function was difficult, so I made running the function easier by automatically filling the fillers:
In[]:=
classify1[x_]:=Round[classifyMethod2[Take[Flatten[Join[{0,0,0,0,0,0,0,0,0,0},x]],-10]]]
When tested, the output was more accurate then the first method. An example is shown below.
In[]:=
classify1[{0,1,2,3}]
Out[]=
4
When using a data set of 100 sequences, this method was right 60 percent of the time. However, an issue with this way is that it only takes a specified amount of terms, which I currently have set to 10.
Creating Neural Networks
Creating Neural Networks
Due to classify being relatively inaccurate, I tried using a neural network instead. I used a template given to me by the TA Nicolò. I only used the first 10,000 sequences in the OEIS data because using the full data set would take at least 24 hours to run.
I imported the data for the first 10,000 sequences in the OEIS Data:
data1000=CloudGet[
https://www.wolframcloud.com/obj/7f7c407b-fbee-4fc4-99be-973276ae1a1d
];list1000=StringSplit[Drop[StringSplit[data1000,"\n"],4]];stringList1000={#[[1]],Drop[#,1]}&/@list1000;sequencesList1000={#[[1]],ToExpression[Flatten[StringSplit[#[[2]],","]]]}&/@stringList1000;sequencesAssoc1000=<|#[[1]]->#[[2]]&/@sequencesList1000|>;sequencesData1000=CreateDataStructure["HashTable",sequencesAssoc1000];sequence1000=sequencesData1000["Values"];sequence21000=Select[sequence1000,boundary[#]&&Length[#]>=11&];Then I turned that data into strings with terms, separated by spaces, that would be associated with the next term in the sequence. Exactly like classifyTrainingSet1:
In[]:=
trainingDataF=Flatten[Table[StringDelete[ToString[Take[#,n]],{"{","}",","}]->ToString[Part[#,n+1]],{n,Range[10]}]&/@sequence21000];
I setup the neural network and created the skeleton of the neural network:
In[]:=
tokens=Append[ToString/@Range[0,1000]," "];
In[]:=
enc=NetEncoder[{"Characters",tokens}];dec=NetDecoder[{"Class",tokens}];
In[]:=
net=NetChain[{EmbeddingLayer[32],LongShortTermMemoryLayer[20],SequenceLastLayer[],LinearLayer[]},"Input"->enc,"Output"->dec]
In[]:=
I then trained the neural network on the data given:
In[]:=
trained=NetTrain[net,trainingDataF,BatchSize->4,MaxTrainingRounds->200,ValidationSet->Scaled[0.1]]
In[]:=
In[]:=
trained["0 1 2 3"]
Out[]=
4
This worked similarly to method 2 of Classify, but took a longer time.
Using Graphs
Using Graphs
Due to the fact that Classify is somewhat unreliable and that making a neural network would take too much time and data, I then tried to use graphs to visualize and also predict the sequences.
Predict
Predict
In order to predict the next term in a sequence, I realized that the positions of the terms matters a lot. When using Classify, I learned that Classify doesn't care about the order of the numbers. So when using this method, it made sense to create coordinates that contained the terms and its position in the sequence.
I created coordinates containing a term and it's position. Then I associated a coordinate pair with the coordinate pair that contains the next term:
In[]:=
method1Set1=Flatten[Table[Join[{Part[#,n]},{n}]->Join[{Part[#,n+1]},{n+1}],{n,Range[1,10]}]&/@sequence3];
Then I used it to predict what the next term is when given a sequence. In order to predict the next term, I found the most common next term:
In[]:=
cases[x_List]:=Normal[ReverseSort[Counts[Select[method1Set1,MatchQ[#,{x[[-1]],Length[x]}->{_,_}]&]]]][[1,1,2,1]];cases[___]:="Please input a list"
As shown below, this method has a similar accuracy rate to Classify. But it only work for non-decreasing sequences:
In[]:=
cases[{0,1,2,3}]
Out[]=
4
Visualize
Visualize
I used 4 different methods to visualize the sequences. The first two methods use coordinates to plot the terms. The last two methods just take a term and the next term. This allows for us to see the connections of terms regardless of position. All the methods contains sequences with more than 10 terms, have positive terms, and had terms smaller than 100.
This method doesn't add any other restrictions. But in order to make the graph less chaotic, I only used the first 1000 sequences and removed the associations that don't repeat at least 6 times :
In[]:=
sequence4=sequence2[[1;;1000]];method2Set1=Flatten[Table[Join[{Part[#,n]},{n}]->Join[{Part[#,n+1]},{n+1}],{n,Range[1,10]}]&/@sequence4];method2Set2=Keys[Select[Counts[method2Set1],#>6&]];
The graph, as shown below, starts to create a pattern of sequences being trapped within a small range. When given more data, this pattern is present in a wider range:
In[]:=
DirectedGraph[method2Set2,VertexLabels->#->#[[1]]&/@DeleteDuplicates[Flatten[Map[{#[[1]],#[[2]]}&,method2Set2],1]],VertexCoordinates->({#[[1]],10-#[[2]]}&/@DeleteDuplicates[Flatten[Map[{#[[1]],#[[2]]}&,method2Set2],1]])]
The second method has restricts it so that all the sequences are non-decreasing and that the associations has been repeat at least 20 times :
The graph, as shown below, also creates a similar pattern. It also presents the pattern in a wider range when given more data:
The last two methods use the same data. However they organized differently. One of them is in a spiral layout, the other is a circle.
For both of these graphs, the more data there is, higher the chance of a number going back to itself.
Conclusion
Conclusion
◼
It is incredibly difficult to be able to predict sequences especially since many of the sequences share terms
◼
For example, multiple sequences have 0,1,1,1 in it. So when using Classify, it doesn't know which sequence you are wanting and outputs an average of all the possibilities
◼
Classify has a low accuracy rate with certain forms of data.
◼
Classify also doesn't care about order.
◼
For example 1,2 is the same as 2,1
◼
There are a lot of patterns that show when looking at a lot of sequences
Future Works
Future Works
◼
Making it possible to predict terms of sequences that contain:
◼
Negative numbers
◼
Numbers larger than 100
◼
Adding the sequences' names in order to identify the sequence
CITE THIS NOTEBOOK
CITE THIS NOTEBOOK
Predict and visualize terms of number sequences
by Anne Shuai
Wolfram Community, STAFF PICKS, July 13, 2023
https://community.wolfram.com/groups/-/m/t/2963302
by Anne Shuai
Wolfram Community, STAFF PICKS, July 13, 2023
https://community.wolfram.com/groups/-/m/t/2963302