Kaka Chen 2017-06-15T04:55:06+00:00 silver.accc@gmail.com Python numpy 2017-06-15T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/06/Python Numpy Numpy Module

Numpy的核心对象是均匀的齐次多维数组

Numpy的维度dimension叫做axes,其中axes的数量叫做rank

称为ndarray,也可以叫做array,但是与标准Python库中的array不同。

ndarray的属性有以下:

  • ndarray.ndim:array的维度axes的数量,即rank
  • ndarray.shape:array的维度,返回一个tuple,表示array中每个维度的大小,比如一个n行m列的矩阵,shape值为(n, m) ,因此shape值的长度就是rank,也就是ndarray.ndim
  • ndarray.size:array中所有元素的个数,等于shape中各个值的乘积
  • ndarray.dtype:array中元素的类型
  • ndarray.itemsize:array中每个元素占的byte数
  • ndarray.data:保存着array真实元素的buffer,通常不需要

Array创建

可以通过一个通常的Python list或者tuple创建,如:

>>> a = np.array([1,2,3])
>>> a
array([1, 2, 3])

注,需要以list作为单个参数,传入,而不是传入若干个数字参数:a = np.array([1,2,3]) # wrong

也可以用zeros或者ones创建全是0或者全是1的array:


>>> np.zeros( (3,4) )
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.int16 )                # dtype can also be specified
array([[[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]],
       [[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) )                                 # uninitialized, output may vary
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

或者通过arange函数创建序列的array

>>> a = np.arange(6)                         # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3)           # 2d array
>>> print(b)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4)         # 3d array
>>> print(c)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]
 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

基本操作

>>> a = np.array( [20,30,40,50] )
>>> b = np.arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False], dtype=bool)

其中+=*=操作都是在原有基础上修改array对象,而不是创建一个新的对象


通用函数

提供sin, cos, exp等数学函数,称为universal functions(ufunc)

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> np.sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([ 2.,  0.,  6.])

索引、分片和迭代

对于一维的array来说,可以被索引、分片和迭代,就像普通的Python list

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0

修改array的shape

假设有一个(3,4)的array,如何修改其shape,有如下办法:

>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.shape
(3, 4)

# 扁平化 

>>> a.ravel()  # returns the array, flattened
array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])

# reshape()命令

>>> a.reshape(6,2)  # returns the array with a modified shape
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])

>>> a.T  # returns the array, transposed
array([[ 2.,  4.,  8.],
       [ 8.,  5.,  9.],
       [ 0.,  1.,  3.],
       [ 6.,  1.,  6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)

将不同的Array粘到一起

不同维度的array可以被粘在一起:vstack()命令

>>> c
array([False, False,  True,  True], dtype=bool)
>>> d
array([False,  True,  True,  True], dtype=bool)
>>> np.vstack((c,d))
array([[False, False,  True,  True],
       [False,  True,  True,  True]], dtype=bool)
>>> e = np.vstack((c,d))
>>> e.ravel()
array([False, False,  True,  True, False,  True,  True,  True], dtype=bool)
# 这个操作可以用到cifar10的eval里

将一个Array拆分成若干个小arrays

可以指定维度来拆分成特定个数的arrays

>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[ 6.,  1.,  0.,  1.,  0.,  6.,  4.,  0.,  7.,  6.,  6.,  2.],
       [ 9.,  1.,  8.,  5.,  0.,  1.,  6.,  1.,  3.,  4.,  4.,  8.]])
# 生成一个(2,12)的array
>>> np.hsplit(a, 3)
[array([[ 6.,  1.,  0.,  1.],
       [ 9.,  1.,  8.,  5.]]), array([[ 0.,  6.,  4.,  0.],
       [ 0.,  1.,  6.,  1.]]), array([[ 7.,  6.,  6.,  2.],
       [ 3.,  4.,  4.,  8.]])]
# 生成三个(2, 4)的array
>>> np.hsplit(a, (3, 4))
[array([[ 6.,  1.,  0.],
       [ 9.,  1.,  8.]]), array([[ 1.],
       [ 5.]]), array([[ 0.,  6.,  4.,  0.,  7.,  6.,  6.,  2.],
       [ 0.,  1.,  6.,  1.,  3.,  4.,  4.,  8.]])]

Copy和Views

当使用和操作时,有时会将array复制到一个新的array里,但有时仅仅是做了引用:

# 这里b和a是同一个对象的不同名称而已

>>> a = np.arange(12)
>>> b = a            # no new object is created
>>> b is a           # a and b are two names for the same ndarray object
True
>>> b.shape = 3,4    # changes the shape of a
>>> a.shape
(3, 4)

View方法创造一个新的对象,其中数据一致

>>> c = a.view()
>>> c is a
False
>>> c.base is a                        # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6                      # a's shape doesn't change
>>> a.shape
(3, 4)
>>> c[0,4] = 1234                      # a's data changes
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

将一个array做切片操作

>>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
>>> s[:] = 10           # s[:] is a view of s. Note the difference between s=10 and s[:]=10
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

Deep Copy

>>> d = a.copy()                          # a new array object with new data is created
>>> d is a
False
>>> d.base is a                           # d doesn't share anything with a
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])
]]>
太平洋的风——记台湾行 2017-05-04T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/05/太平洋的风——记台湾行 前言

从台湾回来已经一个月了,终于有时间和兴趣开始写一写那一个礼拜旅行的故事,我一直觉得我是一个很好的旅行策划师,却不是一个很好的游记写手,因为很多感动和体会并不是我原先计划着的,而是各种旅行路上的邂逅和偶遇。

很早以前就拜读过廖信忠先生写的很多关于台湾的奇闻异事,也听很多朋友聊起过他们所到过的台湾,但总归给我一种盲人摸象的感觉。因此记录一些我在台湾的关键词,谨以纪念这段难忘的旅程。


从杭州到台湾

这段旅程打开始就让我觉得不一般,我这辈子第一次买错了飞机票。傻乎乎的我站在出发航站楼里听东航小哥告诉我“您的机票是昨天”的时候,似乎一点都不吃惊,仿佛已经预感到这种坑爹事会降临一样。我熟练地在柜台买了一张最近的直飞台北的机票,出了一笔大血,但也很幸运地坐上了著名的Hello Kitty航班。

飞机上的便当味道不错,有台湾当地的报纸看,杭州到台北约一小时十五分钟的行程过的很快,感觉一转眼就穿越了这湾被誉为国之殇的海峡,踏上了台湾的土地。

我一直说很遗憾我们生活的世界里没有时光机,我无法回到过去去亲眼看看汉唐宋元明清的故事,但是很激动,我至少能回到中华民国看看。

台北桃园机场非常巨大,是许多游客来台湾的第一站,我在这买了悠游卡、拿了随行Wifi、取了现金、盖了入关章,开始了台湾之行。


台铁和捷运

台湾的有轨交通非常发达,几乎每个我所到的城市都通了高铁,几个主要城市内也都有很发达的捷运网,出行非常方便。台湾的城际铁路分为台铁和高铁,类似于我们的动车和高铁,我从台北往返高雄坐的就是这个,约2小时不到的车程。而台湾把地铁称为捷运,在台北和高雄两大城市里,几乎遍布了捷运车站,尤其是台北更是密集,印象中相邻最近的两个站不超过400米,直达全市所有值得去的旅游景点。

台湾的城市带主要集中于岛内西侧,而台北北投到高雄左营的高铁线路无疑是最繁忙的。在高铁上我选了一个邻窗的位子,看着外面一个个熟悉的地名一划而过:桃园、板桥、新竹、台中、彰化、嘉义、台南、高雄。

其实我并没有在高雄市多做停留,因为一来是不想让太多行程中出现赶路的桥段,二来是高雄作为一个港口工业城市,虽然有自己独特的味道,但似乎并不是我的菜。所以我只是趁着换乘的时候,从左营坐捷运去了趟著名的高雄美丽岛捷运站,近距离看了看著名的光之穹顶


垦丁

其实整个旅行从一开始的环岛游到最后的南北游,被我精简地只剩下了两个地方:台北和垦丁。我放弃了曾经教科书中吹的最凶的台湾第一景点日月潭和阿里山,也放弃了很多好朋友吐血推荐的最美东海岸花莲,只为了能在垦丁多呆上几天,静静地看着海发呆,然后去潜个水。事实上,垦丁也确实没有让我失望。

垦丁位于台湾屏东县恒春镇,在当地人眼中,在没有那么多游客涌入之前,这就是一个没什么人烟的小渔村,以至于当时的民国政府还把一个核电站选址在了这,只要一抬头就能看到矗立在海滩边的两个核电站圆形穹顶,我猜这也是全世界唯一一座位于旅游热区的核电站了吧。

垦丁的海是那种很温柔的海,风不大,浪也不大,沙滩非常舒服。和其他著名的海滩相比,垦丁很有趣的一点是,蓝色的大海之上,是绿色的远山。我们步行上去过雾社公园,虽然没看到大海也没看到梅花鹿,但是确实是一个很安静的公园,只有零星几个当地人在那边散步,几乎没人说话,就是坐在草坪上发呆。

猫鼻头公园是我觉得垦丁看海最美的地方,视野非常开阔,一侧是巴士海峡,一侧是台湾海峡,另一侧是太平洋。蓝色的海面翻起白色的浪花打在黑色的岩石上。

鹅銮鼻公园是垦丁的地标,白色的灯塔是垦丁整个地方的缩影。

我很喜欢的电影《海角七号》的故事就发生在恒春镇,我们骑着当地随处可见的电车一路驰骋来到了恒春镇上。“海角七号”四个字的白色墙就在阿嘉的家边上。

垦丁给我另一个印象就是,海上活动真便宜,几乎500块人民币就能潜水了,很遗憾没能玩到其他游戏,不过也算不枉此行。


台北

作为台湾第一大城市,台北已经其下属的新北、北投、桃园城市带,几乎覆盖了整个台湾岛北部。行走在台北街头,能明显感受到新老两片城区的区别,新城区遍布现代化的商业街和办公楼、高档公寓,而老城区就像是90年代的上虞,低矮的民用住房,配上街边连绵不绝的店面,还有呼啸而过的机车。

101大楼一直是台北的地标,而我个人又是一个高楼控,喜欢上各种大楼上打卡,但是似乎每个大楼上俯瞰城市skyline的味道都各有不同。芝加哥是那种高楼墙,紧密地围着密歇根湖,纽约是那种高楼林,密密麻麻地插满了整个曼哈顿,上海是陆家嘴几栋高楼不分伯仲,然后一点点坡度下降延伸出去。而台北则像是一张披萨饼,整个城市目之所及之处就没能在高度上接近101的,而101恰似是这张巨大的披萨饼上的一根蜡烛。

很遗憾没去象山拍摄出最佳的101全景,只能从信义坊出来找了个角度拍了一下。

从101出来还去了著名的信义坊诚品书店,环境极佳,现在这种高颜值的书店在世界各地都越来越有市场。


自由广场

台北自由广场可能是最著名的一个景点,布局像极了北京天安门广场,只不过没那么大。白底蓝瓦的建筑满满的是国民党的味道。中心轴的两侧是国家大剧院和国家音乐厅,是仿清宫风格。

中正纪念堂位于自由广场的最东面,完完全全是仿照着南京中山陵来建的,只不过一个圆一个方。一般来说中国人讲究坐北朝南,而蒋先生却选了坐东望西,一开始我也没想到这是为何,后来反应过来,西面,那是大陆啊。

纪念堂内部跟美国林肯纪念堂布局类似,很幸运在整点的时候看到了卫兵交接仪式。相比于解放军礼仪队而言,国军的仪式显得复杂而繁重,在纪念堂里前前后后连踏步带比划进行了约莫十几分钟。


九份

台北到九份约40公里,坐九份专线过去要一小时30分钟,而且因为我不想提着行李到处走,只能当天去当天回来,因此我一直在犹豫要不要把它加入行程,最后心一横还是去了。因为“九份的咖啡店”和千与千寻的缘故,九份对我来说一直充满了憧憬。从之前的矿山到现在的旅游景点,这里每家店、每道弯似乎都有故事。

从这个观景台看出去能看到远方的基隆港。感觉基隆港整个一个是台湾近代史的见证人,当时的荷兰人从这上来,郑成功从这上来,施琅从这上来,再到后来侵华日军从这上来,国军光复从这上来,民国政府退出大陆后还是从这上来。

当夜幕降临,九份老街上的每一家店前都亮起了灯笼,这才是九份最美的样子。

芋圆是九份最有名的小吃,我在赖阿婆家稍作休息,店面很破旧,却耐不住这是九份最有名的店家。

阿妹茶楼是千与千寻婆婆的小楼的原型,因此九份也是日本游客的最爱,同车的人里,至少有一般是日本游客,九份的店家都会说一口流利的日语。


台北故宫博物院

之前常常会听到一种说法,国府退到台湾时候从北京故宫搬走了大量珍贵的国宝放到台湾保存,让这些国宝躲过了新中国建国后的各种动荡,是中华文明保存和发扬的功臣。另一种说法是,国府在撤退过程中因为保存不当,导致大量国宝破损和遗失,而到了台湾之后,由于保存手段跟不上,又对一些国宝造成了严重损害,是民族的罪人。但是不可否认的是,台北故宫博物院确实是一个值得深度游的地方,因为这里有太多太多原本只在课本上才能看到的珍宝。

一直以来的说法是,台北故宫有三宝:毛公鼎、大白菜和东坡肉,因为展品轮休的关系,东坡肉没有展出,而毛公鼎和五花肉前果然人流络绎不绝。毛公鼎作为中国历史上发现的铭文最多的青铜器,确实很有价值,但是事实上,大白菜的艺术造诣并没有特别高,只是一个很普通的宫廷工艺品而已。

虽然台北故宫有为数不少的玉器、瓷器和青铜器,但是因为当初南逃仓促,带不了很多占地面积大,又很重的藏品,所以在运输过程中,这部分的损伤也最为严重。而台北故宫中最最值得看的,其实是那些便于携带的字画。

著名的半截《富春山居图》

书神王羲之的《快雪时晴帖》

苏轼的《食寒帖》

褚遂良的《倪宽赞》

其实当时在台北故宫的2号馆正在进行法国奥赛博物馆的展览,有很多印象派大师的作品,可惜时间有限没能去参观了。


台湾夜市

一直知道台湾的夜市和小吃非常出名,这趟来也赶了不少夜市,比如士林夜市、宁夏夜市。总的来说感觉是大同小异。呆的时间最久的当然是垦丁大街的垦丁夜市,满大街都是密密麻麻的行人。垦丁夜市最有特点的就是沿街的性感卖酒辣妹,不过说实话,价格明显偏贵,不如街边正经的调酒摊或者酒吧来的好喝实惠。垦丁大街边上的酒吧真的要好评一波,我在别的地方从没买到过那么便宜的酒,而且味道很有垦丁特色。

一些之前台湾传的非常有名的小吃,如大肠包小肠、蚵仔煎对于我来说似乎并没有那么大的吸引力。倒是有两样东西特别让我特别留恋,一个是台湾的奶茶,每一家不起眼的小店似乎都有特色,尝到之前从没品尝过的味道,而且用料,尤其是珍珠和奶都很地道。另一个是臭豆腐,因为是绍兴人的缘故,所以对臭豆腐有特别的爱,而我在台湾几乎吃遍了各种臭豆腐,各种稀奇古怪的做法,回味无穷。


最美的风景是人

之前就接触过好多来自台湾的朋友,他们给我留下最深刻的印象就是特别热情话多,尤其是他们操着台湾腔跟我聊天聊地的时候,特别有趣。这一趟出行,很遗憾的是没能真的沉下来坐着跟当地人聊聊天,但是也接触了很多有趣的人,机场里的台中阿姨、民宿楼下的酒保小哥、潜水店老板、潜水教练老徐、生鱼片排档老板娘、夜市摊主、纪念品店老板娘、特产店老板、总统府导游等等,他们有的是当年撤退国军的后代、有的是原住民、甚至有的是出生在韩国定居在台湾的山东人。我很乐意坐下来听他们聊他们的生活、他们生活的土地、他们对对岸的看法。

总的来说,台湾普通民众,尤其是吃旅游饭的那些人,对于对岸基本都是肯定态度,事实也证明,庞大的陆客群体给台湾带来的旅游收益远远高于东南亚以及日韩游客。而媒体上,至少就我在电视和报纸上看到的,对于对岸的描述也基本符合事实,虽然一些绿营媒体也会吹捧一下香港黄之峰之流来恶心一下大陆,但绝大多数都认识到了两岸之间的经济和军事上的差距,较为客观地描述着一些新闻事件。比较有代表性的就是台湾媒体对于当时是否购置美国F-35战斗机、川普和习近平会面几个热点新闻的评述。


后记

暂且先写到这些,以后有想到的在另为补充。

]]>
Tensorflow:mnist for ml beginners 2017-04-01T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/04/TensorFlow:MNIST For ML Beginners
  • 创建一个softmax回归函数,该函数是通过查看图像中的每个像素从而识别MNIST数字的模型
  • 通过查看上千个例子,来使用TensorFlow训练模型识别数字
  • 用测试数据检验模型的准确性
  • 创建、训练和测试一个多层卷积神经网络来提高结果

  • 载入MNIST DATA

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

    其中mnist是一个轻量级的类,以Numpy arrays的形式保存了训练、验证和测试集

    下载下来的数据集被分成两部分:60000行的训练数据集(mnist.train)和10000行的测试数据集(mnist.test)

    其中每个数据单元都由两部分组成,一张手写数字图片和一个对应标签,将图片设为xs,标签设为ys


    启动TensorFlow InteractiveSession

    TensorFlow依靠高效的C ++后端来进行计算,与此后端的连接称为会话Session, TensorFlow程序的常见用法是首先创建一个图形Graph,然后在会话中启动它。

    时常会用如Numpy这样的python包来进行复杂的操作和计算,比如矩阵的计算,在GPU和分布式计算方式中,会有很高的cost用在数据转移传输上。其中Python代码的角色是创建一个外部计算的Graph,然后指定哪一部分的Graph需要被运行。


    创建Softmax Regression Model

    Placeholders

    开始创建图像输入和输出的节点:

    x = tf.placeholder(tf.float32, shape=[None, 784])
    y_ = tf.placeholder(tf.float32, shape=[None, 10])
    

    这里的x和y_不是特定的变量,而是一个占位符placeholder,之后会询问TernsorFlow计算得到这个输入。

    作为输入的图像x将会是一个由浮点数组成的2d的tensor,我们把它关联到一个[None, 784]的形状,其中784是单个平坦化28×28像素MNIST图像的维度,None指明的是第一个维度,对应于batch大小,可以是任意大小。目标输出类y_同样是由2d的tensor组成,其中每一行都是一个one-hot的10维向量,表示对应的MNIST图像属于哪个数字类(0到9)。

    变量 Variable

    我们现在定义我们的模型的权重W和偏差b。我们可以想象,像其他输入一样处理这些信息,但是TensorFlow有一个更好的处理方式:变量Variable。变量是居住在TensorFlow计算图中的值。它可以被计算使用甚至修改。在机器学习应用中,一般通常将模型参数设为变量。

    W = tf.Variable(tf.zeros([784,10]))
    b = tf.Variable(tf.zeros([10]))
    

    我们通过调用tf.Variable方法传递每个参数的初始值。在这种情况下,我们都将W和b初始化为充满零的tensor。W是784x10矩阵(因为我们有784个输入特征和10个输出),而b是10维向量(因为我们有10个类)。

    在会话session中可以使用变量variable之前,所有变量需要用session来初始化。在此可以一次就对所有已初始化的值关联到变量上。

    sess.run(tf.global_variables_initializer())
    

    预测类和损失函数

    开始使用回归模型,代码只有一行。我们将向量化的输入图像x乘以权重矩阵W,加上偏移量b

    y = tf.matmul(x,W) + b
    

    我们可以很容易地指定一个损失函数。损失Loss表明模型在一个例子上的预测如果出现错误会有多糟糕;我们尽量在例子的训练中减少这样的损失。在这里,我们的损失函数是应用于模型预测的目标和softmax激活函数之间的交叉熵。 在初学者教程中,我们使用了稳定的公式:

    cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
    

    Note that tf.nn.softmax_cross_entropy_with_logits internally applies the softmax on the model’s unnormalized model prediction and sums across all classes, and tf.reduce_mean takes the average over these sums.

    注意tf.nn.softmax_cross_entropy_with_logits内部将softmax应用于模型的非规则化模型的预测和所有类别的总和,tf.reduce_mean将所得的和取平均。


    训练模型

    在定义好模型和训练损失函数之后,可以通过TensorFlow进行训练,因为TensorFlow知道整个计算的图,所以可以使用自动差分来找到每个变量的损失梯度。TensorFlow用很多内建的优化算法,比如可以使用最陡峭梯度下降,将每次的下降step设定为0.5,来降低交叉熵:

    train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
    

    TensorFlow实际做的事情是在计算图中新加了一个操作,这些操作包括计算梯度、更新steps后计算参数、根据参数更新steps。

    返回的操作train_step在运行时将对参数应用梯度下降更新。因此,训练模型可以通过重复运行train_step来实现。

    for _ in range(1000):
      batch = mnist.train.next_batch(100)
      train_step.run(feed_dict={x: batch[0], y_: batch[1]})
    

    我们在每个训练迭代中加载100个训练样例。然后我们运行train_step操作,使用feed_dict替换训练样例的占位符tensorxy_

    评估模型

    整个模型表现如何?

    首先,我们将弄清楚我们是否预测了正确的标签。tf.argmax是一个非常有用的功能,它可以给出沿某个轴的tensor中最高条目的索引。例如,tf.argmax(y,1)是我们的模型认为对每个输入最有可能的标签,而tf.argmax(y_,1)是真实标签。我们可以使用tf.equal比较两个值来检查我们的预测是否符合真相。

    correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
    

    这给了我们一个布尔的列表。为了确定哪个部分是正确的,我们转换为浮点数,然后取平均值。例如,[True,False,True,True]将变为[1,0,1,1],这将变为0.75。

    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    

    最终可以评估得到测试数据的准确性。大概有92%的准确性。

    print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
    

    构建多层卷积网络

    在MNIST上获得92%的准确性是不好的,几乎是尴尬的不能接受的结果。在本节中,我们将修复这个问题,从一个非常简单的模型跳到中等程度:一个小的卷积神经网络。 这将使我们达到约99.2%的准确度。

    权重初始化

    要创建这个模型,我们将需要创建很多权重和偏差。通常应该用少量的噪声来初始化重量以进行对称断裂symmetry breaking,并且防止0梯度。由于我们使用ReLU神经元,所以用一个轻微的初始化它们初始偏移量以避免“死神经元”也是一个很好的尝试。而不是在构建模型时反复执行,我们创建两个方便的函数:

    def weight_variable(shape):
      initial = tf.truncated_normal(shape, stddev=0.1)
      return tf.Variable(initial)
    
    def bias_variable(shape):
      initial = tf.constant(0.1, shape=shape)
      return tf.Variable(initial)
    

    卷积和集合

    TensorFlow还为卷积和集合操作提供了很大的灵活性。我们如何处理边界?我们的步幅是多少?在这个例子中,我们总是选择vanilla版本。我们的卷积使用步长为1,零填充,使输出与输入的大小尺寸相同。为了使代码更清洁,我们还将这些操作抽象为函数。

    def conv2d(x, W):
      return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
    
    def max_pool_2x2(x):
      return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                            strides=[1, 2, 2, 1], padding='SAME')
    

    第一层卷积层

    我们现在可以实现我们的第一层。 它将由卷积组成,其次是最大合并。 卷积将为每个5x5的patch计算32个功能。其权重tensor将具有[5,5,1,32]的形状。前两个维度是patch大小,下一个是输入通道的数量,最后一个是输出通道的数量。我们还将拥有一个带有每个输出通道分量的偏置矢量。

    W_conv1 = weight_variable([5, 5, 1, 32])
    b_conv1 = bias_variable([32])
    

    为了应用这个层,我们首先将x重新变化成一个四维的tensor,其中第二和第三个维度对应的是宽和高,最后一个维度对应的是颜色通道的数量。

    x_image = tf.reshape(x, [-1,28,28,1])
    

    我们之后用权重tensor卷积x_image,加上偏移量,应用ReLU函数,最终将集合最大化。其中max_pool_2x2方法可以将图像大小减到14x14。

    h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)
    

    第二层卷积层

    为了构建一个深层次的网络,我们堆叠若干层这种类型的层。 第二层将为每个5x5patch提供64个特征。

    W_conv2 = weight_variable([5, 5, 32, 64])
    b_conv2 = bias_variable([64])
    
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)
    

    密集层

    既然现在图像尺寸已经缩小到7x7,我们添加了一个具有1024个神经元的完全连接的图层,以便对整个图像进行处理。我们从汇集层将tensor重塑成一批向量,乘以权重矩阵,添加偏倚并应用ReLU。

    W_fc1 = weight_variable([7 * 7 * 64, 1024])
    b_fc1 = bias_variable([1024])
    
    h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
    

    Dropout

    为了减少过度拟合,我们将在读出层之前应用压差dropout的办法。我们创建一个占位符,以便在dropout时保存神经元的输出。这样可以让我们在训练过程中开启dropout,并在测试过程中将其关闭。TensorFlow的tf.nn.dropout op自动处理缩放神经元输出,并且掩盖它们,所以dropout只是在没有任何额外的缩放时才工作。

    keep_prob = tf.placeholder(tf.float32)
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
    

    读出层

    最后我们添加一层,就像是之前的softmax回归层一样。

    W_fc2 = weight_variable([1024, 10])
    b_fc2 = bias_variable([10])
    
    y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
    

    训练和评估模型

    最终模型表现如何?为了训练和评估它,我们将使用与上面简单的SoftMax网络层几乎相同的代码。

    但是区别在于:

    • 我们将用更复杂的ADAM优化器替换最陡峭的梯度下降优化器。
    • 我们将在feed_dict中添加附加参数keep_prob来控制dropout率。
    • 我们将在训练过程中每100次添加日志记录。
    cross_entropy = tf.reduce_mean(
        tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
    correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    sess.run(tf.global_variables_initializer())
    for i in range(20000):
      batch = mnist.train.next_batch(50)
      if i%100 == 0:
        train_accuracy = accuracy.eval(feed_dict={
            x:batch[0], y_: batch[1], keep_prob: 1.0})
        print("step %d, training accuracy %g"%(i, train_accuracy))
      train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
    
    print("test accuracy %g"%accuracy.eval(feed_dict={
        x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
    

    运行此代码后的最终测试集精度应为大约99.2%。

    ]]>
    Java中volatile变量的应用 2017-03-16T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/03/Java中Volatile变量的应用 Java中Volatile变量的应用

    Volatile变量总体上算作一种轻量级的synchronized,只是开销少,代码少,功能受到一定的阉割。

    锁提供两种特性:

    • 互斥:一次只允许一个线程持有某个特定的锁
    • 可见性:确保释放锁之前对共享的数据做出的变更对于随后获得该锁的线程可见。

    Volatile变量具有可见性但不具有原子性。不会像某些锁一样造成线程阻塞,很少有伸缩性问题,如果读操作大大多于写操作,性能上甚至更优。


    使用Volatile的条件

    • 对变量的写操作不依赖于当前值
    • 该变量不包含在具有其他变量的不变式中

    第一个限制使得volatile变量不能作线程安全计数器。

    以下代码显示了一个非线程安全的数值范围,包含了一个不变式——lower总是不大于upper

    @NotThreadSafe 
    public class NumberRange {
        private int lower, upper;
    
        public int getLower() { return lower; }
        public int getUpper() { return upper; }
    
        public void setLower(int value) { 
            if (value > upper) 
                throw new IllegalArgumentException(...);
            lower = value;
        }
    
        public void setUpper(int value) { 
            if (value < lower) 
                throw new IllegalArgumentException(...);
            upper = value;
        }
    }
    

    这种方式限制了范围的状态变量,因此将 lower 和 upper 字段定义为 volatile 类型不能够充分实现类的线程安全;从而仍然需要使用同步。否则,如果凑巧两个线程在同一时间使用不一致的值执行 setLower 和 setUpper 的话,则会使范围处于不一致的状态。例如,如果初始状态是 (0, 5),同一时间内,线程 A 调用 setLower(4) 并且线程 B 调用 setUpper(3),显然这两个操作交叉存入的值是不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是 (4, 3) —— 一个无效值。至于针对范围的其他操作,我们需要使 setLower() 和 setUpper() 操作原子化 —— 而将字段定义为 volatile 类型是无法实现这一目的的。


    使用Volatile的模式

    1,状态标识

    使用一个boolean变量用于指示一个重要的一次性事件,如:

    volatile boolean shutdownRequested;
    ...
    public void shutdown() { shutdownRequested = true; }
    
    public void doWork() { 
        while (!shutdownRequested) { 
            // do stuff
        }
    }
    

    可能从外部调用shutdown()方法,控制标志转换

    2, 一次性安全发布 one-time safe publication

    同步使得某个更新对象引用从另一个线程写入,确保可见性。

    public class BackgroundFloobleLoader {
        public volatile Flooble theFlooble;
    
        public void initInBackground() {
            // do lots of stuff
            theFlooble = new Flooble();  // this is the only write to theFlooble
        }
    }
    
    public class SomeOtherClass {
        public void doWork() {
            while (true) { 
                // do some stuff...
                // use the Flooble, but only if it is ready
                if (floobleLoader.theFlooble != null) 
                    doSomething(floobleLoader.theFlooble);
            }
        }
    }
    
    

    3,独立观察

    定期 “发布” 观察结果供程序内部使用

    
    public class UserManager {
        public volatile String lastUser;
    
        public boolean authenticate(String user, String password) {
            boolean valid = passwordIsValid(user, password);
            if (valid) {
                User u = new User();
                activeUsers.add(u);
                lastUser = user;
            }
            return valid;
        }
    }
    

    4,Volatile bean模式

    很多框架为易变数据的持有者(例如 HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。

    
    @ThreadSafe
    public class Person {
        private volatile String firstName;
        private volatile String lastName;
        private volatile int age;
    
        public String getFirstName() { return firstName; }
        public String getLastName() { return lastName; }
        public int getAge() { return age; }
    
        public void setFirstName(String firstName) { 
            this.firstName = firstName;
        }
    
        public void setLastName(String lastName) { 
            this.lastName = lastName;
        }
    
        public void setAge(int age) { 
            this.age = age;
        }
    }
    

    5,开销较低的读写锁模式

    如果读操作远远超过写操作,可以结合使用内部锁和volatile变量来减少公共代码路径的开销。

    @ThreadSafe
    public class CheesyCounter {
        // Employs the cheap read-write lock trick
        // All mutative operations MUST be done with the 'this' lock held
        @GuardedBy("this") private volatile int value;
    
        public int getValue() { return value; }
    
        public synchronized int increment() {
            return value++;
        }
    }
    
    ]]>
    Apache avro 2017-01-23T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/01/Apache AVRO Apache Avro是一个数据序列化系统,提供:

    • 丰富的数据结构
    • 一个紧凑的、快速的、二进制数据格式
    • 一个容器文件,保存持久化数据
    • 远程生产调用(Remote producer call,RPC)
    • 和动态语言简单集成

    Schema

    Avro依赖于schema,当读写时都是以此形式存在,不需要其他额外的标识,使得序列化快捷简单,可以理解为Java中的类。整个schema是fully self-describing的,当数据保存在文件中的时候,与数据本身一并保存。当Avro远程调用时,客户端和服务器端用握手协议交换schema。Avro Schema是用json形式定义。

    与其他系统相比

    区别在于

    • 动态输入 : 不需要每次都生成代码,数据一致都伴随着schema,比如Protocol Buffers需要用户先定义好数据结构,然后根据这个结构生成代码,再组装数据,如果数据来自多个数据源,就需要重复执行上述操作。
    • 不加标签的数据 :由于schema一致伴随着数据,所以期望数据中不要保留太多冗余信息,以获取更小的序列化尺寸。
    • 不需要手动分配field IDs : 当schema改变时,不管是新的还是旧的schema都会在处理数据时存在,所以可以用field name来象征性地解决新旧schema之间的冲突。

    Get Started

    1, 下载Avro的jar包

    Avro官网下载所需的jar包,当前最新版本是1.8.1,我在这只用了1.7.7版本,下载了avro-1.7.7.jaravro-tools-1.7.7.jar两个包,放在项目的lib文件夹中。

    2,定义Schema

    生成文件user.avsc

    {"namespace": "com.Avro",
     "type": "record",
     "name": "User",
     "fields": [
         {"name": "name", "type": "string"},
         {"name": "favorite_number",  "type": ["int", "null"]},
         {"name": "favorite_color", "type": ["string", "null"]}
     ]
    }
    
    
    3, 编译Schema

    执行:

    java -jar ${项目路径}/src/lib/avro-tools-1.7.7.jar compile schema user.avsc .

    在当前路径下生成com/avro/User.java目录和文件。

    4,编写测试文件
    public class AvroTest {
    
    	//序列化
        @Test
        public void Serialization() throws IOException {
            User user1 = new User();
            user1.setName("Huyifan");
            user1.setFavoriteColor("Yello");
            user1.setFavoriteNumber(1024);
    
            User user2 = new User("Pengdameng", 12, "Red");
    
            User user3 = User.newBuilder()
                    .setName("Xidongdong")
                    .setFavoriteColor("Blue")
                    .setFavoriteNumber(32).build();
            
            //三种不同的方法构建三个User对象
    
            String path = "/Users/apple/Personal/GitKaka/HadoopExample/data/avro/user(1).avsc";
            DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(User.class);
            DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter);
            dataFileWriter.create(user1.getSchema(), new File(path));
    
            dataFileWriter.append(user1);
            dataFileWriter.append(user2);
            dataFileWriter.append(user3);
            dataFileWriter.close();
            
            //序列化user1、user2、user3到文件中,目标文件名为user(1).avsc
    
        }
    
        //反序列化
        @Test
        public void Deserialization() throws IOException {
            DatumReader<User> reader = new SpecificDatumReader<User>(User.class);
            DataFileReader<User> dataFileReader =
                    new DataFileReader<User>(new File("/Users/apple/Personal/GitKaka/HadoopExample/data/avro/user(1).avsc"), reader);
                    
            //初始化reader
                    
            User user = null;
            while (dataFileReader.hasNext()){
                user = dataFileReader.next();
                System.out.println(user);
            }
        }
    
        public static void main(String[] args) throws Exception{
            Result result = JUnitCore.runClasses(AssertTest.class);
            for (Failure failure : result.getFailures()) {
                System.out.println(result.wasSuccessful());
            }
        }
    
    }
    
    
    ]]>
    Jvm gc小议 2017-01-04T00:00:00+00:00 Kaka Chen http://kakack.github.io/2017/01/JVM GC小议 JVM GC小议

    JVM体系结构概况

    其中GC主要作用于方法区和堆。

    其中:

    • 堆区 储存的全是对象,被所有线程共享,没有基本类型和对象的引用,只有对象本身。
    • 栈区 每个线程有一个栈区,保存基础类型的对象和自定义对象的引用,其中的内容是私有的,其他线程不能访问。
    • 方法区 又称为静态区,同样被共享,包含所有class和static变量。

    GC 算法

    引用计数法

    最简单的gc算法,原理是每个对象里保存该对象被引用的次数,每当发生引用增减时自我更新,当计数变成0的时候表示不再被引用,则可释放相应内存空间。优点是容易实现,预测准确。缺点是对象维护一个引用计数器本身也需要一定量的开销,容易产生线程不同步现象,另外无法判别循环引用的情况出现。

    标记清除/标记压缩算法

    首先从根开始将可能被引用的对象用递归的方式进行标记,然后将没有标记到的对象作为垃圾进行回收。如果是压缩的话就是对所有标记到的对象往一端滑动放到一起,将没标记的视为垃圾回收。

    复制收集算法

    将从根开始被引用的对象复制到另外的空间中,然后,再将复制的对象所能够引用的对象用递归的方式不断复制下去。


    Sun Hotspot内存管理

    采用分代管理的办法

    由于不同对象生命周期不同,所用gc算法效率也不同,因此Permanent主要负责以方法区为主的非堆内存,堆内存分为年起代和年老代两种,其中年轻代又由一个Eden区和两个大小对等的Survivor区组成。

    GC类型:

    • Minor GC 针对年轻代GC,年轻代中对象周期短、数量大、GC频率高,采用复制算法
    • Major GC 针对老年代GC,GC频率相对较低,采用标记-权利-压缩结合优化的算法
    • Full GC 针对永久代、年轻代和老年代的GC

    新生代可用GC

    Serial copy、Parallel Scavenge(多线程垃圾回收)、 ParNew,均使用复制算法。

    新对象的内存分配都是现在Eden中进行,当Eden空间不够了会触发年轻代上的GC(在Eden和Survivor上),称之为Minor GC。每个对象都有一个“年龄”,即经历minor GC的次数,当“年龄”足够大的时候,会从Survivor中升级到年老代中。

    年老代可用GC

    Serial MSC、Parallel Compacting、CMS(Concurrent Mark Sweep)

    当有对象从Survivor升级到Tenured区域但是无新空间给对象时,会触发年老代上的GC,称为Major GC。

    Serial Copy

    串行执行,对经历多次Minor GC仍存活的对象或者大小超过eden space或Survivor剩余size的对象可晋升为年老对象。

    Parallel Scavenge

    并行执行标记清除和标记压缩,消除清除产生的磁盘碎片

    Concurrent Mark Sweep

    CMS相对于Parallel Scavenge而言,减少了暂停应用程序的时间,基本上沿用了标记清除算法,略有不同,分为四个阶段:

    • Initial Mark 其实是Stop-The-World的过程,暂停应用程序的运行,但是不会标记Tenured区域中所有对象,只会从根节点开始,标记到其一层子节点,随后立即恢复程序运行,将暂停影响控制到最低。
    • Concurrent Mark 以IM阶段标记的节点为根节点,重新标记可到达的对象,这步骤不需要暂停应用。
    • Remark 由于Concurrent Mark阶段是跟程序同时运行的,所以无法保证所有对象都被标记了,所以再次暂停程序确保所有对象被标记。
    • Concurrent Sweep 恢复程序执行,执行Sweep。

    调优和监控

    • 堆大小调优:一般来说堆越大越好,但受硬件系统限制,设置参数-Xms堆最小值-Xmx堆最大值
    • 年轻代大小:增大Eden大小会降低Minor GC频率,设置参数-Xmn年轻代大小,调整晋升策略,让对象尽可能留在Survivor中

    JVM参数

    • -XX:+PrintGC 输出GC简要信息
    • -XX:+PrintGCDetails 输出GC详细信息
    • -XX:+PrintGCTimeStamps 输出GC时间戳
    • -XX:+PrintGCApplicationStoppedTime 输出GC暂停时间
    • -Xlogg 输出日志信息到文件

    命令行工具

    • 查看内存使用情况、heap dump:jmap+jhat
    • 查看GC情况:jstat
    ]]>
    Apache kudu的基本思想、架构与impala实践 2016-12-17T00:00:00+00:00 Kaka Chen http://kakack.github.io/2016/12/Apache Kudu的基本思想、架构与Impala实践 Apache Kudu的基本思想、架构和与Impala实践

    Apache Kudu是一个为了Hadoop系统环境而打造的列存储管理器,与一般的Hadoop生态环境中的其他应用一样,具有能在通用硬件上运行、水平扩展性佳和支持高可用性操作等功能。

    在Kudu出现之前,Hadoop生态环境中的储存主要依赖HDFS和HBase,追求高吞吐批处理的用例中使用HDFS,追求低延时随机读取用例下用HBase,而Kudu正好能兼顾这两者。

    • Kudu的主要优点:

      • 快速处理OLAP(Online Analytical Processing)任务
      • 集成MapReduce、Spark和其他Hadoop环境组件
      • 与Impala高度集成,使得这成为一种高效访问交互HDFS的方法
      • 强大而灵活的统一性模型
      • 在执行同时连续随机访问时表现优异
      • 通过Cloudera Manager可以轻松管理控制
      • 高可用性,tablet server和master利用Raft Consensus算法保证节点的可用
      • 结构数据模型
    • 常见的应用场景:

      • 刚刚到达的数据就马上要被终端用户使用访问到
      • 同时支持在大量历史数据中做访问查询和某些特定实体中需要非常快响应的颗粒查询
      • 基于历史数据使用预测模型来做实时的决定和刷新
      • 要求几乎实时的流输入处理

    基本概念

    列数据存储 Columnar Data Store

    Kudu是一种列数据储存结构,以强类型的列(strong-type column)储存数据。

    高效读取

    可选择单个列或者某个列的一部分来访问,可以在满足本身查询需要的状态下,选择最少的磁盘或者存储块来访问,相对于基于行的存储,更节省访问资源,更高效。

    数据比较

    由于给定的某一个列当中都是同样类型的数据,所以对于同一个量级的数据比较时,这种存储方式比混合类型存储的更具优势。

    表Table

    同理,一种数据设计模式schema,根据primary key来排序组织。一个表可以被分到若干个分片中,称为tablet。

    分片Tablet

    一个tablet是指表上一段连续的segment。一个特定的tablet会被复制到多个tablet服务器上,其中一个会被认为是leader tablet。每一个备份tablet都可以支持读取、写入请求。

    分片服务器 Tablet Server

    负责为客户端储存和提供tablets。只有Leader Tablet可以写入请求,其他的tablets只能执行请求。

    Master

    Master负责追踪tablets、tablet severs、catalog table和其他与集群相关的metadata。另外也为客户端协调metadata的操作。

    Raft Consensus算法

    前文介绍过了

    Catalog Table

    Kudu的metadata的中心位置,存储表和tablet的信息,客户端可以通过master用客户端api来访问。

    逻辑复制 Logical Replication

    Kudu并是不是在硬盘数据上做复制的,而是采取了逻辑复制的办法,这有以下一些好处:

    • 尽管insert和update需要通过网络对数据做transmit,但是delete操作不需要移动任何数据。Delete操作的请求会发送到每一个tablet server上,在本地做删除操作。
    • 普通的物理操作,比如数据压缩,并不需要通过网络做数据transmit,但不同于HDFS,每个请求都需要通过网络把请求传送到各个备份节点上来满足操作需要。
    • 每个备份不需要同时进行操作,降低写入压力,避免高延时。

    随机写入效率

    在内存中每个tablet分区维护一个MemRowSet来管理最新更新的数据,当尺寸大于一定大小之后会flush到磁盘上行成DiskRowSet,多个DiskRowSet会在适当的时候做归并操作。 这些被flush到磁盘的DiskRowSet数据分为两种,一种是Base数据,按列式存储格式存在,一旦生成不再修改,另一种是Delta文件,储存Base中有更新的数据,一个Base文件可以对应多个Delta文件。

    Delta文件的存在使得检索过程需要额外的开销,这些Delta文件是根据被更新的行在Base文件中的位移来检索的,而且做合并时也是有选择的进行。

    此外DRS(Distributed Resource Scheduler)自身也会合并,为了保障检索延迟的可预测性。Kudu的DRS默认以32MB为单位进行拆分,Compaction过程是为了对内容进行排序重组,减少不同DRS之间key的overlap,进而在检索的时候减少需要参与检索的DRS的数量。


    Kudu整体框架


    与Impala的简单实践

    安装部分不写了,自己都装出屎了。

    通过Impala使用Kudu可以新建内部表和外部表两种。

    • 内部表(Internal Table):事实上是属于Impala管理的表,当删除时会确确实实地删除表结构和数据。在Impala中建表时,默认建的是内部表。
    • 外部表(External Table):不由Impala管理,当删除这个表时,并不能从源位置将其删除,只是接触了Kudu到Impala之间对于这个表的关联关系

    创建一个简单的Kudu表:

    CREATE TABLE kaka_first
    (
      id BIGINT,
      name STRING
    )
    DISTRIBUTE BY HASH INTO 16 BUCKETS
    TBLPROPERTIES(
      'storage_handler' = 'com.cloudera.kudu.hive.KuduStorageHandler',
      'kudu.table_name' = 'kaka_first',
      'kudu.master_addresses' = '10.10.245.129:7051',
      'kudu.key_columns' = 'id'
    );
    

    建表语句中,默认第一个就是Primary Key,是个not null列,在后面的kudu.key_columns中列出,这边至少写一个。

    • storage_handler:选择通过Impala访问kudu的机制,必须填成com.cloudera.kudu.hive.KuduStorageHandler
    • kudu.table_name:Impala为Kudu建(或者关联的)的表名
    • kudu.master_addresses:Impala需要访问的Kudu master列表
    • kudu.key_columns:Primary key列表

    插入数据

    INSERT INTO kaka_first VALUES (1, "john"), (2, "jane"), (3, "jim");
    
    

    Impala默认一次同时最多插入1024条记录,作为一个batch

    更新数据

    UPDATE kaka_first SET name="bob" where id = 3;
    
    

    删除数据

    DELETE FROM kaka_first WHERE id < 3;
    

    修改表属性

    ALTER TABLE kaka_first RENAME TO employee;
    //重命名
    
    ALTER TABLE employee
    SET TBLPROPERTIES('kudu.master_addresses' = '10.10.245.135:7051');
    //更改kudu master address
    
    ALTER TABLE employee SET TBLPROPERTIES('EXTERNAL' = 'TRUE');
    //将内部表变为外部表
    
    

    一个应用

    从MySql导出数据到本地txt

    select * from DAYCACHETBL into outfile '/tmp/DAYCACHETBL.txt'
         fields terminated by '\t' 
         lines terminated by '\n';
    

    保存到hdfs中/data目录下

    hdfs dfs -mkdir /data
    hdfs dfs -put /tmp/DAYCACHETBL.txt /data
    

    在hive shell中创建hive表

    create table DAYCACHETBL (
    	METERID string,
    	SOURCEID int,
    	VB double,
    	DELTA double,
    	DTIME string,
    	UPGUID string,
    	UPBATCH string,
    	level string,
    	YEAR string,
    	MONTH string,
    	QUARTER string,
    	WEEK string,
    	D_DELTA double
    	)
    ROW FORMAT DELIMITED
    fields terminated by '\t'
    lines terminated by '\n'
    stored as textfile
    location '/data';
    

    在impala-shell下创建kudu表

    create table DAYCACHETBL2 (
    	METERID string,
    	SOURCEID int,
    	VB double,
    	DELTA double,
    	DTIME string,
    	UPGUID string,
    	UPBATCH string,
    	level string,
    	YEAR string,
    	MONTH string,
    	QUARTER string,
    	WEEK string,
    	D_DELTA double
    	)
    DISTRIBUTE BY HASH INTO 16 BUCKETS
    TBLPROPERTIES(
      'storage_handler' = 'com.cloudera.kudu.hive.KuduStorageHandler',
      'kudu.table_name' = 'DAYCACHETBL2',
      'kudu.master_addresses' = 'kudu1:7051,kudu2:7051,kudu3:7051',
      'kudu.key_columns' = 'METERID'
    );
    

    将hive表中的内容插入kudu表

    insert into DAYCACHETBL2 select * from DAYCACHETBL;
    
    ]]>
    分布式系统中的raft consensus算法 2016-12-15T00:00:00+00:00 Kaka Chen http://kakack.github.io/2016/12/分布式系统中的Raft Consensus算法 分布式系统中的Raft Consensus算法

    以Kudu为例子,在复制和更替Partition的时候,常用的算法就是Raft Consensus。其中Consensus是指在多个服务器正常工作状态下,出现一个或若干个节点宕机,影响整个系统功能和稳定,因此需要有一些替换策略将宕机了的节点功能转移到其他节点,来保证整个系统的容错性和稳定性。整个替换策略中,只要有半数的节点达成一致就能完成替换。

    整个过程很类似于现实社会中的选举,参选的节点需要说服选民节点们把票投给自己,一旦当选就可以完成接下去的操作。

    在整个过程中,任何一个节点都会成为以下三个roles中之一:

    • Leader:处理客户端交互,catalog复制等,一般整个过程中只有一个Leader
    • Follower:类似选民,完全被动,有选举权
    • Candidate:当需要一个新Leader时,可以成为被选举的对象

    整个Raft过程分为选举和选举后操作两个过程。


    选举过程

    任何节点都可以成为一个candidate,当需要一个新leader时,可以要求其他节点选自己。

    其他节点同意,返回ok消息。

    这个过程中如果有follower宕机,candidate仍可以自己投票给自己。

    一旦candidate当选leader,可以向follower们发出指令

    然后通过心跳进行日志复制的通知

    如果当这个leader宕机了,那么Follower中会出现一个candidate,发出选举请求。

    Follower同意之后,成为Leader,继续承担日志复制工作

    整个选举过程中,需要一个时间限制,如果两个candidate同时发出投票邀请,可以通过加时赛来解决。


    日志复制

    假设Leader已经被选出,这时客户端邀请增加一个日志,叫“Sally”。

    Leader要求各个Follower把日志追加到各自日志中。

    大多数Follower完成操作,追加成功,返回commited OK。

    在下一个心跳heartbeat中,Leader会通知所有Follwer更新commited 项目。

    ]]>
    Scala的库里化函数 2016-11-28T00:00:00+00:00 Kaka Chen http://kakack.github.io/2016/11/Scala的库里化函数 重新看丢下的Scala语法,打算把Spark源码那部分再捡起来。之前翻看买的《函数式编程Scala》发现越来越跟我的初衷不同,我想全面细致重新再看一遍Scala的语法,而这书越来越集中于讲函数式编程的精神,于是只好退回来重新看《Scala编程》一书。

    从Java程序员到Scala程序员,最大的一个转变就是从面向对象编程转换为函数式编程,所有方法都要求越来越简练,朝着函数命令看齐。Scala在抽象化和代码重用性上较之Java更上了一个层次,在Programming in Scala里有一个很好的例子理解Scala的抽象重用特点。


    背景:需要一个api来搜索某个路径下文件名以特定查询值结尾的文件,可以这样写:

    object FileMatcher {
      private def filesHere = 
      	(new java.io.File(".")).listFiles
      def filesEnding(query: String) =
        for (file <- filesHere; if file.getName.endsWith(query))
          yield file
    }
    

    既然有了查询以特定query结尾的方法,那也可以有文件名中包含某个query值的方法:

    def filesContaining(query: String) =
      for (file <- filesHere; if file.getName.contains(query))
         yield file
    

    或者更通用的关于文件名的查询方法:

    def filesRegex(query: String) =
      for (file <- filesHere; if file.getName.matches(query))
        yield file
    

    再进一步将匹配方法通用到更通用的method:

    def filesMatching(query: String, method) =
       for (file <- filesHere; if file.getName.method(query))
          yield file
    

    但是不能用方法名当做值传递,但是可以传递调用方法的函数值:

    def filesMatching(query: String,
        matcher: (String, String) => Boolean) = {
      for (file <- filesHere; if matcher(file.getName, query))
        yield file
    }
    

    matcher方法表示传入两个String参数:file.getName和query,返回一个boolean值,有了这个方法,就能让三种不同的搜索方法调用,传入合适的函数来简化:

    def filesEnding(query: String) =
      filesMatching(query, _.endsWith(_))
    def filesContaining(query: String) =
      filesMatching(query, _.contains(_))
    def filesRegex(query: String) =
      filesMatching(query, _.matches(_))
    

    进一步简化,去除filesMatching和matcher方法中的query参数,尽量减少自由变量_存在的数量,而多一些绑定变量:

    object FileMatcher {
      private def filesHere = (new java.io.File(".")).listFiles
      private def filesMatching(matcher: String => Boolean) =
        for (file <- filesHere; if matcher(file.getName))
          yield file
      def filesEnding(query: String) =
        filesMatching(_.endsWith(query))
      def filesContaining(query: String) =
        filesMatching(_.contains(query))
      def filesRegex(query: String) =
        filesMatching(_.matches(query))
    }
    

    Curry化的函数被应用了多个参数列表,而不仅仅是一个

    未curry化:

    scala> def plainOldSum(x: Int, y: Int) = x + y
    plainOldSum: (Int,Int)Int
    scala> plainOldSum(1, 2)
    res4: Int = 3
    

    curry化之后:代之以一个列表的两个Int参数

    scala> def curriedSum(x: Int)(y: Int) = x + y
    curriedSum: (Int)(Int)Int
    scala> curriedSum(1)(2)
    res5: Int = 3
    

    可以用占位符标注在curriedSum方法上:如

    scala> val onePlus = curriedSum(1)_
    onePlus: (Int) => Int = <function>
    
    scala> onePlus(2)
    res7: Int = 3
    
    scala> val twoPlus = curriedSum(2)_
    twoPlus: (Int) => Int = <function>
    
    scala> twoPlus(2)
    res8: Int = 4
    

    例如现在有以下代码

     type IntPairPred = (Int, Int) => Boolean
     def sizeConstraint(pred: IntPairPred, n: Int, email: Email) =
          pred(email.text.size, n)
    

    其中谓词函数IntpairPred接收一对整数(值n和Email的长度),检查n相对于Email长度之间的关系 比较的方法可以提前定义作为参数传入:

        val gt: IntPairPred = _ > _
        val ge: IntPairPred = _ >= _
        val lt: IntPairPred = _ < _
        val le: IntPairPred = _ <= _
        val eq: IntPairPred = _ == _
    

    最后调用sizeConstraint方法,用IntPairPred传入第一个参数:

        val minimumSize: (Int, Email) => Boolean = sizeConstraint(ge, _: Int, _: Email)
        val maximumSize: (Int, Email) => Boolean = sizeConstraint(le, _: Int, _: Email)
    

    对于没有传入的参数,需要用占位符_,并制定参数类型而且可以遗漏任意个、任意位置的参数 Scala里,可以用多个参数列表重新定义函数

        def sizeConstraint(pred: IntPairPred)(n: Int)(email: Email): Boolean =
          pred(email.text.size, n)
    //可以变成一个可传递、可赋值的函数对象
        val sizeConstraintFn: IntPairPred => Int => Email => Boolean = sizeConstraint _
    

    这种单参数的链式函数就是“库里化函数”,上面这个函数的意思是,sizeConstraintFn接受一个IntPairPred类型,返回一个函数,这个函数又接受一个Int类型,返回一个函数,最终这个函数接受一个Email类型,返回一个布尔值。现在可以把之前定义的IntPairPred参数传入函数,得到:

        val minSize: Int => Email => Boolean = sizeConstraint(ge)
        val maxSize: Int => Email => Boolean = sizeConstraint(le)
    

    再传入Int参数,得到:

        val min20: Email => Boolean = minSize(20)
        val max20: Email => Boolean = maxSize(20)
    
    ]]>
    正则表达式在java中的应用 2016-11-01T00:00:00+00:00 Kaka Chen http://kakack.github.io/2016/11/正则表达式在Java中的应用 正则表达式在Java中的应用

    因为项目需要用到将字符串中部分内容替换,因此想到了用正则表达式来处理,也顺便系统地回顾了一下。


    正则表达式是通过编程方法对字符串或复杂文本进行构造、搜索的办法。正则表达式的使用并不局限于Java语言,而在Java.util.regexpackage下有以下三个类最为重要:

    • Pattern类:pattern对象是一个正则表达式的编译表示。Pattern类没有公共构造方法。要创建一个Pattern对象,你必须首先调用其公共静态编译方法Pattern.compile(regex),它返回一个Pattern对象。该方法接受一个正则表达式作为它的第一个参数。
    • Matcher类:Matcher对象是对输入字符串进行解释和匹配操作的引擎。与Pattern类一样,Matcher也没有公共构造方法。你需要调用Pattern对象的matcher方法来获得一个Matcher对象,Pattern.Matcher(line)
    • PatternSyntaxException:PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

    表达式语法

    字符 说明
    \ 将下一字符标记为特殊字符、文本、反向引用或八进制转义符。例如,”n”匹配字符”n”。”\n”匹配换行符。序列”\“匹配”",”(“匹配”(“。
    ^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与”\n”或”\r”之后的位置匹配。
    $ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与”\n”或”\r”之前的位置匹配。
    * 零次或多次匹配前面的字符或子表达式。例如,zo* 匹配”z”和”zoo”。* 等效于 {0,}。
    + 一次或多次匹配前面的字符或子表达式。例如,”zo+”与”zo”和”zoo”匹配,但与”z”不匹配。+ 等效于 {1,}。
    ? 零次或一次匹配前面的字符或子表达式。例如,”do(es)?”匹配”do”或”does”中的”do”。? 等效于 {0,1}。
    {n} n 是非负整数。正好匹配 n 次。例如,”o{2}”与”Bob”中的”o”不匹配,但与”food”中的两个”o”匹配。
    {n,} n 是非负整数。至少匹配 n 次。例如,”o{2,}”不匹配”Bob”中的”o”,而匹配”foooood”中的所有 o。”o{1,}”等效于”o+”。”o{0,}”等效于”o*“。
    {n,m} M 和 n 是非负整数,其中 n <= m。匹配至少 n 次,至多 m 次。例如,”o{1,3}”匹配”fooooood”中的头三个 o。’o{0,1}’ 等效于 ‘o?’。注意:您不能将空格插入逗号和数字之间。
    ? 当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是”非贪心的”。”非贪心的”模式匹配搜索到的、尽可能短的字符串,而默认的”贪心的”模式匹配搜索到的、尽可能长的字符串。例如,在字符串”oooo”中,”o+?”只匹配单个”o”,而”o+”匹配所有”o”。
    . 匹配除”\r\n”之外的任何单个字符。若要匹配包括”\r\n”在内的任意字符,请使用诸如”[\s\S]”之类的模式。
    (pattern) 匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果”匹配”集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用”(“或者”)“。
    (?:pattern) 匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用”or”字符 (|) 组合模式部件的情况很有用。例如,’industr(?:y|ies) 是比 ‘industry|industries’ 更经济的表达式。
    (?=pattern) 执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?=95|98|NT&|2000)’ 匹配”Windows 2000”中的”Windows”,但不匹配”Windows 3.1”中的”Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。
    (?!pattern) 执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,’Windows (?!95&|98|NT|2000)’ 匹配”Windows 3.1”中的 “Windows”,但不匹配”Windows 2000”中的”Windows”。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。
    x|y 匹配 x 或 y。例如,’z|food’ 匹配”z”或”food”。’(z|f)ood’ 匹配”zood”或”food”。
    [xyz] 字符集。匹配包含的任一字符。例如,”[abc]”匹配”plain”中的”a”。
    [^xyz] 反向字符集。匹配未包含的任何字符。例如,”[^abc]“匹配”plain”中”p”,”l”,”i”,”n”。
    [a-z] 字符范围。匹配指定范围内的任何字符。例如,”[a-z]”匹配”a”到”z”范围内的任何小写字母。
    [^a-z] 反向范围字符。匹配不在指定的范围内的任何字符。例如,”[^a-z]“匹配任何不在”a”到”z”范围内的任何字符。
    \b 匹配一个字边界,即字与空格间的位置。例如,”er\b”匹配”never”中的”er”,但不匹配”verb”中的”er”。
    \B 非字边界匹配。”er\B”匹配”verb”中的”er”,但不匹配”never”中的”er”。
    \cx 匹配 x 指示的控制字符。例如,\cM 匹配 Control-M 或回车符。x 的值必须在 A-Z 或 a-z 之间。如果不是这样,则假定 c 就是”c”字符本身。
    \d 数字字符匹配。等效于 [0-9]。
    \D 非数字字符匹配。等效于 [^0-9]。
    \f 换页符匹配。等效于 \x0c 和 \cL。
    \n 换行符匹配。等效于 \x0a 和 \cJ。
    \r 匹配一个回车符。等效于 \x0d 和 \cM。
    \s 匹配任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r\t\v] 等效。
    \S 匹配任何非空白字符。与 [^ \f\n\r\t\v] 等效。
    \t 制表符匹配。与 \x09 和 \cI 等效。
    \v 垂直制表符匹配。与 \x0b 和 \cK 等效。
    \w 匹配任何字类字符,包括下划线。与”[A-Za-z0-9_]”等效。
    \W 与任何非单词字符匹配。与”[^A-Za-z0-9_]“等效。
    \xn 匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,”\x41”匹配”A”。”\x041”与”\x04”&”1”等效。允许在正则表达式中使用 ASCII 代码。
    \num 匹配 num,此处的 num 是一个正整数。到捕获匹配的反向引用。例如,”(.)\1”匹配两个连续的相同字符。
    \n 标识一个八进制转义码或反向引用。如果 \n 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。
    \nm 标识一个八进制转义码或反向引用。如果 \nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 \nm 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符 m。如果两种前面的情况都不存在,则 \nm 匹配八进制值 nm,其中 n 和 m 是八进制数字 (0-7)。
    \nml 当 n 是八进制数 (0-3),m 和 l 是八进制数 (0-7) 时,匹配八进制转义码 nml。
    \un 匹配 n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (©)。

    方法与例子

    查找:

    package testcase;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public class MainTester {
    	public static void main(String[] args) {
    		String line = "hello world123llyylln";
    		//需要被查找的字符串
    		String regex = "(l)(.*)(\\s.)(.*)($)";
    		//查找规则
    		Pattern r = Pattern.compile(regex);
    		//生成pattern
    		Matcher m = r.matcher(line);
    		//生成matcher
    		
    		if (m.find()) {
    		    System.out.println("Found: "+ m.group(0));
    		    //group(0)为整个被匹配到的子字符串
    			System.out.println("Found: "+ m.group(1));
    			//group(1)返回regex中第一组,即(l)被匹配到的部分
    			System.out.println("Found: "+ m.group(2));
    			//group(1)返回regex中第二组,即(.*)被匹配到的部分
    			System.out.println("Found: "+ m.group(3));
    			System.out.println("Found: "+ m.group(4));
    			System.out.println("Found: "+ m.group(5));
    			System.out.println("Found: "+ m.start() + ' ' + m.end());
    			//m.start()返回被匹配到的子字符串头部的索引,m.end()为尾部索引, 如果是m.start(int group)则为对应的group头部索引,m.end(int group)亦然
    
    		}
    		else {
    			System.out.println("No match!");
    		}
    	}
    }
    

    输出结果:

    Found: llo world123llyylln
    Found: l
    Found: lo
    Found:  w
    Found: orld123llyylln
    Found: 
    Found: 2 21
    

    Matcher方法

    索引方法

    索引方法提供了有用的索引值,精确表明输入字符串中在哪能找到匹配:

    方法 说明
    public int start() 返回以前匹配的初始索引。
    public int start(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
    public int end() 返回最后匹配字符之后的偏移量。
    public int end(int group) 返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。

    研究方法

    研究方法用来检查输入字符串并返回一个布尔值,表示是否找到该模式:

    方法 说明
    public boolean lookingAt() 尝试将从区域开头开始的输入序列与该模式匹配。
    public boolean find() 尝试查找与该模式匹配的输入序列的下一个子序列。
    public boolean find(int start) 重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
    public boolean matches() 尝试将整个区域与模式匹配。

    替换方法

    替换方法是替换输入字符串里文本的方法:

    方法 说明
    public Matcher appendReplacement(StringBuffer sb, String replacement) 实现非终端添加和替换步骤。
    public StringBuffer appendTail(StringBuffer sb) 实现终端添加和替换步骤。
    public String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列。
    public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列。
    public static String quoteReplacement(String s) 返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher类的appendReplacement 方法一个字面字符串一样工作。

    函数区分

    • matches和lookingAt方法:都是判断是否匹配的方法,前者要求整个字符串都匹配,后者只要求有匹配部分存在,如:
    String line = "hello world123llyylln";
    String regex = "(hello)(\\s)";
    
    Pattern r = Pattern.compile(regex);
    Matcher m = r.matcher(line);
    
    System.out.println("Looking at: " + m.lookingAt());//true
    System.out.println("Matches :" + m.matches());//false
    
    • appendReplacement和appendTail
    String line = "abcdabcdabcd";
    String regex = "bc";		
    String replaced = "--";
    		
    Pattern r = Pattern.compile(regex);
    Matcher m = r.matcher(line);
    
    StringBuffer sb = new StringBuffer();
    while(m.find()){
    	m.appendReplacement(sb, replaced);
    }//替代掉line中的“bc”,存到sb中
    System.out.println(sb.toString());//a--da--da--
    m.appendTail(sb);//把匹配完剩余的补到sb后面
    System.out.println(sb.toString());//a--da--da--d
    
    
    ]]>