├── _posts ├── database │ └── mysql.md ├── machine_learning │ ├── 2019-03-03-titanic.md │ ├── 2019-10-01-linear-regression.md │ ├── 2019-10-03-classification.md │ ├── 2019-10-02-logistics-regression.md │ ├── 2019-09-11-cat-in-the-dat.md │ ├── 2019-03-20-cnns.md │ ├── 2019-03-10-RNN.md │ ├── 2019-03-04-train-dl.md │ ├── 2019-09-30-what-is-machine-learning.md │ └── 2019-10-04-pca.md ├── python │ ├── 2018-11-02-pandas.html │ ├── 2019-09-21-sparse-matrix.md │ ├── 2018-10-06-python-snippets.md │ ├── 2018-07-04-format.md │ └── 2018-06-02-python-re.md ├── rec │ ├── 2019-09-10-ncf-code.md │ ├── 2019-09-12-fm-family-code.md │ ├── 2019-08-31-amazon-item-to-item.md │ ├── 2019-09-06-deep-FM.md │ ├── 2019-09-04-wide-deep.md │ ├── 2019-09-05-FM.md │ ├── 2019-09-17-item-sim-models.md │ ├── 2019-09-10-ncf.md │ └── 2019-09-24-crt-models.md ├── network │ ├── 2015-08-31-http-headers.md │ ├── 2016-08-25-arp.md │ ├── log.md │ ├── 2015-08-31-http-semantic.md │ ├── 2015-09-01-http-cache.md │ ├── 2020-02-20-https.md │ ├── 2020-07-23-计算机网络.md │ ├── 2015-09-03-http-cookit.md │ └── 2020-06-10-http-body-encoding.md ├── unix │ ├── time.md │ ├── open.md │ ├── 2020-01-10-dynamic-memory.md │ ├── 2019-12-18-client-server-pattern.md │ └── signal.md ├── cs │ ├── 2016-05-19-use-summator-do-subtraction.md │ ├── 2020-06-02-codec.md │ ├── 2015-05-01-computer-storage-systemter.md │ └── 2015-02-10-data-representation.md ├── algorithm │ ├── 2015-03-01-pow-no-recurtion.md │ ├── 2017-06-09-newtons-method.md │ ├── 2017-01-05-sort.md │ ├── 2018-01-10-maximum-contiguous-subsequence.md │ ├── 2019-08-11-binary-search.md │ ├── 2019-10-13-sample.md │ └── 2017-04-10-link-list.md ├── cpp │ ├── 2015-04-29-cpp-inherit-type.md │ ├── 2020-02-08-c-vararg.md │ ├── 2015-11-03-effective-cpp-46.md │ ├── 2015-03-15-c-pre-processing.md │ └── 2020-05-20-chrono.md ├── web │ ├── 2016-11-11-avoid-forced-synchonous-layout.md │ ├── 2016-11-01-how-gpu-speed-up-page-render.md │ ├── 2016-05-18-web-security.md │ ├── css │ │ ├── 2015-09-10-css-flex.md │ │ ├── 2015-08-20-css-selector.md │ │ └── 2015-10-10-css-animation.md │ ├── 2016-09-08-css-center.md │ ├── 2016-07-21-fetch-API.md │ └── 2016-04-02-high-performace-web.md └── tools │ └── 2018-11-30-jupyterlab.md ├── README.md ├── site ├── assets │ ├── images │ │ ├── favicon.ico │ │ └── loading.gif │ ├── css │ │ ├── text.scss │ │ ├── style.scss │ │ └── scss │ │ │ ├── m-enhance.scss │ │ │ ├── print.scss │ │ │ ├── var.scss │ │ │ ├── m-tools.scss │ │ │ ├── m-site.scss │ │ │ ├── m-footer.scss │ │ │ ├── m-home.scss │ │ │ ├── m-header.scss │ │ │ ├── m-list.scss │ │ │ ├── notebook.scss │ │ │ ├── common.scss │ │ │ ├── highlight.scss │ │ │ ├── m-post.scss │ │ │ └── m-icon.scss │ └── js │ │ └── lib │ │ └── ansi_up.min.js ├── pages │ ├── 404.md │ ├── about.md │ ├── list.html │ ├── tags.html │ ├── categories.html │ └── index.html ├── _layouts │ ├── page.html │ ├── notebook.html │ ├── post.html │ ├── blank.html │ └── default.html └── _includes │ ├── tongji.js │ ├── init.html │ ├── footer.html │ └── echo.js └── _config.yml /_posts/database/mysql.md: -------------------------------------------------------------------------------- 1 | lqcG,H-w1_d; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Notebook 2 | 3 | 我的编程笔记本,记录平时所学编程相关的知识,便于随时查阅。 4 | -------------------------------------------------------------------------------- /site/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w4096/notebook/HEAD/site/assets/images/favicon.ico -------------------------------------------------------------------------------- /site/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w4096/notebook/HEAD/site/assets/images/loading.gif -------------------------------------------------------------------------------- /site/pages/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: 出错啦! 4 | permalink: /404.html 5 | --- 6 | 7 | 此页面还没开发出来,看看别的页面吧先... :) 8 | -------------------------------------------------------------------------------- /site/assets/css/text.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @charset "utf-8"; 5 | 6 | @import 'var'; 7 | @import 'typo'; 8 | @import "m-site"; 9 | @import "print"; -------------------------------------------------------------------------------- /_posts/machine_learning/2019-03-03-titanic.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: Kaggle - 泰坦尼克号 4 | category: 机器学习 5 | file: "notebook/titanic.ipynb" 6 | --- -------------------------------------------------------------------------------- /_posts/python/2018-11-02-pandas.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: Pandas 操作指南 4 | category: Python 5 | tags: ['编程'] 6 | file: notebook/pandas.ipynb 7 | --- -------------------------------------------------------------------------------- /_posts/python/2019-09-21-sparse-matrix.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: 稀疏矩阵 - COO, CSR, CSC 4 | category: Python 5 | file: notebook/sparse-matrix.ipynb 6 | --- -------------------------------------------------------------------------------- /_posts/machine_learning/2019-10-01-linear-regression.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: 机器学习 - 线性回归 4 | category: 机器学习 5 | file: notebook/002-linear-regression.ipynb 6 | --- -------------------------------------------------------------------------------- /_posts/machine_learning/2019-10-03-classification.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: 机器学习 - 分类问题 4 | category: 机器学习 5 | file: notebook/004-classification.ipynb 6 | --- 7 | -------------------------------------------------------------------------------- /_posts/rec/2019-09-10-ncf-code.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: Neural Collaborative Filtering 实现 4 | category: 推荐系统 5 | tags: ['推荐系统'] 6 | file: notebook/NCF.ipynb 7 | --- -------------------------------------------------------------------------------- /_posts/rec/2019-09-12-fm-family-code.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: FM, FFM, DFM, NFM, Wide & Deep 实现 4 | category: 推荐系统 5 | tags: ['推荐系统'] 6 | file: notebook/FM.ipynb 7 | --- -------------------------------------------------------------------------------- /_posts/machine_learning/2019-10-02-logistics-regression.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: 机器学习 - 逻辑回归 4 | category: 机器学习 5 | file: "notebook/003-logistics-regression.ipynb" 6 | --- -------------------------------------------------------------------------------- /_posts/python/2018-10-06-python-snippets.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: Python 代码片段 4 | category: Python 5 | tags: ['Python','编程'] 6 | file: notebook/python_snippets.ipynb 7 | --- -------------------------------------------------------------------------------- /_posts/machine_learning/2019-09-11-cat-in-the-dat.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: notebook 3 | title: Kaggle - Categorical Feature Encoding Challenge 4 | category: 机器学习 5 | file: "notebook/cat-in-the-dat.ipynb" 6 | --- -------------------------------------------------------------------------------- /site/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | 10 | {{ content }} 11 | -------------------------------------------------------------------------------- /site/_includes/tongji.js: -------------------------------------------------------------------------------- 1 | var _hmt = _hmt || []; 2 | (function() { 3 | if(location.hostname == 'wangyu.name'){ 4 | var hm = document.createElement("script"); 5 | hm.src = "https://hm.baidu.com/hm.js?4f64d9d3a91e47b8d7e045083769fe7f"; 6 | var s = document.getElementsByTagName("script")[0]; 7 | s.parentNode.insertBefore(hm, s); 8 | } 9 | })(); 10 | -------------------------------------------------------------------------------- /site/assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @charset "utf-8"; 5 | 6 | @import 'var'; 7 | @import 'common'; 8 | @import 'typo'; 9 | @import 'm-header'; 10 | @import 'm-footer'; 11 | @import 'm-list'; 12 | @import "m-post"; 13 | @import "m-home"; 14 | @import "m-icon"; 15 | @import "highlight"; 16 | @import "m-site"; 17 | @import "print"; 18 | @import "m-enhance"; 19 | @import "notebook"; -------------------------------------------------------------------------------- /site/_includes/init.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/assets/css/scss/m-enhance.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | .language-chart{ 4 | display: none; 5 | } 6 | 7 | .chart{ 8 | max-width: 800px; 9 | margin: 2em auto; 10 | } 11 | 12 | 13 | .task-list-item{ 14 | list-style: none; 15 | } 16 | .task-list-item-checkbox{ 17 | margin: 0 .5em 0 -1.4em; 18 | vertical-align: middle; 19 | width: 1em; 20 | height: 1em; 21 | } -------------------------------------------------------------------------------- /site/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | {%if page.issue %} 12 | 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /site/pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 关于 4 | nav: about 5 | permalink: /about/ 6 | --- 7 | 8 | 14 | 15 |
16 | 17 | 这是我的编程笔记,主要方便自己随时查阅。 18 | 19 | 20 | 通过下面链接可以找到我: 21 | 22 | - => GitHub:[WangYu](https://github.com/wy-ei) 23 | - => 豆瓣:[wy](https://www.douban.com/people/wy_ei) 24 | - => 邮箱:[wangyu_it@yeah.net](mailto:wangyu_it@yeah.net) -------------------------------------------------------------------------------- /site/assets/css/scss/print.scss: -------------------------------------------------------------------------------- 1 | @media print { 2 | .m-header, 3 | .m-footer, 4 | .m-tools, 5 | .m-to-top{ 6 | display: none; 7 | } 8 | .m-main{ 9 | padding: .2rem; 10 | } 11 | .m-post .typo{ 12 | a{ 13 | color: #444; 14 | border-bottom: 1px solid #555; 15 | } 16 | color: #444; 17 | h3 { 18 | border-bottom: 0.25pt dashed #999!important; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /site/pages/list.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: 随笔 4 | description: 文章列表 5 | permalink: /list/ 6 | nav: list 7 | --- 8 | 9 | 10 |
11 | 20 |
21 | -------------------------------------------------------------------------------- /site/assets/css/scss/var.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | $active-color: #e50053; 4 | $font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, "Hiragino Sans GB", "Microsoft Yahei", 微软雅黑, STHeiti, 华文细黑, sans-serif; 5 | $code-font-family: "SF Mono", Menlo, Consolas, "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace; 6 | $border-color: #ccc; 7 | 8 | $title-color: #000; 9 | $content-color: #000; 10 | $secondary-color: #555; 11 | $gray-background-color: #f3f3f3; 12 | 13 | $content-font-size: .14rem; -------------------------------------------------------------------------------- /site/_layouts/notebook.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | notebook: true 4 | --- 5 | 6 |
7 | {% if page.file %} 8 | 11 | {% else %} 12 |
一定要提供 file 参数
13 | {% endif %} 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /site/assets/css/scss/m-tools.scss: -------------------------------------------------------------------------------- 1 | .m-tools{ 2 | position: fixed; 3 | bottom: .4rem; 4 | right: .1rem; 5 | transform: translateZ(0); 6 | } 7 | 8 | .m-tools .btn{ 9 | display: block; 10 | width: .3rem; 11 | text-align: center; 12 | height: .3rem; 13 | border-radius: .05rem; 14 | background-color: rgba(170, 170, 170 , 0.5); 15 | line-height: .3rem; 16 | cursor: pointer; 17 | } 18 | 19 | .m-tools .btn:first-of-type{ 20 | margin-bottom: 1em; 21 | font-weight: 900; 22 | } 23 | 24 | @media screen and (max-width: 400px) { 25 | .m-tools{ 26 | display: none; 27 | } 28 | } -------------------------------------------------------------------------------- /_posts/network/2015-08-31-http-headers.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: HTTP 常见头部字段 4 | category: 网络 5 | --- 6 | 7 | * toc 8 | {:toc} 9 | 10 | 11 | ## 持久连接 12 | 13 | HTTP 协议需要采用 TCP 做数据传输,在 HTTP 的早期版本中,每次 HTTP 通信都要建立一次 TCP 连接。而 TCP 连接需要三次握手,这大大降低了 HTTP 传输的性能。 14 | 15 | 在 Web 发展的早期,网页中的资源比较少,常常只有一个 html 页面,所以这一矛盾体现的不明显。后来,一个网页中包含几十个图片那是常有的事情,每个资源都需要建立一次 TCP 连接,就相当浪费。 16 | 17 | 解决方法也能直观,多个 HTTP 请求可以用单个 TCP 连接进行传输。这要求服务器在传输完资源之后,不要断开,而是保持 TCP 连接,因为客户端会利用此 TCP 连接发送第二个 HTTP 请求。 18 | 19 | 在 `HTTP/1.0` 和 `HTTP/1.1` 中开始支持这种持久连接。其中 `HTTP/1.0` 中需要显式地指定头部: 20 | 21 | ``` 22 | Connection: Keep-Alive 23 | ``` 24 | 25 | 才能开启持久连接,在 `HTTP/1.1` 中默认开启持久连接。持久连接需要服务器提供支持,客户端在请求中要求持久连接,即设置 `Keep-Alive`,如果服务器支持持久连接,服务器也就会设定持久连接。 -------------------------------------------------------------------------------- /site/assets/css/scss/m-site.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | 4 | // 默认为手机屏幕,手机上不需要很大的字 5 | html { 6 | font-size: 100px; 7 | background: #fff; 8 | } 9 | 10 | body{ 11 | font-size: $content-font-size; 12 | position: relative; 13 | color: $content-color; 14 | min-height: 100vh; 15 | } 16 | 17 | .wrap { 18 | margin: auto; 19 | width: 900px; 20 | } 21 | 22 | @each $max-width, $width in (1400px: 900px, 1023px: 80%, 768px: 95%) { 23 | @media screen and (max-width: $max-width) { 24 | .wrap { 25 | width: $width; 26 | } 27 | } 28 | } 29 | 30 | @media screen and (max-width: 600px) { 31 | body { 32 | font-size: .14rem; 33 | } 34 | } 35 | 36 | /* container layout */ 37 | .m-main { 38 | padding-bottom: 2rem; 39 | } -------------------------------------------------------------------------------- /site/pages/tags.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: 标签 4 | description: 文章标签 5 | permalink: /tags/ 6 | --- 7 | 8 | 9 | 10 | 11 |
12 | {% for tag in site.tags reversed %} 13 | {{ tag[0] }} 14 | {% endfor %} 15 |
16 | 17 | 18 |
19 | {% for tag in site.tags reversed %} 20 |
21 |

{{ tag | first }}

22 | 31 |
32 | {% endfor %} 33 | 34 | -------------------------------------------------------------------------------- /site/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 | 19 |
20 | {% if page.cover %} 21 |
22 | 23 |
24 | {% endif %} 25 | {{ content }} 26 |
27 |
-------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | encoding: utf-8 2 | markdown: kramdown 3 | kramdown: 4 | input: GFM 5 | syntax_highlighter: rouge 6 | syntax_highlighter_opts: 7 | disable: true 8 | toc_levels: 2..3 9 | 10 | permalink: /:year/:slug/ 11 | 12 | layouts_dir: site/_layouts 13 | includes_dir: site/_includes 14 | 15 | future: true 16 | 17 | sass: 18 | sass_dir: /site/assets/css/scss 19 | style: compressed 20 | 21 | 22 | title: WangYu's Notebook 23 | slogan: 好记性不如烂笔头 24 | 25 | images: https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/ 26 | 27 | base_path: "/notebook" 28 | home_path: "/notebook/" 29 | categories_path: /notebook/categories/ 30 | list_path: /notebook/list/ 31 | tags_path: /notebook/tags/ 32 | about_path: /notebook/about/ 33 | 34 | assets_path: /notebook/site/assets 35 | 36 | base_url: "https://wy-ei.github.io/notebook/" 37 | excerpt_separator: "" 38 | author : 39 | name : wy-ei 40 | email : wangyu_it@yeah.net 41 | github : wy-ei 42 | 43 | -------------------------------------------------------------------------------- /site/pages/categories.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: 分类 4 | nav: category 5 | description: 文章分类 6 | permalink: /categories/ 7 | --- 8 | 9 | 10 | 11 |
12 | {% for category in site.categories %} 13 | {{ category | first }} ({{ category | last | size }}) 14 | {% endfor %} 15 |
16 | 17 | 18 |
19 | {% for category in site.categories reversed %} 20 |
21 |

{{ category | first }}

22 | 31 |
32 | {% endfor %} 33 | -------------------------------------------------------------------------------- /_posts/network/2016-08-25-arp.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: ARP 协议工作原理 4 | category: 网络 5 | --- 6 | 7 | 8 | ARP (Address Resolution Protocol) 协议用于完成网络地址到硬件地址的转换,网络地址通常指 IP 地址,硬件地址通常为 MAC 地址。 9 | 10 | 当 IP 分组要发向下一跳时,需要借助于链路层,IP 分组需要加上链路层的头部,比如以太网头部,这其中就包含 MAC 地址,因此网络层需要知道目的 IP 对应的 MAC 地址是多少。 11 | 12 | 一种直观的想法是,直接向当前链路上的设备询问,比如查询 192.169.1.100 的 MAC 地址,那就在链路层上发送广播,问谁的 IP 地址是 192.168.1.100,请把你的 MAC 地址告诉我。 13 | 14 | ARP 请求就是这么做的,要在链路上广播,目的 MAC 地址是 ff:ff:ff:ff:ff:ff,这样链路上所有机器都能接收到,这些机器拿自己的 IP 地址和 ARP 请求报文中的 IP 地址比较,如果匹配那就发送应答。 15 | 16 | 下面考虑一个例子: 17 | 18 | 下图中有三台计算机,A 和 B 处于同一网络,C 处于另一个网络。 19 | 20 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/04/21/5e9e76f1c2a9a83be5449c86.jpg) 21 | 22 | 当 A 要向 B 发送 IP 分组时,需要知道 B 的 MAC 地址,此时 A 使用 B 的 IP 地址发送 ARP 请求,该请求以广播形式发送,B 收到后,发现有人在查询自己的 MAC 地址,此时会发送 ARP 响应,目的地址为 A 的 MAC 地址。 23 | 24 | 如果 A 要向 C 发送 IP 分组呢?因为 A 知道 C 的 IP 地址,且知道它处于另一个网络中,因此需要把 IP 分组发到路由器上,因此查询的 MAC 地址就是路由器的地址了。而 A 的路由表中是有路由器的 IP 地址的,因此使用路由器的 IP 地址查询 MAC 地址就可以了。 25 | 26 | 通常计算机上都有 ARP 缓存,缓存着最近查询过的 IP-MAC 对,这样就不需要每次都发送 ARP 请求来查询了。 -------------------------------------------------------------------------------- /_posts/network/log.md: -------------------------------------------------------------------------------- 1 | ## syslog 2 | 3 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/04/23/5ea10854c2a9a83be587be01.jpg) 4 | 5 | 配置文件: 6 | 7 | ``` 8 | /etc/rsyslog.d/ 9 | 10 | 11 | /var/log/ 12 | ``` 13 | 14 | 配置文件格式: 15 | 16 | ``` 17 | facility.level action 18 | 19 | *.=debug;\ 20 | auth,authpriv.none;\ 21 | news.none;mail.none -/var/log/debug 22 | *.=info;*.=notice;*.=warn;\ 23 | auth,authpriv.none;\ 24 | cron,daemon.none;\ 25 | mail,news.none -/var/log/messages 26 | ``` 27 | 28 | 29 | 30 | ```c++ 31 | openlog(argv[0], 0, LOG_DEBUG); 32 | 33 | // 用于设置哪些级别的日志会被记录 34 | // 多个级别可以使用与操作一次传入 35 | setlogmask(LOG_INFO | LOG_ERR); 36 | 37 | syslog(LOG_ERR, "hello %s\n", "syslog"); 38 | ``` 39 | 40 | ## 用户信息 41 | 42 | 用户 ID(UID)和有效用户 ID(EUID) 都是什么?一个进程拥有两个用户 ID,UID 就是运行该进程的用户 ID,有效用户 ID 则是用于方便控制资源的访问。当一个可执行程序设置了 `set-user-id` 标志时,运行该程序时,该进程的有效用户 ID 就是程序拥有者的 ID。 43 | 44 | 比如 `su` 程序,他的文件拥有是 root,其他用户执行该程序的时候,进程里的用户 ID 就是执行该程序的用户的 ID,由于该程序设置了 `set-user-id` 标志,因此在执行该程序的时候,有效用户 ID 就是 root。 45 | 46 | ## 守护进程 47 | 48 | -------------------------------------------------------------------------------- /_posts/unix/time.md: -------------------------------------------------------------------------------- 1 | ## 时间处理 2 | 3 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/04/10/5e903930504f4bcb0483380d.jpg) 4 | 5 | 6 | ## 定时器 7 | 8 | ### 间隔定时器 9 | 10 | 11 | ### 给阻塞操作设置超时 12 | 13 | 为了避免某些阻塞操作长时间阻塞,我们希望能够给他添加一个超时时间。但超时时间并不是所有接口都有的,比如 `select` 就可以设置超时时间,但是 `read` 就不行。不过我们可以人为地加入对超时的支持。 14 | 15 | 由于阻塞的系统调用会被信号处理程序中断,因此在调用某阻塞操作之前,可以设置一个定时器,当定时器超时后,阻塞的函数调用就会返回,通常为 `-1`,且设置错误码 `errno` 为 `EINTR`。因为定时器超时的默认动作是终止进程,因此需要修改默认的信号处理程序。 16 | 17 | ```c++ 18 | static void do_nothing(int signum){ 19 | } 20 | 21 | void example(){ 22 | struct sigaction sa{}; 23 | sa.sa_flags = 0; 24 | sigemptyset(&sa.sa_mask); 25 | sa.sa_handler = do_nothing; 26 | sigaction(SIGALRM, &sa, nullptr); 27 | 28 | char buf[BUFSIZ]; 29 | 30 | alarm(10); // timeout after 10s 31 | int n = read(STDIN_FILENO, buf, BUFSIZ-1); 32 | alarm(0); // turn off timer 33 | 34 | if(n == -1){ 35 | if(errno == EINTR){ 36 | fprintf(stderr, "timeout for read\n"); 37 | }else{ 38 | fprintf(stderr, "read fail: %s\n", strerror(errno)); 39 | } 40 | }else{ 41 | buf[n] = '\0'; 42 | printf("read success. %s\n", buf); 43 | } 44 | } 45 | ``` -------------------------------------------------------------------------------- /_posts/cs/2016-05-19-use-summator-do-subtraction.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 使用加法器完成减法 4 | category: 理解计算机 5 | published: false 6 | --- 7 | 8 | 在大学的数字电路课程中,学习过一种叫做加法器的东西,它可以用来完成加法运算。其本质上是采用了与非门来实现的。 9 | 10 | 加法器可以用来做加法,那么减法是如何实现的呢? 11 | 12 | 在减法运算中存在借位的问题,借位似乎不像进位那样容易实现,不过可以采用适当的方式让减法运算也不需要借位。看下面的推导: 13 | 14 | ``` 15 | 213 - 168 = 213 + 999 - 168 - 999 16 | ``` 17 | 18 | 以上式子可以转换为: 19 | 20 | ``` 21 | 213 - 168 = 213 + 999 - 168 + 1 - 1000 22 | ``` 23 | 24 | 进一步推导: 25 | 26 | ``` 27 | 213 - 168 28 | = 213 + 999 - 168 + 1 - 1000 29 | = 213 + 831 + 1 - 1000 30 | = 1044 + 1 - 1000 31 | = 45 32 | ``` 33 | 34 | 给原式中加上 999 + 1 - 1000,这样 `999 - 168` 不存在借位,因为 `999` 中的各位都是最大的。最后 `-1000` 只有最高位上可能存在借位(差为负的时候)。 35 | 36 | ## 二进制减法 37 | 38 | 将上面的数转换为二进制: 39 | 40 | 对于二进制,8 位二进制最大的数为 1111 1111。 41 | 42 | ``` 43 | 1101 0101 - 1010 1000 = 1101 0101 + 1111 1111 - 1010 1000 + 1 - 1 0000 0000 44 | ``` 45 | 46 | 其实 `1111 1111 - 1010 1000` 的结果就是将减数的 0 和 1 调换一下,因此得到结果 `0101 0111`,之后的步骤,如十进制中一样: 47 | 48 | 将被减数与以上结果相加: 49 | 50 | ``` 51 | 1101 0101 + 0101 0111 = 1 0010 1100 52 | ``` 53 | 54 | 将结果加 1 55 | 56 | ``` 57 | 1 0010 1100 + 1 = 1 0010 1101 58 | ``` 59 | 60 | 将结果减去 `1 0000 0000` 61 | 62 | ``` 63 | 1 0010 1101 - 1 0000 0000 = 10 1101 64 | ``` 65 | -------------------------------------------------------------------------------- /_posts/unix/open.md: -------------------------------------------------------------------------------- 1 | ## 原子操作和竞争条件 2 | 3 | 多个进程向文件尾部追加内容,如果使用下面的代码,就会出现 bug,因为在执行完 `lseek` 之后,有可能另一个进程执行了写操作,当前进程随后的 `write` 操作就会覆盖另一个进程写入的内容。 4 | 5 | ```c++ 6 | if(lseek(fd, 0, SEEK_END) == -1){ 7 | errExit("lseek"); 8 | } 9 | if(write(fd, buf, len) != len){ 10 | fatal("Partial/failed write"); 11 | } 12 | ``` 13 | 14 | 在打开文件的时候,使用 `O_APPEND` 标志,可以保证写入操作是以追加的形式进行的。 15 | 16 | 17 | ## `/dev/fd` 目录 18 | 19 | 对于每个进程,内核都提供一个特殊的虚拟目录,其中包含 `/dev/fd/n` 形式的文件名,这里的文件 n,就是文件描述符 n 所指的文件。 20 | 21 | ```c++ 22 | fd = open("/dev/fd/1", O_WRONLY); 23 | fd = dup(1); 24 | ``` 25 | 26 | 以上两行代码是等价的。 27 | 28 | ## 创建临时文件 29 | 30 | 有些程序需要的运行时创建一些临时文件,在程序运行完毕后立刻删除。 31 | 32 | ```c++ 33 | #include 34 | 35 | int mkstemp (char *__template) 36 | ``` 37 | 38 | 其用法如下: 39 | 40 | ```c++ 41 | char tmp[] = "/tmp/file_XXXXXX"; 42 | int fd = mkstemp(tmp); 43 | printf("generated filename was: %s\n", tmp); 44 | unlink(tmp); 45 | 46 | /* use file */ 47 | 48 | close(fd); 49 | ``` 50 | 51 | 传入的文件名是一个模板,最后 6 个字符一定是 `XXXXXX`,系统将会修改这 6 个字符,构造出一个唯一的文件名,并创建文件并打开,返回文件描述符。因为该函数会修改参数,因此参数不能是字符串常量。 52 | 53 | 为了避免其他用户和进程看到此文件,在创建完成后可以立刻调用 `unlink`,该文件就会立刻被从文件系统中删除,但只有再 close 的时候,其引用的 inode 才会被删除。在 close 之前,内存中存有文件表,可以正常使用该文件。 54 | 55 | 另一个函数是: 56 | 57 | ```c++ 58 | #include 59 | FILE *tmpfile(); 60 | ``` 61 | 62 | 该函数打开一个以读写方式打开的流,在关闭流的时候,文件会自动删除。 -------------------------------------------------------------------------------- /_posts/algorithm/2015-03-01-pow-no-recurtion.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 非递归求幂 4 | category: 算法 5 | --- 6 | 7 | 求解一个数的 N 次幂,通常使用 pow 函数。要求 `a^N`,最最显而易见的方法是用 N 个 a 连续做乘法,幸运的是,我们有更快的算法。 8 | 9 | 最常见的一种方法是采用递归的求解方式,如下: 10 | 11 | ### 递归解法: 12 | 13 | ```c 14 | long int pow(int x, unsigned int N){ 15 | if (N == 0){ 16 | return 1; 17 | } 18 | if (N == 1){ 19 | return x; 20 | } 21 | if (N & 1 == 0){ 22 | return pow(x * x, N / 2); 23 | } else { 24 | return pow(x * x, N / 2) * x; 25 | } 26 | } 27 | ``` 28 | 29 | 这个算法的复杂度无疑是 `O(logN)` 的。 30 | 31 | 下面不采用递归来求解: 32 | 33 | ### 非递归解法: 34 | 35 | ```c 36 | long int pow(int x, unsigned int N){ 37 | int ans, n; 38 | ans = 1; 39 | n = x; 40 | while (N != 0){ 41 | if (N & 1 == 1){ 42 | ans = ans * n; 43 | } 44 | n = n * n; 45 | N >> 1; 46 | } 47 | return ans; 48 | } 49 | ``` 50 | 51 | 下面来分析一下以上算法的原理: 52 | 53 | 54 | 先举个例子,假如希望求 `5^62`,也就是 5 的 62 次方。为了算法高效,一个原则就是不做重复的计算。 55 | 56 | ``` 57 | 5^62 = 5^(32+16+8+4+2) = 5^32 * 5^16 * 5^8 * 5^4 * 5^2 58 | ``` 59 | 60 | 不管指数是多少,都可以将其分解为 2 的倍数的和,因为任何整数都能够写成 2 进制的形式,比如 `62 = 00111110B`。 61 | 62 | 以上算法中,随着迭代 n 会变成 x, x^2, x^4, x^8,...,我们只需要在合适的时候让它和 ans 相乘即可。合适的时刻就是 N 的二进制表示的相应位上为 1 的时候,这里使用了右移,只需要判断最低位是不是 1 就好了。 63 | 64 | 这个算法也是 `O(logN)` 的,但是没有使用递归。 65 | -------------------------------------------------------------------------------- /site/assets/css/scss/m-footer.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | .m-footer { 4 | color: #333; 5 | background-color: #f3f3f3; 6 | font-size: 0.8em; 7 | border-top: 1px solid #dedede; 8 | position: absolute; 9 | bottom: 0; 10 | left: 0; 11 | right: 0; 12 | .link{ 13 | line-height: 1.2em; 14 | margin-bottom: 1.2em; 15 | h2{ 16 | font-size: .18rem; 17 | color: inherit; 18 | padding-bottom: .04rem; 19 | margin-bottom: .1rem; 20 | } 21 | a{ 22 | display: inline-block; 23 | margin-right: .2rem; 24 | } 25 | } 26 | a{ 27 | color: inherit; 28 | border-bottom: 1px solid #999; 29 | &:hover{ 30 | color: $active-color; 31 | } 32 | } 33 | .copyright{ 34 | color: inherit; 35 | padding: .15rem 0; 36 | p{ 37 | height: .15rem; 38 | line-height: .15rem; 39 | } 40 | } 41 | } 42 | 43 | .m-to-top{ 44 | position: fixed; 45 | bottom: 60px; 46 | right: 20px; 47 | cursor: pointer; 48 | background: #eee; 49 | border-radius: 3px; 50 | width: 30px; 51 | height: 30px; 52 | line-height: 30px; 53 | text-align: center; 54 | z-index: 10000; 55 | } 56 | 57 | .m-to-top:hover{ 58 | background: #999; 59 | } -------------------------------------------------------------------------------- /site/assets/css/scss/m-home.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | 4 | .home{ 5 | padding-top: 20px; 6 | @at-root .article{ 7 | padding-bottom: .25rem; 8 | margin-bottom: .70rem; 9 | &__title{ 10 | font-size: .20rem; 11 | font-weight: 400; 12 | margin-bottom: .1rem; 13 | } 14 | 15 | &__info{ 16 | height: .15rem; 17 | font-size: .12rem; 18 | color: $secondary-color; 19 | } 20 | 21 | &__picture{ 22 | margin: 16px 0; 23 | img{ 24 | width: 100%; 25 | } 26 | } 27 | 28 | &__excerpt{ 29 | margin: 16px 0 0; 30 | line-height: 1.8em; 31 | word-break: break-all; 32 | } 33 | 34 | &__excerpt:empty{ 35 | display: none; 36 | } 37 | 38 | &__more{ 39 | margin-top: .15rem; 40 | display: inline-block; 41 | border-bottom: 1px solid; 42 | } 43 | 44 | } 45 | 46 | .more--article-list{ 47 | margin-top: .40rem; 48 | display: inline-block; 49 | } 50 | 51 | 52 | } 53 | 54 | 55 | .banner{ 56 | border-radius: 7px; 57 | height: 460px; 58 | margin: 18px auto; 59 | box-shadow: rgba(0,0,0,.08) 0 1px 3px; 60 | border: 1px solid rgba(0,0,0,.14); 61 | } -------------------------------------------------------------------------------- /site/assets/css/scss/m-header.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /* header layout */ 4 | .m-header { 5 | position: relative; 6 | width: 100%; 7 | margin-bottom: 1rem; 8 | background-color: #f3f3f3; 9 | 10 | 11 | .site-title{ 12 | padding: .3rem 0 .4rem 0; 13 | } 14 | .site-name{ 15 | font-size: .22rem; 16 | margin-bottom: .2rem; 17 | } 18 | .site-slogan{ 19 | font-size: .15rem; 20 | } 21 | a{ 22 | color: inherit; 23 | display: inline-block; 24 | &:hover,&.active{ 25 | color: $active-color; 26 | } 27 | } 28 | .site-nav{ 29 | padding-bottom: .1rem; 30 | ul{ 31 | list-style: none; 32 | } 33 | li { 34 | display: inline; 35 | margin-right: .2rem; 36 | } 37 | i { 38 | padding-right: .5em; 39 | } 40 | } 41 | } 42 | 43 | @media screen and (max-width: 768px) { 44 | .m-header { 45 | .site-nav{ 46 | li { 47 | a{ 48 | padding: 5px; 49 | } 50 | margin-right: .2rem; 51 | } 52 | } 53 | } 54 | } 55 | 56 | @media screen and (max-width: 430px) { 57 | .m-header { 58 | .site-nav{ 59 | li { 60 | margin-right: .1rem; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /_posts/algorithm/2017-06-09-newtons-method.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 牛顿迭代法求解平方根 4 | category: 算法 5 | --- 6 | 7 | ## 牛顿迭代法 8 | 9 | 对于一元 N 次方程,当 N 大于 2 时没有固定的求根公式,为了求方程的根,可以使用牛顿迭代法。 10 | 11 | 牛顿迭代法的思想是在曲线上任意取一个点,然后求这一点的切线,使用切线的解来逼近多项式的解。 12 | 13 | 14 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/12/03/5fc872aa394ac523781e5357.jpg) 15 | 16 | 17 | 然后在 $x_{n+1}$ 处继续做切线: 18 | 19 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/12/03/5fc87308394ac523781e7e87.jpg) 20 | 21 | 不断的逼近,可以看到上图中切线在 x 轴上的交点 $x_{n+1}$ 已与真实的解 $x_n$ 更近了一些。 22 | 23 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/06/13/5d026c56451253d178402e69.jpg) 24 | 25 | 这个过切点的直线的方程为: 26 | 27 | $$y-f(x_n)=f^\prime(x_n)(x-x_n)$$ 28 | 29 | 令 $y=0$ 可以求得 $x$,这里 $x_{n+1}$ 与 $x_n$ 的关系如下: 30 | 31 | $$x_{n+1}=x_{n}-\frac{f(x_n)}{f^\prime(x_n)}$$ 32 | 33 | 其中 $f^\prime(x_n)$ 表示 $f(x)$ 在 $x_n$ 处的斜率。 34 | 35 | ## 使用牛顿迭代法求平方根 36 | 37 | 求 $N$ 的平方根,可以理解为求如下函数的解: 38 | 39 | $$f(x)=x^2-N$$ 40 | 41 | 其中 $f(x)$ 的导数为: 42 | 43 | $$f^\prime(x)=2*x$$ 44 | 45 | 牛顿迭代式为: 46 | 47 | $$x_{n+1}=x_n-\frac{x_{n}^2-N}{2*x_n}=\frac{1}{2}*(x_n+\frac{N}{x_n})$$ 48 | 49 | 利用以上原理可以写出下面代码: 50 | 51 | ```python 52 | def sqrt(n): 53 | if n < 0: 54 | return float('nan') 55 | 56 | # 因为牛顿迭代法只是逼近真实值,所以需要设置一个误差范围 57 | e = 1e-15 58 | 59 | x = n 60 | x_next = (x + n / x) / 2 61 | 62 | # 两次迭代得到的解之间相差小于误差允许范围后跳出 63 | while abs(x_next - x) > e: 64 | x = x_next 65 | # 计算下一个近似解 66 | x_next = (x + n / x) / 2 67 | 68 | return x 69 | ``` -------------------------------------------------------------------------------- /site/pages/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | description: 博客首页 4 | nav: home 5 | permalink: / 6 | --- 7 | 8 | 9 |

10 | 我的编程笔记,记录平时学到的编程知识,便于自己随时查阅。 11 |

12 | 13 |
14 |

分类

15 | {% for category in site.categories %} 16 | {% assign post = category | last | last %} 17 | {{ category | first }} ({{ category | last | size }}) 18 | {% endfor %} 19 |
20 | 21 | 22 |

最近发布

23 | 33 | 34 | 35 |
36 | {% for category in site.categories reversed %} 37 | {% assign post = category | last | last %} 38 | 39 |
40 |

{{ category | first }}

41 | 50 |
51 | {% endfor %} 52 | -------------------------------------------------------------------------------- /_posts/machine_learning/2019-03-20-cnns.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 多种多样的卷积 4 | category: 机器学习 5 | --- 6 | 7 | 8 | - * 9 | {:toc} 10 | 11 | ## 多输入通道 12 | 13 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/05/12/5cd7e1253a213b04175f126b.jpg) 14 | 15 | 此图引用自 [动手学习深度学习](https://zh.gluon.ai/chapter_convolutional-neural-networks/channels.html) 16 | 17 | 对一个二维张量做卷积操作,很容易理解。当输入是多通道时,比如 RGB 三通道的图片,卷积核作用在三个通道上就会产生三个输出。如果有 10 个卷积核,岂不是有 30 个输出了。这是我最初的想法,后来发现事实并非如此。对于三通道的输入,`5*5` 的卷积核,其实会含有 `5*5*3=75` 个参数,也就是说卷积核也是三个通道,三通道的卷积核在三通道的输入上上下左右移动着做 element-wise 的相乘并求和,最终的结果就是一个一通道的输出。最后再在这个输出上加一个 bias。这就是多输入通道时,卷积核工作方式。 18 | 19 | 所以其实这里的卷积核仔细想来,应该是三维的。这样的卷积才能够捕捉到不同通道之间的关系。 20 | 21 | ## 多输出通道 22 | 23 | 如果输入是 3 通道,卷积核大小为 `5*5`,输出是 10 通道,这种情况该怎么办?根据前面对多输入通道卷积工作过程的描述,就不难理解,输出为 10 通道,上例中只要有 10 个核就可以了。 24 | 25 | ## 1D 卷积 26 | 27 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/05/12/5cd7d5893a213b04175e3e13.jpg) 28 | 29 | 1D 卷积,输入数据是一维的,他可以有多个通道,但每个通道都是一维的。因此一维卷积的卷积核也是一维的,它在输入数据上做一维的移动,并做卷积。最终多个通道相加,得到一个一维的输出。 30 | 31 | ## 1x1 卷积 32 | 33 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/05/12/5cd7d8a63a213b04175e8e0d.jpg) 34 | 35 | 1x1 卷积可以看做是对输入的不同通道做了线性加权求和。 36 | 37 | ## 可分离卷积 38 | 39 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/05/12/5cd7e0be3a213b04175f0e8a.jpg) 40 | 41 | 在 keras 中看到 SeparableConv2D 这个类,直译过来就叫做可分离卷积,所以暂且就这么称呼它了。 42 | 43 | 对常规的 Conv2D 而言,假设输入为 3 通道,卷积核为 `5*5`,输出为 10 通道,那么一共需要 `3*5*5*10+10=760` 个参数,但是对于 `SeparableConv2D` 而言,只需要 `3*5*5 + 10*3 + 10 = 115` 个参数。 44 | 45 | 如上图所示,可分离卷积对输入的 3 个通道做了 layer-wise 的卷积,即一个卷积核对一个输入层对应着来做卷积,得到 3 个输出层。然后并不做加和,这是和通常的卷积核区别,这里使用 2 个 `1x1` 的卷积来对这 3 个中间的输出层做线性组合,最终得到 2 个输出层。可分离卷积输出的通道数是由 `1x1` 卷积核的个数来决定的。 -------------------------------------------------------------------------------- /_posts/unix/2020-01-10-dynamic-memory.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 动态内存分配的实现原理 4 | category: UNIX 编程 5 | --- 6 | 7 | 8 | 动态内存分配通常在堆上进行,堆其实就是一块连续的内存空间。为了实现内存的动态分配与回收,需要提供一组函数来管理这片内存。 9 | 10 | ### 隐式空闲链表 11 | 12 | 为了记录分配的内存的大小,需要使用额外的空间来存储,一种方法是在分配的内存块的前面存放该块的大小。当用户释放了这块内存后,这块内存就可以得到复用,因此还需要记录当前块是否被使用。 13 | 14 | 由于内存块的大小往往需要对齐到 4 字节或 8 字节,因此内存大小的低 2 bit 一定是 0。因此,可以使用这两个 bit 来记录当前块是否被使用。比如最低 bit 为 1,表示该块正在被使用。 15 | 16 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/04/10/5e900a94504f4bcb0458900b.jpg) 17 | 18 | 上图中的这种设计,将内存划分成了一个个块,通过第一块的地址,和块中存放的大小信息,可以得到第二块的地址。在内存分配的时候,可以从第一块开始,寻找一个合适的块,将其交给用户。如果没有已经分配的块可用,就可以在剩余的堆空间中再分配一个块。 19 | 20 | 在选择合适的块时,可以采用多种策略,包括: 21 | 22 | - first fit 23 | - next fit 24 | - best fit 25 | 26 | 这些分配方式最终会将内存划分为一个个块,设想如果将内存全部划分成了 100 字节的块,最后虽然有很多空闲块,但是依然没办法分配一个 200 字节的块。 27 | 28 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/04/10/5e900c3e504f4bcb045a447e.jpg) 29 | 30 | 上图中,每次分配整个空间的一半,两次后,将空间划分为两块,随后需要整个空间那么大的块时,却无法满足。因此,合并空闲的块是非常必要的。 31 | 32 | ### 合并空闲块 33 | 34 | 合并可以发生在释放内存的时候,这叫做立即合并,也可以在下次分配的时候,这叫做推迟合并。在释放时合并,有可能出现合并-划分-再合并的情况。 35 | 36 | 如果是立即合并,在隐式空闲链表的方法中,从前一块访问后一块很容易,但是无法从后一个块访问前一个块。如果当前释放的块的后面一块是空闲的,那么合并很容易进行。但是,如果当前释放的块的前一块时空闲的,而通过当前块是无法获得前一块的信息的,这就很麻烦。 37 | 38 | 一种策略是,在块的尾部添加一个 footer,这和 header 内容一样。这样以来,当前块的 header 前面就是上一个块的 footer,可以通过 footer 信息得到上一块的大小。 39 | 40 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2020/04/10/5e900f69504f4bcb045d0675.jpg) 41 | 42 | 但问题是,每一个块都需要一个 header 和 footer,如果分配大量较小的块,那么单单 header 和 footer 就会占用很大比例的内存。 但其实只有空闲的块是需要 footer,因为如果一个块正在被使用,也用不着合并它。 43 | 44 | 因此,对于使用中的块,不需要 footer 部分,对于未使用的块,可以利用未使用的空间的尾部来放置 footer。在块的 header 部分,可以利用一个未使用的 bit 来表示前一块是否被使用。如果没有被使用,那么 header 前面就是前一块的 footer。 45 | 46 | -------------------------------------------------------------------------------- /_posts/network/2015-08-31-http-semantic.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: HTTP 语义 4 | category: 网络 5 | --- 6 | 7 | * toc 8 | {:toc} 9 | 10 | 11 | 最近做 web 项目发现对 HTTP 的各种方法的语义把握的还是不到位,常见的 HTTP 方法有:GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,另外还有一些不常见的 LINK,UNLINK 等,下面对一些常见的 HTTP 方法的语义进行总结如下: 12 | 13 | 14 | ## GET 15 | 16 | 基于给定的信息或者条件来获取资源。GET 被定义为安全的 HTTP 方法,GET 请求是不应该修改服务器的状态的。 17 | 18 | ## POST 19 | 20 | 基于给定的信息来在当前资源的下一级创建一个新的资源。所以 POST 似乎应该作用于一个集合,但由于 HTML 的表单提交只支持 POST 和 GET 方法,所以 POST 方法的很多运用并不符合这一定义。但是 [HTTP 规范](http://tools.ietf.org/html/rfc2616#section-9.5)中 POST 方法可以具有以下功能: 21 | 22 | + 对现有资源的标注 23 | + 向布告栏,新闻组,邮件列表或者类似的信息的集合发布信息 24 | + 向数据处理流程提供例如表单提交结果的数据块 25 | + 通过追加操作来扩充数据库 26 | 27 | 所以在 form 表单中的提交中使用 POST 也是没有问题的。只是说明一点,POST 方法有时候并不完全符合 `用来创建资源` 这样一个定义。具体的语义要根据实际项目的场景来衡量。 28 | 29 | 30 | ## DELETE 31 | 32 | 销毁一个资源。当客户端希望让一个资源消失的时候,可以发起一个 DELETE 请求来将服务器中的资源销毁。服务器可以决定是否允许删除。 33 | 34 | ## PUT 35 | 36 | 用给定的表述信息替换资源的当前状态。PUT 用于修改资源的状态,服务端会根据用户提供的信息来更新资源的状态,从而让资源的状态和用户描述的一致。同样的 PUT 方法也是幂等的。 37 | 38 | PUT 方法,也可以用来创建资源,但是它与 POST 的区别在于 PUT 方法执行多次只会创建一个资源,后续的 PUT 会覆盖之前的资源,但是 POST 会生成多个副本。 39 | 40 | ## HEAD 41 | 42 | 获取服务器发送过来的报头信息。HEAD 方法的响应不需要发送任何响应实体,只需要发送 HTTP 报头。 43 | 44 | ## OPTIONS 45 | 46 | 获取服务器所能提供的 HTTP 方法列表。OPTIONS 方法用来探索某个资源所支持的所有 HTTP 方法。对于 OPTIONS 的响应的报头中存在一个 Allow 字段,其中列举了服务器支持的方法名。虽然 HTTP 定义了很多种方法,但是服务器可以根据需求支持部分方法。 47 | 48 | ## PATCH 49 | 50 | 根据给定的信息修改资源的部分信息,没有提供的关于资源的状态就保持不变。如果只想更新资源的部分信息,然后使用 PUT 发送全部信息就显得有些浪费,这个时候 PATCH 方法可以允许只提供那部分需要更新的内容。 51 | 52 | ## 补充 53 | 54 | ### 幂等性 55 | 56 | 常常看到人们说幂等这个词语,那么幂等是什么意思呢? 57 | 58 | 如果某个 HTTP 方法作用于一个资源一次或多次该资源的状态都是一致的,那么称该操作是幂等的。幂等这个概念出自数学,一个数乘以 0 一次或者多次,结果都是 0 ,同样的一个数乘以 1 一次或者多次,结果都是等于该数本身。对于 GET 方法,它是幂等的,相当于乘以 1 。而对于 DELETE 方法,它也是幂等的,相当于乘以 0。即对多个资源 GET 和 DELETE 多次效果都是一样的。 59 | -------------------------------------------------------------------------------- /_posts/cpp/2015-04-29-cpp-inherit-type.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: C++ 继承方式 4 | category: C/C++ 5 | --- 6 | 7 | 8 | 9 | ## C++类成员权限 10 | 11 | C++ 中 `class` 和 `struct` 关键词都可以用来定义类,两者唯一的不同是,`class` 定义的类中,类成员访问权限默认是 `private` 的,而 `struct` 是 `public` 的。 12 | 13 | 设置为 `private` 的成员仅能被当前类访问,设置为 `protected` 的成员,可以被子类访问,但是不能被用户访问。设置为 `public` 的成员可以被子类和用户访问。关于 C++ 的类成员的权限控制,就这么多内容。 14 | 15 | ## C++类继承方式 16 | 17 | C++ 在继承的时候,可以采用三种继承方式,`public`、`protected`、 `private`,这会对类成员的访问权限造成影响。 18 | 19 | 首先需要明白,无论派生方式是什么样子的,派生类永远能够访问到基类的公有部分和受保护部分,而永远不能访问基类的私有部分。 20 | 21 | 派生方式影响的是基类的 `public` 和 `protected` 在子类中的表现出来的权限。 22 | 23 | => public 继承(class A: public B) 24 | 25 | 基类中的 `public` 和 `protected` 部分分别成为子类的 `public` 和 `protected` 部分。 26 | 27 | => private 继承(class A: private B) 28 | 29 | 使用 `private` 继承,基类中的 `public` 和 `protected` 部分成为子类中的 `private` 部分。 30 | 31 | => protected 继承(class A: protected B) 32 | 33 | 基类中的 `public` 和 `protected` 部分成为派生类中的 `protected` 部分。 34 | 35 | => 默认的继承保护级别 36 | 37 | 当继承一个类或者结构体时,如果没有注明继承方式,那么默认是什么样的呢? 38 | 39 | 对于 `struct` 和 `class` 我们知道,在定义的时其中的成员如果没有注明访问权限,那么在 `class` 默认是 `private` 的,`struct` 是 `public` 的。在继承的时候如出一辙。没有注明继承方式,如果子类是 `class`,那继承方式就是 `private`,子类是 `struct` 的话,默认方式是 `public`。 40 | 41 | ### 总结 42 | 43 | 继承方式的作用体现在子类的使用者(包括子类的用户(即,使用子类对象的函数)和子类的子类)身上。而对于直接继承自基类的子类而言,派生方式对其没有影响。 44 | 45 | ### 改变个别成员的可访问性 46 | 47 | 但我们使用私有继承的时候,所有继承而来的成员成为子类的私有成员,有时候我们希望其中的某个或者某几个成员能够被子类的用户访问到,此时可以使用 `using` 关键字来改变个别成员的权限。 48 | 49 | ```cpp 50 | class Base{ 51 | public: 52 | int n; 53 | protected: 54 | int size; 55 | }; 56 | 57 | class Derived: private Base{ 58 | public: 59 | using Base::n; 60 | protected: 61 | using Base::size; 62 | } 63 | ``` 64 | 65 | `private` 继承让 `Base` 中的所有成员成为了 `Derived` 的私有成员,但在 `Derived` 中可以明确地修改他们的访问权限。 -------------------------------------------------------------------------------- /_posts/unix/2019-12-18-client-server-pattern.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 客户端/服务器程序设计模式 4 | category: UNIX 编程 5 | hide: T 6 | --- 7 | 8 | - * 9 | {:toc} 10 | 11 | 阅读 [UNP](https://book.douban.com/subject/1500149/) 的时候了解到了多种客户端/服务器程序设计的范式,这些模式很容易理解。由于本书写于20多年前,目前大部分模式都已经过时了。不过现在流行的模式自然是在此基础上演化而来的,因此有必要了解一下这些模式。 12 | 13 | 所谓客户/服务器模式,就是我们常说的 C/S 模式,客户端和服务器基于网络进行通信,通常基于 TCP 协议。本文中总结了常见的客户端/服务器程序设计模式。 14 | 15 | ## 0. 迭代服务器 16 | 17 | 收到一个请求后就同步地处理它,在未处理完之前不能处理其他的请求。这就是最简单的迭代服务器,这种模式只适用于单个请求耗时很少的场景,比如下面的例子中,接收到请求后服务器返回当前的世界,而后立刻断开连接。 18 | 19 | ## 1. 为每个连接创建子进程 20 | 21 | 如果每个请求都需要消耗不少时间才能处理完,那么在处理一个请求的时候,其他客户的请求就被会阻塞。可以为每个请求创建一个进程,用该进程来处理该请求。为每个新的连接创建进程,连接完成后会销毁进程,这导致进程频繁地创建与销毁,存在不少系统开销。 22 | 23 | ## 2. 创建多个子进程,在子进程中 `accept` 24 | 25 | 在多个进程中进行 `accept` 操作,新来一个请求最终只会被单个进程成功 `accept`,这样就能够把多个请求分散到不同的进程中。 26 | 27 | 在主进程中创建多个进程,各个进程都执行 `accept` 操作,新的连接到来后,所有子进程在同一个 `listen_fd` 上执行 `accept` 操作的子进程均被唤醒,但只有最快运行的那个子进程能够 `accept` 成功,该子进程就负责处理此次连接。 28 | 29 | 缺点是单个连接会导致多个子进程被唤醒,如果子进程较多的时候,这种做法会导致性能受损。 30 | 31 | ## 3. 预先派生子进程,accept 使用锁保护 32 | 33 | 为了避免多个子进程同时阻塞在 `accept` 调用上,可以在 `accept` 前面那使用某种锁,让多个子进程阻塞在锁上,其中只有一个能够拿到锁,进而只有单个进程能够阻塞在 `accept` 调用上。 34 | 35 | ## 4. 每个客户一个线程 36 | 37 | `accept` 到新的客户连接后,就创建一个线程,在此线程中处理用户请求。由于处理请求过程中往往涉及到读取文件、数据库,这类 IO 操作都相当耗时,当执行这些 IO 操作的时候,操作系统可以调度其他线程。这样可以让计算资源得到更高效的利用。 38 | 39 | ## 5. 预先创建线程 40 | 41 | 尽管创建和销毁线程的代价较进程的创建与销毁要少,但是毕竟存在消耗。另外如果请求量很大,那么就会创建大量的进程,可能会耗尽内存。因此,可以使用线程池。同样可以在各个线程上做 `accept` 也可以又主线程来 `accept` 而后交给子线程。 42 | 43 | 后者可以使用队列来实现。主线程 `accept` 之后,把文件描述符等信息放入队列,其他子线程从队列中取出文件描述符,并完成后续的服务。这是一个典型的生产者消费者的模型,在实现的时候使用一个互斥锁和条件变量即可轻松实现。 44 | 45 | ## 6. IO 多路复用 46 | 47 | 前面提到的方法中,任何时刻,进程和线程都是在为一个连接服务。如果该连接的处理需要做耗时的 IO 操作,则需要操作系统切换进程或者线程,以此保存不浪费系统资源。而基于 select、poll、epoll 这样的系统调用,可以订阅多个描述符上的事件,一个进程/线程可以同时处理多个连接。 48 | 49 | 这种模式下,当监听的文件描述符上有事件发生时,根据发生的事件类型和对应的文件描述符,可以决定该如何采取行动。但是采取行动往往是在当前线程中进行的,即在处理某事件的时候,其他事件不能得到处理。 50 | 51 | 因此,这种模式常常和多线程搭配使用,在工作线程来处理事件。 -------------------------------------------------------------------------------- /site/assets/css/scss/m-list.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | 4 | 5 | .article-list{ 6 | margin-top: .45rem; 7 | } 8 | 9 | 10 | .m-list { 11 | margin: .1rem 0 .2rem 0; 12 | .list-item { 13 | list-style: none; 14 | text-indent: 0; 15 | line-height: .5rem; 16 | border-bottom: 1px dashed $border-color; 17 | } 18 | .list-item:hover{ 19 | background-color: #f8f8f8; 20 | a{ 21 | padding: 0 0 0 .1rem; 22 | } 23 | } 24 | 25 | .post-title { 26 | color: inherit; 27 | display: inline; 28 | } 29 | .post-date { 30 | float: left; 31 | font-size: .12rem; 32 | padding-right: .2rem; 33 | color: $secondary-color; 34 | } 35 | a { 36 | display: block; 37 | text-decoration: none; 38 | overflow: hidden; 39 | } 40 | 41 | } 42 | 43 | 44 | .m-list__title{ 45 | padding-top: .2rem; 46 | margin-bottom: .1rem; 47 | line-height: 1.5em; 48 | font-size: .18rem; 49 | } 50 | 51 | 52 | @media screen and (max-width: 500px){ 53 | .list-item .post-title { 54 | display: block; 55 | white-space: nowrap; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | } 59 | .list-item .post-date { 60 | display: none; 61 | } 62 | } 63 | 64 | /*--------------------------*/ 65 | 66 | .tag-list { 67 | margin-bottom: .2rem; 68 | a { 69 | border: 1px solid #ddd; 70 | box-sizing: border-box; 71 | font-size: .12rem; 72 | font-weight: normal; 73 | padding: .04rem .1rem; 74 | display: inline-block; 75 | margin-right: 1em; 76 | margin-bottom: 1em; 77 | 78 | &:hover, 79 | &.active { 80 | color: #fff; 81 | background-color: #000; 82 | } 83 | } 84 | .m-list__title{ 85 | margin-bottom: .2rem; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /_posts/python/2018-07-04-format.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: format 的用法 4 | category: Python 5 | --- 6 | 7 | 8 | 9 | ### => 基本语法 10 | 11 | ``` 12 | format_spec ::= [[fill]align][sign][#][0][width][grouping_option][.precision][type] 13 | fill ::= 14 | align ::= "<" | ">" | "=" | "^" 15 | sign ::= "+" | "-" | " " 16 | width ::= digit+ 17 | grouping_option ::= "_" | "," 18 | precision ::= digit+ 19 | type ::= "b"|"c"|"d"|"e"|"E"|"f"|"F"|"g"|"G"|"n"|"o"|"s"|"x"|"X"|"%" 20 | ``` 21 | 22 | ### => 控制填充字符的转换方式 23 | 24 | `!r` 和 `!s` 表示插入使用相应参数的 `__repe__` 和 `__str__` 函数得出的字符表示进行填充。 25 | 26 | ```python 27 | <<< "repr() shows quotes: {!r}; str() doesn't: {!s}".format('test1', 'test2') 28 | >>> "repr() shows quotes: 'test1'; str() doesn't: test2" 29 | ``` 30 | 31 | ### => 对齐与填充 32 | 33 | ```python 34 | <<< '{:<30}'.format('left aligned') 35 | 'left aligned ' 36 | <<< '{:>30}'.format('right aligned') 37 | ' right aligned' 38 | <<< '{:^30}'.format('centered') 39 | ' centered ' 40 | <<< '{:*^30}'.format('centered') # use '*' as a fill char 41 | '***********centered***********' 42 | ``` 43 | 44 | ### => 进制转换 45 | 46 | ```python 47 | <<< "int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}".format(42) 48 | 'int: 42; hex: 2a; oct: 52; bin: 101010' 49 | 50 | # with 0x, 0o, or 0b as prefix: 51 | <<< "int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}".format(42) 52 | 'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010' 53 | ``` 54 | 55 | ### => 数值表示 56 | 57 | ```python 58 | <<< '{:,}'.format(1234567890) 59 | '1,234,567,890' 60 | ``` 61 | 62 | ### => 小数位数与百分比 63 | 64 | 65 | ```python 66 | >>> points = 19 67 | >>> total = 22 68 | >>> 'Correct answers: {:.2%}'.format(points/total) 69 | 'Correct answers: 86.36%' 70 | ``` 71 | 72 | ### => 格式化时间 73 | 74 | ```python 75 | >>> import datetime 76 | >>> d = datetime.datetime(2010, 7, 4, 12, 15, 58) 77 | >>> '{:%Y-%m-%d %H:%M:%S}'.format(d) 78 | '2010-07-04 12:15:58' 79 | ``` -------------------------------------------------------------------------------- /_posts/algorithm/2017-01-05-sort.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 排序 4 | category: 算法 5 | --- 6 | 7 | ## 名词解释 8 | 9 | **排序算法的稳定性** 10 | 11 | ```python 12 | ['AI','Java','ML','NLP','CPP'] 13 | ``` 14 | 15 | 对上面的数组中字符串按照长度进行排序,如果排序后相同长度的字符串的相对位置不变(即排序后 `"AI"` 依然排在 `"ML"` 的前面),则称排序算法是稳定的。 16 | 17 | ## 选择排序 18 | 19 | 找到最小的元素和第一位交换,从第二位开始在最小的元素和第二位交换,如此往复。 20 | 21 | 特点: 22 | 23 | - 运行时间和输入无关 24 | - 数据移动最少 25 | 26 | ```java 27 | public static void sort(Comparable[] a) { 28 | int len = a.length; 29 | for (int i = 0; i < len; i++) { 30 | int min = i; 31 | for (int j = i + 1; j < len; j++) { 32 | if (a[j].compareTo(a[min]) < 0) { 33 | min = j; 34 | } 35 | } 36 | Utils.swap(a, i, min); 37 | } 38 | } 39 | ``` 40 | 41 | ## 插入排序 42 | 43 | 不断和前一个元素比较,如果前一个元素大于当前元素,则交换。就像整理扑克牌一样,第一张牌不动,第二张则插在第一张的左或右边,而第三张则插入在前两张的合适位置。 44 | 45 | 特点: 46 | 47 | - 插入排序对下列数组排序效果很好 : 48 | - 数组中只有几个元素的位置不正确 49 | - 每个元素都离自己正确的位置不远 50 | - 当倒置数很少的时候,性能很好 51 | 52 | ## 希尔排序 53 | 54 | 对于大数组,插入排序工作的并不好,因为它只会交换相邻元素,元素只能一点一点地搬动到另一端。而希尔排序,通过调整跨度,可以将大数组快速地调整为局部有序的数组,而后再采用插入排序完成最终排序。 55 | 56 | 57 | ![shell](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2018-1-5/147756.jpg) 58 | 59 | 60 | 61 | ## 归并排序 62 | 63 | 归并排序的思路是将问题两个有序数组的合并,合并两个有序数组是线性复杂度的。 64 | 65 | ### 自顶向下的归并排序 66 | 67 | 自顶向下的归并排序采用递归的写法,可以想象不断将数组划分为原来的一半,递归到最深层有两个元素进行比较,这时候就相当于是两个有序数组了,可将他们归并为一个有序数组。而后递归退回一层将两个有序数组在归并。这样整个数组就进行排序。 68 | 69 | 70 | merge 算法,需要一个额外的空间来保存两个 a 数组中的内容。 71 | 72 | 73 | 自顶向下的归并排序需要递归,其本质是将两个有序子数组合并为一个有序数组。为了得到有序子数组,纯粹的递归归并,是通过递归深入到最底层,进行两个元素的比较。这才得到了一个有两个元素的有序子数组,而后有四个元素的有序子数组……。 74 | 75 | 这样递归深度是 log2(N) 层,为了减少递归深度,可以在子数组长度较少时可以使用插入排序得到有序的子数组。这样可以减少递归的深度。 76 | 77 | ### 自底向上的归并排序 78 | 79 | 模仿递归的归并排序的效果,可采用迭代完成相同的效果。第一次迭代以 2 为跨度,将两个元素调整为有序,而后以 4 为跨度,将其中的两个有 2 个元素的有序数组合并,而后以 8 为跨度。 80 | 81 | 82 | ## 堆排序 83 | 84 | 堆排序是利用二叉堆的特性,首先用待排序数组构成二叉堆,而后不断将堆中最大元素放到数组的后面,同时减小堆的大小,当堆为空时,排序完成。 85 | 86 | 87 | 堆排序的主要缺点是它不能有效地利用缓存,堆排序中的比较很少在相邻元素间进行,在对大数组排序的时候,缓存往往不会命中。这是为什么看起来堆排序具有和快速排序相同时间复杂度为 `O(NlogN)`,但却没有得到广泛使用的原因。 -------------------------------------------------------------------------------- /_posts/cpp/2020-02-08-c-vararg.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: C 语言中定义参数数量可变的函数 4 | category: C/C++ 5 | --- 6 | 7 | 在使用 C 语言时,常常会用到如 `printf` 这样的可变接受参数的函数。可变参数究竟是如何实现的呢? 8 | 9 | ```c 10 | printf("%d %f %s", 1, 3.14, "hello"); 11 | ``` 12 | 13 | 在 `stdarg.h` 头文件中提供了一组接口用来支持可变参数的函数。下面是一个例子,基于这个例子来讲解参数数量可变的函数的定义方法。这个函数的用来计算多个 `double` 型参数的和,其中第一个参数是数量,之后是对应的浮点数。 14 | 15 | ```c 16 | double sum_of_doubles(int count, ...){ 17 | double sum = 0; 18 | 19 | va_list ap; 20 | va_start(ap, count); 21 | for(int i = 0;i 2 | 3 | 4 | 5 | {{page.title}} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 65 | 66 | 67 |
68 |
69 |
70 | {{ content }} 71 |
72 |
73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /_posts/network/2015-09-01-http-cache.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: HTTP 缓存 4 | category: 网络 5 | --- 6 | 7 | 8 | 和计算机存储体系一样,web中也存在着缓存,这些缓存可以让人们不必每次都访问地域上很遥远的web服务器,缓存的存在大大地减缓了网络拥塞。 9 | 10 | HTTP 协议中就存在一些首部用于控制缓存,下面一一罗列并讲解: 11 | 12 | ## 缓存控制 13 | 14 | **Cache-Control:max-age=1000** 15 | 16 | max-age 定义了文档的最大使用期,从第一次生成文档到文档不再新鲜最大合法存在时间,它是相对于文档的创建时间来说的,单位为秒。 17 | 18 | **Expires:Fri,05 Jul 2002,05:00:00 GMT** 19 | 20 | 指定一个具体的时间,这个时间之后文档就不再有效了。由于客户端设备上的系统时间可能有错误,所以有可能出现意外。 21 | 22 | ## 再验证 23 | 24 | 如果已经缓存的文档过期了,这个时候也不意味着它的内容已经发生了变化,这个时候缓存会向服务器发起再验证,缓存会获取一份这个文档新的副本,如果文档没有更新,那么缓存就将这个数据发回客户端,并更新相应的首部信息,包括新的过期时间。 25 | 26 | 但是如果从原服务器获取文档失败了,那么就不能发送已经过期的缓存了,而是发送错误报文。 27 | 28 | ### 用条件方法进行再验证 29 | 30 | + If-Modified-Since: 31 | 32 | 如果从指定日期后文档被修改了,就执行请求。如果从指定日期后文档没有被修改过,那就会返回一个304 Not Modified 响应,这个时候缓存一般之后发送一些变更过的头部信息。否则就会返回一个200 OK 响应。 33 | 34 | + `If-None-Match:` 35 | 36 | 有些时候仅仅使用最后修改时间来验证是不够的,因为有时候经管文档被修改了,但是修改并不重要到需要全球范围内缓存进行重装,或者尽管修改了,但是内容并没有变化(重写了文档),还有的服务器不能得到文档最后修改时间。为了解决这些问题 HTTP 允许对被称为 **实体标签(ETag)** 的版本标识符来比较。实体标签是附加到文档上的任意的标签。他们可能包含的是文档的版本号或者是序列号等。当发布者修改了文档后,可以修改这些实体标签来说明这是一个新的版本。这样缓存就可以使用 If-None-Match 条件首部来获取文档的新副本了。 37 | 38 | ``` 39 | // request 40 | GET /about.html HTTP/1.0 41 | If-None-Match:"v2.6","v2.5","v2.4" 42 | 43 | // response 44 | HTTP/1.0 304 Not Modified 45 | ETag:"v2.6" 46 | ``` 47 | 48 | ### 何时使用实体标签,何时该使用最后修改时间 49 | 50 | 如果服务器回送了一个实体标签,那么客户端就必须使用实体标签进行验证。如果服务器只回送了 Last-Modified 客户端就可以使用 If-Not-Midified 来验证。 51 | 52 | 如果服务器收到的请求既有If-Modified-Since 又有实体标签,那么只有两者都满足,才会回送 304 响应。 53 | 54 | 55 | ## 控制缓存的能力 56 | 57 | `Cache-Control:no-store|no-cache|must-revalidate|max-age` 58 | 59 | **no-store** 60 | 61 | 标识为 no-store 的响应,是不会进行缓存的,缓存就像非缓存代理一样向客户端转发该相应,然后删除该对象。 62 | 63 | **no-cache** 64 | 65 | 标识为 no-cache 的响应,并非不会存储在缓存中,只是在与原服务器进行新鲜度验证之前,缓存是不能将其提供给客户端的。也就是说,每次访问该文档,都会进行新鲜度验证。 66 | 67 | **max-age** 68 | 69 | 表示从服务器将文档传来时起,具有多少秒的新鲜时间。 70 | 71 | **must-revalidate** 72 | 73 | ... 74 | 75 | ## 试探性过期 76 | 77 | 如果响应首部中没有 expires 和 Cache-Control:max-age 首部,那么缓存就会自己估计一个时间。可能会根据文档的最后修改时间来估计。最近修改的文档很有可能会再次修改,而很久以前修改过的文档很有可能是一份稳定的文档,因此缓存时间可能会较长。 78 | 79 | ## 客户端的新鲜度限制 80 | 81 | 对于用户点击 refresh 按钮这样的行为,是会无条件地从原始服务器中获取文档。当然在请求中头中也可以添加 Cache-Control 来限制文档的新鲜度。 82 | 83 | ``` 84 | Cache-Control:max-stale=|min-fresh=|no-cache|max-age|no-store|only-if-cached 85 | ``` 86 | -------------------------------------------------------------------------------- /_posts/cs/2020-06-02-codec.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 对称加密与非对称加密 4 | category: 理解计算机 5 | --- 6 | 7 | ## 对称加密 8 | 9 | 对称加密使用同一个密钥完成加密和解密操作,通信双方都需要拥有此密钥。 10 | 11 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/10/2020-07-10-181319.png) 12 | 13 | 举个最简单的对称加密的例子,现有密钥 `x`,需要发送的信息为 `m`,加密的方法是用密钥做异或操作,加密后的内容为 `m^x`。对方收到信息后,在次使用 `x` 进行异或,即 `m^x^x=m` 这样就收到了原始信息了。这种方法太过简单不够安全。比如在通信中常常开始和结束的消息是固定的,由此窃听者就能猜测出传输的消息 `m`,进而破获密钥。即使这个例子不够安全,但是已经体现出了对称解密的本质。使用同一个密钥来完成加解密。 14 | 15 | 通常密钥的长度为几百字节,比如 256 字节,但是要发送的信息往往很长,加密过程是把原文分成和密钥等长的段来分别进行的。成熟的对称加密算法对数据做多轮加密,而且会做字节的移动等操作。解密时做逆操作即可。这样做的目的是为了防止绕开密钥破解出信息来。 16 | 17 | 对称加密的难点在如何让通信双方都拥有密钥。一种方法是提前在通信双方的机器上部署密钥,这只适用于固定的某些机器之前进行通信。另外一种方法是使用非对称加密技术来传输密钥。非对称加密,是下一节的内容。 18 | 19 | ## 非对称加密 20 | 21 | 非对称加密算法的代表位 RSA 算法,RSA 是三个发明人的首字母缩写。非对称加密中用于加密和解密的密钥是不同的。 22 | 23 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/10/2020-07-10-181332.png) 24 | 25 | 在非对称解密中,存在两个密钥——公钥和私钥,其中公钥是公开的,它可以通过明文传输。但是使用公钥加密后的加密文档必须使用私钥进行解密。私钥必须要保密,如果私钥被窃取了,非对称解密的安全性就不能保证的。 26 | 27 | 在 RSA 算法中,首先会挑选两个很大的素数 P 和 Q,两者相乘得到 N。取一个小于 `(P-1)(Q-1)` 且与之互质的数作为 `e`,公钥就由 `(e, N)` 两者组成。 28 | 29 | - `N = P * Q` 30 | - `e` 为某与 `(P-1)(Q-1)` 互质的数,通常选择较小的质数,这样加密速度比较快。 31 | 32 | 设需要加密的内容为 `M`,加密后的内容为 `C`,加密方法如下: 33 | 34 | $$C = M^{e} \% N$$ 35 | 36 | 使用 `e` 和 `P`、`Q` 根据如下式子生成 `d`,私钥就由 `(d, N)` 组成。 37 | 38 | $$d = e^{-1} \% (P-1)(Q-1)$$ 39 | 40 | 有了密钥之后,可以用如下运算解密: 41 | 42 | $$M = C^d \% N$$ 43 | 44 | 关于解密的运算,可以参见补充说明。 45 | 46 | **非对称加密的安全性** 47 | 48 | 在非对称加密中,`N` 和 `e` 都是已知的,要想解密必须要知道 `d`,而要想计算出 `d`,必须知道 `P` 和 `Q`,但是目前对 `N` 进行质因数分解得到 `P` 和 `Q` 不存在有效的算法。因为 `N` 的长度通常达到几百位,想要暴力破解是不现实的,因此,非对称加密目前还很安全。 49 | 50 | ## 补充说明 51 | 52 | 其中 `$e^{-1}$` 是 `e` 的倒数,对一个分数进行取模,这还是挺少见的,在此之前我是不知道如何做。关于取模有如下性质: 53 | 54 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/10/2020-07-10-182250.png) 55 | 56 | 但是: 57 | 58 | $$(a/b) \% p \ne (a\%p / b \% p) \% p$$ 59 | 60 | 61 | 所以加减乘、指数都好处理,但是除法不好办。在运算中如果中间结果很大,那么可以进行取余。但是一旦运算中出现了除法,就不好办了。此时需要使用逆元,将除法转换为乘法运算。 62 | 63 | 逆元的定义如下: 64 | 65 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/10/2020-07-10-182433.png) 66 | 67 | 68 | 即如果 `$(a*c) \% n = 1$` 那么 `$(1/a) \% n$` 可以替换为 `$c \% n$`。 69 | 70 | 其实分数在平常使用的数学范畴里,确实是不能取余的,有悖于常识。上面的定义是来自数论中的理论。在数论中,除法的取余就是乘以除数的逆元。 71 | 72 | ## 对称解密和非对称解密的配合使用 73 | 74 | 非对称解密的运算量较大,通常在通信中常常使用非对称解密算法来传输用于对称加密的密钥,因为非对称加密可以安全地传输密钥,传输完密钥之后,可以切换至对称加密。在 HTTPs 中就是这么做的。 -------------------------------------------------------------------------------- /_posts/web/2016-11-11-avoid-forced-synchonous-layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 避免强制性同步布局 4 | category: Web 5 | --- 6 | 7 | 8 | 9 | - * 10 | {:toc} 11 | 12 | 强制性同步布局,发生在使用 JavaScript 改变了 DOM 元素的属性,而后又读取 DOM 元素的属性。比如改变了 DOM 元素的宽度,而后又使用 `clientWidth` 读取 DOM 元素的宽度。这个时候由于为了获取到 DOM 元素真实的宽度,需要重新计算样式。 13 | 14 | ## 案例 15 | 16 | 想象一下,如果有一组 DOM 元素,我们需要读取它们的宽度,并设置其高度与宽度一致。 17 | 18 | ## 解决方案 19 | 20 | ### 1. 新手解决方法 21 | 22 | ```js 23 | for(var i = 0,len = divs.length; i{ 54 | divs[i].style.height = width + 'px'; 55 | }) 56 | } 57 | ``` 58 | 59 | ## 优化效果 60 | 61 | 可以查看[这个例子](https://wy-ei.github.io/60fps/layout/layout-thrashing.html)来对比一下这几种方案的性能差异。打开 Chrome DevTools 在 Timeline 中录制重新布局的过程,可以看到下面三种情形: 62 | 63 | **强制性同步布局:** 64 | 65 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2016-11-11/319890.jpg) 66 | 67 | 这个时候会看到浏览器进行了很多次的重新计算样式(Recalculate Style) 和 布局(Layout),也叫做 reflow 的操作,且这一帧用时很长。 68 | 69 | **分离读写:** 70 | 71 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2016-11-11/147743.jpg) 72 | 73 | 这个时候,浏览器只进行了一次 reflow,用时很短。 74 | 75 | **使用 requestAnimationFrame:** 76 | 77 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2016-11-11/625120.jpg) 78 | 79 | 这个方案也很快,只是因为调用了 `requestAnimationFrame` 很多次添加了很多回调,这个时候会有很多函数调用。建议对于将该方法用在回调较少的场景下。其实另外一个可行的方案是在 `requestAnimationFrame` 中批量来写 DOM 80 | 元素。 81 | 82 | ## 总结 83 | 84 | 在需要操作 DOM 的时候,一定要注意避免强制性同步布局,遇到交替读写 DOM 的操作的时候,可以通过分离读写,使用 `requestAnimationFrame` 来避免强制性同步布局的出现。 85 | -------------------------------------------------------------------------------- /site/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} 7 | 8 | {% if page.description %} 9 | 10 | {% else %} 11 | 12 | {% endif %} 13 | 14 | {% assign timestamp = site.time | date:"%m%d%H%M" %} 15 | 16 | 17 | 18 | 19 | 20 | {% include init.html %} 21 | 22 | 23 | 24 | 41 |
42 |
43 |
44 | {{ content }} 45 |
46 |
47 | {% include footer.html %} 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /_posts/cs/2015-05-01-computer-storage-systemter.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 计算机存储体系 4 | category: 理解计算机 5 | --- 6 | 7 | 8 | 今天看到一个很好的比喻可以说明很形象地说明CPU、寄存器、高速缓存(cacha)、内存(RAM)、硬盘、网络之间的关系。现在把它描述如下,希望能给不懂计算机机的人一个轻松的方式来理解计算机的存储体系。 9 | 10 | ### 工人 & CPU 11 | 12 | 我们知道CPU的工作需要依赖数据。而工人们工作需要工具。所以不妨想象CPU是一个工人,而这数据就是工人手里的工具。他用的工具有螺丝刀、锤子、锯子、钳子、切割机等等。 13 | 14 | 15 | ![tools](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2015-05-01/tools.jpg) 16 | 17 | ### 工人的手 & 寄存器 18 | 19 | 工人干活的时候一般手上只拿少数的一两件工具。工人的手就相当于CPU内部的寄存器,需要干什么事情,换用什么样的工具,不能拿太多的工具在手上,用完一个工具就要放回去,换另外的工具。 20 | 21 | ![工人](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2015-05-01/worker.jpg) 22 | 23 | ### 工具包 & 高速缓存 24 | 25 | 前面说了工人的手上一般只拿少数一两件工具,用完了就要换一件工具。那么要去哪里更换工具呢?在实际工作中常出现的情况是一段时间要频繁使用螺丝刀和扳手,另一段时间频繁使用切割机和尺子。所以工人们随身携带一个工具包,装上常用的工具。对于CPU来说,这个工具包里面就是它常常要处理的数据。我们称之为高速缓存(cache)。当然工具包是带在工人身上的,所以里面装的工具不能太多。同样高速缓存位于CPU内部,容量也十分有限。从工具包换一个工具使用的时间可能就是使用工具一次所用时间的几倍或者数十倍。 26 | 27 | ![工具包](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2015-05-01/toolsbox.jpg) 28 | 29 | ### 工具箱 & 内存 30 | 31 | 干一项工程,肯定不是一两件工具就能完成的。公司接到业务之后,工作人员一般会把需要的工具都放如工具箱里,然后去工作现场。当然在实际工作中从工具箱取工具也是比较快的,但是相对于随身携带的工具包就慢的多了。对CPU来说也是这样,当一个程序被打开时,程序数据将被加载到内存中,这就相当于将需要的工具放入了工具箱里面。而CPU中内存中获取数据所需时间是从内存中获取数据所需时间的几十至几百倍。 32 | 33 | ![工具箱](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2015-05-01/toolsbox2.jpg) 34 | 35 | ### 工具仓库 & 硬盘 36 | 37 | 要干活的时候就会把工具从仓库里面拿出来,装进工具箱。当不干活的时候,就会把工具放到仓库里面。在实际工作中可能出现,工具实在太多或者太大装不进工具箱的情况,这个时候会从仓库中拿出一部分需要的工具放入工具箱,把不再需要的工具放回仓库。在计算机中也是这样,内存毕竟有限,当CPU需要某些数据的时候,它会从工作现场来到硬盘拿回需要的数据放入内存,把内存中不需要的数据放入硬盘中合适的位置。可以想象这个时间是相当长的。在计算机系统中CPU从硬盘获取数据所需时间可能是从内存获取数据的几十万倍。 38 | 39 | ![仓库](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2015-05-01/cangku4.jpg) 40 | 41 | 42 | ### 五金市场 & 网络 43 | 44 | 有时候在工作中,发现自己仓库里面没有某个工具,那就需要去市场上买需要的工具。这就像在计算机系统中一样,当本地硬盘上没有某个数据的时候,就要去网络上去获取。从市场上获取的工具不一定会在用完后放入仓库。在计算机系统中,获取的网页可能在我关闭了网页后就被销毁了,而有的时候我们下载的音乐可能会保存在本地。 45 | 46 | ![五金市场](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2015-05-01/market.jpg) 47 | 48 | 49 | ### 整个体系 50 | 51 | 至此,我们应该明白计算机存储器的体系结构了,对的他就是一个从高速到低速,从小容量到大容量的结构。如下图所示: 52 | 53 | ![计算机存储体系](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2015-05-01/arc.jpg) 54 | 55 | 其实计算机的存储体系中还有很多很多细节,比如在在下级取一个数据时,替换上级的哪一个存储单元。当然以上内容也能让人们对存储结构的分层结构一点概念,不至于说自己的内存有500G。其中有的比喻有一个位置不太合理,那就是高速缓存部分,现在的存储结构中有多级高速缓存,就好比工人随身携带了由小到大的多个工具包一样。将最最常用的工具放入一级工具包,较常用的放入二级工具包。 56 | 57 | 我们知道,从硬盘读取数据是很慢的,每次开机的时候计算机就会从硬盘读取好多数据进入内存,之后常用的数据就在内存中了。而不太会读取硬盘,所以我们的计算机才会如此的快速。实际中,CPU处理的90%以上的数据都是直接从高速缓存中读取的,可以想象一下,如果所有数据都从硬盘读取,那么计算机的速度会是怎么样的? 58 | 59 | 这篇介绍也就结束了,希望能给不明白计算机存储体系的朋友一点点关于它的概念。至于细节,可以查看相关书籍。 -------------------------------------------------------------------------------- /_posts/algorithm/2018-01-10-maximum-contiguous-subsequence.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 最大子序列 4 | category: 算法 5 | --- 6 | 7 | 在数据结构与算法分析一书中讲到了找最大子序列的几种算法,记录如下: 8 | 9 | ## 方法一 10 | 11 | 此方法最直观,当然也比较慢,复杂度 O(N^2) 12 | 13 | ```c++ 14 | int max_sub_sum(const vector &a) { 15 | int max_sum = 0; 16 | for (int i = 0; i < a.size(); i++) { 17 | int this_sum = 0; 18 | for (int j = i; j < a.size(); j++) { 19 | this_sum += a[j]; 20 | if (this_sum > max_sum) { 21 | max_sum = this_sum; 22 | } 23 | } 24 | } 25 | return max_sum; 26 | } 27 | ``` 28 | 29 | 30 | ## 方法二 31 | 32 | 采用分治方法,将整个序列不断切分成原来的一半,找到前半部分和后半部分的最大子序列之和,再从中间向两端找最大子序列之和,三者取最大值。复杂度 O(NlogN) 33 | 34 | ``` 35 | [........................] 36 | <--前半部分--><--后半部分--> 37 | <--中间部分--> 38 | ``` 39 | 40 | 41 | ```cpp 42 | int max_sum_rec(const vector &a, int left, int right) { 43 | if (left == right) { 44 | if (left < 0) { 45 | return 0; 46 | }else { 47 | return a[left]; 48 | } 49 | } 50 | 51 | int center = (left + right) / 2; 52 | int max_left_sum = max_sum_rec(a, left, center); 53 | int max_right_sum = max_sum_rec(a, center +1, right); 54 | 55 | int max_left_border_sum = 0, left_border_sum = 0; 56 | for (int i = center; i >= left; i--) { 57 | left_border_sum += a[i]; 58 | if (left_border_sum > max_left_border_sum) { 59 | max_left_border_sum = left_border_sum; 60 | } 61 | } 62 | 63 | int max_right_border_sum = 0, right_border_sum = 0; 64 | for (int i = center + 1; i <= right; i++) { 65 | right_border_sum += a[i]; 66 | if (right_border_sum > max_right_border_sum) { 67 | max_right_border_sum = right_border_sum; 68 | } 69 | } 70 | 71 | int max_sum = max(max_left_sum, max_right_sum); 72 | max_sum = max(max_sum, max_left_border_sum + max_right_border_sum); 73 | return max_sum; 74 | } 75 | 76 | 77 | int max_sub_sum(const vector &a) { 78 | return max_sum_rec(a, 0, a.size() - 1); 79 | 80 | } 81 | ``` 82 | 83 | 注意:没有处理向量长度为奇数的情况。 84 | 85 | ## 方法三 86 | 87 | 此方法异常简洁,而且很好理解。从开头开始累加,一个为负的元素不可能是最大子序列的第一个元素,另外和为负的子序列不可能是最大子序列的前缀,所以当 `this_sum` 小于 0 时,将其置为 0。复杂度 O(N)。 88 | 89 | 90 | ```c++ 91 | int max_sub_sum(const vector &a) { 92 | int max_sum = 0; 93 | int this_sum = 0; 94 | 95 | for (int i = 0; i < a.size(); i++) { 96 | this_sum += a[i]; 97 | if (this_sum > max_sum) { 98 | max_sum = this_sum; 99 | } else if(this_sum < 0) { 100 | this_sum = 0; 101 | } 102 | } 103 | return max_sum; 104 | } 105 | ``` 106 | 107 | -------------------------------------------------------------------------------- /site/assets/css/scss/notebook.scss: -------------------------------------------------------------------------------- 1 | .nb-notebook { 2 | line-height: 1.5; 3 | } 4 | 5 | .nb-stdout, 6 | .nb-stderr, 7 | .nb-text-output, 8 | .nb-html-output{ 9 | margin-bottom: 0!important; 10 | font-family: $code-font-family; 11 | font-size: 0.9em; 12 | max-height: 300px; 13 | overflow: auto; 14 | } 15 | 16 | .nb-output pre{ 17 | margin: 0px; 18 | overflow-x: auto; 19 | overflow-y: auto; 20 | word-break: break-all; 21 | word-wrap: break-word; 22 | white-space: pre-wrap; 23 | } 24 | 25 | // .nb-output .nb-stdout, 26 | // .nb-output .nb-text-output{ 27 | // background: none; 28 | // } 29 | 30 | .nb-output .nb-stderr, 31 | .nb-output .nb-pyerr{ 32 | background-color: #fdd!important; 33 | } 34 | 35 | 36 | .nb-html-output pre{ 37 | font-family: $code-font-family;; 38 | } 39 | 40 | .nb-cell + .nb-cell { 41 | margin: 1em 0; 42 | } 43 | 44 | .nb-output table { 45 | border: 1px solid #000; 46 | border-collapse: collapse; 47 | } 48 | 49 | .nb-output th { 50 | font-weight: bold; 51 | } 52 | 53 | .nb-output th, .nb-output td { 54 | border: 1px solid #000; 55 | padding: 0.25em; 56 | text-align: left; 57 | vertical-align: middle; 58 | border-collapse: collapse; 59 | } 60 | 61 | .nb-cell { 62 | position: relative; 63 | } 64 | 65 | .nb-raw-cell { 66 | white-space: pre-wrap; 67 | background-color: #f5f2f0; 68 | font-family: $code-font-family;; 69 | padding: 1em; 70 | margin: .5em 0; 71 | } 72 | 73 | .nb-output { 74 | position: relative; 75 | min-height: 1em; 76 | width: 100%; 77 | } 78 | 79 | .nb-output img { 80 | max-width: 100%; 81 | } 82 | 83 | .nb-output::before, .nb-input::before { 84 | position: absolute; 85 | font-size: 12px; 86 | color: #999; 87 | left: -7.5em; 88 | top: 7px; 89 | width: 7em; 90 | text-align: right; 91 | font-family: $code-font-family;; 92 | } 93 | 94 | .nb-input{ 95 | position: relative; 96 | } 97 | 98 | .nb-input pre{ 99 | margin-bottom: 0.5em; 100 | overflow: auto; 101 | } 102 | 103 | 104 | .nb-input::before { 105 | color: #303F9F; 106 | content: "In:" 107 | // content: "In[" attr(data-prompt-number) "]:"; 108 | } 109 | .nb-input+.nb-output::before { 110 | color: #D84315; 111 | content: "Out:"; 112 | } 113 | 114 | div[style="max-height:1000px;max-width:1500px;overflow:auto;"] { 115 | max-height: none !important; 116 | } 117 | 118 | -------------------------------------------------------------------------------- /site/assets/js/lib/ansi_up.min.js: -------------------------------------------------------------------------------- 1 | // ansi_up.js 2 | // version : 1.1.0 3 | // author : Dru Nelson 4 | // license : MIT 5 | // http://github.com/drudru/ansi_up 6 | (function(a,b){function g(){this.fg=this.bg=null,this.bright=0}var c,d="1.1.0",e=typeof module!="undefined",f=[[{color:"0, 0, 0","class":"ansi-black"},{color:"187, 0, 0","class":"ansi-red"},{color:"0, 187, 0","class":"ansi-green"},{color:"187, 187, 0","class":"ansi-yellow"},{color:"0, 0, 187","class":"ansi-blue"},{color:"187, 0, 187","class":"ansi-magenta"},{color:"0, 187, 187","class":"ansi-cyan"},{color:"255,255,255","class":"ansi-white"}],[{color:"85, 85, 85","class":"ansi-bright-black"},{color:"255, 85, 85","class":"ansi-bright-red"},{color:"0, 255, 0","class":"ansi-bright-green"},{color:"255, 255, 85","class":"ansi-bright-yellow"},{color:"85, 85, 255","class":"ansi-bright-blue"},{color:"255, 85, 255","class":"ansi-bright-magenta"},{color:"85, 255, 255","class":"ansi-bright-cyan"},{color:"255, 255, 255","class":"ansi-bright-white"}]];g.prototype.escape_for_html=function(a){return a.replace(/[&<>]/gm,function(a){if(a=="&")return"&";if(a=="<")return"<";if(a==">")return">"})},g.prototype.linkify=function(a){return a.replace(/(https?:\/\/[^\s]+)/gm,function(a){return''+a+""})},g.prototype.ansi_to_html=function(a,b){var c=a.split(/\033\[/),d=c.shift(),e=this,f=c.map(function(a){return e.process_chunk(a,b)});f.unshift(d);var g=f.reduce(function(a,b){return Array.isArray(b)?a.concat(b):(a.push(b),a)},[]),h=g.join("");return h},g.prototype.process_chunk=function(a,b){b=typeof b=="undefined"?{}:b;var c=typeof b.use_classes!="undefined"&&b.use_classes,d=c?"class":"color",e=a.match(/([\d;]*)m([^]*)/m);if(!e)return a;var g=e[2],h=e[1].split(";"),i=this;h.map(function(a){var b=parseInt(a);isNaN(b)||b===0?(i.fg=i.bg=null,i.bright=0):b===1?i.bright=1:b>=30&&b<38?i.fg=f[i.bright][b%10][d]:b>=40&&b<48&&(i.bg=f[0][b%10][d])});if(i.fg===null&&i.bg===null)return g;var j=classes=[];return i.fg&&(c?classes.push(i.fg+"-fg"):j.push("color:rgb("+i.fg+")")),i.bg&&(c?classes.push(i.bg+"-bg"):j.push("background-color:rgb("+i.bg+")")),c?['',g,""]:['',g,""]},c={escape_for_html:function(a){var b=new g;return b.escape_for_html(a)},linkify:function(a){var b=new g;return b.linkify(a)},ansi_to_html:function(a,b){var c=new g;return c.ansi_to_html(a,b)},ansi_to_html_obj:function(){return new g}},e&&(module.exports=c),typeof window!="undefined"&&typeof ender=="undefined"&&(window.ansi_up=c),typeof define=="function"&&define.amd&&define("ansi_up",[],function(){return c})})(Date); -------------------------------------------------------------------------------- /_posts/web/2016-11-01-how-gpu-speed-up-page-render.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: GPU 是如何加速网页渲染的 4 | category: Web 5 | --- 6 | 7 | 8 | 9 | - * 10 | {:toc} 11 | 12 | 前端工程师应该都听说过硬件加速,通常它是指利用 GPU 来加速页面的渲染。那么 GPU 目前在web页面的渲染过程中起到什么作用呢? 13 | 14 | ## GPU 的作用 15 | 16 | 早期浏览器完全依赖 CPU 来进行页面渲染。现在随着 GPU 的能力增强和普及,且目前绝大多数运行浏览器的设备上都集成了 GPU。浏览器可以利用 GPU 来加速网页渲染。 17 | 18 | GPU 包含几百上千个核心,但每个核心的结构都相对简单, GPU 的结构也决定了它适合用来进行大规模并行计算。进行图层合并需要操作大量的像素,这方面 GPU 能比 CPU 更高效的完成。这里有个[视频](http://v.youku.com/v_show/id_XNjY3MTY4NjAw.html),很清楚地说明 CPU 与 GPU 的差别。 19 | 20 | ## 页面渲染过程 21 | 22 | 浏览器利用 HTML 构建出 DOM 树,利用 CSS 构建 CSSOM 树,最终得到 Render 树。 23 | 24 | ![text=渲染树的构建过程](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/16-9-24/93321516.jpg) 25 | 26 | 然而这只是很宏观的描述,浏览器为了将 DOM 元素高效地绘制且正确地出来,将多个元素安排在一个图层中,使用 PaintLayer 来描述,在每个 PaintLayer 中又存在 GraphicsLayers。当某个元素的样式改变后,不需要去重绘某个图层就好了。 27 | 28 | 浏览器的每一帧都可能会经过以下几个步骤: 29 | 30 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/16-9-24/92671229.jpg) 31 | 32 | JavaScript 的执行可能修改 DOM 树和 CSSOM 树,随后浏览器需要重新计算样式,并根据新的样式计算出元素的实际属性(比如 CSS 中 width 是 50%,这里就要利用父元素的宽度得出自己真实的 width 值),重绘有变动的图层,随后将各图层传递给 GPU ,由 GPU 来进行图层的合并。 33 | 34 | 上面 5 个步骤中,Layout 和 Paint 是可以省略的,当修改后的样式不会改变元素的尺寸、位置等涉及布局的属性时候,就没有必要进行 Layout(计算布局),比如修改了 color 属性,这个时候就只需要进行重绘(Paint)步骤。同样的道理,修改某些属性也不需要进行 Paint 步骤,只需要 Composite 就可以。 35 | 36 | 因此,我们希望所做的操作能尽可能地避免 Layout 和 Paint 这两个步骤,这样一帧所需的时间也就会大大缩短,可以明显避免卡顿。 37 | 38 | 目前有三个属性的改变只需要进行 Composite 过程,分别是: 39 | 40 | - filter 41 | - transform 42 | - opacity 43 | 44 | 这几个属性的改变,GPU 只需要在合并图层之前对图层进行一些变换,比如 `opacity` 属性的改变,GPU 只需要在合并之前改变图层的 alpha 通道。transform 和 filter 的改变 GPU 也可以很快地得到相应的图层。 45 | 46 | ## 正确地利用 GPU 47 | 48 | ### 使用 transform, filter 和 opacity 来完成动画 49 | 50 | 使用以上 3 个属性来完成动画,可以避免在动画的每一帧进行重绘。如果在动画中改变了其他属性,那也不能避免重新绘制。 51 | 52 | ### 避免不合理地强制开启硬件加速 53 | 54 | 常常看到有文章指出使用 `transform:translateZ(0);` 这样的 hark 可以强制开启硬件加速来提高性能,这是错误的说法,要知道所谓的硬件加速就是利用 GPU 来将本就存在于 GPU 中的图层进行一些变换得到新的图层。如果改变的属性必须要要进行重绘,比如改变了 background 属性,那么图层还是要进行重绘然后重新加载至 GPU 中。这个时候就算强制开启硬件加速也没有什么用。 55 | 56 | 使用 `transform:translateZ(0);` 这样的 CSS hark 写法会将元素提升至单独的图层。在这么做之前要考虑为什么要这样做,创建新的图层的目的应该是,避免某个元素的改变导致大面积重绘,比如某个小标签的颜色的改变,导致大面积重绘,因此将其提升至单独的图层中。这里有个[例子](https://wy-ei.github.io/60fps/paint/avoid-large-area-repaint.html),小标签背景色的改变会导致大面积的重绘,但是如果将其提升至单独的图层后,改变它的背景色将只会重绘它自身。你可以代码 Chrome 调试工具,通过 Timeline 观察每次闪烁重绘的内容。 57 | 58 | 而如果整个图层的都要被重绘,那么再将其中的部分元素提升至单独的图层,会导致重绘的时候会分多个图层来进行绘制,然后在进行多个图层的合并,这个时候不如将所有元素放置在单个图层中,重绘整个大的图层。 59 | 60 | ## 总结 61 | 62 | 所谓硬件加速,早起浏览器是使用纯软件来渲染页面的,如今现代浏览器利用了 GPU 来进行页面的渲染,在合适的时候浏览器就会自动去使用 GPU 而不是开发者自己去指定。GPU 的功能是在合并图层阶段,它可以在进行图层合并之前来对原图层进行一些变换,合理地使用这个变换可以避免页面重绘,使得每一帧消耗的时间最少,避免卡顿。 63 | -------------------------------------------------------------------------------- /_posts/rec/2019-08-31-amazon-item-to-item.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 论文阅读 - Item-to-Item Collaborative Filtering 4 | category: 推荐系统 5 | tag: 推荐系统 6 | --- 7 | 8 | - * 9 | {:toc} 10 | 11 | 本文是我在阅读 Amazon 工程师 2003 年发表的论文 Item-to-Item Collaborative Filtering 时记录的笔记。 12 | 13 | ## 介绍 14 | 15 | Amazon.com 的推荐系统所面对的挑战: 16 | 17 | - 海量商品+海量用户 18 | - 实时推荐,半秒内做出响应,且生成可靠的推荐结果 19 | - 新用户的信息很少,老用户有大量的信息 20 | - 用户的信息是易变的,用户在短时间内产生的交互信息,就能改变用户的特征,推荐系统需要快速地对用户特征的改变做出反应。 21 | 22 | 传统的 CF 算法,不能满足实时性要求,这里提出 item-to-item collaborative filtering 算法,它的计算量独立于用户数量和商品数量,可以在海量数据的场景下,实时地产生高质量的推荐。 23 | 24 | ## 推荐算法 25 | 26 | ### 传统的协同过滤 27 | 28 | 传统的协同过滤算法将用户表示为一个长度为 $n$ 的向量 $v$,N 是系统中物品的个数,$v_i$ 代表用户有没有购买过商品 $i$ 或者对商品 $i$ 的评分。 29 | 30 | 根据用户向量,可以为每个用户找到一组相似的用户,相似用户购买过或评价高的物品,就可以推荐给该用户。用户的相似度可以通过用户向量间夹角的余弦值来度量: 31 | 32 | $$ 33 | \operatorname{similarity}(\vec{A}, \vec{B})=\cos (\vec{A}, \vec{B})=\frac{\vec{A} \bullet \vec{B}}{\|\vec{A}\| *\|\vec{B}\|} 34 | $$ 35 | 36 | 设系统中有 M 个用户,N 件物品,给目标用户寻找相似用户需要 $O(MN)$,遍历所有用户,计算用户向量的相似度。但是因为用户向量往往是非常稀疏的,所以实际复杂度为 $O(M)$。即便如此,在上亿用户的场景下,这个时间复杂度也是无法接受的。 37 | 38 | 一种权衡的策略是,随机抽一部分用户以减小 M,抛弃掉冷门物品以减小 N。还可以使用聚类,降维等策略来减小计算量。但以上这些策略会引起推荐质量的降低。 39 | 40 | ### Cluster Models 41 | 42 | 聚类模式的策略是将用户先进行聚类,聚类操作可以离线进行。聚类后所有用户被分到了一些小的分组中,且彼此较为相似。对于目标用户,在组内寻找最相似用户,并生成推荐。 43 | 44 | ### Search-Based Methods 45 | 46 | 基于搜索(或基于内容)的策略通过 item 的属性,比如文本、类别等,来寻找相似的 item。用用户购买过的商品,构造一个 query 然后检索出匹配的 item。当用户只有少量的购买记录时,这种方法还勉强奏效。当用户购买记录很多的时候,就很难确定要搜什么东西了,得到的结果是很热门的那些 item,或者范围很窄,比如老是推荐某个作者的书或某一类物品。推荐系统应该帮助用户找到那些新鲜的、相关的、用户感兴趣的物品。 47 | 48 | ## Item-to-Item Collaborative Filtering 49 | 50 | item-to-item CF 寻找与用户购买过的物品相似的其他物品,组合这些相似物品,得到最终的推荐结果。 51 | 52 | > Rather than matching the user to similar customers, item-to-item collaborative filtering matches each of the user’s purchased and rated items to similar items, then combines those similar items into a recommendation list. 53 | 54 | ### 算法细节 55 | 56 | 这里提出的算法依然需要计算 item 间的相似度,只不过 Amazon 的这篇论文提出了一种计算 item 间相似度的策略。 57 | 58 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/08/31/5d6a503b451253d178065409.jpg) 59 | 60 | 从某个用户同时购买过的多个 item 间开启计算,而不是拿某个 item 和其他所有 item 进行计算。因为很多 item 之间并没有某个人都购买过,这两个 item 间的相似度是没法算的。通过上面给出的算法,可以减少计算量。 61 | 62 | 计算完成之后,每个 item 都被关联了 k 个相似的物品。 63 | 64 | ### 可扩展性 65 | 66 | item-to-item collaborative filtering 能够应对大量数据场景,因为 item 之间的相似度具有持久性,可以预先离线进行计算。 67 | 68 | ## 总结 69 | 70 | 通过阅读论文,我感觉 collaborative filtering 在早期(2000年左右),专指 user-based CF,即通过找相似用户,用相似用户喜欢的物品作为推荐结果的方法。后来慢慢引入了 item-based(如本文所描述),然后才将 CF 算法分为 user-based 和 item-based。 71 | 72 | 本文就讲了 item-based 的具体做法,关于相似度计算等方法,就和 user-based CF 一样,所以没有再提。另外 item 之间计算相似度的策略(即前面伪代码所描述)也值得学习。 -------------------------------------------------------------------------------- /_posts/rec/2019-09-06-deep-FM.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 论文阅读 - DeepFM 4 | category: 推荐系统 5 | tag: 推荐系统 6 | --- 7 | 8 | - * 9 | {:toc} 10 | 11 | 12 | 今天看了出自 IJCAI 2017 的论文 DeepFM: A Factorization-Machine based Neural Network for CTR Prediction,思想并不复杂,现将笔记大致记录于此。 13 | 14 | ## 背景 15 | 16 | CRT (click-through rate) 预测,是指预测用户对某个物品(广告)的点击率,以便于推送用户最有可能点击的物品。广告平台(比如微信)当然希望有一个模型能够准确预测出用户最可能点击的广告,这样可以增加平台收益。 17 | 18 | 预测点击概率需要用到用户信息和物品信息,通常是将多种信息融合在一个向量中。离散的用户和物品的属性,就采用 one-hot 表示,连续的属性可以归一化后直接使用。训练样本为 $(x, y)$,其中 $x$ 是一个高维稀疏矩阵,其中包含 user 和 item 的信息,`$y \in\{0,1\}$` 表示用户是否点击了 item。 19 | 20 | 特征间的组合常常很有用,比如从用户点击数据中发现在吃饭的时间用户常常下载点外卖的软件,这说明 time 和 app category 有很大关系。另外可能在数据中发现男孩子常常玩射击类游戏,这说明用户性别和年龄与游戏类别存在很大的联系。 21 | 22 | 要从数据中捕获以上提到的特征,需要模型能够组合不同的特征。线性模型无法进行特征组合,仅能学习到不同特征的权重。为此人们做了特征工程,向线性模型中加入 `$x_ix_j$` 这样的组合特征,为了解决组合特征参数过多的问题,提出了 Factorization Machines。 23 | 24 | 但是 FM 因为计算量的问题也常常只能引入二阶特征(两个特征的组合),为了能够引入更加强大的特征组合。Wide & Deep 模型被提出,结合线性模型和深度神经网络,试图让模型学习到更加复杂的特征。 25 | 26 | 本文的 DeepFM 和 Wide & Deep 的动机,我感觉是差不多的,只是 DeepFM 提出了一种看起来更加简洁的模型。 27 | 28 | ## DeepFM 29 | 30 | DeepFM 的模型架构图如下: 31 | 32 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/06/5d71d1e5451253d17890cf77.jpg) 33 | 34 | 单看此图肯定时看不明白的,稍加解释如下: 35 | 36 | 37 | 模型架构图的左边是一个 FM 模型,FM 的输入就是高维的稀疏向量,这个向量是不同属性的 one-hot 向量拼接得来的。上图中输入向量下面的 Field i 就是一个属性对应的 one-hot 向量。如果熟悉 FM 就知道,FM 模型会对每一个特征学习到一个低维的稠密向量,可以视为特征的 Embedding。 38 | 39 | 右面是一个深度神经网络模型,高维的稀疏向量中每一个 Filed 中只有一个维度有值,每一个 Filed 对应的 one-hot 向量,可以经过一个 Embedding 层转换为低维的稠密向量。Embedding 层中的各个特征的 Embedding 同时也用于 FM 模型。 40 | 41 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/06/5d71d3a3451253d1789101d2.jpg) 42 | 43 | 论文中视各个 Filed 都是离散的,都可以表示为 one-hot 向量,但如果某个属性是连续值,该怎么办?可以直接把连续值放入神经网络,也可以使用这个特征对应的 Embedding。 44 | 45 | FM 模型可以表示为: 46 | 47 | $$ 48 | y_{F M}=\langle w, x\rangle+\sum_{j_{1}=1}^{d} \sum_{j_{2}=j_{1}+1}^{d}\left\langle V_{i}, V_{j}\right\rangle x_{j_{1}} \cdot x_{j_{2}} 49 | $$ 50 | 51 | 深度模型的输入为: 52 | 53 | $$ 54 | a^{(0)}=\left[e_{1}, e_{2}, \ldots, e_{m}\right] 55 | $$ 56 | 57 | 其中 $e_i$ 就是第 Field i 对应的 Embedding,不同 Field 的 Embedding 拼接起来得到一个稠密向量,输入到全连接的神经网络中。神经网络模型可以表示为: 58 | 59 | $$ 60 | a^{(l+1)}=\sigma\left(W^{(l)} a^{(l)}+b^{(l)}\right) 61 | $$ 62 | 63 | $$ 64 | y_{D N N}=\sigma\left(W^{|H|+1} \cdot a^{H}+b^{|H|+1}\right) 65 | $$ 66 | 67 | 最终整个 DeepFM 模型可以表示为: 68 | 69 | $$ 70 | \hat{y}=\operatorname{sigmoid}\left(y_{F M}+y_{D N N}\right) 71 | $$ 72 | 73 | ## 总结 74 | 75 | FM 解决的是输入比较稀疏时,组合特征不好学习的问题。DeepFM 提供了一种结合低阶和高阶特征的方法,联合训练 FM 和 神经网络,让模型抽取到更加丰富的特征。和 Wide & Deep 模型相比,DeepFM 中浅层网络和深层网络使用的输入是相同的。Wide & Deep 网络中 Wide 部分的的输入还是需要做特征工程,而在 DeepFM 中则不需要特征工程或需要的更少。 -------------------------------------------------------------------------------- /_posts/web/2016-05-18-web-security.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Web 安全 4 | category: Web 5 | --- 6 | 7 | 8 | 9 | - * 10 | {:toc} 11 | 12 | 13 | ## XSS 14 | 15 | XSS (Cross Site Script)为了区别于 CSS ,缩写为 XSS。XSS 是指黑客通过 HTML 注入修改页面内容,插入恶意脚本,在用户访问页面的时候,对用户发起攻击的行为。 16 | 17 | 对于下面这个例子,我直接将用户输入的内容添加在页面中,这就存在非常明显的 XSS 漏洞。 18 | 19 | ```html 20 | 21 | 22 | 23 |
24 | 32 | 33 | ``` 34 | 35 | 如果攻击者在文本框中输入如下内容,页面中就会弹出对话框来,攻击者还可以通过此漏洞插入外部脚本在该网页中。 36 | 37 | ``` 38 | 39 | ``` 40 | 41 | 对于 XSS 按照其表现形式不同可以分为下面几种: 42 | 43 | - 反射型 XSS 44 | - 存储型 XSS 45 | 46 | ### 反射型 XSS 47 | 48 | 前面的例子就是一个反射型 XSS ,它只是简单地将输入内容反射给浏览器。很多活动现场,常常看到各种微博、微信留言墙,它允许用户通过微信输入内容,将内容展现在另外一个大屏幕上,如果在将传输过来的字符串插入到 Web 页面上之前,没有过滤 JavaScript 脚本,就出现了 XSS 漏洞。参与互动的人如果输入了恶意脚本改变了页面中的内容,这就会产生很不好的影响。 49 | 50 | 因此对于需要插入到 Web 页面上的内容,在插入之前一定要小心 XSS 攻击,比较简单的方法是将要插入的字符串进行转译。 51 | 52 | ### 存储型 XSS 53 | 54 | 在一些论坛中,允许使用者发布一些帖子,恶意用户可能输入一些破坏性的脚本,然后这些内容被保存到了服务器上,下一个访问该网页的用户会下载这些恶意内容,其中就包含恶意脚本。这样造成的结果是每个访问该页面的用户都遭到了攻击。 55 | 56 | 57 | ## XSS 的防御手段 58 | 59 | ### 给关键的 cookie 设置 httpOnly 标记 60 | 61 | 给关键的 cookie 设置了 httpOnly 标记后可以防止 javascript 读取这些 cookie ,这从一定程度上避免了 cookie 劫持的发生。 62 | 63 | ### 输入检查 64 | 65 | 对用户输入的内容,要进行敏感信息检查,过滤掉 javascript ,script 等字样,对 `" , ' , < , >` 等特殊的字符进行转义。 66 | 67 | ### 输出检查 68 | 69 | 当要把内容输出到 HTML 页面上的时候,可以通过字符编码或转义的方式防止 XSS 攻击。 70 | 71 | 一般要对下面一些字符进行转换: 72 | 73 | ``` 74 | & -> & 75 | < -> < 76 | > -> > 77 | " -> " 78 | ' -> ' 79 | / -> / 80 | ``` 81 | 82 | ## CSRF 83 | 84 | CSRF (Cross Site Request Forgery),跨站点请求伪造。 85 | 86 | 当用户访问了网站 A 之后,该网站在用户的浏览器中留下了 cookie ,当该用户在之后访问到恶意网站后,这个网站可能向先前网站发起请求,而这些请求中会携带网站 A 的 cookie。 87 | 88 | 为了防止 CSRF 可以采用以下措施: 89 | 90 | - Referer Check:通过检查 HTTP 请求头部中的 referer 字段可以检查请求是否来自合法的“源”地址,但有的时候浏览器不会发送 referer 头信息。 91 | 92 | ## 点击劫持 93 | 94 | X-Frame-Options 这个字段是为了防止 ClickJacking 而生的。有以下几个可选值: 95 | 96 | - DENY 97 | - SAMEORIGIN 98 | - ALLOW-FROM origin 99 | 100 | 当值为 DENY 的时候会拒绝当前页面被加载在任何 frame 中。为 SAMEORIGIN 则要求加载该页面的 frame 需要和该 frame 同源。当值为 ALLOW-FROM 的时候,则可以指出允许加载该页面的源地址。 101 | 102 | ## HTML5 安全 103 | 104 | 105 | ### iframe 106 | 107 | HTML5 中专门为 iframe 定义了一个新属性 - sandbox, 这个属性可以控制 iframe 中加载的资源可以执行的动作。 108 | 109 | ### a 标签 110 | 111 | 在 a 标签中指定了 noreferrer 之后,发起的请求中就不会携带 referer 这个头部信息,因为 referer 可能会泄漏一些信息。 112 | -------------------------------------------------------------------------------- /_posts/tools/2018-11-30-jupyterlab.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Jupyter Lab 4 | category: 工具 5 | --- 6 | 7 | 8 | 9 | 本文为我在用 Jupyter Lab 时的备忘笔记,会持续更新。 10 | 11 | ## 简介 12 | 13 | 做数据科学的同学对 jupyter notebook 应该很熟悉,但他的一个缺点是不能够同时打开多个文件。jupyter lab 可以算的上是一个简易的 IDE,你可以同时打开多个窗口,打开终端,对窗口进行分割,同时编辑不同类型的文件。 如果正在使用 jupyter notebook,那么没有理由不切换到 jupyter lab 上。 14 | 15 | 其主界面如下: 16 | 17 | ![jupyter lab 界面](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/05/14/5cda4759697df1fd0cc1e1e9.jpg) 18 | 19 | jupyter lab 的文档在 [JupyterLab Documentation](https://jupyterlab.readthedocs.io/en/stable/)。 20 | 21 | ## 安装与启动 22 | 23 | ```sh 24 | # 安装 25 | pip install jupyterlab 26 | 27 | # 启动 jupyter lab 28 | jupyter lab 29 | ``` 30 | 31 | 32 | ## 通过密码访问 33 | 34 | 在启动 jupyter lab 之后,默认是通过在 url 后面跟上一个 token 来访问的。有时候关闭了页面之后,往往又需要去控制台复制这个带有 token 的 url,常常显得不够方便,为此可以设置一个密码,通过密码来访问。 35 | 36 | 先生成配置文件,然后配置密码,命令如下: 37 | 38 | ```sh 39 | $ jupyter notebook --generate-config 40 | $ jupyter notebook password 41 | ``` 42 | 43 | 这会要求用户输入需要设置的密码。设置完成之后,就可以通过输入密码来访问 jupyter lab 了。 44 | 45 | ## 在服务器上部署 46 | 47 | 一种场景是,希望在服务器上运行 jupyter lab,然后可以在任何地方,使用任何设备访问到 jupyter lab 环境。默认情况下,只能通过 `http://localhost:8888` 这个地址访问,通过服务器的 IP 是访问不了的,需要做一些配置。 48 | 49 | 初始阶段 jupyter lab 采用默认配置,如果需要对其个性化配置,需要先生成配置项: 50 | 51 | ```sh 52 | # 生成配置文件 53 | $ jupyter notebook --generate-config 54 | ``` 55 | 56 | 这个时候在用户根目录下的 `.jupyter` 目录下,就多出来了一个 `jupyter_notebook_config.py` 文件,在这个文件里,用户可以对 jupyter lab 进行个性化配置。 57 | 58 | 需要修改的地方主要有下面几处: 59 | 60 | ```python 61 | # 当你使用服务器的 ip 访问的时候,可以不配置这一项 62 | # 当时如果使用外网穿透技术,访问的 ip 不是部署 jupyter lab 的机器的 ip 的时候, 63 | # 就需要配置这个了,否则部分功能无法正常工作 64 | c.NotebookApp.allow_origin = '*' 65 | 66 | # 修改可以通过本机的任意一个 ip 地址来访问 jupyter lab 环境 67 | c.NotebookApp.ip = '0.0.0.0' 68 | 69 | # 关闭自动打开浏览器的行为 70 | c.NotebookApp.open_browser = False 71 | 72 | # 修改端口,默认为 8888,根据自己的需要修改 73 | c.NotebookApp.port = 8000 74 | ``` 75 | 76 | ## Magic Command 77 | 78 | 此命令可以查看函数的性能瓶颈,看到函数每一行的运行次数和时间。 79 | 80 | ```python 81 | %load_ext line_profiler 82 | 83 | %lprun -f Quick.sort Quick.sort(nums) 84 | ``` 85 | 86 | ## 快捷键 87 | 88 | Notebook 存在两种模式,命名模式和编辑模式,可以使用 Esc 从编辑模式切换为命令模式,使用 Enter 进入编辑模式。 89 | 90 | 在命令模式下,有下列快捷键可以使用: 91 | 92 | m: 将 cell 切换为 markdown 模式 93 | 94 | y: 将 cell 切换为代码模式 95 | 96 | D+D: 删除 cell 97 | 98 | Shift+↑ / ↓: 选中多个 cell,选中后可以进行删除、复制、粘贴、运行等操作。 99 | 100 | Shift+M: 合并选中的 cells 101 | 102 | Shift+Tab: 代码提示,光标定位到某个函数、某个模块的时候,按这个组合键会得出提示 103 | 104 | 在编辑模式,有下面一些技巧: 105 | 106 | 输入 `?function-name` 可以得到对应函数的签名和文档,由此可以确定函数的输入和功能等信息,比如: 107 | 108 | ``` 109 | ?len 110 | ``` 111 | 112 | 输入 `??function-name` 可以得到函数更加详细的信息,包括源代码。 -------------------------------------------------------------------------------- /_posts/network/2020-02-20-https.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: HTTPs 详解 4 | category: 网络 5 | --- 6 | 7 | * - 8 | {:toc} 9 | 10 | ## HTTPs 简介 11 | 12 | HTTP 基于明文进行传输,在传输过程中可能遭遇劫持,网络的中间节点可以更改 HTTP 传输内容。在 HTTPs 广泛应用之前,网页被篡改的情况时有发生。有不法分子在别人的网页中插入了广告,以此牟利。更糟糕的是,基于 HTTP 传输的隐私信息会被他人获取。 13 | 14 | 为了让 HTTP 能够安全地传输 HTTPs 被提出来。HTTPs 并不是什么新的协议,它是在 HTTP 和 TCP 之间加入了一个安全层(Secure Sockets Layer),这个中间层,就负责加解密。通俗地讲,把 HTTP 报文加密后交给传输层,把传输层收到的报文解密后交给应用层。如此,在网络上传输的信息就是加密过的,对方没有办法篡改。 15 | 16 | ## 加密方式 17 | 18 | 在了解 HTTPs 之前,先简要了解一下两种在 HTTPs 中会用到的加密方式。 19 | 20 | ### 对称密钥加密 21 | 22 | 加密和解密采用同一个密钥,过程如图: 23 | 24 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/01/2020-07-01-165952.png) 25 | 26 | 至于加解密是具体如何操作,这里就不深究了。只需要了解,对称加密中,加密和解密都采用同一个密钥。 27 | 28 | ### 非对称密钥加密 29 | 30 | 也称为公开密钥加密,这种加密方式有两个密钥——公钥和私钥。其中公钥用于加密,私钥用于解密。公钥和私钥是成对的,而且公钥往往是公开的。通信双方,分别持有公钥和私钥,使用公钥加密后的内容,必须使用私钥才能解密。 31 | 32 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/01/2020-07-01-170255.png) 33 | 34 | ## HTTPs 中的加密过程 35 | 36 | 在每个支持 HTTPs 的服务器上都存有一对公钥和私钥。客户端想要建立连接时,服务器会把公钥发送给客户端。客户端生成一个用于对称加密的密钥,使用服务器发来的公钥进行加密,然后发给服务器。这样客户端和服务器都有了客户端上生成的对称加密的密钥的。双方可以使用对称加密算法来实现加密传输了。 37 | 38 | 可以看到,这里使用非对称加密来保证对称加密的密钥的安全传输。此后的加密都采用对称加密完成。 39 | 40 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/01/2020-07-01-170959.png) 41 | 42 | 上面的图中清晰地展现了这一过程,其中的 `session key` 就是对称加密的密钥。 43 | 44 | ## 数字证书 45 | 46 | ### 数字证书的作用 47 | 48 | 采用上一节中描述的方法,传输内容就不会被他人截获了,就算被抓了包,得到的也是密文。但是假如 DNS 被劫持了,你登录银行的网站 `bank.com`,在解析域名的时候 DNS 服务的流量被劫持了,你连接到了骗子的服务器上。因为 `bank.com` 解析的 IP 并不是该银行的服务器的 IP 而是骗子的服务器。此时网址虽然显示的是 `bank.com`,此时你却连接到骗子的服务器上。之后你输入密码,都传到了骗子哪里。就算传输的内容是加密过的,那也无济于事,因为此时确实是和骗子在通信。 49 | 50 | 为了保证与之通信的服务器是真实的,数字证书被引入 HTTPs。每个合规的 HTTPs 服务器都需要有一个数字证书,这个数字证书由某个第三方机构颁发,即数字证书认证机构(CA,Certificate Authority)。这是一个客户端和服务器都信赖的第三方机构。 51 | 52 | 数字证书用于证明服务器的真实性,保障服务器和浏览器之间的通信安全,验证网站的真实身份,区别于钓鱼欺诈网站。 53 | 54 | ### 数字证书的生成过程 55 | 56 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/01/2020-07-01-180829.png) 57 | 58 | 59 | 要想得到证书,需要去数字证书认证机构(CA)申请,CA 核实了用户身份之后,会给申请者颁发一个数字证书。同时 CA 会在该数字证书上签名,证明这个数字证书真的是自己颁发出去的。 60 | 61 | 首先 CA 会产生一个公钥和密钥对,这是服务器后期用于非对称加密用的。CA 把其中的公钥作为上图中左侧中的 Data。CA 哪里有另外一个公钥和私钥对,这是是 CA 自己的密钥对。CA 把申请者的公钥做 hash 之后,使用自己的密钥进行加密,此加密内容为签名。然后把用户的公钥和签名拼接起来。 62 | 63 | 最终申请者得到数字证书里面包含有效日期、对应的域名、CA 的信息等等,另外还包含公钥和公钥的签名。 64 | 65 | ### 数字证书的验证方法 66 | 67 | 在通信的时候,服务器把证书发给客户端,客户端收到以后需要检验此证书的真实性。如果证书是假的,说明对方不值得信赖。因为如果证书是假的,说明服务器的拥有者没有得到 CA 的认可,为什么没有得到认可?因为它是骗子。他没有办法提供有效的身份证明,以说明自己是该域名的拥有者。 68 | 69 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/07/01/2020-07-01-174432.png) 70 | 71 | 客户端收到数字证书之后,可以检查该数字证书对应的是不是该网站。如果是,可以进一步检查证书是不是真的。如果是真的,就可以从中取出公钥,基于上一节提到的方法来加密通信了。 72 | 73 | 问题的关键是如何验证证书的真伪。 74 | 75 | 请看上上幅图 _数字证书签名和验证过程_ 的右半部分。用户对公钥运行同样的 hash 算法得到左边的 hash 值。然后使用 CA 的公钥对数字证书的签名进行解密,而后比较两个 hash 值是否相同。如果相同,那说明此数字证书的签名确实是 CA 的签名。如果是旁人的签名,那么解密出来的两个 hash 一定不相同。 76 | 77 | CA 的公钥从何而来呢?因为全球 CA 的数量时有限的,多数浏览器已经内置了 CA 的公钥。CA 的密钥只有 CA 知道,需要高度保密。 -------------------------------------------------------------------------------- /_posts/rec/2019-09-04-wide-deep.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 论文阅读 - Wide & Deep Learning for Recommender Systems 4 | category: 推荐系统 5 | tag: 推荐系统 6 | --- 7 | 8 | 本文为阅读论文 Wide & Deep Learning for Recommender Systems 时记下的笔记。 9 | 10 | - * 11 | {:toc} 12 | 13 | 14 | ## 背景 15 | 16 | 推荐系统给出的结果需要兼顾相关性和新颖性。推荐的内容和用户特征很匹配,就会推荐大量相关内容,时间一久,用户感到无新鲜感。推荐内容过于泛化,用户的兴趣无法满足。本文提出的算法用于 Google Play 的 APP 推荐系统。 17 | 18 | ## 线性模型 19 | 20 | 线性模型中各类特征常采用 one-hot 向量表示,比如“国家”属性,可取的值有 200 多个,要表示“国家”就采用一个 200 多个维度的向量,每一个国家占一个维度。类似地,其他属性也这样表示。如此以来,对某个事物的向量表示,就是拼接各个属性对应的 one-hot 向量,整个向量表示是非常稀疏的。 21 | 22 | ``` 23 | 国家:[0 0 0 1 0 0 ...] 24 | 性别: [1 0] 25 | ``` 26 | 27 | 用户安装过的 APP,可以有多个,采用 bag-of-word 表示,每个维度表示一个 APP 是否安装。 28 | 29 | ``` 30 | APP: [0 0 1 0 1 0 0 ...] 31 | ``` 32 | 33 | 不同的特征之间可以进行组合,比如将国家和性别属性组合,可以表示如“中国男性”这样的组合属性。特征的组合能够给线性模型增加非线性的特征。但是也会极大地增大特征维度。 34 | 35 | 线性模型的使用的特征向量具有维度高、稀疏的特点。线性模型的形式如下: 36 | 37 | $$ 38 | y = \mathbf{w}^T\mathbf{x} + b 39 | $$ 40 | 41 | 由于特征维度很大,往往没有足够的数据来训练模型中的每个参数。因为有的特征组合在数据集中根本就没有出现,或是数量很少。因此对数据集中未出现的情况,模型无法进行泛化。 42 | 43 | 论文中提到了 generalization 和 memorization 这两个词,我读的论文少,对这两个概念理解的还不够透彻。 44 | 45 | 说线性模型有较好的 memorization,我想是指线性模型能够较好地学习到各个特征(包含组合特征)的权重,以及学习到特征之间的相关性。 46 | 47 | ## 深度模型 48 | 49 | 深度模型,常常将某一个属性表示为一个低维的稠密向量,比如“国家”这个属性,可能会将不同的国家表示为一个长度为 10 的向量,这也常被称为 Embedding。如此以来,对某事物的向量表示就是一个较低维度且稠密的向量。然后使用深度网络模型,可以对 Embedding 中各维进行组合。 50 | 51 | 深度模型可以实现很好泛化,当训练数据较稀疏的时候,甚至不能反映训练集的特征。即,太过泛化。好处就是能够应对数据稀疏的场景,缺点常常会得出结果不够相关。 52 | 53 | 泛化大致是指,基于属性相关性的传递,发现过去没有或很少发生的新的特征组合,有利于增加推荐的多样性。 54 | 55 | ## Wide & Deep 56 | 57 | Google 在 2016 年发布的 Wide & Deep 模型,组合了线性模型和深度模型。该模型结合了线性模型的记忆能力和深度模型的泛化能力。因为线性模型的输入是维度很高的向量,模型的输入很 Wide,Wide 出自于此。 58 | 59 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/04/5d6f76ff451253d17822413b.jpg) 60 | 61 | 62 | ## 推荐系统框架 63 | 64 | 工业推荐系统基本都是 Matching 和 Ranking 两部分,Matching 用于从数据库中先粗略地检索出相关内容,极大地减小 item 的数量。Ranking 则对检索出的 item 做更加细致的排序,最终生成推荐。 65 | 66 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/04/5d6f77af451253d178227f50.jpg) 67 | 68 | 这里 Google Play 采用的策略也是如此。 69 | 70 | 71 | ## Wide & Deep 模型架构 72 | 73 | Wide 模型和 Deep 模型是联合起来训练的,如下图所示: 74 | 75 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/04/5d6f79c0451253d178234980.jpg) 76 | 77 | 图中左边是深度模型,将连续属性做归一化。离散属性做 Embedding 后,拼接起来,输入全连接网络。 78 | 79 | $$ 80 | a^{(l+1)}=f\left(W^{(l)} a^{(l)}+b^{(l)}\right) 81 | $$ 82 | 83 | 右边是线性模型,使用了用户安装的 APP 和曝光的 APP 以及两者的组合作为特征。 84 | 85 | 将 Wide 和 Deep 的输出进行求和然后交给 sigmoid 函数求出概率。最终使用 Logistics 损失函数(就是 Logistics Regression 用的损失函数)来作为优化目标,使用 SGD 进行训练。 86 | 87 | $$ 88 | P(Y=1 | \mathbf{x})=\sigma\left(\mathbf{w}_{\text {wide}}^{T}[\mathbf{x}, \phi(\mathbf{x})]+\mathbf{w}_{\text {deep}}^{T} a^{\left(l_{f}\right)}+b\right) 89 | $$ 90 | 91 | Wide & Deep 模型和模型的集成不是一回事。集成是训练多个模型然后将结果进行集成,每个模型都使用了全部的样本特征。集成学习训练的多个模型是独立存在的,并不知道其他模型的存在。而这里用到的 Wide & Deep 联合学习,能够有效地组合各类特征,两个模型协同地优化目标函数。 92 | 93 | ## 总结 94 | 95 | Wide & Deep 模型,在今天看来好像并不新颖,它提出了一种组合深层特征和浅层特征的方法。感觉和图像处理中用到的 ResNet 有相同的思想。 -------------------------------------------------------------------------------- /site/assets/css/scss/common.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | .post-info { 3 | height: .15rem; 4 | font-size: .12rem; 5 | color: $secondary-color; 6 | padding: .1rem 0; 7 | list-style: none; 8 | margin-right: 1em; 9 | span { 10 | margin-right: 1em; 11 | } 12 | } 13 | 14 | .page { 15 | &__title { 16 | font-size: .22rem; 17 | color: $title-color; 18 | border-bottom: 1px solid $border-color; 19 | line-height: 1.8em; 20 | } 21 | } 22 | 23 | .page__header{ 24 | margin-bottom: 40px; 25 | } 26 | 27 | @each $sel in ('.list-item', '.page__title') { 28 | @media screen and (max-width: 500px){ 29 | #{$sel}{ 30 | border-bottom: none!important; 31 | position: relative; 32 | &::after{ 33 | content: ""; 34 | display: block; 35 | width: 100%; 36 | height: 1px; 37 | position: absolute; 38 | bottom: 0; 39 | left: 0; 40 | transform-origin: left bottom; 41 | transform: scaleY(0.5) translateZ(0); 42 | box-shadow: inset 0 0 0 1px #ddd; 43 | } 44 | } 45 | } 46 | 47 | @media screen and (min-resolution: 2dppx) and (max-width: 500px){ 48 | #{$sel}{ 49 | &::after{ 50 | box-shadow: inset 0 0 0 0.5px #ddd; 51 | } 52 | } 53 | 54 | } 55 | 56 | @media screen and (min-resolution: 3dppx) and (max-width: 500px){ 57 | #{$sel}{ 58 | &::after{ 59 | box-shadow: inset 0 0 0 0.333333px #ddd; 60 | } 61 | } 62 | } 63 | } 64 | 65 | 66 | 67 | 68 | 69 | 70 | @each $sel in ('#markdown-toc', '.typo pre', '.tag-list a', '#tweet .tweet-item') { 71 | @media screen and (max-width: 500px){ 72 | #{$sel}{ 73 | box-shadow: 0 0 0 1px $border-color; 74 | border: none!important; 75 | } 76 | } 77 | 78 | @media screen and (min-resolution: 2dppx) and (max-width: 500px){ 79 | #{$sel}{ 80 | box-shadow: 0 0 0 0.5px $border-color; 81 | } 82 | 83 | } 84 | 85 | @media screen and (min-resolution: 3dppx) and (max-width: 500px){ 86 | #{$sel}{ 87 | box-shadow: 0 0 0 0.333333px $border-color; 88 | } 89 | } 90 | 91 | } 92 | 93 | 94 | 95 | #vcomments { 96 | margin-top: 100px; 97 | } 98 | 99 | #vcomments .vempty { 100 | font-size: .14rem; 101 | } 102 | 103 | #vcomments .vinput { 104 | font-size: .14rem!important; 105 | } 106 | 107 | i.icon { 108 | margin-right: .3em; 109 | } 110 | 111 | .loading { 112 | width: 100%; 113 | background-image: url(/site/assets/images/loading.gif); 114 | background-repeat: no-repeat; 115 | background-position: center center; 116 | height: 300px; 117 | } -------------------------------------------------------------------------------- /_posts/web/css/2015-09-10-css-flex.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: CSS flex 布局 4 | category: Web 5 | pid: css 6 | --- 7 | 8 | - toc 9 | {:toc} 10 | 11 | ```html 12 |
13 |
14 |
15 |
16 |
17 | ``` 18 | 19 |
20 | 21 | flex容器存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis) 22 | 23 | ## 容器的属性 24 | 25 | - flex-direction 26 | - flex-wrap 27 | - flex-flow 28 | - justify-content 29 | - align-items 30 | - align-content 31 | 32 | ### flex-direction 33 | 34 | 决定了主轴的方向,可选值: 35 | 36 | - row 37 | - row-reverse 38 | - column 39 | - column-reverse 40 | 41 | ### flex-wrap 42 | 43 | 当容器内的元素的宽度或者高度之和大于容器的宽度或高度的时候,这个属性决定容器类元素是否折行,以及如何折行。 44 | 45 | 有三个值可选: 46 | 47 | - nowrap:不折行,容器内的弹性子元素的宽度或者高度会被压缩,以至于容器能够容纳所有子元素 48 | - wrap:折行,放不下的元素折行在下面(flex-direction:row)或者右面(flex-direction:column)显示 49 | - wrap-reverse:折行,放不下的元素折行在上面(flex-direction:row)或者左面(flex-direction:column)显示 50 | 51 | ### flex-flow 52 | 53 | 这个属性是 flex-direction 和 flex-flow 的简写形式。写法形如:`flex-flow:row wrap` 54 | 55 | ### justify-content 56 | 57 | 定义了子元素在主轴上的对齐方式,可选值为: 58 | 59 | - flex-start 60 | - flex-end 61 | - center 62 | - space-between:两端对其 63 | - space-around:每个项目两侧具有同样的间隔 64 | 65 | ### align-items 66 | 67 | 定义了子元素在交叉轴上的对齐方式,可选值为: 68 | - flex-start 69 | - flex-end 70 | - center 71 | - baseline:以文字的基线对齐 72 | - stretch:如果元素的高度或者宽度为 auto 将占满整个容器的高度(flex-direction:row)或者宽度(flex-direction:column) 73 | 74 | ### align-content 75 | 76 | 定义了多根轴线的对齐方式,可选值为: 77 | 78 | - flex-start 79 | - flex-end 80 | - center 81 | - space-between 82 | - space-around 83 | - stretch 84 | 85 | 当存在多根轴线的时候,这个属性才生效,且设置了这个属性以后,align-items 将不起作用 86 | 87 | ## 项目的属性 88 | - order 89 | - flex-grow 90 | - flex-shrink 91 | - flex-basis 92 | - flex 93 | - align-self 94 | 95 | ### order 96 | 97 | 定义项目的排列顺序。数值越小,排列越靠前,默认为0 98 | 99 | ### flex-grow 100 | 101 | 定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。 102 | 103 | 如果一个弹性元素其宽度为200px,其中包含两个元素,两个元素的宽度都是 50px 且 flex-grow 都是 1,那么这两个元素将平分余下的 100px ,所以结果是两个元素的实际宽度都是 100px。 104 | 105 | ### flex-shrink 106 | 107 | 属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。 108 | 109 | 如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。 110 | 111 | 如果一个弹性元素其宽度为200px,其中包含两个元素,两个元素的宽度都是 200px 且 flex-shrink 都是 1。因为两个元素一共超出了父元素 200px,所以这两个元素将缩小 100px ,所以结果是两个元素的实际宽度都是 100px。 112 | 113 | 如果其中一个元素的 flex-shrink 是 0 另外一个是 1,那么 flex-shrink 为 1 的元素将独自减小 200px。 114 | 115 | ### flex-basis 116 | 117 | 取值同 width 属性,可以是具体的像素值,也可以是百分比,还可以是 auto ,默认是 auto。用于定义元素占据主轴的宽度。width 也可以描述元素的宽度,但如果同时设置了 width 和 flex-basis 那么 flex-basis 会覆盖 width 属性。 118 | 119 | ### flex 120 | 121 | flex-grow, flex-shrink 和 flex-basis 的简写形式,写法形如:`flex:1 1 auto` 122 | 123 | ### align-self 124 | 125 | 允许单个项目与其他项目有不同的对其方式,可以覆盖 align-items 属性。可选值如下: 126 | 127 | - auto 128 | - flex-start 129 | - flex-end 130 | - center 131 | - baseline 132 | - stretch -------------------------------------------------------------------------------- /_posts/unix/signal.md: -------------------------------------------------------------------------------- 1 | ## 信号 2 | 3 | ### signal 4 | 5 | 6 | ### 信号集 7 | 8 | ``` 9 | int sigemptyset (sigset_t *__set) 10 | int sigfillset (sigset_t *__set) 11 | int sigaddset (sigset_t *__set, int __signo) 12 | int sigdelset (sigset_t *__set, int __signo) 13 | ``` 14 | 15 | ### 信号掩码 16 | 17 | 内核会为每一个进程维护一个信号掩码,即一组信号,并将阻塞这些信号向该进程进行传递,直到这些遭到阻塞的信号解除阻塞为止。 18 | 19 | 有如下方式向信号掩码中添加信号: 20 | 21 | 1. 当调用某信号处理程序时,可将引发调用的信号添加到信号掩码中。是否添加,取决于安装信号处理程序时的设置。 22 | 2. 使用 `sigaction` 函数建立信号处理程序时,可以指定额外一组信号,在调用该处理程序的时候,将这些信号阻塞。目的就是为了避免该信号处理程序被其他信号打断。 23 | 3. 使用 `sigprocmask` 系统调用,可以显式地向信号掩码中添加或移除信号。 24 | 25 | ```c++ 26 | int sigprocmask (int __how, const sigset_t *__restrict __set, sigset_t *__restrict __oset); 27 | ``` 28 | 29 | how 可以取3个常量,`SIG_BLOCK`,`SIG_UNBLOCK`,`SIG_SETMASK`,用于说明如何修改掩码。下面是一个例子,注释起来的部分,不会被 `SIGINT` 信号中断。 30 | 31 | ```c++ 32 | sigset_t block_set, old_set; 33 | 34 | sigemptyset(&block_set); 35 | sigaddset(&block_set, SIGINT); 36 | 37 | sigprocmask(SIG_BLOCK, &block_set, &old_set); 38 | 39 | // code that should not be interrupted by SIGINT 40 | 41 | sigprocmask(SIG_SETMASK, &old_set, nullptr); 42 | ``` 43 | 44 | ### sigaction 45 | 46 | ```c++ 47 | int sigaction (int __sig, const struct sigaction *__restrict __act, 48 | struct sigaction *__restrict __oact); 49 | 50 | struct sigaction{ 51 | __sighandler_t sa_handler; 52 | 53 | /* Additional set of signals to be blocked. */ 54 | __sigset_t sa_mask; 55 | 56 | /* Special flags. */ 57 | int sa_flags; 58 | 59 | /* Restore handler. */ 60 | void (*sa_restorer) (void); 61 | }; 62 | ``` 63 | 64 | `sa_mask` 字段定义一组信号,在调用 `sa_handler` 所定义的处理程序时,阻塞该组信号。另外,引发中断的信号,也将自动加入信号掩码中。因此,同一个信号多次抵达,不会递归调用自己。 65 | 66 | ### 等待信号 67 | 68 | ```c++ 69 | int pause(); 70 | ``` 71 | 72 | 调用 `pause` 将暂停进程的执行,直到信号处理器中断该调用为止。 73 | 74 | 75 | ### 信号处理函数的设计 76 | 77 | 1. 在信号处理函数内设置某个全局变量,在主程序中周期性轮询此变量。 78 | 2. 创建一个管道,信号处理函数中向管道中写入数据,在主函数中监听管道的文件描述符。 79 | 80 | 81 | ### 实现 `abort` 82 | 83 | ```c++ 84 | void abort(){ 85 | sigset_t mask; 86 | struct sigaction action{}; 87 | 88 | sigaction(SIGABRT, nullptr, &action); 89 | if(action.sa_handler == SIG_IGN){ 90 | action.sa_handler = SIG_DFL; 91 | sigaction(SIGABRT, &action, nullptr); 92 | } 93 | if(action.sa_handler == SIG_DFL){ 94 | fflush(nullptr); 95 | } 96 | 97 | // block other signal excepting SIGABRT 98 | sigfillset(&mask); 99 | sigdelset(&mask, SIGABRT); 100 | sigprocmask(SIG_SETMASK, &mask, nullptr); 101 | raise(SIGABRT); 102 | 103 | // process caught SIGABRT and returned 104 | 105 | fflush(nullptr); 106 | action.sa_handler = SIG_DFL; 107 | sigaction(SIGABRT, &action, nullptr); 108 | sigprocmask(SIG_SETMASK, &mask, NULL); 109 | raise(SIGABRT); 110 | } 111 | ``` 112 | 113 | 1. 如果当前 `SIGABRT` 信号被忽略,那就修改为默认处理函数 114 | 2. 如果是默认处理函数,那就刷新标准 IO 的缓冲区,因为 `SIGABRT` 信号的默认动作是退出进程,需要在此之前刷新标 IO 的缓冲区。 115 | 3. 阻塞除了 `SIGABRT` 之外的其他信号,防止其他信号打断 `SIGABRT` 的处理函数 116 | 4. 使用 `raise` 向当前进程发送 `SIGABRT` 信号 117 | 5. 如果还会返回回来,说明调用了用户自定义的处理函数。这期间可能还做了一些 IO 操作,因此需要刷新 118 | 6. 把处理函数设置为默认,然后再次发送信号。这一次,一定会调用默认处理函数。 119 | 120 | -------------------------------------------------------------------------------- /_posts/rec/2019-09-05-FM.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 论文阅读 - Factorization Machines 4 | category: 推荐系统 5 | tag: 推荐系统 6 | --- 7 | 8 | 本文为阅读论文 Factorization Machines 时记下的笔记。 9 | 10 | - * 11 | {:toc} 12 | 13 | ## 线性模型 14 | 15 | 线性模型,如 logistics regression 仅学习到输入特征的权重,无法利用组合特征。可以将特征彼此相乘,给线性模型引入非线性特征。如下式所示: 16 | 17 | $$ 18 | \hat{y}(x) := \underbrace {w_0 + \sum_{i=1}^{n} w_i x_i }_{\text{线性回归}} + \underbrace {\sum_{i=1}^{n} \sum_{j=i+1}^{n} w_{ij} x_i x_j}_{\text{交叉项(组合特征)}} 19 | $$ 20 | 21 | 如果输入特征 $x$ 的维度 $\vert x \vert = n$,整个模型的参数量为 $1 + n + n^2$。上式中交叉项 $x_ix_j$ 的系数 $w_{ij}$ 需要依赖特征 $x_i$ 和 $x_j$ 来训练得出。当输入向量 $x$ 很稀疏的时候。比如 $x$ 是使用 bag-of-word 表示的文档。当特征 $x_i$ 和 $x_j$ 没有同时出现时,$w_{ij}$ 就得不到训练。因此对于数据稀疏的场景,交叉项的参数矩阵 $\mathbf{w}$ 得不到充分训练。 22 | 23 | ## FM 24 | 25 | FM (Factorization Machine) 的思想是将组合特征的参数 $\mathbf{w}$ 进行矩阵分解,即 $\mathbf{w} = \mathbf{v}^T \mathbf{v}$。如此以来 $\mathbf{w}$ 可以由一个较小的句子 $\mathbf{v}$ 来表示。其中 $\mathbf{w}_{ij}=\mathbf{v}_i·\mathbf{v}_j$,即组合特征 $x_ix_j$ 的系数由为特征对应的隐向量 $\mathbf{v}_i$ 和 $\mathbf{v}_j$ 的内积。 26 | 27 | FM 模型就可以表示为: 28 | 29 | $$ 30 | \hat{y}(\mathbf{x}) := w_0 + \sum_{i=1}^{n} w_i x_i + \sum_{i=1}^{n} \sum_{j=i+1}^{n} \langle \mathbf{v}_i, \mathbf{v}_j \rangle x_i x_j 31 | $$ 32 | 33 | 其中尖括号表示两个向量内积: 34 | 35 | $$ 36 | \left\langle\mathbf{v}_{i}, \mathbf{v}_{j}\right\rangle :=\sum_{f=1}^{k} v_{i, f} \cdot v_{j, f} 37 | $$ 38 | 39 | 如果隐向量 $\mathbf{v}_i$ 的维度为 $k$,输入特征 $x$ 维度为 $n$,上面式子中第二项的时间复杂度是 $O(kn^2)$。不过这一项在计算的时候可以进行化简: 40 | 41 | 42 | $$ 43 | \sum_{i=1}^n \sum_{j=i+1}^n \langle \mathbf{v}_i, \mathbf{v}_j \rangle x_i x_j = \frac{1}{2} \sum_{f=1}^k \left(\left( \sum_{i=1}^n v_{i, f} x_i \right)^2 - \sum_{i=1}^n v_{i, f}^2 x_i^2 \right) 44 | $$ 45 | 46 | 下面是证明过程: 47 | 48 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/08/29/5d67ba5a451253d1784a9634.jpg) 49 | 50 | 证明过程不难理解,注意下面几点: 51 | 52 | - 第一步:注意第二个 $\sum$ 符号的起始值 53 | - 第二步: 把向量内积展开成相乘并求和 54 | - 第三步:提取公因式 55 | - 第四步:改变符号得到 $\sum$ 的平方项 56 | 57 | ## FM 特点 58 | 59 | 从参数量上来看,FM 模型将组合特征的参数量大幅下降,从 $n * (n-1) / 2$ 降到 $n * k$。 60 | 61 | 另外,采用类似于矩阵分解的策略,交叉项系数 $\mathbf{w}_{ij}$ 原本只能通过 $x_i$ 和 $x_j$ 训练得出,如果这两个特征没有同时出现过,则得出的 $\mathbf{w}_{ij}$ 无意义。在 FM 模型中 $\mathbf{w}_{ij}$ 由 $\mathbf{v}_i$ 和 $\mathbf{v}_j$ 内积得来,而 $\mathbf{v}_i$ 可以通过任何包含特征 $x_i$ 的实例进行学习。对于样本中不存在的特征组合,FM 也能进行泛化。 62 | 63 | ## FM 训练 64 | 65 | 如果用 FM 做回归,可使用 MSE 作为损失函数。用于分类,就使用 logit loss,然后使用 SGD 训练即可。梯度计算如下: 66 | 67 | $$ 68 | \frac{\partial}{\partial \theta} \hat{y}(\mathbf{x})=\left\{\begin{array}{ll}{1,} & {\text { if } \theta \text { is } w_{0}} \\ {x_{i},} & {\text { if } \theta \text { is } w_{i}} \\ {x_{i} \sum_{j=1}^{n} v_{j, f} x_{j}-v_{i, f} x_{i}^{2},} & {\text { if } \theta \text { is } v_{i, f}}\end{array}\right. 69 | $$ 70 | 71 | ## FM 和 SVMs 的比较 72 | 73 | 使用多项式核的 SVMs 的模型可以写成下面这样: 74 | 75 | $$ 76 | \begin{aligned} \hat{y}(\mathrm{x})=w_{0}+\sqrt{2} \sum_{i=1}^{n} w_{i} x_{i} &+\sum_{i=1}^{n} w_{i, i}^{(2)} x_{i}^{2} \\ &+\sqrt{2} \sum_{i=1}^{n} \sum_{j=i+1}^{n} w_{i, j}^{(2)} x_{i} x_{j} \end{aligned} 77 | $$ 78 | 79 | 这里 SVMs 和 FM 用到的特征完全一样,唯一的区别就是交叉项的系数。因为 SVMs 中交叉项系数 $\mathbf{w}_{ij}$ 依赖 $x_i$ 和 $x_j$ 学习出来,SVM 不能用在数据稀疏的场景下。而 FM 可以使用极度稀疏的数据来学习参数。 80 | 81 | ## 总结 82 | 83 | 当数据很稀疏时,组合特征的参数难以学习到,FM 使用基于矩阵分解的策略,组合特征的系数依然能够有效估计,而且可泛化到未观察到的组合特征。 -------------------------------------------------------------------------------- /_posts/rec/2019-09-17-item-sim-models.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: Item Similarity Model 4 | category: 推荐系统 5 | tag: 推荐系统 6 | --- 7 | 8 | - * 9 | {:toc} 10 | 11 | 12 | 13 | ## Item-based CF 14 | 15 | 基于 user-item 评分矩阵,利用 cosine 或者 Pearson correlation 来计算 item 间的相似度。user $u$ 对 item $i$ 的评分估计值为: 16 | 17 | $$ 18 | \hat{y}_{u i}=\sum_{j \in \mathcal{R}_{u}} r_{u j} s_{i j} 19 | $$ 20 | 21 | 其中 `$\mathcal{R}_{u}$` 是 user $u$ 所有评分过的 item 集合,`$s_{i j}$` 是 item $i$ 和 item $j$ 的做了标准化后的相似度。 22 | 23 | 这种方法直接、易行,但是在相似度的度量上,由于矩阵的稀疏性,相似度计算效果不是特别好,推荐质量不够高。 24 | 25 | 26 | ## 矩阵分解 27 | 28 | 矩阵分解的策略是将 user-item 评分矩阵分解为两个低秩的稠密矩阵: 29 | 30 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/02/5d6c8098451253d1786aa283.jpg) 31 | 32 | 在推荐时,可以使用 user 的向量乘上 item 矩阵,得到该 user 对所有 item 的评分的估计值,然后得出推荐结果。 33 | 34 | 但是当 user 与新的 item 产生交互时,user 和 item 的向量应该发生变化。尤其是对 user 而言,要想具有实时性,user 最近的交互信息就一定要能够影响 user 的向量。但是矩阵分解的方法没法做到这种实时性。 35 | 36 | 利用分解得到的 item 矩阵,也可以计算 item 之间的相似度,而且比直接用 user-item 评分矩阵来计算相似度效果更好。因此矩阵分解也可以用在传统的 item-based CF 中,用于计算 item 间的相似度。 37 | 38 | ## SLIM (Sparse LInear Method) 39 | 40 | 论文 _SLIM: Sparse Linear Methods for Top-N Recommender Systems_ 中提出一种方法直接学习出 item-item 间的相似度矩阵。 41 | 42 | 在约束条件下,最小化下式中的 $L$ : 43 | 44 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/10/5d9ecc02451253d17814b363.jpg) 45 | 46 | $$ 47 | \hat{y}_{u i}=\sum_{j \in \mathcal{R}_{u}} r_{u j} s_{i j} 48 | $$ 49 | 50 | 其中 `$S \in \mathbb{R}^{I \times I}$` 是 item 间的相似度矩阵,加入 L2 正则是为了避免过拟合,加入 L1 正则化是希望相似度矩阵尽可能地稀疏,因为相似的 item 不应该很多。$S \ge 0$ 是因为相似度应该介于 0~1 之间。`$diag(\mathbf{S})$` 则是要求 item 和自己的相似度为 0。 51 | 52 | 利用 user-item 评分矩阵中已有的评分数据来上上式最小化,可以学习得到一个相似度矩阵 $S$。 53 | 54 | SLIM 模型的缺点很明显,矩阵 $S$ 的规模很大,训练起来很慢。另外只有 item $i$ 和 item j 同被一个 user 评分过,$S_{ij}$ 才能得到学习。 55 | 56 | ## FISM (Factored Item Similarity Model) 57 | 58 | 出自论文 _FISM: Factored Item Similarity Models for Top-N Recommender Systems_。 59 | 60 | 如果将 item-item 相似度矩阵分解为两个低秩矩阵相乘,即 $S = PQ$。那么 item $i$ 和 item $j$ 之间的相似度表示为 $sim(i,j)=p_i · q_j^T$。 61 | 62 | 如此以来 user $u$ 对 item $i$ 的评分可以表示为: 63 | 64 | $$ 65 | \hat{r}_{u i}=b_{u}+b_{i}+\sum_{j \in \mathcal{R}_{u}} \mathbf{p}_{j} \mathbf{q}_{i}^{T} 66 | $$ 67 | 68 | $b_u$ 和 $b_i$ 为 user 和 item 的 bias,其中 `$\mathcal{R}_{u}$` 是 user $u$ 所有评分过的 item 集合,这里采用的可能是隐式反馈,集合中的 item 的评分都是 1,这是为啥没有评分值 `$r_{ui}$` 的原因。 69 | 70 | 优化目标为: 71 | 72 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/10/5d9ed32e451253d1781c1095.jpg) 73 | 74 | ## NAIS (Neural Attentive Item Similarity) 75 | 76 | 论文 _NAIS: Neural Attentive Item Similarity Model for Recommendation_ 在 FISM 的基础做了改进,加入了 Attention 机制。 77 | 78 | 这里作者将 $p$, $q$ 都视为 item 的 Embedding,作者认为用户评分过的 item 的 Embedding 的均值可以作为 user 的 Embedding。这样以来评分 `$\hat{y}_{u i}$` 的计算就很直接了,user embedding 乘上 item embedding 即可。 79 | 80 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/10/5d9eda1c451253d1782357e1.jpg) 81 | 82 | 想象一下,如果 item 是一个衣服,那么在表示用户时,用户购买的衣服的信息就更重要一些。所以这里给用户评分过的 item 加一个权重,加权得到用户的 embedding。 83 | 84 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/10/5d9ed7b3451253d178205ee2.jpg) 85 | 86 | 加入 Attention 之后对评分值得估计就变成了这样: 87 | 88 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/10/5d9edbea451253d1782664ae.jpg) 89 | -------------------------------------------------------------------------------- /_posts/machine_learning/2019-03-10-RNN.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 循环神经网络 4 | category: 机器学习 5 | math: true 6 | --- 7 | 8 | 本文是阅读 Hands-On Machine Learning with Scikit-Learn and TensorFlow 第 14 章记录的笔记,总结了常见基础 RNN,LSTM 和 GRU 的网络结构,并描述了它们为什么要那样设计。 9 | 10 | ## RNN 11 | 12 | Recurrent neural networks (RNN) 一种号称能够预测未来的网络模型,其实质是输入一个序列,预测接下来的序列。基础的 RNN 的模型非常恨简单,他看起来像是一个全连接网络(下图左),它的输入由两部分构成,当前输入 $x_{(t)}$ 以及上一个时刻的输出 $y_{(t-1)}$。 13 | 14 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/06/5d99a48e451253d1789e19fb.jpg) 15 | 16 | 把序列中的各个元素 $x_{(i)}$ 连同上一时刻的输出 $y_{(i-1)}$ 一并输入给 RNN,得到新的输出 $y_{(i)}$。连同此输出再与序列中下一个元素一并输入 RNN,再次产生新的输出。整个过程就像编程语言中的 for 循环一样,在对一个序列进行处理是,每次循环都用到了前一次循环的状态,以及序列中下一个元素。 17 | 18 | 19 | RNN 的数学表达式如下: 20 | 21 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/06/5d99a6fb451253d1789e6141.jpg) 22 | 23 | 虽然 RNN 的结构让它可以处理无限长的序列,看起来能够捕获跨度很长的模式,但受限于 RNN 简单的结构,序列信息在每一个 time step 都会丢失,RNN 往往也只能捕获短距离的模式。为了解决信息遗忘的问题,很多 RNN 的该键版本被提出,比如 LSTM、GRU,这些改进版本都引入了长期记忆模块。这些具有长时记忆模块的 RNN 结构在实践中被证明很有用,基础版本的 RNN 已经不在被使用了,现在提到 RNN 人们首先想到的会是 LSTM 和 GRU。 24 | 25 | ## LSTM 26 | 27 | Long Short-Term Memory (LSTM) 于 1997 年被提出,之后经过一些改进。它引入长时记忆模块,能够捕获到序列中长距离的依赖关系,在训练中收敛速度比基础版本的 RNN 快。LSTM 的结构如下图所示: 28 | 29 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/06/5d99a7a6451253d1789e70b6.jpg) 30 | 31 | 它的状态分为两部分,$h_{(t)}$ 和 $c_{(t)}$ , $h_{(t)}$ 是短时状态,$c_{(t)}$ 是长期状态,这里 $c$ 和 $h$ 都是向量。其主要思想是,LSTM 能够学习把什么存入长期状态,把什么从长期状态中扔掉。$c_{(t-1)}$ 先经过一个遗忘门,丢去一些记忆,然后再加上一些信息,得到新的状态 $c_{(t)}$。新的状态 $c_{(t)}$ 会经过一个 $tanh$ 函数,得到的向量被 output gate 选择后做为当前 time step 的输出,和新的短时状态 $h_{(t)}$。 32 | 33 | 可以看到 LSTM 的输出是依托于长期状态 $c_{(t)}$ 的,而 $c_{(t)}$ 在每个 time step 会丢掉一些信息,再加入一些信息。控制如何丢弃,如何加入,最终输出什么,由 3 个门控制。加入什么信息由 $g_{(t)}$ 控制。而这些的输入都是 $x_{(t)}$ 和 $h_{(t-1)}$。 34 | 35 | $x_{(t)}$ 和 $h_{(t-1)}$ 经过 4 个全连接网络输出 4 个向量,这 4 个向量有不同的目的: 36 | 37 | $g_{(t)}$ 对应的全连接网络分析当前输入 $x_{(t)}$ 和前一个短期状态 $h_{(t-1)}$ 之间的关系,得到一个输出用于向长期状态中增加信息。 38 | 39 | 其他的三个门 $f_{(t)}$、$i_{(t)}$、$o_{(t)}$ 分别被称为遗忘门、输入门、输出门,它们是由 sigmoid 激活函数输出的,其元素的值介于 0~1 之间。 40 | 41 | - 遗忘门,控制长期状态中那些部分需要被遗忘。$f_{(t)}$ 和 $c_{(t-1)}$ 做对应元素向量,如果遗忘门的中元素为 1,那就是不遗忘,如果为 0,那就是遗忘。 42 | - 输入门,控制 $g_{(t)}$ 中那些部分加入长期状态中。 43 | - 输出门,在 $c_{(t)}$ 经过 $tanh$ 后,输出门控制那些信息会被输出。 44 | 45 | LSTM 能够识别到一个重要的输入,并将该输入的信息存入长期状态中。状态 $c$ 保留长期信息用于之后使用,并在合适的时刻将长期状态中的部分信息遗忘掉。这就是 LSTM 能够捕获序列中长距离模式的原因。 46 | 47 | LSTM 的数学表达式如下: 48 | 49 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/06/5d99a99b451253d1789eae4b.jpg) 50 | 51 | ### Peephole connections 52 | 53 | 从前面的描述可以看出,LSTM 在计算各种门的输出时,只用了 $x_{(t)}$ 和 $h_{(t-1)}$,但有人觉得如果能够用到 $c_{(t-1)}$ 岂不更好。因此在 2000 年研究者提出了一个改进,将 $c_{(t-1)}$ 加入输入门和遗忘门的计算,把 $c_{(t)}$ 加入输出门的计算。 54 | 55 | ## GRU 56 | 57 | Gated Recurrent Unit (GRU) 于 2014 年被提出,是一种简化版本的 LSTM。其模型结构如下: 58 | 59 | 60 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/06/5d99a077451253d1789d9563.jpg) 61 | 62 | 相比于 LSTM,GRU 有以下改变: 63 | 64 | - 只使用了一个状态 $h_{(t)}$。 65 | - 使用一个门 $z_{(t)}$ 同时控制输出门和遗忘门,当遗忘了信息时($z_{(t)}$ 为 0),那么就会输入信息(1-$z_{(t)}$ 为 1,注意图中黑色写着 -1 的圆圈)。当状态中的某个维度被遗忘后,就一定会加入新的信息。 66 | - 去除了输出门,但是加入了新控制门 $r_{(t)}$ 对参与运算的状态 $h_{(t-1)}$ 做了过滤。 67 | 68 | GRU 的数学表达式如下: 69 | 70 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/10/06/5d99a2b1451253d1789ddc3d.jpg) 71 | 72 | 73 | -------------------------------------------------------------------------------- /_posts/network/2020-07-23-计算机网络.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 计算机网络知识总结 4 | category: 计算机网络 5 | permalink: 2020/network/ 6 | published: false 7 | --- 8 | 9 | ## 网络模型 10 | 11 | ### OSI 七层模型 12 | 13 | 学习计算机网络的时候,我们都会学到网络中的协议是分层的,每层负责处理的事情不同。通常课本上会讲到 OSI 的七层模型,其中 OSI 是指 Open Source Interconnect。这七层从下到上分别为: 14 | 15 | 1. 物理层 16 | 2. 数据链路层 17 | 3. 网络层 18 | 4. 运输层 19 | 5. 会话层 20 | 6. 表示层 21 | 7. 应用层 22 | 23 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/08/14/2020-08-14-205101.png) 24 | 25 | **应用层** 26 | 27 | 网络可以用来发送数据,但是接收到一块数据后,我该如何知道这些数据该怎么解读呢?比如数据可以是压缩过的,该如何知道压缩格式呢?多次发送的数据可能混在一起了,该如何区分开这些数据呢?应用层协议为此制定了一套标准,有了这个标准,就像人类有了语言一样,使用同种语言的人就可以交流了。比如下面是一个 HTTP 的请求报文: 28 | 29 | ``` 30 | GET /index.html HTTP/1.1\r\n 31 | Host: 127.0.0.1:8002\r\n 32 | \r\n 33 | ``` 34 | 35 | 基于 HTTP 协议的规定,数据接收方可以准确地获知对方的意思。 36 | 37 | 常见的应用层协议有 HTTP (hypertext transfer protocol)、DNS (domain name system)、FTP (file transfer protocol)、IMAP(Internet massage access protocol)、SMTP (simple mail transfer protocol) 等。 38 | 39 | **表示层** 40 | 41 | 表示层用来对数据做编解码,数据压缩与解压缩等操作,处理诸如大小端转换的问题。 42 | 43 | **会话层** 44 | 45 | 管理一个会话(两个设备之间的网络连接),完成连接的建立、保持、断开等功能。 46 | 47 | **运输层** 48 | 49 | 运输层协议支持数据在跨越多个网络的两个机器之间传输,运输层不关心发送的是什么东西,它只保存数据的发送与接收。就像一辆卡车,你把数据扔给它并指定目的地,它就给你运输,并保证数据在运输过程中不会出错。 50 | 51 | 目前工作在运输层的有 TCP 和 UDP 两个协议。其中 TCP 提供可靠的数据传输,它通过一些复杂的机制,保证数据能够送达对方。而 UDP 协议,只是尽力传输地,数据有可能会丢失。 52 | 53 | **网络层** 54 | 55 | 互联网是有很多个小的网络互联而成的,网络层协议用来实现数据跨网络的传输。网络中存在很多岔路,网络层协议提供了路径选择(路由)功能,不同的网络单次能够发送的数据量不同,因此网络层协议实现了数据切分与拼装的功能。 56 | 57 | 可以把网络层协议的工作想象成一些汽车、飞机、轮船、手推车等运输工具,数据在网络中传输,就是利用这些工具来完成数据的运输。网络层协议就要负责装货和卸货,有时候还需要把货物重新打包一下。 58 | 59 | 网络层的常见协议有 ICMP,ARP。其中路由协议有 OSPF(开放最短路径优先)、BGP(Border Gateway Protocol)、RIP(Routing Information Protocol)等。 60 | 61 | **数据链路层** 62 | 63 | 数据链路层提供点到点的传输服务,在两个直接相连的节点间传输数据。链路层协议控制两个直接相连的设备之间连接的建立和断开。常见的协议有 PPP (Point-to-Point Protocol) 运用在广域网上。局域网中,通常使用以太网,其中用到的协议是 CSMA/CD 64 | 65 | **物理层** 66 | 67 | 物理层协议定义了物理特性和数字之间的转换方式,并且规定了各种接口的标准,比如光纤、以太网、串口等。 68 | 69 | ### TCP/IP 四层模型 70 | 71 | 但是在实际应用中的计算机网络,通常使用 TCP/IP 模型,这是一个四层模型,它和 OSI 大致可以对应起来,如下图所示: 72 | 73 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/2020/08/14/2020-08-14-204347.png) 74 | 75 | 76 | ## 物理层 77 | 78 | 物理层涉及到传输介质、物理层设备、编码方式、通信原理等知识,涉及信号的调制与解调、数字信号和模拟信号的转换等内容。 79 | 80 | 81 | ## 网络层 82 | 83 | 网络层中包含 IP 协议,ARP 协议,ICMP 协议,还有一些路由算法和路由交换协议。 84 | 85 | ### IP 协议 86 | 87 | 88 | 89 | ### NAT 的原理 90 | 91 | 因为 IPv4 的地址是 32 位,因此只有 40 多亿个唯一的 IP 地址,目前这些地址已经显得不太够用了。每个人一部手机+一台电脑,这就已经 100 多亿了。一个子网中常常只有和互联网连接的那个设备,通常是路由器,才有一个唯一的 IP,而子网内部机器的 IP 地址通常是 192.168.x.x 或者 10.1.x.x,这个地址是内网的地址。在一个子网中,这个 IP 是固定的,但是在很多子网中,都在使用 192.168.x.x 这样的地址。此时需要使用 NAT 网络地址穿越。 92 | 93 | 其原理很简单,比如子网中存在 A B 两台设备,其 IP 分别为: 94 | 95 | ``` 96 | A: 192.168.1.100 97 | B: 192.168.1.101 98 | 99 | 路由器: 127.57.71.67 100 | ``` 101 | 102 | 此时 A 使用 3000 和 8.8.8.8:53 连接,即: 103 | 104 | ``` 105 | 192.168.1.100:3000 -> 8.8.8.8:53 106 | ``` 107 | 108 | 路由器收到此连接后会先做改写,然后转发,改写后内容是: 109 | 110 | ``` 111 | 127.57.71.67:4000 -> 8.8.8.8:53 112 | ``` 113 | 114 | 同时记录一个端口映射关系: 115 | 116 | ``` 117 | 192.168.1.100:3000 <-> 4000 118 | ``` 119 | 120 | 这样连接双方都具有了网络上的唯一地址,路由器和 8.8.8.8:53 建立了连接,当对方发来响应的时候,路由器发现是发送到自己的 4000 端口的,于是它从映射表中找到 `192.168.1.100:3000`,并把数据发送过去。 121 | 122 | 以上就是 NAT 的原理了,它把子网中的一个 IP:PORT 映射到自己的某个端口上,代子网中的所有机器去和外边的机器进行连接。 -------------------------------------------------------------------------------- /site/assets/css/scss/highlight.scss: -------------------------------------------------------------------------------- 1 | code[class*="language-"], 2 | pre[class*="language-"] { 3 | color: #393A34; 4 | font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; 5 | direction: ltr; 6 | text-align: left; 7 | white-space: pre; 8 | word-spacing: normal; 9 | word-break: normal; 10 | 11 | 12 | -moz-tab-size: 4; 13 | -o-tab-size: 4; 14 | tab-size: 4; 15 | 16 | -webkit-hyphens: none; 17 | -moz-hyphens: none; 18 | -ms-hyphens: none; 19 | hyphens: none; 20 | } 21 | 22 | 23 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, 24 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { 25 | background: #C1DEF1!important; 26 | } 27 | 28 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection, 29 | code[class*="language-"]::selection, code[class*="language-"] ::selection { 30 | background: #C1DEF1!important; 31 | } 32 | 33 | /* Code blocks */ 34 | pre[class*="language-"] { 35 | padding: 1em; 36 | overflow: auto; 37 | } 38 | 39 | 40 | .token.comment, 41 | .token.prolog, 42 | .token.doctype, 43 | .token.cdata { 44 | color: #008000; 45 | } 46 | 47 | .token.namespace { 48 | opacity: .7; 49 | } 50 | 51 | .token.string { 52 | color: #A31515; 53 | } 54 | 55 | .token.punctuation, 56 | .token.operator { 57 | color: #393A34; /* no highlight */ 58 | } 59 | 60 | .token.url, 61 | .token.symbol, 62 | .token.number, 63 | .token.boolean, 64 | .token.variable, 65 | .token.constant, 66 | .token.inserted { 67 | color: #36acaa; 68 | } 69 | 70 | .token.atrule, 71 | .token.keyword, 72 | .token.attr-value, 73 | .language-autohotkey .token.selector, 74 | .language-json .token.boolean, 75 | .language-json .token.number, 76 | code[class*="language-css"] { 77 | color: #0000ff; 78 | } 79 | 80 | .token.function { 81 | color: #393A34; 82 | } 83 | 84 | .token.deleted, 85 | .language-autohotkey .token.tag { 86 | color: #9a050f; 87 | } 88 | 89 | .token.selector, 90 | .language-autohotkey .token.keyword { 91 | color: #00009f; 92 | } 93 | 94 | .token.important, 95 | .token.bold { 96 | font-weight: bold; 97 | } 98 | 99 | .token.italic { 100 | font-style: italic; 101 | } 102 | 103 | .token.class-name, 104 | .language-json .token.property { 105 | color: #2B91AF; 106 | } 107 | 108 | .token.tag, 109 | .token.selector { 110 | color: #800000; 111 | } 112 | 113 | .token.attr-name, 114 | .token.property, 115 | .token.regex, 116 | .token.entity { 117 | color: #ff0000; 118 | } 119 | 120 | .token.directive.tag .tag { 121 | background: #ffff00; 122 | color: #393A34; 123 | } 124 | 125 | /* overrides color-values for the Line Numbers plugin 126 | * http://prismjs.com/plugins/line-numbers/ 127 | */ 128 | .line-numbers .line-numbers-rows { 129 | border-right-color: #a5a5a5; 130 | } 131 | 132 | .line-numbers-rows > span:before { 133 | color: #2B91AF; 134 | } 135 | 136 | /* overrides color-values for the Line Highlight plugin 137 | * http://prismjs.com/plugins/line-highlight/ 138 | */ 139 | .line-highlight { 140 | background: rgba(193, 222, 241, 0.2); 141 | background: -webkit-linear-gradient(left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0)); 142 | background: linear-gradient(to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0)); 143 | } -------------------------------------------------------------------------------- /_posts/cpp/2015-11-03-effective-cpp-46.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 《Effective C++》条款 46 补充 4 | category: C/C++ 5 | --- 6 | 7 | 8 | ## 问题 9 | 10 | 《Effective C++》条款 46 的标题是:需要类型转换时请为模板定义非成员函数。本节作者定义了一个有理数类,希望他能做如下运算: 11 | 12 | ```cpp 13 | Rational a(1, 3); 14 | Rational b = a * a; 15 | Rational c = a * 3; 16 | Rational d = 3 * a; 17 | ``` 18 | 19 | 类的定义如下: 20 | 21 | ```cpp 22 | template 23 | class Rational{ 24 | public: 25 | Rational(T numerator, T denominator=1): numerator_(numerator), denominator_(denominator){} 26 | T numerator() const{ return numerator_; } 27 | T denominator() const{ return denominator_; } 28 | 29 | private: 30 | T numerator_; 31 | T denominator_; 32 | }; 33 | ``` 34 | 35 | ## 解法 36 | 37 | 为了实现 `b = a * a` 可以重载类的 `operator*` 方法: 38 | 39 | ```cpp 40 | Rational operator*(const Rational &rhs) const{ 41 | Rational ret(numerator_ * rhs.numerator_, denominator_ * rhs.denominator_); 42 | return ret; 43 | } 44 | ``` 45 | 46 | 而要想让 `Retional` 类可以和 `int` 或者 `double` 等直接进行运算,如 `a * 3`,需要这里的 `3` 可以隐式转换为 `Retional`,因此 `Retional` 需要有一个可以接受单参数,且不能是 `explicit` 的构造函数。目前的类定义满足此要求。 47 | 48 | 但是为了实现 `d = 3 * a`,以上的工作都失去了意义,为此需要定义如下运算符: 49 | 50 | ```cpp 51 | template 52 | Rational operator*(const Rational &lhs, const Rational &rhs){ 53 | return Rational(lhs.numerator() * rhs.numerator(), 54 | lhs.denominator() * rhs.denominator()); 55 | } 56 | ``` 57 | 58 | 这个时候,`b = a * a` 可以正常工作,但是 `c = a * 3` 和 `d = 3 * a` 会出错。原因很简单,模板在实例化的时候是不会做由 `int` 到 `Rational` 的转换的。 为了支持这两种运算,可以定义如下模板函数: 59 | 60 | ```cpp 61 | template 62 | Rational operator*(const T &lhs, const Rational &rhs){ 63 | return Rational(lhs) * rhs; 64 | } 65 | 66 | template 67 | Rational operator*(const Rational &lhs, const T &rhs){ 68 | return lhs * Rational(rhs); 69 | } 70 | ``` 71 | 72 | 在这两个模板函数内部,显示地进行了 `T` 到 `Rational` 的转换。 73 | 74 | ## 更精简的解法 75 | 76 | 如果 `T` 可以隐式转换为 `Rational`,那么就只需要一个函数。为了能够隐式转换,这个函数不能是模板函数。但是此函数又必须支持多种类型,一种方法是把他定义在类里面。为了在类里面定义一个普通函数,它就只能是友元的。 77 | 78 | ```cpp 79 | template 80 | class Rational{ 81 | friend Rational operator*(const Rational &lhs, const Rational &rhs); 82 | public: 83 | Rational(T numerator, T denominator=1): numerator_(numerator), denominator_(denominator){} 84 | //... 85 | } 86 | ``` 87 | 88 | 这里只在类里面做了声明,还缺少定义。于是在类外部写下如此定义: 89 | 90 | ```cpp 91 | template 92 | Rational operator*(const Rational &lhs, const Rational &rhs) { 93 | // ... 94 | } 95 | ``` 96 | 97 | 这就再度把自己引入了错误的深渊。因为 `friend` 声明的函数不是一个模板函数,而上面却定义了一个模板函数。结果是友元函数,没有定义。 98 | 99 | ```cpp 100 | friend Rational operator*(const Rational &lhs, const Rational &rhs); 101 | ``` 102 | 103 | 解决的办法就是在类里面完成对友元函数的定义: 104 | 105 | ```cpp 106 | template 107 | class Rational{ 108 | friend Rational operator*(const Rational &lhs, const Rational &rhs){ 109 | return Rational(lhs.numerator() * rhs.numerator(), 110 | lhs.denominator() * rhs.denominator()); 111 | } 112 | // ... 113 | } 114 | ``` 115 | 116 | 这样以来,在对类模板实例化的时候,就对这个函数进行实例化。 117 | 118 | ## 总结 119 | 120 | 在编写模板类的时候,如果需要支持隐式类型转换,那就不能依赖于模板函数,因为模板函数不会做隐式类型转换。此时需要定义一个非模板函数,并把它作为 `friend` 函数,并在类里面完成函数的定义。因为定义在类中的函数会是内联的,因此可以把具体的操作交给类的某个方法来完成。 121 | -------------------------------------------------------------------------------- /_posts/machine_learning/2019-03-04-train-dl.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 训练深度网络 4 | category: 机器学习 5 | --- 6 | 7 | 8 | - * 9 | {:toc} 10 | 11 | ## 优化器 12 | 13 | ### Momentum 14 | 15 | 模拟石头滚下山的状态。在平坦的函数平面上因为有加速度的存在,能够快速通过平坦区域。 16 | 17 | ![width=400](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/07/18/5d3005e4451253d1786a41ca.jpg) 18 | 19 | ```python 20 | optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9) 21 | ``` 22 | 23 | ### Nesterov Accelerated Gradient 24 | 25 | ![width=400](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/07/18/5d3006a3451253d1786a4882.jpg) 26 | 27 | 在计算新的梯度时,考虑到已经加入的动量。 28 | 29 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/07/18/5d3006c9451253d1786a49aa.jpg) 30 | 31 | ```python 32 | optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9, nesterov=True) 33 | ``` 34 | 35 | ### AdaGrad 36 | 37 | ![width=400](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/07/18/5d3007c1451253d1786a53d3.jpg) 38 | 39 | 梯度小的方向,大幅更新。梯度大的方向,小幅度更新。 40 | 41 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/07/18/5d300828451253d1786a5898.jpg) 42 | 43 | 上图中,水平方向较长,较平坦,梯度较小。竖直方向,梯度较大。常规的梯度下降法,会走蓝色路径。而 AdaGrad 会走橙黄色路径。 44 | 45 | 但是 AdaGrad 不断地对梯度值进行累加,最终导致上面式子中 `s` 的值过大,更新越来越慢。 46 | 47 | ### RMSProp 48 | 49 | ![width=400](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/07/18/5d3008cd451253d1786a5dea.jpg) 50 | 51 | RMSProp 修复了 AdaGrad 的问题,通过引入一个衰退系数,让 `s` 仅仅累加最近的梯度。 52 | 53 | ```python 54 | optimizer = keras.optimizers.RMSprop(lr=0.001, rho=0.9) 55 | ``` 56 | 57 | ### Adam and Nadam 58 | 59 | ![width=400](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/07/18/5d300a57451253d1786a6821.jpg) 60 | 61 | Adam 综合了 RMSProp 和 Momentum。3 4 两个式子中 t 代表的是迭代次数,当迭代次数较小的时候,m 和 s 的值能够被放大,当迭代次数增大是分母也就很接近 1 了,m 和 s 的值就不会在被放大了。这是为了在迭代的初始时,m 和 s 的值为 0,通过这两个式子,可以对初试阶段进行加速。 62 | 63 | ```python 64 | optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999) 65 | ``` 66 | 67 | Nadam 是在 Adam 中加入了 Nesterov Accelerated Gradient 中的思想。 68 | 69 | ## 梯度消失 / 梯度爆炸 70 | 71 | 误差梯度在反向传播的过程中,变得越来越小,最后近乎消失,这导致网络的前面的层对应的参数得不到更新。相反,梯度也可能变得越来越大,在反向传播的过程中,梯度越变越大,最终模型无法收敛。 72 | 73 | 74 | 75 | 梯度消失的原因主要是使用 sigmoid 作为激活函数导致的,sigmoid 函数当输入很大或很小时,其梯度都接近于 0。 76 | 77 | Relu 激活函数的问题在于,一旦某个 unit 输出小于 0,那么它之后就只会输出 0,而且梯度也会是 0,即 ReLU 也可能出现梯度消失的问题,此 unit 的权重将得不到更新。这个问题称为 Dead ReLUs。 78 | 79 | 梯度爆炸常常出现在循环神经网络中。_为啥_ 80 | 81 | ## Batch Normalization 82 | 83 | 使用 ReLU 及其变种,加上合适的参数初始化策略,在训练的初期可以很好地消除梯度消失/爆炸的问题,但不能保证在整个训练过程中都不出现梯度消失/爆炸的问题。Batch Normalization 对输入的整个 batch 的数据做标准化,可以持续减缓梯度消失/爆炸的问题。 84 | 85 | Batch Normalization 需要调整的参数不多,`momentum` 用于计算动态调整的均值,它的值应该接近于 1。样本集越大,或者 batch-size 越小时,`momentum` 应该越接近于 1。 86 | 87 | 如果在输入层之后紧接一个 batch normalization 层,对数据做标准化操作就可以不用显式地完成了。 88 | 89 | ## 梯度裁剪 90 | 91 | 梯度裁剪是限制梯度的大小不超过某个阈值。在 RNN 中梯度裁剪尤其重要,因为 RNN 常常出现梯度爆炸的问题。 92 | 93 | ```python 94 | optimizer = keras.optimizers.SGD(clipvalue=1.0) 95 | model.compile(loss="mse", optimizer=optimizer) 96 | ``` 97 | 98 | 在 keras 中设置梯度裁剪尤其简单,以上代码将限制梯度的绝对值小于 1.0。对梯度的某个分量进行裁剪,会导致梯度方向的改变,比如原梯度为 `[100, 1]`, 裁剪后变为 `[1, 1]`,这极大地改变了梯度方向。要想保证梯度方向不变,可以对梯度的 L2 范数做限制,即限制梯度向量的模长。下面的设置保证梯度的模长不大于 1,否则各个分量都进行缩减,保证梯度方向不变。 99 | 100 | ```python 101 | optimizer = keras.optimizers.SGD(clipnorm=1.0) 102 | ``` -------------------------------------------------------------------------------- /_posts/web/2016-09-08-css-center.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: CSS 垂直居中 4 | category: Web 5 | --- 6 | 7 | 8 | 9 | - * 10 | {:toc} 11 | 12 | 24 | 25 | 26 | 垂直居中,这是任何前端开发者都遇到的场景,也是大多数人都为之困惑的问题。记得当时来公司后,进行了一次笔试,其中一个便是用 CSS 实现垂直居中,我突然发现自己竟不能写出一种自信无误的实现方式。 27 | 28 | 下面来总结一下 CSS 垂直居中的方式,对于 hack 气息较重的方法(比如使用 table,button 等)这里不再讨论了,这里主要谈谈现代 CSS 中实现垂直居中的方式。 29 | 30 | 31 | 下面的示例中均采用下面这样的 HTML 结构: 32 | 33 | ```html 34 |
35 |
36 |
37 | ``` 38 | 39 | 40 | 41 | ## 基于 line-height 的解决方案 42 | 43 | 如果 content 的内容是单行的文本,或者是一个图片,或者说内容的 display 属性是 `inline` 或者 `inline-block`,那么我们可以通过 `line-height` 属性来让其居中,最简单的方式是将 `.content` 的 `line-height` 和 `.container` 的 `height` 设为相同的值。 44 | 45 | 46 |
47 |
CSS 垂直居中
48 |
49 | 50 | CSS 代码: 51 | 52 | ```css 53 | .container{ 54 | height: 150px; 55 | } 56 | .container .content{ 57 | line-height: 150px; 58 | text-align: center; 59 | } 60 | ``` 61 | 62 | ## 基于 padding 的解决方案 63 | 64 | 给 `.container` 设置相同的上下内边距,内容自然就实现了垂直居中,这很好理解。 65 | 66 | ## 基于绝对定位的解决方案 67 | 68 | 使用绝对定位,并设置 `top: 50%; left: 50%;` 可以将 `.content` 的左上角定位至容器的中心点。目前还尚未实现居中,还需将将 `.content` 向上向左各移动一半的 `.content` 的高度和宽度。这个时候有两种实现方法: 69 | 70 | ### 1. 使用负外边距移动内容 71 | 72 | 如果 `.content` 的大小已知,比如是 `200px * 120px`,那么我们可以使用 `margin-top: -60px; margin-left: -100px` 来达成目标。最后的 CSS 代码为: 73 | 74 | ```css 75 | .container{ 76 | position: relative; 77 | } 78 | .container .content{ 79 | position: absolute; 80 | top: 50%; 81 | left: 50%; 82 | width: 200px; 83 | height: 120px; 84 | margin-left: -100px; 85 | margin-top: -60px; 86 | } 87 | ``` 88 | 89 | ### 2. 使用 translate 移动内容 90 | 91 | 很多时候 `.content` 的宽高可能并不固定,此时可以使用 `transform` 中的 `translate` 来移动内容,这是因为 `translate(-50%, -50%)` 中的百分比是基于自身尺寸计算的,而非 margin 中那样基于父元素尺寸计算。因此对于内容不固定的情况,以下代码能轻松实现垂直居中: 92 | 93 | ```css 94 | .container{ 95 | position: relative; 96 | } 97 | .container .content{ 98 | position: absolute; 99 | top: 50%; 100 | left: 50%; 101 | transform: translate(-50%, -50%); 102 | } 103 | ``` 104 | 105 | 该方案的缺点是,使用了 translate 可能影响到其他的变形,且为了兼容较旧的浏览器,需要添加浏览器厂商前缀。 106 | 107 | ## 基于视口单位的解决方案 108 | 109 | 在弹出对话框或者类似的场景下,我们希望某元素能够居中于视口中。这个时候可以使用 fixed 定位,外加上面提到的 “基于绝对定位的解决方案” 中类似的方法来实现。 110 | 111 | 此外你还有另外一种选择,那就是使用 `vh` 和 `vw` 这两个单位,`100vw` 就等于视口的宽度,也就是说 `1vw` 等于 1/100 的视口宽度,`vh` 也同理,`1vh` 等于 1/100 视口的高度。因此可以写出下列代码来将一个对话框在视口中居中: 112 | 113 | ```css 114 | .dialog{ 115 | position: fixed; 116 | margin-top: 50vh; 117 | margin-left: 50vw; 118 | transform: translate(-50%, -50%); 119 | } 120 | ``` 121 | 122 | ## 基于 flexbox 的解决方案 123 | 124 | 有了 flexbox 之后会发现实现垂直居中实在不能太容易,因为你只需要将容器的 `display` 指定为 `flex`,然后让内容在主轴和交叉轴上居中就可以了。甚至直接将容器指定为 `flex` 将内容的 `margin` 设置为 `auto` 就可以了。 125 | 126 | ```css 127 | .container{ 128 | display: flex; 129 | } 130 | .container .content{ 131 | margin: auto; 132 | } 133 | ``` 134 | 135 | 或者只需给容器元素设置: 136 | 137 | ```css 138 | .container{ 139 | display: flex; 140 | justify-content: center; 141 | align-items: center; 142 | } 143 | ``` 144 | 145 | 有了这些方案,再也不怕垂直居中了。:sunglasses: 146 | -------------------------------------------------------------------------------- /_posts/rec/2019-09-10-ncf.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 论文阅读 - Neural Collaborative Filtering 4 | category: 推荐系统 5 | tags: ['推荐系统'] 6 | --- 7 | 8 | 这是本文要讨论的论文: 9 | 10 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/10/5d773f80451253d178363e13.jpg) 11 | 12 | - * 13 | {:toc} 14 | 15 | ## 背景 16 | 17 | 在基于模型的协同过滤技术(Model-Based CF)中,矩阵分解(matrix factorization, MF) 应用的最多。在 MF 中 user-item 矩阵被分解为 user 矩阵和 item 矩阵。user 和 item 都被映射到一个隐空间中,各自有一个隐向量。这个隐向量可以用来做基于近邻的推荐(计算隐向量的相似度),也可以使用 user 和 item 隐向量的内积,来预测该 user 对该 item 的评分。 18 | 19 | user 和 item 的隐向量内积,可以用来确定 user 对 item 的评分。有了 user 对各个 item 的评分,自然可以对 item 进行排序,得出推荐。但本篇论文认为,简单地使用隐向量内积,不足以捕获到复杂的交互行为特征,即评分并不一定是隐向量之积。本文通过引入神经网络,来学习用户与物品的隐向量和评分的关系。 20 | 21 | ## 问题设定 22 | 23 | 本论文讨论的是隐式反馈协同过滤场景,关于显示反馈和隐式反馈,定义如下: 24 | 25 | - 显式反馈:直接反应出用户的喜好的行为,比如评分。 26 | - 隐性反馈:间接反应用户的喜好的行为,比如浏览、点击、搜索 27 | 28 | 隐式反馈的数据由 0 和 1 组成,1 不一定表示喜好,0 只表示用户尚未和该物品有过交互。设 $Y$ 为 user-item 矩阵,则 `$y_{ui}=1$` 表示 user u 和 item i 存在交互信息,否则 `$y_{ui}=0$`。 29 | 30 | 推荐问题就变成了预测矩阵中为 0 部分的评分,并以此来排序生成推荐。 31 | 32 | Matrix Factorization,MF 分解 user-item 矩阵,将 user 和 item 映射到低位隐空间中,但 MF 存在一些问题,作者举了一个例子: 33 | 34 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/10/5d7754b8451253d17838bc20.jpg) 35 | 36 | 上图中,左边为原始的 user-item 矩阵,观察这个矩阵可以计算出,`$u_1,u_2,u_3$` 之间的相似度。如果将矩阵进行分解,将 item 的向量降维至 2 维,`$p_i$` 为 `$u_i$` 的隐向量。右图中各向量的夹角可以正确地表达 `$u_1,u_2,u_3$` 之间的相似度,`$u_2$` 和 `$u_3$` 最相似,`$u_1$` 和 `$u_2$` 的相似度大于和 `$u_3$` 的相似度。 37 | 38 | 观察虚线框中的 `$u_4$`,它与 `$u_1$` 最接近,其次是 `$u_3$`,最后才是 `$u_2$`。但在隐空间中,这种关系没法表示出来。`$p_4$` 要想和 `$p_1$` 的夹角最小,那么它必然和 `$p_2$` 的夹角要小于和 `$p_3$` 的夹角。 39 | 40 | **注**:上面这个问题,直观地想,会在使用隐向量计算相似度的时候存在问题,因为相似度是用夹角衡量的。但怎么能说明 MF 使用内积来估计评分是有问题的呢?夹角大小关系在降维后出现了错乱,而 cosine 的分子上其实就是两个向量的内积。可能这能间接地说明,使用内积不足以可靠地预测评分。 41 | 42 | ## Neural Collaborative Filtering 43 | 44 | 作者试图用一个模型来学习 user 和 item 的隐向量和评分之间的关系。下面是模型的基本结构,很容易理解。 45 | 46 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/10/5d7759f9451253d178397ea8.jpg) 47 | 48 | 在 MF 中,其实就相当于对 user 和 item 做了嵌入,然后 user 和 item 的 Embedding 的内积等于 user 对 item 的打分,整体上可以作为一个回归问题,让均方误差最小即可。 49 | 50 | 而此处因为是隐式反馈,在 user-item 矩阵中,user 和 item 有交互就是 1,否则为 0。这里模型的输入为 user 和 item,当输入的 user item 对之间存在交互的时候,就希望模型输出 1,否则输出 0。 51 | 52 | 因此作者把问题转换为了一个分类问题,正例就是从存在交互的 user item 对,负例就是对每个 user 随机抽一些没有交互记录的 item,构成 user item 对,作为反例。 53 | 54 | 整个模型就是做一个二分类的任务,使用 log loss,用梯度下降训练即可。 55 | 56 | ### Generalized Matrix Factorization (GMF) 57 | 58 | 作者指出如果上图中的 Neural CF Layers 部分做的工作就是将 user 和 item 的 Embedding 做点积(对应元素相乘),得到一个和 Embedding 等长的向量,然后交给 logistics regression。 59 | 60 | $$ 61 | \hat{y}_{u i}=a_{o u t}\left(\mathbf{h}^{T}\left(\mathbf{p}_{u} \odot \mathbf{q}_{i}\right)\right) 62 | $$ 63 | 64 | 上面式子中,`$\odot$` 表示对应元素相乘,如果 $h$ 是全 1 向量,那模型实际上就是 MF 了。 65 | 66 | 67 | ### Multi-Layer Perceptron (MLP) 68 | 69 | 既然都说了,MF 存在问题,那自然要改进了,改进方法就是引入多层感知机。把 user 和 item 的 Embedding 拼起来,然后输入给多层感知机,就可以了。这里的 Embedding 在 MF 的语境下,就是隐向量。 70 | 71 | MLP 能够引入非线性的变换,有能力捕获到更加复杂的特征组合。有望利用 user 和 item 的隐向量,学得一个更好的模型,用以估计 user 和 item 是否存在交互。 72 | 73 | ### 结合 GMF 和 MLP 74 | 75 | MF 对 user 和 item 的隐向量做内积,是线性模型。而 MLP 是非线性的。组合线性和非线性也许有效果,那就组合一下吧: 76 | 77 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/10/5d776506451253d1783ac347.jpg) 78 | 79 | 就是 MLP 和 GMF 的最后一层的向量拼接起来,然后交给 logistics regression。上图中好像 GMF 和 MLP 共用了一个 Embedding 一样。论文中说,共用 Embedding 需要 GMF 和 MLP 用相同的维度。学习单独的 Embedding 可能得到更好的集成效果。 80 | 81 | 82 | ## 实验 83 | 84 | 在训练的时候正样本就是由评分记录构造,针对每个 user 随机选取其未交互过的 item 来构造负样本。在训练时为每个 user 保留最近的一个交互过的 item,评估性能时,随机抽取 100 个未与用户交互的 item,并加入保留的 item。用训练好的模型对所有 item 进行排序,然后看保留的这个 item 出现的位置。位置越靠前,说明效果越好。 85 | 86 | ## 代码实现 87 | 88 | 论文原作者在 github 给出了实现:[hexiangnan/neural_collaborative_filtering](https://github.com/hexiangnan/neural_collaborative_filtering) 89 | 90 | 我参考上面的实现进行了一些改写,专注于模型部分:[NCF.ipynb](https://github.com/wy-ei/notebook/blob/master/rec/notebook/NCF.ipynb) -------------------------------------------------------------------------------- /site/assets/css/scss/m-post.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | 4 | .post { 5 | .content { 6 | img { 7 | display: block; 8 | margin: auto; 9 | } 10 | .img-alt { 11 | text-align: center; 12 | > p { 13 | line-height: 1.5em; 14 | display: inline-block; 15 | margin: 1em auto; 16 | text-indent: 0; 17 | border-bottom: 1px solid $border-color; 18 | } 19 | } 20 | 21 | iframe { 22 | display: block; 23 | margin: auto; 24 | padding-top: 1em; 25 | padding-bottom: 2em; 26 | border: none; 27 | } 28 | iframe[src*='music'] { 29 | width: 100%; 30 | } 31 | .emoji { 32 | display: inline-block; 33 | height: 1.33em; 34 | width: 1.33em; 35 | border: none; 36 | vertical-align: -0.3em; 37 | margin: 0 0.1em; 38 | overflow: visible; 39 | } 40 | 41 | table{ 42 | width: 100%; 43 | } 44 | 45 | .footnotes{ 46 | margin-top: 50px; 47 | ol{ 48 | margin-left: 20px; 49 | } 50 | } 51 | .footnotes::before{ 52 | content: "参考文献"; 53 | font-size: 1.2em; 54 | border-bottom: 1px dotted #d1d1d1; 55 | display: block; 56 | line-height: 2em; 57 | margin-bottom: 20px; 58 | } 59 | .footnote{ 60 | margin-left: 5px; 61 | } 62 | .footnote::after{ 63 | content: "]" 64 | } 65 | .footnote::before{ 66 | content: "[" 67 | } 68 | 69 | h2 code, 70 | h3 code, 71 | h4 code, 72 | h5 code, 73 | li code, 74 | lo code, 75 | p code{ 76 | padding: .1em .2em; 77 | color: #e50053; 78 | border-radius: 4px; 79 | 80 | &::before, &::after { 81 | content: "`"; 82 | } 83 | } 84 | 85 | } 86 | 87 | #markdown-toc{ 88 | margin: 0; 89 | padding: 1em; 90 | border: 1px solid #eee; 91 | border-radius: 5px; 92 | margin-bottom: .3rem; 93 | background-color: #f8f8f8; 94 | box-sizing: border-box; 95 | 96 | li { 97 | list-style-position: inside; 98 | } 99 | } 100 | #markdown-toc::before { 101 | content: '目录:'; 102 | display: block; 103 | font-size: .20rem; 104 | border-bottom: 1px dotted #ccc; 105 | margin-bottom: 20px; 106 | } 107 | 108 | .toc{ 109 | margin-bottom: 30px; 110 | background: $gray-background-color; 111 | padding: 1em; 112 | } 113 | .toc ul{ 114 | margin-bottom: 0; 115 | } 116 | .toc h2{ 117 | margin-top: 0; 118 | } 119 | .toc-level-2{ 120 | margin-left: 1.5em; 121 | } 122 | .toc-level-3{ 123 | margin-left: 3em; 124 | } 125 | .toc-level-4{ 126 | margin-left: 4.5em; 127 | } 128 | 129 | } 130 | 131 | 132 | 133 | #markdown-toc.popup{ 134 | display: block; 135 | position: fixed; 136 | top: 50%; 137 | left: 50%; 138 | transform: translate(-50%,-55%); 139 | z-index: 20; 140 | max-height: 80vh; 141 | overflow: auto; 142 | } 143 | 144 | .toc-placeholder{ 145 | display: none; 146 | margin-bottom: .3rem; 147 | border: 1px solid #fff; 148 | } 149 | .popup+.toc-placeholder{ 150 | display: block; 151 | } 152 | .popup+.toc-placeholder::after{ 153 | content: ""; 154 | width: 100%; 155 | height: 100%; 156 | position: fixed; 157 | z-index: 10; 158 | top: 0; 159 | left: 0; 160 | background-color: #000a; 161 | cursor: pointer; 162 | } 163 | 164 | -------------------------------------------------------------------------------- /_posts/machine_learning/2019-09-30-what-is-machine-learning.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: 机器学习 - 简介 4 | category: 机器学习 5 | --- 6 | 7 | 8 | 我接触机器学习有一年多了,在学习过程中一直独自探索,走了不少弯路。比如,在网上查如何学习机器学习,别人会推荐你去看南京大学周志华老师的[《机器学习》](https://book.douban.com/subject/26708119/),李航博士的[《统计学习方法》](https://book.douban.com/subject/33437381/)。但我相信大部分人在如何去读这些书,一定会遭受挫折,进而可能对机器学习敬而远之。因为在缺少必要的背景知识的情况时,去读这些偏重理论的书,是不会成功的。 9 | 10 | 有人认为机器学习很注重数学,应该先去把数学学好。其实错了,在入门阶段,大部分机器学习算法只需要有高中数学+忘得差不多的高等数学有足够了。我们应该去实践这些算法,用这些算法去解决某个问题,在实践中去感受各种算法,然后在去详细地了解原理,而不是一开始就埋头于理论中。 11 | 12 | 学习机器学习和其他编程知识类似,你依然需要从代码入手,先跑一跑简短的例子,然后再试图搞明白其中的原理,这个时候你可以去查阅前面提到的书籍。看书 1 小时,就应该实践 10 小时,机器学习也是如此。本系列文章准备通俗(可能不严谨)地讲解常见的机器学习算法,并给出一些直观的解释。很多涉及推导的算法,我做不到比相关书籍讲的还好,在某些地方也会推荐看相关公开课,和相关书籍。每个知识点,会附加一些实际的例子,通过例子可能切实地观察到机器学习算法运行的过程和结果,加强对各个知识点的理解。对需要补充学习的知识点,我会给出我认为最好的参考资料,以及学习建议。 13 | 14 | 李宏毅老师的机器学习[课程](speech.ee.ntu.edu.tw/~tlkagk/courses_ML16.html) 讲的很好,老师给出了大量的例子,已经直观的比喻,我认为可以从这门课程入手学习必要的理论。在学习过程中千万不要试图在短时间内穷尽所有的算法,应该一个一个来,在学习了理论之后,尝试使用该算法,然后再自行推导该算法,如此反复多次地使用算法。 15 | 16 | 费曼先生说过 “If you want to master something, teach it.”,本系列文章更多是帮助自己梳理机器学习中的各种知识点。同时我希望他能对别人也有用。 17 | 18 | ## 机器学习的整个脉络 19 | 20 | 下图中总结了机器学习算法的大体分类: 21 | 22 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/24/5d8a15f1451253d1785c319a.jpg) 23 | 24 | 图片来源见水印,感谢原图作者。 25 | 26 | 要想运用机器机器学习算法解决某个问题,单了解了机器学习算法是远远不够的,还需要了解数据处理的方法,掌握常见工具的使用。再后面的一系列文章中,我们用到具体的工具时,我会给出一些建议的资料。 27 | 28 | 这里我想先对机器学习中的一些名词和基本概率做一下解释,万一真的有小白来看这些教程呢? 29 | 30 | ## 什么是机器学习 31 | 32 | 在学习具体的机器算法之前,需要大致知道机器学习是什么?机器学习是一系列方法,用于从数据中寻找规律,自动发掘出数据中某些特征之间的关系,进而给出有用的预测。下面举个例子: 33 | 34 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/24/5d8a1bdd451253d1785d6864.jpg) 35 | 36 | 一个邮件过滤程序,需要能够分辨出邮件是否为垃圾邮件。要想分辨出垃圾邮件,传统的编程方法可能是为垃圾邮件制定一系列的规则,比如包含 “促销”、"抢购" 等词的邮件视为垃圾邮件。但这需要大量的规则,而且还会出现误判。 37 | 38 | 如果使用机器学习,解决这一问题的方法是:给机器学习算法一大堆邮件,并告诉它那些是垃圾邮件,那些是正常邮件,然后让算法自动地寻找垃圾邮件和正常邮件的特征。学习完成后,该机器学习算法就会知道垃圾邮件有什么特征,下一次遇到一封新的邮件,此程序就能对该邮件分类。 39 | 40 | 机器学习就是让机器去从数据中学习,至于如何学习,这就是研究机器学习算法的人要考虑的事情了。我们学习机器学习,就是要学会那些能让机器进行自动学习的方法。然后将这些方法用代码实现,而后机器就可以自动地学习了。 41 | 42 | 43 | ## 机器学习算法的分类 44 | 45 | 机器学习算法从不同的角度可以分为很多的类别,但这里,我不愿把问题弄的太复杂,只描述最常见的分类 46 | 47 | 人类每天都在学习,我们通过看书、听课、做练习来进行学习,我们从书本、老师那里学习。机器学习算法是让机器进行学习的一系列方法,我们人类从书中学习,机器从数据中学习,人通过看书来学习,机器通过机器学习算法来学习。机器学习算法就是机器进行学习的策略。 48 | 49 | 在垃圾分类的场景下,机器从大量的邮件中来学习,每个邮件有具体的内容,以及邮件对应的类别(是否为垃圾邮件)。在机器学习中,我们把邮件内容经过处理得到邮件的特征(feature),特征用 $x$ 表示,邮件的类别我们称为标签(label),标签使用 $y$ 来表示。 50 | 51 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/24/5d8a1dbc451253d1785dc523.jpg) 52 | 53 | 机器学习算法就相当于一个函数,把邮件的特征输入进入,函数返回分类的结果。那么这个函数的定义是怎样的呢,函数的定义就是机器要使用机器学习算法来学习的目标。有了这个函数,新的邮件输入进来就可以判断它是否为垃圾邮件了。 54 | 55 | ### 监督学习(supervised learning) 56 | 57 | 每个邮件的特征是多个维度的,比如发件人信息、文本内容、是否包含图片等等,在机器学习中,我们把参与训练的数据中最小单元成为样本。 58 | 59 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/24/5d8a226e451253d1785ec030.jpg) 60 | 61 | 假设这里的样本包含两个维度的特征,每个样本都对应在二维平面上的一个点,图中 O 和 X 是样本的标签。要想区分两类样本,机器学习算法可能会寻找到一条分界线,在平面上将两类样本完全分开。 62 | 63 | 只有样本有对应的标签时,我们才能画出这条分界线。这类使用有标签的数据进行学习的算法被成为监督学习。监督学习给定了输入 $X$ 和 输出 $y$,然后去学习由 $X \to y$ 的映射函数 $f$。 64 | 65 | ### 非监督学习(unsupervised learning) 66 | 67 | 监督学习需要样本有标签,而标签往往时不易获得的,常常需要人工去进行标注。现实生活中有的往往是没有标签的样本。 68 | 69 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/24/5d8a250e451253d1785f7889.jpg) 70 | 71 | 对于没有标签的样本,机器学习算法可以通过分析整个样本集,发现其内在的规律。比如上图中的例子,样本并没有标签,但是它们明显聚成了两个簇。如果一个任务是对样本进行归类,那么不需要标签也可以做到。这类通过挖掘数据分布,而不依赖标签的学习方法被称为非监督学习。常见的聚类算法就是非监督学习。 72 | 73 | ### 半监督学习(semisupervised learning) 74 | 75 | ![](https://wangyu-name.oss-cn-hangzhou.aliyuncs.com/superbed/2019/09/24/5d8a27d6451253d1786013ca.jpg) 76 | 77 | 有大量无标签的样本,和少量有标签的样本。采用非监督学习可以对无标签样本进行聚类,利用落在各个类别中的少量有标签样本,能够确定各个类别的某些特征。比如相册应用,可以将一个人的不同照片聚类,一旦其中的某张照片被用户标记为某个人,那么类别中所有照片就都被标记了。 78 | 79 | ## 总结 80 | 81 | 本文交代了这一系列教程的用意,并对机器学习的个别概念进行了解释,这些内容对于熟悉的人而言没有任何价值,对不懂机器学习的人而言又显得太少。因此,推荐一些阅读材料供入门者食用。 82 | 83 | ## 推荐阅读 84 | 85 | 读完了上面的内容,你一定还是不知道什么是机器学习,这很正常。这里推荐台湾大学李宏毅老师的机器学习课程,你可以看一下第一讲。第一讲中举了大量的例子来描述什么是机器学习,相信看完之后你对机器学习的整体会有较清晰的认识。 86 | 87 | 李宏毅老师机器学习课程介绍 [PPT](http://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2016/Lecture/Introduction%20(v4).pdf) 和 [课程](https://www.bilibili.com/video/av35932863?from=search&seid=1767191564165457969)。 -------------------------------------------------------------------------------- /_posts/web/css/2015-08-20-css-selector.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: CSS 选择器 4 | category: Web 5 | pid: css 6 | --- 7 | 8 | 9 | * toc 10 | {:toc} 11 | 12 | 13 | ## 基本选择器 14 | 15 | ```html 16 |
    17 |
  • CSS
  • 18 |
  • JavaScript
  • 19 |
  • HTML
  • 20 |
21 | ``` 22 | 23 | ```css 24 | /* 选择所有 li */ 25 | li 26 | 27 | /* 选择包含类 item 的元素 */ 28 | .item 29 | 30 | /* 选择 id 为 list 的元素 */ 31 | #list 32 | 33 | /* 选择同时有 item 和 active 类的 标签*/ 34 | .item.active 35 | ``` 36 | 37 | **根据属性来选择** 38 | 39 | ```css 40 | /* 选择所有具有 class 属性的 h1 标签 */ 41 | h1[class] 42 | 43 | /* 选择同时具有 href 和 class 属性的 a 标签 */ 44 | a[href][class] 45 | 46 | /* 选择所有类型为 text 的 input 标签 */ 47 | input[type='text'] 48 | ``` 49 | 50 | **属性的匹配方式有多种,举例说明如下:** 51 | 52 | 53 | ```css 54 | /* 选择所有设置了 href 的 a 标签 */ 55 | a[href] 56 | 57 | 58 | /* 选择所有 type 属性为 'text' 的 input 标签 */ 59 | input[type=text] 60 | 61 | 62 | /* 63 | 选择 alt 属性以单词 css 开头的 img 标签 64 | 65 | css selector 66 | css-selector 67 | */ 68 | img[alt|=css] 69 | 70 | 71 | /* 72 | 选择 src 属性中包含字符串 selector 的 img 标签 73 | 74 | css selector 75 | */ 76 | img[src*=selector] 77 | 78 | 79 | /* 80 | 选择 class 属性中包含字符串单词 selector 的 img 标签,与前一个不同的是,这里要求是单词,前一个匹配的是子字符串 81 | 82 | 83 | */ 84 | img[clsss~=selector] 85 | 86 | 87 | /* 88 | 选择 src 属性以 'http' 为前缀的 img 标签 89 | 90 | 91 | */ 92 | img[src^=http] 93 | 94 | 95 | /* 96 | 选择 src 属性以 'jpg' 为后缀的 img 标签 97 | 98 | 99 | */ 100 | img[src$=jpg] 101 | ``` 102 | 103 | 104 | 注意:乍一看好像 `img[src^=http]` 和 `img[src|=http]` 是一样的,其实不然。前者匹配前缀字符串,后缀匹配第一个单词。举个不恰当的例子: 105 | 106 | ```html 107 | 108 | 109 | ``` 110 | 111 | 第一个 img 标签不会被 `img[src|=http]` 匹配,因为 src 属性开头的第一个单词为 `https`。 112 | 113 | ## 层次选择器 114 | 115 | + `div a` 后代选择器,选择所有在 div 里面的 a 116 | + `p>a` 直接后端选择器,选择所有为 p 元素的直接后代的 a 元素 117 | + `p+a` 相邻兄弟选择器,选择紧跟在 p 元素的 a 元素 118 | + `p~span` 选择 p 元素之后的所有 span 元素 119 | 120 | ## 伪类选择器 121 | 122 | 伪类选择器可以分为 6 类: 123 | 124 | + 动态伪类选择器 125 | + 目标伪类选择器 126 | + 语言伪类选择器 127 | + UI状态伪类选择器 128 | + 结构伪类选择器 129 | + 否定伪类选择器 130 | 131 | ### 动态伪类选择器 132 | 133 | + `a:link`:匹配定义了 `href` 属性的 a 标签 134 | + `a:visited`:匹配点击过的 a 标签 135 | + `E:active`:匹配正被激活的元素,比如正在点击的连接或者按钮 136 | + `E:hover`:匹配鼠标落在其上的 E 137 | + `E:focus`:匹配获得焦点的 E 138 | 139 | 关于以上属性有一个 LoVe/HAte 的规则,至于为什么,可以参看[这里](https://github.com/wy-ei/notebook/blob/master/css/2015-12-19-why-love-hate.md) 140 | 141 | ### 目标伪类选择器 142 | 143 | + `:target`:匹配有着和 URL 中的 hash 相同的 id 的元素 144 | 145 | ```html 146 | see part 1 147 |

part 1

148 | ``` 149 | 150 | 当点击了标签之后,url 中的锚点就变成了 part-1 这就和 p 标签的 id 匹配了。此时该 p 标签被匹配。 151 | 152 | ### 语言伪类选择器 153 | 154 | 语言伪类选择器是通过标签的 lang 属性来进行匹配的 155 | 156 | ```css 157 | E:lang(language){} 158 | ``` 159 | 160 | 可以使用该属性来匹配不同语言的元素 161 | 162 | ### UI状态伪类选择器 163 | 164 | + `E:checked` 165 | + `E:enabled` 166 | + `E:disabled` 167 | + `E:focus` 168 | 169 | 这几个属性通常用在 `` 和 `