最近在看nodejs,感觉它的异步编程思想确实有些不习惯。但好在ES6以后支持async、await特性,会慢慢让代码更加好看。也学了下node中最流行的框架express,以及它的下一代版本叫做koa。给我的感觉就是简练,跟ruby的rails,php中的laravel,还有python的django这些大框架比起来真的是太简练了。这些大框架给你封装了太多东西,只要学会了基本的使用方法就能满足绝大部分业务需求。而express以及koa是真正轻量级框架,真的十分简练。并且它们是类似于组件(在它们中叫做中间件)拼合的开发方式。需要用到什么功能,就引入相应的包就行了。能让开发者在更加底层的视角学习整个web框架运行原理。

好了,言归正传。nodejs中的包管理器是npm(node package manager),它的包来源是其官方的镜像,由于众所周知的原因,对国内开发者而言,在node官方包镜像上下载包简直就是灾难。好在我们的大淘宝为咱国内开发者提供了国内镜像,我们只需要在安装包是指定镜像注册地址就行了,比如我们要安装express,命令如下:

npm install express --registry=https://registry.npm.taobao.org

或者我们可以在系统全局注册这个镜像,在windows下:
找到node的安装目录,在node_modules\npm下有个文件叫做npmrc,在里面最后一行添加一句话:

registry = https://registry.npm.taobao.org

保存就行了,然后重启命令行生效。
以后我们需要装express只需要键入以下命令:

npm install express

在macos下:
在~/下找有没有一个.npmrc的文件,该文件的位置一般在~/下,若没有的话新建它。在里面也添加一句:

registry = https://registry.npm.taobao.org

保存,重启命令行生效。

乍一看题目,使用”meka“实现分类器,是不是笔者打错了啊,应该是weka啊。其实不然,这世上还真有一个叫meka的包,并且他还跟weka算是亲戚关系。只不过,这个meka是一个专门用来解决多标签分类问题的包,并且是基于weka的,可以算作是weka的一个扩展。

那好,什么是多标签分类呢?我们先来看一个示例:如果我有一篇文章,我要推断它是不是“体育”类新闻,那么分类标签就只有“是”或者“不是”,这种问题就是经典的二分类(binary classifier)问题。好,再复杂一点,如果我们要判断这篇文章是属于“体育”、“财经”、“社会”中的哪一类?那么这个问题就变成了一个多类分类问题(multiclass classifier),注意,对于每篇文章它只能属于“体育”、“财经”、“社会”中的某一类,不能属于多个类。也就是我们的样本的标签可以是[0,0,1]或者[0,1,0]或者[1,0,0],但绝不可能是[1,1,0]或者[1,0,1]等。好了,与多分类对应的就是多标签分类(multilabel classifier),它是解决什么问题的呢?如果一篇文章,它既能属于“体育”类,又能属于“财经类”,甚至还能属于“社会”类,也就是我们待分类的文章有可能同时分到多个类中去,对于这样的分类我们就叫多标签分类。

明白了多标签分类的概念,我们怎么实现呢?其实说到底,多类分类,多标签分类,其实底层都是二分类组合。我们当然可以自己手动实现。但有没有已有的库来帮助我们实现呢?当然有!!!sklearn中已经有实现多标签分类的方法了,它的底层支持可以是Decision Trees, Random Forests以及Nearest Neighbors. 不过我们这次不使用我们优雅的sklearn,要跳到JAVA的坑去(好桑心)。JAVA中比较有名的多标签分类包是mulan以及meka,我们今天只讲meka,至于mulan的实现方法其实也跟meka很像,甚至在meka中都已经集成了mulan的jar包。

meka是由三个大牛Jesse Read、Peter Reutemann、Joerg Wicker实现,并且现在已经开源到github了,与weka其实是师出同门。meka的使用基本与weka一致,它甚至到包含一个客户端,长相也跟weka很像。
meka
对于图形界面的使用,可以去看看它们的官方教程,很详细。我们今天来说一说如何使用集成meka-1.9.0.jar来进行编程实现。这个jar包存在于lib文件夹下。

meka也是用arff文件作为输入。官方文档中有这么一句话:”A suitable dataset is the only requirement to begin running experiments with Meka.”,足见meka使用之方便~~
meka中已经自带了几个用于实现多标签分类的数据集在data目录下,这个数据集是描述某段音频的特征,以及它们属于哪些分类(是悲伤类的还是安静类的亦或是疯狂摇滚的…)我们来看看大概看看,每行%后面是我自己加的注释:

% 'Music' dataset; normalised version.
@relation 'Music: -C 6'                       %代表标签一共有6类

@attribute amazed-suprised {0,1}              %第一个分类标签,取值是0或者1,代表是或者不是
@attribute happy-pleased {0,1}                %第二个分类标签
@attribute relaxing-clam {0,1}                %第三个分类标签
@attribute quiet-still {0,1}                  %第四个分类标签
@attribute sad-lonely {0,1}                   %第五个分类标签
@attribute angry-aggresive {0,1}              %第六个分类标签
@attribute Mean_Acc1298_Mean_Mem40_Centroid numeric  %第一个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_Rolloff numeric   %第二个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_Flux numeric      %第三个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_MFCC_0 numeric    %第四个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_MFCC_1 numeric    %第五个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_MFCC_2 numeric    %第六个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_MFCC_3 numeric    %第七个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_MFCC_4 numeric    %第八个特征,numeric类型
@attribute Mean_Acc1298_Mean_Mem40_MFCC_5 numeric    %第九个特征,numeric类型
....
@data

0,1,1,0,0,0,0.132498,0.077848,0.229227,0.602629,0.512861,0.467404,0.529733,0.573498,0.592831,0.520031,0.598853,0.537699,0.780658,0.462982,0.407108,0.684364,0.135824,0.245631,0.157515,0.301285,0.350107,0.459476,0.583274,0.430053,0.416198,0.581916,0.342758,0.309345,0.388929,0.323521,0.455207,0.26139,0.027559,0.149077,0.195433,0.571354,0.326404,0.246745,0.524645,0.354798,0.240244,0.239788,0.128689,0.173252,0.204863,0.131632,0.245653,0.144607,0.258203,0.470051,0.259909,0.61364,0.458314,0.434716,0.448941,0.370609,0.285647,0.663082,0.29708,0.273671,0.286411,0.197026,0.196244,0.164323,0.030017,0.253968,0.008473,0.240602,0.136735,0.058442,0.107594  %第一条数据
1,0,0,0,0,1,0.384281,0.355249,0.16719,0.853089,0.260577,0.332757,0.15393,0.519381,0.268043,0.251955,0.459922,0.430814,0.654323,0.641021,0.356511,0.647367,0.367659,0.539078,0.100569,0.133502,0.337194,0.319752,0.349012,0.171182,0.191357,0.390569,0.289253,0.208641,0.341328,0.265669,0.273736,0.181791,0.028513,0.252827,0.25819,0.011351,0.236247,0.069285,0.192754,0.154258,0.128671,0.116726,0.059704,0.073697,0.080341,0.062701,0.075672,0.041256,0.207782,0.300735,0.888274,0.444,0.294673,0.210429,0.132036,0.167474,0.205996,0.155514,0.086631,0.071462,0.067492,0.093526,0.085649,0.025101,0.182955,0.285714,0.156764,0.270677,0.191377,0.153728,0.197951  %第二条数据

与weka有点区别的是,对于标签在每个样本集中的位置,weka一般是放在每行末尾,虽然可以放在每行的任意位置,可以通过Instances.setClassIndex(n)方法,来指定每行的第n位是属于分类标签。
先来上代码吧:

package com.ayonel.meka;
import meka.classifiers.multilabel.BR;
import weka.classifiers.bayes.NaiveBayesMultinomialUpdateable;
import weka.core.Instances;
import weka.core.converters.ArffLoader;

import java.io.File;

/**
 * @author ayonel
 * @created_at 16/9/27
 */


public class YYRec {
    public static void main(String[] args) throws Exception{
        ArffLoader loader = new ArffLoader();
        loader.setFile(new File("testdata/Music.arff")); //读取测试集
        loader.getStructure();
       
        Instances originDataSet = loader.getDataSet(); //获取测试集
        originDataSet.setClassIndex(6); //这一步是指定前6个元素作为分类标签
        BR br = new BR(); //建立分类器
        br.buildClassifier(originDataSet);//开始训练
        double [] result = br.distributionForInstance(originDataSet.instance(0));//对训练集中的第一个实例进行预测
        //输出每个标签的预测结果(为1的概率值)
        for(int i=0; i<result.length; i++) {
            System.out.println(result[i]);
        }
    }
}

输出结果:

0.0
1.0
0.9859154929577465
0.034482758620689655
0.0
0.013452914798206279

甚至还可以使用多标签分类进行增量学习,使用:
meka.classifiers.multilabel.incremental.BRUpdateable类,该类底层使用weka.classifiers.trees.HoeffdingTree来实现。

以前写过一篇博文是利用weka来实现多项式朴素贝叶斯增量学习。这次利用python中的sklearn学习工具来实现。其实个人感觉像机器学习,数据处理这种活本来就应该让我们高雅而优美的Python来实现,Java你瞎凑什么热闹啊~~

与weka的实现相比,sklearn果然还是继承了Python简洁优雅的传统,实现起来十分方便。如何判断sklearn中分类器是否支持增量学习(incremental learning & online learning)呢?很简单,只需要查阅sklearn文档,如果该分类器存在partial_fit()方法,即说明它支持增量学习。
比如我们的多项式朴素贝叶斯中就有该方法:
sklearn

下面我以一个十分简单的实例来进行演示:

比如我们有三份数据集,先用数据集1进行训练,然后预测数据集3;然后再利用数据集2再对模型进行更新,之后再预测数据集3。
数据集1:‘[ ]’中代表样本向量, ‘#’后面代表样本标签

 [1 1 0 1 2 0 3 1 2 2]    # 1
 [0 3 1 1 2 2 1 3 2 1]    # 0
 [0 3 0 3 2 2 2 1 4 0]    # 0
 [1 4 3 2 4 0 2 1 3 4]    # 1
 [4 4 3 4 0 3 4 2 4 4]    # 0
 [0 2 1 3 3 1 0 1 2 3]    # 1

数据集2:

 [0 1 2 2 2 1 0 0 2 3]    # 0
 [2 1 2 1 0 3 1 2 1 1]    # 1
 [4 0 3 3 3 4 2 2 4 2]    # 0
 [1 3 3 4 1 4 3 0 3 3]    # 1
 [1 4 4 0 3 4 0 2 2 2]    # 1
 [1 4 0 4 1 0 4 1 4 1]    # 1

数据集3(测试集,没有标签):

 [1 4 1 3 0 3 1 2 3 4]
 [0 2 0 2 3 2 0 0 2 2]
 [0 3 4 3 0 1 4 4 4 2]
 [0 4 3 0 2 2 2 1 0 3]
 [4 3 1 3 3 2 2 0 0 1]
 [3 0 3 2 4 2 1 2 1 0]

代码十分简单,先来看看吧:

import numpy as np
#构造数据集,每份数据集是6*10的随机生成的矩阵,代表有6个样本,每个样本中有10个向量。
X1 = np.random.randint(5, size=(6, 10))#数据集1
X2 = np.random.randint(5, size=(6, 10))#数据集2
X3 = np.random.randint(5, size=(6, 10))#数据集3

#打印出3份数据集
print(X1)
print(X2)
print(X3)

y1 = np.array([1, 0, 0, 1, 0, 1])#数据集1中每份数据对应的标签
y2 = np.array([0, 1, 0, 1, 1, 1])#数据集2中每份数据对应的标签

#所有数据集中出现的分类标签
labels = np.array([0, 1])

from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB() #定义分类器

#先利用X1与y1进行部分训练
print('start tranning...')
clf.partial_fit(X1, y1, classes=labels)#核心方法,部分训练,第一次partial_fit,需要在classes参数中给出所有标签
print('第一次预测结果:'+str(clf.predict(X3[:]))) #打印出对数据集3的预测结果

#再利用X2与y2进行模型更新
clf.partial_fit(X2, y2)#
print('更新后预测结果:'+str(clf.predict(X3[:]))) #再打印出对数据集3的预测结果

运行结果为:(由于数据集是随机生成的,所以每次的结果均会不同)

[[1 1 0 1 2 0 3 1 2 2]
 [0 3 1 1 2 2 1 3 2 1]
 [0 3 0 3 2 2 2 1 4 0]
 [1 4 3 2 4 0 2 1 3 4]
 [4 4 3 4 0 3 4 2 4 4]
 [0 2 1 3 3 1 0 1 2 3]]
[[0 1 2 2 2 1 0 0 2 3]
 [2 1 2 1 0 3 1 2 1 1]
 [4 0 3 3 3 4 2 2 4 2]
 [1 3 3 4 1 4 3 0 3 3]
 [1 4 4 0 3 4 0 2 2 2]
 [1 4 0 4 1 0 4 1 4 1]]
[[1 4 1 3 0 3 1 2 3 4]
 [0 2 0 2 3 2 0 0 2 2]
 [0 3 4 3 0 1 4 4 4 2]
 [0 4 3 0 2 2 2 1 0 3]
 [4 3 1 3 3 2 2 0 0 1]
 [3 0 3 2 4 2 1 2 1 0]]
start tranning...
第一次预测结果:[0 1 0 1 0 0]
更新后预测结果:[1 1 1 1 1 0]

最终的预测结果表示的是对数据集3中的6个样本,分别预测出的标签。
如果最终需要的结果不是预测出类别,而是需要每个类别精确的概率值。
可以采用方法clf.predict_proba()

一直在用jetbrains系列的IDE开发,感觉用他们家的IDE跟集邮似的,越玩越上瘾。
先来一张我的解锁图:

去JetBrains官网上一看,他们家的触手已经伸向各大主流语言了。。。
jet

看来以后得跟他们家过一辈子了。。。。
文末来个小福利吧,JetBrains系列破解连接:地址

来源于阮一峰的博客
对两张图片进行相似度检测,思路就是对两张图片分别计算其指纹信息,然后对比两张图片的指纹信息的差异度。
基本算法为:
第一步,缩小尺寸。
将图片缩小到8×8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。

第二步,简化色彩。
将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。

第三步,计算平均值。
计算所有64个像素的灰度平均值。

第四步,比较像素的灰度。
将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。

第五步,计算哈希值
将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。
=8f373714acfcf4d0
得到指纹以后,就可以对比不同的图片,看看64位中有多少位是不一样的。在理论上,这等同于计算”汉明距离”(Hamming distance)。如果不相同的数据位不超过5,就说明两张图片很相似;如果大于10,就说明这是两张不同的图片。

代码由Wote利用Python实现,本文是对Wote的源码做一点修改并附上详细注释。

本程序需要安装pillow,

pip install pillow
#coding: utf-8
'''
本程序运行时需要两张图片路径,需要输入两张图片路径,
'''

from PIL import Image

#计算指纹
def avhash(img):
    if not isinstance(img, Image.Image):
        img = Image.open(img)
    img = img.resize((8, 8), Image.ANTIALIAS).convert('L') #将image压缩为8*8,转化为灰度图
    avg = reduce(lambda x, y: x + y, img.getdata()) / 64. #对每个像素点的灰度累和,最后除以64,得到灰度的平均值

    #这一句代码很pythonic,需要仔细消化
    #map对每个像素做判断,大于平均值为1,否则为0
    #enumerate函数返回一个列表的下标及该下标对应的元素,用tuple装起来: (index, element)
    #reduce,对每个元素右移对应的下标位,并且与前一个元素做或运算,最终得到的结果为一个
    # 64位的二进制数,每一位的0,1代表该位的像素灰度与平均像素灰度的比较结果
    return reduce(lambda x, (y, z): x | (z << y), enumerate(map(lambda i: 0 if i < avg else 1, img.getdata())), 0)

#计算汉明距离
def hamming(h1, h2):
    #直接对两个数按位做异或操作,这样得到一个64位的二进制数,该二进制数包含的1的个数,即为汉明距离
    h, d = 0, h1 ^ h2
    #求d中包含的1的个数
    while d:
        h += 1
        d &= d - 1
    return h

if __name__ == '__main__':
    img1 = raw_input('请输入第一张图片路径:')
    img2 = raw_input('请输入第二张图片路径:')
    h1 = avhash(img1)
    h2 = avhash(img2)
    print "两张图片的指纹汉明距离为:%s" % hamming(h1, h2)

本文打算长期更新一些有意思的python代码片段,就当做是学习笔记。

1.python多线程死锁

import time,threading

locka = threading.Lock()
lockb = threading.Lock()

def fa():
    print('a')
def fb():
    print('b')

def fab():
    locka.acquire()
    try:
        fa()
        time.sleep(1)
        lockb.acquire()
        try:
            fb()
        finally:
            lockb.release()
    finally:
        locka.release()
def fba():
    lockb.acquire()
    try:
        fb()
        time.sleep(1)
        locka.acquire()
        try:
            fa()
        finally:
            locka.release()
    finally:
        lockb.release()

t1 = threading.Thread(target=fab)
t2 = threading.Thread(target=fba)
t1.start()
t2.start()
t1.join()
t2.join()
print('end')

2.协程coroutine(1)

来源:廖雪峰

def consumer():
    r = ''
    while True:

        n = yield r
        if not n:
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)

运行结果为:

[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER] Consuming 3...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER] Consuming 4...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER] Consuming 5...
[PRODUCER] Consumer return: 200 OK

解释:
yield表达式本身没有返回值,它的返回值需要等到下次调用generator函数时,由send(args)函数的参数赋予。

3.协程coroutine(2)

来源:http://blog.csdn.net/gvfdbdf/article/details/49254037

# -*- 异步IO -*-  
import asyncio  
import threading  
 
# @asyncio.coroutine把一个generator标记为coroutine类型  
@asyncio.coroutine  
def sub():  
    print('sub start: ...')  
    n = 10  
    while True:  
        print('yield start')  
        # asyncio.sleep()也是一个coroutine类型的generator,所以线程不会中断,而是直接执行下一个循环,等待yield from的返回  
        # 可以简单的理解为出现yield之后则开启一个协程(类似开启一个新线程),不管这个协程是否执行完毕,继续下一个循环  
        # 开启新协程后,print('yield start')会因为继续执行循环被立即执行,可以通过打印结果观察  
        r = yield from asyncio.sleep(1)  
        n = n - 1  
        print('---sub: %s,  thread:%s' %(n, threading.currentThread()))  
        if n == 0:  
            break  
 
@asyncio.coroutine  
def add():  
    print('add start: ...')  
    n = 10  
    while True:  
        print('yield start')  
        r = yield from asyncio.sleep(2)  
        n = n + 1  
        print('+++add: %s,  thread:%s' %(n, threading.currentThread()))  
        if n > 20:  
            break  
 
 
# 获取EventLoop:  
loop = asyncio.get_event_loop()  
# 执行coroutine  
tasks = [add(),sub()]  
loop.run_until_complete(asyncio.wait(tasks))  
loop.close()

执行结果:

add start: ...
yield start add
sub start: ...
yield start sub
---sub: 9,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start sub
+++add: 11,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start add
---sub: 8,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start sub
---sub: 7,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start sub
+++add: 12,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start add
---sub: 6,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start sub
---sub: 5,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start sub
+++add: 13,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start add
---sub: 4,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start sub
---sub: 3,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start sub
+++add: 14,  thread:<_MainThread(MainThread, started 140735116865536)>
yield start add
......

同步异步,阻塞非阻塞这一直是一个很难理解的概念。以前也看过一些资料,但现在回想起来还是一片空白。所以整理一篇文章来记录下,以期备忘。

网上看到一句话,还是挺形象的:
“阻塞和非阻塞是等待方式的区别,比如你去取钱,等待过程中,你一直在那边等,什么事情都做不了;而非阻塞是先给一个结果“可能要花点时间,你去抽根烟吧”。而同步和异步是通知方式的差别,同步就是,你抽烟过程中,还不断的问“钱出来了吗?钱出来了么?”,是你主动去问的。而异步就是,你取钱的时候,柜员给了你一张小纸条说“钱好了叫你”,注意,是柜员通知你。综述,阻塞是同步的,异步一定是非阻塞的。”

知乎:
不熟悉操作系统的开发人员们只知道有什么用什么,也没去多想怎么优化多线程。现在多线程优化出来了,叫协程,调度器有的有(比如go的用户态调度器)有的没有(比如yield),取决于怎么用。其内存开销仍然比异步回调要大(一个协程一个栈,而异步回调的话一个event loop一共就一个栈),但是现在内存也是便宜了,不算是瓶颈。人最关注的是“千万不能堵着(block)”,要千方百计让CPU转起来,这样concurrency才能上去。而到处乱开协程(因为比线程便宜啊)就能达到这个效果,开十万个协程内存也没爆炸,跑上24个就能把CPU打满。所以异步回调的这个优势已经没了。

作者:Tim Shen
链接:https://www.zhihu.com/question/32218874/answer/56255707
来源:知乎

作者:陈果果果果果栋
链接:https://www.zhihu.com/question/32218874/answer/55469714
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

笔者是一个菜鸟,以下全部是乱喷的….

哈哈,首先我其实不觉得协程是趋势。但是协程真的改进了IO操作的用户体验。

协程是啥
首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元。为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机,我们可以把一个协程 切换到 另一个协程。只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

协程和线程差异
那么这个过程看起来比线程差不多哇。其实不然 线程切换从系统层面远不止 保存和恢复 CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

协程的问题
但是协程有一个问题,就是系统并不感知,所以操作系统不会帮你做切换。那么谁来帮你做切换?让需要执行的协程更多的获得CPU时间才是问题的关键。

笔者知道协程的实现相关的
目前的协程框架一般都是设计成 1:N 模式。所谓 1:N 就是一个线程作为一个容器里面放置多个协程。那么谁来适时的切换这些协程?答案是有协程自己主动让出CPU,也就是每个协程池里面有一个调度器,这个调度器是被动调度的。意思就是他不会主动调度。而且当一个协程发现自己执行不下去了(比如异步等待网络的数据回来,但是当前还没有数据到),这个时候就可以由这个协程通知调度器,这个时候执行到调度器的代码,调度器根据事先设计好的调度算法找到当前最需要CPU的协程。切换这个协程的CPU上下文把CPU的运行权交个这个协程,直到这个协程出现执行不下去需要等等的情况,或者它调用主动让出CPU的API之类,触发下一次调度。对的没错就是类似于 领导人模式

那么这个实现有没有问题?
其实是有问题的,假设这个线程中有一个协程是CPU密集型的他没有IO操作,也就是自己不会主动触发调度器调度的过程,那么就会出现其他协程得不到执行的情况,所以这种情况下需要程序员自己避免。这是一个问题,假设业务开发的人员并不懂这个原理的话就可能会出现问题。

最后讲讲协程的好处
在IO密集型的程序中由于IO操作远远小于CPU的操作,所以往往需要CPU去等IO操作。同步IO下系统需要切换线程,让操作系统可以再IO过程中执行其他的东西。这样虽然代码是符合人类的思维习惯但是由于大量的线程切换带来了大量的性能的浪费,尤其是IO密集型的程序。

所以人们发明了异步IO。就是当数据到达的时候触发我的回调。来减少线程切换带来性能损失。但是这样的坏处也是很大的,主要的坏处就是操作被 “分片” 了,代码写的不是 “一气呵成” 这种。 而是每次来段数据就要判断 数据够不够处理哇,够处理就处理吧,不够处理就在等等吧。这样代码的可读性很低,其实也不符合人类的习惯。

但是协程可以很好解决这个问题。比如 把一个IO操作 写成一个协程。当触发IO操作的时候就自动让出CPU给其他协程。要知道协程的切换很轻的。协程通过这种对异步IO的封装 既保留了性能也保证了代码的 容易编写和可读性。在高IO密集型的程序下很好。但是高CPU密集型的程序下没啥好处。

扯完 碎觉…………… 手酸
—————未完待续———————————

作者:fleuria
链接:https://www.zhihu.com/question/32218874/answer/55151946
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

有时候总觉得 gevent 这类协程库是被 python 的 GIL 逼出来的,如果原生线程支持足够好,协程的必要性可能并不一定很大。

协程最早来自高性能计算领域的成功案例,协作式调度相比抢占式调度而言,可以在牺牲公平性时换取吞吐。

在互联网行业面临 C10K 问题时,线程方案不足以扛住大量的并发,这时的解决方案是 epoll() 式的事件循环,nginx 在这波潮流中顺利换掉 apache 上位。同一时间的开发社区为 nginx 的成绩感到震撼,出现了很多利用事件循环的应用框架,如 tornado / nodejs,也确实能够跑出更高的分数。而且 python/ruby 社区受 GIL 之累,几乎没有并发支持,这时事件循环是一种并发的解放。

然而事件循环的异步控制流对开发者并不友好。业务代码中随处可见的 mysql / memcache 调用,迅速地膨胀成一坨 callback hell。这时社区发现了协程,在用户态实现上下文切换的工具,把 epoll() 事件循环隐藏起来,而且成本不高:用每个协程一个用户态的栈,代替手工的状态管理。似乎同时得到了事件循环和线程同步控制流的好处,既得到了 epoll() 的高性能,又易于开发。甚至通过 monkey patch,旧的同步代码可以几乎无缝地得到异步的高性能,真是太完美了。

然而,跑了一圈回来,协程相比原生线程又有多少差别呢。

1. 用户态栈,更轻量地创建“轻量线程”;
2. 协作式的用户态调度器,更少线程上下文切换;
3. 重新实现 mutex 等同步原语;

协程的创建成本更小,但是创建成本可以被线程池完全绕开,而且线程池更 fine grained,这时相比线程池的优势更多在于开发模型的省力,而不在性能。此外,”轻量线程” 这个名字有一定误导的成分,协程作为用户态线程,需要的上下文信息与系统线程几乎无异。如果说阻碍系统线程 scale 的要素是内存(一个系统线程的栈几乎有 10mb 虚拟内存,线程的数量受虚拟地址空间限制),那么用户态线程的栈如果使用得不节制,也需要同量的内存。

协作式调度相比抢占式调度的优势在于上下文切换开销更少(但是差异是否显著?)、更容易把缓存跑热,但是也放弃了原生线程的优先级概念,如果存在一个较长时间的计算任务,将影响到 IO 任务的响应延时。而内核调度器总是优先 IO 任务,使之尽快得到响应。此外,单线程的协程方案并不能从根本上避免阻塞,比如文件操作、内存缺页,这都属于影响到延时的因素。

事件循环方案被认识的一个优势是可以避免上锁,而锁是万恶之源。协程方案基于事件循环方案,似乎继承了不用上锁的优势。然而并不是。上下文切换的边界之外,并不能保证临界区。该上锁的地方仍需要上锁。

差异存在,但该维护的信息并没有更少。如果运行时对系统线程的支持比较好,业务系统使用协程的综合效益并不一定相比线程池更好。我们业内通常意义上的”高并发”,往往只是要达到几k qps,然而 qps 是衡量吞吐而非并发的指标(并发1k意味着同时响应1k个连接,而 qps 衡量一秒响应多少请求,这可以是排队处理,并不一定”并发”),靠线程池并非做不到。但对 python 这类 GIL 运行时而言,这却拥有显著提升性能的优势了,只是这时瓶颈在 GIL,而不在线程。

至于并发量导向的业务,一般也是状态上下文较少的业务,比如推送,这时 callback hell 基本可控,使用协程相比事件循环依然更容易编程,但效益并不显著。

最后尝试总结一下个人的想法:

协程不是趋势,它是一个在历史中被挖掘出来的、对现有问题的一个有用的补充。

适用的场景:
高性能计算,牺牲公平性换取吞吐;
面向 IO Bound 任务,减少 IO 等待上的闲置,这其实和高性能计算领域内的优势是一致的;
Generator 式的流式计算;
消除 Callback Hell,使用同步模型降低开发成本的同时保留更灵活控制流的好处,比如同时发三个请求;这时节约地使用栈,可以充分地发挥 “轻量” 的优势;
但并不是万灵丹:
如果栈使用得不节制,消耗的内存量和系统线程无异,甚至内存管理还不如系统线程(系统线程可以动态地调整虚拟内存,用户线程的 Segmented Stack 方案存在严重的抖动问题,Continous Stack 方案管理不当也会抖动,为了避免抖动则成了空间换时间,而内核在这方面做了多少 heuristic 呢);
IO Bound 任务可以通过调线程池大小在一定程度上缓解,目标是把 CPU 跑满即可,这点线程池的表现可能不完美,但在业务逻辑这个领域是及格的;
此外,一般的 python/ruby 任务并不是严格的 IO Bound,比如 ORM 的对象创建、模版渲染、GC 甚至解释器本身,都是 CPU 大户;单个请求扣去 redis 请求和数据库请求的时间,其它时间是否仍不少呢?
CPU 上长时间的计算,导致用户线程的调度变差,不能更快地响应,单个请求的平均时间反而可能更长(诚然并发可能更高);然而这在 python 这类 GIL 语言来看并不算劣势,甚至比 GIL 的调度更好,至少 gevent 可以知道各 IO 任务的优先级,而 GIL 的调度是事实上的 FIFO;

http://my.oschina.net/dclink/blog/287198
http://chen498402552-163-com.iteye.com/blog/1997633
http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/
http://www.infoq.com/cn/articles/generator-and-asynchronous-programming/

GitHub API其实是一座宝藏,它拥有着海量项目以及开发者的各类信息,可以作为社交编程以及经验软件工程课题的数据载体。

本次教程较大家如何使用scrapy来爬取GitHub API,抓取我们所需要的特定信息。GitHub API 是GitHub基于OAuth2协议开放出来的数据获取接口,我们能够在GitHub API上获取各类信息,比如一个项目的commit,issue,pull request;一个用户的粉丝,关注,提交活动,评论等等。GitHub API 有着详细的官方文档教程,上面各类数据的获取接口地址,以及一些过滤参数等。本教程将以爬取rails的issue信息为例,教大家如何使用scrapy来爬取GitHub API。

再开始爬取之前,我们需要进行一些准备工作,由于GitHub API采用OAuth2认证,需要我们提供认证token。当然不提供token可以进行爬取,但是爬取速率会大大降低。

For requests using Basic Authentication or OAuth, you can make up to 5,000 requests per hour. For unauthenticated requests, the rate limit allows you to make up to 60 requests per hour. Unauthenticated requests are associated with your IP address, and not the user making requests. Note that the Search API has custom rate limit rules.

上面显示如果我们提供token可以每小时进行5000次请求,对于非认证(不提供token)一小时只能提供60次的请求,超出请求速率限制后,会返回状态码403 forbidden。有人会问,即使认证后5000次/小时的速率也有些慢啊,如果要更快地进行爬取,那就多注册几个GitHub账号,同时利用多个账号的token进行爬取。GitHub的爬取限制针对的是每个用户,而不是IP,也就是同一台机器,只要你保证每个账号每小时进行小于5000次的爬取,也是完全没有问题的。

接下来我们来展示如何获取自己的token,其实全名为personal access token,每个账号可以拥有多个token,所以不小心哪一天忘了的话,重新生成一个新的即可。

首先我们需要登录自己的GitHub账号。然后在设置里面有一个personal access token选项:
QQ截图20160830092436
点进去,再点击Generate new token .

选择你想让你的token拥有的权限,一般默认全选即可。点击绿色的Generate token按钮。
QQ截图20160830092627

这样就生成我们的token了。注意一定要在这时候把token保存下来,这将是你在GitHub上最后一次看见你的token。一旦刷新,token就会隐藏掉。这时候只有生成新的token了。

QQ截图20160830092306

好了,有了预先的准备工作,接下来就可以编写爬虫了。这次爬虫采用scrapy,关于scrapy相关的知识就不再赘述了,网上有很多教程。

简单说一下爬取思路。

GitHub API 返回的数据都是json,这极大地方便了数据的解析。我们本次的任务是爬取rails项目的所有issue,先来大概看一下返回的issue是什么样子。由于内容太长,我就直接放链接,大家可以点击去看。

https://api.github.com/repos/rails/rails/issues

我们可以看到返回的页面是一个json数组,每个元素其实就是一个issue.而每个issue里面又有诸多信息,比如number,title,body等等。我们本次任务就爬取rails所有issue的信息number,提交者(user.login),body以及title.

注意,https://api.github.com/repos/rails/rails/issues返回的是按时间倒叙排列的issue,并且每页默认返回30条,我们需要在url后面接上一些参数,来爬取指定的页。我们将url接上参数构造成如下的样子:

https://api.github.com/repos/rails/rails/issues?per_page=99&page=num

其中num代表页数,我们需要从1开始自增。per_page代表每页返回的元素个数,GitHub最大只能制定到99。

所以我们的爬虫,应该是从1开始不停地自增num,直到返回的json数组元素个数不足99,就说明爬取完了。另外,由于GitHub API 爬取速率的限制,我事先准备了10个不同账号的token,对于每次请求,重新自定义请求header,带上不同的token.

下文是源代码:

# -*- coding: utf-8 -*-
__author__ = 'ayonel'
import itertools
import json
import os
import scrapy
from scrapy import Request

class IssueSpider(scrapy.spiders.Spider):

    name = "issue" #爬虫名称
    allowed_domains = ["github.com"] #制定爬取域名
    num = 1 # 页数,默认从第一页开始
    handle_httpstatus_list = [404, 403, 401] #如果返回这个列表中的状态码,爬虫也不会终止
    output_file = open('issue.txt', "a") #输出文件
    #token列表,隐去部分
    token_list = [
        '293a06ac6ed5a746f7314be5a25f3d**********',
        '66de084042a7d3311544c656ad9273**********',
        'a513f61368e16c2da229e38e139a8e**********',
        '9055150c8fd031468af71cbb4e12c5**********',
        'ba119dc83af804327fa9dad8e07718**********',
        'b93e6996a4d76057d16e5e45788fbf**********',
        'c9c13e5c14d6876c76919520c9b05d**********',
        '3e41cbfc0c8878aec935fba68a0d3c**********',
        '402ff55399ca08ca7c886a2031f49f**********',
        '7cb6e20a24000968983b79b5de705c**********',
    ]
    token_iter = itertools.cycle(token_list) #生成循环迭代器,迭代到最后一个token后,会重新开始迭代


    def __init__(self): #初始化
        scrapy.spiders.Spider.__init__(self)

    def __del__(self): #爬虫结束时,关闭文件
        self.output_file.close()

    def start_requests(self):
        start_urls = [] #初始爬取链接列表
        url = "https://api.github.com/repos/rails/rails/issues?per_page=99&page="+str(self.num) #第一条爬取url
        #添加一个爬取请求
        start_urls.append(scrapy.FormRequest(url, headers={
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'en',
            'Authorization': 'token ' + self.token_iter.next(),#这个字段为添加token字段
            }, callback=self.parse))

        return start_urls

    def yield_request(self): #定义一个生成请求函数
        url = "https://api.github.com/repos/rails/rails/issues?per_page=99&page="+str(self.num) #生成url
        #返回请求
        return Request(url,headers={
                'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0',
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Accept-Language': 'en',
                'Authorization': 'token ' + self.token_iter.next(),
                },callback=self.parse)

    #解析函数
    def parse(self, response):
        if response.status in self.handle_httpstatus_list:#如果遇见handle_httpstatus_list中出现的状态码
            self.num += 1 #num自增,相当于直接跳过,可以输出当前url到log文件
            yield self.yield_request() #产生新的请求
            return

        json_data = json.loads(response.body_as_unicode()) #获取json
        length = len(json_data) #获取json长度

        if length == 99:
            self.num = self.num + 1
            for issue in json_data:
                data = {}
                data['number'] = issue['number']
                data['owner'] = issue['user']['login']
                data['title'] = issue['title']
                data['body'] = issue['body']
               
                self.output_file.write(json.dumps(data)+'\n') #输出每一行,格式也为json
            self.output_file.flush()
            yield self.yield_request() #产生新的请求

        elif length < 99: #意味着爬取到最后一页
            for issue in json_data:
               data = {}
                data['number'] = issue['number']
                data['owner'] = issue['user']['login']
                data['title'] = issue['title']
                data['body'] = issue['body']
                self.output_file.write(json.dumps(data)+'\n')
            self.output_file.flush()

多元混合效应逻辑回归(Mixed Effects Logistic Regression)是什么:
混合效应逻辑回归是一种二分类模型,其输出是一组预测变量(自变量)的线性组合,但是样本不是简单地独立的,而是集群式分布,也即某个群体之间存在内部关联。
(Mixed effects logistic regression is used to model binary outcome variables, in which the log odds of the outcomes are modeled as a linear combination of the predictor variables when data are clustered or there are both fixed and random effects.)

举个例子:
A large HMO wants to know what patient and physician factors are most related to whether a patient’s lung cancer goes into remission after treatment as part of a larger study of treatment outcomes and quality of life in patients with lunger cancer.
一个医疗机构想要知道对于骨瘤是否最终治愈,医生和病人之间哪种属性是影响最大的。比如医生的属性:从业经验,学历等等。病人的属性有骨瘤大小,患病时间,性别等等。
我们现在就会有一组样本:

is_cured    doctor  experience    education    patient    tumour_size    tumour_time    patient_sex
   1         Bob      10            1          jav          3              2                 0    
   0         Bob      10            1          hex          4              1                 1
   1         Bob      10            1          lancy        6              4                 0
   1         Jack     5             3          joogh        1              2                 0    
   0         Jack     5             3          silla        4              1                 1
   0         Jack     5             3          grecy        6              4                 0
   1         Jack     5             2          lori         2              2                 0    
   0         Bob      10            2          carl         5              1                 1
   1         Bob      10            2          sharun       6              4                 0

我们现在有这组数据,我们现在要预测一个骨瘤病人能否治愈。

普通的多元线性逻辑回归会把每一组样本看成独立的,也就是样本之间是完全独立的,但是实际中我们可以看到,这些样本之间其实是聚集分布的,它们并不是完全独立。同为Bob医生的样本之间肯定存在某种关联,同为Jack医生的样本之间也肯定存在关联。多元混合效应逻辑回归就是加入一个随机效应(上面这个例子中就是doctor),弥补了普通线性回归视样本之间并不存在关联的不足。

混合效应线性回归更加贴近于真实的世界环境。UCLA有十分棒的教程,大家可以去读一读。

那么如何在R里面使用混合效应回归呢?我以混合效应逻辑为例。
读入数据:

>data <- csv.read("data.csv")

对数据中的计数类型(比如年龄啊,从业经验啊等等)进行正规化。为什么需要正规化,请自行谷歌。

>pvar <- c("experience","education","tumour_size,tumour_time")
>datsc -< data
>datasc[pvar] <- lapply(datasc[pvar], scale) //正规化

引入我们的核心计算包lme4:

>require(lme4)

进行计算:

>result <- glmer(is_cured ~ experience+education+tumour_size+tumour_time+(1|doctor),data=datsc,family=binomial,control=glmerControl(optimizer="bobyqa")) //随机变量doctor放在(1|doctor),其余变量为固定变量,optimizer需要加上,防止不收敛

输出结果:

>summary(result)

我相信每个程序员心里都有一个黑客梦,我也不例外。本科四年,舍友malash大神天天遨游在学校的各种服务器之间,xss,sql注入,shell提权信手拈来,请允许我再次献上我的膝盖。
好了,废话不多说。最近再看一些web安全相关的东东,对sql注入的原理也有了更为深刻的理解,但纸上得来终觉浅,我的四十米大刀早已饥渴难耐了。
0x00 寻找猎物
其实这个猎物我心里早就有了,这次就是拿他来练手。鉴于和谐原因,暂不公布目标。

0x01 先来点小测试
本来还想先从找出管理员登录页面开始,都已经下好了路径扫描工具。但是当时耶稣的光芒照亮了我,手抖了一下,在目标域名后接了个‘/admin’,就直接跳转到管理员登录页面了,无语。。。。
881F332B-CB90-4D8B-907A-024CD7E1BF47
这一下就令偶信心大增,有简单测了一下,验证码是挂了,多次提交验证码不会刷新,这简直是爽的不要不要啊。并且对管理员用户名的正确与否还有提示,简直了。。。
输入错误的管理员用户名,会提示:
2643AB97-03F2-412B-8C89-0D08923E9353
在管理员处输入admin,则提示:
admin
这后台还真是贴心啊。
0x02 burpsuite登场
到这,验证码是挂的,又知道用户名,就唯独差一个密码了。用burpsuite神器截包并暴力破解,大约跑了2个多小时,基本的常见的字典都跑完了,还是没跑出来。我知道这条路现在是行不通了。看来得换一下思路。
0x03 找出后台框架
邮件审查了一下管理员登录页面的源码,
AF454890-A7B2-4C71-9C7F-3B46DF157715
表单提交模块名是xdcms,大概百度了一下,这是一个php写的cms,在我大乌云(天佑我乌云, 世纪佳缘不要碧莲)上找了一番,这框架简直惨不忍睹啊,翻了几页都没翻完。
0x04 Sql注入
逐个试了好几个漏洞,终于找见了一个能用的。直接上代码:
用户名输入:

admin' UNION SELECT 1,2,3,4,5,6,7,8,9,10,11,12,13,14 FROM (SELECT count(1),concat(round(rand(0)),(SELECT concat(username,0×23,password) FROM c_admin LIMIT 0,1))a FROM information_schema.tables GROUP by a)b#

密码随意填,写上验证码,点击登录:
result
可以看到已经爆出了加密的管理员密码,这个密码是两层md5加密。
0x05 万能的淘宝
cmd5上解那个md5串没解出来,还是万能的淘宝给力,这儿就不给广告了。进去之后,也没啥好看的,还是为了和谐,就不放图了,大家自行脑补。
0x06 总结
第一次黑网站,心情还是很激动的。也踩了很多坑,要是早知道用的xdcms,直接来乌云就可以了,还费了一些时间学习burpsuite.其实好多网站漏洞一大堆,在我来之前说不定早有人已经踩过点了。。
在登陆成功的一刹那,还是很有成就感的。这条路好长,且行且珍惜。