Python for AI 101

Acknowledgement: This post is based mainly on Dr. Quang-Vinh Dinh’s presentation and slides and partly on on TA, STA, and peers’ responses from the course, AIO2025, offered by AI VIETNAM.

Relevance: The audience of this lecture were those who knew nothing about coding, have had some coding experience, and have known a bit of AI knowledge. Absolute beginners are encouraged to read the material and repeatedly practice coding without AI assistance to strengthen their coding and code-reading skills. Note that only necessary Python knowledge is introduced here.

Creating an error while coding is, in fact, a good sign which says you should keep going to figure out what the problem is and how to resolve it.
-Dr. Quang-Vinh Dinh

In today’s post, we’ll look at data representation, functions, and conditions. We’ll also discuss some applications in AI including Loss and Softmax functions.


Before getting into the main materials, I cannot stress enough how important it is to comment on your code.

:pushpin: Pro tip: Annotate your code - always!
Keeping documentation of what your code does is far more important than most people might think. Your log isn’t for today but tomorrow. It benefits not only future you but your colleagues or anyone else trying to read or maintain your code.

  • Use # for single-line comments
  • Use ''' or """ for multi-line comments and docstrings.

Introduction

We consider Python as a tool that alllows us to transform our ideas into a program producing desired results. Don’t worry about becoming an expert in Python since it, just like other programming languages, is a means of solving problems but a goal to reach. Essentially, you need to know its syntax and structure.

It’s usually easier to learn something when we know its history and why it is studied or used. The same goes for Python. Python is widely adopted in the community of Data Science and Machine Learning because it has a giant and vibrant ecosystem with plentiful frameworks, libraries, and packages of diferrent purposes. Some examples can be found in the table below.

Purposes Frameworks Libraries Packages
Scientific Computing   Pandas NumPy
Visualization   matplotlib, plotly  
Machine Learning and Deep Learning TensorFlow, Pytorch Keras  
Natural Language Processing   Natural Language Toolkit (NLTK), GENSIM, spaCy  


To write and run a program in Python, you can choose among some options such as Google Colab, Jupyter, Visual Studio Code, and PyCharm. It’s not difficult to look for a video or post which guides you install and/or use them.

python history


Data Representation

In Python, data can be represented in three groups discussed below. In any programming language, data is stored in a container called variable. This allows us to refer to and manipulate the data later. In Python, defining a variable is straightforward.

variable_name = variable_value

We may think of a variable as the name of the container and its value as the content held inside it. We are allowed to use and/or modify the value as needed.

mystery_box = 'IPhone'

# a container called *mystery_box* has a value of *'IPhone'* in it.

:question: Now, you may ask, What can I can name a variable? Are there any conventions I should be aware of?

In Python, variable naming follows specific rules and conventions.

  • Avoid keyword: A syntax error is returned when a keyword is used as a variable. Usually, editors display a red squiggly underline under such a variable name.
  • Meaningful name: While we can name a variable almost anything, using a name that reflects its purpose is best practice.
  • snake_case: Using lowercase words separated by underscores for a variable name follows PEP8—standard Python style guide.

Primitive Data Types

There are three main categories of primitive data types including logic, number, and text.

Plain Language Python Language Examples


Note: the quotation marks used in a string must be consistent—a string starts with a double quote must end with a double quote, not a single quote, and vice versa.

Overflow and Underflow: No matter how much memory a programming language can access, there are always limits to the size of numbers it can represent. When a number exceeds the maximum value the system can handle, it causes an overflow and Python returns inf for infinity. Conversely, it leads to an underflow when a number is too close to zero and Python returns 0.0.

:bulb: Application in AI: Since computers can only understand numeric values, any text we want a machine to process must be converted into nnumerical format. This is a core task in Natural Language Processing (NLP)—the field that helps machine understand human language. Not familiar with NLP?. That’s totally fine! Just have in mind Google Translate—a popular example of NLP in action.

Abstract Data Structures

Primitive data representation are great for simple tasks, but they fall short when things get more complex. That’s when built-in data structures like lists and dictionaries come in. We’ll introduce their definitions here and dive deeper into how they work in future posts.

List

list_name = [element_1, ..., element_n]

A list is a collection of elements and each of them is automatically assigned to an index. Forward indices start at 0 for the first item on the left and increase by one for each following element. In practice, you access an element’s values via its index. Let’s take a look at an example.

a list of elements with their associated indices
# create a list of fruits
fruits = ['apple', 'orange', 'banana', 'grape', 'mango']

# test
fruits[0]
>>> 'apple'

fruits[2]
>>> 'banana'
'''

Of course, elements of a list can be logic, numbers, another list and even more.

data = [5, 4.7, -2, 'hello', 0, True, -1000.1]

Dictionary

dictionary_name = {key_1: value_1, ..., key_n: value_n}

Another common and powerful data structure is the dictionary. Think of a regular dictionary book: a word has a meaning(s) and no word appears more than once.

A dictionary in Python is created in a similar fashion where it is a collection of two-part elements and each unique key has a specific value. In a dictionary, an item’s value can be retrieved using its key in lieu of index. Here’s a simple example.

# create a dictionary of a fruit's characteristics
fruit = {'name': 'avocado', 'type': 'berry', 'nutrition': ['fat', 'fiber', 'vitamins'], 7: 100}

# test
fruit[7]
>>> 100

fruit['nutrition']
>>> ['fat', 'fiber', 'vitamins']

Other Data Representation
Tabular and Image Data are predominant in Data Analysis. They are both handled using matrix formats.


Functions

def function_name(parameter_1, ..., parameter_n):
    '''
    do docstring here.
    '''

    # code here

    return result

Why Functions?

Imagine you want a machine to make a sandwich for you every morning.

  • It’d be time-consuming to tell it all the steps every single day just to do one job. Wouldn’t it be better if it receives one line of request and automatically undertake all the tasks. That’s exactly what functions are for. It lets us wrap a block of code into one reusable command, that we just need to call the function to handle the work rather than rewriting the entire instruction.
  • Let’s say there are many steps for this sandwich-making process. Sure, you could certainly put all the actions in one go—write all the tasks in one function. But it will improve readability if we separate the tasks into logical parts—one for preparation and another for cooking. As such, we know exactly how to allocate a set of actions to one block of time and reuse those sets in other recipes.
Get the ingredients Put the ingredients together
Get two slice of breads Use the knife to spread peanut butter on one side of a slice
Get the peanut butter container Use the knife to spread peanut butter on one side of the other slice
Get a knife Put the peanut butter sides of two slices together


What is a Function?

function as a magic box
Figure 1. A function takes input and returns output

In Python, a function is a reusable block of code that

  • Takes parameters (inputs), performs an action within itself, and optionally returns outputs.
    • Might not have inputs or outputs.
  • Must start with the def keyword.
  • Should end with the return keyword.
    • If no outputs, put return None.
  • Should be named with a verb to indicate its action and written in snake_case.
    • Default parameters must follow non-defaults.
    • Parameters can be set to default values but they can take other values.
    • Body must be indented.
✅ # default arguments come last
def compute_total_income(bonus, salary = 100000):
    # function body

❌ # default arguments are in wrong position
def compute_total_income(salary = 100000, bonus): 
    # function body

Types of Functions

Two main types of functions are built-in functions and user-defined functions.

Built-in Funtions

Built-in functions are created by and freely available to the Python community. Python comes with a variety of built-in functions that are ready to use. Two common ones are print() for displaying outputs and type() for returning the data type of a variable. We can use them in any Python program without extra setup needed.

Some useful functions live inside special packages called module like random and math. To use those functions, we need to import the module first via the import keyword. Try out the code below and see what it gives us.

import math

x = 4
y = -3

print(math.sqrt(x))
print(math.sqrt(y))
print(math.fabs(x))
print(math.fabs(y))
print(math.log(math.e))  # log is based e
Practice Exercises
Exercise 1: What is the output of the following? If it is an error, what is it?

s = 'Hello World'
print(s)


import math

number = 20.2
print(math.floor(number))


a = 5
Print(a)


a = 5
c = a + b
print(c)


lst = [1, 2, 3, 4, 5]
print(l[1])
print(l[5])


a = 5
s = 'AI'
print(a + s)


a = 5
    b = 2
print(a / b)


s = 'hello world'
print(s[0])
print([7])


:bulb: Applications in AI

graphs of square root, logarithmic, exponential functions
Figure 2. Commonly used functions for applications in AI

Square Root Function $\sqrt{x}$ plays a key role in Object Detection problems. Since the movemnet of an object is measured relative to its size, applying this function can help normalize the change and respond to this challenge.

import math

a = -1
b = 1000
c = 0

math.sqrt(a)
>>> ValueError

math.sqrt(b)
>>> 31.622776601683793

math.sqrt(c)
>>> 0.0

Logarithm Function $\log({x})$ has a couple of extremely useful properties:

  • $\log({1}) = 0$
  • It is monotonically increasing, meaning that $\text{if } x_1 < x_2, \text{ then } f(x_1) < f(x_2)$.

These properties are applied in Optimization Algorithms problems which are everywhere in Machine Learning.

  • When we try to maximize a function, evaluating its logarithm is often computationally easier and faster than the function itself while the location of the maximum stays the same.
  • Logarithms can be used when our focus is on how numbers are relative to each other each other rather than their actual values.
  • Since log of a number grows and reduces much more slowly than the number itself, the program can easily avoid the overflow and underflow issues.
the positions of maximum/minimum of $f(x)$ and $\log{(f(x))}$ are the same
the positions of maximum/minimum of $f(x)$ and $\log{(f(x))}$ are the same
Figure 3. The positions of maximum/minimum of $f(x)$ and $\log{(f(x))}$ are the same
import math

a_1 = 0.00004
b_1 = 0.00003
a_2 = 0.04
b_2 = 0.03

# test
m_1 = a_1 * b_1
m_2 = a_2 * b_2
print('m_1 is', m_1, 'and m_2 is', m_2)
print('m1 is smaller than m2:', m_1 < m_2)

l_1 = math.log(a_1) + math.log(b_1)
l_2 = math.log(a_2) + math.log(b_2)
print('l_1 is', l_1, 'and l_2 is', l_2)
print('l1 is smaller than l2:', l_1 < l_2)

'''
m_1 is 1.2e-09 and m_2 is 0.0012
m1 is smaller than m2: True
l_1 is -20.541 and l_2 is -6.725
l1 is smaller than l2: True
'''

Exponential Function $\exp({x}) \in (0, \infty) \text{ when } x \in (-\infty, \infty)$ has an extremely useful property:

  • $\exp({x}) \in (0, \infty) \text{ when } x \in (-\infty, \infty)$ turns all values into positive numbers.

This property allows exponential functions used for datasets containing both positive and negative numbers. It is a key for $\exp({x})$ to be used in the Softmax Function, which determines how an object and the background of an image are related.

  • Softmax helps avoid the overflow issue from the exponential function.
  • A rabbit/snake near a bush gives a postive relation
  • Fish in a bush gives a negative relation, unless you can show me evidence of a live fish being on land.
import math

a = -7
b = 7
c = 1000

# test
math.exp(a)
>>> 0.0009118819655545162

math.exp(b)
>>> 1096.6331584284585

math.exp(c)
>>> OverflowError

:question: Now you may ask, How do I know which function(s) to use provided a problem?

That’s a great question! Let’s look at an example and we’ll walk through the thought process to develop and improve a solution.

Design a Loss Function
In Information Theory, we aim to measure the surprise of an event. A more surprising event is less likely to happen, which interprets more information. $$Information(x) = \log{\frac{1}{p(x)}} = -\log({p(x)}).$$ Below is a descriptive example involving basic probability.
Problem statement: There are ten balls, nine reds and one blue, in a basket. If you pick up a ball from the basket, would getting a red or blue surprise you more?
Breakdown:
  • $R$ and $B$ are the events of getting a red and blue ball, respectively.
  • $E$ is the event of getting a ball.
  • $p(E)$ is the probability of something happen or our believe.
  • $s(E)$ is the level of surprise.
  • $f(p)$ is the symbolic level of surprise.
Idea: If $p(E)$ is higher, $s(E)$ and $f(p)$ are lower. This means $p(E)$ is disproportional to $s(E)$ and $f(p)$.
  1. We should first compute the probability of getting a ball.
    • $p(R) = \frac{9}{10} = 0.9$
    • $p(B) = 1 - p(R) = 0.1$
    This implies that the event of getting a red ball is more surprising whereas the opposite goes for getting a blue ball.
  2. Since $p(R) > p(B)$, we want $s(R) < s(B)$. There are two ways to build $s(E)$ satisfying this condition.
    • A linear function $s(E) = 1 - p(E)$
    • A non-linear function $s(E) = \frac{1}{p(E)}$
    Non-linearity has more interesting properties, so we choose it for $s(E)$.
  3. Have in mind that $p(E) \in [0, 1]$, so $s(E) = \frac{1}{p(E)} \in [1, \infty)$. This means $s(E) = 1$ shows no surprise, which is mathematically strange. It'd be more logical to have no surprise as $0$.
  4. We desire to achieve the level of surprise in $[0, \infty)$ instead. Note that two important properties of logarithm are
    • $\log({1}) = 0$: helps convert $1$ to $0$ as wanted
    • Logarithm is monotonic: $f(p) = \log({s(E)}) = \log{\frac{1}{p(E)}} = -\log({p(E)})$ is proportional to $s(E)$ as we need $f(p)$ also interprets the level of surprise.
  5. Computing $f(p)$ is more convenient that $s(E)$ since the logarithmic graph approaches $\infty$ significantly more slowly than that of $s(E)$.
The solution is improved with steps 4 and 5, but it can be stopped at step 3.
Design the Softmax Function
We commonly use the Softmax Function in Machine Learning. For instance, given a picture, we want to measure how much the object and background are related.
Problem statement: Given a list of data, what is the percentage of each data over their sum? Note that data can be positive, negative, and zero.
Breakdown:
  • $Z = \{z_1, \cdots, z_n\}$ where $z_i \in (-\infty, \infty)$.
  • $S = \sum_{j=1}^{n} z_j = z_1 + \cdots + z_n$ is the sum of all data in $Z$.
  • $p_i = f(z_i)$ is the probability of $z_i$ over $S$.
Idea: For percentage/probability to make sense, we need to create a non-negative number from negative $z_i$.
  1. Have in mind that $\exp({x}) \in (0, \infty)$ where $x \in (-\infty, \infty)$.
    • $Z = \{e^{z_1}, ..., e^{z_n}\}$ where $e^{z_i} > 0$ as wanted.
    • $S = \sum_{j = 1}^{n} e^{z_j}$.
  2. $p_i = f(z_i) = \frac{e^{z_i}}{\sum_{j = 1}^{n} e^{z_j}}$ is the desired probability.
You can certainly use different bases, but $e$ is standard.


User-defined Functions

In addition to built-in functions, users are allowed to custom functions for specific purposes. Some critical information about user-defined functions is:

  • A function is recommended to be defined at the beginning of the program.
  • A function does nothing until it is called.
  • A function is immediately canceled once its task is completed during the call.
  • A function does not necessarily return a value.
    • The return statement sends a value when the call is finished.
    • return None is recommended if there’s no value to return.

A helpful analogy of a function in a program is a room in a house. We enter when we need to do something inside and leave once we’re done. If we need to take something out of the room, you need to bring a container to hold it. Otherwise, you simply walk in and out empty-handed.

Let’s see this idea in action. When the function is called, any argument passed to it is handed over to a new context where they are processed. Eventually, the function gives out a created-inside-the-function value and shuts down/cancels. Thus, all the variables defined inside the function disappear. If variables inside and outside a function are named the same, the one outside is still accessible to the main program whatsoever.

def compute_salary(salary):
    '''
    This function aims to increase the salary of an employee

    salary (float): the salary of a person

    return (float): the new salary of a person
    '''
    new_salary = salary + 7000

    return new_salary

salary_p1 = 60000

# call function compute_salary() 
# pass arguments: salary_p1 and to it
result = compute_salary(salary_p1)
result
>>> 67000

# test
new_salary
>>> NameError: name 'new_salary' is not defined

Let’s implement a simple math problem in Python.

Estimate the Area of a Unit Circle.
Problem statement: Approximate the area of a unit circle.
Breakdown:
  • $r = 1$ is the radius of the circle.
  • $\alpha$ is the angle in degree starting at positive $x-axis$.
  • $A_{est}$ is the estimated area of the circle.
  • $A_{real}$ is the real area of the circle.
Idea: We can break it into smaller equal-sized pieces in a way that we can approximate their area via some area formula. Thanks to the equality, we can scale up to get the estimated area of the circle.
  1. Divide the circle into equal circular sectors:
    • Their angle for each sector is $\alpha$
    • The number of sectors is $n = \frac{360}{\alpha}$.
  2. Create triangles out of those sectors. There are two ways, either an isoceles or a right triangle.
    • An isoceles triangle: $A_{\alpha} = \frac{(r)(r) \sin({\alpha})}{2} = \frac{(1)(1) \sin({\alpha})}{2} = \frac{\sin({\alpha})}{2}$
    • A right triangle: $A_{\alpha} = \frac{height * base}{2} = \frac{\sin({\alpha}) \cos({\alpha})}{2}$.
  3. Those triangles roughly cover the circle, so their area sum is the estimated area of the circle.
    • Isoceles triangles: $A_{real} \approx A_{est} = n A_{\alpha} = \frac{360}{\alpha} \frac{\sin({\alpha})}{2}$
    • Right triangles: $A_{real} \approx A_{est} = n A_{\alpha} = \frac{360}{\alpha} \frac{\sin({\alpha}) \cos({\alpha})}{2}$
The lower the number of triangles is, the larger the gap between those triangles and the circle is and vice versa. In order to reduce the gap/error, we'll increase the number of triangles or decrease $\alpha$ to roughly 0.


import math 

def estimate_area(alpha):
    '''
    This functions aim to estimate the area of a unit circle
    using isoceles triangle method

    alpha (float): the size of an angle in degree

    return (float): the estimated area of a unit circle
    '''
    # compute sine and cosine
    radian_alpha = math.radians(alpha)
    sin_alpha = math.sin(radian_alpha)

    # compute the alpha area
    area_alpha = sin_alpha / 2
    area = area_alpha * (360 / alpha)

    return area

# test 
print(estimate_area(10))
print(estimate_area(1))

'''
3.125667198004746
3.141433158711032
'''
Practice Exercises
Exercise 1: List differences between two functions below. Is the list updated in both code blocks? Explain why or why not.

def set_one_1(data):
    length = len(data)
    for i in range(length):
        data[i] = 1
    return data

# test the function set_one_1
a_list = [7, -5, 8]
print(a_list)
result = set_one_1(a_list)
print(result)

# rendered output
[7, -5, 8]
[1, 1, 1]


def set_one_1(data):
    length = len(data)
    for i in range(length):
        data[i] = 1
    return None

# test the function set_one_2
a_list = [7, -5, 8]
print(a_list)
set_one_2(a_list)
print(a_list)

# rendered output
[7, -5, 8]
[1, 1, 1]

Exercise 2: Write code to compute loss function based on the above design.
Exercise 3: Write code to compute softmax function based on the above design.
Exercise 4: Write a function to compute estimate the area of a circle using right triangle method in the above example.
Exercise 5: What is the output of the following?

def a_function(n):
    return a_function(n)

a_function(5)


def a_function(x):
    a_var = 3
    result = x * a_var
    return result

a_var = 4
print(a_var)
print(a_function(10))


Branching/Conditions

Conditional statements in Python enables the execution of certain code blocks based on whether a condition is true or false.

Comparison Operators

Comparison operators are symbols used to compare two values and create a condition for a conditional statement.

Operator Math Meaning
$==$ $=$ Equal
$!=$ $\neq$ Not equal
$>$ $>$ Greater than
$<$ $<$ Less than
$>=$ $\geq$ Greater than or equal
$<=$ $\leq$ Less than or equal


Basic Conditional Statements

We’ll introduce three types of conditional statements. Note that the else statement is optional for the last two.

  1. if: if ‘if’ is satisfied, do it; otherwise, move on.
  2. if-else: if ‘if’ is satisfied, do it and ignore ‘else’; otherwise, do ‘else’.
  3. if-elif-else: if ‘if’ is satisfied, do it and ignore the rest; otherwise, check ‘elif’ and if it is true, do ‘elif’ and ignore ‘else’; otherwise, do ‘else’.

Imagine there are two or three doors and you have a key that can unlock exactly one of them. You need to check one by one and once you open the right door, you can walk through without checking the rest.

The process of these statements is illustrated using flowcharts which use shapes and arrows to depict steps and decisions as follows.

flowchart TB
    subgraph C [IF-ELIF-ELSE]
        direction TB
        C1[before if] --> C2{if condition}
        C2 -->|True| C3[inside if] --> C7[after if-elif-else]
        C2 -->|False| C4{elif condition} -->|True| C5[inside elif] --> C7
        C4 -->|False| C6[inside else] --> C7
    end

    subgraph B [IF-ELSE]
        direction TB
        B1[before if] --> B2{if condition}
        B2 -->|True|B3[inside if] --> B5[after if-else]
        B2 -->|False|B4[inside else] --> B5
    end

    subgraph A [<strong>IF</strong>]
        direction TB
        A1[before if] --> A2{if condition}
        A2 -->|True| A3[inside if] --> A4[after if]
        A2 -->|False| A4
    end

    style C2 fill:#ffdab9
    style C4 fill:#ffdab9
    style B2 fill:#ffdab9
    style A2 fill:#ffdab9
    style C3 fill:#c6f777
    style C5 fill:#c6f777
    style C6 fill:#c6f777
    style B3 fill:#c6f777
    style B4 fill:#c6f777
    style A3 fill:#c6f777
Figure 4. Flowcharts of three conditional statements
import math

a = 99
b = 4

# if-else
if a < 0:
    result_1 = b * b
else: 
    result_1 = math.sqrt(b)

result_1
>>> 2.0

# if-elif-else:
if a < 0:
    result_2 = b * b
elif a >= 0:
    result_2 = math.sqrt(b)

result_2
>>> 2.0

:bulb: ApplicationS in AI
We can use conditional statements for activation functions in Machine Learning like ReLU

\[\begin{equation*} ReLu(x) = \begin{cases} x &\text{if } x > 0 \\ 0 &\text{if } x \le 0. \end{cases} \end{equation*}.\]

In practice, rule-based chatbots, automatic chats/replies on websites, are built according to this branching condition idea. The interface for these chatbots can be constructed using streamlit.

DEMONSTRATION VIDEO

One side note is that the if-else and if-elif-else statements can be converted into one mathematical function, even one calculation. This is extremely helpful since

  • we can turn any data into vectors or matrices which computers handle best
  • we only need to do one computation for get the result
  • we can use math to work on real-world problems.
Practice Exercises
Exercise 1: Write a function that works for any a and b in the if-else and if-elif-else example above.
Exercise 2: What is the output of the function in exercise 1 for the following?
  • a = -99, b = 4
  • a = 0, b = 4
Exercise 3: Write a function that computes ReLU function.
Exercise 4: Draw a flowchart for the demonstration video above and write a program with a function for it.

:speech_balloon: FAQ

Here are some good questions asked during class. The answers to these questions came from either some admin, TA, STA, or members of AIO2025.

Questions Answers
Does a program run the comments? No. The comments are only for the owners and readers.
What are real-life applications of a logarithm function? Information theory (entropy), Astronomy (lights’ and stars’ brightness), Machine learning (logistic regression, cross-entropy)
What are values modified for? Value modification is to avoid disadvantageous scenarios like overflow, underflow, slow computations, errors, divergent sequences. Some functions are used to change the range so that values are easier to be dealt with but still preserve core characteristics.
If one module needs a supporting one, do I need to import the latter? No. Modules consist of their supporting modules.
Where do I look for a function that I need? dir() shows attributes of an object or help() provides detailed documentation.
What is the difference between parameters and arguments? Parameters are like variables or placeholders while arguments are like values.



    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • Python for AI 104
  • Python for AI 103
  • Math for AI 103
  • Machine Learning 101 Warm-up
  • Math for AI 101