tensorflow-第四弹

前面三个部分都是关于深度学习,tensorflow框架的东西,再接着深入就要涉及到了深度学习更加深层次的内容了,涉及到深度卷积和深度置信网络了,这一部分涉及的原理比较多,因此相对来说会比较复杂,而为了进行深度学习我们必须在另一个部分进行比较深入的研究,那就是数据,深度学习必然伴随这大数据,可以说没有大数据就没有深度学习。我们接下来学习跟数据有关的一些内容,主要就是针对数据获取和数据预处理两个部分。
我们知道网络上的数据很多,也很复杂,有各种标签的数据,这些数据有的对我们有用,而有些数据对我们来说就没有什么意义了,如何快速获取大量对我们来说有意义的数据是我们亟待解决的问题,所以这一部分看似和tensorflow没有什么关系,实际上它是机器学习的基础,所以我们再后面一部分会着重的介绍数据获取和数据处理,直到这一部分学习完才会继续真正的深度学习方法的学习和介绍,好了废话不多说。
我们这次主要讲爬虫,什么是爬虫或者说robot,简单的来说就是能够从网上获取数据的工具,而这样的工具都是通过代码实现的,我们把这样的代码或者工具就叫做爬虫。好了介绍完爬虫我们基本上就知道爬虫是干什么的了,没错爬虫就是替代我们从网上获取数据的,那么它的工作原理是怎么样的呢,我们会通过两个网站的分析进行介绍:

阅读全文

tensorflow-第三弹

好不容易可以发第三弹了,在这一回的学习和编码过程中遇到了很多的坑,当然咯,要做机器学习特别是用一门自己并不是很熟悉的语言去做,总归是有一些坑的,不过好在这些坑在这两天都被我填上了,所以现在也可以记录一下。在上一回的学习过程中我们通过tensorflow的简单逻辑回归的拟合说明了tensorflow的图的构造以及启动的过程,这一次准备做一些更加深入的学习,采用BPNN对MNIST手写数字库进行分析和识别。首先介绍一些MNIST库,如果做过机器学习和手写数组识别的人入门必备的一个数据,数据包括6w个28×28大小的手写数字图标和它们的label,以及1w个测试样本。数据全部以二进制形式存储,所以读取很方便,另外在tensorflow的教程中已经存在了MNIST数据读取的功能,极大的简化了我们的处理,让我们专注于神经网络结构的设计。
下面我们分析一下神经网络结构的设计,我们做的是最简单的BP神经网络,也就是三层前馈神经网络,其结构如图:

上图是一个比较好的三层BP神经网络的示意图,从图上可以看到假设样本有 $x={x_1,x_2….x_N}$ N个变量,则输入为一个N维的向量,关于这一点在做多波段图像的同学们可能需要注意一下,到底什么是样本什么是变量搞清楚。这N个变量作为输入层,然后输入层经过如下运算得到隐含层:
$h=W\cdot x+b$ (1)
隐含层的神经元的数目是给定的,为什么要给定隐含层的神经元数目,这个确定了隐含层的复杂度,理论上来说隐含层的神经元数目越多其拟合效果应该更好,但是隐含层神经元数目太多会导致两个问题(1)参数数目成指数增长,需要更多的训练样本才能进行充分学习;(2)出现对结果变化影像很小的无效参数,这些参数对结果的贡献很小,甚至可以忽略不计,除了增加计算复杂度再没有别的作用,因此应该去掉这些参数。所以合理的设置隐含层个数的问题也是神经网络调参过程的一个重要问题。
在(1)式中我们得到了隐含层的输入,实际上对于隐含层,其并不是输入等于输出,而是有一个响应函数,将输入从 $[+\infty,-\infty]$ 转换到[-1,1]这种转换有很多种,一般来说满足条件的转换有,sigmoid转换,tanh转换,relu转换等,采用这些转换是因为这些转换有一些特点,1.单调,2.在区间内处处可导;在选取这些函数后我们得到隐含层的输出,然后对应隐含层到输出层:
$o=W_2 \cdot h+b$ (2)
然后输出层与label结果比较,通过比较可得误差,然后通过误差调整误差函数 $\Delta$ 然后通过调整得到输出层的 $W_2$ 和 $b2$ 然后得到计算的 $h$和实际 $h$ 之间的差距然后对数据进行调整 $w1$ 和$b1$ 。至于如何调整,一般来说采用的是梯度下降法进行调整,至于为什么能够进行最优的求解,具体的算法分析可以参考其他文档,在这里并不做详细的分析,我的其他博客上有具体的分析。好了,分析完BPNN的原理之后我们看看其实现的代码:

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
import tensorflow as tf
import numpy as np;

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("mnist/MNIST_data/", one_hot=True)

with tf.name_scope('input'):
x = tf.placeholder(tf.float32, [None, 784],name = 'x-input')
y = tf.placeholder("float", [None, 10],name='y-input')
x_image = tf.reshape(x,[-1,28,28,1],name='img')
tf.summary.image('image',x_image,20)


w1 = tf.Variable(tf.truncated_normal(shape=[784,50],stddev=0.1))
init1 = tf.constant(0.1, shape=[50])
b1 = tf.Variable(tf.zeros([50]),
name='biases1')

w2 = tf.Variable(tf.truncated_normal(shape=[50,10],stddev=0.1))
init2 = tf.constant(0.1, shape=[10])
b2 = tf.Variable(tf.zeros([10]),
name='biases2')

hidden1 = tf.nn.relu(tf.matmul(x,w1)+b1)
y_ = tf.nn.relu(tf.matmul(hidden1,w2)+b2)

with tf.name_scope('loss'):
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=y_))
tf.summary.scalar('loss',loss)

with tf.name_scope('accuracy'):
with tf.name_scope('correct-predict'):
correct_predict = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
with tf.name_scope('accuracy'):
accuracy = tf.reduce_mean(tf.cast(correct_predict, "float"))

merged = tf.summary.merge_all()
step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)

with tf.Session() as sess:
writer = tf.summary.FileWriter('tensorflow-start_Log', sess.graph)
sess.run(tf.global_variables_initializer())
for i in range(100000):
batch_xs, batch_ys = mnist.train.next_batch(100)
summary,_=sess.run([merged,step], feed_dict={x: batch_xs, y: batch_ys})
if i%1000 == 0:
train_acc = accuracy.eval(feed_dict={x: batch_xs, y: batch_ys})
print('step',i,'training accuracy',train_acc)
writer.add_summary(summary, i)

阅读全文

tensorflow-第二弹

刚刚研究了一下以前写的tensorflow发现就开了个头,而且都是两个月之前的事情了,现在看起来作为入门介绍起来也是一件比较简单的事情了,这一段时间按照官网的教程也写了一个mnist识别的cnn的例子,当然咯自己也写了一个自己的图像训练的代码,只是目前还没有开始训练……但是按照官网教程写的东西毕竟只是依葫芦划瓢而已并没有深入理解,这一次慢慢去深入理解tensorflow的机制。其实再tenflow的情况下主要分为两个操作,一个是构建Graph,Graph由很多op,和tensorflow构成,好了今天我们从一个简单的线性回归的例子开始进行深入的剖析。
线性回归可以简单的表示为$y=a\cdot x+b$的形式,那么我们在获取了一系列的x,y数据的情况下可以拟合得到线性方程,而线性拟合通常直接求解,但是我们为了说明tensorflow的原理所以强行用梯度下降算法进行迭代拟合求解,废话不多说,下面我们构建起线性拟合的Graph:

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
import tensorflow as tf
import numpy as np
from numpy.random import RandomState

with tf.name_scope('input'):
x = tf.placeholder(tf.float32,name = 'xinput')
y = tf.placeholder(tf.float32,name = 'yinput')
tf.summary.scalar('x',x)
tf.summary.scalar('y',y)

with tf.name_scope('parameter'):
a = tf.Variable(0.0,name='a')
b = tf.Variable(0.0,name='b')
tf.summary.scalar('a',a)
tf.summary.scalar('b',b)

with tf.name_scope('predict'):
y_=a*x+b;

with tf.name_scope('loss'):
loss = tf.square((y_-y),name='loss')
tf.summary.scalar('loss',loss)

with tf.name_scope('train_step'):
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
#tf.summary.scalar('train_step',train_step)

merged = tf.summary.merge_all()
#data prepare
xdata = np.linspace(0,0.5*np.pi,3000)
ydata = 0.4*xdata+0.5;
rdm = RandomState(1)

with tf.Session() as sess:
writer = tf.summary.FileWriter('tensorflow-start_Log', sess.graph)
sess.run(tf.global_variables_initializer())
for i in range(2000):
idx = rdm.randint(0,3000)
summary,_=sess.run([merged,train_step],feed_dict={x:xdata[idx],y:ydata[idx]})
writer.add_summary(summary, i)
a_value,b_value = sess.run([a,b])
print(a_value)
print(b_value)

阅读全文

global rotation average

为了简单就分到数学类中了,主要是说明了如何通过全局旋转矩阵的方法求解出整体的旋转矩阵,为什么要用这样的方法进行求解呢,最主要的原因在于采取此种方法进行求解不需要进行光束法平差就能够求得所有相机的旋转矩阵,这个应该是此类方法的最大的优点了,昨晚看得比较晚,但是基本概念还是懂了,今天结合OpenMVG进行一个记录,同时更进一步的加深理解,好了废话不多说,下面我们进入正题
做过CV或者摄影测量的人都应该知道,我们做摄影测量也好,做CV或者SFM也好,前面的步骤都大概是,影像获取,影像匹配,求影像间的相对变换关系,相信上面所说的几个步骤只要是做过影像匹配的人很简单就能够明白,现在我们面临最大的问题在于两两之间的相对关系求解出来之后怎么办,实际上面对这样的情况有两种方案,第一种就是类似摄影测量中相对定向的方法,连续进行模型之间的相对定向,将模型坐标统一起来,此外就是我们今天介绍的方法了,下一次再详细探讨在机器视觉和摄影测量中通过连续法进行定向的区别与联系
好了相信看这篇文章的大多是具有一定的基础,所以有关基础的东西就不讲了,直接从旋转矩阵开始,我们假设全局的旋转矩$R=[R_1,R_2,…R_i,R_j]$ 而任意两个旋转矩阵之间的关系我们可以看作是求解的相对元素,则有:$R=R_iR_j^{-1}\forall \in \varepsilon$其中 $\varepsilon$为影像集,从上式我们可以建立一个全局的旋转矩阵和相对的旋转矩阵之间的管理,当然对于一个闭环来说,这样的一个集合我们又称之为特殊正交组$SO(3)$,为什么要提出这个概念为什么要到这提出正交组这个概念,因为正交组有很多特性,根据这些特性可以进行进一步的推导。有了上面的相对变换的旋转矩阵和绝对变换的旋转矩阵之后根据下式就可以进行求解了
$arg min_u \sum_1^n(1)$
(1)式为求解的最小二乘的形式,求解上式的极小值则可以求解出全局的旋转矩阵,当然通过初始值,然后进行迭代求解。然而这样的求解过程依然稍显得有些复杂,我们进一步简化,不考虑旋转矩阵直接从旋转角出发进行考虑,假设全局的旋转角为$w={w_1,w_2,…w_i..w_j…}$ 则相对的旋转角与全局旋转角的关系可以表示为:
$w=w_i-w_j=[…-1,…,1…]\cdot w_g(2)$
则根据式(2),可以列出全局的旋转角和相对旋转角之间的关系为:
$Aw_g=w_r(3)$
式(3)中A为值为0,-1,1的系数矩阵,通过求解上式可以得到旋转角进而得到旋转矩阵,总的来说就是这样,至于整个求解步骤哦我引用论文[1]中的求解步骤:
enter image description here
当然论文中对求解方法进行了一定的改进,不过不管怎么改,总的来说还是万变不离其宗,至于各种的改进算法有兴趣可以自己进行更加深入的了解和学习,下面我们分析一下OpenMVG的做法。
openMVG中计算GlobalRotation的代码为

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
/// Compute from relative rotations the global rotations of the camera poses
bool GlobalSfMReconstructionEngine_RelativeMotions::Compute_Global_Rotations
(
const rotation_averaging::RelativeRotations & relatives_R,
Hash_Map<IndexT, Mat3> & global_rotations
)
{
if(relatives_R.empty())
return false;
// Log statistics about the relative rotation graph
{
std::set<IndexT> set_pose_ids;
for (const auto & relative_R : relatives_R)
{
set_pose_ids.insert(relative_R.i);
set_pose_ids.insert(relative_R.j);
}

std::cout << "\n-------------------------------" << "\n"
<< " Global rotations computation: " << "\n"
<< " #relative rotations: " << relatives_R.size() << "\n"
<< " #global rotations: " << set_pose_ids.size() << std::endl;
}

// Global Rotation solver:
const ERelativeRotationInferenceMethod eRelativeRotationInferenceMethod =
TRIPLET_ROTATION_INFERENCE_COMPOSITION_ERROR;
//TRIPLET_ROTATION_INFERENCE_NONE;

system::Timer t;
GlobalSfM_Rotation_AveragingSolver rotation_averaging_solver;
const bool b_rotation_averaging = rotation_averaging_solver.Run(
eRotation_averaging_method_, eRelativeRotationInferenceMethod,
relatives_R, global_rotations);

std::cout
<< "Found #global_rotations: " << global_rotations.size() << "\n"
<< "Timing: " << t.elapsed() << " seconds" << std::endl;


if (b_rotation_averaging)
{
// Compute & display rotation fitting residual errors
std::vector<float> vec_rotation_fitting_error;
vec_rotation_fitting_error.reserve(relatives_R.size());
for (const auto & relative_R : relatives_R)
{
const Mat3 & Rij = relative_R.Rij;
const IndexT i = relative_R.i;
const IndexT j = relative_R.j;
if (global_rotations.count(i)==0 || global_rotations.count(j)==0)
continue;
const Mat3 & Ri = global_rotations[i];
const Mat3 & Rj = global_rotations[j];
const Mat3 eRij(Rj.transpose()*Rij*Ri);
const double angularErrorDegree = R2D(getRotationMagnitude(eRij));
vec_rotation_fitting_error.push_back(angularErrorDegree);
}

if (!vec_rotation_fitting_error.empty())
{
const float error_max = *max_element(vec_rotation_fitting_error.begin(), vec_rotation_fitting_error.end());
Histogram<float> histo(0.0f,error_max, 20);
histo.Add(vec_rotation_fitting_error.begin(), vec_rotation_fitting_error.end());
std::cout
<< "\nRelative/Global degree rotations residual errors {0," << error_max<< "}:"
<< histo.ToString() << std::endl;
{
Histogram<float> histo(0.0f, 5.0f, 20);
histo.Add(vec_rotation_fitting_error.begin(), vec_rotation_fitting_error.end());
std::cout
<< "\nRelative/Global degree rotations residual errors {0,5}:"
<< histo.ToString() << std::endl;
}
std::cout << "\nStatistics about global rotation evaluation:" << std::endl;
minMaxMeanMedian<float>(vec_rotation_fitting_error.begin(), vec_rotation_fitting_error.end());
}

// Log input graph to the HTML report
if (!sLogging_file_.empty() && !sOut_directory_.empty())
{
// Log a relative pose graph
{
std::set<IndexT> set_pose_ids;
Pair_Set relative_pose_pairs;
for (const auto & view : sfm_data_.GetViews())
{
const IndexT pose_id = view.second->id_pose;
set_pose_ids.insert(pose_id);
}
const std::string sGraph_name = "global_relative_rotation_pose_graph_final";
graph::indexedGraph putativeGraph(set_pose_ids, rotation_averaging_solver.GetUsedPairs());
graph::exportToGraphvizData(
stlplus::create_filespec(sOut_directory_, sGraph_name),
putativeGraph);

using namespace htmlDocument;
std::ostringstream os;

os << "<br>" << sGraph_name << "<br>"
<< "<img src=\""
<< stlplus::create_filespec(sOut_directory_, sGraph_name, "svg")
<< "\" height=\"600\">\n";

html_doc_stream_->pushInfo(os.str());
}
}
}
return b_rotation_averaging;
}

阅读全文

AutoCAD开发学习1

刚来干活公司就给分配了任务,研究AutoCAD二次开发,希望能够开发出一个应用工具满足应用需求,作为熟悉C#的新员工,我当然责无旁贷的承担起了研究AutoCAD二次开发的重任,由于是二次开发,所以CAD环境和二次开发环境的配置就显得比较重要了,公司用的是AutoCAD2012 开发IDE为vs2010,首先安装开发环境,在此基础上安装CAD软件,网上关于CAD二次开发的教程有很多,其中有很多跟我们需求的环境不符合,也有一些很好的代码,在这里我就不详细介绍,这里主要介绍一下自己的环境配置和开发第一个HelloWorld程序的过程。
闲话不多说了,首先介绍一下CAD二次开发的一个基本概念,我们做二次开发一般都是组件式的开发方式,通过向CAD环境添加组件完成我们需要的功能,开发的主要方式是通过开发DLL然后附加到主程序的过程。在了解这个的基础上我们再来进行二次开发目标就比较明确了,首先建立一个DLL工程,然后通过AutoCAD的SDK完成我们需要的相应功能,最后通过组件形式将DLL加载到AutoCAD中,这样我们的二次开发的功能就能够使用了。
下面我们讲讲AutoCAD二次开发的一些方法,首先搭建开发环境,这个是最重要也是最基础的一步,搭建基础环境完成之后就可以进行开发了,首先进行HelloWorld程序的开发,此类开发可以说是所有语言入门必学的教程了,有了一个新世界我们才是这个世界的主宰,所以我们先向这个世界说Hello吧。首先打开C#程序,建立一个c#的动态链接库,添加上一下几个库的引用其中库分别为:<!><>,添加上这几个库之后就可以控制AutoCAD的各个菜单和主界面了,我们现在想再CAD的命令窗口输出HelloWorld的字符串。首先我们需要给出一个Command命令绑定,代码为:

1
[CommandMethod("HelloWorld")]

阅读全文

python解析二进制文件

昨天做了一个小工具将数据转换为了二进制的文件。深度学习工具用的是tensorflow,所以考虑如何在python下读取二进制文件,由于对Python并不是很熟,因此花了一番功夫。顺便记录一下以后有同样的处理也能够进行方便的处理。
好了废话不多说,直接上代码:

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
import os
import struct
import random

datapath = '/home/wuwei/Picture/data.bin'
block_size = 4*50*50

def GetRandomROIs(int randRoiNumbers):
input = open(datapath, 'rb')
size = os.path.getsize(datapath)
number = size/block_size

randomMax = number - randRoiNumbers-1
randomMin = 1
if(randomMax<randomMin)
return null

int stPos = random.randint(randomMin, randomMax)*block_size;
input.seek(stPos*block_size)
binnarydata = input.read(randRoiNumbers*block_size)

fmt = '<%di' %(randRoiNumbers*block_size/4)
data = struct.unpack(fmt,binnarydata);
input.close()
return data

阅读全文

图像规整化处理

    前段时间玩了一下tensorflow觉得用起来还挺方便的,所以准备做一个自己的机器学习的识别系统,正好又一个网络摄像头,也更加方便了,而搞机器学习的首要步骤大概就是样本的选择吧,似乎用Python做图像的处理不太好搞,正好自己又比较熟悉用C++开发,借着这个机会做了一个小工具用以选取训练样本。

阅读全文

故事

有很多故事我想对人诉说,可是!
没有人愿意听~
所以,我将它们说给风听。
希望风儿能够带着它们远去,去到遥远的地方。
风儿说,你的故事太重,重到我无法带走。
我只好,将它们刻在石头上。
希望,有一天能被人看见。
也许,再很久很久以后,有人看见刻着我故事的石头,
然后又随手将石头丢弃再荒野。
时间渐渐过去,荒野成了绿洲。
石头下也长出了青草,长出了快乐的青草。
草儿们在风中舞蹈,在雨中歌唱;
草儿们诵读着我刻在石头上的故事。
草儿们不懂那些故事,可是,草儿们为什么会掉下泪来。
而我,却躲再人群中,等待这那些即将到来和终将远去……

阅读全文

温度变化统计

前一段时间还在学校的时候下载了一些气象方面的数据,数据为气象站点的年平均气温,当然咯,相比于美国,我们国家的气象站点分布得没有那么密集。也可能是公布的数据没有那么密集,我收集了近十年来的平均温度数据,数据为cvs格式,包含的信息有:1.地名;2.地点编号;3.温度(美式单位);4.温度(摄氏度)。由于每一年的数据都有差异(存在着某些站点缺失的问题)
所以在进行统计的时候存在一定的问题,通过一些努力,还是完成了对于某个城市的温度统计,下面就如何使用R语言完成部分统计和制图工作进行一些介绍,同时也作为一个记录。
好了由于一共获取了十年的数据,因此需要将数据进行读取,对于CVS格式的数据,R语言是可以直接读取的,这一点类似于Matlab,直接通过Read函数读取数据,整个数据读取的代码为:

1
2
3
4
5
6
7
8
9
10
temperature1 <-read.csv('/home/wuwei/Data/temperature/stations (0).csv')
temperature2 <-read.csv('/home/wuwei/Data/temperature/stations (1).csv')
temperature3 <-read.csv('/home/wuwei/Data/temperature/stations (2).csv')
temperature4 <-read.csv('/home/wuwei/Data/temperature/stations (3).csv')
temperature5 <-read.csv('/home/wuwei/Data/temperature/stations (4).csv')
temperature6 <-read.csv('/home/wuwei/Data/temperature/stations (5).csv')
temperature7 <-read.csv('/home/wuwei/Data/temperature/stations (6).csv')
temperature8 <-read.csv('/home/wuwei/Data/temperature/stations (7).csv')
temperature9 <-read.csv('/home/wuwei/Data/temperature/stations (8).csv')
temperature10 <-read.csv('/home/wuwei/Data/temperature/stations (9).csv')

阅读全文

不读书(三)

读《呼兰河传》

时代的凄凉,命运的无奈,还有深入骨髓的孤独。这是我看了这本书后三个最深刻的感受了。八十年前的东北小山村,一如当时所有中国的其他村庄,所拥有的只有寒冷,贫瘠和麻木。在时代的大潮中,所有人都显得那么微不足道。个人的命运是如此额卑微与弱小,在时代的洪流中的普通人连一朵浪花都不会激起,人们在这洪流中苦苦挣扎也不过是为了一个又一个看不到希望的明天而已。
呼兰河的村民们迷信“跳大神”,迷信鬼神之说,一如当时中国所有生活再底层的人民。我想大家相信的恐怕不只是鬼神之说,更是心中的敬畏和希望,为了希望不惜付出巨大的代价,为了希望可以做任何能够做到的事情。然而鬼神终究无法带来希望,美好的愿景破灭后只有残酷的现实。如今的我们看起来他们似乎是如此的愚昧无知,我们尽可以嘲笑他们的麻木,他们的无知,他们的咎由自取。可是伸出时代中的他们又该如何呢?他们已经做了当时他们应该做的和能够做的,这是时代的悲哀,而那一家不过是其具像化的表现而已,可以是这一家也可以是那一家。
呼兰河的村民是善良的,看到那些倒在坑中的人和马会不计得失的去帮助他们,呼兰河的村民又是如此的自欺以至于宁愿将大泥坑留着作为吃便宜病猪肉的借口。这坑大概就是无奈屈服于现实的遮羞布吧,谁都不愿意面对血淋淋的现实,将真相强行掩盖于黑暗之中才符合所有人的利益。可是我们就能够依此嘲笑他们么?恐怕是不能的,物质生活的丰富掩盖了人性的软弱。人性的进化是及其缓慢的,人性是禁不起考验和诱惑的……呼兰河的现就就是我们的过去,时代的进步,科技的发展是极快的,然而处于不同时代的我们也许会面临着同样的选择和境遇,我们会如何选择呢?我们会不会比呼兰河村民做的更好,我们能够看到遥远的未来么?
全书以作者回忆的形式描述,也许是因为回忆太遥远而记忆不够真切,又获取是作者刻意将情感疏远而得以保全记忆的真相。全书以一个旁观者的语调对呼兰河上所发生的事情以一种近乎冷酷的态度进行了描述。从字里行间我却看到了一种深入骨髓的孤独,一种对世界不理解的孤独,一种不被世界理解的孤独,一种不知何去何从的孤独。虽然并不缺少关爱,可是生活再成人世界的孩童的疑惑又有谁会去倾听,又有谁会去解释呢。作者回忆往事时那种旁观者的游离与疏远让我不寒而栗。
看惯了张爱玲,林徽因便误以为民国便如人间四月天一般,只有西湖的雨,情人的泪和氤氲在美酒和咖啡香味中的别离。历史总是惊人的相似,历史总是惊人的凄凉,不知道“易子相食”背后是多少的凄凉与无助。史家们总喜欢以春秋笔法去略过那些沉默的大多数,总是不吝笔墨的去描述,去赞美那些王侯将相,却让那些沉默的大多数人渐渐被历史遗忘成为历史的尘埃。

阅读全文