[{"data":1,"prerenderedAt":1456},["ShallowReactive",2],{"lang-switch-post-\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees":3,"post-pt-pattern-recognition-decision-trees":4},"\u002Fen\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees",{"id":5,"title":6,"body":7,"cover":1441,"date":1442,"description":1443,"extension":1444,"meta":1445,"navigation":1446,"order":226,"path":1447,"playlist":1448,"seo":1449,"status":1450,"stem":1451,"tags":1452,"__hash__":1455},"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":9,"toc":1429},"minimark",[10,23,28,81,104,125,138,171,181,184,188,195,260,267,274,278,281,321,589,697,718,950,953,983,990,1035,1049,1053,1056,1068,1071,1075,1082,1125,1140,1150,1161,1165,1181,1231,1242,1246,1249,1265,1275,1279,1291,1297,1301,1346,1350,1356,1381,1422,1425],[11,12,13,14,18,19,22],"p",{},"Aula 5, dividida entre atributos categóricos (",[15,16,17],"code",{},"aula05",") e contínuos (",[15,20,21],{},"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.",[24,25,27],"h2",{"id":26},"o-dataset-avaliação-de-carros-e-um-baseline-que-importa","O dataset: avaliação de carros, e um baseline que importa",[11,29,30,32,33,37,38,41,42,41,45,41,48,41,51,41,54,57,58,41,61,41,64,67,68,41,71,41,74,41,77,80],{},[15,31,17],{}," usa o ",[34,35,36],"strong",{},"Car Evaluation",", um dataset clássico do UCI: 1728 carros, 6 atributos, todos categóricos (",[15,39,40],{},"buying",", ",[15,43,44],{},"maint",[15,46,47],{},"doors",[15,49,50],{},"persons",[15,52,53],{},"lug_boot",[15,55,56],{},"safety",", cada um com uns 3-4 valores possíveis tipo ",[15,59,60],{},"\"low\"",[15,62,63],{},"\"med\"",[15,65,66],{},"\"high\"","), e uma classe (",[15,69,70],{},"unacc",[15,72,73],{},"acc",[15,75,76],{},"good",[15,78,79],{},"vgood",").",[82,83,88],"pre",{"className":84,"code":85,"language":86,"meta":87,"style":87},"language-python shiki shiki-themes github-light github-dark","for label in set(y):\n    print(f\"{label}:\\t{100*sum(y==label)\u002Flen(y):.4}%\")\n","python","",[15,89,90,98],{"__ignoreMap":87},[91,92,95],"span",{"class":93,"line":94},"line",1,[91,96,97],{},"for label in set(y):\n",[91,99,101],{"class":93,"line":100},2,[91,102,103],{},"    print(f\"{label}:\\t{100*sum(y==label)\u002Flen(y):.4}%\")\n",[105,106,107],"blockquote",{},[11,108,109,112,113,115,116,118,119,121,122,124],{},[34,110,111],{},"Saída:"," ",[15,114,70],{}," 70.02%, ",[15,117,73],{}," 22.22%, ",[15,120,76],{}," 3.99%, ",[15,123,79],{}," 3.76%.",[11,126,127,128,131,132,137],{},"Bem desbalanceado. Antes de qualquer árvore, o professor define o modelo mais preguiçoso possível, o ",[34,129,130],{},"ZeroR"," (chuta sempre a classe mais comum, o mesmo espírito do \"modelo idiota\" ",[133,134,136],"a",{"href":135},"\u002Fplaylists\u002Fpattern-recognition\u002Flinear-regression-estimator","que eu já vi lá no primeiro post desta playlist",", só que pra classificação):",[82,139,141],{"className":84,"code":140,"language":86,"meta":87,"style":87},"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",[15,142,143,148,153,159,165],{"__ignoreMap":87},[91,144,145],{"class":93,"line":94},[91,146,147],{},"class ZeroR(BaseEstimator, ClassifierMixin):\n",[91,149,150],{"class":93,"line":100},[91,151,152],{},"    def fit(self, X, y):\n",[91,154,156],{"class":93,"line":155},3,[91,157,158],{},"        self.answer = most_common(y)\n",[91,160,162],{"class":93,"line":161},4,[91,163,164],{},"    def predict(self, X):\n",[91,166,168],{"class":93,"line":167},5,[91,169,170],{},"        return [self.answer]*X.shape[0]\n",[105,172,173],{},[11,174,175,177,178,180],{},[34,176,111],{}," 70.02% de acurácia. Só chutando ",[15,179,70],{}," sempre.",[11,182,183],{},"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.",[24,185,187],{"id":186},"uma-árvore-aleatória-já-bate-o-baseline","Uma árvore aleatória já bate o baseline",[11,189,190,191,194],{},"Passo intermediário interessante: uma \"árvore\" que escolhe a variável e o valor de divisão ",[34,192,193],{},"completamente ao acaso",", e ainda assim recursa até cada folha ficar pura:",[82,196,198],{"className":84,"code":197,"language":86,"meta":87,"style":87},"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",[15,199,200,205,209,214,219,224,230,236,242,248,254],{"__ignoreMap":87},[91,201,202],{"class":93,"line":94},[91,203,204],{},"class DecisionTree(BaseEstimator, ClassifierMixin):\n",[91,206,207],{"class":93,"line":100},[91,208,152],{},[91,210,211],{"class":93,"line":155},[91,212,213],{},"        self.feature = np.random.randint(X.shape[1])\n",[91,215,216],{"class":93,"line":161},[91,217,218],{},"        self.value = np.random.choice(list(set(X[:, self.feature])))\n",[91,220,221],{"class":93,"line":167},[91,222,223],{},"        equals = X[:, self.feature] == self.value\n",[91,225,227],{"class":93,"line":226},6,[91,228,229],{},"        if sum(equals) > 0 and sum(~equals) > 0:\n",[91,231,233],{"class":93,"line":232},7,[91,234,235],{},"            self.equals_tree = DecisionTree().fit(X[equals], y[equals])\n",[91,237,239],{"class":93,"line":238},8,[91,240,241],{},"            self.not_equals_tree = DecisionTree().fit(X[~equals], y[~equals])\n",[91,243,245],{"class":93,"line":244},9,[91,246,247],{},"        else:\n",[91,249,251],{"class":93,"line":250},10,[91,252,253],{},"            self.answer = most_common(y)\n",[91,255,257],{"class":93,"line":256},11,[91,258,259],{},"        return self\n",[105,261,262],{},[11,263,264,266],{},[34,265,111],{}," 75.6% (nos mesmos dados que treinou).",[11,268,269,270,273],{},"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 ",[34,271,272],{},"sempre"," consegue decorar o treino, útil ou não.",[24,275,277],{"id":276},"impureza-de-gini-a-régua-que-decide-qual-pergunta-fazer","Impureza de Gini: a régua que decide qual pergunta fazer",[11,279,280],{},"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:",[82,282,284],{"className":84,"code":283,"language":86,"meta":87,"style":87},"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",[15,285,286,291,296,301,306,311,316],{"__ignoreMap":87},[91,287,288],{"class":93,"line":94},[91,289,290],{},"def gini(y):\n",[91,292,293],{"class":93,"line":100},[91,294,295],{},"    labels = list(set(y))\n",[91,297,298],{"class":93,"line":155},[91,299,300],{},"    x = 0\n",[91,302,303],{"class":93,"line":161},[91,304,305],{},"    for label in labels:\n",[91,307,308],{"class":93,"line":167},[91,309,310],{},"        label_prob = np.mean(y==label)\n",[91,312,313],{"class":93,"line":226},[91,314,315],{},"        x += label_prob**2\n",[91,317,318],{"class":93,"line":232},[91,319,320],{},"    return 1-x\n",[11,322,323],{},[91,324,327,391],{"className":325},[326],"katex",[91,328,331],{"className":329},[330],"katex-mathml",[332,333,335],"math",{"xmlns":334},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[336,337,338,386],"semantics",{},[339,340,341,345,350,354,357,360,364,367,376],"mrow",{},[342,343,344],"mtext",{},"Gini",[346,347,349],"mo",{"stretchy":348},"false","(",[351,352,353],"mi",{},"y",[346,355,356],{"stretchy":348},")",[346,358,359],{},"=",[361,362,363],"mn",{},"1",[346,365,366],{},"−",[368,369,370,373],"msub",{},[346,371,372],{},"∑",[351,374,375],{},"k",[377,378,379,381,383],"msubsup",{},[351,380,11],{},[351,382,375],{},[361,384,385],{},"2",[387,388,390],"annotation",{"encoding":389},"application\u002Fx-tex","\\text{Gini}(y) = 1 - \\sum_{k} p_k^2",[91,392,396,438,459],{"className":393,"ariaHidden":395},[394],"katex-html","true",[91,397,400,405,413,417,422,426,431,435],{"className":398},[399],"base",[91,401],{"className":402,"style":404},[403],"strut","height:1em;vertical-align:-0.25em;",[91,406,410],{"className":407},[408,409],"mord","text",[91,411,344],{"className":412},[408],[91,414,349],{"className":415},[416],"mopen",[91,418,353],{"className":419,"style":421},[408,420],"mathnormal","margin-right:0.0359em;",[91,423,356],{"className":424},[425],"mclose",[91,427],{"className":428,"style":430},[429],"mspace","margin-right:0.2778em;",[91,432,359],{"className":433},[434],"mrel",[91,436],{"className":437,"style":430},[429],[91,439,441,445,448,452,456],{"className":440},[399],[91,442],{"className":443,"style":444},[403],"height:0.7278em;vertical-align:-0.0833em;",[91,446,363],{"className":447},[408],[91,449],{"className":450,"style":451},[429],"margin-right:0.2222em;",[91,453,366],{"className":454},[455],"mbin",[91,457],{"className":458,"style":451},[429],[91,460,462,466,530,534],{"className":461},[399],[91,463],{"className":464,"style":465},[403],"height:1.1138em;vertical-align:-0.2997em;",[91,467,470,476],{"className":468},[469],"mop",[91,471,372],{"className":472,"style":475},[469,473,474],"op-symbol","small-op","position:relative;top:0em;",[91,477,480],{"className":478},[479],"msupsub",[91,481,485,521],{"className":482},[483,484],"vlist-t","vlist-t2",[91,486,489,516],{"className":487},[488],"vlist-r",[91,490,494],{"className":491,"style":493},[492],"vlist","height:0.1864em;",[91,495,497,502],{"style":496},"top:-2.4003em;margin-left:0em;margin-right:0.05em;",[91,498],{"className":499,"style":501},[500],"pstrut","height:2.7em;",[91,503,509],{"className":504},[505,506,507,508],"sizing","reset-size6","size3","mtight",[91,510,512],{"className":511},[408,508],[91,513,375],{"className":514,"style":515},[408,420,508],"margin-right:0.0315em;",[91,517,520],{"className":518},[519],"vlist-s","​",[91,522,524],{"className":523},[488],[91,525,528],{"className":526,"style":527},[492],"height:0.2997em;",[91,529],{},[91,531],{"className":532,"style":533},[429],"margin-right:0.1667em;",[91,535,537,540],{"className":536},[408],[91,538,11],{"className":539},[408,420],[91,541,543],{"className":542},[479],[91,544,546,580],{"className":545},[483,484],[91,547,549,577],{"className":548},[488],[91,550,553,565],{"className":551,"style":552},[492],"height:0.8141em;",[91,554,556,559],{"style":555},"top:-2.4169em;margin-left:0em;margin-right:0.05em;",[91,557],{"className":558,"style":501},[500],[91,560,562],{"className":561},[505,506,507,508],[91,563,375],{"className":564,"style":515},[408,420,508],[91,566,568,571],{"style":567},"top:-3.063em;margin-right:0.05em;",[91,569],{"className":570,"style":501},[500],[91,572,574],{"className":573},[505,506,507,508],[91,575,385],{"className":576},[408,508],[91,578,520],{"className":579},[519],[91,581,583],{"className":582},[488],[91,584,587],{"className":585,"style":586},[492],"height:0.2831em;",[91,588],{},[11,590,591,592,666,667,696],{},"onde ",[91,593,595,613],{"className":594},[326],[91,596,598],{"className":597},[330],[332,599,600],{"xmlns":334},[336,601,602,610],{},[339,603,604],{},[368,605,606,608],{},[351,607,11],{},[351,609,375],{},[387,611,612],{"encoding":389},"p_k",[91,614,616],{"className":615,"ariaHidden":395},[394],[91,617,619,623],{"className":618},[399],[91,620],{"className":621,"style":622},[403],"height:0.625em;vertical-align:-0.1944em;",[91,624,626,629],{"className":625},[408],[91,627,11],{"className":628},[408,420],[91,630,632],{"className":631},[479],[91,633,635,657],{"className":634},[483,484],[91,636,638,654],{"className":637},[488],[91,639,642],{"className":640,"style":641},[492],"height:0.3361em;",[91,643,645,648],{"style":644},"top:-2.55em;margin-left:0em;margin-right:0.05em;",[91,646],{"className":647,"style":501},[500],[91,649,651],{"className":650},[505,506,507,508],[91,652,375],{"className":653,"style":515},[408,420,508],[91,655,520],{"className":656},[519],[91,658,660],{"className":659},[488],[91,661,664],{"className":662,"style":663},[492],"height:0.15em;",[91,665],{}," é a fração de exemplos da classe ",[91,668,670,683],{"className":669},[326],[91,671,673],{"className":672},[330],[332,674,675],{"xmlns":334},[336,676,677,681],{},[339,678,679],{},[351,680,375],{},[387,682,375],{"encoding":389},[91,684,686],{"className":685,"ariaHidden":395},[394],[91,687,689,693],{"className":688},[399],[91,690],{"className":691,"style":692},[403],"height:0.6944em;",[91,694,375],{"className":695,"style":515},[408,420]," dentro do grupo. Dois casos extremos confirmam a intuição:",[105,698,699],{},[11,700,701,112,703,706,707,710,711,713,714,717],{},[34,702,111],{},[15,704,705],{},"gini"," de um grupo com todo mundo da mesma classe: ",[34,708,709],{},"0.0",". ",[15,712,705],{}," de um grupo com 100 classes diferentes, uma de cada: ",[34,715,716],{},"0.99",", bem perto do máximo teórico.",[11,719,720,721,724,725,945,946,949],{},"Zero é pureza total (não sobra nenhuma dúvida sobre a classe). Quanto mais dividido entre classes, mais alto. O Bishop chama isso de ",[34,722,723],{},"índice de Gini"," (ele escreve como ",[91,726,728,775],{"className":727},[326],[91,729,731],{"className":730},[330],[332,732,733],{"xmlns":334},[336,734,735,772],{},[339,736,737,743,754,756,758,760,770],{},[368,738,739,741],{},[346,740,372],{},[351,742,375],{},[368,744,745,747],{},[351,746,11],{},[339,748,749,752],{},[351,750,751],{},"τ",[351,753,375],{},[346,755,349],{"stretchy":348},[361,757,363],{},[346,759,366],{},[368,761,762,764],{},[351,763,11],{},[339,765,766,768],{},[351,767,751],{},[351,769,375],{},[346,771,356],{"stretchy":348},[387,773,774],{"encoding":389},"\\sum_k p_{\\tau k}(1-p_{\\tau k})",[91,776,778,890],{"className":777,"ariaHidden":395},[394],[91,779,781,785,825,828,875,878,881,884,887],{"className":780},[399],[91,782],{"className":783,"style":784},[403],"height:1.0497em;vertical-align:-0.2997em;",[91,786,788,791],{"className":787},[469],[91,789,372],{"className":790,"style":475},[469,473,474],[91,792,794],{"className":793},[479],[91,795,797,817],{"className":796},[483,484],[91,798,800,814],{"className":799},[488],[91,801,803],{"className":802,"style":493},[492],[91,804,805,808],{"style":496},[91,806],{"className":807,"style":501},[500],[91,809,811],{"className":810},[505,506,507,508],[91,812,375],{"className":813,"style":515},[408,420,508],[91,815,520],{"className":816},[519],[91,818,820],{"className":819},[488],[91,821,823],{"className":822,"style":527},[492],[91,824],{},[91,826],{"className":827,"style":533},[429],[91,829,831,834],{"className":830},[408],[91,832,11],{"className":833},[408,420],[91,835,837],{"className":836},[479],[91,838,840,867],{"className":839},[483,484],[91,841,843,864],{"className":842},[488],[91,844,846],{"className":845,"style":641},[492],[91,847,848,851],{"style":644},[91,849],{"className":850,"style":501},[500],[91,852,854],{"className":853},[505,506,507,508],[91,855,857,861],{"className":856},[408,508],[91,858,751],{"className":859,"style":860},[408,420,508],"margin-right:0.1132em;",[91,862,375],{"className":863,"style":515},[408,420,508],[91,865,520],{"className":866},[519],[91,868,870],{"className":869},[488],[91,871,873],{"className":872,"style":663},[492],[91,874],{},[91,876,349],{"className":877},[416],[91,879,363],{"className":880},[408],[91,882],{"className":883,"style":451},[429],[91,885,366],{"className":886},[455],[91,888],{"className":889,"style":451},[429],[91,891,893,896,942],{"className":892},[399],[91,894],{"className":895,"style":404},[403],[91,897,899,902],{"className":898},[408],[91,900,11],{"className":901},[408,420],[91,903,905],{"className":904},[479],[91,906,908,934],{"className":907},[483,484],[91,909,911,931],{"className":910},[488],[91,912,914],{"className":913,"style":641},[492],[91,915,916,919],{"style":644},[91,917],{"className":918,"style":501},[500],[91,920,922],{"className":921},[505,506,507,508],[91,923,925,928],{"className":924},[408,508],[91,926,751],{"className":927,"style":860},[408,420,508],[91,929,375],{"className":930,"style":515},[408,420,508],[91,932,520],{"className":933},[519],[91,935,937],{"className":936},[488],[91,938,940],{"className":939,"style":663},[492],[91,941],{},[91,943,356],{"className":944},[425],", 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 ",[34,947,948],{},"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,951,952],{},"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:",[82,954,956],{"className":84,"code":955,"language":86,"meta":87,"style":87},"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",[15,957,958,963,968,973,978],{"__ignoreMap":87},[91,959,960],{"class":93,"line":94},[91,961,962],{},"def impurity_value(x, y, value, impurity_function):\n",[91,964,965],{"class":93,"line":100},[91,966,967],{},"    equals = x == value\n",[91,969,970],{"class":93,"line":155},[91,971,972],{},"    equals_impurity = impurity_function(y[equals])\n",[91,974,975],{"class":93,"line":161},[91,976,977],{},"    not_equals_impurity = impurity_function(y[~equals])\n",[91,979,980],{"class":93,"line":167},[91,981,982],{},"    return (np.mean(equals)) * equals_impurity + (np.mean(~equals)) * not_equals_impurity\n",[11,984,985,986,989],{},"E a árvore gulosa testa ",[34,987,988],{},"toda"," combinação de variável e valor possível, ficando com a que dá a menor impureza combinada:",[82,991,993],{"className":84,"code":992,"language":86,"meta":87,"style":87},"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",[15,994,995,1000,1005,1010,1015,1020,1025,1030],{"__ignoreMap":87},[91,996,997],{"class":93,"line":94},[91,998,999],{},"def best_feature(X, y, impurity_function):\n",[91,1001,1002],{"class":93,"line":100},[91,1003,1004],{},"    best_feature, best_value, best_value_impurity = None, None, float('inf')\n",[91,1006,1007],{"class":93,"line":155},[91,1008,1009],{},"    for feature in range(X.shape[1]):\n",[91,1011,1012],{"class":93,"line":161},[91,1013,1014],{},"        value, _ = best_split(X[:,feature], y, impurity_function)\n",[91,1016,1017],{"class":93,"line":167},[91,1018,1019],{},"        feature_impurity = impurity_value(X[:,feature], y, value, impurity_function)\n",[91,1021,1022],{"class":93,"line":226},[91,1023,1024],{},"        if feature_impurity \u003C best_value_impurity:\n",[91,1026,1027],{"class":93,"line":232},[91,1028,1029],{},"            best_feature, best_value_impurity, best_value = feature, feature_impurity, value\n",[91,1031,1032],{"class":93,"line":238},[91,1033,1034],{},"    return best_feature, best_value, best_value_impurity\n",[105,1036,1037],{},[11,1038,1039,1041,1042,1044,1045,1048],{},[34,1040,111],{}," a primeira pergunta que a árvore escolhe fazer é sobre ",[15,1043,56],{}," (variável 5), dividindo em \"",[15,1046,1047],{},"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.",[24,1050,1052],{"id":1051},"a-árvore-gulosa-de-verdade","A árvore gulosa de verdade",[11,1054,1055],{},"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:",[105,1057,1058],{},[11,1059,1060,1062,1063,1067],{},[34,1061,111],{}," 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), ",[133,1064,1066],{"href":1065},"\u002Fplaylists\u002Fpattern-recognition\u002Fpipeline-cross-validation","a mesma técnica que eu já vi valer a pena no post anterior",".",[11,1069,1070],{},"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.",[24,1072,1074],{"id":1073},"limitar-a-profundidade-nem-sempre-ajuda","Limitar a profundidade nem sempre ajuda",[11,1076,1077,1078,1081],{},"O jeito mais direto de conter uma árvore é limitar quantas perguntas seguidas ela pode fazer, ",[15,1079,1080],{},"max_depth",":",[82,1083,1085],{"className":84,"code":1084,"language":86,"meta":87,"style":87},"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",[15,1086,1087,1091,1096,1101,1105,1110,1115,1120],{"__ignoreMap":87},[91,1088,1089],{"class":93,"line":94},[91,1090,204],{},[91,1092,1093],{"class":93,"line":100},[91,1094,1095],{},"    def __init__(self, max_depth=9999999):\n",[91,1097,1098],{"class":93,"line":155},[91,1099,1100],{},"        self.max_depth = max_depth\n",[91,1102,1103],{"class":93,"line":161},[91,1104,152],{},[91,1106,1107],{"class":93,"line":167},[91,1108,1109],{},"        ...\n",[91,1111,1112],{"class":93,"line":226},[91,1113,1114],{},"        if sum(equals) > 0 and sum(~equals) > 0 and self.max_depth > 0:\n",[91,1116,1117],{"class":93,"line":232},[91,1118,1119],{},"            self.equals_tree = DecisionTree(self.max_depth-1).fit(X[equals], y[equals])\n",[91,1121,1122],{"class":93,"line":238},[91,1123,1124],{},"            ...\n",[105,1126,1127],{},[11,1128,1129,1131,1132,1135,1136,1139],{},[34,1130,111],{}," com ",[15,1133,1134],{},"max_depth=5",", a acurácia de validação cruzada ",[34,1137,1138],{},"cai"," pra 86.6% (contra 97.2% sem limite nenhum).",[11,1141,1142,1143,1146,1147,1149],{},"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\", é ",[34,1144,1145],{},"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 ",[15,1148,1080],{}," 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,1151,1152,1153,1156,1157,1160],{},"Com ",[15,1154,1155],{},"max_depth=20"," e ",[15,1158,1159],{},"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.",[24,1162,1164],{"id":1163},"atributos-contínuos-a-mesma-ideia-o-corte-muda","Atributos contínuos: a mesma ideia, o corte muda",[11,1166,1167,1169,1170,1156,1173,1176,1177,1180],{},[15,1168,21],{}," troca de dataset (Iris, o clássico das 3 espécies de flor, usando só ",[15,1171,1172],{},"petal length",[15,1174,1175],{},"petal width",") e de tipo de pergunta: em vez de \"é igual a esse valor?\", a pergunta vira \"",[34,1178,1179],{},"é 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:",[82,1182,1184],{"className":84,"code":1183,"language":86,"meta":87,"style":87},"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",[15,1185,1186,1191,1196,1201,1206,1211,1216,1221,1226],{"__ignoreMap":87},[91,1187,1188],{"class":93,"line":94},[91,1189,1190],{},"def best_split(x, y, impurity_function):\n",[91,1192,1193],{"class":93,"line":100},[91,1194,1195],{},"    best_value, best_value_impurity = 0, float('inf')\n",[91,1197,1198],{"class":93,"line":155},[91,1199,1200],{},"    x = np.sort(x)\n",[91,1202,1203],{"class":93,"line":161},[91,1204,1205],{},"    for i in range(1, len(x)):\n",[91,1207,1208],{"class":93,"line":167},[91,1209,1210],{},"        value = (x[i-1]+x[i])\u002F2\n",[91,1212,1213],{"class":93,"line":226},[91,1214,1215],{},"        value_impurity = impurity_value(x, y, value, impurity_function)\n",[91,1217,1218],{"class":93,"line":232},[91,1219,1220],{},"        if value_impurity \u003C best_value_impurity:\n",[91,1222,1223],{"class":93,"line":238},[91,1224,1225],{},"            best_value, best_value_impurity = value, value_impurity\n",[91,1227,1228],{"class":93,"line":244},[91,1229,1230],{},"    return best_value, best_value_impurity\n",[105,1232,1233],{},[11,1234,1235,1237,1238,1241],{},[34,1236,111],{}," a primeira pergunta que a árvore aprende é ",[15,1239,1240],{},"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).",[24,1243,1245],{"id":1244},"interativo-profundidade-mudando-a-fronteira-ao-vivo","Interativo: profundidade mudando a fronteira ao vivo",[11,1247,1248],{},"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:",[1250,1251],"decision-tree-explorer",{":classes":1252,":depth-options":1253,":initial-max-depth":363,":x-max":1254,":x-min":1255,":x-train":1256,":y-max":1257,":y-min":1258,":y-train":1259,"class0-label":1260,"class1-label":1261,"class2-label":1262,"x-label":1263,"y-label":1264},"[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","[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,1266,1267,1268,1270,1271,1274],{},"Com profundidade 1, a árvore só faz uma pergunta (a mesma ",[15,1269,1240],{}," 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 ",[34,1272,1273],{},"retas verticais ou horizontais",", nunca diagonais, porque cada pergunta olha uma variável de cada vez.",[24,1276,1278],{"id":1277},"o-que-a-árvore-não-consegue-fazer-bem","O que a árvore não consegue fazer bem",[11,1280,1281,1282,1285,1286,1290],{},"Essa última observação é uma das limitações que o Bishop aponta: árvores de decisão só cortam ",[34,1283,1284],{},"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: ",[133,1287,1289],{"href":1288},"\u002Fplaylists\u002Fpattern-recognition\u002Fknn-classifier","as regiões do KNN"," já eram mais suaves, sem essa limitação de eixo).",[11,1292,1293,1294,1067],{},"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 ",[133,1295,1296],{"href":1065},"validação cruzada, não uma única divisão, é a forma correta de medir isso",[24,1298,1300],{"id":1299},"fechando","Fechando",[1302,1303,1304,1318],"table",{},[1305,1306,1307],"thead",{},[1308,1309,1310,1315],"tr",{},[1311,1312,1314],"th",{"align":1313},"left","O que eu já sabia",[1311,1316,1317],{"align":1313},"O que essa aula assentou",[1319,1320,1321,1330,1338],"tbody",{},[1308,1322,1323,1327],{},[1324,1325,1326],"td",{"align":1313},"Um baseline idiota ajuda a interpretar qualquer acurácia",[1324,1328,1329],{"align":1313},"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",[1308,1331,1332,1335],{},[1324,1333,1334],{"align":1313},"Overfitting acontece quando o modelo tem liberdade demais",[1324,1336,1337],{"align":1313},"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",[1308,1339,1340,1343],{},[1324,1341,1342],{"align":1313},"Validação cruzada dá uma estimativa mais estável",[1324,1344,1345],{"align":1313},"Á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",[24,1347,1349],{"id":1348},"aplicação-prática","Aplicação Prática",[11,1351,1352,1353,1355],{},"Uso o dataset de avaliação de carros (o categórico, ",[15,1354,17],{},") 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.",[82,1357,1359],{"className":84,"code":1358,"language":86,"meta":87,"style":87},"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",[15,1360,1361,1366,1371,1376],{"__ignoreMap":87},[91,1362,1363],{"class":93,"line":94},[91,1364,1365],{},"for depth in [1, 2, 3, 5, 10, 9999]:\n",[91,1367,1368],{"class":93,"line":100},[91,1369,1370],{},"    model = DecisionTree(max_depth=depth, min_sample_split=2)\n",[91,1372,1373],{"class":93,"line":155},[91,1374,1375],{},"    scores = cross_val_score(model, X, y, cv=KFold(n_splits=5, shuffle=True))\n",[91,1377,1378],{"class":93,"line":161},[91,1379,1380],{},"    print(depth, np.mean(scores))\n",[1302,1382,1383,1395],{},[1305,1384,1385],{},[1308,1386,1387,1391],{},[1311,1388,1390],{"align":1389},"center","Profundidade máxima",[1311,1392,1394],{"align":1393},"right","Acurácia (CV 5 dobras)",[1319,1396,1397,1404,1412],{},[1308,1398,1399,1401],{},[1324,1400,363],{"align":1389},[1324,1402,1403],{"align":1393},"≈ 0.78 (uma pergunta só, pouco acima do baseline de 0.70)",[1308,1405,1406,1409],{},[1324,1407,1408],{"align":1389},"5",[1324,1410,1411],{"align":1393},"0.866",[1308,1413,1414,1417],{},[1324,1415,1416],{"align":1389},"Sem limite",[1324,1418,1419],{"align":1393},[34,1420,1421],{},"0.972",[11,1423,1424],{},"(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.",[1426,1427,1428],"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":87,"searchDepth":100,"depth":100,"links":1430},[1431,1432,1433,1434,1435,1436,1437,1438,1439,1440],{"id":26,"depth":100,"text":27},{"id":186,"depth":100,"text":187},{"id":276,"depth":100,"text":277},{"id":1051,"depth":100,"text":1052},{"id":1073,"depth":100,"text":1074},{"id":1163,"depth":100,"text":1164},{"id":1244,"depth":100,"text":1245},{"id":1277,"depth":100,"text":1278},{"id":1299,"depth":100,"text":1300},{"id":1348,"depth":100,"text":1349},null,"2026-08-19","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.","md",{},true,"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees","pattern-recognition",{"title":6,"description":1443},"published","pt\u002Fplaylists\u002Fpattern-recognition\u002Fdecision-trees",[1453,705,1454],"arvore-de-decisao","overfitting","GiCBmi_XrIuvKcfwixgQDy3aBLAGUG0jtxa620v15k0",1787338983265]