
本文旨在解决keras模型在训练或预测时遇到的输入维度不匹配问题,特别是由于数据预处理(如独热编码)导致训练集与预测集特征数量不一致的情况。文章将详细解释错误原因,并提供确保特征一致性的解决方案,包括使用`pandas`进行列对齐和`sklearn`的`onehotencoder`,以构建健壮的机器学习管道。
在构建机器学习模型时,一个常见且关键的挑战是确保输入数据的维度与模型期望的维度完全一致。当使用Keras等深度学习框架时,如果模型在训练阶段学习了特定数量的输入特征,但在预测阶段接收到的特征数量不同,就会抛出ValueError: Input 0 of layer ... is incompatible with the layer: expected shape=(None, N), found shape=(None, M)的错误。这通常意味着模型期望N个特征,但实际接收到了M个特征。
在提供的代码示例中,问题出现在Keras模型训练后,用户尝试对单个输入进行预测时。错误信息expected shape=(None, 7), found shape=(None, 5)清晰地表明,模型在训练时输入层期望7个特征,但在预测时只接收到5个特征。
分析代码,我们可以发现以下关键步骤:
问题的核心在于 pd.get_dummies 的行为。当对训练集进行独热编码时,它会为训练集中所有唯一的 'Località' 值创建新的列。例如,如果训练集中有 'A', 'B', 'C' 三种地点,那么 get_dummies 会生成 Località_A, Località_B, Località_C 三列。如果原始数据有5个特征(包括'Località'),那么独热编码后,特征数量可能变为 5 - 1 + 3 = 7。这就是模型期望的7个特征的来源。
然而,当用户输入单个预测数据时,例如只输入 Località='A',对这个单行DataFrame应用 pd.get_dummies 只会生成 Località_A 这一列。此时,特征数量可能变为 5 - 1 + 1 = 5。这就导致了预测时特征数量与模型期望的不一致。
为了验证上述推断,可以在代码的关键位置打印出DataFrame的形状和列名:
# ... (之前的导入和函数定义)
def carica_modello():
    dataset = carica_dataset()
    # 原始数据集的特征数量(不含目标列)
    print(f"原始数据集特征数量 (不含目标列): {dataset.drop(columns=['Prezzo']).shape[1]}")
    dataset = pd.get_dummies(dataset, columns=['Località'])
    print(f"训练集独热编码后列名: {dataset.columns.tolist()}")
    X = dataset.drop(columns=['Prezzo'])
    y = dataset['Prezzo']
    X_train, X_test, y_train, y_test = train_test_split(X, y)
    # 训练集特征数量
    print(f"X_train 形状: {X_train.shape}")
    model = Sequential()
    # 确认模型输入维度
    input_dim = X_train.shape[1]
    print(f"Keras模型第一层 input_dim: {input_dim}")
    model.add(Dense(64, activation='relu', input_dim=input_dim,  kernel_regularizer=l2(0.1)))
    # ... (其他层)
    model.compile(loss='mean_squared_error', optimizer=adam, metrics=['accuracy'])
    model.fit(X_train, y_train, epochs=100, batch_size=64)
    return model
# ... (用户输入部分)
dataframe = pd.DataFrame([user_data])
print(f"用户输入DataFrame (独热编码前) 形状: {dataframe.shape}")
print(f"用户输入DataFrame (独热编码前) 列名: {dataframe.columns.tolist()}")
dataframe = pd.get_dummies(dataframe, columns=['Località'])
print(f"用户输入DataFrame (独热编码后) 形状: {dataframe.shape}")
print(f"用户输入DataFrame (独热编码后) 列名: {dataframe.columns.tolist()}")
valori = dataframe.values
# 确认预测输入数据的形状
print(f"预测输入数据形状: {valori.shape}")
prediction = model.predict(valori)[0][0]
print(f'La predizione del prezzo è: {prediction} €')通过这些打印语句,可以清晰地看到训练集和预测集在独热编码后列数量的差异。
解决此问题的核心思想是:在预测时,必须确保输入数据的特征列与模型训练时所用的特征列完全一致,包括列的数量和顺序。
以下是两种常用的解决方案:
这种方法涉及在训练阶段保存独热编码后的训练集列名,然后在预测阶段,将预测数据的列重新索引以匹配这些列名,并用0填充缺失值。
import pandas as pd
from sklearn.model_selection import train_test_split
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import Adam
from keras.regularizers import l2
import numpy as np
# 定义一个全局变量来存储训练时的特征列名
TRAINING_FEATURES_COLUMNS = None
def carica_dataset():
    # 假设 'dataset.csv' 存在且包含 'Prezzo', 'Località' 等列
    dataset = pd.read_csv("dataset.csv")
    return dataset
def carica_modello():
    global TRAINING_FEATURES_COLUMNS # 声明使用全局变量
    dataset = carica_dataset()
    # 对训练数据进行独热编码
    dataset = pd.get_dummies(dataset, columns=['Località'])
    X = dataset.drop(columns=['Prezzo'])
    y = dataset['Prezzo']
    # 保存训练集的特征列名,供预测时使用
    TRAINING_FEATURES_COLUMNS = X.columns.tolist()
    print(f"训练集独热编码后的特征列名: {TRAINING_FEATURES_COLUMNS}")
    print(f"训练集特征数量: {len(TRAINING_FEATURES_COLUMNS)}")
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
    model = Sequential()
    model.add(Dense(64, activation='relu', input_dim=X_train.shape[1],  kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(32, activation='relu',  kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(16, activation='relu', kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(8, activation='relu', kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(1, activation='linear', kernel_regularizer=l2(0.1)))
    adam = Adam(learning_rate=0.001) # 建议指定学习率
    model.compile(loss='mean_squared_error', optimizer=adam, metrics=['mae']) # 将accuracy改为mae更适合回归问题
    print(f"开始训练模型,输入维度: {X_train.shape[1]}")
    model.fit(X_train, y_train, epochs=100, batch_size=64, verbose=0) # verbose=0 减少训练输出
    print("模型训练完成。")
    return model
# 加载数据集并训练模型
dataset = carica_dataset()
model = carica_modello()
# 定义用户输入字段
fields = {
    'Superficie': float,
    'Numero di stanze da letto': int,
    'Numero di bagni': int,
    'Anno di costruzione': int,
    'Località': str
}
user_data = {}
# 获取用户输入
print("\n--- 请输入预测数据 ---")
for key, value in fields.items():
    while True:
        try:
            user_input = input(f"请输入 {key} 的值: ")
            user_data[key] = value(user_input)
            break
        except ValueError:
            print(f"输入无效,请为 {key} 输入一个有效的值。")
# 准备预测数据
dataframe = pd.DataFrame([user_data])
print(f"用户输入原始 DataFrame: {dataframe.columns.tolist()}")
# 对用户输入数据进行独热编码
dataframe = pd.get_dummies(dataframe, columns=['Località'])
print(f"用户输入独热编码后 DataFrame 列: {dataframe.columns.tolist()}")
# 关键步骤:使用训练集列名对预测DataFrame进行reindex,并用0填充缺失列
if TRAINING_FEATURES_COLUMNS is not None:
    # 确保所有训练时的特征列都存在,不存在的用0填充
    dataframe = dataframe.reindex(columns=TRAINING_FEATURES_COLUMNS, fill_value=0)
else:
    raise RuntimeError("训练集的特征列名未被保存,请先运行 carica_modello()。")
print(f"预测数据对齐训练集列后 DataFrame 列: {dataframe.columns.tolist()}")
print(f"预测数据对齐训练集列后 DataFrame 形状: {dataframe.shape}")
# 转换为NumPy数组进行预测
valori = dataframe.values
# 进行预测
prediction = model.predict(valori)[0][0]
print(f'\n预测的房屋价格是: {prediction:.2f} €')
OneHotEncoder 提供了一个更结构化的方式来处理分类特征。它可以在训练数据上 fit,然后用相同的 encoder 来 transform 训练数据和新的预测数据,从而保证特征的一致性。
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import Adam
from keras.regularizers import l2
import numpy as np
def carica_dataset():
    dataset = pd.read_csv("dataset.csv")
    return dataset
# 定义预处理器(全局或作为模型的一部分)
preprocessor = None
model_pipeline = None # 用于存储包含预处理器和模型的管道
def carica_modello():
    global preprocessor, model_pipeline
    dataset = carica_dataset()
    # 识别分类特征和数值特征
    categorical_features = ['Località']
    numerical_features = [col for col in dataset.drop(columns=['Prezzo']).columns if col not in categorical_features]
    # 创建一个预处理管道
    # OneHotEncoder 处理分类特征,handle_unknown='ignore' 允许在预测时遇到未见过的类别时忽略,而不是报错
    # remainder='passthrough' 确保数值特征被保留
    preprocessor = ColumnTransformer(
        transformers=[
            ('cat', OneHotEncoder(handle_unknown='ignore'), categorical_features),
            ('num', 'passthrough', numerical_features)
        ])
    X = dataset.drop(columns=['Prezzo'])
    y = dataset['Prezzo']
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
    # 在训练数据上拟合预处理器并转换数据
    X_train_processed = preprocessor.fit_transform(X_train)
    # Keras模型定义
    model = Sequential()
    model.add(Dense(64, activation='relu', input_dim=X_train_processed.shape[1],  kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(32, activation='relu',  kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(16, activation='relu', kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(8, activation='relu', kernel_regularizer=l2(0.1)))
    model.add(Dropout(0.5))
    model.add(Dense(1, activation='linear', kernel_regularizer=l2(0.1)))
    adam = Adam(learning_rate=0.001)
    model.compile(loss='mean_squared_error', optimizer=adam, metrics=['mae'])
    print(f"Keras模型输入维度: {X_train_processed.shape[1]}")
    model.fit(X_train_processed, y_train, epochs=100, batch_size=64, verbose=0)
    print("模型训练完成。")
    # 可以将预处理器和模型封装在一个Pipeline中,方便后续使用
    # model_pipeline = Pipeline(steps=[('preprocessor', preprocessor), ('regressor', model)])
    # 但这里Keras模型不是sklearn Estimator,所以分开管理更常见
    return model
# 加载数据集并训练模型
dataset = carica_dataset()
model = carica_modello()
# 定义用户输入字段
fields = {
    'Superficie': float,
    'Numero di stanze da letto': int,
    'Numero di bagni': int,
    'Anno di costruzione': int,
    'Località': str
}
user_data = {}
# 获取用户输入
print("\n--- 请输入预测数据 ---")
for key, value in fields.items():
    while True:
        try:
            user_input = input(f"请输入 {key} 的值: ")
            user_data[key] = value(user_input)
            break
        except ValueError:
            print(f"输入无效,请为 {key} 输入一个有效的值。")
# 准备预测数据为DataFrame
dataframe = pd.DataFrame([user_data])
print(f"用户输入原始 DataFrame 列: {dataframe.columns.tolist()}")
# 关键步骤:使用之前拟合的预处理器转换预测数据
if preprocessor is not None:
    valori = preprocessor.transform(dataframe)
else:
    raise RuntimeError("预处理器未被初始化,请先运行 carica_modello()。")
print(f"预测数据预处理后形状: {valori.shape}")
# 进行预测
prediction = model.predict(valori)[0][0]
print(f'\n预测的房屋价格是: {prediction:.2f} €')
注意事项:
Keras模型输入维度不匹配的ValueError通常是数据预处理阶段特征工程不一致的体现,尤其是在处理分类特征并进行独热编码时。解决此问题的关键在于确保训练和预测阶段的特征集具有相同的数量、名称和顺序。通过采用pandas的列对齐机制或sklearn的OneHotEncoder与ColumnTransformer构建健壮的预处理管道,可以有效地避免这类问题,从而构建出更稳定、可靠的机器学习系统。在开发过程中,始终检查数据形状和列名是诊断和预防此类错误的最佳实践。
以上就是Keras模型输入维度不匹配:解决数据预处理中的特征一致性问题的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
 
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号