├── .gitignore ├── Beginner-Images ├── tf2doc-cnn-cifar10.md ├── tf2doc-cnn-cifar10 │ ├── cifar10-eg.jpg │ └── pooling.jpg ├── tf2doc-tfhub-image-tl.md └── tf2doc-tfhub-image-tl │ └── cifar10-eg.jpg ├── Beginner-ML-basics ├── tf2doc-ml-basic-image.md ├── tf2doc-ml-basic-image │ ├── fashion-mnist-sm.jpg │ └── fashion-mnist.jpg ├── tf2doc-ml-basic-overfit.md ├── tf2doc-ml-basic-overfit │ ├── 3_train_val_loss.jpg │ ├── imdb.jpg │ └── l2_dropout_loss.jpg ├── tf2doc-ml-basic-regression.md ├── tf2doc-ml-basic-regression │ ├── early_mse.jpg │ ├── mse.jpg │ ├── sns.jpg │ └── test_true_pred.jpg ├── tf2doc-ml-basic-save-model.md ├── tf2doc-ml-basic-save-model │ └── hdf5.png ├── tf2doc-ml-basic-structured-data.md ├── tf2doc-ml-basic-structured-data │ └── structured-data.jpg ├── tf2doc-ml-basic-text.md └── tf2doc-ml-basic-text │ ├── imdb-sm.jpg │ └── imdb.jpg ├── Beginner-Text-and-sequences ├── tf2doc-rnn-lstm-text.md └── tf2doc-rnn-lstm-text │ ├── acc1.jpg │ ├── acc2.jpg │ ├── rnn.jpg │ └── rnn_small.jpg ├── README.md ├── code ├── auto_mpg_regression.ipynb ├── cnn-cifar-10.ipynb ├── explore_imdb_overfit.ipynb ├── rnn-text.ipynb ├── save_restore_model.ipynb └── tfhub_image_transfer_learning.ipynb ├── tf2doc.md └── tf2doc └── tf.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | *.mx 4 | # code only include ipynb file 5 | code/* 6 | !code/*.ipynb -------------------------------------------------------------------------------- /Beginner-Images/tf2doc-cnn-cifar10.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - 卷积神经网络分类 CIFAR-10 3 | date: 2019-07-19 20:00:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,卷积神经网络(Convolutional Neural Networks, CNN)分类 CIFAR-10 。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | nav: 简明教程 13 | categories: 14 | - TensorFlow2 文档 15 | image: post/tf2doc-cnn-cifar10/pooling.jpg 16 | github: https://github.com/geektutu/tensorflow2-docs-zh 17 | --- 18 | 19 | **TF2.0 TensorFlow 2 / 2.0 中文文档:卷积神经网络分类 CIFAR-10 Convolutional Neural Networks** 20 | 21 | 主要内容:使用卷积神经网络(Convolutional Neural Network, CNN)分类[CIFAT-10数据集](https://www.cs.toronto.edu/~kriz/cifar.html) 22 | 23 | 官方文档使用的是MNIST数据集,之前在[mnist手写数字识别(CNN卷积神经网络)](https://geektutu.com/post/tensorflow2-mnist-cnn.html)这篇文章中已经有详细的介绍了,包括训练模型、使用真实图片预测等。这篇文章选用 CIFAR-10 数据集来验证简单的卷积神经网络在图像分类问题上的表现。 24 | 25 | ## CIFAR-10 数据集简介 26 | 27 | 与 MNIST 手写数字一样,CIFAR-10 包含了60,000张图片,共10类。训练集50,000张,测试集10,000张。但与MNIST不同的是,CIFAR-10 数据集中的图片是彩色的,每张图片的大小是 _32x32x3_ ,3代表 _R/G/B_ 三个通道,每个像素点的颜色由 _R/G/B_ 三个值决定,_R/G/B_ 的取值范围为0-255。熟悉计算机视觉的童鞋应该了解,图片像素点的值还可以由 _R/G/B/A_ 四个值决定,A 代表透明度,取值范围为0-1。比如下面2个颜色,同样是黑色,透明度不同,感官上会有很大差别: 28 | 29 |

rgba(0, 0, 0, 1)

30 |

rgba(0, 0, 0, 0.5)

31 | 32 | 下载 CIFAR-10 数据集 33 | 34 | ```python 35 | # geektutu.com 36 | import matplotlib.pyplot as plt 37 | import tensorflow as tf 38 | from tensorflow.keras import layers, datasets, models 39 | 40 | (train_x, train_y), (test_x, test_y) = datasets.cifar10.load_data() 41 | ``` 42 | 43 | 看一看前15张图片长啥样吧。 44 | 45 | ```python 46 | # geektutu.com 47 | plt.figure(figsize=(5, 3)) 48 | plt.subplots_adjust(hspace=0.1) 49 | for n in range(15): 50 | plt.subplot(3, 5, n+1) 51 | plt.imshow(train_x[n]) 52 | plt.axis('off') 53 | _ = plt.suptitle("geektutu.com CIFAR-10 Example") 54 | ``` 55 | 56 | ![cifar-10 first 15 images](tf2doc-cnn-cifar10/cifar10-eg.jpg) 57 | 58 | 将0-255的像素值转换到0-1 59 | 60 | ```python 61 | # geektutu.com 62 | train_x, test_x = train_x / 255.0, test_x / 255.0 63 | print('train_x shape:', train_x.shape, 'test_x shape:', test_x.shape) 64 | # (50000, 32, 32, 3), (10000, 32, 32, 3) 65 | ``` 66 | 67 | ## 卷积层 68 | 69 | ```python 70 | # geektutu.com 71 | model = models.Sequential() 72 | model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3))) 73 | model.add(layers.MaxPooling2D((2, 2))) 74 | model.add(layers.Conv2D(64, (3, 3), activation='relu')) 75 | model.add(layers.MaxPooling2D((2, 2))) 76 | model.add(layers.Conv2D(64, (3, 3), activation='relu')) 77 | model.summary() 78 | ``` 79 | 80 | ```bash 81 | Model: "sequential_1" 82 | _________________________________________________________________ 83 | Layer (type) Output Shape Param # 84 | ================================================================= 85 | conv2d_3 (Conv2D) (None, 30, 30, 32) 896 86 | _________________________________________________________________ 87 | max_pooling2d_2 (MaxPooling2 (None, 15, 15, 32) 0 88 | _________________________________________________________________ 89 | conv2d_4 (Conv2D) (None, 13, 13, 64) 18496 90 | _________________________________________________________________ 91 | max_pooling2d_3 (MaxPooling2 (None, 6, 6, 64) 0 92 | _________________________________________________________________ 93 | conv2d_5 (Conv2D) (None, 4, 4, 64) 36928 94 | ================================================================= 95 | Total params: 56,320 96 | Trainable params: 56,320 97 | Non-trainable params: 0 98 | _________________________________________________________________ 99 | ``` 100 | 101 | CNN 的输入是三维张量 (image_height, image_width, color_channels),即 input_shape。每一层卷积层使用`tf.keras.layers.Conv2D`来搭建。Conv2D 共接收2个参数,第2个参数是卷积核大小,第1个参数是卷积核的个数。 102 | 103 | 关于 CNN 更详细的内容,可以参考[mnist手写数字识别(CNN卷积神经网络)](https://geektutu.com/post/tensorflow2-mnist-cnn.html),这里有卷积的动态效果图和推荐的视频。 104 | 105 | 第1、2卷积层后紧跟了最大池化层(MaxPooling2D),最大池化即选择图像区域的最大值作为该区域池化后的值,另一个常见的池化操作是平均池化,即计算图像区域的平均值作为该区域池化后的值。 106 | 107 | ![Max & Avg Pooling](tf2doc-cnn-cifar10/pooling.jpg) 108 | 109 | 每一轮卷积或池化后,图像的宽和高的值都会减小,假设图像的高度为h,卷积核大小为 m,那么很容易得出卷积后的高度 h1 = h - m + 1。池化前的高度为 h1,池化滤波器大小为 s,那么池化后的高度为 h1 / s。对应到`model.summary()`的输出,输入大小为 (32, 32),经过32个3x3的卷积核卷积后,大小为 (30, 30),紧接着池化后,大小变为(15, 15)。 110 | 111 | ## 全连接层 112 | 113 | 我们的目的是对图像进行分类,即期望输出一个长度为10的一维向量,第k个值代表输入图片分类为k的概率。因此需要通过 Dense 层,即全连接层,将3维的卷积层输出,转换为一维。这里可以使用`tf.keras.layers.Flatten()`。 114 | 115 | ```python 116 | # geektutu.com 117 | model.add(layers.Flatten()) 118 | model.add(layers.Dense(64, activation='relu')) 119 | model.add(layers.Dense(10, activation='softmax')) 120 | model.summary() 121 | ``` 122 | 123 | 看一下最终的模型。 124 | 125 | ```bash 126 | Model: "sequential_1" 127 | _________________________________________________________________ 128 | Layer (type) Output Shape Param # 129 | ================================================================= 130 | conv2d_3 (Conv2D) (None, 30, 30, 32) 896 131 | _________________________________________________________________ 132 | max_pooling2d_2 (MaxPooling2 (None, 15, 15, 32) 0 133 | _________________________________________________________________ 134 | conv2d_4 (Conv2D) (None, 13, 13, 64) 18496 135 | _________________________________________________________________ 136 | max_pooling2d_3 (MaxPooling2 (None, 6, 6, 64) 0 137 | _________________________________________________________________ 138 | conv2d_5 (Conv2D) (None, 4, 4, 64) 36928 139 | _________________________________________________________________ 140 | flatten_1 (Flatten) (None, 1024) 0 141 | _________________________________________________________________ 142 | dense_2 (Dense) (None, 64) 65600 143 | _________________________________________________________________ 144 | dense_3 (Dense) (None, 10) 650 145 | ================================================================= 146 | Total params: 122,570 147 | Trainable params: 122,570 148 | Non-trainable params: 0 149 | _________________________________________________________________ 150 | ``` 151 | 152 | ## 编译训练模型 153 | 154 | ```python 155 | # geektutu.com 156 | model.compile(optimizer='adam', 157 | loss='sparse_categorical_crossentropy', 158 | metrics=['accuracy']) 159 | 160 | model.fit(train_x, train_y, epochs=5) 161 | ``` 162 | 163 | ## 评估模型 164 | 165 | ```python 166 | # geektutu.com 167 | test_loss, test_acc = model.evaluate(test_x, test_y) 168 | test_acc # 0.683 169 | ``` 170 | 171 | 卷积神经网络非常适合用来处理图像,这个模型如果用来训练 MNIST 手写数字数据集,可以达到 99% 的正确率,但是在 CIFAR10 数据集上,只有 68.3% 的正确率,我们将在后面的文章中,使用复杂网络模型或者迁移学习来提高准确率。 172 | 173 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 174 | 175 | > 完整代码:[Github - cnn-cifar-10.ipynb](https://github.com/geektutu/tensorflow2-docs-zh/tree/master/code) 176 | > 参考文档:[Convolutional Neural Networks](https://www.tensorflow.org/beta/tutorials/images/intro_to_cnns) 177 | 178 | ## 附 推荐 179 | 180 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-Images/tf2doc-cnn-cifar10/cifar10-eg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-Images/tf2doc-cnn-cifar10/cifar10-eg.jpg -------------------------------------------------------------------------------- /Beginner-Images/tf2doc-cnn-cifar10/pooling.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-Images/tf2doc-cnn-cifar10/pooling.jpg -------------------------------------------------------------------------------- /Beginner-Images/tf2doc-tfhub-image-tl.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - TFHub 迁移学习 3 | date: 2019-07-19 22:00:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,迁移学习(transfer learning)分类 CIFAR-10 。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | nav: 简明教程 13 | categories: 14 | - TensorFlow2 文档 15 | image: post/tf2doc-cnn-cifar10/cifar10-eg.jpg 16 | github: https://github.com/geektutu/tensorflow2-docs-zh 17 | --- 18 | 19 | **TF2.0 TensorFlow 2 / 2.0 中文文档:TFHub 迁移学习 transfer learning** 20 | 21 | 主要内容:使用 [TFHub](https://www.tensorflow.org/hub) 中的预训练模型 _ImageNet_ 进行迁移学习,实现图像分类,数据集使用 CIFAR-10。 22 | 23 | ## ImageNet 模型简介 24 | 25 | TFHub 上有很多预训练好的模型(pretrained model),这次我们选择`ImageNet`。ImageNet 数据集大约有1500万张图片,2.2万类,可以说你能想到,想象不到的图片都能在里面找到。想下载感受一下的话可以到官网下载[ImageNet](http://www.image-net.org/)。 26 | 27 | 当然每次训练不太可能使用所有的图片,一般使用子集,比如2012年ILSVRC分类数据集使用了大概1/10的图片。我们今天用于迁移学习的预训练模型就只有1001个分类,想知道这1001类分别有哪些可以看[这里](https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt)。 28 | 29 | ### 下载 ImageNet Classifier 30 | 31 | ```python 32 | # geektutu.com 33 | import numpy as np 34 | from PIL import Image 35 | import matplotlib.pylab as plt 36 | import tensorflow as tf 37 | import tensorflow_hub as hub 38 | from tensorflow.keras import layers, datasets 39 | 40 | url ="https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/4" 41 | model = tf.keras.Sequential([ 42 | hub.KerasLayer(url, input_shape=(224, 224, 3)) 43 | ]) 44 | ``` 45 | 46 | ImageNet 数据集中的图片大小固定为 (224, 224, 3),因此模型的输入也是 (224, 224, 3)。 47 | 48 | ### 测试任意图片 49 | 50 | 在这里选取一张兔子的图片,也就是本站的 logo 来测试这个预训练好的模型。 51 | 52 | ```python 53 | # geektutu.com 54 | tutu = tf.keras.utils.get_file('tutu.png','https://geektutu.com/img/icon.png') 55 | tutu = Image.open(tutu).resize((224, 224)) 56 | tutu 57 | ``` 58 | ![geektutu](https://geektutu.com/img/icon.png) 59 | 60 | ```python 61 | # geektutu.com 62 | result = model.predict(np.array(tutu).reshape(1, 224, 224, 3)/255.0) 63 | ans = np.argmax(result[0], axis=-1) 64 | print('result.shape:', result.shape, 'ans:', ans) 65 | # result.shape: (1, 1001) ans: 332 66 | ``` 67 | 68 | 模型的输出有1001个分类,测试的结果是332,接下来我们将下载 _ImageNetLabels.txt_ ,就可以知道332代表的分类的名称,可以看到结果是 hare,即`兔`。 69 | 70 | ```python 71 | # geektutu.com 72 | labels_url = 'https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt' 73 | labels_path = tf.keras.utils.get_file('ImageNetLabels.txt', labels_url) 74 | imagenet_labels = np.array(open(labels_path).read().splitlines()) 75 | print(imagenet_labels[ans]) 76 | # hare 77 | ``` 78 | 79 | ## 迁移学习 80 | 81 | 在实际的应用中,预训练好的模型的输入输出可能并不能满足我们的需求,另外,训练上百万甚至上千万张图片,可能需要花费好几天的时间,那有没有办法只使用训练好的模型的一部分呢?训练好的模型的前几层对特征提取有非常好的效果,如果可以直接使用,那就事半功倍了。这种方法被称之为迁移学习(transfer learning)。 82 | 83 | 在接下来的例子中,我们复用了 _ImageNet Classifier_ 的特征提取的部分,并定义了自己的输出层。因为原来的模型输出是1001个分类,而我们希望识别的 CIFAR-10 数据集只有10个分类。 84 | 85 | ### resize 数据集 86 | 87 | 这次demo使用的是 CIFAR-10 数据集,这个数据集在上篇文档 [卷积神经网络分类 CIFAR-10](https://geektutu.com/post/tf2doc-cnn-cifar10.html)有比较详细的介绍,这里就不重复介绍了。再简单看一看这个数据集中的15张样例图片。 88 | 89 | ![CIFAR-10 examples](tf2doc-tfhub-image-tl/cifar10-eg.jpg) 90 | 91 | _ImageNet Classifier_ 的输入固定为(224, 224, 3),但 CIFAR-10 数据集中的图片大小是 32 * 32,简单起见,我们将每一张图片大小从 32x32 转换为 224x224,使用`pillow`库提供的 resize 方法。因为读取全部的数据,内存会被撑爆,所以训练集只截取了 30,000 张图片。 92 | 93 | ```python 94 | def resize(d, size=(224, 224)): 95 | return np.array([np.array(Image.fromarray(v).resize(size, Image.ANTIALIAS)) 96 | for i, v in enumerate(d)]) 97 | 98 | (train_x, train_y), (test_x, test_y) = datasets.cifar10.load_data() 99 | train_x, test_x = resize(train_x[:30000])/255.0, resize(test_x)/255.0 100 | train_y = train_y[:30000] 101 | ``` 102 | 103 | ### 下载特征提取层 104 | 105 | TFHub 提供了 _ImageNet Classifier_ 去掉了最后的分类层的版本,可以直接下载使用。 106 | 107 | ```python 108 | feature_extractor_url = 'https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/4' 109 | feature_extractor_layer = hub.KerasLayer(feature_extractor_url, 110 | input_shape=(224,224,3)) 111 | # 这一层的训练值保持不变 112 | feature_extractor_layer.trainable = False 113 | ``` 114 | 115 | ### 添加分类层 116 | 117 | ```python 118 | model = tf.keras.Sequential([ 119 | feature_extractor_layer, 120 | layers.Dense(10, activation='softmax') 121 | ]) 122 | model.compile(optimizer=tf.keras.optimizers.Adam(), 123 | loss='sparse_categorical_crossentropy', 124 | metrics=['acc']) 125 | model.summary() 126 | ``` 127 | 128 | 这一步,我们在特征提取层后面,添加了输出为10的全连接层,用于最后的分类。从`model.summary()`中我们可以看到特征提取层的输出是1280。 129 | 130 | ``` 131 | Model: "sequential_1" 132 | _________________________________________________________________ 133 | Layer (type) Output Shape Param # 134 | ================================================================= 135 | keras_layer_1 (KerasLayer) (None, 1280) 2257984 136 | _________________________________________________________________ 137 | dense (Dense) (None, 10) 12810 138 | ================================================================= 139 | Total params: 2,270,794 140 | Trainable params: 12,810 141 | Non-trainable params: 2,257,984 142 | _________________________________________________________________ 143 | ``` 144 | 145 | ### 训练并评估模型 146 | 147 | ```python 148 | history = model.fit(train_x, train_y, epochs=1) 149 | loss, acc = model.evaluate(test_x, test_y) 150 | print(acc) 151 | ``` 152 | 153 | ```bash 154 | 10000/10000 [=====] - 256s 26ms/sample - loss: 0.7636 - acc: 0.7657 155 | ``` 156 | 157 | 本文的示例模型非常简单,在`feature_extractor_layer`直接添加了输出层,可训练参数很少。而且只使用大约一半的训练集,正确率仍然达到了 76% 。 158 | 159 | 类似于 ImageNet 的预训练模型还有很多,比如非常出名的 VGG 模型,有兴趣都可以尝试。 160 | 161 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 162 | 163 | > 完整代码:[Github - tfhub_image_transfer_learning.ipynb](https://github.com/geektutu/tensorflow2-docs-zh/tree/master/code) 164 | > 参考文档:[TensorFlow Hub with Keras](https://www.tensorflow.org/beta/tutorials/images/hub_with_keras) 165 | 166 | ## 附 推荐 167 | 168 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-Images/tf2doc-tfhub-image-tl/cifar10-eg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-Images/tf2doc-tfhub-image-tl/cifar10-eg.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-image.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - MNIST 图像分类 3 | date: 2019-07-09 00:30:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,图像分类 Classify images,示例使用 Fashion MNIST 数据集。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | - Fashion MNIST 13 | - 图像分类 14 | - Classify images 15 | nav: 简明教程 16 | categories: 17 | - TensorFlow2 文档 18 | image: post/tf2doc-ml-basic-image/fashion-mnist-sm.jpg 19 | github: https://github.com/geektutu/tensorflow2-docs-zh 20 | --- 21 | 22 | **TF2.0 TensorFlow 2 / 2.0 中文文档 - 图像分类 Classify images** 23 | 24 | 主要内容:使用神经网络对服饰图片进行分类。 25 | 26 | 这篇文档使用高级API`tf.keras`在TensorFlow中搭建和训练模型。 27 | 28 | ```python 29 | # TensorFlow and tf.keras 30 | import tensorflow as tf 31 | from tensorflow import keras 32 | 33 | # Helper libraries 34 | import numpy as np 35 | ``` 36 | 37 | ## 使用 Fashion MNIST 数据集 38 | 39 | ![tf2doc-ml-basic-image](tf2doc-ml-basic-image/fashion-mnist.jpg) 40 | 41 | Fashion Mnist数据集由70,000张黑白图片构成,每张图片大小为 28x28,由十类服饰图片构成。另一个MNIST数据集是手写数字,Fashion MNIST 与之相比更有挑战性,适合用来验证算法。 42 | 43 | 我们使用60,000张图片作为训练集,10,000张图片作为测试集。这个数据集可以从 TensorFlow 中直接获取,返回值为numpy数组。 44 | 45 | ```python 46 | fashion_mnist = keras.datasets.fashion_mnist 47 | 48 | (train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data() 49 | ``` 50 | 51 | 图片大小28x28,每个像素值取值范围0-255。标签是整数,取值范围0-9,与实际的服饰类别对应关系如下。 52 | 53 | | 标签 | 类别 | 标签 | 类别 | 标签 | 类别 | 标签 | 类别 | 54 | | ---- | ----------- | ---- | ------ | ---- | ------- | ---- | ---------- | 55 | | 0 | T-shirt/top | 3 | Dress | 6 | Shirt | 9 | Ankle boot | 56 | | 1 | Trouser | 4 | Coat | 7 | Sneaker | | | 57 | | 2 | Pullover | 5 | Sandal | 8 | Bag | | | 58 | 59 | ## 数据格式 60 | 61 | ```python 62 | train_images.shape # (60000, 28, 28) 63 | len(train_labels) # 60000 64 | train_labels # ([9, 0, 0, ..., 3, 0, 5], dtype=uint8) 65 | test_images.shape # (10000, 28, 28) 66 | len(test_labels) # 10000 67 | ``` 68 | 69 | ## 预处理 70 | 71 | 训练之前,我们需要对数据进行预处理。图片的每个像素值在0-255之间,需要转为0-1。训练集和测试集都需要经过相同的处理。 72 | 73 | ```python 74 | train_images = train_images / 255.0 75 | test_images = test_images / 255.0 76 | ``` 77 | 78 | ## 搭建模型 79 | 80 | 神经网络的基本构成是网络层(layer),大部分深度学习网络都由多个简单的 layers 构成。 81 | 82 | ```python 83 | model = keras.Sequential([ 84 | keras.layers.Flatten(input_shape=(28, 28)), 85 | keras.layers.Dense(128, activation='relu'), 86 | keras.layers.Dense(10, activation='softmax') 87 | ]) 88 | ``` 89 | 90 | 网络的第一层,`Flatten`将输入从28x28 的二维数组转为784的一维数组,这一层的作用仅仅是将每一行值平铺在一行。 91 | 92 | 接下来是2层`Dense`,即全连接层(fully connected, FC),第一层`Dense`有128个神经元。第二层有10个神经元,经过 _softmax_ 后,返回了和为1长度为10的概率数组,每一个数分别代表当前图片属于分类0-9的概率。 93 | 94 | ## 编译模型 95 | 96 | 模型准备训练前,在模型编译(Compile)时还需要设置一些参数。 97 | 98 | - _Loss function_ - 损失函数,训练时评估模型的正确率,希望最小化这个函数,往正确的方向训练模型。 99 | - _Optimizer_ - 优化器算法,更新模型参数的算法。 100 | - _Metrics_ - 指标,用来监视训练和测试步数,下面的例子中使用`accuracy`,即图片被正确分类的比例。 101 | 102 | ```python 103 | model.compile(optimizer='adam', 104 | loss='sparse_categorical_crossentropy', 105 | metrics=['accuracy']) 106 | ``` 107 | 108 | ## 训练模型 109 | 110 | 训练神经网络,通常有以下几个步骤。 111 | 112 | - 传入训练数据,`train_images`和`train_labels`。 113 | - 训练模型去关联图片和标签。 114 | - 模型对测试集`test_images`作预测,并用`test_labels`验证预测结果。 115 | 116 | 使用`model.fit`函数开始训练。 117 | 118 | ```python 119 | model.fit(train_images, train_labels, epochs=10) 120 | ``` 121 | 122 | ```bash 123 | Train on 60000 samples 124 | Epoch 1/10 125 | 60000/60000 [========] - 4s 70us/sample - loss: 0.5032 - accuracy: 0.8234 126 | Epoch 2/10 127 | 60000/60000 [========] - 4s 64us/sample - loss: 0.3793 - accuracy: 0.8618 128 | ... 129 | Epoch 10/10 130 | 60000/60000 [========] - 4s 66us/sample - loss: 0.2389 - accuracy: 0.9115 131 | ``` 132 | 133 | 最终达到了88%左右的准确率。 134 | 135 | 136 | 137 | ## 评估准确率 138 | 139 | 接下来,看看在测试集中表现如何? 140 | 141 | ```python 142 | test_loss, test_acc = model.evaluate(test_images, test_labels) 143 | print('\nTest accuracy:', test_acc) 144 | # 10000/10000 [========] - 0s 37us/sample - loss: 0.3610 - accuracy: 0.8777 145 | # Test accuracy: 0.8777 146 | ``` 147 | 148 | 测试集的准确率低于训练集,训练集和测试集准确率之间的差距代表模型_过拟合_(_overfitting_)。即对于训练中没有见过的新数据,模型表现差。 149 | 150 | ## 预测 151 | 152 | 使用`predict`函数进行预测。 153 | 154 | ```python 155 | predictions = model.predict(test_images) 156 | predictions[0] 157 | ``` 158 | 159 | 看下第一张图片的预测结果。 160 | 161 | ```bash 162 | array([1.06-05, 5.06-12, 8.44-08, 4.09-09, 2.87-07, 2.28-04, 163 | 6.18-06, 2.48-02, 3.81-06, 9.74-01], dtype=float32) 164 | ``` 165 | 166 | 每次预测返回长度为10的数组,代表属于每一种分类的可能性,最大可能性的label是9。测试集中的数据也是9,预测正确。 167 | 168 | ```python 169 | np.argmax(predictions[0]) # 9 170 | test_labels[0] # 9 171 | ``` 172 | 173 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 174 | 175 | > 参考地址:[Train your first neural network: basic classification](https://www.tensorflow.org/beta/tutorials/keras/basic_classification) 176 | 177 | ## 附 推荐 178 | 179 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-image/fashion-mnist-sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-image/fashion-mnist-sm.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-image/fashion-mnist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-image/fashion-mnist.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-overfit.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - 过拟合与欠拟合 3 | date: 2019-07-12 01:10:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,过拟合与欠拟合 Explore overfitting and underfitting,示例使用 IMDB 影评数据集。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | - overfitting 13 | - 过拟合 14 | nav: 简明教程 15 | categories: 16 | - TensorFlow2 文档 17 | image: post/tf2doc-ml-basic-overfit/imdb.jpg 18 | github: https://github.com/geektutu/tensorflow2-docs-zh 19 | --- 20 | 21 | **TF2.0 TensorFlow 2 / 2.0 中文文档:过拟合与欠拟合 Explore overfitting and underfitting** 22 | 23 | 24 | 主要内容:探索正则化(weight regularization)和 dropout 两种避免过拟合的方式改善 IMDB 影评分类效果。 25 | 26 | 之前不管是对影评数据分类,还是预测燃油效率,都可以看到模型在验证集的准确率会在训练一段时间后达到顶峰,之后开始下降。 27 | 28 | 换句话说,过拟合了。在训练集上达到较高的准确率是容易的,但我们的目的是在测试集,即模型没有见过的数据集上表现良好。 29 | 30 | 过拟合的反面是欠拟合。即对于测试数据还存在改进的空间。通常由如下原因导致: 31 | 32 | - 模型不够好。 33 | - 过度正则化(over-regularized) 34 | - 训练时间过短。 35 | 36 | 这些都意味着模型没有学习到训练数据的特征。 37 | 38 | 如果训练太久,模型可能开始过拟合,导致在测试集上表现不佳,因此我们需要取得一个平衡。 39 | 40 | 41 | ## IMDB 数据集 42 | 43 | 我们这次采用 `multi-hot encoding`的方式处理 IMDB 数据集,这样能快速达到过拟合的效果。multi-hot 的方式很简单,给每一个单词一个编号,每句话用长度为10,000的一维向量表示,将出现单词的位置置为1即可。 44 | 45 | ```python 46 | import tensorflow as tf 47 | from tensorflow import keras 48 | from tensorflow.keras.datasets import imdb 49 | 50 | import numpy as np 51 | import matplotlib.pyplot as plt 52 | 53 | # 解决中文乱码问题 54 | plt.rcParams['font.sans-serif'] = ['SimHei'] 55 | plt.rcParams['axes.unicode_minus'] = False 56 | plt.rcParams['font.size'] = 20 57 | 58 | N = 10000 59 | 60 | def multi_hot_encoding(sentences, dim=10000): 61 | results = np.zeros((len(sentences), dim)) 62 | for i, word_indices in enumerate(sentences): 63 | results[i, word_indices] = 1.0 64 | return results 65 | 66 | 67 | (train_x, train_y), (test_x, test_y) = imdb.load_data(num_words=N) 68 | train_x = multi_hot_encoding(train_x) 69 | test_x = multi_hot_encoding(test_x) 70 | 71 | plt.plot(train_x[0]) 72 | plt.show() 73 | ``` 74 | ![IMDB words distribution](tf2doc-ml-basic-overfit/imdb.jpg) 75 | 76 | 如果出现这样的错误,[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1045),解决办法可以参考 [Github - google-images-download](https://github.com/hardikvasa/google-images-download/issues/140) 77 | 78 | ## 过拟合 79 | 80 | 防止过拟合最简单的方式是降低模型复杂度,比如减少模型的学习参数(learnable parameters)。减少神经网络的层数,减少每一层网络的节点数都能达到目的。在深度学习中,学习参数的数量往往代表了模型“容量”(capacity)。容量越大,记忆能力越强,更容易学习到训练集的特征和标签的映射关系,类似于字典。如果这种映射关系缺乏泛化能力,在测试集上不可能有好的表现。 81 | 82 | 训练的目的不是为了让模型完全拟合训练数据,而是为了训练出具有泛化能力的模型。 83 | 84 | 反过来,如果模型较小,记忆能力较弱,学习映射关系比较困难。为了最小化损失(loss),模型需要压缩学习到的东西,因而具备更好的预测能力。但是,如果模型过小,没有足够的神经元记住有用的信息,训练会非常困难。因此需要取得权衡。 85 | 86 | 如何选择合适的网络层数和神经元数量呢?没有公式,只能不断尝试。 87 | 88 | 为了确定合适的模型大小,建议一开始采用一个参数少的模型,然后逐渐增大,直到验证集的损失(loss)基本不再变化。我们用之前的 IMDB 影评分类模型来试一试。 89 | 90 | 先搭一个基线模型,再创建大、小2个模型比较。 91 | 92 | ```python 93 | def build_and_train(hidden_dim, regularizer=None, dropout=0): 94 | model = keras.Sequential([ 95 | keras.layers.Dense(hidden_dim, activation='relu', 96 | input_shape=(N,), 97 | kernel_regularizer=regularizer), 98 | keras.layers.Dropout(dropout), 99 | keras.layers.Dense(hidden_dim, activation='relu', 100 | kernel_regularizer=regularizer), 101 | keras.layers.Dropout(dropout), 102 | keras.layers.Dense(1, activation='sigmoid') 103 | ]) 104 | 105 | model.compile(optimizer='adam', loss='binary_crossentropy', 106 | metrics=['accuracy', 'binary_crossentropy']) 107 | history = model.fit(train_x, train_y, epochs=10, batch_size=512, 108 | validation_data=(test_x, test_y), verbose=0) 109 | 110 | return history 111 | 112 | 113 | baseline_history = build_and_train(16) 114 | smaller_history = build_and_train(4) 115 | larger_history = build_and_train(512) 116 | ``` 117 | 118 | 我们画图看一看这三个模型在训练集和验证集上的表现。 119 | 120 | ```python 121 | def plot_history(histories, key='binary_crossentropy'): 122 | plt.figure(figsize=(10, 6)) 123 | 124 | for name, history in histories: 125 | val = plt.plot(history.epoch, history.history['val_'+key], 126 | '--', label=name + ' 验证集') 127 | plt.plot(history.epoch, history.history[key], 128 | color=val[0].get_color(), label=name + ' 训练集') 129 | 130 | plt.xlabel('Epochs') 131 | plt.ylabel('Loss - ' + key) 132 | plt.legend() 133 | 134 | plt.xlim([0, max(history.epoch)]) 135 | 136 | plot_history([('基线', baseline_history), 137 | ('较小', smaller_history), 138 | ('较大', larger_history)]) 139 | ``` 140 | 141 | ![3_train_val_loss](tf2doc-ml-basic-overfit/3_train_val_loss.jpg) 142 | 143 | 大的模型在第一波(epoch)训练后,就已经过拟合了。模型容量越大,就会越快地拟合训练数据,虽然训练集损失(loss)很低,但事实上已经过拟合了,验证集的loss明显大于训练集。 144 | 145 | ## 如何防止过拟合 146 | 147 | ### 权重正则化 148 | 149 | > 如果关于同一个问题有许多种理论,每一种都能作出同样准确的预言,那么应该挑选其中使用假定最少的。尽管越复杂的方法通常能做出越好的预言,但是在结果大致相同的情况下,假设越少越好。 ---- 维基百科《奥卡剃须刀》 150 | 151 | 你可能听说过奥卡剃须刀原则——如无必要,勿增实体。将这个原则应用到神经网络模型,相同的预测能力下,优选简单的模型,比起复杂的模型,简单的模型不容易过拟合。 152 | 153 | 将模型变简单,除了将模型变小(减少网络层数和每层神经元个数)以外,还有另一种方式,减小模型权重(w)的熵(entropy)。即限制权重值在一个较小的范围内,这样模型中权重分布看起来更“regular”,这被称为“权重正则化”(weight regularization)。常用**L1正则化**和**L2正则化**2种方式,L2 正则化更通用。 154 | 155 | > 正则化建议参考:[机器学习中常常提到的正则化到底是什么意思?](https://www.zhihu.com/question/20924039) 156 | 157 | ### Dropout 158 | 159 | 在神经网络中,Dropout是最有效的以及使用最广泛的正则化方式。Dropout作用在网络层,训练过程中随机丢弃(dropping out)一部分输出值(例如置为0),Dropout的比例一般置为0.2到0.5之间。例如: 160 | 161 | ```python 162 | [0.2, 0.3, 0.5, 0.7, 0.9] 163 | # after 40% dropout 164 | [0.2, 0, 0.5, 0.7, 0] 165 | ``` 166 | 167 | 看看效果吧。 168 | 169 | ```python 170 | l2_model_history = build_and_train(16, keras.regularizers.l2(0.001)) 171 | dpt_model_history = build_and_train(16, dropout=0.2) 172 | plot_history([('基线', baseline_history), 173 | ('L2正则', l2_model_history), 174 | ('Dropout', dpt_model_history)]) 175 | ``` 176 | 177 | ![l2_dropout_loss](tf2doc-ml-basic-overfit/l2_dropout_loss.jpg)) 178 | 179 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 180 | 181 | > 完整代码:[Github - explore_imdb_overfit.ipynb](https://github.com/geektutu/tensorflow2-docs-zh/tree/master/code) 182 | > 参考文档:[Explore overfitting and underfitting](https://www.tensorflow.org/beta/tutorials/keras/overfit_and_underfit) 183 | 184 | 185 | ## 附 推荐 186 | 187 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-overfit/3_train_val_loss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-overfit/3_train_val_loss.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-overfit/imdb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-overfit/imdb.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-overfit/l2_dropout_loss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-overfit/l2_dropout_loss.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-regression.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - 回归预测燃油效率 3 | date: 2019-07-11 01:00:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,Regression 回归,示例使用 Auto MPG 燃油效率数据集。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | - Regression 13 | - 回归 14 | nav: 简明教程 15 | categories: 16 | - TensorFlow2 文档 17 | image: post/tf2doc-ml-basic-regression/sns.jpg 18 | github: https://github.com/geektutu/tensorflow2-docs-zh 19 | --- 20 | 21 | **TF2.0 TensorFlow 2 / 2.0 文档:Regression 回归** 22 | 23 | 主要内容:使用回归预测烟油效率。 24 | 25 | 回归通常用来预测连续值,比如价格和概率。分类问题不一样,类别是固定的,目的是判断属于哪一类。比如给你一堆猫和狗的图片,判断一张图片是猫还是狗就是一个典型的分类问题。 26 | 27 | 接下来使用的是经典的 [Auto MPG](https://archive.ics.uci.edu/ml/datasets/auto+mpg) 数据集,这个数据集包括气缸(cylinders),排量(displayment),马力(horsepower) 和重量(weight)等属性。我们需要利用这些属性搭建模型,预测汽车的燃油效率(fuel efficiency)。 28 | 29 | 模型搭建使用`tf.keras` API。 30 | 31 | ```python 32 | import pathlib 33 | 34 | import matplotlib.pyplot as plt 35 | import pandas as pd 36 | import seaborn as sns 37 | import tensorflow as tf 38 | from tensorflow import keras 39 | from tensorflow.keras import layers 40 | ``` 41 | 42 | 43 | 44 | ## Auto MPG 数据集 45 | 46 | ### 获取数据 47 | 48 | ```python 49 | # 下载数据集到本地 50 | url = "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data" 51 | dataset_path = keras.utils.get_file("auto-mpg.data", url) 52 | 53 | # 使用Pandas读取数据 54 | column_names = ['MPG','气缸','排量','马力','重量','加速度', '年份', '产地'] 55 | raw_dataset = pd.read_csv(dataset_path, names=column_names, 56 | na_values = "?", comment='\t', 57 | sep=" ", skipinitialspace=True) 58 | 59 | dataset = raw_dataset.copy() 60 | # 查看前3条数据 61 | dataset.head(3) 62 | ``` 63 | 64 | | | MPG | 气缸 | 排量 | 马力 | 重量 | 加速度 | 年份 | 产地 | 65 | | ---: | ---: | ---: | ----: | ----: | -----: | -----: | ---: | ---: | 66 | | 0 | 18.0 | 8 | 307.0 | 130.0 | 3504.0 | 12.0 | 70 | 1 | 67 | | 1 | 15.0 | 8 | 350.0 | 165.0 | 3693.0 | 11.5 | 70 | 1 | 68 | | 2 | 18.0 | 8 | 318.0 | 150.0 | 3436.0 | 11.0 | 70 | 1 | 69 | 70 | ### 清洗数据 71 | 72 | 检查是否有 NA 值。 73 | 74 | ```python 75 | dataset.isna().sum() 76 | ``` 77 | 78 | ```bash 79 | MPG 0 80 | 气缸 0 81 | 排量 0 82 | 马力 6 83 | 重量 0 84 | 加速度 0 85 | 年份 0 86 | 产地 0 87 | dtype: int64 88 | ``` 89 | 90 | 直接去除含有NA值的行(马力) 91 | 92 | ```python 93 | dataset = dataset.dropna() 94 | ``` 95 | 96 | 在获取的数据集中,`Origin`(产地)不是数值类型,需转为独热编码。 97 | 98 | ```python 99 | origin = dataset.pop('产地') 100 | dataset['美国'] = (origin == 1)*1.0 101 | dataset['欧洲'] = (origin == 2)*1.0 102 | dataset['日本'] = (origin == 3)*1.0 103 | # 看一看转换后的结果 104 | dataset.head(3) 105 | ``` 106 | 107 | | | MPG | 气缸 | 排量 | 马力 | 重量 | 加速度 | 年份 | 美国 | 欧洲 | 日本 | 108 | | ---: | ---: | ---: | ----: | ----: | -----: | -----: | ---: | ---: | ---: | ---: | 109 | | 0 | 18.0 | 8 | 307.0 | 130.0 | 3504.0 | 12.0 | 70 | 1.0 | 0.0 | 0.0 | 110 | | 1 | 15.0 | 8 | 350.0 | 165.0 | 3693.0 | 11.5 | 70 | 1.0 | 0.0 | 0.0 | 111 | | 2 | 18.0 | 8 | 318.0 | 150.0 | 3436.0 | 11.0 | 70 | 1.0 | 0.0 | 0.0 | 112 | 113 | ### 划分训练集与测试集 114 | 115 | ```python 116 | # 训练集 80%, 测试集 20% 117 | train_dataset = dataset.sample(frac=0.8, random_state=0) 118 | test_dataset = dataset.drop(train_dataset.index) 119 | ``` 120 | 121 | ### 检查数据 122 | 123 | 快速看一看训练集中属性两两之间的关系吧。 124 | 125 | ```python 126 | # 解决中文乱码问题 127 | plt.rcParams['font.sans-serif']=['SimHei'] 128 | plt.rcParams['axes.unicode_minus']=False 129 | 130 | sns.pairplot(train_dataset[["MPG", "气缸", "排量", "重量"]], diag_kind="kde") 131 | ``` 132 | 133 | > matplotlib 中文乱码看这里:[matplotlib图例中文乱码?](https://www.zhihu.com/question/25404709) 134 | 135 | ![Seaborn four feature relation](tf2doc-ml-basic-regression/sns.jpg) 136 | 137 | 你还可以使用`train_dataset.describle()`快速浏览每一属性的平均值、标准差、最小值、最大值等信息,能够帮助你快速地识别出不合理的数据。 138 | 139 | ```python 140 | train_stats = train_dataset.describe() 141 | train_stats.pop("MPG") 142 | train_stats = train_stats.transpose() 143 | train_stats 144 | ``` 145 | 146 | | | count | mean | std | min | 25% | 50% | 75% | max | 147 | | ---: | ----: | ---------: | ---------: | ---: | -----: | ----: | -----: | ----: | 148 | | 气缸 | 314.0 | 5.477707 | 1.699788 | 3.0 | 4.00 | 4.0 | 8.00 | 8.0 | 149 | | 排量 | 314.0 | 195.318471 | 104.331589 | 68.0 | 105.50 | 151.0 | 265.75 | 455.0 | 150 | | ... | ... | ... | ... | ... | ... | .... | ... | ... | 151 | 152 | ### 分离 label 153 | 154 | ```python 155 | # 分离 label 156 | train_labels = train_dataset.pop('MPG') 157 | test_labels = test_dataset.pop('MPG') 158 | ``` 159 | 160 | ### 归一化数据 161 | 162 | 通常训练前需要归一化数据,不同属性使用的计量单位不一样,值的范围不一样,训练就会很困难。比如其中一个属性的范围是[0.1, 0.5],而另一个属性的范围是[1000, 5000],那数值大的属性就容易对训练产生干扰,很可能导致训练不能收敛,或者是数值小的属性在模型中几乎没有发挥作用。归一化将不同范围的数据映射到[0,1]的空间内,可以有效地避免这个问题。 163 | 164 | ```python 165 | def norm(x): 166 | return (x - train_stats['mean']) / train_stats['std'] 167 | normed_train_data = norm(train_dataset) 168 | normed_test_data = norm(test_dataset) 169 | ``` 170 | 171 | ## 模型 172 | 173 | ### 搭建模型 174 | 175 | 我们的模型包含2个全连接的隐藏层构成,输出层返回一个连续值。 176 | 177 | ```python 178 | def build_model(): 179 | input_dim = len(train_dataset.keys()) 180 | model = keras.Sequential([ 181 | layers.Dense(64, activation='relu', input_shape=[input_dim,]), 182 | layers.Dense(64, activation='relu'), 183 | layers.Dense(1) 184 | ]) 185 | 186 | model.compile(loss='mse', metrics=['mae', 'mse'], 187 | optimizer=tf.keras.optimizers.RMSprop(0.001)) 188 | return model 189 | 190 | model = build_model() 191 | # 打印模型的描述信息,每一层的大小、参数个数等 192 | model.summary() 193 | ``` 194 | 195 | ```python 196 | Model: "sequential_1" 197 | _________________________________________________________________ 198 | Layer (type) Output Shape Param # 199 | ================================================================= 200 | dense_4 (Dense) (None, 64) 640 201 | _________________________________________________________________ 202 | dense_5 (Dense) (None, 32) 2080 203 | _________________________________________________________________ 204 | dense_6 (Dense) (None, 1) 33 205 | ================================================================= 206 | Total params: 2,753 207 | Trainable params: 2,753 208 | Non-trainable params: 0 209 | _________________________________________________________________ 210 | ``` 211 | 212 | ### 训练模型 213 | 214 | 在之前的案例,比如[结构化数据分类](https://geektutu.com/post/tf2doc-ml-basic-structured-data.html),我们调用`model.fit`会打印出训练的进度。我们可以禁用默认的行为,并自定义训练进度条。 215 | 216 | ```python 217 | import sys 218 | 219 | 220 | EPOCHS = 1000 221 | 222 | class ProgressBar(keras.callbacks.Callback): 223 | def on_epoch_end(self, epoch, logs): 224 | # 显示进度条 225 | self.draw_progress_bar(epoch + 1, EPOCHS) 226 | 227 | def draw_progress_bar(self, cur, total, bar_len=50): 228 | cur_len = int(cur / total * bar_len) 229 | sys.stdout.write("\r") 230 | sys.stdout.write("[{:<{}}] {}/{}".format("=" * cur_len, bar_len, cur, total)) 231 | sys.stdout.flush() 232 | 233 | history = model.fit( 234 | normed_train_data, train_labels, 235 | epochs=EPOCHS, validation_split = 0.2, verbose=0, 236 | callbacks=[ProgressBar()]) 237 | ``` 238 | 239 | ```bash 240 | [==================================================] 1000/1000 241 | ``` 242 | 243 | 训练过程都存储在了`history`对象中,我们可以借助 matplotlib 将训练过程可视化。 244 | 245 | ```python 246 | hist = pd.DataFrame(history.history) 247 | hist['epoch'] = history.epoch 248 | hist.tail(3) 249 | ``` 250 | 251 | | loss | mae | mse | val_loss | val_mae | val_mse | epoch | | 252 | | ---: | -------: | -------: | -------: | -------: | -------: | -------: | ---- | 253 | | 997 | 3.132053 | 1.142280 | 3.132053 | 9.711935 | 2.361466 | 9.711935 | 997 | 254 | | 998 | 3.021109 | 1.093424 | 3.021109 | 9.488593 | 2.298264 | 9.488593 | 998 | 255 | | 999 | 3.028849 | 1.132241 | 3.028849 | 9.453931 | 2.275017 | 9.453931 | 999 | 256 | 257 | ```python 258 | def plot_history(history): 259 | hist = pd.DataFrame(history.history) 260 | hist['epoch'] = history.epoch 261 | plt.figure() 262 | plt.xlabel('epoch') 263 | plt.ylabel('metric - MSE') 264 | plt.plot(hist['epoch'], hist['mse'], label='训练集') 265 | plt.plot(hist['epoch'], hist['val_mse'], label = '验证集') 266 | plt.ylim([0, 20]) 267 | plt.legend() 268 | 269 | plt.figure() 270 | plt.xlabel('epoch') 271 | plt.ylabel('metric - MAE') 272 | plt.plot(hist['epoch'], hist['mae'], label='训练集') 273 | plt.plot(hist['epoch'], hist['val_mae'], label = '验证集') 274 | plt.ylim([0, 5]) 275 | plt.legend() 276 | 277 | plot_history(history) 278 | ``` 279 | 280 | ![MAE](tf2doc-ml-basic-regression/mse.jpg) 281 | 282 | 从图中,我们可以看到,从100 epoch开始,训练集的loss仍旧继续降低,但验证集的loss却在升高,说明过拟合了,训练应该早一点结束。接下来,我们使用 `keras.callbacks.EarlyStopping`,每一波(epoch)训练结束时,测试训练情况,如果训练不再有效果(验证集的loss,即val_loss 不再下降),则自动地停止训练。 283 | 284 | ```python 285 | model = build_model() 286 | early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10) 287 | history = model.fit(normed_train_data, train_labels, epochs=EPOCHS, 288 | validation_split = 0.2, verbose=0, 289 | callbacks=[early_stop, ProgressBar()]) 290 | plot_history(history) 291 | ``` 292 | 293 | ```bash 294 | [=== ] 70/1000 295 | ``` 296 | 297 | 在第 70 epoch 时,停止了训练。 298 | 299 | ![MAE](tf2doc-ml-basic-regression/early_mse.jpg) 300 | 301 | 接下来使用测试集来评估训练效果。 302 | 303 | ```python 304 | loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=0) 305 | print("测试集平均绝对误差(MAE): {:5.2f} MPG".format(mae)) 306 | # 测试集平均绝对误差(MAE): 1.90 MPG 307 | ``` 308 | 309 | 从图中我们也可以看出,1.9比验证集还略低一点。 310 | 311 | ## 预测 312 | 313 | 最后,我们使用测试集中的数据来预测 MPG 值。 314 | 315 | ```python 316 | test_pred = model.predict(normed_test_data).flatten() 317 | 318 | plt.scatter(test_labels, test_pred) 319 | plt.xlabel('真实值') 320 | plt.ylabel('预测值') 321 | plt.axis('equal') 322 | plt.axis('square') 323 | plt.xlim([0,plt.xlim()[1]]) 324 | plt.ylim([0,plt.ylim()[1]]) 325 | plt.plot([-100, 100], [-100, 100]) 326 | ``` 327 | 328 | 看起来,模型训练得还不错。 329 | 330 | ![Test True Pred](tf2doc-ml-basic-regression/test_true_pred.jpg) 331 | 332 | 333 | ## 结论 334 | 335 | - 均方误差(Mean Squared Error, MSE) 常作为回归问题的损失函数(loss function),与分类问题不太一样。 336 | - 同样,评价指标(evaluation metrics)也不一样,分类问题常用准确率(accuracy),回归问题常用平均绝对误差 (Mean Absolute Error, MAE) 337 | - 每一列数据都有不同的范围,每一列,即每一个feature的数据需要分别缩放到相同的范围。常用归一化的方式,缩放到[0, 1]。 338 | - 如果训练数据过少,最好搭建一个隐藏层少的小的神经网络,避免过拟合。 339 | - 早停法(Early Stoping)也是防止过拟合的一种方式。 340 | 341 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 342 | 343 | > 完整代码:[Github - auto_mpg_regression.ipynb](https://github.com/geektutu/tensorflow2-docs-zh/tree/master/code) 344 | > 参考文档:[Regression: Predict fuel efficiency](https://www.tensorflow.org/beta/tutorials/keras/basic_regression) 345 | 346 | ## 附 推荐 347 | 348 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-regression/early_mse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-regression/early_mse.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-regression/mse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-regression/mse.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-regression/sns.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-regression/sns.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-regression/test_true_pred.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-regression/test_true_pred.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-save-model.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - 保存与加载模型 3 | date: 2019-07-13 00:05:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,保存与加载模型 Save and Restore model。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | - overfitting 13 | - 过拟合 14 | nav: 简明教程 15 | categories: 16 | - TensorFlow2 文档 17 | image: post/tf2doc-ml-basic-save-model/hdf5.png 18 | github: https://github.com/geektutu/tensorflow2-docs-zh 19 | --- 20 | 21 | ![TensorFlow HDF5](tf2doc-ml-basic-save-model/hdf5.png) 22 | 23 | 27 | 28 | **TF2.0 TensorFlow 2 / 2.0 中文文档:保存与加载模型 Save and Restore model** 29 | 30 | 主要内容:使用 `tf.keras`接口训练、保存、加载模型,数据集选用 MNIST 。 31 | 32 | ```bash 33 | $ pip install -q tensorflow==2.0.0-beta1 34 | $ pip install -q h5py pyyaml 35 | ``` 36 | 37 | ## 准备训练数据 38 | 39 | ```python 40 | import tensorflow as tf 41 | from tensorflow import keras 42 | from tensorflow.keras import datasets, layers, models, callbacks 43 | from tensorflow.keras.datasets import mnist 44 | 45 | import os 46 | file_path = os.path.abspath('./mnist.npz') 47 | 48 | (train_x, train_y), (test_x, test_y) = datasets.mnist.load_data(path=file_path) 49 | train_y, test_y = train_y[:1000], test_y[:1000] 50 | train_x = train_x[:1000].reshape(-1, 28 * 28) / 255.0 51 | test_x = test_x[:1000].reshape(-1, 28 * 28) / 255.0 52 | ``` 53 | 54 | ## 搭建模型 55 | 56 | ```python 57 | def create_model(): 58 | model = models.Sequential([ 59 | layers.Dense(512, activation='relu', input_shape=(784,)), 60 | layers.Dropout(0.2), 61 | layers.Dense(10, activation='softmax') 62 | ]) 63 | 64 | model.compile(optimizer='adam', metrics=['accuracy'], 65 | loss='sparse_categorical_crossentropy') 66 | 67 | return model 68 | 69 | def evaluate(target_model): 70 | _, acc = target_model.evaluate(test_x, test_y) 71 | print("Restore model, accuracy: {:5.2f}%".format(100*acc)) 72 | ``` 73 | 74 | ## 自动保存 checkpoints 75 | 76 | 这样做,一是训练结束后得到了训练好的模型,使用得不必再重新训练,二是训练过程被中断,可以从断点处继续训练。 77 | 78 | 设置`tf.keras.callbacks.ModelCheckpoint`回调可以实现这一点。 79 | 80 | ```python 81 | # 存储模型的文件名,语法与 str.format 一致 82 | # period=10:每 10 epochs 保存一次 83 | checkpoint_path = "training_2/cp-{epoch:04d}.ckpt" 84 | checkpoint_dir = os.path.dirname(checkpoint_path) 85 | cp_callback = callbacks.ModelCheckpoint( 86 | checkpoint_path, verbose=1, save_weights_only=True, period=10) 87 | 88 | model = create_model() 89 | model.save_weights(checkpoint_path.format(epoch=0)) 90 | model.fit(train_x, train_y, epochs=50, callbacks=[cp_callback], 91 | validation_data=(test_x, test_y), verbose=0) 92 | ``` 93 | 94 | ```python 95 | Epoch 00010: saving model to training_2/cp-0010.ckpt 96 | Epoch 00020: saving model to training_2/cp-0020.ckpt 97 | Epoch 00030: saving model to training_2/cp-0030.ckpt 98 | Epoch 00040: saving model to training_2/cp-0040.ckpt 99 | Epoch 00050: saving model to training_2/cp-0050.ckpt 100 | ``` 101 | 102 | 加载权重: 103 | 104 | ```python 105 | latest = tf.train.latest_checkpoint(checkpoint_dir) 106 | # 'training_2/cp-0050.ckpt' 107 | model = create_model() 108 | model.load_weights(latest) 109 | evaluate(model) 110 | ``` 111 | 112 | ```bash 113 | 1000/1000 [===] - 0s 90us/sample - loss: 0.4703 - accuracy: 0.8780 114 | Restore model, accuracy: 87.80% 115 | ``` 116 | 117 | ## 手动保存权重 118 | 119 | ```python 120 | # 手动保存权重 121 | model.save_weights('./checkpoints/mannul_checkpoint') 122 | model = create_model() 123 | model.load_weights('./checkpoints/mannul_checkpoint') 124 | evaluate(model) 125 | ``` 126 | 127 | ```python 128 | 1000/1000 [===] - 0s 90us/sample - loss: 0.4703 - accuracy: 0.8780 129 | Restore model, accuracy: 87.80% 130 | ``` 131 | 132 | ## 保存整个模型 133 | 134 | 上面的示例仅仅保存了模型中的权重(weights),模型和优化器都可以一起保存,包括权重(weights)、模型配置(architecture)和优化器配置(optimizer configuration)。这样做的好处是,当你恢复模型时,完全不依赖于原来搭建模型的代码。 135 | 136 | 保存完整的模型有很多应用场景,比如在浏览器中使用 TensorFlow.js 加载运行,比如在移动设备上使用 TensorFlow Lite 加载运行。 137 | 138 | ### HDF5 139 | 140 | 直接调用`model.save`即可保存为 HDF5 格式的文件。 141 | 142 | ```python 143 | model.save('my_model.h5') 144 | ``` 145 | 146 | 从 HDF5 中恢复完整的模型。 147 | 148 | ```python 149 | new_model = models.load_model('my_model.h5') 150 | evaluate(new_model) 151 | ``` 152 | 153 | ```bash 154 | 1000/1000 [===] - 0s 90us/sample - loss: 0.4703 - accuracy: 0.8780 155 | Restore model, accuracy: 87.80% 156 | ``` 157 | 158 | ### saved_model 159 | 160 | 保存为`saved_model`格式。 161 | 162 | ```python 163 | import time 164 | saved_model_path = "./saved_models/{}".format(int(time.time())) 165 | tf.keras.experimental.export_saved_model(model, saved_model_path) 166 | ``` 167 | 168 | 恢复模型并预测 169 | 170 | ```python 171 | new_model = tf.keras.experimental.load_from_saved_model(saved_model_path) 172 | model.predict(test_x).shape 173 | ``` 174 | 175 | ```bash 176 | (1000, 10) 177 | ``` 178 | 179 | `saved_model`格式的模型可以直接用来预测(predict),但是 saved_model 没有保存优化器配置,如果要使用`evaluate`方法,则需要先 compile。 180 | 181 | ```python 182 | new_model.compile(optimizer=model.optimizer, 183 | loss='sparse_categorical_crossentropy', 184 | metrics=['accuracy']) 185 | evaluate(new_model) 186 | ``` 187 | 188 | ```bash 189 | 1000/1000 [===] - 0s 90us/sample - loss: 0.4703 - accuracy: 0.8780 190 | Restore model, accuracy: 87.80% 191 | ``` 192 | 193 | ## 最后 194 | 195 | TensorFlow 中还有其他的方式可以保存模型。 196 | 197 | - [Saving in eager](https://www.tensorflow.org/guide/eager#object_based_saving) eager 模型保存模型 198 | - [Save and Restore](https://www.tensorflow.org/guide/saved_model) -- low-level 的接口。 199 | 200 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 201 | 202 | > 完整代码:[Github - save_restore_model.ipynb](https://github.com/geektutu/tensorflow2-docs-zh/tree/master/code) 203 | > 参考文档:[Save and restore models](https://www.tensorflow.org/beta/tutorials/keras/save_and_restore_models) 204 | 205 | ## 附 推荐 206 | 207 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-save-model/hdf5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-save-model/hdf5.png -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-structured-data.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - 特征工程结构化数据分类 3 | date: 2019-07-09 23:45:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,结构化数据分类 Classify structured data,示例使用心脏病数据集。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | - Feature Column 13 | - structured data 14 | nav: 简明教程 15 | categories: 16 | - TensorFlow2 文档 17 | image: post/tf2doc-ml-basic-structured-data/structured-data.jpg 18 | github: https://github.com/geektutu/tensorflow2-docs-zh 19 | --- 20 | 21 | ![structured-data](tf2doc-ml-basic-structured-data/structured-data.jpg) 22 | 23 | 27 | 28 | **TF2.0 TensorFlow 2 / 2.0 中文文档 - 结构化数据分类 Classify structured data** 29 | 30 | 主要内容:介绍如何对结构化数据(例如 CSV 中的表格数据)分类。 31 | 32 | 这个教程包含完整的代码: 33 | 34 | - 使用 Pandas 加载 CSV 文件。 35 | - 使用 tf.data 打乱数据并获取batch。 36 | - 使用特征工程(feature columns)将 CSV 中的列映射为特征值(features) 37 | - 使用 Keras 搭建、训练和评估模型。 38 | 39 | ## 数据集 40 | 41 | 数据集由 Cleveland Clinic Foundation 提供的几百行心脏病数据构成。每一行代表一个病人,每一列描述一个属性,数据共14列,最后一列为是否患病。我们将使用这些信息预测一个病人是否有心脏病。这是一个典型的二分分类问题。 42 | 43 | | 列 | 描述 | 特征类型 | 数据类型 | 44 | | ------ | --------------------------------------------------- | -------- | -------- | 45 | | Age | 年龄 | 数值 | integer | 46 | | Sex | 性别(1男性; 0女性) | 类别 | integer | 47 | | ... | ... | ... | ... | 48 | | Thal | 3 = normal; 6 = fixed defect; 7 = reversable defect | 类别 | string | 49 | | Target | 是否感染,(1是;0否) | 分类 | integer | 50 | 51 | ## 导入库 52 | 53 | ```bash 54 | pip install -q sklearn 55 | pip install -q tensorflow==2.0.0-beta1 56 | ``` 57 | 58 | ```python 59 | import numpy as np 60 | import pandas as pd 61 | import tensorflow as tf 62 | 63 | from tensorflow import feature_column 64 | from tensorflow.keras import layers 65 | from sklearn.model_selection import train_test_split 66 | ``` 67 | 68 | ## 使用 Pandas 读取数据 69 | 70 | ```python 71 | URL = 'https://storage.googleapis.com/applied-dl/heart.csv' 72 | dataframe = pd.read_csv(URL) 73 | dataframe.head() 74 | ``` 75 | 76 | | - | age | sex | cp | … | slope | ca | thal | target | 77 | | ---- | ---- | ---- | ---- | ---- | ----- | ---- | ---------- | ------ | 78 | | 0 | 63 | 1 | 1 | | 3 | 0 | fixed | 0 | 79 | | 1 | 67 | 1 | 4 | | 2 | 3 | normal | 1 | 80 | | 2 | 67 | 1 | 4 | | 2 | 2 | reversible | 0 | 81 | | 3 | 37 | 1 | 3 | | 3 | 0 | normal | 0 | 82 | | 4 | 41 | 0 | 2 | | 1 | 0 | normal | 0 | 83 | 84 | ## 分割训练集、验证集和测试集 85 | 86 | ```python 87 | train, test = train_test_split(dataframe, test_size=0.2) 88 | train, val = train_test_split(train, test_size=0.2) 89 | print(len(train), 'train examples') # 193 90 | print(len(val), 'validation examples') # 49 91 | print(len(test), 'test examples') # 61 92 | ``` 93 | 94 | ## 创建 input pipeline 95 | 96 | 使用 tf.data ,我们可以使用特征工程(feature columns)将 Pandas DataFrame 中的列映射为特征值(features)。如果是一个非常大的 CSV 文件,不能直接放在内存中,就必须直接使用 tf.data 从磁盘中直接读取数据了。 97 | 98 | ```python 99 | # 帮助函数,返回 tf.data 数据集。 100 | def df_to_dataset(dataframe, shuffle=True, batch_size=32): 101 | dataframe = dataframe.copy() 102 | labels = dataframe.pop('target') 103 | ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels)) 104 | if shuffle: 105 | ds = ds.shuffle(buffer_size=len(dataframe)) 106 | ds = ds.batch(batch_size) 107 | return ds 108 | ``` 109 | 110 | ```python 111 | batch_size = 5 # 为了方面展示下面的示例,batch暂时设置为5。 112 | train_ds = df_to_dataset(train, batch_size=batch_size) 113 | val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size) 114 | test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size) 115 | ``` 116 | 117 | ## 理解 input pipeline 118 | 119 | ```python 120 | for feature_batch, label_batch in train_ds.take(1): 121 | print('Every feature:', list(feature_batch.keys())) 122 | print('A batch of ages:', feature_batch['age']) 123 | print('A batch of targets:', label_batch ) 124 | ``` 125 | 126 | ```python 127 | Every feature: ['cp', 'age', 'sex', ... , 'slope', 'ca'] 128 | A batch of ages: tf.Tensor([50 62 37 69 58], shape=(5,), dtype=int32) 129 | A batch of targets: tf.Tensor([0 0 0 0 0], shape=(5,), dtype=int32) 130 | ``` 131 | 132 | 可以看到数据集返回了一个键为列名的字典。 133 | 134 | ## 特征列示例 135 | 136 | TensorFlow 提供了很多种类型的特征列(feature column),接下来给几个例子看一看每一列的值是怎么被转换的。 137 | 138 | ```python 139 | example_batch = next(iter(train_ds))[0] 140 | 141 | # 帮助函数:创建一个特征列,并转换。 142 | def demo(feature_column): 143 | feature_layer = layers.DenseFeatures(feature_column) 144 | print(feature_layer(example_batch).numpy()) 145 | ``` 146 | 147 | ### 1) Numeric column 148 | 149 | 特征列的输出是模型的输入,Numeric columns 是最简单的类型,数值本身代表某个特征真实的值,因此转换后,值不发生改变。 150 | 151 | ```python 152 | age = feature_column.numeric_column("age") 153 | demo(age) 154 | ``` 155 | 156 | ```python 157 | [[50.] 158 | [62.] 159 | [37.] 160 | [69.] 161 | [58.]] 162 | ``` 163 | 164 | 在这个数据集中,大部分列都是数值类型。 165 | 166 | ### 2) Bucketized columns 167 | 168 | 有时候,并不想直接将数值传给模型,而是希望基于数值的范围离散成几个种类。比如人的年龄,0-10归为一类,用0表示;11-20归为一类,用1表示。我们可以用 bucketized column 将年龄划分到不同的 bucket 中。用中文比喻,就好像提供了不同的桶,在某一范围内的扔进A桶,另一范围的数据扔进B桶,以此类推。下面的例子使用独热编码来表示不同的 bucket。 169 | 170 | ```python 171 | age_buckets = feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65]) 172 | demo(age_buckets) 173 | ``` 174 | 175 | ```python 176 | [[0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] 177 | [0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] 178 | [0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] 179 | [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.] 180 | [0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]] 181 | ``` 182 | 183 | ### 3) Categorical columns 184 | 185 | 在这个数据集中,`thal`列使用字符串表示(e.g. 'fixed', 'normal', 'reversible')。字符串不能直接传给模型。所以我们要先将字符串映射为数值。可以使用categorical_column_with_vocabulary_list 和 categorical_column_with_vocabulary_file 来转换,前者接受一个列表作为输入,后者可以传入一个文件。 186 | 187 | ```python 188 | thal = feature_column.categorical_column_with_vocabulary_list( 189 | 'thal', ['fixed', 'normal', 'reversible']) 190 | 191 | thal_one_hot = feature_column.indicator_column(thal) 192 | demo(thal_one_hot) 193 | ``` 194 | 195 | ```python 196 | [[0. 1. 0.] 197 | [0. 1. 0.] 198 | [0. 1. 0.] 199 | [0. 1. 0.] 200 | [0. 0. 1.]] 201 | ``` 202 | 203 | 可以看到,最终的输出向量也是独热编码,和 Bucketized column 相似。 204 | 205 | ### 4) Embedding column 206 | 207 | 假设某一列有上千种类别,用独热编码来表示就不太合适了。这时候,可以使用 embedding column。embedding column 可以压缩维度,因此向量中的值不再只由0或1组成,可以包含任何数字。 208 | 209 | 在有很多种类别时使用 embedding column 是最合适的。接下来只是一个示例,不管输入有多少种可能性,最终的输出向量定长为8。 210 | 211 | ```python 212 | # embedding column的输入是categorical column 213 | thal_embedding = feature_column.embedding_column(thal, dimension=8) 214 | demo(thal_embedding) 215 | ``` 216 | 217 | ```python 218 | [[-0.42 -0.42 0.34 0.47 0.21 0.33 0.34 0.65] 219 | [-0.42 -0.42 0.34 0.47 0.21 0.33 0.34 0.65] 220 | [-0.42 -0.42 0.34 0.47 0.21 0.33 0.34 0.65] 221 | [-0.42 -0.42 0.34 0.47 0.21 0.33 0.34 0.65] 222 | [ 0.20 0.07 0.06 0.01 -0.47 -0.10 -0.70 0.00]] 223 | ``` 224 | 225 | ### 5) Hashed feature columns 226 | 227 | 另一种表示类别很多的 categorical column 的方式是使用 categorical_column_with_hash_bucket。这个特征列会计算输入的哈希值,然后根据哈希值对字符串进行编码。哈希桶(bucket)个数即参数`hash_bucket_size`。哈希桶(hash_buckets)的个数应明显小于实际的类别个数,以节省空间。 228 | 229 | 注意:哈希的一大副作用是可能存在冲突,不同的字符串可能映射到相同的哈希桶中。不过,在某些数据集,这个方式还是非常有效的。 230 | 231 | ```python 232 | thal_hashed = feature_column.categorical_column_with_hash_bucket( 233 | 'thal', hash_bucket_size=1000) 234 | demo(feature_column.indicator_column(thal_hashed)) 235 | ``` 236 | 237 | ```python 238 | [[0. 0. 0. ... 0. 0. 0.] 239 | [0. 0. 0. ... 0. 0. 0.] 240 | [0. 0. 0. ... 0. 0. 0.] 241 | [0. 0. 0. ... 0. 0. 0.] 242 | [0. 0. 0. ... 0. 0. 0.]] 243 | ``` 244 | 245 | ### 6) Crossed feature columns 246 | 247 | 将几个特征组合成一个特征,即 feature crosses,模型可以对每一个特征组合学习独立的权重。接下来,我们将组合 `age` 和 `thal` 列创建一个新的特征。注意:`crossed_column`不会创建所有可能的组合,因为组合可能性会非常多。背后是通过`hashed_column`处理的,可以设置哈希桶的大小。 248 | 249 | ```python 250 | crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000) 251 | demo(feature_column.indicator_column(crossed_feature)) 252 | ``` 253 | 254 | ```python 255 | [[0. 0. 0. ... 0. 0. 0.] 256 | [0. 0. 0. ... 0. 0. 0.] 257 | [0. 0. 0. ... 0. 0. 0.] 258 | [0. 0. 0. ... 0. 0. 0.] 259 | [0. 0. 0. ... 0. 0. 0.]] 260 | ``` 261 | 262 | ## 选择需要使用的列 263 | 264 | 为了训练出准确率高的模型,大数据集、选取有意义的列、数据的展示方式都是非常重要的。 265 | 266 | 接下来的示例,我们随机选取一些列来训练。 267 | 268 | ```python 269 | feature_columns = [] 270 | 271 | # numeric cols 272 | for header in ['age', 'trestbps', 'chol', 'thalach', 'oldpeak', 'slope', 'ca']: 273 | feature_columns.append(feature_column.numeric_column(header)) 274 | 275 | # bucketized cols 276 | age_buckets = feature_column.bucketized_column(age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65]) 277 | feature_columns.append(age_buckets) 278 | 279 | # indicator cols 280 | thal = feature_column.categorical_column_with_vocabulary_list( 281 | 'thal', ['fixed', 'normal', 'reversible']) 282 | thal_one_hot = feature_column.indicator_column(thal) 283 | feature_columns.append(thal_one_hot) 284 | 285 | # embedding cols 286 | thal_embedding = feature_column.embedding_column(thal, dimension=8) 287 | feature_columns.append(thal_embedding) 288 | 289 | # crossed cols 290 | crossed_feature = feature_column.crossed_column([age_buckets, thal], hash_bucket_size=1000) 291 | crossed_feature = feature_column.indicator_column(crossed_feature) 292 | feature_columns.append(crossed_feature) 293 | ``` 294 | 295 | ### 创建特征层 296 | 297 | 我已经定义好了特征列,接下来使用 DenseFeatures 层将特征列传入到模型中。 298 | 299 | ```python 300 | feature_layer = tf.keras.layers.DenseFeatures(feature_columns) 301 | ``` 302 | 303 | 之前 batch 大小设置为5,是为了方便示例。接下来batch设置为32,创建新的 input pipeline。 304 | 305 | ## 创建、编译和训练模型 306 | 307 | ```python 308 | model = tf.keras.Sequential([ 309 | feature_layer, 310 | layers.Dense(128, activation='relu'), 311 | layers.Dense(128, activation='relu'), 312 | layers.Dense(1, activation='sigmoid') 313 | ]) 314 | 315 | model.compile(optimizer='adam', 316 | loss='binary_crossentropy', 317 | metrics=['accuracy'], 318 | run_eagerly=True) 319 | 320 | model.fit(train_ds, 321 | validation_data=val_ds, 322 | epochs=5) 323 | ``` 324 | 325 | ```bash 326 | Epoch 1/5 327 | 7/7 [========] - 1s 142ms/step - loss: 1.3386 - accuracy: 0.6090 - val_loss: 1.0882 - val_accuracy: 0.2857 328 | Epoch 2/5 329 | 7/7 [========] - 0s 31ms/step - loss: 1.4225 - accuracy: 0.3849 - val_loss: 0.9518 - val_accuracy: 0.7347 330 | Epoch 3/5 331 | 7/7 [========] - 0s 32ms/step - loss: 0.6602 - accuracy: 0.7165 - val_loss: 0.7390 - val_accuracy: 0.6327 332 | Epoch 4/5 333 | 7/7 [========] - 0s 30ms/step - loss: 0.7332 - accuracy: 0.6310 - val_loss: 0.6794 - val_accuracy: 0.7143 334 | Epoch 5/5 335 | 7/7 [========] - 0s 31ms/step - loss: 0.5617 - accuracy: 0.7003 - val_loss: 0.5326 - val_accuracy: 0.7143 336 | 337 | 338 | ``` 339 | 340 | ```python 341 | loss, accuracy = model.evaluate(test_ds) 342 | print("Accuracy", accuracy) 343 | ``` 344 | 345 | ```bash 346 | 2/2 [========] - 0s 15ms/step - loss: 0.3907 - accuracy: 0.7869 347 | Accuracy 0.78688526 348 | ``` 349 | 350 | 如果你使用深度学习模型,以及更大、更复杂的数据集,准确率会更高。一般来说,像这样的小数据集,建议使用决策树或者随机森林。这个教程的主要目的不是训练一个准确率高的模型,而是作一个示例:TensorFlow 如何处理结构化的数据。 351 | 352 | 试一试吧。 353 | 354 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 355 | 356 | > 参考文档:[Classify structured data](https://www.tensorflow.org/beta/tutorials/keras/feature_columns) 357 | 358 | ## 附 推荐 359 | 360 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-structured-data/structured-data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-structured-data/structured-data.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-text.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - IMDB 文本分类 3 | date: 2019-07-09 00:40:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,文本分类 Classify text,示例使用 IMDB 数据集。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - Classify text 10 | - TensorFlow2.0 11 | - TensorFlow2文档 12 | - TensorFlow2.0文档 13 | - IMDB datasets 14 | - 文本分类 15 | nav: 简明教程 16 | categories: 17 | - TensorFlow2 文档 18 | image: post/tf2doc-ml-basic-text/imdb-sm.jpg 19 | github: https://github.com/geektutu/tensorflow2-docs-zh 20 | --- 21 | 22 | **TF2.0 TensorFlow 2 / 2.0 中文文档 - 文本分类 Classify text** 23 | 24 | 主要内容:使用迁移学习算法解决一个典型的二分分类(Binary Classification)问题——电影正向评论和负向评论分类。 25 | 26 | 这篇文档使用包含有50,000条电影评论的 IMDB 数据集,25,000用于训练,25,000用于测试。而且训练集和测试集是均衡的,即其中包含同等数量的正向评论和负向评论。 27 | 28 | 代码使用`tf.keras`和`TensorFlow Hub`,TensorFlow Hub 是一个用于迁移学习的平台/库。 29 | 30 | ```python 31 | import numpy as np 32 | import tensorflow as tf # 2.0.0-beta1 33 | import tensorflow_hub as hub # 0.5.0 34 | import tensorflow_datasets as tfds 35 | ``` 36 | 37 | ## 下载 IMDB 数据集 38 | 39 | ![IMDB datasets](tf2doc-ml-basic-text/imdb.jpg) 40 | 41 | IMDB 数据集在`tfds`中是可以直接获取的,调用时会自动下载到你的机器上。 42 | 43 | ```python 44 | # 进一步划分训练集。 45 | # 60%(15,000)用于训练,40%(10,000)用于验证(validation)。 46 | train_validation_split = tfds.Split.TRAIN.subsplit([6, 4]) 47 | 48 | (train_data, validation_data), test_data = tfds.load( 49 | name="imdb_reviews", 50 | split=(train_validation_split, tfds.Split.TEST), 51 | as_supervised=True) 52 | ``` 53 | 54 | ## 数据格式 55 | 56 | 每个例子包含一句电影评论和对应的标签,0或1。0代表负向评论,1代表正向评论。 57 | 58 | 看一下前十条数据。 59 | 60 | ```python 61 | train_examples_batch, train_labels_batch = next(iter(train_data.batch(10))) 62 | train_examples_batch 63 | ``` 64 | 65 | ```python 66 | 77 | ``` 78 | 79 | 前十个标签。 80 | 81 | ```python 82 | train_labels_batch 83 | # 84 | ``` 85 | 86 | ## 搭建模型 87 | 88 | 神经网络需要堆叠多层,架构上需要考虑三点。 89 | 90 | - 文本怎么表示? 91 | - 模型需要多少层? 92 | - 每一层多少个_隐藏节点_ 93 | 94 | 一种表示文本的方式是将句子映射为向量(embeddings vectors),或者称为文本嵌入(text embedding)。嵌入方法很多,比如我们可以采用最简单的独热编码,假设常用单词总共1000个,给每一个单词一个独热编码。假设每句话由10个单词构成,那么每句话均可以映射到10x1000的二维空间中。那么某句话就可以表示为: 95 | 96 | ```python 97 | # 10x1000的二维向量表示一句话 98 | [[0, 0, 0, 1, 0, ... 0], 99 | [0, 0, ..., 0, 1,... 0], 100 | [0, 0, 0, 0, 0, ... 1], 101 | ... 102 | [0, 0, 0, 0, 0, ... 1]] 103 | ``` 104 | 105 | 文本嵌入的方法很多,要考虑的因素也很多,比如同义词如何处理,维度过高怎么办?知乎上有比较详细的回答:[word embedding的解释](https://www.zhihu.com/question/32275069)。 106 | 107 | 我们可以使用一个预训练(pre-trained)好的文本嵌入模型作为第一层,有3个好处。 108 | 109 | - 不用担心文本处理。 110 | - 能从迁移学习中受益。 111 | - 嵌入后size固定,处理起来简单。 112 | 113 | 接下来从 TensorFlow Hub 中选用的**pre-trained 文本嵌入模型**称为[google/tf2-preview/gnews-swivel-20dim/1](https://tfhub.dev/google/tf2-preview/gnews-swivel-20dim/1)。 114 | 115 | 接下来创建一个 Keras Layer 使用这个模型将句子转为向量。取前三条评论试一试。注意无论句子的长度如何,最终的嵌入结果均为长度20的一维向量。 116 | 117 | ```python 118 | embedding = "https://tfhub.dev/google/tf2-preview/gnews-swivel-20dim/1" 119 | hub_layer = hub.KerasLayer(embedding, input_shape=[], 120 | dtype=tf.string, trainable=True) 121 | hub_layer(train_examples_batch[:3]) 122 | ``` 123 | 124 | ```bash 125 | 130 | ``` 131 | 132 | 接下来,搭建完整的神经网络模型。 133 | 134 | ```python 135 | model = tf.keras.Sequential() 136 | model.add(hub_layer) 137 | model.add(tf.keras.layers.Dense(16, activation='relu')) 138 | model.add(tf.keras.layers.Dense(1, activation='sigmoid')) 139 | 140 | model.summary() 141 | ``` 142 | 143 | ```bash 144 | Model: "sequential" 145 | _________________________________________________________________ 146 | Layer (type) Output Shape Param 147 | ================================================================= 148 | keras_layer (KerasLayer) (None, 20) 400020 149 | _________________________________________________________________ 150 | dense (Dense) (None, 16) 336 151 | _________________________________________________________________ 152 | dense_1 (Dense) (None, 1) 17 153 | ================================================================= 154 | Total params: 400,373 155 | Trainable params: 400,373 156 | Non-trainable params: 0 157 | _________________________________________________________________ 158 | ``` 159 | 160 | 1. 第一层是 TensorFlow Hub 层,将句子转换为 tokens,然后映射每个 token,并组合成最终的向量。输出的维度是:句子个数 * 嵌入维度(20)。 161 | 2. 接下来是全连接层(Full-connected, FC),即`Dense`层,16个节点。 162 | 3. 最后一层,也是全连接层,只有一个节点。使用`sigmoid`激活函数,输出值是float,范围0-1,代表可能性/置信度。 163 | 164 | ## 损失函数和优化器 165 | 166 | `binary_crossentropy`更适合处理概率问题,`mean_squared_error`适合处理回归(Regression)问题。 167 | 168 | ```python 169 | model.compile(optimizer='adam', 170 | loss='binary_crossentropy', 171 | metrics=['accuracy']) 172 | ``` 173 | 174 | ## 训练模型 175 | 176 | 共 20 epochs,每个batch 512个数据。即对所有训练数据进行20轮迭代。在训练过程中,将监视模型在包含10,000条数据的验证集上的损失(loss)和正确率(accuracy)。 177 | 178 | ```python 179 | history = model.fit(train_data.shuffle(10000).batch(512), 180 | epochs=20, 181 | validation_data=validation_data.batch(512), 182 | verbose=1) 183 | ``` 184 | 185 | ```bash 186 | Epoch 1/20 187 | 30/30 [========] - 6s 190ms/step - loss: 1.0201 - accuracy: 0.4331 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00 188 | Epoch 2/20 189 | 30/30 [========] - 5s 159ms/step - loss: 0.7801 - accuracy: 0.4677 - val_loss: 0.7407 - val_accuracy: 0.5009 190 | ...... 191 | Epoch 20/20 192 | 30/30 [========] - 5s 152ms/step - loss: 0.1917 - accuracy: 0.9348 - val_loss: 0.2930 - val_accuracy: 0.8784 193 | ``` 194 | 195 | ## 评估模型 196 | 197 | `evaluate`返回2个值,Loss(误差,越小越好) 和 accuracy。 198 | 199 | ```python 200 | results = model.evaluate(test_data.batch(512), verbose=0) 201 | for name, value in zip(model.metrics_names, results): 202 | print("%s: %.3f" % (name, value)) 203 | # loss: 0.314 204 | # accuracy: 0.866 205 | ``` 206 | 207 | 这个非常基础的模型达到了87%的正确率,复杂一点的模型可以达到95%。 208 | 209 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 210 | 211 | > 参考地址:[Text classification of movie reviews with Keras and TensorFlow Hub](https://www.tensorflow.org/beta/tutorials/keras/basic_text_classification_with_tfhub) 212 | 213 | ## 附 推荐 214 | 215 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-text/imdb-sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-text/imdb-sm.jpg -------------------------------------------------------------------------------- /Beginner-ML-basics/tf2doc-ml-basic-text/imdb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-ML-basics/tf2doc-ml-basic-text/imdb.jpg -------------------------------------------------------------------------------- /Beginner-Text-and-sequences/tf2doc-rnn-lstm-text.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 中文文档 - RNN LSTM 文本分类 3 | date: 2019-07-22 23:18:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,循环神经网络(Recurrent Neural Network, RNN) 和 长短期记忆模型(Long Short-Term Memory,LSTM) 分类 IMDB 。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2.0 10 | - TensorFlow2文档 11 | - TensorFlow2.0文档 12 | nav: 简明教程 13 | categories: 14 | - TensorFlow2 文档 15 | image: post/tf2doc-rnn-lstm-text/rnn_small.jpg 16 | github: https://github.com/geektutu/tensorflow2-docs-zh 17 | --- 18 | 19 | **TF2.0 TensorFlow 2 / 2.0 中文文档:RNN LSTM 文本分类 Text classification with an RNN** 20 | 21 | 主要内容:使用循环神经网络(Recurrent Neural Network, RNN) 分类 影评数据 IMDB 22 | 23 | 循环神经网络(Recurrent Neural Network, RNN)广泛适用于自然语言处理领域(Natural Language Processing, NLP),RNN有什么显著的特点呢?普通的神经网络,每一层的输出是下一层的输入,每一层之间是相互独立的,没有关系。但是对于语言来说,一句话中的单词顺序不同,整个语义就完全变了。因此自然语言处理往往需要能够更好地处理序列信息的神经网络,RNN 能够满足这个需求。 24 | 25 | RNN 中,隐藏层的状态,不仅取决于当前输入层的输出,还和上一步隐藏层的状态有关。 26 | 27 | ![RNN](tf2doc-rnn-lstm-text/rnn.jpg) 28 | 29 | 长短期记忆模型(Long short-term memory, LSTM)是一种特殊的RNN,主要是为了解决长序列训练过程中的梯度消失和梯度爆炸问题。简单来说,就是相比普通的RNN,LSTM能够在更长的序列中有更好的表现。 30 | 31 | 接下来我们使用`tf.keras`提供的 LSTM 网络层搭建 RNN 网络模型,对 IMDB 影评数据进行分类。 32 | 33 | ## 下载 IMDB 34 | 35 | ```python 36 | # geektutu.com 37 | import matplotlib.pyplot as plt 38 | import tensorflow_datasets as tfds 39 | import tensorflow as tf 40 | from tensorflow.keras import Sequential, layers 41 | 42 | ds, info = tfds.load('imdb_reviews/subwords8k', with_info=True, 43 | as_supervised=True) 44 | train_ds, test_ds = ds['train'], ds['test'] 45 | 46 | BUFFER_SIZE, BATCH_SIZE = 10000, 64 47 | train_ds = train_ds.shuffle(BUFFER_SIZE) 48 | train_ds = train_ds.padded_batch(BATCH_SIZE, train_ds.output_shapes) 49 | test_ds = test_ds.padded_batch(BATCH_SIZE, test_ds.output_shapes) 50 | ``` 51 | 52 | ## 文本预处理 53 | 54 | 通过 tfds 获取到的数据已经经过了文本预处理,即 Tokenizer,向量化文本(将文本转为数字序列)。 55 | 56 | 接下来我们看一看是如何转换的。 57 | 58 | ```python 59 | # geektutu.com 60 | tokenizer = info.features['text'].encoder 61 | print ('词汇个数:', tokenizer.vocab_size) 62 | 63 | sample_str = 'welcome to geektutu.com' 64 | tokenized_str = tokenizer.encode(sample_str) 65 | print ('向量化文本:', tokenized_str) 66 | 67 | for ts in tokenized_str: 68 | print (ts, '-->', tokenizer.decode([ts])) 69 | ``` 70 | 71 | 可以看到,有些单词被拆分了。因为 tokenizer 中不可能包含所有可能出现的单词,如果在 tokenizer 中没有的单词,就会被拆分。文本预处理有很多种方式,比如我们在[TensorFlow 2 中文文档 - IMDB 文本分类](https://geektutu.com/post/tf2doc-ml-basic-text.html)中使用了预训练好的字词嵌入模型 _google/tf2-preview/gnews-swivel-20dim/1_,来直接将影评文本转换为向量;还有非常出名的自然语言处理工具包 ntlk 在文本预处理环节提供了非常强大的功能。 72 | 73 | ```bash 74 | 词汇个数: 8185 75 | 向量化文本: [6351, 7961, 7, 703, 3108, 999, 999, 7975, 2449] 76 | 6351 --> welcome 77 | 7961 --> 78 | 7 --> to 79 | 703 --> ge 80 | 3108 --> ek 81 | 999 --> tu 82 | 999 --> tu 83 | 7975 --> . 84 | 2449 --> com 85 | ``` 86 | 87 | ## 搭建 RNN 模型 88 | 89 | 借助`tf.keras`,我们可以非常方便地搭建出 LSTM 网络层,在这里我们只使用一层来试一试。 90 | 91 | 如果你比较细心地话,会发现在第一层使用了`tf.keras.layers.Embedding`,那为什么要使用这一层呢?从我们刚才的预处理实验你会发现,IMDB 数据集的预处理是按照单词在 tokenizer 中的下标来处理的,维度(tokenizer.vocab_size)很高也很稀疏,经过 Embedding 层的转换,将产生大小固定为64的向量。而且这个转换是可训练的,经过足够的训练之后,相似语义的句子将产生相似的向量。 92 | 93 | 我们在 LSTM 层外面套了一个壳(层封装器, layer wrappers): `tf.keras.layers.Bidirectional`,这是 RNN 的双向封装器,用于对序列进行前向和后向计算。 94 | 95 | ```python 96 | # geektutu.com 97 | model = Sequential([ 98 | layers.Embedding(tokenizer.vocab_size, 64), 99 | layers.Bidirectional(layers.LSTM(64)), 100 | layers.Dense(64, activation='relu'), 101 | layers.Dense(1, activation='sigmoid') 102 | ]) 103 | model.compile(loss='binary_crossentropy', optimizer='adam', 104 | metrics=['accuracy']) 105 | history1 = model.fit(train_ds, epochs=3, validation_data=test_ds) 106 | loss, acc = model.evaluate(test_ds) 107 | print('准确率:', acc) # 0.81039 108 | ``` 109 | 110 | 最终达到了81%的准确率,我们使用 _matplotlib_ 把训练过程可视化吧。 111 | 112 | ```python 113 | # geektutu.com 114 | # 解决中文乱码问题 115 | plt.rcParams['font.sans-serif'] = ['SimHei'] 116 | plt.rcParams['axes.unicode_minus'] = False 117 | plt.rcParams['font.size'] = 20 118 | 119 | def plot_graphs(history, name): 120 | plt.plot(history.history[name]) 121 | plt.plot(history.history['验证集 - '+ name]) 122 | plt.xlabel("Epochs") 123 | plt.ylabel(name) 124 | plt.legend([name, '验证集 - ' + name]) 125 | plt.show() 126 | 127 | plot_graphs(history1, 'accuracy') 128 | ``` 129 | 130 | ![rnn acc1](tf2doc-rnn-lstm-text/acc1.jpg) 131 | 132 | ## 添加更多 LSTM 层 133 | 134 | ```python 135 | # geektutu.com 136 | model = Sequential([ 137 | layers.Embedding(tokenizer.vocab_size, 64), 138 | layers.Bidirectional(layers.LSTM(64, return_sequences=True)), 139 | layers.Bidirectional(layers.LSTM(32)), 140 | layers.Dense(64, activation='relu'), 141 | layers.Dense(1, activation='sigmoid') 142 | ]) 143 | model.compile(loss='binary_crossentropy', optimizer='adam', 144 | metrics=['accuracy']) 145 | history = model.fit(train_ds, epochs=3, validation_data=test_ds) 146 | loss, acc = model.evaluate(test_ds) 147 | print('准确率:', acc) # 0.83096% 148 | ``` 149 | 150 | 这一次,我们使用了2层 LSTM,正确率达到了83%。 151 | 152 | ```python 153 | # geektutu.com 154 | plot_graphs(history, 'accuracy') 155 | ``` 156 | 157 | ![rnn acc2](tf2doc-rnn-lstm-text/acc2.jpg) 158 | 159 | 返回[文档首页](https://geektutu.com/post/tf2doc.html) 160 | 161 | > 完整代码:[Github - rnn-text.ipynb](https://github.com/geektutu/tensorflow2-docs-zh/tree/master/code) 162 | > 参考文档:[Text classification with an RNN](https://www.tensorflow.org/beta/tutorials/text/text_classification_rnn) 163 | 164 | ## 附 推荐 165 | 166 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) -------------------------------------------------------------------------------- /Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/acc1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/acc1.jpg -------------------------------------------------------------------------------- /Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/acc2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/acc2.jpg -------------------------------------------------------------------------------- /Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/rnn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/rnn.jpg -------------------------------------------------------------------------------- /Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/rnn_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/Beginner-Text-and-sequences/tf2doc-rnn-lstm-text/rnn_small.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TensorFlow 2 / 2.0 官方文档中文版 2 | 3 | ![TensorFlow 2.0](tf2doc/tf.jpg) 4 | 5 | ## 相关链接 6 | 7 | - [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) 8 | - [机器学习笔试面试题](https://geektutu.com/post/qa-ml.html),[Github](https://github.com/geektutu/interview-questions) 9 | - [TensorFlow 2.0 中文文档](https://geektutu.com/post/tf2doc.html),[Github](https://github.com/geektutu/tensorflow2-docs-zh) 10 | - [TensorFlow 2.0 图像识别&强化学习实战](https://geektutu.com/post/tensorflow2-mnist-cnn.html),[Github](https://github.com/geektutu/tensorflow-tutorial-samples) 11 | ## 目录(持续更新) 12 | 13 | ### 基础 - 机器学习基础 ML basics 14 | 15 | 1. [图像分类 Classify images](https://geektutu.com/post/tf2doc-ml-basic-image.html) 16 | 2. [文本分类 Classify text](https://geektutu.com/post/tf2doc-ml-basic-text.html) 17 | 3. [结构化数据分类 Classify structured data](https://geektutu.com/post/tf2doc-ml-basic-structured-data.html) 18 | 4. [回归 Regression](https://geektutu.com/post/tf2doc-ml-basic-regression.html) 19 | 5. [过拟合与欠拟合 Overfitting and underfitting](https://geektutu.com/post/tf2doc-ml-basic-overfit.html) 20 | 6. [保存和恢复模型 Save and restore models](https://geektutu.com/post/tf2doc-ml-basic-save-model.html) 21 | 22 | ### 基础 - 图像分类 23 | 24 | 1. [卷积神经网络 Convolutional Neural Networks](https://geektutu.com/post/tf2doc-cnn-cifar10.html) 25 | 2. [使用TFHub进行迁移学习 TensorFlow Hub with Keras](https://geektutu.com/post/tf2doc-tfhub-image-tl.html) 26 | 3. 使用预训练CNN进行迁移学习 Transfer Learning Using Pretrained ConvNets 27 | 28 | ### 基础 - 文本分类 29 | 30 | 1. [使用RNN对文本分类进行分类 Text classification with an RNN](https://geektutu.com/post/tf2doc-rnn-lstm-text.html) 31 | 32 | ### 进阶 - 自定义 33 | 34 | 1. 张量和操作 Tensors and operations 35 | 2. 自定义层 Custom layers 36 | 3. 自动微分 Automatic differentiation 37 | 4. 自定义训练:攻略 Custom training:walkthrough 38 | 5. 动态图机制 TF function and AutoGraph -------------------------------------------------------------------------------- /code/cnn-cifar-10.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 下载数据集" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 46, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "# geektutu.com\n", 17 | "import matplotlib.pyplot as plt\n", 18 | "import tensorflow as tf\n", 19 | "from tensorflow.keras import layers, datasets, models\n", 20 | "\n", 21 | "(train_x, train_y), (test_x, test_y) = datasets.cifar10.load_data()" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 47, 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "image/png": "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\n", 32 | "text/plain": [ 33 | "
" 34 | ] 35 | }, 36 | "metadata": { 37 | "needs_background": "light" 38 | }, 39 | "output_type": "display_data" 40 | } 41 | ], 42 | "source": [ 43 | "# geektutu.com\n", 44 | "plt.figure(figsize=(5, 3))\n", 45 | "plt.subplots_adjust(hspace=0.1)\n", 46 | "for n in range(15):\n", 47 | " plt.subplot(3, 5, n+1)\n", 48 | " plt.imshow(train_x[n])\n", 49 | " plt.axis('off')\n", 50 | "_ = plt.suptitle(\"geektutu.com CIFAR-10 Example\")" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": 48, 56 | "metadata": {}, 57 | "outputs": [ 58 | { 59 | "name": "stdout", 60 | "output_type": "stream", 61 | "text": [ 62 | "train_x shape: (50000, 32, 32, 3) test_x shape: (10000, 32, 32, 3)\n" 63 | ] 64 | } 65 | ], 66 | "source": [ 67 | "# geektutu.com\n", 68 | "train_x, test_x = train_x / 255.0, test_x / 255.0\n", 69 | "print('train_x shape:', train_x.shape, 'test_x shape:', test_x.shape)\n", 70 | "# (50000, 32, 32, 3), (10000, 32, 32, 3)" 71 | ] 72 | }, 73 | { 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "## 卷积层" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 52, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "name": "stdout", 87 | "output_type": "stream", 88 | "text": [ 89 | "Model: \"sequential_1\"\n", 90 | "_________________________________________________________________\n", 91 | "Layer (type) Output Shape Param # \n", 92 | "=================================================================\n", 93 | "conv2d_3 (Conv2D) (None, 30, 30, 32) 896 \n", 94 | "_________________________________________________________________\n", 95 | "max_pooling2d_2 (MaxPooling2 (None, 15, 15, 32) 0 \n", 96 | "_________________________________________________________________\n", 97 | "conv2d_4 (Conv2D) (None, 13, 13, 64) 18496 \n", 98 | "_________________________________________________________________\n", 99 | "max_pooling2d_3 (MaxPooling2 (None, 6, 6, 64) 0 \n", 100 | "_________________________________________________________________\n", 101 | "conv2d_5 (Conv2D) (None, 4, 4, 64) 36928 \n", 102 | "=================================================================\n", 103 | "Total params: 56,320\n", 104 | "Trainable params: 56,320\n", 105 | "Non-trainable params: 0\n", 106 | "_________________________________________________________________\n" 107 | ] 108 | } 109 | ], 110 | "source": [ 111 | "# geektutu.com\n", 112 | "model = models.Sequential()\n", 113 | "model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))\n", 114 | "model.add(layers.MaxPooling2D((2, 2)))\n", 115 | "model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n", 116 | "model.add(layers.MaxPooling2D((2, 2)))\n", 117 | "model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n", 118 | "model.summary()" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "## 全连接层" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 53, 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "name": "stdout", 135 | "output_type": "stream", 136 | "text": [ 137 | "Model: \"sequential_1\"\n", 138 | "_________________________________________________________________\n", 139 | "Layer (type) Output Shape Param # \n", 140 | "=================================================================\n", 141 | "conv2d_3 (Conv2D) (None, 30, 30, 32) 896 \n", 142 | "_________________________________________________________________\n", 143 | "max_pooling2d_2 (MaxPooling2 (None, 15, 15, 32) 0 \n", 144 | "_________________________________________________________________\n", 145 | "conv2d_4 (Conv2D) (None, 13, 13, 64) 18496 \n", 146 | "_________________________________________________________________\n", 147 | "max_pooling2d_3 (MaxPooling2 (None, 6, 6, 64) 0 \n", 148 | "_________________________________________________________________\n", 149 | "conv2d_5 (Conv2D) (None, 4, 4, 64) 36928 \n", 150 | "_________________________________________________________________\n", 151 | "flatten_1 (Flatten) (None, 1024) 0 \n", 152 | "_________________________________________________________________\n", 153 | "dense_2 (Dense) (None, 64) 65600 \n", 154 | "_________________________________________________________________\n", 155 | "dense_3 (Dense) (None, 10) 650 \n", 156 | "=================================================================\n", 157 | "Total params: 122,570\n", 158 | "Trainable params: 122,570\n", 159 | "Non-trainable params: 0\n", 160 | "_________________________________________________________________\n" 161 | ] 162 | } 163 | ], 164 | "source": [ 165 | "# geektutu.com\n", 166 | "model.add(layers.Flatten())\n", 167 | "model.add(layers.Dense(64, activation='relu'))\n", 168 | "model.add(layers.Dense(10, activation='softmax'))\n", 169 | "model.summary()" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "## 编译训练模型" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 54, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "name": "stderr", 186 | "output_type": "stream", 187 | "text": [ 188 | "WARNING: Logging before flag parsing goes to stderr.\n", 189 | "W0720 16:46:59.197520 140735530943360 deprecation.py:323] From /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/tensorflow/python/ops/math_grad.py:1250: add_dispatch_support..wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.\n", 190 | "Instructions for updating:\n", 191 | "Use tf.where in 2.0, which has the same broadcast rule as np.where\n" 192 | ] 193 | }, 194 | { 195 | "name": "stdout", 196 | "output_type": "stream", 197 | "text": [ 198 | "Train on 50000 samples\n", 199 | "Epoch 1/5\n", 200 | "50000/50000 [==============================] - 20s 394us/sample - loss: 1.4972 - accuracy: 0.4585\n", 201 | "Epoch 2/5\n", 202 | "50000/50000 [==============================] - 19s 386us/sample - loss: 1.1277 - accuracy: 0.6025\n", 203 | "Epoch 3/5\n", 204 | "50000/50000 [==============================] - 20s 398us/sample - loss: 0.9836 - accuracy: 0.6541\n", 205 | "Epoch 4/5\n", 206 | "50000/50000 [==============================] - 20s 404us/sample - loss: 0.8808 - accuracy: 0.6917\n", 207 | "Epoch 5/5\n", 208 | "50000/50000 [==============================] - 20s 406us/sample - loss: 0.8040 - accuracy: 0.7179\n" 209 | ] 210 | }, 211 | { 212 | "data": { 213 | "text/plain": [ 214 | "" 215 | ] 216 | }, 217 | "execution_count": 54, 218 | "metadata": {}, 219 | "output_type": "execute_result" 220 | } 221 | ], 222 | "source": [ 223 | "model.compile(optimizer='adam',\n", 224 | " loss='sparse_categorical_crossentropy',\n", 225 | " metrics=['accuracy'])\n", 226 | "\n", 227 | "model.fit(train_x, train_y, epochs=5)" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "## 评估模型" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": 58, 240 | "metadata": {}, 241 | "outputs": [ 242 | { 243 | "name": "stdout", 244 | "output_type": "stream", 245 | "text": [ 246 | "10000/10000 [==============================] - 1s 122us/sample - loss: 0.9077 - accuracy: 0.6830\n" 247 | ] 248 | }, 249 | { 250 | "data": { 251 | "text/plain": [ 252 | "0.683" 253 | ] 254 | }, 255 | "execution_count": 58, 256 | "metadata": {}, 257 | "output_type": "execute_result" 258 | } 259 | ], 260 | "source": [ 261 | "# geektutu.com\n", 262 | "test_loss, test_acc = model.evaluate(test_x, test_y)\n", 263 | "test_acc # 0.683" 264 | ] 265 | } 266 | ], 267 | "metadata": { 268 | "kernelspec": { 269 | "display_name": "Python 3", 270 | "language": "python", 271 | "name": "python3" 272 | }, 273 | "language_info": { 274 | "codemirror_mode": { 275 | "name": "ipython", 276 | "version": 3 277 | }, 278 | "file_extension": ".py", 279 | "mimetype": "text/x-python", 280 | "name": "python", 281 | "nbconvert_exporter": "python", 282 | "pygments_lexer": "ipython3", 283 | "version": "3.7.0" 284 | } 285 | }, 286 | "nbformat": 4, 287 | "nbformat_minor": 2 288 | } 289 | -------------------------------------------------------------------------------- /code/rnn-text.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 下载数据集" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import matplotlib.pyplot as plt\n", 17 | "import tensorflow_datasets as tfds\n", 18 | "import tensorflow as tf\n", 19 | "from tensorflow.keras import Sequential, layers\n", 20 | "\n", 21 | "ds, info = tfds.load('imdb_reviews/subwords8k', with_info=True,\n", 22 | " as_supervised=True)\n", 23 | "train_ds, test_ds = ds['train'], ds['test']\n", 24 | "\n", 25 | "BUFFER_SIZE, BATCH_SIZE = 10000, 64\n", 26 | "train_ds = train_ds.shuffle(BUFFER_SIZE)\n", 27 | "train_ds = train_ds.padded_batch(BATCH_SIZE, train_ds.output_shapes)\n", 28 | "test_ds = test_ds.padded_batch(BATCH_SIZE, test_ds.output_shapes)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "metadata": {}, 34 | "source": [ 35 | "## 文本预处理" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 2, 41 | "metadata": {}, 42 | "outputs": [ 43 | { 44 | "name": "stdout", 45 | "output_type": "stream", 46 | "text": [ 47 | "词汇个数: 8185\n", 48 | "向量化文本: [6351, 7961, 7, 703, 3108, 999, 999, 7975, 2449]\n", 49 | "6351 --> welcome\n", 50 | "7961 --> \n", 51 | "7 --> to \n", 52 | "703 --> ge\n", 53 | "3108 --> ek\n", 54 | "999 --> tu\n", 55 | "999 --> tu\n", 56 | "7975 --> .\n", 57 | "2449 --> com\n" 58 | ] 59 | } 60 | ], 61 | "source": [ 62 | "# geektutu.com\n", 63 | "tokenizer = info.features['text'].encoder\n", 64 | "print ('词汇个数:', tokenizer.vocab_size)\n", 65 | "\n", 66 | "sample_str = 'welcome to geektutu.com'\n", 67 | "tokenized_str = tokenizer.encode(sample_str)\n", 68 | "print ('向量化文本:', tokenized_str)\n", 69 | "\n", 70 | "for ts in tokenized_str:\n", 71 | " print (ts, '-->', tokenizer.decode([ts]))" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "## 搭建 RNN 模型" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [ 86 | { 87 | "name": "stdout", 88 | "output_type": "stream", 89 | "text": [ 90 | "Epoch 1/10\n", 91 | " 45/Unknown - 132s 3s/step - loss: 0.6927 - accuracy: 0.5083" 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "model = Sequential([\n", 97 | " layers.Embedding(tokenizer.vocab_size, 64),\n", 98 | " layers.Bidirectional(layers.LSTM(64)),\n", 99 | " layers.Dense(64, activation='relu'),\n", 100 | " layers.Dense(1, activation='sigmoid')\n", 101 | "])\n", 102 | "model.compile(loss='binary_crossentropy', optimizer='adam',\n", 103 | " metrics=['accuracy'])\n", 104 | "history1 = model.fit(train_ds, epochs=10, validation_data=test_ds)\n", 105 | "loss, acc = model.evaluate(test_ds)\n", 106 | "print('准确率:', acc)" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 7, 112 | "metadata": {}, 113 | "outputs": [ 114 | { 115 | "data": { 116 | "image/png": "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\n", 117 | "text/plain": [ 118 | "
" 119 | ] 120 | }, 121 | "metadata": { 122 | "needs_background": "light" 123 | }, 124 | "output_type": "display_data" 125 | } 126 | ], 127 | "source": [ 128 | "# geektutu.com\n", 129 | "# 解决中文乱码问题\n", 130 | "plt.rcParams['font.sans-serif'] = ['SimHei']\n", 131 | "plt.rcParams['axes.unicode_minus'] = False\n", 132 | "plt.rcParams['font.size'] = 20\n", 133 | "\n", 134 | "def plot_graphs(history, name):\n", 135 | " plt.plot(history.history[name])\n", 136 | " plt.plot(history.history['val_'+ name])\n", 137 | " plt.xlabel(\"Epochs\")\n", 138 | " plt.ylabel(name)\n", 139 | " plt.legend([name, '验证集 - ' + name])\n", 140 | " plt.show()\n", 141 | "\n", 142 | "plot_graphs(history1, 'accuracy')" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "## 添加更多 LSTM 层" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 5, 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stdout", 159 | "output_type": "stream", 160 | "text": [ 161 | "Epoch 1/10\n", 162 | "391/391 [==============================] - 1811s 5s/step - loss: 0.5664 - accuracy: 0.6991 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00\n", 163 | "Epoch 2/10\n", 164 | "391/391 [==============================] - 1513s 4s/step - loss: 0.3780 - accuracy: 0.8415 - val_loss: 0.4220 - val_accuracy: 0.8242\n", 165 | "Epoch 3/10\n", 166 | "391/391 [==============================] - 1461s 4s/step - loss: 0.3037 - accuracy: 0.8775 - val_loss: 0.4419 - val_accuracy: 0.8112\n", 167 | "Epoch 4/10\n", 168 | "391/391 [==============================] - 1450s 4s/step - loss: 0.2260 - accuracy: 0.9142 - val_loss: 0.4181 - val_accuracy: 0.8461\n", 169 | "Epoch 5/10\n", 170 | "391/391 [==============================] - 1434s 4s/step - loss: 0.1900 - accuracy: 0.9297 - val_loss: 0.4600 - val_accuracy: 0.8032\n", 171 | "Epoch 6/10\n", 172 | "391/391 [==============================] - 1436s 4s/step - loss: 0.3483 - accuracy: 0.8509 - val_loss: 0.4899 - val_accuracy: 0.7797\n", 173 | "Epoch 7/10\n", 174 | "391/391 [==============================] - 1425s 4s/step - loss: 0.3232 - accuracy: 0.8670 - val_loss: 0.4060 - val_accuracy: 0.8207\n", 175 | "Epoch 8/10\n", 176 | "391/391 [==============================] - 1437s 4s/step - loss: 0.2113 - accuracy: 0.9182 - val_loss: 0.4142 - val_accuracy: 0.8446\n", 177 | "Epoch 9/10\n", 178 | "391/391 [==============================] - 1445s 4s/step - loss: 0.1486 - accuracy: 0.9465 - val_loss: 0.4460 - val_accuracy: 0.8392\n", 179 | "Epoch 10/10\n", 180 | "391/391 [==============================] - 1431s 4s/step - loss: 0.0923 - accuracy: 0.9696 - val_loss: 0.5298 - val_accuracy: 0.8310\n", 181 | " 391/Unknown - 435s 1s/step - loss: 0.5298 - accuracy: 0.8310准确率: 0.83096\n" 182 | ] 183 | } 184 | ], 185 | "source": [ 186 | "model = Sequential([\n", 187 | " layers.Embedding(tokenizer.vocab_size, 64),\n", 188 | " layers.Bidirectional(layers.LSTM(64, return_sequences=True)),\n", 189 | " layers.Bidirectional(layers.LSTM(32)),\n", 190 | " layers.Dense(64, activation='relu'),\n", 191 | " layers.Dense(1, activation='sigmoid')\n", 192 | "])\n", 193 | "model.compile(loss='binary_crossentropy', optimizer='adam',\n", 194 | " metrics=['accuracy'])\n", 195 | "history = model.fit(train_ds, epochs=10, validation_data=test_ds)\n", 196 | "loss, acc = model.evaluate(test_ds)\n", 197 | "print('准确率:', acc)" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": 8, 203 | "metadata": {}, 204 | "outputs": [ 205 | { 206 | "data": { 207 | "image/png": "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\n", 208 | "text/plain": [ 209 | "
" 210 | ] 211 | }, 212 | "metadata": { 213 | "needs_background": "light" 214 | }, 215 | "output_type": "display_data" 216 | } 217 | ], 218 | "source": [ 219 | "plot_graphs(history, 'accuracy')" 220 | ] 221 | } 222 | ], 223 | "metadata": { 224 | "kernelspec": { 225 | "display_name": "Python 3", 226 | "language": "python", 227 | "name": "python3" 228 | }, 229 | "language_info": { 230 | "codemirror_mode": { 231 | "name": "ipython", 232 | "version": 3 233 | }, 234 | "file_extension": ".py", 235 | "mimetype": "text/x-python", 236 | "name": "python", 237 | "nbconvert_exporter": "python", 238 | "pygments_lexer": "ipython3", 239 | "version": "3.7.0" 240 | } 241 | }, 242 | "nbformat": 4, 243 | "nbformat_minor": 2 244 | } 245 | -------------------------------------------------------------------------------- /code/save_restore_model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## 准备数据" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 1, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import warnings\n", 17 | "warnings.simplefilter(\"ignore\")" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 2, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "import tensorflow as tf\n", 27 | "from tensorflow import keras\n", 28 | "from tensorflow.keras import datasets, layers, models, callbacks\n", 29 | "from tensorflow.keras.datasets import mnist\n", 30 | "\n", 31 | "import os\n", 32 | "file_path = os.path.abspath('./mnist.npz')\n", 33 | "\n", 34 | "(train_x, train_y), (test_x, test_y) = datasets.mnist.load_data(path=file_path)\n", 35 | "train_y, test_y = train_y[:1000], test_y[:1000]\n", 36 | "train_x = train_x[:1000].reshape(-1, 28 * 28) / 255.0\n", 37 | "test_x = test_x[:1000].reshape(-1, 28 * 28) / 255.0" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "metadata": {}, 43 | "source": [ 44 | "## 搭建模型" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 3, 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "def create_model():\n", 54 | " model = models.Sequential([\n", 55 | " layers.Dense(512, activation='relu', input_shape=(784,)),\n", 56 | " layers.Dropout(0.2),\n", 57 | " layers.Dense(10, activation='softmax')\n", 58 | " ])\n", 59 | "\n", 60 | " model.compile(optimizer='adam', metrics=['accuracy'],\n", 61 | " loss='sparse_categorical_crossentropy')\n", 62 | "\n", 63 | " return model\n", 64 | "\n", 65 | "def evaluate(target_model):\n", 66 | " _, acc = target_model.evaluate(test_x, test_y)\n", 67 | " print(\"Restore model, accuracy: {:5.2f}%\".format(100*acc))" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "## 自动保存 checkpoints" 75 | ] 76 | }, 77 | { 78 | "cell_type": "code", 79 | "execution_count": 4, 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "name": "stderr", 84 | "output_type": "stream", 85 | "text": [ 86 | "WARNING: Logging before flag parsing goes to stderr.\n", 87 | "W0713 00:06:20.997914 140735530943360 callbacks.py:859] `period` argument is deprecated. Please use `save_freq` to specify the frequency in number of samples seen.\n", 88 | "W0713 00:06:21.173481 140735530943360 deprecation.py:323] From /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/tensorflow/python/ops/math_grad.py:1250: add_dispatch_support..wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.\n", 89 | "Instructions for updating:\n", 90 | "Use tf.where in 2.0, which has the same broadcast rule as np.where\n" 91 | ] 92 | }, 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "\n", 98 | "Epoch 00010: saving model to training_2/cp-0010.ckpt\n", 99 | "\n", 100 | "Epoch 00020: saving model to training_2/cp-0020.ckpt\n", 101 | "\n", 102 | "Epoch 00030: saving model to training_2/cp-0030.ckpt\n", 103 | "\n", 104 | "Epoch 00040: saving model to training_2/cp-0040.ckpt\n", 105 | "\n", 106 | "Epoch 00050: saving model to training_2/cp-0050.ckpt\n" 107 | ] 108 | }, 109 | { 110 | "data": { 111 | "text/plain": [ 112 | "" 113 | ] 114 | }, 115 | "execution_count": 4, 116 | "metadata": {}, 117 | "output_type": "execute_result" 118 | } 119 | ], 120 | "source": [ 121 | "# 存储模型的文件名,语法与 str.format 一致\n", 122 | "# period=10:每 10 epochs 保存一次\n", 123 | "checkpoint_path = \"training_2/cp-{epoch:04d}.ckpt\"\n", 124 | "checkpoint_dir = os.path.dirname(checkpoint_path)\n", 125 | "cp_callback = callbacks.ModelCheckpoint(\n", 126 | " checkpoint_path, verbose=1, save_weights_only=True, period=10)\n", 127 | "\n", 128 | "model = create_model()\n", 129 | "model.save_weights(checkpoint_path.format(epoch=0))\n", 130 | "model.fit(train_x, train_y, epochs=50, callbacks=[cp_callback],\n", 131 | " validation_data=(test_x, test_y), verbose=0)" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 5, 137 | "metadata": {}, 138 | "outputs": [ 139 | { 140 | "name": "stdout", 141 | "output_type": "stream", 142 | "text": [ 143 | "1000/1000 [==============================] - 0s 94us/sample - loss: 0.5232 - accuracy: 0.8720\n", 144 | "Restore model, accuracy: 87.20%\n" 145 | ] 146 | } 147 | ], 148 | "source": [ 149 | "latest = tf.train.latest_checkpoint(checkpoint_dir)\n", 150 | "# 'training_2/cp-0050.ckpt'\n", 151 | "model = create_model()\n", 152 | "model.load_weights(latest)\n", 153 | "evaluate(model)" 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "metadata": {}, 159 | "source": [ 160 | "## 手动保存权重" 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": 6, 166 | "metadata": {}, 167 | "outputs": [ 168 | { 169 | "name": "stdout", 170 | "output_type": "stream", 171 | "text": [ 172 | "1000/1000 [==============================] - 0s 90us/sample - loss: 0.5232 - accuracy: 0.8720\n", 173 | "Restore model, accuracy: 87.20%\n" 174 | ] 175 | } 176 | ], 177 | "source": [ 178 | "# 手动保存权重\n", 179 | "model.save_weights('./checkpoints/mannul_checkpoint')\n", 180 | "model = create_model()\n", 181 | "model.load_weights('./checkpoints/mannul_checkpoint')\n", 182 | "evaluate(model)" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "## 保存整个模型" 190 | ] 191 | }, 192 | { 193 | "cell_type": "markdown", 194 | "metadata": {}, 195 | "source": [ 196 | "### HDF5" 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 7, 202 | "metadata": {}, 203 | "outputs": [], 204 | "source": [ 205 | "model.save('my_model.h5')" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 8, 211 | "metadata": {}, 212 | "outputs": [ 213 | { 214 | "name": "stderr", 215 | "output_type": "stream", 216 | "text": [ 217 | "W0713 00:06:28.440529 140735530943360 hdf5_format.py:192] Error in loading the saved optimizer state. As a result, your model is starting with a freshly initialized optimizer.\n" 218 | ] 219 | }, 220 | { 221 | "name": "stdout", 222 | "output_type": "stream", 223 | "text": [ 224 | "1000/1000 [==============================] - 0s 91us/sample - loss: 0.5232 - accuracy: 0.8720\n", 225 | "Restore model, accuracy: 87.20%\n" 226 | ] 227 | } 228 | ], 229 | "source": [ 230 | "new_model = models.load_model('my_model.h5')\n", 231 | "evaluate(new_model)" 232 | ] 233 | }, 234 | { 235 | "cell_type": "markdown", 236 | "metadata": {}, 237 | "source": [ 238 | "### saved_model" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 9, 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "name": "stderr", 248 | "output_type": "stream", 249 | "text": [ 250 | "W0713 00:06:29.094913 140735530943360 deprecation.py:323] From /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/tensorflow/python/saved_model/signature_def_utils_impl.py:253: build_tensor_info (from tensorflow.python.saved_model.utils_impl) is deprecated and will be removed in a future version.\n", 251 | "Instructions for updating:\n", 252 | "This function will only be available through the v1 compatibility library as tf.compat.v1.saved_model.utils.build_tensor_info or tf.compat.v1.saved_model.build_tensor_info.\n", 253 | "W0713 00:06:29.096133 140735530943360 export_utils.py:182] Export includes no default signature!\n", 254 | "W0713 00:06:29.277875 140735530943360 util.py:244] Unresolved object in checkpoint: (root).optimizer.iter\n", 255 | "W0713 00:06:29.278687 140735530943360 util.py:244] Unresolved object in checkpoint: (root).optimizer.beta_1\n", 256 | "W0713 00:06:29.279356 140735530943360 util.py:244] Unresolved object in checkpoint: (root).optimizer.beta_2\n", 257 | "W0713 00:06:29.280627 140735530943360 util.py:244] Unresolved object in checkpoint: (root).optimizer.decay\n", 258 | "W0713 00:06:29.281178 140735530943360 util.py:244] Unresolved object in checkpoint: (root).optimizer.learning_rate\n", 259 | "W0713 00:06:29.281950 140735530943360 util.py:252] A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/alpha/guide/checkpoints#loading_mechanics for details.\n", 260 | "W0713 00:06:29.402854 140735530943360 export_utils.py:182] Export includes no default signature!\n" 261 | ] 262 | }, 263 | { 264 | "data": { 265 | "text/plain": [ 266 | "(1000, 10)" 267 | ] 268 | }, 269 | "execution_count": 9, 270 | "metadata": {}, 271 | "output_type": "execute_result" 272 | } 273 | ], 274 | "source": [ 275 | "import time\n", 276 | "saved_model_path = \"./saved_models/{}\".format(int(time.time()))\n", 277 | "tf.keras.experimental.export_saved_model(model, saved_model_path)\n", 278 | "new_model = tf.keras.experimental.load_from_saved_model(saved_model_path)\n", 279 | "model.predict(test_x).shape" 280 | ] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 10, 285 | "metadata": {}, 286 | "outputs": [ 287 | { 288 | "name": "stdout", 289 | "output_type": "stream", 290 | "text": [ 291 | "1000/1000 [==============================] - 0s 95us/sample - loss: 0.5232 - accuracy: 0.8720\n", 292 | "Restore model, accuracy: 87.20%\n" 293 | ] 294 | } 295 | ], 296 | "source": [ 297 | "new_model.compile(optimizer=model.optimizer,\n", 298 | " loss='sparse_categorical_crossentropy',\n", 299 | " metrics=['accuracy'])\n", 300 | "evaluate(new_model)" 301 | ] 302 | } 303 | ], 304 | "metadata": { 305 | "kernelspec": { 306 | "display_name": "Python 3", 307 | "language": "python", 308 | "name": "python3" 309 | }, 310 | "language_info": { 311 | "codemirror_mode": { 312 | "name": "ipython", 313 | "version": 3 314 | }, 315 | "file_extension": ".py", 316 | "mimetype": "text/x-python", 317 | "name": "python", 318 | "nbconvert_exporter": "python", 319 | "pygments_lexer": "ipython3", 320 | "version": "3.7.0" 321 | } 322 | }, 323 | "nbformat": 4, 324 | "nbformat_minor": 2 325 | } 326 | -------------------------------------------------------------------------------- /tf2doc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: TensorFlow 2 / 2.0 中文文档 3 | date: 2019-07-09 00:10:10 4 | description: TensorFlow2文档,TensorFlow2.0文档,TensorFlow2.0 TF2.0 TensorFlow 2 / 2.0 官方文档中文版,有删改,序言,介绍整个文档的构成。 5 | tags: 6 | - TensorFlow 2 7 | - 官方文档 8 | keywords: 9 | - TensorFlow2文档 10 | - TensorFlow2.0文档 11 | - TensorFlow2.0 12 | - TF2.0 13 | nav: 简明教程 14 | categories: 15 | - TensorFlow2 文档 16 | top: 3 17 | image: post/tf2doc/tf.jpg 18 | github: https://github.com/geektutu/tensorflow2-docs-zh 19 | --- 20 | 21 | ![TensorFlow 2.0](tf2doc/tf.jpg) 22 | 23 | 如果你对 Python 还不熟悉,推荐先阅读 [一篇文章入门 Python](https://geektutu.com/post/quick-python.html) 24 | 25 | ## 文档地址 26 | 27 | - 文档地址:[TensorFlow 2 / 2.0 中文文档](https://geektutu.com/post/tf2doc.html) 28 | - Github:[Github - tensorflow2-docs](https://github.com/geektutu/tensorflow2-docs-zh) 29 | - 知乎专栏:[Zhihu - Tensorflow2-docs](https://zhuanlan.zhihu.com/geektutu) 30 | 31 | ## 目录(持续更新) 32 | 33 | ### 基础 - 机器学习基础 ML basics 34 | 35 | 1. [图像分类 Classify images](https://geektutu.com/post/tf2doc-ml-basic-image.html) 36 | 2. [文本分类 Classify text](https://geektutu.com/post/tf2doc-ml-basic-text.html) 37 | 3. [结构化数据分类 Classify structured data](https://geektutu.com/post/tf2doc-ml-basic-structured-data.html) 38 | 4. [回归 Regression](https://geektutu.com/post/tf2doc-ml-basic-regression.html) 39 | 5. [过拟合与欠拟合 Overfitting and underfitting](https://geektutu.com/post/tf2doc-ml-basic-overfit.html) 40 | 6. [保存和恢复模型 Save and restore models](https://geektutu.com/post/tf2doc-ml-basic-save-model.html) 41 | 42 | ### 基础 - 图像分类 43 | 44 | 1. [卷积神经网络 Convolutional Neural Networks](https://geektutu.com/post/tf2doc-cnn-cifar10.html) 45 | 2. [使用TFHub进行迁移学习 TensorFlow Hub with Keras](https://geektutu.com/post/tf2doc-tfhub-image-tl.html) 46 | 3. 使用预训练CNN进行迁移学习 Transfer Learning Using Pretrained ConvNets 47 | 48 | ### 基础 - 文本分类 49 | 50 | 1. [使用RNN对文本分类进行分类 Text classification with an RNN](https://geektutu.com/post/tf2doc-rnn-lstm-text.html) 51 | 52 | ### 进阶 - 自定义 53 | 54 | 1. 张量和操作 Tensors and operations 55 | 2. 自定义层 Custom layers 56 | 3. 自动微分 Automatic differentiation 57 | 4. 自定义训练:攻略 Custom training:walkthrough 58 | 5. 动态图机制 TF function and AutoGraph 59 | 60 | ## 极客兔兔实战 61 | 62 | ### 监督学习 63 | 64 | 1. [mnist手写数字识别(CNN卷积神经网络)](https://geektutu.com/post/tensorflow2-mnist-cnn.html) 65 | 2. [监督学习玩转 OpenAI gym game](https://geektutu.com/post/tensorflow2-gym-nn.html) 66 | 67 | ### 强化学习 68 | 69 | 1. [强化学习 Q-Learning 玩转 OpenAI gym](https://geektutu.com/post/tensorflow2-gym-q-learning.html) 70 | 2. [强化学习 DQN 玩转 gym Mountain Car](https://geektutu.com/post/tensorflow2-gym-dqn.html) 71 | 3. [强化学习 70行代码实战 Policy Gradient](https://geektutu.com/post/tensorflow2-gym-pg.html) 72 | 73 | ## 声明 74 | 75 | **TensorFlow 2 中文文档**主要参考 [TensorFlow官网](https://www.tensorflow.org/beta/tutorials/keras),书写而成。选取了一些有价值的章节作总结,内容目录基本与官方文档一致,但在内容上作了大量的简化,以代码实践为主。TensorFlow 是机器学习的高阶框架,功能强大,接口很多,TensorFlow 2 废弃了大量重复的接口,将 Keras 作为搭建网络的主力接口,也添加了很多新的特性,极大地改进了可用性,能有效地减少代码量。 76 | 77 | **TensorFlow 2 中文文档**的目的是选取官方文档中有代表性的内容,帮助大家快速入门,一览TensorFlow 在图像识别、文本分类、结构化数据等方面的风采。介绍 TensorFlow 1.x 的文档已经很多,所以这份文档侧重于总结 TensorFlow 2 的新特性。 78 | 79 | TensorFlow官网的文档遵循[署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh)协议,代码遵循[Apache 2.0 协议](https://www.apache.org/licenses/LICENSE-2.0),本文档完全遵守上述协议。将在显著地方注明来源。 80 | 81 | 代码基于**Python3**和**TensorFlow 2.0 beta**实现。 82 | 83 | 力求简洁,部分代码删改过,例如兼容Python 2.x的代码均被删除。 -------------------------------------------------------------------------------- /tf2doc/tf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geektutu/tensorflow2-docs-zh/3418ce1c0e721f549d0cff7eeff65ca030c55f82/tf2doc/tf.jpg --------------------------------------------------------------------------------