-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdb.json
1 lines (1 loc) · 688 KB
/
db.json
1
{"meta":{"version":1,"warehouse":"4.0.0"},"models":{"Asset":[{"_id":"source/images/GraphSAGE.png","path":"images/GraphSAGE.png","modified":1,"renderable":0},{"_id":"source/images/SDNE1.png","path":"images/SDNE1.png","modified":1,"renderable":0},{"_id":"source/images/SDNE2.png","path":"images/SDNE2.png","modified":1,"renderable":0},{"_id":"source/images/deepwalk.png","path":"images/deepwalk.png","modified":1,"renderable":0},{"_id":"source/images/embed1.jpg","path":"images/embed1.jpg","modified":1,"renderable":0},{"_id":"source/images/embed2.png","path":"images/embed2.png","modified":1,"renderable":0},{"_id":"source/images/embed3.png","path":"images/embed3.png","modified":1,"renderable":0},{"_id":"source/images/embed4.png","path":"images/embed4.png","modified":1,"renderable":0},{"_id":"source/images/embed5.png","path":"images/embed5.png","modified":1,"renderable":0},{"_id":"source/images/embed6.png","path":"images/embed6.png","modified":1,"renderable":0},{"_id":"source/images/shang.webp","path":"images/shang.webp","modified":1,"renderable":0},{"_id":"themes/butterfly/source/css/index.styl","path":"css/index.styl","modified":1,"renderable":1},{"_id":"themes/butterfly/source/css/var.styl","path":"css/var.styl","modified":1,"renderable":1},{"_id":"themes/butterfly/source/js/main.js","path":"js/main.js","modified":1,"renderable":1},{"_id":"themes/butterfly/source/js/tw_cn.js","path":"js/tw_cn.js","modified":1,"renderable":1},{"_id":"themes/butterfly/source/js/utils.js","path":"js/utils.js","modified":1,"renderable":1},{"_id":"themes/butterfly/source/img/404.jpg","path":"img/404.jpg","modified":1,"renderable":1},{"_id":"themes/butterfly/source/img/algolia.svg","path":"img/algolia.svg","modified":1,"renderable":1},{"_id":"themes/butterfly/source/img/favicon.png","path":"img/favicon.png","modified":1,"renderable":1},{"_id":"themes/butterfly/source/img/friend_404.gif","path":"img/friend_404.gif","modified":1,"renderable":1},{"_id":"themes/butterfly/source/img/loading.gif","path":"img/loading.gif","modified":1,"renderable":1},{"_id":"themes/butterfly/source/img/头像.jpg","path":"img/头像.jpg","modified":1,"renderable":1},{"_id":"themes/butterfly/source/js/search/algolia.js","path":"js/search/algolia.js","modified":1,"renderable":1},{"_id":"themes/butterfly/source/js/search/local-search.js","path":"js/search/local-search.js","modified":1,"renderable":1}],"Cache":[{"_id":"source/categories/index.md","hash":"4508c2477bd1ae13f929241563d1d55fd798bfad","modified":1631954625916},{"_id":"source/.DS_Store","hash":"3023257260224ae30ec34ec9e109e4736341f0ac","modified":1643093832591},{"_id":"source/messageboard/index.md","hash":"a52020b9bbaf578c858637600e848c9d21d80396","modified":1631954625918},{"_id":"source/images/.DS_Store","hash":"eaa9c3be1632f31298c69ea4e08d4df44075e3de","modified":1643093797295},{"_id":"source/images/SDNE2.png","hash":"417af1bc2df4a0c28cf01bea8bd311291f645505","modified":1631954625917},{"_id":"source/images/deepwalk.png","hash":"d63e64f85d995087231bb96050e75b41429ea3fe","modified":1631954625918},{"_id":"source/images/embed1.jpg","hash":"907ce005e857541e564adb52d5001587670ced59","modified":1641370155305},{"_id":"source/images/embed2.png","hash":"9e137a5771f3a5b19f40f7ba2b9f26533cd70092","modified":1641374935727},{"_id":"source/images/embed3.png","hash":"38fb9aa02edea13ec257f9318ac8fa70aa5c7300","modified":1641372160535},{"_id":"source/images/embed4.png","hash":"3cdab14b6c9ba2f6acaaea0ff0abb9a94e0f82eb","modified":1641375015341},{"_id":"source/images/embed6.png","hash":"4f1572748d351e904712e94201088afe157006dd","modified":1641798532241},{"_id":"source/images/embed5.png","hash":"c2fc93ef56fea1ce4dd5628c38884fef0ca4ed46","modified":1641375029125},{"_id":"source/images/shang.webp","hash":"23d01ec7ef1439d63974b27755233a6aa440e2e6","modified":1631954625918},{"_id":"source/_posts/.DS_Store","hash":"5cb3e32013f867ef0c0d902cf46deff1e8989692","modified":1633663637515},{"_id":"source/_posts/GNN系列之-GraphSAGE.md","hash":"4e83bf2692fe0b3298ea96d1213a2e7ea0d9b84d","modified":1631954625913},{"_id":"source/_posts/GNN系列之-GCN.md","hash":"700123b3bd7240588fdc5fb63bcb474d611afa9e","modified":1631954625913},{"_id":"source/_posts/Graph-embedding之SDNE.md","hash":"07b3ed545d8b313167a50db639faf95eeedf44ba","modified":1631954625913},{"_id":"source/_posts/Graph-embedding之deepwalk.md","hash":"a68e00b8774bce1dc291061796629f0b560bcd5e","modified":1631954625913},{"_id":"source/_posts/Louvain算法.md","hash":"926176cc99fff981cecb351c30da78dd86fe37ea","modified":1631954625913},{"_id":"source/_posts/万物皆可embedding.md","hash":"e1f9651bc343e9f7016d8960d5fffc2ad59137b4","modified":1643093917723},{"_id":"source/_posts/NLP预处理:词干提取和词性还原.md","hash":"d80d0c59dc88b987ff1af9824b4e018f055f749c","modified":1631954625914},{"_id":"source/_posts/初识NLP.md","hash":"5c69587232071280b009c192944b0df99d541a1f","modified":1631954625914},{"_id":"source/_posts/关联挖掘.md","hash":"eed7a90dadcd3c46eaf1653dd14d3cf861478d37","modified":1631955035749},{"_id":"source/_posts/初识Transomer.md","hash":"fb8b262bca41db099a9f6a3b781a0c7c11aee2c8","modified":1631954625914},{"_id":"source/_posts/多进程、多线程(python).md","hash":"cdf19158662e43eb539fb2083424d28a6e329362","modified":1631954625914},{"_id":"source/_posts/异常检测.md","hash":"59c226196b9285406598f58c9e6e3dea47355a49","modified":1631954625915},{"_id":"source/_posts/熵.md","hash":"3a995c56e71ac9b88d4b221d63f5c30afb602f27","modified":1631954625915},{"_id":"source/_posts/数据标准化和中心化.md","hash":"426e2b31094ddbd4fa1013284043ffa5a7b8885b","modified":1631954625915},{"_id":"source/tags/index.md","hash":"6c078b132aa95154b2f4ac9a3d55d59aae8cd71e","modified":1631954625919},{"_id":"source/_posts/对比学习-SimCSE.md","hash":"d2466dab91ac35dbd07f97bd176f570abe76bf51","modified":1634558018575},{"_id":"source/images/SDNE1.png","hash":"4519472ce745052999e5087aa0be0fd865416c09","modified":1631954625917},{"_id":"source/images/GraphSAGE.png","hash":"42092da054b193ab4896d16b4cf53758a9a0831f","modified":1631954625916},{"_id":"themes/butterfly/_config.yml","hash":"207c273586638de2a9600e34ccac71a11f497fb9","modified":1631954625920},{"_id":"themes/butterfly/package.json","hash":"cca572885df4fe6c03a4b1fab63745ca35346a72","modified":1631954625944},{"_id":"themes/butterfly/LICENSE","hash":"c8bc7df08db9dd3b39c2c2259a163a36cf2f6808","modified":1631954625919},{"_id":"themes/butterfly/README_CN.md","hash":"f9c55f6944af584baff66e6d18109907f51a39a0","modified":1631954625919},{"_id":"themes/butterfly/README.md","hash":"000fb40436883605002a4b8ea34bb5ec9b0d4754","modified":1631954625919},{"_id":"themes/butterfly/languages/default.yml","hash":"b9dbdb20bd1f1c7ca8a8f38635bdc4ed8bb1d44c","modified":1631954625920},{"_id":"themes/butterfly/languages/zh-TW.yml","hash":"736bec8eeb3a29d0d43669d81f1fa686e02be18e","modified":1631954625921},{"_id":"themes/butterfly/languages/zh-CN.yml","hash":"46685048a05b419ed9c72cf31bd6e5efef7524f2","modified":1631954625921},{"_id":"themes/butterfly/languages/en.yml","hash":"fd1c1211c8f166d089a7697872185a81182d92e1","modified":1631954625920},{"_id":"themes/butterfly/layout/404.pug","hash":"def6b20a33b69f1be87a750a15a34c305d331a67","modified":1631954625921},{"_id":"themes/butterfly/layout/archive.pug","hash":"2d5bf4b1755f89898c579c18d601be83d2bc8ebd","modified":1631954625921},{"_id":"themes/butterfly/layout/index.pug","hash":"648dcbdb3d145a710de81c909e000e8664d2ac9c","modified":1631954625943},{"_id":"themes/butterfly/layout/category.pug","hash":"5ac3cd8172088843cec03175c612a9c85f49cf2e","modified":1631954625921},{"_id":"themes/butterfly/layout/includes/additional-js.pug","hash":"a1ca58fdff74b890cade94479f8f0eb9ce7e5e73","modified":1631954625922},{"_id":"themes/butterfly/layout/tag.pug","hash":"4bb5efc6dabdf1626685bf6771aaa1467155ae86","modified":1631954625944},{"_id":"themes/butterfly/layout/post.pug","hash":"8f2f13c9ae099dd83827ce3dbac5abc8d7d5bde3","modified":1631954625944},{"_id":"themes/butterfly/layout/page.pug","hash":"cae76ce64c114fd192b5da5a7d14aa0240df2f06","modified":1631954625944},{"_id":"themes/butterfly/layout/includes/footer.pug","hash":"8715948b93e7508b84d913be1969b28c6b067b9b","modified":1631954625922},{"_id":"themes/butterfly/layout/includes/head.pug","hash":"bff5ed7e56cce3cd540da40d971393fe7e93085c","modified":1631954625922},{"_id":"themes/butterfly/layout/includes/layout.pug","hash":"b0786e4b08231d178b8484e6c5cf01c22633612f","modified":1631954625927},{"_id":"themes/butterfly/scripts/events/404.js","hash":"5f6127ede7d139891208340b5cf5bf9314e4be7d","modified":1631954625944},{"_id":"themes/butterfly/scripts/events/init.js","hash":"f7ab55363d05c945569d9ac4afc889f9706308c6","modified":1631954625945},{"_id":"themes/butterfly/scripts/events/welcome.js","hash":"1e1da036fae593be7e2cc502f0a5cbeb2e7881d1","modified":1631954625945},{"_id":"themes/butterfly/scripts/filters/post_lazyload.js","hash":"1e2ac4d26df7cd18a92f1404329f8eb1c388dc18","modified":1631954625945},{"_id":"themes/butterfly/layout/includes/pagination.pug","hash":"5a58561a9b1ef46b1197550d6bfa553044f847a3","modified":1631954625929},{"_id":"themes/butterfly/scripts/helpers/aside_archives.js","hash":"4f712b4ea383b59a3122683db1d54c04a79ccc5d","modified":1631954625946},{"_id":"themes/butterfly/scripts/helpers/inject_head_js.js","hash":"87cefb7d6235744a2448e70df9061c1700169ad6","modified":1631954625946},{"_id":"themes/butterfly/scripts/helpers/aside_categories.js","hash":"376e1884ea764404c38b1e73b16de0358ece519e","modified":1631954625946},{"_id":"themes/butterfly/scripts/helpers/page.js","hash":"7dd77f751f5cc8c4fe30475a52df632dd85ce49a","modified":1631954625947},{"_id":"themes/butterfly/layout/includes/sidebar.pug","hash":"cfdd89b9ba771ecd0392ebea44ae6d751cf97735","modified":1631954625930},{"_id":"themes/butterfly/scripts/helpers/related_post.js","hash":"0745597f4ffadde037e57e0f66f7d984dc576dc6","modified":1631954625947},{"_id":"themes/butterfly/scripts/tag/gallery.js","hash":"7ffbe625f184116e442648c8416ea58614a1cef8","modified":1631954625948},{"_id":"themes/butterfly/scripts/tag/button.js","hash":"54c0c9c6d4d4ca754680cd24d7e204a745a6eec0","modified":1631954625947},{"_id":"themes/butterfly/scripts/tag/hide.js","hash":"eb019da8c190923e759d2fd7cb846281eef8594e","modified":1631954625948},{"_id":"themes/butterfly/scripts/tag/inlineImg.js","hash":"7641adb0d520c5ff29dd36fc1fb8617c52ecc9fb","modified":1631954625948},{"_id":"themes/butterfly/scripts/tag/mermaid.js","hash":"829229b0074b332ba779e159219eb2466612ff6e","modified":1631954625948},{"_id":"themes/butterfly/scripts/tag/note.js","hash":"420ba8b11e7316b4c09eb301d39814bc9bae9f90","modified":1631954625948},{"_id":"themes/butterfly/scripts/tag/tabs.js","hash":"08ea00791bd4738952234cb5d8360e119df6f875","modified":1631954625949},{"_id":"themes/butterfly/layout/includes/rightside.pug","hash":"66b14189a1913119ff5927c0b933b5c73fb37f48","modified":1631954625930},{"_id":"themes/butterfly/scripts/filters/random_cover.js","hash":"bd29f20fad3d3fab600940e7a6dc9a803943cb33","modified":1631954625945},{"_id":"themes/butterfly/source/css/index.styl","hash":"c7924868adcb046b46498626a9223c7a7b3f2e30","modified":1631954625958},{"_id":"themes/butterfly/source/css/var.styl","hash":"31198af95fecc6819d3b2cb7ef03988ef67257da","modified":1631954625959},{"_id":"themes/butterfly/source/js/main.js","hash":"30cd0283dabbcfd6fbdfd61208b7342c816941b5","modified":1631954625962},{"_id":"themes/butterfly/source/js/tw_cn.js","hash":"4db1170be7a9360e2c5399d281b979da730df2a3","modified":1631954625962},{"_id":"themes/butterfly/source/js/utils.js","hash":"ba7e672a7bed2aefb9174e8122d92a643814272a","modified":1631954625963},{"_id":"themes/butterfly/source/img/404.jpg","hash":"fb4489bc1d30c93d28f7332158c1c6c1416148de","modified":1631954625959},{"_id":"themes/butterfly/source/img/algolia.svg","hash":"45eeea0b5fba833e21e38ea10ed5ab385ceb4f01","modified":1631954625959},{"_id":"themes/butterfly/source/img/favicon.png","hash":"3cf89864b4f6c9b532522a4d260a2e887971c92d","modified":1631954625959},{"_id":"themes/butterfly/source/img/friend_404.gif","hash":"8d2d0ebef70a8eb07329f57e645889b0e420fa48","modified":1631954625960},{"_id":"themes/butterfly/source/img/loading.gif","hash":"5f0287fb8fb98872fe1998c6f781111819e71806","modified":1631954625960},{"_id":"themes/butterfly/layout/includes/head/Open_Graph.pug","hash":"07380718ed3af19a7e64b30e8c13726fe5983947","modified":1631954625923},{"_id":"themes/butterfly/layout/includes/head/analytics.pug","hash":"903d12250ce70713dd5b8ac7e1f7a20fe15eb815","modified":1631954625923},{"_id":"themes/butterfly/layout/includes/head/config.pug","hash":"3ca9d9cdfd6e61df1d5b07de40f34349cda7a7c7","modified":1631954625924},{"_id":"themes/butterfly/layout/includes/head/google_adsense.pug","hash":"f29123e603cbbcc6ce277d4e8f600ba67498077c","modified":1631954625924},{"_id":"themes/butterfly/layout/includes/head/config_site.pug","hash":"2913737dc8e3d9d63c22bec637d40c7bced85bfb","modified":1631954625924},{"_id":"themes/butterfly/layout/includes/head/noscript.pug","hash":"72efaa09ff60843567458bd54152e06f0cb2757e","modified":1631954625924},{"_id":"themes/butterfly/layout/includes/head/preconnect.pug","hash":"d6556d5396eb0e10ea0ec10158779c21dc78f738","modified":1631954625925},{"_id":"themes/butterfly/layout/includes/head/pwa.pug","hash":"6dc2c9b85df9ab4f5b554305339fd80a90a6cf43","modified":1631954625925},{"_id":"themes/butterfly/layout/includes/head/site_verification.pug","hash":"1ddbd09e1902da7fa4bf7824654f132e41622c42","modified":1631954625925},{"_id":"themes/butterfly/layout/includes/header/index.pug","hash":"d5747ccfba4861567375d2c7aa0ec6a846a57594","modified":1631954625926},{"_id":"themes/butterfly/layout/includes/header/nav.pug","hash":"419807903e5586b8804b1f8f17cea97bd05f0b17","modified":1631954625926},{"_id":"themes/butterfly/layout/includes/header/menu_item.pug","hash":"4d52000cae1fce333329c382dac6c9a21ad0b195","modified":1631954625926},{"_id":"themes/butterfly/layout/includes/header/social.pug","hash":"631ec7000fd4d6cfa2de118ee02ad8a42ffb34f5","modified":1631954625927},{"_id":"themes/butterfly/layout/includes/loading/loading-js.pug","hash":"2e1ab0c2ca59a1ff5a5ba9b6ef60f3e34af5430c","modified":1631954625927},{"_id":"themes/butterfly/layout/includes/header/post-info.pug","hash":"7677911bd3f43edaf8230eea02f60a248eee9934","modified":1631954625927},{"_id":"themes/butterfly/layout/includes/loading/loading.pug","hash":"dd8e6813976be64e80eba6562b54e74527ab306d","modified":1631954625927},{"_id":"themes/butterfly/layout/includes/mixins/article-sort.pug","hash":"2beb27e33e4492fa80e88c37dcb7a3ffd7da9e24","modified":1631954625928},{"_id":"themes/butterfly/layout/includes/mixins/post-ui.pug","hash":"581d15087d120bc9710967ecdf3efcea23e8297d","modified":1631954625928},{"_id":"themes/butterfly/layout/includes/page/categories.pug","hash":"95924d28d71b28769963529f82b376a9dc731fad","modified":1631954625928},{"_id":"themes/butterfly/layout/includes/page/default-page.pug","hash":"f5ea1e10a169aeb98e42b19ac084c4eb27693b46","modified":1631954625928},{"_id":"themes/butterfly/layout/includes/page/tags.pug","hash":"62dc2e683d56ddc9b588abc44dc1cfb778a2c68f","modified":1631954625929},{"_id":"themes/butterfly/layout/includes/post/reward.pug","hash":"862ed4cd51274e56e8c290a05c92dad896bb5f24","modified":1631954625930},{"_id":"themes/butterfly/layout/includes/page/flink.pug","hash":"2a14b697881d69720912e856ec15ace6be0c6bbd","modified":1631954625929},{"_id":"themes/butterfly/layout/includes/post/post-copyright.pug","hash":"6ec54b77e3a90e148e66e317e5bb6c89624fddca","modified":1631954625929},{"_id":"themes/butterfly/layout/includes/widget/card_ad.pug","hash":"a8312b527493dabbadbb1280760168d3bc909a3b","modified":1631954625941},{"_id":"themes/butterfly/layout/includes/widget/card_announcement.pug","hash":"009bdbef23381f10664622fafdc602f2ff5bf1ec","modified":1631954625941},{"_id":"themes/butterfly/layout/includes/widget/card_archives.pug","hash":"73d33b6930e7944187a4b3403daf25d27077a2dd","modified":1631954625941},{"_id":"themes/butterfly/layout/includes/widget/card_author.pug","hash":"374b2dfaaf615636d2cd4475110916499b388837","modified":1631954625942},{"_id":"themes/butterfly/layout/includes/widget/card_categories.pug","hash":"66e383b4ef374951eb87dd1bf4cdb7a667193fb5","modified":1631954625942},{"_id":"themes/butterfly/layout/includes/widget/card_newest_comment.pug","hash":"cabf9ee6a2886e6ede17f224dfbb161a9b1258e7","modified":1631954625942},{"_id":"themes/butterfly/layout/includes/widget/card_post_toc.pug","hash":"0b5c165664f9f8691551fc5ff435b40e7f73c737","modified":1631954625942},{"_id":"themes/butterfly/layout/includes/widget/card_recent_post.pug","hash":"ff6d8dfc187d6e5e6139bbf3e0d831dbe1e9a530","modified":1631954625942},{"_id":"themes/butterfly/layout/includes/widget/card_tags.pug","hash":"9755cac8424dc578e9ec07dbcaa429fddbedd392","modified":1631954625943},{"_id":"themes/butterfly/layout/includes/widget/card_self.pug","hash":"8318c7c9a6aef966afba44e283111e7ff0b2f04f","modified":1631954625943},{"_id":"themes/butterfly/layout/includes/widget/card_webinfo.pug","hash":"62adef7998fb9bebc5891a49472cfea944a1bf7a","modified":1631954625943},{"_id":"themes/butterfly/layout/includes/widget/index.pug","hash":"8c5b241773597a412dbf3ebaca1f0bf504934a33","modified":1631954625943},{"_id":"themes/butterfly/layout/includes/third-party/effect.pug","hash":"4c3da5a89b4e6fd3ab527f5c6d27790359d57f71","modified":1631954625935},{"_id":"themes/butterfly/layout/includes/third-party/pangu.pug","hash":"d06dafe7faf3a2e328ef62f26166a51ffe7a5579","modified":1631954625938},{"_id":"themes/butterfly/layout/includes/third-party/aplayer.pug","hash":"bee06b3f01fa1f0d8fa08c154234b452da425101","modified":1631954625930},{"_id":"themes/butterfly/layout/includes/third-party/pjax.pug","hash":"7286af98b3089746e74222209cece3b8e2cfde4d","modified":1631954625938},{"_id":"themes/butterfly/layout/includes/third-party/prismjs.pug","hash":"2448b243ded58aa1d5fc22fd115e6d06b636f9fd","modified":1631954625938},{"_id":"themes/butterfly/layout/includes/third-party/subtitle.pug","hash":"4a2ea9fe59f1dc8cb4f4f6a28cf24a40fc300254","modified":1631954625940},{"_id":"themes/butterfly/source/css/_global/function.styl","hash":"f9de9db2a49ac4d5447660ab159171ac5c228e3c","modified":1631954625949},{"_id":"themes/butterfly/source/css/_global/index.styl","hash":"35b424e6459db9c4c3f730b9122827c264efe613","modified":1631954625949},{"_id":"themes/butterfly/source/css/_highlight/highlight.styl","hash":"a0e5790eb82b8f551f4161b72f1bec29a4e501be","modified":1631954625950},{"_id":"themes/butterfly/source/css/_layout/404.styl","hash":"7730741fa8beb29e3231d397171f312957563e7b","modified":1631954625951},{"_id":"themes/butterfly/source/css/_layout/aside.styl","hash":"8627055bae0f877482dc53325152beb0a68d8e17","modified":1631954625951},{"_id":"themes/butterfly/source/css/_highlight/theme.styl","hash":"3f0b8699f529f0842c3b101006f60cb7933c16ff","modified":1631954625951},{"_id":"themes/butterfly/source/css/_layout/chat.styl","hash":"f27ad7b5d781c98bcac5c12c2d70b69b830e0374","modified":1631954625952},{"_id":"themes/butterfly/source/css/_layout/footer.styl","hash":"bfc4b9d8df66593c11c9ae78899f54e69785ca90","modified":1631954625952},{"_id":"themes/butterfly/source/css/_layout/comments.styl","hash":"8e6c9a2c1881985e4b2ae14ef1bdfdbb1bc83b37","modified":1631954625952},{"_id":"themes/butterfly/source/css/_layout/loading.styl","hash":"807f18717c29b5c41de6fc600a69a80ce8b7a19c","modified":1631954625952},{"_id":"themes/butterfly/source/css/_layout/head.styl","hash":"f584c96d911dc061ae9b83966c67b8cfb8065b5d","modified":1631954625952},{"_id":"themes/butterfly/source/css/_layout/pagination.styl","hash":"db7fe645662d87be4f8dfc08e55bbe1a3734bf93","modified":1631954625952},{"_id":"themes/butterfly/source/css/_layout/post.styl","hash":"f5128cd7a435c2c743fac57cb00196f4b2f42b1d","modified":1631954625953},{"_id":"themes/butterfly/source/css/_layout/relatedposts.styl","hash":"8c4e08c8a63dacdfb2a733a0d0035fa97c7f5e9e","modified":1631954625953},{"_id":"themes/butterfly/source/css/_layout/reward.styl","hash":"4ae98e703440b616d2a36da139bda844c94d425e","modified":1631954625953},{"_id":"themes/butterfly/source/css/_layout/rightside.styl","hash":"a00ad74074513a1c7f6e02977b8fa778beaa2108","modified":1631954625953},{"_id":"themes/butterfly/source/css/_layout/sidebar.styl","hash":"78ef182c0f6711c8491a8ee421b708c8f2a63c30","modified":1631954625954},{"_id":"themes/butterfly/source/css/_layout/third-party.styl","hash":"257f08063d056bcc9fe385d7913df19b6503d93d","modified":1631954625954},{"_id":"themes/butterfly/source/css/_mode/darkmode.styl","hash":"5f8ae3acad2575ab7865aa642a9b6bc56d30948d","modified":1631954625954},{"_id":"themes/butterfly/source/css/_mode/readmode.styl","hash":"5cc0a4e5c1c063c2d5cc17bfd6ba61825f6a25d6","modified":1631954625954},{"_id":"themes/butterfly/source/css/_page/archives.styl","hash":"190b85de3e686393e45869d12464995f23b304dc","modified":1631954625954},{"_id":"themes/butterfly/source/css/_page/categories.styl","hash":"ca1d3a885a85a91fcd6f7b55fca96cafb6a1b0a3","modified":1631954625955},{"_id":"themes/butterfly/source/css/_page/flink.styl","hash":"05269a522a7248d9a77569331481cbe18033c774","modified":1631954625955},{"_id":"themes/butterfly/source/css/_page/homepage.styl","hash":"0da09b1811a776106ca66c56be6e2a396dca5680","modified":1631954625955},{"_id":"themes/butterfly/source/css/_page/common.styl","hash":"ab403bfb1abc12ea5015b123ce412d3177e638d9","modified":1631954625955},{"_id":"themes/butterfly/source/css/_page/tags.styl","hash":"26e26797b3175498e20b2a8bf1679d3ed9fac5a6","modified":1631954625956},{"_id":"themes/butterfly/source/css/_tags/gallery.styl","hash":"6c12a03e26ca5d538537d472657d3d03a7fcbb87","modified":1631954625957},{"_id":"themes/butterfly/source/css/_tags/button.styl","hash":"5fe4235c038ed128819bd6918cd0781b372b598e","modified":1631954625957},{"_id":"themes/butterfly/source/css/_tags/hexo.styl","hash":"aa0728c556af75f5d66ecd44fa207722d474c26d","modified":1631954625957},{"_id":"themes/butterfly/source/css/_tags/hide.styl","hash":"56691537c7f06c2cf1ffa1509a328f506d938ee8","modified":1631954625957},{"_id":"themes/butterfly/source/css/_tags/inlineImg.styl","hash":"5a873d01fabebcf7ddf7a6b1c2e2e5e2714097f4","modified":1631954625957},{"_id":"themes/butterfly/source/css/_tags/note.styl","hash":"75a5c15c7029197ec5cffd27f7c5455891c946c9","modified":1631954625958},{"_id":"themes/butterfly/source/css/_tags/tabs.styl","hash":"8520674768dd0b1837f947892e8402b192a4f9c1","modified":1631954625958},{"_id":"themes/butterfly/source/css/_third-party/normalize.min.css","hash":"8549829fb7d3c21cd9e119884962e8c463a4a267","modified":1631954625958},{"_id":"themes/butterfly/source/css/_search/algolia.styl","hash":"a470029a157fbb8406ff72322a75ef63d6675431","modified":1631954625956},{"_id":"themes/butterfly/source/css/_search/index.styl","hash":"202b01db6ef3cca9e5a173e1a0dadfee8854a849","modified":1631954625956},{"_id":"themes/butterfly/source/css/_search/local-search.styl","hash":"92aa84cdec97729c86ba4674a355496eb37c986a","modified":1631954625956},{"_id":"themes/butterfly/source/js/search/algolia.js","hash":"a19f12257b14f70806a0c3cb0e9df1090f36e919","modified":1631954625962},{"_id":"themes/butterfly/source/js/search/local-search.js","hash":"3234942decaa1fde5b872766d4943a2e8cb9ef72","modified":1631954625962},{"_id":"themes/butterfly/layout/includes/third-party/card-post-count/disqus.pug","hash":"f4d21dcbc3b00eed9b1f604e132c4c6811a0a059","modified":1631954625930},{"_id":"themes/butterfly/layout/includes/third-party/card-post-count/fb.pug","hash":"4eebb2d94ca75809ef0cf32d70f13e9bf1e87091","modified":1631954625931},{"_id":"themes/butterfly/layout/includes/third-party/card-post-count/index.pug","hash":"a18415004d03c0a1783eccac522fbcb6ce0ea1f1","modified":1631954625931},{"_id":"themes/butterfly/layout/includes/third-party/card-post-count/twikoo.pug","hash":"eb2d840fa42de3ec7a7fda0eaa30246d52f543e2","modified":1631954625931},{"_id":"themes/butterfly/layout/includes/third-party/card-post-count/waline.pug","hash":"e8dec6fc24af46c4733681a3a25ad8eaad0e8e1a","modified":1631954625931},{"_id":"themes/butterfly/layout/includes/third-party/card-post-count/valine.pug","hash":"896dc0b7480151562c5717771e3000c5a7fc1b16","modified":1631954625931},{"_id":"themes/butterfly/layout/includes/third-party/comments/disqusjs.pug","hash":"eff268aba23c12365a0e77eaabe2aec190092293","modified":1631954625933},{"_id":"themes/butterfly/layout/includes/third-party/comments/disqus.pug","hash":"d5f81fd5443a1b09efb165b5f4447a35949d14ad","modified":1631954625933},{"_id":"themes/butterfly/layout/includes/third-party/comments/gitalk.pug","hash":"0254203aae73e70b90dfce6ec4f9c9d3be9ac9f8","modified":1631954625934},{"_id":"themes/butterfly/layout/includes/third-party/comments/index.pug","hash":"d97403cdd58c759cfd1752dd85aa4a0f9b73dc46","modified":1631954625934},{"_id":"themes/butterfly/layout/includes/third-party/comments/facebook_comments.pug","hash":"03a8937c60403252d33bacea947e252e5007eac8","modified":1631954625933},{"_id":"themes/butterfly/layout/includes/third-party/comments/js.pug","hash":"e877c98949873a62659db7501d19eb3d66650b51","modified":1631954625934},{"_id":"themes/butterfly/layout/includes/third-party/comments/twikoo.pug","hash":"afffc4ac8ce93d39dcc892bd518805cf33531244","modified":1631954625934},{"_id":"themes/butterfly/layout/includes/third-party/comments/livere.pug","hash":"589f8503f264d4fda971c8daf2028f45c4f2867b","modified":1631954625934},{"_id":"themes/butterfly/layout/includes/third-party/comments/valine.pug","hash":"8290994cf1ee2faff60214ff245cf513fbbe94aa","modified":1631954625935},{"_id":"themes/butterfly/layout/includes/third-party/comments/waline.pug","hash":"5e85ef16b881cacc9f401c2a20d69a70ad1bd3e2","modified":1631954625935},{"_id":"themes/butterfly/layout/includes/third-party/comments/utterances.pug","hash":"1c7e9fe86b7d7ef18cc876dfce19263fd4e73066","modified":1631954625935},{"_id":"themes/butterfly/layout/includes/third-party/chat/chatra.pug","hash":"f3f6eaecbcf9352342e259f4a5a3ad7160f31fc9","modified":1631954625932},{"_id":"themes/butterfly/layout/includes/third-party/chat/crisp.pug","hash":"b741b5e942481d779a8a1fe94c45154a62a6b748","modified":1631954625932},{"_id":"themes/butterfly/layout/includes/third-party/chat/daovoice.pug","hash":"e5af55cdb87d1ffd3d8702bc77097159acf95b54","modified":1631954625932},{"_id":"themes/butterfly/layout/includes/third-party/chat/gitter.pug","hash":"794ce3911f17d354b7196deb8c36d191afac63fb","modified":1631954625932},{"_id":"themes/butterfly/layout/includes/third-party/chat/index.pug","hash":"bb467bb22f3d0775b33f9eacbfc086ecb7831e78","modified":1631954625933},{"_id":"themes/butterfly/layout/includes/third-party/chat/tidio.pug","hash":"cd7ab4a776be93eea96a6f6fd0a547977fbe1ea3","modified":1631954625933},{"_id":"themes/butterfly/layout/includes/third-party/math/index.pug","hash":"2afa4c21dd19890f47fb568cfb0d90efb676a253","modified":1631954625936},{"_id":"themes/butterfly/layout/includes/third-party/math/katex.pug","hash":"407d88e4af099d5f1a3f44623ec276c999219ec3","modified":1631954625936},{"_id":"themes/butterfly/layout/includes/third-party/math/mathjax.pug","hash":"6f92311afe60ca5f44055016db9cecce8fb2dc62","modified":1631954625936},{"_id":"themes/butterfly/layout/includes/third-party/math/mermaid.pug","hash":"faf1113de12d6db0486572d7e99b03cbd0c4a896","modified":1631954625936},{"_id":"themes/butterfly/layout/includes/third-party/newest-comments/disqus-comment.pug","hash":"70125dc478ae20d5f6b4b908260fdc451d803c6c","modified":1631954625936},{"_id":"themes/butterfly/layout/includes/third-party/newest-comments/github-issues.pug","hash":"43bf91aab43ff818286fbedb7fa0bad584510883","modified":1631954625937},{"_id":"themes/butterfly/layout/includes/third-party/newest-comments/index.pug","hash":"7935e92db64ca047d94404e5fc390a207e857a87","modified":1631954625937},{"_id":"themes/butterfly/layout/includes/third-party/newest-comments/twikoo-comment.pug","hash":"767c38e3dfa0097b0dc1fdfd0df292c16f757d05","modified":1631954625937},{"_id":"themes/butterfly/layout/includes/third-party/newest-comments/valine.pug","hash":"a3d43cc360666b5b9730e8bb9e5c8fd940ae5b3d","modified":1631954625937},{"_id":"themes/butterfly/layout/includes/third-party/newest-comments/waline.pug","hash":"e4f11ee6b2bce37f7a88b2642ff0079e7d98f26f","modified":1631954625937},{"_id":"themes/butterfly/layout/includes/third-party/search/algolia.pug","hash":"1c06e60b120c946c1856904848ceba6ab58d1f82","modified":1631954625938},{"_id":"themes/butterfly/layout/includes/third-party/search/index.pug","hash":"ff3727c3ec698ec61a28c55cbc8c8508f0efb0a0","modified":1631954625938},{"_id":"themes/butterfly/layout/includes/third-party/search/local-search.pug","hash":"fbcf94ecdca30bed7cfc4f4ec08b200579c2614e","modified":1631954625939},{"_id":"themes/butterfly/layout/includes/third-party/share/add-this.pug","hash":"8b4034e53ca5bf85097f681a6e76a53ce685c205","modified":1631954625939},{"_id":"themes/butterfly/layout/includes/third-party/share/addtoany.pug","hash":"fbac21a6c5924fb2f8d1190fd634105fdbc603d4","modified":1631954625939},{"_id":"themes/butterfly/layout/includes/third-party/share/index.pug","hash":"4898a09d8e67fb358ffd74b3a1f0014f555dd856","modified":1631954625940},{"_id":"themes/butterfly/layout/includes/third-party/share/share-js.pug","hash":"7e9a7347dd8ca4c33c564fb62512743b6012bb2d","modified":1631954625940},{"_id":"themes/butterfly/source/css/_highlight/highlight/diff.styl","hash":"b0916c8c7d15b67b55cb1618be8370870fedbf42","modified":1631954625950},{"_id":"themes/butterfly/source/css/_highlight/highlight/index.styl","hash":"b74c61f6e15b422e9d2df23133552bbd1b5fe513","modified":1631954625950},{"_id":"themes/butterfly/source/css/_highlight/prismjs/diff.styl","hash":"1309292f1c8c53d96cd7333507b106bcc24ca8fc","modified":1631954625950},{"_id":"themes/butterfly/source/css/_highlight/prismjs/index.styl","hash":"4d71aab9082c67d3ee52ad58dfc3c1c9b41f2ab1","modified":1631954625950},{"_id":"themes/butterfly/source/css/_highlight/prismjs/line-number.styl","hash":"0b8aea62d1550113e1fcc237fae1b03743190208","modified":1631954625951},{"_id":"themes/butterfly/source/img/头像.jpg","hash":"c0b53f2f493dc24c3cf03ddc09ea9165e8ffcd38","modified":1631954625962},{"_id":"public/search.xml","hash":"aa5d8656b4510a42710af24ec10f238ed132a7cd","modified":1643093976453},{"_id":"public/categories/index.html","hash":"42e262d607748ad74104ac6854b9b5f06a110797","modified":1643093976453},{"_id":"public/messageboard/index.html","hash":"c93de2f35b3f027ff89ff9b61a5de717fc2146fa","modified":1643093976453},{"_id":"public/tags/index.html","hash":"0388e4cb67a267007d1aa15570f26a39e1528b12","modified":1643093976453},{"_id":"public/2022/01/25/万物皆可embedding/index.html","hash":"28eb247877c21fa228ff7e26f8a1a6896840a3c3","modified":1643093976453},{"_id":"public/2021/09/30/对比学习-SimCSE/index.html","hash":"60d53e8df91347dd636b10087c68fc8b02bd439a","modified":1643093976453},{"_id":"public/2021/09/18/关联挖掘/index.html","hash":"3ea0e48c291c8a1eb5f73c3f1e3c8ba25407022a","modified":1643093976453},{"_id":"public/2021/09/16/Louvain算法/index.html","hash":"194191866ff9c47c603d3af858d1a6d32fc47297","modified":1643093976453},{"_id":"public/2021/08/19/GNN系列之-GCN/index.html","hash":"d779620773fd8c7431cb7546d684ed6532da8264","modified":1643093976453},{"_id":"public/2021/08/19/数据标准化和中心化/index.html","hash":"38ebf5c1f59f2f3b1a897ca192604f0129bc6406","modified":1643093976453},{"_id":"public/2021/08/17/GNN系列之-GraphSAGE/index.html","hash":"61713b6f9ef034b6b7cdd11c79ad147b05cdd3d9","modified":1643093976453},{"_id":"public/2021/07/05/Graph-embedding之SDNE/index.html","hash":"8e59fe342b643ddf20f14da40ed87cd1004d1f14","modified":1643093976453},{"_id":"public/2021/06/04/NLP预处理:词干提取和词性还原/index.html","hash":"2573511b043ebf383324a0eb4f07666804700c49","modified":1643093976453},{"_id":"public/2021/06/04/Graph-embedding之deepwalk/index.html","hash":"e97c0c31f539131f1d6bd9b0dd66deacc791d68a","modified":1643093976453},{"_id":"public/2021/05/31/熵/index.html","hash":"1454822485db41cefc8233dd4596c40e9a4b3ddd","modified":1643093976453},{"_id":"public/2021/04/11/初识NLP/index.html","hash":"0e1cba1eb6637190c34d280364b14786278f3b8a","modified":1643093976453},{"_id":"public/2021/04/11/多进程、多线程(python)/index.html","hash":"ccd4805dfdd730068e88317d532e77b3e780eddb","modified":1643093976453},{"_id":"public/2021/04/11/初识Transomer/index.html","hash":"cca9269159c9de6697e541ecd6acdd20dcec3e27","modified":1643093976453},{"_id":"public/2021/04/08/异常检测/index.html","hash":"47385aeb0c0ca7afb2e54c185e7b7f4ad3ae87d8","modified":1643093976453},{"_id":"public/archives/index.html","hash":"381da8a985ded59fdf939e4ae5bc2630b233a831","modified":1643093976453},{"_id":"public/archives/2021/index.html","hash":"5d38fb1bd1b55022bfadae96ea701512ab8891a0","modified":1643093976453},{"_id":"public/archives/page/2/index.html","hash":"51ead417bc3fe099535e9a2e42a0c2bfa77977f2","modified":1643093976453},{"_id":"public/archives/2021/page/2/index.html","hash":"97c5f7c94d685dd2c3a0d2ff91d106a50fb7fb92","modified":1643093976453},{"_id":"public/archives/2021/04/index.html","hash":"383f39b2a30611eb053d64aeab5e7904a0a1062e","modified":1643093976453},{"_id":"public/archives/2021/06/index.html","hash":"e863ebfe698bb7f6359c020d43744ff9d881c4ca","modified":1643093976453},{"_id":"public/archives/2021/07/index.html","hash":"2d03d6532781bdfcd225dae37d10c238afd3c584","modified":1643093976453},{"_id":"public/archives/2021/08/index.html","hash":"b8f0ba8693119f062e04894efec21ae97ce15c6e","modified":1643093976453},{"_id":"public/archives/2021/09/index.html","hash":"d02132172653a9de3186a72e7fad84f8b40b9058","modified":1643093976453},{"_id":"public/archives/2021/05/index.html","hash":"b28d16c13651ede128bd1203c609d372158bfea5","modified":1643093976453},{"_id":"public/archives/2022/01/index.html","hash":"c7c91e20867f8d40733778a91b4c5ac23debc99a","modified":1643093976453},{"_id":"public/archives/2022/index.html","hash":"57a915012d65cc2adc2daf7af879f6dc780d9140","modified":1643093976453},{"_id":"public/categories/深度学习/index.html","hash":"5ae1c3dbbca8b710622ee32e7b9b9ba93dc6cc1e","modified":1643093976453},{"_id":"public/categories/机器学习/index.html","hash":"f0d61afc515cd1222a9475d0323277aa18688234","modified":1643093976453},{"_id":"public/categories/特征工程/index.html","hash":"7330611fcabd0efb81db932f4c2ae40ed159440f","modified":1643093976453},{"_id":"public/categories/基础知识/index.html","hash":"ea1d52af1f90bd89d31653561a29217faf22b110","modified":1643093976453},{"_id":"public/categories/python/index.html","hash":"570054c3aadeec5f800a34ece397d47e0776f45a","modified":1643093976453},{"_id":"public/index.html","hash":"7269104dd56259c3d96937f89be702cef00fa607","modified":1643093976453},{"_id":"public/tags/GNNs/index.html","hash":"605ed4dc1a07e6bc01ec5eb170dd76dda4155be8","modified":1643093976453},{"_id":"public/tags/Graph-Embedding/index.html","hash":"18627f3c98c232ac65753e10a5e074b4b1536024","modified":1643093976453},{"_id":"public/page/2/index.html","hash":"b03544ea084812dfebe95f26b434ed90555a8d53","modified":1643093976453},{"_id":"public/tags/community-detective/index.html","hash":"599e0d956c8119e34ad03d0ed6fc8eeae246c131","modified":1643093976453},{"_id":"public/tags/NLP/index.html","hash":"ec4aec53f8c8179ca0e99c13c7efb4cd6a8d22a4","modified":1643093976453},{"_id":"public/tags/基础知识/index.html","hash":"f24e4c1f424d5bbaeeca411418061cfa257db392","modified":1643093976453},{"_id":"public/tags/机器学习/index.html","hash":"4ec3b7394436df1418543c82e5c4e6ce00f764fc","modified":1643093976453},{"_id":"public/tags/工程开发/index.html","hash":"61a67b7f3691d546140ad6c642e0b211988526e3","modified":1643093976453},{"_id":"public/tags/异常检测/index.html","hash":"1c553103c9322d33e13b116d9f28968a56f29920","modified":1643093976453},{"_id":"public/tags/数据预处理/index.html","hash":"5a697550c319317b33750ac353d643de8d8d8e6b","modified":1643093976453},{"_id":"public/tags/transfomer/index.html","hash":"c05662acf120ce2d6bbc44eaaa80178c231c6c66","modified":1643093976453},{"_id":"public/tags/对比学习/index.html","hash":"878563e4d3fc5a15ca66d4cfedf2e055f14c1328","modified":1643093976453},{"_id":"public/images/embed1.jpg","hash":"907ce005e857541e564adb52d5001587670ced59","modified":1643093976453},{"_id":"public/images/embed2.png","hash":"9e137a5771f3a5b19f40f7ba2b9f26533cd70092","modified":1643093976453},{"_id":"public/images/embed3.png","hash":"38fb9aa02edea13ec257f9318ac8fa70aa5c7300","modified":1643093976453},{"_id":"public/images/embed4.png","hash":"3cdab14b6c9ba2f6acaaea0ff0abb9a94e0f82eb","modified":1643093976453},{"_id":"public/images/embed5.png","hash":"c2fc93ef56fea1ce4dd5628c38884fef0ca4ed46","modified":1643093976453},{"_id":"public/images/embed6.png","hash":"4f1572748d351e904712e94201088afe157006dd","modified":1643093976453},{"_id":"public/images/shang.webp","hash":"23d01ec7ef1439d63974b27755233a6aa440e2e6","modified":1643093976453},{"_id":"public/img/favicon.png","hash":"3cf89864b4f6c9b532522a4d260a2e887971c92d","modified":1643093976453},{"_id":"public/images/SDNE2.png","hash":"417af1bc2df4a0c28cf01bea8bd311291f645505","modified":1643093976453},{"_id":"public/img/404.jpg","hash":"fb4489bc1d30c93d28f7332158c1c6c1416148de","modified":1643093976453},{"_id":"public/img/loading.gif","hash":"5f0287fb8fb98872fe1998c6f781111819e71806","modified":1643093976453},{"_id":"public/img/algolia.svg","hash":"45eeea0b5fba833e21e38ea10ed5ab385ceb4f01","modified":1643093976453},{"_id":"public/images/deepwalk.png","hash":"d63e64f85d995087231bb96050e75b41429ea3fe","modified":1643093976453},{"_id":"public/images/SDNE1.png","hash":"4519472ce745052999e5087aa0be0fd865416c09","modified":1643093976453},{"_id":"public/img/friend_404.gif","hash":"8d2d0ebef70a8eb07329f57e645889b0e420fa48","modified":1643093976453},{"_id":"public/images/GraphSAGE.png","hash":"42092da054b193ab4896d16b4cf53758a9a0831f","modified":1643093976453},{"_id":"public/img/头像.jpg","hash":"c0b53f2f493dc24c3cf03ddc09ea9165e8ffcd38","modified":1643093976453},{"_id":"public/js/utils.js","hash":"41481019ef510c74e744f4f7ec4d0a13b5ccc04c","modified":1643093976453},{"_id":"public/css/var.css","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1643093976453},{"_id":"public/js/search/algolia.js","hash":"65b45e61586f7e66c3f338370bfd9daadd71a4b7","modified":1643093976453},{"_id":"public/js/search/local-search.js","hash":"459e2541afda483916d16fce4aaa56b41bcd42ba","modified":1643093976453},{"_id":"public/css/index.css","hash":"3e84eb3764c7619e3e2c9de8954c5b7671c0b84c","modified":1643093976453},{"_id":"public/js/main.js","hash":"8b670bed658cf879e37a12d9fda064cf4b55cc3f","modified":1643093976453},{"_id":"public/js/tw_cn.js","hash":"00053ce73210274b3679f42607edef1206eebc68","modified":1643093976453}],"Category":[{"name":"深度学习","_id":"ckytrqb4e0004so3wdwq6887r"},{"name":"机器学习","_id":"ckytrqb5b000mso3w7g2x2wrm"},{"name":"特征工程","_id":"ckytrqb5j000uso3wfanphyf3"},{"name":"基础知识","_id":"ckytrqb5o0010so3wdtyxbhfm"},{"name":"python","_id":"ckytrqb5q0015so3w553wgwp0"}],"Data":[],"Page":[{"title":"文章分类","date":"2021-04-08T07:50:03.000Z","type":"categories","_content":"","source":"categories/index.md","raw":"---\ntitle: 文章分类\ndate: 2021-04-08 15:50:03\ntype: \"categories\"\n---\n","updated":"2021-09-18T08:43:45.916Z","path":"categories/index.html","comments":1,"layout":"page","_id":"ckytrqb3z0000so3wgqjhepqr","content":"","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":""},{"title":"留下你的想法~","date":"2021-04-11T03:34:22.000Z","_content":"","source":"messageboard/index.md","raw":"---\ntitle: 留下你的想法~\ndate: 2021-04-11 11:34:22\n---\n","updated":"2021-09-18T08:43:45.918Z","path":"messageboard/index.html","comments":1,"layout":"page","_id":"ckytrqb4b0002so3w308iah68","content":"","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":""},{"title":"文章分类","date":"2021-04-08T07:49:36.000Z","type":"tags","_content":"","source":"tags/index.md","raw":"---\ntitle: 文章分类\ndate: 2021-04-08 15:49:36\ntype: \"tags\"\n---\n","updated":"2021-09-18T08:43:45.919Z","path":"tags/index.html","comments":1,"layout":"page","_id":"ckytrqb4m0006so3w7iadgh6u","content":"","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":""}],"Post":[{"title":"GNN系列之_GCN","mathjax":true,"date":"2021-08-19T03:05:12.000Z","description":null,"_content":"\n> GCN那段数学推断实在看不懂,不过作为一个worker我觉得一不一样数学原理都要搞的很透彻,毕竟我们只是模块的堆积和简单优化,推导还是交给顶尖大神吧\n\n## 回顾CNN的\n\n图片具有**局部平移不变性**:图片是一个规则的二维矩阵,无论卷积核平移到图片中的哪个位置都可以保证其运算结果的一致性,因此CNN可以利用卷积核进行特征提取。\n\n**CNN的三大特点**\n\n1. 参数共享:如果不共享,其实就是全连接,参数数据差距很大\n2. 局部连接性:卷积计算每次只在与卷积核大小对应的区域进行\n3. 层次化表达:不断叠加卷积层,每一个卷积层都是在前一层的基础上进行的,这样的意义在于,网络越往后,其提取到的特征越高级\n\n## 什么是GCN\n\n由于图片和文本等结构化数据具有上述提到的局部平移不变性,但很明显图的结构是不存在这种性质的,因为图结构的相邻节点数量是不确定的,因此用CNN的方式无法做卷积核。那graph中的CNN是什么呢?\n\n目前的一大思路就是借助谱图理论(Spectral Graph Theory)来实现在拓扑图上的卷积操作,大致步骤为将空域中的拓扑图结构通过傅立叶变换映射到频域中并进行卷积,然后利用逆变换返回空域,从而完成了图卷积操作。\n\n## **Graph Convolution**\n\n17年Thomas N. Kipf的GCN模型的公式\n\n$$\nH^{(l+1)}=\\sigma (\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}H^{l}W^{l})\n$$\n\n\n$\\tilde{A}$:A+I 自连接的领结矩阵\n\n$\\tilde{D}$:度矩阵\n\nW:参数【同层共享】,其作用就是将前一层的数据进行纬度变换\n\n$\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}$:对领结矩阵做标准化,和$\\tilde{D}^{-1}\\tilde{A}$的区别就是前者用的是拉普拉斯变换,想较于加权求和取平均的方式而言,前者不但考虑当前节点的 i 的度,还考虑其他节点 j 的度。\n\n## 结论\n\n他不像CNN可能层数越多越能提取高纬信息,他一般就两层左右。其实我理解就是利用领结矩阵的信息,只是用的神经网络的模式,和SDNE的思想应该挺像的。所以他不训练的效果主要还是取决于领结矩阵信息的表达。并不是说他就直接比node2vec,SDNE等以前训练embedding的方式号。\n\n**优点**\n\n1. 即使不用训练,直接随机参数也可以获得不错的效果\n2. GCN 可以在只标注少量样本的情况下学得出色的效果\n\n**缺点**:\n\n1. 要将整个图信息放入网络中,太占用内存和GPU\n2. 无法用于新节点和新的图,属于直推式学习\n3. 图数据很稀疏,训练和测试节点都是一样的\n\n[1]: https://zhuanlan.zhihu.com/p/120311352\t\"知乎\"\n[2]: https://mp.weixin.qq.com/s/jBQOgP-I4FQT1EU8y72ICA\n[3]: https://arxiv.org/abs/1606.09375\t\"GCN1\"\n[4]: https://arxiv.org/abs/1609.02907v4\t\"GCN2\"\n\n","source":"_posts/GNN系列之-GCN.md","raw":"---\ntitle: GNN系列之_GCN\ncategories:\n - 深度学习\ntags:\n - GNNs\nmathjax: true\ndate: 2021-08-19 11:05:12\ndescription:\n---\n\n> GCN那段数学推断实在看不懂,不过作为一个worker我觉得一不一样数学原理都要搞的很透彻,毕竟我们只是模块的堆积和简单优化,推导还是交给顶尖大神吧\n\n## 回顾CNN的\n\n图片具有**局部平移不变性**:图片是一个规则的二维矩阵,无论卷积核平移到图片中的哪个位置都可以保证其运算结果的一致性,因此CNN可以利用卷积核进行特征提取。\n\n**CNN的三大特点**\n\n1. 参数共享:如果不共享,其实就是全连接,参数数据差距很大\n2. 局部连接性:卷积计算每次只在与卷积核大小对应的区域进行\n3. 层次化表达:不断叠加卷积层,每一个卷积层都是在前一层的基础上进行的,这样的意义在于,网络越往后,其提取到的特征越高级\n\n## 什么是GCN\n\n由于图片和文本等结构化数据具有上述提到的局部平移不变性,但很明显图的结构是不存在这种性质的,因为图结构的相邻节点数量是不确定的,因此用CNN的方式无法做卷积核。那graph中的CNN是什么呢?\n\n目前的一大思路就是借助谱图理论(Spectral Graph Theory)来实现在拓扑图上的卷积操作,大致步骤为将空域中的拓扑图结构通过傅立叶变换映射到频域中并进行卷积,然后利用逆变换返回空域,从而完成了图卷积操作。\n\n## **Graph Convolution**\n\n17年Thomas N. Kipf的GCN模型的公式\n\n$$\nH^{(l+1)}=\\sigma (\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}H^{l}W^{l})\n$$\n\n\n$\\tilde{A}$:A+I 自连接的领结矩阵\n\n$\\tilde{D}$:度矩阵\n\nW:参数【同层共享】,其作用就是将前一层的数据进行纬度变换\n\n$\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}$:对领结矩阵做标准化,和$\\tilde{D}^{-1}\\tilde{A}$的区别就是前者用的是拉普拉斯变换,想较于加权求和取平均的方式而言,前者不但考虑当前节点的 i 的度,还考虑其他节点 j 的度。\n\n## 结论\n\n他不像CNN可能层数越多越能提取高纬信息,他一般就两层左右。其实我理解就是利用领结矩阵的信息,只是用的神经网络的模式,和SDNE的思想应该挺像的。所以他不训练的效果主要还是取决于领结矩阵信息的表达。并不是说他就直接比node2vec,SDNE等以前训练embedding的方式号。\n\n**优点**\n\n1. 即使不用训练,直接随机参数也可以获得不错的效果\n2. GCN 可以在只标注少量样本的情况下学得出色的效果\n\n**缺点**:\n\n1. 要将整个图信息放入网络中,太占用内存和GPU\n2. 无法用于新节点和新的图,属于直推式学习\n3. 图数据很稀疏,训练和测试节点都是一样的\n\n[1]: https://zhuanlan.zhihu.com/p/120311352\t\"知乎\"\n[2]: https://mp.weixin.qq.com/s/jBQOgP-I4FQT1EU8y72ICA\n[3]: https://arxiv.org/abs/1606.09375\t\"GCN1\"\n[4]: https://arxiv.org/abs/1609.02907v4\t\"GCN2\"\n\n","slug":"GNN系列之-GCN","published":1,"updated":"2021-09-18T08:43:45.913Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb480001so3we7wr9e42","content":"<blockquote>\n<p>GCN那段数学推断实在看不懂,不过作为一个worker我觉得一不一样数学原理都要搞的很透彻,毕竟我们只是模块的堆积和简单优化,推导还是交给顶尖大神吧</p>\n</blockquote>\n<h2 id=\"回顾CNN的\"><a href=\"#回顾CNN的\" class=\"headerlink\" title=\"回顾CNN的\"></a>回顾CNN的</h2><p>图片具有<strong>局部平移不变性</strong>:图片是一个规则的二维矩阵,无论卷积核平移到图片中的哪个位置都可以保证其运算结果的一致性,因此CNN可以利用卷积核进行特征提取。</p>\n<p><strong>CNN的三大特点</strong></p>\n<ol>\n<li>参数共享:如果不共享,其实就是全连接,参数数据差距很大</li>\n<li>局部连接性:卷积计算每次只在与卷积核大小对应的区域进行</li>\n<li>层次化表达:不断叠加卷积层,每一个卷积层都是在前一层的基础上进行的,这样的意义在于,网络越往后,其提取到的特征越高级</li>\n</ol>\n<h2 id=\"什么是GCN\"><a href=\"#什么是GCN\" class=\"headerlink\" title=\"什么是GCN\"></a>什么是GCN</h2><p>由于图片和文本等结构化数据具有上述提到的局部平移不变性,但很明显图的结构是不存在这种性质的,因为图结构的相邻节点数量是不确定的,因此用CNN的方式无法做卷积核。那graph中的CNN是什么呢?</p>\n<p>目前的一大思路就是借助谱图理论(Spectral Graph Theory)来实现在拓扑图上的卷积操作,大致步骤为将空域中的拓扑图结构通过傅立叶变换映射到频域中并进行卷积,然后利用逆变换返回空域,从而完成了图卷积操作。</p>\n<h2 id=\"Graph-Convolution\"><a href=\"#Graph-Convolution\" class=\"headerlink\" title=\"Graph Convolution\"></a><strong>Graph Convolution</strong></h2><p>17年Thomas N. Kipf的GCN模型的公式</p>\n<script type=\"math/tex; mode=display\">\nH^{(l+1)}=\\sigma (\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}H^{l}W^{l})</script><p>$\\tilde{A}$:A+I 自连接的领结矩阵</p>\n<p>$\\tilde{D}$:度矩阵</p>\n<p>W:参数【同层共享】,其作用就是将前一层的数据进行纬度变换</p>\n<p>$\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}$:对领结矩阵做标准化,和$\\tilde{D}^{-1}\\tilde{A}$的区别就是前者用的是拉普拉斯变换,想较于加权求和取平均的方式而言,前者不但考虑当前节点的 i 的度,还考虑其他节点 j 的度。</p>\n<h2 id=\"结论\"><a href=\"#结论\" class=\"headerlink\" title=\"结论\"></a>结论</h2><p>他不像CNN可能层数越多越能提取高纬信息,他一般就两层左右。其实我理解就是利用领结矩阵的信息,只是用的神经网络的模式,和SDNE的思想应该挺像的。所以他不训练的效果主要还是取决于领结矩阵信息的表达。并不是说他就直接比node2vec,SDNE等以前训练embedding的方式号。</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>即使不用训练,直接随机参数也可以获得不错的效果</li>\n<li>GCN 可以在只标注少量样本的情况下学得出色的效果</li>\n</ol>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>要将整个图信息放入网络中,太占用内存和GPU</li>\n<li>无法用于新节点和新的图,属于直推式学习</li>\n<li>图数据很稀疏,训练和测试节点都是一样的</li>\n</ol>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<blockquote>\n<p>GCN那段数学推断实在看不懂,不过作为一个worker我觉得一不一样数学原理都要搞的很透彻,毕竟我们只是模块的堆积和简单优化,推导还是交给顶尖大神吧</p>\n</blockquote>\n<h2 id=\"回顾CNN的\"><a href=\"#回顾CNN的\" class=\"headerlink\" title=\"回顾CNN的\"></a>回顾CNN的</h2><p>图片具有<strong>局部平移不变性</strong>:图片是一个规则的二维矩阵,无论卷积核平移到图片中的哪个位置都可以保证其运算结果的一致性,因此CNN可以利用卷积核进行特征提取。</p>\n<p><strong>CNN的三大特点</strong></p>\n<ol>\n<li>参数共享:如果不共享,其实就是全连接,参数数据差距很大</li>\n<li>局部连接性:卷积计算每次只在与卷积核大小对应的区域进行</li>\n<li>层次化表达:不断叠加卷积层,每一个卷积层都是在前一层的基础上进行的,这样的意义在于,网络越往后,其提取到的特征越高级</li>\n</ol>\n<h2 id=\"什么是GCN\"><a href=\"#什么是GCN\" class=\"headerlink\" title=\"什么是GCN\"></a>什么是GCN</h2><p>由于图片和文本等结构化数据具有上述提到的局部平移不变性,但很明显图的结构是不存在这种性质的,因为图结构的相邻节点数量是不确定的,因此用CNN的方式无法做卷积核。那graph中的CNN是什么呢?</p>\n<p>目前的一大思路就是借助谱图理论(Spectral Graph Theory)来实现在拓扑图上的卷积操作,大致步骤为将空域中的拓扑图结构通过傅立叶变换映射到频域中并进行卷积,然后利用逆变换返回空域,从而完成了图卷积操作。</p>\n<h2 id=\"Graph-Convolution\"><a href=\"#Graph-Convolution\" class=\"headerlink\" title=\"Graph Convolution\"></a><strong>Graph Convolution</strong></h2><p>17年Thomas N. Kipf的GCN模型的公式</p>\n<script type=\"math/tex; mode=display\">\nH^{(l+1)}=\\sigma (\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}H^{l}W^{l})</script><p>$\\tilde{A}$:A+I 自连接的领结矩阵</p>\n<p>$\\tilde{D}$:度矩阵</p>\n<p>W:参数【同层共享】,其作用就是将前一层的数据进行纬度变换</p>\n<p>$\\tilde{D}^{-1/2}\\tilde{A}\\tilde{D}^{-1/2}$:对领结矩阵做标准化,和$\\tilde{D}^{-1}\\tilde{A}$的区别就是前者用的是拉普拉斯变换,想较于加权求和取平均的方式而言,前者不但考虑当前节点的 i 的度,还考虑其他节点 j 的度。</p>\n<h2 id=\"结论\"><a href=\"#结论\" class=\"headerlink\" title=\"结论\"></a>结论</h2><p>他不像CNN可能层数越多越能提取高纬信息,他一般就两层左右。其实我理解就是利用领结矩阵的信息,只是用的神经网络的模式,和SDNE的思想应该挺像的。所以他不训练的效果主要还是取决于领结矩阵信息的表达。并不是说他就直接比node2vec,SDNE等以前训练embedding的方式号。</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>即使不用训练,直接随机参数也可以获得不错的效果</li>\n<li>GCN 可以在只标注少量样本的情况下学得出色的效果</li>\n</ol>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>要将整个图信息放入网络中,太占用内存和GPU</li>\n<li>无法用于新节点和新的图,属于直推式学习</li>\n<li>图数据很稀疏,训练和测试节点都是一样的</li>\n</ol>\n"},{"title":"GNN系列之_GraphSAGE","mathjax":true,"date":"2021-08-17T13:34:49.000Z","description":null,"_content":"# GraphSAGE\n\n## 定义\n\n17年**Hamilton**在GCN发布不久以后就发布了这篇文章,原文是《Inductive Representation Learning on Large Graphs》,从题目很明显可以看出,该论文强调两个事情:1.inductive;2.Large Graphs。\n\n1.什么是inductive\n\n在常用的机器学习或者深度学习模型中,我们通常会讲数据集分为训练集,测试集,验证集,各个集合之间相互是独立的,因为如果存在交集就变成了数据泄漏,那测试集的效果就不能正确的反应结果。但是在GCN中,由于模型中存在领结矩阵,这个是数据集通用的,这样的训练方式叫做transductive。因为为了避免这种类似数据泄漏的操作,GraphSAGE是一种inductive模式,即训练集,测试集,验证集相互独立。\n\n2.为什么叫适用于大规模图\n\n从GCN的公式中我们可以知道,GCN的训练需要将全部的领结矩阵放入训练,这样对于大规模图训练是不可用的,而GraphSAGE是利用采样聚合的方式,训练聚合函数,因此可以用minbatch来训练大规模图。\n\n3.为什么叫GraphSAGE\n\n这个是我在一开始就想问的,一个图表示训练模型为什么取这个名字,后来看论文才知道,SAGE取自两个单词:(SAmple and aggreGatE),也是简单的表明该模型的两个特色。\n\n## 实现步骤\n\n**伪代码**\n\n\n\n参数解释:\n\n- K:层数\n- AGGREGATE:聚合函数,有3种\n- concat:拼接矩阵\n\n个人理解:输入初始特征矩阵(可以是one-hot/随机初始化),经过K层聚合矩阵,其实也是聚合了K步的领结信息,利用某种**聚合**函数,将每个节点的特征和其**采样**的领结节点特征进行融合。\n\n**损失函数**\n$$\nJ_{g}(z_{u})=-log(\\sigma (z_{u}^{T}z_{v}))-Q\\cdot E_{v_{n}\\sim P{_{n}}^{(v)}}log(\\sigma (-z_{u}^{T}z_{v_{n}}))\n$$\n\n- $z_{u}$为节点u通过GraphSAGE生成的embedding。\n- 节点v是节点u随机游走访达“邻居”。\n- $v_{n}\\sim P{_{n}}$表示负采样:节点$v_{n}$是从节点u的负采样分布 ![[公式]](https://www.zhihu.com/equation?tex=P_n) 采样的,Q为采样样本数。\n\n简单理解就是希望节点u与“邻居”v的embedding也相似(对应公式第一项),而与“没有交集”的节点 ![[公式]](https://www.zhihu.com/equation?tex=v_n) 不相似(对应公式第二项)。\n\n## 聚合函数\n\n### Mean aggregator\n\n**平均聚合**\n$$\n\\begin{matrix}\nh_{N(v)}^{k}=mean(\\{h_{u}^{k-1},u\\in N(v)\\})\n\\\\ \nh_{v}^{k}=\\sigma (W^{k}\\cdot CONCAT(h_{v}^{k-1},h_{N_{(u)}}^{k}))\n\\end{matrix}\n$$\n就是伪代码写的那种,先对k-1采样的领结节点特征进行求平均,然后和K-1层的节点进行拼接,在利用参数Wk进行纬度转换。\n\n**归纳式聚合**\n$$\nh_{v}^{k}=\\sigma (W^{k}\\cdot mean(\\{h_{v}^{k-1}\\}\\cup \\{h_{u}^{k-1},\\forall u\\in N(v) \\}))\n$$\n直接对k-1层,v节点+采样的领结节点特征进行求平均,利用参数$W^{k}$进行纬度转换。\n\n### LSTM\n\n对领结节点进行随机排序,因为采样的LSTM是固定的,然后作为序列放入LSTM最后输出一个embedding就是v。\n\n### Pooling\n\n$$\nAggregate_{k}^{pool}=max(\\{\\sigma (W_{pool}h_{u_{i}}^{k}+b),\\forall u_{i}\\in N(v)\\})\n$$\n\n把各个邻居节点单独经过一个MLP得到一个向量,最后把所有邻居的向量做一个max-pooling或者mean-pooling来获取。\n\n## 总结\n\n**优点:**\n\n1. GraphSAGE基于采样+聚合的策略,可以很好的解决GCN将整个邻接矩阵放入训练导致内存溢出的问题,可以用于大规模图中。\n2. GCN不能去推测没有看到的节点,因为他的训练依赖邻接矩阵,而GraphSAGE训练的是一个聚合函数,所以他可以用已只节点去推测未知节点,前提是未知节点的领结节点存在于GraphSAGE中。\n\n**不足**:\n\n1. 他既然是聚合函数,没有用到Attention,也就是说对于权重的分配没有采取更好的策略。因此才诞生了GAT。\n\n\n\n\n\n\n\n","source":"_posts/GNN系列之-GraphSAGE.md","raw":"---\ntitle: GNN系列之_GraphSAGE\ncategories:\n - 深度学习\ntags:\n - GNNs\nmathjax: true\ndate: 2021-08-17 21:34:49\ndescription:\n---\n# GraphSAGE\n\n## 定义\n\n17年**Hamilton**在GCN发布不久以后就发布了这篇文章,原文是《Inductive Representation Learning on Large Graphs》,从题目很明显可以看出,该论文强调两个事情:1.inductive;2.Large Graphs。\n\n1.什么是inductive\n\n在常用的机器学习或者深度学习模型中,我们通常会讲数据集分为训练集,测试集,验证集,各个集合之间相互是独立的,因为如果存在交集就变成了数据泄漏,那测试集的效果就不能正确的反应结果。但是在GCN中,由于模型中存在领结矩阵,这个是数据集通用的,这样的训练方式叫做transductive。因为为了避免这种类似数据泄漏的操作,GraphSAGE是一种inductive模式,即训练集,测试集,验证集相互独立。\n\n2.为什么叫适用于大规模图\n\n从GCN的公式中我们可以知道,GCN的训练需要将全部的领结矩阵放入训练,这样对于大规模图训练是不可用的,而GraphSAGE是利用采样聚合的方式,训练聚合函数,因此可以用minbatch来训练大规模图。\n\n3.为什么叫GraphSAGE\n\n这个是我在一开始就想问的,一个图表示训练模型为什么取这个名字,后来看论文才知道,SAGE取自两个单词:(SAmple and aggreGatE),也是简单的表明该模型的两个特色。\n\n## 实现步骤\n\n**伪代码**\n\n\n\n参数解释:\n\n- K:层数\n- AGGREGATE:聚合函数,有3种\n- concat:拼接矩阵\n\n个人理解:输入初始特征矩阵(可以是one-hot/随机初始化),经过K层聚合矩阵,其实也是聚合了K步的领结信息,利用某种**聚合**函数,将每个节点的特征和其**采样**的领结节点特征进行融合。\n\n**损失函数**\n$$\nJ_{g}(z_{u})=-log(\\sigma (z_{u}^{T}z_{v}))-Q\\cdot E_{v_{n}\\sim P{_{n}}^{(v)}}log(\\sigma (-z_{u}^{T}z_{v_{n}}))\n$$\n\n- $z_{u}$为节点u通过GraphSAGE生成的embedding。\n- 节点v是节点u随机游走访达“邻居”。\n- $v_{n}\\sim P{_{n}}$表示负采样:节点$v_{n}$是从节点u的负采样分布 ![[公式]](https://www.zhihu.com/equation?tex=P_n) 采样的,Q为采样样本数。\n\n简单理解就是希望节点u与“邻居”v的embedding也相似(对应公式第一项),而与“没有交集”的节点 ![[公式]](https://www.zhihu.com/equation?tex=v_n) 不相似(对应公式第二项)。\n\n## 聚合函数\n\n### Mean aggregator\n\n**平均聚合**\n$$\n\\begin{matrix}\nh_{N(v)}^{k}=mean(\\{h_{u}^{k-1},u\\in N(v)\\})\n\\\\ \nh_{v}^{k}=\\sigma (W^{k}\\cdot CONCAT(h_{v}^{k-1},h_{N_{(u)}}^{k}))\n\\end{matrix}\n$$\n就是伪代码写的那种,先对k-1采样的领结节点特征进行求平均,然后和K-1层的节点进行拼接,在利用参数Wk进行纬度转换。\n\n**归纳式聚合**\n$$\nh_{v}^{k}=\\sigma (W^{k}\\cdot mean(\\{h_{v}^{k-1}\\}\\cup \\{h_{u}^{k-1},\\forall u\\in N(v) \\}))\n$$\n直接对k-1层,v节点+采样的领结节点特征进行求平均,利用参数$W^{k}$进行纬度转换。\n\n### LSTM\n\n对领结节点进行随机排序,因为采样的LSTM是固定的,然后作为序列放入LSTM最后输出一个embedding就是v。\n\n### Pooling\n\n$$\nAggregate_{k}^{pool}=max(\\{\\sigma (W_{pool}h_{u_{i}}^{k}+b),\\forall u_{i}\\in N(v)\\})\n$$\n\n把各个邻居节点单独经过一个MLP得到一个向量,最后把所有邻居的向量做一个max-pooling或者mean-pooling来获取。\n\n## 总结\n\n**优点:**\n\n1. GraphSAGE基于采样+聚合的策略,可以很好的解决GCN将整个邻接矩阵放入训练导致内存溢出的问题,可以用于大规模图中。\n2. GCN不能去推测没有看到的节点,因为他的训练依赖邻接矩阵,而GraphSAGE训练的是一个聚合函数,所以他可以用已只节点去推测未知节点,前提是未知节点的领结节点存在于GraphSAGE中。\n\n**不足**:\n\n1. 他既然是聚合函数,没有用到Attention,也就是说对于权重的分配没有采取更好的策略。因此才诞生了GAT。\n\n\n\n\n\n\n\n","slug":"GNN系列之-GraphSAGE","published":1,"updated":"2021-09-18T08:43:45.913Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb4c0003so3w4lup7eqs","content":"<h1 id=\"GraphSAGE\"><a href=\"#GraphSAGE\" class=\"headerlink\" title=\"GraphSAGE\"></a>GraphSAGE</h1><h2 id=\"定义\"><a href=\"#定义\" class=\"headerlink\" title=\"定义\"></a>定义</h2><p>17年<strong>Hamilton</strong>在GCN发布不久以后就发布了这篇文章,原文是《Inductive Representation Learning on Large Graphs》,从题目很明显可以看出,该论文强调两个事情:1.inductive;2.Large Graphs。</p>\n<p>1.什么是inductive</p>\n<p>在常用的机器学习或者深度学习模型中,我们通常会讲数据集分为训练集,测试集,验证集,各个集合之间相互是独立的,因为如果存在交集就变成了数据泄漏,那测试集的效果就不能正确的反应结果。但是在GCN中,由于模型中存在领结矩阵,这个是数据集通用的,这样的训练方式叫做transductive。因为为了避免这种类似数据泄漏的操作,GraphSAGE是一种inductive模式,即训练集,测试集,验证集相互独立。</p>\n<p>2.为什么叫适用于大规模图</p>\n<p>从GCN的公式中我们可以知道,GCN的训练需要将全部的领结矩阵放入训练,这样对于大规模图训练是不可用的,而GraphSAGE是利用采样聚合的方式,训练聚合函数,因此可以用minbatch来训练大规模图。</p>\n<p>3.为什么叫GraphSAGE</p>\n<p>这个是我在一开始就想问的,一个图表示训练模型为什么取这个名字,后来看论文才知道,SAGE取自两个单词:(SAmple and aggreGatE),也是简单的表明该模型的两个特色。</p>\n<h2 id=\"实现步骤\"><a href=\"#实现步骤\" class=\"headerlink\" title=\"实现步骤\"></a>实现步骤</h2><p><strong>伪代码</strong></p>\n<p><img src=\"/images/GraphSAGE.png\" alt=\"伪代码\"></p>\n<p>参数解释:</p>\n<ul>\n<li>K:层数</li>\n<li>AGGREGATE:聚合函数,有3种</li>\n<li>concat:拼接矩阵</li>\n</ul>\n<p>个人理解:输入初始特征矩阵(可以是one-hot/随机初始化),经过K层聚合矩阵,其实也是聚合了K步的领结信息,利用某种<strong>聚合</strong>函数,将每个节点的特征和其<strong>采样</strong>的领结节点特征进行融合。</p>\n<p><strong>损失函数</strong></p>\n<script type=\"math/tex; mode=display\">\nJ_{g}(z_{u})=-log(\\sigma (z_{u}^{T}z_{v}))-Q\\cdot E_{v_{n}\\sim P{_{n}}^{(v)}}log(\\sigma (-z_{u}^{T}z_{v_{n}}))</script><ul>\n<li>$z_{u}$为节点u通过GraphSAGE生成的embedding。</li>\n<li>节点v是节点u随机游走访达“邻居”。</li>\n<li>$v<em>{n}\\sim P{</em>{n}}$表示负采样:节点$v_{n}$是从节点u的负采样分布 <img src=\"https://www.zhihu.com/equation?tex=P_n\" alt=\"[公式]\"> 采样的,Q为采样样本数。</li>\n</ul>\n<p>简单理解就是希望节点u与“邻居”v的embedding也相似(对应公式第一项),而与“没有交集”的节点 <img src=\"https://www.zhihu.com/equation?tex=v_n\" alt=\"[公式]\"> 不相似(对应公式第二项)。</p>\n<h2 id=\"聚合函数\"><a href=\"#聚合函数\" class=\"headerlink\" title=\"聚合函数\"></a>聚合函数</h2><h3 id=\"Mean-aggregator\"><a href=\"#Mean-aggregator\" class=\"headerlink\" title=\"Mean aggregator\"></a>Mean aggregator</h3><p><strong>平均聚合</strong></p>\n<script type=\"math/tex; mode=display\">\n\\begin{matrix}\nh_{N(v)}^{k}=mean(\\{h_{u}^{k-1},u\\in N(v)\\})\n\\\\ \nh_{v}^{k}=\\sigma (W^{k}\\cdot CONCAT(h_{v}^{k-1},h_{N_{(u)}}^{k}))\n\\end{matrix}</script><p>就是伪代码写的那种,先对k-1采样的领结节点特征进行求平均,然后和K-1层的节点进行拼接,在利用参数Wk进行纬度转换。</p>\n<p><strong>归纳式聚合</strong></p>\n<script type=\"math/tex; mode=display\">\nh_{v}^{k}=\\sigma (W^{k}\\cdot mean(\\{h_{v}^{k-1}\\}\\cup \\{h_{u}^{k-1},\\forall u\\in N(v) \\}))</script><p>直接对k-1层,v节点+采样的领结节点特征进行求平均,利用参数$W^{k}$进行纬度转换。</p>\n<h3 id=\"LSTM\"><a href=\"#LSTM\" class=\"headerlink\" title=\"LSTM\"></a>LSTM</h3><p>对领结节点进行随机排序,因为采样的LSTM是固定的,然后作为序列放入LSTM最后输出一个embedding就是v。</p>\n<h3 id=\"Pooling\"><a href=\"#Pooling\" class=\"headerlink\" title=\"Pooling\"></a>Pooling</h3><script type=\"math/tex; mode=display\">\nAggregate_{k}^{pool}=max(\\{\\sigma (W_{pool}h_{u_{i}}^{k}+b),\\forall u_{i}\\in N(v)\\})</script><p>把各个邻居节点单独经过一个MLP得到一个向量,最后把所有邻居的向量做一个max-pooling或者mean-pooling来获取。</p>\n<h2 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h2><p><strong>优点:</strong></p>\n<ol>\n<li>GraphSAGE基于采样+聚合的策略,可以很好的解决GCN将整个邻接矩阵放入训练导致内存溢出的问题,可以用于大规模图中。</li>\n<li>GCN不能去推测没有看到的节点,因为他的训练依赖邻接矩阵,而GraphSAGE训练的是一个聚合函数,所以他可以用已只节点去推测未知节点,前提是未知节点的领结节点存在于GraphSAGE中。</li>\n</ol>\n<p><strong>不足</strong>:</p>\n<ol>\n<li>他既然是聚合函数,没有用到Attention,也就是说对于权重的分配没有采取更好的策略。因此才诞生了GAT。</li>\n</ol>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h1 id=\"GraphSAGE\"><a href=\"#GraphSAGE\" class=\"headerlink\" title=\"GraphSAGE\"></a>GraphSAGE</h1><h2 id=\"定义\"><a href=\"#定义\" class=\"headerlink\" title=\"定义\"></a>定义</h2><p>17年<strong>Hamilton</strong>在GCN发布不久以后就发布了这篇文章,原文是《Inductive Representation Learning on Large Graphs》,从题目很明显可以看出,该论文强调两个事情:1.inductive;2.Large Graphs。</p>\n<p>1.什么是inductive</p>\n<p>在常用的机器学习或者深度学习模型中,我们通常会讲数据集分为训练集,测试集,验证集,各个集合之间相互是独立的,因为如果存在交集就变成了数据泄漏,那测试集的效果就不能正确的反应结果。但是在GCN中,由于模型中存在领结矩阵,这个是数据集通用的,这样的训练方式叫做transductive。因为为了避免这种类似数据泄漏的操作,GraphSAGE是一种inductive模式,即训练集,测试集,验证集相互独立。</p>\n<p>2.为什么叫适用于大规模图</p>\n<p>从GCN的公式中我们可以知道,GCN的训练需要将全部的领结矩阵放入训练,这样对于大规模图训练是不可用的,而GraphSAGE是利用采样聚合的方式,训练聚合函数,因此可以用minbatch来训练大规模图。</p>\n<p>3.为什么叫GraphSAGE</p>\n<p>这个是我在一开始就想问的,一个图表示训练模型为什么取这个名字,后来看论文才知道,SAGE取自两个单词:(SAmple and aggreGatE),也是简单的表明该模型的两个特色。</p>\n<h2 id=\"实现步骤\"><a href=\"#实现步骤\" class=\"headerlink\" title=\"实现步骤\"></a>实现步骤</h2><p><strong>伪代码</strong></p>\n<p><img src=\"/images/GraphSAGE.png\" alt=\"伪代码\"></p>\n<p>参数解释:</p>\n<ul>\n<li>K:层数</li>\n<li>AGGREGATE:聚合函数,有3种</li>\n<li>concat:拼接矩阵</li>\n</ul>\n<p>个人理解:输入初始特征矩阵(可以是one-hot/随机初始化),经过K层聚合矩阵,其实也是聚合了K步的领结信息,利用某种<strong>聚合</strong>函数,将每个节点的特征和其<strong>采样</strong>的领结节点特征进行融合。</p>\n<p><strong>损失函数</strong></p>\n<script type=\"math/tex; mode=display\">\nJ_{g}(z_{u})=-log(\\sigma (z_{u}^{T}z_{v}))-Q\\cdot E_{v_{n}\\sim P{_{n}}^{(v)}}log(\\sigma (-z_{u}^{T}z_{v_{n}}))</script><ul>\n<li>$z_{u}$为节点u通过GraphSAGE生成的embedding。</li>\n<li>节点v是节点u随机游走访达“邻居”。</li>\n<li>$v<em>{n}\\sim P{</em>{n}}$表示负采样:节点$v_{n}$是从节点u的负采样分布 <img src=\"https://www.zhihu.com/equation?tex=P_n\" alt=\"[公式]\"> 采样的,Q为采样样本数。</li>\n</ul>\n<p>简单理解就是希望节点u与“邻居”v的embedding也相似(对应公式第一项),而与“没有交集”的节点 <img src=\"https://www.zhihu.com/equation?tex=v_n\" alt=\"[公式]\"> 不相似(对应公式第二项)。</p>\n<h2 id=\"聚合函数\"><a href=\"#聚合函数\" class=\"headerlink\" title=\"聚合函数\"></a>聚合函数</h2><h3 id=\"Mean-aggregator\"><a href=\"#Mean-aggregator\" class=\"headerlink\" title=\"Mean aggregator\"></a>Mean aggregator</h3><p><strong>平均聚合</strong></p>\n<script type=\"math/tex; mode=display\">\n\\begin{matrix}\nh_{N(v)}^{k}=mean(\\{h_{u}^{k-1},u\\in N(v)\\})\n\\\\ \nh_{v}^{k}=\\sigma (W^{k}\\cdot CONCAT(h_{v}^{k-1},h_{N_{(u)}}^{k}))\n\\end{matrix}</script><p>就是伪代码写的那种,先对k-1采样的领结节点特征进行求平均,然后和K-1层的节点进行拼接,在利用参数Wk进行纬度转换。</p>\n<p><strong>归纳式聚合</strong></p>\n<script type=\"math/tex; mode=display\">\nh_{v}^{k}=\\sigma (W^{k}\\cdot mean(\\{h_{v}^{k-1}\\}\\cup \\{h_{u}^{k-1},\\forall u\\in N(v) \\}))</script><p>直接对k-1层,v节点+采样的领结节点特征进行求平均,利用参数$W^{k}$进行纬度转换。</p>\n<h3 id=\"LSTM\"><a href=\"#LSTM\" class=\"headerlink\" title=\"LSTM\"></a>LSTM</h3><p>对领结节点进行随机排序,因为采样的LSTM是固定的,然后作为序列放入LSTM最后输出一个embedding就是v。</p>\n<h3 id=\"Pooling\"><a href=\"#Pooling\" class=\"headerlink\" title=\"Pooling\"></a>Pooling</h3><script type=\"math/tex; mode=display\">\nAggregate_{k}^{pool}=max(\\{\\sigma (W_{pool}h_{u_{i}}^{k}+b),\\forall u_{i}\\in N(v)\\})</script><p>把各个邻居节点单独经过一个MLP得到一个向量,最后把所有邻居的向量做一个max-pooling或者mean-pooling来获取。</p>\n<h2 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h2><p><strong>优点:</strong></p>\n<ol>\n<li>GraphSAGE基于采样+聚合的策略,可以很好的解决GCN将整个邻接矩阵放入训练导致内存溢出的问题,可以用于大规模图中。</li>\n<li>GCN不能去推测没有看到的节点,因为他的训练依赖邻接矩阵,而GraphSAGE训练的是一个聚合函数,所以他可以用已只节点去推测未知节点,前提是未知节点的领结节点存在于GraphSAGE中。</li>\n</ol>\n<p><strong>不足</strong>:</p>\n<ol>\n<li>他既然是聚合函数,没有用到Attention,也就是说对于权重的分配没有采取更好的策略。因此才诞生了GAT。</li>\n</ol>\n"},{"title":"Graph_embedding之SDNE","date":"2021-07-05T05:46:00.000Z","mathjax":true,"description":null,"_content":"\n# 目标\n\n复现SDNE,思考如何利用点击和相关属性获取合适的商品embedding,用于以后的商品聚类。\n\n# 模型简介\n\n## 模型结构\n\n\n\nSDNE是16年发表在kdd上的一篇论文,是第一篇将深度学习模型运用于Graph embedding的论文。其可以看作LINE的一种延伸。使用一个自编码器来同时优化1阶和2阶损失,训练结束的中间向量作为商品embedding。\n\n## 损失函数\n\n\n\n1阶损失:不同节点中间变量的相似程度,α表示控制1阶损失函数\n\n2阶损失:同一节点重构后的差距,Β矩阵用于控制非0元素,对其实施更高的惩罚系数,避免非0元素由于反向传播变0\n\nreg损失:控制模型参数,v正则化参数\n\n## 论文核心\n\n模型的输入:每个节点的领结矩阵,相邻节点的拉普拉斯矩阵\n\n模型的输出:每个节点预测的领结矩阵\n\n### 领接矩阵\n\n用于表示顶点之间相邻关系的矩阵。分为有向无权,有向有权,无向无权,无向有权矩阵。\n\n如果是无权,其实权重代表的就是两个节点之间是否相连,相连为1,不相连为0。如果是有权,权重的设定可以是多样化的,可以只用连接信息设置权重,也可以添加side_info设置综合权重【新品的话就必须加side_info】。\n\n有向无向对于邻接矩阵没有太大的影响,无非有向的邻接是非对称矩阵,而无向的邻接是对称矩阵。\n\n### 拉普拉斯矩阵\n\n给定一个有n个顶点的图G,它的拉普拉斯矩阵$$L:=(li,j)n×nL:=(li,j)n×n$$。\n\nL=D-A,其中D为图的度矩阵,A为图的邻接矩阵。度矩阵在有向图中,有向图的度等于出度和入度之和。\n\n# 复现情况\n\n## 修改部分\n\n- 矩阵生成:由于原论文的复现代码都是直接生成全量的领接矩阵和拉普拉斯矩阵,但考虑实际情况,我们点击商品有**30W,**全量商品有**100W**,直接生成全量矩阵不现实,即使用半精度【FP16】至少要**100多个G**,而且对于深度模型而言,模型的参数无法使用半精度,会导致数值溢出问题。因此就必须改写矩阵生成模块。\n- 模型内部:由于节点很多,导致输入输出的参数非常庞大,因此无法设置过多的层数和较大的embedding维度。这个暂时无法改变,只能限制网络结构用GPU跑动\n- 损失函数:按原论文增加了正则化损失\n- 图的生成:参考浮现的代码只构造了有向图的领接矩阵,为了综合比较,构造了无向图的数据模块。\n\n## 原始数据\n\n参数:\n\n- 数据集:wiki\n- epoch:5/10/20/50\n- 其他参数均一样\n\n| | base_epoch5 | base_epoch10 | base_epoch20 | base_epoch50 | rec_epoch5 | rec_epoch10 | rec_epoch20 | rec_epoch50 |\n| -------- | ----------- | ------------ | ------------ | ------------ | ---------- | ----------- | ----------- | ----------- |\n| micro_f1 | 0.4137 | 0.4927 | 0.6216 | 0.5717 | 0.5841 | 0.6267 | **0.7190** | 0.7072 |\n| macro_f2 | 0.2788 | 0.3417 | 0.4479 | 0.4187 | 0.3640 | 0.4208 | **0.5154** | 0.5510 |\n\n**小点分析**:重构代码效果正常,增加了正则化损失在该数据集更有用\n\n","source":"_posts/Graph-embedding之SDNE.md","raw":"---\ntitle: Graph_embedding之SDNE\ncategories:\n - 深度学习\ntags:\n - Graph Embedding\ndate: 2021-07-05 13:46:00\nmathjax: true\ndescription:\n---\n\n# 目标\n\n复现SDNE,思考如何利用点击和相关属性获取合适的商品embedding,用于以后的商品聚类。\n\n# 模型简介\n\n## 模型结构\n\n\n\nSDNE是16年发表在kdd上的一篇论文,是第一篇将深度学习模型运用于Graph embedding的论文。其可以看作LINE的一种延伸。使用一个自编码器来同时优化1阶和2阶损失,训练结束的中间向量作为商品embedding。\n\n## 损失函数\n\n\n\n1阶损失:不同节点中间变量的相似程度,α表示控制1阶损失函数\n\n2阶损失:同一节点重构后的差距,Β矩阵用于控制非0元素,对其实施更高的惩罚系数,避免非0元素由于反向传播变0\n\nreg损失:控制模型参数,v正则化参数\n\n## 论文核心\n\n模型的输入:每个节点的领结矩阵,相邻节点的拉普拉斯矩阵\n\n模型的输出:每个节点预测的领结矩阵\n\n### 领接矩阵\n\n用于表示顶点之间相邻关系的矩阵。分为有向无权,有向有权,无向无权,无向有权矩阵。\n\n如果是无权,其实权重代表的就是两个节点之间是否相连,相连为1,不相连为0。如果是有权,权重的设定可以是多样化的,可以只用连接信息设置权重,也可以添加side_info设置综合权重【新品的话就必须加side_info】。\n\n有向无向对于邻接矩阵没有太大的影响,无非有向的邻接是非对称矩阵,而无向的邻接是对称矩阵。\n\n### 拉普拉斯矩阵\n\n给定一个有n个顶点的图G,它的拉普拉斯矩阵$$L:=(li,j)n×nL:=(li,j)n×n$$。\n\nL=D-A,其中D为图的度矩阵,A为图的邻接矩阵。度矩阵在有向图中,有向图的度等于出度和入度之和。\n\n# 复现情况\n\n## 修改部分\n\n- 矩阵生成:由于原论文的复现代码都是直接生成全量的领接矩阵和拉普拉斯矩阵,但考虑实际情况,我们点击商品有**30W,**全量商品有**100W**,直接生成全量矩阵不现实,即使用半精度【FP16】至少要**100多个G**,而且对于深度模型而言,模型的参数无法使用半精度,会导致数值溢出问题。因此就必须改写矩阵生成模块。\n- 模型内部:由于节点很多,导致输入输出的参数非常庞大,因此无法设置过多的层数和较大的embedding维度。这个暂时无法改变,只能限制网络结构用GPU跑动\n- 损失函数:按原论文增加了正则化损失\n- 图的生成:参考浮现的代码只构造了有向图的领接矩阵,为了综合比较,构造了无向图的数据模块。\n\n## 原始数据\n\n参数:\n\n- 数据集:wiki\n- epoch:5/10/20/50\n- 其他参数均一样\n\n| | base_epoch5 | base_epoch10 | base_epoch20 | base_epoch50 | rec_epoch5 | rec_epoch10 | rec_epoch20 | rec_epoch50 |\n| -------- | ----------- | ------------ | ------------ | ------------ | ---------- | ----------- | ----------- | ----------- |\n| micro_f1 | 0.4137 | 0.4927 | 0.6216 | 0.5717 | 0.5841 | 0.6267 | **0.7190** | 0.7072 |\n| macro_f2 | 0.2788 | 0.3417 | 0.4479 | 0.4187 | 0.3640 | 0.4208 | **0.5154** | 0.5510 |\n\n**小点分析**:重构代码效果正常,增加了正则化损失在该数据集更有用\n\n","slug":"Graph-embedding之SDNE","published":1,"updated":"2021-09-18T08:43:45.913Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb4o0007so3w6qw7hvqx","content":"<h1 id=\"目标\"><a href=\"#目标\" class=\"headerlink\" title=\"目标\"></a>目标</h1><p>复现SDNE,思考如何利用点击和相关属性获取合适的商品embedding,用于以后的商品聚类。</p>\n<h1 id=\"模型简介\"><a href=\"#模型简介\" class=\"headerlink\" title=\"模型简介\"></a>模型简介</h1><h2 id=\"模型结构\"><a href=\"#模型结构\" class=\"headerlink\" title=\"模型结构\"></a>模型结构</h2><p><img src=\"/images/SDNE1.png\" alt=\"SDNE1\"></p>\n<p>SDNE是16年发表在kdd上的一篇论文,是第一篇将深度学习模型运用于Graph embedding的论文。其可以看作LINE的一种延伸。使用一个自编码器来同时优化1阶和2阶损失,训练结束的中间向量作为商品embedding。</p>\n<h2 id=\"损失函数\"><a href=\"#损失函数\" class=\"headerlink\" title=\"损失函数\"></a>损失函数</h2><p><img src=\"/images/SDNE2.png\" alt=\"SDNE2\"></p>\n<p>1阶损失:不同节点中间变量的相似程度,α表示控制1阶损失函数</p>\n<p>2阶损失:同一节点重构后的差距,Β矩阵用于控制非0元素,对其实施更高的惩罚系数,避免非0元素由于反向传播变0</p>\n<p>reg损失:控制模型参数,v正则化参数</p>\n<h2 id=\"论文核心\"><a href=\"#论文核心\" class=\"headerlink\" title=\"论文核心\"></a>论文核心</h2><p>模型的输入:每个节点的领结矩阵,相邻节点的拉普拉斯矩阵</p>\n<p>模型的输出:每个节点预测的领结矩阵</p>\n<h3 id=\"领接矩阵\"><a href=\"#领接矩阵\" class=\"headerlink\" title=\"领接矩阵\"></a>领接矩阵</h3><p>用于表示顶点之间相邻关系的矩阵。分为有向无权,有向有权,无向无权,无向有权矩阵。</p>\n<p>如果是无权,其实权重代表的就是两个节点之间是否相连,相连为1,不相连为0。如果是有权,权重的设定可以是多样化的,可以只用连接信息设置权重,也可以添加side_info设置综合权重【新品的话就必须加side_info】。</p>\n<p>有向无向对于邻接矩阵没有太大的影响,无非有向的邻接是非对称矩阵,而无向的邻接是对称矩阵。</p>\n<h3 id=\"拉普拉斯矩阵\"><a href=\"#拉普拉斯矩阵\" class=\"headerlink\" title=\"拉普拉斯矩阵\"></a>拉普拉斯矩阵</h3><p>给定一个有n个顶点的图G,它的拉普拉斯矩阵<script type=\"math/tex\">L:=(li,j)n×nL:=(li,j)n×n</script>。</p>\n<p>L=D-A,其中D为图的度矩阵,A为图的邻接矩阵。度矩阵在有向图中,有向图的度等于出度和入度之和。</p>\n<h1 id=\"复现情况\"><a href=\"#复现情况\" class=\"headerlink\" title=\"复现情况\"></a>复现情况</h1><h2 id=\"修改部分\"><a href=\"#修改部分\" class=\"headerlink\" title=\"修改部分\"></a>修改部分</h2><ul>\n<li>矩阵生成:由于原论文的复现代码都是直接生成全量的领接矩阵和拉普拉斯矩阵,但考虑实际情况,我们点击商品有<strong>30W,</strong>全量商品有<strong>100W</strong>,直接生成全量矩阵不现实,即使用半精度【FP16】至少要<strong>100多个G</strong>,而且对于深度模型而言,模型的参数无法使用半精度,会导致数值溢出问题。因此就必须改写矩阵生成模块。</li>\n<li>模型内部:由于节点很多,导致输入输出的参数非常庞大,因此无法设置过多的层数和较大的embedding维度。这个暂时无法改变,只能限制网络结构用GPU跑动</li>\n<li>损失函数:按原论文增加了正则化损失</li>\n<li>图的生成:参考浮现的代码只构造了有向图的领接矩阵,为了综合比较,构造了无向图的数据模块。</li>\n</ul>\n<h2 id=\"原始数据\"><a href=\"#原始数据\" class=\"headerlink\" title=\"原始数据\"></a>原始数据</h2><p>参数:</p>\n<ul>\n<li>数据集:wiki</li>\n<li>epoch:5/10/20/50</li>\n<li>其他参数均一样</li>\n</ul>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th></th>\n<th>base_epoch5</th>\n<th>base_epoch10</th>\n<th>base_epoch20</th>\n<th>base_epoch50</th>\n<th>rec_epoch5</th>\n<th>rec_epoch10</th>\n<th>rec_epoch20</th>\n<th>rec_epoch50</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>micro_f1</td>\n<td>0.4137</td>\n<td>0.4927</td>\n<td>0.6216</td>\n<td>0.5717</td>\n<td>0.5841</td>\n<td>0.6267</td>\n<td><strong>0.7190</strong></td>\n<td>0.7072</td>\n</tr>\n<tr>\n<td>macro_f2</td>\n<td>0.2788</td>\n<td>0.3417</td>\n<td>0.4479</td>\n<td>0.4187</td>\n<td>0.3640</td>\n<td>0.4208</td>\n<td><strong>0.5154</strong></td>\n<td>0.5510</td>\n</tr>\n</tbody>\n</table>\n</div>\n<p><strong>小点分析</strong>:重构代码效果正常,增加了正则化损失在该数据集更有用</p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h1 id=\"目标\"><a href=\"#目标\" class=\"headerlink\" title=\"目标\"></a>目标</h1><p>复现SDNE,思考如何利用点击和相关属性获取合适的商品embedding,用于以后的商品聚类。</p>\n<h1 id=\"模型简介\"><a href=\"#模型简介\" class=\"headerlink\" title=\"模型简介\"></a>模型简介</h1><h2 id=\"模型结构\"><a href=\"#模型结构\" class=\"headerlink\" title=\"模型结构\"></a>模型结构</h2><p><img src=\"/images/SDNE1.png\" alt=\"SDNE1\"></p>\n<p>SDNE是16年发表在kdd上的一篇论文,是第一篇将深度学习模型运用于Graph embedding的论文。其可以看作LINE的一种延伸。使用一个自编码器来同时优化1阶和2阶损失,训练结束的中间向量作为商品embedding。</p>\n<h2 id=\"损失函数\"><a href=\"#损失函数\" class=\"headerlink\" title=\"损失函数\"></a>损失函数</h2><p><img src=\"/images/SDNE2.png\" alt=\"SDNE2\"></p>\n<p>1阶损失:不同节点中间变量的相似程度,α表示控制1阶损失函数</p>\n<p>2阶损失:同一节点重构后的差距,Β矩阵用于控制非0元素,对其实施更高的惩罚系数,避免非0元素由于反向传播变0</p>\n<p>reg损失:控制模型参数,v正则化参数</p>\n<h2 id=\"论文核心\"><a href=\"#论文核心\" class=\"headerlink\" title=\"论文核心\"></a>论文核心</h2><p>模型的输入:每个节点的领结矩阵,相邻节点的拉普拉斯矩阵</p>\n<p>模型的输出:每个节点预测的领结矩阵</p>\n<h3 id=\"领接矩阵\"><a href=\"#领接矩阵\" class=\"headerlink\" title=\"领接矩阵\"></a>领接矩阵</h3><p>用于表示顶点之间相邻关系的矩阵。分为有向无权,有向有权,无向无权,无向有权矩阵。</p>\n<p>如果是无权,其实权重代表的就是两个节点之间是否相连,相连为1,不相连为0。如果是有权,权重的设定可以是多样化的,可以只用连接信息设置权重,也可以添加side_info设置综合权重【新品的话就必须加side_info】。</p>\n<p>有向无向对于邻接矩阵没有太大的影响,无非有向的邻接是非对称矩阵,而无向的邻接是对称矩阵。</p>\n<h3 id=\"拉普拉斯矩阵\"><a href=\"#拉普拉斯矩阵\" class=\"headerlink\" title=\"拉普拉斯矩阵\"></a>拉普拉斯矩阵</h3><p>给定一个有n个顶点的图G,它的拉普拉斯矩阵<script type=\"math/tex\">L:=(li,j)n×nL:=(li,j)n×n</script>。</p>\n<p>L=D-A,其中D为图的度矩阵,A为图的邻接矩阵。度矩阵在有向图中,有向图的度等于出度和入度之和。</p>\n<h1 id=\"复现情况\"><a href=\"#复现情况\" class=\"headerlink\" title=\"复现情况\"></a>复现情况</h1><h2 id=\"修改部分\"><a href=\"#修改部分\" class=\"headerlink\" title=\"修改部分\"></a>修改部分</h2><ul>\n<li>矩阵生成:由于原论文的复现代码都是直接生成全量的领接矩阵和拉普拉斯矩阵,但考虑实际情况,我们点击商品有<strong>30W,</strong>全量商品有<strong>100W</strong>,直接生成全量矩阵不现实,即使用半精度【FP16】至少要<strong>100多个G</strong>,而且对于深度模型而言,模型的参数无法使用半精度,会导致数值溢出问题。因此就必须改写矩阵生成模块。</li>\n<li>模型内部:由于节点很多,导致输入输出的参数非常庞大,因此无法设置过多的层数和较大的embedding维度。这个暂时无法改变,只能限制网络结构用GPU跑动</li>\n<li>损失函数:按原论文增加了正则化损失</li>\n<li>图的生成:参考浮现的代码只构造了有向图的领接矩阵,为了综合比较,构造了无向图的数据模块。</li>\n</ul>\n<h2 id=\"原始数据\"><a href=\"#原始数据\" class=\"headerlink\" title=\"原始数据\"></a>原始数据</h2><p>参数:</p>\n<ul>\n<li>数据集:wiki</li>\n<li>epoch:5/10/20/50</li>\n<li>其他参数均一样</li>\n</ul>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th></th>\n<th>base_epoch5</th>\n<th>base_epoch10</th>\n<th>base_epoch20</th>\n<th>base_epoch50</th>\n<th>rec_epoch5</th>\n<th>rec_epoch10</th>\n<th>rec_epoch20</th>\n<th>rec_epoch50</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>micro_f1</td>\n<td>0.4137</td>\n<td>0.4927</td>\n<td>0.6216</td>\n<td>0.5717</td>\n<td>0.5841</td>\n<td>0.6267</td>\n<td><strong>0.7190</strong></td>\n<td>0.7072</td>\n</tr>\n<tr>\n<td>macro_f2</td>\n<td>0.2788</td>\n<td>0.3417</td>\n<td>0.4479</td>\n<td>0.4187</td>\n<td>0.3640</td>\n<td>0.4208</td>\n<td><strong>0.5154</strong></td>\n<td>0.5510</td>\n</tr>\n</tbody>\n</table>\n</div>\n<p><strong>小点分析</strong>:重构代码效果正常,增加了正则化损失在该数据集更有用</p>\n"},{"title":"Graph_embedding之deepwalk","date":"2021-06-04T06:20:54.000Z","description":null,"_content":"\n### 历史\n\ndeepwalk的作者Bryan参照word2vec的思想,将文本应用到图的结构中,形成了这篇文章,内容也不复杂,也算是为后世的Graph embedding开了一个头。\n\n### 流程图\n\n\n\n### 论文理解\n\n**适用领域**\n\n1. network分类\n2. 异常检测\n\n**主要内容**\n\n1. 前提基于领域假设\n2. deepwalk用的是没有权重的图\n3. 作者证明了单词共现和节点共现有类似的现象,因此文本的那一套也可以用于图。\n4. 当数据稀疏时或者使用低于60%数据量的数据集时效果比传统模型好\n\n**具体步骤**\n\n1. 首先基于节点信息和边的信息,生成底层图谱\n2. 以每个node作为定长序列的起始点,利用随机游走生成定长序列,所以该随机游走也称为截断式随机游走。\n3. 将输出的序列作为word2vec的输入,生成nodes embedding\n4. 将类别信息和nodes embedding输入模型(LR,SVM,深度都可以)做一个多分类。\n\n**看法**\n\n本身word2vec和deepwalk这种生成词向量的算法都是无监督的,但是后面加上一些有监督的算法就可以合理对embedding的效果做出判断,近期看的论文总会提到link predict的方法,这和现在的节点分类提供类似的作用,而且前途也很大,利用某些半监督的算法对无监督算法进行评判。\n\n### 参考\n\n[DeepWalk](https://arxiv.org/abs/1403.6652)","source":"_posts/Graph-embedding之deepwalk.md","raw":"---\ntitle: Graph_embedding之deepwalk\ncategories:\n - 深度学习\ntags:\n - Graph Embedding\ndate: 2021-06-04 14:20:54\ndescription:\n---\n\n### 历史\n\ndeepwalk的作者Bryan参照word2vec的思想,将文本应用到图的结构中,形成了这篇文章,内容也不复杂,也算是为后世的Graph embedding开了一个头。\n\n### 流程图\n\n\n\n### 论文理解\n\n**适用领域**\n\n1. network分类\n2. 异常检测\n\n**主要内容**\n\n1. 前提基于领域假设\n2. deepwalk用的是没有权重的图\n3. 作者证明了单词共现和节点共现有类似的现象,因此文本的那一套也可以用于图。\n4. 当数据稀疏时或者使用低于60%数据量的数据集时效果比传统模型好\n\n**具体步骤**\n\n1. 首先基于节点信息和边的信息,生成底层图谱\n2. 以每个node作为定长序列的起始点,利用随机游走生成定长序列,所以该随机游走也称为截断式随机游走。\n3. 将输出的序列作为word2vec的输入,生成nodes embedding\n4. 将类别信息和nodes embedding输入模型(LR,SVM,深度都可以)做一个多分类。\n\n**看法**\n\n本身word2vec和deepwalk这种生成词向量的算法都是无监督的,但是后面加上一些有监督的算法就可以合理对embedding的效果做出判断,近期看的论文总会提到link predict的方法,这和现在的节点分类提供类似的作用,而且前途也很大,利用某些半监督的算法对无监督算法进行评判。\n\n### 参考\n\n[DeepWalk](https://arxiv.org/abs/1403.6652)","slug":"Graph-embedding之deepwalk","published":1,"updated":"2021-09-18T08:43:45.913Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb4p0008so3w2ewkfkc7","content":"<h3 id=\"历史\"><a href=\"#历史\" class=\"headerlink\" title=\"历史\"></a>历史</h3><p>deepwalk的作者Bryan参照word2vec的思想,将文本应用到图的结构中,形成了这篇文章,内容也不复杂,也算是为后世的Graph embedding开了一个头。</p>\n<h3 id=\"流程图\"><a href=\"#流程图\" class=\"headerlink\" title=\"流程图\"></a>流程图</h3><p><img src=\"/images/deepwalk.png\" alt=\"deepwalk\"></p>\n<h3 id=\"论文理解\"><a href=\"#论文理解\" class=\"headerlink\" title=\"论文理解\"></a>论文理解</h3><p><strong>适用领域</strong></p>\n<ol>\n<li>network分类</li>\n<li>异常检测</li>\n</ol>\n<p><strong>主要内容</strong></p>\n<ol>\n<li>前提基于领域假设</li>\n<li>deepwalk用的是没有权重的图</li>\n<li>作者证明了单词共现和节点共现有类似的现象,因此文本的那一套也可以用于图。</li>\n<li>当数据稀疏时或者使用低于60%数据量的数据集时效果比传统模型好</li>\n</ol>\n<p><strong>具体步骤</strong></p>\n<ol>\n<li>首先基于节点信息和边的信息,生成底层图谱</li>\n<li>以每个node作为定长序列的起始点,利用随机游走生成定长序列,所以该随机游走也称为截断式随机游走。</li>\n<li>将输出的序列作为word2vec的输入,生成nodes embedding</li>\n<li>将类别信息和nodes embedding输入模型(LR,SVM,深度都可以)做一个多分类。</li>\n</ol>\n<p><strong>看法</strong></p>\n<p>本身word2vec和deepwalk这种生成词向量的算法都是无监督的,但是后面加上一些有监督的算法就可以合理对embedding的效果做出判断,近期看的论文总会提到link predict的方法,这和现在的节点分类提供类似的作用,而且前途也很大,利用某些半监督的算法对无监督算法进行评判。</p>\n<h3 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h3><p><a href=\"https://arxiv.org/abs/1403.6652\">DeepWalk</a></p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h3 id=\"历史\"><a href=\"#历史\" class=\"headerlink\" title=\"历史\"></a>历史</h3><p>deepwalk的作者Bryan参照word2vec的思想,将文本应用到图的结构中,形成了这篇文章,内容也不复杂,也算是为后世的Graph embedding开了一个头。</p>\n<h3 id=\"流程图\"><a href=\"#流程图\" class=\"headerlink\" title=\"流程图\"></a>流程图</h3><p><img src=\"/images/deepwalk.png\" alt=\"deepwalk\"></p>\n<h3 id=\"论文理解\"><a href=\"#论文理解\" class=\"headerlink\" title=\"论文理解\"></a>论文理解</h3><p><strong>适用领域</strong></p>\n<ol>\n<li>network分类</li>\n<li>异常检测</li>\n</ol>\n<p><strong>主要内容</strong></p>\n<ol>\n<li>前提基于领域假设</li>\n<li>deepwalk用的是没有权重的图</li>\n<li>作者证明了单词共现和节点共现有类似的现象,因此文本的那一套也可以用于图。</li>\n<li>当数据稀疏时或者使用低于60%数据量的数据集时效果比传统模型好</li>\n</ol>\n<p><strong>具体步骤</strong></p>\n<ol>\n<li>首先基于节点信息和边的信息,生成底层图谱</li>\n<li>以每个node作为定长序列的起始点,利用随机游走生成定长序列,所以该随机游走也称为截断式随机游走。</li>\n<li>将输出的序列作为word2vec的输入,生成nodes embedding</li>\n<li>将类别信息和nodes embedding输入模型(LR,SVM,深度都可以)做一个多分类。</li>\n</ol>\n<p><strong>看法</strong></p>\n<p>本身word2vec和deepwalk这种生成词向量的算法都是无监督的,但是后面加上一些有监督的算法就可以合理对embedding的效果做出判断,近期看的论文总会提到link predict的方法,这和现在的节点分类提供类似的作用,而且前途也很大,利用某些半监督的算法对无监督算法进行评判。</p>\n<h3 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h3><p><a href=\"https://arxiv.org/abs/1403.6652\">DeepWalk</a></p>\n"},{"title":"Louvain算法","mathjax":true,"date":"2021-09-16T13:49:43.000Z","description":null,"_content":"\n> \u0011利用商品的点击和购买,对商品进行聚类分析,因此选择了Louvain这个高效的聚类算法,再此主要是为了记录一下,方便日后回顾\n\n问题\n\n- 什么是模块度,其代表什么含义,公式推导\n- Louvain算法的精髓是什么,以前是怎么做社团发现的\n- 社团发现和广义聚类的区别\n- 为什么Louvain算法会存在resolution问题,后面是怎么解决的。\n\n## 模块度\n\nNewman[1](https://arxiv.org/pdf/cond-mat/0308217.pdf),2003年首次提出了第一版模块度,后在[2](https://www.pnas.org/content/pnas/103/23/8577.full.pdf),2006年提出了第二版模块度,经过多次修正后形成现在我们认知中的模块度。\n\n### 解释\n\n模块度有可以从两个角度解释,一种是较为宏观的表示簇内聚集度和簇外离散度的综合指标,另一种从数学角度认为是在给定组内的边的比例减去边是随机分布的期望分数,其具体的值属于【-0.5,1】。论文中认为0.3-0.8是较好模块度\n\n### 定义\n\n**第一版**\n\n假设网络被划分为 $k$ 个社区,那么定义一个$k×k$的对称矩阵$e$,它的元素 $e_{ij}$表示社区 $i$ 和社区 $j$ 之间的边的数量。矩阵的迹$Tre=\\sum e_{ii}$,也就表示了在相同社区内节点之间的边集合。显然,社区划分的好,也就是社区内部节点之间联系密集,那么该值就越高,这与我们通常对社区的认识是一致的。\n\n但是!如果不划分社区,直接将数据作为一个团,那他的Tre就是1,这是不合理的,因此又定义了一个$a_{i}=\\sum e_{ij}$,表示所有连接到社区ii的边数量。最后形成第一版的模块度函数\n$$\nQ=\\sum (e_{ii}-ai^{2})=Tre-\\left \\| e^{2}\\right \\|\n$$\n**第二版**\n\n为什么Newman要对模块度重新定义呢,因为第一版没有考虑节点度的概念,节点的度在一定程度上能够表示该节点被连接的概率,并且第二版矩阵形式可以应用在spectral optimization algorithms,具体参考[wiki](https://en.wikipedia.org/wiki/Modularity_(networks)#Matrix_formulation)。\n$$\nQ=\\frac{\\sum (A_{ij}-P_{ij})\\delta_{ij}}{2m}\n$$\n\n- 2m:所有节点的度数之和,为了计算的模块度不受m的规模影响\n- $A_{ij}/\\delta_{ij}$:节点的领结矩阵【不考虑有权那就是1,0】\n- $P_{ij}$:任意两个节点i和j连接的概率\n\n我们将$K_i$和$K_j$表示节点i和j的度,那么\n$$\nP_{ij}=\\frac{K_i*K_j}{2m}=K_i*\\frac{K_j}{2m}\n$$\n$K_j/2m$表示节点j被连接的概率,因此$P_{ij}$就表示节点i和j连接的概率。并且第一版和第二版本质上互通的,两者可以直接推导成一个公式。\n\n## 算法步骤\n\n讲完了模块度的概念,那我们知道了模块度是用于衡量一个社团结构好坏的指标,而Louvain算法就是基于该指标,利用迭代不断优化模块度,并且其简单高效。\n\n**具体步骤**\n\n1. 将图中的每个节点看成一个独立的社区,因此社区的数目与节点个数相同\n2. 对于每个节点,尝试将该节点分配到其相邻节点所在的社区,观察其$\\bigtriangledown Q$,并记录其$\\bigtriangledown Q$最大相邻节点的社区,如果$\\bigtriangledown Q>0$,将该节点融入该社区\n3. 重复第二步直至所有节点所在的社团模块度不在变化\n4. 将所有社区压缩至一点节点,社区内节点之间的边的权重转化为新节点的环的权重,社区间的边权重转化为新节点间的边权重。\n5. 重复迭代直至收敛\n\n那为什么说Louvain算法收敛速度很快呢,是因为他是根据相邻节点进行计算的,不是从全局来进行计算的,并且越上层的时候收敛越快,并且可以按层获取对应的社团。\n\n## 算法不足和改进\n\n### 不足\n\n以模块度为目标函数的优化算法会存在一个分辨率限制的问题,即:无法发现社团数量小于$(N/2)^{1/2}$的社团,这对于一些小社团是不公平的。\n\n### 改进\n\n主要是增加分辨率的调整,具体可以参考[3](https://arxiv.org/abs/0812.1770),这也是在python-louvain这个包中的resolution参数的来源\n\n## 具体代码\n\n```python\nimport networkx as nx\nimport community\nimport pandas as pd\nimport os\n\nclass FastLouvain:\n def __init__(self, pair_path, resolution, logger=None):\n self.pair_path = pair_path\n self.resolution = resolution\n self.logger = logger\n\n def generate_graph(self):\n G = nx.read_edgelist(self.pair_path, create_using=nx.Graph(), nodetype=str, data=[('weight', int)])\n self.logger.info('node size :{}'.format(len(G)))\n self.G = G\n return G\n\n def best_community(self):\n self.logger.info('Start louvain training ……')\n partition = community.best_partition(self.G, resolution=self.resolution)\n cluster_label = set([x for x in partition.values()])\n self.logger.info(f'The number of cluster_label is {len(cluster_label)}')\n self.logger.info('Start calculate modularity_q')\n modularity_Q = community.modularity(partition, self.G)\n self.logger.info(f'modularity_Q {modularity_Q}')\n return partition\n\n def run(self):\n G = self.generate_graph()\n partition = self.best_community()\n return G,partition\n\n\nif __name__ == '__main__':\n pair_path = '../input/click_list/0908/normal_pair_click_seq_7.csv'\n resolution = 0.5\n fast_louvain = FastLouvain(pair_path,resolution)\n G,partition = fast_louvain.run()\n\n```\n\n## 参考\n\n- [博客1](https://qinystat.gitee.io/2020/01/22/Modularity/#1-1%E5%8E%9F%E5%A7%8B%E5%AE%9A%E4%B9%89-Q1)\n- [博客2](https://greatpowerlaw.wordpress.com/2013/02/24/community-detection-modularity/)\n- [博客3](https://blog.csdn.net/wangyibo0201/article/details/52048248)\n","source":"_posts/Louvain算法.md","raw":"---\ntitle: Louvain算法\ncategories:\n - 机器学习\ntags:\n - community detective\nmathjax: true\ndate: 2021-09-16 21:49:43\ndescription:\n---\n\n> \u0011利用商品的点击和购买,对商品进行聚类分析,因此选择了Louvain这个高效的聚类算法,再此主要是为了记录一下,方便日后回顾\n\n问题\n\n- 什么是模块度,其代表什么含义,公式推导\n- Louvain算法的精髓是什么,以前是怎么做社团发现的\n- 社团发现和广义聚类的区别\n- 为什么Louvain算法会存在resolution问题,后面是怎么解决的。\n\n## 模块度\n\nNewman[1](https://arxiv.org/pdf/cond-mat/0308217.pdf),2003年首次提出了第一版模块度,后在[2](https://www.pnas.org/content/pnas/103/23/8577.full.pdf),2006年提出了第二版模块度,经过多次修正后形成现在我们认知中的模块度。\n\n### 解释\n\n模块度有可以从两个角度解释,一种是较为宏观的表示簇内聚集度和簇外离散度的综合指标,另一种从数学角度认为是在给定组内的边的比例减去边是随机分布的期望分数,其具体的值属于【-0.5,1】。论文中认为0.3-0.8是较好模块度\n\n### 定义\n\n**第一版**\n\n假设网络被划分为 $k$ 个社区,那么定义一个$k×k$的对称矩阵$e$,它的元素 $e_{ij}$表示社区 $i$ 和社区 $j$ 之间的边的数量。矩阵的迹$Tre=\\sum e_{ii}$,也就表示了在相同社区内节点之间的边集合。显然,社区划分的好,也就是社区内部节点之间联系密集,那么该值就越高,这与我们通常对社区的认识是一致的。\n\n但是!如果不划分社区,直接将数据作为一个团,那他的Tre就是1,这是不合理的,因此又定义了一个$a_{i}=\\sum e_{ij}$,表示所有连接到社区ii的边数量。最后形成第一版的模块度函数\n$$\nQ=\\sum (e_{ii}-ai^{2})=Tre-\\left \\| e^{2}\\right \\|\n$$\n**第二版**\n\n为什么Newman要对模块度重新定义呢,因为第一版没有考虑节点度的概念,节点的度在一定程度上能够表示该节点被连接的概率,并且第二版矩阵形式可以应用在spectral optimization algorithms,具体参考[wiki](https://en.wikipedia.org/wiki/Modularity_(networks)#Matrix_formulation)。\n$$\nQ=\\frac{\\sum (A_{ij}-P_{ij})\\delta_{ij}}{2m}\n$$\n\n- 2m:所有节点的度数之和,为了计算的模块度不受m的规模影响\n- $A_{ij}/\\delta_{ij}$:节点的领结矩阵【不考虑有权那就是1,0】\n- $P_{ij}$:任意两个节点i和j连接的概率\n\n我们将$K_i$和$K_j$表示节点i和j的度,那么\n$$\nP_{ij}=\\frac{K_i*K_j}{2m}=K_i*\\frac{K_j}{2m}\n$$\n$K_j/2m$表示节点j被连接的概率,因此$P_{ij}$就表示节点i和j连接的概率。并且第一版和第二版本质上互通的,两者可以直接推导成一个公式。\n\n## 算法步骤\n\n讲完了模块度的概念,那我们知道了模块度是用于衡量一个社团结构好坏的指标,而Louvain算法就是基于该指标,利用迭代不断优化模块度,并且其简单高效。\n\n**具体步骤**\n\n1. 将图中的每个节点看成一个独立的社区,因此社区的数目与节点个数相同\n2. 对于每个节点,尝试将该节点分配到其相邻节点所在的社区,观察其$\\bigtriangledown Q$,并记录其$\\bigtriangledown Q$最大相邻节点的社区,如果$\\bigtriangledown Q>0$,将该节点融入该社区\n3. 重复第二步直至所有节点所在的社团模块度不在变化\n4. 将所有社区压缩至一点节点,社区内节点之间的边的权重转化为新节点的环的权重,社区间的边权重转化为新节点间的边权重。\n5. 重复迭代直至收敛\n\n那为什么说Louvain算法收敛速度很快呢,是因为他是根据相邻节点进行计算的,不是从全局来进行计算的,并且越上层的时候收敛越快,并且可以按层获取对应的社团。\n\n## 算法不足和改进\n\n### 不足\n\n以模块度为目标函数的优化算法会存在一个分辨率限制的问题,即:无法发现社团数量小于$(N/2)^{1/2}$的社团,这对于一些小社团是不公平的。\n\n### 改进\n\n主要是增加分辨率的调整,具体可以参考[3](https://arxiv.org/abs/0812.1770),这也是在python-louvain这个包中的resolution参数的来源\n\n## 具体代码\n\n```python\nimport networkx as nx\nimport community\nimport pandas as pd\nimport os\n\nclass FastLouvain:\n def __init__(self, pair_path, resolution, logger=None):\n self.pair_path = pair_path\n self.resolution = resolution\n self.logger = logger\n\n def generate_graph(self):\n G = nx.read_edgelist(self.pair_path, create_using=nx.Graph(), nodetype=str, data=[('weight', int)])\n self.logger.info('node size :{}'.format(len(G)))\n self.G = G\n return G\n\n def best_community(self):\n self.logger.info('Start louvain training ……')\n partition = community.best_partition(self.G, resolution=self.resolution)\n cluster_label = set([x for x in partition.values()])\n self.logger.info(f'The number of cluster_label is {len(cluster_label)}')\n self.logger.info('Start calculate modularity_q')\n modularity_Q = community.modularity(partition, self.G)\n self.logger.info(f'modularity_Q {modularity_Q}')\n return partition\n\n def run(self):\n G = self.generate_graph()\n partition = self.best_community()\n return G,partition\n\n\nif __name__ == '__main__':\n pair_path = '../input/click_list/0908/normal_pair_click_seq_7.csv'\n resolution = 0.5\n fast_louvain = FastLouvain(pair_path,resolution)\n G,partition = fast_louvain.run()\n\n```\n\n## 参考\n\n- [博客1](https://qinystat.gitee.io/2020/01/22/Modularity/#1-1%E5%8E%9F%E5%A7%8B%E5%AE%9A%E4%B9%89-Q1)\n- [博客2](https://greatpowerlaw.wordpress.com/2013/02/24/community-detection-modularity/)\n- [博客3](https://blog.csdn.net/wangyibo0201/article/details/52048248)\n","slug":"Louvain算法","published":1,"updated":"2021-09-18T08:43:45.913Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb4r0009so3w8gp62h0m","content":"<blockquote>\n<p>\u0011利用商品的点击和购买,对商品进行聚类分析,因此选择了Louvain这个高效的聚类算法,再此主要是为了记录一下,方便日后回顾</p>\n</blockquote>\n<p>问题</p>\n<ul>\n<li>什么是模块度,其代表什么含义,公式推导</li>\n<li>Louvain算法的精髓是什么,以前是怎么做社团发现的</li>\n<li>社团发现和广义聚类的区别</li>\n<li>为什么Louvain算法会存在resolution问题,后面是怎么解决的。</li>\n</ul>\n<h2 id=\"模块度\"><a href=\"#模块度\" class=\"headerlink\" title=\"模块度\"></a>模块度</h2><p>Newman<a href=\"https://arxiv.org/pdf/cond-mat/0308217.pdf\">1</a>,2003年首次提出了第一版模块度,后在<a href=\"https://www.pnas.org/content/pnas/103/23/8577.full.pdf\">2</a>,2006年提出了第二版模块度,经过多次修正后形成现在我们认知中的模块度。</p>\n<h3 id=\"解释\"><a href=\"#解释\" class=\"headerlink\" title=\"解释\"></a>解释</h3><p>模块度有可以从两个角度解释,一种是较为宏观的表示簇内聚集度和簇外离散度的综合指标,另一种从数学角度认为是在给定组内的边的比例减去边是随机分布的期望分数,其具体的值属于【-0.5,1】。论文中认为0.3-0.8是较好模块度</p>\n<h3 id=\"定义\"><a href=\"#定义\" class=\"headerlink\" title=\"定义\"></a>定义</h3><p><strong>第一版</strong></p>\n<p>假设网络被划分为 $k$ 个社区,那么定义一个$k×k$的对称矩阵$e$,它的元素 $e<em>{ij}$表示社区 $i$ 和社区 $j$ 之间的边的数量。矩阵的迹$Tre=\\sum e</em>{ii}$,也就表示了在相同社区内节点之间的边集合。显然,社区划分的好,也就是社区内部节点之间联系密集,那么该值就越高,这与我们通常对社区的认识是一致的。</p>\n<p>但是!如果不划分社区,直接将数据作为一个团,那他的Tre就是1,这是不合理的,因此又定义了一个$a<em>{i}=\\sum e</em>{ij}$,表示所有连接到社区ii的边数量。最后形成第一版的模块度函数</p>\n<script type=\"math/tex; mode=display\">\nQ=\\sum (e_{ii}-ai^{2})=Tre-\\left \\| e^{2}\\right \\|</script><p><strong>第二版</strong></p>\n<p>为什么Newman要对模块度重新定义呢,因为第一版没有考虑节点度的概念,节点的度在一定程度上能够表示该节点被连接的概率,并且第二版矩阵形式可以应用在spectral optimization algorithms,具体参考<a href=\"https://en.wikipedia.org/wiki/Modularity_(networks\">wiki</a>#Matrix_formulation)。</p>\n<script type=\"math/tex; mode=display\">\nQ=\\frac{\\sum (A_{ij}-P_{ij})\\delta_{ij}}{2m}</script><ul>\n<li>2m:所有节点的度数之和,为了计算的模块度不受m的规模影响</li>\n<li>$A<em>{ij}/\\delta</em>{ij}$:节点的领结矩阵【不考虑有权那就是1,0】</li>\n<li>$P_{ij}$:任意两个节点i和j连接的概率</li>\n</ul>\n<p>我们将$K_i$和$K_j$表示节点i和j的度,那么</p>\n<script type=\"math/tex; mode=display\">\nP_{ij}=\\frac{K_i*K_j}{2m}=K_i*\\frac{K_j}{2m}</script><p>$K<em>j/2m$表示节点j被连接的概率,因此$P</em>{ij}$就表示节点i和j连接的概率。并且第一版和第二版本质上互通的,两者可以直接推导成一个公式。</p>\n<h2 id=\"算法步骤\"><a href=\"#算法步骤\" class=\"headerlink\" title=\"算法步骤\"></a>算法步骤</h2><p>讲完了模块度的概念,那我们知道了模块度是用于衡量一个社团结构好坏的指标,而Louvain算法就是基于该指标,利用迭代不断优化模块度,并且其简单高效。</p>\n<p><strong>具体步骤</strong></p>\n<ol>\n<li>将图中的每个节点看成一个独立的社区,因此社区的数目与节点个数相同</li>\n<li>对于每个节点,尝试将该节点分配到其相邻节点所在的社区,观察其$\\bigtriangledown Q$,并记录其$\\bigtriangledown Q$最大相邻节点的社区,如果$\\bigtriangledown Q>0$,将该节点融入该社区</li>\n<li>重复第二步直至所有节点所在的社团模块度不在变化</li>\n<li>将所有社区压缩至一点节点,社区内节点之间的边的权重转化为新节点的环的权重,社区间的边权重转化为新节点间的边权重。</li>\n<li>重复迭代直至收敛</li>\n</ol>\n<p>那为什么说Louvain算法收敛速度很快呢,是因为他是根据相邻节点进行计算的,不是从全局来进行计算的,并且越上层的时候收敛越快,并且可以按层获取对应的社团。</p>\n<h2 id=\"算法不足和改进\"><a href=\"#算法不足和改进\" class=\"headerlink\" title=\"算法不足和改进\"></a>算法不足和改进</h2><h3 id=\"不足\"><a href=\"#不足\" class=\"headerlink\" title=\"不足\"></a>不足</h3><p>以模块度为目标函数的优化算法会存在一个分辨率限制的问题,即:无法发现社团数量小于$(N/2)^{1/2}$的社团,这对于一些小社团是不公平的。</p>\n<h3 id=\"改进\"><a href=\"#改进\" class=\"headerlink\" title=\"改进\"></a>改进</h3><p>主要是增加分辨率的调整,具体可以参考<a href=\"https://arxiv.org/abs/0812.1770\">3</a>,这也是在python-louvain这个包中的resolution参数的来源</p>\n<h2 id=\"具体代码\"><a href=\"#具体代码\" class=\"headerlink\" title=\"具体代码\"></a>具体代码</h2><figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> networkx <span class=\"keyword\">as</span> nx</span><br><span class=\"line\"><span class=\"keyword\">import</span> community</span><br><span class=\"line\"><span class=\"keyword\">import</span> pandas <span class=\"keyword\">as</span> pd</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">FastLouvain</span>:</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, pair_path, resolution, logger=<span class=\"literal\">None</span></span>):</span></span><br><span class=\"line\"> self.pair_path = pair_path</span><br><span class=\"line\"> self.resolution = resolution</span><br><span class=\"line\"> self.logger = logger</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">generate_graph</span>(<span class=\"params\">self</span>):</span></span><br><span class=\"line\"> G = nx.read_edgelist(self.pair_path, create_using=nx.Graph(), nodetype=<span class=\"built_in\">str</span>, data=[(<span class=\"string\">'weight'</span>, <span class=\"built_in\">int</span>)])</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">'node size :{}'</span>.<span class=\"built_in\">format</span>(<span class=\"built_in\">len</span>(G)))</span><br><span class=\"line\"> self.G = G</span><br><span class=\"line\"> <span class=\"keyword\">return</span> G</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">best_community</span>(<span class=\"params\">self</span>):</span></span><br><span class=\"line\"> self.logger.info(<span class=\"string\">'Start louvain training ……'</span>)</span><br><span class=\"line\"> partition = community.best_partition(self.G, resolution=self.resolution)</span><br><span class=\"line\"> cluster_label = <span class=\"built_in\">set</span>([x <span class=\"keyword\">for</span> x <span class=\"keyword\">in</span> partition.values()])</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">f'The number of cluster_label is <span class=\"subst\">{<span class=\"built_in\">len</span>(cluster_label)}</span>'</span>)</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">'Start calculate modularity_q'</span>)</span><br><span class=\"line\"> modularity_Q = community.modularity(partition, self.G)</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">f'modularity_Q <span class=\"subst\">{modularity_Q}</span>'</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> partition</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">run</span>(<span class=\"params\">self</span>):</span></span><br><span class=\"line\"> G = self.generate_graph()</span><br><span class=\"line\"> partition = self.best_community()</span><br><span class=\"line\"> <span class=\"keyword\">return</span> G,partition</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> pair_path = <span class=\"string\">'../input/click_list/0908/normal_pair_click_seq_7.csv'</span></span><br><span class=\"line\"> resolution = <span class=\"number\">0.5</span></span><br><span class=\"line\"> fast_louvain = FastLouvain(pair_path,resolution)</span><br><span class=\"line\"> G,partition = fast_louvain.run()</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h2 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h2><ul>\n<li><a href=\"https://qinystat.gitee.io/2020/01/22/Modularity/#1-1%E5%8E%9F%E5%A7%8B%E5%AE%9A%E4%B9%89-Q1\">博客1</a></li>\n<li><a href=\"https://greatpowerlaw.wordpress.com/2013/02/24/community-detection-modularity/\">博客2</a></li>\n<li><a href=\"https://blog.csdn.net/wangyibo0201/article/details/52048248\">博客3</a></li>\n</ul>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<blockquote>\n<p>\u0011利用商品的点击和购买,对商品进行聚类分析,因此选择了Louvain这个高效的聚类算法,再此主要是为了记录一下,方便日后回顾</p>\n</blockquote>\n<p>问题</p>\n<ul>\n<li>什么是模块度,其代表什么含义,公式推导</li>\n<li>Louvain算法的精髓是什么,以前是怎么做社团发现的</li>\n<li>社团发现和广义聚类的区别</li>\n<li>为什么Louvain算法会存在resolution问题,后面是怎么解决的。</li>\n</ul>\n<h2 id=\"模块度\"><a href=\"#模块度\" class=\"headerlink\" title=\"模块度\"></a>模块度</h2><p>Newman<a href=\"https://arxiv.org/pdf/cond-mat/0308217.pdf\">1</a>,2003年首次提出了第一版模块度,后在<a href=\"https://www.pnas.org/content/pnas/103/23/8577.full.pdf\">2</a>,2006年提出了第二版模块度,经过多次修正后形成现在我们认知中的模块度。</p>\n<h3 id=\"解释\"><a href=\"#解释\" class=\"headerlink\" title=\"解释\"></a>解释</h3><p>模块度有可以从两个角度解释,一种是较为宏观的表示簇内聚集度和簇外离散度的综合指标,另一种从数学角度认为是在给定组内的边的比例减去边是随机分布的期望分数,其具体的值属于【-0.5,1】。论文中认为0.3-0.8是较好模块度</p>\n<h3 id=\"定义\"><a href=\"#定义\" class=\"headerlink\" title=\"定义\"></a>定义</h3><p><strong>第一版</strong></p>\n<p>假设网络被划分为 $k$ 个社区,那么定义一个$k×k$的对称矩阵$e$,它的元素 $e<em>{ij}$表示社区 $i$ 和社区 $j$ 之间的边的数量。矩阵的迹$Tre=\\sum e</em>{ii}$,也就表示了在相同社区内节点之间的边集合。显然,社区划分的好,也就是社区内部节点之间联系密集,那么该值就越高,这与我们通常对社区的认识是一致的。</p>\n<p>但是!如果不划分社区,直接将数据作为一个团,那他的Tre就是1,这是不合理的,因此又定义了一个$a<em>{i}=\\sum e</em>{ij}$,表示所有连接到社区ii的边数量。最后形成第一版的模块度函数</p>\n<script type=\"math/tex; mode=display\">\nQ=\\sum (e_{ii}-ai^{2})=Tre-\\left \\| e^{2}\\right \\|</script><p><strong>第二版</strong></p>\n<p>为什么Newman要对模块度重新定义呢,因为第一版没有考虑节点度的概念,节点的度在一定程度上能够表示该节点被连接的概率,并且第二版矩阵形式可以应用在spectral optimization algorithms,具体参考<a href=\"https://en.wikipedia.org/wiki/Modularity_(networks\">wiki</a>#Matrix_formulation)。</p>\n<script type=\"math/tex; mode=display\">\nQ=\\frac{\\sum (A_{ij}-P_{ij})\\delta_{ij}}{2m}</script><ul>\n<li>2m:所有节点的度数之和,为了计算的模块度不受m的规模影响</li>\n<li>$A<em>{ij}/\\delta</em>{ij}$:节点的领结矩阵【不考虑有权那就是1,0】</li>\n<li>$P_{ij}$:任意两个节点i和j连接的概率</li>\n</ul>\n<p>我们将$K_i$和$K_j$表示节点i和j的度,那么</p>\n<script type=\"math/tex; mode=display\">\nP_{ij}=\\frac{K_i*K_j}{2m}=K_i*\\frac{K_j}{2m}</script><p>$K<em>j/2m$表示节点j被连接的概率,因此$P</em>{ij}$就表示节点i和j连接的概率。并且第一版和第二版本质上互通的,两者可以直接推导成一个公式。</p>\n<h2 id=\"算法步骤\"><a href=\"#算法步骤\" class=\"headerlink\" title=\"算法步骤\"></a>算法步骤</h2><p>讲完了模块度的概念,那我们知道了模块度是用于衡量一个社团结构好坏的指标,而Louvain算法就是基于该指标,利用迭代不断优化模块度,并且其简单高效。</p>\n<p><strong>具体步骤</strong></p>\n<ol>\n<li>将图中的每个节点看成一个独立的社区,因此社区的数目与节点个数相同</li>\n<li>对于每个节点,尝试将该节点分配到其相邻节点所在的社区,观察其$\\bigtriangledown Q$,并记录其$\\bigtriangledown Q$最大相邻节点的社区,如果$\\bigtriangledown Q>0$,将该节点融入该社区</li>\n<li>重复第二步直至所有节点所在的社团模块度不在变化</li>\n<li>将所有社区压缩至一点节点,社区内节点之间的边的权重转化为新节点的环的权重,社区间的边权重转化为新节点间的边权重。</li>\n<li>重复迭代直至收敛</li>\n</ol>\n<p>那为什么说Louvain算法收敛速度很快呢,是因为他是根据相邻节点进行计算的,不是从全局来进行计算的,并且越上层的时候收敛越快,并且可以按层获取对应的社团。</p>\n<h2 id=\"算法不足和改进\"><a href=\"#算法不足和改进\" class=\"headerlink\" title=\"算法不足和改进\"></a>算法不足和改进</h2><h3 id=\"不足\"><a href=\"#不足\" class=\"headerlink\" title=\"不足\"></a>不足</h3><p>以模块度为目标函数的优化算法会存在一个分辨率限制的问题,即:无法发现社团数量小于$(N/2)^{1/2}$的社团,这对于一些小社团是不公平的。</p>\n<h3 id=\"改进\"><a href=\"#改进\" class=\"headerlink\" title=\"改进\"></a>改进</h3><p>主要是增加分辨率的调整,具体可以参考<a href=\"https://arxiv.org/abs/0812.1770\">3</a>,这也是在python-louvain这个包中的resolution参数的来源</p>\n<h2 id=\"具体代码\"><a href=\"#具体代码\" class=\"headerlink\" title=\"具体代码\"></a>具体代码</h2><figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> networkx <span class=\"keyword\">as</span> nx</span><br><span class=\"line\"><span class=\"keyword\">import</span> community</span><br><span class=\"line\"><span class=\"keyword\">import</span> pandas <span class=\"keyword\">as</span> pd</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">FastLouvain</span>:</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, pair_path, resolution, logger=<span class=\"literal\">None</span></span>):</span></span><br><span class=\"line\"> self.pair_path = pair_path</span><br><span class=\"line\"> self.resolution = resolution</span><br><span class=\"line\"> self.logger = logger</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">generate_graph</span>(<span class=\"params\">self</span>):</span></span><br><span class=\"line\"> G = nx.read_edgelist(self.pair_path, create_using=nx.Graph(), nodetype=<span class=\"built_in\">str</span>, data=[(<span class=\"string\">'weight'</span>, <span class=\"built_in\">int</span>)])</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">'node size :{}'</span>.<span class=\"built_in\">format</span>(<span class=\"built_in\">len</span>(G)))</span><br><span class=\"line\"> self.G = G</span><br><span class=\"line\"> <span class=\"keyword\">return</span> G</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">best_community</span>(<span class=\"params\">self</span>):</span></span><br><span class=\"line\"> self.logger.info(<span class=\"string\">'Start louvain training ……'</span>)</span><br><span class=\"line\"> partition = community.best_partition(self.G, resolution=self.resolution)</span><br><span class=\"line\"> cluster_label = <span class=\"built_in\">set</span>([x <span class=\"keyword\">for</span> x <span class=\"keyword\">in</span> partition.values()])</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">f'The number of cluster_label is <span class=\"subst\">{<span class=\"built_in\">len</span>(cluster_label)}</span>'</span>)</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">'Start calculate modularity_q'</span>)</span><br><span class=\"line\"> modularity_Q = community.modularity(partition, self.G)</span><br><span class=\"line\"> self.logger.info(<span class=\"string\">f'modularity_Q <span class=\"subst\">{modularity_Q}</span>'</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> partition</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">run</span>(<span class=\"params\">self</span>):</span></span><br><span class=\"line\"> G = self.generate_graph()</span><br><span class=\"line\"> partition = self.best_community()</span><br><span class=\"line\"> <span class=\"keyword\">return</span> G,partition</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> pair_path = <span class=\"string\">'../input/click_list/0908/normal_pair_click_seq_7.csv'</span></span><br><span class=\"line\"> resolution = <span class=\"number\">0.5</span></span><br><span class=\"line\"> fast_louvain = FastLouvain(pair_path,resolution)</span><br><span class=\"line\"> G,partition = fast_louvain.run()</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h2 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h2><ul>\n<li><a href=\"https://qinystat.gitee.io/2020/01/22/Modularity/#1-1%E5%8E%9F%E5%A7%8B%E5%AE%9A%E4%B9%89-Q1\">博客1</a></li>\n<li><a href=\"https://greatpowerlaw.wordpress.com/2013/02/24/community-detection-modularity/\">博客2</a></li>\n<li><a href=\"https://blog.csdn.net/wangyibo0201/article/details/52048248\">博客3</a></li>\n</ul>\n"},{"title":"NLP预处理:词干提取和词性还原","date":"2021-06-04T06:45:54.000Z","description":null,"_content":"\n> 最近在做的项目里面需要做一个关键词多属性提取,然后由于以前接触的都是中文的项目,所以就会拿中文分词的一些操作来用,但在英文的项目,其实就有词干提取和词性还原两种非常好用的方法\n\n自然语言处理中一个很重要的操作就是所谓的stemming 和 lemmatization,二者非常类似。它们是词形规范化的两类重要方式,都能够达到有效归并词形的目的,二者既有联系也有区别。\n\n## 词干提取\n\nNLTK中提供了三种最常用的词干提取器接口,即 Porter stemmer, Lancaster Stemmer 和 Snowball Stemmer。而且词干提取主要是基于规则的算法。\n\n**Porter**\n\n1980年最原始的词干提取法\n\n```python\nfrom nltk.stem.porter import PorterStemmer\ntext='chineses'\nporter_stemmer = PorterStemmer()\nstem_text = porter_stemmer.stem(text)\nprint(stem_text)\n--------------------\nOut[148]: 'chines'\n```\n\n**Snowball**\n\n也称为Porter2.0,本人说提取的比1好,这个不清楚,但确实相比较1.0会快一些,感觉上效率有提升1/3.\n\n```python\nfrom nltk.stem.snowball import EnglishStemmer\ntext='chineses'\nporter_stemmer = PorterStemmer()\nstem_text = porter_stemmer.stem(text)\nprint(stem_text)\n--------------------\nOut[148]: ‘chines’\n```\n\n**Lancaster**\n\n据说比较激进,没什么人用过\n\n```python\nfrom nltk.stem import LancasterStemmer\ntext='chineses'\nlancas_stemmer = LancasterStemmer()\nstem_text = lancas_stemmer.stem(text)\nprint(stem_text)\n--------------------\nOut[148]: ‘chines’\n```\n\n## 词性还原\n\n基于字典的映射,而且在NLTK中要求标明词性,否则会出问题。一般需要先走词性标注,再走词性还原,因此整体链路相较于词干提取较长。\n\n```python\nfrom nltk.stem import WordNetLemmatizer \nlemmatizer = WordNetLemmatizer() \nword = lemmatizer.lemmatize('leaves',pos='n') \nprint(word)\n--------------------\nOut[148]: 'leaf'\n```\n\n","source":"_posts/NLP预处理:词干提取和词性还原.md","raw":"---\ntitle: NLP预处理:词干提取和词性还原\ncategories:\n - 特征工程\ntags:\n - NLP\ndate: 2021-06-04 14:45:54\ndescription:\n---\n\n> 最近在做的项目里面需要做一个关键词多属性提取,然后由于以前接触的都是中文的项目,所以就会拿中文分词的一些操作来用,但在英文的项目,其实就有词干提取和词性还原两种非常好用的方法\n\n自然语言处理中一个很重要的操作就是所谓的stemming 和 lemmatization,二者非常类似。它们是词形规范化的两类重要方式,都能够达到有效归并词形的目的,二者既有联系也有区别。\n\n## 词干提取\n\nNLTK中提供了三种最常用的词干提取器接口,即 Porter stemmer, Lancaster Stemmer 和 Snowball Stemmer。而且词干提取主要是基于规则的算法。\n\n**Porter**\n\n1980年最原始的词干提取法\n\n```python\nfrom nltk.stem.porter import PorterStemmer\ntext='chineses'\nporter_stemmer = PorterStemmer()\nstem_text = porter_stemmer.stem(text)\nprint(stem_text)\n--------------------\nOut[148]: 'chines'\n```\n\n**Snowball**\n\n也称为Porter2.0,本人说提取的比1好,这个不清楚,但确实相比较1.0会快一些,感觉上效率有提升1/3.\n\n```python\nfrom nltk.stem.snowball import EnglishStemmer\ntext='chineses'\nporter_stemmer = PorterStemmer()\nstem_text = porter_stemmer.stem(text)\nprint(stem_text)\n--------------------\nOut[148]: ‘chines’\n```\n\n**Lancaster**\n\n据说比较激进,没什么人用过\n\n```python\nfrom nltk.stem import LancasterStemmer\ntext='chineses'\nlancas_stemmer = LancasterStemmer()\nstem_text = lancas_stemmer.stem(text)\nprint(stem_text)\n--------------------\nOut[148]: ‘chines’\n```\n\n## 词性还原\n\n基于字典的映射,而且在NLTK中要求标明词性,否则会出问题。一般需要先走词性标注,再走词性还原,因此整体链路相较于词干提取较长。\n\n```python\nfrom nltk.stem import WordNetLemmatizer \nlemmatizer = WordNetLemmatizer() \nword = lemmatizer.lemmatize('leaves',pos='n') \nprint(word)\n--------------------\nOut[148]: 'leaf'\n```\n\n","slug":"NLP预处理:词干提取和词性还原","published":1,"updated":"2021-09-18T08:43:45.914Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb4w000dso3w9h7g7i8d","content":"<blockquote>\n<p>最近在做的项目里面需要做一个关键词多属性提取,然后由于以前接触的都是中文的项目,所以就会拿中文分词的一些操作来用,但在英文的项目,其实就有词干提取和词性还原两种非常好用的方法</p>\n</blockquote>\n<p>自然语言处理中一个很重要的操作就是所谓的stemming 和 lemmatization,二者非常类似。它们是词形规范化的两类重要方式,都能够达到有效归并词形的目的,二者既有联系也有区别。</p>\n<h2 id=\"词干提取\"><a href=\"#词干提取\" class=\"headerlink\" title=\"词干提取\"></a>词干提取</h2><p>NLTK中提供了三种最常用的词干提取器接口,即 Porter stemmer, Lancaster Stemmer 和 Snowball Stemmer。而且词干提取主要是基于规则的算法。</p>\n<p><strong>Porter</strong></p>\n<p>1980年最原始的词干提取法</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem.porter <span class=\"keyword\">import</span> PorterStemmer</span><br><span class=\"line\">text=<span class=\"string\">'chineses'</span></span><br><span class=\"line\">porter_stemmer = PorterStemmer()</span><br><span class=\"line\">stem_text = porter_stemmer.stem(text)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(stem_text)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: <span class=\"string\">'chines'</span></span><br></pre></td></tr></table></figure>\n<p><strong>Snowball</strong></p>\n<p>也称为Porter2.0,本人说提取的比1好,这个不清楚,但确实相比较1.0会快一些,感觉上效率有提升1/3.</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem.snowball <span class=\"keyword\">import</span> EnglishStemmer</span><br><span class=\"line\">text=<span class=\"string\">'chineses'</span></span><br><span class=\"line\">porter_stemmer = PorterStemmer()</span><br><span class=\"line\">stem_text = porter_stemmer.stem(text)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(stem_text)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: ‘chines’</span><br></pre></td></tr></table></figure>\n<p><strong>Lancaster</strong></p>\n<p>据说比较激进,没什么人用过</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem <span class=\"keyword\">import</span> LancasterStemmer</span><br><span class=\"line\">text=<span class=\"string\">'chineses'</span></span><br><span class=\"line\">lancas_stemmer = LancasterStemmer()</span><br><span class=\"line\">stem_text = lancas_stemmer.stem(text)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(stem_text)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: ‘chines’</span><br></pre></td></tr></table></figure>\n<h2 id=\"词性还原\"><a href=\"#词性还原\" class=\"headerlink\" title=\"词性还原\"></a>词性还原</h2><p>基于字典的映射,而且在NLTK中要求标明词性,否则会出问题。一般需要先走词性标注,再走词性还原,因此整体链路相较于词干提取较长。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem <span class=\"keyword\">import</span> WordNetLemmatizer </span><br><span class=\"line\">lemmatizer = WordNetLemmatizer() </span><br><span class=\"line\">word = lemmatizer.lemmatize(<span class=\"string\">'leaves'</span>,pos=<span class=\"string\">'n'</span>) </span><br><span class=\"line\"><span class=\"built_in\">print</span>(word)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: <span class=\"string\">'leaf'</span></span><br></pre></td></tr></table></figure>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<blockquote>\n<p>最近在做的项目里面需要做一个关键词多属性提取,然后由于以前接触的都是中文的项目,所以就会拿中文分词的一些操作来用,但在英文的项目,其实就有词干提取和词性还原两种非常好用的方法</p>\n</blockquote>\n<p>自然语言处理中一个很重要的操作就是所谓的stemming 和 lemmatization,二者非常类似。它们是词形规范化的两类重要方式,都能够达到有效归并词形的目的,二者既有联系也有区别。</p>\n<h2 id=\"词干提取\"><a href=\"#词干提取\" class=\"headerlink\" title=\"词干提取\"></a>词干提取</h2><p>NLTK中提供了三种最常用的词干提取器接口,即 Porter stemmer, Lancaster Stemmer 和 Snowball Stemmer。而且词干提取主要是基于规则的算法。</p>\n<p><strong>Porter</strong></p>\n<p>1980年最原始的词干提取法</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem.porter <span class=\"keyword\">import</span> PorterStemmer</span><br><span class=\"line\">text=<span class=\"string\">'chineses'</span></span><br><span class=\"line\">porter_stemmer = PorterStemmer()</span><br><span class=\"line\">stem_text = porter_stemmer.stem(text)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(stem_text)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: <span class=\"string\">'chines'</span></span><br></pre></td></tr></table></figure>\n<p><strong>Snowball</strong></p>\n<p>也称为Porter2.0,本人说提取的比1好,这个不清楚,但确实相比较1.0会快一些,感觉上效率有提升1/3.</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem.snowball <span class=\"keyword\">import</span> EnglishStemmer</span><br><span class=\"line\">text=<span class=\"string\">'chineses'</span></span><br><span class=\"line\">porter_stemmer = PorterStemmer()</span><br><span class=\"line\">stem_text = porter_stemmer.stem(text)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(stem_text)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: ‘chines’</span><br></pre></td></tr></table></figure>\n<p><strong>Lancaster</strong></p>\n<p>据说比较激进,没什么人用过</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem <span class=\"keyword\">import</span> LancasterStemmer</span><br><span class=\"line\">text=<span class=\"string\">'chineses'</span></span><br><span class=\"line\">lancas_stemmer = LancasterStemmer()</span><br><span class=\"line\">stem_text = lancas_stemmer.stem(text)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(stem_text)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: ‘chines’</span><br></pre></td></tr></table></figure>\n<h2 id=\"词性还原\"><a href=\"#词性还原\" class=\"headerlink\" title=\"词性还原\"></a>词性还原</h2><p>基于字典的映射,而且在NLTK中要求标明词性,否则会出问题。一般需要先走词性标注,再走词性还原,因此整体链路相较于词干提取较长。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> nltk.stem <span class=\"keyword\">import</span> WordNetLemmatizer </span><br><span class=\"line\">lemmatizer = WordNetLemmatizer() </span><br><span class=\"line\">word = lemmatizer.lemmatize(<span class=\"string\">'leaves'</span>,pos=<span class=\"string\">'n'</span>) </span><br><span class=\"line\"><span class=\"built_in\">print</span>(word)</span><br><span class=\"line\">--------------------</span><br><span class=\"line\">Out[<span class=\"number\">148</span>]: <span class=\"string\">'leaf'</span></span><br></pre></td></tr></table></figure>\n"},{"title":"万物皆可embedding","mathjax":true,"date":"2022-01-25T06:43:15.000Z","description":null,"_content":"\n# 万物皆可embedding\n\n# 前言\n\n\t本文的目的是想通过这次分享,让可爱学长们能够了解在算法人员口中的embedding到底是什么,它是如何作用在我们算法的日常工作中。\n\n# 背景\n\n引入embedding之前,需要先讲一下embedding的前身技术,**one-hot编码**。\n\n## one-hot\n\n\t在机器学习里,存在one-hot向量(英语:one-hot vector)的概念。在一任意维度的向量中,仅有一个维度的值是1,其余为0。譬如向量[0,0,1,0,0],即为5维空间中的一组one-hot向量。将类别型数据转换成one-hot向量的过程则称one-hot编码(英语:one-hot encoding。在统计学中,one-hot我们一般叫做虚拟变量。\n\n\n\n**优势**\n\n1. 可以将类别型数据转化为向量形式,各类别在高维空间中距离相等\n2. 进行损失函数(例如交叉熵损失)或准确率计算时,变得非常方便\n\n**劣势**\n\n1. 当类别数量过于庞杂时,会导致维度灾难\n2. 不能很好的刻画词与词之间的相似性\n3. 强稀疏性\n\n**备注**\n\n- sklearn包:sklearn.preprocessing.OneHotEncoder\n- pandas包:pandas.get_dummies\n\n## embedding\n\n\t为了克服上述缺点,embedding技术就横空出世,最开始应用于**NLP的词表达**中,其最早时在1986年Hinton提出的,其**基本想法**是:\n\n\t通过训练将某种语言中的每一个词 映射成一个固定长度的短向量(当然这里的“短”是相对于One-Hot的“长”而言的),所有这些向量构成一个词向量空间,而每一个向量则可视为 该空间中的一个点,在这个空间上引入“距离”,就可以根据词之间的距离来判断它们之间的语法、语义上的相似性。\n\n\t真正让embedding技术发扬光大的是谷歌Tomas Mikolov团队的Word2Vec工具。该工具效率很高并且在语义表示上具有很好的效果。\t\t\n\n\t时至今日,embedding技术延伸至生活的方方面面,现在我们可以用embedding技术去表达一部电影,一个用户,一个商品等等。\n\n\t说了这么多,那到底什么是embedding技术,一句话概括:一种基于**低维向量**来表示物品的技术我们称之为embedding技术。\n\n# Word2vec\n\nword2vec模型的目的并不是训练词embedding,词embedding只是模型的附带品!!!\n\n\n\n**模型结构**\n\n1. 两层浅层神经网络\n2. 输入单词,输出预测单词的概率\n\n## 两种训练模式\n\n\n\n### CBOW\n\n\n\n通过上下文来预测当前值。相当于一句话中扣掉一个词,让你猜这个词是什么。\n\n### Skip-gram\n\n\n\n用当前词来预测上下文。相当于给你一个词,让你猜前面和后面可能出现什么词。\n\n## 两种优化方式\n\n前文说了,Word2vec的输出是预测单词的概率,这是利用Softmax函数做归一化去处理的,那我们来看看softmax是什么。\n$$\nS_{i}=\\frac{e^{i}}{\\Sigma_{j} e^{j}}\n$$\n那我们看到Softmax需要经历指数计算,指数计算在计算机运算中效率并不高,并且还需要计算全部单词的概率,当单词量级很高的时候,效率就会非常低,因为针对训练提出了两种优化方式:层序softmax和负采样。\n\n### Hierarchical Softmax\n\n利用[霍夫曼树](https://www.cnblogs.com/pinard/p/7160330.html)代替从隐藏层到输出softmax层的映射,霍夫曼树简单来说就是:权重【频率】越低的单词优先合并成根节点的原则生成一整颗二叉树,大家可以看下图\n\n\n\n如果输入样本输出的真实值是W2,那么Hierarchical Softmax利用逻辑回归分类器一层一层做而分类,综合每一层的逻辑回归作为目标函数进行训练。(层序softmax就是一层一层做分类得来的)\n\n**优点**\n\n1. 既然是二叉树,那计算量就从V变成了logV。\n2. 针对高频词,路径越短,计算越快。\n\n**缺点**\n\n当中心词是偏僻词时,路径会很长,训练时间提高\n\n### Negative Sampling\n\nNegative Sampling(负采样)的原理就比较简单,事先选择5-20个单词作为负样本,将softmax函数改为sigmod函数,直接由多分类作为二分类来使用。(至于负采样的个数,利用什么样的策略采样本文不予展开)\n\n**优点**\n\n1. 不用构造霍夫曼树,只需要维护一个负样本数据集\n2. 将softmax修改为sigmod函数,只需要改变第二层神经网络的少量参数,大大提升了效率\n\n## 优缺点\n\n**优点**\n\n1. 经过速度优化以后,训练词向量的速度很快\n2. 通用性很强,可以用在各种 NLP 任务中\n3. 由于考虑上下文,因此效果再18年以前一直都是Embedding技术的王牌\n\n**缺点**\n\n1. 由于词和向量是一对一的关系,所以多义词的问题无法解决\n2. Word2vec 是一种静态的方式,虽然通用性强,但是无法针对特定任务做动态优化\n\n## 问题\n\n1. 为什么要用CBOW和Skip-gram两种方式来训练,而不直接用传统DNN来训练?\n2. CBOW和Skip-gram分别有什么优劣势?\n\n# 应用\n\n## Word embedding及其衍生\n\n用于表示单词于单词之间的相关性【基础任务】\n\n- Word2vec【2013】:NLP中词向量训练的鼻祖\n- Fasttext【2014】:Word2vec基础上引入了字符级别的词向量,对德语,俄语更友好\n- ELMO【2018】:解决了一次多义的词向量模型\n- Embedding layer【Bert时代】:基于Bert后时代的模型,词向量已经不作为单独训练的目的,而是直接作为输入的一层嵌入模型中,跟随任务目标的变化而变化\n- Item2vec【2016】:微软将word2vec应用于推荐领域的一篇实用性很强的文章,主要用于训练item embedding,但更重要的是使其从NLP领域直接扩展到推荐、广告、搜索等任何可以生成sequence的领域。\n- Airbnb Embedding【2018】:Airbnb将word2vec用于推荐的典型文章,主要用于训练user embedding,很多user look alike系统的user embedding都是这么训练来的。\n\n## Sentence embedding\n\n用于表示句子于句子之间的相关性【标题召回】\n\n- Doc2vec【2014】:在Word2vec的基础上引入一个句向量概念,随着模型一起训练\n- Sentence_Bert【2019】:以Bert为基础,用有监督数据训练词向量\n- SimCSE/ConSert/EsimCSE【2021】:对比学习方法,利用Sentence_Bert进行自监督训练\n\n## Graph embedding\n\n节点映射为向量表示的时候尽可能多地保留图的拓扑信息【社区关系】\n\n- Deepwalk/Line/Node2Vec:基于Skip-gram的节点向量训练模型,区别在于他的输入需要用不同的遍历方式构造序列\n- SDNE【2016】:基于自编码器的半监督Graph embedding训练模型,可以看作Line的一种延伸\n- GCN/GraphSAGE/GAT/GIN【GCN时代】:GCN类模型,现阶段Graph embedding效果最好的模型\n- EGES【2018】:很好用的item emebdding训练模型,其原理就是deepwalk+不同的sideinfo形成商品embedding用于推荐搜索\n\n# 问题\n\n1. 什么是Embedding?\n2. Embedding如何作用在我们算法工作?\n\n# 参考\n\n[one-hot](https://zh.wikipedia.org/wiki/One-hot)\n\n[paper list](https://github.com/wzhe06/Reco-papers/tree/master/Embedding)\n\n","source":"_posts/万物皆可embedding.md","raw":"---\ntitle: 万物皆可embedding\ncategories:\n - 深度学习\ntags:\n - NLP\nmathjax: true\ndate: 2022-01-25 14:43:15\ndescription:\n---\n\n# 万物皆可embedding\n\n# 前言\n\n\t本文的目的是想通过这次分享,让可爱学长们能够了解在算法人员口中的embedding到底是什么,它是如何作用在我们算法的日常工作中。\n\n# 背景\n\n引入embedding之前,需要先讲一下embedding的前身技术,**one-hot编码**。\n\n## one-hot\n\n\t在机器学习里,存在one-hot向量(英语:one-hot vector)的概念。在一任意维度的向量中,仅有一个维度的值是1,其余为0。譬如向量[0,0,1,0,0],即为5维空间中的一组one-hot向量。将类别型数据转换成one-hot向量的过程则称one-hot编码(英语:one-hot encoding。在统计学中,one-hot我们一般叫做虚拟变量。\n\n\n\n**优势**\n\n1. 可以将类别型数据转化为向量形式,各类别在高维空间中距离相等\n2. 进行损失函数(例如交叉熵损失)或准确率计算时,变得非常方便\n\n**劣势**\n\n1. 当类别数量过于庞杂时,会导致维度灾难\n2. 不能很好的刻画词与词之间的相似性\n3. 强稀疏性\n\n**备注**\n\n- sklearn包:sklearn.preprocessing.OneHotEncoder\n- pandas包:pandas.get_dummies\n\n## embedding\n\n\t为了克服上述缺点,embedding技术就横空出世,最开始应用于**NLP的词表达**中,其最早时在1986年Hinton提出的,其**基本想法**是:\n\n\t通过训练将某种语言中的每一个词 映射成一个固定长度的短向量(当然这里的“短”是相对于One-Hot的“长”而言的),所有这些向量构成一个词向量空间,而每一个向量则可视为 该空间中的一个点,在这个空间上引入“距离”,就可以根据词之间的距离来判断它们之间的语法、语义上的相似性。\n\n\t真正让embedding技术发扬光大的是谷歌Tomas Mikolov团队的Word2Vec工具。该工具效率很高并且在语义表示上具有很好的效果。\t\t\n\n\t时至今日,embedding技术延伸至生活的方方面面,现在我们可以用embedding技术去表达一部电影,一个用户,一个商品等等。\n\n\t说了这么多,那到底什么是embedding技术,一句话概括:一种基于**低维向量**来表示物品的技术我们称之为embedding技术。\n\n# Word2vec\n\nword2vec模型的目的并不是训练词embedding,词embedding只是模型的附带品!!!\n\n\n\n**模型结构**\n\n1. 两层浅层神经网络\n2. 输入单词,输出预测单词的概率\n\n## 两种训练模式\n\n\n\n### CBOW\n\n\n\n通过上下文来预测当前值。相当于一句话中扣掉一个词,让你猜这个词是什么。\n\n### Skip-gram\n\n\n\n用当前词来预测上下文。相当于给你一个词,让你猜前面和后面可能出现什么词。\n\n## 两种优化方式\n\n前文说了,Word2vec的输出是预测单词的概率,这是利用Softmax函数做归一化去处理的,那我们来看看softmax是什么。\n$$\nS_{i}=\\frac{e^{i}}{\\Sigma_{j} e^{j}}\n$$\n那我们看到Softmax需要经历指数计算,指数计算在计算机运算中效率并不高,并且还需要计算全部单词的概率,当单词量级很高的时候,效率就会非常低,因为针对训练提出了两种优化方式:层序softmax和负采样。\n\n### Hierarchical Softmax\n\n利用[霍夫曼树](https://www.cnblogs.com/pinard/p/7160330.html)代替从隐藏层到输出softmax层的映射,霍夫曼树简单来说就是:权重【频率】越低的单词优先合并成根节点的原则生成一整颗二叉树,大家可以看下图\n\n\n\n如果输入样本输出的真实值是W2,那么Hierarchical Softmax利用逻辑回归分类器一层一层做而分类,综合每一层的逻辑回归作为目标函数进行训练。(层序softmax就是一层一层做分类得来的)\n\n**优点**\n\n1. 既然是二叉树,那计算量就从V变成了logV。\n2. 针对高频词,路径越短,计算越快。\n\n**缺点**\n\n当中心词是偏僻词时,路径会很长,训练时间提高\n\n### Negative Sampling\n\nNegative Sampling(负采样)的原理就比较简单,事先选择5-20个单词作为负样本,将softmax函数改为sigmod函数,直接由多分类作为二分类来使用。(至于负采样的个数,利用什么样的策略采样本文不予展开)\n\n**优点**\n\n1. 不用构造霍夫曼树,只需要维护一个负样本数据集\n2. 将softmax修改为sigmod函数,只需要改变第二层神经网络的少量参数,大大提升了效率\n\n## 优缺点\n\n**优点**\n\n1. 经过速度优化以后,训练词向量的速度很快\n2. 通用性很强,可以用在各种 NLP 任务中\n3. 由于考虑上下文,因此效果再18年以前一直都是Embedding技术的王牌\n\n**缺点**\n\n1. 由于词和向量是一对一的关系,所以多义词的问题无法解决\n2. Word2vec 是一种静态的方式,虽然通用性强,但是无法针对特定任务做动态优化\n\n## 问题\n\n1. 为什么要用CBOW和Skip-gram两种方式来训练,而不直接用传统DNN来训练?\n2. CBOW和Skip-gram分别有什么优劣势?\n\n# 应用\n\n## Word embedding及其衍生\n\n用于表示单词于单词之间的相关性【基础任务】\n\n- Word2vec【2013】:NLP中词向量训练的鼻祖\n- Fasttext【2014】:Word2vec基础上引入了字符级别的词向量,对德语,俄语更友好\n- ELMO【2018】:解决了一次多义的词向量模型\n- Embedding layer【Bert时代】:基于Bert后时代的模型,词向量已经不作为单独训练的目的,而是直接作为输入的一层嵌入模型中,跟随任务目标的变化而变化\n- Item2vec【2016】:微软将word2vec应用于推荐领域的一篇实用性很强的文章,主要用于训练item embedding,但更重要的是使其从NLP领域直接扩展到推荐、广告、搜索等任何可以生成sequence的领域。\n- Airbnb Embedding【2018】:Airbnb将word2vec用于推荐的典型文章,主要用于训练user embedding,很多user look alike系统的user embedding都是这么训练来的。\n\n## Sentence embedding\n\n用于表示句子于句子之间的相关性【标题召回】\n\n- Doc2vec【2014】:在Word2vec的基础上引入一个句向量概念,随着模型一起训练\n- Sentence_Bert【2019】:以Bert为基础,用有监督数据训练词向量\n- SimCSE/ConSert/EsimCSE【2021】:对比学习方法,利用Sentence_Bert进行自监督训练\n\n## Graph embedding\n\n节点映射为向量表示的时候尽可能多地保留图的拓扑信息【社区关系】\n\n- Deepwalk/Line/Node2Vec:基于Skip-gram的节点向量训练模型,区别在于他的输入需要用不同的遍历方式构造序列\n- SDNE【2016】:基于自编码器的半监督Graph embedding训练模型,可以看作Line的一种延伸\n- GCN/GraphSAGE/GAT/GIN【GCN时代】:GCN类模型,现阶段Graph embedding效果最好的模型\n- EGES【2018】:很好用的item emebdding训练模型,其原理就是deepwalk+不同的sideinfo形成商品embedding用于推荐搜索\n\n# 问题\n\n1. 什么是Embedding?\n2. Embedding如何作用在我们算法工作?\n\n# 参考\n\n[one-hot](https://zh.wikipedia.org/wiki/One-hot)\n\n[paper list](https://github.com/wzhe06/Reco-papers/tree/master/Embedding)\n\n","slug":"万物皆可embedding","published":1,"updated":"2022-01-25T06:58:37.723Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb50000eso3wfuldh7sl","content":"<h1 id=\"万物皆可embedding\"><a href=\"#万物皆可embedding\" class=\"headerlink\" title=\"万物皆可embedding\"></a>万物皆可embedding</h1><h1 id=\"前言\"><a href=\"#前言\" class=\"headerlink\" title=\"前言\"></a>前言</h1><p> 本文的目的是想通过这次分享,让可爱学长们能够了解在算法人员口中的embedding到底是什么,它是如何作用在我们算法的日常工作中。</p>\n<h1 id=\"背景\"><a href=\"#背景\" class=\"headerlink\" title=\"背景\"></a>背景</h1><p>引入embedding之前,需要先讲一下embedding的前身技术,<strong>one-hot编码</strong>。</p>\n<h2 id=\"one-hot\"><a href=\"#one-hot\" class=\"headerlink\" title=\"one-hot\"></a>one-hot</h2><p> 在机器学习里,存在one-hot向量(英语:one-hot vector)的概念。在一任意维度的向量中,仅有一个维度的值是1,其余为0。譬如向量[0,0,1,0,0],即为5维空间中的一组one-hot向量。将类别型数据转换成one-hot向量的过程则称one-hot编码(英语:one-hot encoding。在统计学中,one-hot我们一般叫做虚拟变量。</p>\n<p><img src=\"/images/embed1.jpg\" alt=\"embed1\"></p>\n<p><strong>优势</strong></p>\n<ol>\n<li>可以将类别型数据转化为向量形式,各类别在高维空间中距离相等</li>\n<li>进行损失函数(例如交叉熵损失)或准确率计算时,变得非常方便</li>\n</ol>\n<p><strong>劣势</strong></p>\n<ol>\n<li>当类别数量过于庞杂时,会导致维度灾难</li>\n<li>不能很好的刻画词与词之间的相似性</li>\n<li>强稀疏性</li>\n</ol>\n<p><strong>备注</strong></p>\n<ul>\n<li>sklearn包:sklearn.preprocessing.OneHotEncoder</li>\n<li>pandas包:pandas.get_dummies</li>\n</ul>\n<h2 id=\"embedding\"><a href=\"#embedding\" class=\"headerlink\" title=\"embedding\"></a>embedding</h2><p> 为了克服上述缺点,embedding技术就横空出世,最开始应用于<strong>NLP的词表达</strong>中,其最早时在1986年Hinton提出的,其<strong>基本想法</strong>是:</p>\n<p> 通过训练将某种语言中的每一个词 映射成一个固定长度的短向量(当然这里的“短”是相对于One-Hot的“长”而言的),所有这些向量构成一个词向量空间,而每一个向量则可视为 该空间中的一个点,在这个空间上引入“距离”,就可以根据词之间的距离来判断它们之间的语法、语义上的相似性。</p>\n<p> 真正让embedding技术发扬光大的是谷歌Tomas Mikolov团队的Word2Vec工具。该工具效率很高并且在语义表示上具有很好的效果。 </p>\n<p> 时至今日,embedding技术延伸至生活的方方面面,现在我们可以用embedding技术去表达一部电影,一个用户,一个商品等等。</p>\n<p> 说了这么多,那到底什么是embedding技术,一句话概括:一种基于<strong>低维向量</strong>来表示物品的技术我们称之为embedding技术。</p>\n<h1 id=\"Word2vec\"><a href=\"#Word2vec\" class=\"headerlink\" title=\"Word2vec\"></a>Word2vec</h1><p>word2vec模型的目的并不是训练词embedding,词embedding只是模型的附带品!!!</p>\n<p><img src=\"/images/embed2.png\" alt=\"embed2\"></p>\n<p><strong>模型结构</strong></p>\n<ol>\n<li>两层浅层神经网络</li>\n<li>输入单词,输出预测单词的概率</li>\n</ol>\n<h2 id=\"两种训练模式\"><a href=\"#两种训练模式\" class=\"headerlink\" title=\"两种训练模式\"></a>两种训练模式</h2><p><img src=\"/images/embed3.png\" alt=\"embed3\"></p>\n<h3 id=\"CBOW\"><a href=\"#CBOW\" class=\"headerlink\" title=\"CBOW\"></a>CBOW</h3><p><img src=\"/images/embed4.png\" alt=\"embed4\"></p>\n<p>通过上下文来预测当前值。相当于一句话中扣掉一个词,让你猜这个词是什么。</p>\n<h3 id=\"Skip-gram\"><a href=\"#Skip-gram\" class=\"headerlink\" title=\"Skip-gram\"></a>Skip-gram</h3><p><img src=\"/images/embed5.png\" alt=\"embed5\"></p>\n<p>用当前词来预测上下文。相当于给你一个词,让你猜前面和后面可能出现什么词。</p>\n<h2 id=\"两种优化方式\"><a href=\"#两种优化方式\" class=\"headerlink\" title=\"两种优化方式\"></a>两种优化方式</h2><p>前文说了,Word2vec的输出是预测单词的概率,这是利用Softmax函数做归一化去处理的,那我们来看看softmax是什么。</p>\n<script type=\"math/tex; mode=display\">\nS_{i}=\\frac{e^{i}}{\\Sigma_{j} e^{j}}</script><p>那我们看到Softmax需要经历指数计算,指数计算在计算机运算中效率并不高,并且还需要计算全部单词的概率,当单词量级很高的时候,效率就会非常低,因为针对训练提出了两种优化方式:层序softmax和负采样。</p>\n<h3 id=\"Hierarchical-Softmax\"><a href=\"#Hierarchical-Softmax\" class=\"headerlink\" title=\"Hierarchical Softmax\"></a>Hierarchical Softmax</h3><p>利用<a href=\"https://www.cnblogs.com/pinard/p/7160330.html\">霍夫曼树</a>代替从隐藏层到输出softmax层的映射,霍夫曼树简单来说就是:权重【频率】越低的单词优先合并成根节点的原则生成一整颗二叉树,大家可以看下图</p>\n<p><img src=\"/images/embed6.png\" alt=\"embed6\"></p>\n<p>如果输入样本输出的真实值是W2,那么Hierarchical Softmax利用逻辑回归分类器一层一层做而分类,综合每一层的逻辑回归作为目标函数进行训练。(层序softmax就是一层一层做分类得来的)</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>既然是二叉树,那计算量就从V变成了logV。</li>\n<li>针对高频词,路径越短,计算越快。</li>\n</ol>\n<p><strong>缺点</strong></p>\n<p>当中心词是偏僻词时,路径会很长,训练时间提高</p>\n<h3 id=\"Negative-Sampling\"><a href=\"#Negative-Sampling\" class=\"headerlink\" title=\"Negative Sampling\"></a>Negative Sampling</h3><p>Negative Sampling(负采样)的原理就比较简单,事先选择5-20个单词作为负样本,将softmax函数改为sigmod函数,直接由多分类作为二分类来使用。(至于负采样的个数,利用什么样的策略采样本文不予展开)</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>不用构造霍夫曼树,只需要维护一个负样本数据集</li>\n<li>将softmax修改为sigmod函数,只需要改变第二层神经网络的少量参数,大大提升了效率</li>\n</ol>\n<h2 id=\"优缺点\"><a href=\"#优缺点\" class=\"headerlink\" title=\"优缺点\"></a>优缺点</h2><p><strong>优点</strong></p>\n<ol>\n<li>经过速度优化以后,训练词向量的速度很快</li>\n<li>通用性很强,可以用在各种 NLP 任务中</li>\n<li>由于考虑上下文,因此效果再18年以前一直都是Embedding技术的王牌</li>\n</ol>\n<p><strong>缺点</strong></p>\n<ol>\n<li>由于词和向量是一对一的关系,所以多义词的问题无法解决</li>\n<li>Word2vec 是一种静态的方式,虽然通用性强,但是无法针对特定任务做动态优化</li>\n</ol>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><ol>\n<li>为什么要用CBOW和Skip-gram两种方式来训练,而不直接用传统DNN来训练?</li>\n<li>CBOW和Skip-gram分别有什么优劣势?</li>\n</ol>\n<h1 id=\"应用\"><a href=\"#应用\" class=\"headerlink\" title=\"应用\"></a>应用</h1><h2 id=\"Word-embedding及其衍生\"><a href=\"#Word-embedding及其衍生\" class=\"headerlink\" title=\"Word embedding及其衍生\"></a>Word embedding及其衍生</h2><p>用于表示单词于单词之间的相关性【基础任务】</p>\n<ul>\n<li>Word2vec【2013】:NLP中词向量训练的鼻祖</li>\n<li>Fasttext【2014】:Word2vec基础上引入了字符级别的词向量,对德语,俄语更友好</li>\n<li>ELMO【2018】:解决了一次多义的词向量模型</li>\n<li>Embedding layer【Bert时代】:基于Bert后时代的模型,词向量已经不作为单独训练的目的,而是直接作为输入的一层嵌入模型中,跟随任务目标的变化而变化</li>\n<li>Item2vec【2016】:微软将word2vec应用于推荐领域的一篇实用性很强的文章,主要用于训练item embedding,但更重要的是使其从NLP领域直接扩展到推荐、广告、搜索等任何可以生成sequence的领域。</li>\n<li>Airbnb Embedding【2018】:Airbnb将word2vec用于推荐的典型文章,主要用于训练user embedding,很多user look alike系统的user embedding都是这么训练来的。</li>\n</ul>\n<h2 id=\"Sentence-embedding\"><a href=\"#Sentence-embedding\" class=\"headerlink\" title=\"Sentence embedding\"></a>Sentence embedding</h2><p>用于表示句子于句子之间的相关性【标题召回】</p>\n<ul>\n<li>Doc2vec【2014】:在Word2vec的基础上引入一个句向量概念,随着模型一起训练</li>\n<li>Sentence_Bert【2019】:以Bert为基础,用有监督数据训练词向量</li>\n<li>SimCSE/ConSert/EsimCSE【2021】:对比学习方法,利用Sentence_Bert进行自监督训练</li>\n</ul>\n<h2 id=\"Graph-embedding\"><a href=\"#Graph-embedding\" class=\"headerlink\" title=\"Graph embedding\"></a>Graph embedding</h2><p>节点映射为向量表示的时候尽可能多地保留图的拓扑信息【社区关系】</p>\n<ul>\n<li>Deepwalk/Line/Node2Vec:基于Skip-gram的节点向量训练模型,区别在于他的输入需要用不同的遍历方式构造序列</li>\n<li>SDNE【2016】:基于自编码器的半监督Graph embedding训练模型,可以看作Line的一种延伸</li>\n<li>GCN/GraphSAGE/GAT/GIN【GCN时代】:GCN类模型,现阶段Graph embedding效果最好的模型</li>\n<li>EGES【2018】:很好用的item emebdding训练模型,其原理就是deepwalk+不同的sideinfo形成商品embedding用于推荐搜索</li>\n</ul>\n<h1 id=\"问题-1\"><a href=\"#问题-1\" class=\"headerlink\" title=\"问题\"></a>问题</h1><ol>\n<li>什么是Embedding?</li>\n<li>Embedding如何作用在我们算法工作?</li>\n</ol>\n<h1 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h1><p><a href=\"https://zh.wikipedia.org/wiki/One-hot\">one-hot</a></p>\n<p><a href=\"https://github.com/wzhe06/Reco-papers/tree/master/Embedding\">paper list</a></p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h1 id=\"万物皆可embedding\"><a href=\"#万物皆可embedding\" class=\"headerlink\" title=\"万物皆可embedding\"></a>万物皆可embedding</h1><h1 id=\"前言\"><a href=\"#前言\" class=\"headerlink\" title=\"前言\"></a>前言</h1><p> 本文的目的是想通过这次分享,让可爱学长们能够了解在算法人员口中的embedding到底是什么,它是如何作用在我们算法的日常工作中。</p>\n<h1 id=\"背景\"><a href=\"#背景\" class=\"headerlink\" title=\"背景\"></a>背景</h1><p>引入embedding之前,需要先讲一下embedding的前身技术,<strong>one-hot编码</strong>。</p>\n<h2 id=\"one-hot\"><a href=\"#one-hot\" class=\"headerlink\" title=\"one-hot\"></a>one-hot</h2><p> 在机器学习里,存在one-hot向量(英语:one-hot vector)的概念。在一任意维度的向量中,仅有一个维度的值是1,其余为0。譬如向量[0,0,1,0,0],即为5维空间中的一组one-hot向量。将类别型数据转换成one-hot向量的过程则称one-hot编码(英语:one-hot encoding。在统计学中,one-hot我们一般叫做虚拟变量。</p>\n<p><img src=\"/images/embed1.jpg\" alt=\"embed1\"></p>\n<p><strong>优势</strong></p>\n<ol>\n<li>可以将类别型数据转化为向量形式,各类别在高维空间中距离相等</li>\n<li>进行损失函数(例如交叉熵损失)或准确率计算时,变得非常方便</li>\n</ol>\n<p><strong>劣势</strong></p>\n<ol>\n<li>当类别数量过于庞杂时,会导致维度灾难</li>\n<li>不能很好的刻画词与词之间的相似性</li>\n<li>强稀疏性</li>\n</ol>\n<p><strong>备注</strong></p>\n<ul>\n<li>sklearn包:sklearn.preprocessing.OneHotEncoder</li>\n<li>pandas包:pandas.get_dummies</li>\n</ul>\n<h2 id=\"embedding\"><a href=\"#embedding\" class=\"headerlink\" title=\"embedding\"></a>embedding</h2><p> 为了克服上述缺点,embedding技术就横空出世,最开始应用于<strong>NLP的词表达</strong>中,其最早时在1986年Hinton提出的,其<strong>基本想法</strong>是:</p>\n<p> 通过训练将某种语言中的每一个词 映射成一个固定长度的短向量(当然这里的“短”是相对于One-Hot的“长”而言的),所有这些向量构成一个词向量空间,而每一个向量则可视为 该空间中的一个点,在这个空间上引入“距离”,就可以根据词之间的距离来判断它们之间的语法、语义上的相似性。</p>\n<p> 真正让embedding技术发扬光大的是谷歌Tomas Mikolov团队的Word2Vec工具。该工具效率很高并且在语义表示上具有很好的效果。 </p>\n<p> 时至今日,embedding技术延伸至生活的方方面面,现在我们可以用embedding技术去表达一部电影,一个用户,一个商品等等。</p>\n<p> 说了这么多,那到底什么是embedding技术,一句话概括:一种基于<strong>低维向量</strong>来表示物品的技术我们称之为embedding技术。</p>\n<h1 id=\"Word2vec\"><a href=\"#Word2vec\" class=\"headerlink\" title=\"Word2vec\"></a>Word2vec</h1><p>word2vec模型的目的并不是训练词embedding,词embedding只是模型的附带品!!!</p>\n<p><img src=\"/images/embed2.png\" alt=\"embed2\"></p>\n<p><strong>模型结构</strong></p>\n<ol>\n<li>两层浅层神经网络</li>\n<li>输入单词,输出预测单词的概率</li>\n</ol>\n<h2 id=\"两种训练模式\"><a href=\"#两种训练模式\" class=\"headerlink\" title=\"两种训练模式\"></a>两种训练模式</h2><p><img src=\"/images/embed3.png\" alt=\"embed3\"></p>\n<h3 id=\"CBOW\"><a href=\"#CBOW\" class=\"headerlink\" title=\"CBOW\"></a>CBOW</h3><p><img src=\"/images/embed4.png\" alt=\"embed4\"></p>\n<p>通过上下文来预测当前值。相当于一句话中扣掉一个词,让你猜这个词是什么。</p>\n<h3 id=\"Skip-gram\"><a href=\"#Skip-gram\" class=\"headerlink\" title=\"Skip-gram\"></a>Skip-gram</h3><p><img src=\"/images/embed5.png\" alt=\"embed5\"></p>\n<p>用当前词来预测上下文。相当于给你一个词,让你猜前面和后面可能出现什么词。</p>\n<h2 id=\"两种优化方式\"><a href=\"#两种优化方式\" class=\"headerlink\" title=\"两种优化方式\"></a>两种优化方式</h2><p>前文说了,Word2vec的输出是预测单词的概率,这是利用Softmax函数做归一化去处理的,那我们来看看softmax是什么。</p>\n<script type=\"math/tex; mode=display\">\nS_{i}=\\frac{e^{i}}{\\Sigma_{j} e^{j}}</script><p>那我们看到Softmax需要经历指数计算,指数计算在计算机运算中效率并不高,并且还需要计算全部单词的概率,当单词量级很高的时候,效率就会非常低,因为针对训练提出了两种优化方式:层序softmax和负采样。</p>\n<h3 id=\"Hierarchical-Softmax\"><a href=\"#Hierarchical-Softmax\" class=\"headerlink\" title=\"Hierarchical Softmax\"></a>Hierarchical Softmax</h3><p>利用<a href=\"https://www.cnblogs.com/pinard/p/7160330.html\">霍夫曼树</a>代替从隐藏层到输出softmax层的映射,霍夫曼树简单来说就是:权重【频率】越低的单词优先合并成根节点的原则生成一整颗二叉树,大家可以看下图</p>\n<p><img src=\"/images/embed6.png\" alt=\"embed6\"></p>\n<p>如果输入样本输出的真实值是W2,那么Hierarchical Softmax利用逻辑回归分类器一层一层做而分类,综合每一层的逻辑回归作为目标函数进行训练。(层序softmax就是一层一层做分类得来的)</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>既然是二叉树,那计算量就从V变成了logV。</li>\n<li>针对高频词,路径越短,计算越快。</li>\n</ol>\n<p><strong>缺点</strong></p>\n<p>当中心词是偏僻词时,路径会很长,训练时间提高</p>\n<h3 id=\"Negative-Sampling\"><a href=\"#Negative-Sampling\" class=\"headerlink\" title=\"Negative Sampling\"></a>Negative Sampling</h3><p>Negative Sampling(负采样)的原理就比较简单,事先选择5-20个单词作为负样本,将softmax函数改为sigmod函数,直接由多分类作为二分类来使用。(至于负采样的个数,利用什么样的策略采样本文不予展开)</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>不用构造霍夫曼树,只需要维护一个负样本数据集</li>\n<li>将softmax修改为sigmod函数,只需要改变第二层神经网络的少量参数,大大提升了效率</li>\n</ol>\n<h2 id=\"优缺点\"><a href=\"#优缺点\" class=\"headerlink\" title=\"优缺点\"></a>优缺点</h2><p><strong>优点</strong></p>\n<ol>\n<li>经过速度优化以后,训练词向量的速度很快</li>\n<li>通用性很强,可以用在各种 NLP 任务中</li>\n<li>由于考虑上下文,因此效果再18年以前一直都是Embedding技术的王牌</li>\n</ol>\n<p><strong>缺点</strong></p>\n<ol>\n<li>由于词和向量是一对一的关系,所以多义词的问题无法解决</li>\n<li>Word2vec 是一种静态的方式,虽然通用性强,但是无法针对特定任务做动态优化</li>\n</ol>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><ol>\n<li>为什么要用CBOW和Skip-gram两种方式来训练,而不直接用传统DNN来训练?</li>\n<li>CBOW和Skip-gram分别有什么优劣势?</li>\n</ol>\n<h1 id=\"应用\"><a href=\"#应用\" class=\"headerlink\" title=\"应用\"></a>应用</h1><h2 id=\"Word-embedding及其衍生\"><a href=\"#Word-embedding及其衍生\" class=\"headerlink\" title=\"Word embedding及其衍生\"></a>Word embedding及其衍生</h2><p>用于表示单词于单词之间的相关性【基础任务】</p>\n<ul>\n<li>Word2vec【2013】:NLP中词向量训练的鼻祖</li>\n<li>Fasttext【2014】:Word2vec基础上引入了字符级别的词向量,对德语,俄语更友好</li>\n<li>ELMO【2018】:解决了一次多义的词向量模型</li>\n<li>Embedding layer【Bert时代】:基于Bert后时代的模型,词向量已经不作为单独训练的目的,而是直接作为输入的一层嵌入模型中,跟随任务目标的变化而变化</li>\n<li>Item2vec【2016】:微软将word2vec应用于推荐领域的一篇实用性很强的文章,主要用于训练item embedding,但更重要的是使其从NLP领域直接扩展到推荐、广告、搜索等任何可以生成sequence的领域。</li>\n<li>Airbnb Embedding【2018】:Airbnb将word2vec用于推荐的典型文章,主要用于训练user embedding,很多user look alike系统的user embedding都是这么训练来的。</li>\n</ul>\n<h2 id=\"Sentence-embedding\"><a href=\"#Sentence-embedding\" class=\"headerlink\" title=\"Sentence embedding\"></a>Sentence embedding</h2><p>用于表示句子于句子之间的相关性【标题召回】</p>\n<ul>\n<li>Doc2vec【2014】:在Word2vec的基础上引入一个句向量概念,随着模型一起训练</li>\n<li>Sentence_Bert【2019】:以Bert为基础,用有监督数据训练词向量</li>\n<li>SimCSE/ConSert/EsimCSE【2021】:对比学习方法,利用Sentence_Bert进行自监督训练</li>\n</ul>\n<h2 id=\"Graph-embedding\"><a href=\"#Graph-embedding\" class=\"headerlink\" title=\"Graph embedding\"></a>Graph embedding</h2><p>节点映射为向量表示的时候尽可能多地保留图的拓扑信息【社区关系】</p>\n<ul>\n<li>Deepwalk/Line/Node2Vec:基于Skip-gram的节点向量训练模型,区别在于他的输入需要用不同的遍历方式构造序列</li>\n<li>SDNE【2016】:基于自编码器的半监督Graph embedding训练模型,可以看作Line的一种延伸</li>\n<li>GCN/GraphSAGE/GAT/GIN【GCN时代】:GCN类模型,现阶段Graph embedding效果最好的模型</li>\n<li>EGES【2018】:很好用的item emebdding训练模型,其原理就是deepwalk+不同的sideinfo形成商品embedding用于推荐搜索</li>\n</ul>\n<h1 id=\"问题-1\"><a href=\"#问题-1\" class=\"headerlink\" title=\"问题\"></a>问题</h1><ol>\n<li>什么是Embedding?</li>\n<li>Embedding如何作用在我们算法工作?</li>\n</ol>\n<h1 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h1><p><a href=\"https://zh.wikipedia.org/wiki/One-hot\">one-hot</a></p>\n<p><a href=\"https://github.com/wzhe06/Reco-papers/tree/master/Embedding\">paper list</a></p>\n"},{"title":"初识NLP","date":"2021-04-11T03:23:23.000Z","description":["此文档写于2020年,建成于博客创立之前。"],"_content":"\n# 概述\n\n此文档为了更好的理解自然语言处理(natural language processing),自然语言处理(NLP)就是开发能够理解人类语言的应用程序或服务。接下来会讨论一些NLP在国内外的发展状况和实际应用的一些例子,了解当前最前沿的NLP应用并掌握一定的NLP技术是接下来自己需要做的。\n\n# NLP简述\n\nNLP是研究人与人交际中以及在人与计算机交际中的语言问题的一门学科。NLP主要有五个主要任务:分类、匹配、翻译、结构化预测、与序贯决策过程。NLP中的绝大多数问题皆可归入其中的一个,如下表所示。在这些任务中,单词、词组、语句、段落甚至文档通常被看作标记(字符串)序列而采取相似的处理,尽管它们的复杂度并不相同。事实上,语句是 NLP 中最常用的处理单元。\n\n| 任务 | 描述 | 应用 |\n| :----------- | :------------------------- | :----------------------------------------- |\n| 分类 | 给每个string指定一个标签 | 文本分类、情感分析 |\n| 匹配 | 匹配两个strings | 信息检索、问答系统 |\n| 翻译 | 翻译某种语言 | 机器翻译、自动语音识别 |\n| 结构化预测 | 将每个string映射为一种结构 | 命名实体识别、中文分词、词性标注、句法分析 |\n| 有序决策过程 | 对一个动态的过程做出反应 | 多伦对话系统 |\n\n## 应用简介\n\n**文本分类**:用电脑对文本集(或其他实体或物件)按照一定的分类体系或标准进行自动分类标记。\n\n**情感分析**:又称意见挖掘、倾向性分析等。简单而言,是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。\n\n**信息检索**:信息按一定的方式进行加工、整理、组织并存储起来,再根据信息用户特定的需要将相关信息准确的查找出来的过程。\n\n**问答系统**:用准确、简洁的自然语言回答用户用自然语言提出的问题。\n\n**机器翻译**:是利用计算机将一种自然语言(源语言)转换为另一种自然语言(目标语言)的过程,除了传统的文本翻译之外,还有比较特殊的手语翻译和唇语翻译。\n\n**自动语音识别**:一种将人的语音转换为文本的技术。\n\n**命名实体识别**:是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。\n\n**中文分词**:指的是将一个汉字序列切分成一个个单独的词。\n\n**词性标注**:利用语料库内单词的词性按其含义和上下文内容进行标记的文本数据处理技术。\n\n**句法分析**:依存句法分析接口可自动分析文本中的依存句法结构信息,利用句子中词与词之间的依存关系来表示词语的句法结构信息(如“主谓”、“动宾”、“定中”等结构关系),并用树状结构来表示整句的结构(如“主谓宾”、“定状补”等)。\n\n**多伦对话系统**:(封闭域)多轮对话是一种,在人机对话中,初步明确用户意图之后,获取必要信息以最终得到明确用户指令的方式。\n\n# 国内外NLP的应用\n\n由于NLP领域在工业界内还属于探索状态,下表只是罗列了一下了解到的行业内较为优异的公司从事的领域和相关产品\n\n\n| **公司** | **关注点** | **产品** |\n| :------: | :------------------------------------: | :-----------------------------: |\n| 谷歌 | 机器翻译、知识图谱(智能化搜索引擎) | 谷歌翻译、谷歌浏览器 |\n| 微软 | 机器翻译、对话平台、中国文化、阅读理解 | 微软翻译、小娜、小冰、微软对联 |\n| IBM | 对话平台运用在医疗、金融、物联网 | 沃森 |\n| 百度 | 机器翻译、知识图谱、对话平台 | 度秘、百度检索、小度机器人 |\n| 阿里 | 智能导购(技术快速的跟业务对接) | 优酷、YunOS、蚂蚁金服、资讯搜索 |\n| 腾讯 | 社交、内容、游戏 | 腾讯觅影、微信、qq |\n\n# NLP技术支持\n\n接下来研究的重点在于文本分类和情感分析方向,百度,bosonnlp,阿里(收费),腾讯(收费)等公司都提供了优秀的api接口。下面是关于两方面的简单应用。\n\n## 百度API\n\n### 文本分类\n\n对文章按照内容类型进行自动分类,首批支持娱乐、体育、科技等26个主流内容类型,为文章聚类、文本内容分析等应用提供基础技术支持。 目前支持的一级粗粒度分类类目如下: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、综合。\n\n```python\nfrom aip import AipNlp\nAPP_ID = '15438079'\nAPI_KEY = 'rwAgYHnVN0bGkuksdvk7piio'\nSECRET_KEY = 'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'\nclient = AipNlp(APP_ID,API_KEY, SECRET_KEY)\ntitle = '欧洲冠军杯足球赛'\ncontent = \"欧洲冠军联赛是欧洲足球协会联盟主办的年度足球比赛,代表欧洲俱乐部足球最高荣誉和水平,被认为是全世界最高素质、最具影响力以及最高水平的俱乐部赛事,亦是世界上奖金最高的足球赛事和体育赛事之一。\"\nclient.topic(title, content)\n\n##结果展示\n{'log_id': 6074973177044352305,\n 'item': {'lv2_tag_list': [{'score': 0.915631, 'tag': '足球'},\n {'score': 0.803507, 'tag': '国际足球'},\n {'score': 0.77813, 'tag': '英超'}],\n 'lv1_tag_list': [{'score': 0.830915, 'tag': '体育'}]}}\n```\n\n### 情感倾向分析\n\n对包含主观观点信息的文本进行情感极性类别(积极、消极、中性)的判断,并给出相应的置信度。\n\n```python\nfrom aip import AipNlp\nAPP_ID = '15438079'\nAPI_KEY = 'rwAgYHnVN0bGkuksdvk7piio'\nSECRET_KEY = 'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'\nclient = AipNlp(APP_ID,API_KEY, SECRET_KEY)\ntext = \"苹果是一家伟大的公司\"\nclient.sentimentClassify(text)\n\n##结果展示\n{'log_id': 9106725229255700145,\n 'text': '苹果是一家伟大的公司',\n 'items': [{'positive_prob': 0.727802, //表示属于积极类别的概率\n 'confidence': 0.395115, //表示分类的置信度\n 'negative_prob': 0.272198, //表示属于消极类别的概率\n 'sentiment': 2}]} //表示情感极性分类结果\n```\n\n### 百度API优势\n\n1. **永久免费**。2018年5月11日,百度自然语言处理技术宣布对外永久免费,且不限调用量。\n2. **功能丰富**。包括词法分析、依存句法分析、词向量表示、DNN语言模型、词义相似度、短文本相似度、评论观点抽取、情感倾向分析、文章标签、文章分类等。\n3. **接口易用**。标准化接口封装,通过云计算调用可快速使用工具,降低开发人力成本。\n\n## bosonnlp\n\n### 新闻分类\n\n将新闻文本归类到预设的 14 个分类当中:0、体育 1、教育 2、财经 3、社会 4、娱乐 5、军事 6、国内 7、科技 8、互联网 9、房地产 10、国际 11、女人 12、汽车 13、游戏。\n\n```\nfrom bosonnlp import BosonNLP\nimport os\nnlp = BosonNLP('HpikDzL0.32936.Hi3mJFXh1P1b')\nnlp.classify(['俄否决安理会谴责叙军战机空袭阿勒颇平民',\n '邓紫棋谈男友林宥嘉:我觉得我比他唱得好',\n 'Facebook收购印度初创公司'])\n \n##结果展示\n[5, 4, 8]\n```\n\n### 情感分析\n\n其接口将文本的情感分为负面和非负面两类。本引擎用微博、新闻、汽车、餐饮等不同行业语料进行标注和机器学习,调用时请通过 URL 参数选择特定的模型,以获得最佳的情感判断准确率。\n\n```python\nfrom bosonnlp import BosonNLP\nimport os\nnlp = BosonNLP('HpikDzL0.32936.Hi3mJFXh1P1b')\ns = ['苹果是一家垃圾公司', '美好的世界']\nresult = nlp.sentiment(s)\nprint(result)\n\n##结果展示\n[[0.01819305539338867, 0.9818069446066113], [0.92706110187413, 0.07293889812586994]]\n```\n\n### bosonnlp的优势\n\nBOSON实验室的优点在于对于某一个单文本或者多文本,他能够得出一个**综合的分析结果**。\n\n1. **单文本分析**:只输入一段文本,输出的分析结果包括词性分析、实体识别、依存文法、情感分析、新闻摘要、新闻分类、关键词提取、语义联系。\n2. **多文本分析**:同时输入多段文本、新闻,输出的分析结果包括话题聚类、情感分析、词云图、词频图。\n\n# 总结\n\n## NLP最新进展\n\n### 文本分类通用规律\n\n2018年7月,谷歌做了45万次不同类型的文本分类后,总结出一个对于文本分类和情感分析而言通用的“模型选择算法”。\n\n**文本分类的流程图**\n\n```mermaid\ngraph LR\n搜集数据-->探索数据\n探索数据-->选择模型\n选择模型-->准备数据\n准备数据-->构建训练和评估模型\n构建训练和评估模型-->调优超参数\n调优超参数-->部署模型\n```\n\n我们预处理数据的方式将取决于我们选择的模型。模型可以大致分为两类:使用单词排序信息的模型(序列模型),以及仅将文本视为单词的“bags”(sets)的模型(n-gram模型)。\n\n- **序列模型**:卷积神经网络(CNN),递归神经网络(RNN)及其变体。\n\n- **n-gram模型**:逻辑回归(LR),支持向量机(SVM),梯度提升树等。\n\n**结论**:\n\n在实验中,其观察到“样本数”(S)与“每个样本的单词数”(W)的比率与模型的性能具有相关性。\n\n当该**比率的值很小(<1500)**时,以n-gram作为输入的小型多层感知机表现得更好,或者说至少与序列模型一样好。 MLP易于定义和理解,而且比序列模型花费的计算时间更少。(感觉如果数据量特别大的python这个工具也不一定能用)\n\n当此**比率的值很大(> = 1500)**时,使用序列模型。\n\n### BERT\n\n2018年10月,谷歌新发布的一种模型**BERT**(Bidirectional Encoder Representations from Transformers),在NLP业内引起巨大反响。BERT在机器阅读理解测试SQuAD1.1中表现出惊人的成绩:全部两个衡量指标上全面超越人类,并且还在11种不同NLP任务中创出最佳成绩。\n\n**具体做法:**\n\n1. 采取新的预训练的目标函数:the “masked language model” (MLM) 对于mask随机输入一些单词(80%的时间真的用[MASK]取代被选中的词,10%的时间用一个随机词取代它,10%的时间保持不变),然后在预训练中对它们进行预测。这样做的好处是学习到的表征能够融合两个方向上的内容。\n2. 增加句子级别的任务:“next sentence prediction”。作者同时预训练了一个“next sentence prediction”任务。具体做法是随机替换一些句子,然后利用上一句进行此句是否为下一句的预测。\n\n**模型框架**:\n\nBASE:L=12, H=768, A=12, Total Parameters=110M\n\nLARGE:L=24, H=1024, A=16, Total Parameters=340M\n\n这里的L为神经网络层数,H为隐藏向量参数,A为自注意力头数\n\n**结论**:\n\n- 在大数据中效果好,但如果模型经过足够的预训练,在小任务中大模型也能增长。\n- 计算成本相当高,作者动用了谷歌 Cloud AI 资源,用了 64 颗 TPU,算了 4 天,模型参数寻优的训练过程才收敛。\n","source":"_posts/初识NLP.md","raw":"---\ntitle: 初识NLP\ndate: 2021-04-11 11:23:23\ncategories:\n- 基础知识\ntags:\n- 基础知识\ndescription:\n- 此文档写于2020年,建成于博客创立之前。\n---\n\n# 概述\n\n此文档为了更好的理解自然语言处理(natural language processing),自然语言处理(NLP)就是开发能够理解人类语言的应用程序或服务。接下来会讨论一些NLP在国内外的发展状况和实际应用的一些例子,了解当前最前沿的NLP应用并掌握一定的NLP技术是接下来自己需要做的。\n\n# NLP简述\n\nNLP是研究人与人交际中以及在人与计算机交际中的语言问题的一门学科。NLP主要有五个主要任务:分类、匹配、翻译、结构化预测、与序贯决策过程。NLP中的绝大多数问题皆可归入其中的一个,如下表所示。在这些任务中,单词、词组、语句、段落甚至文档通常被看作标记(字符串)序列而采取相似的处理,尽管它们的复杂度并不相同。事实上,语句是 NLP 中最常用的处理单元。\n\n| 任务 | 描述 | 应用 |\n| :----------- | :------------------------- | :----------------------------------------- |\n| 分类 | 给每个string指定一个标签 | 文本分类、情感分析 |\n| 匹配 | 匹配两个strings | 信息检索、问答系统 |\n| 翻译 | 翻译某种语言 | 机器翻译、自动语音识别 |\n| 结构化预测 | 将每个string映射为一种结构 | 命名实体识别、中文分词、词性标注、句法分析 |\n| 有序决策过程 | 对一个动态的过程做出反应 | 多伦对话系统 |\n\n## 应用简介\n\n**文本分类**:用电脑对文本集(或其他实体或物件)按照一定的分类体系或标准进行自动分类标记。\n\n**情感分析**:又称意见挖掘、倾向性分析等。简单而言,是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。\n\n**信息检索**:信息按一定的方式进行加工、整理、组织并存储起来,再根据信息用户特定的需要将相关信息准确的查找出来的过程。\n\n**问答系统**:用准确、简洁的自然语言回答用户用自然语言提出的问题。\n\n**机器翻译**:是利用计算机将一种自然语言(源语言)转换为另一种自然语言(目标语言)的过程,除了传统的文本翻译之外,还有比较特殊的手语翻译和唇语翻译。\n\n**自动语音识别**:一种将人的语音转换为文本的技术。\n\n**命名实体识别**:是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。\n\n**中文分词**:指的是将一个汉字序列切分成一个个单独的词。\n\n**词性标注**:利用语料库内单词的词性按其含义和上下文内容进行标记的文本数据处理技术。\n\n**句法分析**:依存句法分析接口可自动分析文本中的依存句法结构信息,利用句子中词与词之间的依存关系来表示词语的句法结构信息(如“主谓”、“动宾”、“定中”等结构关系),并用树状结构来表示整句的结构(如“主谓宾”、“定状补”等)。\n\n**多伦对话系统**:(封闭域)多轮对话是一种,在人机对话中,初步明确用户意图之后,获取必要信息以最终得到明确用户指令的方式。\n\n# 国内外NLP的应用\n\n由于NLP领域在工业界内还属于探索状态,下表只是罗列了一下了解到的行业内较为优异的公司从事的领域和相关产品\n\n\n| **公司** | **关注点** | **产品** |\n| :------: | :------------------------------------: | :-----------------------------: |\n| 谷歌 | 机器翻译、知识图谱(智能化搜索引擎) | 谷歌翻译、谷歌浏览器 |\n| 微软 | 机器翻译、对话平台、中国文化、阅读理解 | 微软翻译、小娜、小冰、微软对联 |\n| IBM | 对话平台运用在医疗、金融、物联网 | 沃森 |\n| 百度 | 机器翻译、知识图谱、对话平台 | 度秘、百度检索、小度机器人 |\n| 阿里 | 智能导购(技术快速的跟业务对接) | 优酷、YunOS、蚂蚁金服、资讯搜索 |\n| 腾讯 | 社交、内容、游戏 | 腾讯觅影、微信、qq |\n\n# NLP技术支持\n\n接下来研究的重点在于文本分类和情感分析方向,百度,bosonnlp,阿里(收费),腾讯(收费)等公司都提供了优秀的api接口。下面是关于两方面的简单应用。\n\n## 百度API\n\n### 文本分类\n\n对文章按照内容类型进行自动分类,首批支持娱乐、体育、科技等26个主流内容类型,为文章聚类、文本内容分析等应用提供基础技术支持。 目前支持的一级粗粒度分类类目如下: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、综合。\n\n```python\nfrom aip import AipNlp\nAPP_ID = '15438079'\nAPI_KEY = 'rwAgYHnVN0bGkuksdvk7piio'\nSECRET_KEY = 'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'\nclient = AipNlp(APP_ID,API_KEY, SECRET_KEY)\ntitle = '欧洲冠军杯足球赛'\ncontent = \"欧洲冠军联赛是欧洲足球协会联盟主办的年度足球比赛,代表欧洲俱乐部足球最高荣誉和水平,被认为是全世界最高素质、最具影响力以及最高水平的俱乐部赛事,亦是世界上奖金最高的足球赛事和体育赛事之一。\"\nclient.topic(title, content)\n\n##结果展示\n{'log_id': 6074973177044352305,\n 'item': {'lv2_tag_list': [{'score': 0.915631, 'tag': '足球'},\n {'score': 0.803507, 'tag': '国际足球'},\n {'score': 0.77813, 'tag': '英超'}],\n 'lv1_tag_list': [{'score': 0.830915, 'tag': '体育'}]}}\n```\n\n### 情感倾向分析\n\n对包含主观观点信息的文本进行情感极性类别(积极、消极、中性)的判断,并给出相应的置信度。\n\n```python\nfrom aip import AipNlp\nAPP_ID = '15438079'\nAPI_KEY = 'rwAgYHnVN0bGkuksdvk7piio'\nSECRET_KEY = 'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'\nclient = AipNlp(APP_ID,API_KEY, SECRET_KEY)\ntext = \"苹果是一家伟大的公司\"\nclient.sentimentClassify(text)\n\n##结果展示\n{'log_id': 9106725229255700145,\n 'text': '苹果是一家伟大的公司',\n 'items': [{'positive_prob': 0.727802, //表示属于积极类别的概率\n 'confidence': 0.395115, //表示分类的置信度\n 'negative_prob': 0.272198, //表示属于消极类别的概率\n 'sentiment': 2}]} //表示情感极性分类结果\n```\n\n### 百度API优势\n\n1. **永久免费**。2018年5月11日,百度自然语言处理技术宣布对外永久免费,且不限调用量。\n2. **功能丰富**。包括词法分析、依存句法分析、词向量表示、DNN语言模型、词义相似度、短文本相似度、评论观点抽取、情感倾向分析、文章标签、文章分类等。\n3. **接口易用**。标准化接口封装,通过云计算调用可快速使用工具,降低开发人力成本。\n\n## bosonnlp\n\n### 新闻分类\n\n将新闻文本归类到预设的 14 个分类当中:0、体育 1、教育 2、财经 3、社会 4、娱乐 5、军事 6、国内 7、科技 8、互联网 9、房地产 10、国际 11、女人 12、汽车 13、游戏。\n\n```\nfrom bosonnlp import BosonNLP\nimport os\nnlp = BosonNLP('HpikDzL0.32936.Hi3mJFXh1P1b')\nnlp.classify(['俄否决安理会谴责叙军战机空袭阿勒颇平民',\n '邓紫棋谈男友林宥嘉:我觉得我比他唱得好',\n 'Facebook收购印度初创公司'])\n \n##结果展示\n[5, 4, 8]\n```\n\n### 情感分析\n\n其接口将文本的情感分为负面和非负面两类。本引擎用微博、新闻、汽车、餐饮等不同行业语料进行标注和机器学习,调用时请通过 URL 参数选择特定的模型,以获得最佳的情感判断准确率。\n\n```python\nfrom bosonnlp import BosonNLP\nimport os\nnlp = BosonNLP('HpikDzL0.32936.Hi3mJFXh1P1b')\ns = ['苹果是一家垃圾公司', '美好的世界']\nresult = nlp.sentiment(s)\nprint(result)\n\n##结果展示\n[[0.01819305539338867, 0.9818069446066113], [0.92706110187413, 0.07293889812586994]]\n```\n\n### bosonnlp的优势\n\nBOSON实验室的优点在于对于某一个单文本或者多文本,他能够得出一个**综合的分析结果**。\n\n1. **单文本分析**:只输入一段文本,输出的分析结果包括词性分析、实体识别、依存文法、情感分析、新闻摘要、新闻分类、关键词提取、语义联系。\n2. **多文本分析**:同时输入多段文本、新闻,输出的分析结果包括话题聚类、情感分析、词云图、词频图。\n\n# 总结\n\n## NLP最新进展\n\n### 文本分类通用规律\n\n2018年7月,谷歌做了45万次不同类型的文本分类后,总结出一个对于文本分类和情感分析而言通用的“模型选择算法”。\n\n**文本分类的流程图**\n\n```mermaid\ngraph LR\n搜集数据-->探索数据\n探索数据-->选择模型\n选择模型-->准备数据\n准备数据-->构建训练和评估模型\n构建训练和评估模型-->调优超参数\n调优超参数-->部署模型\n```\n\n我们预处理数据的方式将取决于我们选择的模型。模型可以大致分为两类:使用单词排序信息的模型(序列模型),以及仅将文本视为单词的“bags”(sets)的模型(n-gram模型)。\n\n- **序列模型**:卷积神经网络(CNN),递归神经网络(RNN)及其变体。\n\n- **n-gram模型**:逻辑回归(LR),支持向量机(SVM),梯度提升树等。\n\n**结论**:\n\n在实验中,其观察到“样本数”(S)与“每个样本的单词数”(W)的比率与模型的性能具有相关性。\n\n当该**比率的值很小(<1500)**时,以n-gram作为输入的小型多层感知机表现得更好,或者说至少与序列模型一样好。 MLP易于定义和理解,而且比序列模型花费的计算时间更少。(感觉如果数据量特别大的python这个工具也不一定能用)\n\n当此**比率的值很大(> = 1500)**时,使用序列模型。\n\n### BERT\n\n2018年10月,谷歌新发布的一种模型**BERT**(Bidirectional Encoder Representations from Transformers),在NLP业内引起巨大反响。BERT在机器阅读理解测试SQuAD1.1中表现出惊人的成绩:全部两个衡量指标上全面超越人类,并且还在11种不同NLP任务中创出最佳成绩。\n\n**具体做法:**\n\n1. 采取新的预训练的目标函数:the “masked language model” (MLM) 对于mask随机输入一些单词(80%的时间真的用[MASK]取代被选中的词,10%的时间用一个随机词取代它,10%的时间保持不变),然后在预训练中对它们进行预测。这样做的好处是学习到的表征能够融合两个方向上的内容。\n2. 增加句子级别的任务:“next sentence prediction”。作者同时预训练了一个“next sentence prediction”任务。具体做法是随机替换一些句子,然后利用上一句进行此句是否为下一句的预测。\n\n**模型框架**:\n\nBASE:L=12, H=768, A=12, Total Parameters=110M\n\nLARGE:L=24, H=1024, A=16, Total Parameters=340M\n\n这里的L为神经网络层数,H为隐藏向量参数,A为自注意力头数\n\n**结论**:\n\n- 在大数据中效果好,但如果模型经过足够的预训练,在小任务中大模型也能增长。\n- 计算成本相当高,作者动用了谷歌 Cloud AI 资源,用了 64 颗 TPU,算了 4 天,模型参数寻优的训练过程才收敛。\n","slug":"初识NLP","published":1,"updated":"2021-09-18T08:43:45.914Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb58000jso3w01qpgjfp","content":"<h1 id=\"概述\"><a href=\"#概述\" class=\"headerlink\" title=\"概述\"></a>概述</h1><p>此文档为了更好的理解自然语言处理(natural language processing),自然语言处理(NLP)就是开发能够理解人类语言的应用程序或服务。接下来会讨论一些NLP在国内外的发展状况和实际应用的一些例子,了解当前最前沿的NLP应用并掌握一定的NLP技术是接下来自己需要做的。</p>\n<h1 id=\"NLP简述\"><a href=\"#NLP简述\" class=\"headerlink\" title=\"NLP简述\"></a>NLP简述</h1><p>NLP是研究人与人交际中以及在人与计算机交际中的语言问题的一门学科。NLP主要有五个主要任务:分类、匹配、翻译、结构化预测、与序贯决策过程。NLP中的绝大多数问题皆可归入其中的一个,如下表所示。在这些任务中,单词、词组、语句、段落甚至文档通常被看作标记(字符串)序列而采取相似的处理,尽管它们的复杂度并不相同。事实上,语句是 NLP 中最常用的处理单元。</p>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">任务</th>\n<th style=\"text-align:left\">描述</th>\n<th style=\"text-align:left\">应用</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\">分类</td>\n<td style=\"text-align:left\">给每个string指定一个标签</td>\n<td style=\"text-align:left\">文本分类、情感分析</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">匹配</td>\n<td style=\"text-align:left\">匹配两个strings</td>\n<td style=\"text-align:left\">信息检索、问答系统</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">翻译</td>\n<td style=\"text-align:left\">翻译某种语言</td>\n<td style=\"text-align:left\">机器翻译、自动语音识别</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">结构化预测</td>\n<td style=\"text-align:left\">将每个string映射为一种结构</td>\n<td style=\"text-align:left\">命名实体识别、中文分词、词性标注、句法分析</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">有序决策过程</td>\n<td style=\"text-align:left\">对一个动态的过程做出反应</td>\n<td style=\"text-align:left\">多伦对话系统</td>\n</tr>\n</tbody>\n</table>\n</div>\n<h2 id=\"应用简介\"><a href=\"#应用简介\" class=\"headerlink\" title=\"应用简介\"></a>应用简介</h2><p><strong>文本分类</strong>:用电脑对文本集(或其他实体或物件)按照一定的分类体系或标准进行自动分类标记。</p>\n<p><strong>情感分析</strong>:又称意见挖掘、倾向性分析等。简单而言,是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。</p>\n<p><strong>信息检索</strong>:信息按一定的方式进行加工、整理、组织并存储起来,再根据信息用户特定的需要将相关信息准确的查找出来的过程。</p>\n<p><strong>问答系统</strong>:用准确、简洁的自然语言回答用户用自然语言提出的问题。</p>\n<p><strong>机器翻译</strong>:是利用计算机将一种自然语言(源语言)转换为另一种自然语言(目标语言)的过程,除了传统的文本翻译之外,还有比较特殊的手语翻译和唇语翻译。</p>\n<p><strong>自动语音识别</strong>:一种将人的语音转换为文本的技术。</p>\n<p><strong>命名实体识别</strong>:是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。</p>\n<p><strong>中文分词</strong>:指的是将一个汉字序列切分成一个个单独的词。</p>\n<p><strong>词性标注</strong>:利用语料库内单词的词性按其含义和上下文内容进行标记的文本数据处理技术。</p>\n<p><strong>句法分析</strong>:依存句法分析接口可自动分析文本中的依存句法结构信息,利用句子中词与词之间的依存关系来表示词语的句法结构信息(如“主谓”、“动宾”、“定中”等结构关系),并用树状结构来表示整句的结构(如“主谓宾”、“定状补”等)。</p>\n<p><strong>多伦对话系统</strong>:(封闭域)多轮对话是一种,在人机对话中,初步明确用户意图之后,获取必要信息以最终得到明确用户指令的方式。</p>\n<h1 id=\"国内外NLP的应用\"><a href=\"#国内外NLP的应用\" class=\"headerlink\" title=\"国内外NLP的应用\"></a>国内外NLP的应用</h1><p>由于NLP领域在工业界内还属于探索状态,下表只是罗列了一下了解到的行业内较为优异的公司从事的领域和相关产品</p>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th style=\"text-align:center\"><strong>公司</strong></th>\n<th style=\"text-align:center\"><strong>关注点</strong></th>\n<th style=\"text-align:center\"><strong>产品</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:center\">谷歌</td>\n<td style=\"text-align:center\">机器翻译、知识图谱(智能化搜索引擎)</td>\n<td style=\"text-align:center\">谷歌翻译、谷歌浏览器</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">微软</td>\n<td style=\"text-align:center\">机器翻译、对话平台、中国文化、阅读理解</td>\n<td style=\"text-align:center\">微软翻译、小娜、小冰、微软对联</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">IBM</td>\n<td style=\"text-align:center\">对话平台运用在医疗、金融、物联网</td>\n<td style=\"text-align:center\">沃森</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">百度</td>\n<td style=\"text-align:center\">机器翻译、知识图谱、对话平台</td>\n<td style=\"text-align:center\">度秘、百度检索、小度机器人</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">阿里</td>\n<td style=\"text-align:center\">智能导购(技术快速的跟业务对接)</td>\n<td style=\"text-align:center\">优酷、YunOS、蚂蚁金服、资讯搜索</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">腾讯</td>\n<td style=\"text-align:center\">社交、内容、游戏</td>\n<td style=\"text-align:center\">腾讯觅影、微信、qq</td>\n</tr>\n</tbody>\n</table>\n</div>\n<h1 id=\"NLP技术支持\"><a href=\"#NLP技术支持\" class=\"headerlink\" title=\"NLP技术支持\"></a>NLP技术支持</h1><p>接下来研究的重点在于文本分类和情感分析方向,百度,bosonnlp,阿里(收费),腾讯(收费)等公司都提供了优秀的api接口。下面是关于两方面的简单应用。</p>\n<h2 id=\"百度API\"><a href=\"#百度API\" class=\"headerlink\" title=\"百度API\"></a>百度API</h2><h3 id=\"文本分类\"><a href=\"#文本分类\" class=\"headerlink\" title=\"文本分类\"></a>文本分类</h3><p>对文章按照内容类型进行自动分类,首批支持娱乐、体育、科技等26个主流内容类型,为文章聚类、文本内容分析等应用提供基础技术支持。 目前支持的一级粗粒度分类类目如下: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、综合。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> aip <span class=\"keyword\">import</span> AipNlp</span><br><span class=\"line\">APP_ID = <span class=\"string\">'15438079'</span></span><br><span class=\"line\">API_KEY = <span class=\"string\">'rwAgYHnVN0bGkuksdvk7piio'</span></span><br><span class=\"line\">SECRET_KEY = <span class=\"string\">'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'</span></span><br><span class=\"line\">client = AipNlp(APP_ID,API_KEY, SECRET_KEY)</span><br><span class=\"line\">title = <span class=\"string\">'欧洲冠军杯足球赛'</span></span><br><span class=\"line\">content = <span class=\"string\">"欧洲冠军联赛是欧洲足球协会联盟主办的年度足球比赛,代表欧洲俱乐部足球最高荣誉和水平,被认为是全世界最高素质、最具影响力以及最高水平的俱乐部赛事,亦是世界上奖金最高的足球赛事和体育赛事之一。"</span></span><br><span class=\"line\">client.topic(title, content)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">##结果展示</span></span><br><span class=\"line\">{<span class=\"string\">'log_id'</span>: <span class=\"number\">6074973177044352305</span>,</span><br><span class=\"line\"> <span class=\"string\">'item'</span>: {<span class=\"string\">'lv2_tag_list'</span>: [{<span class=\"string\">'score'</span>: <span class=\"number\">0.915631</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'足球'</span>},</span><br><span class=\"line\"> {<span class=\"string\">'score'</span>: <span class=\"number\">0.803507</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'国际足球'</span>},</span><br><span class=\"line\"> {<span class=\"string\">'score'</span>: <span class=\"number\">0.77813</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'英超'</span>}],</span><br><span class=\"line\"> <span class=\"string\">'lv1_tag_list'</span>: [{<span class=\"string\">'score'</span>: <span class=\"number\">0.830915</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'体育'</span>}]}}</span><br></pre></td></tr></table></figure>\n<h3 id=\"情感倾向分析\"><a href=\"#情感倾向分析\" class=\"headerlink\" title=\"情感倾向分析\"></a>情感倾向分析</h3><p>对包含主观观点信息的文本进行情感极性类别(积极、消极、中性)的判断,并给出相应的置信度。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> aip <span class=\"keyword\">import</span> AipNlp</span><br><span class=\"line\">APP_ID = <span class=\"string\">'15438079'</span></span><br><span class=\"line\">API_KEY = <span class=\"string\">'rwAgYHnVN0bGkuksdvk7piio'</span></span><br><span class=\"line\">SECRET_KEY = <span class=\"string\">'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'</span></span><br><span class=\"line\">client = AipNlp(APP_ID,API_KEY, SECRET_KEY)</span><br><span class=\"line\">text = <span class=\"string\">"苹果是一家伟大的公司"</span></span><br><span class=\"line\">client.sentimentClassify(text)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">##结果展示</span></span><br><span class=\"line\">{<span class=\"string\">'log_id'</span>: <span class=\"number\">9106725229255700145</span>,</span><br><span class=\"line\"> <span class=\"string\">'text'</span>: <span class=\"string\">'苹果是一家伟大的公司'</span>,</span><br><span class=\"line\"> <span class=\"string\">'items'</span>: [{<span class=\"string\">'positive_prob'</span>: <span class=\"number\">0.727802</span>, //表示属于积极类别的概率</span><br><span class=\"line\"> <span class=\"string\">'confidence'</span>: <span class=\"number\">0.395115</span>, //表示分类的置信度</span><br><span class=\"line\"> <span class=\"string\">'negative_prob'</span>: <span class=\"number\">0.272198</span>, //表示属于消极类别的概率</span><br><span class=\"line\"> <span class=\"string\">'sentiment'</span>: <span class=\"number\">2</span>}]} //表示情感极性分类结果</span><br></pre></td></tr></table></figure>\n<h3 id=\"百度API优势\"><a href=\"#百度API优势\" class=\"headerlink\" title=\"百度API优势\"></a>百度API优势</h3><ol>\n<li><strong>永久免费</strong>。2018年5月11日,百度自然语言处理技术宣布对外永久免费,且不限调用量。</li>\n<li><strong>功能丰富</strong>。包括词法分析、依存句法分析、词向量表示、DNN语言模型、词义相似度、短文本相似度、评论观点抽取、情感倾向分析、文章标签、文章分类等。</li>\n<li><strong>接口易用</strong>。标准化接口封装,通过云计算调用可快速使用工具,降低开发人力成本。</li>\n</ol>\n<h2 id=\"bosonnlp\"><a href=\"#bosonnlp\" class=\"headerlink\" title=\"bosonnlp\"></a>bosonnlp</h2><h3 id=\"新闻分类\"><a href=\"#新闻分类\" class=\"headerlink\" title=\"新闻分类\"></a>新闻分类</h3><p>将新闻文本归类到预设的 14 个分类当中:0、体育 1、教育 2、财经 3、社会 4、娱乐 5、军事 6、国内 7、科技 8、互联网 9、房地产 10、国际 11、女人 12、汽车 13、游戏。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">from bosonnlp import BosonNLP</span><br><span class=\"line\">import os</span><br><span class=\"line\">nlp = BosonNLP('HpikDzL0.32936.Hi3mJFXh1P1b')</span><br><span class=\"line\">nlp.classify(['俄否决安理会谴责叙军战机空袭阿勒颇平民',</span><br><span class=\"line\"> '邓紫棋谈男友林宥嘉:我觉得我比他唱得好',</span><br><span class=\"line\"> 'Facebook收购印度初创公司'])</span><br><span class=\"line\"> </span><br><span class=\"line\">##结果展示</span><br><span class=\"line\">[5, 4, 8]</span><br></pre></td></tr></table></figure>\n<h3 id=\"情感分析\"><a href=\"#情感分析\" class=\"headerlink\" title=\"情感分析\"></a>情感分析</h3><p>其接口将文本的情感分为负面和非负面两类。本引擎用微博、新闻、汽车、餐饮等不同行业语料进行标注和机器学习,调用时请通过 URL 参数选择特定的模型,以获得最佳的情感判断准确率。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> bosonnlp <span class=\"keyword\">import</span> BosonNLP</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\">nlp = BosonNLP(<span class=\"string\">'HpikDzL0.32936.Hi3mJFXh1P1b'</span>)</span><br><span class=\"line\">s = [<span class=\"string\">'苹果是一家垃圾公司'</span>, <span class=\"string\">'美好的世界'</span>]</span><br><span class=\"line\">result = nlp.sentiment(s)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(result)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">##结果展示</span></span><br><span class=\"line\">[[<span class=\"number\">0.01819305539338867</span>, <span class=\"number\">0.9818069446066113</span>], [<span class=\"number\">0.92706110187413</span>, <span class=\"number\">0.07293889812586994</span>]]</span><br></pre></td></tr></table></figure>\n<h3 id=\"bosonnlp的优势\"><a href=\"#bosonnlp的优势\" class=\"headerlink\" title=\"bosonnlp的优势\"></a>bosonnlp的优势</h3><p>BOSON实验室的优点在于对于某一个单文本或者多文本,他能够得出一个<strong>综合的分析结果</strong>。</p>\n<ol>\n<li><strong>单文本分析</strong>:只输入一段文本,输出的分析结果包括词性分析、实体识别、依存文法、情感分析、新闻摘要、新闻分类、关键词提取、语义联系。</li>\n<li><strong>多文本分析</strong>:同时输入多段文本、新闻,输出的分析结果包括话题聚类、情感分析、词云图、词频图。</li>\n</ol>\n<h1 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h1><h2 id=\"NLP最新进展\"><a href=\"#NLP最新进展\" class=\"headerlink\" title=\"NLP最新进展\"></a>NLP最新进展</h2><h3 id=\"文本分类通用规律\"><a href=\"#文本分类通用规律\" class=\"headerlink\" title=\"文本分类通用规律\"></a>文本分类通用规律</h3><p>2018年7月,谷歌做了45万次不同类型的文本分类后,总结出一个对于文本分类和情感分析而言通用的“模型选择算法”。</p>\n<p><strong>文本分类的流程图</strong></p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">graph LR</span><br><span class=\"line\">搜集数据-->探索数据</span><br><span class=\"line\">探索数据-->选择模型</span><br><span class=\"line\">选择模型-->准备数据</span><br><span class=\"line\">准备数据-->构建训练和评估模型</span><br><span class=\"line\">构建训练和评估模型-->调优超参数</span><br><span class=\"line\">调优超参数-->部署模型</span><br></pre></td></tr></table></figure>\n<p>我们预处理数据的方式将取决于我们选择的模型。模型可以大致分为两类:使用单词排序信息的模型(序列模型),以及仅将文本视为单词的“bags”(sets)的模型(n-gram模型)。</p>\n<ul>\n<li><p><strong>序列模型</strong>:卷积神经网络(CNN),递归神经网络(RNN)及其变体。</p>\n</li>\n<li><p><strong>n-gram模型</strong>:逻辑回归(LR),支持向量机(SVM),梯度提升树等。</p>\n</li>\n</ul>\n<p><strong>结论</strong>:</p>\n<p>在实验中,其观察到“样本数”(S)与“每个样本的单词数”(W)的比率与模型的性能具有相关性。</p>\n<p>当该<strong>比率的值很小(<1500)</strong>时,以n-gram作为输入的小型多层感知机表现得更好,或者说至少与序列模型一样好。 MLP易于定义和理解,而且比序列模型花费的计算时间更少。(感觉如果数据量特别大的python这个工具也不一定能用)</p>\n<p>当此<strong>比率的值很大(> = 1500)</strong>时,使用序列模型。</p>\n<h3 id=\"BERT\"><a href=\"#BERT\" class=\"headerlink\" title=\"BERT\"></a>BERT</h3><p>2018年10月,谷歌新发布的一种模型<strong>BERT</strong>(Bidirectional Encoder Representations from Transformers),在NLP业内引起巨大反响。BERT在机器阅读理解测试SQuAD1.1中表现出惊人的成绩:全部两个衡量指标上全面超越人类,并且还在11种不同NLP任务中创出最佳成绩。</p>\n<p><strong>具体做法:</strong></p>\n<ol>\n<li>采取新的预训练的目标函数:the “masked language model” (MLM) 对于mask随机输入一些单词(80%的时间真的用[MASK]取代被选中的词,10%的时间用一个随机词取代它,10%的时间保持不变),然后在预训练中对它们进行预测。这样做的好处是学习到的表征能够融合两个方向上的内容。</li>\n<li>增加句子级别的任务:“next sentence prediction”。作者同时预训练了一个“next sentence prediction”任务。具体做法是随机替换一些句子,然后利用上一句进行此句是否为下一句的预测。</li>\n</ol>\n<p><strong>模型框架</strong>:</p>\n<p>BASE:L=12, H=768, A=12, Total Parameters=110M</p>\n<p>LARGE:L=24, H=1024, A=16, Total Parameters=340M</p>\n<p>这里的L为神经网络层数,H为隐藏向量参数,A为自注意力头数</p>\n<p><strong>结论</strong>:</p>\n<ul>\n<li>在大数据中效果好,但如果模型经过足够的预训练,在小任务中大模型也能增长。</li>\n<li>计算成本相当高,作者动用了谷歌 Cloud AI 资源,用了 64 颗 TPU,算了 4 天,模型参数寻优的训练过程才收敛。</li>\n</ul>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h1 id=\"概述\"><a href=\"#概述\" class=\"headerlink\" title=\"概述\"></a>概述</h1><p>此文档为了更好的理解自然语言处理(natural language processing),自然语言处理(NLP)就是开发能够理解人类语言的应用程序或服务。接下来会讨论一些NLP在国内外的发展状况和实际应用的一些例子,了解当前最前沿的NLP应用并掌握一定的NLP技术是接下来自己需要做的。</p>\n<h1 id=\"NLP简述\"><a href=\"#NLP简述\" class=\"headerlink\" title=\"NLP简述\"></a>NLP简述</h1><p>NLP是研究人与人交际中以及在人与计算机交际中的语言问题的一门学科。NLP主要有五个主要任务:分类、匹配、翻译、结构化预测、与序贯决策过程。NLP中的绝大多数问题皆可归入其中的一个,如下表所示。在这些任务中,单词、词组、语句、段落甚至文档通常被看作标记(字符串)序列而采取相似的处理,尽管它们的复杂度并不相同。事实上,语句是 NLP 中最常用的处理单元。</p>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th style=\"text-align:left\">任务</th>\n<th style=\"text-align:left\">描述</th>\n<th style=\"text-align:left\">应用</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:left\">分类</td>\n<td style=\"text-align:left\">给每个string指定一个标签</td>\n<td style=\"text-align:left\">文本分类、情感分析</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">匹配</td>\n<td style=\"text-align:left\">匹配两个strings</td>\n<td style=\"text-align:left\">信息检索、问答系统</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">翻译</td>\n<td style=\"text-align:left\">翻译某种语言</td>\n<td style=\"text-align:left\">机器翻译、自动语音识别</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">结构化预测</td>\n<td style=\"text-align:left\">将每个string映射为一种结构</td>\n<td style=\"text-align:left\">命名实体识别、中文分词、词性标注、句法分析</td>\n</tr>\n<tr>\n<td style=\"text-align:left\">有序决策过程</td>\n<td style=\"text-align:left\">对一个动态的过程做出反应</td>\n<td style=\"text-align:left\">多伦对话系统</td>\n</tr>\n</tbody>\n</table>\n</div>\n<h2 id=\"应用简介\"><a href=\"#应用简介\" class=\"headerlink\" title=\"应用简介\"></a>应用简介</h2><p><strong>文本分类</strong>:用电脑对文本集(或其他实体或物件)按照一定的分类体系或标准进行自动分类标记。</p>\n<p><strong>情感分析</strong>:又称意见挖掘、倾向性分析等。简单而言,是对带有情感色彩的主观性文本进行分析、处理、归纳和推理的过程。</p>\n<p><strong>信息检索</strong>:信息按一定的方式进行加工、整理、组织并存储起来,再根据信息用户特定的需要将相关信息准确的查找出来的过程。</p>\n<p><strong>问答系统</strong>:用准确、简洁的自然语言回答用户用自然语言提出的问题。</p>\n<p><strong>机器翻译</strong>:是利用计算机将一种自然语言(源语言)转换为另一种自然语言(目标语言)的过程,除了传统的文本翻译之外,还有比较特殊的手语翻译和唇语翻译。</p>\n<p><strong>自动语音识别</strong>:一种将人的语音转换为文本的技术。</p>\n<p><strong>命名实体识别</strong>:是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。</p>\n<p><strong>中文分词</strong>:指的是将一个汉字序列切分成一个个单独的词。</p>\n<p><strong>词性标注</strong>:利用语料库内单词的词性按其含义和上下文内容进行标记的文本数据处理技术。</p>\n<p><strong>句法分析</strong>:依存句法分析接口可自动分析文本中的依存句法结构信息,利用句子中词与词之间的依存关系来表示词语的句法结构信息(如“主谓”、“动宾”、“定中”等结构关系),并用树状结构来表示整句的结构(如“主谓宾”、“定状补”等)。</p>\n<p><strong>多伦对话系统</strong>:(封闭域)多轮对话是一种,在人机对话中,初步明确用户意图之后,获取必要信息以最终得到明确用户指令的方式。</p>\n<h1 id=\"国内外NLP的应用\"><a href=\"#国内外NLP的应用\" class=\"headerlink\" title=\"国内外NLP的应用\"></a>国内外NLP的应用</h1><p>由于NLP领域在工业界内还属于探索状态,下表只是罗列了一下了解到的行业内较为优异的公司从事的领域和相关产品</p>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th style=\"text-align:center\"><strong>公司</strong></th>\n<th style=\"text-align:center\"><strong>关注点</strong></th>\n<th style=\"text-align:center\"><strong>产品</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td style=\"text-align:center\">谷歌</td>\n<td style=\"text-align:center\">机器翻译、知识图谱(智能化搜索引擎)</td>\n<td style=\"text-align:center\">谷歌翻译、谷歌浏览器</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">微软</td>\n<td style=\"text-align:center\">机器翻译、对话平台、中国文化、阅读理解</td>\n<td style=\"text-align:center\">微软翻译、小娜、小冰、微软对联</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">IBM</td>\n<td style=\"text-align:center\">对话平台运用在医疗、金融、物联网</td>\n<td style=\"text-align:center\">沃森</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">百度</td>\n<td style=\"text-align:center\">机器翻译、知识图谱、对话平台</td>\n<td style=\"text-align:center\">度秘、百度检索、小度机器人</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">阿里</td>\n<td style=\"text-align:center\">智能导购(技术快速的跟业务对接)</td>\n<td style=\"text-align:center\">优酷、YunOS、蚂蚁金服、资讯搜索</td>\n</tr>\n<tr>\n<td style=\"text-align:center\">腾讯</td>\n<td style=\"text-align:center\">社交、内容、游戏</td>\n<td style=\"text-align:center\">腾讯觅影、微信、qq</td>\n</tr>\n</tbody>\n</table>\n</div>\n<h1 id=\"NLP技术支持\"><a href=\"#NLP技术支持\" class=\"headerlink\" title=\"NLP技术支持\"></a>NLP技术支持</h1><p>接下来研究的重点在于文本分类和情感分析方向,百度,bosonnlp,阿里(收费),腾讯(收费)等公司都提供了优秀的api接口。下面是关于两方面的简单应用。</p>\n<h2 id=\"百度API\"><a href=\"#百度API\" class=\"headerlink\" title=\"百度API\"></a>百度API</h2><h3 id=\"文本分类\"><a href=\"#文本分类\" class=\"headerlink\" title=\"文本分类\"></a>文本分类</h3><p>对文章按照内容类型进行自动分类,首批支持娱乐、体育、科技等26个主流内容类型,为文章聚类、文本内容分析等应用提供基础技术支持。 目前支持的一级粗粒度分类类目如下: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、综合。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> aip <span class=\"keyword\">import</span> AipNlp</span><br><span class=\"line\">APP_ID = <span class=\"string\">'15438079'</span></span><br><span class=\"line\">API_KEY = <span class=\"string\">'rwAgYHnVN0bGkuksdvk7piio'</span></span><br><span class=\"line\">SECRET_KEY = <span class=\"string\">'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'</span></span><br><span class=\"line\">client = AipNlp(APP_ID,API_KEY, SECRET_KEY)</span><br><span class=\"line\">title = <span class=\"string\">'欧洲冠军杯足球赛'</span></span><br><span class=\"line\">content = <span class=\"string\">"欧洲冠军联赛是欧洲足球协会联盟主办的年度足球比赛,代表欧洲俱乐部足球最高荣誉和水平,被认为是全世界最高素质、最具影响力以及最高水平的俱乐部赛事,亦是世界上奖金最高的足球赛事和体育赛事之一。"</span></span><br><span class=\"line\">client.topic(title, content)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">##结果展示</span></span><br><span class=\"line\">{<span class=\"string\">'log_id'</span>: <span class=\"number\">6074973177044352305</span>,</span><br><span class=\"line\"> <span class=\"string\">'item'</span>: {<span class=\"string\">'lv2_tag_list'</span>: [{<span class=\"string\">'score'</span>: <span class=\"number\">0.915631</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'足球'</span>},</span><br><span class=\"line\"> {<span class=\"string\">'score'</span>: <span class=\"number\">0.803507</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'国际足球'</span>},</span><br><span class=\"line\"> {<span class=\"string\">'score'</span>: <span class=\"number\">0.77813</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'英超'</span>}],</span><br><span class=\"line\"> <span class=\"string\">'lv1_tag_list'</span>: [{<span class=\"string\">'score'</span>: <span class=\"number\">0.830915</span>, <span class=\"string\">'tag'</span>: <span class=\"string\">'体育'</span>}]}}</span><br></pre></td></tr></table></figure>\n<h3 id=\"情感倾向分析\"><a href=\"#情感倾向分析\" class=\"headerlink\" title=\"情感倾向分析\"></a>情感倾向分析</h3><p>对包含主观观点信息的文本进行情感极性类别(积极、消极、中性)的判断,并给出相应的置信度。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> aip <span class=\"keyword\">import</span> AipNlp</span><br><span class=\"line\">APP_ID = <span class=\"string\">'15438079'</span></span><br><span class=\"line\">API_KEY = <span class=\"string\">'rwAgYHnVN0bGkuksdvk7piio'</span></span><br><span class=\"line\">SECRET_KEY = <span class=\"string\">'PAw4ffLotYoUMKcOS57oF2WrOtCjWTRC'</span></span><br><span class=\"line\">client = AipNlp(APP_ID,API_KEY, SECRET_KEY)</span><br><span class=\"line\">text = <span class=\"string\">"苹果是一家伟大的公司"</span></span><br><span class=\"line\">client.sentimentClassify(text)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">##结果展示</span></span><br><span class=\"line\">{<span class=\"string\">'log_id'</span>: <span class=\"number\">9106725229255700145</span>,</span><br><span class=\"line\"> <span class=\"string\">'text'</span>: <span class=\"string\">'苹果是一家伟大的公司'</span>,</span><br><span class=\"line\"> <span class=\"string\">'items'</span>: [{<span class=\"string\">'positive_prob'</span>: <span class=\"number\">0.727802</span>, //表示属于积极类别的概率</span><br><span class=\"line\"> <span class=\"string\">'confidence'</span>: <span class=\"number\">0.395115</span>, //表示分类的置信度</span><br><span class=\"line\"> <span class=\"string\">'negative_prob'</span>: <span class=\"number\">0.272198</span>, //表示属于消极类别的概率</span><br><span class=\"line\"> <span class=\"string\">'sentiment'</span>: <span class=\"number\">2</span>}]} //表示情感极性分类结果</span><br></pre></td></tr></table></figure>\n<h3 id=\"百度API优势\"><a href=\"#百度API优势\" class=\"headerlink\" title=\"百度API优势\"></a>百度API优势</h3><ol>\n<li><strong>永久免费</strong>。2018年5月11日,百度自然语言处理技术宣布对外永久免费,且不限调用量。</li>\n<li><strong>功能丰富</strong>。包括词法分析、依存句法分析、词向量表示、DNN语言模型、词义相似度、短文本相似度、评论观点抽取、情感倾向分析、文章标签、文章分类等。</li>\n<li><strong>接口易用</strong>。标准化接口封装,通过云计算调用可快速使用工具,降低开发人力成本。</li>\n</ol>\n<h2 id=\"bosonnlp\"><a href=\"#bosonnlp\" class=\"headerlink\" title=\"bosonnlp\"></a>bosonnlp</h2><h3 id=\"新闻分类\"><a href=\"#新闻分类\" class=\"headerlink\" title=\"新闻分类\"></a>新闻分类</h3><p>将新闻文本归类到预设的 14 个分类当中:0、体育 1、教育 2、财经 3、社会 4、娱乐 5、军事 6、国内 7、科技 8、互联网 9、房地产 10、国际 11、女人 12、汽车 13、游戏。</p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">from bosonnlp import BosonNLP</span><br><span class=\"line\">import os</span><br><span class=\"line\">nlp = BosonNLP('HpikDzL0.32936.Hi3mJFXh1P1b')</span><br><span class=\"line\">nlp.classify(['俄否决安理会谴责叙军战机空袭阿勒颇平民',</span><br><span class=\"line\"> '邓紫棋谈男友林宥嘉:我觉得我比他唱得好',</span><br><span class=\"line\"> 'Facebook收购印度初创公司'])</span><br><span class=\"line\"> </span><br><span class=\"line\">##结果展示</span><br><span class=\"line\">[5, 4, 8]</span><br></pre></td></tr></table></figure>\n<h3 id=\"情感分析\"><a href=\"#情感分析\" class=\"headerlink\" title=\"情感分析\"></a>情感分析</h3><p>其接口将文本的情感分为负面和非负面两类。本引擎用微博、新闻、汽车、餐饮等不同行业语料进行标注和机器学习,调用时请通过 URL 参数选择特定的模型,以获得最佳的情感判断准确率。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> bosonnlp <span class=\"keyword\">import</span> BosonNLP</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\">nlp = BosonNLP(<span class=\"string\">'HpikDzL0.32936.Hi3mJFXh1P1b'</span>)</span><br><span class=\"line\">s = [<span class=\"string\">'苹果是一家垃圾公司'</span>, <span class=\"string\">'美好的世界'</span>]</span><br><span class=\"line\">result = nlp.sentiment(s)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(result)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">##结果展示</span></span><br><span class=\"line\">[[<span class=\"number\">0.01819305539338867</span>, <span class=\"number\">0.9818069446066113</span>], [<span class=\"number\">0.92706110187413</span>, <span class=\"number\">0.07293889812586994</span>]]</span><br></pre></td></tr></table></figure>\n<h3 id=\"bosonnlp的优势\"><a href=\"#bosonnlp的优势\" class=\"headerlink\" title=\"bosonnlp的优势\"></a>bosonnlp的优势</h3><p>BOSON实验室的优点在于对于某一个单文本或者多文本,他能够得出一个<strong>综合的分析结果</strong>。</p>\n<ol>\n<li><strong>单文本分析</strong>:只输入一段文本,输出的分析结果包括词性分析、实体识别、依存文法、情感分析、新闻摘要、新闻分类、关键词提取、语义联系。</li>\n<li><strong>多文本分析</strong>:同时输入多段文本、新闻,输出的分析结果包括话题聚类、情感分析、词云图、词频图。</li>\n</ol>\n<h1 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h1><h2 id=\"NLP最新进展\"><a href=\"#NLP最新进展\" class=\"headerlink\" title=\"NLP最新进展\"></a>NLP最新进展</h2><h3 id=\"文本分类通用规律\"><a href=\"#文本分类通用规律\" class=\"headerlink\" title=\"文本分类通用规律\"></a>文本分类通用规律</h3><p>2018年7月,谷歌做了45万次不同类型的文本分类后,总结出一个对于文本分类和情感分析而言通用的“模型选择算法”。</p>\n<p><strong>文本分类的流程图</strong></p>\n<figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">graph LR</span><br><span class=\"line\">搜集数据-->探索数据</span><br><span class=\"line\">探索数据-->选择模型</span><br><span class=\"line\">选择模型-->准备数据</span><br><span class=\"line\">准备数据-->构建训练和评估模型</span><br><span class=\"line\">构建训练和评估模型-->调优超参数</span><br><span class=\"line\">调优超参数-->部署模型</span><br></pre></td></tr></table></figure>\n<p>我们预处理数据的方式将取决于我们选择的模型。模型可以大致分为两类:使用单词排序信息的模型(序列模型),以及仅将文本视为单词的“bags”(sets)的模型(n-gram模型)。</p>\n<ul>\n<li><p><strong>序列模型</strong>:卷积神经网络(CNN),递归神经网络(RNN)及其变体。</p>\n</li>\n<li><p><strong>n-gram模型</strong>:逻辑回归(LR),支持向量机(SVM),梯度提升树等。</p>\n</li>\n</ul>\n<p><strong>结论</strong>:</p>\n<p>在实验中,其观察到“样本数”(S)与“每个样本的单词数”(W)的比率与模型的性能具有相关性。</p>\n<p>当该<strong>比率的值很小(<1500)</strong>时,以n-gram作为输入的小型多层感知机表现得更好,或者说至少与序列模型一样好。 MLP易于定义和理解,而且比序列模型花费的计算时间更少。(感觉如果数据量特别大的python这个工具也不一定能用)</p>\n<p>当此<strong>比率的值很大(> = 1500)</strong>时,使用序列模型。</p>\n<h3 id=\"BERT\"><a href=\"#BERT\" class=\"headerlink\" title=\"BERT\"></a>BERT</h3><p>2018年10月,谷歌新发布的一种模型<strong>BERT</strong>(Bidirectional Encoder Representations from Transformers),在NLP业内引起巨大反响。BERT在机器阅读理解测试SQuAD1.1中表现出惊人的成绩:全部两个衡量指标上全面超越人类,并且还在11种不同NLP任务中创出最佳成绩。</p>\n<p><strong>具体做法:</strong></p>\n<ol>\n<li>采取新的预训练的目标函数:the “masked language model” (MLM) 对于mask随机输入一些单词(80%的时间真的用[MASK]取代被选中的词,10%的时间用一个随机词取代它,10%的时间保持不变),然后在预训练中对它们进行预测。这样做的好处是学习到的表征能够融合两个方向上的内容。</li>\n<li>增加句子级别的任务:“next sentence prediction”。作者同时预训练了一个“next sentence prediction”任务。具体做法是随机替换一些句子,然后利用上一句进行此句是否为下一句的预测。</li>\n</ol>\n<p><strong>模型框架</strong>:</p>\n<p>BASE:L=12, H=768, A=12, Total Parameters=110M</p>\n<p>LARGE:L=24, H=1024, A=16, Total Parameters=340M</p>\n<p>这里的L为神经网络层数,H为隐藏向量参数,A为自注意力头数</p>\n<p><strong>结论</strong>:</p>\n<ul>\n<li>在大数据中效果好,但如果模型经过足够的预训练,在小任务中大模型也能增长。</li>\n<li>计算成本相当高,作者动用了谷歌 Cloud AI 资源,用了 64 颗 TPU,算了 4 天,模型参数寻优的训练过程才收敛。</li>\n</ul>\n"},{"title":"关联挖掘","mathjax":true,"date":"2021-09-18T08:46:37.000Z","description":null,"_content":"\n> \u0011对商品订单进行类目级别的关联挖掘,因为订单太少,无法用到商品这个级别,并且主要是因为做关联挖掘的目的是为了投放广告\n\n## 指标定义\n\n**项于项集**\n\n项,指我们分析数据中的**一个对象**,如啤酒;项集,就是**若干项**的项构成的集合,如集合{啤酒,面包}是一个2项集。\n\n**支持度(support)**\n\n某项集在数据集中出现的概率,支持度体现的是某个项集的频繁程度,只有**项集达到一定程度**,我们才会研究该项集的必要。\n\n其计算公式为:\n\n{% raw %}\n$$\nsupport(A\\cup B)=\\frac{{A\\cup B}}{All}\n$$\n{% endraw %}\n\n举个例子:AB在订单中同时出现了100次,总共有1000个订单,那$A\\cup B$项集的支持度就是0.1\n\n**置信度(confidence)**\n\n当用户购买了商品A后有多大概率会购买商品B,表示关联性的强弱。\n\n其计算公式为:\n{% raw %}\n$$\nconfidence(A\\rightarrow B)=\\frac{support(A\\cup B)}{support(A)}\n$$\n{% endraw %}\n举个例子,AB同时出现的订单有100次,而A购买的订单次数有200次,那置信度就是0.5\n\n**提升度(lift)**\n\n商品A的出现,对商品B的出现概率提升的程度,一般是大于1说明A的出现有组与商品B的出现\n\n其计算公式为:\n\n{% raw %}\n$$\nlift(A\\rightarrow B) = \\frac{confidence(A\\rightarrow B)}{support(B)}\n$$\n{% endraw %}\n\n举个例子,已知总订单100,AB同时出现时20:\n\n- 如果B的订单是20,A的订单时40,那么提升度为5,说明B商品强依赖于A,A的出现有利于B的出现\n- 同样,如果B的订单是50,A的订单是50,那么提升度为0.8,说明B商品和A商品没有强依赖关系。\n\n## 经典算法\n\n### Apriori\n\n具体步骤\n\n1. 计算项集K=1的所有支持度\n2. 选出大于最小支持度的数据\n3. 如果项集为空,则对应 K-1 项集的结果为最终结果。否则重复1,2\n\n该算法时间负责度比较高,相当于,针对每个K都要计算一次数据集。因此用学者利用树来优化算法\n\n### FP-Growth \n\n具体步骤\n\n1. 扫描数据,得到所有频繁1项集的的计数。然后删除支持度低于阈值的项,将1项集放入项头表,并按照支持度降序排列\n2. 扫描数据,将读到的原始数据剔除非频繁1项集,并按照支持度降序排列。\n3. 读入排序后的数据集,插入FP树,插入时按照排序后的顺序,插入FP树中,排序靠前的节点是祖先节点,而靠后的是子孙节点。如果有共用的祖先,则对应的公用祖先节点计数加1。插入后,如果有新节点出现,则项头表对应的节点会通过节点链表链接上新节点。直到所有的数据都插入到FP树后,FP树的建立完成。\n4. 从项头表的底部项依次向上找到项头表项对应的条件模式基。从条件模式基递归挖掘得到项头表项项的频繁项集。\n5. 如果不限制频繁项集的项数,则返回步骤4所有的频繁项集,否则只返回满足项数要求的频繁项集。\n\n该算法只需要扫面两次数据集就可以得出所有结果,因此相较于Apriori速度会有所提神,不过也得看具体可以提出多大的K\n\n## 代码\n\n对于apriori主要有两个包可以用efficient_apriori,mlxtend.frequent_patterns。前者是比较方便,但是问题在于没有具体的数字,后面这个包就比较舒服了,可以直接输出对应的指标。不过主要是我现在用的数据集小,如果数据集很大,那可能FP-Growth更合适一些不过要看具体场景,他的包fptools。\n\n```python\nfrom efficient_apriori import apriori\nfrom mlxtend.preprocessing import TransactionEncoder\nfrom mlxtend.frequent_patterns import apriori as apr\nfrom mlxtend.frequent_patterns import association_rules\nimport pandas as pd\nimport fptools as fp\n\n\ndata=[('牛奶','面包','尿布'),('可乐','面包', '尿布', '啤酒'),\n ('牛奶','尿布', '啤酒', '鸡蛋'),\n ('面包', '牛奶', '尿布', '啤酒'),\n ('面包', '牛奶', '尿布', '可乐')]\n\n# efficient_apriori包的使用\nitemsets, rules = apriori(data, min_support=0.5, min_confidence=1)\n\n# mlxtend.frequent_patterns包的使用\nEncoder = TransactionEncoder()\nencoded_data = Encoder.fit_transform(data)\ndf = pd.DataFrame(encoded_data, columns=Encoder.columns_)\nfrequent_items = apr(df, min_support=0.5, use_colnames=True, max_len=4).sort_values(by='support', ascending=False)\ndf_re = association_rules(frequent_items, metric='lift', min_threshold=1)\n\n#fp\ndata = [('牛奶','面包','尿布'),\n('可乐','面包', '尿布', '啤酒'),\n('牛奶','尿布', '啤酒', '鸡蛋'),\n('面包', '牛奶', '尿布', '啤酒'),\n('面包', '牛奶', '尿布', '可乐')]\nfis = [iset for iset in fp.frequent_itemsets(data, 2)]\nmfis = [iset for iset in fp.maximal_frequent_itemsets(data, 2)]\n```\n\n## 参考\n\n[知乎1](https://zhuanlan.zhihu.com/p/198842818)\n\n[知乎2](https://zhuanlan.zhihu.com/p/66944900)\n\n[博客](https://www.cnblogs.com/zhengxingpeng/p/6679280.html)\n\n","source":"_posts/关联挖掘.md","raw":"---\ntitle: 关联挖掘\ncategories:\n - null\ntags:\n - 机器学习\nmathjax: true\ndate: 2021-09-18 16:46:37\ndescription:\n---\n\n> \u0011对商品订单进行类目级别的关联挖掘,因为订单太少,无法用到商品这个级别,并且主要是因为做关联挖掘的目的是为了投放广告\n\n## 指标定义\n\n**项于项集**\n\n项,指我们分析数据中的**一个对象**,如啤酒;项集,就是**若干项**的项构成的集合,如集合{啤酒,面包}是一个2项集。\n\n**支持度(support)**\n\n某项集在数据集中出现的概率,支持度体现的是某个项集的频繁程度,只有**项集达到一定程度**,我们才会研究该项集的必要。\n\n其计算公式为:\n\n{% raw %}\n$$\nsupport(A\\cup B)=\\frac{{A\\cup B}}{All}\n$$\n{% endraw %}\n\n举个例子:AB在订单中同时出现了100次,总共有1000个订单,那$A\\cup B$项集的支持度就是0.1\n\n**置信度(confidence)**\n\n当用户购买了商品A后有多大概率会购买商品B,表示关联性的强弱。\n\n其计算公式为:\n{% raw %}\n$$\nconfidence(A\\rightarrow B)=\\frac{support(A\\cup B)}{support(A)}\n$$\n{% endraw %}\n举个例子,AB同时出现的订单有100次,而A购买的订单次数有200次,那置信度就是0.5\n\n**提升度(lift)**\n\n商品A的出现,对商品B的出现概率提升的程度,一般是大于1说明A的出现有组与商品B的出现\n\n其计算公式为:\n\n{% raw %}\n$$\nlift(A\\rightarrow B) = \\frac{confidence(A\\rightarrow B)}{support(B)}\n$$\n{% endraw %}\n\n举个例子,已知总订单100,AB同时出现时20:\n\n- 如果B的订单是20,A的订单时40,那么提升度为5,说明B商品强依赖于A,A的出现有利于B的出现\n- 同样,如果B的订单是50,A的订单是50,那么提升度为0.8,说明B商品和A商品没有强依赖关系。\n\n## 经典算法\n\n### Apriori\n\n具体步骤\n\n1. 计算项集K=1的所有支持度\n2. 选出大于最小支持度的数据\n3. 如果项集为空,则对应 K-1 项集的结果为最终结果。否则重复1,2\n\n该算法时间负责度比较高,相当于,针对每个K都要计算一次数据集。因此用学者利用树来优化算法\n\n### FP-Growth \n\n具体步骤\n\n1. 扫描数据,得到所有频繁1项集的的计数。然后删除支持度低于阈值的项,将1项集放入项头表,并按照支持度降序排列\n2. 扫描数据,将读到的原始数据剔除非频繁1项集,并按照支持度降序排列。\n3. 读入排序后的数据集,插入FP树,插入时按照排序后的顺序,插入FP树中,排序靠前的节点是祖先节点,而靠后的是子孙节点。如果有共用的祖先,则对应的公用祖先节点计数加1。插入后,如果有新节点出现,则项头表对应的节点会通过节点链表链接上新节点。直到所有的数据都插入到FP树后,FP树的建立完成。\n4. 从项头表的底部项依次向上找到项头表项对应的条件模式基。从条件模式基递归挖掘得到项头表项项的频繁项集。\n5. 如果不限制频繁项集的项数,则返回步骤4所有的频繁项集,否则只返回满足项数要求的频繁项集。\n\n该算法只需要扫面两次数据集就可以得出所有结果,因此相较于Apriori速度会有所提神,不过也得看具体可以提出多大的K\n\n## 代码\n\n对于apriori主要有两个包可以用efficient_apriori,mlxtend.frequent_patterns。前者是比较方便,但是问题在于没有具体的数字,后面这个包就比较舒服了,可以直接输出对应的指标。不过主要是我现在用的数据集小,如果数据集很大,那可能FP-Growth更合适一些不过要看具体场景,他的包fptools。\n\n```python\nfrom efficient_apriori import apriori\nfrom mlxtend.preprocessing import TransactionEncoder\nfrom mlxtend.frequent_patterns import apriori as apr\nfrom mlxtend.frequent_patterns import association_rules\nimport pandas as pd\nimport fptools as fp\n\n\ndata=[('牛奶','面包','尿布'),('可乐','面包', '尿布', '啤酒'),\n ('牛奶','尿布', '啤酒', '鸡蛋'),\n ('面包', '牛奶', '尿布', '啤酒'),\n ('面包', '牛奶', '尿布', '可乐')]\n\n# efficient_apriori包的使用\nitemsets, rules = apriori(data, min_support=0.5, min_confidence=1)\n\n# mlxtend.frequent_patterns包的使用\nEncoder = TransactionEncoder()\nencoded_data = Encoder.fit_transform(data)\ndf = pd.DataFrame(encoded_data, columns=Encoder.columns_)\nfrequent_items = apr(df, min_support=0.5, use_colnames=True, max_len=4).sort_values(by='support', ascending=False)\ndf_re = association_rules(frequent_items, metric='lift', min_threshold=1)\n\n#fp\ndata = [('牛奶','面包','尿布'),\n('可乐','面包', '尿布', '啤酒'),\n('牛奶','尿布', '啤酒', '鸡蛋'),\n('面包', '牛奶', '尿布', '啤酒'),\n('面包', '牛奶', '尿布', '可乐')]\nfis = [iset for iset in fp.frequent_itemsets(data, 2)]\nmfis = [iset for iset in fp.maximal_frequent_itemsets(data, 2)]\n```\n\n## 参考\n\n[知乎1](https://zhuanlan.zhihu.com/p/198842818)\n\n[知乎2](https://zhuanlan.zhihu.com/p/66944900)\n\n[博客](https://www.cnblogs.com/zhengxingpeng/p/6679280.html)\n\n","slug":"关联挖掘","published":1,"updated":"2021-09-18T08:50:35.749Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb5a000lso3w8970cuw5","content":"<blockquote>\n<p>\u0011对商品订单进行类目级别的关联挖掘,因为订单太少,无法用到商品这个级别,并且主要是因为做关联挖掘的目的是为了投放广告</p>\n</blockquote>\n<h2 id=\"指标定义\"><a href=\"#指标定义\" class=\"headerlink\" title=\"指标定义\"></a>指标定义</h2><p><strong>项于项集</strong></p>\n<p>项,指我们分析数据中的<strong>一个对象</strong>,如啤酒;项集,就是<strong>若干项</strong>的项构成的集合,如集合{啤酒,面包}是一个2项集。</p>\n<p><strong>支持度(support)</strong></p>\n<p>某项集在数据集中出现的概率,支持度体现的是某个项集的频繁程度,只有<strong>项集达到一定程度</strong>,我们才会研究该项集的必要。</p>\n<p>其计算公式为:</p>\n\n$$\nsupport(A\\cup B)=\\frac{{A\\cup B}}{All}\n$$\n\n<p>举个例子:AB在订单中同时出现了100次,总共有1000个订单,那$A\\cup B$项集的支持度就是0.1</p>\n<p><strong>置信度(confidence)</strong></p>\n<p>当用户购买了商品A后有多大概率会购买商品B,表示关联性的强弱。</p>\n<p>其计算公式为:<br>\n$$\nconfidence(A\\rightarrow B)=\\frac{support(A\\cup B)}{support(A)}\n$$\n<br>举个例子,AB同时出现的订单有100次,而A购买的订单次数有200次,那置信度就是0.5</p>\n<p><strong>提升度(lift)</strong></p>\n<p>商品A的出现,对商品B的出现概率提升的程度,一般是大于1说明A的出现有组与商品B的出现</p>\n<p>其计算公式为:</p>\n\n$$\nlift(A\\rightarrow B) = \\frac{confidence(A\\rightarrow B)}{support(B)}\n$$\n\n<p>举个例子,已知总订单100,AB同时出现时20:</p>\n<ul>\n<li>如果B的订单是20,A的订单时40,那么提升度为5,说明B商品强依赖于A,A的出现有利于B的出现</li>\n<li>同样,如果B的订单是50,A的订单是50,那么提升度为0.8,说明B商品和A商品没有强依赖关系。</li>\n</ul>\n<h2 id=\"经典算法\"><a href=\"#经典算法\" class=\"headerlink\" title=\"经典算法\"></a>经典算法</h2><h3 id=\"Apriori\"><a href=\"#Apriori\" class=\"headerlink\" title=\"Apriori\"></a>Apriori</h3><p>具体步骤</p>\n<ol>\n<li>计算项集K=1的所有支持度</li>\n<li>选出大于最小支持度的数据</li>\n<li>如果项集为空,则对应 K-1 项集的结果为最终结果。否则重复1,2</li>\n</ol>\n<p>该算法时间负责度比较高,相当于,针对每个K都要计算一次数据集。因此用学者利用树来优化算法</p>\n<h3 id=\"FP-Growth\"><a href=\"#FP-Growth\" class=\"headerlink\" title=\"FP-Growth\"></a>FP-Growth</h3><p>具体步骤</p>\n<ol>\n<li>扫描数据,得到所有频繁1项集的的计数。然后删除支持度低于阈值的项,将1项集放入项头表,并按照支持度降序排列</li>\n<li>扫描数据,将读到的原始数据剔除非频繁1项集,并按照支持度降序排列。</li>\n<li>读入排序后的数据集,插入FP树,插入时按照排序后的顺序,插入FP树中,排序靠前的节点是祖先节点,而靠后的是子孙节点。如果有共用的祖先,则对应的公用祖先节点计数加1。插入后,如果有新节点出现,则项头表对应的节点会通过节点链表链接上新节点。直到所有的数据都插入到FP树后,FP树的建立完成。</li>\n<li>从项头表的底部项依次向上找到项头表项对应的条件模式基。从条件模式基递归挖掘得到项头表项项的频繁项集。</li>\n<li>如果不限制频繁项集的项数,则返回步骤4所有的频繁项集,否则只返回满足项数要求的频繁项集。</li>\n</ol>\n<p>该算法只需要扫面两次数据集就可以得出所有结果,因此相较于Apriori速度会有所提神,不过也得看具体可以提出多大的K</p>\n<h2 id=\"代码\"><a href=\"#代码\" class=\"headerlink\" title=\"代码\"></a>代码</h2><p>对于apriori主要有两个包可以用efficient_apriori,mlxtend.frequent_patterns。前者是比较方便,但是问题在于没有具体的数字,后面这个包就比较舒服了,可以直接输出对应的指标。不过主要是我现在用的数据集小,如果数据集很大,那可能FP-Growth更合适一些不过要看具体场景,他的包fptools。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> efficient_apriori <span class=\"keyword\">import</span> apriori</span><br><span class=\"line\"><span class=\"keyword\">from</span> mlxtend.preprocessing <span class=\"keyword\">import</span> TransactionEncoder</span><br><span class=\"line\"><span class=\"keyword\">from</span> mlxtend.frequent_patterns <span class=\"keyword\">import</span> apriori <span class=\"keyword\">as</span> apr</span><br><span class=\"line\"><span class=\"keyword\">from</span> mlxtend.frequent_patterns <span class=\"keyword\">import</span> association_rules</span><br><span class=\"line\"><span class=\"keyword\">import</span> pandas <span class=\"keyword\">as</span> pd</span><br><span class=\"line\"><span class=\"keyword\">import</span> fptools <span class=\"keyword\">as</span> fp</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">data=[(<span class=\"string\">'牛奶'</span>,<span class=\"string\">'面包'</span>,<span class=\"string\">'尿布'</span>),(<span class=\"string\">'可乐'</span>,<span class=\"string\">'面包'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\"> (<span class=\"string\">'牛奶'</span>,<span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>, <span class=\"string\">'鸡蛋'</span>),</span><br><span class=\"line\"> (<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\"> (<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'可乐'</span>)]</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># efficient_apriori包的使用</span></span><br><span class=\"line\">itemsets, rules = apriori(data, min_support=<span class=\"number\">0.5</span>, min_confidence=<span class=\"number\">1</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># mlxtend.frequent_patterns包的使用</span></span><br><span class=\"line\">Encoder = TransactionEncoder()</span><br><span class=\"line\">encoded_data = Encoder.fit_transform(data)</span><br><span class=\"line\">df = pd.DataFrame(encoded_data, columns=Encoder.columns_)</span><br><span class=\"line\">frequent_items = apr(df, min_support=<span class=\"number\">0.5</span>, use_colnames=<span class=\"literal\">True</span>, max_len=<span class=\"number\">4</span>).sort_values(by=<span class=\"string\">'support'</span>, ascending=<span class=\"literal\">False</span>)</span><br><span class=\"line\">df_re = association_rules(frequent_items, metric=<span class=\"string\">'lift'</span>, min_threshold=<span class=\"number\">1</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">#fp</span></span><br><span class=\"line\">data = [(<span class=\"string\">'牛奶'</span>,<span class=\"string\">'面包'</span>,<span class=\"string\">'尿布'</span>),</span><br><span class=\"line\">(<span class=\"string\">'可乐'</span>,<span class=\"string\">'面包'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\">(<span class=\"string\">'牛奶'</span>,<span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>, <span class=\"string\">'鸡蛋'</span>),</span><br><span class=\"line\">(<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\">(<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'可乐'</span>)]</span><br><span class=\"line\">fis = [iset <span class=\"keyword\">for</span> iset <span class=\"keyword\">in</span> fp.frequent_itemsets(data, <span class=\"number\">2</span>)]</span><br><span class=\"line\">mfis = [iset <span class=\"keyword\">for</span> iset <span class=\"keyword\">in</span> fp.maximal_frequent_itemsets(data, <span class=\"number\">2</span>)]</span><br></pre></td></tr></table></figure>\n<h2 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h2><p><a href=\"https://zhuanlan.zhihu.com/p/198842818\">知乎1</a></p>\n<p><a href=\"https://zhuanlan.zhihu.com/p/66944900\">知乎2</a></p>\n<p><a href=\"https://www.cnblogs.com/zhengxingpeng/p/6679280.html\">博客</a></p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<blockquote>\n<p>\u0011对商品订单进行类目级别的关联挖掘,因为订单太少,无法用到商品这个级别,并且主要是因为做关联挖掘的目的是为了投放广告</p>\n</blockquote>\n<h2 id=\"指标定义\"><a href=\"#指标定义\" class=\"headerlink\" title=\"指标定义\"></a>指标定义</h2><p><strong>项于项集</strong></p>\n<p>项,指我们分析数据中的<strong>一个对象</strong>,如啤酒;项集,就是<strong>若干项</strong>的项构成的集合,如集合{啤酒,面包}是一个2项集。</p>\n<p><strong>支持度(support)</strong></p>\n<p>某项集在数据集中出现的概率,支持度体现的是某个项集的频繁程度,只有<strong>项集达到一定程度</strong>,我们才会研究该项集的必要。</p>\n<p>其计算公式为:</p>\n\n$$\nsupport(A\\cup B)=\\frac{{A\\cup B}}{All}\n$$\n\n<p>举个例子:AB在订单中同时出现了100次,总共有1000个订单,那$A\\cup B$项集的支持度就是0.1</p>\n<p><strong>置信度(confidence)</strong></p>\n<p>当用户购买了商品A后有多大概率会购买商品B,表示关联性的强弱。</p>\n<p>其计算公式为:<br>\n$$\nconfidence(A\\rightarrow B)=\\frac{support(A\\cup B)}{support(A)}\n$$\n<br>举个例子,AB同时出现的订单有100次,而A购买的订单次数有200次,那置信度就是0.5</p>\n<p><strong>提升度(lift)</strong></p>\n<p>商品A的出现,对商品B的出现概率提升的程度,一般是大于1说明A的出现有组与商品B的出现</p>\n<p>其计算公式为:</p>\n\n$$\nlift(A\\rightarrow B) = \\frac{confidence(A\\rightarrow B)}{support(B)}\n$$\n\n<p>举个例子,已知总订单100,AB同时出现时20:</p>\n<ul>\n<li>如果B的订单是20,A的订单时40,那么提升度为5,说明B商品强依赖于A,A的出现有利于B的出现</li>\n<li>同样,如果B的订单是50,A的订单是50,那么提升度为0.8,说明B商品和A商品没有强依赖关系。</li>\n</ul>\n<h2 id=\"经典算法\"><a href=\"#经典算法\" class=\"headerlink\" title=\"经典算法\"></a>经典算法</h2><h3 id=\"Apriori\"><a href=\"#Apriori\" class=\"headerlink\" title=\"Apriori\"></a>Apriori</h3><p>具体步骤</p>\n<ol>\n<li>计算项集K=1的所有支持度</li>\n<li>选出大于最小支持度的数据</li>\n<li>如果项集为空,则对应 K-1 项集的结果为最终结果。否则重复1,2</li>\n</ol>\n<p>该算法时间负责度比较高,相当于,针对每个K都要计算一次数据集。因此用学者利用树来优化算法</p>\n<h3 id=\"FP-Growth\"><a href=\"#FP-Growth\" class=\"headerlink\" title=\"FP-Growth\"></a>FP-Growth</h3><p>具体步骤</p>\n<ol>\n<li>扫描数据,得到所有频繁1项集的的计数。然后删除支持度低于阈值的项,将1项集放入项头表,并按照支持度降序排列</li>\n<li>扫描数据,将读到的原始数据剔除非频繁1项集,并按照支持度降序排列。</li>\n<li>读入排序后的数据集,插入FP树,插入时按照排序后的顺序,插入FP树中,排序靠前的节点是祖先节点,而靠后的是子孙节点。如果有共用的祖先,则对应的公用祖先节点计数加1。插入后,如果有新节点出现,则项头表对应的节点会通过节点链表链接上新节点。直到所有的数据都插入到FP树后,FP树的建立完成。</li>\n<li>从项头表的底部项依次向上找到项头表项对应的条件模式基。从条件模式基递归挖掘得到项头表项项的频繁项集。</li>\n<li>如果不限制频繁项集的项数,则返回步骤4所有的频繁项集,否则只返回满足项数要求的频繁项集。</li>\n</ol>\n<p>该算法只需要扫面两次数据集就可以得出所有结果,因此相较于Apriori速度会有所提神,不过也得看具体可以提出多大的K</p>\n<h2 id=\"代码\"><a href=\"#代码\" class=\"headerlink\" title=\"代码\"></a>代码</h2><p>对于apriori主要有两个包可以用efficient_apriori,mlxtend.frequent_patterns。前者是比较方便,但是问题在于没有具体的数字,后面这个包就比较舒服了,可以直接输出对应的指标。不过主要是我现在用的数据集小,如果数据集很大,那可能FP-Growth更合适一些不过要看具体场景,他的包fptools。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> efficient_apriori <span class=\"keyword\">import</span> apriori</span><br><span class=\"line\"><span class=\"keyword\">from</span> mlxtend.preprocessing <span class=\"keyword\">import</span> TransactionEncoder</span><br><span class=\"line\"><span class=\"keyword\">from</span> mlxtend.frequent_patterns <span class=\"keyword\">import</span> apriori <span class=\"keyword\">as</span> apr</span><br><span class=\"line\"><span class=\"keyword\">from</span> mlxtend.frequent_patterns <span class=\"keyword\">import</span> association_rules</span><br><span class=\"line\"><span class=\"keyword\">import</span> pandas <span class=\"keyword\">as</span> pd</span><br><span class=\"line\"><span class=\"keyword\">import</span> fptools <span class=\"keyword\">as</span> fp</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">data=[(<span class=\"string\">'牛奶'</span>,<span class=\"string\">'面包'</span>,<span class=\"string\">'尿布'</span>),(<span class=\"string\">'可乐'</span>,<span class=\"string\">'面包'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\"> (<span class=\"string\">'牛奶'</span>,<span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>, <span class=\"string\">'鸡蛋'</span>),</span><br><span class=\"line\"> (<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\"> (<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'可乐'</span>)]</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># efficient_apriori包的使用</span></span><br><span class=\"line\">itemsets, rules = apriori(data, min_support=<span class=\"number\">0.5</span>, min_confidence=<span class=\"number\">1</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># mlxtend.frequent_patterns包的使用</span></span><br><span class=\"line\">Encoder = TransactionEncoder()</span><br><span class=\"line\">encoded_data = Encoder.fit_transform(data)</span><br><span class=\"line\">df = pd.DataFrame(encoded_data, columns=Encoder.columns_)</span><br><span class=\"line\">frequent_items = apr(df, min_support=<span class=\"number\">0.5</span>, use_colnames=<span class=\"literal\">True</span>, max_len=<span class=\"number\">4</span>).sort_values(by=<span class=\"string\">'support'</span>, ascending=<span class=\"literal\">False</span>)</span><br><span class=\"line\">df_re = association_rules(frequent_items, metric=<span class=\"string\">'lift'</span>, min_threshold=<span class=\"number\">1</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">#fp</span></span><br><span class=\"line\">data = [(<span class=\"string\">'牛奶'</span>,<span class=\"string\">'面包'</span>,<span class=\"string\">'尿布'</span>),</span><br><span class=\"line\">(<span class=\"string\">'可乐'</span>,<span class=\"string\">'面包'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\">(<span class=\"string\">'牛奶'</span>,<span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>, <span class=\"string\">'鸡蛋'</span>),</span><br><span class=\"line\">(<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'啤酒'</span>),</span><br><span class=\"line\">(<span class=\"string\">'面包'</span>, <span class=\"string\">'牛奶'</span>, <span class=\"string\">'尿布'</span>, <span class=\"string\">'可乐'</span>)]</span><br><span class=\"line\">fis = [iset <span class=\"keyword\">for</span> iset <span class=\"keyword\">in</span> fp.frequent_itemsets(data, <span class=\"number\">2</span>)]</span><br><span class=\"line\">mfis = [iset <span class=\"keyword\">for</span> iset <span class=\"keyword\">in</span> fp.maximal_frequent_itemsets(data, <span class=\"number\">2</span>)]</span><br></pre></td></tr></table></figure>\n<h2 id=\"参考\"><a href=\"#参考\" class=\"headerlink\" title=\"参考\"></a>参考</h2><p><a href=\"https://zhuanlan.zhihu.com/p/198842818\">知乎1</a></p>\n<p><a href=\"https://zhuanlan.zhihu.com/p/66944900\">知乎2</a></p>\n<p><a href=\"https://www.cnblogs.com/zhengxingpeng/p/6679280.html\">博客</a></p>\n"},{"title":"多进程、多线程","date":"2021-04-11T03:23:23.000Z","description":["此文档写于2020年,建成于博客创立之前。"],"_content":"\n> \t这周接到1个需求,项目经理觉得我这边构造数据太过缓慢,由于数据量过大,以前数据构造完后将其导入MPP,利用copy_from速度还是很快的,一般为10W/s。现在换成Kafka消息队列,又由于python库自带的原因(这个是组里大神告诉我的),无法像java开发利用list导数据,因此我只能一条一条以json的形式将数据放入消息队列,一般为800条/s。原先构造5000W数据需要7H,经过优化后暂时需要3.5H,性能提升一倍。有点小开心,话不多说,石海同学开始将一下个人对于标题的理解。。。。\n>\n> \t进一步优化利用多进程的JoinableQueue,一边一直产生数据,另一边一直消费数据,现在5000W数据大约需要1.5H,性能较最初提升了近五倍。\n\n## 历史\n\n上古年代:在很久很久以前,当时主流的磁盘操作系统MS-DOS是只支持单任务操作的,就打个比方,如果我想在电脑上听音乐和看电影,是不能同时开启的,只能先听音乐后看电影,或者位置互换。\n\n2002:横空出世的Intel Pentium 4HT处理器,提出了cpu多线程(SMT),其支持一个cpu进行多任务开启。\n\n————你总不能要求Guido1989年为了打发圣诞节假期发明的一种编译语言还要设计一下多线程的部分。\n\n2006:在秋季的英特尔信息技术峰会上,Inter总裁宣布,其在11月份将会交付第一台4核cpu处理器,而这距离双核发布还不到1年,支持多核时代就此拉开序幕。\n\n————为什么说python多线程是历史遗留问题,因为在当时想要在两个或者更多的处理器对同一个对象运行时,为了保护线程数据完整性和状态同步,最简单的方法就是加锁,于是出现了GIL这把超级大锁。\n\n2006-至今:GPU也浩浩荡荡发展了十几年了,过程我就不细说了(因为不懂),总之对于图象类任务GPU和CPU不是一个量级上的,以前看过一个视频,很好的解释了CPU和GPU的区别。比如我要画一幅图,cpu需要一笔一划将他画出来,而GPU是直接在脑子里构思好,一炮就把图打出来了。因此对于神经网络这个模型,多个神经元同时进行计算,GPU比CPU快太多。。。。。\n\n## 编译器、解释器、IDE\n\n**编译器**:对于C、C++这类语言需要先编译后运行,其中编译的时候就是编译器。\n\n**解释器**:对于python、PHP、JavaScript等就是利用解释器,’一边编译一边解释‘,不过速度会比较慢,因此产生一种叫预编译的东西,python在运行时就先生成pyc的二进制临时文件,在出结果。\n\n预编译:Java、C#,运行前将源代码编译成中间代码,然后再解释器运行,这种执行效率比编译运行会有效率一些,避免重复编译。\n\n**IDE**:集成[开发环境](https://baike.baidu.com/item/开发环境)([IDE](https://baike.baidu.com/item/IDE),Integrated Development Environment ),用于提供程序开发环境的应用程序,python常用的就是pycharm和jupyter notebook。\n\n## GIL锁\n\n**概念:**GIL全称Global Interpreter Lock,其并不是python的特性,由于Cpython是大部分环境下默认的python执行环境,而在实现python解释器会自动上锁。\n\n**目的:**确保每个进程只有一个线程运行,所以在外面我们一般说python的多线程是伪线程。因为不管你有几个核,你用多线程也只能跑一个核。\n\n## 进程、线程、协程的利用\n\n**进程**:拥有代码和打开的文件资源、数据资源、独立的内存空间。\n\n**线程**:从属于进程,是程序的实际执行者,线程拥有自己的栈空间。\n\n**协程**:从属于线程,是一种比线程更加轻量级的存在。\n\n**总结:**\n\n**对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。**\n\n## 多进程\n\n我觉得最简单的讲,多进程就是你在任务窗口看建了几个任务。比如你电脑上有16个核,理论上你可以开16个进程,每个进程占满一个cpu。对python而言由于没有多线程的利用,所有在单进程无法满足需求时,自然得利用多进程。\n\n### 单任务单进程\n\n```python\nfrom multiprocessing import Process\n\n\ndef work(name):\n print(f'{name}')\n\n\nif __name__ == '__main__':\n p = Process(target=work,args=('single task',))\n p.start()\n p.join()\n\n```\n\n我还看到一种是用run不用start的,但大概看了下没什么特殊的,不过这个join只能用于start.所以建议还是都用start,这个join是阻塞的意思,简单来说:主进程和其他子进程结束的话,都等着,等我结束了才能结束。\n\n举个例子:\n\n```python\nfrom multiprocessing import Process\nimport os\nimport time\n\ndef work1(name):\n for i in range(5):\n print(f'{name}:{os.getpid()}')\n time.sleep(2)\n\n\ndef work2(name):\n for i in range(5):\n print(f'{name}:{os.getpid()}')\n\n\nif __name__ == '__main__':\n p1 = Process(target=work1,args=('single task1',))\n p2 = Process(target=work2,args=('single task2',))\n print(f'主进程:{os.getpid()}')\n p1.start()\n p1.join()\n p2.start()\n p2.join()\n```\n\n如果这么写,主进程你走你的,work2子进程你给我等一下。等我run完你在run。\n\n**注意:**每个子进程之间的join不是串行的,是并行的。既无论你多少个子进程,谁最后结束,谁关闭文件。\n\n### 单任务多进程\n\n比如你要启动50个进程,然后写50个子进程就太慢了。因此,对于多进程有一种新的形式。\n\n```python\ndef long_time_task(name):\n print('Run task %s (%s)...' % (name, os.getpid()))\n start = time.time()\n time.sleep(random.random() * 3)\n end = time.time()\n print('Task %s runs %0.2f seconds.' % (name, (end - start)))\n\nif __name__=='__main__':\n print('Parent process %s.' % os.getpid())\n p = Pool(4)\n for i in range(5):\n p.apply_async(long_time_task, args=(i,))\n print('Waiting for all subprocesses done...')\n p.close()\n p.join()\n print('All subprocesses done.')\n\n\n########################################################\n#运行结果\n########################################################\nParent process 7748.\nWaiting for all subprocesses done...\nRun task 0 (7780)...\nRun task 1 (7781)...\nRun task 2 (7782)...\nRun task 3 (7783)...\nRun task 4 (7781)...\nAll subprocesses done.\n```\n\n可以看到task1和task4的进程是一样的,因为只启动了4个进程,因此第5个进程需要等待其他进程结束才开始运行。\n\n**注意:**close不加直接写join是会报错的。\n\n### 多任务多进程\n\n我这边用的是经典的生产者和消费者模型,及一个模型构造生产者,一个模型构造消费者,两者通过自带的JoinableQueue进行通信。(不要用deque里面的队列!!)\n\n```python\nimport time\nimport random\nfrom multiprocessing import JoinableQueue,Process\ndef producer(q,name,food):\n for i in range(5):\n time.sleep(random.random())\n fd = '%s%s'%(food,i+1)\n q.put(fd)\n print('%s生产了一个%s'%(name,food))\n q.join() # 我启动了生产者之后,生产者函数一直在生成数据,直到生产完所有数据将队列q.join()一下,意思是当我生产的数据都被消费者消费完之后 队列的阻塞才结束。\ndef consumer(q,name): # 消费者不需要像Queue那样判断从队列里拿到None再退出执行消费者函数了\n while True:\n food = q.get()\n time.sleep(random.random())\n print('%s吃了%s'%(name,food))\n q.task_done() # 消费者每次从队列里面q.get()一个数据,处理过后就使用队列.task_done()\nif __name__ == '__main__':\n jq = JoinableQueue()\n p =Process(target=producer,args=(jq,'喜洋洋','包子'))\n p.start()\n c = Process(target=consumer,args=(jq,'灰太狼'))\n c.daemon = True # 守护进程,如果用Pool就不用这个也没事\n c.start()\n p.join() # 阻塞生产者\n```\n\n具体实现情况\n\n1. 启动一个生产者,和一个消费者(这里可以用多进程消费),看具体时间\n2. 生产者结束后将JoinableQueue进行阻塞,直到队列全部被消费后,才结束进程。\n\nQueue与JoinableQueue的区别\n\n1. Queue在消费者中需要if判断队列,不然的话就陷入死循环。而jionableQueue不用,其只需要将队列堵塞后,队列消费完程序才解决。\n2. Queue很难控制,因为如果只是判断队列是否存在,就需要考虑产生和消耗队列的时间。因此我认为还是JoinableQueue好\n\n## 多线程\n\n开头就说了,python虽然线程是真正的线程,但在解释器执行的时候会遇到GIL锁,导致其无论多少核,只能跑满一个核,所以对于比较简单的可以运行多线程,好像在爬虫里面运用多线程的比较多。\n\n那为什么有的程序一个进程一开用了N个cpu,那是因为他进程中存在利用c扩写的库,他将关键的部分用C/C++写成python扩展,其他还用python写。一般计算密集型的程序都会用C代码编写并通过扩展的方式集成到python脚本(numpy)。在扩展中完全可以用C创建原生线程,而且不用锁GIL,充分利用CPU的计算资源。\n\n```python\nfrom threading import Thread\nimport time\n\ndef work1():\n print('work1 has working')\n time.sleep(3)\n\ndef work2():\n print('work2 has working')\n time.sleep(5)\n\ndef work3():\n print('work3 has working')\n time.sleep(8)\n\nif __name__ == '__main__':\n start_time =time.time()\n p1 = Thread(target=work1)\n p2 = Thread(target=work2)\n p3 = Thread(target=work3)\n p1.start()\n p2.start()\n p3.start()\n p3.join()\n print(f'cost time {time.time()-start_time}')\n```\n\n## 问题\n\n```\n能否利用Pool批量构造生产者和消费者,\n暂时只能做到Pool构造消费者,多个Process构造生产者进行生产。。。\n(除非使用process分别构造生产者和消费者模型)\n```\n\n\n\n\n\n\n\n\n\n","source":"_posts/多进程、多线程(python).md","raw":"---\ntitle: 多进程、多线程\ndate: 2021-04-11 11:23:23\ncategories:\n- python\ntags:\n- 工程开发\ndescription:\n- 此文档写于2020年,建成于博客创立之前。\n---\n\n> \t这周接到1个需求,项目经理觉得我这边构造数据太过缓慢,由于数据量过大,以前数据构造完后将其导入MPP,利用copy_from速度还是很快的,一般为10W/s。现在换成Kafka消息队列,又由于python库自带的原因(这个是组里大神告诉我的),无法像java开发利用list导数据,因此我只能一条一条以json的形式将数据放入消息队列,一般为800条/s。原先构造5000W数据需要7H,经过优化后暂时需要3.5H,性能提升一倍。有点小开心,话不多说,石海同学开始将一下个人对于标题的理解。。。。\n>\n> \t进一步优化利用多进程的JoinableQueue,一边一直产生数据,另一边一直消费数据,现在5000W数据大约需要1.5H,性能较最初提升了近五倍。\n\n## 历史\n\n上古年代:在很久很久以前,当时主流的磁盘操作系统MS-DOS是只支持单任务操作的,就打个比方,如果我想在电脑上听音乐和看电影,是不能同时开启的,只能先听音乐后看电影,或者位置互换。\n\n2002:横空出世的Intel Pentium 4HT处理器,提出了cpu多线程(SMT),其支持一个cpu进行多任务开启。\n\n————你总不能要求Guido1989年为了打发圣诞节假期发明的一种编译语言还要设计一下多线程的部分。\n\n2006:在秋季的英特尔信息技术峰会上,Inter总裁宣布,其在11月份将会交付第一台4核cpu处理器,而这距离双核发布还不到1年,支持多核时代就此拉开序幕。\n\n————为什么说python多线程是历史遗留问题,因为在当时想要在两个或者更多的处理器对同一个对象运行时,为了保护线程数据完整性和状态同步,最简单的方法就是加锁,于是出现了GIL这把超级大锁。\n\n2006-至今:GPU也浩浩荡荡发展了十几年了,过程我就不细说了(因为不懂),总之对于图象类任务GPU和CPU不是一个量级上的,以前看过一个视频,很好的解释了CPU和GPU的区别。比如我要画一幅图,cpu需要一笔一划将他画出来,而GPU是直接在脑子里构思好,一炮就把图打出来了。因此对于神经网络这个模型,多个神经元同时进行计算,GPU比CPU快太多。。。。。\n\n## 编译器、解释器、IDE\n\n**编译器**:对于C、C++这类语言需要先编译后运行,其中编译的时候就是编译器。\n\n**解释器**:对于python、PHP、JavaScript等就是利用解释器,’一边编译一边解释‘,不过速度会比较慢,因此产生一种叫预编译的东西,python在运行时就先生成pyc的二进制临时文件,在出结果。\n\n预编译:Java、C#,运行前将源代码编译成中间代码,然后再解释器运行,这种执行效率比编译运行会有效率一些,避免重复编译。\n\n**IDE**:集成[开发环境](https://baike.baidu.com/item/开发环境)([IDE](https://baike.baidu.com/item/IDE),Integrated Development Environment ),用于提供程序开发环境的应用程序,python常用的就是pycharm和jupyter notebook。\n\n## GIL锁\n\n**概念:**GIL全称Global Interpreter Lock,其并不是python的特性,由于Cpython是大部分环境下默认的python执行环境,而在实现python解释器会自动上锁。\n\n**目的:**确保每个进程只有一个线程运行,所以在外面我们一般说python的多线程是伪线程。因为不管你有几个核,你用多线程也只能跑一个核。\n\n## 进程、线程、协程的利用\n\n**进程**:拥有代码和打开的文件资源、数据资源、独立的内存空间。\n\n**线程**:从属于进程,是程序的实际执行者,线程拥有自己的栈空间。\n\n**协程**:从属于线程,是一种比线程更加轻量级的存在。\n\n**总结:**\n\n**对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。**\n\n## 多进程\n\n我觉得最简单的讲,多进程就是你在任务窗口看建了几个任务。比如你电脑上有16个核,理论上你可以开16个进程,每个进程占满一个cpu。对python而言由于没有多线程的利用,所有在单进程无法满足需求时,自然得利用多进程。\n\n### 单任务单进程\n\n```python\nfrom multiprocessing import Process\n\n\ndef work(name):\n print(f'{name}')\n\n\nif __name__ == '__main__':\n p = Process(target=work,args=('single task',))\n p.start()\n p.join()\n\n```\n\n我还看到一种是用run不用start的,但大概看了下没什么特殊的,不过这个join只能用于start.所以建议还是都用start,这个join是阻塞的意思,简单来说:主进程和其他子进程结束的话,都等着,等我结束了才能结束。\n\n举个例子:\n\n```python\nfrom multiprocessing import Process\nimport os\nimport time\n\ndef work1(name):\n for i in range(5):\n print(f'{name}:{os.getpid()}')\n time.sleep(2)\n\n\ndef work2(name):\n for i in range(5):\n print(f'{name}:{os.getpid()}')\n\n\nif __name__ == '__main__':\n p1 = Process(target=work1,args=('single task1',))\n p2 = Process(target=work2,args=('single task2',))\n print(f'主进程:{os.getpid()}')\n p1.start()\n p1.join()\n p2.start()\n p2.join()\n```\n\n如果这么写,主进程你走你的,work2子进程你给我等一下。等我run完你在run。\n\n**注意:**每个子进程之间的join不是串行的,是并行的。既无论你多少个子进程,谁最后结束,谁关闭文件。\n\n### 单任务多进程\n\n比如你要启动50个进程,然后写50个子进程就太慢了。因此,对于多进程有一种新的形式。\n\n```python\ndef long_time_task(name):\n print('Run task %s (%s)...' % (name, os.getpid()))\n start = time.time()\n time.sleep(random.random() * 3)\n end = time.time()\n print('Task %s runs %0.2f seconds.' % (name, (end - start)))\n\nif __name__=='__main__':\n print('Parent process %s.' % os.getpid())\n p = Pool(4)\n for i in range(5):\n p.apply_async(long_time_task, args=(i,))\n print('Waiting for all subprocesses done...')\n p.close()\n p.join()\n print('All subprocesses done.')\n\n\n########################################################\n#运行结果\n########################################################\nParent process 7748.\nWaiting for all subprocesses done...\nRun task 0 (7780)...\nRun task 1 (7781)...\nRun task 2 (7782)...\nRun task 3 (7783)...\nRun task 4 (7781)...\nAll subprocesses done.\n```\n\n可以看到task1和task4的进程是一样的,因为只启动了4个进程,因此第5个进程需要等待其他进程结束才开始运行。\n\n**注意:**close不加直接写join是会报错的。\n\n### 多任务多进程\n\n我这边用的是经典的生产者和消费者模型,及一个模型构造生产者,一个模型构造消费者,两者通过自带的JoinableQueue进行通信。(不要用deque里面的队列!!)\n\n```python\nimport time\nimport random\nfrom multiprocessing import JoinableQueue,Process\ndef producer(q,name,food):\n for i in range(5):\n time.sleep(random.random())\n fd = '%s%s'%(food,i+1)\n q.put(fd)\n print('%s生产了一个%s'%(name,food))\n q.join() # 我启动了生产者之后,生产者函数一直在生成数据,直到生产完所有数据将队列q.join()一下,意思是当我生产的数据都被消费者消费完之后 队列的阻塞才结束。\ndef consumer(q,name): # 消费者不需要像Queue那样判断从队列里拿到None再退出执行消费者函数了\n while True:\n food = q.get()\n time.sleep(random.random())\n print('%s吃了%s'%(name,food))\n q.task_done() # 消费者每次从队列里面q.get()一个数据,处理过后就使用队列.task_done()\nif __name__ == '__main__':\n jq = JoinableQueue()\n p =Process(target=producer,args=(jq,'喜洋洋','包子'))\n p.start()\n c = Process(target=consumer,args=(jq,'灰太狼'))\n c.daemon = True # 守护进程,如果用Pool就不用这个也没事\n c.start()\n p.join() # 阻塞生产者\n```\n\n具体实现情况\n\n1. 启动一个生产者,和一个消费者(这里可以用多进程消费),看具体时间\n2. 生产者结束后将JoinableQueue进行阻塞,直到队列全部被消费后,才结束进程。\n\nQueue与JoinableQueue的区别\n\n1. Queue在消费者中需要if判断队列,不然的话就陷入死循环。而jionableQueue不用,其只需要将队列堵塞后,队列消费完程序才解决。\n2. Queue很难控制,因为如果只是判断队列是否存在,就需要考虑产生和消耗队列的时间。因此我认为还是JoinableQueue好\n\n## 多线程\n\n开头就说了,python虽然线程是真正的线程,但在解释器执行的时候会遇到GIL锁,导致其无论多少核,只能跑满一个核,所以对于比较简单的可以运行多线程,好像在爬虫里面运用多线程的比较多。\n\n那为什么有的程序一个进程一开用了N个cpu,那是因为他进程中存在利用c扩写的库,他将关键的部分用C/C++写成python扩展,其他还用python写。一般计算密集型的程序都会用C代码编写并通过扩展的方式集成到python脚本(numpy)。在扩展中完全可以用C创建原生线程,而且不用锁GIL,充分利用CPU的计算资源。\n\n```python\nfrom threading import Thread\nimport time\n\ndef work1():\n print('work1 has working')\n time.sleep(3)\n\ndef work2():\n print('work2 has working')\n time.sleep(5)\n\ndef work3():\n print('work3 has working')\n time.sleep(8)\n\nif __name__ == '__main__':\n start_time =time.time()\n p1 = Thread(target=work1)\n p2 = Thread(target=work2)\n p3 = Thread(target=work3)\n p1.start()\n p2.start()\n p3.start()\n p3.join()\n print(f'cost time {time.time()-start_time}')\n```\n\n## 问题\n\n```\n能否利用Pool批量构造生产者和消费者,\n暂时只能做到Pool构造消费者,多个Process构造生产者进行生产。。。\n(除非使用process分别构造生产者和消费者模型)\n```\n\n\n\n\n\n\n\n\n\n","slug":"多进程、多线程(python)","published":1,"updated":"2021-09-18T08:43:45.914Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb5d000pso3w776c2lty","content":"<blockquote>\n<p> 这周接到1个需求,项目经理觉得我这边构造数据太过缓慢,由于数据量过大,以前数据构造完后将其导入MPP,利用copy_from速度还是很快的,一般为10W/s。现在换成Kafka消息队列,又由于python库自带的原因(这个是组里大神告诉我的),无法像java开发利用list导数据,因此我只能一条一条以json的形式将数据放入消息队列,一般为800条/s。原先构造5000W数据需要7H,经过优化后暂时需要3.5H,性能提升一倍。有点小开心,话不多说,石海同学开始将一下个人对于标题的理解。。。。</p>\n<p> 进一步优化利用多进程的JoinableQueue,一边一直产生数据,另一边一直消费数据,现在5000W数据大约需要1.5H,性能较最初提升了近五倍。</p>\n</blockquote>\n<h2 id=\"历史\"><a href=\"#历史\" class=\"headerlink\" title=\"历史\"></a>历史</h2><p>上古年代:在很久很久以前,当时主流的磁盘操作系统MS-DOS是只支持单任务操作的,就打个比方,如果我想在电脑上听音乐和看电影,是不能同时开启的,只能先听音乐后看电影,或者位置互换。</p>\n<p>2002:横空出世的Intel Pentium 4HT处理器,提出了cpu多线程(SMT),其支持一个cpu进行多任务开启。</p>\n<p>————你总不能要求Guido1989年为了打发圣诞节假期发明的一种编译语言还要设计一下多线程的部分。</p>\n<p>2006:在秋季的英特尔信息技术峰会上,Inter总裁宣布,其在11月份将会交付第一台4核cpu处理器,而这距离双核发布还不到1年,支持多核时代就此拉开序幕。</p>\n<p>————为什么说python多线程是历史遗留问题,因为在当时想要在两个或者更多的处理器对同一个对象运行时,为了保护线程数据完整性和状态同步,最简单的方法就是加锁,于是出现了GIL这把超级大锁。</p>\n<p>2006-至今:GPU也浩浩荡荡发展了十几年了,过程我就不细说了(因为不懂),总之对于图象类任务GPU和CPU不是一个量级上的,以前看过一个视频,很好的解释了CPU和GPU的区别。比如我要画一幅图,cpu需要一笔一划将他画出来,而GPU是直接在脑子里构思好,一炮就把图打出来了。因此对于神经网络这个模型,多个神经元同时进行计算,GPU比CPU快太多。。。。。</p>\n<h2 id=\"编译器、解释器、IDE\"><a href=\"#编译器、解释器、IDE\" class=\"headerlink\" title=\"编译器、解释器、IDE\"></a>编译器、解释器、IDE</h2><p><strong>编译器</strong>:对于C、C++这类语言需要先编译后运行,其中编译的时候就是编译器。</p>\n<p><strong>解释器</strong>:对于python、PHP、JavaScript等就是利用解释器,’一边编译一边解释‘,不过速度会比较慢,因此产生一种叫预编译的东西,python在运行时就先生成pyc的二进制临时文件,在出结果。</p>\n<p>预编译:Java、C#,运行前将源代码编译成中间代码,然后再解释器运行,这种执行效率比编译运行会有效率一些,避免重复编译。</p>\n<p><strong>IDE</strong>:集成<a href=\"https://baike.baidu.com/item/开发环境\">开发环境</a>(<a href=\"https://baike.baidu.com/item/IDE\">IDE</a>,Integrated Development Environment ),用于提供程序开发环境的应用程序,python常用的就是pycharm和jupyter notebook。</p>\n<h2 id=\"GIL锁\"><a href=\"#GIL锁\" class=\"headerlink\" title=\"GIL锁\"></a>GIL锁</h2><p><strong>概念:</strong>GIL全称Global Interpreter Lock,其并不是python的特性,由于Cpython是大部分环境下默认的python执行环境,而在实现python解释器会自动上锁。</p>\n<p><strong>目的:</strong>确保每个进程只有一个线程运行,所以在外面我们一般说python的多线程是伪线程。因为不管你有几个核,你用多线程也只能跑一个核。</p>\n<h2 id=\"进程、线程、协程的利用\"><a href=\"#进程、线程、协程的利用\" class=\"headerlink\" title=\"进程、线程、协程的利用\"></a>进程、线程、协程的利用</h2><p><strong>进程</strong>:拥有代码和打开的文件资源、数据资源、独立的内存空间。</p>\n<p><strong>线程</strong>:从属于进程,是程序的实际执行者,线程拥有自己的栈空间。</p>\n<p><strong>协程</strong>:从属于线程,是一种比线程更加轻量级的存在。</p>\n<p><strong>总结:</strong></p>\n<p><strong>对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。</strong></p>\n<h2 id=\"多进程\"><a href=\"#多进程\" class=\"headerlink\" title=\"多进程\"></a>多进程</h2><p>我觉得最简单的讲,多进程就是你在任务窗口看建了几个任务。比如你电脑上有16个核,理论上你可以开16个进程,每个进程占满一个cpu。对python而言由于没有多线程的利用,所有在单进程无法满足需求时,自然得利用多进程。</p>\n<h3 id=\"单任务单进程\"><a href=\"#单任务单进程\" class=\"headerlink\" title=\"单任务单进程\"></a>单任务单进程</h3><figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> multiprocessing <span class=\"keyword\">import</span> Process</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'<span class=\"subst\">{name}</span>'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> p = Process(target=work,args=(<span class=\"string\">'single task'</span>,))</span><br><span class=\"line\"> p.start()</span><br><span class=\"line\"> p.join()</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>我还看到一种是用run不用start的,但大概看了下没什么特殊的,不过这个join只能用于start.所以建议还是都用start,这个join是阻塞的意思,简单来说:主进程和其他子进程结束的话,都等着,等我结束了才能结束。</p>\n<p>举个例子:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> multiprocessing <span class=\"keyword\">import</span> Process</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"><span class=\"keyword\">import</span> time</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work1</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'<span class=\"subst\">{name}</span>:<span class=\"subst\">{os.getpid()}</span>'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">2</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work2</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'<span class=\"subst\">{name}</span>:<span class=\"subst\">{os.getpid()}</span>'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> p1 = Process(target=work1,args=(<span class=\"string\">'single task1'</span>,))</span><br><span class=\"line\"> p2 = Process(target=work2,args=(<span class=\"string\">'single task2'</span>,))</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'主进程:<span class=\"subst\">{os.getpid()}</span>'</span>)</span><br><span class=\"line\"> p1.start()</span><br><span class=\"line\"> p1.join()</span><br><span class=\"line\"> p2.start()</span><br><span class=\"line\"> p2.join()</span><br></pre></td></tr></table></figure>\n<p>如果这么写,主进程你走你的,work2子进程你给我等一下。等我run完你在run。</p>\n<p><strong>注意:</strong>每个子进程之间的join不是串行的,是并行的。既无论你多少个子进程,谁最后结束,谁关闭文件。</p>\n<h3 id=\"单任务多进程\"><a href=\"#单任务多进程\" class=\"headerlink\" title=\"单任务多进程\"></a>单任务多进程</h3><p>比如你要启动50个进程,然后写50个子进程就太慢了。因此,对于多进程有一种新的形式。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">long_time_task</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Run task %s (%s)...'</span> % (name, os.getpid()))</span><br><span class=\"line\"> start = time.time()</span><br><span class=\"line\"> time.sleep(random.random() * <span class=\"number\">3</span>)</span><br><span class=\"line\"> end = time.time()</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Task %s runs %0.2f seconds.'</span> % (name, (end - start)))</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__==<span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Parent process %s.'</span> % os.getpid())</span><br><span class=\"line\"> p = Pool(<span class=\"number\">4</span>)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> p.apply_async(long_time_task, args=(i,))</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Waiting for all subprocesses done...'</span>)</span><br><span class=\"line\"> p.close()</span><br><span class=\"line\"> p.join()</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'All subprocesses done.'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">########################################################</span></span><br><span class=\"line\"><span class=\"comment\">#运行结果</span></span><br><span class=\"line\"><span class=\"comment\">########################################################</span></span><br><span class=\"line\">Parent process <span class=\"number\">7748.</span></span><br><span class=\"line\">Waiting <span class=\"keyword\">for</span> <span class=\"built_in\">all</span> subprocesses done...</span><br><span class=\"line\">Run task <span class=\"number\">0</span> (<span class=\"number\">7780</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">1</span> (<span class=\"number\">7781</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">2</span> (<span class=\"number\">7782</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">3</span> (<span class=\"number\">7783</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">4</span> (<span class=\"number\">7781</span>)...</span><br><span class=\"line\">All subprocesses done.</span><br></pre></td></tr></table></figure>\n<p>可以看到task1和task4的进程是一样的,因为只启动了4个进程,因此第5个进程需要等待其他进程结束才开始运行。</p>\n<p><strong>注意:</strong>close不加直接写join是会报错的。</p>\n<h3 id=\"多任务多进程\"><a href=\"#多任务多进程\" class=\"headerlink\" title=\"多任务多进程\"></a>多任务多进程</h3><p>我这边用的是经典的生产者和消费者模型,及一个模型构造生产者,一个模型构造消费者,两者通过自带的JoinableQueue进行通信。(不要用deque里面的队列!!)</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> time</span><br><span class=\"line\"><span class=\"keyword\">import</span> random</span><br><span class=\"line\"><span class=\"keyword\">from</span> multiprocessing <span class=\"keyword\">import</span> JoinableQueue,Process</span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">producer</span>(<span class=\"params\">q,name,food</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> time.sleep(random.random())</span><br><span class=\"line\"> fd = <span class=\"string\">'%s%s'</span>%(food,i+<span class=\"number\">1</span>)</span><br><span class=\"line\"> q.put(fd)</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'%s生产了一个%s'</span>%(name,food))</span><br><span class=\"line\"> q.join() <span class=\"comment\"># 我启动了生产者之后,生产者函数一直在生成数据,直到生产完所有数据将队列q.join()一下,意思是当我生产的数据都被消费者消费完之后 队列的阻塞才结束。</span></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">consumer</span>(<span class=\"params\">q,name</span>):</span> <span class=\"comment\"># 消费者不需要像Queue那样判断从队列里拿到None再退出执行消费者函数了</span></span><br><span class=\"line\"> <span class=\"keyword\">while</span> <span class=\"literal\">True</span>:</span><br><span class=\"line\"> food = q.get()</span><br><span class=\"line\"> time.sleep(random.random())</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'%s吃了%s'</span>%(name,food))</span><br><span class=\"line\"> q.task_done() <span class=\"comment\"># 消费者每次从队列里面q.get()一个数据,处理过后就使用队列.task_done()</span></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> jq = JoinableQueue()</span><br><span class=\"line\"> p =Process(target=producer,args=(jq,<span class=\"string\">'喜洋洋'</span>,<span class=\"string\">'包子'</span>))</span><br><span class=\"line\"> p.start()</span><br><span class=\"line\"> c = Process(target=consumer,args=(jq,<span class=\"string\">'灰太狼'</span>))</span><br><span class=\"line\"> c.daemon = <span class=\"literal\">True</span> <span class=\"comment\"># 守护进程,如果用Pool就不用这个也没事</span></span><br><span class=\"line\"> c.start()</span><br><span class=\"line\"> p.join() <span class=\"comment\"># 阻塞生产者</span></span><br></pre></td></tr></table></figure>\n<p>具体实现情况</p>\n<ol>\n<li>启动一个生产者,和一个消费者(这里可以用多进程消费),看具体时间</li>\n<li>生产者结束后将JoinableQueue进行阻塞,直到队列全部被消费后,才结束进程。</li>\n</ol>\n<p>Queue与JoinableQueue的区别</p>\n<ol>\n<li>Queue在消费者中需要if判断队列,不然的话就陷入死循环。而jionableQueue不用,其只需要将队列堵塞后,队列消费完程序才解决。</li>\n<li>Queue很难控制,因为如果只是判断队列是否存在,就需要考虑产生和消耗队列的时间。因此我认为还是JoinableQueue好</li>\n</ol>\n<h2 id=\"多线程\"><a href=\"#多线程\" class=\"headerlink\" title=\"多线程\"></a>多线程</h2><p>开头就说了,python虽然线程是真正的线程,但在解释器执行的时候会遇到GIL锁,导致其无论多少核,只能跑满一个核,所以对于比较简单的可以运行多线程,好像在爬虫里面运用多线程的比较多。</p>\n<p>那为什么有的程序一个进程一开用了N个cpu,那是因为他进程中存在利用c扩写的库,他将关键的部分用C/C++写成python扩展,其他还用python写。一般计算密集型的程序都会用C代码编写并通过扩展的方式集成到python脚本(numpy)。在扩展中完全可以用C创建原生线程,而且不用锁GIL,充分利用CPU的计算资源。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> threading <span class=\"keyword\">import</span> Thread</span><br><span class=\"line\"><span class=\"keyword\">import</span> time</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work1</span>():</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'work1 has working'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">3</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work2</span>():</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'work2 has working'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">5</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work3</span>():</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'work3 has working'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">8</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> start_time =time.time()</span><br><span class=\"line\"> p1 = Thread(target=work1)</span><br><span class=\"line\"> p2 = Thread(target=work2)</span><br><span class=\"line\"> p3 = Thread(target=work3)</span><br><span class=\"line\"> p1.start()</span><br><span class=\"line\"> p2.start()</span><br><span class=\"line\"> p3.start()</span><br><span class=\"line\"> p3.join()</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'cost time <span class=\"subst\">{time.time()-start_time}</span>'</span>)</span><br></pre></td></tr></table></figure>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">能否利用Pool批量构造生产者和消费者,</span><br><span class=\"line\">暂时只能做到Pool构造消费者,多个Process构造生产者进行生产。。。</span><br><span class=\"line\">(除非使用process分别构造生产者和消费者模型)</span><br></pre></td></tr></table></figure>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<blockquote>\n<p> 这周接到1个需求,项目经理觉得我这边构造数据太过缓慢,由于数据量过大,以前数据构造完后将其导入MPP,利用copy_from速度还是很快的,一般为10W/s。现在换成Kafka消息队列,又由于python库自带的原因(这个是组里大神告诉我的),无法像java开发利用list导数据,因此我只能一条一条以json的形式将数据放入消息队列,一般为800条/s。原先构造5000W数据需要7H,经过优化后暂时需要3.5H,性能提升一倍。有点小开心,话不多说,石海同学开始将一下个人对于标题的理解。。。。</p>\n<p> 进一步优化利用多进程的JoinableQueue,一边一直产生数据,另一边一直消费数据,现在5000W数据大约需要1.5H,性能较最初提升了近五倍。</p>\n</blockquote>\n<h2 id=\"历史\"><a href=\"#历史\" class=\"headerlink\" title=\"历史\"></a>历史</h2><p>上古年代:在很久很久以前,当时主流的磁盘操作系统MS-DOS是只支持单任务操作的,就打个比方,如果我想在电脑上听音乐和看电影,是不能同时开启的,只能先听音乐后看电影,或者位置互换。</p>\n<p>2002:横空出世的Intel Pentium 4HT处理器,提出了cpu多线程(SMT),其支持一个cpu进行多任务开启。</p>\n<p>————你总不能要求Guido1989年为了打发圣诞节假期发明的一种编译语言还要设计一下多线程的部分。</p>\n<p>2006:在秋季的英特尔信息技术峰会上,Inter总裁宣布,其在11月份将会交付第一台4核cpu处理器,而这距离双核发布还不到1年,支持多核时代就此拉开序幕。</p>\n<p>————为什么说python多线程是历史遗留问题,因为在当时想要在两个或者更多的处理器对同一个对象运行时,为了保护线程数据完整性和状态同步,最简单的方法就是加锁,于是出现了GIL这把超级大锁。</p>\n<p>2006-至今:GPU也浩浩荡荡发展了十几年了,过程我就不细说了(因为不懂),总之对于图象类任务GPU和CPU不是一个量级上的,以前看过一个视频,很好的解释了CPU和GPU的区别。比如我要画一幅图,cpu需要一笔一划将他画出来,而GPU是直接在脑子里构思好,一炮就把图打出来了。因此对于神经网络这个模型,多个神经元同时进行计算,GPU比CPU快太多。。。。。</p>\n<h2 id=\"编译器、解释器、IDE\"><a href=\"#编译器、解释器、IDE\" class=\"headerlink\" title=\"编译器、解释器、IDE\"></a>编译器、解释器、IDE</h2><p><strong>编译器</strong>:对于C、C++这类语言需要先编译后运行,其中编译的时候就是编译器。</p>\n<p><strong>解释器</strong>:对于python、PHP、JavaScript等就是利用解释器,’一边编译一边解释‘,不过速度会比较慢,因此产生一种叫预编译的东西,python在运行时就先生成pyc的二进制临时文件,在出结果。</p>\n<p>预编译:Java、C#,运行前将源代码编译成中间代码,然后再解释器运行,这种执行效率比编译运行会有效率一些,避免重复编译。</p>\n<p><strong>IDE</strong>:集成<a href=\"https://baike.baidu.com/item/开发环境\">开发环境</a>(<a href=\"https://baike.baidu.com/item/IDE\">IDE</a>,Integrated Development Environment ),用于提供程序开发环境的应用程序,python常用的就是pycharm和jupyter notebook。</p>\n<h2 id=\"GIL锁\"><a href=\"#GIL锁\" class=\"headerlink\" title=\"GIL锁\"></a>GIL锁</h2><p><strong>概念:</strong>GIL全称Global Interpreter Lock,其并不是python的特性,由于Cpython是大部分环境下默认的python执行环境,而在实现python解释器会自动上锁。</p>\n<p><strong>目的:</strong>确保每个进程只有一个线程运行,所以在外面我们一般说python的多线程是伪线程。因为不管你有几个核,你用多线程也只能跑一个核。</p>\n<h2 id=\"进程、线程、协程的利用\"><a href=\"#进程、线程、协程的利用\" class=\"headerlink\" title=\"进程、线程、协程的利用\"></a>进程、线程、协程的利用</h2><p><strong>进程</strong>:拥有代码和打开的文件资源、数据资源、独立的内存空间。</p>\n<p><strong>线程</strong>:从属于进程,是程序的实际执行者,线程拥有自己的栈空间。</p>\n<p><strong>协程</strong>:从属于线程,是一种比线程更加轻量级的存在。</p>\n<p><strong>总结:</strong></p>\n<p><strong>对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。</strong></p>\n<h2 id=\"多进程\"><a href=\"#多进程\" class=\"headerlink\" title=\"多进程\"></a>多进程</h2><p>我觉得最简单的讲,多进程就是你在任务窗口看建了几个任务。比如你电脑上有16个核,理论上你可以开16个进程,每个进程占满一个cpu。对python而言由于没有多线程的利用,所有在单进程无法满足需求时,自然得利用多进程。</p>\n<h3 id=\"单任务单进程\"><a href=\"#单任务单进程\" class=\"headerlink\" title=\"单任务单进程\"></a>单任务单进程</h3><figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> multiprocessing <span class=\"keyword\">import</span> Process</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'<span class=\"subst\">{name}</span>'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> p = Process(target=work,args=(<span class=\"string\">'single task'</span>,))</span><br><span class=\"line\"> p.start()</span><br><span class=\"line\"> p.join()</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>我还看到一种是用run不用start的,但大概看了下没什么特殊的,不过这个join只能用于start.所以建议还是都用start,这个join是阻塞的意思,简单来说:主进程和其他子进程结束的话,都等着,等我结束了才能结束。</p>\n<p>举个例子:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> multiprocessing <span class=\"keyword\">import</span> Process</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"><span class=\"keyword\">import</span> time</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work1</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'<span class=\"subst\">{name}</span>:<span class=\"subst\">{os.getpid()}</span>'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">2</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work2</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'<span class=\"subst\">{name}</span>:<span class=\"subst\">{os.getpid()}</span>'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> p1 = Process(target=work1,args=(<span class=\"string\">'single task1'</span>,))</span><br><span class=\"line\"> p2 = Process(target=work2,args=(<span class=\"string\">'single task2'</span>,))</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'主进程:<span class=\"subst\">{os.getpid()}</span>'</span>)</span><br><span class=\"line\"> p1.start()</span><br><span class=\"line\"> p1.join()</span><br><span class=\"line\"> p2.start()</span><br><span class=\"line\"> p2.join()</span><br></pre></td></tr></table></figure>\n<p>如果这么写,主进程你走你的,work2子进程你给我等一下。等我run完你在run。</p>\n<p><strong>注意:</strong>每个子进程之间的join不是串行的,是并行的。既无论你多少个子进程,谁最后结束,谁关闭文件。</p>\n<h3 id=\"单任务多进程\"><a href=\"#单任务多进程\" class=\"headerlink\" title=\"单任务多进程\"></a>单任务多进程</h3><p>比如你要启动50个进程,然后写50个子进程就太慢了。因此,对于多进程有一种新的形式。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">long_time_task</span>(<span class=\"params\">name</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Run task %s (%s)...'</span> % (name, os.getpid()))</span><br><span class=\"line\"> start = time.time()</span><br><span class=\"line\"> time.sleep(random.random() * <span class=\"number\">3</span>)</span><br><span class=\"line\"> end = time.time()</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Task %s runs %0.2f seconds.'</span> % (name, (end - start)))</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__==<span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Parent process %s.'</span> % os.getpid())</span><br><span class=\"line\"> p = Pool(<span class=\"number\">4</span>)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> p.apply_async(long_time_task, args=(i,))</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'Waiting for all subprocesses done...'</span>)</span><br><span class=\"line\"> p.close()</span><br><span class=\"line\"> p.join()</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'All subprocesses done.'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\">########################################################</span></span><br><span class=\"line\"><span class=\"comment\">#运行结果</span></span><br><span class=\"line\"><span class=\"comment\">########################################################</span></span><br><span class=\"line\">Parent process <span class=\"number\">7748.</span></span><br><span class=\"line\">Waiting <span class=\"keyword\">for</span> <span class=\"built_in\">all</span> subprocesses done...</span><br><span class=\"line\">Run task <span class=\"number\">0</span> (<span class=\"number\">7780</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">1</span> (<span class=\"number\">7781</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">2</span> (<span class=\"number\">7782</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">3</span> (<span class=\"number\">7783</span>)...</span><br><span class=\"line\">Run task <span class=\"number\">4</span> (<span class=\"number\">7781</span>)...</span><br><span class=\"line\">All subprocesses done.</span><br></pre></td></tr></table></figure>\n<p>可以看到task1和task4的进程是一样的,因为只启动了4个进程,因此第5个进程需要等待其他进程结束才开始运行。</p>\n<p><strong>注意:</strong>close不加直接写join是会报错的。</p>\n<h3 id=\"多任务多进程\"><a href=\"#多任务多进程\" class=\"headerlink\" title=\"多任务多进程\"></a>多任务多进程</h3><p>我这边用的是经典的生产者和消费者模型,及一个模型构造生产者,一个模型构造消费者,两者通过自带的JoinableQueue进行通信。(不要用deque里面的队列!!)</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">import</span> time</span><br><span class=\"line\"><span class=\"keyword\">import</span> random</span><br><span class=\"line\"><span class=\"keyword\">from</span> multiprocessing <span class=\"keyword\">import</span> JoinableQueue,Process</span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">producer</span>(<span class=\"params\">q,name,food</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> i <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(<span class=\"number\">5</span>):</span><br><span class=\"line\"> time.sleep(random.random())</span><br><span class=\"line\"> fd = <span class=\"string\">'%s%s'</span>%(food,i+<span class=\"number\">1</span>)</span><br><span class=\"line\"> q.put(fd)</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'%s生产了一个%s'</span>%(name,food))</span><br><span class=\"line\"> q.join() <span class=\"comment\"># 我启动了生产者之后,生产者函数一直在生成数据,直到生产完所有数据将队列q.join()一下,意思是当我生产的数据都被消费者消费完之后 队列的阻塞才结束。</span></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">consumer</span>(<span class=\"params\">q,name</span>):</span> <span class=\"comment\"># 消费者不需要像Queue那样判断从队列里拿到None再退出执行消费者函数了</span></span><br><span class=\"line\"> <span class=\"keyword\">while</span> <span class=\"literal\">True</span>:</span><br><span class=\"line\"> food = q.get()</span><br><span class=\"line\"> time.sleep(random.random())</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'%s吃了%s'</span>%(name,food))</span><br><span class=\"line\"> q.task_done() <span class=\"comment\"># 消费者每次从队列里面q.get()一个数据,处理过后就使用队列.task_done()</span></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> jq = JoinableQueue()</span><br><span class=\"line\"> p =Process(target=producer,args=(jq,<span class=\"string\">'喜洋洋'</span>,<span class=\"string\">'包子'</span>))</span><br><span class=\"line\"> p.start()</span><br><span class=\"line\"> c = Process(target=consumer,args=(jq,<span class=\"string\">'灰太狼'</span>))</span><br><span class=\"line\"> c.daemon = <span class=\"literal\">True</span> <span class=\"comment\"># 守护进程,如果用Pool就不用这个也没事</span></span><br><span class=\"line\"> c.start()</span><br><span class=\"line\"> p.join() <span class=\"comment\"># 阻塞生产者</span></span><br></pre></td></tr></table></figure>\n<p>具体实现情况</p>\n<ol>\n<li>启动一个生产者,和一个消费者(这里可以用多进程消费),看具体时间</li>\n<li>生产者结束后将JoinableQueue进行阻塞,直到队列全部被消费后,才结束进程。</li>\n</ol>\n<p>Queue与JoinableQueue的区别</p>\n<ol>\n<li>Queue在消费者中需要if判断队列,不然的话就陷入死循环。而jionableQueue不用,其只需要将队列堵塞后,队列消费完程序才解决。</li>\n<li>Queue很难控制,因为如果只是判断队列是否存在,就需要考虑产生和消耗队列的时间。因此我认为还是JoinableQueue好</li>\n</ol>\n<h2 id=\"多线程\"><a href=\"#多线程\" class=\"headerlink\" title=\"多线程\"></a>多线程</h2><p>开头就说了,python虽然线程是真正的线程,但在解释器执行的时候会遇到GIL锁,导致其无论多少核,只能跑满一个核,所以对于比较简单的可以运行多线程,好像在爬虫里面运用多线程的比较多。</p>\n<p>那为什么有的程序一个进程一开用了N个cpu,那是因为他进程中存在利用c扩写的库,他将关键的部分用C/C++写成python扩展,其他还用python写。一般计算密集型的程序都会用C代码编写并通过扩展的方式集成到python脚本(numpy)。在扩展中完全可以用C创建原生线程,而且不用锁GIL,充分利用CPU的计算资源。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> threading <span class=\"keyword\">import</span> Thread</span><br><span class=\"line\"><span class=\"keyword\">import</span> time</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work1</span>():</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'work1 has working'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">3</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work2</span>():</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'work2 has working'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">5</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">work3</span>():</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">'work3 has working'</span>)</span><br><span class=\"line\"> time.sleep(<span class=\"number\">8</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> __name__ == <span class=\"string\">'__main__'</span>:</span><br><span class=\"line\"> start_time =time.time()</span><br><span class=\"line\"> p1 = Thread(target=work1)</span><br><span class=\"line\"> p2 = Thread(target=work2)</span><br><span class=\"line\"> p3 = Thread(target=work3)</span><br><span class=\"line\"> p1.start()</span><br><span class=\"line\"> p2.start()</span><br><span class=\"line\"> p3.start()</span><br><span class=\"line\"> p3.join()</span><br><span class=\"line\"> <span class=\"built_in\">print</span>(<span class=\"string\">f'cost time <span class=\"subst\">{time.time()-start_time}</span>'</span>)</span><br></pre></td></tr></table></figure>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><figure class=\"highlight plain\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">能否利用Pool批量构造生产者和消费者,</span><br><span class=\"line\">暂时只能做到Pool构造消费者,多个Process构造生产者进行生产。。。</span><br><span class=\"line\">(除非使用process分别构造生产者和消费者模型)</span><br></pre></td></tr></table></figure>\n"},{"title":"异常检测整理","date":"2021-04-08T08:25:05.000Z","description":["此文档写于2020年,建成于博客创立之前。"],"_content":"\n## 前言\n\n**定义**:识别不正常情况与挖掘非逻辑数据的技术,也叫outliers。\n\n**前提**:\n\n1. 异常数据只占少数\n2. 异常数据特征值和正常数据差别很大\n\n**应用领域**:\n\n1. CV领域:抖音发现违规视频\n2. 数据挖掘:信用卡盗刷,支付宝,异常金额支出。\n\n**模型**\n\n1. 无监督学习、AutoEncoder、GAN、矩阵因子分解\n2. 半监督学习,强化学习\n3. hybrid(混种)、特征提取+传统算法\n4. 单分类神经网路(MLM)\n\n## 统计学方法\n\n### 3sigma/箱形图\n\n**原理**:远离3sigma(拉依达准则)数据概率低于0.01,认为这些数据为异常值\n\n**缺点**:\n\n1. 要保证异常值较少\n2. 只能检测单维数据\n3. 要假定数据服从正态分布或近似\n\n### 高斯概率密度异常检测算法(1999)\n\n**原理**:首先,该算法假设数据集服从高斯分布的,然后再分别计算训练集在空间中的重心, 和方差, 然后根据高斯概率密度估算每个点被分配到重心的概率,进而完成异常检测任务。(感觉和3sigma想法很像)\n\n**缺点**:\n\n1. 不适用于高维特征数据集\n2. 要求数据大致服从高斯分布的数据集\n\n## 无监督学习\n\n### Isolation Forest(孤立森林)\n\n**定义**:孤立森林是用于异常检测的机器学习算法。这是一种无监督学习算法,通过隔离数据中的离群值识别异常\n\n**原理**:孤立森林通过**随机选择特征**,然后**随机选择**特征的**分割值**,递归地生成数据集的分区。和数据集中「正常」的点相比,要隔离的异常值所需的随机分区更少,因此**异常值是树中路径更短的点**,路径长度是从根节点经过的边数。\n\n**重要参数**\n\n- n_estimators:树的数量\n- max_sample:样本抽样(小样本全抽)\n- contamination:异常占比,这个值很关键\n- max_features:随机选取特征维度\n\n**具体步骤**:\n\n1. 从数据集中按max_sample进行抽样\n2. 随机指定部分维度(论文是只用一个维度),在当前节点数据中随机产生一个切割点p——切割点产生于当前节点数据中指定维度的最大值和最小值之间。\n3. 以此切割点生成了一个超平面,然后将当前节点数据空间划分为2个子空间:把指定维度里小于p的数据放在当前节点的左边,把大于等于p的数据放在当前节点的右边。\n4. 在子节点中递归步骤2和3,不断构造新的子节点,直到子节点中只有一个数据(无法再继续切割)或子节点已到达限定高度(算法设定的)。\n\n**优点**:\n\n1. 节省内存。由于其主要定位异常值,因此其不要求树全部描述出来,可以用最大深度来限制。并且其不像kmeans等需要计算有关距离、密度的指标,可大幅度提升速度。\n2. 适用于小数据集:因此采样,如果数据集很大,\n3. 集成算法,多个专家的树针对不同的异常\n\n**缺点:**\n\n1. 如果只能用1个维度,那对于图片这种高维特征,效果就不佳。\n\n**参考**:\n\n[Isolation Forest](https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf )[2008]\n\n[Isolation-based Anomaly Detection](https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/tkdd11.pdf)[2012] \n\n### Local Outlier Factor(局部异常因子算法)\n\n**定义**:一种典型的基于密度的高精度离群点检测算法\n\n**原理**: LOF算法是通过比较每个点p和邻域点的密度来判断该点是否为异常:点p的密度越低,越有可能是异常点。而点的密度是通过点之间的距离来计算的,点之间距离越远,密度越低;距离越近,密度越高。也就是说,LOF算法中点的密度是通过点的k邻域计算得到的,而不是通过全局计算得到,这里的\"k邻域”也就是该算法中“局部”的概念。\n\n**重要参数**\n\n- n_neighbors:上文的K,检测的领域点个数超过样本数则使用所有的样本进行检测\n- contamination:异常占比,这个值很关键\n- metric:距离度量单位\n- p=2:距离度量单位(l1,l2分别为1,2)\n\n**具体步骤**:主要就是计算LOF,如果想弄明白怎么算的直接看源论文\n\n**优点**\n\n1. 适用于对不同密度的数据的异常检测\n\n**缺点**\n\n1. 检测的数据必须有明显的密度差异,计算较为复杂\n\n**参考**\n\n[Local Outlier Factor](https://www.dbs.ifi.lmu.de/Publikationen/Papers/LOF.pdf )[2000]\n\n### One Class SVM(新颖点检测算法)\n\n**定义**:无监督算法,将数据分类为不同的类型\n\n**原理**:与SVM类似,SVM是寻找一个超平面,使用这个超平面把正常数据和异常数据划分开。而One_class SVM是基于一类数据(正常数据)求超平面,对SVM算法中求解负样本最大间隔目标进行改造。\n\n**优点**:\n\n1. 适用于高纬数据集,毕竟是超平面\n\n### DBSCAN(密度聚类)\n\n**定义**:无监督聚类算法,按照密度将空间内的数据进行聚类,如果单独成簇就是异常值。\n\n**原理**:给定一个距离半径和类内最少多少个点,然后把可以满足的点全部都连起来,判定为同类。\n\n**优点**:\n\n1. 不需要知道K,按距离自动划分为簇\n2. 能发现任意非球形状的簇类\n3. 对输入样本的顺序并不敏感\n\n**缺点**:\n\n1. 不适用于高纬数据\n2. 时间复杂度较高\n\n**参考**\n\n[DBSCAN](http://www2.cs.uh.edu/~ceick/7363/Papers/dbscan.pdf)\n\n## 降维\n\n### PCA\n\n**定义**:主成分分析(PCA),一种使用广泛的数据降维算法,主要思想是将n维特征映射到k维上,k维是全新的正交特征也称为主成分。\n\n**原理**:PCA的工作就是从原始的空间中顺序地寻找一组相互正交的坐标轴,第一个新坐标轴选择是原始数据中**方差最大**的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。取前k个包含绝大部分方差的坐标轴。\n\n**实现方式**\n\n- 基于特征值分解协方差矩阵实现\n- 基于SVD分解协方差矩阵(大样本高效,因此sklearn里面也是用svd分解)\n\n**具体步骤**\n\n1. 中心化(norm_x)\n2. 获取协方差矩阵(np.dot(norm_xT,norm_x))\n3. 计算协方差矩阵的特征值和特征向量(feature_n,vectors_n)\n4. 按特征值排序,选取最大的K组特征(特征值越大方差越大)(feature_k,vectors_k)\n5. 反转,np.dot(norm_x,vectors_k)\n\n**特点**\n\n1. PCA后的特征不具有解释性\n2. PCA不适合线性不可分数据\n\n## 半监督学习(AE类)\n\n### Auto-Encoder(鼻祖)\n\n**背景**:AE的思想最早是1986年被提出来的\n\n**定义**:一种无监督的学习算法,其可以解决PCA无法解决的问题,因为其可以对线性不可分的数据进行降维,利用反向传播算法,让目标等于输入值。\n\n**原理**:构建一个函数使X和Y的Loss最小,保证降维后的图片保持最大的信息。\n\n**重建误差**:就是损失函数,原始X和重建的Y之间的差异称为重建误差\n\n**异常检测的使用**\n\n1. 基于偏差的**半监督学习的**异常检测方法,使用**重建误差**作为异常分数,具有高重建误差作为异常。\n2. 因此在训练阶段仅使用正常数据,训练之后AE可以很好的重建正常数据,而AE未遇到的异常数据则会重建失败(定位异常图片)\n3. 为了增强鲁棒性一般会在正常数据里撒入一些噪声、或者随机将某些值变0(有点像dropout)。\n\n**为什么叫Encoder,明明有Decoder**:\n\n\tVincent在2010的论文中做了研究,发现只要encoder单组W就可以,decoder中的W1可以用encoder中的W转置获得。其证明,W1没有任何作用,完全没有必要训练。\n\n### Denoising Auto-Encoder(降噪自编码)\n\n**背景**:Vincent在2008年的《Extracting and Composing Robust Features with Denoising Autoencoders》提出该模型\n\n**做法**:对输入数据加入噪声,而输出数据是正常的数据,DAE会要求模型只去学习主要特征,输出的数据会有更好的鲁棒性\n\n### Sparse Auto-Encoder(稀疏自编码)\n\n**背景**:Andrew在2011年的《Sparse autoencoder》提出该模型\n\n**做法**:在普通的AE的基础上增加了稀疏性约束,即要求神经元的平均输出较低,如果激活函数是sigmod,尽量让隐藏神经元输出为0,如果激活函数是tanh,尽量把输出变为-1.\n\n> KL散度(相对熵): \n>\n> 1. 一种衡量两个概率分布的匹配程度的指标,两个分布差异越大,KL散度就越大。p目标分布,q是匹配分布,如果两个分布完全匹配,KL散度为0\n> 2. KL散度是非对称的,即D(p||q)不一定等于D(q||p)\n> 3. KL散度又叫相对熵,在信息论中,对于D(p||q),描述的是q去拟合p产生的信息损耗\n\n### Variational Auto-Encoder(VAE) \n\n**背景**:2014年,用于异常检测是2015年\n\n**重建概率**:通过导出原始输入变量分布的参数的随机潜变量来计算重建概率\n\n**损失函数**:重建概率+KL散度\n\n**与AE的区别和联系**:\n\n1. AE中的潜在变量由确定性映射定义,然而,由于VAE使用概率编码器来模拟潜在变量的分布,因此可以在采样过程中考虑潜在空间的可变性\n2. 重建是随机变量,重建概率不仅考虑重建与原始输入之间的差异,而且还考虑分布函数的参数来重建的可变性。\n3. 重建概率的计算不需要对异构数据进行处理,其重建误差的阈值比AE更客观且易于理解。\n\n**异常检测的使用**\n\n1. 一个半监督框架,仅使用正常实例的数据来训练VAE\n2. 抽取样本,对于来自encoder的每个样本,概率解码器输出均值和方差参数,使用这些参数,计算从分布产生原始数据的概率。\n\n### Conditional VAE\n\n**背景**:一种框架,加入某种图片的先验信息,提升训练效果。\n\n**优化点**:\n\n- **多尺度预测目标**(内容很多,并行/串行,输入输出端……):Loss=Loss1(1/4图)+Loss2(1/2图)+Loss3(原图)\n- **对KL散度进行近似**:给KL散度增加罚函数,简单定为:batch_size/样本数量\n- **增加label的one-hot**:对encode和decode的输入加入label的one-hot(conditional就是这一条,但对于一场检测没有意义)\n\n**与VAE的联系与区别**:\n\n1. 基本还是VAE的结构,对encoder和decoder的输入增加先验信息。\n2. Loss函数进行了相应的调整。\n\n### A Deep Hierarchical Variational Auto-Encoder\n\n## 半监督学习(GAN类)\n\n### AnoGAN\n\n**背景**:GAN用于异常检测的开山之作,2017的论文\n\n**基本思想**\n\n1. 训练阶段:塞入正常图片,利用DCGAN训练一个模型,希望生成器能够生成足够好的正常图片,好到辨别器也无法判别他到底对不对。\n2. 测试阶段:固定生成器和辨别器,他希望在Z的潜藏空间中找到一个和X最像的映射,然后利用梯度下降法,更新Z,生成一张由潜藏空间生成且和X最像的图片。\n - 定义一个损失函数,代表潜藏空间生成的图片和X的差异。\n - 随机抽一个Z,利用梯度下降法,不断更新使损失函数变最小,然后利用最好的Z生成图片进行异常分数计算(或者直接用)\n\n**缺点:**\n\n1. 对于X到Z的映射没有再训练阶段完成\n2. z的更新非常耗时,并且每张图片都需要更新。\n3. 该异常检测应该只适用于有严格边界的图像中。\n\n**结果**:\n\n原论文效果并不适用(周末试试用经典数据集进行训练)。可能因为生成模型训练的并不到位。但是loss也已经不变化了\n\n### WGAN\n\n**背景**:令GAN研究者眼前一亮的一种新模型,其从理论上很好的解决了GAN的几大问题.\n\n**GAN的表面问题**\n\n1. 训练不稳定,不容易收敛\n - 判别器训练太好,生成器梯度消失,loss不下降。\n - 判别器训练不好,生成器不稳定。\n2. 生成图片的多样性不足。\n\n**GAN的本质问题**\n\n1. 等价优化的距离衡量(KL散度、JS散度)不合理(改进办法:用Wasserstein距离代替JS散度,合理解决前两个问题)\n2. 生成分布于真实分布没有重叠部分(改进办法:增加噪声)\n\n**WGAN和GAN的区别**\n\n1. 判别器没有sigmoid,他不想DCGAN是2分类,他是回归问题\n2. 生成器和判别器的loss不取log\n3. 每次更新判别器的参数之后把它们的绝对值截断到不超过一个固定常数c\n4. 不用Adam,用rmsprop做优化器\n\n**WGAN_GP和WGAN的区别**\n\n*WGAN的问题*\n\n由于其限制每次判别器更新参数绝对值不超过阈值c,按原论文0.01,因此会出现大量参数集中在0.01和-0.01,这就导致所有参数基本都是0.01和-0.01。判别器性能就会变得很差。\n\n*WGAN_GP的优势*\n\n其设置一个额外的Loss来限制判别器的梯度,将参数更新正态分布到(-0.01,0.01)内。\n\n*改变*\n\n1. 优化器有可以用Adam了。。。\n2. 由于其对每个样本独立的施加梯度惩罚,所以判别器的模型架构中不能使用Batch Normalization,改为使用Layer Normalization\n\n### f-AnoGAN\n\n**背景**:AnoGan的优化版本,2019年的论文。AnoGAN存在一个问题,每一张图片都需要不断迭代训练一个Z来代表其在隐空间内的点。接着利用训练好的DCGAN进行异常检测。由于其迭代优化势必导致时间加剧。而f-AnoGAN通过引入Encoder,解决了这个问题。\n\n**基本思想**:\n\n1. 输入正常图片,利用WGAN_GP进行训练生成器和判别器。\n\n2. 输入正常图片,对Encoder+训练好的WGAN进行优化。提出了三种训练Encoder的方式(3种损失函数)\n\n - izi:image-z-image1,AutoEncoder样式的损失函数\n - ziz:z-image-z1,将Encoder放到Generate后面,比较z与z1的损失函数\n - izif:就是Anogan中的损失函数,从像素和特征两个维度比较图片结果。\n\n 作者实验证明izif效果最好,并且整体训练也很快。\n\n**结果**:\n\nWGAN没有收敛,两边都不断降低,不确定是不是GP哪里有问题。\n\n","source":"_posts/异常检测.md","raw":"---\ntitle: 异常检测整理\ndate: 2021-04-08 16:25:05\ncategories:\n- 机器学习\ntags:\n- 异常检测\ndescription:\n- 此文档写于2020年,建成于博客创立之前。\n---\n\n## 前言\n\n**定义**:识别不正常情况与挖掘非逻辑数据的技术,也叫outliers。\n\n**前提**:\n\n1. 异常数据只占少数\n2. 异常数据特征值和正常数据差别很大\n\n**应用领域**:\n\n1. CV领域:抖音发现违规视频\n2. 数据挖掘:信用卡盗刷,支付宝,异常金额支出。\n\n**模型**\n\n1. 无监督学习、AutoEncoder、GAN、矩阵因子分解\n2. 半监督学习,强化学习\n3. hybrid(混种)、特征提取+传统算法\n4. 单分类神经网路(MLM)\n\n## 统计学方法\n\n### 3sigma/箱形图\n\n**原理**:远离3sigma(拉依达准则)数据概率低于0.01,认为这些数据为异常值\n\n**缺点**:\n\n1. 要保证异常值较少\n2. 只能检测单维数据\n3. 要假定数据服从正态分布或近似\n\n### 高斯概率密度异常检测算法(1999)\n\n**原理**:首先,该算法假设数据集服从高斯分布的,然后再分别计算训练集在空间中的重心, 和方差, 然后根据高斯概率密度估算每个点被分配到重心的概率,进而完成异常检测任务。(感觉和3sigma想法很像)\n\n**缺点**:\n\n1. 不适用于高维特征数据集\n2. 要求数据大致服从高斯分布的数据集\n\n## 无监督学习\n\n### Isolation Forest(孤立森林)\n\n**定义**:孤立森林是用于异常检测的机器学习算法。这是一种无监督学习算法,通过隔离数据中的离群值识别异常\n\n**原理**:孤立森林通过**随机选择特征**,然后**随机选择**特征的**分割值**,递归地生成数据集的分区。和数据集中「正常」的点相比,要隔离的异常值所需的随机分区更少,因此**异常值是树中路径更短的点**,路径长度是从根节点经过的边数。\n\n**重要参数**\n\n- n_estimators:树的数量\n- max_sample:样本抽样(小样本全抽)\n- contamination:异常占比,这个值很关键\n- max_features:随机选取特征维度\n\n**具体步骤**:\n\n1. 从数据集中按max_sample进行抽样\n2. 随机指定部分维度(论文是只用一个维度),在当前节点数据中随机产生一个切割点p——切割点产生于当前节点数据中指定维度的最大值和最小值之间。\n3. 以此切割点生成了一个超平面,然后将当前节点数据空间划分为2个子空间:把指定维度里小于p的数据放在当前节点的左边,把大于等于p的数据放在当前节点的右边。\n4. 在子节点中递归步骤2和3,不断构造新的子节点,直到子节点中只有一个数据(无法再继续切割)或子节点已到达限定高度(算法设定的)。\n\n**优点**:\n\n1. 节省内存。由于其主要定位异常值,因此其不要求树全部描述出来,可以用最大深度来限制。并且其不像kmeans等需要计算有关距离、密度的指标,可大幅度提升速度。\n2. 适用于小数据集:因此采样,如果数据集很大,\n3. 集成算法,多个专家的树针对不同的异常\n\n**缺点:**\n\n1. 如果只能用1个维度,那对于图片这种高维特征,效果就不佳。\n\n**参考**:\n\n[Isolation Forest](https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf )[2008]\n\n[Isolation-based Anomaly Detection](https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/tkdd11.pdf)[2012] \n\n### Local Outlier Factor(局部异常因子算法)\n\n**定义**:一种典型的基于密度的高精度离群点检测算法\n\n**原理**: LOF算法是通过比较每个点p和邻域点的密度来判断该点是否为异常:点p的密度越低,越有可能是异常点。而点的密度是通过点之间的距离来计算的,点之间距离越远,密度越低;距离越近,密度越高。也就是说,LOF算法中点的密度是通过点的k邻域计算得到的,而不是通过全局计算得到,这里的\"k邻域”也就是该算法中“局部”的概念。\n\n**重要参数**\n\n- n_neighbors:上文的K,检测的领域点个数超过样本数则使用所有的样本进行检测\n- contamination:异常占比,这个值很关键\n- metric:距离度量单位\n- p=2:距离度量单位(l1,l2分别为1,2)\n\n**具体步骤**:主要就是计算LOF,如果想弄明白怎么算的直接看源论文\n\n**优点**\n\n1. 适用于对不同密度的数据的异常检测\n\n**缺点**\n\n1. 检测的数据必须有明显的密度差异,计算较为复杂\n\n**参考**\n\n[Local Outlier Factor](https://www.dbs.ifi.lmu.de/Publikationen/Papers/LOF.pdf )[2000]\n\n### One Class SVM(新颖点检测算法)\n\n**定义**:无监督算法,将数据分类为不同的类型\n\n**原理**:与SVM类似,SVM是寻找一个超平面,使用这个超平面把正常数据和异常数据划分开。而One_class SVM是基于一类数据(正常数据)求超平面,对SVM算法中求解负样本最大间隔目标进行改造。\n\n**优点**:\n\n1. 适用于高纬数据集,毕竟是超平面\n\n### DBSCAN(密度聚类)\n\n**定义**:无监督聚类算法,按照密度将空间内的数据进行聚类,如果单独成簇就是异常值。\n\n**原理**:给定一个距离半径和类内最少多少个点,然后把可以满足的点全部都连起来,判定为同类。\n\n**优点**:\n\n1. 不需要知道K,按距离自动划分为簇\n2. 能发现任意非球形状的簇类\n3. 对输入样本的顺序并不敏感\n\n**缺点**:\n\n1. 不适用于高纬数据\n2. 时间复杂度较高\n\n**参考**\n\n[DBSCAN](http://www2.cs.uh.edu/~ceick/7363/Papers/dbscan.pdf)\n\n## 降维\n\n### PCA\n\n**定义**:主成分分析(PCA),一种使用广泛的数据降维算法,主要思想是将n维特征映射到k维上,k维是全新的正交特征也称为主成分。\n\n**原理**:PCA的工作就是从原始的空间中顺序地寻找一组相互正交的坐标轴,第一个新坐标轴选择是原始数据中**方差最大**的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。取前k个包含绝大部分方差的坐标轴。\n\n**实现方式**\n\n- 基于特征值分解协方差矩阵实现\n- 基于SVD分解协方差矩阵(大样本高效,因此sklearn里面也是用svd分解)\n\n**具体步骤**\n\n1. 中心化(norm_x)\n2. 获取协方差矩阵(np.dot(norm_xT,norm_x))\n3. 计算协方差矩阵的特征值和特征向量(feature_n,vectors_n)\n4. 按特征值排序,选取最大的K组特征(特征值越大方差越大)(feature_k,vectors_k)\n5. 反转,np.dot(norm_x,vectors_k)\n\n**特点**\n\n1. PCA后的特征不具有解释性\n2. PCA不适合线性不可分数据\n\n## 半监督学习(AE类)\n\n### Auto-Encoder(鼻祖)\n\n**背景**:AE的思想最早是1986年被提出来的\n\n**定义**:一种无监督的学习算法,其可以解决PCA无法解决的问题,因为其可以对线性不可分的数据进行降维,利用反向传播算法,让目标等于输入值。\n\n**原理**:构建一个函数使X和Y的Loss最小,保证降维后的图片保持最大的信息。\n\n**重建误差**:就是损失函数,原始X和重建的Y之间的差异称为重建误差\n\n**异常检测的使用**\n\n1. 基于偏差的**半监督学习的**异常检测方法,使用**重建误差**作为异常分数,具有高重建误差作为异常。\n2. 因此在训练阶段仅使用正常数据,训练之后AE可以很好的重建正常数据,而AE未遇到的异常数据则会重建失败(定位异常图片)\n3. 为了增强鲁棒性一般会在正常数据里撒入一些噪声、或者随机将某些值变0(有点像dropout)。\n\n**为什么叫Encoder,明明有Decoder**:\n\n\tVincent在2010的论文中做了研究,发现只要encoder单组W就可以,decoder中的W1可以用encoder中的W转置获得。其证明,W1没有任何作用,完全没有必要训练。\n\n### Denoising Auto-Encoder(降噪自编码)\n\n**背景**:Vincent在2008年的《Extracting and Composing Robust Features with Denoising Autoencoders》提出该模型\n\n**做法**:对输入数据加入噪声,而输出数据是正常的数据,DAE会要求模型只去学习主要特征,输出的数据会有更好的鲁棒性\n\n### Sparse Auto-Encoder(稀疏自编码)\n\n**背景**:Andrew在2011年的《Sparse autoencoder》提出该模型\n\n**做法**:在普通的AE的基础上增加了稀疏性约束,即要求神经元的平均输出较低,如果激活函数是sigmod,尽量让隐藏神经元输出为0,如果激活函数是tanh,尽量把输出变为-1.\n\n> KL散度(相对熵): \n>\n> 1. 一种衡量两个概率分布的匹配程度的指标,两个分布差异越大,KL散度就越大。p目标分布,q是匹配分布,如果两个分布完全匹配,KL散度为0\n> 2. KL散度是非对称的,即D(p||q)不一定等于D(q||p)\n> 3. KL散度又叫相对熵,在信息论中,对于D(p||q),描述的是q去拟合p产生的信息损耗\n\n### Variational Auto-Encoder(VAE) \n\n**背景**:2014年,用于异常检测是2015年\n\n**重建概率**:通过导出原始输入变量分布的参数的随机潜变量来计算重建概率\n\n**损失函数**:重建概率+KL散度\n\n**与AE的区别和联系**:\n\n1. AE中的潜在变量由确定性映射定义,然而,由于VAE使用概率编码器来模拟潜在变量的分布,因此可以在采样过程中考虑潜在空间的可变性\n2. 重建是随机变量,重建概率不仅考虑重建与原始输入之间的差异,而且还考虑分布函数的参数来重建的可变性。\n3. 重建概率的计算不需要对异构数据进行处理,其重建误差的阈值比AE更客观且易于理解。\n\n**异常检测的使用**\n\n1. 一个半监督框架,仅使用正常实例的数据来训练VAE\n2. 抽取样本,对于来自encoder的每个样本,概率解码器输出均值和方差参数,使用这些参数,计算从分布产生原始数据的概率。\n\n### Conditional VAE\n\n**背景**:一种框架,加入某种图片的先验信息,提升训练效果。\n\n**优化点**:\n\n- **多尺度预测目标**(内容很多,并行/串行,输入输出端……):Loss=Loss1(1/4图)+Loss2(1/2图)+Loss3(原图)\n- **对KL散度进行近似**:给KL散度增加罚函数,简单定为:batch_size/样本数量\n- **增加label的one-hot**:对encode和decode的输入加入label的one-hot(conditional就是这一条,但对于一场检测没有意义)\n\n**与VAE的联系与区别**:\n\n1. 基本还是VAE的结构,对encoder和decoder的输入增加先验信息。\n2. Loss函数进行了相应的调整。\n\n### A Deep Hierarchical Variational Auto-Encoder\n\n## 半监督学习(GAN类)\n\n### AnoGAN\n\n**背景**:GAN用于异常检测的开山之作,2017的论文\n\n**基本思想**\n\n1. 训练阶段:塞入正常图片,利用DCGAN训练一个模型,希望生成器能够生成足够好的正常图片,好到辨别器也无法判别他到底对不对。\n2. 测试阶段:固定生成器和辨别器,他希望在Z的潜藏空间中找到一个和X最像的映射,然后利用梯度下降法,更新Z,生成一张由潜藏空间生成且和X最像的图片。\n - 定义一个损失函数,代表潜藏空间生成的图片和X的差异。\n - 随机抽一个Z,利用梯度下降法,不断更新使损失函数变最小,然后利用最好的Z生成图片进行异常分数计算(或者直接用)\n\n**缺点:**\n\n1. 对于X到Z的映射没有再训练阶段完成\n2. z的更新非常耗时,并且每张图片都需要更新。\n3. 该异常检测应该只适用于有严格边界的图像中。\n\n**结果**:\n\n原论文效果并不适用(周末试试用经典数据集进行训练)。可能因为生成模型训练的并不到位。但是loss也已经不变化了\n\n### WGAN\n\n**背景**:令GAN研究者眼前一亮的一种新模型,其从理论上很好的解决了GAN的几大问题.\n\n**GAN的表面问题**\n\n1. 训练不稳定,不容易收敛\n - 判别器训练太好,生成器梯度消失,loss不下降。\n - 判别器训练不好,生成器不稳定。\n2. 生成图片的多样性不足。\n\n**GAN的本质问题**\n\n1. 等价优化的距离衡量(KL散度、JS散度)不合理(改进办法:用Wasserstein距离代替JS散度,合理解决前两个问题)\n2. 生成分布于真实分布没有重叠部分(改进办法:增加噪声)\n\n**WGAN和GAN的区别**\n\n1. 判别器没有sigmoid,他不想DCGAN是2分类,他是回归问题\n2. 生成器和判别器的loss不取log\n3. 每次更新判别器的参数之后把它们的绝对值截断到不超过一个固定常数c\n4. 不用Adam,用rmsprop做优化器\n\n**WGAN_GP和WGAN的区别**\n\n*WGAN的问题*\n\n由于其限制每次判别器更新参数绝对值不超过阈值c,按原论文0.01,因此会出现大量参数集中在0.01和-0.01,这就导致所有参数基本都是0.01和-0.01。判别器性能就会变得很差。\n\n*WGAN_GP的优势*\n\n其设置一个额外的Loss来限制判别器的梯度,将参数更新正态分布到(-0.01,0.01)内。\n\n*改变*\n\n1. 优化器有可以用Adam了。。。\n2. 由于其对每个样本独立的施加梯度惩罚,所以判别器的模型架构中不能使用Batch Normalization,改为使用Layer Normalization\n\n### f-AnoGAN\n\n**背景**:AnoGan的优化版本,2019年的论文。AnoGAN存在一个问题,每一张图片都需要不断迭代训练一个Z来代表其在隐空间内的点。接着利用训练好的DCGAN进行异常检测。由于其迭代优化势必导致时间加剧。而f-AnoGAN通过引入Encoder,解决了这个问题。\n\n**基本思想**:\n\n1. 输入正常图片,利用WGAN_GP进行训练生成器和判别器。\n\n2. 输入正常图片,对Encoder+训练好的WGAN进行优化。提出了三种训练Encoder的方式(3种损失函数)\n\n - izi:image-z-image1,AutoEncoder样式的损失函数\n - ziz:z-image-z1,将Encoder放到Generate后面,比较z与z1的损失函数\n - izif:就是Anogan中的损失函数,从像素和特征两个维度比较图片结果。\n\n 作者实验证明izif效果最好,并且整体训练也很快。\n\n**结果**:\n\nWGAN没有收敛,两边都不断降低,不确定是不是GP哪里有问题。\n\n","slug":"异常检测","published":1,"updated":"2021-09-18T08:43:45.915Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb5h000sso3we223g7lk","content":"<h2 id=\"前言\"><a href=\"#前言\" class=\"headerlink\" title=\"前言\"></a>前言</h2><p><strong>定义</strong>:识别不正常情况与挖掘非逻辑数据的技术,也叫outliers。</p>\n<p><strong>前提</strong>:</p>\n<ol>\n<li>异常数据只占少数</li>\n<li>异常数据特征值和正常数据差别很大</li>\n</ol>\n<p><strong>应用领域</strong>:</p>\n<ol>\n<li>CV领域:抖音发现违规视频</li>\n<li>数据挖掘:信用卡盗刷,支付宝,异常金额支出。</li>\n</ol>\n<p><strong>模型</strong></p>\n<ol>\n<li>无监督学习、AutoEncoder、GAN、矩阵因子分解</li>\n<li>半监督学习,强化学习</li>\n<li>hybrid(混种)、特征提取+传统算法</li>\n<li>单分类神经网路(MLM)</li>\n</ol>\n<h2 id=\"统计学方法\"><a href=\"#统计学方法\" class=\"headerlink\" title=\"统计学方法\"></a>统计学方法</h2><h3 id=\"3sigma-箱形图\"><a href=\"#3sigma-箱形图\" class=\"headerlink\" title=\"3sigma/箱形图\"></a>3sigma/箱形图</h3><p><strong>原理</strong>:远离3sigma(拉依达准则)数据概率低于0.01,认为这些数据为异常值</p>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>要保证异常值较少</li>\n<li>只能检测单维数据</li>\n<li>要假定数据服从正态分布或近似</li>\n</ol>\n<h3 id=\"高斯概率密度异常检测算法(1999)\"><a href=\"#高斯概率密度异常检测算法(1999)\" class=\"headerlink\" title=\"高斯概率密度异常检测算法(1999)\"></a>高斯概率密度异常检测算法(1999)</h3><p><strong>原理</strong>:首先,该算法假设数据集服从高斯分布的,然后再分别计算训练集在空间中的重心, 和方差, 然后根据高斯概率密度估算每个点被分配到重心的概率,进而完成异常检测任务。(感觉和3sigma想法很像)</p>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>不适用于高维特征数据集</li>\n<li>要求数据大致服从高斯分布的数据集</li>\n</ol>\n<h2 id=\"无监督学习\"><a href=\"#无监督学习\" class=\"headerlink\" title=\"无监督学习\"></a>无监督学习</h2><h3 id=\"Isolation-Forest-孤立森林\"><a href=\"#Isolation-Forest-孤立森林\" class=\"headerlink\" title=\"Isolation Forest(孤立森林)\"></a>Isolation Forest(孤立森林)</h3><p><strong>定义</strong>:孤立森林是用于异常检测的机器学习算法。这是一种无监督学习算法,通过隔离数据中的离群值识别异常</p>\n<p><strong>原理</strong>:孤立森林通过<strong>随机选择特征</strong>,然后<strong>随机选择</strong>特征的<strong>分割值</strong>,递归地生成数据集的分区。和数据集中「正常」的点相比,要隔离的异常值所需的随机分区更少,因此<strong>异常值是树中路径更短的点</strong>,路径长度是从根节点经过的边数。</p>\n<p><strong>重要参数</strong></p>\n<ul>\n<li>n_estimators:树的数量</li>\n<li>max_sample:样本抽样(小样本全抽)</li>\n<li>contamination:异常占比,这个值很关键</li>\n<li>max_features:随机选取特征维度</li>\n</ul>\n<p><strong>具体步骤</strong>:</p>\n<ol>\n<li>从数据集中按max_sample进行抽样</li>\n<li>随机指定部分维度(论文是只用一个维度),在当前节点数据中随机产生一个切割点p——切割点产生于当前节点数据中指定维度的最大值和最小值之间。</li>\n<li>以此切割点生成了一个超平面,然后将当前节点数据空间划分为2个子空间:把指定维度里小于p的数据放在当前节点的左边,把大于等于p的数据放在当前节点的右边。</li>\n<li>在子节点中递归步骤2和3,不断构造新的子节点,直到子节点中只有一个数据(无法再继续切割)或子节点已到达限定高度(算法设定的)。</li>\n</ol>\n<p><strong>优点</strong>:</p>\n<ol>\n<li>节省内存。由于其主要定位异常值,因此其不要求树全部描述出来,可以用最大深度来限制。并且其不像kmeans等需要计算有关距离、密度的指标,可大幅度提升速度。</li>\n<li>适用于小数据集:因此采样,如果数据集很大,</li>\n<li>集成算法,多个专家的树针对不同的异常</li>\n</ol>\n<p><strong>缺点:</strong></p>\n<ol>\n<li>如果只能用1个维度,那对于图片这种高维特征,效果就不佳。</li>\n</ol>\n<p><strong>参考</strong>:</p>\n<p><a href=\"https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf\">Isolation Forest</a>[2008]</p>\n<p><a href=\"https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/tkdd11.pdf\">Isolation-based Anomaly Detection</a>[2012] </p>\n<h3 id=\"Local-Outlier-Factor(局部异常因子算法)\"><a href=\"#Local-Outlier-Factor(局部异常因子算法)\" class=\"headerlink\" title=\"Local Outlier Factor(局部异常因子算法)\"></a>Local Outlier Factor(局部异常因子算法)</h3><p><strong>定义</strong>:一种典型的基于密度的高精度离群点检测算法</p>\n<p><strong>原理</strong>: LOF算法是通过比较每个点p和邻域点的密度来判断该点是否为异常:点p的密度越低,越有可能是异常点。而点的密度是通过点之间的距离来计算的,点之间距离越远,密度越低;距离越近,密度越高。也就是说,LOF算法中点的密度是通过点的k邻域计算得到的,而不是通过全局计算得到,这里的”k邻域”也就是该算法中“局部”的概念。</p>\n<p><strong>重要参数</strong></p>\n<ul>\n<li>n_neighbors:上文的K,检测的领域点个数超过样本数则使用所有的样本进行检测</li>\n<li>contamination:异常占比,这个值很关键</li>\n<li>metric:距离度量单位</li>\n<li>p=2:距离度量单位(l1,l2分别为1,2)</li>\n</ul>\n<p><strong>具体步骤</strong>:主要就是计算LOF,如果想弄明白怎么算的直接看源论文</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>适用于对不同密度的数据的异常检测</li>\n</ol>\n<p><strong>缺点</strong></p>\n<ol>\n<li>检测的数据必须有明显的密度差异,计算较为复杂</li>\n</ol>\n<p><strong>参考</strong></p>\n<p><a href=\"https://www.dbs.ifi.lmu.de/Publikationen/Papers/LOF.pdf\">Local Outlier Factor</a>[2000]</p>\n<h3 id=\"One-Class-SVM(新颖点检测算法)\"><a href=\"#One-Class-SVM(新颖点检测算法)\" class=\"headerlink\" title=\"One Class SVM(新颖点检测算法)\"></a>One Class SVM(新颖点检测算法)</h3><p><strong>定义</strong>:无监督算法,将数据分类为不同的类型</p>\n<p><strong>原理</strong>:与SVM类似,SVM是寻找一个超平面,使用这个超平面把正常数据和异常数据划分开。而One_class SVM是基于一类数据(正常数据)求超平面,对SVM算法中求解负样本最大间隔目标进行改造。</p>\n<p><strong>优点</strong>:</p>\n<ol>\n<li>适用于高纬数据集,毕竟是超平面</li>\n</ol>\n<h3 id=\"DBSCAN(密度聚类)\"><a href=\"#DBSCAN(密度聚类)\" class=\"headerlink\" title=\"DBSCAN(密度聚类)\"></a>DBSCAN(密度聚类)</h3><p><strong>定义</strong>:无监督聚类算法,按照密度将空间内的数据进行聚类,如果单独成簇就是异常值。</p>\n<p><strong>原理</strong>:给定一个距离半径和类内最少多少个点,然后把可以满足的点全部都连起来,判定为同类。</p>\n<p><strong>优点</strong>:</p>\n<ol>\n<li>不需要知道K,按距离自动划分为簇</li>\n<li>能发现任意非球形状的簇类</li>\n<li>对输入样本的顺序并不敏感</li>\n</ol>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>不适用于高纬数据</li>\n<li>时间复杂度较高</li>\n</ol>\n<p><strong>参考</strong></p>\n<p><a href=\"http://www2.cs.uh.edu/~ceick/7363/Papers/dbscan.pdf\">DBSCAN</a></p>\n<h2 id=\"降维\"><a href=\"#降维\" class=\"headerlink\" title=\"降维\"></a>降维</h2><h3 id=\"PCA\"><a href=\"#PCA\" class=\"headerlink\" title=\"PCA\"></a>PCA</h3><p><strong>定义</strong>:主成分分析(PCA),一种使用广泛的数据降维算法,主要思想是将n维特征映射到k维上,k维是全新的正交特征也称为主成分。</p>\n<p><strong>原理</strong>:PCA的工作就是从原始的空间中顺序地寻找一组相互正交的坐标轴,第一个新坐标轴选择是原始数据中<strong>方差最大</strong>的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。取前k个包含绝大部分方差的坐标轴。</p>\n<p><strong>实现方式</strong></p>\n<ul>\n<li>基于特征值分解协方差矩阵实现</li>\n<li>基于SVD分解协方差矩阵(大样本高效,因此sklearn里面也是用svd分解)</li>\n</ul>\n<p><strong>具体步骤</strong></p>\n<ol>\n<li>中心化(norm_x)</li>\n<li>获取协方差矩阵(np.dot(norm_xT,norm_x))</li>\n<li>计算协方差矩阵的特征值和特征向量(feature_n,vectors_n)</li>\n<li>按特征值排序,选取最大的K组特征(特征值越大方差越大)(feature_k,vectors_k)</li>\n<li>反转,np.dot(norm_x,vectors_k)</li>\n</ol>\n<p><strong>特点</strong></p>\n<ol>\n<li>PCA后的特征不具有解释性</li>\n<li>PCA不适合线性不可分数据</li>\n</ol>\n<h2 id=\"半监督学习(AE类)\"><a href=\"#半监督学习(AE类)\" class=\"headerlink\" title=\"半监督学习(AE类)\"></a>半监督学习(AE类)</h2><h3 id=\"Auto-Encoder(鼻祖)\"><a href=\"#Auto-Encoder(鼻祖)\" class=\"headerlink\" title=\"Auto-Encoder(鼻祖)\"></a>Auto-Encoder(鼻祖)</h3><p><strong>背景</strong>:AE的思想最早是1986年被提出来的</p>\n<p><strong>定义</strong>:一种无监督的学习算法,其可以解决PCA无法解决的问题,因为其可以对线性不可分的数据进行降维,利用反向传播算法,让目标等于输入值。</p>\n<p><strong>原理</strong>:构建一个函数使X和Y的Loss最小,保证降维后的图片保持最大的信息。</p>\n<p><strong>重建误差</strong>:就是损失函数,原始X和重建的Y之间的差异称为重建误差</p>\n<p><strong>异常检测的使用</strong></p>\n<ol>\n<li>基于偏差的<strong>半监督学习的</strong>异常检测方法,使用<strong>重建误差</strong>作为异常分数,具有高重建误差作为异常。</li>\n<li>因此在训练阶段仅使用正常数据,训练之后AE可以很好的重建正常数据,而AE未遇到的异常数据则会重建失败(定位异常图片)</li>\n<li>为了增强鲁棒性一般会在正常数据里撒入一些噪声、或者随机将某些值变0(有点像dropout)。</li>\n</ol>\n<p><strong>为什么叫Encoder,明明有Decoder</strong>:</p>\n<p> Vincent在2010的论文中做了研究,发现只要encoder单组W就可以,decoder中的W1可以用encoder中的W转置获得。其证明,W1没有任何作用,完全没有必要训练。</p>\n<h3 id=\"Denoising-Auto-Encoder(降噪自编码)\"><a href=\"#Denoising-Auto-Encoder(降噪自编码)\" class=\"headerlink\" title=\"Denoising Auto-Encoder(降噪自编码)\"></a>Denoising Auto-Encoder(降噪自编码)</h3><p><strong>背景</strong>:Vincent在2008年的《Extracting and Composing Robust Features with Denoising Autoencoders》提出该模型</p>\n<p><strong>做法</strong>:对输入数据加入噪声,而输出数据是正常的数据,DAE会要求模型只去学习主要特征,输出的数据会有更好的鲁棒性</p>\n<h3 id=\"Sparse-Auto-Encoder(稀疏自编码)\"><a href=\"#Sparse-Auto-Encoder(稀疏自编码)\" class=\"headerlink\" title=\"Sparse Auto-Encoder(稀疏自编码)\"></a>Sparse Auto-Encoder(稀疏自编码)</h3><p><strong>背景</strong>:Andrew在2011年的《Sparse autoencoder》提出该模型</p>\n<p><strong>做法</strong>:在普通的AE的基础上增加了稀疏性约束,即要求神经元的平均输出较低,如果激活函数是sigmod,尽量让隐藏神经元输出为0,如果激活函数是tanh,尽量把输出变为-1.</p>\n<blockquote>\n<p>KL散度(相对熵): </p>\n<ol>\n<li>一种衡量两个概率分布的匹配程度的指标,两个分布差异越大,KL散度就越大。p目标分布,q是匹配分布,如果两个分布完全匹配,KL散度为0</li>\n<li>KL散度是非对称的,即D(p||q)不一定等于D(q||p)</li>\n<li>KL散度又叫相对熵,在信息论中,对于D(p||q),描述的是q去拟合p产生的信息损耗</li>\n</ol>\n</blockquote>\n<h3 id=\"Variational-Auto-Encoder(VAE)\"><a href=\"#Variational-Auto-Encoder(VAE)\" class=\"headerlink\" title=\"Variational Auto-Encoder(VAE)\"></a>Variational Auto-Encoder(VAE)</h3><p><strong>背景</strong>:2014年,用于异常检测是2015年</p>\n<p><strong>重建概率</strong>:通过导出原始输入变量分布的参数的随机潜变量来计算重建概率</p>\n<p><strong>损失函数</strong>:重建概率+KL散度</p>\n<p><strong>与AE的区别和联系</strong>:</p>\n<ol>\n<li>AE中的潜在变量由确定性映射定义,然而,由于VAE使用概率编码器来模拟潜在变量的分布,因此可以在采样过程中考虑潜在空间的可变性</li>\n<li>重建是随机变量,重建概率不仅考虑重建与原始输入之间的差异,而且还考虑分布函数的参数来重建的可变性。</li>\n<li>重建概率的计算不需要对异构数据进行处理,其重建误差的阈值比AE更客观且易于理解。</li>\n</ol>\n<p><strong>异常检测的使用</strong></p>\n<ol>\n<li>一个半监督框架,仅使用正常实例的数据来训练VAE</li>\n<li>抽取样本,对于来自encoder的每个样本,概率解码器输出均值和方差参数,使用这些参数,计算从分布产生原始数据的概率。</li>\n</ol>\n<h3 id=\"Conditional-VAE\"><a href=\"#Conditional-VAE\" class=\"headerlink\" title=\"Conditional VAE\"></a>Conditional VAE</h3><p><strong>背景</strong>:一种框架,加入某种图片的先验信息,提升训练效果。</p>\n<p><strong>优化点</strong>:</p>\n<ul>\n<li><strong>多尺度预测目标</strong>(内容很多,并行/串行,输入输出端……):Loss=Loss1(1/4图)+Loss2(1/2图)+Loss3(原图)</li>\n<li><strong>对KL散度进行近似</strong>:给KL散度增加罚函数,简单定为:batch_size/样本数量</li>\n<li><strong>增加label的one-hot</strong>:对encode和decode的输入加入label的one-hot(conditional就是这一条,但对于一场检测没有意义)</li>\n</ul>\n<p><strong>与VAE的联系与区别</strong>:</p>\n<ol>\n<li>基本还是VAE的结构,对encoder和decoder的输入增加先验信息。</li>\n<li>Loss函数进行了相应的调整。</li>\n</ol>\n<h3 id=\"A-Deep-Hierarchical-Variational-Auto-Encoder\"><a href=\"#A-Deep-Hierarchical-Variational-Auto-Encoder\" class=\"headerlink\" title=\"A Deep Hierarchical Variational Auto-Encoder\"></a>A Deep Hierarchical Variational Auto-Encoder</h3><h2 id=\"半监督学习(GAN类)\"><a href=\"#半监督学习(GAN类)\" class=\"headerlink\" title=\"半监督学习(GAN类)\"></a>半监督学习(GAN类)</h2><h3 id=\"AnoGAN\"><a href=\"#AnoGAN\" class=\"headerlink\" title=\"AnoGAN\"></a>AnoGAN</h3><p><strong>背景</strong>:GAN用于异常检测的开山之作,2017的论文</p>\n<p><strong>基本思想</strong></p>\n<ol>\n<li>训练阶段:塞入正常图片,利用DCGAN训练一个模型,希望生成器能够生成足够好的正常图片,好到辨别器也无法判别他到底对不对。</li>\n<li>测试阶段:固定生成器和辨别器,他希望在Z的潜藏空间中找到一个和X最像的映射,然后利用梯度下降法,更新Z,生成一张由潜藏空间生成且和X最像的图片。<ul>\n<li>定义一个损失函数,代表潜藏空间生成的图片和X的差异。</li>\n<li>随机抽一个Z,利用梯度下降法,不断更新使损失函数变最小,然后利用最好的Z生成图片进行异常分数计算(或者直接用)</li>\n</ul>\n</li>\n</ol>\n<p><strong>缺点:</strong></p>\n<ol>\n<li>对于X到Z的映射没有再训练阶段完成</li>\n<li>z的更新非常耗时,并且每张图片都需要更新。</li>\n<li>该异常检测应该只适用于有严格边界的图像中。</li>\n</ol>\n<p><strong>结果</strong>:</p>\n<p>原论文效果并不适用(周末试试用经典数据集进行训练)。可能因为生成模型训练的并不到位。但是loss也已经不变化了</p>\n<h3 id=\"WGAN\"><a href=\"#WGAN\" class=\"headerlink\" title=\"WGAN\"></a>WGAN</h3><p><strong>背景</strong>:令GAN研究者眼前一亮的一种新模型,其从理论上很好的解决了GAN的几大问题.</p>\n<p><strong>GAN的表面问题</strong></p>\n<ol>\n<li>训练不稳定,不容易收敛<ul>\n<li>判别器训练太好,生成器梯度消失,loss不下降。</li>\n<li>判别器训练不好,生成器不稳定。</li>\n</ul>\n</li>\n<li>生成图片的多样性不足。</li>\n</ol>\n<p><strong>GAN的本质问题</strong></p>\n<ol>\n<li>等价优化的距离衡量(KL散度、JS散度)不合理(改进办法:用Wasserstein距离代替JS散度,合理解决前两个问题)</li>\n<li>生成分布于真实分布没有重叠部分(改进办法:增加噪声)</li>\n</ol>\n<p><strong>WGAN和GAN的区别</strong></p>\n<ol>\n<li>判别器没有sigmoid,他不想DCGAN是2分类,他是回归问题</li>\n<li>生成器和判别器的loss不取log</li>\n<li>每次更新判别器的参数之后把它们的绝对值截断到不超过一个固定常数c</li>\n<li>不用Adam,用rmsprop做优化器</li>\n</ol>\n<p><strong>WGAN_GP和WGAN的区别</strong></p>\n<p><em>WGAN的问题</em></p>\n<p>由于其限制每次判别器更新参数绝对值不超过阈值c,按原论文0.01,因此会出现大量参数集中在0.01和-0.01,这就导致所有参数基本都是0.01和-0.01。判别器性能就会变得很差。</p>\n<p><em>WGAN_GP的优势</em></p>\n<p>其设置一个额外的Loss来限制判别器的梯度,将参数更新正态分布到(-0.01,0.01)内。</p>\n<p><em>改变</em></p>\n<ol>\n<li>优化器有可以用Adam了。。。</li>\n<li>由于其对每个样本独立的施加梯度惩罚,所以判别器的模型架构中不能使用Batch Normalization,改为使用Layer Normalization</li>\n</ol>\n<h3 id=\"f-AnoGAN\"><a href=\"#f-AnoGAN\" class=\"headerlink\" title=\"f-AnoGAN\"></a>f-AnoGAN</h3><p><strong>背景</strong>:AnoGan的优化版本,2019年的论文。AnoGAN存在一个问题,每一张图片都需要不断迭代训练一个Z来代表其在隐空间内的点。接着利用训练好的DCGAN进行异常检测。由于其迭代优化势必导致时间加剧。而f-AnoGAN通过引入Encoder,解决了这个问题。</p>\n<p><strong>基本思想</strong>:</p>\n<ol>\n<li><p>输入正常图片,利用WGAN_GP进行训练生成器和判别器。</p>\n</li>\n<li><p>输入正常图片,对Encoder+训练好的WGAN进行优化。提出了三种训练Encoder的方式(3种损失函数)</p>\n<ul>\n<li>izi:image-z-image1,AutoEncoder样式的损失函数</li>\n<li>ziz:z-image-z1,将Encoder放到Generate后面,比较z与z1的损失函数</li>\n<li>izif:就是Anogan中的损失函数,从像素和特征两个维度比较图片结果。</li>\n</ul>\n<p>作者实验证明izif效果最好,并且整体训练也很快。</p>\n</li>\n</ol>\n<p><strong>结果</strong>:</p>\n<p>WGAN没有收敛,两边都不断降低,不确定是不是GP哪里有问题。</p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h2 id=\"前言\"><a href=\"#前言\" class=\"headerlink\" title=\"前言\"></a>前言</h2><p><strong>定义</strong>:识别不正常情况与挖掘非逻辑数据的技术,也叫outliers。</p>\n<p><strong>前提</strong>:</p>\n<ol>\n<li>异常数据只占少数</li>\n<li>异常数据特征值和正常数据差别很大</li>\n</ol>\n<p><strong>应用领域</strong>:</p>\n<ol>\n<li>CV领域:抖音发现违规视频</li>\n<li>数据挖掘:信用卡盗刷,支付宝,异常金额支出。</li>\n</ol>\n<p><strong>模型</strong></p>\n<ol>\n<li>无监督学习、AutoEncoder、GAN、矩阵因子分解</li>\n<li>半监督学习,强化学习</li>\n<li>hybrid(混种)、特征提取+传统算法</li>\n<li>单分类神经网路(MLM)</li>\n</ol>\n<h2 id=\"统计学方法\"><a href=\"#统计学方法\" class=\"headerlink\" title=\"统计学方法\"></a>统计学方法</h2><h3 id=\"3sigma-箱形图\"><a href=\"#3sigma-箱形图\" class=\"headerlink\" title=\"3sigma/箱形图\"></a>3sigma/箱形图</h3><p><strong>原理</strong>:远离3sigma(拉依达准则)数据概率低于0.01,认为这些数据为异常值</p>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>要保证异常值较少</li>\n<li>只能检测单维数据</li>\n<li>要假定数据服从正态分布或近似</li>\n</ol>\n<h3 id=\"高斯概率密度异常检测算法(1999)\"><a href=\"#高斯概率密度异常检测算法(1999)\" class=\"headerlink\" title=\"高斯概率密度异常检测算法(1999)\"></a>高斯概率密度异常检测算法(1999)</h3><p><strong>原理</strong>:首先,该算法假设数据集服从高斯分布的,然后再分别计算训练集在空间中的重心, 和方差, 然后根据高斯概率密度估算每个点被分配到重心的概率,进而完成异常检测任务。(感觉和3sigma想法很像)</p>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>不适用于高维特征数据集</li>\n<li>要求数据大致服从高斯分布的数据集</li>\n</ol>\n<h2 id=\"无监督学习\"><a href=\"#无监督学习\" class=\"headerlink\" title=\"无监督学习\"></a>无监督学习</h2><h3 id=\"Isolation-Forest-孤立森林\"><a href=\"#Isolation-Forest-孤立森林\" class=\"headerlink\" title=\"Isolation Forest(孤立森林)\"></a>Isolation Forest(孤立森林)</h3><p><strong>定义</strong>:孤立森林是用于异常检测的机器学习算法。这是一种无监督学习算法,通过隔离数据中的离群值识别异常</p>\n<p><strong>原理</strong>:孤立森林通过<strong>随机选择特征</strong>,然后<strong>随机选择</strong>特征的<strong>分割值</strong>,递归地生成数据集的分区。和数据集中「正常」的点相比,要隔离的异常值所需的随机分区更少,因此<strong>异常值是树中路径更短的点</strong>,路径长度是从根节点经过的边数。</p>\n<p><strong>重要参数</strong></p>\n<ul>\n<li>n_estimators:树的数量</li>\n<li>max_sample:样本抽样(小样本全抽)</li>\n<li>contamination:异常占比,这个值很关键</li>\n<li>max_features:随机选取特征维度</li>\n</ul>\n<p><strong>具体步骤</strong>:</p>\n<ol>\n<li>从数据集中按max_sample进行抽样</li>\n<li>随机指定部分维度(论文是只用一个维度),在当前节点数据中随机产生一个切割点p——切割点产生于当前节点数据中指定维度的最大值和最小值之间。</li>\n<li>以此切割点生成了一个超平面,然后将当前节点数据空间划分为2个子空间:把指定维度里小于p的数据放在当前节点的左边,把大于等于p的数据放在当前节点的右边。</li>\n<li>在子节点中递归步骤2和3,不断构造新的子节点,直到子节点中只有一个数据(无法再继续切割)或子节点已到达限定高度(算法设定的)。</li>\n</ol>\n<p><strong>优点</strong>:</p>\n<ol>\n<li>节省内存。由于其主要定位异常值,因此其不要求树全部描述出来,可以用最大深度来限制。并且其不像kmeans等需要计算有关距离、密度的指标,可大幅度提升速度。</li>\n<li>适用于小数据集:因此采样,如果数据集很大,</li>\n<li>集成算法,多个专家的树针对不同的异常</li>\n</ol>\n<p><strong>缺点:</strong></p>\n<ol>\n<li>如果只能用1个维度,那对于图片这种高维特征,效果就不佳。</li>\n</ol>\n<p><strong>参考</strong>:</p>\n<p><a href=\"https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/icdm08b.pdf\">Isolation Forest</a>[2008]</p>\n<p><a href=\"https://cs.nju.edu.cn/zhouzh/zhouzh.files/publication/tkdd11.pdf\">Isolation-based Anomaly Detection</a>[2012] </p>\n<h3 id=\"Local-Outlier-Factor(局部异常因子算法)\"><a href=\"#Local-Outlier-Factor(局部异常因子算法)\" class=\"headerlink\" title=\"Local Outlier Factor(局部异常因子算法)\"></a>Local Outlier Factor(局部异常因子算法)</h3><p><strong>定义</strong>:一种典型的基于密度的高精度离群点检测算法</p>\n<p><strong>原理</strong>: LOF算法是通过比较每个点p和邻域点的密度来判断该点是否为异常:点p的密度越低,越有可能是异常点。而点的密度是通过点之间的距离来计算的,点之间距离越远,密度越低;距离越近,密度越高。也就是说,LOF算法中点的密度是通过点的k邻域计算得到的,而不是通过全局计算得到,这里的”k邻域”也就是该算法中“局部”的概念。</p>\n<p><strong>重要参数</strong></p>\n<ul>\n<li>n_neighbors:上文的K,检测的领域点个数超过样本数则使用所有的样本进行检测</li>\n<li>contamination:异常占比,这个值很关键</li>\n<li>metric:距离度量单位</li>\n<li>p=2:距离度量单位(l1,l2分别为1,2)</li>\n</ul>\n<p><strong>具体步骤</strong>:主要就是计算LOF,如果想弄明白怎么算的直接看源论文</p>\n<p><strong>优点</strong></p>\n<ol>\n<li>适用于对不同密度的数据的异常检测</li>\n</ol>\n<p><strong>缺点</strong></p>\n<ol>\n<li>检测的数据必须有明显的密度差异,计算较为复杂</li>\n</ol>\n<p><strong>参考</strong></p>\n<p><a href=\"https://www.dbs.ifi.lmu.de/Publikationen/Papers/LOF.pdf\">Local Outlier Factor</a>[2000]</p>\n<h3 id=\"One-Class-SVM(新颖点检测算法)\"><a href=\"#One-Class-SVM(新颖点检测算法)\" class=\"headerlink\" title=\"One Class SVM(新颖点检测算法)\"></a>One Class SVM(新颖点检测算法)</h3><p><strong>定义</strong>:无监督算法,将数据分类为不同的类型</p>\n<p><strong>原理</strong>:与SVM类似,SVM是寻找一个超平面,使用这个超平面把正常数据和异常数据划分开。而One_class SVM是基于一类数据(正常数据)求超平面,对SVM算法中求解负样本最大间隔目标进行改造。</p>\n<p><strong>优点</strong>:</p>\n<ol>\n<li>适用于高纬数据集,毕竟是超平面</li>\n</ol>\n<h3 id=\"DBSCAN(密度聚类)\"><a href=\"#DBSCAN(密度聚类)\" class=\"headerlink\" title=\"DBSCAN(密度聚类)\"></a>DBSCAN(密度聚类)</h3><p><strong>定义</strong>:无监督聚类算法,按照密度将空间内的数据进行聚类,如果单独成簇就是异常值。</p>\n<p><strong>原理</strong>:给定一个距离半径和类内最少多少个点,然后把可以满足的点全部都连起来,判定为同类。</p>\n<p><strong>优点</strong>:</p>\n<ol>\n<li>不需要知道K,按距离自动划分为簇</li>\n<li>能发现任意非球形状的簇类</li>\n<li>对输入样本的顺序并不敏感</li>\n</ol>\n<p><strong>缺点</strong>:</p>\n<ol>\n<li>不适用于高纬数据</li>\n<li>时间复杂度较高</li>\n</ol>\n<p><strong>参考</strong></p>\n<p><a href=\"http://www2.cs.uh.edu/~ceick/7363/Papers/dbscan.pdf\">DBSCAN</a></p>\n<h2 id=\"降维\"><a href=\"#降维\" class=\"headerlink\" title=\"降维\"></a>降维</h2><h3 id=\"PCA\"><a href=\"#PCA\" class=\"headerlink\" title=\"PCA\"></a>PCA</h3><p><strong>定义</strong>:主成分分析(PCA),一种使用广泛的数据降维算法,主要思想是将n维特征映射到k维上,k维是全新的正交特征也称为主成分。</p>\n<p><strong>原理</strong>:PCA的工作就是从原始的空间中顺序地寻找一组相互正交的坐标轴,第一个新坐标轴选择是原始数据中<strong>方差最大</strong>的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。取前k个包含绝大部分方差的坐标轴。</p>\n<p><strong>实现方式</strong></p>\n<ul>\n<li>基于特征值分解协方差矩阵实现</li>\n<li>基于SVD分解协方差矩阵(大样本高效,因此sklearn里面也是用svd分解)</li>\n</ul>\n<p><strong>具体步骤</strong></p>\n<ol>\n<li>中心化(norm_x)</li>\n<li>获取协方差矩阵(np.dot(norm_xT,norm_x))</li>\n<li>计算协方差矩阵的特征值和特征向量(feature_n,vectors_n)</li>\n<li>按特征值排序,选取最大的K组特征(特征值越大方差越大)(feature_k,vectors_k)</li>\n<li>反转,np.dot(norm_x,vectors_k)</li>\n</ol>\n<p><strong>特点</strong></p>\n<ol>\n<li>PCA后的特征不具有解释性</li>\n<li>PCA不适合线性不可分数据</li>\n</ol>\n<h2 id=\"半监督学习(AE类)\"><a href=\"#半监督学习(AE类)\" class=\"headerlink\" title=\"半监督学习(AE类)\"></a>半监督学习(AE类)</h2><h3 id=\"Auto-Encoder(鼻祖)\"><a href=\"#Auto-Encoder(鼻祖)\" class=\"headerlink\" title=\"Auto-Encoder(鼻祖)\"></a>Auto-Encoder(鼻祖)</h3><p><strong>背景</strong>:AE的思想最早是1986年被提出来的</p>\n<p><strong>定义</strong>:一种无监督的学习算法,其可以解决PCA无法解决的问题,因为其可以对线性不可分的数据进行降维,利用反向传播算法,让目标等于输入值。</p>\n<p><strong>原理</strong>:构建一个函数使X和Y的Loss最小,保证降维后的图片保持最大的信息。</p>\n<p><strong>重建误差</strong>:就是损失函数,原始X和重建的Y之间的差异称为重建误差</p>\n<p><strong>异常检测的使用</strong></p>\n<ol>\n<li>基于偏差的<strong>半监督学习的</strong>异常检测方法,使用<strong>重建误差</strong>作为异常分数,具有高重建误差作为异常。</li>\n<li>因此在训练阶段仅使用正常数据,训练之后AE可以很好的重建正常数据,而AE未遇到的异常数据则会重建失败(定位异常图片)</li>\n<li>为了增强鲁棒性一般会在正常数据里撒入一些噪声、或者随机将某些值变0(有点像dropout)。</li>\n</ol>\n<p><strong>为什么叫Encoder,明明有Decoder</strong>:</p>\n<p> Vincent在2010的论文中做了研究,发现只要encoder单组W就可以,decoder中的W1可以用encoder中的W转置获得。其证明,W1没有任何作用,完全没有必要训练。</p>\n<h3 id=\"Denoising-Auto-Encoder(降噪自编码)\"><a href=\"#Denoising-Auto-Encoder(降噪自编码)\" class=\"headerlink\" title=\"Denoising Auto-Encoder(降噪自编码)\"></a>Denoising Auto-Encoder(降噪自编码)</h3><p><strong>背景</strong>:Vincent在2008年的《Extracting and Composing Robust Features with Denoising Autoencoders》提出该模型</p>\n<p><strong>做法</strong>:对输入数据加入噪声,而输出数据是正常的数据,DAE会要求模型只去学习主要特征,输出的数据会有更好的鲁棒性</p>\n<h3 id=\"Sparse-Auto-Encoder(稀疏自编码)\"><a href=\"#Sparse-Auto-Encoder(稀疏自编码)\" class=\"headerlink\" title=\"Sparse Auto-Encoder(稀疏自编码)\"></a>Sparse Auto-Encoder(稀疏自编码)</h3><p><strong>背景</strong>:Andrew在2011年的《Sparse autoencoder》提出该模型</p>\n<p><strong>做法</strong>:在普通的AE的基础上增加了稀疏性约束,即要求神经元的平均输出较低,如果激活函数是sigmod,尽量让隐藏神经元输出为0,如果激活函数是tanh,尽量把输出变为-1.</p>\n<blockquote>\n<p>KL散度(相对熵): </p>\n<ol>\n<li>一种衡量两个概率分布的匹配程度的指标,两个分布差异越大,KL散度就越大。p目标分布,q是匹配分布,如果两个分布完全匹配,KL散度为0</li>\n<li>KL散度是非对称的,即D(p||q)不一定等于D(q||p)</li>\n<li>KL散度又叫相对熵,在信息论中,对于D(p||q),描述的是q去拟合p产生的信息损耗</li>\n</ol>\n</blockquote>\n<h3 id=\"Variational-Auto-Encoder(VAE)\"><a href=\"#Variational-Auto-Encoder(VAE)\" class=\"headerlink\" title=\"Variational Auto-Encoder(VAE)\"></a>Variational Auto-Encoder(VAE)</h3><p><strong>背景</strong>:2014年,用于异常检测是2015年</p>\n<p><strong>重建概率</strong>:通过导出原始输入变量分布的参数的随机潜变量来计算重建概率</p>\n<p><strong>损失函数</strong>:重建概率+KL散度</p>\n<p><strong>与AE的区别和联系</strong>:</p>\n<ol>\n<li>AE中的潜在变量由确定性映射定义,然而,由于VAE使用概率编码器来模拟潜在变量的分布,因此可以在采样过程中考虑潜在空间的可变性</li>\n<li>重建是随机变量,重建概率不仅考虑重建与原始输入之间的差异,而且还考虑分布函数的参数来重建的可变性。</li>\n<li>重建概率的计算不需要对异构数据进行处理,其重建误差的阈值比AE更客观且易于理解。</li>\n</ol>\n<p><strong>异常检测的使用</strong></p>\n<ol>\n<li>一个半监督框架,仅使用正常实例的数据来训练VAE</li>\n<li>抽取样本,对于来自encoder的每个样本,概率解码器输出均值和方差参数,使用这些参数,计算从分布产生原始数据的概率。</li>\n</ol>\n<h3 id=\"Conditional-VAE\"><a href=\"#Conditional-VAE\" class=\"headerlink\" title=\"Conditional VAE\"></a>Conditional VAE</h3><p><strong>背景</strong>:一种框架,加入某种图片的先验信息,提升训练效果。</p>\n<p><strong>优化点</strong>:</p>\n<ul>\n<li><strong>多尺度预测目标</strong>(内容很多,并行/串行,输入输出端……):Loss=Loss1(1/4图)+Loss2(1/2图)+Loss3(原图)</li>\n<li><strong>对KL散度进行近似</strong>:给KL散度增加罚函数,简单定为:batch_size/样本数量</li>\n<li><strong>增加label的one-hot</strong>:对encode和decode的输入加入label的one-hot(conditional就是这一条,但对于一场检测没有意义)</li>\n</ul>\n<p><strong>与VAE的联系与区别</strong>:</p>\n<ol>\n<li>基本还是VAE的结构,对encoder和decoder的输入增加先验信息。</li>\n<li>Loss函数进行了相应的调整。</li>\n</ol>\n<h3 id=\"A-Deep-Hierarchical-Variational-Auto-Encoder\"><a href=\"#A-Deep-Hierarchical-Variational-Auto-Encoder\" class=\"headerlink\" title=\"A Deep Hierarchical Variational Auto-Encoder\"></a>A Deep Hierarchical Variational Auto-Encoder</h3><h2 id=\"半监督学习(GAN类)\"><a href=\"#半监督学习(GAN类)\" class=\"headerlink\" title=\"半监督学习(GAN类)\"></a>半监督学习(GAN类)</h2><h3 id=\"AnoGAN\"><a href=\"#AnoGAN\" class=\"headerlink\" title=\"AnoGAN\"></a>AnoGAN</h3><p><strong>背景</strong>:GAN用于异常检测的开山之作,2017的论文</p>\n<p><strong>基本思想</strong></p>\n<ol>\n<li>训练阶段:塞入正常图片,利用DCGAN训练一个模型,希望生成器能够生成足够好的正常图片,好到辨别器也无法判别他到底对不对。</li>\n<li>测试阶段:固定生成器和辨别器,他希望在Z的潜藏空间中找到一个和X最像的映射,然后利用梯度下降法,更新Z,生成一张由潜藏空间生成且和X最像的图片。<ul>\n<li>定义一个损失函数,代表潜藏空间生成的图片和X的差异。</li>\n<li>随机抽一个Z,利用梯度下降法,不断更新使损失函数变最小,然后利用最好的Z生成图片进行异常分数计算(或者直接用)</li>\n</ul>\n</li>\n</ol>\n<p><strong>缺点:</strong></p>\n<ol>\n<li>对于X到Z的映射没有再训练阶段完成</li>\n<li>z的更新非常耗时,并且每张图片都需要更新。</li>\n<li>该异常检测应该只适用于有严格边界的图像中。</li>\n</ol>\n<p><strong>结果</strong>:</p>\n<p>原论文效果并不适用(周末试试用经典数据集进行训练)。可能因为生成模型训练的并不到位。但是loss也已经不变化了</p>\n<h3 id=\"WGAN\"><a href=\"#WGAN\" class=\"headerlink\" title=\"WGAN\"></a>WGAN</h3><p><strong>背景</strong>:令GAN研究者眼前一亮的一种新模型,其从理论上很好的解决了GAN的几大问题.</p>\n<p><strong>GAN的表面问题</strong></p>\n<ol>\n<li>训练不稳定,不容易收敛<ul>\n<li>判别器训练太好,生成器梯度消失,loss不下降。</li>\n<li>判别器训练不好,生成器不稳定。</li>\n</ul>\n</li>\n<li>生成图片的多样性不足。</li>\n</ol>\n<p><strong>GAN的本质问题</strong></p>\n<ol>\n<li>等价优化的距离衡量(KL散度、JS散度)不合理(改进办法:用Wasserstein距离代替JS散度,合理解决前两个问题)</li>\n<li>生成分布于真实分布没有重叠部分(改进办法:增加噪声)</li>\n</ol>\n<p><strong>WGAN和GAN的区别</strong></p>\n<ol>\n<li>判别器没有sigmoid,他不想DCGAN是2分类,他是回归问题</li>\n<li>生成器和判别器的loss不取log</li>\n<li>每次更新判别器的参数之后把它们的绝对值截断到不超过一个固定常数c</li>\n<li>不用Adam,用rmsprop做优化器</li>\n</ol>\n<p><strong>WGAN_GP和WGAN的区别</strong></p>\n<p><em>WGAN的问题</em></p>\n<p>由于其限制每次判别器更新参数绝对值不超过阈值c,按原论文0.01,因此会出现大量参数集中在0.01和-0.01,这就导致所有参数基本都是0.01和-0.01。判别器性能就会变得很差。</p>\n<p><em>WGAN_GP的优势</em></p>\n<p>其设置一个额外的Loss来限制判别器的梯度,将参数更新正态分布到(-0.01,0.01)内。</p>\n<p><em>改变</em></p>\n<ol>\n<li>优化器有可以用Adam了。。。</li>\n<li>由于其对每个样本独立的施加梯度惩罚,所以判别器的模型架构中不能使用Batch Normalization,改为使用Layer Normalization</li>\n</ol>\n<h3 id=\"f-AnoGAN\"><a href=\"#f-AnoGAN\" class=\"headerlink\" title=\"f-AnoGAN\"></a>f-AnoGAN</h3><p><strong>背景</strong>:AnoGan的优化版本,2019年的论文。AnoGAN存在一个问题,每一张图片都需要不断迭代训练一个Z来代表其在隐空间内的点。接着利用训练好的DCGAN进行异常检测。由于其迭代优化势必导致时间加剧。而f-AnoGAN通过引入Encoder,解决了这个问题。</p>\n<p><strong>基本思想</strong>:</p>\n<ol>\n<li><p>输入正常图片,利用WGAN_GP进行训练生成器和判别器。</p>\n</li>\n<li><p>输入正常图片,对Encoder+训练好的WGAN进行优化。提出了三种训练Encoder的方式(3种损失函数)</p>\n<ul>\n<li>izi:image-z-image1,AutoEncoder样式的损失函数</li>\n<li>ziz:z-image-z1,将Encoder放到Generate后面,比较z与z1的损失函数</li>\n<li>izif:就是Anogan中的损失函数,从像素和特征两个维度比较图片结果。</li>\n</ul>\n<p>作者实验证明izif效果最好,并且整体训练也很快。</p>\n</li>\n</ol>\n<p><strong>结果</strong>:</p>\n<p>WGAN没有收敛,两边都不断降低,不确定是不是GP哪里有问题。</p>\n"},{"title":"熵","date":"2021-05-31T03:25:38.000Z","mathjax":true,"description":null,"_content":"\n> 工作了一年多才深度理解熵的各种关系,因此梳理一下形成这篇博客\n\n## 问题\n\n1. 什么是信息熵,相对熵,交叉熵\n2. KL散度到底是什么\n3. 为什么在机器学习中分类任务经常用交叉熵作为损失函数来衡量一个算法的区别\n4. 交叉熵和均方误差的区别和联系\n\n我个人认为带着问题去思考会更有组与理解事情的本质,更容易聚焦\n\n## 信息熵\n\n**定义**\n\n公式:\n\n**信息熵表达了什么?**\n\n- 信息量和事件发生的概率有关,事件发生的概率越低,信息熵越大。\n- 信息熵非负,表明信息量不为0,因此信息熵是个常数。\n- 信息量可以叠加,两个事件独立的联合事件,其信息熵为两者之和。\n\n## 相对熵\n\n**定义**\n\n\t也称为KL散度,对于x有P(x)和P(y)两种分布,其公式为\n$$\n\\begin{align*}\n D_{kl}(p||q) &=\\sum_{i=1}^{n}p(x_{i})log(\\frac{p(x_{i})}{q(x_{i})}) \\\\ \n &=\\sum_{i=1}^{n}p(x_{i})log(p(x_{i})-\\sum_{i=1}^{n}p(x_{i})log(q(x_{i}) \\\\\n &=-H(x)+H(p,q)\n\\end{align*}\n$$\n前者是信息熵,后者就是我们常见的交叉熵\n\n**相对熵表达了什么?**\n\n1. 相对熵和交叉熵呈正比,因为信息熵是常数。\n2. 相对熵主要用于评价两者分布之间的差异,相对熵越大,分布差距越大。\n\n## 交叉熵\n\n**为什么机器学习使用交叉熵**\n\n在机器学习中,我们会将p(x)作为真实分布,q(x)作为预测分布,从相对熵的公式我们看出了相对熵和交叉熵的结果是呈正比的,有由于相对熵计算较为复炸,因此经常会有交叉熵来代替相对熵作为衡量真是分布和预测分布的差距。当然像VAE中用的就是KL散度我猜测是因为预测分布用的是(0,1)分布,可以比较方便使用。\n\n**其和MSE的区别和联系**\n\n**MSE**:用于回归任务\n\n**交叉熵**:用于分类任务\n\n由于交叉熵通常会和激活函数使用,如果用的是MSE在梯度传递过程中,容易出现梯度消失,因此比较多的会选用交叉熵+sigmod联合使用。\n\n## Reference\n\n[知乎1](https://zhuanlan.zhihu.com/p/70804197)\n\n[知乎2](https://zhuanlan.zhihu.com/p/149186719)\n\n","source":"_posts/熵.md","raw":"---\ntitle: 熵\ncategories:\n - 机器学习\ntags:\n - 基础知识\ndate: 2021-05-31 11:25:38\nmathjax: true\ndescription:\n---\n\n> 工作了一年多才深度理解熵的各种关系,因此梳理一下形成这篇博客\n\n## 问题\n\n1. 什么是信息熵,相对熵,交叉熵\n2. KL散度到底是什么\n3. 为什么在机器学习中分类任务经常用交叉熵作为损失函数来衡量一个算法的区别\n4. 交叉熵和均方误差的区别和联系\n\n我个人认为带着问题去思考会更有组与理解事情的本质,更容易聚焦\n\n## 信息熵\n\n**定义**\n\n公式:\n\n**信息熵表达了什么?**\n\n- 信息量和事件发生的概率有关,事件发生的概率越低,信息熵越大。\n- 信息熵非负,表明信息量不为0,因此信息熵是个常数。\n- 信息量可以叠加,两个事件独立的联合事件,其信息熵为两者之和。\n\n## 相对熵\n\n**定义**\n\n\t也称为KL散度,对于x有P(x)和P(y)两种分布,其公式为\n$$\n\\begin{align*}\n D_{kl}(p||q) &=\\sum_{i=1}^{n}p(x_{i})log(\\frac{p(x_{i})}{q(x_{i})}) \\\\ \n &=\\sum_{i=1}^{n}p(x_{i})log(p(x_{i})-\\sum_{i=1}^{n}p(x_{i})log(q(x_{i}) \\\\\n &=-H(x)+H(p,q)\n\\end{align*}\n$$\n前者是信息熵,后者就是我们常见的交叉熵\n\n**相对熵表达了什么?**\n\n1. 相对熵和交叉熵呈正比,因为信息熵是常数。\n2. 相对熵主要用于评价两者分布之间的差异,相对熵越大,分布差距越大。\n\n## 交叉熵\n\n**为什么机器学习使用交叉熵**\n\n在机器学习中,我们会将p(x)作为真实分布,q(x)作为预测分布,从相对熵的公式我们看出了相对熵和交叉熵的结果是呈正比的,有由于相对熵计算较为复炸,因此经常会有交叉熵来代替相对熵作为衡量真是分布和预测分布的差距。当然像VAE中用的就是KL散度我猜测是因为预测分布用的是(0,1)分布,可以比较方便使用。\n\n**其和MSE的区别和联系**\n\n**MSE**:用于回归任务\n\n**交叉熵**:用于分类任务\n\n由于交叉熵通常会和激活函数使用,如果用的是MSE在梯度传递过程中,容易出现梯度消失,因此比较多的会选用交叉熵+sigmod联合使用。\n\n## Reference\n\n[知乎1](https://zhuanlan.zhihu.com/p/70804197)\n\n[知乎2](https://zhuanlan.zhihu.com/p/149186719)\n\n","slug":"熵","published":1,"updated":"2021-09-18T08:43:45.915Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb5m000wso3w1bpy6yqs","content":"<blockquote>\n<p>工作了一年多才深度理解熵的各种关系,因此梳理一下形成这篇博客</p>\n</blockquote>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><ol>\n<li>什么是信息熵,相对熵,交叉熵</li>\n<li>KL散度到底是什么</li>\n<li>为什么在机器学习中分类任务经常用交叉熵作为损失函数来衡量一个算法的区别</li>\n<li>交叉熵和均方误差的区别和联系</li>\n</ol>\n<p>我个人认为带着问题去思考会更有组与理解事情的本质,更容易聚焦</p>\n<h2 id=\"信息熵\"><a href=\"#信息熵\" class=\"headerlink\" title=\"信息熵\"></a>信息熵</h2><p><strong>定义</strong></p>\n<p>公式:<img src=\"/images/shang.webp\" alt=\"shang\"></p>\n<p><strong>信息熵表达了什么?</strong></p>\n<ul>\n<li>信息量和事件发生的概率有关,事件发生的概率越低,信息熵越大。</li>\n<li>信息熵非负,表明信息量不为0,因此信息熵是个常数。</li>\n<li>信息量可以叠加,两个事件独立的联合事件,其信息熵为两者之和。</li>\n</ul>\n<h2 id=\"相对熵\"><a href=\"#相对熵\" class=\"headerlink\" title=\"相对熵\"></a>相对熵</h2><p><strong>定义</strong></p>\n<p> 也称为KL散度,对于x有P(x)和P(y)两种分布,其公式为</p>\n<script type=\"math/tex; mode=display\">\n\\begin{align*}\n D_{kl}(p||q) &=\\sum_{i=1}^{n}p(x_{i})log(\\frac{p(x_{i})}{q(x_{i})}) \\\\ \n &=\\sum_{i=1}^{n}p(x_{i})log(p(x_{i})-\\sum_{i=1}^{n}p(x_{i})log(q(x_{i}) \\\\\n &=-H(x)+H(p,q)\n\\end{align*}</script><p>前者是信息熵,后者就是我们常见的交叉熵</p>\n<p><strong>相对熵表达了什么?</strong></p>\n<ol>\n<li>相对熵和交叉熵呈正比,因为信息熵是常数。</li>\n<li>相对熵主要用于评价两者分布之间的差异,相对熵越大,分布差距越大。</li>\n</ol>\n<h2 id=\"交叉熵\"><a href=\"#交叉熵\" class=\"headerlink\" title=\"交叉熵\"></a>交叉熵</h2><p><strong>为什么机器学习使用交叉熵</strong></p>\n<p>在机器学习中,我们会将p(x)作为真实分布,q(x)作为预测分布,从相对熵的公式我们看出了相对熵和交叉熵的结果是呈正比的,有由于相对熵计算较为复炸,因此经常会有交叉熵来代替相对熵作为衡量真是分布和预测分布的差距。当然像VAE中用的就是KL散度我猜测是因为预测分布用的是(0,1)分布,可以比较方便使用。</p>\n<p><strong>其和MSE的区别和联系</strong></p>\n<p><strong>MSE</strong>:用于回归任务</p>\n<p><strong>交叉熵</strong>:用于分类任务</p>\n<p>由于交叉熵通常会和激活函数使用,如果用的是MSE在梯度传递过程中,容易出现梯度消失,因此比较多的会选用交叉熵+sigmod联合使用。</p>\n<h2 id=\"Reference\"><a href=\"#Reference\" class=\"headerlink\" title=\"Reference\"></a>Reference</h2><p><a href=\"https://zhuanlan.zhihu.com/p/70804197\">知乎1</a></p>\n<p><a href=\"https://zhuanlan.zhihu.com/p/149186719\">知乎2</a></p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<blockquote>\n<p>工作了一年多才深度理解熵的各种关系,因此梳理一下形成这篇博客</p>\n</blockquote>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><ol>\n<li>什么是信息熵,相对熵,交叉熵</li>\n<li>KL散度到底是什么</li>\n<li>为什么在机器学习中分类任务经常用交叉熵作为损失函数来衡量一个算法的区别</li>\n<li>交叉熵和均方误差的区别和联系</li>\n</ol>\n<p>我个人认为带着问题去思考会更有组与理解事情的本质,更容易聚焦</p>\n<h2 id=\"信息熵\"><a href=\"#信息熵\" class=\"headerlink\" title=\"信息熵\"></a>信息熵</h2><p><strong>定义</strong></p>\n<p>公式:<img src=\"/images/shang.webp\" alt=\"shang\"></p>\n<p><strong>信息熵表达了什么?</strong></p>\n<ul>\n<li>信息量和事件发生的概率有关,事件发生的概率越低,信息熵越大。</li>\n<li>信息熵非负,表明信息量不为0,因此信息熵是个常数。</li>\n<li>信息量可以叠加,两个事件独立的联合事件,其信息熵为两者之和。</li>\n</ul>\n<h2 id=\"相对熵\"><a href=\"#相对熵\" class=\"headerlink\" title=\"相对熵\"></a>相对熵</h2><p><strong>定义</strong></p>\n<p> 也称为KL散度,对于x有P(x)和P(y)两种分布,其公式为</p>\n<script type=\"math/tex; mode=display\">\n\\begin{align*}\n D_{kl}(p||q) &=\\sum_{i=1}^{n}p(x_{i})log(\\frac{p(x_{i})}{q(x_{i})}) \\\\ \n &=\\sum_{i=1}^{n}p(x_{i})log(p(x_{i})-\\sum_{i=1}^{n}p(x_{i})log(q(x_{i}) \\\\\n &=-H(x)+H(p,q)\n\\end{align*}</script><p>前者是信息熵,后者就是我们常见的交叉熵</p>\n<p><strong>相对熵表达了什么?</strong></p>\n<ol>\n<li>相对熵和交叉熵呈正比,因为信息熵是常数。</li>\n<li>相对熵主要用于评价两者分布之间的差异,相对熵越大,分布差距越大。</li>\n</ol>\n<h2 id=\"交叉熵\"><a href=\"#交叉熵\" class=\"headerlink\" title=\"交叉熵\"></a>交叉熵</h2><p><strong>为什么机器学习使用交叉熵</strong></p>\n<p>在机器学习中,我们会将p(x)作为真实分布,q(x)作为预测分布,从相对熵的公式我们看出了相对熵和交叉熵的结果是呈正比的,有由于相对熵计算较为复炸,因此经常会有交叉熵来代替相对熵作为衡量真是分布和预测分布的差距。当然像VAE中用的就是KL散度我猜测是因为预测分布用的是(0,1)分布,可以比较方便使用。</p>\n<p><strong>其和MSE的区别和联系</strong></p>\n<p><strong>MSE</strong>:用于回归任务</p>\n<p><strong>交叉熵</strong>:用于分类任务</p>\n<p>由于交叉熵通常会和激活函数使用,如果用的是MSE在梯度传递过程中,容易出现梯度消失,因此比较多的会选用交叉熵+sigmod联合使用。</p>\n<h2 id=\"Reference\"><a href=\"#Reference\" class=\"headerlink\" title=\"Reference\"></a>Reference</h2><p><a href=\"https://zhuanlan.zhihu.com/p/70804197\">知乎1</a></p>\n<p><a href=\"https://zhuanlan.zhihu.com/p/149186719\">知乎2</a></p>\n"},{"title":"数据标准化和中心化","mathjax":true,"date":"2021-08-19T02:51:19.000Z","description":null,"_content":"\n## 定义\n\n标准化:也叫归一化,常用的有最小—最大标准化、Z-score标准化、Sigmod函数等,利用上述标准化操作,将原始数据化为**无量纲**的数值。这在回归和神经网络类模型是很必要的。\n\n中心化:也叫去均质化,让数据通过中心化处理,得到均值为0的数据【如果方差为1,那就是Z-score标准化】。同时中心化后的数据对向量来说也容易描述,因为是以原点为基准的。\n\n## 公式\n\n最小—最大标准化:将数据变为0-1\n$$\n\\tilde{x}=\\frac{x-min}{max-min}\n$$\n\n\nZ-score标准化:\n$$\n\\tilde{x}=\\frac{x-u}{\\sigma }\n$$\nsigmod函数:\n$$\nf(x)=\\frac{1}{1+e^{-x}}\n$$\n**备注**:\n\n1. 当$\\sigma=1$就是中心化公式,所以中心化可以说是一种特殊的标准化\n2. sigmod函数其实主要是在激活函数中对上一层神经层结果进行标准化,但也可以理解成对下一层神经层的输入做标准化。\n\n## 优点\n\n1. 提升精度:消除量纲,针对梯度下降相关的算法,不同量纲的数据会产生干扰,可能本来是A特征对结果的影响更大,但因为其量纲较小,导致其影响力还不如量纲大的特征,从来导致精度的下降。\n2. 提神速度:同样,由于消除了量纲,在梯度下降过程中,将会容易收敛到最优解。\n\n**备注**:但是对于非梯度下降优化器或者是树模型是可以不标准的,因为数据的量纲并不影响树模型的分裂,对于特征而言该在哪里分裂就还是在那里分裂。","source":"_posts/数据标准化和中心化.md","raw":"---\ntitle: 数据标准化和中心化\ncategories:\n - 基础知识\ntags:\n - 数据预处理\nmathjax: true\ndate: 2021-08-19 10:51:19\ndescription:\n---\n\n## 定义\n\n标准化:也叫归一化,常用的有最小—最大标准化、Z-score标准化、Sigmod函数等,利用上述标准化操作,将原始数据化为**无量纲**的数值。这在回归和神经网络类模型是很必要的。\n\n中心化:也叫去均质化,让数据通过中心化处理,得到均值为0的数据【如果方差为1,那就是Z-score标准化】。同时中心化后的数据对向量来说也容易描述,因为是以原点为基准的。\n\n## 公式\n\n最小—最大标准化:将数据变为0-1\n$$\n\\tilde{x}=\\frac{x-min}{max-min}\n$$\n\n\nZ-score标准化:\n$$\n\\tilde{x}=\\frac{x-u}{\\sigma }\n$$\nsigmod函数:\n$$\nf(x)=\\frac{1}{1+e^{-x}}\n$$\n**备注**:\n\n1. 当$\\sigma=1$就是中心化公式,所以中心化可以说是一种特殊的标准化\n2. sigmod函数其实主要是在激活函数中对上一层神经层结果进行标准化,但也可以理解成对下一层神经层的输入做标准化。\n\n## 优点\n\n1. 提升精度:消除量纲,针对梯度下降相关的算法,不同量纲的数据会产生干扰,可能本来是A特征对结果的影响更大,但因为其量纲较小,导致其影响力还不如量纲大的特征,从来导致精度的下降。\n2. 提神速度:同样,由于消除了量纲,在梯度下降过程中,将会容易收敛到最优解。\n\n**备注**:但是对于非梯度下降优化器或者是树模型是可以不标准的,因为数据的量纲并不影响树模型的分裂,对于特征而言该在哪里分裂就还是在那里分裂。","slug":"数据标准化和中心化","published":1,"updated":"2021-09-18T08:43:45.915Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb5n000yso3whtve4rls","content":"<h2 id=\"定义\"><a href=\"#定义\" class=\"headerlink\" title=\"定义\"></a>定义</h2><p>标准化:也叫归一化,常用的有最小—最大标准化、Z-score标准化、Sigmod函数等,利用上述标准化操作,将原始数据化为<strong>无量纲</strong>的数值。这在回归和神经网络类模型是很必要的。</p>\n<p>中心化:也叫去均质化,让数据通过中心化处理,得到均值为0的数据【如果方差为1,那就是Z-score标准化】。同时中心化后的数据对向量来说也容易描述,因为是以原点为基准的。</p>\n<h2 id=\"公式\"><a href=\"#公式\" class=\"headerlink\" title=\"公式\"></a>公式</h2><p>最小—最大标准化:将数据变为0-1</p>\n<script type=\"math/tex; mode=display\">\n\\tilde{x}=\\frac{x-min}{max-min}</script><p>Z-score标准化:</p>\n<script type=\"math/tex; mode=display\">\n\\tilde{x}=\\frac{x-u}{\\sigma }</script><p>sigmod函数:</p>\n<script type=\"math/tex; mode=display\">\nf(x)=\\frac{1}{1+e^{-x}}</script><p><strong>备注</strong>:</p>\n<ol>\n<li>当$\\sigma=1$就是中心化公式,所以中心化可以说是一种特殊的标准化</li>\n<li>sigmod函数其实主要是在激活函数中对上一层神经层结果进行标准化,但也可以理解成对下一层神经层的输入做标准化。</li>\n</ol>\n<h2 id=\"优点\"><a href=\"#优点\" class=\"headerlink\" title=\"优点\"></a>优点</h2><ol>\n<li>提升精度:消除量纲,针对梯度下降相关的算法,不同量纲的数据会产生干扰,可能本来是A特征对结果的影响更大,但因为其量纲较小,导致其影响力还不如量纲大的特征,从来导致精度的下降。</li>\n<li>提神速度:同样,由于消除了量纲,在梯度下降过程中,将会容易收敛到最优解。</li>\n</ol>\n<p><strong>备注</strong>:但是对于非梯度下降优化器或者是树模型是可以不标准的,因为数据的量纲并不影响树模型的分裂,对于特征而言该在哪里分裂就还是在那里分裂。</p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h2 id=\"定义\"><a href=\"#定义\" class=\"headerlink\" title=\"定义\"></a>定义</h2><p>标准化:也叫归一化,常用的有最小—最大标准化、Z-score标准化、Sigmod函数等,利用上述标准化操作,将原始数据化为<strong>无量纲</strong>的数值。这在回归和神经网络类模型是很必要的。</p>\n<p>中心化:也叫去均质化,让数据通过中心化处理,得到均值为0的数据【如果方差为1,那就是Z-score标准化】。同时中心化后的数据对向量来说也容易描述,因为是以原点为基准的。</p>\n<h2 id=\"公式\"><a href=\"#公式\" class=\"headerlink\" title=\"公式\"></a>公式</h2><p>最小—最大标准化:将数据变为0-1</p>\n<script type=\"math/tex; mode=display\">\n\\tilde{x}=\\frac{x-min}{max-min}</script><p>Z-score标准化:</p>\n<script type=\"math/tex; mode=display\">\n\\tilde{x}=\\frac{x-u}{\\sigma }</script><p>sigmod函数:</p>\n<script type=\"math/tex; mode=display\">\nf(x)=\\frac{1}{1+e^{-x}}</script><p><strong>备注</strong>:</p>\n<ol>\n<li>当$\\sigma=1$就是中心化公式,所以中心化可以说是一种特殊的标准化</li>\n<li>sigmod函数其实主要是在激活函数中对上一层神经层结果进行标准化,但也可以理解成对下一层神经层的输入做标准化。</li>\n</ol>\n<h2 id=\"优点\"><a href=\"#优点\" class=\"headerlink\" title=\"优点\"></a>优点</h2><ol>\n<li>提升精度:消除量纲,针对梯度下降相关的算法,不同量纲的数据会产生干扰,可能本来是A特征对结果的影响更大,但因为其量纲较小,导致其影响力还不如量纲大的特征,从来导致精度的下降。</li>\n<li>提神速度:同样,由于消除了量纲,在梯度下降过程中,将会容易收敛到最优解。</li>\n</ol>\n<p><strong>备注</strong>:但是对于非梯度下降优化器或者是树模型是可以不标准的,因为数据的量纲并不影响树模型的分裂,对于特征而言该在哪里分裂就还是在那里分裂。</p>\n"},{"title":"Transomer学习","date":"2021-04-11T03:10:00.000Z","description":["此文档写于2019年,建成于博客创立之前。"],"_content":"\n\n# Transfomer拆分\n\n为了更好的学习当前NLP主流模型,如Bert,GPT2及Bert一系列的衍生物,Transfomer是这一系列的基础。因此本文的主要目的是记录个人基于一些博客和原论文对Transfomer模型进行拆分的结果。\n\n**目的**:减少计算量并提高并行效率,同时不减弱最终的实验结果。\n\n**创新点**:\n\n1. Self-attention\n2. Multi-Head Attention\n\n# 背景知识\n\n## seq2seq\n\n定义:seq2seq模型是采用一系列项目(单词、字母、图像特征等)并输出另一个项目序列的模型。在机器翻译中,序列是一系列单词,经过seq2seq后,输出同样是一系列单词。\n\n<video src=\"F:\\video\\seq2seq_2.mp4\"></video>\n\n接下来我们掀开这个model内部,该模型主要由一个Encoder和一个Decoder组成。\n\n- Encoder:处理输入序列的每个项目,捕捉载体中的信息(context)。\n- Decoder:处理完整序列后,Encoder将信息(context)传递至Decoder,并且开始逐项生产输出序列。\n\n<video src=\"F:\\video\\seq2seq_4.mp4\"></video>\n\n而context是一个向量,其大小基于等同于编码器中RNN的隐藏神经元。\n\n在单词输入之前,我们需要将单词转化为词向量,其可以通过word2vec等模型进行预训练训练词库,然后将单词按照词库的词向量简单提取即可,在上面的资历中,其处理过程如下:\n\n\n\n\n\n这里简单将其设为维度4,通常设为200或300,在此,简单展示一下RNN的实现原理。\n\n<video src=\"F:\\video\\RNN_1.mp4\"></video>\n\n利用先前的输入的隐藏状态,RNN将其输出至一个新的隐藏状态,接下来我们看看seq2seq中的隐藏状态是怎么进行的。\n\n<video src=\"F:\\video\\seq2seq_5.mp4\"></video>\n\nEncoder中最后一个hidden-state实际就是前文提到的context,接下来我们进一步拆解,展示其具体细节。\n\n<video src=\"F:\\video\\seq2seq_6.mp4\"></video>\n\n总结:由于在Encoder阶段,每个单词输入都会产生一个新的hidden_state,最后输出一个context给Decoder进行解码。因此,当文本内容过长时,容易丢失部分信息,为了解决这个问题,Attention应运而生。\n\n## Attention\n\nAttention这个概念最早出现在《Neural machine traslation by jointly learning to align and translate》论文中,其后《Neural image caption generation with visual attention》对attention形式进行总结。\n\n定义:为了解决文本过长信息丢失的问题,相较于seq2seq模型,attention最大的区别就是他不在要求把所以信息都编入最后的隐藏状态中,而是可以在编码过程中,对每一个隐藏状态进行保留,最后在解码的过程中,每一步都会选择性的从编码的隐藏状态中选一个和当前状态最接近的子集进行处理,这样在产生每一个输出时就能够充分利用输入序列携程的信息,下面很好的展示了attention在seq2seq模型中运用。\n\n<video src=\"F:\\video\\seq2seq_7.mp4\"></video>\n\n接下来,我们放大一下decoder对于attention后隐藏状态的具体使用,其在每一步解码均要进行。\n\n1. 查看在attention机制中每个具体的隐藏状态,选出其与句子中的那个单词最相关。\n2. 给每个隐藏状态打分。\n3. 将每个隐藏状态进行softmax以放大具有高分数的隐藏状态。\n4. 进行总和形成attention输入Decoder的向量。\n\n<video src=\"F:\\video\\attention_process.mp4\"></video>\n\n最后,将整个过程放一起,以便更好的理解attention机制(代码实现的时候进一步理解)。\n\n1. Decoder输入:初始化一个解码器隐藏状态+经过预训练后的词向量。\n2. 将前两者输入到RNN中,产生一个新的隐藏状态h4和输出,将输出丢弃(seq2seq是直接将context送入下一个RNN作为输入)。\n3. attention:利用h4和encoder层简历的隐藏状态进行计算context(c4)。\n4. 将c4和h4进行concatenate。\n5. 将其结果通过前向神经网络,输出一个结果。\n6. 该结果表示当前时间输出的对应文字。\n7. 重复下一个步骤。\n\n<video src=\"F:\\video\\attention_tensor_dance.mp4\"></video>\n\n注意:该模型并不是将输入和输出的单词一一对应,他有可能一个单词对应两个单词甚至影响第三个单词,这是经过训练得到的。\n\n# 正文\n\n总算要写到Transformer部分了,有点小激动,让我们一起来看看这个影响到现在的模型到底长啥样,为了便于理解,我这边会结合代码+论文进行讲解。\n\n首先,引入原论文的结构图\n\n\n\n看不懂不要紧,这边引入代码的结构图\n\n\n\n## Encoder-Decoder\n\n从宏观角度来看,Transformer与Seq2Seq的结构相同,依然引入经典的Encoder-Decoder结构,只是其中的神经层已经不是以前的RNN和CNN,而是完全引入注意力机制来进行构建。\n\n\n\n上图代码的复现结构\n\n```python\nclass EncoderDecoder(nn.Module):\n \"\"\"\n 整体来说Transformer还是Encoder和Decoder结构,其中包括两个Embedding,一块Encoder,一块Decoder,一个输出层\n \"\"\"\n\n def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):\n super(EncoderDecoder, self).__init__()\n self.encoder = encoder\n self.decoder = decoder\n self.src_embed = src_embed\n self.tgt_embed = tgt_embed\n self.generator = generator\n\n def forward(self, src, tgt, src_mask, tgt_mask):\n \"\"\"Take in and process masked src and target sequences.\"\"\"\n return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)\n\n def encode(self, src, src_mask):\n return self.encoder(self.src_embed(src), src_mask)\n\n def decode(self, memory, src_mask, tgt, tgt_mask):\n return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)\n```\n\n接下来,我将按照结构顺序一一介绍\n\n## Embedding\n\n这一层没什么好说的倒是,就是一个Embedding层。\n\n```python\nclass Embeddings(nn.Module):\n\t\"\"\"\n\t\t将单词转化为词向量\n\t\"\"\"\n def __init__(self, d_model, vocab):\n super(Embeddings, self).__init__()\n self.lut = nn.Embedding(vocab, d_model)\n self.d_model = d_model\n\n def forward(self, x):\n return self.lut(x) * math.sqrt(self.d_model)\n\n```\n\n**困惑**:为什么要乘以sqrt(d_model),希望有大神给予指点!\n\n## Positional Emcoding\n\n由于Transfomer完全引入注意力机制,其不像CNN和RNN会对输入单词顺序自动打上标签,其无法输出每个单词的顺序,在机器翻译中可是爆炸的啊,举个例子,你输入一句:我欠你的一千万不用还了,他返回一句:你欠我的一千万不用还了,那不是血崩。\n\n为了解决这个问题,Transfomer在Encoder过程中为每个输入单词打上一个位置编码,然后在Decoder将位置变量信息添加,该方法不用模型训练,直接按规则进行添加。\n\n我们看看原论文里的图\n\n\n\n其计算的具体公式在原论文3.5,\n\n\n\n对于上述公式,代码中进行了对数转化,具体如下\n\n```python\nclass PositionalEncoding(nn.Module):\n \"\"\"\n \t位置变量公式详见https://arxiv.org/abs/1706.03762(3.5)\n \"\"\"\n def __init__(self, d_model, dropout, max_len=5000):\n super(PositionalEncoding, self).__init__()\n self.dropout = nn.Dropout(dropout)\n # pe 初始化为0,shape为 [n, d_model] 的矩阵,用来存放最终的PositionalEncoding的\n pe = torch.zeros(max_len, d_model)\n # position 表示位置,shape为 [max_len, 1],从0开始到 max_len\n position = torch.arange(0., max_len).unsqueeze(1)\n # 这个是变形得到的,shape 为 [1, d_model//2]\n div_term = torch.exp(torch.arange(0., d_model, 2) * -(math.log(10000.0) / d_model))\n # 矩阵相乘 (max_len, 1) 与 (1, d_model // 2) 相乘,最后结果 shape (max_len, d_model // 2)\n # 即所有行,一半的列。(行为句子的长度,列为向量的维度)\n boy = position * div_term\n # 偶数列 奇数列 分别赋值\n pe[:, 0::2] = torch.sin(boy)\n pe[:, 1::2] = torch.cos(boy)\n # 为何还要在前面加一维???\n pe = pe.unsqueeze(0)\n # Parameter 会在反向传播时更新\n # Buffer不会,是固定的,本文就是不想让其被修改\n self.register_buffer('pe', pe)\n\n def forward(self, x):\n # x能和后面的相加说明shape是一致的,也是 (1, sentence_len, d_model)\n # forward 其实就是,将原来的Embedding 再加上 Positional Embedding\n x += Variable(self.pe[:, :x.size(1)], requires_grad=False)\n return self.dropout(x)\n```\n\n## Encoder\n\nEncoder代码结构\n\n```python\nclass Encoder(nn.Module):\n \"\"\"核心Encoder是由N层堆叠而成\"\"\"\n\n def __init__(self, layer, N):\n super(Encoder, self).__init__()\n self.layers = clones(layer, N)\n self.norm = LayerNorm(layer.size)\n\n def forward(self, x, mask):\n \"\"\"\n 将输入x和掩码mask逐层传递下去,最后再 LayerNorm 一下。\n \"\"\"\n for layer in self.layers:\n x = layer(x, mask)\n return self.norm(x)\n```\n\nEncoder块主要就是利用Clone函数重复构建相同结构的Encoder_layer.\n\n### Clone\n\n整个Encoder部分由N个Encoder_layer堆积二乘,为了复刻每个层的结构,构建克隆函数。(Decoder类似)\n\n```python\ndef clones(module, N):\n \"\"\"\n \tcopyN层形成一个list\n \"\"\"\n return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])\n```\n\n### Encoder_layer\n\n接下来,我们在将独立的Encoder_layer进行拆分,看看里面到底是什么东西。\n\n\n\n上图很清晰的展示Encoder_layer内部的结构,X1,X2是经过转化的词向量,经过Positional Emcoding进入Encider_layer,喝口水,这部分要讲的有点多……\n\n首先,Encoder_layer分为上下两层,第一层包含self-attention+SublayerConnection,第二层为FNN+SublayerConnection。Attention的内容我后面再说,这里先讲下什么是SublayerConnection。attention的内容我后面再说,这里先讲下什么是SublayerConnection。\n\n**SublayerConnection**\n\nSublayerConnection内部设计基于两个核心:\n\n1. Residual_Connection(残差连接),这是上图的Add\n2. Layer Normalize(层级归一化),这是上图的Normalize\n\n如下图所示,残差连接,就是在原图正常的结构下,将X进行保留,最终得出的结果是X+F(x).\n\n**优势**:反向传播过程中,对X求导会多出一个常数1,梯度连乘,不会出现梯度消失(详细的内容等我以后探究一下)\n\n\n\n具体代码如下:\n\n```python\nclass SublayerConnection(nn.Module):\n \"\"\"\n\t\tA residual connection followed by a layer norm.Note for code simplicity the norms is first as opposed to last.\n \"\"\"\n\n def __init__(self, size, dropout):\n super(SublayerConnection, self).__init__()\n self.norm = LayerNorm(size)\n self.dropout = nn.Dropout(dropout)\n\n def forward(self, x, sublayer):\n return x + self.dropout(sublayer(self.norm(x)))\n\n```\n\n**Layer Normalize**\n\n归一化最简单的理解就是将输入转化为均值为0,方差为1的数据,而由于NLP中Sequence长短不一,LN相较于BN在RNN这种类似的结构效果会好很多。其具体的概括就是:对每一个输入样本进行归一化操作,保证该样本在多维度符合均值0,方差为1.\n\n```python\nclass LayerNorm(nn.Module):\n \"\"\"构建一个 layernorm层,具体细节看论文 https://arxiv.org/abs/1607.06450\n \"\"\"\n\n def __init__(self, features, eps=1e-6):\n super(LayerNorm, self).__init__()\n self.a_2 = nn.Parameter(torch.ones(features))\n self.b_2 = nn.Parameter(torch.zeros(features))\n self.eps = eps\n\n def forward(self, x):\n mean = x.mean(-1, keepdim=True)\n std = x.std(-1, keepdim=True)\n return self.a_2 * (x - mean) / (std + self.eps) + self.b_2\n\n```\n\n**FNN**\n\n前馈神经网络,这就不细说了,具体代码如下:\n\n```python\nclass PositionwiseFeedForward(nn.Module):\n\n def __init__(self, d_model, d_ff, dropout=0.1):\n super(PositionwiseFeedForward, self).__init__()\n self.w_1 = nn.Linear(d_model, d_ff)\n self.w_2 = nn.Linear(d_ff, d_model)\n self.dropout = nn.Dropout(dropout)\n\n def forward(self, x):\n return self.w_2(self.dropout(F.relu(self.w_1(x))))\n\n```\n\n放张图,清晰一点\n\n\n\n## Decoder\n\n与Encoder结构类似,其由N个Decoder_layer组成(Transformer中N=6),相较于Encoder,其接受的参数多了Encoder生成的memory以及目标句子中的掩码gt_mask.\n\n```python\nclass Decoder(nn.Module):\n\t\"\"\"\n\t\tGener is N layer decoder with masking\n\t\"\"\"\n def __init__(self, layer, N):\n super(Decoder, self).__init__()\n self.layers = clones(layer, N)\n self.norm = LayerNorm(layer.size)\n\n def forward(self, x, memory, src_mask, tgt_mask):\n for layer in self.layers:\n x = layer(x, memory, src_mask, tgt_mask)\n return self.norm(x)\n```\n\n### Decoder layer\n\n下图很好的表明了Decoder_layer的区别\n\n\n\n简单的解释一下,每一个Decoder_layer有三层组成\n\n第一层:Self-Attention+SublayerConnection\n\n第二层:Encoder-Decoder Attention+SublayerConnection(Decoder独有)\n\n第三层:FFN+SublayerConnection\n\n具体代码如下:\n\n```python\nclass DecoderLayer(nn.Module):\n\n def __init__(self, size, self_attn, src_attn, feed_forward, dropout):\n super(DecoderLayer, self).__init__()\n self.size = size\n self.self_attn = self_attn\n self.src_attn = src_attn\n self.feed_forward = feed_forward\n self.sublayer = clones(SublayerConnection(size, dropout), 3)\n\n def forward(self, x, memory, src_mask, tgt_mask):\n m = memory\n x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))\n x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))\n return self.sublayer[2](x, self.feed_forward)\n```\n\n### Mask\n\n**作用:**Mask简单来说就是掩码的意思,在我们这里的意思大概就是对某些值进行掩盖,不使其产生效果。\n\n文中的Mask主要包括**两种**:\n\n- src_mask(padding_mask):由于Sequence长短不一,利用Padding对其填充为0,然后在对其进行attention的过程中,这些位置没有意义的,src_mask的作用就是不将Attention机制放在这些位置上进行处理,这就是padding_mask,其基于作用于所有attention。\n- tgt_mask(sequence_mask):对于机器翻译而言,采用监督学习,而原句子和目标句子对输入模型进行训练,那就需要确保,Decoder在生成单词的过程中,不能看到后面的单词,这就是sequence_mask,其主要在Decoder中的self-attention中起作用。\n\n具体代码:\n\n```python\ndef subsequent_mask(size):\n \"\"\"\n \tMask out subequent position\n \t这个是tgt_mask,他不让decoder过程中看到后面的单词,训练模型的过程中掩盖翻译后面的单词。\n \"\"\"\n print(subsequent_mask(3))\n tensor([[[1, 0, 0],\n [1, 1, 0],\n [1, 1, 1]]], dtype=torch.uint8)\n\n \"\"\"\n attn_shape = (1, size, size)\n subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')\n return torch.from_numpy(subsequent_mask) == 0\n```\n\n### Attention\n\nTransfomer的最大创新就是两种Attention,Self-Attention,Multi-Head Attention\n\nSelf-Attention\n\n作用:简单来说,就是计算句子里每个单词受其他单词的影响程度,其最大意义在于可以学到语义依赖关系。\n\n计算公式:\n\n\n\n不好理解,上图\n\n\n\n再看看原论文的图\n\n\n\n再来看看代码:\n\n```python\ndef attention(query, key, value, mask=None, dropout=None):\n \"\"\"Compute 'Scaled Dot Product Attention\"\"\"\n d_k = query.size(-1)\n scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)\n if mask:\n scores = scores.masked_fill(mask == 0, -1e9)\n p_attn = F.softmax(scores, dim=-1)\n if dropout:\n p_attn = dropout(p_attn)\n return torch.matmul(p_attn, value)\n```\n\n基本参照看公式应该可以很好的看懂这些代码,具体的步骤各位就参考原论文吧。\n\n**Multi-Head Attention**\n\n多个Self-Attention并行计算就叫多头计算(Multi-Head Attention)模式,也就是我们俗称的多头怪,你可以将其理解为集成学习,这也是Transformer训练速度超快的原因。\n\n废话不说,上图:\n\n\n\n首先,我们接进来单词转化为词向量X(n,512)和W^Q, W^k, W^v(512,64)相乘,得出Q,K,V(n,64),就生产一个Z(n,64),然后利用8个头并行操作,就生产8个Z(n,64).\n\n\n\n然后将8个Z拼接起来,Z就变成了(n,512)内积W^0(512,512),输出最终的Z,传递到下一个FNN层。\n\n\n\n```python\nclass MultiHeadedAttention(nn.Module):\n def __init__(self, h, d_model, dropout=0.1):\n \"Take in model size and number of heads.\"\n super(MultiHeadedAttention, self).__init__()\n assert d_model % h == 0\n # We assume d_v always equals d_k\n self.d_k = d_model // h\n self.h = h\n self.linears = clones(nn.Linear(d_model, d_model), 4)\n self.attn = None\n self.dropout = nn.Dropout(p=dropout)\n \n def forward(self, query, key, value, mask=None):\n \"Implements Figure 2\"\n if mask is not None:\n # Same mask applied to all h heads.\n mask = mask.unsqueeze(1)\n nbatches = query.size(0)\n \n # 1) Do all the linear projections in batch from d_model => h x d_k \n query, key, value = \\\n [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)\n for l, x in zip(self.linears, (query, key, value))]\n \n # 2) Apply attention on all the projected vectors in batch. \n x, self.attn = attention(query, key, value, mask=mask, \n dropout=self.dropout)\n \n # 3) \"Concat\" using a view and apply a final linear. \n x = x.transpose(1, 2).contiguous() \\\n .view(nbatches, -1, self.h * self.d_k)\n return self.linears[-1](x)\n\n```\n\n## Generator\n\n就是数据经过Encoder-Decoder结构后还需要一个线性连接层和softmax层,这一部分预测层,命名为Generator。\n\n```python\nclass Generator(nn.Module):\n \"\"\"\n \t定义输出结构,一个线性连接层+softmax层\n \"\"\"\n def __init__(self, d_model, vocab):\n super(Generator, self).__init__()\n self.proj = nn.Linear(d_model, vocab)\n\n def forward(self, x):\n return F.log_softmax(self.proj(x), dim=-1)\n\n```\n\n好了,这基本上把整个Transfomer进行了一个非常细致的拆分。Transformer可以说现在所有最新模型的基础,对于这一部分还是需要好好理解。\n\n# 参考链接\n\n- [1] [原论文](https://arxiv.org/pdf/1706.03762.pdf)\n- [2] [NLP国外大牛(Jay Alammar)详解Transformer](https://jalammar.github.io/illustrated-transformer/)\n- [3] [哈佛NLP](https://nlp.seas.harvard.edu/2018/04/03/attention.html#attention)\n- [4] [Blog](https://juejin.im/post/5b9f1af0e51d450e425eb32d#heading-10)\n- [5] [Csdn](https://blog.csdn.net/baidu_20163013/article/details/97389827)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","source":"_posts/初识Transomer.md","raw":"---\ntitle: Transomer学习\ndate: 2021-04-11 11:10:00\ncategories:\n- 深度学习\ntags:\n- transfomer\ndescription:\n- 此文档写于2019年,建成于博客创立之前。\n---\n\n\n# Transfomer拆分\n\n为了更好的学习当前NLP主流模型,如Bert,GPT2及Bert一系列的衍生物,Transfomer是这一系列的基础。因此本文的主要目的是记录个人基于一些博客和原论文对Transfomer模型进行拆分的结果。\n\n**目的**:减少计算量并提高并行效率,同时不减弱最终的实验结果。\n\n**创新点**:\n\n1. Self-attention\n2. Multi-Head Attention\n\n# 背景知识\n\n## seq2seq\n\n定义:seq2seq模型是采用一系列项目(单词、字母、图像特征等)并输出另一个项目序列的模型。在机器翻译中,序列是一系列单词,经过seq2seq后,输出同样是一系列单词。\n\n<video src=\"F:\\video\\seq2seq_2.mp4\"></video>\n\n接下来我们掀开这个model内部,该模型主要由一个Encoder和一个Decoder组成。\n\n- Encoder:处理输入序列的每个项目,捕捉载体中的信息(context)。\n- Decoder:处理完整序列后,Encoder将信息(context)传递至Decoder,并且开始逐项生产输出序列。\n\n<video src=\"F:\\video\\seq2seq_4.mp4\"></video>\n\n而context是一个向量,其大小基于等同于编码器中RNN的隐藏神经元。\n\n在单词输入之前,我们需要将单词转化为词向量,其可以通过word2vec等模型进行预训练训练词库,然后将单词按照词库的词向量简单提取即可,在上面的资历中,其处理过程如下:\n\n\n\n\n\n这里简单将其设为维度4,通常设为200或300,在此,简单展示一下RNN的实现原理。\n\n<video src=\"F:\\video\\RNN_1.mp4\"></video>\n\n利用先前的输入的隐藏状态,RNN将其输出至一个新的隐藏状态,接下来我们看看seq2seq中的隐藏状态是怎么进行的。\n\n<video src=\"F:\\video\\seq2seq_5.mp4\"></video>\n\nEncoder中最后一个hidden-state实际就是前文提到的context,接下来我们进一步拆解,展示其具体细节。\n\n<video src=\"F:\\video\\seq2seq_6.mp4\"></video>\n\n总结:由于在Encoder阶段,每个单词输入都会产生一个新的hidden_state,最后输出一个context给Decoder进行解码。因此,当文本内容过长时,容易丢失部分信息,为了解决这个问题,Attention应运而生。\n\n## Attention\n\nAttention这个概念最早出现在《Neural machine traslation by jointly learning to align and translate》论文中,其后《Neural image caption generation with visual attention》对attention形式进行总结。\n\n定义:为了解决文本过长信息丢失的问题,相较于seq2seq模型,attention最大的区别就是他不在要求把所以信息都编入最后的隐藏状态中,而是可以在编码过程中,对每一个隐藏状态进行保留,最后在解码的过程中,每一步都会选择性的从编码的隐藏状态中选一个和当前状态最接近的子集进行处理,这样在产生每一个输出时就能够充分利用输入序列携程的信息,下面很好的展示了attention在seq2seq模型中运用。\n\n<video src=\"F:\\video\\seq2seq_7.mp4\"></video>\n\n接下来,我们放大一下decoder对于attention后隐藏状态的具体使用,其在每一步解码均要进行。\n\n1. 查看在attention机制中每个具体的隐藏状态,选出其与句子中的那个单词最相关。\n2. 给每个隐藏状态打分。\n3. 将每个隐藏状态进行softmax以放大具有高分数的隐藏状态。\n4. 进行总和形成attention输入Decoder的向量。\n\n<video src=\"F:\\video\\attention_process.mp4\"></video>\n\n最后,将整个过程放一起,以便更好的理解attention机制(代码实现的时候进一步理解)。\n\n1. Decoder输入:初始化一个解码器隐藏状态+经过预训练后的词向量。\n2. 将前两者输入到RNN中,产生一个新的隐藏状态h4和输出,将输出丢弃(seq2seq是直接将context送入下一个RNN作为输入)。\n3. attention:利用h4和encoder层简历的隐藏状态进行计算context(c4)。\n4. 将c4和h4进行concatenate。\n5. 将其结果通过前向神经网络,输出一个结果。\n6. 该结果表示当前时间输出的对应文字。\n7. 重复下一个步骤。\n\n<video src=\"F:\\video\\attention_tensor_dance.mp4\"></video>\n\n注意:该模型并不是将输入和输出的单词一一对应,他有可能一个单词对应两个单词甚至影响第三个单词,这是经过训练得到的。\n\n# 正文\n\n总算要写到Transformer部分了,有点小激动,让我们一起来看看这个影响到现在的模型到底长啥样,为了便于理解,我这边会结合代码+论文进行讲解。\n\n首先,引入原论文的结构图\n\n\n\n看不懂不要紧,这边引入代码的结构图\n\n\n\n## Encoder-Decoder\n\n从宏观角度来看,Transformer与Seq2Seq的结构相同,依然引入经典的Encoder-Decoder结构,只是其中的神经层已经不是以前的RNN和CNN,而是完全引入注意力机制来进行构建。\n\n\n\n上图代码的复现结构\n\n```python\nclass EncoderDecoder(nn.Module):\n \"\"\"\n 整体来说Transformer还是Encoder和Decoder结构,其中包括两个Embedding,一块Encoder,一块Decoder,一个输出层\n \"\"\"\n\n def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):\n super(EncoderDecoder, self).__init__()\n self.encoder = encoder\n self.decoder = decoder\n self.src_embed = src_embed\n self.tgt_embed = tgt_embed\n self.generator = generator\n\n def forward(self, src, tgt, src_mask, tgt_mask):\n \"\"\"Take in and process masked src and target sequences.\"\"\"\n return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)\n\n def encode(self, src, src_mask):\n return self.encoder(self.src_embed(src), src_mask)\n\n def decode(self, memory, src_mask, tgt, tgt_mask):\n return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)\n```\n\n接下来,我将按照结构顺序一一介绍\n\n## Embedding\n\n这一层没什么好说的倒是,就是一个Embedding层。\n\n```python\nclass Embeddings(nn.Module):\n\t\"\"\"\n\t\t将单词转化为词向量\n\t\"\"\"\n def __init__(self, d_model, vocab):\n super(Embeddings, self).__init__()\n self.lut = nn.Embedding(vocab, d_model)\n self.d_model = d_model\n\n def forward(self, x):\n return self.lut(x) * math.sqrt(self.d_model)\n\n```\n\n**困惑**:为什么要乘以sqrt(d_model),希望有大神给予指点!\n\n## Positional Emcoding\n\n由于Transfomer完全引入注意力机制,其不像CNN和RNN会对输入单词顺序自动打上标签,其无法输出每个单词的顺序,在机器翻译中可是爆炸的啊,举个例子,你输入一句:我欠你的一千万不用还了,他返回一句:你欠我的一千万不用还了,那不是血崩。\n\n为了解决这个问题,Transfomer在Encoder过程中为每个输入单词打上一个位置编码,然后在Decoder将位置变量信息添加,该方法不用模型训练,直接按规则进行添加。\n\n我们看看原论文里的图\n\n\n\n其计算的具体公式在原论文3.5,\n\n\n\n对于上述公式,代码中进行了对数转化,具体如下\n\n```python\nclass PositionalEncoding(nn.Module):\n \"\"\"\n \t位置变量公式详见https://arxiv.org/abs/1706.03762(3.5)\n \"\"\"\n def __init__(self, d_model, dropout, max_len=5000):\n super(PositionalEncoding, self).__init__()\n self.dropout = nn.Dropout(dropout)\n # pe 初始化为0,shape为 [n, d_model] 的矩阵,用来存放最终的PositionalEncoding的\n pe = torch.zeros(max_len, d_model)\n # position 表示位置,shape为 [max_len, 1],从0开始到 max_len\n position = torch.arange(0., max_len).unsqueeze(1)\n # 这个是变形得到的,shape 为 [1, d_model//2]\n div_term = torch.exp(torch.arange(0., d_model, 2) * -(math.log(10000.0) / d_model))\n # 矩阵相乘 (max_len, 1) 与 (1, d_model // 2) 相乘,最后结果 shape (max_len, d_model // 2)\n # 即所有行,一半的列。(行为句子的长度,列为向量的维度)\n boy = position * div_term\n # 偶数列 奇数列 分别赋值\n pe[:, 0::2] = torch.sin(boy)\n pe[:, 1::2] = torch.cos(boy)\n # 为何还要在前面加一维???\n pe = pe.unsqueeze(0)\n # Parameter 会在反向传播时更新\n # Buffer不会,是固定的,本文就是不想让其被修改\n self.register_buffer('pe', pe)\n\n def forward(self, x):\n # x能和后面的相加说明shape是一致的,也是 (1, sentence_len, d_model)\n # forward 其实就是,将原来的Embedding 再加上 Positional Embedding\n x += Variable(self.pe[:, :x.size(1)], requires_grad=False)\n return self.dropout(x)\n```\n\n## Encoder\n\nEncoder代码结构\n\n```python\nclass Encoder(nn.Module):\n \"\"\"核心Encoder是由N层堆叠而成\"\"\"\n\n def __init__(self, layer, N):\n super(Encoder, self).__init__()\n self.layers = clones(layer, N)\n self.norm = LayerNorm(layer.size)\n\n def forward(self, x, mask):\n \"\"\"\n 将输入x和掩码mask逐层传递下去,最后再 LayerNorm 一下。\n \"\"\"\n for layer in self.layers:\n x = layer(x, mask)\n return self.norm(x)\n```\n\nEncoder块主要就是利用Clone函数重复构建相同结构的Encoder_layer.\n\n### Clone\n\n整个Encoder部分由N个Encoder_layer堆积二乘,为了复刻每个层的结构,构建克隆函数。(Decoder类似)\n\n```python\ndef clones(module, N):\n \"\"\"\n \tcopyN层形成一个list\n \"\"\"\n return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])\n```\n\n### Encoder_layer\n\n接下来,我们在将独立的Encoder_layer进行拆分,看看里面到底是什么东西。\n\n\n\n上图很清晰的展示Encoder_layer内部的结构,X1,X2是经过转化的词向量,经过Positional Emcoding进入Encider_layer,喝口水,这部分要讲的有点多……\n\n首先,Encoder_layer分为上下两层,第一层包含self-attention+SublayerConnection,第二层为FNN+SublayerConnection。Attention的内容我后面再说,这里先讲下什么是SublayerConnection。attention的内容我后面再说,这里先讲下什么是SublayerConnection。\n\n**SublayerConnection**\n\nSublayerConnection内部设计基于两个核心:\n\n1. Residual_Connection(残差连接),这是上图的Add\n2. Layer Normalize(层级归一化),这是上图的Normalize\n\n如下图所示,残差连接,就是在原图正常的结构下,将X进行保留,最终得出的结果是X+F(x).\n\n**优势**:反向传播过程中,对X求导会多出一个常数1,梯度连乘,不会出现梯度消失(详细的内容等我以后探究一下)\n\n\n\n具体代码如下:\n\n```python\nclass SublayerConnection(nn.Module):\n \"\"\"\n\t\tA residual connection followed by a layer norm.Note for code simplicity the norms is first as opposed to last.\n \"\"\"\n\n def __init__(self, size, dropout):\n super(SublayerConnection, self).__init__()\n self.norm = LayerNorm(size)\n self.dropout = nn.Dropout(dropout)\n\n def forward(self, x, sublayer):\n return x + self.dropout(sublayer(self.norm(x)))\n\n```\n\n**Layer Normalize**\n\n归一化最简单的理解就是将输入转化为均值为0,方差为1的数据,而由于NLP中Sequence长短不一,LN相较于BN在RNN这种类似的结构效果会好很多。其具体的概括就是:对每一个输入样本进行归一化操作,保证该样本在多维度符合均值0,方差为1.\n\n```python\nclass LayerNorm(nn.Module):\n \"\"\"构建一个 layernorm层,具体细节看论文 https://arxiv.org/abs/1607.06450\n \"\"\"\n\n def __init__(self, features, eps=1e-6):\n super(LayerNorm, self).__init__()\n self.a_2 = nn.Parameter(torch.ones(features))\n self.b_2 = nn.Parameter(torch.zeros(features))\n self.eps = eps\n\n def forward(self, x):\n mean = x.mean(-1, keepdim=True)\n std = x.std(-1, keepdim=True)\n return self.a_2 * (x - mean) / (std + self.eps) + self.b_2\n\n```\n\n**FNN**\n\n前馈神经网络,这就不细说了,具体代码如下:\n\n```python\nclass PositionwiseFeedForward(nn.Module):\n\n def __init__(self, d_model, d_ff, dropout=0.1):\n super(PositionwiseFeedForward, self).__init__()\n self.w_1 = nn.Linear(d_model, d_ff)\n self.w_2 = nn.Linear(d_ff, d_model)\n self.dropout = nn.Dropout(dropout)\n\n def forward(self, x):\n return self.w_2(self.dropout(F.relu(self.w_1(x))))\n\n```\n\n放张图,清晰一点\n\n\n\n## Decoder\n\n与Encoder结构类似,其由N个Decoder_layer组成(Transformer中N=6),相较于Encoder,其接受的参数多了Encoder生成的memory以及目标句子中的掩码gt_mask.\n\n```python\nclass Decoder(nn.Module):\n\t\"\"\"\n\t\tGener is N layer decoder with masking\n\t\"\"\"\n def __init__(self, layer, N):\n super(Decoder, self).__init__()\n self.layers = clones(layer, N)\n self.norm = LayerNorm(layer.size)\n\n def forward(self, x, memory, src_mask, tgt_mask):\n for layer in self.layers:\n x = layer(x, memory, src_mask, tgt_mask)\n return self.norm(x)\n```\n\n### Decoder layer\n\n下图很好的表明了Decoder_layer的区别\n\n\n\n简单的解释一下,每一个Decoder_layer有三层组成\n\n第一层:Self-Attention+SublayerConnection\n\n第二层:Encoder-Decoder Attention+SublayerConnection(Decoder独有)\n\n第三层:FFN+SublayerConnection\n\n具体代码如下:\n\n```python\nclass DecoderLayer(nn.Module):\n\n def __init__(self, size, self_attn, src_attn, feed_forward, dropout):\n super(DecoderLayer, self).__init__()\n self.size = size\n self.self_attn = self_attn\n self.src_attn = src_attn\n self.feed_forward = feed_forward\n self.sublayer = clones(SublayerConnection(size, dropout), 3)\n\n def forward(self, x, memory, src_mask, tgt_mask):\n m = memory\n x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))\n x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))\n return self.sublayer[2](x, self.feed_forward)\n```\n\n### Mask\n\n**作用:**Mask简单来说就是掩码的意思,在我们这里的意思大概就是对某些值进行掩盖,不使其产生效果。\n\n文中的Mask主要包括**两种**:\n\n- src_mask(padding_mask):由于Sequence长短不一,利用Padding对其填充为0,然后在对其进行attention的过程中,这些位置没有意义的,src_mask的作用就是不将Attention机制放在这些位置上进行处理,这就是padding_mask,其基于作用于所有attention。\n- tgt_mask(sequence_mask):对于机器翻译而言,采用监督学习,而原句子和目标句子对输入模型进行训练,那就需要确保,Decoder在生成单词的过程中,不能看到后面的单词,这就是sequence_mask,其主要在Decoder中的self-attention中起作用。\n\n具体代码:\n\n```python\ndef subsequent_mask(size):\n \"\"\"\n \tMask out subequent position\n \t这个是tgt_mask,他不让decoder过程中看到后面的单词,训练模型的过程中掩盖翻译后面的单词。\n \"\"\"\n print(subsequent_mask(3))\n tensor([[[1, 0, 0],\n [1, 1, 0],\n [1, 1, 1]]], dtype=torch.uint8)\n\n \"\"\"\n attn_shape = (1, size, size)\n subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')\n return torch.from_numpy(subsequent_mask) == 0\n```\n\n### Attention\n\nTransfomer的最大创新就是两种Attention,Self-Attention,Multi-Head Attention\n\nSelf-Attention\n\n作用:简单来说,就是计算句子里每个单词受其他单词的影响程度,其最大意义在于可以学到语义依赖关系。\n\n计算公式:\n\n\n\n不好理解,上图\n\n\n\n再看看原论文的图\n\n\n\n再来看看代码:\n\n```python\ndef attention(query, key, value, mask=None, dropout=None):\n \"\"\"Compute 'Scaled Dot Product Attention\"\"\"\n d_k = query.size(-1)\n scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)\n if mask:\n scores = scores.masked_fill(mask == 0, -1e9)\n p_attn = F.softmax(scores, dim=-1)\n if dropout:\n p_attn = dropout(p_attn)\n return torch.matmul(p_attn, value)\n```\n\n基本参照看公式应该可以很好的看懂这些代码,具体的步骤各位就参考原论文吧。\n\n**Multi-Head Attention**\n\n多个Self-Attention并行计算就叫多头计算(Multi-Head Attention)模式,也就是我们俗称的多头怪,你可以将其理解为集成学习,这也是Transformer训练速度超快的原因。\n\n废话不说,上图:\n\n\n\n首先,我们接进来单词转化为词向量X(n,512)和W^Q, W^k, W^v(512,64)相乘,得出Q,K,V(n,64),就生产一个Z(n,64),然后利用8个头并行操作,就生产8个Z(n,64).\n\n\n\n然后将8个Z拼接起来,Z就变成了(n,512)内积W^0(512,512),输出最终的Z,传递到下一个FNN层。\n\n\n\n```python\nclass MultiHeadedAttention(nn.Module):\n def __init__(self, h, d_model, dropout=0.1):\n \"Take in model size and number of heads.\"\n super(MultiHeadedAttention, self).__init__()\n assert d_model % h == 0\n # We assume d_v always equals d_k\n self.d_k = d_model // h\n self.h = h\n self.linears = clones(nn.Linear(d_model, d_model), 4)\n self.attn = None\n self.dropout = nn.Dropout(p=dropout)\n \n def forward(self, query, key, value, mask=None):\n \"Implements Figure 2\"\n if mask is not None:\n # Same mask applied to all h heads.\n mask = mask.unsqueeze(1)\n nbatches = query.size(0)\n \n # 1) Do all the linear projections in batch from d_model => h x d_k \n query, key, value = \\\n [l(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)\n for l, x in zip(self.linears, (query, key, value))]\n \n # 2) Apply attention on all the projected vectors in batch. \n x, self.attn = attention(query, key, value, mask=mask, \n dropout=self.dropout)\n \n # 3) \"Concat\" using a view and apply a final linear. \n x = x.transpose(1, 2).contiguous() \\\n .view(nbatches, -1, self.h * self.d_k)\n return self.linears[-1](x)\n\n```\n\n## Generator\n\n就是数据经过Encoder-Decoder结构后还需要一个线性连接层和softmax层,这一部分预测层,命名为Generator。\n\n```python\nclass Generator(nn.Module):\n \"\"\"\n \t定义输出结构,一个线性连接层+softmax层\n \"\"\"\n def __init__(self, d_model, vocab):\n super(Generator, self).__init__()\n self.proj = nn.Linear(d_model, vocab)\n\n def forward(self, x):\n return F.log_softmax(self.proj(x), dim=-1)\n\n```\n\n好了,这基本上把整个Transfomer进行了一个非常细致的拆分。Transformer可以说现在所有最新模型的基础,对于这一部分还是需要好好理解。\n\n# 参考链接\n\n- [1] [原论文](https://arxiv.org/pdf/1706.03762.pdf)\n- [2] [NLP国外大牛(Jay Alammar)详解Transformer](https://jalammar.github.io/illustrated-transformer/)\n- [3] [哈佛NLP](https://nlp.seas.harvard.edu/2018/04/03/attention.html#attention)\n- [4] [Blog](https://juejin.im/post/5b9f1af0e51d450e425eb32d#heading-10)\n- [5] [Csdn](https://blog.csdn.net/baidu_20163013/article/details/97389827)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","slug":"初识Transomer","published":1,"updated":"2021-09-18T08:43:45.914Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb6v001qso3wansd4vvb","content":"<h1 id=\"Transfomer拆分\"><a href=\"#Transfomer拆分\" class=\"headerlink\" title=\"Transfomer拆分\"></a>Transfomer拆分</h1><p>为了更好的学习当前NLP主流模型,如Bert,GPT2及Bert一系列的衍生物,Transfomer是这一系列的基础。因此本文的主要目的是记录个人基于一些博客和原论文对Transfomer模型进行拆分的结果。</p>\n<p><strong>目的</strong>:减少计算量并提高并行效率,同时不减弱最终的实验结果。</p>\n<p><strong>创新点</strong>:</p>\n<ol>\n<li>Self-attention</li>\n<li>Multi-Head Attention</li>\n</ol>\n<h1 id=\"背景知识\"><a href=\"#背景知识\" class=\"headerlink\" title=\"背景知识\"></a>背景知识</h1><h2 id=\"seq2seq\"><a href=\"#seq2seq\" class=\"headerlink\" title=\"seq2seq\"></a>seq2seq</h2><p>定义:seq2seq模型是采用一系列项目(单词、字母、图像特征等)并输出另一个项目序列的模型。在机器翻译中,序列是一系列单词,经过seq2seq后,输出同样是一系列单词。</p>\n<video src=\"F:\\video\\seq2seq_2.mp4\"></video>\n\n<p>接下来我们掀开这个model内部,该模型主要由一个Encoder和一个Decoder组成。</p>\n<ul>\n<li>Encoder:处理输入序列的每个项目,捕捉载体中的信息(context)。</li>\n<li>Decoder:处理完整序列后,Encoder将信息(context)传递至Decoder,并且开始逐项生产输出序列。</li>\n</ul>\n<video src=\"F:\\video\\seq2seq_4.mp4\"></video>\n\n<p>而context是一个向量,其大小基于等同于编码器中RNN的隐藏神经元。</p>\n<p>在单词输入之前,我们需要将单词转化为词向量,其可以通过word2vec等模型进行预训练训练词库,然后将单词按照词库的词向量简单提取即可,在上面的资历中,其处理过程如下:</p>\n<p><img src=\"F:\\video\\embedding.png\" alt=\"embedding\"></p>\n<p>这里简单将其设为维度4,通常设为200或300,在此,简单展示一下RNN的实现原理。</p>\n<video src=\"F:\\video\\RNN_1.mp4\"></video>\n\n<p>利用先前的输入的隐藏状态,RNN将其输出至一个新的隐藏状态,接下来我们看看seq2seq中的隐藏状态是怎么进行的。</p>\n<video src=\"F:\\video\\seq2seq_5.mp4\"></video>\n\n<p>Encoder中最后一个hidden-state实际就是前文提到的context,接下来我们进一步拆解,展示其具体细节。</p>\n<video src=\"F:\\video\\seq2seq_6.mp4\"></video>\n\n<p>总结:由于在Encoder阶段,每个单词输入都会产生一个新的hidden_state,最后输出一个context给Decoder进行解码。因此,当文本内容过长时,容易丢失部分信息,为了解决这个问题,Attention应运而生。</p>\n<h2 id=\"Attention\"><a href=\"#Attention\" class=\"headerlink\" title=\"Attention\"></a>Attention</h2><p>Attention这个概念最早出现在《Neural machine traslation by jointly learning to align and translate》论文中,其后《Neural image caption generation with visual attention》对attention形式进行总结。</p>\n<p>定义:为了解决文本过长信息丢失的问题,相较于seq2seq模型,attention最大的区别就是他不在要求把所以信息都编入最后的隐藏状态中,而是可以在编码过程中,对每一个隐藏状态进行保留,最后在解码的过程中,每一步都会选择性的从编码的隐藏状态中选一个和当前状态最接近的子集进行处理,这样在产生每一个输出时就能够充分利用输入序列携程的信息,下面很好的展示了attention在seq2seq模型中运用。</p>\n<video src=\"F:\\video\\seq2seq_7.mp4\"></video>\n\n<p>接下来,我们放大一下decoder对于attention后隐藏状态的具体使用,其在每一步解码均要进行。</p>\n<ol>\n<li>查看在attention机制中每个具体的隐藏状态,选出其与句子中的那个单词最相关。</li>\n<li>给每个隐藏状态打分。</li>\n<li>将每个隐藏状态进行softmax以放大具有高分数的隐藏状态。</li>\n<li>进行总和形成attention输入Decoder的向量。</li>\n</ol>\n<video src=\"F:\\video\\attention_process.mp4\"></video>\n\n<p>最后,将整个过程放一起,以便更好的理解attention机制(代码实现的时候进一步理解)。</p>\n<ol>\n<li>Decoder输入:初始化一个解码器隐藏状态+经过预训练后的词向量。</li>\n<li>将前两者输入到RNN中,产生一个新的隐藏状态h4和输出,将输出丢弃(seq2seq是直接将context送入下一个RNN作为输入)。</li>\n<li>attention:利用h4和encoder层简历的隐藏状态进行计算context(c4)。</li>\n<li>将c4和h4进行concatenate。</li>\n<li>将其结果通过前向神经网络,输出一个结果。</li>\n<li>该结果表示当前时间输出的对应文字。</li>\n<li>重复下一个步骤。</li>\n</ol>\n<video src=\"F:\\video\\attention_tensor_dance.mp4\"></video>\n\n<p>注意:该模型并不是将输入和输出的单词一一对应,他有可能一个单词对应两个单词甚至影响第三个单词,这是经过训练得到的。</p>\n<h1 id=\"正文\"><a href=\"#正文\" class=\"headerlink\" title=\"正文\"></a>正文</h1><p>总算要写到Transformer部分了,有点小激动,让我们一起来看看这个影响到现在的模型到底长啥样,为了便于理解,我这边会结合代码+论文进行讲解。</p>\n<p>首先,引入原论文的结构图</p>\n<p><img src=\"F:\\video\\结构图.png\" alt=\"结构图\"></p>\n<p>看不懂不要紧,这边引入代码的结构图</p>\n<p><img src=\"F:\\video\\代码结构图.png\" alt=\"代码结构图\"></p>\n<h2 id=\"Encoder-Decoder\"><a href=\"#Encoder-Decoder\" class=\"headerlink\" title=\"Encoder-Decoder\"></a>Encoder-Decoder</h2><p>从宏观角度来看,Transformer与Seq2Seq的结构相同,依然引入经典的Encoder-Decoder结构,只是其中的神经层已经不是以前的RNN和CNN,而是完全引入注意力机制来进行构建。</p>\n<p><img src=\"F:\\video\\stack.png\" alt=\"stack\"></p>\n<p>上图代码的复现结构</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">EncoderDecoder</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> 整体来说Transformer还是Encoder和Decoder结构,其中包括两个Embedding,一块Encoder,一块Decoder,一个输出层</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, encoder, decoder, src_embed, tgt_embed, generator</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(EncoderDecoder, self).__init__()</span><br><span class=\"line\"> self.encoder = encoder</span><br><span class=\"line\"> self.decoder = decoder</span><br><span class=\"line\"> self.src_embed = src_embed</span><br><span class=\"line\"> self.tgt_embed = tgt_embed</span><br><span class=\"line\"> self.generator = generator</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, src, tgt, src_mask, tgt_mask</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""Take in and process masked src and target sequences."""</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">encode</span>(<span class=\"params\">self, src, src_mask</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.encoder(self.src_embed(src), src_mask)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">decode</span>(<span class=\"params\">self, memory, src_mask, tgt, tgt_mask</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)</span><br></pre></td></tr></table></figure>\n<p>接下来,我将按照结构顺序一一介绍</p>\n<h2 id=\"Embedding\"><a href=\"#Embedding\" class=\"headerlink\" title=\"Embedding\"></a>Embedding</h2><p>这一层没什么好说的倒是,就是一个Embedding层。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Embeddings</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\">\t<span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\">\t\t将单词转化为词向量</span></span><br><span class=\"line\"><span class=\"string\">\t"""</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, vocab</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Embeddings, self).__init__()</span><br><span class=\"line\"> self.lut = nn.Embedding(vocab, d_model)</span><br><span class=\"line\"> self.d_model = d_model</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.lut(x) * math.sqrt(self.d_model)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>困惑</strong>:为什么要乘以sqrt(d_model),希望有大神给予指点!</p>\n<h2 id=\"Positional-Emcoding\"><a href=\"#Positional-Emcoding\" class=\"headerlink\" title=\"Positional Emcoding\"></a>Positional Emcoding</h2><p>由于Transfomer完全引入注意力机制,其不像CNN和RNN会对输入单词顺序自动打上标签,其无法输出每个单词的顺序,在机器翻译中可是爆炸的啊,举个例子,你输入一句:我欠你的一千万不用还了,他返回一句:你欠我的一千万不用还了,那不是血崩。</p>\n<p>为了解决这个问题,Transfomer在Encoder过程中为每个输入单词打上一个位置编码,然后在Decoder将位置变量信息添加,该方法不用模型训练,直接按规则进行添加。</p>\n<p>我们看看原论文里的图</p>\n<p><img src=\"F:\\video\\Emcodeing.png\" alt=\"Emcodeing\"></p>\n<p>其计算的具体公式在原论文3.5,</p>\n<p><img src=\"F:\\video\\Emcoding公式.png\" alt=\"Emcoding公式\"></p>\n<p>对于上述公式,代码中进行了对数转化,具体如下</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">PositionalEncoding</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \t位置变量公式详见https://arxiv.org/abs/1706.03762(3.5)</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, dropout, max_len=<span class=\"number\">5000</span></span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(PositionalEncoding, self).__init__()</span><br><span class=\"line\"> self.dropout = nn.Dropout(dropout)</span><br><span class=\"line\"> <span class=\"comment\"># pe 初始化为0,shape为 [n, d_model] 的矩阵,用来存放最终的PositionalEncoding的</span></span><br><span class=\"line\"> pe = torch.zeros(max_len, d_model)</span><br><span class=\"line\"> <span class=\"comment\"># position 表示位置,shape为 [max_len, 1],从0开始到 max_len</span></span><br><span class=\"line\"> position = torch.arange(<span class=\"number\">0.</span>, max_len).unsqueeze(<span class=\"number\">1</span>)</span><br><span class=\"line\"> <span class=\"comment\"># 这个是变形得到的,shape 为 [1, d_model//2]</span></span><br><span class=\"line\"> div_term = torch.exp(torch.arange(<span class=\"number\">0.</span>, d_model, <span class=\"number\">2</span>) * -(math.log(<span class=\"number\">10000.0</span>) / d_model))</span><br><span class=\"line\"> <span class=\"comment\"># 矩阵相乘 (max_len, 1) 与 (1, d_model // 2) 相乘,最后结果 shape (max_len, d_model // 2)</span></span><br><span class=\"line\"> <span class=\"comment\"># 即所有行,一半的列。(行为句子的长度,列为向量的维度)</span></span><br><span class=\"line\"> boy = position * div_term</span><br><span class=\"line\"> <span class=\"comment\"># 偶数列 奇数列 分别赋值</span></span><br><span class=\"line\"> pe[:, <span class=\"number\">0</span>::<span class=\"number\">2</span>] = torch.sin(boy)</span><br><span class=\"line\"> pe[:, <span class=\"number\">1</span>::<span class=\"number\">2</span>] = torch.cos(boy)</span><br><span class=\"line\"> <span class=\"comment\"># 为何还要在前面加一维???</span></span><br><span class=\"line\"> pe = pe.unsqueeze(<span class=\"number\">0</span>)</span><br><span class=\"line\"> <span class=\"comment\"># Parameter 会在反向传播时更新</span></span><br><span class=\"line\"> <span class=\"comment\"># Buffer不会,是固定的,本文就是不想让其被修改</span></span><br><span class=\"line\"> self.register_buffer(<span class=\"string\">'pe'</span>, pe)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"comment\"># x能和后面的相加说明shape是一致的,也是 (1, sentence_len, d_model)</span></span><br><span class=\"line\"> <span class=\"comment\"># forward 其实就是,将原来的Embedding 再加上 Positional Embedding</span></span><br><span class=\"line\"> x += Variable(self.pe[:, :x.size(<span class=\"number\">1</span>)], requires_grad=<span class=\"literal\">False</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.dropout(x)</span><br></pre></td></tr></table></figure>\n<h2 id=\"Encoder\"><a href=\"#Encoder\" class=\"headerlink\" title=\"Encoder\"></a>Encoder</h2><p>Encoder代码结构</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Encoder</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""核心Encoder是由N层堆叠而成"""</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, layer, N</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Encoder, self).__init__()</span><br><span class=\"line\"> self.layers = clones(layer, N)</span><br><span class=\"line\"> self.norm = LayerNorm(layer.size)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, mask</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> 将输入x和掩码mask逐层传递下去,最后再 LayerNorm 一下。</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> layer <span class=\"keyword\">in</span> self.layers:</span><br><span class=\"line\"> x = layer(x, mask)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.norm(x)</span><br></pre></td></tr></table></figure>\n<p>Encoder块主要就是利用Clone函数重复构建相同结构的Encoder_layer.</p>\n<h3 id=\"Clone\"><a href=\"#Clone\" class=\"headerlink\" title=\"Clone\"></a>Clone</h3><p>整个Encoder部分由N个Encoder_layer堆积二乘,为了复刻每个层的结构,构建克隆函数。(Decoder类似)</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">clones</span>(<span class=\"params\">module, N</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \tcopyN层形成一个list</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> nn.ModuleList([copy.deepcopy(module) <span class=\"keyword\">for</span> _ <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(N)])</span><br></pre></td></tr></table></figure>\n<h3 id=\"Encoder-layer\"><a href=\"#Encoder-layer\" class=\"headerlink\" title=\"Encoder_layer\"></a>Encoder_layer</h3><p>接下来,我们在将独立的Encoder_layer进行拆分,看看里面到底是什么东西。</p>\n<p><img src=\"F:\\video\\encoderlayer.png\" alt=\"encoderlayer\"></p>\n<p>上图很清晰的展示Encoder_layer内部的结构,X1,X2是经过转化的词向量,经过Positional Emcoding进入Encider_layer,喝口水,这部分要讲的有点多……</p>\n<p>首先,Encoder_layer分为上下两层,第一层包含self-attention+SublayerConnection,第二层为FNN+SublayerConnection。Attention的内容我后面再说,这里先讲下什么是SublayerConnection。attention的内容我后面再说,这里先讲下什么是SublayerConnection。</p>\n<p><strong>SublayerConnection</strong></p>\n<p>SublayerConnection内部设计基于两个核心:</p>\n<ol>\n<li>Residual_Connection(残差连接),这是上图的Add</li>\n<li>Layer Normalize(层级归一化),这是上图的Normalize</li>\n</ol>\n<p>如下图所示,残差连接,就是在原图正常的结构下,将X进行保留,最终得出的结果是X+F(x).</p>\n<p><strong>优势</strong>:反向传播过程中,对X求导会多出一个常数1,梯度连乘,不会出现梯度消失(详细的内容等我以后探究一下)</p>\n<p><img src=\"F:\\video\\Residual connection.png\" alt=\"Residual connection\"></p>\n<p>具体代码如下:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">SublayerConnection</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\">\t\tA residual connection followed by a layer norm.Note for code simplicity the norms is first as opposed to last.</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, size, dropout</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(SublayerConnection, self).__init__()</span><br><span class=\"line\"> self.norm = LayerNorm(size)</span><br><span class=\"line\"> self.dropout = nn.Dropout(dropout)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, sublayer</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> x + self.dropout(sublayer(self.norm(x)))</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>Layer Normalize</strong></p>\n<p>归一化最简单的理解就是将输入转化为均值为0,方差为1的数据,而由于NLP中Sequence长短不一,LN相较于BN在RNN这种类似的结构效果会好很多。其具体的概括就是:对每一个输入样本进行归一化操作,保证该样本在多维度符合均值0,方差为1.</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">LayerNorm</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""构建一个 layernorm层,具体细节看论文 https://arxiv.org/abs/1607.06450</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, features, eps=<span class=\"number\">1e-6</span></span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(LayerNorm, self).__init__()</span><br><span class=\"line\"> self.a_2 = nn.Parameter(torch.ones(features))</span><br><span class=\"line\"> self.b_2 = nn.Parameter(torch.zeros(features))</span><br><span class=\"line\"> self.eps = eps</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> mean = x.mean(-<span class=\"number\">1</span>, keepdim=<span class=\"literal\">True</span>)</span><br><span class=\"line\"> std = x.std(-<span class=\"number\">1</span>, keepdim=<span class=\"literal\">True</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.a_2 * (x - mean) / (std + self.eps) + self.b_2</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>FNN</strong></p>\n<p>前馈神经网络,这就不细说了,具体代码如下:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">PositionwiseFeedForward</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, d_ff, dropout=<span class=\"number\">0.1</span></span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(PositionwiseFeedForward, self).__init__()</span><br><span class=\"line\"> self.w_1 = nn.Linear(d_model, d_ff)</span><br><span class=\"line\"> self.w_2 = nn.Linear(d_ff, d_model)</span><br><span class=\"line\"> self.dropout = nn.Dropout(dropout)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.w_2(self.dropout(F.relu(self.w_1(x))))</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>放张图,清晰一点</p>\n<p><img src=\"F:\\video\\FNN.png\" alt=\"FNN\"></p>\n<h2 id=\"Decoder\"><a href=\"#Decoder\" class=\"headerlink\" title=\"Decoder\"></a>Decoder</h2><p>与Encoder结构类似,其由N个Decoder_layer组成(Transformer中N=6),相较于Encoder,其接受的参数多了Encoder生成的memory以及目标句子中的掩码gt_mask.</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Decoder</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\">\t<span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\">\t\tGener is N layer decoder with masking</span></span><br><span class=\"line\"><span class=\"string\">\t"""</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, layer, N</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Decoder, self).__init__()</span><br><span class=\"line\"> self.layers = clones(layer, N)</span><br><span class=\"line\"> self.norm = LayerNorm(layer.size)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, memory, src_mask, tgt_mask</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> layer <span class=\"keyword\">in</span> self.layers:</span><br><span class=\"line\"> x = layer(x, memory, src_mask, tgt_mask)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.norm(x)</span><br></pre></td></tr></table></figure>\n<h3 id=\"Decoder-layer\"><a href=\"#Decoder-layer\" class=\"headerlink\" title=\"Decoder layer\"></a>Decoder layer</h3><p>下图很好的表明了Decoder_layer的区别</p>\n<p><img src=\"F:\\video\\Decoder.png\" alt=\"Decoder\"></p>\n<p>简单的解释一下,每一个Decoder_layer有三层组成</p>\n<p>第一层:Self-Attention+SublayerConnection</p>\n<p>第二层:Encoder-Decoder Attention+SublayerConnection(Decoder独有)</p>\n<p>第三层:FFN+SublayerConnection</p>\n<p>具体代码如下:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">DecoderLayer</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, size, self_attn, src_attn, feed_forward, dropout</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(DecoderLayer, self).__init__()</span><br><span class=\"line\"> self.size = size</span><br><span class=\"line\"> self.self_attn = self_attn</span><br><span class=\"line\"> self.src_attn = src_attn</span><br><span class=\"line\"> self.feed_forward = feed_forward</span><br><span class=\"line\"> self.sublayer = clones(SublayerConnection(size, dropout), <span class=\"number\">3</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, memory, src_mask, tgt_mask</span>):</span></span><br><span class=\"line\"> m = memory</span><br><span class=\"line\"> x = self.sublayer[<span class=\"number\">0</span>](x, <span class=\"keyword\">lambda</span> x: self.self_attn(x, x, x, tgt_mask))</span><br><span class=\"line\"> x = self.sublayer[<span class=\"number\">1</span>](x, <span class=\"keyword\">lambda</span> x: self.src_attn(x, m, m, src_mask))</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.sublayer[<span class=\"number\">2</span>](x, self.feed_forward)</span><br></pre></td></tr></table></figure>\n<h3 id=\"Mask\"><a href=\"#Mask\" class=\"headerlink\" title=\"Mask\"></a>Mask</h3><p><strong>作用:</strong>Mask简单来说就是掩码的意思,在我们这里的意思大概就是对某些值进行掩盖,不使其产生效果。</p>\n<p>文中的Mask主要包括<strong>两种</strong>:</p>\n<ul>\n<li>src_mask(padding_mask):由于Sequence长短不一,利用Padding对其填充为0,然后在对其进行attention的过程中,这些位置没有意义的,src_mask的作用就是不将Attention机制放在这些位置上进行处理,这就是padding_mask,其基于作用于所有attention。</li>\n<li>tgt_mask(sequence_mask):对于机器翻译而言,采用监督学习,而原句子和目标句子对输入模型进行训练,那就需要确保,Decoder在生成单词的过程中,不能看到后面的单词,这就是sequence_mask,其主要在Decoder中的self-attention中起作用。</li>\n</ul>\n<p>具体代码:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">subsequent_mask</span>(<span class=\"params\">size</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \tMask out subequent position</span></span><br><span class=\"line\"><span class=\"string\"> \t这个是tgt_mask,他不让decoder过程中看到后面的单词,训练模型的过程中掩盖翻译后面的单词。</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(subsequent_mask(<span class=\"number\">3</span>))</span><br><span class=\"line\"> tensor([[[<span class=\"number\">1</span>, <span class=\"number\">0</span>, <span class=\"number\">0</span>],</span><br><span class=\"line\"> [<span class=\"number\">1</span>, <span class=\"number\">1</span>, <span class=\"number\">0</span>],</span><br><span class=\"line\"> [<span class=\"number\">1</span>, <span class=\"number\">1</span>, <span class=\"number\">1</span>]]], dtype=torch.uint8)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> attn_shape = (1, size, size)</span></span><br><span class=\"line\"><span class=\"string\"> subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')</span></span><br><span class=\"line\"><span class=\"string\"> return torch.from_numpy(subsequent_mask) == 0</span></span><br></pre></td></tr></table></figure>\n<h3 id=\"Attention-1\"><a href=\"#Attention-1\" class=\"headerlink\" title=\"Attention\"></a>Attention</h3><p>Transfomer的最大创新就是两种Attention,Self-Attention,Multi-Head Attention</p>\n<p>Self-Attention</p>\n<p>作用:简单来说,就是计算句子里每个单词受其他单词的影响程度,其最大意义在于可以学到语义依赖关系。</p>\n<p>计算公式:</p>\n<p><img src=\"C:\\Users\\Administrator\\AppData\\Roaming\\Typora\\typora-user-images\\1588909761573.png\" alt=\"1588909761573\"></p>\n<p>不好理解,上图</p>\n<p><img src=\"F:\\video\\softmax.png\" alt=\"softmax\"></p>\n<p>再看看原论文的图</p>\n<p><img src=\"F:\\video\\scaled dot attention.png\" alt=\"scaled dot attention\"></p>\n<p>再来看看代码:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">attention</span>(<span class=\"params\">query, key, value, mask=<span class=\"literal\">None</span>, dropout=<span class=\"literal\">None</span></span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""Compute 'Scaled Dot Product Attention"""</span></span><br><span class=\"line\"> d_k = query.size(-<span class=\"number\">1</span>)</span><br><span class=\"line\"> scores = torch.matmul(query, key.transpose(-<span class=\"number\">2</span>, -<span class=\"number\">1</span>)) / math.sqrt(d_k)</span><br><span class=\"line\"> <span class=\"keyword\">if</span> mask:</span><br><span class=\"line\"> scores = scores.masked_fill(mask == <span class=\"number\">0</span>, -<span class=\"number\">1e9</span>)</span><br><span class=\"line\"> p_attn = F.softmax(scores, dim=-<span class=\"number\">1</span>)</span><br><span class=\"line\"> <span class=\"keyword\">if</span> dropout:</span><br><span class=\"line\"> p_attn = dropout(p_attn)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> torch.matmul(p_attn, value)</span><br></pre></td></tr></table></figure>\n<p>基本参照看公式应该可以很好的看懂这些代码,具体的步骤各位就参考原论文吧。</p>\n<p><strong>Multi-Head Attention</strong></p>\n<p>多个Self-Attention并行计算就叫多头计算(Multi-Head Attention)模式,也就是我们俗称的多头怪,你可以将其理解为集成学习,这也是Transformer训练速度超快的原因。</p>\n<p>废话不说,上图:</p>\n<p><img src=\"F:\\video\\Multi-head.png\" alt=\"Multi-head\"></p>\n<p>首先,我们接进来单词转化为词向量X(n,512)和W^Q, W^k, W^v(512,64)相乘,得出Q,K,V(n,64),就生产一个Z(n,64),然后利用8个头并行操作,就生产8个Z(n,64).</p>\n<p><img src=\"F:\\video\\multi-head1.png\" alt=\"multi-head1\"></p>\n<p>然后将8个Z拼接起来,Z就变成了(n,512)内积W^0(512,512),输出最终的Z,传递到下一个FNN层。</p>\n<p><img src=\"F:\\video\\Multi-Head 结构层.png\" alt=\"Multi-Head 结构层\"></p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">MultiHeadedAttention</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, h, d_model, dropout=<span class=\"number\">0.1</span></span>):</span></span><br><span class=\"line\"> <span class=\"string\">"Take in model size and number of heads."</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(MultiHeadedAttention, self).__init__()</span><br><span class=\"line\"> <span class=\"keyword\">assert</span> d_model % h == <span class=\"number\">0</span></span><br><span class=\"line\"> <span class=\"comment\"># We assume d_v always equals d_k</span></span><br><span class=\"line\"> self.d_k = d_model // h</span><br><span class=\"line\"> self.h = h</span><br><span class=\"line\"> self.linears = clones(nn.Linear(d_model, d_model), <span class=\"number\">4</span>)</span><br><span class=\"line\"> self.attn = <span class=\"literal\">None</span></span><br><span class=\"line\"> self.dropout = nn.Dropout(p=dropout)</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, query, key, value, mask=<span class=\"literal\">None</span></span>):</span></span><br><span class=\"line\"> <span class=\"string\">"Implements Figure 2"</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> mask <span class=\"keyword\">is</span> <span class=\"keyword\">not</span> <span class=\"literal\">None</span>:</span><br><span class=\"line\"> <span class=\"comment\"># Same mask applied to all h heads.</span></span><br><span class=\"line\"> mask = mask.unsqueeze(<span class=\"number\">1</span>)</span><br><span class=\"line\"> nbatches = query.size(<span class=\"number\">0</span>)</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\"># 1) Do all the linear projections in batch from d_model => h x d_k </span></span><br><span class=\"line\"> query, key, value = \\</span><br><span class=\"line\"> [l(x).view(nbatches, -<span class=\"number\">1</span>, self.h, self.d_k).transpose(<span class=\"number\">1</span>, <span class=\"number\">2</span>)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> l, x <span class=\"keyword\">in</span> <span class=\"built_in\">zip</span>(self.linears, (query, key, value))]</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\"># 2) Apply attention on all the projected vectors in batch. </span></span><br><span class=\"line\"> x, self.attn = attention(query, key, value, mask=mask, </span><br><span class=\"line\"> dropout=self.dropout)</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\"># 3) "Concat" using a view and apply a final linear. </span></span><br><span class=\"line\"> x = x.transpose(<span class=\"number\">1</span>, <span class=\"number\">2</span>).contiguous() \\</span><br><span class=\"line\"> .view(nbatches, -<span class=\"number\">1</span>, self.h * self.d_k)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.linears[-<span class=\"number\">1</span>](x)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h2 id=\"Generator\"><a href=\"#Generator\" class=\"headerlink\" title=\"Generator\"></a>Generator</h2><p>就是数据经过Encoder-Decoder结构后还需要一个线性连接层和softmax层,这一部分预测层,命名为Generator。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Generator</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \t定义输出结构,一个线性连接层+softmax层</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, vocab</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Generator, self).__init__()</span><br><span class=\"line\"> self.proj = nn.Linear(d_model, vocab)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> F.log_softmax(self.proj(x), dim=-<span class=\"number\">1</span>)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>好了,这基本上把整个Transfomer进行了一个非常细致的拆分。Transformer可以说现在所有最新模型的基础,对于这一部分还是需要好好理解。</p>\n<h1 id=\"参考链接\"><a href=\"#参考链接\" class=\"headerlink\" title=\"参考链接\"></a>参考链接</h1><ul>\n<li>[1] <a href=\"https://arxiv.org/pdf/1706.03762.pdf\">原论文</a></li>\n<li>[2] <a href=\"https://jalammar.github.io/illustrated-transformer/\">NLP国外大牛(Jay Alammar)详解Transformer</a></li>\n<li>[3] <a href=\"https://nlp.seas.harvard.edu/2018/04/03/attention.html#attention\">哈佛NLP</a></li>\n<li>[4] <a href=\"https://juejin.im/post/5b9f1af0e51d450e425eb32d#heading-10\">Blog</a></li>\n<li>[5] <a href=\"https://blog.csdn.net/baidu_20163013/article/details/97389827\">Csdn</a></li>\n</ul>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h1 id=\"Transfomer拆分\"><a href=\"#Transfomer拆分\" class=\"headerlink\" title=\"Transfomer拆分\"></a>Transfomer拆分</h1><p>为了更好的学习当前NLP主流模型,如Bert,GPT2及Bert一系列的衍生物,Transfomer是这一系列的基础。因此本文的主要目的是记录个人基于一些博客和原论文对Transfomer模型进行拆分的结果。</p>\n<p><strong>目的</strong>:减少计算量并提高并行效率,同时不减弱最终的实验结果。</p>\n<p><strong>创新点</strong>:</p>\n<ol>\n<li>Self-attention</li>\n<li>Multi-Head Attention</li>\n</ol>\n<h1 id=\"背景知识\"><a href=\"#背景知识\" class=\"headerlink\" title=\"背景知识\"></a>背景知识</h1><h2 id=\"seq2seq\"><a href=\"#seq2seq\" class=\"headerlink\" title=\"seq2seq\"></a>seq2seq</h2><p>定义:seq2seq模型是采用一系列项目(单词、字母、图像特征等)并输出另一个项目序列的模型。在机器翻译中,序列是一系列单词,经过seq2seq后,输出同样是一系列单词。</p>\n<video src=\"F:\\video\\seq2seq_2.mp4\"></video>\n\n<p>接下来我们掀开这个model内部,该模型主要由一个Encoder和一个Decoder组成。</p>\n<ul>\n<li>Encoder:处理输入序列的每个项目,捕捉载体中的信息(context)。</li>\n<li>Decoder:处理完整序列后,Encoder将信息(context)传递至Decoder,并且开始逐项生产输出序列。</li>\n</ul>\n<video src=\"F:\\video\\seq2seq_4.mp4\"></video>\n\n<p>而context是一个向量,其大小基于等同于编码器中RNN的隐藏神经元。</p>\n<p>在单词输入之前,我们需要将单词转化为词向量,其可以通过word2vec等模型进行预训练训练词库,然后将单词按照词库的词向量简单提取即可,在上面的资历中,其处理过程如下:</p>\n<p><img src=\"F:\\video\\embedding.png\" alt=\"embedding\"></p>\n<p>这里简单将其设为维度4,通常设为200或300,在此,简单展示一下RNN的实现原理。</p>\n<video src=\"F:\\video\\RNN_1.mp4\"></video>\n\n<p>利用先前的输入的隐藏状态,RNN将其输出至一个新的隐藏状态,接下来我们看看seq2seq中的隐藏状态是怎么进行的。</p>\n<video src=\"F:\\video\\seq2seq_5.mp4\"></video>\n\n<p>Encoder中最后一个hidden-state实际就是前文提到的context,接下来我们进一步拆解,展示其具体细节。</p>\n<video src=\"F:\\video\\seq2seq_6.mp4\"></video>\n\n<p>总结:由于在Encoder阶段,每个单词输入都会产生一个新的hidden_state,最后输出一个context给Decoder进行解码。因此,当文本内容过长时,容易丢失部分信息,为了解决这个问题,Attention应运而生。</p>\n<h2 id=\"Attention\"><a href=\"#Attention\" class=\"headerlink\" title=\"Attention\"></a>Attention</h2><p>Attention这个概念最早出现在《Neural machine traslation by jointly learning to align and translate》论文中,其后《Neural image caption generation with visual attention》对attention形式进行总结。</p>\n<p>定义:为了解决文本过长信息丢失的问题,相较于seq2seq模型,attention最大的区别就是他不在要求把所以信息都编入最后的隐藏状态中,而是可以在编码过程中,对每一个隐藏状态进行保留,最后在解码的过程中,每一步都会选择性的从编码的隐藏状态中选一个和当前状态最接近的子集进行处理,这样在产生每一个输出时就能够充分利用输入序列携程的信息,下面很好的展示了attention在seq2seq模型中运用。</p>\n<video src=\"F:\\video\\seq2seq_7.mp4\"></video>\n\n<p>接下来,我们放大一下decoder对于attention后隐藏状态的具体使用,其在每一步解码均要进行。</p>\n<ol>\n<li>查看在attention机制中每个具体的隐藏状态,选出其与句子中的那个单词最相关。</li>\n<li>给每个隐藏状态打分。</li>\n<li>将每个隐藏状态进行softmax以放大具有高分数的隐藏状态。</li>\n<li>进行总和形成attention输入Decoder的向量。</li>\n</ol>\n<video src=\"F:\\video\\attention_process.mp4\"></video>\n\n<p>最后,将整个过程放一起,以便更好的理解attention机制(代码实现的时候进一步理解)。</p>\n<ol>\n<li>Decoder输入:初始化一个解码器隐藏状态+经过预训练后的词向量。</li>\n<li>将前两者输入到RNN中,产生一个新的隐藏状态h4和输出,将输出丢弃(seq2seq是直接将context送入下一个RNN作为输入)。</li>\n<li>attention:利用h4和encoder层简历的隐藏状态进行计算context(c4)。</li>\n<li>将c4和h4进行concatenate。</li>\n<li>将其结果通过前向神经网络,输出一个结果。</li>\n<li>该结果表示当前时间输出的对应文字。</li>\n<li>重复下一个步骤。</li>\n</ol>\n<video src=\"F:\\video\\attention_tensor_dance.mp4\"></video>\n\n<p>注意:该模型并不是将输入和输出的单词一一对应,他有可能一个单词对应两个单词甚至影响第三个单词,这是经过训练得到的。</p>\n<h1 id=\"正文\"><a href=\"#正文\" class=\"headerlink\" title=\"正文\"></a>正文</h1><p>总算要写到Transformer部分了,有点小激动,让我们一起来看看这个影响到现在的模型到底长啥样,为了便于理解,我这边会结合代码+论文进行讲解。</p>\n<p>首先,引入原论文的结构图</p>\n<p><img src=\"F:\\video\\结构图.png\" alt=\"结构图\"></p>\n<p>看不懂不要紧,这边引入代码的结构图</p>\n<p><img src=\"F:\\video\\代码结构图.png\" alt=\"代码结构图\"></p>\n<h2 id=\"Encoder-Decoder\"><a href=\"#Encoder-Decoder\" class=\"headerlink\" title=\"Encoder-Decoder\"></a>Encoder-Decoder</h2><p>从宏观角度来看,Transformer与Seq2Seq的结构相同,依然引入经典的Encoder-Decoder结构,只是其中的神经层已经不是以前的RNN和CNN,而是完全引入注意力机制来进行构建。</p>\n<p><img src=\"F:\\video\\stack.png\" alt=\"stack\"></p>\n<p>上图代码的复现结构</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">EncoderDecoder</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> 整体来说Transformer还是Encoder和Decoder结构,其中包括两个Embedding,一块Encoder,一块Decoder,一个输出层</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, encoder, decoder, src_embed, tgt_embed, generator</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(EncoderDecoder, self).__init__()</span><br><span class=\"line\"> self.encoder = encoder</span><br><span class=\"line\"> self.decoder = decoder</span><br><span class=\"line\"> self.src_embed = src_embed</span><br><span class=\"line\"> self.tgt_embed = tgt_embed</span><br><span class=\"line\"> self.generator = generator</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, src, tgt, src_mask, tgt_mask</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""Take in and process masked src and target sequences."""</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">encode</span>(<span class=\"params\">self, src, src_mask</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.encoder(self.src_embed(src), src_mask)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">decode</span>(<span class=\"params\">self, memory, src_mask, tgt, tgt_mask</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)</span><br></pre></td></tr></table></figure>\n<p>接下来,我将按照结构顺序一一介绍</p>\n<h2 id=\"Embedding\"><a href=\"#Embedding\" class=\"headerlink\" title=\"Embedding\"></a>Embedding</h2><p>这一层没什么好说的倒是,就是一个Embedding层。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Embeddings</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\">\t<span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\">\t\t将单词转化为词向量</span></span><br><span class=\"line\"><span class=\"string\">\t"""</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, vocab</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Embeddings, self).__init__()</span><br><span class=\"line\"> self.lut = nn.Embedding(vocab, d_model)</span><br><span class=\"line\"> self.d_model = d_model</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.lut(x) * math.sqrt(self.d_model)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>困惑</strong>:为什么要乘以sqrt(d_model),希望有大神给予指点!</p>\n<h2 id=\"Positional-Emcoding\"><a href=\"#Positional-Emcoding\" class=\"headerlink\" title=\"Positional Emcoding\"></a>Positional Emcoding</h2><p>由于Transfomer完全引入注意力机制,其不像CNN和RNN会对输入单词顺序自动打上标签,其无法输出每个单词的顺序,在机器翻译中可是爆炸的啊,举个例子,你输入一句:我欠你的一千万不用还了,他返回一句:你欠我的一千万不用还了,那不是血崩。</p>\n<p>为了解决这个问题,Transfomer在Encoder过程中为每个输入单词打上一个位置编码,然后在Decoder将位置变量信息添加,该方法不用模型训练,直接按规则进行添加。</p>\n<p>我们看看原论文里的图</p>\n<p><img src=\"F:\\video\\Emcodeing.png\" alt=\"Emcodeing\"></p>\n<p>其计算的具体公式在原论文3.5,</p>\n<p><img src=\"F:\\video\\Emcoding公式.png\" alt=\"Emcoding公式\"></p>\n<p>对于上述公式,代码中进行了对数转化,具体如下</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">PositionalEncoding</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \t位置变量公式详见https://arxiv.org/abs/1706.03762(3.5)</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, dropout, max_len=<span class=\"number\">5000</span></span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(PositionalEncoding, self).__init__()</span><br><span class=\"line\"> self.dropout = nn.Dropout(dropout)</span><br><span class=\"line\"> <span class=\"comment\"># pe 初始化为0,shape为 [n, d_model] 的矩阵,用来存放最终的PositionalEncoding的</span></span><br><span class=\"line\"> pe = torch.zeros(max_len, d_model)</span><br><span class=\"line\"> <span class=\"comment\"># position 表示位置,shape为 [max_len, 1],从0开始到 max_len</span></span><br><span class=\"line\"> position = torch.arange(<span class=\"number\">0.</span>, max_len).unsqueeze(<span class=\"number\">1</span>)</span><br><span class=\"line\"> <span class=\"comment\"># 这个是变形得到的,shape 为 [1, d_model//2]</span></span><br><span class=\"line\"> div_term = torch.exp(torch.arange(<span class=\"number\">0.</span>, d_model, <span class=\"number\">2</span>) * -(math.log(<span class=\"number\">10000.0</span>) / d_model))</span><br><span class=\"line\"> <span class=\"comment\"># 矩阵相乘 (max_len, 1) 与 (1, d_model // 2) 相乘,最后结果 shape (max_len, d_model // 2)</span></span><br><span class=\"line\"> <span class=\"comment\"># 即所有行,一半的列。(行为句子的长度,列为向量的维度)</span></span><br><span class=\"line\"> boy = position * div_term</span><br><span class=\"line\"> <span class=\"comment\"># 偶数列 奇数列 分别赋值</span></span><br><span class=\"line\"> pe[:, <span class=\"number\">0</span>::<span class=\"number\">2</span>] = torch.sin(boy)</span><br><span class=\"line\"> pe[:, <span class=\"number\">1</span>::<span class=\"number\">2</span>] = torch.cos(boy)</span><br><span class=\"line\"> <span class=\"comment\"># 为何还要在前面加一维???</span></span><br><span class=\"line\"> pe = pe.unsqueeze(<span class=\"number\">0</span>)</span><br><span class=\"line\"> <span class=\"comment\"># Parameter 会在反向传播时更新</span></span><br><span class=\"line\"> <span class=\"comment\"># Buffer不会,是固定的,本文就是不想让其被修改</span></span><br><span class=\"line\"> self.register_buffer(<span class=\"string\">'pe'</span>, pe)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"comment\"># x能和后面的相加说明shape是一致的,也是 (1, sentence_len, d_model)</span></span><br><span class=\"line\"> <span class=\"comment\"># forward 其实就是,将原来的Embedding 再加上 Positional Embedding</span></span><br><span class=\"line\"> x += Variable(self.pe[:, :x.size(<span class=\"number\">1</span>)], requires_grad=<span class=\"literal\">False</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.dropout(x)</span><br></pre></td></tr></table></figure>\n<h2 id=\"Encoder\"><a href=\"#Encoder\" class=\"headerlink\" title=\"Encoder\"></a>Encoder</h2><p>Encoder代码结构</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Encoder</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""核心Encoder是由N层堆叠而成"""</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, layer, N</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Encoder, self).__init__()</span><br><span class=\"line\"> self.layers = clones(layer, N)</span><br><span class=\"line\"> self.norm = LayerNorm(layer.size)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, mask</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> 将输入x和掩码mask逐层传递下去,最后再 LayerNorm 一下。</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> layer <span class=\"keyword\">in</span> self.layers:</span><br><span class=\"line\"> x = layer(x, mask)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.norm(x)</span><br></pre></td></tr></table></figure>\n<p>Encoder块主要就是利用Clone函数重复构建相同结构的Encoder_layer.</p>\n<h3 id=\"Clone\"><a href=\"#Clone\" class=\"headerlink\" title=\"Clone\"></a>Clone</h3><p>整个Encoder部分由N个Encoder_layer堆积二乘,为了复刻每个层的结构,构建克隆函数。(Decoder类似)</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">clones</span>(<span class=\"params\">module, N</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \tcopyN层形成一个list</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> nn.ModuleList([copy.deepcopy(module) <span class=\"keyword\">for</span> _ <span class=\"keyword\">in</span> <span class=\"built_in\">range</span>(N)])</span><br></pre></td></tr></table></figure>\n<h3 id=\"Encoder-layer\"><a href=\"#Encoder-layer\" class=\"headerlink\" title=\"Encoder_layer\"></a>Encoder_layer</h3><p>接下来,我们在将独立的Encoder_layer进行拆分,看看里面到底是什么东西。</p>\n<p><img src=\"F:\\video\\encoderlayer.png\" alt=\"encoderlayer\"></p>\n<p>上图很清晰的展示Encoder_layer内部的结构,X1,X2是经过转化的词向量,经过Positional Emcoding进入Encider_layer,喝口水,这部分要讲的有点多……</p>\n<p>首先,Encoder_layer分为上下两层,第一层包含self-attention+SublayerConnection,第二层为FNN+SublayerConnection。Attention的内容我后面再说,这里先讲下什么是SublayerConnection。attention的内容我后面再说,这里先讲下什么是SublayerConnection。</p>\n<p><strong>SublayerConnection</strong></p>\n<p>SublayerConnection内部设计基于两个核心:</p>\n<ol>\n<li>Residual_Connection(残差连接),这是上图的Add</li>\n<li>Layer Normalize(层级归一化),这是上图的Normalize</li>\n</ol>\n<p>如下图所示,残差连接,就是在原图正常的结构下,将X进行保留,最终得出的结果是X+F(x).</p>\n<p><strong>优势</strong>:反向传播过程中,对X求导会多出一个常数1,梯度连乘,不会出现梯度消失(详细的内容等我以后探究一下)</p>\n<p><img src=\"F:\\video\\Residual connection.png\" alt=\"Residual connection\"></p>\n<p>具体代码如下:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">SublayerConnection</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\">\t\tA residual connection followed by a layer norm.Note for code simplicity the norms is first as opposed to last.</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, size, dropout</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(SublayerConnection, self).__init__()</span><br><span class=\"line\"> self.norm = LayerNorm(size)</span><br><span class=\"line\"> self.dropout = nn.Dropout(dropout)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, sublayer</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> x + self.dropout(sublayer(self.norm(x)))</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>Layer Normalize</strong></p>\n<p>归一化最简单的理解就是将输入转化为均值为0,方差为1的数据,而由于NLP中Sequence长短不一,LN相较于BN在RNN这种类似的结构效果会好很多。其具体的概括就是:对每一个输入样本进行归一化操作,保证该样本在多维度符合均值0,方差为1.</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">LayerNorm</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""构建一个 layernorm层,具体细节看论文 https://arxiv.org/abs/1607.06450</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, features, eps=<span class=\"number\">1e-6</span></span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(LayerNorm, self).__init__()</span><br><span class=\"line\"> self.a_2 = nn.Parameter(torch.ones(features))</span><br><span class=\"line\"> self.b_2 = nn.Parameter(torch.zeros(features))</span><br><span class=\"line\"> self.eps = eps</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> mean = x.mean(-<span class=\"number\">1</span>, keepdim=<span class=\"literal\">True</span>)</span><br><span class=\"line\"> std = x.std(-<span class=\"number\">1</span>, keepdim=<span class=\"literal\">True</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.a_2 * (x - mean) / (std + self.eps) + self.b_2</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>FNN</strong></p>\n<p>前馈神经网络,这就不细说了,具体代码如下:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">PositionwiseFeedForward</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, d_ff, dropout=<span class=\"number\">0.1</span></span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(PositionwiseFeedForward, self).__init__()</span><br><span class=\"line\"> self.w_1 = nn.Linear(d_model, d_ff)</span><br><span class=\"line\"> self.w_2 = nn.Linear(d_ff, d_model)</span><br><span class=\"line\"> self.dropout = nn.Dropout(dropout)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.w_2(self.dropout(F.relu(self.w_1(x))))</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>放张图,清晰一点</p>\n<p><img src=\"F:\\video\\FNN.png\" alt=\"FNN\"></p>\n<h2 id=\"Decoder\"><a href=\"#Decoder\" class=\"headerlink\" title=\"Decoder\"></a>Decoder</h2><p>与Encoder结构类似,其由N个Decoder_layer组成(Transformer中N=6),相较于Encoder,其接受的参数多了Encoder生成的memory以及目标句子中的掩码gt_mask.</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Decoder</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\">\t<span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\">\t\tGener is N layer decoder with masking</span></span><br><span class=\"line\"><span class=\"string\">\t"""</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, layer, N</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Decoder, self).__init__()</span><br><span class=\"line\"> self.layers = clones(layer, N)</span><br><span class=\"line\"> self.norm = LayerNorm(layer.size)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, memory, src_mask, tgt_mask</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> layer <span class=\"keyword\">in</span> self.layers:</span><br><span class=\"line\"> x = layer(x, memory, src_mask, tgt_mask)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.norm(x)</span><br></pre></td></tr></table></figure>\n<h3 id=\"Decoder-layer\"><a href=\"#Decoder-layer\" class=\"headerlink\" title=\"Decoder layer\"></a>Decoder layer</h3><p>下图很好的表明了Decoder_layer的区别</p>\n<p><img src=\"F:\\video\\Decoder.png\" alt=\"Decoder\"></p>\n<p>简单的解释一下,每一个Decoder_layer有三层组成</p>\n<p>第一层:Self-Attention+SublayerConnection</p>\n<p>第二层:Encoder-Decoder Attention+SublayerConnection(Decoder独有)</p>\n<p>第三层:FFN+SublayerConnection</p>\n<p>具体代码如下:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">DecoderLayer</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, size, self_attn, src_attn, feed_forward, dropout</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(DecoderLayer, self).__init__()</span><br><span class=\"line\"> self.size = size</span><br><span class=\"line\"> self.self_attn = self_attn</span><br><span class=\"line\"> self.src_attn = src_attn</span><br><span class=\"line\"> self.feed_forward = feed_forward</span><br><span class=\"line\"> self.sublayer = clones(SublayerConnection(size, dropout), <span class=\"number\">3</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x, memory, src_mask, tgt_mask</span>):</span></span><br><span class=\"line\"> m = memory</span><br><span class=\"line\"> x = self.sublayer[<span class=\"number\">0</span>](x, <span class=\"keyword\">lambda</span> x: self.self_attn(x, x, x, tgt_mask))</span><br><span class=\"line\"> x = self.sublayer[<span class=\"number\">1</span>](x, <span class=\"keyword\">lambda</span> x: self.src_attn(x, m, m, src_mask))</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.sublayer[<span class=\"number\">2</span>](x, self.feed_forward)</span><br></pre></td></tr></table></figure>\n<h3 id=\"Mask\"><a href=\"#Mask\" class=\"headerlink\" title=\"Mask\"></a>Mask</h3><p><strong>作用:</strong>Mask简单来说就是掩码的意思,在我们这里的意思大概就是对某些值进行掩盖,不使其产生效果。</p>\n<p>文中的Mask主要包括<strong>两种</strong>:</p>\n<ul>\n<li>src_mask(padding_mask):由于Sequence长短不一,利用Padding对其填充为0,然后在对其进行attention的过程中,这些位置没有意义的,src_mask的作用就是不将Attention机制放在这些位置上进行处理,这就是padding_mask,其基于作用于所有attention。</li>\n<li>tgt_mask(sequence_mask):对于机器翻译而言,采用监督学习,而原句子和目标句子对输入模型进行训练,那就需要确保,Decoder在生成单词的过程中,不能看到后面的单词,这就是sequence_mask,其主要在Decoder中的self-attention中起作用。</li>\n</ul>\n<p>具体代码:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">subsequent_mask</span>(<span class=\"params\">size</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \tMask out subequent position</span></span><br><span class=\"line\"><span class=\"string\"> \t这个是tgt_mask,他不让decoder过程中看到后面的单词,训练模型的过程中掩盖翻译后面的单词。</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"built_in\">print</span>(subsequent_mask(<span class=\"number\">3</span>))</span><br><span class=\"line\"> tensor([[[<span class=\"number\">1</span>, <span class=\"number\">0</span>, <span class=\"number\">0</span>],</span><br><span class=\"line\"> [<span class=\"number\">1</span>, <span class=\"number\">1</span>, <span class=\"number\">0</span>],</span><br><span class=\"line\"> [<span class=\"number\">1</span>, <span class=\"number\">1</span>, <span class=\"number\">1</span>]]], dtype=torch.uint8)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> attn_shape = (1, size, size)</span></span><br><span class=\"line\"><span class=\"string\"> subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')</span></span><br><span class=\"line\"><span class=\"string\"> return torch.from_numpy(subsequent_mask) == 0</span></span><br></pre></td></tr></table></figure>\n<h3 id=\"Attention-1\"><a href=\"#Attention-1\" class=\"headerlink\" title=\"Attention\"></a>Attention</h3><p>Transfomer的最大创新就是两种Attention,Self-Attention,Multi-Head Attention</p>\n<p>Self-Attention</p>\n<p>作用:简单来说,就是计算句子里每个单词受其他单词的影响程度,其最大意义在于可以学到语义依赖关系。</p>\n<p>计算公式:</p>\n<p><img src=\"C:\\Users\\Administrator\\AppData\\Roaming\\Typora\\typora-user-images\\1588909761573.png\" alt=\"1588909761573\"></p>\n<p>不好理解,上图</p>\n<p><img src=\"F:\\video\\softmax.png\" alt=\"softmax\"></p>\n<p>再看看原论文的图</p>\n<p><img src=\"F:\\video\\scaled dot attention.png\" alt=\"scaled dot attention\"></p>\n<p>再来看看代码:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">attention</span>(<span class=\"params\">query, key, value, mask=<span class=\"literal\">None</span>, dropout=<span class=\"literal\">None</span></span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""Compute 'Scaled Dot Product Attention"""</span></span><br><span class=\"line\"> d_k = query.size(-<span class=\"number\">1</span>)</span><br><span class=\"line\"> scores = torch.matmul(query, key.transpose(-<span class=\"number\">2</span>, -<span class=\"number\">1</span>)) / math.sqrt(d_k)</span><br><span class=\"line\"> <span class=\"keyword\">if</span> mask:</span><br><span class=\"line\"> scores = scores.masked_fill(mask == <span class=\"number\">0</span>, -<span class=\"number\">1e9</span>)</span><br><span class=\"line\"> p_attn = F.softmax(scores, dim=-<span class=\"number\">1</span>)</span><br><span class=\"line\"> <span class=\"keyword\">if</span> dropout:</span><br><span class=\"line\"> p_attn = dropout(p_attn)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> torch.matmul(p_attn, value)</span><br></pre></td></tr></table></figure>\n<p>基本参照看公式应该可以很好的看懂这些代码,具体的步骤各位就参考原论文吧。</p>\n<p><strong>Multi-Head Attention</strong></p>\n<p>多个Self-Attention并行计算就叫多头计算(Multi-Head Attention)模式,也就是我们俗称的多头怪,你可以将其理解为集成学习,这也是Transformer训练速度超快的原因。</p>\n<p>废话不说,上图:</p>\n<p><img src=\"F:\\video\\Multi-head.png\" alt=\"Multi-head\"></p>\n<p>首先,我们接进来单词转化为词向量X(n,512)和W^Q, W^k, W^v(512,64)相乘,得出Q,K,V(n,64),就生产一个Z(n,64),然后利用8个头并行操作,就生产8个Z(n,64).</p>\n<p><img src=\"F:\\video\\multi-head1.png\" alt=\"multi-head1\"></p>\n<p>然后将8个Z拼接起来,Z就变成了(n,512)内积W^0(512,512),输出最终的Z,传递到下一个FNN层。</p>\n<p><img src=\"F:\\video\\Multi-Head 结构层.png\" alt=\"Multi-Head 结构层\"></p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">MultiHeadedAttention</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, h, d_model, dropout=<span class=\"number\">0.1</span></span>):</span></span><br><span class=\"line\"> <span class=\"string\">"Take in model size and number of heads."</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(MultiHeadedAttention, self).__init__()</span><br><span class=\"line\"> <span class=\"keyword\">assert</span> d_model % h == <span class=\"number\">0</span></span><br><span class=\"line\"> <span class=\"comment\"># We assume d_v always equals d_k</span></span><br><span class=\"line\"> self.d_k = d_model // h</span><br><span class=\"line\"> self.h = h</span><br><span class=\"line\"> self.linears = clones(nn.Linear(d_model, d_model), <span class=\"number\">4</span>)</span><br><span class=\"line\"> self.attn = <span class=\"literal\">None</span></span><br><span class=\"line\"> self.dropout = nn.Dropout(p=dropout)</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, query, key, value, mask=<span class=\"literal\">None</span></span>):</span></span><br><span class=\"line\"> <span class=\"string\">"Implements Figure 2"</span></span><br><span class=\"line\"> <span class=\"keyword\">if</span> mask <span class=\"keyword\">is</span> <span class=\"keyword\">not</span> <span class=\"literal\">None</span>:</span><br><span class=\"line\"> <span class=\"comment\"># Same mask applied to all h heads.</span></span><br><span class=\"line\"> mask = mask.unsqueeze(<span class=\"number\">1</span>)</span><br><span class=\"line\"> nbatches = query.size(<span class=\"number\">0</span>)</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\"># 1) Do all the linear projections in batch from d_model => h x d_k </span></span><br><span class=\"line\"> query, key, value = \\</span><br><span class=\"line\"> [l(x).view(nbatches, -<span class=\"number\">1</span>, self.h, self.d_k).transpose(<span class=\"number\">1</span>, <span class=\"number\">2</span>)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> l, x <span class=\"keyword\">in</span> <span class=\"built_in\">zip</span>(self.linears, (query, key, value))]</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\"># 2) Apply attention on all the projected vectors in batch. </span></span><br><span class=\"line\"> x, self.attn = attention(query, key, value, mask=mask, </span><br><span class=\"line\"> dropout=self.dropout)</span><br><span class=\"line\"> </span><br><span class=\"line\"> <span class=\"comment\"># 3) "Concat" using a view and apply a final linear. </span></span><br><span class=\"line\"> x = x.transpose(<span class=\"number\">1</span>, <span class=\"number\">2</span>).contiguous() \\</span><br><span class=\"line\"> .view(nbatches, -<span class=\"number\">1</span>, self.h * self.d_k)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> self.linears[-<span class=\"number\">1</span>](x)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<h2 id=\"Generator\"><a href=\"#Generator\" class=\"headerlink\" title=\"Generator\"></a>Generator</h2><p>就是数据经过Encoder-Decoder结构后还需要一个线性连接层和softmax层,这一部分预测层,命名为Generator。</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"class\"><span class=\"keyword\">class</span> <span class=\"title\">Generator</span>(<span class=\"params\">nn.Module</span>):</span></span><br><span class=\"line\"> <span class=\"string\">"""</span></span><br><span class=\"line\"><span class=\"string\"> \t定义输出结构,一个线性连接层+softmax层</span></span><br><span class=\"line\"><span class=\"string\"> """</span></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">__init__</span>(<span class=\"params\">self, d_model, vocab</span>):</span></span><br><span class=\"line\"> <span class=\"built_in\">super</span>(Generator, self).__init__()</span><br><span class=\"line\"> self.proj = nn.Linear(d_model, vocab)</span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">forward</span>(<span class=\"params\">self, x</span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> F.log_softmax(self.proj(x), dim=-<span class=\"number\">1</span>)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p>好了,这基本上把整个Transfomer进行了一个非常细致的拆分。Transformer可以说现在所有最新模型的基础,对于这一部分还是需要好好理解。</p>\n<h1 id=\"参考链接\"><a href=\"#参考链接\" class=\"headerlink\" title=\"参考链接\"></a>参考链接</h1><ul>\n<li>[1] <a href=\"https://arxiv.org/pdf/1706.03762.pdf\">原论文</a></li>\n<li>[2] <a href=\"https://jalammar.github.io/illustrated-transformer/\">NLP国外大牛(Jay Alammar)详解Transformer</a></li>\n<li>[3] <a href=\"https://nlp.seas.harvard.edu/2018/04/03/attention.html#attention\">哈佛NLP</a></li>\n<li>[4] <a href=\"https://juejin.im/post/5b9f1af0e51d450e425eb32d#heading-10\">Blog</a></li>\n<li>[5] <a href=\"https://blog.csdn.net/baidu_20163013/article/details/97389827\">Csdn</a></li>\n</ul>\n"},{"title":"对比学习_SimCSE","mathjax":true,"date":"2021-09-30T09:10:39.000Z","description":null,"_content":"# 对比学习之SimCSE\n\n> 前短时间接到一个title i2i召回数据的任务,最开始只是想用简单的Doc2vec输出每个title的emb然后用faiss做召回,但是效果很差。后来尝试了用Bert直接输出结果,效果也很差。后面看论文发现Bert的emb存在模型坍塌问题【详细可以参考美团的ConSERT任务】。最后利用SimCSE比较合理的解决了需求\n\n## 问题\n\n1. 什么是自监督学习,什么是对比学习?\n2. SimCSE生成监督数据的方法是什么?\n3. 为什么生成的句向量这么有用?\n4. 他的优劣势是什么?\n\n## 概念\n\n### 自监督学习\n\n首先,我们知道现在训练模型的方式主要是两种:有监督和无监督,并且我个人认为无监督的使用场景是远远大于有监督的。无监督有一个比较特别的分支是自监督,他的特点就是不需要人工标注的类别标签信息,直接利用数据本身作为监督信息,来学习样本数据的特征表达。\n\n自监督主要分为两类:\n\n- Generative Methods:其中的典型代表就是自编码器,其利用数据本身压缩再重构去比较生成的数据与原始数据的差距。\n- Contrastive Methods:这类方法则是通过将数据分别与正例样本和负例样本在特征空间进行对比,来学习样本的特征表示。\n\n**备注**:我个人任务word2vec也可以属于自监督范畴,不过不属于上面两类,他是利用文本词与词之间关系去学习每个单词的embedding\n\n### 对比学习\n\n上面说了,对比学习属于自监督学习的一个大类,他主要的难点就是如何生成相似的正样本。其最开始是在cv中使用的,后面NLPer也不甘示弱的进行研究,才有了今天NLP在对比学习领域的百花齐放,具体的进展可以看[张俊林博客](https://zhuanlan.zhihu.com/p/367290573)。\n\nSimCSE全称(Simple Contrastive Learning of Sentence Embeddings),是一种在没有监督训练数据的情况下训练句子向量的对比学习框架。并且从无监督和有监督两种方法给出了结果。\n\n- 无监督SimCSE:仅使用dropout进行数据增强操作。具体来说,将同一个样本输入预训练编码器两次(BERT),由于每次的dropout是不同的,那么就会生成两个经过dropout后的句向量表示。这里的dropout主要是指传统的feature dropout,在标准的Transformer实现中,每个sub-layer后都默认配置了dropout。除此之外,Transformer也在multi-head attention和feed-forward network的激活函数层添加了dropout。利用dropout将这两个样本作为“正样本对”,于此同时,同一个batch里面不同样本做\"负样本对\"\n- 有监督SimCSE:基于natural language infer\u0002ence (NLI) 数据集进行训练,具体来说,论文将entailment样本作为“正样本对”,并将contradiction样本作为hard“负样本对”,并且从实验证明该方法是有效的。\n\n## 指标详解\n\n在《**Understanding Contrastive Representation Learning through Alignment and Uniformity on the Hypersphere**》一文中,作者指出了现有的对比学习都遵循以下两大原则即:相似样本应该保持靠近,并且不同的样本应该分散嵌入在高维球体。于此同时SimCSE的作者也提到了这两点,他也认为这两点是衡量表示质量很重要的指标。\n\n### alignment\n\n用于衡量两个样本的相似程度,其计算公式如下:\n$$\nL=E\\left \\| f(x)-f(y)\\right \\|_{2 }^{\\alpha}\n$$\n同时作者给出了在pytorch中的代码\n\n```python\ndef lalign(x, y, alpha=2):\n return (x - y).norm(dim=1).pow(alpha).mean()\n```\n\n### uniformity\n\n衡量规整后的特征在unit 超球体上的分布的均匀性,其计算公式如下:\n$$\nL=log(E[e^{-t\\left \\| f(x)-f(y)\\right \\|^{2}}])\n$$\n同时作者给出了在pytorch中的代码\n\n```python\ndef lunif(x, t=2):\n sq_pdist = torch.pdist(x, p=2).pow(2)\n return sq_pdist.mul(-t).exp().mean().log()\n```\n\n两者结合,作者是介绍可以直接作为损失进行优化的:\n\n```python\nloss = lalign(x, y) + lam * (lunif(x) + lunif(y)) / 2\n```\n\n## 损失函数\n\n### unsup\n\n$$\nL=-log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau }}{\\sum_{j=1}^{N}e^{sim(h_{i},{h_{j}}^{+})/\\tau}}\n$$\n\n- $h_{i}$表示查询向量,+表示对比向量,j表示大小为N的batch下其他向量\n- $\\tau$表示温度参数,默认是0.07,[详解温度参数](https://zhuanlan.zhihu.com/p/357071960)\n- sim()表示余弦相似度,当然用内积也是一样的,因为l2标准化后的内积等价于余弦相似度\n\n### Sup\n\n$$\nL = -log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau}}{\\sum_{j=1}^{N}(e^{sim(h_{i},{h_{j}}^{+})/\\tau}+e^{sim(h_{i},{h_{j}}^{-})/\\tau})}\n$$\n\n- 其他和无监督方法没有区别,只是将负向量也纳入分母\n\n**备注**:其实他的目标就是为了让分子尽可能的小,分母尽可能的大,那分子其实就代表alignment指标,分母就是代表uniformity指标,其实简单看和交叉墒损失函数很像,并且在Sentence transfomer包中的实现也是这样子的。\n\n## 代码详解\n\n### SimCSE包\n\n```python\nfrom simcse import SimCSE\nmodel = SimCSE(\"princeton-nlp/sup-simcse-bert-base-uncased\")\nsentences = ['A woman is reading.', 'A man is playing a guitar.']\nmodel.build_index(sentences)\nresults = model.search(\"He plays guitar.\")\n```\n\n作者直接将训练好的模型放在huggingface上,并且下游利用Faiss进行向量搜索,详情可以直接看github代码,如果想自己训练也很简单,利用Sentence_transfomer包有现成的demo\n\n### Sentence_transfomer包\n\n**unsup**\n\n```python\nfrom torch.utils.data import DataLoader\nimport math\nfrom sentence_transformers import models, losses\nfrom sentence_transformers import LoggingHandler, SentenceTransformer, util, InputExample\nfrom sentence_transformers.evaluation import EmbeddingSimilarityEvaluator\nimport logging\nfrom torch import nn\nfrom datetime import datetime\nimport os\nimport gzip\nimport csv\n\nos.environ['CUDA_VISIBLE_DEVICES'] = '0,1'\nmodel_name = 'distilbert-base-uncased'\ntrain_batch_size = 128\nnum_epochs = 1\nmax_seq_length = 32\nmodel_save_path = 'output/training_stsb_simcse-{}-{}-{}'.format(model_name, train_batch_size,\n datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\"))\nsts_dataset_path = 'data/stsbenchmark.tsv.gz'\nif not os.path.exists(sts_dataset_path):\n util.http_get('https://sbert.net/datasets/stsbenchmark.tsv.gz', sts_dataset_path)\n\n# Here we define our SentenceTransformer model\nword_embedding_model = models.Transformer(model_name, max_seq_length=max_seq_length,\n cache_dir='../distilbert-base-uncased')\npooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())\ndense = models.Dense(pooling_model.pooling_output_dimension,512,activation_function=nn.ReLU()) # 降维操作\nmodel = SentenceTransformer(modules=[word_embedding_model, pooling_model,dense],device=\"cuda:1\")\nwikipedia_dataset_path = 'data/wiki1m_for_simcse.txt'\nif not os.path.exists(wikipedia_dataset_path):\n util.http_get(\n 'https://huggingface.co/datasets/princeton-nlp/datasets-for-simcse/resolve/main/wiki1m_for_simcse.txt',\n wikipedia_dataset_path)\nprint('read wiki data')\ntrain_samples = []\nwith open(wikipedia_dataset_path, 'r', encoding='utf8') as fIn:\n for line in fIn.readlines()[:10000]:\n line = line.strip()\n if len(line) >= 10:\n train_samples.append(InputExample(texts=[line, line]))\nprint(len(train_samples))\nprint('Read STSB dev dataset')\ndev_samples = []\ntest_samples = []\nwith gzip.open(sts_dataset_path, 'rt', encoding='utf8') as fIn:\n reader = csv.DictReader(fIn, delimiter='\\t', quoting=csv.QUOTE_NONE)\n for row in reader:\n score = float(row['score']) / 5.0 # Normalize score to range 0 ... 1\n\n if row['split'] == 'dev':\n dev_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n elif row['split'] == 'test':\n test_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n\ndev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,\n name='sts-dev')\ntest_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,\n name='sts-test')\n\ntrain_dataloader = DataLoader(train_samples, shuffle=True, batch_size=train_batch_size, drop_last=True)\ntrain_loss = losses.MultipleNegativesRankingLoss(model)\nwarmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1) # 10% of train data for warm-up\nevaluation_steps = int(len(train_dataloader) * 0.1) #Evaluate every 10% of the data\n\ndev_evaluator(model)\n\nprint('Start train model')\n\n# train the model\nmodel.fit(train_objectives=[(train_dataloader,train_loss)],\n evaluator=dev_evaluator,\n epochs=num_epochs,\n evaluation_steps=evaluation_steps,\n warmup_steps=warmup_steps,\n output_path=model_save_path,\n optimizer_params={'lr':5e-5},\n use_amp=False\n )\n\nmodel = SentenceTransformer(model_save_path)\ntest_evaluator(model, output_path=model_save_path)\n\n```\n\n**sup**\n\n```python\nfrom torch.utils.data import DataLoader\nimport math\nfrom sentence_transformers import models, losses\nfrom sentence_transformers import LoggingHandler, SentenceTransformer, util, InputExample\nfrom sentence_transformers.evaluation import EmbeddingSimilarityEvaluator\nimport logging\nfrom datetime import datetime\nimport os\nimport gzip\nimport csv\n\n# argument\nmodel_name = 'distilbert-base-uncased' # 可以自行替换\ntrain_batch_size = 32\nnum_epochs = 1\nmax_seq_length = 64\n\n# Here we define our SentenceTransformer model\nword_embedding_model = models.Transformer('bert-base-uncased', max_seq_length=max_seq_length,\n cache_dir='../distilbert-base-uncased')\npooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())\nmodel = SentenceTransformer(modules=[word_embedding_model, pooling_model])\n\n# Use label sentences from NLI dataset train to train out model\n\nnli_dataset_path = 'data/AllNLI.tsv.gz'\n\nif not os.path.exists(nli_dataset_path):\n util.http_get('https://sbert.net/datasets/AllNLI.tsv.gz', nli_dataset_path)\n\nprint('Read NLI dataset')\nmodel_save_path = 'output/simcse-{}-{}-{}'.format(model_name, train_batch_size,\n datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\"))\ntrain_samples = []\nwith gzip.open(nli_dataset_path, 'rt', encoding='utf8') as fIn:\n reader = csv.DictReader(fIn, delimiter='\\t', quoting=csv.QUOTE_NONE)\n count = 0\n for row in reader:\n count += 1\n label = row['label']\n if label == 'contradiction':\n premise = row['sentence1']\n negative = row['sentence2']\n elif label == 'entailment':\n premise = row['sentence1']\n positive = row['sentence2']\n if count % 3 == 0:\n if row['split'] == 'train':\n train_samples.append(InputExample(texts=[premise, positive, negative]))\nprint(f'train sample length:{len(train_samples)}')\n\n# Check if dataset exsist. If not, download and extract it\nsts_dataset_path = 'data/stsbenchmark.tsv.gz'\n\nif not os.path.exists(sts_dataset_path):\n util.http_get('https://sbert.net/datasets/stsbenchmark.tsv.gz', sts_dataset_path)\n\ndev_samples = []\ntest_samples = []\nwith gzip.open(sts_dataset_path, 'rt', encoding='utf8') as fIn:\n reader = csv.DictReader(fIn, delimiter='\\t', quoting=csv.QUOTE_NONE)\n for row in reader:\n score = float(row['score']) / 5.0 # Normalize score to range 0 ... 1\n\n if row['split'] == 'dev':\n dev_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n elif row['split'] == 'test':\n test_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n\n\ntrain_dataloader = DataLoader(train_samples, shuffle=True, batch_size=train_batch_size, drop_last=True)\ntrain_loss = losses.MultipleNegativesRankingLoss(model)\ndev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,\n name='nli-dev')\ntest_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,\n name='nli-test')\nwarmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1) # 10% of train data for warm-up\nevaluation_steps = int(len(train_dataloader) * 0.1) # Evaluate every 10% of the data\n\nprint('Start training')\nmodel.fit(train_objectives=[(train_dataloader, train_loss)],\n evaluator=dev_evaluator,\n epochs=num_epochs,\n evaluation_steps=evaluation_steps,\n warmup_steps=warmup_steps,\n output_path=model_save_path,\n optimizer_params={'lr': 5e-5},\n use_amp=True # Set to True, if your GPU supports FP16 cores\n )\n\nprint('Start test')\nmodel = SentenceTransformer(model_save_path)\ntest_evaluator(model, output_path=model_save_path)\n```\n\nsup在demo中没有,只是训练数据的构造需要自己去改写一下。如果是简单的复现,用上面几个包就可以了,避免重复造轮子,但如果想对组件进行相应的替换,那就得自己写,我以后有时间也想尝试复现一下。\n\n## 实际对比效果\n\n**query**:Sexy Elegant Base Tanks & Camis Female Smooth Sling Dress\n\n**result**:\n\n| Doc2vec | SimCSE |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| Glossy Stainless Steel Simple Laser Open Bracelet | Sexy Tanks & Camis Female Viscose Sling Dress |\n| Elegant Small Smile Face Cute Letter Earrings | Waisted Sexy Fitting Base Tanks & Camis Sling Dress |\n| Elegant Simple Cross Shell Beads Earrings | Sexy Base Tanks & Camis Summer Female Sling Dress |\n| Sexy Takato Ultra-thin Lace Lace Sexy Stockings | Waisted Split Elegant Base Tanks & Camis Female New Style Sling Dress |\n| Magnet Pillow Core Will Gift Magnet Gift Pillow | Waisted Base Tanks & Camis Vintage Female Sling Dress |\n| Fitting Suit Collar Short Sleeve Spring Summer High Waist Female Knit Dress | Elegant V-neck Satin Fitting Thin Sling Dress Female Tanks & Camis External Wear Dress |\n| Elegant Heart Simple Small Piercing Jewelry | Sexy Off-the-shoulder Casual Pirinted Sling Dress |\n| Sexy Dress Women's One Shoulder Slit Long Skirt | Thin Long Split Sexy Fitting Elegant Base Tanks & Camis Female Black Sling Dress |\n| Leather Spring Summer Leather New Arrived First Layer Vintage Manual Simple Single Shoes | Mid-length Sexy Tanks & Camis Ultrashort Female Smooth Dress |\n\n我只能说,对比学习在语句表达上确实和docvec不是一个量级的,太强了。。。\n\n## 总结\n\n1. SimCSE在语句表达上很不错,而且他好像还比美团后出的Consert还要强一些\n2. 至少这篇论文说明了在模型中做数据增强,比从源头做替换/裁剪等方式数据增强效果更好\n3. SimCSE有两个问题,在原作者近期的论文中【ESimCSE】提出来了,并且提出了解决方案:\n 1. 由于正样本长度都是一样的,因此从长度这个特征来说,就可以区分很多样本,为了避免这个问题。原作者利用一种相对安全的方式:word repetition,就是对随机对句子进行单词重复的操作,一方面改变了正样本长度相等的缺陷,另一方面保持两者特征相似。\n 2. 他承担不了大的batch-size,一方面是内存不允许,一方面是效果也会下降,因此作者仿造在cv中的Momentum Contrast的操作用于提升性能。【为了缩小train和predict的差距,ESimCSE关闭了MC encoder中的dropout】\n4. 这篇论文的模型就是用的Sentence Bert,只是损失函数换了,原论文是CosineSimilarityLoss,而这篇论文改成了InfoNCE,利用Bert的dropout直接强有力的提升对比学习的效果。\n\n## 附录\n\n[Alignment & Uniformity](https://arxiv.org/pdf/2005.10242.pdf)\n\n[github_simcse](https://github.com/princeton-nlp/SimCSE)\n\n[github_sen_tran](https://github.com/UKPLab/sentence-transformers/)\n\n[paper_simcse](https://arxiv.org/abs/2104.08821)\n\n[paper_consert](https://arxiv.org/abs/2105.11741)\n\n[paper_esimcse](https://arxiv.org/pdf/2109.04380.pdf)\n\n[paper Moco](https://arxiv.org/abs/1911.05722)","source":"_posts/对比学习-SimCSE.md","raw":"---\ntitle: 对比学习_SimCSE\ncategories:\n - 深度学习\ntags:\n - 对比学习\nmathjax: true\ndate: 2021-09-30 17:10:39\ndescription:\n---\n# 对比学习之SimCSE\n\n> 前短时间接到一个title i2i召回数据的任务,最开始只是想用简单的Doc2vec输出每个title的emb然后用faiss做召回,但是效果很差。后来尝试了用Bert直接输出结果,效果也很差。后面看论文发现Bert的emb存在模型坍塌问题【详细可以参考美团的ConSERT任务】。最后利用SimCSE比较合理的解决了需求\n\n## 问题\n\n1. 什么是自监督学习,什么是对比学习?\n2. SimCSE生成监督数据的方法是什么?\n3. 为什么生成的句向量这么有用?\n4. 他的优劣势是什么?\n\n## 概念\n\n### 自监督学习\n\n首先,我们知道现在训练模型的方式主要是两种:有监督和无监督,并且我个人认为无监督的使用场景是远远大于有监督的。无监督有一个比较特别的分支是自监督,他的特点就是不需要人工标注的类别标签信息,直接利用数据本身作为监督信息,来学习样本数据的特征表达。\n\n自监督主要分为两类:\n\n- Generative Methods:其中的典型代表就是自编码器,其利用数据本身压缩再重构去比较生成的数据与原始数据的差距。\n- Contrastive Methods:这类方法则是通过将数据分别与正例样本和负例样本在特征空间进行对比,来学习样本的特征表示。\n\n**备注**:我个人任务word2vec也可以属于自监督范畴,不过不属于上面两类,他是利用文本词与词之间关系去学习每个单词的embedding\n\n### 对比学习\n\n上面说了,对比学习属于自监督学习的一个大类,他主要的难点就是如何生成相似的正样本。其最开始是在cv中使用的,后面NLPer也不甘示弱的进行研究,才有了今天NLP在对比学习领域的百花齐放,具体的进展可以看[张俊林博客](https://zhuanlan.zhihu.com/p/367290573)。\n\nSimCSE全称(Simple Contrastive Learning of Sentence Embeddings),是一种在没有监督训练数据的情况下训练句子向量的对比学习框架。并且从无监督和有监督两种方法给出了结果。\n\n- 无监督SimCSE:仅使用dropout进行数据增强操作。具体来说,将同一个样本输入预训练编码器两次(BERT),由于每次的dropout是不同的,那么就会生成两个经过dropout后的句向量表示。这里的dropout主要是指传统的feature dropout,在标准的Transformer实现中,每个sub-layer后都默认配置了dropout。除此之外,Transformer也在multi-head attention和feed-forward network的激活函数层添加了dropout。利用dropout将这两个样本作为“正样本对”,于此同时,同一个batch里面不同样本做\"负样本对\"\n- 有监督SimCSE:基于natural language infer\u0002ence (NLI) 数据集进行训练,具体来说,论文将entailment样本作为“正样本对”,并将contradiction样本作为hard“负样本对”,并且从实验证明该方法是有效的。\n\n## 指标详解\n\n在《**Understanding Contrastive Representation Learning through Alignment and Uniformity on the Hypersphere**》一文中,作者指出了现有的对比学习都遵循以下两大原则即:相似样本应该保持靠近,并且不同的样本应该分散嵌入在高维球体。于此同时SimCSE的作者也提到了这两点,他也认为这两点是衡量表示质量很重要的指标。\n\n### alignment\n\n用于衡量两个样本的相似程度,其计算公式如下:\n$$\nL=E\\left \\| f(x)-f(y)\\right \\|_{2 }^{\\alpha}\n$$\n同时作者给出了在pytorch中的代码\n\n```python\ndef lalign(x, y, alpha=2):\n return (x - y).norm(dim=1).pow(alpha).mean()\n```\n\n### uniformity\n\n衡量规整后的特征在unit 超球体上的分布的均匀性,其计算公式如下:\n$$\nL=log(E[e^{-t\\left \\| f(x)-f(y)\\right \\|^{2}}])\n$$\n同时作者给出了在pytorch中的代码\n\n```python\ndef lunif(x, t=2):\n sq_pdist = torch.pdist(x, p=2).pow(2)\n return sq_pdist.mul(-t).exp().mean().log()\n```\n\n两者结合,作者是介绍可以直接作为损失进行优化的:\n\n```python\nloss = lalign(x, y) + lam * (lunif(x) + lunif(y)) / 2\n```\n\n## 损失函数\n\n### unsup\n\n$$\nL=-log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau }}{\\sum_{j=1}^{N}e^{sim(h_{i},{h_{j}}^{+})/\\tau}}\n$$\n\n- $h_{i}$表示查询向量,+表示对比向量,j表示大小为N的batch下其他向量\n- $\\tau$表示温度参数,默认是0.07,[详解温度参数](https://zhuanlan.zhihu.com/p/357071960)\n- sim()表示余弦相似度,当然用内积也是一样的,因为l2标准化后的内积等价于余弦相似度\n\n### Sup\n\n$$\nL = -log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau}}{\\sum_{j=1}^{N}(e^{sim(h_{i},{h_{j}}^{+})/\\tau}+e^{sim(h_{i},{h_{j}}^{-})/\\tau})}\n$$\n\n- 其他和无监督方法没有区别,只是将负向量也纳入分母\n\n**备注**:其实他的目标就是为了让分子尽可能的小,分母尽可能的大,那分子其实就代表alignment指标,分母就是代表uniformity指标,其实简单看和交叉墒损失函数很像,并且在Sentence transfomer包中的实现也是这样子的。\n\n## 代码详解\n\n### SimCSE包\n\n```python\nfrom simcse import SimCSE\nmodel = SimCSE(\"princeton-nlp/sup-simcse-bert-base-uncased\")\nsentences = ['A woman is reading.', 'A man is playing a guitar.']\nmodel.build_index(sentences)\nresults = model.search(\"He plays guitar.\")\n```\n\n作者直接将训练好的模型放在huggingface上,并且下游利用Faiss进行向量搜索,详情可以直接看github代码,如果想自己训练也很简单,利用Sentence_transfomer包有现成的demo\n\n### Sentence_transfomer包\n\n**unsup**\n\n```python\nfrom torch.utils.data import DataLoader\nimport math\nfrom sentence_transformers import models, losses\nfrom sentence_transformers import LoggingHandler, SentenceTransformer, util, InputExample\nfrom sentence_transformers.evaluation import EmbeddingSimilarityEvaluator\nimport logging\nfrom torch import nn\nfrom datetime import datetime\nimport os\nimport gzip\nimport csv\n\nos.environ['CUDA_VISIBLE_DEVICES'] = '0,1'\nmodel_name = 'distilbert-base-uncased'\ntrain_batch_size = 128\nnum_epochs = 1\nmax_seq_length = 32\nmodel_save_path = 'output/training_stsb_simcse-{}-{}-{}'.format(model_name, train_batch_size,\n datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\"))\nsts_dataset_path = 'data/stsbenchmark.tsv.gz'\nif not os.path.exists(sts_dataset_path):\n util.http_get('https://sbert.net/datasets/stsbenchmark.tsv.gz', sts_dataset_path)\n\n# Here we define our SentenceTransformer model\nword_embedding_model = models.Transformer(model_name, max_seq_length=max_seq_length,\n cache_dir='../distilbert-base-uncased')\npooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())\ndense = models.Dense(pooling_model.pooling_output_dimension,512,activation_function=nn.ReLU()) # 降维操作\nmodel = SentenceTransformer(modules=[word_embedding_model, pooling_model,dense],device=\"cuda:1\")\nwikipedia_dataset_path = 'data/wiki1m_for_simcse.txt'\nif not os.path.exists(wikipedia_dataset_path):\n util.http_get(\n 'https://huggingface.co/datasets/princeton-nlp/datasets-for-simcse/resolve/main/wiki1m_for_simcse.txt',\n wikipedia_dataset_path)\nprint('read wiki data')\ntrain_samples = []\nwith open(wikipedia_dataset_path, 'r', encoding='utf8') as fIn:\n for line in fIn.readlines()[:10000]:\n line = line.strip()\n if len(line) >= 10:\n train_samples.append(InputExample(texts=[line, line]))\nprint(len(train_samples))\nprint('Read STSB dev dataset')\ndev_samples = []\ntest_samples = []\nwith gzip.open(sts_dataset_path, 'rt', encoding='utf8') as fIn:\n reader = csv.DictReader(fIn, delimiter='\\t', quoting=csv.QUOTE_NONE)\n for row in reader:\n score = float(row['score']) / 5.0 # Normalize score to range 0 ... 1\n\n if row['split'] == 'dev':\n dev_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n elif row['split'] == 'test':\n test_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n\ndev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,\n name='sts-dev')\ntest_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,\n name='sts-test')\n\ntrain_dataloader = DataLoader(train_samples, shuffle=True, batch_size=train_batch_size, drop_last=True)\ntrain_loss = losses.MultipleNegativesRankingLoss(model)\nwarmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1) # 10% of train data for warm-up\nevaluation_steps = int(len(train_dataloader) * 0.1) #Evaluate every 10% of the data\n\ndev_evaluator(model)\n\nprint('Start train model')\n\n# train the model\nmodel.fit(train_objectives=[(train_dataloader,train_loss)],\n evaluator=dev_evaluator,\n epochs=num_epochs,\n evaluation_steps=evaluation_steps,\n warmup_steps=warmup_steps,\n output_path=model_save_path,\n optimizer_params={'lr':5e-5},\n use_amp=False\n )\n\nmodel = SentenceTransformer(model_save_path)\ntest_evaluator(model, output_path=model_save_path)\n\n```\n\n**sup**\n\n```python\nfrom torch.utils.data import DataLoader\nimport math\nfrom sentence_transformers import models, losses\nfrom sentence_transformers import LoggingHandler, SentenceTransformer, util, InputExample\nfrom sentence_transformers.evaluation import EmbeddingSimilarityEvaluator\nimport logging\nfrom datetime import datetime\nimport os\nimport gzip\nimport csv\n\n# argument\nmodel_name = 'distilbert-base-uncased' # 可以自行替换\ntrain_batch_size = 32\nnum_epochs = 1\nmax_seq_length = 64\n\n# Here we define our SentenceTransformer model\nword_embedding_model = models.Transformer('bert-base-uncased', max_seq_length=max_seq_length,\n cache_dir='../distilbert-base-uncased')\npooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())\nmodel = SentenceTransformer(modules=[word_embedding_model, pooling_model])\n\n# Use label sentences from NLI dataset train to train out model\n\nnli_dataset_path = 'data/AllNLI.tsv.gz'\n\nif not os.path.exists(nli_dataset_path):\n util.http_get('https://sbert.net/datasets/AllNLI.tsv.gz', nli_dataset_path)\n\nprint('Read NLI dataset')\nmodel_save_path = 'output/simcse-{}-{}-{}'.format(model_name, train_batch_size,\n datetime.now().strftime(\"%Y-%m-%d_%H-%M-%S\"))\ntrain_samples = []\nwith gzip.open(nli_dataset_path, 'rt', encoding='utf8') as fIn:\n reader = csv.DictReader(fIn, delimiter='\\t', quoting=csv.QUOTE_NONE)\n count = 0\n for row in reader:\n count += 1\n label = row['label']\n if label == 'contradiction':\n premise = row['sentence1']\n negative = row['sentence2']\n elif label == 'entailment':\n premise = row['sentence1']\n positive = row['sentence2']\n if count % 3 == 0:\n if row['split'] == 'train':\n train_samples.append(InputExample(texts=[premise, positive, negative]))\nprint(f'train sample length:{len(train_samples)}')\n\n# Check if dataset exsist. If not, download and extract it\nsts_dataset_path = 'data/stsbenchmark.tsv.gz'\n\nif not os.path.exists(sts_dataset_path):\n util.http_get('https://sbert.net/datasets/stsbenchmark.tsv.gz', sts_dataset_path)\n\ndev_samples = []\ntest_samples = []\nwith gzip.open(sts_dataset_path, 'rt', encoding='utf8') as fIn:\n reader = csv.DictReader(fIn, delimiter='\\t', quoting=csv.QUOTE_NONE)\n for row in reader:\n score = float(row['score']) / 5.0 # Normalize score to range 0 ... 1\n\n if row['split'] == 'dev':\n dev_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n elif row['split'] == 'test':\n test_samples.append(InputExample(texts=[row['sentence1'], row['sentence2']], label=score))\n\n\ntrain_dataloader = DataLoader(train_samples, shuffle=True, batch_size=train_batch_size, drop_last=True)\ntrain_loss = losses.MultipleNegativesRankingLoss(model)\ndev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,\n name='nli-dev')\ntest_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,\n name='nli-test')\nwarmup_steps = math.ceil(len(train_dataloader) * num_epochs * 0.1) # 10% of train data for warm-up\nevaluation_steps = int(len(train_dataloader) * 0.1) # Evaluate every 10% of the data\n\nprint('Start training')\nmodel.fit(train_objectives=[(train_dataloader, train_loss)],\n evaluator=dev_evaluator,\n epochs=num_epochs,\n evaluation_steps=evaluation_steps,\n warmup_steps=warmup_steps,\n output_path=model_save_path,\n optimizer_params={'lr': 5e-5},\n use_amp=True # Set to True, if your GPU supports FP16 cores\n )\n\nprint('Start test')\nmodel = SentenceTransformer(model_save_path)\ntest_evaluator(model, output_path=model_save_path)\n```\n\nsup在demo中没有,只是训练数据的构造需要自己去改写一下。如果是简单的复现,用上面几个包就可以了,避免重复造轮子,但如果想对组件进行相应的替换,那就得自己写,我以后有时间也想尝试复现一下。\n\n## 实际对比效果\n\n**query**:Sexy Elegant Base Tanks & Camis Female Smooth Sling Dress\n\n**result**:\n\n| Doc2vec | SimCSE |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| Glossy Stainless Steel Simple Laser Open Bracelet | Sexy Tanks & Camis Female Viscose Sling Dress |\n| Elegant Small Smile Face Cute Letter Earrings | Waisted Sexy Fitting Base Tanks & Camis Sling Dress |\n| Elegant Simple Cross Shell Beads Earrings | Sexy Base Tanks & Camis Summer Female Sling Dress |\n| Sexy Takato Ultra-thin Lace Lace Sexy Stockings | Waisted Split Elegant Base Tanks & Camis Female New Style Sling Dress |\n| Magnet Pillow Core Will Gift Magnet Gift Pillow | Waisted Base Tanks & Camis Vintage Female Sling Dress |\n| Fitting Suit Collar Short Sleeve Spring Summer High Waist Female Knit Dress | Elegant V-neck Satin Fitting Thin Sling Dress Female Tanks & Camis External Wear Dress |\n| Elegant Heart Simple Small Piercing Jewelry | Sexy Off-the-shoulder Casual Pirinted Sling Dress |\n| Sexy Dress Women's One Shoulder Slit Long Skirt | Thin Long Split Sexy Fitting Elegant Base Tanks & Camis Female Black Sling Dress |\n| Leather Spring Summer Leather New Arrived First Layer Vintage Manual Simple Single Shoes | Mid-length Sexy Tanks & Camis Ultrashort Female Smooth Dress |\n\n我只能说,对比学习在语句表达上确实和docvec不是一个量级的,太强了。。。\n\n## 总结\n\n1. SimCSE在语句表达上很不错,而且他好像还比美团后出的Consert还要强一些\n2. 至少这篇论文说明了在模型中做数据增强,比从源头做替换/裁剪等方式数据增强效果更好\n3. SimCSE有两个问题,在原作者近期的论文中【ESimCSE】提出来了,并且提出了解决方案:\n 1. 由于正样本长度都是一样的,因此从长度这个特征来说,就可以区分很多样本,为了避免这个问题。原作者利用一种相对安全的方式:word repetition,就是对随机对句子进行单词重复的操作,一方面改变了正样本长度相等的缺陷,另一方面保持两者特征相似。\n 2. 他承担不了大的batch-size,一方面是内存不允许,一方面是效果也会下降,因此作者仿造在cv中的Momentum Contrast的操作用于提升性能。【为了缩小train和predict的差距,ESimCSE关闭了MC encoder中的dropout】\n4. 这篇论文的模型就是用的Sentence Bert,只是损失函数换了,原论文是CosineSimilarityLoss,而这篇论文改成了InfoNCE,利用Bert的dropout直接强有力的提升对比学习的效果。\n\n## 附录\n\n[Alignment & Uniformity](https://arxiv.org/pdf/2005.10242.pdf)\n\n[github_simcse](https://github.com/princeton-nlp/SimCSE)\n\n[github_sen_tran](https://github.com/UKPLab/sentence-transformers/)\n\n[paper_simcse](https://arxiv.org/abs/2104.08821)\n\n[paper_consert](https://arxiv.org/abs/2105.11741)\n\n[paper_esimcse](https://arxiv.org/pdf/2109.04380.pdf)\n\n[paper Moco](https://arxiv.org/abs/1911.05722)","slug":"对比学习-SimCSE","published":1,"updated":"2021-10-18T11:53:38.575Z","comments":1,"layout":"post","photos":[],"link":"","_id":"ckytrqb6w001rso3w8xy2hyo5","content":"<h1 id=\"对比学习之SimCSE\"><a href=\"#对比学习之SimCSE\" class=\"headerlink\" title=\"对比学习之SimCSE\"></a>对比学习之SimCSE</h1><blockquote>\n<p>前短时间接到一个title i2i召回数据的任务,最开始只是想用简单的Doc2vec输出每个title的emb然后用faiss做召回,但是效果很差。后来尝试了用Bert直接输出结果,效果也很差。后面看论文发现Bert的emb存在模型坍塌问题【详细可以参考美团的ConSERT任务】。最后利用SimCSE比较合理的解决了需求</p>\n</blockquote>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><ol>\n<li>什么是自监督学习,什么是对比学习?</li>\n<li>SimCSE生成监督数据的方法是什么?</li>\n<li>为什么生成的句向量这么有用?</li>\n<li>他的优劣势是什么?</li>\n</ol>\n<h2 id=\"概念\"><a href=\"#概念\" class=\"headerlink\" title=\"概念\"></a>概念</h2><h3 id=\"自监督学习\"><a href=\"#自监督学习\" class=\"headerlink\" title=\"自监督学习\"></a>自监督学习</h3><p>首先,我们知道现在训练模型的方式主要是两种:有监督和无监督,并且我个人认为无监督的使用场景是远远大于有监督的。无监督有一个比较特别的分支是自监督,他的特点就是不需要人工标注的类别标签信息,直接利用数据本身作为监督信息,来学习样本数据的特征表达。</p>\n<p>自监督主要分为两类:</p>\n<ul>\n<li>Generative Methods:其中的典型代表就是自编码器,其利用数据本身压缩再重构去比较生成的数据与原始数据的差距。</li>\n<li>Contrastive Methods:这类方法则是通过将数据分别与正例样本和负例样本在特征空间进行对比,来学习样本的特征表示。</li>\n</ul>\n<p><strong>备注</strong>:我个人任务word2vec也可以属于自监督范畴,不过不属于上面两类,他是利用文本词与词之间关系去学习每个单词的embedding</p>\n<h3 id=\"对比学习\"><a href=\"#对比学习\" class=\"headerlink\" title=\"对比学习\"></a>对比学习</h3><p>上面说了,对比学习属于自监督学习的一个大类,他主要的难点就是如何生成相似的正样本。其最开始是在cv中使用的,后面NLPer也不甘示弱的进行研究,才有了今天NLP在对比学习领域的百花齐放,具体的进展可以看<a href=\"https://zhuanlan.zhihu.com/p/367290573\">张俊林博客</a>。</p>\n<p>SimCSE全称(Simple Contrastive Learning of Sentence Embeddings),是一种在没有监督训练数据的情况下训练句子向量的对比学习框架。并且从无监督和有监督两种方法给出了结果。</p>\n<ul>\n<li>无监督SimCSE:仅使用dropout进行数据增强操作。具体来说,将同一个样本输入预训练编码器两次(BERT),由于每次的dropout是不同的,那么就会生成两个经过dropout后的句向量表示。这里的dropout主要是指传统的feature dropout,在标准的Transformer实现中,每个sub-layer后都默认配置了dropout。除此之外,Transformer也在multi-head attention和feed-forward network的激活函数层添加了dropout。利用dropout将这两个样本作为“正样本对”,于此同时,同一个batch里面不同样本做”负样本对”</li>\n<li>有监督SimCSE:基于natural language infer\u0002ence (NLI) 数据集进行训练,具体来说,论文将entailment样本作为“正样本对”,并将contradiction样本作为hard“负样本对”,并且从实验证明该方法是有效的。</li>\n</ul>\n<h2 id=\"指标详解\"><a href=\"#指标详解\" class=\"headerlink\" title=\"指标详解\"></a>指标详解</h2><p>在《<strong>Understanding Contrastive Representation Learning through Alignment and Uniformity on the Hypersphere</strong>》一文中,作者指出了现有的对比学习都遵循以下两大原则即:相似样本应该保持靠近,并且不同的样本应该分散嵌入在高维球体。于此同时SimCSE的作者也提到了这两点,他也认为这两点是衡量表示质量很重要的指标。</p>\n<h3 id=\"alignment\"><a href=\"#alignment\" class=\"headerlink\" title=\"alignment\"></a>alignment</h3><p>用于衡量两个样本的相似程度,其计算公式如下:</p>\n<script type=\"math/tex; mode=display\">\nL=E\\left \\| f(x)-f(y)\\right \\|_{2 }^{\\alpha}</script><p>同时作者给出了在pytorch中的代码</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">lalign</span>(<span class=\"params\">x, y, alpha=<span class=\"number\">2</span></span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> (x - y).norm(dim=<span class=\"number\">1</span>).<span class=\"built_in\">pow</span>(alpha).mean()</span><br></pre></td></tr></table></figure>\n<h3 id=\"uniformity\"><a href=\"#uniformity\" class=\"headerlink\" title=\"uniformity\"></a>uniformity</h3><p>衡量规整后的特征在unit 超球体上的分布的均匀性,其计算公式如下:</p>\n<script type=\"math/tex; mode=display\">\nL=log(E[e^{-t\\left \\| f(x)-f(y)\\right \\|^{2}}])</script><p>同时作者给出了在pytorch中的代码</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">lunif</span>(<span class=\"params\">x, t=<span class=\"number\">2</span></span>):</span></span><br><span class=\"line\"> sq_pdist = torch.pdist(x, p=<span class=\"number\">2</span>).<span class=\"built_in\">pow</span>(<span class=\"number\">2</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> sq_pdist.mul(-t).exp().mean().log()</span><br></pre></td></tr></table></figure>\n<p>两者结合,作者是介绍可以直接作为损失进行优化的:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">loss = lalign(x, y) + lam * (lunif(x) + lunif(y)) / <span class=\"number\">2</span></span><br></pre></td></tr></table></figure>\n<h2 id=\"损失函数\"><a href=\"#损失函数\" class=\"headerlink\" title=\"损失函数\"></a>损失函数</h2><h3 id=\"unsup\"><a href=\"#unsup\" class=\"headerlink\" title=\"unsup\"></a>unsup</h3><script type=\"math/tex; mode=display\">\nL=-log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau }}{\\sum_{j=1}^{N}e^{sim(h_{i},{h_{j}}^{+})/\\tau}}</script><ul>\n<li>$h_{i}$表示查询向量,+表示对比向量,j表示大小为N的batch下其他向量</li>\n<li>$\\tau$表示温度参数,默认是0.07,<a href=\"https://zhuanlan.zhihu.com/p/357071960\">详解温度参数</a></li>\n<li>sim()表示余弦相似度,当然用内积也是一样的,因为l2标准化后的内积等价于余弦相似度</li>\n</ul>\n<h3 id=\"Sup\"><a href=\"#Sup\" class=\"headerlink\" title=\"Sup\"></a>Sup</h3><script type=\"math/tex; mode=display\">\nL = -log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau}}{\\sum_{j=1}^{N}(e^{sim(h_{i},{h_{j}}^{+})/\\tau}+e^{sim(h_{i},{h_{j}}^{-})/\\tau})}</script><ul>\n<li>其他和无监督方法没有区别,只是将负向量也纳入分母</li>\n</ul>\n<p><strong>备注</strong>:其实他的目标就是为了让分子尽可能的小,分母尽可能的大,那分子其实就代表alignment指标,分母就是代表uniformity指标,其实简单看和交叉墒损失函数很像,并且在Sentence transfomer包中的实现也是这样子的。</p>\n<h2 id=\"代码详解\"><a href=\"#代码详解\" class=\"headerlink\" title=\"代码详解\"></a>代码详解</h2><h3 id=\"SimCSE包\"><a href=\"#SimCSE包\" class=\"headerlink\" title=\"SimCSE包\"></a>SimCSE包</h3><figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> simcse <span class=\"keyword\">import</span> SimCSE</span><br><span class=\"line\">model = SimCSE(<span class=\"string\">"princeton-nlp/sup-simcse-bert-base-uncased"</span>)</span><br><span class=\"line\">sentences = [<span class=\"string\">'A woman is reading.'</span>, <span class=\"string\">'A man is playing a guitar.'</span>]</span><br><span class=\"line\">model.build_index(sentences)</span><br><span class=\"line\">results = model.search(<span class=\"string\">"He plays guitar."</span>)</span><br></pre></td></tr></table></figure>\n<p>作者直接将训练好的模型放在huggingface上,并且下游利用Faiss进行向量搜索,详情可以直接看github代码,如果想自己训练也很简单,利用Sentence_transfomer包有现成的demo</p>\n<h3 id=\"Sentence-transfomer包\"><a href=\"#Sentence-transfomer包\" class=\"headerlink\" title=\"Sentence_transfomer包\"></a>Sentence_transfomer包</h3><p><strong>unsup</strong></p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br><span class=\"line\">52</span><br><span class=\"line\">53</span><br><span class=\"line\">54</span><br><span class=\"line\">55</span><br><span class=\"line\">56</span><br><span class=\"line\">57</span><br><span class=\"line\">58</span><br><span class=\"line\">59</span><br><span class=\"line\">60</span><br><span class=\"line\">61</span><br><span class=\"line\">62</span><br><span class=\"line\">63</span><br><span class=\"line\">64</span><br><span class=\"line\">65</span><br><span class=\"line\">66</span><br><span class=\"line\">67</span><br><span class=\"line\">68</span><br><span class=\"line\">69</span><br><span class=\"line\">70</span><br><span class=\"line\">71</span><br><span class=\"line\">72</span><br><span class=\"line\">73</span><br><span class=\"line\">74</span><br><span class=\"line\">75</span><br><span class=\"line\">76</span><br><span class=\"line\">77</span><br><span class=\"line\">78</span><br><span class=\"line\">79</span><br><span class=\"line\">80</span><br><span class=\"line\">81</span><br><span class=\"line\">82</span><br><span class=\"line\">83</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> torch.utils.data <span class=\"keyword\">import</span> DataLoader</span><br><span class=\"line\"><span class=\"keyword\">import</span> math</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> models, losses</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> LoggingHandler, SentenceTransformer, util, InputExample</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers.evaluation <span class=\"keyword\">import</span> EmbeddingSimilarityEvaluator</span><br><span class=\"line\"><span class=\"keyword\">import</span> logging</span><br><span class=\"line\"><span class=\"keyword\">from</span> torch <span class=\"keyword\">import</span> nn</span><br><span class=\"line\"><span class=\"keyword\">from</span> datetime <span class=\"keyword\">import</span> datetime</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"><span class=\"keyword\">import</span> gzip</span><br><span class=\"line\"><span class=\"keyword\">import</span> csv</span><br><span class=\"line\"></span><br><span class=\"line\">os.environ[<span class=\"string\">'CUDA_VISIBLE_DEVICES'</span>] = <span class=\"string\">'0,1'</span></span><br><span class=\"line\">model_name = <span class=\"string\">'distilbert-base-uncased'</span></span><br><span class=\"line\">train_batch_size = <span class=\"number\">128</span></span><br><span class=\"line\">num_epochs = <span class=\"number\">1</span></span><br><span class=\"line\">max_seq_length = <span class=\"number\">32</span></span><br><span class=\"line\">model_save_path = <span class=\"string\">'output/training_stsb_simcse-{}-{}-{}'</span>.<span class=\"built_in\">format</span>(model_name, train_batch_size,</span><br><span class=\"line\"> datetime.now().strftime(<span class=\"string\">"%Y-%m-%d_%H-%M-%S"</span>))</span><br><span class=\"line\">sts_dataset_path = <span class=\"string\">'data/stsbenchmark.tsv.gz'</span></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(sts_dataset_path):</span><br><span class=\"line\"> util.http_get(<span class=\"string\">'https://sbert.net/datasets/stsbenchmark.tsv.gz'</span>, sts_dataset_path)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Here we define our SentenceTransformer model</span></span><br><span class=\"line\">word_embedding_model = models.Transformer(model_name, max_seq_length=max_seq_length,</span><br><span class=\"line\"> cache_dir=<span class=\"string\">'../distilbert-base-uncased'</span>)</span><br><span class=\"line\">pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())</span><br><span class=\"line\">dense = models.Dense(pooling_model.pooling_output_dimension,<span class=\"number\">512</span>,activation_function=nn.ReLU()) <span class=\"comment\"># 降维操作</span></span><br><span class=\"line\">model = SentenceTransformer(modules=[word_embedding_model, pooling_model,dense],device=<span class=\"string\">"cuda:1"</span>)</span><br><span class=\"line\">wikipedia_dataset_path = <span class=\"string\">'data/wiki1m_for_simcse.txt'</span></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(wikipedia_dataset_path):</span><br><span class=\"line\"> util.http_get(</span><br><span class=\"line\"> <span class=\"string\">'https://huggingface.co/datasets/princeton-nlp/datasets-for-simcse/resolve/main/wiki1m_for_simcse.txt'</span>,</span><br><span class=\"line\"> wikipedia_dataset_path)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'read wiki data'</span>)</span><br><span class=\"line\">train_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> <span class=\"built_in\">open</span>(wikipedia_dataset_path, <span class=\"string\">'r'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> <span class=\"keyword\">for</span> line <span class=\"keyword\">in</span> fIn.readlines()[:<span class=\"number\">10000</span>]:</span><br><span class=\"line\"> line = line.strip()</span><br><span class=\"line\"> <span class=\"keyword\">if</span> <span class=\"built_in\">len</span>(line) >= <span class=\"number\">10</span>:</span><br><span class=\"line\"> train_samples.append(InputExample(texts=[line, line]))</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"built_in\">len</span>(train_samples))</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Read STSB dev dataset'</span>)</span><br><span class=\"line\">dev_samples = []</span><br><span class=\"line\">test_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> gzip.<span class=\"built_in\">open</span>(sts_dataset_path, <span class=\"string\">'rt'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> reader = csv.DictReader(fIn, delimiter=<span class=\"string\">'\\t'</span>, quoting=csv.QUOTE_NONE)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> row <span class=\"keyword\">in</span> reader:</span><br><span class=\"line\"> score = <span class=\"built_in\">float</span>(row[<span class=\"string\">'score'</span>]) / <span class=\"number\">5.0</span> <span class=\"comment\"># Normalize score to range 0 ... 1</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">if</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'dev'</span>:</span><br><span class=\"line\"> dev_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"> <span class=\"keyword\">elif</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'test'</span>:</span><br><span class=\"line\"> test_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"></span><br><span class=\"line\">dev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'sts-dev'</span>)</span><br><span class=\"line\">test_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'sts-test'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\">train_dataloader = DataLoader(train_samples, shuffle=<span class=\"literal\">True</span>, batch_size=train_batch_size, drop_last=<span class=\"literal\">True</span>)</span><br><span class=\"line\">train_loss = losses.MultipleNegativesRankingLoss(model)</span><br><span class=\"line\">warmup_steps = math.ceil(<span class=\"built_in\">len</span>(train_dataloader) * num_epochs * <span class=\"number\">0.1</span>) <span class=\"comment\"># 10% of train data for warm-up</span></span><br><span class=\"line\">evaluation_steps = <span class=\"built_in\">int</span>(<span class=\"built_in\">len</span>(train_dataloader) * <span class=\"number\">0.1</span>) <span class=\"comment\">#Evaluate every 10% of the data</span></span><br><span class=\"line\"></span><br><span class=\"line\">dev_evaluator(model)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Start train model'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># train the model</span></span><br><span class=\"line\">model.fit(train_objectives=[(train_dataloader,train_loss)],</span><br><span class=\"line\"> evaluator=dev_evaluator,</span><br><span class=\"line\"> epochs=num_epochs,</span><br><span class=\"line\"> evaluation_steps=evaluation_steps,</span><br><span class=\"line\"> warmup_steps=warmup_steps,</span><br><span class=\"line\"> output_path=model_save_path,</span><br><span class=\"line\"> optimizer_params={<span class=\"string\">'lr'</span>:<span class=\"number\">5e-5</span>},</span><br><span class=\"line\"> use_amp=<span class=\"literal\">False</span></span><br><span class=\"line\"> )</span><br><span class=\"line\"></span><br><span class=\"line\">model = SentenceTransformer(model_save_path)</span><br><span class=\"line\">test_evaluator(model, output_path=model_save_path)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>sup</strong></p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br><span class=\"line\">52</span><br><span class=\"line\">53</span><br><span class=\"line\">54</span><br><span class=\"line\">55</span><br><span class=\"line\">56</span><br><span class=\"line\">57</span><br><span class=\"line\">58</span><br><span class=\"line\">59</span><br><span class=\"line\">60</span><br><span class=\"line\">61</span><br><span class=\"line\">62</span><br><span class=\"line\">63</span><br><span class=\"line\">64</span><br><span class=\"line\">65</span><br><span class=\"line\">66</span><br><span class=\"line\">67</span><br><span class=\"line\">68</span><br><span class=\"line\">69</span><br><span class=\"line\">70</span><br><span class=\"line\">71</span><br><span class=\"line\">72</span><br><span class=\"line\">73</span><br><span class=\"line\">74</span><br><span class=\"line\">75</span><br><span class=\"line\">76</span><br><span class=\"line\">77</span><br><span class=\"line\">78</span><br><span class=\"line\">79</span><br><span class=\"line\">80</span><br><span class=\"line\">81</span><br><span class=\"line\">82</span><br><span class=\"line\">83</span><br><span class=\"line\">84</span><br><span class=\"line\">85</span><br><span class=\"line\">86</span><br><span class=\"line\">87</span><br><span class=\"line\">88</span><br><span class=\"line\">89</span><br><span class=\"line\">90</span><br><span class=\"line\">91</span><br><span class=\"line\">92</span><br><span class=\"line\">93</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> torch.utils.data <span class=\"keyword\">import</span> DataLoader</span><br><span class=\"line\"><span class=\"keyword\">import</span> math</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> models, losses</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> LoggingHandler, SentenceTransformer, util, InputExample</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers.evaluation <span class=\"keyword\">import</span> EmbeddingSimilarityEvaluator</span><br><span class=\"line\"><span class=\"keyword\">import</span> logging</span><br><span class=\"line\"><span class=\"keyword\">from</span> datetime <span class=\"keyword\">import</span> datetime</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"><span class=\"keyword\">import</span> gzip</span><br><span class=\"line\"><span class=\"keyword\">import</span> csv</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># argument</span></span><br><span class=\"line\">model_name = <span class=\"string\">'distilbert-base-uncased'</span> <span class=\"comment\"># 可以自行替换</span></span><br><span class=\"line\">train_batch_size = <span class=\"number\">32</span></span><br><span class=\"line\">num_epochs = <span class=\"number\">1</span></span><br><span class=\"line\">max_seq_length = <span class=\"number\">64</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Here we define our SentenceTransformer model</span></span><br><span class=\"line\">word_embedding_model = models.Transformer(<span class=\"string\">'bert-base-uncased'</span>, max_seq_length=max_seq_length,</span><br><span class=\"line\"> cache_dir=<span class=\"string\">'../distilbert-base-uncased'</span>)</span><br><span class=\"line\">pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())</span><br><span class=\"line\">model = SentenceTransformer(modules=[word_embedding_model, pooling_model])</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Use label sentences from NLI dataset train to train out model</span></span><br><span class=\"line\"></span><br><span class=\"line\">nli_dataset_path = <span class=\"string\">'data/AllNLI.tsv.gz'</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(nli_dataset_path):</span><br><span class=\"line\"> util.http_get(<span class=\"string\">'https://sbert.net/datasets/AllNLI.tsv.gz'</span>, nli_dataset_path)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Read NLI dataset'</span>)</span><br><span class=\"line\">model_save_path = <span class=\"string\">'output/simcse-{}-{}-{}'</span>.<span class=\"built_in\">format</span>(model_name, train_batch_size,</span><br><span class=\"line\"> datetime.now().strftime(<span class=\"string\">"%Y-%m-%d_%H-%M-%S"</span>))</span><br><span class=\"line\">train_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> gzip.<span class=\"built_in\">open</span>(nli_dataset_path, <span class=\"string\">'rt'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> reader = csv.DictReader(fIn, delimiter=<span class=\"string\">'\\t'</span>, quoting=csv.QUOTE_NONE)</span><br><span class=\"line\"> count = <span class=\"number\">0</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> row <span class=\"keyword\">in</span> reader:</span><br><span class=\"line\"> count += <span class=\"number\">1</span></span><br><span class=\"line\"> label = row[<span class=\"string\">'label'</span>]</span><br><span class=\"line\"> <span class=\"keyword\">if</span> label == <span class=\"string\">'contradiction'</span>:</span><br><span class=\"line\"> premise = row[<span class=\"string\">'sentence1'</span>]</span><br><span class=\"line\"> negative = row[<span class=\"string\">'sentence2'</span>]</span><br><span class=\"line\"> <span class=\"keyword\">elif</span> label == <span class=\"string\">'entailment'</span>:</span><br><span class=\"line\"> premise = row[<span class=\"string\">'sentence1'</span>]</span><br><span class=\"line\"> positive = row[<span class=\"string\">'sentence2'</span>]</span><br><span class=\"line\"> <span class=\"keyword\">if</span> count % <span class=\"number\">3</span> == <span class=\"number\">0</span>:</span><br><span class=\"line\"> <span class=\"keyword\">if</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'train'</span>:</span><br><span class=\"line\"> train_samples.append(InputExample(texts=[premise, positive, negative]))</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">f'train sample length:<span class=\"subst\">{<span class=\"built_in\">len</span>(train_samples)}</span>'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Check if dataset exsist. If not, download and extract it</span></span><br><span class=\"line\">sts_dataset_path = <span class=\"string\">'data/stsbenchmark.tsv.gz'</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(sts_dataset_path):</span><br><span class=\"line\"> util.http_get(<span class=\"string\">'https://sbert.net/datasets/stsbenchmark.tsv.gz'</span>, sts_dataset_path)</span><br><span class=\"line\"></span><br><span class=\"line\">dev_samples = []</span><br><span class=\"line\">test_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> gzip.<span class=\"built_in\">open</span>(sts_dataset_path, <span class=\"string\">'rt'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> reader = csv.DictReader(fIn, delimiter=<span class=\"string\">'\\t'</span>, quoting=csv.QUOTE_NONE)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> row <span class=\"keyword\">in</span> reader:</span><br><span class=\"line\"> score = <span class=\"built_in\">float</span>(row[<span class=\"string\">'score'</span>]) / <span class=\"number\">5.0</span> <span class=\"comment\"># Normalize score to range 0 ... 1</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">if</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'dev'</span>:</span><br><span class=\"line\"> dev_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"> <span class=\"keyword\">elif</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'test'</span>:</span><br><span class=\"line\"> test_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">train_dataloader = DataLoader(train_samples, shuffle=<span class=\"literal\">True</span>, batch_size=train_batch_size, drop_last=<span class=\"literal\">True</span>)</span><br><span class=\"line\">train_loss = losses.MultipleNegativesRankingLoss(model)</span><br><span class=\"line\">dev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'nli-dev'</span>)</span><br><span class=\"line\">test_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'nli-test'</span>)</span><br><span class=\"line\">warmup_steps = math.ceil(<span class=\"built_in\">len</span>(train_dataloader) * num_epochs * <span class=\"number\">0.1</span>) <span class=\"comment\"># 10% of train data for warm-up</span></span><br><span class=\"line\">evaluation_steps = <span class=\"built_in\">int</span>(<span class=\"built_in\">len</span>(train_dataloader) * <span class=\"number\">0.1</span>) <span class=\"comment\"># Evaluate every 10% of the data</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Start training'</span>)</span><br><span class=\"line\">model.fit(train_objectives=[(train_dataloader, train_loss)],</span><br><span class=\"line\"> evaluator=dev_evaluator,</span><br><span class=\"line\"> epochs=num_epochs,</span><br><span class=\"line\"> evaluation_steps=evaluation_steps,</span><br><span class=\"line\"> warmup_steps=warmup_steps,</span><br><span class=\"line\"> output_path=model_save_path,</span><br><span class=\"line\"> optimizer_params={<span class=\"string\">'lr'</span>: <span class=\"number\">5e-5</span>},</span><br><span class=\"line\"> use_amp=<span class=\"literal\">True</span> <span class=\"comment\"># Set to True, if your GPU supports FP16 cores</span></span><br><span class=\"line\"> )</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Start test'</span>)</span><br><span class=\"line\">model = SentenceTransformer(model_save_path)</span><br><span class=\"line\">test_evaluator(model, output_path=model_save_path)</span><br></pre></td></tr></table></figure>\n<p>sup在demo中没有,只是训练数据的构造需要自己去改写一下。如果是简单的复现,用上面几个包就可以了,避免重复造轮子,但如果想对组件进行相应的替换,那就得自己写,我以后有时间也想尝试复现一下。</p>\n<h2 id=\"实际对比效果\"><a href=\"#实际对比效果\" class=\"headerlink\" title=\"实际对比效果\"></a>实际对比效果</h2><p><strong>query</strong>:Sexy Elegant Base Tanks & Camis Female Smooth Sling Dress</p>\n<p><strong>result</strong>:</p>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th>Doc2vec</th>\n<th>SimCSE</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Glossy Stainless Steel Simple Laser Open Bracelet</td>\n<td>Sexy Tanks & Camis Female Viscose Sling Dress</td>\n</tr>\n<tr>\n<td>Elegant Small Smile Face Cute Letter Earrings</td>\n<td>Waisted Sexy Fitting Base Tanks & Camis Sling Dress</td>\n</tr>\n<tr>\n<td>Elegant Simple Cross Shell Beads Earrings</td>\n<td>Sexy Base Tanks & Camis Summer Female Sling Dress</td>\n</tr>\n<tr>\n<td>Sexy Takato Ultra-thin Lace Lace Sexy Stockings</td>\n<td>Waisted Split Elegant Base Tanks & Camis Female New Style Sling Dress</td>\n</tr>\n<tr>\n<td>Magnet Pillow Core Will Gift Magnet Gift Pillow</td>\n<td>Waisted Base Tanks & Camis Vintage Female Sling Dress</td>\n</tr>\n<tr>\n<td>Fitting Suit Collar Short Sleeve Spring Summer High Waist Female Knit Dress</td>\n<td>Elegant V-neck Satin Fitting Thin Sling Dress Female Tanks & Camis External Wear Dress</td>\n</tr>\n<tr>\n<td>Elegant Heart Simple Small Piercing Jewelry</td>\n<td>Sexy Off-the-shoulder Casual Pirinted Sling Dress</td>\n</tr>\n<tr>\n<td>Sexy Dress Women’s One Shoulder Slit Long Skirt</td>\n<td>Thin Long Split Sexy Fitting Elegant Base Tanks & Camis Female Black Sling Dress</td>\n</tr>\n<tr>\n<td>Leather Spring Summer Leather New Arrived First Layer Vintage Manual Simple Single Shoes</td>\n<td>Mid-length Sexy Tanks & Camis Ultrashort Female Smooth Dress</td>\n</tr>\n</tbody>\n</table>\n</div>\n<p>我只能说,对比学习在语句表达上确实和docvec不是一个量级的,太强了。。。</p>\n<h2 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h2><ol>\n<li>SimCSE在语句表达上很不错,而且他好像还比美团后出的Consert还要强一些</li>\n<li>至少这篇论文说明了在模型中做数据增强,比从源头做替换/裁剪等方式数据增强效果更好</li>\n<li>SimCSE有两个问题,在原作者近期的论文中【ESimCSE】提出来了,并且提出了解决方案:<ol>\n<li>由于正样本长度都是一样的,因此从长度这个特征来说,就可以区分很多样本,为了避免这个问题。原作者利用一种相对安全的方式:word repetition,就是对随机对句子进行单词重复的操作,一方面改变了正样本长度相等的缺陷,另一方面保持两者特征相似。</li>\n<li>他承担不了大的batch-size,一方面是内存不允许,一方面是效果也会下降,因此作者仿造在cv中的Momentum Contrast的操作用于提升性能。【为了缩小train和predict的差距,ESimCSE关闭了MC encoder中的dropout】</li>\n</ol>\n</li>\n<li>这篇论文的模型就是用的Sentence Bert,只是损失函数换了,原论文是CosineSimilarityLoss,而这篇论文改成了InfoNCE,利用Bert的dropout直接强有力的提升对比学习的效果。</li>\n</ol>\n<h2 id=\"附录\"><a href=\"#附录\" class=\"headerlink\" title=\"附录\"></a>附录</h2><p><a href=\"https://arxiv.org/pdf/2005.10242.pdf\">Alignment & Uniformity</a></p>\n<p><a href=\"https://github.com/princeton-nlp/SimCSE\">github_simcse</a></p>\n<p><a href=\"https://github.com/UKPLab/sentence-transformers/\">github_sen_tran</a></p>\n<p><a href=\"https://arxiv.org/abs/2104.08821\">paper_simcse</a></p>\n<p><a href=\"https://arxiv.org/abs/2105.11741\">paper_consert</a></p>\n<p><a href=\"https://arxiv.org/pdf/2109.04380.pdf\">paper_esimcse</a></p>\n<p><a href=\"https://arxiv.org/abs/1911.05722\">paper Moco</a></p>\n","site":{"data":{}},"cover":"https://cdn.jsdelivr.net/npm/butterfly-extsrc@1/img/default.jpg","excerpt":"","more":"<h1 id=\"对比学习之SimCSE\"><a href=\"#对比学习之SimCSE\" class=\"headerlink\" title=\"对比学习之SimCSE\"></a>对比学习之SimCSE</h1><blockquote>\n<p>前短时间接到一个title i2i召回数据的任务,最开始只是想用简单的Doc2vec输出每个title的emb然后用faiss做召回,但是效果很差。后来尝试了用Bert直接输出结果,效果也很差。后面看论文发现Bert的emb存在模型坍塌问题【详细可以参考美团的ConSERT任务】。最后利用SimCSE比较合理的解决了需求</p>\n</blockquote>\n<h2 id=\"问题\"><a href=\"#问题\" class=\"headerlink\" title=\"问题\"></a>问题</h2><ol>\n<li>什么是自监督学习,什么是对比学习?</li>\n<li>SimCSE生成监督数据的方法是什么?</li>\n<li>为什么生成的句向量这么有用?</li>\n<li>他的优劣势是什么?</li>\n</ol>\n<h2 id=\"概念\"><a href=\"#概念\" class=\"headerlink\" title=\"概念\"></a>概念</h2><h3 id=\"自监督学习\"><a href=\"#自监督学习\" class=\"headerlink\" title=\"自监督学习\"></a>自监督学习</h3><p>首先,我们知道现在训练模型的方式主要是两种:有监督和无监督,并且我个人认为无监督的使用场景是远远大于有监督的。无监督有一个比较特别的分支是自监督,他的特点就是不需要人工标注的类别标签信息,直接利用数据本身作为监督信息,来学习样本数据的特征表达。</p>\n<p>自监督主要分为两类:</p>\n<ul>\n<li>Generative Methods:其中的典型代表就是自编码器,其利用数据本身压缩再重构去比较生成的数据与原始数据的差距。</li>\n<li>Contrastive Methods:这类方法则是通过将数据分别与正例样本和负例样本在特征空间进行对比,来学习样本的特征表示。</li>\n</ul>\n<p><strong>备注</strong>:我个人任务word2vec也可以属于自监督范畴,不过不属于上面两类,他是利用文本词与词之间关系去学习每个单词的embedding</p>\n<h3 id=\"对比学习\"><a href=\"#对比学习\" class=\"headerlink\" title=\"对比学习\"></a>对比学习</h3><p>上面说了,对比学习属于自监督学习的一个大类,他主要的难点就是如何生成相似的正样本。其最开始是在cv中使用的,后面NLPer也不甘示弱的进行研究,才有了今天NLP在对比学习领域的百花齐放,具体的进展可以看<a href=\"https://zhuanlan.zhihu.com/p/367290573\">张俊林博客</a>。</p>\n<p>SimCSE全称(Simple Contrastive Learning of Sentence Embeddings),是一种在没有监督训练数据的情况下训练句子向量的对比学习框架。并且从无监督和有监督两种方法给出了结果。</p>\n<ul>\n<li>无监督SimCSE:仅使用dropout进行数据增强操作。具体来说,将同一个样本输入预训练编码器两次(BERT),由于每次的dropout是不同的,那么就会生成两个经过dropout后的句向量表示。这里的dropout主要是指传统的feature dropout,在标准的Transformer实现中,每个sub-layer后都默认配置了dropout。除此之外,Transformer也在multi-head attention和feed-forward network的激活函数层添加了dropout。利用dropout将这两个样本作为“正样本对”,于此同时,同一个batch里面不同样本做”负样本对”</li>\n<li>有监督SimCSE:基于natural language infer\u0002ence (NLI) 数据集进行训练,具体来说,论文将entailment样本作为“正样本对”,并将contradiction样本作为hard“负样本对”,并且从实验证明该方法是有效的。</li>\n</ul>\n<h2 id=\"指标详解\"><a href=\"#指标详解\" class=\"headerlink\" title=\"指标详解\"></a>指标详解</h2><p>在《<strong>Understanding Contrastive Representation Learning through Alignment and Uniformity on the Hypersphere</strong>》一文中,作者指出了现有的对比学习都遵循以下两大原则即:相似样本应该保持靠近,并且不同的样本应该分散嵌入在高维球体。于此同时SimCSE的作者也提到了这两点,他也认为这两点是衡量表示质量很重要的指标。</p>\n<h3 id=\"alignment\"><a href=\"#alignment\" class=\"headerlink\" title=\"alignment\"></a>alignment</h3><p>用于衡量两个样本的相似程度,其计算公式如下:</p>\n<script type=\"math/tex; mode=display\">\nL=E\\left \\| f(x)-f(y)\\right \\|_{2 }^{\\alpha}</script><p>同时作者给出了在pytorch中的代码</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">lalign</span>(<span class=\"params\">x, y, alpha=<span class=\"number\">2</span></span>):</span></span><br><span class=\"line\"> <span class=\"keyword\">return</span> (x - y).norm(dim=<span class=\"number\">1</span>).<span class=\"built_in\">pow</span>(alpha).mean()</span><br></pre></td></tr></table></figure>\n<h3 id=\"uniformity\"><a href=\"#uniformity\" class=\"headerlink\" title=\"uniformity\"></a>uniformity</h3><p>衡量规整后的特征在unit 超球体上的分布的均匀性,其计算公式如下:</p>\n<script type=\"math/tex; mode=display\">\nL=log(E[e^{-t\\left \\| f(x)-f(y)\\right \\|^{2}}])</script><p>同时作者给出了在pytorch中的代码</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"function\"><span class=\"keyword\">def</span> <span class=\"title\">lunif</span>(<span class=\"params\">x, t=<span class=\"number\">2</span></span>):</span></span><br><span class=\"line\"> sq_pdist = torch.pdist(x, p=<span class=\"number\">2</span>).<span class=\"built_in\">pow</span>(<span class=\"number\">2</span>)</span><br><span class=\"line\"> <span class=\"keyword\">return</span> sq_pdist.mul(-t).exp().mean().log()</span><br></pre></td></tr></table></figure>\n<p>两者结合,作者是介绍可以直接作为损失进行优化的:</p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br></pre></td><td class=\"code\"><pre><span class=\"line\">loss = lalign(x, y) + lam * (lunif(x) + lunif(y)) / <span class=\"number\">2</span></span><br></pre></td></tr></table></figure>\n<h2 id=\"损失函数\"><a href=\"#损失函数\" class=\"headerlink\" title=\"损失函数\"></a>损失函数</h2><h3 id=\"unsup\"><a href=\"#unsup\" class=\"headerlink\" title=\"unsup\"></a>unsup</h3><script type=\"math/tex; mode=display\">\nL=-log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau }}{\\sum_{j=1}^{N}e^{sim(h_{i},{h_{j}}^{+})/\\tau}}</script><ul>\n<li>$h_{i}$表示查询向量,+表示对比向量,j表示大小为N的batch下其他向量</li>\n<li>$\\tau$表示温度参数,默认是0.07,<a href=\"https://zhuanlan.zhihu.com/p/357071960\">详解温度参数</a></li>\n<li>sim()表示余弦相似度,当然用内积也是一样的,因为l2标准化后的内积等价于余弦相似度</li>\n</ul>\n<h3 id=\"Sup\"><a href=\"#Sup\" class=\"headerlink\" title=\"Sup\"></a>Sup</h3><script type=\"math/tex; mode=display\">\nL = -log\\frac{e^{sim(h_{i},{h_{i}}^{+})/\\tau}}{\\sum_{j=1}^{N}(e^{sim(h_{i},{h_{j}}^{+})/\\tau}+e^{sim(h_{i},{h_{j}}^{-})/\\tau})}</script><ul>\n<li>其他和无监督方法没有区别,只是将负向量也纳入分母</li>\n</ul>\n<p><strong>备注</strong>:其实他的目标就是为了让分子尽可能的小,分母尽可能的大,那分子其实就代表alignment指标,分母就是代表uniformity指标,其实简单看和交叉墒损失函数很像,并且在Sentence transfomer包中的实现也是这样子的。</p>\n<h2 id=\"代码详解\"><a href=\"#代码详解\" class=\"headerlink\" title=\"代码详解\"></a>代码详解</h2><h3 id=\"SimCSE包\"><a href=\"#SimCSE包\" class=\"headerlink\" title=\"SimCSE包\"></a>SimCSE包</h3><figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> simcse <span class=\"keyword\">import</span> SimCSE</span><br><span class=\"line\">model = SimCSE(<span class=\"string\">"princeton-nlp/sup-simcse-bert-base-uncased"</span>)</span><br><span class=\"line\">sentences = [<span class=\"string\">'A woman is reading.'</span>, <span class=\"string\">'A man is playing a guitar.'</span>]</span><br><span class=\"line\">model.build_index(sentences)</span><br><span class=\"line\">results = model.search(<span class=\"string\">"He plays guitar."</span>)</span><br></pre></td></tr></table></figure>\n<p>作者直接将训练好的模型放在huggingface上,并且下游利用Faiss进行向量搜索,详情可以直接看github代码,如果想自己训练也很简单,利用Sentence_transfomer包有现成的demo</p>\n<h3 id=\"Sentence-transfomer包\"><a href=\"#Sentence-transfomer包\" class=\"headerlink\" title=\"Sentence_transfomer包\"></a>Sentence_transfomer包</h3><p><strong>unsup</strong></p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br><span class=\"line\">52</span><br><span class=\"line\">53</span><br><span class=\"line\">54</span><br><span class=\"line\">55</span><br><span class=\"line\">56</span><br><span class=\"line\">57</span><br><span class=\"line\">58</span><br><span class=\"line\">59</span><br><span class=\"line\">60</span><br><span class=\"line\">61</span><br><span class=\"line\">62</span><br><span class=\"line\">63</span><br><span class=\"line\">64</span><br><span class=\"line\">65</span><br><span class=\"line\">66</span><br><span class=\"line\">67</span><br><span class=\"line\">68</span><br><span class=\"line\">69</span><br><span class=\"line\">70</span><br><span class=\"line\">71</span><br><span class=\"line\">72</span><br><span class=\"line\">73</span><br><span class=\"line\">74</span><br><span class=\"line\">75</span><br><span class=\"line\">76</span><br><span class=\"line\">77</span><br><span class=\"line\">78</span><br><span class=\"line\">79</span><br><span class=\"line\">80</span><br><span class=\"line\">81</span><br><span class=\"line\">82</span><br><span class=\"line\">83</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> torch.utils.data <span class=\"keyword\">import</span> DataLoader</span><br><span class=\"line\"><span class=\"keyword\">import</span> math</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> models, losses</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> LoggingHandler, SentenceTransformer, util, InputExample</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers.evaluation <span class=\"keyword\">import</span> EmbeddingSimilarityEvaluator</span><br><span class=\"line\"><span class=\"keyword\">import</span> logging</span><br><span class=\"line\"><span class=\"keyword\">from</span> torch <span class=\"keyword\">import</span> nn</span><br><span class=\"line\"><span class=\"keyword\">from</span> datetime <span class=\"keyword\">import</span> datetime</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"><span class=\"keyword\">import</span> gzip</span><br><span class=\"line\"><span class=\"keyword\">import</span> csv</span><br><span class=\"line\"></span><br><span class=\"line\">os.environ[<span class=\"string\">'CUDA_VISIBLE_DEVICES'</span>] = <span class=\"string\">'0,1'</span></span><br><span class=\"line\">model_name = <span class=\"string\">'distilbert-base-uncased'</span></span><br><span class=\"line\">train_batch_size = <span class=\"number\">128</span></span><br><span class=\"line\">num_epochs = <span class=\"number\">1</span></span><br><span class=\"line\">max_seq_length = <span class=\"number\">32</span></span><br><span class=\"line\">model_save_path = <span class=\"string\">'output/training_stsb_simcse-{}-{}-{}'</span>.<span class=\"built_in\">format</span>(model_name, train_batch_size,</span><br><span class=\"line\"> datetime.now().strftime(<span class=\"string\">"%Y-%m-%d_%H-%M-%S"</span>))</span><br><span class=\"line\">sts_dataset_path = <span class=\"string\">'data/stsbenchmark.tsv.gz'</span></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(sts_dataset_path):</span><br><span class=\"line\"> util.http_get(<span class=\"string\">'https://sbert.net/datasets/stsbenchmark.tsv.gz'</span>, sts_dataset_path)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Here we define our SentenceTransformer model</span></span><br><span class=\"line\">word_embedding_model = models.Transformer(model_name, max_seq_length=max_seq_length,</span><br><span class=\"line\"> cache_dir=<span class=\"string\">'../distilbert-base-uncased'</span>)</span><br><span class=\"line\">pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())</span><br><span class=\"line\">dense = models.Dense(pooling_model.pooling_output_dimension,<span class=\"number\">512</span>,activation_function=nn.ReLU()) <span class=\"comment\"># 降维操作</span></span><br><span class=\"line\">model = SentenceTransformer(modules=[word_embedding_model, pooling_model,dense],device=<span class=\"string\">"cuda:1"</span>)</span><br><span class=\"line\">wikipedia_dataset_path = <span class=\"string\">'data/wiki1m_for_simcse.txt'</span></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(wikipedia_dataset_path):</span><br><span class=\"line\"> util.http_get(</span><br><span class=\"line\"> <span class=\"string\">'https://huggingface.co/datasets/princeton-nlp/datasets-for-simcse/resolve/main/wiki1m_for_simcse.txt'</span>,</span><br><span class=\"line\"> wikipedia_dataset_path)</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'read wiki data'</span>)</span><br><span class=\"line\">train_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> <span class=\"built_in\">open</span>(wikipedia_dataset_path, <span class=\"string\">'r'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> <span class=\"keyword\">for</span> line <span class=\"keyword\">in</span> fIn.readlines()[:<span class=\"number\">10000</span>]:</span><br><span class=\"line\"> line = line.strip()</span><br><span class=\"line\"> <span class=\"keyword\">if</span> <span class=\"built_in\">len</span>(line) >= <span class=\"number\">10</span>:</span><br><span class=\"line\"> train_samples.append(InputExample(texts=[line, line]))</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"built_in\">len</span>(train_samples))</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Read STSB dev dataset'</span>)</span><br><span class=\"line\">dev_samples = []</span><br><span class=\"line\">test_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> gzip.<span class=\"built_in\">open</span>(sts_dataset_path, <span class=\"string\">'rt'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> reader = csv.DictReader(fIn, delimiter=<span class=\"string\">'\\t'</span>, quoting=csv.QUOTE_NONE)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> row <span class=\"keyword\">in</span> reader:</span><br><span class=\"line\"> score = <span class=\"built_in\">float</span>(row[<span class=\"string\">'score'</span>]) / <span class=\"number\">5.0</span> <span class=\"comment\"># Normalize score to range 0 ... 1</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">if</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'dev'</span>:</span><br><span class=\"line\"> dev_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"> <span class=\"keyword\">elif</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'test'</span>:</span><br><span class=\"line\"> test_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"></span><br><span class=\"line\">dev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'sts-dev'</span>)</span><br><span class=\"line\">test_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'sts-test'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\">train_dataloader = DataLoader(train_samples, shuffle=<span class=\"literal\">True</span>, batch_size=train_batch_size, drop_last=<span class=\"literal\">True</span>)</span><br><span class=\"line\">train_loss = losses.MultipleNegativesRankingLoss(model)</span><br><span class=\"line\">warmup_steps = math.ceil(<span class=\"built_in\">len</span>(train_dataloader) * num_epochs * <span class=\"number\">0.1</span>) <span class=\"comment\"># 10% of train data for warm-up</span></span><br><span class=\"line\">evaluation_steps = <span class=\"built_in\">int</span>(<span class=\"built_in\">len</span>(train_dataloader) * <span class=\"number\">0.1</span>) <span class=\"comment\">#Evaluate every 10% of the data</span></span><br><span class=\"line\"></span><br><span class=\"line\">dev_evaluator(model)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Start train model'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># train the model</span></span><br><span class=\"line\">model.fit(train_objectives=[(train_dataloader,train_loss)],</span><br><span class=\"line\"> evaluator=dev_evaluator,</span><br><span class=\"line\"> epochs=num_epochs,</span><br><span class=\"line\"> evaluation_steps=evaluation_steps,</span><br><span class=\"line\"> warmup_steps=warmup_steps,</span><br><span class=\"line\"> output_path=model_save_path,</span><br><span class=\"line\"> optimizer_params={<span class=\"string\">'lr'</span>:<span class=\"number\">5e-5</span>},</span><br><span class=\"line\"> use_amp=<span class=\"literal\">False</span></span><br><span class=\"line\"> )</span><br><span class=\"line\"></span><br><span class=\"line\">model = SentenceTransformer(model_save_path)</span><br><span class=\"line\">test_evaluator(model, output_path=model_save_path)</span><br><span class=\"line\"></span><br></pre></td></tr></table></figure>\n<p><strong>sup</strong></p>\n<figure class=\"highlight python\"><table><tr><td class=\"gutter\"><pre><span class=\"line\">1</span><br><span class=\"line\">2</span><br><span class=\"line\">3</span><br><span class=\"line\">4</span><br><span class=\"line\">5</span><br><span class=\"line\">6</span><br><span class=\"line\">7</span><br><span class=\"line\">8</span><br><span class=\"line\">9</span><br><span class=\"line\">10</span><br><span class=\"line\">11</span><br><span class=\"line\">12</span><br><span class=\"line\">13</span><br><span class=\"line\">14</span><br><span class=\"line\">15</span><br><span class=\"line\">16</span><br><span class=\"line\">17</span><br><span class=\"line\">18</span><br><span class=\"line\">19</span><br><span class=\"line\">20</span><br><span class=\"line\">21</span><br><span class=\"line\">22</span><br><span class=\"line\">23</span><br><span class=\"line\">24</span><br><span class=\"line\">25</span><br><span class=\"line\">26</span><br><span class=\"line\">27</span><br><span class=\"line\">28</span><br><span class=\"line\">29</span><br><span class=\"line\">30</span><br><span class=\"line\">31</span><br><span class=\"line\">32</span><br><span class=\"line\">33</span><br><span class=\"line\">34</span><br><span class=\"line\">35</span><br><span class=\"line\">36</span><br><span class=\"line\">37</span><br><span class=\"line\">38</span><br><span class=\"line\">39</span><br><span class=\"line\">40</span><br><span class=\"line\">41</span><br><span class=\"line\">42</span><br><span class=\"line\">43</span><br><span class=\"line\">44</span><br><span class=\"line\">45</span><br><span class=\"line\">46</span><br><span class=\"line\">47</span><br><span class=\"line\">48</span><br><span class=\"line\">49</span><br><span class=\"line\">50</span><br><span class=\"line\">51</span><br><span class=\"line\">52</span><br><span class=\"line\">53</span><br><span class=\"line\">54</span><br><span class=\"line\">55</span><br><span class=\"line\">56</span><br><span class=\"line\">57</span><br><span class=\"line\">58</span><br><span class=\"line\">59</span><br><span class=\"line\">60</span><br><span class=\"line\">61</span><br><span class=\"line\">62</span><br><span class=\"line\">63</span><br><span class=\"line\">64</span><br><span class=\"line\">65</span><br><span class=\"line\">66</span><br><span class=\"line\">67</span><br><span class=\"line\">68</span><br><span class=\"line\">69</span><br><span class=\"line\">70</span><br><span class=\"line\">71</span><br><span class=\"line\">72</span><br><span class=\"line\">73</span><br><span class=\"line\">74</span><br><span class=\"line\">75</span><br><span class=\"line\">76</span><br><span class=\"line\">77</span><br><span class=\"line\">78</span><br><span class=\"line\">79</span><br><span class=\"line\">80</span><br><span class=\"line\">81</span><br><span class=\"line\">82</span><br><span class=\"line\">83</span><br><span class=\"line\">84</span><br><span class=\"line\">85</span><br><span class=\"line\">86</span><br><span class=\"line\">87</span><br><span class=\"line\">88</span><br><span class=\"line\">89</span><br><span class=\"line\">90</span><br><span class=\"line\">91</span><br><span class=\"line\">92</span><br><span class=\"line\">93</span><br></pre></td><td class=\"code\"><pre><span class=\"line\"><span class=\"keyword\">from</span> torch.utils.data <span class=\"keyword\">import</span> DataLoader</span><br><span class=\"line\"><span class=\"keyword\">import</span> math</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> models, losses</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers <span class=\"keyword\">import</span> LoggingHandler, SentenceTransformer, util, InputExample</span><br><span class=\"line\"><span class=\"keyword\">from</span> sentence_transformers.evaluation <span class=\"keyword\">import</span> EmbeddingSimilarityEvaluator</span><br><span class=\"line\"><span class=\"keyword\">import</span> logging</span><br><span class=\"line\"><span class=\"keyword\">from</span> datetime <span class=\"keyword\">import</span> datetime</span><br><span class=\"line\"><span class=\"keyword\">import</span> os</span><br><span class=\"line\"><span class=\"keyword\">import</span> gzip</span><br><span class=\"line\"><span class=\"keyword\">import</span> csv</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># argument</span></span><br><span class=\"line\">model_name = <span class=\"string\">'distilbert-base-uncased'</span> <span class=\"comment\"># 可以自行替换</span></span><br><span class=\"line\">train_batch_size = <span class=\"number\">32</span></span><br><span class=\"line\">num_epochs = <span class=\"number\">1</span></span><br><span class=\"line\">max_seq_length = <span class=\"number\">64</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Here we define our SentenceTransformer model</span></span><br><span class=\"line\">word_embedding_model = models.Transformer(<span class=\"string\">'bert-base-uncased'</span>, max_seq_length=max_seq_length,</span><br><span class=\"line\"> cache_dir=<span class=\"string\">'../distilbert-base-uncased'</span>)</span><br><span class=\"line\">pooling_model = models.Pooling(word_embedding_model.get_word_embedding_dimension())</span><br><span class=\"line\">model = SentenceTransformer(modules=[word_embedding_model, pooling_model])</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Use label sentences from NLI dataset train to train out model</span></span><br><span class=\"line\"></span><br><span class=\"line\">nli_dataset_path = <span class=\"string\">'data/AllNLI.tsv.gz'</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(nli_dataset_path):</span><br><span class=\"line\"> util.http_get(<span class=\"string\">'https://sbert.net/datasets/AllNLI.tsv.gz'</span>, nli_dataset_path)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Read NLI dataset'</span>)</span><br><span class=\"line\">model_save_path = <span class=\"string\">'output/simcse-{}-{}-{}'</span>.<span class=\"built_in\">format</span>(model_name, train_batch_size,</span><br><span class=\"line\"> datetime.now().strftime(<span class=\"string\">"%Y-%m-%d_%H-%M-%S"</span>))</span><br><span class=\"line\">train_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> gzip.<span class=\"built_in\">open</span>(nli_dataset_path, <span class=\"string\">'rt'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> reader = csv.DictReader(fIn, delimiter=<span class=\"string\">'\\t'</span>, quoting=csv.QUOTE_NONE)</span><br><span class=\"line\"> count = <span class=\"number\">0</span></span><br><span class=\"line\"> <span class=\"keyword\">for</span> row <span class=\"keyword\">in</span> reader:</span><br><span class=\"line\"> count += <span class=\"number\">1</span></span><br><span class=\"line\"> label = row[<span class=\"string\">'label'</span>]</span><br><span class=\"line\"> <span class=\"keyword\">if</span> label == <span class=\"string\">'contradiction'</span>:</span><br><span class=\"line\"> premise = row[<span class=\"string\">'sentence1'</span>]</span><br><span class=\"line\"> negative = row[<span class=\"string\">'sentence2'</span>]</span><br><span class=\"line\"> <span class=\"keyword\">elif</span> label == <span class=\"string\">'entailment'</span>:</span><br><span class=\"line\"> premise = row[<span class=\"string\">'sentence1'</span>]</span><br><span class=\"line\"> positive = row[<span class=\"string\">'sentence2'</span>]</span><br><span class=\"line\"> <span class=\"keyword\">if</span> count % <span class=\"number\">3</span> == <span class=\"number\">0</span>:</span><br><span class=\"line\"> <span class=\"keyword\">if</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'train'</span>:</span><br><span class=\"line\"> train_samples.append(InputExample(texts=[premise, positive, negative]))</span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">f'train sample length:<span class=\"subst\">{<span class=\"built_in\">len</span>(train_samples)}</span>'</span>)</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"comment\"># Check if dataset exsist. If not, download and extract it</span></span><br><span class=\"line\">sts_dataset_path = <span class=\"string\">'data/stsbenchmark.tsv.gz'</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"keyword\">if</span> <span class=\"keyword\">not</span> os.path.exists(sts_dataset_path):</span><br><span class=\"line\"> util.http_get(<span class=\"string\">'https://sbert.net/datasets/stsbenchmark.tsv.gz'</span>, sts_dataset_path)</span><br><span class=\"line\"></span><br><span class=\"line\">dev_samples = []</span><br><span class=\"line\">test_samples = []</span><br><span class=\"line\"><span class=\"keyword\">with</span> gzip.<span class=\"built_in\">open</span>(sts_dataset_path, <span class=\"string\">'rt'</span>, encoding=<span class=\"string\">'utf8'</span>) <span class=\"keyword\">as</span> fIn:</span><br><span class=\"line\"> reader = csv.DictReader(fIn, delimiter=<span class=\"string\">'\\t'</span>, quoting=csv.QUOTE_NONE)</span><br><span class=\"line\"> <span class=\"keyword\">for</span> row <span class=\"keyword\">in</span> reader:</span><br><span class=\"line\"> score = <span class=\"built_in\">float</span>(row[<span class=\"string\">'score'</span>]) / <span class=\"number\">5.0</span> <span class=\"comment\"># Normalize score to range 0 ... 1</span></span><br><span class=\"line\"></span><br><span class=\"line\"> <span class=\"keyword\">if</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'dev'</span>:</span><br><span class=\"line\"> dev_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"> <span class=\"keyword\">elif</span> row[<span class=\"string\">'split'</span>] == <span class=\"string\">'test'</span>:</span><br><span class=\"line\"> test_samples.append(InputExample(texts=[row[<span class=\"string\">'sentence1'</span>], row[<span class=\"string\">'sentence2'</span>]], label=score))</span><br><span class=\"line\"></span><br><span class=\"line\"></span><br><span class=\"line\">train_dataloader = DataLoader(train_samples, shuffle=<span class=\"literal\">True</span>, batch_size=train_batch_size, drop_last=<span class=\"literal\">True</span>)</span><br><span class=\"line\">train_loss = losses.MultipleNegativesRankingLoss(model)</span><br><span class=\"line\">dev_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(dev_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'nli-dev'</span>)</span><br><span class=\"line\">test_evaluator = EmbeddingSimilarityEvaluator.from_input_examples(test_samples, batch_size=train_batch_size,</span><br><span class=\"line\"> name=<span class=\"string\">'nli-test'</span>)</span><br><span class=\"line\">warmup_steps = math.ceil(<span class=\"built_in\">len</span>(train_dataloader) * num_epochs * <span class=\"number\">0.1</span>) <span class=\"comment\"># 10% of train data for warm-up</span></span><br><span class=\"line\">evaluation_steps = <span class=\"built_in\">int</span>(<span class=\"built_in\">len</span>(train_dataloader) * <span class=\"number\">0.1</span>) <span class=\"comment\"># Evaluate every 10% of the data</span></span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Start training'</span>)</span><br><span class=\"line\">model.fit(train_objectives=[(train_dataloader, train_loss)],</span><br><span class=\"line\"> evaluator=dev_evaluator,</span><br><span class=\"line\"> epochs=num_epochs,</span><br><span class=\"line\"> evaluation_steps=evaluation_steps,</span><br><span class=\"line\"> warmup_steps=warmup_steps,</span><br><span class=\"line\"> output_path=model_save_path,</span><br><span class=\"line\"> optimizer_params={<span class=\"string\">'lr'</span>: <span class=\"number\">5e-5</span>},</span><br><span class=\"line\"> use_amp=<span class=\"literal\">True</span> <span class=\"comment\"># Set to True, if your GPU supports FP16 cores</span></span><br><span class=\"line\"> )</span><br><span class=\"line\"></span><br><span class=\"line\"><span class=\"built_in\">print</span>(<span class=\"string\">'Start test'</span>)</span><br><span class=\"line\">model = SentenceTransformer(model_save_path)</span><br><span class=\"line\">test_evaluator(model, output_path=model_save_path)</span><br></pre></td></tr></table></figure>\n<p>sup在demo中没有,只是训练数据的构造需要自己去改写一下。如果是简单的复现,用上面几个包就可以了,避免重复造轮子,但如果想对组件进行相应的替换,那就得自己写,我以后有时间也想尝试复现一下。</p>\n<h2 id=\"实际对比效果\"><a href=\"#实际对比效果\" class=\"headerlink\" title=\"实际对比效果\"></a>实际对比效果</h2><p><strong>query</strong>:Sexy Elegant Base Tanks & Camis Female Smooth Sling Dress</p>\n<p><strong>result</strong>:</p>\n<div class=\"table-container\">\n<table>\n<thead>\n<tr>\n<th>Doc2vec</th>\n<th>SimCSE</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Glossy Stainless Steel Simple Laser Open Bracelet</td>\n<td>Sexy Tanks & Camis Female Viscose Sling Dress</td>\n</tr>\n<tr>\n<td>Elegant Small Smile Face Cute Letter Earrings</td>\n<td>Waisted Sexy Fitting Base Tanks & Camis Sling Dress</td>\n</tr>\n<tr>\n<td>Elegant Simple Cross Shell Beads Earrings</td>\n<td>Sexy Base Tanks & Camis Summer Female Sling Dress</td>\n</tr>\n<tr>\n<td>Sexy Takato Ultra-thin Lace Lace Sexy Stockings</td>\n<td>Waisted Split Elegant Base Tanks & Camis Female New Style Sling Dress</td>\n</tr>\n<tr>\n<td>Magnet Pillow Core Will Gift Magnet Gift Pillow</td>\n<td>Waisted Base Tanks & Camis Vintage Female Sling Dress</td>\n</tr>\n<tr>\n<td>Fitting Suit Collar Short Sleeve Spring Summer High Waist Female Knit Dress</td>\n<td>Elegant V-neck Satin Fitting Thin Sling Dress Female Tanks & Camis External Wear Dress</td>\n</tr>\n<tr>\n<td>Elegant Heart Simple Small Piercing Jewelry</td>\n<td>Sexy Off-the-shoulder Casual Pirinted Sling Dress</td>\n</tr>\n<tr>\n<td>Sexy Dress Women’s One Shoulder Slit Long Skirt</td>\n<td>Thin Long Split Sexy Fitting Elegant Base Tanks & Camis Female Black Sling Dress</td>\n</tr>\n<tr>\n<td>Leather Spring Summer Leather New Arrived First Layer Vintage Manual Simple Single Shoes</td>\n<td>Mid-length Sexy Tanks & Camis Ultrashort Female Smooth Dress</td>\n</tr>\n</tbody>\n</table>\n</div>\n<p>我只能说,对比学习在语句表达上确实和docvec不是一个量级的,太强了。。。</p>\n<h2 id=\"总结\"><a href=\"#总结\" class=\"headerlink\" title=\"总结\"></a>总结</h2><ol>\n<li>SimCSE在语句表达上很不错,而且他好像还比美团后出的Consert还要强一些</li>\n<li>至少这篇论文说明了在模型中做数据增强,比从源头做替换/裁剪等方式数据增强效果更好</li>\n<li>SimCSE有两个问题,在原作者近期的论文中【ESimCSE】提出来了,并且提出了解决方案:<ol>\n<li>由于正样本长度都是一样的,因此从长度这个特征来说,就可以区分很多样本,为了避免这个问题。原作者利用一种相对安全的方式:word repetition,就是对随机对句子进行单词重复的操作,一方面改变了正样本长度相等的缺陷,另一方面保持两者特征相似。</li>\n<li>他承担不了大的batch-size,一方面是内存不允许,一方面是效果也会下降,因此作者仿造在cv中的Momentum Contrast的操作用于提升性能。【为了缩小train和predict的差距,ESimCSE关闭了MC encoder中的dropout】</li>\n</ol>\n</li>\n<li>这篇论文的模型就是用的Sentence Bert,只是损失函数换了,原论文是CosineSimilarityLoss,而这篇论文改成了InfoNCE,利用Bert的dropout直接强有力的提升对比学习的效果。</li>\n</ol>\n<h2 id=\"附录\"><a href=\"#附录\" class=\"headerlink\" title=\"附录\"></a>附录</h2><p><a href=\"https://arxiv.org/pdf/2005.10242.pdf\">Alignment & Uniformity</a></p>\n<p><a href=\"https://github.com/princeton-nlp/SimCSE\">github_simcse</a></p>\n<p><a href=\"https://github.com/UKPLab/sentence-transformers/\">github_sen_tran</a></p>\n<p><a href=\"https://arxiv.org/abs/2104.08821\">paper_simcse</a></p>\n<p><a href=\"https://arxiv.org/abs/2105.11741\">paper_consert</a></p>\n<p><a href=\"https://arxiv.org/pdf/2109.04380.pdf\">paper_esimcse</a></p>\n<p><a href=\"https://arxiv.org/abs/1911.05722\">paper Moco</a></p>\n"}],"PostAsset":[],"PostCategory":[{"post_id":"ckytrqb4p0008so3w2ewkfkc7","category_id":"ckytrqb4e0004so3wdwq6887r","_id":"ckytrqb54000fso3w9amn7typ"},{"post_id":"ckytrqb480001so3we7wr9e42","category_id":"ckytrqb4e0004so3wdwq6887r","_id":"ckytrqb59000kso3w3aiz0fkd"},{"post_id":"ckytrqb4c0003so3w4lup7eqs","category_id":"ckytrqb4e0004so3wdwq6887r","_id":"ckytrqb5c000nso3w9f2xdnvy"},{"post_id":"ckytrqb50000eso3wfuldh7sl","category_id":"ckytrqb4e0004so3wdwq6887r","_id":"ckytrqb5f000qso3wc7w49bqr"},{"post_id":"ckytrqb4o0007so3w6qw7hvqx","category_id":"ckytrqb4e0004so3wdwq6887r","_id":"ckytrqb5j000tso3w6zfwbdmf"},{"post_id":"ckytrqb4r0009so3w8gp62h0m","category_id":"ckytrqb5b000mso3w7g2x2wrm","_id":"ckytrqb5o000zso3w51sq6i0x"},{"post_id":"ckytrqb5h000sso3we223g7lk","category_id":"ckytrqb5b000mso3w7g2x2wrm","_id":"ckytrqb5p0012so3w2gkd73jc"},{"post_id":"ckytrqb5m000wso3w1bpy6yqs","category_id":"ckytrqb5b000mso3w7g2x2wrm","_id":"ckytrqb5q0014so3w9izhcy2f"},{"post_id":"ckytrqb4w000dso3w9h7g7i8d","category_id":"ckytrqb5j000uso3wfanphyf3","_id":"ckytrqb5u0017so3w4l3u5ol8"},{"post_id":"ckytrqb58000jso3w01qpgjfp","category_id":"ckytrqb5o0010so3wdtyxbhfm","_id":"ckytrqb5u0019so3wc0vsf6ti"},{"post_id":"ckytrqb5d000pso3w776c2lty","category_id":"ckytrqb5q0015so3w553wgwp0","_id":"ckytrqb5x001dso3w40eget0s"},{"post_id":"ckytrqb5n000yso3whtve4rls","category_id":"ckytrqb5o0010so3wdtyxbhfm","_id":"ckytrqb5z001gso3wcw41f9tc"},{"post_id":"ckytrqb6v001qso3wansd4vvb","category_id":"ckytrqb4e0004so3wdwq6887r","_id":"ckytrqb70001tso3w9gv45qmx"},{"post_id":"ckytrqb6w001rso3w8xy2hyo5","category_id":"ckytrqb4e0004so3wdwq6887r","_id":"ckytrqb71001uso3w7aiib42f"}],"PostTag":[{"post_id":"ckytrqb480001so3we7wr9e42","tag_id":"ckytrqb4j0005so3wc9c339mu","_id":"ckytrqb4w000cso3w0i2kc6mz"},{"post_id":"ckytrqb4c0003so3w4lup7eqs","tag_id":"ckytrqb4j0005so3wc9c339mu","_id":"ckytrqb58000iso3wf8qwa6mh"},{"post_id":"ckytrqb4o0007so3w6qw7hvqx","tag_id":"ckytrqb56000hso3wg0cd80o6","_id":"ckytrqb5g000rso3wfo3bgrf2"},{"post_id":"ckytrqb4p0008so3w2ewkfkc7","tag_id":"ckytrqb56000hso3wg0cd80o6","_id":"ckytrqb5m000xso3w0zv28pgs"},{"post_id":"ckytrqb4r0009so3w8gp62h0m","tag_id":"ckytrqb5k000vso3wepxxh09i","_id":"ckytrqb5p0013so3w8onaeef0"},{"post_id":"ckytrqb4w000dso3w9h7g7i8d","tag_id":"ckytrqb5o0011so3w79yo5h5b","_id":"ckytrqb5u0018so3wagpo9487"},{"post_id":"ckytrqb50000eso3wfuldh7sl","tag_id":"ckytrqb5o0011so3w79yo5h5b","_id":"ckytrqb5v001cso3w6yq02we4"},{"post_id":"ckytrqb58000jso3w01qpgjfp","tag_id":"ckytrqb5u001aso3w2hnk86lm","_id":"ckytrqb5y001fso3wba6e338v"},{"post_id":"ckytrqb5a000lso3w8970cuw5","tag_id":"ckytrqb5x001eso3w8sm2hndl","_id":"ckytrqb60001iso3w3qjy2uab"},{"post_id":"ckytrqb5d000pso3w776c2lty","tag_id":"ckytrqb5z001hso3w9djv59tz","_id":"ckytrqb62001kso3wfvqv4sew"},{"post_id":"ckytrqb5h000sso3we223g7lk","tag_id":"ckytrqb61001jso3w0i555dof","_id":"ckytrqb63001mso3whuiiegcb"},{"post_id":"ckytrqb5m000wso3w1bpy6yqs","tag_id":"ckytrqb5u001aso3w2hnk86lm","_id":"ckytrqb64001oso3wctvu8jm4"},{"post_id":"ckytrqb5n000yso3whtve4rls","tag_id":"ckytrqb63001nso3w2dkxh09k","_id":"ckytrqb64001pso3wbuev0hur"},{"post_id":"ckytrqb6v001qso3wansd4vvb","tag_id":"ckytrqb70001sso3whrec0yu4","_id":"ckytrqb71001wso3w4wpphqu4"},{"post_id":"ckytrqb6w001rso3w8xy2hyo5","tag_id":"ckytrqb71001vso3w5t1eh9u7","_id":"ckytrqb71001xso3wdltgb0w9"}],"Tag":[{"name":"GNNs","_id":"ckytrqb4j0005so3wc9c339mu"},{"name":"Graph Embedding","_id":"ckytrqb56000hso3wg0cd80o6"},{"name":"community detective","_id":"ckytrqb5k000vso3wepxxh09i"},{"name":"NLP","_id":"ckytrqb5o0011so3w79yo5h5b"},{"name":"基础知识","_id":"ckytrqb5u001aso3w2hnk86lm"},{"name":"机器学习","_id":"ckytrqb5x001eso3w8sm2hndl"},{"name":"工程开发","_id":"ckytrqb5z001hso3w9djv59tz"},{"name":"异常检测","_id":"ckytrqb61001jso3w0i555dof"},{"name":"数据预处理","_id":"ckytrqb63001nso3w2dkxh09k"},{"name":"transfomer","_id":"ckytrqb70001sso3whrec0yu4"},{"name":"对比学习","_id":"ckytrqb71001vso3w5t1eh9u7"}]}}