For nine modules you built the ideas by hand. You wrote the gradient-descent update rule, coded the sigmoid, split a dataset with your own loop, and counted accuracy one comparison at a time. That was the point: the durable skill is knowing what the model does, not the keystroke that summons it.
Now meet the tool that does all of it in one line. scikit-learn is the Python library the whole industry reaches for, and every algorithm you implemented has a direct equivalent inside it. The good news is that nothing you learned is wasted, because it is exactly what makes you dangerous with this library instead of dependent on it.
flowchart LR A["your by-hand MSE"] --> B["sklearn.metrics.mean_squared_error"] C["your by-hand split"] --> D["sklearn.train_test_split"] E["your gradient loop"] --> F["LinearRegression.fit"] style B fill:#10b981,color:#fff style D fill:#10b981,color:#fff style F fill:#10b981,color:#fff
One API to rule them all
scikit-learn's power is that every model speaks the same three-word language. You create an estimator, call .fit(X, y) to learn from data, and call .predict(X_new) to guess new answers. That is the whole interface. A linear regressor, a logistic classifier, a decision tree, a random forest, a k-means clustering — they all expose the same verbs, so once you know the pattern you can swap models the way you swap functions.
The data lives in a 2-D feature matrix X, where each row is one example and each column is one feature, alongside a 1-D target vector y. That is the same design matrix you shaped by hand in Module 2; scikit-learn just insists on its shape and refuses to guess.
from sklearn.linear_model import LinearRegression X = [[1], [2], [3], [4]] y = [2, 4, 6, 8] model = LinearRegression() model.fit(X, y) print(model.coef_) # [2.] print(model.intercept_) # 0.0 print(model.score(X, y)) # 1.0
Reading what fit learned
After .fit(), the model holds the parameters it found, each exposed as an attribute ending in a trailing underscore — a scikit-learn convention that means this was learned from data, not set by you. A fitted LinearRegression exposes .coef_, the slopes with one entry per feature, and .intercept_, the bias term. On the data above, where y was exactly 2 * x, those come out as 2.0 and 0.0, the same values your by-hand gradient loop spent Module 3 converging toward.
.score(X, y) reports how well the fitted model explains y. For a regressor that is the R-squared, the fraction of variance captured, where 1.0 is a perfect fit and 0.0 is no better than always predicting the mean.
Score on training data is an optimistic lie
There is a catch hiding in the example above: .score(X, y) was called on the very same X and y the model just learned from, so of course it looks great. A model can memorise its training data and still collapse on anything new, which is the whole reason you built a train/test split by hand in Module 2.
scikit-learn ships that split as train_test_split: you fit on one portion of the data and grade on a holdout the model never saw. The number you report to anyone is always the holdout score, never the training score. That discipline is the entire subject of the next lesson, where a Pipeline makes it impossible to forget.
flowchart LR D["dataset"] --> TR["train: fit the model"] D --> TE["test: grade honestly"] style TE fill:#b45309,color:#fff
# The one-weight model from Module 3: prediction = w * x + b. # This is exactly what LinearRegression generalizes to many columns. xs = [1, 2, 3, 4] w, b = 2.0, 0.0 predictions = [w * x + b for x in xs] print(predictions)
The metrics you built are here too
The loss and metric functions you implemented by hand — mean squared error, accuracy, precision, recall — all live in sklearn.metrics, and they compute exactly what yours did. You no longer need them to understand the idea; you need them to stop rewriting the arithmetic on every project.
Run the block below and it should feel familiar. It is the same MSE from Module 3, the loop-and-average you wrote yourself, producing the same number scikit-learn would return. The library did not replace your understanding; it packaged it.
# Mean squared error, the loss you built in Module 3.
def mse(predictions, targets):
total = 0.0
for p, t in zip(predictions, targets):
total += (p - t) ** 2
return total / len(predictions)
preds = [2.0, 4.0, 6.0, 9.0]
ys = [2.0, 4.0, 6.0, 8.0]
print(round(mse(preds, ys), 4))
Why building it by hand still matters
You might wonder why this course spent nine modules on by-hand implementations if a single import does the same work. The answer is debugging. When a scikit-learn model hands back a nonsense score, a practitioner who only knows the API is stuck reading documentation; one who built the loss, the gradient, and the split can ask whether the data was scaled, whether a feature quietly leaked the target, or whether the classes were balanced.
The library makes the happy path fast. Your from-scratch work is what lets you survive every other path, and it is what separates someone who calls .fit() from someone whose models are actually worth fitting.
flowchart LR A["model = LinearRegression()"] --> B["model.fit(X, y)"] B --> C["model.predict(X_new)"] B --> D["model.score(X, y)"] style B fill:#10b981,color:#fff
# Accuracy: the share of predictions a classifier got right.
def accuracy(y_true, y_pred):
correct = sum(t == p for t, p in zip(y_true, y_pred))
return correct / len(y_true)
print(round(accuracy([0, 0, 1, 1], [0, 1, 1, 1]), 4))
What does calling model.fit(X, y) actually do?
fit() runs the learning step: it searches for the parameters that best fit X to y, then stores them as learned attributes (like .coef_). It does not save files, print scores, or split data.
What does this print? The data is exactly y = 2x, so the fit is perfect. (Build-verified; the browser cannot run scikit-learn.)
from sklearn.linear_model import LinearRegression X = [[1], [2], [3], [4]] y = [2, 4, 6, 8] model = LinearRegression().fit(X, y) print(round(model.coef_[0], 2)) print(round(float(model.intercept_), 2)) print(round(model.score(X, y), 2))
The relationship is exactly y = 2x, so the slope is 2 and the intercept is 0.
A perfect fit has no error, so R-squared is 1.0. Each value prints on its own line.
Write mse(predictions, targets) returning the mean of the squared differences between two equal-length lists — the loss you built in Module 3, and the same value sklearn.metrics.mean_squared_error would compute.
def mse(predictions, targets):
# average of (p - t) squared
pass
Sum (p - t) ** 2 across the pairs with zip.
Divide the total by len(predictions).
def mse(predictions, targets):
total = 0.0
for p, t in zip(predictions, targets):
total += (p - t) ** 2
return total / len(predictions)
This accuracy uses integer division (//), so any score below 1.0 collapses to 0. A 3-out-of-4 result should be 0.75, not 0. Fix the one operator so the share of correct predictions is returned as a real fraction.
def accuracy(y_true, y_pred):
correct = sum(t == p for t, p in zip(y_true, y_pred))
return correct // len(y_true)
Integer division throws away the fraction. Use true division instead.
Change the // to a single /.
def accuracy(y_true, y_pred):
correct = sum(t == p for t, p in zip(y_true, y_pred))
return correct / len(y_true)
You call .score(X, y) on a fitted LinearRegression and it returns 0.83. What does that 0.83 mean?
For a regressor, .score() returns R-squared, the fraction of variance in y the model accounts for. 1.0 is perfect and 0.83 is a solid fit. It is not accuracy — that is what .score() returns for a classifier, which is exactly why the same method name can mislead.
Recap
- scikit-learn gives every model the same verbs:
.fit(X, y)to learn,.predict(X_new)to guess,.score(X, y)to grade. - Learned parameters wear a trailing underscore —
.coef_,.intercept_— marking them as fit from data rather than set by hand. - The metrics you built by hand live in
sklearn.metrics, and they compute the same numbers. .score()is polymorphic: R-squared for regressors, accuracy for classifiers, so read it in context.
Next you will wire these estimators into a Pipeline so that scaling and fitting stay sealed inside cross-validation, making the Module 2 leakage bug structurally impossible.