← 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