003.00 监督式学习

003.00 监督式学习#

建檔日期: 2019/09/17#
更新日期: None#

相关软件信息:

Win 10 Python 3.7.2

说明:所有内容欢迎引用,只需注明来源及作者,本文内容如有错误或用词不当,敬请指正.

主题: 003.00 监督式学习#

前言:#

看到人工智能,机器学习,再到深度学习,一大堆杂七杂八的数据,得花多久的时间才能搞懂作什么,先找了一个范例,这个范例是采用 100 张的明星照片,建立一个模型用来分辨照片中的明星是男是女,先区分为 80 张的训练组及 20 张的测试组,除了图片以外,还要自行确认每张照片中的明星是男是女,建立性别数据。该模型输出只有两种类别,男或女,或者说是不是男的,或者说是不是女的,这是一种二选一分类的模型,不同的功能会有不同的模型,这里采用的是逻辑回归的算法.

#
本范例主要的是采用 numpy 来作所有的处理,而不是如其的人工智能库给你一个函数,什么都作完了,这样你不会清楚知道发生什么事。在本范例中了解了整个模型建构及算法细节,感觉在人工智能上跨了一大步.#

范例来源: https://datascienceintuition.wordpress.com...

要一步一步的说明细节,不如看下图比较直接,后面再稍作解释,就容易看懂了.#

Python

1. 准备数据作为输入#

a. 照片来源#

照片当然可以自己拍,不过挺花时间的,在学习阶段,直接上网下载就行,网上有很多数据库免费提供,比如: https://lionbridge.ai/datasets/the-50-best....


一般的库大都是一行就下载下来,这里是从网页中读取 100 张的照片,存放到子目录中,下次就不用再下载一次

def load_100_pictures():
    if not os.path.exists('img_align_celeba'):
        os.mkdir('img_align_celeba')
        for img_i in range(1, 101):
            f = '000%03d.jpg' % img_i
            url = 'https://s3.amazonaws.com/cadl/celeb-align/' + f
            print(url, end='\r')
            urllib.request.urlretrieve(url, os.path.join('img_align_celeba', f))
    print('Celeb Net dataset already downloaded')
b. 资料分为两群,训练群及测试群.#

一般测试群大约占 1/4 数据,这个比例可以自行分配。训练群用来让模型找到自己内部最佳的参数,也就是 wi 及 b. 测试群用来测试最后模型的准确性.

c. 照片为 jpg 檔#

每一个像素都含有三个 0~255 的 R,G,B 值,光的三要素 R (红),G (绿),B (蓝) 合成在一起,才是该像素的颜色.

d. 照片资料的 shape 为 (100 张,218 高,178 寛,3 色)#

在输入到模型中,必须先处理一下,才能符合模型的输入要求。除了简单化,也可以减少内存的使用和处理的时间。不过,过度的简化可能会造成信息量的流失,造成模型建造失败.

e. 每张照片高 218 像素,寛 178 像素#

我们先裁成 (178, 178) 的正方形。照片群就变成 (100 张,178 高,178 寛,3 色), 数据量一下就少了 20% 左右.

def imcrop_tosquare(img):
    if img.shape[0] > img.shape[1]:
        extra = (img.shape[0] - img.shape[1]) // 2
        crop = img[extra:-extra, :]
    elif img.shape[1] > img.shape[0]:
        extra = (img.shape[1] - img.shape[0]) // 2
        crop = img[:, extra:-extra]
    else:
        crop = img
    return crop
f. 将照片的尺寸由 (178, 178) 改为 (64, 64)#

缩小的照小在人眼看来,仍然可以轻易辨认出性别来。照片群就变成 (100 张,64 高,64 寛,3 色), 因为这个尺寸是以平方来计算的,所以数据量一下就少了 87% 左右,总计数据量只有约原来的 10%. 当然,我们还可以再把 RGB 三色转换为灰阶,数据量就会变成只有约原来的 3.3%, 不过我们还是保留没有这样作,当然各位可以试试.

imgs = []
for file_i in files:
    # 照片格式都是(218, 178, 3), 高218, 寛178, RGB三色/每点
    img = plt.imread(file_i)
    # 裁成(178,178,3), 正方形照片
    square = imcrop_tosquare(img)
    # 以LANCZOS方法来重设大小为(64,64,3),
    # 足以辨识就可以了, 照片太大, 处理时间与内存都会占用太多
    rsz = np.array(Image.fromarray(square).resize((64, 64), resample=Image.LANCZOS))
    # list结果为(100, 64, 64, 3), 100笔, 64x64, RGB三色的照片资料
    imgs.append(rsz)
g. 再把数据从 0 ~ 255 转为 0 ~ 1#

在逻辑回归計算时,有使用到指数的运算,大于 1 的数,很容易就会造成系统数值溢出错误.

data = np.array(imgs)/255
h. 建立資料标签#

照片资料中并没有我们所要的性别数据,所以我们要自己看照片判断建立。还有建立两个分类的标签 [‘Male 男’, ‘Female 女’], 这里我们定义 0 为男性,1 为女性.

classes=np.array(['Male', 'Female'])
y=np.array([1,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,1,0,1,1,1,1,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,1,0,0,1,0,0,0,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1])
y=y.reshape(1,y.shape[0])
i. 把照片分为两群,训练群及测试群#
train_x_orig=data[:80,:,:,:]
test_x_orig=data[80:,:,:,:]
y_train=y[:,:80]
y_test=y[:,80:]
j. 最后我们要把二维的像素数据转换为一维的像素数据#

这样才能符合模型的输入要求。转换的方式是把每一行的点连续接在一起再转置,如此一来,训练组照片从 (80, 64, 64, 3) 变为 (12288,80), 测试组照片从 (20,64,64,3) 变为 (12288,20).

# 训练数据80笔, 测试资料20笔, 照片每边64点
m_train = train_x_orig.shape[0]
m_test = y_test.shape[1]
num_px = train_x_orig.shape[1]
# (80, 64, 64, 3) -> (1228,80), (20,64,64,3) -> (1228,20)
train_x = train_x_orig.reshape(train_x_orig.shape[0],-1).T
test_x = test_x_orig.reshape(test_x_orig.shape[0],-1).T

2. 对每一笔输入训练用数据的特征值 X 或 xi 随意设置权重 W 或 wi, 并随意设置一个偏差值 b#

在这里,简单为主,我们都设为 0

    w = np.zeros((1,12288))
    b = 0

3. 计算 sigma, Σ 值#

只要在计算前把矩阵的 shape, 也就是数据摆对方向及位置,就会很容易计算,这也是 numpy 数组强大的所在,简单的一个运算就把事情作完了.

sigma =np.dot(w,X)+b

4. 使用 sigmoid 函数#

将 Σ 作非线性转换成 - 1 ~ 1 的数值,得到我们的预测值 A 或 a, A 中的每一项 a 就代表该照片经模型预测的性别结果,因为在模型中,利用预测 A 与实际 Y 的差异去调整权重 wi 及偏差 b, 以得到更接近实际 Y 的预测 A, 这也是负回馈的一种应用.

def sigmoid(z):
    # 建立sigmoid函数, 将输入值非线性转换为-1~+1的数值
    s = 1/(1+np.exp(-z))
    return s
A = sigmoid(np.dot(w,X)+b)

5. 以逻辑回归的方式,计算误差值#

这里称为成本 cost, 原则上,cost 越小越好,因为 cost 可以代表所有预测值与实际值的差异或误差值总和.

cost = (-1/m)*(np.dot(Y,np.log(A.T))+ np.dot((1-Y),np.log((1-A).T)))

6. 根据输入的特征值,预测值及测试用的实际值,计算成本对权重 wi 的导数 dw (实际上是 dcost/dw), 以及对偏差值 b 的导数 db (实际上是 dcost/db)#

在 sigma Σ 中,xi 不是一个变量,是已知的输入数据,wi 及 b 才是在模型作训练时的变量,所以我们要对 wi 及 b 来求导数,用来作为回归方法中的反馈参数。导数代表的意义是指该变量的变化量,所造成输出的变化量,导数值越大,变化量越大,我们要调整 wi 或 b 的量就越大,越接近最低点,导数值就越小,一直到导数值为 0, 或足够接近 0, 最低点的导数或斜率就是 0.

    dw = (1/m)*np.dot((A-Y),X.T)
    db = (1/m)*np.sum((A-Y))

7. 根据成本对权重 wi 的导数,对偏差值 b 的导数,以及 alpha 或 α, 修改 wi 及 b#

这里的 alpha 或 α, 用来对调整的量作一个限制,因为调整太多,会找不到最低点,调整太少会花太多的时间在调整上,所以 alpha 或 α 被称为学习速率。一般来说,alpha 值大都在 1 以下,当然也有使用 1 以上的情况,甚至可能是上千。这个值的设置是随情况而定的,也可以说是试出来的.

        w = w-learning_rate*dw
        b = b-learning_rate*db

8. 重复 N 次步骤 3 到步骤 7, 得到最小的 cost, 最适当的 wi 及 b 值.#

N 是模型更新 wi 及 b 的次数,多少次才是对的,这是不一定的,因为要看模型的复杂度,alpha 值的选择,通常有个上限,因为作得再多次,结果变化也不大。模型中所用的方法是数值分析的一种,基本上,作的更多次,会更靠近目标点,只是更靠近,并不一定会落在目标点上。简单的模型,可能很快就可以找到目标点,但在复杂的模型中,作得太多次可能会更糟糕,这种情形就叫作 over-fitting 过度拟合或过适,因为这里追求的是训练组的磨合,结果可能测试组的结果变差了,而我们真正要的结果是测试组的结果.

9. 使用该模型,根据已知最适当的 wi 及 b 值,计算出训练组数据的预测值#

与实际值作比较,确认训练后的正确率是否够好,不够好可以调整 alpha 值及 N 值,也有可能是该模型的建构方式无法符合要求,必须采用不同的模型来重新训练及测试.

10. 使用该模型,根据已知最适当的 wi 及 b 值,计算出测试组数据的预测值#

与实际值作比较,确认训练后,该模型的正确率是否够好,是否可以适用到一般的应用上.

结论:本文所述的模型因为简单,所以在输入层与输出层中,只有一层的 wi 及 b, 所计算出来的训练组正确率达到 100%, 但测试组的正确率却只有 65%, 可以有很多的方式来改善,比如:#

  • 这里用的梯度下降法是最常用的方法,但只能在简单的模型中找到目标点;在复杂的模型中可能只是找到局部的目标点,而不是全局的目标点.
  • 使用更多层的 wi 和 b, 这代表更复杂的模型,在很多人工智能的建模都可以看到,如下图.
  • 数据量的保留,缩减通常会造成信息的流失
  • 改变不同的算法,比如模型中的 sigma Σ, sigmoid, cost, 回归法等等

Python

输出结果:#

运算次数与 cost 的对照表
Python
测试组中预测错误的七张照片
Python
原照片与经加权 / 偏移计算的照片比对
Python

附件 (程序)#

# -*- coding: utf-8 -*-
import os
import urllib.request
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

def load_100_pictures():
    if not os.path.exists('img_align_celeba'):
        os.mkdir('img_align_celeba')
        for img_i in range(1, 101):
            f = '000%03d.jpg' % img_i
            url = 'https://s3.amazonaws.com/cadl/celeb-align/' + f
            print(url, end='\r')
            urllib.request.urlretrieve(url, os.path.join('img_align_celeba', f))
    print('Celeb Net dataset already downloaded')

# 从网站下载100张明星照片, 在到img_align_celeba目录下, 如果目录已存在, 就不下载了
load_100_pictures()
# 从目录中取得所有JPG文件的档名(长度100的list)
files = [os.path.join('img_align_celeba', file_i) for file_i in os.listdir('img_align_celeba') if '.jpg' in file_i]

# 自行建立一个数组, 内容为各照片中明星的性别, 0为男性, 1为女性
# shape从(100,)改为(1,100)
y=np.array([1,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,1,0,1,1,1,1,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,1,0,0,1,0,0,0,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1])
y=y.reshape(1,y.shape[0])

# 建立分类标签内容'Male', 'Female'
classes=np.array(['Male', 'Female'])

# 目标分为两群, 前80张为训练用群, 后20张为测试群
y_train=y[:,:80]
y_test=y[:,80:]

# 截取照片成最大的正方形
def imcrop_tosquare(img):
    if img.shape[0] > img.shape[1]:
        extra = (img.shape[0] - img.shape[1]) // 2
        crop = img[extra:-extra, :]
    elif img.shape[1] > img.shape[0]:
        extra = (img.shape[1] - img.shape[0]) // 2
        crop = img[:, extra:-extra]
    else:
        crop = img
    return crop

# 读入所有的照片
imgs = []
for file_i in files:
    # 照片格式都是(218, 178, 3), 高218, 寛178, RGB三色/每点
    img = plt.imread(file_i)
    # 裁成(178,178,3), 正方形照片
    square = imcrop_tosquare(img)
    # 以LANCZOS方法来重设大小为(64,64,3),
    # 足以辨识就可以了, 照片太大, 处理时间与内存都会占用太多
    rsz = np.array(Image.fromarray(square).resize((64, 64), resample=Image.LANCZOS))
    # list结果为(100, 64, 64, 3), 100笔, 64x64, RGB三色的照片资料
    imgs.append(rsz)

# 降低数据大小, 避免在逻辑回归时, 指数运算造成系统数值溢出错误
data = np.array(imgs)/255

# 资料分为两群, 前80张为训练用群, 后20张为测试群
train_x_orig=data[:80,:,:,:]
test_x_orig=data[80:,:,:,:]

# 对只有两种结果的事件, 采用逻辑回归法

# 训练数据80笔, 测试资料20笔, 照片每边64点
m_train = train_x_orig.shape[0]
m_test = y_test.shape[1]
num_px = train_x_orig.shape[1]

# (80, 64, 64, 3) -> (12288,80), (20,64,64,3) -> (12288,20)
train_x = train_x_orig.reshape(train_x_orig.shape[0],-1).T
test_x = test_x_orig.reshape(test_x_orig.shape[0],-1).T

# Initialize parameters W and b

def initialize_with_zeros(dim):
    # 建立w是一个内容都为0, 点数大小的数组(1, 点数12288), b为0
    w = np.zeros((1,dim))
    b = 0
    # 确认w及b符合要求
    assert(w.shape == (1, dim))
    assert(isinstance(b, float) or isinstance(b, int))
    return w, b

def sigmoid(z):
    # 建立sigmoid函数, 将输入值非线性转换为-1~+1的数值
    s = 1/(1+np.exp(-z))
    return s

def propagate(w, b, X, Y):
    '''
    输入
    w -- 加权数组, 对应到一张照片所有的点的三颜色, (点数*3, 1) (12288, 1)
    b -- 偏差, 纯量
    X -- 输入的数据数组, (点数*3, 训练用照片数) (12288, 80)
    Y -- 结果卷标0, 1的数组, (1, 训练用照片数) (1, 80)

    输出:
    cost -- 负对数可能值, 是逻辑回归法中的成本或误差值
    dw -- dcost/dw, (点数*3, 1)
    db -- dcost/db, 纯量

    '''
    m = X.shape[1]
    # 正向传递 (从输入到成本 X -> cost), A预测值(1, 80), cost = -1/m*sum(y*log(a)+(1-y)*log(1-a)
    A = sigmoid(np.dot(w,X)+b)
    cost = (-1/m)*(np.dot(Y,np.log(A.T))+ np.dot((1-Y),np.log((1-A).T)))
    # 反向传递 (找出梯度grads), dZ=A-Y, dw = 1/m*(dZ . XT), db = mean(dZ)
    dw = (1/m)*np.dot((A-Y),X.T)
    db = (1/m)*np.sum((A-Y))
    assert(dw.shape == w.shape)
    assert(db.dtype == float)
    cost = np.squeeze(cost)
    assert(cost.shape == ())
    grads = {"dw": dw,
             "db": db}
    return grads, cost

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
    """
    利用梯度下降算法优化 w, b

    输入
    w -- 加权数组, 对应到一张照片所有的点的三颜色, (点数*3, 1) (12288, 1)
    b -- 偏差, 纯量
    X -- 所有输入的数据数组, (点数*3, 训练用照片数) (12288, 80)
    Y -- 结果卷标0, 1的数组, (1, 训练用照片数) (1, 80)
    num_iterations -- 参数优化运算次数
    learning_rate -- 参数修正学习速率
    print_cost -- 是否每100次运算, 打印成本cost

    输出
    params -- { } w, b
    grads -- {'dw':dw, 'db':db'} cost对w的导数, cost对b的导数
    costs -- [ ] 所有cost记录, 供画图使用

    """

    costs = []

    for i in range(num_iterations):

        # 按输入数据, 目标卷标, 模型中的权重及偏移值计算成本Cost及梯度
        grads, cost = propagate(w, b, X, Y)

        # 从grads中取回dw, db
        dw = grads["dw"]
        db = grads["db"]

        # 更新权重及偏差
        w = w-learning_rate*dw
        b = b-learning_rate*db

        # 每100次记录下cost值
        if i % 100 == 0:
            costs.append(cost)

        # 是否每100次打印cost
        if print_cost and i % 100 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))

    # 画cost曲线
    plt.rcParams['figure.figsize'] = (10.0, 10.0)
    plt.plot(np.squeeze(costs))
    plt.ylabel('cost')
    plt.xlabel('iterations (per hundreds)')
    plt.title("Learning rate =" + str(learning_rate))
    plt.show()
    params = {"w": w, "b": b}
    grads = {"dw": dw, "db": db}

    return params, grads, costs

def predict(w, b, X):
    '''
    按已优化的 w, b 预测输入的照片结果

    输入
    w -- 模型中已优化的权重 (12288, 1)
    b -- 偏差值
    X -- 输入料数组 (12288, 照片数)

    输出
    Y_prediction -- 预测结果
    '''

    m = X.shape[1]
    Y_prediction = np.zeros((1,m))
    #w = w.reshape(X.shape[0], 1)

    # 计算预测值, 并转成0~1的机率值, 再四舍五入取整数
    A = sigmoid(np.dot(w,X)+b)
    Y_prediction=np.round(A)

    assert(Y_prediction.shape == (1, m))

    return Y_prediction

def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
    """
    建立逻辑回归模型

    输入参数
    X_train -- 训练用输入的数据数组, (点数*3, 训练用照片数) (12288, 80)
    Y_train -- 训练用结果卷标0, 1的数组, (1, 训练用照片数) (1, 80)
    X_test -- 测试用输入的数据数组, (点数*3, 测试用照片数) (12288, 20)
    Y_test -- 测试用结果卷标0, 1的数组, (1, 训练用照片数) (1, 20)
    num_iterations -- 参数优化运算次数
    learning_rate -- 参数修正学习速率
    print_cost -- 是否每100次运算, 打印成本cost

    输出:
    d -- { } 有关模型的信息
    """

    # 初值选定 (1, 12288)
    m_train=X_train.shape[0]
    w, b = initialize_with_zeros(m_train)

    # 梯度下降优化
    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations= num_iterations, learning_rate = learning_rate, print_cost = print_cost)

    # 取出 w, b
    w = parameters["w"]
    b = parameters["b"]

    # 检查训练组及测试组
    Y_prediction_test = predict(w, b, X_test)
    Y_prediction_train = predict(w, b, X_train)

    # 打印检查正确率
    print("train accuracy: {} %".format(100*(1 - np.mean(np.abs(Y_prediction_train - Y_train)) )))
    print("test accuracy: {} %".format(100*(1 - np.mean(np.abs(Y_prediction_test - Y_test)) )))

    d = {"costs": costs,
         "Y_prediction_test": Y_prediction_test,
         "Y_prediction_train" : Y_prediction_train,
         "w" : w,
         "b" : b,
         "learning_rate" : learning_rate,
         "num_iterations": num_iterations}

    return d

d = model(train_x, y_train, test_x, y_test, num_iterations = 1000, learning_rate = 0.005, print_cost = True)

def print_mislabeled_images(classes, X, y, p):
    """
    显示在测试组判断错误的照片
    X -- 照片资料组
    y -- 正确标签组
    p -- 预测标签组
    """
    a = p + y
    mislabeled_indices = np.asarray(np.where(a == 1))
    plt.rcParams['figure.figsize'] = (40.0, 40.0) # set default size of plots
    num_images = len(mislabeled_indices[0])
    for i in range(num_images):
        index = mislabeled_indices[1][i]

        plt.subplot(2, num_images, i + 1)
        plt.imshow(X[:,index].reshape(64,64,3), interpolation='sinc')
        plt.axis('off')
        plt.rc('font', size=10)
        plt.title("Prediction: " + classes[int(p[0,index])] + " \n Class: " + classes[y[0,index]])
    plt.show()

print_mislabeled_images(classes, test_x, y_test, d["Y_prediction_test"])

# 使用sinc函数来重建经过比重后的照片

test=d["w"].T*train_x*255
test=test.T.reshape(80,64,64,3)
plt.rcParams['figure.figsize'] = (10.0, 10.0)
plt.imshow(test[0], interpolation='sinc')

# reconstructed data by others

methods = [None, 'none', 'nearest', 'bilinear', 'bicubic', 'spline16',
           'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
           'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']

# Fixing random state for reproducibility
np.random.seed(19680801)

fig, axes = plt.subplots(3, 6, figsize=(24, 12),
                         subplot_kw={'xticks': [], 'yticks': []})

fig.subplots_adjust(hspace=0.3, wspace=0.05)

for ax, interp_method in zip(axes.flat, methods):
    ax.imshow(test[0], interpolation=interp_method, cmap=None)
    ax.set_title(interp_method)

plt.show()

# compare the reconstructed images vs original.

def montage(images, saveto='montage.png'):
    """Draw all images as a montage separated by 1 pixel borders.
    Also saves the file to the destination specified by `saveto`.
    Parameters
    ----------
    images : numpy.ndarray
        Input array to create montage of.  Array should be:
        batch x height x width x channels.
    saveto : str
        Location to save the resulting montage image.
    Returns
    -------
    m : numpy.ndarray
        Montage image.
    """
    if isinstance(images, list):
        images = np.array(images)
    img_h = images.shape[1]
    img_w = images.shape[2]
    n_plots = int(np.ceil(np.sqrt(images.shape[0])))
    if len(images.shape) == 4 and images.shape[3] == 3:
        m = np.ones(
            (images.shape[1] * n_plots + n_plots + 1,
             images.shape[2] * n_plots + n_plots + 1, 3)) * 0.5
    else:
        m = np.ones(
            (images.shape[1] * n_plots + n_plots + 1,
             images.shape[2] * n_plots + n_plots + 1)) * 0.5
    for i in range(n_plots):
        for j in range(n_plots):
            this_filter = i * n_plots + j
            if this_filter < images.shape[0]:
                this_img = images[this_filter]
                m[1 + i + i * img_h:1 + i + (i + 1) * img_h,
                  1 + j + j * img_w:1 + j + (j + 1) * img_w] = this_img
    #plt.imsave(arr=m, fname=saveto)
    return m

compare = np.concatenate((test[52:54], data[52:54]), axis=0)
compare.shape

plt.imshow(montage(compare,saveto='montage.png'),interpolation='spline36')
plt.show()
plt.imshow(montage(compare,saveto='montage.png'),interpolation='bicubic')
plt.show()

# generate the montage with different interpolations for comparison.

methods = [None, 'none', 'nearest', 'bilinear', 'bicubic', 'spline16',
           'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
           'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']

# Fixing random state for reproducibility
np.random.seed(19680801)

fig, axes = plt.subplots(3, 6, figsize=(24, 12),
                         subplot_kw={'xticks': [], 'yticks': []})

fig.subplots_adjust(hspace=0.3, wspace=0.05)

for ax, interp_method in zip(axes.flat, methods):
    ax.imshow(montage(compare,saveto='montage.png'), interpolation=interp_method, cmap=None)
    ax.set_title(interp_method)

plt.show()
本作品采用《CC 协议》,转载必须注明作者和本文链接
Jason Yang