[{"data":1,"prerenderedAt":1120},["ShallowReactive",2],{"lang-switch-post-\u002Fen\u002Fplaylists\u002Fmachine-learning-specialization\u002Fscikit-learn-pitfalls":3,"post-en-machine-learning-specialization-scikit-learn-pitfalls":4},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Fscikit-learn-pitfalls",{"id":5,"title":6,"body":7,"cover":1103,"date":1104,"description":1105,"extension":1106,"meta":1107,"navigation":1108,"order":1109,"path":1110,"playlist":1111,"seo":1112,"status":1113,"stem":1114,"tags":1115,"__hash__":1119},"posts\u002Fen\u002Fplaylists\u002Fmachine-learning-specialization\u002Fscikit-learn-pitfalls.md","Just a Little Extra: Scikit-Learn Pitfalls",{"type":8,"value":9,"toc":1087},"minimark",[10,20,33,57,60,83,92,101,105,112,594,606,613,620,629,638,679,685,689,801,805,808,813,829,916,920,923,932,936,971,1038,1044,1050,1062,1066,1069,1083],[11,12,13,14,19],"p",{},"This post doesn't come from any required section of the lab, it's what was left over after I split off the essentials of scikit-learn and stochastic gradient descent into ",[15,16,18],"a",{"href":17},"\u002Fen\u002Fplaylists\u002Fmachine-learning-specialization\u002Fw2-lab05-scikit-learn","the main post",". These are the traps that only show up once the code leaves the educational notebook and heads into a real project.",[21,22,24,28,29,32],"h2",{"id":23},"fit_transform-and-transform-arent-the-same-thing",[25,26,27],"code",{},"fit_transform"," and ",[25,30,31],{},"transform"," aren't the same thing",[11,34,35,36,38,39,43,44,46,47,50,51,53,54,56],{},"This is where most people slip. ",[25,37,27],{}," ",[40,41,42],"strong",{},"learns and applies",". ",[25,45,31],{}," only ",[40,48,49],{},"applies what was already learned",". On training data, ",[25,52,27],{},". On anything else (validation, test, production), ",[25,55,31],{},".",[11,58,59],{},"I split the 100-house dataset into 75 train \u002F 25 test and compared size's mean computed both ways:",[61,62,67],"pre",{"className":63,"code":64,"language":65,"meta":66,"style":66},"language-python shiki shiki-themes github-light github-dark","mu_correct = size[train].mean()          # correct: train only\nmu_wrong = size[test].mean()             # wrong: if I recomputed on test\n","python","",[25,68,69,77],{"__ignoreMap":66},[70,71,74],"span",{"class":72,"line":73},"line",1,[70,75,76],{},"mu_correct = size[train].mean()          # correct: train only\n",[70,78,80],{"class":72,"line":79},2,[70,81,82],{},"mu_wrong = size[test].mean()             # wrong: if I recomputed on test\n",[84,85,86],"blockquote",{},[11,87,88,91],{},[40,89,90],{},"Output:"," train mean = 1449.93, test mean = 1305.04, a difference of 144.89",[11,93,94,95,97,98,100],{},"If I called ",[25,96,27],{}," on the test set instead of ",[25,99,31],{},", the scaler would start using 1305.04 instead of 1449.93, a difference large enough to distort any prediction made afterward. And then the validation metric ends up looking better than it actually is, without me noticing, because the model \"saw\" statistics from data that should have been unknown.",[21,102,104],{"id":103},"sgd-without-normalizing-simply-explodes","SGD without normalizing simply explodes",[11,106,107,108,111],{},"Unlike ",[25,109,110],{},"LinearRegression",", normalizing here isn't optional. With features on very different scales, the default step size becomes a giant leap in the direction of the largest-scale feature. I ran stochastic gradient descent directly on the raw data (no normalization), on house size in sqft:",[113,114,115,180],"table",{},[116,117,118],"thead",{},[119,120,121,172,176],"tr",{},[122,123,125],"th",{"align":124},"center",[70,126,129,152],{"className":127},[128],"katex",[70,130,133],{"className":131},[132],"katex-mathml",[134,135,137],"math",{"xmlns":136},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[138,139,140,147],"semantics",{},[141,142,143],"mrow",{},[144,145,146],"mi",{},"α",[148,149,151],"annotation",{"encoding":150},"application\u002Fx-tex","\\alpha",[70,153,157],{"className":154,"ariaHidden":156},[155],"katex-html","true",[70,158,161,166],{"className":159},[160],"base",[70,162],{"className":163,"style":165},[164],"strut","height:0.4306em;",[70,167,146],{"className":168,"style":171},[169,170],"mord","mathnormal","margin-right:0.0037em;",[122,173,175],{"align":174},"right","Steps",[122,177,179],{"align":178},"left","Result",[181,182,183,403],"tbody",{},[119,184,185,308,311],{},[186,187,188],"td",{"align":124},[70,189,191,225],{"className":190},[128],[70,192,194],{"className":193},[132],[134,195,196],{"xmlns":136},[138,197,198,222],{},[141,199,200,204,208],{},[201,202,203],"mn",{},"9",[205,206,207],"mo",{},"×",[209,210,211,214],"msup",{},[201,212,213],{},"10",[141,215,216,219],{},[205,217,218],{},"−",[201,220,221],{},"7",[148,223,224],{"encoding":150},"9\\times10^{-7}",[70,226,228,250],{"className":227,"ariaHidden":156},[155],[70,229,231,235,238,243,247],{"className":230},[160],[70,232],{"className":233,"style":234},[164],"height:0.7278em;vertical-align:-0.0833em;",[70,236,203],{"className":237},[169],[70,239],{"className":240,"style":242},[241],"mspace","margin-right:0.2222em;",[70,244,207],{"className":245},[246],"mbin",[70,248],{"className":249,"style":242},[241],[70,251,253,257,261],{"className":252},[160],[70,254],{"className":255,"style":256},[164],"height:0.8141em;",[70,258,260],{"className":259},[169],"1",[70,262,264,268],{"className":263},[169],[70,265,267],{"className":266},[169],"0",[70,269,272],{"className":270},[271],"msupsub",[70,273,276],{"className":274},[275],"vlist-t",[70,277,280],{"className":278},[279],"vlist-r",[70,281,284],{"className":282,"style":256},[283],"vlist",[70,285,287,292],{"style":286},"top:-3.063em;margin-right:0.05em;",[70,288],{"className":289,"style":291},[290],"pstrut","height:2.7em;",[70,293,299],{"className":294},[295,296,297,298],"sizing","reset-size6","size3","mtight",[70,300,302,305],{"className":301},[169,298],[70,303,218],{"className":304},[169,298],[70,306,221],{"className":307},[169,298],[186,309,310],{"align":174},"43",[186,312,313,316,317],{"align":178},[40,314,315],{},"diverges",", cost explodes to ",[70,318,320,344],{"className":319},[128],[70,321,323],{"className":322},[132],[134,324,325],{"xmlns":136},[138,326,327,341],{},[141,328,329,332,334],{},[201,330,331],{},"2.1",[205,333,207],{},[209,335,336,338],{},[201,337,213],{},[201,339,340],{},"6",[148,342,343],{"encoding":150},"2.1\\times10^6",[70,345,347,365],{"className":346,"ariaHidden":156},[155],[70,348,350,353,356,359,362],{"className":349},[160],[70,351],{"className":352,"style":234},[164],[70,354,331],{"className":355},[169],[70,357],{"className":358,"style":242},[241],[70,360,207],{"className":361},[246],[70,363],{"className":364,"style":242},[241],[70,366,368,371,374],{"className":367},[160],[70,369],{"className":370,"style":256},[164],[70,372,260],{"className":373},[169],[70,375,377,380],{"className":376},[169],[70,378,267],{"className":379},[169],[70,381,383],{"className":382},[271],[70,384,386],{"className":385},[275],[70,387,389],{"className":388},[279],[70,390,392],{"className":391,"style":256},[283],[70,393,394,397],{"style":286},[70,395],{"className":396,"style":291},[290],[70,398,400],{"className":399},[295,296,297,298],[70,401,340],{"className":402},[169,298],[119,404,405,501,504],{},[186,406,407],{"align":124},[70,408,410,436],{"className":409},[128],[70,411,413],{"className":412},[132],[134,414,415],{"xmlns":136},[138,416,417,433],{},[141,418,419,421,423],{},[201,420,260],{},[205,422,207],{},[209,424,425,427],{},[201,426,213],{},[141,428,429,431],{},[205,430,218],{},[201,432,221],{},[148,434,435],{"encoding":150},"1\\times10^{-7}",[70,437,439,457],{"className":438,"ariaHidden":156},[155],[70,440,442,445,448,451,454],{"className":441},[160],[70,443],{"className":444,"style":234},[164],[70,446,260],{"className":447},[169],[70,449],{"className":450,"style":242},[241],[70,452,207],{"className":453},[246],[70,455],{"className":456,"style":242},[241],[70,458,460,463,466],{"className":459},[160],[70,461],{"className":462,"style":256},[164],[70,464,260],{"className":465},[169],[70,467,469,472],{"className":468},[169],[70,470,267],{"className":471},[169],[70,473,475],{"className":474},[271],[70,476,478],{"className":477},[275],[70,479,481],{"className":480},[279],[70,482,484],{"className":483,"style":256},[283],[70,485,486,489],{"style":286},[70,487],{"className":488,"style":291},[290],[70,490,492],{"className":491},[295,296,297,298],[70,493,495,498],{"className":494},[169,298],[70,496,218],{"className":497},[169,298],[70,499,221],{"className":500},[169,298],[186,502,503],{"align":174},"200",[186,505,506,507,593],{"align":178},"stable, but still far from the minimum (cost ",[70,508,510,534],{"className":509},[128],[70,511,513],{"className":512},[132],[134,514,515],{"xmlns":136},[138,516,517,531],{},[141,518,519,522,524],{},[201,520,521],{},"1.6",[205,523,207],{},[209,525,526,528],{},[201,527,213],{},[201,529,530],{},"3",[148,532,533],{"encoding":150},"1.6\\times10^3",[70,535,537,555],{"className":536,"ariaHidden":156},[155],[70,538,540,543,546,549,552],{"className":539},[160],[70,541],{"className":542,"style":234},[164],[70,544,521],{"className":545},[169],[70,547],{"className":548,"style":242},[241],[70,550,207],{"className":551},[246],[70,553],{"className":554,"style":242},[241],[70,556,558,561,564],{"className":557},[160],[70,559],{"className":560,"style":256},[164],[70,562,260],{"className":563},[169],[70,565,567,570],{"className":566},[169],[70,568,267],{"className":569},[169],[70,571,573],{"className":572},[271],[70,574,576],{"className":575},[275],[70,577,579],{"className":578},[279],[70,580,582],{"className":581,"style":256},[283],[70,583,584,587],{"style":286},[70,585],{"className":586,"style":291},[290],[70,588,590],{"className":589},[295,296,297,298],[70,591,530],{"className":592},[169,298],")",[11,595,596,597,601,602,605],{},"The same scale phenomenon I already saw with batch gradient descent ",[15,598,600],{"href":599},"\u002Fen\u002Fplaylists\u002Fmachine-learning-specialization\u002Flab04-gradient-descent","in earlier posts",", just now with SGD. The difference is that here normalizing isn't just \"recommended,\" it's practically mandatory, and the reason is that SGD already starts from a shakier place before scale even enters the picture: every step uses the gradient of a ",[40,603,604],{},"single example",", not the smoothed-out average over the whole dataset like batch does, so the step is already jumping around noisily from example to example. Multiply that noisy gradient by a feature on a giant scale (house size in square feet, in the thousands) and the step size starts swinging wildly from one iteration to the next, with none of the batch's averaging left to absorb the jolt. It's the same wrong-direction problem as always, just without the shock absorber batch got for free.",[21,607,609,612],{"id":608},"pipeline-and-cross-validation-the-professional-way",[25,610,611],{},"Pipeline"," and cross-validation: the professional way",[11,614,615,616,619],{},"Every metric I showed in the main post was measured on ",[40,617,618],{},"training"," data, it tells me how much the model memorized, not how well it generalizes. And there's a second problem: if I normalize once and then run cross-validation, the scaler has already seen the data from every fold. Leakage again, just more subtle.",[11,621,622,624,625,628],{},[25,623,611],{}," fixes both: it chains normalization and the model into a single estimator, and on every fit (including inside each cross-validation fold) the scaler gets refit using ",[40,626,627],{},"only"," that fold's training portion.",[11,630,631,632,28,634,637],{},"I ran a real 5-fold cross-validation, comparing ",[25,633,110],{},[25,635,636],{},"Ridge(alpha=1)",":",[113,639,640,653],{},[116,641,642],{},[119,643,644,647,650],{},[122,645,646],{"align":178},"Model",[122,648,649],{"align":178},"R² per fold",[122,651,652],{"align":174},"Mean ± std",[181,654,655,667],{},[119,656,657,661,664],{},[186,658,659],{"align":178},[25,660,110],{},[186,662,663],{"align":178},"0.955, 0.952, 0.915, 0.944, 0.96",[186,665,666],{"align":174},"0.9450 ± 0.0160",[119,668,669,673,676],{},[186,670,671],{"align":178},[25,672,636],{},[186,674,675],{"align":178},"0.953, 0.954, 0.919, 0.942, 0.955",[186,677,678],{"align":174},"0.9444 ± 0.0137",[11,680,681,682,684],{},"Notice the validation average (0.945) is lower than the training R² I cited in ",[15,683,18],{"href":17}," (0.9594), exactly as expected: validation is always more honest than training. And the two models end up nearly tied, with 100 examples and 4 features there isn't excess capacity Ridge needs to hold back.",[21,686,688],{"id":687},"the-other-pitfalls-in-a-list","The other pitfalls, in a list",[690,691,692,708,723,742,751,769,781],"ul",{},[693,694,695,704,705,707],"li",{},[40,696,697,700,701],{},[25,698,699],{},"intercept_"," is an array in ",[25,702,703],{},"SGDRegressor"," and a scalar in ",[25,706,110],{},". Code assuming one of the two shapes breaks on the other.",[693,709,710,716,717,719,720,56],{},[40,711,712,715],{},[25,713,714],{},"max_iter"," is a ceiling, not a target"," (already covered in ",[15,718,18],{"href":17},"). Always check ",[25,721,722],{},"n_iter_",[693,724,725,731,732,734,735,738,739,56],{},[40,726,727,730],{},[25,728,729],{},"penalty='l2'"," is on by default"," in ",[25,733,703],{}," (",[15,736,737],{"href":17},"main post","). For pure least squares, pass ",[25,740,741],{},"penalty=None",[693,743,744,750],{},[40,745,746,749],{},[25,747,748],{},"random_state"," isn't optional"," if you want a reproducible result.",[693,752,753,758,759,761,762,765,766,768],{},[40,754,755],{},[25,756,757],{},"ConvergenceWarning"," means the model hit ",[25,760,714],{}," without satisfying ",[25,763,764],{},"tol",". Don't ignore it: raise ",[25,767,714],{},", adjust the learning rate, or check whether you normalized.",[693,770,771,777,778,780],{},[40,772,773,774],{},"Normalizing the target ",[25,775,776],{},"y"," isn't done automatically by any of this. If ",[25,779,776],{}," has an extreme magnitude, consider normalizing it, and remember to denormalize predictions before reporting any metric.",[693,782,783,789,790,793,794,796,797,800],{},[40,784,785,788],{},[25,786,787],{},"fit"," resets the model from scratch."," For incremental training, with data arriving gradually (instead of all at once), there's ",[25,791,792],{},"partial_fit",": the model updates itself without ever needing to see the whole dataset at once. That's the real use case behind \"data that doesn't fit in memory\" I mentioned in ",[15,795,18],{"href":17},", and it's literally what ",[15,798,799],{"href":17},"the main post's"," stochastic simulator already shows: every step only sees one house, never the whole dataset at once, and the model still walks toward the same place batch does.",[21,802,804],{"id":803},"exercises","Exercises",[11,806,807],{},"Try them before opening the answer.",[809,810,812],"h3",{"id":811},"exercise-1-reproduce-the-scaler","Exercise 1: reproduce the scaler",[11,814,815,816,818,819,821,822,824,825,56],{},"Implement your own z-score normalizer with ",[25,817,787],{},", ",[25,820,31],{},", and ",[25,823,27],{}," methods, following the trailing-underscore attribute convention. Check it matches the result from ",[15,826,828],{"href":827},"\u002Fen\u002Fplaylists\u002Fmachine-learning-specialization\u002Fw2-lab03-feature-scaling","the normalization post",[830,831,832,839,902],"details",{},[833,834,835],"summary",{},[836,837,838],"b",{},"Answer",[61,840,842],{"className":63,"code":841,"language":65,"meta":66,"style":66},"class MyScaler:\n    def fit(self, X):\n        self.mean_ = X.mean(axis=0)\n        self.scale_ = X.std(axis=0)\n        self.scale_[self.scale_ == 0] = 1.0\n        return self\n    def transform(self, X):\n        return (X - self.mean_) \u002F self.scale_\n    def fit_transform(self, X):\n        return self.fit(X).transform(X)\n",[25,843,844,849,854,860,866,872,878,884,890,896],{"__ignoreMap":66},[70,845,846],{"class":72,"line":73},[70,847,848],{},"class MyScaler:\n",[70,850,851],{"class":72,"line":79},[70,852,853],{},"    def fit(self, X):\n",[70,855,857],{"class":72,"line":856},3,[70,858,859],{},"        self.mean_ = X.mean(axis=0)\n",[70,861,863],{"class":72,"line":862},4,[70,864,865],{},"        self.scale_ = X.std(axis=0)\n",[70,867,869],{"class":72,"line":868},5,[70,870,871],{},"        self.scale_[self.scale_ == 0] = 1.0\n",[70,873,875],{"class":72,"line":874},6,[70,876,877],{},"        return self\n",[70,879,881],{"class":72,"line":880},7,[70,882,883],{},"    def transform(self, X):\n",[70,885,887],{"class":72,"line":886},8,[70,888,889],{},"        return (X - self.mean_) \u002F self.scale_\n",[70,891,893],{"class":72,"line":892},9,[70,894,895],{},"    def fit_transform(self, X):\n",[70,897,899],{"class":72,"line":898},10,[70,900,901],{},"        return self.fit(X).transform(X)\n",[11,903,904,905,908,909,911,912,915],{},"Returning ",[25,906,907],{},"self"," from ",[25,910,787],{}," is what lets you chain ",[25,913,914],{},"MyScaler().fit(X).transform(X)",", the same pattern every scikit-learn transformer follows.",[809,917,919],{"id":918},"exercise-2-the-effect-of-sgds-learning-rate","Exercise 2: the effect of SGD's learning rate",[11,921,922],{},"Run stochastic gradient descent with a constant rate at several values. What happens at both extremes?",[830,924,925,929],{},[833,926,927],{},[836,928,838],{},[11,930,931],{},"With too small a rate, the model never gets close to the minimum within the available steps. With too large a rate, it diverges, exactly what I showed in this post with the raw data. A rate that decays over training (scikit-learn's default) exists precisely so you don't have to get this right by hand: it starts bigger, takes big steps early while still far away, and shrinks as it gets close.",[809,933,935],{"id":934},"exercise-3-how-much-is-the-hidden-ridge-costing","Exercise 3: how much is the hidden Ridge costing?",[11,937,938,939,941,942,970],{},"Compare ",[25,940,741],{}," against several ",[70,943,945,958],{"className":944},[128],[70,946,948],{"className":947},[132],[134,949,950],{"xmlns":136},[138,951,952,956],{},[141,953,954],{},[144,955,146],{},[148,957,151],{"encoding":150},[70,959,961],{"className":960,"ariaHidden":156},[155],[70,962,964,967],{"className":963},[160],[70,965],{"className":966,"style":165},[164],[70,968,146],{"className":969,"style":171},[169,170]," values using cross-validation. At what point does regularization start hurting? What if the dataset only had 20 examples instead of 100?",[830,972,973,977],{},[833,974,975],{},[836,976,838],{},[11,978,979,980,1008,1009,1037],{},"With 100 examples and 4 features there's no excess capacity, so any large ",[70,981,983,996],{"className":982},[128],[70,984,986],{"className":985},[132],[134,987,988],{"xmlns":136},[138,989,990,994],{},[141,991,992],{},[144,993,146],{},[148,995,151],{"encoding":150},[70,997,999],{"className":998,"ariaHidden":156},[155],[70,1000,1002,1005],{"className":1001},[160],[70,1003],{"className":1004,"style":165},[164],[70,1006,146],{"className":1007,"style":171},[169,170]," just hurts. With 20 examples the story changes: the examples-to-parameters ratio gets tight, and a moderate ",[70,1010,1012,1025],{"className":1011},[128],[70,1013,1015],{"className":1014},[132],[134,1016,1017],{"xmlns":136},[138,1018,1019,1023],{},[141,1020,1021],{},[144,1022,146],{},[148,1024,151],{"encoding":150},[70,1026,1028],{"className":1027,"ariaHidden":156},[155],[70,1029,1031,1034],{"className":1030},[160],[70,1032],{"className":1033,"style":165},[164],[70,1035,146],{"className":1036,"style":171},[169,170]," genuinely helps. The usual lesson: regularization isn't good or bad on its own, it's a response to excess capacity that may or may not exist in your data.",[809,1039,1041,1042],{"id":1040},"exercise-4-cross-validation-with-and-without-pipeline","Exercise 4: cross-validation with and without ",[25,1043,611],{},[11,1045,1046,1047,1049],{},"Compare cross-validation R² in two scenarios: normalizing once beforehand and feeding the result into cross-validation, versus using a ",[25,1048,611],{},". Do the numbers match?",[830,1051,1052,1056],{},[833,1053,1054],{},[836,1055,838],{},[11,1057,1058,1059,1061],{},"In this dataset the difference is small, because the folds look similar to each other. But the first scenario is conceptually wrong: the scaler saw the validation data. In a small dataset, with outliers, or with temporal structure, that difference stops being small. Use ",[25,1060,611],{}," always, not because the number changes much today, but because it makes the mistake impossible to make by accident.",[809,1063,1065],{"id":1064},"exercise-5-does-the-model-generalize-to-houses-outside-the-range","Exercise 5: does the model generalize to houses outside the range?",[11,1067,1068],{},"Set aside the 20 most expensive houses as test and train on the remaining 80. What happens to the test R²?",[830,1070,1071,1075],{},[833,1072,1073],{},[836,1074,838],{},[11,1076,1077,1078,1082],{},"The R² tanks, and can even go negative. The reason isn't overfitting, it's that the split isn't random: the test set ended up systematically different from training (extrapolation, the same problem I saw with polynomials in ",[15,1079,1081],{"href":1080},"\u002Fen\u002Fplaylists\u002Fmachine-learning-specialization\u002Fw2-lab04-feature-engineering","the feature engineering post","). Random splitting assumes the data is interchangeable, when it isn't (time, geography, price range), the split needs to respect that.",[1084,1085,1086],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":66,"searchDepth":79,"depth":79,"links":1088},[1089,1091,1092,1094,1095],{"id":23,"depth":79,"text":1090},"fit_transform and transform aren't the same thing",{"id":103,"depth":79,"text":104},{"id":608,"depth":79,"text":1093},"Pipeline and cross-validation: the professional way",{"id":687,"depth":79,"text":688},{"id":803,"depth":79,"text":804,"children":1096},[1097,1098,1099,1100,1102],{"id":811,"depth":856,"text":812},{"id":918,"depth":856,"text":919},{"id":934,"depth":856,"text":935},{"id":1040,"depth":856,"text":1101},"Exercise 4: cross-validation with and without Pipeline",{"id":1064,"depth":856,"text":1065},null,"2026-08-19","Data leakage between fit_transform and transform, SGD exploding without normalizing, a real Pipeline with cross-validation, and the API gotchas that trip up beginners.","md",{},true,11,"\u002Fen\u002Fplaylists\u002Fmachine-learning-specialization\u002Fscikit-learn-pitfalls","machine-learning-specialization",{"title":6,"description":1105},"draft","en\u002Fplaylists\u002Fmachine-learning-specialization\u002Fscikit-learn-pitfalls",[1116,1117,1118],"scikit-learn","pipeline","cross-validation","PLaYoOXl5tkPwzSKfN3f36-e71TR-Eo6aURNVSUbQrA",1787338985250]