Question 121 · Convolutional Neural Networks: How Your Phone Recognizes Your Face · hard
Your phone's face-unlock feature uses a convolutional layer as its very first processing step, running directly on the raw grayscale image so a budget smartphone can do the whole computation on-device without draining the battery. This layer applies 6 filters, each sized 5×5, with stride 1 and no padding, to a single-channel 28×28 image, and each filter has its own bias term. How many learnable parameters (weights plus biases) does this one convolutional layer have?
156 — six 5×5 filters (25 weights each) plus one bias per filter: 6 × (25 + 1) = 156.
150 — six 5×5 filters, but this count forgets to add a bias term for each filter: 6 × 25 = 150.
4,704 — as if each output value were fully connected to all 784 pixels in the 28×28 image, ignoring the weight sharing that makes convolution efficient.
26 — the parameter count for a single 5×5 filter with its bias, without multiplying by the 6 filters actually used in this layer.
Answer: A. 156 — six 5×5 filters (25 weights each) plus one bias per filter: 6 × (25 + 1) = 156.
ExplanationEach of the 6 filters slides over the single-channel image, so every filter has 5 × 5 = 25 weights plus its own bias, for 26 parameters per filter. Because a convolutional layer reuses that same set of 26 numbers at every position the filter slides to — this is weight sharing — the total for the whole layer is just 6 × 26 = 156 parameters, completely independent of the 28×28 image size. Compare that to a fully-connected layer producing 6 outputs from all 784 pixels: it would need 6 × 784 = 4,704 weights (plus biases) because every output connects separately to every pixel. That huge gap — 156 versus 4,704 — is exactly why CNNs can run face-unlock in real time on a phone's limited processor instead of needing a cloud server for every scan.
Question 122 · Natural Language Processing: Making Machines Understand Language · hard
An IRCTC customer-support chatbot is trained on exactly three past chat lines (each treated as one sentence, with `<s>` marking the sentence start and `</s>` marking the sentence end):
```
<s> book my ticket </s>
<s> book my seat </s>
<s> cancel my ticket </s>
```
It uses a bigram language model: the probability of a sentence is the product of each word's probability given only the single word right before it,
P(w1...wn) = P(w1|<s>) x P(w2|w1) x ... x P(</s>|wn),
where each conditional probability is estimated straight from the corpus with no smoothing: P(wi|wi-1) = Count(wi-1, wi) / Count(wi-1). A user then types a brand-new message, "cancel my seat", which never occurred word-for-word in training. What probability does this bigram model assign to "cancel my seat"?
1/3, since the model only needs to multiply P(my|cancel) by P(seat|my), skipping the probability of starting the sentence with cancel right after the boundary marker.
1/6, because Count(my) is taken as 2 by counting only the two ticket-related sentences, which makes P(seat|my) come out to 1/2 instead of 1/3.
1/9, since P(cancel|<s>) x P(my|cancel) x P(seat|my) x P(</s>|seat) works out to (1/3) x 1 x (1/3) x 1.
0, since the exact three-word sequence cancel my seat never occurs among the three training sentences, so the model has no way to assign it any probability at all.
Answer: C. 1/9, since P(cancel|<s>) x P(my|cancel) x P(seat|my) x P(</s>|seat) works out to (1/3) x 1 x (1/3) x 1.
ExplanationA bigram model scores a sentence by chaining together local, word-to-word transition probabilities rather than by matching the whole sentence against something it memorized, so it can still assign a nonzero score to "cancel my seat" even though that exact three-word sequence never appeared in training — all that matters is whether each consecutive pair of words was individually seen. Walking through the corpus: `<s>` is followed by "cancel" in exactly 1 of the 3 training sentences, so P(cancel|<s>) = 1/3. The word "cancel" appears only once in the whole corpus and is always followed by "my", so P(my|cancel) = 1/1 = 1. The word "my" appears 3 times (once per sentence), and only 1 of those 3 occurrences is followed by "seat" (in "book my seat"), so P(seat|my) = 1/3. Finally, "seat" appears once and is always followed by the end marker, so P(</s>|seat) = 1/1 = 1. Chaining these together gives (1/3) x 1 x (1/3) x 1 = 1/9. Landing on 1/3 comes from dropping the P(cancel|<s>) factor and starting the chain at "my" instead of at the sentence boundary. Landing on 1/6 comes from undercounting Count(my) as 2, by forgetting that "my" also occurs in the ticket-cancellation sentence and not just the two sentences that mention a specific booking action. Landing on 0 confuses an n-gram model with a full-sentence lookup table — the entire point of factoring a sentence into bigrams is that the model can generalize to new word combinations by recombining transitions it has already seen, rather than requiring an exact match to a training sentence.
Question 123 · Computer Vision for Self-Driving Cars · hard
A self-driving car's front camera captures video at 20 frames per second, and its computer-vision pipeline takes 150 milliseconds after a frame arrives to detect a pedestrian and issue a braking command. The car is travelling at 72 km/h (20 m/s) in a straight line. In the worst case — where a pedestrian steps into the camera's view immediately after a frame has just been captured — how far does the car travel before the braking command is issued?
4 meters, because the worst-case delay is the 50 ms wait until the next frame is captured plus the 150 ms processing time (200 ms total), and the car covers 20 m/s x 0.2 s = 4 m in that time.
3 meters, because only the 150 ms processing time counts as delay, and the car covers 20 m/s x 0.15 s = 3 m while the pipeline computes the braking command.
1 meter, because only the 50 ms gap until the next frame arrives matters, and the car covers 20 m/s x 0.05 s = 1 m before the camera even captures the pedestrian.
5 meters, because the system needs two full frame intervals before it starts processing, adding 250 ms of delay, so the car covers 20 m/s x 0.25 s = 5 m.
Answer: A. 4 meters, because the worst-case delay is the 50 ms wait until the next frame is captured plus the 150 ms processing time (200 ms total), and the car covers 20 m/s x 0.2 s = 4 m in that time.
ExplanationThe key idea a lot of students miss is that a camera-based perception system has two separate sources of delay that stack, not one. First, the camera only samples the world at discrete moments — at 20 frames per second, a new frame is captured every 1/20 s = 50 ms. If a pedestrian steps into view right after a frame was just taken, the system has no idea anything happened until the next frame arrives, which in the worst case takes nearly the full 50 ms frame period. Only once that frame is captured does the 150 ms compute-vision pipeline (detection plus decision) start running. So the total worst-case perception-to-action delay is 50 ms + 150 ms = 200 ms = 0.2 s, not just the 150 ms processing time alone. Converting speed: 72 km/h = 72000 m / 3600 s = 20 m/s. Distance covered during the 0.2 s delay is 20 m/s x 0.2 s = 4 m. This 4 m gap is added on top of the car's normal mechanical braking distance, which is why real autonomous-driving stacks are engineered to minimize both frame rate lag and inference latency — every millisecond of delay directly becomes extra stopping distance at highway speed, similar to how a driver's reaction time before applying the brake adds distance in ordinary drivers' education physics problems.
Question 124 · Cybersecurity Fundamentals: Encryption, Authentication, and Staying Safe · hard
Alice and Bob want to agree on a shared secret key over an insecure network using Diffie–Hellman key exchange with public modulus p = 23 and public base g = 5. Alice secretly picks a = 6 and publicly sends A = 5^6 mod 23 = 8. Bob secretly picks b = 15 and publicly sends B = 5^15 mod 23 = 19. Only p, g, A, and B ever travel across the network — the exponents a and b are never sent. Using her private exponent a = 6 together with the value B = 19 that she received from Bob, what is the shared secret key Alice actually computes, and why can an eavesdropper who intercepts only p, g, A, and B not compute it the same way?
B^a mod p = 19^6 mod 23 = 2 — Alice can compute this because she knows her own private exponent a, but an eavesdropper cannot without also knowing a.
A × B mod p = 8 × 19 mod 23 = 14 — but this is fully computable by an eavesdropper too, since A and B are both values that were sent openly.
(A + B) mod p = 27 mod 23 = 4 — but this is also fully computable by an eavesdropper, since it only uses the two intercepted public values.
B^b mod p = 19^15 mod 23 = 20 — but Alice would need Bob's private exponent b to compute this, and b never travels over the network at all.
Answer: A. B^a mod p = 19^6 mod 23 = 2 — Alice can compute this because she knows her own private exponent a, but an eavesdropper cannot without also knowing a.
ExplanationThis is Diffie–Hellman key exchange, and the whole scheme rests on one asymmetry: modular exponentiation is cheap to compute in the forward direction but expensive to reverse (the discrete logarithm problem). Alice knows her private exponent a = 6, so she computes B^a mod p = 19^6 mod 23 by repeated multiplication and reduction: 19^2 mod 23 = 361 mod 23 = 16; 19^3 mod 23 = 16 × 19 mod 23 = 304 mod 23 = 5; 19^4 mod 23 = 5 × 19 mod 23 = 95 mod 23 = 3; 19^5 mod 23 = 3 × 19 mod 23 = 57 mod 23 = 11; 19^6 mod 23 = 11 × 19 mod 23 = 209 mod 23 = 2. So Alice's shared secret is 2. Bob independently lands on the same number by computing A^b mod p = 8^15 mod 23, which also equals 2 — that both sides land on the identical value without ever exchanging it directly is the entire point of the protocol. An eavesdropper watching the wire sees only p, g, A, and B, never a or b, so reproducing B^a or A^b would mean first recovering a hidden exponent from a public value — computationally infeasible once p is realistically large. Multiplying or adding the intercepted public values (getting 14 or 4) looks like a shortcut but is a trap: both operations use nothing except information the eavesdropper already has, so if either were the real shared secret, the key exchange would offer zero protection. Raising B to Bob's own exponent b instead of Alice's a (getting 20) is a different kind of mistake — it isn't something Alice could ever produce herself, since b is Bob's private value and is never sent across the network for her to use.
Question 125 · Feature Engineering: The Art of Data Preparation · hard
A farm-yield prediction dataset from Punjab has a "monsoon_rainfall_mm" feature. In the training set this feature ranges from a minimum of 200 mm to a maximum of 1000 mm, and a data scientist engineers a normalized feature using min-max scaling: normalized = (rainfall - min) / (max - min), fit once on the training data and then applied unchanged to any new data. When a test-set district reports monsoon rainfall of 1200 mm, what value does the normalized feature take, and why?
1.25, since the fitted scaler always reuses the training set's stored minimum (200) and maximum (1000) — it never recalculates these values from new data, so an out-of-range input can normalize to more than 1.
Exactly 1.0, because scikit-learn's MinMaxScaler clips every transformed value to the [0, 1] boundary by default, regardless of how far the raw input exceeds the training maximum.
1.0 exactly, because a fitted scaler quietly updates its stored minimum and maximum whenever it meets a new value, treating 1200 mm as the new maximum during transformation.
-0.25, computed as (max - rainfall) / (max - min), since values that exceed the training maximum should scale toward the lower end of the normalized range.
Answer: A. 1.25, since the fitted scaler always reuses the training set's stored minimum (200) and maximum (1000) — it never recalculates these values from new data, so an out-of-range input can normalize to more than 1.
ExplanationMin-max scaling is a feature-engineering step where the minimum and maximum are learned from the training data alone and then frozen — they are stored constants, not something recomputed on every new input. With min = 200 and max = 1000 from training, the formula for the test point is (1200 - 200) / (1000 - 200) = 1000 / 800 = 1.25. This is the whole point of "fit on train, transform on test": the scaler has no awareness of the 1200 mm value's existence beyond plugging it into the frozen formula, so nothing stops the output from leaving the [0, 1] range when real-world data exceeds what training ever showed it — a genuine and consequential edge case, since a downstream model trained only on inputs in [0, 1] may behave unpredictably on 1.25. Clipping to [0, 1] is not automatic behavior of standard min-max scaling; a scaler does not silently update its stored min/max on every transform call, since that would make outputs inconsistent across time (the same rainfall value would normalize differently depending on what else had been transformed before it) and defeat the purpose of a fixed, reproducible feature; and flipping the numerator to (max - rainfall) computes a different, unrelated quantity (a "distance from the top" measure) rather than the standard min-max normalization, and gives -0.25, not the correct 1.25.
A fintech analyst is using gradient boosting to predict daily UPI transaction volume (in lakhs) for a merchant app, using one feature: whether the day is a festival day. The training data is:
| Day | Type | Actual transactions (lakh) |
|-----|----------|------------------------------|
| 1 | Normal | 80 |
| 2 | Normal | 100 |
| 3 | Festival | 180 |
| 4 | Festival | 200 |
**Round 0:** The model starts with F₀, a constant that predicts the mean of all four targets for every day.
**Round 1:** A regression tree is trained on the residuals (actual − F₀), splitting on the festival-day feature. Each leaf outputs the average residual of the days that land in it.
**Shrinkage:** Before adding the new tree's output to the running prediction, XGBoost multiplies it by a learning rate η = 0.1 — this is standard practice specifically to stop any single tree from dominating the ensemble and causing overfitting.
After this one boosting round, what transaction volume (in lakhs) does the model predict for a festival day?
145 lakh transactions — the base prediction (140) plus the shrunk correction (0.1 × 50 = 5)
190 lakh transactions — the base prediction (140) plus the full, un-shrunk residual correction (50)
50 lakh transactions — just the first tree's leaf output, treating it as a standalone predictor
95 lakh transactions — the average of the base prediction (140) and the tree's leaf output (50)
Answer: A. 145 lakh transactions — the base prediction (140) plus the shrunk correction (0.1 × 50 = 5)
ExplanationStart with the base model. The mean of all four targets is (80 + 100 + 180 + 200) / 4 = 560 / 4 = 140, so F₀ = 140 for every day.
Next, compute residuals (actual − F₀): normal days give 80 − 140 = −60 and 100 − 140 = −40; festival days give 180 − 140 = 40 and 200 − 140 = 60. The first tree splits on the festival-day feature, and each leaf outputs the average residual of its group: the normal-day leaf outputs (−60 + −40) / 2 = −50, and the festival-day leaf outputs (40 + 60) / 2 = 50.
The key idea in gradient boosting is that this tree does not replace the base prediction — it corrects it, and only by a fraction of its full output. XGBoost scales every new tree's contribution by the learning rate η before adding it in: F₁ = F₀ + η × (leaf value). For a festival day, that's F₁ = 140 + 0.1 × 50 = 140 + 5 = 145.
Skipping the learning rate and adding the full residual (140 + 50 = 190) is the most common error — it ignores why shrinkage exists: it deliberately caps each tree's influence so the model needs many small, cautious steps rather than one large, overfitting-prone jump. Treating the tree's leaf value (50) as the whole prediction confuses gradient boosting with a single decision tree — boosted models are additive ensembles, and F₀ is never discarded. Averaging the base prediction with the leaf value (95) invents an operation gradient boosting doesn't use; the tree's output is always a scaled, additive correction, never blended in by averaging.
Question 127 · Introduction to Attention Mechanisms · hard
In a transformer's self-attention layer, the pronoun "it" in the sentence "The steel trunk didn't fit into the almirah because it was too large" must decide which earlier word to pay attention to. Its query vector is q = (4, 1). The key vectors for three candidate words are: trunk → (2, 3), almirah → (1, 5), and large → (0, 2). The raw attention score for each candidate is the dot product of q with that word's key vector, and these scores are then passed through softmax to produce the final attention weights. Which word receives the highest attention weight from "it"?
"almirah", since its key vector (1, 5) has the largest magnitude of the three key vectors, and a longer key vector should attract more attention regardless of its alignment with the query.
"large", since its dot-product score with the query is only 2, the lowest of the three, and in attention the smallest score is meant to represent the strongest match, the way a smaller distance means two points are closer together.
"trunk", since its dot-product score with the query is 11, the highest of the three raw scores, and softmax is a strictly increasing function, so the highest raw score always maps to the highest final attention weight.
All three words, since softmax always converts its inputs into probabilities that sum to exactly 1, which means every candidate word ends up receiving an equal share of the attention weight.
Answer: C. "trunk", since its dot-product score with the query is 11, the highest of the three raw scores, and softmax is a strictly increasing function, so the highest raw score always maps to the highest final attention weight.
ExplanationThe raw attention score between the query and each key is the dot product: q·k(trunk) = 4(2) + 1(3) = 8 + 3 = 11, q·k(almirah) = 4(1) + 1(5) = 4 + 5 = 9, and q·k(large) = 4(0) + 1(2) = 0 + 2 = 2. In self-attention, a larger dot product means the query and key vectors point in more similar directions — stronger relatedness — which is the opposite of a distance metric, where a smaller value means two things are closer. Softmax then turns these three scores into probabilities, but softmax is a strictly increasing (monotonic) function: it never changes the relative ranking of its inputs, so whichever score goes in largest comes out as the largest weight. Since 11 is the largest of the three raw scores, "trunk" receives the highest attention weight — which also matches the sentence itself, since something described as "too large" to fit is the trunk, not the almirah (the container) or the word "large" (an adjective, not a real antecedent). Key-vector magnitude on its own does not determine attention weight: "almirah"'s key vector has the largest magnitude (√26 ≈ 5.10) of the three, yet it still scores lower than "trunk" once direction is combined with the query through the dot product. And softmax does not force equal outputs — it only guarantees the outputs are positive and sum to 1, while still preserving the order of the scores that were fed into it.
Question 128 · Anomaly Detection: Finding the Unusual · hard
A campus Wi-Fi anomaly-detection script flags an hour as suspicious if its login-attempt count is more than 2 standard deviations above the mean of the known-normal traffic (here, standard deviation means the population standard deviation — the square root of the average squared deviation from the mean). Over 9 typical hours, the attempt counts were 42, 45, 40, 48, 44, 43, 46, 41, 47. A 10th hour then recorded 50 attempts. Using only the 9 typical hours to build the baseline, is the 50-attempt hour flagged as anomalous, and what is the correct threshold?
Yes — the baseline mean is 44 and the population standard deviation is about 2.58, so the threshold (mean + 2 standard deviations) is about 49.2 attempts; since 50 exceeds this, the hour is flagged as anomalous.
No — the baseline mean is 44, and squaring the deviations gives a spread of about 6.67, so the threshold (mean + 2 x 6.67) is about 57.3 attempts, well above 50, so the hour is not flagged.
No — folding all 10 hours (the 9 typical hours plus the new one) into the baseline shifts the mean to 44.6 and the standard deviation to about 3.04, pushing the threshold to about 50.7 attempts, just above 50, so the hour is not flagged.
Yes — since the standard deviation of the 9 typical hours is about 2.58, any hour more than 1 standard deviation above the mean of 44 (i.e., above about 46.6) counts as anomalous under this rule, and 50 clearly exceeds this.
Answer: A. Yes — the baseline mean is 44 and the population standard deviation is about 2.58, so the threshold (mean + 2 standard deviations) is about 49.2 attempts; since 50 exceeds this, the hour is flagged as anomalous.
ExplanationThe 9 baseline values (42, 45, 40, 48, 44, 43, 46, 41, 47) sum to 396, giving a mean of 396/9 = 44. Squaring each deviation from 44 gives 4, 1, 16, 16, 0, 1, 4, 9, 9, which sum to 60. Dividing by 9 (population variance) gives 6.67, and taking its square root gives the population standard deviation, about 2.58. Two standard deviations above the mean is 44 + 2(2.58) ≈ 49.2 attempts — that is the flagging threshold. The 10th hour recorded 50 attempts, which is greater than 49.2, so it crosses the threshold and gets flagged as anomalous.
Squaring the deviations but never taking the square root (using variance instead of standard deviation) inflates the apparent spread to 6.67 and the threshold to about 57, which is why that path wrongly clears the 50-attempt hour. Folding the new 50-attempt reading into the baseline itself is a classic anomaly-detection pitfall: it shifts the mean to 44.6 and the standard deviation to about 3.04, nudging the threshold up to about 50.7 — the outlier contaminates its own baseline and just barely escapes detection. And using only 1 standard deviation above the mean (about 46.6) ignores the problem's explicit 2-standard-deviation rule, even though it happens to still flag the hour for the wrong reason.
Question 129 · Parallel Computing: Making Programs Faster · hard
An IRCTC ticket-booking server runs a nightly batch job that reconciles the waitlist. Profiling shows the job takes 100 seconds total: 20 seconds is unavoidably sequential (updating one shared waitlist counter that cannot be split across threads), and the remaining 80 seconds is work that can be divided evenly across parallel worker threads. If this parallelizable portion is spread across 4 CPU cores instead of running on 1, what is the overall speedup of the batch job (original time divided by new total time)?
2.5x, because the serial 20 seconds stays fixed while only the 80 seconds of parallel work shrinks to 20 seconds on 4 cores, giving a new total of 40 seconds
4x, because running on 4 cores multiplies the entire job's speed by 4 regardless of which parts are sequential
3.2x, because multiplying the 4 cores by the 80% parallelizable fraction directly gives the overall speedup factor
5x, because dividing the 80-second parallel portion by 4 cores gives a new total runtime of 20 seconds, so the job finishes 5 times faster
Answer: A. 2.5x, because the serial 20 seconds stays fixed while only the 80 seconds of parallel work shrinks to 20 seconds on 4 cores, giving a new total of 40 seconds
ExplanationThe 100-second job splits into a 20-second sequential portion (updating the shared counter) that no amount of parallel hardware can shrink, and an 80-second portion whose runtime scales with the number of cores used. Spreading that 80-second portion across 4 cores brings it down to 80 ÷ 4 = 20 seconds, so the new total runtime is 20 (serial) + 20 (parallel) = 40 seconds. Comparing the original 100 seconds to the new 40 seconds gives a speedup of 100 ÷ 40 = 2.5x. This is exactly what Amdahl's Law predicts: the unavoidable serial fraction (20% of the original job) caps how much benefit adding cores can deliver, so 4 cores never produce a full 4x improvement.
The 4x answer assumes the entire job speeds up in proportion to the core count, ignoring that the 20-second serial section still runs on a single core no matter how many cores are available. The 3.2x answer comes from multiplying the core count by the parallelizable fraction (4 x 0.8) — a shortcut that never actually computes a real execution time, so it doesn't correspond to how the job actually runs. The 5x answer results from a bookkeeping slip: it divides only the parallel portion (80 ÷ 4 = 20 seconds) and forgets to add the fixed 20-second serial portion back in, treating the new total runtime as just 20 seconds instead of the true 40 seconds.
Question 130 · Scikit-Learn Mastery: Pipelines & Model Selection · hard
A student preparing for a Kaggle-style hackathon is building an SVC spam classifier for 200 archived IRCTC support-ticket emails, represented as 200 numeric feature vectors in `X` with labels in `y`. They write this code to evaluate the model with 5-fold cross-validation:
```python
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
X_scaled = StandardScaler().fit_transform(X)
scores = cross_val_score(SVC(), X_scaled, y, cv=5)
```
Compared to wrapping both steps in a single `Pipeline([('scaler', StandardScaler()), ('svc', SVC())])` and passing the *unscaled* `X` to `cross_val_score`, what is the most accurate description of what goes wrong with the code above?
StandardScaler().fit_transform(X) computes the mean and standard deviation from all 200 emails at once, so in every one of the 5 folds, the 40 validation emails held out by cross_val_score have already influenced the scaling applied to the 160 training emails; this data leakage makes the reported accuracy optimistically biased compared to a Pipeline, which recomputes scaling statistics from only each fold's 160 training emails.
cross_val_score automatically re-fits every preprocessing step inside whatever estimator it receives, so passing the already-scaled X_scaled changes nothing scaling statistics are still recomputed independently on each of the 5 training folds before SVC is trained.
The real flaw has nothing to do with leakage; StandardScaler's default settings strip away variance that SVC needs to place its decision boundary, so the fix is to replace StandardScaler with MinMaxScaler inside a Pipeline rather than to change when scaling happens.
Calling fit_transform() before cross_val_score consumes one of the 5 folds internally to fit the scaler, so cv=5 silently behaves like cv=4 and only 160 of the 200 emails ever get evaluated as validation data.
Answer: A. StandardScaler().fit_transform(X) computes the mean and standard deviation from all 200 emails at once, so in every one of the 5 folds, the 40 validation emails held out by cross_val_score have already influenced the scaling applied to the 160 training emails; this data leakage makes the reported accuracy optimistically biased compared to a Pipeline, which recomputes scaling statistics from only each fold's 160 training emails.
ExplanationCross-validation only gives a fair estimate of real-world accuracy when every piece of information the model uses—including preprocessing statistics like a scaler's mean and standard deviation—is learned exclusively from the training portion of each fold, with the held-out portion staying completely untouched until scoring time. In the code shown, StandardScaler().fit_transform(X) runs once on the entire 200-email dataset before cross_val_score ever splits it, so the mean and standard deviation baked into every scaled feature already reflect information from all 200 emails. When cross_val_score then forms its 5 folds of 160 training and 40 validation emails each, the 40 validation emails in every single fold have already left a statistical fingerprint on the very features the SVC is trained on. cross_val_score cannot undo this: it only controls what happens to the estimator object you hand it (here, plain SVC()), not any transformation you applied beforehand. Wrapping StandardScaler and SVC together in a Pipeline fixes this precisely because cross_val_score then refits the whole Pipeline on each fold, so the scaler's mean and standard deviation get recomputed from only that fold's 160 training emails, leaving its 40 validation emails genuinely unseen until scoring. The practical consequence of the leaked version is that the reported cross-validated accuracy tends to look better than the model will actually achieve on brand-new, never-before-seen tickets, since the scaler has effectively already been shown a numerical summary of the test data.
Question 131 · OpenCV: Image Processing & Face Detection · hard
A student building a school attendance app runs OpenCV's Haar Cascade face detector on a classroom photo using the code below:
```python
faces = face_cascade.detectMultiScale(
gray_img,
scaleFactor=1.5,
minNeighbors=5,
minSize=(20, 20),
maxSize=(150, 150)
)
```
detectMultiScale searches for faces by testing detection windows that start at minSize and repeatedly grow each side by multiplying it by scaleFactor, stopping as soon as a window size would exceed maxSize. How many distinct window sizes does detectMultiScale test on this image?
5 window sizes are tested: 20, 30, 45, 67.5, and 101.25 pixels — the next size (151.875) exceeds maxSize and is skipped.
6 window sizes are tested, because 151.875 is treated as close enough to fit within the 150-pixel maxSize limit.
4 window sizes are tested, since only the results of multiplying by scaleFactor count as tested scales — the starting minSize window itself isn't one of them.
3 window sizes are tested, because detectMultiScale only tries window sizes that come out as whole-number multiples of minSize, skipping any fractional-pixel sizes.
Answer: A. 5 window sizes are tested: 20, 30, 45, 67.5, and 101.25 pixels — the next size (151.875) exceeds maxSize and is skipped.
ExplanationdetectMultiScale builds an image pyramid: it starts the detection window at minSize and multiplies its side length by scaleFactor at each step, stopping the instant a size would exceed maxSize. Starting from 20 px: 20 -> 20 x 1.5 = 30 -> 30 x 1.5 = 45 -> 45 x 1.5 = 67.5 -> 67.5 x 1.5 = 101.25 -> 101.25 x 1.5 = 151.875. That sixth value, 151.875 px, is larger than maxSize = 150 px, so that window is never tested. This leaves exactly five window sizes actually used: 20, 30, 45, 67.5, and 101.25 pixels.
Two common slips explain the wrong answers. Rounding 151.875 down as "basically 150" and counting it anyway gives 6 — but detectMultiScale checks the exact size against maxSize, with no rounding tolerance. Forgetting that minSize itself is the first window tested (and only counting the sizes produced by multiplication) gives 4, an off-by-one error. Finally, OpenCV does not restrict window sizes to whole-number multiples of minSize — internally it scales the image by 1/scaleFactor at each pyramid level, so fractional-pixel sizes like 67.5 and 101.25 are completely normal, which is why the "3 whole-multiple sizes" answer is wrong too.
Question 132 · Word Embeddings — Turning Language into Mathematics · hard
A toy word-embedding model represents three words as 2-D vectors (real embeddings use hundreds of dimensions; this toy version keeps the arithmetic simple): cricket = (6, 8), bat = (3, 4), cat = (8, 6). Using the cosine similarity formula cosθ = (A·B) / (|A||B|) alongside the plain Euclidean distance formula, which word — bat or cat — is genuinely more semantically similar to cricket, and why do the two metrics disagree?
Cosine similarity gives 1.00 for cricket–bat versus 0.96 for cricket–cat, so bat is the more semantically similar word by direction, even though cat sits closer to cricket in raw Euclidean distance (≈2.83 vs 5) — embeddings prefer cosine similarity because it ignores magnitude differences that often just reflect how frequently a word appears in the training corpus.
Cat's Euclidean distance from cricket (≈2.83) beats bat's (5), so cat must be the more semantically similar word, since the nearest point in any vector space by straight-line distance is always the closest in meaning.
Swapping the usual result, cosine similarity actually comes out higher for cricket–cat (1.00) than for cricket–bat (0.96), which matches cat's shorter Euclidean distance and confirms cat as the closer word in meaning.
The dot product between cricket and cat (96) exceeds the dot product between cricket and bat (50), so cat must be the more similar word, because a larger raw dot product always signals stronger semantic similarity regardless of vector length.
Answer: A. Cosine similarity gives 1.00 for cricket–bat versus 0.96 for cricket–cat, so bat is the more semantically similar word by direction, even though cat sits closer to cricket in raw Euclidean distance (≈2.83 vs 5) — embeddings prefer cosine similarity because it ignores magnitude differences that often just reflect how frequently a word appears in the training corpus.
ExplanationTreat cricket, bat, and cat as arrows from the origin in a toy 2-D embedding space: cricket = (6, 8), bat = (3, 4), cat = (8, 6). Cosine similarity measures the angle between two vectors — cosθ = (A·B)/(|A||B|) — while Euclidean distance measures the straight-line gap between their tips; these are different questions and can point to different "winners."
Magnitudes: |cricket| = √(36+64) = √100 = 10, |bat| = √(9+16) = √25 = 5, |cat| = √(64+36) = √100 = 10.
cricket·bat = 6(3) + 8(4) = 18 + 32 = 50, so cos(cricket, bat) = 50/(10·5) = 1.00 — bat points in exactly the same direction as cricket, since cricket is literally 2×bat.
cricket·cat = 6(8) + 8(6) = 48 + 48 = 96, so cos(cricket, cat) = 96/(10·10) = 0.96 — a noticeably different direction.
Meanwhile Euclidean distance goes the other way: cricket to bat is √((6-3)² + (8-4)²) = √25 = 5, but cricket to cat is √((6-8)² + (8-6)²) = √8 ≈ 2.83 — cat's raw coordinates sit numerically closer to cricket's.
This is exactly why NLP systems compare word embeddings using cosine similarity rather than raw distance or a bare dot product. Two words can end up far apart in coordinate space simply because one appears more often in the training corpus and so has a longer vector, yet still mean almost the same thing as long as they point the same way (this is why bat's vector is a shorter, scaled-down copy of cricket's — same direction, different magnitude). Trusting Euclidean distance, or trusting the un-normalized dot product (96 > 50 looks convincing until you notice cat's vector is also longer, which inflates the dot product), both wrongly crown cat as the better match for cricket, when direction — not raw coordinate closeness — is what captures meaning.
Question 133 · SQL for Data Scientists: Advanced Queries · hard
A ride-hailing analytics team stores every completed IRCTC-linked corporate travel reimbursement in one table, `train_bookings(state, passenger, amount)`. The Karnataka and Maharashtra rows are:
| state | passenger | amount (₹) |
|---|---|---|
| Karnataka | Aisha | 4200 |
| Karnataka | Rohan | 3800 |
| Karnataka | Meera | 3800 |
| Karnataka | Kabir | 3500 |
| Maharashtra | Dev | 5000 |
| Maharashtra | Priya | 5000 |
| Maharashtra | Sana | 4700 |
This query is run:
```sql
SELECT state, passenger, amount,
RANK() OVER (PARTITION BY state ORDER BY amount DESC) AS rnk
FROM train_bookings;
```
What value does the `rnk` column show in Kabir's row?
1, because ORDER BY amount DESC ranks the smallest amount as rank 1 within each partition
3, because RANK() and DENSE_RANK() always produce identical output whenever there is a tie
7, because PARTITION BY state has no effect and every row in the table is ranked together in one continuous sequence
4, because RANK() gives Rohan and Meera the same rank 2 for their tied amount, then jumps Kabir to rank 4 instead of 3
Answer: D. 4, because RANK() gives Rohan and Meera the same rank 2 for their tied amount, then jumps Kabir to rank 4 instead of 3
ExplanationPARTITION BY state splits the table into separate ranking groups, so Kabir competes only against the other three Karnataka rows, not the Maharashtra ones. Within Karnataka, ORDER BY amount DESC sorts the amounts as 4200, 3800, 3800, 3500 — Aisha gets rnk 1, and Rohan and Meera tie for rnk 2 since they have the same amount. RANK() does not compress the sequence after a tie the way DENSE_RANK() does: it counts how many rows sit ahead of Kabir in the ordering (Aisha, Rohan, and Meera — three rows), so Kabir's rank is 3 + 1 = 4, leaving rnk 3 permanently unused in this partition. If the query used DENSE_RANK() instead, Kabir would get 3, because DENSE_RANK() numbers only the distinct amount values (4200, 3800, 3500) with no gaps. Reading ORDER BY amount DESC as ranking the smallest amount first would wrongly place Kabir at rnk 1, and ignoring PARTITION BY state and ranking all seven rows together (5000, 5000, 4700, 4200, 3800, 3800, 3500) would wrongly give Kabir rnk 7.
Question 134 · Dimensionality Reduction: PCA, t-SNE, UMAP · hard
A Class 9 student in Bengaluru is building a school analytics dashboard that uses PCA to compress each student's marks across 5 subjects (English, Hindi, Maths, Science, Social Science) into fewer "performance axes" for a 2D visualization. Running PCA on the standardized marks gives covariance-matrix eigenvalues of 42, 28, 18, 8, and 4 (variance units), listed from largest to smallest, one per principal component. If the dashboard must retain at least 90% of the total variance in the data, what is the minimum number of principal components she needs to keep?
Keep 2 components, since the first two eigenvalues sum to 42 + 28 = 70% of the total variance.
Keep 3 components, since the first three eigenvalues sum to 70 + 18 = 88% of the total variance.
Keep 4 components, since the first four eigenvalues sum to 88 + 8 = 96% of the total variance, the smallest count that clears the 90% threshold.
Keep 5 components, since only retaining every eigenvalue guarantees exactly 100% of the variance is preserved.
Answer: C. Keep 4 components, since the first four eigenvalues sum to 88 + 8 = 96% of the total variance, the smallest count that clears the 90% threshold.
ExplanationTotal variance equals the sum of all eigenvalues: 42 + 28 + 18 + 8 + 4 = 100 variance units. Tracking the cumulative share as components are added one at a time: the first component alone covers 42%, the first two cover 42 + 28 = 70%, the first three cover 70 + 18 = 88%, and the first four cover 88 + 8 = 96%. Three components still fall short of the 90% target at 88%, while four components clear it at 96%, so four is the smallest number of principal components that satisfies the requirement. This mirrors how a real PCA pipeline chooses component count in practice — by plotting cumulative explained variance and picking the smallest number of components above a target threshold (commonly 90-95%), rather than defaulting to 2 components just because that's convenient for plotting, or keeping all 5 components, which keeps 100% of the variance but defeats the purpose of dimensionality reduction entirely.
Question 135 · Advanced Testing: pytest, Mocking, Coverage · hard
An IRCTC ticket-booking backend batches UPI charges through a payment gateway object, and this pytest test mocks that gateway using `unittest.mock.Mock`:
```python
from unittest.mock import Mock
def charge_all(gateway, fares):
txn_ids = []
for fare in fares:
txn_ids.append(gateway.charge(fare))
return txn_ids
def test_charge_all():
gateway = Mock()
gateway.charge.side_effect = [1001, 1002, 1003]
fares = [640, 1280, 1920]
txn_ids = charge_all(gateway, fares)
gateway.charge.assert_called_with(1920)
```
After `test_charge_all()` runs, what is the value of `txn_ids`, and does the final `assert_called_with(1920)` line pass or raise an AssertionError?
Mock.side_effect, when set to a list, returns successive items on each call regardless of the arguments passed in, so txn_ids is [1001, 1002, 1003]; assert_called_with(1920) then passes because it checks only the arguments of gateway.charge's most recent call, not its full call history.
Since gateway is a plain Mock() without autospec, calling gateway.charge(fare) simply echoes the argument back, so txn_ids equals the original fares [640, 1280, 1920], and assert_called_with(1920) passes because the last fare charged actually was 1920.
assert_called_with verifies that every recorded call to gateway.charge matches the given arguments, not just the latest one, so even though txn_ids is [1001, 1002, 1003], checking against 1920 alone raises an AssertionError because 640 and 1280 were also charged.
The side_effect list is a finite sequence, so once gateway.charge has returned 1001, 1002, and 1003 in the loop, Mock immediately raises StopIteration to signal that the sequence is exhausted, even though no fourth call is ever made.
Answer: A. Mock.side_effect, when set to a list, returns successive items on each call regardless of the arguments passed in, so txn_ids is [1001, 1002, 1003]; assert_called_with(1920) then passes because it checks only the arguments of gateway.charge's most recent call, not its full call history.
ExplanationMock() creates a generic mock object, and setting gateway.charge.side_effect to a list makes each call to gateway.charge return the next item from that list in order — 1001 on the first call, 1002 on the second, 1003 on the third — completely independent of what argument was actually passed in. So after the loop runs over fares [640, 1280, 1920], txn_ids ends up as [1001, 1002, 1003], not the fares themselves. Separately, Mock records every call it receives, but assert_called_with(...) checks only the arguments of the single most recent call — it is not the same as assert_has_calls(...), which would check the full sequence of calls. Since the last call made inside the loop was gateway.charge(1920), the assertion matches and passes silently, even though gateway.charge was also called earlier with 640 and 1280. No StopIteration occurs either, because side_effect only raises that error if the mock is called again after its list is exhausted — here gateway.charge is called exactly three times, once per fare, matching the three items in the list precisely.
Question 136 · Neural Style Transfer: Artistic AI · hard
In Neural Style Transfer (Gatys et al.), the style loss at a layer is computed from the **Gram matrix** of that layer's feature maps — not from the raw activations directly. Suppose one convolutional layer has just 2 filters, and after flattening each filter's activation map you get:
```
Style image, filter A: [3, 1, 2, 0]
Style image, filter B: [1, 2, 0, 1]
Generated image, filter A: [2, 2, 1, 1]
Generated image, filter B: [0, 1, 1, 0]
```
The Gram matrix G for a set of filters is defined as G[i,j] = (sum over positions k) F_i[k] × F_j[k], computed for every pair of filters i, j, including i = j. The style loss for this layer is the sum of squared differences between every entry of the style image's Gram matrix and the corresponding entry of the generated image's Gram matrix. What is the style loss for this layer?
40
36
32
8
Answer: A. 40
ExplanationStyle loss compares Gram matrices, not raw activations, because the Gram matrix captures which filters fire together (feature correlations) — brushstroke patterns, color palettes, textures — independent of where in the image they occur. That correlation information is exactly what "style" means to a CNN.
For the style image: G_AA = 3²+1²+2²+0² = 14, G_BB = 1²+2²+0²+1² = 6, G_AB = G_BA = (3×1)+(1×2)+(2×0)+(0×1) = 5.
For the generated image: G_AA = 2²+2²+1²+1² = 10, G_BB = 0²+1²+1²+0² = 2, G_AB = G_BA = (2×0)+(2×1)+(1×1)+(1×0) = 3.
The Gram matrix here is a full 2×2 matrix, so the style loss sums the squared difference over all four entries — AA, AB, BA, and BB — not just the diagonal or one triangle:
(14−10)² + (5−3)² + (5−3)² + (6−2)² = 16 + 4 + 4 + 16 = 40.
36 comes from forgetting that AB and BA are both counted separately (summing the upper triangle only once), which understates the loss whenever cross-filter correlation shifts. 32 comes from using only the diagonal entries (AA and BB), which throws away exactly the cross-filter correlation that makes Gram matrices useful for capturing style — with only diagonals you're left comparing each filter's own activation energy, which misses how filters co-occur. 8 comes from skipping the Gram matrix entirely and computing the squared difference of the raw activations directly — that is literally the content loss formula, not the style loss, which is why style transfer must compute the two losses differently even though the images being compared look similar on the surface.
Question 137 · Bayesian Optimization for Hyperparameter Tuning · hard
A researcher at an Indian agri-tech startup is using Bayesian Optimization with the Upper Confidence Bound (UCB) acquisition function, UCB(x) = μ(x) + κ·σ(x), to pick the next learning rate to test for a neural network that forecasts monsoon rainfall for Tamil Nadu. Using κ = 2, the surrogate model built from previous trials predicts these validation accuracies for three untested learning rates — Candidate A: μ = 0.82, σ = 0.03; Candidate B: μ = 0.78, σ = 0.08; Candidate C: μ = 0.85, σ = 0.01. Which learning rate should Bayesian Optimization choose to evaluate next, and why?
Candidate C, since it posts the highest predicted mean accuracy (μ = 0.85) — Bayesian Optimization greedily targets the best expected value and ignores uncertainty once enough trials have been observed.
Candidate B, because its UCB score of 0.78 + 2×0.08 = 0.94 beats Candidate A's 0.82 + 2×0.03 = 0.88 and Candidate C's 0.85 + 2×0.01 = 0.87 — the large uncertainty term outweighs its lower mean.
Candidate A, because balancing its mean and uncertainty gives it a UCB score of 0.88, the highest of the three once both exploitation and exploration are combined.
Candidate C, because the κ·σ(x) exploration term is only added to break ties between candidates with equal means, so the candidate with the single highest mean is chosen outright.
Answer: B. Candidate B, because its UCB score of 0.78 + 2×0.08 = 0.94 beats Candidate A's 0.82 + 2×0.03 = 0.88 and Candidate C's 0.85 + 2×0.01 = 0.87 — the large uncertainty term outweighs its lower mean.
ExplanationBayesian Optimization doesn't just chase the candidate with the best predicted mean — it scores every candidate with an acquisition function that also rewards uncertainty, since testing a poorly-understood region can uncover an even better hyperparameter than anything seen so far. Computing UCB = μ + 2σ for each candidate: Candidate A gives 0.82 + 2(0.03) = 0.88, Candidate B gives 0.78 + 2(0.08) = 0.94, and Candidate C gives 0.85 + 2(0.01) = 0.87. Even though Candidate C has the best predicted accuracy on its own, the surrogate model is far less certain about Candidate B (σ = 0.08 versus 0.01 for C), and that large uncertainty earns it a bigger exploration bonus — enough to push its UCB score to 0.94, the highest of the three. So Bayesian Optimization selects Candidate B next: not because it currently looks best, but because evaluating it does the most to reduce uncertainty in a region that might be hiding a superior learning rate. This is exactly what separates Bayesian Optimization from a purely greedy search — it deliberately spends some trials exploring high-uncertainty regions rather than always exploiting the current best guess.
Question 138 · Data Augmentation Techniques and Best Practices · hard
Ridhima is training an OCR model to read digits on scanned CBSE board-exam admit cards. She starts with 2,000 original digit images and, for each one, generates 3 augmented copies (small rotations, zoom, and brightness changes), giving her 2,000 + (2,000 × 3) = 8,000 images total. She then randomly splits all 8,000 images into 80% training (6,400 images) and 20% test (1,600 images), trains her model, and gets 99.2% test accuracy — yet the model performs poorly when scanning real admit cards after deployment. What is the most likely explanation for this gap?
Data leakage occurred because augmentation was applied to all 2,000 images before the train-test split, so several augmented siblings of the same original image ended up in both the 6,400-image training set and the 1,600-image test set.
The ±15° rotation range was too aggressive for numeral recognition, since even a small rotation like that flips visually similar digits such as 6 and 9 into each other and silently corrupts their labels.
The model simply overfit because 8,000 images is too small for a digit-recognition task, and no split strategy could have prevented the accuracy gap seen at deployment.
The 80/20 split ratio was too aggressive; using a 90/10 split instead would have given the model enough extra training examples to close the gap seen during deployment.
Answer: A. Data leakage occurred because augmentation was applied to all 2,000 images before the train-test split, so several augmented siblings of the same original image ended up in both the 6,400-image training set and the 1,600-image test set.
ExplanationWith 2,000 original admit-card digit images and 3 augmented copies made per original, Ridhima's pool grows to 2,000 + (2,000 × 3) = 8,000 images. Because augmentation happened before the split, when this pool is randomly divided 80/20 into 6,400 training and 1,600 test images, near-identical siblings of the same source photo — one lightly rotated, one zoomed, one brightness-shifted — can land on opposite sides of the split. The test set then contains images the model has effectively already seen in a slightly transformed form, which inflates the reported 99.2% accuracy without measuring true generalization to unseen admit cards. A ±15° rotation is far too small to turn a 6 into a 9 (that needs a rotation close to 180°), so no labels were actually corrupted, and 8,000 images being "too small" doesn't explain why the test score itself came out so high — the split, not the dataset size or ratio, is the culprit. The best-practice fix is to split the original 2,000 images into train and test first, and only then augment the training portion, leaving the test set entirely free of near-duplicates so it reflects real-world performance.
Question 139 · Federated Learning: Collaborative ML Without Sharing Data · hard
Three Indian banks — Bank A, Bank B, and Bank C — jointly train a fraud-detection model using federated learning, so none of them ever shares its raw transaction data with the others or with the central server. Each bank trains the model locally on its own transactions and sends back only a single updated model weight; the coordinating server then combines these using the standard FedAvg algorithm, which weights each bank's update in proportion to how many transactions it trained on. Bank A trained on 4,000 transactions and returned a local weight of 0.60. Bank B trained on 1,000 transactions and returned a local weight of 0.90. Bank C trained on 5,000 transactions and returned a local weight of 0.50. What global weight does the FedAvg-based server compute for the shared model?
0.58, obtained by weighting each bank's local weight in proportion to how many transactions it trained on before combining them
0.67, obtained by averaging the three banks' local weights equally with no regard for how many transactions each one used
0.50, obtained by discarding the two smaller banks and using only Bank C's locally trained weight since it holds the most data
0.60, obtained by taking the middle value among the three banks' local weights instead of combining all three
Answer: A. 0.58, obtained by weighting each bank's local weight in proportion to how many transactions it trained on before combining them
ExplanationFederated learning's core idea is that raw data never leaves each device or organization — only model updates travel to the server — and FedAvg (Federated Averaging) combines those updates in proportion to how much data produced them, not by treating every contributor equally. Here Bank A contributed 4,000 transactions, Bank B contributed 1,000, and Bank C contributed 5,000, for a total of 10,000 transactions across the federation. Each bank's local weight is multiplied by its own transaction count: Bank A contributes 4,000 x 0.60 = 2,400, Bank B contributes 1,000 x 0.90 = 900, and Bank C contributes 5,000 x 0.50 = 2,500. Adding these gives 2,400 + 900 + 2,500 = 5,800, and dividing by the total of 10,000 transactions gives the global weight: 5,800 / 10,000 = 0.58. Treating all three banks equally — a plain average of 0.60, 0.90, and 0.50 — would give 0.67 and would let Bank B's tiny 1,000-transaction sample distort the global model just as much as Bank C's far larger 5,000-transaction sample, which is the opposite of what data-proportional aggregation is supposed to prevent. Letting the largest bank's update stand in for the whole federation (0.50) throws away the genuine signal Banks A and B contributed, defeating the purpose of collaborating at all. Picking the middle value (0.60) simply ignores how large each dataset actually was. FedAvg's data-proportional weighting is precisely what lets many small, privacy-protected contributors combine into one accurate model without any of them ever pooling their raw transaction records.
Question 140 · MLflow: Tracking Experiments and Managing Models · hard
A student is training a UPI fraud-detection classifier and uses MLflow to track three runs, each with a different learning rate. After logging each run's accuracy as a metric, she writes the following code to pull out what she believes is the best-performing run:
```python
import mlflow
for lr, acc in [(0.1, 0.82), (0.01, 0.91), (0.001, 0.88)]:
with mlflow.start_run():
mlflow.log_param("lr", lr)
mlflow.log_metric("accuracy", acc)
best_run = mlflow.search_runs(order_by=["metrics.accuracy"]).iloc[0]
print(best_run["metrics.accuracy"])
```
What value does this code print, and why?
0.91, because search_runs(order_by=["metrics.accuracy"]) sorts results from highest to lowest by default, so iloc[0] is the run with the best accuracy.
0.82, because MLflow's order_by treats an unqualified column name as ascending by default, so iloc[0] actually returns the run with the LOWEST accuracy, not the best one.
0.88, because MLflow ignores the order_by argument here and always returns results sorted by start_time descending, so iloc[0] is simply the most recently started run.
The code raises an error, because logging the parameter key "lr" in more than one separate run within the same experiment violates MLflow's uniqueness constraint on parameter names.
Answer: B. 0.82, because MLflow's order_by treats an unqualified column name as ascending by default, so iloc[0] actually returns the run with the LOWEST accuracy, not the best one.
ExplanationMLflow's search_runs(order_by=...) treats a column name with no direction as ascending — passing ["metrics.accuracy"] behaves exactly like ["metrics.accuracy ASC"]. To get results ranked from best to worst, the direction must be spelled out explicitly, as in order_by=["metrics.accuracy DESC"]. The three logged accuracies are 0.82, 0.91, and 0.88; sorted ascending that becomes 0.82, 0.88, 0.91, so .iloc[0] grabs the first row in that order — the run with lr=0.1 and accuracy 0.82, which is actually the WORST of the three models, not the best. Because an explicit order_by list was supplied, MLflow honors it rather than falling back to its default start_time-descending sort, so the "most recent run" explanation doesn't apply either. The for loop also opens a fresh mlflow.start_run() context on every iteration, so logging the parameter key "lr" three times causes no conflict — parameters are scoped to their own run, not shared across the whole experiment — so the code runs cleanly and prints 0.82. This ascending-by-default behavior is one of the most common real-world MLflow mistakes: developers assume "sorting by a metric" naturally means best-first, when DESC always has to be stated to get that.