[{"data":1,"prerenderedAt":16799},["ShallowReactive",2],{"lang-switch-post-\u002Fplaylists\u002Fpattern-recognition":3,"playlist-pt-pattern-recognition":4,"playlist-posts-pt-pattern-recognition":69},null,{"id":5,"title":6,"body":7,"cover":3,"description":60,"extension":61,"meta":62,"navigation":63,"order":58,"path":64,"seo":65,"status":66,"stem":67,"__hash__":68},"playlists\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Findex.md","Reconhecimento de Padrões",{"type":8,"value":9,"toc":56},"minimark",[10,25,38,48],[11,12,13,14,18,19,24],"p",{},"Essa playlist é o caderno de anotações da matéria de Reconhecimento de Padrões que eu cursei, dada pelo ",[15,16,17],"strong",{},"Dr. Francisco Boldt",". Ele é fera, literalmente coda os modelos na mão, ao vivo, na aula, sem slide de fórmula pronta, e agora tá me lecionando ",[20,21,23],"a",{"href":22},"\u002Fplaylists\u002Fneural-networks","redes neurais também",". Se você caiu aqui vindo de uma aula dele, já sabe do que eu tô falando.",[11,26,27,28,32,33,37],{},"Diferente da ",[20,29,31],{"href":30},"\u002Fplaylists\u002Fmachine-learning-specialization","playlist da especialização do Andrew Ng",", aqui os notebooks das aulas são bem mais enxutos: pouquíssima célula de markdown, é o professor codando ao vivo e a gente acompanhando. Então o trabalho de esmiuçar o \"por quê\" por trás de cada linha de código é maior, e pra isso eu uso como referência teórica o livro ",[34,35,36],"em",{},"Pattern Recognition and Machine Learning",", do Christopher Bishop (2006), praticamente uma bíblia da área.",[11,39,40,41,47],{},"Os notebooks vêm do repositório da matéria, ",[20,42,46],{"href":43,"rel":44},"https:\u002F\u002Fgithub.com\u002Fpablobelmiro\u002Faulasml\u002Ftree\u002F2026-1",[45],"nofollow","pablobelmiro\u002Faulasml",", um fork do repositório original do próprio Dr. Boldt, onde ele publica o código de cada aula. Nos posts dessa playlist, quem \"faz\" o código é sempre ele, o professor; a explicação de fundamento, com a metáfora, a analogia torta e o tom de colega do lado, essa é a minha.",[11,49,50,51,55],{},"Cada aula da matéria vira um post aqui. Começamos pelo começo: o mesmo problema de ",[20,52,54],{"href":53},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Flab02-model-representation","regressão linear que já vimos na outra playlist",", só que agora com o código sendo escrito na cara do quadro.",{"title":57,"searchDepth":58,"depth":58,"links":59},"",2,[],"Minhas anotações da disciplina de Reconhecimento de Padrões, aula por aula, com o livro do Bishop como base teórica.","md",{},true,"\u002Fpt\u002Fplaylists\u002Fpattern-recognition",{"title":6,"description":60},"published","pt\u002Fplaylists\u002Fpattern-recognition\u002Findex","XpBRgaSNS0YRRtovdqN7u3MdAmbF-PKEJQoOiyOjl-M",[70,2011,4133,5158,5810,6971,8290,9200,9955,11460,12085,13418,14541,15206,16057],{"id":71,"title":72,"body":73,"cover":3,"date":1999,"description":2000,"extension":61,"meta":2001,"navigation":63,"order":107,"path":2002,"playlist":2003,"seo":2004,"status":66,"stem":2005,"tags":2006,"__hash__":2010},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Flinear-regression-estimator.md","Regressão Linear na Unha: Construindo um Estimador do Zero",{"type":8,"value":74,"toc":1988},[75,78,83,95,128,139,143,272,281,312,327,344,348,356,386,432,441,450,910,913,1151,1160,1164,1179,1199,1207,1215,1240,1427,1434,1438,1449,1493,1538,1545,1588,1594,1626,1634,1641,1645,1648,1741,1766,1769,1778,1801,1804,1818,1830,1834,1841,1856,1867,1870,1874,1924,1940,1944,1947,1967,1974,1981,1984],[11,76,77],{},"Aula 1 da matéria do Dr. Boldt. O notebook dele quase não tem célula de markdown, é ele codando direto, então boa parte do trabalho deste post é meu: recontar o \"por quê\" de cada bloco de código, com o Bishop do lado como referência. Bora.",[79,80,82],"h2",{"id":81},"o-dataset-442-pacientes-de-verdade","O dataset: 442 pacientes de verdade",[11,84,85,86,90,91,94],{},"Diferente da playlist da especialização do Andrew Ng, aqui o dataset já vem embutido no próprio scikit-learn: o ",[87,88,89],"code",{},"load_diabetes",", com ",[15,92,93],{},"442 pacientes"," reais, 10 variáveis de entrada (idade, sexo, índice de massa corporal, pressão arterial média e seis medidas de sangue) e um alvo, uma medida numérica de quanto a diabetes progrediu depois de um ano.",[96,97,101],"pre",{"className":98,"code":99,"language":100,"meta":57,"style":57},"language-python shiki shiki-themes github-light github-dark","from sklearn.datasets import load_diabetes\ndata = load_diabetes()\nX = data.data   # (442, 10)\ny = data.target # (442,)\n","python",[87,102,103,111,116,122],{"__ignoreMap":57},[104,105,108],"span",{"class":106,"line":107},"line",1,[104,109,110],{},"from sklearn.datasets import load_diabetes\n",[104,112,113],{"class":106,"line":58},[104,114,115],{},"data = load_diabetes()\n",[104,117,119],{"class":106,"line":118},3,[104,120,121],{},"X = data.data   # (442, 10)\n",[104,123,125],{"class":106,"line":124},4,[104,126,127],{},"y = data.target # (442,)\n",[11,129,130,131,134,135,138],{},"Um detalhe que pega muita gente desprevenida: as 10 colunas de ",[87,132,133],{},"X"," ",[15,136,137],{},"não"," estão nas unidades originais. O scikit-learn já entrega esse dataset com cada coluna centralizada em zero e reescalada. Por isso o índice de massa corporal (a coluna que eu vou usar o post inteiro) varia entre -0.09 e 0.17 em vez de ficar na faixa normal de 15 a 40. Guarda essa informação, ela explica por que os coeficientes que vão aparecer mais pra frente parecem números gigantes: eles estão compensando uma variável de entrada minúscula.",[79,140,142],{"id":141},"o-modelo-mais-simples-possível","O modelo mais simples possível",[11,144,145],{},[104,146,149,190],{"className":147},[148],"katex",[104,150,153],{"className":151},[152],"katex-mathml",[154,155,157],"math",{"xmlns":156},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[158,159,160,185],"semantics",{},[161,162,163,167,171,173,176,179,182],"mrow",{},[164,165,166],"mi",{},"y",[168,169,170],"mo",{},"=",[164,172,20],{},[168,174,175],{},"⋅",[164,177,178],{},"x",[168,180,181],{},"+",[164,183,184],{},"b",[186,187,189],"annotation",{"encoding":188},"application\u002Fx-tex","y = a \\cdot x + b",[104,191,195,222,243,262],{"className":192,"ariaHidden":194},[193],"katex-html","true",[104,196,199,204,210,215,219],{"className":197},[198],"base",[104,200],{"className":201,"style":203},[202],"strut","height:0.625em;vertical-align:-0.1944em;",[104,205,166],{"className":206,"style":209},[207,208],"mord","mathnormal","margin-right:0.0359em;",[104,211],{"className":212,"style":214},[213],"mspace","margin-right:0.2778em;",[104,216,170],{"className":217},[218],"mrel",[104,220],{"className":221,"style":214},[213],[104,223,225,229,232,236,240],{"className":224},[198],[104,226],{"className":227,"style":228},[202],"height:0.4445em;",[104,230,20],{"className":231},[207,208],[104,233],{"className":234,"style":235},[213],"margin-right:0.2222em;",[104,237,175],{"className":238},[239],"mbin",[104,241],{"className":242,"style":235},[213],[104,244,246,250,253,256,259],{"className":245},[198],[104,247],{"className":248,"style":249},[202],"height:0.6667em;vertical-align:-0.0833em;",[104,251,178],{"className":252},[207,208],[104,254],{"className":255,"style":235},[213],[104,257,181],{"className":258},[239],[104,260],{"className":261,"style":235},[213],[104,263,265,269],{"className":264},[198],[104,266],{"className":267,"style":268},[202],"height:0.6944em;",[104,270,184],{"className":271},[207,208],[11,273,274,275,277,278,280],{},"A mesma cara de sempre: uma reta, onde ",[87,276,20],{}," é a inclinação (o quanto o alvo muda pra cada unidade de índice de massa corporal) e ",[87,279,184],{}," é o intercepto. O professor testa dois chutes na mão, sem otimizar nada ainda, só pra ver a cara da reta:",[96,282,284],{"className":98,"code":283,"language":100,"meta":57,"style":57},"def modelo_linear(X, a, b):\n    return a*X + b\n\nypred = modelo_linear(X2, 1000, 100)  # primeiro chute\nypred = modelo_linear(X2, 1500, 150)  # segundo chute\n",[87,285,286,291,296,301,306],{"__ignoreMap":57},[104,287,288],{"class":106,"line":107},[104,289,290],{},"def modelo_linear(X, a, b):\n",[104,292,293],{"class":106,"line":58},[104,294,295],{},"    return a*X + b\n",[104,297,298],{"class":106,"line":118},[104,299,300],{"emptyLinePlaceholder":63},"\n",[104,302,303],{"class":106,"line":124},[104,304,305],{},"ypred = modelo_linear(X2, 1000, 100)  # primeiro chute\n",[104,307,309],{"class":106,"line":308},5,[104,310,311],{},"ypred = modelo_linear(X2, 1500, 150)  # segundo chute\n",[11,313,314,315,318,319,321,322,318,324,326],{},"Mexe nos sliders abaixo (aqui no blog eu uso a notação padrão ",[87,316,317],{},"w","\u002F",[87,320,184],{},", é exatamente o mesmo ",[87,323,20],{},[87,325,184],{}," do professor) numa amostra real de 45 pacientes do conjunto de treino e vê você mesmo qual reta cai melhor em cima dos pontos:",[328,329],"model-playground",{":b-max":330,":b-min":331,":b-step":332,":initial-b":333,":initial-w":334,":w-max":335,":w-min":331,":w-step":336,":x-train":337,":y-train":338,"dataLabel":339,"error-label":340,"prediction-label":341,"x-label":342,"y-label":343},"300","0","5","100","1000","1600","20","[-0.0062, -0.0903, 0.0714, 0.0445, 0.0143, -0.0698, -0.0849, -0.0461, -0.0655, -0.0213, -0.0041, 0.0358, 0.0692, 0.0207, -0.0504, 0.024, -0.0094, 0.1102, 0.0477, 0.0143, -0.0256, -0.0385, 0.08, -0.0041, 0.0412, -0.0418, 0.093, 0.01, 0.0606, 0.0391, 0.0595, 0.0164, -0.0105, 0.0067, 0.0013, -0.0299, -0.0235, -0.072, -0.0084, 0.1609, -0.0224, 0.0595, -0.0073, -0.0105, 0.0067]","[219, 94, 295, 129, 90, 48, 90, 72, 214, 281, 68, 184, 277, 197, 189, 121, 257, 258, 317, 191, 252, 127, 257, 198, 198, 103, 128, 150, 245, 246, 178, 268, 168, 109, 229, 118, 71, 77, 81, 346, 84, 85, 52, 25, 67]","Paciente","Erro total (soma dos erros absolutos)","Modelo","IMC (normalizado)","progressão da diabetes",[79,345,347],{"id":346},"como-saber-se-um-chute-é-bom","Como saber se um chute é bom",[11,349,350,351,355],{},"\"Cai melhor\" precisa virar número. O professor implementa três métricas na unha, a mesma trinca que já apareceu ",[20,352,354],{"href":353},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Fw2-lab05-scikit-learn","na playlist da especialização"," (se você já leu aquela, pode passar os olhos rápido):",[357,358,359,370,380],"ul",{},[360,361,362,365,366,369],"li",{},[15,363,364],{},"MAE"," (erro absoluto médio): a média de ",[87,367,368],{},"|previsto - real|",". Fácil de interpretar (tá em unidade de paciente), mas trata todo erro do mesmo jeito.",[360,371,372,375,376,379],{},[15,373,374],{},"MSE"," (erro quadrático médio): a média de ",[87,377,378],{},"(previsto - real)²",". Eleva ao quadrado, então um erro grande pesa desproporcionalmente mais que vários erros pequenos.",[360,381,382,385],{},[15,383,384],{},"RMSE",": a raiz do MSE, pra voltar pra unidade original depois de ter quadrado tudo.",[96,387,389],{"className":98,"code":388,"language":100,"meta":57,"style":57},"def mae(y, ypred):\n    return np.sum(np.abs(y - ypred)) \u002F len(y)\n\ndef mse(y, ypred):\n    return np.sum((y - ypred)**2) \u002F len(y)\n\ndef rmse(y, ypred):\n    return np.sqrt(mse(y, ypred))\n",[87,390,391,396,401,405,410,415,420,426],{"__ignoreMap":57},[104,392,393],{"class":106,"line":107},[104,394,395],{},"def mae(y, ypred):\n",[104,397,398],{"class":106,"line":58},[104,399,400],{},"    return np.sum(np.abs(y - ypred)) \u002F len(y)\n",[104,402,403],{"class":106,"line":118},[104,404,300],{"emptyLinePlaceholder":63},[104,406,407],{"class":106,"line":124},[104,408,409],{},"def mse(y, ypred):\n",[104,411,412],{"class":106,"line":308},[104,413,414],{},"    return np.sum((y - ypred)**2) \u002F len(y)\n",[104,416,418],{"class":106,"line":417},6,[104,419,300],{"emptyLinePlaceholder":63},[104,421,423],{"class":106,"line":422},7,[104,424,425],{},"def rmse(y, ypred):\n",[104,427,429],{"class":106,"line":428},8,[104,430,431],{},"    return np.sqrt(mse(y, ypred))\n",[433,434,435],"blockquote",{},[11,436,437,440],{},[15,438,439],{},"Saída no dataset inteiro (442 pacientes):"," chute (1000, 100) → MAE 64.93, MSE 6614.14, RMSE 81.33. Chute (1500, 150) → MAE 53.93, MSE 4580.80, RMSE 67.68. O segundo chute é melhor nas três métricas.",[11,442,443,444,449],{},"O Bishop chama isso de ",[445,446,448],"glossary-term",{"definition":447},"a conta que resume, num número só, o quão longe o modelo está dos dados de treino","função de erro",", e define ela quase igual ao MSE, só que sem dividir pela quantidade de pontos e com um fator 1\u002F2 de bônus:",[11,451,452],{},[104,453,455,549],{"className":454},[148],[104,456,458],{"className":457},[152],[154,459,460],{"xmlns":156},[158,461,462,546],{},[161,463,464,467,471,474,477,479,489,507,510,512,514,521,524,526,528,531,538],{},[164,465,466],{},"E",[168,468,470],{"stretchy":469},"false","(",[164,472,317],{"mathvariant":473},"bold",[168,475,476],{"stretchy":469},")",[168,478,170],{},[480,481,482,486],"mfrac",{},[483,484,485],"mn",{},"1",[483,487,488],{},"2",[490,491,492,495,504],"msubsup",{},[168,493,494],{},"∑",[161,496,497,500,502],{},[164,498,499],{},"n",[168,501,170],{},[483,503,485],{},[164,505,506],{},"N",[168,508,509],{"stretchy":469},"{",[164,511,166],{},[168,513,470],{"stretchy":469},[515,516,517,519],"msub",{},[164,518,178],{},[164,520,499],{},[168,522,523],{"separator":194},",",[164,525,317],{"mathvariant":473},[168,527,476],{"stretchy":469},[168,529,530],{},"−",[515,532,533,536],{},[164,534,535],{},"t",[164,537,499],{},[539,540,541,544],"msup",{},[168,542,543],{"stretchy":469},"}",[483,545,488],{},[186,547,548],{"encoding":188},"E(\\mathbf{w}) = \\frac{1}{2}\\sum_{n=1}^{N}\\{y(x_n,\\mathbf{w}) - t_n\\}^2",[104,550,552,585,832],{"className":551,"ariaHidden":194},[193],[104,553,555,559,563,567,572,576,579,582],{"className":554},[198],[104,556],{"className":557,"style":558},[202],"height:1em;vertical-align:-0.25em;",[104,560,466],{"className":561,"style":562},[207,208],"margin-right:0.0576em;",[104,564,470],{"className":565},[566],"mopen",[104,568,317],{"className":569,"style":571},[207,570],"mathbf","margin-right:0.016em;",[104,573,476],{"className":574},[575],"mclose",[104,577],{"className":578,"style":214},[213],[104,580,170],{"className":581},[218],[104,583],{"className":584,"style":214},[213],[104,586,588,592,680,684,758,761,764,767,810,814,817,820,823,826,829],{"className":587},[198],[104,589],{"className":590,"style":591},[202],"height:1.3262em;vertical-align:-0.345em;",[104,593,595,599,677],{"className":594},[207],[104,596],{"className":597},[566,598],"nulldelimiter",[104,600,602],{"className":601},[480],[104,603,607,668],{"className":604},[605,606],"vlist-t","vlist-t2",[104,608,611,663],{"className":609},[610],"vlist-r",[104,612,616,637,648],{"className":613,"style":615},[614],"vlist","height:0.8451em;",[104,617,619,624],{"style":618},"top:-2.655em;",[104,620],{"className":621,"style":623},[622],"pstrut","height:3em;",[104,625,631],{"className":626},[627,628,629,630],"sizing","reset-size6","size3","mtight",[104,632,634],{"className":633},[207,630],[104,635,488],{"className":636},[207,630],[104,638,640,643],{"style":639},"top:-3.23em;",[104,641],{"className":642,"style":623},[622],[104,644],{"className":645,"style":647},[646],"frac-line","border-bottom-width:0.04em;",[104,649,651,654],{"style":650},"top:-3.394em;",[104,652],{"className":653,"style":623},[622],[104,655,657],{"className":656},[627,628,629,630],[104,658,660],{"className":659},[207,630],[104,661,485],{"className":662},[207,630],[104,664,667],{"className":665},[666],"vlist-s","​",[104,669,671],{"className":670},[610],[104,672,675],{"className":673,"style":674},[614],"height:0.345em;",[104,676],{},[104,678],{"className":679},[575,598],[104,681],{"className":682,"style":683},[213],"margin-right:0.1667em;",[104,685,688,694],{"className":686},[687],"mop",[104,689,494],{"className":690,"style":693},[687,691,692],"op-symbol","small-op","position:relative;top:0em;",[104,695,698],{"className":696},[697],"msupsub",[104,699,701,749],{"className":700},[605,606],[104,702,704,746],{"className":703},[610],[104,705,708,730],{"className":706,"style":707},[614],"height:0.9812em;",[104,709,711,715],{"style":710},"top:-2.4003em;margin-left:0em;margin-right:0.05em;",[104,712],{"className":713,"style":714},[622],"height:2.7em;",[104,716,718],{"className":717},[627,628,629,630],[104,719,721,724,727],{"className":720},[207,630],[104,722,499],{"className":723},[207,208,630],[104,725,170],{"className":726},[218,630],[104,728,485],{"className":729},[207,630],[104,731,733,736],{"style":732},"top:-3.2029em;margin-right:0.05em;",[104,734],{"className":735,"style":714},[622],[104,737,739],{"className":738},[627,628,629,630],[104,740,742],{"className":741},[207,630],[104,743,506],{"className":744,"style":745},[207,208,630],"margin-right:0.109em;",[104,747,667],{"className":748},[666],[104,750,752],{"className":751},[610],[104,753,756],{"className":754,"style":755},[614],"height:0.2997em;",[104,757],{},[104,759,509],{"className":760},[566],[104,762,166],{"className":763,"style":209},[207,208],[104,765,470],{"className":766},[566],[104,768,770,773],{"className":769},[207],[104,771,178],{"className":772},[207,208],[104,774,776],{"className":775},[697],[104,777,779,801],{"className":778},[605,606],[104,780,782,798],{"className":781},[610],[104,783,786],{"className":784,"style":785},[614],"height:0.1514em;",[104,787,789,792],{"style":788},"top:-2.55em;margin-left:0em;margin-right:0.05em;",[104,790],{"className":791,"style":714},[622],[104,793,795],{"className":794},[627,628,629,630],[104,796,499],{"className":797},[207,208,630],[104,799,667],{"className":800},[666],[104,802,804],{"className":803},[610],[104,805,808],{"className":806,"style":807},[614],"height:0.15em;",[104,809],{},[104,811,523],{"className":812},[813],"mpunct",[104,815],{"className":816,"style":683},[213],[104,818,317],{"className":819,"style":571},[207,570],[104,821,476],{"className":822},[575],[104,824],{"className":825,"style":235},[213],[104,827,530],{"className":828},[239],[104,830],{"className":831,"style":235},[213],[104,833,835,839,879],{"className":834},[198],[104,836],{"className":837,"style":838},[202],"height:1.0641em;vertical-align:-0.25em;",[104,840,842,845],{"className":841},[207],[104,843,535],{"className":844},[207,208],[104,846,848],{"className":847},[697],[104,849,851,871],{"className":850},[605,606],[104,852,854,868],{"className":853},[610],[104,855,857],{"className":856,"style":785},[614],[104,858,859,862],{"style":788},[104,860],{"className":861,"style":714},[622],[104,863,865],{"className":864},[627,628,629,630],[104,866,499],{"className":867},[207,208,630],[104,869,667],{"className":870},[666],[104,872,874],{"className":873},[610],[104,875,877],{"className":876,"style":807},[614],[104,878],{},[104,880,882,885],{"className":881},[575],[104,883,543],{"className":884},[575],[104,886,888],{"className":887},[697],[104,889,891],{"className":890},[605],[104,892,894],{"className":893},[610],[104,895,898],{"className":896,"style":897},[614],"height:0.8141em;",[104,899,901,904],{"style":900},"top:-3.063em;margin-right:0.05em;",[104,902],{"className":903,"style":714},[622],[104,905,907],{"className":906},[627,628,629,630],[104,908,488],{"className":909},[207,630],[11,911,912],{},"O 1\u002F2 existe só por conveniência: quando você deriva essa função pra achar o mínimo, o quadrado desce multiplicando e cancela o 1\u002F2 exatamente. Detalhe estético, não muda pra onde o mínimo aponta. E a versão \"raiz\" que ele usa pra comparar datasets de tamanhos diferentes,",[11,914,915],{},[104,916,918,965],{"className":917},[148],[104,919,921],{"className":920},[152],[154,922,923],{"xmlns":156},[158,924,925,962],{},[161,926,927,935,937],{},[515,928,929,931],{},[164,930,466],{},[932,933,934],"mtext",{},"RMS",[168,936,170],{},[938,939,940],"msqrt",{},[161,941,942,944,946,948,955,957,960],{},[483,943,488],{},[164,945,466],{},[168,947,470],{"stretchy":469},[539,949,950,952],{},[164,951,317],{"mathvariant":473},[168,953,954],{},"∗",[168,956,476],{"stretchy":469},[164,958,318],{"mathvariant":959},"normal",[164,961,506],{},[186,963,964],{"encoding":188},"E_{\\text{RMS}} = \\sqrt{2E(\\mathbf{w}^*)\u002FN}",[104,966,968,1033],{"className":967,"ariaHidden":194},[193],[104,969,971,975,1024,1027,1030],{"className":970},[198],[104,972],{"className":973,"style":974},[202],"height:0.8333em;vertical-align:-0.15em;",[104,976,978,981],{"className":977},[207],[104,979,466],{"className":980,"style":562},[207,208],[104,982,984],{"className":983},[697],[104,985,987,1016],{"className":986},[605,606],[104,988,990,1013],{"className":989},[610],[104,991,994],{"className":992,"style":993},[614],"height:0.3283em;",[104,995,997,1000],{"style":996},"top:-2.55em;margin-left:-0.0576em;margin-right:0.05em;",[104,998],{"className":999,"style":714},[622],[104,1001,1003],{"className":1002},[627,628,629,630],[104,1004,1006],{"className":1005},[207,630],[104,1007,1010],{"className":1008},[207,1009,630],"text",[104,1011,934],{"className":1012},[207,630],[104,1014,667],{"className":1015},[666],[104,1017,1019],{"className":1018},[610],[104,1020,1022],{"className":1021,"style":807},[614],[104,1023],{},[104,1025],{"className":1026,"style":214},[213],[104,1028,170],{"className":1029},[218],[104,1031],{"className":1032,"style":214},[213],[104,1034,1036,1040],{"className":1035},[198],[104,1037],{"className":1038,"style":1039},[202],"height:1.24em;vertical-align:-0.305em;",[104,1041,1044],{"className":1042},[207,1043],"sqrt",[104,1045,1047,1142],{"className":1046},[605,606],[104,1048,1050,1139],{"className":1049},[610],[104,1051,1054,1116],{"className":1052,"style":1053},[614],"height:0.935em;",[104,1055,1059,1063],{"className":1056,"style":1058},[1057],"svg-align","top:-3.2em;",[104,1060],{"className":1061,"style":1062},[622],"height:3.2em;",[104,1064,1067,1070,1073,1076,1107,1110,1113],{"className":1065,"style":1066},[207],"padding-left:1em;",[104,1068,488],{"className":1069},[207],[104,1071,466],{"className":1072,"style":562},[207,208],[104,1074,470],{"className":1075},[566],[104,1077,1079,1082],{"className":1078},[207],[104,1080,317],{"className":1081,"style":571},[207,570],[104,1083,1085],{"className":1084},[697],[104,1086,1088],{"className":1087},[605],[104,1089,1091],{"className":1090},[610],[104,1092,1095],{"className":1093,"style":1094},[614],"height:0.6147em;",[104,1096,1098,1101],{"style":1097},"top:-2.989em;margin-right:0.05em;",[104,1099],{"className":1100,"style":714},[622],[104,1102,1104],{"className":1103},[627,628,629,630],[104,1105,954],{"className":1106},[239,630],[104,1108,476],{"className":1109},[575],[104,1111,318],{"className":1112},[207],[104,1114,506],{"className":1115,"style":745},[207,208],[104,1117,1119,1122],{"style":1118},"top:-2.895em;",[104,1120],{"className":1121,"style":1062},[622],[104,1123,1127],{"className":1124,"style":1126},[1125],"hide-tail","min-width:1.02em;height:1.28em;",[1128,1129,1135],"svg",{"xmlns":1130,"width":1131,"height":1132,"viewBox":1133,"preserveAspectRatio":1134},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","400em","1.28em","0 0 400000 1296","xMinYMin slice",[1136,1137],"path",{"d":1138},"M263,681c0.7,0,18,39.7,52,119\nc34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120\nc340,-704.7,510.7,-1060.3,512,-1067\nl0 -0\nc4.7,-7.3,11,-11,19,-11\nH40000v40H1012.3\ns-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232\nc-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1\ns-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26\nc-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z\nM1001 80h400000v40h-400000z",[104,1140,667],{"className":1141},[666],[104,1143,1145],{"className":1144},[610],[104,1146,1149],{"className":1147,"style":1148},[614],"height:0.305em;",[104,1150],{},[11,1152,1153,1154,1156,1157,1159],{},"é a mesma RMSE que a gente acabou de calcular, com a mesma divisão por ",[87,1155,506],{}," escondida atrás do fator 2 (porque a função dele não divide por ",[87,1158,506],{},", só a nossa MSE já divide).",[79,1161,1163],{"id":1162},"o-modelo-idiota-que-vira-régua","O modelo idiota que vira régua",[11,1165,1166,1167,1170,1171,1174,1175,1178],{},"Antes de qualquer regressão de verdade, o segundo notebook (",[87,1168,1169],{},"aula02a",") faz uma coisa esperta: separa 20% dos dados pra teste (",[87,1172,1173],{},"train_test_split"," implementado na mão, ",[87,1176,1177],{},"random_state=42"," pra dar sempre o mesmo resultado) e define o modelo mais preguiçoso que existe, chutar sempre a média:",[96,1180,1182],{"className":98,"code":1181,"language":100,"meta":57,"style":57},"def modelo_media(y):\n    media = np.mean(y)\n    return np.ones_like(y) * media\n",[87,1183,1184,1189,1194],{"__ignoreMap":57},[104,1185,1186],{"class":106,"line":107},[104,1187,1188],{},"def modelo_media(y):\n",[104,1190,1191],{"class":106,"line":58},[104,1192,1193],{},"    media = np.mean(y)\n",[104,1195,1196],{"class":106,"line":118},[104,1197,1198],{},"    return np.ones_like(y) * media\n",[433,1200,1201],{},[11,1202,1203,1206],{},[15,1204,1205],{},"Saída (353 pacientes de treino):"," MSE do chute (1500, 150) = 4646.20. MSE do \"sempre a média\" = 5978.59.",[11,1208,1209,1210,1214],{},"Por que eu preciso disso? Porque um MSE de 4646 sozinho não diz nada, é só um número. Comparado com o baseline da média, ele fica menor, então o modelo tá aprendendo alguma coisa. Isso é literalmente a definição de ",[445,1211,1213],{"definition":1212},"fração da variância do alvo que o modelo explica, comparado a só chutar a média sempre, onde 1 é perfeito, 0 é igual a chutar a média, negativo é pior que chutar a média","R²",":",[96,1216,1218],{"className":98,"code":1217,"language":100,"meta":57,"style":57},"def r2_score(y, ypred):\n    ss_res = mse(y, ypred)\n    ss_tot = mse(y, modelo_media(y))\n    return 1 - (ss_res \u002F ss_tot)\n",[87,1219,1220,1225,1230,1235],{"__ignoreMap":57},[104,1221,1222],{"class":106,"line":107},[104,1223,1224],{},"def r2_score(y, ypred):\n",[104,1226,1227],{"class":106,"line":58},[104,1228,1229],{},"    ss_res = mse(y, ypred)\n",[104,1231,1232],{"class":106,"line":118},[104,1233,1234],{},"    ss_tot = mse(y, modelo_media(y))\n",[104,1236,1237],{"class":106,"line":124},[104,1238,1239],{},"    return 1 - (ss_res \u002F ss_tot)\n",[11,1241,1242],{},[104,1243,1245,1278],{"className":1244},[148],[104,1246,1248],{"className":1247},[152],[154,1249,1250],{"xmlns":156},[158,1251,1252,1275],{},[161,1253,1254,1261,1263,1265,1267],{},[539,1255,1256,1259],{},[164,1257,1258],{},"R",[483,1260,488],{},[168,1262,170],{},[483,1264,485],{},[168,1266,530],{},[480,1268,1269,1272],{},[932,1270,1271],{},"MSE do modelo",[932,1273,1274],{},"MSE do baseline",[186,1276,1277],{"encoding":188},"R^2 = 1 - \\frac{\\text{MSE do modelo}}{\\text{MSE do baseline}}",[104,1279,1281,1326,1345],{"className":1280,"ariaHidden":194},[193],[104,1282,1284,1287,1317,1320,1323],{"className":1283},[198],[104,1285],{"className":1286,"style":897},[202],[104,1288,1290,1294],{"className":1289},[207],[104,1291,1258],{"className":1292,"style":1293},[207,208],"margin-right:0.0077em;",[104,1295,1297],{"className":1296},[697],[104,1298,1300],{"className":1299},[605],[104,1301,1303],{"className":1302},[610],[104,1304,1306],{"className":1305,"style":897},[614],[104,1307,1308,1311],{"style":900},[104,1309],{"className":1310,"style":714},[622],[104,1312,1314],{"className":1313},[627,628,629,630],[104,1315,488],{"className":1316},[207,630],[104,1318],{"className":1319,"style":214},[213],[104,1321,170],{"className":1322},[218],[104,1324],{"className":1325,"style":214},[213],[104,1327,1329,1333,1336,1339,1342],{"className":1328},[198],[104,1330],{"className":1331,"style":1332},[202],"height:0.7278em;vertical-align:-0.0833em;",[104,1334,485],{"className":1335},[207],[104,1337],{"className":1338,"style":235},[213],[104,1340,530],{"className":1341},[239],[104,1343],{"className":1344,"style":235},[213],[104,1346,1348,1352],{"className":1347},[198],[104,1349],{"className":1350,"style":1351},[202],"height:1.2251em;vertical-align:-0.345em;",[104,1353,1355,1358,1424],{"className":1354},[207],[104,1356],{"className":1357},[566,598],[104,1359,1361],{"className":1360},[480],[104,1362,1364,1416],{"className":1363},[605,606],[104,1365,1367,1413],{"className":1366},[610],[104,1368,1371,1388,1396],{"className":1369,"style":1370},[614],"height:0.8801em;",[104,1372,1373,1376],{"style":618},[104,1374],{"className":1375,"style":623},[622],[104,1377,1379],{"className":1378},[627,628,629,630],[104,1380,1382],{"className":1381},[207,630],[104,1383,1385],{"className":1384},[207,1009,630],[104,1386,1274],{"className":1387},[207,630],[104,1389,1390,1393],{"style":639},[104,1391],{"className":1392,"style":623},[622],[104,1394],{"className":1395,"style":647},[646],[104,1397,1398,1401],{"style":650},[104,1399],{"className":1400,"style":623},[622],[104,1402,1404],{"className":1403},[627,628,629,630],[104,1405,1407],{"className":1406},[207,630],[104,1408,1410],{"className":1409},[207,1009,630],[104,1411,1271],{"className":1412},[207,630],[104,1414,667],{"className":1415},[666],[104,1417,1419],{"className":1418},[610],[104,1420,1422],{"className":1421,"style":674},[614],[104,1423],{},[104,1425],{"className":1426},[575,598],[11,1428,1429,1430,1433],{},"Com o chute (1500, 150): R² = 1 - 4646.20\u002F5978.59 = ",[15,1431,1432],{},"0.2229",". Só uma variável explicando 22% da variação já é um começo, considerando que a diabetes depende de um monte de coisa que não tá nem nessas 10 colunas. E o R² do próprio baseline, por definição, é sempre zero.",[79,1435,1437],{"id":1436},"a-api-do-scikit-learn-construída-por-dentro","A API do scikit-learn, construída por dentro",[11,1439,1440,1441,1444,1445,1448],{},"Aqui que a aula fica interessante de verdade. Em vez de só chamar ",[87,1442,1443],{},"LinearRegression()"," pronto, o professor ensina a ",[15,1446,1447],{},"construir um estimador que segue a mesma convenção"," do scikit-learn, herdando de duas classes-base:",[96,1450,1452],{"className":98,"code":1451,"language":100,"meta":57,"style":57},"from sklearn.base import BaseEstimator, RegressorMixin\n\nclass AverageRegressor(BaseEstimator, RegressorMixin):\n    def fit(self, X, y):\n        self.media_ = np.mean(y)\n        return self\n    def predict(self, X):\n        return np.ones(shape=(X.shape[0],)) * self.media_\n",[87,1453,1454,1459,1463,1468,1473,1478,1483,1488],{"__ignoreMap":57},[104,1455,1456],{"class":106,"line":107},[104,1457,1458],{},"from sklearn.base import BaseEstimator, RegressorMixin\n",[104,1460,1461],{"class":106,"line":58},[104,1462,300],{"emptyLinePlaceholder":63},[104,1464,1465],{"class":106,"line":118},[104,1466,1467],{},"class AverageRegressor(BaseEstimator, RegressorMixin):\n",[104,1469,1470],{"class":106,"line":124},[104,1471,1472],{},"    def fit(self, X, y):\n",[104,1474,1475],{"class":106,"line":308},[104,1476,1477],{},"        self.media_ = np.mean(y)\n",[104,1479,1480],{"class":106,"line":417},[104,1481,1482],{},"        return self\n",[104,1484,1485],{"class":106,"line":422},[104,1486,1487],{},"    def predict(self, X):\n",[104,1489,1490],{"class":106,"line":428},[104,1491,1492],{},"        return np.ones(shape=(X.shape[0],)) * self.media_\n",[11,1494,1495,1498,1499,1502,1503,1506,1507,1510,1511,1514,1515,1518,1519,1522,1523,1526,1527,1534,1535,1537],{},[87,1496,1497],{},"BaseEstimator"," dá de graça um monte de infraestrutura (comparação de parâmetros, clonagem, integração com ",[87,1500,1501],{},"Pipeline"," e ",[87,1504,1505],{},"GridSearchCV",", coisas que vão aparecer só lá na frente da matéria). ",[87,1508,1509],{},"RegressorMixin"," adiciona o método ",[87,1512,1513],{},".score()"," padrão. E repara na convenção do underscore: ",[87,1516,1517],{},"self.media_",", terminando em ",[87,1520,1521],{},"_",", marca \"isso foi ",[15,1524,1525],{},"aprendido"," dos dados\", diferente de um parâmetro configurado na mão. É o mesmo detalhe que eu já tinha visto de fora, ",[20,1528,1529,1530,1533],{"href":353},"usando ",[87,1531,1532],{},"SGDRegressor"," na outra playlist",", só que agora eu tô escrevendo o ",[87,1536,1521],{}," com a própria mão.",[11,1539,1540,1541,1544],{},"O próximo passo mostra algo sutil: um regressor com coeficientes ",[15,1542,1543],{},"fixos",", passados no construtor, sem aprender nada:",[96,1546,1548],{"className":98,"code":1547,"language":100,"meta":57,"style":57},"class LinearRegressor(BaseEstimator, RegressorMixin):\n    def __init__(self, a, b):\n        self.a_ = a\n        self.b_ = b\n    def fit(self, X, y):\n        return self  # não aprende nada, os coeficientes já vieram prontos\n    def predict(self, X):\n        return (self.a_*X + self.b_).reshape(X.shape[0],)\n",[87,1549,1550,1555,1560,1565,1570,1574,1579,1583],{"__ignoreMap":57},[104,1551,1552],{"class":106,"line":107},[104,1553,1554],{},"class LinearRegressor(BaseEstimator, RegressorMixin):\n",[104,1556,1557],{"class":106,"line":58},[104,1558,1559],{},"    def __init__(self, a, b):\n",[104,1561,1562],{"class":106,"line":118},[104,1563,1564],{},"        self.a_ = a\n",[104,1566,1567],{"class":106,"line":124},[104,1568,1569],{},"        self.b_ = b\n",[104,1571,1572],{"class":106,"line":308},[104,1573,1472],{},[104,1575,1576],{"class":106,"line":417},[104,1577,1578],{},"        return self  # não aprende nada, os coeficientes já vieram prontos\n",[104,1580,1581],{"class":106,"line":422},[104,1582,1487],{},[104,1584,1585],{"class":106,"line":428},[104,1586,1587],{},"        return (self.a_*X + self.b_).reshape(X.shape[0],)\n",[11,1589,1590,1591,1214],{},"E depois, a versão que ",[15,1592,1593],{},"inicializa aleatório",[96,1595,1597],{"className":98,"code":1596,"language":100,"meta":57,"style":57},"class LinearRegressor(BaseEstimator, RegressorMixin):\n    def fit(self, X, y):\n        self.a_ = np.random.rand()\n        self.b_ = np.random.rand()\n        return self\n    # ...\n",[87,1598,1599,1603,1607,1612,1617,1621],{"__ignoreMap":57},[104,1600,1601],{"class":106,"line":107},[104,1602,1554],{},[104,1604,1605],{"class":106,"line":58},[104,1606,1472],{},[104,1608,1609],{"class":106,"line":118},[104,1610,1611],{},"        self.a_ = np.random.rand()\n",[104,1613,1614],{"class":106,"line":124},[104,1615,1616],{},"        self.b_ = np.random.rand()\n",[104,1618,1619],{"class":106,"line":308},[104,1620,1482],{},[104,1622,1623],{"class":106,"line":417},[104,1624,1625],{},"    # ...\n",[433,1627,1628],{},[11,1629,1630,1633],{},[15,1631,1632],{},"Saída:"," MSE do chute (1500, 150) do professor = 4646.20. MSE do coeficiente aleatório = 29993.30.",[11,1635,1636,1637,1640],{},"Isso rende um R² de ",[15,1638,1639],{},"-4.02"," para o chute aleatório. Negativo: pior que simplesmente chutar a média toda vez. É a prova de que \"aprender\" não é mágica, é sair de um lugar ruim (aleatório) na direção de um lugar melhor, e um número aleatório sozinho não tem nenhum motivo pra ser bom.",[79,1642,1644],{"id":1643},"do-chute-aleatório-ao-gradiente","Do chute aleatório ao gradiente",[11,1646,1647],{},"A última versão da classe substitui \"aleatório e parado\" por \"aleatório e caminhando\":",[96,1649,1651],{"className":98,"code":1650,"language":100,"meta":57,"style":57},"class LinearRegressor(BaseEstimator, RegressorMixin):\n    def __init__(self, max_iter=1000, learning_rate=0.001):\n        self.max_iter = max_iter\n        self.learning_rate = learning_rate\n\n    def fit(self, X, y):\n        self.coefs_ = np.random.rand(X.shape[1])\n        self.intercept_ = np.random.rand()\n        for i in range(self.max_iter):\n            y_pred = self.predict(X)\n            error = y - y_pred\n            self.coefs_ += X.T @ error * self.learning_rate\n            self.intercept_ += error.sum() * self.learning_rate\n        return self\n\n    def predict(self, X):\n        return (X @ self.coefs_ + self.intercept_).reshape(X.shape[0],)\n",[87,1652,1653,1657,1662,1667,1672,1676,1680,1685,1690,1696,1702,1708,1714,1720,1725,1730,1735],{"__ignoreMap":57},[104,1654,1655],{"class":106,"line":107},[104,1656,1554],{},[104,1658,1659],{"class":106,"line":58},[104,1660,1661],{},"    def __init__(self, max_iter=1000, learning_rate=0.001):\n",[104,1663,1664],{"class":106,"line":118},[104,1665,1666],{},"        self.max_iter = max_iter\n",[104,1668,1669],{"class":106,"line":124},[104,1670,1671],{},"        self.learning_rate = learning_rate\n",[104,1673,1674],{"class":106,"line":308},[104,1675,300],{"emptyLinePlaceholder":63},[104,1677,1678],{"class":106,"line":417},[104,1679,1472],{},[104,1681,1682],{"class":106,"line":422},[104,1683,1684],{},"        self.coefs_ = np.random.rand(X.shape[1])\n",[104,1686,1687],{"class":106,"line":428},[104,1688,1689],{},"        self.intercept_ = np.random.rand()\n",[104,1691,1693],{"class":106,"line":1692},9,[104,1694,1695],{},"        for i in range(self.max_iter):\n",[104,1697,1699],{"class":106,"line":1698},10,[104,1700,1701],{},"            y_pred = self.predict(X)\n",[104,1703,1705],{"class":106,"line":1704},11,[104,1706,1707],{},"            error = y - y_pred\n",[104,1709,1711],{"class":106,"line":1710},12,[104,1712,1713],{},"            self.coefs_ += X.T @ error * self.learning_rate\n",[104,1715,1717],{"class":106,"line":1716},13,[104,1718,1719],{},"            self.intercept_ += error.sum() * self.learning_rate\n",[104,1721,1723],{"class":106,"line":1722},14,[104,1724,1482],{},[104,1726,1728],{"class":106,"line":1727},15,[104,1729,300],{"emptyLinePlaceholder":63},[104,1731,1733],{"class":106,"line":1732},16,[104,1734,1487],{},[104,1736,1738],{"class":106,"line":1737},17,[104,1739,1740],{},"        return (X @ self.coefs_ + self.intercept_).reshape(X.shape[0],)\n",[11,1742,1743,1744,1748,1749,1753,1754,1757,1758,1761,1762,1765],{},"Se você já passou pela ",[20,1745,1747],{"href":1746},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Flab04-gradient-descent","playlist da especialização",", essa conta te é familiar: é o mesmo ",[445,1750,1752],{"definition":1751},"algoritmo que ajusta os parâmetros repetidamente, na direção que mais reduz o erro, até parar de melhorar","gradiente descendente"," batch de sempre, ",[87,1755,1756],{},"coeficiente += X.T @ erro * taxa",", só que sem dividir pela quantidade de exemplos (o que só empurra o efeito pra dentro da ",[87,1759,1760],{},"learning_rate",", um pouco menor compensa a falta da divisão). Esse formato específico, atualizar o peso proporcionalmente ao erro vezes a entrada, tem nome próprio na literatura: ",[15,1763,1764],{},"regra delta"," (ou regra de Widrow-Hoff), um dos algoritmos de aprendizado mais antigos que existem, décadas antes do termo \"gradiente descendente\" pegar.",[11,1767,1768],{},"Roda ao vivo abaixo, na mesma amostra de 45 pacientes, e repara como ele sai de longe e converge pra perto do ajuste ideal:",[1770,1771],"gradient-descent-simulator",{":alpha-presets":1772,":alpha-slider-max":1773,":alpha-slider-min":1774,":alpha-slider-step":1774,":b-range":1775,":initial-alpha":1776,":initial-b":331,":initial-w":331,":w-range":1777,":x-train":337,":y-train":338,"b-label":184,"w-label":20},"[0.01, 0.1, 0.5, 1, 1.5]","1.8","0.01","[-100, 300]","0.5","[0, 1000]",[11,1779,1780,1781,1784,1785,1788,1789,1791,1792,1794,1795,1797,1798,1800],{},"Nessa amostra de 45 pontos (menor que os 353 do treino inteiro, então o ajuste ideal aqui não é exatamente igual ao número que a aula reporta), com ",[87,1782,1783],{},"alpha=0.5"," clicando em \"Rodar 2000\" ele já pousa bem perto do fundo do vale. Testa também um ",[87,1786,1787],{},"alpha"," maior, tipo 1.5: o ",[87,1790,184],{}," começa a oscilar antes de convergir, porque a escala de ",[87,1793,20],{}," (multiplicado por um ",[87,1796,178],{}," minúsculo, entre -0.09 e 0.17) e a escala de ",[87,1799,184],{}," (que soma direto) são bem diferentes, o mesmo problema de escala que a normalização resolve, só que aqui apareceu escondido dentro do próprio dataset já normalizado.",[11,1802,1803],{},"No dataset de treino inteiro (353 pacientes), rodando essa mesma classe com uma única feature (o índice de massa corporal):",[433,1805,1806],{},[11,1807,1808,134,1810,1813,1814,1817],{},[15,1809,1632],{},[87,1811,1812],{},"coefs_ = [529.65]",", ",[87,1815,1816],{},"intercept_ = 154.82",", MSE = 4302.16.",[11,1819,1820,1821,1824,1825,1502,1827,1829],{},"Isso já bate o melhor chute manual (4646.20 → 4302.16) e rende R² = 1 - 4302.16\u002F5978.59 = ",[15,1822,1823],{},"0.2804",". Sem eu escolher ",[87,1826,20],{},[87,1828,184],{}," na mão, o gradiente achou um ajuste melhor sozinho.",[79,1831,1833],{"id":1832},"de-uma-variável-pra-dez","De uma variável pra dez",[11,1835,1836,1837,1840],{},"O dataset tem 10 colunas, e até agora eu só usei uma (o índice de massa corporal). A mesma classe, sem nenhuma mudança de código, aceita ",[87,1838,1839],{},"X_train"," inteiro:",[96,1842,1844],{"className":98,"code":1843,"language":100,"meta":57,"style":57},"regressor = LinearRegressor()\nregressor.fit(X_train, y_train)  # agora com as 10 colunas\n",[87,1845,1846,1851],{"__ignoreMap":57},[104,1847,1848],{"class":106,"line":107},[104,1849,1850],{},"regressor = LinearRegressor()\n",[104,1852,1853],{"class":106,"line":58},[104,1854,1855],{},"regressor.fit(X_train, y_train)  # agora com as 10 colunas\n",[433,1857,1858],{},[11,1859,1860,1862,1863,1866],{},[15,1861,1632],{}," MSE = 3027.12 (contra 4302.16 usando só uma variável). RMSE cai de 65.59 para 55.02. R² sobe de 0.2804 para ",[15,1864,1865],{},"0.4937",".",[11,1868,1869],{},"Quase o dobro de variância explicada, só por deixar o modelo enxergar as outras 9 variáveis (idade, sexo, pressão, as seis medidas de sangue) que ele simplesmente não tinha acesso antes. Ninguém mudou o algoritmo, só a quantidade de informação que ele recebeu.",[79,1871,1873],{"id":1872},"fechando","Fechando",[1875,1876,1877,1891],"table",{},[1878,1879,1880],"thead",{},[1881,1882,1883,1888],"tr",{},[1884,1885,1887],"th",{"align":1886},"left","O que eu já sabia",[1884,1889,1890],{"align":1886},"O que essa aula assentou",[1892,1893,1894,1908,1916],"tbody",{},[1881,1895,1896,1900],{},[1897,1898,1899],"td",{"align":1886},"Regressão linear é ajustar uma reta que minimiza o erro",[1897,1901,1902,1903,318,1905,1907],{"align":1886},"O ",[87,1904,1497],{},[87,1906,1509],{}," é o contrato que faz qualquer classe minha se comportar como um estimador de verdade do scikit-learn",[1881,1909,1910,1913],{},[1897,1911,1912],{"align":1886},"MAE, MSE, RMSE medem o quão errado o modelo está",[1897,1914,1915],{"align":1886},"Sem comparar com um baseline (tipo chutar a média), esses números não dizem nada sozinhos: R² resolve isso",[1881,1917,1918,1921],{},[1897,1919,1920],{"align":1886},"Gradiente descendente ajusta pesos na direção que reduz o erro",[1897,1922,1923],{"align":1886},"Esse ajuste tem nome próprio na literatura clássica (regra delta), e \"aprender\" nada mais é que sair de um chute ruim andando na direção certa",[11,1925,1926,1927,1935,1936,1939],{},"Duas coisas ficaram em aberto de propósito. Primeiro: resolver isso tudo iterando, passo a passo, funciona, mas regressão linear tem solução exata, fechada, numa única conta, e é isso que ",[20,1928,1930,1931,1934],{"href":1929},"\u002Fplaylists\u002Fpattern-recognition\u002Fnormal-equation","a próxima aula (",[87,1932,1933],{},"aula02b",", equação normal) resolve",". Segundo: eu não toquei nos outros modelos que a aula compara (",[87,1937,1938],{},"aula02c","), fica pro próximo post também.",[79,1941,1943],{"id":1942},"aplicação-prática","Aplicação Prática",[11,1945,1946],{},"Uso o mesmo dataset de diabetes que já apareceu o post inteiro, sem introduzir nada de novo: o objetivo aqui é só visualizar o quão bom ficou o ajuste de 10 variáveis que acabei de mostrar.",[96,1948,1950],{"className":98,"code":1949,"language":100,"meta":57,"style":57},"regressor = LinearRegressor(max_iter=1000, learning_rate=0.001)\nregressor.fit(X_train, y_train)\ny_pred = regressor.predict(X_train)\n",[87,1951,1952,1957,1962],{"__ignoreMap":57},[104,1953,1954],{"class":106,"line":107},[104,1955,1956],{},"regressor = LinearRegressor(max_iter=1000, learning_rate=0.001)\n",[104,1958,1959],{"class":106,"line":58},[104,1960,1961],{},"regressor.fit(X_train, y_train)\n",[104,1963,1964],{"class":106,"line":118},[104,1965,1966],{},"y_pred = regressor.predict(X_train)\n",[11,1968,1969,1970,1973],{},"Reproduzi essa mesma classe (com uma semente aleatória diferente, então os coeficientes batem só na segunda casa decimal com os do professor, o MSE final ficou em 3027.43, praticamente idêntico ao 3027.12 reportado na aula) pra extrair os pares de valor real contra valor previsto de 40 pacientes do treino. Cada ponto é um paciente: quanto mais perto da linha pontilhada (a previsão perfeita, ",[87,1971,1972],{},"previsto = real","), melhor o modelo acertou aquele caso.",[1975,1976],"predicted-vs-actual-scatter",{":actual":1977,":predicted":1978,"point-label":339,"x-label":1979,"y-label":1980},"[42, 51, 52, 59, 60, 67, 71, 72, 72, 77, 77, 78, 83, 89, 96, 102, 104, 144, 146, 150, 150, 150, 170, 171, 172, 174, 179, 190, 198, 212, 219, 262, 265, 275, 276, 281, 292, 308, 317, 341]","[143.2, 80.1, 78.4, 120.6, 140.3, 183.2, 95.9, 94.7, 92.1, 91.1, 174.5, 86.5, 138.4, 157.2, 132.4, 125.7, 94.1, 165.1, 157.4, 163.9, 111.3, 204.9, 172.6, 183.3, 140.6, 172.7, 175.6, 122.7, 192.1, 198.0, 160.9, 177.3, 185.5, 198.8, 143.2, 236.4, 209.2, 234.2, 230.8, 242.5]","progressão real da diabetes","progressão prevista",[11,1982,1983],{},"Dá pra ver a nuvem de pontos seguindo a diagonal, mas com bastante espalhamento, exatamente o que um R² de 0.49 significa na prática: o modelo capturou boa parte do padrão, mas ainda erra bastante caso a caso. Nada mau pra um modelo linear simples, sem nenhuma feature nova, sem regularização, só 10 números crus e 1000 passos de gradiente.",[1985,1986,1987],"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":57,"searchDepth":58,"depth":58,"links":1989},[1990,1991,1992,1993,1994,1995,1996,1997,1998],{"id":81,"depth":58,"text":82},{"id":141,"depth":58,"text":142},{"id":346,"depth":58,"text":347},{"id":1162,"depth":58,"text":1163},{"id":1436,"depth":58,"text":1437},{"id":1643,"depth":58,"text":1644},{"id":1832,"depth":58,"text":1833},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"2026-08-19","Primeira aula da matéria de Reconhecimento de Padrões: o professor pega um dataset real de diabetes, chuta coeficientes na mão, e constrói um estimador próprio seguindo a API do scikit-learn. Eu reconto o porquê de cada passo, com o Bishop do lado.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Flinear-regression-estimator","pattern-recognition",{"title":72,"description":2000},"pt\u002Fplaylists\u002Fpattern-recognition\u002Flinear-regression-estimator",[2007,2008,2009],"regressao-linear","scikit-learn","gradiente-descendente","hoaj3uts_Gr2n11tQErHx5mqUvvuDxuJfR_v79r23no",{"id":2012,"title":2013,"body":2014,"cover":3,"date":1999,"description":4124,"extension":61,"meta":4125,"navigation":63,"order":58,"path":4126,"playlist":2003,"seo":4127,"status":66,"stem":4128,"tags":4129,"__hash__":4132},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fnormal-equation.md","Equação Normal: Resolvendo Regressão numa Conta Só",{"type":8,"value":2015,"toc":4113},[2016,2023,2027,2030,2037,2513,2519,2775,2782,2786,3120,3125,3140,3162,3166,3176,3245,3259,3266,3524,3568,3644,3648,3654,3692,3703,3709,3713,3720,3822,3825,3859,3864,3866,4066,4068,4080,4098,4104,4111],[11,2017,2018,2022],{},[20,2019,2021],{"href":2020},"\u002Fplaylists\u002Fpattern-recognition\u002Flinear-regression-estimator","No post anterior"," eu deixei um gancho: dá pra resolver regressão linear numa conta só, sem ficar iterando gradiente. É exatamente isso que as aulas 2b e 2c mostram, e de quebra o professor compara o resultado contra mais cinco tipos de regressor diferentes.",[79,2024,2026],{"id":2025},"zerando-a-derivada-em-vez-de-andar-até-ela","Zerando a derivada em vez de andar até ela",[11,2028,2029],{},"O gradiente descendente funciona porque, a cada passo, ele anda um pouquinho na direção que reduz o erro. Mas pra regressão linear, a função de erro (a soma dos quadrados que eu já vinha usando) tem uma propriedade especial: ela é uma parábola em relação aos pesos, uma tigela lisa sem vales falsos. E uma tigela lisa tem um único ponto onde a derivada é exatamente zero, o fundo dela. Em vez de caminhar até lá passo a passo, dá pra calcular esse ponto direto.",[11,2031,2032,2033,2036],{},"O Bishop escreve o modelo linear numa forma mais geral que a minha, com uma matriz Φ (a ",[15,2034,2035],{},"matriz de design",", cada linha é um paciente, cada coluna uma variável de entrada, mais uma coluna de 1's pro termo de bias). A função de erro é",[11,2038,2039],{},[104,2040,2042,2127],{"className":2041},[148],[104,2043,2045],{"className":2044},[152],[154,2046,2047],{"xmlns":156},[158,2048,2049,2124],{},[161,2050,2051,2058,2060,2062,2064,2066,2072,2086,2088,2094,2096,2104,2108,2110,2116,2118],{},[515,2052,2053,2055],{},[164,2054,466],{},[164,2056,2057],{},"D",[168,2059,470],{"stretchy":469},[164,2061,317],{"mathvariant":473},[168,2063,476],{"stretchy":469},[168,2065,170],{},[480,2067,2068,2070],{},[483,2069,485],{},[483,2071,488],{},[490,2073,2074,2076,2084],{},[168,2075,494],{},[161,2077,2078,2080,2082],{},[164,2079,499],{},[168,2081,170],{},[483,2083,485],{},[164,2085,506],{},[168,2087,509],{"stretchy":469},[515,2089,2090,2092],{},[164,2091,535],{},[164,2093,499],{},[168,2095,530],{},[539,2097,2098,2100],{},[164,2099,317],{"mathvariant":473},[164,2101,2103],{"mathvariant":2102},"sans-serif","T",[164,2105,2107],{"mathvariant":2106},"bold-italic","ϕ",[168,2109,470],{"stretchy":469},[515,2111,2112,2114],{},[164,2113,178],{"mathvariant":473},[164,2115,499],{},[168,2117,476],{"stretchy":469},[539,2119,2120,2122],{},[168,2121,543],{"stretchy":469},[483,2123,488],{},[186,2125,2126],{"encoding":188},"E_D(\\mathbf{w}) = \\frac{1}{2}\\sum_{n=1}^{N}\\{t_n - \\mathbf{w}^{\\mathsf{T}}\\boldsymbol{\\phi}(\\mathbf{x}_n)\\}^2",[104,2128,2130,2195,2387],{"className":2129,"ariaHidden":194},[193],[104,2131,2133,2136,2177,2180,2183,2186,2189,2192],{"className":2132},[198],[104,2134],{"className":2135,"style":558},[202],[104,2137,2139,2142],{"className":2138},[207],[104,2140,466],{"className":2141,"style":562},[207,208],[104,2143,2145],{"className":2144},[697],[104,2146,2148,2169],{"className":2147},[605,606],[104,2149,2151,2166],{"className":2150},[610],[104,2152,2154],{"className":2153,"style":993},[614],[104,2155,2156,2159],{"style":996},[104,2157],{"className":2158,"style":714},[622],[104,2160,2162],{"className":2161},[627,628,629,630],[104,2163,2057],{"className":2164,"style":2165},[207,208,630],"margin-right:0.0278em;",[104,2167,667],{"className":2168},[666],[104,2170,2172],{"className":2171},[610],[104,2173,2175],{"className":2174,"style":807},[614],[104,2176],{},[104,2178,470],{"className":2179},[566],[104,2181,317],{"className":2182,"style":571},[207,570],[104,2184,476],{"className":2185},[575],[104,2187],{"className":2188,"style":214},[213],[104,2190,170],{"className":2191},[218],[104,2193],{"className":2194,"style":214},[213],[104,2196,2198,2201,2269,2272,2335,2338,2378,2381,2384],{"className":2197},[198],[104,2199],{"className":2200,"style":591},[202],[104,2202,2204,2207,2266],{"className":2203},[207],[104,2205],{"className":2206},[566,598],[104,2208,2210],{"className":2209},[480],[104,2211,2213,2258],{"className":2212},[605,606],[104,2214,2216,2255],{"className":2215},[610],[104,2217,2219,2233,2241],{"className":2218,"style":615},[614],[104,2220,2221,2224],{"style":618},[104,2222],{"className":2223,"style":623},[622],[104,2225,2227],{"className":2226},[627,628,629,630],[104,2228,2230],{"className":2229},[207,630],[104,2231,488],{"className":2232},[207,630],[104,2234,2235,2238],{"style":639},[104,2236],{"className":2237,"style":623},[622],[104,2239],{"className":2240,"style":647},[646],[104,2242,2243,2246],{"style":650},[104,2244],{"className":2245,"style":623},[622],[104,2247,2249],{"className":2248},[627,628,629,630],[104,2250,2252],{"className":2251},[207,630],[104,2253,485],{"className":2254},[207,630],[104,2256,667],{"className":2257},[666],[104,2259,2261],{"className":2260},[610],[104,2262,2264],{"className":2263,"style":674},[614],[104,2265],{},[104,2267],{"className":2268},[575,598],[104,2270],{"className":2271,"style":683},[213],[104,2273,2275,2278],{"className":2274},[687],[104,2276,494],{"className":2277,"style":693},[687,691,692],[104,2279,2281],{"className":2280},[697],[104,2282,2284,2327],{"className":2283},[605,606],[104,2285,2287,2324],{"className":2286},[610],[104,2288,2290,2310],{"className":2289,"style":707},[614],[104,2291,2292,2295],{"style":710},[104,2293],{"className":2294,"style":714},[622],[104,2296,2298],{"className":2297},[627,628,629,630],[104,2299,2301,2304,2307],{"className":2300},[207,630],[104,2302,499],{"className":2303},[207,208,630],[104,2305,170],{"className":2306},[218,630],[104,2308,485],{"className":2309},[207,630],[104,2311,2312,2315],{"style":732},[104,2313],{"className":2314,"style":714},[622],[104,2316,2318],{"className":2317},[627,628,629,630],[104,2319,2321],{"className":2320},[207,630],[104,2322,506],{"className":2323,"style":745},[207,208,630],[104,2325,667],{"className":2326},[666],[104,2328,2330],{"className":2329},[610],[104,2331,2333],{"className":2332,"style":755},[614],[104,2334],{},[104,2336,509],{"className":2337},[566],[104,2339,2341,2344],{"className":2340},[207],[104,2342,535],{"className":2343},[207,208],[104,2345,2347],{"className":2346},[697],[104,2348,2350,2370],{"className":2349},[605,606],[104,2351,2353,2367],{"className":2352},[610],[104,2354,2356],{"className":2355,"style":785},[614],[104,2357,2358,2361],{"style":788},[104,2359],{"className":2360,"style":714},[622],[104,2362,2364],{"className":2363},[627,628,629,630],[104,2365,499],{"className":2366},[207,208,630],[104,2368,667],{"className":2369},[666],[104,2371,2373],{"className":2372},[610],[104,2374,2376],{"className":2375,"style":807},[614],[104,2377],{},[104,2379],{"className":2380,"style":235},[213],[104,2382,530],{"className":2383},[239],[104,2385],{"className":2386,"style":235},[213],[104,2388,2390,2394,2428,2438,2441,2481,2484],{"className":2389},[198],[104,2391],{"className":2392,"style":2393},[202],"height:1.0991em;vertical-align:-0.25em;",[104,2395,2397,2400],{"className":2396},[207],[104,2398,317],{"className":2399,"style":571},[207,570],[104,2401,2403],{"className":2402},[697],[104,2404,2406],{"className":2405},[605],[104,2407,2409],{"className":2408},[610],[104,2410,2413],{"className":2411,"style":2412},[614],"height:0.8491em;",[104,2414,2415,2418],{"style":900},[104,2416],{"className":2417,"style":714},[622],[104,2419,2421],{"className":2420},[627,628,629,630],[104,2422,2424],{"className":2423},[207,630],[104,2425,2103],{"className":2426},[207,2427,630],"mathsf",[104,2429,2431],{"className":2430},[207],[104,2432,2434],{"className":2433},[207],[104,2435,2107],{"className":2436},[207,2437],"boldsymbol",[104,2439,470],{"className":2440},[566],[104,2442,2444,2447],{"className":2443},[207],[104,2445,178],{"className":2446},[207,570],[104,2448,2450],{"className":2449},[697],[104,2451,2453,2473],{"className":2452},[605,606],[104,2454,2456,2470],{"className":2455},[610],[104,2457,2459],{"className":2458,"style":785},[614],[104,2460,2461,2464],{"style":788},[104,2462],{"className":2463,"style":714},[622],[104,2465,2467],{"className":2466},[627,628,629,630],[104,2468,499],{"className":2469},[207,208,630],[104,2471,667],{"className":2472},[666],[104,2474,2476],{"className":2475},[610],[104,2477,2479],{"className":2478,"style":807},[614],[104,2480],{},[104,2482,476],{"className":2483},[575],[104,2485,2487,2490],{"className":2486},[575],[104,2488,543],{"className":2489},[575],[104,2491,2493],{"className":2492},[697],[104,2494,2496],{"className":2495},[605],[104,2497,2499],{"className":2498},[610],[104,2500,2502],{"className":2501,"style":897},[614],[104,2503,2504,2507],{"style":900},[104,2505],{"className":2506,"style":714},[622],[104,2508,2510],{"className":2509},[627,628,629,630],[104,2511,488],{"className":2512},[207,630],[11,2514,2515,2516,2518],{},"Igualando o gradiente dessa conta a zero e isolando ",[87,2517,317],{},", sobra",[11,2520,2521],{},[104,2522,2524,2574],{"className":2523},[148],[104,2525,2527],{"className":2526},[152],[154,2528,2529],{"xmlns":156},[158,2530,2531,2571],{},[161,2532,2533,2540,2542,2544,2551,2553,2563,2569],{},[515,2534,2535,2537],{},[164,2536,317],{"mathvariant":473},[932,2538,2539],{},"ML",[168,2541,170],{},[168,2543,470],{"stretchy":469},[539,2545,2546,2549],{},[164,2547,2548],{"mathvariant":473},"Φ",[164,2550,2103],{"mathvariant":2102},[164,2552,2548],{"mathvariant":473},[539,2554,2555,2557],{},[168,2556,476],{"stretchy":469},[161,2558,2559,2561],{},[168,2560,530],{},[483,2562,485],{},[539,2564,2565,2567],{},[164,2566,2548],{"mathvariant":473},[164,2568,2103],{"mathvariant":2102},[164,2570,535],{"mathvariant":473},[186,2572,2573],{"encoding":188},"\\mathbf{w}_{\\text{ML}} = (\\boldsymbol{\\Phi}^{\\mathsf{T}}\\boldsymbol{\\Phi})^{-1}\\boldsymbol{\\Phi}^{\\mathsf{T}}\\mathbf{t}",[104,2575,2577,2640],{"className":2576,"ariaHidden":194},[193],[104,2578,2580,2584,2631,2634,2637],{"className":2579},[198],[104,2581],{"className":2582,"style":2583},[202],"height:0.5944em;vertical-align:-0.15em;",[104,2585,2587,2590],{"className":2586},[207],[104,2588,317],{"className":2589,"style":571},[207,570],[104,2591,2593],{"className":2592},[697],[104,2594,2596,2623],{"className":2595},[605,606],[104,2597,2599,2620],{"className":2598},[610],[104,2600,2602],{"className":2601,"style":993},[614],[104,2603,2605,2608],{"style":2604},"top:-2.55em;margin-left:-0.016em;margin-right:0.05em;",[104,2606],{"className":2607,"style":714},[622],[104,2609,2611],{"className":2610},[627,628,629,630],[104,2612,2614],{"className":2613},[207,630],[104,2615,2617],{"className":2616},[207,1009,630],[104,2618,2539],{"className":2619},[207,630],[104,2621,667],{"className":2622},[666],[104,2624,2626],{"className":2625},[610],[104,2627,2629],{"className":2628,"style":807},[614],[104,2630],{},[104,2632],{"className":2633,"style":214},[213],[104,2635,170],{"className":2636},[218],[104,2638],{"className":2639,"style":214},[213],[104,2641,2643,2647,2650,2690,2699,2734,2772],{"className":2642},[198],[104,2644],{"className":2645,"style":2646},[202],"height:1.1751em;vertical-align:-0.25em;",[104,2648,470],{"className":2649},[566],[104,2651,2653,2662],{"className":2652},[207],[104,2654,2656],{"className":2655},[207],[104,2657,2659],{"className":2658},[207],[104,2660,2548],{"className":2661},[207,570],[104,2663,2665],{"className":2664},[697],[104,2666,2668],{"className":2667},[605],[104,2669,2671],{"className":2670},[610],[104,2672,2675],{"className":2673,"style":2674},[614],"height:0.9251em;",[104,2676,2678,2681],{"style":2677},"top:-3.139em;margin-right:0.05em;",[104,2679],{"className":2680,"style":714},[622],[104,2682,2684],{"className":2683},[627,628,629,630],[104,2685,2687],{"className":2686},[207,630],[104,2688,2103],{"className":2689},[207,2427,630],[104,2691,2693],{"className":2692},[207],[104,2694,2696],{"className":2695},[207],[104,2697,2548],{"className":2698},[207,570],[104,2700,2702,2705],{"className":2701},[575],[104,2703,476],{"className":2704},[575],[104,2706,2708],{"className":2707},[697],[104,2709,2711],{"className":2710},[605],[104,2712,2714],{"className":2713},[610],[104,2715,2717],{"className":2716,"style":897},[614],[104,2718,2719,2722],{"style":900},[104,2720],{"className":2721,"style":714},[622],[104,2723,2725],{"className":2724},[627,628,629,630],[104,2726,2728,2731],{"className":2727},[207,630],[104,2729,530],{"className":2730},[207,630],[104,2732,485],{"className":2733},[207,630],[104,2735,2737,2746],{"className":2736},[207],[104,2738,2740],{"className":2739},[207],[104,2741,2743],{"className":2742},[207],[104,2744,2548],{"className":2745},[207,570],[104,2747,2749],{"className":2748},[697],[104,2750,2752],{"className":2751},[605],[104,2753,2755],{"className":2754},[610],[104,2756,2758],{"className":2757,"style":2674},[614],[104,2759,2760,2763],{"style":2677},[104,2761],{"className":2762,"style":714},[622],[104,2764,2766],{"className":2765},[627,628,629,630],[104,2767,2769],{"className":2768},[207,630],[104,2770,2103],{"className":2771},[207,2427,630],[104,2773,535],{"className":2774},[207,570],[11,2776,2777,2778,2781],{},"Essa é a ",[15,2779,2780],{},"equação normal",". Uma matriz, duas multiplicações e uma inversão, e pronto, o ajuste ótimo sai direto, sem taxa de aprendizado, sem escolher quantas iterações, sem nenhum dos cuidados que o gradiente descendente exige.",[79,2783,2785],{"id":2784},"o-truque-de-embutir-o-bias","O truque de embutir o bias",[11,2787,2788,2789,2792,2793,2795,2796,2870,2871,2979,2980,3049,3050,3119],{},"Repara que a fórmula acima não tem um ",[87,2790,2791],{},"+ b"," separado, o bias tá dentro do próprio ",[87,2794,317],{}," (o Bishop chama de ",[104,2797,2799,2817],{"className":2798},[148],[104,2800,2802],{"className":2801},[152],[154,2803,2804],{"xmlns":156},[158,2805,2806,2814],{},[161,2807,2808],{},[515,2809,2810,2812],{},[164,2811,317],{},[483,2813,331],{},[186,2815,2816],{"encoding":188},"w_0",[104,2818,2820],{"className":2819,"ariaHidden":194},[193],[104,2821,2823,2827],{"className":2822},[198],[104,2824],{"className":2825,"style":2826},[202],"height:0.5806em;vertical-align:-0.15em;",[104,2828,2830,2834],{"className":2829},[207],[104,2831,317],{"className":2832,"style":2833},[207,208],"margin-right:0.0269em;",[104,2835,2837],{"className":2836},[697],[104,2838,2840,2862],{"className":2839},[605,606],[104,2841,2843,2859],{"className":2842},[610],[104,2844,2847],{"className":2845,"style":2846},[614],"height:0.3011em;",[104,2848,2850,2853],{"style":2849},"top:-2.55em;margin-left:-0.0269em;margin-right:0.05em;",[104,2851],{"className":2852,"style":714},[622],[104,2854,2856],{"className":2855},[627,628,629,630],[104,2857,331],{"className":2858},[207,630],[104,2860,667],{"className":2861},[666],[104,2863,2865],{"className":2864},[610],[104,2866,2868],{"className":2867,"style":807},[614],[104,2869],{},"). Isso só funciona porque ele define uma \"função de base\" falsa, ",[104,2872,2874,2902],{"className":2873},[148],[104,2875,2877],{"className":2876},[152],[154,2878,2879],{"xmlns":156},[158,2880,2881,2899],{},[161,2882,2883,2889,2891,2893,2895,2897],{},[515,2884,2885,2887],{},[164,2886,2107],{},[483,2888,331],{},[168,2890,470],{"stretchy":469},[164,2892,178],{"mathvariant":473},[168,2894,476],{"stretchy":469},[168,2896,170],{},[483,2898,485],{},[186,2900,2901],{"encoding":188},"\\phi_0(\\mathbf{x}) = 1",[104,2903,2905,2969],{"className":2904,"ariaHidden":194},[193],[104,2906,2908,2911,2951,2954,2957,2960,2963,2966],{"className":2907},[198],[104,2909],{"className":2910,"style":558},[202],[104,2912,2914,2917],{"className":2913},[207],[104,2915,2107],{"className":2916},[207,208],[104,2918,2920],{"className":2919},[697],[104,2921,2923,2943],{"className":2922},[605,606],[104,2924,2926,2940],{"className":2925},[610],[104,2927,2929],{"className":2928,"style":2846},[614],[104,2930,2931,2934],{"style":788},[104,2932],{"className":2933,"style":714},[622],[104,2935,2937],{"className":2936},[627,628,629,630],[104,2938,331],{"className":2939},[207,630],[104,2941,667],{"className":2942},[666],[104,2944,2946],{"className":2945},[610],[104,2947,2949],{"className":2948,"style":807},[614],[104,2950],{},[104,2952,470],{"className":2953},[566],[104,2955,178],{"className":2956},[207,570],[104,2958,476],{"className":2959},[575],[104,2961],{"className":2962,"style":214},[213],[104,2964,170],{"className":2965},[218],[104,2967],{"className":2968,"style":214},[213],[104,2970,2972,2976],{"className":2971},[198],[104,2973],{"className":2974,"style":2975},[202],"height:0.6444em;",[104,2977,485],{"className":2978},[207],", uma coluna inteira de 1's, então multiplicar por ",[104,2981,2983,3000],{"className":2982},[148],[104,2984,2986],{"className":2985},[152],[154,2987,2988],{"xmlns":156},[158,2989,2990,2998],{},[161,2991,2992],{},[515,2993,2994,2996],{},[164,2995,317],{},[483,2997,331],{},[186,2999,2816],{"encoding":188},[104,3001,3003],{"className":3002,"ariaHidden":194},[193],[104,3004,3006,3009],{"className":3005},[198],[104,3007],{"className":3008,"style":2826},[202],[104,3010,3012,3015],{"className":3011},[207],[104,3013,317],{"className":3014,"style":2833},[207,208],[104,3016,3018],{"className":3017},[697],[104,3019,3021,3041],{"className":3020},[605,606],[104,3022,3024,3038],{"className":3023},[610],[104,3025,3027],{"className":3026,"style":2846},[614],[104,3028,3029,3032],{"style":2849},[104,3030],{"className":3031,"style":714},[622],[104,3033,3035],{"className":3034},[627,628,629,630],[104,3036,331],{"className":3037},[207,630],[104,3039,667],{"className":3040},[666],[104,3042,3044],{"className":3043},[610],[104,3045,3047],{"className":3046,"style":807},[614],[104,3048],{}," dá exatamente ",[104,3051,3053,3070],{"className":3052},[148],[104,3054,3056],{"className":3055},[152],[154,3057,3058],{"xmlns":156},[158,3059,3060,3068],{},[161,3061,3062],{},[515,3063,3064,3066],{},[164,3065,317],{},[483,3067,331],{},[186,3069,2816],{"encoding":188},[104,3071,3073],{"className":3072,"ariaHidden":194},[193],[104,3074,3076,3079],{"className":3075},[198],[104,3077],{"className":3078,"style":2826},[202],[104,3080,3082,3085],{"className":3081},[207],[104,3083,317],{"className":3084,"style":2833},[207,208],[104,3086,3088],{"className":3087},[697],[104,3089,3091,3111],{"className":3090},[605,606],[104,3092,3094,3108],{"className":3093},[610],[104,3095,3097],{"className":3096,"style":2846},[614],[104,3098,3099,3102],{"style":2849},[104,3100],{"className":3101,"style":714},[622],[104,3103,3105],{"className":3104},[627,628,629,630],[104,3106,331],{"className":3107},[207,630],[104,3109,667],{"className":3110},[666],[104,3112,3114],{"className":3113},[610],[104,3115,3117],{"className":3116,"style":807},[614],[104,3118],{}," pra todo paciente, o mesmo efeito de somar um bias fixo.",[11,3121,3122,3123,1214],{},"O código faz isso na unha, colando uma coluna de 1's na frente de ",[87,3124,133],{},[96,3126,3128],{"className":98,"code":3127,"language":100,"meta":57,"style":57},"def include_bias(X):\n    return np.hstack((np.ones((X.shape[0], 1)), X))\n",[87,3129,3130,3135],{"__ignoreMap":57},[104,3131,3132],{"class":106,"line":107},[104,3133,3134],{},"def include_bias(X):\n",[104,3136,3137],{"class":106,"line":58},[104,3138,3139],{},"    return np.hstack((np.ones((X.shape[0], 1)), X))\n",[11,3141,3142,3143,1502,3146,3149,3150,3153,3154,3157,3158,3161],{},"Isso muda a arquitetura da classe: em vez de guardar ",[87,3144,3145],{},"coefs_",[87,3147,3148],{},"intercept_"," como duas coisas separadas (",[20,3151,3152],{"href":2020},"como no post anterior","), agora existe um único vetor ",[87,3155,3156],{},"w_",", onde ",[87,3159,3160],{},"w_[0]"," é o bias e o resto são os coeficientes de cada variável.",[79,3163,3165],{"id":3164},"três-versões-uma-comparação-real","Três versões, uma comparação real",[11,3167,3168,3169,3172,3173,3175],{},"O notebook roda três variações da mesma classe ",[87,3170,3171],{},"LinearRegressor",", todas no mesmo dataset de diabetes (agora usando o ",[87,3174,1173],{}," de verdade do scikit-learn, então os números não batem exatamente com os do post anterior, que usava minha própria implementação na unha):",[1875,3177,3178,3192],{},[1878,3179,3180],{},[1881,3181,3182,3185,3188],{},[1884,3183,3184],{"align":1886},"Versão",[1884,3186,3187],{"align":1886},"Como resolve",[1884,3189,3191],{"align":3190},"right","MSE (treino)",[1892,3193,3194,3210,3227],{},[1881,3195,3196,3199,3207],{},[1897,3197,3198],{"align":1886},"Gradiente, bias separado",[1897,3200,3201,1813,3204,3206],{"align":1886},[87,3202,3203],{},"coefs_ += X.T@erro*0.001",[87,3205,3148],{}," à parte",[1897,3208,3209],{"align":3190},"3142.25",[1881,3211,3212,3215,3224],{},[1897,3213,3214],{"align":1886},"Gradiente, bias embutido",[1897,3216,3217,3218,1502,3221],{"align":1886},"mesma ideia, mas com ",[87,3219,3220],{},"include_bias",[87,3222,3223],{},"learning_rate=0.005",[1897,3225,3226],{"align":3190},"2898.90",[1881,3228,3229,3234,3240],{},[1897,3230,3231],{"align":1886},[15,3232,3233],{},"Equação normal",[1897,3235,3236,3239],{"align":1886},[87,3237,3238],{},"w_ = np.linalg.pinv(X) @ y",", sem iterar",[1897,3241,3242],{"align":3190},[15,3243,3244],{},"2868.55",[11,3246,3247,3248,3250,3251,3254,3255,3258],{},"A equação normal ganha das duas, sem eu precisar escolher ",[87,3249,1760],{}," nem ",[87,3252,3253],{},"max_iter",". Não é coincidência: gradiente descendente é uma forma de ",[34,3256,3257],{},"aproximar"," essa mesma resposta iterando, e com iterações e taxa de aprendizado suficientes ele converge pro mesmo lugar. A equação normal só pula direto pro final.",[79,3260,3262,3263],{"id":3261},"a-pseudo-inversa-é-o-pinv","A pseudo-inversa é o ",[87,3264,3265],{},"pinv",[11,3267,3268,3423,3424,3427,3428,3488,3489,3519,3520,3523],{},[104,3269,3271,3309],{"className":3270},[148],[104,3272,3274],{"className":3273},[152],[154,3275,3276],{"xmlns":156},[158,3277,3278,3306],{},[161,3279,3280,3282,3288,3290,3300],{},[168,3281,470],{"stretchy":469},[539,3283,3284,3286],{},[164,3285,2548],{"mathvariant":959},[164,3287,2103],{"mathvariant":2102},[164,3289,2548],{"mathvariant":959},[539,3291,3292,3294],{},[168,3293,476],{"stretchy":469},[161,3295,3296,3298],{},[168,3297,530],{},[483,3299,485],{},[539,3301,3302,3304],{},[164,3303,2548],{"mathvariant":959},[164,3305,2103],{"mathvariant":2102},[186,3307,3308],{"encoding":188},"(\\Phi^{\\mathsf{T}}\\Phi)^{-1}\\Phi^{\\mathsf{T}}",[104,3310,3312],{"className":3311,"ariaHidden":194},[193],[104,3313,3315,3318,3321,3353,3356,3391],{"className":3314},[198],[104,3316],{"className":3317,"style":2393},[202],[104,3319,470],{"className":3320},[566],[104,3322,3324,3327],{"className":3323},[207],[104,3325,2548],{"className":3326},[207],[104,3328,3330],{"className":3329},[697],[104,3331,3333],{"className":3332},[605],[104,3334,3336],{"className":3335},[610],[104,3337,3339],{"className":3338,"style":2412},[614],[104,3340,3341,3344],{"style":900},[104,3342],{"className":3343,"style":714},[622],[104,3345,3347],{"className":3346},[627,628,629,630],[104,3348,3350],{"className":3349},[207,630],[104,3351,2103],{"className":3352},[207,2427,630],[104,3354,2548],{"className":3355},[207],[104,3357,3359,3362],{"className":3358},[575],[104,3360,476],{"className":3361},[575],[104,3363,3365],{"className":3364},[697],[104,3366,3368],{"className":3367},[605],[104,3369,3371],{"className":3370},[610],[104,3372,3374],{"className":3373,"style":897},[614],[104,3375,3376,3379],{"style":900},[104,3377],{"className":3378,"style":714},[622],[104,3380,3382],{"className":3381},[627,628,629,630],[104,3383,3385,3388],{"className":3384},[207,630],[104,3386,530],{"className":3387},[207,630],[104,3389,485],{"className":3390},[207,630],[104,3392,3394,3397],{"className":3393},[207],[104,3395,2548],{"className":3396},[207],[104,3398,3400],{"className":3399},[697],[104,3401,3403],{"className":3402},[605],[104,3404,3406],{"className":3405},[610],[104,3407,3409],{"className":3408,"style":2412},[614],[104,3410,3411,3414],{"style":900},[104,3412],{"className":3413,"style":714},[622],[104,3415,3417],{"className":3416},[627,628,629,630],[104,3418,3420],{"className":3419},[207,630],[104,3421,2103],{"className":3422},[207,2427,630]," tem nome próprio: ",[15,3425,3426],{},"pseudo-inversa de Moore-Penrose",", denotada ",[104,3429,3431,3450],{"className":3430},[148],[104,3432,3434],{"className":3433},[152],[154,3435,3436],{"xmlns":156},[158,3437,3438,3447],{},[161,3439,3440],{},[539,3441,3442,3444],{},[164,3443,2548],{"mathvariant":959},[168,3445,3446],{},"†",[186,3448,3449],{"encoding":188},"\\Phi^\\dagger",[104,3451,3453],{"className":3452,"ariaHidden":194},[193],[104,3454,3456,3459],{"className":3455},[198],[104,3457],{"className":3458,"style":2412},[202],[104,3460,3462,3465],{"className":3461},[207],[104,3463,2548],{"className":3464},[207],[104,3466,3468],{"className":3467},[697],[104,3469,3471],{"className":3470},[605],[104,3472,3474],{"className":3473},[610],[104,3475,3477],{"className":3476,"style":2412},[614],[104,3478,3479,3482],{"style":900},[104,3480],{"className":3481,"style":714},[622],[104,3483,3485],{"className":3484},[627,628,629,630],[104,3486,3446],{"className":3487},[239,630],". É uma generalização de \"matriz inversa\" pra matrizes que não são quadradas (o que é sempre o caso aqui: ",[104,3490,3492,3506],{"className":3491},[148],[104,3493,3495],{"className":3494},[152],[154,3496,3497],{"xmlns":156},[158,3498,3499,3503],{},[161,3500,3501],{},[164,3502,2548],{"mathvariant":959},[186,3504,3505],{"encoding":188},"\\Phi",[104,3507,3509],{"className":3508,"ariaHidden":194},[193],[104,3510,3512,3516],{"className":3511},[198],[104,3513],{"className":3514,"style":3515},[202],"height:0.6833em;",[104,3517,2548],{"className":3518},[207]," tem uma linha por paciente e uma coluna por variável, quase nunca são iguais). ",[87,3521,3522],{},"np.linalg.pinv(X)"," calcula exatamente essa conta, então a linha toda vira",[96,3525,3527],{"className":98,"code":3526,"language":100,"meta":57,"style":57},"class LinearRegressor(BaseEstimator, RegressorMixin):\n    def fit(self, X, y):\n        X = include_bias(X)\n        self.w_ = np.linalg.pinv(X) @ y\n        return self\n\n    def predict(self, X):\n        X = include_bias(X)\n        return (X @ self.w_).reshape(X.shape[0],)\n",[87,3528,3529,3533,3537,3542,3547,3551,3555,3559,3563],{"__ignoreMap":57},[104,3530,3531],{"class":106,"line":107},[104,3532,1554],{},[104,3534,3535],{"class":106,"line":58},[104,3536,1472],{},[104,3538,3539],{"class":106,"line":118},[104,3540,3541],{},"        X = include_bias(X)\n",[104,3543,3544],{"class":106,"line":124},[104,3545,3546],{},"        self.w_ = np.linalg.pinv(X) @ y\n",[104,3548,3549],{"class":106,"line":308},[104,3550,1482],{},[104,3552,3553],{"class":106,"line":417},[104,3554,300],{"emptyLinePlaceholder":63},[104,3556,3557],{"class":106,"line":422},[104,3558,1487],{},[104,3560,3561],{"class":106,"line":428},[104,3562,3541],{},[104,3564,3565],{"class":106,"line":1692},[104,3566,3567],{},"        return (X @ self.w_).reshape(X.shape[0],)\n",[11,3569,3570,3571,3575,3576,3643],{},"Uma classe inteira de regressão linear, resolvida numa linha. E o Bishop já avisa o risco: se duas colunas de entrada forem muito parecidas entre si (",[445,3572,3574],{"definition":3573},"quando duas ou mais variáveis de entrada carregam quase a mesma informação, o que deixa a matriz Φ quase singular e a solução da equação normal instável","colinearidade","), ",[104,3577,3579,3599],{"className":3578},[148],[104,3580,3582],{"className":3581},[152],[154,3583,3584],{"xmlns":156},[158,3585,3586,3596],{},[161,3587,3588,3594],{},[539,3589,3590,3592],{},[164,3591,2548],{"mathvariant":959},[164,3593,2103],{"mathvariant":2102},[164,3595,2548],{"mathvariant":959},[186,3597,3598],{"encoding":188},"\\Phi^{\\mathsf{T}}\\Phi",[104,3600,3602],{"className":3601,"ariaHidden":194},[193],[104,3603,3605,3608,3640],{"className":3604},[198],[104,3606],{"className":3607,"style":2412},[202],[104,3609,3611,3614],{"className":3610},[207],[104,3612,2548],{"className":3613},[207],[104,3615,3617],{"className":3616},[697],[104,3618,3620],{"className":3619},[605],[104,3621,3623],{"className":3622},[610],[104,3624,3626],{"className":3625,"style":2412},[614],[104,3627,3628,3631],{"style":900},[104,3629],{"className":3630,"style":714},[622],[104,3632,3634],{"className":3633},[627,628,629,630],[104,3635,3637],{"className":3636},[207,630],[104,3638,2103],{"className":3639},[207,2427,630],[104,3641,2548],{"className":3642},[207]," fica perto de singular e a conta fica numericamente instável. Isso vai voltar quando a matéria chegar em seleção de características.",[79,3645,3647],{"id":3646},"bate-com-o-scikit-learn-na-casa-decimal","Bate com o scikit-learn, na casa decimal",[11,3649,3650,3651,3653],{},"A prova de que a conta tá certa: rodei a mesma pseudo-inversa e o ",[87,3652,1443],{}," pronto do scikit-learn, lado a lado.",[96,3655,3657],{"className":98,"code":3656,"language":100,"meta":57,"style":57},"regressor = LinearRegressor()  # a classe com pinv, acima\nregressor.fit(X_train, y_train)\nprint(mean_squared_error(y_train, regressor.predict(X_train)))\n\nfrom sklearn.linear_model import LinearRegression\nsk_regressor = LinearRegression().fit(X_train, y_train)\nprint(mean_squared_error(y_train, sk_regressor.predict(X_train)))\n",[87,3658,3659,3664,3668,3673,3677,3682,3687],{"__ignoreMap":57},[104,3660,3661],{"class":106,"line":107},[104,3662,3663],{},"regressor = LinearRegressor()  # a classe com pinv, acima\n",[104,3665,3666],{"class":106,"line":58},[104,3667,1961],{},[104,3669,3670],{"class":106,"line":118},[104,3671,3672],{},"print(mean_squared_error(y_train, regressor.predict(X_train)))\n",[104,3674,3675],{"class":106,"line":124},[104,3676,300],{"emptyLinePlaceholder":63},[104,3678,3679],{"class":106,"line":308},[104,3680,3681],{},"from sklearn.linear_model import LinearRegression\n",[104,3683,3684],{"class":106,"line":417},[104,3685,3686],{},"sk_regressor = LinearRegression().fit(X_train, y_train)\n",[104,3688,3689],{"class":106,"line":422},[104,3690,3691],{},"print(mean_squared_error(y_train, sk_regressor.predict(X_train)))\n",[433,3693,3694],{},[11,3695,3696,3698,3699,3702],{},[15,3697,1632],{}," minha versão: MSE treino 2868.5497028355776. ",[87,3700,3701],{},"LinearRegression"," do scikit-learn: MSE treino 2868.549702835577.",[11,3704,3705,3706,3708],{},"A diferença aparece só na última casa decimal, ruído de ponto flutuante, não de método. ",[87,3707,1443],{}," faz exatamente essa conta por baixo do capô.",[79,3710,3712],{"id":3711},"o-zoológico-de-regressores","O zoológico de regressores",[11,3714,3715,3716,3719],{},"A aula 2c pega esse mesmo regressor da equação normal e mede o MSE também no conjunto de ",[15,3717,3718],{},"teste"," (os 20% que ficaram de fora do treino), e depois compara contra mais cinco tipos de modelo bem diferentes entre si, todos com os parâmetros padrão do scikit-learn:",[1875,3721,3722,3734],{},[1878,3723,3724],{},[1881,3725,3726,3728,3731],{},[1884,3727,341],{"align":1886},[1884,3729,3730],{"align":3190},"MSE treino",[1884,3732,3733],{"align":3190},"MSE teste",[1892,3735,3736,3752,3767,3784,3796,3809],{},[1881,3737,3738,3744,3749],{},[1897,3739,3740,3741,476],{"align":1886},"Árvore de decisão (",[87,3742,3743],{},"DecisionTreeRegressor",[1897,3745,3746],{"align":3190},[15,3747,3748],{},"0.00",[1897,3750,3751],{"align":3190},"4872.20",[1881,3753,3754,3761,3764],{},[1897,3755,3756,3757,3760],{"align":1886},"KNN (",[87,3758,3759],{},"KNeighborsRegressor",", k=5)",[1897,3762,3763],{"align":3190},"2528.59",[1897,3765,3766],{"align":3190},"3019.08",[1881,3768,3769,3776,3779],{},[1897,3770,3771,3772,3775],{"align":1886},"Floresta aleatória (",[87,3773,3774],{},"RandomForestRegressor",", profundidade 3)",[1897,3777,3778],{"align":3190},"2530.82",[1897,3780,3781],{"align":3190},[15,3782,3783],{},"2785.98",[1881,3785,3786,3791,3793],{},[1897,3787,3788,3789],{"align":1886},"Equação normal \u002F ",[87,3790,3701],{},[1897,3792,3244],{"align":3190},[1897,3794,3795],{"align":3190},"2900.19",[1881,3797,3798,3803,3806],{},[1897,3799,3800,3802],{"align":1886},[87,3801,1532],{}," (10000 iterações)",[1897,3804,3805],{"align":3190},"2950.64",[1897,3807,3808],{"align":3190},"2863.35",[1881,3810,3811,3816,3819],{},[1897,3812,3813],{"align":1886},[87,3814,3815],{},"LinearSVR",[1897,3817,3818],{"align":3190},"8224.56",[1897,3820,3821],{"align":3190},"6775.88",[11,3823,3824],{},"Eu ainda não vou explicar como cada um desses modelos funciona por dentro (KNN, árvore de decisão e floresta aleatória têm aula própria mais na frente da matéria, e é lá que eu volto neles com calma). Mas dá pra tirar três lições só olhando a tabela:",[3826,3827,3828,3839,3845],"ol",{},[360,3829,3830,3833,3834,3838],{},[15,3831,3832],{},"MSE de treino baixo não significa nada sozinho."," A árvore de decisão zerou o erro de treino (ela literalmente decorou cada paciente) e foi a pior de todas no teste. Isso é ",[445,3835,3837],{"definition":3836},"quando o modelo memoriza os dados de treino em vez de aprender o padrão geral, e por isso erra mais em dado novo","overfitting"," em estado puro, o mesmo fenômeno que o Bishop mostrou lá no capítulo 1 com o polinômio de grau 9.",[360,3840,3841,3844],{},[15,3842,3843],{},"O vencedor no teste não foi o modelo mais \"exato\" no treino."," A floresta aleatória acerta o treino quase tão bem quanto a árvore cheia (2530.82, bem perto do zero absurdo da árvore), mas sem exagerar, e por isso generaliza melhor: 2785.98 no teste, o menor MSE de teste da tabela inteira. Uma floresta é várias árvores treinadas em pedaços diferentes dos dados, com a previsão final sendo a média de todas, e essa média cancela boa parte do exagero que cada árvore individual comete.",[360,3846,3847,3852,3853,3855,3856,3858],{},[15,3848,1902,3849,3851],{},[87,3850,1532],{}," bateu a equação normal exata no teste"," (2863.35 contra 2900.19), mesmo com um MSE de treino um pouco pior. Não é coincidência: como eu já vi ",[20,3854,354],{"href":353},", o ",[87,3857,1532],{}," do scikit-learn vem com regularização L2 ligada por padrão. Aqui essa regularização, sem eu pedir, acabou ajudando a generalizar melhor.",[11,3860,1902,3861,3863],{},[87,3862,3815],{}," ficou visivelmente pior que todo o resto, treino e teste, mas isso é mais sobre os hiperparâmetros padrão dele não servirem bem pra esse dataset do que sobre o método em si, história pra outro dia.",[79,3865,1873],{"id":1872},[1875,3867,3868,3877],{},[1878,3869,3870],{},[1881,3871,3872,3874],{},[1884,3873,1887],{"align":1886},[1884,3875,3876],{"align":1886},"O que essas duas aulas assentaram",[1892,3878,3879,3887,4058],{},[1881,3880,3881,3884],{},[1897,3882,3883],{"align":1886},"Gradiente descendente encontra o ajuste ótimo iterando",[1897,3885,3886],{"align":1886},"Regressão linear tem solução fechada: a equação normal chega lá numa única conta",[1881,3888,3889,3894],{},[1897,3890,3891,3893],{"align":1886},[87,3892,1443],{}," do scikit-learn \"só funciona\"",[1897,3895,3896,3897,4057],{"align":1886},"Por baixo, ela calcula exatamente ",[104,3898,3900,3940],{"className":3899},[148],[104,3901,3903],{"className":3902},[152],[154,3904,3905],{"xmlns":156},[158,3906,3907,3937],{},[161,3908,3909,3911,3917,3919,3929,3935],{},[168,3910,470],{"stretchy":469},[539,3912,3913,3915],{},[164,3914,2548],{"mathvariant":959},[164,3916,2103],{"mathvariant":2102},[164,3918,2548],{"mathvariant":959},[539,3920,3921,3923],{},[168,3922,476],{"stretchy":469},[161,3924,3925,3927],{},[168,3926,530],{},[483,3928,485],{},[539,3930,3931,3933],{},[164,3932,2548],{"mathvariant":959},[164,3934,2103],{"mathvariant":2102},[164,3936,535],{"mathvariant":473},[186,3938,3939],{"encoding":188},"(\\Phi^{\\mathsf{T}}\\Phi)^{-1}\\Phi^{\\mathsf{T}}\\mathbf{t}",[104,3941,3943],{"className":3942,"ariaHidden":194},[193],[104,3944,3946,3949,3952,3984,3987,4022,4054],{"className":3945},[198],[104,3947],{"className":3948,"style":2393},[202],[104,3950,470],{"className":3951},[566],[104,3953,3955,3958],{"className":3954},[207],[104,3956,2548],{"className":3957},[207],[104,3959,3961],{"className":3960},[697],[104,3962,3964],{"className":3963},[605],[104,3965,3967],{"className":3966},[610],[104,3968,3970],{"className":3969,"style":2412},[614],[104,3971,3972,3975],{"style":900},[104,3973],{"className":3974,"style":714},[622],[104,3976,3978],{"className":3977},[627,628,629,630],[104,3979,3981],{"className":3980},[207,630],[104,3982,2103],{"className":3983},[207,2427,630],[104,3985,2548],{"className":3986},[207],[104,3988,3990,3993],{"className":3989},[575],[104,3991,476],{"className":3992},[575],[104,3994,3996],{"className":3995},[697],[104,3997,3999],{"className":3998},[605],[104,4000,4002],{"className":4001},[610],[104,4003,4005],{"className":4004,"style":897},[614],[104,4006,4007,4010],{"style":900},[104,4008],{"className":4009,"style":714},[622],[104,4011,4013],{"className":4012},[627,628,629,630],[104,4014,4016,4019],{"className":4015},[207,630],[104,4017,530],{"className":4018},[207,630],[104,4020,485],{"className":4021},[207,630],[104,4023,4025,4028],{"className":4024},[207],[104,4026,2548],{"className":4027},[207],[104,4029,4031],{"className":4030},[697],[104,4032,4034],{"className":4033},[605],[104,4035,4037],{"className":4036},[610],[104,4038,4040],{"className":4039,"style":2412},[614],[104,4041,4042,4045],{"style":900},[104,4043],{"className":4044,"style":714},[622],[104,4046,4048],{"className":4047},[627,628,629,630],[104,4049,4051],{"className":4050},[207,630],[104,4052,2103],{"className":4053},[207,2427,630],[104,4055,535],{"className":4056},[207,570]," via pseudo-inversa",[1881,4059,4060,4063],{},[1897,4061,4062],{"align":1886},"MSE de treino baixo é bom sinal",[1897,4064,4065],{"align":1886},"Só quando o MSE de teste concorda. Treino baixo com teste alto é a assinatura de overfitting",[79,4067,1943],{"id":1942},[11,4069,4070,4071,4073,4074,4076,4077,4079],{},"Uso o modelo da equação normal (o mesmo ",[87,4072,3171],{}," com ",[87,4075,3265],{}," de mais acima) e olho pro conjunto de ",[15,4078,3718],{},", os 89 pacientes que o modelo nunca viu durante o ajuste, pra visualizar o que aquele MSE de 2900.19 realmente significa caso a caso.",[96,4081,4083],{"className":98,"code":4082,"language":100,"meta":57,"style":57},"regressor = LinearRegressor()\nregressor.fit(X_train, y_train)\ny_pred_test = regressor.predict(X_test)\n",[87,4084,4085,4089,4093],{"__ignoreMap":57},[104,4086,4087],{"class":106,"line":107},[104,4088,1850],{},[104,4090,4091],{"class":106,"line":58},[104,4092,1961],{},[104,4094,4095],{"class":106,"line":118},[104,4096,4097],{},"y_pred_test = regressor.predict(X_test)\n",[1975,4099],{":actual":4100,":predicted":4101,"point-label":339,"x-label":4102,"y-label":4103},"[37, 42, 48, 48, 52, 52, 60, 61, 63, 63, 64, 67, 68, 69, 70, 72, 72, 72, 77, 84, 84, 87, 89, 90, 90, 90, 91, 94, 94, 95, 96, 96, 98, 99, 101, 102, 107, 108, 110, 111, 113, 118, 122, 128, 129, 135, 136, 140, 140, 151, 153, 156, 158, 164, 168, 168, 170, 171, 172, 180, 181, 184, 186, 187, 190, 200, 202, 202, 214, 219, 220, 222, 230, 232, 233, 233, 237, 242, 248, 252, 258, 263, 264, 272, 275, 281, 295, 297, 310]","[81.6, 124.4, 71.6, 48.0, 206.5, 61.5, 77.6, 146.0, 59.1, 54.4, 88.5, 130.7, 107.5, 103.5, 179.5, 109.2, 94.4, 55.9, 180.4, 119.7, 92.2, 115.0, 79.9, 51.6, 133.0, 171.0, 157.4, 94.1, 90.2, 154.6, 54.8, 108.6, 79.0, 53.5, 182.0, 105.6, 109.0, 107.7, 164.9, 123.8, 86.4, 159.4, 187.9, 70.4, 154.9, 100.1, 152.5, 174.8, 124.7, 210.7, 115.0, 165.5, 63.0, 184.5, 120.3, 155.0, 196.4, 175.6, 148.0, 173.8, 171.1, 166.3, 192.7, 140.6, 140.9, 71.7, 134.0, 144.8, 130.7, 139.5, 208.9, 199.4, 291.4, 189.0, 190.4, 206.0, 158.6, 258.2, 202.2, 168.4, 167.6, 188.3, 250.8, 181.3, 218.6, 234.2, 219.5, 206.6, 207.4]","progressão real (teste)","progressão prevista (teste)",[11,4105,4106,4107,4110],{},"Repara que a nuvem é bem parecida com ",[20,4108,4109],{"href":2020},"a do post anterior"," (que era em cima do treino), sem ficar visivelmente pior no teste. Isso confirma numericamente o que a tabela já mostrou: MSE treino 2868.55 contra MSE teste 2900.19, uma diferença pequena. O modelo não decorou o treino, ele generalizou de verdade, só que sendo um modelo linear simples, ainda erra bastante caso a caso, o mesmo teto que eu já via no post anterior.",[1985,4112,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":4114},[4115,4116,4117,4118,4120,4121,4122,4123],{"id":2025,"depth":58,"text":2026},{"id":2784,"depth":58,"text":2785},{"id":3164,"depth":58,"text":3165},{"id":3261,"depth":58,"text":4119},"A pseudo-inversa é o pinv",{"id":3646,"depth":58,"text":3647},{"id":3711,"depth":58,"text":3712},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 2b e 2c: em vez de iterar com gradiente descendente, o professor resolve a regressão linear numa única conta fechada com a pseudo-inversa, bate exatamente com o LinearRegression do scikit-learn, e compara contra mais cinco regressores diferentes. Eu explico o porquê de cada conta.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fnormal-equation",{"title":2013,"description":4124},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fnormal-equation",[4130,4131,3837],"equacao-normal","minimos-quadrados","0p6GHHgymidBOg6X__49FRr1_v2iPUMpgRvYAF4XdQI",{"id":4134,"title":4135,"body":4136,"cover":3,"date":1999,"description":5148,"extension":61,"meta":5149,"navigation":63,"order":118,"path":5150,"playlist":2003,"seo":5151,"status":66,"stem":5152,"tags":5153,"__hash__":5157},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fclassification-threshold.md","Classificação com um Limiar: Quando Regressão Vira Decisão",{"type":8,"value":4137,"toc":5136},[4138,4145,4149,4156,4165,4184,4190,4194,4205,4214,4224,4228,4237,4254,4264,4271,4295,4305,4336,4340,4343,4387,4401,4764,4768,4771,4780,4790,4812,4816,4834,4859,4874,4889,4893,4900,4920,4931,4938,4942,4958,4977,4984,4987,4994,5007,5029,5031,5071,5074,5076,5082,5102,5116,5128,5134],[11,4139,4140,4141,4144],{},"Aula 3, dividida em duas partes: classificação (3a) e normalização (3b). E o professor começa de um jeito provocador, reaproveitando a mesma classe de regressão ",[20,4142,4143],{"href":1929},"do post anterior"," pra resolver um problema que não é bem de regressão.",[79,4146,4148],{"id":4147},"o-dataset-câncer-de-mama-569-pacientes","O dataset: câncer de mama, 569 pacientes",[11,4150,4151,4152,4155],{},"O professor troca de dataset: agora é o ",[87,4153,4154],{},"load_breast_cancer"," do scikit-learn, 569 pacientes, 30 variáveis medidas a partir de imagem de biópsia (raio, textura, perímetro, área, e por aí vai), e um alvo binário, maligno ou benigno. Antes de escolher qualquer variável, ele calcula a correlação de cada uma das 30 com o alvo e ordena.",[96,4157,4159],{"className":98,"code":4158,"language":100,"meta":57,"style":57},"correlations = df.corr()['target'].drop('target')\n",[87,4160,4161],{"__ignoreMap":57},[104,4162,4163],{"class":106,"line":107},[104,4164,4158],{},[433,4166,4167],{},[11,4168,4169,4171,4172,4175,4176,4179,4180,4183],{},[15,4170,1632],{}," a mais correlacionada é ",[87,4173,4174],{},"worst concave points"," (-0.79), seguida de ",[87,4177,4178],{},"worst perimeter"," (-0.78) e ",[87,4181,4182],{},"mean concave points"," (-0.78). Faz sentido clinicamente: pontos de concavidade no contorno do tumor e o perímetro dele são exatamente o tipo de coisa que um patologista olha pra suspeitar de malignidade.",[11,4185,4186,4187,4189],{},"Ele fica com ",[87,4188,4174],{}," como a única variável de entrada por enquanto, guardando as outras 29 pra usar mais adiante.",[79,4191,4193],{"id":4192},"o-alvo-binário-invertido-de-propósito","O alvo binário, invertido de propósito",[11,4195,4196,4197,4200,4201,4204],{},"O scikit-learn já vem com uma convenção pra esse dataset: ",[87,4198,4199],{},"target=0"," é maligno, ",[87,4202,4203],{},"target=1"," é benigno. O professor inverte isso na mão:",[96,4206,4208],{"className":98,"code":4207,"language":100,"meta":57,"style":57},"y = np.array(y==0, dtype=int)  # agora 1 = maligno\n",[87,4209,4210],{"__ignoreMap":57},[104,4211,4212],{"class":106,"line":107},[104,4213,4207],{},[11,4215,4216,4217,4219,4220,4223],{},"Por quê? Porque a convenção mais comum em problema de detecção (fraude, doença, defeito) é fazer o rótulo ",[87,4218,485],{}," representar o caso que você quer ",[15,4221,4222],{},"flagar",", o evento raro e preocupante, não o caso \"normal\". Depois da troca, a fração de pacientes malignos é 0.373, ou seja, 37% dos 569 casos.",[79,4225,4227],{"id":4226},"regressão-pra-classificar","Regressão pra classificar?",[11,4229,4230,4231,4236],{},"Aqui que a aula fica provocadora. Em vez de criar um classificador do zero, o professor reaproveita a ",[15,4232,4233,4234],{},"mesma classe ",[87,4235,3171],{}," do post anterior (a que resolve por pseudo-inversa) e simplesmente treina ela em cima do alvo 0\u002F1, como se fosse um número contínuo qualquer:",[96,4238,4240],{"className":98,"code":4239,"language":100,"meta":57,"style":57},"regressor = LinearRegressor()\nregressor.fit(X_train, y_train)\ny_pred = regressor.predict(X_train)\n",[87,4241,4242,4246,4250],{"__ignoreMap":57},[104,4243,4244],{"class":106,"line":107},[104,4245,1850],{},[104,4247,4248],{"class":106,"line":58},[104,4249,1961],{},[104,4251,4252],{"class":106,"line":118},[104,4253,1966],{},[433,4255,4256],{},[11,4257,4258,134,4260,4263],{},[15,4259,1632],{},[87,4261,4262],{},"w_ = [-0.303, 5.844]",", MSE = 0.0863.",[11,4265,4266,4267,4270],{},"Um MSE de 0.0863 parece ótimo à primeira vista (é bem menor que 1), mas repara no que acontece quando o professor mede a ",[15,4268,4269],{},"acurácia"," desse resultado:",[96,4272,4274],{"className":98,"code":4273,"language":100,"meta":57,"style":57},"def accuracy(y, y_pred):\n    return np.sum(y == y_pred) \u002F len(y)\n\nprint(accuracy(y_train, y_pred))\n",[87,4275,4276,4281,4286,4290],{"__ignoreMap":57},[104,4277,4278],{"class":106,"line":107},[104,4279,4280],{},"def accuracy(y, y_pred):\n",[104,4282,4283],{"class":106,"line":58},[104,4284,4285],{},"    return np.sum(y == y_pred) \u002F len(y)\n",[104,4287,4288],{"class":106,"line":118},[104,4289,300],{"emptyLinePlaceholder":63},[104,4291,4292],{"class":106,"line":124},[104,4293,4294],{},"print(accuracy(y_train, y_pred))\n",[433,4296,4297],{},[11,4298,4299,134,4301,4304],{},[15,4300,1632],{},[87,4302,4303],{},"0.0",". Zero por cento de acerto.",[11,4306,4307,4308,4311,4312,4315,4316,4319,4320,4323,4324,4319,4326,4328,4329,4073,4332,4335],{},"Isso não é um bug, é uma pegadinha proposital. ",[87,4309,4310],{},"y_pred"," é a saída ",[15,4313,4314],{},"contínua"," da regressão, tipo ",[87,4317,4318],{},"0.312"," ou ",[87,4321,4322],{},"0.847",", nunca exatamente ",[87,4325,331],{},[87,4327,485],{},". Comparar ",[87,4330,4331],{},"y == y_pred",[87,4333,4334],{},"=="," entre um inteiro e um float quase nunca dá igual, então a acurácia calculada desse jeito sempre vai bater zero, não importa o quão bom o modelo seja. Falta uma peça: transformar aquele número contínuo numa decisão.",[79,4337,4339],{"id":4338},"o-limiar-de-número-contínuo-pra-decisão","O limiar: de número contínuo pra decisão",[11,4341,4342],{},"A correção é pequena: comparar a saída da regressão com 0.5 antes de virar rótulo.",[96,4344,4346],{"className":98,"code":4345,"language":100,"meta":57,"style":57},"class LinearClassifier(BaseEstimator, ClassifierMixin):\n    def fit(self, X, y):\n        X = include_bias(X)\n        self.w_ = np.linalg.pinv(X) @ y\n        return self\n    def predict(self, X):\n        X = include_bias(X)\n        y_pred = X @ self.w_\n        return (y_pred.reshape(X.shape[0],) > 0.5).astype(int)\n",[87,4347,4348,4353,4357,4361,4365,4369,4373,4377,4382],{"__ignoreMap":57},[104,4349,4350],{"class":106,"line":107},[104,4351,4352],{},"class LinearClassifier(BaseEstimator, ClassifierMixin):\n",[104,4354,4355],{"class":106,"line":58},[104,4356,1472],{},[104,4358,4359],{"class":106,"line":118},[104,4360,3541],{},[104,4362,4363],{"class":106,"line":124},[104,4364,3546],{},[104,4366,4367],{"class":106,"line":308},[104,4368,1482],{},[104,4370,4371],{"class":106,"line":417},[104,4372,1487],{},[104,4374,4375],{"class":106,"line":422},[104,4376,3541],{},[104,4378,4379],{"class":106,"line":428},[104,4380,4381],{},"        y_pred = X @ self.w_\n",[104,4383,4384],{"class":106,"line":1692},[104,4385,4386],{},"        return (y_pred.reshape(X.shape[0],) > 0.5).astype(int)\n",[433,4388,4389],{},[11,4390,4391,4393,4394,4397,4398,1866],{},[15,4392,1632],{}," acurácia de treino agora é ",[15,4395,4396],{},"0.9165",". No conjunto de teste, ",[15,4399,4400],{},"0.8947",[11,4402,4403,4404,4406,4407,4411,4412,4580,4581,4625,4626,4629,4630,4696,4697,4763],{},"O mesmo ajuste (o mesmo ",[87,4405,3156],{},"), só que agora com um limiar decidindo o rótulo final. É exatamente o que o Bishop chama de ",[445,4408,4410],{"definition":4409},"uma função y(x) = w·x + w0 que decide a classe olhando o sinal (ou, como aqui, se passa de um limiar): de um lado da fronteira é uma classe, do outro é a outra","função discriminante linear",": ",[104,4413,4415,4453],{"className":4414},[148],[104,4416,4418],{"className":4417},[152],[154,4419,4420],{"xmlns":156},[158,4421,4422,4450],{},[161,4423,4424,4426,4428,4430,4432,4434,4440,4442,4444],{},[164,4425,166],{},[168,4427,470],{"stretchy":469},[164,4429,178],{"mathvariant":473},[168,4431,476],{"stretchy":469},[168,4433,170],{},[539,4435,4436,4438],{},[164,4437,317],{"mathvariant":473},[164,4439,2103],{"mathvariant":2102},[164,4441,178],{"mathvariant":473},[168,4443,181],{},[515,4445,4446,4448],{},[164,4447,317],{},[483,4449,331],{},[186,4451,4452],{"encoding":188},"y(\\mathbf{x}) = \\mathbf{w}^{\\mathsf{T}}\\mathbf{x} + w_0",[104,4454,4456,4483,4534],{"className":4455,"ariaHidden":194},[193],[104,4457,4459,4462,4465,4468,4471,4474,4477,4480],{"className":4458},[198],[104,4460],{"className":4461,"style":558},[202],[104,4463,166],{"className":4464,"style":209},[207,208],[104,4466,470],{"className":4467},[566],[104,4469,178],{"className":4470},[207,570],[104,4472,476],{"className":4473},[575],[104,4475],{"className":4476,"style":214},[213],[104,4478,170],{"className":4479},[218],[104,4481],{"className":4482,"style":214},[213],[104,4484,4486,4490,4522,4525,4528,4531],{"className":4485},[198],[104,4487],{"className":4488,"style":4489},[202],"height:0.9324em;vertical-align:-0.0833em;",[104,4491,4493,4496],{"className":4492},[207],[104,4494,317],{"className":4495,"style":571},[207,570],[104,4497,4499],{"className":4498},[697],[104,4500,4502],{"className":4501},[605],[104,4503,4505],{"className":4504},[610],[104,4506,4508],{"className":4507,"style":2412},[614],[104,4509,4510,4513],{"style":900},[104,4511],{"className":4512,"style":714},[622],[104,4514,4516],{"className":4515},[627,628,629,630],[104,4517,4519],{"className":4518},[207,630],[104,4520,2103],{"className":4521},[207,2427,630],[104,4523,178],{"className":4524},[207,570],[104,4526],{"className":4527,"style":235},[213],[104,4529,181],{"className":4530},[239],[104,4532],{"className":4533,"style":235},[213],[104,4535,4537,4540],{"className":4536},[198],[104,4538],{"className":4539,"style":2826},[202],[104,4541,4543,4546],{"className":4542},[207],[104,4544,317],{"className":4545,"style":2833},[207,208],[104,4547,4549],{"className":4548},[697],[104,4550,4552,4572],{"className":4551},[605,606],[104,4553,4555,4569],{"className":4554},[610],[104,4556,4558],{"className":4557,"style":2846},[614],[104,4559,4560,4563],{"style":2849},[104,4561],{"className":4562,"style":714},[622],[104,4564,4566],{"className":4565},[627,628,629,630],[104,4567,331],{"className":4568},[207,630],[104,4570,667],{"className":4571},[666],[104,4573,4575],{"className":4574},[610],[104,4576,4578],{"className":4577,"style":807},[614],[104,4579],{},", e o ponto onde ",[104,4582,4584,4604],{"className":4583},[148],[104,4585,4587],{"className":4586},[152],[154,4588,4589],{"xmlns":156},[158,4590,4591,4601],{},[161,4592,4593,4595,4597,4599],{},[164,4594,166],{},[168,4596,470],{"stretchy":469},[164,4598,178],{"mathvariant":473},[168,4600,476],{"stretchy":469},[186,4602,4603],{"encoding":188},"y(\\mathbf{x})",[104,4605,4607],{"className":4606,"ariaHidden":194},[193],[104,4608,4610,4613,4616,4619,4622],{"className":4609},[198],[104,4611],{"className":4612,"style":558},[202],[104,4614,166],{"className":4615,"style":209},[207,208],[104,4617,470],{"className":4618},[566],[104,4620,178],{"className":4621},[207,570],[104,4623,476],{"className":4624},[575]," cruza o limiar de decisão é a ",[15,4627,4628],{},"fronteira de decisão",". No caso clássico do Bishop essa fronteira fica em ",[104,4631,4633,4657],{"className":4632},[148],[104,4634,4636],{"className":4635},[152],[154,4637,4638],{"xmlns":156},[158,4639,4640,4654],{},[161,4641,4642,4644,4646,4648,4650,4652],{},[164,4643,166],{},[168,4645,470],{"stretchy":469},[164,4647,178],{"mathvariant":473},[168,4649,476],{"stretchy":469},[168,4651,170],{},[483,4653,331],{},[186,4655,4656],{"encoding":188},"y(\\mathbf{x})=0",[104,4658,4660,4687],{"className":4659,"ariaHidden":194},[193],[104,4661,4663,4666,4669,4672,4675,4678,4681,4684],{"className":4662},[198],[104,4664],{"className":4665,"style":558},[202],[104,4667,166],{"className":4668,"style":209},[207,208],[104,4670,470],{"className":4671},[566],[104,4673,178],{"className":4674},[207,570],[104,4676,476],{"className":4677},[575],[104,4679],{"className":4680,"style":214},[213],[104,4682,170],{"className":4683},[218],[104,4685],{"className":4686,"style":214},[213],[104,4688,4690,4693],{"className":4689},[198],[104,4691],{"className":4692,"style":2975},[202],[104,4694,331],{"className":4695},[207],", aqui fica em ",[104,4698,4700,4724],{"className":4699},[148],[104,4701,4703],{"className":4702},[152],[154,4704,4705],{"xmlns":156},[158,4706,4707,4721],{},[161,4708,4709,4711,4713,4715,4717,4719],{},[164,4710,166],{},[168,4712,470],{"stretchy":469},[164,4714,178],{"mathvariant":473},[168,4716,476],{"stretchy":469},[168,4718,170],{},[483,4720,1776],{},[186,4722,4723],{"encoding":188},"y(\\mathbf{x})=0.5",[104,4725,4727,4754],{"className":4726,"ariaHidden":194},[193],[104,4728,4730,4733,4736,4739,4742,4745,4748,4751],{"className":4729},[198],[104,4731],{"className":4732,"style":558},[202],[104,4734,166],{"className":4735,"style":209},[207,208],[104,4737,470],{"className":4738},[566],[104,4740,178],{"className":4741},[207,570],[104,4743,476],{"className":4744},[575],[104,4746],{"className":4747,"style":214},[213],[104,4749,170],{"className":4750},[218],[104,4752],{"className":4753,"style":214},[213],[104,4755,4757,4760],{"className":4756},[198],[104,4758],{"className":4759,"style":2975},[202],[104,4761,1776],{"className":4762},[207]," porque o alvo tá codificado como 0\u002F1 em vez de centralizado em zero, mas a ideia geométrica é idêntica: dos dois lados de uma linha, a decisão muda.",[79,4765,4767],{"id":4766},"uma-identidade-elegante","Uma identidade elegante",[11,4769,4770],{},"O professor soma acurácia com MSE (ambos calculados sobre as previsões já limiarizadas, 0 ou 1):",[96,4772,4774],{"className":98,"code":4773,"language":100,"meta":57,"style":57},"accuracy(y_train, y_pred) + mean_squared_error(y_train, y_pred)\n",[87,4775,4776],{"__ignoreMap":57},[104,4777,4778],{"class":106,"line":107},[104,4779,4773],{},[433,4781,4782],{},[11,4783,4784,134,4786,4789],{},[15,4785,1632],{},[87,4787,4788],{},"1.0",", exatamente.",[11,4791,4792,4793,1502,4795,4797,4798,4801,4802,4804,4805,4807,4808,4811],{},"Não é coincidência. Quando ",[87,4794,166],{},[87,4796,4310],{}," são só 0 ou 1, o erro quadrático ",[87,4799,4800],{},"(y - y_pred)²"," vale ",[87,4803,331],{}," quando acerta e ",[87,4806,485],{}," quando erra, exatamente igual ao erro absoluto. Então o MSE médio é literalmente a ",[15,4809,4810],{},"taxa de erro",", e taxa de erro mais taxa de acerto sempre soma 1. Uma identidade boba de algebrismo, mas que mostra uma coisa importante: MSE sobre rótulo binário limiarizado é só outro nome pra \"fração de erro\".",[79,4813,4815],{"id":4814},"o-ajuste-ótimo-pro-erro-contínuo-não-é-o-ajuste-ótimo-pra-classificar","O ajuste ótimo pro erro contínuo não é o ajuste ótimo pra classificar",[11,4817,4818,4819,4821,4822,4825,4826,4829,4830,4833],{},"Aqui mora o ponto mais sutil da aula. O professor pega o ",[87,4820,3156],{}," que a pseudo-inversa encontrou (o que minimiza o erro quadrático no alvo 0\u002F1 ",[15,4823,4824],{},"contínuo",") e varre valores próximos do coeficiente ",[87,4827,4828],{},"w_[1]",", medindo o MSE ",[15,4831,4832],{},"depois de limiarizar"," em cada um:",[96,4835,4837],{"className":98,"code":4836,"language":100,"meta":57,"style":57},"w1_values = np.linspace(original_w[1] - 3, original_w[1] + 3, 100)\nfor w1_candidate in w1_values:\n    classifier.w_ = np.array([original_w[0], w1_candidate])\n    mse_values.append(mean_squared_error(y_train, classifier.predict(X_train)))\n",[87,4838,4839,4844,4849,4854],{"__ignoreMap":57},[104,4840,4841],{"class":106,"line":107},[104,4842,4843],{},"w1_values = np.linspace(original_w[1] - 3, original_w[1] + 3, 100)\n",[104,4845,4846],{"class":106,"line":58},[104,4847,4848],{},"for w1_candidate in w1_values:\n",[104,4850,4851],{"class":106,"line":118},[104,4852,4853],{},"    classifier.w_ = np.array([original_w[0], w1_candidate])\n",[104,4855,4856],{"class":106,"line":124},[104,4857,4858],{},"    mse_values.append(mean_squared_error(y_train, classifier.predict(X_train)))\n",[433,4860,4861],{},[11,4862,4863,4865,4866,4869,4870,4873],{},[15,4864,1632],{}," o menor MSE encontrado (0.0791) acontece em ",[87,4867,4868],{},"w_[1] = 5.5709",", não no ",[87,4871,4872],{},"w_[1] = 5.8436"," que a pseudo-inversa devolveu.",[11,4875,4876,4877,4880,4881,4884,4885,4888],{},"O ajuste que minimiza o erro quadrático ",[15,4878,4879],{},"antes"," de limiarizar não é o mesmo ajuste que minimiza o erro ",[15,4882,4883],{},"depois"," de limiarizar. São dois objetivos parecidos, mas não idênticos. O Bishop explica o motivo: mínimos quadrados corresponde a assumir que o ruído nos dados segue uma distribuição gaussiana (capítulo 3), o que faz todo sentido pra prever um número contínuo. Mas um rótulo binário não tem ruído gaussiano, e por isso mínimos quadrados aplicado direto em classificação sofre de um problema específico: pontos que já estão \"certos demais\", bem longe da fronteira, do lado certo, ainda ",[15,4886,4887],{},"puxam o ajuste",", porque contribuem erro quadrático mesmo sem precisar. O Bishop mostra isso com um exemplo onde adicionar pontos extras, todos do lado certo da fronteira, muda a fronteira de decisão pra pior, coisa que não acontece com um método pensado direito pra classificação (regressão logística, que a matéria ainda não chegou, mas que existe exatamente pra resolver essa mancada).",[79,4890,4892],{"id":4891},"todas-as-30-variáveis","Todas as 30 variáveis",[11,4894,4895,4896,4899],{},"Com o mesmo ",[87,4897,4898],{},"LinearClassifier",", agora treinado nas 30 colunas em vez de 1:",[96,4901,4903],{"className":98,"code":4902,"language":100,"meta":57,"style":57},"modelo = LinearClassifier()\nmodelo.fit(X_train, y_train)\nprint(accuracy_score(y_test, modelo.predict(X_test)))\n",[87,4904,4905,4910,4915],{"__ignoreMap":57},[104,4906,4907],{"class":106,"line":107},[104,4908,4909],{},"modelo = LinearClassifier()\n",[104,4911,4912],{"class":106,"line":58},[104,4913,4914],{},"modelo.fit(X_train, y_train)\n",[104,4916,4917],{"class":106,"line":118},[104,4918,4919],{},"print(accuracy_score(y_test, modelo.predict(X_test)))\n",[433,4921,4922],{},[11,4923,4924,4926,4927,4930],{},[15,4925,1632],{}," acurácia de teste ",[15,4928,4929],{},"0.9561",", contra 0.8947 usando só uma variável.",[11,4932,4933,4934,4937],{},"O mesmo salto que eu já vi ",[20,4935,4936],{"href":2020},"nos dois posts anteriores",": mais informação, sem mudar o algoritmo, sem mudar o limiar, e o resultado melhora bastante.",[79,4939,4941],{"id":4940},"normalizando-mas-por-um-motivo-diferente","Normalizando, mas por um motivo diferente",[11,4943,4944,4945,4948,4949,4952,4953,4957],{},"A segunda metade da aula (",[87,4946,4947],{},"aula03b",") muda de assunto: em vez de regressão limiarizada, o professor usa o ",[87,4950,4951],{},"KNeighborsClassifier"," do scikit-learn (",[445,4954,4956],{"definition":4955},"classifica um ponto novo olhando os K vizinhos mais parecidos (mais próximos) no conjunto de treino e votando pela classe mais comum entre eles, com aula própria mais na frente da matéria","KNN",", K vizinhos mais próximos, tema de uma aula só sua mais na frente, aqui ele só aparece de relance) direto no dataset de câncer, sem normalizar nada:",[96,4959,4961],{"className":98,"code":4960,"language":100,"meta":57,"style":57},"modelo = KNeighborsClassifier()\nmodelo.fit(X_train, y_train)\nmodelo.score(X_test, y_test)\n",[87,4962,4963,4968,4972],{"__ignoreMap":57},[104,4964,4965],{"class":106,"line":107},[104,4966,4967],{},"modelo = KNeighborsClassifier()\n",[104,4969,4970],{"class":106,"line":58},[104,4971,4914],{},[104,4973,4974],{"class":106,"line":118},[104,4975,4976],{},"modelo.score(X_test, y_test)\n",[433,4978,4979],{},[11,4980,4981,4983],{},[15,4982,1632],{}," 0.9298.",[11,4985,4986],{},"Depois ele normaliza (min-max, na faixa 0 a 1, calculado só com estatística do treino e aplicado igual no teste) e repete:",[433,4988,4989],{},[11,4990,4991,4993],{},[15,4992,1632],{}," 0.9649.",[11,4995,4996,4997,5001,5002,5006],{},"E confere que padronizar (",[445,4998,5000],{"definition":4999},"subtrair a média e dividir pelo desvio padrão, deixando cada variável centrada em zero com espalhamento 1","z-score",", a mesma normalização ",[20,5003,5005],{"href":5004},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Fw2-lab03-feature-scaling","da playlist da especialização",") dá o mesmo resultado: 0.9649 também.",[11,5008,5009,5010,5013,5014,5016,5017,5020,5021,5024,5025,5028],{},"Eu já vi normalização importar antes, mas por um motivo diferente: ",[20,5011,5012],{"href":5004},"lá na playlist da especialização",", normalizar ajudava o ",[15,5015,1752],{}," a convergir mais rápido, porque a tigela de custo ficava menos alongada. Aqui o motivo é outro. KNN decide a classe olhando a ",[15,5018,5019],{},"distância"," entre pacientes, geralmente distância euclidiana, a raiz da soma dos quadrados das diferenças em cada variável. Se uma variável (tipo ",[87,5022,5023],{},"mean area",", que varia de 143 até 2501) tem uma escala centenas de vezes maior que outra (tipo ",[87,5026,5027],{},"mean smoothness",", que varia de 0.05 até 0.16), a distância acaba sendo decidida quase inteiramente pela variável de escala grande, as outras 29 praticamente não pesam na conta. Normalizar bota todo mundo na mesma régua antes de medir distância, e de repente as 30 variáveis contribuem de verdade, não só uma.",[79,5030,1873],{"id":1872},[1875,5032,5033,5041],{},[1878,5034,5035],{},[1881,5036,5037,5039],{},[1884,5038,1887],{"align":1886},[1884,5040,1890],{"align":1886},[1892,5042,5043,5055,5063],{},[1881,5044,5045,5048],{},[1897,5046,5047],{"align":1886},"Regressão ajusta um número contínuo",[1897,5049,5050,5051,5054],{"align":1886},"Threshold (um ",[87,5052,5053],{},"> 0.5",") transforma esse número numa decisão binária, virando um classificador",[1881,5056,5057,5060],{},[1897,5058,5059],{"align":1886},"MSE e acurácia medem coisas diferentes",[1897,5061,5062],{"align":1886},"Pra rótulo binário limiarizado, elas são literalmente complementares: acurácia + MSE = 1",[1881,5064,5065,5068],{},[1897,5066,5067],{"align":1886},"Normalizar ajuda o gradiente descendente a convergir",[1897,5069,5070],{"align":1886},"Normalizar também ajuda qualquer método baseado em distância (como o KNN) a não deixar uma variável de escala grande dominar sozinha",[11,5072,5073],{},"E ficou um gancho puro pra próxima aula, que ainda não chegou nessa playlist: mínimos quadrados aplicado em classificação tem um viés estrutural (o Bishop mostrou o porquê), e o jeito certo de resolver isso é trocar a função de erro por uma pensada pra classificação, regressão logística.",[79,5075,1943],{"id":1942},[11,5077,5078,5079,5081],{},"Repito o classificador de uma variável (",[87,5080,4174],{},") e visualizo, paciente por paciente no conjunto de teste, onde ele acerta e onde erra, junto com a fronteira de decisão real.",[96,5083,5085],{"className":98,"code":5084,"language":100,"meta":57,"style":57},"classifier = LinearClassifier()\nclassifier.fit(X_train, y_train)  # 1 variável: worst concave points\ny_pred_test = classifier.predict(X_test)\n",[87,5086,5087,5092,5097],{"__ignoreMap":57},[104,5088,5089],{"class":106,"line":107},[104,5090,5091],{},"classifier = LinearClassifier()\n",[104,5093,5094],{"class":106,"line":58},[104,5095,5096],{},"classifier.fit(X_train, y_train)  # 1 variável: worst concave points\n",[104,5098,5099],{"class":106,"line":118},[104,5100,5101],{},"y_pred_test = classifier.predict(X_test)\n",[11,5103,5104,5105,5107,5108,5111,5112,5115],{},"Refiz esse ajuste com ",[87,5106,1177],{}," (o notebook original não fixa a semente, então os números variam a cada execução, aqui uso um treino\u002Fteste fixo pra poder mostrar os pacientes exatos) e obtive ",[87,5109,5110],{},"w_ = [-0.296, 5.841]",", o que coloca a fronteira de decisão em ",[87,5113,5114],{},"worst concave points ≈ 0.136",": abaixo disso o modelo prevê benigno, acima, maligno.",[5117,5118],"classification-fit-scatter",{":actual":5119,":correct":5120,":threshold":5121,":x":5122,"class0-label":5123,"class1-label":5124,"correct-label":5125,"threshold-label":5126,"wrong-label":5127,"x-label":4174},"[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]","[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]","0.1362","[0.0, 0.0, 0.0, 0.0, 0.0223, 0.032, 0.0341, 0.0431, 0.0459, 0.0481, 0.0533, 0.0556, 0.0558, 0.0622, 0.0627, 0.0641, 0.0737, 0.0763, 0.0783, 0.0791, 0.0796, 0.0828, 0.0831, 0.0875, 0.0895, 0.0917, 0.0961, 0.0998, 0.101, 0.1015, 0.1015, 0.1017, 0.1045, 0.1047, 0.1092, 0.1105, 0.1136, 0.118, 0.1225, 0.1312, 0.1427, 0.1456, 0.1474, 0.1659, 0.1673, 0.1739, 0.1789, 0.1841, 0.1848, 0.1932, 0.1974, 0.2013, 0.2148, 0.221, 0.2264, 0.2422, 0.243, 0.2508, 0.265, 0.2867]","Benigno","Maligno","Acertou","Fronteira de decisão","Errou",[11,5129,5130,5131,5133],{},"Os triângulos vermelhos (os erros) se concentram bem perto da fronteira pontilhada, dos dois lados, exatamente onde eu esperaria: são os casos ambíguos, onde ",[87,5132,4174],{}," sozinho não separa bem maligno de benigno. Longe da fronteira, dos dois lados, o modelo acerta quase sempre. Faz sentido: uma variável só carrega bastante sinal (ela tinha 0.79 de correlação, afinal), mas não é suficiente pra nunca errar, e é exatamente por isso que as 30 variáveis juntas (0.9561) batem a variável sozinha (0.8947).",[1985,5135,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":5137},[5138,5139,5140,5141,5142,5143,5144,5145,5146,5147],{"id":4147,"depth":58,"text":4148},{"id":4192,"depth":58,"text":4193},{"id":4226,"depth":58,"text":4227},{"id":4338,"depth":58,"text":4339},{"id":4766,"depth":58,"text":4767},{"id":4814,"depth":58,"text":4815},{"id":4891,"depth":58,"text":4892},{"id":4940,"depth":58,"text":4941},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 3a e 3b: o professor reaproveita a mesma classe de regressão linear pra classificar câncer de mama, mostra por que faltava um limiar, e depois normaliza os dados por um motivo diferente de tudo que eu já tinha visto.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fclassification-threshold",{"title":4135,"description":5148},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fclassification-threshold",[5154,5155,5156],"classificacao","normalizacao","knn","zexb9kLmS_F0d2-k22F7rLKZW98HgfwPVOA1qHE5A_M",{"id":5159,"title":5160,"body":5161,"cover":3,"date":1999,"description":5802,"extension":61,"meta":5803,"navigation":63,"order":124,"path":5804,"playlist":2003,"seo":5805,"status":66,"stem":5806,"tags":5807,"__hash__":5809},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fknn-classifier.md","KNN: Classificar Olhando pros Vizinhos",{"type":8,"value":5162,"toc":5793},[5163,5166,5170,5185,5210,5214,5220,5300,5311,5322,5555,5559,5567,5579,5582,5586,5589,5634,5641,5648,5652,5666,5668,5707,5709,5712,5727,5777,5791],[11,5164,5165],{},"Aula 4a: KNN, o modelo mais \"sem modelo\" que existe. Não tem coeficiente pra ajustar, não tem gradiente, não tem equação normal. É só uma régua.",[79,5167,5169],{"id":5168},"o-dataset-vinhos-3-castas","O dataset: vinhos, 3 castas",[11,5171,5172,5173,5176,5177,5180,5181,5184],{},"Dataset novo: ",[87,5174,5175],{},"load_wine",", 178 garrafas, 13 medidas químicas (teor alcoólico, acidez, magnésio, e por aí vai) e 3 classes, a casta da uva. O professor escolhe duas colunas pra poder plotar num plano: ",[87,5178,5179],{},"flavanoids"," (índice 6) e ",[87,5182,5183],{},"color_intensity"," (índice 9).",[96,5186,5188],{"className":98,"code":5187,"language":100,"meta":57,"style":57},"from sklearn.datasets import load_wine\nX, y = load_wine(return_X_y=True)\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)\nfeats = [6, 9]\n",[87,5189,5190,5195,5200,5205],{"__ignoreMap":57},[104,5191,5192],{"class":106,"line":107},[104,5193,5194],{},"from sklearn.datasets import load_wine\n",[104,5196,5197],{"class":106,"line":58},[104,5198,5199],{},"X, y = load_wine(return_X_y=True)\n",[104,5201,5202],{"class":106,"line":118},[104,5203,5204],{},"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)\n",[104,5206,5207],{"class":106,"line":124},[104,5208,5209],{},"feats = [6, 9]\n",[79,5211,5213],{"id":5212},"a-ideia-não-existe-treino-só-memória","A ideia: não existe treino, só memória",[11,5215,5216,5217,5219],{},"Todo modelo que eu já vi até aqui (regressão, o classificador limiarizado) ajusta parâmetros: acha um ",[87,5218,317],{}," que minimiza algum erro. KNN não faz nada disso. O \"treino\" dele é literalmente guardar os dados:",[96,5221,5223],{"className":98,"code":5222,"language":100,"meta":57,"style":57},"class K1NN(BaseEstimator, ClassifierMixin):\n    def fit(self, X, y):\n        self.X = X\n        self.y = y\n        return self\n\n    def distance_(self, x):\n        return np.sum((self.X - x)**2, axis=1)**0.5\n\n    def predict(self, X):\n        y_pred = np.empty((X.shape[0],))\n        for i, x in enumerate(X):\n            distances = self.distance_(x)\n            min_idx = np.argmin(distances)\n            y_pred[i] = self.y[min_idx]\n        return y_pred\n",[87,5224,5225,5230,5234,5239,5244,5248,5252,5257,5262,5266,5270,5275,5280,5285,5290,5295],{"__ignoreMap":57},[104,5226,5227],{"class":106,"line":107},[104,5228,5229],{},"class K1NN(BaseEstimator, ClassifierMixin):\n",[104,5231,5232],{"class":106,"line":58},[104,5233,1472],{},[104,5235,5236],{"class":106,"line":118},[104,5237,5238],{},"        self.X = X\n",[104,5240,5241],{"class":106,"line":124},[104,5242,5243],{},"        self.y = y\n",[104,5245,5246],{"class":106,"line":308},[104,5247,1482],{},[104,5249,5250],{"class":106,"line":417},[104,5251,300],{"emptyLinePlaceholder":63},[104,5253,5254],{"class":106,"line":422},[104,5255,5256],{},"    def distance_(self, x):\n",[104,5258,5259],{"class":106,"line":428},[104,5260,5261],{},"        return np.sum((self.X - x)**2, axis=1)**0.5\n",[104,5263,5264],{"class":106,"line":1692},[104,5265,300],{"emptyLinePlaceholder":63},[104,5267,5268],{"class":106,"line":1698},[104,5269,1487],{},[104,5271,5272],{"class":106,"line":1704},[104,5273,5274],{},"        y_pred = np.empty((X.shape[0],))\n",[104,5276,5277],{"class":106,"line":1710},[104,5278,5279],{},"        for i, x in enumerate(X):\n",[104,5281,5282],{"class":106,"line":1716},[104,5283,5284],{},"            distances = self.distance_(x)\n",[104,5286,5287],{"class":106,"line":1722},[104,5288,5289],{},"            min_idx = np.argmin(distances)\n",[104,5291,5292],{"class":106,"line":1727},[104,5293,5294],{},"            y_pred[i] = self.y[min_idx]\n",[104,5296,5297],{"class":106,"line":1732},[104,5298,5299],{},"        return y_pred\n",[11,5301,5302,5303,5306,5307,5310],{},"Pra classificar um vinho novo, ele calcula a distância euclidiana até ",[15,5304,5305],{},"todo"," vinho do treino, acha o mais parecido, e copia a classe dele. Chama isso de ",[15,5308,5309],{},"K1NN"," porque olha só pro vizinho mais próximo, K=1.",[433,5312,5313],{},[11,5314,5315,5317,5318,5321],{},[15,5316,1632],{}," acurácia de 0.97 no teste. Idêntica ao ",[87,5319,5320],{},"KNeighborsClassifier(n_neighbors=1)"," do scikit-learn, rodado nos mesmos dados.",[11,5323,5324,5325,5355,5356,5440,5441,5469,5470,5498,5499,5502,5503,5554],{},"O Bishop trata isso como um caso particular de um resultado mais geral (capítulo 2.5.2): se você desenha uma esfera em volta de um ponto novo até ela conter exatamente ",[104,5326,5328,5342],{"className":5327},[148],[104,5329,5331],{"className":5330},[152],[154,5332,5333],{"xmlns":156},[158,5334,5335,5340],{},[161,5336,5337],{},[164,5338,5339],{},"K",[186,5341,5339],{"encoding":188},[104,5343,5345],{"className":5344,"ariaHidden":194},[193],[104,5346,5348,5351],{"className":5347},[198],[104,5349],{"className":5350,"style":3515},[202],[104,5352,5339],{"className":5353,"style":5354},[207,208],"margin-right:0.0715em;"," vizinhos, e olha qual classe é maioria entre eles, a probabilidade posterior de cada classe é simplesmente ",[104,5357,5359,5382],{"className":5358},[148],[104,5360,5362],{"className":5361},[152],[154,5363,5364],{"xmlns":156},[158,5365,5366,5379],{},[161,5367,5368,5375,5377],{},[515,5369,5370,5372],{},[164,5371,5339],{},[164,5373,5374],{},"k",[164,5376,318],{"mathvariant":959},[164,5378,5339],{},[186,5380,5381],{"encoding":188},"K_k\u002FK",[104,5383,5385],{"className":5384,"ariaHidden":194},[193],[104,5386,5388,5391,5434,5437],{"className":5387},[198],[104,5389],{"className":5390,"style":558},[202],[104,5392,5394,5397],{"className":5393},[207],[104,5395,5339],{"className":5396,"style":5354},[207,208],[104,5398,5400],{"className":5399},[697],[104,5401,5403,5426],{"className":5402},[605,606],[104,5404,5406,5423],{"className":5405},[610],[104,5407,5410],{"className":5408,"style":5409},[614],"height:0.3361em;",[104,5411,5413,5416],{"style":5412},"top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;",[104,5414],{"className":5415,"style":714},[622],[104,5417,5419],{"className":5418},[627,628,629,630],[104,5420,5374],{"className":5421,"style":5422},[207,208,630],"margin-right:0.0315em;",[104,5424,667],{"className":5425},[666],[104,5427,5429],{"className":5428},[610],[104,5430,5432],{"className":5431,"style":807},[614],[104,5433],{},[104,5435,318],{"className":5436},[207],[104,5438,5339],{"className":5439,"style":5354},[207,208]," (a fração dos ",[104,5442,5444,5457],{"className":5443},[148],[104,5445,5447],{"className":5446},[152],[154,5448,5449],{"xmlns":156},[158,5450,5451,5455],{},[161,5452,5453],{},[164,5454,5339],{},[186,5456,5339],{"encoding":188},[104,5458,5460],{"className":5459,"ariaHidden":194},[193],[104,5461,5463,5466],{"className":5462},[198],[104,5464],{"className":5465,"style":3515},[202],[104,5467,5339],{"className":5468,"style":5354},[207,208]," vizinhos que pertence àquela classe). Classificar pela classe mais comum entre os ",[104,5471,5473,5486],{"className":5472},[148],[104,5474,5476],{"className":5475},[152],[154,5477,5478],{"xmlns":156},[158,5479,5480,5484],{},[161,5481,5482],{},[164,5483,5339],{},[186,5485,5339],{"encoding":188},[104,5487,5489],{"className":5488,"ariaHidden":194},[193],[104,5490,5492,5495],{"className":5491},[198],[104,5493],{"className":5494,"style":3515},[202],[104,5496,5339],{"className":5497,"style":5354},[207,208]," vizinhos mais próximos ",[15,5500,5501],{},"é"," aplicar Bayes nesse resultado. ",[104,5504,5506,5524],{"className":5505},[148],[104,5507,5509],{"className":5508},[152],[154,5510,5511],{"xmlns":156},[158,5512,5513,5521],{},[161,5514,5515,5517,5519],{},[164,5516,5339],{},[168,5518,170],{},[483,5520,485],{},[186,5522,5523],{"encoding":188},"K=1",[104,5525,5527,5545],{"className":5526,"ariaHidden":194},[193],[104,5528,5530,5533,5536,5539,5542],{"className":5529},[198],[104,5531],{"className":5532,"style":3515},[202],[104,5534,5339],{"className":5535,"style":5354},[207,208],[104,5537],{"className":5538,"style":214},[213],[104,5540,170],{"className":5541},[218],[104,5543],{"className":5544,"style":214},[213],[104,5546,5548,5551],{"className":5547},[198],[104,5549],{"className":5550,"style":2975},[202],[104,5552,485],{"className":5553},[207]," é só o caso mais extremo: a \"esfera\" cresce até tocar um único ponto, e você copia a classe dele sem votação nenhuma.",[79,5556,5558],{"id":5557},"interativo-mexendo-no-k-de-verdade","Interativo: mexendo no K de verdade",[11,5560,5561,5562,1502,5564,5566],{},"Em vez de rodar célula por célula pra cada K como o notebook faz, dá pra ver a fronteira de decisão mudando ao vivo. Isso é reconstrução minha em cima dos 142 vinhos reais do treino (as mesmas duas colunas, ",[87,5563,5179],{},[87,5565,5183],{},"), rodando o mesmo algoritmo de voto por maioria:",[5568,5569],"knn-decision-explorer",{":classes":5570,":initial-k":485,":x-max":5571,":x-min":331,":x-train":5572,":y-max":5573,":y-min":5574,":y-train":5575,"class0-label":5576,"class1-label":5577,"class2-label":5578,"x-label":5179,"y-label":5183},"[2, 2, 1, 2, 0, 1, 1, 1, 2, 0, 1, 1, 2, 0, 1, 0, 0, 2, 2, 1, 1, 0, 1, 0, 2, 1, 1, 2, 0, 0, 0, 2, 0, 0, 1, 2, 1, 0, 2, 1, 0, 2, 1, 1, 0, 1, 0, 0, 1, 0, 0, 2, 1, 1, 1, 0, 1, 1, 1, 2, 2, 0, 1, 2, 2, 1, 1, 0, 1, 2, 2, 1, 2, 1, 1, 1, 0, 0, 2, 0, 2, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 2, 1, 1, 1, 2, 2, 1, 0, 0, 1, 2, 2, 0, 1, 2, 2, 2, 2, 1, 0, 1, 0, 2, 0, 0, 1, 0, 0, 2, 1, 0, 2, 2, 0, 0, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 0, 1, 1]","5.3","[1.31, 0.6, 3.75, 1.1, 2.64, 2.04, 1.94, 1.3, 0.8, 3.24, 2.17, 2.13, 0.58, 2.99, 2.03, 3.0, 2.94, 0.83, 0.48, 1.92, 1.85, 2.68, 2.25, 2.88, 0.34, 2.5, 1.28, 0.66, 2.43, 2.52, 2.19, 0.78, 2.69, 2.69, 2.27, 0.49, 1.57, 3.32, 0.65, 2.65, 3.06, 0.52, 3.15, 1.75, 3.0, 0.99, 2.97, 3.29, 2.24, 2.68, 2.37, 1.09, 2.53, 2.55, 2.21, 3.04, 1.36, 1.69, 2.79, 0.61, 0.6, 3.39, 2.26, 0.66, 0.92, 1.41, 2.89, 2.74, 2.65, 1.2, 0.6, 1.79, 0.68, 2.03, 2.17, 1.84, 2.79, 3.39, 0.92, 3.49, 0.47, 3.54, 2.98, 2.26, 1.32, 2.53, 3.17, 2.51, 3.03, 2.63, 1.02, 0.47, 2.58, 1.25, 1.59, 0.83, 0.56, 1.59, 2.98, 3.69, 0.57, 1.28, 1.22, 3.4, 1.84, 0.63, 0.58, 0.76, 0.7, 3.1, 2.9, 1.76, 3.27, 0.69, 3.67, 2.92, 1.69, 2.41, 3.23, 0.7, 1.75, 2.43, 0.58, 0.84, 2.76, 3.74, 1.39, 1.25, 1.36, 1.64, 2.99, 1.6, 1.61, 2.14, 5.08, 0.76, 3.17, 2.86, 2.03, 3.64, 1.46, 2.11]","13.2","1.5","[13.0, 5.0, 4.5, 11.75, 3.7, 2.7, 2.62, 3.17, 4.4, 5.68, 3.3, 2.08, 7.6, 5.6, 4.6, 6.38, 4.8, 10.52, 5.7, 2.94, 3.4, 4.6, 2.15, 3.8, 4.9, 2.9, 2.85, 10.26, 5.0, 5.25, 3.95, 8.21, 4.32, 3.84, 2.6, 5.5, 3.8, 5.75, 7.65, 2.6, 5.64, 4.35, 3.94, 2.95, 5.04, 2.5, 4.5, 6.13, 3.0, 3.58, 3.93, 5.7, 3.9, 2.57, 3.05, 5.1, 2.45, 2.45, 3.25, 7.7, 5.58, 6.1, 3.25, 7.1, 10.68, 5.75, 3.35, 5.4, 2.76, 5.0, 4.92, 3.8, 9.3, 3.8, 1.9, 2.7, 6.3, 6.75, 7.65, 7.8, 6.62, 8.9, 5.1, 2.5, 2.65, 4.2, 4.9, 5.05, 2.8, 4.36, 3.05, 3.85, 2.9, 3.6, 4.8, 9.01, 5.88, 1.74, 5.2, 5.4, 1.95, 7.1, 5.4, 6.6, 3.74, 7.9, 5.45, 8.42, 5.28, 4.45, 5.85, 3.3, 7.2, 10.2, 6.8, 6.2, 2.8, 4.5, 6.0, 9.7, 2.6, 4.25, 7.5, 8.66, 4.38, 7.05, 9.4, 4.1, 10.8, 2.06, 2.3, 1.95, 2.6, 3.21, 6.0, 9.2, 5.65, 3.38, 3.4, 7.5, 3.05, 2.8]","Casta 0","Casta 1","Casta 2",[11,5580,5581],{},"Clica em cada valor de K e repara: com K=1, as regiões coloridas têm um monte de ilhazinhas isoladas, cada uma abraçando um único ponto de treino, a fronteira fica toda picotada. Com K maior, as ilhas somem e as regiões viram blocos mais lisos e contínuos.",[79,5583,5585],{"id":5584},"k-é-um-parâmetro-de-suavização-não-quanto-maior-melhor","K é um parâmetro de suavização, não \"quanto maior melhor\"",[11,5587,5588],{},"O professor testa K=1, 3, 5, 13 nos dados de teste de verdade:",[1875,5590,5591,5601],{},[1878,5592,5593],{},[1881,5594,5595,5598],{},[1884,5596,5339],{"align":5597},"center",[1884,5599,5600],{"align":3190},"Acurácia",[1892,5602,5603,5612,5620,5626],{},[1881,5604,5605,5607],{},[1897,5606,485],{"align":5597},[1897,5608,5609],{"align":3190},[15,5610,5611],{},"0.97",[1881,5613,5614,5617],{},[1897,5615,5616],{"align":5597},"3",[1897,5618,5619],{"align":3190},"0.92",[1881,5621,5622,5624],{},[1897,5623,332],{"align":5597},[1897,5625,5619],{"align":3190},[1881,5627,5628,5631],{},[1897,5629,5630],{"align":5597},"13",[1897,5632,5633],{"align":3190},"0.89",[11,5635,5636,5637,5640],{},"Contra-intuitivo à primeira vista: eu esperaria que olhar mais vizinhos desse um resultado mais \"confiável\", mas aqui a acurácia só piora conforme K cresce. O Bishop chama K exatamente disso, um ",[15,5638,5639],{},"parâmetro de suavização",": K pequeno deixa a fronteira de decisão bem apertada em volta dos dados de treino (baixo viés, mas sensível a cada ponto individual, inclusive ruído), enquanto K grande borra a fronteira, misturando vizinhos de regiões diferentes na votação (mais viés, menos sensível a ruído). Nesse dataset específico, com só 142 pontos de treino espalhados em 3 classes, aumentar K começa a puxar vizinhos de outra casta pra dentro do voto rápido demais, então o ponto ótimo aqui está bem perto de K=1.",[11,5642,5643,5644,1866],{},"Isso não quer dizer \"sempre use K=1\". O Bishop cita um resultado interessante: no limite de infinitos dados de treino, o classificador de vizinho mais próximo (K=1) nunca comete mais que o dobro do erro do classificador ótimo teoricamente possível, uma garantia surpreendentemente boa pra um método tão simples. Mas com pouco dado (como aqui, 142 pontos), K=1 pode estar só \"decorando\" o treino, e a escolha certa de K é uma pergunta empírica, não uma regra fixa. Vou voltar nisso com mais rigor (validação, não só \"testei e vi que K=1 ganhou\") ",[20,5645,5647],{"href":5646},"\u002Fplaylists\u002Fpattern-recognition\u002Fpipeline-cross-validation","no próximo post",[79,5649,5651],{"id":5650},"um-lembrete-rápido-distância-também-pede-normalização","Um lembrete rápido: distância também pede normalização",[11,5653,5654,5655,5659,5660,5663,5664,1866],{},"Eu já vi isso ",[20,5656,5658],{"href":5657},"\u002Fplaylists\u002Fpattern-recognition\u002Fclassification-threshold","no post anterior",": como o KNN decide por distância, uma variável de escala grande domina a conta sozinha. Aqui não é diferente, e ",[20,5661,5662],{"href":5646},"o próximo post (bônus)"," mostra o tamanho real do estrago e o jeito certo de evitar, com ",[87,5665,1501],{},[79,5667,1873],{"id":1872},[1875,5669,5670,5678],{},[1878,5671,5672],{},[1881,5673,5674,5676],{},[1884,5675,1887],{"align":1886},[1884,5677,1890],{"align":1886},[1892,5679,5680,5688,5699],{},[1881,5681,5682,5685],{},[1897,5683,5684],{"align":1886},"Regressão e classificação limiarizada ajustam parâmetros",[1897,5686,5687],{"align":1886},"KNN não ajusta nada, \"treinar\" é só guardar os dados e comparar distância na hora de prever",[1881,5689,5690,5693],{},[1897,5691,5692],{"align":1886},"Bayes conecta probabilidade condicional e prior",[1897,5694,5695,5696,5698],{"align":1886},"A regra de votar pela classe mais comum entre os K vizinhos ",[15,5697,5501],{}," uma aplicação direta de Bayes numa estimativa de densidade local",[1881,5700,5701,5704],{},[1897,5702,5703],{"align":1886},"Hiperparâmetro é algo que eu escolho, não que o modelo aprende",[1897,5705,5706],{"align":1886},"K é o exemplo mais direto disso: nem grande nem pequeno demais, o valor certo depende dos dados que eu tenho",[79,5708,1943],{"id":1942},[11,5710,5711],{},"Uso o mesmo dataset de vinhos, agora com as 13 variáveis (não só as 2 que dava pra plotar), pra ver o efeito da normalização na prática, uma coisa que eu só descrevi em palavras até aqui.",[96,5713,5715],{"className":98,"code":5714,"language":100,"meta":57,"style":57},"model_raw = KNeighborsClassifier(n_neighbors=k).fit(X_train, y_train)\nmodel_norm = KNeighborsClassifier(n_neighbors=k).fit(X_train_normalizado, y_train)\n",[87,5716,5717,5722],{"__ignoreMap":57},[104,5718,5719],{"class":106,"line":107},[104,5720,5721],{},"model_raw = KNeighborsClassifier(n_neighbors=k).fit(X_train, y_train)\n",[104,5723,5724],{"class":106,"line":58},[104,5725,5726],{},"model_norm = KNeighborsClassifier(n_neighbors=k).fit(X_train_normalizado, y_train)\n",[1875,5728,5729,5741],{},[1878,5730,5731],{},[1881,5732,5733,5735,5738],{},[1884,5734,5339],{"align":5597},[1884,5736,5737],{"align":3190},"Sem normalizar",[1884,5739,5740],{"align":3190},"Normalizado",[1892,5742,5743,5755,5766],{},[1881,5744,5745,5747,5750],{},[1897,5746,485],{"align":5597},[1897,5748,5749],{"align":3190},"0.7778",[1897,5751,5752],{"align":3190},[15,5753,5754],{},"0.9444",[1881,5756,5757,5759,5762],{},[1897,5758,5616],{"align":5597},[1897,5760,5761],{"align":3190},"0.8056",[1897,5763,5764],{"align":3190},[15,5765,5754],{},[1881,5767,5768,5770,5773],{},[1897,5769,332],{"align":5597},[1897,5771,5772],{"align":3190},"0.7222",[1897,5774,5775],{"align":3190},[15,5776,5754],{},[11,5778,5779,5780,5783,5784,5787,5788,5790],{},"Sem normalizar, a acurácia varia bastante e nunca passa de 0.81 (13 variáveis em escalas bem diferentes, tipo ",[87,5781,5782],{},"proline"," que vai até quase 1700 contra ",[87,5785,5786],{},"hue"," que fica abaixo de 2, então a distância é decidida quase só por ",[87,5789,5782],{},"). Normalizado, a acurácia bate 0.9444 em qualquer um dos três K, um salto enorme, e olha que interessante: depois de normalizar, a escolha de K deixa de importar tanto, os três dão exatamente o mesmo resultado nesse teste. Faz sentido: quando cada variável realmente contribui pra distância (em vez de uma única variável dominar tudo), a vizinhança fica mais \"certa\" logo de cara, e sobra menos trabalho pro ajuste fino de K resolver.",[1985,5792,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":5794},[5795,5796,5797,5798,5799,5800,5801],{"id":5168,"depth":58,"text":5169},{"id":5212,"depth":58,"text":5213},{"id":5557,"depth":58,"text":5558},{"id":5584,"depth":58,"text":5585},{"id":5650,"depth":58,"text":5651},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 4a: o professor implementa K-vizinhos-mais-próximos na unha, bate exatamente com o KNeighborsClassifier do scikit-learn, e mostra que K não é 'quanto maior melhor'. Eu explico por que K é um parâmetro de suavização.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fknn-classifier",{"title":5160,"description":5802},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fknn-classifier",[5156,5154,5808],"hiperparametros","DcYL_FXzg7eAtK1Hsdawp0_f5xLgxzoxPFfFSQyy9HU",{"id":5811,"title":5812,"body":5813,"cover":3,"date":1999,"description":6961,"extension":61,"meta":6962,"navigation":63,"order":308,"path":6963,"playlist":2003,"seo":6964,"status":66,"stem":6965,"tags":6966,"__hash__":6970},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fpipeline-cross-validation.md","Pipeline, Validação Cruzada e GridSearch: o Fluxo de Verdade",{"type":8,"value":5814,"toc":6950},[5815,5822,5826,5835,5850,5858,5869,5877,5883,5913,5923,5927,5940,5974,5985,6000,6004,6007,6082,6126,6144,6158,6337,6341,6373,6426,6445,6448,6452,6462,6487,6497,6513,6523,6529,6533,6552,6613,6638,6652,6655,6659,6665,6674,6681,6787,6789,6836,6838,6841,6889,6945,6948],[11,5816,5817,5818,5821],{},"O resto da aula 4, o \"trabalho de verdade\" que fica escondido atrás de qualquer número de acurácia que eu já mostrei até aqui. Sem isso, todo K que eu escolhi ",[20,5819,4936],{"href":5820},"\u002Fplaylists\u002Fpattern-recognition\u002Fknn-classifier"," foi, sem exagero, chute educado.",[79,5823,5825],{"id":5824},"pipeline-o-que-eu-já-sabia-agora-com-classificação","Pipeline: o que eu já sabia, agora com classificação",[11,5827,1902,5828,134,5830,5834],{},[87,5829,1501],{},[20,5831,5833],{"href":5832},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Fscikit-learn-pitfalls","já apareceu na outra playlist",": encadeia normalização e modelo num único objeto, então o scaler nunca vê dado que deveria ficar de fora do treino. Aqui o professor confirma a mesma conclusão, só que num problema de classificação com 3 classes:",[96,5836,5838],{"className":98,"code":5837,"language":100,"meta":57,"style":57},"model = KNeighborsClassifier()\nmodel.fit(X_train, y_train)\n",[87,5839,5840,5845],{"__ignoreMap":57},[104,5841,5842],{"class":106,"line":107},[104,5843,5844],{},"model = KNeighborsClassifier()\n",[104,5846,5847],{"class":106,"line":58},[104,5848,5849],{},"model.fit(X_train, y_train)\n",[433,5851,5852],{},[11,5853,5854,5857],{},[15,5855,5856],{},"Saída (sem normalizar):"," acurácia 0.67.",[11,5859,5860,5861,5864,5865,5868],{},"Depois ele normaliza de quatro jeitos diferentes (min-max na mão, ",[87,5862,5863],{},"MinMaxScaler",", z-score na mão, ",[87,5866,5867],{},"StandardScaler",") e todos batem:",[433,5870,5871],{},[11,5872,5873,5876],{},[15,5874,5875],{},"Saída (qualquer normalização):"," acurácia 0.92.",[11,5878,5879,5880,5882],{},"E o ",[87,5881,1501],{}," chega no mesmo 0.92, encadeando os dois passos automaticamente:",[96,5884,5886],{"className":98,"code":5885,"language":100,"meta":57,"style":57},"pipeline = Pipeline([\n    ('scaler', StandardScaler()),\n    ('model', KNeighborsClassifier())\n])\npipeline.fit(X_train, y_train)\n",[87,5887,5888,5893,5898,5903,5908],{"__ignoreMap":57},[104,5889,5890],{"class":106,"line":107},[104,5891,5892],{},"pipeline = Pipeline([\n",[104,5894,5895],{"class":106,"line":58},[104,5896,5897],{},"    ('scaler', StandardScaler()),\n",[104,5899,5900],{"class":106,"line":118},[104,5901,5902],{},"    ('model', KNeighborsClassifier())\n",[104,5904,5905],{"class":106,"line":124},[104,5906,5907],{},"])\n",[104,5909,5910],{"class":106,"line":308},[104,5911,5912],{},"pipeline.fit(X_train, y_train)\n",[11,5914,5915,5916,5918,5919,5922],{},"Nada novo em termos de mecânica (eu já vi ",[87,5917,1501],{}," de fora), mas o salto de 0.67 pra 0.92 é o maior que eu vi até agora só de normalizar, e reforça o motivo que ",[20,5920,5921],{"href":5657},"eu já expliquei",": 13 variáveis em escalas bem diferentes, e o KNN decide tudo por distância.",[79,5924,5926],{"id":5925},"separando-validação-de-teste","Separando validação de teste",[11,5928,1902,5929,5932,5933,318,5936,5939],{},[87,5930,5931],{},"aula04c"," começa devagar: separa uma fatia do treino só pra validação (",[87,5934,5935],{},"X_tr",[87,5937,5938],{},"X_val","), testa vários valores de K, e escolhe o que ganhou na validação:",[96,5941,5943],{"className":98,"code":5942,"language":100,"meta":57,"style":57},"X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.2)\n\nfor k in range(1, 21, 2):\n    model = KNeighborsClassifier(n_neighbors=k)\n    model.fit(X_tr, y_tr)\n    acc = accuracy_score(y_val, model.predict(X_val))\n",[87,5944,5945,5950,5954,5959,5964,5969],{"__ignoreMap":57},[104,5946,5947],{"class":106,"line":107},[104,5948,5949],{},"X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.2)\n",[104,5951,5952],{"class":106,"line":58},[104,5953,300],{"emptyLinePlaceholder":63},[104,5955,5956],{"class":106,"line":118},[104,5957,5958],{},"for k in range(1, 21, 2):\n",[104,5960,5961],{"class":106,"line":124},[104,5962,5963],{},"    model = KNeighborsClassifier(n_neighbors=k)\n",[104,5965,5966],{"class":106,"line":308},[104,5967,5968],{},"    model.fit(X_tr, y_tr)\n",[104,5970,5971],{"class":106,"line":417},[104,5972,5973],{},"    acc = accuracy_score(y_val, model.predict(X_val))\n",[433,5975,5976],{},[11,5977,5978,5980,5981,5984],{},[15,5979,1632],{}," o melhor K encontrado foi ",[15,5982,5983],{},"k=9",", com acurácia de validação 0.759.",[11,5986,5987,5988,5990,5991,5994,5995,318,5997,5999],{},"Isso já é bem melhor que \"testei no teste e vi qual K ganhou\" (o que seria fazer exatamente o mesmo erro que qualquer curso de estatística avisa pra não fazer: usar o conjunto de teste pra escolher hiperparâmetro é uma forma de vazamento, você acaba enviesando a estimativa final porque ela deixou de ser sobre dado nunca visto). Mas tem um problema visível aqui: o ",[87,5989,1173],{}," nem no holdout nem na validação fixa ",[87,5992,5993],{},"random_state",", então cada vez que eu rodar essa célula de novo, ",[87,5996,5935],{},[87,5998,5938],{}," mudam, e o K vencedor pode mudar junto. Uma única divisão de validação é uma amostra só, e pode ter tido sorte ou azar.",[79,6001,6003],{"id":6002},"um-bug-real-escondido-num-loop","Um bug real, escondido num loop",[11,6005,6006],{},"O professor então parte pra validação cruzada, implementada na unha:",[96,6008,6010],{"className":98,"code":6009,"language":100,"meta":57,"style":57},"def cross_validation(model, X, y, k=3):\n    n = int(len(y)\u002Fk)\n    idx = np.random.permutation(len(y))\n    X = X[idx]\n    y = y[idx]\n    for i in range(k):\n        X_tr = np.concatenate([X[:i*n], X[(i+1)*n:]])\n        y_tr = np.concatenate([y[:i*n], y[(i+1)*n:]])\n        X_val = X[i*n:(i+1)*n]\n        y_val = y[i*n:(i+1)*n]\n        model.fit(X_tr, y_tr)\n        y_pred = model.predict(X_val)\n        acc = accuracy_score(y_val, y_pred)\n        return acc\n",[87,6011,6012,6017,6022,6027,6032,6037,6042,6047,6052,6057,6062,6067,6072,6077],{"__ignoreMap":57},[104,6013,6014],{"class":106,"line":107},[104,6015,6016],{},"def cross_validation(model, X, y, k=3):\n",[104,6018,6019],{"class":106,"line":58},[104,6020,6021],{},"    n = int(len(y)\u002Fk)\n",[104,6023,6024],{"class":106,"line":118},[104,6025,6026],{},"    idx = np.random.permutation(len(y))\n",[104,6028,6029],{"class":106,"line":124},[104,6030,6031],{},"    X = X[idx]\n",[104,6033,6034],{"class":106,"line":308},[104,6035,6036],{},"    y = y[idx]\n",[104,6038,6039],{"class":106,"line":417},[104,6040,6041],{},"    for i in range(k):\n",[104,6043,6044],{"class":106,"line":422},[104,6045,6046],{},"        X_tr = np.concatenate([X[:i*n], X[(i+1)*n:]])\n",[104,6048,6049],{"class":106,"line":428},[104,6050,6051],{},"        y_tr = np.concatenate([y[:i*n], y[(i+1)*n:]])\n",[104,6053,6054],{"class":106,"line":1692},[104,6055,6056],{},"        X_val = X[i*n:(i+1)*n]\n",[104,6058,6059],{"class":106,"line":1698},[104,6060,6061],{},"        y_val = y[i*n:(i+1)*n]\n",[104,6063,6064],{"class":106,"line":1704},[104,6065,6066],{},"        model.fit(X_tr, y_tr)\n",[104,6068,6069],{"class":106,"line":1710},[104,6070,6071],{},"        y_pred = model.predict(X_val)\n",[104,6073,6074],{"class":106,"line":1716},[104,6075,6076],{},"        acc = accuracy_score(y_val, y_pred)\n",[104,6078,6079],{"class":106,"line":1722},[104,6080,6081],{},"        return acc\n",[11,6083,6084,6085,6095,6096,6099,6100,6103,6104,6106,6107,6110,6111,6114,6115,6117,6118,6121,6122,6125],{},"Vale a pena ler essa função com atenção, porque ela ensina uma lição que não tem nada a ver com machine learning: ",[15,6086,6087,6088,6091,6092],{},"o ",[87,6089,6090],{},"return"," está dentro do ",[87,6093,6094],{},"for",". O laço roda a variável ",[87,6097,6098],{},"i"," de 0 até ",[87,6101,6102],{},"k-1"," pra fazer ",[87,6105,5374],{}," dobras diferentes (a ideia certa de validação cruzada: cada dobra vira validação uma vez, o resto vira treino), mas a função sai fora e devolve o resultado assim que termina a ",[15,6108,6109],{},"primeira"," iteração (",[87,6112,6113],{},"i=0","). As outras ",[87,6116,6102],{}," dobras nunca chegam a rodar. ",[87,6119,6120],{},"cross_validation()",", apesar do nome, calcula só ",[15,6123,6124],{},"uma"," divisão treino\u002Fvalidação, exatamente a mesma limitação da seção anterior, só que escondida atrás de um nome que promete mais do que entrega.",[433,6127,6128],{},[11,6129,6130,134,6132,6135,6136,6139,6140,6143],{},[15,6131,1632],{},[87,6133,6134],{},"cross_validation(model, X_train, y_train)"," devolve ",[87,6137,6138],{},"0.723",", um único número de uma única dobra, embaralhada de um jeito diferente a cada chamada (porque ",[87,6141,6142],{},"np.random.permutation"," roda de novo toda vez).",[11,6145,6146,6147,6150,6151,6153,6154,6157],{},"Isso explica uma coisa estranha que aparece logo depois: ",[87,6148,6149],{},"repeated_cross_validation",", que chama ",[87,6152,6120],{}," dez vezes e tira média e desvio padrão, funciona meio que por acidente. Ela não está fazendo \"10 repetições de validação cruzada de verdade\" (que seria dobra completa, repetida 10 vezes), está fazendo ",[15,6155,6156],{},"holdout repetido",": 10 divisões aleatórias diferentes, cada uma avaliada uma vez só. A média das 10 (0.717, desvio 0.057, calculada logo depois no notebook) ainda é uma estimativa mais estável que uma única divisão, porque reduz a variância de \"eu tive sorte ou azar numa divisão só\". Só não é validação cruzada no sentido técnico do termo: nenhum ponto de treino nunca vira validação em mais de uma dessas 10 rodadas por acaso, não por garantia de cobertura completa como o k-fold de verdade promete.",[11,6159,6160,6161,6190,6191,6219,6220,6272,6273,6301,6302,6330,6331,6333,6334,6336],{},"O Bishop descreve exatamente esse k-fold de verdade (ele chama de ",[104,6162,6164,6178],{"className":6163},[148],[104,6165,6167],{"className":6166},[152],[154,6168,6169],{"xmlns":156},[158,6170,6171,6176],{},[161,6172,6173],{},[164,6174,6175],{},"S",[186,6177,6175],{"encoding":188},[104,6179,6181],{"className":6180,"ariaHidden":194},[193],[104,6182,6184,6187],{"className":6183},[198],[104,6185],{"className":6186,"style":3515},[202],[104,6188,6175],{"className":6189,"style":562},[207,208],"-fold): divide o dado em ",[104,6192,6194,6207],{"className":6193},[148],[104,6195,6197],{"className":6196},[152],[154,6198,6199],{"xmlns":156},[158,6200,6201,6205],{},[161,6202,6203],{},[164,6204,6175],{},[186,6206,6175],{"encoding":188},[104,6208,6210],{"className":6209,"ariaHidden":194},[193],[104,6211,6213,6216],{"className":6212},[198],[104,6214],{"className":6215,"style":3515},[202],[104,6217,6175],{"className":6218,"style":562},[207,208]," blocos, usa ",[104,6221,6223,6241],{"className":6222},[148],[104,6224,6226],{"className":6225},[152],[154,6227,6228],{"xmlns":156},[158,6229,6230,6238],{},[161,6231,6232,6234,6236],{},[164,6233,6175],{},[168,6235,530],{},[483,6237,485],{},[186,6239,6240],{"encoding":188},"S-1",[104,6242,6244,6263],{"className":6243,"ariaHidden":194},[193],[104,6245,6247,6251,6254,6257,6260],{"className":6246},[198],[104,6248],{"className":6249,"style":6250},[202],"height:0.7667em;vertical-align:-0.0833em;",[104,6252,6175],{"className":6253,"style":562},[207,208],[104,6255],{"className":6256,"style":235},[213],[104,6258,530],{"className":6259},[239],[104,6261],{"className":6262,"style":235},[213],[104,6264,6266,6269],{"className":6265},[198],[104,6267],{"className":6268,"style":2975},[202],[104,6270,485],{"className":6271},[207]," pra treinar e 1 pra validar, repete ",[104,6274,6276,6289],{"className":6275},[148],[104,6277,6279],{"className":6278},[152],[154,6280,6281],{"xmlns":156},[158,6282,6283,6287],{},[161,6284,6285],{},[164,6286,6175],{},[186,6288,6175],{"encoding":188},[104,6290,6292],{"className":6291,"ariaHidden":194},[193],[104,6293,6295,6298],{"className":6294},[198],[104,6296],{"className":6297,"style":3515},[202],[104,6299,6175],{"className":6300,"style":562},[207,208]," vezes trocando qual bloco fica de fora, e faz a média das ",[104,6303,6305,6318],{"className":6304},[148],[104,6306,6308],{"className":6307},[152],[154,6309,6310],{"xmlns":156},[158,6311,6312,6316],{},[161,6313,6314],{},[164,6315,6175],{},[186,6317,6175],{"encoding":188},[104,6319,6321],{"className":6320,"ariaHidden":194},[193],[104,6322,6324,6327],{"className":6323},[198],[104,6325],{"className":6326,"style":3515},[202],[104,6328,6175],{"className":6329,"style":562},[207,208]," notas. A garantia importante que o ",[87,6332,6120],{}," do professor perde com o bug: no k-fold de verdade, ",[15,6335,5305],{}," ponto vira validação exatamente uma vez, cobrindo o dataset inteiro sem sobreposição. Com holdout repetido (mesmo que repetido várias vezes), alguns pontos podem nunca cair na validação, e outros podem cair repetidas vezes, por puro acaso da amostragem aleatória.",[79,6338,6340],{"id":6339},"fazendo-do-jeito-certo","Fazendo do jeito certo",[11,6342,6343,6344,6372],{},"O scikit-learn já implementa o ",[104,6345,6347,6360],{"className":6346},[148],[104,6348,6350],{"className":6349},[152],[154,6351,6352],{"xmlns":156},[158,6353,6354,6358],{},[161,6355,6356],{},[164,6357,6175],{},[186,6359,6175],{"encoding":188},[104,6361,6363],{"className":6362,"ariaHidden":194},[193],[104,6364,6366,6369],{"className":6365},[198],[104,6367],{"className":6368,"style":3515},[202],[104,6370,6175],{"className":6371,"style":562},[207,208],"-fold do Bishop direitinho:",[96,6374,6376],{"className":98,"code":6375,"language":100,"meta":57,"style":57},"from sklearn.model_selection import KFold\n\nkf = KFold(n_splits=3, shuffle=True)\naccs = []\nfor train_index, val_index in kf.split(X_train):\n    model = KNeighborsClassifier(n_neighbors=5)\n    model.fit(X_train[train_index], y_train[train_index])\n    accs.append(accuracy_score(y_train[val_index], model.predict(X_train[val_index])))\n\nprint(np.mean(accs))\n",[87,6377,6378,6383,6387,6392,6397,6402,6407,6412,6417,6421],{"__ignoreMap":57},[104,6379,6380],{"class":106,"line":107},[104,6381,6382],{},"from sklearn.model_selection import KFold\n",[104,6384,6385],{"class":106,"line":58},[104,6386,300],{"emptyLinePlaceholder":63},[104,6388,6389],{"class":106,"line":118},[104,6390,6391],{},"kf = KFold(n_splits=3, shuffle=True)\n",[104,6393,6394],{"class":106,"line":124},[104,6395,6396],{},"accs = []\n",[104,6398,6399],{"class":106,"line":308},[104,6400,6401],{},"for train_index, val_index in kf.split(X_train):\n",[104,6403,6404],{"class":106,"line":417},[104,6405,6406],{},"    model = KNeighborsClassifier(n_neighbors=5)\n",[104,6408,6409],{"class":106,"line":422},[104,6410,6411],{},"    model.fit(X_train[train_index], y_train[train_index])\n",[104,6413,6414],{"class":106,"line":428},[104,6415,6416],{},"    accs.append(accuracy_score(y_train[val_index], model.predict(X_train[val_index])))\n",[104,6418,6419],{"class":106,"line":1692},[104,6420,300],{"emptyLinePlaceholder":63},[104,6422,6423],{"class":106,"line":1698},[104,6424,6425],{},"print(np.mean(accs))\n",[433,6427,6428],{},[11,6429,6430,6432,6433,6436,6437,6440,6441,6444],{},[15,6431,1632],{}," 0.726, com ",[87,6434,6435],{},"KFold(n_splits=3)",". Com ",[87,6438,6439],{},"RepeatedKFold(n_splits=3, n_repeats=10)"," (o k-fold de verdade, repetido 10 vezes com embaralhamentos diferentes, pra reduzir ainda mais a variância da estimativa): 0.676. E o atalho de uma linha só, ",[87,6442,6443],{},"cross_val_score",", bate os dois: 0.704 e 0.701 respectivamente.",[11,6446,6447],{},"Os números não são muito diferentes do holdout repetido \"com bug\" (0.717), e isso é esperado: mesmo com a implementação errada, a ideia geral (testar em pedaços que não foram usados pra treinar) já capturava a maior parte do sinal. O ganho do k-fold de verdade é robustez, não necessariamente um número dramaticamente diferente nesse dataset específico. Mas você não sabe disso sem comparar, e é exatamente por isso que vale a pena implementar (ou usar) a coisa certa em vez de confiar que \"deu um número plausível\" significa \"o código está certo\".",[79,6449,6451],{"id":6450},"gridsearchcv-automatizando-a-busca","GridSearchCV: automatizando a busca",[11,6453,6454,6455,6458,6459,6461],{},"Em vez de escrever um ",[87,6456,6457],{},"for k in range(1, 21, 2)"," toda vez, o ",[87,6460,1505],{}," faz a busca (e a validação cruzada por trás dela) automaticamente:",[96,6463,6465],{"className":98,"code":6464,"language":100,"meta":57,"style":57},"params = {'n_neighbors': range(1, 21, 2)}\ngrid = GridSearchCV(KNeighborsClassifier(), params, scoring='accuracy')\ngrid.fit(X_train, y_train)\nprint(grid.best_params_, grid.best_score_)\n",[87,6466,6467,6472,6477,6482],{"__ignoreMap":57},[104,6468,6469],{"class":106,"line":107},[104,6470,6471],{},"params = {'n_neighbors': range(1, 21, 2)}\n",[104,6473,6474],{"class":106,"line":58},[104,6475,6476],{},"grid = GridSearchCV(KNeighborsClassifier(), params, scoring='accuracy')\n",[104,6478,6479],{"class":106,"line":118},[104,6480,6481],{},"grid.fit(X_train, y_train)\n",[104,6483,6484],{"class":106,"line":124},[104,6485,6486],{},"print(grid.best_params_, grid.best_score_)\n",[433,6488,6489],{},[11,6490,6491,134,6493,6496],{},[15,6492,1632],{},[87,6494,6495],{},"{'n_neighbors': 1}",", com nota de validação cruzada 0.761.",[11,6498,6499,6500,1813,6503,1813,6506,6509,6510,1214],{},"E expandindo a busca pra três hiperparâmetros de uma vez (",[87,6501,6502],{},"n_neighbors",[87,6504,6505],{},"weights",[87,6507,6508],{},"metric","), com ",[87,6511,6512],{},"KFold(n_splits=5)",[433,6514,6515],{},[11,6516,6517,134,6519,6522],{},[15,6518,1632],{},[87,6520,6521],{},"{'metric': 'manhattan', 'n_neighbors': 11, 'weights': 'distance'}",", nota 0.803.",[11,6524,6525,6526,6528],{},"O Bishop já avisa exatamente esse tipo de situação no capítulo 1.3: quando você tem mais de um hiperparâmetro pra ajustar, testar cada combinação manualmente vira uma explosão combinatória rápido. ",[87,6527,1505],{}," é força bruta organizada: testa toda combinação da grade, valida cada uma com k-fold, e devolve a melhor.",[79,6530,6532],{"id":6531},"pipeline-gridsearch-aí-sim-decola","Pipeline + GridSearch: aí sim decola",[11,6534,6535,6536,134,6538,6541,6542,6544,6545,6547,6548,6551],{},"A virada de chave do post inteiro: colocar o ",[87,6537,1505],{},[15,6539,6540],{},"dentro"," de um ",[87,6543,1501],{},", com o normalizador junto, e ajustar até os hiperparâmetros do próprio ",[87,6546,4951],{}," (usando o prefixo ",[87,6549,6550],{},"model__"," pra apontar qual etapa do pipeline cada parâmetro pertence):",[96,6553,6555],{"className":98,"code":6554,"language":100,"meta":57,"style":57},"pipeline = Pipeline([\n    ('scaler', StandardScaler()),\n    ('model', KNeighborsClassifier())\n])\nparams = {\n    'model__n_neighbors': range(1, 21, 2),\n    'model__weights': ['uniform', 'distance'],\n    'model__metric': ['euclidean', 'manhattan', 'minkowski'],\n}\ngrid = GridSearchCV(pipeline, params, scoring='accuracy', cv=KFold(n_splits=5, shuffle=True))\nscores = cross_val_score(grid, X_train, y_train, cv=KFold(n_splits=5, shuffle=True))\nprint(np.mean(scores))\n",[87,6556,6557,6561,6565,6569,6573,6578,6583,6588,6593,6598,6603,6608],{"__ignoreMap":57},[104,6558,6559],{"class":106,"line":107},[104,6560,5892],{},[104,6562,6563],{"class":106,"line":58},[104,6564,5897],{},[104,6566,6567],{"class":106,"line":118},[104,6568,5902],{},[104,6570,6571],{"class":106,"line":124},[104,6572,5907],{},[104,6574,6575],{"class":106,"line":308},[104,6576,6577],{},"params = {\n",[104,6579,6580],{"class":106,"line":417},[104,6581,6582],{},"    'model__n_neighbors': range(1, 21, 2),\n",[104,6584,6585],{"class":106,"line":422},[104,6586,6587],{},"    'model__weights': ['uniform', 'distance'],\n",[104,6589,6590],{"class":106,"line":428},[104,6591,6592],{},"    'model__metric': ['euclidean', 'manhattan', 'minkowski'],\n",[104,6594,6595],{"class":106,"line":1692},[104,6596,6597],{},"}\n",[104,6599,6600],{"class":106,"line":1698},[104,6601,6602],{},"grid = GridSearchCV(pipeline, params, scoring='accuracy', cv=KFold(n_splits=5, shuffle=True))\n",[104,6604,6605],{"class":106,"line":1704},[104,6606,6607],{},"scores = cross_val_score(grid, X_train, y_train, cv=KFold(n_splits=5, shuffle=True))\n",[104,6609,6610],{"class":106,"line":1710},[104,6611,6612],{},"print(np.mean(scores))\n",[11,6614,6615,6616,134,6618,6541,6621,6623,6624,6627,6628,6630,6631,6633,6634,6637],{},"Repara na estrutura: tem um ",[87,6617,6443],{},[15,6619,6620],{},"por fora",[87,6622,1505],{}," inteiro. Isso é validação cruzada ",[15,6625,6626],{},"aninhada",": o laço de fora mede o quão bem o processo completo (normalizar, buscar os melhores hiperparâmetros, treinar) generaliza, e o laço de dentro (dentro do ",[87,6629,1505],{},") é só pra escolher os hiperparâmetros. Sem esse aninhamento, a nota de validação cruzada do próprio ",[87,6632,1505],{}," (",[87,6635,6636],{},"best_score_",") fica levemente otimista, porque o mesmo dado que escolheu os hiperparâmetros também foi usado pra avaliar o resultado final.",[433,6639,6640],{},[11,6641,6642,6644,6645,1502,6648,6651],{},[15,6643,1632],{}," média de 0.957 (contra 0.81 sem normalizar dentro do pipeline, e contra 0.68 do KNN cru sem grade nenhuma). Adicionando ",[87,6646,6647],{},"scaler__with_mean",[87,6649,6650],{},"scaler__with_std"," à grade de busca (deixando até a normalização ser parte do que é otimizado): 0.979.",[11,6653,6654],{},"Do KNN cru (0.67-0.68) pro pipeline completo com busca de hiperparâmetro aninhada (0.979): a distância inteira que separa \"rodei o modelo default\" de \"fiz isso direito\".",[79,6656,6658],{"id":6657},"mais-rápido-que-grade-completa-busca-aleatória","Mais rápido que grade completa: busca aleatória",[11,6660,6661,6664],{},[87,6662,6663],{},"RandomizedSearchCV"," testa só uma amostra aleatória de combinações (aqui, 20) em vez de todas:",[96,6666,6668],{"className":98,"code":6667,"language":100,"meta":57,"style":57},"grid = RandomizedSearchCV(pipeline, params, scoring='accuracy', cv=KFold(n_splits=5, shuffle=True), n_iter=20)\n",[87,6669,6670],{"__ignoreMap":57},[104,6671,6672],{"class":106,"line":107},[104,6673,6667],{},[433,6675,6676],{},[11,6677,6678,6680],{},[15,6679,1632],{}," 0.957, praticamente empatado com a grade completa, testando bem menos combinações.",[11,6682,6683,6684,6782,6783,6786],{},"Faz sentido quando a grade de busca é grande demais pra testar tudo (aqui já são ",[104,6685,6687,6716],{"className":6686},[148],[104,6688,6690],{"className":6689},[152],[154,6691,6692],{"xmlns":156},[158,6693,6694,6713],{},[161,6695,6696,6699,6702,6704,6706,6708,6710],{},[483,6697,6698],{},"10",[168,6700,6701],{},"×",[483,6703,488],{},[168,6705,6701],{},[483,6707,5616],{},[168,6709,170],{},[483,6711,6712],{},"60",[186,6714,6715],{"encoding":188},"10 \\times 2 \\times 3 = 60",[104,6717,6719,6737,6755,6773],{"className":6718,"ariaHidden":194},[193],[104,6720,6722,6725,6728,6731,6734],{"className":6721},[198],[104,6723],{"className":6724,"style":1332},[202],[104,6726,6698],{"className":6727},[207],[104,6729],{"className":6730,"style":235},[213],[104,6732,6701],{"className":6733},[239],[104,6735],{"className":6736,"style":235},[213],[104,6738,6740,6743,6746,6749,6752],{"className":6739},[198],[104,6741],{"className":6742,"style":1332},[202],[104,6744,488],{"className":6745},[207],[104,6747],{"className":6748,"style":235},[213],[104,6750,6701],{"className":6751},[239],[104,6753],{"className":6754,"style":235},[213],[104,6756,6758,6761,6764,6767,6770],{"className":6757},[198],[104,6759],{"className":6760,"style":2975},[202],[104,6762,5616],{"className":6763},[207],[104,6765],{"className":6766,"style":214},[213],[104,6768,170],{"className":6769},[218],[104,6771],{"className":6772,"style":214},[213],[104,6774,6776,6779],{"className":6775},[198],[104,6777],{"className":6778,"style":2975},[202],[104,6780,6712],{"className":6781},[207]," combinações, cada uma com 5 dobras, 300 ajustes de modelo, e com mais hiperparâmetros isso explode rápido). O notebook ainda dá uma espiada rápida no ",[87,6784,6785],{},"Optuna",", uma biblioteca de busca bayesiana que escolhe a próxima combinação a testar com base no que já funcionou até agora, em vez de sortear ou testar tudo às cegas, mas isso é só uma menção de passagem, não o foco da aula.",[79,6788,1873],{"id":1872},[1875,6790,6791,6800],{},[1878,6792,6793],{},[1881,6794,6795,6797],{},[1884,6796,1887],{"align":1886},[1884,6798,6799],{"align":1886},"O que essas três aulas assentaram",[1892,6801,6802,6812,6823],{},[1881,6803,6804,6809],{},[1897,6805,6806,6808],{"align":1886},[87,6807,1501],{}," evita vazamento entre normalização e modelo",[1897,6810,6811],{"align":1886},"O mesmo vale pra classificação, não só regressão, e o ganho aqui foi enorme (0.67 → 0.92)",[1881,6813,6814,6817],{},[1897,6815,6816],{"align":1886},"Validação cruzada existe pra dar uma estimativa mais estável que uma única divisão treino\u002Fteste",[1897,6818,6819,6820,6822],{"align":1886},"Um ",[87,6821,6090],{}," no lugar errado transforma \"validação cruzada\" em holdout repetido sem eu perceber, então vale a pena ler o próprio código de validação, não só confiar no nome da função",[1881,6824,6825,6828],{},[1897,6826,6827],{"align":1886},"Hiperparâmetro é escolha minha",[1897,6829,6830,6832,6833,6835],{"align":1886},[87,6831,1505],{}," automatiza a busca, e colocado dentro de um ",[87,6834,1501],{},", com validação cruzada aninhada por fora, dá a estimativa mais honesta de generalização que eu já produzi nessa playlist",[79,6837,1943],{"id":1942},[11,6839,6840],{},"Reproduzo o pipeline completo (normalização + busca de hiperparâmetro + validação cruzada aninhada) no mesmo dataset de vinhos, com semente fixa pra um resultado reprodutível, coisa que o notebook original não tem em nenhuma das buscas.",[96,6842,6844],{"className":98,"code":6843,"language":100,"meta":57,"style":57},"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n\npipeline = Pipeline([('scaler', StandardScaler()), ('model', KNeighborsClassifier())])\nparams = {\n    'model__n_neighbors': range(1, 21, 2),\n    'model__weights': ['uniform', 'distance'],\n    'model__metric': ['euclidean', 'manhattan', 'minkowski'],\n}\ngrid = GridSearchCV(pipeline, params, scoring='accuracy', cv=KFold(n_splits=5, shuffle=True, random_state=42))\ngrid.fit(X_train, y_train)\n",[87,6845,6846,6851,6855,6860,6864,6868,6872,6876,6880,6885],{"__ignoreMap":57},[104,6847,6848],{"class":106,"line":107},[104,6849,6850],{},"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",[104,6852,6853],{"class":106,"line":58},[104,6854,300],{"emptyLinePlaceholder":63},[104,6856,6857],{"class":106,"line":118},[104,6858,6859],{},"pipeline = Pipeline([('scaler', StandardScaler()), ('model', KNeighborsClassifier())])\n",[104,6861,6862],{"class":106,"line":124},[104,6863,6577],{},[104,6865,6866],{"class":106,"line":308},[104,6867,6582],{},[104,6869,6870],{"class":106,"line":417},[104,6871,6587],{},[104,6873,6874],{"class":106,"line":422},[104,6875,6592],{},[104,6877,6878],{"class":106,"line":428},[104,6879,6597],{},[104,6881,6882],{"class":106,"line":1692},[104,6883,6884],{},"grid = GridSearchCV(pipeline, params, scoring='accuracy', cv=KFold(n_splits=5, shuffle=True, random_state=42))\n",[104,6886,6887],{"class":106,"line":1698},[104,6888,6481],{},[1875,6890,6891,6901],{},[1878,6892,6893],{},[1881,6894,6895,6898],{},[1884,6896,6897],{"align":1886},"Etapa",[1884,6899,6900],{"align":3190},"Resultado",[1892,6902,6903,6911,6921,6929,6937],{},[1881,6904,6905,6908],{},[1897,6906,6907],{"align":1886},"KNN cru, sem normalizar, sem buscar hiperparâmetro",[1897,6909,6910],{"align":3190},"0.7222 (teste)",[1881,6912,6913,6916],{},[1897,6914,6915],{"align":1886},"Melhor combinação encontrada pela grade",[1897,6917,6918],{"align":3190},[87,6919,6920],{},"metric=manhattan, n_neighbors=9, weights=uniform",[1881,6922,6923,6926],{},[1897,6924,6925],{"align":1886},"Nota de validação cruzada da grade",[1897,6927,6928],{"align":3190},"0.9862",[1881,6930,6931,6934],{},[1897,6932,6933],{"align":1886},"Acurácia no teste, com o melhor pipeline",[1897,6935,6936],{"align":3190},"0.9722",[1881,6938,6939,6942],{},[1897,6940,6941],{"align":1886},"Validação cruzada aninhada (estimativa honesta)",[1897,6943,6944],{"align":3190},"0.9791",[11,6946,6947],{},"A nota de validação cruzada aninhada (0.9791) e a acurácia no teste (0.9722) ficam bem próximas uma da outra, e é isso que eu quero ver: significa que a validação cruzada aninhada não estava sendo otimista demais, ela realmente previu como o pipeline se sairia num dado que nunca tinha visto. Contra o KNN cru (0.7222), a diferença inteira (25 pontos percentuais) veio só de normalizar e escolher hiperparâmetro direito, sem tocar no algoritmo em si.",[1985,6949,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":6951},[6952,6953,6954,6955,6956,6957,6958,6959,6960],{"id":5824,"depth":58,"text":5825},{"id":5925,"depth":58,"text":5926},{"id":6002,"depth":58,"text":6003},{"id":6339,"depth":58,"text":6340},{"id":6450,"depth":58,"text":6451},{"id":6531,"depth":58,"text":6532},{"id":6657,"depth":58,"text":6658},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 4b, 4c e 4d: o professor monta o fluxo profissional de verdade em cima do KNN, e no meio do caminho eu encontro um bug real no código dele, um return que mora dentro do loop errado.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fpipeline-cross-validation",{"title":5812,"description":6961},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fpipeline-cross-validation",[6967,6968,6969],"pipeline","validacao-cruzada","gridsearch","vPKMfwxYXwgpAGB5wY54TMhX5jA6NKe_vXuuY72ojaQ",{"id":6972,"title":6973,"body":6974,"cover":3,"date":1999,"description":8282,"extension":61,"meta":8283,"navigation":63,"order":417,"path":8284,"playlist":2003,"seo":8285,"status":66,"stem":8286,"tags":8287,"__hash__":8289},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees.md","Árvores de Decisão: Perguntas de Sim ou Não até Sobrar Só uma Resposta",{"type":8,"value":6975,"toc":8270},[6976,6987,6991,7042,7057,7075,7086,7114,7124,7127,7131,7138,7196,7203,7210,7214,7217,7257,7461,7564,7584,7816,7819,7849,7856,7901,7915,7919,7922,7932,7935,7939,7945,7988,8002,8012,8022,8026,8042,8092,8103,8107,8110,8125,8135,8139,8150,8156,8158,8194,8196,8202,8227,8265,8268],[11,6977,6978,6979,6982,6983,6986],{},"Aula 5, dividida entre atributos categóricos (",[87,6980,6981],{},"aula05",") e contínuos (",[87,6984,6985],{},"aula05b","). O Bishop não dedica muito espaço pra árvores de decisão (um parágrafo curto lá no capítulo 14), então essa aula fica bem mais em cima do que o professor mostrou, com a matemática de Gini como o fio condutor.",[79,6988,6990],{"id":6989},"o-dataset-avaliação-de-carros-e-um-baseline-que-importa","O dataset: avaliação de carros, e um baseline que importa",[11,6992,6993,6995,6996,6999,7000,1813,7003,1813,7006,1813,7009,1813,7012,1813,7015,7018,7019,1813,7022,1813,7025,7028,7029,1813,7032,1813,7035,1813,7038,7041],{},[87,6994,6981],{}," usa o ",[15,6997,6998],{},"Car Evaluation",", um dataset clássico do UCI: 1728 carros, 6 atributos, todos categóricos (",[87,7001,7002],{},"buying",[87,7004,7005],{},"maint",[87,7007,7008],{},"doors",[87,7010,7011],{},"persons",[87,7013,7014],{},"lug_boot",[87,7016,7017],{},"safety",", cada um com uns 3-4 valores possíveis tipo ",[87,7020,7021],{},"\"low\"",[87,7023,7024],{},"\"med\"",[87,7026,7027],{},"\"high\"","), e uma classe (",[87,7030,7031],{},"unacc",[87,7033,7034],{},"acc",[87,7036,7037],{},"good",[87,7039,7040],{},"vgood",").",[96,7043,7045],{"className":98,"code":7044,"language":100,"meta":57,"style":57},"for label in set(y):\n    print(f\"{label}:\\t{100*sum(y==label)\u002Flen(y):.4}%\")\n",[87,7046,7047,7052],{"__ignoreMap":57},[104,7048,7049],{"class":106,"line":107},[104,7050,7051],{},"for label in set(y):\n",[104,7053,7054],{"class":106,"line":58},[104,7055,7056],{},"    print(f\"{label}:\\t{100*sum(y==label)\u002Flen(y):.4}%\")\n",[433,7058,7059],{},[11,7060,7061,134,7063,7065,7066,7068,7069,7071,7072,7074],{},[15,7062,1632],{},[87,7064,7031],{}," 70.02%, ",[87,7067,7034],{}," 22.22%, ",[87,7070,7037],{}," 3.99%, ",[87,7073,7040],{}," 3.76%.",[11,7076,7077,7078,7081,7082,7085],{},"Bem desbalanceado. Antes de qualquer árvore, o professor define o modelo mais preguiçoso possível, o ",[15,7079,7080],{},"ZeroR"," (chuta sempre a classe mais comum, o mesmo espírito do \"modelo idiota\" ",[20,7083,7084],{"href":2020},"que eu já vi lá no primeiro post desta playlist",", só que pra classificação):",[96,7087,7089],{"className":98,"code":7088,"language":100,"meta":57,"style":57},"class ZeroR(BaseEstimator, ClassifierMixin):\n    def fit(self, X, y):\n        self.answer = most_common(y)\n    def predict(self, X):\n        return [self.answer]*X.shape[0]\n",[87,7090,7091,7096,7100,7105,7109],{"__ignoreMap":57},[104,7092,7093],{"class":106,"line":107},[104,7094,7095],{},"class ZeroR(BaseEstimator, ClassifierMixin):\n",[104,7097,7098],{"class":106,"line":58},[104,7099,1472],{},[104,7101,7102],{"class":106,"line":118},[104,7103,7104],{},"        self.answer = most_common(y)\n",[104,7106,7107],{"class":106,"line":124},[104,7108,1487],{},[104,7110,7111],{"class":106,"line":308},[104,7112,7113],{},"        return [self.answer]*X.shape[0]\n",[433,7115,7116],{},[11,7117,7118,7120,7121,7123],{},[15,7119,1632],{}," 70.02% de acurácia. Só chutando ",[87,7122,7031],{}," sempre.",[11,7125,7126],{},"Guarda esse número. Qualquer modelo que eu treinar daqui pra frente só é interessante se bater 70%, senão ele não aprendeu nada que \"sempre chutar a classe maioria\" já não desse de graça.",[79,7128,7130],{"id":7129},"uma-árvore-aleatória-já-bate-o-baseline","Uma árvore aleatória já bate o baseline",[11,7132,7133,7134,7137],{},"Passo intermediário interessante: uma \"árvore\" que escolhe a variável e o valor de divisão ",[15,7135,7136],{},"completamente ao acaso",", e ainda assim recursa até cada folha ficar pura:",[96,7139,7141],{"className":98,"code":7140,"language":100,"meta":57,"style":57},"class DecisionTree(BaseEstimator, ClassifierMixin):\n    def fit(self, X, y):\n        self.feature = np.random.randint(X.shape[1])\n        self.value = np.random.choice(list(set(X[:, self.feature])))\n        equals = X[:, self.feature] == self.value\n        if sum(equals) > 0 and sum(~equals) > 0:\n            self.equals_tree = DecisionTree().fit(X[equals], y[equals])\n            self.not_equals_tree = DecisionTree().fit(X[~equals], y[~equals])\n        else:\n            self.answer = most_common(y)\n        return self\n",[87,7142,7143,7148,7152,7157,7162,7167,7172,7177,7182,7187,7192],{"__ignoreMap":57},[104,7144,7145],{"class":106,"line":107},[104,7146,7147],{},"class DecisionTree(BaseEstimator, ClassifierMixin):\n",[104,7149,7150],{"class":106,"line":58},[104,7151,1472],{},[104,7153,7154],{"class":106,"line":118},[104,7155,7156],{},"        self.feature = np.random.randint(X.shape[1])\n",[104,7158,7159],{"class":106,"line":124},[104,7160,7161],{},"        self.value = np.random.choice(list(set(X[:, self.feature])))\n",[104,7163,7164],{"class":106,"line":308},[104,7165,7166],{},"        equals = X[:, self.feature] == self.value\n",[104,7168,7169],{"class":106,"line":417},[104,7170,7171],{},"        if sum(equals) > 0 and sum(~equals) > 0:\n",[104,7173,7174],{"class":106,"line":422},[104,7175,7176],{},"            self.equals_tree = DecisionTree().fit(X[equals], y[equals])\n",[104,7178,7179],{"class":106,"line":428},[104,7180,7181],{},"            self.not_equals_tree = DecisionTree().fit(X[~equals], y[~equals])\n",[104,7183,7184],{"class":106,"line":1692},[104,7185,7186],{},"        else:\n",[104,7188,7189],{"class":106,"line":1698},[104,7190,7191],{},"            self.answer = most_common(y)\n",[104,7193,7194],{"class":106,"line":1704},[104,7195,1482],{},[433,7197,7198],{},[11,7199,7200,7202],{},[15,7201,1632],{}," 75.6% (nos mesmos dados que treinou).",[11,7204,7205,7206,7209],{},"Já bate o ZeroR, mesmo escolhendo a pergunta ao acaso. Faz sentido: toda divisão, mesmo aleatória, separa o dado em dois grupos menores, e menor quase sempre significa \"um pouco menos misturado\" que o grupo original. Repetindo isso recursivamente até sobrar só uma classe em cada folha, a árvore acaba memorizando o treino, não porque a pergunta escolhida foi boa, mas porque ela nunca para de perguntar até não ter mais dúvida nenhuma. Isso já é um aviso do que vem: uma árvore sem nenhum freio ",[15,7207,7208],{},"sempre"," consegue decorar o treino, útil ou não.",[79,7211,7213],{"id":7212},"impureza-de-gini-a-régua-que-decide-qual-pergunta-fazer","Impureza de Gini: a régua que decide qual pergunta fazer",[11,7215,7216],{},"Pra escolher a pergunta certa (em vez de aleatória), precisa de um jeito de medir \"quão misturadas\" as classes estão dentro de um grupo:",[96,7218,7220],{"className":98,"code":7219,"language":100,"meta":57,"style":57},"def gini(y):\n    labels = list(set(y))\n    x = 0\n    for label in labels:\n        label_prob = np.mean(y==label)\n        x += label_prob**2\n    return 1-x\n",[87,7221,7222,7227,7232,7237,7242,7247,7252],{"__ignoreMap":57},[104,7223,7224],{"class":106,"line":107},[104,7225,7226],{},"def gini(y):\n",[104,7228,7229],{"class":106,"line":58},[104,7230,7231],{},"    labels = list(set(y))\n",[104,7233,7234],{"class":106,"line":118},[104,7235,7236],{},"    x = 0\n",[104,7238,7239],{"class":106,"line":124},[104,7240,7241],{},"    for label in labels:\n",[104,7243,7244],{"class":106,"line":308},[104,7245,7246],{},"        label_prob = np.mean(y==label)\n",[104,7248,7249],{"class":106,"line":417},[104,7250,7251],{},"        x += label_prob**2\n",[104,7253,7254],{"class":106,"line":422},[104,7255,7256],{},"    return 1-x\n",[11,7258,7259],{},[104,7260,7262,7303],{"className":7261},[148],[104,7263,7265],{"className":7264},[152],[154,7266,7267],{"xmlns":156},[158,7268,7269,7300],{},[161,7270,7271,7274,7276,7278,7280,7282,7284,7286,7292],{},[932,7272,7273],{},"Gini",[168,7275,470],{"stretchy":469},[164,7277,166],{},[168,7279,476],{"stretchy":469},[168,7281,170],{},[483,7283,485],{},[168,7285,530],{},[515,7287,7288,7290],{},[168,7289,494],{},[164,7291,5374],{},[490,7293,7294,7296,7298],{},[164,7295,11],{},[164,7297,5374],{},[483,7299,488],{},[186,7301,7302],{"encoding":188},"\\text{Gini}(y) = 1 - \\sum_{k} p_k^2",[104,7304,7306,7336,7354],{"className":7305,"ariaHidden":194},[193],[104,7307,7309,7312,7318,7321,7324,7327,7330,7333],{"className":7308},[198],[104,7310],{"className":7311,"style":558},[202],[104,7313,7315],{"className":7314},[207,1009],[104,7316,7273],{"className":7317},[207],[104,7319,470],{"className":7320},[566],[104,7322,166],{"className":7323,"style":209},[207,208],[104,7325,476],{"className":7326},[575],[104,7328],{"className":7329,"style":214},[213],[104,7331,170],{"className":7332},[218],[104,7334],{"className":7335,"style":214},[213],[104,7337,7339,7342,7345,7348,7351],{"className":7338},[198],[104,7340],{"className":7341,"style":1332},[202],[104,7343,485],{"className":7344},[207],[104,7346],{"className":7347,"style":235},[213],[104,7349,530],{"className":7350},[239],[104,7352],{"className":7353,"style":235},[213],[104,7355,7357,7361,7405,7408],{"className":7356},[198],[104,7358],{"className":7359,"style":7360},[202],"height:1.1138em;vertical-align:-0.2997em;",[104,7362,7364,7367],{"className":7363},[687],[104,7365,494],{"className":7366,"style":693},[687,691,692],[104,7368,7370],{"className":7369},[697],[104,7371,7373,7397],{"className":7372},[605,606],[104,7374,7376,7394],{"className":7375},[610],[104,7377,7380],{"className":7378,"style":7379},[614],"height:0.1864em;",[104,7381,7382,7385],{"style":710},[104,7383],{"className":7384,"style":714},[622],[104,7386,7388],{"className":7387},[627,628,629,630],[104,7389,7391],{"className":7390},[207,630],[104,7392,5374],{"className":7393,"style":5422},[207,208,630],[104,7395,667],{"className":7396},[666],[104,7398,7400],{"className":7399},[610],[104,7401,7403],{"className":7402,"style":755},[614],[104,7404],{},[104,7406],{"className":7407,"style":683},[213],[104,7409,7411,7414],{"className":7410},[207],[104,7412,11],{"className":7413},[207,208],[104,7415,7417],{"className":7416},[697],[104,7418,7420,7452],{"className":7419},[605,606],[104,7421,7423,7449],{"className":7422},[610],[104,7424,7426,7438],{"className":7425,"style":897},[614],[104,7427,7429,7432],{"style":7428},"top:-2.4169em;margin-left:0em;margin-right:0.05em;",[104,7430],{"className":7431,"style":714},[622],[104,7433,7435],{"className":7434},[627,628,629,630],[104,7436,5374],{"className":7437,"style":5422},[207,208,630],[104,7439,7440,7443],{"style":900},[104,7441],{"className":7442,"style":714},[622],[104,7444,7446],{"className":7445},[627,628,629,630],[104,7447,488],{"className":7448},[207,630],[104,7450,667],{"className":7451},[666],[104,7453,7455],{"className":7454},[610],[104,7456,7459],{"className":7457,"style":7458},[614],"height:0.2831em;",[104,7460],{},[11,7462,7463,7464,7534,7535,7563],{},"onde ",[104,7465,7467,7485],{"className":7466},[148],[104,7468,7470],{"className":7469},[152],[154,7471,7472],{"xmlns":156},[158,7473,7474,7482],{},[161,7475,7476],{},[515,7477,7478,7480],{},[164,7479,11],{},[164,7481,5374],{},[186,7483,7484],{"encoding":188},"p_k",[104,7486,7488],{"className":7487,"ariaHidden":194},[193],[104,7489,7491,7494],{"className":7490},[198],[104,7492],{"className":7493,"style":203},[202],[104,7495,7497,7500],{"className":7496},[207],[104,7498,11],{"className":7499},[207,208],[104,7501,7503],{"className":7502},[697],[104,7504,7506,7526],{"className":7505},[605,606],[104,7507,7509,7523],{"className":7508},[610],[104,7510,7512],{"className":7511,"style":5409},[614],[104,7513,7514,7517],{"style":788},[104,7515],{"className":7516,"style":714},[622],[104,7518,7520],{"className":7519},[627,628,629,630],[104,7521,5374],{"className":7522,"style":5422},[207,208,630],[104,7524,667],{"className":7525},[666],[104,7527,7529],{"className":7528},[610],[104,7530,7532],{"className":7531,"style":807},[614],[104,7533],{}," é a fração de exemplos da classe ",[104,7536,7538,7551],{"className":7537},[148],[104,7539,7541],{"className":7540},[152],[154,7542,7543],{"xmlns":156},[158,7544,7545,7549],{},[161,7546,7547],{},[164,7548,5374],{},[186,7550,5374],{"encoding":188},[104,7552,7554],{"className":7553,"ariaHidden":194},[193],[104,7555,7557,7560],{"className":7556},[198],[104,7558],{"className":7559,"style":268},[202],[104,7561,5374],{"className":7562,"style":5422},[207,208]," dentro do grupo. Dois casos extremos confirmam a intuição:",[433,7565,7566],{},[11,7567,7568,134,7570,7573,7574,7576,7577,7579,7580,7583],{},[15,7569,1632],{},[87,7571,7572],{},"gini"," de um grupo com todo mundo da mesma classe: ",[15,7575,4303],{},". ",[87,7578,7572],{}," de um grupo com 100 classes diferentes, uma de cada: ",[15,7581,7582],{},"0.99",", bem perto do máximo teórico.",[11,7585,7586,7587,7590,7591,7811,7812,7815],{},"Zero é pureza total (não sobra nenhuma dúvida sobre a classe). Quanto mais dividido entre classes, mais alto. O Bishop chama isso de ",[15,7588,7589],{},"índice de Gini"," (ele escreve como ",[104,7592,7594,7641],{"className":7593},[148],[104,7595,7597],{"className":7596},[152],[154,7598,7599],{"xmlns":156},[158,7600,7601,7638],{},[161,7602,7603,7609,7620,7622,7624,7626,7636],{},[515,7604,7605,7607],{},[168,7606,494],{},[164,7608,5374],{},[515,7610,7611,7613],{},[164,7612,11],{},[161,7614,7615,7618],{},[164,7616,7617],{},"τ",[164,7619,5374],{},[168,7621,470],{"stretchy":469},[483,7623,485],{},[168,7625,530],{},[515,7627,7628,7630],{},[164,7629,11],{},[161,7631,7632,7634],{},[164,7633,7617],{},[164,7635,5374],{},[168,7637,476],{"stretchy":469},[186,7639,7640],{"encoding":188},"\\sum_k p_{\\tau k}(1-p_{\\tau k})",[104,7642,7644,7756],{"className":7643,"ariaHidden":194},[193],[104,7645,7647,7651,7691,7694,7741,7744,7747,7750,7753],{"className":7646},[198],[104,7648],{"className":7649,"style":7650},[202],"height:1.0497em;vertical-align:-0.2997em;",[104,7652,7654,7657],{"className":7653},[687],[104,7655,494],{"className":7656,"style":693},[687,691,692],[104,7658,7660],{"className":7659},[697],[104,7661,7663,7683],{"className":7662},[605,606],[104,7664,7666,7680],{"className":7665},[610],[104,7667,7669],{"className":7668,"style":7379},[614],[104,7670,7671,7674],{"style":710},[104,7672],{"className":7673,"style":714},[622],[104,7675,7677],{"className":7676},[627,628,629,630],[104,7678,5374],{"className":7679,"style":5422},[207,208,630],[104,7681,667],{"className":7682},[666],[104,7684,7686],{"className":7685},[610],[104,7687,7689],{"className":7688,"style":755},[614],[104,7690],{},[104,7692],{"className":7693,"style":683},[213],[104,7695,7697,7700],{"className":7696},[207],[104,7698,11],{"className":7699},[207,208],[104,7701,7703],{"className":7702},[697],[104,7704,7706,7733],{"className":7705},[605,606],[104,7707,7709,7730],{"className":7708},[610],[104,7710,7712],{"className":7711,"style":5409},[614],[104,7713,7714,7717],{"style":788},[104,7715],{"className":7716,"style":714},[622],[104,7718,7720],{"className":7719},[627,628,629,630],[104,7721,7723,7727],{"className":7722},[207,630],[104,7724,7617],{"className":7725,"style":7726},[207,208,630],"margin-right:0.1132em;",[104,7728,5374],{"className":7729,"style":5422},[207,208,630],[104,7731,667],{"className":7732},[666],[104,7734,7736],{"className":7735},[610],[104,7737,7739],{"className":7738,"style":807},[614],[104,7740],{},[104,7742,470],{"className":7743},[566],[104,7745,485],{"className":7746},[207],[104,7748],{"className":7749,"style":235},[213],[104,7751,530],{"className":7752},[239],[104,7754],{"className":7755,"style":235},[213],[104,7757,7759,7762,7808],{"className":7758},[198],[104,7760],{"className":7761,"style":558},[202],[104,7763,7765,7768],{"className":7764},[207],[104,7766,11],{"className":7767},[207,208],[104,7769,7771],{"className":7770},[697],[104,7772,7774,7800],{"className":7773},[605,606],[104,7775,7777,7797],{"className":7776},[610],[104,7778,7780],{"className":7779,"style":5409},[614],[104,7781,7782,7785],{"style":788},[104,7783],{"className":7784,"style":714},[622],[104,7786,7788],{"className":7787},[627,628,629,630],[104,7789,7791,7794],{"className":7790},[207,630],[104,7792,7617],{"className":7793,"style":7726},[207,208,630],[104,7795,5374],{"className":7796,"style":5422},[207,208,630],[104,7798,667],{"className":7799},[666],[104,7801,7803],{"className":7802},[610],[104,7804,7806],{"className":7805,"style":807},[614],[104,7807],{},[104,7809,476],{"className":7810},[575],", a mesma soma, só rearranjada algebricamente) e explica por que ele (junto com a entropia cruzada, a alternativa mais comum) é preferido à taxa de erro pura pra ",[15,7813,7814],{},"crescer"," a árvore: ele é mais sensível a mudanças pequenas na proporção de classes dentro de um grupo, então guia melhor qual pergunta separa melhor, mesmo quando nenhuma pergunta ainda consegue classificar tudo certo.",[11,7817,7818],{},"Uma divisão binária (separar o grupo em \"igual a esse valor\" vs \"diferente\") tem uma impureza combinada, a média das impurezas dos dois lados, ponderada pelo tamanho de cada lado:",[96,7820,7822],{"className":98,"code":7821,"language":100,"meta":57,"style":57},"def impurity_value(x, y, value, impurity_function):\n    equals = x == value\n    equals_impurity = impurity_function(y[equals])\n    not_equals_impurity = impurity_function(y[~equals])\n    return (np.mean(equals)) * equals_impurity + (np.mean(~equals)) * not_equals_impurity\n",[87,7823,7824,7829,7834,7839,7844],{"__ignoreMap":57},[104,7825,7826],{"class":106,"line":107},[104,7827,7828],{},"def impurity_value(x, y, value, impurity_function):\n",[104,7830,7831],{"class":106,"line":58},[104,7832,7833],{},"    equals = x == value\n",[104,7835,7836],{"class":106,"line":118},[104,7837,7838],{},"    equals_impurity = impurity_function(y[equals])\n",[104,7840,7841],{"class":106,"line":124},[104,7842,7843],{},"    not_equals_impurity = impurity_function(y[~equals])\n",[104,7845,7846],{"class":106,"line":308},[104,7847,7848],{},"    return (np.mean(equals)) * equals_impurity + (np.mean(~equals)) * not_equals_impurity\n",[11,7850,7851,7852,7855],{},"E a árvore gulosa testa ",[15,7853,7854],{},"toda"," combinação de variável e valor possível, ficando com a que dá a menor impureza combinada:",[96,7857,7859],{"className":98,"code":7858,"language":100,"meta":57,"style":57},"def best_feature(X, y, impurity_function):\n    best_feature, best_value, best_value_impurity = None, None, float('inf')\n    for feature in range(X.shape[1]):\n        value, _ = best_split(X[:,feature], y, impurity_function)\n        feature_impurity = impurity_value(X[:,feature], y, value, impurity_function)\n        if feature_impurity \u003C best_value_impurity:\n            best_feature, best_value_impurity, best_value = feature, feature_impurity, value\n    return best_feature, best_value, best_value_impurity\n",[87,7860,7861,7866,7871,7876,7881,7886,7891,7896],{"__ignoreMap":57},[104,7862,7863],{"class":106,"line":107},[104,7864,7865],{},"def best_feature(X, y, impurity_function):\n",[104,7867,7868],{"class":106,"line":58},[104,7869,7870],{},"    best_feature, best_value, best_value_impurity = None, None, float('inf')\n",[104,7872,7873],{"class":106,"line":118},[104,7874,7875],{},"    for feature in range(X.shape[1]):\n",[104,7877,7878],{"class":106,"line":124},[104,7879,7880],{},"        value, _ = best_split(X[:,feature], y, impurity_function)\n",[104,7882,7883],{"class":106,"line":308},[104,7884,7885],{},"        feature_impurity = impurity_value(X[:,feature], y, value, impurity_function)\n",[104,7887,7888],{"class":106,"line":417},[104,7889,7890],{},"        if feature_impurity \u003C best_value_impurity:\n",[104,7892,7893],{"class":106,"line":422},[104,7894,7895],{},"            best_feature, best_value_impurity, best_value = feature, feature_impurity, value\n",[104,7897,7898],{"class":106,"line":428},[104,7899,7900],{},"    return best_feature, best_value, best_value_impurity\n",[433,7902,7903],{},[11,7904,7905,7907,7908,7910,7911,7914],{},[15,7906,1632],{}," a primeira pergunta que a árvore escolhe fazer é sobre ",[87,7909,7017],{}," (variável 5), dividindo em \"",[87,7912,7913],{},"low","\" contra o resto, com impureza combinada 0.385, a menor entre as 6 variáveis testadas. Faz sentido de um jeito bem humano: segurança baixa provavelmente reprova o carro direto, então essa pergunta já separa bastante gente.",[79,7916,7918],{"id":7917},"a-árvore-gulosa-de-verdade","A árvore gulosa de verdade",[11,7920,7921],{},"Juntando tudo, cada nó da árvore pergunta \"essa variável é igual a esse valor?\" e recursa nos dois grupos, sempre escolhendo a pergunta que mais reduz a impureza:",[433,7923,7924],{},[11,7925,7926,7928,7929,1866],{},[15,7927,1632],{}," 100% de acurácia no dado que treinou. 96.8% num conjunto de teste separado. 97.2% de acurácia média numa validação cruzada de 5 dobras (0.977, 0.986, 0.962, 0.968, 0.968), ",[20,7930,7931],{"href":5646},"a mesma técnica que eu já vi valer a pena no post anterior",[11,7933,7934],{},"Cem por cento no treino é sempre um sinal de alerta de overfitting (a árvore, sem limite, sempre consegue decorar), mas aqui o teste e a validação cruzada também saem muito bons, então não é decoreba vazia dessa vez: esse dataset em particular vem de uma regra determinística (é um dataset sintético, feito a partir de uma tabela de regras de avaliação de carro, sem ruído nenhum), então uma árvore suficientemente funda literalmente consegue reconstruir a regra verdadeira por trás dos dados.",[79,7936,7938],{"id":7937},"limitar-a-profundidade-nem-sempre-ajuda","Limitar a profundidade nem sempre ajuda",[11,7940,7941,7942,1214],{},"O jeito mais direto de conter uma árvore é limitar quantas perguntas seguidas ela pode fazer, ",[87,7943,7944],{},"max_depth",[96,7946,7948],{"className":98,"code":7947,"language":100,"meta":57,"style":57},"class DecisionTree(BaseEstimator, ClassifierMixin):\n    def __init__(self, max_depth=9999999):\n        self.max_depth = max_depth\n    def fit(self, X, y):\n        ...\n        if sum(equals) > 0 and sum(~equals) > 0 and self.max_depth > 0:\n            self.equals_tree = DecisionTree(self.max_depth-1).fit(X[equals], y[equals])\n            ...\n",[87,7949,7950,7954,7959,7964,7968,7973,7978,7983],{"__ignoreMap":57},[104,7951,7952],{"class":106,"line":107},[104,7953,7147],{},[104,7955,7956],{"class":106,"line":58},[104,7957,7958],{},"    def __init__(self, max_depth=9999999):\n",[104,7960,7961],{"class":106,"line":118},[104,7962,7963],{},"        self.max_depth = max_depth\n",[104,7965,7966],{"class":106,"line":124},[104,7967,1472],{},[104,7969,7970],{"class":106,"line":308},[104,7971,7972],{},"        ...\n",[104,7974,7975],{"class":106,"line":417},[104,7976,7977],{},"        if sum(equals) > 0 and sum(~equals) > 0 and self.max_depth > 0:\n",[104,7979,7980],{"class":106,"line":422},[104,7981,7982],{},"            self.equals_tree = DecisionTree(self.max_depth-1).fit(X[equals], y[equals])\n",[104,7984,7985],{"class":106,"line":428},[104,7986,7987],{},"            ...\n",[433,7989,7990],{},[11,7991,7992,4073,7994,7997,7998,8001],{},[15,7993,1632],{},[87,7995,7996],{},"max_depth=5",", a acurácia de validação cruzada ",[15,7999,8000],{},"cai"," pra 86.6% (contra 97.2% sem limite nenhum).",[11,8003,8004,8005,8008,8009,8011],{},"Contra-intuitivo à primeira vista, mas conecta direto com o motivo do dataset ser \"limpo\": como a regra verdadeira por trás dos dados realmente depende de combinar várias variáveis em sequência, cortar a árvore antes da hora tira dela exatamente a capacidade de que ela precisa pra representar a regra completa. Isso não é \"regularização ajudando contra overfitting\", é ",[15,8006,8007],{},"sub-ajuste"," (a árvore fica simples demais pra esse problema específico). A lição não é \"sempre limite a profundidade\", é \"meça antes de decidir\": o ",[87,8010,7944],{}," certo depende inteiramente de quão complicado o padrão verdadeiro é, e só dá pra saber testando, com validação cruzada, não assumindo.",[11,8013,8014,8015,1502,8018,8021],{},"Com ",[87,8016,8017],{},"max_depth=20",[87,8019,8020],{},"min_sample_split=10"," (parar de dividir um grupo com 10 exemplos ou menos, mesmo que ainda esteja impuro) o resultado fica no meio do caminho: 95.6%, ligeiramente abaixo do ótimo sem limite, mas já bem mais controlado que deixar crescer sem parâmetro nenhum.",[79,8023,8025],{"id":8024},"atributos-contínuos-a-mesma-ideia-o-corte-muda","Atributos contínuos: a mesma ideia, o corte muda",[11,8027,8028,8030,8031,1502,8034,8037,8038,8041],{},[87,8029,6985],{}," troca de dataset (Iris, o clássico das 3 espécies de flor, usando só ",[87,8032,8033],{},"petal length",[87,8035,8036],{},"petal width",") e de tipo de pergunta: em vez de \"é igual a esse valor?\", a pergunta vira \"",[15,8039,8040],{},"é maior ou igual"," a esse limiar?\". Pra achar o melhor limiar numa variável contínua, o professor ordena os valores e testa o ponto médio entre cada par de vizinhos:",[96,8043,8045],{"className":98,"code":8044,"language":100,"meta":57,"style":57},"def best_split(x, y, impurity_function):\n    best_value, best_value_impurity = 0, float('inf')\n    x = np.sort(x)\n    for i in range(1, len(x)):\n        value = (x[i-1]+x[i])\u002F2\n        value_impurity = impurity_value(x, y, value, impurity_function)\n        if value_impurity \u003C best_value_impurity:\n            best_value, best_value_impurity = value, value_impurity\n    return best_value, best_value_impurity\n",[87,8046,8047,8052,8057,8062,8067,8072,8077,8082,8087],{"__ignoreMap":57},[104,8048,8049],{"class":106,"line":107},[104,8050,8051],{},"def best_split(x, y, impurity_function):\n",[104,8053,8054],{"class":106,"line":58},[104,8055,8056],{},"    best_value, best_value_impurity = 0, float('inf')\n",[104,8058,8059],{"class":106,"line":118},[104,8060,8061],{},"    x = np.sort(x)\n",[104,8063,8064],{"class":106,"line":124},[104,8065,8066],{},"    for i in range(1, len(x)):\n",[104,8068,8069],{"class":106,"line":308},[104,8070,8071],{},"        value = (x[i-1]+x[i])\u002F2\n",[104,8073,8074],{"class":106,"line":417},[104,8075,8076],{},"        value_impurity = impurity_value(x, y, value, impurity_function)\n",[104,8078,8079],{"class":106,"line":422},[104,8080,8081],{},"        if value_impurity \u003C best_value_impurity:\n",[104,8083,8084],{"class":106,"line":428},[104,8085,8086],{},"            best_value, best_value_impurity = value, value_impurity\n",[104,8088,8089],{"class":106,"line":1692},[104,8090,8091],{},"    return best_value, best_value_impurity\n",[433,8093,8094],{},[11,8095,8096,8098,8099,8102],{},[15,8097,1632],{}," a primeira pergunta que a árvore aprende é ",[87,8100,8101],{},"petal length >= 2.45",", com impureza 0.333 (a menor possível aqui, já que essa única pergunta já separa perfeitamente uma das 3 espécies do resto).",[79,8104,8106],{"id":8105},"interativo-profundidade-mudando-a-fronteira-ao-vivo","Interativo: profundidade mudando a fronteira ao vivo",[11,8108,8109],{},"Reconstrução minha do mesmo algoritmo, nos 150 pontos reais do Iris (as mesmas duas variáveis, comprimento e largura da pétala). Clica nos valores de profundidade e repara como a fronteira ganha \"degraus\" novos a cada nível:",[8111,8112],"decision-tree-explorer",{":classes":8113,":depth-options":8114,":initial-max-depth":485,":x-max":8115,":x-min":8116,":x-train":8117,":y-max":8118,":y-min":331,":y-train":8119,"class0-label":8120,"class1-label":8121,"class2-label":8122,"x-label":8123,"y-label":8124},"[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]","[1, 2, 3, 5, 9999]","7.1","0.8","[1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.6, 1.4, 1.1, 1.2, 1.5, 1.3, 1.4, 1.7, 1.5, 1.7, 1.5, 1.0, 1.7, 1.9, 1.6, 1.6, 1.5, 1.4, 1.6, 1.6, 1.5, 1.5, 1.4, 1.5, 1.2, 1.3, 1.4, 1.3, 1.5, 1.3, 1.3, 1.3, 1.6, 1.9, 1.4, 1.6, 1.4, 1.5, 1.4, 4.7, 4.5, 4.9, 4.0, 4.6, 4.5, 4.7, 3.3, 4.6, 3.9, 3.5, 4.2, 4.0, 4.7, 3.6, 4.4, 4.5, 4.1, 4.5, 3.9, 4.8, 4.0, 4.9, 4.7, 4.3, 4.4, 4.8, 5.0, 4.5, 3.5, 3.8, 3.7, 3.9, 5.1, 4.5, 4.5, 4.7, 4.4, 4.1, 4.0, 4.4, 4.6, 4.0, 3.3, 4.2, 4.2, 4.2, 4.3, 3.0, 4.1, 6.0, 5.1, 5.9, 5.6, 5.8, 6.6, 4.5, 6.3, 5.8, 6.1, 5.1, 5.3, 5.5, 5.0, 5.1, 5.3, 5.5, 6.7, 6.9, 5.0, 5.7, 4.9, 6.7, 4.9, 5.7, 6.0, 4.8, 4.9, 5.6, 5.8, 6.1, 6.4, 5.6, 5.1, 5.6, 6.1, 5.6, 5.5, 4.8, 5.4, 5.6, 5.1, 5.1, 5.9, 5.7, 5.2, 5.0, 5.2, 5.4, 5.1]","2.6","[0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.2, 0.1, 0.1, 0.2, 0.4, 0.4, 0.3, 0.3, 0.3, 0.2, 0.4, 0.2, 0.5, 0.2, 0.2, 0.4, 0.2, 0.2, 0.2, 0.2, 0.4, 0.1, 0.2, 0.2, 0.2, 0.2, 0.1, 0.2, 0.2, 0.3, 0.3, 0.2, 0.6, 0.4, 0.3, 0.2, 0.2, 0.2, 0.2, 1.4, 1.5, 1.5, 1.3, 1.5, 1.3, 1.6, 1.0, 1.3, 1.4, 1.0, 1.5, 1.0, 1.4, 1.3, 1.4, 1.5, 1.0, 1.5, 1.1, 1.8, 1.3, 1.5, 1.2, 1.3, 1.4, 1.4, 1.7, 1.5, 1.0, 1.1, 1.0, 1.2, 1.6, 1.5, 1.6, 1.5, 1.3, 1.3, 1.3, 1.2, 1.4, 1.2, 1.0, 1.3, 1.2, 1.3, 1.3, 1.1, 1.3, 2.5, 1.9, 2.1, 1.8, 2.2, 2.1, 1.7, 1.8, 1.8, 2.5, 2.0, 1.9, 2.1, 2.0, 2.4, 2.3, 1.8, 2.2, 2.3, 1.5, 2.3, 2.0, 2.0, 1.8, 2.1, 1.8, 1.8, 1.8, 2.1, 1.6, 1.9, 2.0, 2.2, 1.5, 1.4, 2.3, 2.4, 1.8, 1.8, 2.1, 2.4, 2.3, 1.9, 2.3, 2.5, 2.3, 1.9, 2.0, 2.3, 1.8]","Setosa","Versicolor","Virginica","comprimento da pétala (cm)","largura da pétala (cm)",[11,8126,8127,8128,8130,8131,8134],{},"Com profundidade 1, a árvore só faz uma pergunta (a mesma ",[87,8129,8101],{}," de cima), então o gráfico vira exatamente duas regiões, uma reta vertical. A partir da profundidade 2, aparece um segundo corte dividindo a segunda região em duas, e por aí vai: cada nível de profundidade soma no máximo mais um corte por região existente. Repara também que os cortes são sempre ",[15,8132,8133],{},"retas verticais ou horizontais",", nunca diagonais, porque cada pergunta olha uma variável de cada vez.",[79,8136,8138],{"id":8137},"o-que-a-árvore-não-consegue-fazer-bem","O que a árvore não consegue fazer bem",[11,8140,8141,8142,8145,8146,8149],{},"Essa última observação é uma das limitações que o Bishop aponta: árvores de decisão só cortam ",[15,8143,8144],{},"alinhado aos eixos",". Se a fronteira \"certa\" entre duas classes corresse na diagonal, uma árvore precisaria de um monte de cortes em escadinha pra chegar perto, enquanto uma única fronteira diagonal resolveria de uma vez (compara com o post anterior: ",[20,8147,8148],{"href":5820},"as regiões do KNN"," já eram mais suaves, sem essa limitação de eixo).",[11,8151,8152,8153,1866],{},"O Bishop também cita outro problema, a instabilidade: uma mudança pequena no dado de treino pode mudar a estrutura inteira da árvore, porque a escolha de qual variável dividir primeiro no topo cascateia pra toda a árvore por baixo. O próprio notebook mostra isso sem querer: rodando o mesmo treino três vezes, com divisões treino\u002Fteste diferentes (sem fixar semente), a acurácia de teste saiu 90%, depois 96.7%, depois 100%. Com só 30 exemplos de teste, uma diferença de 2-3 classificações já move a acurácia vários pontos percentuais, exatamente o motivo pelo qual ",[20,8154,8155],{"href":5646},"validação cruzada, não uma única divisão, é a forma correta de medir isso",[79,8157,1873],{"id":1872},[1875,8159,8160,8168],{},[1878,8161,8162],{},[1881,8163,8164,8166],{},[1884,8165,1887],{"align":1886},[1884,8167,1890],{"align":1886},[1892,8169,8170,8178,8186],{},[1881,8171,8172,8175],{},[1897,8173,8174],{"align":1886},"Um baseline idiota ajuda a interpretar qualquer acurácia",[1897,8176,8177],{"align":1886},"Com classes desbalanceadas (70% numa só), o baseline pode ser surpreendentemente alto, e \"97% de acurácia\" sem comparar com ele não diz nada",[1881,8179,8180,8183],{},[1897,8181,8182],{"align":1886},"Overfitting acontece quando o modelo tem liberdade demais",[1897,8184,8185],{"align":1886},"Uma árvore sem limite de profundidade sempre decora o treino, mas limitar demais também pode piorar, se o padrão verdadeiro precisar mesmo daquela complexidade",[1881,8187,8188,8191],{},[1897,8189,8190],{"align":1886},"Validação cruzada dá uma estimativa mais estável",[1897,8192,8193],{"align":1886},"Árvores são especialmente instáveis a mudanças pequenas no treino, então uma única divisão de teste é ainda mais enganosa aqui do que em outros modelos",[79,8195,1943],{"id":1942},[11,8197,8198,8199,8201],{},"Uso o dataset de avaliação de carros (o categórico, ",[87,8200,6981],{},") pra comparar profundidades diferentes de um jeito sistemático, com validação cruzada de verdade em cada uma, em vez de testar um valor de cada vez.",[96,8203,8205],{"className":98,"code":8204,"language":100,"meta":57,"style":57},"for depth in [1, 2, 3, 5, 10, 9999]:\n    model = DecisionTree(max_depth=depth, min_sample_split=2)\n    scores = cross_val_score(model, X, y, cv=KFold(n_splits=5, shuffle=True))\n    print(depth, np.mean(scores))\n",[87,8206,8207,8212,8217,8222],{"__ignoreMap":57},[104,8208,8209],{"class":106,"line":107},[104,8210,8211],{},"for depth in [1, 2, 3, 5, 10, 9999]:\n",[104,8213,8214],{"class":106,"line":58},[104,8215,8216],{},"    model = DecisionTree(max_depth=depth, min_sample_split=2)\n",[104,8218,8219],{"class":106,"line":118},[104,8220,8221],{},"    scores = cross_val_score(model, X, y, cv=KFold(n_splits=5, shuffle=True))\n",[104,8223,8224],{"class":106,"line":124},[104,8225,8226],{},"    print(depth, np.mean(scores))\n",[1875,8228,8229,8239],{},[1878,8230,8231],{},[1881,8232,8233,8236],{},[1884,8234,8235],{"align":5597},"Profundidade máxima",[1884,8237,8238],{"align":3190},"Acurácia (CV 5 dobras)",[1892,8240,8241,8248,8255],{},[1881,8242,8243,8245],{},[1897,8244,485],{"align":5597},[1897,8246,8247],{"align":3190},"≈ 0.78 (uma pergunta só, pouco acima do baseline de 0.70)",[1881,8249,8250,8252],{},[1897,8251,332],{"align":5597},[1897,8253,8254],{"align":3190},"0.866",[1881,8256,8257,8260],{},[1897,8258,8259],{"align":5597},"Sem limite",[1897,8261,8262],{"align":3190},[15,8263,8264],{},"0.972",[11,8266,8267],{},"(A linha de profundidade 1 é uma estimativa rápida minha em cima da mesma ideia, não uma célula do notebook original. As outras duas são os números reais já mostrados neste post.) A tendência é clara: no Car Evaluation, mais profundidade quase sempre ajuda, porque o padrão verdadeiro por trás do dado é genuinamente complexo (depende de combinar várias das 6 variáveis) e não tem ruído nenhum atrapalhando. É o oposto exato do que costuma acontecer com dado real e ruidoso, onde profundidade demais decora o ruído em vez do padrão. A lição de novo: não existe \"profundidade certa\" universal, existe testar com validação cruzada e deixar o dado decidir.",[1985,8269,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":8271},[8272,8273,8274,8275,8276,8277,8278,8279,8280,8281],{"id":6989,"depth":58,"text":6990},{"id":7129,"depth":58,"text":7130},{"id":7212,"depth":58,"text":7213},{"id":7917,"depth":58,"text":7918},{"id":7937,"depth":58,"text":7938},{"id":8024,"depth":58,"text":8025},{"id":8105,"depth":58,"text":8106},{"id":8137,"depth":58,"text":8138},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 5 e 5b: o professor constrói uma árvore de decisão gulosa do zero, guiada pela impureza de Gini, primeiro em atributos categóricos, depois em atributos contínuos. Eu explico por que limitar a profundidade nem sempre ajuda.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees",{"title":6973,"description":8282},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees",[8288,7572,3837],"arvore-de-decisao","GiCBmi_XrIuvKcfwixgQDy3aBLAGUG0jtxa620v15k0",{"id":8291,"title":8292,"body":8293,"cover":3,"date":9189,"description":9190,"extension":61,"meta":9191,"navigation":63,"order":422,"path":9192,"playlist":2003,"seo":9193,"status":66,"stem":9194,"tags":9195,"__hash__":9199},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fensembles.md","Ensembles: a Sabedoria das Multidões (e os Seus Limites)",{"type":8,"value":8294,"toc":9178},[8295,8298,8302,8316,8320,8323,8360,8363,8397,8404,8511,8517,8521,8524,8531,8538,8553,8564,8579,8590,8601,8604,8608,8618,8627,8633,8640,8651,8658,8670,8674,8946,8956,8964,8968,8978,8985,8995,8999,9006,9017,9024,9027,9029,9069,9072,9074,9080,9114,9169,9176],[11,8296,8297],{},"Aula 6, e o professor sai do \"um modelo só\" pela primeira vez na matéria: em vez de escolher o melhor classificador, treina vários e deixa eles votarem. O dataset também muda de figura: reconhecimento de rosto de verdade.",[79,8299,8301],{"id":8300},"o-dataset-rostos-não-números","O dataset: rostos, não números",[11,8303,8304,8307,8308,8311,8312,8315],{},[87,8305,8306],{},"fetch_olivetti_faces"," traz 400 fotos (64×64 pixels, então cada foto vira um vetor de ",[15,8309,8310],{},"4096 números",", um por pixel) de 40 pessoas diferentes, 10 fotos de cada. A tarefa é dizer, a partir da foto, ",[15,8313,8314],{},"qual das 40 pessoas"," é. Isso já é um problema bem mais difícil que qualquer coisa que eu vi até aqui nessa playlist: 40 classes (contra 3 ou 4 das aulas anteriores) e 4096 variáveis de entrada (contra 2, 6, 13 ou 30).",[79,8317,8319],{"id":8318},"três-chutes-diferentes-uma-votação","Três chutes diferentes, uma votação",[11,8321,8322],{},"O professor treina três classificadores bem diferentes entre si no mesmo dado normalizado:",[1875,8324,8325,8334],{},[1878,8326,8327],{},[1881,8328,8329,8331],{},[1884,8330,341],{"align":1886},[1884,8332,8333],{"align":3190},"Acurácia sozinho",[1892,8335,8336,8344,8352],{},[1881,8337,8338,8341],{},[1897,8339,8340],{"align":1886},"KNN (K=5)",[1897,8342,8343],{"align":3190},"0.8625",[1881,8345,8346,8349],{},[1897,8347,8348],{"align":1886},"Naive Bayes Gaussiano",[1897,8350,8351],{"align":3190},"0.8375",[1881,8353,8354,8357],{},[1897,8355,8356],{"align":1886},"Perceptron",[1897,8358,8359],{"align":3190},"0.875",[11,8361,8362],{},"Nenhum passa de 88%. Só que, em vez de escolher o melhor dos três, ele junta as três previsões e vota pela mais comum:",[96,8364,8366],{"className":98,"code":8365,"language":100,"meta":57,"style":57},"hits = np.stack((knn_hits, gnb_hits, ppn_hits))\ny_pred = np.stack((knn_pred, gnb_pred, ppn_pred))\n\nfrom scipy.stats import mode\ny_pred_mode, _ = mode(y_pred, axis=0)\nvote_hits = y_pred_mode == y_test\n",[87,8367,8368,8373,8378,8382,8387,8392],{"__ignoreMap":57},[104,8369,8370],{"class":106,"line":107},[104,8371,8372],{},"hits = np.stack((knn_hits, gnb_hits, ppn_hits))\n",[104,8374,8375],{"class":106,"line":58},[104,8376,8377],{},"y_pred = np.stack((knn_pred, gnb_pred, ppn_pred))\n",[104,8379,8380],{"class":106,"line":118},[104,8381,300],{"emptyLinePlaceholder":63},[104,8383,8384],{"class":106,"line":124},[104,8385,8386],{},"from scipy.stats import mode\n",[104,8388,8389],{"class":106,"line":308},[104,8390,8391],{},"y_pred_mode, _ = mode(y_pred, axis=0)\n",[104,8393,8394],{"class":106,"line":417},[104,8395,8396],{},"vote_hits = y_pred_mode == y_test\n",[433,8398,8399],{},[11,8400,8401,8403],{},[15,8402,1632],{}," 0.9625. Melhor que qualquer um dos três sozinho, por uma margem e tanto.",[11,8405,443,8406,8409,8410,6633,8448,8476,8477,8510],{},[15,8407,8408],{},"comitê"," (capítulo 14.2), e a matemática por trás é simpática: se os erros de cada modelo forem descorrelacionados (um erra num lugar diferente do outro), o erro médio do comitê cai por um fator de ",[104,8411,8413,8432],{"className":8412},[148],[104,8414,8416],{"className":8415},[152],[154,8417,8418],{"xmlns":156},[158,8419,8420,8429],{},[161,8421,8422,8424,8426],{},[483,8423,485],{},[164,8425,318],{"mathvariant":959},[164,8427,8428],{},"M",[186,8430,8431],{"encoding":188},"1\u002FM",[104,8433,8435],{"className":8434,"ariaHidden":194},[193],[104,8436,8438,8441,8445],{"className":8437},[198],[104,8439],{"className":8440,"style":558},[202],[104,8442,8444],{"className":8443},[207],"1\u002F",[104,8446,8428],{"className":8447,"style":745},[207,208],[104,8449,8451,8464],{"className":8450},[148],[104,8452,8454],{"className":8453},[152],[154,8455,8456],{"xmlns":156},[158,8457,8458,8462],{},[161,8459,8460],{},[164,8461,8428],{},[186,8463,8428],{"encoding":188},[104,8465,8467],{"className":8466,"ariaHidden":194},[193],[104,8468,8470,8473],{"className":8469},[198],[104,8471],{"className":8472,"style":3515},[202],[104,8474,8428],{"className":8475,"style":745},[207,208]," = número de modelos) comparado ao erro médio de cada modelo sozinho. ",[104,8478,8480,8498],{"className":8479},[148],[104,8481,8483],{"className":8482},[152],[154,8484,8485],{"xmlns":156},[158,8486,8487,8495],{},[161,8488,8489,8491,8493],{},[483,8490,485],{},[164,8492,318],{"mathvariant":959},[483,8494,5616],{},[186,8496,8497],{"encoding":188},"1\u002F3",[104,8499,8501],{"className":8500,"ariaHidden":194},[193],[104,8502,8504,8507],{"className":8503},[198],[104,8505],{"className":8506,"style":558},[202],[104,8508,8497],{"className":8509},[207]," do erro é otimista demais pra acontecer na prática (o próprio Bishop avisa: \"na prática os erros costumam ser bastante correlacionados, e a redução real é geralmente pequena\"), mas a ideia central bate: onde o KNN erra, o Perceptron às vezes acerta, e vice-versa, então a maioria tende a acertar mesmo quando um dos três individualmente erra.",[11,8512,8513,8516],{},[87,8514,8515],{},"VotingClassifier"," do scikit-learn faz exatamente essa conta, e bate o mesmo 0.9625, confirmando que a implementação na mão tá certa.",[79,8518,8520],{"id":8519},"a-pegadinha-votar-não-ajuda-se-todo-mundo-pensa-igual","A pegadinha: votar não ajuda se todo mundo pensa igual",[11,8522,8523],{},"Aqui vem o experimento mais importante do post. Primeiro, o professor troca os três modelos diferentes por três KNNs com K diferente (1, 3, 5):",[433,8525,8526],{},[11,8527,8528,8530],{},[15,8529,1632],{}," 0.90. Pior que os três modelos diferentes (0.9625).",[11,8532,8533,8534,8537],{},"Depois, algo mais revelador ainda. Uma única árvore de decisão (sem limite, sem nada especial): 0.50, bem fraca nesse dataset de 4096 variáveis. E se eu treinar ",[15,8535,8536],{},"10 cópias"," dessa mesma árvore e fizer elas votarem?",[96,8539,8541],{"className":98,"code":8540,"language":100,"meta":57,"style":57},"base_estimators = [(f\"dtc({i})\", DecisionTreeClassifier()) for i in range(10)]\nvoting_clf = VotingClassifier(base_estimators, voting='hard')\n",[87,8542,8543,8548],{"__ignoreMap":57},[104,8544,8545],{"class":106,"line":107},[104,8546,8547],{},"base_estimators = [(f\"dtc({i})\", DecisionTreeClassifier()) for i in range(10)]\n",[104,8549,8550],{"class":106,"line":58},[104,8551,8552],{},"voting_clf = VotingClassifier(base_estimators, voting='hard')\n",[433,8554,8555],{},[11,8556,8557,8559,8560,8563],{},[15,8558,1632],{}," 0.50. ",[15,8561,8562],{},"Exatamente igual"," à árvore sozinha.",[11,8565,8566,8567,8570,8571,8574,8575,8578],{},"Faz todo sentido, e é exatamente o que o Bishop avisou: o ",[87,8568,8569],{},"DecisionTreeClassifier()"," padrão do scikit-learn é ",[15,8572,8573],{},"determinístico",", sempre escolhe a mesma pergunta ótima pra dividir cada grupo. Dez cópias do mesmo algoritmo, no mesmo dado, sempre chegam na mesma árvore, sempre erram exatamente nos mesmos exemplos. Não existe \"maioria\" quando todo mundo vota igual: 10 votos idênticos valem o mesmo que 1. Comitê só funciona quando os membros ",[15,8576,8577],{},"discordam"," de verdade, e discordância exige alguma fonte real de variação entre os modelos.",[11,8580,8581,8582,8585,8586,8589],{},"O professor introduz essa variação trocando o jeito de escolher a divisão de ",[87,8583,8584],{},"'best'"," (sempre a pergunta ótima) pra ",[87,8587,8588],{},"'random'"," (a árvore escolhe uma pergunta boa, mas ao acaso, entre as candidatas):",[433,8591,8592],{},[11,8593,8594,8596,8597,8600],{},[15,8595,1632],{}," uma única árvore aleatória: 0.6125, já melhor que a determinística sozinha. Dez árvores aleatórias votando: ",[15,8598,8599],{},"0.7875",", um salto e tanto.",[11,8602,8603],{},"Agora cada árvore é genuinamente diferente da outra (aleatoriedade real na hora de escolher a pergunta), então os erros deixam de ser idênticos, e a votação começa a valer a pena de verdade.",[79,8605,8607],{"id":8606},"bagging-random-forest-e-extra-trees-aleatoriedade-organizada","Bagging, Random Forest e Extra Trees: aleatoriedade organizada",[11,8609,1902,8610,8613,8614,8617],{},[87,8611,8612],{},"BaggingClassifier"," generaliza essa ideia: treina várias cópias do mesmo modelo, cada uma vendo uma ",[15,8615,8616],{},"amostra aleatória com reposição"," dos dados de treino (bootstrap), então cada árvore vê um conjunto ligeiramente diferente, além de escolher divisões diferentes:",[433,8619,8620],{},[11,8621,8622,134,8624,8626],{},[15,8623,1632],{},[87,8625,8612],{}," com 100 árvores aleatórias: 0.9125.",[11,8628,8629,8632],{},[87,8630,8631],{},"RandomForestClassifier"," é essencialmente essa mesma receita empacotada (bootstrap + árvores, mas também sorteando um subconjunto das variáveis em cada divisão, não só embaralhando quais exemplos cada árvore vê):",[433,8634,8635],{},[11,8636,8637,8639],{},[15,8638,1632],{}," Random Forest, 100 árvores: 0.9125, empatado com o bagging manual.",[11,8641,8642,8643,8646,8647,8650],{},"E ",[87,8644,8645],{},"ExtraTreesClassifier"," vai um passo além, também sorteando o ",[15,8648,8649],{},"limiar"," de cada divisão em vez de buscar o ótimo (mais aleatoriedade ainda):",[433,8652,8653],{},[11,8654,8655,8657],{},[15,8656,1632],{}," 0.9625, o melhor entre todas as florestas testadas, empatando com o voto original dos três modelos diferentes.",[11,8659,8660,8661,8663,8664,8666,8667,8669],{},"Os números aqui não são só ilustração, eles confirmam a tese da seção anterior: o que faz um comitê valer a pena é o quanto as árvores discordam entre si. Bootstrap sozinho (",[87,8662,8612],{},") já sorteia qual exemplo cada árvore vê, e sortear também qual variável cada árvore pode usar em cada divisão (",[87,8665,8631],{},") empata exatamente com isso, 0.9125 os dois, o que sugere que nesse dataset a variação por bootstrap já estava gerando praticamente toda a discordância útil, e forçar as árvores a olhar pra colunas diferentes não acrescentou muito mais desacordo em cima disso. Já sortear o limiar de corte também (",[87,8668,8645],{},"), não só quais exemplos e quais variáveis, mas literalmente o valor que separa \"sim\" de \"não\" em cada divisão, é uma fonte de discordância mais forte: duas árvores podem ver os mesmos dados, as mesmas variáveis, e ainda assim cortar em lugares bem diferentes. É por isso que o salto de 0.9125 pra 0.9625 vem justamente do ExtraTrees: mais aleatoriedade no processo de construção, mais árvores genuinamente diferentes, mais chance de os erros de cada uma não coincidirem.",[79,8671,8673],{"id":8672},"boosting-aprender-com-o-erro-do-último","Boosting: aprender com o erro do último",[11,8675,8676,8679,8680,8683,8684,8754,8755,8758,8759,8873,8874,8945],{},[87,8677,8678],{},"AdaBoostClassifier"," muda a estratégia: em vez de treinar todo mundo em paralelo e votar, treina em ",[15,8681,8682],{},"sequência",", e cada novo modelo dá mais peso pros exemplos que o modelo anterior errou. O Bishop descreve o algoritmo formalmente: cada exemplo tem um peso ",[104,8685,8687,8705],{"className":8686},[148],[104,8688,8690],{"className":8689},[152],[154,8691,8692],{"xmlns":156},[158,8693,8694,8702],{},[161,8695,8696],{},[515,8697,8698,8700],{},[164,8699,317],{},[164,8701,499],{},[186,8703,8704],{"encoding":188},"w_n",[104,8706,8708],{"className":8707,"ariaHidden":194},[193],[104,8709,8711,8714],{"className":8710},[198],[104,8712],{"className":8713,"style":2826},[202],[104,8715,8717,8720],{"className":8716},[207],[104,8718,317],{"className":8719,"style":2833},[207,208],[104,8721,8723],{"className":8722},[697],[104,8724,8726,8746],{"className":8725},[605,606],[104,8727,8729,8743],{"className":8728},[610],[104,8730,8732],{"className":8731,"style":785},[614],[104,8733,8734,8737],{"style":2849},[104,8735],{"className":8736,"style":714},[622],[104,8738,8740],{"className":8739},[627,628,629,630],[104,8741,499],{"className":8742},[207,208,630],[104,8744,667],{"className":8745},[666],[104,8747,8749],{"className":8748},[610],[104,8750,8752],{"className":8751,"style":807},[614],[104,8753],{},", começando igual pra todo mundo. Depois de cada classificador treinado, o peso dos exemplos que ele errou ",[15,8756,8757],{},"aumenta"," (multiplicado por ",[104,8760,8762,8787],{"className":8761},[148],[104,8763,8765],{"className":8764},[152],[154,8766,8767],{"xmlns":156},[158,8768,8769,8784],{},[161,8770,8771],{},[539,8772,8773,8776],{},[164,8774,8775],{},"e",[515,8777,8778,8781],{},[164,8779,8780],{},"α",[164,8782,8783],{},"m",[186,8785,8786],{"encoding":188},"e^{\\alpha_m}",[104,8788,8790],{"className":8789,"ariaHidden":194},[193],[104,8791,8793,8797],{"className":8792},[198],[104,8794],{"className":8795,"style":8796},[202],"height:0.6644em;",[104,8798,8800,8803],{"className":8799},[207],[104,8801,8775],{"className":8802},[207,208],[104,8804,8806],{"className":8805},[697],[104,8807,8809],{"className":8808},[605],[104,8810,8812],{"className":8811},[610],[104,8813,8815],{"className":8814,"style":8796},[614],[104,8816,8817,8820],{"style":900},[104,8818],{"className":8819,"style":714},[622],[104,8821,8823],{"className":8822},[627,628,629,630],[104,8824,8826],{"className":8825},[207,630],[104,8827,8829,8833],{"className":8828},[207,630],[104,8830,8780],{"className":8831,"style":8832},[207,208,630],"margin-right:0.0037em;",[104,8834,8836],{"className":8835},[697],[104,8837,8839,8864],{"className":8838},[605,606],[104,8840,8842,8861],{"className":8841},[610],[104,8843,8846],{"className":8844,"style":8845},[614],"height:0.1645em;",[104,8847,8849,8853],{"style":8848},"top:-2.357em;margin-left:-0.0037em;margin-right:0.0714em;",[104,8850],{"className":8851,"style":8852},[622],"height:2.5em;",[104,8854,8858],{"className":8855},[627,8856,8857,630],"reset-size3","size1",[104,8859,8783],{"className":8860},[207,208,630],[104,8862,667],{"className":8863},[666],[104,8865,8867],{"className":8866},[610],[104,8868,8871],{"className":8869,"style":8870},[614],"height:0.143em;",[104,8872],{},"), então o próximo classificador da sequência é forçado a prestar mais atenção neles. No fim, a previsão final é uma votação ponderada, onde classificadores mais precisos (",[104,8875,8877,8895],{"className":8876},[148],[104,8878,8880],{"className":8879},[152],[154,8881,8882],{"xmlns":156},[158,8883,8884,8892],{},[161,8885,8886],{},[515,8887,8888,8890],{},[164,8889,8780],{},[164,8891,8783],{},[186,8893,8894],{"encoding":188},"\\alpha_m",[104,8896,8898],{"className":8897,"ariaHidden":194},[193],[104,8899,8901,8904],{"className":8900},[198],[104,8902],{"className":8903,"style":2826},[202],[104,8905,8907,8910],{"className":8906},[207],[104,8908,8780],{"className":8909,"style":8832},[207,208],[104,8911,8913],{"className":8912},[697],[104,8914,8916,8937],{"className":8915},[605,606],[104,8917,8919,8934],{"className":8918},[610],[104,8920,8922],{"className":8921,"style":785},[614],[104,8923,8925,8928],{"style":8924},"top:-2.55em;margin-left:-0.0037em;margin-right:0.05em;",[104,8926],{"className":8927,"style":714},[622],[104,8929,8931],{"className":8930},[627,628,629,630],[104,8932,8783],{"className":8933},[207,208,630],[104,8935,667],{"className":8936},[666],[104,8938,8940],{"className":8939},[610],[104,8941,8943],{"className":8942,"style":807},[614],[104,8944],{}," maior) pesam mais que os fracos.",[433,8947,8948],{},[11,8949,8950,8952,8953,8955],{},[15,8951,1632],{}," AdaBoost com árvores aleatórias profundas: 0.925 no teste, ",[15,8954,4788],{}," no treino.",[11,8957,8958,8959,8963],{},"Cem por cento no treino é sinal de que o boosting, com base fraca suficiente e rodadas suficientes, também consegue decorar o treino (o mesmo overfitting que ",[20,8960,8962],{"href":8961},"\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees","eu já vi acontecer com árvore sem limite de profundidade","), mas o teste ainda sai bem melhor que qualquer árvore individual sozinha.",[79,8965,8967],{"id":8966},"stacking-nem-sempre-o-mais-sofisticado-ganha","Stacking: nem sempre o mais sofisticado ganha",[11,8969,8970,8973,8974,8977],{},[87,8971,8972],{},"StackingClassifier"," tenta ser mais esperto que votar: em vez de contar votos, treina um modelo extra (o \"meta-modelo\") que aprende a ",[15,8975,8976],{},"combinar"," as previsões dos modelos base, usando validação cruzada internamente pra não colar os dados de treino no meta-modelo.",[433,8979,8980],{},[11,8981,8982,8984],{},[15,8983,1632],{}," stacking dos três modelos originais (KNN, Naive Bayes, Perceptron): 0.80. Stacking de votação + Random Forest + Extra Trees: 0.7625.",[11,8986,8987,8988,8990,8991,8994],{},"Pior que o voto simples dos mesmos modelos (0.9625) nas duas vezes que o professor tentou. Vale registrar isso sem meias palavras: a técnica mais sofisticada ",[15,8989,137],{}," venceu aqui. Com poucos exemplos de treino por classe (10 fotos por pessoa, e o ",[87,8992,8993],{},"cv=3"," do stacking ainda separa uma fatia disso só pra treinar o meta-modelo), não sobra dado suficiente pra esse meta-modelo aprender uma combinação melhor do que \"cada um vale um voto\".",[79,8996,8998],{"id":8997},"o-tombo-de-humildade-um-modelo-simples-ganha-de-todo-mundo","O tombo de humildade: um modelo simples ganha de todo mundo",[11,9000,9001,9002,9005],{},"Depois de tanto ensemble, o professor testa ",[87,9003,9004],{},"LogisticRegression"," sozinha, sem comitê nenhum:",[433,9007,9008],{},[11,9009,9010,9012,9013,9016],{},[15,9011,1632],{}," 0.975 no teste. ",[15,9014,9015],{},"O melhor resultado do post inteiro",", batendo todo ensemble tentado, incluindo o Extra Trees (0.9625) e o AdaBoost (0.925).",[11,9018,9019,9020,9023],{},"E pra garantir que não foi sorte de um teste só, uma validação cruzada de 5 dobras confirma: 0.965 de média (1.0, 0.95, 0.95, 0.975, 0.95). O professor ainda tenta afinar hiperparâmetro com o Optuna (50 tentativas, buscando profundidade da árvore, taxa de aprendizado e número de estimadores do AdaBoost), e o melhor resultado encontrado depois de tudo isso é 0.9125 no teste, ",[15,9021,9022],{},"ainda atrás"," da regressão logística crua.",[11,9025,9026],{},"Por que a regressão logística ganha aqui? Um bom palpite: com 4096 variáveis (pixels) e só 400 exemplos, o problema já é quase linear na prática, imagens da mesma pessoa formam uma \"nuvem\" que um hiperplano separa razoavelmente bem, e não sobra tanto padrão não-linear pra árvores e florestas explorarem. Ensembles de árvore brilham quando o padrão verdadeiro é genuinamente complexo e não-linear (como no post anterior, com o Car Evaluation). Aqui, o problema já tinha um jeito simples e eficaz de ser resolvido, e a ferramenta certa venceu a ferramenta chique.",[79,9028,1873],{"id":1872},[1875,9030,9031,9039],{},[1878,9032,9033],{},[1881,9034,9035,9037],{},[1884,9036,1887],{"align":1886},[1884,9038,1890],{"align":1886},[1892,9040,9041,9053,9061],{},[1881,9042,9043,9046],{},[1897,9044,9045],{"align":1886},"Um modelo só faz sua melhor previsão sozinho",[1897,9047,9048,9049,9052],{"align":1886},"Combinar vários modelos pode bater cada um individualmente, ",[15,9050,9051],{},"se"," eles discordarem de verdade entre si",[1881,9054,9055,9058],{},[1897,9056,9057],{"align":1886},"Árvore sem limite decora o treino",[1897,9059,9060],{"align":1886},"Copiar a mesma árvore determinística 10 vezes não ajuda em nada: sem variação real entre os modelos, votar não muda o resultado",[1881,9062,9063,9066],{},[1897,9064,9065],{"align":1886},"Técnica mais avançada costuma ser melhor",[1897,9067,9068],{"align":1886},"Nem sempre: stacking perdeu pro voto simples aqui, e regressão logística crua bateu todo ensemble tentado",[11,9070,9071],{},"A lição que fica: ensemble é uma ferramenta poderosa, não uma bala de prata. Antes de empilhar modelos, vale testar se o problema já tem uma solução simples e eficaz, porque às vezes tem.",[79,9073,1943],{"id":1942},[11,9075,9076,9077,9079],{},"O post inteiro comparou ensembles contra ",[87,9078,9004],{}," usando só o teste que o notebook já tinha rodado. Pra fechar com mais confiança, rodei uma validação cruzada de 5 dobras de verdade nos três melhores modelos do post (Random Forest, Extra Trees e a regressão logística), todos sob o mesmo critério, coisa que o notebook original só fez pra regressão logística.",[96,9081,9083],{"className":98,"code":9082,"language":100,"meta":57,"style":57},"from sklearn.model_selection import cross_val_score, StratifiedKFold\n\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nfor name, model in models.items():\n    scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')\n    print(name, scores.mean(), scores.std())\n",[87,9084,9085,9090,9094,9099,9104,9109],{"__ignoreMap":57},[104,9086,9087],{"class":106,"line":107},[104,9088,9089],{},"from sklearn.model_selection import cross_val_score, StratifiedKFold\n",[104,9091,9092],{"class":106,"line":58},[104,9093,300],{"emptyLinePlaceholder":63},[104,9095,9096],{"class":106,"line":118},[104,9097,9098],{},"cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n",[104,9100,9101],{"class":106,"line":124},[104,9102,9103],{},"for name, model in models.items():\n",[104,9105,9106],{"class":106,"line":308},[104,9107,9108],{},"    scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')\n",[104,9110,9111],{"class":106,"line":417},[104,9112,9113],{},"    print(name, scores.mean(), scores.std())\n",[1875,9115,9116,9128],{},[1878,9117,9118],{},[1881,9119,9120,9122,9125],{},[1884,9121,341],{"align":1886},[1884,9123,9124],{"align":3190},"Acurácia média (5 dobras)",[1884,9126,9127],{"align":3190},"Desvio padrão",[1892,9129,9130,9141,9152],{},[1881,9131,9132,9135,9138],{},[1897,9133,9134],{"align":1886},"Random Forest (100 árvores)",[1897,9136,9137],{"align":3190},"0.9450",[1897,9139,9140],{"align":3190},"0.0232",[1881,9142,9143,9146,9149],{},[1897,9144,9145],{"align":1886},"Extra Trees (100 árvores)",[1897,9147,9148],{"align":3190},"0.9600",[1897,9150,9151],{"align":3190},"0.0146",[1881,9153,9154,9159,9164],{},[1897,9155,9156],{"align":1886},[15,9157,9158],{},"Regressão Logística",[1897,9160,9161],{"align":3190},[15,9162,9163],{},"0.9750",[1897,9165,9166],{"align":3190},[15,9167,9168],{},"0.0079",[11,9170,9171,9172,9175],{},"A vitória da regressão logística não foi sorte do split específico que o notebook usou: ela ganha nas 5 dobras testadas, e ainda por cima com o ",[15,9173,9174],{},"menor"," desvio padrão dos três, ou seja, o resultado mais consistente de dobra pra dobra. Confirma numericamente a lição da seção anterior, com o cuidado extra de medir todo mundo pela mesma régua.",[1985,9177,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":9179},[9180,9181,9182,9183,9184,9185,9186,9187,9188],{"id":8300,"depth":58,"text":8301},{"id":8318,"depth":58,"text":8319},{"id":8519,"depth":58,"text":8520},{"id":8606,"depth":58,"text":8607},{"id":8672,"depth":58,"text":8673},{"id":8966,"depth":58,"text":8967},{"id":8997,"depth":58,"text":8998},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"2026-08-20","Aula 6: o professor combina vários classificadores fracos em cima de rostos reais e chega a 96%, mas o final é uma lição de humildade: um modelo simples sozinho bate cada ensemble tentado, até o mais sofisticado.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fensembles",{"title":8292,"description":9190},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fensembles",[9196,9197,9198],"ensembles","bagging","boosting","txyoNkryw2AOk1gwlke4iyoSLmiVib2g1FCnxFctsp4",{"id":9201,"title":9202,"body":9203,"cover":3,"date":9189,"description":9946,"extension":61,"meta":9947,"navigation":63,"order":428,"path":9948,"playlist":2003,"seo":9949,"status":66,"stem":9950,"tags":9951,"__hash__":9954},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Ftitanic.md","O Titanic: Meu Primeiro Dado Sujo de Verdade",{"type":8,"value":9204,"toc":9936},[9205,9208,9212,9232,9273,9282,9296,9300,9315,9327,9341,9361,9374,9378,9413,9433,9445,9449,9465,9513,9527,9580,9586,9619,9626,9632,9689,9693,9708,9715,9726,9741,9748,9751,9755,9761,9768,9784,9791,9797,9799,9835,9837,9840,9864,9927,9934],[11,9206,9207],{},"Aula 7, e o professor muda de regime: até aqui todo dataset já chegava pronto, sem buraco, sem coluna inútil, sem texto solto. O Titanic (o dataset mais famoso do Kaggle, provavelmente o primeiro projeto de todo mundo que começa em ciência de dados) não tem esse luxo.",[79,9209,9211],{"id":9210},"o-dado-sujo-891-passageiros-nem-toda-coluna-serve","O dado sujo: 891 passageiros, nem toda coluna serve",[96,9213,9215],{"className":98,"code":9214,"language":100,"meta":57,"style":57},"df = pd.read_csv('train.csv')\ny = df['Survived']\nX = df.drop('Survived', axis=1)\n",[87,9216,9217,9222,9227],{"__ignoreMap":57},[104,9218,9219],{"class":106,"line":107},[104,9220,9221],{},"df = pd.read_csv('train.csv')\n",[104,9223,9224],{"class":106,"line":58},[104,9225,9226],{},"y = df['Survived']\n",[104,9228,9229],{"class":106,"line":118},[104,9230,9231],{},"X = df.drop('Survived', axis=1)\n",[11,9233,9234,9235,9238,9239,9242,9243,1813,9246,1813,9249,1813,9252,9255,9256,9259,9260,1813,9263,1813,9266,1813,9269,9272],{},"891 passageiros, ",[87,9236,9237],{},"Survived"," (0 ou 1) como alvo, e um punhado de colunas de entrada bem misturadas: ",[87,9240,9241],{},"Pclass"," (classe do bilhete), ",[87,9244,9245],{},"Name",[87,9247,9248],{},"Sex",[87,9250,9251],{},"Age",[87,9253,9254],{},"SibSp"," (irmãos\u002Fcônjuge a bordo), ",[87,9257,9258],{},"Parch"," (pais\u002Ffilhos a bordo), ",[87,9261,9262],{},"Ticket",[87,9264,9265],{},"Fare",[87,9267,9268],{},"Cabin",[87,9270,9271],{},"Embarked"," (porto de embarque). O professor descarta quatro delas de cara:",[96,9274,9276],{"className":98,"code":9275,"language":100,"meta":57,"style":57},"caracteristicas_indesejadas = ['PassengerId', 'Name', 'Ticket', 'Cabin']\n",[87,9277,9278],{"__ignoreMap":57},[104,9279,9280],{"class":106,"line":107},[104,9281,9275],{},[11,9283,9284,9287,9288,1502,9290,9292,9293,9295],{},[87,9285,9286],{},"PassengerId"," é só um número sequencial, não carrega informação nenhuma sobre a pessoa. ",[87,9289,9245],{},[87,9291,9262],{}," são texto livre, quase todo valor é único (891 nomes diferentes pra 891 passageiros), então um modelo não tem como generalizar nada a partir disso sem processar o texto primeiro (extrair título, tipo \"Mr.\"\u002F\"Mrs.\", seria um jeito válido, mas é trabalho extra que a aula não fez). ",[87,9294,9268],{}," falta em boa parte das linhas e também é bem específica demais.",[79,9297,9299],{"id":9298},"dado-faltando-é-a-regra-não-a-exceção","Dado faltando é a regra, não a exceção",[96,9301,9303],{"className":98,"code":9302,"language":100,"meta":57,"style":57},"for col in Xnum.columns:\n    print(f\"{col:>12} {Xnum[col].isnull().sum():2}\")\n",[87,9304,9305,9310],{"__ignoreMap":57},[104,9306,9307],{"class":106,"line":107},[104,9308,9309],{},"for col in Xnum.columns:\n",[104,9311,9312],{"class":106,"line":58},[104,9313,9314],{},"    print(f\"{col:>12} {Xnum[col].isnull().sum():2}\")\n",[433,9316,9317],{},[11,9318,9319,134,9321,9323,9324,9326],{},[15,9320,1632],{},[87,9322,9251],{}," falta em 177 dos 891 passageiros (quase 20%). ",[87,9325,9271],{}," falta em 2.",[11,9328,9329,9330,9333,9334,9336,9337,9340],{},"Isso nunca tinha acontecido nessa playlist até agora: todo dataset anterior já vinha completo. Aqui, quase 1 em cada 5 idades é ",[87,9331,9332],{},"NaN",". Descartar essas linhas jogaria fora 20% dos dados, e descartar a coluna ",[87,9335,9251],{}," inteira jogaria fora uma variável provavelmente importante (criança tinha prioridade no resgate). A solução do professor é ",[15,9338,9339],{},"imputação",": preencher o buraco com um valor estimado.",[96,9342,9344],{"className":98,"code":9343,"language":100,"meta":57,"style":57},"from sklearn.impute import SimpleImputer\nimputer = SimpleImputer(strategy='median')\nXnumTratado = imputer.fit_transform(Xnum)\n",[87,9345,9346,9351,9356],{"__ignoreMap":57},[104,9347,9348],{"class":106,"line":107},[104,9349,9350],{},"from sklearn.impute import SimpleImputer\n",[104,9352,9353],{"class":106,"line":58},[104,9354,9355],{},"imputer = SimpleImputer(strategy='median')\n",[104,9357,9358],{"class":106,"line":118},[104,9359,9360],{},"XnumTratado = imputer.fit_transform(Xnum)\n",[11,9362,9363,9364,9366,9367,9369,9370,9373],{},"Pra variáveis numéricas, a mediana (não a média): a distribuição de idade e de tarifa (",[87,9365,9265],{},") tem gente bem fora da curva (bebê de meses, tarifas de primeira classe bem altas), e a mediana não se deixa puxar por esses extremos do jeito que a média se deixa. Pra variáveis categóricas (",[87,9368,9271],{},"), não existe \"mediana\" de porto, então a estratégia vira ",[87,9371,9372],{},"'most_frequent'",", o valor que mais aparece.",[79,9375,9377],{"id":9376},"categoria-não-é-número-mesmo-quando-parece","Categoria não é número, mesmo quando parece",[11,9379,9380,1502,9382,9384,9385,318,9388,1813,9391,318,9394,318,9397,9400,9401,9404,9405,9408,9409,9412],{},[87,9381,9248],{},[87,9383,9271],{}," são texto (",[87,9386,9387],{},"\"male\"",[87,9389,9390],{},"\"female\"",[87,9392,9393],{},"\"S\"",[87,9395,9396],{},"\"C\"",[87,9398,9399],{},"\"Q\"","). Um modelo só entende número, mas simplesmente numerar (",[87,9402,9403],{},"male=0, female=1",", ou ",[87,9406,9407],{},"S=0, C=1, Q=2",") inventaria uma ordem que não existe: por que \"Q\" seria \"maior\" que \"S\"? O jeito certo é o ",[15,9410,9411],{},"one-hot encoding",": cada categoria vira sua própria coluna binária.",[96,9414,9416],{"className":98,"code":9415,"language":100,"meta":57,"style":57},"from sklearn.preprocessing import OneHotEncoder\nencoder = OneHotEncoder()\nXcatTratadoHot = encoder.fit_transform(XcatTratado)\n",[87,9417,9418,9423,9428],{"__ignoreMap":57},[104,9419,9420],{"class":106,"line":107},[104,9421,9422],{},"from sklearn.preprocessing import OneHotEncoder\n",[104,9424,9425],{"class":106,"line":58},[104,9426,9427],{},"encoder = OneHotEncoder()\n",[104,9429,9430],{"class":106,"line":118},[104,9431,9432],{},"XcatTratadoHot = encoder.fit_transform(XcatTratado)\n",[433,9434,9435],{},[11,9436,9437,9439,9440,1813,9442,9444],{},[15,9438,1632],{}," as 2 colunas categóricas (",[87,9441,9248],{},[87,9443,9271],{},") viram 5 colunas binárias (2 pra sexo, já que uma sobra redundante com a outra, e 3 pra porto de embarque). Nenhuma delas tem uma ordem numérica implícita, só \"é\" ou \"não é\" aquela categoria.",[79,9446,9448],{"id":9447},"meu-próprio-transformer-nem-tudo-é-classificador","Meu próprio Transformer: nem tudo é classificador",[11,9450,9451,9452,4319,9455,9457,9458,9461,9462,1214],{},"Até aqui, toda classe própria dessa playlist herdava de ",[87,9453,9454],{},"ClassifierMixin",[87,9456,1509],{},", porque sempre terminava em ",[87,9459,9460],{},".predict()",". Aqui o objetivo é diferente: só transformar o dado, sem prever nada. Pra isso existe o ",[87,9463,9464],{},"TransformerMixin",[96,9466,9468],{"className":98,"code":9467,"language":100,"meta":57,"style":57},"from sklearn.base import BaseEstimator, TransformerMixin\n\nclass AtributosDesejados(BaseEstimator, TransformerMixin):\n    def __init__(self):\n        self.colunas_indesejadas = ['PassengerId', 'Name', 'Ticket', 'Cabin']\n    def fit(self, X, y=None):\n        return self\n    def transform(self, X, y=None):\n        return X.drop(self.colunas_indesejadas, axis=1)\n",[87,9469,9470,9475,9479,9484,9489,9494,9499,9503,9508],{"__ignoreMap":57},[104,9471,9472],{"class":106,"line":107},[104,9473,9474],{},"from sklearn.base import BaseEstimator, TransformerMixin\n",[104,9476,9477],{"class":106,"line":58},[104,9478,300],{"emptyLinePlaceholder":63},[104,9480,9481],{"class":106,"line":118},[104,9482,9483],{},"class AtributosDesejados(BaseEstimator, TransformerMixin):\n",[104,9485,9486],{"class":106,"line":124},[104,9487,9488],{},"    def __init__(self):\n",[104,9490,9491],{"class":106,"line":308},[104,9492,9493],{},"        self.colunas_indesejadas = ['PassengerId', 'Name', 'Ticket', 'Cabin']\n",[104,9495,9496],{"class":106,"line":417},[104,9497,9498],{},"    def fit(self, X, y=None):\n",[104,9500,9501],{"class":106,"line":422},[104,9502,1482],{},[104,9504,9505],{"class":106,"line":428},[104,9506,9507],{},"    def transform(self, X, y=None):\n",[104,9509,9510],{"class":106,"line":1692},[104,9511,9512],{},"        return X.drop(self.colunas_indesejadas, axis=1)\n",[11,9514,9515,9516,9519,9520,9523,9524,9526],{},"Junto com ",[87,9517,9518],{},"AtributosNumericos"," (separa só as colunas numéricas) e ",[87,9521,9522],{},"AtributosCategoricos"," (separa só as categóricas), o professor monta dois ",[87,9525,1501],{}," paralelos, um pra cada tipo de dado:",[96,9528,9530],{"className":98,"code":9529,"language":100,"meta":57,"style":57},"pipenum = Pipeline([\n    ('atributos_numericos', AtributosNumericos()),\n    ('imputer', SimpleImputer(strategy='median')),\n    ('scaler', StandardScaler())\n])\npipecat = Pipeline([\n    ('atributos_categoricos', AtributosCategoricos()),\n    ('imputer', SimpleImputer(strategy='most_frequent')),\n    ('encoder', OneHotEncoder())\n])\n",[87,9531,9532,9537,9542,9547,9552,9556,9561,9566,9571,9576],{"__ignoreMap":57},[104,9533,9534],{"class":106,"line":107},[104,9535,9536],{},"pipenum = Pipeline([\n",[104,9538,9539],{"class":106,"line":58},[104,9540,9541],{},"    ('atributos_numericos', AtributosNumericos()),\n",[104,9543,9544],{"class":106,"line":118},[104,9545,9546],{},"    ('imputer', SimpleImputer(strategy='median')),\n",[104,9548,9549],{"class":106,"line":124},[104,9550,9551],{},"    ('scaler', StandardScaler())\n",[104,9553,9554],{"class":106,"line":308},[104,9555,5907],{},[104,9557,9558],{"class":106,"line":417},[104,9559,9560],{},"pipecat = Pipeline([\n",[104,9562,9563],{"class":106,"line":422},[104,9564,9565],{},"    ('atributos_categoricos', AtributosCategoricos()),\n",[104,9567,9568],{"class":106,"line":428},[104,9569,9570],{},"    ('imputer', SimpleImputer(strategy='most_frequent')),\n",[104,9572,9573],{"class":106,"line":1692},[104,9574,9575],{},"    ('encoder', OneHotEncoder())\n",[104,9577,9578],{"class":106,"line":1698},[104,9579,5907],{},[11,9581,5879,9582,9585],{},[87,9583,9584],{},"FeatureUnion"," junta a saída dos dois num só conjunto de colunas, lado a lado:",[96,9587,9589],{"className":98,"code":9588,"language":100,"meta":57,"style":57},"from sklearn.pipeline import FeatureUnion\n\nunecaracteristicas = FeatureUnion([\n    ('pipenum', pipenum),\n    ('pipecat', pipecat)\n])\n",[87,9590,9591,9596,9600,9605,9610,9615],{"__ignoreMap":57},[104,9592,9593],{"class":106,"line":107},[104,9594,9595],{},"from sklearn.pipeline import FeatureUnion\n",[104,9597,9598],{"class":106,"line":58},[104,9599,300],{"emptyLinePlaceholder":63},[104,9601,9602],{"class":106,"line":118},[104,9603,9604],{},"unecaracteristicas = FeatureUnion([\n",[104,9606,9607],{"class":106,"line":124},[104,9608,9609],{},"    ('pipenum', pipenum),\n",[104,9611,9612],{"class":106,"line":308},[104,9613,9614],{},"    ('pipecat', pipecat)\n",[104,9616,9617],{"class":106,"line":417},[104,9618,5907],{},[433,9620,9621],{},[11,9622,9623,9625],{},[15,9624,1632],{}," as 7 colunas originais (depois de descartar as 4 inúteis) viram 10 colunas tratadas: 5 numéricas (já imputadas e normalizadas) mais 5 categóricas (já imputadas e viradas one-hot).",[11,9627,9628,9629,1214],{},"O pipeline final encadeia tudo, do dado cru até o classificador, numa única chamada de ",[87,9630,9631],{},".fit()",[96,9633,9635],{"className":98,"code":9634,"language":100,"meta":57,"style":57},"preproc = Pipeline([\n    ('atributos_desejados', AtributosDesejados()),\n    ('unecaracteristicas', unecaracteristicas),\n    ('to_dense', DenseTransformer())\n])\n\nclf = Pipeline([\n    ('preproc', preproc),\n    ('classificador', RandomForestClassifier())\n])\nclf.fit(X, y)\n",[87,9636,9637,9642,9647,9652,9657,9661,9665,9670,9675,9680,9684],{"__ignoreMap":57},[104,9638,9639],{"class":106,"line":107},[104,9640,9641],{},"preproc = Pipeline([\n",[104,9643,9644],{"class":106,"line":58},[104,9645,9646],{},"    ('atributos_desejados', AtributosDesejados()),\n",[104,9648,9649],{"class":106,"line":118},[104,9650,9651],{},"    ('unecaracteristicas', unecaracteristicas),\n",[104,9653,9654],{"class":106,"line":124},[104,9655,9656],{},"    ('to_dense', DenseTransformer())\n",[104,9658,9659],{"class":106,"line":308},[104,9660,5907],{},[104,9662,9663],{"class":106,"line":417},[104,9664,300],{"emptyLinePlaceholder":63},[104,9666,9667],{"class":106,"line":422},[104,9668,9669],{},"clf = Pipeline([\n",[104,9671,9672],{"class":106,"line":428},[104,9673,9674],{},"    ('preproc', preproc),\n",[104,9676,9677],{"class":106,"line":1692},[104,9678,9679],{},"    ('classificador', RandomForestClassifier())\n",[104,9681,9682],{"class":106,"line":1698},[104,9683,5907],{},[104,9685,9686],{"class":106,"line":1704},[104,9687,9688],{},"clf.fit(X, y)\n",[79,9690,9692],{"id":9691},"o-mesmo-erro-de-sempre-com-consequência-de-verdade","O mesmo erro de sempre, com consequência de verdade",[96,9694,9696],{"className":98,"code":9695,"language":100,"meta":57,"style":57},"y_pred = clf.predict(X)\naccuracy_score(y, y_pred)\n",[87,9697,9698,9703],{"__ignoreMap":57},[104,9699,9700],{"class":106,"line":107},[104,9701,9702],{},"y_pred = clf.predict(X)\n",[104,9704,9705],{"class":106,"line":58},[104,9706,9707],{},"accuracy_score(y, y_pred)\n",[433,9709,9710],{},[11,9711,9712,9714],{},[15,9713,1632],{}," 0.9798. Quase 98% de acerto.",[11,9716,9717,9718,9721,9722,9725],{},"Se você ",[20,9719,9720],{"href":5646},"já leu o post sobre validação cruzada"," dessa playlist, essa acurácia deveria acender um alerta: é medida no ",[15,9723,9724],{},"mesmo"," dado que treinou. A prova de que é decoreba, não aprendizado:",[96,9727,9729],{"className":98,"code":9728,"language":100,"meta":57,"style":57},"from sklearn.model_selection import cross_val_score\nscores = cross_val_score(clf, X, y)\n",[87,9730,9731,9736],{"__ignoreMap":57},[104,9732,9733],{"class":106,"line":107},[104,9734,9735],{},"from sklearn.model_selection import cross_val_score\n",[104,9737,9738],{"class":106,"line":58},[104,9739,9740],{},"scores = cross_val_score(clf, X, y)\n",[433,9742,9743],{},[11,9744,9745,9747],{},[15,9746,1632],{}," 0.807 de média (0.765, 0.815, 0.854, 0.775, 0.826 nas 5 dobras).",[11,9749,9750],{},"Uma queda de quase 17 pontos percentuais entre \"acurácia no treino\" e \"acurácia honesta\". A Random Forest, sem limite de árvore nenhum, memoriza boa parte dos 891 passageiros individualmente, e o número de 98% não media capacidade de generalizar, media capacidade de decorar.",[79,9752,9754],{"id":9753},"tentando-bater-o-80-stacking-e-uma-rede-neural","Tentando bater o 80%: stacking e uma rede neural",[11,9756,9757,9758,9760],{},"O professor tenta duas coisas pra melhorar o 0.807 honesto. Primeiro, ",[87,9759,8972],{}," com 8 modelos bem diferentes entre si de uma vez (Random Forest, Extra Trees, uma rede neural, SGD, Ridge, KNN, Naive Bayes, regressão logística):",[433,9762,9763],{},[11,9764,9765,9767],{},[15,9766,1632],{}," 0.824 de validação cruzada. Uma melhora real, ainda que pequena.",[11,9769,9770,9771,9774,9775,9778,9779,9783],{},"Depois, uma rede neural sozinha (",[87,9772,9773],{},"MLPClassifier",", sigla de ",[34,9776,9777],{},"Multi-Layer Perceptron",", um Perceptron ",[20,9780,9782],{"href":9781},"\u002Fplaylists\u002Fpattern-recognition\u002Fensembles","que eu já vi antes nessa playlist"," empilhado em várias camadas, o assunto de uma matéria própria que ainda não é o foco aqui):",[433,9785,9786],{},[11,9787,9788,9790],{},[15,9789,1632],{}," 0.822 de validação cruzada, praticamente empatada com o stacking de 8 modelos.",[11,9792,9793,9794,9796],{},"Nenhum dos dois é um salto gigante sobre o 0.807 da floresta sozinha, mas os dois batem, de forma consistente, a versão mais simples. Diferente ",[20,9795,4143],{"href":9781}," (onde um modelo simples bateu todo ensemble tentado), aqui a complexidade extra realmente ajudou, um pouco. A lição de antes continua valendo: não dá pra saber qual vai ganhar sem medir os dois com a mesma régua.",[79,9798,1873],{"id":1872},[1875,9800,9801,9809],{},[1878,9802,9803],{},[1881,9804,9805,9807],{},[1884,9806,1887],{"align":1886},[1884,9808,1890],{"align":1886},[1892,9810,9811,9819,9827],{},[1881,9812,9813,9816],{},[1897,9814,9815],{"align":1886},"Todo dataset até aqui já vinha completo",[1897,9817,9818],{"align":1886},"Dado faltando é normal em dado real, e a solução (imputação) precisa de uma estratégia pensada, não só \"descartar a linha\"",[1881,9820,9821,9824],{},[1897,9822,9823],{"align":1886},"Categoria vira número de algum jeito",[1897,9825,9826],{"align":1886},"One-hot encoding evita inventar uma ordem que não existe entre categorias",[1881,9828,9829,9832],{},[1897,9830,9831],{"align":1886},"Validação cruzada é mais honesta que medir no treino",[1897,9833,9834],{"align":1886},"Mesmo alerta de sempre, mas dessa vez numa Random Forest real: 98% no treino contra 81% de verdade",[79,9836,1943],{"id":1942},[11,9838,9839],{},"Reproduzi o pipeline inteiro com semente fixa (o notebook original não fixa nenhuma), pra ter um número estável e poder comparar as três abordagens sob o mesmo critério exato de validação cruzada.",[96,9841,9843],{"className":98,"code":9842,"language":100,"meta":57,"style":57},"cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nscores_rf = cross_val_score(clf_rf, X, y, cv=cv)\nscores_stacking = cross_val_score(clf_stacking, X, y, cv=cv)\nscores_mlp = cross_val_score(clf_mlp, X, y, cv=cv)\n",[87,9844,9845,9849,9854,9859],{"__ignoreMap":57},[104,9846,9847],{"class":106,"line":107},[104,9848,9098],{},[104,9850,9851],{"class":106,"line":58},[104,9852,9853],{},"scores_rf = cross_val_score(clf_rf, X, y, cv=cv)\n",[104,9855,9856],{"class":106,"line":118},[104,9857,9858],{},"scores_stacking = cross_val_score(clf_stacking, X, y, cv=cv)\n",[104,9860,9861],{"class":106,"line":124},[104,9862,9863],{},"scores_mlp = cross_val_score(clf_mlp, X, y, cv=cv)\n",[1875,9865,9866,9880],{},[1878,9867,9868],{},[1881,9869,9870,9872,9875,9878],{},[1884,9871,341],{"align":1886},[1884,9873,9874],{"align":3190},"Acurácia (treino, decoreba)",[1884,9876,9877],{"align":3190},"Acurácia (validação cruzada)",[1884,9879,9127],{"align":3190},[1892,9881,9882,9896,9914],{},[1881,9883,9884,9887,9890,9893],{},[1897,9885,9886],{"align":1886},"Random Forest",[1897,9888,9889],{"align":3190},"0.9798",[1897,9891,9892],{"align":3190},"0.8092",[1897,9894,9895],{"align":3190},"0.0268",[1881,9897,9898,9901,9904,9909],{},[1897,9899,9900],{"align":1886},"Stacking (8 modelos)",[1897,9902,9903],{"align":3190},"(não medi)",[1897,9905,9906],{"align":3190},[15,9907,9908],{},"0.8283",[1897,9910,9911],{"align":3190},[15,9912,9913],{},"0.0077",[1881,9915,9916,9919,9921,9924],{},[1897,9917,9918],{"align":1886},"Rede neural (MLP)",[1897,9920,9903],{"align":3190},[1897,9922,9923],{"align":3190},"0.8182",[1897,9925,9926],{"align":3190},"0.0197",[11,9928,9929,9930,9933],{},"Os números batem de perto com os do notebook original (0.807, 0.824, 0.822), confirmando que a diferença entre os três não foi sorte de uma rodada específica. E repara no desvio padrão: o stacking não é só o que teve a melhor média, é também o mais ",[15,9931,9932],{},"estável"," de dobra pra dobra (0.0077 contra 0.0268 da floresta sozinha), sinal de que combinar vários modelos diferentes amorteceu parte da variação que cada um sozinho carrega.",[1985,9935,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":9937},[9938,9939,9940,9941,9942,9943,9944,9945],{"id":9210,"depth":58,"text":9211},{"id":9298,"depth":58,"text":9299},{"id":9376,"depth":58,"text":9377},{"id":9447,"depth":58,"text":9448},{"id":9691,"depth":58,"text":9692},{"id":9753,"depth":58,"text":9754},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 7: o professor monta um pipeline completo de pré-processamento em cima do dataset clássico do Titanic, dado faltando e tudo, e mostra o mesmo erro de medir no dado de treino que já vimos antes, só que dessa vez com consequência real.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Ftitanic",{"title":9202,"description":9946},"pt\u002Fplaylists\u002Fpattern-recognition\u002Ftitanic",[9952,9953,6967],"dados-faltantes","one-hot-encoding","719_Oxdjy5LSzenMUePhyEOi3IYw5ZxAq8hA0dD52WY",{"id":9956,"title":9957,"body":9958,"cover":3,"date":9189,"description":11450,"extension":61,"meta":11451,"navigation":63,"order":1692,"path":11452,"playlist":2003,"seo":11453,"status":66,"stem":11454,"tags":11455,"__hash__":11459},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fkmeans.md","K-means: Quando Agrupar Vira um Truque de Engenharia de Features",{"type":8,"value":9959,"toc":11440},[9960,9966,9970,9976,9980,10091,10105,10112,10126,10132,10559,10961,10965,11001,11006,11009,11013,11049,11064,11071,11082,11086,11093,11126,11138,11167,11175,11201,11204,11219,11222,11226,11232,11280,11283,11285,11323,11325,11335,11364,11432,11438],[11,9961,9962,9963,9965],{},"Aula 8a, 8b e 8c, e a matéria muda de regime pela primeira vez: até aqui, todo modelo aprendia a prever um rótulo que já vinha no dado (",[87,9964,166],{},"). K-means não recebe rótulo nenhum, só encontra grupos.",[79,9967,9969],{"id":9968},"o-problema-separar-sem-saber-a-resposta","O problema: separar sem saber a resposta",[11,9971,9972,9973,9975],{},"O professor volta pro Iris, mas dessa vez esconde a espécie (",[87,9974,166],{},") e passa só as duas variáveis (comprimento e largura da pétala) pro algoritmo. A pergunta muda de \"que espécie é essa flor?\" pra \"quantos grupos naturais existem aqui, e onde ficam?\".",[79,9977,9979],{"id":9978},"k-means-na-unha","K-means na unha",[96,9981,9983],{"className":98,"code":9982,"language":100,"meta":57,"style":57},"from sklearn.base import BaseEstimator, ClusterMixin, TransformerMixin\n\nclass KMeans(BaseEstimator, ClusterMixin, TransformerMixin):\n    def __init__(self, n_clusters=3, max_iter=100):\n        self.n_clusters = n_clusters\n        self.max_iter = max_iter\n\n    def fit(self, X, y=None):\n        self.centroids = X[random.sample(range(len(X)), self.n_clusters)]\n        max_iter = self.max_iter\n        while max_iter > 0:\n            max_iter -= 1\n            y_pred = self.predict(X)\n            for i in range(self.n_clusters):\n                self.centroids[i] = np.mean(X[y_pred==i], axis=0)\n            if np.allclose(self.centroids, self.previous_centroids[-1], atol=1e-9):\n                break\n        return self\n\n    def predict(self, X):\n        # atribui cada ponto ao centroide mais próximo\n        ...\n",[87,9984,9985,9990,9994,9999,10004,10009,10013,10017,10021,10026,10031,10036,10041,10045,10050,10055,10060,10065,10070,10075,10080,10086],{"__ignoreMap":57},[104,9986,9987],{"class":106,"line":107},[104,9988,9989],{},"from sklearn.base import BaseEstimator, ClusterMixin, TransformerMixin\n",[104,9991,9992],{"class":106,"line":58},[104,9993,300],{"emptyLinePlaceholder":63},[104,9995,9996],{"class":106,"line":118},[104,9997,9998],{},"class KMeans(BaseEstimator, ClusterMixin, TransformerMixin):\n",[104,10000,10001],{"class":106,"line":124},[104,10002,10003],{},"    def __init__(self, n_clusters=3, max_iter=100):\n",[104,10005,10006],{"class":106,"line":308},[104,10007,10008],{},"        self.n_clusters = n_clusters\n",[104,10010,10011],{"class":106,"line":417},[104,10012,1666],{},[104,10014,10015],{"class":106,"line":422},[104,10016,300],{"emptyLinePlaceholder":63},[104,10018,10019],{"class":106,"line":428},[104,10020,9498],{},[104,10022,10023],{"class":106,"line":1692},[104,10024,10025],{},"        self.centroids = X[random.sample(range(len(X)), self.n_clusters)]\n",[104,10027,10028],{"class":106,"line":1698},[104,10029,10030],{},"        max_iter = self.max_iter\n",[104,10032,10033],{"class":106,"line":1704},[104,10034,10035],{},"        while max_iter > 0:\n",[104,10037,10038],{"class":106,"line":1710},[104,10039,10040],{},"            max_iter -= 1\n",[104,10042,10043],{"class":106,"line":1716},[104,10044,1701],{},[104,10046,10047],{"class":106,"line":1722},[104,10048,10049],{},"            for i in range(self.n_clusters):\n",[104,10051,10052],{"class":106,"line":1727},[104,10053,10054],{},"                self.centroids[i] = np.mean(X[y_pred==i], axis=0)\n",[104,10056,10057],{"class":106,"line":1732},[104,10058,10059],{},"            if np.allclose(self.centroids, self.previous_centroids[-1], atol=1e-9):\n",[104,10061,10062],{"class":106,"line":1737},[104,10063,10064],{},"                break\n",[104,10066,10068],{"class":106,"line":10067},18,[104,10069,1482],{},[104,10071,10073],{"class":106,"line":10072},19,[104,10074,300],{"emptyLinePlaceholder":63},[104,10076,10078],{"class":106,"line":10077},20,[104,10079,1487],{},[104,10081,10083],{"class":106,"line":10082},21,[104,10084,10085],{},"        # atribui cada ponto ao centroide mais próximo\n",[104,10087,10089],{"class":106,"line":10088},22,[104,10090,7972],{},[11,10092,10093,10094,134,10097,134,10099,10101,10102,10104],{},"Repara na assinatura da classe: ",[87,10095,10096],{},"ClusterMixin",[15,10098,8775],{},[87,10100,9464],{}," juntos, a primeira vez que uma classe dessa playlist herda de duas coisas ao mesmo tempo além de ",[87,10103,1497],{},". Isso não é acaso, e o motivo vai ficar claro mais adiante no post.",[11,10106,10107,10108,10111],{},"O algoritmo (chamado ",[15,10109,10110],{},"K-means de Lloyd",", o mais comum) é só dois passos repetidos até parar de mudar:",[3826,10113,10114,10120],{},[360,10115,10116,10119],{},[15,10117,10118],{},"Atribuir",": cada ponto vai pro centroide mais próximo (distância euclidiana).",[360,10121,10122,10125],{},[15,10123,10124],{},"Recalcular",": cada centroide vira a média dos pontos que foram atribuídos a ele.",[11,10127,10128,10129,523],{},"O Bishop formaliza os dois passos como a minimização de uma ",[15,10130,10131],{},"medida de distorção",[11,10133,10134],{},[104,10135,10137,10217],{"className":10136},[148],[104,10138,10140],{"className":10139},[152],[154,10141,10142],{"xmlns":156},[158,10143,10144,10214],{},[161,10145,10146,10149,10151,10165,10179,10190,10193,10199,10201,10208],{},[164,10147,10148],{},"J",[168,10150,170],{},[490,10152,10153,10155,10163],{},[168,10154,494],{},[161,10156,10157,10159,10161],{},[164,10158,499],{},[168,10160,170],{},[483,10162,485],{},[164,10164,506],{},[490,10166,10167,10169,10177],{},[168,10168,494],{},[161,10170,10171,10173,10175],{},[164,10172,5374],{},[168,10174,170],{},[483,10176,485],{},[164,10178,5339],{},[515,10180,10181,10184],{},[164,10182,10183],{},"r",[161,10185,10186,10188],{},[164,10187,499],{},[164,10189,5374],{},[164,10191,10192],{"mathvariant":959},"∥",[515,10194,10195,10197],{},[164,10196,178],{"mathvariant":473},[164,10198,499],{},[168,10200,530],{},[515,10202,10203,10206],{},[164,10204,10205],{"mathvariant":2106},"μ",[164,10207,5374],{},[539,10209,10210,10212],{},[164,10211,10192],{"mathvariant":959},[483,10213,488],{},[186,10215,10216],{"encoding":188},"J = \\sum_{n=1}^{N}\\sum_{k=1}^{K} r_{nk}\\|\\mathbf{x}_n - \\boldsymbol{\\mu}_k\\|^2",[104,10218,10220,10239,10475],{"className":10219,"ariaHidden":194},[193],[104,10221,10223,10226,10230,10233,10236],{"className":10222},[198],[104,10224],{"className":10225,"style":3515},[202],[104,10227,10148],{"className":10228,"style":10229},[207,208],"margin-right:0.0962em;",[104,10231],{"className":10232,"style":214},[213],[104,10234,170],{"className":10235},[218],[104,10237],{"className":10238,"style":214},[213],[104,10240,10242,10246,10309,10312,10375,10378,10423,10426,10466,10469,10472],{"className":10241},[198],[104,10243],{"className":10244,"style":10245},[202],"height:1.2809em;vertical-align:-0.2997em;",[104,10247,10249,10252],{"className":10248},[687],[104,10250,494],{"className":10251,"style":693},[687,691,692],[104,10253,10255],{"className":10254},[697],[104,10256,10258,10301],{"className":10257},[605,606],[104,10259,10261,10298],{"className":10260},[610],[104,10262,10264,10284],{"className":10263,"style":707},[614],[104,10265,10266,10269],{"style":710},[104,10267],{"className":10268,"style":714},[622],[104,10270,10272],{"className":10271},[627,628,629,630],[104,10273,10275,10278,10281],{"className":10274},[207,630],[104,10276,499],{"className":10277},[207,208,630],[104,10279,170],{"className":10280},[218,630],[104,10282,485],{"className":10283},[207,630],[104,10285,10286,10289],{"style":732},[104,10287],{"className":10288,"style":714},[622],[104,10290,10292],{"className":10291},[627,628,629,630],[104,10293,10295],{"className":10294},[207,630],[104,10296,506],{"className":10297,"style":745},[207,208,630],[104,10299,667],{"className":10300},[666],[104,10302,10304],{"className":10303},[610],[104,10305,10307],{"className":10306,"style":755},[614],[104,10308],{},[104,10310],{"className":10311,"style":683},[213],[104,10313,10315,10318],{"className":10314},[687],[104,10316,494],{"className":10317,"style":693},[687,691,692],[104,10319,10321],{"className":10320},[697],[104,10322,10324,10367],{"className":10323},[605,606],[104,10325,10327,10364],{"className":10326},[610],[104,10328,10330,10350],{"className":10329,"style":707},[614],[104,10331,10332,10335],{"style":710},[104,10333],{"className":10334,"style":714},[622],[104,10336,10338],{"className":10337},[627,628,629,630],[104,10339,10341,10344,10347],{"className":10340},[207,630],[104,10342,5374],{"className":10343,"style":5422},[207,208,630],[104,10345,170],{"className":10346},[218,630],[104,10348,485],{"className":10349},[207,630],[104,10351,10352,10355],{"style":732},[104,10353],{"className":10354,"style":714},[622],[104,10356,10358],{"className":10357},[627,628,629,630],[104,10359,10361],{"className":10360},[207,630],[104,10362,5339],{"className":10363,"style":5354},[207,208,630],[104,10365,667],{"className":10366},[666],[104,10368,10370],{"className":10369},[610],[104,10371,10373],{"className":10372,"style":755},[614],[104,10374],{},[104,10376],{"className":10377,"style":683},[213],[104,10379,10381,10384],{"className":10380},[207],[104,10382,10183],{"className":10383,"style":2165},[207,208],[104,10385,10387],{"className":10386},[697],[104,10388,10390,10415],{"className":10389},[605,606],[104,10391,10393,10412],{"className":10392},[610],[104,10394,10396],{"className":10395,"style":5409},[614],[104,10397,10399,10402],{"style":10398},"top:-2.55em;margin-left:-0.0278em;margin-right:0.05em;",[104,10400],{"className":10401,"style":714},[622],[104,10403,10405],{"className":10404},[627,628,629,630],[104,10406,10408],{"className":10407},[207,630],[104,10409,10411],{"className":10410,"style":5422},[207,208,630],"nk",[104,10413,667],{"className":10414},[666],[104,10416,10418],{"className":10417},[610],[104,10419,10421],{"className":10420,"style":807},[614],[104,10422],{},[104,10424,10192],{"className":10425},[207],[104,10427,10429,10432],{"className":10428},[207],[104,10430,178],{"className":10431},[207,570],[104,10433,10435],{"className":10434},[697],[104,10436,10438,10458],{"className":10437},[605,606],[104,10439,10441,10455],{"className":10440},[610],[104,10442,10444],{"className":10443,"style":785},[614],[104,10445,10446,10449],{"style":788},[104,10447],{"className":10448,"style":714},[622],[104,10450,10452],{"className":10451},[627,628,629,630],[104,10453,499],{"className":10454},[207,208,630],[104,10456,667],{"className":10457},[666],[104,10459,10461],{"className":10460},[610],[104,10462,10464],{"className":10463,"style":807},[614],[104,10465],{},[104,10467],{"className":10468,"style":235},[213],[104,10470,530],{"className":10471},[239],[104,10473],{"className":10474,"style":235},[213],[104,10476,10478,10481,10530],{"className":10477},[198],[104,10479],{"className":10480,"style":838},[202],[104,10482,10484,10493],{"className":10483},[207],[104,10485,10487],{"className":10486},[207],[104,10488,10490],{"className":10489},[207],[104,10491,10205],{"className":10492},[207,2437],[104,10494,10496],{"className":10495},[697],[104,10497,10499,10521],{"className":10498},[605,606],[104,10500,10502,10518],{"className":10501},[610],[104,10503,10506],{"className":10504,"style":10505},[614],"height:0.242em;",[104,10507,10509,10512],{"style":10508},"top:-2.4559em;margin-right:0.05em;",[104,10510],{"className":10511,"style":714},[622],[104,10513,10515],{"className":10514},[627,628,629,630],[104,10516,5374],{"className":10517,"style":5422},[207,208,630],[104,10519,667],{"className":10520},[666],[104,10522,10524],{"className":10523},[610],[104,10525,10528],{"className":10526,"style":10527},[614],"height:0.2441em;",[104,10529],{},[104,10531,10533,10536],{"className":10532},[207],[104,10534,10192],{"className":10535},[207],[104,10537,10539],{"className":10538},[697],[104,10540,10542],{"className":10541},[605],[104,10543,10545],{"className":10544},[610],[104,10546,10548],{"className":10547,"style":897},[614],[104,10549,10550,10553],{"style":900},[104,10551],{"className":10552,"style":714},[622],[104,10554,10556],{"className":10555},[627,628,629,630],[104,10557,488],{"className":10558},[207,630],[11,10560,7463,10561,10660,10661,10690,10691,10719,10720,10797,10798,10874,10875,10903,10904,10932,10933,7041],{},[104,10562,10564,10590],{"className":10563},[148],[104,10565,10567],{"className":10566},[152],[154,10568,10569],{"xmlns":156},[158,10570,10571,10587],{},[161,10572,10573,10583,10585],{},[515,10574,10575,10577],{},[164,10576,10183],{},[161,10578,10579,10581],{},[164,10580,499],{},[164,10582,5374],{},[168,10584,170],{},[483,10586,485],{},[186,10588,10589],{"encoding":188},"r_{nk}=1",[104,10591,10593,10651],{"className":10592,"ariaHidden":194},[193],[104,10594,10596,10599,10642,10645,10648],{"className":10595},[198],[104,10597],{"className":10598,"style":2826},[202],[104,10600,10602,10605],{"className":10601},[207],[104,10603,10183],{"className":10604,"style":2165},[207,208],[104,10606,10608],{"className":10607},[697],[104,10609,10611,10634],{"className":10610},[605,606],[104,10612,10614,10631],{"className":10613},[610],[104,10615,10617],{"className":10616,"style":5409},[614],[104,10618,10619,10622],{"style":10398},[104,10620],{"className":10621,"style":714},[622],[104,10623,10625],{"className":10624},[627,628,629,630],[104,10626,10628],{"className":10627},[207,630],[104,10629,10411],{"className":10630,"style":5422},[207,208,630],[104,10632,667],{"className":10633},[666],[104,10635,10637],{"className":10636},[610],[104,10638,10640],{"className":10639,"style":807},[614],[104,10641],{},[104,10643],{"className":10644,"style":214},[213],[104,10646,170],{"className":10647},[218],[104,10649],{"className":10650,"style":214},[213],[104,10652,10654,10657],{"className":10653},[198],[104,10655],{"className":10656,"style":2975},[202],[104,10658,485],{"className":10659},[207]," se o ponto ",[104,10662,10664,10677],{"className":10663},[148],[104,10665,10667],{"className":10666},[152],[154,10668,10669],{"xmlns":156},[158,10670,10671,10675],{},[161,10672,10673],{},[164,10674,499],{},[186,10676,499],{"encoding":188},[104,10678,10680],{"className":10679,"ariaHidden":194},[193],[104,10681,10683,10687],{"className":10682},[198],[104,10684],{"className":10685,"style":10686},[202],"height:0.4306em;",[104,10688,499],{"className":10689},[207,208]," pertence ao grupo ",[104,10692,10694,10707],{"className":10693},[148],[104,10695,10697],{"className":10696},[152],[154,10698,10699],{"xmlns":156},[158,10700,10701,10705],{},[161,10702,10703],{},[164,10704,5374],{},[186,10706,5374],{"encoding":188},[104,10708,10710],{"className":10709,"ariaHidden":194},[193],[104,10711,10713,10716],{"className":10712},[198],[104,10714],{"className":10715,"style":268},[202],[104,10717,5374],{"className":10718,"style":5422},[207,208]," (e 0 caso contrário). Fixando os centroides, o ",[104,10721,10723,10745],{"className":10722},[148],[104,10724,10726],{"className":10725},[152],[154,10727,10728],{"xmlns":156},[158,10729,10730,10742],{},[161,10731,10732],{},[515,10733,10734,10736],{},[164,10735,10183],{},[161,10737,10738,10740],{},[164,10739,499],{},[164,10741,5374],{},[186,10743,10744],{"encoding":188},"r_{nk}",[104,10746,10748],{"className":10747,"ariaHidden":194},[193],[104,10749,10751,10754],{"className":10750},[198],[104,10752],{"className":10753,"style":2826},[202],[104,10755,10757,10760],{"className":10756},[207],[104,10758,10183],{"className":10759,"style":2165},[207,208],[104,10761,10763],{"className":10762},[697],[104,10764,10766,10789],{"className":10765},[605,606],[104,10767,10769,10786],{"className":10768},[610],[104,10770,10772],{"className":10771,"style":5409},[614],[104,10773,10774,10777],{"style":10398},[104,10775],{"className":10776,"style":714},[622],[104,10778,10780],{"className":10779},[627,628,629,630],[104,10781,10783],{"className":10782},[207,630],[104,10784,10411],{"className":10785,"style":5422},[207,208,630],[104,10787,667],{"className":10788},[666],[104,10790,10792],{"className":10791},[610],[104,10793,10795],{"className":10794,"style":807},[614],[104,10796],{}," ótimo é óbvio (atribui pro mais próximo, é exatamente o passo 1). Fixando os ",[104,10799,10801,10822],{"className":10800},[148],[104,10802,10804],{"className":10803},[152],[154,10805,10806],{"xmlns":156},[158,10807,10808,10820],{},[161,10809,10810],{},[515,10811,10812,10814],{},[164,10813,10183],{},[161,10815,10816,10818],{},[164,10817,499],{},[164,10819,5374],{},[186,10821,10744],{"encoding":188},[104,10823,10825],{"className":10824,"ariaHidden":194},[193],[104,10826,10828,10831],{"className":10827},[198],[104,10829],{"className":10830,"style":2826},[202],[104,10832,10834,10837],{"className":10833},[207],[104,10835,10183],{"className":10836,"style":2165},[207,208],[104,10838,10840],{"className":10839},[697],[104,10841,10843,10866],{"className":10842},[605,606],[104,10844,10846,10863],{"className":10845},[610],[104,10847,10849],{"className":10848,"style":5409},[614],[104,10850,10851,10854],{"style":10398},[104,10852],{"className":10853,"style":714},[622],[104,10855,10857],{"className":10856},[627,628,629,630],[104,10858,10860],{"className":10859},[207,630],[104,10861,10411],{"className":10862,"style":5422},[207,208,630],[104,10864,667],{"className":10865},[666],[104,10867,10869],{"className":10868},[610],[104,10870,10872],{"className":10871,"style":807},[614],[104,10873],{},", o centroide ótimo é a média dos pontos atribuídos a ele, porque é ali que a derivada de ",[104,10876,10878,10891],{"className":10877},[148],[104,10879,10881],{"className":10880},[152],[154,10882,10883],{"xmlns":156},[158,10884,10885,10889],{},[161,10886,10887],{},[164,10888,10148],{},[186,10890,10148],{"encoding":188},[104,10892,10894],{"className":10893,"ariaHidden":194},[193],[104,10895,10897,10900],{"className":10896},[198],[104,10898],{"className":10899,"style":3515},[202],[104,10901,10148],{"className":10902,"style":10229},[207,208]," zera (é exatamente o passo 2). Cada passo só pode diminuir ",[104,10905,10907,10920],{"className":10906},[148],[104,10908,10910],{"className":10909},[152],[154,10911,10912],{"xmlns":156},[158,10913,10914,10918],{},[161,10915,10916],{},[164,10917,10148],{},[186,10919,10148],{"encoding":188},[104,10921,10923],{"className":10922,"ariaHidden":194},[193],[104,10924,10926,10929],{"className":10925},[198],[104,10927],{"className":10928,"style":3515},[202],[104,10930,10148],{"className":10931,"style":10229},[207,208]," ou empatar, nunca aumentar, então o algoritmo sempre converge, só que pode parar num mínimo local, não necessariamente o melhor agrupamento possível (por isso, na prática, o scikit-learn roda o K-means várias vezes com centroides iniciais diferentes e fica com o resultado de menor ",[104,10934,10936,10949],{"className":10935},[148],[104,10937,10939],{"className":10938},[152],[154,10940,10941],{"xmlns":156},[158,10942,10943,10947],{},[161,10944,10945],{},[164,10946,10148],{},[186,10948,10148],{"encoding":188},[104,10950,10952],{"className":10951,"ariaHidden":194},[193],[104,10953,10955,10958],{"className":10954},[198],[104,10956],{"className":10957,"style":3515},[202],[104,10959,10148],{"className":10960,"style":10229},[207,208],[79,10962,10964],{"id":10963},"interativo-vendo-os-centroides-se-moverem","Interativo: vendo os centroides se moverem",[11,10966,10967,10968,10971,10972,11000],{},"Reconstrução minha do mesmo algoritmo (atribuir, recalcular, repetir), nos 150 pontos reais do Iris, ",[20,10969,10970],{"href":8961},"as mesmas duas variáveis do post de árvores de decisão",". Clica em \"Passo\" e acompanha a inércia (",[104,10973,10975,10988],{"className":10974},[148],[104,10976,10978],{"className":10977},[152],[154,10979,10980],{"xmlns":156},[158,10981,10982,10986],{},[161,10983,10984],{},[164,10985,10148],{},[186,10987,10148],{"encoding":188},[104,10989,10991],{"className":10990,"ariaHidden":194},[193],[104,10992,10994,10997],{"className":10993},[198],[104,10995],{"className":10996,"style":3515},[202],[104,10998,10148],{"className":10999,"style":10229},[207,208],") cair a cada rodada, e as regiões (quem pertence a qual centroide) se ajustarem:",[11002,11003],"k-means-explorer",{":n-clusters":5616,":points":11004,":true-classes":11005,":x-max":8115,":x-min":8116,":y-max":8118,":y-min":331,"class0-label":8120,"class1-label":8121,"class2-label":8122,"x-label":8123,"y-label":8124},"[[1.4,0.2],[1.4,0.2],[1.3,0.2],[1.5,0.2],[1.4,0.2],[1.7,0.4],[1.4,0.3],[1.5,0.2],[1.4,0.2],[1.5,0.1],[1.5,0.2],[1.6,0.2],[1.4,0.1],[1.1,0.1],[1.2,0.2],[1.5,0.4],[1.3,0.4],[1.4,0.3],[1.7,0.3],[1.5,0.3],[1.7,0.2],[1.5,0.4],[1.0,0.2],[1.7,0.5],[1.9,0.2],[1.6,0.2],[1.6,0.4],[1.5,0.2],[1.4,0.2],[1.6,0.2],[1.6,0.2],[1.5,0.4],[1.5,0.1],[1.4,0.2],[1.5,0.2],[1.2,0.2],[1.3,0.2],[1.4,0.1],[1.3,0.2],[1.5,0.2],[1.3,0.3],[1.3,0.3],[1.3,0.2],[1.6,0.6],[1.9,0.4],[1.4,0.3],[1.6,0.2],[1.4,0.2],[1.5,0.2],[1.4,0.2],[4.7,1.4],[4.5,1.5],[4.9,1.5],[4.0,1.3],[4.6,1.5],[4.5,1.3],[4.7,1.6],[3.3,1.0],[4.6,1.3],[3.9,1.4],[3.5,1.0],[4.2,1.5],[4.0,1.0],[4.7,1.4],[3.6,1.3],[4.4,1.4],[4.5,1.5],[4.1,1.0],[4.5,1.5],[3.9,1.1],[4.8,1.8],[4.0,1.3],[4.9,1.5],[4.7,1.2],[4.3,1.3],[4.4,1.4],[4.8,1.4],[5.0,1.7],[4.5,1.5],[3.5,1.0],[3.8,1.1],[3.7,1.0],[3.9,1.2],[5.1,1.6],[4.5,1.5],[4.5,1.6],[4.7,1.5],[4.4,1.3],[4.1,1.3],[4.0,1.3],[4.4,1.2],[4.6,1.4],[4.0,1.2],[3.3,1.0],[4.2,1.3],[4.2,1.2],[4.2,1.3],[4.3,1.3],[3.0,1.1],[4.1,1.3],[6.0,2.5],[5.1,1.9],[5.9,2.1],[5.6,1.8],[5.8,2.2],[6.6,2.1],[4.5,1.7],[6.3,1.8],[5.8,1.8],[6.1,2.5],[5.1,2.0],[5.3,1.9],[5.5,2.1],[5.0,2.0],[5.1,2.4],[5.3,2.3],[5.5,1.8],[6.7,2.2],[6.9,2.3],[5.0,1.5],[5.7,2.3],[4.9,2.0],[6.7,2.0],[4.9,2.0],[5.7,1.8],[6.0,2.1],[4.8,1.8],[4.9,1.8],[5.6,2.1],[5.8,1.6],[6.1,1.9],[6.4,2.0],[5.6,2.2],[5.1,1.5],[5.6,1.4],[6.1,2.3],[5.6,2.4],[5.5,1.8],[4.8,1.8],[5.4,2.1],[5.6,2.4],[5.1,2.3],[5.1,1.9],[5.9,2.3],[5.7,2.5],[5.2,2.3],[5.0,1.9],[5.2,2.0],[5.4,2.3],[5.1,1.8]]","[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2]",[11,11007,11008],{},"Os pontos continuam coloridos pela espécie real, só pra você comparar visualmente: as regiões que o K-means encontra sozinho, sem nunca ver o rótulo, batem bastante com as espécies de verdade, principalmente pra Setosa (que já era claramente separada desde o post de árvores). Clica em \"Reiniciar\" algumas vezes: dependendo de onde os centroides caem por sorteio, o resultado final pode variar um pouco, exatamente a limitação de mínimo local que o Bishop descreve.",[79,11010,11012],{"id":11011},"escolhendo-k-o-método-do-cotovelo","Escolhendo K: o método do cotovelo",[11,11014,11015,11016,11019,11020,11048],{},"Faltou uma pergunta: como escolher quantos grupos (K) procurar? O K-means do scikit-learn expõe ",[87,11017,11018],{},"inertia_",", que é exatamente o ",[104,11021,11023,11036],{"className":11022},[148],[104,11024,11026],{"className":11025},[152],[154,11027,11028],{"xmlns":156},[158,11029,11030,11034],{},[161,11031,11032],{},[164,11033,10148],{},[186,11035,10148],{"encoding":188},[104,11037,11039],{"className":11038,"ariaHidden":194},[193],[104,11040,11042,11045],{"className":11041},[198],[104,11043],{"className":11044,"style":3515},[202],[104,11046,10148],{"className":11047,"style":10229},[207,208]," do Bishop no final do treino:",[96,11050,11052],{"className":98,"code":11051,"language":100,"meta":57,"style":57},"kmeans_k3 = KMeans(n_clusters=3).fit(X)\nkmeans_k8 = KMeans(n_clusters=8).fit(X)\n",[87,11053,11054,11059],{"__ignoreMap":57},[104,11055,11056],{"class":106,"line":107},[104,11057,11058],{},"kmeans_k3 = KMeans(n_clusters=3).fit(X)\n",[104,11060,11061],{"class":106,"line":58},[104,11062,11063],{},"kmeans_k8 = KMeans(n_clusters=8).fit(X)\n",[433,11065,11066],{},[11,11067,11068,11070],{},[15,11069,1632],{}," inércia com K=3: 31.37. Com K=8: 8.31.",[11,11072,11073,11074,11077,11078,11081],{},"Mais grupos sempre reduz (ou empata) a inércia, no limite (K = número de pontos) ela chega a zero, cada ponto vira seu próprio grupo, o que não serve pra nada. O truque é plotar a inércia contra vários valores de K e procurar o ",[15,11075,11076],{},"cotovelo",": o ponto onde a curva para de cair rápido e passa a cair devagar. Antes do cotovelo, cada grupo a mais ainda captura estrutura real. Depois dele, cada grupo a mais só está fatiando ruído. O professor repete esse gráfico pro Iris, pro dataset de vinhos ",[20,11079,11080],{"href":5820},"que já apareceu nessa playlist",", e pro Digits (1797 imagens de dígitos escritos à mão, 8×8 pixels cada), sem tirar um número fechado de nenhum, o objetivo é só mostrar a forma da curva.",[79,11083,11085],{"id":11084},"o-truque-usar-os-grupos-como-feature-nova","O truque: usar os grupos como feature nova",[11,11087,11088,11089,11092],{},"Aqui que a aula 8b vira o post inteiro de cabeça pra baixo. Em vez de usar K-means só pra agrupar, o professor usa ele ",[15,11090,11091],{},"dentro de um pipeline supervisionado",", como uma etapa de pré-processamento:",[96,11094,11096],{"className":98,"code":11095,"language":100,"meta":57,"style":57},"from sklearn.linear_model import RidgeClassifier\nfrom sklearn.model_selection import cross_val_score\nfrom sklearn.pipeline import make_pipeline\n\nmodel = RidgeClassifier()\nscores = cross_val_score(model, X, y, cv=5)\n",[87,11097,11098,11103,11107,11112,11116,11121],{"__ignoreMap":57},[104,11099,11100],{"class":106,"line":107},[104,11101,11102],{},"from sklearn.linear_model import RidgeClassifier\n",[104,11104,11105],{"class":106,"line":58},[104,11106,9735],{},[104,11108,11109],{"class":106,"line":118},[104,11110,11111],{},"from sklearn.pipeline import make_pipeline\n",[104,11113,11114],{"class":106,"line":124},[104,11115,300],{"emptyLinePlaceholder":63},[104,11117,11118],{"class":106,"line":308},[104,11119,11120],{},"model = RidgeClassifier()\n",[104,11122,11123],{"class":106,"line":417},[104,11124,11125],{},"scores = cross_val_score(model, X, y, cv=5)\n",[433,11127,11128],{},[11,11129,11130,11137],{},[15,11131,11132,11133,11136],{},"Saída (Digits, ",[87,11134,11135],{},"RidgeClassifier"," sozinho, direto nos 64 pixels):"," 0.888 de acurácia média.",[96,11139,11141],{"className":98,"code":11140,"language":100,"meta":57,"style":57},"model = make_pipeline(\n    KMeans(n_clusters=50),\n    RidgeClassifier()\n)\nscores = cross_val_score(model, X, y, cv=5)\n",[87,11142,11143,11148,11153,11158,11163],{"__ignoreMap":57},[104,11144,11145],{"class":106,"line":107},[104,11146,11147],{},"model = make_pipeline(\n",[104,11149,11150],{"class":106,"line":58},[104,11151,11152],{},"    KMeans(n_clusters=50),\n",[104,11154,11155],{"class":106,"line":118},[104,11156,11157],{},"    RidgeClassifier()\n",[104,11159,11160],{"class":106,"line":124},[104,11161,11162],{},")\n",[104,11164,11165],{"class":106,"line":308},[104,11166,11125],{},[433,11168,11169],{},[11,11170,11171,11174],{},[15,11172,11173],{},"Saída (mesmo dataset, com K-means de 50 grupos antes do classificador):"," 0.939.",[11,11176,11177,11178,11180,11181,11183,11184,11187,11188,11190,11191,11193,11194,11196,11197,11200],{},"Um salto e tanto, só de encaixar um K-means no meio do caminho. Como isso funciona? É aqui que o ",[87,11179,9464],{}," que eu mencionei lá em cima entra em cena. Dentro de um ",[87,11182,1501],{},", toda etapa que não é a última precisa ter ",[87,11185,11186],{},".transform()",", não ",[87,11189,9460],{},". O K-means do scikit-learn implementa os dois: ",[87,11192,9460],{}," devolve o índice do grupo mais próximo (um número só), mas ",[87,11195,11186],{}," devolve a ",[15,11198,11199],{},"distância até cada um dos K centroides"," (K números). Uma imagem de 64 pixels vira um vetor de 50 números, cada um dizendo \"quão parecida essa imagem é com o protótipo do grupo 1, do grupo 2, ..., do grupo 50\". Cada centroide funciona como um \"dígito prototípico\" aprendido sem rótulo nenhum, e a distância até cada um vira uma feature nova, mais informativa pro classificador linear do que o pixel cru.",[11,11202,11203],{},"O professor ainda usa o Optuna pra buscar o número ideal de grupos (entre 10 e 200):",[433,11205,11206],{},[11,11207,11208,11210,11211,11214,11215,11218],{},[15,11209,1632],{}," o melhor encontrado foi ",[87,11212,11213],{},"n_clusters=136",", com 0.963 de acurácia média em validação cruzada. Testando ",[87,11216,11217],{},"n_clusters=250"," na mão (mais grupos que o Optuna sequer tentou): 0.966, ainda melhor.",[11,11220,11221],{},"Mais grupos, mais \"protótipos\" diferentes pra comparar, mais informação pro classificador final, até onde o professor testou.",[79,11223,11225],{"id":11224},"o-mesmo-truque-num-dataset-maior-mnist","O mesmo truque, num dataset maior: MNIST",[11,11227,11228,11231],{},[87,11229,11230],{},"aula08c"," repete a receita exata no MNIST (60 mil imagens de treino, 28×28 pixels cada, então 784 variáveis por imagem, bem mais que o Digits):",[1875,11233,11234,11244],{},[1878,11235,11236],{},[1881,11237,11238,11241],{},[1884,11239,11240],{"align":1886},"Abordagem",[1884,11242,11243],{"align":3190},"Acurácia de teste",[1892,11245,11246,11256,11268],{},[1881,11247,11248,11253],{},[1897,11249,11250,11252],{"align":1886},[87,11251,11135],{}," direto nos 784 pixels",[1897,11254,11255],{"align":3190},"0.8603",[1881,11257,11258,11265],{},[1897,11259,11260,11262,11263],{"align":1886},[87,11261,5867],{}," + ",[87,11264,11135],{},[1897,11266,11267],{"align":3190},"(praticamente igual, normalizar pixel não ajudou aqui)",[1881,11269,11270,11275],{},[1897,11271,11272,11273],{"align":1886},"K-means (250 grupos) + ",[87,11274,11135],{},[1897,11276,11277],{"align":3190},[15,11278,11279],{},"0.9403",[11,11281,11282],{},"Oito pontos percentuais de ganho, no mesmo dataset, no mesmo classificador linear, só trocando \"64\u002F784 valores de pixel cru\" por \"250 distâncias até protótipos aprendidos sem rótulo\". A ideia de reaproveitar um algoritmo não supervisionado como fonte de features pra um problema supervisionado é mais geral do que parece: qualquer vetor de \"distância até um protótipo\" carrega informação que o pixel cru não carrega sozinho.",[79,11284,1873],{"id":1872},[1875,11286,11287,11295],{},[1878,11288,11289],{},[1881,11290,11291,11293],{},[1884,11292,1887],{"align":1886},[1884,11294,1890],{"align":1886},[1892,11296,11297,11305,11315],{},[1881,11298,11299,11302],{},[1897,11300,11301],{"align":1886},"Todo modelo até aqui aprendia a prever um rótulo dado",[1897,11303,11304],{"align":1886},"K-means agrupa sem nenhum rótulo, só a estrutura geométrica dos dados",[1881,11306,11307,11312],{},[1897,11308,11309,11311],{"align":1886},[87,11310,9464],{}," serve pra etapas que só transformam, sem prever",[1897,11313,11314],{"align":1886},"Um algoritmo de clustering também pode ser um transformador: a distância até cada centroide vira uma feature nova",[1881,11316,11317,11320],{},[1897,11318,11319],{"align":1886},"Mais complexidade nem sempre ajuda",[1897,11321,11322],{"align":1886},"Aqui ajudou bastante: trocar pixel cru por distância-até-protótipo rendeu ganhos de 5 a 8 pontos percentuais em dois datasets diferentes",[79,11324,1943],{"id":1942},[11,11326,11327,11328,11330,11331,11334],{},"Reproduzi a mesma receita (K-means como transformador antes do ",[87,11329,11135],{},") no Digits, com semente fixa e ",[87,11332,11333],{},"n_init=10"," (o K-means roda 10 vezes com centroides iniciais diferentes e fica com o de menor inércia, o padrão do scikit-learn), pra confirmar o efeito de forma reprodutível.",[96,11336,11338],{"className":98,"code":11337,"language":100,"meta":57,"style":57},"cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nridge_scores = cross_val_score(RidgeClassifier(random_state=42), X, y, cv=cv)\nfor k in [50, 136, 250]:\n    model = make_pipeline(KMeans(n_clusters=k, random_state=42, n_init=10), RidgeClassifier(random_state=42))\n    scores = cross_val_score(model, X, y, cv=cv)\n",[87,11339,11340,11344,11349,11354,11359],{"__ignoreMap":57},[104,11341,11342],{"class":106,"line":107},[104,11343,9098],{},[104,11345,11346],{"class":106,"line":58},[104,11347,11348],{},"ridge_scores = cross_val_score(RidgeClassifier(random_state=42), X, y, cv=cv)\n",[104,11350,11351],{"class":106,"line":118},[104,11352,11353],{},"for k in [50, 136, 250]:\n",[104,11355,11356],{"class":106,"line":124},[104,11357,11358],{},"    model = make_pipeline(KMeans(n_clusters=k, random_state=42, n_init=10), RidgeClassifier(random_state=42))\n",[104,11360,11361],{"class":106,"line":308},[104,11362,11363],{},"    scores = cross_val_score(model, X, y, cv=cv)\n",[1875,11365,11366,11376],{},[1878,11367,11368],{},[1881,11369,11370,11372,11374],{},[1884,11371,11240],{"align":1886},[1884,11373,9124],{"align":3190},[1884,11375,9127],{"align":3190},[1892,11377,11378,11391,11404,11417],{},[1881,11379,11380,11385,11388],{},[1897,11381,11382,11384],{"align":1886},[87,11383,11135],{}," direto nos pixels",[1897,11386,11387],{"align":3190},"0.9343",[1897,11389,11390],{"align":3190},"0.0072",[1881,11392,11393,11398,11401],{},[1897,11394,11395,11396],{"align":1886},"K-means (50) + ",[87,11397,11135],{},[1897,11399,11400],{"align":3190},"0.9666",[1897,11402,11403],{"align":3190},"0.0128",[1881,11405,11406,11411,11414],{},[1897,11407,11408,11409],{"align":1886},"K-means (136) + ",[87,11410,11135],{},[1897,11412,11413],{"align":3190},"0.9844",[1897,11415,11416],{"align":3190},"0.0045",[1881,11418,11419,11424,11429],{},[1897,11420,11421,11422],{"align":1886},"K-means (250) + ",[87,11423,11135],{},[1897,11425,11426],{"align":3190},[15,11427,11428],{},"0.9916",[1897,11430,11431],{"align":3190},"0.0056",[11,11433,11434,11435,11437],{},"Meus números saem mais altos que os do notebook original (0.888\u002F0.939\u002F0.963), provavelmente porque fixei ",[87,11436,11333],{}," (o K-means tenta várias inicializações e fica com a melhor, evitando os mínimos locais ruins que o Bishop avisou lá em cima) e uma semente que meu ambiente conseguiu reproduzir de forma estável. Mas a tendência é idêntica à do notebook: quanto mais grupos, melhor a acurácia, e a melhora de \"pixel cru\" pra \"distância até protótipo\" é grande e consistente em qualquer contagem de grupos testada.",[1985,11439,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":11441},[11442,11443,11444,11445,11446,11447,11448,11449],{"id":9968,"depth":58,"text":9969},{"id":9978,"depth":58,"text":9979},{"id":10963,"depth":58,"text":10964},{"id":11011,"depth":58,"text":11012},{"id":11084,"depth":58,"text":11085},{"id":11224,"depth":58,"text":11225},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 8: o professor entra em aprendizado não supervisionado com K-means feito à mão, o método do cotovelo pra escolher K, e um truque que eu não esperava: usar os grupos encontrados como features novas pra um classificador supervisionado.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fkmeans",{"title":9957,"description":11450},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fkmeans",[11456,11457,11458],"k-means","nao-supervisionado","engenharia-de-features","H_gKXhe92KNF_F8iU2dBZdA6JqmGE0bYvaf7v2-JS4w",{"id":11461,"title":11462,"body":11463,"cover":3,"date":9189,"description":12075,"extension":61,"meta":12076,"navigation":63,"order":1698,"path":12077,"playlist":2003,"seo":12078,"status":66,"stem":12079,"tags":12080,"__hash__":12084},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fdbscan-semi-supervised.md","DBSCAN e Aprendizado Semi-Supervisionado: Quando Agrupar Ajuda a Rotular",{"type":8,"value":11464,"toc":12063},[11465,11468,11472,11479,11494,11510,11514,11517,11605,11608,11628,11636,11667,11676,11691,11695,11698,11709,11721,11727,11734,11759,11769,11775,11779,11782,11802,11810,11818,11822,11829,11849,11852,11862,11865,11869,11876,11896,11907,11910,11917,11927,11929,11973,11975,11978,11993,12047,12061],[11,11466,11467],{},"Aula 9a e 9b. O Bishop não cobre nem DBSCAN nem aprendizado semi-supervisionado no livro (os dois ficaram mais populares na literatura depois de 2006), então esse post fica mais em cima do que o professor mostrou, com bem menos citação direta ao livro do que o normal por aqui.",[79,11469,11471],{"id":11470},"onde-o-k-means-quebra-a-cara","Onde o K-means quebra a cara",[11,11473,11474,11475,11478],{},"O professor gera um dataset sintético clássico, ",[87,11476,11477],{},"make_moons",": dois arcos entrelaçados, tipo duas meias-luas encaixadas.",[96,11480,11482],{"className":98,"code":11481,"language":100,"meta":57,"style":57},"from sklearn.datasets import make_moons\nX, y = make_moons(n_samples=1000, noise=0.05, random_state=42)\n",[87,11483,11484,11489],{"__ignoreMap":57},[104,11485,11486],{"class":106,"line":107},[104,11487,11488],{},"from sklearn.datasets import make_moons\n",[104,11490,11491],{"class":106,"line":58},[104,11492,11493],{},"X, y = make_moons(n_samples=1000, noise=0.05, random_state=42)\n",[11,11495,11496,11497,11501,11502,11505,11506,11509],{},"Roda ",[20,11498,11500],{"href":11499},"\u002Fplaylists\u002Fpattern-recognition\u002Fkmeans","o K-means que eu já vi no post anterior"," com K=2 nesse dado, e o resultado é ruim: K-means sempre corta o espaço em regiões convexas (cada ponto vai pro centroide mais próximo, então a fronteira entre grupos é sempre uma reta), mas as duas luas não são convexas, elas se encaixam uma na curva da outra. O K-means acaba cortando quase no meio, ignorando o formato real dos arcos. Medindo a concordância entre o agrupamento do K-means e a divisão real das duas luas (usando o ",[15,11503,11504],{},"índice de Rand ajustado",", uma métrica que vale 1 quando os grupos batem perfeitamente com a divisão real e perto de 0 quando é como sortear ao acaso): ",[15,11507,11508],{},"0.24",". Quase não bateu.",[79,11511,11513],{"id":11512},"dbscan-agrupar-por-densidade-não-por-distância-até-um-centro","DBSCAN: agrupar por densidade, não por distância até um centro",[11,11515,11516],{},"O professor descreve o algoritmo em texto puro, sem fórmula:",[357,11518,11519,11584,11595,11598],{},[360,11520,11521,11522,11552,11553,1866],{},"Pra cada ponto, conta quantos outros pontos estão a uma distância pequena ",[104,11523,11525,11540],{"className":11524},[148],[104,11526,11528],{"className":11527},[152],[154,11529,11530],{"xmlns":156},[158,11531,11532,11537],{},[161,11533,11534],{},[164,11535,11536],{},"ε",[186,11538,11539],{"encoding":188},"\\varepsilon",[104,11541,11543],{"className":11542,"ariaHidden":194},[193],[104,11544,11546,11549],{"className":11545},[198],[104,11547],{"className":11548,"style":10686},[202],[104,11550,11536],{"className":11551},[207,208]," (epsilon) dele. Essa é a ",[15,11554,11555,11556],{},"vizinhança-",[104,11557,11559,11572],{"className":11558},[148],[104,11560,11562],{"className":11561},[152],[154,11563,11564],{"xmlns":156},[158,11565,11566,11570],{},[161,11567,11568],{},[164,11569,11536],{},[186,11571,11539],{"encoding":188},[104,11573,11575],{"className":11574,"ariaHidden":194},[193],[104,11576,11578,11581],{"className":11577},[198],[104,11579],{"className":11580,"style":10686},[202],[104,11582,11536],{"className":11583},[207,208],[360,11585,11586,11587,11590,11591,11594],{},"Se um ponto tem pelo menos ",[87,11588,11589],{},"min_samples"," vizinhos nessa distância (contando ele mesmo), ele é um ",[15,11592,11593],{},"ponto central"," (mora numa região densa).",[360,11596,11597],{},"Todo ponto na vizinhança de um ponto central pertence ao mesmo grupo. Como essa vizinhança pode conter outros pontos centrais, uma cadeia de pontos centrais vizinhos forma um grupo só, não importa o quão longo o \"cordão\" fique.",[360,11599,11600,11601,11604],{},"Todo ponto que não é central e não está na vizinhança de nenhum central é ",[15,11602,11603],{},"ruído"," (rótulo -1), não pertence a grupo nenhum.",[11,11606,11607],{},"A diferença de fundo pro K-means: DBSCAN nunca assume que um grupo tem \"um centro\". Ele só segue regiões densas, então consegue acompanhar formatos tortos, como duas meias-luas.",[96,11609,11611],{"className":98,"code":11610,"language":100,"meta":57,"style":57},"from sklearn.cluster import DBSCAN\ndbscan = DBSCAN(eps=0.05, min_samples=5)\ny_pred = dbscan.fit_predict(X)\n",[87,11612,11613,11618,11623],{"__ignoreMap":57},[104,11614,11615],{"class":106,"line":107},[104,11616,11617],{},"from sklearn.cluster import DBSCAN\n",[104,11619,11620],{"class":106,"line":58},[104,11621,11622],{},"dbscan = DBSCAN(eps=0.05, min_samples=5)\n",[104,11624,11625],{"class":106,"line":118},[104,11626,11627],{},"y_pred = dbscan.fit_predict(X)\n",[433,11629,11630],{},[11,11631,11632,11635],{},[15,11633,11634],{},"Saída (eps=0.05):"," 7 grupos, 77 pontos marcados como ruído.",[11,11637,11638,11666],{},[104,11639,11641,11654],{"className":11640},[148],[104,11642,11644],{"className":11643},[152],[154,11645,11646],{"xmlns":156},[158,11647,11648,11652],{},[161,11649,11650],{},[164,11651,11536],{},[186,11653,11539],{"encoding":188},[104,11655,11657],{"className":11656,"ariaHidden":194},[193],[104,11658,11660,11663],{"className":11659},[198],[104,11661],{"className":11662,"style":10686},[202],[104,11664,11536],{"className":11665},[207,208]," pequeno demais fragmenta o dado inteiro em pedacinhos, porque poucos pontos caem dentro de uma vizinhança tão apertada.",[96,11668,11670],{"className":98,"code":11669,"language":100,"meta":57,"style":57},"dbscan = DBSCAN(eps=0.2, min_samples=5)\n",[87,11671,11672],{"__ignoreMap":57},[104,11673,11674],{"class":106,"line":107},[104,11675,11669],{},[433,11677,11678],{},[11,11679,11680,11683,11684,11687,11688,11690],{},[15,11681,11682],{},"Saída (eps=0.2):"," exatamente 2 grupos, ",[15,11685,11686],{},"zero"," pontos de ruído, índice de Rand ajustado de ",[15,11689,4788],{},": bate perfeitamente com a divisão real das duas luas.",[79,11692,11694],{"id":11693},"interativo-mexendo-em-eps-e-min_samples-ao-vivo","Interativo: mexendo em eps e min_samples ao vivo",[11,11696,11697],{},"Reconstrução minha do algoritmo (numa amostra de 300 pontos das mesmas duas luas, pra rodar rápido no navegador). Mexe nos dois controles e observa como o número de grupos e de pontos de ruído muda:",[11699,11700],"dbscan-explorer",{":initial-eps":11701,":initial-min-samples":332,":points":11702,":x-max":11703,":x-min":11704,":y-max":11705,":y-min":11706,"x-label":11707,"y-label":11708},"0.2","[[0.6225,-0.3721],[1.9043,-0.1363],[-0.0694,0.4561],[0.9339,0.2375],[1.1804,-0.4908],[1.0254,-0.4625],[0.412,-0.286],[-0.6127,0.7298],[0.9481,0.0796],[0.1712,0.0016],[1.292,-0.3773],[0.3722,0.8922],[0.9163,-0.4881],[0.6263,0.866],[0.1712,-0.0556],[0.9715,-0.5275],[1.9931,0.3036],[0.7204,-0.4162],[-0.9279,0.0214],[0.1161,0.2384],[-0.0621,1.0061],[-0.7831,0.6132],[1.5022,-0.4123],[1.5101,-0.3856],[-0.6721,0.8306],[0.6121,0.7783],[1.8842,0.1128],[2.0745,0.4222],[0.0384,0.2894],[1.4003,-0.3676],[1.0046,0.3922],[0.044,0.2312],[0.8199,0.6096],[0.8525,0.4013],[0.7624,-0.3969],[-0.8232,0.5753],[1.005,0.0664],[-0.2738,1.0104],[0.5843,0.9074],[-0.0347,1.0917],[-0.4168,0.8609],[-0.0435,1.0931],[1.9617,0.1827],[0.2362,0.9484],[-0.1524,0.9336],[0.3173,1.0369],[1.1925,-0.5033],[1.0977,-0.3781],[-0.6756,0.7637],[1.1265,-0.4876],[0.2258,-0.0849],[-1.0893,0.1693],[1.2702,-0.3481],[0.0238,1.0035],[2.0211,0.4524],[1.8792,0.0819],[-0.4126,0.8663],[-0.3918,0.9149],[0.4147,-0.2016],[0.8824,0.5321],[1.126,-0.4437],[0.3486,0.9116],[0.9026,0.0651],[0.2267,-0.1147],[0.1929,0.0119],[0.7861,0.6043],[0.8949,0.4939],[1.5902,-0.24],[0.3683,0.9676],[0.261,-0.2189],[-0.5534,0.814],[-0.9743,0.4438],[1.176,-0.5531],[0.4221,-0.2541],[-0.9474,0.4089],[1.8319,0.2377],[-0.1729,1.0277],[-0.1683,1.0479],[0.1566,0.9942],[2.0012,0.3138],[0.095,0.2055],[-0.0297,0.4838],[0.819,0.4685],[-0.8794,0.5028],[-0.1289,0.9931],[-0.6763,0.7113],[-0.7693,0.6125],[-0.0196,0.9564],[0.9332,0.3339],[0.9813,0.2585],[0.424,-0.2975],[-0.4163,0.8682],[0.115,1.0836],[1.0495,-0.4091],[0.7053,0.7522],[0.871,0.3702],[0.1733,-0.1418],[-0.9475,-0.0484],[1.7686,0.0151],[0.186,1.036],[1.1108,-0.4234],[0.1507,0.9867],[0.7902,0.594],[0.2472,-0.1959],[1.7709,-0.1614],[1.0243,-0.486],[-1.1047,0.1436],[0.9234,0.3677],[0.2191,-0.0929],[-0.3621,0.9146],[-0.9521,0.2709],[1.6424,-0.1766],[-0.1871,1.0478],[1.4341,-0.3949],[1.431,-0.3057],[-0.7618,0.7186],[-0.9895,0.1209],[2.0405,0.3612],[0.4011,-0.35],[0.6942,-0.381],[0.378,0.8995],[0.0191,0.1248],[0.2904,-0.1639],[0.0736,0.0406],[-1.0022,0.276],[1.6145,-0.3083],[0.6951,0.758],[-0.8887,0.4015],[0.417,-0.2926],[1.9998,0.097],[-0.9743,0.2466],[-0.7659,0.7278],[0.1085,0.115],[-0.6781,0.6087],[1.872,0.0288],[1.79,-0.0846],[-0.0662,0.3852],[0.1494,-0.1353],[0.1642,-0.0546],[1.0248,0.1024],[-0.7113,0.626],[-1.0288,0.1551],[0.1303,-0.1004],[-0.7001,0.5585],[0.6677,-0.5632],[1.7231,-0.2821],[-0.8929,0.4958],[-0.0381,1.0039],[0.895,0.4512],[-0.2188,0.9388],[1.7972,-0.0936],[-0.6859,0.7415],[0.9297,-0.517],[0.0251,0.3777],[1.9736,0.1427],[1.3943,-0.3992],[1.066,-0.4479],[0.1769,1.0591],[0.4505,-0.3813],[-0.1498,0.9101],[0.6642,-0.4237],[0.0193,0.3177],[0.9501,0.2384],[0.7169,0.6415],[0.8116,0.536],[-1.0306,0.1198],[1.8253,0.2232],[1.6022,-0.317],[0.0995,0.1044],[0.4393,0.8531],[-0.3925,0.9184],[0.7612,-0.3591],[1.5035,-0.4277],[0.6208,0.7106],[-0.287,0.8166],[1.3767,-0.432],[-1.0389,0.3106],[0.7965,0.5345],[2.1312,0.2933],[-0.9932,0.1475],[0.476,-0.3315],[1.0553,0.0386],[1.7577,0.0136],[1.7538,-0.0907],[-0.5036,0.8634],[1.6893,-0.1679],[1.5937,-0.2946],[0.0667,0.101],[-0.4914,0.8638],[0.7527,0.74],[0.1704,0.9778],[-0.0135,0.5474],[1.2276,-0.4057],[0.5094,-0.5011],[1.4132,-0.4749],[0.8942,0.2307],[1.986,0.4779],[1.8255,-0.1199],[0.798,0.5534],[0.8926,-0.4648],[0.9527,0.0642],[-0.8168,0.6014],[0.8292,0.6288],[0.5567,-0.3878],[-0.0227,0.2088],[0.7545,-0.427],[1.9394,0.2505],[-0.9089,0.2076],[0.2391,0.9378],[-0.9086,0.3396],[-0.8343,0.4789],[1.9004,0.3254],[0.9329,0.1422],[0.1033,1.0373],[1.6953,-0.289],[0.2286,1.1078],[0.5895,0.8026],[2.0015,0.1202],[0.0188,0.3959],[0.6775,0.6899],[-0.9205,0.6155],[1.8839,0.0203],[0.8172,-0.4449],[0.1289,0.463],[0.8553,0.4595],[1.7494,-0.2473],[-0.6379,0.6167],[-0.1481,1.022],[0.7028,-0.4736],[0.6643,-0.4785],[0.6243,0.7253],[1.3207,-0.513],[2.026,0.3243],[1.7031,-0.353],[0.437,-0.3331],[0.9168,0.2827],[0.5479,0.8445],[-0.4335,0.9241],[1.6864,-0.1545],[1.0469,-0.4909],[1.982,-0.0765],[0.594,-0.5127],[-0.0771,0.2081],[0.5575,0.7976],[0.857,-0.5003],[-1.0039,0.1106],[0.3767,-0.3132],[0.8872,-0.499],[1.5544,-0.3866],[0.9935,0.0647],[-0.6349,0.7638],[0.4429,0.8925],[-0.9186,0.2783],[0.9822,0.0096],[-0.8881,0.3235],[-0.5537,0.8375],[0.5944,0.8086],[-0.1486,0.9464],[0.5168,-0.3284],[0.6918,0.6162],[-0.2751,0.8524],[0.4946,0.8189],[1.4033,-0.5486],[0.875,0.2434],[-0.2322,0.9582],[0.0396,0.2363],[2.1022,0.4284],[-0.9815,0.2901],[1.9037,0.2547],[1.2988,-0.3973],[0.5192,0.9439],[0.4704,0.8959],[0.2064,0.0938],[0.2693,-0.2072],[1.9919,0.5192],[0.3084,-0.3219],[0.0618,0.12],[0.2692,0.9691],[0.4539,0.808],[1.2866,-0.5198],[1.5766,-0.4242],[1.0076,0.4132],[0.0269,0.2997],[1.9151,-0.0421],[0.3507,-0.2937],[1.5484,-0.2249],[-1.0176,0.0121],[-0.9118,0.3732],[0.099,0.4766],[-0.5718,0.8495],[-0.2355,0.9921],[0.726,-0.4291],[-0.9189,0.6139],[1.934,0.4887],[0.9656,0.4153],[0.345,-0.1873],[0.138,1.0504],[-0.6391,0.8191],[1.7425,-0.077],[-0.507,0.8433]]","2.3","-1.3","1.2","-0.7","x1","x2",[11,11710,8014,11711,11714,11715,11717,11718,11720],{},[87,11712,11713],{},"eps"," bem pequeno, quase todo mundo vira ruído (cinza). Subindo ",[87,11716,11713],{}," aos poucos, os grupos começam a se formar, até que num certo ponto as duas luas aparecem inteiras. Passar demais do ponto certo funde as duas luas numa só. ",[87,11719,11589],{}," funciona parecido: valores altos exigem uma vizinhança mais lotada pra alguém virar ponto central, então grupos ficam mais exigentes (mais ruído, grupos mais \"sólidos\").",[79,11722,11724,11725],{"id":11723},"o-problema-de-não-ter-predict","O problema de não ter ",[87,11726,9460],{},[11,11728,11729,11730,11733],{},"DBSCAN não guarda centroide nenhum, então não tem como perguntar \"esse ponto novo pertence a qual grupo?\" do mesmo jeito que o K-means faz. A solução do professor: treinar um KNN separado, usando só os pontos centrais do DBSCAN (",[87,11731,11732],{},"dbscan.components_",") e o grupo de cada um como rótulo.",[96,11735,11737],{"className":98,"code":11736,"language":100,"meta":57,"style":57},"from sklearn.neighbors import KNeighborsClassifier\nknn = KNeighborsClassifier(n_neighbors=50)\nknn.fit(dbscan.components_, dbscan.labels_[dbscan.core_sample_indices_])\nprint(knn.predict(X_new))\n",[87,11738,11739,11744,11749,11754],{"__ignoreMap":57},[104,11740,11741],{"class":106,"line":107},[104,11742,11743],{},"from sklearn.neighbors import KNeighborsClassifier\n",[104,11745,11746],{"class":106,"line":58},[104,11747,11748],{},"knn = KNeighborsClassifier(n_neighbors=50)\n",[104,11750,11751],{"class":106,"line":118},[104,11752,11753],{},"knn.fit(dbscan.components_, dbscan.labels_[dbscan.core_sample_indices_])\n",[104,11755,11756],{"class":106,"line":124},[104,11757,11758],{},"print(knn.predict(X_new))\n",[433,11760,11761],{},[11,11762,11763,134,11765,11768],{},[15,11764,1632],{},[87,11766,11767],{},"[1, 0, 1, 0]"," pros 4 pontos novos testados.",[11,11770,11771,11772,11774],{},"Mas isso tem um problema: KNN sempre encontra um vizinho mais próximo, não importa a distância, então ele vai \"prever\" um grupo pra qualquer ponto, mesmo um bem longe de tudo, que deveria ser ruído. O ajuste fino: olhar a distância até o vizinho mais próximo e, se for grande demais (o professor usa o próprio ",[87,11773,11713],{}," como corte), marcar como ruído (-1) em vez de forçar num grupo.",[79,11776,11778],{"id":11777},"trocando-de-assunto-e-se-rótulo-for-caro","Trocando de assunto: e se rótulo for caro?",[11,11780,11781],{},"A segunda metade da aula muda de problema, mas reaproveita a ideia de agrupar sem rótulo. Cenário: 1400 imagens de dígitos escritos à mão pra treinar, mas rotular cada uma é trabalho manual caro, então só dá pra rotular 50.",[96,11783,11785],{"className":98,"code":11784,"language":100,"meta":57,"style":57},"n_labeled = 50\nlog_reg = LogisticRegression(solver=\"liblinear\", max_iter=5000)\nlog_reg.fit(X_train[:n_labeled], y_train[:n_labeled])\n",[87,11786,11787,11792,11797],{"__ignoreMap":57},[104,11788,11789],{"class":106,"line":107},[104,11790,11791],{},"n_labeled = 50\n",[104,11793,11794],{"class":106,"line":58},[104,11795,11796],{},"log_reg = LogisticRegression(solver=\"liblinear\", max_iter=5000)\n",[104,11798,11799],{"class":106,"line":118},[104,11800,11801],{},"log_reg.fit(X_train[:n_labeled], y_train[:n_labeled])\n",[433,11803,11804],{},[11,11805,11806,11809],{},[15,11807,11808],{},"Saída (rotulando as 50 primeiras imagens, na ordem que vieram):"," 0.766 de acurácia no teste.",[433,11811,11812],{},[11,11813,11814,11817],{},[15,11815,11816],{},"Saída (se eu tivesse rótulo pras 1400, o teto teórico):"," 0.902.",[79,11819,11821],{"id":11820},"o-truque-escolher-quais-50-rotular-não-quais-vêm-primeiro","O truque: escolher quais 50 rotular, não quais vêm primeiro",[11,11823,11824,11825,11828],{},"Em vez de rotular as 50 primeiras (ordem arbitrária), o professor usa K-means com ",[15,11826,11827],{},"K=50"," (o mesmo orçamento de rótulos!) pra achar 50 grupos nas 1400 imagens sem rótulo, e escolhe, de cada grupo, a imagem mais próxima do centro (a mais \"típica\" daquele grupo):",[96,11830,11832],{"className":98,"code":11831,"language":100,"meta":57,"style":57},"kmeans = KMeans(n_clusters=50)\nX_digits_dist = kmeans.fit_transform(X_train)\nrepresentative_digit_idx = np.argmin(X_digits_dist, axis=0)\n",[87,11833,11834,11839,11844],{"__ignoreMap":57},[104,11835,11836],{"class":106,"line":107},[104,11837,11838],{},"kmeans = KMeans(n_clusters=50)\n",[104,11840,11841],{"class":106,"line":58},[104,11842,11843],{},"X_digits_dist = kmeans.fit_transform(X_train)\n",[104,11845,11846],{"class":106,"line":118},[104,11847,11848],{},"representative_digit_idx = np.argmin(X_digits_dist, axis=0)\n",[11,11850,11851],{},"Só essas 50 imagens representativas são rotuladas manualmente (o professor colou os rótulos certos na mão, célula 8 do notebook). Treinando com só essas 50:",[433,11853,11854],{},[11,11855,11856,11858,11859,11861],{},[15,11857,1632],{}," 0.834. Melhor que os 50 rótulos aleatórios (0.766), com o ",[15,11860,9724],{}," número de rótulos.",[11,11863,11864],{},"Faz sentido: 50 imagens escolhidas pra representar 50 grupos diferentes cobrem mais variedade do que 50 imagens na ordem em que apareceram (que podem repetir o mesmo estilo de \"7\" várias vezes e nunca mostrar um \"3\" torto).",[79,11866,11868],{"id":11867},"propagação-espalhar-o-rótulo-pro-grupo-inteiro","Propagação: espalhar o rótulo pro grupo inteiro",[11,11870,11871,11872,11875],{},"Se a imagem representante do grupo 12 é um \"7\", é razoável supor que ",[15,11873,11874],{},"todo mundo"," no grupo 12 também é um \"7\" (foi por isso que caíram no mesmo grupo, afinal). Propagando o rótulo do representante pra cada membro do cluster:",[96,11877,11879],{"className":98,"code":11878,"language":100,"meta":57,"style":57},"y_train_propagated = np.empty(len(X_train), dtype=np.int32)\nfor i in range(k):\n    y_train_propagated[kmeans.labels_==i] = y_representative_digits[i]\n",[87,11880,11881,11886,11891],{"__ignoreMap":57},[104,11882,11883],{"class":106,"line":107},[104,11884,11885],{},"y_train_propagated = np.empty(len(X_train), dtype=np.int32)\n",[104,11887,11888],{"class":106,"line":58},[104,11889,11890],{},"for i in range(k):\n",[104,11892,11893],{"class":106,"line":118},[104,11894,11895],{},"    y_train_propagated[kmeans.labels_==i] = y_representative_digits[i]\n",[433,11897,11898],{},[11,11899,11900,11902,11903,11906],{},[15,11901,1632],{}," treinando com os 1400 rótulos propagados (mas só 50 verdadeiramente checados à mão): 0.869. E conferindo contra o rótulo real (coisa que só dá pra fazer aqui porque é um exercício, na vida real você não saberia): a propagação acerta ",[15,11904,11905],{},"95.4%"," das vezes.",[11,11908,11909],{},"Um refinamento a mais: descartar, de cada grupo, o 20% mais distante do centro (os pontos \"na fronteira\", mais prováveis de pertencer errado ao grupo) antes de propagar:",[433,11911,11912],{},[11,11913,11914,11916],{},[15,11915,1632],{}," sobra 1111 exemplos (de 1400), com 97.7% de acerto na propagação (subiu de 95.4%). Treinando só com esses: 0.879, o melhor resultado da aula usando só 50 rótulos de verdade.",[11,11918,11919,11920,11922,11923,11926],{},"E o padrão se repete trocando ",[87,11921,9004],{}," por KNN, Random Forest, Naive Bayes Gaussiano e ",[87,11924,11925],{},"NearestCentroid",": em todo classificador testado, \"representativo\" bate \"50 aleatórios\", e \"propagado\" bate \"representativo sozinho\".",[79,11928,1873],{"id":1872},[1875,11930,11931,11939],{},[1878,11932,11933],{},[1881,11934,11935,11937],{},[1884,11936,1887],{"align":1886},[1884,11938,1890],{"align":1886},[1892,11940,11941,11949,11965],{},[1881,11942,11943,11946],{},[1897,11944,11945],{"align":1886},"K-means agrupa por distância até um centroide",[1897,11947,11948],{"align":1886},"DBSCAN agrupa por densidade, sem assumir formato nenhum de grupo, então lida bem com formas que não são convexas",[1881,11950,11951,11954],{},[1897,11952,11953],{"align":1886},"Clustering serve pra explorar dado sem rótulo",[1897,11955,11956,11957,11960,11961,11964],{"align":1886},"Clustering também ajuda a ",[15,11958,11959],{},"decidir o que rotular"," quando rotular é caro, e a ",[15,11962,11963],{},"estender"," poucos rótulos pro resto do dado",[1881,11966,11967,11970],{},[1897,11968,11969],{"align":1886},"Mais dado rotulado é sempre melhor",[1897,11971,11972],{"align":1886},"Às vezes 50 rótulos bem escolhidos (via clustering) valem mais que 50 rótulos na ordem que chegaram",[79,11974,1943],{"id":1942},[11,11976,11977],{},"Reproduzi a cadeia completa (50 aleatórios → representativos → propagados) no mesmo dataset de dígitos, com semente fixa, pra confirmar que o ganho não foi coincidência de uma rodada só.",[96,11979,11981],{"className":98,"code":11980,"language":100,"meta":57,"style":57},"log_reg = LogisticRegression(solver=\"lbfgs\", max_iter=5000, random_state=42)\n# ... mesmo procedimento do professor, com random_state fixo em cada etapa\n",[87,11982,11983,11988],{"__ignoreMap":57},[104,11984,11985],{"class":106,"line":107},[104,11986,11987],{},"log_reg = LogisticRegression(solver=\"lbfgs\", max_iter=5000, random_state=42)\n",[104,11989,11990],{"class":106,"line":58},[104,11991,11992],{},"# ... mesmo procedimento do professor, com random_state fixo em cada etapa\n",[1875,11994,11995,12005],{},[1878,11996,11997],{},[1881,11998,11999,12002],{},[1884,12000,12001],{"align":5597},"Estratégia de rotulagem",[1884,12003,12004],{"align":3190},"Acurácia no teste",[1892,12006,12007,12015,12023,12031,12039],{},[1881,12008,12009,12012],{},[1897,12010,12011],{"align":5597},"50 rótulos aleatórios (as 50 primeiras imagens)",[1897,12013,12014],{"align":3190},"0.7582",[1881,12016,12017,12020],{},[1897,12018,12019],{"align":5597},"50 rótulos representativos (1 por grupo do K-means)",[1897,12021,12022],{"align":3190},"0.8388",[1881,12024,12025,12028],{},[1897,12026,12027],{"align":5597},"Rótulos propagados pro grupo inteiro",[1897,12029,12030],{"align":3190},"0.8589",[1881,12032,12033,12036],{},[1897,12034,12035],{"align":5597},"Acerto da propagação (contra o rótulo real)",[1897,12037,12038],{"align":3190},"0.9500",[1881,12040,12041,12044],{},[1897,12042,12043],{"align":5597},"Todos os 1400 rótulos (teto teórico)",[1897,12045,12046],{"align":3190},"0.9093",[11,12048,12049,12050,12053,12054,12057,12058,12060],{},"Meus números batem de perto com os do notebook original (0.766 \u002F 0.834 \u002F 0.869 \u002F 0.954 \u002F 0.902), a diferença mora só na semente aleatória e no solver da regressão logística (troquei ",[87,12051,12052],{},"liblinear"," por ",[87,12055,12056],{},"lbfgs",", porque a versão mais nova do scikit-learn no meu ambiente não aceita mais ",[87,12059,12052],{}," em problemas com mais de duas classes). A ordem entre as estratégias é idêntica em ambas as rodadas: aleatório perde pra representativo, que perde pra propagado, confirmando que o ganho é real, não sorte de uma rodada específica.",[1985,12062,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":12064},[12065,12066,12067,12068,12070,12071,12072,12073,12074],{"id":11470,"depth":58,"text":11471},{"id":11512,"depth":58,"text":11513},{"id":11693,"depth":58,"text":11694},{"id":11723,"depth":58,"text":12069},"O problema de não ter .predict()",{"id":11777,"depth":58,"text":11778},{"id":11820,"depth":58,"text":11821},{"id":11867,"depth":58,"text":11868},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 9: o professor mostra onde K-means falha feio, apresenta o DBSCAN como alternativa por densidade, e depois usa clustering pra escolher melhor quais exemplos rotular quando rótulo é caro.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fdbscan-semi-supervised",{"title":11462,"description":12075},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fdbscan-semi-supervised",[12081,12082,12083],"dbscan","aprendizado-semi-supervisionado","clustering","X15zTole8ayFBXQjDSSihaii_MZy7OVq7B9j9OmHL3c",{"id":12086,"title":12087,"body":12088,"cover":3,"date":9189,"description":13408,"extension":61,"meta":13409,"navigation":63,"order":1704,"path":13410,"playlist":2003,"seo":13411,"status":66,"stem":13412,"tags":13413,"__hash__":13417},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fpca.md","PCA: Reduzir Dimensão sem Perder o que Importa (Nem Sempre)",{"type":8,"value":12089,"toc":13399},[12090,12093,12097,12103,12128,12139,12142,12221,12410,12620,12629,12633,12636,12651,12654,12726,12737,12744,12763,12773,12777,12945,12965,12975,12978,13002,13009,13164,13168,13171,13178,13213,13217,13223,13253,13263,13266,13305,13308,13311,13320,13323,13325,13361,13363,13374,13394,13397],[11,12091,12092],{},"Aula 10a e 10b. Depois de K-means e DBSCAN (agrupar sem rótulo), PCA ataca outro problema sem rótulo: como resumir um monte de variáveis em poucas, sem perder o que importa. Spoiler do post inteiro: \"o que importa\" é uma pergunta traiçoeira.",[79,12094,12096],{"id":12095},"o-que-pca-calcula-de-verdade","O que PCA calcula, de verdade",[11,12098,12099,12100,12102],{},"O professor volta pro dataset de câncer de mama ",[20,12101,11080],{"href":5657},", 30 variáveis, 569 pacientes:",[96,12104,12106],{"className":98,"code":12105,"language":100,"meta":57,"style":57},"from sklearn.decomposition import PCA\npca = PCA(n_components=30)\nX_pca = pca.fit_transform(X)\nprint(pca.explained_variance_ratio_)\n",[87,12107,12108,12113,12118,12123],{"__ignoreMap":57},[104,12109,12110],{"class":106,"line":107},[104,12111,12112],{},"from sklearn.decomposition import PCA\n",[104,12114,12115],{"class":106,"line":58},[104,12116,12117],{},"pca = PCA(n_components=30)\n",[104,12119,12120],{"class":106,"line":118},[104,12121,12122],{},"X_pca = pca.fit_transform(X)\n",[104,12124,12125],{"class":106,"line":124},[104,12126,12127],{},"print(pca.explained_variance_ratio_)\n",[433,12129,12130],{},[11,12131,12132,12134,12135,12138],{},[15,12133,1632],{}," o primeiro componente sozinho explica ",[15,12136,12137],{},"98.2%"," de toda a variância dos dados. Os 29 restantes, juntos, explicam os 1.8% que sobram.",[11,12140,12141],{},"Uma concentração absurda, e faz sentido: várias dessas 30 variáveis medem essencialmente a mesma coisa de jeitos diferentes (raio, perímetro e área do tumor são praticamente a mesma informação, só em unidades diferentes), então a variação real do dataset é bem mais \"estreita\" do que 30 números sugerem.",[11,12143,12144,12145,12148,12149,12220],{},"O Bishop define PCA como achar a direção que ",[15,12146,12147],{},"maximiza a variância"," dos dados projetados. Pra um único componente, isso vira um problema de autovalor: a direção ótima ",[104,12150,12152,12171],{"className":12151},[148],[104,12153,12155],{"className":12154},[152],[154,12156,12157],{"xmlns":156},[158,12158,12159,12168],{},[161,12160,12161],{},[515,12162,12163,12166],{},[164,12164,12165],{"mathvariant":473},"u",[483,12167,485],{},[186,12169,12170],{"encoding":188},"\\mathbf{u}_1",[104,12172,12174],{"className":12173,"ariaHidden":194},[193],[104,12175,12177,12180],{"className":12176},[198],[104,12178],{"className":12179,"style":2583},[202],[104,12181,12183,12186],{"className":12182},[207],[104,12184,12165],{"className":12185},[207,570],[104,12187,12189],{"className":12188},[697],[104,12190,12192,12212],{"className":12191},[605,606],[104,12193,12195,12209],{"className":12194},[610],[104,12196,12198],{"className":12197,"style":2846},[614],[104,12199,12200,12203],{"style":788},[104,12201],{"className":12202,"style":714},[622],[104,12204,12206],{"className":12205},[627,628,629,630],[104,12207,485],{"className":12208},[207,630],[104,12210,667],{"className":12211},[666],[104,12213,12215],{"className":12214},[610],[104,12216,12218],{"className":12217,"style":807},[614],[104,12219],{}," satisfaz",[11,12222,12223],{},[104,12224,12226,12261],{"className":12225},[148],[104,12227,12229],{"className":12228},[152],[154,12230,12231],{"xmlns":156},[158,12232,12233,12258],{},[161,12234,12235,12237,12243,12245,12252],{},[164,12236,6175],{"mathvariant":473},[515,12238,12239,12241],{},[164,12240,12165],{"mathvariant":473},[483,12242,485],{},[168,12244,170],{},[515,12246,12247,12250],{},[164,12248,12249],{},"λ",[483,12251,485],{},[515,12253,12254,12256],{},[164,12255,12165],{"mathvariant":473},[483,12257,485],{},[186,12259,12260],{"encoding":188},"\\mathbf{S}\\mathbf{u}_1 = \\lambda_1 \\mathbf{u}_1",[104,12262,12264,12323],{"className":12263,"ariaHidden":194},[193],[104,12265,12267,12271,12274,12314,12317,12320],{"className":12266},[198],[104,12268],{"className":12269,"style":12270},[202],"height:0.8361em;vertical-align:-0.15em;",[104,12272,6175],{"className":12273},[207,570],[104,12275,12277,12280],{"className":12276},[207],[104,12278,12165],{"className":12279},[207,570],[104,12281,12283],{"className":12282},[697],[104,12284,12286,12306],{"className":12285},[605,606],[104,12287,12289,12303],{"className":12288},[610],[104,12290,12292],{"className":12291,"style":2846},[614],[104,12293,12294,12297],{"style":788},[104,12295],{"className":12296,"style":714},[622],[104,12298,12300],{"className":12299},[627,628,629,630],[104,12301,485],{"className":12302},[207,630],[104,12304,667],{"className":12305},[666],[104,12307,12309],{"className":12308},[610],[104,12310,12312],{"className":12311,"style":807},[614],[104,12313],{},[104,12315],{"className":12316,"style":214},[213],[104,12318,170],{"className":12319},[218],[104,12321],{"className":12322,"style":214},[213],[104,12324,12326,12330,12370],{"className":12325},[198],[104,12327],{"className":12328,"style":12329},[202],"height:0.8444em;vertical-align:-0.15em;",[104,12331,12333,12336],{"className":12332},[207],[104,12334,12249],{"className":12335},[207,208],[104,12337,12339],{"className":12338},[697],[104,12340,12342,12362],{"className":12341},[605,606],[104,12343,12345,12359],{"className":12344},[610],[104,12346,12348],{"className":12347,"style":2846},[614],[104,12349,12350,12353],{"style":788},[104,12351],{"className":12352,"style":714},[622],[104,12354,12356],{"className":12355},[627,628,629,630],[104,12357,485],{"className":12358},[207,630],[104,12360,667],{"className":12361},[666],[104,12363,12365],{"className":12364},[610],[104,12366,12368],{"className":12367,"style":807},[614],[104,12369],{},[104,12371,12373,12376],{"className":12372},[207],[104,12374,12165],{"className":12375},[207,570],[104,12377,12379],{"className":12378},[697],[104,12380,12382,12402],{"className":12381},[605,606],[104,12383,12385,12399],{"className":12384},[610],[104,12386,12388],{"className":12387,"style":2846},[614],[104,12389,12390,12393],{"style":788},[104,12391],{"className":12392,"style":714},[622],[104,12394,12396],{"className":12395},[627,628,629,630],[104,12397,485],{"className":12398},[207,630],[104,12400,667],{"className":12401},[666],[104,12403,12405],{"className":12404},[610],[104,12406,12408],{"className":12407,"style":807},[614],[104,12409],{},[11,12411,7463,12412,12442,12443,12512,12513,12516,12517,12545,12546,134,12549,12619],{},[104,12413,12415,12429],{"className":12414},[148],[104,12416,12418],{"className":12417},[152],[154,12419,12420],{"xmlns":156},[158,12421,12422,12426],{},[161,12423,12424],{},[164,12425,6175],{"mathvariant":473},[186,12427,12428],{"encoding":188},"\\mathbf{S}",[104,12430,12432],{"className":12431,"ariaHidden":194},[193],[104,12433,12435,12439],{"className":12434},[198],[104,12436],{"className":12437,"style":12438},[202],"height:0.6861em;",[104,12440,6175],{"className":12441},[207,570]," é a matriz de covariância dos dados. Ou seja, ",[104,12444,12446,12463],{"className":12445},[148],[104,12447,12449],{"className":12448},[152],[154,12450,12451],{"xmlns":156},[158,12452,12453,12461],{},[161,12454,12455],{},[515,12456,12457,12459],{},[164,12458,12165],{"mathvariant":473},[483,12460,485],{},[186,12462,12170],{"encoding":188},[104,12464,12466],{"className":12465,"ariaHidden":194},[193],[104,12467,12469,12472],{"className":12468},[198],[104,12470],{"className":12471,"style":2583},[202],[104,12473,12475,12478],{"className":12474},[207],[104,12476,12165],{"className":12477},[207,570],[104,12479,12481],{"className":12480},[697],[104,12482,12484,12504],{"className":12483},[605,606],[104,12485,12487,12501],{"className":12486},[610],[104,12488,12490],{"className":12489,"style":2846},[614],[104,12491,12492,12495],{"style":788},[104,12493],{"className":12494,"style":714},[622],[104,12496,12498],{"className":12497},[627,628,629,630],[104,12499,485],{"className":12500},[207,630],[104,12502,667],{"className":12503},[666],[104,12505,12507],{"className":12506},[610],[104,12508,12510],{"className":12509,"style":807},[614],[104,12511],{}," precisa ser um ",[15,12514,12515],{},"autovetor"," de ",[104,12518,12520,12533],{"className":12519},[148],[104,12521,12523],{"className":12522},[152],[154,12524,12525],{"xmlns":156},[158,12526,12527,12531],{},[161,12528,12529],{},[164,12530,6175],{"mathvariant":473},[186,12532,12428],{"encoding":188},[104,12534,12536],{"className":12535,"ariaHidden":194},[193],[104,12537,12539,12542],{"className":12538},[198],[104,12540],{"className":12541,"style":12438},[202],[104,12543,6175],{"className":12544},[207,570],", e a variância capturada é exatamente o ",[15,12547,12548],{},"autovalor",[104,12550,12552,12570],{"className":12551},[148],[104,12553,12555],{"className":12554},[152],[154,12556,12557],{"xmlns":156},[158,12558,12559,12567],{},[161,12560,12561],{},[515,12562,12563,12565],{},[164,12564,12249],{},[483,12566,485],{},[186,12568,12569],{"encoding":188},"\\lambda_1",[104,12571,12573],{"className":12572,"ariaHidden":194},[193],[104,12574,12576,12579],{"className":12575},[198],[104,12577],{"className":12578,"style":12329},[202],[104,12580,12582,12585],{"className":12581},[207],[104,12583,12249],{"className":12584},[207,208],[104,12586,12588],{"className":12587},[697],[104,12589,12591,12611],{"className":12590},[605,606],[104,12592,12594,12608],{"className":12593},[610],[104,12595,12597],{"className":12596,"style":2846},[614],[104,12598,12599,12602],{"style":788},[104,12600],{"className":12601,"style":714},[622],[104,12603,12605],{"className":12604},[627,628,629,630],[104,12606,485],{"className":12607},[207,630],[104,12609,667],{"className":12610},[666],[104,12612,12614],{"className":12613},[610],[104,12615,12617],{"className":12616,"style":807},[614],[104,12618],{}," correspondente. Quanto maior o autovalor, mais variância aquela direção captura, e é por isso que o \"primeiro componente principal\" é sempre o autovetor de maior autovalor. Os componentes seguintes repetem a receita, cada um maximizando a variância entre as direções que sobraram, ortogonais às anteriores.",[11,12621,12622,12623,12625,12626,12628],{},"Repara: em nenhum momento essa conta usa o rótulo ",[87,12624,166],{},". PCA olha só pra ",[87,12627,133],{},", procurando onde os dados variam mais. Guarda essa frase, ela é o fio condutor do post inteiro.",[79,12630,12632],{"id":12631},"reduzindo-de-verdade-com-um-porém","Reduzindo de verdade, com um porém",[11,12634,12635],{},"Com 9 componentes (30% das 30 variáveis originais), a variância capturada já é 99.99997%:",[96,12637,12639],{"className":98,"code":12638,"language":100,"meta":57,"style":57},"n_most_important = int(X.shape[1]*0.3)  # 9\nscores = cross_val_score(model, X_pca[:, :n_most_important], y, cv=splitter)\n",[87,12640,12641,12646],{"__ignoreMap":57},[104,12642,12643],{"class":106,"line":107},[104,12644,12645],{},"n_most_important = int(X.shape[1]*0.3)  # 9\n",[104,12647,12648],{"class":106,"line":58},[104,12649,12650],{},"scores = cross_val_score(model, X_pca[:, :n_most_important], y, cv=splitter)\n",[11,12652,12653],{},"O professor compara acurácia (espaço original de 30 variáveis vs os 9 primeiros componentes) em cinco classificadores diferentes:",[1875,12655,12656,12669],{},[1878,12657,12658],{},[1881,12659,12660,12663,12666],{},[1884,12661,12662],{"align":1886},"Classificador",[1884,12664,12665],{"align":3190},"30 variáveis originais",[1884,12667,12668],{"align":3190},"9 componentes PCA",[1892,12670,12671,12682,12692,12704,12715],{},[1881,12672,12673,12676,12679],{},[1897,12674,12675],{"align":1886},"KNN (K=3)",[1897,12677,12678],{"align":3190},"0.9274",[1897,12680,12681],{"align":3190},"0.9274 (idêntico)",[1881,12683,12684,12686,12689],{},[1897,12685,9158],{"align":1886},[1897,12687,12688],{"align":3190},"0.9520",[1897,12690,12691],{"align":3190},"0.9502",[1881,12693,12694,12696,12699],{},[1897,12695,8348],{"align":1886},[1897,12697,12698],{"align":3190},"0.9391",[1897,12700,12701],{"align":3190},[15,12702,12703],{},"0.9039",[1881,12705,12706,12709,12712],{},[1897,12707,12708],{"align":1886},"SVM",[1897,12710,12711],{"align":3190},"0.9150",[1897,12713,12714],{"align":3190},"0.9221",[1881,12716,12717,12720,12723],{},[1897,12718,12719],{"align":1886},"Árvore de decisão",[1897,12721,12722],{"align":3190},"0.9232",[1897,12724,12725],{"align":3190},"0.9203",[11,12727,12728,12729,12732,12733,12736],{},"Repara que o efeito não é o mesmo pra todo mundo. KNN nem percebe a diferença (PCA com quase toda a variância preservada é basicamente uma rotação + um corte quase sem perda, e distância euclidiana não liga pra rotação). Naive Bayes Gaussiano ",[15,12730,12731],{},"piora bastante",", o que é meio contra-intuitivo à primeira vista: ele assume que as variáveis são independentes entre si, e os componentes do PCA são descorrelacionados por construção, então eu esperaria uma ajuda, não um tombo. Mas \"descorrelacionado\" não é a mesma coisa que \"todo mundo importa igual\": os 9 componentes que sobraram têm variância bem desigual entre si (o primeiro sozinho já carrega a fatia esmagadora da variância total), e Naive Bayes Gaussiano ajusta uma variância por variável por classe, então ele confia mais em componentes de variância maior. O problema é que \"maior variância\" e \"melhor pra separar as classes\" não são a mesma coisa, a mesma pegadinha que a próxima seção deixa ainda mais clara: descartar os componentes de menor variância pode estar jogando fora justamente o eixo onde a média de uma classe difere da outra, o que Naive Bayes Gaussiano sente na pele porque ele depende diretamente dessas variâncias por variável, ao contrário do KNN, que só olha a distância total. E árvore de decisão piora um pouquinho, ",[20,12734,12735],{"href":8961},"pelo mesmo motivo que já vi no post de árvores",": árvore corta um eixo de cada vez, e PCA gira os eixos, então a fronteira de decisão que ficava alinhada com uma variável original pode virar diagonal nos componentes, mais difícil de recortar com cortes retos.",[11,12738,12739,12740,12743],{},"O scikit-learn ainda deixa escolher a quantidade de componentes por ",[15,12741,12742],{},"fração de variância"," em vez de contagem fixa:",[96,12745,12747],{"className":98,"code":12746,"language":100,"meta":57,"style":57},"pca = PCA(n_components=0.9999)\nX_pca = pca.fit_transform(X)\nprint(X_pca.shape)\n",[87,12748,12749,12754,12758],{"__ignoreMap":57},[104,12750,12751],{"class":106,"line":107},[104,12752,12753],{},"pca = PCA(n_components=0.9999)\n",[104,12755,12756],{"class":106,"line":58},[104,12757,12122],{},[104,12759,12760],{"class":106,"line":118},[104,12761,12762],{},"print(X_pca.shape)\n",[433,12764,12765],{},[11,12766,12767,134,12769,12772],{},[15,12768,1632],{},[87,12770,12771],{},"(569, 5)",". Só 5 componentes já bastam pra 99.99% da variância, menos até que os 9 escolhidos na mão.",[79,12774,12776],{"id":12775},"o-aviso-pca-não-sabe-o-que-é-importante-pra-você","O aviso: PCA não sabe o que é \"importante\" pra você",[11,12778,12779,12780,12834,12835,12887,12888,12916,12917,1866],{},"Essa seção (o professor batizou de \"PCA fail\" no próprio notebook) é o motivo pelo qual eu escrevi \"guarda essa frase\" lá em cima. Ele constrói um dataset artificial de propósito: 12 grupos de pontos, arranjados em duas fileiras (uma em ",[104,12781,12783,12803],{"className":12782},[148],[104,12784,12786],{"className":12785},[152],[154,12787,12788],{"xmlns":156},[158,12789,12790,12800],{},[161,12791,12792,12794,12797],{},[164,12793,166],{},[168,12795,12796],{},"≈",[483,12798,12799],{},"0.1",[186,12801,12802],{"encoding":188},"y\\approx0.1",[104,12804,12806,12825],{"className":12805,"ariaHidden":194},[193],[104,12807,12809,12813,12816,12819,12822],{"className":12808},[198],[104,12810],{"className":12811,"style":12812},[202],"height:0.6776em;vertical-align:-0.1944em;",[104,12814,166],{"className":12815,"style":209},[207,208],[104,12817],{"className":12818,"style":214},[213],[104,12820,12796],{"className":12821},[218],[104,12823],{"className":12824,"style":214},[213],[104,12826,12828,12831],{"className":12827},[198],[104,12829],{"className":12830,"style":2975},[202],[104,12832,12799],{"className":12833},[207],", outra em ",[104,12836,12838,12857],{"className":12837},[148],[104,12839,12841],{"className":12840},[152],[154,12842,12843],{"xmlns":156},[158,12844,12845,12854],{},[161,12846,12847,12849,12851],{},[164,12848,166],{},[168,12850,12796],{},[483,12852,12853],{},"0.4",[186,12855,12856],{"encoding":188},"y\\approx0.4",[104,12858,12860,12878],{"className":12859,"ariaHidden":194},[193],[104,12861,12863,12866,12869,12872,12875],{"className":12862},[198],[104,12864],{"className":12865,"style":12812},[202],[104,12867,166],{"className":12868,"style":209},[207,208],[104,12870],{"className":12871,"style":214},[213],[104,12873,12796],{"className":12874},[218],[104,12876],{"className":12877,"style":214},[213],[104,12879,12881,12884],{"className":12880},[198],[104,12882],{"className":12883,"style":2975},[202],[104,12885,12853],{"className":12886},[207],"), espalhados ao longo do eixo ",[104,12889,12891,12904],{"className":12890},[148],[104,12892,12894],{"className":12893},[152],[154,12895,12896],{"xmlns":156},[158,12897,12898,12902],{},[161,12899,12900],{},[164,12901,178],{},[186,12903,178],{"encoding":188},[104,12905,12907],{"className":12906,"ariaHidden":194},[193],[104,12908,12910,12913],{"className":12909},[198],[104,12911],{"className":12912,"style":10686},[202],[104,12914,178],{"className":12915},[207,208]," de 0 a 1.1. A classe alterna: fileira de baixo é uma classe, fileira de cima é outra, não importa o valor de ",[104,12918,12920,12933],{"className":12919},[148],[104,12921,12923],{"className":12922},[152],[154,12924,12925],{"xmlns":156},[158,12926,12927,12931],{},[161,12928,12929],{},[164,12930,178],{},[186,12932,178],{"encoding":188},[104,12934,12936],{"className":12935,"ariaHidden":194},[193],[104,12937,12939,12942],{"className":12938},[198],[104,12940],{"className":12941,"style":10686},[202],[104,12943,178],{"className":12944},[207,208],[96,12946,12948],{"className":98,"code":12947,"language":100,"meta":57,"style":57},"model = Perceptron()\nmodel.fit(X, y)\nprint(accuracy_score(y, model.predict(X)))\n",[87,12949,12950,12955,12960],{"__ignoreMap":57},[104,12951,12952],{"class":106,"line":107},[104,12953,12954],{},"model = Perceptron()\n",[104,12956,12957],{"class":106,"line":58},[104,12958,12959],{},"model.fit(X, y)\n",[104,12961,12962],{"class":106,"line":118},[104,12963,12964],{},"print(accuracy_score(y, model.predict(X)))\n",[433,12966,12967],{},[11,12968,12969,12971,12972,12974],{},[15,12970,1632],{}," 1.0. Cem por cento, porque separar por fileira (valor de ",[87,12973,166],{},") é fácil, é só uma reta horizontal.",[11,12976,12977],{},"Agora aplica PCA com 1 componente antes de treinar:",[96,12979,12981],{"className":98,"code":12980,"language":100,"meta":57,"style":57},"pca = PCA(n_components=1)\nX_pca = pca.fit_transform(X)\nmodel.fit(X_pca, y)\nprint(accuracy_score(y, model.predict(X_pca)))\n",[87,12982,12983,12988,12992,12997],{"__ignoreMap":57},[104,12984,12985],{"class":106,"line":107},[104,12986,12987],{},"pca = PCA(n_components=1)\n",[104,12989,12990],{"class":106,"line":58},[104,12991,12122],{},[104,12993,12994],{"class":106,"line":118},[104,12995,12996],{},"model.fit(X_pca, y)\n",[104,12998,12999],{"class":106,"line":124},[104,13000,13001],{},"print(accuracy_score(y, model.predict(X_pca)))\n",[433,13003,13004],{},[11,13005,13006,13008],{},[15,13007,1632],{}," 0.47. Praticamente a cara de uma moeda sendo jogada.",[11,13010,13011,13012,13040,13041,13069,13070,13073,13074,13102,13103,13131,13132,13134,13135,13163],{},"O que aconteceu: o eixo ",[104,13013,13015,13028],{"className":13014},[148],[104,13016,13018],{"className":13017},[152],[154,13019,13020],{"xmlns":156},[158,13021,13022,13026],{},[161,13023,13024],{},[164,13025,178],{},[186,13027,178],{"encoding":188},[104,13029,13031],{"className":13030,"ariaHidden":194},[193],[104,13032,13034,13037],{"className":13033},[198],[104,13035],{"className":13036,"style":10686},[202],[104,13038,178],{"className":13039},[207,208]," (0 a 1.1) tem muito mais espalhamento que o eixo ",[104,13042,13044,13057],{"className":13043},[148],[104,13045,13047],{"className":13046},[152],[154,13048,13049],{"xmlns":156},[158,13050,13051,13055],{},[161,13052,13053],{},[164,13054,166],{},[186,13056,166],{"encoding":188},[104,13058,13060],{"className":13059,"ariaHidden":194},[193],[104,13061,13063,13066],{"className":13062},[198],[104,13064],{"className":13065,"style":203},[202],[104,13067,166],{"className":13068,"style":209},[207,208]," (0.1 a 0.4), então a direção de ",[15,13071,13072],{},"maior variância"," é quase totalmente ao longo de ",[104,13075,13077,13090],{"className":13076},[148],[104,13078,13080],{"className":13079},[152],[154,13081,13082],{"xmlns":156},[158,13083,13084,13088],{},[161,13085,13086],{},[164,13087,178],{},[186,13089,178],{"encoding":188},[104,13091,13093],{"className":13092,"ariaHidden":194},[193],[104,13094,13096,13099],{"className":13095},[198],[104,13097],{"className":13098,"style":10686},[202],[104,13100,178],{"className":13101},[207,208],". PCA, fazendo exatamente o que promete, escolhe essa direção como o único componente. Só que a informação que separa as classes mora no eixo ",[104,13104,13106,13119],{"className":13105},[148],[104,13107,13109],{"className":13108},[152],[154,13110,13111],{"xmlns":156},[158,13112,13113,13117],{},[161,13114,13115],{},[164,13116,166],{},[186,13118,166],{"encoding":188},[104,13120,13122],{"className":13121,"ariaHidden":194},[193],[104,13123,13125,13128],{"className":13124},[198],[104,13126],{"className":13127,"style":203},[202],[104,13129,166],{"className":13130,"style":209},[207,208],", o de ",[15,13133,9174],{}," variância, e PCA jogou ela fora inteira. O algoritmo não tem como saber que ",[104,13136,13138,13151],{"className":13137},[148],[104,13139,13141],{"className":13140},[152],[154,13142,13143],{"xmlns":156},[158,13144,13145,13149],{},[161,13146,13147],{},[164,13148,166],{},[186,13150,166],{"encoding":188},[104,13152,13154],{"className":13153,"ariaHidden":194},[193],[104,13155,13157,13160],{"className":13156},[198],[104,13158],{"className":13159,"style":203},[202],[104,13161,166],{"className":13162,"style":209},[207,208]," importava mais pra classificar, porque ele nunca olhou pro rótulo.",[79,13165,13167],{"id":13166},"interativo-pra-onde-o-primeiro-componente-aponta","Interativo: pra onde o primeiro componente aponta",[11,13169,13170],{},"Reconstrução minha do mesmo dataset de 100 pontos (dois grupos, mesma disposição em fileiras), com os dois componentes principais desenhados por cima (vermelho sólido = 1º componente, verde tracejado = 2º, cada seta proporcional à raiz quadrada da variância que aquele componente explica):",[13172,13173],"pca-explorer",{":classes":13174,":points":13175,"class0-label":13176,"class1-label":13177,"x-label":11707,"y-label":11708},"[1,1,0,1,1,1,0,1,1,1,0,0,1,1,0,1,0,0,0,0,0,1,1,1,1,0,1,1,1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,1,0,1,0,0,1,0,1,0,1,1,0,1,0,0,0,0,1,1,0,1,0,0,1,0,0,0,1,1,1,1,1,0,1,1,0,1,0,1,1,0,0,0,1,0,0,1,0,0,0,1,0,0,1,1,0,0]","[[1.0237,0.3964],[0.158,0.3845],[0.6041,0.0987],[0.176,0.3907],[0.7513,0.4394],[0.0411,0.339],[0.7129,0.0963],[0.766,0.4116],[0.0733,0.3887],[-0.0454,0.3294],[0.0883,0.0883],[1.1138,0.1414],[0.3649,0.3836],[0.9558,0.4077],[1.0877,0.0623],[0.8482,0.4206],[0.2662,0.1306],[0.9407,0.0385],[1.0555,0.0592],[0.6919,0.1202],[0.489,0.1179],[0.554,0.4775],[1.0029,0.3429],[0.5505,0.3717],[0.8411,0.4948],[0.1248,0.0931],[0.4049,0.4484],[0.0034,0.3288],[0.9777,0.4428],[0.2261,0.064],[0.5469,0.4237],[0.3369,0.1086],[0.1964,0.4502],[1.1313,0.0571],[0.2515,0.4466],[0.369,0.1411],[1.0087,0.4193],[0.3104,0.002],[0.9114,0.1654],[1.0961,0.1171],[0.8147,0.3643],[0.5046,0.0006],[0.2181,0.3677],[0.2166,0.4488],[0.913,0.1391],[0.8579,0.359],[0.6983,0.0416],[0.179,0.1384],[0.9888,0.4357],[0.0765,0.1271],[0.6396,0.3545],[0.5458,0.1164],[0.8933,0.4237],[1.0107,0.3377],[0.4982,0.1782],[0.9577,0.3243],[0.7943,0.1087],[0.9261,0.1148],[0.0768,0.0767],[1.0465,0.1241],[0.3829,0.3599],[0.4148,0.4131],[0.8382,0.034],[0.7404,0.4328],[0.8608,0.0839],[0.2942,0.0849],[0.6701,0.3299],[0.8196,0.1092],[0.6904,0.1151],[1.1007,0.1727],[0.3292,0.379],[0.6293,0.5095],[0.1447,0.3402],[0.5225,0.4034],[-0.03,0.3854],[0.9125,0.1173],[-0.0575,0.4188],[-0.0301,0.4926],[0.0138,0.0719],[0.605,0.3748],[0.2336,0.1098],[0.3735,0.4257],[0.2181,0.4769],[0.703,0.2232],[0.1121,0.0043],[0.7571,0.1376],[-0.0272,0.4055],[0.277,0.1529],[0.3172,0.0118],[0.4003,0.3883],[0.3162,0.0807],[0.5739,0.0741],[0.5044,0.085],[0.3804,0.3268],[0.4596,0.0749],[0.0494,0.1157],[0.2406,0.4678],[-0.0007,0.3471],[1.0868,0.236],[0.1324,0.1762]]","Fileira de baixo","Fileira de cima",[11,13179,13180,13181,13209,13210,13212],{},"Sem normalizar, o componente 1 (vermelho) fica quase deitado, seguindo o eixo ",[104,13182,13184,13197],{"className":13183},[148],[104,13185,13187],{"className":13186},[152],[154,13188,13189],{"xmlns":156},[158,13190,13191,13195],{},[161,13192,13193],{},[164,13194,178],{},[186,13196,178],{"encoding":188},[104,13198,13200],{"className":13199,"ariaHidden":194},[193],[104,13201,13203,13206],{"className":13202},[198],[104,13204],{"className":13205,"style":10686},[202],[104,13207,178],{"className":13208},[207,208],", exatamente a direção que ",[15,13211,137],{}," separa as classes. Clica em \"Normalizado (z-score)\": agora as duas variáveis competem em pé de igualdade, e o componente 1 gira pra uma diagonal, ainda não perfeitamente alinhado com a separação real (por isso o resultado com normalização, 0.81 de acurácia no notebook, é melhor mas não perfeito), mas bem menos cego ao que importa do que a versão sem normalizar.",[79,13214,13216],{"id":13215},"pca-como-compressão-de-verdade","PCA como compressão de verdade",[11,13218,13219,13222],{},[87,13220,13221],{},"aula10b"," troca de dataset pro MNIST (70 mil imagens, 784 pixels cada) e usa PCA pra achar quantos componentes bastam pra reter 95% da variância:",[96,13224,13226],{"className":98,"code":13225,"language":100,"meta":57,"style":57},"pca = PCA()\npca.fit(X_train)\ncumsum = np.cumsum(pca.explained_variance_ratio_)\nd = np.argmax(cumsum >= 0.95) + 1\nprint(d)\n",[87,13227,13228,13233,13238,13243,13248],{"__ignoreMap":57},[104,13229,13230],{"class":106,"line":107},[104,13231,13232],{},"pca = PCA()\n",[104,13234,13235],{"class":106,"line":58},[104,13236,13237],{},"pca.fit(X_train)\n",[104,13239,13240],{"class":106,"line":118},[104,13241,13242],{},"cumsum = np.cumsum(pca.explained_variance_ratio_)\n",[104,13244,13245],{"class":106,"line":124},[104,13246,13247],{},"d = np.argmax(cumsum >= 0.95) + 1\n",[104,13249,13250],{"class":106,"line":308},[104,13251,13252],{},"print(d)\n",[433,13254,13255],{},[11,13256,13257,134,13259,13262],{},[15,13258,1632],{},[87,13260,13261],{},"d = 154",". De 784 pixels pra 154 componentes, uma redução de quase 80% no tamanho, ainda guardando 95% da variância.",[11,13264,13265],{},"Comparando acurácia, pixel cru contra os 154 componentes:",[1875,13267,13268,13280],{},[1878,13269,13270],{},[1881,13271,13272,13274,13277],{},[1884,13273,12662],{"align":1886},[1884,13275,13276],{"align":3190},"784 pixels",[1884,13278,13279],{"align":3190},"154 componentes PCA",[1892,13281,13282,13293],{},[1881,13283,13284,13288,13290],{},[1897,13285,13286],{"align":1886},[87,13287,11135],{},[1897,13289,11255],{"align":3190},[1897,13291,13292],{"align":3190},"0.8609 (praticamente igual)",[1881,13294,13295,13299,13302],{},[1897,13296,13297],{"align":1886},[87,13298,8631],{},[1897,13300,13301],{"align":3190},"0.9705",[1897,13303,13304],{"align":3190},"0.9488 (piora notável)",[11,13306,13307],{},"O mesmo padrão do dataset de câncer: modelo linear não se importa (ou até melhora um pouquinho), floresta de árvores piora, porque de novo perde o alinhamento de eixo que ela precisa pra cortar bem.",[11,13309,13310],{},"E porque PCA guarda a direção de cada componente, dá pra fazer o caminho de volta (com perda) e reconstruir uma aproximação da imagem original:",[96,13312,13314],{"className":98,"code":13313,"language":100,"meta":57,"style":57},"X_train_recovered = pca.inverse_transform(X_train_reduced)\n",[87,13315,13316],{"__ignoreMap":57},[104,13317,13318],{"class":106,"line":107},[104,13319,13313],{},[11,13321,13322],{},"Os dígitos reconstruídos ficam visivelmente mais \"borrados\" que o original, mas ainda dá pra reconhecer qual número é qual, com só 154 números guardados por imagem em vez de 784. É literalmente compressão com perda, no mesmo espírito de um JPEG, só que a \"base\" usada pra descrever a imagem foi aprendida a partir do próprio dataset em vez de ser genérica.",[79,13324,1873],{"id":1872},[1875,13326,13327,13335],{},[1878,13328,13329],{},[1881,13330,13331,13333],{},[1884,13332,1887],{"align":1886},[1884,13334,1890],{"align":1886},[1892,13336,13337,13345,13353],{},[1881,13338,13339,13342],{},[1897,13340,13341],{"align":1886},"Reduzir dimensão simplifica o problema",[1897,13343,13344],{"align":1886},"PCA reduz preservando variância, não preservando \"o que separa as classes\", e essas duas coisas podem apontar pra direções completamente diferentes",[1881,13346,13347,13350],{},[1897,13348,13349],{"align":1886},"Árvores cortam eixo por eixo",[1897,13351,13352],{"align":1886},"Rodar PCA antes de uma árvore ou floresta pode piorar o resultado, porque a rotação quebra o alinhamento que esses modelos exploram",[1881,13354,13355,13358],{},[1897,13356,13357],{"align":1886},"Compressão com perda existe pra imagem e áudio",[1897,13359,13360],{"align":1886},"PCA faz exatamente isso em qualquer dado tabular: reconstrução aproximada, guardando só as direções que mais variam",[79,13362,1943],{"id":1942},[11,13364,13365,13366,13369,13370,13373],{},"Sem normalizar, o componente 1 do dataset \"PCA fail\" captura 83.5% da variância total, quase todo mundo. Normalizado, cai pra 54.8%, quase empatado com o componente 2 (45.2%). Isso por si só já é um sinal de alerta útil: quando o primeiro componente domina ",[15,13367,13368],{},"demais"," a variância (como no câncer de mama, 98.2%, ou aqui sem normalizar, 83.5%), vale desconfiar que uma variável de escala grande está dominando a conta sozinha, o mesmo problema de escala ",[20,13371,13372],{"href":5820},"que eu já vi lá atrás com KNN",", só que agora afetando o próprio PCA em vez de uma distância.",[96,13375,13377],{"className":98,"code":13376,"language":100,"meta":57,"style":57},"# variância explicada pelo componente 1, com e sem normalizar\nrazao_sem_normalizar = 0.835  # x domina, y quase não conta\nrazao_normalizado = 0.548     # x e y competem em pé de igualdade\n",[87,13378,13379,13384,13389],{"__ignoreMap":57},[104,13380,13381],{"class":106,"line":107},[104,13382,13383],{},"# variância explicada pelo componente 1, com e sem normalizar\n",[104,13385,13386],{"class":106,"line":58},[104,13387,13388],{},"razao_sem_normalizar = 0.835  # x domina, y quase não conta\n",[104,13390,13391],{"class":106,"line":118},[104,13392,13393],{},"razao_normalizado = 0.548     # x e y competem em pé de igualdade\n",[11,13395,13396],{},"A régua prática que fica: sempre que eu for usar PCA em variáveis que não estão na mesma unidade (preço em dólar ao lado de idade em anos, por exemplo), normalizar antes não é opcional, é o mesmo cuidado de sempre, agora aplicado a um lugar novo.",[1985,13398,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":13400},[13401,13402,13403,13404,13405,13406,13407],{"id":12095,"depth":58,"text":12096},{"id":12631,"depth":58,"text":12632},{"id":12775,"depth":58,"text":12776},{"id":13166,"depth":58,"text":13167},{"id":13215,"depth":58,"text":13216},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 10: o professor usa PCA pra comprimir dado de verdade sem perder muita acurácia, mas fecha com um aviso importante: PCA só olha pra variância, não pro rótulo, e às vezes joga fora exatamente a informação que você precisava.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fpca",{"title":12087,"description":13408},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fpca",[13414,13415,13416],"pca","reducao-de-dimensionalidade","variancia","AtXsxL6KY3hqBu1-PAR96UNRtKv554RxF_iyLuSLGEE",{"id":13419,"title":13420,"body":13421,"cover":3,"date":9189,"description":14532,"extension":61,"meta":14533,"navigation":63,"order":1710,"path":14534,"playlist":2003,"seo":14535,"status":66,"stem":14536,"tags":14537,"__hash__":14540},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fgaussian-mixtures-anomaly-detection.md","Misturas Gaussianas e Detecção de Anomalia: Clustering com Formato",{"type":8,"value":13422,"toc":14522},[13423,13430,13434,13437,13441,13461,13500,13852,13929,14052,14055,14066,14070,14073,14081,14088,14092,14095,14115,14118,14122,14131,14138,14255,14266,14291,14330,14333,14337,14347,14404,14425,14435,14437,14479,14481,14484,14509,14517,14520],[11,13424,13425,13426,13429],{},"Aula 11a e 11b. Continuação direta do ",[20,13427,13428],{"href":11499},"post de K-means",": e se os grupos não forem redondos?",[79,13431,13433],{"id":13432},"onde-o-k-means-capenga-de-novo","Onde o K-means capenga de novo",[11,13435,13436],{},"O professor gera um dataset de propósito: dois grupos esticados e girados (multiplicando os pontos por uma matriz de rotação), mais um terceiro grupo bem menor e afastado dos outros dois. K-means, mesmo com uma inicialização boa (centros escolhidos na mão, perto do lugar certo), tem dificuldade: como ele só enxerga \"distância até um centro\", ele tende a cortar os grupos alongados em pedaços mais redondos do que realmente são, porque a fronteira dele entre dois grupos é sempre uma reta perpendicular à linha entre os centros, não uma elipse.",[79,13438,13440],{"id":13439},"misturas-gaussianas-cada-grupo-vira-uma-elipse-não-um-ponto","Misturas gaussianas: cada grupo vira uma elipse, não um ponto",[96,13442,13444],{"className":98,"code":13443,"language":100,"meta":57,"style":57},"from sklearn.mixture import GaussianMixture\ngm = GaussianMixture(n_components=3, n_init=10, random_state=42)\ngm.fit(X)\n",[87,13445,13446,13451,13456],{"__ignoreMap":57},[104,13447,13448],{"class":106,"line":107},[104,13449,13450],{},"from sklearn.mixture import GaussianMixture\n",[104,13452,13453],{"class":106,"line":58},[104,13454,13455],{},"gm = GaussianMixture(n_components=3, n_init=10, random_state=42)\n",[104,13457,13458],{"class":106,"line":118},[104,13459,13460],{},"gm.fit(X)\n",[11,13462,13463,13464,13467,13468,13470,13471,13499],{},"A diferença central pro K-means: em vez de guardar só um centro por grupo, uma ",[15,13465,13466],{},"mistura gaussiana"," guarda uma distribuição normal inteira por grupo, com sua própria média ",[15,13469,8775],{}," matriz de covariância (que captura o formato, o quanto o grupo é alongado e em qual direção). O modelo completo é uma soma ponderada de ",[104,13472,13474,13487],{"className":13473},[148],[104,13475,13477],{"className":13476},[152],[154,13478,13479],{"xmlns":156},[158,13480,13481,13485],{},[161,13482,13483],{},[164,13484,5339],{},[186,13486,5339],{"encoding":188},[104,13488,13490],{"className":13489,"ariaHidden":194},[193],[104,13491,13493,13496],{"className":13492},[198],[104,13494],{"className":13495,"style":3515},[202],[104,13497,5339],{"className":13498,"style":5354},[207,208]," gaussianas:",[11,13501,13502],{},[104,13503,13505,13578],{"className":13504},[148],[104,13506,13508],{"className":13507},[152],[154,13509,13510],{"xmlns":156},[158,13511,13512,13575],{},[161,13513,13514,13516,13518,13520,13522,13524,13538,13545,13548,13551,13553,13555,13558,13564,13566,13573],{},[164,13515,11],{},[168,13517,470],{"stretchy":469},[164,13519,178],{"mathvariant":473},[168,13521,476],{"stretchy":469},[168,13523,170],{},[490,13525,13526,13528,13536],{},[168,13527,494],{},[161,13529,13530,13532,13534],{},[164,13531,5374],{},[168,13533,170],{},[483,13535,485],{},[164,13537,5339],{},[515,13539,13540,13543],{},[164,13541,13542],{},"π",[164,13544,5374],{},[932,13546,13547],{}," ",[164,13549,506],{"mathvariant":13550},"script",[168,13552,470],{"stretchy":469},[164,13554,178],{"mathvariant":473},[168,13556,13557],{},"∣",[515,13559,13560,13562],{},[164,13561,10205],{"mathvariant":2106},[164,13563,5374],{},[168,13565,523],{"separator":194},[515,13567,13568,13571],{},[164,13569,13570],{"mathvariant":473},"Σ",[164,13572,5374],{},[168,13574,476],{"stretchy":469},[186,13576,13577],{"encoding":188},"p(\\mathbf{x}) = \\sum_{k=1}^{K} \\pi_k \\, \\mathcal{N}(\\mathbf{x} \\mid \\boldsymbol{\\mu}_k, \\boldsymbol{\\Sigma}_k)",[104,13579,13581,13608,13744],{"className":13580,"ariaHidden":194},[193],[104,13582,13584,13587,13590,13593,13596,13599,13602,13605],{"className":13583},[198],[104,13585],{"className":13586,"style":558},[202],[104,13588,11],{"className":13589},[207,208],[104,13591,470],{"className":13592},[566],[104,13594,178],{"className":13595},[207,570],[104,13597,476],{"className":13598},[575],[104,13600],{"className":13601,"style":214},[213],[104,13603,170],{"className":13604},[218],[104,13606],{"className":13607,"style":214},[213],[104,13609,13611,13614,13677,13680,13721,13724,13729,13732,13735,13738,13741],{"className":13610},[198],[104,13612],{"className":13613,"style":10245},[202],[104,13615,13617,13620],{"className":13616},[687],[104,13618,494],{"className":13619,"style":693},[687,691,692],[104,13621,13623],{"className":13622},[697],[104,13624,13626,13669],{"className":13625},[605,606],[104,13627,13629,13666],{"className":13628},[610],[104,13630,13632,13652],{"className":13631,"style":707},[614],[104,13633,13634,13637],{"style":710},[104,13635],{"className":13636,"style":714},[622],[104,13638,13640],{"className":13639},[627,628,629,630],[104,13641,13643,13646,13649],{"className":13642},[207,630],[104,13644,5374],{"className":13645,"style":5422},[207,208,630],[104,13647,170],{"className":13648},[218,630],[104,13650,485],{"className":13651},[207,630],[104,13653,13654,13657],{"style":732},[104,13655],{"className":13656,"style":714},[622],[104,13658,13660],{"className":13659},[627,628,629,630],[104,13661,13663],{"className":13662},[207,630],[104,13664,5339],{"className":13665,"style":5354},[207,208,630],[104,13667,667],{"className":13668},[666],[104,13670,13672],{"className":13671},[610],[104,13673,13675],{"className":13674,"style":755},[614],[104,13676],{},[104,13678],{"className":13679,"style":683},[213],[104,13681,13683,13686],{"className":13682},[207],[104,13684,13542],{"className":13685,"style":209},[207,208],[104,13687,13689],{"className":13688},[697],[104,13690,13692,13713],{"className":13691},[605,606],[104,13693,13695,13710],{"className":13694},[610],[104,13696,13698],{"className":13697,"style":5409},[614],[104,13699,13701,13704],{"style":13700},"top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;",[104,13702],{"className":13703,"style":714},[622],[104,13705,13707],{"className":13706},[627,628,629,630],[104,13708,5374],{"className":13709,"style":5422},[207,208,630],[104,13711,667],{"className":13712},[666],[104,13714,13716],{"className":13715},[610],[104,13717,13719],{"className":13718,"style":807},[614],[104,13720],{},[104,13722],{"className":13723,"style":683},[213],[104,13725,506],{"className":13726,"style":13728},[207,13727],"mathcal","margin-right:0.1474em;",[104,13730,470],{"className":13731},[566],[104,13733,178],{"className":13734},[207,570],[104,13736],{"className":13737,"style":214},[213],[104,13739,13557],{"className":13740},[218],[104,13742],{"className":13743,"style":214},[213],[104,13745,13747,13750,13796,13799,13802,13849],{"className":13746},[198],[104,13748],{"className":13749,"style":558},[202],[104,13751,13753,13762],{"className":13752},[207],[104,13754,13756],{"className":13755},[207],[104,13757,13759],{"className":13758},[207],[104,13760,10205],{"className":13761},[207,2437],[104,13763,13765],{"className":13764},[697],[104,13766,13768,13788],{"className":13767},[605,606],[104,13769,13771,13785],{"className":13770},[610],[104,13772,13774],{"className":13773,"style":10505},[614],[104,13775,13776,13779],{"style":10508},[104,13777],{"className":13778,"style":714},[622],[104,13780,13782],{"className":13781},[627,628,629,630],[104,13783,5374],{"className":13784,"style":5422},[207,208,630],[104,13786,667],{"className":13787},[666],[104,13789,13791],{"className":13790},[610],[104,13792,13794],{"className":13793,"style":10527},[614],[104,13795],{},[104,13797,523],{"className":13798},[813],[104,13800],{"className":13801,"style":683},[213],[104,13803,13805,13814],{"className":13804},[207],[104,13806,13808],{"className":13807},[207],[104,13809,13811],{"className":13810},[207],[104,13812,13570],{"className":13813},[207,570],[104,13815,13817],{"className":13816},[697],[104,13818,13820,13841],{"className":13819},[605,606],[104,13821,13823,13838],{"className":13822},[610],[104,13824,13826],{"className":13825,"style":5409},[614],[104,13827,13829,13832],{"style":13828},"top:-2.55em;margin-right:0.05em;",[104,13830],{"className":13831,"style":714},[622],[104,13833,13835],{"className":13834},[627,628,629,630],[104,13836,5374],{"className":13837,"style":5422},[207,208,630],[104,13839,667],{"className":13840},[666],[104,13842,13844],{"className":13843},[610],[104,13845,13847],{"className":13846,"style":807},[614],[104,13848],{},[104,13850,476],{"className":13851},[575],[11,13853,7463,13854,13924,13925,13928],{},[104,13855,13857,13875],{"className":13856},[148],[104,13858,13860],{"className":13859},[152],[154,13861,13862],{"xmlns":156},[158,13863,13864,13872],{},[161,13865,13866],{},[515,13867,13868,13870],{},[164,13869,13542],{},[164,13871,5374],{},[186,13873,13874],{"encoding":188},"\\pi_k",[104,13876,13878],{"className":13877,"ariaHidden":194},[193],[104,13879,13881,13884],{"className":13880},[198],[104,13882],{"className":13883,"style":2826},[202],[104,13885,13887,13890],{"className":13886},[207],[104,13888,13542],{"className":13889,"style":209},[207,208],[104,13891,13893],{"className":13892},[697],[104,13894,13896,13916],{"className":13895},[605,606],[104,13897,13899,13913],{"className":13898},[610],[104,13900,13902],{"className":13901,"style":5409},[614],[104,13903,13904,13907],{"style":13700},[104,13905],{"className":13906,"style":714},[622],[104,13908,13910],{"className":13909},[627,628,629,630],[104,13911,5374],{"className":13912,"style":5422},[207,208,630],[104,13914,667],{"className":13915},[666],[104,13917,13919],{"className":13918},[610],[104,13920,13922],{"className":13921,"style":807},[614],[104,13923],{}," é o peso (a fração de dados que pertence àquele grupo). O Bishop deriva o ajuste desse modelo via um algoritmo chamado ",[15,13926,13927],{},"EM"," (esperança-maximização), que alterna dois passos até convergir:",[357,13930,13931,14046],{},[360,13932,13933,13936,13937,134,13940,14037,14038,14041,14042,14045],{},[15,13934,13935],{},"Passo E (esperança)",": pra cada ponto, calcula a ",[15,13938,13939],{},"responsabilidade",[104,13941,13943,13973],{"className":13942},[148],[104,13944,13946],{"className":13945},[152],[154,13947,13948],{"xmlns":156},[158,13949,13950,13970],{},[161,13951,13952,13955,13957,13968],{},[164,13953,13954],{},"γ",[168,13956,470],{"stretchy":469},[515,13958,13959,13962],{},[164,13960,13961],{},"z",[161,13963,13964,13966],{},[164,13965,499],{},[164,13967,5374],{},[168,13969,476],{"stretchy":469},[186,13971,13972],{"encoding":188},"\\gamma(z_{nk})",[104,13974,13976],{"className":13975,"ariaHidden":194},[193],[104,13977,13979,13982,13986,13989,14034],{"className":13978},[198],[104,13980],{"className":13981,"style":558},[202],[104,13983,13954],{"className":13984,"style":13985},[207,208],"margin-right:0.0556em;",[104,13987,470],{"className":13988},[566],[104,13990,13992,13996],{"className":13991},[207],[104,13993,13961],{"className":13994,"style":13995},[207,208],"margin-right:0.044em;",[104,13997,13999],{"className":13998},[697],[104,14000,14002,14026],{"className":14001},[605,606],[104,14003,14005,14023],{"className":14004},[610],[104,14006,14008],{"className":14007,"style":5409},[614],[104,14009,14011,14014],{"style":14010},"top:-2.55em;margin-left:-0.044em;margin-right:0.05em;",[104,14012],{"className":14013,"style":714},[622],[104,14015,14017],{"className":14016},[627,628,629,630],[104,14018,14020],{"className":14019},[207,630],[104,14021,10411],{"className":14022,"style":5422},[207,208,630],[104,14024,667],{"className":14025},[666],[104,14027,14029],{"className":14028},[610],[104,14030,14032],{"className":14031,"style":807},[614],[104,14033],{},[104,14035,476],{"className":14036},[575]," de cada componente, a probabilidade (via Bayes) de que aquele ponto tenha vindo daquele grupo específico, dado onde as gaussianas estão agora. Diferente do K-means, que atribui cada ponto a ",[15,14039,14040],{},"um só"," grupo, o EM atribui uma responsabilidade ",[15,14043,14044],{},"fracionária"," a todos os grupos (um ponto na fronteira entre dois grupos pode ter 60% de responsabilidade num e 40% no outro).",[360,14047,14048,14051],{},[15,14049,14050],{},"Passo M (maximização)",": recalcula média, covariância e peso de cada gaussiana, usando essas responsabilidades como peso. Um ponto com responsabilidade 0.9 pro grupo 1 conta quase inteiro pra média e covariância do grupo 1, já um com responsabilidade 0.5\u002F0.5 conta meio a meio pros dois.",[11,14053,14054],{},"Isso é literalmente a versão \"macia\" do K-means: troca \"cada ponto pertence a exatamente um grupo\" por \"cada ponto pertence um pouco a cada grupo\", e troca \"grupo é só uma média\" por \"grupo é uma média mais um formato\".",[433,14056,14057],{},[11,14058,14059,14061,14062,14065],{},[15,14060,1632],{}," pesos encontrados, ",[87,14063,14064],{},"[0.40, 0.21, 0.39]"," (bate com a proporção real dos três grupos gerados). Convergiu em só 4 iterações.",[79,14067,14069],{"id":14068},"interativo-as-elipses-se-ajustando","Interativo: as elipses se ajustando",[11,14071,14072],{},"Reconstrução minha do algoritmo EM (nos mesmos 380 pontos, 3 grupos, dois deles esticados). Clica em \"Passo EM\" e acompanha as elipses (cada uma o contorno de 1 desvio padrão daquela gaussiana) girarem e se esticarem até encaixar no formato real dos dados:",[14074,14075],"gmm-explorer",{":n-components":5616,":points":14076,":x-max":14077,":x-min":14078,":y-max":14079,":y-min":14080,"x-label":11707,"y-label":11708},"[[-1.397,0.6689],[-1.5544,0.9572],[0.4216,0.4219],[-1.4608,0.7519],[-2.2469,0.7798],[-2.6499,0.1603],[-0.5571,0.0035],[-0.3012,1.944],[-2.1017,0.3399],[-0.7528,-1.1562],[-0.3647,2.9444],[-1.8702,0.663],[-1.3084,-0.5343],[-0.1774,0.0732],[-0.4236,-0.1319],[-1.8018,1.6157],[-0.3874,3.4638],[-0.3632,2.2896],[-1.5163,0.9009],[-0.2556,3.0671],[0.5872,0.5437],[-1.5002,-2.5391],[-1.691,1.0455],[0.1699,-0.2883],[0.2436,1.5866],[-1.8124,0.5865],[0.4329,1.4138],[-1.0305,1.3562],[-0.6777,-1.6514],[-0.1738,-0.6312],[-2.1474,0.6027],[1.3162,1.41],[0.0003,-0.635],[1.0803,0.8774],[-0.1051,-0.5368],[-0.4542,2.9433],[-0.5984,2.7038],[-0.5293,-0.3695],[-1.265,-1.6818],[-1.6018,1.2726],[-0.0668,0.5579],[-0.741,-0.6176],[0.9641,1.7692],[-0.1353,2.9909],[-0.0816,-0.0108],[-0.6033,-1.0474],[-1.9353,-2.3436],[-0.544,-0.9764],[-0.499,-0.7219],[0.6757,0.5116],[-2.169,-0.4307],[-1.6183,1.3119],[-0.957,-1.9875],[0.0241,-0.5593],[-1.8944,0.5322],[0.0781,0.0503],[1.234,1.02],[-1.0304,2.212],[0.0279,0.4623],[-0.3033,-0.0086],[-0.6413,1.4614],[-1.6879,1.2439],[-0.7695,2.5701],[0.026,0.2301],[-0.4519,-1.4045],[-1.5718,-2.2173],[-0.2266,-1.261],[-2.217,0.1283],[-0.7787,-0.9779],[-0.5035,-1.0994],[-0.6094,2.4055],[-0.7201,-1.1225],[-1.2544,1.2117],[1.1962,1.1776],[-0.5273,-1.0992],[-0.8385,-2.0174],[-2.4497,0.6202],[0.0672,0.4268],[-1.9462,0.6892],[-1.7471,0.8418],[0.6934,0.9687],[-1.2374,1.1307],[-1.0847,1.8531],[0.6181,0.3445],[0.4234,0.6546],[1.2764,2.13],[-0.7243,1.9397],[0.0644,0.2269],[-1.2528,1.4129],[-1.2586,2.5021],[-2.0632,2.267],[1.5012,1.4466],[-2.0287,1.4448],[-0.5999,-0.9094],[-1.944,1.9186],[0.4245,0.1732],[-1.7969,1.2382],[-1.2832,1.4056],[-1.5927,1.4856],[-0.2796,3.3672],[-1.6957,1.1181],[-0.7625,2.1224],[0.3737,0.3211],[0.4339,-0.622],[-1.9618,-0.0241],[-2.8526,0.3066],[-1.2317,0.759],[-1.5875,0.5392],[-0.5509,-0.2138],[-0.5282,-0.1087],[0.0566,0.3988],[-0.2795,-0.0704],[0.2273,1.597],[-2.3005,0.6762],[0.7842,1.0364],[-1.7827,1.3026],[0.1197,0.3453],[-0.8305,1.6026],[-0.7473,-0.6929],[-0.8915,-0.0462],[-1.5467,0.8436],[-1.65,0.6603],[-0.849,2.4742],[-1.1966,1.4964],[-1.0852,1.8029],[-1.5808,0.6337],[-1.3083,1.814],[-0.111,-0.992],[-0.3001,2.3096],[-1.2104,1.2865],[-1.7846,0.2639],[-2.0529,-0.4044],[1.7441,2.0217],[0.6524,0.6848],[0.11,-0.1965],[-0.0356,0.4157],[-0.0749,2.9341],[-1.5994,2.0179],[-1.0351,-1.8731],[-0.171,2.6711],[-0.4753,3.3274],[-2.3558,1.0955],[-2.0755,0.7241],[-1.8707,0.9155],[-2.8612,-0.5567],[-2.742,0.4937],[-0.3161,0.2769],[-1.7449,2.1745],[0.8469,-0.159],[-0.6866,2.0795],[-1.0555,1.2891],[-0.2582,0.0552],[-1.1792,-2.3321],[-0.2038,-0.2389],[-0.9721,1.4959],[-1.769,1.3658],[-0.4673,-0.3329],[-1.5077,-2.8246],[-2.2638,0.8664],[1.2534,1.3654],[1.0126,1.5736],[-0.5988,3.3043],[-1.898,-0.0087],[-1.4826,0.682],[0.3936,2.9382],[-1.7633,1.2016],[0.4435,0.7339],[-0.9929,1.6227],[-1.3397,-1.6588],[-0.7939,-0.9446],[-0.6199,-0.8483],[-1.2111,1.5388],[-2.0879,0.5661],[-0.6015,-0.4415],[-0.9774,-0.3869],[-2.8054,-0.2992],[0.2876,-0.0551],[-1.8222,0.4918],[-2.6337,-0.4616],[-1.6066,-1.8044],[0.9042,1.2706],[-2.4887,-0.5669],[-1.4762,2.1884],[-3.3941,-2.2838],[-0.0459,0.611],[0.3615,-0.6934],[-0.6614,-0.5482],[-0.9675,-1.1216],[-0.8114,1.6109],[-0.4249,-0.8102],[-0.3229,-0.2177],[-1.8453,1.2592],[0.6648,1.5428],[-2.018,1.4594],[-2.7884,0.4345],[0.257,0.8384],[-1.0581,1.6156],[-0.594,2.3061],[-0.8174,-0.8417],[2.0556,3.0074],[-1.0839,-1.2423],[0.5036,1.2528],[-2.7214,-0.3583],[-1.9335,1.9023],[0.093,0.2204],[-2.1258,0.0677],[1.971,3.5944],[0.2497,0.3212],[-0.5735,-1.5174],[-0.3901,2.4058],[0.0023,-0.8388],[-0.1752,0.0744],[0.4605,0.9383],[-0.7978,-0.7208],[-2.3619,0.0751],[-0.1661,3.8238],[0.2842,-0.7207],[0.7606,1.8876],[1.5808,4.2012],[0.2639,3.3232],[-1.2204,0.9527],[-1.7879,-1.6378],[-1.1302,1.8454],[1.2432,1.4315],[-2.1969,0.8246],[0.7229,0.4503],[0.3372,-0.0962],[-0.6671,2.5567],[-0.2091,-0.5909],[-0.5144,0.1183],[0.2037,0.7191],[0.4719,0.4481],[-0.723,2.1655],[0.0977,0.8399],[-0.4848,-0.1881],[0.3503,-0.8179],[-0.5901,-0.4508],[0.7263,1.0749],[-0.5648,-0.294],[-0.2182,-0.1902],[-0.9943,1.5624],[1.2449,1.9522],[0.623,1.2615],[-2.3547,-0.325],[-2.2113,0.7627],[-1.8254,1.4898],[1.2256,0.4739],[-0.4011,0.7219],[-0.1606,0.123],[-1.5009,1.0681],[0.0147,0.6807],[-0.0381,0.1796],[-1.0194,2.0814],[0.4602,2.7832],[-2.594,0.6801],[-1.0335,-1.261],[0.3909,3.2754],[0.0448,0.86],[-1.8919,1.0616],[0.0364,0.0331],[-0.9895,2.5191],[-0.8706,2.4464],[-1.3902,1.6082],[-1.3474,1.7972],[0.2534,0.1369],[-1.5536,0.9959],[-0.8877,2.0832],[-0.7537,-0.7697],[-0.9112,-0.787],[0.1413,-0.5064],[-1.9722,0.4258],[-2.2693,-0.1881],[-1.4793,0.6792],[-0.3873,2.6294],[0.1175,4.0415],[-2.4398,0.4219],[-0.1291,-0.6354],[1.6276,1.5164],[-1.9332,2.4903],[0.4905,1.1607],[-0.8633,2.2045],[-0.9049,1.9118],[0.6161,1.4715],[-2.5119,-0.4271],[0.8632,0.6314],[-1.8683,2.1036],[-1.8102,-0.5893],[-0.854,-1.0794],[-1.0491,2.6654],[-1.8057,1.3328],[-1.9607,0.4713],[0.1505,0.8971],[0.0872,0.6326],[1.8988,1.959],[-1.324,-1.569],[-1.2021,1.1349],[-2.8576,-0.3023],[-0.699,2.2929],[-0.0063,-0.3087],[-1.9344,0.5451],[3.455,2.5789],[2.9464,1.1252],[1.572,0.9878],[3.3295,1.4183],[2.6823,0.5125],[3.4773,-0.0434],[3.5826,-0.9733],[2.5003,0.448],[2.3398,1.39],[5.377,1.1889],[2.8108,1.2465],[2.4285,1.4879],[3.5583,-0.4105],[2.5828,-0.398],[4.0777,3.2047],[3.2711,1.3714],[2.0754,0.5936],[5.3566,1.4881],[3.2984,1.3158],[3.2566,0.7801],[2.8891,2.8666],[2.7887,0.6866],[2.8902,0.7226],[1.9401,1.0828],[2.7075,0.6922],[2.8139,1.626],[3.7183,2.3214],[4.9565,0.7885],[3.8149,0.6292],[3.6997,-0.9454],[5.07,1.7817],[4.1385,2.5373],[3.0302,2.0714],[4.3033,2.3705],[4.8936,-0.3876],[3.551,3.4775],[4.4062,1.343],[2.3845,-0.1819],[3.5879,1.9829],[3.8344,-0.7488],[0.8711,1.8362],[4.9687,0.496],[3.7413,1.3607],[3.7328,-0.899],[3.8221,1.9898],[4.2818,0.1049],[3.4561,-0.1544],[2.961,1.5276],[4.0127,1.3113],[3.4959,0.7797],[3.8522,2.5523],[3.0213,1.5568],[2.1626,1.2111],[3.7507,1.7961],[3.0274,0.5486],[1.8833,1.1989],[3.0987,-0.4492],[2.6516,0.7051],[2.2539,-0.3062],[4.3043,-0.2166],[2.5161,1.8014],[3.8524,0.3692],[3.5905,0.5108],[4.5218,1.9456],[3.7839,0.2999],[3.7484,0.9398],[3.1481,0.212],[3.5778,0.7153],[3.0116,0.8286],[2.0123,0.2944],[4.6336,1.7662],[2.2995,1.6708],[3.4188,2.0178],[4.3133,-0.2066],[2.5714,2.5642],[1.7659,0.452],[2.478,1.3285],[4.2293,1.1857],[3.7869,1.2753],[3.3752,0.7132]]","5.7","-3.7","4.5","-3.2",[11,14082,14083,14084,14087],{},"Repara que os pesos mostrados abaixo do gráfico se aproximam de ",[87,14085,14086],{},"0.40, 0.21, 0.39"," conforme você clica em \"Passo EM\" repetidas vezes, e as elipses vão de círculos genéricos (a inicialização) pra formas alongadas que seguem a direção real dos grupos.",[79,14089,14091],{"id":14090},"detecção-de-anomalia-de-graça","Detecção de anomalia de graça",[11,14093,14094],{},"Uma vantagem de ter um modelo de densidade (não só um agrupamento): dá pra perguntar \"quão provável é este ponto, dado o modelo?\" e marcar os menos prováveis como anomalia.",[96,14096,14098],{"className":98,"code":14097,"language":100,"meta":57,"style":57},"densities = gm.score_samples(X)\ndensity_threshold = np.percentile(densities, 2)\nanomalies = X[densities \u003C density_threshold]\n",[87,14099,14100,14105,14110],{"__ignoreMap":57},[104,14101,14102],{"class":106,"line":107},[104,14103,14104],{},"densities = gm.score_samples(X)\n",[104,14106,14107],{"class":106,"line":58},[104,14108,14109],{},"density_threshold = np.percentile(densities, 2)\n",[104,14111,14112],{"class":106,"line":118},[104,14113,14114],{},"anomalies = X[densities \u003C density_threshold]\n",[11,14116,14117],{},"Os pontos com densidade abaixo do percentil 2 (os 2% menos prováveis) viram candidatos a anomalia, e capturam exatamente o pequeno grupo isolado que o professor colocou de propósito longe dos outros dois.",[79,14119,14121],{"id":14120},"quantos-grupos-usar-bic-aic-e-um-jeito-mais-esperto","Quantos grupos usar? BIC, AIC, e um jeito mais esperto",[96,14123,14125],{"className":98,"code":14124,"language":100,"meta":57,"style":57},"gm.bic(X), gm.aic(X)\n",[87,14126,14127],{"__ignoreMap":57},[104,14128,14129],{"class":106,"line":107},[104,14130,14124],{},[433,14132,14133],{},[11,14134,14135,14137],{},[15,14136,1632],{}," BIC = 8189.73, AIC = 8102.51.",[11,14139,14140,14141,14144,14145,14195,14196,14225,14226,14254],{},"Os dois são ",[15,14142,14143],{},"critérios de informação",": medem o quão bem o modelo explica o dado, com uma penalidade por complexidade (mais grupos = mais parâmetros = penalidade maior), pra evitar escolher \"quanto mais grupo, melhor\" só porque mais grupos sempre ajusta melhor. Rodando pra ",[104,14146,14148,14165],{"className":14147},[148],[104,14149,14151],{"className":14150},[152],[154,14152,14153],{"xmlns":156},[158,14154,14155,14163],{},[161,14156,14157,14159,14161],{},[164,14158,5339],{},[168,14160,170],{},[483,14162,485],{},[186,14164,5523],{"encoding":188},[104,14166,14168,14186],{"className":14167,"ariaHidden":194},[193],[104,14169,14171,14174,14177,14180,14183],{"className":14170},[198],[104,14172],{"className":14173,"style":3515},[202],[104,14175,5339],{"className":14176,"style":5354},[207,208],[104,14178],{"className":14179,"style":214},[213],[104,14181,170],{"className":14182},[218],[104,14184],{"className":14185,"style":214},[213],[104,14187,14189,14192],{"className":14188},[198],[104,14190],{"className":14191,"style":2975},[202],[104,14193,485],{"className":14194},[207]," até ",[104,14197,14199,14213],{"className":14198},[148],[104,14200,14202],{"className":14201},[152],[154,14203,14204],{"xmlns":156},[158,14205,14206,14211],{},[161,14207,14208],{},[483,14209,14210],{},"9",[186,14212,14210],{"encoding":188},[104,14214,14216],{"className":14215,"ariaHidden":194},[193],[104,14217,14219,14222],{"className":14218},[198],[104,14220],{"className":14221,"style":2975},[202],[104,14223,14210],{"className":14224},[207]," e plotando os dois contra ",[104,14227,14229,14242],{"className":14228},[148],[104,14230,14232],{"className":14231},[152],[154,14233,14234],{"xmlns":156},[158,14235,14236,14240],{},[161,14237,14238],{},[164,14239,5339],{},[186,14241,5339],{"encoding":188},[104,14243,14245],{"className":14244,"ariaHidden":194},[193],[104,14246,14248,14251],{"className":14247},[198],[104,14249],{"className":14250,"style":3515},[202],[104,14252,5339],{"className":14253,"style":5354},[207,208],", o formato da curva aponta pro número de grupos que equilibra ajuste e simplicidade.",[11,14256,14257,14258,14261,14262,14265],{},"Tem um jeito ainda mais direto: ",[87,14259,14260],{},"BayesianGaussianMixture"," recebe um número ",[15,14263,14264],{},"generoso"," de componentes (10, nesse caso) e poda sozinho os que não são necessários, zerando o peso deles:",[96,14267,14269],{"className":98,"code":14268,"language":100,"meta":57,"style":57},"from sklearn.mixture import BayesianGaussianMixture\nbgm = BayesianGaussianMixture(n_components=10, n_init=10, random_state=42)\nbgm.fit(X)\nprint(np.round(bgm.weights_, 2))\n",[87,14270,14271,14276,14281,14286],{"__ignoreMap":57},[104,14272,14273],{"class":106,"line":107},[104,14274,14275],{},"from sklearn.mixture import BayesianGaussianMixture\n",[104,14277,14278],{"class":106,"line":58},[104,14279,14280],{},"bgm = BayesianGaussianMixture(n_components=10, n_init=10, random_state=42)\n",[104,14282,14283],{"class":106,"line":118},[104,14284,14285],{},"bgm.fit(X)\n",[104,14287,14288],{"class":106,"line":124},[104,14289,14290],{},"print(np.round(bgm.weights_, 2))\n",[433,14292,14293],{},[11,14294,14295,134,14297,14300,14301,14329],{},[15,14296,1632],{},[87,14298,14299],{},"[0.4, 0.21, 0.39, 0, 0, 0, 0, 0, 0, 0]",". Sete dos dez componentes zeraram sozinhos, sobrando os três de verdade, sem eu precisar escanear ",[104,14302,14304,14317],{"className":14303},[148],[104,14305,14307],{"className":14306},[152],[154,14308,14309],{"xmlns":156},[158,14310,14311,14315],{},[161,14312,14313],{},[164,14314,5339],{},[186,14316,5339],{"encoding":188},[104,14318,14320],{"className":14319,"ariaHidden":194},[193],[104,14321,14323,14326],{"className":14322},[198],[104,14324],{"className":14325,"style":3515},[202],[104,14327,5339],{"className":14328,"style":5354},[207,208]," manualmente.",[11,14331,14332],{},"O Bishop avisa de um problema técnico que vale conhecer: se uma gaussiana \"colapsar\" bem em cima de um único ponto de dado, a variância dela pode ir a zero e a verossimilhança do modelo vai a infinito, uma singularidade, não um bom ajuste. Implementações de verdade (como a do scikit-learn) evitam isso na prática com salvaguardas numéricas, mas é um lembrete de que \"achar o máximo da verossimilhança\" nem sempre é um problema tão bem-comportado quanto parece.",[79,14334,14336],{"id":14335},"trocando-de-assunto-outros-jeitos-de-achar-anomalia","Trocando de assunto: outros jeitos de achar anomalia",[11,14338,14339,14342,14343,14346],{},[87,14340,14341],{},"aula11b"," monta um cenário mais direto pra detecção de anomalia: 980 pontos \"normais\" (3 grupos bem comportados) mais 20 pontos espalhados aleatoriamente pelo espaço (as anomalias de verdade), e compara três detectores diferentes, cada um com o ",[87,14344,14345],{},"contamination"," padrão (0.1, ou seja \"assuma que 10% do dado é anomalia\") e depois ajustado via Optuna:",[1875,14348,14349,14362],{},[1878,14350,14351],{},[1881,14352,14353,14356,14359],{},[1884,14354,14355],{"align":1886},"Detector",[1884,14357,14358],{"align":3190},"F1 (anomalia), padrão",[1884,14360,14361],{"align":3190},"F1 (anomalia), ajustado",[1892,14363,14364,14377,14391],{},[1881,14365,14366,14371,14374],{},[1897,14367,14368],{"align":1886},[87,14369,14370],{},"IsolationForest",[1897,14372,14373],{"align":3190},"≈ 0.27",[1897,14375,14376],{"align":3190},"0.68",[1881,14378,14379,14384,14386],{},[1897,14380,14381],{"align":1886},[87,14382,14383],{},"LocalOutlierFactor",[1897,14385,14373],{"align":3190},[1897,14387,14388],{"align":3190},[15,14389,14390],{},"0.79",[1881,14392,14393,14398,14401],{},[1897,14394,14395],{"align":1886},[87,14396,14397],{},"OneClassSVM",[1897,14399,14400],{"align":3190},"≈ 0.24",[1897,14402,14403],{"align":3190},"0.64",[11,14405,1902,14406,14409,14410,14413,14414,14416,14417,14419,14420,14424],{},[87,14407,14408],{},"contamination=0.1"," padrão manda cada detector marcar ",[15,14411,14412],{},"10%"," do dado como anomalia (98 pontos), mas só 20 dos 1000 pontos (2%) são anomalias de verdade. Forçar o modelo a encontrar 5 vezes mais anomalias do que realmente existem garante um monte de falso positivo, por isso o F1 baixo em todos os três antes do ajuste. Depois que o Optuna busca o ",[87,14415,14345],{}," certo (perto de 0.02, o valor real) e outros hiperparâmetros de cada modelo, os três melhoram bastante, e o ",[87,14418,14383],{}," (que decide \"anômalo\" comparando a densidade local de um ponto com a dos vizinhos, ",[20,14421,14423],{"href":14422},"\u002Fplaylists\u002Fpattern-recognition\u002Fdbscan-semi-supervised","o mesmo tipo de raciocínio por densidade que o DBSCAN usa",") sai na frente.",[11,14426,14427,14428,14430,14431,14434],{},"A lição prática, batendo com o resto da aula: ",[87,14429,14345],{}," não é um detalhe cosmético, é a peça mais importante do ajuste, porque ele diz ao modelo ",[15,14432,14433],{},"quantas"," anomalias procurar. Sem saber (ou estimar bem) essa fração de antemão, qualquer um desses três detectores erra o alvo.",[79,14436,1873],{"id":1872},[1875,14438,14439,14447],{},[1878,14440,14441],{},[1881,14442,14443,14445],{},[1884,14444,1887],{"align":1886},[1884,14446,1890],{"align":1886},[1892,14448,14449,14460,14468],{},[1881,14450,14451,14454],{},[1897,14452,14453],{"align":1886},"K-means agrupa por distância até um centro",[1897,14455,14456,14457,14459],{"align":1886},"Misturas gaussianas agrupam por densidade ",[15,14458,8775],{}," formato, com responsabilidade fracionária em vez de atribuição rígida",[1881,14461,14462,14465],{},[1897,14463,14464],{"align":1886},"DBSCAN acha anomalia como \"quem não é núcleo de nada\"",[1897,14466,14467],{"align":1886},"Densidade de modelo (GMM) e densidade local (LOF) são outros dois jeitos válidos de definir \"anomalia\", cada um com seu próprio viés",[1881,14469,14470,14473],{},[1897,14471,14472],{"align":1886},"Hiperparâmetro importa",[1897,14474,14475,14476,14478],{"align":1886},"Pra detecção de anomalia especificamente, ",[87,14477,14345],{}," é o hiperparâmetro que mais importa, porque a maioria dos algoritmos precisa saber de antemão quanto de anomalia procurar",[79,14480,1943],{"id":1942},[11,14482,14483],{},"Uso a mesma ideia de densidade da seção de detecção de anomalia via GMM, mas comparo dois cortes de percentil diferentes no mesmo dataset de 3 grupos, pra ver o quanto a escolha do corte muda quantos pontos viram \"anomalia\".",[96,14485,14487],{"className":98,"code":14486,"language":100,"meta":57,"style":57},"for percentil in [1, 2, 5, 10]:\n    threshold = np.percentile(densities, percentil)\n    n_anomalias = (densities \u003C threshold).sum()\n    print(percentil, n_anomalias)\n",[87,14488,14489,14494,14499,14504],{"__ignoreMap":57},[104,14490,14491],{"class":106,"line":107},[104,14492,14493],{},"for percentil in [1, 2, 5, 10]:\n",[104,14495,14496],{"class":106,"line":58},[104,14497,14498],{},"    threshold = np.percentile(densities, percentil)\n",[104,14500,14501],{"class":106,"line":118},[104,14502,14503],{},"    n_anomalias = (densities \u003C threshold).sum()\n",[104,14505,14506],{"class":106,"line":124},[104,14507,14508],{},"    print(percentil, n_anomalias)\n",[433,14510,14511],{},[11,14512,14513,14516],{},[15,14514,14515],{},"Saída (1250 pontos no total, os mesmos 750+250 gerados na aula):"," com corte em 1%, 13 pontos marcados. Com 2% (o valor usado no notebook), 25 pontos. Com 5%, 63 pontos. Com 10%, 125 pontos.",[11,14518,14519],{},"O número de \"anomalias\" encontradas escala praticamente linear com o percentil escolhido, porque é literalmente assim que um corte de percentil funciona: ele sempre acha exatamente aquela fração do dado, não importa se existe uma anomalia real ali ou não. É o mesmo ponto que a comparação de detectores da aula acabou de mostrar de outro jeito: definir \"quanto\" procurar é uma escolha que muda o resultado tanto quanto o algoritmo em si.",[1985,14521,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":14523},[14524,14525,14526,14527,14528,14529,14530,14531],{"id":13432,"depth":58,"text":13433},{"id":13439,"depth":58,"text":13440},{"id":14068,"depth":58,"text":14069},{"id":14090,"depth":58,"text":14091},{"id":14120,"depth":58,"text":14121},{"id":14335,"depth":58,"text":14336},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 11: o professor troca o K-means por misturas gaussianas quando os grupos não são redondos, usa densidade pra achar anomalia, e depois compara três detectores de anomalia diferentes, todos precisando de ajuste fino pra funcionar bem.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fgaussian-mixtures-anomaly-detection",{"title":13420,"description":14532},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fgaussian-mixtures-anomaly-detection",[14538,34,14539],"misturas-gaussianas","deteccao-de-anomalia","H0DLLJk6DsuXBzKw0B6xDUaziNCInFD-yrYxCpmC3wk",{"id":14542,"title":14543,"body":14544,"cover":3,"date":9189,"description":15196,"extension":61,"meta":15197,"navigation":63,"order":1716,"path":15198,"playlist":2003,"seo":15199,"status":66,"stem":15200,"tags":15201,"__hash__":15205},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fcredit-card-fraud.md","Detecção de Fraude: Quando 99.8% de Acurácia Não Significa Nada",{"type":8,"value":14545,"toc":15186},[14546,14555,14559,14574,14581,14603,14607,14658,14671,14678,14682,14696,14707,14710,14721,14725,14728,14841,14848,14855,14859,14865,14899,14906,14924,14928,14934,14981,14988,15014,15033,15035,15081,15083,15090,15105,15171,15177,15184],[11,14547,14548,14549,14554],{},"Aula 12, e o dataset (o ",[20,14550,14553],{"href":14551,"rel":14552},"https:\u002F\u002Fwww.kaggle.com\u002Fdatasets\u002Fmlg-ulb\u002Fcreditcardfraud",[45],"Credit Card Fraud Detection do Kaggle",", transações reais e anonimizadas de cartão de crédito europeu) é grande demais e protegido demais pra eu conseguir baixar e reproduzir sozinho nesse ambiente, sem conta no Kaggle. Esse post fica em cima dos números que o próprio notebook já rodou (executado de verdade, saídas reais), e a seção de Aplicação Prática reconstrói o achado mais importante da aula num dataset sintético que eu consigo gerar e conferir na hora.",[79,14556,14558],{"id":14557},"o-dataset-492-fraudes-em-quase-285-mil-transações","O dataset: 492 fraudes em quase 285 mil transações",[96,14560,14562],{"className":98,"code":14561,"language":100,"meta":57,"style":57},"df = pd.read_csv('creditcard.csv')\nprint(df['Class'].value_counts())\n",[87,14563,14564,14569],{"__ignoreMap":57},[104,14565,14566],{"class":106,"line":107},[104,14567,14568],{},"df = pd.read_csv('creditcard.csv')\n",[104,14570,14571],{"class":106,"line":58},[104,14572,14573],{},"print(df['Class'].value_counts())\n",[433,14575,14576],{},[11,14577,14578,14580],{},[15,14579,1632],{}," classe 0 (transação normal): 284315. Classe 1 (fraude): 492.",[11,14582,14583,14584,14587,14588,14591,14592,14595,14596,1502,14599,14602],{},"Isso é ",[15,14585,14586],{},"0.17%"," de fraude. As variáveis de entrada já vêm transformadas por PCA (",[87,14589,14590],{},"V1"," a ",[87,14593,14594],{},"V28",", sem nome original, por privacidade do banco), mais ",[87,14597,14598],{},"Time",[87,14600,14601],{},"Amount"," sem transformar. Esse desbalanceamento extremo é o assunto do post inteiro.",[79,14604,14606],{"id":14605},"o-baseline-que-expõe-a-mentira-da-acurácia","O baseline que expõe a mentira da acurácia",[96,14608,14610],{"className":98,"code":14609,"language":100,"meta":57,"style":57},"class ZeroR(BaseEstimator, TransformerMixin):\n    def fit(self, X, y):\n        self.most_frequent_class_ = y.value_counts().idxmax()\n        return self\n    def transform(self, X):\n        return [self.most_frequent_class_] * len(X)\n\nmodel = ZeroR()\nmodel.fit(X_train, y_train)\nprint(accuracy_score(y_test, model.transform(X_test)))\n",[87,14611,14612,14617,14621,14626,14630,14635,14640,14644,14649,14653],{"__ignoreMap":57},[104,14613,14614],{"class":106,"line":107},[104,14615,14616],{},"class ZeroR(BaseEstimator, TransformerMixin):\n",[104,14618,14619],{"class":106,"line":58},[104,14620,1472],{},[104,14622,14623],{"class":106,"line":118},[104,14624,14625],{},"        self.most_frequent_class_ = y.value_counts().idxmax()\n",[104,14627,14628],{"class":106,"line":124},[104,14629,1482],{},[104,14631,14632],{"class":106,"line":308},[104,14633,14634],{},"    def transform(self, X):\n",[104,14636,14637],{"class":106,"line":417},[104,14638,14639],{},"        return [self.most_frequent_class_] * len(X)\n",[104,14641,14642],{"class":106,"line":422},[104,14643,300],{"emptyLinePlaceholder":63},[104,14645,14646],{"class":106,"line":428},[104,14647,14648],{},"model = ZeroR()\n",[104,14650,14651],{"class":106,"line":1692},[104,14652,5849],{},[104,14654,14655],{"class":106,"line":1698},[104,14656,14657],{},"print(accuracy_score(y_test, model.transform(X_test)))\n",[433,14659,14660],{},[11,14661,14662,134,14664,14667,14668,1866],{},[15,14663,1632],{},[87,14665,14666],{},"0.9983",". Chutando \"não é fraude\" sempre, sem olhar pra nenhuma variável, o modelo mais idiota possível acerta ",[15,14669,14670],{},"99.83%",[11,14672,14673,14674,14677],{},"Esse é o baseline mais extremo que já apareceu nessa playlist (",[20,14675,14676],{"href":8961},"o Car Evaluation, no post de árvores, tinha 70%",", aqui são quase 100%). Qualquer notícia de \"modelo de fraude com 99% de acurácia\" precisa dessa régua do lado, porque sem ela o número não diz nada.",[79,14679,14681],{"id":14680},"um-modelo-de-verdade-e-duas-curvas-melhores-que-acurácia","Um modelo de verdade, e duas curvas melhores que acurácia",[96,14683,14685],{"className":98,"code":14684,"language":100,"meta":57,"style":57},"model = LogisticRegression(tol=0.005)\nmodel.fit(X_train, y_train)\n",[87,14686,14687,14692],{"__ignoreMap":57},[104,14688,14689],{"class":106,"line":107},[104,14690,14691],{},"model = LogisticRegression(tol=0.005)\n",[104,14693,14694],{"class":106,"line":58},[104,14695,5849],{},[433,14697,14698],{},[11,14699,14700,14702,14703,14706],{},[15,14701,1632],{}," matriz de confusão ",[87,14704,14705],{},"[[56832, 32], [26, 72]]",". Precisão 0.69, revocação 0.73, F1 0.71 (pra classe fraude). ROC AUC 0.867. AUC da curva precisão-revocação: 0.613.",[11,14708,14709],{},"Repara que eu nem cito acurácia aqui, ela ia esconder tudo (98 fraudes entre quase 57 mil transações, então qualquer modelo razoável já bate 99.8%+ de acurácia). Precisão e revocação, sim, contam a história certa: das transações que o modelo marcou como fraude, 69% eram fraude de verdade. Das fraudes reais, o modelo pegou 73%.",[11,14711,14712,14713,14716,14717,14720],{},"E entre as duas curvas, a curva precisão-revocação (AUC 0.613) é mais honesta que a curva ROC (AUC 0.867) pra esse tipo de problema. A curva ROC usa a taxa de falso positivo no eixo, que é ",[87,14714,14715],{},"falsos positivos \u002F total de negativos",", e com quase 57 mil negativos, até um punhado de falsos positivos vira uma fração minúscula, a curva parece ótima quase à toa. A curva precisão-revocação, em compensação, usa precisão no eixo, que é ",[87,14718,14719],{},"verdadeiros positivos \u002F (verdadeiros positivos + falsos positivos)",", direto sensível a quantos falsos positivos existem comparado às poucas fraudes reais, sem se diluir no mar de negativos.",[79,14722,14724],{"id":14723},"resampling-o-remédio-que-piora-as-coisas","Resampling: o remédio que piora as coisas",[11,14726,14727],{},"A ideia mais comum pra lidar com desbalanceamento é reamostrar o treino, de um jeito ou de outro, pra equilibrar as classes:",[1875,14729,14730,14749],{},[1878,14731,14732],{},[1881,14733,14734,14737,14740,14743,14746],{},[1884,14735,14736],{"align":1886},"Técnica",[1884,14738,14739],{"align":1886},"Como funciona",[1884,14741,14742],{"align":3190},"Precisão",[1884,14744,14745],{"align":3190},"Revocação",[1884,14747,14748],{"align":3190},"F1",[1892,14750,14751,14768,14786,14804,14823],{},[1881,14752,14753,14756,14759,14762,14765],{},[1897,14754,14755],{"align":1886},"Nenhuma (baseline)",[1897,14757,14758],{"align":1886},"-",[1897,14760,14761],{"align":3190},"0.69",[1897,14763,14764],{"align":3190},"0.73",[1897,14766,14767],{"align":3190},"0.71",[1881,14769,14770,14775,14778,14781,14783],{},[1897,14771,14772],{"align":1886},[87,14773,14774],{},"RandomOverSampler",[1897,14776,14777],{"align":1886},"duplica exemplos da classe minoritária",[1897,14779,14780],{"align":3190},"0.04",[1897,14782,5619],{"align":3190},[1897,14784,14785],{"align":3190},"0.08",[1881,14787,14788,14793,14796,14799,14801],{},[1897,14789,14790],{"align":1886},[87,14791,14792],{},"RandomUnderSampler",[1897,14794,14795],{"align":1886},"descarta exemplos da classe majoritária",[1897,14797,14798],{"align":3190},"0.03",[1897,14800,5619],{"align":3190},[1897,14802,14803],{"align":3190},"0.06",[1881,14805,14806,14811,14814,14817,14820],{},[1897,14807,14808],{"align":1886},[87,14809,14810],{},"SMOTE",[1897,14812,14813],{"align":1886},"cria exemplos sintéticos interpolando vizinhos da minoria",[1897,14815,14816],{"align":3190},"0.07",[1897,14818,14819],{"align":3190},"0.91",[1897,14821,14822],{"align":3190},"0.12",[1881,14824,14825,14830,14833,14835,14838],{},[1897,14826,14827],{"align":1886},[87,14828,14829],{},"NearMiss",[1897,14831,14832],{"align":1886},"descarta exemplos majoritários perto da fronteira",[1897,14834,14798],{"align":3190},[1897,14836,14837],{"align":3190},"0.90",[1897,14839,14840],{"align":3190},"0.05",[11,14842,14843,14844,14847],{},"Nas quatro técnicas, a revocação sobe (de 0.73 pra ~0.90), o modelo passa a pegar mais fraudes de verdade. Mas a precisão ",[15,14845,14846],{},"despenca"," (de 0.69 pra 0.03-0.07), o modelo passa a gritar \"fraude!\" pra um monte de transação normal também. O F1 (que equilibra as duas) piora bastante em todos os quatro casos. Reamostrar não é bala de prata, é uma troca, e nesse dataset específico a troca sai perdendo.",[11,14849,14850,14851,14854],{},"O motivo da precisão desabar tão feio é uma mistura simples de duas coisas. Primeiro, o reamostramento só mexe no ",[15,14852,14853],{},"treino",": o modelo aprende num mundo artificialmente equilibrado (perto de 50\u002F50), mas continua sendo testado no mundo real, onde fraude é 0.17% das transações. Segundo, com tanta transação normal no teste (quase 57 mil), até uma taxinha pequena de erro nelas vira um monte de caso em número absoluto: se o modelo, calibrado pra um mundo onde fraude é comum, passa a \"desconfiar\" de qualquer coisa que se pareça um pouco com fraude, mesmo uma taxa de falso positivo de 1-2% em cima de 57 mil transações normais já gera centenas de alarmes falsos, muito mais do que as poucas dezenas de fraudes reais que existem pra acertar. É essa desproporção entre \"quantos normais existem\" e \"quantos falsos positivos o modelo agora comete\" que faz a precisão desabar.",[79,14856,14858],{"id":14857},"o-jeito-errado-de-reamostrar-e-por-que-ele-engana","O jeito errado de reamostrar (e por que ele engana)",[11,14860,14861,14862,1866],{},"O notebook tem uma seção com o título mais direto da matéria inteira: ",[15,14863,14864],{},"\"Abordagem ERRADA - Não fazer assim!\"",[96,14866,14868],{"className":98,"code":14867,"language":100,"meta":57,"style":57},"smote = SMOTE(random_state=42)\nX_resampled, y_resampled = smote.fit_resample(X, y)  # reamostra ANTES de separar treino\u002Fteste\n\nX_train_resampled, X_test_resampled, y_train_resampled, y_test_resampled = train_test_split(\n    X_resampled, y_resampled, test_size=0.2, random_state=42)\nmodel.fit(X_train_resampled, y_train_resampled)\n",[87,14869,14870,14875,14880,14884,14889,14894],{"__ignoreMap":57},[104,14871,14872],{"class":106,"line":107},[104,14873,14874],{},"smote = SMOTE(random_state=42)\n",[104,14876,14877],{"class":106,"line":58},[104,14878,14879],{},"X_resampled, y_resampled = smote.fit_resample(X, y)  # reamostra ANTES de separar treino\u002Fteste\n",[104,14881,14882],{"class":106,"line":118},[104,14883,300],{"emptyLinePlaceholder":63},[104,14885,14886],{"class":106,"line":124},[104,14887,14888],{},"X_train_resampled, X_test_resampled, y_train_resampled, y_test_resampled = train_test_split(\n",[104,14890,14891],{"class":106,"line":308},[104,14892,14893],{},"    X_resampled, y_resampled, test_size=0.2, random_state=42)\n",[104,14895,14896],{"class":106,"line":417},[104,14897,14898],{},"model.fit(X_train_resampled, y_train_resampled)\n",[433,14900,14901],{},[11,14902,14903,14905],{},[15,14904,1632],{}," precisão 0.98, revocação 0.97, F1 0.97. Um resultado impressionante.",[11,14907,14908,14909,14912,14913,14915,14916,14919,14920,14923],{},"Impressionante e ",[15,14910,14911],{},"inválido",". O ",[87,14914,14810],{}," foi chamado em cima do dataset inteiro, antes de separar treino e teste. Como SMOTE cria exemplo sintético interpolando entre vizinhos reais da classe minoritária, alguns dos exemplos sintéticos que caem no \"treino\" depois da divisão são quase idênticos a exemplos reais que caíram no \"teste\". O modelo não está generalizando pra dado nunca visto, está reconhecendo cópias quase idênticas do que ele já treinou, ",[20,14917,14918],{"href":5646},"o mesmo tipo de vazamento que eu já vi antes",", só que dessa vez escondido dentro de uma técnica de reamostragem em vez de um ",[87,14921,14922],{},"fit_transform"," no lugar errado.",[79,14925,14927],{"id":14926},"o-jeito-certo-reamostragem-dentro-do-pipeline","O jeito certo: reamostragem dentro do pipeline",[11,14929,14930,14931,14933],{},"A correção é reamostrar ",[15,14932,4883],{}," de cada divisão de validação cruzada, nunca antes, exatamente como qualquer normalização ou seleção de feature deveria ser feita:",[96,14935,14937],{"className":98,"code":14936,"language":100,"meta":57,"style":57},"from imblearn.pipeline import Pipeline\nfrom sklearn.model_selection import cross_validate, StratifiedKFold\n\npipe = Pipeline([\n    ('sampling', SMOTE(random_state=42)),\n    ('model', LogisticRegression(tol=0.005))\n])\ncv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\nscores = cross_validate(pipe, X, y, cv=cv, scoring=['precision', 'recall', 'f1'])\n",[87,14938,14939,14944,14949,14953,14958,14963,14968,14972,14976],{"__ignoreMap":57},[104,14940,14941],{"class":106,"line":107},[104,14942,14943],{},"from imblearn.pipeline import Pipeline\n",[104,14945,14946],{"class":106,"line":58},[104,14947,14948],{},"from sklearn.model_selection import cross_validate, StratifiedKFold\n",[104,14950,14951],{"class":106,"line":118},[104,14952,300],{"emptyLinePlaceholder":63},[104,14954,14955],{"class":106,"line":124},[104,14956,14957],{},"pipe = Pipeline([\n",[104,14959,14960],{"class":106,"line":308},[104,14961,14962],{},"    ('sampling', SMOTE(random_state=42)),\n",[104,14964,14965],{"class":106,"line":417},[104,14966,14967],{},"    ('model', LogisticRegression(tol=0.005))\n",[104,14969,14970],{"class":106,"line":422},[104,14971,5907],{},[104,14973,14974],{"class":106,"line":428},[104,14975,9098],{},[104,14977,14978],{"class":106,"line":1692},[104,14979,14980],{},"scores = cross_validate(pipe, X, y, cv=cv, scoring=['precision', 'recall', 'f1'])\n",[433,14982,14983],{},[11,14984,14985,14987],{},[15,14986,1632],{}," precisão média ≈ 0.07, revocação média ≈ 0.89, F1 médio ≈ 0.14, nas 5 dobras.",[11,14989,14990,14991,14993,14994,14997,14998,15000,15001,15003,15004,15006,15007,134,15009,134,15011,15013],{},"Bem mais parecido com o resultado honesto do SMOTE isolado (F1 0.12) do que com o 0.97 inflado da abordagem errada. Repara no detalhe técnico: aqui é o ",[87,14992,1501],{}," do pacote ",[87,14995,14996],{},"imblearn",", não o do scikit-learn. O ",[87,14999,1501],{}," comum só aceita etapas que transformam ",[87,15002,133],{},", mas ",[87,15005,14810],{}," precisa mexer em ",[87,15008,133],{},[15,15010,8775],{},[87,15012,166],{}," juntos (cria linhas novas nos dois), então precisa de uma versão de pipeline que saiba propagar essa mudança de tamanho adiante.",[11,15015,15016,15017,15019,15020,15022,15023,15026,15027,134,15029,15032],{},"Um bônus notado de passagem: adicionar um ",[87,15018,5867],{}," antes do ",[87,15021,14810],{}," no pipeline não muda muito o resultado, mas deixa o ajuste ",[15,15024,15025],{},"bem mais rápido"," (de uns 15-19 segundos por dobra pra 2-3 segundos). Faz sentido: o ",[87,15028,14810],{},[20,15030,15031],{"href":5820},"precisa achar vizinhos mais próximos pra interpolar"," pra criar cada exemplo sintético, e busca de vizinho em dado não normalizado, com variáveis em escalas bem diferentes, é mais cara de calcular.",[79,15034,1873],{"id":1872},[1875,15036,15037,15045],{},[1878,15038,15039],{},[1881,15040,15041,15043],{},[1884,15042,1887],{"align":1886},[1884,15044,1890],{"align":1886},[1892,15046,15047,15055,15063],{},[1881,15048,15049,15052],{},[1897,15050,15051],{"align":1886},"Um baseline idiota ajuda a interpretar acurácia",[1897,15053,15054],{"align":1886},"Com desbalanceamento extremo (0.17% de fraude), a acurácia praticamente perde o sentido, e precisão\u002Frevocação\u002Fcurva PR precisam assumir o posto",[1881,15056,15057,15060],{},[1897,15058,15059],{"align":1886},"Vazamento acontece quando o mesmo dado influencia treino e avaliação",[1897,15061,15062],{"align":1886},"Reamostrar antes de separar treino\u002Fteste é um vazamento tão sério quanto normalizar errado, só que mais fácil de não perceber",[1881,15064,15065,15070],{},[1897,15066,15067,15069],{"align":1886},[87,15068,1501],{}," evita vazamento entre etapas",[1897,15071,15072,15075,15076,15078,15079],{"align":1886},[87,15073,15074],{},"imblearn.Pipeline"," estende a mesma ideia pra técnicas que também mudam o ",[87,15077,166],{},", não só o ",[87,15080,133],{},[79,15082,1943],{"id":1942},[11,15084,15085,15086,15089],{},"Não consigo baixar o dataset real de fraude aqui (precisa de login no Kaggle), então reconstruí o achado mais importante da aula, o vazamento do SMOTE-antes-do-split, num dataset sintético que eu controlo e posso conferir: 20 mil exemplos, 2% de classe positiva (",[87,15087,15088],{},"make_classification"," do scikit-learn, com uma fração de rótulo invertido de propósito pra não ficar bom demais).",[96,15091,15093],{"className":98,"code":15092,"language":100,"meta":57,"style":57},"from sklearn.datasets import make_classification\nX, y = make_classification(n_samples=20000, weights=[0.98, 0.02], flip_y=0.01, random_state=42)\n",[87,15094,15095,15100],{"__ignoreMap":57},[104,15096,15097],{"class":106,"line":107},[104,15098,15099],{},"from sklearn.datasets import make_classification\n",[104,15101,15102],{"class":106,"line":58},[104,15103,15104],{},"X, y = make_classification(n_samples=20000, weights=[0.98, 0.02], flip_y=0.01, random_state=42)\n",[1875,15106,15107,15119],{},[1878,15108,15109],{},[1881,15110,15111,15113,15115,15117],{},[1884,15112,11240],{"align":1886},[1884,15114,14742],{"align":3190},[1884,15116,14745],{"align":3190},[1884,15118,14748],{"align":3190},[1892,15120,15121,15135,15157],{},[1881,15122,15123,15126,15129,15132],{},[1897,15124,15125],{"align":1886},"Regressão logística, sem reamostrar",[1897,15127,15128],{"align":3190},"1.000",[1897,15130,15131],{"align":3190},"0.190",[1897,15133,15134],{"align":3190},"0.319",[1881,15136,15137,15142,15147,15152],{},[1897,15138,15139],{"align":1886},[15,15140,15141],{},"SMOTE antes do split (errado)",[1897,15143,15144],{"align":3190},[15,15145,15146],{},"0.801",[1897,15148,15149],{"align":3190},[15,15150,15151],{},"0.791",[1897,15153,15154],{"align":3190},[15,15155,15156],{},"0.796",[1881,15158,15159,15162,15165,15168],{},[1897,15160,15161],{"align":1886},"SMOTE dentro do pipeline + validação cruzada (certo)",[1897,15163,15164],{"align":3190},"0.086",[1897,15166,15167],{"align":3190},"0.731",[1897,15169,15170],{"align":3190},"0.154",[11,15172,15173,15174,15176],{},"(O ",[87,15175,7080],{},", sempre chutando \"não é fraude\", bate 0.975 de acurácia sem detectar nenhuma fraude, o mesmo baseline idiota de sempre, agora numa escala menor.)",[11,15178,15179,15180,15183],{},"A diferença entre \"errado\" e \"certo\" aqui é ainda mais dramática que no dataset real de fraude: F1 de 0.796 (parece excelente) contra 0.154 (o número honesto), só trocando ",[15,15181,15182],{},"quando"," o SMOTE roda. Mesma conclusão da aula, confirmada num dataset que eu montei do zero: o vazamento de reamostrar antes de separar treino e teste não é um detalhe teórico, ele infla o resultado o suficiente pra transformar um modelo mediano num modelo que parece pronto pra produção, sem ser.",[1985,15185,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":15187},[15188,15189,15190,15191,15192,15193,15194,15195],{"id":14557,"depth":58,"text":14558},{"id":14605,"depth":58,"text":14606},{"id":14680,"depth":58,"text":14681},{"id":14723,"depth":58,"text":14724},{"id":14857,"depth":58,"text":14858},{"id":14926,"depth":58,"text":14927},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 12: o professor usa o dataset real de fraude de cartão do Kaggle (492 fraudes em quase 285 mil transações) pra mostrar por que acurácia mente em dado desbalanceado, e um jeito de vazar dado tão sutil que o resultado errado parece ótimo.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fcredit-card-fraud",{"title":14543,"description":15196},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fcredit-card-fraud",[15202,15203,15204],"dados-desbalanceados","deteccao-de-fraude","vazamento-de-dados","4nM_cL1UfP9bisw2YNJW2W3TwuUQcYaA3-rUSb9ve3U",{"id":15207,"title":15208,"body":15209,"cover":3,"date":9189,"description":16047,"extension":61,"meta":16048,"navigation":63,"order":1722,"path":16049,"playlist":2003,"seo":16050,"status":66,"stem":16051,"tags":16052,"__hash__":16056},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Ffeature-selection.md","Seleção de Características: Deixar o Rótulo Escolher, não só a Variância",{"type":8,"value":15210,"toc":16037},[15211,15219,15223,15229,15265,15273,15279,15283,15298,15309,15322,15326,15595,15640,15670,15674,15677,15684,15687,15691,15738,15745,15752,15759,15763,15770,15805,15816,15887,15889,15928,15930,15940,15970,16032,16035],[11,15212,15213,15214,15218],{},"Aula 13, e o assunto fecha um ciclo aberto ",[20,15215,15217],{"href":15216},"\u002Fplaylists\u002Fpattern-recognition\u002Fpca","lá no post de PCA",": reduzir dimensão sem usar o rótulo pode jogar fora informação importante. Seleção de características resolve isso do jeito mais direto possível, usando o rótulo pra decidir o que descartar.",[79,15220,15222],{"id":15221},"o-dataset-dígitos-pixel-por-pixel","O dataset: dígitos, pixel por pixel",[11,15224,15225,15226,15228],{},"MNIST de novo, 60 mil imagens de treino, 784 pixels cada. O professor imprime um dígito como arte ASCII (",[87,15227,133],{}," onde tem tinta, espaço onde não tem), um jeito bem literal de lembrar que cada uma dessas 784 \"variáveis\" é só um pixel, e a maioria deles (as bordas da imagem, por exemplo) provavelmente nunca tem tinta nenhuma.",[96,15230,15232],{"className":98,"code":15231,"language":100,"meta":57,"style":57},"class Normalizer255(BaseEstimator, TransformerMixin):\n    def fit(self, X, y=None):\n        return self\n    def transform(self, X, y=None):\n        return X \u002F 255.0\n\npipeline = make_pipeline(Normalizer255(), RidgeClassifier())\n",[87,15233,15234,15239,15243,15247,15251,15256,15260],{"__ignoreMap":57},[104,15235,15236],{"class":106,"line":107},[104,15237,15238],{},"class Normalizer255(BaseEstimator, TransformerMixin):\n",[104,15240,15241],{"class":106,"line":58},[104,15242,9498],{},[104,15244,15245],{"class":106,"line":118},[104,15246,1482],{},[104,15248,15249],{"class":106,"line":124},[104,15250,9507],{},[104,15252,15253],{"class":106,"line":308},[104,15254,15255],{},"        return X \u002F 255.0\n",[104,15257,15258],{"class":106,"line":417},[104,15259,300],{"emptyLinePlaceholder":63},[104,15261,15262],{"class":106,"line":422},[104,15263,15264],{},"pipeline = make_pipeline(Normalizer255(), RidgeClassifier())\n",[433,15266,15267],{},[11,15268,15269,15272],{},[15,15270,15271],{},"Saída (todos os 784 pixels):"," 0.8604 de acurácia no teste.",[11,15274,15275,15276,15278],{},"Repara na normalização: em vez de ",[87,15277,5867],{}," (que precisa calcular média e desvio do treino), o professor usa uma constante fixa, 255, porque ele já sabe de antemão que pixel de imagem em escala de cinza vai de 0 a 255. Conhecimento do domínio substituindo uma conta que seria desnecessária.",[79,15280,15282],{"id":15281},"descartar-por-variância-nem-sempre-ajuda","Descartar por variância: nem sempre ajuda",[96,15284,15286],{"className":98,"code":15285,"language":100,"meta":57,"style":57},"from sklearn.feature_selection import VarianceThreshold\nX_transformed = VarianceThreshold(0.1).fit_transform(X_train)\n",[87,15287,15288,15293],{"__ignoreMap":57},[104,15289,15290],{"class":106,"line":107},[104,15291,15292],{},"from sklearn.feature_selection import VarianceThreshold\n",[104,15294,15295],{"class":106,"line":58},[104,15296,15297],{},"X_transformed = VarianceThreshold(0.1).fit_transform(X_train)\n",[433,15299,15300],{},[11,15301,15302,15304,15305,15308],{},[15,15303,1632],{}," de 784 pixels pra 695 (89 descartados, provavelmente as bordas que quase nunca têm tinta). Acurácia depois do corte: 0.8449, ",[15,15306,15307],{},"pior"," que os 784 originais.",[11,15310,15311,15312,15315,15316,15318,15319,15321],{},"Vale registrar essa honestidade: mesmo um corte que parece óbvio (descartar pixel que quase nunca muda) piorou o resultado aqui, em vez de simplificar sem custo. ",[87,15313,15314],{},"VarianceThreshold"," só olha pra ",[87,15317,133],{},", nunca pro rótulo ",[87,15320,166],{},", então não tem como saber se aquele pouquinho de variância que ele descartou carregava sinal relevante pra separar os dígitos.",[79,15323,15325],{"id":15324},"a-régua-caseira-intra-classe-contra-inter-classe","A régua caseira: intra-classe contra inter-classe",[11,15327,15328,15329,15594],{},"O professor constrói do zero uma função de distância euclidiana vetorizada (a mesma ideia que dá pra derivar de ",[104,15330,15332,15392],{"className":15331},[148],[104,15333,15335],{"className":15334},[152],[154,15336,15337],{"xmlns":156},[158,15338,15339,15389],{},[161,15340,15341,15343,15345,15347,15349,15355,15357,15359,15361,15367,15369,15371,15373,15375,15377,15379,15381,15383],{},[164,15342,10192],{"mathvariant":959},[164,15344,20],{},[168,15346,530],{},[164,15348,184],{},[539,15350,15351,15353],{},[164,15352,10192],{"mathvariant":959},[483,15354,488],{},[168,15356,170],{},[164,15358,10192],{"mathvariant":959},[164,15360,20],{},[539,15362,15363,15365],{},[164,15364,10192],{"mathvariant":959},[483,15366,488],{},[168,15368,530],{},[483,15370,488],{},[164,15372,20],{},[168,15374,175],{},[164,15376,184],{},[168,15378,181],{},[164,15380,10192],{"mathvariant":959},[164,15382,184],{},[539,15384,15385,15387],{},[164,15386,10192],{"mathvariant":959},[483,15388,488],{},[186,15390,15391],{"encoding":188},"\\|a-b\\|^2 = \\|a\\|^2 - 2a\\cdot b + \\|b\\|^2",[104,15393,15395,15416,15463,15513,15534,15553],{"className":15394,"ariaHidden":194},[193],[104,15396,15398,15401,15404,15407,15410,15413],{"className":15397},[198],[104,15399],{"className":15400,"style":558},[202],[104,15402,10192],{"className":15403},[207],[104,15405,20],{"className":15406},[207,208],[104,15408],{"className":15409,"style":235},[213],[104,15411,530],{"className":15412},[239],[104,15414],{"className":15415,"style":235},[213],[104,15417,15419,15422,15425,15454,15457,15460],{"className":15418},[198],[104,15420],{"className":15421,"style":838},[202],[104,15423,184],{"className":15424},[207,208],[104,15426,15428,15431],{"className":15427},[207],[104,15429,10192],{"className":15430},[207],[104,15432,15434],{"className":15433},[697],[104,15435,15437],{"className":15436},[605],[104,15438,15440],{"className":15439},[610],[104,15441,15443],{"className":15442,"style":897},[614],[104,15444,15445,15448],{"style":900},[104,15446],{"className":15447,"style":714},[622],[104,15449,15451],{"className":15450},[627,628,629,630],[104,15452,488],{"className":15453},[207,630],[104,15455],{"className":15456,"style":214},[213],[104,15458,170],{"className":15459},[218],[104,15461],{"className":15462,"style":214},[213],[104,15464,15466,15469,15472,15475,15504,15507,15510],{"className":15465},[198],[104,15467],{"className":15468,"style":838},[202],[104,15470,10192],{"className":15471},[207],[104,15473,20],{"className":15474},[207,208],[104,15476,15478,15481],{"className":15477},[207],[104,15479,10192],{"className":15480},[207],[104,15482,15484],{"className":15483},[697],[104,15485,15487],{"className":15486},[605],[104,15488,15490],{"className":15489},[610],[104,15491,15493],{"className":15492,"style":897},[614],[104,15494,15495,15498],{"style":900},[104,15496],{"className":15497,"style":714},[622],[104,15499,15501],{"className":15500},[627,628,629,630],[104,15502,488],{"className":15503},[207,630],[104,15505],{"className":15506,"style":235},[213],[104,15508,530],{"className":15509},[239],[104,15511],{"className":15512,"style":235},[213],[104,15514,15516,15519,15522,15525,15528,15531],{"className":15515},[198],[104,15517],{"className":15518,"style":2975},[202],[104,15520,488],{"className":15521},[207],[104,15523,20],{"className":15524},[207,208],[104,15526],{"className":15527,"style":235},[213],[104,15529,175],{"className":15530},[239],[104,15532],{"className":15533,"style":235},[213],[104,15535,15537,15541,15544,15547,15550],{"className":15536},[198],[104,15538],{"className":15539,"style":15540},[202],"height:0.7778em;vertical-align:-0.0833em;",[104,15542,184],{"className":15543},[207,208],[104,15545],{"className":15546,"style":235},[213],[104,15548,181],{"className":15549},[239],[104,15551],{"className":15552,"style":235},[213],[104,15554,15556,15559,15562,15565],{"className":15555},[198],[104,15557],{"className":15558,"style":838},[202],[104,15560,10192],{"className":15561},[207],[104,15563,184],{"className":15564},[207,208],[104,15566,15568,15571],{"className":15567},[207],[104,15569,10192],{"className":15570},[207],[104,15572,15574],{"className":15573},[697],[104,15575,15577],{"className":15576},[605],[104,15578,15580],{"className":15579},[610],[104,15581,15583],{"className":15582,"style":897},[614],[104,15584,15585,15588],{"style":900},[104,15586],{"className":15587,"style":714},[622],[104,15589,15591],{"className":15590},[627,628,629,630],[104,15592,488],{"className":15593},[207,630],", evitando um laço por par de pontos) e usa ela pra definir uma régua de qualidade por variável:",[96,15596,15598],{"className":98,"code":15597,"language":100,"meta":57,"style":57},"def distance_score(X, y):\n    in_dist, out_dist = 0, 0\n    for label in np.unique(y):\n        in_class = X[y == label]\n        out_class = X[y != label]\n        in_dist += pairwise_distances(in_class, in_class).mean()\n        out_dist += pairwise_distances(in_class, out_class).mean()\n    return in_dist \u002F (out_dist + 1e-8)\n",[87,15599,15600,15605,15610,15615,15620,15625,15630,15635],{"__ignoreMap":57},[104,15601,15602],{"class":106,"line":107},[104,15603,15604],{},"def distance_score(X, y):\n",[104,15606,15607],{"class":106,"line":58},[104,15608,15609],{},"    in_dist, out_dist = 0, 0\n",[104,15611,15612],{"class":106,"line":118},[104,15613,15614],{},"    for label in np.unique(y):\n",[104,15616,15617],{"class":106,"line":124},[104,15618,15619],{},"        in_class = X[y == label]\n",[104,15621,15622],{"class":106,"line":308},[104,15623,15624],{},"        out_class = X[y != label]\n",[104,15626,15627],{"class":106,"line":417},[104,15628,15629],{},"        in_dist += pairwise_distances(in_class, in_class).mean()\n",[104,15631,15632],{"class":106,"line":422},[104,15633,15634],{},"        out_dist += pairwise_distances(in_class, out_class).mean()\n",[104,15636,15637],{"class":106,"line":428},[104,15638,15639],{},"    return in_dist \u002F (out_dist + 1e-8)\n",[11,15641,15642,15643,15645,15646,15649,15650,15653,15654,15657,15658,15661,15662,15665,15666,15669],{},"A ideia: pra cada variável, mede a distância média ",[15,15644,6540],{}," de cada classe (será que todos os \"3\" se parecem nessa variável?) e a distância média ",[15,15647,15648],{},"entre"," classes diferentes (será que um \"3\" e um \"7\" são bem diferentes nessa variável?). Uma variável boa tem distância intra-classe ",[15,15651,15652],{},"baixa"," e distância inter-classe ",[15,15655,15656],{},"alta",", então a razão ",[87,15659,15660],{},"intra\u002Finter"," fica pequena. Isso é praticamente a mesma ideia do ",[15,15663,15664],{},"discriminante de Fisher"," que o Bishop descreve (capítulo 4.1.4, ",[20,15667,15668],{"href":5657},"que eu já citei lá no post de classificação"," de outro jeito): separação entre classes dividida pela variação dentro de cada classe, só que aqui aplicada variável por variável, em vez de numa direção de projeção aprendida.",[79,15671,15673],{"id":15672},"o-custo-de-calcular-isso-em-tudo","O custo de calcular isso em tudo",[11,15675,15676],{},"Calcular distância par a par em 60 mil pontos é caro (o custo cresce com o quadrado da quantidade de pontos), então o professor reduz pra uma amostra de 1200 exemplos de treino antes de rankear:",[433,15678,15679],{},[11,15680,15681,15683],{},[15,15682,1632],{}," rankear as 784 variáveis nessa amostra menor levou cerca de 20 segundos. Mesmo assim, bem mais rápido que tentar isso nos 60 mil pontos inteiros.",[11,15685,15686],{},"Um lembrete direto de que \"quanto custa calcular\" também é parte do design de uma técnica, não só \"o que ela mede\".",[79,15688,15690],{"id":15689},"selecionando-de-verdade","Selecionando de verdade",[96,15692,15694],{"className":98,"code":15693,"language":100,"meta":57,"style":57},"class UnivariatedRanking(BaseEstimator, TransformerMixin):\n    def __init__(self, n_features):\n        self.n_features = n_features\n    def fit(self, X, y=None):\n        self.scores_ = univariate_ranking(X, y)\n        return self\n    def transform(self, X, y=None):\n        X_sorted = X[:, self.scores_.argsort()]\n        return X_sorted[:, :self.n_features]\n",[87,15695,15696,15701,15706,15711,15715,15720,15724,15728,15733],{"__ignoreMap":57},[104,15697,15698],{"class":106,"line":107},[104,15699,15700],{},"class UnivariatedRanking(BaseEstimator, TransformerMixin):\n",[104,15702,15703],{"class":106,"line":58},[104,15704,15705],{},"    def __init__(self, n_features):\n",[104,15707,15708],{"class":106,"line":118},[104,15709,15710],{},"        self.n_features = n_features\n",[104,15712,15713],{"class":106,"line":124},[104,15714,9498],{},[104,15716,15717],{"class":106,"line":308},[104,15718,15719],{},"        self.scores_ = univariate_ranking(X, y)\n",[104,15721,15722],{"class":106,"line":417},[104,15723,1482],{},[104,15725,15726],{"class":106,"line":422},[104,15727,9507],{},[104,15729,15730],{"class":106,"line":428},[104,15731,15732],{},"        X_sorted = X[:, self.scores_.argsort()]\n",[104,15734,15735],{"class":106,"line":1692},[104,15736,15737],{},"        return X_sorted[:, :self.n_features]\n",[11,15739,15740,15741,15744],{},"Com só 1200 exemplos de treino (bem menos que os 60 mil de antes, pra caber no orçamento de tempo), o baseline com todas as 784 variáveis já cai bastante, pra 0.7678, o preço de treinar com menos dado. Selecionando só as ",[15,15742,15743],{},"180 melhores"," variáveis (23% do total):",[433,15746,15747],{},[11,15748,15749,15751],{},[15,15750,1632],{}," 0.7681. Praticamente empatado com usar tudo, só que com menos de um quarto das variáveis.",[11,15753,15754,15755,15758],{},"Essa é a diferença de fundo pro PCA: aqui o rótulo participa da escolha desde o início, então a técnica não corre o risco de descartar exatamente a informação que separa as classes, ",[20,15756,15757],{"href":15216},"o mesmo risco que derrubou a acurácia de 100% pra 47% naquele dataset artificial do post de PCA",". Seleção supervisionada de variáveis e redução não supervisionada de dimensão resolvem problemas parecidos, mas com garantias bem diferentes.",[79,15760,15762],{"id":15761},"automatizando-o-quantas-variáveis-manter","Automatizando o \"quantas variáveis manter\"",[11,15764,15765,15766,15769],{},"Faltava decidir 180 de algum jeito, e o professor resolve isso também: ",[87,15767,15768],{},"HybridRanking"," usa o ranking univariado pra ordenar as variáveis, depois testa incrementalmente (1 variável, 2 variáveis, 3...) num conjunto de validação separado, guardando o tamanho que deu a melhor acurácia:",[96,15771,15773],{"className":98,"code":15772,"language":100,"meta":57,"style":57},"for i in range(1, len(score_idxs)):\n    X_selected = X_tr[:, score_idxs[0:i]]\n    self.estimator.fit(X_selected, y_tr)\n    acc = self.estimator.score(X_val[:, score_idxs[0:i]], y_val)\n    if acc > best_score:\n        best_score, best_set = acc, i\n",[87,15774,15775,15780,15785,15790,15795,15800],{"__ignoreMap":57},[104,15776,15777],{"class":106,"line":107},[104,15778,15779],{},"for i in range(1, len(score_idxs)):\n",[104,15781,15782],{"class":106,"line":58},[104,15783,15784],{},"    X_selected = X_tr[:, score_idxs[0:i]]\n",[104,15786,15787],{"class":106,"line":118},[104,15788,15789],{},"    self.estimator.fit(X_selected, y_tr)\n",[104,15791,15792],{"class":106,"line":124},[104,15793,15794],{},"    acc = self.estimator.score(X_val[:, score_idxs[0:i]], y_val)\n",[104,15796,15797],{"class":106,"line":308},[104,15798,15799],{},"    if acc > best_score:\n",[104,15801,15802],{"class":106,"line":417},[104,15803,15804],{},"        best_score, best_set = acc, i\n",[433,15806,15807],{},[11,15808,15809,15811,15812,15815],{},[15,15810,1632],{}," o algoritmo escolheu sozinho ",[15,15813,15814],{},"199"," variáveis, com acurácia 0.7702551020408164, exatamente igual, até a última casa decimal, ao resultado de escolher manualmente as 199 melhores variáveis do ranking.",[11,15817,15818,15819,15822,15823,15886],{},"A combinação (ranking rápido primeiro, depois testar tamanhos incrementalmente) é um padrão clássico de seleção de variáveis: um filtro barato (",[87,15820,15821],{},"distance_score",", calcula uma vez, ordena) reduz o espaço de busca, e um método mais caro (treinar e validar de verdade) decide o ponto de corte, sem precisar treinar um modelo pra cada uma das ",[104,15824,15826,15845],{"className":15825},[148],[104,15827,15829],{"className":15828},[152],[154,15830,15831],{"xmlns":156},[158,15832,15833,15842],{},[161,15834,15835],{},[539,15836,15837,15839],{},[483,15838,488],{},[483,15840,15841],{},"784",[186,15843,15844],{"encoding":188},"2^{784}",[104,15846,15848],{"className":15847,"ariaHidden":194},[193],[104,15849,15851,15854],{"className":15850},[198],[104,15852],{"className":15853,"style":897},[202],[104,15855,15857,15860],{"className":15856},[207],[104,15858,488],{"className":15859},[207],[104,15861,15863],{"className":15862},[697],[104,15864,15866],{"className":15865},[605],[104,15867,15869],{"className":15868},[610],[104,15870,15872],{"className":15871,"style":897},[614],[104,15873,15874,15877],{"style":900},[104,15875],{"className":15876,"style":714},[622],[104,15878,15880],{"className":15879},[627,628,629,630],[104,15881,15883],{"className":15882},[207,630],[104,15884,15841],{"className":15885},[207,630]," combinações possíveis de variáveis.",[79,15888,1873],{"id":1872},[1875,15890,15891,15899],{},[1878,15892,15893],{},[1881,15894,15895,15897],{},[1884,15896,1887],{"align":1886},[1884,15898,1890],{"align":1886},[1892,15900,15901,15909,15920],{},[1881,15902,15903,15906],{},[1897,15904,15905],{"align":1886},"PCA reduz dimensão maximizando variância",[1897,15907,15908],{"align":1886},"Seleção de variáveis pode reduzir dimensão usando o rótulo, evitando o risco de descartar o que realmente importa pra classificar",[1881,15910,15911,15914],{},[1897,15912,15913],{"align":1886},"Descartar variável \"óbvia\" sempre ajuda",[1897,15915,15916,15917,15919],{"align":1886},"Nem sempre: ",[87,15918,15314],{}," piorou o resultado aqui, porque variância baixa não é sinônimo de inútil",[1881,15921,15922,15925],{},[1897,15923,15924],{"align":1886},"Fisher mede separação entre classes sobre variação dentro delas",[1897,15926,15927],{"align":1886},"A mesma ideia rende uma régua simples pra rankear variáveis individualmente, não só pra achar uma direção de projeção",[79,15929,1943],{"id":1942},[11,15931,15932,15933,15935,15936,15939],{},"Reproduzi a mesma régua (",[87,15934,15821],{},", ranking univariado) num dataset de dígitos menor, ",[20,15937,15938],{"href":11499},"o mesmo Digits (1797 imagens, 8×8 pixels) que já apareceu no post de K-means",", rápido o bastante pra rodar nos 64 pixels inteiros sem precisar reduzir a amostra.",[96,15941,15943],{"className":98,"code":15942,"language":100,"meta":57,"style":57},"scores = univariate_ranking(X_train, y_train)\nsorted_idxs = np.argsort(scores)\nfor n in [10, 20, 32]:\n    model = RidgeClassifier().fit(X_train[:, sorted_idxs[:n]], y_train)\n    acc = accuracy_score(y_test, model.predict(X_test[:, sorted_idxs[:n]]))\n",[87,15944,15945,15950,15955,15960,15965],{"__ignoreMap":57},[104,15946,15947],{"class":106,"line":107},[104,15948,15949],{},"scores = univariate_ranking(X_train, y_train)\n",[104,15951,15952],{"class":106,"line":58},[104,15953,15954],{},"sorted_idxs = np.argsort(scores)\n",[104,15956,15957],{"class":106,"line":118},[104,15958,15959],{},"for n in [10, 20, 32]:\n",[104,15961,15962],{"class":106,"line":124},[104,15963,15964],{},"    model = RidgeClassifier().fit(X_train[:, sorted_idxs[:n]], y_train)\n",[104,15966,15967],{"class":106,"line":308},[104,15968,15969],{},"    acc = accuracy_score(y_test, model.predict(X_test[:, sorted_idxs[:n]]))\n",[1875,15971,15972,15984],{},[1878,15973,15974],{},[1881,15975,15976,15979,15982],{},[1884,15977,15978],{"align":5597},"Variáveis usadas",[1884,15980,15981],{"align":5597},"Fração do total",[1884,15983,12004],{"align":3190},[1892,15985,15986,15997,16010,16021],{},[1881,15987,15988,15991,15994],{},[1897,15989,15990],{"align":5597},"64 (todas)",[1897,15992,15993],{"align":5597},"100%",[1897,15995,15996],{"align":3190},"0.9389",[1881,15998,15999,16002,16005],{},[1897,16000,16001],{"align":5597},"32 (melhores)",[1897,16003,16004],{"align":5597},"50%",[1897,16006,16007],{"align":3190},[15,16008,16009],{},"0.9278",[1881,16011,16012,16015,16018],{},[1897,16013,16014],{"align":5597},"20 (melhores)",[1897,16016,16017],{"align":5597},"31%",[1897,16019,16020],{"align":3190},"0.8806",[1881,16022,16023,16026,16029],{},[1897,16024,16025],{"align":5597},"10 (melhores)",[1897,16027,16028],{"align":5597},"16%",[1897,16030,16031],{"align":3190},"0.7722",[11,16033,16034],{},"Com metade das variáveis (32 de 64), a acurácia cai só um ponto percentual (0.9389 para 0.9278). A curva não é linear: cortar de 64 pra 32 quase não dói, mas cortar de 32 pra 20 e depois pra 10 começa a doer rápido, sinal de que boa parte do sinal útil realmente está concentrada numa fração das variáveis, exatamente a suposição que torna seleção de características uma técnica que vale a pena.",[1985,16036,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":16038},[16039,16040,16041,16042,16043,16044,16045,16046],{"id":15221,"depth":58,"text":15222},{"id":15281,"depth":58,"text":15282},{"id":15324,"depth":58,"text":15325},{"id":15672,"depth":58,"text":15673},{"id":15689,"depth":58,"text":15690},{"id":15761,"depth":58,"text":15762},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 13: o professor constrói do zero uma régua pra rankear quais pixels do MNIST realmente ajudam a distinguir um dígito do outro, e mostra que descartar 77% das variáveis quase não custa acurácia.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Ffeature-selection",{"title":15208,"description":16047},"pt\u002Fplaylists\u002Fpattern-recognition\u002Ffeature-selection",[16053,16054,16055],"selecao-de-caracteristicas","mnist","fisher","2EOT2DU5iW71t3OJ5T1muZprnbIG1Atm2mQ9clY0FR8",{"id":16058,"title":16059,"body":16060,"cover":3,"date":9189,"description":16789,"extension":61,"meta":16790,"navigation":63,"order":1727,"path":16791,"playlist":2003,"seo":16792,"status":66,"stem":16793,"tags":16794,"__hash__":16798},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro.md","Texto Vira Vetor: Meus Primeiros Passos em NLP",{"type":8,"value":16061,"toc":16779},[16062,16079,16083,16086,16129,16138,16145,16149,16241,16248,16262,16274,16278,16287,16302,16314,16318,16321,16404,16411,16426,16433,16437,16445,16517,16538,16547,16554,16560,16565,16569,16572,16613,16625,16627,16668,16675,16677,16695,16720,16737,16774,16777],[11,16063,16064,16065,16068,16069,16073,16074,16078],{},"Aula 14. A última da matéria, e o assunto fecha a playlist inteira com uma virada bacana: todo modelo que eu usei até aqui, ",[20,16066,16067],{"href":2020},"da regressão linear lá no primeiro post"," até a ",[20,16070,16072],{"href":16071},"\u002Fplaylists\u002Fpattern-recognition\u002Ffeature-selection","seleção de características no post anterior",", espera um vetor de número como entrada. Pixel já é número. Medida química de vinho já é número. Mas texto, uma resenha de filme escrita por gente de verdade, não é número nenhum. Essa aula é sobre o primeiro passo de qualquer ",[445,16075,16077],{"definition":16076},"Processamento de Linguagem Natural, o campo de ML que lida com texto: das buscas do Google até o corretor do teclado do celular","NLP"," (Natural Language Processing): como transformar texto em vetor sem perder o que importa.",[79,16080,16082],{"id":16081},"padronizar-e-tokenizar-o-mínimo-necessário","Padronizar e tokenizar: o mínimo necessário",[11,16084,16085],{},"Antes de qualquer conta, o professor limpa o texto: tudo minúsculo, pontuação fora.",[96,16087,16089],{"className":98,"code":16088,"language":100,"meta":57,"style":57},"def standardize(text):\n    text = text.lower()\n    return \"\".join(c for c in text if c not in string.punctuation)\n\ndef tokenize(text):\n    return standardize(text).split()\n\ntokenize(\"I write, erase, rewrite, erase again, and then a poppy blooms!\")\n",[87,16090,16091,16096,16101,16106,16110,16115,16120,16124],{"__ignoreMap":57},[104,16092,16093],{"class":106,"line":107},[104,16094,16095],{},"def standardize(text):\n",[104,16097,16098],{"class":106,"line":58},[104,16099,16100],{},"    text = text.lower()\n",[104,16102,16103],{"class":106,"line":118},[104,16104,16105],{},"    return \"\".join(c for c in text if c not in string.punctuation)\n",[104,16107,16108],{"class":106,"line":124},[104,16109,300],{"emptyLinePlaceholder":63},[104,16111,16112],{"class":106,"line":308},[104,16113,16114],{},"def tokenize(text):\n",[104,16116,16117],{"class":106,"line":417},[104,16118,16119],{},"    return standardize(text).split()\n",[104,16121,16122],{"class":106,"line":422},[104,16123,300],{"emptyLinePlaceholder":63},[104,16125,16126],{"class":106,"line":428},[104,16127,16128],{},"tokenize(\"I write, erase, rewrite, erase again, and then a poppy blooms!\")\n",[433,16130,16131],{},[11,16132,16133,134,16135,1866],{},[15,16134,1632],{},[87,16136,16137],{},"['i', 'write', 'erase', 'rewrite', 'erase', 'again', 'and', 'then', 'a', 'poppy', 'blooms']",[11,16139,16140,16141,16144],{},"Repara que \"Erase\" e \"erase\" agora são a mesma palavra, e a vírgula depois de \"erase\" sumiu sem grudar na palavra seguinte. Sem esse passo, o modelo trataria \"erase\" e \"erase,\" (com vírgula colada) como duas palavras completamente diferentes, e \"Write\" e \"write\" também, inflando o vocabulário com duplicata que não deveria existir. É o mesmo espírito da normalização que já vi ",[20,16142,16143],{"href":5820},"lá no post de KNN",", só que em vez de reescalar número, aqui é reescalar texto pra uma forma canônica.",[79,16146,16148],{"id":16147},"o-vetorizador-caseiro-palavra-vira-índice","O vetorizador caseiro: palavra vira índice",[96,16150,16152],{"className":98,"code":16151,"language":100,"meta":57,"style":57},"class Vectorizer:\n    def standardize(self, text):\n        ...\n    def tokenize(self, text):\n        ...\n    def make_vocabolary(self, dataset):\n        self.vocabulary = {\"\": 0, \"[UNK]\": 1}\n        for text in dataset:\n            text = self.standardize(text)\n            tokens = self.tokenize(text)\n            for token in tokens:\n                if token not in self.vocabulary:\n                    self.vocabulary[token] = len(self.vocabulary)\n\n    def encode(self, text):\n        text = self.standardize(text)\n        tokens = self.tokenize(text)\n        return [self.vocabulary.get(token, 1) for token in tokens]\n",[87,16153,16154,16159,16164,16168,16173,16177,16182,16187,16192,16197,16202,16207,16212,16217,16221,16226,16231,16236],{"__ignoreMap":57},[104,16155,16156],{"class":106,"line":107},[104,16157,16158],{},"class Vectorizer:\n",[104,16160,16161],{"class":106,"line":58},[104,16162,16163],{},"    def standardize(self, text):\n",[104,16165,16166],{"class":106,"line":118},[104,16167,7972],{},[104,16169,16170],{"class":106,"line":124},[104,16171,16172],{},"    def tokenize(self, text):\n",[104,16174,16175],{"class":106,"line":308},[104,16176,7972],{},[104,16178,16179],{"class":106,"line":417},[104,16180,16181],{},"    def make_vocabolary(self, dataset):\n",[104,16183,16184],{"class":106,"line":422},[104,16185,16186],{},"        self.vocabulary = {\"\": 0, \"[UNK]\": 1}\n",[104,16188,16189],{"class":106,"line":428},[104,16190,16191],{},"        for text in dataset:\n",[104,16193,16194],{"class":106,"line":1692},[104,16195,16196],{},"            text = self.standardize(text)\n",[104,16198,16199],{"class":106,"line":1698},[104,16200,16201],{},"            tokens = self.tokenize(text)\n",[104,16203,16204],{"class":106,"line":1704},[104,16205,16206],{},"            for token in tokens:\n",[104,16208,16209],{"class":106,"line":1710},[104,16210,16211],{},"                if token not in self.vocabulary:\n",[104,16213,16214],{"class":106,"line":1716},[104,16215,16216],{},"                    self.vocabulary[token] = len(self.vocabulary)\n",[104,16218,16219],{"class":106,"line":1722},[104,16220,300],{"emptyLinePlaceholder":63},[104,16222,16223],{"class":106,"line":1727},[104,16224,16225],{},"    def encode(self, text):\n",[104,16227,16228],{"class":106,"line":1732},[104,16229,16230],{},"        text = self.standardize(text)\n",[104,16232,16233],{"class":106,"line":1737},[104,16234,16235],{},"        tokens = self.tokenize(text)\n",[104,16237,16238],{"class":106,"line":10067},[104,16239,16240],{},"        return [self.vocabulary.get(token, 1) for token in tokens]\n",[11,16242,16243,16244,16247],{},"A ideia é literal: cada palavra vista no treino ganha um número inteiro, um índice num dicionário. \"",[104,16245,16246],{},"UNK","\" (unknown) fica reservado no índice 1 desde o início, pra qualquer palavra nova que apareça depois, numa frase de teste, e não estava no vocabulário de treino. O professor testa isso com uma frase que usa a palavra \"still\", ausente do dataset de treino de 3 frases:",[433,16249,16250],{},[11,16251,16252,16254,16255,16257,16258,16261],{},[15,16253,1632],{}," a palavra \"still\" vira o índice ",[87,16256,485],{},", o mesmo de ",[87,16259,16260],{},"[UNK]",", enquanto as outras palavras da frase (que já apareciam no treino) viram seus índices de verdade.",[11,16263,16264,16265,16269,16270,16273],{},"Isso é o mesmo problema que categoria nova resolvia ",[20,16266,16268],{"href":16267},"\u002Fplaylists\u002Fpattern-recognition\u002Ftitanic","lá no post do Titanic"," com o ",[87,16271,16272],{},"OneHotEncoder",": o que fazer quando o dado de produção traz algo que o treino nunca viu. Lá era categoria de embarque, aqui é palavra.",[79,16275,16277],{"id":16276},"o-dataset-50-mil-resenhas-de-filme-de-verdade","O dataset: 50 mil resenhas de filme, de verdade",[11,16279,16280,16281,16286],{},"O professor baixa o ",[20,16282,16285],{"href":16283,"rel":16284},"https:\u002F\u002Fai.stanford.edu\u002F~amaas\u002Fdata\u002Fsentiment\u002F",[45],"IMDB Large Movie Review Dataset",", da Stanford, um dos datasets mais citados da área de análise de sentimento: 50 mil resenhas reais do site IMDB, metade marcada como positiva, metade como negativa, já divididas 25 mil treino \u002F 25 mil teste.",[96,16288,16290],{"className":98,"code":16289,"language":100,"meta":57,"style":57},"train_df = text_dataset_from_directory('aclImdb\u002Ftrain')\ntest_df = text_dataset_from_directory('aclImdb\u002Ftest')\n",[87,16291,16292,16297],{"__ignoreMap":57},[104,16293,16294],{"class":106,"line":107},[104,16295,16296],{},"train_df = text_dataset_from_directory('aclImdb\u002Ftrain')\n",[104,16298,16299],{"class":106,"line":58},[104,16300,16301],{},"test_df = text_dataset_from_directory('aclImdb\u002Ftest')\n",[433,16303,16304],{},[11,16305,16306,16308,16309,1502,16311,1866],{},[15,16307,1632],{}," 25000 linhas de treino, 25000 de teste, colunas ",[87,16310,1009],{},[87,16312,16313],{},"label",[79,16315,16317],{"id":16316},"bag-of-words-cada-palavra-é-um-voto-sem-ordem","Bag-of-words: cada palavra é um voto, sem ordem",[11,16319,16320],{},"A primeira estratégia pra vetorizar uma resenha inteira: um vetor do tamanho do vocabulário, com 1 em cada posição cuja palavra aparece na resenha, 0 no resto. Ignora completamente a ordem das palavras, só marca presença.",[96,16322,16324],{"className":98,"code":16323,"language":100,"meta":57,"style":57},"class UnigramTransformer(BaseEstimator, TransformerMixin):\n    def fit(self, X, y=None):\n        self.vocabulary = {\"\": 0, \"[UNK]\": 1}\n        for text in X:\n            for token in set(self.tokenize(text)):\n                if token not in self.vocabulary and len(self.vocabulary) \u003C self.max_features:\n                    self.vocabulary[token] = len(self.vocabulary)\n        return self\n\n    def transform(self, X, y=None):\n        rows, cols, data = [], [], []\n        for row, text in enumerate(X):\n            for token in set(self.tokenize(text)):\n                rows.append(row)\n                cols.append(self.vocabulary.get(token, 1))\n                data.append(1)\n        return csr_matrix((data, (rows, cols)), shape=(len(X), len(self.vocabulary)))\n",[87,16325,16326,16331,16335,16339,16344,16349,16354,16358,16362,16366,16370,16375,16380,16384,16389,16394,16399],{"__ignoreMap":57},[104,16327,16328],{"class":106,"line":107},[104,16329,16330],{},"class UnigramTransformer(BaseEstimator, TransformerMixin):\n",[104,16332,16333],{"class":106,"line":58},[104,16334,9498],{},[104,16336,16337],{"class":106,"line":118},[104,16338,16186],{},[104,16340,16341],{"class":106,"line":124},[104,16342,16343],{},"        for text in X:\n",[104,16345,16346],{"class":106,"line":308},[104,16347,16348],{},"            for token in set(self.tokenize(text)):\n",[104,16350,16351],{"class":106,"line":417},[104,16352,16353],{},"                if token not in self.vocabulary and len(self.vocabulary) \u003C self.max_features:\n",[104,16355,16356],{"class":106,"line":422},[104,16357,16216],{},[104,16359,16360],{"class":106,"line":428},[104,16361,1482],{},[104,16363,16364],{"class":106,"line":1692},[104,16365,300],{"emptyLinePlaceholder":63},[104,16367,16368],{"class":106,"line":1698},[104,16369,9507],{},[104,16371,16372],{"class":106,"line":1704},[104,16373,16374],{},"        rows, cols, data = [], [], []\n",[104,16376,16377],{"class":106,"line":1710},[104,16378,16379],{},"        for row, text in enumerate(X):\n",[104,16381,16382],{"class":106,"line":1716},[104,16383,16348],{},[104,16385,16386],{"class":106,"line":1722},[104,16387,16388],{},"                rows.append(row)\n",[104,16390,16391],{"class":106,"line":1727},[104,16392,16393],{},"                cols.append(self.vocabulary.get(token, 1))\n",[104,16395,16396],{"class":106,"line":1732},[104,16397,16398],{},"                data.append(1)\n",[104,16400,16401],{"class":106,"line":1737},[104,16402,16403],{},"        return csr_matrix((data, (rows, cols)), shape=(len(X), len(self.vocabulary)))\n",[11,16405,16406,16407,16410],{},"Repara no ",[87,16408,16409],{},"csr_matrix",": com um vocabulário de 10 mil palavras e cada resenha usando só umas poucas centenas delas, a matriz é quase toda zero. Guardar um vetor denso de 10 mil posições, quase todas zero, pra cada uma das 20 mil resenhas seria um desperdício gigante de memória. O formato esparso guarda só as posições com valor diferente de zero.",[96,16412,16414],{"className":98,"code":16413,"language":100,"meta":57,"style":57},"pipeline = Pipeline([(\"vectorizer\", UnigramTransformer(10000)), (\"classifier\", RandomForestClassifier(random_state=42))])\npipeline.fit(train_texts, train_labels)\n",[87,16415,16416,16421],{"__ignoreMap":57},[104,16417,16418],{"class":106,"line":107},[104,16419,16420],{},"pipeline = Pipeline([(\"vectorizer\", UnigramTransformer(10000)), (\"classifier\", RandomForestClassifier(random_state=42))])\n",[104,16422,16423],{"class":106,"line":58},[104,16424,16425],{},"pipeline.fit(train_texts, train_labels)\n",[433,16427,16428],{},[11,16429,16430,16432],{},[15,16431,1632],{}," 0.832 de acurácia na validação, só olhando pra quais palavras aparecem, sem ordem nenhuma.",[79,16434,16436],{"id":16435},"tf-idf-nem-toda-palavra-pesa-igual","TF-IDF: nem toda palavra pesa igual",[11,16438,16439,16440,16444],{},"Bag-of-words trata \"the\" (aparece em quase toda resenha) e \"wonderful\" (aparece só nas boas) do mesmo jeito: os dois contam 1 se aparecem. Mas \"the\" não carrega informação nenhuma sobre a resenha ser boa ou ruim, já \"wonderful\" carrega bastante. O ",[445,16441,16443],{"definition":16442},"Term Frequency times Inverse Document Frequency: pondera cada palavra pela frequência dentro do texto, multiplicada pelo quão rara ela é no restante do corpus","TF-IDF"," resolve isso com duas contas multiplicadas:",[96,16446,16448],{"className":98,"code":16447,"language":100,"meta":57,"style":57},"class TfidfTransformer(BaseEstimator, TransformerMixin):\n    def fit(self, X, y=None):\n        doc_freq = {}\n        for text in X:\n            for token in set(self.tokenize(text)):\n                doc_freq[token] = doc_freq.get(token, 0) + 1\n        ...\n        self.idf = {token: math.log(len(X) \u002F freq) for token, freq in doc_freq.items()}\n        return self\n\n    def transform(self, X, y=None):\n        ...\n        for token, count in token_counts.items():\n            tf = count \u002F total_tokens_no_documento\n            value = tf * self.idf[token]\n",[87,16449,16450,16455,16459,16464,16468,16472,16477,16481,16486,16490,16494,16498,16502,16507,16512],{"__ignoreMap":57},[104,16451,16452],{"class":106,"line":107},[104,16453,16454],{},"class TfidfTransformer(BaseEstimator, TransformerMixin):\n",[104,16456,16457],{"class":106,"line":58},[104,16458,9498],{},[104,16460,16461],{"class":106,"line":118},[104,16462,16463],{},"        doc_freq = {}\n",[104,16465,16466],{"class":106,"line":124},[104,16467,16343],{},[104,16469,16470],{"class":106,"line":308},[104,16471,16348],{},[104,16473,16474],{"class":106,"line":417},[104,16475,16476],{},"                doc_freq[token] = doc_freq.get(token, 0) + 1\n",[104,16478,16479],{"class":106,"line":422},[104,16480,7972],{},[104,16482,16483],{"class":106,"line":428},[104,16484,16485],{},"        self.idf = {token: math.log(len(X) \u002F freq) for token, freq in doc_freq.items()}\n",[104,16487,16488],{"class":106,"line":1692},[104,16489,1482],{},[104,16491,16492],{"class":106,"line":1698},[104,16493,300],{"emptyLinePlaceholder":63},[104,16495,16496],{"class":106,"line":1704},[104,16497,9507],{},[104,16499,16500],{"class":106,"line":1710},[104,16501,7972],{},[104,16503,16504],{"class":106,"line":1716},[104,16505,16506],{},"        for token, count in token_counts.items():\n",[104,16508,16509],{"class":106,"line":1722},[104,16510,16511],{},"            tf = count \u002F total_tokens_no_documento\n",[104,16513,16514],{"class":106,"line":1727},[104,16515,16516],{},"            value = tf * self.idf[token]\n",[11,16518,16519,16522,16523,16526,16527,16530,16531,16534,16535,16537],{},[15,16520,16521],{},"TF"," (term frequency) é simples: quantas vezes a palavra aparece na resenha, dividido pelo total de palavras dela, o peso \"local\". ",[15,16524,16525],{},"IDF"," (inverse document frequency) é o peso \"global\": ",[87,16528,16529],{},"log(total de documentos \u002F quantos documentos contêm a palavra)",". Palavra que aparece em quase todo documento (tipo \"the\") tem IDF perto de zero, quase não pesa nada. Já palavra rara, presente em poucos documentos, tem IDF alto. Multiplicando os dois, uma palavra só ganha peso alto se for frequente ",[15,16532,16533],{},"naquela"," resenha específica ",[15,16536,8775],{}," rara no restante do corpus, exatamente o tipo de palavra que ajuda a diferenciar uma resenha da outra.",[96,16539,16541],{"className":98,"code":16540,"language":100,"meta":57,"style":57},"pipeline = Pipeline([(\"vectorizer\", TfidfTransformer(10000)), (\"classifier\", RandomForestClassifier(random_state=42))])\n",[87,16542,16543],{"__ignoreMap":57},[104,16544,16545],{"class":106,"line":107},[104,16546,16540],{},[433,16548,16549],{},[11,16550,16551,16553],{},[15,16552,1632],{}," 0.8316 de acurácia na validação, praticamente empatado com bag-of-words (0.832).",[11,16555,16556,16557,16559],{},"Interativo: digita uma frase abaixo (em inglês, o mesmo idioma do dataset) e vê o peso TF-IDF de cada palavra, calculado contra um corpuzinho de 16 resenhas curtas de exemplo. Palavra comum tipo \"the\" ou \"was\" fica com barra curta, palavra rara e carregada tipo \"wonderful\", \"terrible\" ou \"boring\" fica com barra bem mais alta, e palavra que esse corpo de exemplo nunca viu aparece marcada como ",[87,16558,16260],{},", sem peso, o mesmo problema do vetorizador caseiro lá em cima.",[16561,16562],"text-vectorizer-explorer",{"label":16563,"readout-suffix":16564,"unk-label":16246},"Digite uma frase (em inglês, mesmo vocabulário do corpus de exemplo)","token(s) reconhecidos no vocabulário de exemplo",[79,16566,16568],{"id":16567},"no-dataset-inteiro-e-contra-o-tfidfvectorizer-oficial","No dataset inteiro, e contra o TfidfVectorizer oficial",[11,16570,16571],{},"Com as duas técnicas testadas na validação, o professor treina de novo em cima do treino completo (25 mil resenhas, não só a fatia de 20 mil) e mede no teste real:",[1875,16573,16574,16582],{},[1878,16575,16576],{},[1881,16577,16578,16580],{},[1884,16579,14736],{"align":1886},[1884,16581,12004],{"align":3190},[1892,16583,16584,16592,16602],{},[1881,16585,16586,16589],{},[1897,16587,16588],{"align":1886},"Bag-of-words (unigram)",[1897,16590,16591],{"align":3190},"0.83804",[1881,16593,16594,16597],{},[1897,16595,16596],{"align":1886},"TF-IDF caseiro",[1897,16598,16599],{"align":3190},[15,16600,16601],{},"0.84292",[1881,16603,16604,16610],{},[1897,16605,16606,16609],{"align":1886},[87,16607,16608],{},"TfidfVectorizer"," do scikit-learn",[1897,16611,16612],{"align":3190},"0.83872",[11,16614,16615,16616,1502,16619,14912,16622,16624],{},"TF-IDF ganha por uma margem pequena do bag-of-words, e o mais importante: minha implementação caseira de TF-IDF (0.84292) fica a menos de meio ponto percentual da implementação oficial do scikit-learn (0.83872), a mesma régua de \"bate com o profissional, na casa decimal\" que já vi ",[20,16617,16618],{"href":1929},"lá na equação normal",[20,16620,16621],{"href":15216},"no PCA",[87,16623,16608],{}," de verdade tem otimizações e detalhes a mais (normalização L2 do vetor final, por exemplo), mas a ideia central, a mesma conta de TF vezes IDF, é idêntica.",[79,16626,1873],{"id":1872},[1875,16628,16629,16637],{},[1878,16630,16631],{},[1881,16632,16633,16635],{},[1884,16634,1887],{"align":1886},[1884,16636,1890],{"align":1886},[1892,16638,16639,16647,16657],{},[1881,16640,16641,16644],{},[1897,16642,16643],{"align":1886},"Todo modelo espera um vetor de número",[1897,16645,16646],{"align":1886},"Texto também precisa virar vetor, e o jeito de fazer isso é uma escolha de design, não um detalhe automático",[1881,16648,16649,16652],{},[1897,16650,16651],{"align":1886},"Categoria nova em produção precisa de um plano",[1897,16653,16654,16656],{"align":1886},[87,16655,16260],{}," resolve pra palavra o mesmo problema que categoria desconhecida resolvia pro Titanic",[1881,16658,16659,16662],{},[1897,16660,16661],{"align":1886},"Bater com o scikit-learn valida a implementação caseira",[1897,16663,16664,16665,16667],{"align":1886},"TF-IDF montado na mão (0.84292) chega perto o bastante do ",[87,16666,16608],{}," oficial (0.83872) pra confirmar que a lógica está certa",[11,16669,16670,16671,16674],{},"E com isso fecho as 14 aulas dessa matéria. Comecei ",[20,16672,16673],{"href":2020},"ajustando uma reta com equação normal"," e termino aqui, transformando resenha de filme em vetor de palavra pesada. No meio do caminho teve árvore, ensemble, cluster, redução de dimensão, dado desbalanceado, e o Bishop segurando a ponta teórica de quase tudo isso, menos das últimas três aulas (DBSCAN, semi-supervisionado e NLP), que vieram depois do livro dele de 2006 e mesmo assim encaixaram direitinho na mesma lógica de sempre: dado vira vetor, vetor vira decisão. Valeu, professor Boldt.",[79,16676,1943],{"id":1942},[11,16678,16679,16680,16682,16683,16686,16687,16690,16691,16694],{},"Pra confirmar o achado da aula (bag-of-words, TF-IDF caseiro e ",[87,16681,16608],{}," oficial dando resultado parecido) num dataset diferente do IMDB, usei o ",[87,16684,16685],{},"20newsgroups"," do próprio scikit-learn: posts de fórum reais, sobre dois assuntos bem distintos, ",[87,16688,16689],{},"sci.space"," (espaço) e ",[87,16692,16693],{},"rec.sport.baseball"," (beisebol), 1190 posts de treino e 791 de teste.",[96,16696,16698],{"className":98,"code":16697,"language":100,"meta":57,"style":57},"from sklearn.datasets import fetch_20newsgroups\ncats = ['sci.space', 'rec.sport.baseball']\ntrain = fetch_20newsgroups(subset='train', categories=cats, remove=('headers','footers','quotes'), random_state=42)\ntest = fetch_20newsgroups(subset='test', categories=cats, remove=('headers','footers','quotes'), random_state=42)\n",[87,16699,16700,16705,16710,16715],{"__ignoreMap":57},[104,16701,16702],{"class":106,"line":107},[104,16703,16704],{},"from sklearn.datasets import fetch_20newsgroups\n",[104,16706,16707],{"class":106,"line":58},[104,16708,16709],{},"cats = ['sci.space', 'rec.sport.baseball']\n",[104,16711,16712],{"class":106,"line":118},[104,16713,16714],{},"train = fetch_20newsgroups(subset='train', categories=cats, remove=('headers','footers','quotes'), random_state=42)\n",[104,16716,16717],{"class":106,"line":124},[104,16718,16719],{},"test = fetch_20newsgroups(subset='test', categories=cats, remove=('headers','footers','quotes'), random_state=42)\n",[11,16721,16722,16723,1502,16726,16729,16730,16732,16733,16736],{},"Reproduzi as três abordagens (",[87,16724,16725],{},"UnigramTransformer",[87,16727,16728],{},"TfidfTransformer"," do jeito exato que o professor fez, mais o ",[87,16731,16608],{}," oficial), com ",[87,16734,16735],{},"RandomForestClassifier(random_state=42)"," em cima de um vocabulário de 5000 palavras:",[1875,16738,16739,16747],{},[1878,16740,16741],{},[1881,16742,16743,16745],{},[1884,16744,14736],{"align":1886},[1884,16746,12004],{"align":3190},[1892,16748,16749,16756,16763],{},[1881,16750,16751,16753],{},[1897,16752,16588],{"align":1886},[1897,16754,16755],{"align":3190},"0.8786",[1881,16757,16758,16760],{},[1897,16759,16596],{"align":1886},[1897,16761,16762],{"align":3190},"0.8774",[1881,16764,16765,16769],{},[1897,16766,16767,16609],{"align":1886},[87,16768,16608],{},[1897,16770,16771],{"align":3190},[15,16772,16773],{},"0.8963",[11,16775,16776],{},"Três números bem próximos de novo, num assunto e num dataset totalmente diferentes dos filmes do IMDB, o que dá confiança de que o padrão da aula não foi coincidência daquele dataset específico: qualquer vetorização razoável de texto (presença de palavra ou TF-IDF) já entrega a maior parte do sinal útil pra separar duas classes bem distintas, aqui espaço de foguete contra taco de beisebol.",[1985,16778,1987],{},{"title":57,"searchDepth":58,"depth":58,"links":16780},[16781,16782,16783,16784,16785,16786,16787,16788],{"id":16081,"depth":58,"text":16082},{"id":16147,"depth":58,"text":16148},{"id":16276,"depth":58,"text":16277},{"id":16316,"depth":58,"text":16317},{"id":16435,"depth":58,"text":16436},{"id":16567,"depth":58,"text":16568},{"id":1872,"depth":58,"text":1873},{"id":1942,"depth":58,"text":1943},"Aula 14, a última da matéria: o professor transforma resenha de filme em número, na mão, e mostra que um TF-IDF caseiro bate com o TfidfVectorizer oficial do scikit-learn.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro",{"title":16059,"description":16789},"pt\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro",[16795,16796,16797],"nlp","tfidf","texto","jfq4X0Wib-h99AJDbGIBwiEnx3DPe564o_yW9JMbIcw",1787338982374]