Pr´
e-processamento de Dados
Fabr´ıcio Olivetti de Fran¸ca
T´
opicos
1. Pr´e-Processamento dos Dados
2. Conceitos B´asicos
3. Representa¸c˜ao Textual
4. Padr˜oes Recorrentes
5. Padronizando e Normalizando os Atributos
Data Sets
Conjunto de objetos de dados. Objeto de dados ´e uma entidade:
• funcion´ario de uma empresa
• medi¸c˜oes da atividade cerebral em um paciente • descri¸c˜ao de uma ocorrˆencia policial
Objetos de Dados
• Idade • Local
• Valor do impulso el´etrico • etc.
Tipos de Atributos
• Categ´oricos: • Nominal • Bin´ario • Ordinal • Num´ericos: • Intervalar • Raz˜aoAtributos Nominais
S´ımbolos ou nomes descritivos: • Descreve uma caracter´ıstica
• N˜ao apresentam rela¸c˜ao de ordem, apenas igualdade Ex.: ocupa¸c˜ao, cor dos olhos, etc.
Atributos Bin´
arios
Atributo nominal com apenas dois valores poss´ıveis: 0 ou 1. Ex.: resultado de um exame, gˆenero, etc.
Atributos Ordinais
Valores que apresentam ordem, mas n˜ao possuem rela¸c˜ao de magnitude. Ex.: {p´essimo, ruim, regular, bom, ´otimo}
Atributos Intervalares
Atributo num´erico com rela¸c˜ao de ordem e magnitude.
Ex.: Temperatura de 20◦C ´e 10 unidades mais quente do que 10◦C . N˜ao podemos dizer que 20◦C ´e duas vezes mais quente do que 10◦C .
Atributos Intervalares
O 0 absoluto em Celsius est´a em −273.15◦C .
20◦C est´a a 293.15 de nenhum calor, o dobro desse calor seria 586.30 ou 313.15◦C .
Atributos de Raz˜
oes
Atributos num´ericos com a mesmas propriedades do intervalar, mas com um ponto-zero.
• Altura
• Contagem de palavras • Temperatura em Kelvin
Representando Objetos
Precisamos de uma representa¸c˜ao num´erica. • Dados num´ericos: ok! (ou quase sempre ok) • Dados nominais: vetor bin´ario
• Dados ordinais: rank
Representando Objetos
Tabela 1: Base de Dados.
Profiss˜ao Conceito Nota Engenheiro A 9.5
Professor B 8.4 Engenheiro A 7.3
Atributos Categ´
oricos → Bin´
arios
Tabela 2: Base de Dados.
Engenheiro Professor Gerente Conceito Nota
1 0 0 A 9.5
0 1 0 B 8.4
1 0 0 A 7.3
0 0 1 D 9.1
Atributos Ordinais → Num´
ericos
# conceitos = 5 Rank: 5, 4, 3, 2, 1 z =(rank−1)#−1
Tabela 3: Base de Dados.
Engenheiro Professor Gerente Conceito Nota
1 0 0 1 9.5
0 1 0 0.75 8.4
1 0 0 1 7.3
Leitura da Base de Dados
data Profissao = Engenheiro | Professor | Gerente | Estudante
deriving (Show, Read, Eq, Enum, Bounded)
data Conceito = F | D | C | B | A
deriving (Show, Read, Enum)
type Nota = Double
type Objeto = (Profissao, Conceito, Nota)
type Objeto’ = [Double]
Leitura da Base de Dados
parseFile :: String -> [Objeto]
parseFile file = map parseLine (lines file) where
parseLine l = toObj (words l)
toObj [w1, w2, w3] = (read w1 :: Profissao, read w2 :: Conceito, read w3 :: Nota)
A fun¸c˜ao words separa uma string em uma lista de strings cortando nos caracteres de espa¸co.
Leitura da Base de Dados
transformData :: [Objeto] -> [Objeto’]
transformData data = map parseObj data where
parseObj (prof, conc, nota) = (binariza prof)
++ [rank conc, nota]
Leitura da Base de Dados
binariza :: Profissao -> [Double]
binariza p = map bool2double [p == p’ | p’ <- profissoes] where
profissoes = [minBound..] :: [Profissao] bool2double True = 1.0
Leitura da Base de Dados
rank :: Conceito -> Double
rank co = (fromEnum’ co) / (fromEnum’ A) where
fromEnum’ = fromIntegral . fromEnum
Fluxo dos Dados
A leitura e transforma¸c˜ao dos dados segue um fluxo bem definido. ´E f´acil perceber que, enquanto uma linha do arquivo est´a sendo processada pela fun¸c˜ao transformData, outra pode ser processada pela fun¸c˜ao parseFile.
Minera¸
c˜
ao de Textos
Documentos de textos:
• N˜ao possuem representa¸c˜ao vetorial • Interdependˆencia dos atributos • Tamanho vari´avel
Textos como conjuntos
Se representarmos os documentos de textos como o conjunto de suas palavras:
D1 = ”Estou assistindo a uma aula de Big Data, mas tudo que aprendi foi Haskell durante a aula!”
D2 = ”Hoje aprendi Haskell na aula, ser´a que o que aprendi ser´a ´util na minha vida?”
Textos como conjuntos
Se representarmos os documentos de textos como o conjunto de suas palavras:
D1 = {Estou, assistindo, a, uma, aula, de, Big , Data, mas, tudo, que, aprendi , foi , Haskell , durante, aula!}
Textos como conjuntos
Calculando a similaridade de Jaccard, temos:
D1 ∩ D2 = {que, aprendi , Haskell }
D1 ∪ D2 = {Estou, assistindo, a, uma, aula, de, Big , Data, mas, tudo, que, aprendi , foi , Haskell , durante, aula!, Hoje, na, aula, ser ´a, o, ´util , minha, vida?}
J(D1, D2) = 3 24= 0.125
Essa representa¸c˜ao ´e conhecida como Bag-of-Words, em que geramos os atributos do texto como atributos categ´oricos.
Normaliza¸
c˜
ao do Texto
Pode ser interessante padronizar a forma do texto para termos serem considerados como um elemento ´unico do conjunto independente de como ´e escrito.
Por exemplo: Estou, estou, esTou, aula!, aula, aula?, ´util, util.
D1 = {estou, assistindo, a, uma, aula, de, big , data, mas, tudo, que, aprendi , foi , haskell , durante, aula}
D2 = {hoje, aprendi , haskell , na, aula, sera, que, o, util , minha, vida}
J(D1, D2) = 4 23= 0.17
Elimina¸
c˜
ao de atributos irrelevantes
Podemos eliminar palavras que n˜ao apresentam significado sozinhas:
D1 = {estou, assistindo, aula, big , data, tudo, aprendi , haskell , durante, aula} D2 = {hoje, aprendi , haskell , aula, sera, util , minha, vida}
J(D1, D2) = 4 14= 0.28
Bag-of-Words
type Doc = String
type Token = String
bagofwords :: [Doc] -> [[Token]]
bagofwords docs = naoVazio $ map tokeniza docs where
tokeniza doc = nub
$ filter maisDe2
$ map normaliza (words doc) normaliza palavra = map toLower
$ filter isAlphaNum palavra
Bag-of-Words
A fun¸c˜ao toLower converte um caractere mai´usculo para min´usculo, e a fun¸c˜ao isAlphaNum retorna verdadeiro se o caractere ´e uma letra do alfabeto ou um n´umero.
Fluxo dos Dados
O fluxo dos dados pode ser descrito com o seguinte fluxograma:
map tokeniza words map normaliza filter isAlphaNum map toLower
filter maisDe2 nub
Term-Frequency
Se um termo aparece repetidas vezes em um documento, isso significa que ele pode ter uma importˆancia maior do que os outros termos. No nosso exemplo, a repeti¸c˜ao do termo Haskell indica um dos temas dos nossos documentos.
Term-Frequency
A informa¸c˜ao de frequˆencia pode ser importante para a representa¸c˜ao de nossos documentos. Podemos fazer ent˜ao:
fn(t, d ) = f (t, d ) |d | ,
com f (t, d ) sendo a frequˆencia do termo t no documento d e |d | a quantidade de termos no documento d .
Term-Frequency
A ideia para computar os vetores TF ´e primeiro representar cada documento como uma lista (token, 1.0) e, em seguida:
• Ordenar essa lista pelo token • Agrupar os itens com mesmo token • Somar os valores em cada grupo • Dividir os valores pelo n´umero de tokens
TF
type Freq = Double
tf :: [Doc] -> [[(Token, Freq)]]
tf docs = naoVazio $ map tokeniza docs where
tokeniza doc = fn $ map normTupla (words doc) normTupla w = (normaliza w, 1.0)
TF
fn :: [(Token, Double)] -> [(Token, Freq)]
A partir desse momento trabalharemos frequentemente com bases de dados representadas como listas de tuplas.
Essas tuplas devem ser encaradas como chave e valor, respectivamente. Para tornar o c´odigo mais leg´ıvel, vamos definir as fun¸c˜oes mapByKey, foldByKey, groupByKey, sortByKey
mapByKey
mapByKey aplica a fun¸c˜ao g apenas no valor da tupla, deixando a chave intacta:
foldByKey
foldByKey’ assume uma lista de tuplas chave-valor em que todas as chaves s˜ao iguais.
Essa fun¸c˜ao aplica foldl1’ apenas nos valores das tuplas, resultando em uma tupla (k, v ) em que k ´e a chave de todas as tuplas e v o resultado da opera¸c˜ao fold:
foldByKey’ g = foldl1’ (\(k1,v1) (k2,v2) -> (k1, g v1 v2))
sortByKey
sortByKey ordena uma lista de tuplas pela chave:
sortByKey = sortBy ordenaTupla
ordenaTupla :: (Ord a) => (a, t) -> (a, t) -> Ordering
ordenaTupla (a1,b1) (a2,b2)
| a1 < a2 = LT | a1 > a2 = GT
groupByKey
groupByKey agrupa uma lista ordenada de tuplas gerando uma lista de listas, com cada lista agrupando as tuplas de mesma chave:
groupByKey = groupBy agrupaTupla
agrupaTupla :: (Eq a) => (a, t0) -> (a, t0) -> Bool
agrupaTupla (a1, b1) (a2, b2) = a1==a2
groupByKey [(1,0.1), (1,0.2), (2,0.1)]
== [ [(1,0.1),(1,0.2)], [2,0.1] ]
TF
fn :: [(Token, Double)] -> [(Token, Freq)]
fn tokens = mapByKey (/n) $ map (foldByKey’ (+)) $ groupByKey $ sortByKey tokens where n = length’ tokens
Fluxo dos Dados
O fluxo dos dados pode ser descrito com o seguinte fluxograma:
map tokeniza words map normaliza sortByKey groupByKey
map foldByKey mapByKey
Inverse Document Frequency
Algumas palavras aparecem com uma frequˆencia muito superior as demais, como: e, que, ou, etc.
Essas palavras n˜ao costumam apresentar um significado discriminat´orio e, portanto, podem ter um peso menor. Para isso podemos multiplicar o TF por:
idf (t) = log |D| |{d ∈ D : t ∈ D}|
TF-IDF
Como veremos mais adiante, ´e poss´ıvel pensar no vetor IDF como uma matriz associativa. Para isso utilizaremos o HashMap do Haskell.
idf :: [[(Token, Freq)]] -> M.HashMap Token Freq
idf corpus = M.fromList
$ mapByKey (\v -> log (n/v)) $ map (foldByKey’ (+)) $ groupByKey $ sortByKey $ map (\ (k,v) -> (k,1)) $ concat corpus where n = length’ corpus 43
HashMap
-- importa a biblioteca HashMap.Strict apelidade de M
import qualified Data.HashMap.Strict as M
-- cria um mapa M.HashMap Integer Double da lista de tuplas
mapa = M.fromList [(1, 0.1), (2, 0.3), (3, 0.04)]
mapa M.! 2 -- retorna 0.3
TF-IDF
tfidf :: [[(Token, Freq)]] -> M.HashMap Token Freq
-> [[(Token, Freq)]]
tfidf tf’ idf’ = map multIDF tf’ where
multIDF = mapByKey (\(k,v) -> (k, v * (idf’ M.! k)) )
Fluxo dos Dados
O fluxo dos dados pode ser descrito com o seguinte fluxograma:
map multIDF mapByKey concat map sortByKey
groupByKey map foldByKey
mapByKey fromList
Padr˜
oes de sequˆ
encia de fun¸
c˜
oes
Nos exemplos anteriores, podemos perceber um padr˜ao recorrente de chamada de fun¸c˜oes utilizadas em diversas solu¸c˜oes:
map (foldByKey’ (+)) $ groupByKey $ sortByKey
Esse padr˜ao precede a chamada de uma fun¸c˜ao map que transforma um valor em uma tupla chave-valor (k, v ).
Padr˜
oes de sequˆ
encia de fun¸
c˜
oes
Basicamente esse padr˜ao combina os valores das tuplas com a mesma chave utilizando uma fun¸c˜ao (nos exemplos utilizamos (+)). ´E interessante, ent˜ao, criar a fun¸c˜ao:
combine :: Ord k
=> (v -> v -> v) -> [(k, v)] -> [(k, v)]
combine f xs = map (foldByKey’ f) $ groupByKey $ sortByKey xs Com isso, muitas fun¸c˜oes se tornam sequˆencias de combine . map.
fn
fn :: [(Token, Double)] -> [(Token, Freq)]
fn tokens = mapByKey (/n)
$ combine (+) tokens
where
idf
Como veremos mais adiante, ´e poss´ıvel pensar no vetor IDF como uma matriz associativa. Para isso utilizaremos o HashMap do Haskell.
idf :: [[(Token, Freq)]] -> M.HashMap Token Freq
idf corpus = M.fromList
$ mapByKey (\v -> log (n/v)) $ combine (+) $ mapByKey (\v -> 1) $ concat corpus where n = length’ corpus 52
Padronizando e Normalizando os
Atributos
Padroniza¸
c˜
ao
Muitos algoritmos de Aprendizado de M´aquina sup˜oem que os valores dos atributos seguem N(0, 1).
Se um atributo n˜ao segue esse padr˜ao, pode dominar a fun¸c˜ao-objetivo e se tornar importante demais.
Padroniza¸
c˜
ao
Dado uma matriz de dados X , podemos padronizar os valores de cada um de seus elementos como:
ˆ
Xi ,j = Xi ,j− ¯Xi ,j σj
Padroniza¸
c˜
ao
padroniza :: [[Double]] -> [[Double]]
padroniza x = mapColunas padroniza’ x
padroniza’ :: [Double] -> [Double]
padroniza’ x = devMedia ./ sigma where
media xs = (sum xs) / n devMedia = x .- (media x)
sigma = sqrt $ media $ devMedia .** 2
n = length’ x
Fluxo dos Dados
O fluxo dos dados pode ser descrito com o seguinte fluxograma:
mapColunas padroniza’ media devMedia ./ .** 2
Escalonamento
Algoritmos baseados em m´etodos de gradiente tendem a se beneficiar quando os atributos est˜ao entre [0, 1].
ˆ
Xi ,j = Xi ,j − min X:,j max X:,j− min X:,j
Padroniza¸
c˜
ao
maxminScale :: [[Double]] -> [[Double]]
maxminScale x = mapColunas maxminScale’ x
maxminScale’ :: [Double] -> [Double]
maxminScale’ x = map scale x where
Normaliza¸
c˜
ao
Finalmente podemos normalizar cada amostra da base utilizando a normaliza¸c˜ao de vetores:
ˆ
Xi ,j = Xi ,j kXikp
Padroniza¸
c˜
ao
normaliza :: [[Double]] -> [[Double]]
normaliza x = map normaliza’ x where
normaliza’ xi = xi ./ (norma xi) norma xi = sqrt . sum $ xi .^ 2
Paralelizando o
Pr´
e-Processamento
Paralelizando chunks
Conforme vimos na aula anterior, ao paralelizar um programa
objetivamos minimizar o n´umero de sparks e tentar distribuir a tarefa de forma homogˆenea entre as threads.
Paralelizando chunks
Para isso adotamos a estrat´egia de dividir nossos dados em peda¸cos (denominados chunks), processar cada chunk em paralelo e, em seguida, juntar os resultados.
Paralelizando chunks
Vamos continuar utilizando o tipo ChunksOf definido anteriormente para representar nossos arquivos distribu´ıdos entre diversas m´aquinas.
Paralelizando Leitura dos Dados
Com isso, nossa fun¸c˜ao de processamento dos dados em paralelo
simplesmente aplica a fun¸c˜ao transformData em cada um dos chunks em paralelo.
transformDataPar :: ChunksOf [Objeto] -> ChunksOf [Objeto’]
transformDataPar chunks = (map transformData chunks ‘using‘ parList rdeepseq) Note que nenhuma altera¸c˜ao ´e necess´aria para a fun¸c˜ao transformData que continuar´a recebendo o tipo [Objeto] e retornando [Objeto’].
Paralelizando a Padroniza¸
c˜
ao
Na padroniza¸c˜ao, n˜ao podemos calcular as trˆes partes da equa¸c˜ao em paralelo. Al´em disso, temos um outro desafio, recebemos peda¸cos de linhas, mas temos que trabalhar com as colunas:
padroniza :: [[Double]] -> [[Double]]
padroniza x = mapColunas padroniza’ x
padroniza’ :: [Double] -> [Double]
padroniza’ x = (x .- media) ./ sigma where
media = (sum x) / n
sigma = sqrt $ sum $ (x .- media) .** 2
Paralelizando a Padroniza¸
c˜
ao
Primeiro, vamos alterar a assinatura da fun¸c˜ao para sabermos onde queremos chegar:
padronizaPar :: ChunksOf [[Double]] -> ChunksOf [[Double]]
padronizaPar chunks = parmap padroniza chunks where
padroniza = map (\xi -> (xi .-. media) ./. desvio) media = mapReduce id (.+.) chunks
desvio = map (\x -> sqrt (x/n))
$ mapReduce desvQuad (.+.) chunks desvQuad xi = (xi .-. media).**2
n = sum $ map length’ chunks
Paralelizando a Normaliza¸
c˜
ao
A implementa¸c˜ao paralela de normaliza pode ser feita diretamente aplicando a vers˜ao sequencial em cada chunk.
normalizaPar :: ChunksOf [[Double]] -> ChunksOf [[Double]]
normalizaPar chunks = parmap normaliza chunks
normaliza :: [[Double]] -> [[Double]]
normaliza x = map normaliza’ x where
normaliza’ xi = xi ./ (norma xi) norma xi = sqrt . sum $ xi .^ 2
Paralelizando o TF
Da forma que organizamos o algoritmo de TF, utilizando map e combine, basta aplicar a fun¸c˜ao tf em cada chunk.
tfPar :: ChunksOf [Doc] -> ChunksOf [[(Token, Freq)]]
tfPar chunks = parmap tf chunks
tf :: [[Token]] -> [[(Token, Freq)]]
tf docs = map normFreq docs where
normFreq doc = fn $ map (\w -> (w, 1.0)) doc
Paralelizando a o IDF
Para paralelizar o IDF, precisamos calcular a contagem de quantos documentos cont´em cada token em cada chunk. Em seguida, ´e necess´ario combinar os resultados de cada chunk para ent˜ao calcular o idf.
idfPar :: ChunksOf [[(Token, Freq)]] -> M.HashMap Token Freq
idfPar chunks = M.fromList
$ mapByKey (\v -> log (n/v))
$ combine (+) $ concat
$ (map idf chunks ‘using‘ parList rdeepseq) where
n = sum $ map length’ chunks
idf :: [[(Token, Freq)]] -> [(Token, Freq)]
Padr˜
ao de paralelismo
Note que o padr˜ao de paralelismo do c´alculo do IDF difere dos anteriores pois ´e necess´ario o uso do combine no lugar de foldl1’ e a aplica¸c˜ao de concat na sa´ıda do map.
Padr˜
ao de paralelismo
Uma fun¸c˜ao gen´erica pode ser escrita da seguinte forma e aplicada em muitas situa¸c˜oes em que trabalhamos com lista de tuplas:
mapReduceByKey :: (NFData k, NFData v, Ord k)
=> (a -> (k, v)) -> (v -> v -> v) -> ChunksOf [a] -> [(k, v)]
mapReduceByKey f g xs = combine g
$ concat
$ (map f’ xs
‘using‘ parList rdeepseq) where
Padr˜
ao de paralelismo
Nossa fun¸c˜ao idfPar ficaria:
idfPar :: ChunksOf [[(Token, Freq)]] -> M.HashMap Token Freq
idfPar chunks = M.fromList
$ mapByKey (\v -> log (n/v))
$ mapReduceByKey (\(k,v) -> (k,1)) (+)
$ parmap concat chunks where
n = sum $ map length’ chunks