[{"data":1,"prerenderedAt":1473},["ShallowReactive",2],{"lang-switch-post-\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo":3,"post-pt-neural-networks-funcoes-de-custo":4},"\u002Fen\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo",{"id":5,"title":6,"body":7,"cover":1458,"date":1459,"description":1460,"extension":1461,"meta":1462,"navigation":65,"order":62,"path":1463,"playlist":1464,"seo":1465,"status":1466,"stem":1467,"tags":1468,"__hash__":1472},"posts\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo.md","Trocando a Função de Custo Como Quem Troca de Roupa",{"type":8,"value":9,"toc":1450},"minimark",[10,19,24,158,192,221,230,234,307,341,345,381,622,658,672,693,753,784,800,1080,1118,1129,1143,1147,1247,1256,1273,1277,1338,1342,1360,1437,1446],[11,12,13,14,18],"p",{},"Aula 3a e 3b. Até aqui eu vi 4 algoritmos diferentes (perceptron, perceptron vetorizado, Adaline via pseudo-inversa, Adaline via gradiente) meio que como coisas separadas. Essa aula mostra que eles são, na verdade, a ",[15,16,17],"strong",{},"mesma receita",", só trocando um ingrediente: a função de custo.",[20,21,23],"h2",{"id":22},"primeiro-o-algoritmo-de-treino-vira-plugável","Primeiro, o algoritmo de treino vira plugável",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark","class TrainingAlgorithm(ABC):\n  @abstractmethod\n  def get_w(self, X, y):\n    pass\n\nclass PseudoInverse(TrainingAlgorithm):\n  def get_w(self, X, y):\n    return np.linalg.pinv(X) @ y\n\nclass NeuralNetwork(BaseEstimator, ClassifierMixin):\n  def __init__(self, training_algorithm=PseudoInverse()):\n    self.training_algorithm = training_algorithm\n\n  def fit(self, X, y):\n    X = include_bias(X)\n    self.w_ = self.training_algorithm.get_w(X, y)\n    return self\n\n  def predict(self, X):\n    X = include_bias(X)\n    return np.sign(X @ self.w_)\n","python","",[32,33,34,42,48,54,60,67,73,78,84,89,95,101,107,112,118,124,130,136,141,147,152],"code",{"__ignoreMap":30},[35,36,39],"span",{"class":37,"line":38},"line",1,[35,40,41],{},"class TrainingAlgorithm(ABC):\n",[35,43,45],{"class":37,"line":44},2,[35,46,47],{},"  @abstractmethod\n",[35,49,51],{"class":37,"line":50},3,[35,52,53],{},"  def get_w(self, X, y):\n",[35,55,57],{"class":37,"line":56},4,[35,58,59],{},"    pass\n",[35,61,63],{"class":37,"line":62},5,[35,64,66],{"emptyLinePlaceholder":65},true,"\n",[35,68,70],{"class":37,"line":69},6,[35,71,72],{},"class PseudoInverse(TrainingAlgorithm):\n",[35,74,76],{"class":37,"line":75},7,[35,77,53],{},[35,79,81],{"class":37,"line":80},8,[35,82,83],{},"    return np.linalg.pinv(X) @ y\n",[35,85,87],{"class":37,"line":86},9,[35,88,66],{"emptyLinePlaceholder":65},[35,90,92],{"class":37,"line":91},10,[35,93,94],{},"class NeuralNetwork(BaseEstimator, ClassifierMixin):\n",[35,96,98],{"class":37,"line":97},11,[35,99,100],{},"  def __init__(self, training_algorithm=PseudoInverse()):\n",[35,102,104],{"class":37,"line":103},12,[35,105,106],{},"    self.training_algorithm = training_algorithm\n",[35,108,110],{"class":37,"line":109},13,[35,111,66],{"emptyLinePlaceholder":65},[35,113,115],{"class":37,"line":114},14,[35,116,117],{},"  def fit(self, X, y):\n",[35,119,121],{"class":37,"line":120},15,[35,122,123],{},"    X = include_bias(X)\n",[35,125,127],{"class":37,"line":126},16,[35,128,129],{},"    self.w_ = self.training_algorithm.get_w(X, y)\n",[35,131,133],{"class":37,"line":132},17,[35,134,135],{},"    return self\n",[35,137,139],{"class":37,"line":138},18,[35,140,66],{"emptyLinePlaceholder":65},[35,142,144],{"class":37,"line":143},19,[35,145,146],{},"  def predict(self, X):\n",[35,148,150],{"class":37,"line":149},20,[35,151,123],{},[35,153,155],{"class":37,"line":154},21,[35,156,157],{},"    return np.sign(X @ self.w_)\n",[11,159,160,161,164,165,168,169,172,173,176,177,180,181,184,185,188,189,191],{},"Isso é o padrão de projeto ",[15,162,163],{},"Strategy",": ",[32,166,167],{},"NeuralNetwork"," não sabe mais ",[15,170,171],{},"como"," os pesos são calculados, só que existe um objeto ",[32,174,175],{},"training_algorithm"," com um método ",[32,178,179],{},"get_w",". Trocar ",[32,182,183],{},"PseudoInverse()"," por ",[32,186,187],{},"SGD()"," no construtor troca o algoritmo de treino inteiro, sem tocar em ",[32,190,167],{},".",[193,194,195],"blockquote",{},[11,196,197,204,205,208,209,212,213,204,218,220],{},[15,198,199,200,203],{},"Saída (",[32,201,202],{},"PseudoInverse","):"," acurácia ",[32,206,207],{},"0.95",", pesos ",[32,210,211],{},"[-1.905, 2.656, 1.049]",".\n",[15,214,199,215,203],{},[32,216,217],{},"SGD",[32,219,207],{},", pesos praticamente idênticos.",[11,222,223,224,229],{},"Confirma de novo, agora com o código organizado de um jeito mais limpo, o que ",[225,226,228],"a",{"href":227},"\u002Fplaylists\u002Fneural-networks\u002Fadaline-regra-delta","já vi nos dois posts passados",": pseudo-inversa e gradiente descendente resolvem o mesmíssimo problema.",[20,231,233],{"id":232},"agora-a-função-de-custo-também-vira-plugável","Agora a função de custo também vira plugável",[25,235,237],{"className":27,"code":236,"language":29,"meta":30,"style":30},"class CostFunction(ABC):\n  @abstractstaticmethod\n  def get_cost(y, y_pred):\n    pass\n  @abstractstaticmethod\n  def get_gradient(X, y, y_pred):\n    pass\n\nclass WidrowHoff(CostFunction):\n  @staticmethod\n  def get_cost(y, y_pred):\n    return np.mean((y-y_pred)**2)\n  @staticmethod\n  def get_gradient(X, y, y_pred):\n    return X.T @ (y-y_pred)\n",[32,238,239,244,249,254,258,262,267,271,275,280,285,289,294,298,302],{"__ignoreMap":30},[35,240,241],{"class":37,"line":38},[35,242,243],{},"class CostFunction(ABC):\n",[35,245,246],{"class":37,"line":44},[35,247,248],{},"  @abstractstaticmethod\n",[35,250,251],{"class":37,"line":50},[35,252,253],{},"  def get_cost(y, y_pred):\n",[35,255,256],{"class":37,"line":56},[35,257,59],{},[35,259,260],{"class":37,"line":62},[35,261,248],{},[35,263,264],{"class":37,"line":69},[35,265,266],{},"  def get_gradient(X, y, y_pred):\n",[35,268,269],{"class":37,"line":75},[35,270,59],{},[35,272,273],{"class":37,"line":80},[35,274,66],{"emptyLinePlaceholder":65},[35,276,277],{"class":37,"line":86},[35,278,279],{},"class WidrowHoff(CostFunction):\n",[35,281,282],{"class":37,"line":91},[35,283,284],{},"  @staticmethod\n",[35,286,287],{"class":37,"line":97},[35,288,253],{},[35,290,291],{"class":37,"line":103},[35,292,293],{},"    return np.mean((y-y_pred)**2)\n",[35,295,296],{"class":37,"line":109},[35,297,284],{},[35,299,300],{"class":37,"line":114},[35,301,266],{},[35,303,304],{"class":37,"line":120},[35,305,306],{},"    return X.T @ (y-y_pred)\n",[11,308,309,310,312,313,316,317,320,321,324,325,328,329,332,333,336,337,340],{},"O ",[32,311,217],{}," agora recebe um ",[32,314,315],{},"cost_function"," também, e usa ",[32,318,319],{},"self.cost_function.get_gradient(...)"," em vez de calcular o gradiente na mão. ",[32,322,323],{},"WidrowHoff"," é ",[225,326,327],{"href":227},"exatamente a regra delta que eu vi na aula passada",": erro contínuo (",[32,330,331],{},"y - y_pred",", sem ",[32,334,335],{},"sign()",") vezes a entrada. Trocar a função de custo aqui é trocar ",[15,338,339],{},"o que \"erro\" significa",", sem tocar no laço de treino.",[20,342,344],{"id":343},"cada-função-de-custo-recupera-um-algoritmo-diferente","Cada função de custo recupera um algoritmo diferente",[25,346,348],{"className":27,"code":347,"language":29,"meta":30,"style":30},"class SmoothedSurrogate(CostFunction):\n  @staticmethod\n  def get_cost(y, y_pred):\n    return np.sum(np.maximum(np.zeros(y.shape), -y * y_pred))\n  @staticmethod\n  def get_gradient(X, y, y_pred):\n    return X.T @ (y - np.sign(y_pred))\n",[32,349,350,355,359,363,368,372,376],{"__ignoreMap":30},[35,351,352],{"class":37,"line":38},[35,353,354],{},"class SmoothedSurrogate(CostFunction):\n",[35,356,357],{"class":37,"line":44},[35,358,284],{},[35,360,361],{"class":37,"line":50},[35,362,253],{},[35,364,365],{"class":37,"line":56},[35,366,367],{},"    return np.sum(np.maximum(np.zeros(y.shape), -y * y_pred))\n",[35,369,370],{"class":37,"line":62},[35,371,284],{},[35,373,374],{"class":37,"line":69},[35,375,266],{},[35,377,378],{"class":37,"line":75},[35,379,380],{},"    return X.T @ (y - np.sign(y_pred))\n",[11,382,383,384,387,388,391,392,396,397,400,401,164,404,613,614,617,618,621],{},"Repara no ",[32,385,386],{},"np.sign(y_pred)"," dentro do gradiente: isso volta a medir erro ",[15,389,390],{},"depois"," do limiar, exatamente como ",[225,393,395],{"href":394},"\u002Fplaylists\u002Fneural-networks\u002Fmcculloch-pitts-perceptron","o perceptron do Rosenblatt",". O nome ",[32,398,399],{},"SmoothedSurrogate"," bate com o que o Aggarwal chama de ",[15,402,403],{},"critério do perceptron",[35,405,408,473],{"className":406},[407],"katex",[35,409,412],{"className":410},[411],"katex-mathml",[413,414,416],"math",{"xmlns":415},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[417,418,419,468],"semantics",{},[420,421,422,426,430,433,436,440,444,448,451,454,457,465],"mrow",{},[423,424,425],"mi",{},"L",[427,428,429],"mo",{},"=",[423,431,432],{},"max",[427,434,435],{},"⁡",[427,437,439],{"stretchy":438},"false","(",[441,442,443],"mn",{},"0",[427,445,447],{"separator":446},"true",",",[427,449,450],{},"−",[423,452,453],{},"y",[427,455,456],{},"⋅",[458,459,460,462],"mover",{"accent":446},[423,461,453],{},[427,463,464],{},"^",[427,466,467],{"stretchy":438},")",[469,470,472],"annotation",{"encoding":471},"application\u002Fx-tex","L = \\max(0, -y \\cdot \\hat{y})",[35,474,477,503,547],{"className":475,"ariaHidden":446},[476],"katex-html",[35,478,481,486,491,496,500],{"className":479},[480],"base",[35,482],{"className":483,"style":485},[484],"strut","height:0.6833em;",[35,487,425],{"className":488},[489,490],"mord","mathnormal",[35,492],{"className":493,"style":495},[494],"mspace","margin-right:0.2778em;",[35,497,429],{"className":498},[499],"mrel",[35,501],{"className":502,"style":495},[494],[35,504,506,510,514,518,521,525,529,532,536,540,544],{"className":505},[480],[35,507],{"className":508,"style":509},[484],"height:1em;vertical-align:-0.25em;",[35,511,432],{"className":512},[513],"mop",[35,515,439],{"className":516},[517],"mopen",[35,519,443],{"className":520},[489],[35,522,447],{"className":523},[524],"mpunct",[35,526],{"className":527,"style":528},[494],"margin-right:0.1667em;",[35,530,450],{"className":531},[489],[35,533,453],{"className":534,"style":535},[489,490],"margin-right:0.0359em;",[35,537],{"className":538,"style":539},[494],"margin-right:0.2222em;",[35,541,456],{"className":542},[543],"mbin",[35,545],{"className":546,"style":539},[494],[35,548,550,553,609],{"className":549},[480],[35,551],{"className":552,"style":509},[484],[35,554,557],{"className":555},[489,556],"accent",[35,558,562,600],{"className":559},[560,561],"vlist-t","vlist-t2",[35,563,566,595],{"className":564},[565],"vlist-r",[35,567,571,582],{"className":568,"style":570},[569],"vlist","height:0.6944em;",[35,572,574,579],{"style":573},"top:-3em;",[35,575],{"className":576,"style":578},[577],"pstrut","height:3em;",[35,580,453],{"className":581,"style":535},[489,490],[35,583,584,587],{"style":573},[35,585],{"className":586,"style":578},[577],[35,588,592],{"className":589,"style":591},[590],"accent-body","left:-0.1944em;",[35,593,464],{"className":594},[489],[35,596,599],{"className":597},[598],"vlist-s","​",[35,601,603],{"className":602},[565],[35,604,607],{"className":605,"style":606},[569],"height:0.1944em;",[35,608],{},[35,610,467],{"className":611},[612],"mclose",", zero quando o ponto já está do lado certo, crescendo linearmente quando está errado. Não é coincidência a acurácia bater ",[32,615,616],{},"1.0",": essa função de custo, plugada nesse framework genérico, ",[15,619,620],{},"é"," o perceptron original de novo, só que expresso na linguagem de \"função de custo\" em vez de \"regra de atualização\".",[25,623,625],{"className":27,"code":624,"language":29,"meta":30,"style":30},"class LogLikehood(CostFunction):\n  @staticmethod\n  def get_cost(y, y_pred):\n    return np.sum(np.maximum(np.zeros(y.shape), 1 - y * y_pred))\n  @staticmethod\n  def get_gradient(X, y, y_pred):\n    return X.T @ (y - expit(y_pred))\n",[32,626,627,632,636,640,645,649,653],{"__ignoreMap":30},[35,628,629],{"class":37,"line":38},[35,630,631],{},"class LogLikehood(CostFunction):\n",[35,633,634],{"class":37,"line":44},[35,635,284],{},[35,637,638],{"class":37,"line":50},[35,639,253],{},[35,641,642],{"class":37,"line":56},[35,643,644],{},"    return np.sum(np.maximum(np.zeros(y.shape), 1 - y * y_pred))\n",[35,646,647],{"class":37,"line":62},[35,648,284],{},[35,650,651],{"class":37,"line":69},[35,652,266],{},[35,654,655],{"class":37,"line":75},[35,656,657],{},"    return X.T @ (y - expit(y_pred))\n",[193,659,660],{},[11,661,662,204,665,208,668,671],{},[15,663,664],{},"Saída:",[15,666,667],{},"0.65",[32,669,670],{},"[-60.78, 27.44, -24.79]",". Bem pior que tudo que eu vi até aqui, e os pesos ficaram enormes.",[11,673,674,675,678,679,682,683,686,687,689,690,692],{},"Duas coisas erradas aqui, e vale separar. Primeiro, um detalhe que não afeta o resultado: o ",[32,676,677],{},"get_cost"," dessa classe usa a fórmula do hinge loss (",[32,680,681],{},"max(0, 1 - y·ŷ)","), não uma fórmula de log-verossimilhança de verdade. Isso não quebra nada na prática porque ",[32,684,685],{},"get_gradient"," é a única coisa que o ",[32,688,217],{}," chama, ",[32,691,677],{}," nunca é usado durante o treino, sobrou como resíduo de copiar e colar de outra célula.",[11,694,695,696,699,700,702,703,706,707,324,709,712,713,716,717,720,721,724,725,728,729,731,732,735,736,738,739,741,742,745,746,749,750,752],{},"O segundo problema é de verdade, e explica a acurácia ruim: ",[32,697,698],{},"expit"," (a função sigmoide) devolve valores só entre ",[32,701,443],{}," e ",[32,704,705],{},"1",", mas o rótulo ",[32,708,453],{},[32,710,711],{},"-1"," ou ",[32,714,715],{},"+1",". Pra classe ",[32,718,719],{},"y=-1",", o erro ",[32,722,723],{},"y - expit(y_pred)"," ",[15,726,727],{},"nunca"," consegue chegar perto de zero, porque ",[32,730,698],{}," nunca fica negativo: mesmo com a previsão infinitamente confiante do lado certo, ",[32,733,734],{},"-1 - expit(y_pred)"," fica travado perto de ",[32,737,711],{},", nunca de ",[32,740,443],{},". Conferi isso na mão: ",[32,743,744],{},"expit(-1000) = 0.0",", então ",[32,747,748],{},"-1 - expit(-1000) = -1.0"," cravado, não ",[32,751,443],{},". O gradiente pra metade dos pontos nunca some, então o treino nunca sossega, e os pesos ficam crescendo tentando compensar um erro que é estruturalmente impossível de zerar.",[25,754,756],{"className":27,"code":755,"language":29,"meta":30,"style":30},"class LogLikehood(CostFunction):\n  @staticmethod\n  def get_gradient(X, y, y_pred):\n    return X.T @ (y - tanh(y_pred))\n\nmodel = NeuralNetwork(training_algorithm=SGD(max_iter=10000, cost_function=LogLikehood()))\n",[32,757,758,762,766,770,775,779],{"__ignoreMap":30},[35,759,760],{"class":37,"line":38},[35,761,631],{},[35,763,764],{"class":37,"line":44},[35,765,284],{},[35,767,768],{"class":37,"line":50},[35,769,266],{},[35,771,772],{"class":37,"line":56},[35,773,774],{},"    return X.T @ (y - tanh(y_pred))\n",[35,776,777],{"class":37,"line":62},[35,778,66],{"emptyLinePlaceholder":65},[35,780,781],{"class":37,"line":69},[35,782,783],{},"model = NeuralNetwork(training_algorithm=SGD(max_iter=10000, cost_function=LogLikehood()))\n",[193,785,786],{},[11,787,788,204,790,208,792,795,796,799],{},[15,789,664],{},[15,791,616],{},[32,793,794],{},"[-9.84, 12.40, 8.37]"," (com ",[32,797,798],{},"max_iter=10000",", dez vezes mais iterações).",[11,801,802,803,184,805,808,809,811,812,702,814,816,817,745,820,823,824,965,966,1018,1019,1076,1077,191],{},"Trocando ",[32,804,698],{},[32,806,807],{},"tanh"," (mesma classe redefinida, o Python deixa isso rodando ao vivo numa sessão de notebook), o problema some. Faz sentido: ",[32,810,807],{}," varia entre ",[32,813,711],{},[32,815,715],{},", exatamente a faixa dos rótulos. Conferi de novo na mão: ",[32,818,819],{},"tanh(-1000) = -1.0",[32,821,822],{},"-1 - tanh(-1000) = 0.0",", o erro consegue mesmo chegar a zero dessa vez. O Aggarwal cita essa relação direto no capítulo 1: ",[35,825,827,873],{"className":826},[407],[35,828,830],{"className":829},[411],[413,831,832],{"xmlns":415},[417,833,834,870],{},[420,835,836,838,840,842,845,847,849,852,854,858,860,862,864,866,868],{},[423,837,807],{},[427,839,435],{},[427,841,439],{"stretchy":438},[423,843,844],{},"v",[427,846,467],{"stretchy":438},[427,848,429],{},[441,850,851],{},"2",[427,853,456],{},[855,856,857],"mtext",{},"sigmoide",[427,859,439],{"stretchy":438},[441,861,851],{},[423,863,844],{},[427,865,467],{"stretchy":438},[427,867,450],{},[441,869,705],{},[469,871,872],{"encoding":471},"\\tanh(v) = 2 \\cdot \\text{sigmoide}(2v) - 1",[35,874,876,903,922,956],{"className":875,"ariaHidden":446},[476],[35,877,879,882,885,888,891,894,897,900],{"className":878},[480],[35,880],{"className":881,"style":509},[484],[35,883,807],{"className":884},[513],[35,886,439],{"className":887},[517],[35,889,844],{"className":890,"style":535},[489,490],[35,892,467],{"className":893},[612],[35,895],{"className":896,"style":495},[494],[35,898,429],{"className":899},[499],[35,901],{"className":902,"style":495},[494],[35,904,906,910,913,916,919],{"className":905},[480],[35,907],{"className":908,"style":909},[484],"height:0.6444em;",[35,911,851],{"className":912},[489],[35,914],{"className":915,"style":539},[494],[35,917,456],{"className":918},[543],[35,920],{"className":921,"style":539},[494],[35,923,925,928,935,938,941,944,947,950,953],{"className":924},[480],[35,926],{"className":927,"style":509},[484],[35,929,932],{"className":930},[489,931],"text",[35,933,857],{"className":934},[489],[35,936,439],{"className":937},[517],[35,939,851],{"className":940},[489],[35,942,844],{"className":943,"style":535},[489,490],[35,945,467],{"className":946},[612],[35,948],{"className":949,"style":539},[494],[35,951,450],{"className":952},[543],[35,954],{"className":955,"style":539},[494],[35,957,959,962],{"className":958},[480],[35,960],{"className":961,"style":909},[484],[35,963,705],{"className":964},[489],", e é exatamente esse deslocamento de faixa, de ",[35,967,969,991],{"className":968},[407],[35,970,972],{"className":971},[411],[413,973,974],{"xmlns":415},[417,975,976,988],{},[420,977,978,980,982,984,986],{},[427,979,439],{"stretchy":438},[441,981,443],{},[427,983,447],{"separator":446},[441,985,705],{},[427,987,467],{"stretchy":438},[469,989,990],{"encoding":471},"(0,1)",[35,992,994],{"className":993,"ariaHidden":446},[476],[35,995,997,1000,1003,1006,1009,1012,1015],{"className":996},[480],[35,998],{"className":999,"style":509},[484],[35,1001,439],{"className":1002},[517],[35,1004,443],{"className":1005},[489],[35,1007,447],{"className":1008},[524],[35,1010],{"className":1011,"style":528},[494],[35,1013,705],{"className":1014},[489],[35,1016,467],{"className":1017},[612]," pra ",[35,1020,1022,1046],{"className":1021},[407],[35,1023,1025],{"className":1024},[411],[413,1026,1027],{"xmlns":415},[417,1028,1029,1043],{},[420,1030,1031,1033,1035,1037,1039,1041],{},[427,1032,439],{"stretchy":438},[427,1034,450],{},[441,1036,705],{},[427,1038,447],{"separator":446},[441,1040,705],{},[427,1042,467],{"stretchy":438},[469,1044,1045],{"encoding":471},"(-1,1)",[35,1047,1049],{"className":1048,"ariaHidden":446},[476],[35,1050,1052,1055,1058,1061,1064,1067,1070,1073],{"className":1051},[480],[35,1053],{"className":1054,"style":509},[484],[35,1056,439],{"className":1057},[517],[35,1059,450],{"className":1060},[489],[35,1062,705],{"className":1063},[489],[35,1065,447],{"className":1066},[524],[35,1068],{"className":1069,"style":528},[494],[35,1071,705],{"className":1072},[489],[35,1074,467],{"className":1075},[612],", que resolve o descompasso com rótulo ",[32,1078,1079],{},"±1",[25,1081,1083],{"className":27,"code":1082,"language":29,"meta":30,"style":30},"class HingeLoss(CostFunction):\n  @staticmethod\n  def get_gradient(X, y, y_pred):\n    marginal_errors = (y * y_pred) \u003C 1\n    marginal_ys = np.copy(y)\n    marginal_ys[~marginal_errors] = 0\n    return X.T @ marginal_ys\n",[32,1084,1085,1090,1094,1098,1103,1108,1113],{"__ignoreMap":30},[35,1086,1087],{"class":37,"line":38},[35,1088,1089],{},"class HingeLoss(CostFunction):\n",[35,1091,1092],{"class":37,"line":44},[35,1093,284],{},[35,1095,1096],{"class":37,"line":50},[35,1097,266],{},[35,1099,1100],{"class":37,"line":56},[35,1101,1102],{},"    marginal_errors = (y * y_pred) \u003C 1\n",[35,1104,1105],{"class":37,"line":62},[35,1106,1107],{},"    marginal_ys = np.copy(y)\n",[35,1109,1110],{"class":37,"line":69},[35,1111,1112],{},"    marginal_ys[~marginal_errors] = 0\n",[35,1114,1115],{"class":37,"line":75},[35,1116,1117],{},"    return X.T @ marginal_ys\n",[193,1119,1120],{},[11,1121,1122,204,1124,208,1126,191],{},[15,1123,664],{},[15,1125,616],{},[32,1127,1128],{},"[-8.70, 11.59, 7.71]",[11,1130,383,1131,1134,1135,1138,1139,1142],{},[32,1132,1133],{},"marginal_ys[~marginal_errors] = 0",": pontos que já estão bem classificados, com folga (",[32,1136,1137],{},"y · ŷ ≥ 1","), são zerados e ",[15,1140,1141],{},"não contribuem nada"," pro gradiente. Só os pontos dentro da margem (ou errados) participam da atualização. Essa é literalmente a ideia central da SVM: só os pontos perto da fronteira (os \"vetores de suporte\") importam pra decidir onde ela fica. O resto do dataset é ignorado depois que já está bem separado.",[20,1144,1146],{"id":1145},"as-quatro-curvas-lado-a-lado","As quatro curvas, lado a lado",[11,1148,1149,1150,1246],{},"O professor tem uma imagem de referência guardada no notebook comparando as quatro curvas de penalidade em função da \"margem\" (",[35,1151,1153,1175],{"className":1152},[407],[35,1154,1156],{"className":1155},[411],[413,1157,1158],{"xmlns":415},[417,1159,1160,1172],{},[420,1161,1162,1164,1166],{},[423,1163,453],{},[427,1165,456],{},[458,1167,1168,1170],{"accent":446},[423,1169,453],{},[427,1171,464],{},[469,1173,1174],{"encoding":471},"y \\cdot \\hat{y}",[35,1176,1178,1197],{"className":1177,"ariaHidden":446},[476],[35,1179,1181,1185,1188,1191,1194],{"className":1180},[480],[35,1182],{"className":1183,"style":1184},[484],"height:0.6389em;vertical-align:-0.1944em;",[35,1186,453],{"className":1187,"style":535},[489,490],[35,1189],{"className":1190,"style":539},[494],[35,1192,456],{"className":1193},[543],[35,1195],{"className":1196,"style":539},[494],[35,1198,1200,1204],{"className":1199},[480],[35,1201],{"className":1202,"style":1203},[484],"height:0.8889em;vertical-align:-0.1944em;",[35,1205,1207],{"className":1206},[489,556],[35,1208,1210,1238],{"className":1209},[560,561],[35,1211,1213,1235],{"className":1212},[565],[35,1214,1216,1224],{"className":1215,"style":570},[569],[35,1217,1218,1221],{"style":573},[35,1219],{"className":1220,"style":578},[577],[35,1222,453],{"className":1223,"style":535},[489,490],[35,1225,1226,1229],{"style":573},[35,1227],{"className":1228,"style":578},[577],[35,1230,1232],{"className":1231,"style":591},[590],[35,1233,464],{"className":1234},[489],[35,1236,599],{"className":1237},[598],[35,1239,1241],{"className":1240},[565],[35,1242,1244],{"className":1243,"style":606},[569],[35,1245],{},": positivo e grande é acerto confiante, negativo é erro). Recriei a mesma ideia aqui, interativa:",[1248,1249],"margin-loss-chart",{"hinge-label":1250,"logistic-label":1251,"perceptron-label":1252,"widrow-hoff-label":1253,"x-label":1254,"y-label":1255},"Hinge (SVM)","Logística","Perceptron","Widrow-Hoff","margem (y · ŷ)","penalidade",[11,1257,1258,1259,1262,1263,1266,1267,1269,1270,1272],{},"Passa o mouse em cima de qualquer ponto do eixo x e compara as quatro. Repara nos formatos: Widrow-Hoff é uma parábola, penaliza até ponto que já acertou com folga (margem ",[32,1260,1261],{},"> 1","), porque ela não sabe que \"acerto é acerto\", só sabe medir distância até o alvo contínuo. Perceptron e Hinge são os dois únicos que ",[15,1264,1265],{},"zeram"," de vez quando o ponto está bem classificado (perceptron zera assim que passa de ",[32,1268,443],{},", hinge exige passar de ",[32,1271,705],{},", com folga). Logística nunca zera de verdade, só se aproxima de zero, o que é o preço de ela devolver uma probabilidade suave em vez de uma decisão binária.",[20,1274,1276],{"id":1275},"fechando","Fechando",[1278,1279,1280,1294],"table",{},[1281,1282,1283],"thead",{},[1284,1285,1286,1291],"tr",{},[1287,1288,1290],"th",{"align":1289},"left","O que eu já sabia",[1287,1292,1293],{"align":1289},"O que essa aula assentou",[1295,1296,1297,1316,1330],"tbody",{},[1284,1298,1299,1303],{},[1300,1301,1302],"td",{"align":1289},"Perceptron, Adaline, gradiente batch pareciam algoritmos separados",[1300,1304,1305,1306,1308,1309,1308,1312,1315],{"align":1289},"São o mesmo framework (",[32,1307,167],{}," + ",[32,1310,1311],{},"TrainingAlgorithm",[32,1313,1314],{},"CostFunction","), só trocando qual função de custo plugar",[1284,1317,1318,1321],{},[1300,1319,1320],{"align":1289},"Sigmoide devolve probabilidade entre 0 e 1",[1300,1322,1323,1324,1326,1327,1329],{"align":1289},"Usar sigmoide direto contra rótulo ",[32,1325,1079],{}," trava o gradiente, porque a faixa de saída não bate com a faixa do alvo. ",[32,1328,807],{}," resolve isso",[1284,1331,1332,1335],{},[1300,1333,1334],{"align":1289},"SVM usa \"vetores de suporte\"",[1300,1336,1337],{"align":1289},"Isso não é jargão vazio: o gradiente do hinge loss literalmente zera a contribuição de todo ponto que não é um vetor de suporte",[20,1339,1341],{"id":1340},"aplicação-prática","Aplicação Prática",[11,1343,1344,1345,1347,1348,1352,1353,1356,1357,1359],{},"Rodei as quatro funções de custo (Widrow-Hoff, critério do perceptron, hinge, e a versão log-verossimilhança com ",[32,1346,807],{},") no Iris (",[1349,1350,1351],"em",{},"setosa"," vs. ",[1349,1354,1355],{},"versicolor","), mais a versão com ",[32,1358,698],{}," de propósito, pra confirmar que o problema da sigmoide não é exclusivo do dataset sintético de brinquedo.",[1278,1361,1362,1376],{},[1281,1363,1364],{},[1284,1365,1366,1369,1373],{},[1287,1367,1368],{"align":1289},"Função de custo",[1287,1370,1372],{"align":1371},"right","Acurácia treino",[1287,1374,1375],{"align":1371},"Acurácia teste",[1295,1377,1378,1388,1398,1409,1422],{},[1284,1379,1380,1382,1384],{},[1300,1381,1253],{"align":1289},[1300,1383,616],{"align":1371},[1300,1385,1386],{"align":1371},[15,1387,616],{},[1284,1389,1390,1392,1394],{},[1300,1391,1252],{"align":1289},[1300,1393,616],{"align":1371},[1300,1395,1396],{"align":1371},[15,1397,616],{},[1284,1399,1400,1403,1405],{},[1300,1401,1402],{"align":1289},"Hinge",[1300,1404,616],{"align":1371},[1300,1406,1407],{"align":1371},[15,1408,616],{},[1284,1410,1411,1416,1418],{},[1300,1412,1413,1414,467],{"align":1289},"Log-verossimilhança (",[32,1415,807],{},[1300,1417,616],{"align":1371},[1300,1419,1420],{"align":1371},[15,1421,616],{},[1284,1423,1424,1429,1432],{},[1300,1425,1413,1426,1428],{"align":1289},[32,1427,698],{},", sigmoide)",[1300,1430,1431],{"align":1371},"0.843",[1300,1433,1434],{"align":1371},[15,1435,1436],{},"0.933",[11,1438,1439,1440,1442,1443,1445],{},"Quatro das cinco batem 100% (o Iris tem margem generosa o bastante pra qualquer uma delas achar uma fronteira perfeita), e a sigmoide de novo fica pra trás, dessa vez em dado real, não só no dataset sintético do notebook. Confirma que não foi coincidência de uma rodada: o descompasso de faixa entre ",[32,1441,698],{}," e rótulo ",[32,1444,1079],{}," prejudica a convergência de verdade, em qualquer dataset onde eu tentei.",[1447,1448,1449],"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":30,"searchDepth":44,"depth":44,"links":1451},[1452,1453,1454,1455,1456,1457],{"id":22,"depth":44,"text":23},{"id":232,"depth":44,"text":233},{"id":343,"depth":44,"text":344},{"id":1145,"depth":44,"text":1146},{"id":1275,"depth":44,"text":1276},{"id":1340,"depth":44,"text":1341},null,"2026-08-20","Aula 3a e 3b: o professor generaliza o treino pra aceitar qualquer função de custo plugável, e cada escolha (Widrow-Hoff, critério do perceptron, log-verossimilhança, hinge) recupera um algoritmo diferente dessa playlist. No meio do caminho, achei uma incompatibilidade real entre função de ativação e codificação de rótulo.","md",{},"\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo","neural-networks",{"title":6,"description":1460},"published","pt\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo",[1469,1470,1471],"funcoes-de-custo","hinge-loss","regressao-logistica","vrlcf9sVSWCNt1PSqoQuZ_AyK-_pQcbYMuR7_tC7G-c",1787338982218]