mapminmax 是一个在数据预处理(尤其是在机器学习和深度学习领域)中非常常见的函数,它的主要作用是将一组数据线性地映射到一个指定的区间,通常是 [0, 1] 或 [-1, 1]。
mapminmax 的数学原理
假设我们有一组数据 X,我们想将其映射到新的区间 [new_min, new_max]。
-
找到原始数据的最小值和最大值:
X_min = min(X)X_max = max(X)
-
计算原始数据的范围:
X_range = X_max - X_min- 注意:
X_range为 0(即所有数据都相同),为了避免除以零的错误,通常会将所有数据映射到区间的中点,(new_min + new_max) / 2。
-
应用线性变换公式: 对于原始数据中的每一个值
x,其映射后的新值y计算如下:y = (x - X_min) / X_range * (new_max - new_min) + new_min这个公式的含义是:
(x - X_min): 将数据平移,使其最小值变为 0。/ X_range: 将数据缩放,使其范围变为 0 到 1。* (new_max - new_min): 将数据缩放,使其范围与目标区间[new_min, new_max]的宽度相匹配。+ new_min: 将数据平移,使其最小值对齐到new_min。
C 语言实现
下面我们将分步实现这个功能,并提供一个完整的、可运行的 C 语言示例。
步骤 1:定义函数原型
我们将创建一个函数,它接收原始数据数组、数组大小、目标区间的最小值和最大值,以及一个用于存储结果的数组。
/** * @brief 将数据线性映射到指定的 [new_min, new_max] 区间 * * @param data 原始数据数组 * @param size 数组大小 * @param new_min 目标区间的最小值 * @param new_max 目标区间的最大值 * @param result 用于存储结果的数组 */ void mapminmax(const double data[], int size, double new_min, double new_max, double result[]);
步骤 2:实现函数逻辑
在函数内部,我们需要完成以下操作:
- 遍历数组找到
data_min和data_max。 - 计算
data_range。 - 处理
data_range为 0 的特殊情况。 - 遍历原始数组,应用公式计算每个新值并存入
result数组。
#include <stdio.h> // 用于 printf
void mapminmax(const double data[], int size, double new_min, double new_max, double result[]) {
if (size <= 0) {
printf("错误:数组大小必须大于 0,\n");
return;
}
// 1. 找到原始数据的最小值和最大值
double data_min = data[0];
double data_max = data[0];
for (int i = 1; i < size; i++) {
if (data[i] < data_min) {
data_min = data[i];
}
if (data[i] > data_max) {
data_max = data[i];
}
}
// 2. 计算原始数据的范围
double data_range = data_max - data_min;
// 3. 处理所有数据都相同的情况 (data_range == 0)
if (data_range == 0.0) {
double target_value = (new_min + new_max) / 2.0;
for (int i = 0; i < size; i++) {
result[i] = target_value;
}
return;
}
// 4. 应用线性变换公式
double new_range = new_max - new_min;
for (int i = 0; i < size; i++) {
result[i] = (data[i] - data_min) / data_range * new_range + new_min;
}
}
步骤 3:创建一个完整的示例程序
下面是一个完整的 C 程序,它定义了数据,调用 mapminmax 函数,并打印出结果。
#include <stdio.h>
/**
* @brief 将数据线性映射到指定的 [new_min, new_max] 区间
*
* @param data 原始数据数组
* @param size 数组大小
* @param new_min 目标区间的最小值
* @param new_max 目标区间的最大值
* @param result 用于存储结果的数组
*/
void mapminmax(const double data[], int size, double new_min, double new_max, double result[]);
// 辅助函数:打印数组
void print_array(const double arr[], int size, const char* label) {
printf("%s: [", label);
for (int i = 0; i < size; i++) {
printf("%.4f", arr[i]);
if (i < size - 1) {
printf(", ");
}
}
printf("]\n");
}
int main() {
// 示例 1: 映射到 [0, 1]
double data1[] = {10.0, 20.0, 30.0, 40.0, 50.0};
int size1 = sizeof(data1) / sizeof(data1[0]);
double result1[size1];
printf("--- 示例 1: 映射到 [0, 1] ---\n");
print_array(data1, size1, "原始数据");
mapminmax(data1, size1, 0.0, 1.0, result1);
print_array(result1, size1, "映射结果");
printf("\n");
// 示例 2: 映射到 [-1, 1]
double data2[] = {-5.0, 0.0, 5.0, 10.0, 15.0};
int size2 = sizeof(data2) / sizeof(data2[0]);
double result2[size2];
printf("--- 示例 2: 映射到 [-1, 1] ---\n");
print_array(data2, size2, "原始数据");
mapminmax(data2, size2, -1.0, 1.0, result2);
print_array(result2, size2, "映射结果");
printf("\n");
// 示例 3: 处理所有数据都相同的情况
double data3[] = {7.0, 7.0, 7.0, 7.0};
int size3 = sizeof(data3) / sizeof(data3[0]);
double result3[size3];
printf("--- 示例 3: 所有数据相同,映射到 [0, 1] ---\n");
print_array(data3, size3, "原始数据");
mapminmax(data3, size3, 0.0, 1.0, result3);
print_array(result3, size3, "映射结果 (应为 0.5)");
return 0;
}
如何编译和运行
- 将上述代码保存为文件,
mapminmax_example.c。 - 打开终端或命令提示符。
- 使用 GCC 编译器进行编译:
gcc mapminmax_example.c -o mapminmax_example -lm
-o mapminmax_example:指定输出的可执行文件名。-lm:链接数学库(虽然这个例子没有直接使用math.h,但这是一个好习惯,以防未来需要)。
- 运行生成的可执行文件:
./mapminmax_example
预期输出
--- 示例 1: 映射到 [0, 1] ---
原始数据: [10.0000, 20.0000, 30.0000, 40.0000, 50.0000]
映射结果: [0.0000, 0.2500, 0.5000, 0.7500, 1.0000]
--- 示例 2: 映射到 [-1, 1] ---
原始数据: [-5.0000, 0.0000, 5.0000, 10.0000, 15.0000]
映射结果: [-1.0000, -0.6000, -0.2000, 0.2000, 0.6000]
--- 示例 3: 所有数据相同,映射到 [0, 1] ---
原始数据: [7.0000, 7.0000, 7.0000, 7.0000]
映射结果: [0.5000, 0.5000, 0.5000, 0.5000]
高级变种:反向变换
在很多应用场景中,你可能还需要将已经归一化的数据还原回原始范围,这可以通过记录原始的 data_min, data_max, new_min, 和 new_max 来实现。
我们可以修改函数,让它返回一个包含这些参数的结构体,以便后续进行反向变换。
#include <stdio.h>
#include <stdlib.h> // 用于 malloc 和 free
// 结构体用于存储归一化所需的参数
typedef struct {
double original_min;
double original_max;
double new_min;
double new_max;
} NormalizationParams;
// 归一化函数,返回参数结构体
NormalizationParams mapminmax_with_params(const double data[], int size, double new_min, double new_max, double result[]);
// 反向变换函数
void inverse_mapminmax(const NormalizationParams params, const double normalized_data[], int size, double original_data[]);
// ... (print_array 函数保持不变) ...
int main() {
double data[] = {10.0, 20.0, 30.0, 40.0, 50.0};
int size = sizeof(data) / sizeof(data[0]);
double normalized_data[size];
double restored_data[size];
printf("--- 带参数的归一化和反向变换 ---\n");
print_array(data, size, "原始数据");
NormalizationParams params = mapminmax_with_params(data, size, 0.0, 1.0, normalized_data);
printf("归一化参数: min_orig=%.2f, max_orig=%.2f, min_new=%.2f, max_new=%.2f\n",
params.original_min, params.original_max, params.new_min, params.new_max);
print_array(normalized_data, size, "归一化数据");
inverse_mapminmax(params, normalized_data, size, restored_data);
print_array(restored_data, size, "恢复后的数据");
return 0;
}
NormalizationParams mapminmax_with_params(const double data[], int size, double new_min, double new_max, double result[]) {
NormalizationParams params;
params.new_min = new_min;
params.new_max = new_max;
// ... (查找 data_min, data_max 的逻辑与之前相同) ...
double data_min = data[0];
double data_max = data[0];
for (int i = 1; i < size; i++) {
if (data[i] < data_min) data_min = data[i];
if (data[i] > data_max) data_max = data[i];
}
params.original_min = data_min;
params.original_max = data_max;
double data_range = data_max - data_min;
if (data_range == 0.0) {
double target_value = (new_min + new_max) / 2.0;
for (int i = 0; i < size; i++) result[i] = target_value;
return params;
}
double new_range = new_max - new_min;
for (int i = 0; i < size; i++) {
result[i] = (data[i] - data_min) / data_range * new_range + new_min;
}
return params;
}
void inverse_mapminmax(const NormalizationParams params, const double normalized_data[], int size, double original_data[]) {
double orig_range = params.original_max - params.original_min;
double new_range = params.new_max - params.new_min;
if (new_range == 0.0) { // 如果目标区间宽度为0,则无法反向变换
printf("错误:目标区间宽度为0,无法进行反向变换,\n");
return;
}
for (int i = 0; i < size; i++) {
original_data[i] = (normalized_data[i] - params.new_min) / new_range * orig_range + params.original_min;
}
}
这个高级版本更加实用,因为它完整地封装了归一化过程和其逆过程,是构建数据处理管道时的常用模式。
