[{"data":1,"prerenderedAt":820},["ShallowReactive",2],{"lang-switch-post-\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro":3,"post-pt-pattern-recognition-nlp-intro":4},"\u002Fen\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro",{"id":5,"title":6,"body":7,"cover":805,"date":806,"description":807,"extension":808,"meta":809,"navigation":74,"order":206,"path":810,"playlist":811,"seo":812,"status":813,"stem":814,"tags":815,"__hash__":819},"posts\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro.md","Texto Vira Vetor: Meus Primeiros Passos em NLP",{"type":8,"value":9,"toc":795},"minimark",[10,31,36,39,99,113,121,125,228,235,250,262,266,276,291,305,309,312,398,405,420,427,431,439,511,533,542,549,555,560,564,567,618,633,637,680,687,691,709,734,751,788,791],[11,12,13,14,19,20,24,25,30],"p",{},"Aula 14. A última da matéria, e o assunto fecha a playlist inteira com uma virada bacana: todo modelo que eu usei até aqui, ",[15,16,18],"a",{"href":17},"\u002Fplaylists\u002Fpattern-recognition\u002Flinear-regression-estimator","da regressão linear lá no primeiro post"," até a ",[15,21,23],{"href":22},"\u002Fplaylists\u002Fpattern-recognition\u002Ffeature-selection","seleção de características no post anterior",", espera um vetor de número como entrada. Pixel já é número. Medida química de vinho já é número. Mas texto, uma resenha de filme escrita por gente de verdade, não é número nenhum. Essa aula é sobre o primeiro passo de qualquer ",[26,27,29],"glossary-term",{"definition":28},"Processamento de Linguagem Natural, o campo de ML que lida com texto: das buscas do Google até o corretor do teclado do celular","NLP"," (Natural Language Processing): como transformar texto em vetor sem perder o que importa.",[32,33,35],"h2",{"id":34},"padronizar-e-tokenizar-o-mínimo-necessário","Padronizar e tokenizar: o mínimo necessário",[11,37,38],{},"Antes de qualquer conta, o professor limpa o texto: tudo minúsculo, pontuação fora.",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-python shiki shiki-themes github-light github-dark","def standardize(text):\n    text = text.lower()\n    return \"\".join(c for c in text if c not in string.punctuation)\n\ndef tokenize(text):\n    return standardize(text).split()\n\ntokenize(\"I write, erase, rewrite, erase again, and then a poppy blooms!\")\n","python","",[47,48,49,57,63,69,76,82,88,93],"code",{"__ignoreMap":45},[50,51,54],"span",{"class":52,"line":53},"line",1,[50,55,56],{},"def standardize(text):\n",[50,58,60],{"class":52,"line":59},2,[50,61,62],{},"    text = text.lower()\n",[50,64,66],{"class":52,"line":65},3,[50,67,68],{},"    return \"\".join(c for c in text if c not in string.punctuation)\n",[50,70,72],{"class":52,"line":71},4,[50,73,75],{"emptyLinePlaceholder":74},true,"\n",[50,77,79],{"class":52,"line":78},5,[50,80,81],{},"def tokenize(text):\n",[50,83,85],{"class":52,"line":84},6,[50,86,87],{},"    return standardize(text).split()\n",[50,89,91],{"class":52,"line":90},7,[50,92,75],{"emptyLinePlaceholder":74},[50,94,96],{"class":52,"line":95},8,[50,97,98],{},"tokenize(\"I write, erase, rewrite, erase again, and then a poppy blooms!\")\n",[100,101,102],"blockquote",{},[11,103,104,108,109,112],{},[105,106,107],"strong",{},"Saída:"," ",[47,110,111],{},"['i', 'write', 'erase', 'rewrite', 'erase', 'again', 'and', 'then', 'a', 'poppy', 'blooms']",".",[11,114,115,116,120],{},"Repara que \"Erase\" e \"erase\" agora são a mesma palavra, e a vírgula depois de \"erase\" sumiu sem grudar na palavra seguinte. Sem esse passo, o modelo trataria \"erase\" e \"erase,\" (com vírgula colada) como duas palavras completamente diferentes, e \"Write\" e \"write\" também, inflando o vocabulário com duplicata que não deveria existir. É o mesmo espírito da normalização que já vi ",[15,117,119],{"href":118},"\u002Fplaylists\u002Fpattern-recognition\u002Fknn-classifier","lá no post de KNN",", só que em vez de reescalar número, aqui é reescalar texto pra uma forma canônica.",[32,122,124],{"id":123},"o-vetorizador-caseiro-palavra-vira-índice","O vetorizador caseiro: palavra vira índice",[40,126,128],{"className":42,"code":127,"language":44,"meta":45,"style":45},"class Vectorizer:\n    def standardize(self, text):\n        ...\n    def tokenize(self, text):\n        ...\n    def make_vocabolary(self, dataset):\n        self.vocabulary = {\"\": 0, \"[UNK]\": 1}\n        for text in dataset:\n            text = self.standardize(text)\n            tokens = self.tokenize(text)\n            for token in tokens:\n                if token not in self.vocabulary:\n                    self.vocabulary[token] = len(self.vocabulary)\n\n    def encode(self, text):\n        text = self.standardize(text)\n        tokens = self.tokenize(text)\n        return [self.vocabulary.get(token, 1) for token in tokens]\n",[47,129,130,135,140,145,150,154,159,164,169,175,181,187,193,199,204,210,216,222],{"__ignoreMap":45},[50,131,132],{"class":52,"line":53},[50,133,134],{},"class Vectorizer:\n",[50,136,137],{"class":52,"line":59},[50,138,139],{},"    def standardize(self, text):\n",[50,141,142],{"class":52,"line":65},[50,143,144],{},"        ...\n",[50,146,147],{"class":52,"line":71},[50,148,149],{},"    def tokenize(self, text):\n",[50,151,152],{"class":52,"line":78},[50,153,144],{},[50,155,156],{"class":52,"line":84},[50,157,158],{},"    def make_vocabolary(self, dataset):\n",[50,160,161],{"class":52,"line":90},[50,162,163],{},"        self.vocabulary = {\"\": 0, \"[UNK]\": 1}\n",[50,165,166],{"class":52,"line":95},[50,167,168],{},"        for text in dataset:\n",[50,170,172],{"class":52,"line":171},9,[50,173,174],{},"            text = self.standardize(text)\n",[50,176,178],{"class":52,"line":177},10,[50,179,180],{},"            tokens = self.tokenize(text)\n",[50,182,184],{"class":52,"line":183},11,[50,185,186],{},"            for token in tokens:\n",[50,188,190],{"class":52,"line":189},12,[50,191,192],{},"                if token not in self.vocabulary:\n",[50,194,196],{"class":52,"line":195},13,[50,197,198],{},"                    self.vocabulary[token] = len(self.vocabulary)\n",[50,200,202],{"class":52,"line":201},14,[50,203,75],{"emptyLinePlaceholder":74},[50,205,207],{"class":52,"line":206},15,[50,208,209],{},"    def encode(self, text):\n",[50,211,213],{"class":52,"line":212},16,[50,214,215],{},"        text = self.standardize(text)\n",[50,217,219],{"class":52,"line":218},17,[50,220,221],{},"        tokens = self.tokenize(text)\n",[50,223,225],{"class":52,"line":224},18,[50,226,227],{},"        return [self.vocabulary.get(token, 1) for token in tokens]\n",[11,229,230,231,234],{},"A ideia é literal: cada palavra vista no treino ganha um número inteiro, um índice num dicionário. \"",[50,232,233],{},"UNK","\" (unknown) fica reservado no índice 1 desde o início, pra qualquer palavra nova que apareça depois, numa frase de teste, e não estava no vocabulário de treino. O professor testa isso com uma frase que usa a palavra \"still\", ausente do dataset de treino de 3 frases:",[100,236,237],{},[11,238,239,241,242,245,246,249],{},[105,240,107],{}," a palavra \"still\" vira o índice ",[47,243,244],{},"1",", o mesmo de ",[47,247,248],{},"[UNK]",", enquanto as outras palavras da frase (que já apareciam no treino) viram seus índices de verdade.",[11,251,252,253,257,258,261],{},"Isso é o mesmo problema que categoria nova resolvia ",[15,254,256],{"href":255},"\u002Fplaylists\u002Fpattern-recognition\u002Ftitanic","lá no post do Titanic"," com o ",[47,259,260],{},"OneHotEncoder",": o que fazer quando o dado de produção traz algo que o treino nunca viu. Lá era categoria de embarque, aqui é palavra.",[32,263,265],{"id":264},"o-dataset-50-mil-resenhas-de-filme-de-verdade","O dataset: 50 mil resenhas de filme, de verdade",[11,267,268,269,275],{},"O professor baixa o ",[15,270,274],{"href":271,"rel":272},"https:\u002F\u002Fai.stanford.edu\u002F~amaas\u002Fdata\u002Fsentiment\u002F",[273],"nofollow","IMDB Large Movie Review Dataset",", da Stanford, um dos datasets mais citados da área de análise de sentimento: 50 mil resenhas reais do site IMDB, metade marcada como positiva, metade como negativa, já divididas 25 mil treino \u002F 25 mil teste.",[40,277,279],{"className":42,"code":278,"language":44,"meta":45,"style":45},"train_df = text_dataset_from_directory('aclImdb\u002Ftrain')\ntest_df = text_dataset_from_directory('aclImdb\u002Ftest')\n",[47,280,281,286],{"__ignoreMap":45},[50,282,283],{"class":52,"line":53},[50,284,285],{},"train_df = text_dataset_from_directory('aclImdb\u002Ftrain')\n",[50,287,288],{"class":52,"line":59},[50,289,290],{},"test_df = text_dataset_from_directory('aclImdb\u002Ftest')\n",[100,292,293],{},[11,294,295,297,298,301,302,112],{},[105,296,107],{}," 25000 linhas de treino, 25000 de teste, colunas ",[47,299,300],{},"text"," e ",[47,303,304],{},"label",[32,306,308],{"id":307},"bag-of-words-cada-palavra-é-um-voto-sem-ordem","Bag-of-words: cada palavra é um voto, sem ordem",[11,310,311],{},"A primeira estratégia pra vetorizar uma resenha inteira: um vetor do tamanho do vocabulário, com 1 em cada posição cuja palavra aparece na resenha, 0 no resto. Ignora completamente a ordem das palavras, só marca presença.",[40,313,315],{"className":42,"code":314,"language":44,"meta":45,"style":45},"class UnigramTransformer(BaseEstimator, TransformerMixin):\n    def fit(self, X, y=None):\n        self.vocabulary = {\"\": 0, \"[UNK]\": 1}\n        for text in X:\n            for token in set(self.tokenize(text)):\n                if token not in self.vocabulary and len(self.vocabulary) \u003C self.max_features:\n                    self.vocabulary[token] = len(self.vocabulary)\n        return self\n\n    def transform(self, X, y=None):\n        rows, cols, data = [], [], []\n        for row, text in enumerate(X):\n            for token in set(self.tokenize(text)):\n                rows.append(row)\n                cols.append(self.vocabulary.get(token, 1))\n                data.append(1)\n        return csr_matrix((data, (rows, cols)), shape=(len(X), len(self.vocabulary)))\n",[47,316,317,322,327,331,336,341,346,350,355,359,364,369,374,378,383,388,393],{"__ignoreMap":45},[50,318,319],{"class":52,"line":53},[50,320,321],{},"class UnigramTransformer(BaseEstimator, TransformerMixin):\n",[50,323,324],{"class":52,"line":59},[50,325,326],{},"    def fit(self, X, y=None):\n",[50,328,329],{"class":52,"line":65},[50,330,163],{},[50,332,333],{"class":52,"line":71},[50,334,335],{},"        for text in X:\n",[50,337,338],{"class":52,"line":78},[50,339,340],{},"            for token in set(self.tokenize(text)):\n",[50,342,343],{"class":52,"line":84},[50,344,345],{},"                if token not in self.vocabulary and len(self.vocabulary) \u003C self.max_features:\n",[50,347,348],{"class":52,"line":90},[50,349,198],{},[50,351,352],{"class":52,"line":95},[50,353,354],{},"        return self\n",[50,356,357],{"class":52,"line":171},[50,358,75],{"emptyLinePlaceholder":74},[50,360,361],{"class":52,"line":177},[50,362,363],{},"    def transform(self, X, y=None):\n",[50,365,366],{"class":52,"line":183},[50,367,368],{},"        rows, cols, data = [], [], []\n",[50,370,371],{"class":52,"line":189},[50,372,373],{},"        for row, text in enumerate(X):\n",[50,375,376],{"class":52,"line":195},[50,377,340],{},[50,379,380],{"class":52,"line":201},[50,381,382],{},"                rows.append(row)\n",[50,384,385],{"class":52,"line":206},[50,386,387],{},"                cols.append(self.vocabulary.get(token, 1))\n",[50,389,390],{"class":52,"line":212},[50,391,392],{},"                data.append(1)\n",[50,394,395],{"class":52,"line":218},[50,396,397],{},"        return csr_matrix((data, (rows, cols)), shape=(len(X), len(self.vocabulary)))\n",[11,399,400,401,404],{},"Repara no ",[47,402,403],{},"csr_matrix",": com um vocabulário de 10 mil palavras e cada resenha usando só umas poucas centenas delas, a matriz é quase toda zero. Guardar um vetor denso de 10 mil posições, quase todas zero, pra cada uma das 20 mil resenhas seria um desperdício gigante de memória. O formato esparso guarda só as posições com valor diferente de zero.",[40,406,408],{"className":42,"code":407,"language":44,"meta":45,"style":45},"pipeline = Pipeline([(\"vectorizer\", UnigramTransformer(10000)), (\"classifier\", RandomForestClassifier(random_state=42))])\npipeline.fit(train_texts, train_labels)\n",[47,409,410,415],{"__ignoreMap":45},[50,411,412],{"class":52,"line":53},[50,413,414],{},"pipeline = Pipeline([(\"vectorizer\", UnigramTransformer(10000)), (\"classifier\", RandomForestClassifier(random_state=42))])\n",[50,416,417],{"class":52,"line":59},[50,418,419],{},"pipeline.fit(train_texts, train_labels)\n",[100,421,422],{},[11,423,424,426],{},[105,425,107],{}," 0.832 de acurácia na validação, só olhando pra quais palavras aparecem, sem ordem nenhuma.",[32,428,430],{"id":429},"tf-idf-nem-toda-palavra-pesa-igual","TF-IDF: nem toda palavra pesa igual",[11,432,433,434,438],{},"Bag-of-words trata \"the\" (aparece em quase toda resenha) e \"wonderful\" (aparece só nas boas) do mesmo jeito: os dois contam 1 se aparecem. Mas \"the\" não carrega informação nenhuma sobre a resenha ser boa ou ruim, já \"wonderful\" carrega bastante. O ",[26,435,437],{"definition":436},"Term Frequency times Inverse Document Frequency: pondera cada palavra pela frequência dentro do texto, multiplicada pelo quão rara ela é no restante do corpus","TF-IDF"," resolve isso com duas contas multiplicadas:",[40,440,442],{"className":42,"code":441,"language":44,"meta":45,"style":45},"class TfidfTransformer(BaseEstimator, TransformerMixin):\n    def fit(self, X, y=None):\n        doc_freq = {}\n        for text in X:\n            for token in set(self.tokenize(text)):\n                doc_freq[token] = doc_freq.get(token, 0) + 1\n        ...\n        self.idf = {token: math.log(len(X) \u002F freq) for token, freq in doc_freq.items()}\n        return self\n\n    def transform(self, X, y=None):\n        ...\n        for token, count in token_counts.items():\n            tf = count \u002F total_tokens_no_documento\n            value = tf * self.idf[token]\n",[47,443,444,449,453,458,462,466,471,475,480,484,488,492,496,501,506],{"__ignoreMap":45},[50,445,446],{"class":52,"line":53},[50,447,448],{},"class TfidfTransformer(BaseEstimator, TransformerMixin):\n",[50,450,451],{"class":52,"line":59},[50,452,326],{},[50,454,455],{"class":52,"line":65},[50,456,457],{},"        doc_freq = {}\n",[50,459,460],{"class":52,"line":71},[50,461,335],{},[50,463,464],{"class":52,"line":78},[50,465,340],{},[50,467,468],{"class":52,"line":84},[50,469,470],{},"                doc_freq[token] = doc_freq.get(token, 0) + 1\n",[50,472,473],{"class":52,"line":90},[50,474,144],{},[50,476,477],{"class":52,"line":95},[50,478,479],{},"        self.idf = {token: math.log(len(X) \u002F freq) for token, freq in doc_freq.items()}\n",[50,481,482],{"class":52,"line":171},[50,483,354],{},[50,485,486],{"class":52,"line":177},[50,487,75],{"emptyLinePlaceholder":74},[50,489,490],{"class":52,"line":183},[50,491,363],{},[50,493,494],{"class":52,"line":189},[50,495,144],{},[50,497,498],{"class":52,"line":195},[50,499,500],{},"        for token, count in token_counts.items():\n",[50,502,503],{"class":52,"line":201},[50,504,505],{},"            tf = count \u002F total_tokens_no_documento\n",[50,507,508],{"class":52,"line":206},[50,509,510],{},"            value = tf * self.idf[token]\n",[11,512,513,516,517,520,521,524,525,528,529,532],{},[105,514,515],{},"TF"," (term frequency) é simples: quantas vezes a palavra aparece na resenha, dividido pelo total de palavras dela, o peso \"local\". ",[105,518,519],{},"IDF"," (inverse document frequency) é o peso \"global\": ",[47,522,523],{},"log(total de documentos \u002F quantos documentos contêm a palavra)",". Palavra que aparece em quase todo documento (tipo \"the\") tem IDF perto de zero, quase não pesa nada. Já palavra rara, presente em poucos documentos, tem IDF alto. Multiplicando os dois, uma palavra só ganha peso alto se for frequente ",[105,526,527],{},"naquela"," resenha específica ",[105,530,531],{},"e"," rara no restante do corpus, exatamente o tipo de palavra que ajuda a diferenciar uma resenha da outra.",[40,534,536],{"className":42,"code":535,"language":44,"meta":45,"style":45},"pipeline = Pipeline([(\"vectorizer\", TfidfTransformer(10000)), (\"classifier\", RandomForestClassifier(random_state=42))])\n",[47,537,538],{"__ignoreMap":45},[50,539,540],{"class":52,"line":53},[50,541,535],{},[100,543,544],{},[11,545,546,548],{},[105,547,107],{}," 0.8316 de acurácia na validação, praticamente empatado com bag-of-words (0.832).",[11,550,551,552,554],{},"Interativo: digita uma frase abaixo (em inglês, o mesmo idioma do dataset) e vê o peso TF-IDF de cada palavra, calculado contra um corpuzinho de 16 resenhas curtas de exemplo. Palavra comum tipo \"the\" ou \"was\" fica com barra curta, palavra rara e carregada tipo \"wonderful\", \"terrible\" ou \"boring\" fica com barra bem mais alta, e palavra que esse corpo de exemplo nunca viu aparece marcada como ",[47,553,248],{},", sem peso, o mesmo problema do vetorizador caseiro lá em cima.",[556,557],"text-vectorizer-explorer",{"label":558,"readout-suffix":559,"unk-label":233},"Digite uma frase (em inglês, mesmo vocabulário do corpus de exemplo)","token(s) reconhecidos no vocabulário de exemplo",[32,561,563],{"id":562},"no-dataset-inteiro-e-contra-o-tfidfvectorizer-oficial","No dataset inteiro, e contra o TfidfVectorizer oficial",[11,565,566],{},"Com as duas técnicas testadas na validação, o professor treina de novo em cima do treino completo (25 mil resenhas, não só a fatia de 20 mil) e mede no teste real:",[568,569,570,585],"table",{},[571,572,573],"thead",{},[574,575,576,581],"tr",{},[577,578,580],"th",{"align":579},"left","Técnica",[577,582,584],{"align":583},"right","Acurácia no teste",[586,587,588,597,607],"tbody",{},[574,589,590,594],{},[591,592,593],"td",{"align":579},"Bag-of-words (unigram)",[591,595,596],{"align":583},"0.83804",[574,598,599,602],{},[591,600,601],{"align":579},"TF-IDF caseiro",[591,603,604],{"align":583},[105,605,606],{},"0.84292",[574,608,609,615],{},[591,610,611,614],{"align":579},[47,612,613],{},"TfidfVectorizer"," do scikit-learn",[591,616,617],{"align":583},"0.83872",[11,619,620,621,301,625,629,630,632],{},"TF-IDF ganha por uma margem pequena do bag-of-words, e o mais importante: minha implementação caseira de TF-IDF (0.84292) fica a menos de meio ponto percentual da implementação oficial do scikit-learn (0.83872), a mesma régua de \"bate com o profissional, na casa decimal\" que já vi ",[15,622,624],{"href":623},"\u002Fplaylists\u002Fpattern-recognition\u002Fnormal-equation","lá na equação normal",[15,626,628],{"href":627},"\u002Fplaylists\u002Fpattern-recognition\u002Fpca","no PCA",". O ",[47,631,613],{}," de verdade tem otimizações e detalhes a mais (normalização L2 do vetor final, por exemplo), mas a ideia central, a mesma conta de TF vezes IDF, é idêntica.",[32,634,636],{"id":635},"fechando","Fechando",[568,638,639,649],{},[571,640,641],{},[574,642,643,646],{},[577,644,645],{"align":579},"O que eu já sabia",[577,647,648],{"align":579},"O que essa aula assentou",[586,650,651,659,669],{},[574,652,653,656],{},[591,654,655],{"align":579},"Todo modelo espera um vetor de número",[591,657,658],{"align":579},"Texto também precisa virar vetor, e o jeito de fazer isso é uma escolha de design, não um detalhe automático",[574,660,661,664],{},[591,662,663],{"align":579},"Categoria nova em produção precisa de um plano",[591,665,666,668],{"align":579},[47,667,248],{}," resolve pra palavra o mesmo problema que categoria desconhecida resolvia pro Titanic",[574,670,671,674],{},[591,672,673],{"align":579},"Bater com o scikit-learn valida a implementação caseira",[591,675,676,677,679],{"align":579},"TF-IDF montado na mão (0.84292) chega perto o bastante do ",[47,678,613],{}," oficial (0.83872) pra confirmar que a lógica está certa",[11,681,682,683,686],{},"E com isso fecho as 14 aulas dessa matéria. Comecei ",[15,684,685],{"href":17},"ajustando uma reta com equação normal"," e termino aqui, transformando resenha de filme em vetor de palavra pesada. No meio do caminho teve árvore, ensemble, cluster, redução de dimensão, dado desbalanceado, e o Bishop segurando a ponta teórica de quase tudo isso, menos das últimas três aulas (DBSCAN, semi-supervisionado e NLP), que vieram depois do livro dele de 2006 e mesmo assim encaixaram direitinho na mesma lógica de sempre: dado vira vetor, vetor vira decisão. Valeu, professor Boldt.",[32,688,690],{"id":689},"aplicação-prática","Aplicação Prática",[11,692,693,694,696,697,700,701,704,705,708],{},"Pra confirmar o achado da aula (bag-of-words, TF-IDF caseiro e ",[47,695,613],{}," oficial dando resultado parecido) num dataset diferente do IMDB, usei o ",[47,698,699],{},"20newsgroups"," do próprio scikit-learn: posts de fórum reais, sobre dois assuntos bem distintos, ",[47,702,703],{},"sci.space"," (espaço) e ",[47,706,707],{},"rec.sport.baseball"," (beisebol), 1190 posts de treino e 791 de teste.",[40,710,712],{"className":42,"code":711,"language":44,"meta":45,"style":45},"from sklearn.datasets import fetch_20newsgroups\ncats = ['sci.space', 'rec.sport.baseball']\ntrain = fetch_20newsgroups(subset='train', categories=cats, remove=('headers','footers','quotes'), random_state=42)\ntest = fetch_20newsgroups(subset='test', categories=cats, remove=('headers','footers','quotes'), random_state=42)\n",[47,713,714,719,724,729],{"__ignoreMap":45},[50,715,716],{"class":52,"line":53},[50,717,718],{},"from sklearn.datasets import fetch_20newsgroups\n",[50,720,721],{"class":52,"line":59},[50,722,723],{},"cats = ['sci.space', 'rec.sport.baseball']\n",[50,725,726],{"class":52,"line":65},[50,727,728],{},"train = fetch_20newsgroups(subset='train', categories=cats, remove=('headers','footers','quotes'), random_state=42)\n",[50,730,731],{"class":52,"line":71},[50,732,733],{},"test = fetch_20newsgroups(subset='test', categories=cats, remove=('headers','footers','quotes'), random_state=42)\n",[11,735,736,737,301,740,743,744,746,747,750],{},"Reproduzi as três abordagens (",[47,738,739],{},"UnigramTransformer",[47,741,742],{},"TfidfTransformer"," do jeito exato que o professor fez, mais o ",[47,745,613],{}," oficial), com ",[47,748,749],{},"RandomForestClassifier(random_state=42)"," em cima de um vocabulário de 5000 palavras:",[568,752,753,761],{},[571,754,755],{},[574,756,757,759],{},[577,758,580],{"align":579},[577,760,584],{"align":583},[586,762,763,770,777],{},[574,764,765,767],{},[591,766,593],{"align":579},[591,768,769],{"align":583},"0.8786",[574,771,772,774],{},[591,773,601],{"align":579},[591,775,776],{"align":583},"0.8774",[574,778,779,783],{},[591,780,781,614],{"align":579},[47,782,613],{},[591,784,785],{"align":583},[105,786,787],{},"0.8963",[11,789,790],{},"Três números bem próximos de novo, num assunto e num dataset totalmente diferentes dos filmes do IMDB, o que dá confiança de que o padrão da aula não foi coincidência daquele dataset específico: qualquer vetorização razoável de texto (presença de palavra ou TF-IDF) já entrega a maior parte do sinal útil pra separar duas classes bem distintas, aqui espaço de foguete contra taco de beisebol.",[792,793,794],"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":45,"searchDepth":59,"depth":59,"links":796},[797,798,799,800,801,802,803,804],{"id":34,"depth":59,"text":35},{"id":123,"depth":59,"text":124},{"id":264,"depth":59,"text":265},{"id":307,"depth":59,"text":308},{"id":429,"depth":59,"text":430},{"id":562,"depth":59,"text":563},{"id":635,"depth":59,"text":636},{"id":689,"depth":59,"text":690},null,"2026-08-20","Aula 14, a última da matéria: o professor transforma resenha de filme em número, na mão, e mostra que um TF-IDF caseiro bate com o TfidfVectorizer oficial do scikit-learn.","md",{},"\u002Fpt\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro","pattern-recognition",{"title":6,"description":807},"published","pt\u002Fplaylists\u002Fpattern-recognition\u002Fnlp-intro",[816,817,818],"nlp","tfidf","texto","jfq4X0Wib-h99AJDbGIBwiEnx3DPe564o_yW9JMbIcw",1787338983539]