“华为杯”第十五届中国研究生数学建模竞赛-对恐怖袭击事件记录数据的量化分析(Python,Pandas,Scikit-learn,PyTorch,Matplotlib,seaborn)

首先先说一下编程的工具

  • Python:编程语言
  • Pandas:数据处理,清洗,分析的工具
  • Scikit-learn:机器学习工具箱
  • PyTorch:深度学习搭建神经网络,训练等的工具
  • Matplotlib,seaborn:可视化工具

一,数据集

数据集中的每一行记录了一起恐怖袭击事件的信息,分为以下几部分进行了记录:

  • GTD的标志号和日期:每一起事件发生的起讫时间等。
  • 事件信息:每一起事件的时间,地点,人物,内容,过程,原因,入选标准等相关信息。
  • 事件发生的地点:包括国家、地区、城市和地理位置信息等
  • 攻击信息:解释攻击类型如:暗杀、劫持、绑架、路障事件、轰炸 /爆炸、武装突袭、徒手攻击等的信息。
  • 武器信息:记录了事件中所使用的武器类型如生物武器、化学武器、轻武器、爆炸物/炸弹/炸药、假武器、燃烧武器、致乱武器及其子类型等等。
  • 目标/受害者信息:为每一个事件记录了多达三个目标/受害者的信息。
  • 凶手信息:每一事件记录多达三名凶手的信息。包括凶手所在组织的名称、宣称对袭击负责的细节。
  • 伤亡和后果:包括人员伤亡、财产损失、人质或绑架的受害者、索要赎金等信息
  • 附加信息和来源:这个字段是用来说明与攻击有关的额外细节的。包含无法在上述任一字段中说明的信息等其他一些信息。
    其中每个类别下面又分为多个子段进行更详细的记录。
    数据集中一共包括三种数据类型:数值型,文本型,分类标记型
    我们要完成的任务,读题:
  • 依据危害性对恐怖袭击事件分级:对灾难性事件比如地震、交通事故、气象灾害等等进行分级是社会管理中的重要工作。通常的分级一般采用主观方法,由权威组织或部门选择若干个主要指标,强制规定分级标准,如我国《道路交通事故处理办法》第六条规定的交通事故等级划分标准,主要按照人员伤亡经济损失程度划分。但恐怖袭击事件的危害性不仅取决于人员伤亡和经济损失这两个方面,还与发生的时机、地域、针对的对象等等诸多因素有关,因而采用上述分级方法难以形成统一标准。请你们依据附件 1 以及其它有关信息,结合现代信息处理技术, 借助数学建模方法建立基于数据分析的量化分级模型,将附件 1给出的事件按危害程度从高到低分为一至五级,列出近二十年来危害程度最高的十大恐怖袭击事件,并给出表 1 中事件的分级。
  • 依据事件特征发现恐怖袭击事件制造者:附件 1 中有多起恐怖袭击事件尚未确定作案者。如果将可能是同一个恐怖组织或个人在不同时间、不同地点多次作案的若干案件串联起来统一组织侦査,有助于提高破案效率,有利于尽早发现新生或者隐藏的恐怖分子。请你们针对在 2015、2016年度发生的、尚未有组织或个人宣称负责的恐怖袭击事件,运用数学建模方法寻找上述可能性,即将可能是同一个恐怖组织或个人在不同时间、不同地点多次作案的若干案件归为一类,对应的未知作案组织或个人标记不同的代号,并按该组织或个人的危害性从大到小选出其中的前5 个,记为 1 号-5 号。 再对表 2 列出的恐袭事件,按嫌疑程度对 5个嫌疑人排序,并将结果填入下表(表中样例的意思是:对事件编号为 XX 的事件,3 号的嫌疑最大,其次是 4 号,最后是 5号),如果认为某嫌疑人关系不大,也可以保留空格。
  • 对未来反恐态势的分析:对未来反恐态势的分析评估有助于提高反恐斗争的针对性和效率。请你们依据附件 1 并结合因特网上的有关信息,建立适当的数学模型,研究近三年来恐怖袭击事件发生的主要原因、时空特性、蔓延特性、级别分布等规律,进而分析研判下一年全球或某些重点地区的反恐态势,用图/表给出你们的研究结果,提出你们对反恐斗争的见解和建议。
  • 自由发挥:你们认为通过数学建模还可以发挥附件 1 数据的哪些作用?给出你们的模型和方法。

二,问题定义与分析

关于数据集的问题:

  • 数据类型不统一:数据集是数据值类型,文本类型,分类类型多种的混合,所以首先要想办法将数据集的数据类型进行统一。可以利用自然语言处理中的方法,将文本类型的数据嵌入为向量,我看了好几篇一等论文,对于文本类型的数据直接扔掉了,文本类型中也包含着大量有用的信息,比如通过summary-对于事件描述的这一列,我们直接可以得知这起恐怖事件对于社会的影响。数据值类型的列直接按照它原先的记录进行保留。分类类型一律转为one-hot的形式。所以最后这个数据集的形式是每一列都是数字。每一年的恐怖袭击事件作为一个矩阵,不同年份的恐怖袭击事件堆叠在一起形成一个三维张量。
  • 缺失值:数据集中的有些列是因为自身子段的属性导致了存在大量的缺失值,这种列不能直接删除,可以给它缺失的数据一个特殊标记。有的列存在大量的缺失值是因为客观原因无法确定,对于这种,统计这一列缺失值的统计信息,设定一个阈值,如果缺的太多,直接把这一列删除,因为它已经对于最终问题的解决提供不了太多有帮助的信息。
  • 数据特征冗余:最初的数据集是一个特征(列)数为135的一个文件(样本(行)数无所谓,越多越好),即使经过我们上一步删除一些缺失值过多的列,即使假设删除了一半,最后也还会有60多列,所以必须对数据集使用特征降维的方法,这样才能方便我们接下来的一系列分析,常用的降维方法首先想到的就是机器学习中的PCA,LDA,深度学习中的Encoder-Decoder结构也能做降维,后面我会编程实现一下,验证效果。

关于机器学习,深度学习模型的问题:

  • 没有标签:最严重的问题。机器学习,深度学习的软肋,深度学习的另一个软肋-数据规模的问题,对于这个问题还好,我看了一下数据集有20多万的样本,足够了。标签的问题不解决,管你多牛b的机器学习,深度学习方法,都用不了。想到的解决方法有:人工标注,题目中不是已经限制为是一个5分类问题,标注大约6万条就足够训练用的。利用聚类算法产生标签,根据这个标签进行训练,但是这种方式,模型的准确度严重依赖于聚类算法的准确性。利用Encoder-Decoder结构,数据集整理完成之后训练一个Encoder-Decoder结构,训练完成后,拿出Encoder部分接一个分类层直接分类,其中Encoder-Decoder可以利用带有多头注意力机制的Transformer,因为数据集中带有时序性的信息。
  • 模型的验证问题:如何验证学习到的模型的准确性,如果模型真的从数据集中学习到了知识,那么如果我令恐怖袭击事件发起人那列作为target,让模型进行预测,也就是我对模型定义一个预测这个恐怖袭击事件发起人的任务,如果它分类的很好,那么我可以认为它真的从数据集中学习到了相应的知识,我可以使用这个模型。(自然语言处理中的Mark思想,遮盖住一句话中的一些词语,让模型进行预测,有个专业术语叫self-supervised)

对于题目中让我们解决的几个任务的处理:

  • 题目让我们完成对于每一起恐怖袭击事件进行分类,总共分为5个类别,然后给了我们一个表格,让我们把表格中的事件也进行分类,找出近20年来的重大恐怖袭击事件。5分类问题,数据处理后,训练一个带有多头注意力的Encoder-Decoder结构,文本类型的列进行完词嵌入之后,整句话的词向量表示会很长,接一个前馈神经网络进行压缩,放在Encoder-Decoder结构中一起训练,训练完成后,提取Encoder结构,对于Encoder的输出利用聚类进行分类。查找表格中事件的分类,查找所有第一分类的事件。
  • 利用第一问训练完毕的Encoder结构,外接分类层,冻结Encoder浅层的参数权重,将恐怖袭击事件发起人那列作为label进行分类层参数,以及Encoder深层参数的训练。按照我们第一问的分类结果,在第一类事件中计算不同恐怖组织的危害性,选取前5个,改变分类层的分类个数,预测题目中的事件,根据分类层softmax后的概率将嫌疑排序。
  • 同理还是利用训练完毕的Encoder,题目让我们发现近三年来恐怖袭击事件发生的主要原因、时空特性、蔓延特性、级别分布等规律。提取近三年的数据作为训练集,还是上面提到的self-supervised思想,让发现谁的规律,分类层就分谁,根据概率找主要原因,主要的地点,级别分布第一问已经做完了直接可视化结果就可以。最后的预测是根据我们图表中的信息给出的。
  • 第4问仿照前面的思想就可以。

(以下是代码部分)

三,数据处理与探索

# 数据处理
import numpy as np
import pandas as pd
import warnings

# 忽略警告
warnings.filterwarnings('ignore')
# 设置pandas显示属性
pd.set_option('display.max_columns', 20)
# 读取Excel数据集
data = pd.read_excel('1993.xlsx')
# # 查看两行数据
# print(data.head(2))
# # 查看数据的列索引
# print(data.columns)

# 按照不同类别字段切分表
# GTD的标志号和日期
# copy()保证不影响原先表
data_of_GTD = data.iloc[:, :7].copy()

# 事件发生的地点
data_of_location = data.iloc[:, 7:18].copy()

# 事件信息
# 这里根据包含的字段名进行获取,中间有间断,无法进行切片
# DataFrame['name']:获取name列的值
# DataFrame[['name1','name2',...]]:获取多列的值
data_of_info = data[
    ['summary', 'crit1', 'crit2', 'crit3', 'doubtterr', 'alternative', 'alternative_txt', 'multiple', 'related']].copy()

# 攻击信息
# 索性都用字段名吧
# 前面突然来了一个间断断了节奏,好烦
# 要索引的字段中重复的字段
# 用列表自己生成
sup_list = [['attacktype' + str(i), 'attacktype' + str(i) + '_txt'] for i in range(1, 4)]
# 要索引的列
index_columns = ['success', 'suicide']
index_columns += sup_list[0]
index_columns += sup_list[1]
index_columns += sup_list[2]
data_of_attack = data[index_columns].copy()
# 也可以
# data_of_attack = data[
#     ['success', 'suicide', 'attacktype1', 'attacktype1_txt', 'attacktype2', 'attacktype2_txt', 'attacktype3',
#      'attacktype3_txt']].copy()

# 目标/受害者信息
sup_list = [['targtype' + str(i), 'targtype' + str(i) + '_txt', 'targsubtype' + str(i), 'targsubtype' + str(i) + '_txt',
             'corp' + str(i), 'target' + str(i), 'natlty' + str(i), 'natlty' + str(i) + '_txt'] for i in range(1, 4)]
index_columns = []
index_columns += sup_list[0]
index_columns += sup_list[1]
index_columns += sup_list[2]
data_of_target = data[index_columns].copy()

# 凶手信息
index_columns = ['gname', 'gsubname']
sup_list = [['gname' + str(i), 'gsubname' + str(i)] for i in range(2, 4)]
index_columns += sup_list[0]
index_columns += sup_list[1]
index_columns.append('motive')
sup_list = [['guncertain' + str(i)] for i in range(1, 4)]
index_columns += sup_list[0]
index_columns += sup_list[1]
index_columns += sup_list[2]
index_columns.append('individual')
index_columns.append('nperps')
index_columns.append('nperpcap')
index_columns.append('claimed')
index_columns.append('claimmode')
index_columns.append('claimmode_txt')
sup_list = [['claim' + str(i), 'claimmode' + str(i), 'claimmode' + str(i) + '_txt'] for i in range(2, 4)]
index_columns += sup_list[0]
index_columns += sup_list[1]
index_columns.append('compclaim')
data_of_murder = data[index_columns].copy()

# 武器信息
index_columns = []
sup_list = [['weaptype' + str(i), 'weaptype' + str(i) + '_txt', 'weapsubtype' + str(i), 'weapsubtype' + str(i) + '_txt']
            for i in range(1, 5)]
index_columns += sup_list[0]
index_columns += sup_list[1]
index_columns += sup_list[2]
index_columns += sup_list[3]
index_columns.append('weapdetail')
data_of_weapon = data[index_columns].copy()

# 伤亡和后果
data_of_result = data.iloc[:, 99:125].copy()

# 附加信息和来源
data_of_append = data.iloc[:, 125:135].copy()

# 依次处理每个表
# 1.对于‘GTD的标志号和日期 ’这一类字段信息
# 因为eventid中已经包含了时间信息
# 把后面iyear,imonth,iday等冗余信息删除
# resolution这一列虽然存在很多缺失值
# 但是不应该直接删除
# 因为它反映了恐怖袭击事件持续的时长
# 持续的时间越长,说明这起恐怖袭击事件越恶劣
# 所以将resolution这一列缺失值填为0
# 然后以小时为计时单位进行换算,计算出这起恐怖袭击事件持续的时间
# 获取resolution列非空的行
data_with_resolution = data_of_GTD[data_of_GTD.resolution.notnull()]
# 按照前面所述换算成以小时为单位的持续时间
from datetime import datetime

# 获取事件开始的时间
# 获取resolution列非空行的索引
index_list = data_with_resolution.index
start_time = [str(data_with_resolution['iyear'][index_list[i]]) + '-' + str(
    data_with_resolution['imonth'][index_list[i]]) + '-' + str(data_with_resolution['iday'][index_list[i]]) for i in
              range(len(data_with_resolution.index))]
# 转换为时间序列
start_time = pd.to_datetime(start_time)
# 获取事件结束的时间,转换为时间序列
end_time = pd.to_datetime(data_with_resolution['resolution'])
# 获取时间差值,以小时为单位存储
time_diff = pd.to_timedelta(end_time - start_time).dt.days * 24
# 修改原表中的数值
# 按照索引来,不能直接赋值
for i in index_list:
    data_of_GTD['resolution'][i] = time_diff[i]
# 将resolution列的缺失值设为0
data_of_GTD.resolution.fillna(0, inplace=True)
# 删除iyear,imonth,iday,approxdate,extended列
delete_columns = ['iyear', 'imonth', 'iday', 'approxdate', 'extended']
data_of_GTD.drop(delete_columns, inplace=True, axis=1)
print("处理之后的GTD信息为:")
print(data_of_GTD.head(2))

# 2.对于‘事件发生的地点’这一类字段信息
# contry与contry_txt是一对映射关系,保留contry即可
data_of_location.drop(['country_txt'], inplace=True, axis=1)
# region与region_txt同理
data_of_location.drop(['region_txt'], inplace=True, axis=1)
# provstate,city这两列分类型的文本数据,转换为one_hot编码
# 后面有攻击信息中有一段转换为one-hot编码的代码段,可以类比进行写,这里不写了
# latitude,longitude,specificity表示位置的经纬度,属于冗余信息,且缺失值较多,直接删除
data_of_location.drop(['latitude', 'longitude', 'specificity'], inplace=True, axis=1)
# location这列虽然缺失值较多,但是提供了关于位置,甚至是恐怖袭击的一些信息,需要保留,
# 下一步用自然语言处理的方法进行处理,缺失值可以设置为'UNK'
# data_of_location['location'] = data_of_location['location'].apply(lambda x: 'UNK' if len(str(x)) == 0 else x)
data_of_location.location.fillna('UNK', inplace=True)
print("处理之后的事件发生的地点信息为:")
print(data_of_location.head(2))

# 3.对于‘事件信息’这一类字段信息
# summary需要保留,如果有缺失值设为'UNK'
data_of_info.summary.fillna('UNK', inplace=True)
# crit1,crit2,crit3,doubtterr属于同一个语义域的记录,合并到一起作为一个特征
# 转换为str类型才能连接,否则是加法
data_of_info['crit1_crit2_crit3_doubtterr'] = data_of_info['crit1'].map(str) + data_of_info['crit2'].map(str) + \
                                              data_of_info['crit3'].map(str) + \
                                              data_of_info['doubtterr'].map(str)
data_of_info.drop(['crit1', 'crit2', 'crit3', 'doubtterr'], inplace=True, axis=1)
# 删除alternative,alternative_txt
data_of_info.drop(['alternative', 'alternative_txt'], inplace=True, axis=1)
# multiple这列表示一个恐怖袭击事件组,是有用的特征,但是模型学习的时候可以不考虑
# 因为模型是在一个事件一个事件的进行处理
# 如果后续想以事件组进行考虑可以使用这个特征,暂时删除
data_of_info.drop(['multiple'], inplace=True, axis=1)
data_of_info.drop(['related'], inplace=True, axis=1)
print("处理之后的事件信息:")
print(data_of_info.head(2))

# 4.对于‘攻击信息’这一类字段信息
# 全部保留,分类型的文本数据转为one-hot编码,
# 攻击类型缺失值用10填充
data_of_attack.attacktype1.fillna(10, inplace=True)
data_of_attack.attacktype2.fillna(10, inplace=True)
data_of_attack.attacktype3.fillna(10, inplace=True)
# 攻击类型文本缺失值用UNK填充
data_of_attack.attacktype1_txt.fillna('UNK', inplace=True)
data_of_attack.attacktype2_txt.fillna('UNK', inplace=True)
data_of_attack.attacktype3_txt.fillna('UNK', inplace=True)
# 将文本型的数据转换为one-hot类型的数据
# 获取这一列不同的值
unique_list = data_of_attack['attacktype1_txt'].unique()
# 建立映射字典
unique_value_map = {}
for index, value in enumerate(unique_list):
    unique_value_map[value] = index
# 将文本类型转换为分类类型
# 这里只是简单的标记,可以利用scikit-learn真正的转换为one-hot编码
# 但是这样后面就可以处理了,因为已经标记出了不同类别
data_of_attack['attacktype1_txt'] = data_of_attack['attacktype1_txt'].apply(lambda x: int(unique_value_map[x]))

# 获取这一列不同的值
unique_list = data_of_attack['attacktype2_txt'].unique()
# 建立映射字典
unique_value_map = {}
for index, value in enumerate(unique_list):
    unique_value_map[value] = index
# 将文本类型转换为分类类型
data_of_attack['attacktype2_txt'] = data_of_attack['attacktype2_txt'].apply(lambda x: int(unique_value_map[x]))

# 获取这一列不同的值
unique_list = data_of_attack['attacktype3_txt'].unique()
# 建立映射字典
unique_value_map = {}
for index, value in enumerate(unique_list):
    unique_value_map[value] = index
# 将文本类型转换为分类类型
data_of_attack['attacktype3_txt'] = data_of_attack['attacktype3_txt'].apply(lambda x: int(unique_value_map[x]))
print("处理完之后的攻击信息:")
print(data_of_attack.head(2))

# 5.对于‘目标/受害者信息’这一类字段信息
# 类似于‘攻击信息’的处理方式
# 合并为一个特征
print('处理完之后的目标信息:')
print(data_of_target.head(2))

# 6.对于‘凶手信息’这一类字段信息
# 保留gname列,然后转为one_hot编码
# 其余列全部删除
delete_columns = ['gsubname']
sup_list = [['gname' + str(i), 'gsubname' + str(i)] for i in range(2, 4)]
delete_columns += sup_list[0]
delete_columns += sup_list[1]
delete_columns.append('motive')
sup_list = [['guncertain' + str(i)] for i in range(1, 4)]
delete_columns += sup_list[0]
delete_columns += sup_list[1]
delete_columns += sup_list[2]
delete_columns.append('individual')
delete_columns.append('nperps')
delete_columns.append('nperpcap')
delete_columns.append('claimed')
delete_columns.append('claimmode')
delete_columns.append('claimmode_txt')
sup_list = [['claim' + str(i), 'claimmode' + str(i), 'claimmode' + str(i) + '_txt'] for i in range(2, 4)]
delete_columns += sup_list[0]
delete_columns += sup_list[1]
delete_columns.append('compclaim')
data_of_murder.drop(delete_columns, inplace=True, axis=1)
# 第一问暂时用不到这类信息
# 因为即使是同一个凶手做的恐怖袭击事件,你也无法保证每次所做的恐怖袭击事件是同样的级别
print("处理完之后的凶手信息:")
print(data_of_murder.head(2))

# 7.对于‘武器信息’这一类字段信息
# 删除掉weapdetail这一列
data_of_weapon.drop(['weapdetail'], inplace=True, axis=1)
# 其余的列如果是数值型直接保留,如果是分类型的文本数据转换为one-hot编码的形式
print("处理完之后的武器信息:")
print(data_of_weapon.head(2))

# 8.对于‘伤亡和后果’这一类字段信息
# propextent的缺失值设置为4
data_of_result.propextent.fillna(4, inplace=True)
# 删除propextent_txt,propvalue,propcomment
# nhostkid,nhostkidus,nhours,ndays,divert
# kidhijcountry,ransom,ransomamt
# ransomamtus,ransompaid,ransomnote,hostkidoutcome_txt,nreleased
delete_columns = [
    'propextent_txt', 'propvalue', 'propcomment',
    'nhostkid', 'nhostkidus', 'nhours', 'ndays',
    'divert', 'kidhijcountry', 'ransom',
    'ransomamt', 'ransomamtus', 'ransompaid',
    'ransomnote', 'hostkidoutcome_txt', 'nreleased'
]
data_of_result.drop(delete_columns, inplace=True, axis=1)
print("处理完之后的事件结果信息:")
print(data_of_result.head(2))

# 9.对于‘附加信息和来源’这一类字段信息
# 合并addnotes,scite1,scite2,scite3到一列
# 合并INT_LOG,INT_IDEO,INT_MISC,INT_ANY到一列
data_of_append.addnotes.fillna('UNK', inplace=True)
data_of_append.scite1.fillna('UNK', inplace=True)
data_of_append.scite2.fillna('UNK', inplace=True)
data_of_append.scite3.fillna('UNK', inplace=True)

data_of_append['addnotes_scite1_scite2_scite3'] = data_of_append['addnotes'].map(str) + data_of_append['scite1'].map(
    str) + data_of_append['scite2'].map(str) + data_of_append['scite3'].map(str)
data_of_append.drop('dbsource', inplace=True, axis=1)
data_of_append['INT_LOG_INT_IDEO_INT_MISC_INT_ANY'] = data_of_append['INT_LOG'].apply(lambda x: abs(x)).map(str) + \
                                                      data_of_append[
                                                          'INT_IDEO'].apply(lambda x: abs(x)).map(str) + data_of_append[
                                                          'INT_MISC'].apply(lambda x: abs(x)).map(str) + \
                                                      data_of_append['INT_ANY'].apply(lambda x: abs(x)).map(str)
delete_columns = ['addnotes', 'scite1', 'scite2', 'scite3', 'INT_LOG', 'INT_IDEO', 'INT_MISC', 'INT_ANY']
data_of_append.drop(delete_columns, inplace=True, axis=1)
data_of_append.drop(['related'], inplace=True, axis=1)
print('处理完之后的附加信息为:')
print(data_of_append.head(2))

你可能感兴趣的:(其它,python)