diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..f186104 --- /dev/null +++ b/404.html @@ -0,0 +1,282 @@ + + + + + + + + +404 | GranaData + + +404 | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+
+

404 Page does not exist!

+

Please use the search bar at the top or visit our homepage!

+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..9857bd3 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +granadata.art diff --git a/HMM-padel-part2/index.html b/HMM-padel-part2/index.html new file mode 100644 index 0000000..bcf5aa5 --- /dev/null +++ b/HMM-padel-part2/index.html @@ -0,0 +1,1037 @@ + + + + + + + + +Modelling a padel match with Hidden Markov Models (Part 2) | GranaData + + +Modelling a padel match with Hidden Markov Models (Part 2) | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 15 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Modelling a padel match with Hidden Markov Models (Part 2)

+ +
+ + + + + + + + + + +
+ + + +

In my last post I explained the usefulness of Hidden Markov Models for predicting the outcome of a padel match with only a few observations. There I also showed how easy it was to implement everything in Python, but I left the most important part: the HMM itself. Today we are going to learn how to design a HMM to predict the result of a point. This is going to be an iterative process. The final model, as you will see, is a monstruosity. But step by step we are goint to build it succesfully. “Rome wasn’t made in a day”.

+ +

Inspiration and design

+ +

Before diving into the details, let me explain how I tackled this problem. I think the creative process is worth mentioning. If you just want the details, you can skip this section. Before starting to code or thinking on my own for the solution of a problem I always research for similar problems that are already solved so that I can get some inspiration. In this case, it turned out that somebody had already designed a HMM for tenis. In this article, they present a HMM for following the state of a tennis match. The observations in this article were poorly defined, but the hidden states wer very clear. I could get an idea of what I had to do by just looking at this image.

+ +

tenisHMM

+ +

This graph is just depicting visually the rules of tenis. In our case we just have to show the rules of padel with a similar graph. If you look carefully you will see that the white boxes represent hidden states with an obvious observation. For padel there will be more of them because apart from bouncing on the ground, the ball can bounce on the walls, which makes the graph more complex but the idea is the same. Another helpful diagram on the same article represented the same graph but organised in several subgraphs.

+ +

tenisHMM2

+ +

What’s interesting here is that those four subgraphs will be the same for padel. The high-level view of the process (top) is exactly the same. That’s part of the job which is already done. We just have to change the internal representation between those four subgraphs and maintain the interconnections.

+ +

What about designing graphs? What is the software I used for that? You may say. Well, it is actually a pen and a lot of papers. No matter how well developed graph visualizations software are, drawing a simple graph by hand will always be faster than coding it. Of course, when the project keeps growing and the graphs becomes massive you will need those software tools which I will mention later. But at the beginning, just take a pen and start drawing. In the other sections I will present diagrams made with a computer because they are visually more pleasant and you will understand them better. Nevertheless, here is one of the graphs I painted by hand, in case you are curious.

+ +

hand-diagram

+ +

The hidden states’ graph

+ +

For a HMM we need the transition and emission matrices. The transition matrix is going to be the adjacency matrix of the transitions graph. That graph is simply a representation of the rules of the game. I am going to distinguish two main parts in that rules. The rules for the serve and the rules for the normal game. The reason for creating two distinct graphs is because the effect of the ball going out or touching the net is different at the beginning.

+ +

Serve

+ +

What happens when a player is on their serve? It can go in, it can go out or it can touch the net. And if it touches the net, it can then go in or out. Let’s ignore when it goes in without touching the net for the moment. How would you represent the 1st serve? Like this?

+ +

1st-serve

+ +

Did you think about the init state? Remember, this model is a realistic one. In practice you don’t know when is the point starting. Therefore, you need a special state for waiting until you have enough evidence that the match has begun. If you look closely, you will see that there is a self-loop on the init state. That is how we represent a waiting state in a HMM, by a self-loop. Now, let’s pass to the 2nd-serve. How would you design it?

+ +

2nd-serve

+ +

Exactly the same as the first service. The simpler, the better. We are ignoring when the ball goes in and the game continues. We are just focusing on when the ball goes out or to the net. The rest of the details will be added later on. For now let’s just focus on what we have. We have a graph with several nodes, each representing a state. What do we need? Consistent labels across the whole graph. One of the limitations of the HMM is that you have to fulfill the Markov property. Which means that the state representing going out after the 1st-serve is different than the state of going out after the 2nd-serve. So they need different names. In my case, I just added a suffix number when that happened. That way, going out in the first service is ‘out1’ and after the second is ‘out2’. Another state that repeats a lot across the graph is the ‘in’ state. For that one adding a suffix number is not enough, so I added a more descriptive suffix. For instance, going in after touching the net in the first serve is ‘in-net1’. This is a decision that could have been made of many ways, but I decided to make it like this.

+ +

Okay, let’s now talk about what happens when the ball actually goes in and the game continues. To keep it simple, let’s focus on what happens before any player hits the ball. And let’s call this the ace model. As the name states it, one of the things than can happen is an ace. What characterizes an ace? The fact that the ball touches the ground again before any player hitting it. Try to draw the scheme for the ace model. Keep in mind that before touching again the ground it can hit the walls. And also bare in mind that there are two types of walls. What are the connections among those states? Which combinations are valid and which not? Here is my solution for that problem, omitting the connections to the states Point-server and Point-receiver representing the end of the game.

+ +

ace

+ +

Did you thought of the ‘time-out’ state? Again, this is a real model so it has to deal with real problems. And one of them is that you miss the observation that characterizes the end of the game. If that happens you can only know the game has finished by time. That’s why you need a state to represent the end of the game by time. Later on when defining the emissions it will be made more clear why this state is needed. Most of the extra states are created so that when an observation is wrong, there is still a path in the graph to the end. Otherwise, the model will give an error and doesn’t return anything. Observe also that there are two ‘in’ states. Can you imagine why? The state ‘in1’ is when the ball goes in on the first serve, and ‘in2’ on the second. Since we have to maintain the Markov property, those two states are different although they have the same emissions and are identical to us.

+ +

Before going to the next block, which is when a player hits the ball and the game actually starts, let me explain how I made this pictures and how you can code graphs on Python. With the library networkx you can do many thing on graphs, it has implemented almost every algorithm that exists related to graphs. In this project we only use it to define the graphs. The syntax is pretty straighforward, you define a DiGraph which stands for directed graph, and add nodes and edges with the functions add_nodes_from and add_edges_from. After that, you can save the model in gml format and open it with Gephi. That’s it. Here is the code for the three models presented above.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+
import networkx as nx
+
+folder_path = 'graphs/'
+
+""" First serve model """
+first = nx.DiGraph()
+first.add_nodes_from([
+    ("init", {"hidden": True}),
+    ("1st-serve", {"hidden": True}),
+    ("net1", {"hidden": True}),
+    ("out1", {"hidden": True}),
+    ("in-net1", {"hidden": True}),
+])
+first.add_edges_from([
+    ("init", "1st-serve"), ("init", "init"),
+    ("1st-serve","net1"), ("1st-serve","out1"),
+    ("net1","in-net1"), ("net1","out1"),
+    ("in-net1","1st-serve")
+])
+nx.write_gml(first, folder_path + 'first.gml')
+
+""" Second serve model """
+second = nx.DiGraph()
+second.add_nodes_from([
+    ("2nd-serve", {"hidden": True}),
+    ("net2", {"hidden": True}),
+    ("out2", {"hidden": True}),
+    ("in-net2", {"hidden": True}),
+])
+second.add_edges_from([
+    ("2nd-serve","net2"), ("2nd-serve","out2"),
+    ("net2","in-net2"), ("net2","out2"),
+    ("in-net2","2nd-serve")
+])
+nx.write_gml(second, folder_path + 'second.gml')
+
+""" Ace model """
+ace = nx.DiGraph()
+ace.add_nodes_from([
+    ("in1", {"hidden": True}),
+    ("in2", {"hidden": True}),
+    ("time-out", {"hidden": True}),
+    ("ground", {"hidden": True}),
+    ("wall-outer1", {"hidden": True}),
+    ("wall-outer2", {"hidden": True}),
+    ("wall-inner1", {"hidden": True}),
+    ("wall-inner2", {"hidden": True}),
+])
+ace.add_edges_from([
+    ("in1", "time-out"), ("in1", "ground"), ("in1", "wall-outer1"), ("in1", "wall-inner1"),
+    ("in2", "time-out"), ("in2", "ground"), ("in2", "wall-outer1"), ("in2", "wall-inner2"),
+    ("wall-outer1", "time-out"), ("wall-outer1", "ground"), ("wall-outer1", "wall-outer2"),
+    ("wall-outer2", "time-out"), ("wall-outer2", "ground"),
+])
+nx.write_gml(ace, folder_path + "ace.gml")
+
+ +

Gephi has some handy features that make posible visualize big graphs. Concretely, you can use the Force Atlas distribution to reorder the nodes by simulating forces proportional to the number of edges they have. It has many parameters you can try, but I normally just click on execute and wait a few seconds for convergence.

+ +

Force Atlas

+ +

Then, on the previsualization tab, you can create the diagrams I showed you. It has many options, like curved edges. I don’t use that feature for this post because for complex graphs it can be messy. But for some graphs I think is prettier with curvy edges. Other things to adapt are the font size and the size of the arrows. With a bit of practice you can create nice figures quite fast.

+ +

Previsualization

+ +

Rally

+ +

The rally model is a bit more complex than the ones presented above. There are two ways to design it based on which observations you have. If you only have an observation for bouncing on the ground, anywhere, then the rally model only has one ‘HIT’ state and the rest is similar to the ace model. However, in practice you can have more information than that. Suppose you have an image of a game and you know the location of the ball together with the fact that it is a bounce in the ground. You could, potentially, distinguish if the ball is on the server side or on the receiver side. You just need to segment the court and see if the ball is on the upper side or not. This is not trivial, but is possible to achieve. For that reason, we are going to have two ‘HIT’ states: one for the server side and one for the receiver. The rest is just identical to the ace model, with one exception. Instead of ending the game or returning to the beginning, the states point to the other ‘HIT’ states. Similar to what would happen in a match. Your head is going from one side to the other. Here the hidden state is moving from one model to the other. Finally, the diagram.

+ +

Rally

+ +

If you understood the ace model, you just need to focus on the arrows that cross from left to right and vice versa. The rest is just the standard mechanics of a padel game. One more thing to notice is that there are many arrows missing. Concretely, the arrow between the models and the arrows that point to the absorbing states, that is, those which end the game. In the next section, I will try to describe the connections between the models and will show you the (almost) full picture of the HMM transition graph. Also, below is the code for this model.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+
""" Rally model """
+rally = nx.DiGraph()
+rally.add_nodes_from([
+    ("HIT1", {"hidden": True}),
+    ("net-HIT1", {"hidden": True}),
+    ("in-HIT1", {"hidden": True}),
+    ("out-HIT1", {"hidden": True}),
+    ("time-out-HIT1", {"hidden": True}),
+    ("ground-HIT1", {"hidden": True}),
+    ("wall-inner-HIT1", {"hidden": True}),
+    ("wall-outer1-HIT1", {"hidden": True}),
+    ("wall-outer2-HIT1", {"hidden": True}),
+    
+    ("HIT2", {"hidden": True}),
+    ("net-HIT2", {"hidden": True}),
+    ("in-HIT2", {"hidden": True}),
+    ("out-HIT2", {"hidden": True}),
+    ("time-out-HIT2", {"hidden": True}),
+    ("ground-HIT2", {"hidden": True}),
+    ("wall-inner-HIT2", {"hidden": True}),
+    ("wall-outer1-HIT2", {"hidden": True}),
+    ("wall-outer2-HIT2", {"hidden": True}),
+])
+rally.add_edges_from([
+    ("HIT1", "out-HIT1"), ("HIT1", "in-HIT1"), ("HIT1", "net-HIT1"),
+    ("in-HIT1", "HIT2"), ("wall-inner-HIT1", "HIT2"), ("wall-outer1-HIT1", "HIT2"), ("wall-outer2-HIT1", "HIT2"),
+    ("net-HIT1", "in-HIT1"), ("net-HIT1", "out-HIT1"),
+    ("in-HIT1", "time-out-HIT1"), ("in-HIT1", "ground-HIT1"), ("in-HIT1", "wall-inner-HIT1"), ("in-HIT1", "wall-outer1-HIT1"), 
+    ("wall-inner-HIT1", "time-out-HIT1"), ("wall-inner-HIT1", "ground-HIT1"), ("wall-inner-HIT1", "wall-outer2-HIT1"),
+    ("wall-outer1-HIT1", "time-out-HIT1"), ("wall-outer1-HIT1", "ground-HIT1"), ("wall-outer1-HIT1", "wall-outer2-HIT1"),
+    ("wall-outer2-HIT1", "time-out-HIT1"), ("wall-outer2-HIT1", "ground-HIT1"),
+
+    ("HIT1", "HIT2"), ("HIT2", "HIT1"),
+    
+    ("HIT2", "out-HIT2"), ("HIT2", "in-HIT2"), ("HIT2", "net-HIT2"),
+    ("in-HIT2", "HIT1"), ("wall-inner-HIT2", "HIT1"), ("wall-outer1-HIT2", "HIT1"), ("wall-outer2-HIT2", "HIT1"),
+    ("net-HIT2", "in-HIT2"), ("net-HIT2", "out-HIT2"),
+    ("in-HIT2", "time-out-HIT2"), ("in-HIT2", "ground-HIT2"), ("in-HIT2", "wall-inner-HIT2"), ("in-HIT2", "wall-outer1-HIT2"), 
+    ("wall-inner-HIT2", "time-out-HIT2"), ("wall-inner-HIT2", "ground-HIT2"), ("wall-inner-HIT2", "wall-outer2-HIT2"),
+    ("wall-outer1-HIT2", "time-out-HIT2"), ("wall-outer1-HIT2", "ground-HIT2"), ("wall-outer1-HIT2", "wall-outer2-HIT2"),
+    ("wall-outer2-HIT2", "time-out-HIT2"), ("wall-outer2-HIT2", "ground-HIT2")
+
+])
+nx.write_gml(rally, folder_path + "rally.gml")
+
+ +

Interconnections

+ +

Let’s go model by model, edge by edge, starting by the 1st-serve. It has two connections, one to the ‘in1’ state of the ace model and one to the 2nd-serve. The latter is from the ‘out1’ state, if the ball goes out, you have a second service, that’s the rules. The 2nd-serve model is similar, one connection to the ‘in2’ state and one to the absorbing state ‘Point-receiver’. If you fail your second serve, you lose the point. Simple, concise and clear. Let’s continue with the ace model. The ‘ground’ and ‘time-out’ states point to ‘Point-server’. This is the representation of an ace. If the ball hits the ground twice, it’s an ace and the point goes to the server side. The rest of the connections are to the ‘HIT1’ state, which means that the receiver has hit the ball and the game continues. And that’s it. The only remaining connections are between the rally model and the absorbing states, just like in the ace model. If the ball bounces twice, or if it goes out of the court after bouncing in, the last player wins. If it goes out, the last player loses. For those of you that know how to play padel, this should be no surprise. There are many cases to deal with because the ball can hit the walls and the net. But more or less it is summarized like that. Here’s the full picture.

+ +

Union

+ +

The code for this part is different. This time we have to join the four different models. To do so we are going to use the function union_all that creates a new graph with all the nodes and edges from before but without any connections between the subgraphs. To add those connections we use the add_edge function and for the absorbing states the add_nodes_from. This is the result.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
""" Graph union plus connection edges """
+union = nx.union_all((first, second, ace, rally))
+union.add_edge("out1", "2nd-serve")
+union.add_edge("1st-serve", "in1")
+union.add_edge("2nd-serve", "in2")
+union.add_edge("wall-inner1", "2nd-serve")
+
+""" Absorbing states """
+union.add_nodes_from([
+    ("Point-receiver", {"hidden": True}),
+    ("Point-server", {"hidden": True})
+])
+union.add_edge("wall-inner2", "Point-receiver")
+union.add_edge("out2", "Point-receiver")
+union.add_edge("time-out", "Point-server")
+union.add_edge("ground", "Point-server")
+union.add_edge("out-HIT1", "Point-server")
+union.add_edge("time-out-HIT1", "Point-receiver")
+union.add_edge("ground-HIT1", "Point-receiver")
+union.add_edge("out-HIT2", "Point-receiver")
+union.add_edge("time-out-HIT2", "Point-server")
+union.add_edge("ground-HIT2", "Point-server")
+
+""" Connection between ace and rally """
+union.add_edge("in1", "HIT1")
+union.add_edge("in2", "HIT1")
+union.add_edge("wall-outer1", "HIT1")
+union.add_edge("wall-outer2", "HIT1")
+nx.write_gml(union, folder_path + "union.gml")
+
+ +

Delays

+ +

Previously I said this was almost the full picture. The reason for that is that the model here does not take into account that the ball is flying in between bounces. In an ideal model this would be irrelevant, with just the bounces we can predict the outcome of the game. But in practice that is not true. Do you remember the reason for using a ‘time-out’ state? Here is similar. Imagine you detect the same bounce twice by error. If you only look at bounces you will assume that the game has finished. However, if you detect twice the same bounce, in time they will be very close. In contrast with what will happen if those two bounces are both real. Therefore, if you somehow take into account the time between observations you can solve those kind of errors. The way to do that is by adding ‘flying’ states. In between any two states you include a ‘flying’ state and in the emissions you consider ‘flying’ as a plausible observation. This way you have a way of measuring time. The more ‘flying’ observations you have, the more time that has passed between states. The key here is that the ‘flying’ state has a self-loop. Similar to the ‘init’ state. You don’t know how much time is going to occur between states. For that reason you add a self-loop to stay in that state until there is evidence enough that you are not flying anymore.

+ +

For this part there is no diagram. As you may have guessed, adding one state for every edge is going to make the model huge and very complicated to deal with. At this step, I mostly work with the code. Adding all the edges by hand is a nightmare. For that reason, I let python do that for me. Before showing you the code, there is one more hypothesis to deal with. I said that the reason for the ‘flying’ state is to correct duplicate observations. But to do that it is needed to add more than one ‘flying’ state per edge. Why? Because the first ‘flying’ states are going to be corrective states without self-loop. It is only the last one that is a waiting state. Those corrective states can emit the same observations as the first state. While the waiting state can only emit the ‘flying’ observation. The reason for this is mostly empirical. Using only one ‘flying’ state yielded unsatisfactory results. In my experiments I ended up using five ‘flying’ states: four correctives, and one waiting. Finally, the code for that.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
""" Add flying states """
+current_edges = list(union.edges)
+fly_err_len = 5
+for (u,v) in current_edges:
+    if u == "init":
+        continue
+    union.add_nodes_from([("flying-"+u+'-'+str(k), {"hidden": True}) for k in range(fly_err_len+1)])
+    union.remove_edge(u, v)
+    union.add_edge(u, "flying-"+u+"-0")
+    union.add_edge(u, "flying-"+u+"-"+str(fly_err_len))
+    for k in range(fly_err_len):
+        union.add_edge("flying-"+u+'-'+str(k), "flying-"+u+'-'+str(k+1))
+        union.add_edge("flying-"+u+'-'+str(k), "flying-"+u+'-'+str(fly_err_len))
+    union.add_edge("flying-"+u+'-'+str(fly_err_len), "flying-"+u+'-'+str(fly_err_len))
+    union.add_edge("flying-"+u+'-'+str(fly_err_len), v)
+
+ +

And the last part of the code is to convert the ‘Point-server’ and ‘Point-receiver’ states into waiting states. The reason for this is numerical. When creating the transition and emission matrices the values need to be normalized. If you don’t add this connections you have rows with all zeros that give errors and it is easier to solve them like this. Those edges are added after creating the ‘flying’ states because those edges don’t need any ‘flying’ states attached to them, they are simply a trick for the computation.

+ +
1
+2
+3
+
""" Self-loops for absorbing states """
+union.add_edge("Point-receiver", "Point-receiver")
+union.add_edge("Point-server", "Point-server")
+
+ +

The observations’ graph

+ +

First, let’s define the observations.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
""" Possible observations """
+obs = nx.DiGraph()
+obs.add_nodes_from([
+    ("player-hit", {"hidden": False}),
+    ("bounce-ground-receiver", {"hidden": False}),
+    ("bounce-ground-server", {"hidden": False}),
+    ("bounce-net", {"hidden": False}),
+    ("bounce-wall-inner", {"hidden": False}),
+    ("bounce-wall-outer", {"hidden": False}),
+    ("flying", {"hidden": False}),
+    ("end", {"hidden": False})
+])
+
+ +

This is all we can observe, at least automatically with a camera. We can observe the ball bouncing anywhere: in the walls, in the net, or in the ground. And we can observe any player hitting the ball. Notice that we don’t have to distinguish which player hits the ball, that job is done by distinguishing where is the ball bouncing. The reason for that is that it is quite difficult in practice to detect when a player hits the ball and which player it is due to projection. With just one observation representing all the player is enough to solve the problem. Keep in mind that this is for real cases, and the model has to reflect the limitations of the detections. There is one special observation called ‘end’. The HMM presented here can only deal with isolated points. The ‘end’ observation is only emitted by the absorbing states. It is a way of forcing the HMM to find a solution. When dealing with more than one point it is needed to detect when the point has finished.

+ +

As with the ‘flying’ states, I didn’t add all the emissions by hand. There are a lot of nodes in the transition graph. And the emission graph is a bipartite graph with transitions on one side and emissions on the other. That is a lot of edges. But in the end, is no more than a regex problem. The states have descriptive names. Any state with ‘ground’ in their name is going to emit either ‘bounce-ground-receiver’ or ‘bounce-ground-server’. And the ‘flying’ states emit the ‘flying’ observation. There is no fancy ideas here, just nasty work. I leave here the code for you. There are better ways to code this for sure, but this works and it’s mine, so I like it.

+ +

There are two more things to mention. The flying probability and the missing probability. As I said before there are corrective states. Those corrective states can emit the same observation as the state they are attached to it, but with a smaller probability. Otherwise, if the probability isn’t lower, we are not taking into account the fact that the more separated two observations are, the more likely they are to be correct. Thus, the corrective ‘flying’ states have some probability of emit ‘flying’ and some probability of emiting other things. The missing probability is similar but is for the other states. If instead of a duplicate you miss an observation, the graph still needs to find a path to the end. For that reason every state has some little probability of emitting ‘flying’. And there are many other little changes that were added in the process of creating this matrix. The justification behind most of the strange things you will see in the code is empirical. You start with a simple model and find a case where it doesn’t work, change the model and repeat. After several iterations you arrive at this.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+
""" Create bipartite graph representing emissions """
+G = union.copy()
+G.clear_edges()
+U = nx.union(G, obs)
+flying_prob = 0.9
+err_miss_prob = 0.001
+eps = 1e-12
+for u in G.nodes():
+    if "Point" in u: 
+        #U.add_weighted_edges_from([(u, "flying", 1)])
+        U.add_weighted_edges_from([(u, "end", 1)])
+        continue
+    if "flying" in u and "init" not in u:
+        U.add_weighted_edges_from([(u, "flying", flying_prob)])
+        U.add_weighted_edges_from([(u, "bounce-ground-server", eps)])
+        U.add_weighted_edges_from([(u, "bounce-ground-receiver", eps)])
+        if fly_err_len > 0 and fly_err_len == int(u.split('-')[-1]):
+            continue
+    elif u not in ["net1", "net2", "wall-inner1", "wall-inner2"]:
+        U.add_weighted_edges_from([(u, "flying", err_miss_prob)])
+
+    if "init" in u:
+        U.add_weighted_edges_from([(u, "bounce-ground-receiver", (1-flying_prob)/5)])
+        U.add_weighted_edges_from([(u, "bounce-ground-server", (1-flying_prob)/5)])
+        U.add_weighted_edges_from([(u, "bounce-wall-inner", (1-flying_prob)/5)])
+        U.add_weighted_edges_from([(u, "bounce-wall-outer", (1-flying_prob)/5)])
+        U.add_weighted_edges_from([(u, "bounce-net", (1-flying_prob)/5)]) 
+        # U.add_weighted_edges_from([(u, "player-hit", eps)]) 
+        U.add_weighted_edges_from([(u, "flying", flying_prob)]) 
+    elif "serve" in u or u == "HIT1" or u == "HIT2" or "flying-HIT1-" in u or "flying-HIT2-" in u:
+        U.add_weighted_edges_from([(u, "player-hit", 1-flying_prob)])
+        U.add_weighted_edges_from([(u, "bounce-ground-receiver", eps)])
+        U.add_weighted_edges_from([(u, "bounce-ground-server", eps)])
+        U.add_weighted_edges_from([(u, "bounce-wall-inner", eps)])
+        U.add_weighted_edges_from([(u, "bounce-wall-outer", eps)])
+    elif "time" in u:
+        U.add_weighted_edges_from([(u, "flying", 1)])
+    elif "wall" in u: # this must be before in and out
+        if "inner" in u:
+            U.add_weighted_edges_from([(u, "bounce-wall-inner", 1-flying_prob)])
+        elif "outer" in u:
+            U.add_weighted_edges_from([(u, "bounce-wall-outer", (1-flying_prob)/2)])
+            if "HIT1" in u:
+                U.add_weighted_edges_from([(u, "bounce-ground-server", (1-flying_prob)/2)])
+            elif "HIT2" in u:
+                U.add_weighted_edges_from([(u, "bounce-ground-receiver", (1-flying_prob)/2)])
+        else: assert(False)
+        U.add_weighted_edges_from([(u, "player-hit", eps)])
+    elif ("in" in u or "ground" in u) and "flying-net" not in u and "flying-out" not in u:
+        if "in1" in u or "in2" in u or "in-HIT2" in u or "ground-HIT2" in u\
+            or "in-net1" in u or "in-net2" in u:
+            U.add_weighted_edges_from([(u, "bounce-ground-receiver", 1-flying_prob)])
+            if "ground-HIT2" in u:
+                U.add_weighted_edges_from([(u, "bounce-ground-server", eps)])
+        elif "in-HIT1" in u or "ground-HIT1" in u:
+            U.add_weighted_edges_from([(u, "bounce-ground-server", 1-flying_prob)])
+            if "ground-HIT1" in u:
+                U.add_weighted_edges_from([(u, "bounce-ground-receiver", eps)])
+        elif "ground" in u:
+            U.add_weighted_edges_from([(u, "bounce-ground-receiver", 1-flying_prob)])
+            U.add_weighted_edges_from([(u, "bounce-ground-server", eps)])
+        else: assert(False)
+        U.add_weighted_edges_from([(u, "player-hit", eps)])
+    elif "out" in u:
+        if "out-HIT1" in u:
+            U.add_weighted_edges_from([(u, "bounce-ground-receiver", (1-flying_prob) / 4)])
+        elif "out-HIT2" in u or "out1" in u or "out2" in u:
+            U.add_weighted_edges_from([(u, "bounce-ground-server", (1-flying_prob) / 4)])
+        U.add_weighted_edges_from([(u, "bounce-wall-inner", (1-flying_prob) / 4)])
+        U.add_weighted_edges_from([(u, "bounce-wall-outer", (1-flying_prob) / 4)])
+        U.add_weighted_edges_from([(u, "bounce-net", (1-flying_prob) / 4)])
+        U.add_weighted_edges_from([(u, "player-hit", eps)])
+    elif "net" in u:
+        U.add_weighted_edges_from([(u, "bounce-net", 1-flying_prob)])
+        #U.add_weighted_edges_from([(u, "player-hit", eps)])
+
+
+ +

The matrices

+ +

Okay, we have the graphs, but what about the matrices? We need those for the hmmkay library. How do we generate them? NetworkX provides a function for generating adjacency matrices (adjacency_matrix). However, we cannot use those matrices as they are, we need to normalize them so that the rows sum up to one. Remember, they represent probabilities. After normalization, we can save the result using pandas.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
import pandas as pd
+
+""" Emission matrix """
+V1 = len(G.nodes())
+B = nx.adjacency_matrix(U).toarray()[:V1,V1:]
+err_change = 0
+B += err_change
+B = B / B.sum(axis=1).reshape((-1,1))
+B_df = pd.DataFrame(B, columns=obs.nodes(), index=G.nodes())
+B_df.to_csv(folder_path + 'B.csv')
+
+""" Transition matrix """
+A = nx.adjacency_matrix(union).toarray()
+A = A / A.sum(axis=1).reshape((-1,1))
+A_df = pd.DataFrame(A, columns=G.nodes(), index=G.nodes())
+A_df.to_csv(folder_path + 'A.csv')
+
+ +

The err_change variable is for adding noise to the emissions so that every state can emit every observation with a little probability. In my experience it doesn’t work well, but I leave it there in case you want to experiment with it.

+ +

Conclusion

+ +

In this post we have seen how to properly design a HMM for following the result of a padel match. We have learnt to use the NetworkX library to create the graphs and the Gephi program to visualize the process. In the next post of this series we will learn how to actually test whether the HMM works. Stay tuned.

+ + +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/HMM-padel-part3/index.html b/HMM-padel-part3/index.html new file mode 100644 index 0000000..6ced170 --- /dev/null +++ b/HMM-padel-part3/index.html @@ -0,0 +1,595 @@ + + + + + + + + +Modelling a padel match with Hidden Markov Models (Part 3) | GranaData + + +Modelling a padel match with Hidden Markov Models (Part 3) | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 6 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Modelling a padel match with Hidden Markov Models (Part 3)

+ +
+ + + + + + + + + + +
+ + + +

In part I and part II of this series I have talked about what is a Hidden Markov Model, why it is useful for modelling a padel match, and how to design it properly. Today the topic is about testing the model. Testing a machine learning model is the basis for deploying it. Here I will be explaining how to test in python and guide you through our HMM example.

+ +

PyTest

+ +

Testing in python can be done with several libraries. One of the most popular ones is pytest. There are many good tutorials out there about how to use it, like this one. And if you have a project on your hands and have specific needs, you can always go and have a look at their documentation. For this post I will pass very briefly on the funcionalities that we need for testing the HMM.

+ +

In pytest you have files that contain the word ‘test’ on it. Those files contain functions that also have the string ‘test’ in their names. And those functions must have code that yields True of False depending on whether they pass the test. To run the tests you just execute pytest on the command line and the library does the rest of the job for you. This is the big picture, let’s define now what are going to be our tests.

+ +

Our tests are going to have a sequence of observations as input and we are going to check for the result of the match. That is the final goal of the HMM. The difference is that here we are going to deal with simple scenarios where we know for sure the result. That way we can check that the HMM is at least well implemented. We cannot check if the design is going to work in a real-world scenario, but we can check that it works in ideal scenarios. If your model breaks in an environment where you control the input, then something is clearly wrong in the code. However, if it breaks in production it may be that the model hypothesis about the world are wrong. That’s why you need these tests, to detect bugs prior to analysing the correctness of the model itself.

+ +

Once the tests are designed, it’s time to implement them. For that, pytest comes with two main features that make the process easier.

+ +

Parameterization

+ +

In pytest you can parameterize the input. When testing a function, several input-output pairs are used to ensure that the function produces the desired result. Using a different file for each input-output pair would be an inconvenience. Instead, you can specify several input-output pairs for a given test. Consider the following test where we are interested in knowing if the HMM correctly detects an ace:

+ +
1
+2
+3
+4
+5
+6
+
def test_ace(sequence, indexers, hmm, hidden_states):
+    indexer_hidden, indexer_obs = indexers
+    sequences = [[indexer_obs[obs] for obs in sequence]]
+    decoded_seq = hmm.decode(sequences)
+    decoded_seq = [hidden_states[idx] for idx in decoded_seq[0]]
+    assert('Point-server' in decoded_seq)
+
+ +

The parameter sequence is a list of observations that corresponds to an ace. Without parameterization we would have to specify a different test function for each sequence that represents an ace. With parameterization we can reuse the function. In pytest that features is implemented with decorators. For those of you who don’t know python enough, a decorator is basically a function of functions. Here it takes the test function as input and creates a parameterized test function as output. Syntactically it is coded like this:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+
@pytest.mark.parametrize("sequence", [
+    ### Ace examples
+    ])
+def test_ace(sequence, indexers, hmm, hidden_states):
+    indexer_hidden, indexer_obs = indexers
+    sequences = [[indexer_obs[obs] for obs in sequence]]
+    decoded_seq = hmm.decode(sequences)
+    decoded_seq = [hidden_states[idx] for idx in decoded_seq[0]]
+    assert('Point-server' in decoded_seq)
+
+ +

You only have to add the decorator on top of the function. If you recall the problem at hands, which sequences could possibly represent an ace? Let’s see some examples:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
### ace in first service ###
+['player-hit', *['flying'] * 10, 'bounce-ground-receiver', *['flying'] * 10, 
+    'bounce-ground-receiver', *['flying'] * 100, 'end'],
+    # Bounce on wall
+['player-hit', *['flying'] * 10, 'bounce-ground-receiver', *['flying'] * 10,
+    'bounce-wall-outer', *['flying'] * 10, 'end'], 
+    # Bounce on wall twice
+['player-hit', *['flying'] * 10, 'bounce-ground-receiver', *['flying'] * 10,
+    'bounce-wall-outer', *['flying'] * 10, 'bounce-wall-outer', *['flying'] * 10, 'end'],
+
+### ace in second service ###
+    # Bounce on wall
+['player-hit', *['flying'] * 10, 'bounce-ground-server', *['flying'] * 10, 
+    'player-hit', *['flying'] * 10, 'bounce-ground-receiver', *['flying'] * 10, 
+    'bounce-wall-outer', *['flying'] * 10, 'end'],
+    # Bounce on wall twice
+['player-hit', *['flying'] * 10, 'bounce-ground-server', *['flying'] * 10, 
+    'player-hit', *['flying'] * 10, 'bounce-ground-receiver', *['flying'] * 10, 
+    'bounce-wall-outer', *['flying'] * 10, 'bounce-wall-outer', *['flying'] * 10, 'end']
+
+ +

Basically, whenever there are two consecutive bounces on the other side after the first player has hit the ball we have an ace. When an ace happens the server wins, therefore we have to check that the last hidden state correspond to the server winning. If that doesn’t happens, then our HMM isn’t working. Passing this test doesn’t prove that the HMM works, but by adding more and more tests we at least know that our model is robust to all those cases.

+ +

Fixtures

+ +

Have you wondered how do you pass parameters to a test? In the previous section I talk about parameterizing with a decorator. But what about the rest of the parameters? Not every parameter is part of the input. There are parameters that are part of the function itself. For instance, the parameter hmm. How does pytest know where to look for that object?

+ +

At the beginning I said that you just have to run pytest in the command line. You don’t specify parameters to the internal test functions directly. Instead, you use fixtures. A fixture is another decorator provided by pytest. In this case, you decorate a function that returns the parameter you want to use later on. Let’s look at an example by specifying the fixture for the hmm object. Suppose that you have a function in your code (not your test, your real code) that initialized the hmm and returns it. Then, you would convert that function to a fixture this way:

+ +
1
+2
+3
+
@pytest.fixture()
+def hmm():
+    return read_hmm()
+
+ +

That’s everything you have to do in order for every other function to know where to look for the hmm object. The same would be needed for the indexers and hidden_states that in my case are just dictionaries to convert from strings of states to the internal identifiers that the HMM uses.

+ +

Noisy tests

+ +

To end this post I’ll show you some concrete tests I designed for my HMM. They are a bit different than the rest of the tests. When evaluating the HMM I said that we give ideal scenarios as input to the tests. But it is possible to give noisy scenarios too, if you control the noise. There are no rules for writing tests, they just serve to check that your code does what you want it to do. And if I want my HMM to be robust to noise, I can test for it.

+ +

When I talk about noise in this problem it would be missing some observation or having repeated observations for the same hidden state. We designed our model so that it would work even on those cases. So if I provide a series of noisy observations, it must predict correctly the result. For example, in the following code there is the test for two cases where the server fails on the second service. However, the first hit is repeated and some bounces are repeated too. This series of observations doesn’t correspond to an ideal one, but the model should correctly predict the result.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
@pytest.mark.parametrize("sequence", [
+    ### Fail in second service ###
+    # Bounce in and then on inner wall
+    ['player-hit','flying', 'flying', 'player-hit', *['flying'] * 13, 
+     'bounce-ground-server', 'flying', 'bounce-ground-server', *['flying'] * 9, 
+     'player-hit', *['flying'] * 10, 'bounce-ground-receiver', *['flying'] * 4, 
+     'bounce-wall-inner', *['flying'] * 10, 'end'],
+     # Bounce out
+    ['player-hit', *['flying'] * 15, 'bounce-ground-server', *['flying'] * 10,
+     'player-hit', 'player-hit', *['flying'] * 12, 'bounce-ground-server', *['flying'] * 10, 'end']
+])
+def test_fail_noise(sequence, indexers, hmm, hidden_states):
+    indexer_hidden, indexer_obs = indexers
+    sequences = [[indexer_obs[obs] for obs in sequence]]
+    decoded_seq = hmm.decode(sequences)
+    decoded_seq = [hidden_states[idx] for idx in decoded_seq[0]]
+    assert('Point-receiver' in decoded_seq)
+
+ +

You can also have tests that don’t pass on purpose. I call this one ‘impossible_noise_test’:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+
""" Test designed to make the model fail """
+@pytest.mark.parametrize("sequence", [
+    [*['flying'] * 3, 'bounce-ground-server', *['flying'] * 3,
+    'bounce-ground-server', *['flying'] * 3,'player-hit', # Bounces in the ground badly detected as player-hit
+    *['flying'] * 3, 'bounce-ground-server', # Badly detected bounce
+    'bounce-ground-receiver', *['flying'] * 5, 'player-hit', *['flying'] * 4, # Well detected serve
+    'bounce-ground-server', 'bounce-ground-server', 'bounce-ground-server', # Same bounce 
+    *['flying'] * 5, 'player-hit', # Well detected response
+    *['player-hit', *['flying'] * 5]*10, # Normal rally (now the ball is for receiver)
+     'bounce-ground-receiver', 'end'] # It goes to net and out
+])
+def test_impossible_noise(sequence, indexers, hmm, hidden_states):
+    indexer_hidden, indexer_obs = indexers
+    sequences = [[indexer_obs[obs] for obs in sequence]]
+    decoded_seq = hmm.decode(sequences)
+    decoded_seq = [hidden_states[idx] for idx in decoded_seq[0]]
+    assert("Point-server" in decoded_seq)
+
+ +

This type of test allows you to know the limits of the model. There must be a limit, and if you don’t manage to create a test that fails, that is also and indicator that something is wrong. Maybe you are not creative enough for the cases, or you have some bug that makes all the test pass (that has happened to me). So it is also a good idea to have a test that fails, just in case.

+ +

Conclusion

+ +

With this post the Hidden Markov Model for Padel Modelling series comes to an end. We have learned everything to deploy a HMM. From the theory behind the model, till testing the model extensively. Those are the steps to put any machine learning model into production. Learning the basics, designing the model and testing the model. The only thing left is to create a pipeline and integrate it into the final product, but that is a topic for another day. I hope you liked the process as much as I did. See you on my next series.

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/HMM-padel/index.html b/HMM-padel/index.html new file mode 100644 index 0000000..ea151ed --- /dev/null +++ b/HMM-padel/index.html @@ -0,0 +1,522 @@ + + + + + + + + +Modelling a padel match with Hidden Markov Models | GranaData + + +Modelling a padel match with Hidden Markov Models | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 5 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Modelling a padel match with Hidden Markov Models

+ +
+ + + + + + + + + + +
+ + + +

Imagine you are at a padel match. You watch the ball go from one side to the other, hit the wall, hit the ground, the net, and after some time someone wins the point. In your head you are following the state of the match until the end to know the winner. Imagine now that you were distracted by a fly and lost concentration on the players. You don’t know what has happened when you were distracted but you managed to watch the last part of the point and so you still know who is the winner. How can we replicate this situation with a model? Which is the correct model to manage situations where you can lose track in the middle but by looking the last part you know the result? The property of only needing to know the last part to know the result is called the Markov property. So a suitable model for this task is a Hidden Markov Model.

+ +

In this series I will describe how to properly design a Hidden Markov Model (HMM from now on) to keep track of the state of a padel match. I will also provide functional python code that you could play with. And as extra material I will talk about unit testing and how to use unit tests to incrementally build a model like this one. Let’s begin.

+ +

First of all a rapid introduction on HMM and how to code them. A Hidden Markov Model has two main elements: hidden states and observations. The hidden states are what represent the model, in the padel case a hidden state can be first service, or ace or second service. The observations are what you can observe directly, continuing with the analogy an observation can be when the ball hits the ground. What the HMM does is to infer the hidden states based only on a sequence of observations. If you watch that the first player hits the ball and then the ball hits the ground twice on the other side, you can infer that the sequence of states is first service and then ace. A more abstract scheme is shown below.

+ +

simple

+ +

Every arrow represents a transition either between hidden states or between a hidden state or an observation. Each transition is nothing more than a probability. For example, the probability of going from second service to first service is zero because the first service always goes first. The transitions between hidden states and observations are called emissions. One emission could be that the probability of observing the ball hit the ground after first service is $0.5$. The transition and emission probabilities are represented as matrices. Below you can see the transitions of an example from a toy HMM.

+ +

simple

+ +

Once you have a HMM there are three things you can do with it. Given a sequence of observations you can decode the most probable sequence of hidden states with an algorithm call Viterbi’s. You can estimate the internal probabilities of the HMM using several sequences of observations with the Baum-Welch algorithm. Or you can sample a new sequence of observations. We are only interested in the first one, decoding. The transition and emission probabilities will be designed to model the rules of a padel match.

+ +

Now that we have seen the theory, let’s see how we can decode sequences in Python. As you may have guessed there are libraries that implement all the previously mentioned algorithms. My favorite one so far is hmmmkay. It is quite easy to use and is fast enough for my use cases. You can create a HMM with a few lines of code. See below.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
import pandas as pd
+import numpy as np
+from hmmkay import HMM
+
+folder_path = 'graphs/'
+transition_probas = pd.read_csv(folder_path + 'A.csv', index_col=0)
+emission_probas = pd.read_csv(folder_path + 'B.csv', index_col=0)
+hidden_states = emission_probas.shape[0]
+init_probas = np.zeros(hidden_states)
+init_probas[0] = 1
+hmm = HMM(init_probas, transition_probas, emission_probas)
+
+ +

The important method is HMM(init_probas, transition_probas, emission_probas). Given the transition and emission matrices and given also some initial probabilities for the hidden states it returns an object that can decode any sequence. The details of how to create the matrices will be described in later posts of the series. I can advance you a bit about the process. You first begin by drawing a graph in paper with the transitions you like, you then parse that graph in paper into a graph in digital format (.gml). And finally, you use the adjacency matrices of the graph as the probability matrices. But for now, let’s assume we already have those files. How can we decode a sequence? Like this.

+ +
1
+
decoded_seq = hmm.decode(sequences)
+
+ +

The only concern to bear in mind is that the input and the output are numbers starting from zero. You need to parse those values to have something significant. I normally use the column names of the matrices as dictionaries to parse the sequences.

+ +
1
+2
+3
+4
+5
+6
+
indexer_hidden = dict()
+for k, col in enumerate(transition_probas.columns):
+    indexer_hidden[col] = k
+indexer_obs = dict()
+for k, col in enumerate(emission_probas.columns):
+    indexer_obs[col] = k
+
+ +

Putting everything together, to decode a sequence the program would look something like this code.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
### ace in first service ###
+sequence = ['player-hit', *['flying'] * 10, 'bounce-ground-receiver', *['flying'] * 10, 
+     'bounce-ground-receiver', *['flying'] * 10, 'end']
+sequences = [[indexer_obs[obs] for obs in sequence]]
+decoded_seq = hmm.decode(sequences)
+decoded_seq = [hidden_states[idx] for idx in decoded_seq[0]]
+print(decoded_seq)
+# Result: ['1st-serve', 'flying-1st-serve-0', 'flying-1st-serve-1', 'flying-1st-serve-2', 'flying-1st-serve-3', 
+# 'flying-1st-serve-4', 'flying-1st-serve-5', 'flying-1st-serve-5', 'flying-1st-serve-5', 'flying-1st-serve-5', 
+# 'flying-1st-serve-5', 'in1', 'flying-in1-0', 'flying-in1-1', 'flying-in1-2', 'flying-in1-3', 'flying-in1-4', 
+# 'flying-in1-5', 'flying-in1-5', 'flying-in1-5', 'flying-in1-5', 'flying-in1-5', 'ground', 'flying-ground-0', 
+# 'flying-ground-1', 'flying-ground-2', 'flying-ground-3', 'flying-ground-4', 'flying-ground-5', 
+# 'flying-ground-5', 'flying-ground-5', 'flying-ground-5', 'flying-ground-5', 'Point-server']
+
+ +

Don’t worry if you don’t understand all this fuzzy names, they are the names that I chose for the hidden states and observations. On my next post I will explain in detail what everything means. For now I just want you to notice that the HMM is correctly identifying the winner in this point. The sequence of observations correspond to an ace in the first service. The player hits the balls and it bounces twice in the other side. Any person watching the match will automatically identify that as an ace. Here, it gives more information than that. It recognizes the instant in which the ace is achieved. The hidden state in1 means the ball has correctly entered into the other side and the state ground means that it has bounced again in the ground. After a while of having the ball flying, the model outputs the state Point-server correctly giving the victory to the server.

+ +

On my next post I will talk about the process of designing the transition and emission matrices. I will also talk about what is reasonable to be defined as observation and which hidden states are needed. And on a later post I will cover a lesson on noisy decoding. One important feature of Hidden Markov Models is that they can decode the sequence of observations even if it is wrong at some point. Like I said at the beginning you could ignore the match for some time and then you would still be able to recognize the winner. HMMs can go even further. Imagine that you are not looking the match and you are just hearing it from the radio. If the commentator makes some mistake you may hear something impossible like a player hitting twice the ball without the match finishing. HMM can decode the sequence correctly even in those situations. That is because HMMs work with probabilities. If a player hits twice the ball and the game continues, the HMM will identify that second hit as a mistake and ignore it. Of course, if the sequence is completely wrong, the result will be wrong too. But HMM are quite robust to noise in the input if you design them carefully. See you on my next post to learn how to design Hidden Markov Models.

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/NAS-parte2/index.html b/NAS-parte2/index.html new file mode 100644 index 0000000..19a00e7 --- /dev/null +++ b/NAS-parte2/index.html @@ -0,0 +1,464 @@ + + + + + + + + +Neural Architecture Search (Part 2) | GranaData + + +Neural Architecture Search (Part 2) | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 9 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Neural Architecture Search (Part 2)

+ +
+ + + + + + + + + + +
+ + + +

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the explanation on how to design more complex architectures. Before diving into how to modify the controller, let’s introduce another way of thinking about recurrent nertworks. Typically, LSTM and GRU are explained through formulas or diagrams like the one I showed in the previous article. However, in the NAS paper they introduced another way of thinking about them. They used a graph representation in which states are nodes, and the edges represent the way of merging states. For instance, and edge can mean to apply a sigmoid function, another can be summing two hidden states, and so on. Below is a simple example visualized.

+ +

simple

+ +

Here the input is $x_t$, the hidden state from the previous step $h_{t-1}$ and the cell state $c_{t-1}$ which is used as memory. As you can see the states are combined either using multiplication or addition, and then some activation functions are applied. Now, think for a moment how can you represent this same graph in a linear way, as a sequence of operations. You have it? Well, one possible way would be [Add, Tanh, Multiply, ReLU, Multiply, sigmoid, Add, ReLU, 1, 0]. Don’t worry, there are many ways to represent the above graph sequentally, this one is just the one provided by the author of the NAS. To understand it look at the next picture.

+ +

simple2

+ +

Let’s analyze it step by step. As you can see the process is split in 5. The first three are what you see, the way in which to combine $x_t$ and $h_{t-1}$ to produce $h_{t}$. However, that description is not complete because the cells state can be injected to any tree index. The last two numbers of the sequences indicate when to inject, in this example there is a 1 and a 0, which means that the cell state is injected to the tree index 0 and that the new cell state is the value in tree index 1. And the content of the cell inject part is how you inject the cell state. Let’s recap. Tree index 0 is $a_0 = \text{tanh}(W_1 \cdot x_t + W_2 \cdot h_{t-1})$, which is located at the right part of the graph above. Tree index 1 is $a_1 = \text{ReLU}(W_3 \cdot x_t \odot W_4 \cdot h_{t-1})$ located at the left. This is the simple part. Now things get complicated, the number at the end tells which tree index to inject the cell state, in this case the 0. So we have to update $a_0$ by $a_0 \leftarrow \text{ReLU}(a_0 + c_{t-1})$. Note that there are no learnable parameters in this step. Having done that we can now compute tree index 2: $a_2 = \text{sigmoid}( a_0 \odot a_{1})$. And this is the new hidden state $h_t \leftarrow a_2$. There is just one thing left, what is the new cell state? The value at tree index 1, which is the number we haven’t used yet. So the new cell state is the value at tree index 1 previous to activation so $c_t \leftarrow W_3 \cdot x_t \odot W_4 \cdot h_{t-1}$.

+ +

It is a mess at the beginning but once you understand it, is awesome. You can represent any combination by a sequence and so you can learn to generate the optimal sequence. The irony here is that we are using recurrent networks to design recurrent networks. And although the authors didn’t try it, it could be interesting to iterate that process. Use an RNN to design a better RNN, then use that new RNN to design another one and so on. My guess is that it would converge, but who knows, maybe you get an infinitely better network.

+ +

Okay, we have learned a way to represent RNN, so, how does the LSTM look like with this new representation? It looks like this

+ +

lstm

+ +

If you are interested you can go through the graph step by step to check that the formulas are the same.

+ +

Finally, the moment we were all expecting, the new and better Recurrent cell found by the authors of the NAS, the so-called NASCell (you can find it in tensorflow with that name).

+ +

nascell

+ +

In order to find it the authors required a lot of computation. This RNN is supposed to be better at language tasks than the normal LSTM. However, since this article came before big transformers were made, this recurrent cell got forgotten after that, and didn’t get much attention. Nevertheless, it is interesting to know that there are many possible RNN, not only the LSTM and the GRU. So the next time you want to try a simple RNN instead of a big transformer, you can think of using the NASCell.

+ +

If you liked this, then you are going the enjoy the last part of this series. In the next and last chapter I will be explaining another modification of the controller to include residual connections. Making the controller capable of designing architectures such as ResNet or EfficienNet. Stay tuned!

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/NAS-parte3/index.html b/NAS-parte3/index.html new file mode 100644 index 0000000..3094402 --- /dev/null +++ b/NAS-parte3/index.html @@ -0,0 +1,456 @@ + + + + + + + + +Neural Architecture Search (Part 3) | GranaData + + +Neural Architecture Search (Part 3) | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 4 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Neural Architecture Search (Part 3)

+ +
+ + + + + + + + + + +
+ + + +

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to it? The answer is obviously yes (otherwise there would be no point on this post). Let’s recap, NAS is just a method where you learn something that is non-learnable by backpropagation using trial and error, aka, reinforcement learning. The key for using it in more complex settings is to have a well defined space of parameters. In the first part that space was simply the real range for each hyperparameter. In the second part the search space included every recurrent network. Since that is a pretty broad space we narrowed it down by only working with some specific recurrent networks that could be generated by a tree-like structure. In this third part the search space is going to be the set of all convolutional neural networks. And to reduce that space to something manageable we are only going to consider networks generated by residual connections. Residual connections are typically just an addition operation like this

+ +

simple

+ +

The good property of residual connections is that they prevent vanishing gradients for very deep networks. Their discovery allowed to increase the number of layers. ResNet achieved state of the art results when it was first created, and big transformer-based architecture also use residual connections. Normally the residual connection is between one layer and the next one. Can you think of a way of generalising this? Is there a ways of creating a bigger set of possible architectures? Similar to what we did for recurrent networks, we could break that restriction of just connecting to the next layer. This way we could end up with something like the architecture presented in the original NAS paper

+ +

simple

+ +

Again, the real question is how to encode that architecture, because using a picture is not quite computer-friendly. Think of the way of representing a graph, you use the adjacency matrix, or the adjacency lists. Here it is similar, for each layer you just need to know the inputs. Or in other words, the architecture is saved as adjacency lists of incoming vertices. That’s simple enough for the controller to generate. For each layer the controller has an array of previous layers and simply selects one or more indices from there representing the incoming residual connections. The rest of hyperparameters are generated afterwards. See the diagram

+ +

simple

+ +

What the anchor point box is doing is just that, sampling indices representing the incoming layers. That box may implemented as a feed forward network with several classes. It could seem that the number of classes varies and so we cannot fix the last layer to have a constant size, and that is true, but for a given layer it is always the same. Therefore, fixing the total number of layers also fixes the number of output classes for each anchor point and so we can train them. Another reasonable way to implement the anchor box is as a multinomial variable. That way you only need to learn the probabilities, maybe conditioned to something. Although that poses another problem since another way of applying backpropagation needs to be designed, probably by using some reparameterisation trick. The original implementation was done through attention. They gave some hidden state to each anchor box and used Bahdanau attention mechanism to select the most similar layers (according to that hidden state that is learnable) to use as residual connections. This solves the problem of variable sized input, you can use the same attention mechanism for every anchor point. Thus, reducing the number of total parameters while making all the connections related to each other.

+ +

But simply selecting residual connections at random has one issue, have you seen it? There could be layers without connections, or layers that are not connected to anything. The solution is simple, for those layers that are not connected, you simply connect them to the last layer. And for those that doesn’t receive any input, the input image is used as the input, and not any other layer. This way, many complex architectures can emerge, not just linear ones. And there you have it, you can use reinforcement learning to explore the search space of convolutional neural networks.

+ +

Here I have presented you three ways of using neural architecture search to better design neural network architectures. If you are feeling short of ideas to create networks, maybe try designing a broad search space and traing a NAS controller on a toy dataset. And don’t feel that it is an outdated technique, Google is still using it, see this article. The main disadvantage of this tecnique is that it requires a lot of computational power, but as everything else, you can use it with reduced capabilities on smaller datasets to try on your own. Maybe you find the next transformer this way, who knows?

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/NAS/index.html b/NAS/index.html new file mode 100644 index 0000000..5af7dd1 --- /dev/null +++ b/NAS/index.html @@ -0,0 +1,465 @@ + + + + + + + + +Neural Architecture Search | GranaData + + +Neural Architecture Search | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 7 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Neural Architecture Search

+ +
+ + + + + + + + + + +
+ + + +

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from an AI training itself, neural architecture search consists of using a network to design other networks in a similar way a human would do it, but automatically. The process can be described as follows.

+ +

overview

+ +

The blue box is the network we want, it can be a Convolutional Neural Network to classify images, or a recurrent neural network to do sentiment analysis. On the other side, the red box is the network that is going to design the solution to our problem for us. However, the controller is not going to output the full code solving your problem, it is not so smart. Instead, it is going to generate the hyperparameters of your network, they can be the filter size, the number of channels of your convolutional layers, or the number of layers of your LSTM, I’ll talk in more details later which hyperparameters can be predicted.

+ +

But first, how are we going to train the controller? Because training the supervised model is easy, you throw some data to it and apply gradient descent. However, the controller is not a supervised model. There is no data about the hyperparameters and there is no loss function between the values it gives and the optimal values because, of course, we don’t know the optimal values. Nevertheless, we do have a reward function: the accuracy of the child network. And so we can apply the reinforcement learning paradigm. There is still one problem, the accuracy is a reward function, but it is not differentiable, and we don’t know how it relates to the hyperparameters so it seems that we cannot compute the gradient, and therefore, we can’t apply gradient descent. The currently used solution for that problem was invented by Williams in 1992, they derived the following formula for the gradient:

+
+$$ + \nabla_{\theta_c} J(\theta_c) = \sum_{t=1}^T \mathbb{E}_{P(a_{1:T};\theta_c)}[\nabla_{\theta_c}(\log(P(a_t|a_{(t-1):1};\theta_c)))\cdot R] +$$ +
+ +

That formula deserves its own post, but for now just bear in mind this is the gradient used to train the controller. The process is fairly simple, withdraw an architecture from the controller, train the architecture in a supervised manner and get a validation accuracy. Use that accuracy as reward and train the controller using the above gradient. Repeat suficiently many times and voilà, your controller has learnt to design architectures. The process is illustrated below. Several controllers are trained in parallel due to the high number of attempts the controller needs in order to achieve good performance. Remember, the controller is learning by try and error.

+ +

training

+ +

Now, the details. What is the controller, exactly? In the original paper it was an LSTM, however, any architecture valid for correlated data can be used, like a transformer. But, the NAS article was published the same year as the transformer paper, so they could only try the LSTM because there was no transformer at the time. More precisely, this is the scheme they present in their paper:

+ +

controller

+ +

As you can see the controller is predicting very simple parameters, the parameters of a CNN that we normally ignore and use by default. But don’t be fooled by its simplicity, it can get quite complex. Imagine we want to design a recurrent neural network, similar to the LSTM or the GRU. There are hidden states and hidden memory cells but, what are the connections? For the LSTM the connections are the ones shown below.

+ +

lstm

+ +

So think about all those arrows, what if I told you that with neural architecture search we can learn which arrows are the optimal? If you want to know how, wait for the next part in this series.

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/WebApp-Docker-Django/index.html b/WebApp-Docker-Django/index.html new file mode 100644 index 0000000..45a93fe --- /dev/null +++ b/WebApp-Docker-Django/index.html @@ -0,0 +1,960 @@ + + + + + + + + +Deploying AWS webapp tutorial | GranaData + + +Deploying AWS webapp tutorial | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 35 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Deploying AWS webapp tutorial

+ +
+ + + + + + + + + + +
+ + + +

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. +I will even be referencing many of those here. But the main difference with those posts is that mine is going to be straight to the point. +The following is a tutorial on how to create a web app from scratch. The backend will be on Django and the database will be Postgresql. +The app will be running on AWS and to deploy it there we will create a Docker image. Last but not least, I’ll explain how to buy a domain and link the domain to the AWS ip address. Let’s get ours hands dirty! Also, don’t worry about the code, it is all available here.

+ +

Django and Postgresql

+ +

Let’s start by creating a minimal python environment with just Django. You can do it either via python or conda. For reproducibility, please use python3.10 and Django 4.2.2. Open a terminal and run the following:

+ +
1
+2
+3
+
python3.10 -m venv .venv
+source .venv/bin/activate # For Windows use: .\.venv\Script\activate
+pip install django==4.2.2 psycopg2-binary
+
+ +

For the Conda installation:

+ +
1
+2
+3
+
conda create --name .venv python=3.10
+conda activate .venv
+pip install django==4.2.2 psycopg2-binary
+
+ +

Postgresql server setup

+ +

The next step is to create a prosgresql server. To install postgresql go to the official page. Once installed, you need to start running the server on your system:

+ +
1
+2
+3
+
mkdir /usr/local/var/postgres  # Create folder if it does not exist
+initdb -D /usr/local/var/postgres  # Initialize database cluster
+pg_ctl -D /usr/local/var/postgres start  # Start server
+
+ +

This will start the server and save everything into the /usr/local/var/postgres folder. For Window users, replace pg_ctl and initdb with the path to the pg_ctl.exe and initdb.exe binaries, which may be something similar to "C:\Program Files\PostgreSQL\14\bin\pg_ctl.exe" and use any data directory you want.

+ +

Once the server is running we need to create a database, for that, you need to run postgres in a terminal and execute the relevant SQL code:

+ +
1
+2
+3
+4
+5
+
psql postgres  # Start SQL shell
+postgres=# CREATE DATABASE mydatabase;
+postgres=# CREATE USER myuser WITH PASSWORD 'mypassword';
+postgres=# GRANT ALL PRIVILEGES ON DATABASE mydatabase TO myuser;
+postgres=# exit;
+
+ +

The commands are self-explanatory, just replace mydatabase, myuser and mypassword with what you deem appropiate. Now you have the database ready to use locally, you can connect to it using any database management system you want, I recommend Dbeaver. The connection is through localhost and port 5432. Later on we will see how to automate this process but for now, this is how it is done locally.

+ +

Django webapp setup

+ +

Given the database, we need a web on top. With the python environment activated, run the following using any name you want:

+ +
1
+
django-admin startproject myprojectname
+
+ +

This will create the basic skeleton for a Django project. We now have to configure the database and create a simple app to store data. Go to settings.py and locate the INSTALLED_APPS variable. Append 'django.db.backends.postgresql', to the list, it should be like this:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+
INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'django.db.backends.postgresql',
+]
+
+ +

Also locate the DATABASES variable and modify it to contain the information necessary to connect to the posgresql database:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql',
+        'NAME': 'mydatabase',
+        'USER': 'myuser',
+        'PASSWORD': 'mypassword',
+        'HOST': 'localhost',
+        'PORT': '5432',
+    }
+}
+
+ +

Since storing passwords in plain text is normally not a good idea, I recommend you use environment variables for that:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql',
+        'NAME': 'mydatabase',
+        'USER': 'myuser',
+        'PASSWORD': os.environ.get('DB_PASSWORD', ''),  # Don't forget to import os
+        'HOST': 'localhost',
+        'PORT': '5432',
+    }
+}
+
+ +

You can now provide the password through environment variables:

+ +
1
+2
+3
+
export DB_PASSWORD='mypassword'  # Unix
+set DB_PASSWORD "mypassword"  # CMD Windows
+$env:DB_PASSWORD="mypassword"  # PowerShell Windows
+
+ +

Finally, let’s create a simple page. Start by typing:

+ +
1
+
python manage.py startapp simpleapp
+
+ +

Now, modify settings.py to include it by adding it to the INSTALLED_APPS variable:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'django.db.backends.postgresql',
+    'simpleapp',
+]
+
+ +

After that, we will add a very simple model and view to handle data. Our model will only contain names of users. Go to simpleapp/models.py and add this:

+ +
1
+2
+3
+4
+5
+
class User(models.Model):
+    name = models.CharField(max_length=50)
+
+    def __str__(self):
+        return self.name
+
+ +

Then, create simpleapp/forms.py and add this:

+ +
1
+2
+3
+4
+5
+6
+7
+
from django import forms
+from .models import User
+
+class UserForm(forms.ModelForm):
+    class Meta:
+        model = User
+        fields = ['name']
+
+ +

Next, modify simpleapp/views.py to include the following:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+
from django.views.generic.edit import CreateView
+from .models import User
+from .forms import UserForm
+
+class UserCreateView(CreateView):
+    model = User
+    form_class = UserForm
+    template_name = 'user_form.html'
+    success_url = '/create_user
+
+ +

You also need to create the template under a templates folder, create simpleapp/templates/user_form.html and insert this:

+ +
1
+2
+3
+4
+5
+
<form method="post">
+    {% csrf_token %}
+    {{ form.as_p }}
+    <button type="submit">Create</button>
+</form>
+
+ +

Last, we need to link the urls for everything to work properly. Create the file simpleapp/urls.py and write:

+ +
1
+2
+3
+4
+5
+6
+
from django.urls import path
+from .views import UserCreateView
+
+urlpatterns = [
+    path('create_user/', UserCreateView.as_view(), name='create_user'),
+]
+
+ +

Go to the main myprojectname/urls.py and edit it to be like this:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+
from django.contrib import admin
+from django.urls import path
+from django.urls import include
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+    path('simpleapp/', include('simpleapp.urls')),
+]
+
+ +

Now you are good to go. We can finally start adding rows to the database. For doing so, apply migrations and start the webapp:

+ +
1
+2
+3
+
python manage.py makemigrations
+python manage.py migrate
+python manage.py runserver
+
+ +

Open a browser, go to http://127.0.0.1:8000/simpleapp/create_user/ and you will be able to input users’ names. If it is your first time using Django, this is a whole lot, I know. This is a simple example using Django’s class-based views. Things can get very, very complex. The aim of this tutorial is to set up a minimal working webapp. For more information on Django, you can go to their official documentation. Okay, close everything and let’s start our Docker journey. To stop the webapp run ctrl+C and to stop the posgresql server run:

+ +
1
+
pg_ctl -D /usr/local/var/postgres stop
+
+ +

Docker

+ +

Setting everything from scratch is time consuming but if you only need to do it once, it is affordable. The problem comes when you want to migrate to other machines or you want to scale. Having to go through all the process above everytime is annoying. As I mentioned before, it would be nice to automate it. That’s when Docker comes into play. It is a way to pack everything up so that it can run on your machine, the cloud or a microwave, if it has Docker installed, of course. A Docker is basically made of a few configuration files that are used to construct an image that does whatever you want, in our case handle data through a web app. Having introduced the concept, let’s build a Docker for our web app.

+ +

This section is mostly inspired by this other tutorial. Hand over there if you feel curious. Also, I recommend you giving a look at the official Docker beginner tutorial for more information on how to set up Docker and the basics of it.

+ +

To start, create a Dockerfile inside of the Django project directory. Specify the following information on it:

+ +
1
+2
+3
+4
+5
+6
+
FROM python:3.10.2-slim-bullseye
+
+WORKDIR /code
+
+COPY . .
+RUN pip install -r requirements.txt
+
+ +

You also need to create a requirements.txt with this:

+ +
django==4.2.2
+psycopg2-binary
+
+ +

You could simply install it with pip, but when the project grows you will be thankfull to have it all in a requirements.txt file. So, that’s the container of the webapp. Simple, right? However, we still need to connect it to a posgres database. For that we need to use docker compose to run another container with the database and connect them. For that, create a docker-compose.yml file with the following:

+ +
version: "3"
+
+services:
+  web:
+    build: .
+    command: sh start.sh
+    environment:
+      - DB_PASSWORD
+    volumes:
+      - .:/code
+    ports:
+      - 8000:8000
+    depends_on:
+      db:
+        condition: service_healthy
+  db:
+    image: postgres:14
+    restart: always
+    ports:
+      - 5432:5432
+    environment:
+      POSTGRES_DB: mydatabase
+      POSTGRES_USER: myuser
+      POSTGRES_PASSWORD: ${DB_PASSWORD}
+    volumes:
+      - ./postgres_data:/var/lib/postgresql/data/
+    healthcheck:
+      test: pg_isready -U myuser -d mydatabase
+      interval: 1s
+      timeout: 10s
+      retries: 10
+      start_period: 30s
+
+ +

You will need to substitute myuser and mydatabase with what you like most. To explain a bit what is happening here, we are running a postgres container, then performing healthy checks to be sure the database is running and after that we launch the webapp container. You could provide the password also there, but for security reasons is better to provide it through an environment variable, just like before. The database is stored in the folder postgres_data locally, so that whenever you kill the container you don’t lose the data. The port 5432 is forwarded locally so you can connect to the database from your machine when the container is running and see the data.

+ +

Wait! We have not finished yet. We need to create the start.sh and modify the myprojectname/settings.py file. The DATABASES variable should look like this:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.postgresql',
+        'NAME': 'mydatabase',
+        'USER': 'myuser',
+        'PASSWORD': os.environ.get('DB_PASSWORD', ''),
+        'HOST': 'db',
+        'PORT': '5432',
+    }
+}
+
+ +

The only change is on HOST. It is now set up to db which is the name of the posgres container. And the start.sh script is the following:

+ +
1
+2
+3
+
python manage.py makemigrations
+python manage.py migrate
+python manage.py runserver 0.0.0.0:8000
+
+ +

It is creating all the migrations needed to set up all the models from Django into postgres. Finally, just run the container:

+ +
1
+
docker compose up
+
+ +

To stop it, do ctrl+C and then docker compose rm. That’s it, that’s all you need to do to restart your webapp from anywhere. You can now take your code to any machine and you won’t need to set up posgres, python and django from scratch. Just install docker and run docker compose up. Also, it is a good practice to include a .dockerignore, just like .gitignore. For this simple app I have

+ +
postgres_data/
+Dockerfile
+docker-compose.yml
+*/__pycache__/
+*/*/__pycache__/
+*/*/*/__pycache__/
+
+ +

This way I don’t load any unnecessary files to the container, making it faster. So now that we have everything packed up in our bag, let’s travel. Let’s deploy the web to AWS for others to use it.

+ +

AWS

+ +

Amazon Web Services are a way to deploy your code into servers that you don’t need to manage. This way instead of the cost of setting up a whole server, you just pay for the hours used. Nevertheless, you won’t save yourself the cost of configuring everything. Even though configuring AWS may be simpler than configuring a server, it is still an important investment of time. For that, I will provide here the bare minimum to make our webapp work on AWS. You will still need to read the AWS docs extensively, there are many tutorials online, but Amazon keeps changing the interface every so often. The only web that is for sure updated is the AWS official docs page.

+ +

Account and IAM roles

+ +

Before we can start configuring the server, we need to configure an account. For that you will need access keys. You can create access keys for your root account but it is not recommended. AWS recommends that you create role with less permissions than your root account (specially without billing permissions) and to use those access keys. In the past this was made using the Identity and Access Management (IAM) app. Now, it is being migrated to IAM Identity Center. Both methods still work as of this writing but I will explain the second one which is more updated. The following is a reduced version of this tutorial. Go to the AWS console. There look for the IAM Identity Center. Once on the IAM Identity Center you will need to create an user, create a permission set and link both. In the section User click to Add User and fill the neccesary information. Then, on permission sets, click on Create permission set and create the predefined role AdministratorAccess. After that, go to AWS accounts, select the account under root and click Assign users or groups. Select your created user, click next, select the role, click next, review it and click submit. Finally, to activate that user, you must open the mail you provided and register that user with some password. Before you continue, go to Dashboard and save your AWS access portal URL, that is the URL you need to use to log in with that user. Now, click that URL and sign in. Once you are logged in you should see your user and two links at the right: one for Management console and one for Command line or programatic access. Click the latter and you will see your access keys.

+ +

The next step is to install and configure the AWS CLI. Go here and follow the steps for the installation. Once installed execute aws configure and provide the access key and secret access key obtained previously. It will also ask for a region, I will be using us-east-1. If you choose a different one you may encounter problems later on because the free tiers differ across regions. And for the output format choose json. You are now (almost) ready to start launching instances.

+ +

EC2 Instances

+ +

Having created our account it is time to create an instance where to deploy our webapp. If your page gets too large you may be interested in storing the database in S3 buckets, but for now I will store code and data in the same instance. You can find the docs for EC2 here. As before, I will summarize it to just use what we need. First, create the instance. For that go to the EC2 console. Under Instances section, click Launch instances. There give it a name, select the OS and arch (I recommend Ubuntu and x86-64), select the instance type (I will be using t2.micro cuase it is free), select a key-pair or create since you probably don’t have one and leave everything as default. Once you launched it you can now access your machine through ssh. In the instances section, you can click on your created instance and then click on Connect and it will give you instructions on how to connect. The next steps are to install Docker, copy your webapp to the instance, change the firewall of the instance to allow http and postgres traffic and finally deploy the app.

+ +

Installing Docker (again)

+ +

If you have chosen Ubuntu as your OS you can follow the instructions here. You just basically need to execute the following commands after accessing the machine:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
sudo apt-get update -y
+sudo apt-get install ca-certificates curl gnupg -y
+sudo install -m 0755 -d /etc/apt/keyrings
+curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
+sudo chmod a+r /etc/apt/keyrings/docker.gpg
+echo \
+  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
+  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
+  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
+sudo apt-get update -y
+sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
+
+ +

And if you want to check that everything is working just do

+ +
1
+
sudo docker run hello-world
+
+ +

Upload your code to EC2

+ +

You can copy your entire directory recursively into you EC2 instance with scp:

+ +
1
+
scp -r -i YOUR_KEY ./* ubuntu@YOUR_EC2_ADDRESS:.
+
+ +

YOUR_KEY is the key pair previously created and YOUR_EC2_ADDRESS may look something like ec2-30-29-46-221.compute-1.amazonaws.com. It is the same address that you use to ssh into your machine.

+ +

Changing the firewall

+ +

In AWS, the instances have some security rules that control the inbound and outbound traffic of your app. By default all the ports are closed except for 22 which is the ssh port. We will need to open the port 80 and 5432 since they are the http and postgres ports. If you have an SSL certificate you could open the port 443 for https, but we will just use those two for now. Let’s go back to the EC2 console and go to Security Groups. Click on Create security group. Give it a name and add two inbound rules. You can just select HTTP and Postgresql in the dropdown menu and it will set the port for you automatically. Then, on source, select Anywhere IPv4 and click Create security group. Now go back to your created instance and click Actions > Security > Change security groups. There simply add the newly created security group and you are free to go.

+ +

Deploy the app

+ +

In order to deploy our app we need to make one change to our docker-compose.yml. Initially we were redirecting port 8000 into 8000, we are now going to redirect it to 80 which the http port. The line to change will end up like this

+ +
ports:
+  - 80:8000
+
+ +

Finally, ssh into your machine with docker installed and execute

+ +
1
+
sudo DB_PASSWORD=... docker compose up -d
+
+ +

Remember that you have to specify the password of the database as a environment variable. Okay, the app is running but, how can we access it? Well, we cannot. We still need to make some changes. Stop the container and let’s finish this:

+ +
1
+
sudo DB_PASSWORD=... docker compose down
+
+ +

The first thing to know is what is the IP that we can use to access this page. In the AWS console, when you enter your instance it displays somewhere “Public IPv4 address”. That is the IP of your app. However, if you were to enter there, Django will not let you in. That is because you need to allow that host. For that, go the setting.py of your app and add it:

+ +
1
+
ALLOWED_HOSTS = ['YOUR_IP']
+
+ +

Also, even after changing this, when you access your ip you don’t see the page. That is because the base url is not pointing anywhere, but we can fix that. Create a view that only has the redirection:

+ +
1
+2
+3
+4
+
from django.shortcuts import redirect
+
+def redirect_to_create_user(request):
+    return redirect('/simpleapp/create_user')
+
+ +

Then, in your main urls.py add path('', redirect_to_create_user), it will end up like this:

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
from django.contrib import admin
+from django.urls import path
+from django.urls import include
+from .views import redirect_to_create_user
+
+urlpatterns = [
+    path('', redirect_to_create_user),
+    path('admin/', admin.site.urls),
+    path('simpleapp/', include('simpleapp.urls')),
+]
+
+ +

Now, copy again all the files into your machine and deploy the webapp:

+ +
1
+
sudo DB_PASSWORD=... docker compose up -d
+
+ +

Accessing the DataBase

+ +

Let’s see how we can access the server database locally. Open you favourite DB program (mine is DBeaver) and create a new connection. This time you will have to provide an URL instead of localhost. Everything else is the same as when you did it locally. The port is 5432, the user is what you gave it, and the database name is what you name it. If you have configured properly the EC2 security group you could now access your database locally.

+ +

Web Domain

+ +

Nice, we have our fantastic webapp up and running, but wait, are you going to share to your friends the page 50.283.48.100? Obviously not, you need a fancy domain like myawesomepage.com or something that describes your project. To achieve that you need to first buy a domain and then link that domain to your IP. Domains that are not on high demand typically cost around 10$ to 20$. You can buy them on Namecheap. Once you have it you need to do several things on the AWS side. You will need to fix the IP so that it doesn’t change, otherwise the DNS redirection will get broken over time. After that you need to create nameservers and then route your domain to that IP. Let’s go step by step. To fix the IP go to the section Elastic IP in the left bar of the EC2 menu. Create such Elastic IP and then, in actions, associate it to your instance. Once you have done that, you will need to create the hosted zone. For that, search in AWS the Route 53 service. Once there, click on Create hosted zone. Insert your domain and create it. Before we continue, two more records need to be created. Create one with Type A and your previous Elastic IP under the value section, everything else as default. Repeat now but add ‘www’ in subdomain so that your page can be accessed either by its domain or adding www at the beginning. Once you have done that, go to your domain on Namecheap and click on manage. Select custom DNS and enter the four nameservers that were created previously. If you didn’t understand something, you can check the tutorials I followed both for the AWS and Namecheap part. DNS redirection may take up to 48 hours. There is one last thing to modify, remember that you need to include the IP in ALLOWED_HOSTS? Well, you also need to include your domain there. Change that and you will have your marvelous webpage running.

+ +

Conclusion

+ +

Congratulations! You have managed to reach to the end of this tutorial. If you followed the steps carefully you now know how to create your own web apps. The first time you do it is quite tiresome, but once you know how to do it you can get your millionaire idea up and running at the moment. Let’s recap. First, you need to create your Django app. Then, you create a Docker to launch it easily. After that, you create an AWS instance and deploy your app there. Finally, you link your domain to the instance IP. Once you are done you can enjoy your creation!

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/about.html b/about.html new file mode 100644 index 0000000..5676428 --- /dev/null +++ b/about.html @@ -0,0 +1,318 @@ + + + + + + + + +About | GranaData + + +About | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+
+
+

About

+
+ +
+
+ + +
+ +

This blog was bornt from my need to explain everything that I have learnt during my degrees and to show to the world the results of my research in a more divulgative way.

+ +

The name from the blog was inspired from the love I have for the place I belong: Granada, and the love I have for Data Science. As you may have noticed, the footer image is of La Alhambra, as it couldn't be otherwise.

+ +

About me I can say that I always have many things in mind, but seldom have time to put them in practice. Below is a photo of me surrounded by olives, you cannot see it but behind the camera there was a rally. I hope you enjoy my blog.

+ + +

jekyll template mediumish

+ + +
+ +
+ + +
+
Buy me a coffee
+ +

Thank you for your support! Your donation helps me to maintain and improve this blog.

+ +Buy me a coffee + +
+
+
+ +
+ + +
+
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/assets/audio/audiobook/chapter2_audio4_2.mp3 b/assets/audio/audiobook/chapter2_audio4_2.mp3 new file mode 100644 index 0000000..1f79d69 Binary files /dev/null and b/assets/audio/audiobook/chapter2_audio4_2.mp3 differ diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..530c720 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,444 @@ +/* We need to add display:inline in order to align the '>>' of the 'read more' link */ +@import url(themes/tango.css); +.post-excerpt p { + display: inline; +} + +.highlight { + background: #fff; + border: 0; + padding: 0; + margin-bottom: 1.7rem; + /* Comment */ + /* Error */ + /* Keyword */ + /* Operator */ + /* Comment.Multiline */ + /* Comment.Preproc */ + /* Comment.Single */ + /* Comment.Special */ + /* Generic.Deleted */ + /* Generic.Deleted.Specific */ + /* Generic.Emph */ + /* Generic.Error */ + /* Generic.Heading */ + /* Generic.Inserted */ + /* Generic.Inserted.Specific */ + /* Generic.Output */ + /* Generic.Prompt */ + /* Generic.Strong */ + /* Generic.Subheading */ + /* Generic.Traceback */ + /* Keyword.Constant */ + /* Keyword.Declaration */ + /* Keyword.Pseudo */ + /* Keyword.Reserved */ + /* Keyword.Type */ + /* Literal.Number */ + /* Literal.String */ + /* Name.Attribute */ + /* Name.Builtin */ + /* Name.Class */ + /* Name.Constant */ + /* Name.Entity */ + /* Name.Exception */ + /* Name.Function */ + /* Name.Namespace */ + /* Name.Tag */ + /* Name.Variable */ + /* Operator.Word */ + /* Text.Whitespace */ + /* Literal.Number.Float */ + /* Literal.Number.Hex */ + /* Literal.Number.Integer */ + /* Literal.Number.Oct */ + /* Literal.String.Backtick */ + /* Literal.String.Char */ + /* Literal.String.Doc */ + /* Literal.String.Double */ + /* Literal.String.Escape */ + /* Literal.String.Heredoc */ + /* Literal.String.Interpol */ + /* Literal.String.Other */ + /* Literal.String.Regex */ + /* Literal.String.Single */ + /* Literal.String.Symbol */ + /* Name.Builtin.Pseudo */ + /* Name.Variable.Class */ + /* Name.Variable.Global */ + /* Name.Variable.Instance */ + /* Literal.Number.Integer.Long */ +} +.highlight .c { + color: #999988; + font-style: italic; +} +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} +.highlight .k { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .cm { + color: #999988; + font-style: italic; +} +.highlight .cp { + color: #999999; + font-weight: bold; +} +.highlight .c1 { + color: #999988; + font-style: italic; +} +.highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; +} +.highlight .gd { + color: #000000; + background-color: #ffdddd; +} +.highlight .gd .x { + color: #000000; + background-color: #ffaaaa; +} +.highlight .ge { + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .gi { + color: #000000; + background-color: #ddffdd; +} +.highlight .gi .x { + color: #000000; + background-color: #aaffaa; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .gt { + color: #aa0000; +} +.highlight .kc { + font-weight: bold; +} +.highlight .kd { + font-weight: bold; +} +.highlight .kp { + font-weight: bold; +} +.highlight .kr { + font-weight: bold; +} +.highlight .kt { + color: #445588; + font-weight: bold; +} +.highlight .m { + color: #009999; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #445588; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #990000; + font-weight: bold; +} +.highlight .nf { + color: #990000; + font-weight: bold; +} +.highlight .nn { + color: #555555; +} +.highlight .nt { + color: #000080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + font-weight: bold; +} +.highlight .w { + color: #bbbbbb; +} +.highlight .mf { + color: #009999; +} +.highlight .mh { + color: #009999; +} +.highlight .mi { + color: #009999; +} +.highlight .mo { + color: #009999; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .bp { + color: #999999; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .il { + color: #009999; +} + +td.rouge-code { + width: 100%; +} + +pre.lineno { + color: rgba(153, 153, 153, 0.6); +} + +.rating-holder { + font-size: 16px; + display: inline-block; + background-color: #fff; + border-radius: 1.5625em; + box-sizing: border-box; +} + +.c-rating button { + display: inline-block; + float: left; + width: 1.25em; + height: 1.25em; + border: 0; + text-indent: -9999px; + outline: none; + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="0"] button:nth-child(-n+0) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="0.25"] button:nth-child(-n+1) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="0.25"] button:nth-child(1) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="0.5"] button:nth-child(-n+1) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="0.5"] button:nth-child(1) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="0.75"] button:nth-child(-n+1) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="0.75"] button:nth-child(1) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="1"] button:nth-child(-n+1) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="1.25"] button:nth-child(-n+2) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="1.25"] button:nth-child(2) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="1.5"] button:nth-child(-n+2) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="1.5"] button:nth-child(2) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="1.75"] button:nth-child(-n+2) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="1.75"] button:nth-child(2) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="2"] button:nth-child(-n+2) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="2.25"] button:nth-child(-n+3) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="2.25"] button:nth-child(3) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="2.5"] button:nth-child(-n+3) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="2.5"] button:nth-child(3) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="2.75"] button:nth-child(-n+3) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="2.75"] button:nth-child(3) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="3"] button:nth-child(-n+3) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="3.25"] button:nth-child(-n+4) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="3.25"] button:nth-child(4) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="3.5"] button:nth-child(-n+4) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="3.5"] button:nth-child(4) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="3.75"] button:nth-child(-n+4) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="3.75"] button:nth-child(4) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="4"] button:nth-child(-n+4) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="4.25"] button:nth-child(-n+5) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="4.25"] button:nth-child(5) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="4.5"] button:nth-child(-n+5) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="4.5"] button:nth-child(5) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="4.75"] button:nth-child(-n+5) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="4.75"] button:nth-child(5) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat, url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating[data-rating-value="5"] button:nth-child(-n+5) { + background: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E") center/cover no-repeat; +} + +.c-rating--small { + font-size: 50%; +} + +.c-rating--big { + font-size: 150%; +} + +/*# sourceMappingURL=main.css.map */ \ No newline at end of file diff --git a/assets/css/main.css.map b/assets/css/main.css.map new file mode 100644 index 0000000..29b0d12 --- /dev/null +++ b/assets/css/main.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["main.scss","../../_sass/_syntax.scss","../../_sass/_starsnonscss.scss"],"names":[],"mappings":"AAAA;AAYQ;AAXR;EACC;;;ACDD;EACC;EACG;EACA;EACA;AACuC;AACS;AAC1B;AACA;AACkB;AACD;AACC;AACmB;AACZ;AACG;AAC1B;AACJ;AACA;AAC2B;AACG;AAC9B;AACA;AACG;AACH;AACA;AACG;AACA;AACA;AACA;AACgB;AACpB;AACH;AACI;AACA;AACmB;AACnB;AACA;AACmB;AACA;AACnB;AACA;AACA;AACG;AACJ;AACC;AACA;AACA;AACA;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AACH;AACG;AACA;AACA;AACA;AACA;AACA;;AA1DvB;EAAK;EAAgB;;AACrB;EAAO;EAAgB;;AACvB;EAAK;;AACL;EAAK;;AACL;EAAM;EAAgB;;AACtB;EAAM;EAAgB;;AACtB;EAAM;EAAgB;;AACtB;EAAM;EAAgB;EAAmB;;AACzC;EAAM;EAAgB;;AACtB;EAAS;EAAgB;;AACzB;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;EAAgB;;AACtB;EAAS;EAAgB;;AACzB;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;EAAgB;;AACtB;EAAK;;AACL;EAAK;;AACL;EAAM;;AACN;EAAM;;AACN;EAAM;EAAgB;;AACtB;EAAM;;AACN;EAAM;;AACN;EAAM;EAAgB;;AACtB;EAAM;EAAgB;;AACtB;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAK;;AACL;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;AACN;EAAM;;;AAGP;EAAgB;;;AAChB;EAAa;;;ACpEb;EACG;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE;;;AACF;EACE","sourcesContent":["/* We need to add display:inline in order to align the '>>' of the 'read more' link */\n.post-excerpt p {\n\tdisplay:inline;\n}\n\n// Import partials from `sass_dir` (defaults to `_sass`)\n@import\n\t\"syntax\",\n \"starsnonscss\"\n;\n\n\n@import url(themes/tango.css);\n\n","\n.highlight {\n\tbackground: #fff;\n border: 0;\n padding: 0;\n margin-bottom:1.7rem;\n\t.c { color: #999988; font-style: italic } /* Comment */\n\t.err { color: #a61717; background-color: #e3d2d2 } /* Error */\n\t.k { font-weight: bold } /* Keyword */\n\t.o { font-weight: bold } /* Operator */\n\t.cm { color: #999988; font-style: italic } /* Comment.Multiline */\n\t.cp { color: #999999; font-weight: bold } /* Comment.Preproc */\n\t.c1 { color: #999988; font-style: italic } /* Comment.Single */\n\t.cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */\n\t.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */\n\t.gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */\n\t.ge { font-style: italic } /* Generic.Emph */\n\t.gr { color: #aa0000 } /* Generic.Error */\n\t.gh { color: #999999 } /* Generic.Heading */\n\t.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */\n\t.gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */\n\t.go { color: #888888 } /* Generic.Output */\n\t.gp { color: #555555 } /* Generic.Prompt */\n\t.gs { font-weight: bold } /* Generic.Strong */\n\t.gu { color: #aaaaaa } /* Generic.Subheading */\n\t.gt { color: #aa0000 } /* Generic.Traceback */\n\t.kc { font-weight: bold } /* Keyword.Constant */\n\t.kd { font-weight: bold } /* Keyword.Declaration */\n\t.kp { font-weight: bold } /* Keyword.Pseudo */\n\t.kr { font-weight: bold } /* Keyword.Reserved */\n\t.kt { color: #445588; font-weight: bold } /* Keyword.Type */\n\t.m { color: #009999 } /* Literal.Number */\n\t.s { color: #d14 } /* Literal.String */\n\t.na { color: #008080 } /* Name.Attribute */\n\t.nb { color: #0086B3 } /* Name.Builtin */\n\t.nc { color: #445588; font-weight: bold } /* Name.Class */\n\t.no { color: #008080 } /* Name.Constant */\n\t.ni { color: #800080 } /* Name.Entity */\n\t.ne { color: #990000; font-weight: bold } /* Name.Exception */\n\t.nf { color: #990000; font-weight: bold } /* Name.Function */\n\t.nn { color: #555555 } /* Name.Namespace */\n\t.nt { color: #000080 } /* Name.Tag */\n\t.nv { color: #008080 } /* Name.Variable */\n\t.ow { font-weight: bold } /* Operator.Word */\n\t.w { color: #bbbbbb } /* Text.Whitespace */\n\t.mf { color: #009999 } /* Literal.Number.Float */\n\t.mh { color: #009999 } /* Literal.Number.Hex */\n\t.mi { color: #009999 } /* Literal.Number.Integer */\n\t.mo { color: #009999 } /* Literal.Number.Oct */\n\t.sb { color: #d14 } /* Literal.String.Backtick */\n\t.sc { color: #d14 } /* Literal.String.Char */\n\t.sd { color: #d14 } /* Literal.String.Doc */\n\t.s2 { color: #d14 } /* Literal.String.Double */\n\t.se { color: #d14 } /* Literal.String.Escape */\n\t.sh { color: #d14 } /* Literal.String.Heredoc */\n\t.si { color: #d14 } /* Literal.String.Interpol */\n\t.sx { color: #d14 } /* Literal.String.Other */\n\t.sr { color: #009926 } /* Literal.String.Regex */\n\t.s1 { color: #d14 } /* Literal.String.Single */\n\t.ss { color: #990073 } /* Literal.String.Symbol */\n\t.bp { color: #999999 } /* Name.Builtin.Pseudo */\n\t.vc { color: #008080 } /* Name.Variable.Class */\n\t.vg { color: #008080 } /* Name.Variable.Global */\n\t.vi { color: #008080 } /* Name.Variable.Instance */\n\t.il { color: #009999 } /* Literal.Number.Integer.Long */\n}\n\ntd.rouge-code { width: 100%;}\npre.lineno { color: #9999;}",".rating-holder {\n font-size: 16px;\n display: inline-block;\n background-color: #fff;\n border-radius: 1.5625em;\n box-sizing: border-box; }\n \n .c-rating button {\n display: inline-block;\n float: left;\n width: 1.25em;\n height: 1.25em;\n border: 0;\n text-indent: -9999px;\n outline: none;\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"0\"] button:nth-child(-n+0) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"0.25\"] button:nth-child(-n+1) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"0.25\"] button:nth-child(1) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"0.5\"] button:nth-child(-n+1) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"0.5\"] button:nth-child(1) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"0.75\"] button:nth-child(-n+1) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"0.75\"] button:nth-child(1) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"1\"] button:nth-child(-n+1) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"1.25\"] button:nth-child(-n+2) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"1.25\"] button:nth-child(2) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"1.5\"] button:nth-child(-n+2) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"1.5\"] button:nth-child(2) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"1.75\"] button:nth-child(-n+2) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"1.75\"] button:nth-child(2) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"2\"] button:nth-child(-n+2) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"2.25\"] button:nth-child(-n+3) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"2.25\"] button:nth-child(3) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"2.5\"] button:nth-child(-n+3) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"2.5\"] button:nth-child(3) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"2.75\"] button:nth-child(-n+3) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"2.75\"] button:nth-child(3) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"3\"] button:nth-child(-n+3) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"3.25\"] button:nth-child(-n+4) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"3.25\"] button:nth-child(4) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"3.5\"] button:nth-child(-n+4) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"3.5\"] button:nth-child(4) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"3.75\"] button:nth-child(-n+4) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"3.75\"] button:nth-child(4) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"4\"] button:nth-child(-n+4) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"4.25\"] button:nth-child(-n+5) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"4.25\"] button:nth-child(5) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M196.208 415.2v-224.8l-139.504 20.272 100.944 98.384-23.84 138.928z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"4.5\"] button:nth-child(-n+5) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"4.5\"] button:nth-child(5) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M258.672 64l-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6v-318.4z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"4.75\"] button:nth-child(-n+5) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"4.75\"] button:nth-child(5) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M321.616 190.496l-0.656-0.096-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 63.024 33.136z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat, url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22%23ddd%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating[data-rating-value=\"5\"] button:nth-child(-n+5) {\n background: url(\"data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20width%3D%22512%22%20height%3D%22512%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20fill%3D%22gold%22%20d%3D%22M457.888 210.672l-139.504-20.272-62.384-126.4-62.384 126.4-139.504 20.272 100.944 98.384-23.84 138.928 124.768-65.6 124.768 65.6-23.84-138.928c0 0 100.944-98.384 100.944-98.384z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E\") center/cover no-repeat; }\n .c-rating--small {\n font-size: 50%; }\n .c-rating--big {\n font-size: 150%; }"],"file":"main.css"} \ No newline at end of file diff --git a/assets/css/screen.css b/assets/css/screen.css new file mode 100644 index 0000000..bb03fc6 --- /dev/null +++ b/assets/css/screen.css @@ -0,0 +1,1016 @@ +/* +Template Name: Mediumish +Copyright: Sal, WowThemes.net, https://www.wowthemes.net +License: https://www.wowthemes.net/freebies-license/ +*/ +@media screen and (min-width:1500px) { + html { font-size:18px; } /* Increase the font size on higher resolutions */ + .container {max-width:80%;} +} + +div.article-post, figure, .language-python { + max-width: 95vw; + overflow-x: scroll; +} + +.inlined { display:inline; } + +.container { + max-width: 85%; +} + +.center-inputs { + display:flex; + justify-content:center; +} + +.inside-container { + max-width: 100%; +} + +.mainheading { + padding: 1rem 0rem; +} + +a { + color: #00ab6b; + transition: all 0.2s; +} + +a:hover { + color: #038252; + text-decoration: none; +} + +pre { + -moz-box-sizing: border-box; + box-sizing: border-box; + border: #E3EDF3 1px solid; + width: 100%; + padding: 7px; + font-family: monospace, sans-serif; + font-size: .9rem; + white-space: pre; + overflow: auto; + background: #fff; + border-radius: 0px; + line-height: 1.6; + color: #333; + margin-bottom: -rem; +} + +.mediumnavigation { + background: rgba(255, 255, 255, .97); + box-shadow: 0 2px 2px -2px rgba(0, 0, 0, .15); + transition: top 0.2s ease-in-out; +} + +.main-content { + min-height: 300px; +} + +.site-content { + min-height: 60vh; + padding-top: 1.5rem; + margin-top: 57px; + transition: all 0.4s; +} + +section { + margin-bottom: 20px; +} + +section.recent-posts { + margin-bottom: 0; +} + +.section-title h2 { + border-bottom: 1px solid rgba(0, 0, 0, .125); + margin-bottom: 25px; + font-weight: 700; + font-size: 1.4rem; + margin-bottom: 27px; +} + +.section-title span { + border-bottom: 1px solid rgba(0, 0, 0, .44); + display: inline-block; + padding-bottom: 20px; + margin-bottom: -1px; +} + +.article-post ol, +.article-post ul { + margin-bottom: 1.5rem; +} + +.article-post ol ol, +.article-post ul ul { + list-style: disc; + margin-bottom: 0rem; +} + +@media (min-width:576px) { + .card-columns.listfeaturedtag { + -webkit-column-count: 2; + -moz-column-count: 2; + column-count: 2; + } +} + +@media (min-width:992px) { + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 0.8rem; + padding-left: 0.8rem; + } +} + +.listfeaturedtag { + border: 1px solid rgba(0, 0, 0, .125); + border-radius: .25rem; + transition: all 0.3s cubic-bezier(.25, .8, .25, 1); +} + +.listfeaturedtag .wrapthumbnail { + height: 290px; + flex: 0 0 auto; + height: 100%; +} + +.maxthumb { + max-height: 300px; + overflow: hidden; +} + +.listfeaturedtag .card, +.card-footer { + border: 0; +} + +.listfeaturedtag .thumbnail { + background-size: cover; + height: 100%; + display: block; + background-position: 38% 22% !important; + background-origin: border-box !important; + border-top-left-radius: .25rem; + border-bottom-left-radius: .25rem; +} + +.listfeaturedtag .card-block { + padding-left: 0; +} + +.listfeaturedtag h2.card-title, +.listrecent h2.card-title { + font-size: 1.3rem; + font-weight: 700; + line-height: 1.25; +} + +.listfeaturedtag h4.card-text, +.listrecent h4.card-text { + color: rgba(0, 0, 0, .44); + font-size: 0.95rem; + line-height: 1.6; + font-weight: 400; +} + +.featured-box-img-cover { + object-fit: cover; + width: 100%; + height: 100%; + max-height: 100%; +} + +@media (max-width:991px) { + .featured-box-img-cover { + height: auto; + width: 100%; + } +} + +.wrapfooter { + font-size: .8rem; + display: flex; + align-items: center; + margin-bottom: 15px; +} + +.author-thumb { + width: 40px; + height: 40px; + margin-right: 13px; + border-radius: 100%; +} + +.post-top-meta { + margin-bottom: 2rem; +} + +.post-top-meta .author-thumb { + width: 72px; + height: 72px; +} + +.post-top-meta.authorpage .author-thumb { + margin-top: 40px; +} + +.post-top-meta span { + font-size: 0.9rem; + color: rgba(0, 0, 0, .44); + display: inline-block; +} + +.post-top-meta .author-description { + margin-bottom: 5px; + margin-top: 5px; + font-size: 0.95rem; +} + +.toc ul { + list-style: decimal; + font-weight: 400; +} + +.author-meta { + flex: 1 1 auto; + white-space: nowrap !important; + text-overflow: ellipsis !important; + overflow: hidden !important; +} + +span.post-name, +span.post-date, +span.author-meta { + display: inline-block; +} + +span.post-date, +span.post-read { + color: rgba(0, 0, 0, .44); +} + +span.post-date a{ + color: rgba(0, 0, 0, .44); +} + +span.post-read-more { + align-items: center; + display: inline-block; + float: right; + margin-top: 8px; +} + +span.post-read-more a { + color: rgba(0, 0, 0, .44); +} + +span.post-name a, +span.post-read-more a:hover { + color: rgba(0, 0, 0, .8); +} + +.dot:after { + content: "·"; + margin-left: 3px; + margin-right: 3px; +} + +.mediumnavigation .form-control { + font-size: 0.8rem; + border-radius: 30px; + overflow: hidden; + border: 1px solid rgba(0, 0, 0, 0.09); + min-width: 180px; +} + +.mediumnavigation .form-inline { + margin-left: 15px; +} + +.mediumnavigation .form-inline .btn { + margin-left: -50px; + border: 0; + border-radius: 30px; + cursor: pointer; +} + +.mediumnavigation .form-inline .btn:hover, +.mediumnavigation .form-inline .btn:active { + background: transparent; + color: green; +} + +.mediumnavigation .navbar-brand { + font-weight: 500; +} + +.mediumnavigation .dropdown-menu { + border: 1px solid rgba(0, 0, 0, 0.08); + margin: .5rem 0 0; +} + +.mediumnavigation .nav-item, +.dropdown-menu { + font-size: 0.9rem; +} + +.mediumnavigation .search-icon { + margin-left: -40px; + display: inline-block; + margin-top: 3px; + cursor: pointer; +} + +.mediumnavigation .navbar-brand img { + max-height: 30px; + margin-right: 5px; +} + +.mainheading h1.sitetitle { + font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: 700; +} + +.mainheading h1.posttitle { + font-weight: 700; + margin-bottom: 1rem; +} + +.footer { + border-top: 1px solid rgba(0, 0, 0, .05) !important; + padding-top: 15px; + padding-bottom: 12px; + font-size: 0.8rem; + color: rgba(0, 0, 0, .44); + margin-top: 50px; + margin-bottom: 62px; + position: relative; + background: #fff; +} + +.link-dark { + color: rgba(0, 0, 0, .8); +} + +.article-post { + font-family: Merriweather; + font-size: 1.1rem; + line-height: 1.84; + color: rgba(0, 0, 0, .8); +} + +blockquote { + border-left: 4px solid #00ab6b; + padding: 0 20px; + font-style: italic; + color: rgba(0, 0, 0, .5); +} + +.article-post p, +.article-post blockquote { + margin: 0 0 1.5rem 0; +} + +.featured-image { + display: block; + margin-bottom: 1.5rem; +} + +.share { + text-align: center; +} + +.share p { + margin-bottom: 10px; + font-size: 0.95rem; +} + +.share ul li { + display: inline-block; + margin-bottom: 9px; +} + +.share ul { + padding-left: 0; + margin-left: 0; +} + +.share ul li i.fa { + border: 1px solid #ddd; + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + border-radius: 50%; +} + +.svgIcon { + vertical-align: middle; +} + +.sticky-top-offset { + top: 100px; +} + +@media (min-width:1024px) { + .share ul li { + display: block; + } +} + +@media (max-width:999px) { + .featured-box-img-cover { + height: 359px; + } + + .alertbar { + position: relative !Important; + margin-bottom: 20px; + margin-top: 20px; + box-shadow: none !Important; + padding-right: 14px !Important; + } + + #comments { + margin-right: 15px; + } + + .jumbotron.fortags { + margin-bottom: 0 !Important; + } + + .alertbar form { + margin-top: 20px; + } + + .alertbar span, + .alertbar form { + display: block; + } + + .alertbar input[type="submit"] { + border-radius: 3px !Important; + } + + .alertbar input[type="email"] { + margin-right: 0px !Important; + display: block; + border-right: 1px solid #ddd !Important; + margin-bottom: 10px; + } + + .jumbotron { + margin-bottom: 0; + border-radius: 0; + } + + .listfeaturedtag .card { + height: auto; + } + + .listfeaturedtag .wrapfooter { + position: relative; + margin-top: 30px; + } + + .listfeaturedtag .card-block { + padding: 20px; + } + + .footer { + margin-top: 0px; + margin-bottom: 0px; + } +} + +@media (max-width:1024px) { + .post-top-meta .col-md-10 { + text-align: center; + } +} + +@media (max-width:767px) { + .post-top-meta.authorpage { + text-align: center; + } +} + +.share, +.share a { + color: rgba(0, 0, 0, .44); + fill: rgba(0, 0, 0, .44); +} + +.graybg { + background-color: #fafafa; + padding: 40px 0 46px; + position: relative; +} + +.listrelated .card { + box-shadow: 0 1px 7px rgba(0, 0, 0, .05); + border: 0; +} + +ul.tags { + list-style: none; + padding-left: 0; + margin: 0 0 3rem 0; +} + +ul.tags li { + display: inline-block; + font-size: 0.9rem; +} + +ul.tags li a { + background: rgba(0, 0, 0, .05); + color: rgba(0, 0, 0, .6); + border-radius: 3px; + padding: 5px 10px; +} + +ul.tags li a:hover { + background: rgba(0, 0, 0, .07); + text-decoration: none; +} + +.margtop3rem { + margin-top: 3rem; +} + +.sep { + height: 1px; + width: 20px; + background: #999; + margin: 0px auto; + margin-bottom: 1.2rem; +} + +.btn.follow { + border-color: #02B875; + color: #1C9963; + padding: 3px 10px; + text-align: center; + border-radius: 999em; + font-size: 0.85rem; + display: inline-block; +} + +.btn.readtime { + border-color: #000; + color: #1C9963; + padding: 3px 10px; + text-align: right; + border-radius: 999em; + border-width: 2px; + font-size: 0.85rem; + display: inline-block; +} + +.btn.subscribe { + background-color: #1C9963; + border-color: #1C9963; + color: rgba(255, 255, 255, 1); + fill: rgba(255, 255, 255, 1); + border-radius: 30px; + font-size: 0.85rem; + margin-left: 10px; + font-weight: 600; + text-transform: uppercase; +} + +.post-top-meta .btn.follow { + margin-left: 5px; + margin-top: -4px; +} + +.alertbar { + box-shadow: 0 -3px 10px 0 rgba(0, 0, 0, .0785); + position: fixed; + bottom: 0; + left: 0; + background-color: #fff; + width: 100%; + padding: 14px 0; + z-index: 1; + display: none; +} + +.alertbar form { + display: inline-block; +} + +.alertbar input[type="email"] { + font-size: 0.85rem; + padding: 3px 5px 3px 10px; + border-radius: 3px; + border: 1px solid #ddd; + margin-right: 0px; + height: 34px; + letter-spacing: 0.5px; + margin-left: 5px; +} + +.alertbar input[type="submit"] { + background-color: #1C9963; + border: 1px solid #1C9963; + color: rgba(255, 255, 255, 1); + fill: rgba(255, 255, 255, 1); + font-size: 0.85rem; + padding: 4px 10px; + font-weight: 600; + height: 34px; + letter-spacing: 0.5px; + cursor: pointer; + margin-left:15px; + border-radius:20px; + line-height:1.2; +} + +.form-control::-webkit-input-placeholder { + color: rgba(0, 0, 0, .5); +} + +.form-control:-moz-placeholder { + color: rgba(0, 0, 0, .5); +} + +.form-control::-moz-placeholder { + color: rgba(0, 0, 0, .5); +} + +.form-control:-ms-input-placeholder { + color: rgba(0, 0, 0, .5); +} + +.form-control::-ms-input-placeholder { + color: rgba(0, 0, 0, .5); +} + +.authorpage h1 { + font-weight: 700; + font-size: 30px; +} + +.post-top-meta.authorpage .author-thumb { + float: none; +} + +.authorpage .author-description { + font-size: 1rem; + color: rgba(0, 0, 0, .6); +} + +.post-top-meta.authorpage .btn.follow { + padding: 7px 20px; + margin-top: 10px; + margin-left: 0; + font-size: 0.9rem; +} + +.graybg.authorpage { + border-top: 1px solid #f0f0f0; +} + +.authorpostbox { + width: 760px; + margin: 0px auto; + margin-bottom: 1.5rem; + max-width: 100%; +} + +.authorpostbox .img-thumb { + width: 100%; +} + +.sociallinks { + margin: 1rem 0; +} + +.sociallinks a { + background: #666; + color: #fff; + width: 22px; + height: 22px; + display: inline-block; + text-align: center; + line-height: 22px; + border-radius: 50%; + font-size: 12px; +} + +#comments { + margin-top: 3rem; + margin-bottom: 1.5rem; +} + +.h1, +.h2, +.h3, +.h4, +.h5, +.h6, +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; +} + +.article-post .h1, +.article-post .h2, +.article-post .h3, +.article-post .h4, +.article-post .h5, +.article-post .h6, +.article-post h1, +.article-post h2, +.article-post h3, +.article-post h4, +.article-post h5, +.article-post h6 { + font-weight: 700; + margin-bottom: 1.5rem; +} + +.article-post img.shadow { + -webkit-box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.30); + -moz-box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.30); + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.30); +} + +.layout-page .article-post { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; +} + +.layout-page .article-post p { + margin-bottom: 1rem; +} + +img { + max-width: 100%; +} + +.bottompagination span.navigation { + display: block; + font-size: 0.93rem; + padding: 15px 0 0 0; + text-align: center; + margin-bottom: 0rem; + color: #999; + border-top: 1px solid #ddd; +} + +.pointerup { + margin-bottom: -17px; + margin-left: 49%; + font-size: 30px; +} + +.pointerup i.fa { + color: #eaeaea; +} + +.bottompagination span.navigation i { + display: inline-block; +} + +span.navigation { + display: inline-block; + font-size: 0.93rem; + font-weight: 700; + text-align: center; +} + +.pagination { + display: block; +} + +iframe { + max-width: 100%; +} + +.transpdark { + background: rgba(0, 0, 0, 0.75); + color: #fff; +} + +@media (min-width:768px) { + .jumbotron.fortags { + margin-bottom: -50px; + margin-top: 3rem; + padding: 0; + height: 350px; + border-radius: 0; + background-image: url(../images/jumbotron.jpg); + background-size: cover; + } + + .jumbotron.fortags .col-md-4 { + background: rgba(0, 0, 0, 0.75); + color: #fff; + } + + .jumbotron.fortags .row { + margin: 0; + } +} + +.jumbotron.fortags { + margin-top: 1rem; + padding: 0; + border-radius: 0; + background-image: url(../images/jumbotron.jpg); + background-size: cover; +} + +.jumbotron.fortags a { + padding: 5px 10px 7px; + background: #222; + border-radius: 30px; + color: #fff; + font-weight: 500; + text-transform: lowercase; + font-size: 0.8rem; + display: inline-block; +} + +.layout-page .jumbotron.fortags { + display: none; +} + +.mb-30px { + margin-bottom: 30px; +} + +.flex-first { + -webkit-box-ordinal-group: 0; + -webkit-order: -1; + -ms-flex-order: -1; + order: -1; +} + +@media (min-width: 768px) { + .flex-md-unordered { + -webkit-box-ordinal-group: 1; + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; + } + + .flex-first { + -webkit-box-ordinal-group: 0; + -webkit-order: 1; + -ms-flex-order: 1; + order: 1; + } +} + +@media (max-width: 768px) { + .share { + margin-top: 30px; + } +} + +.card .img-fluid { + width: 100%; +} + +.sticky-top-80 { + top: 80px; +} + +.spoiler { + color: transparent; + text-shadow: 0 0 10px rgba(0, 0, 0, 0.4); + transition: all .4s; + cursor: pointer; + position: relative; +} + +.spoiler:after { + position: absolute; + opacity: 0; + content: 'Click to reveal spoiler'; + top: 45%; + left: calc(50% - 100px); + text-shadow: none; + background: #222; + color: #fff; + display: inline-block; + font-size: 13px; + line-height: 1; + padding: 2px 3px; + width: 150px; + font-family: Arial; + text-align: center; + border-radius: 3px; + transition: all .4s; +} + +.spoiler:hover:after { + opacity: 1; +} + +/** Lazy img **/ +.lazyimg { + display: block; + border: 0 none; + opacity: 1; + transition: opacity .25s; + background: #f2f2f2; + outline: 0 none; +} + +.lazyimg[data-src], +.lazyimg[data-srcset] { + opacity: 0; + transition: opacity .25s; +} + +/* Math environments */ +.theorem { + display: block; + margin: 12px 0; + font-style: italic; +} + +.theorem:before { + content: "Theorem."; + font-weight: bold; + font-style: normal; +} + +.lemma { + display: block; + margin: 12px 0; + font-style: italic; +} + +.lemma:before { + content: "Lemma."; + font-weight: bold; + font-style: normal; +} + +.prop { + display: block; + margin: 12px 0; + font-style: italic; +} + +.prop:before { + content: "Proposition."; + font-weight: bold; + font-style: normal; +} + +.proof { + display: block; + margin: 12px 0; + font-style: normal; +} + +.proof:before { + content: "Proof."; + font-style: italic; +} + +.proof:after { + content: "\25FC"; + float:right; +} + +.definition { + display: block; + margin: 12px 0; + font-style: normal; +} + +.definition:before { + content: "Definition."; + font-weight: bold; + font-style: normal; +} + + +.image-grid { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 10px; /* Adjust this value if you want more or less spacing between images */ + justify-content: center; + align-items: center; +} + +.image-container img { + max-width: 100%; /* This ensures the image doesn't overflow its container */ + display: block; /* Removes any unwanted space below the image */ + margin: 0 auto; /* Centers the image if it's narrower than its container */ +} diff --git a/assets/css/themes/autumn.css b/assets/css/themes/autumn.css new file mode 100644 index 0000000..855ae2c --- /dev/null +++ b/assets/css/themes/autumn.css @@ -0,0 +1,66 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #aaaaaa; font-style: italic } /* Comment */ +.highlight .err { color: #FF0000; background-color: #FFAAAA } /* Error */ +.highlight .k { color: #0000aa } /* Keyword */ +.highlight .ch { color: #aaaaaa; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #aaaaaa; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #4c8317 } /* Comment.Preproc */ +.highlight .cpf { color: #aaaaaa; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #aaaaaa; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #0000aa; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #aa0000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00aa00 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { color: #0000aa } /* Keyword.Constant */ +.highlight .kd { color: #0000aa } /* Keyword.Declaration */ +.highlight .kn { color: #0000aa } /* Keyword.Namespace */ +.highlight .kp { color: #0000aa } /* Keyword.Pseudo */ +.highlight .kr { color: #0000aa } /* Keyword.Reserved */ +.highlight .kt { color: #00aaaa } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #aa5500 } /* Literal.String */ +.highlight .na { color: #1e90ff } /* Name.Attribute */ +.highlight .nb { color: #00aaaa } /* Name.Builtin */ +.highlight .nc { color: #00aa00; text-decoration: underline } /* Name.Class */ +.highlight .no { color: #aa0000 } /* Name.Constant */ +.highlight .nd { color: #888888 } /* Name.Decorator */ +.highlight .ni { color: #880000; font-weight: bold } /* Name.Entity */ +.highlight .nf { color: #00aa00 } /* Name.Function */ +.highlight .nn { color: #00aaaa; text-decoration: underline } /* Name.Namespace */ +.highlight .nt { color: #1e90ff; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #aa0000 } /* Name.Variable */ +.highlight .ow { color: #0000aa } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #009999 } /* Literal.Number.Bin */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sa { color: #aa5500 } /* Literal.String.Affix */ +.highlight .sb { color: #aa5500 } /* Literal.String.Backtick */ +.highlight .sc { color: #aa5500 } /* Literal.String.Char */ +.highlight .dl { color: #aa5500 } /* Literal.String.Delimiter */ +.highlight .sd { color: #aa5500 } /* Literal.String.Doc */ +.highlight .s2 { color: #aa5500 } /* Literal.String.Double */ +.highlight .se { color: #aa5500 } /* Literal.String.Escape */ +.highlight .sh { color: #aa5500 } /* Literal.String.Heredoc */ +.highlight .si { color: #aa5500 } /* Literal.String.Interpol */ +.highlight .sx { color: #aa5500 } /* Literal.String.Other */ +.highlight .sr { color: #009999 } /* Literal.String.Regex */ +.highlight .s1 { color: #aa5500 } /* Literal.String.Single */ +.highlight .ss { color: #0000aa } /* Literal.String.Symbol */ +.highlight .bp { color: #00aaaa } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #00aa00 } /* Name.Function.Magic */ +.highlight .vc { color: #aa0000 } /* Name.Variable.Class */ +.highlight .vg { color: #aa0000 } /* Name.Variable.Global */ +.highlight .vi { color: #aa0000 } /* Name.Variable.Instance */ +.highlight .vm { color: #aa0000 } /* Name.Variable.Magic */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/borland.css b/assets/css/themes/borland.css new file mode 100644 index 0000000..8f60b24 --- /dev/null +++ b/assets/css/themes/borland.css @@ -0,0 +1,52 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #008800; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { color: #000080; font-weight: bold } /* Keyword */ +.highlight .ch { color: #008800; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #008080 } /* Comment.Preproc */ +.highlight .cpf { color: #008800; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { color: #000080; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #000080; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #000080; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #000080; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #000080; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #000080; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #0000FF } /* Literal.Number */ +.highlight .s { color: #0000FF } /* Literal.String */ +.highlight .na { color: #FF0000 } /* Name.Attribute */ +.highlight .nt { color: #000080; font-weight: bold } /* Name.Tag */ +.highlight .ow { font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #0000FF } /* Literal.Number.Bin */ +.highlight .mf { color: #0000FF } /* Literal.Number.Float */ +.highlight .mh { color: #0000FF } /* Literal.Number.Hex */ +.highlight .mi { color: #0000FF } /* Literal.Number.Integer */ +.highlight .mo { color: #0000FF } /* Literal.Number.Oct */ +.highlight .sa { color: #0000FF } /* Literal.String.Affix */ +.highlight .sb { color: #0000FF } /* Literal.String.Backtick */ +.highlight .sc { color: #800080 } /* Literal.String.Char */ +.highlight .dl { color: #0000FF } /* Literal.String.Delimiter */ +.highlight .sd { color: #0000FF } /* Literal.String.Doc */ +.highlight .s2 { color: #0000FF } /* Literal.String.Double */ +.highlight .se { color: #0000FF } /* Literal.String.Escape */ +.highlight .sh { color: #0000FF } /* Literal.String.Heredoc */ +.highlight .si { color: #0000FF } /* Literal.String.Interpol */ +.highlight .sx { color: #0000FF } /* Literal.String.Other */ +.highlight .sr { color: #0000FF } /* Literal.String.Regex */ +.highlight .s1 { color: #0000FF } /* Literal.String.Single */ +.highlight .ss { color: #0000FF } /* Literal.String.Symbol */ +.highlight .il { color: #0000FF } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/bw.css b/assets/css/themes/bw.css new file mode 100644 index 0000000..2c9c9fe --- /dev/null +++ b/assets/css/themes/bw.css @@ -0,0 +1,39 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { font-weight: bold } /* Keyword */ +.highlight .ch { font-style: italic } /* Comment.Hashbang */ +.highlight .cm { font-style: italic } /* Comment.Multiline */ +.highlight .cpf { font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { font-style: italic } /* Comment.Single */ +.highlight .cs { font-style: italic } /* Comment.Special */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gh { font-weight: bold } /* Generic.Heading */ +.highlight .gp { font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { font-weight: bold } /* Generic.Subheading */ +.highlight .kc { font-weight: bold } /* Keyword.Constant */ +.highlight .kd { font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { font-weight: bold } /* Keyword.Namespace */ +.highlight .kr { font-weight: bold } /* Keyword.Reserved */ +.highlight .s { font-style: italic } /* Literal.String */ +.highlight .nc { font-weight: bold } /* Name.Class */ +.highlight .ni { font-weight: bold } /* Name.Entity */ +.highlight .ne { font-weight: bold } /* Name.Exception */ +.highlight .nn { font-weight: bold } /* Name.Namespace */ +.highlight .nt { font-weight: bold } /* Name.Tag */ +.highlight .ow { font-weight: bold } /* Operator.Word */ +.highlight .sa { font-style: italic } /* Literal.String.Affix */ +.highlight .sb { font-style: italic } /* Literal.String.Backtick */ +.highlight .sc { font-style: italic } /* Literal.String.Char */ +.highlight .dl { font-style: italic } /* Literal.String.Delimiter */ +.highlight .sd { font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { font-style: italic } /* Literal.String.Double */ +.highlight .se { font-weight: bold; font-style: italic } /* Literal.String.Escape */ +.highlight .sh { font-style: italic } /* Literal.String.Heredoc */ +.highlight .si { font-weight: bold; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { font-style: italic } /* Literal.String.Other */ +.highlight .sr { font-style: italic } /* Literal.String.Regex */ +.highlight .s1 { font-style: italic } /* Literal.String.Single */ +.highlight .ss { font-style: italic } /* Literal.String.Symbol */ diff --git a/assets/css/themes/colorful.css b/assets/css/themes/colorful.css new file mode 100644 index 0000000..480df4a --- /dev/null +++ b/assets/css/themes/colorful.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #888888 } /* Comment */ +.highlight .err { color: #FF0000; background-color: #FFAAAA } /* Error */ +.highlight .k { color: #008800; font-weight: bold } /* Keyword */ +.highlight .o { color: #333333 } /* Operator */ +.highlight .ch { color: #888888 } /* Comment.Hashbang */ +.highlight .cm { color: #888888 } /* Comment.Multiline */ +.highlight .cp { color: #557799 } /* Comment.Preproc */ +.highlight .cpf { color: #888888 } /* Comment.PreprocFile */ +.highlight .c1 { color: #888888 } /* Comment.Single */ +.highlight .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #003388; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #333399; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #6600EE; font-weight: bold } /* Literal.Number */ +.highlight .s { background-color: #fff0f0 } /* Literal.String */ +.highlight .na { color: #0000CC } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #BB0066; font-weight: bold } /* Name.Class */ +.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #880000; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #FF0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0066BB; font-weight: bold } /* Name.Function */ +.highlight .nl { color: #997700; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #007700 } /* Name.Tag */ +.highlight .nv { color: #996633 } /* Name.Variable */ +.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #6600EE; font-weight: bold } /* Literal.Number.Bin */ +.highlight .mf { color: #6600EE; font-weight: bold } /* Literal.Number.Float */ +.highlight .mh { color: #005588; font-weight: bold } /* Literal.Number.Hex */ +.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ +.highlight .mo { color: #4400EE; font-weight: bold } /* Literal.Number.Oct */ +.highlight .sa { background-color: #fff0f0 } /* Literal.String.Affix */ +.highlight .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ +.highlight .sc { color: #0044DD } /* Literal.String.Char */ +.highlight .dl { background-color: #fff0f0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #DD4422 } /* Literal.String.Doc */ +.highlight .s2 { background-color: #fff0f0 } /* Literal.String.Double */ +.highlight .se { color: #666666; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ +.highlight .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ +.highlight .si { background-color: #eeeeee } /* Literal.String.Interpol */ +.highlight .sx { color: #DD2200; background-color: #fff0f0 } /* Literal.String.Other */ +.highlight .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ +.highlight .s1 { background-color: #fff0f0 } /* Literal.String.Single */ +.highlight .ss { color: #AA6600 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0066BB; font-weight: bold } /* Name.Function.Magic */ +.highlight .vc { color: #336699 } /* Name.Variable.Class */ +.highlight .vg { color: #dd7700; font-weight: bold } /* Name.Variable.Global */ +.highlight .vi { color: #3333BB } /* Name.Variable.Instance */ +.highlight .vm { color: #996633 } /* Name.Variable.Magic */ +.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/default.css b/assets/css/themes/default.css new file mode 100644 index 0000000..572069c --- /dev/null +++ b/assets/css/themes/default.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #408080; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #BC7A00 } /* Comment.Preproc */ +.highlight .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #7D9029 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #A0A000 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/emacs.css b/assets/css/themes/emacs.css new file mode 100644 index 0000000..192d4db --- /dev/null +++ b/assets/css/themes/emacs.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #008800; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #AA22FF; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #008800; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #008800; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #008800 } /* Comment.Preproc */ +.highlight .cpf { color: #008800; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #008800; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #008800; font-weight: bold } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #AA22FF; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #AA22FF } /* Keyword.Pseudo */ +.highlight .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #00BB00; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BB4444 } /* Literal.String */ +.highlight .na { color: #BB4444 } /* Name.Attribute */ +.highlight .nb { color: #AA22FF } /* Name.Builtin */ +.highlight .nc { color: #0000FF } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #00A000 } /* Name.Function */ +.highlight .nl { color: #A0A000 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #B8860B } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BB4444 } /* Literal.String.Affix */ +.highlight .sb { color: #BB4444 } /* Literal.String.Backtick */ +.highlight .sc { color: #BB4444 } /* Literal.String.Char */ +.highlight .dl { color: #BB4444 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BB4444 } /* Literal.String.Double */ +.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BB4444 } /* Literal.String.Heredoc */ +.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ +.highlight .s1 { color: #BB4444 } /* Literal.String.Single */ +.highlight .ss { color: #B8860B } /* Literal.String.Symbol */ +.highlight .bp { color: #AA22FF } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #00A000 } /* Name.Function.Magic */ +.highlight .vc { color: #B8860B } /* Name.Variable.Class */ +.highlight .vg { color: #B8860B } /* Name.Variable.Global */ +.highlight .vi { color: #B8860B } /* Name.Variable.Instance */ +.highlight .vm { color: #B8860B } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/friendly.css b/assets/css/themes/friendly.css new file mode 100644 index 0000000..f7907dd --- /dev/null +++ b/assets/css/themes/friendly.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f0f0f0; } +.highlight .c { color: #60a0b0; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #40a070 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #40a070 } /* Literal.Number.Bin */ +.highlight .mf { color: #40a070 } /* Literal.Number.Float */ +.highlight .mh { color: #40a070 } /* Literal.Number.Hex */ +.highlight .mi { color: #40a070 } /* Literal.Number.Integer */ +.highlight .mo { color: #40a070 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/fruity.css b/assets/css/themes/fruity.css new file mode 100644 index 0000000..4354ed0 --- /dev/null +++ b/assets/css/themes/fruity.css @@ -0,0 +1,78 @@ +.highlight .hll { background-color: #333333 } +.highlight { background: #111111; color: #ffffff } +.highlight .c { color: #008800; font-style: italic; background-color: #0f140f } /* Comment */ +.highlight .err { color: #ffffff } /* Error */ +.highlight .esc { color: #ffffff } /* Escape */ +.highlight .g { color: #ffffff } /* Generic */ +.highlight .k { color: #fb660a; font-weight: bold } /* Keyword */ +.highlight .l { color: #ffffff } /* Literal */ +.highlight .n { color: #ffffff } /* Name */ +.highlight .o { color: #ffffff } /* Operator */ +.highlight .x { color: #ffffff } /* Other */ +.highlight .p { color: #ffffff } /* Punctuation */ +.highlight .ch { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Hashbang */ +.highlight .cm { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Multiline */ +.highlight .cp { color: #ff0007; font-weight: bold; font-style: italic; background-color: #0f140f } /* Comment.Preproc */ +.highlight .cpf { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.PreprocFile */ +.highlight .c1 { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Single */ +.highlight .cs { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Special */ +.highlight .gd { color: #ffffff } /* Generic.Deleted */ +.highlight .ge { color: #ffffff } /* Generic.Emph */ +.highlight .gr { color: #ffffff } /* Generic.Error */ +.highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #ffffff } /* Generic.Inserted */ +.highlight .go { color: #444444; background-color: #222222 } /* Generic.Output */ +.highlight .gp { color: #ffffff } /* Generic.Prompt */ +.highlight .gs { color: #ffffff } /* Generic.Strong */ +.highlight .gu { color: #ffffff; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #ffffff } /* Generic.Traceback */ +.highlight .kc { color: #fb660a; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #fb660a; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #fb660a; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #fb660a } /* Keyword.Pseudo */ +.highlight .kr { color: #fb660a; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #cdcaa9; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #ffffff } /* Literal.Date */ +.highlight .m { color: #0086f7; font-weight: bold } /* Literal.Number */ +.highlight .s { color: #0086d2 } /* Literal.String */ +.highlight .na { color: #ff0086; font-weight: bold } /* Name.Attribute */ +.highlight .nb { color: #ffffff } /* Name.Builtin */ +.highlight .nc { color: #ffffff } /* Name.Class */ +.highlight .no { color: #0086d2 } /* Name.Constant */ +.highlight .nd { color: #ffffff } /* Name.Decorator */ +.highlight .ni { color: #ffffff } /* Name.Entity */ +.highlight .ne { color: #ffffff } /* Name.Exception */ +.highlight .nf { color: #ff0086; font-weight: bold } /* Name.Function */ +.highlight .nl { color: #ffffff } /* Name.Label */ +.highlight .nn { color: #ffffff } /* Name.Namespace */ +.highlight .nx { color: #ffffff } /* Name.Other */ +.highlight .py { color: #ffffff } /* Name.Property */ +.highlight .nt { color: #fb660a; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #fb660a } /* Name.Variable */ +.highlight .ow { color: #ffffff } /* Operator.Word */ +.highlight .w { color: #888888 } /* Text.Whitespace */ +.highlight .mb { color: #0086f7; font-weight: bold } /* Literal.Number.Bin */ +.highlight .mf { color: #0086f7; font-weight: bold } /* Literal.Number.Float */ +.highlight .mh { color: #0086f7; font-weight: bold } /* Literal.Number.Hex */ +.highlight .mi { color: #0086f7; font-weight: bold } /* Literal.Number.Integer */ +.highlight .mo { color: #0086f7; font-weight: bold } /* Literal.Number.Oct */ +.highlight .sa { color: #0086d2 } /* Literal.String.Affix */ +.highlight .sb { color: #0086d2 } /* Literal.String.Backtick */ +.highlight .sc { color: #0086d2 } /* Literal.String.Char */ +.highlight .dl { color: #0086d2 } /* Literal.String.Delimiter */ +.highlight .sd { color: #0086d2 } /* Literal.String.Doc */ +.highlight .s2 { color: #0086d2 } /* Literal.String.Double */ +.highlight .se { color: #0086d2 } /* Literal.String.Escape */ +.highlight .sh { color: #0086d2 } /* Literal.String.Heredoc */ +.highlight .si { color: #0086d2 } /* Literal.String.Interpol */ +.highlight .sx { color: #0086d2 } /* Literal.String.Other */ +.highlight .sr { color: #0086d2 } /* Literal.String.Regex */ +.highlight .s1 { color: #0086d2 } /* Literal.String.Single */ +.highlight .ss { color: #0086d2 } /* Literal.String.Symbol */ +.highlight .bp { color: #ffffff } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #ff0086; font-weight: bold } /* Name.Function.Magic */ +.highlight .vc { color: #fb660a } /* Name.Variable.Class */ +.highlight .vg { color: #fb660a } /* Name.Variable.Global */ +.highlight .vi { color: #fb660a } /* Name.Variable.Instance */ +.highlight .vm { color: #fb660a } /* Name.Variable.Magic */ +.highlight .il { color: #0086f7; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/manni.css b/assets/css/themes/manni.css new file mode 100644 index 0000000..0fea96c --- /dev/null +++ b/assets/css/themes/manni.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f0f3f3; } +.highlight .c { color: #0099FF; font-style: italic } /* Comment */ +.highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */ +.highlight .k { color: #006699; font-weight: bold } /* Keyword */ +.highlight .o { color: #555555 } /* Operator */ +.highlight .ch { color: #0099FF; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #009999 } /* Comment.Preproc */ +.highlight .cpf { color: #0099FF; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */ +.highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ +.highlight .go { color: #AAAAAA } /* Generic.Output */ +.highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #99CC66 } /* Generic.Traceback */ +.highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #006699 } /* Keyword.Pseudo */ +.highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #FF6600 } /* Literal.Number */ +.highlight .s { color: #CC3300 } /* Literal.String */ +.highlight .na { color: #330099 } /* Name.Attribute */ +.highlight .nb { color: #336666 } /* Name.Builtin */ +.highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */ +.highlight .no { color: #336600 } /* Name.Constant */ +.highlight .nd { color: #9999FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #CC00FF } /* Name.Function */ +.highlight .nl { color: #9999FF } /* Name.Label */ +.highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #003333 } /* Name.Variable */ +.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #FF6600 } /* Literal.Number.Bin */ +.highlight .mf { color: #FF6600 } /* Literal.Number.Float */ +.highlight .mh { color: #FF6600 } /* Literal.Number.Hex */ +.highlight .mi { color: #FF6600 } /* Literal.Number.Integer */ +.highlight .mo { color: #FF6600 } /* Literal.Number.Oct */ +.highlight .sa { color: #CC3300 } /* Literal.String.Affix */ +.highlight .sb { color: #CC3300 } /* Literal.String.Backtick */ +.highlight .sc { color: #CC3300 } /* Literal.String.Char */ +.highlight .dl { color: #CC3300 } /* Literal.String.Delimiter */ +.highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #CC3300 } /* Literal.String.Double */ +.highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */ +.highlight .si { color: #AA0000 } /* Literal.String.Interpol */ +.highlight .sx { color: #CC3300 } /* Literal.String.Other */ +.highlight .sr { color: #33AAAA } /* Literal.String.Regex */ +.highlight .s1 { color: #CC3300 } /* Literal.String.Single */ +.highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */ +.highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #CC00FF } /* Name.Function.Magic */ +.highlight .vc { color: #003333 } /* Name.Variable.Class */ +.highlight .vg { color: #003333 } /* Name.Variable.Global */ +.highlight .vi { color: #003333 } /* Name.Variable.Instance */ +.highlight .vm { color: #003333 } /* Name.Variable.Magic */ +.highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/monokai.css b/assets/css/themes/monokai.css new file mode 100644 index 0000000..d8777a8 --- /dev/null +++ b/assets/css/themes/monokai.css @@ -0,0 +1,70 @@ +.highlight .hll { background-color: #49483e } +.highlight { background: #272822; color: #f8f8f2 } +.highlight .c { color: #75715e } /* Comment */ +.highlight .err { color: #960050; background-color: #1e0010 } /* Error */ +.highlight .k { color: #66d9ef } /* Keyword */ +.highlight .l { color: #ae81ff } /* Literal */ +.highlight .n { color: #f8f8f2 } /* Name */ +.highlight .o { color: #f92672 } /* Operator */ +.highlight .p { color: #f8f8f2 } /* Punctuation */ +.highlight .ch { color: #75715e } /* Comment.Hashbang */ +.highlight .cm { color: #75715e } /* Comment.Multiline */ +.highlight .cp { color: #75715e } /* Comment.Preproc */ +.highlight .cpf { color: #75715e } /* Comment.PreprocFile */ +.highlight .c1 { color: #75715e } /* Comment.Single */ +.highlight .cs { color: #75715e } /* Comment.Special */ +.highlight .gd { color: #f92672 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gi { color: #a6e22e } /* Generic.Inserted */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #75715e } /* Generic.Subheading */ +.highlight .kc { color: #66d9ef } /* Keyword.Constant */ +.highlight .kd { color: #66d9ef } /* Keyword.Declaration */ +.highlight .kn { color: #f92672 } /* Keyword.Namespace */ +.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ +.highlight .kr { color: #66d9ef } /* Keyword.Reserved */ +.highlight .kt { color: #66d9ef } /* Keyword.Type */ +.highlight .ld { color: #e6db74 } /* Literal.Date */ +.highlight .m { color: #ae81ff } /* Literal.Number */ +.highlight .s { color: #e6db74 } /* Literal.String */ +.highlight .na { color: #a6e22e } /* Name.Attribute */ +.highlight .nb { color: #f8f8f2 } /* Name.Builtin */ +.highlight .nc { color: #a6e22e } /* Name.Class */ +.highlight .no { color: #66d9ef } /* Name.Constant */ +.highlight .nd { color: #a6e22e } /* Name.Decorator */ +.highlight .ni { color: #f8f8f2 } /* Name.Entity */ +.highlight .ne { color: #a6e22e } /* Name.Exception */ +.highlight .nf { color: #a6e22e } /* Name.Function */ +.highlight .nl { color: #f8f8f2 } /* Name.Label */ +.highlight .nn { color: #f8f8f2 } /* Name.Namespace */ +.highlight .nx { color: #a6e22e } /* Name.Other */ +.highlight .py { color: #f8f8f2 } /* Name.Property */ +.highlight .nt { color: #f92672 } /* Name.Tag */ +.highlight .nv { color: #f8f8f2 } /* Name.Variable */ +.highlight .ow { color: #f92672 } /* Operator.Word */ +.highlight .w { color: #f8f8f2 } /* Text.Whitespace */ +.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ +.highlight .mf { color: #ae81ff } /* Literal.Number.Float */ +.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ +.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ +.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ +.highlight .sa { color: #e6db74 } /* Literal.String.Affix */ +.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ +.highlight .sc { color: #e6db74 } /* Literal.String.Char */ +.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ +.highlight .sd { color: #e6db74 } /* Literal.String.Doc */ +.highlight .s2 { color: #e6db74 } /* Literal.String.Double */ +.highlight .se { color: #ae81ff } /* Literal.String.Escape */ +.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ +.highlight .si { color: #e6db74 } /* Literal.String.Interpol */ +.highlight .sx { color: #e6db74 } /* Literal.String.Other */ +.highlight .sr { color: #e6db74 } /* Literal.String.Regex */ +.highlight .s1 { color: #e6db74 } /* Literal.String.Single */ +.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ +.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #a6e22e } /* Name.Function.Magic */ +.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ +.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ +.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ +.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ +.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/murphy.css b/assets/css/themes/murphy.css new file mode 100644 index 0000000..9cc971c --- /dev/null +++ b/assets/css/themes/murphy.css @@ -0,0 +1,69 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #666666; font-style: italic } /* Comment */ +.highlight .err { color: #FF0000; background-color: #FFAAAA } /* Error */ +.highlight .k { color: #228899; font-weight: bold } /* Keyword */ +.highlight .o { color: #333333 } /* Operator */ +.highlight .ch { color: #666666; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #666666; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #557799 } /* Comment.Preproc */ +.highlight .cpf { color: #666666; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #666666; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #cc0000; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #228899; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #228899; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #228899; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #0088ff; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #228899; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #6666ff; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #6600EE; font-weight: bold } /* Literal.Number */ +.highlight .s { background-color: #e0e0ff } /* Literal.String */ +.highlight .na { color: #000077 } /* Name.Attribute */ +.highlight .nb { color: #007722 } /* Name.Builtin */ +.highlight .nc { color: #ee99ee; font-weight: bold } /* Name.Class */ +.highlight .no { color: #55eedd; font-weight: bold } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #880000 } /* Name.Entity */ +.highlight .ne { color: #FF0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #55eedd; font-weight: bold } /* Name.Function */ +.highlight .nl { color: #997700; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #007700 } /* Name.Tag */ +.highlight .nv { color: #003366 } /* Name.Variable */ +.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #6600EE; font-weight: bold } /* Literal.Number.Bin */ +.highlight .mf { color: #6600EE; font-weight: bold } /* Literal.Number.Float */ +.highlight .mh { color: #005588; font-weight: bold } /* Literal.Number.Hex */ +.highlight .mi { color: #6666ff; font-weight: bold } /* Literal.Number.Integer */ +.highlight .mo { color: #4400EE; font-weight: bold } /* Literal.Number.Oct */ +.highlight .sa { background-color: #e0e0ff } /* Literal.String.Affix */ +.highlight .sb { background-color: #e0e0ff } /* Literal.String.Backtick */ +.highlight .sc { color: #8888FF } /* Literal.String.Char */ +.highlight .dl { background-color: #e0e0ff } /* Literal.String.Delimiter */ +.highlight .sd { color: #DD4422 } /* Literal.String.Doc */ +.highlight .s2 { background-color: #e0e0ff } /* Literal.String.Double */ +.highlight .se { color: #666666; font-weight: bold; background-color: #e0e0ff } /* Literal.String.Escape */ +.highlight .sh { background-color: #e0e0ff } /* Literal.String.Heredoc */ +.highlight .si { background-color: #eeeeee } /* Literal.String.Interpol */ +.highlight .sx { color: #ff8888; background-color: #e0e0ff } /* Literal.String.Other */ +.highlight .sr { color: #000000; background-color: #e0e0ff } /* Literal.String.Regex */ +.highlight .s1 { background-color: #e0e0ff } /* Literal.String.Single */ +.highlight .ss { color: #ffcc88 } /* Literal.String.Symbol */ +.highlight .bp { color: #007722 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #55eedd; font-weight: bold } /* Name.Function.Magic */ +.highlight .vc { color: #ccccff } /* Name.Variable.Class */ +.highlight .vg { color: #ff8844 } /* Name.Variable.Global */ +.highlight .vi { color: #aaaaff } /* Name.Variable.Instance */ +.highlight .vm { color: #003366 } /* Name.Variable.Magic */ +.highlight .il { color: #6666ff; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/native.css b/assets/css/themes/native.css new file mode 100644 index 0000000..324d7e7 --- /dev/null +++ b/assets/css/themes/native.css @@ -0,0 +1,78 @@ +.highlight .hll { background-color: #404040 } +.highlight { background: #202020; color: #d0d0d0 } +.highlight .c { color: #999999; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .esc { color: #d0d0d0 } /* Escape */ +.highlight .g { color: #d0d0d0 } /* Generic */ +.highlight .k { color: #6ab825; font-weight: bold } /* Keyword */ +.highlight .l { color: #d0d0d0 } /* Literal */ +.highlight .n { color: #d0d0d0 } /* Name */ +.highlight .o { color: #d0d0d0 } /* Operator */ +.highlight .x { color: #d0d0d0 } /* Other */ +.highlight .p { color: #d0d0d0 } /* Punctuation */ +.highlight .ch { color: #999999; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #999999; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #cd2828; font-weight: bold } /* Comment.Preproc */ +.highlight .cpf { color: #999999; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #999999; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #e50808; font-weight: bold; background-color: #520000 } /* Comment.Special */ +.highlight .gd { color: #d22323 } /* Generic.Deleted */ +.highlight .ge { color: #d0d0d0; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #d22323 } /* Generic.Error */ +.highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #589819 } /* Generic.Inserted */ +.highlight .go { color: #cccccc } /* Generic.Output */ +.highlight .gp { color: #aaaaaa } /* Generic.Prompt */ +.highlight .gs { color: #d0d0d0; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #ffffff; text-decoration: underline } /* Generic.Subheading */ +.highlight .gt { color: #d22323 } /* Generic.Traceback */ +.highlight .kc { color: #6ab825; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #6ab825; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #6ab825; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #6ab825 } /* Keyword.Pseudo */ +.highlight .kr { color: #6ab825; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #6ab825; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #d0d0d0 } /* Literal.Date */ +.highlight .m { color: #3677a9 } /* Literal.Number */ +.highlight .s { color: #ed9d13 } /* Literal.String */ +.highlight .na { color: #bbbbbb } /* Name.Attribute */ +.highlight .nb { color: #24909d } /* Name.Builtin */ +.highlight .nc { color: #447fcf; text-decoration: underline } /* Name.Class */ +.highlight .no { color: #40ffff } /* Name.Constant */ +.highlight .nd { color: #ffa500 } /* Name.Decorator */ +.highlight .ni { color: #d0d0d0 } /* Name.Entity */ +.highlight .ne { color: #bbbbbb } /* Name.Exception */ +.highlight .nf { color: #447fcf } /* Name.Function */ +.highlight .nl { color: #d0d0d0 } /* Name.Label */ +.highlight .nn { color: #447fcf; text-decoration: underline } /* Name.Namespace */ +.highlight .nx { color: #d0d0d0 } /* Name.Other */ +.highlight .py { color: #d0d0d0 } /* Name.Property */ +.highlight .nt { color: #6ab825; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #40ffff } /* Name.Variable */ +.highlight .ow { color: #6ab825; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #666666 } /* Text.Whitespace */ +.highlight .mb { color: #3677a9 } /* Literal.Number.Bin */ +.highlight .mf { color: #3677a9 } /* Literal.Number.Float */ +.highlight .mh { color: #3677a9 } /* Literal.Number.Hex */ +.highlight .mi { color: #3677a9 } /* Literal.Number.Integer */ +.highlight .mo { color: #3677a9 } /* Literal.Number.Oct */ +.highlight .sa { color: #ed9d13 } /* Literal.String.Affix */ +.highlight .sb { color: #ed9d13 } /* Literal.String.Backtick */ +.highlight .sc { color: #ed9d13 } /* Literal.String.Char */ +.highlight .dl { color: #ed9d13 } /* Literal.String.Delimiter */ +.highlight .sd { color: #ed9d13 } /* Literal.String.Doc */ +.highlight .s2 { color: #ed9d13 } /* Literal.String.Double */ +.highlight .se { color: #ed9d13 } /* Literal.String.Escape */ +.highlight .sh { color: #ed9d13 } /* Literal.String.Heredoc */ +.highlight .si { color: #ed9d13 } /* Literal.String.Interpol */ +.highlight .sx { color: #ffa500 } /* Literal.String.Other */ +.highlight .sr { color: #ed9d13 } /* Literal.String.Regex */ +.highlight .s1 { color: #ed9d13 } /* Literal.String.Single */ +.highlight .ss { color: #ed9d13 } /* Literal.String.Symbol */ +.highlight .bp { color: #24909d } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #447fcf } /* Name.Function.Magic */ +.highlight .vc { color: #40ffff } /* Name.Variable.Class */ +.highlight .vg { color: #40ffff } /* Name.Variable.Global */ +.highlight .vi { color: #40ffff } /* Name.Variable.Instance */ +.highlight .vm { color: #40ffff } /* Name.Variable.Magic */ +.highlight .il { color: #3677a9 } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/pastie.css b/assets/css/themes/pastie.css new file mode 100644 index 0000000..6b8be0d --- /dev/null +++ b/assets/css/themes/pastie.css @@ -0,0 +1,68 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #888888 } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { color: #008800; font-weight: bold } /* Keyword */ +.highlight .ch { color: #888888 } /* Comment.Hashbang */ +.highlight .cm { color: #888888 } /* Comment.Multiline */ +.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ +.highlight .cpf { color: #888888 } /* Comment.PreprocFile */ +.highlight .c1 { color: #888888 } /* Comment.Single */ +.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #333333 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #666666 } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008800 } /* Keyword.Pseudo */ +.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ +.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ +.highlight .na { color: #336699 } /* Name.Attribute */ +.highlight .nb { color: #003388 } /* Name.Builtin */ +.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ +.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ +.highlight .nd { color: #555555 } /* Name.Decorator */ +.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ +.highlight .nl { color: #336699; font-style: italic } /* Name.Label */ +.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ +.highlight .py { color: #336699; font-weight: bold } /* Name.Property */ +.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #336699 } /* Name.Variable */ +.highlight .ow { color: #008800 } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ +.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ +.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ +.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ +.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ +.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ +.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ +.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ +.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ +.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ +.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ +.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ +.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ +.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ +.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ +.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ +.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ +.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ +.highlight .vc { color: #336699 } /* Name.Variable.Class */ +.highlight .vg { color: #dd7700 } /* Name.Variable.Global */ +.highlight .vi { color: #3333bb } /* Name.Variable.Instance */ +.highlight .vm { color: #336699 } /* Name.Variable.Magic */ +.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/perldoc.css b/assets/css/themes/perldoc.css new file mode 100644 index 0000000..3786a86 --- /dev/null +++ b/assets/css/themes/perldoc.css @@ -0,0 +1,66 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeeedd; } +.highlight .c { color: #228B22 } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { color: #8B008B; font-weight: bold } /* Keyword */ +.highlight .ch { color: #228B22 } /* Comment.Hashbang */ +.highlight .cm { color: #228B22 } /* Comment.Multiline */ +.highlight .cp { color: #1e889b } /* Comment.Preproc */ +.highlight .cpf { color: #228B22 } /* Comment.PreprocFile */ +.highlight .c1 { color: #228B22 } /* Comment.Single */ +.highlight .cs { color: #8B008B; font-weight: bold } /* Comment.Special */ +.highlight .gd { color: #aa0000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00aa00 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { color: #8B008B; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #8B008B; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #8B008B; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #8B008B; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #8B008B; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #00688B; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #B452CD } /* Literal.Number */ +.highlight .s { color: #CD5555 } /* Literal.String */ +.highlight .na { color: #658b00 } /* Name.Attribute */ +.highlight .nb { color: #658b00 } /* Name.Builtin */ +.highlight .nc { color: #008b45; font-weight: bold } /* Name.Class */ +.highlight .no { color: #00688B } /* Name.Constant */ +.highlight .nd { color: #707a7c } /* Name.Decorator */ +.highlight .ne { color: #008b45; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #008b45 } /* Name.Function */ +.highlight .nn { color: #008b45; text-decoration: underline } /* Name.Namespace */ +.highlight .nt { color: #8B008B; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #00688B } /* Name.Variable */ +.highlight .ow { color: #8B008B } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #B452CD } /* Literal.Number.Bin */ +.highlight .mf { color: #B452CD } /* Literal.Number.Float */ +.highlight .mh { color: #B452CD } /* Literal.Number.Hex */ +.highlight .mi { color: #B452CD } /* Literal.Number.Integer */ +.highlight .mo { color: #B452CD } /* Literal.Number.Oct */ +.highlight .sa { color: #CD5555 } /* Literal.String.Affix */ +.highlight .sb { color: #CD5555 } /* Literal.String.Backtick */ +.highlight .sc { color: #CD5555 } /* Literal.String.Char */ +.highlight .dl { color: #CD5555 } /* Literal.String.Delimiter */ +.highlight .sd { color: #CD5555 } /* Literal.String.Doc */ +.highlight .s2 { color: #CD5555 } /* Literal.String.Double */ +.highlight .se { color: #CD5555 } /* Literal.String.Escape */ +.highlight .sh { color: #1c7e71; font-style: italic } /* Literal.String.Heredoc */ +.highlight .si { color: #CD5555 } /* Literal.String.Interpol */ +.highlight .sx { color: #cb6c20 } /* Literal.String.Other */ +.highlight .sr { color: #1c7e71 } /* Literal.String.Regex */ +.highlight .s1 { color: #CD5555 } /* Literal.String.Single */ +.highlight .ss { color: #CD5555 } /* Literal.String.Symbol */ +.highlight .bp { color: #658b00 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #008b45 } /* Name.Function.Magic */ +.highlight .vc { color: #00688B } /* Name.Variable.Class */ +.highlight .vg { color: #00688B } /* Name.Variable.Global */ +.highlight .vi { color: #00688B } /* Name.Variable.Instance */ +.highlight .vm { color: #00688B } /* Name.Variable.Magic */ +.highlight .il { color: #B452CD } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/tango.css b/assets/css/themes/tango.css new file mode 100644 index 0000000..0fa0bea --- /dev/null +++ b/assets/css/themes/tango.css @@ -0,0 +1,77 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #8f5902; font-style: italic } /* Comment */ +.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ +.highlight .g { color: #000000 } /* Generic */ +.highlight .k { color: #204a87; font-weight: bold } /* Keyword */ +.highlight .l { color: #000000 } /* Literal */ +.highlight .n { color: #000000 } /* Name */ +.highlight .o { color: #ce5c00; font-weight: bold } /* Operator */ +.highlight .x { color: #000000 } /* Other */ +.highlight .p { color: #000000; font-weight: bold } /* Punctuation */ +.highlight .ch { color: #8f5902; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #8f5902; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #8f5902; font-style: italic } /* Comment.Preproc */ +.highlight .cpf { color: #8f5902; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #8f5902; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #8f5902; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #a40000 } /* Generic.Deleted */ +.highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #ef2929 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #000000; font-style: italic } /* Generic.Output */ +.highlight .gp { color: #8f5902 } /* Generic.Prompt */ +.highlight .gs { color: #000000; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ +.highlight .kc { color: #204a87; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #204a87; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #204a87; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #204a87; font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { color: #204a87; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #204a87; font-weight: bold } /* Keyword.Type */ +.highlight .ld { color: #000000 } /* Literal.Date */ +.highlight .m { color: #0000cf; font-weight: bold } /* Literal.Number */ +.highlight .s { color: #4e9a06 } /* Literal.String */ +.highlight .na { color: #c4a000 } /* Name.Attribute */ +.highlight .nb { color: #204a87 } /* Name.Builtin */ +.highlight .nc { color: #000000 } /* Name.Class */ +.highlight .no { color: #000000 } /* Name.Constant */ +.highlight .nd { color: #5c35cc; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #ce5c00 } /* Name.Entity */ +.highlight .ne { color: #cc0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #000000 } /* Name.Function */ +.highlight .nl { color: #f57900 } /* Name.Label */ +.highlight .nn { color: #000000 } /* Name.Namespace */ +.highlight .nx { color: #000000 } /* Name.Other */ +.highlight .py { color: #000000 } /* Name.Property */ +.highlight .nt { color: #204a87; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #000000 } /* Name.Variable */ +.highlight .ow { color: #204a87; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ +.highlight .mb { color: #0000cf; font-weight: bold } /* Literal.Number.Bin */ +.highlight .mf { color: #0000cf; font-weight: bold } /* Literal.Number.Float */ +.highlight .mh { color: #0000cf; font-weight: bold } /* Literal.Number.Hex */ +.highlight .mi { color: #0000cf; font-weight: bold } /* Literal.Number.Integer */ +.highlight .mo { color: #0000cf; font-weight: bold } /* Literal.Number.Oct */ +.highlight .sa { color: #4e9a06 } /* Literal.String.Affix */ +.highlight .sb { color: #4e9a06 } /* Literal.String.Backtick */ +.highlight .sc { color: #4e9a06 } /* Literal.String.Char */ +.highlight .dl { color: #4e9a06 } /* Literal.String.Delimiter */ +.highlight .sd { color: #8f5902; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4e9a06 } /* Literal.String.Double */ +.highlight .se { color: #4e9a06 } /* Literal.String.Escape */ +.highlight .sh { color: #4e9a06 } /* Literal.String.Heredoc */ +.highlight .si { color: #4e9a06 } /* Literal.String.Interpol */ +.highlight .sx { color: #4e9a06 } /* Literal.String.Other */ +.highlight .sr { color: #4e9a06 } /* Literal.String.Regex */ +.highlight .s1 { color: #4e9a06 } /* Literal.String.Single */ +.highlight .ss { color: #4e9a06 } /* Literal.String.Symbol */ +.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #000000 } /* Name.Function.Magic */ +.highlight .vc { color: #000000 } /* Name.Variable.Class */ +.highlight .vg { color: #000000 } /* Name.Variable.Global */ +.highlight .vi { color: #000000 } /* Name.Variable.Instance */ +.highlight .vm { color: #000000 } /* Name.Variable.Magic */ +.highlight .il { color: #0000cf; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/trac.css b/assets/css/themes/trac.css new file mode 100644 index 0000000..de27db3 --- /dev/null +++ b/assets/css/themes/trac.css @@ -0,0 +1,67 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #999988; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { font-weight: bold } /* Keyword */ +.highlight .o { font-weight: bold } /* Operator */ +.highlight .ch { color: #999988; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ +.highlight .cpf { color: #999988; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #aaaaaa } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { font-weight: bold } /* Keyword.Constant */ +.highlight .kd { font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #bb8844 } /* Literal.String */ +.highlight .na { color: #008080 } /* Name.Attribute */ +.highlight .nb { color: #999999 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: #008080 } /* Name.Constant */ +.highlight .ni { color: #800080 } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nn { color: #555555 } /* Name.Namespace */ +.highlight .nt { color: #000080 } /* Name.Tag */ +.highlight .nv { color: #008080 } /* Name.Variable */ +.highlight .ow { font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #009999 } /* Literal.Number.Bin */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sa { color: #bb8844 } /* Literal.String.Affix */ +.highlight .sb { color: #bb8844 } /* Literal.String.Backtick */ +.highlight .sc { color: #bb8844 } /* Literal.String.Char */ +.highlight .dl { color: #bb8844 } /* Literal.String.Delimiter */ +.highlight .sd { color: #bb8844 } /* Literal.String.Doc */ +.highlight .s2 { color: #bb8844 } /* Literal.String.Double */ +.highlight .se { color: #bb8844 } /* Literal.String.Escape */ +.highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ +.highlight .si { color: #bb8844 } /* Literal.String.Interpol */ +.highlight .sx { color: #bb8844 } /* Literal.String.Other */ +.highlight .sr { color: #808000 } /* Literal.String.Regex */ +.highlight .s1 { color: #bb8844 } /* Literal.String.Single */ +.highlight .ss { color: #bb8844 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #990000; font-weight: bold } /* Name.Function.Magic */ +.highlight .vc { color: #008080 } /* Name.Variable.Class */ +.highlight .vg { color: #008080 } /* Name.Variable.Global */ +.highlight .vi { color: #008080 } /* Name.Variable.Instance */ +.highlight .vm { color: #008080 } /* Name.Variable.Magic */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/vim.css b/assets/css/themes/vim.css new file mode 100644 index 0000000..3b5d33e --- /dev/null +++ b/assets/css/themes/vim.css @@ -0,0 +1,78 @@ +.highlight .hll { background-color: #222222 } +.highlight { background: #000000; color: #cccccc } +.highlight .c { color: #000080 } /* Comment */ +.highlight .err { color: #cccccc; border: 1px solid #FF0000 } /* Error */ +.highlight .esc { color: #cccccc } /* Escape */ +.highlight .g { color: #cccccc } /* Generic */ +.highlight .k { color: #cdcd00 } /* Keyword */ +.highlight .l { color: #cccccc } /* Literal */ +.highlight .n { color: #cccccc } /* Name */ +.highlight .o { color: #3399cc } /* Operator */ +.highlight .x { color: #cccccc } /* Other */ +.highlight .p { color: #cccccc } /* Punctuation */ +.highlight .ch { color: #000080 } /* Comment.Hashbang */ +.highlight .cm { color: #000080 } /* Comment.Multiline */ +.highlight .cp { color: #000080 } /* Comment.Preproc */ +.highlight .cpf { color: #000080 } /* Comment.PreprocFile */ +.highlight .c1 { color: #000080 } /* Comment.Single */ +.highlight .cs { color: #cd0000; font-weight: bold } /* Comment.Special */ +.highlight .gd { color: #cd0000 } /* Generic.Deleted */ +.highlight .ge { color: #cccccc; font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00cd00 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { color: #cccccc; font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #cdcd00 } /* Keyword.Constant */ +.highlight .kd { color: #00cd00 } /* Keyword.Declaration */ +.highlight .kn { color: #cd00cd } /* Keyword.Namespace */ +.highlight .kp { color: #cdcd00 } /* Keyword.Pseudo */ +.highlight .kr { color: #cdcd00 } /* Keyword.Reserved */ +.highlight .kt { color: #00cd00 } /* Keyword.Type */ +.highlight .ld { color: #cccccc } /* Literal.Date */ +.highlight .m { color: #cd00cd } /* Literal.Number */ +.highlight .s { color: #cd0000 } /* Literal.String */ +.highlight .na { color: #cccccc } /* Name.Attribute */ +.highlight .nb { color: #cd00cd } /* Name.Builtin */ +.highlight .nc { color: #00cdcd } /* Name.Class */ +.highlight .no { color: #cccccc } /* Name.Constant */ +.highlight .nd { color: #cccccc } /* Name.Decorator */ +.highlight .ni { color: #cccccc } /* Name.Entity */ +.highlight .ne { color: #666699; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #cccccc } /* Name.Function */ +.highlight .nl { color: #cccccc } /* Name.Label */ +.highlight .nn { color: #cccccc } /* Name.Namespace */ +.highlight .nx { color: #cccccc } /* Name.Other */ +.highlight .py { color: #cccccc } /* Name.Property */ +.highlight .nt { color: #cccccc } /* Name.Tag */ +.highlight .nv { color: #00cdcd } /* Name.Variable */ +.highlight .ow { color: #cdcd00 } /* Operator.Word */ +.highlight .w { color: #cccccc } /* Text.Whitespace */ +.highlight .mb { color: #cd00cd } /* Literal.Number.Bin */ +.highlight .mf { color: #cd00cd } /* Literal.Number.Float */ +.highlight .mh { color: #cd00cd } /* Literal.Number.Hex */ +.highlight .mi { color: #cd00cd } /* Literal.Number.Integer */ +.highlight .mo { color: #cd00cd } /* Literal.Number.Oct */ +.highlight .sa { color: #cd0000 } /* Literal.String.Affix */ +.highlight .sb { color: #cd0000 } /* Literal.String.Backtick */ +.highlight .sc { color: #cd0000 } /* Literal.String.Char */ +.highlight .dl { color: #cd0000 } /* Literal.String.Delimiter */ +.highlight .sd { color: #cd0000 } /* Literal.String.Doc */ +.highlight .s2 { color: #cd0000 } /* Literal.String.Double */ +.highlight .se { color: #cd0000 } /* Literal.String.Escape */ +.highlight .sh { color: #cd0000 } /* Literal.String.Heredoc */ +.highlight .si { color: #cd0000 } /* Literal.String.Interpol */ +.highlight .sx { color: #cd0000 } /* Literal.String.Other */ +.highlight .sr { color: #cd0000 } /* Literal.String.Regex */ +.highlight .s1 { color: #cd0000 } /* Literal.String.Single */ +.highlight .ss { color: #cd0000 } /* Literal.String.Symbol */ +.highlight .bp { color: #cd00cd } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #cccccc } /* Name.Function.Magic */ +.highlight .vc { color: #00cdcd } /* Name.Variable.Class */ +.highlight .vg { color: #00cdcd } /* Name.Variable.Global */ +.highlight .vi { color: #00cdcd } /* Name.Variable.Instance */ +.highlight .vm { color: #00cdcd } /* Name.Variable.Magic */ +.highlight .il { color: #cd00cd } /* Literal.Number.Integer.Long */ diff --git a/assets/css/themes/vs.css b/assets/css/themes/vs.css new file mode 100644 index 0000000..b6436fe --- /dev/null +++ b/assets/css/themes/vs.css @@ -0,0 +1,38 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #ffffff; } +.highlight .c { color: #008000 } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #0000ff } /* Keyword */ +.highlight .ch { color: #008000 } /* Comment.Hashbang */ +.highlight .cm { color: #008000 } /* Comment.Multiline */ +.highlight .cp { color: #0000ff } /* Comment.Preproc */ +.highlight .cpf { color: #008000 } /* Comment.PreprocFile */ +.highlight .c1 { color: #008000 } /* Comment.Single */ +.highlight .cs { color: #008000 } /* Comment.Special */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gh { font-weight: bold } /* Generic.Heading */ +.highlight .gp { font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { font-weight: bold } /* Generic.Subheading */ +.highlight .kc { color: #0000ff } /* Keyword.Constant */ +.highlight .kd { color: #0000ff } /* Keyword.Declaration */ +.highlight .kn { color: #0000ff } /* Keyword.Namespace */ +.highlight .kp { color: #0000ff } /* Keyword.Pseudo */ +.highlight .kr { color: #0000ff } /* Keyword.Reserved */ +.highlight .kt { color: #2b91af } /* Keyword.Type */ +.highlight .s { color: #a31515 } /* Literal.String */ +.highlight .nc { color: #2b91af } /* Name.Class */ +.highlight .ow { color: #0000ff } /* Operator.Word */ +.highlight .sa { color: #a31515 } /* Literal.String.Affix */ +.highlight .sb { color: #a31515 } /* Literal.String.Backtick */ +.highlight .sc { color: #a31515 } /* Literal.String.Char */ +.highlight .dl { color: #a31515 } /* Literal.String.Delimiter */ +.highlight .sd { color: #a31515 } /* Literal.String.Doc */ +.highlight .s2 { color: #a31515 } /* Literal.String.Double */ +.highlight .se { color: #a31515 } /* Literal.String.Escape */ +.highlight .sh { color: #a31515 } /* Literal.String.Heredoc */ +.highlight .si { color: #a31515 } /* Literal.String.Interpol */ +.highlight .sx { color: #a31515 } /* Literal.String.Other */ +.highlight .sr { color: #a31515 } /* Literal.String.Regex */ +.highlight .s1 { color: #a31515 } /* Literal.String.Single */ +.highlight .ss { color: #a31515 } /* Literal.String.Symbol */ diff --git a/assets/fonts/casper-icons.eot b/assets/fonts/casper-icons.eot new file mode 100644 index 0000000..0bfce34 Binary files /dev/null and b/assets/fonts/casper-icons.eot differ diff --git a/assets/fonts/casper-icons.svg b/assets/fonts/casper-icons.svg new file mode 100644 index 0000000..98efd05 --- /dev/null +++ b/assets/fonts/casper-icons.svg @@ -0,0 +1,19 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/fonts/casper-icons.ttf b/assets/fonts/casper-icons.ttf new file mode 100644 index 0000000..f1b8da8 Binary files /dev/null and b/assets/fonts/casper-icons.ttf differ diff --git a/assets/fonts/casper-icons.woff b/assets/fonts/casper-icons.woff new file mode 100644 index 0000000..4a98893 Binary files /dev/null and b/assets/fonts/casper-icons.woff differ diff --git a/assets/images/GAN/gan-cat.png b/assets/images/GAN/gan-cat.png new file mode 100644 index 0000000..d7025e3 Binary files /dev/null and b/assets/images/GAN/gan-cat.png differ diff --git a/assets/images/GAN/gan-cat2.png b/assets/images/GAN/gan-cat2.png new file mode 100644 index 0000000..2171ebf Binary files /dev/null and b/assets/images/GAN/gan-cat2.png differ diff --git a/assets/images/GAN/gan-diagram.png b/assets/images/GAN/gan-diagram.png new file mode 100644 index 0000000..b067b19 Binary files /dev/null and b/assets/images/GAN/gan-diagram.png differ diff --git a/assets/images/GAN/pix2pix.png b/assets/images/GAN/pix2pix.png new file mode 100644 index 0000000..379b3cc Binary files /dev/null and b/assets/images/GAN/pix2pix.png differ diff --git a/assets/images/GAN/stylegan.png b/assets/images/GAN/stylegan.png new file mode 100644 index 0000000..f18a428 Binary files /dev/null and b/assets/images/GAN/stylegan.png differ diff --git a/assets/images/HMM/ace.png b/assets/images/HMM/ace.png new file mode 100644 index 0000000..1f116de Binary files /dev/null and b/assets/images/HMM/ace.png differ diff --git a/assets/images/HMM/atlas.png b/assets/images/HMM/atlas.png new file mode 100644 index 0000000..80cca06 Binary files /dev/null and b/assets/images/HMM/atlas.png differ diff --git a/assets/images/HMM/first.png b/assets/images/HMM/first.png new file mode 100644 index 0000000..50cd797 Binary files /dev/null and b/assets/images/HMM/first.png differ diff --git a/assets/images/HMM/flying.png b/assets/images/HMM/flying.png new file mode 100644 index 0000000..fdc0b62 Binary files /dev/null and b/assets/images/HMM/flying.png differ diff --git a/assets/images/HMM/hand.jpg b/assets/images/HMM/hand.jpg new file mode 100644 index 0000000..d93150c Binary files /dev/null and b/assets/images/HMM/hand.jpg differ diff --git a/assets/images/HMM/hmm-tenis.png b/assets/images/HMM/hmm-tenis.png new file mode 100644 index 0000000..7eb71d1 Binary files /dev/null and b/assets/images/HMM/hmm-tenis.png differ diff --git a/assets/images/HMM/hmm-tenis2.png b/assets/images/HMM/hmm-tenis2.png new file mode 100644 index 0000000..28a4543 Binary files /dev/null and b/assets/images/HMM/hmm-tenis2.png differ diff --git a/assets/images/HMM/obs_seq.png b/assets/images/HMM/obs_seq.png new file mode 100644 index 0000000..6521cd9 Binary files /dev/null and b/assets/images/HMM/obs_seq.png differ diff --git a/assets/images/HMM/padel_court.png b/assets/images/HMM/padel_court.png new file mode 100644 index 0000000..b8d9608 Binary files /dev/null and b/assets/images/HMM/padel_court.png differ diff --git a/assets/images/HMM/previs.png b/assets/images/HMM/previs.png new file mode 100644 index 0000000..31b5b79 Binary files /dev/null and b/assets/images/HMM/previs.png differ diff --git a/assets/images/HMM/rally.png b/assets/images/HMM/rally.png new file mode 100644 index 0000000..0d8eb87 Binary files /dev/null and b/assets/images/HMM/rally.png differ diff --git a/assets/images/HMM/second.png b/assets/images/HMM/second.png new file mode 100644 index 0000000..bebc410 Binary files /dev/null and b/assets/images/HMM/second.png differ diff --git a/assets/images/HMM/transition.png b/assets/images/HMM/transition.png new file mode 100644 index 0000000..f932a3d Binary files /dev/null and b/assets/images/HMM/transition.png differ diff --git a/assets/images/HMM/union.png b/assets/images/HMM/union.png new file mode 100644 index 0000000..d94292b Binary files /dev/null and b/assets/images/HMM/union.png differ diff --git a/assets/images/JoseAvatar.png b/assets/images/JoseAvatar.png new file mode 100644 index 0000000..b8cda25 Binary files /dev/null and b/assets/images/JoseAvatar.png differ diff --git a/assets/images/NAS/controller.png b/assets/images/NAS/controller.png new file mode 100644 index 0000000..f327015 Binary files /dev/null and b/assets/images/NAS/controller.png differ diff --git a/assets/images/NAS/lstm.png b/assets/images/NAS/lstm.png new file mode 100644 index 0000000..51cc6f7 Binary files /dev/null and b/assets/images/NAS/lstm.png differ diff --git a/assets/images/NAS/lstm_.png b/assets/images/NAS/lstm_.png new file mode 100644 index 0000000..b7637ac Binary files /dev/null and b/assets/images/NAS/lstm_.png differ diff --git a/assets/images/NAS/nas-image.png b/assets/images/NAS/nas-image.png new file mode 100644 index 0000000..9a5e5d6 Binary files /dev/null and b/assets/images/NAS/nas-image.png differ diff --git a/assets/images/NAS/nas2-image.png b/assets/images/NAS/nas2-image.png new file mode 100644 index 0000000..c3e8023 Binary files /dev/null and b/assets/images/NAS/nas2-image.png differ diff --git a/assets/images/NAS/nas3-image.png b/assets/images/NAS/nas3-image.png new file mode 100644 index 0000000..57557d2 Binary files /dev/null and b/assets/images/NAS/nas3-image.png differ diff --git a/assets/images/NAS/nascell.png b/assets/images/NAS/nascell.png new file mode 100644 index 0000000..3b48d81 Binary files /dev/null and b/assets/images/NAS/nascell.png differ diff --git a/assets/images/NAS/neural-architecture-search.webp b/assets/images/NAS/neural-architecture-search.webp new file mode 100644 index 0000000..2cf9867 Binary files /dev/null and b/assets/images/NAS/neural-architecture-search.webp differ diff --git a/assets/images/NAS/orig-res.png b/assets/images/NAS/orig-res.png new file mode 100644 index 0000000..c367503 Binary files /dev/null and b/assets/images/NAS/orig-res.png differ diff --git a/assets/images/NAS/overview.png b/assets/images/NAS/overview.png new file mode 100644 index 0000000..5584298 Binary files /dev/null and b/assets/images/NAS/overview.png differ diff --git a/assets/images/NAS/res-diagram.png b/assets/images/NAS/res-diagram.png new file mode 100644 index 0000000..421d417 Binary files /dev/null and b/assets/images/NAS/res-diagram.png differ diff --git a/assets/images/NAS/residual-def.png b/assets/images/NAS/residual-def.png new file mode 100644 index 0000000..514706e Binary files /dev/null and b/assets/images/NAS/residual-def.png differ diff --git a/assets/images/NAS/simple-explained.png b/assets/images/NAS/simple-explained.png new file mode 100644 index 0000000..41d7422 Binary files /dev/null and b/assets/images/NAS/simple-explained.png differ diff --git a/assets/images/NAS/simple-rnn.png b/assets/images/NAS/simple-rnn.png new file mode 100644 index 0000000..d98b3c5 Binary files /dev/null and b/assets/images/NAS/simple-rnn.png differ diff --git a/assets/images/NAS/training.png b/assets/images/NAS/training.png new file mode 100644 index 0000000..d51a437 Binary files /dev/null and b/assets/images/NAS/training.png differ diff --git a/assets/images/StableDiffusion/HFSettings.png b/assets/images/StableDiffusion/HFSettings.png new file mode 100644 index 0000000..3f9abd7 Binary files /dev/null and b/assets/images/StableDiffusion/HFSettings.png differ diff --git a/assets/images/StableDiffusion/HFToken.png b/assets/images/StableDiffusion/HFToken.png new file mode 100644 index 0000000..b37f2f9 Binary files /dev/null and b/assets/images/StableDiffusion/HFToken.png differ diff --git a/assets/images/StableDiffusion/HFToken2.png b/assets/images/StableDiffusion/HFToken2.png new file mode 100644 index 0000000..a86ceea Binary files /dev/null and b/assets/images/StableDiffusion/HFToken2.png differ diff --git a/assets/images/StableDiffusion/HFToken3.png b/assets/images/StableDiffusion/HFToken3.png new file mode 100644 index 0000000..b5dc893 Binary files /dev/null and b/assets/images/StableDiffusion/HFToken3.png differ diff --git a/assets/images/StableDiffusion/HFlogin.png b/assets/images/StableDiffusion/HFlogin.png new file mode 100644 index 0000000..4349725 Binary files /dev/null and b/assets/images/StableDiffusion/HFlogin.png differ diff --git a/assets/images/StableDiffusion/SDimage.png b/assets/images/StableDiffusion/SDimage.png new file mode 100644 index 0000000..ed19054 Binary files /dev/null and b/assets/images/StableDiffusion/SDimage.png differ diff --git a/assets/images/StableDiffusion/access.png b/assets/images/StableDiffusion/access.png new file mode 100644 index 0000000..86d25ad Binary files /dev/null and b/assets/images/StableDiffusion/access.png differ diff --git a/assets/images/StableDiffusion/cell.png b/assets/images/StableDiffusion/cell.png new file mode 100644 index 0000000..f3578eb Binary files /dev/null and b/assets/images/StableDiffusion/cell.png differ diff --git a/assets/images/StableDiffusion/cell2.png b/assets/images/StableDiffusion/cell2.png new file mode 100644 index 0000000..c6866e5 Binary files /dev/null and b/assets/images/StableDiffusion/cell2.png differ diff --git a/assets/images/StableDiffusion/cell3.png b/assets/images/StableDiffusion/cell3.png new file mode 100644 index 0000000..ea86360 Binary files /dev/null and b/assets/images/StableDiffusion/cell3.png differ diff --git a/assets/images/StableDiffusion/cell4.png b/assets/images/StableDiffusion/cell4.png new file mode 100644 index 0000000..5ffe8d1 Binary files /dev/null and b/assets/images/StableDiffusion/cell4.png differ diff --git a/assets/images/StableDiffusion/cellOutput.png b/assets/images/StableDiffusion/cellOutput.png new file mode 100644 index 0000000..aca3b2a Binary files /dev/null and b/assets/images/StableDiffusion/cellOutput.png differ diff --git a/assets/images/StableDiffusion/code.png b/assets/images/StableDiffusion/code.png new file mode 100644 index 0000000..1b6c9eb Binary files /dev/null and b/assets/images/StableDiffusion/code.png differ diff --git a/assets/images/StableDiffusion/code2.png b/assets/images/StableDiffusion/code2.png new file mode 100644 index 0000000..0421902 Binary files /dev/null and b/assets/images/StableDiffusion/code2.png differ diff --git a/assets/images/StableDiffusion/code3.png b/assets/images/StableDiffusion/code3.png new file mode 100644 index 0000000..40e5613 Binary files /dev/null and b/assets/images/StableDiffusion/code3.png differ diff --git a/assets/images/StableDiffusion/counter.png b/assets/images/StableDiffusion/counter.png new file mode 100644 index 0000000..8e12c51 Binary files /dev/null and b/assets/images/StableDiffusion/counter.png differ diff --git a/assets/images/StableDiffusion/download.png b/assets/images/StableDiffusion/download.png new file mode 100644 index 0000000..327ae17 Binary files /dev/null and b/assets/images/StableDiffusion/download.png differ diff --git a/assets/images/StableDiffusion/error.png b/assets/images/StableDiffusion/error.png new file mode 100644 index 0000000..76a0344 Binary files /dev/null and b/assets/images/StableDiffusion/error.png differ diff --git a/assets/images/StableDiffusion/ex1.png b/assets/images/StableDiffusion/ex1.png new file mode 100644 index 0000000..895d632 Binary files /dev/null and b/assets/images/StableDiffusion/ex1.png differ diff --git a/assets/images/StableDiffusion/huggingfaceRegister.png b/assets/images/StableDiffusion/huggingfaceRegister.png new file mode 100644 index 0000000..c9f77c7 Binary files /dev/null and b/assets/images/StableDiffusion/huggingfaceRegister.png differ diff --git a/assets/images/StableDiffusion/huggingfaceRegister2.png b/assets/images/StableDiffusion/huggingfaceRegister2.png new file mode 100644 index 0000000..f12afcf Binary files /dev/null and b/assets/images/StableDiffusion/huggingfaceRegister2.png differ diff --git a/assets/images/StableDiffusion/kaggleRegister.png b/assets/images/StableDiffusion/kaggleRegister.png new file mode 100644 index 0000000..f3e151e Binary files /dev/null and b/assets/images/StableDiffusion/kaggleRegister.png differ diff --git a/assets/images/StableDiffusion/kaggleRegister2.png b/assets/images/StableDiffusion/kaggleRegister2.png new file mode 100644 index 0000000..9c1d0a7 Binary files /dev/null and b/assets/images/StableDiffusion/kaggleRegister2.png differ diff --git a/assets/images/StableDiffusion/kaggleSD.png b/assets/images/StableDiffusion/kaggleSD.png new file mode 100644 index 0000000..00f14fc Binary files /dev/null and b/assets/images/StableDiffusion/kaggleSD.png differ diff --git a/assets/images/StableDiffusion/kaggleSD2.png b/assets/images/StableDiffusion/kaggleSD2.png new file mode 100644 index 0000000..80b65c4 Binary files /dev/null and b/assets/images/StableDiffusion/kaggleSD2.png differ diff --git a/assets/images/StableDiffusion/libs.png b/assets/images/StableDiffusion/libs.png new file mode 100644 index 0000000..54b6bc4 Binary files /dev/null and b/assets/images/StableDiffusion/libs.png differ diff --git a/assets/images/StableDiffusion/notebook.png b/assets/images/StableDiffusion/notebook.png new file mode 100644 index 0000000..19d6551 Binary files /dev/null and b/assets/images/StableDiffusion/notebook.png differ diff --git a/assets/images/StableDiffusion/notebook2.png b/assets/images/StableDiffusion/notebook2.png new file mode 100644 index 0000000..37c2f02 Binary files /dev/null and b/assets/images/StableDiffusion/notebook2.png differ diff --git a/assets/images/StableDiffusion/notebook3.png b/assets/images/StableDiffusion/notebook3.png new file mode 100644 index 0000000..12cbebe Binary files /dev/null and b/assets/images/StableDiffusion/notebook3.png differ diff --git a/assets/images/StableDiffusion/terms.png b/assets/images/StableDiffusion/terms.png new file mode 100644 index 0000000..3c65542 Binary files /dev/null and b/assets/images/StableDiffusion/terms.png differ diff --git a/assets/images/StableDiffusion/version.png b/assets/images/StableDiffusion/version.png new file mode 100644 index 0000000..f47da44 Binary files /dev/null and b/assets/images/StableDiffusion/version.png differ diff --git a/assets/images/about.jpg b/assets/images/about.jpg new file mode 100644 index 0000000..187ef66 Binary files /dev/null and b/assets/images/about.jpg differ diff --git a/assets/images/audiobook/Cover.png b/assets/images/audiobook/Cover.png new file mode 100644 index 0000000..7290913 Binary files /dev/null and b/assets/images/audiobook/Cover.png differ diff --git a/assets/images/audiobook/after.png b/assets/images/audiobook/after.png new file mode 100644 index 0000000..3cfa7a2 Binary files /dev/null and b/assets/images/audiobook/after.png differ diff --git a/assets/images/audiobook/before.png b/assets/images/audiobook/before.png new file mode 100644 index 0000000..53f9765 Binary files /dev/null and b/assets/images/audiobook/before.png differ diff --git a/assets/images/audiobook/dalle1-1.png b/assets/images/audiobook/dalle1-1.png new file mode 100644 index 0000000..8f631f0 Binary files /dev/null and b/assets/images/audiobook/dalle1-1.png differ diff --git a/assets/images/audiobook/dalle1-2.png b/assets/images/audiobook/dalle1-2.png new file mode 100644 index 0000000..99acf91 Binary files /dev/null and b/assets/images/audiobook/dalle1-2.png differ diff --git a/assets/images/audiobook/dalle1-3.png b/assets/images/audiobook/dalle1-3.png new file mode 100644 index 0000000..e8e3427 Binary files /dev/null and b/assets/images/audiobook/dalle1-3.png differ diff --git a/assets/images/audiobook/dalle1-4.png b/assets/images/audiobook/dalle1-4.png new file mode 100644 index 0000000..057d847 Binary files /dev/null and b/assets/images/audiobook/dalle1-4.png differ diff --git a/assets/images/audiobook/fail1.png b/assets/images/audiobook/fail1.png new file mode 100644 index 0000000..048a7cf Binary files /dev/null and b/assets/images/audiobook/fail1.png differ diff --git a/assets/images/audiobook/fail2.png b/assets/images/audiobook/fail2.png new file mode 100644 index 0000000..0f2ef23 Binary files /dev/null and b/assets/images/audiobook/fail2.png differ diff --git a/assets/images/audiobook/fail3.png b/assets/images/audiobook/fail3.png new file mode 100644 index 0000000..46dd1a0 Binary files /dev/null and b/assets/images/audiobook/fail3.png differ diff --git a/assets/images/audiobook/fail4.png b/assets/images/audiobook/fail4.png new file mode 100644 index 0000000..92c01ec Binary files /dev/null and b/assets/images/audiobook/fail4.png differ diff --git a/assets/images/audiobook/fail5.png b/assets/images/audiobook/fail5.png new file mode 100644 index 0000000..b945129 Binary files /dev/null and b/assets/images/audiobook/fail5.png differ diff --git a/assets/images/audiobook/fail6.png b/assets/images/audiobook/fail6.png new file mode 100644 index 0000000..0ccd2de Binary files /dev/null and b/assets/images/audiobook/fail6.png differ diff --git a/assets/images/audiobook/text1-1.png b/assets/images/audiobook/text1-1.png new file mode 100644 index 0000000..162a4d8 Binary files /dev/null and b/assets/images/audiobook/text1-1.png differ diff --git a/assets/images/audiobook/text2-3.png b/assets/images/audiobook/text2-3.png new file mode 100644 index 0000000..d6e0776 Binary files /dev/null and b/assets/images/audiobook/text2-3.png differ diff --git a/assets/images/aws-logo.png b/assets/images/aws-logo.png new file mode 100644 index 0000000..b2a77a6 Binary files /dev/null and b/assets/images/aws-logo.png differ diff --git a/assets/images/buddha.jpeg b/assets/images/buddha.jpeg new file mode 100644 index 0000000..c7c431f Binary files /dev/null and b/assets/images/buddha.jpeg differ diff --git a/assets/images/compass.jpg b/assets/images/compass.jpg new file mode 100644 index 0000000..08e8470 Binary files /dev/null and b/assets/images/compass.jpg differ diff --git a/assets/images/favicon.ico b/assets/images/favicon.ico new file mode 100644 index 0000000..909d9d0 Binary files /dev/null and b/assets/images/favicon.ico differ diff --git a/assets/images/favicon_io/android-chrome-192x192.png b/assets/images/favicon_io/android-chrome-192x192.png new file mode 100644 index 0000000..a526227 Binary files /dev/null and b/assets/images/favicon_io/android-chrome-192x192.png differ diff --git a/assets/images/favicon_io/android-chrome-512x512.png b/assets/images/favicon_io/android-chrome-512x512.png new file mode 100644 index 0000000..58aa803 Binary files /dev/null and b/assets/images/favicon_io/android-chrome-512x512.png differ diff --git a/assets/images/favicon_io/apple-touch-icon.png b/assets/images/favicon_io/apple-touch-icon.png new file mode 100644 index 0000000..1f1c174 Binary files /dev/null and b/assets/images/favicon_io/apple-touch-icon.png differ diff --git a/assets/images/favicon_io/favicon-16x16.png b/assets/images/favicon_io/favicon-16x16.png new file mode 100644 index 0000000..d2f7fa3 Binary files /dev/null and b/assets/images/favicon_io/favicon-16x16.png differ diff --git a/assets/images/favicon_io/favicon-32x32.png b/assets/images/favicon_io/favicon-32x32.png new file mode 100644 index 0000000..451e0fd Binary files /dev/null and b/assets/images/favicon_io/favicon-32x32.png differ diff --git a/assets/images/favicon_io/site.webmanifest b/assets/images/favicon_io/site.webmanifest new file mode 100644 index 0000000..45dc8a2 --- /dev/null +++ b/assets/images/favicon_io/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/assets/images/finish.jpg b/assets/images/finish.jpg new file mode 100644 index 0000000..cd56571 Binary files /dev/null and b/assets/images/finish.jpg differ diff --git a/assets/images/jumbotron.jpg b/assets/images/jumbotron.jpg new file mode 100644 index 0000000..36be1f1 Binary files /dev/null and b/assets/images/jumbotron.jpg differ diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..ecff63a Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/images/raspberry-pi-modelos.png b/assets/images/raspberry-pi-modelos.png new file mode 100644 index 0000000..ee4305d Binary files /dev/null and b/assets/images/raspberry-pi-modelos.png differ diff --git a/assets/js/ie10-viewport-bug-workaround.js b/assets/js/ie10-viewport-bug-workaround.js new file mode 100644 index 0000000..b335ef9 --- /dev/null +++ b/assets/js/ie10-viewport-bug-workaround.js @@ -0,0 +1,24 @@ +/*! + * IE10 viewport hack for Surface/desktop Windows 8 bug + * Copyright 2014-2017 The Bootstrap Authors + * Copyright 2014-2017 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// See the Getting Started docs for more information: +// https://getbootstrap.com/getting-started/#support-ie10-width + +(function () { + 'use strict' + + if (navigator.userAgent.match(/IEMobile\/10\.0/)) { + var msViewportStyle = document.createElement('style') + msViewportStyle.appendChild( + document.createTextNode( + '@-ms-viewport{width:auto!important}' + ) + ) + document.head.appendChild(msViewportStyle) + } + +}()) diff --git a/assets/js/jquery.min.js b/assets/js/jquery.min.js new file mode 100644 index 0000000..4d9b3a2 --- /dev/null +++ b/assets/js/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.3.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){"use strict";var n=[],r=e.document,i=Object.getPrototypeOf,o=n.slice,a=n.concat,s=n.push,u=n.indexOf,l={},c=l.toString,f=l.hasOwnProperty,p=f.toString,d=p.call(Object),h={},g=function e(t){return"function"==typeof t&&"number"!=typeof t.nodeType},y=function e(t){return null!=t&&t===t.window},v={type:!0,src:!0,noModule:!0};function m(e,t,n){var i,o=(t=t||r).createElement("script");if(o.text=e,n)for(i in v)n[i]&&(o[i]=n[i]);t.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[c.call(e)]||"object":typeof e}var b="3.3.1",w=function(e,t){return new w.fn.init(e,t)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;w.fn=w.prototype={jquery:"3.3.1",constructor:w,length:0,toArray:function(){return o.call(this)},get:function(e){return null==e?o.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=w.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return w.each(this,e)},map:function(e){return this.pushStack(w.map(this,function(t,n){return e.call(t,n,t)}))},slice:function(){return this.pushStack(o.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(n>=0&&n0&&t-1 in e)}var E=function(e){var t,n,r,i,o,a,s,u,l,c,f,p,d,h,g,y,v,m,x,b="sizzle"+1*new Date,w=e.document,T=0,C=0,E=ae(),k=ae(),S=ae(),D=function(e,t){return e===t&&(f=!0),0},N={}.hasOwnProperty,A=[],j=A.pop,q=A.push,L=A.push,H=A.slice,O=function(e,t){for(var n=0,r=e.length;n+~]|"+M+")"+M+"*"),z=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),X=new RegExp(W),U=new RegExp("^"+R+"$"),V={ID:new RegExp("^#("+R+")"),CLASS:new RegExp("^\\.("+R+")"),TAG:new RegExp("^("+R+"|[*])"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+P+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},G=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,K=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ee=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},te=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ne=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},re=function(){p()},ie=me(function(e){return!0===e.disabled&&("form"in e||"label"in e)},{dir:"parentNode",next:"legend"});try{L.apply(A=H.call(w.childNodes),w.childNodes),A[w.childNodes.length].nodeType}catch(e){L={apply:A.length?function(e,t){q.apply(e,H.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function oe(e,t,r,i){var o,s,l,c,f,h,v,m=t&&t.ownerDocument,T=t?t.nodeType:9;if(r=r||[],"string"!=typeof e||!e||1!==T&&9!==T&&11!==T)return r;if(!i&&((t?t.ownerDocument||t:w)!==d&&p(t),t=t||d,g)){if(11!==T&&(f=J.exec(e)))if(o=f[1]){if(9===T){if(!(l=t.getElementById(o)))return r;if(l.id===o)return r.push(l),r}else if(m&&(l=m.getElementById(o))&&x(t,l)&&l.id===o)return r.push(l),r}else{if(f[2])return L.apply(r,t.getElementsByTagName(e)),r;if((o=f[3])&&n.getElementsByClassName&&t.getElementsByClassName)return L.apply(r,t.getElementsByClassName(o)),r}if(n.qsa&&!S[e+" "]&&(!y||!y.test(e))){if(1!==T)m=t,v=e;else if("object"!==t.nodeName.toLowerCase()){(c=t.getAttribute("id"))?c=c.replace(te,ne):t.setAttribute("id",c=b),s=(h=a(e)).length;while(s--)h[s]="#"+c+" "+ve(h[s]);v=h.join(","),m=K.test(e)&&ge(t.parentNode)||t}if(v)try{return L.apply(r,m.querySelectorAll(v)),r}catch(e){}finally{c===b&&t.removeAttribute("id")}}}return u(e.replace(B,"$1"),t,r,i)}function ae(){var e=[];function t(n,i){return e.push(n+" ")>r.cacheLength&&delete t[e.shift()],t[n+" "]=i}return t}function se(e){return e[b]=!0,e}function ue(e){var t=d.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function le(e,t){var n=e.split("|"),i=n.length;while(i--)r.attrHandle[n[i]]=t}function ce(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function fe(e){return function(t){return"input"===t.nodeName.toLowerCase()&&t.type===e}}function pe(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function de(e){return function(t){return"form"in t?t.parentNode&&!1===t.disabled?"label"in t?"label"in t.parentNode?t.parentNode.disabled===e:t.disabled===e:t.isDisabled===e||t.isDisabled!==!e&&ie(t)===e:t.disabled===e:"label"in t&&t.disabled===e}}function he(e){return se(function(t){return t=+t,se(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function ge(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}n=oe.support={},o=oe.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},p=oe.setDocument=function(e){var t,i,a=e?e.ownerDocument||e:w;return a!==d&&9===a.nodeType&&a.documentElement?(d=a,h=d.documentElement,g=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",re,!1):i.attachEvent&&i.attachEvent("onunload",re)),n.attributes=ue(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ue(function(e){return e.appendChild(d.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=Q.test(d.getElementsByClassName),n.getById=ue(function(e){return h.appendChild(e).id=b,!d.getElementsByName||!d.getElementsByName(b).length}),n.getById?(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){return e.getAttribute("id")===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n=t.getElementById(e);return n?[n]:[]}}):(r.filter.ID=function(e){var t=e.replace(Z,ee);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}},r.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&g){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):n.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&g)return t.getElementsByClassName(e)},v=[],y=[],(n.qsa=Q.test(d.querySelectorAll))&&(ue(function(e){h.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+P+")"),e.querySelectorAll("[id~="+b+"-]").length||y.push("~="),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+b+"+*").length||y.push(".#.+[+~]")}),ue(function(e){e.innerHTML="";var t=d.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),h.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(n.matchesSelector=Q.test(m=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ue(function(e){n.disconnectedMatch=m.call(e,"*"),m.call(e,"[s!='']:x"),v.push("!=",W)}),y=y.length&&new RegExp(y.join("|")),v=v.length&&new RegExp(v.join("|")),t=Q.test(h.compareDocumentPosition),x=t||Q.test(h.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return f=!0,0;var r=!e.compareDocumentPosition-!t.compareDocumentPosition;return r||(1&(r=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!n.sortDetached&&t.compareDocumentPosition(e)===r?e===d||e.ownerDocument===w&&x(w,e)?-1:t===d||t.ownerDocument===w&&x(w,t)?1:c?O(c,e)-O(c,t):0:4&r?-1:1)}:function(e,t){if(e===t)return f=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===d?-1:t===d?1:i?-1:o?1:c?O(c,e)-O(c,t):0;if(i===o)return ce(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?ce(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},oe.matches=function(e,t){return oe(e,null,null,t)},oe.matchesSelector=function(e,t){if((e.ownerDocument||e)!==d&&p(e),t=t.replace(z,"='$1']"),n.matchesSelector&&g&&!S[t+" "]&&(!v||!v.test(t))&&(!y||!y.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(e){}return oe(t,d,null,[e]).length>0},oe.contains=function(e,t){return(e.ownerDocument||e)!==d&&p(e),x(e,t)},oe.attr=function(e,t){(e.ownerDocument||e)!==d&&p(e);var i=r.attrHandle[t.toLowerCase()],o=i&&N.call(r.attrHandle,t.toLowerCase())?i(e,t,!g):void 0;return void 0!==o?o:n.attributes||!g?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null},oe.escape=function(e){return(e+"").replace(te,ne)},oe.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},oe.uniqueSort=function(e){var t,r=[],i=0,o=0;if(f=!n.detectDuplicates,c=!n.sortStable&&e.slice(0),e.sort(D),f){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return c=null,e},i=oe.getText=function(e){var t,n="",r=0,o=e.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=i(e)}else if(3===o||4===o)return e.nodeValue}else while(t=e[r++])n+=i(t);return n},(r=oe.selectors={cacheLength:50,createPseudo:se,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(Z,ee),e[3]=(e[3]||e[4]||e[5]||"").replace(Z,ee),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||oe.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&oe.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return V.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=a(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(Z,ee).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=E[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&E(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=oe.attr(r,e);return null==i?"!="===t:!t||(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i.replace($," ")+" ").indexOf(n)>-1:"|="===t&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",y=t.parentNode,v=s&&t.nodeName.toLowerCase(),m=!u&&!s,x=!1;if(y){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===v:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?y.firstChild:y.lastChild],a&&m){x=(d=(l=(c=(f=(p=y)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1])&&l[2],p=d&&y.childNodes[d];while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if(1===p.nodeType&&++x&&p===t){c[e]=[T,d,x];break}}else if(m&&(x=d=(l=(c=(f=(p=t)[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]||[])[0]===T&&l[1]),!1===x)while(p=++d&&p&&p[g]||(x=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===v:1===p.nodeType)&&++x&&(m&&((c=(f=p[b]||(p[b]={}))[p.uniqueID]||(f[p.uniqueID]={}))[e]=[T,x]),p===t))break;return(x-=i)===r||x%r==0&&x/r>=0}}},PSEUDO:function(e,t){var n,i=r.pseudos[e]||r.setFilters[e.toLowerCase()]||oe.error("unsupported pseudo: "+e);return i[b]?i(t):i.length>1?(n=[e,e,"",t],r.setFilters.hasOwnProperty(e.toLowerCase())?se(function(e,n){var r,o=i(e,t),a=o.length;while(a--)e[r=O(e,o[a])]=!(n[r]=o[a])}):function(e){return i(e,0,n)}):i}},pseudos:{not:se(function(e){var t=[],n=[],r=s(e.replace(B,"$1"));return r[b]?se(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),t[0]=null,!n.pop()}}),has:se(function(e){return function(t){return oe(e,t).length>0}}),contains:se(function(e){return e=e.replace(Z,ee),function(t){return(t.textContent||t.innerText||i(t)).indexOf(e)>-1}}),lang:se(function(e){return U.test(e||"")||oe.error("unsupported lang: "+e),e=e.replace(Z,ee).toLowerCase(),function(t){var n;do{if(n=g?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return(n=n.toLowerCase())===e||0===n.indexOf(e+"-")}while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===h},focus:function(e){return e===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:de(!1),disabled:de(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!r.pseudos.empty(e)},header:function(e){return Y.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:he(function(){return[0]}),last:he(function(e,t){return[t-1]}),eq:he(function(e,t,n){return[n<0?n+t:n]}),even:he(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:he(function(e,t,n){for(var r=n<0?n+t:n;++r1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function be(e,t,n){for(var r=0,i=t.length;r-1&&(o[l]=!(a[l]=f))}}else v=we(v===a?v.splice(h,v.length):v),i?i(null,a,v,u):L.apply(a,v)})}function Ce(e){for(var t,n,i,o=e.length,a=r.relative[e[0].type],s=a||r.relative[" "],u=a?1:0,c=me(function(e){return e===t},s,!0),f=me(function(e){return O(t,e)>-1},s,!0),p=[function(e,n,r){var i=!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):f(e,n,r));return t=null,i}];u1&&xe(p),u>1&&ve(e.slice(0,u-1).concat({value:" "===e[u-2].type?"*":""})).replace(B,"$1"),n,u0,i=e.length>0,o=function(o,a,s,u,c){var f,h,y,v=0,m="0",x=o&&[],b=[],w=l,C=o||i&&r.find.TAG("*",c),E=T+=null==w?1:Math.random()||.1,k=C.length;for(c&&(l=a===d||a||c);m!==k&&null!=(f=C[m]);m++){if(i&&f){h=0,a||f.ownerDocument===d||(p(f),s=!g);while(y=e[h++])if(y(f,a||d,s)){u.push(f);break}c&&(T=E)}n&&((f=!y&&f)&&v--,o&&x.push(f))}if(v+=m,n&&m!==v){h=0;while(y=t[h++])y(x,b,a,s);if(o){if(v>0)while(m--)x[m]||b[m]||(b[m]=j.call(u));b=we(b)}L.apply(u,b),c&&!o&&b.length>0&&v+t.length>1&&oe.uniqueSort(u)}return c&&(T=E,l=w),x};return n?se(o):o}return s=oe.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=a(e)),n=t.length;while(n--)(o=Ce(t[n]))[b]?r.push(o):i.push(o);(o=S(e,Ee(i,r))).selector=e}return o},u=oe.select=function(e,t,n,i){var o,u,l,c,f,p="function"==typeof e&&e,d=!i&&a(e=p.selector||e);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(l=u[0]).type&&9===t.nodeType&&g&&r.relative[u[1].type]){if(!(t=(r.find.ID(l.matches[0].replace(Z,ee),t)||[])[0]))return n;p&&(t=t.parentNode),e=e.slice(u.shift().value.length)}o=V.needsContext.test(e)?0:u.length;while(o--){if(l=u[o],r.relative[c=l.type])break;if((f=r.find[c])&&(i=f(l.matches[0].replace(Z,ee),K.test(u[0].type)&&ge(t.parentNode)||t))){if(u.splice(o,1),!(e=i.length&&ve(u)))return L.apply(n,i),n;break}}}return(p||s(e,d))(i,t,!g,n,!t||K.test(e)&&ge(t.parentNode)||t),n},n.sortStable=b.split("").sort(D).join("")===b,n.detectDuplicates=!!f,p(),n.sortDetached=ue(function(e){return 1&e.compareDocumentPosition(d.createElement("fieldset"))}),ue(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||le("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ue(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||le("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ue(function(e){return null==e.getAttribute("disabled")})||le(P,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),oe}(e);w.find=E,w.expr=E.selectors,w.expr[":"]=w.expr.pseudos,w.uniqueSort=w.unique=E.uniqueSort,w.text=E.getText,w.isXMLDoc=E.isXML,w.contains=E.contains,w.escapeSelector=E.escape;var k=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&w(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},D=w.expr.match.needsContext;function N(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var A=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,t,n){return g(t)?w.grep(e,function(e,r){return!!t.call(e,r,e)!==n}):t.nodeType?w.grep(e,function(e){return e===t!==n}):"string"!=typeof t?w.grep(e,function(e){return u.call(t,e)>-1!==n}):w.filter(t,e,n)}w.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?w.find.matchesSelector(r,e)?[r]:[]:w.find.matches(e,w.grep(t,function(e){return 1===e.nodeType}))},w.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(w(e).filter(function(){for(t=0;t1?w.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,"string"==typeof e&&D.test(e)?w(e):e||[],!1).length}});var q,L=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(w.fn.init=function(e,t,n){var i,o;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(i="<"===e[0]&&">"===e[e.length-1]&&e.length>=3?[null,e,null]:L.exec(e))||!i[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(i[1]){if(t=t instanceof w?t[0]:t,w.merge(this,w.parseHTML(i[1],t&&t.nodeType?t.ownerDocument||t:r,!0)),A.test(i[1])&&w.isPlainObject(t))for(i in t)g(this[i])?this[i](t[i]):this.attr(i,t[i]);return this}return(o=r.getElementById(i[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):g(e)?void 0!==n.ready?n.ready(e):e(w):w.makeArray(e,this)}).prototype=w.fn,q=w(r);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};w.fn.extend({has:function(e){var t=w(e,this),n=t.length;return this.filter(function(){for(var e=0;e-1:1===n.nodeType&&w.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?w.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?u.call(w(e),this[0]):u.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(w.uniqueSort(w.merge(this.get(),w(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}w.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return k(e,"parentNode")},parentsUntil:function(e,t,n){return k(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return k(e,"nextSibling")},prevAll:function(e){return k(e,"previousSibling")},nextUntil:function(e,t,n){return k(e,"nextSibling",n)},prevUntil:function(e,t,n){return k(e,"previousSibling",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return N(e,"iframe")?e.contentDocument:(N(e,"template")&&(e=e.content||e),w.merge([],e.childNodes))}},function(e,t){w.fn[e]=function(n,r){var i=w.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=w.filter(r,i)),this.length>1&&(O[e]||w.uniqueSort(i),H.test(e)&&i.reverse()),this.pushStack(i)}});var M=/[^\x20\t\r\n\f]+/g;function R(e){var t={};return w.each(e.match(M)||[],function(e,n){t[n]=!0}),t}w.Callbacks=function(e){e="string"==typeof e?R(e):w.extend({},e);var t,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||e.once,r=t=!0;a.length;s=-1){n=a.shift();while(++s-1)o.splice(n,1),n<=s&&s--}),this},has:function(e){return e?w.inArray(e,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||t||(o=n=""),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=[e,(n=n||[]).slice?n.slice():n],a.push(n),t||u()),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!r}};return l};function I(e){return e}function W(e){throw e}function $(e,t,n,r){var i;try{e&&g(i=e.promise)?i.call(e).done(t).fail(n):e&&g(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}w.extend({Deferred:function(t){var n=[["notify","progress",w.Callbacks("memory"),w.Callbacks("memory"),2],["resolve","done",w.Callbacks("once memory"),w.Callbacks("once memory"),0,"resolved"],["reject","fail",w.Callbacks("once memory"),w.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},"catch":function(e){return i.then(null,e)},pipe:function(){var e=arguments;return w.Deferred(function(t){w.each(n,function(n,r){var i=g(e[r[4]])&&e[r[4]];o[r[1]](function(){var e=i&&i.apply(this,arguments);e&&g(e.promise)?e.promise().progress(t.notify).done(t.resolve).fail(t.reject):t[r[0]+"With"](this,i?[e]:arguments)})}),e=null}).promise()},then:function(t,r,i){var o=0;function a(t,n,r,i){return function(){var s=this,u=arguments,l=function(){var e,l;if(!(t=o&&(r!==W&&(s=void 0,u=[e]),n.rejectWith(s,u))}};t?c():(w.Deferred.getStackHook&&(c.stackTrace=w.Deferred.getStackHook()),e.setTimeout(c))}}return w.Deferred(function(e){n[0][3].add(a(0,e,g(i)?i:I,e.notifyWith)),n[1][3].add(a(0,e,g(t)?t:I)),n[2][3].add(a(0,e,g(r)?r:W))}).promise()},promise:function(e){return null!=e?w.extend(e,i):i}},o={};return w.each(n,function(e,t){var a=t[2],s=t[5];i[t[1]]=a.add,s&&a.add(function(){r=s},n[3-e][2].disable,n[3-e][3].disable,n[0][2].lock,n[0][3].lock),a.add(t[3].fire),o[t[0]]=function(){return o[t[0]+"With"](this===o?void 0:this,arguments),this},o[t[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(e){var t=arguments.length,n=t,r=Array(n),i=o.call(arguments),a=w.Deferred(),s=function(e){return function(n){r[e]=this,i[e]=arguments.length>1?o.call(arguments):n,--t||a.resolveWith(r,i)}};if(t<=1&&($(e,a.done(s(n)).resolve,a.reject,!t),"pending"===a.state()||g(i[n]&&i[n].then)))return a.then();while(n--)$(i[n],s(n),a.reject);return a.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;w.Deferred.exceptionHook=function(t,n){e.console&&e.console.warn&&t&&B.test(t.name)&&e.console.warn("jQuery.Deferred exception: "+t.message,t.stack,n)},w.readyException=function(t){e.setTimeout(function(){throw t})};var F=w.Deferred();w.fn.ready=function(e){return F.then(e)["catch"](function(e){w.readyException(e)}),this},w.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--w.readyWait:w.isReady)||(w.isReady=!0,!0!==e&&--w.readyWait>0||F.resolveWith(r,[w]))}}),w.ready.then=F.then;function _(){r.removeEventListener("DOMContentLoaded",_),e.removeEventListener("load",_),w.ready()}"complete"===r.readyState||"loading"!==r.readyState&&!r.documentElement.doScroll?e.setTimeout(w.ready):(r.addEventListener("DOMContentLoaded",_),e.addEventListener("load",_));var z=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n)){i=!0;for(s in n)z(e,t,s,n[s],!0,o,a)}else if(void 0!==r&&(i=!0,g(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(w(e),n)})),t))for(;s1,null,!0)},removeData:function(e){return this.each(function(){K.remove(this,e)})}}),w.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=J.get(e,t),n&&(!r||Array.isArray(n)?r=J.access(e,t,w.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=w.queue(e,t),r=n.length,i=n.shift(),o=w._queueHooks(e,t),a=function(){w.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return J.get(e,n)||J.access(e,n,{empty:w.Callbacks("once memory").add(function(){J.remove(e,[t+"queue",n])})})}}),w.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length\x20\t\r\n\f]+)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&N(e,t)?w.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n-1)i&&i.push(o);else if(l=w.contains(o.ownerDocument,o),a=ye(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])he.test(o.type||"")&&n.push(o)}return f}!function(){var e=r.createDocumentFragment().appendChild(r.createElement("div")),t=r.createElement("input");t.setAttribute("type","radio"),t.setAttribute("checked","checked"),t.setAttribute("name","t"),e.appendChild(t),h.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,e.innerHTML="",h.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue}();var be=r.documentElement,we=/^key/,Te=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ce=/^([^.]*)(?:\.(.+)|)/;function Ee(){return!0}function ke(){return!1}function Se(){try{return r.activeElement}catch(e){}}function De(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)De(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=ke;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return w().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=w.guid++)),e.each(function(){w.event.add(this,t,i,r,n)})}w.event={global:{},add:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.get(e);if(y){n.handler&&(n=(o=n).handler,i=o.selector),i&&w.find.matchesSelector(be,i),n.guid||(n.guid=w.guid++),(u=y.events)||(u=y.events={}),(a=y.handle)||(a=y.handle=function(t){return"undefined"!=typeof w&&w.event.triggered!==t.type?w.event.dispatch.apply(e,arguments):void 0}),l=(t=(t||"").match(M)||[""]).length;while(l--)d=g=(s=Ce.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=w.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=w.event.special[d]||{},c=w.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&w.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(e,r,h,a)||e.addEventListener&&e.addEventListener(d,a)),f.add&&(f.add.call(e,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),w.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,y=J.hasData(e)&&J.get(e);if(y&&(u=y.events)){l=(t=(t||"").match(M)||[""]).length;while(l--)if(s=Ce.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){f=w.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,y.handle)||w.removeEvent(e,d,y.handle),delete u[d])}else for(d in u)w.event.remove(e,d+t[l],n,r,!0);w.isEmptyObject(u)&&J.remove(e,"handle events")}},dispatch:function(e){var t=w.event.fix(e),n,r,i,o,a,s,u=new Array(arguments.length),l=(J.get(this,"events")||{})[t.type]||[],c=w.event.special[t.type]||{};for(u[0]=t,n=1;n=1))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n-1:w.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,Ae=/\s*$/g;function Le(e,t){return N(e,"table")&&N(11!==t.nodeType?t:t.firstChild,"tr")?w(e).children("tbody")[0]||e:e}function He(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Oe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Pe(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(J.hasData(e)&&(o=J.access(e),a=J.set(t,o),l=o.events)){delete a.handle,a.events={};for(i in l)for(n=0,r=l[i].length;n1&&"string"==typeof y&&!h.checkClone&&je.test(y))return e.each(function(i){var o=e.eq(i);v&&(t[0]=y.call(this,i,o.html())),Re(o,t,n,r)});if(p&&(i=xe(t,e[0].ownerDocument,!1,e,r),o=i.firstChild,1===i.childNodes.length&&(i=o),o||r)){for(u=(s=w.map(ye(i,"script"),He)).length;f")},clone:function(e,t,n){var r,i,o,a,s=e.cloneNode(!0),u=w.contains(e.ownerDocument,e);if(!(h.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||w.isXMLDoc(e)))for(a=ye(s),r=0,i=(o=ye(e)).length;r0&&ve(a,!u&&ye(e,"script")),s},cleanData:function(e){for(var t,n,r,i=w.event.special,o=0;void 0!==(n=e[o]);o++)if(Y(n)){if(t=n[J.expando]){if(t.events)for(r in t.events)i[r]?w.event.remove(n,r):w.removeEvent(n,r,t.handle);n[J.expando]=void 0}n[K.expando]&&(n[K.expando]=void 0)}}}),w.fn.extend({detach:function(e){return Ie(this,e,!0)},remove:function(e){return Ie(this,e)},text:function(e){return z(this,function(e){return void 0===e?w.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Re(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Re(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Re(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(w.cleanData(ye(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return w.clone(this,e,t)})},html:function(e){return z(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Ae.test(e)&&!ge[(de.exec(e)||["",""])[1].toLowerCase()]){e=w.htmlPrefilter(e);try{for(;n=0&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))),u}function et(e,t,n){var r=$e(e),i=Fe(e,t,r),o="border-box"===w.css(e,"boxSizing",!1,r),a=o;if(We.test(i)){if(!n)return i;i="auto"}return a=a&&(h.boxSizingReliable()||i===e.style[t]),("auto"===i||!parseFloat(i)&&"inline"===w.css(e,"display",!1,r))&&(i=e["offset"+t[0].toUpperCase()+t.slice(1)],a=!0),(i=parseFloat(i)||0)+Ze(e,t,n||(o?"border":"content"),a,r,i)+"px"}w.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Fe(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=G(t),u=Xe.test(t),l=e.style;if(u||(t=Je(s)),a=w.cssHooks[t]||w.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"==(o=typeof n)&&(i=ie.exec(n))&&i[1]&&(n=ue(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(w.cssNumber[s]?"":"px")),h.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=G(t);return Xe.test(t)||(t=Je(s)),(a=w.cssHooks[t]||w.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Fe(e,t,r)),"normal"===i&&t in Ve&&(i=Ve[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),w.each(["height","width"],function(e,t){w.cssHooks[t]={get:function(e,n,r){if(n)return!ze.test(w.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,t,r):se(e,Ue,function(){return et(e,t,r)})},set:function(e,n,r){var i,o=$e(e),a="border-box"===w.css(e,"boxSizing",!1,o),s=r&&Ze(e,t,r,a,o);return a&&h.scrollboxSize()===o.position&&(s-=Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-parseFloat(o[t])-Ze(e,t,"border",!1,o)-.5)),s&&(i=ie.exec(n))&&"px"!==(i[3]||"px")&&(e.style[t]=n,n=w.css(e,t)),Ke(e,n,s)}}}),w.cssHooks.marginLeft=_e(h.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Fe(e,"marginLeft"))||e.getBoundingClientRect().left-se(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),w.each({margin:"",padding:"",border:"Width"},function(e,t){w.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+oe[r]+t]=o[r]||o[r-2]||o[0];return i}},"margin"!==e&&(w.cssHooks[e+t].set=Ke)}),w.fn.extend({css:function(e,t){return z(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=$e(e),i=t.length;a1)}});function tt(e,t,n,r,i){return new tt.prototype.init(e,t,n,r,i)}w.Tween=tt,tt.prototype={constructor:tt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||w.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(w.cssNumber[n]?"":"px")},cur:function(){var e=tt.propHooks[this.prop];return e&&e.get?e.get(this):tt.propHooks._default.get(this)},run:function(e){var t,n=tt.propHooks[this.prop];return this.options.duration?this.pos=t=w.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):tt.propHooks._default.set(this),this}},tt.prototype.init.prototype=tt.prototype,tt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=w.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){w.fx.step[e.prop]?w.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[w.cssProps[e.prop]]&&!w.cssHooks[e.prop]?e.elem[e.prop]=e.now:w.style(e.elem,e.prop,e.now+e.unit)}}},tt.propHooks.scrollTop=tt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},w.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},w.fx=tt.prototype.init,w.fx.step={};var nt,rt,it=/^(?:toggle|show|hide)$/,ot=/queueHooks$/;function at(){rt&&(!1===r.hidden&&e.requestAnimationFrame?e.requestAnimationFrame(at):e.setTimeout(at,w.fx.interval),w.fx.tick())}function st(){return e.setTimeout(function(){nt=void 0}),nt=Date.now()}function ut(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=oe[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function lt(e,t,n){for(var r,i=(pt.tweeners[t]||[]).concat(pt.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(e){return this.each(function(){w.removeAttr(this,e)})}}),w.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?w.prop(e,t,n):(1===o&&w.isXMLDoc(e)||(i=w.attrHooks[t.toLowerCase()]||(w.expr.match.bool.test(t)?dt:void 0)),void 0!==n?null===n?void w.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=w.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!h.radioValue&&"radio"===t&&N(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(M);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),dt={set:function(e,t,n){return!1===t?w.removeAttr(e,n):e.setAttribute(n,n),n}},w.each(w.expr.match.bool.source.match(/\w+/g),function(e,t){var n=ht[t]||w.find.attr;ht[t]=function(e,t,r){var i,o,a=t.toLowerCase();return r||(o=ht[a],ht[a]=i,i=null!=n(e,t,r)?a:null,ht[a]=o),i}});var gt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;w.fn.extend({prop:function(e,t){return z(this,w.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[w.propFix[e]||e]})}}),w.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&w.isXMLDoc(e)||(t=w.propFix[t]||t,i=w.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=w.find.attr(e,"tabindex");return t?parseInt(t,10):gt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),h.optSelected||(w.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),w.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){w.propFix[this.toLowerCase()]=this});function vt(e){return(e.match(M)||[]).join(" ")}function mt(e){return e.getAttribute&&e.getAttribute("class")||""}function xt(e){return Array.isArray(e)?e:"string"==typeof e?e.match(M)||[]:[]}w.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).addClass(e.call(this,t,mt(this)))});if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(g(e))return this.each(function(t){w(this).removeClass(e.call(this,t,mt(this)))});if(!arguments.length)return this.attr("class","");if((t=xt(e)).length)while(n=this[u++])if(i=mt(n),r=1===n.nodeType&&" "+vt(i)+" "){a=0;while(o=t[a++])while(r.indexOf(" "+o+" ")>-1)r=r.replace(" "+o+" "," ");i!==(s=vt(r))&&n.setAttribute("class",s)}return this},toggleClass:function(e,t){var n=typeof e,r="string"===n||Array.isArray(e);return"boolean"==typeof t&&r?t?this.addClass(e):this.removeClass(e):g(e)?this.each(function(n){w(this).toggleClass(e.call(this,n,mt(this),t),t)}):this.each(function(){var t,i,o,a;if(r){i=0,o=w(this),a=xt(e);while(t=a[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else void 0!==e&&"boolean"!==n||((t=mt(this))&&J.set(this,"__className__",t),this.setAttribute&&this.setAttribute("class",t||!1===e?"":J.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&(" "+vt(mt(n))+" ").indexOf(t)>-1)return!0;return!1}});var bt=/\r/g;w.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=g(e),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?e.call(this,n,w(this).val()):e)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=w.map(i,function(e){return null==e?"":e+""})),(t=w.valHooks[this.type]||w.valHooks[this.nodeName.toLowerCase()])&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return(t=w.valHooks[i.type]||w.valHooks[i.nodeName.toLowerCase()])&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(bt,""):null==n?"":n}}}),w.extend({valHooks:{option:{get:function(e){var t=w.find.attr(e,"value");return null!=t?t:vt(w.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),w.each(["radio","checkbox"],function(){w.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=w.inArray(w(e).val(),t)>-1}},h.checkOn||(w.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),h.focusin="onfocusin"in e;var wt=/^(?:focusinfocus|focusoutblur)$/,Tt=function(e){e.stopPropagation()};w.extend(w.event,{trigger:function(t,n,i,o){var a,s,u,l,c,p,d,h,v=[i||r],m=f.call(t,"type")?t.type:t,x=f.call(t,"namespace")?t.namespace.split("."):[];if(s=h=u=i=i||r,3!==i.nodeType&&8!==i.nodeType&&!wt.test(m+w.event.triggered)&&(m.indexOf(".")>-1&&(m=(x=m.split(".")).shift(),x.sort()),c=m.indexOf(":")<0&&"on"+m,t=t[w.expando]?t:new w.Event(m,"object"==typeof t&&t),t.isTrigger=o?2:3,t.namespace=x.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+x.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=i),n=null==n?[t]:w.makeArray(n,[t]),d=w.event.special[m]||{},o||!d.trigger||!1!==d.trigger.apply(i,n))){if(!o&&!d.noBubble&&!y(i)){for(l=d.delegateType||m,wt.test(l+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(i.ownerDocument||r)&&v.push(u.defaultView||u.parentWindow||e)}a=0;while((s=v[a++])&&!t.isPropagationStopped())h=s,t.type=a>1?l:d.bindType||m,(p=(J.get(s,"events")||{})[t.type]&&J.get(s,"handle"))&&p.apply(s,n),(p=c&&s[c])&&p.apply&&Y(s)&&(t.result=p.apply(s,n),!1===t.result&&t.preventDefault());return t.type=m,o||t.isDefaultPrevented()||d._default&&!1!==d._default.apply(v.pop(),n)||!Y(i)||c&&g(i[m])&&!y(i)&&((u=i[c])&&(i[c]=null),w.event.triggered=m,t.isPropagationStopped()&&h.addEventListener(m,Tt),i[m](),t.isPropagationStopped()&&h.removeEventListener(m,Tt),w.event.triggered=void 0,u&&(i[c]=u)),t.result}},simulate:function(e,t,n){var r=w.extend(new w.Event,n,{type:e,isSimulated:!0});w.event.trigger(r,null,t)}}),w.fn.extend({trigger:function(e,t){return this.each(function(){w.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return w.event.trigger(e,t,n,!0)}}),h.focusin||w.each({focus:"focusin",blur:"focusout"},function(e,t){var n=function(e){w.event.simulate(t,e.target,w.event.fix(e))};w.event.special[t]={setup:function(){var r=this.ownerDocument||this,i=J.access(r,t);i||r.addEventListener(e,n,!0),J.access(r,t,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=J.access(r,t)-1;i?J.access(r,t,i):(r.removeEventListener(e,n,!0),J.remove(r,t))}}});var Ct=e.location,Et=Date.now(),kt=/\?/;w.parseXML=function(t){var n;if(!t||"string"!=typeof t)return null;try{n=(new e.DOMParser).parseFromString(t,"text/xml")}catch(e){n=void 0}return n&&!n.getElementsByTagName("parsererror").length||w.error("Invalid XML: "+t),n};var St=/\[\]$/,Dt=/\r?\n/g,Nt=/^(?:submit|button|image|reset|file)$/i,At=/^(?:input|select|textarea|keygen)/i;function jt(e,t,n,r){var i;if(Array.isArray(t))w.each(t,function(t,i){n||St.test(e)?r(e,i):jt(e+"["+("object"==typeof i&&null!=i?t:"")+"]",i,n,r)});else if(n||"object"!==x(t))r(e,t);else for(i in t)jt(e+"["+i+"]",t[i],n,r)}w.param=function(e,t){var n,r=[],i=function(e,t){var n=g(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(e)||e.jquery&&!w.isPlainObject(e))w.each(e,function(){i(this.name,this.value)});else for(n in e)jt(n,e[n],t,i);return r.join("&")},w.fn.extend({serialize:function(){return w.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=w.prop(this,"elements");return e?w.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!w(this).is(":disabled")&&At.test(this.nodeName)&&!Nt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=w(this).val();return null==n?null:Array.isArray(n)?w.map(n,function(e){return{name:t.name,value:e.replace(Dt,"\r\n")}}):{name:t.name,value:n.replace(Dt,"\r\n")}}).get()}});var qt=/%20/g,Lt=/#.*$/,Ht=/([?&])_=[^&]*/,Ot=/^(.*?):[ \t]*([^\r\n]*)$/gm,Pt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Mt=/^(?:GET|HEAD)$/,Rt=/^\/\//,It={},Wt={},$t="*/".concat("*"),Bt=r.createElement("a");Bt.href=Ct.href;function Ft(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(M)||[];if(g(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function _t(e,t,n,r){var i={},o=e===Wt;function a(s){var u;return i[s]=!0,w.each(e[s]||[],function(e,s){var l=s(t,n,r);return"string"!=typeof l||o||i[l]?o?!(u=l):void 0:(t.dataTypes.unshift(l),a(l),!1)}),u}return a(t.dataTypes[0])||!i["*"]&&a("*")}function zt(e,t){var n,r,i=w.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&w.extend(!0,e,r),e}function Xt(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}function Ut(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}w.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ct.href,type:"GET",isLocal:Pt.test(Ct.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":$t,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":w.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,w.ajaxSettings),t):zt(w.ajaxSettings,e)},ajaxPrefilter:Ft(It),ajaxTransport:Ft(Wt),ajax:function(t,n){"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,p,d,h=w.ajaxSetup({},n),g=h.context||h,y=h.context&&(g.nodeType||g.jquery)?w(g):w.event,v=w.Deferred(),m=w.Callbacks("once memory"),x=h.statusCode||{},b={},T={},C="canceled",E={readyState:0,getResponseHeader:function(e){var t;if(c){if(!s){s={};while(t=Ot.exec(a))s[t[1].toLowerCase()]=t[2]}t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return c?a:null},setRequestHeader:function(e,t){return null==c&&(e=T[e.toLowerCase()]=T[e.toLowerCase()]||e,b[e]=t),this},overrideMimeType:function(e){return null==c&&(h.mimeType=e),this},statusCode:function(e){var t;if(e)if(c)E.always(e[E.status]);else for(t in e)x[t]=[x[t],e[t]];return this},abort:function(e){var t=e||C;return i&&i.abort(t),k(0,t),this}};if(v.promise(E),h.url=((t||h.url||Ct.href)+"").replace(Rt,Ct.protocol+"//"),h.type=n.method||n.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(M)||[""],null==h.crossDomain){l=r.createElement("a");try{l.href=h.url,l.href=l.href,h.crossDomain=Bt.protocol+"//"+Bt.host!=l.protocol+"//"+l.host}catch(e){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=w.param(h.data,h.traditional)),_t(It,h,n,E),c)return E;(f=w.event&&h.global)&&0==w.active++&&w.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!Mt.test(h.type),o=h.url.replace(Lt,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(qt,"+")):(d=h.url.slice(o.length),h.data&&(h.processData||"string"==typeof h.data)&&(o+=(kt.test(o)?"&":"?")+h.data,delete h.data),!1===h.cache&&(o=o.replace(Ht,"$1"),d=(kt.test(o)?"&":"?")+"_="+Et+++d),h.url=o+d),h.ifModified&&(w.lastModified[o]&&E.setRequestHeader("If-Modified-Since",w.lastModified[o]),w.etag[o]&&E.setRequestHeader("If-None-Match",w.etag[o])),(h.data&&h.hasContent&&!1!==h.contentType||n.contentType)&&E.setRequestHeader("Content-Type",h.contentType),E.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+$t+"; q=0.01":""):h.accepts["*"]);for(p in h.headers)E.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(g,E,h)||c))return E.abort();if(C="abort",m.add(h.complete),E.done(h.success),E.fail(h.error),i=_t(Wt,h,n,E)){if(E.readyState=1,f&&y.trigger("ajaxSend",[E,h]),c)return E;h.async&&h.timeout>0&&(u=e.setTimeout(function(){E.abort("timeout")},h.timeout));try{c=!1,i.send(b,k)}catch(e){if(c)throw e;k(-1,e)}}else k(-1,"No Transport");function k(t,n,r,s){var l,p,d,b,T,C=n;c||(c=!0,u&&e.clearTimeout(u),i=void 0,a=s||"",E.readyState=t>0?4:0,l=t>=200&&t<300||304===t,r&&(b=Xt(h,E,r)),b=Ut(h,b,E,l),l?(h.ifModified&&((T=E.getResponseHeader("Last-Modified"))&&(w.lastModified[o]=T),(T=E.getResponseHeader("etag"))&&(w.etag[o]=T)),204===t||"HEAD"===h.type?C="nocontent":304===t?C="notmodified":(C=b.state,p=b.data,l=!(d=b.error))):(d=C,!t&&C||(C="error",t<0&&(t=0))),E.status=t,E.statusText=(n||C)+"",l?v.resolveWith(g,[p,C,E]):v.rejectWith(g,[E,C,d]),E.statusCode(x),x=void 0,f&&y.trigger(l?"ajaxSuccess":"ajaxError",[E,h,l?p:d]),m.fireWith(g,[E,C]),f&&(y.trigger("ajaxComplete",[E,h]),--w.active||w.event.trigger("ajaxStop")))}return E},getJSON:function(e,t,n){return w.get(e,t,n,"json")},getScript:function(e,t){return w.get(e,void 0,t,"script")}}),w.each(["get","post"],function(e,t){w[t]=function(e,n,r,i){return g(n)&&(i=i||r,r=n,n=void 0),w.ajax(w.extend({url:e,type:t,dataType:i,data:n,success:r},w.isPlainObject(e)&&e))}}),w._evalUrl=function(e){return w.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},w.fn.extend({wrapAll:function(e){var t;return this[0]&&(g(e)&&(e=e.call(this[0])),t=w(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(e){return g(e)?this.each(function(t){w(this).wrapInner(e.call(this,t))}):this.each(function(){var t=w(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=g(e);return this.each(function(n){w(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(e){return this.parent(e).not("body").each(function(){w(this).replaceWith(this.childNodes)}),this}}),w.expr.pseudos.hidden=function(e){return!w.expr.pseudos.visible(e)},w.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},w.ajaxSettings.xhr=function(){try{return new e.XMLHttpRequest}catch(e){}};var Vt={0:200,1223:204},Gt=w.ajaxSettings.xhr();h.cors=!!Gt&&"withCredentials"in Gt,h.ajax=Gt=!!Gt,w.ajaxTransport(function(t){var n,r;if(h.cors||Gt&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");for(a in i)s.setRequestHeader(a,i[a]);n=function(e){return function(){n&&(n=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===e?s.abort():"error"===e?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Vt[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=n(),r=s.onerror=s.ontimeout=n("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&e.setTimeout(function(){n&&r()})},n=n("abort");try{s.send(t.hasContent&&t.data||null)}catch(e){if(n)throw e}},abort:function(){n&&n()}}}),w.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),w.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return w.globalEval(e),e}}}),w.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),w.ajaxTransport("script",function(e){if(e.crossDomain){var t,n;return{send:function(i,o){t=w(" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+
+ +
+

Category Stories

+
+ + + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ The buddhist pace + +

+

This is a story I read years ago from Reddit that has guided me through most parts of my life and whenever I want to engage in more that I...

+
+ +
+
+ + + + + + + +
+

Category Philosophy

+
+ + + + + + + +
+
+ +
+

+ The buddhist pace + +

+

This is a story I read years ago from Reddit that has guided me through most parts of my life and whenever I want to engage in more that I...

+
+ +
+
+ + + + + + + +
+

Category Deep Learning

+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + + + + +
+

Category Reinforcement Learning

+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + + + + +
+

Category Supervised Learning

+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + + + + +
+

Category Happy Ideas

+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + + + + +
+

Category GAN

+
+ + + + + + + +
+
+ +
+

+ GAN convergence proof + +

+

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ GAN optimality proof + +

+

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that...

+
+ +
+
+ + + + + + + +
+

Category Theory

+
+ + + + + + + +
+
+ +
+

+ GAN convergence proof + +

+

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ GAN optimality proof + +

+

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that...

+
+ +
+
+ + + + + + + +
+

Category unsupervised learning

+
+ + + + + + + +
+
+ +
+

+ GAN convergence proof + +

+

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ GAN optimality proof + +

+

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that...

+
+ +
+
+ + + + + + + +
+

Category Tutorial

+
+ + + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Headless installation of Ubuntu on Rasbperry + +

+

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Stable Diffusion Tutorial (Deprecated) + +

+

Three days ago Stable Diffusion was publicly released and today I am bringing to you an easy way of using the model without the need of having any kind of...

+
+ +
+
+ + + + + + + +
+

Category Servers

+
+ + + + + + + +
+
+ +
+

+ Headless installation of Ubuntu on Rasbperry + +

+

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena...

+
+ +
+
+ + + + + + + +
+

Category Raspberry Pi

+
+ + + + + + + +
+
+ +
+

+ Headless installation of Ubuntu on Rasbperry + +

+

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena...

+
+ +
+
+ + + + + + + +
+

Category Hidden Markov Model

+
+ + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models (Part 3) + +

+

In part I and part II of this series I have talked about what is a Hidden Markov Model, why it is useful for modelling a padel match, and how...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models (Part 2) + +

+

In my last post I explained the usefulness of Hidden Markov Models for predicting the outcome of a padel match with only a few observations. There I also showed how...

+
+ +
+
+ + + + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models + +

+

Imagine you are at a padel match. You watch the ball go from one side to the other, hit the wall, hit the ground, the net, and after some time...

+
+ +
+
+ + + + + + + +
+

Category Django

+
+ + + + + + + +
+
+ +
+

+ Deploying AWS webapp tutorial + +

+

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. I will...

+
+ +
+
+ + + + + + + +
+

Category Docker

+
+ + + + + + + +
+
+ +
+

+ Deploying AWS webapp tutorial + +

+

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. I will...

+
+ +
+
+ + + + + + + +
+

Category AWS

+
+ + + + + + + +
+
+ +
+

+ Deploying AWS webapp tutorial + +

+

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. I will...

+
+ +
+
+ + + + + + + +
+

Category AI

+
+ + + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + + + + +
+
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/ai/index.html b/category/ai/index.html new file mode 100644 index 0000000..50aa7f4 --- /dev/null +++ b/category/ai/index.html @@ -0,0 +1,339 @@ + + + + + + + + +AI | GranaData + + +AI | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'AI'

+
+
+ + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/aws/index.html b/category/aws/index.html new file mode 100644 index 0000000..e783638 --- /dev/null +++ b/category/aws/index.html @@ -0,0 +1,339 @@ + + + + + + + + +AWS | GranaData + + +AWS | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'AWS'

+
+
+ + + + + + +
+
+ +
+

+ Deploying AWS webapp tutorial + +

+

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. I will...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/deep-learning/index.html b/category/deep-learning/index.html new file mode 100644 index 0000000..5fb913e --- /dev/null +++ b/category/deep-learning/index.html @@ -0,0 +1,437 @@ + + + + + + + + +Deep Learning | GranaData + + +Deep Learning | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Deep Learning'

+
+
+ + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/django/index.html b/category/django/index.html new file mode 100644 index 0000000..4ba84b0 --- /dev/null +++ b/category/django/index.html @@ -0,0 +1,339 @@ + + + + + + + + +Django | GranaData + + +Django | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Django'

+
+
+ + + + + + +
+
+ +
+

+ Deploying AWS webapp tutorial + +

+

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. I will...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/docker/index.html b/category/docker/index.html new file mode 100644 index 0000000..6fbef22 --- /dev/null +++ b/category/docker/index.html @@ -0,0 +1,339 @@ + + + + + + + + +Docker | GranaData + + +Docker | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Docker'

+
+
+ + + + + + +
+
+ +
+

+ Deploying AWS webapp tutorial + +

+

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. I will...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/gan/index.html b/category/gan/index.html new file mode 100644 index 0000000..9cf2f10 --- /dev/null +++ b/category/gan/index.html @@ -0,0 +1,388 @@ + + + + + + + + +GAN | GranaData + + +GAN | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'GAN'

+
+
+ + + + + + +
+
+ +
+

+ GAN convergence proof + +

+

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ GAN optimality proof + +

+

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/happy-ideas/index.html b/category/happy-ideas/index.html new file mode 100644 index 0000000..ec892ed --- /dev/null +++ b/category/happy-ideas/index.html @@ -0,0 +1,437 @@ + + + + + + + + +Happy Ideas | GranaData + + +Happy Ideas | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Happy Ideas'

+
+
+ + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/hidden-markov-model/index.html b/category/hidden-markov-model/index.html new file mode 100644 index 0000000..2e03099 --- /dev/null +++ b/category/hidden-markov-model/index.html @@ -0,0 +1,437 @@ + + + + + + + + +Hidden Markov Model | GranaData + + +Hidden Markov Model | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Hidden Markov Model'

+
+
+ + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models (Part 3) + +

+

In part I and part II of this series I have talked about what is a Hidden Markov Model, why it is useful for modelling a padel match, and how...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models (Part 2) + +

+

In my last post I explained the usefulness of Hidden Markov Models for predicting the outcome of a padel match with only a few observations. There I also showed how...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models + +

+

Imagine you are at a padel match. You watch the ball go from one side to the other, hit the wall, hit the ground, the net, and after some time...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/philosophy/index.html b/category/philosophy/index.html new file mode 100644 index 0000000..ab25c78 --- /dev/null +++ b/category/philosophy/index.html @@ -0,0 +1,339 @@ + + + + + + + + +Philosophy | GranaData + + +Philosophy | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Philosophy'

+
+
+ + + + + + +
+
+ +
+

+ The buddhist pace + +

+

This is a story I read years ago from Reddit that has guided me through most parts of my life and whenever I want to engage in more that I...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/raspberry-pi/index.html b/category/raspberry-pi/index.html new file mode 100644 index 0000000..8d049fc --- /dev/null +++ b/category/raspberry-pi/index.html @@ -0,0 +1,339 @@ + + + + + + + + +Raspberry Pi | GranaData + + +Raspberry Pi | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Raspberry Pi'

+
+
+ + + + + + +
+
+ +
+

+ Headless installation of Ubuntu on Rasbperry + +

+

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/reinforcement-learning/index.html b/category/reinforcement-learning/index.html new file mode 100644 index 0000000..e9fdaec --- /dev/null +++ b/category/reinforcement-learning/index.html @@ -0,0 +1,437 @@ + + + + + + + + +Reinforcement Learning | GranaData + + +Reinforcement Learning | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Reinforcement Learning'

+
+
+ + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/servers/index.html b/category/servers/index.html new file mode 100644 index 0000000..d33b6af --- /dev/null +++ b/category/servers/index.html @@ -0,0 +1,339 @@ + + + + + + + + +Servers | GranaData + + +Servers | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Servers'

+
+
+ + + + + + +
+
+ +
+

+ Headless installation of Ubuntu on Rasbperry + +

+

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/stories/index.html b/category/stories/index.html new file mode 100644 index 0000000..f61e159 --- /dev/null +++ b/category/stories/index.html @@ -0,0 +1,388 @@ + + + + + + + + +Stories | GranaData + + +Stories | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Stories'

+
+
+ + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ The buddhist pace + +

+

This is a story I read years ago from Reddit that has guided me through most parts of my life and whenever I want to engage in more that I...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/supervised-learning/index.html b/category/supervised-learning/index.html new file mode 100644 index 0000000..8171348 --- /dev/null +++ b/category/supervised-learning/index.html @@ -0,0 +1,437 @@ + + + + + + + + +Supervised Learning | GranaData + + +Supervised Learning | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Supervised Learning'

+
+
+ + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/theory/index.html b/category/theory/index.html new file mode 100644 index 0000000..d747bf6 --- /dev/null +++ b/category/theory/index.html @@ -0,0 +1,388 @@ + + + + + + + + +Theory | GranaData + + +Theory | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Theory'

+
+
+ + + + + + +
+
+ +
+

+ GAN convergence proof + +

+

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ GAN optimality proof + +

+

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/tutorial/index.html b/category/tutorial/index.html new file mode 100644 index 0000000..e0ffe1b --- /dev/null +++ b/category/tutorial/index.html @@ -0,0 +1,437 @@ + + + + + + + + +Tutorial | GranaData + + +Tutorial | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'Tutorial'

+
+
+ + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Headless installation of Ubuntu on Rasbperry + +

+

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Stable Diffusion Tutorial (Deprecated) + +

+

Three days ago Stable Diffusion was publicly released and today I am bringing to you an easy way of using the model without the need of having any kind of...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/category/unsupervised-learning/index.html b/category/unsupervised-learning/index.html new file mode 100644 index 0000000..44d95f8 --- /dev/null +++ b/category/unsupervised-learning/index.html @@ -0,0 +1,388 @@ + + + + + + + + +unsupervised learning | GranaData + + +unsupervised learning | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+
+

Archive of posts with category 'unsupervised learning'

+
+
+ + + + + + +
+
+ +
+

+ GAN convergence proof + +

+

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ GAN optimality proof + +

+

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that...

+
+ +
+
+ + + + +
+
+ +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6feb362 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +jekyll: + image: jekyll/jekyll:latest + command: jekyll serve --force_polling + ports: + - 4000:4000 + volumes: + - .:/srv/jekyll \ No newline at end of file diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..d572e7c --- /dev/null +++ b/feed.xml @@ -0,0 +1,2331 @@ + + + + GranaData + Data science from Granada to the world. + https://granadata.art/ + + Sun, 15 Oct 2023 14:01:06 +0000 + Sun, 15 Oct 2023 14:01:06 +0000 + Jekyll v4.3.2 + + + How to make a full audiobook with AI tools. + <p>A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you will see, it played out decently and that’s the reason for this tutorial. Since there is not yet a full consensus on the copyright of this material, I won’t be posting the full audiobook just in case. But if you follow this tutorial you may end up with a similar copy in your hands. The process is quite simple:</p> + +<ol> + <li>Ask GPT-4 to structure a book around a given topic, which in my case was “animals in Africa”.</li> + <li>Create the figures using DALLE·3.</li> + <li>Give texture to letters using Adobe text effects.</li> + <li>Compose letters and images in powerpoint.</li> + <li>Generate the audio using Tortoise.</li> + <li>Edit video and audio using Davinci Resolve.</li> +</ol> + +<p>The objective is to reduce the human in the process as much as possible, although some careful editing and reviewing will always be neccesary. Let’s dive into the details.</p> + +<h1 id="gpt-4">GPT-4</h1> + +<p>One would think that a lot of prompt engineering is needed to make a full book, even if it is for children, right? Well, no. The prompt I used was simply this:</p> + +<blockquote> + <p>hey, i want to create a child book for my nephew, he loves animal from africa, like giraffes and lions and pumas. Would you like to help me build a narrative together with the proper animations?</p> +</blockquote> + +<p>Yeah, that’s it! Even with caligraphic errors GPT-4 understood the task and gave me this in response:</p> + +<blockquote> + <h3 id="narrative">Narrative:</h3> + + <p><strong>Title</strong>: “The Great African Adventure”</p> + + <p><strong>1. Introduction</strong>: +In a land far away, where the golden savannah stretches as far as the eye can see, three unlikely friends embark on an unforgettable journey. Meet Gigi the Giraffe, Leo the Lion, and Pippa the Puma.</p> + + <p><strong>2. The Lost Waterhole</strong>: +One day, Gigi notices that their favorite waterhole is starting to dry up. The three friends decide to search for a new one, ensuring they have water during the hot summer days.</p> + + <p><strong>3. The Mysterious Map</strong>: +While resting under an acacia tree, Pippa finds a tattered map blowing in the wind. It hints at a hidden waterhole, surrounded by lush greenery and cool shade. They decide to follow it.</p> + + <p><strong>4. Challenges Along the Way</strong>: +As they journey, they face various challenges. They cross rapid rivers, climb steep hills, and navigate through dense forests. Each friend uses their unique abilities to help the group. Gigi uses her long neck to scout ahead, Leo roars to scare away potential threats, and Pippa’s agility helps them find shortcuts.</p> + + <p><strong>5. The Hidden Oasis</strong>: +After days of searching, they stumble upon a hidden oasis, just as the map had shown. It’s more beautiful than they could have imagined, with fresh, clear water and plenty of food.</p> + + <p><strong>6. Conclusion</strong>: +The trio realizes that their journey was about more than just finding water. It was about friendship, teamwork, and the adventures they shared. They decide to keep the map as a reminder of their great African adventure.</p> +</blockquote> + +<blockquote> + <p>Now, let’s create some illustrations to go with our story. I’ll start by creating images for the introduction and the first two events. Shall we?</p> +</blockquote> + +<p>Not bad, but not a book either, this is more like a summary. So, given that summary, I then asked GPT-4 to create the full book, chapter by chapter. This was my prompt for the introduction:</p> + +<blockquote> + <p>Okay, let’s be more sistematic. We have 6 chapters. Let’s start with the introduction. Write me a simple story that would fit in 5 pages with one sentence in each page. Also, for each page an illustration is needed. The characters must be the same as before: Gigi the Giraffe, Leo the Lion and Pippa the Puma.</p> +</blockquote> + +<p>And the response:</p> + +<blockquote> + <h3 id="introduction-meeting-the-trio">Introduction: Meeting the Trio</h3> + + <p><strong>Page 1</strong>: +<strong>Sentence</strong>: “In the heart of the African savannah, three friends stood tall and proud.” +<strong>Illustration</strong>: A vast savannah landscape with Gigi the Giraffe, Leo the Lion, and Pippa the Puma standing together, the sun setting in the background.</p> + + <p><strong>Page 2</strong>: +<strong>Sentence</strong>: “Gigi, with her long neck, loved to watch the clouds and dream.” +<strong>Illustration</strong>: Gigi gazing upwards at the sky, a dreamy expression on her face, as whimsical cloud shapes float above.</p> + + <p><strong>Page 3</strong>: +<strong>Sentence</strong>: “Leo, with his majestic mane, loved to roar and ensure everyone was safe.” +<strong>Illustration</strong>: Leo standing on a small mound, letting out a powerful roar, with birds taking flight in the background.</p> + + <p><strong>Page 4</strong>: +<strong>Sentence</strong>: “Pippa, always curious, loved to explore hidden paths and trails.” +<strong>Illustration</strong>: Pippa sneaking through some tall grass, her tail flicking with excitement, as she discovers a hidden trail.</p> + + <p><strong>Page 5</strong>: +<strong>Sentence</strong>: “Together, they were ready for any adventure the savannah had in store.” +<strong>Illustration</strong>: The three of them walking side by side, with the vast savannah ahead, signaling the beginning of their journey.</p> +</blockquote> + +<blockquote> + <p>Let’s start by creating the illustrations for these pages. Shall we?</p> +</blockquote> + +<p>And this is the process, you tell GPT-4 how many pages you want per chapter and it expands the summary to fit into that number of pages. Even better, it suggests some illustrations, let’s see how to make them a reality.</p> + +<h1 id="dalle3">DALLE·3</h1> + +<p>A few months ago, to achieve the illustrations one would have to take the prompt suggested by GPT-4 and take it to Midjourney, or Stable Diffusion to generate a valid image. The process required a lot of trial and error and a bit of prompt engineering. Now, however, DALLE·3 is embedded inside GPT-4, so you just have to say “yes” and it will generate the illustrations, like this:</p> + +<div class="image-grid"> + <div class="image-container"> + <img src="/assets/images/audiobook/dalle1-1.png" alt="dalle1_1" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/dalle1-2.png" alt="dalle1_2" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/dalle1-3.png" alt="dalle1_3" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/dalle1-4.png" alt="dalle1_4" /> + </div> +</div> + +<p>Since it is limited to generating four at a time you need to generate them in batches, which is a minor problem since you can just say “continue” and it will do the rest. As you can notice, even though I did not specify any specific style or artist, these images do not seem to be copyright-free. Some of them resemble too much to Disney style or to other cartoons. Technically, OpenAI gives you ownership of this images to do whatever you want, commercial or not. But it is not clear to me that you are really allowed to <em>do whatever you want</em>. The problem is, even if I wanted to credit the authors of this material, or ask for permission to cite, or even request a commercial license, I don’t know <em>who</em> is the author I should be asking permission for. Anyway, the process is as simple as I described here and it is more or less automatic. In many cases the algorithm does not generate what I ask it for, but by repeating the question several times you can arrive to a satisfying image almost always.</p> + +<p>Next step, generating covers for every chapter. This is a bit more challenging since generating letters in images is a quite difficult task, and it fails horribly most of the time. For instance, generating the words “The Hidden Oasis” seems an impossible task, here are some examples of the failure cases:</p> + +<div class="image-grid"> + <div class="image-container"> + <img src="/assets/images/audiobook/fail1.png" alt="fail1" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/fail2.png" alt="fail2" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/fail3.png" alt="fail3" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/fail4.png" alt="fail4" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/fail5.png" alt="fail4" /> + </div> + <div class="image-container"> + <img src="/assets/images/audiobook/fail6.png" alt="fail4" /> + </div> +</div> + +<p>With a lot of trial and error I could manage to get cover for all of the chapters, but re-generating the illustrations to include extra text on them seemed and impossible task. For that reason I decided to edit the text in Adobe Firefly.</p> + +<h1 id="adobe-text-effects">Adobe text effects</h1> + +<p>I could have just used plain colors for the text, but it seemed too boring, so I decided to try this AI tool. By using a prompt like “moon” you could add texture to your letters based on it, although the result is not quite there yet in my opinion:</p> + +<p class="text-center"><img class="" src="/assets/images/audiobook/text1-1.png" alt="text" /></p> + +<p>For every prompt, no matter what I told to the tool it always gave me dark images. I surrendered and decided to apply a gain to the result if the background was also dark. Nonetheless, I used this tool for all the images because I found it funny, look, there is another example:</p> + +<p class="text-center"><img class="" src="/assets/images/audiobook/text2-3.png" alt="text" /></p> + +<p>This one is a bit more special, because I didn’t use any original prompt, I just used the whole sentece provided by GPT-4 “The oasis was a paradise, with crystal-clear water and an abundance of food.”. This way everything is automatic, you use the sentence as prompt to give texture to itself. The biggest limitation is that the tool only allows a few characters at a time, which made the process quite long and exhausting. The most consuming part was realizing that the text was not even useful because the color didn’t match the scene. This is probably the worst part of the book and the part that has the most room for improvement in terms of AI tools.</p> + +<h1 id="tortoise">Tortoise</h1> + +<p>Given the catastrophic failure that the letters were, we need to fix it somehow. A possible solution for an unreadable text is to create an audiobook. Can’t you read the text? No problem, here is somebody reading it for you. Well, more like <em>something</em>. Because we are going to use another AI tool to generate the voices automatically. This tool is not deployed on any pages like the previous tools. If you want to use it you will need to install it in your machine or in colab. In their <a href="https://github.com/neonbjb/tortoise-tts" target="_blank">github repository</a> you have instructions on how to do it. You will need an NVIDIA GPU with CUDA capabilities or a mac with Apple Silicon or a lot of time, you choose. In my case I have a Mac M1 Max and the generation of all the 33 audios took one and a half hour. As an example, the following command generated the audio that is below:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nv">PYTORCH_ENABLE_MPS_FALLBACK</span><span class="o">=</span>1 python tortoise/do_tts.py <span class="nt">--text</span> <span class="s2">"</span><span class="se">\[</span><span class="s2">I am really excited,</span><span class="se">\]</span><span class="s2"> Let's follow the birds; they'll lead us to water."</span> <span class="nt">--voice</span> train_dreams <span class="nt">--preset</span> fast <span class="nt">--output_path</span> book/chapter2/audio4/ <span class="nt">--candidates</span> 1 <span class="nt">--seed</span> 210501<span class="p">;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<audio controls=""> + <source src="/assets/audio/audiobook/chapter2_audio4_2.mp3" type="audio/mpeg" /> + Your browser does not support the audio element. +</audio> + +<p>The quality is decent. Its main limitation is the generation of emotions. As you can see in the example above, it is possible to trick the algorithm into expressing emotions, but there is much room for improvement and you need to specify which emotion you want. You could probably use GPT-4 to automatically identify which emotions to give, but for this side project I didn’t want to overcomplicate things.</p> + +<h1 id="davinci-resolve">Davinci Resolve</h1> + +<p>Finally, let’s put everything together and generate the audiobook. We have images, text and audio. So, we can create a video. In my case I decided to compose images and text in powerpoint and then use a predefined animation to pass between pages. Then, I put the presentation in automatic mode giving each slide 8 seconds and recorded the screen. After that, I had to manually synchronize audio and video by cutting and moving segments. Here is the timeline before editing:</p> + +<p class="text-center"><img class="" src="/assets/images/audiobook/before.png" alt="before" /></p> + +<p>And here is after editing:</p> + +<p class="text-center"><img class="" src="/assets/images/audiobook/after.png" alt="after" /></p> + +<p>Hit render and you are done.</p> + +<h1 id="final-thoughts">Final thoughts</h1> + +<p>The whole process took me 3 days, but I was experimenting the most part. If this was fully automated it could be done in 1 day. Imagine, a editorial could put a hole office of, let’s say, 20 employees and create 400 books in a month. Even more, if the process gets even more automatic, a child could read a different book each day of its whole life, or at least until this kind of books bore them. Nobody can deny this technology is going to have a huge impact in ours lives. Either positive or negative is still to be seen. For me, this can have a good impact since it will democratize knowledge even more. Not only that, people will be able to create masterpieces, books, films and many more. You will no longer need a huge budget to do this kind of things. Indie creators could compete with huge studios or editorials. It will all depend on what the legislation will say. If future (or present) politicians decide against this technology, that democratization will never arrive. Big fishes will remain big, and monopolies will win. As much as I believe this technology is capable of wonderful creations, I also believe that politicians are capable of awful laws. Time will say.</p> + + Sun, 15 Oct 2023 00:00:00 +0000 + https://granadata.art/gpt4-audiobook/ + https://granadata.art/gpt4-audiobook/ + + AI + + + Stories + + Tutorial + + AI + + + + + Deploying AWS webapp tutorial + <p>Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. +I will even be referencing many of those here. But the main difference with those posts is that mine is going to be straight to the point. +The following is a tutorial on how to create a web app from scratch. The backend will be on Django and the database will be Postgresql. +The app will be running on AWS and to deploy it there we will create a Docker image. Last but not least, I’ll explain how to buy a domain and link the domain to the AWS ip address. Let’s get ours hands dirty! Also, don’t worry about the code, it is all available <a href="https://github.com/Jerry-Master/GranaData/tree/main/_code/django_docker_aws" target="_blank">here</a>.</p> + +<h1 id="django-and-postgresql">Django and Postgresql</h1> + +<p>Let’s start by creating a minimal python environment with just Django. You can do it either via python or conda. For reproducibility, please use python3.10 and Django 4.2.2. Open a terminal and run the following:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre>python3.10 <span class="nt">-m</span> venv .venv +<span class="nb">source</span> .venv/bin/activate <span class="c"># For Windows use: .\.venv\Script\activate</span> +pip <span class="nb">install </span><span class="nv">django</span><span class="o">==</span>4.2.2 psycopg2-binary +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>For the Conda installation:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre>conda create <span class="nt">--name</span> .venv <span class="nv">python</span><span class="o">=</span>3.10 +conda activate .venv +pip <span class="nb">install </span><span class="nv">django</span><span class="o">==</span>4.2.2 psycopg2-binary +</pre></td></tr></tbody></table></code></pre></div></div> + +<h2 id="postgresql-server-setup">Postgresql server setup</h2> + +<p>The next step is to create a prosgresql server. To install postgresql go to the <a href="https://www.postgresql.org/download/" target="_blank">official page</a>. Once installed, you need to start running the server on your system:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="nb">mkdir</span> /usr/local/var/postgres <span class="c"># Create folder if it does not exist</span> +initdb <span class="nt">-D</span> /usr/local/var/postgres <span class="c"># Initialize database cluster</span> +pg_ctl <span class="nt">-D</span> /usr/local/var/postgres start <span class="c"># Start server</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>This will start the server and save everything into the <code class="language-html highlighter-rouge">/usr/local/var/postgres</code> folder. For Window users, replace <code class="language-html highlighter-rouge">pg_ctl</code> and <code class="language-html highlighter-rouge">initdb</code> with the path to the <code class="language-html highlighter-rouge">pg_ctl.exe</code> and <code class="language-html highlighter-rouge">initdb.exe</code> binaries, which may be something similar to <code class="language-html highlighter-rouge">"C:\Program Files\PostgreSQL\14\bin\pg_ctl.exe"</code> and use any data directory you want.</p> + +<p>Once the server is running we need to create a database, for that, you need to run postgres in a terminal and execute the relevant SQL code:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre>psql postgres <span class="c"># Start SQL shell</span> +<span class="nv">postgres</span><span class="o">=</span><span class="c"># CREATE DATABASE mydatabase;</span> +<span class="nv">postgres</span><span class="o">=</span><span class="c"># CREATE USER myuser WITH PASSWORD 'mypassword';</span> +<span class="nv">postgres</span><span class="o">=</span><span class="c"># GRANT ALL PRIVILEGES ON DATABASE mydatabase TO myuser;</span> +<span class="nv">postgres</span><span class="o">=</span><span class="c"># exit;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>The commands are self-explanatory, just replace <code class="language-html highlighter-rouge">mydatabase</code>, <code class="language-html highlighter-rouge">myuser</code> and <code class="language-html highlighter-rouge">mypassword</code> with what you deem appropiate. Now you have the database ready to use locally, you can connect to it using any database management system you want, I recommend <a href="https://dbeaver.io/download/" target="_blank">Dbeaver</a>. The connection is through localhost and port 5432. Later on we will see how to automate this process but for now, this is how it is done locally.</p> + +<h2 id="django-webapp-setup">Django webapp setup</h2> + +<p>Given the database, we need a web on top. With the python environment activated, run the following using any name you want:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>django-admin startproject myprojectname +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>This will create the basic skeleton for a Django project. We now have to configure the database and create a simple app to store data. Go to <code class="language-html highlighter-rouge">settings.py</code> and locate the <code class="highlight language-python" data-lang="python"><span class="n">INSTALLED_APPS</span></code> variable. Append <code class="highlight language-python" data-lang="python"><span class="sh">'</span><span class="s">django.db.backends.postgresql</span><span class="sh">'</span><span class="p">,</span></code> to the list, it should be like this:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span> + <span class="sh">'</span><span class="s">django.contrib.admin</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.auth</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.contenttypes</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.sessions</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.messages</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.staticfiles</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.db.backends.postgresql</span><span class="sh">'</span><span class="p">,</span> +<span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Also locate the <code class="highlight language-python" data-lang="python"><span class="n">DATABASES</span></code> variable and modify it to contain the information necessary to connect to the posgresql database:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="n">DATABASES</span> <span class="o">=</span> <span class="p">{</span> + <span class="sh">'</span><span class="s">default</span><span class="sh">'</span><span class="p">:</span> <span class="p">{</span> + <span class="sh">'</span><span class="s">ENGINE</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">django.db.backends.postgresql</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">NAME</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mydatabase</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">USER</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">myuser</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">PASSWORD</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mypassword</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">HOST</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">localhost</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">PORT</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">5432</span><span class="sh">'</span><span class="p">,</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Since storing passwords in plain text is normally not a good idea, I recommend you use environment variables for that:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="n">DATABASES</span> <span class="o">=</span> <span class="p">{</span> + <span class="sh">'</span><span class="s">default</span><span class="sh">'</span><span class="p">:</span> <span class="p">{</span> + <span class="sh">'</span><span class="s">ENGINE</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">django.db.backends.postgresql</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">NAME</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mydatabase</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">USER</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">myuser</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">PASSWORD</span><span class="sh">'</span><span class="p">:</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">'</span><span class="s">DB_PASSWORD</span><span class="sh">'</span><span class="p">,</span> <span class="sh">''</span><span class="p">),</span> <span class="c1"># Don't forget to import os +</span> <span class="sh">'</span><span class="s">HOST</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">localhost</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">PORT</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">5432</span><span class="sh">'</span><span class="p">,</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>You can now provide the password through environment variables:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="nb">export </span><span class="nv">DB_PASSWORD</span><span class="o">=</span><span class="s1">'mypassword'</span> <span class="c"># Unix</span> +<span class="nb">set </span>DB_PASSWORD <span class="s2">"mypassword"</span> <span class="c"># CMD Windows</span> +<span class="nv">$env</span>:DB_PASSWORD<span class="o">=</span><span class="s2">"mypassword"</span> <span class="c"># PowerShell Windows</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Finally, let’s create a simple page. Start by typing:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>python manage.py startapp simpleapp +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Now, modify <code class="language-html highlighter-rouge">settings.py</code> to include it by adding it to the <code class="highlight language-python" data-lang="python"><span class="n">INSTALLED_APPS</span></code> variable:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span> + <span class="sh">'</span><span class="s">django.contrib.admin</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.auth</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.contenttypes</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.sessions</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.messages</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.contrib.staticfiles</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">django.db.backends.postgresql</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">simpleapp</span><span class="sh">'</span><span class="p">,</span> +<span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>After that, we will add a very simple model and view to handle data. Our model will only contain names of users. Go to <code class="language-html highlighter-rouge">simpleapp/models.py</code> and add this:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">models</span><span class="p">.</span><span class="n">Model</span><span class="p">):</span> + <span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="p">.</span><span class="nc">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">50</span><span class="p">)</span> + + <span class="k">def</span> <span class="nf">__str__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span> + <span class="k">return</span> <span class="n">self</span><span class="p">.</span><span class="n">name</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Then, create <code class="language-html highlighter-rouge">simpleapp/forms.py</code> and add this:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">django</span> <span class="kn">import</span> <span class="n">forms</span> +<span class="kn">from</span> <span class="n">.models</span> <span class="kn">import</span> <span class="n">User</span> + +<span class="k">class</span> <span class="nc">UserForm</span><span class="p">(</span><span class="n">forms</span><span class="p">.</span><span class="n">ModelForm</span><span class="p">):</span> + <span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span> + <span class="n">model</span> <span class="o">=</span> <span class="n">User</span> + <span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">name</span><span class="sh">'</span><span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Next, modify <code class="language-html highlighter-rouge">simpleapp/views.py</code> to include the following:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">django.views.generic.edit</span> <span class="kn">import</span> <span class="n">CreateView</span> +<span class="kn">from</span> <span class="n">.models</span> <span class="kn">import</span> <span class="n">User</span> +<span class="kn">from</span> <span class="n">.forms</span> <span class="kn">import</span> <span class="n">UserForm</span> + +<span class="k">class</span> <span class="nc">UserCreateView</span><span class="p">(</span><span class="n">CreateView</span><span class="p">):</span> + <span class="n">model</span> <span class="o">=</span> <span class="n">User</span> + <span class="n">form_class</span> <span class="o">=</span> <span class="n">UserForm</span> + <span class="n">template_name</span> <span class="o">=</span> <span class="sh">'</span><span class="s">user_form.html</span><span class="sh">'</span> + <span class="n">success_url</span> <span class="o">=</span> <span class="sh">'</span><span class="s">/create_user +</span></pre></td></tr></tbody></table></code></pre></div></div> + +<p>You also need to create the template under a templates folder, create <code class="language-html highlighter-rouge">simpleapp/templates/user_form.html</code> and insert this:</p> + +<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre><span class="nt">&lt;form</span> <span class="na">method=</span><span class="s">"post"</span><span class="nt">&gt;</span> + {% csrf_token %} + {{ form.as_p }} + <span class="nt">&lt;button</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">&gt;</span>Create<span class="nt">&lt;/button&gt;</span> +<span class="nt">&lt;/form&gt;</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Last, we need to link the urls for everything to work properly. Create the file <code class="language-html highlighter-rouge">simpleapp/urls.py</code> and write:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">django.urls</span> <span class="kn">import</span> <span class="n">path</span> +<span class="kn">from</span> <span class="n">.views</span> <span class="kn">import</span> <span class="n">UserCreateView</span> + +<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span> + <span class="nf">path</span><span class="p">(</span><span class="sh">'</span><span class="s">create_user/</span><span class="sh">'</span><span class="p">,</span> <span class="n">UserCreateView</span><span class="p">.</span><span class="nf">as_view</span><span class="p">(),</span> <span class="n">name</span><span class="o">=</span><span class="sh">'</span><span class="s">create_user</span><span class="sh">'</span><span class="p">),</span> +<span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Go to the main <code class="language-html highlighter-rouge">myprojectname/urls.py</code> and edit it to be like this:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span> +<span class="kn">from</span> <span class="n">django.urls</span> <span class="kn">import</span> <span class="n">path</span> +<span class="kn">from</span> <span class="n">django.urls</span> <span class="kn">import</span> <span class="n">include</span> + +<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span> + <span class="nf">path</span><span class="p">(</span><span class="sh">'</span><span class="s">admin/</span><span class="sh">'</span><span class="p">,</span> <span class="n">admin</span><span class="p">.</span><span class="n">site</span><span class="p">.</span><span class="n">urls</span><span class="p">),</span> + <span class="nf">path</span><span class="p">(</span><span class="sh">'</span><span class="s">simpleapp/</span><span class="sh">'</span><span class="p">,</span> <span class="nf">include</span><span class="p">(</span><span class="sh">'</span><span class="s">simpleapp.urls</span><span class="sh">'</span><span class="p">)),</span> +<span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Now you are good to go. We can finally start adding rows to the database. For doing so, apply migrations and start the webapp:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre>python manage.py makemigrations +python manage.py migrate +python manage.py runserver +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Open a browser, go to <code class="language-html highlighter-rouge">http://127.0.0.1:8000/simpleapp/create_user/</code> and you will be able to input users’ names. If it is your first time using Django, this is a whole lot, I know. This is a simple example using Django’s class-based views. Things can get very, very complex. The aim of this tutorial is to set up a minimal working webapp. For more information on Django, you can go to their <a href="https://docs.djangoproject.com/en/4.2/" target="_blank">official documentation</a>. Okay, close everything and let’s start our Docker journey. To stop the webapp run <code class="language-html highlighter-rouge">ctrl+C</code> and to stop the posgresql server run:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>pg_ctl <span class="nt">-D</span> /usr/local/var/postgres stop +</pre></td></tr></tbody></table></code></pre></div></div> + +<h1 id="docker">Docker</h1> + +<p>Setting everything from scratch is time consuming but if you only need to do it once, it is affordable. The problem comes when you want to migrate to other machines or you want to scale. Having to go through all the process above everytime is annoying. As I mentioned before, it would be nice to automate it. That’s when Docker comes into play. It is a way to pack everything up so that it can run on your machine, the cloud or a microwave, if it has Docker installed, of course. A Docker is basically made of a few configuration files that are used to construct an image that does whatever you want, in our case handle data through a web app. Having introduced the concept, let’s build a Docker for our web app.</p> + +<p>This section is mostly inspired by <a href="https://learndjango.com/tutorials/django-docker-and-postgresql-tutorial" target="_blank">this other tutorial</a>. Hand over there if you feel curious. Also, I recommend you giving a look at the <a href="https://docker-curriculum.com/" target="_blank">official Docker beginner tutorial</a> for more information on how to set up Docker and the basics of it.</p> + +<p>To start, create a Dockerfile inside of the Django project directory. Specify the following information on it:</p> + +<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="k">FROM</span><span class="s"> python:3.10.2-slim-bullseye</span> + +<span class="k">WORKDIR</span><span class="s"> /code</span> + +<span class="k">COPY</span><span class="s"> . .</span> +<span class="k">RUN </span>pip <span class="nb">install</span> <span class="nt">-r</span> requirements.txt +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>You also need to create a <code class="language-html highlighter-rouge">requirements.txt</code> with this:</p> + +<pre><code class="language-txt">django==4.2.2 +psycopg2-binary +</code></pre> + +<p>You could simply install it with pip, but when the project grows you will be thankfull to have it all in a <code class="language-html highlighter-rouge">requirements.txt</code> file. So, that’s the container of the webapp. Simple, right? However, we still need to connect it to a posgres database. For that we need to use docker compose to run another container with the database and connect them. For that, create a <code class="language-html highlighter-rouge">docker-compose.yml</code> file with the following:</p> + +<pre><code class="language-txt">version: "3" + +services: + web: + build: . + command: sh start.sh + environment: + - DB_PASSWORD + volumes: + - .:/code + ports: + - 8000:8000 + depends_on: + db: + condition: service_healthy + db: + image: postgres:14 + restart: always + ports: + - 5432:5432 + environment: + POSTGRES_DB: mydatabase + POSTGRES_USER: myuser + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - ./postgres_data:/var/lib/postgresql/data/ + healthcheck: + test: pg_isready -U myuser -d mydatabase + interval: 1s + timeout: 10s + retries: 10 + start_period: 30s +</code></pre> + +<p>You will need to substitute <code class="language-html highlighter-rouge">myuser</code> and <code class="language-html highlighter-rouge">mydatabase</code> with what you like most. To explain a bit what is happening here, we are running a postgres container, then performing healthy checks to be sure the database is running and after that we launch the webapp container. You could provide the password also there, but for security reasons is better to provide it through an environment variable, just like before. The database is stored in the folder <code class="language-html highlighter-rouge">postgres_data</code> locally, so that whenever you kill the container you don’t lose the data. The port 5432 is forwarded locally so you can connect to the database from your machine when the container is running and see the data.</p> + +<p>Wait! We have not finished yet. We need to create the <code class="language-html highlighter-rouge">start.sh</code> and modify the <code class="language-html highlighter-rouge">myprojectname/settings.py</code> file. The <code class="language-html highlighter-rouge">DATABASES</code> variable should look like this:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="n">DATABASES</span> <span class="o">=</span> <span class="p">{</span> + <span class="sh">'</span><span class="s">default</span><span class="sh">'</span><span class="p">:</span> <span class="p">{</span> + <span class="sh">'</span><span class="s">ENGINE</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">django.db.backends.postgresql</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">NAME</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">mydatabase</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">USER</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">myuser</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">PASSWORD</span><span class="sh">'</span><span class="p">:</span> <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="sh">'</span><span class="s">DB_PASSWORD</span><span class="sh">'</span><span class="p">,</span> <span class="sh">''</span><span class="p">),</span> + <span class="sh">'</span><span class="s">HOST</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">db</span><span class="sh">'</span><span class="p">,</span> + <span class="sh">'</span><span class="s">PORT</span><span class="sh">'</span><span class="p">:</span> <span class="sh">'</span><span class="s">5432</span><span class="sh">'</span><span class="p">,</span> + <span class="p">}</span> +<span class="p">}</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>The only change is on <code class="language-html highlighter-rouge">HOST</code>. It is now set up to <code class="language-html highlighter-rouge">db</code> which is the name of the posgres container. And the <code class="language-html highlighter-rouge">start.sh</code> script is the following:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="n">python</span> <span class="n">manage</span><span class="p">.</span><span class="n">py</span> <span class="n">makemigrations</span> +<span class="n">python</span> <span class="n">manage</span><span class="p">.</span><span class="n">py</span> <span class="n">migrate</span> +<span class="n">python</span> <span class="n">manage</span><span class="p">.</span><span class="n">py</span> <span class="n">runserver</span> <span class="mf">0.0</span><span class="p">.</span><span class="mf">0.0</span><span class="p">:</span><span class="mi">8000</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>It is creating all the migrations needed to set up all the models from Django into postgres. Finally, just run the container:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>docker compose up +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>To stop it, do <code class="language-html highlighter-rouge">ctrl+C</code> and then <code class="language-html highlighter-rouge">docker compose rm</code>. That’s it, that’s all you need to do to restart your webapp from anywhere. You can now take your code to any machine and you won’t need to set up posgres, python and django from scratch. Just install docker and run <code class="language-html highlighter-rouge">docker compose up</code>. Also, it is a good practice to include a <code class="language-html highlighter-rouge">.dockerignore</code>, just like <code class="language-html highlighter-rouge">.gitignore</code>. For this simple app I have</p> + +<pre><code class="language-txt">postgres_data/ +Dockerfile +docker-compose.yml +*/__pycache__/ +*/*/__pycache__/ +*/*/*/__pycache__/ +</code></pre> + +<p>This way I don’t load any unnecessary files to the container, making it faster. So now that we have everything packed up in our bag, let’s travel. Let’s deploy the web to AWS for others to use it.</p> + +<h1 id="aws">AWS</h1> + +<p>Amazon Web Services are a way to deploy your code into servers that you don’t need to manage. This way instead of the cost of setting up a whole server, you just pay for the hours used. Nevertheless, you won’t save yourself the cost of configuring everything. Even though configuring AWS may be simpler than configuring a server, it is still an important investment of time. For that, I will provide here the bare minimum to make our webapp work on AWS. You will still need to read the AWS docs extensively, there are many tutorials online, but Amazon keeps changing the interface every so often. The only web that is for sure updated is the <a href="https://docs.aws.amazon.com/" target="_blank">AWS official docs page</a>.</p> + +<h2 id="account-and-iam-roles">Account and IAM roles</h2> + +<p>Before we can start configuring the server, we need to configure an account. For that you will need access keys. You can create access keys for your root account but it is not recommended. AWS recommends that you create role with less permissions than your root account (specially without billing permissions) and to use those access keys. In the past this was made using the Identity and Access Management (IAM) app. Now, it is being migrated to IAM Identity Center. Both methods still work as of this writing but I will explain the second one which is more updated. The following is a reduced version of <a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/getting-started.html" target="_blank">this tutorial</a>. Go to the <a href="https://console.aws.amazon.com/" target="_blank">AWS console</a>. There look for the IAM Identity Center. Once on the IAM Identity Center you will need to create an user, create a permission set and link both. In the section User click to Add User and fill the neccesary information. Then, on permission sets, click on Create permission set and create the predefined role AdministratorAccess. After that, go to AWS accounts, select the account under root and click Assign users or groups. Select your created user, click next, select the role, click next, review it and click submit. Finally, to activate that user, you must open the mail you provided and register that user with some password. Before you continue, go to Dashboard and save your AWS access portal URL, that is the URL you need to use to log in with that user. Now, click that URL and sign in. Once you are logged in you should see your user and two links at the right: one for Management console and one for Command line or programatic access. Click the latter and you will see your access keys.</p> + +<p>The next step is to install and configure the AWS CLI. Go <a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" target="_blank">here</a> and follow the steps for the installation. Once installed execute <code class="language-html highlighter-rouge">aws configure</code> and provide the access key and secret access key obtained previously. It will also ask for a region, I will be using <code class="language-html highlighter-rouge">us-east-1</code>. If you choose a different one you may encounter problems later on because the free tiers differ across regions. And for the output format choose <code class="language-html highlighter-rouge">json</code>. You are now (almost) ready to start launching instances.</p> + +<h2 id="ec2-instances">EC2 Instances</h2> + +<p>Having created our account it is time to create an instance where to deploy our webapp. If your page gets too large you may be interested in storing the database in S3 buckets, but for now I will store code and data in the same instance. You can find the docs for EC2 <a href="https://docs.aws.amazon.com/ec2/index.html" target="_blank">here</a>. As before, I will summarize it to just use what we need. First, create the instance. For that go to the <a href="https://us-east-1.console.aws.amazon.com/ec2" target="_blank">EC2 console</a>. Under Instances section, click Launch instances. There give it a name, select the OS and arch (I recommend Ubuntu and x86-64), select the instance type (I will be using t2.micro cuase it is free), select a key-pair or create since you probably don’t have one and leave everything as default. Once you launched it you can now access your machine through ssh. In the instances section, you can click on your created instance and then click on Connect and it will give you instructions on how to connect. The next steps are to install Docker, copy your webapp to the instance, change the firewall of the instance to allow http and postgres traffic and finally deploy the app.</p> + +<h3 id="installing-docker-again">Installing Docker (again)</h3> + +<p>If you have chosen Ubuntu as your OS you can follow the instructions <a href="https://docs.docker.com/engine/install/ubuntu/" target="_blank">here</a>. You just basically need to execute the following commands after accessing the machine:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>apt-get update <span class="nt">-y</span> +<span class="nb">sudo </span>apt-get <span class="nb">install </span>ca-certificates curl gnupg <span class="nt">-y</span> +<span class="nb">sudo install</span> <span class="nt">-m</span> 0755 <span class="nt">-d</span> /etc/apt/keyrings +curl <span class="nt">-fsSL</span> https://download.docker.com/linux/ubuntu/gpg | <span class="nb">sudo </span>gpg <span class="nt">--dearmor</span> <span class="nt">-o</span> /etc/apt/keyrings/docker.gpg +<span class="nb">sudo chmod </span>a+r /etc/apt/keyrings/docker.gpg +<span class="nb">echo</span> <span class="se">\</span> + <span class="s2">"deb [arch="</span><span class="si">$(</span>dpkg <span class="nt">--print-architecture</span><span class="si">)</span><span class="s2">" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu </span><span class="se">\</span><span class="s2"> + "</span><span class="si">$(</span><span class="nb">.</span> /etc/os-release <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$VERSION_CODENAME</span><span class="s2">"</span><span class="si">)</span><span class="s2">" stable"</span> | <span class="se">\</span> + <span class="nb">sudo tee</span> /etc/apt/sources.list.d/docker.list <span class="o">&gt;</span> /dev/null +<span class="nb">sudo </span>apt-get update <span class="nt">-y</span> +<span class="nb">sudo </span>apt-get <span class="nb">install </span>docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin <span class="nt">-y</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>And if you want to check that everything is working just do</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span>docker run hello-world +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="upload-your-code-to-ec2">Upload your code to EC2</h3> + +<p>You can copy your entire directory recursively into you EC2 instance with scp:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre>scp <span class="nt">-r</span> <span class="nt">-i</span> YOUR_KEY ./<span class="k">*</span> ubuntu@YOUR_EC2_ADDRESS:. +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><code class="language-html highlighter-rouge">YOUR_KEY</code> is the key pair previously created and <code class="language-html highlighter-rouge">YOUR_EC2_ADDRESS</code> may look something like <code class="language-html highlighter-rouge">ec2-30-29-46-221.compute-1.amazonaws.com</code>. It is the same address that you use to ssh into your machine.</p> + +<h3 id="changing-the-firewall">Changing the firewall</h3> + +<p>In AWS, the instances have some security rules that control the inbound and outbound traffic of your app. By default all the ports are closed except for 22 which is the ssh port. We will need to open the port 80 and 5432 since they are the http and postgres ports. If you have an SSL certificate you could open the port 443 for https, but we will just use those two for now. Let’s go back to the <a href="https://us-east-1.console.aws.amazon.com/ec2" target="_blank">EC2 console</a> and go to Security Groups. Click on Create security group. Give it a name and add two inbound rules. You can just select HTTP and Postgresql in the dropdown menu and it will set the port for you automatically. Then, on source, select Anywhere IPv4 and click Create security group. Now go back to your created instance and click Actions &gt; Security &gt; Change security groups. There simply add the newly created security group and you are free to go.</p> + +<h2 id="deploy-the-app">Deploy the app</h2> + +<p>In order to deploy our app we need to make one change to our <code class="language-html highlighter-rouge">docker-compose.yml</code>. Initially we were redirecting port 8000 into 8000, we are now going to redirect it to 80 which the http port. The line to change will end up like this</p> + +<pre><code class="language-txt">ports: + - 80:8000 +</code></pre> + +<p>Finally, ssh into your machine with docker installed and execute</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span><span class="nv">DB_PASSWORD</span><span class="o">=</span>... docker compose up <span class="nt">-d</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Remember that you have to specify the password of the database as a environment variable. Okay, the app is running but, how can we access it? Well, we cannot. We still need to make some changes. Stop the container and let’s finish this:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span><span class="nv">DB_PASSWORD</span><span class="o">=</span>... docker compose down +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>The first thing to know is what is the IP that we can use to access this page. In the AWS console, when you enter your instance it displays somewhere “Public IPv4 address”. That is the IP of your app. However, if you were to enter there, Django will not let you in. That is because you need to allow that host. For that, go the <code class="language-html highlighter-rouge">setting.py</code> of your app and add it:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="n">ALLOWED_HOSTS</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">YOUR_IP</span><span class="sh">'</span><span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Also, even after changing this, when you access your ip you don’t see the page. That is because the base url is not pointing anywhere, but we can fix that. Create a view that only has the redirection:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">django.shortcuts</span> <span class="kn">import</span> <span class="n">redirect</span> + +<span class="k">def</span> <span class="nf">redirect_to_create_user</span><span class="p">(</span><span class="n">request</span><span class="p">):</span> + <span class="k">return</span> <span class="nf">redirect</span><span class="p">(</span><span class="sh">'</span><span class="s">/simpleapp/create_user</span><span class="sh">'</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Then, in your main <code class="language-html highlighter-rouge">urls.py</code> add <code class="language-html highlighter-rouge">path('', redirect_to_create_user)</code>, it will end up like this:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +</pre></td><td class="rouge-code"><pre><span class="kn">from</span> <span class="n">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span> +<span class="kn">from</span> <span class="n">django.urls</span> <span class="kn">import</span> <span class="n">path</span> +<span class="kn">from</span> <span class="n">django.urls</span> <span class="kn">import</span> <span class="n">include</span> +<span class="kn">from</span> <span class="n">.views</span> <span class="kn">import</span> <span class="n">redirect_to_create_user</span> + +<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span> + <span class="nf">path</span><span class="p">(</span><span class="sh">''</span><span class="p">,</span> <span class="n">redirect_to_create_user</span><span class="p">),</span> + <span class="nf">path</span><span class="p">(</span><span class="sh">'</span><span class="s">admin/</span><span class="sh">'</span><span class="p">,</span> <span class="n">admin</span><span class="p">.</span><span class="n">site</span><span class="p">.</span><span class="n">urls</span><span class="p">),</span> + <span class="nf">path</span><span class="p">(</span><span class="sh">'</span><span class="s">simpleapp/</span><span class="sh">'</span><span class="p">,</span> <span class="nf">include</span><span class="p">(</span><span class="sh">'</span><span class="s">simpleapp.urls</span><span class="sh">'</span><span class="p">)),</span> +<span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Now, copy again all the files into your machine and deploy the webapp:</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="nb">sudo </span><span class="nv">DB_PASSWORD</span><span class="o">=</span>... docker compose up <span class="nt">-d</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="accessing-the-database">Accessing the DataBase</h3> + +<p>Let’s see how we can access the server database locally. Open you favourite DB program (mine is DBeaver) and create a new connection. This time you will have to provide an URL instead of localhost. Everything else is the same as when you did it locally. The port is 5432, the user is what you gave it, and the database name is what you name it. If you have configured properly the EC2 security group you could now access your database locally.</p> + +<h1 id="web-domain">Web Domain</h1> + +<p>Nice, we have our fantastic webapp up and running, but wait, are you going to share to your friends the page 50.283.48.100? Obviously not, you need a fancy domain like myawesomepage.com or something that describes your project. To achieve that you need to first buy a domain and then link that domain to your IP. Domains that are not on high demand typically cost around 10$ to 20$. You can buy them on <a href="https://www.namecheap.com/" target="_blank">Namecheap</a>. Once you have it you need to do several things on the AWS side. You will need to fix the IP so that it doesn’t change, otherwise the DNS redirection will get broken over time. After that you need to create nameservers and then route your domain to that IP. Let’s go step by step. To fix the IP go to the section Elastic IP in the left bar of the EC2 menu. Create such Elastic IP and then, in actions, associate it to your instance. Once you have done that, you will need to create the hosted zone. For that, search in AWS the Route 53 service. Once there, click on Create hosted zone. Insert your domain and create it. Before we continue, two more records need to be created. Create one with Type A and your previous Elastic IP under the value section, everything else as default. Repeat now but add ‘www’ in subdomain so that your page can be accessed either by its domain or adding www at the beginning. Once you have done that, go to your domain on Namecheap and click on manage. Select custom DNS and enter the four nameservers that were created previously. If you didn’t understand something, you can check the tutorials I followed both for <a href="https://techgenix.com/namecheap-aws-ec2-linux/" target="_blank">the AWS</a> and <a href="https://www.namecheap.com/support/knowledgebase/article.aspx/10371/2208/how-do-i-link-my-domain-to-amazon-web-services/" target="_blank">Namecheap part</a>. DNS redirection may take up to 48 hours. There is one last thing to modify, remember that you need to include the IP in <code class="language-html highlighter-rouge">ALLOWED_HOSTS</code>? Well, you also need to include your domain there. Change that and you will have your marvelous webpage running.</p> + +<h1 id="conclusion">Conclusion</h1> + +<p>Congratulations! You have managed to reach to the end of this tutorial. If you followed the steps carefully you now know how to create your own web apps. The first time you do it is quite tiresome, but once you know how to do it you can get your millionaire idea up and running at the moment. Let’s recap. First, you need to create your Django app. Then, you create a Docker to launch it easily. After that, you create an AWS instance and deploy your app there. Finally, you link your domain to the instance IP. Once you are done you can enjoy your creation!</p> + + Wed, 12 Jul 2023 00:00:00 +0000 + https://granadata.art/WebApp-Docker-Django/ + https://granadata.art/WebApp-Docker-Django/ + + + Django + + Docker + + AWS + + + + + Modelling a padel match with Hidden Markov Models (Part 3) + <p>In <a href="/HMM-padel" target="_blank">part I</a> and <a href="/HMM-padel-part2" target="_blank">part II</a> of this series I have talked about what is a Hidden Markov Model, why it is useful for modelling a padel match, and how to design it properly. Today the topic is about testing the model. Testing a machine learning model is the basis for deploying it. Here I will be explaining how to test in python and guide you through our HMM example.</p> + +<h2 id="pytest">PyTest</h2> + +<p>Testing in python can be done with several libraries. One of the most popular ones is <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code>. There are many good tutorials out there about how to use it, like <a href="https://towardsdatascience.com/getting-started-unit-testing-with-pytest-9cba6d366d61" target="_blank">this one</a>. And if you have a project on your hands and have specific needs, you can always go and have a look at <a href="https://docs.pytest.org/en/6.2.x/contents.html" target="_blank">their documentation</a>. For this post I will pass very briefly on the funcionalities that we need for testing the HMM.</p> + +<p>In pytest you have files that contain the word ‘test’ on it. Those files contain functions that also have the string ‘test’ in their names. And those functions must have code that yields <code class="highlight language-python" data-lang="python"><span class="bp">True</span></code> of <code class="highlight language-python" data-lang="python"><span class="bp">False</span></code> depending on whether they pass the test. To run the tests you just execute <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code> on the command line and the library does the rest of the job for you. This is the big picture, let’s define now what are going to be our tests.</p> + +<p>Our tests are going to have a sequence of observations as input and we are going to check for the result of the match. That is the final goal of the HMM. The difference is that here we are going to deal with simple scenarios where we know for sure the result. That way we can check that the HMM is at least well implemented. We cannot check if the design is going to work in a real-world scenario, but we can check that it works in ideal scenarios. If your model breaks in an environment where you control the input, then something is clearly wrong in the code. However, if it breaks in production it may be that the model hypothesis about the world are wrong. That’s why you need these tests, to detect bugs prior to analysing the correctness of the model itself.</p> + +<p>Once the tests are designed, it’s time to implement them. For that, <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code> comes with two main features that make the process easier.</p> + +<h3 id="parameterization">Parameterization</h3> + +<p>In <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code> you can parameterize the input. When testing a function, several input-output pairs are used to ensure that the function produces the desired result. Using a different file for each input-output pair would be an inconvenience. Instead, you can specify several input-output pairs for a given test. Consider the following test where we are interested in knowing if the HMM correctly detects an ace:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="k">def</span> <span class="nf">test_ace</span><span class="p">(</span><span class="n">sequence</span><span class="p">,</span> <span class="n">indexers</span><span class="p">,</span> <span class="n">hmm</span><span class="p">,</span> <span class="n">hidden_states</span><span class="p">):</span> + <span class="n">indexer_hidden</span><span class="p">,</span> <span class="n">indexer_obs</span> <span class="o">=</span> <span class="n">indexers</span> + <span class="n">sequences</span> <span class="o">=</span> <span class="p">[[</span><span class="n">indexer_obs</span><span class="p">[</span><span class="n">obs</span><span class="p">]</span> <span class="k">for</span> <span class="n">obs</span> <span class="ow">in</span> <span class="n">sequence</span><span class="p">]]</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="n">hmm</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">sequences</span><span class="p">)</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="p">[</span><span class="n">hidden_states</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> + <span class="nf">assert</span><span class="p">(</span><span class="sh">'</span><span class="s">Point-server</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>The parameter sequence is a list of observations that corresponds to an ace. Without parameterization we would have to specify a different test function for each sequence that represents an ace. With parameterization we can reuse the function. In <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code> that features is implemented with decorators. For those of you who don’t know python enough, a decorator is basically a function of functions. Here it takes the test function as input and creates a parameterized test function as output. Syntactically it is coded like this:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre><span class="nd">@pytest.mark.parametrize</span><span class="p">(</span><span class="sh">"</span><span class="s">sequence</span><span class="sh">"</span><span class="p">,</span> <span class="p">[</span> + <span class="c1">### Ace examples +</span> <span class="p">])</span> +<span class="k">def</span> <span class="nf">test_ace</span><span class="p">(</span><span class="n">sequence</span><span class="p">,</span> <span class="n">indexers</span><span class="p">,</span> <span class="n">hmm</span><span class="p">,</span> <span class="n">hidden_states</span><span class="p">):</span> + <span class="n">indexer_hidden</span><span class="p">,</span> <span class="n">indexer_obs</span> <span class="o">=</span> <span class="n">indexers</span> + <span class="n">sequences</span> <span class="o">=</span> <span class="p">[[</span><span class="n">indexer_obs</span><span class="p">[</span><span class="n">obs</span><span class="p">]</span> <span class="k">for</span> <span class="n">obs</span> <span class="ow">in</span> <span class="n">sequence</span><span class="p">]]</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="n">hmm</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">sequences</span><span class="p">)</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="p">[</span><span class="n">hidden_states</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> + <span class="nf">assert</span><span class="p">(</span><span class="sh">'</span><span class="s">Point-server</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>You only have to add the decorator on top of the function. If you recall the problem at hands, which sequences could possibly represent an ace? Let’s see some examples:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +</pre></td><td class="rouge-code"><pre><span class="c1">### ace in first service ### +</span><span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">100</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">],</span> + <span class="c1"># Bounce on wall +</span><span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-wall-outer</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">],</span> + <span class="c1"># Bounce on wall twice +</span><span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-wall-outer</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-wall-outer</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">],</span> + +<span class="c1">### ace in second service ### +</span> <span class="c1"># Bounce on wall +</span><span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-wall-outer</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">],</span> + <span class="c1"># Bounce on wall twice +</span><span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-wall-outer</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-wall-outer</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">]</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Basically, whenever there are two consecutive bounces on the other side after the first player has hit the ball we have an ace. When an ace happens the server wins, therefore we have to check that the last hidden state correspond to the server winning. If that doesn’t happens, then our HMM isn’t working. Passing this test doesn’t prove that the HMM works, but by adding more and more tests we at least know that our model is robust to all those cases.</p> + +<h3 id="fixtures">Fixtures</h3> + +<p>Have you wondered how do you pass parameters to a test? In the previous section I talk about parameterizing with a decorator. But what about the rest of the parameters? Not every parameter is part of the input. There are parameters that are part of the function itself. For instance, the parameter <code class="highlight language-python" data-lang="python"><span class="n">hmm</span></code>. How does <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code> know where to look for that object?</p> + +<p>At the beginning I said that you just have to run <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code> in the command line. You don’t specify parameters to the internal test functions directly. Instead, you use fixtures. A fixture is another decorator provided by <code class="highlight language-python" data-lang="python"><span class="n">pytest</span></code>. In this case, you decorate a function that returns the parameter you want to use later on. Let’s look at an example by specifying the fixture for the <code class="highlight language-python" data-lang="python"><span class="n">hmm</span></code> object. Suppose that you have a function in your code (not your test, your real code) that initialized the <code class="highlight language-python" data-lang="python"><span class="n">hmm</span></code> and returns it. Then, you would convert that function to a fixture this way:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="nd">@pytest.fixture</span><span class="p">()</span> +<span class="k">def</span> <span class="nf">hmm</span><span class="p">():</span> + <span class="k">return</span> <span class="nf">read_hmm</span><span class="p">()</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>That’s everything you have to do in order for every other function to know where to look for the <code class="highlight language-python" data-lang="python"><span class="n">hmm</span></code> object. The same would be needed for the <code class="highlight language-python" data-lang="python"><span class="n">indexers</span></code> and <code class="highlight language-python" data-lang="python"><span class="n">hidden_states</span></code> that in my case are just dictionaries to convert from strings of states to the internal identifiers that the HMM uses.</p> + +<h2 id="noisy-tests">Noisy tests</h2> + +<p>To end this post I’ll show you some concrete tests I designed for my HMM. They are a bit different than the rest of the tests. When evaluating the HMM I said that we give ideal scenarios as input to the tests. But it is possible to give noisy scenarios too, if you control the noise. There are no rules for writing tests, they just serve to check that your code does what you want it to do. And if I want my HMM to be robust to noise, I can test for it.</p> + +<p>When I talk about noise in this problem it would be missing some observation or having repeated observations for the same hidden state. We designed our model so that it would work even on those cases. So if I provide a series of noisy observations, it must predict correctly the result. For example, in the following code there is the test for two cases where the server fails on the second service. However, the first hit is repeated and some bounces are repeated too. This series of observations doesn’t correspond to an ideal one, but the model should correctly predict the result.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +</pre></td><td class="rouge-code"><pre><span class="nd">@pytest.mark.parametrize</span><span class="p">(</span><span class="sh">"</span><span class="s">sequence</span><span class="sh">"</span><span class="p">,</span> <span class="p">[</span> + <span class="c1">### Fail in second service ### +</span> <span class="c1"># Bounce in and then on inner wall +</span> <span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">13</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">9</span><span class="p">,</span> + <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">4</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-wall-inner</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">],</span> + <span class="c1"># Bounce out +</span> <span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">15</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">12</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">]</span> +<span class="p">])</span> +<span class="k">def</span> <span class="nf">test_fail_noise</span><span class="p">(</span><span class="n">sequence</span><span class="p">,</span> <span class="n">indexers</span><span class="p">,</span> <span class="n">hmm</span><span class="p">,</span> <span class="n">hidden_states</span><span class="p">):</span> + <span class="n">indexer_hidden</span><span class="p">,</span> <span class="n">indexer_obs</span> <span class="o">=</span> <span class="n">indexers</span> + <span class="n">sequences</span> <span class="o">=</span> <span class="p">[[</span><span class="n">indexer_obs</span><span class="p">[</span><span class="n">obs</span><span class="p">]</span> <span class="k">for</span> <span class="n">obs</span> <span class="ow">in</span> <span class="n">sequence</span><span class="p">]]</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="n">hmm</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">sequences</span><span class="p">)</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="p">[</span><span class="n">hidden_states</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> + <span class="nf">assert</span><span class="p">(</span><span class="sh">'</span><span class="s">Point-receiver</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>You can also have tests that don’t pass on purpose. I call this one ‘impossible_noise_test’:</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s"> Test designed to make the model fail </span><span class="sh">"""</span> +<span class="nd">@pytest.mark.parametrize</span><span class="p">(</span><span class="sh">"</span><span class="s">sequence</span><span class="sh">"</span><span class="p">,</span> <span class="p">[</span> + <span class="p">[</span><span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">3</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">3</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">3</span><span class="p">,</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="c1"># Bounces in the ground badly detected as player-hit +</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">3</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="c1"># Badly detected bounce +</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">5</span><span class="p">,</span> <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">4</span><span class="p">,</span> <span class="c1"># Well detected serve +</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-server</span><span class="sh">'</span><span class="p">,</span> <span class="c1"># Same bounce +</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">5</span><span class="p">,</span> <span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="c1"># Well detected response +</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">5</span><span class="p">]</span><span class="o">*</span><span class="mi">10</span><span class="p">,</span> <span class="c1"># Normal rally (now the ball is for receiver) +</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">]</span> <span class="c1"># It goes to net and out +</span><span class="p">])</span> +<span class="k">def</span> <span class="nf">test_impossible_noise</span><span class="p">(</span><span class="n">sequence</span><span class="p">,</span> <span class="n">indexers</span><span class="p">,</span> <span class="n">hmm</span><span class="p">,</span> <span class="n">hidden_states</span><span class="p">):</span> + <span class="n">indexer_hidden</span><span class="p">,</span> <span class="n">indexer_obs</span> <span class="o">=</span> <span class="n">indexers</span> + <span class="n">sequences</span> <span class="o">=</span> <span class="p">[[</span><span class="n">indexer_obs</span><span class="p">[</span><span class="n">obs</span><span class="p">]</span> <span class="k">for</span> <span class="n">obs</span> <span class="ow">in</span> <span class="n">sequence</span><span class="p">]]</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="n">hmm</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">sequences</span><span class="p">)</span> + <span class="n">decoded_seq</span> <span class="o">=</span> <span class="p">[</span><span class="n">hidden_states</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> + <span class="nf">assert</span><span class="p">(</span><span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>This type of test allows you to know the limits of the model. There must be a limit, and if you don’t manage to create a test that fails, that is also and indicator that something is wrong. Maybe you are not creative enough for the cases, or you have some bug that makes all the test pass (that has happened to me). So it is also a good idea to have a test that fails, just in case.</p> + +<h2 id="conclusion">Conclusion</h2> + +<p>With this post the Hidden Markov Model for Padel Modelling series comes to an end. We have learned everything to deploy a HMM. From the theory behind the model, till testing the model extensively. Those are the steps to put any machine learning model into production. Learning the basics, designing the model and testing the model. The only thing left is to create a pipeline and integrate it into the final product, but that is a topic for another day. I hope you liked the process as much as I did. See you on my next series.</p> + + Sun, 16 Oct 2022 00:00:00 +0000 + https://granadata.art/HMM-padel-part3/ + https://granadata.art/HMM-padel-part3/ + + + Hidden Markov Model + + + + + Modelling a padel match with Hidden Markov Models (Part 2) + <p>In my <a href="/HMM-padel" target="_blank">last post</a> I explained the usefulness of Hidden Markov Models for predicting the outcome of a padel match with only a few observations. There I also showed how easy it was to implement everything in Python, but I left the most important part: the HMM itself. Today we are going to learn how to design a HMM to predict the result of a point. This is going to be an iterative process. The final model, as you will see, is a monstruosity. But step by step we are goint to build it succesfully. “Rome wasn’t made in a day”.</p> + +<h2 id="inspiration-and-design">Inspiration and design</h2> + +<p>Before diving into the details, let me explain how I tackled this problem. I think the creative process is worth mentioning. If you just want the details, you can skip this section. Before starting to code or thinking on my own for the solution of a problem I always research for similar problems that are already solved so that I can get some inspiration. In this case, it turned out that somebody had already designed a HMM for tenis. In <a href="https://ieeexplore.ieee.org/document/1423025" target="_blank">this article</a>, they present a HMM for following the state of a tennis match. The observations in this article were poorly defined, but the hidden states wer very clear. I could get an idea of what I had to do by just looking at this image.</p> + +<p class="text-center"><img src="/assets/images/HMM/hmm-tenis.png" alt="tenisHMM" /></p> + +<p>This graph is just depicting visually the rules of tenis. In our case we just have to show the rules of padel with a similar graph. If you look carefully you will see that the white boxes represent hidden states with an obvious observation. For padel there will be more of them because apart from bouncing on the ground, the ball can bounce on the walls, which makes the graph more complex but the idea is the same. Another helpful diagram on the same article represented the same graph but organised in several subgraphs.</p> + +<p class="text-center"><img src="/assets/images/HMM/hmm-tenis2.png" alt="tenisHMM2" /></p> + +<p>What’s interesting here is that those four subgraphs will be the same for padel. The high-level view of the process (top) is exactly the same. That’s part of the job which is already done. We just have to change the internal representation between those four subgraphs and maintain the interconnections.</p> + +<p>What about designing graphs? What is the software I used for that? You may say. Well, it is actually a pen and a lot of papers. No matter how well developed graph visualizations software are, drawing a simple graph by hand will always be faster than coding it. Of course, when the project keeps growing and the graphs becomes massive you will need those software tools which I will mention later. But at the beginning, just take a pen and start drawing. In the other sections I will present diagrams made with a computer because they are visually more pleasant and you will understand them better. Nevertheless, here is one of the graphs I painted by hand, in case you are curious.</p> + +<p class="text-center"><img src="/assets/images/HMM/hand.jpg" alt="hand-diagram" /></p> + +<h2 id="the-hidden-states-graph">The hidden states’ graph</h2> + +<p>For a HMM we need the transition and emission matrices. The transition matrix is going to be the adjacency matrix of the transitions graph. That graph is simply a representation of the rules of the game. I am going to distinguish two main parts in that rules. The rules for the serve and the rules for the normal game. The reason for creating two distinct graphs is because the effect of the ball going out or touching the net is different at the beginning.</p> + +<h3 id="serve">Serve</h3> + +<p>What happens when a player is on their serve? It can go in, it can go out or it can touch the net. And if it touches the net, it can then go in or out. Let’s ignore when it goes in without touching the net for the moment. How would you represent the 1st serve? Like this?</p> + +<p class="text-center"><img src="/assets/images/HMM/first.png" alt="1st-serve" /></p> + +<p>Did you think about the init state? Remember, this model is a realistic one. In practice you don’t know when is the point starting. Therefore, you need a special state for waiting until you have enough evidence that the match has begun. If you look closely, you will see that there is a self-loop on the init state. That is how we represent a waiting state in a HMM, by a self-loop. Now, let’s pass to the 2nd-serve. How would you design it?</p> + +<p class="text-center"><img src="/assets/images/HMM/second.png" alt="2nd-serve" /></p> + +<p>Exactly the same as the first service. The simpler, the better. We are ignoring when the ball goes in and the game continues. We are just focusing on when the ball goes out or to the net. The rest of the details will be added later on. For now let’s just focus on what we have. We have a graph with several nodes, each representing a state. What do we need? Consistent labels across the whole graph. One of the limitations of the HMM is that you have to fulfill the Markov property. Which means that the state representing going out after the 1st-serve is different than the state of going out after the 2nd-serve. So they need different names. In my case, I just added a suffix number when that happened. That way, going out in the first service is ‘out1’ and after the second is ‘out2’. Another state that repeats a lot across the graph is the ‘in’ state. For that one adding a suffix number is not enough, so I added a more descriptive suffix. For instance, going in after touching the net in the first serve is ‘in-net1’. This is a decision that could have been made of many ways, but I decided to make it like this.</p> + +<p>Okay, let’s now talk about what happens when the ball actually goes in and the game continues. To keep it simple, let’s focus on what happens before any player hits the ball. And let’s call this the ace model. As the name states it, one of the things than can happen is an ace. What characterizes an ace? The fact that the ball touches the ground again before any player hitting it. Try to draw the scheme for the ace model. Keep in mind that before touching again the ground it can hit the walls. And also bare in mind that there are two types of walls. What are the connections among those states? Which combinations are valid and which not? Here is my solution for that problem, omitting the connections to the states Point-server and Point-receiver representing the end of the game.</p> + +<p class="text-center"><img src="/assets/images/HMM/ace.png" alt="ace" /></p> + +<p>Did you thought of the ‘time-out’ state? Again, this is a real model so it has to deal with real problems. And one of them is that you miss the observation that characterizes the end of the game. If that happens you can only know the game has finished by time. That’s why you need a state to represent the end of the game by time. Later on when defining the emissions it will be made more clear why this state is needed. Most of the extra states are created so that when an observation is wrong, there is still a path in the graph to the end. Otherwise, the model will give an error and doesn’t return anything. Observe also that there are two ‘in’ states. Can you imagine why? The state ‘in1’ is when the ball goes in on the first serve, and ‘in2’ on the second. Since we have to maintain the Markov property, those two states are different although they have the same emissions and are identical to us.</p> + +<p>Before going to the next block, which is when a player hits the ball and the game actually starts, let me explain how I made this pictures and how you can code graphs on Python. With the library <code class="highlight language-python" data-lang="python"><span class="n">networkx</span></code> you can do many thing on graphs, it has implemented almost every algorithm that exists related to graphs. In this project we only use it to define the graphs. The syntax is pretty straighforward, you define a <code class="highlight language-python" data-lang="python"><span class="n">DiGraph</span></code> which stands for directed graph, and add nodes and edges with the functions <code class="highlight language-python" data-lang="python"><span class="n">add_nodes_from</span></code> and <code class="highlight language-python" data-lang="python"><span class="n">add_edges_from</span></code>. After that, you can save the model in <code class="highlight language-python" data-lang="python"><span class="n">gml</span></code> format and open it with <a href="https://gephi.org/" target="_blank">Gephi</a>. That’s it. Here is the code for the three models presented above.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">networkx</span> <span class="k">as</span> <span class="n">nx</span> + +<span class="n">folder_path</span> <span class="o">=</span> <span class="sh">'</span><span class="s">graphs/</span><span class="sh">'</span> + +<span class="sh">"""</span><span class="s"> First serve model </span><span class="sh">"""</span> +<span class="n">first</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">DiGraph</span><span class="p">()</span> +<span class="n">first</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">init</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">1st-serve</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">out1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-net1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> +<span class="p">])</span> +<span class="n">first</span><span class="p">.</span><span class="nf">add_edges_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">init</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">1st-serve</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">init</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">init</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">1st-serve</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">net1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">1st-serve</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">out1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net1</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">in-net1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">net1</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">out1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-net1</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">1st-serve</span><span class="sh">"</span><span class="p">)</span> +<span class="p">])</span> +<span class="n">nx</span><span class="p">.</span><span class="nf">write_gml</span><span class="p">(</span><span class="n">first</span><span class="p">,</span> <span class="n">folder_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">first.gml</span><span class="sh">'</span><span class="p">)</span> + +<span class="sh">"""</span><span class="s"> Second serve model </span><span class="sh">"""</span> +<span class="n">second</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">DiGraph</span><span class="p">()</span> +<span class="n">second</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">2nd-serve</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">out2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-net2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> +<span class="p">])</span> +<span class="n">second</span><span class="p">.</span><span class="nf">add_edges_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">2nd-serve</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">net2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">2nd-serve</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">out2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net2</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">in-net2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">net2</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">out2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-net2</span><span class="sh">"</span><span class="p">,</span><span class="sh">"</span><span class="s">2nd-serve</span><span class="sh">"</span><span class="p">)</span> +<span class="p">])</span> +<span class="n">nx</span><span class="p">.</span><span class="nf">write_gml</span><span class="p">(</span><span class="n">second</span><span class="p">,</span> <span class="n">folder_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">second.gml</span><span class="sh">'</span><span class="p">)</span> + +<span class="sh">"""</span><span class="s"> Ace model </span><span class="sh">"""</span> +<span class="n">ace</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">DiGraph</span><span class="p">()</span> +<span class="n">ace</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">time-out</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">ground</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> +<span class="p">])</span> +<span class="n">ace</span><span class="p">.</span><span class="nf">add_edges_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-inner1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-inner2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground</span><span class="sh">"</span><span class="p">),</span> +<span class="p">])</span> +<span class="n">nx</span><span class="p">.</span><span class="nf">write_gml</span><span class="p">(</span><span class="n">ace</span><span class="p">,</span> <span class="n">folder_path</span> <span class="o">+</span> <span class="sh">"</span><span class="s">ace.gml</span><span class="sh">"</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p><a href="https://gephi.org/" target="_blank">Gephi</a> has some handy features that make posible visualize big graphs. Concretely, you can use the Force Atlas distribution to reorder the nodes by simulating forces proportional to the number of edges they have. It has many parameters you can try, but I normally just click on execute and wait a few seconds for convergence.</p> + +<p class="text-center"><img src="/assets/images/HMM/atlas.png" alt="Force Atlas" /></p> + +<p>Then, on the previsualization tab, you can create the diagrams I showed you. It has many options, like curved edges. I don’t use that feature for this post because for complex graphs it can be messy. But for some graphs I think is prettier with curvy edges. Other things to adapt are the font size and the size of the arrows. With a bit of practice you can create nice figures quite fast.</p> + +<p class="text-center"><img src="/assets/images/HMM/previs.png" alt="Previsualization" /></p> + +<h3 id="rally">Rally</h3> + +<p>The rally model is a bit more complex than the ones presented above. There are two ways to design it based on which observations you have. If you only have an observation for bouncing on the ground, anywhere, then the rally model only has one ‘HIT’ state and the rest is similar to the ace model. However, in practice you can have more information than that. Suppose you have an image of a game and you know the location of the ball together with the fact that it is a bounce in the ground. You could, potentially, distinguish if the ball is on the server side or on the receiver side. You just need to segment the court and see if the ball is on the upper side or not. This is not trivial, but is possible to achieve. For that reason, we are going to have two ‘HIT’ states: one for the server side and one for the receiver. The rest is just identical to the ace model, with one exception. Instead of ending the game or returning to the beginning, the states point to the other ‘HIT’ states. Similar to what would happen in a match. Your head is going from one side to the other. Here the hidden state is moving from one model to the other. Finally, the diagram.</p> + +<p class="text-center"><img src="/assets/images/HMM/rally.png" alt="Rally" /></p> + +<p>If you understood the ace model, you just need to focus on the arrows that cross from left to right and vice versa. The rest is just the standard mechanics of a padel game. One more thing to notice is that there are many arrows missing. Concretely, the arrow between the models and the arrows that point to the absorbing states, that is, those which end the game. In the next section, I will try to describe the connections between the models and will show you the (almost) full picture of the HMM transition graph. Also, below is the code for this model.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s"> Rally model </span><span class="sh">"""</span> +<span class="n">rally</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">DiGraph</span><span class="p">()</span> +<span class="n">rally</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">out-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">time-out-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + + <span class="p">(</span><span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">out-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">time-out-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> +<span class="p">])</span> +<span class="n">rally</span><span class="p">.</span><span class="nf">add_edges_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">out-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">net-HIT1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">net-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">out-HIT1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-inner-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer1-HIT1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer2-HIT1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer2-HIT1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span><span class="p">),</span> + + <span class="p">(</span><span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">),</span> + + <span class="p">(</span><span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">out-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">net-HIT2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">net-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">net-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">out-HIT2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-inner-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer1-HIT2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-inner-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer2-HIT2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-outer2-HIT2</span><span class="sh">"</span><span class="p">),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">time-out-HIT2</span><span class="sh">"</span><span class="p">),</span> <span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span><span class="p">)</span> + +<span class="p">])</span> +<span class="n">nx</span><span class="p">.</span><span class="nf">write_gml</span><span class="p">(</span><span class="n">rally</span><span class="p">,</span> <span class="n">folder_path</span> <span class="o">+</span> <span class="sh">"</span><span class="s">rally.gml</span><span class="sh">"</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="interconnections">Interconnections</h3> + +<p>Let’s go model by model, edge by edge, starting by the 1st-serve. It has two connections, one to the ‘in1’ state of the ace model and one to the 2nd-serve. The latter is from the ‘out1’ state, if the ball goes out, you have a second service, that’s the rules. The 2nd-serve model is similar, one connection to the ‘in2’ state and one to the absorbing state ‘Point-receiver’. If you fail your second serve, you lose the point. Simple, concise and clear. Let’s continue with the ace model. The ‘ground’ and ‘time-out’ states point to ‘Point-server’. This is the representation of an ace. If the ball hits the ground twice, it’s an ace and the point goes to the server side. The rest of the connections are to the ‘HIT1’ state, which means that the receiver has hit the ball and the game continues. And that’s it. The only remaining connections are between the rally model and the absorbing states, just like in the ace model. If the ball bounces twice, or if it goes out of the court after bouncing in, the last player wins. If it goes out, the last player loses. For those of you that know how to play padel, this should be no surprise. There are many cases to deal with because the ball can hit the walls and the net. But more or less it is summarized like that. Here’s the full picture.</p> + +<p class="text-center"><img src="/assets/images/HMM/union.png" alt="Union" /></p> + +<p>The code for this part is different. This time we have to join the four different models. To do so we are going to use the function <code class="highlight language-python" data-lang="python"><span class="n">union_all</span></code> that creates a new graph with all the nodes and edges from before but without any connections between the subgraphs. To add those connections we use the <code class="highlight language-python" data-lang="python"><span class="n">add_edge</span></code> function and for the absorbing states the <code class="highlight language-python" data-lang="python"><span class="n">add_nodes_from</span></code>. This is the result.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s"> Graph union plus connection edges </span><span class="sh">"""</span> +<span class="n">union</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">union_all</span><span class="p">((</span><span class="n">first</span><span class="p">,</span> <span class="n">second</span><span class="p">,</span> <span class="n">ace</span><span class="p">,</span> <span class="n">rally</span><span class="p">))</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">out1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">2nd-serve</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">1st-serve</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">in1</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">2nd-serve</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">in2</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">wall-inner1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">2nd-serve</span><span class="sh">"</span><span class="p">)</span> + +<span class="sh">"""</span><span class="s"> Absorbing states </span><span class="sh">"""</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">})</span> +<span class="p">])</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">wall-inner2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">out2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">time-out</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">ground</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">out-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">time-out-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">out-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">time-out-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">)</span> + +<span class="sh">"""</span><span class="s"> Connection between ace and rally </span><span class="sh">"""</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">in1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">in2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">wall-outer1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">wall-outer2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span><span class="p">)</span> +<span class="n">nx</span><span class="p">.</span><span class="nf">write_gml</span><span class="p">(</span><span class="n">union</span><span class="p">,</span> <span class="n">folder_path</span> <span class="o">+</span> <span class="sh">"</span><span class="s">union.gml</span><span class="sh">"</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h3 id="delays">Delays</h3> + +<p>Previously I said this was almost the full picture. The reason for that is that the model here does not take into account that the ball is flying in between bounces. In an ideal model this would be irrelevant, with just the bounces we can predict the outcome of the game. But in practice that is not true. Do you remember the reason for using a ‘time-out’ state? Here is similar. Imagine you detect the same bounce twice by error. If you only look at bounces you will assume that the game has finished. However, if you detect twice the same bounce, in time they will be very close. In contrast with what will happen if those two bounces are both real. Therefore, if you somehow take into account the time between observations you can solve those kind of errors. The way to do that is by adding ‘flying’ states. In between any two states you include a ‘flying’ state and in the emissions you consider ‘flying’ as a plausible observation. This way you have a way of measuring time. The more ‘flying’ observations you have, the more time that has passed between states. The key here is that the ‘flying’ state has a self-loop. Similar to the ‘init’ state. You don’t know how much time is going to occur between states. For that reason you add a self-loop to stay in that state until there is evidence enough that you are not flying anymore.</p> + +<p>For this part there is no diagram. As you may have guessed, adding one state for every edge is going to make the model huge and very complicated to deal with. At this step, I mostly work with the code. Adding all the edges by hand is a nightmare. For that reason, I let python do that for me. Before showing you the code, there is one more hypothesis to deal with. I said that the reason for the ‘flying’ state is to correct duplicate observations. But to do that it is needed to add more than one ‘flying’ state per edge. Why? Because the first ‘flying’ states are going to be corrective states without self-loop. It is only the last one that is a waiting state. Those corrective states can emit the same observations as the first state. While the waiting state can only emit the ‘flying’ observation. The reason for this is mostly empirical. Using only one ‘flying’ state yielded unsatisfactory results. In my experiments I ended up using five ‘flying’ states: four correctives, and one waiting. Finally, the code for that.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s"> Add flying states </span><span class="sh">"""</span> +<span class="n">current_edges</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="n">union</span><span class="p">.</span><span class="n">edges</span><span class="p">)</span> +<span class="n">fly_err_len</span> <span class="o">=</span> <span class="mi">5</span> +<span class="nf">for </span><span class="p">(</span><span class="n">u</span><span class="p">,</span><span class="n">v</span><span class="p">)</span> <span class="ow">in</span> <span class="n">current_edges</span><span class="p">:</span> + <span class="k">if</span> <span class="n">u</span> <span class="o">==</span> <span class="sh">"</span><span class="s">init</span><span class="sh">"</span><span class="p">:</span> + <span class="k">continue</span> + <span class="n">union</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">([(</span><span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">k</span><span class="p">),</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">True</span><span class="p">})</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">fly_err_len</span><span class="o">+</span><span class="mi">1</span><span class="p">)])</span> + <span class="n">union</span><span class="p">.</span><span class="nf">remove_edge</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> + <span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">"</span><span class="s">-0</span><span class="sh">"</span><span class="p">)</span> + <span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">"</span><span class="s">-</span><span class="sh">"</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">fly_err_len</span><span class="p">))</span> + <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">fly_err_len</span><span class="p">):</span> + <span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">k</span><span class="p">),</span> <span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="p">))</span> + <span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">k</span><span class="p">),</span> <span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">fly_err_len</span><span class="p">))</span> + <span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">fly_err_len</span><span class="p">),</span> <span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">fly_err_len</span><span class="p">))</span> + <span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">flying-</span><span class="sh">"</span><span class="o">+</span><span class="n">u</span><span class="o">+</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">fly_err_len</span><span class="p">),</span> <span class="n">v</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>And the last part of the code is to convert the ‘Point-server’ and ‘Point-receiver’ states into waiting states. The reason for this is numerical. When creating the transition and emission matrices the values need to be normalized. If you don’t add this connections you have rows with all zeros that give errors and it is easier to solve them like this. Those edges are added after creating the ‘flying’ states because those edges don’t need any ‘flying’ states attached to them, they are simply a trick for the computation.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s"> Self-loops for absorbing states </span><span class="sh">"""</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-receiver</span><span class="sh">"</span><span class="p">)</span> +<span class="n">union</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">Point-server</span><span class="sh">"</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h2 id="the-observations-graph">The observations’ graph</h2> + +<p>First, let’s define the observations.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s"> Possible observations </span><span class="sh">"""</span> +<span class="n">obs</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">DiGraph</span><span class="p">()</span> +<span class="n">obs</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">([</span> + <span class="p">(</span><span class="sh">"</span><span class="s">player-hit</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">bounce-net</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">bounce-wall-inner</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">bounce-wall-outer</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">flying</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}),</span> + <span class="p">(</span><span class="sh">"</span><span class="s">end</span><span class="sh">"</span><span class="p">,</span> <span class="p">{</span><span class="sh">"</span><span class="s">hidden</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">})</span> +<span class="p">])</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>This is all we can observe, at least automatically with a camera. We can observe the ball bouncing anywhere: in the walls, in the net, or in the ground. And we can observe any player hitting the ball. Notice that we don’t have to distinguish which player hits the ball, that job is done by distinguishing where is the ball bouncing. The reason for that is that it is quite difficult in practice to detect when a player hits the ball and which player it is due to projection. With just one observation representing all the player is enough to solve the problem. Keep in mind that this is for real cases, and the model has to reflect the limitations of the detections. There is one special observation called ‘end’. The HMM presented here can only deal with isolated points. The ‘end’ observation is only emitted by the absorbing states. It is a way of forcing the HMM to find a solution. When dealing with more than one point it is needed to detect when the point has finished.</p> + +<p>As with the ‘flying’ states, I didn’t add all the emissions by hand. There are a lot of nodes in the transition graph. And the emission graph is a bipartite graph with transitions on one side and emissions on the other. That is a lot of edges. But in the end, is no more than a regex problem. The states have descriptive names. Any state with ‘ground’ in their name is going to emit either ‘bounce-ground-receiver’ or ‘bounce-ground-server’. And the ‘flying’ states emit the ‘flying’ observation. There is no fancy ideas here, just nasty work. I leave here the code for you. There are better ways to code this for sure, but this works and it’s mine, so I like it.</p> + +<p>There are two more things to mention. The flying probability and the missing probability. As I said before there are corrective states. Those corrective states can emit the same observation as the state they are attached to it, but with a smaller probability. Otherwise, if the probability isn’t lower, we are not taking into account the fact that the more separated two observations are, the more likely they are to be correct. Thus, the corrective ‘flying’ states have some probability of emit ‘flying’ and some probability of emiting other things. The missing probability is similar but is for the other states. If instead of a duplicate you miss an observation, the graph still needs to find a path to the end. For that reason every state has some little probability of emitting ‘flying’. And there are many other little changes that were added in the process of creating this matrix. The justification behind most of the strange things you will see in the code is empirical. You start with a simple model and find a case where it doesn’t work, change the model and repeat. After several iterations you arrive at this.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +</pre></td><td class="rouge-code"><pre><span class="sh">"""</span><span class="s"> Create bipartite graph representing emissions </span><span class="sh">"""</span> +<span class="n">G</span> <span class="o">=</span> <span class="n">union</span><span class="p">.</span><span class="nf">copy</span><span class="p">()</span> +<span class="n">G</span><span class="p">.</span><span class="nf">clear_edges</span><span class="p">()</span> +<span class="n">U</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">union</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">obs</span><span class="p">)</span> +<span class="n">flying_prob</span> <span class="o">=</span> <span class="mf">0.9</span> +<span class="n">err_miss_prob</span> <span class="o">=</span> <span class="mf">0.001</span> +<span class="n">eps</span> <span class="o">=</span> <span class="mf">1e-12</span> +<span class="k">for</span> <span class="n">u</span> <span class="ow">in</span> <span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">():</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">Point</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="c1">#U.add_weighted_edges_from([(u, "flying", 1)]) +</span> <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">end</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)])</span> + <span class="k">continue</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">flying</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">and</span> <span class="sh">"</span><span class="s">init</span><span class="sh">"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">flying</span><span class="sh">"</span><span class="p">,</span> <span class="n">flying_prob</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="k">if</span> <span class="n">fly_err_len</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">fly_err_len</span> <span class="o">==</span> <span class="nf">int</span><span class="p">(</span><span class="n">u</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="sh">'</span><span class="s">-</span><span class="sh">'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]):</span> + <span class="k">continue</span> + <span class="k">elif</span> <span class="n">u</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">[</span><span class="sh">"</span><span class="s">net1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">net2</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-inner1</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">wall-inner2</span><span class="sh">"</span><span class="p">]:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">flying</span><span class="sh">"</span><span class="p">,</span> <span class="n">err_miss_prob</span><span class="p">)])</span> + + <span class="k">if</span> <span class="sh">"</span><span class="s">init</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">5</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">5</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-inner</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">5</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-outer</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">5</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-net</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">5</span><span class="p">)])</span> + <span class="c1"># U.add_weighted_edges_from([(u, "player-hit", eps)]) +</span> <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">flying</span><span class="sh">"</span><span class="p">,</span> <span class="n">flying_prob</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">serve</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="n">u</span> <span class="o">==</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span> <span class="ow">or</span> <span class="n">u</span> <span class="o">==</span> <span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">flying-HIT1-</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">flying-HIT2-</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">player-hit</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-inner</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-outer</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">time</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">flying</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">wall</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> <span class="c1"># this must be before in and out +</span> <span class="k">if</span> <span class="sh">"</span><span class="s">inner</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-inner</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">outer</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-outer</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span><span class="p">)])</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">HIT1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">HIT2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span><span class="p">)])</span> + <span class="k">else</span><span class="p">:</span> <span class="nf">assert</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">player-hit</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="nf">elif </span><span class="p">(</span><span class="sh">"</span><span class="s">in</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">ground</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">)</span> <span class="ow">and</span> <span class="sh">"</span><span class="s">flying-net</span><span class="sh">"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">and</span> <span class="sh">"</span><span class="s">flying-out</span><span class="sh">"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">in1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">in2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">in-HIT2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span>\ + <span class="ow">or</span> <span class="sh">"</span><span class="s">in-net1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">in-net2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)])</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">ground-HIT2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">in-HIT1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)])</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">ground-HIT1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">ground</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="k">else</span><span class="p">:</span> <span class="nf">assert</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">player-hit</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">out</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">out-HIT1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-receiver</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span> <span class="o">/</span> <span class="mi">4</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">out-HIT2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">out1</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span> <span class="ow">or</span> <span class="sh">"</span><span class="s">out2</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-ground-server</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span> <span class="o">/</span> <span class="mi">4</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-inner</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span> <span class="o">/</span> <span class="mi">4</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-wall-outer</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span> <span class="o">/</span> <span class="mi">4</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-net</span><span class="sh">"</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)</span> <span class="o">/</span> <span class="mi">4</span><span class="p">)])</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">player-hit</span><span class="sh">"</span><span class="p">,</span> <span class="n">eps</span><span class="p">)])</span> + <span class="k">elif</span> <span class="sh">"</span><span class="s">net</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">u</span><span class="p">:</span> + <span class="n">U</span><span class="p">.</span><span class="nf">add_weighted_edges_from</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span> <span class="sh">"</span><span class="s">bounce-net</span><span class="sh">"</span><span class="p">,</span> <span class="mi">1</span><span class="o">-</span><span class="n">flying_prob</span><span class="p">)])</span> + <span class="c1">#U.add_weighted_edges_from([(u, "player-hit", eps)]) +</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<h2 id="the-matrices">The matrices</h2> + +<p>Okay, we have the graphs, but what about the matrices? We need those for the <code class="highlight language-python" data-lang="python"><span class="n">hmmkay</span></code> library. How do we generate them? NetworkX provides a function for generating adjacency matrices (<code class="highlight language-python" data-lang="python"><span class="n">adjacency_matrix</span></code>). However, we cannot use those matrices as they are, we need to normalize them so that the rows sum up to one. Remember, they represent probabilities. After normalization, we can save the result using <code class="highlight language-python" data-lang="python"><span class="n">pandas</span></code>.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span> + +<span class="sh">"""</span><span class="s"> Emission matrix </span><span class="sh">"""</span> +<span class="n">V1</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">())</span> +<span class="n">B</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">adjacency_matrix</span><span class="p">(</span><span class="n">U</span><span class="p">).</span><span class="nf">toarray</span><span class="p">()[:</span><span class="n">V1</span><span class="p">,</span><span class="n">V1</span><span class="p">:]</span> +<span class="n">err_change</span> <span class="o">=</span> <span class="mi">0</span> +<span class="n">B</span> <span class="o">+=</span> <span class="n">err_change</span> +<span class="n">B</span> <span class="o">=</span> <span class="n">B</span> <span class="o">/</span> <span class="n">B</span><span class="p">.</span><span class="nf">sum</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">).</span><span class="nf">reshape</span><span class="p">((</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">))</span> +<span class="n">B_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nc">DataFrame</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="n">obs</span><span class="p">.</span><span class="nf">nodes</span><span class="p">(),</span> <span class="n">index</span><span class="o">=</span><span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">())</span> +<span class="n">B_df</span><span class="p">.</span><span class="nf">to_csv</span><span class="p">(</span><span class="n">folder_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">B.csv</span><span class="sh">'</span><span class="p">)</span> + +<span class="sh">"""</span><span class="s"> Transition matrix </span><span class="sh">"""</span> +<span class="n">A</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">adjacency_matrix</span><span class="p">(</span><span class="n">union</span><span class="p">).</span><span class="nf">toarray</span><span class="p">()</span> +<span class="n">A</span> <span class="o">=</span> <span class="n">A</span> <span class="o">/</span> <span class="n">A</span><span class="p">.</span><span class="nf">sum</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">).</span><span class="nf">reshape</span><span class="p">((</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">))</span> +<span class="n">A_df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nc">DataFrame</span><span class="p">(</span><span class="n">A</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">(),</span> <span class="n">index</span><span class="o">=</span><span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">())</span> +<span class="n">A_df</span><span class="p">.</span><span class="nf">to_csv</span><span class="p">(</span><span class="n">folder_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">A.csv</span><span class="sh">'</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>The <code class="highlight language-python" data-lang="python"><span class="n">err_change</span></code> variable is for adding noise to the emissions so that every state can emit every observation with a little probability. In my experience it doesn’t work well, but I leave it there in case you want to experiment with it.</p> + +<h1 id="conclusion">Conclusion</h1> + +<p>In this post we have seen how to properly design a HMM for following the result of a padel match. We have learnt to use the NetworkX library to create the graphs and the Gephi program to visualize the process. In the <a href="/HMM-padel-part3" target="_blank">next post</a> of this series we will learn how to actually test whether the HMM works. Stay tuned.</p> + + + Fri, 16 Sep 2022 00:00:00 +0000 + https://granadata.art/HMM-padel-part2/ + https://granadata.art/HMM-padel-part2/ + + + Hidden Markov Model + + + + + Modelling a padel match with Hidden Markov Models + <p>Imagine you are at a padel match. You watch the ball go from one side to the other, hit the wall, hit the ground, the net, and after some time someone wins the point. In your head you are following the state of the match until the end to know the winner. Imagine now that you were distracted by a fly and lost concentration on the players. You don’t know what has happened when you were distracted but you managed to watch the last part of the point and so you still know who is the winner. How can we replicate this situation with a model? Which is the correct model to manage situations where you can lose track in the middle but by looking the last part you know the result? The property of only needing to know the last part to know the result is called the Markov property. So a suitable model for this task is a Hidden Markov Model.</p> + +<p>In this series I will describe how to properly design a Hidden Markov Model (HMM from now on) to keep track of the state of a padel match. I will also provide functional python code that you could play with. And as extra material I will talk about unit testing and how to use unit tests to incrementally build a model like this one. Let’s begin.</p> + +<p>First of all a rapid introduction on HMM and how to code them. A Hidden Markov Model has two main elements: hidden states and observations. The hidden states are what represent the model, in the padel case a hidden state can be first service, or ace or second service. The observations are what you can observe directly, continuing with the analogy an observation can be when the ball hits the ground. What the HMM does is to infer the hidden states based only on a sequence of observations. If you watch that the first player hits the ball and then the ball hits the ground twice on the other side, you can infer that the sequence of states is first service and then ace. A more abstract scheme is shown below.</p> + +<p class="text-center"><img class="" src="/assets/images/HMM/obs_seq.png" alt="simple" /></p> + +<p>Every arrow represents a transition either between hidden states or between a hidden state or an observation. Each transition is nothing more than a probability. For example, the probability of going from second service to first service is zero because the first service always goes first. The transitions between hidden states and observations are called emissions. One emission could be that the probability of observing the ball hit the ground after first service is $0.5$. The transition and emission probabilities are represented as matrices. Below you can see the transitions of an example from a toy HMM.</p> + +<p class="text-center"><img class="" src="/assets/images/HMM/transition.png" alt="simple" /></p> + +<p>Once you have a HMM there are three things you can do with it. Given a sequence of observations you can decode the most probable sequence of hidden states with an algorithm call <a href="https://www.youtube.com/watch?v=6JVqutwtzmo" target="_blank">Viterbi’s</a>. You can estimate the internal probabilities of the HMM using several sequences of observations with the <a href="https://medium.com/analytics-vidhya/baum-welch-algorithm-for-training-a-hidden-markov-model-part-2-of-the-hmm-series-d0e393b4fb86" target="_blank">Baum-Welch algorithm</a>. Or you can sample a new sequence of observations. We are only interested in the first one, decoding. The transition and emission probabilities will be designed to model the rules of a padel match.</p> + +<p>Now that we have seen the theory, let’s see how we can decode sequences in Python. As you may have guessed there are libraries that implement all the previously mentioned algorithms. My favorite one so far is <a href="https://github.com/NicolasHug/hmmkay" target="_blank"><code class="highlight language-python" data-lang="python"><span class="n">hmmmkay</span></code></a>. It is quite easy to use and is fast enough for my use cases. You can create a HMM with a few lines of code. See below.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +</pre></td><td class="rouge-code"><pre><span class="kn">import</span> <span class="n">pandas</span> <span class="k">as</span> <span class="n">pd</span> +<span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span> +<span class="kn">from</span> <span class="n">hmmkay</span> <span class="kn">import</span> <span class="n">HMM</span> + +<span class="n">folder_path</span> <span class="o">=</span> <span class="sh">'</span><span class="s">graphs/</span><span class="sh">'</span> +<span class="n">transition_probas</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="n">folder_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">A.csv</span><span class="sh">'</span><span class="p">,</span> <span class="n">index_col</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span> +<span class="n">emission_probas</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="nf">read_csv</span><span class="p">(</span><span class="n">folder_path</span> <span class="o">+</span> <span class="sh">'</span><span class="s">B.csv</span><span class="sh">'</span><span class="p">,</span> <span class="n">index_col</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span> +<span class="n">hidden_states</span> <span class="o">=</span> <span class="n">emission_probas</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> +<span class="n">init_probas</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">hidden_states</span><span class="p">)</span> +<span class="n">init_probas</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span> +<span class="n">hmm</span> <span class="o">=</span> <span class="nc">HMM</span><span class="p">(</span><span class="n">init_probas</span><span class="p">,</span> <span class="n">transition_probas</span><span class="p">,</span> <span class="n">emission_probas</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>The important method is <code class="highlight language-python" data-lang="python"><span class="nc">HMM</span><span class="p">(</span><span class="n">init_probas</span><span class="p">,</span> <span class="n">transition_probas</span><span class="p">,</span> <span class="n">emission_probas</span><span class="p">)</span></code>. Given the transition and emission matrices and given also some initial probabilities for the hidden states it returns an object that can decode any sequence. The details of how to create the matrices will be described in later posts of the series. I can advance you a bit about the process. You first begin by drawing a graph in paper with the transitions you like, you then parse that graph in paper into a graph in digital format (.gml). And finally, you use the adjacency matrices of the graph as the probability matrices. But for now, let’s assume we already have those files. How can we decode a sequence? Like this.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +</pre></td><td class="rouge-code"><pre><span class="n">decoded_seq</span> <span class="o">=</span> <span class="n">hmm</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">sequences</span><span class="p">)</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>The only concern to bear in mind is that the input and the output are numbers starting from zero. You need to parse those values to have something significant. I normally use the column names of the matrices as dictionaries to parse the sequences.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +</pre></td><td class="rouge-code"><pre><span class="n">indexer_hidden</span> <span class="o">=</span> <span class="nf">dict</span><span class="p">()</span> +<span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">col</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">transition_probas</span><span class="p">.</span><span class="n">columns</span><span class="p">):</span> + <span class="n">indexer_hidden</span><span class="p">[</span><span class="n">col</span><span class="p">]</span> <span class="o">=</span> <span class="n">k</span> +<span class="n">indexer_obs</span> <span class="o">=</span> <span class="nf">dict</span><span class="p">()</span> +<span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">col</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">emission_probas</span><span class="p">.</span><span class="n">columns</span><span class="p">):</span> + <span class="n">indexer_obs</span><span class="p">[</span><span class="n">col</span><span class="p">]</span> <span class="o">=</span> <span class="n">k</span> +</pre></td></tr></tbody></table></code></pre></div></div> + +<p>Putting everything together, to decode a sequence the program would look something like this code.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +</pre></td><td class="rouge-code"><pre><span class="c1">### ace in first service ### +</span><span class="n">sequence</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">player-hit</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> + <span class="sh">'</span><span class="s">bounce-ground-receiver</span><span class="sh">'</span><span class="p">,</span> <span class="o">*</span><span class="p">[</span><span class="sh">'</span><span class="s">flying</span><span class="sh">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span> <span class="sh">'</span><span class="s">end</span><span class="sh">'</span><span class="p">]</span> +<span class="n">sequences</span> <span class="o">=</span> <span class="p">[[</span><span class="n">indexer_obs</span><span class="p">[</span><span class="n">obs</span><span class="p">]</span> <span class="k">for</span> <span class="n">obs</span> <span class="ow">in</span> <span class="n">sequence</span><span class="p">]]</span> +<span class="n">decoded_seq</span> <span class="o">=</span> <span class="n">hmm</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">sequences</span><span class="p">)</span> +<span class="n">decoded_seq</span> <span class="o">=</span> <span class="p">[</span><span class="n">hidden_states</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="k">for</span> <span class="n">idx</span> <span class="ow">in</span> <span class="n">decoded_seq</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> +<span class="nf">print</span><span class="p">(</span><span class="n">decoded_seq</span><span class="p">)</span> +<span class="c1"># Result: ['1st-serve', 'flying-1st-serve-0', 'flying-1st-serve-1', 'flying-1st-serve-2', 'flying-1st-serve-3', +# 'flying-1st-serve-4', 'flying-1st-serve-5', 'flying-1st-serve-5', 'flying-1st-serve-5', 'flying-1st-serve-5', +# 'flying-1st-serve-5', 'in1', 'flying-in1-0', 'flying-in1-1', 'flying-in1-2', 'flying-in1-3', 'flying-in1-4', +# 'flying-in1-5', 'flying-in1-5', 'flying-in1-5', 'flying-in1-5', 'flying-in1-5', 'ground', 'flying-ground-0', +# 'flying-ground-1', 'flying-ground-2', 'flying-ground-3', 'flying-ground-4', 'flying-ground-5', +# 'flying-ground-5', 'flying-ground-5', 'flying-ground-5', 'flying-ground-5', 'Point-server'] +</span></pre></td></tr></tbody></table></code></pre></div></div> + +<p>Don’t worry if you don’t understand all this fuzzy names, they are the names that I chose for the hidden states and observations. On my <a href="/HMM-padel-part2" target="_blank">next post</a> I will explain in detail what everything means. For now I just want you to notice that the HMM is correctly identifying the winner in this point. The sequence of observations correspond to an ace in the first service. The player hits the balls and it bounces twice in the other side. Any person watching the match will automatically identify that as an ace. Here, it gives more information than that. It recognizes the instant in which the ace is achieved. The hidden state <code class="highlight language-python" data-lang="python"><span class="n">in1</span></code> means the ball has correctly entered into the other side and the state <code class="highlight language-python" data-lang="python"><span class="n">ground</span></code> means that it has bounced again in the ground. After a while of having the ball flying, the model outputs the state <code class="highlight language-python" data-lang="python"><span class="n">Point</span><span class="o">-</span><span class="n">server</span></code> correctly giving the victory to the server.</p> + +<p>On my <a href="/HMM-padel-part2" target="_blank">next post</a> I will talk about the process of designing the transition and emission matrices. I will also talk about what is reasonable to be defined as observation and which hidden states are needed. And on a later post I will cover a lesson on noisy decoding. One important feature of Hidden Markov Models is that they can decode the sequence of observations even if it is wrong at some point. Like I said at the beginning you could ignore the match for some time and then you would still be able to recognize the winner. HMMs can go even further. Imagine that you are not looking the match and you are just hearing it from the radio. If the commentator makes some mistake you may hear something impossible like a player hitting twice the ball without the match finishing. HMM can decode the sequence correctly even in those situations. That is because HMMs work with probabilities. If a player hits twice the ball and the game continues, the HMM will identify that second hit as a mistake and ignore it. Of course, if the sequence is completely wrong, the result will be wrong too. But HMM are quite robust to noise in the input if you design them carefully. See you on my <a href="/HMM-padel-part2" target="_blank">next post</a> to learn how to design Hidden Markov Models.</p> + + Tue, 06 Sep 2022 00:00:00 +0000 + https://granadata.art/HMM-padel/ + https://granadata.art/HMM-padel/ + + + Hidden Markov Model + + + + + GAN convergence proof + <p>In my <a href="/gan_optimality_proof" target="_blank">previous post</a> about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the optimum of the cost function, although it didn’t say how to find such optimum. In this post I will specify how to find such optimum and prove that the algorithm provided works. Keep in mind that this algorithm is the original proposed by Ian Goodfellow, several improvements have been made since then. At the end I will describe some of the faults of this algorithm and various changes that have been tried since it was published.</p> + +<p>The algorithm can be described in pseudocode as follows:</p> + +<pre id="gan" class="pseudocode" style="display:hidden;"> + % This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition) + \begin{algorithm} + \caption{GAN convergence algorithm} + \begin{algorithmic} + \FOR{number of training iterations} + \FOR{\$k\$ steps} + \STATE Sample minibatch of \$m\$ noise samples \( \{ \)\$z\$\( ^{(1)},\dots,\)\$z\$\(^{(m)}\} \) from noise prior \$p\_z(z)\$. + \STATE Sample minibatch of \$m\$ examples \( \{ \)\$x\$\( ^{(1)},\dots,\)\$x\$\(^{(m)}\} \) from data generating distribution \$p\_\{data\}(z)\$. + \STATE Update the discriminator by ascending its stochastic gradient: + $\nabla_{\theta_d}\frac{1}{m} \sum_{i=1}^{m}[log(D (x^{(i)})) + log(1-D(G(z^{(i)})))$ + \normalsize + \ENDFOR + \STATE Sample minibatch of \$m\$ noise samples \( \{ \)\$z\$\( ^{(1)},\dots,\)\$z\$\(^{(m)}\} \) from noise prior \$p\_z(z)\$. + \STATE Update the generator by descending its stochastic gradient: $\nabla_{\theta_g}\frac{1}{m} \sum_{i=1}^{m}log(1-D(G(z^{(i)})))$ + \ENDFOR + \end{algorithmic} + \end{algorithm} +</pre> + +<p>Let’s disect the algorithm piece by piece. Recall which function we were trying to optimize:</p> + +<div>$$V(G,D) = \mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D(\textbf{x}))] + \mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))]$$</div> + +<p>We wanted the maximum with respect to the discriminator and then the minimum with respect to the generator:</p> + +<div>$$\min_G \max_D V(G,D)$$</div> + +<p>If you look at the pseudocode we are doing just that. We first apply gradient ascent on the discriminator and then gradient descent on the generator. You may have noticed that the formulas in the pseudocode are different from what I showed you in the previous post. That is because we are approximating the expectation using a Monte Carlo method. To compute the expected value of a variable you need to compute an integral which in this case, and many others, is intractable. For that reason the expectation is approximated by the mean:</p> + +<div>$$\mathbb{E}_{\textbf{x} \sim p}[f(\textbf{x})] \approx \frac{1}{m}\sum_{i=1}^{m} f(\textbf{x}^{(i)})$$</div> + +<p>where the $\textbf{x}^{(i)}$ are sampled from the distribution $p$. If you go to the pseudocode you will see that we sample the $\textbf{z}$ from the prior noise and the $\textbf{x}$ from the real data. You may have also noticed that there is one term missing when applying gradient descent for the generator. The reason for that is the first summand in the formula does not depend on the generator, therefore the gradient of that term is zero.</p> + +<p>As you can see the algorithm itself is quite intuitive. For maximization apply gradient ascent and for minimization you apply gradient descent. That’s it. But being simple and intuitive is not enough for an algorithm to be correct, there needs to be a proof of their correctness. In this case there needs to be a proof of convergence and optimality. And like every theorem, that proof is going to come with some hypothesis.</p> + +<p>The first hypothesis is that $D$ and $G$ have enough capacity. This means that whatever model you use for them can reach the optimum. That seems a pretty reasonable hypothesis, but in practice you never know how much complexity is enough complexity.</p> + +<p>The second hypothesis is that $D$ can reach the optimum at each step given $G$. This is basically saying that the parameter $k$ used is sufficiently large, and that the learning rate is sufficiently small so that other theorems of gradient descent convergence hold. That optimum is going to be called $D^*_G$. This hypothesis is less important than the next one, and in practice reaching the optimal discriminator at any step is a problem that I will describe later on in this post.</p> + +<p>The last hypothesis is that at each step the criterion $C(G)=\min_D(V(G,D))$ improves. Which is basically saying that gradient descent is working on the generator. Formally, the theorem can be expressed like follows</p> + +<div class="theorem"> +If $G$ and $D$ have enough capacity, and at each step of Algorithm 1, the discriminator is allowed to reach its optimum given $G$, and $p_g$ is updated so as to improve the criterion +<div>$$\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D^*_G(\textbf{x}))] + \mathbb{E}_{\textbf{x} \sim p_g}[\log(1-D^*_G(\textbf{x}))]$$</div> +then $p_g$ converges to $p_{data}$. +</div> + +<p>The proof consists of two main steps: proving the cost function is convex with respect to the generator and proving the gradient descent at the optimal $D$ given $G$ converges to the same as the gradient descent for the global optimal $D$. This way we don’t need to know the real optimal discriminator, just the one that is optimum for each generator. The proof for convexity is more technical and I will explain it later. Let’s go with showing we only need a suboptimal discriminator.</p> + +<p>As everything in math, we start with definitions and notation that will made the rest of the proof easier to follow. The first change is for the value function, let’s call $U(p_g,D)=V(G,D)$ to the value function, changing the dependency to the generated distribution instead of the model. This is to highlight that we are working with ideal models in the function space not in the parametric space. Let’s call $U(p_g) = \sup_DU(p_g,D)$ the value function for the optimal discriminator. Since we are working in the function space the supremum is used instead of the maximum. Now the problem is to find $\inf_{p_g}U(p_g)$.</p> + +<p>Assuming $U(p_g,D)$ is convex for every $D$ there is <a href="https://math.stackexchange.com/questions/3363996/convexity-of-supremum-of-convex-functions" target="_blank">a theorem</a> saying that $U(p_g)$ is also convex. Now, the key of the proof is showing that any <a href="https://en.wikipedia.org/wiki/Subgradient_method" target="_blank">subgradient</a> of $U(p_g,D^*_G)$ is also a subgradient of $U(p_g)$ when $D^*_G=\text{argsup}_D U(p_g,D)$. This way gradient descent on $U(p_g,D^*_G)$ converges to the same value as gradient descent on $U(p_g)$, since that function is convex and the global optimum exists, that optimum is found. Now, the details.</p> + +<p>Mathematically, the argument of the subgradients can be expressed as follows:</p> + +<div>$$\partial U(p_g, \text{argsup}_D U(p_g,D)) \subseteq \partial \sup_D U(p_g,D)$$</div> + +<p>Being $\partial f$ the <a href="https://en.wikipedia.org/wiki/Subderivative#The_subgradient" target="_blank">set of subgradients</a> of $f$. The proof of that is a matter of using correctly the definitions. We have $U(p_g,D^*_G)=U(p_g)$. If $g\in \partial U(p_g,D^*_G)$, then by definition we have $U(p_g’,D^*_G) \ge U(p_g,D^*_G) + g^T (p_g’-p_g)$ for every other distribution $p_g’$, being the last term the scalar product of functions. By definition of supremum we have $U(p_g’) \ge U(p_g’,D^*_G)$. Joining everything we obtain</p> +<div>$$U(p_g') \ge U(p_g',D^*_G) \ge U(p_g,D^*_G) + g^T (p_g'-p_g) = U(p_g) + g^T (p_g'-p_g)$$</div> +<p>And therefore $U(p_g’)\ge U(p_g) + g^T (p_g’-p_g)$ which shows that $g\in \partial U(p_g)$. In Algorithm 1 we don’t use subgradients, and that is because if we assume the cost function is differentiable, then the only subgradient is the gradient. And we always use cost functions that are differentiable. For that reason, this part of the proof can also be expressed like this</p> + +<div>$$\nabla V(G,D^*_G) = \nabla C(G)$$</div> + +<p>which gives a way of optimizing $C(G)=\sup_D V(G,D)$ without needing to know the function itself. Quite a nice property of convex functions. Let’s now prove that the function is in fact convex. What is the definition of convex in this context? It simply means that</p> +<div>$$U(tp_g'+(1-t)p_g,D) \ge tU(p_g',D)+(1-t)U(p_g,D)$$</div> + +<p>which, after removing at both sides the terms that doesn’t depend on $p_g$, translates to</p> + +<div>$$\mathbb{E}_{\textbf{x} \sim tp_g'+(1-t)p_g}[\log(1-D(\textbf{x}))] \ge t\mathbb{E}_{\textbf{x}\sim p_g'}[\log(1-D(\textbf{x}))] + (1-t)\mathbb{E}_{\textbf{x}\sim p_g}[\log(1-D(\textbf{x}))]$$</div> + +<p>To prove that, we are going to prove an even stronger statement, which is</p> + +<div>$$\mathbb{E}_{\textbf{x} \sim tp_g'+(1-t)p_g} \equiv t\mathbb{E}_{\textbf{x}\sim p_g'} + (1-t)\mathbb{E}_{\textbf{x}\sim p_g}$$</div> + +<p>And this is fairly easy to prove, we just need to recall the linearity of integrals</p> + +<div>$$\mathbb{E}_{\textbf{x} \sim tp_g'+(1-t)p_g} [f] =\int (tp_g'+(1-t)p_g)f = t\int p_g' f + (1-t) \int p_g f = t\mathbb{E}_{\textbf{x}\sim p_g'}[f] + (1-t)\mathbb{E}_{\textbf{x}\sim p_g}[f]$$</div> + +<p>which finally proves the convexity of $U(p_g,D)$ with respect to $p_g$. Let’s start now with the faults of this proof and why it doesn’t hold on practice. The main reason it doesn’t hold in practice is the convexity of the cost function. Whenever we change the set of all distributions $p_g$ by the set of parametrized generators $G$ the corresponding cost function becomes non-convex. This is so because we are reducing the space from an infinite convex space, to a finite space. Now $tG’+(1-t)G$ may not be any valid generator, and so the set of reproducible distributions could be non-convex. In addition, it is well-known that using deep neural networks creates non-convex costs functions. That limits the possibility of finding the global optimum by only using gradient descent.</p> + +<p>There is another problem with this proof. It requires that we find a perfect discriminator at each step, and after it we apply gradient descent on the generator. The reason why that doesn’t work is mainly numerical. A perfect discriminator is going to produce gradients close to zero. When propagating the gradient it results on the generator not training at all. In practice, many articles limit the ability of learning of the discriminator so that the gradient is non-zero. One technique for doing so is using a smaller learning rate for the discriminator. This way the discriminator is learning at a slower pace than the generator.</p> + +<p>However, there are no results either proving or disproving that a neural network is not well-suited for this task. Many researchers have achieved decent results when training GANs. I wouldn’t even be writing this posts at all if GANs were a loss of time, which they aren’t. In practice they can work very well, but bare in mind that they are difficult to train. There is no theorem guaranteeing that Algorithm 1 works always. And you may probably need to use a modification of Algorithm 1 to make it work. But all in all, you can consider real GANs as approximations of an ideal GAN that always converge. It’s better than nothing.</p> + +<script> + pseudocode.renderElement(document.getElementById("gan")); +</script> + +<script> + pseudocode.renderClass("pseudocode"); +</script> + + + Mon, 05 Sep 2022 00:00:00 +0000 + https://granadata.art/gan-convergence-proof/ + https://granadata.art/gan-convergence-proof/ + + + GAN + + Theory + + unsupervised learning + + + + + Headless installation of Ubuntu on Rasbperry + <p>This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from <a href="https://cdimage.ubuntu.com/releases/22.04.1/release/ubuntu-22.04.1-preinstalled-server-arm64+raspi.img.xz">here</a>. Then, using <a href="https://www.balena.io/etcher/" target="_blank">Balena Etcher</a> boot the image into a USB. The process is simple, just follow the steps of the program.</p> + +<p>Now, the configuration part. Access your bootable USB. In my case I accessed it via the bash command line with <code class="language-html highlighter-rouge">cd /Volumes/system-boot</code> but you can access it in any other ways. In order to enable SSH on the first boot, you need to create a file called <code class="language-html highlighter-rouge">ssh</code> with nothing on it. But, to be able to connect to the Raspberry you also need it to be connected to a wifi. By default, it doesn’t connect to any wifi, just to the ethernet in case it is plugged. So you need to modify the <code class="language-html highlighter-rouge">network-config</code> file. At the end of the file you can see something like this</p> +<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +</pre></td><td class="rouge-code"><pre>version: 2 +ethernets: + eth0: + dhcp4: true + optional: true +</pre></td></tr></tbody></table></code></pre></div></div> +<p>After that there are many commented lines. You need to uncomment and modify those lines. The first you need to do is to add your wifi**. For that, after <code class="language-html highlighter-rouge">access-points</code> include the SSID which is the name of the wifi, and the password of that wifi:</p> +<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +</pre></td><td class="rouge-code"><pre>access-points: + <span class="nt">&lt;SSID&gt;</span>: + password: "<span class="nt">&lt;password&gt;</span>" +</pre></td></tr></tbody></table></code></pre></div></div> +<p>Be sure to maintain those commas and remove any other wifis from there.</p> + +<p>Finally, insert the SD card, start your raspberry and you are free to go. Connect to your Raspberry Pi using the command <code class="language-html highlighter-rouge">ssh ubuntu@<span class="nt">&lt;raspberry</span> <span class="na">IP</span><span class="nt">&gt;</span></code>***, enter the default password “ubuntu”. You will be asked to change the default password and voilà, you have a server.</p> + +<p>*For a more detailed explanation you can go to the official <a href="https://ubuntu.com/tutorials/how-to-install-ubuntu-on-your-raspberry-pi#3-wifi-or-ethernet" target="_blank">Ubuntu page</a>. And for more details on how netplan and their config files work you can go to <a href="https://netplan.io/reference/#dhcp-overrides" target="_blank">their reference.</a></p> + +<p>**If you have a Mac and an iPhone you can create a wifi hotspot from the iPhone’s internet. That way you can access your Raspberry anywhere. <a href="https://www.howtogeek.com/214053/how-to-turn-your-mac-into-a-wi-fi-hotspot/" target="_blank">Here</a> you have a tutorial for creating the hotspot. One more thing, use the channel 1, otherwise the Raspberry Pi won’t be able to connect to that frequency.</p> + +<p>***To find the assigned IP yourself you can try nmaping several IPs in the range to see which one has the 22 port open. You can also follow <a href="https://osxdaily.com/2016/11/03/view-lan-device-ip-address-arp/" target="_blank">this tutorial for MacOS</a> or any other you find.</p> + + Tue, 30 Aug 2022 00:00:00 +0000 + https://granadata.art/headless-raspberry-setup/ + https://granadata.art/headless-raspberry-setup/ + + + Servers + + Raspberry Pi + + Tutorial + + + + + Stable Diffusion Tutorial (Deprecated) + <p>Three days ago Stable Diffusion was publicly released and today I am bringing to you an easy way of using the model without the need of having any kind of extra hardware, just your laptop and wifi connection. At the end I will also leave a script in case you do have some extra hardware and want to put that RTX 3080 to good use.</p> + +<p>In case any of you didn’t know what Stable Diffusion is, it is similar to DALLE·2. It is a diffusion model able to create images from text. For example, for the prompt “Amazing, complex, intricate and highly detailed treehouse in a snow covered bonsai tree on top of a table, steampunk, vibrant colors, vibrant, beautiful, contrast, neon highlights, Highly detailed, ray tracing, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by Beeple, Mike Winklemann, 8k” you get this amazing result.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/ex1.png" alt="Example images from stable diffusion" /></p> + +<p>If you want to be able to produce this astonishing images with just a few words and 20 seconds of computation continue reading. In the internet you may find many tutorials for using this model on Colab. However, the Colab notebooks sometimes don’t give you access to GPU resources and so you may take several minutes to generate one single image. To avoid that we are goint to run Stable Diffusion in Kaggle, their servers provide you with 30 weekly hours of GPU computation, which roughly translates to 5000 generated images, more than neccessary to satisfy your needs.</p> + +<p>The first step you need to do is to create a Kaggle and HuggingFace account. The Kaggle account is to have access to GPUs as I said before, and the HuggingFace account is to have access to the Stable Diffusion model. I’ll go step by step. Let’s create the HuggingFace account. Go to <a href="https://huggingface.co/">https://huggingface.co/</a>.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/huggingfaceRegister.png" alt="Hugging Face registration" /></p> + +<p>At the top right click on Sign Up.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/huggingfaceRegister2.png" alt="Hugging Face registration 2nd part" /></p> + +<p>Follow the steps and log in with your account. Then, when you are logged in go to Settings as showed in the next image.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/HFlogin.png" alt="Already logged HF page" /></p> + +<p>Now, go to the Access Tokens section.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/HFSettings.png" alt="Settings HF page" /></p> + +<p>Finally, let’s create our needed token. Click on New token.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/HFToken.png" alt="Token HF page" /></p> + +<p>Enter any name you like, I will use StableDiffusion for obvious reasons. You can use write or read permissions, but you only need read permissions so I advise you to leave it like that.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/HFToken2.png" alt="Token HF page 2" /></p> + +<p>And you should end up with something like this. Copy the token and save it for later use in Kaggle.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/HFToken3.png" alt="Token HF page 3" /></p> + +<p>Before you can use this token, you need to agree to the terms and conditions of the Stable Diffusion model. Go to the page <a href="https://huggingface.co/CompVis/stable-diffusion-v1-4">https://huggingface.co/CompVis/stable-diffusion-v1-4</a>, access the repository and accept the terms and conditions. If you cannot see the tick box, you just need to log in.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/access.png" alt="Terms" /></p> + +<p>Same process, to create your account go to <a href="https://www.kaggle.com/">https://www.kaggle.com/</a> and register yourself.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/kaggleRegister.png" alt="Kaggle registration" /></p> + +<p>Again, just follow the steps.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/kaggleRegister2.png" alt="Kaggle registration 2" /></p> + +<p>Once you are logged in, we can start generating images. I have created a notebook with everything explained. To go to the notebook just go to this link: <a href="https://www.kaggle.com/code/josepc/stable-diffusion-nsfw/notebook">https://www.kaggle.com/code/josepc/stable-diffusion-nsfw/notebook</a>. You should see something like this. Click on the 3 dots at the top right corner. And then go to Copy &amp; edit notebook.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/kaggleSD.png" alt="Kaggle SD notebook" /></p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/kaggleSD2.png" alt="Kaggle SD notebook 2" /></p> + +<p>Ok, if it is your first time at kaggle there are a few things to explain before running the notebook. Once you are inside the notebook editor, to start the notebook you have to click the On button, but first you need to make sure the GPU is enabled and that the internet is enabled too. To do this, click the bottom right arrow.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/notebook.png" alt="notebook" /></p> + +<p>You should see this,</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/notebook2.png" alt="notebook 2" /></p> + +<p>If you don’t see the GPU there, then click on it and set it to GPU. Leave everything else as it is and start the notebook. Once the notebook started some extra bars appear to show the GPU is running.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/notebook3.png" alt="notebook 3" /></p> + +<p>To run each cell, click on it and do shift+Enter. Everything is explained there, but I’ll go cell by cell again here explaining what you need to modify to make it work for you.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/cell.png" alt="cell 1" /></p> + +<p>The first cell is just for installing libraries, run it as it is. The second one is where you actually need to enter your token. Modify the highlighted line copying there your HuggingFace token previously created. Keep in mind that the token should be enclosed by commas.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/cell2.png" alt="cell 2" /></p> + +<p>Once you hit shift+Enter, it will start downloading the model. It takes a few, you can see the progress like this. That 1.72G bar is the biggest one, when that is finished you are almost done.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/download.png" alt="download" /></p> + +<p>The third cell is for moving the model to the GPU, just run it normally with shift+Enter.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/cell3.png" alt="cell 3" /></p> + +<p>The fourth cell is the important one, here is where you are actually generating the images. There are two variables that you need to modify. The first one is <code class="language-html highlighter-rouge">num_images</code>, set it to the number of images you want to generate from a given prompt, but I advise you not to use more than 4 images at once for reasons I will mention at the end. The second variable is the <code class="language-html highlighter-rouge">prompt</code> variable, modify it to include your desired prompt. Delete the text in between commas (it is quite big) and write what you want. The result should be something like this: <code class="language-html highlighter-rouge">prompt = ['GIVEN TEXT'] * num_images</code>, don’t change the format or it won’t work. Just write your prompt inside the commas.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/cell4.png" alt="cell 4" /></p> + +<p>Once you hit shift+Enter, a progress bar will appear. When the value reaches 51 you are done. It takes nearly 20 seconds per image.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/counter.png" alt="progress bar" /></p> + +<p>The last two cells are for visualizing and saving the images. Just hit shift+Enter and there you have it, fabulous new images that noone has ever seen before.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/cellOutput.png" alt="cell output" /></p> + +<h2 id="possible-errors">Possible errors</h2> + +<p>There is the chance that you ran into an error call CUDA out of memory. When that happens, the only solution is to reset the notebook and rerun everything. If you create images in batches of 4, then it is quite difficult for that error to happen. But if for some reason you see a big red error message, just ignore it and restart your notebook. For those interested, the reason why that error happens is because there is a memory leakage into the GPU, the model creates some auxiliary tensors and then forgets of their existence. You cannot delete them because they are internal to the model, and the cache memory manager cannot delete them because they are still active, although never used. To solve it one would have to find the references of the allocated tensors and deallocate them manually, but it is easier to just restart the environment.</p> + +<p>The error looks like this, so that it doesn’t take you by surprise. If you go to the end of the large message you will see this.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/error.png" alt="error message" /></p> + +<h2 id="nsfw-version">NSFW version</h2> + +<p>The tutorial I showed you is for using the standard Stable Diffusion version from Hugging Face which has a safety checker to ensure you don’t generate nasty images. However, since the model is Open Source, it is possible to modify the code to remove that safety checker. In the name of liberty, I created another version of the notebook removing that safety checker. If you go to <a href="https://www.kaggle.com/code/josepc/stable-diffusion-nsfw">the original link</a> of the notebook, you will see that there is a box stating Version 2 of 2.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/version.png" alt="version" /></p> + +<p>If you click it you can see the first version which contains two extra cells to remove the safety checker. You can run that version or copy those cells into your copy of the other version. These are the cells. The first one is for loading extra libraries, and the second one is the actual code removing the checker.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/libs.png" alt="extra libs" /></p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/code.png" alt="extra code" /></p> + +<p>This last cell is quite large, but the only important change was done at the end. Commenting out those two lines is the only thing needed.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/code2.png" alt="extra code 2" /></p> + +<p>After removing the checker you just change the original call function with the modified one.</p> + +<p class="text-center"><img class="shadow" src="/assets/images/StableDiffusion/code3.png" alt="extra code 3" /></p> + +<p>But you don’t need to understand this, just copy and use it. I have taken the time to make it work for you.</p> + +<h2 id="script-version">Script version</h2> + +<p>Since I have explained everything before I will just leave the script version here for those of you that have access to some server with GPUs or those of you rich enough to have GPUs in your houses. The usage is quite simple, there are only 3 flags that you need to know: the number of images to generate, the prompt and the token. The <code class="language-html highlighter-rouge">save_path</code> can be left with the default value. The whole script is pasted below.</p> + +<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 +2 +3 +4 +5 +6 +7 +8 +9 +</pre></td><td class="rouge-code"><pre>Creates several images from a given prompt. + +optional arguments: + -h, --help Usage python3 StableDiffusion.py -n N --promp Text --token HuggingFaceToken [--save_path dir]]] + -n N Number of images to output. + --prompt PROMPT Prompt to generate image. + --token TOKEN HuggingFace token to download the model. + --save_path SAVE_PATH + Path to the folder to save the results. +</pre></td></tr></tbody></table></code></pre></div></div> + +<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="n">os</span> +<span class="kn">import</span> <span class="n">inspect</span> +<span class="kn">import</span> <span class="n">warnings</span> +<span class="kn">from</span> <span class="n">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Optional</span><span class="p">,</span> <span class="n">Union</span> +<span class="kn">import</span> <span class="n">torch</span> +<span class="kn">from</span> <span class="n">torch</span> <span class="kn">import</span> <span class="n">autocast</span> +<span class="kn">from</span> <span class="n">tqdm.auto</span> <span class="kn">import</span> <span class="n">tqdm</span> +<span class="kn">from</span> <span class="n">transformers</span> <span class="kn">import</span> <span class="n">CLIPFeatureExtractor</span><span class="p">,</span> <span class="n">CLIPTextModel</span><span class="p">,</span> <span class="n">CLIPTokenizer</span> +<span class="kn">from</span> <span class="n">diffusers.models</span> <span class="kn">import</span> <span class="n">AutoencoderKL</span><span class="p">,</span> <span class="n">UNet2DConditionModel</span> +<span class="kn">from</span> <span class="n">diffusers.pipeline_utils</span> <span class="kn">import</span> <span class="n">DiffusionPipeline</span> +<span class="kn">from</span> <span class="n">diffusers.schedulers</span> <span class="kn">import</span> <span class="n">DDIMScheduler</span><span class="p">,</span> <span class="n">LMSDiscreteScheduler</span><span class="p">,</span> <span class="n">PNDMScheduler</span> +<span class="kn">from</span> <span class="n">diffusers</span> <span class="kn">import</span> <span class="n">StableDiffusionPipeline</span> +<span class="kn">import</span> <span class="n">argparse</span> + +<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="p">.</span><span class="nc">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="sh">'</span><span class="s">Creates several images from a given prompt.</span><span class="sh">'</span><span class="p">,</span> <span class="n">add_help</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span> +<span class="n">parser</span><span class="p">.</span><span class="nf">add_argument</span><span class="p">(</span><span class="sh">'</span><span class="s">-h</span><span class="sh">'</span><span class="p">,</span> <span class="sh">'</span><span class="s">--help</span><span class="sh">'</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="sh">'</span><span class="s">help</span><span class="sh">'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="n">argparse</span><span class="p">.</span><span class="n">SUPPRESS</span><span class="p">,</span> + <span class="n">help</span><span class="o">=</span><span class="sh">'</span><span class="s">Usage python3 StableDiffusion.py -n N --promp Text --token HuggingFaceToken [--save_path dir]]]</span><span class="sh">'</span><span class="p">)</span> +<span class="n">parser</span><span class="p">.</span><span class="nf">add_argument</span><span class="p">(</span><span class="sh">'</span><span class="s">-n</span><span class="sh">'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="sh">'</span><span class="s">1</span><span class="sh">'</span><span class="p">,</span> + <span class="n">help</span><span class="o">=</span><span class="sh">'</span><span class="s">Number of images to output.</span><span class="sh">'</span><span class="p">)</span> +<span class="n">parser</span><span class="p">.</span><span class="nf">add_argument</span><span class="p">(</span><span class="sh">'</span><span class="s">--prompt</span><span class="sh">'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> + <span class="n">help</span><span class="o">=</span><span class="sh">'</span><span class="s">Prompt to generate image.</span><span class="sh">'</span><span class="p">)</span> +<span class="n">parser</span><span class="p">.</span><span class="nf">add_argument</span><span class="p">(</span><span class="sh">'</span><span class="s">--token</span><span class="sh">'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> + <span class="n">help</span><span class="o">=</span><span class="sh">'</span><span class="s">HuggingFace token to download the model.</span><span class="sh">'</span><span class="p">)</span> +<span class="n">parser</span><span class="p">.</span><span class="nf">add_argument</span><span class="p">(</span><span class="sh">'</span><span class="s">--save_path</span><span class="sh">'</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="sh">'</span><span class="s">outputs</span><span class="sh">'</span><span class="p">,</span> + <span class="n">help</span><span class="o">=</span><span class="sh">'</span><span class="s">Path to the folder to save the results.</span><span class="sh">'</span><span class="p">)</span> + +<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="p">.</span><span class="nf">parse_args</span><span class="p">()</span> + +<span class="n">pipe</span> <span class="o">=</span> <span class="n">StableDiffusionPipeline</span><span class="p">.</span><span class="nf">from_pretrained</span><span class="p">(</span><span class="sh">"</span><span class="s">CompVis/stable-diffusion-v1-4</span><span class="sh">"</span><span class="p">,</span> + <span class="n">revision</span><span class="o">=</span><span class="sh">"</span><span class="s">fp16</span><span class="sh">"</span><span class="p">,</span> + <span class="n">torch_dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float16</span><span class="p">,</span> + <span class="n">use_auth_token</span><span class="o">=</span><span class="n">args</span><span class="p">.</span><span class="n">token</span><span class="p">)</span> +<span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Loaded model</span><span class="sh">'</span><span class="p">)</span> + +<span class="nd">@torch.no_grad</span><span class="p">()</span> +<span class="k">def</span> <span class="nf">NSFWcall</span><span class="p">(</span> + <span class="n">self</span><span class="p">,</span> + <span class="n">prompt</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">]],</span> + <span class="n">height</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">512</span><span class="p">,</span> + <span class="n">width</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">512</span><span class="p">,</span> + <span class="n">num_inference_steps</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">50</span><span class="p">,</span> + <span class="n">guidance_scale</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="mf">7.5</span><span class="p">,</span> + <span class="n">eta</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">float</span><span class="p">]</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">,</span> + <span class="n">generator</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">torch</span><span class="p">.</span><span class="n">Generator</span><span class="p">]</span> <span class="o">=</span> <span class="bp">None</span><span class="p">,</span> + <span class="n">output_type</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="sh">"</span><span class="s">pil</span><span class="sh">"</span><span class="p">,</span> + <span class="o">**</span><span class="n">kwargs</span><span class="p">,</span> +<span class="p">):</span> + <span class="sh">"""</span><span class="s"> Modified version to remove the NSFW filter. </span><span class="sh">"""</span> + <span class="k">if</span> <span class="sh">"</span><span class="s">torch_device</span><span class="sh">"</span> <span class="ow">in</span> <span class="n">kwargs</span><span class="p">:</span> + <span class="n">device</span> <span class="o">=</span> <span class="n">kwargs</span><span class="p">.</span><span class="nf">pop</span><span class="p">(</span><span class="sh">"</span><span class="s">torch_device</span><span class="sh">"</span><span class="p">)</span> + <span class="n">warnings</span><span class="p">.</span><span class="nf">warn</span><span class="p">(</span> + <span class="sh">"</span><span class="s">`torch_device` is deprecated as an input argument to `__call__` and will be removed in v0.3.0.</span><span class="sh">"</span> + <span class="sh">"</span><span class="s"> Consider using `pipe.to(torch_device)` instead.</span><span class="sh">"</span> + <span class="p">)</span> + + <span class="c1"># Set device as before (to be removed in 0.3.0) +</span> <span class="k">if</span> <span class="n">device</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span> + <span class="n">device</span> <span class="o">=</span> <span class="sh">"</span><span class="s">cuda</span><span class="sh">"</span> <span class="k">if</span> <span class="n">torch</span><span class="p">.</span><span class="n">cuda</span><span class="p">.</span><span class="nf">is_available</span><span class="p">()</span> <span class="k">else</span> <span class="sh">"</span><span class="s">cpu</span><span class="sh">"</span> + <span class="n">self</span><span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span> + + <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">prompt</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span> + <span class="n">batch_size</span> <span class="o">=</span> <span class="mi">1</span> + <span class="k">elif</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">prompt</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span> + <span class="n">batch_size</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span> + <span class="k">else</span><span class="p">:</span> + <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">`prompt` has to be of type `str` or `list` but is </span><span class="si">{</span><span class="nf">type</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span> + + <span class="k">if</span> <span class="n">height</span> <span class="o">%</span> <span class="mi">8</span> <span class="o">!=</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">width</span> <span class="o">%</span> <span class="mi">8</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span> + <span class="k">raise</span> <span class="nc">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">`height` and `width` have to be divisible by 8 but are </span><span class="si">{</span><span class="n">height</span><span class="si">}</span><span class="s"> and </span><span class="si">{</span><span class="n">width</span><span class="si">}</span><span class="s">.</span><span class="sh">"</span><span class="p">)</span> + + <span class="c1"># get prompt text embeddings +</span> <span class="n">text_input</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">tokenizer</span><span class="p">(</span> + <span class="n">prompt</span><span class="p">,</span> + <span class="n">padding</span><span class="o">=</span><span class="sh">"</span><span class="s">max_length</span><span class="sh">"</span><span class="p">,</span> + <span class="n">max_length</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">tokenizer</span><span class="p">.</span><span class="n">model_max_length</span><span class="p">,</span> + <span class="n">truncation</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> + <span class="n">return_tensors</span><span class="o">=</span><span class="sh">"</span><span class="s">pt</span><span class="sh">"</span><span class="p">,</span> + <span class="p">)</span> + <span class="n">text_embeddings</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">text_encoder</span><span class="p">(</span><span class="n">text_input</span><span class="p">.</span><span class="n">input_ids</span><span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">device</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> + + <span class="c1"># here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) +</span> <span class="c1"># of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` +</span> <span class="c1"># corresponds to doing no classifier free guidance. +</span> <span class="n">do_classifier_free_guidance</span> <span class="o">=</span> <span class="n">guidance_scale</span> <span class="o">&gt;</span> <span class="mf">1.0</span> + <span class="c1"># get unconditional embeddings for classifier free guidance +</span> <span class="k">if</span> <span class="n">do_classifier_free_guidance</span><span class="p">:</span> + <span class="n">max_length</span> <span class="o">=</span> <span class="n">text_input</span><span class="p">.</span><span class="n">input_ids</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> + <span class="n">uncond_input</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">tokenizer</span><span class="p">(</span> + <span class="p">[</span><span class="sh">""</span><span class="p">]</span> <span class="o">*</span> <span class="n">batch_size</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="sh">"</span><span class="s">max_length</span><span class="sh">"</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="n">max_length</span><span class="p">,</span> <span class="n">return_tensors</span><span class="o">=</span><span class="sh">"</span><span class="s">pt</span><span class="sh">"</span> + <span class="p">)</span> + <span class="n">uncond_embeddings</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">text_encoder</span><span class="p">(</span><span class="n">uncond_input</span><span class="p">.</span><span class="n">input_ids</span><span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">device</span><span class="p">))[</span><span class="mi">0</span><span class="p">]</span> + + <span class="c1"># For classifier free guidance, we need to do two forward passes. +</span> <span class="c1"># Here we concatenate the unconditional and text embeddings into a single batch +</span> <span class="c1"># to avoid doing two forward passes +</span> <span class="n">text_embeddings</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">cat</span><span class="p">([</span><span class="n">uncond_embeddings</span><span class="p">,</span> <span class="n">text_embeddings</span><span class="p">])</span> + + <span class="c1"># get the intial random noise +</span> <span class="n">latents</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">randn</span><span class="p">(</span> + <span class="p">(</span><span class="n">batch_size</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">unet</span><span class="p">.</span><span class="n">in_channels</span><span class="p">,</span> <span class="n">height</span> <span class="o">//</span> <span class="mi">8</span><span class="p">,</span> <span class="n">width</span> <span class="o">//</span> <span class="mi">8</span><span class="p">),</span> + <span class="n">generator</span><span class="o">=</span><span class="n">generator</span><span class="p">,</span> + <span class="n">device</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">device</span><span class="p">,</span> + <span class="p">)</span> + + <span class="c1"># set timesteps +</span> <span class="n">accepts_offset</span> <span class="o">=</span> <span class="sh">"</span><span class="s">offset</span><span class="sh">"</span> <span class="ow">in</span> <span class="nf">set</span><span class="p">(</span><span class="n">inspect</span><span class="p">.</span><span class="nf">signature</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="n">set_timesteps</span><span class="p">).</span><span class="n">parameters</span><span class="p">.</span><span class="nf">keys</span><span class="p">())</span> + <span class="n">extra_set_kwargs</span> <span class="o">=</span> <span class="p">{}</span> + <span class="k">if</span> <span class="n">accepts_offset</span><span class="p">:</span> + <span class="n">extra_set_kwargs</span><span class="p">[</span><span class="sh">"</span><span class="s">offset</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span> + + <span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="nf">set_timesteps</span><span class="p">(</span><span class="n">num_inference_steps</span><span class="p">,</span> <span class="o">**</span><span class="n">extra_set_kwargs</span><span class="p">)</span> + + <span class="c1"># if we use LMSDiscreteScheduler, let's make sure latents are mulitplied by sigmas +</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">,</span> <span class="n">LMSDiscreteScheduler</span><span class="p">):</span> + <span class="n">latents</span> <span class="o">=</span> <span class="n">latents</span> <span class="o">*</span> <span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="n">sigmas</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> + + <span class="c1"># prepare extra kwargs for the scheduler step, since not all schedulers have the same signature +</span> <span class="c1"># eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. +</span> <span class="c1"># eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 +</span> <span class="c1"># and should be between [0, 1] +</span> <span class="n">accepts_eta</span> <span class="o">=</span> <span class="sh">"</span><span class="s">eta</span><span class="sh">"</span> <span class="ow">in</span> <span class="nf">set</span><span class="p">(</span><span class="n">inspect</span><span class="p">.</span><span class="nf">signature</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="n">step</span><span class="p">).</span><span class="n">parameters</span><span class="p">.</span><span class="nf">keys</span><span class="p">())</span> + <span class="n">extra_step_kwargs</span> <span class="o">=</span> <span class="p">{}</span> + <span class="k">if</span> <span class="n">accepts_eta</span><span class="p">:</span> + <span class="n">extra_step_kwargs</span><span class="p">[</span><span class="sh">"</span><span class="s">eta</span><span class="sh">"</span><span class="p">]</span> <span class="o">=</span> <span class="n">eta</span> + + <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">t</span> <span class="ow">in</span> <span class="nf">tqdm</span><span class="p">(</span><span class="nf">enumerate</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="n">timesteps</span><span class="p">)):</span> + <span class="c1"># expand the latents if we are doing classifier free guidance +</span> <span class="n">latent_model_input</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">cat</span><span class="p">([</span><span class="n">latents</span><span class="p">]</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span> <span class="k">if</span> <span class="n">do_classifier_free_guidance</span> <span class="k">else</span> <span class="n">latents</span> + <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">,</span> <span class="n">LMSDiscreteScheduler</span><span class="p">):</span> + <span class="n">sigma</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="n">sigmas</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> + <span class="n">latent_model_input</span> <span class="o">=</span> <span class="n">latent_model_input</span> <span class="o">/</span> <span class="p">((</span><span class="n">sigma</span><span class="o">**</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">**</span> <span class="mf">0.5</span><span class="p">)</span> + + <span class="c1"># predict the noise residual +</span> <span class="n">noise_pred</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">unet</span><span class="p">(</span><span class="n">latent_model_input</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">encoder_hidden_states</span><span class="o">=</span><span class="n">text_embeddings</span><span class="p">)[</span><span class="sh">"</span><span class="s">sample</span><span class="sh">"</span><span class="p">]</span> + + <span class="c1"># perform guidance +</span> <span class="k">if</span> <span class="n">do_classifier_free_guidance</span><span class="p">:</span> + <span class="n">noise_pred_uncond</span><span class="p">,</span> <span class="n">noise_pred_text</span> <span class="o">=</span> <span class="n">noise_pred</span><span class="p">.</span><span class="nf">chunk</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> + <span class="n">noise_pred</span> <span class="o">=</span> <span class="n">noise_pred_uncond</span> <span class="o">+</span> <span class="n">guidance_scale</span> <span class="o">*</span> <span class="p">(</span><span class="n">noise_pred_text</span> <span class="o">-</span> <span class="n">noise_pred_uncond</span><span class="p">)</span> + + <span class="c1"># compute the previous noisy sample x_t -&gt; x_t-1 +</span> <span class="k">if</span> <span class="nf">isinstance</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">,</span> <span class="n">LMSDiscreteScheduler</span><span class="p">):</span> + <span class="n">latents</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="nf">step</span><span class="p">(</span><span class="n">noise_pred</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">latents</span><span class="p">,</span> <span class="o">**</span><span class="n">extra_step_kwargs</span><span class="p">)[</span><span class="sh">"</span><span class="s">prev_sample</span><span class="sh">"</span><span class="p">]</span> + <span class="k">else</span><span class="p">:</span> + <span class="n">latents</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">scheduler</span><span class="p">.</span><span class="nf">step</span><span class="p">(</span><span class="n">noise_pred</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">latents</span><span class="p">,</span> <span class="o">**</span><span class="n">extra_step_kwargs</span><span class="p">)[</span><span class="sh">"</span><span class="s">prev_sample</span><span class="sh">"</span><span class="p">]</span> + + <span class="c1"># scale and decode the image latents with vae +</span> <span class="n">latents</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">/</span> <span class="mf">0.18215</span> <span class="o">*</span> <span class="n">latents</span> + <span class="n">image</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="n">vae</span><span class="p">.</span><span class="nf">decode</span><span class="p">(</span><span class="n">latents</span><span class="p">)</span> + + <span class="n">image</span> <span class="o">=</span> <span class="p">(</span><span class="n">image</span> <span class="o">/</span> <span class="mi">2</span> <span class="o">+</span> <span class="mf">0.5</span><span class="p">).</span><span class="nf">clamp</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> + <span class="n">image</span> <span class="o">=</span> <span class="n">image</span><span class="p">.</span><span class="nf">cpu</span><span class="p">().</span><span class="nf">permute</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">).</span><span class="nf">numpy</span><span class="p">()</span> + + <span class="c1"># run safety checker +</span> <span class="c1">#safety_cheker_input = self.feature_extractor(self.numpy_to_pil(image), return_tensors="pt").to(self.device) +</span> <span class="c1">#image, has_nsfw_concept = self.safety_checker(images=image, clip_input=safety_cheker_input.pixel_values) +</span> + <span class="k">if</span> <span class="n">output_type</span> <span class="o">==</span> <span class="sh">"</span><span class="s">pil</span><span class="sh">"</span><span class="p">:</span> + <span class="n">image</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">numpy_to_pil</span><span class="p">(</span><span class="n">image</span><span class="p">)</span> + + <span class="k">return</span> <span class="p">{</span><span class="sh">"</span><span class="s">sample</span><span class="sh">"</span><span class="p">:</span> <span class="n">image</span><span class="p">,</span> <span class="sh">"</span><span class="s">nsfw_content_detected</span><span class="sh">"</span><span class="p">:</span> <span class="bp">False</span><span class="p">}</span> + +<span class="c1"># Change the call function to remove NSFW filter +</span><span class="n">StableDiffusionPipeline</span><span class="p">.</span><span class="n">__call__</span> <span class="o">=</span> <span class="n">NSFWcall</span> +<span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Removed NSFW filter</span><span class="sh">'</span><span class="p">)</span> +<span class="n">pipe</span> <span class="o">=</span> <span class="n">pipe</span><span class="p">.</span><span class="nf">to</span><span class="p">(</span><span class="sh">"</span><span class="s">cuda</span><span class="sh">"</span><span class="p">)</span> +<span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Loaded model into GPU</span><span class="sh">'</span><span class="p">)</span> + +<span class="n">prompts</span> <span class="o">=</span> <span class="p">[</span><span class="n">args</span><span class="p">.</span><span class="n">prompt</span><span class="p">]</span> <span class="o">*</span> <span class="n">args</span><span class="p">.</span><span class="n">n</span> +<span class="k">with</span> <span class="nf">autocast</span><span class="p">(</span><span class="sh">"</span><span class="s">cuda</span><span class="sh">"</span><span class="p">):</span> + <span class="n">images</span> <span class="o">=</span> <span class="nf">pipe</span><span class="p">(</span><span class="n">prompts</span><span class="p">)[</span><span class="sh">"</span><span class="s">sample</span><span class="sh">"</span><span class="p">]</span> +<span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Executed model</span><span class="sh">'</span><span class="p">)</span> +<span class="k">if</span> <span class="n">args</span><span class="p">.</span><span class="n">save_path</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sh">'</span><span class="s">/</span><span class="sh">'</span><span class="p">:</span> + <span class="n">args</span><span class="p">.</span><span class="n">save_path</span> <span class="o">=</span> <span class="n">args</span><span class="p">.</span><span class="n">save_path</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> +<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="nf">isdir</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">save_path</span><span class="p">):</span> + <span class="n">os</span><span class="p">.</span><span class="nf">mkdir</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">save_path</span><span class="p">)</span> +<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">image</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">images</span><span class="p">):</span> + <span class="n">image</span><span class="p">.</span><span class="nf">save</span><span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">save_path</span><span class="o">+</span><span class="sh">"</span><span class="s">/output</span><span class="sh">"</span><span class="o">+</span><span class="nf">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="sh">"</span><span class="s">.png</span><span class="sh">"</span><span class="p">)</span> +<span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Saved images</span><span class="sh">'</span><span class="p">)</span> + </code></pre></figure> + + Thu, 25 Aug 2022 00:00:00 +0000 + https://granadata.art/stable_diffusion_tutorial/ + https://granadata.art/stable_diffusion_tutorial/ + + + Tutorial + + + + + Neural Architecture Search (Part 3) + <p>We have seen in <a href="/NAS" target="_blank">Part 1</a> and <a href="/NAS-parte2" target="_blank">Part 2</a> that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to it? The answer is obviously yes (otherwise there would be no point on this post). Let’s recap, NAS is just a method where you learn something that is non-learnable by backpropagation using trial and error, aka, reinforcement learning. The key for using it in more complex settings is to have a well defined space of parameters. In the first part that space was simply the real range for each hyperparameter. In the second part the search space included every recurrent network. Since that is a pretty broad space we narrowed it down by only working with some specific recurrent networks that could be generated by a tree-like structure. In this third part the search space is going to be the set of all convolutional neural networks. And to reduce that space to something manageable we are only going to consider networks generated by residual connections. Residual connections are typically just an addition operation like this</p> + +<p class="text-center"><img class="" src="/assets/images/NAS/residual-def.png" alt="simple" /></p> + +<p>The good property of residual connections is that they prevent vanishing gradients for very deep networks. Their discovery allowed to increase the number of layers. ResNet achieved state of the art results when it was first created, and big transformer-based architecture also use residual connections. Normally the residual connection is between one layer and the next one. Can you think of a way of generalising this? Is there a ways of creating a bigger set of possible architectures? Similar to what we did for recurrent networks, we could break that restriction of just connecting to the next layer. This way we could end up with something like the architecture presented in the original NAS paper</p> + +<p class="text-center"><img class="" src="/assets/images/NAS/orig-res.png" alt="simple" /></p> + +<p>Again, the real question is how to encode that architecture, because using a picture is not quite computer-friendly. Think of the way of representing a graph, you use the adjacency matrix, or the adjacency lists. Here it is similar, for each layer you just need to know the inputs. Or in other words, the architecture is saved as adjacency lists of incoming vertices. That’s simple enough for the controller to generate. For each layer the controller has an array of previous layers and simply selects one or more indices from there representing the incoming residual connections. The rest of hyperparameters are generated afterwards. See the diagram</p> + +<p class="text-center"><img class="" src="/assets/images/NAS/res-diagram.png" alt="simple" /></p> + +<p>What the anchor point box is doing is just that, sampling indices representing the incoming layers. That box may implemented as a feed forward network with several classes. It could seem that the number of classes varies and so we cannot fix the last layer to have a constant size, and that is true, but for a given layer it is always the same. Therefore, fixing the total number of layers also fixes the number of output classes for each anchor point and so we can train them. Another reasonable way to implement the anchor box is as a multinomial variable. That way you only need to learn the probabilities, maybe conditioned to something. Although that poses another problem since another way of applying backpropagation needs to be designed, probably by using some reparameterisation trick. The original implementation was done through attention. They gave some hidden state to each anchor box and used Bahdanau attention mechanism to select the most similar layers (according to that hidden state that is learnable) to use as residual connections. This solves the problem of variable sized input, you can use the same attention mechanism for every anchor point. Thus, reducing the number of total parameters while making all the connections related to each other.</p> + +<p>But simply selecting residual connections at random has one issue, have you seen it? There could be layers without connections, or layers that are not connected to anything. The solution is simple, for those layers that are not connected, you simply connect them to the last layer. And for those that doesn’t receive any input, the input image is used as the input, and not any other layer. This way, many complex architectures can emerge, not just linear ones. And there you have it, you can use reinforcement learning to explore the search space of convolutional neural networks.</p> + +<p>Here I have presented you three ways of using neural architecture search to better design neural network architectures. If you are feeling short of ideas to create networks, maybe try designing a broad search space and traing a NAS controller on a toy dataset. And don’t feel that it is an outdated technique, Google is still using it, see <a href="https://ai.googleblog.com/2022/08/building-efficient-multiple-visual.html">this article</a>. The main disadvantage of this tecnique is that it requires a lot of computational power, but as everything else, you can use it with reduced capabilities on smaller datasets to try on your own. Maybe you find the next transformer this way, who knows?</p> + + Mon, 22 Aug 2022 00:00:00 +0000 + https://granadata.art/NAS-parte3/ + https://granadata.art/NAS-parte3/ + + + Deep Learning + + Reinforcement Learning + + Supervised Learning + + Happy Ideas + + + + + GAN optimality proof + <p>The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that states GANs are optimal in the limit when no parametric model is taken into account. But first, what is a GAN and what “amazing results” am I talking about?</p> + +<p class="text-center"><img class="" src="/assets/images/GAN/pix2pix.png" alt="simple" /></p> + +<p>The above example is about pix2pix which is a conditional adversarial network which gives style to your draft. You draw some sketch, tell some style and the network does the rest of the work. But there are other types of GANs, like style GAN which is very good at generating images of some given type, like the ones below, which are all artificial.</p> + +<p class="text-center"><img class="" src="/assets/images/GAN/stylegan.png" alt="simple" /></p> + +<p>GANs are also related to DALLE-2, the famous text-to-image model. Both are a special case of energy-based models, which is a more general framework. If you want to know more about it you can look at the <a href="https://www.youtube.com/watch?v=tVwV14YkbYs">Yann Lecunn lectures about it</a>.</p> + +<p>Let’s dive into the details of the original GAN formulation. Typically, GANs are presented with the following diagram.</p> + +<p class="text-center"><img class="" src="/assets/images/GAN/gan-diagram.png" alt="simple" /></p> + +<p>But I prefer to understand models in the mathematical realm. There a GAN is a min-max problem. Concretely it is <em>this</em> min-max problem:</p> + +<div>$$\min_G \max_D V(G,D)$$</div> + +<p>Where $D$ is the discriminator, it receives an image an outputs the probability of it being fake. $G$ is the generator, it receives some input vector $\textbf{z}$ and outputs a fake image, denoted by $\textbf{x}$. $V$ is a value function, which is defined as follows (each term will be explained in detail later):</p> + +<div>$$\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D(\textbf{x}))] + \mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))]$$</div> + +<p>How can we interpret those two terms? The first one is large when the discriminator is correctly identifying real images as real. The second one is large when the discriminator is correctly identifying the generator images as fake. Maximising this quantity yields a perfect discriminator. However, we want more than that, we want to fool the discriminator. That means creating a good generator. Fooling the discriminator means minimising the value function. Thus, we have the min-max problem as stated above. Intuitively the equilibrium of this problem will be reached when we have a perfect generator and the discriminator always outputs $\frac{1}{2}$ because the generated images are the same as the real ones. This is exactly what the optimality theorem from the original Ian Goodfellow’s paper proves.</p> + +<p>To understand the theorem, it is needed to give some formalism to the intuitive idea of perfect generator. What is a perfect generator? For us humans, it means that the images seem real. But mathematically, what does “real” means? It means that the probability distribution of the generated images and the probability distribution of the data are the same. Mathematically the images are random variables, whose observations are represented by a vector. If the real and fake vectors come from the same distribution, then, they can be considered to be the same. As an example, consider the probability distribution function (pdf) of images of Granada. From that pdf it is more probable to draw an image of the Alhambra than an image of the Eiffel tower. Also, random noise has zero probability on that pdf. So now, a good generator pdf should mimick that behaviour, giving high probability to images of Granada and low probability to everything else.</p> + +<p>In mathematical terms, the data pdf is denoted by $p_{data}(\textbf{x})$ and the generator pdf is $p_{g}(\textbf{x})$. So producing “real” images means that $p_{data}(\textbf{x}) = p_{g}(\textbf{x})$, $\forall \textbf{x}$. Now we can state the optimality theorem:</p> + +<div class="theorem"> The global minimum of $C(G) = \max_{D}V(G,D)$ is reached if and only if $p_{data}\equiv p_g$. Also, that minimum value is $-\log(4)$.</div> + +<p>Before proving it, let’s analyse the consequences of it and what it is telling us. The main takeaway is that the solution to the min-max problem gives a perfect generator, which means that we can learn to reproduce any probability distribution. For simpler distributions this doesn’t seem much, one can simply compute the histogram of a variable and use it as the estimated distribution. However, for images that is not possible. How can you compute the histogram of a set of images? There are no repeated values, we just have a bunch of different images with some similarities. So one can think of a GAN as a way of estimating the histogram of a set of images. But it is more than that, it also provides a way of drawing points (images) from that distribution.</p> + +<p>Another takeaway from the theorem is the optimal value of the value function. It may seem useless at first but it is a good indicator of whether or not the training is converging or not. Because in practice that optimal generator does not appears to us by divine revelation, an iterative method is typically used to find it. If you see that during training the model converges to a value different than $-\log(4) \approx -1.38$ then you can be certain that you have not solved the min-max problem.</p> + +<p>So far so good but, the generator as presented above is just a deterministic function, where does the variability comes from? It is there on the formulas, you just need to give a closer look. When defining the value function the second term was computed from the distribution $p_{\textbf{z}}$, what is that? It is a prior distribution for the input of the generator, which means that the generator is a function transforming one distribution into another. Mathematically this means $\textbf{z} \sim p_{\textbf{z}} \Rightarrow G(\textbf{z}) \sim p_g$, a key fact that will be used in the proof. The inference process is now quite easy, just draw a point from $p_{\textbf{z}}$ and apply the generator to get a new image. In practice you can just put any value you want for $\textbf{z}$ since the prior is a noise prior, not anything in particular.</p> + +<p>Finally, let’s prove the theorem. The first step is to find the optimal discriminator given the generator. After that, that value is substituted into the formulas and everything is rearranged into something with an obvious lower bound. I will skip many computational details, you can just check them by hand or if they don’t seem trivial to you, email me and I will write an appendix with more details. Let’s go, first part:</p> + +<div class="prop"> If $G$ is fixed, the optimal $D$ is +<div>$$ D^*(\textbf{x}) = \frac{p_{data}(\textbf{x})}{p_{data}(\textbf{x}) + p_{g}(\textbf{x})} $$</div> +</div> + +<div class="proof"> We have $V(G,D)=\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D(\textbf{x}))] + \mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))]$, in order to combine both integrals we need them to depend on the same variable $\textbf{x}$. To do so we are going to exploit the fact that $\textbf{z} \sim p_{\textbf{z}} \Rightarrow G(\textbf{z}) \sim p_g$. This fact, in conjunction with the <a href="https://en.wikipedia.org/wiki/Radon%E2%80%93Nikodym_theorem">Radon-Nikodym Theorem</a> lets us conclude that $\mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))] = \mathbb{E}_{\textbf{x} \sim p_{g}}[\log(1-D(\textbf{x}))]$. Where $\textbf{x} = G(\textbf{z})$. Now, if we express the expectations in integral form we get the following +<div>$$ \int_{\textbf{x}} p_{data}(\textbf{x})\log(D(\textbf{x})) + p_g(\textbf{x})\log(1-D(\textbf{x})) d\textbf{x} $$</div> + +The integrand is now bounded by a function that does not depend on $D$ and so that bound is the optimum. That bound is found by differentiating with respect to $D$ and equalling to zero. The integrand there is basically $a \log(D) + b\log(1-D)$, which has the maximum at $\frac{a}{a+b}$ if $a$, $b$ are constant with respect to $D$. I still have <a href="https://datascience.stackexchange.com/questions/113390/minor-error-in-ian-goodfellows-gan-optimality-proof">my doubts</a> with respect to that assumption, but assuming it we get that the maximum is reached when $D=\frac{p_{data}}{p_{data}+p_g}$ which ends the proof. +</div> + +<p>We now have found the optimal discrimator so we can now compute the value function for that optimal discriminator. If we call $C(G)=\max_{D}(V(D,G))$ the value of the value function for the optimal discriminator, we just want to find the minimum of $C(G)$ for any given generator. The “rearrangement” I mentioned above is the following</p> + +<div>$$ +\begin{align*} +\max_D V(G,D) &amp;=\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D^*(\textbf{x}))] + \mathbb{E}_{\textbf{x} \sim p_{g}}[1-\log(D^*(\textbf{x}))] \\ +&amp;= \mathbb{E}_{\textbf{x} \sim p_{data}}[\log(\frac{p_{data}(\textbf{x})}{p_{data}(\textbf{x}) + p_{g}(\textbf{x})})] + \mathbb{E}_{\textbf{x} \sim p_{g}}[\log(\frac{p_{g}(\textbf{x})}{p_{data}(\textbf{x}) + p_{g}(\textbf{x})}] \\ +&amp;= -\log(4) + KL(p_{data}||\frac{p_{data}+p_g}{2}) + KL(p_g||\frac{p_{data}+p_g}{2})\\ +&amp;= -\log(4) + 2 \cdot JSD(p_{data}||p_g) +\end{align*} +$$</div> + +<p>The acronyms means <a href="https://en.wikipedia.org/wiki/Kullback%E2%80%93Leibler_divergence">Kullback-Leibler divergence</a> and <a href="https://en.wikipedia.org/wiki/Jensen%E2%80%93Shannon_divergence">Jensen-Shannon divergence</a>. For a more in-depth explanation of this rearrangement you can look at this <a href="https://srome.github.io/An-Annotated-Proof-of-Generative-Adversarial-Networks-with-Implementation-Notes/">great post</a>.</p> + +<p>We are almost finished, there is a property of the JSD that states it is a nonnegative value, being zero if and only if both distributions are equal. That property translates into $C(G) \ge -\log(4)$ with equality if and only if $p_{data} = p_g$ exactly as desired. Magic, isn’t it?</p> + +<p>Unfortunately, this theorem only states that the optimal generator exists but it doesn’t give a way of finding it. In my <a href="/gan-convergence-proof" target="_blank">next post</a> on the GANs series I will show the proof of convergence for the algorithm also proposed by Ian Goodfellow which proves that such generators can be found, at least in theory.</p> + + Wed, 10 Aug 2022 00:00:00 +0000 + https://granadata.art/gan_optimality_proof/ + https://granadata.art/gan_optimality_proof/ + + + GAN + + Theory + + unsupervised learning + + + + + diff --git a/gan-convergence-proof/index.html b/gan-convergence-proof/index.html new file mode 100644 index 0000000..81a0956 --- /dev/null +++ b/gan-convergence-proof/index.html @@ -0,0 +1,549 @@ + + + + + + + + +GAN convergence proof | GranaData + + +GAN convergence proof | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 16 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

GAN convergence proof

+ +
+ + + + + + + + + + +
+ + + +

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the optimum of the cost function, although it didn’t say how to find such optimum. In this post I will specify how to find such optimum and prove that the algorithm provided works. Keep in mind that this algorithm is the original proposed by Ian Goodfellow, several improvements have been made since then. At the end I will describe some of the faults of this algorithm and various changes that have been tried since it was published.

+ +

The algorithm can be described in pseudocode as follows:

+ +
+    % This quicksort algorithm is extracted from Chapter 7, Introduction to Algorithms (3rd edition)
+    \begin{algorithm}
+    \caption{GAN convergence algorithm}
+    \begin{algorithmic}
+    \FOR{number of training iterations}
+        \FOR{\$k\$ steps}
+            \STATE Sample minibatch of \$m\$ noise samples \( \{ \)\$z\$\( ^{(1)},\dots,\)\$z\$\(^{(m)}\} \) from noise prior \$p\_z(z)\$.
+            \STATE Sample minibatch of \$m\$ examples \( \{ \)\$x\$\( ^{(1)},\dots,\)\$x\$\(^{(m)}\} \) from data generating distribution \$p\_\{data\}(z)\$.
+            \STATE Update the discriminator by ascending its stochastic gradient:
+            $\nabla_{\theta_d}\frac{1}{m} \sum_{i=1}^{m}[log(D (x^{(i)})) + log(1-D(G(z^{(i)})))$
+            \normalsize
+        \ENDFOR
+        \STATE Sample minibatch of \$m\$ noise samples \( \{ \)\$z\$\( ^{(1)},\dots,\)\$z\$\(^{(m)}\} \) from noise prior \$p\_z(z)\$.
+        \STATE Update the generator by descending its stochastic gradient: $\nabla_{\theta_g}\frac{1}{m} \sum_{i=1}^{m}log(1-D(G(z^{(i)})))$
+    \ENDFOR
+    \end{algorithmic}
+    \end{algorithm}
+
+ +

Let’s disect the algorithm piece by piece. Recall which function we were trying to optimize:

+ +
$$V(G,D) = \mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D(\textbf{x}))] + \mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))]$$
+ +

We wanted the maximum with respect to the discriminator and then the minimum with respect to the generator:

+ +
$$\min_G \max_D V(G,D)$$
+ +

If you look at the pseudocode we are doing just that. We first apply gradient ascent on the discriminator and then gradient descent on the generator. You may have noticed that the formulas in the pseudocode are different from what I showed you in the previous post. That is because we are approximating the expectation using a Monte Carlo method. To compute the expected value of a variable you need to compute an integral which in this case, and many others, is intractable. For that reason the expectation is approximated by the mean:

+ +
$$\mathbb{E}_{\textbf{x} \sim p}[f(\textbf{x})] \approx \frac{1}{m}\sum_{i=1}^{m} f(\textbf{x}^{(i)})$$
+ +

where the $\textbf{x}^{(i)}$ are sampled from the distribution $p$. If you go to the pseudocode you will see that we sample the $\textbf{z}$ from the prior noise and the $\textbf{x}$ from the real data. You may have also noticed that there is one term missing when applying gradient descent for the generator. The reason for that is the first summand in the formula does not depend on the generator, therefore the gradient of that term is zero.

+ +

As you can see the algorithm itself is quite intuitive. For maximization apply gradient ascent and for minimization you apply gradient descent. That’s it. But being simple and intuitive is not enough for an algorithm to be correct, there needs to be a proof of their correctness. In this case there needs to be a proof of convergence and optimality. And like every theorem, that proof is going to come with some hypothesis.

+ +

The first hypothesis is that $D$ and $G$ have enough capacity. This means that whatever model you use for them can reach the optimum. That seems a pretty reasonable hypothesis, but in practice you never know how much complexity is enough complexity.

+ +

The second hypothesis is that $D$ can reach the optimum at each step given $G$. This is basically saying that the parameter $k$ used is sufficiently large, and that the learning rate is sufficiently small so that other theorems of gradient descent convergence hold. That optimum is going to be called $D^*_G$. This hypothesis is less important than the next one, and in practice reaching the optimal discriminator at any step is a problem that I will describe later on in this post.

+ +

The last hypothesis is that at each step the criterion $C(G)=\min_D(V(G,D))$ improves. Which is basically saying that gradient descent is working on the generator. Formally, the theorem can be expressed like follows

+ +
+If $G$ and $D$ have enough capacity, and at each step of Algorithm 1, the discriminator is allowed to reach its optimum given $G$, and $p_g$ is updated so as to improve the criterion +
$$\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D^*_G(\textbf{x}))] + \mathbb{E}_{\textbf{x} \sim p_g}[\log(1-D^*_G(\textbf{x}))]$$
+then $p_g$ converges to $p_{data}$. +
+ +

The proof consists of two main steps: proving the cost function is convex with respect to the generator and proving the gradient descent at the optimal $D$ given $G$ converges to the same as the gradient descent for the global optimal $D$. This way we don’t need to know the real optimal discriminator, just the one that is optimum for each generator. The proof for convexity is more technical and I will explain it later. Let’s go with showing we only need a suboptimal discriminator.

+ +

As everything in math, we start with definitions and notation that will made the rest of the proof easier to follow. The first change is for the value function, let’s call $U(p_g,D)=V(G,D)$ to the value function, changing the dependency to the generated distribution instead of the model. This is to highlight that we are working with ideal models in the function space not in the parametric space. Let’s call $U(p_g) = \sup_DU(p_g,D)$ the value function for the optimal discriminator. Since we are working in the function space the supremum is used instead of the maximum. Now the problem is to find $\inf_{p_g}U(p_g)$.

+ +

Assuming $U(p_g,D)$ is convex for every $D$ there is a theorem saying that $U(p_g)$ is also convex. Now, the key of the proof is showing that any subgradient of $U(p_g,D^*_G)$ is also a subgradient of $U(p_g)$ when $D^*_G=\text{argsup}_D U(p_g,D)$. This way gradient descent on $U(p_g,D^*_G)$ converges to the same value as gradient descent on $U(p_g)$, since that function is convex and the global optimum exists, that optimum is found. Now, the details.

+ +

Mathematically, the argument of the subgradients can be expressed as follows:

+ +
$$\partial U(p_g, \text{argsup}_D U(p_g,D)) \subseteq \partial \sup_D U(p_g,D)$$
+ +

Being $\partial f$ the set of subgradients of $f$. The proof of that is a matter of using correctly the definitions. We have $U(p_g,D^*_G)=U(p_g)$. If $g\in \partial U(p_g,D^*_G)$, then by definition we have $U(p_g’,D^*_G) \ge U(p_g,D^*_G) + g^T (p_g’-p_g)$ for every other distribution $p_g’$, being the last term the scalar product of functions. By definition of supremum we have $U(p_g’) \ge U(p_g’,D^*_G)$. Joining everything we obtain

+
$$U(p_g') \ge U(p_g',D^*_G) \ge U(p_g,D^*_G) + g^T (p_g'-p_g) = U(p_g) + g^T (p_g'-p_g)$$
+

And therefore $U(p_g’)\ge U(p_g) + g^T (p_g’-p_g)$ which shows that $g\in \partial U(p_g)$. In Algorithm 1 we don’t use subgradients, and that is because if we assume the cost function is differentiable, then the only subgradient is the gradient. And we always use cost functions that are differentiable. For that reason, this part of the proof can also be expressed like this

+ +
$$\nabla V(G,D^*_G) = \nabla C(G)$$
+ +

which gives a way of optimizing $C(G)=\sup_D V(G,D)$ without needing to know the function itself. Quite a nice property of convex functions. Let’s now prove that the function is in fact convex. What is the definition of convex in this context? It simply means that

+
$$U(tp_g'+(1-t)p_g,D) \ge tU(p_g',D)+(1-t)U(p_g,D)$$
+ +

which, after removing at both sides the terms that doesn’t depend on $p_g$, translates to

+ +
$$\mathbb{E}_{\textbf{x} \sim tp_g'+(1-t)p_g}[\log(1-D(\textbf{x}))] \ge t\mathbb{E}_{\textbf{x}\sim p_g'}[\log(1-D(\textbf{x}))] + (1-t)\mathbb{E}_{\textbf{x}\sim p_g}[\log(1-D(\textbf{x}))]$$
+ +

To prove that, we are going to prove an even stronger statement, which is

+ +
$$\mathbb{E}_{\textbf{x} \sim tp_g'+(1-t)p_g} \equiv t\mathbb{E}_{\textbf{x}\sim p_g'} + (1-t)\mathbb{E}_{\textbf{x}\sim p_g}$$
+ +

And this is fairly easy to prove, we just need to recall the linearity of integrals

+ +
$$\mathbb{E}_{\textbf{x} \sim tp_g'+(1-t)p_g} [f] =\int (tp_g'+(1-t)p_g)f = t\int p_g' f + (1-t) \int p_g f = t\mathbb{E}_{\textbf{x}\sim p_g'}[f] + (1-t)\mathbb{E}_{\textbf{x}\sim p_g}[f]$$
+ +

which finally proves the convexity of $U(p_g,D)$ with respect to $p_g$. Let’s start now with the faults of this proof and why it doesn’t hold on practice. The main reason it doesn’t hold in practice is the convexity of the cost function. Whenever we change the set of all distributions $p_g$ by the set of parametrized generators $G$ the corresponding cost function becomes non-convex. This is so because we are reducing the space from an infinite convex space, to a finite space. Now $tG’+(1-t)G$ may not be any valid generator, and so the set of reproducible distributions could be non-convex. In addition, it is well-known that using deep neural networks creates non-convex costs functions. That limits the possibility of finding the global optimum by only using gradient descent.

+ +

There is another problem with this proof. It requires that we find a perfect discriminator at each step, and after it we apply gradient descent on the generator. The reason why that doesn’t work is mainly numerical. A perfect discriminator is going to produce gradients close to zero. When propagating the gradient it results on the generator not training at all. In practice, many articles limit the ability of learning of the discriminator so that the gradient is non-zero. One technique for doing so is using a smaller learning rate for the discriminator. This way the discriminator is learning at a slower pace than the generator.

+ +

However, there are no results either proving or disproving that a neural network is not well-suited for this task. Many researchers have achieved decent results when training GANs. I wouldn’t even be writing this posts at all if GANs were a loss of time, which they aren’t. In practice they can work very well, but bare in mind that they are difficult to train. There is no theorem guaranteeing that Algorithm 1 works always. And you may probably need to use a modification of Algorithm 1 to make it work. But all in all, you can consider real GANs as approximations of an ideal GAN that always converge. It’s better than nothing.

+ + + + + + +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/gan_optimality_proof/index.html b/gan_optimality_proof/index.html new file mode 100644 index 0000000..93962ef --- /dev/null +++ b/gan_optimality_proof/index.html @@ -0,0 +1,499 @@ + + + + + + + + +GAN optimality proof | GranaData + + +GAN optimality proof | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 15 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

GAN optimality proof

+ +
+ + + + + + + + + + +
+ + + +

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that states GANs are optimal in the limit when no parametric model is taken into account. But first, what is a GAN and what “amazing results” am I talking about?

+ +

simple

+ +

The above example is about pix2pix which is a conditional adversarial network which gives style to your draft. You draw some sketch, tell some style and the network does the rest of the work. But there are other types of GANs, like style GAN which is very good at generating images of some given type, like the ones below, which are all artificial.

+ +

simple

+ +

GANs are also related to DALLE-2, the famous text-to-image model. Both are a special case of energy-based models, which is a more general framework. If you want to know more about it you can look at the Yann Lecunn lectures about it.

+ +

Let’s dive into the details of the original GAN formulation. Typically, GANs are presented with the following diagram.

+ +

simple

+ +

But I prefer to understand models in the mathematical realm. There a GAN is a min-max problem. Concretely it is this min-max problem:

+ +
$$\min_G \max_D V(G,D)$$
+ +

Where $D$ is the discriminator, it receives an image an outputs the probability of it being fake. $G$ is the generator, it receives some input vector $\textbf{z}$ and outputs a fake image, denoted by $\textbf{x}$. $V$ is a value function, which is defined as follows (each term will be explained in detail later):

+ +
$$\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D(\textbf{x}))] + \mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))]$$
+ +

How can we interpret those two terms? The first one is large when the discriminator is correctly identifying real images as real. The second one is large when the discriminator is correctly identifying the generator images as fake. Maximising this quantity yields a perfect discriminator. However, we want more than that, we want to fool the discriminator. That means creating a good generator. Fooling the discriminator means minimising the value function. Thus, we have the min-max problem as stated above. Intuitively the equilibrium of this problem will be reached when we have a perfect generator and the discriminator always outputs $\frac{1}{2}$ because the generated images are the same as the real ones. This is exactly what the optimality theorem from the original Ian Goodfellow’s paper proves.

+ +

To understand the theorem, it is needed to give some formalism to the intuitive idea of perfect generator. What is a perfect generator? For us humans, it means that the images seem real. But mathematically, what does “real” means? It means that the probability distribution of the generated images and the probability distribution of the data are the same. Mathematically the images are random variables, whose observations are represented by a vector. If the real and fake vectors come from the same distribution, then, they can be considered to be the same. As an example, consider the probability distribution function (pdf) of images of Granada. From that pdf it is more probable to draw an image of the Alhambra than an image of the Eiffel tower. Also, random noise has zero probability on that pdf. So now, a good generator pdf should mimick that behaviour, giving high probability to images of Granada and low probability to everything else.

+ +

In mathematical terms, the data pdf is denoted by $p_{data}(\textbf{x})$ and the generator pdf is $p_{g}(\textbf{x})$. So producing “real” images means that $p_{data}(\textbf{x}) = p_{g}(\textbf{x})$, $\forall \textbf{x}$. Now we can state the optimality theorem:

+ +
The global minimum of $C(G) = \max_{D}V(G,D)$ is reached if and only if $p_{data}\equiv p_g$. Also, that minimum value is $-\log(4)$.
+ +

Before proving it, let’s analyse the consequences of it and what it is telling us. The main takeaway is that the solution to the min-max problem gives a perfect generator, which means that we can learn to reproduce any probability distribution. For simpler distributions this doesn’t seem much, one can simply compute the histogram of a variable and use it as the estimated distribution. However, for images that is not possible. How can you compute the histogram of a set of images? There are no repeated values, we just have a bunch of different images with some similarities. So one can think of a GAN as a way of estimating the histogram of a set of images. But it is more than that, it also provides a way of drawing points (images) from that distribution.

+ +

Another takeaway from the theorem is the optimal value of the value function. It may seem useless at first but it is a good indicator of whether or not the training is converging or not. Because in practice that optimal generator does not appears to us by divine revelation, an iterative method is typically used to find it. If you see that during training the model converges to a value different than $-\log(4) \approx -1.38$ then you can be certain that you have not solved the min-max problem.

+ +

So far so good but, the generator as presented above is just a deterministic function, where does the variability comes from? It is there on the formulas, you just need to give a closer look. When defining the value function the second term was computed from the distribution $p_{\textbf{z}}$, what is that? It is a prior distribution for the input of the generator, which means that the generator is a function transforming one distribution into another. Mathematically this means $\textbf{z} \sim p_{\textbf{z}} \Rightarrow G(\textbf{z}) \sim p_g$, a key fact that will be used in the proof. The inference process is now quite easy, just draw a point from $p_{\textbf{z}}$ and apply the generator to get a new image. In practice you can just put any value you want for $\textbf{z}$ since the prior is a noise prior, not anything in particular.

+ +

Finally, let’s prove the theorem. The first step is to find the optimal discriminator given the generator. After that, that value is substituted into the formulas and everything is rearranged into something with an obvious lower bound. I will skip many computational details, you can just check them by hand or if they don’t seem trivial to you, email me and I will write an appendix with more details. Let’s go, first part:

+ +
If $G$ is fixed, the optimal $D$ is +
$$ D^*(\textbf{x}) = \frac{p_{data}(\textbf{x})}{p_{data}(\textbf{x}) + p_{g}(\textbf{x})} $$
+
+ +
We have $V(G,D)=\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D(\textbf{x}))] + \mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))]$, in order to combine both integrals we need them to depend on the same variable $\textbf{x}$. To do so we are going to exploit the fact that $\textbf{z} \sim p_{\textbf{z}} \Rightarrow G(\textbf{z}) \sim p_g$. This fact, in conjunction with the Radon-Nikodym Theorem lets us conclude that $\mathbb{E}_{\textbf{z} \sim p_{\textbf{z}}}[\log(1-D(G(\textbf{z})))] = \mathbb{E}_{\textbf{x} \sim p_{g}}[\log(1-D(\textbf{x}))]$. Where $\textbf{x} = G(\textbf{z})$. Now, if we express the expectations in integral form we get the following +
$$ \int_{\textbf{x}} p_{data}(\textbf{x})\log(D(\textbf{x})) + p_g(\textbf{x})\log(1-D(\textbf{x})) d\textbf{x} $$
+ +The integrand is now bounded by a function that does not depend on $D$ and so that bound is the optimum. That bound is found by differentiating with respect to $D$ and equalling to zero. The integrand there is basically $a \log(D) + b\log(1-D)$, which has the maximum at $\frac{a}{a+b}$ if $a$, $b$ are constant with respect to $D$. I still have my doubts with respect to that assumption, but assuming it we get that the maximum is reached when $D=\frac{p_{data}}{p_{data}+p_g}$ which ends the proof. +
+ +

We now have found the optimal discrimator so we can now compute the value function for that optimal discriminator. If we call $C(G)=\max_{D}(V(D,G))$ the value of the value function for the optimal discriminator, we just want to find the minimum of $C(G)$ for any given generator. The “rearrangement” I mentioned above is the following

+ +
$$ +\begin{align*} +\max_D V(G,D) &=\mathbb{E}_{\textbf{x} \sim p_{data}}[\log(D^*(\textbf{x}))] + \mathbb{E}_{\textbf{x} \sim p_{g}}[1-\log(D^*(\textbf{x}))] \\ +&= \mathbb{E}_{\textbf{x} \sim p_{data}}[\log(\frac{p_{data}(\textbf{x})}{p_{data}(\textbf{x}) + p_{g}(\textbf{x})})] + \mathbb{E}_{\textbf{x} \sim p_{g}}[\log(\frac{p_{g}(\textbf{x})}{p_{data}(\textbf{x}) + p_{g}(\textbf{x})}] \\ +&= -\log(4) + KL(p_{data}||\frac{p_{data}+p_g}{2}) + KL(p_g||\frac{p_{data}+p_g}{2})\\ +&= -\log(4) + 2 \cdot JSD(p_{data}||p_g) +\end{align*} +$$
+ +

The acronyms means Kullback-Leibler divergence and Jensen-Shannon divergence. For a more in-depth explanation of this rearrangement you can look at this great post.

+ +

We are almost finished, there is a property of the JSD that states it is a nonnegative value, being zero if and only if both distributions are equal. That property translates into $C(G) \ge -\log(4)$ with equality if and only if $p_{data} = p_g$ exactly as desired. Magic, isn’t it?

+ +

Unfortunately, this theorem only states that the optimal generator exists but it doesn’t give a way of finding it. In my next post on the GANs series I will show the proof of convergence for the algorithm also proposed by Ian Goodfellow which proves that such generators can be found, at least in theory.

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/gpt4-audiobook/index.html b/gpt4-audiobook/index.html new file mode 100644 index 0000000..0d67f3c --- /dev/null +++ b/gpt4-audiobook/index.html @@ -0,0 +1,585 @@ + + + + + + + + +How to make a full audiobook with AI tools. | GranaData + + +How to make a full audiobook with AI tools. | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 7 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

How to make a full audiobook with AI tools.

+ +
+ + + + + + + + + + +
+ + + +

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you will see, it played out decently and that’s the reason for this tutorial. Since there is not yet a full consensus on the copyright of this material, I won’t be posting the full audiobook just in case. But if you follow this tutorial you may end up with a similar copy in your hands. The process is quite simple:

+ +
    +
  1. Ask GPT-4 to structure a book around a given topic, which in my case was “animals in Africa”.
  2. +
  3. Create the figures using DALLE·3.
  4. +
  5. Give texture to letters using Adobe text effects.
  6. +
  7. Compose letters and images in powerpoint.
  8. +
  9. Generate the audio using Tortoise.
  10. +
  11. Edit video and audio using Davinci Resolve.
  12. +
+ +

The objective is to reduce the human in the process as much as possible, although some careful editing and reviewing will always be neccesary. Let’s dive into the details.

+ +

GPT-4

+ +

One would think that a lot of prompt engineering is needed to make a full book, even if it is for children, right? Well, no. The prompt I used was simply this:

+ +
+

hey, i want to create a child book for my nephew, he loves animal from africa, like giraffes and lions and pumas. Would you like to help me build a narrative together with the proper animations?

+
+ +

Yeah, that’s it! Even with caligraphic errors GPT-4 understood the task and gave me this in response:

+ +
+

Narrative:

+ +

Title: “The Great African Adventure”

+ +

1. Introduction: +In a land far away, where the golden savannah stretches as far as the eye can see, three unlikely friends embark on an unforgettable journey. Meet Gigi the Giraffe, Leo the Lion, and Pippa the Puma.

+ +

2. The Lost Waterhole: +One day, Gigi notices that their favorite waterhole is starting to dry up. The three friends decide to search for a new one, ensuring they have water during the hot summer days.

+ +

3. The Mysterious Map: +While resting under an acacia tree, Pippa finds a tattered map blowing in the wind. It hints at a hidden waterhole, surrounded by lush greenery and cool shade. They decide to follow it.

+ +

4. Challenges Along the Way: +As they journey, they face various challenges. They cross rapid rivers, climb steep hills, and navigate through dense forests. Each friend uses their unique abilities to help the group. Gigi uses her long neck to scout ahead, Leo roars to scare away potential threats, and Pippa’s agility helps them find shortcuts.

+ +

5. The Hidden Oasis: +After days of searching, they stumble upon a hidden oasis, just as the map had shown. It’s more beautiful than they could have imagined, with fresh, clear water and plenty of food.

+ +

6. Conclusion: +The trio realizes that their journey was about more than just finding water. It was about friendship, teamwork, and the adventures they shared. They decide to keep the map as a reminder of their great African adventure.

+
+ +
+

Now, let’s create some illustrations to go with our story. I’ll start by creating images for the introduction and the first two events. Shall we?

+
+ +

Not bad, but not a book either, this is more like a summary. So, given that summary, I then asked GPT-4 to create the full book, chapter by chapter. This was my prompt for the introduction:

+ +
+

Okay, let’s be more sistematic. We have 6 chapters. Let’s start with the introduction. Write me a simple story that would fit in 5 pages with one sentence in each page. Also, for each page an illustration is needed. The characters must be the same as before: Gigi the Giraffe, Leo the Lion and Pippa the Puma.

+
+ +

And the response:

+ +
+

Introduction: Meeting the Trio

+ +

Page 1: +Sentence: “In the heart of the African savannah, three friends stood tall and proud.” +Illustration: A vast savannah landscape with Gigi the Giraffe, Leo the Lion, and Pippa the Puma standing together, the sun setting in the background.

+ +

Page 2: +Sentence: “Gigi, with her long neck, loved to watch the clouds and dream.” +Illustration: Gigi gazing upwards at the sky, a dreamy expression on her face, as whimsical cloud shapes float above.

+ +

Page 3: +Sentence: “Leo, with his majestic mane, loved to roar and ensure everyone was safe.” +Illustration: Leo standing on a small mound, letting out a powerful roar, with birds taking flight in the background.

+ +

Page 4: +Sentence: “Pippa, always curious, loved to explore hidden paths and trails.” +Illustration: Pippa sneaking through some tall grass, her tail flicking with excitement, as she discovers a hidden trail.

+ +

Page 5: +Sentence: “Together, they were ready for any adventure the savannah had in store.” +Illustration: The three of them walking side by side, with the vast savannah ahead, signaling the beginning of their journey.

+
+ +
+

Let’s start by creating the illustrations for these pages. Shall we?

+
+ +

And this is the process, you tell GPT-4 how many pages you want per chapter and it expands the summary to fit into that number of pages. Even better, it suggests some illustrations, let’s see how to make them a reality.

+ +

DALLE·3

+ +

A few months ago, to achieve the illustrations one would have to take the prompt suggested by GPT-4 and take it to Midjourney, or Stable Diffusion to generate a valid image. The process required a lot of trial and error and a bit of prompt engineering. Now, however, DALLE·3 is embedded inside GPT-4, so you just have to say “yes” and it will generate the illustrations, like this:

+ +
+
+ dalle1_1 +
+
+ dalle1_2 +
+
+ dalle1_3 +
+
+ dalle1_4 +
+
+ +

Since it is limited to generating four at a time you need to generate them in batches, which is a minor problem since you can just say “continue” and it will do the rest. As you can notice, even though I did not specify any specific style or artist, these images do not seem to be copyright-free. Some of them resemble too much to Disney style or to other cartoons. Technically, OpenAI gives you ownership of this images to do whatever you want, commercial or not. But it is not clear to me that you are really allowed to do whatever you want. The problem is, even if I wanted to credit the authors of this material, or ask for permission to cite, or even request a commercial license, I don’t know who is the author I should be asking permission for. Anyway, the process is as simple as I described here and it is more or less automatic. In many cases the algorithm does not generate what I ask it for, but by repeating the question several times you can arrive to a satisfying image almost always.

+ +

Next step, generating covers for every chapter. This is a bit more challenging since generating letters in images is a quite difficult task, and it fails horribly most of the time. For instance, generating the words “The Hidden Oasis” seems an impossible task, here are some examples of the failure cases:

+ +
+
+ fail1 +
+
+ fail2 +
+
+ fail3 +
+
+ fail4 +
+
+ fail4 +
+
+ fail4 +
+
+ +

With a lot of trial and error I could manage to get cover for all of the chapters, but re-generating the illustrations to include extra text on them seemed and impossible task. For that reason I decided to edit the text in Adobe Firefly.

+ +

Adobe text effects

+ +

I could have just used plain colors for the text, but it seemed too boring, so I decided to try this AI tool. By using a prompt like “moon” you could add texture to your letters based on it, although the result is not quite there yet in my opinion:

+ +

text

+ +

For every prompt, no matter what I told to the tool it always gave me dark images. I surrendered and decided to apply a gain to the result if the background was also dark. Nonetheless, I used this tool for all the images because I found it funny, look, there is another example:

+ +

text

+ +

This one is a bit more special, because I didn’t use any original prompt, I just used the whole sentece provided by GPT-4 “The oasis was a paradise, with crystal-clear water and an abundance of food.”. This way everything is automatic, you use the sentence as prompt to give texture to itself. The biggest limitation is that the tool only allows a few characters at a time, which made the process quite long and exhausting. The most consuming part was realizing that the text was not even useful because the color didn’t match the scene. This is probably the worst part of the book and the part that has the most room for improvement in terms of AI tools.

+ +

Tortoise

+ +

Given the catastrophic failure that the letters were, we need to fix it somehow. A possible solution for an unreadable text is to create an audiobook. Can’t you read the text? No problem, here is somebody reading it for you. Well, more like something. Because we are going to use another AI tool to generate the voices automatically. This tool is not deployed on any pages like the previous tools. If you want to use it you will need to install it in your machine or in colab. In their github repository you have instructions on how to do it. You will need an NVIDIA GPU with CUDA capabilities or a mac with Apple Silicon or a lot of time, you choose. In my case I have a Mac M1 Max and the generation of all the 33 audios took one and a half hour. As an example, the following command generated the audio that is below:

+ +
1
+
PYTORCH_ENABLE_MPS_FALLBACK=1 python tortoise/do_tts.py --text "\[I am really excited,\] Let's follow the birds; they'll lead us to water." --voice train_dreams --preset fast --output_path book/chapter2/audio4/ --candidates 1 --seed 210501;
+
+ + + +

The quality is decent. Its main limitation is the generation of emotions. As you can see in the example above, it is possible to trick the algorithm into expressing emotions, but there is much room for improvement and you need to specify which emotion you want. You could probably use GPT-4 to automatically identify which emotions to give, but for this side project I didn’t want to overcomplicate things.

+ +

Davinci Resolve

+ +

Finally, let’s put everything together and generate the audiobook. We have images, text and audio. So, we can create a video. In my case I decided to compose images and text in powerpoint and then use a predefined animation to pass between pages. Then, I put the presentation in automatic mode giving each slide 8 seconds and recorded the screen. After that, I had to manually synchronize audio and video by cutting and moving segments. Here is the timeline before editing:

+ +

before

+ +

And here is after editing:

+ +

after

+ +

Hit render and you are done.

+ +

Final thoughts

+ +

The whole process took me 3 days, but I was experimenting the most part. If this was fully automated it could be done in 1 day. Imagine, a editorial could put a hole office of, let’s say, 20 employees and create 400 books in a month. Even more, if the process gets even more automatic, a child could read a different book each day of its whole life, or at least until this kind of books bore them. Nobody can deny this technology is going to have a huge impact in ours lives. Either positive or negative is still to be seen. For me, this can have a good impact since it will democratize knowledge even more. Not only that, people will be able to create masterpieces, books, films and many more. You will no longer need a huge budget to do this kind of things. Indie creators could compete with huge studios or editorials. It will all depend on what the legislation will say. If future (or present) politicians decide against this technology, that democratization will never arrive. Big fishes will remain big, and monopolies will win. As much as I believe this technology is capable of wonderful creations, I also believe that politicians are capable of awful laws. Time will say.

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/headless-raspberry-setup/index.html b/headless-raspberry-setup/index.html new file mode 100644 index 0000000..3a2b532 --- /dev/null +++ b/headless-raspberry-setup/index.html @@ -0,0 +1,437 @@ + + + + + + + + +Headless installation of Ubuntu on Rasbperry | GranaData + + +Headless installation of Ubuntu on Rasbperry | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 1 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Headless installation of Ubuntu on Rasbperry

+ +
+ + + + + + + + + + +
+ + + +

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena Etcher boot the image into a USB. The process is simple, just follow the steps of the program.

+ +

Now, the configuration part. Access your bootable USB. In my case I accessed it via the bash command line with cd /Volumes/system-boot but you can access it in any other ways. In order to enable SSH on the first boot, you need to create a file called ssh with nothing on it. But, to be able to connect to the Raspberry you also need it to be connected to a wifi. By default, it doesn’t connect to any wifi, just to the ethernet in case it is plugged. So you need to modify the network-config file. At the end of the file you can see something like this

+
1
+2
+3
+4
+5
+
version: 2
+ethernets:
+  eth0:
+    dhcp4: true
+    optional: true
+
+

After that there are many commented lines. You need to uncomment and modify those lines. The first you need to do is to add your wifi**. For that, after access-points include the SSID which is the name of the wifi, and the password of that wifi:

+
1
+2
+3
+
access-points:
+  <SSID>:
+    password: "<password>"
+
+

Be sure to maintain those commas and remove any other wifis from there.

+ +

Finally, insert the SD card, start your raspberry and you are free to go. Connect to your Raspberry Pi using the command ssh ubuntu@<raspberry IP>***, enter the default password “ubuntu”. You will be asked to change the default password and voilà, you have a server.

+ +

*For a more detailed explanation you can go to the official Ubuntu page. And for more details on how netplan and their config files work you can go to their reference.

+ +

**If you have a Mac and an iPhone you can create a wifi hotspot from the iPhone’s internet. That way you can access your Raspberry anywhere. Here you have a tutorial for creating the hotspot. One more thing, use the channel 1, otherwise the Raspberry Pi won’t be able to connect to that frequency.

+ +

***To find the assigned IP yourself you can try nmaping several IPs in the range to see which one has the 22 port open. You can also follow this tutorial for MacOS or any other you find.

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..708d7f3 --- /dev/null +++ b/index.html @@ -0,0 +1,750 @@ + + + + + + + + +Home | GranaData + + +Home | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ + + + + + + + +
+ +
+ +

All Stories

+ +
+ +
+ + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Deploying AWS webapp tutorial + +

+

Following the philosophy of my blog, this will be a very specific post. You can find many resources on the internet about how to deploy a web app. I will...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models (Part 3) + +

+

In part I and part II of this series I have talked about what is a Hidden Markov Model, why it is useful for modelling a padel match, and how...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models (Part 2) + +

+

In my last post I explained the usefulness of Hidden Markov Models for predicting the outcome of a padel match with only a few observations. There I also showed how...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Modelling a padel match with Hidden Markov Models + +

+

Imagine you are at a padel match. You watch the ball go from one side to the other, hit the wall, hit the ground, the net, and after some time...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ GAN convergence proof + +

+

In my previous post about GANs I explained the mathematical proof of why GANs work. The theorem stated that the real data distribution was mimicked by the generator at the...

+
+ +
+
+ + + + +
+ +
+ + +
+
+ + + + + +
+ + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/page2/index.html b/page2/index.html new file mode 100644 index 0000000..e5d0d5b --- /dev/null +++ b/page2/index.html @@ -0,0 +1,622 @@ + + + + + + + + +Home | GranaData + + +Page 2 of 2 for Home | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ + + +
+ +
+ +

All Stories

+ +
+ +
+ + + + + + +
+
+ +
+

+ Headless installation of Ubuntu on Rasbperry + +

+

This tutorial aims to be a fast* tutorial for installing Ubuntu Server into a Raspberry Pi and accessing it via ssh. First, download the image from here. Then, using Balena...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Stable Diffusion Tutorial (Deprecated) + +

+

Three days ago Stable Diffusion was publicly released and today I am bringing to you an easy way of using the model without the need of having any kind of...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 3) + +

+

We have seen in Part 1 and Part 2 that neural architecture search can be used to find hyperparameters and to design recurrent networks. Is there any more use to...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ GAN optimality proof + +

+

The so-called Generative Adversarial Networks have been with us since 2014, producing amazing results lately. Today I am bringing the proof of why they work. That is, a proof that...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search (Part 2) + +

+

In the previous article we discussed how we can use reinforcement learning to design simple architectures like some types of convolutional neural networks. Today I am bringing to you the...

+
+ +
+
+ + + + + + + +
+
+ +
+

+ Neural Architecture Search + +

+

Today we are going to dive into an idea that some may fear, and others may praise: AI training itself. Well, in reality the idea is a bit different from...

+
+ +
+
+ + + + +
+ +
+ + +
+
+ + + + + +
+ + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/read_time/index.html b/read_time/index.html new file mode 100644 index 0000000..d822d05 --- /dev/null +++ b/read_time/index.html @@ -0,0 +1,424 @@ + + + + + + + + +How is the reading time computed? | GranaData + + +How is the reading time computed? | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + <1 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

How is the reading time computed?

+ +
+ + + + + + + + + + +
+ + + +

The way the reading time is computed is quite easy. It follows this formula

+ +
$$ \frac{\text{Number of words in the post}}{\text{Average reading speed in words / min}} $$
+ +

The average reading speed for college students is assumed to be 300 words per minute, according to this page, for normal text. If there are formulas and new concepts I use 100 words per minute. When the post is very short, the time is simply $<1$ less than one minute, like this one. Also, the words are counted by copying the whole raw text with html tags into this page, and the result is rounded up so it may be bigger than expected.

+ +
+ + + + + +

+ + + + +

+ + +
+
    + + +
+
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..169c122 --- /dev/null +++ b/robots.txt @@ -0,0 +1 @@ +Sitemap: https://granadata.art/sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..beced63 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,125 @@ + + + +https://granadata.art/read_time/ +2000-05-21T00:00:00+00:00 + + +https://granadata.art/the-buddhist-pace/ +2022-06-26T00:00:00+00:00 + + +https://granadata.art/NAS/ +2022-07-12T00:00:00+00:00 + + +https://granadata.art/NAS-parte2/ +2022-07-13T00:00:00+00:00 + + +https://granadata.art/gan_optimality_proof/ +2022-08-10T00:00:00+00:00 + + +https://granadata.art/NAS-parte3/ +2022-08-22T00:00:00+00:00 + + +https://granadata.art/stable_diffusion_tutorial/ +2022-08-25T00:00:00+00:00 + + +https://granadata.art/headless-raspberry-setup/ +2022-08-30T00:00:00+00:00 + + +https://granadata.art/gan-convergence-proof/ +2022-09-05T00:00:00+00:00 + + +https://granadata.art/HMM-padel/ +2022-09-06T00:00:00+00:00 + + +https://granadata.art/HMM-padel-part2/ +2022-09-16T00:00:00+00:00 + + +https://granadata.art/HMM-padel-part3/ +2022-10-16T00:00:00+00:00 + + +https://granadata.art/WebApp-Docker-Django/ +2023-07-12T00:00:00+00:00 + + +https://granadata.art/gpt4-audiobook/ +2023-10-15T00:00:00+00:00 + + +https://granadata.art/about + + +https://granadata.art/categories + + +https://granadata.art/ + + +https://granadata.art/tags + + +https://granadata.art/category/stories/ + + +https://granadata.art/category/philosophy/ + + +https://granadata.art/category/deep-learning/ + + +https://granadata.art/category/reinforcement-learning/ + + +https://granadata.art/category/supervised-learning/ + + +https://granadata.art/category/happy-ideas/ + + +https://granadata.art/category/gan/ + + +https://granadata.art/category/theory/ + + +https://granadata.art/category/unsupervised-learning/ + + +https://granadata.art/category/tutorial/ + + +https://granadata.art/category/servers/ + + +https://granadata.art/category/raspberry-pi/ + + +https://granadata.art/category/hidden-markov-model/ + + +https://granadata.art/category/django/ + + +https://granadata.art/category/docker/ + + +https://granadata.art/category/aws/ + + +https://granadata.art/category/ai/ + + +https://granadata.art/page2/ + + diff --git a/stable_diffusion_tutorial/index.html b/stable_diffusion_tutorial/index.html new file mode 100644 index 0000000..56642cb --- /dev/null +++ b/stable_diffusion_tutorial/index.html @@ -0,0 +1,726 @@ + + + + + + + + +Stable Diffusion Tutorial (Deprecated) | GranaData + + +Stable Diffusion Tutorial (Deprecated) | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 10 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

Stable Diffusion Tutorial (Deprecated)

+ +
+ + + + + + + + + + +
+ + + +

Three days ago Stable Diffusion was publicly released and today I am bringing to you an easy way of using the model without the need of having any kind of extra hardware, just your laptop and wifi connection. At the end I will also leave a script in case you do have some extra hardware and want to put that RTX 3080 to good use.

+ +

In case any of you didn’t know what Stable Diffusion is, it is similar to DALLE·2. It is a diffusion model able to create images from text. For example, for the prompt “Amazing, complex, intricate and highly detailed treehouse in a snow covered bonsai tree on top of a table, steampunk, vibrant colors, vibrant, beautiful, contrast, neon highlights, Highly detailed, ray tracing, digital painting, artstation, concept art, smooth, sharp focus, illustration, art by Beeple, Mike Winklemann, 8k” you get this amazing result.

+ +

Example images from stable diffusion

+ +

If you want to be able to produce this astonishing images with just a few words and 20 seconds of computation continue reading. In the internet you may find many tutorials for using this model on Colab. However, the Colab notebooks sometimes don’t give you access to GPU resources and so you may take several minutes to generate one single image. To avoid that we are goint to run Stable Diffusion in Kaggle, their servers provide you with 30 weekly hours of GPU computation, which roughly translates to 5000 generated images, more than neccessary to satisfy your needs.

+ +

The first step you need to do is to create a Kaggle and HuggingFace account. The Kaggle account is to have access to GPUs as I said before, and the HuggingFace account is to have access to the Stable Diffusion model. I’ll go step by step. Let’s create the HuggingFace account. Go to https://huggingface.co/.

+ +

Hugging Face registration

+ +

At the top right click on Sign Up.

+ +

Hugging Face registration 2nd part

+ +

Follow the steps and log in with your account. Then, when you are logged in go to Settings as showed in the next image.

+ +

Already logged HF page

+ +

Now, go to the Access Tokens section.

+ +

Settings HF page

+ +

Finally, let’s create our needed token. Click on New token.

+ +

Token HF page

+ +

Enter any name you like, I will use StableDiffusion for obvious reasons. You can use write or read permissions, but you only need read permissions so I advise you to leave it like that.

+ +

Token HF page 2

+ +

And you should end up with something like this. Copy the token and save it for later use in Kaggle.

+ +

Token HF page 3

+ +

Before you can use this token, you need to agree to the terms and conditions of the Stable Diffusion model. Go to the page https://huggingface.co/CompVis/stable-diffusion-v1-4, access the repository and accept the terms and conditions. If you cannot see the tick box, you just need to log in.

+ +

Terms

+ +

Same process, to create your account go to https://www.kaggle.com/ and register yourself.

+ +

Kaggle registration

+ +

Again, just follow the steps.

+ +

Kaggle registration 2

+ +

Once you are logged in, we can start generating images. I have created a notebook with everything explained. To go to the notebook just go to this link: https://www.kaggle.com/code/josepc/stable-diffusion-nsfw/notebook. You should see something like this. Click on the 3 dots at the top right corner. And then go to Copy & edit notebook.

+ +

Kaggle SD notebook

+ +

Kaggle SD notebook 2

+ +

Ok, if it is your first time at kaggle there are a few things to explain before running the notebook. Once you are inside the notebook editor, to start the notebook you have to click the On button, but first you need to make sure the GPU is enabled and that the internet is enabled too. To do this, click the bottom right arrow.

+ +

notebook

+ +

You should see this,

+ +

notebook 2

+ +

If you don’t see the GPU there, then click on it and set it to GPU. Leave everything else as it is and start the notebook. Once the notebook started some extra bars appear to show the GPU is running.

+ +

notebook 3

+ +

To run each cell, click on it and do shift+Enter. Everything is explained there, but I’ll go cell by cell again here explaining what you need to modify to make it work for you.

+ +

cell 1

+ +

The first cell is just for installing libraries, run it as it is. The second one is where you actually need to enter your token. Modify the highlighted line copying there your HuggingFace token previously created. Keep in mind that the token should be enclosed by commas.

+ +

cell 2

+ +

Once you hit shift+Enter, it will start downloading the model. It takes a few, you can see the progress like this. That 1.72G bar is the biggest one, when that is finished you are almost done.

+ +

download

+ +

The third cell is for moving the model to the GPU, just run it normally with shift+Enter.

+ +

cell 3

+ +

The fourth cell is the important one, here is where you are actually generating the images. There are two variables that you need to modify. The first one is num_images, set it to the number of images you want to generate from a given prompt, but I advise you not to use more than 4 images at once for reasons I will mention at the end. The second variable is the prompt variable, modify it to include your desired prompt. Delete the text in between commas (it is quite big) and write what you want. The result should be something like this: prompt = ['GIVEN TEXT'] * num_images, don’t change the format or it won’t work. Just write your prompt inside the commas.

+ +

cell 4

+ +

Once you hit shift+Enter, a progress bar will appear. When the value reaches 51 you are done. It takes nearly 20 seconds per image.

+ +

progress bar

+ +

The last two cells are for visualizing and saving the images. Just hit shift+Enter and there you have it, fabulous new images that noone has ever seen before.

+ +

cell output

+ +

Possible errors

+ +

There is the chance that you ran into an error call CUDA out of memory. When that happens, the only solution is to reset the notebook and rerun everything. If you create images in batches of 4, then it is quite difficult for that error to happen. But if for some reason you see a big red error message, just ignore it and restart your notebook. For those interested, the reason why that error happens is because there is a memory leakage into the GPU, the model creates some auxiliary tensors and then forgets of their existence. You cannot delete them because they are internal to the model, and the cache memory manager cannot delete them because they are still active, although never used. To solve it one would have to find the references of the allocated tensors and deallocate them manually, but it is easier to just restart the environment.

+ +

The error looks like this, so that it doesn’t take you by surprise. If you go to the end of the large message you will see this.

+ +

error message

+ +

NSFW version

+ +

The tutorial I showed you is for using the standard Stable Diffusion version from Hugging Face which has a safety checker to ensure you don’t generate nasty images. However, since the model is Open Source, it is possible to modify the code to remove that safety checker. In the name of liberty, I created another version of the notebook removing that safety checker. If you go to the original link of the notebook, you will see that there is a box stating Version 2 of 2.

+ +

version

+ +

If you click it you can see the first version which contains two extra cells to remove the safety checker. You can run that version or copy those cells into your copy of the other version. These are the cells. The first one is for loading extra libraries, and the second one is the actual code removing the checker.

+ +

extra libs

+ +

extra code

+ +

This last cell is quite large, but the only important change was done at the end. Commenting out those two lines is the only thing needed.

+ +

extra code 2

+ +

After removing the checker you just change the original call function with the modified one.

+ +

extra code 3

+ +

But you don’t need to understand this, just copy and use it. I have taken the time to make it work for you.

+ +

Script version

+ +

Since I have explained everything before I will just leave the script version here for those of you that have access to some server with GPUs or those of you rich enough to have GPUs in your houses. The usage is quite simple, there are only 3 flags that you need to know: the number of images to generate, the prompt and the token. The save_path can be left with the default value. The whole script is pasted below.

+ +
1
+2
+3
+4
+5
+6
+7
+8
+9
+
Creates several images from a given prompt.
+
+optional arguments:
+  -h, --help            Usage python3 StableDiffusion.py -n N --promp Text --token HuggingFaceToken [--save_path dir]]]
+  -n N                  Number of images to output.
+  --prompt PROMPT       Prompt to generate image.
+  --token TOKEN         HuggingFace token to download the model.
+  --save_path SAVE_PATH
+                        Path to the folder to save the results.
+
+ +
import os
+import inspect
+import warnings
+from typing import List, Optional, Union
+import torch
+from torch import autocast
+from tqdm.auto import tqdm
+from transformers import CLIPFeatureExtractor, CLIPTextModel, CLIPTokenizer
+from diffusers.models import AutoencoderKL, UNet2DConditionModel
+from diffusers.pipeline_utils import DiffusionPipeline
+from diffusers.schedulers import DDIMScheduler, LMSDiscreteScheduler, PNDMScheduler
+from diffusers import StableDiffusionPipeline
+import argparse
+
+parser = argparse.ArgumentParser(description='Creates several images from a given prompt.', add_help=False)
+parser.add_argument('-h', '--help', action='help', default=argparse.SUPPRESS,
+                    help='Usage python3 StableDiffusion.py -n N --promp Text --token HuggingFaceToken [--save_path dir]]]')
+parser.add_argument('-n', type=int, default='1',
+                    help='Number of images to output.')
+parser.add_argument('--prompt', type=str,
+                    help='Prompt to generate image.')
+parser.add_argument('--token', type=str,
+                    help='HuggingFace token to download the model.')
+parser.add_argument('--save_path', type=str, default='outputs',
+                    help='Path to the folder to save the results.')
+
+args = parser.parse_args()
+
+pipe = StableDiffusionPipeline.from_pretrained("CompVis/stable-diffusion-v1-4", 
+                                               revision="fp16", 
+                                               torch_dtype=torch.float16, 
+                                               use_auth_token=args.token)
+print('Loaded model')
+
+@torch.no_grad()
+def NSFWcall(
+    self,
+    prompt: Union[str, List[str]],
+    height: Optional[int] = 512,
+    width: Optional[int] = 512,
+    num_inference_steps: Optional[int] = 50,
+    guidance_scale: Optional[float] = 7.5,
+    eta: Optional[float] = 0.0,
+    generator: Optional[torch.Generator] = None,
+    output_type: Optional[str] = "pil",
+    **kwargs,
+):
+    """ Modified version to remove the NSFW filter. """
+    if "torch_device" in kwargs:
+        device = kwargs.pop("torch_device")
+        warnings.warn(
+            "`torch_device` is deprecated as an input argument to `__call__` and will be removed in v0.3.0."
+            " Consider using `pipe.to(torch_device)` instead."
+        )
+
+        # Set device as before (to be removed in 0.3.0)
+        if device is None:
+            device = "cuda" if torch.cuda.is_available() else "cpu"
+        self.to(device)
+
+    if isinstance(prompt, str):
+        batch_size = 1
+    elif isinstance(prompt, list):
+        batch_size = len(prompt)
+    else:
+        raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
+
+    if height % 8 != 0 or width % 8 != 0:
+        raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
+
+    # get prompt text embeddings
+    text_input = self.tokenizer(
+        prompt,
+        padding="max_length",
+        max_length=self.tokenizer.model_max_length,
+        truncation=True,
+        return_tensors="pt",
+    )
+    text_embeddings = self.text_encoder(text_input.input_ids.to(self.device))[0]
+
+    # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
+    # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
+    # corresponds to doing no classifier free guidance.
+    do_classifier_free_guidance = guidance_scale > 1.0
+    # get unconditional embeddings for classifier free guidance
+    if do_classifier_free_guidance:
+        max_length = text_input.input_ids.shape[-1]
+        uncond_input = self.tokenizer(
+            [""] * batch_size, padding="max_length", max_length=max_length, return_tensors="pt"
+        )
+        uncond_embeddings = self.text_encoder(uncond_input.input_ids.to(self.device))[0]
+
+        # For classifier free guidance, we need to do two forward passes.
+        # Here we concatenate the unconditional and text embeddings into a single batch
+        # to avoid doing two forward passes
+        text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
+
+    # get the intial random noise
+    latents = torch.randn(
+        (batch_size, self.unet.in_channels, height // 8, width // 8),
+        generator=generator,
+        device=self.device,
+    )
+
+    # set timesteps
+    accepts_offset = "offset" in set(inspect.signature(self.scheduler.set_timesteps).parameters.keys())
+    extra_set_kwargs = {}
+    if accepts_offset:
+        extra_set_kwargs["offset"] = 1
+
+    self.scheduler.set_timesteps(num_inference_steps, **extra_set_kwargs)
+
+    # if we use LMSDiscreteScheduler, let's make sure latents are mulitplied by sigmas
+    if isinstance(self.scheduler, LMSDiscreteScheduler):
+        latents = latents * self.scheduler.sigmas[0]
+
+    # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
+    # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
+    # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
+    # and should be between [0, 1]
+    accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
+    extra_step_kwargs = {}
+    if accepts_eta:
+        extra_step_kwargs["eta"] = eta
+
+    for i, t in tqdm(enumerate(self.scheduler.timesteps)):
+        # expand the latents if we are doing classifier free guidance
+        latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
+        if isinstance(self.scheduler, LMSDiscreteScheduler):
+            sigma = self.scheduler.sigmas[i]
+            latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5)
+
+        # predict the noise residual
+        noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=text_embeddings)["sample"]
+
+        # perform guidance
+        if do_classifier_free_guidance:
+            noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
+            noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
+
+        # compute the previous noisy sample x_t -> x_t-1
+        if isinstance(self.scheduler, LMSDiscreteScheduler):
+            latents = self.scheduler.step(noise_pred, i, latents, **extra_step_kwargs)["prev_sample"]
+        else:
+            latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs)["prev_sample"]
+
+    # scale and decode the image latents with vae
+    latents = 1 / 0.18215 * latents
+    image = self.vae.decode(latents)
+
+    image = (image / 2 + 0.5).clamp(0, 1)
+    image = image.cpu().permute(0, 2, 3, 1).numpy()
+
+    # run safety checker
+    #safety_cheker_input = self.feature_extractor(self.numpy_to_pil(image), return_tensors="pt").to(self.device)
+    #image, has_nsfw_concept = self.safety_checker(images=image, clip_input=safety_cheker_input.pixel_values)
+
+    if output_type == "pil":
+        image = self.numpy_to_pil(image)
+
+    return {"sample": image, "nsfw_content_detected": False}
+
+# Change the call function to remove NSFW filter
+StableDiffusionPipeline.__call__ = NSFWcall
+print('Removed NSFW filter')
+pipe = pipe.to("cuda")
+print('Loaded model into GPU')
+
+prompts = [args.prompt] * args.n
+with autocast("cuda"):
+    images = pipe(prompts)["sample"]
+print('Executed model')
+if args.save_path[-1] == '/':
+    args.save_path = args.save_path[:-1]
+if not os.path.isdir(args.save_path):
+    os.mkdir(args.save_path)
+for i, image in enumerate(images):
+    image.save(args.save_path+"/output"+str(i)+".png")
+print('Saved images')
+ 
+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/tags.html b/tags.html new file mode 100644 index 0000000..b86a983 --- /dev/null +++ b/tags.html @@ -0,0 +1,339 @@ + + + + + + + + +Tags | GranaData + + +Tags | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+
+ +
+

Tag AI

+
+ + + + + + + +
+
+ +
+

+ How to make a full audiobook with AI tools. + +

+

A few days ago I got access to DALLE·3 and thought, “what if I made a book for children with it?” It may seem a bold idea, but as you...

+
+ +
+
+ + + + + + + +
+
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/the-buddhist-pace/index.html b/the-buddhist-pace/index.html new file mode 100644 index 0000000..f4f6fa5 --- /dev/null +++ b/the-buddhist-pace/index.html @@ -0,0 +1,419 @@ + + + + + + + + +The buddhist pace | GranaData + + +The buddhist pace | GranaData + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

GranaData

+

+ Data science from Granada to the world. +

+
+ + +
+ +
+ +
+ + + + + + +
+ + + +
+
+ + Jose + +
+
+ Jose + + + 3 min read + + Data scientist from Alcalá la Real. Studied at BarcelonaTech, worked as a researcher at the University of Granada and currently a machine learning engineer at El Ranchito. Always wanting to explain my knowledge to the world. +
+
+ + + +

The buddhist pace

+ +
+ + + + + + + + + + +
+ + + +

This is a story I read years ago from Reddit that has guided me through most parts of my life and whenever I want to engage in more that I can handle I remember it and calm myself down. The original story is lost, so I can only give to you my memory of it.

+ +

The story is about several hikers in what could possibly be their longest journey. One day they are challenged by a misterious man to complete a walk. They are told that the first one to arrive at the finish line will receive a generous prize. However, they won’t know where exactly is the finish line, their only tool will be a compass specially designed to point to the end and nothing else. No length is provided, no unevenness, and of course, no map.

+ +

compass

+ +

The hikers, confident on their experience and physical shape, started as fast as they could, some of them even running at the beginning. All except one, they called him the buddhist. He set himself a different pace, instead of going as fast as he could, he decided to go as fast as he could maintain indefinitely, which is a very different pace. He asked to himself: how long could I stay going at this velocity? If the answer was not forever, then it was too fast. But he also posed to himself another question: can I go faster? That way he set his pace, the buddhist pace, and mantained it.

+ +

At the beginning the other hikers overtook the buddhist since they were almost running, like if they were in a marathon. But it was not a marathon, it was a different kind of race. With that in mind the buddhist forgot about the others and simply continued his way at the velocity he decided. The days passed and the buddhist didn’t meet the other hikers, they have taken a big advantage, but nonetheless he remained still, constant, and with the same pace always, with no rest but without accelerating himself, always the same speed.

+ +

The race continued and the days passed, a week after it has started the buddhist finally met the other hikers. To his surprise, they were exhausted and walking really slow. After a week going so fast they had consumed all their batteries and were left with no energy. The buddhist looked at them, fresher than a lettuce, and continued. Now it was him the one overtaking the others. They were also astonished. The buddhist started so slow and they started so fast, how is it that he is here overtaking us and we can no longer compete with him? ‘In the long run, being constant is better than sprinting.’ The buddhist answered. The others couldn’t help themselves but assume that the victory was for the buddhist.

+ +

compass

+ +

What is remarkable about this story is that it is a good metaphor of life. You never know when it is going to end and you have to keep improving without losing its way. Sometimes you feel the urge to accelerate. For instance, the summer comes and you want to be in good shape so you start exercising as much as you can and begin a very restrictive diet. At first you will see benefits very fast (like the other hikers) but then you will be exhausted and find very difficult to continue the rythm. On the other hand, if you keep an exercise routine which is not so demanding, but you maintain it always, then, in some years you’ll be in perfect shape. More importantly, you will be able to maintain the shape without effort, as the buddhist maintained its pace.

+ +

For me this story is a guide on my career, my philosophy. Researching can be really rewarding when it is successful, but in data science you never know when it is going to be a success. Even expert researchers have to experiment a lot to find the architecture that works. Although you may have some intuition, some compass, you ignore where the finish line is. Whenever I want to spend more hours fine-tuning a model, or coding a different architecture, or reading extra articles, I remember the buddhist and stop. More days will come. It is better to be constant than to engage in many things and then getting exhausted. As the buddhist would say: ‘set a pace, and maintain that pace’.

+ +
+ + + + + +

+ + + + +

+ + +
+ +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + +
+
+ Never miss a story from us, subscribe to this blog +
+
+ + +
+
+
+
+ + +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +