-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
279 lines (166 loc) · 152 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Dynastywind</title>
<subtitle>Sapientia Et Virtus</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://blog.lyndonli.com/"/>
<updated>2019-12-15T08:16:02.384Z</updated>
<id>http://blog.lyndonli.com/</id>
<author>
<name>Lyndon Li</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>变分自编码器</title>
<link href="http://blog.lyndonli.com/2019/12/15/%E5%8F%98%E5%88%86%E8%87%AA%E7%BC%96%E7%A0%81%E5%99%A8/"/>
<id>http://blog.lyndonli.com/2019/12/15/变分自编码器/</id>
<published>2019-12-15T07:16:02.000Z</published>
<updated>2019-12-15T08:16:02.384Z</updated>
<content type="html"><![CDATA[<blockquote><p>道在不可见,用在不可知。 <em>– 《韩非子》</em></p></blockquote><a id="more"></a><script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML" async></script><h1 id="总述"><a href="#总述" class="headerlink" title="总述"></a>总述</h1><p><a href="https://arxiv.org/pdf/1312.6114.pdf" target="_blank" rel="noopener">变分自编码器</a>(下称VAE)是一种类似自编码器(Autoencoder,下称AE)的神经网络结构,存在encoder和decoder部分,前者用于计算输入变量的隐变量分布,后者用于将隐变量还原为输入变量。但是和普通的AE不同的是,VAE经过encode过程之后得到的是输入变量的隐变量分布,而不是AE中的隐变量本身。同样的,decoder的输入也是通过一个采样过程来实现的,并不是简单的一个隐变量输入。因此,VAE相比于AE有更好的鲁棒性,抗噪能力更强,除了常规的图像处理任务以外,VAE也能胜任时序数据处理任务,比如异常点检测等。</p><h1 id="从无到有"><a href="#从无到有" class="headerlink" title="从无到有"></a>从无到有</h1><p>现在来看一下VAE思想是如何产生的。我们希望有一个模型,将输入变量经过这个模型转换后输出的结果是原始数据真实分布中产生的,没有原始输入的噪声。现在假设我们有一批数据样本$\{X_1, X_2, …, X_n\}$,其总体记为X,如果我们能直接从这些样本中得到X的分布p(X),那就可以直接使用p(X)来采样获得所有可能的X了。然而,现实总是很残酷的,X的真实分布往往是很难获取的,所以我们想通过曲线迂回的方式来达到我们的目的。既然X本身的分布不好获取,那是否能够通过一个已知的分布来间接获取X的分布呢?这就是VAE模型的起点。现在我们使用一个服从标准正态分布的变量Z,通过Z来计算X的分布可以利用下式得到:<br>$$p(X) = \sum_{Z}p(X|Z)p(Z)$$<br>由于Z服从标准正态分布,即$p(Z)=N(0, 1)$,因此我们可以从标准正态分布中采样一个Z,然后根据这个Z来计算一个X,进而解决之前的问题。所以我们构建一个特殊的AE模型,在这个模型中,我们希望encoder能够生成输入变量X的隐变量Z的分布,而不是隐变量本身。由于最终要将生成结果和输入数据一一对比,因此encoder的生成结果是<strong>输入变量特定</strong>的分布,即encoder的生成结果是一个Z的后验概率分布$p(Z|X_k)$,而不是直接利用$p(Z)$来还原,否则将无法确定生成的结果对应于哪一个原始输入。而$p(Z|X_k)$是一个正态分布,由于一个正态分布由均值和方差唯一确定,我们无法直接获取这两个值,转而借由神经网络来进行拟合。所以VAE的encoder有两个,一个用于生成均值,一个用于生成方差,通过这两个结果来构建一个正态分布,再通过这个分布来采样得到隐变量Z,进而经过decoder生成一个X。下面来看一下VAE的训练目标。</p><h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><p>VAE构建了两个神经网络:$\mu_k=f_1(X_k)$用于计算$X_k$的均值,$log \sigma_k^2=f_2(X_k)$用于计算$X_k$的方差(拟合log函数而非方差本身能够避免引入激活函数限定值域范围),通过这两个函数我们唯一确定一个正态分布,通过采样获得对应于$X_k$的$Z_k$,接着使用decoder得到$\hat{X_k}=g(Z_k)$,最小化$X_k$与$\hat{X_k}$的差异$D(X_k, \hat{X_k})^2$即是我们的目标。</p><p>然而,直接这样训练存在着问题,因为$Z_k$是通过采样得到的,每一次采样都存在噪声(由方差引入),所以模型的训练结果会尽量让这个方差变为0来完美拟合均值,最终结果就是VAE退化成了一个普通的AE。为了解决这个问题,VAE要求所有$p(Z|X_k)$的分布均向标准正态分布看齐,而不是向方差为0看齐,根据定义:</p><p>$$p(Z)=\sum_{X}p(Z|X)p(X)=\sum_{X}N(0,1)p(X)=N(0,1)\sum_{X}p(X)=N(0,1)$$</p><p>于是我们就有了一个服从标准正态分布的Z,可以从这个分布中进行采样生成数据同时避免0噪声了。</p><p>但是如何要求$p(Z|X)$都向标准正态分布看齐呢?这里引入KL散度来衡量两个分布之间的距离并使之最小化:</p><p>$$KL(N(\mu, \sigma^2)||N(0,1))\\<br>=\int\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x-\mu)^2/2\sigma^2}(log\frac{e^{-(x-u)^2/2\sigma^2}/\sqrt{2\pi\sigma^2}}{e^{-x^2/2}/\sqrt{2\pi}})dx\\<br>=\int\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x-\mu)^2/2\sigma^2}log(\frac{1}{\sqrt{\sigma^2}}exp(\frac{x^2-(x-\mu)^2/\sigma^2}{2}))dx\\<br>=\frac{1}{2}\int\frac{1}{\sqrt{2\pi\sigma^2}}e^{-(x-\mu)^2/2\sigma^2}[-log\sigma^2+x^2-(x-\mu)^2/\sigma^2]dx\\<br>=\frac{1}{2}(-log\sigma^2+\mu^2+\sigma^2-1)$$</p><p>(上式倒数第二行中括号中的项结合积分,第一项就是$-log\sigma^2$与概率密度积分之积,第二项为正态分布二阶矩,第三项为两个方差之商)</p><h2 id="再参数化(reparameterization)"><a href="#再参数化(reparameterization)" class="headerlink" title="再参数化(reparameterization)"></a>再参数化(reparameterization)</h2><p>由于$Z_k$是通过$p(Z|X_k)$采样得到的,因此该过程在数学上是不可导的,为了能够使用梯度下降方法进行训练,我们利用再参数化技巧利用采样结果进行求导,而不是采样动作本身。由于$p(Z|X_k)$服从正态分布,因此可以有下式成立:</p><p>$$\int\frac{1}{\sqrt{2\pi\sigma^2}}exp(-\frac{(z-\mu)^2}{2\sigma^2})dz\\<br>=\int\frac{1}{\sqrt{2\pi}}exp[-\frac{1}{2}(\frac{z-\mu}{\sigma})^2]d(\frac{z-\mu}{\sigma})$$</p><p>因此$\epsilon=(z-\mu)/\sigma$是服从标准正态分布的,我们使用标准正态分布中的变量$\epsilon$经过变换$Z=\epsilon*\sigma+\mu$来得到Z。</p><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>有别于普通的AE,VAE通过训练两个encoder来生成一个隐变量分布,并通过采样的方式获得隐变量,第一个encoder用于生成均值,由于采样过程引入了噪声,因此训练出来的decoder能够有比较好的抗噪能力。同时KL散度的引入正则化了均值encoder,使得均值向0靠拢而不是去拟合噪声。第二个encoder用于生成方差,即数据噪声,动态调节噪声强度。当decoder还未训练成熟时,方差encoder会适当降低噪声,让重构误差D下降,KL散度上升;而当decoder训练得很好时,方差encoder就会提高噪声,让重构误差D回升,KL散度下降。由此可见,重构误差希望没有噪声,而KL散度希望引入噪声,两者互相对立并一同走向统一,有点GAN的味道,只不过VAE里矛盾双方是共同进化的。至于为什么VAE叫变分自编码器,是因为训练过程中引入了KL散度,该函数是一个泛函,求泛函的极值要使用变分法,但是这里直接利用了正态分布的一些性质,从而避免使用复杂的变分法求解问题。</p>]]></content>
<summary type="html">
<blockquote>
<p>道在不可见,用在不可知。 <em>– 《韩非子》</em></p>
</blockquote>
</summary>
<category term="machine learning" scheme="http://blog.lyndonli.com/categories/machine-learning/"/>
<category term="AI" scheme="http://blog.lyndonli.com/categories/machine-learning/AI/"/>
<category term="computer science" scheme="http://blog.lyndonli.com/categories/machine-learning/AI/computer-science/"/>
<category term="vae" scheme="http://blog.lyndonli.com/tags/vae/"/>
<category term="variational autoencoder" scheme="http://blog.lyndonli.com/tags/variational-autoencoder/"/>
<category term="变分自编码器" scheme="http://blog.lyndonli.com/tags/%E5%8F%98%E5%88%86%E8%87%AA%E7%BC%96%E7%A0%81%E5%99%A8/"/>
</entry>
<entry>
<title>前馈神经网络</title>
<link href="http://blog.lyndonli.com/2019/10/13/Neural-Network/"/>
<id>http://blog.lyndonli.com/2019/10/13/Neural-Network/</id>
<published>2019-10-13T06:57:24.000Z</published>
<updated>2019-10-13T09:02:23.010Z</updated>
<content type="html"><![CDATA[<blockquote><p>人法地,地法天,天法道,道法自然。 <em>– 《道德经》</em></p></blockquote><a id="more"></a><script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML" async></script><h1 id="总述"><a href="#总述" class="headerlink" title="总述"></a>总述</h1><p>人工神经网络(以下简称NNet)是一种很著名的机器学习算法,其学习方式的独特性和自动提取数据特征的能力使得它能够独立于其它的传统机器学习方法(比如SVM、决策树等)而自成一派。随着深度学习的普及,NNet大有立于时代浪潮之巅的势头。然而,如今火热的NNet算法,自其诞生开始就历经了波折,其进化之旅并不是一帆风顺的。本文将从NNet的演进开始介绍,重点涉及前馈神经网络及其最核心的后向传播算法,同时附带一些优化技巧,作为近期学习和使用的阶段性总结。</p><h1 id="演进"><a href="#演进" class="headerlink" title="演进"></a>演进</h1><p>NNet并不是一种很年轻的算法。相反,最早的NNet研究从1943年就已经开始了。当全世界都还处在二战的阴影中时,心理学家McCulloch和数学家Pitts参考生物神经元结构(如下图),提出了一种Mindlike Machine的构想,指出这种机器可以通过模拟生物神经元互相联结的结构(轴突与树突)来实现,从而第一次将类神经元模型引入了计算机领域。</p><img src="/2019/10/13/Neural-Network/nnet.jpg" title="自然神经网络"><p>此后不久,Rosenblatt创造出了著名的感知器模型,并成功地完成了一些简单的机器学习任务,从此NNet的研究便开始变得火热起来。然而好景不长,Minsky和Papert从数学上证明了感知器模型并不能实现复杂的逻辑功能,因此将不同的感知器通过多层叠加以实现非线性逻辑成为了一种必然,这就形成了类神经网络的雏形,然而这种多层感知器的训练却一直是个难以解决的问题。所幸在20世纪80年代,Rumelhart和Hinton提出了著名的后向传播算法(Backpropagation),解决了这一棘手难题,并在前馈神经网络中普及应用,NNet模型终于开始大规模地在工业领域铺开。</p><p>但是随着数据变得越加复杂,数据量呈指数级增长,受限于当时的计算机算力,类神经网络模型渐渐地被训练更加高效的其它机器学习算法取代(尽管这些算法往往需要非常优秀的特征工程作为辅助手段),其应用规模和影响力逐渐下滑,并在20世纪90年代进入低谷期,尤其是在90年代中期,NNet模型更是遭遇到了来自SVM的强有力挑战(全局最优解、高效训练)。</p><p>10年沉沦之后,在2006年,提出了后向传播算法的Hinton在《Science》上发表了论文,首次提出了“深度信度网络”的概念,通过预训练(pre-training)和微调(fine-tuning)过程的结合,大大减少了多层NNet的训练时间,同时有赖于计算机算力的提升(尤其是GPU提供的强大的矩阵运算能力),NNet在训练上的劣势逐渐变得不再重要,相反,自动提取数据特征这一优势却使得NNet超越了其它模型而鹤立鸡群,这一模型学习方式就是现在最火热的“深度学习”,这种多层的NNet也被称为“深度神经网络”。现如今,深度网络已经逐渐在图像领域实现了大规模成功的应用,在NLP领域也有不少成功的例子,其火热程度目前没有任何一个机器学习算法可以匹敌。</p><p>在这一部分的剩余篇幅内,我们将介绍NNet演进过程中的三个里程碑式的模型:感知器、类神经网络以及深度神经网络。</p><h2 id="感知器-Perceptron"><a href="#感知器-Perceptron" class="headerlink" title="感知器(Perceptron)"></a>感知器(Perceptron)</h2><p>感知器是模拟生物神经元的产物,其基本结构如下图所示:</p><img src="/2019/10/13/Neural-Network/perceptron.png" title="感知器"><p>最左边的一列代表外界的输入值,在生物神经元模型中类比外界刺激,每一个刺激都有一个权重与之相称,代表这个刺激对触发神经元的贡献大小。通过输入与权重相乘并加总之后,我们就得到了一个触发值,经过一个非线性函数的转换,这个神经元最终的触发结果就被确定了。整个过程非常类似生物神经元模型中神经元接受外界刺激并作出应激反应,因此这一模型也被称为“感知器模型”。用数学的形式来定义感知器模型,可以描述成以下线性函数的形式:<br>$$g(X) = W^TX$$<br>上式中X是输入向量,W是权重向量。感知器其实就是一个线性函数。</p><h3 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h3><ul><li>模型简单易解释,权重易于计算</li><li>通过多个感知器在同一层的加入,可以模拟任何线性分类逻辑</li></ul><h3 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h3><ul><li>不能模拟非线性逻辑(比如异或运算)</li></ul><h2 id="类神经网络-Artificial-Neural-Network"><a href="#类神经网络-Artificial-Neural-Network" class="headerlink" title="类神经网络(Artificial Neural Network)"></a>类神经网络(Artificial Neural Network)</h2><p>通过多层、每层多个感知器的叠加,我们就构建出了类神经网络。本文将重点介绍前馈神经网络(以下的NNet将特指此类网络)及其训练方法。一个典型的NNet一般具有如下结构:</p><img src="/2019/10/13/Neural-Network/ann.png" title="类神经网络"><p>左边第一层称为输入层,右边第一层称为输出层,其余层均称为隐藏层。除了输入层以外,每一层都由若干个神经元组成。除了输出层以外,每一层都有一个非零的偏差项(bias),用来在普通神经元计算结果为0的时候仍然能够有非零的输出。除输出层外,每一个神经元有一个激活函数,用来产出这个神经元最终的计算结果。一个NNet的预测过程,就是从一个输入数据出发,后面每一层对上一层的输入进行线性组合(称为分数,score)并完成非线性转换,最终输出预测结果(称为假设,hypothesis)。由于有这样从前到后的预测过程,这种网络也被称为<strong>前馈神经网络</strong>。</p><p>NNet模型相比于其它机器学习算法而言,其优势在于自动特征提取(但是数据质量仍然要保证)。通过多层感知器的叠加,模型可以自动提取数据中的关联关系并进行数据拟合,因此NNet可以被广泛应用在图像识别、声纹识别、NLP等数据特征不容易人为提取的应用领域。</p><h3 id="激活函数"><a href="#激活函数" class="headerlink" title="激活函数"></a>激活函数</h3><p>任何一个在定义域内连续的函数都能成为激活函数(或称转换函数)的备选项。之所以要求函数连续,是因为我们希望在应用类似梯度下降(gradient descent)算法进行训练的时候能够计算损失函数的梯度(依赖于微分计算)从而指引优化方向,而不连续的函数将在不连续的区间内给梯度计算带来不必要的麻烦。比较常见的激活函数包括logistic函数、tanh等。</p><p>同时,激活函数一般要求是非线性的,以实现非线性逻辑,否则N个线性组合的线性组合产生的结果仍将是一个线性变换能够实现的效果。</p><h3 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h3><ul><li>自动特征提取</li><li>模拟线性或非线性逻辑</li></ul><h3 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h3><ul><li>训练成本随着网络层数增加而增加</li><li>模型可解释性差</li><li>可能陷入局部最优解</li></ul><h2 id="深度神经网络-Deep-Neural-Network"><a href="#深度神经网络-Deep-Neural-Network" class="headerlink" title="深度神经网络(Deep Neural Network)"></a>深度神经网络(Deep Neural Network)</h2><p>本文只简要介绍深度神经网络(下称DNN)的结构,关于DNN的训练将会另外写一篇文章论述。</p><img src="/2019/10/13/Neural-Network/dnn.png" title="深度神经网络"><p>上图就是DNN的基本结构,真实的DNN可能拥有更多的隐藏层。每一个隐藏层其实都是一轮数据特征的提取,随着层数的增加,提取的特征也就越加丰富,直到最后一层高级特征用来做最终判断。</p><p>但是DNN也并不简单是多层网络的叠加,和传统NNet不同的是它的<strong>预训练</strong>过程。为了缓解NNet中的局部最优解问题,预训练通过训练数据的自表征(self-representation)来获取DNN的初始权重,而不是采用NNet中的随机赋值方法。这种训练自表征的机制又被称为自编码器(auto-encoder),优秀的自编码器能够让DNN在一个靠近全局最优解的位置开始梯度下降的训练,同时减少数据噪声的干扰。良好的预训练过程也能够使得后面的权重微调过程更快地收敛。</p><h3 id="优点-2"><a href="#优点-2" class="headerlink" title="优点"></a>优点</h3><ul><li>所有NNet的优点</li><li>减少数据噪声的影响</li><li>缓解局部最优解问题</li></ul><h3 id="缺点-2"><a href="#缺点-2" class="headerlink" title="缺点"></a>缺点</h3><ul><li>模型可解释性差</li><li>需要比较大的算力支持</li></ul><h1 id="前馈神经网络的学习"><a href="#前馈神经网络的学习" class="headerlink" title="前馈神经网络的学习"></a>前馈神经网络的学习</h1><p>前馈神经网络的学习依赖于一种叫做<strong>梯度下降</strong>的算法(及其变种,比如随机梯度下降)进行每一层神经元权重的学习。由于最终的训练结果与每一层神经元的权重相关,因此如何将预测误差合理分配到每个权重上就成了一个困难。这个困难曾经阻碍了NNet的发展,直到后向传播算法的出现。下面这一部分将重点介绍前馈神经网络中各神经元权重的学习过程。</p><h2 id="变量定义"><a href="#变量定义" class="headerlink" title="变量定义"></a>变量定义</h2><p>神经元层数:$$L \in \mathbb{N}^+$$</p><p>第l层第j个神经元的得分:$$s_j^{(l)}$$</p><p>激活函数:$$f(s)$$</p><p>输出标签:$$y \in \mathbb{R}$$</p><p>以权重矩阵W为参数的假设函数:$$h_W(X)$$</p><p>第n个数据的预测误差(本文使用平方误差作为损失函数):$$e_n = (y_n - h_W(X_n))^2$$</p><p>第n个数据的预测误差在第l层第i个参数的第j个神经元上的梯度:$$\triangledown h_W(X_n) = \frac{\partial e_n}{\partial w_{ij}^{(l)}}$$</p><h2 id="后向传播-Backpropagation"><a href="#后向传播-Backpropagation" class="headerlink" title="后向传播(Backpropagation)"></a>后向传播(Backpropagation)</h2><p>后向传播算法基于一种最直观的判断,即最终的预测误差是由上一层的计算直接导致的,而这一层的运算又是由之前一层的运算得出的,因此最终的错误责任应该可以从后往前推算,从而将错误的责任按照一定的规则分配到不同的神经元上。为此,我们先来看输出层(假设只有一个神经元)的梯度如何计算。</p><h3 id="输出层梯度"><a href="#输出层梯度" class="headerlink" title="输出层梯度"></a>输出层梯度</h3><p>使用平方误差,预测误差可以写成如下形式:</p><p>$$e_n = (y_n - h_W(X_n))^2 = (y_n - s_1^{(L)})^2 = (y_n - \sum_{i=0}^{d^{(L - 1)}}w_{i1}^{(L)}x_i^{(L-1)})^2$$</p><p>那么损失函数在输出层权重上的微分可以应用<strong>链式法则</strong>推导如下:</p><p>$$\delta_1^{(L)} = \frac{\partial e_n}{\partial w_{i1}^{(L)}} = \frac{\partial e_n}{\partial s_{1}^{(L)}} \cdot \frac{\partial s_1^{(L)}}{\partial w_{i1}^{(L)}} = 2(y_n - s_1^{(L)}) \cdot (x_i^{(L-1)})$$</p><h3 id="一般情况的梯度"><a href="#一般情况的梯度" class="headerlink" title="一般情况的梯度"></a>一般情况的梯度</h3><p>由上式进行泛化推导,可以得到如下的更一般的梯度计算方法:</p><p>$$\frac{\partial e_n}{\partial w_{ij}^{(l)}} = \frac{\partial e_n}{\partial s_{j}^{(l)}} \cdot \frac{\partial s_j^{(l)}}{\partial w_{ij}^{(l)}} = \delta_j^{(l)} \cdot (x_i^{(l-1)})$$</p><p>$\delta_1^{(L)}$已经在上一节获取到了,那么为了获取剩下的所有$\delta$值,我们要来看一下权重的传递关系:</p><p>$$s_j^{(l)} \stackrel{f}{\Longrightarrow} x_j^{(l)} \stackrel{w_{jk}^{(l)}}{\Longrightarrow} s^{(l+1)} \Longrightarrow \cdots \Longrightarrow e_n$$</p><p>第l层的分数经过激活函数转换后得到本层的输入,再与本层的权重进行线性组合得到下一层的分数,以此演进,直到最后输出。因此,一般的$\delta$的计算也可以通过使用<strong>链式法则</strong>得到:</p><p>$$\delta_j^{(l)} = \frac{\partial e_n}{\partial s_j^{(l)}} = \sum_{k=1}^{d^{(l+1)}} \frac{\partial e_n}{\partial s_k^{(l+1)}} \cdot \frac{\partial s_k^{(l+1)}}{\partial x_j^{(l)}} \cdot \frac{\partial x_j^{(l)}}{\partial s_j^{(l)}} = \sum_{k=1}^{d^{(l+1)}} (\delta_k^{(l+1)}) \cdot (w_{jk}^{(l)}) \cdot (f’(s_j^{(l)}))$$</p><p>所以,从输出层的$\delta$开始,我们可以通过层层回溯计算出每一层的梯度值。有了梯度之后,新的权重可以更新如下($\eta$为学习步长):</p><p>$$w_{ij}^{(l)} \leftarrow w_{ij}^{(l)} - \eta x_i^{(l-1)}\delta_j^{(l)}$$</p><p>经过多轮迭代之后,NNet的权重矩阵将会收敛到一个稳定的值附近,此时的权重矩阵就可以作为模型的最终训练结果了。</p><h2 id="优化"><a href="#优化" class="headerlink" title="优化"></a>优化</h2><p>NNet模型的训练存在某些问题,可能导致最终得到的模型的预测效果并不理想。下面浅列一些最常遇到的问题以及应对的办法。</p><h3 id="局部最优问题"><a href="#局部最优问题" class="headerlink" title="局部最优问题"></a>局部最优问题</h3><p>NNet模型的损失函数往往并不能保证是<em>凸函数</em>,因此往往存在多个局部最优解,即使使用梯度下降的方法也很难找到全局最优解。为了缓解这个问题,一般NNet的训练会要求使用若干随机的初始权重矩阵(矩阵中的值都比较小以避免过早梯度饱和),并且训练多个模型进行效果比较。</p><h3 id="过拟合-Overfit-与正则化-Regularization"><a href="#过拟合-Overfit-与正则化-Regularization" class="headerlink" title="过拟合(Overfit)与正则化(Regularization)"></a>过拟合(Overfit)与正则化(Regularization)</h3><p>NNet模型是一个强大的模型,只要神经元数量和层数越多,它对原始数据集的拟合程度就会越高,于是造成过拟合的问题,使得模型不能很好地应用到外部数据集上。为了解决这个问题,我们往往会在损失函数中引入正则项来“压缩”权重,防止过拟合。NNet中一般使用$L_2$正则来保证损失函数可微:</p><p>$$L_2 = \sum(w_{ij}^{(l)})^2$$</p><p>并且在实践中,更多使用基于$L_2$的<strong>权重消除</strong>正则方法使得小权重能够被缩减为0:</p><p>$$\sum \frac{(w_{ij}^{(l)})^2}{1 + (w_{ij}^{(l)})^2}$$</p><p>与此同时,NNet的训练还会采用<strong>提前终止(early stopping)</strong>的策略在适当的迭代次数后停止训练,防止过拟合。</p><h1 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h1><p>NNet模型是一个庞大的门类,本文介绍的前馈网络只是其中一种,还有反馈神经网络等其它类型的神经网络模型存在。但是随着深度学习的普及,前馈神经网络及其衍生模型开始大行其道,DNN在诸多领域中的成功应用也向人们昭示了这一模型的强大力量。这一篇简短的文章和训练推导过程既帮助我自己复习巩固所学,也希望能够帮到更多人了解并进一步掌握NNet模型的理论基础和一些实践技巧。</p>]]></content>
<summary type="html">
<blockquote>
<p>人法地,地法天,天法道,道法自然。 <em>– 《道德经》</em></p>
</blockquote>
</summary>
<category term="machine learning" scheme="http://blog.lyndonli.com/categories/machine-learning/"/>
<category term="AI" scheme="http://blog.lyndonli.com/categories/machine-learning/AI/"/>
<category term="computer science" scheme="http://blog.lyndonli.com/categories/machine-learning/AI/computer-science/"/>
<category term="nnet" scheme="http://blog.lyndonli.com/tags/nnet/"/>
<category term="人工神经网络" scheme="http://blog.lyndonli.com/tags/%E4%BA%BA%E5%B7%A5%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/"/>
</entry>
<entry>
<title>Locality Sensitive Hash浅析</title>
<link href="http://blog.lyndonli.com/2019/06/02/Locality-Sensitive-Hash/"/>
<id>http://blog.lyndonli.com/2019/06/02/Locality-Sensitive-Hash/</id>
<published>2019-06-02T12:38:34.000Z</published>
<updated>2019-06-02T14:28:00.615Z</updated>
<content type="html"><![CDATA[<blockquote><p>大智若愚,大巧若拙。<em>—— 《道德经》</em></p></blockquote><a id="more"></a><script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML" async></script><h1 id="总述"><a href="#总述" class="headerlink" title="总述"></a>总述</h1><p>Locality sensitive hash中文可以译成<strong>位置敏感哈希</strong>,是一种特殊的hash算法,简称LSH。与一般的hash算法类似,LSH也是将一个数值映射成另一个数值,但是LSH有一个位置敏感特性,即多维空间中相似的数值会被映射成相同的结果值或映射到相同的空间,而该空间中不相似的数值则会被映射成不同的结果值或映射到不同的空间。</p><h1 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h1><p>既然需要体现数据在空间中的位置特性,那么LSH所采用的哈希函数就要能够在具有一般哈希函数性质的同时(单向性、分散性)考量两个数据的相似性,并将这种相似性体现在映射结果上。</p><h2 id="相似性度量"><a href="#相似性度量" class="headerlink" title="相似性度量"></a>相似性度量</h2><p>如果把两个数据看成是多维空间中的点,用向量来表征这些点,那么这两个数据的相似性就可以用<strong>多维空间中点的距离</strong>来衡量。典型的衡量多维空间中两个点之间的距离的算法有如下几种:</p><p>Jaccard距离:<br>$$sim(U, V) = \frac{U \cap V}{U \cup V}$$</p><p>余弦距离:<br>$$sim(\mathbf{x}, \mathbf{y}) = \frac{\mathbf{x} \cdot \mathbf{y}}{||\mathbf{x}|| \cdot ||\mathbf{y}||}$$</p><p>闵可夫斯基距离:<br>$$sim(v_1, v_2) = \sqrt[p]{|v_{11} - v_{21}|^p + |v_{12} - v_{22}|^p + … + |v_{1n} - v_{2n}|^p}$$</p><p>特别的,当p=1时,闵可夫斯基距离即为<strong>曼哈顿距离</strong>;当p=2时,闵可夫斯基距离即为<strong>欧几里得距离</strong>;当p趋于无穷大时,闵可夫斯基距离即为<strong>切比雪夫距离</strong>。</p><p>上述相似度计算方法从距离角度来看均满足距离度量函数的普遍特征(相似度需要转换成距离),即<br>$$d(x, y) \geq 0$$<br>$$d(x, y) = 0 \quad 当且仅当x=y时成立$$<br>$$d(x, y) = d(y, x) \quad 对称性$$<br>$$d(x, y) \leq d(x, z) + d(y, z) \quad 三角不等式$$</p><h2 id="位置敏感哈希"><a href="#位置敏感哈希" class="headerlink" title="位置敏感哈希"></a>位置敏感哈希</h2><p>有了距离度量方法之后,就可以设计一个哈希函数,将空间上相邻的点映射到相同的值或空间中去。如何设计这种函数呢?先来看几个重要的数学概念和结论。</p><h3 id="p稳定分布"><a href="#p稳定分布" class="headerlink" title="p稳定分布"></a>p稳定分布</h3><p>$$若\exists p \geq 0, 有n个服从实数集\Bbb{R}上的分布\mathbf{D}的变量X_i(n>0, 1 \leq i \leq n), v \in \Bbb{R}$$<br>$$如果随机变量\sum_{i=1}^n{v_iX_i}与(\sum_{i=1}^n{|v_i|^p)^\frac{1}{p}X}同分布,则\mathbf{D}为一个p稳定分布$$</p><p>因此,针对两个特征向量v1和v2,如果有一个n维的随机向量X,X中的每一维均独立地从相同的p稳定分布中随机产生,那么就有</p><p>$$(\vec{v_1} - \vec{v_2})\vec{a} 与 ||\vec{v_1} - \vec{v_2}||_pX \quad 同分布(\vec{a}是随机向量X中的一个向量)$$</p><p>两个向量同分布意味着二者是线性相关的,因此这里就可以用av来近似表示v的p阶范数,从而能够使用点乘运算av将特征向量v从高维空间映射到实数集空间(一维),实现降维。</p><p>已知并且常用的p稳定分布包括:<br>$$柯西分布(p=1): \quad f(x; x_0, \gamma) = \frac{1}{\pi}[\frac{\gamma}{(x-x_0)^2+\gamma^2}]$$<br>$$高斯分布(p=2): \quad f(x) = \frac{1}{\sqrt{2 \pi}\sigma}exp(-\frac{(x-\mu)^2}{2\sigma^2})$$</p><p>如果我们取p=2,就可以用一个每一维数值均满足高斯分布的向量与特征向量之差的点积来近似模拟特征向量之间在高维空间的欧几里得距离。</p><h3 id="设计哈希函数"><a href="#设计哈希函数" class="headerlink" title="设计哈希函数"></a>设计哈希函数</h3><p>在上述结论的基础上,我们将实数轴做等分处理,每段长为r,并对每一段进行标号,av落到哪个区间,我们就把这个区间号作为映射结果进行返回。于是,LSH可以表示为如下形式:<br>$$h_{\vec{a}, b}(\vec{v}): \Bbb{R^d} \to \Bbb{N}$$<br>$$h_{\vec{a}, b}(\vec{v}) = \lfloor \frac{\vec{a} \cdot \vec{v} + b}{r} \rfloor, \quad b \in [0, r]$$</p><p>上式中a是一个确定的d维向量,每一维数据均从p稳定分布中产生,b为一个随机噪音,r为分段后每段的长度。<br>那么,两个点被映射成同一个哈希值的概率可以借助p稳定分布的概率密度函数来表示成:<br>$$Pr_{\vec{a},b}[h_{\vec{a}, b}(\vec{v_1}) = h_{\vec{a}, b}(\vec{v_2})] = \int_0^r{\frac{1}{c}f_p(\frac{t}{c})(1-\frac{t}{r})}{\rm d}t$$<br>$$其中 \quad c = ||\vec{v_1} - \vec{v_2}||_p, \ f_p(x)为p稳定分布的概率密度函数$$</p><p>由此可见,随着v1与v2之间距离的增大,哈希结果的相似概率随之减小,因此我们说上述函数h是距离敏感的,即h满足如下条件:<br>$$如果d(\vec{v_1}, \vec{v_2}) < d_1, \quad Pr[h(\vec{v_1})=h(\vec{v_2})] \geq p_1$$<br>$$如果d(\vec{v_1}, \vec{v_2}) > d_2, \quad Pr[h(\vec{v_1})=h(\vec{v_2})] \leq p_2$$</p><p>也就是说,当两个点足够相近时,使用上述函数映射得到相同的结果的概率足够大;当两个点足够相异时,使用上述函数映射得到相同结果的概率足够小。</p><h3 id="概率调节"><a href="#概率调节" class="headerlink" title="概率调节"></a>概率调节</h3><p>既然是概率保证,也就意味着仍然有一定的可能使得两个不相似的点被映射成相同的值(伪正例),或者相似的点被哈希成不同的哈希值(伪反例)。</p><p>为解决伪正例问题,可以构建一个<strong>LSH与函数组</strong>,每个函数之间相互独立,计算同一个特征的多个哈希值,只有当这些哈希值均相同时才认为特征相似。</p><p>为解决伪反例问题,可以构建一个<strong>LSH或函数组</strong>,每个函数之间相互独立,计算同一个特征的多个哈希值,该组函数中若有一个返回相同的哈希值,则认为特征相似。</p><p>结合上面两个方案,可以得到一个综合方案,同时满足<strong>与函数组之间的或判断</strong>的才认为特征相似。假设与函数组中有x个LSH函数,或函数组中有y个LSH与函数组,且一组特征的相似概率为p,则该组判断中共有xy个LSH函数,那么对该组特征进行组合哈希后的相似概率为<br>$$1 - (1 - p^x)^y, \quad 0 \leq p \leq 1$$<br>由此可见,当x越大时,相似概率越小;当y越大时,相似概率越大。x与y作为LSH函数组合的超参数,可以根据实际情况进行调节。</p><h3 id="优势"><a href="#优势" class="headerlink" title="优势"></a>优势</h3><p>LSH在进行高维数据之间的相似性检查时具有如下的优势:</p><ul><li>哈希效率高于线性对比</li><li>通过多个独立LSH函数的组合,可以提升查准率</li></ul><p>LSH具有上述的理论优势,但是在实际使用过程中,还需要与合适的索引结构进行配合才能借助计算机的力量实现快速匹配。</p><h1 id="应用"><a href="#应用" class="headerlink" title="应用"></a>应用</h1><p>LSH算法目前主要应用在各种相似性检索问题中,往往用来解决海量数据间的相似性比对问题,具体应用比如相似图片查找、相似文本检索等。</p><h1 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h1><p>LSH作者:<a href="http://people.csail.mit.edu/indyk/" target="_blank" rel="noopener">http://people.csail.mit.edu/indyk/</a></p><p>一种实现:<a href="http://www.mit.edu/~andoni/LSH/" target="_blank" rel="noopener">http://www.mit.edu/~andoni/LSH/</a></p>]]></content>
<summary type="html">
<blockquote>
<p>大智若愚,大巧若拙。<em>—— 《道德经》</em></p>
</blockquote>
</summary>
<category term="algorithm" scheme="http://blog.lyndonli.com/categories/algorithm/"/>
<category term="computer science" scheme="http://blog.lyndonli.com/categories/algorithm/computer-science/"/>
<category term="lsh" scheme="http://blog.lyndonli.com/tags/lsh/"/>
<category term="hash" scheme="http://blog.lyndonli.com/tags/hash/"/>
</entry>
<entry>
<title>登顶富士山</title>
<link href="http://blog.lyndonli.com/2017/07/16/%E7%99%BB%E9%A1%B6%E5%AF%8C%E5%A3%AB%E5%B1%B1/"/>
<id>http://blog.lyndonli.com/2017/07/16/登顶富士山/</id>
<published>2017-07-16T07:55:15.000Z</published>
<updated>2019-08-26T13:29:35.202Z</updated>
<content type="html"><![CDATA[<img src="/2017/07/16/登顶富士山/sunrise.jpg" title="富士山日出"><blockquote><p><strong>会当凌绝顶,一览众山小。</strong></p><p><唐> 杜甫   <em>—— 望岳</em></p></blockquote><br><p>公元2017年7月9日凌晨2点40分,这是一个我此生都不会忘怀的时刻,曾经多少次梦想着攀登富士山,此刻我终于可以大声地宣称:我征服富士山了!疲惫与兴奋相交织,酸楚与快乐相融合,这真的是只有在付出努力与艰辛之后才能体会到的成功的喜悦。距离这一激动人心的时刻已经过去一周,照片也做得差不多了,终于有时间可以回顾一下这段难忘的旅程了。Let’s kick it off!</p><a id="more"></a><h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>中国有句古话叫做“不到长城非好汉”,而在日本,富士山的地位与长城相当。攀登富士山,不说是所有日本人的梦想,至少也被绝大部分日本人列入了他们的人生Todo List。遗憾的是鄙人并没有到过长城,连伟大光荣正确的我党的心脏所在之帝都都没有去过。既然在东京工作,那么为了弥补这一缺憾,在3月份的时候我萌生了攀登富士山的想法(呃,好像并没有什么🐦关系)。世界上貌似并没有一座海拔在3800米左右的山像富士山一样如此地具有象征意义,吸引了不仅是日本本土,更有来自世界各地的游客前来挑战(其实也不难理解,日本被他们崇奉的天照大神安置在了动不动就舒展筋骨的小岛上,好不容易有座像样的山那还不得像祖宗一样供起来?再者,富士山3776米的海拔在让人体会到欲仙欲死的高原缺氧快感的同时,又不会真的让人往生极乐,适合想作死又不想真死的广大犯贱人群,实在是常人居家登山之良选)。日本政府出于安全考虑,每年仅开放7月至9月初这两个多月的时间供游客攀登富士山,并且有四条规划好的路线供游客选择,每一条路线都是纯天然的,不像国内某些名山还给你修个楼梯或者电梯啥的。所以你要是爬不到顶,就只能自己下来,没人驮你,这一点还是比较残酷的。然而就我当时的体能储备而言,要适应高原气候并且完成超过1500米垂直高度的攀爬几乎是不可能的。正好在当时看了一篇关于健康的博文,想到码农这份工作每天要在N块显示屏的辐射前静坐耕耘至少8小时以上,又联想到伟大光荣正确的我党的科学发展观以及被无数人证明了的谁活得更长谁就能笑得更灿烂的历史哲理,秉持着我真的还想再活五百年的崇高目标,我开始跑步以储备体能。在组建登山国际纵队以及具体规划行程之前,我已经能够每周跑4~5次,其中有一次半马或超半马,周跑量维持在40公里以上。6月份,在老板的建议下我攀登了海拔1000米不到的御岳山作为练兵。自此,对于登顶富士山,我开始有了点底气。(BTW,御岳山底也是很漂亮的,至于这座山本身嘛,实在没什么意思,附图为证)</p><img src="/2017/07/16/登顶富士山/mitake.jpg" title="御岳山溪涧"><h2 id="计划"><a href="#计划" class="headerlink" title="计划"></a>计划</h2><p>6月初,制定登山计划正式纳入日程表。众所周知,对于一个小的项目,从前到后由一个全栈工程师全程handle并没有什么问题,但是对于攀登富士山这样一个庞大的系统工程而言,还是找几个伴比较靠谱,万一挂在山上,也好有人把你的骨灰撒向人间。正巧在一次闲聊中,坐我旁边的西班牙斗牛士(不要误会,他只是西班牙人,并不喜欢斗牛,人家很善良地边吃牛排边说斗牛结束要刺杀牛太残忍)透露了他想去爬富士山的想法,表示想今年先探路,明年带老婆一块去。Bingo!那就一起呗!于是斗牛士入伙了。然后就是我的另一位国际友人,户外运动能手,来自浪漫之都的巴黎小王子,在接收到offer之后,光速accept。最后一个入伙的是小王子的好基友登山控,顾名思义,就是这辈子不知道爬了几座山的老油条,据他自己称最高爬过5200米的山。第一次见登山控是在日本的浴衣节,你能想象一个法国人穿着日本的浴衣赤着脚在办公室里面踱步的场景吗?真是因缺思厅。但毕竟姜还是老的辣,在我们规划行程以及装备的时候,登山控提出了许多建设性的意见,与会同志都给予了充分的肯定,他的真知灼见也打消了我是否需要准备高原反应药物以及氧气瓶的疑虑:完全不用担心,总有人会替我收尸。真是一个乐于助人的活雷锋。</p><p>在组建团队的同时,路线规划也在紧锣密鼓地进行着。如前所述,攀登富士山可以选择官方给出的四条路线(你也可以别出心裁,另辟蹊径,做第一个吃癞蛤蟆的人),这四条路中以富士吉田口路线为最易,当然攀登人数也最多,据统计每年来爬富士山的有超过一半的人选择这条路。对于身体倍儿棒,吃嘛嘛香的我们而言,不走寻常路,挑战极限一直是我们的座右铭,而且我们一致同意要避开糟心的人群。于是在经过了一番详细的研究之后,我们果然不负众望地选择了最易的吉田路线。要说人日本的服务真的是没话说,除了官方网站的信息以外,日本环境省还拍摄了攀登富士山的指导视频,居然还有中文配音版,那傻B到家的中文配音真是听得人如痴如醉,满满的译制片的味道,至今那一句充满了真挚感情的“为了把这座举世无双的富士山留给后世,让我们遵规守纪,共同保护吧”真是余音绕梁,让人三月不知肉味。视频中推荐了三种登山方式,有傍晚开始爬,山上过夜,第二天清早登顶的常规套餐,也有清早开始爬,中途无休,当日登顶下山的无聊套餐,还有夜里出发,中途无休,凌晨登顶,早晨下山的最高难度子弹登山套餐。官方明确声明了子弹登山是禁止的。作为一个遵纪守法的国际登山团队,我们当然毫不犹豫地选择了子弹登山。你要问为什么,因为我们要看日出啊!因为我们没钱租山顶小屋过夜啊!你以为一个外国人在日本生活很逍遥吗?你以为码农的工资很高嘛?这边东西都很贵的好嘛!水果都是按个卖的好嘛!还要交税的好嘛!工作满一年还要再加坑爹的住民税好嘛!版块活动频繁还诱使你花额外的钱买地震保险好嘛!嗯哼,失态了失态了,还是要强调日本是一个服务最体贴最周到且最适宜养老的国度。总之,我们最后作出了使用子弹登山法避开难民流,经由吉田路线登顶富士山观看日出的英明决定。</p><p>最后是登山装备。小王子早就心有所属地推荐了Yamarent(翻译成中文就是山租。注意,这不是玩笑,也不是广告)。这家店提供全套登山装备的租赁服务,在富士山下设有分店,在新宿的总店还有巨漂亮的服务员MM竭诚为您服务。我和小王子提前去了总店预约租赁(真是小的可以,一种蜗居的感觉),我要了头灯、登山服、登山小棉袄、登山裤、登山袜、登山鞋、登山杖,小王子只比我少了一对登山杖。我查看了开销,居然差了2500日元,而我自己买一对登山杖的开销也就2500日元上下,日!这就是贼精的日本商人。Anyway,租这家店的好处就是等到登山结束,我们可以在富士山的分店直接归还装备,就不用再拖着疲惫的身躯回这糟心的小蜗居了。</p><h2 id="启程"><a href="#启程" class="headerlink" title="启程"></a>启程</h2><p>公元2017年7月8日,天气晴,目的地天气状况A+,无云,极其适合登山和看日出。下午2点,我备齐了装备,从家里整装出发,去赴那四点半的约。虽说登山要轻装简行,但是担负团队摄影重任的我不得不带上我的两部感动常在以及三脚架,再加上必不可少的食物和2升液体,这直接导致我的背包重达10kg。更加悲剧的是这个重量基本全程不会减轻,因为水和食物只会从包里转移到胃里,而我不会在山上上厕所,不是因为我超凡脱俗,早已摆脱了三急的烦恼,实在是因为那些厕所收费(根据海拔不同,收费从100~200日元不等。其实这也可以理解,海拔这么高,建设和维护下水道的成本也相应增加,稍微收点钱也在情理之中),而我则付不起厕所费。我们包了,哦不,是订了大巴座位,于5点从新宿出发前往富士吉田口。整辆车除了司机全是外国人,法国人居多,富有的日本土著们都选择自驾出行,这再次证明了外国人在日本生活的艰辛。一路上全车人都有说有笑,我向斗牛士介绍了中国小学的小红花制度,双方并就中国的红领巾和共青团问题热情地交换了意见。中原实在是有太多有趣的事情可以和夷狄们分享了,再一次感谢伟大光荣正确的我党。</p><p>说着说着,7点左右我们就抵达了富士吉田口,全程耗时2小时,比预定时间提前了1小时。这个时间从起点出发的人果然不多,更多的是返程的人群,就是那些选择了常规或者无聊登山套餐的人群。</p><img src="/2017/07/16/登顶富士山/start.jpg" title="五合目"><p>据官方介绍,吉田登山路线需要耗时4~7小时方能登顶,这意味着如果我们此刻出发,最早将在午夜11点,最迟将在第二天凌晨2点抵达山顶,而日出时间则是早上4点37分。初中科学告诉我们,海拔每升高1000米,气温就会下降6摄氏度。当时的起点气温是16摄氏度,海拔2300米左右,据此推算,山顶气温应该在6摄氏度左右,再加上山风拂面,体感温度应该在零摄氏度左右。上帝赐予我们的智慧以及求生的本能告诉我们人类不适宜在这种温度下穿着薄薄的小棉袄长时间干坐。我们要想办法缩短在山顶等待的时间,同时又要能够及时抢占有利地形方便拍摄日出。这是一个带有约束条件的最优化问题,四颗聪明的大脑经过碰撞产生的智慧的火花决定放缓登山的脚步,多在半山休息,看人流量决定是否加速。至于具体会消耗多少时间,登山控摆出了老江湖的架势,淡淡地表示一切都在掌控之中。</p><p>开始登山前,我们当然需要换上登山装备。为保持私密,小王子和斗牛士决定去厕所更换装备,我也只能从了他们,然而从这里开始厕所居然就要收费了,100日元一次,但是用完后可以免费拿一张明信片。好了,这并不是什么重点,有意思的是厕所里马桶的冲水方式。我想着既然都花钱进来了,就要充分利用这次机会,把能排的都排干净。于是我解了个手,马桶里没有水,只有一个深不见底的无底洞,完事后我按下了冲水键。顿时马达轰鸣,鄙人没见过这阵仗,吓了一跳,以为启动了什么了不起的机关,而这马桶也半天不见出水。稍稍过了一会再低头一看,发现马桶内部已经被泡沫覆盖,而这些泡沫随后也滑入了无底洞。原来这并不是传统的冲水马桶,而是奇特的泡沫马桶,可能因为冲一次水成本太高的缘故吧,能省就省了。</p><p>用完了高原马桶,我们找到了一个locker来寄放换下的衣物。这里的locker天杀的要600日元的保管费,而且只接受100日元硬币。倒霉的是在locker上锁之后我突然发现登山杖没有拿出来。当时我想就这么直接上山算了,但是小王子劝我还是要冷静,相比2500日元的租赁费,这600日元真的不算什么。真是一语点醒梦中人啊!我还是又花了600日元把登山杖拿了出来。事实证明,这是一个无价之举,因为假如没有登山杖,我可能在下山的时候就去见列祖列宗了。另外据说白天还可以在这里买登山的金刚杖(其实就是一根木头棒子,日本人特喜欢取一些中二的名字,但往往雷声大雨点小,这一点从他们给战国时候的那些个武将起的绰号就可以看出来),然后在山上的某处可以再花钱烙印。鉴于我们已经有了登山杖以及没有钱这一残酷的事实,这金刚伏魔杖就不在考虑之列了。换完装备,我们在入口前的广场上吃了点东西,拍了合照以防我们之中有人再也看不到明天的太阳(富士山是一座活火山,虽然几百年没有喷发了,但是保不齐它今晚就想打个哈欠,哦,居然忘记买保险了)。万事俱备,开锚起航!</p><img src="/2017/07/16/登顶富士山/entrance.jpg" title="入口合照"><h2 id="五合目-本八合目"><a href="#五合目-本八合目" class="headerlink" title="五合目~本八合目"></a>五合目~本八合目</h2><p>吉田路线的入口所在就是五合目,从这里开始直到山顶,一路上还会遇到六、七、八、本八、九合目,这些是供游客驻足休息的平台。</p><img src="/2017/07/16/登顶富士山/five.jpg" title="入口"><p>夜幕降临得非常迅速,换装备时还是阳光普照,现在已是漆黑一片。打开头灯,这种时候玩点鬼影重重倒是别有风情。</p><img src="/2017/07/16/登顶富士山/light.jpg" title="鬼影重重"><p>从入口到泉瀑布(然而并没有见到什么瀑布,连水声都没听到)地势平坦,根本不算登山,一路上植被葱郁,也能看到稀稀拉拉不怕死的壮士们跟我们一样玩子弹登山。从泉瀑布开始,稍微有了点坡度,登山才算真正开始,但是鉴于鄙人跑得比较多,也爬了点小山热身,所以这种坡度也就跟玩儿一样了。路上见到了收赞助金的地方,理论上来说每个爬富士山的人都要支付1000日元作为富士山保养的赞助费,可以理解为门票钱,但由于我们是官方不允许的子弹登山,收费站早已关门大吉,因此省下了这1000日元的买路钱,想想就有点小兴奋呢。可笑的是官方还在门口放了不得子弹登山的警示牌,but who cares?</p><img src="/2017/07/16/登顶富士山/bullet.jpg" title="禁止子弹登山"><p>起初我脖子上还挂着重得要死的无敌狮随手乱拍,但是随着海拔增高以及坡度的增加,我渐渐感到了吃力,也开始意识到胸前挂着一个到处晃荡的金贵玩意儿不便于攀爬,于是在七合目把相机收进了背包。放下包后我感到身体一阵燥热,之前的攀爬产生了大量的热量,由于我最外层穿了登山的防风服,汗水难以蒸发,导致我一停下来就想脱外套散热。在脱掉外套的一刹那,10摄氏度的寒风吹过汗水已经湿透的衣服,那个酸爽!衣服瞬间冰凉,一拧还能出冰水。而斗牛士的裤子由于透水性太好,已经完全湿透。倒是登山控悠然自得,喝着大瓶的饮料,彰显着老江湖的优越感。</p><img src="/2017/07/16/登顶富士山/light2.jpg" title="调皮的登山控"><p>然而登山控已经不满足于现在的进度,提出了登山时团队成员可以不同时行动的分头行动理论。为了检验这一理论的正确性,从七合目开始,我们团队开始分为两个梯队,小王子和登山控由于户外经验丰富,脚步轻灵,所以他们先行一步,我和斗牛士还是准备稳扎稳打,步步为营。分道扬镳之后,我们相约在本八合目碰头。</p><p>七合目往上海拔已经超过2700米,植被开始变得稀疏,大片的绿色逐渐消失,只能看到散落各处的几撮小草在寒风中摇曳。同时路面开始变为更加艰难的熔岩岩石,坡度陡增,倾斜超过30度。该路段没有明显的道路标记,只有绳索和锁链告诉你哪里是可以攀爬的路段。当时图方便,我一路揪着链条往上爬,但是后来了解到这是极其危险的,天知道这些绳索和链条到底牢不牢固。这么想来我能活着下山已经是上天的恩赐了,悠悠苍天,果不负我啊。但是小伙伴们还是应该脚踏实地攀爬,遇到需要手脚并用的地方也不要吝惜自己的双手。往上爬的时候我不建议使用登山杖,这玩意儿太过碍事,斗牛士先用了一阵,但是后来还是收了起来,徒手攀爬。夜里黑灯瞎火的,加上爬山颇费体力,也就没什么心思拍照片了,一心只想着到下一站休息。我们在八合目休息的时候,收到了小王子从本八合目发来的贺电。于是我们也没做长时间的停留,戴上手套,马上奔赴本八合目与第一梯队汇合。</p><h2 id="本八合目-登顶"><a href="#本八合目-登顶" class="headerlink" title="本八合目~登顶"></a>本八合目~登顶</h2><p>本八合目海拔3400米,是真正的高原地段,植被已经彻底不见踪影,到处是红褐色的火山岩,加上气温寒冷,可以看到雪层覆盖在岩石表面。由于这里是吉田路线和须走路线的融汇点,因此休息的人特别多。这里有最后一间山屋御来光馆供游客休息(当然是要收费的,而且床位早就已经被预订一空了,现在只能花钱进屋取个暖),由于它是距山顶最近的山屋,因此价格也颇为不菲。在山屋外可以看到有人用自带的小煤气炉煮泡面充饥,有人拿出氧气瓶像吸毒一样补充氧气,还有些没能订到床位的人已经躲在睡袋里面睡觉了,也不知冻不冻得慌。挂在屋外的气温计显示此地已是6摄氏度的低温,此时此刻任谁都想进一个温暖的地方烤火吃东西,但是我谨记孔圣人的教诲,相信这是上天要把重任交与我前赐予我的折磨,坚定了在屋外与寒风抗战到底的决心。</p><img src="/2017/07/16/登顶富士山/hut.jpg" title="御来光馆"><p>由于这是最后一个大型的休息站点,根据路牌显示,再有80分钟我们就可以登顶,而此时仅仅是零点,与其吹更冷的360度环绕立体妖风,不如在这6摄氏度的“暖风”中多休息一会。期间结识了一位法国来的小猎豹,手持一部感动常在APS-C,行动迅捷,连登山控都自叹弗如。休息结束后我们一同出发,但为了不影响人家的进度,我们很快就和小猎豹说再见了。根据计划,第一梯队负有抢占有利地形的重任,由于两条路线合流,路面变窄,冲顶的人变多,为了早日完成这一任务,小王子和登山控再次先行一步,与我们相约在顶峰。</p><p>尽管在海平面我已经训练长跑若干月了,但事实证明这点训练量所提升的心肺能力在3000多米的高海拔地区带来的助益仍然十分有限。从本八合目到九合目,虽然海拔高度只差了200米,但是由于空气极度新鲜,新鲜到氧气极度稀薄,每踏出一步都极耗费体能,可谓是举步维艰。并且由于通宵攀爬的缘故,我开始出现轻微的头痛,因此每升高数十米我就不得不停下来恢复一下,同时等待正在后面步步为营的斗牛士与我汇合。等到了海拔3600米的九合目鸟居,我已直接趴坐在门口。斗牛士开始后悔入伙来爬富士山,并且坚决表示明年也不会带老婆再来爬一次了(人毕竟是多变的)。此时第一梯队已经抵达山顶并且抢占了面东的有利地形。受友军胜利的鼓舞,我也顾不上那些嵌在鸟居牌坊柱子上的许多100日元钢镚了(拿下来还能上厕所用呢),提提沉重的屁股,怀着我跑半马跟玩似的我怕谁的大无畏精神,与斗牛士继续完成这最后的100米极限攀登。</p><p>And then, we made it, quietly at 2:40 AM. 富士山已被我征服(虽然它被很多人征服过,听着也蛮惨的,但是说出这句话来还是有一种自豪的感觉,谁让我这辈子没到过这么高的地方呢)。</p><h2 id="日出"><a href="#日出" class="headerlink" title="日出"></a>日出</h2><p>所谓“无限风光在险峰”,但是我们所在的白山岳真的没什么好看的,真的是“白”山岳,全是光秃秃的石头,要不是有美丽的日出,真的白费这么大力气了。与第一梯队汇合后距离日出还有差不多1个小时的时间,此刻的我只想倒头就睡,怎奈寒风凛冽,衣着单薄,没有火炉,羞于抱团,只能边发抖边小憩。小王子和登山控则在一边精神饱满地边发抖边聊天,斗牛士则掏出了早就备好的锡箔纸裹住下半身开始睡觉。</p><img src="/2017/07/16/登顶富士山/lie.jpg" title="沉睡中的斗牛士"><p>就这样抖了半个小时,4:15 东方逐渐发白,我起身开始架设三脚架和器材,拍一些日出前的夜景暖手。根据著名的李氏“气温-智商”影响链定理,即:</p><p>气温下降->生物酶活性降低->大脑活力下降->智商崩盘</p><p>该恶劣环境最终导致鄙人忘记了使用长曝的技术拍摄夜景,转而使用高ISO这种愚蠢的手法,使得最后的成片夹杂着大量噪点。虽然做了降噪处理,但是质量仍然不敢恭维,只够凑合着看看。</p><img src="/2017/07/16/登顶富士山/hdr.jpg" title="日出前"><p>调试的时间转瞬即逝,突然间一个小红点开始露头。没错,日出开始了!</p><img src="/2017/07/16/登顶富士山/sunrise-before.jpg" title="日出时"><p>整个日出过程维持了几分钟,身边充斥着相机快门的咔嚓声。我们也掏出了之前准备的日本酒开始小酌。</p><img src="/2017/07/16/登顶富士山/wine.jpg" title="干杯"><p>当然也不免拿出吉祥物沾沾喜气。</p><img src="/2017/07/16/登顶富士山/with-sunrise.jpg" title="吉祥物与日出"><p>这是一个晴朗的日子,万里无云,透过雾气我们可以看到下方的河口湖。一个月前我还在河口湖遥望富士山,现在却已经站在富士山顶鸟瞰河口湖了,真有一种站在风口浪尖,紧握日月旋转的成就感,一种再活五百年的豪气油然而生。</p><img src="/2017/07/16/登顶富士山/sunrise-after.jpg" title="日出后"><p>不自觉地,我迎向日光,仿佛在倾听着赫利俄斯的玉音。</p><p>Hold infinity in the palm of your hand,<br>And eternity in an hour.</p><p>顿时,我突然感受到了我所肩负的国家存续,民族兴亡,中华复兴的伟大使命,宋儒张载“为天地立心,为生民立命,为往圣继绝学,为万世开太平”的宏图大志分分钟涌上心头,我开始思考人生,思考我们存在的意义,思考我们的未来(不要误会,只是摆个傻B姿势拍照而已,我只想着这坡这么陡,待会儿怎么下山)。</p><img src="/2017/07/16/登顶富士山/gaze.jpg" title="思考人生"><p>好了,发完情之后太阳出来也有一会儿了,气温开始回升,可以摘掉手套脱掉小棉袄了。我很庆幸在起点的合照之后,我们仍然能够在一起看那日出东方。那么,为什么不再合影留念呢?</p><img src="/2017/07/16/登顶富士山/success.jpg" title="山顶合照"><p>这将是一张我会珍藏一生的照片。</p><h2 id="无尽的zigzag"><a href="#无尽的zigzag" class="headerlink" title="无尽的zigzag"></a>无尽的zigzag</h2><p>5:30 在有钱人上完厕所后我们开始下山。我们预定了11点的返程大巴,要在10点半前赶到车站,而官方给出的下山时间是3<del>4小时,我们有1</del>2小时的机动时间,因此理论上来讲时间还是比较宽裕的。然而我们低估了人群的力量。</p><img src="/2017/07/16/登顶富士山/people.jpg" title="全是人"><p>返回到山顶入口一看,全是人。不仅是下山的人,还有不断上山的人。于是本来就很狭窄的登山道硬是被分成了更狭窄的左右两边,大家伙排队上下山。有鉴于此,我们没敢冒险再花时间去火山口兜圈子,而是直接选择下山。这时候2500日元租来的登山杖开始大显神威。由于坡度陡,段差大,没有登山杖的话膝盖要承受比较大的压力(比如小王子,但是人家是铁打的膝盖),而此时我已经很疲惫了,膝盖的肌肉也处在罢工的边缘,如果没有登山杖的辅助,我觉得我会一路滚下去(倒是省力了)。人这么多,闲着也是闲着,正好可以拍点夜里没拍的山景。</p><img src="/2017/07/16/登顶富士山/bleak.jpg" title="荒凉山景"><p>其实也蛮荒凉的,真的是寸草不生,只有一群群像是中了邪的登山者。渐渐地,道路渐宽,我们抵达了下山和上山道路的分叉点。从这里开始,就是无穷无尽的之字形下山道路,全是火山岩,坡度超过30度,我细数了一下,有将近40个拐口,真是山路四十弯。下行的时候尘土飞扬,想戴口罩吧,又有碍氧气的摄入,想一路滑行吧,没那么好的膝盖,于是只能靠着登山杖一步步往下走,期间有一次鞋带散了,右脚绊在了左脚上,直接一个屁股向后平沙落雁式,Duang一声坐在了火山岩上,这酸爽,像做股底按摩一样。第一梯队使用纯熟的滑行技术飞速下山了,我和斗牛士则像蜗牛一样一步步地往下爬。</p><img src="/2017/07/16/登顶富士山/zigzag.jpg" title="无尽的zigzag"><p>随着海拔的降低,植被开始出现,氧气开始充足,心肺的压力开始变小,但是腿实在是受不🐦了。由于下行的时候要刹住下冲的劲儿,左脚的大脚趾承受了太大的压力,出现了淤青,但是为了早日抵达入口,也只能敲碎了牙往肚里咽。我真的怀疑上山的时候我们是否走了这么多路,为什么这下山的路是这么的漫长,这么的折磨人。“上山容易下山难”,果然不是盖的。</p><p>结束了之字路,又是连续的超陡直线下坡路,不过有楼梯可供使用,算是一点小慰藉。这之后就是问候祖宗路段了。你已经是下山道了,为什么还要再来一个上坡路?上坡之后又是下坡路,脑子落在剑锋了吗?骂着骂着就回到了那个收赞助金的地方,看着新上山的人们在那里交着钱,心里稍微平复了一点。正欣慰着,不远处走来一匹高头大马,上面载着一个懒汉。这就是传说中的帮你从五合目入口送到登山入口的土豪弼马温服务,返程的人也可以骑这种马从这里返回五合目,只是我们上山的时候是夜里,马夫们都下班了,所以没见着。“真是懦夫!”我想着,“要不是因为我穷……哼!”因为有了这种马,路上也就出现了马粪,占地面积不小,下山时候要小心,不要把脚或者登山杖插到这里面去做搅屎棍。</p><p>此时第一梯队已经抵达入口,但是locker的钥匙在我这里啊,没法换衣服吧,嘿嘿。天气开始变得一般,云雾开始汇集,周围开始呈现出一种仙境般的感觉,只可惜实在没力气拍照片了。最后一段路,我卯足了劲,撇下了斗牛士,终于在9点半左右和第一梯队在入口处会师了。</p><p>终于,活着下来了!</p><h2 id="返程"><a href="#返程" class="headerlink" title="返程"></a>返程</h2><p>换完衣服,归还了登山装备,吃了简单的早饭,把行李扔进大巴的行李库,我们终于坐上了返程大巴。此刻的我如释重负,疲惫感迅速袭上全身,来不及思考国计民生,倒头就睡,这沙发椅真的是比火山岩舒服啊。与来时截然相反,全程全车除了司机报站以外没有人说话,所有人都闭着眼睛,安静得像太平间。2个小时后,这辆灵车抵达目的地新宿,全车迅速像诈了尸一样活泛起来。下了车,互致珍重之后我们就各回各家,各找各妈了,就像所有的传奇故事都有一个平淡的结局一样,属于我们国际登山纵队的传奇故事也就此告一段落,但就像小王子说的,</p><p>“We’re ready for the next challenge”,</p><p>不管还有没有下次,不管下次这个团队里还会有谁,我们都时刻准备着。</p><h2 id="最后说几句话"><a href="#最后说几句话" class="headerlink" title="最后说几句话"></a>最后说几句话</h2><p>此次富士山之行全程开销包括装备租赁费10500日元以及来回车票5400日元,再加伙食、locker和厕所开销,总计在20000日元以内,还是可以接受的。但是如果你要租山屋或者骑马,那就肯定破2万了,土豪们各自斟酌。</p><p>我这一辈子通宵的次数屈指可数,除了本科毕业时的通宵KTV、硕士毕业答辩前的通宵准备之外,这是又一次,然而这却是最刻骨铭心的一次。尽管下山之后筋疲力尽,花了整整一周才逐渐恢复元气,但是我却爱上了这种健康的感觉,这种活力四射的状态,这种身心俱疲时的充实感,这种付出艰辛达成目标后的成就感,就像海洛因一样让人上瘾不可自拔,这富士山之行也彻底改变了我的人生轨迹(因为原来的人生轨迹上没有富士山这一点)。不管将来我生活在世界的哪一个地方,我都会永远记得曾经在那一天,那一刻,我在日本的富士山之巅,和我的朋友们一起共同沐浴着东方的华光。</p><p>如果有人问,明年还会去爬富士山吗?呵呵,年轻人,明年我会在哪呢?好了,要去写代码挣钱了,不要来打扰我。</p>]]></content>
<summary type="html">
<img src="/2017/07/16/登顶富士山/sunrise.jpg" title="富士山日出">
<blockquote>
<p><strong>会当凌绝顶,一览众山小。</strong></p>
<p>&lt;唐&gt; 杜甫 &emsp;&emsp;<em>—— 望岳</em></p>
</blockquote>
<br>
<p>公元2017年7月9日凌晨2点40分,这是一个我此生都不会忘怀的时刻,曾经多少次梦想着攀登富士山,此刻我终于可以大声地宣称:我征服富士山了!疲惫与兴奋相交织,酸楚与快乐相融合,这真的是只有在付出努力与艰辛之后才能体会到的成功的喜悦。距离这一激动人心的时刻已经过去一周,照片也做得差不多了,终于有时间可以回顾一下这段难忘的旅程了。Let’s kick it off!</p>
</summary>
<category term="Diary" scheme="http://blog.lyndonli.com/categories/Diary/"/>
<category term="Mt. Fuji" scheme="http://blog.lyndonli.com/categories/Diary/Mt-Fuji/"/>
<category term="Japan" scheme="http://blog.lyndonli.com/categories/Diary/Mt-Fuji/Japan/"/>
<category term="富士山" scheme="http://blog.lyndonli.com/tags/%E5%AF%8C%E5%A3%AB%E5%B1%B1/"/>
<category term="日本" scheme="http://blog.lyndonli.com/tags/%E6%97%A5%E6%9C%AC/"/>
<category term="回忆" scheme="http://blog.lyndonli.com/tags/%E5%9B%9E%E5%BF%86/"/>
</entry>
<entry>
<title>Hot-Swapping Java Class File During Runtime</title>
<link href="http://blog.lyndonli.com/2016/07/18/Hot-Swapping-Java-Class-File-During-Runtime/"/>
<id>http://blog.lyndonli.com/2016/07/18/Hot-Swapping-Java-Class-File-During-Runtime/</id>
<published>2016-07-18T13:59:30.000Z</published>
<updated>2016-07-18T14:07:34.000Z</updated>
<content type="html"><![CDATA[<p>Hot-swapping class file is a popular technique in Java to provide class file replacement during runtime. This is a handy feature for developers that makes it possible to modify class files and test their functions on the fly without recompiling the whole project. And it also serves as a base function when implementing automatic system upgrade. This passage mainly focuses on the core technique required to acheive class file hot-swapping, the <strong>Class Loaders</strong> in Java, and a naive implementation of class file hot-swapping.</p><a id="more"></a><h2 id="Java-Class-Loader"><a href="#Java-Class-Loader" class="headerlink" title="Java Class Loader"></a>Java Class Loader</h2><p>Before we get into the implementation of class file hot-swapping, it’s necessary to have an understanding of class loader technique in Java as it’s the solid pillar underneath the grand hall.</p><p>As you may know, Java has a hierarchical class loader architecture, which can be depicted as below.</p><p>![Java Class Loader Hierarchy](<a href="http://g.gravizo.com/g" target="_blank" rel="noopener">http://g.gravizo.com/g</a>? digraph G {<br> BootstrapClassLoader [shape=box]<br> ExtClassLoader [shape=box]<br> AppClassLoader [shape=box]<br> CustomClassLoader [shape=box]<br> BootstrapClassLoader -> ExtClassLoader<br> ExtClassLoader -> AppClassLoader<br> AppClassLoader -> CustomClassLoader<br> }<br>)</p><p>From top level to the bottom, they’re namely <strong>BootstrapClassLoader</strong>, <strong>ExtClassLoader</strong>, <strong>AppClassLoader</strong> and <strong>CustomClassLoader</strong>. They’re responsbile for different kinds of class loading tasks. Below demonstrates a general picture.</p><p><strong>BootstrapClassLoader:</strong> The toppest level class loader, which reads all class files under <em>sun.boot.class.path</em>. This path defaultly includes all core Java APIs under <em>jre/lib</em> directory and all jar files under the directories that are specified using <em>-Xbootclasspath</em> parameter when the program is loaded into JVM. If lower level class loaders can’t find a specified class file, this one would be the last resort by default.</p><p><strong>ExtClassLoader:</strong> As we know that Java has a variety of extension libraries provided in JRE under <em>jre/lib/ext</em> directory. So in addition to core APIs, JVM will search for this path to load extension jar or class files by default. This task is handled by <strong>ExtClassLoader</strong>, which will look for class files defined in <em>java.ext.dirs</em>. Also you can provide your own paths that containing your extension class files by specifing the value of <em>-Djava.ext.dirs</em> to overwrite the default one.</p><p><strong>AppClassLoader:</strong> This class loader takes charge in all class files defined under <em>java.class.path</em>. The default value is defined in <strong>CLASSPATH</strong> environment variable. And you can modify it either by changing this variable or specify your own with <em>-classpath</em>.</p><p><strong>CustomClassLoader:</strong> Althogh it’s the bottom level class loader, it’s the most powerful one as you can define your own class loading mechanism here to do whatever you want to do.</p><p>In order to load a class, JVM will firstly search the class loader hierarchy from bottom to the top to see if a class has been loaded or not. If it has been loaded, then returns the reference to this class. If all class loaders haven’t loaded this class, JVM will try to load this class from top to bottom. If this fails after trying with all class loaders, an exception will be thrown. Since each class loader has its own namespace, classes with same name can only be loaded once in a class loader.</p><p>If we don’t provide our own class loader, our code will be loaded using <strong>AppClassLoader</strong> by default, which means a class file can only be loaded once despite of any changes on its content. To overcome this, we need to implement our own class loader so that different versions of a same class file can be loaded dynamically.</p><h2 id="Customized-Class-Loader"><a href="#Customized-Class-Loader" class="headerlink" title="Customized Class Loader"></a>Customized Class Loader</h2><p>Althogh customized class loader can be powerful, we still need to obey its rules. Since a class can only be loaded by a class loader only once, in order to load a same class file with different version, we need to create a different class loader. As a result, the default system class loaders can’t be involved in our implementation. And because we want to JVM know that our class loader is a <strong>class loader</strong> so that it can be used by the JVM to read class files, we also need to extend our class from an abstract class <strong>ClassLoader</strong> which is provided by Java.</p><p>Below is our implementation of customized class loader.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HotSwapClassLoader</span> <span class="keyword">extends</span> <span class="title">ClassLoader</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String baseDir;</span><br><span class="line"> <span class="keyword">private</span> Set<String> classNames;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">HotSwapClassLoader</span><span class="params">(String baseDir, String[] classNames)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">super</span>(<span class="keyword">null</span>); <span class="comment">// Set parent class loader to null since we don't want system class loader to load our class</span></span><br><span class="line"> <span class="keyword">this</span>.baseDir = baseDir;</span><br><span class="line"> <span class="keyword">this</span>.classNames = <span class="keyword">new</span> HashSet<>();</span><br><span class="line"> loadClass(classNames);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Entry method for loading a class. All explicit class loading is implemented by this method.</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Class <span class="title">loadClass</span><span class="params">(String name, <span class="keyword">boolean</span> resolve)</span> <span class="keyword">throws</span> ClassNotFoundException </span>{</span><br><span class="line"> Class clazz = findLoadedClass(name); <span class="comment">// To find if this class has been loaded or not</span></span><br><span class="line"> <span class="keyword">if</span>(!<span class="keyword">this</span>.classNames.contains(name) && <span class="keyword">null</span> == clazz) {</span><br><span class="line"> clazz = getSystemClassLoader().loadClass(name); <span class="comment">// Delegate class files we're not intrested in to system class loaders</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">null</span> == clazz) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ClassNotFoundException(name);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span>(resolve) {</span><br><span class="line"> resolveClass(clazz); <span class="comment">// Link a class file to make sure it's usable</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">loadClass</span><span class="params">(String[] classNames)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">for</span>(String className : classNames) {</span><br><span class="line"> loadDirectly(className);</span><br><span class="line"> <span class="keyword">this</span>.classNames.add(className);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">loadDirectly</span><span class="params">(String name)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> StringBuffer sb = <span class="keyword">new</span> StringBuffer(<span class="keyword">this</span>.baseDir);</span><br><span class="line"> String className = name.replace(<span class="string">'.'</span>, File.separatorChar) + <span class="string">".class"</span>;</span><br><span class="line"> sb.append(File.separator + className);</span><br><span class="line"> File classFile = <span class="keyword">new</span> File(sb.toString());</span><br><span class="line"> <span class="keyword">byte</span>[] raw = <span class="keyword">new</span> <span class="keyword">byte</span>[(<span class="keyword">int</span>)classFile.length()];</span><br><span class="line"> InputStream is = <span class="keyword">new</span> FileInputStream(classFile);</span><br><span class="line"> is.read(raw);</span><br><span class="line"> is.close();</span><br><span class="line"> defineClass(name, raw, <span class="number">0</span>, raw.length); <span class="comment">// Accept a byte array of class data and transform it to a class</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>The general idea of this implementation is that we load all requried class files during the instantiation period of our class loader and delegate others to system class loaders. With this in hand, now we’re able to acheive our goal: <strong>class file hot-swapping</strong>.</p><h2 id="Class-File-Hot-Swapping"><a href="#Class-File-Hot-Swapping" class="headerlink" title="Class File Hot-Swapping"></a>Class File Hot-Swapping</h2><p>In order to make an experiment, we create a simple class file first which will be replaced by newer version later.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Foo</span> </span>{ </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sayHello</span><span class="params">()</span> </span>{ </span><br><span class="line"> System.out.println(<span class="string">"Hello world! V1.0"</span>); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>We compile this java file first and put it under the <em>resources</em> directory of our project. Then we create a new thread to load that class and invoke its <em>sayHello</em> method every two seconds.</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> HotSwapClassLoader loader = <span class="keyword">new</span> HotSwapClassLoader(<span class="string">"hotswap/src/main/resources/"</span>, <span class="keyword">new</span> String[] {<span class="string">"Foo"</span>});</span><br><span class="line"> Class clazz = loader.loadClass(<span class="string">"Foo"</span>);</span><br><span class="line"> Object foo = clazz.newInstance();</span><br><span class="line"> Method method = foo.getClass().getMethod(<span class="string">"sayHello"</span>);</span><br><span class="line"> method.invoke(foo);</span><br><span class="line"> Thread.sleep(<span class="number">2000</span>);</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Note that each time we load this class there’s a new instance of class loader generated first. Because, as is mentioned before, class files with the same qualified name can only be loaded into a class loader once.</p><p>Now we run the program and change the version in <em>Foo</em> to <em>V2.0</em>, you will see an output similar to below.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Hello world! V1.0</span><br><span class="line">Hello world! V1.0</span><br><span class="line">Hello world! V1.0</span><br><span class="line">Hello world! V1.0</span><br><span class="line">Hello world! V2.0</span><br><span class="line">Hello world! V2.0</span><br></pre></td></tr></table></figure><p>The class <em>Foo</em> has been hot-swapped successfully during runtime.</p><h2 id="Notice"><a href="#Notice" class="headerlink" title="Notice"></a>Notice</h2><p>That’s it. Althogh our example is quite simple, we have a workable hot-swap class loader now for demonstration purpose. But when considering the production environment, things can be more tricky. Our naive implementation of hot-swap class loader can never be applied onto this environment due to performance issue. As is mentioned earlier, we’ll create a new instance of our class loader every two seconds. This behavior is redundant and a wast of resource even though we enlarge the sleeping cycle. We need to modify it to do the same work only when it is needed. Hence, if you’re desining your production system, you need more work and consideration.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This passage has discussed basic technologies covered in <em>class file hot-swapping in Java</em>. This topic can be very complicated if we involve other aspects like system auto-upgrading/auto-downgrading, and concrete implementation should depend on detailed requirements. Besides above, more details should be digged out by readers yourselves. Let’s keep moving forward!</p>]]></content>
<summary type="html">
<p>Hot-swapping class file is a popular technique in Java to provide class file replacement during runtime. This is a handy feature for developers that makes it possible to modify class files and test their functions on the fly without recompiling the whole project. And it also serves as a base function when implementing automatic system upgrade. This passage mainly focuses on the core technique required to acheive class file hot-swapping, the <strong>Class Loaders</strong> in Java, and a naive implementation of class file hot-swapping.</p>
</summary>
<category term="Technology" scheme="http://blog.lyndonli.com/categories/Technology/"/>
<category term="Java" scheme="http://blog.lyndonli.com/categories/Technology/Java/"/>
<category term="Hot-Swap" scheme="http://blog.lyndonli.com/tags/Hot-Swap/"/>
<category term="Class Loader" scheme="http://blog.lyndonli.com/tags/Class-Loader/"/>
</entry>
<entry>
<title>Install Alibaba Tair</title>
<link href="http://blog.lyndonli.com/2016/05/28/Install-Alibaba-Tair/"/>
<id>http://blog.lyndonli.com/2016/05/28/Install-Alibaba-Tair/</id>
<published>2016-05-28T12:54:35.000Z</published>
<updated>2016-05-28T12:54:35.000Z</updated>
<content type="html"><![CDATA[<p>This passage mainly introduces how to install <strong>Tair</strong>, Alibaba’s own distributed key-value storage system. Because of several errors existing in Tair’s source code and out-of-date introduction documentation, we may get into trouble when installing and deploying this system in our own environment. I’ve successfully installed it on my CentOS 7 and will show the whole process here, wishing it can help those who has trapped into a dilemma.</p><a id="more"></a><br><p><a href="https://github.com/alibaba/tair" target="_blank" rel="noopener">Tair</a> is a distributed key-value storage system delveloped by Alibaba’s Taobao team, the biggest e-commerce platform in China. Tair focuses more on distributed storage and offers features like fault tolerance, pluggable storage, etc. More information can be found <a href="https://github.com/alibaba/tair/blob/master/README" target="_blank" rel="noopener">here</a>.</p><br><p>Tair is written in C++. Like other C++ programs, it needs to be compiled using different platforms’ compilers before it can be run on corresponding platforms. However, the introduction documentation on the official site is kind of out-of-date and there are some bugs residing in the source code. Hence, installing Tair may be a troublesome work. For me, I solved these problems after several trying and am willing to share my experience with others who’s planning to install it and have a try. Perhaps there may be some little differences on different <strong>Linux platforms</strong> (What? You’re using <strong>Windows</strong>? Come on! Serious?), the general process is the same.</p><br><h2 id="Environment"><a href="#Environment" class="headerlink" title="Environment"></a>Environment</h2><p>I deploy Tair on <strong>CentOS 7</strong>. You may choose other Linux distributions but it must be a Linux since Tair uses some Linux-only libraries like <strong>epoll</strong>. Otherwise you’ll need to do a source code porting first, which can be difficult and time-consuming.</p><h2 id="Installation"><a href="#Installation" class="headerlink" title="Installation"></a>Installation</h2><p><strong>I.</strong> Install <strong>svn</strong>, <strong>g++</strong>, <strong>automake</strong>, <strong>libtool</strong>, <strong>boost-devel</strong> and <strong>zlib-devel</strong> before you proceed to compile Tair and its supporting libraries. You can use a software package management tool on your platform to facilitate your installation, like <strong>yum</strong> on <strong>CentOS</strong>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ yum install svn gcc-c++ automake libtool boost-devel zlib-devel -y</span><br></pre></td></tr></table></figure><p>You may need to add <strong>sudo</strong> in front of the <strong>yum</strong> command for system permission.</p><p><strong>II.</strong> Download the source code of Tair’s supporting libraries <strong>tbsys</strong> and <strong>tbnet</strong>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ svn checkout http://code.taobao.org/svn/tb-common-utils/trunk/ tb-common-utils</span><br></pre></td></tr></table></figure><p><strong>III.</strong> Modify <strong>CLogger::CLogger&</strong> to <strong>CLogger&</strong> in the <strong>tblog.cpp</strong> file (You may skip this step if this bug has been corrected).</p><p><strong>IV.</strong> Download Tair’s source code.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ svn checkout http://code.taobao.org/svn/tair/trunk/ tair</span><br></pre></td></tr></table></figure><p>Beware that there do exist a <strong>space</strong> between the last “/“ and last <strong>tair</strong> in the above url. This is <strong>definitely not a typo</strong>! And you should use <strong>svn</strong> to get this source code instead of cloning its Github repository. The latter one contains some problems (at least before I write this passage).</p><p><strong>V.</strong> Configure and make the supporting libraries. First you need to export <strong>TBLIB_ROOT</strong> to tell the script where you want to put the generated libraries. These libraries will be used in Tair’s compilation later.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">export</span> TBLIB_ROOT=/your/path/to/tair-lib</span><br></pre></td></tr></table></figure><p>Then you can configure and make it using the provided <strong>build.sh</strong> script. If everything is OK, you will see the genearted libraries have now existed in the directory where <strong>TBLIB_ROOT</strong> points to.</p><p><strong>VI.</strong> Go to your Tair’s source code directory, make and install Tair as below.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ./bootstrap.sh</span><br><span class="line">$ ./configure</span><br><span class="line">$ make</span><br><span class="line">$ make install</span><br></pre></td></tr></table></figure><p>Now you’ll see a directory called <strong>tair_bin</strong> locating at your user’s directory. That’s where tair’s binary code resides. Congratulations!</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Now you have a runnable Tair on your machine. But in order to run your Tair, you still need to configure your Tair first. For this part, you can refer to <a href="http://code.taobao.org/p/tair/wiki/deploy/" target="_blank" rel="noopener">this official documentation</a>. Good luck!</p>]]></content>
<summary type="html">
<p>This passage mainly introduces how to install <strong>Tair</strong>, Alibaba’s own distributed key-value storage system. Because of several errors existing in Tair’s source code and out-of-date introduction documentation, we may get into trouble when installing and deploying this system in our own environment. I’ve successfully installed it on my CentOS 7 and will show the whole process here, wishing it can help those who has trapped into a dilemma.</p>
</summary>
<category term="Technology" scheme="http://blog.lyndonli.com/categories/Technology/"/>
<category term="Blog" scheme="http://blog.lyndonli.com/categories/Technology/Blog/"/>
<category term="Alibaba Tair" scheme="http://blog.lyndonli.com/tags/Alibaba-Tair/"/>
<category term="Installation Guide" scheme="http://blog.lyndonli.com/tags/Installation-Guide/"/>
</entry>
<entry>
<title>香港记忆</title>
<link href="http://blog.lyndonli.com/2016/03/04/%E9%A6%99%E6%B8%AF%E8%AE%B0%E5%BF%86/"/>
<id>http://blog.lyndonli.com/2016/03/04/香港记忆/</id>
<published>2016-03-03T16:37:54.000Z</published>
<updated>2019-06-02T13:30:35.272Z</updated>
<content type="html"><![CDATA[<img src="/2016/03/04/香港记忆/victoria_harbour.jpg" title="Victoria Harbour"><blockquote><p><strong>I leave no trace of wings in the air, but I am glad I have had my flight.</strong><br>Leo Nikolayevich Tolstoy   <em>—— Flying Fire</em></p></blockquote><br><p>  不知不觉间,距离我去香港已经过去19个月了,原来设想的只是来香港读个一年的硕士就滚回家找工作的,没想到又多待了半年多时间。但是不管怎样,我在香港的生活终究是告一段落了。香港是一个物质的国际大都市,通过TVB,通过香港的电影,我曾对她有过无数的设想,然而直到来了香港,我才发现以前很多的看法都是不靠谱的。曾经对这种看起来金钱至上、缺乏温情的城市没有什么好感,但是这一年半却让我看到了一个不同于我脑海中印象的香港,一个充满了感情的城市。值此难眠之夜,伴着那一抹抹不舍,我想就自己的经历说一些对香港的看法。</p><a id="more"></a><h2 id="曾经的幻想"><a href="#曾经的幻想" class="headerlink" title="曾经的幻想"></a>曾经的幻想</h2><p>  小时候看了很多港剧,既有武侠的,也有现代都市的,比如黄日华的天龙八部啦,古天乐的神雕侠侣啦等等,印象最深的要数林保怡主演的<strong>鉴证实录</strong>系列了,了解现代香港,我就是从这部电视剧起步的。抛开武侠剧不谈,单从都市剧出发,香港给我印象最深的就是她的维港夜景、兰桂坊的酒吧还有尖沙咀的富庶。总之,在我曾经的印象中,香港是一个奢华、繁荣、自由的国际大都市,似乎<strong>香港的每一个角落都是由中环复制粘贴得到的,只是建筑的形状各有不同而已</strong>。这是我来香港前抱有的幻想之一。<br>  其二,我就读的是<strong>香港大学</strong>,虽然看过了很多港剧,但是似乎都没怎么在剧里面见过主人公谈论香港的学校。为了搞清楚港大究竟是怎么样的一所学校,我通过官网仔细地查了它的一些有关信息,尤其是这所学校的外貌。通过官网和谷歌上的照片,我深深地被这所学校的欧式风格所打动(那个主教学楼真是太赞),被“新校区”的大草坪和硕大的校徽所打动。再看看剩下那些学校,俗!于是第二个幻想就此形成:<strong>港大是一所充满了欧式风情的园林式大学校</strong>。  其三,从港剧来看,香港人住的房子都不错,哪怕是家境一般的角色住的房子也还凑合。虽然我对香港寸土寸金,房价高得离谱等传言也是略有耳闻,但是想想人家出重金毕竟住的环境优雅嘛,在所难免,贵一点就贵一点吧。再说了,再贵和出国应该也差不了多少吧。于是,第三个幻想就是<strong>香港的房价高,但是住的房子舒适优雅</strong>。  其四,香港是数一数二的国际化大都市,曾经长期作为英国的殖民地,其官方语言为英语和汉语(普通话和广东话),即三言两语。因此,香港人的英语应该是倍儿棒的,虽然他们都说广东话,但是我如果用英语和他们交流,比如问个路啦等等,应该是没有问题的。这就是第四个幻想:<strong>香港人的英语都很好</strong>。  第五个幻想:香港是国际大都市,生活水平之高那是世界瞩目的。为了维持这个高水准的生活,那么<strong>香港的薪资水平应该也是普遍很高的,传闻一个普通洗碗工的月薪都有两万港币</strong>。  好了,下面我将开始我的香港回忆之旅,看看现实是如何冰冷地击碎我的这些幻想的。</p><h2 id="生活在香港"><a href="#生活在香港" class="headerlink" title="生活在香港"></a>生活在香港</h2><blockquote><p>在香港学习,必定离不开在香港生活。这一节将从在香港的<strong>衣、食、住、行</strong>四个方面出发谈一谈在香港的生活情况。这些信息都是总结自我这一年半的香港生活,绝对字字真心话。随着介绍的深入,上述的某些幻想也将就此被无情地击碎。</p></blockquote><h3 id="衣"><a href="#衣" class="headerlink" title="衣"></a>衣</h3><p>  香港没有冬天,或者更确切地说,没有我们理解中的冬天。我们印象中的冬天应该是身穿羽绒服,裹着厚围巾,戴着厚手套,在寒风中瑟瑟发抖的。然而,香港的冬天和我们的秋天差不多。香港全年的气温一般在12~37摄氏度之间,很少有低于10度或高于38度的气温,而持续时间最长的温度范围约为20至33摄氏度。因此,在香港生活,根本不需要羽绒服,穿的时间最长的就是夏装。可能是长年温暖的气候使得香港本地人不怎么抗冻,也特别怕热,只要气温降到十几度,他们就会穿起羽绒服(当然是比较薄的那种),室内就会开起热空调。而只要气温升到27摄氏度以上,冷气就会开动,而且一般会开在22摄氏度左右。因此,夏天上课前短袖短裤地去,上课时裹着厚外套的属于常见现象。香港人酷爱冷气,基本什么地方都装冷气,因此夏天在外面觉得热了,只要随便找家店铺逛逛坐会儿就能纳凉了。</p><p>  香港人的穿着打扮也和大陆人不同,具体我也很难用言辞来表达,但是你一看就基本能从服饰和发型上判断这个人是港人还是大陆人了。一般而言,港人打扮比较随意,有点嘻哈风,头发用周立波的话来讲就是“头势比较清爽”,而大陆人的身上则完全没有这种感觉。</p><h3 id="食"><a href="#食" class="headerlink" title="食"></a>食</h3><p>  香港的确是一个美食天堂,全世界的美食都可以在这里找到。但是在路上见得最多的还是当地的特色餐点:<strong>叉烧饭</strong>,各种各样的叉烧饭。我有一个初中同学特别喜欢吃香港的叉烧饭,诚然,我刚来香港的时候也特别喜欢吃这种甜甜的、既有叉烧肉又有烧鸡的饭,关键是价格还比较便宜。如果在学校吃单拼叉烧饭,一般只要$20左右,和动不动就三四十的饭一比,性价比超高。然而,任何东西吃多了就会厌烦,我也是在不知道吃了多少顿叉烧饭后开始厌恶这个油腻的大杂烩。于是,在室友和同事的循循善诱下,我开始寻觅其它的美食,开辟新的天地,下面将为大家展示鄙人的劳动成果。</p><p>  以下所列是地处西环的美食店,基本都是家常菜,全是我亲自鉴赏过的哟,并且熟悉的我都用港币明码标价了(排名不分先后,因为实在太难排,加粗的为<strong>特别推荐</strong>)。</p><ol><li><strong>客尚来</strong>(上海菜,各种经济客饭还送豆浆,$42)</li><li>赞记厨房(椒盐猪扒饭,$45)</li><li><strong>潮苑</strong>(午市牛扒餐,司华力肠+煎蛋+黑椒汁+中汤+热奶茶,原价$41,出示学生证$37)</li><li><strong>阿元来了</strong>(台式卤肉饭+皇室奶茶,$49)</li><li><strong>桃源</strong>(各种午市套餐,$38~$48不等。黑帮人士开的店,吃饭记得要带钱哦!)</li><li>八方云集(红油抄手+招牌水饺套餐,附送豆浆一杯,$39)</li><li><strong>鱼米</strong>(果醋猪软骨菜饭,$45)</li><li>Oppa Chicken(韩式炸鸡,根据点餐不同,价钱浮动较大)</li><li>吉野家(和风牛肉饭,$38.5)</li><li>大家乐(只推荐<strong>惠州梅菜酱油鸡</strong>,$49)</li><li>书湘门第(湖南菜)</li><li><strong>巴依餐厅</strong>(米其林推荐餐厅,新疆菜,<strong>大盘鸡</strong>乃是一绝!)</li></ol><p>  以上就是在西环的美食了,其中<strong>潮苑</strong>的<strong>午市牛扒餐</strong>我是亲眼看着它从$36涨到$38,最后到现在的$41的,简直就是心痛。还有其它的店铺,比如<strong>金祥排骨面</strong>,<strong>北方饺子源</strong>等等,我吃的比较少,印象也比较浅,就不说了。下面罗列一下整个特区范围内的美食,该列表除了有做家常菜的店,还包括一些价格不那么家常的店(排名不分先后,加粗表示特别推荐)。</p><ol><li><strong>添好运</strong>(米其林一星的点心店,在中环IFC以及奥海城都有)</li><li>九记牛腩(上环的山上)</li><li>满记甜品总店(西贡)</li><li><strong>荣氏烧鸡扒</strong>(铜锣湾有分店,人均$50左右)</li><li>澳洲牛奶公司(佐敦,各种鸡蛋制品和奶制品,光速上餐)</li><li>第一肠粉(太子,各式肠粉,比“一粥面”强上一百倍)</li><li><strong>唐记包点</strong>(元朗有分店,各种大包子,物美价廉)</li><li><strong>Ruby Tuesday</strong>、<strong>Outback</strong>(一流的牛扒餐厅,在铜锣湾,人均$200以上)</li><li><strong>一兰拉面</strong>(日本的拉面馆,在铜锣湾,一碗面$98以上)</li><li>豚王拉面(日本的拉面馆,在铜锣湾,一碗面$90以上)</li></ol><p>  可能有人会问,为什么上面的清单里面没有海鲜?作为一个来自海滨城市的人,<strong>你以为我不想吃嘛</strong>?好吧,平复一下心情,其实对于一个穷鬼来说,海鲜在香港实在是消费不起,鄙人也没吃过多少像样的海鲜,西贡海鲜街的<strong>全记海鲜</strong>还比较不错,其它的就不清楚了。香港的食物总体给人的感觉就是<strong>菜比肉贵,海鲜最贵。肉要自己切,菜要整株吃</strong>。肉跟不要钱似的,菜连切都懒得切,不知道港人是怎么想的。</p><p>  如果你要问香港什么吃的最便宜?答案毫无疑问是<strong>麦当劳</strong>!香港到处都有麦当劳,而在大陆比较常见的<strong>肯德基</strong>却是难觅踪迹,其中一部分原因就是香港的肯德基相比于麦当劳价格贵上许多。比如说,麦当劳的各式汉堡套餐的价格从22港币到38港币不等,均为汉堡+薯条+碳酸饮料的标配,但是肯德基类似的套餐价格均在40港币以上。麦当劳还有两款汉堡的单点价格只要10港币!折算成人民币才8元钱一个,简直逆了天了。所以,在香港的麦当劳店里可以看到各式各样的社会人等,也是当地贫下阶层偏爱的就餐选择。反观香港的肯德基,价格贵不说,地方少不说,连款式都跟大陆不一样,我们熟知的鸡肉卷等等餐点在这里都是找不到的,更可气的是味道还很一般,同样的东西其质量没法和内地相提并论。更有甚者,某些地方的肯德基简直在用它们的粗制滥造砸肯德基的招牌,在这里点名批评一下<strong>马场</strong>的肯德基,汉堡肉硬的跟砖头一样我就不计较了,薯条粗壮我就不吐槽了,<strong>能麻烦您以后把土豆皮削干净了再炸吗</strong>?!</p><p>  说到香港的肯德基,我就想列一个<strong>黑名单</strong>,把我不喜欢吃的都罗列一下,不过它们仅仅是不符合我的口味哦(同样排名不分先后,各有瑕疵,实难排序)。</p><ol><li>新钊记(我住了一年的均益大厦二期对面,来港第一餐吃的就是它,之后被迫跟朋友来这吃了回夜宵,然后就没有然后了)</li><li>西湘记(做肉夹馍的,简直没法跟正宗的陕西肉夹馍相比,凉皮也是不地道)</li><li>莉苑(吃了一回就拉肚子)</li><li>港大学生会食堂(其实就是大家乐,廉价但是……各式叉烧饭吃腻,住家巧手菜的配图与现实从来不符,唯有<strong>惠州梅菜酱油鸡</strong>,但也只是昙花一现)</li><li>Groove(港大内的西餐厅,很难有西餐厅能做到什么都这么难吃的)</li><li>一念素食(港大内的素食餐厅,又贵又难吃,我居然带朋友去那里尝鲜,当时脑子真是进水了)</li><li>一粥面(除了<strong>肠粉</strong>是少有的亮点外,其它都不咋地,尤其是面食,不习惯碱水面的同志赶紧撤退吧)</li><li>Kebab(港大的伊斯兰餐厅,不习惯中东菜的还是歇歇吧)</li></ol><p>  <strong>黑名单</strong>就列这些吧,毕竟我是很仁慈的,不到万不得已,不会妄开吐槽金口。</p><p>  对于吃货而言,在追求美食的道路上是永无止境的。上述所列的美食清单只是香港美食的冰山一角,还有很多好吃的等着我去发掘,然而我已经没有时间了。虽然很可惜,但是我相信我一定会再回到这个地方,再来品尝那些我曾经的最爱的!</p><h3 id="住"><a href="#住" class="headerlink" title="住"></a>住</h3><p>  下面就到了在香港学习生活最不想面对,却又不得不面对的最大问题了,那就是怎么在香港住的问题。香港寸土寸金,想必大家都听说过,尽管看上去香港七百多万的人口以及1104平方公里的面积好像密度并不是很大,但是其实香港人口都拥挤在32.6%的非绿地面积上,尤其是港岛和九龙。因此,如果穿梭在铜锣湾或者油尖旺的路上,用人山人海来形容一点都不为过,再加上香港的道路都很窄,还有汽车在身边穿梭,所以整体给人一种特别拥挤的感觉。人又多,住宅用地又少,因此香港的房子只能管天空要地,二三十层楼高的住宅在香港比比皆是,屡见不鲜。另外,由于香港发展较早,现今已经有很多高龄房产,但是这些房产因为有人居住,所以不能拆迁造新楼,因此在港岛的西环、九龙的油尖旺地区到处可见建于数十年前的破旧房屋,一点都没有现代化的感觉,晚上走在路上甚至能看到硕大的老鼠从你眼前窜过,让人为之大跌眼镜:原来香港并不是一个走哪都是中银大厦,到哪都是星光大道的,其实香港的老城区相比于内地的城市的老城区而言也差不多,甚至更差。</p><p>  那么问题就来了,香港的房价到底有多贵呢?我第一次搜香港房价的时候看到港岛西环的房价大概1万~2万港币一平,顿时觉得好便宜啊,比北上广便宜多了!为什么都说香港房价贵呢?但是一搜整体价格,一套40平方米的房子动不动就五六百万,根本就不是1万多一平啊!后来才知道,原来香港的一平默认情况下指的是一平方尺,大概是一平方米的十分之一,F***。那么租金呢?其它地方我是不清楚,但是港岛西环附近的房子一般都是12000港币起步价(两室一厅,无阳台,40平方米),近港铁站的就更贵了。好吧,既然房子这么贵,面积又这么小,那装潢啥的总该讲究了吧?毕竟香港人都是追求生活品质的嘛!<strong>大错特错!</strong>一般在西环的房子都是老的不能再老的旧房,大部分都是老一辈香港人年轻时候住的,现在他们都搬到环境更优雅的地方去了,这些房子就正好留给我们这些来香港留学的住,毕竟离香港大学近嘛,而且离港铁站也很近,出行啥的都很方便。所以,尽管房子又小又破,厕所马桶很有可能不干净,洗澡没有独立淋浴间(弄湿整个厕所,包括马桶),晚上走在小弄堂(香港的路按照从宽到窄的顺序依次是道Road、街Street、里Lane,小弄堂就是里)还能看到个别老鼠走亲戚,但是房价依然是居高不下,毕竟是卖方市场嘛。因此,四五千的月租租个几平米的小单间已经是算不错的了,就不要再奢求租下整套房了,如果不是隔板房的话,那真的要谢天谢地了。</p><p>  下面谈谈住房类型问题。香港的房子按照外形和设施一般可以分为唐楼和洋楼两种。所谓唐楼呢,就是传统住宅,这种楼一般楼层较低,没有电梯(香港叫升降机),内部设施一般也比较简陋,因此租金也比较便宜。而洋楼就是相对“现代化”一点的房子,楼层很高,有电梯,现代装潢,当然租金就比较高了。如果你住的楼层高,那么每次往返学校都要花费比较长的时间在电梯里,但是如果你住的偏低,就会被地面上的各种状况干扰,比如施工啦(香港的道路每时每刻都在施工,早晨能督促你早起)、跑车啦(剧烈的引擎轰鸣声,晚上让你难以入眠)、摩托啦(效果同跑车)等等,吵死你不偿命。所以,在香港选房子的楼层,<strong>中庸</strong>是很重要的。另外,香港的楼层有一个地面层叫<strong>G</strong>层(Ground Floor),而不是大陆的1层,香港的1层相当于大陆的2层,以此类推,刚来香港的人可能会搞混。</p><p>  一般来香港念书租房子都会选择和学生一块合租,但是也有机会能够和房东一起住哦!我在香港的后期就是和房东一家人一块住的,由于主人家也住这套房,因此房子格外的干净和漂亮,设施也很齐全。再加上房东一家人都很好(既有大陆人又有香港本地人,还有个可爱的小女儿,会请我一块儿吃晚餐啥的,生病了还会拿药给我),我以不到5000的租金就获得了超过一个小单间的价值,还能融入香港社会,绝对是物超所值。所以,和学生合租并不一定是唯一的选择,可能还会有更好的机会,只要肯花时间在相关的网站上搜索,比如<a href="http://www.hkucssa.com/forum.php?mod=forumdisplay&fid=44" target="_blank" rel="noopener">HKUCSSA</a>。如果你想和老外住一块的话,很简单,准备6000以上的租金就好了,他们一般都住在比较优雅的地段,比如湾仔啥的(整房2万以上很正常),然后就可以去找他们合租了,Facebook上有的是相关的信息。</p><p>  说完了我们,那么香港本地人是怎么住的呢?大家都直接去买新房住吗?答案是否定的。其实,香港人最头疼的一个问题就是买房的问题。香港房价居高不下,越来越多的本地人难以买房,因此香港政府为香港本地人提供了廉租房,这些政府公屋的价格都比较便宜,但是并不是所有人都能申请居住的,它有月收入的限制,超过该月收入限制的人并不能申请这类房子。所以有些香港人就会同时身兼好几份工作,然后提交一份低工资的收入证明,从而申请到这类房子。但这也仅仅是缓兵之计,最终还是需要自己的努力,贷款来购买属于自己的房子。总之在香港外来人居住是个问题,而本地人的居住也是一个问题,住房资源的紧张,工作岗位的有限,也就不难理解为什么有些港人十分排斥外来移民了(尤其是大量的大陆移民,他们大多以投资或者优才计划的方式移民到香港)</p><h3 id="行"><a href="#行" class="headerlink" title="行"></a>行</h3><p>  香港作为国际大都市,其交通之便利在这世上简直难有其匹,这一点要大大地表扬一下。有人说过,私家车在香港没什么大的用处,此言确实不虚,因为香港的公共交通网络几乎覆盖了私家车所能到达的任何一个地段,其多层次立体化的架构让人叹为观止。</p><p>  香港的公共交通主要由小巴、大巴、地铁、轻轨、电车以及出租车这几个大类组成,它们各自还有不同的具体组成部分,不同的交通工具也具有其自身的特点。</p><ul><li>小巴:香港的小巴分为红色公共小巴和绿色专线小巴两种,两者都有固定的最大乘客人数(一人一座,不许站立),都没有自动报站服务,司机都只会说粤语,但是红色小巴不设停靠站,随叫随停,没有固定的行驶路线,而绿色的则有停靠站以及固定的行驶路线。所以很明显,这种小巴基本就是服务香港本地人的,除非你会讲些粤语,并且熟悉这条巴士的线路,否则人家把你扔哪了你都不知道。香港小巴的最大特点就是<strong>亡命小巴</strong>,行驶时速基本维持在每小时60公里(这是限速。。),尤其在乘坐小巴走盘山公路下山的时候,你更加会体会到一种生不如死的感觉。</li><li>大巴:香港的大巴因袭英国的传统,基本都是双层巴士,只有少数是单层,比如A10这种机场快线。这些大巴由若干家私营公司经营,比较常见的有新巴、城巴和九巴。这些巴士的服务规程与大陆的巴士相似,但是坐着非常舒服(坐在二层时候可能会晕车),收费根据上车站点的不同也不一样,一些跨区的巴士收费可能会高达20港币,甚至更高。所以乘坐香港的大巴有时候也是种奢侈。</li><li>地铁:搭载人数最多的公共交通工具自然是非地铁莫属了。香港地铁由港铁运营,它的线路相比于北京和上海来说并不算多,总线路也并不算长,但是服务质量绝对是一流的,也不太会像上海的某些线路一样经常出问题。但是香港的地铁是比较昂贵的,起步价5港币,只要是跨过了维港那就是10港币以上了。但是学生可以通过使用学生八达通享受半价优惠,港铁公司也会在某些节假日或者纪念日推出优惠活动,可以减半甚至是全免出行费用。但是无论如何,地铁是香港综合了各种因素后最方便的出行选择了。</li><li>轻轨:香港的轻轨只有在元朗至屯门地区才有,本质上仍然是一种陆上轨道交通,我只在去香港湿地公园的时候搭乘过一次,感觉与地铁差不多,就不详述了。</li><li>电车:这里说的电车自然指的是大名鼎鼎的<strong>叮叮车</strong>啦!叮叮车只有港岛北部才有,西起坚尼地城,东至筲箕湾,它最大的优点就是便宜(成人只要2.3港币),最大的缺点就是龟速。叮叮车自1904年起就已经在香港服役,至今已有110多年的历史了。港岛本来就很拥挤,车水马龙的,如果没有电车专用车道,叮叮车就得和机动车抢位,你说怎么可能抢得过机动车?而且叮叮车的维护费用居高不下,仅凭这一点点的车资收入,当然是入不敷出,赤字连连。尽管如此,香港政府仍然保留了这一交通方式,为的就是保留这一份珍贵的香港记忆。除了常规的交通以外,叮叮车还提供<strong>电车派对</strong>服务,可以租用一辆电车开晚间派对,一边玩乐,一边欣赏港岛的夜景。</li><li>出租车:香港的出租车被戏称为“皇冠跑车”,根据颜色的不同还可以细分为红色跑车、绿色跑车和粉色跑车,原因就在于其行驶的高速度。香港的出租车根据所属地段的不同涂有不同的颜色,红色代表市区的出租车,亦即港岛的出租车,绿色代表九龙和新界的出租车,粉色代表大屿山的出租车。不管去哪里,坐出租车一定是最惬意的出行方式了,但是很遗憾,鄙人从来没有做过香港的出租车(主要还是因为穷),所以对香港出租车的服务也不敢妄加置喙,具体感受还是要靠自己去体会了。</li></ul><p>  在香港选择公共交通出行,除了红色小巴以外,都可以使用八达通付款,非常方便。除了上述的陆路交通外,还有一些水上,甚至是空中交通工具,比如往返于港岛和各个离岛的汽船、往返于港岛和九龙的天星小轮(下层只要2港币,最便宜的跨维港方式)以及跨维港的直升机,当然还有往返香港和澳门的喷射飞航。</p><p>  说到出行,不得不推荐一些鄙人去过的,觉得还不错的香港当地景点,现将其罗列如下,仅供参考。</p><ul><li>海洋公园</li><li>昂坪360</li><li>西贡郊野公园(比较大,适合两日游览,著名景点有万宜水库等)</li><li>龙脊(港岛东南部山脊)</li><li>太平山(山顶凌霄阁、杜莎夫人蜡像馆等)</li><li>维多利亚港(星光大道)</li><li>飞鹅山</li><li>新娘潭瀑布</li><li>赤柱</li><li>清水湾、深水湾、浅水湾</li><li>南丫岛</li><li>长洲岛</li><li>湿地公园</li><li>志莲净院与南莲园池</li><li>马湾公园</li></ul><h2 id="学习在香港"><a href="#学习在香港" class="headerlink" title="学习在香港"></a>学习在香港</h2><blockquote><p><strong>我来香港是来读书的!</strong><br><strong>我来香港是来读书的!</strong><br><strong>我来香港是来读书的!</strong><br>重要的事情说三遍!虽然我自己都不信。。好吧,我来香港真是来读书的,顺便搞点副业而已。香港的教育系统也是十分发达的,我就读于香港大学,但是也去过其他的一些学校,因此这一部分就来谈谈我对香港高校的一些看法(仅包括对大陆开放招生的高校)。</p></blockquote><h3 id="香港大学-The-University-of-Hong-Kong"><a href="#香港大学-The-University-of-Hong-Kong" class="headerlink" title="香港大学 (The University of Hong Kong)"></a>香港大学 (The University of Hong Kong)</h3><img src="/2016/03/04/香港记忆/hku.jpg" title="The University of Hong Kong"><p>  这是我就读的学校,香港的,一度也是亚洲的,最高学府。这里曾出过多位名人,比如孙中山、张爱玲等等。香港大学拥有好几个校区,最大的就是位于太平山山腰的主校区,另外还有位于沙宣道的李嘉诚医学院校区、薄扶林道的体育馆以及位于港岛东南角的太古海洋科学研究所。香港大学的主校区又可以分为旧校区以及百周年校区,后者是2012年香港大学百周年校庆时候启用的新校区。由于和医学院的课程没有半点交集,港大的主校区也就成了我的主要活动区域。</p><p>  港大的主校区可以分为历史悠久的主楼、梅堂、太古楼、梁銶琚楼、纽鲁诗楼、图书馆、庄月明楼、黄克兢楼、明华综合楼、嘉道理生物楼、周亦卿楼、智华馆等几个部分,港大就是这些楼的综合体,大多数楼之间都有道路相连,雨天在这些楼之间穿梭完全不需要打伞,因此游历港大仅仅依靠二维的地图是远远不够的,还需要三维的导航。主楼就是经常出现在媒体上的那栋欧式风情的建筑,极其典雅、庄重,位于主楼的陆佑堂是香港大学每年举行高桌酒宴和舞会的地方,充满了英式风情(然而它更为人所熟知的是作为我们购买苹果学生机的地方^_^)。如此典雅之处自然也就成了港大文学院的基地,承载了自清末以来广大文人志士的智慧,并继续滋润着后来者。梅堂的建筑风格与主楼类似,但是现在仅仅作为办公的地方,失去了教学的功能。好了,以上就是港大唯二的具有欧式风情的建筑了,作为硕果仅存的两栋欧式建筑,它们自然也就难免受到游客和学生的青睐,成为毕业照拍摄圣地也就不足为奇了。除了这两个别具特色的建筑以外,我就读的工程学院坐落在黄克兢楼,而我所在的计算机科学系则位于新建的周亦卿楼,我后来工作的实验室也位于周亦卿楼。在这些建筑里最值得一提的当然是<strong>智华馆</strong>了。该馆位于港大百周年校园内,在来港大之前我对一所大学的自习室的理解顶多是这样的:</p><img src="/2016/03/04/香港记忆/zixi.jpg" title="自习教室"><p>  但是来到智华馆之后才知道,原来一个大学的自习室也可以是这样的:</p><img src="/2016/03/04/香港记忆/chiwah.jpg" title="Chi Wah Learning Commons"><p>  智华馆提供了极尽奢华的学习设施,除了各式沙发、摇椅、性能强劲的Windows PC机、讨论室之外,还有懒人沙发、5K显示屏的Mac电脑、优雅的静音深度学习区等平时难以想象的设施,让人不知不觉爱上学习,由衷地发出一种“原来学习也可以这样惬意”的感慨,果然是一分价钱一分货,这么多的学费的确没有白交。</p><p>  尽管没有医学课程,但是由于一个室友是医学院的学生,因此也去过几次医学院校区。该校区坐落在沙宣道,毗邻大海,享受得天独厚的海景房学习环境,旁边就是玛丽女王医院,方便学生实习。港大的医学院接受了来自李嘉诚的巨额捐助,是港大的王牌学科,汇集了全世界的医学精英,大量课程都是外籍教师直接授课,而它的学费也是极其昂贵(硕士一年的学费是我们工程类的1.5倍)。</p><p>  港大硕士的学习压力相对本科而言并不是很大,但是自从我们这一届之后,新的计算机硕士课程要求至少一年半的学习时间,每门课每学期至少三次作业,并且必须完成硕士答辩项目才能毕业,比我们严格了许多。港大采用全英文授课,上课的老师成分比较复杂,有本校的讲师和教授,也有外聘的讲师和客座教授,与斯坦福等世界名校的联系也比较紧密。除外籍教师以外,本地的老师说英文大多带有港式英语的腔调,但是听多了还是能够习惯。所有老师,注意是所有,都是以学生为第一服务对象的,他们上课很耐心、很认真,同时也极其欢迎学生的互动,对于学生的提问总是有问必答,甚至还会对学生的问题进行拓展,引导学生做进一步的思考。这里的老师绝大多数时候不会直接把答案告诉学生,而是告诉他们找到答案的途径,从而引导学生自主地发现问题的解决方法。这种启发式而非填鸭式的教学方式以及在此方式下培养出来的学生的创造能力之高确实值得我们内地的教育界人士反思。</p><p>  与内地的大学不同,港大除了不受教育部的管辖从而拥有独立的<strong>大学人格</strong>之外,还有独立的企业运营部,该部门通过专业化的资金管理,维持学校的运转,并为学校盈利,从而提供更优质的教学资源。这种成功的运作方式培养了一批又一批的高材生,很多后来事业有成者都会给学校捐款(其实普通学生就已经在为学校捐款了,比如毕业证书的外套就是捐赠后获得的),这些大额捐款通过企业运营部门的运作又会给学校带来更大的收益。如此的良性循环,是港大能够长期屹立于学术之林顶端的重要经济支柱,这一点也值得我们内地的学校参考。</p><h3 id="其它大学"><a href="#其它大学" class="headerlink" title="其它大学"></a>其它大学</h3><p>  由于鄙人只是在港大念过书,因此对于香港其它高校并没有什么比较深入的认识,无法单独长篇大论地对这些学校进行描述,因此统一写在这里。</p><p>  所谓尺有所长寸有所短,尽管香港公认的最好的高校只有<strong>香港大学、香港科技大学(The Hong Kong University of Science and Technology)以及香港中文大学(The Chinese University of Hong Kong)</strong>三所,但是并不意味着其它学校就都比这些学校差。相反,在某些学科领域,其它学校的表现甚至强于这三所学校。</p><p>  香港科技大学是这些学校里面最年轻的一所学校,1991年成立,但是它的进步却是突飞猛进式的,现在已经超过香港中文大学,在综合排名榜单上成为与港大并驾齐驱的豪门了。顾名思义,科大的优势学科就是理工科,它所处的地方——清水湾——也是一个杳无人烟、风景绝佳、适合埋头苦读、与世隔绝的地方。我去过科大两次,都是为了看风景,但给我的总体感觉并不是很好,所谓的清水湾海景也不过如此,海滩BBQ也就那样,不能让人提起精神。而且港科大的建筑也没有港大来的优雅有格调(个人感觉),历史底蕴更是无从谈起,所以以一个游览者的身份,我对科大的评价不是很高(非学术)。</p><p>  香港中文大学是香港所有大学里面积最大的一个,大约是港大的14倍,坐落于新界沙田的山峦之上,港铁东铁线的<strong>大学</strong>站即是香港中文大学。走进港中文,就有一种非常熟悉的感觉,整体的建制、环境都与内地的学校差不多,真正的园林式大学。学校的各个建筑位于不同的山峦上,因此内部有免费的校园巴士可以搭载(仅对港中文学生和职员提供),不同楼之间也有扶梯相连。尽管叫做中文大学,但是港中文的强势学科却是理工科,计算机科学也是一大亮点。我只去过中大一次,仅仅逛了冰山一角,学校实在太大,而且与世隔绝(比港科大要好一点,至少有地铁),最近的购物中心在沙田,需要搭载地铁到达,整体感觉还是没有港大好(曾经申请过港中文,并且在港大之前获得了offer,但是仍然狠了狠心拒绝了港中文,最终等来了港大的offer,现在想想还是很满意当时的决策的)。</p><p>  香港的其它大学还包括香港城市大学、香港理工大学、香港浸会大学、岭南大学、香港教育学院、香港公开大学、香港树仁大学等,其中后两所不面向大陆招生。鄙人曾经去过一次城市大学,那个感觉就是:<strong>真的是城市里面的大学</strong>。城大位于九龙塘,隐藏于普通的城市建筑群之中,建筑风格与普通城市建筑无二,缺乏独特性,没有指示路牌难以发现这所学校的存在。学校出门之后就是地铁站和又一城购物中心,shopping虽然便利,但是很难让人联想到<strong>学校</strong>与这些建筑的联系,我个人不是很喜欢这所学校(最初也考虑申请城大,但是做了一番了解之后还是决定放弃,全力追逐港大)。除此之外的学校鄙人从未涉足,也知之甚少,就不在这里讨论了。</p><h2 id="工作在香港"><a href="#工作在香港" class="headerlink" title="工作在香港"></a>工作在香港</h2><blockquote><p>我想很多在香港求过学的学生都多少会想要留在香港工作一段时间,然而由于一些现实的原因,很多都选择了毕业就立即回大陆找工作。我有幸在读书时候就在香港兼职工作了半年,并且在学业结束等待拿取毕业证的这段时间里在香港全职工作了半年多,累计也算工作了一年多,所以顺便谈一谈在香港的工作情况。</p></blockquote><p>  在香港工作,绝对是一件不容易的事情。香港的工作压力之大世人皆知,高居世界第一(当然不同领域有所不同,相同领域的不同工作环境也有所不同,学校里工作相对于企业当然更加轻松自在)。作为自由经济体,香港的工资除了缴纳一部分所得税以及强积金(香港的养老基金)以外,其余都是个人的可支配收入。香港的工资水平相比普通城市而言虽然很高,但是在发达城市之间比较并不占很大的优势,坊间盛传的洗碗工月收入两万虽然确有此事,但不是普遍现象,即使是那些月入两万的洗碗工,工作压力的增加与工资的增加不成比例。一般香港的本科应届毕业生的工资在12000~14000港币左右,硕士在某些领域(比如IT)可能高个几千港币,博士则浮动很大,上不封顶。就行业来看,香港的第一职业金融业的收入最高,部分应届生的入职薪资在月入数十万港币也是有的。第二,各类政府相关的职业,比如律师、公务员、警察等的工资也是比较可观的,一个警察的基本月收入可以达到六到七万港币,公务员在廉政公署的计划下(高薪养廉)也有丰厚的待遇,但是贪污的后果也是很严重的。此外,医生也是一个高薪群体,拥有一定资历的医生(通常是港大医学或牙科毕业,并且在海外进修过高级课程的医生)收入可以与金融从业者匹敌。相反,本应该是信息革命先锋官的科技领域在香港并不吃香,甚至信息化在各个领域的普及程度也完全不及大陆(比如很多香港人看到支付宝以及微信支付时候的态度是:哇,原来还有这种神奇的东西,但是这些对于现在的大陆人而言早已是司空见惯了),科技工作者的主要工作是作为二等公民为金融和政府职能部门提供信息技术支持,那么相应的科技工作者的收入也普遍不如上述这些领域的工作者。由此带来的一个问题就是香港社会的进一步发展问题。随着世界经济下行压力的增强以及中国大陆金融市场的逐步开放,香港在国际金融市场上的地位正在逐渐下降,大陆对香港的依赖也在逐渐减少,香港的未来应该何去何从成了悬而未决的问题,尽管港府已经开始认识到科技在经济以及产业革命中的重要性并且特意开拓了数码港、科技园等高科技园区以大力扶持本土科技产业的发展,但是收效甚微,在其它领域的挤压下,新兴科技企业难以在高物价水平的香港生存。我个人并不认同香港发展科技产业的做法,相反,我比较同意使用林毅夫先生的比较优势理论来解决香港的问题。每个国家,每个城市,都有它生存的特点,这些特点给这些国家和城市带来了不同的优缺点,而经济要想发展,企业要想生存,就必须要求这些企业拥有自生能力(即在公平竞争、没有政府扶持的市场环境下,企业通过妥善经营能够获得盈利从而生存下去的能力),而企业是否具有自生能力则取决于该企业所处地域的比较优势。对于香港而言,金融、医学等领域无疑是香港拥有比较优势的领域(相比于美国硅谷、北京中关村而言),而科技则是劣势领域,在大环境没有改变的情况下,通过政府扶持发展科技产业只会造就没有自生能力的企业,这些企业在政府扶持结束之后将难以生存,得不偿失。因此,稳定政治局势,继续发展强势产业才是香港的当务之急,至于科技嘛,用钱买就好了,毕竟香港是一个金融都市,而不是一个科技都市,已有的金融市场规模和操盘经验是香港最宝贵的财富,作为一个城市,它已经是佼佼者了,硬要充当全能战士只会画虎不成反类犬。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>  真实的香港和我原来印象中的香港确实有天壤之别,这里并不是豪宅林立、富可流油,也并没有充满英式风情,完全被西化,相反,这是一个具有独特<strong>人格</strong>的城市,一个<strong>现实</strong>的城市。在这里,富贵与贫贱并存,东方与西方融合,生活节奏可以快到路上所有人都行色匆匆,同样也可以悠闲到大家去公园里散步赏花。它是一个大都市,中环高楼林立,但是也有大面积的森林覆盖,绿意盎然。它有繁华的维多利亚港,也有西贡的清澈海滩。在大街上,你可以听到粤语、普通话、英语、日语、德语、法语等各种语言,可以见到来自世界各个国家的人在一起亲切攀谈。早上出门的时候你住处的门卫会对你说<strong>早上</strong>(粤语,意思是早上好),帮了别人哪怕一点点忙的时候你会听到对方的<strong>唔该</strong>(粤语,意思是谢谢),街道虽然没有日本的干净,但是人的素质却是世界一流。作为全世界最自由的经济体,自由、开放、国际化,这才是香港的地标,赖以生存的基础。我相信,只要坚守这些理念,不去理会其它的流言蜚语和恶意挑唆,香港一定会在接下来的时间里继续扮演重要的金融中心的角色,继续它的繁华与宁静。作为我人生中的重要一站,香港终结了我的学业,开启了我的事业,也开拓了我的视野,尽管期间出现了“占中”等中港冲突事件,但是在一年半的生活、学习与工作中我逐渐认识了这个陌生的城市,逐渐感受到了隐藏在资本光鲜外衣下的属于香港的返璞归真的热情,我从没有对当初的决定感到后悔,相反,它将作为我人生的一笔财富,随我远行。</p><blockquote><p>谨以此文,聊寄遐思</p></blockquote>]]></content>
<summary type="html">
<img src="/2016/03/04/香港记忆/victoria_harbour.jpg" title="Victoria Harbour">
<blockquote>
<p><strong>I leave no trace of wings in the air, but I am glad I have had my flight.</strong><br>Leo Nikolayevich Tolstoy &emsp;&emsp;<em>—— Flying Fire</em></p>
</blockquote>
<br>
<p>&emsp;&emsp;不知不觉间,距离我去香港已经过去19个月了,原来设想的只是来香港读个一年的硕士就滚回家找工作的,没想到又多待了半年多时间。但是不管怎样,我在香港的生活终究是告一段落了。香港是一个物质的国际大都市,通过TVB,通过香港的电影,我曾对她有过无数的设想,然而直到来了香港,我才发现以前很多的看法都是不靠谱的。曾经对这种看起来金钱至上、缺乏温情的城市没有什么好感,但是这一年半却让我看到了一个不同于我脑海中印象的香港,一个充满了感情的城市。值此难眠之夜,伴着那一抹抹不舍,我想就自己的经历说一些对香港的看法。</p>
</summary>
<category term="Diary" scheme="http://blog.lyndonli.com/categories/Diary/"/>
<category term="Hong Kong" scheme="http://blog.lyndonli.com/categories/Diary/Hong-Kong/"/>
<category term="回忆" scheme="http://blog.lyndonli.com/tags/%E5%9B%9E%E5%BF%86/"/>
<category term="香港" scheme="http://blog.lyndonli.com/tags/%E9%A6%99%E6%B8%AF/"/>
</entry>
<entry>
<title>Establish Hexo with GitHub</title>
<link href="http://blog.lyndonli.com/2015/12/21/Establish-Hexo-with-GitHub/"/>
<id>http://blog.lyndonli.com/2015/12/21/Establish-Hexo-with-GitHub/</id>
<published>2015-12-21T11:24:38.000Z</published>
<updated>2016-03-04T13:35:41.000Z</updated>
<content type="html"><![CDATA[<p>This is the first time I write my own blog on my own site. I’m inspired by those who use <strong>Hexo</strong> to establish their own wonderful blog sites. Its flexibility and programmer-friendly paradigm deeply attracts me so that I decide to make a site of my own with Hexo.</p><a id="more"></a><p>Hexo is a static blog framework written by a Taiwanese in <strong>NodeJS</strong> and then soon adopted by large amounts of people, especially programmers and technical geeks. Even those who have already got used to other blog frameworks like <strong>WordPress</strong> also migrated to this brand new platform.</p><p>This blog is about how to establish a Hexo blog site from scratch on <strong>Mac OS X El Capitan</strong>. Althogh there’re many similar passages available on the Internet, this one mainly focuses on the difficulties I’ve encountered.</p><h2 id="Installation"><a href="#Installation" class="headerlink" title="Installation"></a>Installation</h2><h3 id="Install-Git-With-Administrator-Permission-and-Homebrew"><a href="#Install-Git-With-Administrator-Permission-and-Homebrew" class="headerlink" title="Install Git (With Administrator Permission and Homebrew)"></a>Install Git (With <strong>Administrator Permission</strong> and <strong>Homebrew</strong>)</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ brew install git</span><br></pre></td></tr></table></figure><h3 id="Install-NodeJS"><a href="#Install-NodeJS" class="headerlink" title="Install NodeJS"></a>Install NodeJS</h3><blockquote><p>Select a proper version and download the installation package from <a href="https://nodejs.org/en/download/stable/" target="_blank" rel="noopener">NodeJS Official Site</a></p></blockquote><h3 id="Install-Hexo-With-Administrator-Permission"><a href="#Install-Hexo-With-Administrator-Permission" class="headerlink" title="Install Hexo (With Administrator Permission)"></a>Install Hexo (With <strong>Administrator Permission</strong>)</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install hexo -g</span><br></pre></td></tr></table></figure><h2 id="Start-Hexo"><a href="#Start-Hexo" class="headerlink" title="Start Hexo"></a>Start Hexo</h2><p>Change to any directory where you want to put your blog. Then initiate a blog site with a customized name using</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo init <span class="string">"blog_name"</span></span><br></pre></td></tr></table></figure><p>Now we can see a directory with your specified name right at the current directory. Go into the blog directory and type in</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p>Hexo server will be started on <strong>0.0.0.0</strong> at port <strong>4000</strong> by default. You can have a look by typing the address in a browser. You can terminate it by typing <strong>Ctrl-C</strong> in your <strong>Terminal</strong>.</p><p>All Hexo-related configs are stored in <strong>_config.yml</strong> (If you’re not familiar with <strong>yml</strong>, you can just regard it as a simplified version of <strong>json</strong>). The comments there are sufficient for you to understand the usage of each item.</p><p>If you’d like to create your own post now, you can type in</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"post_name"</span></span><br></pre></td></tr></table></figure><p>Then restart the server as before. Now you can see your new post on your blog site.</p><p>If you want to know more about Hexo’s commands (Eg. shorthand of commands), simpliy type in</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> hexo <span class="built_in">help</span></span></span><br></pre></td></tr></table></figure><p>in your <strong>Terminal</strong>. Then help messages will pop out. If this is not enough, you can visit <a href="https://hexo.io/docs/" target="_blank" rel="noopener">Hexo’s Official Documents</a> for more information.</p><h2 id="Link-with-GitHub"><a href="#Link-with-GitHub" class="headerlink" title="Link with GitHub"></a>Link with GitHub</h2><p>It’s necessary for you to have a GitHub account before you can proceed further. Since it’s simple to get a new account on GitHub, I’ll omit the process and suppose that you now have a GitHub account and its name is <em>xxx</em>.</p><h3 id="Create-a-New-GitHub-Repository"><a href="#Create-a-New-GitHub-Repository" class="headerlink" title="Create a New GitHub Repository"></a>Create a New GitHub Repository</h3><p>Login to your GitHub account and create a new repository called <strong>xxx.github.io</strong>. The notation <strong>xxx</strong> here refers to your GitHub account name above.</p><h3 id="Config-Hexo"><a href="#Config-Hexo" class="headerlink" title="Config Hexo"></a>Config Hexo</h3><p>Edit <strong>_config.yml</strong> file under your Hexo directory. Find and change corresponding line as below.</p><figure class="highlight less"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">deploy</span>:</span><br><span class="line"> <span class="attribute">type</span>: git</span><br><span class="line"> <span class="attribute">repository</span>: git<span class="variable">@github</span>.<span class="attribute">com</span>:xxx/xxx.github.io.git</span><br><span class="line"> <span class="attribute">branch</span>: master</span><br></pre></td></tr></table></figure><h3 id="RSA-Key-Pair-Generation"><a href="#RSA-Key-Pair-Generation" class="headerlink" title="RSA Key Pair Generation"></a>RSA Key Pair Generation</h3><p>To successfully connect to the GitHub server via SSH, you need an <strong>RSA key pair</strong> for authorization purpose. It’s quite easy to accomplish on Mac since almost all of modern Macs have already installed tools we need. Open a <strong>Terminal</strong> and type in below commands.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -t rsa -C <span class="string">"any_comment eg. your email address"</span></span><br></pre></td></tr></table></figure><p>Then all the way hit <strong>enter</strong> to apply default settings during the key generation process. During this process, you may be asked to set a password to protect your key pair. You can either ignore it by just hitting <strong>enter</strong> or input a customized password and then hit <strong>enter</strong>.</p><p>If everything works fine, you will find a key pair has been generated under <em>~/.ssh/</em> directory. Open <em>id_rsa.pub</em> with an editor and copy the contents in this file.</p><p>Login to your GitHub account and under your <strong>Account Settings</strong> select <strong>SSH Keys</strong>. Then you just need to enter a customized title for your key and paste what you’ve copied below the title. <strong>Done!</strong></p><p>To test your connection with GitHub, type in below command in your <strong>Terminal</strong>.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh -T [email protected]</span><br></pre></td></tr></table></figure><p>If you’ve set a password before, you’ll be asked to enter the password to allow the program to read the protected private key.</p><p>If everything is OK, now a connection success message will prompt out.</p><h3 id="Deploy-Hexo"><a href="#Deploy-Hexo" class="headerlink" title="Deploy Hexo"></a>Deploy Hexo</h3><p>As long as your connection with GitHub is OK, you can just type in below command within your blog directory to deploy your local site to GitHub every time.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>A <strong>Deployment done: git</strong> information will pop out on your <strong>Terminal</strong>. To double verify it, you can find that your site has been published at <em><a href="https://xxx.github.io" target="_blank" rel="noopener">https://xxx.github.io</a></em> on <em>GitHub Pages</em> tab under your site’s <em>Repository Settings</em>.</p><h2 id="Link-Your-Domain"><a href="#Link-Your-Domain" class="headerlink" title="Link Your Domain"></a>Link Your Domain</h2><p>So far you can reach your own blog site through <em><a href="https://xxx.github.io" target="_blank" rel="noopener">https://xxx.github.io</a></em> address. But it’s always better to have your own domain name linked with your blog site so that you can access your site directly via your own domain (I suppose you’ve already had your own domain. If not, please google it first and config your domain. Believe me, it’s quite easy).</p><p>To accomplish it, you only need to modify <strong>CNAME</strong> file under your blog site’s <strong>source</strong> directory by <strong>adding your domain name into this file</strong>.</p><p>OK! Everything’s in position. Now type your associated domain in your browser’s address field and hit <strong>enter</strong>. You can see your own blog site right there in front of your eyes.</p><p>Congratulations!</p><h2 id="Annex"><a href="#Annex" class="headerlink" title="Annex"></a>Annex</h2><p>Above are only basic confurations for our Hexo site. If you want to make it elegant and attractive, there is little you can do with only Hexo core module. But fortunately, thanks to its well designed core module, Hexo supports various kinds of plugins and themes to make itself look better and perform better.</p><p>Here I want to introduce two plugins and one theme that I’ve used in my own blog.</p><h3 id="Theme-Yelee"><a href="#Theme-Yelee" class="headerlink" title="Theme: Yelee"></a>Theme: Yelee</h3><p>There’re many themes available on <a href="https://hexo.io/themes/" target="_blank" rel="noopener">Hexo’s official site</a>. One of which I’m fond of is desinged by a Chinese front-end developer <strong>MOxFIVE</strong> and it’s called <strong>Yelee</strong>. Its elegant and modern design have conquered me completely. You can check it <a href="https://github.com/MOxFIVE/hexo-theme-yelee" target="_blank" rel="noopener">here</a>. </p><h3 id="Plugin-Hexo-generator-feed"><a href="#Plugin-Hexo-generator-feed" class="headerlink" title="Plugin: Hexo-generator-feed"></a>Plugin: Hexo-generator-feed</h3><p>It’s a plugin written in NodeJS to help you generate <strong>RSS</strong> for your site. Basically it’s convenient to install this plugin with <em>npm install</em> command and config it in your site’s <em>_config.yml</em> file like below.</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">plugins:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">hexo-generator-feed</span></span><br><span class="line"></span><br><span class="line"><span class="attr">feed:</span></span><br><span class="line"><span class="attr">type:</span> <span class="string">atom</span></span><br><span class="line"><span class="attr">path:</span> <span class="string">atom.xml</span></span><br><span class="line"><span class="attr">limit:</span> <span class="number">20</span></span><br></pre></td></tr></table></figure><p>Most tutorials on the Internet which introduce the installation of Hexo’s RSS tool tell you that that’s all you need to do to generate your site’s RSS feed. But acutally it’s not the case for me. If you deploy your site after above operations, you probably can’t see what you desire. Your feed won’t appear at <em>your_site/atom.xml</em> in this way.</p><p>To solve this problem, you need to add your Hexo plugin into your site’s <strong>package.json</strong> file and put it under <em>dependencies</em> item like this.</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">"dependencies": {</span><br><span class="line">"hexo-generator-feed": "^1.0.3"</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>You need to find your plugin’s version manually. Usually it’s the same as its latest version on GitHub if you install it with <em>npm</em> lately.</p><p>Besides, the original <em>package.json</em> file is <strong>read-only</strong> for other users except for its <strong>owner</strong>. And, of course, you are not its owner by default. So in order to make a change on this file, you may have to change its owner to you first with <em>chown</em> command.</p><p>Now regenerate your site and deploy it again, you will find your RSS feed has been generated successfully.</p><h3 id="Plugin-Hexo-generator-sitemap"><a href="#Plugin-Hexo-generator-sitemap" class="headerlink" title="Plugin: Hexo-generator-sitemap"></a>Plugin: Hexo-generator-sitemap</h3><p>It’s also a NodeJS plugin but aimes to generate a sitemap for you. The installation process is analogous with Hexo’s feed plugin except that it doesn’t need to modify your <em>package.json</em> file but only needs to add it into your site’s <em>_config.yml</em> file like below.</p><figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">plugins:</span></span><br><span class="line"><span class="bullet">-</span> <span class="string">hexo-generator-sitemap</span></span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>That’s all for what I want to say about Hexo. I’m still new here and I’m continuing my exploration on Hexo. I wish you, who have or haven’t seen this passage, all the best in establishing your own Hexo blog sites and be excited on what you’ve acheived.</p>]]></content>
<summary type="html">
<p>This is the first time I write my own blog on my own site. I’m inspired by those who use <strong>Hexo</strong> to establish their own wonderful blog sites. Its flexibility and programmer-friendly paradigm deeply attracts me so that I decide to make a site of my own with Hexo.</p>
</summary>
<category term="Technology" scheme="http://blog.lyndonli.com/categories/Technology/"/>
<category term="Blog" scheme="http://blog.lyndonli.com/categories/Technology/Blog/"/>
<category term="Hexo" scheme="http://blog.lyndonli.com/tags/Hexo/"/>
<category term="GitHub" scheme="http://blog.lyndonli.com/tags/GitHub/"/>
</entry>
<entry>
<title>Hello World</title>
<link href="http://blog.lyndonli.com/2015/12/14/hello-world/"/>
<id>http://blog.lyndonli.com/2015/12/14/hello-world/</id>
<published>2015-12-14T15:23:47.000Z</published>
<updated>2015-12-14T15:23:47.000Z</updated>
<content type="html"><![CDATA[<p>Welcome to <a href="http://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="http://hexo.io/docs/" target="_blank" rel="noopener">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="http://hexo.io/docs/troubleshooting.html" target="_blank" rel="noopener">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="noopener">GitHub</a>.</p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure><p>More info: <a href="http://hexo.io/docs/writing.html" target="_blank" rel="noopener">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p>More info: <a href="http://hexo.io/docs/server.html" target="_blank" rel="noopener">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure><p>More info: <a href="http://hexo.io/docs/generating.html" target="_blank" rel="noopener">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>More info: <a href="http://hexo.io/docs/deployment.html" target="_blank" rel="noopener">Deployment</a></p>]]></content>
<summary type="html">
<p>Welcome to <a href="http://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="http://hexo.io
</summary>
</entry>
</feed>