《三国演义》每回内容梗概(31~60)

第三十一回 曹操仓亭破本初 玄德荆州依刘表 袁绍手下主要有三股势力:长子袁谭守青州;次子袁熙守幽州;三子袁尚,后妻刘氏所生,绍最爱之,留身边,守冀州;外甥高干守并州。听说袁绍官渡之战败了,都来支援。于是袁绍聚集四州兵马,屯兵仓亭,准备再和曹操干一仗。结果,曹操谋士程昱献十面埋伏之计,大败袁绍,于是袁绍回老巢,转为防守。 却说刘备势力趁曹操忙于官渡、仓亭之战,偷袭许昌。曹操打败了袁绍之后,赶紧南下收拾刘备。刘备大败,谋士孙乾建议投靠荆州刘表。刘表谋士蔡瑁进谏:不可。刘备先从吕布,后事曹操,近投袁绍,皆不克终,足可见其为人。今若纳之,曹操必加兵于我,枉动干戈。不如斩孙乾之首,以献曹操,操必重待主公也。不过孙乾凭口才和智勇,打动刘表,刘表同意接受刘备。 第三十二回 夺冀州袁尚争锋 决漳河许攸献计 却说曹操见刘备依附了刘表,打算转而继续攻打袁绍集团。三子袁尚出阵迎敌,大败而走。袁绍听说袁尚大败,旧病复发,吐血斗升而死。临死之前,刘氏问袁尚能继位吗,袁绍点头。 曹操谋士郭嘉进谏,袁绍立了三子为后,长子袁谭必定会和袁尚争夺继位权,不如先退兵,让他们兄弟两先内斗,等两败俱伤之后,再进军一举歼灭他们。曹操从其言。果然袁谭和袁尚为了继位权干起来了,袁谭败,遂投降曹操。曹操进军攻打冀州城,袁尚败走,谋士审配守冀州城。曹操谋士许攸献计,可决漳河之水淹冀州城。果然冀州城被淹,城里也没粮食,审配大败,曹操占领冀州城。 第三十三回 曹丕乘乱纳甄氏 郭嘉遗计定辽东 曹操拿下冀州城之后,他的儿子曹丕进城时发现袁熙的老婆甄氏很漂亮,于是没有杀她,把她留下来当老婆了。 却说袁谭听说曹操打跑了袁尚,竟然想夺回冀州,不听曹操指挥了。于是袁谭和曹操干了一仗,袁谭被杀。高干所在的并州团队也被曹操灭了,高干被杀。袁尚被曹操逼得走投无路,投奔兄长袁熙,两兄弟又逃命到辽西乌桓。 却说曹操谋士郭嘉,因北伐路途遥远水土不服,生病了,于是曹操就把他留在了易州养病,自己继续北伐追杀袁尚和袁熙。袁氏兄弟被追的又往辽东跑了。但是路上天气寒冷干旱,军队又缺粮。曹操打算暂时回撤,回到易州时,郭嘉已经死了,给曹操留了一个精囊妙计,告诉曹操不要紧逼袁氏兄弟,自然有人提着他两的头来的。果然,辽东太守公孙康担心袁氏兄弟来到辽东之后,鸠占鹊巢,干脆杀了袁氏兄弟,来投降曹操了。自此曹操彻底平了袁绍集团,占领了北方。 第三十四回 蔡夫人隔屏听密语 刘皇叔跃马过檀溪 却说刘备依附刘表之后,江夏有人造反,刘备主动请缨去平反,平反的过程中缴获一匹的卢马。刘备本来把马送给了刘表,但刘表手下有人谗言说骑着个马妨主,刘表又把这个马还给刘备了。同时,刘表后妻蔡氏不太喜欢刘备,老担心他篡夺荆州,于是刘表暂且把刘备安排到襄阳的新野县了。在新野的时候,甘夫人还替刘备生了刘禅,因甘夫人晚上做梦梦见吞下北斗星而怀孕,给刘禅取小名阿斗。 后来有一天,刘表请刘备来荆州喝酒,问刘备到底应该立前妻陈氏生的长子刘琦还是后妻蔡氏生的次子刘琼呢。刘备说自古废长立幼,取乱之道,不如慢慢削弱蔡氏的权力,还是要立长子啊。这话正好被蔡夫人偷听到了,于是蔡夫人和外戚蔡瑁怀恨在心,打算除掉刘备势力,以绝后患。 于是蔡夫人找了个借口,宴请群臣,顺便把刘备也请去了。蔡瑁把吃饭的地方三面围住,只留西门,因为西门口正好有檀溪阻隔。吃饭的时候,蔡瑁把刘备的手下都灌醉了,准备对刘备下手。正好有一个小罗罗给刘备报信,刘备赶紧骑着的卢马往西门跑,的卢马也不是吃素的,纵身一跃居然跳过了檀溪,救了刘备一命。 第三十五回 玄德南漳逢隐沦 单福新野遇英主 却说刘备越过檀溪之后,一通乱走来到了南漳,遇到了著名的水镜先生司马徽,司马徽告诉刘备,得卧龙、凤雏其中之一,可安天下。但是司马徽自己不肯出山。 后来赵云他们找到了刘备,一起回了新野。刘备在街上遇到了谋士单福(其实是后面的徐庶)前来投靠。 却说曹操平了袁绍后,想南下占领荆州,就派曹仁先去新野打刘备了。正好,单福初露头角,献上一计,破了曹仁。 第三十六回 玄德用计袭樊城 元直走马荐诸葛 后续,刘备用单福的计谋,两破曹仁,同时占领了曹仁的樊城。曹仁败北之后,回见曹操,说刘备肯定有高人相助,后来程昱调查发现这个单福真名其实是徐庶,因之前杀人了,现在隐姓埋名改成单福,程昱说这个人的才能是自己的十倍。但是徐庶特别孝顺,家里只有一个老母亲。曹操为了把徐庶骗到许都,为自己效力,把徐庶的老母亲骗到许都,同时模仿其母的字迹,给徐庶写了一封信,说自己被曹操软禁,希望徐庶能来许昌救自己。因为徐庶很孝顺,所以泪别刘备,刘备也很不舍。徐庶为了感谢刘备,临走之前,给刘备推荐了襄阳隆中的诸葛亮,同时担心诸葛亮不出山帮刘备,还亲自去隆中跟诸葛亮说了刘备这个人。 第三十七回 司马徽再荐名士 刘玄德三顾草庐 司马徽听说徐庶在刘备这,特来刘备这找徐庶聊天,没曾想徐庶已经去曹操那了。刘备就顺便和司马徽聊起了卧龙,司马徽特别夸赞和推荐了诸葛亮。于是就出现了著名的刘备三顾茅庐请诸葛亮出山的故事。这三顾,第一次只见到门童,诸葛亮不在;第二次只见到诸葛亮的弟弟诸葛均,诸葛亮排行第二,大哥诸葛瑾,在孙权那当谋士。 第三十八回 定三分隆中决策 战长江孙氏报仇 刘备第三次顾茅庐终于见到了诸葛亮,诸葛亮虽在隆中,但尽知天下事,和刘备指点江山,分析当前天下局势,然后给刘备出了一个大政方针:将军欲成霸业,北让曹操占天时,南让孙权占地利,将军可占人和。先取荆州为家,后即取西川建基业,以成鼎足之势,然后可图中原也。 话分两头,吴国第三代领导人孙权,为了给父亲孙坚报仇(孙坚在第七回中被刘表手下杀害),进攻江夏,江夏当时是由刘表的部下黄祖镇守。黄祖大败,被杀,孙权拿下江夏。但是孙权手下谋士张昭说江夏是个孤城,不可守,不如回江东,于是孙权报完仇之后就回江东了。 第三十九回 荆州城公子三求计 博望坡军师初用兵 黄祖被杀,江夏失守,刘表慌了,请刘备共商御敌大计。这个时候,刘表前妻的儿子,也就是大儿子刘琦来找刘备帮忙,说继母容不下自己,自己的处境很危险。刘备说这个事情你可以找诸葛亮来处理。第二天,刘琦找诸葛亮帮忙,诸葛亮只推刘表自己的家事不便插手,推了三次,刘琦也求了三次。最后诸葛亮终于给了刘琦一计,说现在孙权走了之后,江夏急需人防守,你可以请求调去江夏,这样不在刘表和继母的身边,反倒安全。刘琦照做,刘表果然同意。 却说曹操听说刘备在新野招兵买马,久留必有后患,可早图之。于是曹操派出以夏侯惇为首的十万大军,直抵博望坡,以窥新野。诸葛亮作为刘备的军师,第一次排兵布阵,安排大家用火攻,大败夏侯惇。这是诸葛亮初出茅庐的第一功。 第四十回 蔡夫人议献荆州 诸葛亮火烧新野 夏侯惇打了败仗,回报曹操,曹操大怒,亲自带领五十万大军南下攻打荆州,孔融进谏曹操说刘表刘备都是汉室宗亲,不可兴无义之师。曹操大怒,把孔融一家老小全杀了。此孔融即孔融让梨的孔融。 此时刘表病危,商议要立长子刘琦为荆州之主,让刘备辅佐。蔡夫人听说之后,刘表死后,篡改遗嘱,立次子(自己生的儿子)刘琮为荆州之主,而且不发丧。更可恨的是,听说曹操大军压境,刘琮手下和蔡夫人等人商议投降曹操,保全荆州之主的位置。当派使者去给曹操投降时,被刘备手下的人抓到,刘备这才知道刘表已死,刘琮把荆州卖了。这个时候,诸葛亮献上一计,曹操那么多兵来了,新野是守不住了,不如弃新野,走樊城。于是诸葛亮像上次博望坡一样,提前把新野的老百姓撤离,新野成了一个空城,诱敌入内,然后火烧新野。曹军大败。 第四十一回 刘玄德携民渡江 赵子龙单骑救主 虽然曹军大败,但毕竟人数众多,曹军一路追击,刘备一路逃难,前后去了樊城→襄阳→江陵,即使情况再危险,刘备也不肯放弃新野的老百姓,所以逃难的速度很慢。一路上被曹军冲杀,大家伙都走散了。刘备安排赵云护送甘夫人、糜夫人和阿斗,但是路上二位夫人也走散了,赵云就返回去,冲入敌军,找他们。路上经过长坂坡等地,找到了甘夫人,后来也找到人糜夫人和阿斗,但是糜夫人受伤了,不肯走,怕耽误赵云和阿斗,于是投井自杀了。于是赵云怀抱着阿斗,不断冲杀,突出重围。 第四十二回 张翼德大闹长坂桥 刘豫州败走汉津口 赵云救下阿斗之后,开始撤退,回到长坂坡,已经筋疲力尽了,这个时候正好张飞在长坂坡等着,于是请张飞支援。张飞站在长坂桥上,后面曹军追上,张飞厉声大喝:燕人张翼德在此!谁敢来决死战?,连叫了好几次,把曹军吓破胆了,曹军灰溜溜逃走了。 曹军走之后,张飞把长坂桥拆了,曹军知道后,知道刘备那边没什么兵力,之前是被吓唬到了,所以又开始追击刘备。刘备他们就退走到汉津,关羽守夏口,刘备刘琦去江夏,以成掎角之势。 第四十三回 诸葛亮舌战群儒 鲁子敬力排众议 这个时候,孙权听说刘表已死,刘备新败,想接连刘备共抗曹操。于是派鲁肃假借给刘表吊丧的名义,想接连刘备刘琦。而刘备也担心仅凭自己的力量难以抵御曹操的进攻,想要向孙权借兵,联合孙权一起抵抗曹操。于是派诸葛亮和鲁肃前往吴地柴桑,向孙权搬救兵。 而孙权的手下又分为两股势力,张昭等大部分认为为了保全吴国,必须投降曹操;而鲁肃认为,投降曹操之后,对其他大臣没有任何影响,他们是什么官还是什么官,但是对孙权就不同了,孙权就要听命于曹操,相当于势力被削弱了,所以他认为孙权应该起兵共同抵抗曹操。 在孙权处,诸葛亮和孙权的手下众多谋士进行辩论,凭三寸不烂之舌,把一个个谋士都辩论下去了。而且鲁肃也力排众议,建议孙权联合刘备一起抵抗曹操。 第四十四回 孔明用智激周瑜 孙权决计破曹操 孙权纠结的时候,吴国太提醒说当年孙策临死之前有言:内事不决问张昭,外事不决问周瑜,为什么不问问周瑜的意见呢。于是孙权把在外的周瑜召回来问建议。周瑜到了之后,主降派和主战派轮番来见周瑜,叫周瑜劝孙权降或战。周瑜是打算降的,轮到诸葛亮和鲁肃来说的时候,诸葛亮用了一个计谋来激将周瑜,说曹操来攻打吴国,目的是为了得到大乔和小乔,好锁到铜雀台欢愉。这可把周瑜气炸了,因为大乔是孙策的老婆,小乔是周瑜的老婆。周瑜一听,自己的老婆可能要被曹操抢走,马上改变主意主战了。 ...

July 11, 2019 · 1 min

《三国演义》每回内容梗概(1~30)

第一回 宴桃园豪杰三结义 斩黄巾英雄首立功 黄巾起义,刘关张结为兄弟,大小顺序为刘关张,张飞是卖猪肉的,很有钱。刘备:双股剑;关羽:青龙偃月刀;张飞:丈八蛇矛。 第二回 张翼德怒鞭督邮 何国舅谋诛宦竖 刘关张镇压黄巾起义有功,但只得到一个很小的县令官,与民秋毫无犯,但被上面下来检查的督邮视察时,因没有贿赂督邮,被督邮穿小鞋,张飞怒不可遏,鞭打督邮。 此时,朝廷内,十常侍专权,把持朝政。汉灵帝驾崩之后,何进拥立刘辩为皇,因为刘辩是灵帝和何进的姐姐的儿子。同时,把董后鸩杀。同时,何进为了除掉十常侍,引董卓入宫。 第三回 议温明董卓叱丁原 馈金珠李肃说吕布 十常侍压力山大,为了保命,先下手为强,请何皇后把何进单独召进宫,进宫的路上,把何进杀了。宫内大乱,十常侍被何进部下杀掉。同时,董卓入宫,为彰显威严,欲废少帝辩,立刘协为新皇帝,在温明园讨论废立之事时,忠臣丁原挺身反对,董卓仗势欺人,斥责丁原。无奈丁原背后站着义子吕布,董卓奈何不了丁原。 次日,丁原携吕布向董卓宣战,吕布骁勇善战,无人能破。此时,董卓部下李肃是吕布的老朋友,带着金银珠宝和董卓的赤兔马来劝说吕布,凭着李肃的三寸不烂之舌和吕布的头脑简单,吕布被说服,杀掉义父丁原,同时投奔董卓麾下。 第四回 废汉帝陈留践位 谋董贼孟德献刀 董卓既得吕布,态度更加强硬,9月,废少帝,立陈留王刘协为新皇帝。把刘辩打入冷宫,某日,刘辩发牢骚写了首诗,董卓抓住机会,赐鸩酒把刘辩和何太后都杀了。 曹操为了谋杀奸臣董卓,偷偷带着司徒王允的七宝刀,找了一个机会靠近董卓,本来要刺杀董卓,没成想被董卓从衣镜里发现了,曹操马上改口说有一口很好的宝刀,要献给董卓,然后开溜。董卓后来才get到曹操是要来刺杀自己的。 第五回 发矫诏诸镇应曹公 破关兵三英战吕布 曹操逃出来之后,被董卓通缉,所以只能发布通告,尽书董卓恶行,招兵买马,准备讨伐董卓。各大诸侯太守都来响应,组成了一个十八路诸侯联盟,盟主是原朝廷重臣袁绍。但是袁绍统领诸侯,调度大军的能力不够,各诸侯也各怀鬼胎,没有凝聚力。 前锋部队孙坚在进军汜水关时被华雄击败,华雄不可一世,在潘凤等大将接连被华雄斩杀之时,关羽主动请缨前去战华雄,在温酒未冷却的极短时间内斩杀华雄,关羽从此名震诸侯。此即温酒斩华(huà)雄的故事。 董卓折了华雄,起兵二十万,兵分两路,其中一路由董卓亲自带队,和吕布等人,守住虎牢关,就是标题中的关兵中的关。在虎牢关处,吕布骑着赤兔马,不可一世,盟军无人能敌。最后,刘关张三人亲自出马,大战吕布,吕布败走。 第六回 焚金阙董卓行凶 匿玉玺孙坚背约 吕布新败,董卓引兵回洛阳,迁都长安,同时把洛阳的宫殿烧毁。孙坚飞奔洛阳,救火的同时,在井中发现了传国玉玺,并私藏起来。后来被盟主袁绍发现,孙坚感到被羞耻了,拔寨离洛阳而去。袁绍大怒,写信给荆州刘表,因为孙坚回老家江东——扬州(?)要经过刘表家,所以袁绍写信给刘表,叫他半路截住孙坚。果然在半路上,孙坚和刘表来了一场恶战,亏孙坚部下三员大将程普、黄盖、韩当死救得脱。自此,孙坚和刘表结怨。 第七回 袁绍磐河战公孙 孙坚跨江击刘表 袁绍屯兵河内,缺少粮草,向冀州太守韩馥借粮,谋士逢纪说大丈夫落到向别人借粮,可耻啊,冀州乃钱粮广盛之地,不如取而代之。袁绍于是和公孙瓒密谋,让公孙瓒出兵冀州,则韩馥必向袁绍求救,袁绍乘虚而入,占领冀州,然后和公孙瓒平分冀州。 公孙瓒照做,但是当袁绍占领冀州之后,并没有和公孙瓒平分冀州,而是独占了。公孙瓒派弟弟公孙越去袁绍处,想要分点油水,没想到反被袁绍杀害。于是,公孙瓒大怒,举兵攻打袁绍。两军交战于磐河。此战互有胜负,公孙瓒手下赵云出场,和同在公孙瓒手下的刘关张相见,一见如故,分别时泪如雨下。 袁绍的弟弟袁术,在南阳,听说哥哥新得冀州,想要哥哥赏赐点马匹,袁绍不给,自此兄弟不睦。袁术又向荆州刘表借粮,刘表也不给。袁术怒了,写信给孙坚,说昔日孙坚私藏玉玺回老家的路上,被刘表伏击,今日,我袁术愿与你结盟,攻打刘表。于是孙坚果然起兵,跨过汉水(长江),攻打刘表。没成想中了刘表部下蒯良的计谋,被杀了,可惜啊。孙坚部下黄盖生擒刘表部下黄祖,于是和刘表交换回孙坚尸体。孙坚大儿子孙策,字伯符;二儿子孙权,字仲谋。 第八回 王司徒巧使连环计 董太师大闹凤仪亭 董卓在长安听说孙坚死了,更加骄奢淫逸。此时,司徒王允为了江山社稷愁死了,王允府上的歌伎貂蝉,特别漂亮,允以亲女待之。貂蝉想帮王允分担忧愁,于是王允想出了一个连环计。因董卓和其干儿子吕布都是有勇无谋,贪财好色之徒,王允先把貂蝉许配给吕布,然后又悄悄把貂蝉送给董卓。貂蝉从中挑不离间,致使父子二人翻脸。 因为貂蝉已经被送到董卓府上,有一天,吕布趁着董卓和汉献帝聊天,偷偷来到董卓府上,在府上的凤仪亭看到了貂蝉,和貂蝉搂搂抱抱,被董卓赶回来发现了,董卓大闹凤仪亭,追着吕布打。自此父子二人结怨。 第九回 除暴徒吕布助司徒 犯长安李傕听贾诩 经过了上面的事情,貂蝉劝董卓搬家,于是董卓和貂蝉搬到郿坞去了。司徒王允和吕布想了一个计策,说汉献帝病刚好,想召集文武百官吃个饭,于是派昔日董卓心腹李肃(就是第三回的李肃,因为董卓没有给李肃升官,李肃对董卓也有怨念),去郿坞宣旨。董卓傻乎乎兴高采烈来到宫内,被早就在此埋伏的王允、吕布、李肃等人杀死,同时去郿坞把董卓全家灭口,包括杀了董卓谋士李儒。吕布得到貂蝉。 董卓手下四员大将李傕、郭汜、张济、樊稠,听说董卓被杀,打算吃个散伙饭,各自逃命。谋士贾诩说,我们都被通缉,既然自首也要死,不如来个你死我活。四人听了之后认为有道理,于是在西凉起兵,杀奔长安。而且他们制定的军事政策是,其中两个人在山外前后诱杀吕布,但又不恋战,另两个人偷偷起兵直接攻打长安城,让吕布和长安城首尾不能接应。此计果然奏效,董卓余党在长安城内为内应,打开城门,四员大将进入城中,烧杀抢掠,把王允也杀了。吕布弃了家小,投袁术去了。 第十回 勤王室马腾举义 报父仇曹操兴师 李傕、郭汜、张济、樊稠占领宫内之后,骄奢蛮横,残虐百姓。西凉太守马腾和并州刺史韩遂,密谋贼党,但是都失败了。此时,马腾之子马超出场。樊稠因放过同乡人韩遂,被李傕郭汜杀掉。 因朝廷昏庸无能,青州黄巾起义又起,太傅朱儁推荐派曹操去剿灭黄巾起义,李傕郭汜同意。东郡太守曹操和济北相鲍信一同破贼。 曹操在兖州,招贤纳士,群贤毕至。文官:荀彧、荀攸,叔侄二人;程昱;郭嘉;刘晔;满宠;吕虔;毛玠。武官:于禁;典韦。自是曹操部下文有谋臣,武有猛将,威镇山东。 曹操一高兴,打算把琅琊郡的老父亲曹嵩接过来享天伦之乐,于是,曹嵩和弟弟曹德并一家老小准备赶往兖州。途径徐州,徐州太守陶谦,想讨好曹操,大设宴席款待曹嵩等人,并派部下张闿护送曹嵩。没曾想,张闿原是黄巾余党,在陶谦处没有得到重用,今贼心不改,在路上把曹嵩一家老小全杀了。曹操听到消息,大怒,亲自起兵杀奔徐州。 第十一回 刘皇叔北海救孔融 吕温侯濮阳破曹操 于是,徐州陶谦,向北海孔融求救,正商议间,黄巾余党管亥来北海攻打孔融。幸好孔融平时人品好,善待城外的一个老奶奶,老奶奶听说孔融有难,叫回来省亲的儿子太史慈去救孔融。太史慈虽然很厉害,但毕竟只有一个人,孔融就叫太史慈杀出重围,请刘备来救援。刘备于是向公孙瓒借了赵云,带着关张来救孔融。 刘备来了之后,先给曹操写了封信,好言相劝,劝和。正好,这个时候,从宫中逃出来的吕布,攻陷了曹操的老巢兖州和濮阳,于是曹操送刘备一个人情,撤兵回老巢了。 曹操经过商议之后,准备亲自领兵,去夺回濮阳,在濮阳和吕布进行了恶战,战败,差点被吕布围剿。 第十二回 陶恭祖三让徐州 曹孟德大战吕布 吕布的谋士陈宫,诡计多端,出了一个点子,诱使曹操进濮阳城,待曹操进城之后,关门放火,差点把曹操灭了,众将死救得脱。 陶谦在徐州,已经63岁了,儿子又无才,于是,想把徐州送给刘备接管,刘备死活不肯要。就这样来来回回三次,陶谦都要死了,刘备才肯接管徐州。 曹操自从被吕布打了个败仗,回老家待着。谋士荀彧说,现在收成不好,可以去陈地、汝南、颍川抢占地盘,这些地方都是黄巾余党,乌合之众,轻易可破,又可得粮草。曹操听之,果然占领了这些地方,顺带还收了一员武将许褚。 某天听说兖州吕布手下大将薛兰、李封都出去劫掠了,可以乘虚而入,夺回兖州。曹操听之,果然夺回兖州,同时六员大将齐战吕布,吕布败走。 第十三回 李傕郭汜大交兵 杨奉董承双救驾 吕布败走之后,来徐州投奔刘备,屯兵小沛。 却说李傕郭汜在宫廷横行无忌,太尉杨彪和大司农朱儁密谋诛杀李傕郭汜。杨彪献一反间计:郭汜的妻子妒忌心很强,可派人秘密告诉郭汜妻子,郭汜在和李傕夫人偷情。反间计成功,李傕郭汜反目成仇,李傕劫了天子,郭汜劫了文武百官,每日厮杀。 ...

July 10, 2019 · 1 min

CS224N(1.10)Word Vectors 2 and Word Senses

这一讲是上一讲的补充,内容比较零碎,包括:Word2vec回顾、优化、基于统计的词向量、GloVe、词向量评价、词义等,前两个内容没必要再介绍了,下面逐一介绍后四个内容。 基于统计的词向量 词向量的目的就是希望通过低维稠密向量来表示词的含义,而词的分布式语义表示方法认为词的含义由其上下文语境决定。Word2vec把中心词和临近词抽取出来,通过预测的方式训练得到词向量。在Word2vec之前,传统的方式通过统计词的共现性来得到词向量,即一个词的词向量表示为其临近词出现的频率,如果两个词的含义很相近,则其临近词分布会比较像,得到的词向量也比较像。其具体计算过程在第一次作业中有详细的描述,这里再简单回顾如下。 假设一个语料库中包含三个句子,共有8个特异词(包括点号),对于每个词,统计其前后一个词的词频(临近窗口为1),由此能得到一个8×8的对称矩阵,其每一行(或每一列)表示该词的词向量。比如对于like这个词,在三个句子中,其左右共出现2次I,1次deep和1次NLP,所以like对应的词向量中,I、deep和NLP维的值分别为2,1,1。 这种基于词频统计的方法很简单,但是它有如下不足: 特异的词很多,所以矩阵很大,维度很高,需要的存储空间也很大 特异词的数目是在不断增长的,则词向量的维度也在不断增长 矩阵很稀疏,即词向量很稀疏,会导致很多NLP任务会遇到稀疏计算的问题 所以需要把上述计数矩阵转换为一个低维稠密的矩阵,方法就是SVD分解。上述矩阵原本是一个\(n\times n\)的矩阵,SVD分解后能得到一个\(n\times k\)的矩阵,其中\(k\ll n\)。即原本的词向量是一个\(n\)维的高维稀疏向量,变成了\(k\)维的低维稠密向量,而且还不会损失太多的信息。 2005年的一篇文章对上述简单的计数方法进行了改进,包括去掉停用词、使用倾斜窗口、使用皮尔逊相关系数等,提出了COALS模型,该模型得到的词向量效果也不错,也具有句法特征和语义特征。使用统计的方法和使用预测的方法训练词向量,两者的对比如下。基于统计计数的方法的主要特点是:训练速度快,能充分利用统计信息,主要用来捕获词的相似性。基于预测的方法的主要特点是:对语料库的大小可扩展,没有充分利用统计信息,能捕获除了词的相似性之外的其他复杂特征。 GloVe GloVe的全称是GloVe: Global Vectors for Word Representation,正是这门课的老师Christopher D. Manning的研究成果。有关GloVe论文的详细解读,可以看这篇博客。GloVe希望能综合上述基于统计和基于预测的两种方法的优点。 GloVe的基本思想依然是基于统计的方法,当统计得到共现矩阵X之后,可以计算得到词\(k\)是词\(i\)的临近词的概率: $$P_{i,k}=\dfrac{X_{i,k}}{X_{i}}$$再定义两个\(P\)的比值: $$ratio_{i,j,k}=\dfrac{P_{i,k}}{P_{j,k}}$$如果词\(k\)在两个词\(i\)和\(j\)的临近概率相同,无论是同样大(water)还是同样小(fashion),经过比值计算后,\(ratio_{i,j,k}\)都约等于1了,说明在维度water和fashion上,无法区分ice和steam。而在维度solid和gash上,由于概率\(P\)的差异,导致\(ratio_{i,j,k}\)很大或者很小,这是有意义的,说明在solid和gas维度上,可以区分ice和steam的语义。 基于这样的观察,GloVe首先统计语料库中三元组\(i,j,k\)的\(ratio_{i,j,k}\),然后初始化词向量\(v\),构造函数\(g\),使得利用词向量计算得到的\(g(v_{i},v_{j},v_{k})\)和真实\(ratio_{i,j,k}\)尽量接近。 $$\dfrac{P_{i,k}}{P_{j,k}}=ratio_{i,j,k}=g(v_{i},v_{j},v_{k})$$$$J=\sum_{i,j,k}^N(\dfrac{P_{i,k}}{P_{j,k}}-g(v_{i},v_{j},v_{k}))^2$$但是上述方法的复杂度太高了,对于一个\(N\times N\)的共现矩阵,上述算法需要计算所有的三元组,复杂度是\(N\times N\times N\)。GloVe文章通过各种转换技巧,把复杂度降为了一个\(N\times N\)的问题,具体过程可以看上面提到的博客或者paper原文。 总的来说,基于共现矩阵这种统计的方法,能捕获整个语料库全局的信息;而类似word2vec的预测的方法,则主要捕获局部的滑动窗口内的共现信息,两种方法训练得到的词向量效果都不错。 词向量评价 评价词向量的好坏主要有两个尺度,一是内部任务评价(intrinsic),一是外部任务评价(extrinsic),两者的主要特点如下。大概意思是内部任务是根据词本身具有的性质,比如近义、反义等,评价词向量本身的性能。外部任务是指词向量对NLP下游任务的性能的影响,比如同样是一个文本分类问题,换不同的词向量,对文本分类任务的性能的影响能反映出词向量的性能。 常见的内部任务评价是词的类比推理(Word Vector Analogies),就是类似man:woman :: king:queen这种,word2vec还专门整理出了这样的测试数据:word2vec/trunk/questions-words.txt。另一个内部任务评价是使用训练得到的词向量计算的词相似度,和人类认为的相似度做比较,有团队专门整理出了人类对两个词的相似度打分,具体可以看这里。 对词向量的外部任务评价就很多了,几乎所有的NLP任务都可以用来作为词向量的外部任务评价,比如命名实体识别、文本分类等等,这里不再展开。 词义 一个词往往具有多个含义(word senses),特别是对于常用的词或者存在很久的词。那么一个词向量能同时包含这个词的多个语义吗?有文章把一个词的多个语义通过线性加权的方式叠加到一个词向量中,然后还能通过稀疏编码的方式求解出每个语义的词向量,具体可以看下图中的参考文献。

June 23, 2019 · 1 min

CS224N(1.8)Introduction and Word Vectors

今天开始介绍大名鼎鼎的NLP网课Stanford-CS224N,我学的是Winter 2019的版本,课程网站:https://web.stanford.edu/class/archive/cs/cs224n/cs224n.1194/。第一讲内容为课程简介和词向量。 词向量即用来表示这个词的含义的向量。早期的NLP常用one-hot编码来表示词向量,假如词典中共有10000个词,则这个one-hot向量长度就是10000,该词在词典中所处位置对应的值为1,其他值为0。 one-hot表示方法虽然简单,但其有诸多缺点:1. 词典中的词是不断增多的,比如英语,通过对原有的词增加前缀和后缀,可以变换出很多不同的词,one-hot编码会导致向量维度非常大,且每个向量是稀疏的;2. 不同词的one-hot编码向量是垂直的,在向量空间中无法表示近似关系,即使两个含义相近的词,它们的词向量点积也为0。 既然one-hot编码有这么多缺点,那我们就换一种编码,one-hot是高维稀疏向量,那新的编码就改用低维稠密向量,这样就解决了上述问题,那么怎样得到一个词的低维稠密的词向量呢?这就是word2vec算法。 word2vec采用了分布式语义的方法来表示一个词的含义。本质上,一个词的含义就是这个词所处的上下文语境。回想一下我们高中做英语完形填空时,一篇短文,挖了好多空,让我们根据空缺词的上下文语境选择合适的词。也就是说上下文语境已经能够确定这个词的含义了,如果选词正确,也就意味着我们理解了这个空缺词的含义。 word2vec算法发表于2013年,包括两种训练算法Skip-grams (SG)和Continuous Bag of Words (CBOW),这两种方法很类似,其中CBOW和上述介绍到的英语完形填空几乎是一样的,由上下文词预测中心词;而SG则和CBOW正好相反,由中心词预测上下文词,本文主要介绍SG算法。 给定一个语料库,这个语料库包含了很多文章,每篇文章又包含很多句子,每个句子又包含很多词语。所以一个语料库是一个天然的标注集,因为对于每一个选定的中心词,我们都知道其临近的词是什么。这样一个(中心词,临近词)对就构成了一个标注集。SG算法的中心思想就是对于每个选定的中心词,尽量准确的预测其周围可能出现的词的概率分布。具体来说,SG算法首先随机初始化每个词的词向量;然后预测不同临近词出现的概率,最后最大化实际临近词出现的概率。 形式化来说,就是用极大似然估计的方法,求解每个词的词向量。其目标函数如下,其中\(\theta\)是待求解的参数;\(t\)为选定的中心词位置;对于每个\(t\)(外层\(\prod\)),估计其邻域\(\pm m\)个词出现的概率(内层\(\prod\))。 求解极大似然估计的方法比较成熟,一般先把极大似然转换为最小化-log似然,然后用梯度下降求解。所以核心问题就变成了如何求解\(P(w_{t+j}|w_t;\theta)\)。 对于每个词\(w\),定义其两个词向量:\(v_w\)表示当\(w\)为中心词时的词向量,\(u_w\)表示当\(w\)为其他词的临近词时的词向量。则对于一个中心词\(c\)和其临近词\(o\),有: 上式本质是一个softmax函数,因为给定\(c\),\(o\)相当于是标注结果,所以把它们的点积作为分子,希望分子越大越好;而分母则是所有可能的\(u_w\)和\(v_c\)的点积之和,起到归一化作用。 题外话:讲这张幻灯片时,还提到softmax的一个形象解释。softmax包括max和soft两层含义。假设对于一个数组[1,2,3,4],直接max也就是hard max的结果是保留最大值,其他全变为0,即[0,0,0,4]。但是softmax对他们求\(\frac{exp(x_i)}{\sum_{j=1}^nexp(x_j)}\),变成了[0.03, 0.09, 0.24, 0.64],最大的还是第4个数,但第四个数的优势被放大了,原来4只是1的4倍,现在0.64是0.03的21倍。所以softmax不但保留了max的属性,还变得更soft了,原来小的数不会被抹为0,只不过拉大了差异。 使用梯度下降还需要求解\(P\)对参数\(\theta\)的梯度,在这里\(\theta\)代表了所有词的中心词向量和临近词向量。对于上式,\(u_o\)、\(v_c\)等就是\(\theta\)的一部分。不断利用求导的链式法则,容易得到: $$\begin{eqnarray}\frac{\partial P(o|c)}{\partial v_c}=u_o-\sum_{w\in V}P(w|c)u_w.\tag{1}\end{eqnarray}$$最后算出来的梯度很有意思,\(u_o\)表示观察到的上下文词向量(o表示observed),减号后面的是这个位置的期望的词向量,期望=概率*值。差值就是真实观察词向量减去期望词向量,这就是梯度。当它们的差值很小时,说明给定\(c\)能很好的预测其临近词的概率分布。 OK,当以上内容都准备妥当之后,我们就可以开始求解词向量了。首先随机初始化每个词\(w\)的中心词向量\(v_w\)和临近词向量\(u_w\);然后求解-log损失函数\(J(\theta)\);最后根据梯度下降更新所有参数\(\theta\)。 上述word2vec算法简单,直观,但写代码实现比较复杂。在实际应用场景中,人们往往使用神经网络的方法来求解词向量,具体教程请看这里: http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/ 。 我们把训练词向量的问题转换为端到端的文本分类问题。如下图所示,对于语料库中的一个句子,假设临近窗口为前后两个词,则可以抽取出如下图右边所示的训练样本,每个训练样本是一个(中心词,临近词)的pair。比如对于(the, quick)训练样本,我们希望神经网络在输入the这个中心词时,能以较高的概率预测出quick这个词。 网络的结构如下图所示,也非常简单,是仅含一个隐藏层的全连接网络。比如上图的一组训练数据是(the, quick),表示输入是the的one-hot编码,输出是quick的one-hot编码。假设词典里有10,000个不同的词,则one-hot编码长度为10,000。有一个隐藏层的全连接网络,对应权重构成两个权重矩阵,和输入层连接的矩阵为\(V\),其每一行表示词作为中心词时的词向量。输入行向量乘以\(V\)正好得到输入词的词向量,这对应课上讲的作为中心词的词向量\(v_c\)。 隐层和输出层连接的权重矩阵为\(U\),其每一列表示输出层的词的临近词词向量。隐层的行向量\(v_c\)乘以矩阵\(U\),得到词\(c\)的临近词的概率分布,再经过softmax激活,进行归一化。其实反过来看,从输出往隐层看,相当于输出层的行向量乘以\(U\)的转置,得到隐层词向量。这其实就是另一种训练词向量的方法CBOW,即英语完形填空,用临近词来预测中心词。 对于下图的神经网络,输出用softmax激活,损失函数使用-log损失,训练网络时使用梯度下降,其效果正好是课上讲的使用极大似然估计的方法! 另一方面,上图的这种结构是skip-gram模型,如果把对应的箭头反一下,输入变输出,输出变输入,其实就变成了CBOW模型了。 上述全连接网络虽然能很方便的计算词向量,但存在两个问题:1. 网络过于庞大,参数量太多;2. 训练样本太多,每个样本都更新所有参数,训练速度慢。针对这两个问题,作者分别提出了 subsampling 和 negative sampling 的技巧,具体请看教程: http://mccormickml.com/2017/01/11/word2vec-tutorial-part-2-negative-sampling/ 。 第一个问题,网络参数量太多。假设有1w个特异的词,词向量长度为300,整个网络就有两个300w的矩阵(上图的V和U)参数需要优化。另一方面,训练语料库往往是很大的,随随便便就是成百上千万的文章,由此拆分得到的训练词组对就更大了,很容易到上亿的级别。几百万的参数,几亿的训练数据, 导致网络太过庞大,训练不动。 subsampling技巧是指,每个词有一个保留概率p,以这个概率p保留其在训练数据中,以1-p删掉这个词。比如上面的例子,删掉了fox,则fox对应的4个训练数据也一并删掉了,所以能减少较多的训练数据。对于词\(w_i\),其概率\(P(w_i)\)公式如下,其中\(z(w_i)\)是词\(w\)的词频。概率p和这个词在语料库中的词频有关,词频越大,保留概率越低,即被删掉的概率越大,所以subsampling之后应该能较大的减少训练数据。 $$\begin{eqnarray}P(w_i) = (\sqrt{\frac{z(w_i)}{0.001}} + 1) \cdot \frac{0.001}{z(w_i)} .\tag{2}\end{eqnarray}$$ 第二个问题,原来的网络在训练时,对于每个输入中心词,都会对两个很大的参数矩阵V和U(和上面假设一样,300w)进行轻微的更新,更新操作太多了。 negative sampling技巧,只更新一小部分参数。比如对于(“fox”, “quick”),期望的输出是quick的one-hot,即只有quick对应位为1,其他为0。但网络的softmax输出肯定不可能是完完全全的quick为1,其他为0;有可能是quick为0.8,其他位有些0.001,0.03之类的非0值,这就导致输出层的所有神经元都有误差。按照传统的方法,输出层所有神经元对应的U矩阵的权重都要更新。negative sampling技巧是,只更新和quick连接的U权重以及随机选5个输出神经元的连接权重进行更新,这样一下把需要更新的U权重个数从300w降到了6*300=1800,只需要更新0.06%的参数,大大减小了参数更新量! 5个随机选中的神经元(输出位置,即对应1w维的某个词)被称为negative sample,被选中的概率和词频成正比,词频越大的词被选中的概率越大,和上面subsampling类似。概率公式如下,其中\(f(w_i)\)应该和(2)中的\(z(w_i)\)一样,都表示词频。 $$\begin{eqnarray}P(w_i) = \frac{ {f(w_i)}^{3/4} }{\sum_{j=0}^{n}\left( {f(w_j)}^{3/4} \right) }.\tag{3}\end{eqnarray}$$ 作业请见GitHub: https://github.com/01joy/stanford-cs224n-winter-2019/blob/master/1.8/assignment/a1/exploring_word_vectors.ipynb ...

June 22, 2019 · 1 min

“蛋白质结构预测”问题描述

相信很多学CS的同学之前都没听说过“蛋白质结构预测”这个问题,直到2018年12月初,一则劲爆消息瞬间引爆了CSer的朋友圈,那就是Google Deepmind团队开发的AlphaFold一举拿下当年的CASP比赛冠军,而且远远甩开了第二名。我当时就转载过类似的公众号文章,大家可以阅读并想象当时朋友圈的欢呼声:阿尔法狗再下一城 | 蛋白结构预测AlphaFold大胜传统人类模型。 当时,很多同学也转载过类似的文章,但其实很少有人真正明白“蛋白质结构预测”这个问题是什么,它的难度有多大,CASP是个什么比赛,以及AlphaFold的内部原理是什么。当然,对于这一连串的问题,我当时也是懵逼的。不过自己好歹也是个跟蛋白质有关的PhD,如此热点事件,自然是要关注的。不过之后一直没时间,直到今年相关顶级文章再次爆出,我就借着准备文献讲评的机会了解了相关的知识,在这里跟大家分享一下。 https://upload.wikimedia.org/wikipedia/commons/a/a9/Protein_folding.png 蛋白质结构分为四级,分别是一级结构、二级结构、三级结构和四级结构,下面分别描述。 一级结构 蛋白质的一级结构可以理解为一条线性的字符串,比如MSFIKTFSGKHFYYDKINKDDIVINDIAVSLSNICR。其基本组成单元是一个个的氨基酸,即一个个的字母。氨基酸有单字母表示和三字母表示,为了简洁,本文使用单字母表示,下图的例子是三字母表示。常见的氨基酸只有20种,所以一级结构的字符串通常只包含20种字母,不包含的6种字母是BJOUXZ。 http://oregonstate.edu/instruct/bb450/450material/schedule450s17e.html 本文大部分蛋白质基础知识都来源于此 20种氨基酸的结构符合一个通式,如下图所示,中间的碳原子称为Cα碳原子,表示它处在α位;左边连了一个氨基-NH2,称为N端;右边连了一个羧基-COOH,称为C端。20种不同氨基酸的差别就在于Cα上连接的侧链基团R,具体的差别网上一搜就能查到。 https://upload.wikimedia.org/wikipedia/commons/c/ce/AminoAcidball.svg 20种氨基酸连接的方式为脱水缩合,即一个氨基酸的羧基-COOH和另一个氨基酸的氨基-NH2反应,丢掉一个H2O,形成一个肽键-CO-NH-,如下图所示。丢掉了羧基和氨基的氨基酸被称为氨基酸残基,这个名词很形象,氨基酸缺胳膊少腿,所以变成了“残”基。 二级结构 二级结构就是在一级结构的字符串的基础上,肽链怎样进行盘旋、折叠等变换,形成一种局部的三维结构,这种局部的三维结构通常由氢键支撑。常见的二级结构有α螺旋和β折叠,如下图所示。其中α螺旋的每个残基的-NH的H和临近的第4个残基的-CO的O形成氢键,由此支撑α螺旋的结构稳定性,如下图的箭头所指虚线。β折叠则是两条肽链,平行排列,对应残基的-NH的H和-CO的O形成氢键,由此形成两股β折叠的结构,多股β折叠形成类似手风琴的样子。β折叠分为平行和反平行排列,我们前面介绍到肽段分为N端和C端,如果形成β折叠的两股链都是从N到C(或从C到N),则称为平行排列,否则是反平行排列。每股β折叠都有一个大箭头表示其方向。 细分的话,蛋白质的二级结构总共有8种,包括转角、无规则卷曲等。目前常采用DSSP的分类方法,有些文献会把8种结构粗分为α螺旋、β折叠和转角这三种结构。 由上图可知,蛋白质的二级结构极大的决定了其三级结构(下面介绍),所以有很多工作是研究怎样准确预测蛋白质的二级结构的,即预测每个氨基酸残基处于哪一种二级结构中。形式化表示就是,对于一个蛋白质一级结构字符串\(A_1A_2A_3A_4A_5…\),输出\(a_1a_2a_3a_4a_5…\),其中\(a_i\)∈{α螺旋,β折叠,转角}。所以,蛋白质的二级结构是一个端到端的问题,很像机器翻译,目前很多文章都会用深度学习NLP的方法来预测蛋白质的二级结构。 三级结构 简单理解,三级结构就是把多个二级结构拼接到一起,折叠成一个完整的蛋白质三维结构,如下图所示。维持蛋白质三级结构的力比较多样,除了氢键之外,还有二硫键、金属键等。 四级结构 简单理解,四级结构就是多个三级结构分子组合成一个复合物,就是四级结构。 https://en.wikipedia.org/wiki/Protein_quaternary_structure 对于CSer来说,由于四级结构仅仅是多个三级结构组合到一起,我们常说的蛋白质三维结构预测问题,通常是指预测蛋白质的三级结构。问题是,构成蛋白质链的原子非常多,我们怎样形式化描述一条蛋白质的三维结构呢?这还要从最原始的一级结构说起。 蛋白质结构预测问题 前面提到,两个氨基酸通过脱水缩合的方式形成肽键从而连接到一起形成一级结构(本文图四),肽键虽然是单键,但它具有类似双键的特点,即难以旋转(比如羧基中的-C=O键就是双键,无法旋转)。所以,由肽键及周围的6个原子形成了一个固定的肽键平面,这6个原子分别是-C-CO-NH-C-,如下图所示,箭头所指的红色键就是肽键,它周围画出了一个平面,就是肽键平面。 肽键平面的存在极大的简化了蛋白质结构,可以认为这6个原子的相对位置是固定的了!另一方面,跟这个平面相连的左右两个C原子的两个键是单键,所以他们可以旋转,旋转的角度称为扭转角ϕ和ψ,为了更直观的感受肽链的肽键平面和两个扭转角,可以看下面的动画:K0045879-Rotation_around_amide_bonds_in_protein.mp4(来自https://www.sciencephoto.com/media/639617/view) 事实上,扭转角ϕ和ψ并不是在360°范围内随机均匀分布的,1963年就有科学家统计过扭转角ϕ和ψ的分布,他们发现稳定的蛋白质结构的ϕ和ψ通常只分布在一小部分区域,如下图的拉氏图所示,这些区域正好对应了常见的α螺旋和β折叠的结构。 最后,我们还需要介绍一个角度,那就是ω。前面提到,虽然肽键具有双键的特点,难以旋转,但它在少数情况下还是可以旋转的。假设通常情况下,肽键的角度定义为ω=0°,如下图所示,红色的键即为肽键,这种结构的好处是它能让形成肽键的两个残基的侧链R(图中黑色基团)离得尽量的远,这样能保持比较稳定的结构。如果肽键旋转为ω=180°,变为下图的样子,则两个侧链R很靠近,就产生位阻效应,就不稳定,所以这种情况比较少见。但不管怎么说,肽键的扭转角ω也是一个变量因素。 综上所述,对于一条肽链,如果知道每个残基的三个扭转角ϕ、ψ和ω,则可以重构出肽链的主干部分的三维结构,这就像将极坐标转换为直角坐标一样容易。需要提醒的是,本文提到的蛋白质三维结构预测问题,对蛋白质的结构进行了简化,包括:1. 仅预测蛋白质或肽链的主干结构,不考虑侧链R的结构;2. 假设肽链主干中每个键的长度是固定的;3. 不考虑键的角度,比如对于上图的肽键,仅考虑肽键绕肽键轴本身的旋转,不考虑肽键绕着某一端原子的旋转,比如固定左边的蓝色小球,肽键和右边的红色小球旋转出平面了。 下图的肽键平面,详细的标识出了各个相对固定的值。 Figure 8-1 from Fundamentals of Biochemistry 所以,对于CSer来说,蛋白质的三维结构预测问题,就可以看成一个端到端的学习问题,输入是一个字符串,输出是每个字符(残基)对应的三个扭转角ϕ、ψ和ω,问题看起来非常的简洁漂亮。而且,这个问题和NLP中的序列标注、机器翻译等问题很像,所以很多NLP的技术可以用来预测蛋白质的三维结构。下图的插画就是最近发表在Cell Sytems上的一篇用LSTM预测蛋白质三维结构的文章,我会在下一篇博客中和大家分享这篇文章。 https://www.sciencedirect.com/science/article/pii/S2405471219300766?via%3Dihub 有关“蛋白质结构预测”本身的最后一个问题是,为什么能仅仅通过一级结构的序列信息,预测得到其三级结构呢?也就是说蛋白质结构预测这个问题是否可解,如果蛋白质的三级结构还由其他因素决定,那么即使Deeplearning玩出花了,在生物上也是不可行的。所以,每遇到一个新问题,都要自问一下,这个问题从原理上是否可解。对于“蛋白质结构预测”这个问题,最开始也有人进行了类似的自问,得到的答案是可行的: 1965年,安芬森(Anfinsen)基于还原变性的牛胰RNase在不需其他任何物质帮助下,仅通过去除变性剂和还原剂就使其恢复天然结构的实验结果,提出了“多肽链的氨基酸序列包含了形成其热力学上稳定的天然构象所必需的全部信息”的“自组装学说”,随后这个学说又得到一些补充。这些学说表明:氨基酸序列确定其空间构象,从而为蛋白质结构预测提供了可行性。 http://chinaxiv.org/user/download.htm?id=6478 CASP比赛 提到蛋白质三级结构预测,不得不提的是CASP这个比赛。CASP的全称是The Critical Assessment of protein Structure Prediction (CASP),即蛋白质结构预测的关键评估,被誉为蛋白质结构预测的奥林匹克竞赛。CASP从1994年开始举办,每两年一届,最近的一届是2018年的CASP13。 每一届CASP比赛,都会提供大约100条未知结构的蛋白质序列,让所有参赛者进行结构预测,比赛结束之后,主办方会通过生化方法测定这些蛋白质的三维结构,然后和参赛者预测的结果进行比对,然后给出预测得分。提供的蛋白质序列分为两类:一类序列和PDB数据库中已有结构的序列有相似性,由此可以基于模板预测,准确度比较高,这类算法称为Template-Based Modeling;另一类序列和PDB库已知结构的序列相似度很低,可以认为是全新的蛋白质,因为无法利用已有模板信息,需要进行从头测序(De novo或ab initio或Free Modeling),目前的准确率比较低。参赛选手也分为两组,一组是servers only,即仅允许算法参赛,给定3天的时间;另一组是human and servers,即允许人和算法合作,共同预测蛋白质结构,给定3周的时间。 CASP同时提供多种比赛项目,比如常规的结构预测(Regular targets)、数据辅助预测(Data-Assisted targets)和蛋白质接触面预测(Contact predictions)等,其中数据辅助预测中提供了核磁数据(NMR)、交联数据(XLMS)等,对的,交联数据就是我目前研究的pLink处理的数据。 ...

May 25, 2019 · 1 min

Neural Networks and Deep Learning(七)番外篇·Pytorch MNIST教程

由于本书成书较早(2015),作者当时使用的是Theano,但Theano已不再维护,所以本博客使用当下流行的Pytorch框架讲解MNIST图片分类的代码实现,具体就是Pytorch官方给出的MNIST代码:https://github.com/pytorch/examples/tree/master/mnist。 使用该工具在线制作:http://alexlenail.me/NN-SVG/LeNet.html 下面,我首先贴出经过我注释的Pytorch MNIST代码,然后对一些关键问题进行解释。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 from __future__ import print_function import argparse import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torchvision import datasets, transforms # 所有网络类要继承nn.Module class Net(nn.Module): def __init__(self): super(Net, self).__init__() # 调用父类构造函数 self.conv1 = nn.Conv2d(1, 20, 5, 1) # (in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True) self.conv2 = nn.Conv2d(20, 50, 5, 1) # 这一层的in_channels正好是上一层的out_channels self.fc1 = nn.Linear(4*4*50, 500) self.fc2 = nn.Linear(500, 10) def forward(self, x): x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2, 2) # kernel_size=2, stride=2,pooling之后的大小除以2 x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2, 2) x = x.view(-1, 4*4*50) # 展开成 (z, 4*4*50),其中z是通过自动推导得到的,所以这里设置为-1,这里相当于展开成行向量,便于后续全连接 x = F.relu(self.fc1(x)) x = self.fc2(x) return F.log_softmax(x, dim=1) # log_softmax 即 log(softmax(x));dim=1对行进行softmax,因为上面x.view展开成行向量了,log_softmax速度和数值稳定性都比softmax好一些 def train(args, model, device, train_loader, optimizer, epoch): model.train() # 告诉pytorch,这是训练阶段 https://stackoverflow.com/a/51433411/2468587 for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() # 每个batch的梯度重新累加 output = model(data) loss = F.nll_loss(output, target) # 这里的nll_loss就是Michael Nielsen在ch3提到的log-likelihood cost function,配合softmax使用,batch的梯度/loss要求均值mean loss.backward() # 求loss对参数的梯度dw optimizer.step() # 梯度下降,w'=w-η*dw if batch_idx % args.log_interval == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item())) def test(args, model, device, test_loader): model.eval() # 告诉pytorch,这是预测(评价)阶段 test_loss = 0 correct = 0 with torch.no_grad(): # 预测时不需要误差反传,https://discuss.pytorch.org/t/model-eval-vs-with-torch-no-grad/19615/2 for data, target in test_loader: data, target = data.to(device), target.to(device) output = model(data) test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss,预测时的loss求sum,L54再求均值 pred = output.argmax(dim=1, keepdim=True) # get the index of the max log-probability correct += pred.eq(target.view_as(pred)).sum().item() test_loss /= len(test_loader.dataset) print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format( test_loss, correct, len(test_loader.dataset), 100. * correct / len(test_loader.dataset))) def plot1digit(data_loader): import numpy as np import matplotlib.pyplot as plt examples = enumerate(data_loader) batch_idx, (Xs, ys) = next(examples) # 读取到的是一个batch的所有数据 X=Xs[0].numpy()[0] # Xs[0]取出batch中的第一个数据,由tensor转换为numpy,因为pytorch tensor的格式是[channel, height, width],所以最后[0]取出其第一个通道的[h,w] y=ys[0].numpy() # y没有通道,就一个标量值 np.savetxt('../../../fig/%d.csv'%y, X, delimiter=',') plt.imshow(X, cmap='Greys') # or 'Greys_r' plt.savefig('../../../fig/%d.png'%y) plt.show() def main(): # Training settings parser = argparse.ArgumentParser(description='PyTorch MNIST Example') parser.add_argument('--batch-size', type=int, default=64, metavar='N', help='input batch size for training (default: 64)') parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N', help='input batch size for testing (default: 1000)') parser.add_argument('--epochs', type=int, default=10, metavar='N', help='number of epochs to train (default: 10)') parser.add_argument('--lr', type=float, default=0.01, metavar='LR', help='learning rate (default: 0.01)') parser.add_argument('--momentum', type=float, default=0.5, metavar='M', help='SGD momentum (default: 0.5)') parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument('--seed', type=int, default=1, metavar='S', help='random seed (default: 1)') parser.add_argument('--log-interval', type=int, default=10, metavar='N', help='how many batches to wait before logging training status') parser.add_argument('--save-model', action='store_true', default=False, help='For Saving the current Model') args = parser.parse_args() use_cuda = not args.no_cuda and torch.cuda.is_available() torch.manual_seed(args.seed) device = torch.device("cuda" if use_cuda else "cpu") kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {} train_loader = torch.utils.data.DataLoader( datasets.MNIST('../data', train=True, download=True, transform=transforms.Compose([ # https://discuss.pytorch.org/t/can-some-please-explain-how-the-transforms-work-and-why-normalize-the-data/2461/3 transforms.ToTensor(), # 把[0,255]的(H,W,C)的图片转换为[0,1]的(channel,height,width)的图片 transforms.Normalize((0.1307,), (0.3081,)) # 进行z-score标准化,这两个数分别是MNIST的均值和标准差 ])), batch_size=args.batch_size, shuffle=True, **kwargs) test_loader = torch.utils.data.DataLoader( datasets.MNIST('../data', train=False, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])), batch_size=args.test_batch_size, shuffle=True, **kwargs) # plot1digit(train_loader) model = Net().to(device) optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) for epoch in range(1, args.epochs + 1): train(args, model, device, train_loader, optimizer, epoch) test(args, model, device, test_loader) if (args.save_model): torch.save(model.state_dict(),"mnist_cnn.pt") if __name__ == '__main__': main() 首先是MNIST数据格式的问题,在L108~L120,我们使用Pytorch的DataLoader载入了训练和测试数据,数据格式本质上和本系列博客的第一篇博客介绍的是一致的,即每张图片都是28*28的灰度图片,因为是灰度图片,所以只有一个通道数,默认格式是(H,W,C),且值域范围是[0,255]。但上述代码对原始图片进行了两个变换,分别是ToTensor和Normalize。ToTensor将[0,255]的灰度图片(H,W,C)转换为[0,1]的灰度图片(C,H,W),即Pytorch对2D图片的格式要求都是channel在前。所以经过这一转换,一张图片的shape是(1,28,28),是一个三维矩阵;如果是彩色图片的话,有R,G,B三个通道,C=3。Normalize对图片数据进行z-score标准化,即减去均值再除以标准差;L112的两个值就是预先计算的MNIST数据集的均值和标准差。这些操作的好处是能让模型更加平稳快速收敛。同第一篇博客一样,我们可以把Pytorch格式的图片打印出来以便直观理解,L61的plot1digit函数就是这个作用。 ...

May 19, 2019 · 3 min

《三体》始末

简化版 叶文洁向宇宙发射了一个信号 三体人接收到了这个信号 三体人计划逃离水深火热的三体星系,殖民太阳系 地球人在保卫太阳系的末日之战中,被三体舰队团灭,太阳系岌岌可危 罗辑参透了黑暗森林法则,并假借雪地工程实现了对三体星系的威慑,三体撤军,太阳系幸存 罗辑年老体衰,程心接替罗辑成为新的执剑人 三体人预料到程心心慈手软,不敢实施黑暗森林打击 三体人果断进军太阳系,程心果然没有实施黑暗森林打击,地球沦为三体人的殖民地 在太空执行任务的地球飞船发射了三体坐标,三体再次撤军,并随后遭到黑暗森林打击,三体星系灭亡 发射三体坐标也暴露了太阳系坐标,太阳系遭到更高级的打击——降维打击,太阳系被二维化 程心借助光速飞船逃离太阳系来到了云天明送给她的类地行星蓝星上 程心又进入了云天明送给她的平行小宇宙,企图躲避大宇宙的归零大爆炸 太多的小宇宙导致大宇宙质量流失,无法归零 大宇宙向所有宇宙发布回归运动声明,请求小宇宙归还质量 程心最终归还质量,回到了大宇宙 大宇宙坍缩成奇点,完成大爆炸,宇宙开始了新的轮回 扩充版 《三体I·地球往事》 叶文洁经历了父亲在文革中被迫害致死、在大兴安岭被出卖等种种事件之后,对人类的恶彻底绝望了,她决定在红岸基地度过一生。在红岸基地,她意外发现可以利用太阳作为放大器把信号发往其他星球,于是她向宇宙发出了地球的第一个信号,希望外星文明来拯救罪恶的人类。隔壁的三体文明正处于水生火热之中,收到地球信号后,决定冲出三体星球,占领地球。三体人首先派出了两个质子(智子),封锁地球的基础研究,然后派出大型三体舰队进军地球。在地球上,分两个大阵营:一个是以叶文洁为领袖的地球三体组织,可以认为是地球的叛军;另一个是各国组织的政府军,准备消灭地球叛军并对战三体人。地球三体组织又分为三个派系,降临派、拯救派和幸存派。政府军能打败地球叛军并阻止三体人的进攻吗,请看下回分解。 《三体II·黑暗森林》 地球人为了抵抗三体舰队的入侵,利用三体人思维透明的弱点,选定了四个人开展面壁者计划,其中三人相继失败。200年后,三体星球派来的水滴团灭了地球舰队的舰队方阵,足足有两千多艘几个足球场大的战舰,在一个小时内团灭。侥幸逃离的几艘战舰之间为了维持自身的生存,开始自相残杀,地球文明面临灭顶之灾。罗辑,唯一没有被识破的面壁人,参透了黑暗森林法则: 宇宙就是一座黑暗森林,每个文明都是带枪的猎人,像幽灵般潜行于林间,轻轻拨开挡路的树枝,竭力不让脚步发出一点儿声音,连呼吸都小心翼翼……他必须小心,因为林中到处都有与他一样潜行的猎人。如果他发现了别的生命,不管是不是猎人,不管是天使还是魔鬼,不管是娇嫩的婴儿还是步履蹒跚的老人,也不管是天仙般的少女还是天神般的男孩,能做的只有一件事:开枪消灭之。在这片森林中,他人就是地狱,就是永恒的威胁,任何暴露自己存在的生命都将很快被消灭。这就是宇宙文明的图景,这就是对费米悖论的解释。” 罗辑假借雪地工程,制造了一个和三体文明同归于尽的方案,即在太阳周围精心安排一层油膜,使得从宇宙其他文明的视角来看,透过油膜的点点亮光,表示三体星系的坐标。只要这个坐标发射,三体文明就会遭到黑暗森林打击。罗辑通过雪地工程,使地球文明第一次获得了和三体文明谈判的资格,在此之前,地球文明卑微如一只蚂蚁。罗辑成功了,三体文明接受了罗辑的谈判条件,地球文明幸存了下来,并且三体智子解除了对地球的科技封锁。接下来地球文明和三体文明又会发生怎样惊心动魄的故事呢,请听下回分解。 《三体III·死神永生》 《三体II》之后,罗辑拯救了地球文明,地球和三体处在互相制衡的状态,地球处于威慑纪元。由于罗辑掌握发射三体坐标的开关,决定着两个文明的生死存亡,罗辑被称为执剑人。渐渐的,罗辑老了,需要新人接替罗辑成为执剑人,程心最终竞选成功,成为新的执剑人。在这期间三体文明和地球文明交流密切,关系融洽,似乎一切都那么的平静和美好。 突然,意想不到的事情发生了,三体派出大批舰队进攻地球,而此时的执剑人程心却没能说服自己发射三体坐标(因为如果发射三体坐标,也会同时暴露地球坐标,导致地球遭受打击,作为圣母心的程心自然是受不了的)。就这样,地球沦陷,三体舰队全面占领地球,把地球人圈养在澳大利亚。 就在地球文明生死存亡之际,在外太空执行任务的“万有引力”号飞船广播了三体坐标,三体文明自知死路一条,撤离地球,地球再一次得救,处于广播纪元。不久,三体遭受黑暗森林打击,三体文明毁灭。广播三体坐标也暴露了地球的坐标,所以地球人开始探索拯救地球免于黑暗森林打击的方案。 三体文明虽然被毁灭,但由于文明发达,仍有三体人得以逃往外太空。在三体智子和地球告别之际,智子安排程心和云天明会面,云天明是程心的大学同学,暗恋程心,买下一颗遥远的恒星并送给程心,程心却在不知情的情况下把云天明的大脑发射到三体人手中。云天明被三体人复活,并被安排和程心会面,在和程心会面过程中,云天明给程心讲了三个故事,通过多重隐喻的方式传达了拯救地球的方案。 地球人通过对三个故事的研究,总结出拯救地球的三个方案: 安全声明,降低太阳系的光速,使太阳系变成一个低光速黑域,地球人把自己锁死在太阳系,永远也无法逃出。通过这种方案,让地外文明觉得太阳系不是威胁,打消进攻的念头。 超光速飞船,制造超光速飞船,飞离被暴露的太阳系,寻找新的家园。 掩体计划,将地球人迁移到类木行星的背阳面,由于类木行星距离太阳较远,当黑暗森林打击到来时,用类木行星作为盾牌,抵挡太阳爆炸发射的冲击波。 经过不断的争论和调整,地球人最终选定掩体计划,因为安全声明方案需要降低光速,难度太大,而超光速飞船即使研制出来,肯定只能让少数人逃生,由此会引发普通阶层的不满,导致地球内乱。于是,地球进入掩体纪元。 随着掩体计划的实施,地球人陆续搬迁到类木行星背阳面的太空近地轨道居住,地球人又过上了幸福的生活。可好景不长,太阳系的坐标终究是暴露了,被高级得多的歌者文明发现,他们自然知道使用常规的黑暗森林打击无法消灭躲在类木行星后面的地球人,于是他们启用了更高级的武器——降维打击!他们向太阳系发射了一张小纸条,不久这张小纸条扩大成一张二维平面,这张二维平面就像一个超级黑洞一样,把周围的三维物体吸到它的平面上,压扁,变成一张静态的二维图片。就这样,太阳系的行星包括太阳本身不断被吸到这个二维平面,坍缩成一张死去了的二维图片。要想逃躲被二维化的命运,必须以超光速飞离太阳系,但是之前的超光速飞船计划已经被明令禁止了。通常被公开禁止的东西,都有人在私底下偷偷流通,超光速飞船也不例外。程心的公司,因为各种原因,私底下偷偷研制成功了超光速的曲率驱动飞船。于是,程心和她的助理艾AA乘坐超光速飞船逃离了太阳系,来到了云天明送给她的那颗恒星的一个类地行星蓝星上,程心等人进入了银河纪元。 没想到,蓝星上有人!是之前逃离太阳系的万有引力号上的成员关一帆。在蓝星上,关一帆检测到旁边的行星灰星有飞船迹象,以为是云天明,于是和程心乘坐飞船前往灰星,艾AA就留在了蓝星。在前往灰星的路上,关一帆告诉程心,太阳系向二维平面的跌落会永远进行下去,直到整个宇宙都跌入到二维。实际上,宇宙原本是十维空间,但是由于星际战争,不断有文明使用降维打击,慢慢的,宇宙的维度就被打成了三维,现在又将被打成二维。当宇宙被星际战争打成零维之后,宇宙重启,就像把时针拨过12点一样。比起降维打击,之前人类参透的黑暗森林打击不值一提,在星际战争中,黑暗森林打击就像狙击手之间的阵地战,对于整个战争来说是件小事,而最有威力的武器是利用宇宙规律,比如降低维度用来攻击,降低光速用来防御,真是太可怕了。 关一帆和程心来到灰星之后,发现了曲率驱动飞船留下的尾迹——死线,这五根死线非常粗非常黑,只有很高级的飞船才能产生如此粗和黑的死线,关一帆猜测是归零者的飞船留下来的,归零者是一群智慧个体,想重启宇宙回到田园时代。这些死线(很粗的圆柱体)是绝对的光速为零的黑域,任何东西只要进去了,就逃不出来,必死无疑。这些死线还有一个特点是如果周围有其他曲率驱动飞船,则产生的死线会和已有的死线发生干扰,使得黑域扩散。 所以非常不巧的是,归零者来到了灰星,而云天明来到了蓝星,而程心他们却去了灰星。更可怕的是,云天明的曲率驱动飞船产生的尾迹和归零者的死线产生了干扰,导致黑域扩散,关一帆和程心的飞船跌入黑域,光速变慢。在黑域里,电子计算机和量子计算机失效,关一帆启动了神经元计算机,同时,由于氧气不足,他们两进入了冬眠。经过几天的航行,他们的飞船终于回到了蓝星,但因为他们的光速变慢了,所以他们的几天,对于处在蓝星上的艾AA和云天明来说已经是几千万年之后了。关一帆和程心在蓝星上找到了艾AA和云天明留给他们的礼物,一扇门,一扇通往另一个平行小宇宙的门,当然,这个小宇宙也是云天明送给他们的。关一帆和程心来到了这个小宇宙,很巧的是,智子也在这个小宇宙里,作为该小宇宙的管家。智子告诉两位,这个小宇宙是时间之外的宇宙,和之前的宇宙是平行的,能躲过之前大宇宙的坍缩。当大宇宙坍缩到奇点然后大爆炸形成新的大宇宙之后,他们就可以从这个小宇宙回到新的大宇宙,开始新的田园生活了。 原本以为关一帆和程心会在小宇宙中幸福的生活下去,没想到,他们突然收到了大宇宙的超膜广播,用一百多万种语言写成的广播,广播内容是回归运动声明: 回归运动声明:我们宇宙的总质量减少至临界值以下,宇宙将由封闭转变为开放,宇宙将在永恒的膨胀中死去,所有的生命和记忆都将死去。请归还你们拿走的质量,只把记忆体送往新宇宙。 即有太多的文明发现了可以制造小宇宙来躲避大宇宙的坍缩,导致大宇宙的质量减小到临界值而无法完成归零的大爆炸,大宇宙将由封闭转变为开放,在永恒的膨胀中死去。该声明请求所有小宇宙归还他们拿走的质量,以完成大宇宙的归零。 在经历了几百年的星际战争,在亲眼目睹了太阳系母亲的坍缩和宇宙的黑暗之后,程心和关一帆内心平静,他们决定响应回归运动,将小宇宙的所有质量,包括天、地、太阳、飞船等等一切质量,都拆卸下来归还给了大宇宙。最后,关一帆、程心和智子,手拉手,离开了小宇宙,进入了大宇宙,开始了宇宙新一轮轮回。死神永生! 读后感:佩服大刘巨大的脑洞!全书看完,完全不觉得是科幻小说,所有物理、生物、计算机的知识,运用得天衣无缝,毫无破绽,觉得这就是地球、太阳系、宇宙的未来。科幻作家首先要是一名合格的作家,本文的文学性毫不弱于其科幻性,我贫乏的语言已经不足以表达这部作品的伟大了。《三体》系列完全可以拍成一部不输于冰与火之歌的史诗巨作!推荐看完全书的同学去B站看文曰小强的速读视频,这个up主也是厉害,如此硬核的小说,用84分钟就讲完了。如果没看过原书就不推荐看了,因为小说本身的信息密度就很高,再经过小强加工压缩到84分钟,信息密度就更高了,很可能会看得一头雾水。总之,膜拜大刘,一举把中国的科幻水平提高到世界水准。

May 18, 2019 · 1 min

Neural Networks and Deep Learning(六)深度学习

今天我们终于进入到了本书的重头戏——深度学习。其实,这一章的深度学习主要介绍的是卷积神经网络,即CNN。 本书之前的章节介绍的都是如下图的全连接网络,虽然全连接网络已经能够在MNIST数据集上取得98%以上的测试准确率,但有两个比较大的缺点:1. 训练参数太多,容易过拟合;2. 难以捕捉图片的局部信息。第一点很好理解,参数一多,网络就难以训练,难以加深。对于第二点,因为全连接的每个神经元都和上一层的所有神经元相连,无论距离远近,也就是说网络不会捕捉图片的局部信息和空间结构信息。 本章要介绍的卷积神经网络,相对于全连接网络,有如下三个特点:1. 局部感知local receptive fields 2. 权值共享shared weights 3. 池化pooling,下面分别介绍这三部分内容。 局部感知 对于MNIST的一张28*28灰度图片,全连接网络的输入把图片展开成一个维度为784的向量,这就天然丢失了图片的空间结构信息。而CNN的输入保持了图片28*28的二维空间结构信息,相应的,CNN的中间层也是二维的。这就涉及到输入层的二维图片和隐藏层的二维图片如何对应的问题。 CNN使用一个被称为“卷积核”的东西,把输入图片转换为隐藏层的特征图(feature map),如下图所示,假设卷积核大小为5*5,则输入图片每5*5的一个小区域被转换为隐藏层的一个神经元(像素),这个小区域就称为局部感受野。 当卷积核不断的在输入图片中移动时,假设每次移动一格(stride=1),则原来28*28的图片,经过一次卷积后,得到的feature map大小为24*24,相比输入图片小了一圈。 权值共享 那么,这个卷积操作具体是怎样执行的呢,非常简单。5*5的卷积核本质是一个5*5的矩阵,矩阵中的每个值相当于这个卷积核的参数,或者说权值w。每次卷积时,5*5的矩阵和输入图片中5*5的感受野对应位相乘再相加得到隐藏层的一个值。 下图是一个缩小版的动图例子,左图的绿色大图相当于输入的5*5图片,移动的黄色小图相当于当前卷积的感受野,大小为3*3。在这个3*3的感受野中,每个单元格居中的数字是输入图片的像素值,右下角的红色小字表示卷积核的权值。每次卷积操作,感受野内的图片像素和卷积核权值相乘再相加,得到右图红色小图中的一个单元格的值,这就完成了一次卷积。当黄色感受野不断在输入图片中移动时,右边的feature map也不断被填充,直到一轮卷积完成。整个过程进行了9次卷积,feature map的大小为3*3=9卷积次数。 https://hackernoon.com/visualizing-parts-of-convolutional-neural-networks-using-keras-and-cats-5cc01b214e59 这里又涉及到CNN的第二个特点——权值共享。注意到,对于上图的一轮卷积操作,不同感受野内右下角的权值矩阵是一样的,也就是说9次卷积的卷积核权值是一样的。权值共享有两个好处,一是特征位置无关,二是参数量大大下降。 对于特征位置无关 。这个3*3的卷积核相当于一个特征提取器或者说滤波器,比如这个特征提取器能够提取“猫”这个特征,则无论猫在输入图片的左上角还是右下角,“猫”这个特征都能被提取出来,因为卷积核在小范围移动,无论“猫”位于图片的哪个区域,当卷积核移动到这个区域时,卷积得到的输出比较大,被激活,得到“猫”这个特征。所以CNN对位置不敏感,这对图像处理尤其有利。正因为这个特点,经过卷积核卷积操作之后的小图片(上图右边的红色图片)被称为特征图(feature map),因为它就是用卷积核提取出来的符合这个卷积核描述的一个特征。 对于参数量大大下降。事实上,一次卷积操作除了上面动图显示的卷积核与感受野内的图片相乘再相加之外,还会对加和之后的值做一个激活输出。回到我们的MNIST例子,一次卷积操作用公式来表示就是: $$\begin{eqnarray}\sigma\left(b + \sum_{l=0}^4 \sum_{m=0}^4 w_{l,m} a_{j+l, k+m} \right).\tag{1}\end{eqnarray}$$\(w\)表示卷积核权值矩阵,\(a\)表示感受野内的输入图片,两个累加\(\sum\)就是上面动图显示的相乘相加过程,得到和之后,还会加上一个偏移量\(b\),最后进行激活输出\(\sigma\)。所以一个5*5的卷积核,参数量为5*5+1=26。如果有20个卷积核,参数总量为20*26=520。但如果是全连接网络,假设隐藏层有30个,则参数量为784*30+30=23550。所以仅考虑隐藏层的参数量,CNN就比全连接网络少了45倍的参数,参数量少了,就能加快训练,网络也有可能加深。 池化 池化就很好理解了,对于卷积得到的feature map,再画一个框(类似于卷积层的感受野),把框内的最大值取出来作为池化之后的值,这就是max-pooling。池化的目的是用来简化信息的,相当于降维。池化的框也可以称为核kernel,如果kernel的大小是2*2的,则一个24*24的feature map,经过max-pooling之后就变成了12*12了,维度瞬间降了一半, 把原来的feature map变成了一个紧凑的feature map。 池化层往往跟在卷积层的后面,下图表示一张28*28的图片,使用3个5*5的卷积核之后,得到了3个24*24的feature map,再经过2*2的max-pooling,得到3个12*12的feature map。 到这里,CNN的三大特点就介绍完毕了。对于上图,三个卷积核相当于提取了三种特征,我们还需要完成最终的分类任务,这时候还得把全连接网络请过来。经过max-pooling之后,我们再接一个包含10个神经元的全连接层,作为输出层,完整的网络结果如下: 最后的全连接层和我们前面介绍的全连接网络是完全一样的,只不过全连接的输入是3个经过max-pooling之后的feature map,再和输出层相连时,可以想象成先把3个12*12的feature map展开并首尾相连,得到一个3*12*12=432的向量,再和输出层的10个神经元进行全连接。这就是一个非常简单的CNN网络,包含一个输入层、一个卷积层、一个池化层和一个输出层。 本文的代码示例network3.py中,构建了一个和上图类似的简单的CNN网络,如下图所示,使用了20个卷积核,相当于提取了20种特征;max-pooling之后使用了两个全连接层,前一层包含100个隐藏神经元,使用sigmoid激活;后一层包含10个神经元,使用softmax激活,作为输出层。就是这么一个简单的CNN网络,其在测试集上的准确率达到了98.78%,超过了本文之前构建的所有的全连接网络。 由于原文使用的是已经不再维护的Theano,本博客不打算详细介绍其代码实现,我将在稍后的博文中分享Pytorch的CNN代码。不过我还是把原文对CNN的优化过程总结如下,用测试集的准确率作为性能指标: 上图简单的CNN网络,98.78% 增加一个卷积层,且把激活函数换成ReLU,99.23% 数据增强,把原有的5000张图片,上下左右各平移一个像素,增加了4倍数据,99.37% 增加一个全连接层,且全连接层神经元增加为1000个,使用dropout=0.5,epoch相应减少到40个,99.6%。因为卷积层有权值共享,天然参数少防止过拟合,所以dropout一般只用于全连接层 模型融合ensemble,5个上述模型,采用majority vote,99.67%,已接近人类水平 虽然经过上述5步,准确率没有达到100%,但那些分类错误的图片,真的很难说分错了,因为图片看起来就不是它标注的结果(右上角),就应该是分错的结果(右下角)。总的来说,我觉得已经非常不错了。 稍微解释两个问题。 ...

May 4, 2019 · 1 min

Neural Networks and Deep Learning(五)为什么深度神经网络难以训练

本章我们将分析一下为什么深度神经网络难以训练的问题。 首先来看问题:如果神经网络的层次不断加深,则在BP误差反向传播的过程中,网络前几层的梯度更新会非常慢,导致前几层的权重无法学习到比较好的值,这就是梯度消失问题(The vanishing gradient problem)。 以我们在第三章学习的network2.py为例(交叉熵损失函数+Sigmoid激活函数),我们可以计算每个神经元中误差对偏移量\(b\)的偏导\(\partial C/ \partial b\),根据第二章BP网络的知识,\(\partial C/ \partial b\)也是\(\partial C/ \partial w\)的一部分(BP3和BP4的关系),所以如果\(\partial C/ \partial b\)的绝对值大,则说明梯度大,在误差反向传播的时候,\(b\)和\(w\)更新就快。 假设network2的网络结构是[784,30,30,10],即有两个隐藏层,则我们可以画出在误差反向传播过程中,隐藏层每个神经元的\(\partial C/ \partial b\)的大小,用柱子长度表示。由下图可知,我们发现第二个隐藏层的梯度普遍大于第一个隐藏层的梯度,这会是一般现象吗,还是偶然现象? 既然梯度出现了层与层的差异,则可以定义第\(l\)层的梯度(如不加说明,则默认是误差\(C\)对偏移量\(b\)的梯度)向量的长度为\(\| \delta^l \|\),比如\(\| \delta^1 \|\)表示第一个隐藏层中每个神经元的\(\partial C/ \partial b\)的绝对值之和,就是一范数,如果\(\| \delta^l \|\)越大,则说明这一层权重的更新越快。 由此,我们可以画出当有两个隐藏层时,\(\| \delta^l \|\)随epoch的变化情况: 当有三个隐藏层时: 当有四个隐藏层时: 我们发现,规律是惊人的一致,即越靠近输出层的隐藏层,\(\| \delta^l \|\)越大,即梯度更新越快;越靠近输入层的隐藏层,\(\| \delta^l \|\)越小,即梯度更新越慢。 这就会导致梯度消失的问题(The vanishing gradient problem):即在误差反向传播过程中,刚开始权重更新比较快,越到后面(越靠近输入层),则权重更新变得很慢,无法搜索到比较优的值。 所以,对于同样的network2,其他参数都不变,只是单纯增加网络层数,验证集上的准确率反而会下降!按理说网络层数增加,验证集上的准确率会上升,或者不变,至少不应该下降啊,因为最不济增加的网络层什么都不做,准确率应该一样才对,为什么反而下降了呢。虽然层数增加了,但因为上述梯度消失问题,靠近输入层的权重反而没学好,因为权重是随机初始化的,所以验证集上的准确率反而下降了。 那么,为什么层数增加会导致梯度消失问题呢,我们可以从BP的更新公式中一探究竟。 为了简化问题,假设我们的网络每一层只有一个神经元: 则根据BP的更新公式,可以计算得到 $$\begin{eqnarray}\frac{\partial C}{\partial b_1} = \sigma'(z_1) \, w_2 \sigma'(z_2) \,w_3 \sigma'(z_3) \, w_4 \sigma'(z_4) \, \frac{\partial C}{\partial a_4}.\tag{1}\end{eqnarray}$$计算过程其实很简单,对照本博客开头的那张图,\(\sigma'(z_4) \, \frac{\partial C}{\partial a_4}\)就是(BP1),把(BP1)带入(BP2),就是不断乘以\(w^{l+1} \sigma'(z^l)\),然后就能得到下图的公式。 ...

April 14, 2019 · 1 min

Neural Networks and Deep Learning(四)图解神经网络为什么能拟合任意函数

我们应该都听说过神经网络强大到能拟合任意一个函数,但细究起来很少有人能论证这个观点,这一章就用通俗易懂的图解方式来证明神经网络为什么能拟合任意一个函数。 开始介绍之前,有两点需要注意: 并不是说神经网络可以精确计算任意一个函数\(f(x)\),而是说当隐藏层神经元增加时,可以无限逼近\(f(x)\),比如对于任何一个输入\(x\),网络的输出\(g(x)\)和正确值\(f(x)\)的差小于某个阈值,\(|g(x) – f(x)| < \epsilon\); 神经网络拟合的是连续函数,而不是那种不连续、离散、急剧变化的函数。 假设给定一个下图的连续函数,函数形式未知,本章将用图解的方式来证明,一个单隐层的神经网络就可以很好的拟合这个未知函数。 首先,假设我们的隐藏层只有两个神经元,激活函数使用Sigmoid,并且我们暂时只关注上面那个神经元的参数和输出。则通过调整该神经元的\(w\)和\(b\),可以得到不同形状的Sigmoid函数形式。 极端情况下,如果\(w\)很大而\(b\)很小,则可以用Sigmoid函数模拟阶梯函数: 如果令\(s = -b/w\),则只用一个\(s\)就可以确定Sigmoid的函数图像: 如果把隐藏层下面那个神经元也考虑进来,并且令隐藏层的两个神经元和输出层的神经元的连接权重互为相反数,则输出层未激活值\(z=w_1 a_1 + w_2 a_2\)的函数图像变成了一个神奇的鼓包,这个鼓包就是我们后续拟合任意函数的基本单元。根据严格的函数形式,还可以知道\(w_1\)和\(w_2\)的绝对值控制着鼓包的高度,\(s_1\)和\(s_2\)的值控制着鼓包的位置和宽度。大家可以去原始网页上体验一下作者给出的可交互版本,很有意思。 有了这个基本单元之后,我们可以通过增加隐藏层神经元的个数来增加鼓包的个数,比如再增加一对隐层神经元,可增加一个鼓包。虽然下图的例子中两个鼓包相互独立,但通过调整4个\(s\),可以让两个鼓包相连甚至交错,大家可以去原网页试一试。 继续增加隐层神经元个数,则可以继续增加鼓包的数量,如下图所示。 到这里想必大家马上知道了为什么神经网络能拟合任何一个函数了,如果隐层神经元足够多,则右图的小鼓包可以足够密,通过调整每个鼓包的高度,则无穷多个鼓包的顶点连线可以拟合任意一个函数。这和我们求函数积分(函数下方面积)时使用多个小矩形近似是一个道理! 所以对于本章开头的未知函数,我们通过调整不同鼓包的高度,可以使得小矩形面积之和与真实积分的差在\( \epsilon=0.4\)以内。如果无限增加隐层神经元个数,则可以无限逼近真实值。这就说明神经网络确实可以拟合任意一个函数。 上述推导稍微需要注意的一点是,右图的输出是未激活函数值\(\sum_j w_j a_j\),而网络真正的输出是激活值\(\sigma(\sum_j w_j a_j + b)\)。这没有太大的关系,因为上面已经说明未激活输出能拟合任意函数,激活函数也是一个函数。增加激活函数就要求右图需要拟合激活函数和真实函数的嵌套函数。既然未激活输出能拟合任意函数,肯定能拟合这个嵌套函数\(\sigma^{-1} \circ f(x)\),再用激活函数作用一下\(\sigma\circ\sigma^{-1} \circ f(x)\),激活函数抵消了,正好得到\(f(x)\)。 如果输入是多维,或者输出是多维,都是类似的道理。这就说明神经网络确实可以拟合任意函数,真的很强大哦。

April 7, 2019 · 1 min