Paper 2 AI and ML Answers
These answers correspond to Paper 2 AI and ML Drills.
Verification note: ChatGPT-work reported executing the revised pure-Python and scikit-learn model blocks in isolated temporary processes. In this Codex runtime, the pure-Python blocks were re-executed; live scikit-learn execution requires scikit-learn to be installed. Fixed random_state controls and stable centre ordering are used where exact output depends on stochastic or label-order behaviour.
Answer 1: Absolute Distance
def distance(a, b):
return abs(a - b)
print(distance(10, 3))
print(distance(4, 9))Expected output:
7
5Mark points:
- defines
distance(a, b); - subtracts the values;
- returns the absolute value.
Answer 2: Nearest Training Example
The question assumes training is non-empty and that the nearest example is unique for the supplied tests.
def nearest_example(training, value):
best_label = training[0][1]
best_distance = abs(training[0][0] - value)
for feature_value, label in training:
current_distance = abs(feature_value - value)
if current_distance < best_distance:
best_distance = current_distance
best_label = label
return best_label
training = [(20, "not spam"), (25, "not spam"), (180, "spam")]
print(nearest_example(training, 30))
print(nearest_example(training, 170))Expected output:
not spam
spamMark points:
- initialises a best distance and label;
- loops through training examples;
- calculates distance from
value; - updates the best example when a smaller distance is found;
- returns the label;
- produces both expected outputs.
Answer 3: k-NN Classification
The question assumes training is non-empty, 1 <= k <= len(training), no unresolved majority-label tie occurs, and original list order may be used if equal distances occur at the boundary.
def classify_knn(training, value, k):
distances = []
for feature_value, label in training:
distances.append((abs(feature_value - value), label))
distances.sort()
neighbours = distances[:k]
counts = {}
for _, label in neighbours:
counts[label] = counts.get(label, 0) + 1
best_label = None
best_count = -1
for label, count in counts.items():
if count > best_count:
best_label = label
best_count = count
return best_label
training = [(20, "not spam"), (25, "not spam"), (180, "spam"), (200, "spam")]
print(classify_knn(training, 30, 3))
print(classify_knn(training, 190, 3))Expected output:
not spam
spamSorting tuples such as (distance, label) orders by distance first, so distances[:k] selects the nearest k examples.
Mark points:
- calculates distance for every training example;
- stores distance with label;
- sorts by distance;
- selects the first
kneighbours; - counts neighbour labels;
- chooses the majority label;
- returns the predicted label;
- uses the supplied
k; - works for both test values;
- does not assume labels from the new item.
Answer 4: Accuracy
The question assumes predicted and actual have the same positive length.
def accuracy(predicted, actual):
correct = 0
for i in range(len(predicted)):
if predicted[i] == actual[i]:
correct += 1
return correct / len(predicted)
print(accuracy(["spam", "not spam", "spam"], ["spam", "spam", "spam"]))Expected output:
0.6666666666666666Mark points:
- loops through predictions;
- compares each prediction with the actual label;
- counts correct predictions;
- divides by the number of examples;
- returns the proportion.
Answer 5: Find Nearest Centre
The question assumes centres is non-empty. The strict < comparison keeps the earlier lower-index centre if distances are equal.
def nearest_centre(value, centres):
best_index = 0
best_distance = abs(value - centres[0])
for i in range(1, len(centres)):
current_distance = abs(value - centres[i])
if current_distance < best_distance:
best_index = i
best_distance = current_distance
return best_index
print(nearest_centre(3, [2, 10]))
print(nearest_centre(9, [2, 10]))Expected output:
0
1Mark points:
- initialises nearest centre index;
- compares distance to each centre;
- updates the index when a closer centre is found;
- returns the index;
- passes both tests.
Answer 6: k-Means Assignment Step
def nearest_centre(value, centres):
best_index = 0
best_distance = abs(value - centres[0])
for i in range(1, len(centres)):
current_distance = abs(value - centres[i])
if current_distance < best_distance:
best_index = i
best_distance = current_distance
return best_index
def assign_clusters(points, centres):
assignments = []
for point in points:
assignments.append(nearest_centre(point, centres))
return assignments
print(assign_clusters([2, 3, 10, 11], [2, 10]))Expected output:
[0, 0, 1, 1]Mark points:
- creates an assignment list;
- loops through all points;
- finds the nearest centre for each point;
- appends the centre index;
- returns the full assignment list;
- matches the expected output.
Answer 7: Mean of a Cluster
def mean(values):
return sum(values) / len(values)
print(mean([2, 3]))
print(mean([10, 11]))Expected output:
2.5
10.5Mark points:
- sums the values;
- counts the values;
- divides sum by count;
- returns a numeric average.
Answer 8: Update k-Means Centres
The question assumptions exclude empty clusters, so mean(cluster_points) is always called with a non-empty list.
def mean(values):
return sum(values) / len(values)
def update_centres(points, assignments, k):
new_centres = []
for cluster_index in range(k):
cluster_points = []
for i in range(len(points)):
if assignments[i] == cluster_index:
cluster_points.append(points[i])
new_centres.append(mean(cluster_points))
return new_centres
points = [2, 3, 10, 11]
assignments = [0, 0, 1, 1]
print(update_centres(points, assignments, 2))Expected output:
[2.5, 10.5]Mark points:
- loops over cluster indexes;
- collects points assigned to each cluster;
- uses matching positions in
pointsandassignments; - calculates mean for each cluster;
- appends each new centre;
- returns the new centre list;
- uses
kto control number of clusters; - matches the expected output.
Answer 9: k-NN Parameter Tuning
from sklearn.neighbors import KNeighborsClassifier
X_train = [[1], [2], [3], [8], [9], [10]]
y_train = ["low risk", "low risk", "high risk", "high risk", "high risk", "high risk"]
X_valid = [[3.2], [7.8]]
y_valid = ["low risk", "high risk"]
scores = {}
for k in [1, 3]:
model = KNeighborsClassifier(n_neighbors=k)
model.fit(X_train, y_train)
score = model.score(X_valid, y_valid)
scores[k] = score
print(f"k = {k} accuracy: {score}")
better_k = max(scores, key=scores.get)
print(f"better k: {better_k}")Expected output:
k = 1 accuracy: 0.5
k = 3 accuracy: 1.0
better k: 3The better value is k = 3 because it has higher validation accuracy. Comparing parameter values such as n_neighbors is parameter tuning. A final untouched test set would be preferable for a fair final estimate after tuning.
Mark points:
- imports or correctly uses
KNeighborsClassifier; - creates and fits a model with
n_neighbors=1; - creates and fits a model with
n_neighbors=3; - obtains the validation score for both models;
- prints or records both scores correctly;
- compares the two scores;
- selects
k = 3; - identifies the comparison as parameter tuning.
Answer 10: scikit-learn Integration
Part A: k-NN prediction [4]
from sklearn.neighbors import KNeighborsClassifier
X = [[20], [25], [180], [200]]
y = ["not spam", "not spam", "spam", "spam"]
knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X, y)
print(knn.predict([ [30] ])[0])
print(knn.predict([ [190] ])[0])Expected output:
not spam
spamfit(X, y) trains or stores the labelled examples for the k-NN classifier.
Mark points:
- imports or uses
KNeighborsClassifier; - creates
KNeighborsClassifier(n_neighbors=3); - fits the model with
Xandy; - predicts and extracts both labels correctly.
Part B: k-means centres [3]
from sklearn.cluster import KMeans
X = [[2], [3], [10], [11]]
kmeans = KMeans(n_clusters=2, random_state=0, n_init=10)
kmeans.fit(X)
centres = sorted(round(centre[0], 1) for centre in kmeans.cluster_centers_)
print(centres)Expected output:
[2.5, 10.5]fit(X) performs clustering. The cluster labels may be assigned in either order, so sorting the centre values gives a stable output for checking.
Mark points:
- creates
KMeans(n_clusters=2, random_state=0, n_init=10); - fits the model using
X; - extracts, rounds, sorts, and prints the centre values.
Part C: train/test split and cross-validation [5]
from sklearn.model_selection import train_test_split, cross_validate
from sklearn.neighbors import KNeighborsClassifier
X = [[1], [2], [3], [10], [11], [12]]
y = ["A", "A", "A", "B", "B", "B"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.33, random_state=0, stratify=y
)
model = KNeighborsClassifier(n_neighbors=1)
model.fit(X_train, y_train)
print(model.score(X_test, y_test))
results = cross_validate(KNeighborsClassifier(n_neighbors=1), X, y, cv=3)
print([round(score, 2) for score in results["test_score"]])Expected output:
1.0
[1.0, 1.0, 1.0].score(X_test, y_test) returns the classifier accuracy on the held-out test data. results["test_score"] contains the accuracy from each cross-validation fold.
Mark points:
- uses
train_test_split(...)with the specified arguments; - trains and scores
KNeighborsClassifier(n_neighbors=1); - prints the correct test accuracy;
- uses
cross_validate(...)withcv=3and prints the rounded scores; - explains what
.score(...)andresults["test_score"]represent.
Common weak answer:
- manually calculating the centres but not using the required library classes.