VGG16内置于Keras,可以通过keras.applications模块中导入。

--------------------------------------------------------将VGG16 卷积实例化:-------------------------------------------------------------------------------------------------------------------------------------

  1. from keras.applications import VGG16
  2.  
  3. conv_base = VGG16(weights = 'imagenet',#指定模型初始化的权重检查点
  4. include_top = False,
  5. input_shape = (150,150,30))

weights:指定模型初始化的权重检查点、

include_top: 指定模型最后是否包含密集连接分类器。默认情况下,这个密集连接分类器对应于ImageNet的100个类别。如果打算使用自己的密集连接分类器,可以不适用它,置为False。

input_shape: 是输入到网络中的图像张量的形状。这个参数完全是可选的,如果不传入这个参数,那么网络能够处理任意形状的输入。

--------------------------------------------------------查看VGG详细架构:conv_base.summary()----------------------------------------------------------------------------------------------------------

最后一特征图形状为(4,4,512),我们将在这个特征上添加一个密集连接分类器,有两种方式:

方法一:在你的数据集上运行卷积基,将输出保存为numpy数组,然后用这个数据做输入,输入到独立的密集连接分类器中。这种方法速度快,计算代价低,因为对于每个输入图像只需运行一次卷积基,而卷积基是日前流程中计算代价最高的。但这种方法不允许使用数据增强。

方法二:在顶部添加Dense层来扩展已有模型,并在输入数据上端到端地运行整个模型。这样你可以使用数据增强,因为每个输入图像进入模型时都会经过卷积基。但这种方法的计算代价比第一种要高很多。

  1. #方法一:不使用数据增强的快速特征提取
  2. import os
  3. import numpy as np
  4. from keras.preprocessing.image import ImageDataGenerator
  5.  
  6. base_dir = 'cats_dogs_images'
  7.  
  8. train_dir = os.path.join(base_dir,'train')
  9. validation_dir = os.path.join(base_dir,'validation')
  10. test_dir = os.path.join(base_dir,'test')
  11.  
  12. datagen = ImageDataGenerator(rescale = 1./255)#将所有图像乘以1/255缩放
  13. batch_size = 20
  14.  
  15. def extract_features(directory,sample_count):
  16. features = np.zeros(shape=(sample_count,4,4,512))
  17. labels = np.zeros(shape=(sample_count))
  18. # 通过.flow或.flow_from_directory(directory)方法实例化一个针对图像batch的生成器,这些生成器
  19. # 可以被用作keras模型相关方法的输入,如fit_generator,evaluate_generator和predict_generator
  20. generator = datagen.flow_from_directory(
  21. directory,
  22. target_size = (150,150),
  23. batch_size = batch_size,
  24. class_mode = 'binary')
  25. i = 0
  26. for inputs_batch,labels_batch in generator:
  27. features_batch = conv_base.predict(inputs_batch)
  28. features[i * batch_size:(i+1) * batch_size] = features_batch
  29. labels[i * batch_size:(i+1)*batch_size] = labels_batch
  30. i += 1
  31. if i * batch_size >= sample_count:
  32. break
  33. return features,labels
  34.  
  35. train_features,train_labels = extract_features(train_dir,2000)
  36. validation_features,validation_labels = extract_features(validataion_dir,1000)
  37. test_features,test_labels = extract_features(test_dir,1000)
  38.  
  39. #要将特征(samples,4,4,512)输入密集连接分类器中,首先必须将其形状展平为(samples,8192)
  40.  
  41. train_features = np.reshape(train_features,(2000,4*4*512))
  42. validation_features = np.reshape(validation_features,(2000,4*4*512))
  43. test_features = np.reshape(test_features,(2000,4*4*512))
  44.  
  45. #定义并训练密集连接分类器
  46. from keras import models
  47. from keras import layers
  48. from keras import optimizers
  49.  
  50. model = models.Sequential()
  51. model.add(layers.Dense(256,activation='relu',input_dim=4*4*512))
  52. model.add(layers.Dropout(0.5))
  53. model.add(layers.Dense(1,activation='sigmoid'))
  54.  
  55. model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
  56. loss='binary_crossentropy',
  57. metrics = ['acc'])
  58.  
  59. history = model.fit(train_features,train_labels,
  60. epochs=30,batch_size=20,
  61. validation_data = (validation_features,validation_labels) )

  1. #在卷积基上添加一个密集连接分类器
  2.  
  3. from keras import models
  4. from keras import layers
  5.  
  6. model = models.Sequential()
  7. model.add(conv_base)
  8. model.add(layers.Flatten())
  9. model.add(layers.Dense(256,activation='relu'))
  10. model.add(layers.Dense(1,activation='sigmoid'))
  11.  
  12. model.summary()

如上图,VGG16的卷积基有14714688个参数,非常多。在其上添加的分类器有200万个参数。

在编译和训练模型之前,一定要“冻结”卷积基。

冻结指一个或多个层在训练过程中保持其权重不变

如果不这么做,那么卷积基之前学到的表示将会在训练过程中被修改。因为其上添加的Dense层是随机初始化的,所以非常大的权重更新将会在网络中传播,对之前学到的表示造成很大破坏。

在keras中,冻结网络的方法是将其trainable属性设置为False

eg. conv_base.trainable = False

  1. #使用冻结的卷积基端到端地训练模型
  2.  
  3. conv_base.trainable = False
  4. from keras.preprocessing.image import ImageDataGenerator
  5. from keras import optimizers
  6.  
  7. train_datagen = ImageDataGenerator(
  8. rescale = 1./255,
  9. rotation_range = 40,
  10. width_shift_range = 0.2,
  11. height_shift_range = 0.2,
  12. shear_range = 0.2,
  13. zoom_range = 0.2,
  14. horizontal_flip = True,
  15. fill_mode = 'nearest'
  16. )
  17.  
  18. test_datagen = ImageDataGenerator(rescale=1./255)
  19.  
  20. train_generator = train_datagen.flow_from_directory(
  21. train_dir,
  22. target_size = (150,150),
  23. batch_size = 20,
  24. class_mode = 'binary'
  25. )
  26.  
  27. validation_generator = test_datagen.flow_from_directory(
  28. validation_dir,
  29. target_size = (150,150),
  30. batch_size = 20,
  31. class_mode = 'binary'
  32. )
  33.  
  34. model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
  35. loss='binary_crossentropy',
  36. metrics = ['acc'])
  37.  
  38. history = model.fit_generator(
  39. train_generator,
  40. steps_per_epoch = 100,
  41. epochs = 30,
  42. validation_data = validation_generator,
  43. validation_steps=50
  44. )
  1. #绘制损失曲线和精度曲线
  2. import matplotlib.pyplot as plt
  3.  
  4. acc = history.history['acc']
  5. val_acc = history.history['val_acc']
  6. loss = history.history['loss']
  7. val_loss = history.history['val_loss']
  8.  
  9. epochs = range(1,len(acc)+1)
  10.  
  11. plt.plot(epochs,acc,'bo',label='Training_acc')
  12. plt.plot(epochs,val_acc,'b',label='Validation_acc')
  13. plt.title('Traing and validation accuracy')
  14. plt.legend()
  15.  
  16. plt.figure()
  17.  
  18. plt.plot(epochs,loss,'bo',label='Training loss')
  19. plt.plot(epochs,val_loss,'b',label='Validation_loss')
  20. plt.title('Traing and validation loss')
  21. plt.legend()
  22.  
  23. plt.show()
 

电脑带不起来,慢死,这边就不截图了。

验证精度约为96%

--------------------------------------------------------微调模型--------------------------------------------------------------------------------------------------------------------------------------------------------------

另外一种广泛使用的模型复用方法是模型微调,与特征提取互为补充。

对于用于特征提取的冻结的模型基,微调是指将其顶部的几层“解冻”,并将这解冻的几层和新增加的部分联合训练。之所以叫作微调,是因为他只是略微调整了所复用模型中更加抽象的表示,

以便让这些表示与手头的问题更加相关。

冻结VGG16的卷积基是为了能够在上面训练一个随机初始化的分类器。同理,只有上面的分类器训练好了,才能微调卷积基的顶部几层。如果分类器没有训练好,那么训练期间通过网络传播的

误差信号会特别大,微调的几层之前学到的表示都会被破坏。因此,微调网络的步骤如下:

(1)在已经训练好的基网络上添加自定义网络

(2)冻结基网络

(3)训练所添加的部分

(4)解冻基网络的一些层

(5)联合训练解冻的这些层和添加的部分

  1. # 冻结直到某一层的所有层
  2. #仅微调卷积基的最后的两三层
  3. conv_base.trainable = True
  4.  
  5. set_trainable = False
  6. for layer in conv_base.layers[:-1]:
  7. if layer.name == 'block5_conv1':
  8. set_trainable = True
  9. if set_trainable:
  10. print(layer)
  11. set_trainable = True
  12. else:
  13. set_trainable = False

  1. #微调模型
  2. model.compile(loss='binary_crossentropy',
  3. optimizer = optimizers.RMSprop(lr=1e-5),
  4. metrics = ['acc'])
  5. history = model.fit_generator(
  6. train_generator,
  7. steps_per_epoch=10,
  8. epochs = 5,
  9. validation_data = validation_generator,
  10. validation_steps = 50
  11. )
 
  1. test_generator = test_datagen.flow_from_directory(
  2. test_dir,
  3. target_size = (150,150),
  4. batch_size = 20,
  5. class_mode = 'binary'
  6. )
  7.  
  8. test_loss,test_acc = model.evaluate_generator(test_generator,steps=50)

得到了97%的测试精度

  1. #使曲线变得平滑
  2. def smooth_curve(points,factor = 0.2):
  3. smoothed_points = []
  4. for point in points:
  5. if smoothed_points:
  6. previous = smoothed_points[-1]
  7. smoothed_points.append(previous*factor+point*(1-factor))
  8. else:
  9. smoothed_points.append(point)
  10. return smoothed_points
 

为什么不微调更多层?为什么不微调整个卷积基?当然可以这么做,但需要先考虑以下几点:

(1)卷积基中更靠底部的层编码的是更加通用的可复用特征,而更靠顶部的层编码的是更专业化的特征。微调这些更专业化的特征更加有用,因为它们需要在你的新问题上改变用途。

微调更靠底部的层,得到的汇报会更少

(2)训练的参数越多,过拟合的风险越大。卷积基有1500万个参数,所以在你的小型数据集上训练这么多参数是有风险的。

--------------------------------------------------------卷积神经网络的可视化------------------------------------------------------------------------------------------------------------------------------------------------------------------------

三种最容易理解也最有用的方法:

(1)可视化卷积神经网络的中间输出(中间激活):有助于理解卷积神经网络连续的层如何对输入进行变换,也有助于初步了解神经网络每个过滤器的含义

(2)可视化卷积神经网络的过滤器:有助于精确理解卷积神经网络中每个过滤器容易接受的视觉模式或视觉概念

(3)可视化图像中类激活的热力图:有助于理解图像的哪个部分被识别为属于哪个类别,从而可以定位图像中的物体

可视化卷积神经网络的中间输出(中间激活) 可视化卷积神经网络的过滤器

from keras.models import load_model

model = load_model('cats_and_dogs_small_2.h5')

model.summary()

  1. #为过滤器的可视化定义损失张量
  2. from keras.applications import VGG16
  3. from keras import backend as K
  4.  
  5. model = VGG16(weights = 'imagenet',include_top = False)
  6.  
  7. def generate_pattern(layer_name,filter_index,size=150):
  8. layer_output = model.get_layer(layer_name).output
  9. loss = K.mean(layer_output[:,:,:,filter_index])
  10.  
  11. #获取损失相对于输入的梯度
  12. grads = K.gradients(loss,model.input)[0]
  13.  
  14. #梯度标准化
  15. grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)#1e-5防止不小心除以0
  16. #给定numpy输入值,得到numpy输出值
  17. iterate = K.function([model.input],[loss,grads])
  18.  
  19. #通过随机梯度下降让损失最大化
  20. input_img_data = np.random.random((1,size,size,3))*20+128.
  21. step = 1.
  22. for i in range(40):
  23. loss_value,grads_value = iterate([input_img_data])
  24. input_img_data += grads_value * step
  25.  
  26. img = input_img_data[0]
  27. return deprocess_image(img)
  1. #预处理单张图像
  2. img_path = 'cats_and_dogs_small/test/cats/cat.1501.jpg'
  3.  
  4. from keras.preprocessing import image
  5. import numpy as np
  6.  
  7. img = image.load_img(img_path,target_size=(150,150))
  8. img_tensor = image.img_to_array(img)
  9. # img_tensor = np.expand_dims(img_tensor,axis=0)
  10. img_tensor = img_tensor.reshape((1,)+img_tensor.shape)
  11. img_tensor /= 255.
  12.  
  13. print(img_tensor.shape)

  1. #将张量转化为有效图像的实用函数
  2. def deprocess_image(x):
  3. x -= x.mean()
  4. x /= (x.std() + 1e-5)
  5. x *= 0.1
  6. x += 0.5
  7. x = np.clip(x,0,1)
  8.  
  9. x *= 255
  10. x = np.clip(x,0,255).astype('uint8')
  11. return
  1.  

#显示测试图像
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()

plt.imshow(generate_pattern('block3_conv1',0))

 

 

  1. #用一个输入张量和一个输出张量列表将模型实例化
  2. from keras import models
  3.  
  4. layer_outputs = [layer.output for layer in model.layers[:8]]
  5. activation_model = models.Model(inputs=model.input,outputs=layer_outputs)#创建一个模型,给定模型输入,可以返回这些输出
  6. #这个模型有一个输入和8个输出,即每层激活对于一个输出

  1. #生成某一层中所有过滤器响应模式组成的网络
  2. def create_vision(layer_name):
  3. size = 64
  4. margin = 5
  5.  
  6. results = np.zeros((8 * size + 7 * margin , 8 * size + 7*margin ,3))
  7. for i in range(8):
  8. for j in range(8):
  9. filter_img = generate_pattern(layer_name, i + (j * 8), size = size)
  10. horizontal_start = i * size + i * margin
  11. horizontal_end = horizontal_start + size
  12. vertical_start = j*size + j * margin
  13. vertical_end = vertical_start + size
  14. results[horizontal_start:horizontal_end, vertical_start:vertical_end, :] = filter_img
  15. plt.figure(figsize=(20, 20))
  16. plt.imshow(results.astype('uint8'))
  17. #不知为何deprocess_image无效,使得results矩阵并不是uint8格式,故需要转换否则不显示
  1. #以预测模式运行模型
  2. activations = activation_model.predict(img_tensor) #返回8个numpy数组组成的列表,每个层激活对应一个numpy数组
  3.  
  4. first_layer_activation = activations[3] #first_layer_activation.shape--> 32个通道 (1, 148, 148, 32)
  5.  
  6. import matplotlib.pyplot as plt
  7. plt.matshow(first_layer_activation[0,:,:,15],cmap='viridis')

layer_name = 'block1_conv1'

layer_name = 'block4_conv1'

  1. #将每个中间激活的所有通道可视化
  2. layer_names = []
  3. for layer in model.layers[:8]:
  4. layer_names.append(layer.name)#层的名字
  5.  
  6. images_per_row = 16
  7.  
  8. for layer_name,layer_activation in zip(layer_names,activations):
  9. n_features = layer_activation.shape[-1] #[32, 32, 64, 64, 128, 128, 128, 128] 特征个数
  10.  
  11. size = layer_activation.shape[1] #特征图的形状为(1,size,size,n_features)
  12.  
  13. n_cols = n_features // images_per_row
  14. display_grid = np.zeros((size*n_cols,images_per_row*size))
  15.  
  16. for col in range(n_cols):
  17. for row in range(images_per_row):
  18. channel_image = layer_activation[0,:,:,col*images_per_row + row]
  19. channel_image -= channel_image.mean()
  20. channel_image /= channel_image.std()
  21. channel_image *= 64
  22. channel_image += 128
  23. channel_image = np.clip(channel_image,0,255).astype('uint8')
  24. display_grid[col*size:(col+1)*size,row*size:(row+1)*size]=channel_image
  25. scale = 1./size
  26. plt.figure(figsize=(scale*display_grid.shape[1],
  27. scale*display_grid.shape[0]))
  28. plt.title(layer_name)
  29. plt.grid(False)
  30. plt.imshow(display_grid,aspect='auto',cmap='viridis')
 
 

 

可视化图像中类激活的热力图

  • 有助于了解一张图像的哪一步分让卷积神经网络做出了最终的分类决策。
  • 这有助于对CNN进行调试,特别是在分类错误的情况下。
  • 这种方法还能定位图像中的特定目标

from keras.applications import VGG16

model = VGG16(weights = 'imagenet',include_top = True)

 
  1. #为VGG16模型预处理一张输入图像
  2. from keras.preprocessing import image
  3. from keras.applications.vgg16 import preprocess_input,decode_predictions
  4. import numpy as np
  5.  
  6. img_path = 'E:/软件/nxf_software/pythonht/nxf_practice/keras/大象.jpg'
  7.  
  8. img = image.load_img(img_path,target_size=(224,224))#读取图像并调整大小
  9.  
  10. x = image.img_to_array(img) # ==> array(150,150,3)
  11.  
  12. plt.figure()
  13. implot = plt.imshow(image.array_to_img(x))
  14.  
  15. plt.show()
 
  1. x = x.reshape((1,)+x.shape) # ==> array(1,150,150,3)
  2. x = preprocess_input(x)#对批量进行预处理(按通道进行颜色标准化)

 preds = model.predict(x)
 np.argmax(preds[0])

 decode_predictions(preds,top=3)[0]

 

  1. #为了展示图像中哪些部分最像非洲象,我们使用Grad-CAM算法
  2.  
  3. african_elephant_output = model.output[:386] #预测向量中的'非洲象'元素
  4.  
  5. last_conv_layer = model.get_layer('block5_conv3')#VGG16的最后一个卷积层
  6.  
  7. grads = K.gradients(african_elephant_output,last_conv_layer)[0]
  8.  
  9. iterate = K.function([model.input],[pooled_grads,last_conv_layer.output[0]])
  10.  
  11. pooled_grads_value,conv_layer_output_value = iterate([x])
  12.  
  13. for i in range(512):
  14. conv_layer_output_value[:,:,i] *= pooled_grads_value[i]
  15.  
  16. headmap = np.mean(conv_layer_output_value,axis = -1)
 

heatmap = np.maximum(heatmap,0)

headmap /= np.max(heatmap)

plt.matshow(heatmap)

 

  1. #将热力图与原始图像叠加
  2. import cv2
  3.  
  4. img = cv2.imread(img_path) #用cv2加载原始图像
  5.  
  6. heatmap = cv2.resize(heatmap,(img.shape[1],img.shape[0]))#将热力图的大小调整为与原始图像相同
  7.  
  8. heatmap = np.uint8(255 * heatmap)#将热力图转换为RGB格式
  9.  
  10. heatmap = cv2.applyColorMap(heatmap,cv2.COLORMAP_JET)#将热力图应用于原始图像
  11.  
  12. superimposed_img = heatmap * 0.4 + img #0.4是热力图强度因子
  13.  
  14. cv2.imwrite('E:/软件/nxf_software/pythonht/nxf_practice/keras/大象1.jpg',superimposed_img) #将图像保存到硬盘
 

这种可视化方法回答了两个重要问题

  • 网络为什么会认为这张图像中包含一头非洲象
  • 非洲象在图像中的什么位置
 
   

1.keras实现-->使用预训练的卷积神经网络(VGG16)的更多相关文章

  1. keras中VGG19预训练模型的使用

    keras提供了VGG19在ImageNet上的预训练权重模型文件,其他可用的模型还有VGG16.Xception.ResNet50.InceptionV3 4个. VGG19在keras中的定义: ...

  2. 卷积神经网络(CNN)的训练过程

    卷积神经网络的训练过程 卷积神经网络的训练过程分为两个阶段.第一个阶段是数据由低层次向高层次传播的阶段,即前向传播阶段.另外一个阶段是,当前向传播得出的结果与预期不相符时,将误差从高层次向底层次进行传 ...

  3. LeNet - Python中的卷积神经网络

    本教程将  主要面向代码,  旨在帮助您 深入学习和卷积神经网络.由于这个意图,我  不会花很多时间讨论激活功能,池层或密集/完全连接的层 - 将来会有  很多教程在PyImageSearch博客上将 ...

  4. 卷积神经网络CNN在自然语言处理中的应用

    卷积神经网络(Convolution Neural Network, CNN)在数字图像处理领域取得了巨大的成功,从而掀起了深度学习在自然语言处理领域(Natural Language Process ...

  5. TensorFlow实战之实现AlexNet经典卷积神经网络

    本文根据最近学习TensorFlow书籍网络文章的情况,特将一些学习心得做了总结,详情如下.如有不当之处,请各位大拿多多指点,在此谢过. 一.AlexNet模型及其基本原理阐述 1.关于AlexNet ...

  6. TensorFlow实现卷积神经网络

    1 卷积神经网络简介 在介绍卷积神经网络(CNN)之前,我们需要了解全连接神经网络与卷积神经网络的区别,下面先看一下两者的结构,如下所示: 图1 全连接神经网络与卷积神经网络结构 虽然上图中显示的全连 ...

  7. 直白介绍卷积神经网络(CNN)【转】

    英文地址:https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/ 中文译文:http://mp.weixin.qq.com/s ...

  8. 论文解读丨基于局部特征保留的图卷积神经网络架构(LPD-GCN)

    摘要:本文提出一种基于局部特征保留的图卷积网络架构,与最新的对比算法相比,该方法在多个数据集上的图分类性能得到大幅度提升,泛化性能也得到了改善. 本文分享自华为云社区<论文解读:基于局部特征保留 ...

  9. 在Keras模型中one-hot编码,Embedding层,使用预训练的词向量/处理图片

    最近看了吴恩达老师的深度学习课程,又看了python深度学习这本书,对深度学习有了大概的了解,但是在实战的时候, 还是会有一些细枝末节没有完全弄懂,这篇文章就用来总结一下用keras实现深度学习算法的 ...

随机推荐

  1. 二、K3 Cloud 开发插件《K3 Cloud 常用数据表整理》

    一.数据库查询常用表 --查询数据表select * from ( ),t1.FKERNELXML.query('//TableName')) as 'Item',t1.FKERNELXML,t2.F ...

  2. LeetCode 35 Search Insert Position(查找插入位置)

    题目链接: https://leetcode.com/problems/search-insert-position/?tab=Description   在给定的有序数组中插入一个目标数字,求出插入 ...

  3. sencha touch 视图(view) activate与deactivate事件探讨

    在sencha touch2.2中采用card布局 之前的需求是考虑show,hide事件发现不可取 http://www.cnblogs.com/mlzs/archive/2013/06/13/31 ...

  4. javascript 原型世界浅析

    一. 无中生有 起初,什么都没有. 造物主说:没有东西本身也是一种东西啊,于是就有了null: 现在我们要造点儿东西出来.但是没有原料怎么办? 有一个声音说:不是有null嘛? 另一个声音说:可是nu ...

  5. github命令行下载项目源码

    一.git clone [URL] 下载指定ur的源码 $ git clone https://github.com/jquery/jquery 二.指定参数, -b是分支, --depth 1 最新 ...

  6. ASP.NET Cookie概念、CURD操作、原理、实际运用

    会话就WEB开发来说,一个会话就是你通过浏览器与服务器之间的一次通话,只不过这种通话是以用浏览器浏览的方式来实现的. 就会话的应用来说,一般会话是用来识别用户的,比如你可以使用会话级变量记录当前用户已 ...

  7. kvm/qemu虚拟机桥接网络创建与配置

    首先阐述一下kvm与qemu的关系,kvm是修改过的qemu,而且使用了硬件支持的仿真,仿真速度比QEMU快. 配置kvm/qemu的网络有两种方法.其一,默认方式为用户模式网络(Usermode N ...

  8. CentOS7下Elastic Stack 5.0日志分析系统搭建

    原文链接:http://www.2cto.com/net/201612/572296_3.html 在http://localhost:5601下新建索引页面输入“metricbeat-*”,之后ki ...

  9. 【CF903G】Yet Another Maxflow Problem 线段树

    [CF903G]Yet Another Maxflow Problem 题意:一张图分为两部分,左边有n个点A,右边有m个点B,所有Ai->Ai+1有边,所有Bi->Bi+1有边,某些Ai ...

  10. C++ 术语(C++ Primer)

    argument(实参):传递给被调用函数的值.block(块):花括号括起来的语句序列.buffer(缓冲区):一段用来存放数据的存储区域.IO 设备常存储输入(或输出)到缓冲区,并独立于程序动作对 ...