Cozinhando com Aprendizado de Máquina: Redução de Dimensão

Recentemente me deparei com este conjunto de dados de receitas de culinária em Kaggle, e isso me inspirou a combinar dois dos meus principais interesses na vida. Alimentação e aprendizado de máquina.

Diego Toledo Segue 1 de ago de 2018 · 6 min ler

O que torna este conjunto de dados especial é que contém receitas de 20 cozinhas diferentes, 6714 ingredientes diferentes, mas apenas 26648 amostras. Algumas cozinhas têm menos receitas do que outras.

Isso é demais recursos para essa quantidade de dados. O primeiro passo antes de trabalhar com esse conjunto de dados deve ser reduzir suas dimensões. E neste post mostrarei como usar o PCA para reduzir esses 6714 ingredientes em um espaço latente com apenas 700 dimensões. E como bônus, usaremos esse modelo como um detector de anomalias.

Aqui está como os dados se parecem:

 { 
"id": 24717,
"cozinha": "indiano",
"ingredientes": [
"tumeric",
"caldo de legumes",
"tomates",
"garam masala",
"naan"
"lentilhas vermelhas",
"pimenta vermelha",
"cebolas"
"espinafre",
"batatas doces"
]
}

O aspecto mais desafiador é que é muito escasso, aqui está a composição dos ingredientes por receita:

 significa 10.76771257605471 
min 1 #rice!
Receita de fettuccine com no máximo 65 #
std 4.428921893064523

Outra maneira de ver isso é verificar este histograma de variância:

E se nós aumentarmos um pouco:

O que significa que, em média, cada linha de 6714 recursos tem apenas 10 recursos ativos. Menos de 1%. Isso deve dificultar as coisas para dividir os dados em um conjunto de treinamento e teste. Por causa disso, é muito provável que você acabe com receitas com padrões completamente diferentes em ambos os conjuntos. Uma boa maneira de resolver esse problema seria dobrar os dados, mas não neste caso. Os dados são muito escassos, não melhorariam muito.

Para um conjunto de dados esparsos com tantos recursos, um primeiro passo é geralmente reduzir o número de dimensões. Você não ouviu falar da maldição dimensionalidade?

Para este post vou usar um método muito popular para reduzir dimensões: PCA

Transformando os dados

Hora de se ocupar! Vamos fazer algumas transformações básicas nos dados. A ideia principal aqui é que, como temos dados qualitativos, precisamos fazer algo chamado codificação de um-quente. Longa história curta: 6714 ingredientes -> 6714 colunas. Quando um ingrediente está presente em uma receita, sua coluna vai para 1. Todo o resto permanece como 0. Em média, apenas 10 dessas colunas estarão "ativas" em cada linha. Este código irá criar o "transformador", que irá obter um ingrediente e produzir sua representação vetorial

 de sklearn.preprocessing import LabelEncoder 
de sklearn.preprocessing import OneHotEncoder
da matriz de importação numpy
import json
f = open ('recipes_train.json', 'r')
recipes_train_txt = f.read ()
recipes_train_json = json.loads (
recipes_train_txt ) #get lista de ingredientes
ingredientes = set ()
ingredients_matrix = []
para receita em recipes_train_json: ingredients_matrix.append (recipe ["ingredients"])
para ingred in recipe ["ingredientes"]:
ingredients.add (ingred) ingredients = list (ingredientes) ingredients.sort () #it facilitou a minha vida para ordenar quando eu precisava verificar o que é o que nos valores vetoriais codificados = array (ingredientes) label_encoder = LabelEncoder () # fornece um valor int exclusivo para cada ingrediente de string e salva o #mapping. você precisa disso para o codificador. algo como:
# ['banana'] -> [1]
integer_encoded = label_encoder.fit_transform (valores)

onehot_encoder = OneHotEncoder (sparse = False)
integer_encoded = integer_encoded.reshape (len (integer_encoded), 1)
#aqui você codifica algo como: [2] -> [0,1,0,0, ...]
onehot_encoded = onehot_encoder.fit_transform (integer_encoded)
def transform_value (s):

l = array ([s])
integer_encoded = label_encoder.transform (l)
integer_encoded = integer_encoded.reshape (len (integer_encoded), 1)
onehot_encoded = onehot_encoder.transform (integer_encoded)

devoluções onehot_encoded [0]

Este código nos dá um codificador que irá obter um ingrediente (string) como entrada e saída de sua representação vetorial. O vetor final contendo todos os ingredientes da receita será o resultado de um 'lógico ou' em cada um desses vetores de ingredientes

PCA

PCA é uma escolha muito popular. Ele é usado como uma ferramenta de pré-processamento antes de alimentar o novo conjunto de dados reduzido a ser visualizado com t-sne, mas também é a ferramenta que você pode usar para reduzir seus recursos antes de alimentar um algoritmo de aprendizado de máquina.

Isso é desejável porque quanto mais recursos você tiver, mais dados serão necessários e mais lento será o processo de aprendizado. Assim, manter as coisas pequenas aumentará seu desempenho.

Mas antes de minimizar os dados, você precisa fazer uma ligação: quão pequena você quer isso? Há uma desvantagem aqui, quanto menor você for, mais informações você perde. Você pode medir isso usando o mesmo modelo treinado usado para minimizar os dados, para depois maximizar o tamanho original. Depois disso, você pode comparar as duas amostras e medir o quão diferentes elas são (lembre-se, você perde as informações quando desce).

Então, vamos apenas treinar vários modelos diferentes e escolher um com poucos recursos, mas com um erro de reconstrução baixo.

No eixo X, temos o número de vetores componentes, enquanto no eixo Y, o erro de reconstrução para toda a amostra (usando L2). Esses resultados parecem bons, certo? Mas vamos cavar mais fundo aqui. 700 parece ser um número seguro para escolher, não há muita melhoria em torno dessa área. Já é uma melhoria enorme de 6714 recursos. Vamos comparar com alguns dados não vistos, o conjunto de testes.

Aqui podemos ver que a PCA fez um trabalho decente na generalização da estrutura dos dados. O erro médio quadrático do trem ~ = 0,000171% e o erro quadrático médio do teste ~ = 0,0002431% . Não é ruim.

Sabemos de antes que os dados têm em média 10 ingredientes, desvio padrão de 4,42. E se criarmos algumas 'receitas aleatórias' usando essa distribuição (escolhendo ingredientes aleatoriamente)? Isto pode ser conseguido usando um gerador gaussiano. Se o PCA aprendeu alguma coisa, deveríamos estar vendo alguns erros importantes de reconstrução.

 para n_candidates no intervalo (N_CANDIDATES): 
dna = [0 para i na faixa (N_INGREDIENTS)]

n_flips = int (arredondado (random.gauss (mu = MEAN, sigma = STD)))
indexes_to_flip = [random.randint (0, N_INGREDIENTS-1) para i no intervalo (n_flips)]
para i no intervalo (n_flips):

dna [indexes_to_flip [i]] = 1
BAD_DATA.append (dna)

Vamos ver como o modelo faz com esses dados falsos.

Esse não foi o objetivo inicial aqui, mas parece que temos um bom modelo para detectar receitas de anomalia. Se definirmos um limiar para 0,0004 e considerarmos qualquer coisa com um erro de reconstrução maior do que uma anomalia, obtemos a seguinte matriz:

Conclusão

Reduzimos esse conjunto de dados de 6714 recursos para apenas 700.

Podemos concluir agora que o modelo aprendeu algo com o conjunto de treinamento. Nós tentamos enganar o modelo PCA e aprendemos que alguns ingredientes geralmente se juntam e outros não se misturam. Você também pode usar esse modelo como uma detecção de anomalia, em que as receitas ruins são anomalias (você não deve comê-las). Talvez, como um projeto de acompanhamento, eu possa tentar aproveitar esse "aprendizado".

Como você pode ver, há um padrão entre as diferentes cozinhas. Já temos um modelo para detectar receitas de anomalia que não se enquadram em nenhum desses padrões, quão difícil seria gerar novas receitas? Seria possível criar nova comida francesa?