[{"data":1,"prerenderedAt":755},["ShallowReactive",2],{"lang-switch-post-\u002Fplaylists\u002Fneural-networks\u002Fregressao-e-equacao-normal":3,"post-pt-neural-networks-regressao-e-equacao-normal":4},"\u002Fen\u002Fplaylists\u002Fneural-networks\u002Fregressao-e-equacao-normal",{"id":5,"title":6,"body":7,"cover":740,"date":741,"description":742,"extension":743,"meta":744,"navigation":161,"order":51,"path":745,"playlist":746,"seo":747,"status":748,"stem":749,"tags":750,"__hash__":754},"posts\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Fregressao-e-equacao-normal.md","Da Classificação pra Regressão, e a Solução em Uma Linha",{"type":8,"value":9,"toc":732},"minimark",[10,19,26,79,98,175,324,353,371,375,434,462,499,515,534,538,552,564,568,629,633,649,658,673,690,722,728],[11,12,13,14,18],"p",{},"Aula 2c e 2d. A estrutura de código muda muito pouco, mas o significado muda bastante: sai o ",[15,16,17],"code",{},"sign()",", entra a previsão contínua, e no fim uma conta fechada substitui centenas de iterações.",[20,21,23,24],"h2",{"id":22},"trocando-classificação-por-regressão-só-tira-o-sign","Trocando classificação por regressão: só tira o ",[15,25,17],{},[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-python shiki shiki-themes github-light github-dark","def createRegressionDataset(n=20):\n  X = np.random.rand(n,1)\n  coef = 0.7\n  intercept = 0.2\n  noise = np.random.randn(n,1) * 0.1\n  y = X * coef + intercept + noise\n  return X, y.reshape(-1)\n","python","",[15,34,35,43,49,55,61,67,73],{"__ignoreMap":32},[36,37,40],"span",{"class":38,"line":39},"line",1,[36,41,42],{},"def createRegressionDataset(n=20):\n",[36,44,46],{"class":38,"line":45},2,[36,47,48],{},"  X = np.random.rand(n,1)\n",[36,50,52],{"class":38,"line":51},3,[36,53,54],{},"  coef = 0.7\n",[36,56,58],{"class":38,"line":57},4,[36,59,60],{},"  intercept = 0.2\n",[36,62,64],{"class":38,"line":63},5,[36,65,66],{},"  noise = np.random.randn(n,1) * 0.1\n",[36,68,70],{"class":38,"line":69},6,[36,71,72],{},"  y = X * coef + intercept + noise\n",[36,74,76],{"class":38,"line":75},7,[36,77,78],{},"  return X, y.reshape(-1)\n",[11,80,81,82,85,86,89,90,93,94,97],{},"Diferente dos datasets anteriores, ",[15,83,84],{},"y"," aqui não é mais ",[15,87,88],{},"-1"," ou ",[15,91,92],{},"+1",", é um número contínuo, ",[15,95,96],{},"0.7 * x + 0.2"," mais um ruído gaussiano pequeno. Não tem classe pra acertar, tem uma reta pra encontrar.",[27,99,101],{"className":29,"code":100,"language":31,"meta":32,"style":32},"class LinearRegression(BaseEstimator, ClassifierMixin):\n  def fit(self, X, y):\n    self.w_ = np.random.rand(X.shape[1])\n    self.b_ = np.random.rand()\n    for _ in range(self.max_iter):\n      y_pred = self.predict(X)\n      error = y - y_pred\n      self.w_ += np.dot(X.T, error) * self.learning_rate\n      self.b_ += np.sum(error) * self.learning_rate\n    return self\n\n  def predict(self, X):\n    return X @ self.w_ + self.b_\n",[15,102,103,108,113,118,123,128,133,138,144,150,156,163,169],{"__ignoreMap":32},[36,104,105],{"class":38,"line":39},[36,106,107],{},"class LinearRegression(BaseEstimator, ClassifierMixin):\n",[36,109,110],{"class":38,"line":45},[36,111,112],{},"  def fit(self, X, y):\n",[36,114,115],{"class":38,"line":51},[36,116,117],{},"    self.w_ = np.random.rand(X.shape[1])\n",[36,119,120],{"class":38,"line":57},[36,121,122],{},"    self.b_ = np.random.rand()\n",[36,124,125],{"class":38,"line":63},[36,126,127],{},"    for _ in range(self.max_iter):\n",[36,129,130],{"class":38,"line":69},[36,131,132],{},"      y_pred = self.predict(X)\n",[36,134,135],{"class":38,"line":75},[36,136,137],{},"      error = y - y_pred\n",[36,139,141],{"class":38,"line":140},8,[36,142,143],{},"      self.w_ += np.dot(X.T, error) * self.learning_rate\n",[36,145,147],{"class":38,"line":146},9,[36,148,149],{},"      self.b_ += np.sum(error) * self.learning_rate\n",[36,151,153],{"class":38,"line":152},10,[36,154,155],{},"    return self\n",[36,157,159],{"class":38,"line":158},11,[36,160,162],{"emptyLinePlaceholder":161},true,"\n",[36,164,166],{"class":38,"line":165},12,[36,167,168],{},"  def predict(self, X):\n",[36,170,172],{"class":38,"line":171},13,[36,173,174],{},"    return X @ self.w_ + self.b_\n",[11,176,177,178,182,183,188,189,192,193,196,197,200,201,203,204,208,209,306,307,310,311,314,315,318,319,323],{},"Repara como isso é ",[179,180,181],"strong",{},"quase idêntico"," ao perceptron vetorizado ",[184,185,187],"a",{"href":186},"\u002Fplaylists\u002Fneural-networks\u002Fperceptron-com-bias","do post passado",": mesmo ",[15,190,191],{},"X.T @ error"," pra atualizar peso, mesmo ",[15,194,195],{},"sum(error)"," pra atualizar bias. As duas diferenças são pequenas no código, mas mudam o significado inteiro: primeiro, ",[15,198,199],{},"predict"," não passa mais por ",[15,202,17],{},", a previsão fica contínua (ativação linear, ",[184,205,207],{"href":206},"\u002Fplaylists\u002Fneural-networks\u002Fmcculloch-pitts-perceptron","a mesma ideia que o Aggarwal descreve"," como a função de ativação mais simples que existe, ",[36,210,213,253],{"className":211},[212],"katex",[36,214,217],{"className":215},[216],"katex-mathml",[218,219,221],"math",{"xmlns":220},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[222,223,224,248],"semantics",{},[225,226,227,232,237,240,243,246],"mrow",{},[228,229,231],"mi",{"mathvariant":230},"normal","Φ",[233,234,236],"mo",{"stretchy":235},"false","(",[228,238,239],{},"v",[233,241,242],{"stretchy":235},")",[233,244,245],{},"=",[228,247,239],{},[249,250,252],"annotation",{"encoding":251},"application\u002Fx-tex","\\Phi(v) = v",[36,254,258,296],{"className":255,"ariaHidden":257},[256],"katex-html","true",[36,259,262,267,271,275,280,284,289,293],{"className":260},[261],"base",[36,263],{"className":264,"style":266},[265],"strut","height:1em;vertical-align:-0.25em;",[36,268,231],{"className":269},[270],"mord",[36,272,236],{"className":273},[274],"mopen",[36,276,239],{"className":277,"style":279},[270,278],"mathnormal","margin-right:0.0359em;",[36,281,242],{"className":282},[283],"mclose",[36,285],{"className":286,"style":288},[287],"mspace","margin-right:0.2778em;",[36,290,245],{"className":291},[292],"mrel",[36,294],{"className":295,"style":288},[287],[36,297,299,303],{"className":298},[261],[36,300],{"className":301,"style":302},[265],"height:0.4306em;",[36,304,239],{"className":305,"style":279},[270,278],"). Segundo, apareceu um ",[15,308,309],{},"learning_rate"," multiplicando a atualização. No perceptron essa taxa de aprendizado nem existia, era implicitamente ",[15,312,313],{},"1",". O Aggarwal chama isso de peculiaridade interessante do perceptron, dá pra fixar a taxa em 1 porque ela só reescala o peso, não muda a direção do ajuste. Aqui, com erro contínuo em vez de erro ",[15,316,317],{},"{-2,0,+2}",", a magnitude do ajuste pode ficar grande ou pequena demais dependendo da escala do erro, e por isso a taxa de aprendizado explícita vira necessária pra controlar o tamanho do passo, ",[184,320,322],{"href":321},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Fw2-lab03-feature-scaling","o mesmo assunto que já rendeu um post inteiro na outra playlist",".",[325,326,327],"blockquote",{},[11,328,329,332,333,336,337,340,341,344,345,348,349,352],{},[179,330,331],{},"Saída:"," RMSE ",[15,334,335],{},"0.0948",", pesos ",[15,338,339],{},"[0.706]",", bias ",[15,342,343],{},"0.204",". Bem perto do gerador de verdade (",[15,346,347],{},"0.7"," e ",[15,350,351],{},"0.2","), a diferença é só o ruído que foi embutido de propósito no dataset.",[11,354,355,356,359,360,363,364,348,367,370],{},"Uma observação honesta: a célula seguinte do notebook original chama ",[15,357,358],{},"createDataset"," (não ",[15,361,362],{},"createRegressionDataset","), ",[15,365,366],{},"accuracy_score",[15,368,369],{},"plotHyperplan",", nomes que não existem nesse notebook, só no da aula passada. Isso só rodou porque o Colab do professor estava com a sessão anterior ainda na memória (variável reaproveitada de aula pra aula). Rodando esse notebook do zero, essa célula quebraria. Não reproduzo esse pedaço aqui, porque ele não testa de fato o modelo de regressão que acabou de ser treinado.",[20,372,374],{"id":373},"a-equação-normal-a-mesma-pergunta-resolvida-sem-iterar","A equação normal: a mesma pergunta, resolvida sem iterar",[27,376,378],{"className":29,"code":377,"language":31,"meta":32,"style":32},"def include_bias(X):\n  return np.hstack((np.ones((X.shape[0],1)), X))\n\nclass NormalEquation(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",[15,379,380,385,390,394,399,403,408,413,417,421,425,429],{"__ignoreMap":32},[36,381,382],{"class":38,"line":39},[36,383,384],{},"def include_bias(X):\n",[36,386,387],{"class":38,"line":45},[36,388,389],{},"  return np.hstack((np.ones((X.shape[0],1)), X))\n",[36,391,392],{"class":38,"line":51},[36,393,162],{"emptyLinePlaceholder":161},[36,395,396],{"class":38,"line":57},[36,397,398],{},"class NormalEquation(BaseEstimator, ClassifierMixin):\n",[36,400,401],{"class":38,"line":63},[36,402,112],{},[36,404,405],{"class":38,"line":69},[36,406,407],{},"    X = include_bias(X)\n",[36,409,410],{"class":38,"line":75},[36,411,412],{},"    self.w_ = np.linalg.pinv(X) @ y\n",[36,414,415],{"class":38,"line":140},[36,416,155],{},[36,418,419],{"class":38,"line":146},[36,420,162],{"emptyLinePlaceholder":161},[36,422,423],{"class":38,"line":152},[36,424,168],{},[36,426,427],{"class":38,"line":158},[36,428,407],{},[36,430,431],{"class":38,"line":165},[36,432,433],{},"    return X @ self.w_\n",[11,435,436,437,440,441,443,444,447,448,451,452,454,455,458,459,323],{},"Duas coisas novas aqui. A primeira é o ",[15,438,439],{},"include_bias",": gruda uma coluna de ",[15,442,313],{},"s na frente de ",[15,445,446],{},"X",". Isso é literalmente o truque que eu descrevi em palavras ",[184,449,450],{"href":186},"no post passado",", o bias como peso de uma variável fantasma que vale sempre ",[15,453,313],{},", só que agora escrito como código de verdade em vez de um ",[15,456,457],{},"b_"," separado. Com essa coluna extra, o bias vira só mais um peso normal dentro de ",[15,460,461],{},"w_",[11,463,464,465,468,469,471,472,475,476,480,481,484,485,488,489,491,492,495,496,498],{},"A segunda é o ",[15,466,467],{},"NormalEquation"," em si: nada de laço, nada de ",[15,470,309],{},", só ",[15,473,474],{},"np.linalg.pinv(X) @ y",". Essa é a solução fechada ",[184,477,479],{"href":478},"\u002Fplaylists\u002Fpattern-recognition\u002Fnormal-equation","que eu já explorei com detalhe lá no Reconhecimento de Padrões",", a mesma pergunta (\"qual reta minimiza o erro quadrático\") resolvida direto, numa única conta, em vez de descida passo a passo. A diferença técnica aqui é o método: lá eu implementei via eliminação de Gauss-Jordan com pivotamento. O professor usa a ",[179,482,483],{},"pseudo-inversa"," (",[15,486,487],{},"pinv","), que resolve por decomposição SVD por baixo dos panos. A vantagem da pseudo-inversa é ela nunca travar: mesmo se ",[15,490,446],{}," tiver colunas redundantes (linearmente dependentes) e a matriz ",[15,493,494],{},"X^T X"," não puder ser invertida da forma tradicional, ",[15,497,487],{}," ainda devolve uma resposta válida (a de menor norma entre as infinitas soluções possíveis). Eliminação de Gauss-Jordan pura, nesse mesmo caso, simplesmente quebra.",[325,500,501],{},[11,502,503,332,505,336,508,511,512,514],{},[179,504,331],{},[15,506,507],{},"0.12364226682065012",[15,509,510],{},"[0.25402951 0.61056989]"," (bias e coeficiente, nessa ordem, por causa do ",[15,513,439],{},").",[11,516,517,518,521,522,525,526,529,530,533],{},"E o gradiente descendente da célula anterior, no mesmo dataset, chegou em RMSE ",[15,519,520],{},"0.12364226680246916",", pesos praticamente idênticos. Duas contas completamente diferentes (uma iterativa, uma fechada) convergindo pro mesmíssimo lugar, até a sétima casa decimal. Reproduzi isso eu mesmo com um dataset seedado (",[15,523,524],{},"np.random.seed(7)",") pra confirmar que não foi coincidência de uma rodada só: GD deu pesos ",[15,527,528],{},"[0.6478, bias 0.2041]",", equação normal deu ",[15,531,532],{},"[bias 0.2041, coef 0.6478]",", RMSE idêntico até a nona casa decimal nos dois.",[20,535,537],{"id":536},"interativo-ache-a-reta-você-mesmo","Interativo: ache a reta você mesmo",[11,539,540,541,544,545,348,548,551],{},"Antes de ver a máquina resolver, tenta resolver na mão. Esse é o mesmo dataset seedado de cima (20 pontos reais gerados por ",[15,542,543],{},"coef=0.7, intercept=0.2"," mais ruído), mexe nos sliders de ",[15,546,547],{},"w",[15,549,550],{},"b"," e observa o erro total mudando ao vivo.",[553,554],"model-playground",{":b-max":313,":b-min":555,":b-step":556,":initial-b":557,":initial-w":557,":w-max":558,":w-min":557,":w-step":556,":x-train":559,":y-train":560,"dataLabel":561,"prediction-label":562,"x-label":563,"y-label":84},"-0.5","0.05","0","1.5","[0.0763, 0.7799, 0.4384, 0.7235, 0.978, 0.5385, 0.5011, 0.0721, 0.2684, 0.4999, 0.6792, 0.8037, 0.3809, 0.0659, 0.2881, 0.9096, 0.2134, 0.4521, 0.9312, 0.0249]","[0.3089, 0.7583, 0.5343, 0.5538, 1.0497, 0.5924, 0.5121, 0.4533, 0.3834, 0.4048, 0.6349, 0.5338, 0.5716, 0.2045, 0.3274, 0.944, 0.1843, 0.57, 0.6454, 0.1512]","pontos de treino","reta ajustada","x",[20,565,567],{"id":566},"fechando","Fechando",[569,570,571,585],"table",{},[572,573,574],"thead",{},[575,576,577,582],"tr",{},[578,579,581],"th",{"align":580},"left","O que eu já sabia",[578,583,584],{"align":580},"O que essa aula assentou",[586,587,588,602,616],"tbody",{},[575,589,590,596],{},[591,592,593,594],"td",{"align":580},"Perceptron classifica com ",[15,595,17],{},[591,597,598,599,601],{"align":580},"Tirar o ",[15,600,17],{}," da mesma estrutura de código transforma classificação em regressão, quase sem mexer em mais nada",[575,603,604,607],{},[591,605,606],{"align":580},"Bias é peso de uma variável fantasma de valor 1",[591,608,609,610,612,613,615],{"align":580},"Isso vira código explícito com ",[15,611,439],{},", em vez de um ",[15,614,457],{}," separado",[575,617,618,621],{},[591,619,620],{"align":580},"Equação normal já resolvi via Gauss-Jordan",[591,622,623,625,626,628],{"align":580},[15,624,487],{}," (pseudo-inversa via SVD) resolve a mesma conta e ainda funciona quando ",[15,627,494],{}," não é invertível",[20,630,632],{"id":631},"aplicação-prática","Aplicação Prática",[11,634,635,636,640,641,644,645,648],{},"Testei gradiente descendente contra a equação normal no dataset real de 50 casas ",[184,637,639],{"href":638},"\u002Fplaylists\u002Fmachine-learning-specialization\u002Flab02-model-representation","que já apareceu na outra playlist",", usando só ",[15,642,643],{},"square_feet"," pra prever ",[15,646,647],{},"price",", sem normalizar nada primeiro.",[27,650,652],{"className":29,"code":651,"language":31,"meta":32,"style":32},"w, b = fit_gd(X, y, max_iter=1000, learning_rate=0.01)  # sem normalizar\n",[15,653,654],{"__ignoreMap":32},[36,655,656],{"class":38,"line":39},[36,657,651],{},[325,659,660],{},[11,661,662,664,665,668,669,672],{},[179,663,331],{}," o peso vira ",[15,666,667],{},"-inf"," já na iteração 71. O gradiente descendente ",[179,670,671],{},"diverge"," completamente.",[11,674,675,676,679,680,682,683,685,686,689],{},"Esperado: ",[184,677,678],{"href":321},"é a mesma lição da feature scaling na outra playlist",", só que reencontrada aqui dentro do contexto de rede neural. ",[15,681,643],{}," vive na casa das centenas e ",[15,684,647],{}," na casa das centenas de milhares, então o gradiente é enorme e o passo de ",[15,687,688],{},"0.01"," explode.",[569,691,692,703],{},[572,693,694],{},[575,695,696,699],{},[578,697,698],{"align":580},"Abordagem",[578,700,702],{"align":701},"right","RMSE",[586,704,705,713],{},[575,706,707,710],{},[591,708,709],{"align":580},"Gradiente descendente, dado normalizado",[591,711,712],{"align":701},"101878.42",[575,714,715,718],{},[591,716,717],{"align":580},"Equação normal, dado cru (sem normalizar)",[591,719,720],{"align":701},[179,721,712],{},[11,723,724,725,727],{},"Normalizando ",[15,726,643],{}," antes de rodar gradiente descendente, ele converge e chega exatamente no mesmo RMSE que a equação normal encontra direto no dado cru, sem precisar normalizar nada. Faz sentido: a equação normal resolve o sistema linear de uma vez só, então a escala das variáveis afeta só a estabilidade numérica da conta, não se ela converge ou não (diferente do gradiente descendente, que literalmente pode divergir se o passo for grande demais pra escala do dado).",[729,730,731],"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":32,"searchDepth":45,"depth":45,"links":733},[734,736,737,738,739],{"id":22,"depth":45,"text":735},"Trocando classificação por regressão: só tira o sign()",{"id":373,"depth":45,"text":374},{"id":536,"depth":45,"text":537},{"id":566,"depth":45,"text":567},{"id":631,"depth":45,"text":632},null,"2026-08-20","Aula 2c e 2d: o professor troca o sinal binário pela reta contínua (regressão linear) e depois substitui gradiente descendente inteiro por uma única conta fechada, a equação normal via pseudo-inversa.","md",{},"\u002Fpt\u002Fplaylists\u002Fneural-networks\u002Fregressao-e-equacao-normal","neural-networks",{"title":6,"description":742},"published","pt\u002Fplaylists\u002Fneural-networks\u002Fregressao-e-equacao-normal",[751,752,753],"regressao-linear","equacao-normal","gradiente-descendente","WaS0edSiFTL2UX4OXXs55rOfEizPZpaLDDENEz5LVik",1787338982948]