[{"data":1,"prerenderedAt":2974},["ShallowReactive",2],{"lang-switch-post-\u002F":3,"home-playlists-pt":4,"home-latest-posts-pt":196,"home-latest-timeline-pt":2895},null,[5,40,97,168],{"id":6,"title":7,"body":8,"cover":3,"description":30,"extension":31,"meta":32,"navigation":33,"order":34,"path":35,"seo":36,"status":37,"stem":38,"__hash__":39},"playlists\u002Fpt\u002Fplaylists\u002Fmachine-learning-specialization\u002Findex.md","Machine Learning Specialization (Andrew Ng)",{"type":9,"value":10,"toc":26},"minimark",[11,20,23],[12,13,14,15,19],"p",{},"Essa playlist é o meu caderno de anotações público enquanto estudo a ",[16,17,18],"strong",{},"Machine Learning Specialization",", do Andrew Ng (DeepLearning.AI \u002F Stanford), provavelmente o curso mais recomendado pra quem tá começando em ML.",[12,21,22],{},"A ideia aqui não é só \"resolver o notebook e seguir\". Cada lab do curso vira um post onde eu reconto o que entendi, com metáfora, exemplo do dia a dia, e claro, o nome técnico certo, porque isso você vai precisar quando for procurar mais coisa sobre o assunto depois.",[12,24,25],{},"Começamos pelo começo: representar o modelo mais simples que existe, a regressão linear com uma variável.",{"title":27,"searchDepth":28,"depth":28,"links":29},"",2,[],"Meus estudos passo a passo pela especialização de Machine Learning do Andrew Ng (DeepLearning.AI \u002F Stanford), curso por curso, lab por lab.","md",{},true,1,"\u002Fpt\u002Fplaylists\u002Fmachine-learning-specialization",{"title":7,"description":30},"published","pt\u002Fplaylists\u002Fmachine-learning-specialization\u002Findex","E9Okc28mL6OITzVoZv3DkKW9sSyof7S1q6TRo9ovEso",{"id":41,"title":42,"body":43,"cover":3,"description":91,"extension":31,"meta":92,"navigation":33,"order":28,"path":93,"seo":94,"status":37,"stem":95,"__hash__":96},"playlists\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Findex.md","Reconhecimento de Padrões",{"type":9,"value":44,"toc":89},[45,58,71,81],[12,46,47,48,51,52,57],{},"Essa playlist é o caderno de anotações da matéria de Reconhecimento de Padrões que eu cursei, dada pelo ",[16,49,50],{},"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 ",[53,54,56],"a",{"href":55},"\u002Fplaylists\u002Fneural-networks","redes neurais também",". Se você caiu aqui vindo de uma aula dele, já sabe do que eu tô falando.",[12,59,60,61,65,66,70],{},"Diferente da ",[53,62,64],{"href":63},"\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 ",[67,68,69],"em",{},"Pattern Recognition and Machine Learning",", do Christopher Bishop (2006), praticamente uma bíblia da área.",[12,72,73,74,80],{},"Os notebooks vêm do repositório da matéria, ",[53,75,79],{"href":76,"rel":77},"https:\u002F\u002Fgithub.com\u002Fpablobelmiro\u002Faulasml\u002Ftree\u002F2026-1",[78],"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.",[12,82,83,84,88],{},"Cada aula da matéria vira um post aqui. Começamos pelo começo: o mesmo problema de ",[53,85,87],{"href":86},"\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":27,"searchDepth":28,"depth":28,"links":90},[],"Minhas anotações da disciplina de Reconhecimento de Padrões, aula por aula, com o livro do Bishop como base teórica.",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition",{"title":42,"description":91},"pt\u002Fplaylists\u002Fpattern-recognition\u002Findex","XpBRgaSNS0YRRtovdqN7u3MdAmbF-PKEJQoOiyOjl-M",{"id":98,"title":99,"body":100,"cover":3,"description":161,"extension":31,"meta":162,"navigation":33,"order":163,"path":164,"seo":165,"status":37,"stem":166,"__hash__":167},"playlists\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Findex.md","Redes Neurais",{"type":9,"value":101,"toc":159},[102,112,129,143],[12,103,104,105,107,108,111],{},"Segunda playlist com o ",[16,106,50],{}," (a primeira foi ",[53,109,42],{"href":110},"\u002Fplaylists\u002Fpattern-recognition","), agora na disciplina de Redes Neurais. Mesmo estilo de sempre: código na mão, ao vivo, na aula, sem slide de fórmula pronta. Se você já leu a playlist anterior, sabe exatamente o que esperar.",[12,113,114,115,118,119,123,124,128],{},"O livro de referência muda: aqui eu uso ",[67,116,117],{},"Neural Networks and Deep Learning: A Textbook",", do Charu Aggarwal (2018), pra fazer o papel que o Bishop fazia na playlist passada, encher de fundamento o que o notebook só mostra em código. Boa parte do assunto também conecta direto com o que eu já vi antes: ",[53,120,122],{"href":121},"\u002Fplaylists\u002Fpattern-recognition\u002Flinear-regression-estimator","regra delta e regressão linear já apareceram lá no Reconhecimento de Padrões",", e ",[53,125,127],{"href":126},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Floss-functions","funções de custo já apareceram na especialização do Andrew Ng",", então sempre que der eu vou puxar esses fios em vez de reensinar do zero.",[12,130,73,131,136,137,142],{},[53,132,135],{"href":133,"rel":134},"https:\u002F\u002Fgithub.com\u002Fpablobelmiro\u002Faulasann",[78],"pablobelmiro\u002Faulasann",", um fork do repositório original do próprio Dr. Boldt, ",[53,138,141],{"href":139,"rel":140},"https:\u002F\u002Fgithub.com\u002Ffboldt\u002Faulasann",[78],"fboldt\u002Faulasann",", onde ele publica o código de cada aula. Mesma convenção da playlist anterior: quem \"faz\" o código é sempre ele, o professor; a explicação de fundamento é minha.",[12,144,145,146,149,150,153,154,158],{},"Um detalhe importante dessa vez: essa disciplina está sendo dada ",[16,147,148],{},"agora",", ao vivo, e o repositório só tem o começo do curso até este momento (perceptron, Adaline, funções de custo, e um gancho bem no limite do que um único neurônio consegue resolver). Diferente da playlist de Reconhecimento de Padrões, que eu comecei só depois que a matéria inteira já tinha acabado, essa aqui é uma ",[16,151,152],{},"playlist viva",": cresce toda vez que o professor publica uma aula nova, e eu volto pra continuar. Começamos pelo começo de tudo: ",[53,155,157],{"href":156},"\u002Fplaylists\u002Fneural-networks\u002Fmcculloch-pitts-perceptron","o neurônio mais simples que existe",".",{"title":27,"searchDepth":28,"depth":28,"links":160},[],"Minhas anotações da disciplina de Redes Neurais, aula por aula, com o livro do Aggarwal como base teórica. Playlist viva, crescendo junto com o curso.",{},3,"\u002Fpt\u002Fplaylists\u002Fneural-networks",{"title":99,"description":161},"pt\u002Fplaylists\u002Fneural-networks\u002Findex","E7qSDm0PDSKcEZo03_C5IczMHfp2NxtLtMpDImcPtBc",{"id":169,"title":170,"body":171,"cover":3,"description":189,"extension":31,"meta":190,"navigation":33,"order":191,"path":192,"seo":193,"status":37,"stem":194,"__hash__":195},"playlists\u002Fpt\u002Fplaylists\u002Fpapers\u002Findex.md","Artigos",{"type":9,"value":172,"toc":187},[173,176,179],[12,174,175],{},"As outras três playlists daqui são estudo de verdade: notebook de aula, livro-texto do lado, cada linha de código esmiuçada até doer. Essa aqui é outra vibe. De vez em quando eu leio um artigo ou survey que me deixa empolgado, geralmente na diagonal, sem ler cada página com lupa, e queria um lugar pra falar sobre ele sem toda a cerimônia de uma aula.",[12,177,178],{},"É isso que essa playlist é: eu conto o que achei mais maneiro do artigo, com metáfora torta, uma zoeira aqui e ali, e sempre que der, um código Python simples e um gráfico só pra dar aquela visão. Não espera rigor de notebook nem cobertura seção-por-seção, o objetivo aqui é papo reto sobre uma ideia interessante, não um resumo acadêmico.",[12,180,181,182,186],{},"Primeiro da série é sobre um assunto que não dá pra fugir hoje em dia: ",[53,183,185],{"href":184},"\u002Fplaylists\u002Fpapers\u002Fagentic-reasoning-for-large-language-models","raciocínio agêntico em modelos de linguagem",", um survey gigante que tenta organizar tudo que virou moda chamar de \"agente de IA\".",{"title":27,"searchDepth":28,"depth":28,"links":188},[],"Leitura diagonal de artigos e surveys que eu achei maneiros, sem a pretensão de virar aula. Código simples, gráfico pra dar uma visão, e a conversa de sempre com você.",{},4,"\u002Fpt\u002Fplaylists\u002Fpapers",{"title":170,"description":189},"pt\u002Fplaylists\u002Fpapers\u002Findex","vdbQsWX-nWs5oIGxtPjwUJoPdxwWZJza7ld2fNeGb0o",[197,897,2292],{"id":198,"title":199,"body":200,"cover":3,"date":885,"description":886,"extension":31,"meta":887,"navigation":33,"order":191,"path":888,"playlist":889,"seo":890,"status":37,"stem":891,"tags":892,"__hash__":896},"posts\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Fadaline-regra-delta.md","Adaline: Treinar na Reta, Classificar no Sinal",{"type":9,"value":201,"toc":877},[202,216,221,281,314,330,337,395,419,433,462,466,480,491,494,498,545,565,581,585,620,640,654,661,705,717,721,766,770,781,796,819,862,873],[12,203,204,205,208,209,212,213,158],{},"Aula 2e e 2f, e o nome do modelo muda pra ",[16,206,207],{},"Adaline"," (ADAptive LInear NEuron), de Bernard Widrow e Ted Hoff, 1960, só dois anos depois do Rosenblatt. A ideia central deles é sutil, mas separa duas coisas que até aqui eu tratava como uma só: ",[16,210,211],{},"o que o modelo otimiza durante o treino"," e ",[16,214,215],{},"o que ele calcula na hora de prever",[217,218,220],"h2",{"id":219},"pré-ativação-e-pós-ativação-a-distinção-que-o-adaline-introduz","Pré-ativação e pós-ativação: a distinção que o Adaline introduz",[222,223,227],"pre",{"className":224,"code":225,"language":226,"meta":27,"style":27},"language-python shiki shiki-themes github-light github-dark","class AdalinePseudoInverse(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\n  def predict(self, X):\n    X = include_bias(X)\n    return X @ self.w_\n","python",[228,229,230,237,242,247,252,258,264,270,275],"code",{"__ignoreMap":27},[231,232,234],"span",{"class":233,"line":34},"line",[231,235,236],{},"class AdalinePseudoInverse(BaseEstimator, ClassifierMixin):\n",[231,238,239],{"class":233,"line":28},[231,240,241],{},"  def fit(self, X, y):\n",[231,243,244],{"class":233,"line":163},[231,245,246],{},"    X = include_bias(X)\n",[231,248,249],{"class":233,"line":191},[231,250,251],{},"    self.w_ = np.linalg.pinv(X) @ y\n",[231,253,255],{"class":233,"line":254},5,[231,256,257],{},"    return self\n",[231,259,261],{"class":233,"line":260},6,[231,262,263],{"emptyLinePlaceholder":33},"\n",[231,265,267],{"class":233,"line":266},7,[231,268,269],{},"  def predict(self, X):\n",[231,271,273],{"class":233,"line":272},8,[231,274,246],{},[231,276,278],{"class":233,"line":277},9,[231,279,280],{},"    return X @ self.w_\n",[12,282,283,284,287,288,292,293,296,297,300,301,304,305,307,308,310,311,158],{},"Repara: isso é ",[16,285,286],{},"exatamente"," ",[53,289,291],{"href":290},"\u002Fplaylists\u002Fneural-networks\u002Fregressao-e-equacao-normal","a equação normal do post passado",", sem mudar uma linha, só que agora ",[228,294,295],{},"y"," é ",[228,298,299],{},"-1"," ou ",[228,302,303],{},"+1"," em vez de contínuo. O professor está tratando classificação como se fosse regressão: ajustando a reta pra chegar o mais perto possível de ",[228,306,299],{}," nos pontos de uma classe e ",[228,309,303],{}," na outra, sem nunca aplicar ",[228,312,313],{},"sign()",[315,316,317],"blockquote",{},[12,318,319,322,323,326,327,158],{},[16,320,321],{},"Saída:"," RMSE ",[228,324,325],{},"0.4148",", pesos ",[228,328,329],{},"[-1.76, 1.01, 2.74]",[12,331,332,333,336],{},"RMSE faz sentido aqui porque ",[228,334,335],{},"predict"," devolve um número contínuo, não uma classe. Só que pra classificar de verdade, falta o último passo:",[222,338,340],{"className":224,"code":339,"language":226,"meta":27,"style":27},"class AdalinePseudoInverse(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\n  def pre_activation(self, X):\n    X = include_bias(X)\n    return X @ self.w_\n\n  def predict(self, X):\n    return np.sign(self.pre_activation(X))\n",[228,341,342,346,350,354,358,362,366,371,375,379,384,389],{"__ignoreMap":27},[231,343,344],{"class":233,"line":34},[231,345,236],{},[231,347,348],{"class":233,"line":28},[231,349,241],{},[231,351,352],{"class":233,"line":163},[231,353,246],{},[231,355,356],{"class":233,"line":191},[231,357,251],{},[231,359,360],{"class":233,"line":254},[231,361,257],{},[231,363,364],{"class":233,"line":260},[231,365,263],{"emptyLinePlaceholder":33},[231,367,368],{"class":233,"line":266},[231,369,370],{},"  def pre_activation(self, X):\n",[231,372,373],{"class":233,"line":272},[231,374,246],{},[231,376,377],{"class":233,"line":277},[231,378,280],{},[231,380,382],{"class":233,"line":381},10,[231,383,263],{"emptyLinePlaceholder":33},[231,385,387],{"class":233,"line":386},11,[231,388,269],{},[231,390,392],{"class":233,"line":391},12,[231,393,394],{},"    return np.sign(self.pre_activation(X))\n",[12,396,397,398,401,402,404,405,407,408,212,411,414,415,418],{},"Agora tem dois métodos: ",[228,399,400],{},"pre_activation"," (o valor contínuo, antes de qualquer limiar) e ",[228,403,335],{}," (aplica ",[228,406,313],{}," em cima). O Aggarwal chama exatamente essas duas coisas de ",[16,409,410],{},"valor de pré-ativação",[16,412,413],{},"valor de pós-ativação"," (capítulo 1): tudo que um neurônio calcula acontece em duas etapas, primeiro a soma ponderada, depois a função de ativação em cima dela. O Adaline treina em cima da pré-ativação (ela é contínua, então dá pra medir \"quão longe\" cada previsão ficou do alvo) e só aplica a ativação (",[228,416,417],{},"sign",") na hora de decidir a classe final.",[315,420,421],{},[12,422,423,425,426,429,430,432],{},[16,424,321],{}," acurácia ",[228,427,428],{},"1.0",", os mesmos pesos ",[228,431,329],{}," de antes (é a mesma conta, só que agora avaliada por acurácia em vez de RMSE).",[12,434,435,436,123,439,442,443,446,447,450,451,453,454,457,458,461],{},"Isso é a ",[16,437,438],{},"regra delta",[53,440,441],{"href":121},"eu já vi ela antes, com esse nome exato",": \"atualizar o peso proporcionalmente ao erro vezes a entrada\" é a assinatura do Widrow-Hoff. A diferença pro perceptron do Rosenblatt (que eu vi ",[53,444,445],{"href":156},"duas aulas atrás",") é justamente essa: o perceptron mede o erro ",[16,448,449],{},"depois"," de aplicar ",[228,452,313],{}," (erro em ",[228,455,456],{},"{-2,0,+2}","), o Adaline mede o erro ",[16,459,460],{},"antes",", na pré-ativação contínua. Isso parece um detalhe pequeno, mas muda tudo: erro contínuo dá um gradiente de verdade, suave, que aponta pra melhor direção mesmo quando a previsão já está do lado certo mas ainda meio \"insegura\". Já o erro binário do perceptron só liga quando classifica errado, sem noção de \"quão errado\".",[217,463,465],{"id":464},"interativo-mexendo-na-pré-ativação-e-vendo-o-rmse-e-a-acurácia-mudarem","Interativo: mexendo na pré-ativação e vendo o RMSE (e a acurácia) mudarem",[12,467,468,469,472,473,212,476,479],{},"Em vez de treinar automaticamente, arrasta os sliders de ",[228,470,471],{},"w0",", ",[228,474,475],{},"w1",[228,477,478],{},"bias"," na mão e observa duas leituras ao mesmo tempo: o RMSE (contínuo, muda suavemente a cada arrastão) e a acurácia (discreta, só pula quando um ponto atravessa a fronteira de decisão).",[481,482],"adaline-explorer",{":classes":483,":points":484,":x-max":485,":x-min":486,":y-max":485,":y-min":486,"negative-label":487,"positive-label":488,"x-label":489,"y-label":490},"[-1, 1, -1, -1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1]","[[0.0856, 0.2368], [0.8013, 0.5822], [0.0941, 0.4331], [0.4791, 0.1597], [0.7346, 0.1137], [0.3912, 0.5167], [0.4306, 0.5868], [0.7378, 0.9563], [0.2842, 0.6485], [0.6962, 0.2927], [0.0015, 0.9735], [0.2984, 0.314], [0.8917, 0.5852], [0.4713, 0.7733], [0.0303, 0.707], [0.3742, 0.0909], [0.6605, 0.9315], [0.2072, 0.6301], [0.2982, 0.7418], [0.7222, 0.2187]]","1","0","classe -1","classe +1","x0","x1",[12,492,493],{},"Repara que o RMSE quase sempre continua mudando um pouquinho mesmo depois que a acurácia já bateu 100%: dá pra empurrar a fronteira mais fundo pro meio do vazio entre as duas classes (RMSE cai mais) sem ganhar nem perder nenhum ponto (acurácia parada). É exatamente essa diferença que separa \"achar uma reta que separa\" (o que o perceptron faz) de \"achar a reta que separa com folga\" (o que dá pra melhorar otimizando RMSE em vez de só contar erro).",[217,495,497],{"id":496},"aula-2f-a-mesma-conta-só-que-iterando-e-o-notebook-chama-isso-de-sgd","Aula 2f: a mesma conta, só que iterando (e o notebook chama isso de \"SGD\")",[222,499,501],{"className":224,"code":500,"language":226,"meta":27,"style":27},"class Adaline(BaseEstimator, ClassifierMixin):\n  def fit(self, X, y):\n    X = include_bias(X)\n    self.w_ = np.zeros(X.shape[1])\n    for _ in range(self.max_iter):\n      y_pred = X @ self.w_\n      error = y - y_pred\n      self.w_ += self.learning_rate * error @ X\n    return self\n",[228,502,503,508,512,516,521,526,531,536,541],{"__ignoreMap":27},[231,504,505],{"class":233,"line":34},[231,506,507],{},"class Adaline(BaseEstimator, ClassifierMixin):\n",[231,509,510],{"class":233,"line":28},[231,511,241],{},[231,513,514],{"class":233,"line":163},[231,515,246],{},[231,517,518],{"class":233,"line":191},[231,519,520],{},"    self.w_ = np.zeros(X.shape[1])\n",[231,522,523],{"class":233,"line":254},[231,524,525],{},"    for _ in range(self.max_iter):\n",[231,527,528],{"class":233,"line":260},[231,529,530],{},"      y_pred = X @ self.w_\n",[231,532,533],{"class":233,"line":266},[231,534,535],{},"      error = y - y_pred\n",[231,537,538],{"class":233,"line":272},[231,539,540],{},"      self.w_ += self.learning_rate * error @ X\n",[231,542,543],{"class":233,"line":277},[231,544,257],{},[12,546,547,548,551,552,555,556,564],{},"Um detalhe honesto sobre o nome do notebook (",[228,549,550],{},"aula02f adaline with SGD","): o código mostrado aqui é ",[16,553,554],{},"gradiente em lote"," (",[53,557,559,560,563],{"href":558},"\u002Fplaylists\u002Fneural-networks\u002Fperceptron-com-bias","o mesmo ",[228,561,562],{},"X.T @ error"," de sempre","), calculando o erro em cima do dataset inteiro a cada iteração, não SGD de verdade (que atualizaria um exemplo por vez, em ordem embaralhada). É um jeito comum de falar informalmente (\"é tipo gradiente descendente, então chamo de SGD\"), mas vale registrar a diferença técnica, já que os nomes têm significado preciso.",[315,566,567],{},[12,568,569,425,571,573,574,577,578,158],{},[16,570,321],{},[228,572,428],{}," no treino, pesos ",[228,575,576],{},"[-2.92, 3.32, 2.63]",". Testando em 1000 pontos novos: acurácia ",[16,579,580],{},"0.953",[217,582,584],{"id":583},"o-dataset-ruim-o-que-ele-realmente-prova","O dataset \"ruim\": o que ele realmente prova",[222,586,588],{"className":224,"code":587,"language":226,"meta":27,"style":27},"X_bad = np.concatenate((X,np.ones_like(X)))\ny_bad = np.concatenate((y,np.ones_like(y)))\nX_bad = np.concatenate((X_bad,np.ones_like(X)))\ny_bad = np.concatenate((y_bad,np.ones_like(y)))\nclf_bad = Adaline()\nclf_bad.fit(X_bad, y_bad)\n",[228,589,590,595,600,605,610,615],{"__ignoreMap":27},[231,591,592],{"class":233,"line":34},[231,593,594],{},"X_bad = np.concatenate((X,np.ones_like(X)))\n",[231,596,597],{"class":233,"line":28},[231,598,599],{},"y_bad = np.concatenate((y,np.ones_like(y)))\n",[231,601,602],{"class":233,"line":163},[231,603,604],{},"X_bad = np.concatenate((X_bad,np.ones_like(X)))\n",[231,606,607],{"class":233,"line":191},[231,608,609],{},"y_bad = np.concatenate((y_bad,np.ones_like(y)))\n",[231,611,612],{"class":233,"line":254},[231,613,614],{},"clf_bad = Adaline()\n",[231,616,617],{"class":233,"line":260},[231,618,619],{},"clf_bad.fit(X_bad, y_bad)\n",[12,621,622,623,626,627,630,631,633,634,636,637,639],{},"O professor concatena o dataset original com ",[16,624,625],{},"dois blocos extras"," de pontos artificiais: todo mundo em ",[228,628,629],{},"(1,1)",", todo mundo rotulado ",[228,632,303],{},". Isso não é ruído, é um enviesamento deliberado, empurrando o treino a \"acreditar\" que a região perto de ",[228,635,629],{}," é ainda mais fortemente classe ",[228,638,303],{}," do que ela realmente é.",[315,641,642],{},[12,643,644,425,646,649,650,653],{},[16,645,321],{},[228,647,648],{},"0.967"," no treino (sobre o dataset enviesado), mas só ",[16,651,652],{},"0.811"," nos mesmos 1000 pontos de teste limpos de antes.",[12,655,656,657,660],{},"Caiu de 0.953 pra 0.811. Antes de escrever esse post, eu tinha a hipótese de que essa célula ia mostrar a equação normal (pseudo-inversa) quebrando nesse dataset problemático, e o SGD aguentando melhor. Reproduzi o experimento eu mesmo, comparando pseudo-inversa contra o gradiente em lote ",[16,658,659],{},"no mesmo dataset enviesado",", e a hipótese não se sustentou:",[662,663,664,682],"table",{},[665,666,667],"thead",{},[668,669,670,675,679],"tr",{},[671,672,674],"th",{"align":673},"left","Método",[671,676,678],{"align":677},"right","Acurácia treino (enviesado)",[671,680,681],{"align":677},"Acurácia teste (limpo)",[683,684,685,696],"tbody",{},[668,686,687,691,693],{},[688,689,690],"td",{"align":673},"Pseudo-inversa",[688,692,648],{"align":677},[688,694,695],{"align":677},"0.783",[668,697,698,701,703],{},[688,699,700],{"align":673},"Gradiente em lote",[688,702,648],{"align":677},[688,704,695],{"align":677},[12,706,707,708,711,712,716],{},"Os dois caem exatamente igual, com pesos praticamente idênticos entre si. A lição de verdade dessa célula não é sobre qual algoritmo de treino é mais robusto, é sobre ",[16,709,710],{},"qualidade do dado de treino",": enviesar a distribuição do treino (mesmo sem adicionar \"ruído\" no sentido de erro aleatório) desloca a fronteira aprendida pra um lugar que não representa mais a distribuição real, e isso prejudica os dois métodos igualmente, porque os dois estão resolvendo o mesmíssimo problema de otimização por baixo dos panos. ",[53,713,715],{"href":714},"\u002Fplaylists\u002Fpattern-recognition\u002Fcredit-card-fraud","Isso ecoa uma lição que já vi na outra playlist",": treino com distribuição diferente do mundo real é um problema de dado, não de algoritmo.",[217,718,720],{"id":719},"fechando","Fechando",[662,722,723,733],{},[665,724,725],{},[668,726,727,730],{},[671,728,729],{"align":673},"O que eu já sabia",[671,731,732],{"align":673},"O que essa aula assentou",[683,734,735,746,758],{},[668,736,737,740],{},[688,738,739],{"align":673},"Perceptron classifica e atualiza pelo erro binário",[688,741,742,743,745],{"align":673},"Adaline separa pré-ativação (contínua, usada no treino) de pós-ativação (",[228,744,417],{},", usada só na hora de decidir a classe)",[668,747,748,751],{},[688,749,750],{"align":673},"Regra delta já apareceu na outra playlist",[688,752,753,754,757],{"align":673},"Adaline ",[16,755,756],{},"é"," a regra delta aplicada à classificação, treinando como se fosse regressão nos rótulos ±1",[668,759,760,763],{},[688,761,762],{"align":673},"Achar uma reta que separa parece suficiente",[688,764,765],{"align":673},"Otimizar RMSE (não só contar erro) continua achando fronteira melhor mesmo depois da acurácia bater 100%",[217,767,769],{"id":768},"aplicação-prática","Aplicação Prática",[12,771,772,773,776,777,780],{},"Reproduzi pseudo-inversa vs. gradiente em lote no Iris (",[67,774,775],{},"setosa"," vs. ",[67,778,779],{},"versicolor","), a essa altura já um velho conhecido dessa playlist, mas dessa vez treinando como Adaline, no alvo contínuo ±1, não como perceptron.",[222,782,784],{"className":224,"code":783,"language":226,"meta":27,"style":27},"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)\nw_pinv = np.linalg.pinv(include_bias(X_train)) @ y_train\n",[228,785,786,791],{"__ignoreMap":27},[231,787,788],{"class":233,"line":34},[231,789,790],{},"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)\n",[231,792,793],{"class":233,"line":28},[231,794,795],{},"w_pinv = np.linalg.pinv(include_bias(X_train)) @ y_train\n",[12,797,798,799,802,803,806,807,810,811,814,815,818],{},"Na primeira tentativa, usei o mesmo ",[228,800,801],{},"learning_rate=0.01"," do notebook original pro gradiente em lote, e ele ",[16,804,805],{},"divergiu"," (peso virou ",[228,808,809],{},"NaN",") direto no dado cru do Iris. Nada de surpresa nessa altura: ",[53,812,813],{"href":290},"é a mesma lição de escala do post da equação normal",", só reencontrada de novo, dessa vez precisando de uma taxa bem menor (",[228,816,817],{},"0.001",") pra não explodir.",[662,820,821,833],{},[665,822,823],{},[668,824,825,827,830],{},[671,826,674],{"align":673},[671,828,829],{"align":677},"RMSE (treino)",[671,831,832],{"align":677},"Acurácia (teste)",[683,834,835,847],{},[668,836,837,840,843],{},[688,838,839],{"align":673},"Pseudo-inversa (Adaline)",[688,841,842],{"align":677},"0.2414",[688,844,845],{"align":677},[16,846,428],{},[668,848,849,856,858],{},[688,850,851,852,855],{"align":673},"Gradiente em lote, ",[228,853,854],{},"learning_rate=0.001"," (Adaline)",[688,857,842],{"align":677},[688,859,860],{"align":677},[16,861,428],{},[12,863,864,865,868,869,872],{},"Com a taxa ajustada, mesmo resultado dos dois métodos de novo, RMSE idêntico até a quarta casa decimal, e as duas acurácias batendo 100%, ",[53,866,867],{"href":558},"a mesma margem generosa do Iris que já favoreceu o perceptron com bias",". A diferença real entre os métodos, nesse dataset fácil, continua sendo só velocidade de convergência (e sensibilidade à escolha de ",[228,870,871],{},"learning_rate","), não qualidade da solução final.",[874,875,876],"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":27,"searchDepth":28,"depth":28,"links":878},[879,880,881,882,883,884],{"id":219,"depth":28,"text":220},{"id":464,"depth":28,"text":465},{"id":496,"depth":28,"text":497},{"id":583,"depth":28,"text":584},{"id":719,"depth":28,"text":720},{"id":768,"depth":28,"text":769},"2026-08-20","Aula 2e e 2f: o professor treina o Adaline ajustando uma reta contínua nos rótulos ±1 (em vez de ajustar direto no sinal), e eu conecto isso com a regra delta que já apareceu na outra playlist. No fim, um dataset artificialmente enviesado revela que o problema nunca foi o algoritmo de treino.",{},"\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Fadaline-regra-delta","neural-networks",{"title":199,"description":886},"pt\u002Fplaylists\u002Fneural-networks\u002Fadaline-regra-delta",[893,894,895],"adaline","regra-delta","widrow-hoff","cbghfvPwR6a9QLRUcV5MuER7WPdR2fYLxW2Wn5ttVUs",{"id":898,"title":899,"body":900,"cover":3,"date":885,"description":2282,"extension":31,"meta":2283,"navigation":33,"order":254,"path":2284,"playlist":889,"seo":2285,"status":37,"stem":2286,"tags":2287,"__hash__":2291},"posts\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo.md","Trocando a Função de Custo Como Quem Troca de Roupa",{"type":9,"value":901,"toc":2274},[902,909,913,1022,1055,1081,1089,1093,1166,1198,1202,1238,1473,1509,1522,1543,1597,1628,1644,1924,1962,1973,1987,1991,2091,2100,2117,2119,2171,2173,2187,2263,2272],[12,903,904,905,908],{},"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 ",[16,906,907],{},"mesma receita",", só trocando um ingrediente: a função de custo.",[217,910,912],{"id":911},"primeiro-o-algoritmo-de-treino-vira-plugável","Primeiro, o algoritmo de treino vira plugável",[222,914,916],{"className":224,"code":915,"language":226,"meta":27,"style":27},"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",[228,917,918,923,928,933,938,942,947,951,956,960,965,970,975,980,985,990,996,1001,1006,1011,1016],{"__ignoreMap":27},[231,919,920],{"class":233,"line":34},[231,921,922],{},"class TrainingAlgorithm(ABC):\n",[231,924,925],{"class":233,"line":28},[231,926,927],{},"  @abstractmethod\n",[231,929,930],{"class":233,"line":163},[231,931,932],{},"  def get_w(self, X, y):\n",[231,934,935],{"class":233,"line":191},[231,936,937],{},"    pass\n",[231,939,940],{"class":233,"line":254},[231,941,263],{"emptyLinePlaceholder":33},[231,943,944],{"class":233,"line":260},[231,945,946],{},"class PseudoInverse(TrainingAlgorithm):\n",[231,948,949],{"class":233,"line":266},[231,950,932],{},[231,952,953],{"class":233,"line":272},[231,954,955],{},"    return np.linalg.pinv(X) @ y\n",[231,957,958],{"class":233,"line":277},[231,959,263],{"emptyLinePlaceholder":33},[231,961,962],{"class":233,"line":381},[231,963,964],{},"class NeuralNetwork(BaseEstimator, ClassifierMixin):\n",[231,966,967],{"class":233,"line":386},[231,968,969],{},"  def __init__(self, training_algorithm=PseudoInverse()):\n",[231,971,972],{"class":233,"line":391},[231,973,974],{},"    self.training_algorithm = training_algorithm\n",[231,976,978],{"class":233,"line":977},13,[231,979,263],{"emptyLinePlaceholder":33},[231,981,983],{"class":233,"line":982},14,[231,984,241],{},[231,986,988],{"class":233,"line":987},15,[231,989,246],{},[231,991,993],{"class":233,"line":992},16,[231,994,995],{},"    self.w_ = self.training_algorithm.get_w(X, y)\n",[231,997,999],{"class":233,"line":998},17,[231,1000,257],{},[231,1002,1004],{"class":233,"line":1003},18,[231,1005,263],{"emptyLinePlaceholder":33},[231,1007,1009],{"class":233,"line":1008},19,[231,1010,269],{},[231,1012,1014],{"class":233,"line":1013},20,[231,1015,246],{},[231,1017,1019],{"class":233,"line":1018},21,[231,1020,1021],{},"    return np.sign(X @ self.w_)\n",[12,1023,1024,1025,1028,1029,1032,1033,1036,1037,1040,1041,1044,1045,1048,1049,1052,1053,158],{},"Isso é o padrão de projeto ",[16,1026,1027],{},"Strategy",": ",[228,1030,1031],{},"NeuralNetwork"," não sabe mais ",[16,1034,1035],{},"como"," os pesos são calculados, só que existe um objeto ",[228,1038,1039],{},"training_algorithm"," com um método ",[228,1042,1043],{},"get_w",". Trocar ",[228,1046,1047],{},"PseudoInverse()"," por ",[228,1050,1051],{},"SGD()"," no construtor troca o algoritmo de treino inteiro, sem tocar em ",[228,1054,1031],{},[315,1056,1057],{},[12,1058,1059,425,1066,326,1069,1072,1073,425,1078,1080],{},[16,1060,1061,1062,1065],{},"Saída (",[228,1063,1064],{},"PseudoInverse","):",[228,1067,1068],{},"0.95",[228,1070,1071],{},"[-1.905, 2.656, 1.049]",".\n",[16,1074,1061,1075,1065],{},[228,1076,1077],{},"SGD",[228,1079,1068],{},", pesos praticamente idênticos.",[12,1082,1083,1084,1088],{},"Confirma de novo, agora com o código organizado de um jeito mais limpo, o que ",[53,1085,1087],{"href":1086},"\u002Fplaylists\u002Fneural-networks\u002Fadaline-regra-delta","já vi nos dois posts passados",": pseudo-inversa e gradiente descendente resolvem o mesmíssimo problema.",[217,1090,1092],{"id":1091},"agora-a-função-de-custo-também-vira-plugável","Agora a função de custo também vira plugável",[222,1094,1096],{"className":224,"code":1095,"language":226,"meta":27,"style":27},"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",[228,1097,1098,1103,1108,1113,1117,1121,1126,1130,1134,1139,1144,1148,1153,1157,1161],{"__ignoreMap":27},[231,1099,1100],{"class":233,"line":34},[231,1101,1102],{},"class CostFunction(ABC):\n",[231,1104,1105],{"class":233,"line":28},[231,1106,1107],{},"  @abstractstaticmethod\n",[231,1109,1110],{"class":233,"line":163},[231,1111,1112],{},"  def get_cost(y, y_pred):\n",[231,1114,1115],{"class":233,"line":191},[231,1116,937],{},[231,1118,1119],{"class":233,"line":254},[231,1120,1107],{},[231,1122,1123],{"class":233,"line":260},[231,1124,1125],{},"  def get_gradient(X, y, y_pred):\n",[231,1127,1128],{"class":233,"line":266},[231,1129,937],{},[231,1131,1132],{"class":233,"line":272},[231,1133,263],{"emptyLinePlaceholder":33},[231,1135,1136],{"class":233,"line":277},[231,1137,1138],{},"class WidrowHoff(CostFunction):\n",[231,1140,1141],{"class":233,"line":381},[231,1142,1143],{},"  @staticmethod\n",[231,1145,1146],{"class":233,"line":386},[231,1147,1112],{},[231,1149,1150],{"class":233,"line":391},[231,1151,1152],{},"    return np.mean((y-y_pred)**2)\n",[231,1154,1155],{"class":233,"line":977},[231,1156,1143],{},[231,1158,1159],{"class":233,"line":982},[231,1160,1125],{},[231,1162,1163],{"class":233,"line":987},[231,1164,1165],{},"    return X.T @ (y-y_pred)\n",[12,1167,1168,1169,1171,1172,1175,1176,1179,1180,296,1183,1186,1187,1190,1191,1193,1194,1197],{},"O ",[228,1170,1077],{}," agora recebe um ",[228,1173,1174],{},"cost_function"," também, e usa ",[228,1177,1178],{},"self.cost_function.get_gradient(...)"," em vez de calcular o gradiente na mão. ",[228,1181,1182],{},"WidrowHoff",[53,1184,1185],{"href":1086},"exatamente a regra delta que eu vi na aula passada",": erro contínuo (",[228,1188,1189],{},"y - y_pred",", sem ",[228,1192,313],{},") vezes a entrada. Trocar a função de custo aqui é trocar ",[16,1195,1196],{},"o que \"erro\" significa",", sem tocar no laço de treino.",[217,1199,1201],{"id":1200},"cada-função-de-custo-recupera-um-algoritmo-diferente","Cada função de custo recupera um algoritmo diferente",[222,1203,1205],{"className":224,"code":1204,"language":226,"meta":27,"style":27},"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",[228,1206,1207,1212,1216,1220,1225,1229,1233],{"__ignoreMap":27},[231,1208,1209],{"class":233,"line":34},[231,1210,1211],{},"class SmoothedSurrogate(CostFunction):\n",[231,1213,1214],{"class":233,"line":28},[231,1215,1143],{},[231,1217,1218],{"class":233,"line":163},[231,1219,1112],{},[231,1221,1222],{"class":233,"line":191},[231,1223,1224],{},"    return np.sum(np.maximum(np.zeros(y.shape), -y * y_pred))\n",[231,1226,1227],{"class":233,"line":254},[231,1228,1143],{},[231,1230,1231],{"class":233,"line":260},[231,1232,1125],{},[231,1234,1235],{"class":233,"line":266},[231,1236,1237],{},"    return X.T @ (y - np.sign(y_pred))\n",[12,1239,1240,1241,1244,1245,1247,1248,1251,1252,1255,1256,1028,1259,1466,1467,1469,1470,1472],{},"Repara no ",[228,1242,1243],{},"np.sign(y_pred)"," dentro do gradiente: isso volta a medir erro ",[16,1246,449],{}," do limiar, exatamente como ",[53,1249,1250],{"href":156},"o perceptron do Rosenblatt",". O nome ",[228,1253,1254],{},"SmoothedSurrogate"," bate com o que o Aggarwal chama de ",[16,1257,1258],{},"critério do perceptron",[231,1260,1263,1326],{"className":1261},[1262],"katex",[231,1264,1267],{"className":1265},[1266],"katex-mathml",[1268,1269,1271],"math",{"xmlns":1270},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[1272,1273,1274,1321],"semantics",{},[1275,1276,1277,1281,1285,1288,1291,1295,1298,1302,1305,1307,1310,1318],"mrow",{},[1278,1279,1280],"mi",{},"L",[1282,1283,1284],"mo",{},"=",[1278,1286,1287],{},"max",[1282,1289,1290],{},"⁡",[1282,1292,1294],{"stretchy":1293},"false","(",[1296,1297,486],"mn",{},[1282,1299,1301],{"separator":1300},"true",",",[1282,1303,1304],{},"−",[1278,1306,295],{},[1282,1308,1309],{},"⋅",[1311,1312,1313,1315],"mover",{"accent":1300},[1278,1314,295],{},[1282,1316,1317],{},"^",[1282,1319,1320],{"stretchy":1293},")",[1322,1323,1325],"annotation",{"encoding":1324},"application\u002Fx-tex","L = \\max(0, -y \\cdot \\hat{y})",[231,1327,1330,1356,1400],{"className":1328,"ariaHidden":1300},[1329],"katex-html",[231,1331,1334,1339,1344,1349,1353],{"className":1332},[1333],"base",[231,1335],{"className":1336,"style":1338},[1337],"strut","height:0.6833em;",[231,1340,1280],{"className":1341},[1342,1343],"mord","mathnormal",[231,1345],{"className":1346,"style":1348},[1347],"mspace","margin-right:0.2778em;",[231,1350,1284],{"className":1351},[1352],"mrel",[231,1354],{"className":1355,"style":1348},[1347],[231,1357,1359,1363,1367,1371,1374,1378,1382,1385,1389,1393,1397],{"className":1358},[1333],[231,1360],{"className":1361,"style":1362},[1337],"height:1em;vertical-align:-0.25em;",[231,1364,1287],{"className":1365},[1366],"mop",[231,1368,1294],{"className":1369},[1370],"mopen",[231,1372,486],{"className":1373},[1342],[231,1375,1301],{"className":1376},[1377],"mpunct",[231,1379],{"className":1380,"style":1381},[1347],"margin-right:0.1667em;",[231,1383,1304],{"className":1384},[1342],[231,1386,295],{"className":1387,"style":1388},[1342,1343],"margin-right:0.0359em;",[231,1390],{"className":1391,"style":1392},[1347],"margin-right:0.2222em;",[231,1394,1309],{"className":1395},[1396],"mbin",[231,1398],{"className":1399,"style":1392},[1347],[231,1401,1403,1406,1462],{"className":1402},[1333],[231,1404],{"className":1405,"style":1362},[1337],[231,1407,1410],{"className":1408},[1342,1409],"accent",[231,1411,1415,1453],{"className":1412},[1413,1414],"vlist-t","vlist-t2",[231,1416,1419,1448],{"className":1417},[1418],"vlist-r",[231,1420,1424,1435],{"className":1421,"style":1423},[1422],"vlist","height:0.6944em;",[231,1425,1427,1432],{"style":1426},"top:-3em;",[231,1428],{"className":1429,"style":1431},[1430],"pstrut","height:3em;",[231,1433,295],{"className":1434,"style":1388},[1342,1343],[231,1436,1437,1440],{"style":1426},[231,1438],{"className":1439,"style":1431},[1430],[231,1441,1445],{"className":1442,"style":1444},[1443],"accent-body","left:-0.1944em;",[231,1446,1317],{"className":1447},[1342],[231,1449,1452],{"className":1450},[1451],"vlist-s","​",[231,1454,1456],{"className":1455},[1418],[231,1457,1460],{"className":1458,"style":1459},[1422],"height:0.1944em;",[231,1461],{},[231,1463,1320],{"className":1464},[1465],"mclose",", zero quando o ponto já está do lado certo, crescendo linearmente quando está errado. Não é coincidência a acurácia bater ",[228,1468,428],{},": essa função de custo, plugada nesse framework genérico, ",[16,1471,756],{}," o perceptron original de novo, só que expresso na linguagem de \"função de custo\" em vez de \"regra de atualização\".",[222,1474,1476],{"className":224,"code":1475,"language":226,"meta":27,"style":27},"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",[228,1477,1478,1483,1487,1491,1496,1500,1504],{"__ignoreMap":27},[231,1479,1480],{"class":233,"line":34},[231,1481,1482],{},"class LogLikehood(CostFunction):\n",[231,1484,1485],{"class":233,"line":28},[231,1486,1143],{},[231,1488,1489],{"class":233,"line":163},[231,1490,1112],{},[231,1492,1493],{"class":233,"line":191},[231,1494,1495],{},"    return np.sum(np.maximum(np.zeros(y.shape), 1 - y * y_pred))\n",[231,1497,1498],{"class":233,"line":254},[231,1499,1143],{},[231,1501,1502],{"class":233,"line":260},[231,1503,1125],{},[231,1505,1506],{"class":233,"line":266},[231,1507,1508],{},"    return X.T @ (y - expit(y_pred))\n",[315,1510,1511],{},[12,1512,1513,425,1515,326,1518,1521],{},[16,1514,321],{},[16,1516,1517],{},"0.65",[228,1519,1520],{},"[-60.78, 27.44, -24.79]",". Bem pior que tudo que eu vi até aqui, e os pesos ficaram enormes.",[12,1523,1524,1525,1528,1529,1532,1533,1536,1537,1539,1540,1542],{},"Duas coisas erradas aqui, e vale separar. Primeiro, um detalhe que não afeta o resultado: o ",[228,1526,1527],{},"get_cost"," dessa classe usa a fórmula do hinge loss (",[228,1530,1531],{},"max(0, 1 - y·ŷ)","), não uma fórmula de log-verossimilhança de verdade. Isso não quebra nada na prática porque ",[228,1534,1535],{},"get_gradient"," é a única coisa que o ",[228,1538,1077],{}," chama, ",[228,1541,1527],{}," nunca é usado durante o treino, sobrou como resíduo de copiar e colar de outra célula.",[12,1544,1545,1546,1549,1550,212,1552,1554,1555,296,1557,300,1559,1561,1562,1565,1566,287,1569,1572,1573,1575,1576,1579,1580,1582,1583,1585,1586,1589,1590,1593,1594,1596],{},"O segundo problema é de verdade, e explica a acurácia ruim: ",[228,1547,1548],{},"expit"," (a função sigmoide) devolve valores só entre ",[228,1551,486],{},[228,1553,485],{},", mas o rótulo ",[228,1556,295],{},[228,1558,299],{},[228,1560,303],{},". Pra classe ",[228,1563,1564],{},"y=-1",", o erro ",[228,1567,1568],{},"y - expit(y_pred)",[16,1570,1571],{},"nunca"," consegue chegar perto de zero, porque ",[228,1574,1548],{}," nunca fica negativo: mesmo com a previsão infinitamente confiante do lado certo, ",[228,1577,1578],{},"-1 - expit(y_pred)"," fica travado perto de ",[228,1581,299],{},", nunca de ",[228,1584,486],{},". Conferi isso na mão: ",[228,1587,1588],{},"expit(-1000) = 0.0",", então ",[228,1591,1592],{},"-1 - expit(-1000) = -1.0"," cravado, não ",[228,1595,486],{},". 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.",[222,1598,1600],{"className":224,"code":1599,"language":226,"meta":27,"style":27},"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",[228,1601,1602,1606,1610,1614,1619,1623],{"__ignoreMap":27},[231,1603,1604],{"class":233,"line":34},[231,1605,1482],{},[231,1607,1608],{"class":233,"line":28},[231,1609,1143],{},[231,1611,1612],{"class":233,"line":163},[231,1613,1125],{},[231,1615,1616],{"class":233,"line":191},[231,1617,1618],{},"    return X.T @ (y - tanh(y_pred))\n",[231,1620,1621],{"class":233,"line":254},[231,1622,263],{"emptyLinePlaceholder":33},[231,1624,1625],{"class":233,"line":260},[231,1626,1627],{},"model = NeuralNetwork(training_algorithm=SGD(max_iter=10000, cost_function=LogLikehood()))\n",[315,1629,1630],{},[12,1631,1632,425,1634,326,1636,1639,1640,1643],{},[16,1633,321],{},[16,1635,428],{},[228,1637,1638],{},"[-9.84, 12.40, 8.37]"," (com ",[228,1641,1642],{},"max_iter=10000",", dez vezes mais iterações).",[12,1645,1646,1647,1048,1649,1652,1653,1655,1656,212,1658,1660,1661,1589,1664,1667,1668,1809,1810,1862,1863,1920,1921,158],{},"Trocando ",[228,1648,1548],{},[228,1650,1651],{},"tanh"," (mesma classe redefinida, o Python deixa isso rodando ao vivo numa sessão de notebook), o problema some. Faz sentido: ",[228,1654,1651],{}," varia entre ",[228,1657,299],{},[228,1659,303],{},", exatamente a faixa dos rótulos. Conferi de novo na mão: ",[228,1662,1663],{},"tanh(-1000) = -1.0",[228,1665,1666],{},"-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: ",[231,1669,1671,1717],{"className":1670},[1262],[231,1672,1674],{"className":1673},[1266],[1268,1675,1676],{"xmlns":1270},[1272,1677,1678,1714],{},[1275,1679,1680,1682,1684,1686,1689,1691,1693,1696,1698,1702,1704,1706,1708,1710,1712],{},[1278,1681,1651],{},[1282,1683,1290],{},[1282,1685,1294],{"stretchy":1293},[1278,1687,1688],{},"v",[1282,1690,1320],{"stretchy":1293},[1282,1692,1284],{},[1296,1694,1695],{},"2",[1282,1697,1309],{},[1699,1700,1701],"mtext",{},"sigmoide",[1282,1703,1294],{"stretchy":1293},[1296,1705,1695],{},[1278,1707,1688],{},[1282,1709,1320],{"stretchy":1293},[1282,1711,1304],{},[1296,1713,485],{},[1322,1715,1716],{"encoding":1324},"\\tanh(v) = 2 \\cdot \\text{sigmoide}(2v) - 1",[231,1718,1720,1747,1766,1800],{"className":1719,"ariaHidden":1300},[1329],[231,1721,1723,1726,1729,1732,1735,1738,1741,1744],{"className":1722},[1333],[231,1724],{"className":1725,"style":1362},[1337],[231,1727,1651],{"className":1728},[1366],[231,1730,1294],{"className":1731},[1370],[231,1733,1688],{"className":1734,"style":1388},[1342,1343],[231,1736,1320],{"className":1737},[1465],[231,1739],{"className":1740,"style":1348},[1347],[231,1742,1284],{"className":1743},[1352],[231,1745],{"className":1746,"style":1348},[1347],[231,1748,1750,1754,1757,1760,1763],{"className":1749},[1333],[231,1751],{"className":1752,"style":1753},[1337],"height:0.6444em;",[231,1755,1695],{"className":1756},[1342],[231,1758],{"className":1759,"style":1392},[1347],[231,1761,1309],{"className":1762},[1396],[231,1764],{"className":1765,"style":1392},[1347],[231,1767,1769,1772,1779,1782,1785,1788,1791,1794,1797],{"className":1768},[1333],[231,1770],{"className":1771,"style":1362},[1337],[231,1773,1776],{"className":1774},[1342,1775],"text",[231,1777,1701],{"className":1778},[1342],[231,1780,1294],{"className":1781},[1370],[231,1783,1695],{"className":1784},[1342],[231,1786,1688],{"className":1787,"style":1388},[1342,1343],[231,1789,1320],{"className":1790},[1465],[231,1792],{"className":1793,"style":1392},[1347],[231,1795,1304],{"className":1796},[1396],[231,1798],{"className":1799,"style":1392},[1347],[231,1801,1803,1806],{"className":1802},[1333],[231,1804],{"className":1805,"style":1753},[1337],[231,1807,485],{"className":1808},[1342],", e é exatamente esse deslocamento de faixa, de ",[231,1811,1813,1835],{"className":1812},[1262],[231,1814,1816],{"className":1815},[1266],[1268,1817,1818],{"xmlns":1270},[1272,1819,1820,1832],{},[1275,1821,1822,1824,1826,1828,1830],{},[1282,1823,1294],{"stretchy":1293},[1296,1825,486],{},[1282,1827,1301],{"separator":1300},[1296,1829,485],{},[1282,1831,1320],{"stretchy":1293},[1322,1833,1834],{"encoding":1324},"(0,1)",[231,1836,1838],{"className":1837,"ariaHidden":1300},[1329],[231,1839,1841,1844,1847,1850,1853,1856,1859],{"className":1840},[1333],[231,1842],{"className":1843,"style":1362},[1337],[231,1845,1294],{"className":1846},[1370],[231,1848,486],{"className":1849},[1342],[231,1851,1301],{"className":1852},[1377],[231,1854],{"className":1855,"style":1381},[1347],[231,1857,485],{"className":1858},[1342],[231,1860,1320],{"className":1861},[1465]," pra ",[231,1864,1866,1890],{"className":1865},[1262],[231,1867,1869],{"className":1868},[1266],[1268,1870,1871],{"xmlns":1270},[1272,1872,1873,1887],{},[1275,1874,1875,1877,1879,1881,1883,1885],{},[1282,1876,1294],{"stretchy":1293},[1282,1878,1304],{},[1296,1880,485],{},[1282,1882,1301],{"separator":1300},[1296,1884,485],{},[1282,1886,1320],{"stretchy":1293},[1322,1888,1889],{"encoding":1324},"(-1,1)",[231,1891,1893],{"className":1892,"ariaHidden":1300},[1329],[231,1894,1896,1899,1902,1905,1908,1911,1914,1917],{"className":1895},[1333],[231,1897],{"className":1898,"style":1362},[1337],[231,1900,1294],{"className":1901},[1370],[231,1903,1304],{"className":1904},[1342],[231,1906,485],{"className":1907},[1342],[231,1909,1301],{"className":1910},[1377],[231,1912],{"className":1913,"style":1381},[1347],[231,1915,485],{"className":1916},[1342],[231,1918,1320],{"className":1919},[1465],", que resolve o descompasso com rótulo ",[228,1922,1923],{},"±1",[222,1925,1927],{"className":224,"code":1926,"language":226,"meta":27,"style":27},"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",[228,1928,1929,1934,1938,1942,1947,1952,1957],{"__ignoreMap":27},[231,1930,1931],{"class":233,"line":34},[231,1932,1933],{},"class HingeLoss(CostFunction):\n",[231,1935,1936],{"class":233,"line":28},[231,1937,1143],{},[231,1939,1940],{"class":233,"line":163},[231,1941,1125],{},[231,1943,1944],{"class":233,"line":191},[231,1945,1946],{},"    marginal_errors = (y * y_pred) \u003C 1\n",[231,1948,1949],{"class":233,"line":254},[231,1950,1951],{},"    marginal_ys = np.copy(y)\n",[231,1953,1954],{"class":233,"line":260},[231,1955,1956],{},"    marginal_ys[~marginal_errors] = 0\n",[231,1958,1959],{"class":233,"line":266},[231,1960,1961],{},"    return X.T @ marginal_ys\n",[315,1963,1964],{},[12,1965,1966,425,1968,326,1970,158],{},[16,1967,321],{},[16,1969,428],{},[228,1971,1972],{},"[-8.70, 11.59, 7.71]",[12,1974,1240,1975,1978,1979,1982,1983,1986],{},[228,1976,1977],{},"marginal_ys[~marginal_errors] = 0",": pontos que já estão bem classificados, com folga (",[228,1980,1981],{},"y · ŷ ≥ 1","), são zerados e ",[16,1984,1985],{},"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.",[217,1988,1990],{"id":1989},"as-quatro-curvas-lado-a-lado","As quatro curvas, lado a lado",[12,1992,1993,1994,2090],{},"O professor tem uma imagem de referência guardada no notebook comparando as quatro curvas de penalidade em função da \"margem\" (",[231,1995,1997,2019],{"className":1996},[1262],[231,1998,2000],{"className":1999},[1266],[1268,2001,2002],{"xmlns":1270},[1272,2003,2004,2016],{},[1275,2005,2006,2008,2010],{},[1278,2007,295],{},[1282,2009,1309],{},[1311,2011,2012,2014],{"accent":1300},[1278,2013,295],{},[1282,2015,1317],{},[1322,2017,2018],{"encoding":1324},"y \\cdot \\hat{y}",[231,2020,2022,2041],{"className":2021,"ariaHidden":1300},[1329],[231,2023,2025,2029,2032,2035,2038],{"className":2024},[1333],[231,2026],{"className":2027,"style":2028},[1337],"height:0.6389em;vertical-align:-0.1944em;",[231,2030,295],{"className":2031,"style":1388},[1342,1343],[231,2033],{"className":2034,"style":1392},[1347],[231,2036,1309],{"className":2037},[1396],[231,2039],{"className":2040,"style":1392},[1347],[231,2042,2044,2048],{"className":2043},[1333],[231,2045],{"className":2046,"style":2047},[1337],"height:0.8889em;vertical-align:-0.1944em;",[231,2049,2051],{"className":2050},[1342,1409],[231,2052,2054,2082],{"className":2053},[1413,1414],[231,2055,2057,2079],{"className":2056},[1418],[231,2058,2060,2068],{"className":2059,"style":1423},[1422],[231,2061,2062,2065],{"style":1426},[231,2063],{"className":2064,"style":1431},[1430],[231,2066,295],{"className":2067,"style":1388},[1342,1343],[231,2069,2070,2073],{"style":1426},[231,2071],{"className":2072,"style":1431},[1430],[231,2074,2076],{"className":2075,"style":1444},[1443],[231,2077,1317],{"className":2078},[1342],[231,2080,1452],{"className":2081},[1451],[231,2083,2085],{"className":2084},[1418],[231,2086,2088],{"className":2087,"style":1459},[1422],[231,2089],{},": positivo e grande é acerto confiante, negativo é erro). Recriei a mesma ideia aqui, interativa:",[2092,2093],"margin-loss-chart",{"hinge-label":2094,"logistic-label":2095,"perceptron-label":2096,"widrow-hoff-label":2097,"x-label":2098,"y-label":2099},"Hinge (SVM)","Logística","Perceptron","Widrow-Hoff","margem (y · ŷ)","penalidade",[12,2101,2102,2103,2106,2107,2110,2111,2113,2114,2116],{},"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 ",[228,2104,2105],{},"> 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 ",[16,2108,2109],{},"zeram"," de vez quando o ponto está bem classificado (perceptron zera assim que passa de ",[228,2112,486],{},", hinge exige passar de ",[228,2115,485],{},", 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.",[217,2118,720],{"id":719},[662,2120,2121,2129],{},[665,2122,2123],{},[668,2124,2125,2127],{},[671,2126,729],{"align":673},[671,2128,732],{"align":673},[683,2130,2131,2149,2163],{},[668,2132,2133,2136],{},[688,2134,2135],{"align":673},"Perceptron, Adaline, gradiente batch pareciam algoritmos separados",[688,2137,2138,2139,2141,2142,2141,2145,2148],{"align":673},"São o mesmo framework (",[228,2140,1031],{}," + ",[228,2143,2144],{},"TrainingAlgorithm",[228,2146,2147],{},"CostFunction","), só trocando qual função de custo plugar",[668,2150,2151,2154],{},[688,2152,2153],{"align":673},"Sigmoide devolve probabilidade entre 0 e 1",[688,2155,2156,2157,2159,2160,2162],{"align":673},"Usar sigmoide direto contra rótulo ",[228,2158,1923],{}," trava o gradiente, porque a faixa de saída não bate com a faixa do alvo. ",[228,2161,1651],{}," resolve isso",[668,2164,2165,2168],{},[688,2166,2167],{"align":673},"SVM usa \"vetores de suporte\"",[688,2169,2170],{"align":673},"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",[217,2172,769],{"id":768},[12,2174,2175,2176,2178,2179,776,2181,2183,2184,2186],{},"Rodei as quatro funções de custo (Widrow-Hoff, critério do perceptron, hinge, e a versão log-verossimilhança com ",[228,2177,1651],{},") no Iris (",[67,2180,775],{},[67,2182,779],{},"), mais a versão com ",[228,2185,1548],{}," de propósito, pra confirmar que o problema da sigmoide não é exclusivo do dataset sintético de brinquedo.",[662,2188,2189,2202],{},[665,2190,2191],{},[668,2192,2193,2196,2199],{},[671,2194,2195],{"align":673},"Função de custo",[671,2197,2198],{"align":677},"Acurácia treino",[671,2200,2201],{"align":677},"Acurácia teste",[683,2203,2204,2214,2224,2235,2248],{},[668,2205,2206,2208,2210],{},[688,2207,2097],{"align":673},[688,2209,428],{"align":677},[688,2211,2212],{"align":677},[16,2213,428],{},[668,2215,2216,2218,2220],{},[688,2217,2096],{"align":673},[688,2219,428],{"align":677},[688,2221,2222],{"align":677},[16,2223,428],{},[668,2225,2226,2229,2231],{},[688,2227,2228],{"align":673},"Hinge",[688,2230,428],{"align":677},[688,2232,2233],{"align":677},[16,2234,428],{},[668,2236,2237,2242,2244],{},[688,2238,2239,2240,1320],{"align":673},"Log-verossimilhança (",[228,2241,1651],{},[688,2243,428],{"align":677},[688,2245,2246],{"align":677},[16,2247,428],{},[668,2249,2250,2255,2258],{},[688,2251,2239,2252,2254],{"align":673},[228,2253,1548],{},", sigmoide)",[688,2256,2257],{"align":677},"0.843",[688,2259,2260],{"align":677},[16,2261,2262],{},"0.933",[12,2264,2265,2266,2268,2269,2271],{},"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 ",[228,2267,1548],{}," e rótulo ",[228,2270,1923],{}," prejudica a convergência de verdade, em qualquer dataset onde eu tentei.",[874,2273,876],{},{"title":27,"searchDepth":28,"depth":28,"links":2275},[2276,2277,2278,2279,2280,2281],{"id":911,"depth":28,"text":912},{"id":1091,"depth":28,"text":1092},{"id":1200,"depth":28,"text":1201},{"id":1989,"depth":28,"text":1990},{"id":719,"depth":28,"text":720},{"id":768,"depth":28,"text":769},"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.",{},"\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo",{"title":899,"description":2282},"pt\u002Fplaylists\u002Fneural-networks\u002Ffuncoes-de-custo",[2288,2289,2290],"funcoes-de-custo","hinge-loss","regressao-logistica","vrlcf9sVSWCNt1PSqoQuZ_AyK-_pQcbYMuR7_tC7G-c",{"id":2293,"title":2294,"body":2295,"cover":3,"date":885,"description":2885,"extension":31,"meta":2886,"navigation":33,"order":34,"path":2887,"playlist":889,"seo":2888,"status":37,"stem":2889,"tags":2890,"__hash__":2894},"posts\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Fmcculloch-pitts-perceptron.md","Perceptron: o Primeiro Neurônio que Aprende Sozinho",{"type":9,"value":2296,"toc":2876},[2297,2300,2304,2313,2320,2324,2359,2387,2412,2431,2445,2449,2550,2564,2606,2613,2617,2636,2653,2660,2677,2689,2702,2712,2716,2727,2732,2746,2748,2784,2786,2797,2827,2830,2871,2874],[12,2298,2299],{},"Primeira aula da playlist viva, e ela começa exatamente onde qualquer curso de redes neurais deveria começar: no neurônio mais simples que existe.",[217,2301,2303],{"id":2302},"antes-do-código-dois-papers-e-15-anos-de-distância","Antes do código: dois papers e 15 anos de distância",[12,2305,2306,2307,2312],{},"Rede neural não nasceu como código, nasceu como pergunta de biofísica. Em 1943, Warren McCulloch e Walter Pitts publicaram ",[53,2308,2311],{"href":2309,"rel":2310},"https:\u002F\u002Fwww.cs.cmu.edu\u002F~epxing\u002FClass\u002F10715\u002Freading\u002FMcCulloch.and.Pitts.pdf",[78],"\"A Logical Calculus of the Ideas Immanent in Nervous Activity\"",", propondo um modelo matemático bem enxuto pra um neurônio biológico: soma os sinais de entrada, e dispara um sinal binário (tudo ou nada) se essa soma passar de um limiar. Sem aprendizado nenhum ainda, é só um circuito lógico fixo, cada \"neurônio\" resolve uma função lógica que alguém já decidiu de antemão.",[12,2314,2315,2316,2319],{},"O salto que faltava veio 15 anos depois, com Frank Rosenblatt em 1958: e se, em vez de alguém escolher os pesos à mão, o próprio neurônio aprendesse os pesos certos olhando pra exemplos? Esse é o Perceptron, e é exatamente o que a aula 1a do professor implementa: o ",[16,2317,2318],{},"Perceptron Learning Algorithm"," (PLA), do jeito mais cru possível, sem bias ainda (isso fica pra próxima aula).",[217,2321,2323],{"id":2322},"o-dataset-dois-grupos-separáveis-por-uma-reta","O dataset: dois grupos separáveis por uma reta",[222,2325,2327],{"className":224,"code":2326,"language":226,"meta":27,"style":27},"def createDataset(n=20):\n  X = np.random.rand(n,2)\n  coefs = np.array([1, -1])\n  labels = X @ coefs\n  y = np.array(labels>0, dtype=int)*2-1\n  return X, y\n",[228,2328,2329,2334,2339,2344,2349,2354],{"__ignoreMap":27},[231,2330,2331],{"class":233,"line":34},[231,2332,2333],{},"def createDataset(n=20):\n",[231,2335,2336],{"class":233,"line":28},[231,2337,2338],{},"  X = np.random.rand(n,2)\n",[231,2340,2341],{"class":233,"line":163},[231,2342,2343],{},"  coefs = np.array([1, -1])\n",[231,2345,2346],{"class":233,"line":191},[231,2347,2348],{},"  labels = X @ coefs\n",[231,2350,2351],{"class":233,"line":254},[231,2352,2353],{},"  y = np.array(labels>0, dtype=int)*2-1\n",[231,2355,2356],{"class":233,"line":260},[231,2357,2358],{},"  return X, y\n",[12,2360,2361,2362,2365,2366,2369,2370,2372,2373,2375,2376,555,2378,300,2380,2382,2383,2386],{},"O professor gera pontos aleatórios em 2D e rotula cada um pelo sinal de ",[228,2363,2364],{},"X @ coefs",", ou seja, o produto escalar entre o ponto e o vetor ",[228,2367,2368],{},"[1, -1]",". Geometricamente, ",[228,2371,2364],{}," é positivo de um lado da reta que passa pela origem e é perpendicular a ",[228,2374,2368],{},", e negativo do outro lado, então o rótulo ",[228,2377,295],{},[228,2379,299],{},[228,2381,303],{},") já nasce ",[16,2384,2385],{},"linearmente separável por construção",": existe uma reta reta que separa as duas classes perfeitamente, porque foi exatamente essa reta que gerou os rótulos.",[222,2388,2390],{"className":224,"code":2389,"language":226,"meta":27,"style":27},"def plotHyperplan(vector):\n  xs = np.array([0,1])\n  ys = -(vector[0]*xs)\u002Fvector[1]\n  plt.plot(xs, ys)\n",[228,2391,2392,2397,2402,2407],{"__ignoreMap":27},[231,2393,2394],{"class":233,"line":34},[231,2395,2396],{},"def plotHyperplan(vector):\n",[231,2398,2399],{"class":233,"line":28},[231,2400,2401],{},"  xs = np.array([0,1])\n",[231,2403,2404],{"class":233,"line":163},[231,2405,2406],{},"  ys = -(vector[0]*xs)\u002Fvector[1]\n",[231,2408,2409],{"class":233,"line":191},[231,2410,2411],{},"  plt.plot(xs, ys)\n",[12,2413,2414,2415,2418,2419,2422,2423,2426,2427,2430],{},"Essa função desenha a reta onde ",[228,2416,2417],{},"vector[0]*x + vector[1]*y = 0",", a fronteira de decisão de um vetor de pesos ",[228,2420,2421],{},"w"," qualquer. É a mesma equação de sempre, ",[228,2424,2425],{},"w · x = 0"," define um hiperplano, só que aqui sem bias, então o hiperplano é obrigado a passar pela origem ",[228,2428,2429],{},"(0,0)",". Guarda esse detalhe, ele vai importar daqui a pouco.",[12,2432,2433,2434,2437,2438,2440,2441,2444],{},"O professor até testa um ",[228,2435,2436],{},"DummyClassifier"," com pesos fixos ",[228,2439,2368],{},", os mesmos que geraram o dataset, e claro que acerta 100%: é o gabarito jogado direto na resposta. A pergunta de verdade é: dá pra ",[16,2442,2443],{},"aprender"," esses pesos só olhando pros exemplos, sem eu entregar a resposta pronta?",[217,2446,2448],{"id":2447},"o-algoritmo-ajustar-o-peso-na-direção-do-erro","O algoritmo: ajustar o peso na direção do erro",[222,2450,2452],{"className":224,"code":2451,"language":226,"meta":27,"style":27},"class PLA(BaseEstimator, ClassifierMixin):\n  def __init__(self, max_iter=10):\n    self.max_iter = max_iter\n\n  def fit(self, X, y):\n    self.w_ = np.random.rand(X.shape[1])\n    for _ in range(self.max_iter):\n      cost = 0\n      idx = np.arange(X.shape[0])\n      np.random.shuffle(idx)\n      for i in idx:\n        logits = X[i] @ self.w_\n        y_pred = np.sign(logits)\n        error = y[i] - y_pred\n        if error != 0:\n          cost += error**2\n          self.w_ += error*X[i]\n        if cost == 0:\n          break\n    return self\n",[228,2453,2454,2459,2464,2469,2473,2477,2482,2486,2491,2496,2501,2506,2511,2516,2521,2526,2531,2536,2541,2546],{"__ignoreMap":27},[231,2455,2456],{"class":233,"line":34},[231,2457,2458],{},"class PLA(BaseEstimator, ClassifierMixin):\n",[231,2460,2461],{"class":233,"line":28},[231,2462,2463],{},"  def __init__(self, max_iter=10):\n",[231,2465,2466],{"class":233,"line":163},[231,2467,2468],{},"    self.max_iter = max_iter\n",[231,2470,2471],{"class":233,"line":191},[231,2472,263],{"emptyLinePlaceholder":33},[231,2474,2475],{"class":233,"line":254},[231,2476,241],{},[231,2478,2479],{"class":233,"line":260},[231,2480,2481],{},"    self.w_ = np.random.rand(X.shape[1])\n",[231,2483,2484],{"class":233,"line":266},[231,2485,525],{},[231,2487,2488],{"class":233,"line":272},[231,2489,2490],{},"      cost = 0\n",[231,2492,2493],{"class":233,"line":277},[231,2494,2495],{},"      idx = np.arange(X.shape[0])\n",[231,2497,2498],{"class":233,"line":381},[231,2499,2500],{},"      np.random.shuffle(idx)\n",[231,2502,2503],{"class":233,"line":386},[231,2504,2505],{},"      for i in idx:\n",[231,2507,2508],{"class":233,"line":391},[231,2509,2510],{},"        logits = X[i] @ self.w_\n",[231,2512,2513],{"class":233,"line":977},[231,2514,2515],{},"        y_pred = np.sign(logits)\n",[231,2517,2518],{"class":233,"line":982},[231,2519,2520],{},"        error = y[i] - y_pred\n",[231,2522,2523],{"class":233,"line":987},[231,2524,2525],{},"        if error != 0:\n",[231,2527,2528],{"class":233,"line":992},[231,2529,2530],{},"          cost += error**2\n",[231,2532,2533],{"class":233,"line":998},[231,2534,2535],{},"          self.w_ += error*X[i]\n",[231,2537,2538],{"class":233,"line":1003},[231,2539,2540],{},"        if cost == 0:\n",[231,2542,2543],{"class":233,"line":1008},[231,2544,2545],{},"          break\n",[231,2547,2548],{"class":233,"line":1013},[231,2549,257],{},[12,2551,1168,2552,2555,2556,2559,2560,2563],{},[228,2553,2554],{},"w_"," começa aleatório. Depois, ponto por ponto, em ordem embaralhada: calcula a previsão (",[228,2557,2558],{},"sign(w · x)","), compara com o rótulo verdadeiro, e se errou, atualiza ",[228,2561,2562],{},"w_ += error * x",". Essa é a regra de aprendizado inteira, e vale entender por que ela funciona, não só decorar a fórmula.",[12,2565,2566,2567,2569,2570,2573,2574,2576,2577,2580,2581,2583,2584,2587,2588,212,2591,2593,2594,2597,2598,2601,2602,2605],{},"Pensa geometricamente: ",[228,2568,2421],{}," é o vetor normal ao hiperplano de decisão, ele aponta pro lado que o modelo considera \"classe +1\". Se o modelo errou um ponto que era ",[16,2571,2572],{},"de verdade"," classe +1 mas foi classificado como -1, isso significa que ",[228,2575,2421],{}," está apontando \"longe demais\" desse ponto. Somar ",[228,2578,2579],{},"error * x"," a ",[228,2582,2421],{}," (aqui ",[228,2585,2586],{},"error = +2",", já que ",[228,2589,2590],{},"y_pred",[228,2592,295],{}," estão em ",[228,2595,2596],{},"{-1,+1}",") empurra o vetor de pesos ",[16,2599,2600],{},"na direção desse ponto",", deixando ",[228,2603,2604],{},"w · x"," um pouco mais positivo da próxima vez. O oposto acontece quando o erro é pro outro lado. É um ajuste local e barato: cada erro move a fronteira de decisão um pouquinho na direção que teria acertado aquele ponto específico, sem precisar calcular gradiente nenhum de verdade (isso é justamente o que o Aggarwal chama de \"critério do perceptron\", capítulo 1: uma regra de atualização heurística que se parece muito com gradiente descendente, mas que foi desenhada direto em cima do erro de classificação, antes de alguém formalizar qual função de custo suave ela estaria otimizando por baixo dos panos).",[12,2607,2608,2609,2612],{},"E o Rosenblatt provou, lá em 1958, algo forte: se os dados são mesmo linearmente separáveis (como são aqui, por construção), o PLA ",[16,2610,2611],{},"sempre converge"," pra uma solução com erro zero, em um número finito de passos. Não é \"geralmente funciona\", é uma garantia matemática.",[217,2614,2616],{"id":2615},"um-bug-real-escondido-na-condição-de-parada","Um bug real, escondido na condição de parada",[12,2618,2619,2620,2623,2624,2627,2628,2631,2632,2635],{},"Só que reparar de perto no ",[228,2621,2622],{},"if cost == 0: break"," revela um problema. Essa checagem está ",[16,2625,2626],{},"dentro"," do laço que percorre os pontos, não depois dele. Isso significa: assim que ",[228,2629,2630],{},"cost"," (que só cresce quando há erro) estiver em zero, o laço ",[16,2633,2634],{},"para imediatamente",", mesmo que ainda faltem pontos pra checar naquela época.",[12,2637,2638,2639,2641,2642,2645,2646,2648,2649,2652],{},"O problema é que, logo no início de cada época, ",[228,2640,2630],{}," já começa em zero. Então se o ",[16,2643,2644],{},"primeiro ponto sorteado"," naquela época por acaso já está classificado certo, ",[228,2647,2630],{}," continua zero, e o ",[228,2650,2651],{},"break"," dispara ali mesmo, sem checar os outros 19 pontos. A época termina achando que \"está tudo certo\", quando na verdade só um ponto foi conferido.",[12,2654,2655,2656,2659],{},"Rodei esse código exato, byte a byte, com uma semente fixa (",[228,2657,2658],{},"np.random.seed(10)","), pra ver o estrago na prática:",[315,2661,2662],{},[12,2663,2664,2666,2667,1028,2669,2672,2673,2676],{},[16,2665,321],{}," contando quantos pontos (de 20) cada época realmente processou antes do ",[228,2668,2651],{},[228,2670,2671],{},"[1, 1, 1, 1, 1, 1, 1, 1, 20, 20]",". Só as duas últimas épocas conferiram o dataset inteiro. Acurácia final: ",[16,2674,2675],{},"0.6",", bem longe da separação perfeita que o Rosenblatt garante.",[12,2678,2679,2680,2682,2683,2685,2686,2688],{},"Movendo só o ",[228,2681,2622],{}," pra fora do laço interno (checar zero erros ",[16,2684,449],{}," de passar por todos os 20 pontos, não a cada um), a mesma semente, os mesmos dados, o mesmo ",[228,2687,2421],{}," inicial:",[315,2690,2691],{},[12,2692,2693,287,2695,2698,2699,2701],{},[16,2694,321],{},[228,2696,2697],{},"[20, 20]",", duas épocas completas, e pronto: convergiu com acurácia ",[16,2700,428],{},", exatamente o que o teorema promete pra dado linearmente separável.",[12,2703,2704,2705,2707,2708,2711],{},"Achado real, não hipotético: a intenção óbvia do código é \"para quando não tiver mais erro nesta época\", mas o jeito como o ",[228,2706,2651],{}," foi posicionado faz ele parar assim que ",[16,2709,2710],{},"um único ponto favorável"," aparece, mesmo cercado de erro em algum lugar mais à frente na fila. Com sorte (como em boa parte das sementes que testei), isso não muda o resultado final porque outras épocas compensam. Mas com essa semente específica, o algoritmo declara sucesso prematuramente, oito vezes seguidas, e nunca chega na solução perfeita que deveria.",[217,2713,2715],{"id":2714},"interativo-treinando-o-perceptron-ponto-por-ponto","Interativo: treinando o perceptron ponto por ponto",[12,2717,2718,2719,2722,2723,2726],{},"Reconstruí o mesmo dataset (a semente ",[228,2720,2721],{},"10"," de cima, os mesmos 20 pontos) num componente que roda a versão ",[16,2724,2725],{},"correta"," do algoritmo, um ponto por vez. Clica em \"Processar próximo ponto\" e repara: cada vez que um ponto colorido cai do lado errado da região de fundo, isso é um erro, e o próximo clique empurra a fronteira em direção a ele.",[2728,2729],"perceptron-explorer",{":classes":2730,":points":2731,":x-max":485,":x-min":486,":y-max":485,":y-min":486,"negative-label":487,"positive-label":488,"x-label":489,"y-label":490},"[1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1]","[[0.7713, 0.0208], [0.6336, 0.7488], [0.4985, 0.2248], [0.1981, 0.7605], [0.1691, 0.0883], [0.6854, 0.9534], [0.0039, 0.5122], [0.8126, 0.6125], [0.7218, 0.2919], [0.9178, 0.7146], [0.5425, 0.1422], [0.3733, 0.6741], [0.4418, 0.434], [0.6178, 0.5131], [0.6504, 0.601], [0.8052, 0.5216], [0.9086, 0.3192], [0.0905, 0.3007], [0.114, 0.8287], [0.0469, 0.6263]]",[12,2733,2734,2735,2737,2738,2741,2742,2745],{},"Repara que, sem bias, a fronteira é sempre uma reta passando pela origem ",[228,2736,2429],{},", ela só pode ",[16,2739,2740],{},"girar",", nunca ",[16,2743,2744],{},"deslizar",". Nesse dataset funciona porque os dados foram desenhados em volta da origem de propósito. Mas e se a nuvem de pontos estivesse toda deslocada, longe da origem? Chego nisso na Aplicação Prática.",[217,2747,720],{"id":719},[662,2749,2750,2758],{},[665,2751,2752],{},[668,2753,2754,2756],{},[671,2755,729],{"align":673},[671,2757,732],{"align":673},[683,2759,2760,2768,2776],{},[668,2761,2762,2765],{},[688,2763,2764],{"align":673},"Rede neural é sobre \"aprender pesos\"",[688,2766,2767],{"align":673},"O McCulloch-Pitts vem antes disso: primeiro alguém teve que propor que um neurônio pudesse ser modelado matematicamente, aprendizado veio 15 anos depois com Rosenblatt",[668,2769,2770,2773],{},[688,2771,2772],{"align":673},"Atualizar peso \"na direção do erro\" é intuitivo",[688,2774,2775],{"align":673},"Isso tem nome (critério do perceptron) e uma garantia matemática de convergência pra dado linearmente separável",[668,2777,2778,2781],{},[688,2779,2780],{"align":673},"Código do professor é a referência de verdade",[688,2782,2783],{"align":673},"Mesmo código de referência pode ter um bug sutil escondido numa condição de parada, e vale a pena testar em vez de confiar de olhos fechados",[217,2785,769],{"id":768},[12,2787,2788,2789,2791,2792,776,2794,2796],{},"Testei o mesmo PLA sem bias (código corrigido, sem o bug do ",[228,2790,2651],{},") num dataset real: Iris, as duas classes mais fáceis de separar (",[67,2793,775],{},[67,2795,779],{},"), usando comprimento e largura da pétala como as duas variáveis.",[222,2798,2800],{"className":224,"code":2799,"language":226,"meta":27,"style":27},"from sklearn.datasets import load_iris\niris = load_iris()\nmask = iris.target \u003C 2\nX = iris.data[mask][:, [2, 3]]  # comprimento e largura da pétala\ny = np.where(iris.target[mask] == 0, -1, 1)\n",[228,2801,2802,2807,2812,2817,2822],{"__ignoreMap":27},[231,2803,2804],{"class":233,"line":34},[231,2805,2806],{},"from sklearn.datasets import load_iris\n",[231,2808,2809],{"class":233,"line":28},[231,2810,2811],{},"iris = load_iris()\n",[231,2813,2814],{"class":233,"line":163},[231,2815,2816],{},"mask = iris.target \u003C 2\n",[231,2818,2819],{"class":233,"line":191},[231,2820,2821],{},"X = iris.data[mask][:, [2, 3]]  # comprimento e largura da pétala\n",[231,2823,2824],{"class":233,"line":254},[231,2825,2826],{},"y = np.where(iris.target[mask] == 0, -1, 1)\n",[12,2828,2829],{},"Essas duas classes são genuinamente linearmente separáveis (é um dos exemplos mais citados de \"separável de verdade\" em todo material introdutório de ML). Só que comprimento e largura de pétala nunca são negativos, então a nuvem de pontos inteira vive longe da origem, bem diferente do dataset sintético de cima.",[662,2831,2832,2846],{},[665,2833,2834],{},[668,2835,2836,2839,2843],{},[671,2837,2838],{"align":673},"Versão",[671,2840,2842],{"align":2841},"center","Convergiu em (máx. 50 épocas)",[671,2844,2845],{"align":677},"Acurácia final",[683,2847,2848,2859],{},[668,2849,2850,2853,2856],{},[688,2851,2852],{"align":673},"PLA sem bias",[688,2854,2855],{"align":2841},"nunca convergiu",[688,2857,2858],{"align":677},"0.84 a 0.93 (variando a semente)",[668,2860,2861,2864,2867],{},[688,2862,2863],{"align":673},"PLA com bias",[688,2865,2866],{"align":2841},"2 épocas",[688,2868,2869],{"align":677},[16,2870,428],{},[12,2872,2873],{},"Sem bias, o PLA nunca declara convergência dentro do limite de 50 épocas, porque a reta que separaria de verdade as duas classes não passa pela origem, e sem bias essa reta está simplesmente fora do alcance do modelo. Rodei 5 sementes diferentes e nenhuma passou de 93% de acurácia. Adicionando um único parâmetro (o bias, que desloca o hiperplano em vez de só girá-lo em torno da origem), o mesmo algoritmo converge em só 2 épocas com acurácia perfeita. É o exato limite que a próxima aula ataca de frente.",[874,2875,876],{},{"title":27,"searchDepth":28,"depth":28,"links":2877},[2878,2879,2880,2881,2882,2883,2884],{"id":2302,"depth":28,"text":2303},{"id":2322,"depth":28,"text":2323},{"id":2447,"depth":28,"text":2448},{"id":2615,"depth":28,"text":2616},{"id":2714,"depth":28,"text":2715},{"id":719,"depth":28,"text":720},{"id":768,"depth":28,"text":769},"Aula 1a: o professor implementa o Perceptron Learning Algorithm do zero, sem bias. Eu conto a história de dois papers que vêm antes dele e encontro um bug real escondido na condição de parada do algoritmo.",{},"\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Fmcculloch-pitts-perceptron",{"title":2294,"description":2885},"pt\u002Fplaylists\u002Fneural-networks\u002Fmcculloch-pitts-perceptron",[2891,2892,2893],"perceptron","mcculloch-pitts","rosenblatt","iUSXZah74pR9ZYkVjOWTICZgEnqAVF1Tf6zU8epgbV0",[2896,2934],{"id":2897,"title":2898,"body":2899,"date":2919,"description":2920,"extension":31,"meta":2921,"navigation":33,"path":2922,"photos":2923,"seo":2928,"status":37,"stem":2929,"tags":2930,"__hash__":2933},"timeline\u002Fpt\u002Ftimeline\u002F2026-08-19-estudando-go.md","Tomando um sacode de Go",{"type":9,"value":2900,"toc":2917},[2901,2908,2911,2914],[12,2902,2903,2904,2907],{},"Ando estudando bastante Go essas últimas semanas, e não é força de expressão: peguei o livro ",[67,2905,2906],{},"A Linguagem de Programação Go"," (o clássico do Donovan e Kernighan) até na praia. Entre uma onda e outra, deu pra ler bastante coisa.",[12,2909,2910],{},"Tô tomando um sacode danado pra me adaptar ao \"way of Go\". Depois de anos acostumado com Python, a cabeça precisa reaprender um jeito de pensar bem mais explícito: erro tratado na mão em vez de exception, tipagem estática, concorrência que é parte da linguagem e não um framework por cima. Nada absurdo, só um jeito diferente de resolver o mesmo problema.",[12,2912,2913],{},"E o entusiasmo é grande. Tô sentindo bastante vontade de ir migrando pra Golang aos poucos, principalmente pela robustez: o compilador pega um monte de erro bobo antes mesmo de rodar, e a performance é de outro patamar. Infelizmente ainda falta bastante lib madura pro lado de Machine Learning e IA no ecossistema Go hoje, então não dá pra trocar tudo de uma vez. Mas acho que no longo prazo a linguagem tem tudo pra virar uma peça importante nessa área, principalmente pra servir modelo em produção.",[12,2915,2916],{},"E sim, o gato resolveu aparecer numa das fotos também. Ele não estuda Go comigo, só supervisiona.",{"title":27,"searchDepth":28,"depth":28,"links":2918},[],"2026-08-19","Ando estudando bastante Go essas últimas semanas, e não é força de expressão: peguei o livro A Linguagem de Programação Go (o clássico do Donovan e Kernighan) até na praia. Entre uma onda e outra, deu pra ler bastante coisa.",{},"\u002Fpt\u002Ftimeline\u002F2026-08-19-estudando-go",[2924,2925,2926,2927],"\u002Fimages\u002Ftimeline\u002F20260819\u002Fgolang_book.jpeg","\u002Fimages\u002Ftimeline\u002F20260819\u002Fgolang_cup.jpeg","\u002Fimages\u002Ftimeline\u002F20260819\u002Fgolang_learn.jpeg","\u002Fimages\u002Ftimeline\u002F20260819\u002Fmy_cat.jpeg",{"title":2898,"description":2920},"pt\u002Ftimeline\u002F2026-08-19-estudando-go",[2931,2932],"golang","go","miisWLO8OEtn3EfDAQ-2h-9V2pr1LKdz3pcO52hK9xI",{"id":2935,"title":2936,"body":2937,"date":2958,"description":2959,"extension":31,"meta":2960,"navigation":33,"path":2961,"photos":2962,"seo":2966,"status":37,"stem":2967,"tags":2968,"__hash__":2973},"timeline\u002Fpt\u002Ftimeline\u002F2026-08-18-slm-dom-casmurro.md","Capítulo 2 do livro de SLM, só que com Dom Casmurro",{"type":9,"value":2938,"toc":2956},[2939,2946,2953],[12,2940,2941,2942,2945],{},"Entrei no Capítulo 2 do livro ",[67,2943,2944],{},"How to Build and Fine-Tune a Small Language Model",", do Prof. Paul Liu, e o exercício da vez é construir um GPT pequeno do zero, na linha do \"build a GPT\" do Karpathy, uns 10 milhões de parâmetros.",[12,2947,2948,2949,2952],{},"Só que em vez de usar o dataset de exemplo do próprio livro, resolvi trocar por um texto que eu gosto de verdade: ",[67,2950,2951],{},"Dom Casmurro",", do Machado de Assis. Mesmo código, mesma arquitetura, mas agora tokenizando e treinando em cima do Bentinho e da Capitu em vez de um corpus genérico.",[12,2954,2955],{},"Em breve posto mais atualizações quando eu finalizar esse estudo.",{"title":27,"searchDepth":28,"depth":28,"links":2957},[],"2026-08-18","Entrei no Capítulo 2 do livro How to Build and Fine-Tune a Small Language Model, do Prof. Paul Liu, e o exercício da vez é construir um GPT pequeno do zero, na linha do \"build a GPT\" do Karpathy, uns 10 milhões de parâmetros.",{},"\u002Fpt\u002Ftimeline\u002F2026-08-18-slm-dom-casmurro",[2963,2964,2965],"\u002Fimages\u002Ftimeline\u002F20260818\u002Fslms_book.jpeg","\u002Fimages\u002Ftimeline\u002F20260818\u002Fcapitulo_2_implementacao.jpeg","\u002Fimages\u002Ftimeline\u002F20260818\u002Ftrecho_livro.jpeg",{"title":2936,"description":2959},"pt\u002Ftimeline\u002F2026-08-18-slm-dom-casmurro",[2969,2970,2971,2972],"slm","gpt","nlp","dom-casmurro","qxLx4P9s4vK7tdmQxz52QFEdmvDHTWuXJ4sZ4yf9op8",1787338107975]