Search
Students are heavily advised to use a Linux based OS. A good starter choice is an LTS Ubuntu distribution (2016 or 2020). Windows is mostly unsuitable for robotics/AI based work/research. If you use Windows, you will likely encounter problems and we will not be able to provide advice.
You will be using python in this subject, as well as in many of your subjects throughout the Bachelors and Masters course. Python is extremely flexible and powerful, mostly due to the ease of use and the vast amount of available libraries for almost anything. We will be using python 3 (do not use python 2). Any version above 3.6 should be fine. Q: Isn't python slow? Yes, python is an interpreted language so it is slow. When performing computation / optimization python is used by calling very fast subroutines that are written in C/Fortran. Usually, over 95% of the computational time taken is spent in some low level library. This means that the python overhead is negligible. Python is unsuitable for applications such search in large graphs, high-end games, applications which are time-critical.
Your linux distribution (ubuntu for example) already comes with some version of python 3 installed. This is the system interpreter. You can tell that you are installing onto your system interpreter if you need 'sudo' to perform a pip install command for example. Another solution would be to work with python environment. This can be done using an environment manager such as virtualenv and conda. This allows you to have multiple python environments on a single machine, each with different versions of packages etc. This guarantees that you don't have any conflicts between projects. Some more avid python users can exhibit a *mild* to severe reaction if you tell them that you install packages to your system interpreter.
Python scripts can be written in a simple “.py” file and run in the command terminal using “python myfile.py”. We do however advise the use of an IDE for development. A good choice is Pycharm (community version is okay, Czech Technical University provides the license for the Professional version, which ease working on the server later on). It provides autocompletion, debugging, syntax checking and many useful other tools.
To save and manage your code you can use git for version control. Pycharm has integrated git support. You can commit and push code with a single button.
#!/usr/bin/env python # coding: utf-8 # !!Tip!!: To execute a specific block of code from pycharm: # a) open a python console (Tools -> python console), # b) highlight code # c) alt + shift + e to execute (You can bind this key combo to a single key in settings -> keymap) ### Python refresher. ### # Numbers a = 3 # These will be python integers b = 2. # The dot makes it a float c = float(5) # Explicit definition # Check the types (everything is a class btw) print(a, type(a)) print(b, type(b)) print(c, type(c)) # Potentially nasty bugs if this is done wrong. Float usually has type priority. print(2/a, 2//a, 2./a, 2/c) # Formating string for printing print("a: {}, b: {}, c: {}, a * b + c: {}".format(a, b, c, a * b + c)) # Also a shorter format version: foo = 3 print(f'foo = {foo}') # Note: mostly the " and ' are interchangeable for strings. # Lists: Universal container which can contain anything. Can be iterated. my_list = [1,2,33,["foo"], 4] my_list.append(print) # Add stuff to list (yes, I added a function to the list. It can be identified and called later) del my_list[0] # Delete stuff # Indexation is modular print(my_list[-1], my_list[-2]) # Tuples: Immutable containers, also useful. Using round brackets and adding a comma makes it a tuple not_a_tuple = (1) a_tuple = (1,) another_tuple = (1,3,3,7) print(type(not_a_tuple), type(a_tuple), type(another_tuple)) # Broadcasting: print(my_list[1:3]) my_list_2 = my_list[2:] # The traditional way of iterating through a list, not very pythonic, avoid this. for i in range(len(my_list)): print(my_list[i]) # Proper way of iterating through list: for l in my_list: if callable(l): # If it's that print function from earlier, then call it with an argument l("Hello") else: print(l) # Enumerate while iterating for i, t in enumerate(another_tuple): print(i, t) # Iterate over n iterables at the same time. for e1, e2 in zip(my_list, another_tuple): assert e1 != e2 # This is like an 'if' statement, but will crash the program if not satisfied. Can be used in a try-catch # Shortcut iterating my_shortcut_list = [i**2 if i % 2 == 0 else "banana" for i in range(10)] # None type. This is analogous to the null or void types in other languages q = None print(q is None) # Dont check None using == sign! # Functions def myfun_1(arg): arg = 3 a = 1 myfun_1(a) print(a) # Unchanged def myfun_2(arg): arg[1] = "err" mod_list = list(range(10)) # Note than the function range gives a generator (which is iterable). It has to be converted to a list if you want to work with it myfun_2(mod_list) # This will modify the list print(mod_list) # Tip: If you highlight the called function and press CTRL + b in pycharm, the IDE will show you the declaration of the function. # This is extremely useful in navigating over the larger repository of code. fun_list = (lambda x : x + i for i in range(10)) # Generator of anonymous functions print(list(fun(1) for fun in fun_list)) # Dictionaries: Very useful container. They key and value can literally be anything that's hashable my_d = {"key_1" : "value_1", 24 : "value_2", (3,2,"33") : [111]} my_d_2 = dict(var1=4, var2="3") # Can also be defined like this, instead of the {}. I never use this # Add to dict my_d["key_x"] = 9 print(my_d, '\n', my_d_2) # Check if key is in dict print((3,2,"33") in my_d) # Iterate dict for k in my_d_2.keys(): print(k) for v in my_d_2.values(): print(v) for k,v in my_d_2.items(): # Both keys and values at the same time print(k,v) # !!! IMPORTANT NOTES AND TIPS !!!: # For loops are very slow and should be avoided if possible. Broadcasting should be done instead # You should absolutely master indexing and broadcasting. # Everything is a class, be carefull when passing references! # If you need to do something with an array/list you can be almost sure that a function already exists that helps you do that. # Try out the debugger! Put a breakpoint somewhere and step the computation using F8. Observe the available variables and their values/attributes.
#!/usr/bin/env python # coding: utf-8 # Tip: To execute a specific block of code from pycharm: # a) open a python console (Tools -> python console), # b) highlight code # c) alt + shift + e to execute import numpy as np ### Simple numerical manipulation recap. ### # Tip: Use debugger to check the shapes and other attributes of the following vectors and matrices. # Tip: When stopped at a breakpoint during debugging, you can rightclick -> evaluate expression. You can then type in # whatever you want (all the variables at the point at available) to test what will happen, without crashing the current execution. # Vectors a = np.array([10, 40, 20]) b = np.array([1, 0.1, 0.01]) c = 1. print("a: {}, b: {}, c: {}, a * b + c: {}".format(a, b, c, a * b + c)) # Elementwise multiplication a = np.array([[10, 40, 20]]) # Added nested list print("a.shape: {}, b.shape: {}".format(a.shape, b.shape)) # Shapes of vectors as numpy sees it print("a.T * b + c:") # Vector multiplication. print(a.T * b + c) # Notice that you can add single numbers to whole vectors and matrices. You can also add vectors to matrices # Matrices A = np.eye(3) B = np.random.randn(3, 3) # Identity and random 3x3 matrices. There are various 'random' matrix types (normal, uniform, integer uniform, etc) print("A: ") print(A) print("B: ") print(B) print("A * B: ") print(A * B) # Elementwise multiplication print("AB: ") print(A @ B) # Matrix multiplication, A @ B == A.dot(B) == np.matmul(A,B) v = np.arange(3)[:, np.newaxis] # The arange function is builtin. The np.newaxis adds an additional dimension which makes v a matrix print("A + v: ") print(A + v) # Addition. print("A + v.T: ") print(A + v.T) # Addition. # Indexing. a = np.arange(100, 110) print(a[0]) # Same as for lists idx_vec = [0,3,3,8]; print(a[idx_vec]) # You can use a vector to index. ';' delimits commands. # Slicing: #x[start:stop:step] print(a[1:10:2]) # Some other ways of writing small or large floats. The 'e' just means exponent, it's not Eulers number print(1e7, 1e-4) # Other usefull data types a = np.inf b = np.nan c = None print(a + a, a - a, a + b) try: a + c # Can't operate with None except TypeError: print("Can't do that") print(np.isinf(a), np.isnan(b), c is None) # Proper way how to check these vars # Note: # - Never use for loops to implement any sort of vector or matrix multiplication! # - The difference between proper and sloppy implementation can be in several orders of magnitude of wait time. import time N = 1000000 vec1 = np.random.rand(N) vec2 = np.random.rand(N) vec3 = np.zeros(N) t1 = time.time() for i in range(N): vec3[i] = vec1[i] + vec2[i] print(f"Naive vector addition took: {time.time() - t1} s on {N} data") t1 = time.time() vec3 = vec1 + vec2 print(f"Vectorized addition took: {time.time() - t1} s on {N} data") # Note, this method isn't the most accurate to measure time taken, especially for very short intervals. # Practice (5 mins): # 1) Make any [3x3] matrix M and [3x1] vector x. Multiply Q = Mx, Q = $x^T$M. Add x to M elementwise row by row with x (and then column by column). # 2) Make an array a = [1,2,...30] (tip:use np.arange), b = [0,3,11,29]. Set the array a at positions indicated by vector b to 42 (tip:index a using b) # 3) Make matrix B by tiling the first 5 elements of a 5 times vertically. Resultant matrix should have 5 row vectors which are [42,2,3,42,5] (tip: use np.tile) # 4) Set last 2 columns of the last 2 rows of matrix B to zero
Pytorch is a numerical computation library with autograd capabilities. The following tutorial is to help refresh numpy basics and familiarize the student with the Pytorch numerical library. There are plenty high quality tutorials available online ranging from very basics to advanced concepts and state of the art implementations.
A good place to start would be here or the tutorial below.
Make sure that you have Pytorch installed preferably with python3. You can do so by following the instructions from the getting started section. If you have a recent NVIDIA GPU then try to install the GPU version. In some cases the GPU can offer speedups of up to ~30x.
import torch import numpy as np ### Pytorch Basics, use Debugger to inspect tensors (for finding out their shape and other attributes) ### # Terminology: Tensor = any dimensional matrix # Empty tensor of shape 5x3. Notice the initial values are (sometimes) whatever garbage was in memory. x = torch.empty(5, 3) print(x) # Construct tensor directly from data x = torch.tensor([5.5, 3]) print(x) # Convert numpy arrays to pytorch nparr = np.array([1, 2]) x = torch.from_numpy(nparr) # Convert pytorch arrays into numpy nparr = x.numpy() # Make operation using tensors (they support classic operators) a = torch.tensor([3.]) b = torch.rand(1) c = a + b # a + b = torch.add(a,b) print("a: {}, b: {}, a + b: {}".format(a, b, c)) # Note, when performing operations make sure # that the operands are of the same data type a = torch.tensor(3) # int64 type b = torch.tensor(3.) # float32 type print("atype: {}, btype: {}".format(a.dtype, b.dtype)) # ERROR, data type mismatch: # print(a + b) # Convert data type b = torch.tensor(3., dtype=torch.int32) # Explicitly force datatype b = b.long() print(a + b) ### Autograd ### # Make two tensors a = torch.tensor(8., requires_grad=True) b = torch.tensor(3., requires_grad=True) # c tensor is a function of a,b c = torch.exp((a / 2.) - b * b + 0.5) print("c value before applying gradients: {}".format(c)) # Backwards pass c.backward() # Print gradients of individual tensors which contributed to c print("a.grad: {}, b.grad: {}".format(a.grad, b.grad)) # Move a,b towards the direction of greatest increase of c. a = a + a.grad b = b + b.grad c = torch.exp((a / 2.) - b * b + 0.5) print("c value after applying gradients: {}".format(c)) # If we don't want to track the history of operations then # the torch.no_grad() context is used to create tensors and operations print(a.requires_grad) with torch.no_grad(): print((a + 2).requires_grad) # Note: Whenever we create a tensor or perform an operation requiring # a gradient, a node is added to the operation graph in the background. # This graph enables the backwards() pass to be called on any node and find all # ancestor operations.