
转自:https://mp.weixin.qq.com/s/6rNhxV9gw7Q6DpsbQlbe2Q
分享个人经验,保留阅读记录,做时间的朋友
转自:https://mp.weixin.qq.com/s/6rNhxV9gw7Q6DpsbQlbe2Q
今天笔者就带大家,梳理几个常见的基于文本终端的 UI 框架,一睹为快!
首先出场的是 Curses[1]。
Curses 是一个能提供基于文本终端窗口功能的动态库,它可以:
Curses 可以在任何遵循 ANSI/POSIX 标准的 Unix/Linux 系统上运行。Windows 上也可以运行,不过需要额外安装 windows-curses
库:
pip install windows-curses
上面图片,就是一哥们用 Curses 写的 俄罗斯方块游戏[2],是不感觉满满的回忆吧,可以拿去复活古董机了。
我们也来试试牛刀:
import curses
myscreen = curses.initscr()
myscreen.border(0)
myscreen.addstr(12, 25, "Python curses in action!")
myscreen.refresh()
myscreen.getch()
curses.endwin()
addstr
前两个参数是字符坐标,不是像素坐标getch
会阻塞程序,直到等待键盘输入curses.endwin()
作用是退出窗口getch()
获得的输入进行判断代码运行效果如下:
Curses 非常轻巧,特别适合处理一下简单交互,代替复杂参数输入的程序,既优雅,有简单,而且 Curses 也是其他文字终端 UI 的基础。
Npyscreen[3] 也是一个用了编写文本终端的 Python 组件库,是基于 Curses 构建的应用框架。
比起 Curses,Npyscreen 更接近 UI 式编程,通过组件的组合完成 UI 展示和交互,而且 Npyscreen 可以自适应屏幕变化。
Npyscreen 提供了多个控件,比如 表单(Form)、单行文本输入框(TitleText)、日期控件(TitleDateCombo)、多行文本输入框(MultiLineEdit)、单选列表(TitleSelectOne)、进度条(TitleSlider)等多种控件。
提供强大的功能,满足快速开发程序的要求,无论是简单的单页程序还是复杂的多页应用。
来看一个小例子:
import npyscreen
class TestApp(npyscreen.NPSApp):
def main(self):
# These lines create the form and populate it with widgets.
# A fairly complex screen in only 8 or so lines of code - a line for each control.
F = npyscreen.Form(name = "Welcome to Npyscreen",)
t = F.add(npyscreen.TitleText, name = "Text:",)
fn = F.add(npyscreen.TitleFilename, name = "Filename:")
fn2 = F.add(npyscreen.TitleFilenameCombo, name="Filename2:")
dt = F.add(npyscreen.TitleDateCombo, name = "Date:")
s = F.add(npyscreen.TitleSlider, out_of=12, name = "Slider")
ml = F.add(npyscreen.MultiLineEdit,
value = """try typing here!nMutiline text, press ^R to reformat.n""",
max_height=5, rely=9)
ms = F.add(npyscreen.TitleSelectOne, max_height=4, value = [1,], name="Pick One",
values = ["Option1","Option2","Option3"], scroll_exit=True)
ms2= F.add(npyscreen.TitleMultiSelect, max_height =-2, value = [1,], name="Pick Several",
values = ["Option1","Option2","Option3"], scroll_exit=True)
# This lets the user interact with the Form.
F.edit()
print(ms.get_selected_objects())
if __name__ == "__main__":
App = TestApp()
App.run()
pip install npyscreen
npyscreen.NPSApp
创建一个应用类 TestApp
main
方法,方法里创建一个 Form
表单对象,然后向表单对象上添加各种控件,并设置控件的一些属性Edit
方法,将操作权交给用户TestAPP
,然后调用 run
方法启动应用,应用即可进入等待用户交互的状态上面代码运行的效果如下:
是不是感觉很神奇,用文本原来可以做这么多复杂的操作,之前对命令行中的进度显示的疑惑是否有所清晰了~
如果说 Curses 和 Npysreen 是轻量级的文本终端 UI 框架,那么 Urwid[5] 绝对称得上是重量级选手。
Urwid 包含了众多开发文本 UI 的特性,例如:
看看效果:
不知道你看了是什么感觉,我的感觉是:这也太卷了吧~
几乎可以做 GUI 下的所有事情!
更厉害的是,Urwid 完全是按照面向对象的思想打造的框架:
现在我们来小试一把,感受一下 Urwid 的强大:
import urwid
def show_or_exit(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
txt.set_text(repr(key))
txt = urwid.Text(u"Hello World")
fill = urwid.Filler(txt, 'middle')
loop = urwid.MainLoop(fill, unhandled_input=show_or_exit)
loop.run()
show_or_exit
urwid.Text
是一个文本控件,接受一个字符串作为显示信息urwid.Filler
类似于 panel,将 txt 控件填充在上面,位置设置在窗口中央urwid.MainLoop
设置 Urwid 的主循环,将 fill 作为控件的绘制入口,参数 unhandled_input
接受一个按键事件处理方法,用的就是前面定义的 show_or_exit
loop.run()
启动 UI,并监控各种事件运行这段代码,就可以看到命令行被设置为交互模式,按键时会在窗口中央显示出键名,如果按下 q 键,程序就会退出。
注意:
Urwid 只能在 Linux 操作系统中运行,Windows 上会因为缺失必要组件无法运行
限于篇幅,这里只展示了三种文本终端框架,不过已经能对基于文本终端 UI 框架的强大感受一二了。
还有一些框架也很优秀,比如 prompt_toolkit,有兴趣的同学可以研究一下。
虽然基于文本终端的 UI 早已不是主流,但是在一些特殊的行业或者业务中,还是有其存在的价值,研究一下,说不定在特殊的地方可以帮助到我们。
最后,推荐一个很有意思的基于文本终端的应用 —— 命令行网易云音乐[9]:
是基于 Curses 开发,如果运行起来,能被它的强悍所震撼,有空可以玩玩,比心!
Curses: https://docs.python.org/3/howto/curses.html
[2]
俄罗斯方块游戏: https://github.com/cSquaerd/CursaTetra
[3]
npyscreen: https://npyscreen.readthedocs.io/
[4]
vim: https://www.vim.org/
[5]
Urwid: https://urwid.org/index.html
[6]
Twisted: https://www.twistedmatrix.com/trac/
[7]
Glib: https://docs.gtk.org/glib/
[8]
Tornado: https://www.tornadoweb.org/en/stable/
[9]
命令行网易云音乐 : https://github.com/darknessomi/musicbox
转自:https://mp.weixin.qq.com/s/Mb2FZvubINMJkFF3f_XDOQ
转自:https://mp.weixin.qq.com/s/J-LnhCX9TXkuGoIvXnNyyA
好长时间没有给大家写俄乌战场上的形势了,今天我们就再给大家说说这个事情——因为现在已经到了关键期了!
目前俄乌战场上的局势变化不大:俄军进入了分割、包围阶段,这个阶段的进展注定是要比较缓慢的。
由于俄军进展“缓慢”,网络上又开始有一些胡说八道带节奏的文章了。
2月24日,俄乌战争发生以后,俄军在两个小时内就干掉了乌克兰的海军和空军,很多人立刻高呼,认为俄罗斯在几天内就能拿下整个乌克兰……
我也很希望俄罗斯能速赢,狠狠的在老美脸上扇出一个巴掌印,但是我的认知告诉我,战争不会那么快。
如果大家记得的话,在俄军灭掉乌克兰的海军和空军以后,我在文中写道:
“这场战争才刚刚开始,极有可能不是短时间内就能解决的——乌克兰军队不太可能出现像去年阿富汗政府军那样的溃败——最终有可能‘复制’伊拉克战争的模式。”
“这注定是一场残酷的战争,虽然目前俄罗斯还没有对城市发动攻击,但随着战线的不断推进,大量溃败的乌克兰军队必然要躲进城市里层层阻击俄军,最终将逼迫俄军对城市发动攻击,双方甚至有可能形成巷战,最后极有可能会让整个乌克兰变成一片废墟,造成大量平民死亡和难民潮!”
当时,很多人不相信!
有人在群里质疑我的这个结论,不过由于我没有太多时间,再加上有些东西需要时间来验证,在结果没有出来之前,我说什么他也不会信的——关于这点,我是深知的,成年人的世界,每个人都有自己的想法,想要改变他们是非常困难的。
原本要几个月才能打完的战争,一些不懂的号主硬是把它写成几天就能打完,给读者打了一针强鸡血……半个月后,俄军没打下来,他们又开始绝望了,又开始胡乱分析了,什么俄军战力不行、经济不行、兵力不足、战术错误……
下面,我们就和大家说说,我当时是如何得出这个结论的?
大家知道现在俄军在乌克兰境内有多少军队吗?
6-8万,最多8万!
乌军总共有25万左右,俄军仅有8万,而且是分三路进攻,平均每路仅有2万多人,俄军不但要攻打数倍于己的乌军,而且还要对乌军实施分割包围战略。
说实话,这个兵力实在有点太少了——原本我以为演习的20万大军都进去了,后来才知道仅仅进去了一半都不到!
俄军进去的人数为什么那么少?是兵力不足吗?
当然不是!
大家知道俄军一共有多少人吗?
俄罗斯武装力量总兵力有103万,构成是:
陆军:24万;
海军:15万;
空天军:43万;
火箭军:12万;
空降军:4.5万;
……
如此强大的军力,为什么只动用了8万?
我认为主要有两个原因:
第一,用不着那么多;
第二,在防着北约呢!
截至目前,俄罗斯的海军、空天军和火箭军几乎都没有动,仅仅只是发射了一些导弹而已!
俄乌战争发生以后,美国多次发表声明:北约不会参与乌克兰战争。
但是,我就问你:谁告诉你他说的就是真话?如果北约真的参与了,你怎么办?
别忘记了,俄军在军事演习期间,无数次声明说俄罗斯没有攻打乌克兰的计划,哪怕是在开战前两天,也还是这么说的!
我们假设一下:如果俄罗斯大军全部进入了乌克兰,这个时候北约也宣布参加战争,俄罗斯会面临什么结果?
整个战略部署全部被打乱!
虽然在俄乌战争发生以后,俄罗斯很快就宣布“核威慑力量转入特殊战备状态”,但是你自己想想:如果北约真参与了,俄罗斯会发动核战吗?
北约只是在乌克兰境内和俄军打而已,又没有想攻入莫斯科,又没想开启灭国大战,俄罗斯能动核武器吗?很显然,是不可能的!
普京的“核威慑”其实就是“核讹诈”,很苍白无力的!
正是为了防备北约的介入,俄大军才没有全面出动——死死地盯住北约呢!
那么,这就是我所说的“这场战争极有可能不是短期就能解决”的原因吗?
不是!
而是因为这场战争的性质决定了俄军不能速胜!
俄罗斯攻打乌克兰与美国打伊拉克、阿富汗等有一个很大的不同:俄罗斯和乌克兰是邻居,而且乌克兰境内拥有大量亲俄的人,所以它不能像美军那么随意轰炸。
大家看下面一个图:在乌克兰的南部、东部和中东部都是亲俄的,说俄语的比例都非常高,而现在俄军攻打的恰恰就是这些地区。
这张图里面蕴藏了巨大的信息,我们提几个问题:
第一,如果乌军顶不住俄军的攻势会不会进城?
2014年的时候,整个乌克兰的军队加起来只有12万左右,拥有作战能力只有6000人,后来俄罗斯吞并了克里米亚,在北约的支持下,才建立了一支20多万人的军队。
这支乌军构成是什么样的?
不用有任何的怀疑,一定是亲西方的人居多——如果乌克兰在东部大量招兵,那么在关键时刻他们是极有可能会“反水”的,所以乌军主力一定是以西乌兵为主。
亲西方的西乌兵在亲俄的东部和俄军打起来,你说他们在顶不住的时候会不会撤退到城市里?
一定会!
第二,一旦乌军撤退到城市里,俄军能对城市发动大规模的轰炸吗?
我们整个村里人都是支持你的,你的敌人跑进我们村里,你能把我们的村子连带轰掉吗?
我们都是支持你的,但是你却想把我炸死、想把我的房子等财产都炸掉了,你让我们会怎么想?
所以,俄罗斯绝对不能大规模地轰炸城市——这就是普京为什么在战前反复叮嘱“确保平民安全”的主要原因,因为战区绝大多数老百姓都是亲俄的。
乌军顶不住俄军的进攻一定会躲在城市里,而城市里的老百姓大多又是亲俄的,你说普京怎么办?
一是让平民都撤出来;
二是打巷战!
无论采取哪种方法,都注定俄军不可能速胜!
了解这层关系以后,大家就能理解乌军为什么一定会撤到城市里,并拿老百姓当“挡箭牌”了吧。
对于俄罗斯来说,不到逼不得已的时候是不能打巷战的,因为那个伤亡代价太大了。所以,俄军只能围着乌军,不断地施压,想尽办法让城中的老百姓撤出来。
试问:在这样的背景下,你让俄军如何速胜?
我相信大家肯定看新闻了,现在乌克兰至少有280万难民逃往欧洲了,但是大家不知道的是,目前也有260万难民申请撤到俄罗斯境内(再加上在战前转移到俄罗斯境内的70万,合计就是330多万了)!
毫无疑问,逃往欧洲的难民大多都是亲欧的,逃往俄罗斯的难民大多都是亲俄的——这也再次验证,乌东地区亲俄势力非常强大!
正是因为受到这么多因素的影响,这才让俄罗斯放不开手脚,打的非常煎熬:不能大规模的轰炸城市,因为那里的人都是亲俄的,如果你把他们的房子都轰了,将来难民回来了,一无所有了,他们可能就不亲你了,你就无法建立亲俄政府了(如果有必要)!
虽然现在表面上来看俄军占领的面积好像不大,但事实上,整个乌克兰的南部、东部、中东部都已经在俄罗斯的控制之下了,因为有些地区根本就没有乌军,没有必要浪费兵力去占领——亲俄势力范围内的乌军几乎都已经被包围了。
由于乌军撤退到城市里了,俄军为了尽量多的让平民撤出来、尽量不轰炸老百姓的房子,只能慢慢地围困了。
现在东部和南部大势已定,只是时间问题。
大家可以想象,一旦俄军解决东部问题以后会如何对待西部?
乌克兰的西部都是亲欧的,都是反俄的,你觉得那个时候普京还会手软吗?
相信大家前几天肯定看到一则重大新闻了:3月11日,俄军导弹袭击了乌克兰西部城市卢茨克(Lutsk)和伊万诺-弗兰科夫斯克(Ivano-Frankivsk)的机场,这是两周以来的第一次。
13日,俄军再次用高精度远程武器打击了乌克兰武装力量在西部利沃夫州的亚沃洛夫斯基军事训练中心等地。打死多达180名外国雇佣兵,大量外国武器被摧毁。
东部亲俄地区大势已定,俄军已经开始轰炸亲欧的西部,这时俄军就没有那么多的负担了。
于是,我们就看到一则信息:3月14日,乌克兰总统办公室顾问阿列斯托维奇表示,乌克兰与俄罗斯双方最快有望在一至两周内签署和平协议,最迟将在5月签署和平协议。
关于这份声明,俄罗斯没有做出回应!
有人说,这是乌克兰的缓兵之计!
如果您看懂我们上面的分析,你就会知道:这真不是乌克兰的缓兵之计,而是因为俄罗斯即将对亲欧的乌克兰族下死手了!
如果说乌军在东部、南部地区和俄军死磕,损失最大的乌克兰俄罗斯族人和亲俄老百姓的利益的话,那么西部就真的是完完全全的是乌克兰人的利益了!
那么俄乌之间能否像乌克兰所说的那样“最快有望在一至两周内签署和平协议”呢?
我们先看俄罗斯提出的条件,共有6个(其中5个要写入乌克兰的宪法当中),它们分别是:
第一,乌克兰放弃加入北约,保持中立地位,俄罗斯愿意成为乌克兰安全的保障者;
第二,赋予俄语乌克兰第二官方语言地位,取消所有针对俄语的限制;
第三,承认顿涅茨克和卢甘斯克的独立;
第四,承认俄罗斯对克里米亚的主权;
第五,乌克兰去纳粹化和禁止极端民族主义、纳粹和新纳粹党派和社会组织的活动,废除现有的美化纳粹和新纳粹的法律;
第六,乌克兰的非军事化:完全放弃进攻性武器。
大家看看上面的条件:如果你是乌克兰的领导人,局势发展到现在,你觉得哪些条件能答应,哪些不能答应?
第一条,放弃加入北约,这个可以答应。
第二条,取消对俄语的限制,这个也可以答应。
第三条,承认顿涅茨克和卢甘斯克的独立,这个不能答应。
第四条,承认克里米亚是俄罗斯的,这个不能答应。
第五条,去纳粹化,这个可以答应。
第六条,放弃发展进攻性武器,这个可以答应。
我们再换位思考下:如果你是普京,你觉得哪个条件可以让步?
这六个条件一个都不能少,乌克兰必须全部答应!
克里米亚就不用说了,地理位置那么重要,已经吃进嘴的,怎么可能让给你?
卢甘斯克和顿涅茨克呢?
我相信,肯定会有人认为:这个可以商量。
我告诉你,这个也没有任何商量的余地!
为什么?
你就想想,这两个州从2014年宣布独立以后,和乌克兰人打了8年,死了那么多人,如果俄罗斯把它们给“卖”了,你说那些地区的人能饶得了普京?俄罗斯国内老百姓能饶得了普京?
所以,俄罗斯提出的六个条件就是俄罗斯最底线,没有任何讨价还价的空间!
俄罗斯提出的六个条件中,至少有两个是现在的乌克兰无法接受的,怎么办?
还得打,一直打到乌克兰同意为止!
接下来的战争,很可能就会异常残酷了,因为后面的战场,俄军就会放开手脚了——俄军中最强大的空天军即将出动,大量轰炸机将会疯狂的对乌西部地区进行轰炸!
俄空天军力量有多强大?
仅次于美国!
俄空天军共有拥有380架苏-27、267架米格-29、131架米格-31、274架苏-24、193架苏-25、123架苏-30、125架苏-34、78架苏-35;另外,俄罗斯正在服役的主力轰炸机数量有110架左右,其中图-22M3轰炸机50架左右,图-95轰炸机40架左右,图-160轰炸机16架左右。
这样的军事力量,不要说一个小小的乌克兰了,就算对付整个欧洲都是绰绰有余的!
俄罗斯军力世界排名第二,你以为那是闹着玩的呢?
有些人说俄军的战力不行,我真不知道他们的脑袋里到底是怎么想的?他们真的以为军事排名世界第二的头衔是一个摆设?不懂,去查查啊,不能张口就胡说八道啊!
接下来,俄罗斯就要动真格的了!
如果在接下来的战争中,俄罗斯的损失比较小的话,那么乌克兰只要答应俄罗斯上面提出的6个条件就可以了。
但是……
但是,如果接下来的战争中,乌克兰给俄罗斯造成比较大的损失,那么乌克兰就惨了!
那时,俄罗斯的条件就绝对不是上面六条那么简单了!
大家再来看下局势图中画圈的地方:
目前被俄罗斯占领的赫尔松南部地区的一名乌克兰高级官员表示,俄罗斯人正在敦促地区委员会同意就该地区从乌克兰“独立”进行全民投票。
赫尔森地区委员会代表Serhiy Khlan说:“占领者正在准备就创建赫尔森人民共和国举行全民公决。”
大家知道俄罗斯为什么想要让那个地方独立公投吗?
大家知道俄军打下赫尔森后做的第一件事是什么吗?
炸掉了北克里米亚运河上的混凝土拦水坝!
这是怎么回事?
克里米亚是一个半岛,85%的淡水需要从第聂伯河引水,而在2014年俄罗斯吞并了克里米亚以后,乌克兰就在运河上修建了拦水坝,停止了对克里米亚的供水。
岛上没有淡水,俄罗斯得想办法解决吧?
这给俄罗斯造成多大损失呢?
据俄罗斯卫星通讯社sputniknews报道,俄罗斯总检察院网站的材料显示,基辅关闭北克里米亚运河造成的总损失超过1.4万亿卢布。
1.4万亿卢布相当于多少钱?
相当于1400亿人民币!
所以,如果乌克兰给俄军造成比较大的损失,那么赫尔森也就不属于乌克兰的了!
顿巴斯地区独立了,赫尔森也独立了,夹在它们中间的是马里乌波尔,你觉得俄罗斯会放过吗?
你以为这是最坏的结果吗?
不,还有更坏的!
大家还记得在乌克兰战争以后,普京说过一句非常吓人的话吗?
3月5日,俄罗斯总统普京表示,乌克兰现任政府必须明白,如果继续抵抗俄罗斯军队,乌克兰的国家地位可能不保。
这是什么意思啊?
意思就是:以后,这个世界上可能再也没有乌克兰这个国家了,整个乌克兰都要重回俄罗斯了!
普京早就为这件事打好伏笔了!
想要灭亡一个国家,首先要做的是什么?
欲灭其国,先亡其史!
普京恰恰做完了这件事。
在俄乌战争开打之前,普京就发表了一篇非常长的讲话,在那次讲话中,普京除了历数了西方不断步步紧逼、出尔反尔外,还细数了这么年俄罗斯对乌克兰的“恩情”,其中包括帮助乌克兰还了2500亿美元债务、提供千亿美元的低息贷款、低价卖能源让乌克兰节省了827亿美元(算的这么清楚)……
然后话锋一转:就连乌克兰这个国家都是俄罗斯人赏给乌克兰的。
大家看下面的图,普京说:乌克兰的东部是列宁给的,南部是赫鲁晓夫给的,西部是斯大林给的,北部是以前俄国人从其它国家抢来的,真正属于乌克兰人自己的就是中间那么点大!
大家明白了吧,普京从历史的角度把乌克兰给解构了!
这就是所谓的“欲灭其国,先亡其史”!
普京的意思很明显:既然你乌克兰不承认苏联、反苏联,正好我们也不承认。既然我们都不承认苏联,那么苏联给出去的东西自然是不作数的,我们都要拿回来!
现在大家明白了,乌克兰面临的最严重后果可不是现在俄罗斯提出的六个要求,搞不好,乌克兰就把整个国家给整没了!
总之,乌克兰这个国家是完蛋了!
这就是做美狗的下场!
转自:https://mp.weixin.qq.com/s/vWRW75HhqBszLz1YA6cvYw
本篇和大家介绍一个经典的异常检测算法:局部离群因子(Local Outlier Factor),简称LOF算法。
Local Outlier Factor(LOF)是基于密度的经典算法(Breuning et. al. 2000), 文章发表于 SIGMOD 2000, 到目前已经有 3000+ 的引用。
在 LOF 之前的异常检测算法大多是基于统计方法的,或者是借用了一些聚类算法用于异常点的识别(比如 ,DBSCAN,OPTICS)。这些方法都有一些不完美的地方:
相比较而言,基于密度的LOF算法要更简单、直观。它不需要对数据的分布做太多要求,还能量化每个数据点的异常程度(outlierness)。
下面开始正式介绍LOF算法。
首先,基于密度的离群点检测方法有一个基本假设:非离群点对象周围的密度与其邻域周围的密度类似,而离群点对象周围的密度显著不同于其邻域周围的密度。
什么意思呢?看下面图片感受下。
集群 C1
包含了 400 多个点,集群 C2
包含 100 个点。C1
和 C2
都是一类集群点,区别是 C1
位置比较集中,或者说密度比较大。而像 o1
、o2
点均为异常点,因为基于我们的假设,这两个点周围的密度显著不同于周围点的密度。
LOF 就是基于密度来判断异常点的,通过给每个数据点都分配一个依赖于邻域密度的离群因子 LOF,进而判断该数据点是否为离群点。 如果 ,则该点为离群点,如果 ,则该点为正常数据点。
那什么是LOF呢?
了解LOF前,必须先知道一下几个基本概念,因为LOF是基于这几个概念而来的。
1. k邻近距离
在距离数据点 最近的几个点中,第 个最近的点跟点 之间的距离称为点 的 K-邻近距离,记为 k-distance (p),公式如下:
点 为距离点 最近的第 个点。
比如上图中,距离点 最近的第 个点是点 。
这里的距离计算可以采用欧式距离、汉明距离、马氏距离等等。比如用欧式距离的计算公式如下:
这里的重点是找到第 个最近的那个点,然后带公式计算距离。
2. k距离领域
以点 为圆心,以k邻近距离 为半径画圆,这个圆以内的范围就是k距离领域,公式如下:
还是上图所示,假设k=4,那么点 1-6 均是邻域范围内的点。
3. 可达距离
这个可达距离大家需要留意点,点 到点 的第 可达距离:
这里计算 到点 的第 可达距离,但是要以点 为中心,取一个最大值,也就是在点 与 的距离、距离点 最近的第 个点距离中取较大的一个,如图下所示。
距离 远,那么两者之间的可达距离就是它们的实际距离。如果距离足够近,如点 ,实际距离将被 的 距离代替。所有 接近 的统计波动 可以显著减少,这可以通过参数 来控制, 值越高,同一邻域内的点的可达距离越相似。
4. 局部可达密度
先给出公式。
数据点 的局部可达密度就是基于 的最近邻的平均可达距离的倒数。距离越大,密度越小。
5. 局部异常因子
根据局部可达密度的定义,如果一个数据点跟其他点比较疏远的话,那么显然它的局部可达密度就小。但LOF算法衡量一个数据点的异常程度,并不是看它的绝对局部密度,而是看它跟周围邻近的数据点的相对密度。
这样做的好处是可以允许数据分布不均匀、密度不同的情况。局部异常因子即是用局部相对密度来定义的。数据点 的局部相对密度(局部异常因子)为点 邻域内点的平均局部可达密度跟数据点 的局部可达密度的比值,即:
了解了 LOF 的定义以后,整个算法也就显而易见了:
对于每个数据点,计算它与其它所有点的距离,并按从近到远排序;
对于每个数据点,找到它的 k-nearest-neighbor,计算 LOF 得分;
如果LOF值越大,说明越异常,反之如果越小,说明越趋于正常。
优点
LOF 的一个优点是它同时考虑了数据集的局部和全局属性。异常值不是按绝对值确定的,而是相对于它们的邻域点密度确定的。当数据集中存在不同密度的不同集群时,LOF表现良好,比较适用于中等高维的数据集。
缺点
LOF算法中关于局部可达密度的定义其实暗含了一个假设,即:不存在大于等于 k 个重复的点。
当这样的重复点存在的时候,这些点的平均可达距离为零,局部可达密度就变为无穷大,会给计算带来一些麻烦。在实际应用时,为了避免这样的情况出现,可以把 k-distance 改为 k-distinct-distance,不考虑重复的情况。或者,还可以考虑给可达距离都加一个很小的值,避免可达距离等于零。
另外,LOF 算法需要计算数据点两两之间的距离,造成整个算法时间复杂度为 。为了提高算法效率,后续有算法尝试改进。FastLOF (Goldstein,2012)先将整个数据随机的分成多个子集,然后在每个子集里计算 LOF 值。对于那些 LOF 异常得分小于等于 1 的,从数据集里剔除,剩下的在下一轮寻找更合适的 nearest-neighbor,并更新 LOF 值。
有两个库可以计算LOF,分别是PyOD
和Sklearn
,下面分别介绍。
使用pyod
自带的方法生成200个训练样本和100个测试样本的数据集。正态样本由多元高斯分布生成,异常样本是使用均匀分布生成的。
训练和测试数据集都有 5 个特征,10% 的行被标记为异常。并且在数据中添加了一些随机噪声,让完美分离正常点和异常点变得稍微困难一些。
from pyod.utils.data import generate_data
import numpy as np
X_train, y_train, X_test, y_test =
generate_data(n_train=200,
n_test=100,
n_features=5,
contamination=0.1,
random_state=3)
X_train = X_train * np.random.uniform(0, 1, size=X_train.shape)
X_test = X_test * np.random.uniform(0,1, size=X_test.shape)
PyOD
下面将训练数据拟合了 LOF 模型并将其应用于合成测试数据。
在 PyOD
中,有两个关键方法:decision_function
和 predict
。
from pyod.models.lof import LOF
clf_name = 'LOF'
clf = LOF()
clf.fit(X_train)
test_scores = clf.decision_function(X_test)
roc = round(roc_auc_score(y_test, test_scores), ndigits=4)
prn = round(precision_n_scores(y_test, test_scores), ndigits=4)
print(f'{clf_name} ROC:{roc}, precision @ rank n:{prn}')
>> LOF ROC:0.9656, precision @ rank n:0.8
可以通过 LOF 模型方法查看 LOF 分数的分布。在下图中看到正常数据(蓝色)的分数聚集在 1.0 左右。离群数据点(橙色)的得分均大于 1.0,一般高于正常数据。
Sklearn
在scikit-learn
中实现 LOF
进行异常检测时,有两种模式选择:异常检测模式 (novelty=False)
和 novelty检测模式 (novelty=True)
。
在异常检测模式下,只有fit_predict
生成离群点预测的方法可用。可以使用negative_outlier_factor_
属性检索训练数据的异常值分数,但无法为未见过的数据生成分数。模型会根据contamination
参数(默认值为 0.1)自动选择异常值的阈值。
import matplotlib.pyplot as plt
detector = LOF()
scores = detector.fit(X_train).decision_function(X_test)
sns.distplot(scores[y_test==0], label="inlier scores")
sns.distplot(scores[y_test==1], label="outlier scores").set_title("Distribution of Outlier Scores from LOF Detector")
plt.legend()
plt.xlabel("Outlier score")
在novelty检测模式下,只有decision_function
用于生成异常值可用。fit_predict
方法不可用,但predict
方法可用于生成异常值预测。
clf = LocalOutlierFactor(novelty=True)
clf = clf.fit(X_train)
test_scores = clf.decision_function(X_test)
test_scores = -1*test_scores
roc = round(roc_auc_score(y_test, test_scores), ndigits=4)
prn = round(precision_n_scores(y_test, test_scores), ndigits=4)
print(f'{clf_name} ROC:{roc}, precision @ rank n:{prn}')
该模式下模型的异常值分数被反转,异常值的分数低于正常值。
转自:https://mp.weixin.qq.com/s/9ANa5QKV65ZEWCaisWDjEA