We closed module 6 with an observation: we have written six modules of code and, without saying so, we have made a tooling decision in every one of them. Pure Python for the graphs and the rules, scikit-learn and PyTorch for the models, and passing mentions of Prolog, Lisp, CLIPS or Drools that we never installed. This lesson puts that first decision, the choice of language, in order, because it is the one that conditions all the others. We will see what artificial intelligence asks of a programming language, why Python has become its lingua franca despite being slow (and how that slowness is compensated for, with a micro-benchmark you will run yourself), what the historical languages of symbolic AI (Lisp and Prolog) contributed and still contribute, what role R and Julia play in the scientific world, and which languages appear when a model leaves the laptop and enters production: Java and Scala, C++, JavaScript, Rust, Go and, above all, SQL, because NovaMarket's data does not live in a CSV but in a database. We will finish with the idea that structures the industry, "train in Python, serve in X", with the interchange formats that make it possible, and with the concrete decision for Marta's workshop. It matters because choosing the wrong language does not show in the prototype; it shows six months later, when the team tries to maintain it, integrate it or speed it up. The specific libraries are covered in 07-03 and the environments in 07-04; here we talk only about languages.

Contents

  1. What AI asks of a language
  2. Python: why it dominates
  3. Python's weak spot and how it is compensated: micro-benchmark
  4. The historical languages of symbolic AI: Lisp and Prolog
  5. R and Julia: the languages of the scientific world
  6. Production and deployment languages
  7. SQL: the data lives in databases
  8. "Train in Python, serve in X": interchange formats
  9. Language comparison table
  10. The decision for NovaMarket
  11. Common Mistakes and Tips
  12. Exercises
  13. Conclusion

  1. What AI asks of a language

An AI program is not just any program. Look at what we have done throughout the course: explore graphs and game trees (module 3), transform tables of thousands of rows and train models that multiply matrices millions of times (modules 4 and 5), represent rules and facts and reason over them (module 6), and in every case experiment: try, measure, change a parameter and try again. Five requirements come out of that:

Requirement What it means Where we have felt it
Expressiveness Writing the idea in little code and reading it back later; rich data structures (lists, dictionaries, tuples) built into the language The rule engine of 06-02 fitted in 60 lines; heapq and deque in 03-02
Numerical ecosystem Arrays, linear algebra, statistics, optimisation, plotting, already written and tested NumPy, scikit-learn, PyTorch in modules 4 and 5
Performance (via native libraries) Heavy computation running at C or GPU speed even if the language is slow The MLP of 05-03 trained 300 epochs in seconds because PyTorch is written in C++
Interoperability Talking to databases, web APIs, files of every kind, other languages The csv report of 02-03; in 07-03 we will see FastAPI
Community and talent Documentation, examples, answers in forums, people to hire Every error you looked up on the Internet while following the course

No language wins on everything. The history of languages in AI is the history of which of these requirements weighed most in each era: in the 1960s and 70s symbolic expressiveness (Lisp, Prolog); in the 2000s statistics (R); today the numerical ecosystem and the community (Python), with performance delegated to native libraries and with systems languages for serving in production.

  1. Python: why it dominates

Python was born in 1991 as a general-purpose language, not designed for AI. Its current dominance is explained by the sum of four factors:

  • Readable, dynamic syntax. It is written almost like pseudocode (compare forward_chain from 06-01 with its prose description), it does not require declaring types or compiling, and the "write, run, look" loop takes seconds. For experimenting, that is what matters.
  • Ecosystem. On top of NumPy (2006, heir to Numeric from 1995) everything else was built: SciPy, pandas, Matplotlib, scikit-learn, and later Theano, TensorFlow and PyTorch. When deep learning took off in 2012, Python already had the tools and the scientific community that R lacked in networks and Lisp lacked in numbers.
  • It is a "glue" language. Python is easy to extend with code in C, C++ or CUDA (the language of NVIDIA GPUs). NumPy, PyTorch and scikit-learn are really Python wrappers around native libraries: you write a @ b and the multiplication is done by a BLAS routine in C or Fortran; you write net(xb) and the computation is done by PyTorch's C++ core. Python provides the comfortable interface; someone else provides the metal.
  • Notebooks. Jupyter (07-04) made Python the natural environment for exploratory analysis: code, results and plots in the same document.

Besides, Python is the language that data engineers, system administrators and web developers already know, which lowers the friction for a team like NovaMarket's to bring in AI without switching languages.

  1. Python's weak spot and how it is compensated: micro-benchmark

Python is slow for numerical computation written in Python. Two reasons:

  • It is interpreted and dynamic. Every total += x in a loop involves checking the type of total and of x, looking up the addition operation for those types, creating a new float object... dozens of machine instructions for each addition that in C would be one.
  • The GIL (Global Interpreter Lock): in the standard interpreter only one thread executes Python code at a time, so threads do not speed up pure computation (they do help with waiting on the network or disk). Experimental GIL-free versions of the interpreter have existed for a short while, but the usual practice is still a different one: take the computation out of the interpreter.

The compensation is called vectorisation: instead of a Python loop that adds a million numbers one by one, you call one native function that walks the data in C. Check it yourself:

import time, random
import numpy as np

N = 1_000_000
random.seed(42)
list_a = [random.random() for _ in range(N)]      # a million floats in a Python list
list_b = [random.random() for _ in range(N)]

def time_it(name, function, repetitions=3):
    """Runs function() several times and prints the best time in milliseconds."""
    best = float("inf")
    for _ in range(repetitions):
        start = time.perf_counter()
        result = function()
        best = min(best, time.perf_counter() - start)
    print(f"{name:38s} {best*1000:9.2f} ms   result={result:.4f}")
    return best

# 1) Sum of a million numbers
def sum_loop():
    total = 0.0
    for x in list_a:
        total += x
    return total

def sum_builtin():
    return sum(list_a)

arr_a = np.array(list_a)                           # copy into a float64 ndarray
arr_b = np.array(list_b)
def sum_numpy():
    return arr_a.sum()

# 2) Dot product: sum(a[i] * b[i])
def dot_loop():
    total = 0.0
    for x, y in zip(list_a, list_b):
        total += x * y
    return total

def dot_numpy():
    return float(arr_a @ arr_b)

t1 = time_it("sum: Python for loop", sum_loop)
t2 = time_it("sum: Python sum()", sum_builtin)
t3 = time_it("sum: numpy .sum()", sum_numpy)
t4 = time_it("dot product: for loop + zip", dot_loop)
t5 = time_it("dot product: numpy a @ b", dot_numpy)
print(f"speed-up sum: x{t1/t3:.0f}   speed-up dot product: x{t4/t5:.0f}")

Output on the course machine (absolute times will vary on yours; the ratios, hardly):

sum: Python for loop                       15.35 ms   result=500014.5439
sum: Python sum()                           5.69 ms   result=500014.5439
sum: numpy .sum()                           0.33 ms   result=500014.5439
dot product: for loop + zip                29.34 ms   result=249910.5634
dot product: numpy a @ b                    0.39 ms   result=249910.5634
speed-up sum: x47   speed-up dot product: x75

Explanation, line by line of what matters:

  • time.perf_counter() is the high-resolution clock suited to measuring intervals; we take the best of three to reduce operating-system noise.
  • sum_loop is pure Python: about 15 ms for a million additions. sum() is a built-in function written in C, but it still walks Python objects one by one: 3 times faster. arr_a.sum() walks a contiguous block of 8-byte numbers with the processor's vector instructions: about 45 times faster than the loop.
  • The dot product widens the gap (about 70 times): the Python loop does two operations per element and @ delegates to BLAS, the optimised linear-algebra library that scikit-learn and PyTorch also use.
  • The result is identical in every variant; only who does the work changes. With matrices instead of vectors, or on a GPU, the difference reaches thousands of times.

From this comes the golden rule of scientific Python, which we will develop in 07-02: do not write loops over numbers; write operations over arrays. And when something cannot be vectorised, there are native extensions (Cython, Numba, or C++ linked directly with pybind11) and JIT compilers; they are part of the same pattern: Python organises, native code computes.

  1. The historical languages of symbolic AI: Lisp and Prolog

In 01-01 we placed the birth of AI in the 1950s with John McCarthy, and in 06-01 we saw that logic was its first formal language. Each of those two traditions had its own programming language.

4.1 Lisp: lists, and code as data

Lisp (McCarthy, 1958) is the second-oldest high-level language still in use. Its central idea: the program is written with the same data structure it manipulates, the list in parentheses. A function is a list, a datum is a list, and a function can build another function and run it. That made it ideal for the symbolic AI of the 1960s-80s: symbol processing, search trees, rules that are generated and transformed. An illustrative example (it does not run in the course environment) of a function that decides whether an order is within the withdrawal period, and of how code is data:

;; Definition: (defun name (parameters) body). Everything is a list in parentheses.
(defun within-period (days) (<= days 14))

(within-period 9)                    ; => T   (true)
(mapcar #'within-period '(9 20 3))   ; => (T NIL T): applies the function to each element

;; Code as data: a list that IS an expression, evaluated later with eval
(defvar rule '(<= 9 14))             ; the quote stops it being evaluated now
(eval rule)                          ; => T

defun defines functions, mapcar applies a function to a list (our Python map), and eval runs a list as if it were code: that is why the expert systems of the 1980s were written in Lisp, and from Lisp Python and JavaScript inherit ideas such as first-class functions, lambda, garbage collection and the interactive read-eval-print loop (REPL) that today is the Python console. Its current dialects (Common Lisp, Scheme, Clojure on the JVM) have their niches, but for AI its role today is historical.

4.2 Prolog: facts, rules and queries

Prolog (Colmerauer and Kowalski, 1972) is first-order logic turned into a language: the program is facts and rules in the form of Horn clauses (06-01), and running is querying: the interpreter searches for a proof by backward chaining with unification and backtracking, exactly the prove algorithm we wrote in 06-01. It was the language of the Japanese Fifth Generation project (01-01). The seven return rules of 06-01, illustratively in Prolog:

% Facts for order 48213 (NovaBrew coffee maker): delivered 9 days ago, with label, unused
days_since_delivery(o48213, 9).
original_label(o48213).
unused(o48213).
% Facts for order 48377 (NovaSound headphones): 400 days, defective
days_since_delivery(o48377, 400).
defective(o48377).

% Rules R1-R7 from 06-01. ":-" reads "if"; the comma is "and"; capitals are variables
within_period(O)         :- days_since_delivery(O, D), D =< 14.          % R1
under_warranty(O)        :- days_since_delivery(O, D), D =< 1095.        % R2
withdrawal_ok(O)         :- within_period(O), original_label(O), unused(O).  % R3
warranty_ok(O)           :- defective(O), under_warranty(O).             % R4
refund_applies(O)        :- withdrawal_ok(O).                            % R5
refund_applies(O)        :- warranty_ok(O).                              % R6
free_return_shipping(O)  :- warranty_ok(O).                              % R7

% Queries (what you type at the interpreter and what it answers):
% ?- refund_applies(o48213).           -> true.   (via R1, R3, R5)
% ?- refund_applies(o48377).           -> true.   (via R2, R4, R6)
% ?- free_return_shipping(o48213).     -> false.  (it is not defective)
% ?- refund_applies(X).                -> X = o48213 ; X = o48377.

Notice the last query: X is a variable and Prolog returns all the orders for which it can prove the goal; in Python we had to write the loop ourselves. Prolog is still alive in niches (rule engines, language analysis, verification, planning) and its descendants are the rule engines of 06-02/06-04 (CLIPS, Drools, experta) and deductive databases (Datalog). What it lacks is a numerical ecosystem and an ML community: you would not train the logistic regression of 04-04 in Prolog.

  1. R and Julia: the languages of the scientific world

5.1 R: statistics as a language

R (1993) was born from S, the language of the Bell Labs statisticians, and it remains the reference language in academic statistics, biostatistics and "classical" data science: it has the linear model, the tests, the plots and thousands of statistical packages (CRAN) that in Python sometimes do not exist or arrive late. The tidyverse (dplyr, ggplot2) is the reference for data manipulation and grammar-based plotting. Illustrative example (not runnable in the course environment): the regression Diego would want in order to know whether amount and delivery explain returns.

# Illustrative: R reads orders.csv and fits a logistic regression with the formula
orders <- read.csv("orders.csv")
model <- glm(returned ~ amount + delivery_days + new_customer + category,
             data = orders, family = binomial)
summary(model)           # coefficients, standard errors, p-values, in one table

The formula syntax (y ~ x1 + x2) and the summary with statistical inference are what R does better than anyone: in scikit-learn (04-04) we had coefficients but no p-values, because scikit-learn is designed to predict, not to explain statistically. R loses to Python in deep learning, in deployment and in integration with systems; mixed teams usually have the statisticians in R and the engineers in Python, and they talk to each other through CSV, databases or bridge packages.

5.2 Julia: C speed with scientific syntax

Julia (2012) was designed to solve the "two-language problem": prototyping in Python and rewriting in C++ for production. It compiles just in time (JIT) to native code, so a loop written in Julia runs at C speed without vectorising, and its syntax is designed for mathematics (1-based indexing, unicode operators, dot broadcasting). Illustrative:

# Illustrative: the sum and dot product of the benchmark, in Julia. A loop is fast.
a = rand(1_000_000); b = rand(1_000_000)
function dot(a, b)
    total = 0.0
    for i in eachindex(a)
        total += a[i] * b[i]
    end
    return total
end
dot(a, b)           # the compiled loop runs at native speed
sum(a .* b)         # vectorised version: the dot applies * element by element

Julia has a strong community in scientific computing, differential equations and optimisation, and ML libraries (Flux, MLJ), but a much smaller ecosystem than Python and less demand in companies. It is the option to keep an eye on if your project's bottleneck is a numerical algorithm of your own that fits neither NumPy nor PyTorch.

  1. Production and deployment languages

A trained model has to be served: answering in milliseconds inside NovaMarket's website, running on the delivery driver's phone or running on a cluster over terabytes of orders. That is where other languages appear, each with its role:

  • Java and Scala. The enterprise ecosystem (banks, telecoms, large retailers) runs on the JVM. Apache Spark, the data engine at scale, is written in Scala and used from Scala, Java or Python (PySpark). A model trained in Python can run inside a Java application through ONNX (section 8) or Java ML libraries (Deeplearning4j, Tribuo), or simply by calling a Python service over HTTP.
  • C++. The language of the engines: PyTorch, TensorFlow, ONNX Runtime, OpenCV and NumPy's kernels are written in C++ (and CUDA). When inference has to go at top speed or be embedded in a device (a camera in the Zaragoza warehouse counting boxes), the model is loaded from C++ with libtorch (PyTorch's C++ API) or ONNX Runtime. Almost nobody trains in C++; many serve in C++ without knowing it.
  • JavaScript / TypeScript. The language of the browser. TensorFlow.js and ONNX Runtime Web run models on the client (without sending data to the server: useful for privacy, 02-04) and Node.js serves APIs. A lightweight NovaMarket recommender could reorder products on the page itself.
  • Rust and Go. Modern systems languages, safe and fast, for services: inference servers, data tools (Polars, the fast DataFrame we will see in 07-03, is written in Rust; so are many LLM tokenisers). Go dominates in infrastructure (Docker and Kubernetes are in Go). You do not train in them; you serve and orchestrate.
  • Kotlin/Swift for mobile with exported models (TensorFlow Lite, Core ML), and CUDA/Triton for those who write GPU kernels. Mentions for the map; outside the scope of the course.

  1. SQL: the data lives in databases

The language that Marta's whole team does have to know, besides Python, is SQL. NovaMarket's real orders are not in orders.csv: they are in the store's transactional database and in the data warehouse. The first step of almost any project is a SQL query that extracts, filters and aggregates. And many aggregations are faster and clearer in SQL, inside the database, than bringing millions of rows into pandas.

Runnable example: we load the 3,000 synthetic orders of 04-01 into an in-memory SQLite database (Python ships with it) and compute the return rate by category, the query Diego would ask for on day one:

import sqlite3
from novamarket_ml import generate_orders_ml     # the generator from module 4

orders = generate_orders_ml(3000, 42)            # DataFrame with amount, category, returned...
con = sqlite3.connect(":memory:")                # temporary SQLite database in RAM
orders.to_sql("orders", con, index=False)        # pandas creates the table and fills it

sql = """
SELECT category,
       COUNT(*)                            AS orders,
       SUM(returned)                       AS returned_orders,
       ROUND(100.0 * AVG(returned), 1)     AS return_rate_pct,
       ROUND(AVG(amount), 2)               AS mean_amount
FROM orders
WHERE amount > 20                        -- discard tiny orders
GROUP BY category
ORDER BY return_rate_pct DESC;
"""
for row in con.execute(sql):
    print(row)

Output:

('electronics', 1006, 213, 21.2, 126.94)
('computing', 596, 97, 16.3, 129.18)
('home', 887, 122, 13.8, 128.16)
('accessories', 430, 56, 13.0, 130.97)

Reading it: SELECT chooses columns and expressions; COUNT(*) counts the rows in the group; SUM(returned) counts returned orders because the column is 0/1; AVG(returned) is directly the rate (the mean of a 0/1 is a proportion, the same trick we used in pandas in 04-01); WHERE filters before grouping; GROUP BY category forms one group per category; ORDER BY ... DESC sorts from highest to lowest. Electronics returns 21 % and accessories 13 %, consistent with the generator's "hidden truth" (the term +1.0 * (category == "electronics")). In 07-02 we will do exactly this operation with pandas groupby for comparison.

SQL is declarative (you say what you want, not how to compute it, like Prolog), it is the same in PostgreSQL, MySQL, SQL Server, BigQuery or Snowflake with small variations, and it is the natural border between the data team and the AI team: most of the features of 04-03 (previous_orders, previous_return_rate) can be computed with SQL in the data warehouse and served ready-made to the model.

  1. "Train in Python, serve in X": interchange formats

The consequence of sections 6 and 7 is an architecture that repeats in almost every company:

flowchart LR
    DB[(Database<br/>SQL)] -->|query / extraction| PY["Python<br/>pandas + scikit-learn / PyTorch<br/>exploration and training"]
    PY -->|joblib / pickle| SRV1["Python service<br/>FastAPI HTTP API"]
    PY -->|ONNX export| ONNX[.onnx file]
    PY -->|state_dict / TorchScript| SRV1
    ONNX --> JAVA["Java application<br/>ONNX Runtime"]
    ONNX --> CPP["C++ engine / device<br/>ONNX Runtime, libtorch"]
    ONNX --> WEB["Browser / mobile<br/>ONNX Runtime Web, TF.js"]
    SRV1 --> STORE[NovaMarket website]
    JAVA --> STORE
    CPP --> WH["Cameras / warehouse"]
    WEB --> STORE

The interchange formats are the piece that lets you switch languages without retraining:

Format What it stores Who reads it Risks and limits
pickle / joblib Any serialised Python object (the complete Pipeline of 04-03, with its ColumnTransformer) Python only, with the same versions of scikit-learn and dependencies Executes code when loaded: never load a pickle from an untrusted source; breaks when versions change; no other language reads it
PyTorch state_dict (torch.save) The parameter tensors of a network (the 497 numbers of the 05-02 MLP), not the architecture Python with PyTorch; you must rebuild the network with the same code before loading It is a pickle underneath: same security precautions
ONNX (Open Neural Network Exchange) The model's computation graph in an open standard format ONNX Runtime from Python, C++, Java, C#, JavaScript, mobile; converters from PyTorch, scikit-learn (skl2onnx), TensorFlow Not every operation converts (exotic preprocessing steps tend to fight back); operator versions
SavedModel / TFLite / Core ML TensorFlow models, for mobile (TFLite) or Apple (Core ML) Their own ecosystems Tied to one vendor
PMML Classical statistical models in XML Older enterprise tools, Java Dated, not very expressive

In 07-03 we will run the save and reload with joblib and state_dict and check that the reloaded model predicts the same. Here it is enough to keep the rule: saving a model is saving code + weights + versions; the format you choose determines from which language you will be able to use it and for how long it will keep loading.

  1. Language comparison table

Language Paradigm Strength in AI AI ecosystem When to choose it
Python Multi-paradigm, dynamic, interpreted Prototyping, training, data science, LLMs By far the largest (NumPy, pandas, scikit-learn, PyTorch, Hugging Face...) The default for almost everything we have done in the course
Lisp Functional, symbolic, code = data Historical symbolic AI, metaprogramming Small today Historical interest; niches in Clojure/Common Lisp
Prolog Logic, declarative Rules, unification, search with backtracking Small, specialised When the problem is pure logic (06-01); today an embedded rule engine is usually preferred
R Functional/vector, statistical Statistics, inference, plots, reports Very large in statistics (CRAN, tidyverse); smaller in DL Rigorous statistical analysis, teams of statisticians, biostatistics
Julia Multi-paradigm, JIT-compiled Fast scientific computing without two languages Medium and growing Custom numerical algorithms where the loop is the bottleneck
Java / Scala Object-oriented / functional, JVM Enterprise integration, Spark, services Large in data (Spark, Kafka), medium in ML Serving models inside existing Java systems; data at scale
C++ Imperative/OO, compiled Engines, top-speed inference, embedded It is the core of almost everything (PyTorch, ONNX Runtime, OpenCV) Extreme performance, devices, writing your own engine
JavaScript / TS Multi-paradigm, dynamic Inference in the browser and Node, interfaces TF.js, ONNX Runtime Web Models on the client, web demos, privacy
Rust / Go Systems, compiled, safe Inference services, data tools, infrastructure Growing (Polars, tokenisers) / infrastructure High-performance, high-reliability services
SQL Declarative, relational Extraction, aggregation, features in the data warehouse Universal in databases Whenever the data is in a database, that is, almost always

  1. The decision for NovaMarket

Marta gathers the team (two analysts, a Java backend developer, herself) and Diego asks the usual question: "how much does it cost and what are we getting into?". The decision that comes out, and that will hold up module 8:

  • Python to explore, prototype and train. It is what the data team knows, what we have used in modules 3-6 and where the libraries are (07-03). With style rules: vectorise (07-02), and do not rewrite in another language until a profile proves it is needed.
  • SQL to extract and aggregate. The queries of section 7 are done in the data warehouse; only what is needed reaches Python, already aggregated when possible. Features that can be computed in SQL (customer history) are computed there, so that training and production use the same definition.
  • Serve in Python with an HTTP API (FastAPI, 07-03) that NovaMarket's website calls: the returns predictor, the recommender and the assistant start that way, because it is the cheapest to build and maintain with the current team.
  • ONNX in reserve if the Java developer needs to run a model inside the store platform without depending on a Python service, or if one day a warehouse camera has to run the 05-04 classifier locally.
  • No Prolog, Lisp, R or Julia in production: they are known, they are respected, and Prolog's ideas live on in the Python rule engine of 06-02 (or in an enterprise rule engine if it grows).

With this decision, what remains to learn is how to use Python well for data: NumPy, pandas and Matplotlib, the libraries we used in module 4 without explaining them. That is the next lesson.

Common Mistakes and Tips

  • "Python is slow, so I'll rewrite in C++". Almost never. 95 % of the time the problem is a Python loop over numbers that vectorises into one line of NumPy (section 3) or a query that should have been done in SQL. Measure first (profiler or time.perf_counter) and rewrite only the hot 5 %.
  • Confusing the language with the library. "PyTorch is fast" does not mean "Python is fast"; it means you are calling C++ from Python. If you put a Python loop inside the training step, you are back to slow.
  • Loading pickles from anywhere. A downloaded .pkl or .pt can execute arbitrary code when opened. Only models from a trusted source; to share with third parties, prefer formats that do not execute code (ONNX, safetensors).
  • Choosing the language by fashion or by CV. Julia or Rust are excellent, but if the team knows Python and the bottleneck does not exist, introducing them adds maintenance cost with no benefit: that is Diego's question.
  • Forgetting SQL. The most frequent mistake in people coming from academic AI: loading the whole table into pandas and filtering there what the database would have filtered in milliseconds. Learn SELECT, WHERE, GROUP BY, JOIN as seriously as fit/predict.
  • Ignoring versions. The pickle of the pipeline trained today may not load with the next version of scikit-learn. Write down the versions (07-04) next to the model.

Exercises

Exercise 1. Extend the micro-benchmark of section 3 with a third operation: the mean of squares, sum(x*x for x in list) / N versus (arr ** 2).mean(). Before running it, predict whether the speed-up will be larger or smaller than that of the simple sum, and why. Run it and compare.

Exercise 2. Write (without running it, this is illustrative Prolog) the two missing pieces from exercise 3 of 06-01: rule R8 "an opened hygiene product is not returnable" and a fact for an opened NovaSmile toothbrush. Add a query and its expected answer. Then answer: which problem from 06-01 (the conflict between R8 and R3/R5) still exists in the Prolog version, and why does the language not solve it on its own?

Exercise 3. Modify the SQL query of section 7 to obtain the return rate by category and by whether the customer is new, sorted by category and then by rate, and only for groups with at least 100 orders (hint: HAVING, which filters after grouping). Run it and interpret the row with the highest rate in the light of the "hidden truth" of the 04-01 generator.

Solutions

Solution 1. Adding to the script:

def squares_loop():
    return sum(x * x for x in list_a) / N
def squares_numpy():
    return float((arr_a ** 2).mean())
t6 = time_it("mean of squares: Python generator", squares_loop)
t7 = time_it("mean of squares: numpy (a**2).mean()", squares_numpy)
print(f"speed-up squares: x{t6/t7:.0f}")

On the course machine: about 28.6 ms for the generator versus about 0.87 ms for NumPy (a speed-up of about 33 times, result 0.3332 in both, which is the theoretical mean of x² for x uniform in [0, 1], 1/3). The reasonable prediction was "the same order as the sum, perhaps somewhat smaller": the Python loop now does two operations per element (multiply and add), but NumPy also does two passes (** 2 creates a temporary array of a million floats and .mean() walks it), so the advantage is shared out and ends up somewhat below that of the simple sum. Still, tens of times. The lesson: the gap does not depend on the specific operation but on who runs the loop; and chained NumPy expressions create temporary arrays, a cost we will learn to take into account in 07-02.

Solution 2.

hygiene(o50001).
opened(o50001).
days_since_delivery(o50001, 5).
original_label(o50001).
unused(o50001).
not_returnable(O) :- hygiene(O), opened(O).                 % R8
% ?- not_returnable(o50001).      -> true.
% ?- refund_applies(o50001).      -> true.   (via R1, R3, R5: it is still derived!)

Prolog derives both things, just like our engine of 06-01: Horn clauses have no priority and no negation in the premises, and the interpreter limits itself to proving what it is asked. For R8 to block the refund you would have to rewrite R3 with Prolog's negation as failure (\+ not_returnable(O)), which is precisely the conflict resolution by specificity that we built by hand in 06-02. The language provides the engine; the conflict policy is still put in by the engineer.

Solution 3.

sql = """
SELECT category, new_customer,
       COUNT(*)                        AS orders,
       ROUND(100.0 * AVG(returned), 1) AS rate_pct
FROM orders
GROUP BY category, new_customer
HAVING COUNT(*) >= 100
ORDER BY category, rate_pct DESC;
"""
for row in con.execute(sql):
    print(row)

Output:

('accessories', 1, 143, 25.9)
('accessories', 0, 296, 6.8)
('computing', 1, 181, 30.4)
('computing', 0, 434, 9.9)
('electronics', 1, 316, 41.5)
('electronics', 0, 722, 11.6)
('home', 1, 266, 29.7)
('home', 0, 642, 6.9)

(We removed the WHERE amount > 20 filter to count every order; that is why the totals per category do not match those of section 7.) The row with the highest rate is electronics + new customer (41.5 %): in the 04-01 generator electronics adds +1.0 to the logit and new customer +1.6, on top of the new × amount cross term. Every group exceeds 100 orders, so HAVING removes none here; with a filter of 300 only the returning customers of electronics, home and computing and the new customers of electronics would remain. Notice that in every category new customers return between 3 and 4 times more: the same signal the logistic regression of 04-04 learned on its own.

Conclusion

We have put the first decision of Marta's workshop in order. A language for AI must be expressive, have a numerical ecosystem, offer performance through native libraries, interoperate with data and systems and have a community. Python wins on four of the five and compensates for the fifth, its slowness as an interpreter and the GIL, with vectorisation (our benchmark: 45-75 times faster with NumPy than with a loop) and native extensions: Python organises, C/C++/CUDA compute. We have seen where we come from, Lisp with its code as data and Prolog with the return rules of 06-01 written as facts, rules and queries; what R (statistics and inference) and Julia (speed without two languages) bring; which languages await the model when it leaves the laptop (Java/Scala and Spark, C++ with libtorch and ONNX Runtime, JavaScript in the browser, Rust and Go in services) and why SQL is the team's compulsory second language, with the return rate by category computed in the database. The "train in Python, serve in X" architecture rests on interchange formats (pickle/joblib with their risks, state_dict, ONNX) and NovaMarket's decision is clear: Python to prototype and train, SQL to extract, a Python service to serve and ONNX in reserve.

Now we have to learn to use that Python well. In modules 4 and 5 we wrote generate_orders_ml, groupby, to_datetime, boolean masks and curves with Matplotlib without stopping to explain them. In 07-02, Scientific Python: NumPy, pandas and Matplotlib, we will open up those three libraries with NovaMarket's orders and weekly demand: arrays and broadcasting, DataFrames, groupings, joins and dates, and plots that are described before they are drawn.

Fundamentals of Artificial Intelligence (AI)

Module 1: Introduction to Artificial Intelligence

Module 2: Basic Principles of AI

Module 3: Algorithms in AI

Module 4: Machine Learning

Module 5: Neural Networks and Deep Learning

Module 6: Logic and Expert Systems

Module 7: Tools and Programming Languages in AI

Module 8: Projects and Case Studies

Module 9: Exercises and Practice

Module 10: Additional Resources

© Copyright 2026. All rights reserved