2 votos

Regresión para la aproximación de funciones

Tengo un programa para calcular intercambiadores de calor que utiliza correlaciones complejas y muy poco lineales. Necesito llegar a una aproximación de la función mediante regresión.

La función toma dos entradas: temperatura del aire de entrada $T_{a,i}$ y señal de control de la válvula $u$ . La salida es $\Delta T_a$ . He representado gráficamente los valores de salida del programa para un intervalo de valores de entrada. ¿Cómo se puede aproximar la función con regresión?

EDITAR : Los datos se generan a partir del siguiente programa python que implementa el modelo de intercambiador de calor en [2] que también se utiliza en la Biblioteca de Simulación de Edificios 3 . Necesito encontrar aproximaciones para la eficiencia y la diferencia de temperatura entre la salida y la entrada de las corrientes de aire y agua. La eficiencia es sencilla y se puede aproximar mediante una función exponencial, pero tengo problemas con las otras.

# Air-water finned heat exchanger model
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from pylab import meshgrid,show,figure
exp = np.exp
rand = np.random.rand

def fcumodel(T_a_in, T_w_in, fs, val):
    # Nominal parameters
    T_a_in_0 = 20.0  # Nominal air inlet temperature
    T_w_in_0 = 40.0  # Nominal water inlet temperature
    T_a_out_0 = 27.0 # Nominal air outlet temperature
    T_w_out_0 = 32.0 # Nominal water outlet temperature

    c_a = 1005.0     # specific heat capacity of air
    c_w = 4180.0     # specific heat capacity of water

    rh = 0.5         # ratio of heat transfers (at nominal conditions)

    mdota_0 = 5.0    # Nominal air mass flow rate
    mdotw_0 = 1.0    # Nominal water mass flow rate

    #NTU0 = calculate_nominal(T_a_in_0, T_w_in_0, T_a_out_0, T_w_out_0, mdota_0, mdotw_0)
    #print NTU0

    NTU0 = 0.747     # precomputed externally for above parameters

    UA_0 = NTU0 * min(c_a * mdota_0, c_w * mdotw_0)   # Nominal conductance

    hw_0 = UA_0 * (rh+1)/rh   # water side heat transfer

    ha_0 = rh * hw_0          # air side heat transfer

    # operating values
    mdota = fs * mdota_0      # air mass flow rate  
    mdotw = val * mdotw_0     # water mass flow rate

    Cdota = c_a * mdota       # air capacitance rate
    Cdotw = c_w * mdotw       # water capacitance rate

    x_a = 1 + 4.769 * 1e-3 * (T_a_in - T_a_in_0)   # air side heat transfer temperature variation

    ha = ha_0 * x_a * (mdota/mdota_0)**0.7         # air side heat transfer

    s_w = 0.014/(1 + 0.014 * T_w_in)               # water side heat transfer temperature sensitivity

    x_w = 1 + s_w * (T_w_in - T_w_in_0)            # water side heat transfer temperature dependence

    hw = hw_0 * x_w * (mdotw/mdotw_0)**0.85        # water side heat transfer

    UA = 1/(1/hw + 1/ha)         # overall heat exchanger conductance             

    Cdotmin = min(Cdota, Cdotw) 
    Cdotmax = max(Cdota, Cdotw)
    Z = Cdotmin/Cdotmax

    if Cdota < Cdotw:
        small = 'air'
    else :
        small = 'water'

    NTU = UA/Cdotmin

    eff = 1 - exp((exp(-Z * (NTU ** 0.78) ) - 1) * (NTU ** 0.22) / Z)    # Cross flow heat exchanger eff

    Qdot = eff * Cdotmin * (T_w_in - T_a_in)

    T_a_out = T_a_in + Qdot/Cdota
    T_w_out = T_w_in - Qdot/Cdotw

    print "eff", eff 
    #print "det:", eff*(Z+1)

    return Qdot, T_a_out, T_w_out, 1/UA, NTU, eff, small

R_a = np.arange(15,21,0.5)
R_u = np.arange(0.1,1,0.05)

N_a = R_a.shape[0]
N_v = R_u.shape[0]

EFF = np.zeros((N_a,N_v),dtype = 'float64')
Ta = np.zeros((N_a,N_v), dtype = 'float64')
Tw = np.zeros((N_a,N_v), dtype = 'float64')

for i in range(N_a):
    for j in range(N_v):
        T_a_in = R_a[i]
        T_w_in = 35

        fs = R_u[j]
        val = R_u[j]

        Qdot, T_a_out, T_w_out, R, NTU, eff, small = fcumodel(T_a_in, T_w_in, fs, val)

        EFF[i][j] = eff
        Ta[i][j] = T_a_out - T_a_in
        Tw[i][j] = T_w_out - T_w_in

[x, y] = meshgrid(R_u, R_a)

fig1 = figure(1)
ax = Axes3D(fig1)
ax.plot_surface(x,y,EFF)

fig2 = figure(2)
ax = Axes3D(fig2)
ax.plot_surface(x,y,Ta)

fig3 = figure(3)
ax = Axes3D(fig3)
ax.plot_surface(x,y,Tw)

show()

Heating Coil

[2]: Wetter, M. (1999). Simulation model finned water-air-coil withoutcondensation (No. LBNL--42355). Ernest Orlando Lawrence Berkeley National Laboratory, Berkeley, CA (EE.UU.).

1 votos

Es tan suave que cualquier ajuste de curva no lineal debería funcionar aquí. En regresión se linealizan los datos o se utiliza algo como GAM, que debería funcionar muy bien.

0 votos

¿Podría publicar un enlace a los datos? Los pasaré por el "buscador de funciones" de ecuaciones de superficie en 3D de mi sitio web para ver si encuentro una función de aproximación sencilla.

0 votos

Para su función de aproximación, ¿cuánto le importa la precisión (es decir, lo cerca que está la aproximación de la curva real) frente a la facilidad intuitiva de comunicación? Por ejemplo, regresión polinómica fraccionaria pueden proporcionar un ajuste funcional preciso, mientras que funciones de bisagra puede describir intuitivamente funciones en un modelo de mínimos cuadrados no lineales ( a la lineal hasta $X=$ *valor*, cuando la línea cambia de pendiente. Además: ¿hasta qué punto te importa la robustez entre diferentes muestras?

2voto

OmaL Puntos 106

Introducción

No necesitas interpolación para este problema. En general es cierto que la interpolación es muy adecuada para aproximar una función desconocida y suave de unas pocas entradas ( $\mathbf{x}=(x_1,\dots,x_n$ ), con $n$ pequeño) sobre un hipercubo, partiendo de los valores de la función sobre un conjunto de puntos de entrenamiento $S=\{(\mathbf{x}_i,y_i)\}_i^N$ situado en el hipercubo. Esto es especialmente cierto cuando los valores de la función en los nuevos puntos de entrenamiento pueden generarse a bajo coste, que es tu caso, ya que tu código se ejecuta muy rápidamente. Además, la interpolación tiene un error de aproximación (cercano a la máquina) nulo en los puntos en $S$ : esto es una desventaja (sobreajuste) cuando se trata de aproximar datos ruidosos, pero en realidad es el comportamiento correcto cuando se aproximan datos que se conocen sin error, como en tu caso.

Por otro lado, la interpolación tiene algunas desventajas claras:

  • el número de parámetros que hay que almacenar para realizar la interpolación crece con el tamaño del conjunto de entrenamiento. Por lo tanto, los requisitos de memoria crecen rápidamente con el tamaño del conjunto de entrenamiento.
  • hacer predicciones con algunos métodos de interpolación (por ejemplo, los Procesos Gaussianos de vainilla) es mucho más lento que hacer predicciones con un simple modelo de regresión paramétrica, aunque probablemente siga siendo mucho más rápido que ejecutar su código informático
  • el error de aproximación (diferencia entre los valores previstos por el método de interpolación $y_p$ y valores de función desconocidos $y$ ) crece mucho en cuanto se sale de la región del espacio de parámetros de entrada "abarcada" por el conjunto de entrenamiento (extrapolación). Los modelos de regresión suelen tener el mismo problema, pero no siempre (véase más adelante).

en su caso es evidente que, para cada fijo $u$ , $\Delta T$ se aproxima muy bien mediante una función lineal de $T_{a,in}$ mientras que para cada $T_{a,in}$ , $\Delta T$ se aproxima muy bien mediante una función logarítmica de $u$ (más una intercepción). Es importante verificar que el modelo tiene sentido desde el punto de vista físico: basándose en su conocimiento físico del problema, ¿es plausible que para $u$ , $\Delta T$ es lineal en $T_{a,in}$ y para fijo $T_{a,in}$ es logarítmica en $u$ ? Si es así, podemos intentar ajustar el siguiente modelo:

$$\Delta T \approx \beta_0+\beta_1 T_{a,in} + \beta_2 \log(u) + \beta_3 T_{a,in}\log(u) $$

y comprobar si en los puntos de datos extrapolados, no utilizados para el entrenamiento, su precisión sigue siendo buena. Tenga en cuenta que este modelo sólo tiene 4 parámetros.


Ajuste de modelos

Utilizaré R para mostrar el enfoque:

# R_a and R_u are the corresponding vectors in your code
R_a <- seq(15 ,20.5, by = 0.5)
R_u <- seq(0.1, 0.95, by = 0.05)

# DeltaT_matrix is the output matrix which you called Ta in your Python code
DeltaT_matrix <- structure(c(9.16474143429817, 8.94118290271588, 8.71731921196482, 
8.49315204987976, 8.26868309204161, 8.04391400188728, 7.81884643081838, 
7.59348201830876, 7.36782239201091, 7.14186916786124, 6.9156239501842, 
6.68908833179539, 8.77268383665777, 8.55914642773912, 8.3452924610834, 
8.13112363233619, 7.91664162521941, 7.70184811163494, 7.48674475176713, 
7.27133319418426, 7.05561507593893, 6.83959202266747, 6.62326564868829, 
6.40663755709921, 8.48960063743449, 8.28327754024661, 8.07663085966664, 
7.86966228789457, 7.66237350551092, 7.45476618157531, 7.24684197372405, 
7.03860252826679, 6.8300494802822, 6.62118445371259, 6.4120090614577, 
6.20252490546751, 8.26779964588656, 8.06711631671647, 7.86610461555747, 
7.66476622681244, 7.46310282353722, 7.26111606753518, 7.05880760945069, 
6.85617908886182, 6.65323213437205, 6.44996836370108, 6.24638938377474, 
6.04249679081404, 8.08543143687201, 7.88937668440084, 7.69299009870359, 
7.49627335453585, 7.29922811555063, 7.10185603438936, 6.90415875277201, 
6.70613790158635, 6.50779510097636, 6.30913196042974, 6.11015007886457, 
5.91085104471518, 7.93061256207576, 7.73848107017399, 7.54601514630728, 
7.3532164548195, 7.16008664917265, 6.96662737203485, 6.7728402553675, 
6.57872692051164, 6.38428897827341, 6.18952802900874, 5.99444566270719, 
5.79904345907504, 7.79614628340926, 7.60741771126701, 7.41835270460851, 
7.2289529171241, 7.03921999182307, 6.84915556111902, 6.65876124691438, 
6.46803866068416, 6.27698940355893, 6.08561506640695, 5.89391722991556, 
5.70189746467181, 7.67733775913916, 7.49161228525216, 7.30554880534835, 
7.11914896248988, 6.93241438924182, 6.74534670775515, 6.55794752984902, 
6.37021845709215, 6.18216108088354, 5.99377698253235, 5.80506773333712, 
5.61603489466418, 7.5709520023041, 7.3879127571883, 7.20453425705099, 
7.02081813449161, 6.83676601178218, 6.65237950094813, 6.46766020384846, 
6.28260971225508, 6.09722960793146, 5.91152146271053, 5.72548683857184, 
5.53912728771803, 7.47466418569886, 7.29405385400307, 7.11310326613761, 
6.9318140444779, 6.75018780122923, 6.5682261385056, 6.38593064840804, 
6.20330291310201, 6.02034450489423, 5.83705698630867, 5.65344191016203, 
5.46950081963829, 7.3867468512066, 7.20835232899164, 7.02961674413013, 
6.85054170904833, 6.67112882614881, 6.49137968788769, 6.31129587685102, 
6.13087896583062, 5.95013051789909, 5.76905208648422, 5.58764521544269, 
5.40591143913309, 7.30588106490289, 7.12952297600297, 6.95282317363471, 
6.77578326056511, 6.59840482967455, 6.4206894640324, 6.24263873697182, 
6.06425421216397, 5.88553744369151, 5.70648997612139, 5.52711334457709, 
5.34740907481004, 7.23103683577564, 7.05656212263367, 6.88174517153485, 
6.70658757587974, 6.53109091931105, 6.35525677578768, 6.17908670965812, 
6.00258227573309, 5.82574501935767, 5.64857647648261, 5.47107817373513, 
5.29325162848903, 7.16139436134207, 6.98867089917336, 6.81560477840227, 
6.64219758335165, 6.46845088870781, 6.29436625959328, 6.1199452516386, 
5.94518941105382, 5.77010027469909, 5.59467937015477, 5.41892821579085, 
5.24284832083573, 7.09629042461093, 6.92520301178304, 6.75377260599608, 
6.58200078277584, 6.40988910812628, 6.23743913860088, 6.06465242137331, 
5.89153049430752, 5.71807488602711, 5.54428711598418, 5.37016869452754, 
5.19572112297026, 7.03518087250459, 6.86562818364286, 6.69573223984451, 
6.52549460810856, 6.35491684602097, 6.18400050182467, 6.01274711448908, 
5.84115821377896, 5.66923532032263, 5.49697994567962, 5.32439359240773, 
5.15147775412946, 6.97761370854148, 6.80950593760835, 6.64105471097168, 
6.47226158736275, 6.30312811620306, 6.13365583767325, 5.96384628278145, 
5.79370097343104, 5.62322142248771, 5.45240913384602, 5.28126560249541, 
5.1097923145855, 6.92320938647168, 6.75646639488318, 6.58937979896262, 
6.42195114942078, 6.25418198775705, 6.08607384632729, 5.91762824841104, 
5.74884670827825, 5.57973073125531, 5.41028181379056, 5.24050144351922, 
5.07039109932776), .Dim = c(12L, 18L), .Dimnames = list(NULL, 
    NULL))

Ahora remodelamos los datos para agilizar la estimación del modelo de regresión:

# flatten response and create design matrix from predictor levels
DeltaT <- as.vector(DeltaT_matrix)
Ta_in <- rep(R_a, 18)
u <- rep(R_u, each = 12)

# assemble data frame for modeling
df <- data.frame(Ta_in, u, DeltaT)

Por último, ajustamos el modelo lineal:

# fit linear model
my_model <- lm(data = df, DeltaT ~ Ta_in*log(u) )
summary(my_model)
# 
# Call:
#   lm(formula = DeltaT ~ Ta_in * log(u), data = df)
# 
# Residuals:
#   Min         1Q     Median         3Q        Max 
# -0.0165326 -0.0020604  0.0003775  0.0032458  0.0070474 
# 
# Coefficients:
#   Estimate Std. Error t value Pr(>|t|)    
# (Intercept)  11.8946112  0.0051852  2294.0   <2e-16 ***
#   Ta_in        -0.3343834  0.0002908 -1150.1   <2e-16 ***
#   log(u)       -1.7572983  0.0050434  -348.4   <2e-16 ***
#   Ta_in:log(u)  0.0504915  0.0002828   178.5   <2e-16 ***
#   ---
#   Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
# 
# Residual standard error: 0.004541 on 212 degrees of freedom
# Multiple R-squared:      1,   Adjusted R-squared:      1 
# F-statistic: 2.513e+06 on 3 and 212 DF,  p-value: < 2.2e-16

my_summary <- summary(my_model)
my_summary$adj.r.squared
# [1] 0.9999715

El ajuste $R^2$ es muy alto, teniendo en cuenta que tenemos muchos puntos de entrenamiento y sólo 4 parámetros. Para obtener otra medida de la precisión de la aproximación, calculemos la raíz cuadrada media relativa error: podemos hacerlo porque DeltaT nunca se acerca a 0.

RMSRE <- function(residuals, response){
     sqrt(mean((residuals/response)^2))
}
RMSRE(my_summary$residuals, df$DeltaT)
# [1] 0.0006351842

Comprobación del modelo con datos extrapolados

Veamos cómo nos va en la extrapolación: Volví a ejecutar su código Python cambiando sólo estas líneas

R_a = np.arange(1, 15, 1)
R_u = np.arange(0.01, 0.1, 0.01)

y tengo un nuevo Ta de su código. Repitiendo pasos análogos a los realizados anteriormente, almaceno los puntos extrapolados en el dataframe df_extrapolated :

DeltaT <- as.vector(DeltaT_matrix_extrapolated) 
R_a <- seq(1, 14)
R_u <- seq(0.01, 0.09, by = 0.01)
Ta_in <- rep(R_a, 9)
u <- rep(R_u, each = 14)
df_extrapolated <- data.frame(Ta_in, u, DeltaT)

Para obtener predicciones en los nuevos puntos, es conveniente generar la matriz modelo X y convertirlo en un marco de datos, para utilizarlo con la función genérica predict :

X <- data.frame(model.matrix.lm(my_model, data = df_extrapolated))
my_predictions <- predict(my_model, X)
extrapolation_residuals <- my_predictions - DeltaT
RMSRE(extrapolation_residuals, DeltaT)
# [1] 0.0160831

El error relativo cuadrático medio sigue siendo bastante pequeño ( $\sim 1.6\%$ ) aunque probáramos el modelo para valores de $T_a$ y $u$ considerablemente inferiores a los utilizados en el ajuste del modelo. Recuerde que $\log(0)$ es indefinido, por lo que el hecho de que incluso cuando nos acercamos a la $u=0$ línea, la aproximación sigue siendo buena, es definitivamente notable.

1voto

throwaway Puntos 18

Tu problema es mucho más sencillo que un problema de regresión típico porque 1) Conoces la verdad real y puedes generar tantos puntos de datos como quieras, 2) No hay ruido y 3) La función se comporta muy bien (suave y de baja dimensión).

Simple interpolación debería funcionar bien en este contexto (véase también ici ). Por ejemplo, es casi seguro que el gráfico de superficie que has mostrado se haya generado mediante interpolación. En este enfoque, se evalúa la función en un conjunto fijo de puntos de muestra. A continuación, la función se aproxima como un conjunto de trozos locales. Cada trozo es una función simple definida sobre el espacio entre puntos de muestra vecinos, y recorre los valores verdaderos de la función en estos puntos.

El tipo de función local utilizada determina el tipo de interpolación. Por ejemplo, la interpolación spline lineal y cúbica son opciones populares. Los splines cúbicos requieren más cálculo, pero producen aproximaciones más suaves. Ya que estás usando Python, echa un vistazo a scipy.interpolate .

Interpolar utilizando un mayor número de puntos de muestreo proporciona una aproximación más precisa a costa de un mayor número de cálculos. En algunos casos, es posible equilibrar la precisión y el gasto de cálculo asignando los puntos de muestreo de forma adaptativa, de modo que estén distribuidos más densamente en las regiones en las que la función es más complicada. En el caso más sencillo, los puntos de muestreo pueden elegirse en una cuadrícula regular. Extrapolar fuera del rango de los puntos de muestreo puede no funcionar bien, por lo que deben elegirse de forma que abarquen el mayor rango que prevea necesitar.

Hay muchos métodos de regresión más sofisticados que también darían buenos resultados (por ejemplo, regresión de procesos gaussianos, métodos de kernel, métodos basados en árboles/bosques, diversas expansiones de bases, etc.). Pero, debido a la simplicidad de tu función y a la ausencia de ruido, no creo que la complejidad añadida te aporte nada más que una simple interpolación.

Otra alternativa sería idear una forma paramétrica sencilla para la aproximación y, a continuación, ajustar los parámetros. Esto tendría la ventaja de ser muy eficiente desde el punto de vista computacional y fácilmente interpretable. Pero, requeriría que tal forma existiera (con suficiente precisión), y que usted pudiera encontrarla.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X