我的Rust代码比等效的Python代码长得多,结果错误

时间:2018-07-13 20:36:35

标签: rust

existing Python code转换为Rust时,行数增加,类型更改很多,最糟糕的是结果错误!我不确定自己是在做错什么,还是有办法对其进行优化:

主要功能是:

fn main() {
    let series = [
        30, 21, 29, 31, 40, 48, 53, 47, 37, 39, 31, 29, 17, 9, 20, 24, 27, 35, 41, 38, 27, 31, 27,
        26, 21, 13, 21, 18, 33, 35, 40, 36, 22, 24, 21, 20, 17, 14, 17, 19, 26, 29, 40, 31, 20, 24,
        18, 26, 17, 9, 17, 21, 28, 32, 46, 33, 23, 28, 22, 27, 18, 8, 17, 21, 31, 34, 44, 38, 31,
        30, 26, 32,
    ];

    triple_exponential_smoothing(&series, 12, 0.716, 0.029, 0.993, 24);
}

triple_exponential_smoothing调用了我测试过的另外两个函数,它们给出了正确的结果:

fn initial_trend(series: &[i32], slen: i32) -> f32 {
    let mut sum = 0.0;
    for i in 0..slen as usize { // in Python: for i in range(slen)
        sum += (series[i + slen as usize] as f32 - series[i] as f32) / slen as f32;
    }
    return sum / slen as f32;
}

这是Python代码的转换:

def initial_trend(series, slen):
    sum = 0.0
    for i in range(slen):
        sum += float(series[i+slen] - series[i]) / slen
    return sum / slen

# >>> initial_trend(series, 12)
# -0.7847222222222222

第二个是:

fn initial_seasonal_components(series: &[i32], slen: i32) -> Vec<f32> {
    let mut seasonals = Vec::new();
    let n_seasons = series.len() as i32 / slen;
    // # compute season averages
    let season_chunks = series //season_averages
            .chunks(slen as usize)
            .collect::<Vec<_>>();
    let season_averages = season_chunks
        .iter()
        .map(|chunk| chunk.iter().sum::<i32>() as f32 / chunk.len() as f32)
        .collect::<Vec<f32>>();
    // # compute initial values
    for i in 0..slen as usize {
        let mut sum_of_vals_over_avg = 0.0;
        for j in 0..n_seasons as usize {
            sum_of_vals_over_avg +=
                series[i + j * slen as usize] as f32 - season_averages[j] as f32;
        }
        seasonals.push(sum_of_vals_over_avg / n_seasons as f32);
    }
    return seasonals;
}

这是Python代码的转换:

def initial_seasonal_components(series, slen):
    seasonals = {}
    season_averages = []
    n_seasons = int(len(series)/slen)
    # compute season averages
    for j in range(n_seasons):
        season_averages.append(sum(series[slen*j:slen*j+slen])/float(slen))
    # compute initial values
    for i in range(slen):
        sum_of_vals_over_avg = 0.0
        for j in range(n_seasons):
            sum_of_vals_over_avg += series[slen*j+i]-season_averages[j]
        seasonals[i] = sum_of_vals_over_avg/n_seasons
    return seasonals

# >>> initial_seasonal_components(series, 12)
# {0: -7.4305555555555545, 1: -15.097222222222221, 2: -7.263888888888888, 3: -5.097222222222222, 4: 3.402777777777778, 5: 8.069444444444445, 6: 16.569444444444446, 7: 9.736111111111112, 8: -0.7638888888888887, 9: 1.902777777777778, 10: -3.263888888888889, 11: -0.7638888888888887}

该函数中似乎有错误:

fn triple_exponential_smoothing(
    series: &[i32],
    slen: i32,
    alpha: f32,
    beta: f32,
    gamma: f32,
    n_preds: i32,
) {
    let mut result: Vec<f32> = Vec::new();
    let mut seasonals = initial_seasonal_components(&series, slen);
    println!("The seasonalities are: {:#?}", seasonals);
    let mut smooth = 0.0;
    let mut trend = 0.0;
    // for i in range(len(series)+n_preds):
    for i in 0..(series.len() + n_preds as usize) as usize {
        match i {
            0 => {
                // # initial values
                smooth = series[0] as f32;
                trend = initial_trend(&series, slen);
                println!("The initial_trend is: {:#?}", trend);
                result.push(series[0] as f32);
            }
            i if i >= series.len() => {
                // # we are forecasting
                let m = i - series.len() + 1;
                result.push(
                    (smooth as usize + m * trend as usize) as f32 + seasonals[i % slen as usize],
                )
            }
            _ => {
                let val = series[i];
                let last_smooth = smooth;
                smooth = alpha * (val as f32 - seasonals[i % slen as usize])
                    + (1.0 - alpha) * (smooth + trend);
                trend = beta * (smooth - last_smooth) + (1.0 - beta) * trend;
                seasonals[i % slen as usize] = gamma * (val as f32 - smooth)
                    + (1 - gamma as usize) as f32 * seasonals[i % slen as usize];
                result.push(smooth + trend + seasonals[i % slen as usize]);
            }
        }
    }
    println!("The forecast is: {:#?}", result);
}

这是Python代码的转换:

def triple_exponential_smoothing(series, slen, alpha, beta, gamma, n_preds):
    result = []
    seasonals = initial_seasonal_components(series, slen)
    for i in range(len(series)+n_preds):
        if i == 0: # initial values
            smooth = series[0]
            trend = initial_trend(series, slen)
            result.append(series[0])
            continue
        if i >= len(series): # we are forecasting
            m = i - len(series) + 1
            result.append((smooth + m*trend) + seasonals[i%slen])
        else:
            val = series[i]
            last_smooth, smooth = smooth, alpha*(val-seasonals[i%slen]) + (1-alpha)*(smooth+trend)
            trend = beta * (smooth-last_smooth) + (1-beta)*trend
            seasonals[i%slen] = gamma*(val-smooth) + (1-gamma)*seasonals[i%slen]
            result.append(smooth+trend+seasonals[i%slen])
    return result

# # forecast 24 points (i.e. two seasons)
# >>> triple_exponential_smoothing(series, 12, 0.716, 0.029, 0.993, 24)
# [30, 20.34449316666667, 28.410051892109554, 30.438122252647577, 39.466817731253066, ...

My complete code is available in the playground

我感谢您为优化代码和修复错误所做的任何评论。

2 个答案:

答案 0 :(得分:3)

在Rust中,您一直将所有内容都转换为usize

(1 - gamma as usize) as f32

如果考虑到那个,(1 - gamma as usize)只能是01,具体取决于gamma的值。如果您改为将其更改为

(1.0 - gamma) as f32

还要改变

(smooth as usize + m * trend as usize) as f32

(smooth + m as f32 * trend)

然后您获得与Python中相同的结果。

关于性能,这看起来很正确,但是您可以引入一些临时变量,以避免始终都重新计算相同的内容(尽管优化程序应该有所帮助)。 Rust的默认编译模式是调试,请确保切换到基准测试版本。

答案 1 :(得分:0)

我在这里发布我收到的最佳评论和答案,希望对其他人有用:

尝试最小化(可能减少到零)代码中的“ as”强制转换数量。 into()/ from()和try_from()帮助;

尝试用迭代器替换一些原始循环;

Triple_exponential_smoothing函数具有一些易于在调用点混淆的参数,因为Rust目前没有命名参数。为了避免出现此问题,您可以尝试在struct / tuple中打包一些参数。

在Rust函数的末尾使用“返回”并不是很多习惯。

此外,值得注意的是,在Python中,浮点类型是double浮点,在Rust中为f64。尽管可能没什么大不了的,但这可能会导致准确性上的微小差异。

被替换的地雷在此PlayGound中的功能纯净代码:

fn main() {
    let series = [
        30,21,29,31,40,48,53,47,37,39,31,29,17,9,20,24,27,35,41,38,
        27,31,27,26,21,13,21,18,33,35,40,36,22,24,21,20,17,14,17,19,
        26,29,40,31,20,24,18,26,17,9,17,21,28,32,46,33,23,28,22,27,
        18,8,17,21,31,34,44,38,31,30,26,32];

    triple_exponential_smoothing(&series, 12, 0.716, 0.029, 0.993, 24);
}

fn initial_trend(series: &[i32], slen: usize) -> f32 {
    series[..slen].iter().zip(&series[slen..])
        .map(|(&a, &b)| (b as f32 - a as f32) / slen as f32).sum::<f32>() / slen as f32
}

fn initial_seasonal_components (series: &[i32], slen: usize) -> Vec<f32> {
    let n_seasons = series.len() / slen;
    // # compute season averages
    let season_averages = series //season_averages
        .chunks(slen)
        .map(|chunk| chunk.iter().sum::<i32>() as f32 / chunk.len() as f32)
        .collect::<Vec<f32>>();
    // # compute initial values
    (0..slen).map(|i| {
        let mut sum_of_vals_over_avg = 0.0;
        for j in 0..n_seasons {
            sum_of_vals_over_avg += series[i + j * slen] as f32 - season_averages[j] as f32;
        }
        sum_of_vals_over_avg / n_seasons as f32
    }).collect()
}

fn triple_exponential_smoothing(series: &[i32], slen: usize, alpha: f32, beta: f32,
                                gamma: f32, n_preds: usize) {
    let mut result: Vec<f32> = Vec::new();
    let mut seasonals = initial_seasonal_components(&series, slen);
    println!("The seasonalities are: {:#?}", seasonals);
    let mut smooth = 0.0;
    let mut trend = 0.0;

    for i in 0..(series.len() + n_preds) {
        match i {
            0 => {  // # initial values
                smooth = series[0] as f32;
                trend = initial_trend(&series, slen);
                println!("The initial_trend is: {:#?}", trend);
                result.push(series[0] as f32);
            },
            i if i >= series.len() => {  // # we are forecasting
                let m = i - series.len() + 1;
                result.push((smooth + m as f32 * trend) + seasonals[i % slen])
            },
            _ => {
                let val = series[i];
                let last_smooth = smooth;
                smooth = alpha * (val as f32 - seasonals[i % slen]) +
                    (1.0 - alpha)*(smooth + trend);
                trend = beta * (smooth - last_smooth) + (1.0 - beta) * trend;
                seasonals[i % slen] = gamma * (val as f32 - smooth) +
                    (1.0 - gamma) * seasonals[i % slen];
                result.push(smooth + trend + seasonals[i % slen]);
            }
        }
    }
    println!("The forecast is: {:#?}", result);
}