Xem trên TensorFlow.org | Chạy trong Google Colab | Xem nguồn trên GitHub | Tải xuống sổ ghi chép |
TensorFlow 2.x bao gồm nhiều thay đổi API từ TF 1.x và các API tf.compat.v1
, chẳng hạn như sắp xếp lại các đối số, đổi tên ký hiệu và thay đổi giá trị mặc định cho các tham số. Thực hiện thủ công tất cả các sửa đổi này sẽ rất tẻ nhạt và dễ xảy ra lỗi. Để hợp lý hóa các thay đổi và để quá trình chuyển đổi của bạn sang TF 2.x diễn ra liền mạch nhất có thể, nhóm TensorFlow đã tạo tiện ích tf_upgrade_v2
để giúp chuyển đổi mã kế thừa sang API mới.
Cách sử dụng điển hình là như thế này:
tf_upgrade_v2 \ --intree my_project/ \ --outtree my_project_v2/ \ --reportfile report.txt
Nó sẽ đẩy nhanh quá trình nâng cấp của bạn bằng cách chuyển đổi các tập lệnh Python TensorFlow 1.x hiện có thành TensorFlow 2.x.
Tập lệnh chuyển đổi tự động hóa nhiều phép chuyển đổi API cơ học, mặc dù không thể tự động di chuyển nhiều API. Nó cũng không thể làm cho mã của bạn hoàn toàn tương thích với các hành vi và API TF2. Vì vậy, nó chỉ là một phần trong hành trình di cư của bạn.
Các mô-đun tương thích
Không thể nâng cấp một số ký hiệu API nhất định bằng cách sử dụng thay thế chuỗi. Những thứ không thể tự động nâng cấp sẽ được ánh xạ tới vị trí của chúng trong mô-đun compat.v1
. Mô-đun này thay thế các ký hiệu TF 1.x như tf.foo
bằng tham chiếu tf.compat.v1.foo
tương đương. Nếu bạn đang sử dụng các API compat.v1
bằng cách nhập TF thông qua import tensorflow.compat.v1 as tf
, thì tập lệnh tf_upgrade_v2
sẽ cố gắng chuyển đổi các cách sử dụng này thành các API không phải compat nếu có thể. Lưu ý rằng trong khi một số API compat.v1
tương thích với các hành vi TF2.x, nhiều API lại không. Vì vậy, chúng tôi khuyên bạn nên đọc bản sửa lỗi thay thế theo cách thủ công và di chuyển chúng sang các API mới trong không gian tên tf.*
Thay vì không gian tên tf.compat.v1
càng nhanh càng tốt.
Do mô-đun TensorFlow 2.x không được dùng nữa (ví dụ: tf.flags
và tf.contrib
), một số thay đổi không thể khắc phục được bằng cách chuyển sang compat.v1
. Việc nâng cấp mã này có thể yêu cầu sử dụng một thư viện bổ sung (ví dụ: absl.flags
) hoặc chuyển sang một gói trong tensorflow / addons .
Quy trình nâng cấp được đề xuất
Phần còn lại của hướng dẫn này trình bày cách sử dụng tập lệnh viết lại ký hiệu. Mặc dù tập lệnh dễ sử dụng, nhưng bạn nên sử dụng tập lệnh như một phần của quy trình sau:
Kiểm tra đơn vị : Đảm bảo rằng mã bạn đang nâng cấp có bộ kiểm tra đơn vị với mức độ phù hợp hợp lý. Đây là mã Python, vì vậy ngôn ngữ này sẽ không bảo vệ bạn khỏi nhiều lỗi sai. Đồng thời đảm bảo rằng mọi phụ thuộc bạn đã được nâng cấp để tương thích với TensorFlow 2.x.
Cài đặt TensorFlow 1.15 : Nâng cấp TensorFlow của bạn lên phiên bản TensorFlow 1.x mới nhất, ít nhất là 1.15. Điều này bao gồm API TensorFlow 2.0 cuối cùng trong
tf.compat.v2
.Kiểm tra Với 1.15 : Đảm bảo các bài kiểm tra đơn vị của bạn vượt qua tại thời điểm này. Bạn sẽ chạy chúng nhiều lần khi nâng cấp vì vậy việc bắt đầu từ màu xanh lục là rất quan trọng.
Chạy tập lệnh nâng cấp : Chạy
tf_upgrade_v2
trên toàn bộ cây nguồn của bạn, bao gồm các thử nghiệm. Điều này sẽ nâng cấp mã của bạn lên một định dạng mà nó chỉ sử dụng các ký hiệu có sẵn trong TensorFlow 2.0. Các ký hiệu không dùng nữa sẽ được truy cập bằngtf.compat.v1
. Những điều này cuối cùng sẽ đòi hỏi sự chú ý thủ công, nhưng không phải ngay lập tức.Chạy các bài kiểm tra đã chuyển đổi với TensorFlow 1.15 : Mã của bạn sẽ vẫn chạy tốt trong TensorFlow 1.15. Chạy lại các bài kiểm tra đơn vị của bạn. Bất kỳ lỗi nào trong các thử nghiệm của bạn ở đây có nghĩa là có lỗi trong tập lệnh nâng cấp. Xin vui lòng cho chúng tôi biết .
Kiểm tra báo cáo nâng cấp để biết các cảnh báo và lỗi : Tập lệnh viết tệp báo cáo giải thích bất kỳ chuyển đổi nào bạn nên kiểm tra kỹ hoặc bất kỳ hành động thủ công nào bạn cần thực hiện. Ví dụ: Mọi trường hợp còn lại của đóng góp sẽ yêu cầu thao tác thủ công để xóa. Vui lòng tham khảo RFC để biết thêm hướng dẫn .
Cài đặt TensorFlow 2.x : Tại thời điểm này, có thể an toàn khi chuyển sang tệp nhị phân TensorFlow 2.x, ngay cả khi bạn đang chạy với các hành vi cũ
Kiểm tra với
v1.disable_v2_behavior
: Chạy lại các kiểm tra của bạn vớiv1.disable_v2_behavior()
trong chức năng chính của kiểm tra sẽ cho kết quả tương tự như khi chạy dưới 1.15.Bật hành vi V2 : Giờ đây, các thử nghiệm của bạn hoạt động bằng cách sử dụng nhị phân TF2, giờ đây bạn có thể bắt đầu di chuyển mã của mình để tránh
tf.estimator
và chỉ sử dụng các hành vi TF2 được hỗ trợ (không tắt hành vi TF2). Xem hướng dẫn Di chuyển để biết chi tiết.
Sử dụng tập lệnh tf_upgrade_v2
viết lại biểu tượng
Thành lập
Trước khi bắt đầu, hãy đảm bảo rằng TensorFlow 2.x đã được cài đặt.
import tensorflow as tf
print(tf.__version__)
2.6.0
Sao chép kho lưu trữ tensorflow / styles git để bạn có một số mã để kiểm tra:
git clone --branch r1.13.0 --depth 1 https://github.com/tensorflow/models
Cloning into 'models'... remote: Enumerating objects: 2927, done.[K remote: Counting objects: 100% (2927/2927), done.[K remote: Compressing objects: 100% (2428/2428), done.[K remote: Total 2927 (delta 504), reused 2113 (delta 424), pack-reused 0[K Receiving objects: 100% (2927/2927), 369.04 MiB | 27.58 MiB/s, done. Resolving deltas: 100% (504/504), done. Checking out files: 100% (2768/2768), done.
Đọc phần trợ giúp
Tập lệnh phải được cài đặt bằng TensorFlow. Đây là trợ giúp nội trang:
tf_upgrade_v2 -h
usage: tf_upgrade_v2 [-h] [--infile INPUT_FILE] [--outfile OUTPUT_FILE] [--intree INPUT_TREE] [--outtree OUTPUT_TREE] [--copyotherfiles COPY_OTHER_FILES] [--inplace] [--no_import_rename] [--no_upgrade_compat_v1_import] [--reportfile REPORT_FILENAME] [--mode {DEFAULT,SAFETY}] [--print_all] Convert a TensorFlow Python file from 1.x to 2.0 Simple usage: tf_upgrade_v2.py --infile foo.py --outfile bar.py tf_upgrade_v2.py --infile foo.ipynb --outfile bar.ipynb tf_upgrade_v2.py --intree ~/code/old --outtree ~/code/new optional arguments: -h, --help show this help message and exit --infile INPUT_FILE If converting a single file, the name of the file to convert --outfile OUTPUT_FILE If converting a single file, the output filename. --intree INPUT_TREE If converting a whole tree of files, the directory to read from (relative or absolute). --outtree OUTPUT_TREE If converting a whole tree of files, the output directory (relative or absolute). --copyotherfiles COPY_OTHER_FILES If converting a whole tree of files, whether to copy the other files. --inplace If converting a set of files, whether to allow the conversion to be performed on the input files. --no_import_rename Not to rename import to compat.v2 explicitly. --no_upgrade_compat_v1_import If specified, don't upgrade explicit imports of `tensorflow.compat.v1 as tf` to the v2 APIs. Otherwise, explicit imports of the form `tensorflow.compat.v1 as tf` will be upgraded. --reportfile REPORT_FILENAME The name of the file where the report log is stored.(default: report.txt) --mode {DEFAULT,SAFETY} Upgrade script mode. Supported modes: DEFAULT: Perform only straightforward conversions to upgrade to 2.0. In more difficult cases, switch to use compat.v1. SAFETY: Keep 1.* code intact and import compat.v1 module. --print_all Print full log to stdout instead of just printing errors
Ví dụ về mã TF1
Đây là một tập lệnh TensorFlow 1.0 đơn giản:
head -n 65 models/samples/cookbook/regression/custom_regression.py | tail -n 10
# Calculate loss using mean squared error average_loss = tf.losses.mean_squared_error(labels, predictions) # Pre-made estimators use the total_loss instead of the average, # so report total_loss for compatibility. batch_size = tf.shape(labels)[0] total_loss = tf.to_float(batch_size) * average_loss if mode == tf.estimator.ModeKeys.TRAIN: optimizer = params.get("optimizer", tf.train.AdamOptimizer)
Với TensorFlow 2.x được cài đặt, nó không chạy:
(cd models/samples/cookbook/regression && python custom_regression.py)
Traceback (most recent call last): File "custom_regression.py", line 162, in <module> tf.logging.set_verbosity(tf.logging.INFO) AttributeError: module 'tensorflow' has no attribute 'logging'
Một tập tin
Tập lệnh có thể được chạy trên một tệp Python duy nhất:
!tf_upgrade_v2 \
--infile models/samples/cookbook/regression/custom_regression.py \
--outfile /tmp/custom_regression_v2.py
INFO line 38:8: Renamed 'tf.feature_column.input_layer' to 'tf.compat.v1.feature_column.input_layer' INFO line 43:10: Renamed 'tf.layers.dense' to 'tf.compat.v1.layers.dense' INFO line 46:17: Renamed 'tf.layers.dense' to 'tf.compat.v1.layers.dense' INFO line 57:17: tf.losses.mean_squared_error requires manual check. tf.losses have been replaced with object oriented versions in TF 2.0 and after. The loss function calls have been converted to compat.v1 for backward compatibility. Please update these calls to the TF 2.0 versions. INFO line 57:17: Renamed 'tf.losses.mean_squared_error' to 'tf.compat.v1.losses.mean_squared_error' INFO line 61:15: Added keywords to args of function 'tf.shape' INFO line 62:15: Changed tf.to_float call to tf.cast(..., dtype=tf.float32). INFO line 65:40: Renamed 'tf.train.AdamOptimizer' to 'tf.compat.v1.train.AdamOptimizer' INFO line 68:39: Renamed 'tf.train.get_global_step' to 'tf.compat.v1.train.get_global_step' INFO line 83:9: tf.metrics.root_mean_squared_error requires manual check. tf.metrics have been replaced with object oriented versions in TF 2.0 and after. The metric function calls have been converted to compat.v1 for backward compatibility. Please update these calls to the TF 2.0 versions. INFO line 83:9: Renamed 'tf.metrics.root_mean_squared_error' to 'tf.compat.v1.metrics.root_mean_squared_error' INFO line 142:23: Renamed 'tf.train.AdamOptimizer' to 'tf.compat.v1.train.AdamOptimizer' INFO line 162:2: Renamed 'tf.logging.set_verbosity' to 'tf.compat.v1.logging.set_verbosity' INFO line 162:27: Renamed 'tf.logging.INFO' to 'tf.compat.v1.logging.INFO' INFO line 163:2: Renamed 'tf.app.run' to 'tf.compat.v1.app.run' TensorFlow 2.0 Upgrade Script ----------------------------- Converted 1 files Detected 0 issues that require attention -------------------------------------------------------------------------------- Make sure to read the detailed log 'report.txt'
Tập lệnh sẽ in lỗi nếu nó không thể tìm thấy bản sửa lỗi cho mã.
Cây thư mục
Các dự án điển hình, bao gồm cả ví dụ đơn giản này, sẽ sử dụng nhiều hơn một tệp. Thông thường muốn cập nhật toàn bộ gói, do đó, tập lệnh cũng có thể được chạy trên cây thư mục:
# update the .py files and copy all the other files to the outtree
!tf_upgrade_v2 \
--intree models/samples/cookbook/regression/ \
--outtree regression_v2/ \
--reportfile tree_report.txt
INFO line 82:10: tf.estimator.LinearRegressor: Default value of loss_reduction has been changed to SUM_OVER_BATCH_SIZE; inserting old default value tf.keras.losses.Reduction.SUM. INFO line 105:2: Renamed 'tf.logging.set_verbosity' to 'tf.compat.v1.logging.set_verbosity' INFO line 105:27: Renamed 'tf.logging.INFO' to 'tf.compat.v1.logging.INFO' INFO line 106:2: Renamed 'tf.app.run' to 'tf.compat.v1.app.run' INFO line 38:8: Renamed 'tf.feature_column.input_layer' to 'tf.compat.v1.feature_column.input_layer' INFO line 43:10: Renamed 'tf.layers.dense' to 'tf.compat.v1.layers.dense' INFO line 46:17: Renamed 'tf.layers.dense' to 'tf.compat.v1.layers.dense' INFO line 57:17: tf.losses.mean_squared_error requires manual check. tf.losses have been replaced with object oriented versions in TF 2.0 and after. The loss function calls have been converted to compat.v1 for backward compatibility. Please update these calls to the TF 2.0 versions. INFO line 57:17: Renamed 'tf.losses.mean_squared_error' to 'tf.compat.v1.losses.mean_squared_error' INFO line 61:15: Added keywords to args of function 'tf.shape' INFO line 62:15: Changed tf.to_float call to tf.cast(..., dtype=tf.float32). INFO line 65:40: Renamed 'tf.train.AdamOptimizer' to 'tf.compat.v1.train.AdamOptimizer' INFO line 68:39: Renamed 'tf.train.get_global_step' to 'tf.compat.v1.train.get_global_step' INFO line 83:9: tf.metrics.root_mean_squared_error requires manual check. tf.metrics have been replaced with object oriented versions in TF 2.0 and after. The metric function calls have been converted to compat.v1 for backward compatibility. Please update these calls to the TF 2.0 versions. INFO line 83:9: Renamed 'tf.metrics.root_mean_squared_error' to 'tf.compat.v1.metrics.root_mean_squared_error' INFO line 142:23: Renamed 'tf.train.AdamOptimizer' to 'tf.compat.v1.train.AdamOptimizer' INFO line 162:2: Renamed 'tf.logging.set_verbosity' to 'tf.compat.v1.logging.set_verbosity' INFO line 162:27: Renamed 'tf.logging.INFO' to 'tf.compat.v1.logging.INFO' INFO line 163:2: Renamed 'tf.app.run' to 'tf.compat.v1.app.run' INFO line 58:10: tf.estimator.LinearRegressor: Default value of loss_reduction has been changed to SUM_OVER_BATCH_SIZE; inserting old default value tf.keras.losses.Reduction.SUM. INFO line 101:2: Renamed 'tf.logging.set_verbosity' to 'tf.compat.v1.logging.set_verbosity' INFO line 101:27: Renamed 'tf.logging.INFO' to 'tf.compat.v1.logging.INFO' INFO line 102:2: Renamed 'tf.app.run' to 'tf.compat.v1.app.run' INFO line 72:10: tf.estimator.DNNRegressor: Default value of loss_reduction has been changed to SUM_OVER_BATCH_SIZE; inserting old default value tf.keras.losses.Reduction.SUM. INFO line 96:2: Renamed 'tf.logging.set_verbosity' to 'tf.compat.v1.logging.set_verbosity' INFO line 96:27: Renamed 'tf.logging.INFO' to 'tf.compat.v1.logging.INFO' INFO line 97:2: Renamed 'tf.app.run' to 'tf.compat.v1.app.run' WARNING line 125:15: Changing dataset.make_one_shot_iterator() to tf.compat.v1.data.make_one_shot_iterator(dataset). Please check this transformation. INFO line 40:7: Renamed 'tf.test.mock' to 'tf.compat.v1.test.mock' TensorFlow 2.0 Upgrade Script ----------------------------- Converted 7 files Detected 1 issues that require attention -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- File: models/samples/cookbook/regression/automobile_data.py -------------------------------------------------------------------------------- models/samples/cookbook/regression/automobile_data.py:125:15: WARNING: Changing dataset.make_one_shot_iterator() to tf.compat.v1.data.make_one_shot_iterator(dataset). Please check this transformation. Make sure to read the detailed log 'tree_report.txt'
Lưu ý một cảnh báo về hàm dataset.make_one_shot_iterator
.
Bây giờ tập lệnh hoạt động với TensorFlow 2.x:
Lưu ý rằng vì mô-đun tf.compat.v1
được bao gồm trong TF 1.15, tập lệnh được chuyển đổi cũng sẽ chạy trong TensorFlow 1.15.
(cd regression_v2 && python custom_regression.py 2>&1) | tail
I0922 22:16:42.778216 140254758430528 estimator.py:2074] Saving dict for global step 1000: global_step = 1000, loss = 651.5428, rmse = 3.684265 INFO:tensorflow:Saving 'checkpoint_path' summary for global step 1000: /tmp/tmpk2_4r192/model.ckpt-1000 I0922 22:16:42.817190 140254758430528 estimator.py:2135] Saving 'checkpoint_path' summary for global step 1000: /tmp/tmpk2_4r192/model.ckpt-1000 Tensor("IteratorGetNext:25", shape=(None,), dtype=float64, device=/device:CPU:0) Tensor("Squeeze:0", shape=(None,), dtype=float32) ******************************************************************************** RMS error for the test set: $3684
Báo cáo chi tiết
Tập lệnh cũng báo cáo một danh sách các thay đổi chi tiết. Trong ví dụ này, nó tìm thấy một chuyển đổi có thể không an toàn và bao gồm một cảnh báo ở đầu tệp:
head -n 20 tree_report.txt
TensorFlow 2.0 Upgrade Script ----------------------------- Converted 7 files Detected 1 issues that require attention -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- File: models/samples/cookbook/regression/automobile_data.py -------------------------------------------------------------------------------- models/samples/cookbook/regression/automobile_data.py:125:15: WARNING: Changing dataset.make_one_shot_iterator() to tf.compat.v1.data.make_one_shot_iterator(dataset). Please check this transformation. ================================================================================ Detailed log follows: ================================================================================ ================================================================================ Input tree: 'models/samples/cookbook/regression/' ================================================================================ -------------------------------------------------------------------------------- Processing file 'models/samples/cookbook/regression/__init__.py' outputting to 'regression_v2/__init__.py'
Lưu ý lại một cảnh báo về Dataset.make_one_shot_iterator function
.
Trong các trường hợp khác, đầu ra sẽ giải thích lý do cho những thay đổi không nhỏ:
%%writefile dropout.py
import tensorflow as tf
d = tf.nn.dropout(tf.range(10), 0.2)
z = tf.zeros_like(d, optimize=False)
Writing dropout.py
!tf_upgrade_v2 \
--infile dropout.py \
--outfile dropout_v2.py \
--reportfile dropout_report.txt > /dev/null
cat dropout_report.txt
TensorFlow 2.0 Upgrade Script ----------------------------- Converted 1 files Detected 0 issues that require attention -------------------------------------------------------------------------------- ================================================================================ Detailed log follows: ================================================================================ -------------------------------------------------------------------------------- Processing file 'dropout.py' outputting to 'dropout_v2.py' -------------------------------------------------------------------------------- 3:4: INFO: Changing keep_prob arg of tf.nn.dropout to rate, and recomputing value. 4:4: INFO: Renaming tf.zeros_like to tf.compat.v1.zeros_like because argument optimize is present. tf.zeros_like no longer takes an optimize argument, and behaves as if optimize=True. This call site specifies something other than optimize=True, so it was converted to compat.v1. --------------------------------------------------------------------------------
Đây là nội dung tệp đã sửa đổi, lưu ý cách tập lệnh thêm tên đối số để xử lý các đối số đã chuyển và đã đổi tên:
cat dropout_v2.py
import tensorflow as tf d = tf.nn.dropout(tf.range(10), rate=1 - (0.2)) z = tf.compat.v1.zeros_like(d, optimize=False)
Một dự án lớn hơn có thể chứa một vài lỗi. Ví dụ chuyển đổi mô hình deeplab:
!tf_upgrade_v2 \
--intree models/research/deeplab \
--outtree deeplab_v2 \
--reportfile deeplab_report.txt > /dev/null
Nó tạo ra các tệp đầu ra:
ls deeplab_v2
README.md datasets input_preprocess.py train.py __init__.py deeplab_demo.ipynb local_test.sh utils common.py eval.py local_test_mobilenetv2.sh vis.py common_test.py export_model.py model.py core g3doc model_test.py
Nhưng đã có sai sót. Báo cáo sẽ giúp bạn xác định những gì bạn cần sửa trước khi chạy. Đây là ba lỗi đầu tiên:
cat deeplab_report.txt | grep -i models/research/deeplab | grep -i error | head -n 3
models/research/deeplab/eval.py:28:7: ERROR: Using member tf.contrib.slim in deprecated module tf.contrib. tf.contrib.slim cannot be converted automatically. tf.contrib will not be distributed with TensorFlow 2.0, please consider an alternative in non-contrib TensorFlow, a community-maintained repository such as tensorflow/addons, or fork the required code. models/research/deeplab/eval.py:146:8: ERROR: Using member tf.contrib.metrics.aggregate_metric_map in deprecated module tf.contrib. tf.contrib.metrics.aggregate_metric_map cannot be converted automatically. tf.contrib will not be distributed with TensorFlow 2.0, please consider an alternative in non-contrib TensorFlow, a community-maintained repository such as tensorflow/addons, or fork the required code. models/research/deeplab/export_model.py:25:7: ERROR: Using member tf.contrib.slim in deprecated module tf.contrib. tf.contrib.slim cannot be converted automatically. tf.contrib will not be distributed with TensorFlow 2.0, please consider an alternative in non-contrib TensorFlow, a community-maintained repository such as tensorflow/addons, or fork the required code.
"Chế độ an toàn
Tập lệnh chuyển đổi cũng có một chế độ SAFETY
ít xâm lấn hơn, chỉ cần thay đổi các mục nhập để sử dụng mô-đun tensorflow.compat.v1
:
cat dropout.py
import tensorflow as tf d = tf.nn.dropout(tf.range(10), 0.2) z = tf.zeros_like(d, optimize=False)
tf_upgrade_v2 --mode SAFETY --infile dropout.py --outfile dropout_v2_safe.py > /dev/null
cat dropout_v2_safe.py
import tensorflow.compat.v1 as tf d = tf.nn.dropout(tf.range(10), 0.2) z = tf.zeros_like(d, optimize=False)
Như bạn có thể thấy, điều này không nâng cấp mã của bạn, nhưng cho phép mã TensorFlow 1 chạy với mã nhị phân TensorFlow 2. Lưu ý rằng điều này không có nghĩa là mã của bạn đang chạy các hành vi TF 2.x được hỗ trợ!
Cảnh báo
Không cập nhật các phần mã của bạn theo cách thủ công trước khi chạy tập lệnh này. Đặc biệt, các hàm có các đối số được sắp xếp lại như
tf.argmax
hoặctf.batch_to_space
khiến tập lệnh thêm sai đối số từ khóa làm sai mã hiện tại của bạn.Tập lệnh giả định rằng
tensorflow
được nhập bằng cáchimport tensorflow as tf
hoặcimport tensorflow.compat.v1 as tf
.Tập lệnh này không sắp xếp lại các đối số. Thay vào đó, tập lệnh thêm các đối số từ khóa vào các hàm có các đối số của chúng được sắp xếp lại.
Hãy xem tf2up.ml để biết một công cụ thuận tiện để nâng cấp sổ ghi chép Jupyter và tệp Python trong kho lưu trữ GitHub.
Để báo cáo lỗi tập lệnh nâng cấp hoặc yêu cầu tính năng, vui lòng gửi sự cố trên GitHub .