我认为您的代码中存在一些错误。例如,您没有向我解释的一件事是为什么您在标签上调用stats.mode(labels)。另外,我不知道你是否正确使用了 SMOTE 等等。
但是,由于您无法在 stackoverflow 上提供任何数据,因此我从 Kaggle 获取了 Rain in Australia 数据集。
它看起来和你的很相似。最大的不同是我们只有两个关于“明天下雨”的课程:yes 和 no。但它也很不平衡:
yes 77%
no 23%
我用一些非常懒惰的预处理实现了一个简单的模型(所以这一切可能会提高很多。但是,你应该从这个例子中学习并与你的例子进行比较就足够了. 只需下载数据here 并尝试一下。
在没有任何调整和平衡的情况下,我能够在测试数据上获得大约 82% 的准确度。在 Kaggle 上,我看到人们在这方面得到了 88%,因此我们绝对可以改进我们的模型。
您需要做的就是确保weatherAUS.csv 在您的系统上可用,并且您安装了 Tensorflow(我使用的是 2.4.1)和其他依赖项。
之后,您应该能够更改代码 s.t.它在你的数据集上运行。目前,不考虑不平衡数据。一旦你的模型开始预测一些更合理的东西,我们可以尝试通过使用一些平衡技术来改进它。
代码(weatherAUS.csv)
import os
import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from tensorflow import keras
from tensorflow.keras import layers
def preprocess(df_, split, num_pipeline, cat_pipeline, targets_encoder, numeric_columns, categorical_columns):
"""Quick, dirty and lazy preprocessing."""
df_split = df_.loc[split]
nums = num_pipeline.transform(df_split[numeric_columns])
cats = cat_pipeline.transform(df_split[categorical_columns].drop(columns='RainTomorrow')).todense()
df_num = pd.DataFrame(nums, index=df_split[numeric_columns].index, columns=df_split[numeric_columns].columns)
df_cat = pd.DataFrame(cats, index=df_split[categorical_columns].index, columns=list(np.concatenate(cat_pipeline.categories_)))
df_X = df_num.join(df_cat).sort_index()
df_Y = targets_encoder.transform(df_split.RainTomorrow.values.reshape(-1, 1)).todense()
df_Y = pd.DataFrame(df_Y, index=df_split.index, columns=targets_encoder.categories_[0]).sort_index()
return df_X, df_Y
def sample_generator(df_X, df_Y, nb_days: int, seed: int = None, max_samples: int = None):
rnd = np.random.RandomState(seed)
locations = df_X.index.get_level_values(0)
loc_indices = list(range(len(locations)))
count = 0
while True:
if max_samples is not None and count >= max_samples:
break
count += 1
# We cannot simply sample a series arbitrarily. Need to make sure
# that samples come from one particular location (city). Thus, we pic a location first ...
location = locations[rnd.choice(loc_indices)]
# .. and now we pick nb_days for that location
offs = rnd.randint(0, len(df_X.loc[location]) - nb_days)
start = offs
end = offs + nb_days
x = df_X.loc[location].iloc[start:end]
y = df_Y.loc[location].iloc[end - 1]
yield x, y
def fill_missing_values(df_raw, numeric_columns, categorical_columns):
"""Quick and dirty missing-value preprocessing."""
locations = set(df_raw.Location.unique())
# Fill missing values
dfs = list()
for location in locations:
df_loc = df_raw[df_raw.Location == location].set_index('Date')
# We resample and interpolate ..
df_num = df_loc[numeric_columns].resample('1D').mean().interpolate()
# .. and fill the rest with mean values
df_num = df_num.fillna(df_num.mean())
df_cat = df_loc[categorical_columns.union({'Location'})]
# For categorical features we simple take the mode
df_cat = df_cat.fillna(df_cat.mode())
df_ = df_num.join(df_cat)
df_ = df_.reset_index().set_index(['Location', 'Date'])
dfs.append(df_)
df_ = pd.concat(dfs)
df_ = df_[~df_.RainTomorrow.isnull()]
# There could still be some missing values. Again, out of laziness, I just fill nan values with averages and modes
df_num = df_[numeric_columns].fillna(df_[numeric_columns].mean())
df_cat = df_[categorical_columns]
df_cat = df_cat.fillna(df_cat.mode().iloc[0])
df = df_num.join(df_cat)
df = df[sorted(df.columns)]
return df
def main():
file_path = 'weatherAUS.csv'
assert os.path.exists(file_path), f'Cannot find data-file: {file_path}'
seed = 42
df_raw = pd.read_csv(file_path)
# Convert to datetime
df_raw.Date = pd.to_datetime(df_raw.Date, infer_datetime_format=True)
# If a column contains more than 15% null-values we do not consider it as feature.
# I only do this out of laziness. We could probably do better.
null_threshold = 0.15
null_perc = df_raw.isnull().sum() / len(df_raw)
useful_columns = set(null_perc[null_perc < null_threshold].index)
# Separate (useful) numeric and categorical columns
numeric_columns = useful_columns & set(df_raw.select_dtypes(exclude=['object', 'datetime64[ns]']).columns)
categorical_columns = useful_columns & set(df_raw.select_dtypes(exclude=['float64', 'datetime64[ns]']).columns)
categorical_columns = categorical_columns - {'Location'}
df = fill_missing_values(df_raw, numeric_columns=numeric_columns, categorical_columns=categorical_columns)
# Create train/test split by location
train, test = train_test_split(df.index.get_level_values(0).unique(), test_size=0.2, random_state=seed)
# Create preprocessing pipeline for numeric and categorical data
num_pipeline = StandardScaler()
num_pipeline.fit(df.loc[train][numeric_columns])
cat_pipeline = OneHotEncoder()
cat_pipeline.fit(df.loc[train][categorical_columns].drop(columns='RainTomorrow'))
# Targets encoder
targets_encoder = OneHotEncoder()
targets_encoder.fit(df.loc[train].RainTomorrow.dropna().unique().reshape(-1, 1))
# Preprocess
df_train_X, df_train_Y = preprocess(
df, train,
num_pipeline=num_pipeline,
cat_pipeline=cat_pipeline,
targets_encoder=targets_encoder,
numeric_columns=numeric_columns,
categorical_columns=categorical_columns
)
df_test_X, df_test_Y = preprocess(
df, test,
num_pipeline=num_pipeline,
cat_pipeline=cat_pipeline,
targets_encoder=targets_encoder,
numeric_columns=numeric_columns,
categorical_columns=categorical_columns
)
# Just checking that we have indeed separated locations
assert len(set(df_train_X.index.get_level_values(0)).intersection(set(df_test_X.index.get_level_values(0)))) == 0
# Create Tensorflow dataset
nb_days = 7 # The number of days we consider
batch_size = 50
nb_features = len(df_train_X.columns)
nb_classes = len(targets_encoder.categories_[0])
train_data = tf.data.Dataset.from_generator(
lambda: sample_generator(df_train_X, df_train_Y, nb_days, seed=seed),
output_shapes=((None, nb_features), (nb_classes,)),
output_types=(tf.float32, tf.float32)
).prefetch(batch_size).padded_batch(batch_size)
test_data = tf.data.Dataset.from_generator(
lambda: sample_generator(df_test_X, df_test_Y, nb_days, seed=seed, max_samples=1000),
output_shapes=((None, nb_features), (nb_classes,)),
output_types=(tf.float32, tf.float32)
).prefetch(batch_size).padded_batch(batch_size)
# Create model
inputs = layers.Input(shape=(None, nb_features,))
x = inputs
x = layers.Bidirectional(layers.LSTM(128, return_sequences=True, dropout=0.1))(x)
x = layers.Bidirectional(layers.LSTM(128, return_sequences=False, dropout=0.1))(x)
x = layers.Dense(nb_classes, activation='softmax')(x)
model = keras.Model(inputs=inputs, outputs=x)
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics='accuracy'
)
# Train
model.fit(
train_data,
epochs=20,
steps_per_epoch=100,
validation_data=test_data.repeat(),
validation_steps=50
)
_, accuracy = model.evaluate(test_data)
random = (df.RainTomorrow.value_counts() / df.RainTomorrow.value_counts().sum()).max()
print(f'Accuracy: {100 * accuracy:.2f} %')
print(f'Random: {100 * random:.2f} %')
if __name__ == '__main__':
main()
输出
Epoch 1/5
100/100 [==============================] - 50s 469ms/step - loss: 0.4868 - accuracy: 0.7843 - val_loss: 0.5015 - val_accuracy: 0.7708
Epoch 2/5
100/100 [==============================] - 45s 459ms/step - loss: 0.4142 - accuracy: 0.8202 - val_loss: 0.4592 - val_accuracy: 0.7972
Epoch 3/5
100/100 [==============================] - 46s 462ms/step - loss: 0.3692 - accuracy: 0.8383 - val_loss: 0.4622 - val_accuracy: 0.8036
Epoch 4/5
100/100 [==============================] - 46s 461ms/step - loss: 0.3535 - accuracy: 0.8469 - val_loss: 0.4463 - val_accuracy: 0.8204
Epoch 5/5
100/100 [==============================] - 46s 463ms/step - loss: 0.3505 - accuracy: 0.8427 - val_loss: 0.4347 - val_accuracy: 0.8132
20/20 [==============================] - 3s 145ms/step - loss: 0.4384 - accuracy: 0.8140
Accuracy: 81.40 %
Random: 77.58 %
OP 数据的代码和结果
注意:提供的数据集非常小(约 3000 个样本)。
from operator import itemgetter
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import tensorflow as tf
from sklearn.metrics import confusion_matrix, classification_report
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from tensorflow import keras
from tensorflow.keras import layers
def preprocess(df: pd.DataFrame, inputs_pipeline, targets_encoder):
numeric_columns = df.select_dtypes(exclude=['object']).columns
inputs = inputs_pipeline.transform(df[numeric_columns])
targets = targets_encoder.transform(df.Rainfall9AM.values.reshape(-1, 1)).todense()
df_inputs = pd.DataFrame(inputs, index=df.index, columns=numeric_columns)
df_targets = pd.DataFrame(targets, index=df.index, columns=targets_encoder.categories_[0])
return df_inputs, df_targets
def sample_generator(df_inputs: pd.DataFrame, df_targets: pd.DataFrame, nb_days: int, max_samples: int = None, seed=42):
rnd = np.random.RandomState(seed)
count = 0
while True:
if max_samples is not None and count >= max_samples:
break
count += 1
offs = rnd.randint(0, len(df_inputs) - nb_days)
start = offs
end = offs + nb_days
x = df_inputs.iloc[start:end]
y = df_targets.iloc[end]
yield x, y
def fill_missing_values(df: pd.DataFrame) -> pd.DataFrame:
"""Very lazy and dirty preprocessing. Replace this by something more sophisticated."""
df_targets = df[['Rainfall9AM']]
df = df.resample('1D').mean().interpolate()
df = df.fillna(df.mean())
df = df[sorted(df.columns)]
df = df.join(df_targets)
df = df[~df.Rainfall9AM.isnull()]
df = df.dropna(1)
assert not df.isnull().any().any()
df = df.sort_index()
return df
def get_model(nb_features: int, nb_classes: int):
inputs = layers.Input(shape=(None, nb_features,))
x = inputs
x = layers.Bidirectional(layers.LSTM(128, return_sequences=True, dropout=0.1))(x)
x = layers.Bidirectional(layers.LSTM(128, return_sequences=False, dropout=0.1))(x)
x = layers.Dense(nb_classes, activation='softmax')(x)
model = keras.Model(inputs=inputs, outputs=x)
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics='accuracy'
)
return model
def main():
fp = 'Arbaminch_Rainfall_Classiffication.csv'
df_raw = pd.read_csv(fp)
# Convert to datetime
df_raw.Date = pd.to_datetime(
df_raw.Date,
infer_datetime_format=True,
# FIXME The provided data contains errors (invalid dates e.g. 2/29/2009)
errors='coerce'
)
df_raw = df_raw.set_index('Date')
# Fill missing values
# FIXME The preprocessing is lazy and should be improved!
df = fill_missing_values(df_raw)
# Create train/test split
nb_train = int(len(df) * 0.8)
df_train_data, df_test_data = df[:nb_train], df[nb_train:]
print(f'Training samples: {len(df_train_data)}')
print(f'Test samples: {len(df_test_data)}')
# Create preprocessing pipeline for numeric and categorical data
numeric_columns = df_train_data.select_dtypes(exclude=['object']).columns
inputs_pipeline = StandardScaler()
inputs_pipeline.fit(df_train_data[numeric_columns])
targets_encoder = OneHotEncoder()
targets_encoder.fit(df_train_data.Rainfall9AM.dropna().unique().reshape(-1, 1))
# Preprocess
df_train_X, df_train_Y = preprocess(
df_train_data,
inputs_pipeline=inputs_pipeline,
targets_encoder=targets_encoder
)
df_test_X, df_test_Y = preprocess(
df_test_data,
inputs_pipeline=inputs_pipeline,
targets_encoder=targets_encoder
)
assert len(set(df_train_X.index.get_level_values(0)).intersection(set(df_test_X.index.get_level_values(0)))) == 0
# Create Tensorflow dataset
nb_days = 7
batch_size = 100
nb_features = len(df_train_X.columns)
nb_classes = len(targets_encoder.categories_[0])
train_data = tf.data.Dataset.from_generator(
lambda: sample_generator(df_train_X, df_train_Y, nb_days=nb_days),
output_shapes=((None, nb_features), (nb_classes,)),
output_types=(tf.float32, tf.float32)
).prefetch(batch_size).padded_batch(batch_size)
test_data = tf.data.Dataset.from_generator(
lambda: sample_generator(df_test_X, df_test_Y, nb_days=nb_days, max_samples=1000),
output_shapes=((None, nb_features), (nb_classes,)),
output_types=(tf.float32, tf.float32)
).prefetch(batch_size).padded_batch(batch_size)
# Get model
model = get_model(nb_features=nb_features, nb_classes=nb_classes)
# Start training
class_names = list(targets_encoder.categories_[0])
class_counts = dict(df_train_data.Rainfall9AM.value_counts())
total = float(len(df_train_data))
class_weight = dict([(i, np.log(total / class_counts[cname])) for i, cname in enumerate(class_names)])
model.fit(
train_data,
epochs=5,
steps_per_epoch=100,
validation_data=test_data.repeat(),
validation_steps=50,
class_weight=class_weight
)
# Evaluate model
y_pred = np.argmax(model.predict(test_data), axis=1)
y_true = np.argmax(np.concatenate(list(map(itemgetter(1), list(test_data)))), axis=1)
confusion = pd.DataFrame(confusion_matrix(y_true, y_pred), index=class_names, columns=class_names)
plt.figure()
sns.heatmap(confusion, annot=True, fmt='d')
plt.show()
print(classification_report(y_true, y_pred, target_names=class_names))
if __name__ == '__main__':
main()
precision recall f1-score support
Heavy_Rain 0.25 0.25 0.25 170
Light_Rain 0.12 0.31 0.17 126
Moderate 0.18 0.37 0.24 140
No_Rain 0.79 0.30 0.43 564
accuracy 0.30 1000
macro avg 0.33 0.31 0.27 1000
weighted avg 0.53 0.30 0.34 1000