利用Python进行邮件预测(LSTMs 和 时间序列)

本文包括邮箱登入,邮件数据分析,长短时神经网络分析,时间序列分析。

参考文献原文为对google邮箱的翻译


1 163邮箱登入

本文利用imapclient库进行登入,网易邮箱登入会遇到错误可以refer一下链接 本文主要获取邮件收取的时间序列,需要获取邮件更多信息可以参考 。获取2016年1月1日开始至今的邮件。

import imapclient
import pandas as pd
import getpass

youremail = input()
yourpassword = getpass.getpass()

imapObj = imapclient.IMAPClient("imap.163.com", ssl=True) #若使用其他邮箱 可以换为其他邮箱的IMAP服务器地址 
imapObj.login(youremail, yourpassword)
imapObj.select_folder("INBOX", readonly=True)

UIDs = imapObj.search('(SINCE "01-Jan-2016")')

mails = []
for msgid, data in imapObj.fetch(UIDs, ["ENVELOPE"]).items():
    envelope = data[b"ENVELOPE"]
    date = envelope.date
    if envelope.subject is not None:
        subject = envelope.subject.decode()
    else:
        subject = None
    mails.append((subject, date))

mail_df = pd.DataFrame(mails)
mail_df.columns = ["Subject", "Date"]
mail_df["Date"] = pd.to_datetime(mail_df["Date"])
mail_df = mail_df.set_index("Date")

print("A total of {} e-mails loaded.".format(len(mail_df)))

python2 环境可以运行,换完python3 环境会遇到以下问题[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:748) 暂未解决


2 邮件数据分析

本节对数据按照小时和星期进行总汇分析,并进行可视化。
对于可视化的色彩渐变可参考,更改调色板可参考。

#分析汇总代码
import calendar
import seaborn as sns
import matplotlib.pyplot as plt

weekdays = [calendar.day_name[i] for i in range(7)]

# E-Mails per Hour
per_hour = pd.DataFrame(mail_df["Subject"].resample("h").count())
per_hour_day = (
    per_hour.groupby([per_hour.index.hour]).sum()
    / per_hour.groupby([per_hour.index.hour]).count()
)
per_hour_day.reset_index(inplace=True)
per_hour_day.columns = ["Hour", "Count"]

# E-Mails per day
per_day = pd.DataFrame(mail_df["Subject"].resample("d").count())
per_day_week = (
    per_day.groupby([per_day.index.weekday]).sum()
    / per_day.groupby([per_day.index.weekday]).count()
)

per_day_week.reset_index(inplace=True)
per_day_week.columns = ["Weekday", "Count"]
per_day_week["Weekday"] = weekdays
#可视化代码
def return_cmap(data):
    # Function to create a colormap
    v = data["Count"].values
    colors = plt.cm.YlGnBu((v - v.min()) / (v.max() - v.min()))
    return colors

plt.figure(figsize=(12, 10), dpi=600)
plt.subplot(2, 1, 1)
cmap = return_cmap(per_hour_day)
sns.barplot(x="Hour", y="Count", data=per_hour_day, palette=cmap)
plt.title("Emails per hour")

plt.subplot(2, 1, 2)
cmap = return_cmap(per_day_week)
sns.barplot(x="Weekday", y="Count", data=per_day_week, palette=cmap)
plt.title("Emails per weekday")

plt.show()

print("Average number of emails per day: {:.2f}".format(per_hour_day.sum()["Count"]))

利用Python进行邮件预测(LSTMs 和 时间序列)_第1张图片
(本文图为做作邮箱的个人邮件信息)在一天中,邮件分发主要时间段为8点到18点,也是主要的工作时间,其中10-11点获取的邮件最多。在一周中,Mon-Thur的邮件较多,Fir开始邮件量减少,周末邮件也相对较少。由于每小时电子邮件量均有各自特征,因此对每小时进行预测是没有意义的,作者将尝试预测每天的电子邮件工作量


3 邮件LSTMs 预测可行性分析

在大多数情况下,发送方互相不认识,因此可以假设它们在统计上是独立的。另外,我们可以将收件近似为泊松过程(Poissonian),它的标准偏差等于平均值。由于分布是随机的,12封电子邮件的期望的RMSE值至少为3.5(sqrt(11.95)~3.461)。
RMSE与标准差对比:标准差是用来衡量一组数自身的离散程度,而均方根误差是用来衡量观测值同真值之间的偏差,它们的研究对象和研究目的不同,但是计算过程类似。

如果想进一步阅读,可以看三种排队模型,本文用埃尔朗模型。这里提供一篇用该分布做的机场排队模型的分布。

计算基准线

为了创建基线模型,我们可以使用历史数据中的查询表。对于任意的某一天,计算在一天之前收到的电子邮件数量。为了对模型进行基准测试,我使用一个移动窗口来创建查询表,并计算到第二天的预测差异值。

from sklearn.metrics import mean_squared_error
import numpy as np

data = per_day.copy()
test_split = int(len(data) * 0.8)

pred_base = []
for i in range(len(data) - test_split):
    train_data = data[i : test_split + i]
    test_data = data.iloc[test_split + i]
    train_data_week = (
        train_data.groupby([train_data.index.weekday]).sum()
        / train_data.groupby([train_data.index.weekday]).count()
    )

    baseline_prediction = train_data_week.loc[test_data.name.weekday()]
    pred_base.append(baseline_prediction.values)

test_data = data[test_split:]

mse_baseline = mean_squared_error(test_data.values, pred_base)

print("RMSE for BASELINE {:.2f}".format(np.sqrt(mse_baseline)))

基线模型得到的RMSE为7.86 ,相对于3.5的预期值,这个结果还不错。

4 利用LSTM模型进行预测

作为进阶模型,我们将使用长短时记忆(LSTM)神经网络。在这里可以找到对LSTM的很好的介绍(https:/machinelearningmaster ery.com/time-Series-prediction-lstm-rrurn-neuro-network-python-keras/)。

在这里,我们将窗口设置为6天,并让模型预测第7天。

利用python3进行预测。

import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation
from keras.layers import LSTM
from sklearn.preprocessing import MinMaxScaler

def get_window_data(data, window):
    # Get window data and scale
    scaler = MinMaxScaler(feature_range=(0, 1))
    data = scaler.fit_transform(data.reshape(-1, 1))

    X,y = [],[]
    for i in range(len(data) - window - 1):
        X.append(data[i : i + window])
        y.append(data[i + window + 1])
    X = np.asarray(X)
    y = np.asarray(y)
    return X, y, scaler

window_size = 6
X, y, scaler = get_window_data(per_day["Subject"].values, window_size)

X_train = X[:test_split]
X_test = X[test_split:]

y_train = y[:test_split]
y_test = y[test_split:]
 

model = Sequential()
model.add(LSTM(50, input_shape=(window_size, 1)))
model.add(Dropout(0.2))
model.add(Dense(1))
model.add(Activation("linear"))
model.compile(loss="mse", optimizer="adam")

history = model.fit(X_train,y_train,epochs=20,batch_size=1,validation_data=(X_test, y_test),verbose=2,shuffle=False,)

# plot history
plt.figure(figsize=(6, 5), dpi=600)
plt.plot(history.history["loss"], 'darkred', label="Train")
plt.plot(history.history["val_loss"], 'darkblue', label="Test")
plt.title("Loss over epoch")
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()

mse_lstm = mean_squared_error(
    scaler.inverse_transform(y_test),
    scaler.inverse_transform(model.predict(X_test)),
)

print("RMSE for LSTM {:.2f}".format(np.sqrt(mse_lstm)))

利用Python进行邮件预测(LSTMs 和 时间序列)_第2张图片

5 利用prophet的时间序列预测

接下来,我们将使用facebook的Prophet库(https://github.com/facebook/prophet)。它是一个加性模型,我们可以用年、周和日的季节性来拟合非线性趋势。同样地,我们将把数据分成训练集和测试集,并计算RMSE值。

在python2 上运行。

from fbprophet import Prophet
from tqdm import tqdm

prophet_data = data.reset_index()
prophet_data["ds"] = prophet_data["Date"]
prophet_data["y"] = prophet_data["Subject"]

pred = []
for i in tqdm(range(len(data) - test_split)):
    data_to_fit = prophet_data[: (test_split + i)]
    prophet_model = Prophet(interval_width=0.95)

    prophet_model.fit(data_to_fit)

    prophet_forecast = prophet_model.make_future_dataframe(periods=1, freq="d")
    prophet_forecast = prophet_model.predict(prophet_forecast)

    pred.append(prophet_forecast["yhat"].iloc[-1])

mse_prophet = mean_squared_error(test_data.values, pred)

print("RMSE for PROPHET {:.2f}".format(np.sqrt(mse_prophet)))

从RMSE来看,Prophet模型实现了最佳性能。现在让我们研究一下这是为什么。为此,我们绘制模型的各个构成部分,以便更好地理解模型所做的工作。这只需要使用性能指标函数就可以输出结果。

通过检查Prophet模型的构成,我们可以看到它识别出了数据中的关键趋势。总趋势表明邮件数量在整体上不断增加。每周的季节性准确地描绘了工作日/周末的时间波动。每年的季节性显示主要节日,即新年电子邮件很少,但在圣诞节前有所增加,而低点在9月份。

总结:预测邮件数量。

最后,我们使用Prophet模型作为我们的预测工具。为此,我们再次登录到IMAP服务器,并使用自2016年1月1日以来的所有历史数据来训练我们的预测模型。训练完毕后,我们绘制了前一周的历史数据和下一周的预测情况。

from datetime import datetime, timedelta
import matplotlib.pyplot as plt
from fbprophet import Prophet
import imapclient
import pandas as pd
import getpass

'''
youremail ='[email protected]'
yourpassword = getpass.getpass()

imapObj = imapclient.IMAPClient("imap.gmail.com", ssl=True)
imapObj.login(youremail, yourpassword)
imapObj.select_folder("INBOX", readonly=True)

UIDs = imapObj.search('(SINCE "01-Jan-2016")')

mails = []
for msgid, data in imapObj.fetch(UIDs, ["ENVELOPE"]).items():
    envelope = data[b"ENVELOPE"]
    date = envelope.date
    if envelope.subject is not None:
        subject = envelope.subject.decode()
    else:
        subject = None
    mails.append((subject, date))

mail_df = pd.DataFrame(mails)
mail_df.columns = ["Subject", "Date"]
mail_df["Date"] = pd.to_datetime(mail_df["Date"])
mail_df = mail_df.set_index("Date")

data = pd.DataFrame(mail_df["Subject"].resample("d").count())
'''
 
data = per_day
prophet_model = Prophet(interval_width=0.95)

prophet_data = data.reset_index()
prophet_data["ds"] = prophet_data["Date"]
prophet_data["y"] = prophet_data["Subject"]

prophet_model.fit(prophet_data)

prophet_forecast = prophet_model.make_future_dataframe(periods=7, freq="d")
prophet_forecast = prophet_model.predict(prophet_forecast)

fig1 = prophet_model.plot(prophet_forecast)

datenow = datetime.now()
dateend = datenow + timedelta(days=7)
datestart = dateend - timedelta(days=14)

plt.xlim([datestart, dateend])
plt.title("Email forecast", fontsize=20)
plt.xlabel("Day", fontsize=20)
plt.ylabel("Emails expected", fontsize=20)
plt.axvline(datenow, color="k", linestyle=":")
plt.show()

讨论

LSTMS和Facebook的Prophet模型提供了一种简单易懂的方法来预测电子邮件数量,而且具有相当好的准确性。考虑到模型的基本机制,这一结果是可以理解的。LSTM预测是基于一组最后的值,因此不太容易考虑到季节差异。相比之下,Prophet模型发现并显示了季节性。

该模型可以对规划未来的工作量或人员配置提供参考。

这类问题的一个关键挑战是,如果只有零星的偶发事件,那么内在趋势是不可预测的。

最后别忘了,我们的基线模型的表现就很不错,所以你不一定总是需要复杂的机器学习算法来搭建你的预测模型。

你可能感兴趣的:(小demo)