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
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!