← Python · estudo medido
Quanto dá pra encolher um sinal de vibração sem mudar o diagnóstico do motor, e o atalho que economiza igual, troca o defeito e não deixa rastro.
O painel de vibração do Neon grava 199 amostras por segundo. Parece pouco até você
deixar rodando: uma noite de máquina são 6,7 milhões de amostras: 9 horas e 21 minutos
de gravação. Em float64, o padrão de quem só abre o arquivo e não pensa, isso são
51,1 MB na memória. Salvo em CSV, 127 MB em disco.
A pergunta que eu quis responder medindo, não achando: até onde dá pra encolher esse sinal sem mudar o que o diagnóstico diz? Porque economizar memória é fácil; o difícil é economizar sem trocar o defeito do motor.
Todo o código abaixo roda de ponta a ponta num notebook, no Google Colab ele roda sem instalar nada. O próprio notebook gera o sinal, então não depende de arquivo meu nenhum.
Se as palavras abaixo forem novas pra você, leia esta parte primeiro. O resto do estudo fica fácil depois dela.
Com isso na mão, o estudo inteiro cabe em três frases: trocar o formato do número é de graça e economiza 75%; jogar amostra fora é traiçoeiro, porque troca o defeito do motor e o número de conferência não acusa; e um minuto de gravação responde o mesmo que nove horas quando o que você quer é achar a frequência.
Um sinal que imita o que o acelerômetro vê num motor de 3600 RPM (60 voltas por segundo, ou seja, pico em 60 Hz), com dois harmônicos, um defeito fraco de rolamento em 91 Hz, de propósito, bem no limite de aparecer, e o chiado do próprio sensor:
import numpy as np
fs, N = 199.0, 6_700_000 # 199 amostras por segundo, 9,35 horas
t = np.arange(N, dtype=np.float64) / fs
rng = np.random.default_rng(42) # pra você medir os MESMOS números
x = (0.55*np.sin(2*np.pi*60.0*t) # a rotação: 3600 RPM
+ 0.20*np.sin(2*np.pi*20.0*t) # uma frequência baixa da estrutura
+ 0.08*np.sin(2*np.pi*80.0*t) # um harmônico
+ 0.004*np.sin(2*np.pi*91.0*t) # o defeito FRACO de rolamento
+ rng.normal(0, 0.02, N)) # o chiado do sensor
E a "máquina de diagnosticar", que é o que a gente compara em cada teste: o pico do espectro (em que frequência a máquina vibra mais), o RMS (o quanto ela vibra, que é o número que a ISO 10816 usa) e a altura do defeito fraco:
def diagnostica(sinal, fs):
y = np.asarray(sinal, dtype=np.float64)
esp = np.abs(np.fft.rfft(y - y.mean()))
f = np.fft.rfftfreq(len(y), 1/fs)
janela = (f > 90) & (f < 92)
return f[esp.argmax()], np.sqrt(np.mean(y**2)), esp[janela].max()/len(y)*2
Mesmo sinal, guardado de quatro jeitos. O int16 é o formato cru, do jeito que o
conversor do sensor entrega: a faixa de ±2 g repartida em 65.536 degraus.
| formato | memória | pico | RMS | defeito de 91 Hz |
|---|---|---|---|---|
| float64 (padrão) | 51,12 MB | 60,000 Hz | 0,4182 g | 0,00393 g |
| float32 | 25,56 MB | 60,000 Hz | 0,4182 g | 100% |
| float16 | 12,78 MB | 60,000 Hz | 0,4182 g | 100% |
| int16 (cru, ±2 g) | 12,78 MB | 60,000 Hz | 0,4182 g | 100% |
Por que o int16 não estraga: o degrau dele é 0,000061 g, quase cinquenta vezes
menor que o ruído do próprio sensor parado, que no painel do Neon mede 0,00294 g. Guardar
mais casas decimais do que o ruído do sensor é guardar chiado com precisão de laboratório.
Aqui mora o motivo deste estudo existir. A economia mais óbvia do mundo é pegar uma amostra a cada duas: metade do arquivo, metade da memória, e "o sinal continua lá".
pico, rms, defeito = diagnostica(x[::2], fs/2) # 1 amostra a cada 2
| o que eu fiz | memória | pico medido | pico real | RMS |
|---|---|---|---|---|
| tudo | 51,12 MB | 60,000 Hz | 60 Hz | 0,4182 g |
| 1 a cada 2 | 25,56 MB | 39,500 Hz | 60 Hz | 0,4182 g |
| 1 a cada 3 | 17,04 MB | 6,333 Hz | 60 Hz | 0,4182 g |
O nome disso é aliasing, e a conta é simples: quando você fica com fs' = 99,5
amostras por segundo, tudo que passa de fs'/2 é rebatido pra baixo, em
|f − fs'|. Daí 99,5 − 60 = 39,5 Hz. Com 1 a cada 3, 66,33 − 60 = 6,33 Hz.
Regra prática que sai daí: você só pode jogar amostra fora se a taxa que sobrar for mais que o dobro da maior frequência que te interessa. Pra enxergar 91 Hz, nada abaixo de 182 amostras por segundo, ou seja, com 199 não sobra folga nenhuma.
Se o problema é a memória, talvez o erro seja carregar a noite inteira. Medi o mesmo diagnóstico em pedaços cada vez maiores do sinal:
| gravação | amostras | memória | pico | RMS | defeito |
|---|---|---|---|---|---|
| 10 segundos | 1.990 | 0,02 MB | 60,000 Hz | 0,4185 g | 0,00447 g |
| 1 minuto | 11.940 | 0,09 MB | 60,000 Hz | 0,4181 g | 0,00421 g |
| 10 minutos | 119.400 | 0,91 MB | 60,000 Hz | 0,4182 g | 0,00403 g |
| 1 hora | 716.400 | 5,47 MB | 60,000 Hz | 0,4182 g | 0,00396 g |
| tudo (9,35 h) | 6.700.000 | 51,12 MB | 60,000 Hz | 0,4182 g | 0,00393 g |
O que melhora com mais tempo é a resolução: com 10 segundos, cada raia do espectro vale 0,1 Hz; com 10 minutos, 0,002 Hz. Isso importa quando você precisa separar duas frequências quase coladas, não pra achar um pico que está sozinho.
Eu esperava encontrar um "imposto do pandas". Medi: um DataFrame com a mesma
coluna float32 gastou 132 bytes a mais que o array puro do numpy. Nada.
O que pesa mesmo é o que você decide guardar. Guardar a coluna de tempo junto do sinal dobra a memória, de 25,56 para 51,12 MB, e essa coluna não precisa existir: ela se recria com uma conta, porque as amostras são igualmente espaçadas.
t = np.arange(len(sinal)) / fs # o tempo se recria; não precisa ser guardado
E, para somar sem carregar tudo, o velho truque dos lotes: acumulei a soma dos quadrados em 67 lotes de 100 mil amostras. O RMS deu 0,418152 g, exatamente o mesmo do sinal inteiro: diferença de 0,00. A memória passa a depender do lote, não do arquivo.
| como salvei as mesmas 6,7 milhões de amostras | tamanho |
|---|---|
| CSV (o jeito que o painel salva) | 127 MB |
| .npy em float64 | 51,1 MB |
| .npy em int16 | 12,8 MB |
O CSV custa dez vezes o arquivo binário em int16, porque ele guarda cada número como texto, com vírgula, sinal e quebra de linha. Pra guardar sinal de sensor, o CSV é o formato mais caro que existe.
Vale pra qualquer sinal de sensor: vibração, corrente, temperatura, pressão. Antes de pedir uma máquina maior na nuvem, é isto que eu faço, e, nos meus testes, o arquivo da noite inteira virou um problema de 0,09 MB.
Estudo do Método Neon · sinal gerado pelo próprio notebook, medições refeitas com numpy 2.5.3 e pandas 3.0.5 · o mesmo código roda no Google Colab, sem instalar nada · os números do sensor parado (0,00294 g) e da taxa de 199 amostras por segundo vêm do painel de vibração do Neon