我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用机器学习生成图像描述

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用机器学习生成图像描述

例如,网络模型可以生成与下图相关的以下任何,即“A white dog in a grassy area”,“white dog with brown spots”甚至“A dog on grass and some pink flowers ”。

[[395666]]

数据集

我们选择的数据集为“ Flickr 8k”。 我们之所以选择此数据,是因为它易于访问且具有可以在普通PC上进行训练的完美大小,也足够训练网络生成适当的。 数据分为三组,主要是包含6k图像的训练集,包含1k图像的开发集和包含1k图像的测试集。 每个图像包含5个。 示例之一如下:


A child in a pink dress is climbing up a set of stairs in an entryway.

A girl going into a wooden building.

A little girl climbing into a wooden playhouse.

A little girl climbing the stairs to her playhouse.

A little girl in a pink dress going into a wooden cabin.

数据清理

任何机器学习程序的第一步也是最重要的一步是清理数据并清除所有不需要的数据。在处理中的文本数据时,我们将执行基本的清理步骤,例如将计算机中的所有字母都转换为小写字母“ Hey”和“ hey”是两个完全不同的单词,删除特殊标记和标点符号,例如*, (,£,$,%等),并消除所有包含数字的单词。

我们首先为数据集中的所有唯一内容创建词汇表,即8000(图片数量)* 5(每个图像的)= 40000。我们发现它等于8763。但是这些词中的大多数只出现了1到2次,我们不希望它们出现在我们的模型中,因为这不会使我们的模型对异常值具有鲁棒性。因此,我们将词汇中包含的单词的最少出现次数设置为10个阈值,该阈值等于1652个唯一单词。

我们要做的另一件事是在每个描述中添加两个标记,以指示字幕的开始和结束。这两个标记分别是“ startseq”和“ endseq”,分别表示字幕的开始和结尾。

首先,导入所有必需的库:

  1. import numpy as np   
  2. from numpy import array   
  3. import pandas as pd   
  4. import matplotlib.pyplot as plt   
  5. import string   
  6. import os   
  7. from PIL import Image   
  8. import glob   
  9. import pickle   
  10. from time import time   
  11. from keras.preprocessing import sequence   
  12. from keras.models import Sequential   
  13. from keras.layers import LSTM, Embedding, Dense, Flatten, Reshape, concatenate, Dropout   
  14. from keras.optimizers import Adam   
  15. from keras.layers.merge import add   
  16. from keras.applications.inception_v3 import InceptionV3   
  17. from keras.preprocessing import image   
  18. from keras.models import Model   
  19. from keras import Input, layers   
  20. from keras.applications.inception_v3 import preprocess_input   
  21. from keras.preprocessing.sequence import pad_sequences   
  22. from keras.utils import to_categorical  

让我们定义一些辅助函数:

  1. load descriptions   
  2. def load_doc(filename):   
  3. file = open(filename, 'r')   
  4. text = file.read()   
  5. file.close()   
  6. return text   
  7.   
  8.   
  9. def load_descriptions(doc):   
  10. mapping = dict()   
  11. for line in doc.split('\n'):   
  12. tokens = line.split()   
  13. if len(line) < 2:   
  14. continue   
  15. image_id, image_desc = tokens[0], tokens[1:]   
  16. image_id = image_id.split('.')[0]   
  17. image_desc = ' '.join(image_desc)   
  18. if image_id not in mapping:   
  19. mapping[image_id] = list()   
  20. mapping[image_id].append(image_desc)   
  21. return mapping   
  22.   
  23. def clean_descriptions(descriptions):   
  24. table = str.maketrans('''', string.punctuation)   
  25. for key, desc_list in descriptions.items():   
  26. for i in range(len(desc_list)):   
  27. desc = desc_list[i]   
  28. desc = desc.split()   
  29. desc = [word.lower() for word in desc]   
  30. desc = [w.translate(tablefor w in desc]   
  31. desc = [word for word in desc if len(word)>1]   
  32. desc = [word for word in desc if word.isalpha()]   
  33. desc_list[i] = ' '.join(desc)   
  34.   
  35. return descriptions   
  36.   
  37. # save descriptions to file, one per line   
  38. def save_descriptions(descriptions, filename):   
  39. lines = list()   
  40. for key, desc_list in descriptions.items():   
  41. for desc in desc_list:   
  42. lines.append(key + ' ' + desc)   
  43. data = '\n'.join(lines)   
  44. file = open(filename, 'w')   
  45. file.write(data)   
  46. file.close()   
  47.   
  48.   
  49. load clean descriptions into memory   
  50. def load_clean_descriptions(filename, dataset):   
  51. doc = load_doc(filename)   
  52. descriptions = dict()   
  53. for line in doc.split('\n'):   
  54. tokens = line.split()   
  55. image_id, image_desc = tokens[0], tokens[1:]   
  56. if image_id in dataset:   
  57. if image_id not in descriptions:   
  58. descriptions[image_id] = list()   
  59. desc = 'startseq ' + ' '.join(image_desc) + ' endseq'   
  60. descriptions[image_id].append(desc)   
  61. return descriptions   
  62.   
  63. def load_set(filename):   
  64. doc = load_doc(filename)   
  65. dataset = list()   
  66. for line in doc.split('\n'):   
  67. if len(line) < 1:   
  68. continue   
  69. identifier = line.split('.')[0]   
  70. dataset.append(identifier)   
  71. return set(dataset)   
  72.   
  73. load training dataset   
  74.   
  75.   
  76. filename = "dataset/Flickr8k_text/Flickr8k.token.txt"   
  77. doc = load_doc(filename)   
  78. descriptions = load_descriptions(doc)   
  79. descriptions = clean_descriptions(descriptions)   
  80. save_descriptions(descriptions, 'descriptions.txt')   
  81. filename = 'dataset/Flickr8k_text/Flickr_8k.trainImages.txt'   
  82. train = load_set(filename)   
  83. train_descriptions = load_clean_descriptions('descriptions.txt', train)  

让我们一一解释:

load_doc:获取文件的路径并返回该文件内的内容

load_descriptions:获取包含描述的文件的内容,并生成一个字典,其中以图像id为键,以描述为值列表

clean_descriptions:通过将所有字母都转换为小写字母,忽略数字和标点符号以及仅包含一个字符的单词来清理描述

save_descriptions:将描述字典作为文本文件保存到内存中

load_set:从文本文件加载图像的所有唯一标识符

load_clean_descriptions:使用上面提取的唯一标识符加载所有已清理的描述

数据预处理

接下来,我们对图像和字幕进行一些数据预处理。 图像基本上是我们的特征向量,即我们对网络的输入。 因此,我们需要先将它们转换为固定大小的向量,然后再将其传递到神经网络中。 为此,我们使用了由Google Research [3]创建的Inception V3模型(卷积神经网络)进行迁移学习。 该模型在'ImageNet'数据集[4]上进行了训练,可以对1000张图像进行图像分类,但是我们的目标不是进行分类,因此我们删除了最后一个softmax层,并为每张图像提取了2048个固定矢量,如图所示 以下:


文字是我们模型的输出,即我们必须预测的内容。 但是预测并不会一次全部发生,而是会逐字预测字幕。 为此,我们需要将每个单词编码为固定大小的向量(将在下一部分中完成)。 为此,我们首先需要创建两个字典,即“单词到索引”将每个单词映射到一个索引(在我们的情况下为1到1652),以及“索引到单词”将字典将每个索引 映射到其对应的单词字典。 我们要做的最后一件事是计算在数据集中具有最大长度的描述的长度,以便我们可以填充所有其他内容以保持固定长度。 在我们的情况下,该长度等于34。

字词嵌入

如前所述,我们将每个单词映射到固定大小的向量(即200)中,我们将使用预训练的GLOVE模型。 最后,我们为词汇表中的所有1652个单词创建一个嵌入矩阵,其中为词汇表中的每个单词包含一个固定大小的向量。

  1. Create a list of all the training captions   
  2. all_train_captions = []   
  3. for key, val in train_descriptions.items():   
  4. for cap in val:   
  5. all_train_captions.append(cap)   
  6.   
  7.   
  8. # Consider only words which occur at least 10 times in the corpus   
  9. word_count_threshold = 10   
  10. word_counts = {}   
  11. nsents = 0   
  12. for sent in all_train_captions:   
  13. nsents += 1   
  14. for w in sent.split(' '):   
  15. word_counts[w] = word_counts.get(w, 0) + 1   
  16.   
  17. vocab = [w for w in word_counts if word_counts[w] >= word_count_threshold]   
  18. print('Preprocessed words {} -> {}'.format(len(word_counts), len(vocab)))   
  19.   
  20.   
  21. ixtoword = {}   
  22. wordtoix = {}   
  23.   
  24. ix = 1   
  25. for w in vocab:   
  26. wordtoix[w] = ix   
  27. ixtoword[ix] = w   
  28. ix += 1   
  29.   
  30. vocab_size = len(ixtoword) + 1 # one for appended 0's   
  31.   
  32. Load Glove vectors   
  33. glove_dir = 'glove.6B'   
  34. embeddings_index = {}   
  35. f = open(os.path.join(glove_dir, 'glove.6B.200d.txt'), encoding="utf-8")   
  36.   
  37. for line in f:   
  38. values = line.split()   
  39. word = values[0]   
  40. coefs = np.asarray(values[1:], dtype='float32')   
  41. embeddings_index[word] = coefs   
  42. f.close()   
  43.   
  44. embedding_dim = 200   
  45.   
  46. # Get 200-dim dense vector for each of the words in out vocabulary   
  47. embedding_matrix = np.zeros((vocab_size, embedding_dim))   
  48.   
  49. for word, i in wordtoix.items():   
  50. embedding_vector = embeddings_index.get(word)   
  51. if embedding_vector is not None:   
  52. embedding_matrix[i] = embedding_vector  
  53.   

让我们接收下这段代码:

第1至5行:将所有训练图像的所有描述提取到一个列表中

第9-18行:仅选择词汇中出现次数超过10次的单词

第21–30行:创建一个要索引的单词和一个对单词词典的索引。

第33–42行:将Glove Embeddings加载到字典中,以单词作为键,将vector嵌入为值

第44–52行:使用上面加载的嵌入为词汇表中的单词创建嵌入矩阵

数据准备

这是该项目最重要的方面之一。 对于图像,我们需要使用Inception V3模型将它们转换为固定大小的矢量,如前所述。

  1. # Below path contains all the images   
  2. all_images_path = 'dataset/Flickr8k_Dataset/Flicker8k_Dataset/'   
  3. Create a list of all image names in the directory   
  4. all_images = glob.glob(all_images_path + '*.jpg')   
  5.   
  6. Create a list of all the training and testing images with their full path names   
  7. def create_list_of_images(file_path):   
  8. images_names = set(open(file_path, 'r').read().strip().split('\n'))   
  9. images = []   
  10.   
  11. for image in all_images:   
  12. if image[len(all_images_path):] in image_names:   
  13. images.append(image)   
  14.   
  15. return images   
  16.   
  17.   
  18. train_images_path = 'dataset/Flickr8k_text/Flickr_8k.trainImages.txt'   
  19. test_images_path = 'dataset/Flickr8k_text/Flickr_8k.testImages.txt'   
  20.   
  21. train_images = create_list_of_images(train_images_path)   
  22. test_images = create_list_of_images(test_images_path)   
  23.   
  24. #preprocessing the images   
  25. def preprocess(image_path):   
  26. img = image.load_img(image_path, target_size=(299, 299))   
  27. x = image.img_to_array(img)   
  28. x = np.expand_dims(x, axis=0)   
  29. x = preprocess_input(x)   
  30. return x   
  31.   
  32. Load the inception v3 model   
  33. model = InceptionV3(weights='imagenet')   
  34.   
  35. Create a new model, by removing the last layer (output layer) from the inception v3   
  36. model_new = Model(model.input, model.layers[-2].output)   
  37.   
  38. # Encoding a given image into a vector of size (2048, )   
  39. def encode(image):   
  40. image = preprocess(image)   
  41. fea_vec = model_new.predict(image)   
  42. fea_vec = np.reshape(fea_vec, fea_vec.shape[1])   
  43. return fea_vec   
  44.   
  45.   
  46. encoding_train = {}   
  47. for img in train_images:   
  48. encoding_train[img[len(all_images_path):]] = encode(img)   
  49.   
  50.   
  51. encoding_test = {}   
  52. for img in test_images:   
  53. encoding_test[img[len(all_images_path):]] = encode(img)   
  54.   
  55. # Save the bottleneck features to disk   
  56. with open("encoded_files/encoded_train_images.pkl""wb"as encoded_pickle:   
  57. pickle.dump(encoding_train, encoded_pickle)   
  58.   
  59. with open("encoded_files/encoded_test_images.pkl""wb"as encoded_pickle:   
  60. pickle.dump(encoding_test, encoded_pickle)   
  61.   
  62.   
  63. train_features = load(open("encoded_files/encoded_train_images.pkl""rb"))  
  1. 第1-22行:将训练和测试图像的路径加载到单独的列表中
  2. 第25–53行:循环训练和测试集中的每个图像,将它们加载为固定大小,对其进行预处理,使用InceptionV3模型提取特征,最后对其进行重塑。
  3. 第56–63行:将提取的特征保存到磁盘

现在,我们不会一次预测所有的文字,因为我们不只是将图像提供给计算机,并要求它为其生成文字。 我们要做的就是给它图像的特征向量,以及的第一个单词,并让它预测第二个单词。 然后我们给它给出前两个单词,并让它预测第三个单词。 让我们考虑数据集部分中给出的图像和“一个女孩正在进入木结构建筑”。 在这种情况下,在添加令牌“ startseq”和“ endseq”之后,以下分别是我们的输入(Xi)和输出(Yi)。


此后,我们将使用我们创建的“索引”字典来更改输入和输出中的每个词以映射索引。 在进行批处理时,我们希望所有序列的长度均等,这就是为什么要在每个序列后附加0直到它们成为最大长度(如上所述计算为34)的原因。 正如人们所看到的那样,这是大量的数据,将其立即加载到内存中是根本不可行的,为此,我们将使用一个数据生成器将其加载到小块中降低是用的内存。

  1. # data generator, intended to be used in a call to model.fit_generator()   
  2. def data_generator(descriptions, photos, wordtoix, max_length, num_photos_per_batch):   
  3. X1, X2, y = list(), list(), list()   
  4. n=0   
  5. # loop for ever over images   
  6. while 1:   
  7. for key, desc_list in descriptions.items():   
  8. n+=1   
  9. # retrieve the photo feature   
  10. photo = photos[key+'.jpg']   
  11. for desc in desc_list:   
  12. # encode the sequence   
  13. seq = [wordtoix[word] for word in desc.split(' ') if word in wordtoix]   
  14. # split one sequence into multiple X, y pairs   
  15. for i in range(1, len(seq)):   
  16. # split into input and output pair   
  17. in_seq, out_seq = seq[:i], seq[i]   
  18. # pad input sequence   
  19. in_seq = pad_sequences([in_seq], maxlen=max_length)[0]   
  20. # encode output sequence   
  21. out_seq = to_categorical([out_seq], num_classes=vocab_size)[0]   
  22. # store   
  23. X1.append(photo)   
  24. X2.append(in_seq)   
  25. y.append(out_seq)   
  26. # yield the batch data   
  27. if n==num_photos_per_batch:   
  28. yield [[array(X1), array(X2)], array(y)]   
  29. X1, X2, y = list(), list(), list()   
  30. n=0  

上面的代码遍历所有图像和描述,并生成表中的数据项。 yield将使函数再次从同一行运行,因此,让我们分批加载数据

模型架构和训练

如前所述,我们的模型在每个点都有两个输入,一个输入特征图像矢量,另一个输入部分文字。 我们首先将0.5的Dropout应用于图像矢量,然后将其与256个神经元层连接。 对于部分文字,我们首先将其连接到嵌入层,并使用如上所述经过GLOVE训练的嵌入矩阵的权重。 然后,我们应用Dropout 0.5和LSTM(长期短期记忆)。 最后,我们将这两种方法结合在一起,并将它们连接到256个神经元层,最后是一个softmax层,该层预测我们词汇中每个单词的概率。 可以使用下图概括高级体系结构:

 

 

以下是训练期间选择的超参数:损失被选择为“categorical-loss entropy”,优化器为“Adam”。 该模型总共训练了30轮,但对于前20轮,批次大小和学习率分别为0.001和3,而接下来的10轮分别为0.0001和6。

  1. inputs1 = Input(shape=(2048,))   
  2. fe1 = Dropout(0.5)(inputs1)   
  3. fe2 = Dense(256, activation='relu')(fe1)   
  4. inputs2 = Input(shape=(max_length1,))   
  5. se1 = Embedding(vocab_size, embedding_dim, mask_zero=True)(inputs2)   
  6. se2 = Dropout(0.5)(se1)   
  7. se3 = LSTM(256)(se2)   
  8. decoder1 = add([fe2, se3])   
  9. decoder2 = Dense(256, activation='relu')(decoder1)   
  10. outputs = Dense(vocab_size, activation='softmax')(decoder2)   
  11. model = Model(inputs=[inputs1, inputs2], outputs=outputs)   
  12.   
  13. model.layers[2].set_weights([embedding_matrix])   
  14. model.layers[2].trainable = False   
  15.   
  16. model.compile(loss='categorical_crossentropy', optimizer='adam')   
  17.   
  18. epochs = 20   
  19. number_pics_per_batch = 3   
  20. steps = len(train_descriptions)//number_pics_per_batch   
  21.   
  22. generator = data_generator(train_descriptions, train_features, wordtoix, max_length1, number_pics_per_batch)   
  23. history = model.fit_generator(generator, epochs=20, steps_per_epoch=steps, verbose=1)   
  24.   
  25.   
  26. model.optimizer.lr = 0.0001   
  27. epochs = 10   
  28. number_pics_per_batch = 6   
  29. steps = len(train_descriptions)//number_pics_per_batch   
  30.   
  31. generator = data_generator(train_descriptions, train_features, wordtoix, max_length1, number_pics_per_batch)   
  32. history1 = model.fit_generator(generator, epochs=10, steps_per_epoch=steps, verbose=1)   
  33. model.save('saved_model/model_' + str(30) + '.h5')  

让我们来解释一下代码:

第1-11行:定义模型架构

第13–14行:将嵌入层的权重设置为上面创建的嵌入矩阵,并且还设置trainable = False,因此该层将不再受任何训练

第16–33行:如上所述,使用超参数在两个单独的间隔中训练模型

推理

下面显示了前20轮的训练损失,然后是接下来的10轮的训练损失:


为了进行推断,我们编写了一个函数,该函数根据我们的模型(即贪心)将下一个单词预测为具有最大概率的单词

  1. def greedySearch(photo):   
  2. in_text = 'startseq'   
  3. for i in range(max_length1):   
  4. sequence = [wordtoix[w] for w in in_text.split() if w in wordtoix]   
  5. sequence = pad_sequences([sequence], maxlen=max_length1)   
  6. yhat = model.predict([photo,sequence], verbose=0)   
  7. yhat = np.argmax(yhat)   
  8. word = ixtoword[yhat]   
  9. in_text += ' ' + word   
  10. if word == 'endseq':   
  11. break   
  12. final = in_text.split()   
  13. final = final[1:-1]   
  14. final = ' '.join(final)   
  15. return final   
  16.   
  17. z=1   
  18. pic = list(encoding_test.keys())[999]   
  19. image = encoding_test[pic].reshape((1,2048))   
  20. x=plt.imread(images+pic)   
  21. plt.imshow(x)   
  22. plt.show()   
  23. print("Greedy:",greedySearch(image))  

效果还不错

 

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用机器学习生成图像描述

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

使用机器学习生成图像描述

图像描述是为图像提供适当文字描述的过程。 作为人类,这似乎是一件容易的任务,即使是五岁的孩子也可以轻松完成,但是我们如何编写一个将输入作为图像并生成标题作为输出的计算机程序呢?

医学图像的深度学习的完整代码示例:使用Pytorch对MRI脑扫描的图像进行分割

图像分割是医学图像分析中最重要的任务之一,在许多临床应用中往往是第一步也是最关键的一步。在脑MRI分析中,图像分割通常用于测量和可视化解剖结构,分析大脑变化,描绘病理区域以及手术计划和图像引导干预,分割是大多数形态学分析的先决条件。

机器学习Erdos Renyi随机图生成方法及特性是什么

这篇“机器学习Erdos Renyi随机图生成方法及特性是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“机器学习Erdo
2023-06-30

使用机器学习对图片进行分类

在入门教程《在macOS上编写TensorFlow程序》中使用TensorFlow对手写的数字进行分类,本篇文章将对衣服、鞋子等服装图片进行分类。

双足机器人Cassie使用机器学习完成5公里慢跑

据外媒报道,四年对于机器人技术来说是一段很长的时间,尤其是对于俄勒冈州立大学(OSU)研发的双足机器人Cassie来说。

如何在边缘使用机器学习提高生产线质量

工业4.0鼓励制造业使用机器学习、人工智能、云计算和工业物联网技术来改进工业流程、产品质量,并降低成本和上市时间。

深度学习项目示例 使用自编码器进行模糊图像修复

图像模糊是由相机或拍摄对象移动、对焦不准确或使用光圈配置不当导致的图像不清晰。

使用特征传播重构缺失数据进行图机器学习

大多数图神经网络通常在所有节点都可用的特征假设下运行。但是在现实世界的中,特征通常只有部分可用(例如,在社交网络中,只有一小部分用户可以知道年龄和性别)。

使用SAP Cloud Platform Leonardo机器学习提取图片的特征向量

选中一个需要进行测试的Leonardo机器学习服务,点击Configure Environments:
2023-06-03

在机器学习生命周期管理中使用MLflow的综合指南

本文介绍的综合指南可以从基础到高级的学习和掌握MLflow,并结合实际用例和端到端项目,学习如何管理机器学习生命周期。

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录