import unittest
from datetime import datetime
from unittest.mock import MagicMock, patch

from bson import ObjectId
from freezegun import freeze_time
from freezegun.api import FakeDatetime

from extensions.revere.models.revere import RevereTest, RevereTestResult
from extensions.revere.repository.mongo_revere_repository import (
    MongoRevereTestRepository,
)

MONGO_REPO_PATH = "extensions.revere.repository.mongo_revere_repository"
SAMPLE_ID = "600a8476a961574fb38157d5"
REVERE_COLLECTION = MongoRevereTestRepository.REVERE_TEST_COLLECTION

@patch(f"{MONGO_REPO_PATH}.inject", MagicMock())
class MongoRevereRepositoryTestCase(unittest.TestCase):
    @freeze_time("2012-01-01")
    def test_success_create_test(self):
        db = MagicMock()
        repo = MongoRevereTestRepository(database=db)
        user_id = SAMPLE_ID
        deployment_id = SAMPLE_ID
        module_name = "name"
        repo.create_test(
            user_id=user_id, deployment_id=deployment_id, module_name=module_name
        )
        db[REVERE_COLLECTION].insert_one.assert_called_with(
            {
                RevereTest.USER_ID: ObjectId(user_id),
                RevereTest.START_DATE_TIME: FakeDatetime(2012, 1, 1, 0, 0),
                RevereTest.RESULTS: [],
                RevereTest.STATUS: RevereTest.Status.STARTED.value,
                RevereTest.DEPLOYMENT_ID: ObjectId(deployment_id),
                RevereTest.MODULE_ID: module_name,
            }
        )

    @patch(
        f"{MONGO_REPO_PATH}.RevereTest",
    )
    def test_success_retrieve_test(self, revere_test):
        db = MagicMock()
        repo = MongoRevereTestRepository(database=db)
        user_id = SAMPLE_ID
        test_id = SAMPLE_ID
        repo.retrieve_test(user_id=user_id, test_id=test_id)
        db[REVERE_COLLECTION].find_one.assert_called_with(
            {revere_test.ID_: ObjectId(test_id), revere_test.USER_ID: ObjectId(user_id)}
        )

    def test_success_retrieve_user_tests(self):
        user_id = SAMPLE_ID
        status = "status"
        db = MagicMock()
        repo = MongoRevereTestRepository(database=db)
        repo.retrieve_user_tests(user_id=user_id, status=status)
        db[REVERE_COLLECTION].find.assert_called_with(
            {RevereTest.USER_ID: ObjectId(user_id), RevereTest.STATUS: status}
        )

    @freeze_time("2012-01-01")
    def test_success_finish_test(self):
        user_id = SAMPLE_ID
        test_id = SAMPLE_ID
        db = MagicMock()
        repo = MongoRevereTestRepository(database=db)
        repo.finish_test(user_id=user_id, test_id=test_id)
        db[REVERE_COLLECTION].update_one.assert_called_with(
            {RevereTest.ID_: ObjectId(test_id), RevereTest.USER_ID: ObjectId(user_id)},
            {
                "$set": {
                    RevereTest.END_DATE_TIME: FakeDatetime(2012, 1, 1, 0, 0),
                    RevereTest.STATUS: RevereTest.Status.FINISHED.value,
                }
            },
        )

    def test_success_save_word_list_result(self):
        test_id = SAMPLE_ID
        user_id = SAMPLE_ID
        word_list_id = SAMPLE_ID
        result_word_list = ["aaa", "bbb"]
        db = MagicMock()
        repo = MongoRevereTestRepository(database=db)
        result = RevereTestResult(
            wordsResult=result_word_list,
            id=word_list_id,
            createDateTime=datetime.utcnow(),
        )
        repo.save_word_list_result(test_id=test_id, user_id=user_id, result=result)
        db[REVERE_COLLECTION].update_one.assert_called_with(
            {RevereTest.ID_: ObjectId(test_id), RevereTest.USER_ID: ObjectId(user_id)},
            {"$push": {RevereTest.RESULTS: result.to_dict(include_none=False)}},
        )

if __name__ == "__main__":
    unittest.main()


import os
import random
import sys

import joblib
import math
import lightgbm as lgb
import xgboost as xgb
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn.svm as svm
from sklearn.ensemble import RandomForestClassifier, VotingClassifier
from sklearn.linear_model import LinearRegression, LogisticRegression
from catboost import CatBoostClassifier
from sklearn.metrics import (accuracy_score, average_precision_score,
                             classification_report, confusion_matrix, f1_score,
                             precision_recall_curve, roc_auc_score, roc_curve)
from sklearn.model_selection import GroupKFold
from sklearn.naive_bayes import GaussianNB
from keras.layers import LSTM
from keras.layers import Dense
from keras.models import Sequential
from keras.utils import to_categorical
import keras.callbacks as kcallbacks
from utilClass import RocAucMetricCallback
from utils import series_to_supervised
from tqdm import tqdm
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from imblearn.pipeline import Pipeline

sys.path.append('..')

pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)

class AtoA:
    def __init__(self, mode, type='df', seed=2021, scale='all'):
        self.seed = seed
        self.window = 0  # 在线数据读取中滑动窗口的长度
        self.win_df = pd.DataFrame()  # 在线数据读取中始终维持一个长度为最大滑动窗口的dataframe
        self.mode = mode  # 离线或在线
        self.type = type  # df 或者 dcs
        self.scale = scale  # 模型量级
        self.current_row = None  # 当前时刻样本
        self.pre_row = None  # 上一时刻样本

    # dogfight特征工程工具函数
    def FE_DF(self, data):
        """ DF特征工程
        Args:
            data (dataframe): 原始数据
        Returns:
            DataFrame: 特征工程后数据
        """
        data = data.sort_values(by=['id'])
        if self.scale == 'all':
            # 计算敌机的速度，先用diffh函数得到和上一时刻xyz差值，然后除以时间得到速度
            for f in ['x', 'y', 'z']:
                data['enemy_v_{}'.format(f)] = data.groupby('id')[
                    'enemy_{}'.format(f)].diff(1) / 0.02
            # 敌我两机加速度，先用diffh函数得到和上一时刻v_x,v_y,v_z差值，然后除以时间得到加速度
            for f in ['v_x', 'v_y', 'v_z']:
                data[f'my_{f}_acc'] = data.groupby(
                    'id')[f'my_{f}'].diff() / 0.2
                data[f'enemy_{f}_acc'] = data.groupby(
                    'id')[f'enemy_{f}'].diff() / 0.2
            # 敌我两机速度与位置交互式差值
            for f in ['x', 'y', 'z', 'v_x', 'v_y', 'v_z']:
                data[f'{f}_me_minus'] = data[f'my_{f}'] - data[f'enemy_{f}']

        # 飞机之间的距离
        data['distance'] = ((data['my_x'] - data['enemy_x'])**2 +
                            (data['my_y'] - data['enemy_y'])**2 +
                            (data['my_z'] - data['enemy_z'])**2)**0.5

        # 瞄准夹角
        data['cos'] = ((data['my_v_x'] * (data['enemy_x'] - data['my_x'])) +
                       (data['my_v_y'] * (data['enemy_y'] - data['my_y'])) +
                       (data['my_v_z'] * (data['enemy_z'] - data['my_z'])))
        # 合速度
        data['speedAll'] = ((data['my_v_x']**2 + data['my_v_y']**2 +
                             data['my_v_z']**2)**0.5)
        # 夹角cos值
        data['cosValue'] = data['cos'] / (data['speedAll'] * data['distance'])
        # 缺失值补0
        data.fillna(0, inplace=True)

        return data

    def online_FE_DF(self, row_dict):
        """ DF在线特征工程
        Args:
            row_dict(dict): 传入的一行字典记录
        Returns:
            DataFrame: 加入特征后的记录dataframe
        """
        # 将字典转为dataframe格式
        data = pd.DataFrame(row_dict, index=[0])
        # 飞机之间的距离
        data['distance'] = ((data['my_x'] - data['enemy_x'])**2 +
                            (data['my_y'] - data['enemy_y'])**2 +
                            (data['my_z'] - data['enemy_z'])**2)**0.5

        # 瞄准夹角
        data['cos'] = ((data['my_v_x'] * (data['enemy_x'] - data['my_x'])) +
                       (data['my_v_y'] * (data['enemy_y'] - data['my_y'])) +
                       (data['my_v_z'] * (data['enemy_z'] - data['my_z'])))
        # 合速度
        data['speedAll'] = ((data['my_v_x']**2 + data['my_v_y']**2 +
                             data['my_v_z']**2)**0.5)
        # 夹角cos值
        data['cosValue'] = data['cos'] / (data['speedAll'] * data['distance'])
        # 缺失值补0
        data.fillna(0, inplace=True)
        return data

    # DCS特征工程工具函数
    def FE_DCS(self, data_):
        """ DCS特征工程
        Args:
            data (dataframe): 原始数据
        Returns:
            DataFrame: 特征工程后数据
        """
        data = data_.copy(deep=True)
        if self.mode == 'offline':
            # 如果是离线训练，需要根据id进行数据分组
            data = data.sort_values(by=['id'])

        # 飞机之间的距离
        data['distance'] = (
            (data['my_position_x'] - data['enemy_position_x'])**2 +
            (data['my_position_y'] - data['enemy_position_y'])**2 +
            (data['my_position_z'] - data['enemy_position_z'])**2)**0.5
        # 向量乘法，向量 a = （x,y,z） b = (x2,y2,z2) c = (x3,y3,z3),a代表我机速度向量
        # b代表位置向量，c代表敌机位置向量，我机中心到敌机中心向量d = c - b
        # d与a之间cos = d×a/(|d|*|a|)
        data['cos'] = ((data['my_speed_x'] *
                        (data['enemy_position_x'] - data['my_position_x'])) +
                       (data['my_speed_y'] *
                        (data['enemy_position_y'] - data['my_position_y'])) +
                       (data['my_speed_z'] *
                        (data['enemy_position_z'] - data['my_position_z'])))
        # 速度向量
        data['speedAll'] = ((data['my_speed_x']**2 + data['my_speed_y']**2 +
                             data['my_speed_z']**2)**0.5)
        # 向量之间夹角
        data['cosValue'] = data['cos'] / (data['speedAll'] * data['distance'])
        # 敌我两机位置交互式差值
        for f in ['position_x', 'position_y', 'position_z']:
            data[f'{f}_diff'] = data[f'my_{f}'] - data[f'enemy_{f}']

        return data

    @staticmethod
    def _caculate_speed_connect_cos(x, y, z, enemy_x, enemy_y, enemy_z,
                                    speed_x, speed_y, speed_z):
        """
        计算我敌连线矢量与我机速度矢量夹角
        Args:
            x, y, z: 我机坐标
            enemy_x, enemy_y, enemy_z：敌机坐标
            speed_x, speed_y, speed_z: 我机或敌机速度
        Returns:
            speed_connect_cos:我敌连线矢量与速度矢量夹角余弦值
        """
        connect_vec = np.array([enemy_x - x, enemy_y - y, enemy_z - z])
        my_speed_vec = np.array([speed_x, speed_y, speed_z])

        speed_connect_cos = connect_vec.dot(my_speed_vec) / np.sqrt(
            connect_vec.dot(connect_vec) * my_speed_vec.dot(my_speed_vec))

        return speed_connect_cos

    @staticmethod
    def _caculate_speed_cos(speed_x, speed_y, speed_z, enemy_speed_x,
                            enemy_speed_y, enemy_speed_z):
        """
        计算我机速度矢量与敌机速度矢量夹角
        Args:
            speed_x, speed_y, speed_z：我机速度
            enemy_speed_x, enemy_speed_y, enemy_speed_z: 敌机速度
        Returns:
            speed_cos:敌机速度与我机速度矢量夹角余弦值
        """
        my_speed_vec = np.array([speed_x, speed_y, speed_z])
        enemy_speed_vec = np.array(
            [enemy_speed_x, enemy_speed_y, enemy_speed_z])

        speed_cos = my_speed_vec.dot(enemy_speed_vec) / np.sqrt(
            my_speed_vec.dot(my_speed_vec) *
            enemy_speed_vec.dot(enemy_speed_vec))

        return speed_cos

    def FE_DCS_new(self, data_):
        """新DCS任务特征工程

        Args:
            data_ (dataframe): 原始数据

        Returns:
            data: 特征工程后数据
        """
        data = data_.copy()
        data = data.sort_values(by=['id', 'ISO time'])
        data.reset_index(drop=True, inplace=True)
        data.rename(columns={
            'U': 'x',
            'V': 'z',
            'Altitude': 'y',
            'enemy_U': 'enemy_x',
            'enemy_V': 'enemy_z',
            'enemy_Altitude': 'enemy_y',
        },
                    inplace=True)
        if self.mode == 'offline':
            if self.scale == 'all':
                # 计算我机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'speed_x': data.groupby('id')['x'].diff(),
                        'speed_y': data.groupby('id')['y'].diff(),
                        'speed_z': data.groupby('id')['z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[['speed_x', 'speed_y', 'speed_z'
                      ]] = data[['speed_x', 'speed_y', 'speed_z']] / 0.05
                data['speed'] = data.apply(lambda x: np.sqrt(x[
                    'speed_x']**2 + x['speed_y']**2 + x['speed_z']**2),
                                           axis=1)

                # 计算敌机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'enemy_speed_x':
                        data.groupby('id')['enemy_x'].diff(),
                        'enemy_speed_y':
                        data.groupby('id')['enemy_y'].diff(),
                        'enemy_speed_z':
                        data.groupby('id')['enemy_z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[[
                    'enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                ]] = data[['enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                           ]] / 0.05
                data['enemy_speed'] = data.apply(
                    lambda x: np.sqrt(x['enemy_speed_x']**2 + x[
                        'enemy_speed_y']**2 + x['enemy_speed_z']**2),
                    axis=1)

                # 计算敌我距离
                data['distance'] = data.apply(lambda x: np.sqrt(
                    (x['x'] - x['enemy_x'])**2 + (x['y'] - x['enemy_y'])**2 +
                    (x['z'] - x['enemy_z'])**2),
                                              axis=1)

                # 计算我机速度与敌我连线夹角余弦值
                data['speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['speed_x'], x['speed_y'], x['speed_z'
                                                                      ]),
                    axis=1)

                # 计算敌机速度与敌我连线夹角余弦值
                data['enemy_speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['enemy_speed_x'], x['enemy_speed_y'],
                        x['enemy_speed_z']),
                    axis=1)

                # 计算两机速度夹角余弦值
                data['speed_cos'] = data.apply(
                    lambda x: self._caculate_speed_cos(x['speed_x'], x[
                        'speed_y'], x['speed_z'], x['enemy_speed_x'], x[
                            'enemy_speed_y'], x['enemy_speed_z']),
                    axis=1)

                # 我机朝向处理
                data['Heading'] = data['Heading'] % 360

                # 计算相对位置与速度
                for f in [
                        'x', 'y', 'z', 'speed_x', 'speed_y', 'speed_z', 'speed'
                ]:
                    data[f'relative_{f}'] = data[f'enemy_{f}'] - data[f'{f}']

                # 计算是否领先追逐
                data['is_lead_chase'] = data.apply(
                    lambda x: self._is_lead_chase(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'],
                        x['enemy_z'], x['speed_x'], x['speed_y'], x['speed_z'],
                        x['enemy_speed_x'], x['enemy_speed_y'], x[
                            'enemy_speed_z'], x['speed_connect_cos'], x[
                                'enemy_speed_connect_cos']),
                    axis=1)

                # 筛除不能开火标签(两机距离大于1000或背对)
                data['label'] = data.apply(
                    lambda x: 0 if x['speed_connect_cos'] < 0 or x[
                        'distance'] > 1000 else x['label'],
                    axis=1)
                data.fillna(0, inplace=True)
                data.dropna(inplace=True)
                data.to_csv('a2a_fe.csv', index=False)
                return data

            elif self.scale == 'light':
                # 计算我机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'speed_x': data.groupby('id')['x'].diff(),
                        'speed_y': data.groupby('id')['y'].diff(),
                        'speed_z': data.groupby('id')['z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[['speed_x', 'speed_y', 'speed_z'
                      ]] = data[['speed_x', 'speed_y', 'speed_z']] / 0.05
                data['speed'] = data.apply(lambda x: np.sqrt(x[
                    'speed_x']**2 + x['speed_y']**2 + x['speed_z']**2),
                                           axis=1)

                # 计算敌机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'enemy_speed_x':
                        data.groupby('id')['enemy_x'].diff(),
                        'enemy_speed_y':
                        data.groupby('id')['enemy_y'].diff(),
                        'enemy_speed_z':
                        data.groupby('id')['enemy_z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[[
                    'enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                ]] = data[['enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                           ]] / 0.05
                data['enemy_speed'] = data.apply(
                    lambda x: np.sqrt(x['enemy_speed_x']**2 + x[
                        'enemy_speed_y']**2 + x['enemy_speed_z']**2),
                    axis=1)

                # 计算敌我距离
                data['distance'] = data.apply(lambda x: np.sqrt(
                    (x['x'] - x['enemy_x'])**2 + (x['y'] - x['enemy_y'])**2 +
                    (x['z'] - x['enemy_z'])**2),
                                              axis=1)

                # 计算我机速度与敌我连线夹角余弦值
                data['speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['speed_x'], x['speed_y'], x['speed_z'
                                                                      ]),
                    axis=1)

                # 计算敌机速度与敌我连线夹角余弦值
                data['enemy_speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['enemy_speed_x'], x['enemy_speed_y'],
                        x['enemy_speed_z']),
                    axis=1)

                # 计算两机速度夹角余弦
                data['speed_cos'] = data.apply(
                    lambda x: self._caculate_speed_cos(x['speed_x'], x[
                        'speed_y'], x['speed_z'], x['enemy_speed_x'], x[
                            'enemy_speed_y'], x['enemy_speed_z']),
                    axis=1)

                # 计算相对位置
                for f in ['z', 'speed']:
                    data[f'relative_{f}'] = data[f'enemy_{f}'] - data[f'{f}']

                # 计算是否领先追逐
                data['is_lead_chase'] = data.apply(
                    lambda x: self._is_lead_chase(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'],
                        x['enemy_z'], x['speed_x'], x['speed_y'], x['speed_z'],
                        x['enemy_speed_x'], x['enemy_speed_y'], x[
                            'enemy_speed_z'], x['speed_connect_cos'], x[
                                'enemy_speed_connect_cos']),
                    axis=1)

                # 筛除不能开火标签(两机距离大于1000或背对)
                data['label'] = data.apply(
                    lambda x: 0 if x['speed_connect_cos'] < 0 or x[
                        'distance'] > 1000 else x['label'],
                    axis=1)

                data.fillna(0, inplace=True)
                data.dropna(inplace=True)
                data.to_csv('a2a_fe.csv', index=False)
                return data
        if self.mode == 'online':
            if self.scale == 'all':
                # 计算我机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'speed_x': data['x'].diff(),
                        'speed_y': data['y'].diff(),
                        'speed_z': data['z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[['speed_x', 'speed_y', 'speed_z'
                      ]] = data[['speed_x', 'speed_y', 'speed_z']] / 0.05
                data['speed'] = data.apply(lambda x: np.sqrt(x[
                    'speed_x']**2 + x['speed_y']**2 + x['speed_z']**2),
                                           axis=1)

                # 计算敌机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'enemy_speed_x': data['enemy_x'].diff(),
                        'enemy_speed_y': data['enemy_y'].diff(),
                        'enemy_speed_z': data['enemy_z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[[
                    'enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                ]] = data[['enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                           ]] / 0.05
                data['enemy_speed'] = data.apply(
                    lambda x: np.sqrt(x['enemy_speed_x']**2 + x[
                        'enemy_speed_y']**2 + x['enemy_speed_z']**2),
                    axis=1)

                # 计算敌我距离
                data['distance'] = data.apply(lambda x: np.sqrt(
                    (x['x'] - x['enemy_x'])**2 + (x['y'] - x['enemy_y'])**2 +
                    (x['z'] - x['enemy_z'])**2),
                                              axis=1)

                # 计算我机速度与敌我连线夹角余弦值
                data['speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['speed_x'], x['speed_y'], x['speed_z'
                                                                      ]),
                    axis=1)

                # 计算敌机速度与敌我连线夹角余弦值
                data['enemy_speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['enemy_speed_x'], x['enemy_speed_y'],
                        x['enemy_speed_z']),
                    axis=1)

                # 计算两机速度夹角余弦值
                data['speed_cos'] = data.apply(
                    lambda x: self._caculate_speed_cos(x['speed_x'], x[
                        'speed_y'], x['speed_z'], x['enemy_speed_x'], x[
                            'enemy_speed_y'], x['enemy_speed_z']),
                    axis=1)

                # 我机朝向处理
                data['Heading'] = data['Heading'] % 360

                # 计算相对位置与速度
                for f in [
                        'x', 'y', 'z', 'speed_x', 'speed_y', 'speed_z', 'speed'
                ]:
                    data[f'relative_{f}'] = data[f'enemy_{f}'] - data[f'{f}']

                # 筛除不能开火标签(两机距离大于1000或背对)
                data['label'] = data.apply(
                    lambda x: 0 if x['speed_connect_cos'] < 0 or x[
                        'distance'] > 1000 else x['label'],
                    axis=1)
                data.fillna(0, inplace=True)
                data.dropna(inplace=True)
                data.to_csv('a2a_fe.csv', index=False)
                return data

            elif self.scale == 'light':
                # 计算我机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'speed_x': data['x'].diff(),
                        'speed_y': data['y'].diff(),
                        'speed_z': data['z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[['speed_x', 'speed_y', 'speed_z'
                      ]] = data[['speed_x', 'speed_y', 'speed_z']] / 0.05
                data['speed'] = data.apply(lambda x: np.sqrt(x[
                    'speed_x']**2 + x['speed_y']**2 + x['speed_z']**2),
                                           axis=1)

                # 计算敌机速度
                data = pd.concat([
                    data,
                    pd.DataFrame({
                        'enemy_speed_x': data['enemy_x'].diff(),
                        'enemy_speed_y': data['enemy_y'].diff(),
                        'enemy_speed_z': data['enemy_z'].diff()
                    })
                ],
                                 sort=False,
                                 axis=1)
                data.fillna(0, inplace=True)
                data[[
                    'enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                ]] = data[['enemy_speed_x', 'enemy_speed_y', 'enemy_speed_z'
                           ]] / 0.05
                data['enemy_speed'] = data.apply(
                    lambda x: np.sqrt(x['enemy_speed_x']**2 + x[
                        'enemy_speed_y']**2 + x['enemy_speed_z']**2),
                    axis=1)

                # 计算敌我距离
                data['distance'] = data.apply(lambda x: np.sqrt(
                    (x['x'] - x['enemy_x'])**2 + (x['y'] - x['enemy_y'])**2 +
                    (x['z'] - x['enemy_z'])**2),
                                              axis=1)

                # 计算我机速度与敌我连线夹角余弦值
                data['speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['speed_x'], x['speed_y'], x['speed_z'
                                                                      ]),
                    axis=1)

                # 计算敌机速度与敌我连线夹角余弦值
                data['enemy_speed_connect_cos'] = data.apply(
                    lambda x: self._caculate_speed_connect_cos(
                        x['x'], x['y'], x['z'], x['enemy_x'], x['enemy_y'], x[
                            'enemy_z'], x['enemy_speed_x'], x['enemy_speed_y'],
                        x['enemy_speed_z']),
                    axis=1)

                # 计算两机速度夹角余弦
                data['speed_cos'] = data.apply(
                    lambda x: self._caculate_speed_cos(x['speed_x'], x[
                        'speed_y'], x['speed_z'], x['enemy_speed_x'], x[
                            'enemy_speed_y'], x['enemy_speed_z']),
                    axis=1)

                # 计算相对位置
                for f in ['y', 'speed']:
                    data[f'relative_{f}'] = data[f'enemy_{f}'] - data[f'{f}']

                # 筛除不能开火标签(两机距离大于1000或背对)
                data['label'] = data.apply(
                    lambda x: 0 if x['speed_connect_cos'] < 0 or x[
                        'distance'] > 1000 else x['label'],
                    axis=1)

                data.fillna(0, inplace=True)
                data.dropna(inplace=True)
                data.to_csv('a2a_fe.csv', index=False)
                return data

    # DCS在线特征工程
    def online_FE_DCS(self, row_dict):
        """ DCS在线特征工程
        Args:
            row_dict(dict): 传入的一行字典记录
        Returns:
            DataFrame: 加入特征后的记录dataframe
        """
        # 字典转dataframe
        row = pd.DataFrame(row_dict, index=[0])
        # 调用离线特征工程函数
        FE_row = self.FE_DCS(row)
        return FE_row

        # DCS在线特征工程

    def online_FE_DCS_new(self, row_dict):
        """ AtoA在线特征工程
        Args:
            row_dict(dict): 传入的一行字典记录
        Returns:
            DataFrame: 加入特征后的记录dataframe
        """
        row = pd.DataFrame(row_dict, index=[0])
        self.current_row = row
        if self.pre_row is None:
            FE_row = self.online_FE_DCS(self.current_row)
        else:
            window = pd.concat([self.pre_row, self.current_row], axis=0)
            FE_row = self.online_FE_DCS(window)[-1:]
        self.pre_row = self.current_row
        return FE_row

    def train_val_split(self, df_train, percent=0.8):
        """ 数据集划分
        划分数据集为训练集与测试
        Args:
            df_train(dataframe): 原始数据
            percent(int): 切分比例
        Returns:
            train(dataframe): 训练集
            val_data(dataframe): 验证集
        """
        # 获取所有id
        all_ids = df_train['id'].values.tolist()
        # id去重
        all_ids = list(set(all_ids))
        # 每次 set 的结果都不一样，所以要先排序，防止结果不可复现
        all_ids.sort()
        # random.seed 只能生效一次，所以每次 random.sample 之前都要设置
        random.seed(self.seed)
        # 训练集id采样
        train_ids = random.sample(all_ids, int(len(all_ids) * percent))
        # 获取验证集id
        val_ids = list(set(all_ids) - set(train_ids))
        # 根据id获取训练数据
        train_data = df_train[df_train['id'].isin(train_ids)]
        # 根据id获取验证数据
        val_data = df_train[df_train['id'].isin(val_ids)]
        # 连续序列数据，但是是以单个样本建模的情况下，需要 shuffle 打乱
        train_data = train_data.sample(
            frac=1, random_state=self.seed).reset_index(drop=True)

        return train_data, val_data

    def smote(self, data_):
        data = data_.copy()
        over = SMOTE(sampling_strategy=0.2, random_state=self.seed)
        under = RandomUnderSampler(sampling_strategy=1.0,
                                   random_state=self.seed)
        steps = [('o', over), ('u', under)]
        pipeline = Pipeline(steps=steps)
        X, y = pipeline.fit_resample(
            data[[i for i in data.columns if i not in ['label']]],
            data['label'])

        return pd.concat([X, y], axis=1)

    def _feature_name(self):
        """ 获取保留列名
        Returns:
            feature_names(list): 列名信息
        """
        # 固定顺序，否则模型预测会出错
        if self.type == 'df':
            if self.scale == 'all':
                feature_names = [
                    'my_x', 'my_y', 'my_z', 'my_v_x', 'my_v_y', 'my_v_z',
                    'my_rot_x', 'my_rot_y', 'my_rot_z', 'enemy_x', 'enemy_y',
                    'enemy_z', 'enemy_v_x', 'enemy_v_y', 'enemy_v_z',
                    'my_v_x_acc', 'enemy_v_x_acc', 'my_v_y_acc',
                    'enemy_v_y_acc', 'my_v_z_acc', 'enemy_v_z_acc',
                    'x_me_minus', 'y_me_minus', 'z_me_minus', 'v_x_me_minus',
                    'v_y_me_minus', 'v_z_me_minus', 'distance', 'cos',
                    'speedAll', 'cosValue'
                ]
            else:
                feature_names = ['cosValue', 'speedAll', 'distance']
        elif self.type == 'dcs':
            if self.scale == 'all':
                feature_names = [
                    'z', 'Roll', 'Pitch', 'Yaw', 'x', 'y', 'Heading',
                    'enemy_z', 'enemy_x', 'enemy_y', 'speed_x', 'speed_y',
                    'speed_z', 'enemy_speed_x', 'enemy_speed_y',
                    'enemy_speed_z', 'distance', 'speed', 'speed_connect_cos',
                    'enemy_speed_connect_cos', 'relative_x', 'relative_z',
                    'relative_y', 'relative_speed_x', 'relative_speed_y',
                    'relative_speed_z', 'relative_speed', 'speed_cos'
                ]
            elif self.scale == 'light':
                feature_names = [
                    'distance', 'speed_connect_cos', 'enemy_speed_connect_cos',
                    'relative_y', 'speed_cos'
                ]
            else:
                feature_names = [
                    'z', 'Roll', 'Pitch', 'Yaw', 'x', 'y', 'Heading',
                    'enemy_z', 'enemy_x', 'enemy_y'
                ]

        return feature_names

    # 留出法数据
    def _hold_out(self, raw_train, percent_train):
        """ 获取留出法的训练数据
        Args:
            raw_train(dataframe): 原始数据
            percent_train(int): 训练集占比
        Returns:
            train(dataframe): 训练集
            val(dataframe): 验证集
        """
        # 获取保留的列名
        feature_names = self._feature_name()
        # 切分训练集、验证集
        train_data, val_data = self.train_val_split(raw_train,
                                                    percent=percent_train)
        if self.type == 'dcs':
            train_data = self.smote(train_data)
        # 获取训练验证数据和标签数据
        X_train, X_val, y_train, y_val = train_data[feature_names], val_data[
            feature_names], train_data['label'], val_data['label']
        return X_train, X_val, y_train, y_val

    # k折交叉验证数据
    def _k_fold(self, raw_train, k):
        """ 获取交叉验证数据
        Args:
            raw_train(dataframe): 原始数据
            k(int): 交叉折数
        Returns:
            train(dataframe): k折交叉验证的训练集
            val(dataframe): 验证集
        """
        # 获取保留列名
        feature_names = self._feature_name()
        # 根据id分组
        groups = list(raw_train['id'])
        # 分组交叉验证
        gkf = GroupKFold(n_splits=k)
        data_list = []
        # 获取交叉验证数据
        for train_index, val_index in gkf.split(raw_train[feature_names],
                                                raw_train['label'],
                                                groups=groups):
            # 根据index索引获取每一折数据
            X_train, y_train, X_val, y_val = raw_train.iloc[train_index][feature_names], \
                                             raw_train.iloc[train_index]['label'], \
                                             raw_train.iloc[val_index][feature_names], \
                                             raw_train.iloc[val_index]['label']
            # 将数据加入列表保存
            data_list.append([X_train, X_val, y_train, y_val])
        # 返回列表
        return data_list

    def _bootstrap(self, raw_train):
        """ 获取提升法数据
        Args:
            raw_train(dataframe): 原始数据
        Returns:
            train(dataframe): 提升法训练集
            val(dataframe): 验证集
        """
        # 获取保留列名
        feature_names = self._feature_name()
        # 获取所有数据id，并去重
        ids = pd.DataFrame(set(raw_train['id']), columns=['id'], index=None)
        random.seed(self.seed)
        # 根据id采样
        train_group_ids = ids.sample(frac=1.0,
                                     replace=True,
                                     random_state=self.seed)
        # 总id减去训练集的id，得到验证集id
        val_group_ids = ids.loc[ids.index.difference(
            train_group_ids.index)].copy()
        # 创建两个dataframe
        train_data = pd.DataFrame()
        val_data = pd.DataFrame()
        # 获取训练与验证数据id号
        train_group_ids = list(train_group_ids['id'])
        val_group_ids = list(val_group_ids['id'])
        # 根据id获取数据
        for train_group_id in train_group_ids:
            train_data = train_data.append(
                raw_train[raw_train['id'] == train_group_id])
        for val_group_id in val_group_ids:
            val_data = val_data.append(
                raw_train[raw_train['id'] == val_group_id])
        # 切分训练数据与真实标签
        X_train, X_val, y_train, y_val = train_data[feature_names], val_data[
            feature_names], train_data['label'], val_data['label']

        return X_train, X_val, y_train, y_val

    # 定义LSTM模型
    def _lstm(self, n_steps, n_features):
        model = Sequential()
        model.add(
            LSTM(units=100,
                 activation='relu',
                 input_shape=(n_steps, n_features)))
        model.add(Dense(1))
        model.compile(optimizer='adam', loss='mse', metrics=['accuracy'])
        return model

    # 定义lightgbm模型
    def _lgb(self):
        lgb_model = lgb.LGBMClassifier(objective='binary',
                                       boosting_type='gbdt',
                                       num_leaves=32,
                                       max_depth=6,
                                       learning_rate=0.01,
                                       n_estimators=100000,
                                       subsample=0.8,
                                       feature_fraction=0.6,
                                       reg_alpha=10,
                                       reg_lambda=12,
                                       random_state=self.seed,
                                       is_unbalance=True,
                                       metric='auc')
        return lgb_model

    def _xgb(self):
        xgb_model = xgb.XGBClassifier(booster='gbtree',
                                      objective='binary:logistic',
                                      eval_metric='auc',
                                      silent=0,
                                      eta=0.01,
                                      gamma=0.1,
                                      max_depth=6,
                                      min_child_weight=3,
                                      subsample=0.7,
                                      colsample_bytree=0.5,
                                      reg_alpha=0,
                                      reg_lambda=1,
                                      n_estimators=100000,
                                      seed=2021)
        return xgb_model

    # 定义svm模型
    @staticmethod
    def _svm():
        svm_model = svm.SVC(C=1.0,
                            kernel='rbf',
                            degree=3,
                            gamma='auto',
                            coef0=0.0,
                            shrinking=True,
                            probability=True,
                            tol=0.001,
                            class_weight=None,
                            verbose=True,
                            max_iter=1000,
                            decision_function_shape='ovr',
                            random_state=None)
        return svm_model

    # 定义集成模型
    @staticmethod
    def _ensemble():
        clf1 = LogisticRegression(random_state=1)
        clf2 = RandomForestClassifier(n_estimators=50, random_state=1)
        clf3 = CatBoostClassifier(iterations=100,
                                  depth=5,
                                  learning_rate=0.5,
                                  loss_function='Logloss',
                                  logging_level='Verbose')

        ensemble_model = VotingClassifier(estimators=[('lr', clf1),
                                                      ('rf', clf2),
                                                      ('gnb', clf3)],
                                          voting='soft')

        return ensemble_model

    def _train_lstm(self, raw_train, n_steps, val_type, k, percent_train=0.8):
        """ 训练lstm模型
        Args:
            raw_train(dataframe): 原始数据
            n_steps: 前向依赖时间步
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            importance(dataframe): 特征重要度
            best_thread(float): 最佳阈值
        """
        # 获取保留列名
        if val_type == 'hold-out':  # 留出法
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            # 数据准备
            X_train = series_to_supervised(X_train, n_steps)
            X_val = series_to_supervised(X_val, n_steps)
            y_train = y_train[n_steps - 1:]
            y_val = y_val[n_steps - 1:]
            lstm_model = self._lstm(n_steps=n_steps,
                                    n_features=X_train.shape[-1])
            # 模型训练，使用早停策略

            my_callbacks = [
                RocAucMetricCallback(),  # include it before EarlyStopping!
                kcallbacks.EarlyStopping(monitor='val_auc',
                                         patience=3,
                                         verbose=1,
                                         mode='max')
            ]
            lstm_model.fit(X_train,
                           y_train,
                           epochs=20,
                           batch_size=256,
                           validation_data=(X_val, y_val),
                           verbose=True,
                           shuffle=False,
                           callbacks=my_callbacks)
            # 模型预测
            pred_val = lstm_model.predict(X_val)
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return lstm_model, None, best_thread

        elif val_type == 'k-fold':
            # 创建模型保存列表
            model_list = []
            # 创建最佳阈值列表
            BC_list = []
            # 获取k折数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                y_train = to_categorical(y_train.values.tolist(),
                                         num_classes=None)
                y_val = to_categorical(y_val.values.tolist(), num_classes=None)
                # 获取lgb模型并且编号
                names['lstm_%s' % i] = self._lstm(n_steps_in=20,
                                                  n_features=len(
                                                      X_train.columns))
                # 模型训练，使用早停策略
                names['lstm_%s' % i].fit(X_train,
                                         y_train,
                                         epochs=100,
                                         batch_size=256,
                                         validation_data=(X_val, y_val),
                                         verbose=True,
                                         shuffle=False)
                # 预测验证集
                pred_val = names['lstm_%s' % i].predict(X_val)[:, 1]
                # 获取最佳阈值
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 保存k个模型
                model_list.append(names['lstm_%s' % i])
                # 保存k个阈值
                BC_list.append(names['best_thread % s' % i])

            return model_list, None, BC_list

        elif val_type == 'bootstrap':
            # 获取提升法数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            y_train = to_categorical(y_train.values.tolist(), num_classes=None)
            y_val = to_categorical(y_val.values.tolist(), num_classes=None)
            # 获取lgb模型
            lstm_model = self._lstm(n_steps_in=20,
                                    n_features=len(X_train.columns))
            # 模型训练，使用早停策略
            lstm_model.fit(X_train,
                           y_train,
                           epochs=100,
                           batch_size=256,
                           validation_data=(X_val, y_val),
                           verbose=True,
                           shuffle=False)

            # 模型预测
            pred_val = lstm_model.predict(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return lstm_model, None, best_thread
        else:
            print("无该验证方法!")
            exit(-1)

    def _train_xgb(self, raw_train, val_type, k, percent_train=0.8):
        # 获取保留列名
        feature_names = self._feature_name()
        if val_type == 'hold-out':  # 留出法
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            xgb_model = self._xgb()
            xgb_model.fit(X_train,
                          y_train,
                          eval_set=[(X_train, y_train), (X_val, y_val)],
                          verbose=100,
                          early_stopping_rounds=50)
            # 获取特征重要性
            df_importance = pd.DataFrame({
                'column':
                feature_names,
                'importance':
                xgb_model.feature_importances_,
            }).sort_values(by='importance',
                           ascending=False).reset_index(drop=True)
            # 最大最小归一
            df_importance['importance'] = (
                df_importance['importance'] -
                df_importance['importance'].min()) / (
                    df_importance['importance'].max() -
                    df_importance['importance'].min())
            # 模型预测
            pred_val = xgb_model.predict_proba(X_val)[:, 1]
            X_val['pred_prob'] = pred_val
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return xgb_model, df_importance, best_thread

        elif val_type == 'k-fold':
            # 创建模型保存列表
            model_list = []
            # 创建阈值保存列表
            BC_list = []
            # 创建重要性保存列表
            importance_list = []
            # 获取k折数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                # 获取lgb模型并且编号
                names['lgb_%s' % i] = self._xgb()
                # 模型训练，使用早停策略
                names['lgb_%s' % i].fit(X_train,
                                        y_train,
                                        eval_set=[(X_train, y_train),
                                                  (X_val, y_val)],
                                        verbose=100,
                                        early_stopping_rounds=50)
                # 获取特征重要性
                df_importance = pd.DataFrame({
                    'column':
                    feature_names,
                    'importance':
                    names['lgb_%s' % i].feature_importances_,
                }).sort_values(by='importance',
                               ascending=False).reset_index(drop=True)
                # 预测验证集
                pred_val = names['lgb_%s' % i].predict_proba(X_val)[:, 1]
                # 获取最佳阈值
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 保存k个模型
                model_list.append(names['lgb_%s' % i])
                # 保存k个重要性
                importance_list.append(df_importance)
                # 保存k个阈值
                BC_list.append(names['best_thread % s' % i])

            mean_dict = dict()
            # 获取平均特征重要度
            for feat in feature_names:
                mean_dict[feat] = 0

            for df_importance in importance_list:
                for feat in feature_names:
                    mean_dict[feat] += int(
                        df_importance[df_importance['column'] ==
                                      feat]['importance'].values[0])

            for feat in feature_names:
                mean_dict[feat] /= k
            # 重要度排序
            mean_imp_df = pd.DataFrame({
                'column': feature_names,
                'importance': list(mean_dict.values()),
            }).sort_values(by='importance',
                           ascending=False).reset_index(drop=True)
            # 最大最小归一
            mean_imp_df['importance'] = (
                df_importance['importance'] -
                df_importance['importance'].min()) / (
                    df_importance['importance'].max() -
                    df_importance['importance'].min())
            # 获取平均最佳阈值
            mean_BC = np.array(BC_list).mean()

            return model_list, mean_imp_df, mean_BC

        elif val_type == 'bootstrap':
            # 获取提升法数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            # 获取lgb模型
            xgb_model = self._xgb()
            # 模型训练，使用早停策略
            xgb_model.fit(X_train,
                          y_train,
                          eval_set=[(X_train, y_train), (X_val, y_val)],
                          verbose=100,
                          early_stopping_rounds=50)
            # 获取模型的特征重要性特征
            df_importance = pd.DataFrame({
                'column':
                feature_names,
                'importance':
                xgb_model.feature_importances_,
            }).sort_values(by='importance',
                           ascending=False).reset_index(drop=True)
            # 最大最小归一
            df_importance['importance'] = (
                df_importance['importance'] -
                df_importance['importance'].min()) / (
                    df_importance['importance'].max() -
                    df_importance['importance'].min())

            # 模型预测
            pred_val = xgb_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return xgb_model, df_importance, best_thread

        else:
            print("无该验证方法!")
            exit(-1)

    def _train_lgb(self, raw_train, val_type, k, percent_train=0.8):
        """ 训练lightgbm模型
        Args:
            raw_train(dataframe): 原始数据
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            importance(dataframe): 特征重要度
            best_thread(float): 最佳阈值
        """
        # 获取保留列名
        feature_names = self._feature_name()
        if val_type == 'hold-out':  # 留出法
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            # 获取lgb模型
            lgb_model = self._lgb()
            # 模型训练，使用早停策略
            lgb_model.fit(X_train,
                          y_train,
                          eval_names=['train', 'valid'],
                          eval_set=[(X_train, y_train), (X_val, y_val)],
                          verbose=100,
                          early_stopping_rounds=50)
            # 获取特征重要性
            df_importance = pd.DataFrame({
                'column':
                feature_names,
                'importance':
                lgb_model.feature_importances_,
            }).sort_values(by='importance',
                           ascending=False).reset_index(drop=True)
            # 最大最小归一
            df_importance['importance'] = (
                df_importance['importance'] -
                df_importance['importance'].min()) / (
                    df_importance['importance'].max() -
                    df_importance['importance'].min())
            print(df_importance)
            # 模型预测
            pred_val = lgb_model.predict_proba(X_val)[:, 1]
            X_val['pred_prob'] = pred_val
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return lgb_model, df_importance, best_thread

        elif val_type == 'k-fold':
            # 创建模型保存列表
            model_list = []
            # 创建阈值保存列表
            BC_list = []
            # 创建重要性保存列表
            importance_list = []
            # 获取k折数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                # 获取lgb模型并且编号
                names['lgb_%s' % i] = self._lgb()
                # 模型训练，使用早停策略
                names['lgb_%s' % i].fit(X_train,
                                        y_train,
                                        eval_names=['train', 'valid'],
                                        eval_set=[(X_train, y_train),
                                                  (X_val, y_val)],
                                        verbose=100,
                                        early_stopping_rounds=50)
                # 获取特征重要性
                df_importance = pd.DataFrame({
                    'column':
                    feature_names,
                    'importance':
                    names['lgb_%s' % i].feature_importances_,
                }).sort_values(by='importance',
                               ascending=False).reset_index(drop=True)
                # 预测验证集
                pred_val = names['lgb_%s' % i].predict_proba(X_val)[:, 1]
                # 获取最佳阈值
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 保存k个模型
                model_list.append(names['lgb_%s' % i])
                # 保存k个重要性
                importance_list.append(df_importance)
                # 保存k个阈值
                BC_list.append(names['best_thread % s' % i])

            mean_dict = dict()
            # 获取平均特征重要度
            for feat in feature_names:
                mean_dict[feat] = 0

            for df_importance in importance_list:
                for feat in feature_names:
                    mean_dict[feat] += int(
                        df_importance[df_importance['column'] ==
                                      feat]['importance'].values[0])

            for feat in feature_names:
                mean_dict[feat] /= k
            # 重要度排序
            mean_imp_df = pd.DataFrame({
                'column': feature_names,
                'importance': list(mean_dict.values()),
            }).sort_values(by='importance',
                           ascending=False).reset_index(drop=True)
            # 最大最小归一
            mean_imp_df['importance'] = (
                df_importance['importance'] -
                df_importance['importance'].min()) / (
                    df_importance['importance'].max() -
                    df_importance['importance'].min())
            # 获取平均最佳阈值
            mean_BC = np.array(BC_list).mean()

            return model_list, mean_imp_df, mean_BC

        elif val_type == 'bootstrap':
            # 获取提升法数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            # 获取lgb模型
            lgb_model = self._lgb()
            # 模型训练，使用早停策略
            lgb_model.fit(X_train,
                          y_train,
                          eval_names=['train', 'valid'],
                          eval_set=[(X_train, y_train), (X_val, y_val)],
                          verbose=100,
                          early_stopping_rounds=50)
            # 获取模型的特征重要性特征
            df_importance = pd.DataFrame({
                'column':
                feature_names,
                'importance':
                lgb_model.feature_importances_,
            }).sort_values(by='importance',
                           ascending=False).reset_index(drop=True)
            # 最大最小归一
            df_importance['importance'] = (
                df_importance['importance'] -
                df_importance['importance'].min()) / (
                    df_importance['importance'].max() -
                    df_importance['importance'].min())

            # 模型预测
            pred_val = lgb_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return lgb_model, df_importance, best_thread

        else:
            print("无该验证方法!")
            exit(-1)

    def _train_nb(self, raw_train, val_type, k, percent_train=0.8):
        """ 训练朴素贝叶斯模型
        Args:
            raw_train(dataframe): 原始数据
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            best_thread(float): 最佳阈值
        """
        if val_type == 'hold-out':  # 留出法
            # 获取训练集、验证集
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            # 获取朴素贝叶斯模型
            gnb_model = GaussianNB()
            # 模型训练
            gnb_model.fit(X_train, y_train)
            # 模型预测
            pred_val = gnb_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return gnb_model, None, best_thread

        elif val_type == 'k-fold':
            # 创建模型保存列表
            model_list = []
            # 创建阈值保存列表
            BC_list = []
            # 获取k折交叉验证数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                names['gnb_%s' % i] = GaussianNB()
                # 模型训练
                names['gnb_%s' % i].fit(X_train, y_train)
                # 模型预测
                pred_val = names['gnb_%s' % i].predict_proba(X_val)[:, 1]
                # 阈值搜索
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 模型加入列表
                model_list.append(names['gnb_%s' % i])
                # 阈值加入列表
                BC_list.append(names['best_thread % s' % i])
            # 平均最佳阈值
            mean_BC = np.array(BC_list).mean()

            return model_list, None, mean_BC

        elif val_type == 'bootstrap':
            # 提升法获取训练、验证数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            # 获取模型
            gnb_model = GaussianNB()
            # 模型训练
            gnb_model.fit(X_train, y_train)
            # 模型预测
            pred_val = gnb_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return gnb_model, None, best_thread

        else:
            print("无该验证方法!")
            exit(-1)

    def _train_linearReg(self, raw_train, val_type, k, percent_train=0.8):
        """ 训练线性回归模型
        Args:
            raw_train(dataframe): 原始数据
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            best_thread(float): 最佳阈值
        """
        if val_type == 'hold-out':  # 留出法
            # 获取训练集、验证集
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            # 获取线性回归模型
            linear_model = LinearRegression()
            # 模型训练
            linear_model.fit(X_train, y_train)
            # 模型预测
            pred_val = linear_model.predict(X_val)
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return linear_model, None, best_thread

        elif val_type == 'k-fold':
            # 创建模型保存列表
            model_list = []
            # 阈值保存列表
            BC_list = []
            # k折数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                # 获取模型并编号
                names['linear_%s' % i] = LinearRegression()
                # 模型训练
                names['linear_%s' % i].fit(X_train, y_train)
                # 模型预测
                pred_val = names['linear_%s' % i].predict(X_val)
                # 阈值搜索
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 模型加入列表
                model_list.append(names['linear_%s' % i])
                # 阈值加入列表
                BC_list.append(names['best_thread % s' % i])
            # 平均最佳阈值
            mean_BC = np.array(BC_list).mean()

            return model_list, None, mean_BC

        elif val_type == 'bootstrap':
            # 提升法获取训练、验证数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            linear_model = LinearRegression()
            # 模型训练
            linear_model.fit(X_train, y_train)
            # 模型预测
            pred_val = linear_model.predict(X_val)
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return linear_model, None, best_thread

        else:
            print("无该验证方法!")
            exit(-1)

    def _train_logisticReg(self, raw_train, val_type, k, percent_train=0.8):
        """ 训练逻辑回归模型
        Args:
            raw_train(dataframe): 原始数据
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            best_thread(float): 最佳阈值
        """
        if val_type == 'hold-out':  # 留出法
            # 获取训练集、验证集
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            # 获取逻辑回归模型
            logistic_model = LogisticRegression(C=1.0, penalty='l2', tol=0.01)
            # 模型训练
            logistic_model.fit(X_train, y_train)
            # 模型预测
            pred_val = logistic_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return logistic_model, None, best_thread

        elif val_type == 'k-fold':
            # 模型保存列表
            model_list = []
            # 阈值保存列表
            BC_list = []
            # k交叉数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                # 创建模型并命名
                names['logistic_%s' % i] = LogisticRegression(C=1.0,
                                                              penalty='l2',
                                                              tol=0.01)
                # 模型训练
                names['logistic_%s' % i].fit(X_train, y_train)
                # 模型预测
                pred_val = names['logistic_%s' % i].predict_proba(X_val)[:, 1]
                # 阈值搜索
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 加入模型列表
                model_list.append(names['logistic_%s' % i])
                # 加入阈值列表
                BC_list.append(names['best_thread % s' % i])
            # 平均最佳阈值
            mean_BC = np.array(BC_list).mean()

            return model_list, None, mean_BC

        elif val_type == 'bootstrap':
            # 提升法获取训练、验证数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            # 获取模型
            logistic_model = LogisticRegression(C=1.0, penalty='l2', tol=0.01)
            # 模型训练
            logistic_model.fit(X_train, y_train)
            # 模型预测
            pred_val = logistic_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return logistic_model, None, best_thread

        else:
            print("无该验证方法!")
            exit(-1)

    def _train_svm(self, raw_train, val_type, k, percent_train=0.8):
        """ 训练支持向量机模型
        Args:
            raw_train(dataframe): 原始数据
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            best_thread(float): 最佳阈值
        """
        if val_type == 'hold-out':  # 留出法
            # 留出法获得训练集、验证集
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            # 获取模型
            svm_model = self._svm()
            # 模型训练
            svm_model.fit(X_train, y_train)
            # 模型预测
            pred_val = svm_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return svm_model, None, best_thread

        elif val_type == 'k-fold':
            # 模型保存列表
            model_list = []
            # 阈值保存列表
            BC_list = []
            # k交叉数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                # 获取模型并命名
                names['svm_%s' % i] = self._svm()
                # 模型训练
                names['svm_%s' % i].fit(X_train, y_train)
                # 模型预测
                pred_val = names['svm_%s' % i].predict_proba(X_val)[:, 1]
                # 阈值搜索
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 模型保存
                model_list.append(names['svm_%s' % i])
                # 阈值保存
                BC_list.append(names['best_thread % s' % i])
            # 平均最佳阈值
            mean_BC = np.array(BC_list).mean()

            return model_list, None, mean_BC

        elif val_type == 'bootstrap':
            # 获取数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            # 获取模型
            svm_model = self._svm()
            # 模型训练
            svm_model.fit(X_train, y_train)
            # 模型预测
            pred_val = svm_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return svm_model, None, best_thread

        else:
            print("无该验证方法!")
            exit(-1)

    def _train_ensemble(self, raw_train, val_type, k, percent_train=0.8):
        """ 训练集成模型
        Args:
            raw_train(dataframe): 原始数据
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            best_thread(float): 最佳阈值
        """
        if val_type == 'hold-out':  # 留出法
            # 留出法获得训练集、验证集
            X_train, X_val, y_train, y_val = self._hold_out(
                raw_train, percent_train)
            # 获取集成模型
            ensemble_model = self._ensemble()
            # 模型训练
            ensemble_model.fit(X_train, y_train)
            # 模型预测
            pred_val = ensemble_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)

            return ensemble_model, None, best_thread

        elif val_type == 'k-fold':
            # 模型保存列表
            model_list = []
            # 阈值保存列表
            BC_list = []
            # k交叉数据
            data_list = self._k_fold(raw_train, k)
            # 实现可变变量名
            names = locals()
            for member, i in zip(data_list, range(k)):
                X_train, X_val, y_train, y_val = member[0], member[1], member[
                    2], member[3]
                # 获取模型并命名
                names['ensemble_%s' % i] = self._ensemble()
                # 模型训练
                names['ensemble_%s' % i].fit(X_train, y_train)
                # 模型预测
                pred_val = names['ensemble_%s' % i].predict_proba(X_val)[:, 1]
                # 阈值搜索
                names['best_thread % s' % i] = self._BC_thread_search(
                    y_val, pred_val)
                # 模型保存
                model_list.append(names['ensemble_%s' % i])
                # 阈值保存
                BC_list.append(names['best_thread % s' % i])
            # 阈值平均
            mean_BC = np.array(BC_list).mean()

            return model_list, None, mean_BC

        elif val_type == 'bootstrap':
            # 获取数据
            X_train, X_val, y_train, y_val = self._bootstrap(raw_train)
            # 获取模型
            ensemble_model = self._ensemble()
            # 模型训练
            ensemble_model.fit(X_train, y_train)
            # 模型预测
            pred_val = ensemble_model.predict_proba(X_val)[:, :1]
            # 搜寻最佳阈值
            best_thread = self._BC_thread_search(y_val, pred_val)
            return ensemble_model, None, best_thread

        else:
            print("无该验证方法!")
            exit(-1)

    # 模型训练
    def train_model(self, train, model_type, val_type, k=0, percent_train=0.8):
        """ 模型训练
        Args:
            train(dataframe): 原始数据
            model_type(string): 模型类型
            val_type(string): 验证方式
            k(int): 交叉折数
            percent_train(float): 训练集比例
        Returns:
            model: 训练模型
            importance(dataframe): 特征重要度
            best_thread(float): 最佳阈值
        """
        # lightgbm模型
        if model_type == 'lgb':
            model, df_importance, best_thread = self._train_lgb(
                train, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        # xgboost模型
        if model_type == 'xgb':
            model, df_importance, best_thread = self._train_xgb(
                train, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        # 朴素贝叶斯
        elif model_type == 'nb':
            model, df_importance, best_thread = self._train_nb(
                train, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        # 线性模型
        elif model_type == 'linear':
            model, df_importance, best_thread = self._train_linearReg(
                train, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        # 逻辑回归
        elif model_type == 'logistic':
            model, df_importance, best_thread = self._train_logisticReg(
                train, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        # 支持向量机
        elif model_type == 'svm':
            model, df_importance, best_thread = self._train_svm(
                train, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        # 集成模型
        elif model_type == 'ensemble':
            model, df_importance, best_thread = self._train_ensemble(
                train, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        elif model_type == 'lstm':
            model, df_importance, best_thread = self._train_lstm(
                train, 20, val_type, k, percent_train=percent_train)
            return model, df_importance, best_thread
        else:
            print('无该模型！')
            exit(-1)

    @staticmethod
    def save_model(model, save_dir, name, val_type, best_thread):
        """ 模型保存
        Args:
            model: 模型
            save_dir(string): 保存路径
            name(string): 模型名称
            val_type(string): 验证方式
            best_thread(float): 阈值
        Returns:
            None
        """
        # 如果是k折交叉验证，保存k个模型
        if val_type == 'k-fold':
            # 遍历k个模型
            for i in range(len(model)):
                # 保存路径
                save_path = os.path.join(
                    save_dir, name + str(i) + '_' + val_type + '_' +
                    str(best_thread) + '.pkl')
                # 模型保存
                joblib.dump(model[i], save_path)
        else:
            # 非交叉验证模型保存
            save_path = os.path.join(
                save_dir,
                name + '_' + val_type + '_' + str(best_thread) + '.pkl')
            # 模型保存
            joblib.dump(model, save_path)

    @staticmethod
    def load_model(path):
        """ 加载模型
        Args:
            path(string): 模型路径
        Returns:
            None
        """
        # 模型加载
        model = joblib.load(path)
        return model

    @staticmethod
    def _BC_thread_search(y_val, pred_val):
        """ 阈值搜索
        Args:
            y_val: 真实标签
            pred_val: 预测标签
        Returns:
            best_t(float): 阈值
        """
        # f1阈值敏感，暴力搜索最佳阈值
        t0 = 0.05
        v = 0.002
        best_t = t0
        best_f1 = 0
        # 循环搜索
        for step in tqdm(range(500)):
            curr_t = t0 + step * v
            y = [1 if x >= curr_t else 0 for x in pred_val]
            curr_f1 = f1_score(y_val, y)
            # 如果f1变大则替换
            if curr_f1 > best_f1:
                best_t = curr_t
                best_f1 = curr_f1

        print(f'search finish. best thread is {best_t}')

        return best_t

    def offline_predict(self, model, df_test, threshold=0.5):
        """ 离线预测
        Args:
            model: 模型
            df_test(dataframe): 测试集
            threshold(float): 最佳阈值
        Returns:
            pred_prob(dataframe): 预测概率
            pred_label(dataframe): 预测标签
        """
        # 获取需要的列名
        feature_names = self._feature_name()
        test_pred = df_test.copy(deep=True)
        # 创建预测列名
        test_pred['pred_prob'] = 0
        # 如果是k折交叉验证预测k次
        if isinstance(model, list):
            for i in range(len(model)):
                try:
                    # 预测概率相加
                    test_pred['pred_prob'] += model[i].predict_proba(
                        df_test[feature_names])[:, 1]
                except AttributeError:
                    # 预测概率相加
                    test_pred['pred_prob'] += model[i].predict(
                        df_test[feature_names])
            # 平均k个预测概率
            test_pred['pred_prob'] /= len(model)
            # 创建标签预测列
            test_pred['pred_label'] = 0
            # 大于阈值的预测为1，小于的预测为0
            test_pred.loc[test_pred['pred_prob'] > threshold, 'pred_label'] = 1
            # 转列表
            pred_prob = test_pred['pred_prob'].values.tolist()
            # 转列表
            pred_label = test_pred['pred_label'].values.tolist()
        # 其他验证方法
        else:
            try:
                # 如果非线性模型预测方式
                test_pred['pred_prob'] = model.predict_proba(
                    df_test[feature_names])[:, 1]
            except AttributeError:
                # 线性模型预测方式
                test_pred['pred_prob'] = model.predict(df_test[feature_names])
            except ValueError:
                # lstm预测方式
                lstm_test = series_to_supervised(df_test[feature_names], 20)
                pred_prob = model.predict(lstm_test)
                pred_prob = np.concatenate(
                    [np.zeros(19).reshape(19, 1),
                     np.array(pred_prob)])
                test_pred['pred_prob'] = pred_prob
            test_pred['pred_label'] = 0
            # 大于阈值的预测为1，小于的预测为0
            test_pred.loc[test_pred['pred_prob'] > threshold, 'pred_label'] = 1
            # 转列表
            pred_prob = test_pred['pred_prob'].values.tolist()
            # 转列表
            pred_label = test_pred['pred_label'].values.tolist()

        test_pred.to_csv('a2a_pred.csv', index=False)

        return pred_prob, pred_label

    def online_predict(self, model, row, threshold=0.5):
        """ 在线预测
        Args:
            model: 模型
            row(dict): 一行数据
            threshold(float): 最佳阈值
        Returns:
            pred_prob(float): 预测概率
            pred_label(int): 预测标签
        """
        # 获取需要的列名
        feature_names = self._feature_name()

        try:
            # 如果非线性模型预测方式
            pred_prob = model.predict_proba(
                np.array(row[feature_names]).reshape(1, -1))[:, 1]
        except AttributeError:
            # 线性模型预测方式
            pred_prob = model.predict(
                np.array(row[feature_names]).reshape(1, -1))[:, 1]
        # 在线场景下只有一条数据，返回实数，而非列表
        pred_prob = pred_prob[0]
        # 标签预测，大于阈值预测为1否则为0
        pred_label = 1 if pred_prob > threshold else 0

        return pred_prob, pred_label

    def score(self, label, pred_prob, pred_label, name, val_type):
        """ 评分函数
        Args:
            label: 真实标签
            pred_prob: 预测概率
            pred_label: 预测标签
            name:模型名称
            val_type:验证方式
        Returns:
            dict: 评分字典
        """
        # 获得评价指标
        cr = classification_report(label, pred_label, digits=4)
        print(cr)
        # 获取auc值
        auc = roc_auc_score(label, pred_prob)
        print('auc:', auc)
        # 获取acc值
        acc = accuracy_score(label, pred_label)
        print('acc:', acc)
        # 获取roc曲线
        roc_title = f'AtoA offline {name} {val_type} ROC Curve'
        roc = self._plot_roc(label, pred_prob, roc_title)
        # 获取pr曲线
        pr_title = f'AtoA offline {name} {val_type} PR Curve'
        pr = self._plot_pr(label, pred_prob, pr_title)

        # 混淆矩阵
        cm = confusion_matrix(label, pred_label)
        print("---------------混淆矩阵\n", cm)
        # 评价字典
        dictionary = {'auc': auc, 'cr': cr, 'acc': acc, 'roc': roc, 'pr': pr}
        return dictionary

    @staticmethod
    def _plot_roc(label, pred_prob, roc_title):
        """ roc画图
        Args:
            label: 真实标签
            pred_prob: 预测概率
        Returns:
            fig: roc图
        """
        # 画ROC
        fig = plt.figure()
        fpr_test, tpr_test, _ = roc_curve(label, pred_prob)
        plt.plot(fpr_test, tpr_test)
        plt.plot([0, 1], [0, 1], 'd--')
        # x轴名称
        plt.xlabel('False Positive Rate')
        # y轴名称
        plt.ylabel('True Positive Rate')
        # 图名称
        plt.title(roc_title)

        return fig

    @staticmethod
    def _plot_pr(label, pred_prob, pr_title):
        """ pr画图
        Args:
            label: 真实标签
            pred_prob: 预测概率
        Returns:
            fig: pr图
        """
        # 画PR
        fig = plt.figure()
        precision, recall, _ = precision_recall_curve(label, pred_prob)
        plt.step(recall, precision, color='b', alpha=0.2, where='post')
        plt.fill_between(recall, precision, step='post', alpha=0.2, color='b')
        # x轴名称
        plt.xlabel('Recall')
        # y轴名称
        plt.ylabel('Precision')
        # 设置xy轴范围
        plt.ylim([0.0, 1.05])
        plt.xlim([0.0, 1.0])
        # 计算AP
        AP = average_precision_score(label,
                                     pred_prob,
                                     average='macro',
                                     pos_label=1,
                                     sample_weight=None)
        # 图名称
        plt.title(pr_title + ':AP={0:0.2f}'.format(AP))
        return fig

    def evaluate(self, save_dir, dictionary, name, val_type):
        """ 评估保存函数
        Args:
            save_dir(string): 保存路径
            dictionary(dict): 字典
            name(string): 模型名称
            val_type(string): 验证方式
        Returns:
            None
        """
        # roc图
        plt_roc = dictionary['roc']
        # pr图
        pr_roc = dictionary['pr']
        # 保存roc图
        plt_roc.savefig(
            os.path.join(save_dir, name + '_' + val_type + '_roc.png'))
        # 保存pr图
        pr_roc.savefig(
            os.path.join(save_dir, name + '_' + val_type + '_pr.png'))
        # 保存评价指标，形成txt文件
        with open(os.path.join(save_dir, name + '_' + val_type + '_score.txt'),
                  'a+',
                  encoding='utf-8') as fr:
            line = '模型{}的'.format(
                name) + self.mode + ' auc为{:.4f},acc为{:.4f}\n'.format(
                    dictionary['auc'], dictionary['acc'])
            # 一行数据写入文件
            fr.write(line)
            # 评估报告写入文件
            fr.write(self.mode + '评估报告为:\n')
            # 评估字典写入文件
            fr.write(dictionary['cr'])


a = int(input())

for i in range(a):
  b = raw_input()
  bLength = len(b)//2
  x = 0
  string = ""

  for j in range(bLength):
    string += (b[x+1] * int(b[x]))
    x = x+2

  print(string)


"""
Wind Statistics
----------------

Topics: Using array methods over different axes, fancy indexing.

1. The data in 'wind.data' has the following format::

        61  1  1 15.04 14.96 13.17  9.29 13.96  9.87 13.67 10.25 10.83 12.58 18.50 15.04
        61  1  2 14.71 16.88 10.83  6.50 12.62  7.67 11.50 10.04  9.79  9.67 17.54 13.83
        61  1  3 18.50 16.88 12.33 10.13 11.17  6.17 11.25  8.04  8.50  7.67 12.75 12.71

   The first three columns are year, month and day.  The
   remaining 12 columns are average windspeeds in knots at 12
   locations in Ireland on that day.

   Use the 'loadtxt' function from numpy to read the data into
   an array.

2. Calculate the min, max and mean windspeeds and standard deviation of the
   windspeeds over all the locations and all the times (a single set of numbers
   for the entire dataset).

3. Calculate the min, max and mean windspeeds and standard deviations of the
   windspeeds at each location over all the days (a different set of numbers
   for each location)

4. Calculate the min, max and mean windspeed and standard deviations of the
   windspeeds across all the locations at each day (a different set of numbers
   for each day)

5. Find the location which has the greatest windspeed on each day (an integer
   column number for each day).

6. Find the year, month and day on which the greatest windspeed was recorded.

7. Find the average windspeed in January for each location.

You should be able to perform all of these operations without using a for
loop or other looping construct.

Bonus
~~~~~

1. Calculate the mean windspeed for each month in the dataset.  Treat
   January 1961 and January 1962 as *different* months. (hint: first find a
   way to create an identifier unique for each month. The second step might
   require a for loop.)

2. Calculate the min, max and mean windspeeds and standard deviations of the
   windspeeds across all locations for each week (assume that the first week
   starts on January 1 1961) for the first 52 weeks. This can be done without
   any for loop.

Bonus Bonus
~~~~~~~~~~~

Calculate the mean windspeed for each month without using a for loop.
(Hint: look at `searchsorted` and `add.reduceat`.)

Notes
~~~~~

These data were analyzed in detail in the following article:

   Haslett, J. and Raftery, A. E. (1989). Space-time Modelling with
   Long-memory Dependence: Assessing Ireland's Wind Power Resource
   (with Discussion). Applied Statistics 38, 1-50.

See :ref:`wind-statistics-solution`.
"""

from numpy import loadtxt
import numpy as np

# 1
data = loadtxt("wind.data")

# 2
wind_data = data[::, 3:15]

overall_min = wind_data.min()
overall_max = wind_data.max()
overall_mean = wind_data.mean()
overall_std = wind_data.std()

# 3
locations_min = wind_data.min(axis=0)
locations_max = wind_data.max(axis=0)
locations_mean = wind_data.mean(axis=0)
locations_std = wind_data.std(axis=0)

# 4
days_min = wind_data.min(axis=1)
days_max = wind_data.max(axis=1)
days_mean = wind_data.mean(axis=1)
days_std = wind_data.std(axis=1)

# 5
indexes_days_max = wind_data.argmax(axis=1)

# 6
index_overall_max = wind_data.argmax()
y = index_overall_max // wind_data.shape[1]
max_windspeed_day = data[y, 0:3]

# 7
all_january = data[data[:, 1] == 1]
all_january_wind = all_january[::, 3:15]

all_january_average = all_january_wind.mean()

# bonus1
january_means = []
y = 0
previous_y = y
for i in range(17):
    y = 31*(i+1)
    january_means.append(all_january_wind[previous_y:y, ::].mean())
    previous_y = y

# bonus2
weeks = wind_data[:52*7, ::]
weeks = np.reshape(weeks, (52, 7*12))

weeks_min = weeks.min(axis=1)
weeks_max = weeks.max(axis=1)
weeks_mean = weeks.mean(axis=1)
weeks_std = weeks.std(axis=1)

# bonus3


import time
import logging
import random
import unittest
import numpy as np

from tdamapper.neighbors import BallNeighbors, KNNeighbors

logger = logging.getLogger()
logger.level = logging.INFO

def dist(x, y):
    return np.linalg.norm(x - y)

def dataset(dim=1, num=10000):
    return [np.random.rand(dim) for _ in range(num)]

class TestSearch(unittest.TestCase):

    def testTrivial(self):
        data = dataset(dim=1, num=100)
        bs = BallNeighbors(10, dist)
        bs.fit(data)
        for x in data:
            result = bs.search(x)
            expected = [y for y in data if dist(x, y) <= 10]
            self.assertEqual(len(expected), len(result))

    def testBench(self):
        times = 10
        data = dataset()
        bs = BallNeighbors(0.0005, dist)
        ks = KNNeighbors(10, dist)
        t0 = time.time()
        for _ in range(times):
            bs.fit(data)
        t1 = time.time()
        for _ in range(times):
            ks.fit(data)
        t2 = time.time()
        for _ in range(times):
            x = random.choice(data)
            bsResults = bs.search(x)
        t3 = time.time()
        for _ in range(times):
            x = random.choice(data)
            knnResults = ks.search(x)
        t4 = time.time()
        logger.debug(f'Ball Search: {len(bsResults)} results, fit in {t1 - t0}s, search in {t3 - t2}s')
        logger.debug(f'KNN Search: {len(knnResults)} results, fit in {t2 - t1}s, search in {t4 - t3}s')


from pyspark.sql import SparkSession
from pyspark import SparkContext
from pyspark.sql import functions as f

sc = SparkContext("local[*]", "test").getOrCreate()
spark = SparkSession.builder.master("local[*]").getOrCreate()

regExp = r"^[\w - \.]+@([\w -]+\.)+[\w -]{2,4}$"

columns = ["correo"]

data = [["dpinedaj@unal.edu.co"],
        ["vanesalo10@gmail.com"],
        ["dpineda@other.com"],
        ["jass.com"],
        ["TestMail"]]

df = sc.parallelize(data).toDF(columns)

df2 = df.withColumn("validation", f.when(f.regexp_extract(
    "correo", regExp, 1) != '', f.lit(True)).otherwise(f.lit(False)))


from multiprocessing.queues import Empty
import torch
from typing import Dict

import asrel.core.workers.events as events
from asrel.pipelines.base import BasePipeline

class StandardDatasetPipeline(BasePipeline):
  """
  Standard dataset pipeline
  """
  def __init__(
    self,
    **kwargs,
  ):
    super().__init__(**kwargs)

    self.store_output_queue = self.registry.output_queues["store"][0]
    self.learner_input_queue = self.registry.input_queues["learner"][0]

    config = self.registry.configs["learner"][0]
    self.learner_device = torch.device(
      config.get("learner_config", {}).get("device", "cuda")
    )

  def run(self):
    while self.process_state["running"]:
      output = self._get_output()
      if not output: continue

      batch_tensor = self._process_output(output)

      task = {
        "type": events.LEARNER_TRAIN_TASK,
        "data": batch_tensor,
      }
      self.send_task(self.learner_input_queue, task)

  def _get_output(self) -> Dict:
    try:
      output = self.store_output_queue.get(block=True, timeout=self.queue_timeout)

      if output["type"] != events.RETURNED_BATCH_DATA_EVENT:
        return None # Note this skips other returned events from the actor

      return output
    except Empty:
      return None

  def _process_output(self, output: Dict) -> Dict[str, torch.Tensor]:
    batch_tensor = {
      k: torch.tensor(v, device=self.learner_device)
      for k, v in output["data"].items()
    }
    return batch_tensor


from line import Line
from line_to_point_adapter import LineToPointAdapter
from point import Point
from rectangle import Rectangle

def draw_point(p):
    print('.', end='')

def draw(rcs):
    print('Drawing some rectangles...')
    for rc in rcs:
        for line in rc:
            adapter = LineToPointAdapter(line)
            for p in adapter:
                draw_point(p)
    print('\n')

rs = [
    Rectangle(1, 1, 10, 10),
    Rectangle(3, 3, 6, 6)
]

draw(rs)
draw(rs)

print(hash(Line(Point(1, 1), Point(10, 10))))


import cv2
import tensorflow as tf
from matplotlib import pyplot as plt
from keras import backend as K
from glob import glob
import numpy as np
import os
import streamlit as st
from PIL import Image

#Read data

import pandas as pd
df = pd.read_csv('train.csv')

# Working with the dataframe.
df.rename(columns = {'class':'class_name'}, inplace = True)

# Creating new columns called case, day and slice to store caseid, day9d, slideid
df["case"] = df["id"].apply(lambda x: int(x.split("_")[0].replace("case", "")))
df["day"] = df["id"].apply(lambda x: int(x.split("_")[1].replace("day", "")))
df["slice"] = df["id"].apply(lambda x: x.split("_")[3])

# Extracting images from Train folder
TRAIN_DIR="/Users/carlosnino/Documents/random/AML/proyectoF/train"
all_train_images = glob(os.path.join(TRAIN_DIR, "**", "*.png"), recursive=True)
x = all_train_images[0].rsplit("/", 4)[0]

path_partial_list = []
for i in range(0, df.shape[0]):
    path_partial_list.append(os.path.join(x,
                          "case"+str(df["case"].values[i]),
                          "case"+str(df["case"].values[i])+"_"+ "day"+str(df["day"].values[i]),
                          "scans",
                          "slice_"+str(df["slice"].values[i])))
df["path_partial"] = path_partial_list

path_partial_list = []
for i in range(0, len(all_train_images)):
    path_partial_list.append(str(all_train_images[i].rsplit("_",4)[0]))

tmp_df = pd.DataFrame()
tmp_df['path_partial'] = path_partial_list
tmp_df['path'] = all_train_images

# Adding the path to images to the dataframe
df = df.merge(tmp_df, on="path_partial").drop(columns=["path_partial"])

# Creating new columns height and width from the path details of images
df["width"] = df["path"].apply(lambda x: int(x[:-4].rsplit("_",4)[1]))
df["height"] = df["path"].apply(lambda x: int(x[:-4].rsplit("_",4)[2]))

# Deleting redundant columns
del x,path_partial_list,tmp_df

def rle_decode(mask_rle, shape):
    '''mask_rle: run-length as string formated (start length)
    shape: (height,width) of array to return
    Returns numpy array, 1 - mask, 0 - background
    '''
    s = mask_rle.split()
    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]
    starts -= 1
    ends = starts + lengths
    img = np.zeros(shape[0]*shape[1], dtype=np.float32)
    for lo, hi in zip(starts, ends):
        img[lo:hi] = 1
    return img.reshape(shape)  # Needed to align to RLE direction

df_train = pd.DataFrame({'id':df['id'][::3]})

df_train['large_bowel'] = df['segmentation'][::3].values
df_train['small_bowel'] = df['segmentation'][1::3].values
df_train['stomach'] = df['segmentation'][2::3].values

df_train['path'] = df['path'][::3].values
df_train['case'] = df['case'][::3].values
df_train['day'] = df['day'][::3].values
df_train['slice'] = df['slice'][::3].values
df_train['width'] = df['width'][::3].values
df_train['height'] = df['height'][::3].values

df_train.reset_index(inplace=True,drop=True)
df_train = df_train.dropna()
df_train.reset_index(inplace=True,drop=True)

im_large = df_train['large_bowel'][0]
mask_large = rle_decode(im_large, (266,266,1))

num_0_large = list(mask_large[:][:].flatten()).count(0)
num_1_large = list(mask_large[:][:].flatten()).count(1)

weight_for_0_large = (1.0 / num_0_large) * ((num_0_large+num_1_large) / 2.0)
weight_for_1_large = (1.0 / num_1_large) * ((num_0_large+num_1_large) / 2.0)

class_weights_large = {0:weight_for_0_large, 1:weight_for_1_large}

im_small = df_train['small_bowel'][0]
mask_small = rle_decode(im_small, (266,266,1))

num_0_small = list(mask_small[:][:].flatten()).count(0)
num_1_small = list(mask_small[:][:].flatten()).count(1)

weight_for_0_small = (1.0 / num_0_small) * ((num_0_small+num_1_small) / 2.0)
weight_for_1_small = (1.0 / num_1_small) * ((num_0_small+num_1_small) / 2.0)

class_weights_small = {0:weight_for_0_small, 1:weight_for_1_small}

im_stomach = df_train['stomach'][0]
mask_stomach = rle_decode(im_stomach, (266,266,1))

num_0_stomach = list(mask_stomach[:][:].flatten()).count(0)
num_1_stomach = list(mask_stomach[:][:].flatten()).count(1)

weight_for_0_stomach = (1.0 / num_0_stomach) * ((num_0_stomach+num_1_stomach) / 2.0)
weight_for_1_stomach = (1.0 / num_1_stomach) * ((num_0_stomach+num_1_stomach) / 2.0)

class_weights_stomach = {0:weight_for_0_stomach, 1:weight_for_1_stomach}

def custom_binary_loss(y_true, y_pred):
  y_true = K.flatten(y_true)
  y_pred = K.flatten(y_pred)
  y_pred = K.clip(y_pred, K.epsilon(), 1 - K.epsilon())

  term_0 = 1.4 * weight_for_0_large*((1 - y_true) * K.log(1 - y_pred + K.epsilon()))  # Cancels out when target is 1
  term_1 = 0.55 * weight_for_1_large*(y_true * K.log(y_pred + K.epsilon())) # Cancels out when target is 0

  return -K.mean(term_0 + term_1, axis=-1)

best_model = tf.keras.models.load_model('best_model_AML.h5', custom_objects={'custom_binary_loss': custom_binary_loss})

img_path = df_train['path'][1]
img = cv2.imread(img_path, cv2.IMREAD_ANYDEPTH)
img = (img - img.min())/(img.max() - img.min())*255.0
img = cv2.resize(img, (256, 256))
img = np.expand_dims(img, axis=-1)
img = img.astype(np.float32) / 255.
pred = best_model.predict(np.array([img]))

def color_class(pred):
  classes = {'large bowel':0.3, 'small bowel':0.5, 'stomach':0.9}
  threshold = 0.95
  t = 0.94999
  pixels_to_change_large = np.where(pred[0][0] > threshold)
  pixels_to_change1_large = np.where(pred[0][0] < t)

  pixels_to_change_small = np.where(pred[1][0] > threshold)
  pixels_to_change1_small = np.where(pred[1][0] < t)

  pixels_to_change_stomach = np.where(pred[2][0] > threshold)
  pixels_to_change1_stomach = np.where(pred[2][0] < t)
  # Modify identified pixels
  pred[0][0][pixels_to_change_large] = classes['large bowel']
  pred[0][0][pixels_to_change1_large] = 0
  pred[1][0][pixels_to_change_small] = classes['small bowel']
  pred[1][0][pixels_to_change1_small] = 0
  pred[2][0][pixels_to_change_stomach] = classes['stomach']
  pred[2][0][pixels_to_change1_stomach] = 0

  tot_mask = pred[0][0] + pred[1][0] + pred[2][0]

  return tot_mask

mask_pred = color_class(pred)

plt.figure(figsize=(5*5, 7))
plt.imshow(img, cmap='bone', alpha=1)
plt.imshow(mask_pred, alpha=0.45, cmap='hot')
plt.axis('off')

def main():

   st.title('Optimización del tratamiento de cáncer GI')
   st.markdown('Elige una imagen de validación para probar el modelo.')

   option = st.selectbox('Imagen:',('81', '82', '83', '84'))

   path = 'slice_00'+ option +'_266_266_1.50_1.50.png'

   st.write('Imagen seleccionada:', option)

   img = cv2.imread(path, cv2.IMREAD_ANYDEPTH)
   img = (img - img.min())/(img.max() - img.min())*255.0
   img = cv2.resize(img, (256, 256))
   img = np.expand_dims(img, axis=-1)
   img = img.astype(np.float32) / 255.
   fig, ax = plt.subplots()
   ax.imshow(img)

   st.write('Imagen seleccionada')
   st.pyplot(fig)

   pred = best_model.predict(np.array([img]))
   mask_pred = color_class(pred)

   fig1, ax1 = plt.subplots()
   ax1.imshow(img, cmap='bone', alpha=1)
   st.write('Imagen con predicción:')
   ax1.imshow(mask_pred, alpha=0.45, cmap='hot')
   st.pyplot(fig1)

if __name__ == '__main__':
    main()


__copyright__ = 'Copyright(c) Gordon Elliott 2017'

"""
"""

from a_tuin.api import (
    node_class,
    node_connection_field,
    get_update_mutation,
    get_create_mutation,
    get_local_fields
)
from glod.api.parishioner_leaf import ParishionerLeaf
from glod.db.parish_list.parishioner import Parishioner, ParishionerQuery

parishioner_fields = get_local_fields(Parishioner)

ParishionerNode = node_class(Parishioner.__name__, ParishionerLeaf, parishioner_fields)

parishioners_connection_field = node_connection_field(
    Parishioner,
    ParishionerQuery,
    ParishionerNode,
    description="List of all parishioners"
)

parishioners_options_field = node_connection_field(
    Parishioner,
    ParishionerQuery,
    ParishionerLeaf,
    description="List of all parishioners for select fields"
)

CreateParishionerLeaf = get_create_mutation(Parishioner, parishioner_fields, ParishionerLeaf)
UpdateParishionerLeaf = get_update_mutation(Parishioner, parishioner_fields, ParishionerLeaf)


from flask import render_template,request, jsonify
from app import app
import datetime
#Manejar error de página no encontrada
@app.errorhandler(404)
def page_not_found(e):
    if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:#Si la solicitud acepta json y no HTML
        response = jsonify({'error': 'not found'})#Responder con JSON
        response.status_code = 404
        return response
    return render_template('404.html'), 404#Sino responder con template HTML

#Manejar error de error interno
@app.errorhandler(500)
def internal_server_error(e):
    print(e)
    getLogEvents(e)
    #Si la solicitud acepta json y no HTML
    if request.accept_mimetypes.accept_json and not request.accept_mimetypes.accept_html:
        #Responder con JSON
        response = jsonify({'error': 'internal server error'})
        response.status_code = 500
        return response
    #Sino responder con template HTML
    return render_template('500.html'), 500

def getLogEvents(error):
    log=open('error.log','a')
    log.write("\n")
    log.write(f"\nHa ocurrido el error:{error})")
    log.write(f"\nDATE:{datetime.datetime.now()}$\n")
    log.close()

def errores():
    return render_template('500.html')


import numpy as np
from imports import *

def get_object_list():
    object_list = [Sphere ([5., 0., 5.], 1., Matte ([255, 0, 0])), Sphere([0., 5., 5.], 0.5, Matte([0, 255, 0])),
                   InfinitePlane([0.,0.,0.],[1.,0.,0.],Matte([0,0,255]))]

    return object_list

def raytrace_init ():
    camera_coors = np.array([0, 0, 0,])
    screenZ = 1.

    image_dims = (500, 500)
    image = np.ndarray((image_dims[0], image_dims[1], 3), np.uint8)

    object_list = get_object_list()

    for row in xrange (image_dims[0]):
        for col in xrange (image_dims[1]):
            camera_ray_dir = np.array ([col / image_dims[0] - camera_coors[0], row / image_dims[1] - camera_coors[1], screenZ - camera_coors[2]])

            raytrace_main (camera_coors, camera_ray_dir, 0., object_list, image[row][col])

    writePPM (image)

if (__name__ == '__main__'): raytrace_init()