Compare commits
8 Commits
d7c8019f96
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| eab1a62ffb | |||
| 6d42d9dac3 | |||
| 1e1d4b0d17 | |||
| cc85e3807a | |||
| 77e38fd15b | |||
| ff0fbf96f7 | |||
| 844cf9a130 | |||
| d70bd54c41 |
362
README.md
362
README.md
@@ -1,253 +1,279 @@
|
|||||||
# 基于多维特征挖掘的员工缺勤分析与预测系统
|
# 中国企业员工缺勤分析与预测系统
|
||||||
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
|
|
||||||
本系统基于 UCI Absenteeism 数据集,利用机器学习算法对员工考勤数据进行深度分析,挖掘影响缺勤的多维度特征,构建缺勤预测模型,为企业人力资源管理提供科学、客观的决策支持。
|
本项目面向企业人力资源管理与运营分析场景,围绕员工缺勤事件构建了一个集数据分析、风险预测、群体画像与可视化展示于一体的毕业设计系统。系统支持缺勤趋势分析、影响因素挖掘、单次缺勤时长预测、多模型对比以及员工群体聚类展示。
|
||||||
|
|
||||||
## 功能特性
|
后端采用 `Flask + scikit-learn + PyTorch`,前端采用 `Vue 3 + Element Plus + ECharts`。当前版本同时支持传统机器学习模型和 `LSTM+MLP` 深度学习模型。
|
||||||
|
|
||||||
### F01 数据概览与全局统计
|
## 功能模块
|
||||||
- 基础统计指标展示(样本总数、员工总数、缺勤总时长等)
|
|
||||||
|
### 1. 数据概览
|
||||||
|
|
||||||
|
- 基础统计指标展示
|
||||||
- 月度缺勤趋势分析
|
- 月度缺勤趋势分析
|
||||||
- 星期分布分析
|
- 星期分布分析
|
||||||
- 缺勤原因分布分析
|
- 请假类型与原因分布分析
|
||||||
- 季节分布分析
|
- 季节分布分析
|
||||||
|
|
||||||
### F02 多维特征挖掘与影响因素分析
|
### 2. 影响因素分析
|
||||||
- 特征重要性排序(基于随机森林)
|
|
||||||
- 相关性热力图分析
|
|
||||||
- 群体对比分析(饮酒/吸烟/学历/子女等维度)
|
|
||||||
|
|
||||||
### F03 员工缺勤风险预测
|
- 特征重要性排序
|
||||||
- 单次缺勤预测
|
- 相关性热力图
|
||||||
- 风险等级评估(低/中/高)
|
- 多维群体对比分析
|
||||||
- 模型性能展示(R²、MSE、RMSE、MAE)
|
|
||||||
|
|
||||||
### F04 员工画像与群体聚类
|
### 3. 缺勤预测
|
||||||
- K-Means 聚类结果展示
|
|
||||||
- 员工群体雷达图
|
- 单次缺勤时长预测
|
||||||
- 聚类散点图可视化
|
- 风险等级评估
|
||||||
|
- 多模型结果对比
|
||||||
|
- 传统模型与深度学习模型切换
|
||||||
|
|
||||||
|
### 4. 员工画像
|
||||||
|
|
||||||
|
- 聚类结果展示
|
||||||
|
- 群体画像分析
|
||||||
|
- 群体散点图可视化
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
### 后端
|
### 后端
|
||||||
|
|
||||||
- Python 3.11
|
- Python 3.11
|
||||||
- Flask 2.3.3
|
- Flask 2.3.3
|
||||||
- scikit-learn 1.3.0
|
- Flask-CORS 4.0.0
|
||||||
- XGBoost 1.7.6
|
|
||||||
- LightGBM 4.1.0
|
|
||||||
- pandas 2.0.3
|
- pandas 2.0.3
|
||||||
- numpy 1.24.3
|
- numpy 1.24.3
|
||||||
|
- scikit-learn 1.3.0
|
||||||
|
- xgboost 1.7.6
|
||||||
|
- lightgbm 4.1.0
|
||||||
|
- PyTorch 2.6.0
|
||||||
|
|
||||||
### 前端
|
### 前端
|
||||||
- Vue 3.4
|
|
||||||
- Element Plus 2.4
|
- Vue 3
|
||||||
- ECharts 5.4
|
- Vite
|
||||||
- Axios 1.6
|
- Element Plus
|
||||||
- Vue Router 4.2
|
- ECharts
|
||||||
- Vite 5.0
|
- Axios
|
||||||
|
- Vue Router
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```text
|
||||||
|
forsetsystem/
|
||||||
|
├── backend/
|
||||||
|
│ ├── api/ # 接口层
|
||||||
|
│ ├── core/ # 数据生成、特征工程、训练、聚类、深度学习
|
||||||
|
│ ├── services/ # 业务服务层
|
||||||
|
│ ├── data/
|
||||||
|
│ │ └── raw/
|
||||||
|
│ │ └── china_enterprise_absence_events.csv
|
||||||
|
│ ├── models/ # 模型文件与训练工件
|
||||||
|
│ ├── app.py # 后端入口
|
||||||
|
│ ├── config.py # 项目配置
|
||||||
|
│ └── requirements.txt
|
||||||
|
├── frontend/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── api/
|
||||||
|
│ │ ├── router/
|
||||||
|
│ │ ├── styles/
|
||||||
|
│ │ ├── views/
|
||||||
|
│ │ ├── App.vue
|
||||||
|
│ │ └── main.js
|
||||||
|
│ ├── package.json
|
||||||
|
│ └── vite.config.js
|
||||||
|
├── docs/ # 系统文档、论文文档与安装说明
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
## 环境要求
|
## 环境要求
|
||||||
|
|
||||||
| 项目 | 要求 |
|
| 项目 | 要求 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 操作系统 | Windows 10/11、Linux、macOS |
|
| 操作系统 | Windows 10 / Windows 11 |
|
||||||
| Python | 3.11 |
|
| Python | 3.11 |
|
||||||
| Node.js | 16.0+ |
|
|
||||||
| Conda | Anaconda 或 Miniconda |
|
| Conda | Anaconda 或 Miniconda |
|
||||||
| pnpm | 8.0+ |
|
| Node.js | 16+ |
|
||||||
|
| pnpm | 8+ |
|
||||||
|
| CUDA | 建议与 PyTorch `cu124` 轮子匹配 |
|
||||||
|
|
||||||
## 安装部署
|
## 安装部署
|
||||||
|
|
||||||
### 1. 克隆项目
|
推荐使用 `conda` 虚拟环境,并优先安装官方 GPU 版 `PyTorch`。
|
||||||
|
|
||||||
```bash
|
### 1. 创建并激活 conda 环境
|
||||||
git clone <repository-url>
|
|
||||||
cd forsetsystem
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 后端环境配置
|
|
||||||
|
|
||||||
#### 创建 Conda 环境
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
conda create -n forsetenv python=3.11 -y
|
conda create -n forsetenv python=3.11 -y
|
||||||
conda activate forsetenv
|
conda activate forsetenv
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 安装机器学习库(使用 conda-forge)
|
### 2. 安装 PyTorch GPU 版
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
conda install -c conda-forge pandas=2.0.3 numpy=1.24.3 scikit-learn=1.3.0 xgboost=1.7.6 lightgbm=4.1.0 joblib=1.3.1 -y
|
pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 --index-url https://download.pytorch.org/whl/cu124
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 安装 Web 框架
|
### 3. 安装其余后端依赖
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
pip install Flask==2.3.3 Flask-CORS==4.0.0 python-dotenv==1.0.0
|
pip install Flask==2.3.3 Flask-CORS==4.0.0 python-dotenv==1.0.0
|
||||||
|
pip install pandas==2.0.3 numpy==1.24.3 scikit-learn==1.3.0 joblib==1.3.1
|
||||||
|
pip install xgboost==1.7.6 lightgbm==4.1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 验证安装
|
如需直接使用依赖文件,可在安装 GPU 版 `PyTorch` 后执行:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
python -c "import pandas,numpy,sklearn,xgboost,lightgbm,flask;print('All libraries installed successfully')"
|
pip install -r backend/requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 训练模型
|
### 4. 安装前端依赖
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
cd backend
|
|
||||||
python core/train_model.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 前端环境配置
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
cd frontend
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
## 运行说明
|
## 启动方式
|
||||||
|
|
||||||
### 启动后端服务
|
### 1. 生成数据集
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
conda activate forsetenv
|
|
||||||
cd backend
|
cd backend
|
||||||
|
python core/generate_dataset.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 训练模型
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python core/train_model.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动后端
|
||||||
|
|
||||||
|
```powershell
|
||||||
python app.py
|
python app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
后端服务运行在 http://localhost:5000
|
后端默认地址:
|
||||||
|
|
||||||
### 启动前端服务
|
```text
|
||||||
|
http://127.0.0.1:5000
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
### 4. 启动前端
|
||||||
cd frontend
|
|
||||||
|
```powershell
|
||||||
|
cd ..\frontend
|
||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
前端服务运行在 http://localhost:5173
|
前端默认地址:
|
||||||
|
|
||||||
### 访问系统
|
```text
|
||||||
|
http://127.0.0.1:5173
|
||||||
打开浏览器访问 http://localhost:5173
|
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
forsetsystem/
|
|
||||||
├── backend/ # 后端项目
|
|
||||||
│ ├── api/ # API 接口层
|
|
||||||
│ │ ├── overview_routes.py # 数据概览接口
|
|
||||||
│ │ ├── analysis_routes.py # 影响因素分析接口
|
|
||||||
│ │ ├── predict_routes.py # 预测接口
|
|
||||||
│ │ └── cluster_routes.py # 聚类接口
|
|
||||||
│ ├── services/ # 业务逻辑层
|
|
||||||
│ ├── core/ # 核心算法层
|
|
||||||
│ │ ├── preprocessing.py # 数据预处理
|
|
||||||
│ │ ├── feature_mining.py # 特征挖掘
|
|
||||||
│ │ ├── train_model.py # 模型训练
|
|
||||||
│ │ └── clustering.py # 聚类分析
|
|
||||||
│ ├── data/ # 数据存储
|
|
||||||
│ ├── models/ # 模型存储
|
|
||||||
│ ├── utils/ # 工具函数
|
|
||||||
│ ├── app.py # 应用入口
|
|
||||||
│ ├── config.py # 配置文件
|
|
||||||
│ └── requirements.txt # 依赖清单
|
|
||||||
│
|
|
||||||
├── frontend/ # 前端项目
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── api/ # API 调用
|
|
||||||
│ │ ├── views/ # 页面组件
|
|
||||||
│ │ ├── router/ # 路由配置
|
|
||||||
│ │ ├── App.vue # 根组件
|
|
||||||
│ │ └── main.js # 入口文件
|
|
||||||
│ ├── index.html
|
|
||||||
│ ├── package.json
|
|
||||||
│ └── vite.config.js
|
|
||||||
│
|
|
||||||
├── data/ # 原始数据
|
|
||||||
│ └── Absenteeism_at_work.csv
|
|
||||||
│
|
|
||||||
├── docs/ # 项目文档
|
|
||||||
│ ├── 00_需求规格说明书.md
|
|
||||||
│ ├── 01_系统架构设计.md
|
|
||||||
│ ├── 02_接口设计文档.md
|
|
||||||
│ ├── 03_数据设计文档.md
|
|
||||||
│ └── 04_UI原型设计.md
|
|
||||||
│
|
|
||||||
└── README.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API 接口
|
## 模型说明
|
||||||
|
|
||||||
### 数据概览模块
|
当前系统支持以下模型类型:
|
||||||
| 接口 | 方法 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| /api/overview/stats | GET | 基础统计指标 |
|
|
||||||
| /api/overview/trend | GET | 月度缺勤趋势 |
|
|
||||||
| /api/overview/weekday | GET | 星期分布 |
|
|
||||||
| /api/overview/reasons | GET | 缺勤原因分布 |
|
|
||||||
| /api/overview/seasons | GET | 季节分布 |
|
|
||||||
|
|
||||||
### 影响因素分析模块
|
- `random_forest`
|
||||||
| 接口 | 方法 | 说明 |
|
- `gradient_boosting`
|
||||||
|------|------|------|
|
- `extra_trees`
|
||||||
| /api/analysis/importance | GET | 特征重要性 |
|
- `xgboost`
|
||||||
| /api/analysis/correlation | GET | 相关性矩阵 |
|
- `lightgbm`
|
||||||
| /api/analysis/compare | GET | 群体对比分析 |
|
- `lstm_mlp`
|
||||||
|
|
||||||
### 预测模块
|
其中:
|
||||||
| 接口 | 方法 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| /api/predict/single | POST | 单次预测 |
|
|
||||||
| /api/predict/model-info | GET | 模型信息 |
|
|
||||||
|
|
||||||
### 聚类模块
|
- 传统模型适合结构化特征解释与特征重要性分析
|
||||||
| 接口 | 方法 | 说明 |
|
- `LSTM+MLP` 适合结合事件序列与静态特征进行预测
|
||||||
|------|------|------|
|
|
||||||
| /api/cluster/result | GET | 聚类结果 |
|
|
||||||
| /api/cluster/profile | GET | 群体画像 |
|
|
||||||
| /api/cluster/scatter | GET | 散点数据 |
|
|
||||||
|
|
||||||
## 作者信息
|
## 数据与训练文件
|
||||||
|
|
||||||
- **作者**:张硕
|
常用路径如下:
|
||||||
- **学校**:河南农业大学软件学院
|
|
||||||
- **项目类型**:本科毕业设计
|
|
||||||
- **完成时间**:2026年3月
|
|
||||||
|
|
||||||
## 后续改进计划
|
- 数据集文件:[china_enterprise_absence_events.csv](D:/VScodeProject/forsetsystem/backend/data/raw/china_enterprise_absence_events.csv)
|
||||||
|
- 配置文件:[config.py](D:/VScodeProject/forsetsystem/backend/config.py)
|
||||||
|
- 数据生成脚本:[generate_dataset.py](D:/VScodeProject/forsetsystem/backend/core/generate_dataset.py)
|
||||||
|
- 模型训练脚本:[train_model.py](D:/VScodeProject/forsetsystem/backend/core/train_model.py)
|
||||||
|
- 深度学习脚本:[deep_learning_model.py](D:/VScodeProject/forsetsystem/backend/core/deep_learning_model.py)
|
||||||
|
|
||||||
### 模型优化
|
## 接口概览
|
||||||
- [ ] 引入深度学习模型(如 LSTM)处理时序特征
|
|
||||||
- [ ] 增加模型解释性分析(SHAP 值可视化)
|
|
||||||
- [ ] 实现模型自动调参(Optuna/Hyperopt)
|
|
||||||
- [ ] 支持多模型集成预测
|
|
||||||
|
|
||||||
### 功能扩展
|
### 数据概览
|
||||||
- [ ] 增加用户认证与权限管理
|
|
||||||
- [ ] 支持自定义数据集上传与分析
|
|
||||||
- [ ] 增加数据导出功能(Excel/PDF 报告)
|
|
||||||
- [ ] 实现预测结果的批量导出
|
|
||||||
- [ ] 增加数据可视化大屏展示
|
|
||||||
|
|
||||||
### 技术改进
|
- `GET /api/overview/stats`
|
||||||
- [ ] 后端迁移至 FastAPI 提升性能
|
- `GET /api/overview/trend`
|
||||||
- [ ] 引入 Redis 缓存常用查询结果
|
- `GET /api/overview/weekday`
|
||||||
- [ ] 使用 Docker 容器化部署
|
- `GET /api/overview/reasons`
|
||||||
- [ ] 增加 CI/CD 自动化测试与部署
|
- `GET /api/overview/seasons`
|
||||||
- [ ] 前端状态管理迁移至 Pinia
|
|
||||||
|
|
||||||
### 数据层面
|
### 影响因素分析
|
||||||
- [ ] 支持数据库存储(MySQL/PostgreSQL)
|
|
||||||
- [ ] 实现数据增量更新机制
|
|
||||||
- [ ] 增加数据质量检测与清洗功能
|
|
||||||
|
|
||||||
## 参考资料
|
- `GET /api/analysis/importance`
|
||||||
|
- `GET /api/analysis/correlation`
|
||||||
|
- `GET /api/analysis/compare`
|
||||||
|
|
||||||
- [UCI Machine Learning Repository - Absenteeism at work Data Set](https://archive.ics.uci.edu/ml/datasets/Absenteeism+at+work)
|
### 缺勤预测
|
||||||
- [Flask 官方文档](https://flask.palletsprojects.com/)
|
|
||||||
- [Vue 3 官方文档](https://vuejs.org/)
|
- `GET /api/predict/models`
|
||||||
- [Element Plus 组件库](https://element-plus.org/)
|
- `GET /api/predict/model-info`
|
||||||
- [ECharts 图表库](https://echarts.apache.org/)
|
- `POST /api/predict/single`
|
||||||
|
- `POST /api/predict/compare`
|
||||||
|
|
||||||
|
### 员工画像
|
||||||
|
|
||||||
|
- `GET /api/cluster/result`
|
||||||
|
- `GET /api/cluster/profile`
|
||||||
|
- `GET /api/cluster/scatter`
|
||||||
|
|
||||||
|
## 文档目录
|
||||||
|
|
||||||
|
详细设计文档见:
|
||||||
|
|
||||||
|
- [docs/README.md](D:/VScodeProject/forsetsystem/docs/README.md)
|
||||||
|
- [09_环境配置与安装说明.md](D:/VScodeProject/forsetsystem/docs/09_环境配置与安装说明.md)
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. `flask_cors` 缺失
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install Flask-CORS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `xgboost` 或 `lightgbm` 缺失
|
||||||
|
|
||||||
|
执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install xgboost==1.7.6 lightgbm==4.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. PyTorch 被安装成 CPU 版
|
||||||
|
|
||||||
|
请重新执行官方 GPU 安装命令:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 --index-url https://download.pytorch.org/whl/cu124
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 如何确认当前使用的是 conda 环境
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda info --envs
|
||||||
|
where python
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目信息
|
||||||
|
|
||||||
|
- 作者:张硕
|
||||||
|
- 学校:河南农业大学软件学院
|
||||||
|
- 项目类型:本科毕业设计
|
||||||
|
- 完成时间:2026 年 3 月
|
||||||
|
|||||||
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@@ -67,3 +67,5 @@ Thumbs.db
|
|||||||
.dmypy.json
|
.dmypy.json
|
||||||
dmypy.json
|
dmypy.json
|
||||||
models
|
models
|
||||||
|
|
||||||
|
data
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ TEST_SIZE = 0.2
|
|||||||
TARGET_COLUMN = '缺勤时长(小时)'
|
TARGET_COLUMN = '缺勤时长(小时)'
|
||||||
EMPLOYEE_ID_COLUMN = '员工编号'
|
EMPLOYEE_ID_COLUMN = '员工编号'
|
||||||
COMPANY_ID_COLUMN = '企业编号'
|
COMPANY_ID_COLUMN = '企业编号'
|
||||||
|
EVENT_SEQUENCE_COLUMN = '事件序号'
|
||||||
|
EVENT_DATE_INDEX_COLUMN = '事件日期索引'
|
||||||
|
|
||||||
WEEKDAY_NAMES = {
|
WEEKDAY_NAMES = {
|
||||||
1: '周一',
|
1: '周一',
|
||||||
@@ -127,6 +129,10 @@ FEATURE_NAME_CN = {
|
|||||||
'是否临时请假': '临时请假',
|
'是否临时请假': '临时请假',
|
||||||
'是否连续缺勤': '连续缺勤',
|
'是否连续缺勤': '连续缺勤',
|
||||||
'前一工作日是否加班': '前一工作日加班',
|
'前一工作日是否加班': '前一工作日加班',
|
||||||
|
'事件日期': '事件日期',
|
||||||
|
'事件日期索引': '事件日期索引',
|
||||||
|
'事件序号': '事件序号',
|
||||||
|
'员工历史事件数': '员工历史事件数',
|
||||||
'缺勤时长(小时)': '缺勤时长',
|
'缺勤时长(小时)': '缺勤时长',
|
||||||
'加班通勤压力指数': '加班通勤压力指数',
|
'加班通勤压力指数': '加班通勤压力指数',
|
||||||
'家庭负担指数': '家庭负担指数',
|
'家庭负担指数': '家庭负担指数',
|
||||||
|
|||||||
712
backend/core/deep_learning_model.py
Normal file
712
backend/core/deep_learning_model.py
Normal file
@@ -0,0 +1,712 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
|
||||||
|
|
||||||
|
import config
|
||||||
|
from core.model_features import engineer_features
|
||||||
|
|
||||||
|
try:
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
from torch.utils.data import DataLoader, Dataset
|
||||||
|
except ImportError:
|
||||||
|
torch = None
|
||||||
|
nn = None
|
||||||
|
DataLoader = None
|
||||||
|
Dataset = object
|
||||||
|
|
||||||
|
|
||||||
|
WINDOW_SIZE = 8
|
||||||
|
SEQUENCE_FEATURES = [
|
||||||
|
'缺勤月份',
|
||||||
|
'星期几',
|
||||||
|
'是否节假日前后',
|
||||||
|
'请假类型',
|
||||||
|
'请假原因大类',
|
||||||
|
'是否提供医院证明',
|
||||||
|
'是否临时请假',
|
||||||
|
'是否连续缺勤',
|
||||||
|
'前一工作日是否加班',
|
||||||
|
'月均加班时长',
|
||||||
|
'通勤时长分钟',
|
||||||
|
'是否夜班岗位',
|
||||||
|
'是否慢性病史',
|
||||||
|
'加班通勤压力指数',
|
||||||
|
'缺勤历史强度',
|
||||||
|
]
|
||||||
|
STATIC_FEATURES = [
|
||||||
|
'所属行业',
|
||||||
|
'婚姻状态',
|
||||||
|
'岗位序列',
|
||||||
|
'岗位级别',
|
||||||
|
'年龄',
|
||||||
|
'司龄年数',
|
||||||
|
'子女数量',
|
||||||
|
'班次类型',
|
||||||
|
'绩效等级',
|
||||||
|
'BMI',
|
||||||
|
'健康风险指数',
|
||||||
|
'家庭负担指数',
|
||||||
|
'岗位稳定性指数',
|
||||||
|
]
|
||||||
|
DEFAULT_EPOCHS = 80
|
||||||
|
DEFAULT_BATCH_SIZE = 128
|
||||||
|
EARLY_STOPPING_PATIENCE = 16
|
||||||
|
TRANSFORMER_D_MODEL = 160
|
||||||
|
TRANSFORMER_HEADS = 5
|
||||||
|
TRANSFORMER_LAYERS = 3
|
||||||
|
|
||||||
|
|
||||||
|
BaseTorchModule = nn.Module if nn is not None else object
|
||||||
|
|
||||||
|
|
||||||
|
class SequenceStaticDataset(Dataset):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
seq_num: np.ndarray,
|
||||||
|
seq_cat: np.ndarray,
|
||||||
|
static_num: np.ndarray,
|
||||||
|
static_cat: np.ndarray,
|
||||||
|
targets: np.ndarray,
|
||||||
|
):
|
||||||
|
self.seq_num = torch.tensor(seq_num, dtype=torch.float32)
|
||||||
|
self.seq_cat = torch.tensor(seq_cat, dtype=torch.long)
|
||||||
|
self.static_num = torch.tensor(static_num, dtype=torch.float32)
|
||||||
|
self.static_cat = torch.tensor(static_cat, dtype=torch.long)
|
||||||
|
self.targets = torch.tensor(targets, dtype=torch.float32)
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
|
def __getitem__(self, index: int):
|
||||||
|
return (
|
||||||
|
self.seq_num[index],
|
||||||
|
self.seq_cat[index],
|
||||||
|
self.static_num[index],
|
||||||
|
self.static_cat[index],
|
||||||
|
self.targets[index],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LearnedAttentionPooling(BaseTorchModule):
|
||||||
|
def __init__(self, hidden_dim: int):
|
||||||
|
super().__init__()
|
||||||
|
self.score = nn.Sequential(
|
||||||
|
nn.Linear(hidden_dim, hidden_dim),
|
||||||
|
nn.Tanh(),
|
||||||
|
nn.Linear(hidden_dim, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
def forward(self, sequence_x: torch.Tensor) -> torch.Tensor:
|
||||||
|
attn_scores = self.score(sequence_x).squeeze(-1)
|
||||||
|
attn_weights = torch.softmax(attn_scores, dim=1)
|
||||||
|
return torch.sum(sequence_x * attn_weights.unsqueeze(-1), dim=1)
|
||||||
|
|
||||||
|
|
||||||
|
class GatedResidualBlock(BaseTorchModule):
|
||||||
|
def __init__(self, input_dim: int, hidden_dim: int, dropout: float = 0.15):
|
||||||
|
super().__init__()
|
||||||
|
self.proj = nn.Linear(input_dim, hidden_dim) if input_dim != hidden_dim else nn.Identity()
|
||||||
|
self.net = nn.Sequential(
|
||||||
|
nn.Linear(input_dim, hidden_dim),
|
||||||
|
nn.LayerNorm(hidden_dim),
|
||||||
|
nn.GELU(),
|
||||||
|
nn.Dropout(dropout),
|
||||||
|
nn.Linear(hidden_dim, hidden_dim),
|
||||||
|
)
|
||||||
|
self.gate = nn.Sequential(
|
||||||
|
nn.Linear(hidden_dim * 2, hidden_dim),
|
||||||
|
nn.Sigmoid(),
|
||||||
|
)
|
||||||
|
self.out_norm = nn.LayerNorm(hidden_dim)
|
||||||
|
|
||||||
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||||
|
residual = self.proj(x)
|
||||||
|
transformed = self.net(x)
|
||||||
|
gate = self.gate(torch.cat([residual, transformed], dim=-1))
|
||||||
|
return self.out_norm(residual + transformed * gate)
|
||||||
|
|
||||||
|
|
||||||
|
class TemporalFusionRegressor(BaseTorchModule):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
seq_num_dim: int,
|
||||||
|
static_num_dim: int,
|
||||||
|
seq_cat_cardinalities: List[int],
|
||||||
|
static_cat_cardinalities: List[int],
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
self.seq_cat_embeddings = nn.ModuleList(
|
||||||
|
[nn.Embedding(cardinality, _embedding_dim(cardinality)) for cardinality in seq_cat_cardinalities]
|
||||||
|
)
|
||||||
|
self.static_cat_embeddings = nn.ModuleList(
|
||||||
|
[nn.Embedding(cardinality, _embedding_dim(cardinality)) for cardinality in static_cat_cardinalities]
|
||||||
|
)
|
||||||
|
|
||||||
|
seq_cat_dim = sum(embedding.embedding_dim for embedding in self.seq_cat_embeddings)
|
||||||
|
static_cat_dim = sum(embedding.embedding_dim for embedding in self.static_cat_embeddings)
|
||||||
|
seq_input_dim = seq_num_dim + seq_cat_dim
|
||||||
|
static_input_dim = static_num_dim + static_cat_dim
|
||||||
|
self.position_embedding = nn.Parameter(torch.randn(WINDOW_SIZE, TRANSFORMER_D_MODEL) * 0.02)
|
||||||
|
|
||||||
|
self.seq_projection = nn.Sequential(
|
||||||
|
nn.Linear(seq_input_dim, TRANSFORMER_D_MODEL),
|
||||||
|
nn.LayerNorm(TRANSFORMER_D_MODEL),
|
||||||
|
nn.GELU(),
|
||||||
|
nn.Dropout(0.12),
|
||||||
|
)
|
||||||
|
encoder_layer = nn.TransformerEncoderLayer(
|
||||||
|
d_model=TRANSFORMER_D_MODEL,
|
||||||
|
nhead=TRANSFORMER_HEADS,
|
||||||
|
dim_feedforward=TRANSFORMER_D_MODEL * 3,
|
||||||
|
dropout=0.15,
|
||||||
|
activation='gelu',
|
||||||
|
batch_first=True,
|
||||||
|
norm_first=True,
|
||||||
|
)
|
||||||
|
self.sequence_encoder = nn.TransformerEncoder(
|
||||||
|
encoder_layer,
|
||||||
|
num_layers=TRANSFORMER_LAYERS,
|
||||||
|
)
|
||||||
|
self.sequence_pool = LearnedAttentionPooling(TRANSFORMER_D_MODEL)
|
||||||
|
self.sequence_head = nn.Sequential(
|
||||||
|
nn.Linear(TRANSFORMER_D_MODEL * 3, 192),
|
||||||
|
nn.LayerNorm(192),
|
||||||
|
nn.GELU(),
|
||||||
|
nn.Dropout(0.18),
|
||||||
|
nn.Linear(192, 128),
|
||||||
|
nn.GELU(),
|
||||||
|
)
|
||||||
|
self.static_net = nn.Sequential(
|
||||||
|
GatedResidualBlock(static_input_dim, 128, dropout=0.15),
|
||||||
|
GatedResidualBlock(128, 96, dropout=0.12),
|
||||||
|
)
|
||||||
|
self.context_gate = nn.Sequential(
|
||||||
|
nn.Linear(128 + 96, 128 + 96),
|
||||||
|
nn.Sigmoid(),
|
||||||
|
)
|
||||||
|
self.fusion = nn.Sequential(
|
||||||
|
GatedResidualBlock(128 + 96, 160, dropout=0.18),
|
||||||
|
nn.Dropout(0.12),
|
||||||
|
nn.Linear(160, 96),
|
||||||
|
nn.GELU(),
|
||||||
|
nn.Dropout(0.08),
|
||||||
|
nn.Linear(96, 1),
|
||||||
|
)
|
||||||
|
self.shortcut_head = nn.Sequential(
|
||||||
|
nn.Linear(seq_num_dim + static_num_dim, 64),
|
||||||
|
nn.LayerNorm(64),
|
||||||
|
nn.GELU(),
|
||||||
|
nn.Dropout(0.08),
|
||||||
|
nn.Linear(64, 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _embed_categorical(self, inputs: torch.Tensor, embeddings: nn.ModuleList) -> Optional[torch.Tensor]:
|
||||||
|
if not embeddings:
|
||||||
|
return None
|
||||||
|
parts = [embedding(inputs[..., index]) for index, embedding in enumerate(embeddings)]
|
||||||
|
return torch.cat(parts, dim=-1)
|
||||||
|
|
||||||
|
def forward(self, seq_num_x, seq_cat_x, static_num_x, static_cat_x):
|
||||||
|
seq_parts = [seq_num_x]
|
||||||
|
seq_embedded = self._embed_categorical(seq_cat_x, self.seq_cat_embeddings)
|
||||||
|
if seq_embedded is not None:
|
||||||
|
seq_parts.append(seq_embedded)
|
||||||
|
seq_input = torch.cat(seq_parts, dim=-1)
|
||||||
|
seq_input = self.seq_projection(seq_input)
|
||||||
|
seq_input = seq_input + self.position_embedding.unsqueeze(0)
|
||||||
|
sequence_context = self.sequence_encoder(seq_input)
|
||||||
|
sequence_last = sequence_context[:, -1, :]
|
||||||
|
sequence_mean = sequence_context.mean(dim=1)
|
||||||
|
sequence_attended = self.sequence_pool(sequence_context)
|
||||||
|
sequence_repr = self.sequence_head(torch.cat([sequence_last, sequence_mean, sequence_attended], dim=1))
|
||||||
|
|
||||||
|
static_parts = [static_num_x]
|
||||||
|
static_embedded = self._embed_categorical(static_cat_x, self.static_cat_embeddings)
|
||||||
|
if static_embedded is not None:
|
||||||
|
static_parts.append(static_embedded)
|
||||||
|
static_input = torch.cat(static_parts, dim=-1)
|
||||||
|
static_repr = self.static_net(static_input)
|
||||||
|
|
||||||
|
fused = torch.cat([sequence_repr, static_repr], dim=1)
|
||||||
|
fused = fused * self.context_gate(fused)
|
||||||
|
shortcut = self.shortcut_head(torch.cat([seq_num_x[:, -1, :], static_num_x], dim=1))
|
||||||
|
return (self.fusion(fused) + shortcut).squeeze(1)
|
||||||
|
|
||||||
|
|
||||||
|
class LSTMMLPRegressor(TemporalFusionRegressor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def is_available() -> bool:
|
||||||
|
return torch is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _embedding_dim(cardinality: int) -> int:
|
||||||
|
return int(min(24, max(4, round(cardinality ** 0.35 * 2))))
|
||||||
|
|
||||||
|
|
||||||
|
def _split_feature_types(df: pd.DataFrame, features: List[str]) -> Tuple[List[str], List[str]]:
|
||||||
|
categorical = []
|
||||||
|
numerical = []
|
||||||
|
for feature in features:
|
||||||
|
if feature not in df.columns:
|
||||||
|
continue
|
||||||
|
if pd.api.types.is_numeric_dtype(df[feature]):
|
||||||
|
numerical.append(feature)
|
||||||
|
else:
|
||||||
|
categorical.append(feature)
|
||||||
|
return categorical, numerical
|
||||||
|
|
||||||
|
|
||||||
|
def _fit_category_maps(df: pd.DataFrame, features: List[str]) -> Dict[str, Dict[str, int]]:
|
||||||
|
category_maps = {}
|
||||||
|
for feature in features:
|
||||||
|
if feature not in df.columns:
|
||||||
|
continue
|
||||||
|
values = sorted(df[feature].astype(str).fillna('__MISSING__').unique().tolist())
|
||||||
|
category_maps[feature] = {value: idx + 1 for idx, value in enumerate(values)}
|
||||||
|
return category_maps
|
||||||
|
|
||||||
|
|
||||||
|
def _encode_categorical_series(values: pd.Series, mapping: Dict[str, int]) -> np.ndarray:
|
||||||
|
return values.astype(str).fillna('__MISSING__').map(lambda value: mapping.get(value, 0)).to_numpy(dtype=np.int64)
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_standardize(values: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
|
||||||
|
if values.shape[1] == 0:
|
||||||
|
return np.zeros((0,), dtype=np.float32), np.ones((0,), dtype=np.float32)
|
||||||
|
mean = values.mean(axis=0)
|
||||||
|
std = values.std(axis=0)
|
||||||
|
std = np.where(std < 1e-6, 1.0, std)
|
||||||
|
return mean.astype(np.float32), std.astype(np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_feature_layout(train_df: pd.DataFrame) -> Dict[str, List[str]]:
|
||||||
|
used_features = sorted(set(SEQUENCE_FEATURES + STATIC_FEATURES))
|
||||||
|
seq_cat_features, seq_num_features = _split_feature_types(train_df, SEQUENCE_FEATURES)
|
||||||
|
static_cat_features, static_num_features = _split_feature_types(train_df, STATIC_FEATURES)
|
||||||
|
all_cat_features = sorted(set(seq_cat_features + static_cat_features))
|
||||||
|
return {
|
||||||
|
'used_features': used_features,
|
||||||
|
'seq_cat_features': seq_cat_features,
|
||||||
|
'seq_num_features': seq_num_features,
|
||||||
|
'static_cat_features': static_cat_features,
|
||||||
|
'static_num_features': static_num_features,
|
||||||
|
'all_cat_features': all_cat_features,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _build_sequence_arrays(
|
||||||
|
df: pd.DataFrame,
|
||||||
|
feature_layout: Dict[str, List[str]],
|
||||||
|
category_maps: Dict[str, Dict[str, int]],
|
||||||
|
target_transform: str,
|
||||||
|
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
||||||
|
df = engineer_features(df.copy())
|
||||||
|
|
||||||
|
for feature in feature_layout['used_features']:
|
||||||
|
if feature not in df.columns:
|
||||||
|
df[feature] = 0
|
||||||
|
|
||||||
|
df = df.sort_values(
|
||||||
|
[config.EMPLOYEE_ID_COLUMN, config.EVENT_DATE_INDEX_COLUMN, config.EVENT_SEQUENCE_COLUMN]
|
||||||
|
).reset_index(drop=True)
|
||||||
|
|
||||||
|
sequence_num_samples = []
|
||||||
|
sequence_cat_samples = []
|
||||||
|
static_num_samples = []
|
||||||
|
static_cat_samples = []
|
||||||
|
targets = []
|
||||||
|
|
||||||
|
for _, group in df.groupby(config.EMPLOYEE_ID_COLUMN, sort=False):
|
||||||
|
seq_num_values = group[feature_layout['seq_num_features']].astype(float).to_numpy(dtype=np.float32)
|
||||||
|
static_num_values = group[feature_layout['static_num_features']].astype(float).to_numpy(dtype=np.float32)
|
||||||
|
target_values = group[config.TARGET_COLUMN].astype(float).to_numpy(dtype=np.float32)
|
||||||
|
|
||||||
|
if feature_layout['seq_cat_features']:
|
||||||
|
seq_cat_values = np.column_stack(
|
||||||
|
[
|
||||||
|
_encode_categorical_series(group[feature], category_maps[feature])
|
||||||
|
for feature in feature_layout['seq_cat_features']
|
||||||
|
]
|
||||||
|
).astype(np.int64)
|
||||||
|
else:
|
||||||
|
seq_cat_values = np.zeros((len(group), 0), dtype=np.int64)
|
||||||
|
|
||||||
|
if feature_layout['static_cat_features']:
|
||||||
|
static_cat_values = np.column_stack(
|
||||||
|
[
|
||||||
|
_encode_categorical_series(group[feature], category_maps[feature])
|
||||||
|
for feature in feature_layout['static_cat_features']
|
||||||
|
]
|
||||||
|
).astype(np.int64)
|
||||||
|
else:
|
||||||
|
static_cat_values = np.zeros((len(group), 0), dtype=np.int64)
|
||||||
|
|
||||||
|
for index in range(len(group)):
|
||||||
|
start_index = max(0, index - WINDOW_SIZE + 1)
|
||||||
|
num_slice = seq_num_values[start_index: index + 1]
|
||||||
|
cat_slice = seq_cat_values[start_index: index + 1]
|
||||||
|
|
||||||
|
num_window = np.zeros((WINDOW_SIZE, len(feature_layout['seq_num_features'])), dtype=np.float32)
|
||||||
|
cat_window = np.zeros((WINDOW_SIZE, len(feature_layout['seq_cat_features'])), dtype=np.int64)
|
||||||
|
num_window[-len(num_slice):] = num_slice
|
||||||
|
if len(feature_layout['seq_cat_features']) > 0:
|
||||||
|
cat_window[-len(cat_slice):] = cat_slice
|
||||||
|
|
||||||
|
sequence_num_samples.append(num_window)
|
||||||
|
sequence_cat_samples.append(cat_window)
|
||||||
|
static_num_samples.append(static_num_values[index].astype(np.float32))
|
||||||
|
static_cat_samples.append(static_cat_values[index].astype(np.int64))
|
||||||
|
targets.append(float(target_values[index]))
|
||||||
|
|
||||||
|
targets_array = np.array(targets, dtype=np.float32)
|
||||||
|
if target_transform == 'log1p':
|
||||||
|
targets_array = np.log1p(np.clip(targets_array, a_min=0, a_max=None)).astype(np.float32)
|
||||||
|
|
||||||
|
return (
|
||||||
|
np.array(sequence_num_samples, dtype=np.float32),
|
||||||
|
np.array(sequence_cat_samples, dtype=np.int64),
|
||||||
|
np.array(static_num_samples, dtype=np.float32),
|
||||||
|
np.array(static_cat_samples, dtype=np.int64),
|
||||||
|
targets_array,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _train_validation_split(train_df: pd.DataFrame, validation_ratio: float = 0.15) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||||
|
employee_ids = train_df[config.EMPLOYEE_ID_COLUMN].dropna().astype(str).unique().tolist()
|
||||||
|
rng = np.random.default_rng(config.RANDOM_STATE)
|
||||||
|
rng.shuffle(employee_ids)
|
||||||
|
validation_count = max(1, int(len(employee_ids) * validation_ratio))
|
||||||
|
validation_ids = set(employee_ids[:validation_count])
|
||||||
|
|
||||||
|
validation_df = train_df[train_df[config.EMPLOYEE_ID_COLUMN].astype(str).isin(validation_ids)].copy()
|
||||||
|
fit_df = train_df[~train_df[config.EMPLOYEE_ID_COLUMN].astype(str).isin(validation_ids)].copy()
|
||||||
|
if fit_df.empty or validation_df.empty:
|
||||||
|
split_index = max(1, int(len(train_df) * (1 - validation_ratio)))
|
||||||
|
fit_df = train_df.iloc[:split_index].copy()
|
||||||
|
validation_df = train_df.iloc[split_index:].copy()
|
||||||
|
return fit_df, validation_df
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_inference_window(
|
||||||
|
df: pd.DataFrame,
|
||||||
|
feature_layout: Dict[str, List[str]],
|
||||||
|
category_maps: Dict[str, Dict[str, int]],
|
||||||
|
default_sequence_num_prefix: np.ndarray,
|
||||||
|
default_sequence_cat_prefix: np.ndarray,
|
||||||
|
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
||||||
|
df = engineer_features(df.copy())
|
||||||
|
for feature in feature_layout['used_features']:
|
||||||
|
if feature not in df.columns:
|
||||||
|
df[feature] = 0
|
||||||
|
|
||||||
|
row = df.iloc[0]
|
||||||
|
|
||||||
|
seq_num_row = row[feature_layout['seq_num_features']].astype(float).to_numpy(dtype=np.float32)
|
||||||
|
static_num_row = row[feature_layout['static_num_features']].astype(float).to_numpy(dtype=np.float32)
|
||||||
|
|
||||||
|
if feature_layout['seq_cat_features']:
|
||||||
|
seq_cat_row = np.array(
|
||||||
|
[category_maps[feature].get(str(row[feature]), 0) for feature in feature_layout['seq_cat_features']],
|
||||||
|
dtype=np.int64,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
seq_cat_row = np.zeros((0,), dtype=np.int64)
|
||||||
|
|
||||||
|
if feature_layout['static_cat_features']:
|
||||||
|
static_cat_row = np.array(
|
||||||
|
[category_maps[feature].get(str(row[feature]), 0) for feature in feature_layout['static_cat_features']],
|
||||||
|
dtype=np.int64,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
static_cat_row = np.zeros((0,), dtype=np.int64)
|
||||||
|
|
||||||
|
sequence_num_window = np.vstack([default_sequence_num_prefix, seq_num_row.reshape(1, -1)]).astype(np.float32)
|
||||||
|
if len(feature_layout['seq_cat_features']) > 0:
|
||||||
|
sequence_cat_window = np.vstack([default_sequence_cat_prefix, seq_cat_row.reshape(1, -1)]).astype(np.int64)
|
||||||
|
else:
|
||||||
|
sequence_cat_window = np.zeros((WINDOW_SIZE, 0), dtype=np.int64)
|
||||||
|
|
||||||
|
return sequence_num_window, sequence_cat_window, static_num_row, static_cat_row
|
||||||
|
|
||||||
|
|
||||||
|
def _evaluate_model(
|
||||||
|
model: nn.Module,
|
||||||
|
loader: DataLoader,
|
||||||
|
device: torch.device,
|
||||||
|
target_transform: str,
|
||||||
|
) -> Tuple[float, Dict[str, float]]:
|
||||||
|
model.eval()
|
||||||
|
predictions = []
|
||||||
|
targets = []
|
||||||
|
with torch.no_grad():
|
||||||
|
for batch_seq_num, batch_seq_cat, batch_static_num, batch_static_cat, batch_target in loader:
|
||||||
|
batch_seq_num = batch_seq_num.to(device)
|
||||||
|
batch_seq_cat = batch_seq_cat.to(device)
|
||||||
|
batch_static_num = batch_static_num.to(device)
|
||||||
|
batch_static_cat = batch_static_cat.to(device)
|
||||||
|
batch_predictions = model(batch_seq_num, batch_seq_cat, batch_static_num, batch_static_cat)
|
||||||
|
predictions.append(batch_predictions.cpu().numpy())
|
||||||
|
targets.append(batch_target.numpy())
|
||||||
|
|
||||||
|
y_pred = np.concatenate(predictions) if predictions else np.array([], dtype=np.float32)
|
||||||
|
y_true = np.concatenate(targets) if targets else np.array([], dtype=np.float32)
|
||||||
|
|
||||||
|
if target_transform == 'log1p':
|
||||||
|
y_pred_eval = np.expm1(y_pred)
|
||||||
|
y_true_eval = np.expm1(y_true)
|
||||||
|
else:
|
||||||
|
y_pred_eval = y_pred
|
||||||
|
y_true_eval = y_true
|
||||||
|
y_pred_eval = np.clip(y_pred_eval, a_min=0, a_max=None)
|
||||||
|
mse = mean_squared_error(y_true_eval, y_pred_eval)
|
||||||
|
metrics = {
|
||||||
|
'r2': float(r2_score(y_true_eval, y_pred_eval)),
|
||||||
|
'mse': float(mse),
|
||||||
|
'rmse': float(np.sqrt(mse)),
|
||||||
|
'mae': float(mean_absolute_error(y_true_eval, y_pred_eval)),
|
||||||
|
}
|
||||||
|
return metrics['rmse'], metrics
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_sample_weights(targets: torch.Tensor, target_transform: str) -> torch.Tensor:
|
||||||
|
if target_transform == 'log1p':
|
||||||
|
base_targets = torch.expm1(targets)
|
||||||
|
else:
|
||||||
|
base_targets = targets
|
||||||
|
normalized = torch.clamp(base_targets / 12.0, min=0.0, max=2.0)
|
||||||
|
return 1.0 + normalized * 0.8
|
||||||
|
|
||||||
|
|
||||||
|
def train_lstm_mlp(
|
||||||
|
train_df: pd.DataFrame,
|
||||||
|
test_df: pd.DataFrame,
|
||||||
|
model_path: str,
|
||||||
|
target_transform: str = 'log1p',
|
||||||
|
epochs: int = DEFAULT_EPOCHS,
|
||||||
|
batch_size: int = DEFAULT_BATCH_SIZE,
|
||||||
|
) -> Optional[Dict]:
|
||||||
|
if torch is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
fit_df, validation_df = _train_validation_split(train_df)
|
||||||
|
feature_layout = _build_feature_layout(fit_df)
|
||||||
|
category_maps = _fit_category_maps(fit_df, feature_layout['all_cat_features'])
|
||||||
|
|
||||||
|
train_seq_num, train_seq_cat, train_static_num, train_static_cat, y_train = _build_sequence_arrays(
|
||||||
|
fit_df, feature_layout, category_maps, target_transform
|
||||||
|
)
|
||||||
|
val_seq_num, val_seq_cat, val_static_num, val_static_cat, y_val = _build_sequence_arrays(
|
||||||
|
validation_df, feature_layout, category_maps, target_transform
|
||||||
|
)
|
||||||
|
test_seq_num, test_seq_cat, test_static_num, test_static_cat, y_test_aligned = _build_sequence_arrays(
|
||||||
|
test_df, feature_layout, category_maps, target_transform
|
||||||
|
)
|
||||||
|
|
||||||
|
seq_mean, seq_std = _safe_standardize(train_seq_num.reshape(-1, train_seq_num.shape[-1]))
|
||||||
|
static_mean, static_std = _safe_standardize(train_static_num)
|
||||||
|
|
||||||
|
train_seq_num = ((train_seq_num - seq_mean) / seq_std).astype(np.float32)
|
||||||
|
val_seq_num = ((val_seq_num - seq_mean) / seq_std).astype(np.float32)
|
||||||
|
test_seq_num = ((test_seq_num - seq_mean) / seq_std).astype(np.float32)
|
||||||
|
|
||||||
|
train_static_num = ((train_static_num - static_mean) / static_std).astype(np.float32)
|
||||||
|
val_static_num = ((val_static_num - static_mean) / static_std).astype(np.float32)
|
||||||
|
test_static_num = ((test_static_num - static_mean) / static_std).astype(np.float32)
|
||||||
|
|
||||||
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||||
|
if device.type == 'cuda':
|
||||||
|
print(f'[lstm_mlp] Training device: CUDA ({torch.cuda.get_device_name(device)})')
|
||||||
|
else:
|
||||||
|
print('[lstm_mlp] Training device: CPU')
|
||||||
|
|
||||||
|
model = TemporalFusionRegressor(
|
||||||
|
seq_num_dim=train_seq_num.shape[-1],
|
||||||
|
static_num_dim=train_static_num.shape[-1],
|
||||||
|
seq_cat_cardinalities=[len(category_maps[feature]) + 1 for feature in feature_layout['seq_cat_features']],
|
||||||
|
static_cat_cardinalities=[len(category_maps[feature]) + 1 for feature in feature_layout['static_cat_features']],
|
||||||
|
).to(device)
|
||||||
|
|
||||||
|
optimizer = torch.optim.AdamW(model.parameters(), lr=9e-4, weight_decay=3e-4)
|
||||||
|
criterion = nn.SmoothL1Loss(beta=0.28, reduction='none')
|
||||||
|
train_loader = DataLoader(
|
||||||
|
SequenceStaticDataset(train_seq_num, train_seq_cat, train_static_num, train_static_cat, y_train),
|
||||||
|
batch_size=batch_size,
|
||||||
|
shuffle=True,
|
||||||
|
drop_last=False,
|
||||||
|
)
|
||||||
|
val_loader = DataLoader(
|
||||||
|
SequenceStaticDataset(val_seq_num, val_seq_cat, val_static_num, val_static_cat, y_val),
|
||||||
|
batch_size=batch_size,
|
||||||
|
shuffle=False,
|
||||||
|
)
|
||||||
|
total_steps = max(20, epochs * max(1, len(train_loader)))
|
||||||
|
scheduler = torch.optim.lr_scheduler.OneCycleLR(
|
||||||
|
optimizer,
|
||||||
|
max_lr=0.0014,
|
||||||
|
total_steps=total_steps,
|
||||||
|
pct_start=0.12,
|
||||||
|
div_factor=12.0,
|
||||||
|
final_div_factor=40.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
best_state = None
|
||||||
|
best_metrics = None
|
||||||
|
best_val_rmse = float('inf')
|
||||||
|
stale_epochs = 0
|
||||||
|
|
||||||
|
for epoch in range(epochs):
|
||||||
|
model.train()
|
||||||
|
running_loss = 0.0
|
||||||
|
for batch_seq_num, batch_seq_cat, batch_static_num, batch_static_cat, batch_target in train_loader:
|
||||||
|
batch_seq_num = batch_seq_num.to(device)
|
||||||
|
batch_seq_cat = batch_seq_cat.to(device)
|
||||||
|
batch_static_num = batch_static_num.to(device)
|
||||||
|
batch_static_cat = batch_static_cat.to(device)
|
||||||
|
batch_target = batch_target.to(device)
|
||||||
|
|
||||||
|
optimizer.zero_grad(set_to_none=True)
|
||||||
|
predictions = model(batch_seq_num, batch_seq_cat, batch_static_num, batch_static_cat)
|
||||||
|
sample_weights = _compute_sample_weights(batch_target, target_transform)
|
||||||
|
loss = criterion(predictions, batch_target)
|
||||||
|
loss = (loss * sample_weights).mean()
|
||||||
|
loss.backward()
|
||||||
|
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
||||||
|
optimizer.step()
|
||||||
|
scheduler.step()
|
||||||
|
running_loss += float(loss.item()) * len(batch_target)
|
||||||
|
|
||||||
|
train_loss = running_loss / max(1, len(train_loader.dataset))
|
||||||
|
val_rmse, val_metrics = _evaluate_model(model, val_loader, device, target_transform)
|
||||||
|
|
||||||
|
improved = val_rmse + 1e-4 < best_val_rmse
|
||||||
|
if improved:
|
||||||
|
best_val_rmse = val_rmse
|
||||||
|
best_metrics = val_metrics
|
||||||
|
best_state = copy.deepcopy(model.state_dict())
|
||||||
|
stale_epochs = 0
|
||||||
|
else:
|
||||||
|
stale_epochs += 1
|
||||||
|
|
||||||
|
if epoch == 0 or (epoch + 1) % 5 == 0 or improved:
|
||||||
|
print(
|
||||||
|
f'[lstm_mlp] epoch={epoch + 1:02d} train_loss={train_loss:.4f} '
|
||||||
|
f'val_r2={val_metrics["r2"]:.4f} val_rmse={val_metrics["rmse"]:.4f}'
|
||||||
|
)
|
||||||
|
|
||||||
|
if stale_epochs >= EARLY_STOPPING_PATIENCE:
|
||||||
|
print(f'[lstm_mlp] Early stopping at epoch {epoch + 1}')
|
||||||
|
break
|
||||||
|
|
||||||
|
if best_state is None:
|
||||||
|
best_state = copy.deepcopy(model.state_dict())
|
||||||
|
model.load_state_dict(best_state)
|
||||||
|
|
||||||
|
model.eval()
|
||||||
|
with torch.no_grad():
|
||||||
|
predictions = model(
|
||||||
|
torch.tensor(test_seq_num, dtype=torch.float32).to(device),
|
||||||
|
torch.tensor(test_seq_cat, dtype=torch.long).to(device),
|
||||||
|
torch.tensor(test_static_num, dtype=torch.float32).to(device),
|
||||||
|
torch.tensor(test_static_cat, dtype=torch.long).to(device),
|
||||||
|
).cpu().numpy()
|
||||||
|
|
||||||
|
if target_transform == 'log1p':
|
||||||
|
y_pred = np.expm1(predictions)
|
||||||
|
y_true = np.expm1(y_test_aligned)
|
||||||
|
else:
|
||||||
|
y_pred = predictions
|
||||||
|
y_true = y_test_aligned
|
||||||
|
y_pred = np.clip(y_pred, a_min=0, a_max=None)
|
||||||
|
mse = mean_squared_error(y_true, y_pred)
|
||||||
|
|
||||||
|
default_sequence_num_prefix = train_seq_num[:, :-1, :].mean(axis=0).astype(np.float32)
|
||||||
|
if train_seq_cat.shape[-1] > 0:
|
||||||
|
default_sequence_cat_prefix = np.rint(train_seq_cat[:, :-1, :].mean(axis=0)).astype(np.int64)
|
||||||
|
else:
|
||||||
|
default_sequence_cat_prefix = np.zeros((WINDOW_SIZE - 1, 0), dtype=np.int64)
|
||||||
|
|
||||||
|
bundle = {
|
||||||
|
'state_dict': model.state_dict(),
|
||||||
|
'architecture': 'temporal_fusion_transformer',
|
||||||
|
'window_size': WINDOW_SIZE,
|
||||||
|
'target_transform': target_transform,
|
||||||
|
'feature_layout': feature_layout,
|
||||||
|
'category_maps': category_maps,
|
||||||
|
'seq_mean': seq_mean,
|
||||||
|
'seq_std': seq_std,
|
||||||
|
'static_mean': static_mean,
|
||||||
|
'static_std': static_std,
|
||||||
|
'default_sequence_num_prefix': default_sequence_num_prefix,
|
||||||
|
'default_sequence_cat_prefix': default_sequence_cat_prefix,
|
||||||
|
'seq_num_dim': train_seq_num.shape[-1],
|
||||||
|
'static_num_dim': train_static_num.shape[-1],
|
||||||
|
'seq_cat_cardinalities': [len(category_maps[feature]) + 1 for feature in feature_layout['seq_cat_features']],
|
||||||
|
'static_cat_cardinalities': [len(category_maps[feature]) + 1 for feature in feature_layout['static_cat_features']],
|
||||||
|
'best_validation_metrics': best_metrics,
|
||||||
|
}
|
||||||
|
torch.save(bundle, model_path)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'metrics': {
|
||||||
|
'r2': round(float(r2_score(y_true, y_pred)), 4),
|
||||||
|
'mse': round(float(mse), 4),
|
||||||
|
'rmse': round(float(np.sqrt(mse)), 4),
|
||||||
|
'mae': round(float(mean_absolute_error(y_true, y_pred)), 4),
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'sequence_window_size': WINDOW_SIZE,
|
||||||
|
'sequence_feature_names': SEQUENCE_FEATURES,
|
||||||
|
'static_feature_names': STATIC_FEATURES,
|
||||||
|
'deep_learning_architecture': 'temporal_fusion_transformer',
|
||||||
|
'deep_validation_r2': round(float(best_metrics['r2']), 4) if best_metrics else None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_lstm_mlp_bundle(model_path: str) -> Optional[Dict]:
|
||||||
|
if torch is None or not os.path.exists(model_path):
|
||||||
|
return None
|
||||||
|
bundle = torch.load(model_path, map_location='cpu', weights_only=False)
|
||||||
|
model = LSTMMLPRegressor(
|
||||||
|
seq_num_dim=bundle['seq_num_dim'],
|
||||||
|
static_num_dim=bundle['static_num_dim'],
|
||||||
|
seq_cat_cardinalities=bundle['seq_cat_cardinalities'],
|
||||||
|
static_cat_cardinalities=bundle['static_cat_cardinalities'],
|
||||||
|
)
|
||||||
|
model.load_state_dict(bundle['state_dict'])
|
||||||
|
model.eval()
|
||||||
|
bundle['model'] = model
|
||||||
|
return bundle
|
||||||
|
|
||||||
|
|
||||||
|
def predict_lstm_mlp(bundle: Dict, current_df: pd.DataFrame) -> float:
|
||||||
|
sequence_num_window, sequence_cat_window, static_num_row, static_cat_row = _prepare_inference_window(
|
||||||
|
current_df,
|
||||||
|
bundle['feature_layout'],
|
||||||
|
bundle['category_maps'],
|
||||||
|
bundle['default_sequence_num_prefix'],
|
||||||
|
bundle['default_sequence_cat_prefix'],
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence_num_window = ((sequence_num_window - bundle['seq_mean']) / bundle['seq_std']).astype(np.float32)
|
||||||
|
static_num_row = ((static_num_row - bundle['static_mean']) / bundle['static_std']).astype(np.float32)
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
prediction = bundle['model'](
|
||||||
|
torch.tensor(sequence_num_window, dtype=torch.float32).unsqueeze(0),
|
||||||
|
torch.tensor(sequence_cat_window, dtype=torch.long).unsqueeze(0),
|
||||||
|
torch.tensor(static_num_row, dtype=torch.float32).unsqueeze(0),
|
||||||
|
torch.tensor(static_cat_row, dtype=torch.long).unsqueeze(0),
|
||||||
|
).cpu().numpy()[0]
|
||||||
|
|
||||||
|
if bundle.get('target_transform') == 'log1p':
|
||||||
|
prediction = np.expm1(prediction)
|
||||||
|
return float(max(0.5, prediction))
|
||||||
@@ -161,68 +161,109 @@ def sample_event(rng, employee):
|
|||||||
weekday = int(rng.integers(1, 8))
|
weekday = int(rng.integers(1, 8))
|
||||||
near_holiday = int(rng.random() < (0.3 if month in [1, 2, 4, 5, 9, 10] else 0.16))
|
near_holiday = int(rng.random() < (0.3 if month in [1, 2, 4, 5, 9, 10] else 0.16))
|
||||||
leave_type_items = ['病假', '事假', '年假', '调休', '婚假', '丧假', '产检育儿假', '工伤假', '其他']
|
leave_type_items = ['病假', '事假', '年假', '调休', '婚假', '丧假', '产检育儿假', '工伤假', '其他']
|
||||||
leave_type = weighted_choice(rng, leave_type_items, [0.3, 0.22, 0.12, 0.14, 0.03, 0.02, 0.06, 0.02, 0.09])
|
leave_probs = [0.26, 0.22, 0.11, 0.14, 0.03, 0.02, 0.07, 0.03, 0.12]
|
||||||
if employee['子女数量'] > 0 and rng.random() < 0.14:
|
if employee['是否慢性病史'] == 1 or employee['年度体检异常标记'] == 1:
|
||||||
reason_category = '子女照护'
|
leave_probs = [0.34, 0.18, 0.08, 0.1, 0.02, 0.02, 0.08, 0.04, 0.14]
|
||||||
|
elif employee['子女数量'] >= 2:
|
||||||
|
leave_probs = [0.22, 0.24, 0.1, 0.12, 0.03, 0.02, 0.12, 0.02, 0.13]
|
||||||
|
leave_type = weighted_choice(rng, leave_type_items, leave_probs)
|
||||||
|
|
||||||
|
if leave_type in ['病假', '工伤假']:
|
||||||
|
reason_category = weighted_choice(rng, ['身体不适', '就医复查', '职业疲劳'], [0.52, 0.3, 0.18])
|
||||||
|
elif leave_type == '产检育儿假':
|
||||||
|
reason_category = weighted_choice(rng, ['子女照护', '家庭事务', '身体不适'], [0.6, 0.25, 0.15])
|
||||||
|
elif leave_type in ['婚假', '丧假']:
|
||||||
|
reason_category = weighted_choice(rng, ['家庭事务', '突发事件'], [0.72, 0.28])
|
||||||
|
elif leave_type in ['年假', '调休']:
|
||||||
|
reason_category = weighted_choice(rng, ['职业疲劳', '家庭事务', '交通受阻'], [0.52, 0.28, 0.2])
|
||||||
else:
|
else:
|
||||||
reason_category = weighted_choice(
|
reason_category = weighted_choice(
|
||||||
rng,
|
rng,
|
||||||
['身体不适', '家庭事务', '交通受阻', '突发事件', '职业疲劳', '就医复查'],
|
['身体不适', '家庭事务', '子女照护', '交通受阻', '突发事件', '职业疲劳'],
|
||||||
[0.28, 0.19, 0.09, 0.11, 0.2, 0.13],
|
[0.2, 0.22, 0.14, 0.12, 0.12, 0.2],
|
||||||
|
)
|
||||||
|
|
||||||
|
medical_certificate = int(
|
||||||
|
leave_type in ['病假', '工伤假']
|
||||||
|
or reason_category in ['身体不适', '就医复查']
|
||||||
|
or (employee['是否慢性病史'] == 1 and leave_type == '其他')
|
||||||
|
)
|
||||||
|
urgent_leave = int(
|
||||||
|
leave_type in ['病假', '工伤假']
|
||||||
|
or reason_category in ['突发事件', '身体不适']
|
||||||
|
or (near_holiday == 0 and leave_type == '事假' and rng.random() < 0.35)
|
||||||
|
)
|
||||||
|
continuous_absence = int(
|
||||||
|
leave_type in ['病假', '工伤假', '产检育儿假']
|
||||||
|
and (employee['近90天缺勤次数'] >= 2 or employee['近180天请假总时长'] >= 28)
|
||||||
|
)
|
||||||
|
previous_overtime = int(
|
||||||
|
employee['月均加班时长'] >= 30
|
||||||
|
or (employee['月均加班时长'] >= 24 and weekday in [1, 2, 5])
|
||||||
|
or (employee['是否夜班岗位'] == 1 and rng.random() < 0.65)
|
||||||
)
|
)
|
||||||
medical_certificate = int(leave_type in ['病假', '工伤假'] or reason_category in ['身体不适', '就医复查'])
|
|
||||||
urgent_leave = int(rng.random() < (0.45 if leave_type in ['病假', '事假', '工伤假'] else 0.18))
|
|
||||||
continuous_absence = int(rng.random() < (0.2 if leave_type in ['病假', '产检育儿假', '工伤假'] else 0.08))
|
|
||||||
previous_overtime = int(rng.random() < min(0.85, employee['月均加班时长'] / 65))
|
|
||||||
season = season_from_month(month)
|
season = season_from_month(month)
|
||||||
channel = weighted_choice(rng, ['系统申请', '主管代提', '临时电话报备'], [0.68, 0.18, 0.14])
|
channel = weighted_choice(rng, ['系统申请', '主管代提', '临时电话报备'], [0.68, 0.18, 0.14])
|
||||||
|
|
||||||
base = 0.95
|
pressure_score = (
|
||||||
base += min(employee['月均加班时长'] / 28, 1.8)
|
employee['月均加班时长'] * 0.032
|
||||||
base += min(employee['通勤时长分钟'] / 65, 1.2)
|
+ employee['通勤时长分钟'] * 0.018
|
||||||
base += employee['是否夜班岗位'] * 0.9
|
+ employee['是否夜班岗位'] * 0.75
|
||||||
base += employee['是否慢性病史'] * 1.25
|
+ employee['是否跨城通勤'] * 0.32
|
||||||
base += employee['年度体检异常标记'] * 0.6
|
+ previous_overtime * 0.35
|
||||||
base += 0.35 * employee['子女数量']
|
)
|
||||||
base += 0.5 if employee['心理压力等级'] == '高' else (0.2 if employee['心理压力等级'] == '中' else -0.1)
|
health_score = (
|
||||||
base += 0.4 if employee['是否跨城通勤'] else 0
|
employee['是否慢性病史'] * 1.2
|
||||||
base += 0.35 if previous_overtime else 0
|
+ employee['年度体检异常标记'] * 0.55
|
||||||
base += 0.35 if near_holiday else 0
|
+ (employee['BMI'] >= 28) * 0.3
|
||||||
base += 0.3 if continuous_absence else 0
|
+ (employee['近30天睡眠时长均值'] < 6.4) * 0.45
|
||||||
base += 0.3 if employee['近90天缺勤次数'] >= 3 else 0
|
)
|
||||||
base -= 0.35 if employee['绩效等级'] == 'A' else (0.15 if employee['绩效等级'] == 'B' else 0)
|
family_score = employee['子女数量'] * 0.18 + employee['是否独生子女家庭负担'] * 0.28
|
||||||
base -= min(employee['司龄年数'] / 40, 0.5)
|
resilience_score = (
|
||||||
base -= min(employee['每周运动频次'] * 0.08, 0.3)
|
(0.55 if employee['绩效等级'] == 'A' else 0.25 if employee['绩效等级'] == 'B' else 0.0)
|
||||||
base -= 0.2 if employee['近30天睡眠时长均值'] >= 7.5 else 0
|
+ min(employee['司龄年数'] / 26, 0.65)
|
||||||
|
+ min(employee['每周运动频次'] * 0.06, 0.25)
|
||||||
|
)
|
||||||
|
|
||||||
|
base = 0.35
|
||||||
|
base += pressure_score
|
||||||
|
base += health_score
|
||||||
|
base += family_score
|
||||||
|
base += 0.4 if employee['心理压力等级'] == '高' else (0.18 if employee['心理压力等级'] == '中' else -0.05)
|
||||||
|
base += 0.18 if near_holiday else 0.0
|
||||||
|
base += 0.35 if continuous_absence else 0.0
|
||||||
|
base += 0.28 if employee['近90天缺勤次数'] >= 3 else 0.0
|
||||||
|
base += 0.18 if employee['近180天请假总时长'] >= 36 else 0.0
|
||||||
|
base -= resilience_score
|
||||||
|
|
||||||
leave_bonus = {
|
leave_bonus = {
|
||||||
'病假': 2.0,
|
'病假': 2.1,
|
||||||
'事假': 0.8,
|
'事假': 0.8,
|
||||||
'年假': 0.1,
|
'年假': 0.15,
|
||||||
'调休': 0.1,
|
'调休': 0.1,
|
||||||
'婚假': 3.0,
|
'婚假': 3.1,
|
||||||
'丧假': 2.8,
|
'丧假': 2.8,
|
||||||
'产检育儿假': 2.4,
|
'产检育儿假': 2.35,
|
||||||
'工伤假': 3.8,
|
'工伤假': 3.9,
|
||||||
'其他': 0.5,
|
'其他': 0.55,
|
||||||
}
|
}
|
||||||
reason_bonus = {
|
reason_bonus = {
|
||||||
'身体不适': 1.0,
|
'身体不适': 1.0,
|
||||||
'家庭事务': 0.5,
|
'家庭事务': 0.55,
|
||||||
'子女照护': 0.8,
|
'子女照护': 0.75,
|
||||||
'交通受阻': 0.2,
|
'交通受阻': 0.2,
|
||||||
'突发事件': 0.6,
|
'突发事件': 0.6,
|
||||||
'职业疲劳': 0.7,
|
'职业疲劳': 0.7,
|
||||||
'就医复查': 1.2,
|
'就医复查': 1.15,
|
||||||
}
|
}
|
||||||
industry_bonus = {
|
industry_bonus = {
|
||||||
'制造业': 0.35,
|
'制造业': 0.42,
|
||||||
'互联网': 0.2,
|
'互联网': 0.22,
|
||||||
'零售连锁': 0.25,
|
'零售连锁': 0.28,
|
||||||
'物流运输': 0.4,
|
'物流运输': 0.5,
|
||||||
'金融服务': 0.1,
|
'金融服务': 0.12,
|
||||||
'医药健康': 0.2,
|
'医药健康': 0.24,
|
||||||
'建筑工程': 0.35,
|
'建筑工程': 0.4,
|
||||||
}
|
}
|
||||||
season_bonus = {1: 0.35, 2: 0.0, 3: 0.15, 4: 0.05}
|
season_bonus = {1: 0.35, 2: 0.0, 3: 0.15, 4: 0.05}
|
||||||
weekday_bonus = {1: 0.05, 2: 0.0, 3: 0.0, 4: 0.05, 5: 0.15, 6: 0.25, 7: 0.3}
|
weekday_bonus = {1: 0.05, 2: 0.0, 3: 0.0, 4: 0.05, 5: 0.15, 6: 0.25, 7: 0.3}
|
||||||
@@ -233,18 +274,21 @@ def sample_event(rng, employee):
|
|||||||
duration += industry_bonus[employee['所属行业']]
|
duration += industry_bonus[employee['所属行业']]
|
||||||
duration += season_bonus[season]
|
duration += season_bonus[season]
|
||||||
duration += weekday_bonus[weekday]
|
duration += weekday_bonus[weekday]
|
||||||
duration += 0.55 if medical_certificate else 0
|
duration += 0.55 if medical_certificate else 0.0
|
||||||
duration += 0.4 if urgent_leave else -0.05
|
duration += 0.28 if urgent_leave else -0.06
|
||||||
duration += rng.normal(0, 0.9)
|
|
||||||
|
|
||||||
if leave_type in ['婚假', '丧假', '工伤假'] and rng.random() < 0.5:
|
if leave_type == '病假' and employee['是否慢性病史'] == 1:
|
||||||
duration += rng.uniform(1.5, 5)
|
duration += 0.85
|
||||||
if leave_type == '病假' and employee['是否慢性病史'] == 1 and rng.random() < 0.35:
|
if leave_type == '工伤假':
|
||||||
duration += rng.uniform(1, 4)
|
duration += 1.0 + employee['是否夜班岗位'] * 0.3
|
||||||
|
if leave_type in ['婚假', '丧假']:
|
||||||
|
duration += 0.7 + 0.18 * near_holiday
|
||||||
|
if leave_type == '产检育儿假':
|
||||||
|
duration += 0.55 + employee['子女数量'] * 0.12
|
||||||
if leave_type in ['年假', '调休']:
|
if leave_type in ['年假', '调休']:
|
||||||
duration *= rng.uniform(0.7, 0.95)
|
duration *= 0.82 if near_holiday == 0 else 0.9
|
||||||
|
|
||||||
duration = round(float(np.clip(duration, 0.5, 24.0)), 1)
|
duration = round(float(np.clip(duration + rng.normal(0, 0.35), 0.5, 18.0)), 1)
|
||||||
|
|
||||||
event = employee.copy()
|
event = employee.copy()
|
||||||
event.update({
|
event.update({
|
||||||
@@ -264,6 +308,28 @@ def sample_event(rng, employee):
|
|||||||
return event
|
return event
|
||||||
|
|
||||||
|
|
||||||
|
def attach_event_timeline(df):
|
||||||
|
df = df.copy()
|
||||||
|
rng = np.random.default_rng(config.RANDOM_STATE)
|
||||||
|
base_date = np.datetime64('2025-01-01')
|
||||||
|
timelines = []
|
||||||
|
|
||||||
|
for employee_id, group in df.groupby('员工编号', sort=False):
|
||||||
|
group = group.copy().reset_index(drop=True)
|
||||||
|
event_count = len(group)
|
||||||
|
offsets = np.sort(rng.integers(0, 365, size=event_count))
|
||||||
|
group['事件日期'] = [
|
||||||
|
str(pd.Timestamp(base_date + np.timedelta64(int(offset), 'D')).date())
|
||||||
|
for offset in offsets
|
||||||
|
]
|
||||||
|
group['事件日期索引'] = offsets.astype(int)
|
||||||
|
group['事件序号'] = np.arange(1, event_count + 1)
|
||||||
|
group['员工历史事件数'] = event_count
|
||||||
|
timelines.append(group)
|
||||||
|
|
||||||
|
return pd.concat(timelines, ignore_index=True)
|
||||||
|
|
||||||
|
|
||||||
def validate_dataset(df):
|
def validate_dataset(df):
|
||||||
required_columns = [
|
required_columns = [
|
||||||
'员工编号',
|
'员工编号',
|
||||||
@@ -273,6 +339,9 @@ def validate_dataset(df):
|
|||||||
'通勤时长分钟',
|
'通勤时长分钟',
|
||||||
'是否慢性病史',
|
'是否慢性病史',
|
||||||
'请假类型',
|
'请假类型',
|
||||||
|
'事件序号',
|
||||||
|
'事件日期索引',
|
||||||
|
'员工历史事件数',
|
||||||
'缺勤时长(小时)',
|
'缺勤时长(小时)',
|
||||||
]
|
]
|
||||||
for column in required_columns:
|
for column in required_columns:
|
||||||
@@ -309,7 +378,7 @@ def generate_dataset(output_path=None, sample_count=12000, random_state=None):
|
|||||||
for idx in employee_idx:
|
for idx in employee_idx:
|
||||||
events.append(sample_event(rng, employees[int(idx)]))
|
events.append(sample_event(rng, employees[int(idx)]))
|
||||||
|
|
||||||
df = pd.DataFrame(events)
|
df = attach_event_timeline(pd.DataFrame(events))
|
||||||
validate_dataset(df)
|
validate_dataset(df)
|
||||||
|
|
||||||
if output_path:
|
if output_path:
|
||||||
|
|||||||
330
backend/core/generate_evaluation_plots.py
Normal file
330
backend/core/generate_evaluation_plots.py
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import joblib
|
||||||
|
import matplotlib
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from sklearn.metrics import confusion_matrix
|
||||||
|
from sklearn.model_selection import train_test_split
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
import config
|
||||||
|
from core.deep_learning_model import (
|
||||||
|
_build_sequence_arrays,
|
||||||
|
load_lstm_mlp_bundle,
|
||||||
|
)
|
||||||
|
from core.model_features import (
|
||||||
|
NUMERICAL_OUTLIER_COLUMNS,
|
||||||
|
TARGET_COLUMN,
|
||||||
|
apply_outlier_bounds,
|
||||||
|
engineer_features,
|
||||||
|
fit_outlier_bounds,
|
||||||
|
make_target_bins,
|
||||||
|
normalize_columns,
|
||||||
|
)
|
||||||
|
from core.preprocessing import get_clean_data
|
||||||
|
|
||||||
|
|
||||||
|
matplotlib.rcParams['font.sans-serif'] = [
|
||||||
|
'Microsoft YaHei',
|
||||||
|
'SimHei',
|
||||||
|
'Noto Sans CJK SC',
|
||||||
|
'Arial Unicode MS',
|
||||||
|
'DejaVu Sans',
|
||||||
|
]
|
||||||
|
matplotlib.rcParams['axes.unicode_minus'] = False
|
||||||
|
|
||||||
|
BASE_DIR = Path(config.BASE_DIR)
|
||||||
|
MODELS_DIR = Path(config.MODELS_DIR)
|
||||||
|
OUTPUT_DIR = BASE_DIR / 'outputs' / 'eval_figures'
|
||||||
|
PREDICTION_CSV = OUTPUT_DIR / 'lstm_predictions.csv'
|
||||||
|
SUMMARY_JSON = OUTPUT_DIR / 'evaluation_summary.json'
|
||||||
|
|
||||||
|
MODEL_DISPLAY_NAMES = {
|
||||||
|
'lstm_mlp': '时序注意力融合网络',
|
||||||
|
'xgboost': 'XGBoost',
|
||||||
|
'gradient_boosting': 'GBDT',
|
||||||
|
'random_forest': '随机森林',
|
||||||
|
'extra_trees': '极端随机树',
|
||||||
|
'lightgbm': 'LightGBM',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_output_dir():
|
||||||
|
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def load_metrics():
|
||||||
|
metrics_path = MODELS_DIR / 'model_metrics.pkl'
|
||||||
|
if not metrics_path.exists():
|
||||||
|
raise FileNotFoundError(f'未找到模型评估文件: {metrics_path}')
|
||||||
|
metrics = joblib.load(metrics_path)
|
||||||
|
return dict(sorted(metrics.items(), key=lambda item: item[1].get('r2', -999), reverse=True))
|
||||||
|
|
||||||
|
|
||||||
|
def get_test_split():
|
||||||
|
raw_df = normalize_columns(get_clean_data())
|
||||||
|
target_bins = make_target_bins(raw_df[TARGET_COLUMN].values)
|
||||||
|
raw_train_df, raw_test_df = train_test_split(
|
||||||
|
raw_df,
|
||||||
|
test_size=config.TEST_SIZE,
|
||||||
|
random_state=config.RANDOM_STATE,
|
||||||
|
stratify=target_bins,
|
||||||
|
)
|
||||||
|
return raw_train_df.reset_index(drop=True), raw_test_df.reset_index(drop=True)
|
||||||
|
|
||||||
|
|
||||||
|
def classify_risk(values):
|
||||||
|
values = np.asarray(values, dtype=float)
|
||||||
|
return np.where(values < 4, '低风险', np.where(values <= 8, '中风险', '高风险'))
|
||||||
|
|
||||||
|
|
||||||
|
def load_lstm_predictions():
|
||||||
|
model_path = MODELS_DIR / 'lstm_mlp_model.pt'
|
||||||
|
if not model_path.exists():
|
||||||
|
raise FileNotFoundError(f'未找到深度学习模型文件: {model_path}')
|
||||||
|
|
||||||
|
bundle = load_lstm_mlp_bundle(str(model_path))
|
||||||
|
if bundle is None:
|
||||||
|
raise RuntimeError('无法加载深度学习模型,请确认 torch 环境和模型文件正常。')
|
||||||
|
|
||||||
|
raw_train_df, raw_test_df = get_test_split()
|
||||||
|
outlier_bounds = fit_outlier_bounds(raw_train_df, NUMERICAL_OUTLIER_COLUMNS)
|
||||||
|
fit_df = apply_outlier_bounds(raw_train_df, outlier_bounds)
|
||||||
|
test_df = apply_outlier_bounds(raw_test_df, outlier_bounds)
|
||||||
|
|
||||||
|
feature_layout = bundle['feature_layout']
|
||||||
|
category_maps = bundle['category_maps']
|
||||||
|
target_transform = bundle['target_transform']
|
||||||
|
|
||||||
|
_, _, _, _, _ = _build_sequence_arrays(
|
||||||
|
fit_df,
|
||||||
|
feature_layout,
|
||||||
|
category_maps,
|
||||||
|
target_transform,
|
||||||
|
)
|
||||||
|
test_seq_num, test_seq_cat, test_static_num, test_static_cat, y_test = _build_sequence_arrays(
|
||||||
|
test_df,
|
||||||
|
feature_layout,
|
||||||
|
category_maps,
|
||||||
|
target_transform,
|
||||||
|
)
|
||||||
|
|
||||||
|
test_seq_num = ((test_seq_num - bundle['seq_mean']) / bundle['seq_std']).astype(np.float32)
|
||||||
|
test_static_num = ((test_static_num - bundle['static_mean']) / bundle['static_std']).astype(np.float32)
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
model = bundle['model']
|
||||||
|
model.eval()
|
||||||
|
with torch.no_grad():
|
||||||
|
predictions = model(
|
||||||
|
torch.tensor(test_seq_num, dtype=torch.float32),
|
||||||
|
torch.tensor(test_seq_cat, dtype=torch.long),
|
||||||
|
torch.tensor(test_static_num, dtype=torch.float32),
|
||||||
|
torch.tensor(test_static_cat, dtype=torch.long),
|
||||||
|
).cpu().numpy()
|
||||||
|
|
||||||
|
if target_transform == 'log1p':
|
||||||
|
y_true = np.expm1(y_test)
|
||||||
|
y_pred = np.expm1(predictions)
|
||||||
|
else:
|
||||||
|
y_true = y_test
|
||||||
|
y_pred = predictions
|
||||||
|
|
||||||
|
y_true = np.asarray(y_true, dtype=float)
|
||||||
|
y_pred = np.clip(np.asarray(y_pred, dtype=float), a_min=0.0, a_max=None)
|
||||||
|
residuals = y_pred - y_true
|
||||||
|
|
||||||
|
prediction_df = pd.DataFrame({
|
||||||
|
'真实值': np.round(y_true, 4),
|
||||||
|
'预测值': np.round(y_pred, 4),
|
||||||
|
'残差': np.round(residuals, 4),
|
||||||
|
'真实风险等级': classify_risk(y_true),
|
||||||
|
'预测风险等级': classify_risk(y_pred),
|
||||||
|
})
|
||||||
|
prediction_df.to_csv(PREDICTION_CSV, index=False, encoding='utf-8-sig')
|
||||||
|
return prediction_df
|
||||||
|
|
||||||
|
|
||||||
|
def plot_model_comparison(metrics):
|
||||||
|
model_names = [MODEL_DISPLAY_NAMES.get(name, name) for name in metrics.keys()]
|
||||||
|
r2_values = [metrics[name]['r2'] for name in metrics]
|
||||||
|
rmse_values = [metrics[name]['rmse'] for name in metrics]
|
||||||
|
mae_values = [metrics[name]['mae'] for name in metrics]
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
|
||||||
|
bar_colors = ['#0f766e' if name == 'lstm_mlp' else '#94a3b8' for name in metrics.keys()]
|
||||||
|
|
||||||
|
axes[0].bar(model_names, r2_values, color=bar_colors)
|
||||||
|
axes[0].set_title('模型R2对比')
|
||||||
|
axes[0].set_ylabel('R2')
|
||||||
|
axes[0].tick_params(axis='x', rotation=20)
|
||||||
|
|
||||||
|
axes[1].bar(model_names, rmse_values, color=bar_colors)
|
||||||
|
axes[1].set_title('模型RMSE对比')
|
||||||
|
axes[1].set_ylabel('RMSE')
|
||||||
|
axes[1].tick_params(axis='x', rotation=20)
|
||||||
|
|
||||||
|
axes[2].bar(model_names, mae_values, color=bar_colors)
|
||||||
|
axes[2].set_title('模型MAE对比')
|
||||||
|
axes[2].set_ylabel('MAE')
|
||||||
|
axes[2].tick_params(axis='x', rotation=20)
|
||||||
|
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(OUTPUT_DIR / '01_模型性能对比.png', dpi=220, bbox_inches='tight')
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_actual_vs_pred(prediction_df):
|
||||||
|
y_true = prediction_df['真实值'].to_numpy()
|
||||||
|
y_pred = prediction_df['预测值'].to_numpy()
|
||||||
|
max_value = max(float(y_true.max()), float(y_pred.max()))
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(7, 7))
|
||||||
|
ax.scatter(y_true, y_pred, s=18, alpha=0.55, color='#0f766e', edgecolors='none')
|
||||||
|
ax.plot([0, max_value], [0, max_value], color='#dc2626', linestyle='--', linewidth=1.5)
|
||||||
|
ax.set_title('LSTM模型真实值与预测值对比')
|
||||||
|
ax.set_xlabel('真实缺勤时长(小时)')
|
||||||
|
ax.set_ylabel('预测缺勤时长(小时)')
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(OUTPUT_DIR / '02_LSTM真实值_vs_预测值.png', dpi=220, bbox_inches='tight')
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_residuals(prediction_df):
|
||||||
|
y_pred = prediction_df['预测值'].to_numpy()
|
||||||
|
residuals = prediction_df['残差'].to_numpy()
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
|
||||||
|
|
||||||
|
axes[0].hist(residuals, bins=30, color='#2563eb', alpha=0.85, edgecolor='white')
|
||||||
|
axes[0].axvline(0, color='#dc2626', linestyle='--', linewidth=1.2)
|
||||||
|
axes[0].set_title('LSTM残差分布')
|
||||||
|
axes[0].set_xlabel('残差(预测值 - 真实值)')
|
||||||
|
axes[0].set_ylabel('样本数')
|
||||||
|
|
||||||
|
axes[1].scatter(y_pred, residuals, s=18, alpha=0.55, color='#7c3aed', edgecolors='none')
|
||||||
|
axes[1].axhline(0, color='#dc2626', linestyle='--', linewidth=1.2)
|
||||||
|
axes[1].set_title('LSTM残差散点图')
|
||||||
|
axes[1].set_xlabel('预测缺勤时长(小时)')
|
||||||
|
axes[1].set_ylabel('残差')
|
||||||
|
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(OUTPUT_DIR / '03_LSTM残差分析.png', dpi=220, bbox_inches='tight')
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_confusion_matrix(prediction_df):
|
||||||
|
labels = ['低风险', '中风险', '高风险']
|
||||||
|
cm = confusion_matrix(
|
||||||
|
prediction_df['真实风险等级'],
|
||||||
|
prediction_df['预测风险等级'],
|
||||||
|
labels=labels,
|
||||||
|
)
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(6, 5))
|
||||||
|
image = ax.imshow(cm, cmap='GnBu')
|
||||||
|
ax.set_title('LSTM风险等级混淆矩阵')
|
||||||
|
ax.set_xlabel('预测类别')
|
||||||
|
ax.set_ylabel('真实类别')
|
||||||
|
ax.set_xticks(range(len(labels)))
|
||||||
|
ax.set_xticklabels(labels)
|
||||||
|
ax.set_yticks(range(len(labels)))
|
||||||
|
ax.set_yticklabels(labels)
|
||||||
|
|
||||||
|
for row in range(cm.shape[0]):
|
||||||
|
for col in range(cm.shape[1]):
|
||||||
|
ax.text(col, row, int(cm[row, col]), ha='center', va='center', color='#111827')
|
||||||
|
|
||||||
|
fig.colorbar(image, ax=ax, fraction=0.046, pad=0.04)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(OUTPUT_DIR / '04_LSTM风险等级混淆矩阵.png', dpi=220, bbox_inches='tight')
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_feature_importance():
|
||||||
|
candidate_files = [
|
||||||
|
('xgboost', MODELS_DIR / 'xgboost_model.pkl'),
|
||||||
|
('random_forest', MODELS_DIR / 'random_forest_model.pkl'),
|
||||||
|
('extra_trees', MODELS_DIR / 'extra_trees_model.pkl'),
|
||||||
|
]
|
||||||
|
selected_features_path = MODELS_DIR / 'selected_features.pkl'
|
||||||
|
feature_names_path = MODELS_DIR / 'feature_names.pkl'
|
||||||
|
selected_features = joblib.load(selected_features_path) if selected_features_path.exists() else None
|
||||||
|
feature_names = joblib.load(feature_names_path) if feature_names_path.exists() else None
|
||||||
|
|
||||||
|
for model_name, model_path in candidate_files:
|
||||||
|
if not model_path.exists():
|
||||||
|
continue
|
||||||
|
model = joblib.load(model_path)
|
||||||
|
if not hasattr(model, 'feature_importances_'):
|
||||||
|
continue
|
||||||
|
importances = model.feature_importances_
|
||||||
|
names = selected_features or feature_names or [f'feature_{idx}' for idx in range(len(importances))]
|
||||||
|
if len(names) != len(importances):
|
||||||
|
names = [f'feature_{idx}' for idx in range(len(importances))]
|
||||||
|
|
||||||
|
top_items = sorted(zip(names, importances), key=lambda item: item[1], reverse=True)[:15]
|
||||||
|
top_items.reverse()
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(8, 6))
|
||||||
|
ax.barh(
|
||||||
|
[config.FEATURE_NAME_CN.get(name, name) for name, _ in top_items],
|
||||||
|
[float(value) for _, value in top_items],
|
||||||
|
color='#0f766e',
|
||||||
|
)
|
||||||
|
ax.set_title(f'{MODEL_DISPLAY_NAMES.get(model_name, model_name)}特征重要性 Top15')
|
||||||
|
ax.set_xlabel('重要性')
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(OUTPUT_DIR / '05_特征重要性_Top15.png', dpi=220, bbox_inches='tight')
|
||||||
|
plt.close(fig)
|
||||||
|
return model_name
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def save_summary(metrics, prediction_df, feature_model_name):
|
||||||
|
residuals = prediction_df['残差'].to_numpy()
|
||||||
|
summary = {
|
||||||
|
'best_model': next(iter(metrics.keys())),
|
||||||
|
'metrics': metrics,
|
||||||
|
'lstm_prediction_summary': {
|
||||||
|
'prediction_count': int(len(prediction_df)),
|
||||||
|
'residual_mean': round(float(residuals.mean()), 4),
|
||||||
|
'residual_std': round(float(residuals.std()), 4),
|
||||||
|
'risk_accuracy': round(
|
||||||
|
float((prediction_df['真实风险等级'] == prediction_df['预测风险等级']).mean()),
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'feature_importance_model': feature_model_name,
|
||||||
|
'generated_files': sorted([file.name for file in OUTPUT_DIR.iterdir() if file.is_file()]),
|
||||||
|
}
|
||||||
|
SUMMARY_JSON.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ensure_output_dir()
|
||||||
|
metrics = load_metrics()
|
||||||
|
prediction_df = load_lstm_predictions()
|
||||||
|
|
||||||
|
plot_model_comparison(metrics)
|
||||||
|
plot_actual_vs_pred(prediction_df)
|
||||||
|
plot_residuals(prediction_df)
|
||||||
|
plot_confusion_matrix(prediction_df)
|
||||||
|
feature_model_name = plot_feature_importance()
|
||||||
|
save_summary(metrics, prediction_df, feature_model_name)
|
||||||
|
|
||||||
|
print(f'评估图片已生成: {OUTPUT_DIR}')
|
||||||
|
print(f'LSTM预测明细: {PREDICTION_CSV}')
|
||||||
|
print(f'评估摘要: {SUMMARY_JSON}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import inspect
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import joblib
|
import joblib
|
||||||
@@ -14,6 +15,8 @@ from sklearn.preprocessing import RobustScaler
|
|||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
from core.deep_learning_model import is_available as deep_learning_available
|
||||||
|
from core.deep_learning_model import train_lstm_mlp
|
||||||
from core.model_features import (
|
from core.model_features import (
|
||||||
NUMERICAL_OUTLIER_COLUMNS,
|
NUMERICAL_OUTLIER_COLUMNS,
|
||||||
ORDINAL_COLUMNS,
|
ORDINAL_COLUMNS,
|
||||||
@@ -43,6 +46,41 @@ except ImportError:
|
|||||||
xgb = None
|
xgb = None
|
||||||
|
|
||||||
|
|
||||||
|
def patch_lightgbm_sklearn_compatibility():
|
||||||
|
if lgb is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sklearn.utils.validation import check_X_y
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
params = inspect.signature(check_X_y).parameters
|
||||||
|
if 'force_all_finite' in params:
|
||||||
|
return
|
||||||
|
|
||||||
|
def wrapped_check_X_y(*args, force_all_finite=None, **kwargs):
|
||||||
|
if (
|
||||||
|
force_all_finite is not None
|
||||||
|
and 'ensure_all_finite' in params
|
||||||
|
and 'ensure_all_finite' not in kwargs
|
||||||
|
):
|
||||||
|
kwargs['ensure_all_finite'] = force_all_finite
|
||||||
|
return check_X_y(*args, **kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import lightgbm.compat as lgb_compat
|
||||||
|
import lightgbm.sklearn as lgb_sklearn
|
||||||
|
|
||||||
|
lgb_compat._LGBMCheckXY = wrapped_check_X_y
|
||||||
|
lgb_sklearn._LGBMCheckXY = wrapped_check_X_y
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
patch_lightgbm_sklearn_compatibility()
|
||||||
|
|
||||||
|
|
||||||
def print_training_log(model_name, start_time, best_score, best_params, n_iter, cv_folds):
|
def print_training_log(model_name, start_time, best_score, best_params, n_iter, cv_folds):
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.time() - start_time
|
||||||
print(f' {"-" * 50}')
|
print(f' {"-" * 50}')
|
||||||
@@ -68,6 +106,10 @@ class OptimizedModelTrainer:
|
|||||||
self.feature_k = 22
|
self.feature_k = 22
|
||||||
self.target_transform = 'log1p'
|
self.target_transform = 'log1p'
|
||||||
self.enabled_models = ['random_forest', 'gradient_boosting', 'extra_trees', 'lightgbm', 'xgboost']
|
self.enabled_models = ['random_forest', 'gradient_boosting', 'extra_trees', 'lightgbm', 'xgboost']
|
||||||
|
if deep_learning_available():
|
||||||
|
self.enabled_models.append('lstm_mlp')
|
||||||
|
self.raw_train_df = None
|
||||||
|
self.raw_test_df = None
|
||||||
|
|
||||||
def analyze_data(self, df):
|
def analyze_data(self, df):
|
||||||
y = df[TARGET_COLUMN]
|
y = df[TARGET_COLUMN]
|
||||||
@@ -96,19 +138,21 @@ class OptimizedModelTrainer:
|
|||||||
return self.feature_selector.transform(X) if self.feature_selector else X
|
return self.feature_selector.transform(X) if self.feature_selector else X
|
||||||
|
|
||||||
def prepare_data(self):
|
def prepare_data(self):
|
||||||
df = normalize_columns(get_clean_data())
|
raw_df = normalize_columns(get_clean_data())
|
||||||
df = prepare_modeling_dataframe(df)
|
self.analyze_data(prepare_modeling_dataframe(raw_df.copy()))
|
||||||
self.analyze_data(df)
|
|
||||||
|
|
||||||
target_bins = make_target_bins(df[TARGET_COLUMN].values)
|
target_bins = make_target_bins(raw_df[TARGET_COLUMN].values)
|
||||||
train_df, test_df = train_test_split(
|
raw_train_df, raw_test_df = train_test_split(
|
||||||
df,
|
raw_df,
|
||||||
test_size=config.TEST_SIZE,
|
test_size=config.TEST_SIZE,
|
||||||
random_state=config.RANDOM_STATE,
|
random_state=config.RANDOM_STATE,
|
||||||
stratify=target_bins,
|
stratify=target_bins,
|
||||||
)
|
)
|
||||||
train_df = train_df.reset_index(drop=True)
|
self.raw_train_df = raw_train_df.reset_index(drop=True)
|
||||||
test_df = test_df.reset_index(drop=True)
|
self.raw_test_df = raw_test_df.reset_index(drop=True)
|
||||||
|
|
||||||
|
train_df = prepare_modeling_dataframe(self.raw_train_df)
|
||||||
|
test_df = prepare_modeling_dataframe(self.raw_test_df)
|
||||||
|
|
||||||
self.outlier_bounds = fit_outlier_bounds(train_df, NUMERICAL_OUTLIER_COLUMNS)
|
self.outlier_bounds = fit_outlier_bounds(train_df, NUMERICAL_OUTLIER_COLUMNS)
|
||||||
train_df = apply_outlier_bounds(train_df, self.outlier_bounds)
|
train_df = apply_outlier_bounds(train_df, self.outlier_bounds)
|
||||||
@@ -138,7 +182,8 @@ class OptimizedModelTrainer:
|
|||||||
'feature_count_after_selection': int(X_train.shape[1]),
|
'feature_count_after_selection': int(X_train.shape[1]),
|
||||||
'training_date': datetime.now().strftime('%Y-%m-%d'),
|
'training_date': datetime.now().strftime('%Y-%m-%d'),
|
||||||
'target_transform': self.target_transform,
|
'target_transform': self.target_transform,
|
||||||
'available_models': list(self.enabled_models),
|
'available_models': [],
|
||||||
|
'deep_learning_available': False,
|
||||||
}
|
}
|
||||||
return X_train, X_test, y_train, y_test
|
return X_train, X_test, y_train, y_test
|
||||||
|
|
||||||
@@ -206,6 +251,7 @@ class OptimizedModelTrainer:
|
|||||||
def train_lightgbm(self, X_train, y_train):
|
def train_lightgbm(self, X_train, y_train):
|
||||||
if lgb is None:
|
if lgb is None:
|
||||||
return
|
return
|
||||||
|
try:
|
||||||
self._run_search(
|
self._run_search(
|
||||||
'lightgbm',
|
'lightgbm',
|
||||||
lgb.LGBMRegressor(random_state=config.RANDOM_STATE, n_jobs=-1, verbose=-1),
|
lgb.LGBMRegressor(random_state=config.RANDOM_STATE, n_jobs=-1, verbose=-1),
|
||||||
@@ -220,6 +266,10 @@ class OptimizedModelTrainer:
|
|||||||
X_train,
|
X_train,
|
||||||
y_train,
|
y_train,
|
||||||
)
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f' {"-" * 50}')
|
||||||
|
print(' Model: lightgbm')
|
||||||
|
print(f' Skipped: {exc}')
|
||||||
|
|
||||||
def train_xgboost(self, X_train, y_train):
|
def train_xgboost(self, X_train, y_train):
|
||||||
if xgb is None:
|
if xgb is None:
|
||||||
@@ -254,6 +304,7 @@ class OptimizedModelTrainer:
|
|||||||
os.makedirs(config.MODELS_DIR, exist_ok=True)
|
os.makedirs(config.MODELS_DIR, exist_ok=True)
|
||||||
for name, model in self.models.items():
|
for name, model in self.models.items():
|
||||||
joblib.dump(model, os.path.join(config.MODELS_DIR, f'{name}_model.pkl'))
|
joblib.dump(model, os.path.join(config.MODELS_DIR, f'{name}_model.pkl'))
|
||||||
|
self.training_metadata['available_models'] = list(self.model_metrics.keys())
|
||||||
joblib.dump(self.scaler, config.SCALER_PATH)
|
joblib.dump(self.scaler, config.SCALER_PATH)
|
||||||
joblib.dump(self.feature_names, os.path.join(config.MODELS_DIR, 'feature_names.pkl'))
|
joblib.dump(self.feature_names, os.path.join(config.MODELS_DIR, 'feature_names.pkl'))
|
||||||
joblib.dump(self.selected_features, os.path.join(config.MODELS_DIR, 'selected_features.pkl'))
|
joblib.dump(self.selected_features, os.path.join(config.MODELS_DIR, 'selected_features.pkl'))
|
||||||
@@ -282,6 +333,23 @@ class OptimizedModelTrainer:
|
|||||||
self.model_metrics[name] = metrics
|
self.model_metrics[name] = metrics
|
||||||
print(f' {name:20s} R2={metrics["r2"]:.4f} RMSE={metrics["rmse"]:.4f} MAE={metrics["mae"]:.4f}')
|
print(f' {name:20s} R2={metrics["r2"]:.4f} RMSE={metrics["rmse"]:.4f} MAE={metrics["mae"]:.4f}')
|
||||||
|
|
||||||
|
if 'lstm_mlp' in self.enabled_models and self.raw_train_df is not None and self.raw_test_df is not None:
|
||||||
|
deep_model_path = os.path.join(config.MODELS_DIR, 'lstm_mlp_model.pt')
|
||||||
|
deep_result = train_lstm_mlp(
|
||||||
|
self.raw_train_df,
|
||||||
|
self.raw_test_df,
|
||||||
|
deep_model_path,
|
||||||
|
target_transform=self.target_transform,
|
||||||
|
)
|
||||||
|
if deep_result:
|
||||||
|
self.model_metrics['lstm_mlp'] = deep_result['metrics']
|
||||||
|
self.training_metadata['deep_learning_available'] = True
|
||||||
|
self.training_metadata.update(deep_result['metadata'])
|
||||||
|
print(
|
||||||
|
f' {"lstm_mlp":20s} R2={deep_result["metrics"]["r2"]:.4f} '
|
||||||
|
f'RMSE={deep_result["metrics"]["rmse"]:.4f} MAE={deep_result["metrics"]["mae"]:.4f}'
|
||||||
|
)
|
||||||
|
|
||||||
self.save_models()
|
self.save_models()
|
||||||
return self.model_metrics
|
return self.model_metrics
|
||||||
|
|
||||||
|
|||||||
@@ -1,741 +0,0 @@
|
|||||||
ID;Reason for absence;Month of absence;Day of the week;Seasons;Transportation expense;Distance from Residence to Work;Service time;Age;Work load Average/day ;Hit target;Disciplinary failure;Education;Son;Social drinker;Social smoker;Pet;Weight;Height;Body mass index;Absenteeism time in hours
|
|
||||||
11;26;7;3;1;289;36;13;33;239.554;97;0;1;2;1;0;1;90;172;30;4
|
|
||||||
36;0;7;3;1;118;13;18;50;239.554;97;1;1;1;1;0;0;98;178;31;0
|
|
||||||
3;23;7;4;1;179;51;18;38;239.554;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
7;7;7;5;1;279;5;14;39;239.554;97;0;1;2;1;1;0;68;168;24;4
|
|
||||||
11;23;7;5;1;289;36;13;33;239.554;97;0;1;2;1;0;1;90;172;30;2
|
|
||||||
3;23;7;6;1;179;51;18;38;239.554;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
10;22;7;6;1;361;52;3;28;239.554;97;0;1;1;1;0;4;80;172;27;8
|
|
||||||
20;23;7;6;1;260;50;11;36;239.554;97;0;1;4;1;0;0;65;168;23;4
|
|
||||||
14;19;7;2;1;155;12;14;34;239.554;97;0;1;2;1;0;0;95;196;25;40
|
|
||||||
1;22;7;2;1;235;11;14;37;239.554;97;0;3;1;0;0;1;88;172;29;8
|
|
||||||
20;1;7;2;1;260;50;11;36;239.554;97;0;1;4;1;0;0;65;168;23;8
|
|
||||||
20;1;7;3;1;260;50;11;36;239.554;97;0;1;4;1;0;0;65;168;23;8
|
|
||||||
20;11;7;4;1;260;50;11;36;239.554;97;0;1;4;1;0;0;65;168;23;8
|
|
||||||
3;11;7;4;1;179;51;18;38;239.554;97;0;1;0;1;0;0;89;170;31;1
|
|
||||||
3;23;7;4;1;179;51;18;38;239.554;97;0;1;0;1;0;0;89;170;31;4
|
|
||||||
24;14;7;6;1;246;25;16;41;239.554;97;0;1;0;1;0;0;67;170;23;8
|
|
||||||
3;23;7;6;1;179;51;18;38;239.554;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;21;7;2;1;179;51;18;38;239.554;97;0;1;0;1;0;0;89;170;31;8
|
|
||||||
6;11;7;5;1;189;29;13;33;239.554;97;0;1;2;0;0;2;69;167;25;8
|
|
||||||
33;23;8;4;1;248;25;14;47;205.917;92;0;1;2;0;0;1;86;165;32;2
|
|
||||||
18;10;8;4;1;330;16;4;28;205.917;92;0;2;0;0;0;0;84;182;25;8
|
|
||||||
3;11;8;2;1;179;51;18;38;205.917;92;0;1;0;1;0;0;89;170;31;1
|
|
||||||
10;13;8;2;1;361;52;3;28;205.917;92;0;1;1;1;0;4;80;172;27;40
|
|
||||||
20;28;8;6;1;260;50;11;36;205.917;92;0;1;4;1;0;0;65;168;23;4
|
|
||||||
11;18;8;2;1;289;36;13;33;205.917;92;0;1;2;1;0;1;90;172;30;8
|
|
||||||
10;25;8;2;1;361;52;3;28;205.917;92;0;1;1;1;0;4;80;172;27;7
|
|
||||||
11;23;8;3;1;289;36;13;33;205.917;92;0;1;2;1;0;1;90;172;30;1
|
|
||||||
30;28;8;4;1;157;27;6;29;205.917;92;0;1;0;1;1;0;75;185;22;4
|
|
||||||
11;18;8;4;1;289;36;13;33;205.917;92;0;1;2;1;0;1;90;172;30;8
|
|
||||||
3;23;8;6;1;179;51;18;38;205.917;92;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;18;8;2;1;179;51;18;38;205.917;92;0;1;0;1;0;0;89;170;31;8
|
|
||||||
2;18;8;5;1;235;29;12;48;205.917;92;0;1;1;0;1;5;88;163;33;8
|
|
||||||
1;23;8;5;1;235;11;14;37;205.917;92;0;3;1;0;0;1;88;172;29;4
|
|
||||||
2;18;8;2;1;235;29;12;48;205.917;92;0;1;1;0;1;5;88;163;33;8
|
|
||||||
3;23;8;2;1;179;51;18;38;205.917;92;0;1;0;1;0;0;89;170;31;2
|
|
||||||
10;23;8;2;1;361;52;3;28;205.917;92;0;1;1;1;0;4;80;172;27;1
|
|
||||||
11;24;8;3;1;289;36;13;33;205.917;92;0;1;2;1;0;1;90;172;30;8
|
|
||||||
19;11;8;5;1;291;50;12;32;205.917;92;0;1;0;1;0;0;65;169;23;4
|
|
||||||
2;28;8;6;1;235;29;12;48;205.917;92;0;1;1;0;1;5;88;163;33;8
|
|
||||||
20;23;8;6;1;260;50;11;36;205.917;92;0;1;4;1;0;0;65;168;23;4
|
|
||||||
27;23;9;3;1;184;42;7;27;241.476;92;0;1;0;0;0;0;58;167;21;2
|
|
||||||
34;23;9;2;1;118;10;10;37;241.476;92;0;1;0;0;0;0;83;172;28;4
|
|
||||||
3;23;9;3;1;179;51;18;38;241.476;92;0;1;0;1;0;0;89;170;31;4
|
|
||||||
5;19;9;3;1;235;20;13;43;241.476;92;0;1;1;1;0;0;106;167;38;8
|
|
||||||
14;23;9;4;1;155;12;14;34;241.476;92;0;1;2;1;0;0;95;196;25;2
|
|
||||||
34;23;9;2;1;118;10;10;37;241.476;92;0;1;0;0;0;0;83;172;28;3
|
|
||||||
3;23;9;3;1;179;51;18;38;241.476;92;0;1;0;1;0;0;89;170;31;3
|
|
||||||
15;23;9;5;1;291;31;12;40;241.476;92;0;1;1;1;0;1;73;171;25;4
|
|
||||||
20;22;9;6;1;260;50;11;36;241.476;92;0;1;4;1;0;0;65;168;23;8
|
|
||||||
15;14;9;2;4;291;31;12;40;241.476;92;0;1;1;1;0;1;73;171;25;32
|
|
||||||
20;0;9;2;4;260;50;11;36;241.476;92;1;1;4;1;0;0;65;168;23;0
|
|
||||||
29;0;9;2;4;225;26;9;28;241.476;92;1;1;1;0;0;2;69;169;24;0
|
|
||||||
28;23;9;3;4;225;26;9;28;241.476;92;0;1;1;0;0;2;69;169;24;2
|
|
||||||
34;23;9;3;4;118;10;10;37;241.476;92;0;1;0;0;0;0;83;172;28;2
|
|
||||||
11;0;9;3;4;289;36;13;33;241.476;92;1;1;2;1;0;1;90;172;30;0
|
|
||||||
36;0;9;3;4;118;13;18;50;241.476;92;1;1;1;1;0;0;98;178;31;0
|
|
||||||
28;18;9;4;4;225;26;9;28;241.476;92;0;1;1;0;0;2;69;169;24;3
|
|
||||||
3;23;9;4;4;179;51;18;38;241.476;92;0;1;0;1;0;0;89;170;31;3
|
|
||||||
13;0;9;4;4;369;17;12;31;241.476;92;1;1;3;1;0;0;70;169;25;0
|
|
||||||
33;23;9;6;4;248;25;14;47;241.476;92;0;1;2;0;0;1;86;165;32;1
|
|
||||||
3;23;9;6;4;179;51;18;38;241.476;92;0;1;0;1;0;0;89;170;31;3
|
|
||||||
20;23;9;6;4;260;50;11;36;241.476;92;0;1;4;1;0;0;65;168;23;4
|
|
||||||
3;23;10;3;4;179;51;18;38;253.465;93;0;1;0;1;0;0;89;170;31;3
|
|
||||||
34;23;10;3;4;118;10;10;37;253.465;93;0;1;0;0;0;0;83;172;28;3
|
|
||||||
36;0;10;4;4;118;13;18;50;253.465;93;1;1;1;1;0;0;98;178;31;0
|
|
||||||
22;23;10;5;4;179;26;9;30;253.465;93;0;3;0;0;0;0;56;171;19;1
|
|
||||||
3;23;10;6;4;179;51;18;38;253.465;93;0;1;0;1;0;0;89;170;31;3
|
|
||||||
28;23;10;6;4;225;26;9;28;253.465;93;0;1;1;0;0;2;69;169;24;3
|
|
||||||
34;23;10;3;4;118;10;10;37;253.465;93;0;1;0;0;0;0;83;172;28;3
|
|
||||||
28;23;10;4;4;225;26;9;28;253.465;93;0;1;1;0;0;2;69;169;24;2
|
|
||||||
33;23;10;4;4;248;25;14;47;253.465;93;0;1;2;0;0;1;86;165;32;2
|
|
||||||
15;23;10;5;4;291;31;12;40;253.465;93;0;1;1;1;0;1;73;171;25;5
|
|
||||||
3;23;10;4;4;179;51;18;38;253.465;93;0;1;0;1;0;0;89;170;31;8
|
|
||||||
28;23;10;4;4;225;26;9;28;253.465;93;0;1;1;0;0;2;69;169;24;3
|
|
||||||
20;19;10;5;4;260;50;11;36;253.465;93;0;1;4;1;0;0;65;168;23;16
|
|
||||||
15;14;10;3;4;291;31;12;40;253.465;93;0;1;1;1;0;1;73;171;25;8
|
|
||||||
28;28;10;3;4;225;26;9;28;253.465;93;0;1;1;0;0;2;69;169;24;2
|
|
||||||
11;26;10;4;4;289;36;13;33;253.465;93;0;1;2;1;0;1;90;172;30;8
|
|
||||||
10;23;10;6;4;361;52;3;28;253.465;93;0;1;1;1;0;4;80;172;27;1
|
|
||||||
20;28;10;6;4;260;50;11;36;253.465;93;0;1;4;1;0;0;65;168;23;3
|
|
||||||
3;23;11;5;4;179;51;18;38;306.345;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
28;23;11;4;4;225;26;9;28;306.345;93;0;1;1;0;0;2;69;169;24;1
|
|
||||||
3;13;11;5;4;179;51;18;38;306.345;93;0;1;0;1;0;0;89;170;31;8
|
|
||||||
17;21;11;5;4;179;22;17;40;306.345;93;0;2;2;0;1;0;63;170;22;8
|
|
||||||
15;23;11;5;4;291;31;12;40;306.345;93;0;1;1;1;0;1;73;171;25;5
|
|
||||||
14;10;11;2;4;155;12;14;34;306.345;93;0;1;2;1;0;0;95;196;25;32
|
|
||||||
6;22;11;2;4;189;29;13;33;306.345;93;0;1;2;0;0;2;69;167;25;8
|
|
||||||
15;14;11;2;4;291;31;12;40;306.345;93;0;1;1;1;0;1;73;171;25;40
|
|
||||||
28;23;11;4;4;225;26;9;28;306.345;93;0;1;1;0;0;2;69;169;24;1
|
|
||||||
14;6;11;6;4;155;12;14;34;306.345;93;0;1;2;1;0;0;95;196;25;8
|
|
||||||
28;23;11;4;4;225;26;9;28;306.345;93;0;1;1;0;0;2;69;169;24;3
|
|
||||||
17;21;11;4;4;179;22;17;40;306.345;93;0;2;2;0;1;0;63;170;22;8
|
|
||||||
28;13;11;6;4;225;26;9;28;306.345;93;0;1;1;0;0;2;69;169;24;3
|
|
||||||
20;28;11;6;4;260;50;11;36;306.345;93;0;1;4;1;0;0;65;168;23;4
|
|
||||||
33;28;11;2;4;248;25;14;47;306.345;93;0;1;2;0;0;1;86;165;32;1
|
|
||||||
28;28;11;3;4;225;26;9;28;306.345;93;0;1;1;0;0;2;69;169;24;3
|
|
||||||
11;7;11;4;4;289;36;13;33;306.345;93;0;1;2;1;0;1;90;172;30;24
|
|
||||||
15;23;11;5;4;291;31;12;40;306.345;93;0;1;1;1;0;1;73;171;25;3
|
|
||||||
33;23;12;3;4;248;25;14;47;261.306;97;0;1;2;0;0;1;86;165;32;1
|
|
||||||
34;19;12;3;4;118;10;10;37;261.306;97;0;1;0;0;0;0;83;172;28;64
|
|
||||||
36;23;12;4;4;118;13;18;50;261.306;97;0;1;1;1;0;0;98;178;31;2
|
|
||||||
1;26;12;4;4;235;11;14;37;261.306;97;0;3;1;0;0;1;88;172;29;8
|
|
||||||
28;23;12;5;4;225;26;9;28;261.306;97;0;1;1;0;0;2;69;169;24;2
|
|
||||||
20;26;12;6;4;260;50;11;36;261.306;97;0;1;4;1;0;0;65;168;23;8
|
|
||||||
34;19;12;3;4;118;10;10;37;261.306;97;0;1;0;0;0;0;83;172;28;56
|
|
||||||
10;22;12;4;4;361;52;3;28;261.306;97;0;1;1;1;0;4;80;172;27;8
|
|
||||||
28;28;12;5;4;225;26;9;28;261.306;97;0;1;1;0;0;2;69;169;24;3
|
|
||||||
20;28;12;6;4;260;50;11;36;261.306;97;0;1;4;1;0;0;65;168;23;3
|
|
||||||
28;23;12;3;4;225;26;9;28;261.306;97;0;1;1;0;0;2;69;169;24;2
|
|
||||||
10;22;12;4;4;361;52;3;28;261.306;97;0;1;1;1;0;4;80;172;27;8
|
|
||||||
34;27;12;6;4;118;10;10;37;261.306;97;0;1;0;0;0;0;83;172;28;2
|
|
||||||
24;19;12;6;2;246;25;16;41;261.306;97;0;1;0;1;0;0;67;170;23;8
|
|
||||||
28;23;12;6;2;225;26;9;28;261.306;97;0;1;1;0;0;2;69;169;24;2
|
|
||||||
28;23;1;4;2;225;26;9;28;308.593;95;0;1;1;0;0;2;69;169;24;1
|
|
||||||
34;19;1;2;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;1
|
|
||||||
34;27;1;3;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;1
|
|
||||||
14;18;1;3;2;155;12;14;34;308.593;95;0;1;2;1;0;0;95;196;25;8
|
|
||||||
28;27;1;4;2;225;26;9;28;308.593;95;0;1;1;0;0;2;69;169;24;2
|
|
||||||
27;23;1;5;2;184;42;7;27;308.593;95;0;1;0;0;0;0;58;167;21;2
|
|
||||||
28;28;1;5;2;225;26;9;28;308.593;95;0;1;1;0;0;2;69;169;24;2
|
|
||||||
28;27;1;6;2;225;26;9;28;308.593;95;0;1;1;0;0;2;69;169;24;1
|
|
||||||
34;27;1;2;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
28;27;1;3;2;225;26;9;28;308.593;95;0;1;1;0;0;2;69;169;24;2
|
|
||||||
34;27;1;3;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
34;27;1;4;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
34;27;1;5;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
34;27;1;6;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
34;27;1;2;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
34;27;1;3;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
22;18;1;3;2;179;26;9;30;308.593;95;0;3;0;0;0;0;56;171;19;8
|
|
||||||
11;18;1;3;2;289;36;13;33;308.593;95;0;1;2;1;0;1;90;172;30;8
|
|
||||||
34;27;1;4;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
27;23;1;5;2;184;42;7;27;308.593;95;0;1;0;0;0;0;58;167;21;2
|
|
||||||
34;27;1;5;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
34;27;1;2;2;118;10;10;37;308.593;95;0;1;0;0;0;0;83;172;28;0
|
|
||||||
28;23;1;3;2;225;26;9;28;308.593;95;0;1;1;0;0;2;69;169;24;1
|
|
||||||
11;22;1;5;2;289;36;13;33;308.593;95;0;1;2;1;0;1;90;172;30;3
|
|
||||||
27;23;2;6;2;184;42;7;27;302.585;99;0;1;0;0;0;0;58;167;21;1
|
|
||||||
24;1;2;4;2;246;25;16;41;302.585;99;0;1;0;1;0;0;67;170;23;8
|
|
||||||
3;11;2;4;2;179;51;18;38;302.585;99;0;1;0;1;0;0;89;170;31;8
|
|
||||||
14;28;2;5;2;155;12;14;34;302.585;99;0;1;2;1;0;0;95;196;25;2
|
|
||||||
6;23;2;5;2;189;29;13;33;302.585;99;0;1;2;0;0;2;69;167;25;8
|
|
||||||
20;28;2;6;2;260;50;11;36;302.585;99;0;1;4;1;0;0;65;168;23;2
|
|
||||||
11;22;2;6;2;289;36;13;33;302.585;99;0;1;2;1;0;1;90;172;30;8
|
|
||||||
31;11;2;2;2;388;15;9;50;302.585;99;0;1;0;0;0;0;76;178;24;8
|
|
||||||
31;1;2;3;2;388;15;9;50;302.585;99;0;1;0;0;0;0;76;178;24;8
|
|
||||||
28;28;2;2;2;225;26;9;28;302.585;99;0;1;1;0;0;2;69;169;24;2
|
|
||||||
28;23;2;3;2;225;26;9;28;302.585;99;0;1;1;0;0;2;69;169;24;2
|
|
||||||
22;23;2;3;2;179;26;9;30;302.585;99;0;3;0;0;0;0;56;171;19;1
|
|
||||||
27;23;2;3;2;184;42;7;27;302.585;99;0;1;0;0;0;0;58;167;21;8
|
|
||||||
28;25;2;5;2;225;26;9;28;302.585;99;0;1;1;0;0;2;69;169;24;3
|
|
||||||
18;18;2;2;2;330;16;4;28;302.585;99;0;2;0;0;0;0;84;182;25;8
|
|
||||||
18;23;2;3;2;330;16;4;28;302.585;99;0;2;0;0;0;0;84;182;25;1
|
|
||||||
28;23;2;4;2;225;26;9;28;302.585;99;0;1;1;0;0;2;69;169;24;1
|
|
||||||
6;19;2;5;2;189;29;13;33;302.585;99;0;1;2;0;0;2;69;167;25;8
|
|
||||||
19;28;3;3;2;291;50;12;32;343.253;95;0;1;0;1;0;0;65;169;23;2
|
|
||||||
20;19;3;3;2;260;50;11;36;343.253;95;0;1;4;1;0;0;65;168;23;8
|
|
||||||
30;19;3;3;2;157;27;6;29;343.253;95;0;1;0;1;1;0;75;185;22;3
|
|
||||||
17;17;3;3;2;179;22;17;40;343.253;95;0;2;2;0;1;0;63;170;22;8
|
|
||||||
15;22;3;4;2;291;31;12;40;343.253;95;0;1;1;1;0;1;73;171;25;8
|
|
||||||
20;13;3;4;2;260;50;11;36;343.253;95;0;1;4;1;0;0;65;168;23;8
|
|
||||||
22;13;3;5;2;179;26;9;30;343.253;95;0;3;0;0;0;0;56;171;19;8
|
|
||||||
33;14;3;6;2;248;25;14;47;343.253;95;0;1;2;0;0;1;86;165;32;3
|
|
||||||
20;13;3;6;2;260;50;11;36;343.253;95;0;1;4;1;0;0;65;168;23;40
|
|
||||||
17;11;3;2;2;179;22;17;40;343.253;95;0;2;2;0;1;0;63;170;22;40
|
|
||||||
14;1;3;2;2;155;12;14;34;343.253;95;0;1;2;1;0;0;95;196;25;16
|
|
||||||
20;26;3;2;2;260;50;11;36;343.253;95;0;1;4;1;0;0;65;168;23;16
|
|
||||||
14;13;3;3;2;155;12;14;34;343.253;95;0;1;2;1;0;0;95;196;25;8
|
|
||||||
11;6;3;5;2;289;36;13;33;343.253;95;0;1;2;1;0;1;90;172;30;8
|
|
||||||
17;8;3;5;2;179;22;17;40;343.253;95;0;2;2;0;1;0;63;170;22;8
|
|
||||||
20;28;3;6;2;260;50;11;36;343.253;95;0;1;4;1;0;0;65;168;23;4
|
|
||||||
28;23;3;6;2;225;26;9;28;343.253;95;0;1;1;0;0;2;69;169;24;1
|
|
||||||
7;14;3;2;2;279;5;14;39;343.253;95;0;1;2;1;1;0;68;168;24;8
|
|
||||||
3;13;3;3;2;179;51;18;38;343.253;95;0;1;0;1;0;0;89;170;31;24
|
|
||||||
28;23;3;4;2;225;26;9;28;343.253;95;0;1;1;0;0;2;69;169;24;2
|
|
||||||
28;11;3;2;3;225;26;9;28;343.253;95;0;1;1;0;0;2;69;169;24;8
|
|
||||||
22;13;3;2;3;179;26;9;30;343.253;95;0;3;0;0;0;0;56;171;19;1
|
|
||||||
28;11;3;3;3;225;26;9;28;343.253;95;0;1;1;0;0;2;69;169;24;8
|
|
||||||
28;11;3;4;3;225;26;9;28;343.253;95;0;1;1;0;0;2;69;169;24;16
|
|
||||||
3;13;3;4;3;179;51;18;38;343.253;95;0;1;0;1;0;0;89;170;31;3
|
|
||||||
7;14;3;5;3;279;5;14;39;343.253;95;0;1;2;1;1;0;68;168;24;16
|
|
||||||
28;28;3;6;3;225;26;9;28;343.253;95;0;1;1;0;0;2;69;169;24;2
|
|
||||||
33;14;3;6;3;248;25;14;47;343.253;95;0;1;2;0;0;1;86;165;32;3
|
|
||||||
28;28;3;2;3;225;26;9;28;343.253;95;0;1;1;0;0;2;69;169;24;1
|
|
||||||
15;28;4;4;3;291;31;12;40;326.452;96;0;1;1;1;0;1;73;171;25;1
|
|
||||||
28;23;4;4;3;225;26;9;28;326.452;96;0;1;1;0;0;2;69;169;24;1
|
|
||||||
14;28;4;3;3;155;12;14;34;326.452;96;0;1;2;1;0;0;95;196;25;1
|
|
||||||
24;13;4;4;3;246;25;16;41;326.452;96;0;1;0;1;0;0;67;170;23;24
|
|
||||||
14;23;4;5;3;155;12;14;34;326.452;96;0;1;2;1;0;0;95;196;25;1
|
|
||||||
28;28;4;6;3;225;26;9;28;326.452;96;0;1;1;0;0;2;69;169;24;2
|
|
||||||
20;28;4;6;3;260;50;11;36;326.452;96;0;1;4;1;0;0;65;168;23;4
|
|
||||||
3;13;4;4;3;179;51;18;38;326.452;96;0;1;0;1;0;0;89;170;31;24
|
|
||||||
36;23;4;5;3;118;13;18;50;326.452;96;0;1;1;1;0;0;98;178;31;1
|
|
||||||
15;23;4;6;3;291;31;12;40;326.452;96;0;1;1;1;0;1;73;171;25;3
|
|
||||||
24;14;4;6;3;246;25;16;41;326.452;96;0;1;0;1;0;0;67;170;23;8
|
|
||||||
15;28;4;6;3;291;31;12;40;326.452;96;0;1;1;1;0;1;73;171;25;1
|
|
||||||
33;28;4;6;3;248;25;14;47;326.452;96;0;1;2;0;0;1;86;165;32;8
|
|
||||||
20;19;4;6;3;260;50;11;36;326.452;96;0;1;4;1;0;0;65;168;23;56
|
|
||||||
11;19;4;3;3;289;36;13;33;326.452;96;0;1;2;1;0;1;90;172;30;8
|
|
||||||
14;12;4;4;3;155;12;14;34;326.452;96;0;1;2;1;0;0;95;196;25;24
|
|
||||||
23;19;4;4;3;378;49;11;36;326.452;96;0;1;2;0;1;4;65;174;21;8
|
|
||||||
11;13;4;5;3;289;36;13;33;326.452;96;0;1;2;1;0;1;90;172;30;16
|
|
||||||
1;7;4;6;3;235;11;14;37;326.452;96;0;3;1;0;0;1;88;172;29;3
|
|
||||||
2;0;4;2;3;235;29;12;48;326.452;96;1;1;1;0;1;5;88;163;33;0
|
|
||||||
11;13;5;4;3;289;36;13;33;378.884;92;0;1;2;1;0;1;90;172;30;8
|
|
||||||
14;28;5;5;3;155;12;14;34;378.884;92;0;1;2;1;0;0;95;196;25;2
|
|
||||||
14;28;5;2;3;155;12;14;34;378.884;92;0;1;2;1;0;0;95;196;25;1
|
|
||||||
3;18;5;3;3;179;51;18;38;378.884;92;0;1;0;1;0;0;89;170;31;8
|
|
||||||
28;19;5;3;3;225;26;9;28;378.884;92;0;1;1;0;0;2;69;169;24;8
|
|
||||||
27;7;5;4;3;184;42;7;27;378.884;92;0;1;0;0;0;0;58;167;21;4
|
|
||||||
14;28;5;2;3;155;12;14;34;378.884;92;0;1;2;1;0;0;95;196;25;2
|
|
||||||
3;12;5;3;3;179;51;18;38;378.884;92;0;1;0;1;0;0;89;170;31;1
|
|
||||||
11;13;5;4;3;289;36;13;33;378.884;92;0;1;2;1;0;1;90;172;30;24
|
|
||||||
7;0;5;4;3;279;5;14;39;378.884;92;1;1;2;1;1;0;68;168;24;0
|
|
||||||
18;0;5;4;3;330;16;4;28;378.884;92;1;2;0;0;0;0;84;182;25;0
|
|
||||||
23;0;5;4;3;378;49;11;36;378.884;92;1;1;2;0;1;4;65;174;21;0
|
|
||||||
31;0;5;4;3;388;15;9;50;378.884;92;1;1;0;0;0;0;76;178;24;0
|
|
||||||
3;11;5;3;3;179;51;18;38;378.884;92;0;1;0;1;0;0;89;170;31;1
|
|
||||||
36;13;5;4;3;118;13;18;50;378.884;92;0;1;1;1;0;0;98;178;31;24
|
|
||||||
10;22;5;6;3;361;52;3;28;378.884;92;0;1;1;1;0;4;80;172;27;8
|
|
||||||
24;19;6;2;3;246;25;16;41;377.550;94;0;1;0;1;0;0;67;170;23;8
|
|
||||||
10;22;6;2;3;361;52;3;28;377.550;94;0;1;1;1;0;4;80;172;27;8
|
|
||||||
24;10;6;3;3;246;25;16;41;377.550;94;0;1;0;1;0;0;67;170;23;24
|
|
||||||
15;23;6;5;3;291;31;12;40;377.550;94;0;1;1;1;0;1;73;171;25;4
|
|
||||||
24;10;6;6;3;246;25;16;41;377.550;94;0;1;0;1;0;0;67;170;23;8
|
|
||||||
3;11;6;2;3;179;51;18;38;377.550;94;0;1;0;1;0;0;89;170;31;8
|
|
||||||
14;23;6;2;3;155;12;14;34;377.550;94;0;1;2;1;0;0;95;196;25;4
|
|
||||||
24;10;6;2;3;246;25;16;41;377.550;94;0;1;0;1;0;0;67;170;23;8
|
|
||||||
36;13;6;4;3;118;13;18;50;377.550;94;0;1;1;1;0;0;98;178;31;8
|
|
||||||
1;13;6;6;3;235;11;14;37;377.550;94;0;3;1;0;0;1;88;172;29;16
|
|
||||||
36;23;6;3;3;118;13;18;50;377.550;94;0;1;1;1;0;0;98;178;31;1
|
|
||||||
36;13;6;4;3;118;13;18;50;377.550;94;0;1;1;1;0;0;98;178;31;80
|
|
||||||
23;22;6;5;3;378;49;11;36;377.550;94;0;1;2;0;1;4;65;174;21;8
|
|
||||||
3;11;6;6;3;179;51;18;38;377.550;94;0;1;0;1;0;0;89;170;31;2
|
|
||||||
32;28;6;2;1;289;48;29;49;377.550;94;0;1;0;0;0;2;108;172;36;2
|
|
||||||
28;28;6;5;1;225;26;9;28;377.550;94;0;1;1;0;0;2;69;169;24;2
|
|
||||||
14;19;7;3;1;155;12;14;34;275.312;98;0;1;2;1;0;0;95;196;25;16
|
|
||||||
36;1;7;4;1;118;13;18;50;275.312;98;0;1;1;1;0;0;98;178;31;8
|
|
||||||
34;5;7;6;1;118;10;10;37;275.312;98;0;1;0;0;0;0;83;172;28;8
|
|
||||||
34;26;7;6;1;118;10;10;37;275.312;98;0;1;0;0;0;0;83;172;28;4
|
|
||||||
18;26;7;3;1;330;16;4;28;275.312;98;0;2;0;0;0;0;84;182;25;8
|
|
||||||
22;18;7;5;1;179;26;9;30;275.312;98;0;3;0;0;0;0;56;171;19;8
|
|
||||||
14;25;7;6;1;155;12;14;34;275.312;98;0;1;2;1;0;0;95;196;25;2
|
|
||||||
18;1;7;2;1;330;16;4;28;275.312;98;0;2;0;0;0;0;84;182;25;8
|
|
||||||
18;1;7;3;1;330;16;4;28;275.312;98;0;2;0;0;0;0;84;182;25;8
|
|
||||||
30;25;7;2;1;157;27;6;29;275.312;98;0;1;0;1;1;0;75;185;22;3
|
|
||||||
10;22;7;3;1;361;52;3;28;275.312;98;0;1;1;1;0;4;80;172;27;8
|
|
||||||
11;26;7;4;1;289;36;13;33;275.312;98;0;1;2;1;0;1;90;172;30;8
|
|
||||||
3;26;7;5;1;179;51;18;38;275.312;98;0;1;0;1;0;0;89;170;31;8
|
|
||||||
11;19;7;2;1;289;36;13;33;275.312;98;0;1;2;1;0;1;90;172;30;32
|
|
||||||
11;19;7;5;1;289;36;13;33;275.312;98;0;1;2;1;0;1;90;172;30;8
|
|
||||||
20;0;7;5;1;260;50;11;36;275.312;98;1;1;4;1;0;0;65;168;23;0
|
|
||||||
11;19;8;6;1;289;36;13;33;265.615;94;0;1;2;1;0;1;90;172;30;8
|
|
||||||
30;19;8;6;1;157;27;6;29;265.615;94;0;1;0;1;1;0;75;185;22;3
|
|
||||||
11;23;8;2;1;289;36;13;33;265.615;94;0;1;2;1;0;1;90;172;30;1
|
|
||||||
9;18;8;3;1;228;14;16;58;265.615;94;0;1;2;0;0;1;65;172;22;8
|
|
||||||
26;13;8;5;1;300;26;13;43;265.615;94;0;1;2;1;1;1;77;175;25;1
|
|
||||||
26;14;8;5;1;300;26;13;43;265.615;94;0;1;2;1;1;1;77;175;25;2
|
|
||||||
20;28;8;6;1;260;50;11;36;265.615;94;0;1;4;1;0;0;65;168;23;4
|
|
||||||
11;23;8;3;1;289;36;13;33;265.615;94;0;1;2;1;0;1;90;172;30;4
|
|
||||||
33;23;8;4;1;248;25;14;47;265.615;94;0;1;2;0;0;1;86;165;32;1
|
|
||||||
21;11;8;5;1;268;11;8;33;265.615;94;0;2;0;0;0;0;79;178;25;8
|
|
||||||
22;23;8;5;1;179;26;9;30;265.615;94;0;3;0;0;0;0;56;171;19;1
|
|
||||||
36;13;8;5;1;118;13;18;50;265.615;94;0;1;1;1;0;0;98;178;31;3
|
|
||||||
33;25;8;2;1;248;25;14;47;265.615;94;0;1;2;0;0;1;86;165;32;2
|
|
||||||
1;23;8;3;1;235;11;14;37;265.615;94;0;3;1;0;0;1;88;172;29;1
|
|
||||||
36;23;8;5;1;118;13;18;50;265.615;94;0;1;1;1;0;0;98;178;31;1
|
|
||||||
1;19;8;5;1;235;11;14;37;265.615;94;0;3;1;0;0;1;88;172;29;8
|
|
||||||
10;8;8;3;1;361;52;3;28;265.615;94;0;1;1;1;0;4;80;172;27;8
|
|
||||||
27;6;8;4;1;184;42;7;27;265.615;94;0;1;0;0;0;0;58;167;21;8
|
|
||||||
3;11;9;2;1;179;51;18;38;294.217;81;0;1;0;1;0;0;89;170;31;8
|
|
||||||
3;23;9;6;1;179;51;18;38;294.217;81;0;1;0;1;0;0;89;170;31;3
|
|
||||||
11;19;9;4;1;289;36;13;33;294.217;81;0;1;2;1;0;1;90;172;30;24
|
|
||||||
5;0;9;5;1;235;20;13;43;294.217;81;1;1;1;1;0;0;106;167;38;0
|
|
||||||
24;9;9;2;1;246;25;16;41;294.217;81;0;1;0;1;0;0;67;170;23;16
|
|
||||||
15;28;9;3;1;291;31;12;40;294.217;81;0;1;1;1;0;1;73;171;25;3
|
|
||||||
8;0;9;3;1;231;35;14;39;294.217;81;1;1;2;1;0;2;100;170;35;0
|
|
||||||
19;0;9;3;1;291;50;12;32;294.217;81;1;1;0;1;0;0;65;169;23;0
|
|
||||||
3;13;9;4;1;179;51;18;38;294.217;81;0;1;0;1;0;0;89;170;31;8
|
|
||||||
24;9;9;4;1;246;25;16;41;294.217;81;0;1;0;1;0;0;67;170;23;32
|
|
||||||
3;23;9;5;1;179;51;18;38;294.217;81;0;1;0;1;0;0;89;170;31;1
|
|
||||||
15;28;9;6;1;291;31;12;40;294.217;81;0;1;1;1;0;1;73;171;25;4
|
|
||||||
20;28;9;6;1;260;50;11;36;294.217;81;0;1;4;1;0;0;65;168;23;4
|
|
||||||
5;26;9;4;4;235;20;13;43;294.217;81;0;1;1;1;0;0;106;167;38;8
|
|
||||||
36;28;9;5;4;118;13;18;50;294.217;81;0;1;1;1;0;0;98;178;31;1
|
|
||||||
5;0;9;5;4;235;20;13;43;294.217;81;1;1;1;1;0;0;106;167;38;0
|
|
||||||
15;28;9;6;4;291;31;12;40;294.217;81;0;1;1;1;0;1;73;171;25;3
|
|
||||||
15;7;9;2;4;291;31;12;40;294.217;81;0;1;1;1;0;1;73;171;25;40
|
|
||||||
3;13;9;2;4;179;51;18;38;294.217;81;0;1;0;1;0;0;89;170;31;8
|
|
||||||
11;24;10;2;4;289;36;13;33;265.017;88;0;1;2;1;0;1;90;172;30;8
|
|
||||||
1;26;10;2;4;235;11;14;37;265.017;88;0;3;1;0;0;1;88;172;29;4
|
|
||||||
11;26;10;2;4;289;36;13;33;265.017;88;0;1;2;1;0;1;90;172;30;8
|
|
||||||
11;22;10;6;4;289;36;13;33;265.017;88;0;1;2;1;0;1;90;172;30;8
|
|
||||||
36;0;10;6;4;118;13;18;50;265.017;88;1;1;1;1;0;0;98;178;31;0
|
|
||||||
33;0;10;6;4;248;25;14;47;265.017;88;1;1;2;0;0;1;86;165;32;0
|
|
||||||
22;1;10;2;4;179;26;9;30;265.017;88;0;3;0;0;0;0;56;171;19;8
|
|
||||||
34;7;10;2;4;118;10;10;37;265.017;88;0;1;0;0;0;0;83;172;28;3
|
|
||||||
13;22;10;2;4;369;17;12;31;265.017;88;0;1;3;1;0;0;70;169;25;8
|
|
||||||
3;28;10;4;4;179;51;18;38;265.017;88;0;1;0;1;0;0;89;170;31;1
|
|
||||||
22;1;10;4;4;179;26;9;30;265.017;88;0;3;0;0;0;0;56;171;19;64
|
|
||||||
5;0;10;4;4;235;20;13;43;265.017;88;1;1;1;1;0;0;106;167;38;0
|
|
||||||
11;19;10;5;4;289;36;13;33;265.017;88;0;1;2;1;0;1;90;172;30;16
|
|
||||||
20;28;10;6;4;260;50;11;36;265.017;88;0;1;4;1;0;0;65;168;23;3
|
|
||||||
5;0;10;6;4;235;20;13;43;265.017;88;1;1;1;1;0;0;106;167;38;0
|
|
||||||
5;23;10;2;4;235;20;13;43;265.017;88;0;1;1;1;0;0;106;167;38;2
|
|
||||||
5;23;10;2;4;235;20;13;43;265.017;88;0;1;1;1;0;0;106;167;38;2
|
|
||||||
36;28;10;3;4;118;13;18;50;265.017;88;0;1;1;1;0;0;98;178;31;1
|
|
||||||
15;28;10;3;4;291;31;12;40;265.017;88;0;1;1;1;0;1;73;171;25;4
|
|
||||||
22;23;10;5;4;179;26;9;30;265.017;88;0;3;0;0;0;0;56;171;19;16
|
|
||||||
36;28;10;5;4;118;13;18;50;265.017;88;0;1;1;1;0;0;98;178;31;1
|
|
||||||
10;10;10;2;4;361;52;3;28;265.017;88;0;1;1;1;0;4;80;172;27;8
|
|
||||||
20;0;10;3;4;260;50;11;36;265.017;88;1;1;4;1;0;0;65;168;23;0
|
|
||||||
15;0;10;3;4;291;31;12;40;265.017;88;1;1;1;1;0;1;73;171;25;0
|
|
||||||
30;0;10;3;4;157;27;6;29;265.017;88;1;1;0;1;1;0;75;185;22;0
|
|
||||||
22;1;10;4;4;179;26;9;30;265.017;88;0;3;0;0;0;0;56;171;19;5
|
|
||||||
22;7;10;4;4;179;26;9;30;265.017;88;0;3;0;0;0;0;56;171;19;5
|
|
||||||
36;23;10;5;4;118;13;18;50;265.017;88;0;1;1;1;0;0;98;178;31;1
|
|
||||||
34;11;11;2;4;118;10;10;37;284.031;97;0;1;0;0;0;0;83;172;28;8
|
|
||||||
33;23;11;2;4;248;25;14;47;284.031;97;0;1;2;0;0;1;86;165;32;2
|
|
||||||
3;6;11;3;4;179;51;18;38;284.031;97;0;1;0;1;0;0;89;170;31;8
|
|
||||||
20;28;11;6;4;260;50;11;36;284.031;97;0;1;4;1;0;0;65;168;23;3
|
|
||||||
15;23;11;2;4;291;31;12;40;284.031;97;0;1;1;1;0;1;73;171;25;1
|
|
||||||
23;1;11;2;4;378;49;11;36;284.031;97;0;1;2;0;1;4;65;174;21;8
|
|
||||||
14;11;11;2;4;155;12;14;34;284.031;97;0;1;2;1;0;0;95;196;25;120
|
|
||||||
5;26;11;2;4;235;20;13;43;284.031;97;0;1;1;1;0;0;106;167;38;8
|
|
||||||
18;0;11;3;4;330;16;4;28;284.031;97;1;2;0;0;0;0;84;182;25;0
|
|
||||||
1;18;11;4;4;235;11;14;37;284.031;97;0;3;1;0;0;1;88;172;29;1
|
|
||||||
34;11;11;4;4;118;10;10;37;284.031;97;0;1;0;0;0;0;83;172;28;3
|
|
||||||
1;25;11;5;4;235;11;14;37;284.031;97;0;3;1;0;0;1;88;172;29;2
|
|
||||||
3;28;11;5;4;179;51;18;38;284.031;97;0;1;0;1;0;0;89;170;31;3
|
|
||||||
24;13;11;6;4;246;25;16;41;284.031;97;0;1;0;1;0;0;67;170;23;8
|
|
||||||
15;12;11;6;4;291;31;12;40;284.031;97;0;1;1;1;0;1;73;171;25;4
|
|
||||||
24;13;11;2;4;246;25;16;41;284.031;97;0;1;0;1;0;0;67;170;23;8
|
|
||||||
3;28;11;3;4;179;51;18;38;284.031;97;0;1;0;1;0;0;89;170;31;1
|
|
||||||
20;10;11;4;4;260;50;11;36;284.031;97;0;1;4;1;0;0;65;168;23;8
|
|
||||||
20;15;11;6;4;260;50;11;36;284.031;97;0;1;4;1;0;0;65;168;23;8
|
|
||||||
23;0;11;6;4;378;49;11;36;284.031;97;1;1;2;0;1;4;65;174;21;0
|
|
||||||
7;0;11;3;4;279;5;14;39;284.031;97;1;1;2;1;1;0;68;168;24;0
|
|
||||||
3;23;11;5;4;179;51;18;38;284.031;97;0;1;0;1;0;0;89;170;31;1
|
|
||||||
28;12;12;2;4;225;26;9;28;236.629;93;0;1;1;0;0;2;69;169;24;3
|
|
||||||
3;28;12;2;4;179;51;18;38;236.629;93;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;28;12;2;4;179;51;18;38;236.629;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
1;23;12;2;4;235;11;14;37;236.629;93;0;3;1;0;0;1;88;172;29;3
|
|
||||||
36;28;12;3;4;118;13;18;50;236.629;93;0;1;1;1;0;0;98;178;31;1
|
|
||||||
20;28;12;6;4;260;50;11;36;236.629;93;0;1;4;1;0;0;65;168;23;4
|
|
||||||
24;4;12;5;4;246;25;16;41;236.629;93;0;1;0;1;0;0;67;170;23;8
|
|
||||||
3;28;12;5;4;179;51;18;38;236.629;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
3;28;12;6;4;179;51;18;38;236.629;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
22;23;12;3;4;179;26;9;30;236.629;93;0;3;0;0;0;0;56;171;19;1
|
|
||||||
34;25;12;3;4;118;10;10;37;236.629;93;0;1;0;0;0;0;83;172;28;8
|
|
||||||
1;25;12;5;4;235;11;14;37;236.629;93;0;3;1;0;0;1;88;172;29;2
|
|
||||||
3;28;12;6;4;179;51;18;38;236.629;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
5;13;12;3;2;235;20;13;43;236.629;93;0;1;1;1;0;0;106;167;38;8
|
|
||||||
1;14;12;3;2;235;11;14;37;236.629;93;0;3;1;0;0;1;88;172;29;4
|
|
||||||
20;26;12;4;2;260;50;11;36;236.629;93;0;1;4;1;0;0;65;168;23;8
|
|
||||||
30;28;12;2;2;157;27;6;29;236.629;93;0;1;0;1;1;0;75;185;22;2
|
|
||||||
3;28;12;2;2;179;51;18;38;236.629;93;0;1;0;1;0;0;89;170;31;3
|
|
||||||
11;19;12;2;2;289;36;13;33;236.629;93;0;1;2;1;0;1;90;172;30;8
|
|
||||||
28;23;1;4;2;225;26;9;28;330.061;100;0;1;1;0;0;2;69;169;24;5
|
|
||||||
34;19;1;2;2;118;10;10;37;330.061;100;0;1;0;0;0;0;83;172;28;32
|
|
||||||
14;23;1;2;2;155;12;14;34;330.061;100;0;1;2;1;0;0;95;196;25;2
|
|
||||||
1;13;1;3;2;235;11;14;37;330.061;100;0;3;1;0;0;1;88;172;29;1
|
|
||||||
14;23;1;3;2;155;12;14;34;330.061;100;0;1;2;1;0;0;95;196;25;4
|
|
||||||
11;26;1;2;2;289;36;13;33;330.061;100;0;1;2;1;0;1;90;172;30;8
|
|
||||||
15;3;1;4;2;291;31;12;40;330.061;100;0;1;1;1;0;1;73;171;25;8
|
|
||||||
5;26;1;2;2;235;20;13;43;330.061;100;0;1;1;1;0;0;106;167;38;8
|
|
||||||
36;26;1;2;2;118;13;18;50;330.061;100;0;1;1;1;0;0;98;178;31;4
|
|
||||||
3;28;1;4;2;179;51;18;38;330.061;100;0;1;0;1;0;0;89;170;31;1
|
|
||||||
3;28;1;6;2;179;51;18;38;330.061;100;0;1;0;1;0;0;89;170;31;1
|
|
||||||
34;28;2;3;2;118;10;10;37;251.818;96;0;1;0;0;0;0;83;172;28;2
|
|
||||||
3;27;2;4;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
28;7;2;4;2;225;26;9;28;251.818;96;0;1;1;0;0;2;69;169;24;1
|
|
||||||
11;22;2;6;2;289;36;13;33;251.818;96;0;1;2;1;0;1;90;172;30;3
|
|
||||||
20;28;2;6;2;260;50;11;36;251.818;96;0;1;4;1;0;0;65;168;23;3
|
|
||||||
3;23;2;6;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;27;2;2;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;27;2;4;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;10;2;5;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;8
|
|
||||||
24;26;2;5;2;246;25;16;41;251.818;96;0;1;0;1;0;0;67;170;23;8
|
|
||||||
3;27;2;6;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
6;22;2;2;2;189;29;13;33;251.818;96;0;1;2;0;0;2;69;167;25;8
|
|
||||||
3;27;2;2;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
24;23;2;3;2;246;25;16;41;251.818;96;0;1;0;1;0;0;67;170;23;2
|
|
||||||
15;23;2;3;2;291;31;12;40;251.818;96;0;1;1;1;0;1;73;171;25;2
|
|
||||||
30;11;2;4;2;157;27;6;29;251.818;96;0;1;0;1;1;0;75;185;22;16
|
|
||||||
3;27;2;4;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;27;2;6;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
24;10;2;6;2;246;25;16;41;251.818;96;0;1;0;1;0;0;67;170;23;24
|
|
||||||
3;27;2;4;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;27;2;6;2;179;51;18;38;251.818;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
34;18;3;3;2;118;10;10;37;244.387;98;0;1;0;0;0;0;83;172;28;8
|
|
||||||
24;19;3;4;2;246;25;16;41;244.387;98;0;1;0;1;0;0;67;170;23;16
|
|
||||||
24;28;3;6;2;246;25;16;41;244.387;98;0;1;0;1;0;0;67;170;23;2
|
|
||||||
20;28;3;6;2;260;50;11;36;244.387;98;0;1;4;1;0;0;65;168;23;4
|
|
||||||
3;28;3;2;2;179;51;18;38;244.387;98;0;1;0;1;0;0;89;170;31;2
|
|
||||||
1;22;3;2;2;235;11;14;37;244.387;98;0;3;1;0;0;1;88;172;29;8
|
|
||||||
17;22;3;3;2;179;22;17;40;244.387;98;0;2;2;0;1;0;63;170;22;8
|
|
||||||
23;22;3;3;2;378;49;11;36;244.387;98;0;1;2;0;1;4;65;174;21;8
|
|
||||||
3;28;3;2;2;179;51;18;38;244.387;98;0;1;0;1;0;0;89;170;31;16
|
|
||||||
10;22;3;4;2;361;52;3;28;244.387;98;0;1;1;1;0;4;80;172;27;8
|
|
||||||
13;0;3;4;2;369;17;12;31;244.387;98;1;1;3;1;0;0;70;169;25;0
|
|
||||||
1;21;3;5;2;235;11;14;37;244.387;98;0;3;1;0;0;1;88;172;29;8
|
|
||||||
36;23;3;6;3;118;13;18;50;244.387;98;0;1;1;1;0;0;98;178;31;2
|
|
||||||
36;14;3;3;3;118;13;18;50;244.387;98;0;1;1;1;0;0;98;178;31;3
|
|
||||||
36;13;3;4;3;118;13;18;50;244.387;98;0;1;1;1;0;0;98;178;31;8
|
|
||||||
1;0;3;5;3;235;11;14;37;244.387;98;1;3;1;0;0;1;88;172;29;0
|
|
||||||
24;0;3;5;3;246;25;16;41;244.387;98;1;1;0;1;0;0;67;170;23;0
|
|
||||||
36;0;3;5;3;118;13;18;50;244.387;98;1;1;1;1;0;0;98;178;31;0
|
|
||||||
3;28;3;6;3;179;51;18;38;244.387;98;0;1;0;1;0;0;89;170;31;8
|
|
||||||
11;22;3;6;3;289;36;13;33;244.387;98;0;1;2;1;0;1;90;172;30;8
|
|
||||||
20;19;3;2;3;260;50;11;36;244.387;98;0;1;4;1;0;0;65;168;23;8
|
|
||||||
24;28;3;3;3;246;25;16;41;244.387;98;0;1;0;1;0;0;67;170;23;2
|
|
||||||
3;28;4;4;3;179;51;18;38;239.409;98;0;1;0;1;0;0;89;170;31;4
|
|
||||||
20;28;4;6;3;260;50;11;36;239.409;98;0;1;4;1;0;0;65;168;23;3
|
|
||||||
18;26;4;6;3;330;16;4;28;239.409;98;0;2;0;0;0;0;84;182;25;4
|
|
||||||
13;22;4;2;3;369;17;12;31;239.409;98;0;1;3;1;0;0;70;169;25;4
|
|
||||||
33;26;4;2;3;248;25;14;47;239.409;98;0;1;2;0;0;1;86;165;32;4
|
|
||||||
18;23;4;4;3;330;16;4;28;239.409;98;0;2;0;0;0;0;84;182;25;8
|
|
||||||
3;28;4;4;3;179;51;18;38;239.409;98;0;1;0;1;0;0;89;170;31;8
|
|
||||||
36;23;4;2;3;118;13;18;50;239.409;98;0;1;1;1;0;0;98;178;31;1
|
|
||||||
36;13;4;4;3;118;13;18;50;239.409;98;0;1;1;1;0;0;98;178;31;120
|
|
||||||
26;28;4;6;3;300;26;13;43;239.409;98;0;1;2;1;1;1;77;175;25;8
|
|
||||||
20;28;4;6;3;260;50;11;36;239.409;98;0;1;4;1;0;0;65;168;23;4
|
|
||||||
3;28;4;2;3;179;51;18;38;239.409;98;0;1;0;1;0;0;89;170;31;4
|
|
||||||
34;11;4;4;3;118;10;10;37;239.409;98;0;1;0;0;0;0;83;172;28;2
|
|
||||||
5;13;5;2;3;235;20;13;43;246.074;99;0;1;1;1;0;0;106;167;38;16
|
|
||||||
33;23;5;4;3;248;25;14;47;246.074;99;0;1;2;0;0;1;86;165;32;2
|
|
||||||
13;10;5;2;3;369;17;12;31;246.074;99;0;1;3;1;0;0;70;169;25;8
|
|
||||||
22;23;5;4;3;179;26;9;30;246.074;99;0;3;0;0;0;0;56;171;19;3
|
|
||||||
3;28;5;4;3;179;51;18;38;246.074;99;0;1;0;1;0;0;89;170;31;4
|
|
||||||
10;23;5;5;3;361;52;3;28;246.074;99;0;1;1;1;0;4;80;172;27;1
|
|
||||||
20;28;5;6;3;260;50;11;36;246.074;99;0;1;4;1;0;0;65;168;23;3
|
|
||||||
17;11;5;2;3;179;22;17;40;246.074;99;0;2;2;0;1;0;63;170;22;2
|
|
||||||
17;8;5;2;3;179;22;17;40;246.074;99;0;2;2;0;1;0;63;170;22;3
|
|
||||||
9;18;5;4;3;228;14;16;58;246.074;99;0;1;2;0;0;1;65;172;22;8
|
|
||||||
28;25;5;4;3;225;26;9;28;246.074;99;0;1;1;0;0;2;69;169;24;3
|
|
||||||
18;13;5;6;3;330;16;4;28;246.074;99;0;2;0;0;0;0;84;182;25;8
|
|
||||||
22;25;5;2;3;179;26;9;30;246.074;99;0;3;0;0;0;0;56;171;19;2
|
|
||||||
34;28;5;2;3;118;10;10;37;246.074;99;0;1;0;0;0;0;83;172;28;1
|
|
||||||
1;1;5;2;3;235;11;14;37;246.074;99;0;3;1;0;0;1;88;172;29;8
|
|
||||||
22;23;5;4;3;179;26;9;30;246.074;99;0;3;0;0;0;0;56;171;19;3
|
|
||||||
34;23;6;2;3;118;10;10;37;253.957;95;0;1;0;0;0;0;83;172;28;3
|
|
||||||
3;28;6;2;3;179;51;18;38;253.957;95;0;1;0;1;0;0;89;170;31;3
|
|
||||||
34;28;6;3;3;118;10;10;37;253.957;95;0;1;0;0;0;0;83;172;28;2
|
|
||||||
28;23;6;5;3;225;26;9;28;253.957;95;0;1;1;0;0;2;69;169;24;4
|
|
||||||
20;28;6;6;3;260;50;11;36;253.957;95;0;1;4;1;0;0;65;168;23;4
|
|
||||||
3;0;6;6;3;179;51;18;38;253.957;95;1;1;0;1;0;0;89;170;31;0
|
|
||||||
15;13;6;2;3;291;31;12;40;253.957;95;0;1;1;1;0;1;73;171;25;40
|
|
||||||
3;28;6;2;3;179;51;18;38;253.957;95;0;1;0;1;0;0;89;170;31;24
|
|
||||||
24;28;6;3;3;246;25;16;41;253.957;95;0;1;0;1;0;0;67;170;23;3
|
|
||||||
3;28;6;2;3;179;51;18;38;253.957;95;0;1;0;1;0;0;89;170;31;4
|
|
||||||
5;26;6;3;3;235;20;13;43;253.957;95;0;1;1;1;0;0;106;167;38;8
|
|
||||||
3;28;6;2;1;179;51;18;38;253.957;95;0;1;0;1;0;0;89;170;31;2
|
|
||||||
28;23;6;4;1;225;26;9;28;253.957;95;0;1;1;0;0;2;69;169;24;2
|
|
||||||
36;23;6;4;1;118;13;18;50;253.957;95;0;1;1;1;0;0;98;178;31;2
|
|
||||||
3;5;6;4;1;179;51;18;38;253.957;95;0;1;0;1;0;0;89;170;31;8
|
|
||||||
22;21;6;4;1;179;26;9;30;253.957;95;0;3;0;0;0;0;56;171;19;2
|
|
||||||
24;28;6;6;1;246;25;16;41;253.957;95;0;1;0;1;0;0;67;170;23;2
|
|
||||||
18;11;6;3;1;330;16;4;28;253.957;95;0;2;0;0;0;0;84;182;25;1
|
|
||||||
1;13;6;3;1;235;11;14;37;253.957;95;0;3;1;0;0;1;88;172;29;8
|
|
||||||
22;23;7;5;1;179;26;9;30;230.290;92;0;3;0;0;0;0;56;171;19;2
|
|
||||||
28;25;7;5;1;225;26;9;28;230.290;92;0;1;1;0;0;2;69;169;24;4
|
|
||||||
20;13;7;6;1;260;50;11;36;230.290;92;0;1;4;1;0;0;65;168;23;8
|
|
||||||
21;7;7;2;1;268;11;8;33;230.290;92;0;2;0;0;0;0;79;178;25;8
|
|
||||||
18;25;7;6;1;330;16;4;28;230.290;92;0;2;0;0;0;0;84;182;25;8
|
|
||||||
34;26;7;6;1;118;10;10;37;230.290;92;0;1;0;0;0;0;83;172;28;8
|
|
||||||
20;26;7;2;1;260;50;11;36;230.290;92;0;1;4;1;0;0;65;168;23;4
|
|
||||||
34;28;7;3;1;118;10;10;37;230.290;92;0;1;0;0;0;0;83;172;28;8
|
|
||||||
26;15;7;2;1;300;26;13;43;230.290;92;0;1;2;1;1;1;77;175;25;8
|
|
||||||
2;23;7;2;1;235;29;12;48;230.290;92;0;1;1;0;1;5;88;163;33;1
|
|
||||||
24;28;7;3;1;246;25;16;41;230.290;92;0;1;0;1;0;0;67;170;23;2
|
|
||||||
28;9;7;3;1;225;26;9;28;230.290;92;0;1;1;0;0;2;69;169;24;112
|
|
||||||
3;28;7;3;1;179;51;18;38;230.290;92;0;1;0;1;0;0;89;170;31;1
|
|
||||||
36;23;7;6;1;118;13;18;50;230.290;92;0;1;1;1;0;0;98;178;31;1
|
|
||||||
10;22;7;6;1;361;52;3;28;230.290;92;0;1;1;1;0;4;80;172;27;8
|
|
||||||
11;22;7;2;1;289;36;13;33;230.290;92;0;1;2;1;0;1;90;172;30;8
|
|
||||||
5;26;7;2;1;235;20;13;43;230.290;92;0;1;1;1;0;0;106;167;38;8
|
|
||||||
24;28;7;3;1;246;25;16;41;230.290;92;0;1;0;1;0;0;67;170;23;2
|
|
||||||
15;28;7;5;1;291;31;12;40;230.290;92;0;1;1;1;0;1;73;171;25;1
|
|
||||||
7;23;7;5;1;279;5;14;39;230.290;92;0;1;2;1;1;0;68;168;24;2
|
|
||||||
3;25;8;5;1;179;51;18;38;249.797;93;0;1;0;1;0;0;89;170;31;4
|
|
||||||
17;25;8;2;1;179;22;17;40;249.797;93;0;2;2;0;1;0;63;170;22;1
|
|
||||||
24;28;8;3;1;246;25;16;41;249.797;93;0;1;0;1;0;0;67;170;23;4
|
|
||||||
34;28;8;3;1;118;10;10;37;249.797;93;0;1;0;0;0;0;83;172;28;4
|
|
||||||
11;26;8;3;1;289;36;13;33;249.797;93;0;1;2;1;0;1;90;172;30;8
|
|
||||||
5;26;8;3;1;235;20;13;43;249.797;93;0;1;1;1;0;0;106;167;38;8
|
|
||||||
15;28;8;5;1;291;31;12;40;249.797;93;0;1;1;1;0;1;73;171;25;4
|
|
||||||
3;25;8;2;1;179;51;18;38;249.797;93;0;1;0;1;0;0;89;170;31;4
|
|
||||||
17;25;8;3;1;179;22;17;40;249.797;93;0;2;2;0;1;0;63;170;22;8
|
|
||||||
18;23;8;5;1;330;16;4;28;249.797;93;0;2;0;0;0;0;84;182;25;16
|
|
||||||
1;23;8;3;1;235;11;14;37;249.797;93;0;3;1;0;0;1;88;172;29;4
|
|
||||||
24;28;8;3;1;246;25;16;41;249.797;93;0;1;0;1;0;0;67;170;23;1
|
|
||||||
34;28;8;3;1;118;10;10;37;249.797;93;0;1;0;0;0;0;83;172;28;5
|
|
||||||
15;28;8;5;1;291;31;12;40;249.797;93;0;1;1;1;0;1;73;171;25;2
|
|
||||||
20;28;8;2;1;260;50;11;36;249.797;93;0;1;4;1;0;0;65;168;23;3
|
|
||||||
24;28;9;3;1;246;25;16;41;261.756;87;0;1;0;1;0;0;67;170;23;1
|
|
||||||
24;28;9;3;1;246;25;16;41;261.756;87;0;1;0;1;0;0;67;170;23;1
|
|
||||||
34;28;9;3;1;118;10;10;37;261.756;87;0;1;0;0;0;0;83;172;28;3
|
|
||||||
14;23;9;3;1;155;12;14;34;261.756;87;0;1;2;1;0;0;95;196;25;2
|
|
||||||
15;28;9;5;1;291;31;12;40;261.756;87;0;1;1;1;0;1;73;171;25;2
|
|
||||||
22;23;9;6;1;179;26;9;30;261.756;87;0;3;0;0;0;0;56;171;19;8
|
|
||||||
33;23;9;6;1;248;25;14;47;261.756;87;0;1;2;0;0;1;86;165;32;1
|
|
||||||
3;23;9;2;1;179;51;18;38;261.756;87;0;1;0;1;0;0;89;170;31;4
|
|
||||||
28;23;9;4;1;225;26;9;28;261.756;87;0;1;1;0;0;2;69;169;24;1
|
|
||||||
22;23;9;2;1;179;26;9;30;261.756;87;0;3;0;0;0;0;56;171;19;2
|
|
||||||
13;23;9;3;4;369;17;12;31;261.756;87;0;1;3;1;0;0;70;169;25;8
|
|
||||||
10;22;9;3;4;361;52;3;28;261.756;87;0;1;1;1;0;4;80;172;27;8
|
|
||||||
32;4;10;5;4;289;48;29;49;284.853;91;0;1;0;0;0;2;108;172;36;1
|
|
||||||
25;11;10;5;4;235;16;8;32;284.853;91;0;3;0;0;0;0;75;178;25;3
|
|
||||||
24;26;10;6;4;246;25;16;41;284.853;91;0;1;0;1;0;0;67;170;23;8
|
|
||||||
32;14;10;4;4;289;48;29;49;284.853;91;0;1;0;0;0;2;108;172;36;3
|
|
||||||
15;28;10;4;4;291;31;12;40;284.853;91;0;1;1;1;0;1;73;171;25;2
|
|
||||||
34;23;10;3;4;118;10;10;37;284.853;91;0;1;0;0;0;0;83;172;28;2
|
|
||||||
32;23;10;5;4;289;48;29;49;284.853;91;0;1;0;0;0;2;108;172;36;2
|
|
||||||
15;23;10;6;4;291;31;12;40;284.853;91;0;1;1;1;0;1;73;171;25;1
|
|
||||||
28;23;10;3;4;225;26;9;28;284.853;91;0;1;1;0;0;2;69;169;24;2
|
|
||||||
13;23;10;3;4;369;17;12;31;284.853;91;0;1;3;1;0;0;70;169;25;8
|
|
||||||
13;23;10;3;4;369;17;12;31;284.853;91;0;1;3;1;0;0;70;169;25;3
|
|
||||||
28;23;10;3;4;225;26;9;28;284.853;91;0;1;1;0;0;2;69;169;24;4
|
|
||||||
13;26;10;3;4;369;17;12;31;284.853;91;0;1;3;1;0;0;70;169;25;8
|
|
||||||
3;28;10;4;4;179;51;18;38;284.853;91;0;1;0;1;0;0;89;170;31;3
|
|
||||||
9;1;10;4;4;228;14;16;58;284.853;91;0;1;2;0;0;1;65;172;22;1
|
|
||||||
15;23;10;4;4;291;31;12;40;284.853;91;0;1;1;1;0;1;73;171;25;1
|
|
||||||
13;10;10;5;4;369;17;12;31;284.853;91;0;1;3;1;0;0;70;169;25;8
|
|
||||||
28;13;10;5;4;225;26;9;28;284.853;91;0;1;1;0;0;2;69;169;24;1
|
|
||||||
13;10;10;6;4;369;17;12;31;284.853;91;0;1;3;1;0;0;70;169;25;8
|
|
||||||
28;10;10;6;4;225;26;9;28;284.853;91;0;1;1;0;0;2;69;169;24;3
|
|
||||||
6;23;10;2;4;189;29;13;33;284.853;91;0;1;2;0;0;2;69;167;25;8
|
|
||||||
25;6;10;2;4;235;16;8;32;284.853;91;0;3;0;0;0;0;75;178;25;8
|
|
||||||
33;10;10;2;4;248;25;14;47;284.853;91;0;1;2;0;0;1;86;165;32;8
|
|
||||||
28;0;10;2;4;225;26;9;28;284.853;91;1;1;1;0;0;2;69;169;24;0
|
|
||||||
28;13;10;3;4;225;26;9;28;284.853;91;0;1;1;0;0;2;69;169;24;3
|
|
||||||
3;21;11;3;4;179;51;18;38;268.519;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
34;28;11;4;4;118;10;10;37;268.519;93;0;1;0;0;0;0;83;172;28;3
|
|
||||||
18;2;11;4;4;330;16;4;28;268.519;93;0;2;0;0;0;0;84;182;25;24
|
|
||||||
3;28;11;6;4;179;51;18;38;268.519;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
34;9;11;3;4;118;10;10;37;268.519;93;0;1;0;0;0;0;83;172;28;8
|
|
||||||
11;24;11;4;4;289;36;13;33;268.519;93;0;1;2;1;0;1;90;172;30;8
|
|
||||||
25;1;11;6;4;235;16;8;32;268.519;93;0;3;0;0;0;0;75;178;25;8
|
|
||||||
28;23;11;6;4;225;26;9;28;268.519;93;0;1;1;0;0;2;69;169;24;4
|
|
||||||
10;22;11;3;4;361;52;3;28;268.519;93;0;1;1;1;0;4;80;172;27;8
|
|
||||||
15;28;11;4;4;291;31;12;40;268.519;93;0;1;1;1;0;1;73;171;25;2
|
|
||||||
34;13;11;5;4;118;10;10;37;268.519;93;0;1;0;0;0;0;83;172;28;2
|
|
||||||
28;14;11;5;4;225;26;9;28;268.519;93;0;1;1;0;0;2;69;169;24;3
|
|
||||||
3;28;11;2;4;179;51;18;38;268.519;93;0;1;0;1;0;0;89;170;31;1
|
|
||||||
34;23;11;2;4;118;10;10;37;268.519;93;0;1;0;0;0;0;83;172;28;8
|
|
||||||
34;8;11;3;4;118;10;10;37;268.519;93;0;1;0;0;0;0;83;172;28;8
|
|
||||||
28;23;11;3;4;225;26;9;28;268.519;93;0;1;1;0;0;2;69;169;24;2
|
|
||||||
15;0;11;3;4;291;31;12;40;268.519;93;1;1;1;1;0;1;73;171;25;0
|
|
||||||
11;0;11;4;4;289;36;13;33;268.519;93;1;1;2;1;0;1;90;172;30;0
|
|
||||||
33;14;11;5;4;248;25;14;47;268.519;93;0;1;2;0;0;1;86;165;32;4
|
|
||||||
5;0;11;5;4;235;20;13;43;268.519;93;1;1;1;1;0;0;106;167;38;0
|
|
||||||
28;23;11;6;4;225;26;9;28;268.519;93;0;1;1;0;0;2;69;169;24;2
|
|
||||||
13;26;11;6;4;369;17;12;31;268.519;93;0;1;3;1;0;0;70;169;25;8
|
|
||||||
10;28;11;2;4;361;52;3;28;268.519;93;0;1;1;1;0;4;80;172;27;2
|
|
||||||
3;13;12;3;4;179;51;18;38;280.549;98;0;1;0;1;0;0;89;170;31;32
|
|
||||||
15;28;12;4;4;291;31;12;40;280.549;98;0;1;1;1;0;1;73;171;25;1
|
|
||||||
28;23;12;4;4;225;26;9;28;280.549;98;0;1;1;0;0;2;69;169;24;3
|
|
||||||
22;13;12;6;4;179;26;9;30;280.549;98;0;3;0;0;0;0;56;171;19;1
|
|
||||||
28;23;12;6;4;225;26;9;28;280.549;98;0;1;1;0;0;2;69;169;24;3
|
|
||||||
28;23;12;4;4;225;26;9;28;280.549;98;0;1;1;0;0;2;69;169;24;3
|
|
||||||
10;14;12;5;4;361;52;3;28;280.549;98;0;1;1;1;0;4;80;172;27;4
|
|
||||||
17;18;12;6;4;179;22;17;40;280.549;98;0;2;2;0;1;0;63;170;22;2
|
|
||||||
5;26;12;6;4;235;20;13;43;280.549;98;0;1;1;1;0;0;106;167;38;8
|
|
||||||
12;18;12;2;4;233;51;1;31;280.549;98;0;2;1;1;0;8;68;178;21;8
|
|
||||||
22;13;12;3;4;179;26;9;30;280.549;98;0;3;0;0;0;0;56;171;19;16
|
|
||||||
28;23;12;3;4;225;26;9;28;280.549;98;0;1;1;0;0;2;69;169;24;2
|
|
||||||
28;23;12;5;4;225;26;9;28;280.549;98;0;1;1;0;0;2;69;169;24;3
|
|
||||||
28;23;12;2;4;225;26;9;28;280.549;98;0;1;1;0;0;2;69;169;24;2
|
|
||||||
14;18;12;3;2;155;12;14;34;280.549;98;0;1;2;1;0;0;95;196;25;80
|
|
||||||
22;12;1;2;2;179;26;9;30;313.532;96;0;3;0;0;0;0;56;171;19;24
|
|
||||||
22;12;1;5;2;179;26;9;30;313.532;96;0;3;0;0;0;0;56;171;19;16
|
|
||||||
17;25;1;5;2;179;22;17;40;313.532;96;0;2;2;0;1;0;63;170;22;2
|
|
||||||
17;25;1;6;2;179;22;17;40;313.532;96;0;2;2;0;1;0;63;170;22;2
|
|
||||||
22;13;1;2;2;179;26;9;30;313.532;96;0;3;0;0;0;0;56;171;19;3
|
|
||||||
17;25;1;4;2;179;22;17;40;313.532;96;0;2;2;0;1;0;63;170;22;2
|
|
||||||
32;10;1;5;2;289;48;29;49;313.532;96;0;1;0;0;0;2;108;172;36;8
|
|
||||||
17;18;1;6;2;179;22;17;40;313.532;96;0;2;2;0;1;0;63;170;22;3
|
|
||||||
22;27;1;2;2;179;26;9;30;313.532;96;0;3;0;0;0;0;56;171;19;2
|
|
||||||
14;18;1;3;2;155;12;14;34;313.532;96;0;1;2;1;0;0;95;196;25;8
|
|
||||||
22;27;1;4;2;179;26;9;30;313.532;96;0;3;0;0;0;0;56;171;19;2
|
|
||||||
3;27;1;4;2;179;51;18;38;313.532;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
11;13;1;4;2;289;36;13;33;313.532;96;0;1;2;1;0;1;90;172;30;8
|
|
||||||
3;27;1;5;2;179;51;18;38;313.532;96;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;27;1;6;2;179;51;18;38;313.532;96;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;13;2;3;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;8
|
|
||||||
28;23;2;3;2;225;26;9;28;264.249;97;0;1;1;0;0;2;69;169;24;3
|
|
||||||
33;1;2;4;2;248;25;14;47;264.249;97;0;1;2;0;0;1;86;165;32;8
|
|
||||||
3;27;2;4;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
28;28;2;5;2;225;26;9;28;264.249;97;0;1;1;0;0;2;69;169;24;3
|
|
||||||
3;27;2;5;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
22;27;2;5;2;179;26;9;30;264.249;97;0;3;0;0;0;0;56;171;19;2
|
|
||||||
29;28;2;6;2;225;15;15;41;264.249;97;0;4;2;1;0;2;94;182;28;2
|
|
||||||
3;27;2;6;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
12;19;2;2;2;233;51;1;31;264.249;97;0;2;1;1;0;8;68;178;21;2
|
|
||||||
3;27;2;2;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
28;7;2;3;2;225;26;9;28;264.249;97;0;1;1;0;0;2;69;169;24;8
|
|
||||||
3;27;2;4;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;27;2;5;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;3
|
|
||||||
28;25;2;5;2;225;26;9;28;264.249;97;0;1;1;0;0;2;69;169;24;3
|
|
||||||
22;13;2;5;2;179;26;9;30;264.249;97;0;3;0;0;0;0;56;171;19;2
|
|
||||||
17;23;2;6;2;179;22;17;40;264.249;97;0;2;2;0;1;0;63;170;22;2
|
|
||||||
3;27;2;6;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;3
|
|
||||||
12;12;2;4;2;233;51;1;31;264.249;97;0;2;1;1;0;8;68;178;21;3
|
|
||||||
22;27;2;4;2;179;26;9;30;264.249;97;0;3;0;0;0;0;56;171;19;2
|
|
||||||
3;27;2;4;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;13;2;5;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;8
|
|
||||||
3;27;2;6;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
14;25;2;2;2;155;12;14;34;264.249;97;0;1;2;1;0;0;95;196;25;5
|
|
||||||
25;25;2;2;2;235;16;8;32;264.249;97;0;3;0;0;0;0;75;178;25;3
|
|
||||||
3;27;2;2;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
28;7;2;2;2;225;26;9;28;264.249;97;0;1;1;0;0;2;69;169;24;2
|
|
||||||
3;27;2;3;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
33;23;2;3;2;248;25;14;47;264.249;97;0;1;2;0;0;1;86;165;32;2
|
|
||||||
28;25;2;3;2;225;26;9;28;264.249;97;0;1;1;0;0;2;69;169;24;2
|
|
||||||
3;27;2;4;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;27;2;5;2;179;51;18;38;264.249;97;0;1;0;1;0;0;89;170;31;2
|
|
||||||
25;25;2;6;2;235;16;8;32;264.249;97;0;3;0;0;0;0;75;178;25;2
|
|
||||||
3;27;3;2;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;2
|
|
||||||
33;23;3;2;2;248;25;14;47;222.196;99;0;1;2;0;0;1;86;165;32;2
|
|
||||||
9;25;3;3;2;228;14;16;58;222.196;99;0;1;2;0;0;1;65;172;22;3
|
|
||||||
33;25;3;3;2;248;25;14;47;222.196;99;0;1;2;0;0;1;86;165;32;3
|
|
||||||
9;12;3;3;2;228;14;16;58;222.196;99;0;1;2;0;0;1;65;172;22;112
|
|
||||||
3;27;3;4;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;2
|
|
||||||
28;27;3;5;2;225;26;9;28;222.196;99;0;1;1;0;0;2;69;169;24;2
|
|
||||||
3;27;3;5;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
28;25;3;5;2;225;26;9;28;222.196;99;0;1;1;0;0;2;69;169;24;2
|
|
||||||
22;27;3;6;2;179;26;9;30;222.196;99;0;3;0;0;0;0;56;171;19;3
|
|
||||||
25;25;3;2;2;235;16;8;32;222.196;99;0;3;0;0;0;0;75;178;25;3
|
|
||||||
10;19;3;2;2;361;52;3;28;222.196;99;0;1;1;1;0;4;80;172;27;8
|
|
||||||
3;13;3;3;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;8
|
|
||||||
3;27;3;4;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;2
|
|
||||||
3;27;3;5;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
22;27;3;6;2;179;26;9;30;222.196;99;0;3;0;0;0;0;56;171;19;2
|
|
||||||
3;10;3;2;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;4
|
|
||||||
33;13;3;2;2;248;25;14;47;222.196;99;0;1;2;0;0;1;86;165;32;2
|
|
||||||
3;27;3;2;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
28;7;3;2;2;225;26;9;28;222.196;99;0;1;1;0;0;2;69;169;24;8
|
|
||||||
3;27;3;3;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;2
|
|
||||||
11;23;3;4;2;289;36;13;33;222.196;99;0;1;2;1;0;1;90;172;30;8
|
|
||||||
9;25;3;4;2;228;14;16;58;222.196;99;0;1;2;0;0;1;65;172;22;2
|
|
||||||
3;27;3;4;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;2
|
|
||||||
33;23;3;5;2;248;25;14;47;222.196;99;0;1;2;0;0;1;86;165;32;3
|
|
||||||
3;27;3;5;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
22;23;3;6;2;179;26;9;30;222.196;99;0;3;0;0;0;0;56;171;19;2
|
|
||||||
3;27;3;6;2;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;27;3;3;3;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
16;23;3;4;3;118;15;24;46;222.196;99;0;1;2;1;1;0;75;175;25;8
|
|
||||||
14;13;3;4;3;155;12;14;34;222.196;99;0;1;2;1;0;0;95;196;25;24
|
|
||||||
3;27;3;4;3;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
3;27;3;5;3;179;51;18;38;222.196;99;0;1;0;1;0;0;89;170;31;3
|
|
||||||
22;13;3;2;3;179;26;9;30;222.196;99;0;3;0;0;0;0;56;171;19;2
|
|
||||||
11;19;3;2;3;289;36;13;33;222.196;99;0;1;2;1;0;1;90;172;30;104
|
|
||||||
13;22;3;4;3;369;17;12;31;222.196;99;0;1;3;1;0;0;70;169;25;8
|
|
||||||
28;13;4;2;3;225;26;9;28;246.288;91;0;1;1;0;0;2;69;169;24;8
|
|
||||||
34;10;4;2;3;118;10;10;37;246.288;91;0;1;0;0;0;0;83;172;28;8
|
|
||||||
10;19;4;3;3;361;52;3;28;246.288;91;0;1;1;1;0;4;80;172;27;8
|
|
||||||
33;19;4;4;3;248;25;14;47;246.288;91;0;1;2;0;0;1;86;165;32;8
|
|
||||||
6;13;4;5;3;189;29;13;33;246.288;91;0;1;2;0;0;2;69;167;25;8
|
|
||||||
22;27;4;6;3;179;26;9;30;246.288;91;0;3;0;0;0;0;56;171;19;2
|
|
||||||
13;7;4;2;3;369;17;12;31;246.288;91;0;1;3;1;0;0;70;169;25;24
|
|
||||||
17;16;4;3;3;179;22;17;40;246.288;91;0;2;2;0;1;0;63;170;22;2
|
|
||||||
36;23;4;3;3;118;13;18;50;246.288;91;0;1;1;1;0;0;98;178;31;3
|
|
||||||
10;23;4;3;3;361;52;3;28;246.288;91;0;1;1;1;0;4;80;172;27;2
|
|
||||||
34;10;4;4;3;118;10;10;37;246.288;91;0;1;0;0;0;0;83;172;28;2
|
|
||||||
1;22;4;6;3;235;11;14;37;246.288;91;0;3;1;0;0;1;88;172;29;8
|
|
||||||
22;27;4;6;3;179;26;9;30;246.288;91;0;3;0;0;0;0;56;171;19;2
|
|
||||||
28;19;4;2;3;225;26;9;28;246.288;91;0;1;1;0;0;2;69;169;24;8
|
|
||||||
25;16;4;3;3;235;16;8;32;246.288;91;0;3;0;0;0;0;75;178;25;3
|
|
||||||
22;27;4;6;3;179;26;9;30;246.288;91;0;3;0;0;0;0;56;171;19;2
|
|
||||||
14;28;4;3;3;155;12;14;34;246.288;91;0;1;2;1;0;0;95;196;25;4
|
|
||||||
28;19;4;5;3;225;26;9;28;246.288;91;0;1;1;0;0;2;69;169;24;8
|
|
||||||
36;14;4;5;3;118;13;18;50;246.288;91;0;1;1;1;0;0;98;178;31;2
|
|
||||||
22;27;4;6;3;179;26;9;30;246.288;91;0;3;0;0;0;0;56;171;19;2
|
|
||||||
1;22;5;2;3;235;11;14;37;237.656;99;0;3;1;0;0;1;88;172;29;8
|
|
||||||
29;19;5;4;3;225;15;15;41;237.656;99;0;4;2;1;0;2;94;182;28;3
|
|
||||||
25;28;5;4;3;235;16;8;32;237.656;99;0;3;0;0;0;0;75;178;25;2
|
|
||||||
34;8;5;4;3;118;10;10;37;237.656;99;0;1;0;0;0;0;83;172;28;3
|
|
||||||
5;26;5;4;3;235;20;13;43;237.656;99;0;1;1;1;0;0;106;167;38;8
|
|
||||||
22;13;5;5;3;179;26;9;30;237.656;99;0;3;0;0;0;0;56;171;19;1
|
|
||||||
15;28;5;5;3;291;31;12;40;237.656;99;0;1;1;1;0;1;73;171;25;2
|
|
||||||
29;14;5;5;3;225;15;15;41;237.656;99;0;4;2;1;0;2;94;182;28;8
|
|
||||||
26;19;5;6;3;300;26;13;43;237.656;99;0;1;2;1;1;1;77;175;25;64
|
|
||||||
29;22;5;6;3;225;15;15;41;237.656;99;0;4;2;1;0;2;94;182;28;8
|
|
||||||
22;27;5;6;3;179;26;9;30;237.656;99;0;3;0;0;0;0;56;171;19;2
|
|
||||||
36;23;5;2;3;118;13;18;50;237.656;99;0;1;1;1;0;0;98;178;31;2
|
|
||||||
36;5;5;3;3;118;13;18;50;237.656;99;0;1;1;1;0;0;98;178;31;3
|
|
||||||
34;28;5;3;3;118;10;10;37;237.656;99;0;1;0;0;0;0;83;172;28;1
|
|
||||||
36;0;5;3;3;118;13;18;50;237.656;99;1;1;1;1;0;0;98;178;31;0
|
|
||||||
22;27;5;4;3;179;26;9;30;237.656;99;0;3;0;0;0;0;56;171;19;2
|
|
||||||
23;0;5;4;3;378;49;11;36;237.656;99;1;1;2;0;1;4;65;174;21;0
|
|
||||||
17;16;5;6;3;179;22;17;40;237.656;99;0;2;2;0;1;0;63;170;22;1
|
|
||||||
14;10;5;2;3;155;12;14;34;237.656;99;0;1;2;1;0;0;95;196;25;48
|
|
||||||
25;10;5;2;3;235;16;8;32;237.656;99;0;3;0;0;0;0;75;178;25;8
|
|
||||||
15;22;5;4;3;291;31;12;40;237.656;99;0;1;1;1;0;1;73;171;25;8
|
|
||||||
17;10;5;4;3;179;22;17;40;237.656;99;0;2;2;0;1;0;63;170;22;8
|
|
||||||
28;6;5;4;3;225;26;9;28;237.656;99;0;1;1;0;0;2;69;169;24;3
|
|
||||||
18;10;5;5;3;330;16;4;28;237.656;99;0;2;0;0;0;0;84;182;25;8
|
|
||||||
25;23;5;5;3;235;16;8;32;237.656;99;0;3;0;0;0;0;75;178;25;2
|
|
||||||
15;28;5;5;3;291;31;12;40;237.656;99;0;1;1;1;0;1;73;171;25;2
|
|
||||||
22;27;5;6;3;179;26;9;30;237.656;99;0;3;0;0;0;0;56;171;19;2
|
|
||||||
10;7;5;2;3;361;52;3;28;237.656;99;0;1;1;1;0;4;80;172;27;8
|
|
||||||
14;23;5;4;3;155;12;14;34;237.656;99;0;1;2;1;0;0;95;196;25;2
|
|
||||||
17;25;5;6;3;179;22;17;40;237.656;99;0;2;2;0;1;0;63;170;22;8
|
|
||||||
14;10;5;6;3;155;12;14;34;237.656;99;0;1;2;1;0;0;95;196;25;8
|
|
||||||
28;11;5;2;3;225;26;9;28;237.656;99;0;1;1;0;0;2;69;169;24;1
|
|
||||||
16;7;6;4;3;118;15;24;46;275.089;96;0;1;2;1;1;0;75;175;25;8
|
|
||||||
22;27;6;4;3;179;26;9;30;275.089;96;0;3;0;0;0;0;56;171;19;3
|
|
||||||
34;26;6;6;3;118;10;10;37;275.089;96;0;1;0;0;0;0;83;172;28;8
|
|
||||||
34;10;6;4;3;118;10;10;37;275.089;96;0;1;0;0;0;0;83;172;28;8
|
|
||||||
23;22;6;5;3;378;49;11;36;275.089;96;0;1;2;0;1;4;65;174;21;8
|
|
||||||
36;19;6;5;3;118;13;18;50;275.089;96;0;1;1;1;0;0;98;178;31;24
|
|
||||||
12;19;6;6;3;233;51;1;31;275.089;96;0;2;1;1;0;8;68;178;21;8
|
|
||||||
22;27;6;6;3;179;26;9;30;275.089;96;0;3;0;0;0;0;56;171;19;2
|
|
||||||
2;0;6;2;3;235;29;12;48;275.089;96;1;1;1;0;1;5;88;163;33;0
|
|
||||||
21;0;6;2;3;268;11;8;33;275.089;96;1;2;0;0;0;0;79;178;25;0
|
|
||||||
36;19;6;5;3;118;13;18;50;275.089;96;0;1;1;1;0;0;98;178;31;3
|
|
||||||
22;13;6;5;3;179;26;9;30;275.089;96;0;3;0;0;0;0;56;171;19;2
|
|
||||||
15;28;6;5;3;291;31;12;40;275.089;96;0;1;1;1;0;1;73;171;25;2
|
|
||||||
22;13;6;2;1;179;26;9;30;275.089;96;0;3;0;0;0;0;56;171;19;3
|
|
||||||
34;25;6;2;1;118;10;10;37;275.089;96;0;1;0;0;0;0;83;172;28;3
|
|
||||||
12;22;6;5;1;233;51;1;31;275.089;96;0;2;1;1;0;8;68;178;21;8
|
|
||||||
34;8;6;6;1;118;10;10;37;275.089;96;0;1;0;0;0;0;83;172;28;2
|
|
||||||
34;10;6;4;1;118;10;10;37;275.089;96;0;1;0;0;0;0;83;172;28;3
|
|
||||||
12;22;6;4;1;233;51;1;31;275.089;96;0;2;1;1;0;8;68;178;21;3
|
|
||||||
5;26;7;4;1;235;20;13;43;264.604;93;0;1;1;1;0;0;106;167;38;4
|
|
||||||
12;19;7;6;1;233;51;1;31;264.604;93;0;2;1;1;0;8;68;178;21;2
|
|
||||||
9;6;7;2;1;228;14;16;58;264.604;93;0;1;2;0;0;1;65;172;22;8
|
|
||||||
34;28;7;2;1;118;10;10;37;264.604;93;0;1;0;0;0;0;83;172;28;4
|
|
||||||
9;6;7;3;1;228;14;16;58;264.604;93;0;1;2;0;0;1;65;172;22;120
|
|
||||||
6;22;7;3;1;189;29;13;33;264.604;93;0;1;2;0;0;2;69;167;25;16
|
|
||||||
34;23;7;4;1;118;10;10;37;264.604;93;0;1;0;0;0;0;83;172;28;2
|
|
||||||
10;22;7;4;1;361;52;3;28;264.604;93;0;1;1;1;0;4;80;172;27;8
|
|
||||||
28;22;7;4;1;225;26;9;28;264.604;93;0;1;1;0;0;2;69;169;24;8
|
|
||||||
13;13;7;2;1;369;17;12;31;264.604;93;0;1;3;1;0;0;70;169;25;80
|
|
||||||
11;14;7;3;1;289;36;13;33;264.604;93;0;1;2;1;0;1;90;172;30;8
|
|
||||||
1;11;7;3;1;235;11;14;37;264.604;93;0;3;1;0;0;1;88;172;29;4
|
|
||||||
4;0;0;3;1;118;14;13;40;271.219;95;0;1;1;1;0;8;98;170;34;0
|
|
||||||
8;0;0;4;2;231;35;14;39;271.219;95;0;1;2;1;0;2;100;170;35;0
|
|
||||||
35;0;0;6;3;179;45;14;53;271.219;95;0;1;1;0;0;1;77;175;25;0
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ numpy==1.24.3
|
|||||||
scikit-learn==1.3.0
|
scikit-learn==1.3.0
|
||||||
xgboost==1.7.6
|
xgboost==1.7.6
|
||||||
lightgbm==4.1.0
|
lightgbm==4.1.0
|
||||||
|
torch==2.6.0
|
||||||
joblib==1.3.1
|
joblib==1.3.1
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import joblib
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
from core.deep_learning_model import load_lstm_mlp_bundle, predict_lstm_mlp
|
||||||
from core.model_features import (
|
from core.model_features import (
|
||||||
align_feature_frame,
|
align_feature_frame,
|
||||||
apply_label_encoders,
|
apply_label_encoders,
|
||||||
@@ -20,6 +21,11 @@ MODEL_INFO = {
|
|||||||
'gradient_boosting': {'name': 'gradient_boosting', 'name_cn': 'GBDT', 'description': '梯度提升决策树'},
|
'gradient_boosting': {'name': 'gradient_boosting', 'name_cn': 'GBDT', 'description': '梯度提升决策树'},
|
||||||
'extra_trees': {'name': 'extra_trees', 'name_cn': '极端随机树', 'description': '高随机性的树模型'},
|
'extra_trees': {'name': 'extra_trees', 'name_cn': '极端随机树', 'description': '高随机性的树模型'},
|
||||||
'stacking': {'name': 'stacking', 'name_cn': 'Stacking集成', 'description': '多模型融合'},
|
'stacking': {'name': 'stacking', 'name_cn': 'Stacking集成', 'description': '多模型融合'},
|
||||||
|
'lstm_mlp': {
|
||||||
|
'name': 'lstm_mlp',
|
||||||
|
'name_cn': '时序注意力融合网络',
|
||||||
|
'description': 'Transformer时序编码 + 静态特征门控融合的深度学习模型',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +56,7 @@ class PredictService:
|
|||||||
'gradient_boosting': 'gradient_boosting_model.pkl',
|
'gradient_boosting': 'gradient_boosting_model.pkl',
|
||||||
'extra_trees': 'extra_trees_model.pkl',
|
'extra_trees': 'extra_trees_model.pkl',
|
||||||
'stacking': 'stacking_model.pkl',
|
'stacking': 'stacking_model.pkl',
|
||||||
|
'lstm_mlp': 'lstm_mlp_model.pt',
|
||||||
}
|
}
|
||||||
allowed_models = self.training_metadata.get('available_models')
|
allowed_models = self.training_metadata.get('available_models')
|
||||||
if allowed_models:
|
if allowed_models:
|
||||||
@@ -59,6 +66,11 @@ class PredictService:
|
|||||||
path = os.path.join(config.MODELS_DIR, filename)
|
path = os.path.join(config.MODELS_DIR, filename)
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
try:
|
try:
|
||||||
|
if name == 'lstm_mlp':
|
||||||
|
bundle = load_lstm_mlp_bundle(path)
|
||||||
|
if bundle is not None:
|
||||||
|
self.models[name] = bundle
|
||||||
|
else:
|
||||||
self.models[name] = joblib.load(path)
|
self.models[name] = joblib.load(path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f'Failed to load model {name}: {exc}')
|
print(f'Failed to load model {name}: {exc}')
|
||||||
@@ -107,6 +119,10 @@ class PredictService:
|
|||||||
|
|
||||||
features = self._prepare_features(data)
|
features = self._prepare_features(data)
|
||||||
try:
|
try:
|
||||||
|
if model_type == 'lstm_mlp':
|
||||||
|
current_df = build_prediction_dataframe(data)
|
||||||
|
predicted_hours = predict_lstm_mlp(self.models[model_type], current_df)
|
||||||
|
else:
|
||||||
predicted_hours = self.models[model_type].predict([features])[0]
|
predicted_hours = self.models[model_type].predict([features])[0]
|
||||||
predicted_hours = self._inverse_transform_prediction(predicted_hours)
|
predicted_hours = self._inverse_transform_prediction(predicted_hours)
|
||||||
predicted_hours = max(0.5, float(predicted_hours))
|
predicted_hours = max(0.5, float(predicted_hours))
|
||||||
@@ -196,6 +212,8 @@ class PredictService:
|
|||||||
'test_samples': self.training_metadata.get('test_samples', 0),
|
'test_samples': self.training_metadata.get('test_samples', 0),
|
||||||
'feature_count': self.training_metadata.get('feature_count_after_selection', 0),
|
'feature_count': self.training_metadata.get('feature_count_after_selection', 0),
|
||||||
'training_date': self.training_metadata.get('training_date', ''),
|
'training_date': self.training_metadata.get('training_date', ''),
|
||||||
|
'sequence_window_size': self.training_metadata.get('sequence_window_size', 0),
|
||||||
|
'deep_learning_available': self.training_metadata.get('deep_learning_available', False),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
系统采用前后端分离架构:
|
系统采用前后端分离架构:
|
||||||
|
|
||||||
- 前端:Vue 3 + Vue Router + Element Plus + ECharts
|
- 前端:Vue 3 + Vue Router + Element Plus + ECharts
|
||||||
- 后端:Flask + Pandas + Scikit-learn + Joblib
|
- 后端:Flask + Pandas + Scikit-learn + PyTorch + Joblib
|
||||||
- 数据层:CSV 数据文件 + 模型文件
|
- 数据层:CSV 数据文件 + 模型文件
|
||||||
|
|
||||||
整体架构分为四层:
|
整体架构分为四层:
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
- `preprocessing.py`:数据清洗与预处理
|
- `preprocessing.py`:数据清洗与预处理
|
||||||
- `model_features.py`:特征构建与预测输入映射
|
- `model_features.py`:特征构建与预测输入映射
|
||||||
- `train_model.py`:模型训练与评估
|
- `train_model.py`:模型训练与评估
|
||||||
|
- `deep_learning_model.py`:LSTM+MLP 深度学习训练与推理
|
||||||
- `feature_mining.py`:相关性分析与群体对比
|
- `feature_mining.py`:相关性分析与群体对比
|
||||||
- `clustering.py`:K-Means 聚类分析
|
- `clustering.py`:K-Means 聚类分析
|
||||||
|
|
||||||
@@ -67,6 +68,12 @@
|
|||||||
5. 训练多种模型并评估性能
|
5. 训练多种模型并评估性能
|
||||||
6. 保存模型、特征信息和训练元数据
|
6. 保存模型、特征信息和训练元数据
|
||||||
|
|
||||||
|
其中深度学习路径采用:
|
||||||
|
|
||||||
|
- `LSTM` 处理员工最近多次缺勤事件构成的时间窗口序列
|
||||||
|
- `MLP` 处理员工静态属性特征
|
||||||
|
- 融合层输出缺勤时长回归结果
|
||||||
|
|
||||||
### 4.2 预测流程
|
### 4.2 预测流程
|
||||||
|
|
||||||
1. 前端输入核心预测字段
|
1. 前端输入核心预测字段
|
||||||
@@ -121,6 +128,12 @@ frontend/
|
|||||||
- 适合传统机器学习建模
|
- 适合传统机器学习建模
|
||||||
- 提供随机森林、GBDT、Extra Trees 等成熟算法
|
- 提供随机森林、GBDT、Extra Trees 等成熟算法
|
||||||
|
|
||||||
|
### 6.4 PyTorch
|
||||||
|
|
||||||
|
- 用于实现 LSTM+MLP 深度学习模型
|
||||||
|
- 支持将时序特征与静态特征进行融合建模
|
||||||
|
- 便于在论文中增加深度学习对比实验内容
|
||||||
|
|
||||||
## 7. 部署方式
|
## 7. 部署方式
|
||||||
|
|
||||||
- 本地前端开发服务器:Vite
|
- 本地前端开发服务器:Vite
|
||||||
@@ -133,3 +146,4 @@ frontend/
|
|||||||
- 前后端职责明确
|
- 前后端职责明确
|
||||||
- 支持快速展示图表与预测效果
|
- 支持快速展示图表与预测效果
|
||||||
- 支持后续扩展为数据库或更复杂模型架构
|
- 支持后续扩展为数据库或更复杂模型架构
|
||||||
|
- 同时支持传统机器学习模型与深度学习模型的实验对比
|
||||||
|
|||||||
@@ -136,13 +136,18 @@
|
|||||||
|
|
||||||
- URL:`/api/predict/models`
|
- URL:`/api/predict/models`
|
||||||
- 方法:`GET`
|
- 方法:`GET`
|
||||||
- 说明:返回可用模型及其性能指标
|
- 说明:返回可用模型及其性能指标,包含传统模型与 `LSTM+MLP` 深度学习模型
|
||||||
|
|
||||||
### 4.4 获取模型信息
|
### 4.4 获取模型信息
|
||||||
|
|
||||||
- URL:`/api/predict/model-info`
|
- URL:`/api/predict/model-info`
|
||||||
- 方法:`GET`
|
- 方法:`GET`
|
||||||
- 说明:返回训练样本量、特征数量和训练日期
|
- 说明:返回训练样本量、特征数量、训练日期以及深度学习窗口信息
|
||||||
|
|
||||||
|
新增返回字段示例:
|
||||||
|
|
||||||
|
- `sequence_window_size`
|
||||||
|
- `deep_learning_available`
|
||||||
|
|
||||||
## 5. 员工画像接口
|
## 5. 员工画像接口
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,10 @@
|
|||||||
- 星期几
|
- 星期几
|
||||||
- 是否节假日前后
|
- 是否节假日前后
|
||||||
- 季节
|
- 季节
|
||||||
|
- 事件日期
|
||||||
|
- 事件日期索引
|
||||||
|
- 事件序号
|
||||||
|
- 员工历史事件数
|
||||||
- 请假申请渠道
|
- 请假申请渠道
|
||||||
- 请假类型
|
- 请假类型
|
||||||
- 请假原因大类
|
- 请假原因大类
|
||||||
@@ -129,6 +133,23 @@
|
|||||||
- 慢性病史和健康异常会提升缺勤时长
|
- 慢性病史和健康异常会提升缺勤时长
|
||||||
- 年假和调休通常对应较短缺勤时长
|
- 年假和调休通常对应较短缺勤时长
|
||||||
|
|
||||||
|
### 6.3 时序样本构造
|
||||||
|
|
||||||
|
为支持 LSTM+MLP 深度学习模型,数据集在事件层面额外补充了时序字段:
|
||||||
|
|
||||||
|
- `事件日期`:缺勤事件发生日期
|
||||||
|
- `事件日期索引`:便于排序和窗口切片的数值型时间索引
|
||||||
|
- `事件序号`:同一员工内部的事件顺序
|
||||||
|
- `员工历史事件数`:该员工在数据集中对应的事件总数
|
||||||
|
|
||||||
|
深度学习样本构造规则如下:
|
||||||
|
|
||||||
|
- 以员工为单位按 `事件日期索引` 和 `事件序号` 排序
|
||||||
|
- 取最近 `5` 次缺勤事件作为时间窗口输入
|
||||||
|
- 序列不足时使用前向零填充
|
||||||
|
- 当前事件作为窗口最后一个时间步
|
||||||
|
- 静态特征单独输入 MLP 分支,与 LSTM 输出融合后进行回归预测
|
||||||
|
|
||||||
## 7. 数据质量要求
|
## 7. 数据质量要求
|
||||||
|
|
||||||
- 无大量缺失值
|
- 无大量缺失值
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
随着企业管理数字化水平的提升,员工缺勤行为分析逐渐成为人力资源管理中的重要研究内容。针对传统缺勤管理方式依赖人工统计、分析效率较低、风险预警能力不足等问题,本文设计并实现了一套基于中国企业员工缺勤事件分析与预测系统。系统围绕缺勤事件数据,构建了数据概览、影响因素分析、缺勤风险预测和员工群体画像四个核心模块,实现了缺勤时长统计分析、关键因素挖掘、多模型预测与聚类画像展示等功能。
|
随着企业管理数字化水平的提升,员工缺勤行为分析逐渐成为人力资源管理中的重要研究内容。针对传统缺勤管理方式依赖人工统计、分析效率较低、风险预警能力不足等问题,本文设计并实现了一套基于中国企业员工缺勤事件分析与预测系统。系统围绕缺勤事件数据,构建了数据概览、影响因素分析、缺勤风险预测和员工群体画像四个核心模块,实现了缺勤时长统计分析、关键因素挖掘、多模型预测与聚类画像展示等功能。
|
||||||
|
|
||||||
在系统实现过程中,后端采用 Flask 框架构建接口服务,结合 Pandas 与 Scikit-learn 完成数据处理、特征工程、模型训练与预测;前端采用 Vue 3、Element Plus 与 ECharts 实现交互式可视化界面。针对毕业设计场景,系统构建了一套符合中国企业特征的员工缺勤事件数据集,并设计了请假类型、医院证明、加班通勤压力、健康风险等关键影响因素。实验结果表明,系统能够较好地完成缺勤时长预测任务,并通过可视化方式直观展现缺勤趋势、影响因素和员工群体特征。
|
在系统实现过程中,后端采用 Flask 框架构建接口服务,结合 Pandas、Scikit-learn 与 PyTorch 完成数据处理、特征工程、模型训练与预测;前端采用 Vue 3、Element Plus 与 ECharts 实现交互式可视化界面。针对毕业设计场景,系统构建了一套符合中国企业特征的员工缺勤事件数据集,并设计了请假类型、医院证明、加班通勤压力、健康风险等关键影响因素。同时,为增强论文的算法研究内容,系统引入了 LSTM+MLP 深度学习模型,将员工历史缺勤事件序列与静态属性特征进行融合建模。实验结果表明,系统能够较好地完成缺勤时长预测任务,并通过可视化方式直观展现缺勤趋势、影响因素和员工群体特征。
|
||||||
|
|
||||||
本文的研究工作对企业缺勤行为分析与管理辅助决策具有一定参考价值,同时也为后续扩展员工行为分析、离职预警和绩效管理等方向提供了基础。
|
本文的研究工作对企业缺勤行为分析与管理辅助决策具有一定参考价值,同时也为后续扩展员工行为分析、离职预警和绩效管理等方向提供了基础。
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
- 风险预测
|
- 风险预测
|
||||||
- 特征挖掘
|
- 特征挖掘
|
||||||
- 机器学习
|
- 机器学习
|
||||||
|
- 深度学习
|
||||||
- 可视化系统
|
- 可视化系统
|
||||||
- Vue
|
- Vue
|
||||||
- Flask
|
- Flask
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
- 2.2 Vue 3 前端框架
|
- 2.2 Vue 3 前端框架
|
||||||
- 2.3 ECharts 可视化技术
|
- 2.3 ECharts 可视化技术
|
||||||
- 2.4 机器学习相关算法
|
- 2.4 机器学习相关算法
|
||||||
- 2.5 K-Means 聚类方法
|
- 2.5 深度学习相关算法
|
||||||
|
- 2.6 K-Means 聚类方法
|
||||||
|
|
||||||
### 第3章 系统需求分析
|
### 第3章 系统需求分析
|
||||||
|
|
||||||
@@ -41,15 +42,16 @@
|
|||||||
- 5.1 数据概览模块实现
|
- 5.1 数据概览模块实现
|
||||||
- 5.2 影响因素分析模块实现
|
- 5.2 影响因素分析模块实现
|
||||||
- 5.3 缺勤预测模块实现
|
- 5.3 缺勤预测模块实现
|
||||||
- 5.4 员工画像模块实现
|
- 5.4 LSTM+MLP 深度学习模型实现
|
||||||
- 5.5 前端界面实现
|
- 5.5 员工画像模块实现
|
||||||
|
- 5.6 前端界面实现
|
||||||
|
|
||||||
### 第6章 系统测试与结果分析
|
### 第6章 系统测试与结果分析
|
||||||
|
|
||||||
- 6.1 测试环境
|
- 6.1 测试环境
|
||||||
- 6.2 功能测试
|
- 6.2 功能测试
|
||||||
- 6.3 接口测试
|
- 6.3 接口测试
|
||||||
- 6.4 模型效果分析
|
- 6.4 传统模型与深度学习模型对比
|
||||||
- 6.5 系统展示效果分析
|
- 6.5 系统展示效果分析
|
||||||
|
|
||||||
### 第7章 总结与展望
|
### 第7章 总结与展望
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
- Vue 3 的组件化优势
|
- Vue 3 的组件化优势
|
||||||
- Element Plus 和 ECharts 的可视化能力
|
- Element Plus 和 ECharts 的可视化能力
|
||||||
- 随机森林、GBDT、Extra Trees 的基本原理
|
- 随机森林、GBDT、Extra Trees 的基本原理
|
||||||
|
- LSTM 与 MLP 的基本原理
|
||||||
|
- 时序序列建模与多输入融合思想
|
||||||
- K-Means 聚类思想
|
- K-Means 聚类思想
|
||||||
|
|
||||||
## 第3章 系统需求分析
|
## 第3章 系统需求分析
|
||||||
@@ -71,6 +73,7 @@
|
|||||||
- 数据生成与预处理实现
|
- 数据生成与预处理实现
|
||||||
- 特征工程实现
|
- 特征工程实现
|
||||||
- 模型训练与保存实现
|
- 模型训练与保存实现
|
||||||
|
- LSTM+MLP 深度学习训练流程
|
||||||
- 后端接口实现
|
- 后端接口实现
|
||||||
- 前端页面实现
|
- 前端页面实现
|
||||||
- 预测页卡片布局与交互实现
|
- 预测页卡片布局与交互实现
|
||||||
@@ -89,6 +92,7 @@
|
|||||||
- 预测功能测试
|
- 预测功能测试
|
||||||
- 聚类与分析结果测试
|
- 聚类与分析结果测试
|
||||||
- 模型性能指标分析
|
- 模型性能指标分析
|
||||||
|
- 传统模型与深度学习模型对比分析
|
||||||
|
|
||||||
## 第7章 总结与展望
|
## 第7章 总结与展望
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
- 前后端分离结构清晰
|
- 前后端分离结构清晰
|
||||||
- 采用多模型训练与比较
|
- 采用多模型训练与比较
|
||||||
|
- 引入 LSTM+MLP 深度学习模型,支持时序行为建模
|
||||||
- 融合特征工程与聚类分析
|
- 融合特征工程与聚类分析
|
||||||
- 前端页面采用卡片式可视化布局,适合展示
|
- 前端页面采用卡片式可视化布局,适合展示
|
||||||
|
|
||||||
|
|||||||
193
docs/09_环境配置与安装说明.md
Normal file
193
docs/09_环境配置与安装说明.md
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# 环境配置与安装说明
|
||||||
|
|
||||||
|
## 1. 推荐环境
|
||||||
|
|
||||||
|
为保证传统机器学习模型和 `LSTM+MLP` 深度学习模型均可正常训练,推荐使用 **conda 虚拟环境** 管理本项目依赖。
|
||||||
|
|
||||||
|
推荐环境:
|
||||||
|
|
||||||
|
- 操作系统:Windows 10 / Windows 11
|
||||||
|
- Python:3.11
|
||||||
|
- Conda:Anaconda 或 Miniconda
|
||||||
|
- Node.js:16+
|
||||||
|
- pnpm:8+
|
||||||
|
- CUDA:建议与 PyTorch GPU 轮子版本匹配
|
||||||
|
|
||||||
|
## 2. 创建 conda 虚拟环境
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda create -n forsetenv python=3.11 -y
|
||||||
|
conda activate forsetenv
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 后续所有 Python 依赖安装、数据生成、模型训练和后端启动,均建议在 `forsetenv` 环境中进行。
|
||||||
|
|
||||||
|
## 3. 推荐安装顺序
|
||||||
|
|
||||||
|
推荐严格按下面顺序执行:
|
||||||
|
|
||||||
|
1. 创建并激活 `conda` 虚拟环境
|
||||||
|
2. 单独安装 `PyTorch GPU` 版
|
||||||
|
3. 安装其余后端依赖
|
||||||
|
4. 安装前端依赖
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `backend/requirements.txt` 中包含 `torch==2.6.0`
|
||||||
|
- 如果在 Windows 下先直接执行 `pip install -r backend/requirements.txt`,可能安装成非预期构建
|
||||||
|
- 因此深度学习环境建议先执行官方 `cu124` 安装命令,再补齐其余依赖
|
||||||
|
|
||||||
|
## 4. 安装 PyTorch GPU 版
|
||||||
|
|
||||||
|
本项目的 hybrid 深度学习模型要求:
|
||||||
|
|
||||||
|
- `torch >= 2.6`
|
||||||
|
|
||||||
|
推荐安装方式:
|
||||||
|
|
||||||
|
- 使用 **pip 官方 cu124 轮子**
|
||||||
|
- 避免在 Windows 上由 conda 自动解析成 `cpu_mkl` 构建
|
||||||
|
|
||||||
|
安装命令如下:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 --index-url https://download.pytorch.org/whl/cu124
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 安装其余后端依赖
|
||||||
|
|
||||||
|
如果你已经按上一步安装了 GPU 版 `torch`,推荐补装其余后端依赖:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install Flask==2.3.3 Flask-CORS==4.0.0 python-dotenv==1.0.0
|
||||||
|
pip install pandas==2.0.3 numpy==1.24.3 scikit-learn==1.3.0 joblib==1.3.1
|
||||||
|
pip install xgboost==1.7.6 lightgbm==4.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
如果你仍然希望直接使用依赖文件,可以在完成 GPU 版 `torch` 安装后执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install -r backend/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
这一步通常不会影响已经安装好的 `cu124` 版本;如有覆盖风险,可在执行后再次运行上一节的 GPU 安装命令。
|
||||||
|
|
||||||
|
## 6. 安装前端依赖
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd frontend
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 一键执行示例
|
||||||
|
|
||||||
|
下面是一套推荐的 `conda` 环境安装流程:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda create -n forsetenv python=3.11 -y
|
||||||
|
conda activate forsetenv
|
||||||
|
pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 --index-url https://download.pytorch.org/whl/cu124
|
||||||
|
pip install Flask==2.3.3 Flask-CORS==4.0.0 python-dotenv==1.0.0
|
||||||
|
pip install pandas==2.0.3 numpy==1.24.3 scikit-learn==1.3.0 joblib==1.3.1
|
||||||
|
pip install xgboost==1.7.6 lightgbm==4.1.0
|
||||||
|
cd frontend
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 验证安装
|
||||||
|
|
||||||
|
### 8.1 验证基础依赖
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -c "import pandas,numpy,sklearn,flask;print('base ok')"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 验证传统模型依赖
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -c "import xgboost,lightgbm;print('ml ok')"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 验证 PyTorch GPU
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -c "import torch;print(torch.__version__);print(torch.cuda.is_available())"
|
||||||
|
```
|
||||||
|
|
||||||
|
如果输出为 `True`,说明 GPU 版本 PyTorch 可正常使用。
|
||||||
|
|
||||||
|
## 9. 项目启动顺序
|
||||||
|
|
||||||
|
### 9.1 生成数据集
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd backend
|
||||||
|
python core/generate_dataset.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 训练模型
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python core/train_model.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 启动后端
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.4 启动前端
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd ..\frontend
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. 常见问题
|
||||||
|
|
||||||
|
### 10.1 PyTorch 被安装成 CPU 版
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 使用了默认 `pip install torch`
|
||||||
|
- 或使用 conda 在 Windows 上自动解析成 CPU 构建
|
||||||
|
|
||||||
|
建议:
|
||||||
|
|
||||||
|
- 直接使用本文提供的官方 `cu124` 安装命令
|
||||||
|
|
||||||
|
### 10.2 训练过程中无法加载深度学习模型
|
||||||
|
|
||||||
|
检查项:
|
||||||
|
|
||||||
|
- 当前是否处于 `forsetenv` conda 环境
|
||||||
|
- `torch` 是否成功安装
|
||||||
|
- `torch.cuda.is_available()` 是否为 `True`
|
||||||
|
|
||||||
|
### 10.3 xgboost / lightgbm 缺失
|
||||||
|
|
||||||
|
可执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
pip install xgboost==1.7.6 lightgbm==4.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.4 如何确认当前使用的是 conda 环境
|
||||||
|
|
||||||
|
可执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
conda info --envs
|
||||||
|
where python
|
||||||
|
```
|
||||||
|
|
||||||
|
如果当前环境为 `forsetenv`,且 `python` 指向对应环境目录,说明切换成功。
|
||||||
|
|
||||||
|
## 11. 建议
|
||||||
|
|
||||||
|
- 毕设演示或论文实验时,统一使用 `conda activate forsetenv`
|
||||||
|
- 深度学习模型训练时优先使用 GPU 环境
|
||||||
|
- 若仅进行页面展示,可先训练传统模型,再补充深度学习实验结果
|
||||||
23
docs/10_题目名称_技术路线_预期结果.md
Normal file
23
docs/10_题目名称_技术路线_预期结果.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 题目名称、主要技术路线或方法、预期结果
|
||||||
|
|
||||||
|
## 1. 题目名称
|
||||||
|
|
||||||
|
基于中国企业员工缺勤事件的分析与预测系统设计与实现
|
||||||
|
|
||||||
|
## 2. 论文(设计)采取的主要技术路线或方法
|
||||||
|
|
||||||
|
本课题围绕企业员工缺勤管理场景,采用前后端分离的系统设计思路开展研究与实现。前端基于 `Vue 3`、`Element Plus` 和 `ECharts` 构建可视化展示界面,实现缺勤趋势、影响因素、预测结果和员工画像等内容的交互式展示;后端基于 `Flask` 搭建接口服务,负责数据处理、分析计算、模型推理和聚类结果组织。
|
||||||
|
|
||||||
|
在数据处理方面,首先结合项目内部构建的中国企业员工缺勤事件数据集,使用 `Pandas` 和 `NumPy` 完成数据清洗、字段转换、统计分析与特征整理。随后围绕员工属性、岗位信息、班次安排、健康风险、请假原因、通勤压力和加班情况等因素进行特征工程,形成适用于分析与预测的结构化数据。
|
||||||
|
|
||||||
|
在算法研究方面,课题采用传统机器学习与深度学习相结合的技术路线。传统模型依托 `scikit-learn`、`XGBoost` 和 `LightGBM` 完成缺勤时长预测与模型对比分析,并通过特征重要性排序和相关性分析挖掘关键影响因素;深度学习部分基于 `PyTorch` 构建 `LSTM+MLP` 融合模型,将员工历史缺勤事件序列与静态属性特征结合,用于提升预测研究的完整性和论文的技术深度。
|
||||||
|
|
||||||
|
在员工画像分析方面,课题采用 `K-Means` 聚类方法对员工缺勤行为进行分群,结合散点图、雷达图和群体说明完成群体画像展示,从而辅助企业识别不同类型的缺勤风险群体。最终通过系统集成与前后端联调,实现缺勤数据概览、影响因素分析、单次缺勤预测和员工画像分析四个核心功能模块。
|
||||||
|
|
||||||
|
## 3. 论文(设计)预期结果
|
||||||
|
|
||||||
|
本课题预期完成一个可运行、可展示、可支撑论文撰写的员工缺勤分析与预测系统。系统能够实现缺勤事件统计展示、趋势分析、原因分布分析、关键因素挖掘、缺勤时长预测、风险等级评估和员工群体画像展示等功能,满足本科毕业设计对系统实现和功能展示的要求。
|
||||||
|
|
||||||
|
在研究结果方面,预期能够形成一套较完整的员工缺勤分析方法流程,包括数据预处理、特征工程、相关性分析、特征重要性评估、预测建模和聚类画像分析。系统应能够根据输入的关键业务字段输出缺勤时长预测结果、风险等级和多模型对比结果,并通过可视化图表直观展示分析结论,为企业人力资源管理提供辅助决策依据。
|
||||||
|
|
||||||
|
在论文成果方面,预期形成与项目实现一致的毕业设计文档与论文材料,包括需求分析、系统架构设计、接口设计、数据设计、系统实现、实验分析和总结展望等内容,并能够支撑后续开题、中期检查、论文提交和答辩展示工作。
|
||||||
@@ -16,9 +16,16 @@
|
|||||||
- [06_毕业论文目录与章节设计.md](D:/VScodeProject/forsetsystem/docs/06_毕业论文目录与章节设计.md)
|
- [06_毕业论文目录与章节设计.md](D:/VScodeProject/forsetsystem/docs/06_毕业论文目录与章节设计.md)
|
||||||
- [07_毕业论文写作提纲.md](D:/VScodeProject/forsetsystem/docs/07_毕业论文写作提纲.md)
|
- [07_毕业论文写作提纲.md](D:/VScodeProject/forsetsystem/docs/07_毕业论文写作提纲.md)
|
||||||
- [08_答辩汇报提纲.md](D:/VScodeProject/forsetsystem/docs/08_答辩汇报提纲.md)
|
- [08_答辩汇报提纲.md](D:/VScodeProject/forsetsystem/docs/08_答辩汇报提纲.md)
|
||||||
|
- [10_题目名称_技术路线_预期结果.md](D:/VScodeProject/forsetsystem/docs/10_题目名称_技术路线_预期结果.md)
|
||||||
|
|
||||||
|
## 环境配置文档
|
||||||
|
|
||||||
|
- [09_环境配置与安装说明.md](D:/VScodeProject/forsetsystem/docs/09_环境配置与安装说明.md)
|
||||||
|
|
||||||
## 说明
|
## 说明
|
||||||
|
|
||||||
- 系统文档以当前项目实现为准,围绕中国企业员工缺勤分析、风险预测与群体画像展开。
|
- 系统文档以当前项目实现为准,围绕中国企业员工缺勤分析、风险预测与群体画像展开。
|
||||||
- 论文文档采用本科毕业设计常用结构,便于后续继续扩写为正式论文。
|
- 论文文档采用本科毕业设计常用结构,便于后续继续扩写为正式论文。
|
||||||
- 若后续系统功能或字段发生变化,应同步更新本目录下相关文档。
|
- 若后续系统功能或字段发生变化,应同步更新本目录下相关文档。
|
||||||
|
- 深度学习部分推荐使用 `conda` 虚拟环境配合 `pip` 安装 PyTorch GPU 版。
|
||||||
|
- 推荐安装顺序为:创建 `conda` 环境、安装官方 `cu124` 的 PyTorch、再补充其余后端依赖。
|
||||||
|
|||||||
Reference in New Issue
Block a user