2020年认证杯SPSSPRO杯数学建模
D题 让电脑桌面飞起来
原题再现:
对于一些必须每天使用电脑工作的白领来说,电脑桌面有着非常特殊的意义,通常一些频繁使用或者比较重要的图标会一直保留在桌面上,但是随着时间的推移,桌面上的图标会越来越多,有的时候想找到一个马上要用的图标是非常困难的,就比如下图所示的桌面。各种各样的图标会让你眼花缭乱,甚至有的时候反复找好几遍都找不到你想要的图标。其实只要把图标进行有效的分类就可以解决这个问题,但是手工分类往往非常浪费时间,有的时候使用者也会陷入分类决定困难,不知该如何选择。
第一阶段问题: 请你的团队建立合理的数学模型,设计一种快速、有效地桌面图标分类的算法,使得能够根据图标的名字进行自动分类,让电脑桌面真正飞起来!这里需要注意的是,应用程序的图标文件不能通过扩展名来区分其功能,只能通过在已有的数据库或者互联网上查询文件名来判断其功能。当然也可以通过机器学习的方式来学习人的分类经验来形成一个分类模式。
整体求解过程概述(摘要)
随着电脑办公的普及,对于桌面图标的分类整理已成为让很多电脑使用者极为头疼的一件事,无论是专业的计算机开发者还是日常办公人员,进行有效的桌面图标整理工作,对于办公效率提升其价值是不言而喻的,对于桌面图标的有效分类以及桌面区域的划分是解决该问题的有效途径。
针对图标分类而言,我们获取桌面图标有关的 logo 图像、描述文本、以及近期访问量等三大特征。对于图像特征利用 CNN 进行图像特征提取匹配 logo 相似程度,根据图标名称寻找对应详细的文本介绍借助自然语言处理中的 LDA 主题提取算法,通过计算图标之间有关用途、来源等文本主题进行文本聚类,形成以主题为依据的图标存放单元。针对桌面图标点击量进行排序单独划分访问区域,根据图标 logo 特征提取后的结果结合图标的点击率调整图标的透明度和大小。最后得到五大主题图标区域与两块文件分类区域,在桌面上添加图标搜索栏,以完成对于文档类型图标的快速查找。
最后利用 ROC 曲线通过新增图标数量判别分类器准确度为 87.8%,通过实际测试得到该模型对于桌面图标查找速度提升为 65%。
问题分析:
(一)问题一的分析
对于桌面图标而言,人们往往凭借记忆和直观视觉完成对于所需图标范围的定位与查找,这往往耗费使用者大量时间。因此查找桌面图标时间的长短是衡量桌面图标分类效果的重要标准,与此同时优化图标对于用户的视觉舒适度。首先对于桌面图标需要合理设置若干个基本存放单元[1],将桌面区域进行划分,缩小用户对于所需图标的查找范围,对于新产生的图标可通过分类器判决进入对应的基本存放单元。
桌面图标包括三个维度的信息:1、所属文件的类型与自然文本 2、图标图像信息 3、近期桌面图标浏览量。首先,利用 CNN 对于图像论文进行特性提取,在输出层外接 LGAM快速训练模型,判决图标之间的相近程度。接着通过图标的名称向 Web 服务器发出请求查找对应名称内容的相应数据库,根据 LDA 主题模型进行主题聚类[2],将同一类型产品进行规律,最终根据用户的使用频率与图标颜色近似程度来改变的图标的透明度,最后留出一块基本存放单元体现桌面图标访问状况。通过桌面图标的访问评论调整图标的大小,并且对于使用频率低于一周一次的图标向用户申请删除命令。于此同时,针对大量同类型图标文件例如 Excel 与 Word 报表,在桌面增添搜索区域增加对于相近文件的区分度。
模型假设:
1.假设用户对于桌面图标重命名合理(符合图标内容属性);
2.假设用户图标类型较为常见,不存在大量图标名称极为近似
3.假设图标的透明度与大小能进行调整
4.假设图标名称与所关联的文本内容是一致的
5.假设申请删除某桌面图标的标准为访问周期小于一周
论文缩略图:
全部论文请见下方“ 只会建模 QQ名片” 点击QQ名片即可
部分程序代码:(代码和文档not free)
import logging
import logging.config
import ConfigParser
import numpy as np
import random
import codecs
import os
from collections import OrderedDict
#获取当前路径
path = os.getcwd()
#导入日志配置文件
logging.config.fileConfig("logging.conf")
#创建日志对象
logger = logging.getLogger()
# loggerInfo = logging.getLogger("TimeInfoLogger")
# Consolelogger = logging.getLogger("ConsoleLogger")
#导入配置文件
conf = ConfigParser.ConfigParser()
conf.read("setting.conf")
#文件路径
trainfile = os.path.join(path,os.path.normpath(conf.get("filepath", "trainfile")))
wordidmapfile = os.path.join(path,os.path.normpath(conf.get("filepath","wordidmapfile")))
thetafile = os.path.join(path,os.path.normpath(conf.get("filepath","thetafile")))
phifile = os.path.join(path,os.path.normpath(conf.get("filepath","phifile")))
paramfile = os.path.join(path,os.path.normpath(conf.get("filepath","paramfile")))
topNfile = os.path.join(path,os.path.normpath(conf.get("filepath","topNfile")))
tassginfile = os.path.join(path,os.path.normpath(conf.get("filepath","tassginfile")))
#模型初始参数
K = int(conf.get("model_args","K"))
alpha = float(conf.get("model_args","alpha"))
beta = float(conf.get("model_args","beta"))
iter_times = int(conf.get("model_args","iter_times"))
top_words_num = int(conf.get("model_args","top_words_num"))
class Document(object):
def __init__(self):
self.words = []
self.length = 0
#把整个文档及真的单词构成 vocabulary(不允许重复)
class DataPreProcessing(object):
def __init__(self):
self.docs_count = 0
self.words_count = 0
#保存每个文档 d 的信息(单词序列,以及 length)
self.docs = []
#建立 vocabulary 表,照片文档的单词
self.word2id = OrderedDict()
def cachewordidmap(self):
with codecs.open(wordidmapfile, 'w','utf-8') as f:
for word,id in self.word2id.items():
f.write(word +"\t"+str(id)+"\n")
class LDAModel(object):
def __init__(self,dpre):
self.dpre = dpre #获取预处理参数
#
#模型参数
#聚类个数 K,迭代次数 iter_times,每个类特征词个数 top_words_num,超参数α(alpha) β(beta)
#
self.K = K
self.beta = beta
self.alpha = alpha
self.iter_times = iter_times
self.top_words_num = top_words_num
#
#文件变量
#分好词的文件 trainfile
#词对应 id 文件 wordidmapfile
#文章-主题分布文件 thetafile
#词-主题分布文件 phifile
#每个主题 topN 词文件 topNfile
#最后分派结果文件 tassginfile
#模型训练选择的参数文件 paramfile
#
self.wordidmapfile = wordidmapfile
self.trainfile = trainfile
self.thetafile = thetafile
self.phifile = phifile
self.topNfile = topNfile
self.tassginfile = tassginfile
self.paramfile = paramfile
# p,概率向量 double 类型,存储采样的临时变量
# nw,词 word 在主题 topic 上的分布
# nwsum,每各 topic 的词的总数
# nd,每个 doc 中各个 topic 的词的总数
# ndsum,每各 doc 中词的总数
self.p = np.zeros(self.K)
# nw,词 word 在主题 topic 上的分布
self.nw = np.zeros((self.dpre.words_count,self.K),dtype="int")
# nwsum,每各 topic 的词的总数
self.nwsum = np.zeros(self.K,dtype="int")
# nd,每个 doc 中各个 topic 的词的总数
self.nd = np.zeros((self.dpre.docs_count,self.K),dtype="int")
# ndsum,每各 doc 中词的总数
self.ndsum = np.zeros(dpre.docs_count,dtype="int")
self.Z = np.array([ [0 for y in xrange(dpre.docs[x].length)] for x in xrange(dpre.docs_count)])
# M*doc.size(),文档中词的主题分布
#随机先分配类型,为每个文档中的各个单词分配主题
for x in xrange(len(self.Z)):
self.ndsum[x] = self.dpre.docs[x].length
for y in xrange(self.dpre.docs[x].length):
topic = random.randint(0,self.K-1)#随机取一个主题
self.Z[x][y] = topic#文档中词的主题分布
self.nw[self.dpre.docs[x].words[y]][topic] += 1
self.nd[x][topic] += 1
self.nwsum[topic] += 1
self.theta = np.array([ [0.0 for y in xrange(self.K)] for x in xrange(self.dpre.docs_count) ])
self.phi = np.array([ [ 0.0 for y in xrange(self.dpre.words_count) ] for x in xrange(self.K)])
def sampling(self,i,j):
#换主题
topic = self.Z[i][j]
#只是单词的编号,都是从 0 开始 word 就是等于 j
word = self.dpre.docs[i].words[j]
#if word==j:
# print 'true'
self.nw[word][topic] -= 1
self.nd[i][topic] -= 1
self.nwsum[topic] -= 1
self.ndsum[i] -= 1
Vbeta = self.dpre.words_count * self.beta
Kalpha = self.K * self.alpha
self.p = (self.nw[word] + self.beta)/(self.nwsum + Vbeta) * \
(self.nd[i] + self.alpha) / (self.ndsum[i] + Kalpha)
#随机更新主题的吗
# for k in xrange(1,self.K):
# self.p[k] += self.p[k-1]
# u = random.uniform(0,self.p[self.K-1])
# for topic in xrange(self.K):
# if self.p[topic]>u:
# break
#按这个更新主题更好理解,这个效果还不错
p = np.squeeze(np.asarray(self.p/np.sum(self.p)))
topic = np.argmax(np.random.multinomial(1, p))
self.nw[word][topic] +=1
self.nwsum[topic] +=1
self.nd[i][topic] +=1
self.ndsum[i] +=1
return topic
def est(self):
# Consolelogger.info(u"迭代次数为%s 次" % self.iter_times)
for x in xrange(self.iter_times):
for i in xrange(self.dpre.docs_count):
for j in xrange(self.dpre.docs[i].length):
topic = self.sampling(i,j)
self.Z[i][j] = topic
logger.info(u"迭代完成。")
logger.debug(u"计算文章-主题分布")
self._theta()
logger.debug(u"计算词-主题分布")
self._phi()
logger.debug(u"保存模型")
self.save()
def _theta(self):
for i in xrange(self.dpre.docs_count):#遍历文档的个数词
self.theta[i] = (self.nd[i]+self.alpha)/(self.ndsum[i]+self.K * self.alpha)
def _phi(self):
for i in xrange(self.K):
self.phi[i] = (self.nw.T[i] + self.beta)/(self.nwsum[i]+self.dpre.words_count * self.beta)
def save(self):
# 保存 theta 文章-主题分布
logger.info(u"文章-主题分布已保存到%s" % self.thetafile)
with codecs.open(self.thetafile,'w') as f:
for x in xrange(self.dpre.docs_count): for y in xrange(self.K):
f.write(str(self.theta[x][y]) + '\t')
f.write('\n')
# 保存 phi 词-主题分布
logger.info(u"词-主题分布已保存到%s" % self.phifile)
with codecs.open(self.phifile,'w') as f:
for x in xrange(self.K):
for y in xrange(self.dpre.words_count):
f.write(str(self.phi[x][y]) + '\t')
f.write('\n')
# 保存参数设置
logger.info(u"参数设置已保存到%s" % self.paramfile)
with codecs.open(self.paramfile,'w','utf-8') as f:
f.write('K=' + str(self.K) + '\n')
f.write('alpha=' + str(self.alpha) + '\n')
f.write('beta=' + str(self.beta) + '\n')
f.write(u'迭代次数 iter_times=' + str(self.iter_times) + '\n')
f.write(u'每个类的高频词显示个数 top_words_num=' + str(self.top_words_num) + '\n')
# 保存每个主题 topic 的词
logger.info(u"主题 topN 词已保存到%s" % self.topNfile)
lda = LDAModel(dpre)
lda.est()
if __name__ == '__main__':
run()