Datasets documentation
Sử dụng Datasets với TensorFlow
Sử dụng Datasets với TensorFlow
Tài liệu này là phần giới thiệu nhanh về cách sử dụng datasets với TensorFlow, tập trung đặc biệt vào cách lấy các đối tượng tf.Tensor từ các dataset của chúng ta, và cách truyền dữ liệu từ các đối tượng Dataset của Hugging Face đến các phương thức Keras
như model.fit().
Định dạng tập dữ liệu
Mặc định, các tập dữ liệu trả về các đối tượng Python thông thường: số nguyên, số thực, chuỗi, danh sách, v.v.
Để nhận các tensor TensorFlow thay thế, bạn có thể đặt định dạng của tập dữ liệu thành tf:
>>> from datasets import Dataset
>>> data = [[1, 2],[3, 4]]
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([1, 2])>}
>>> ds[:2]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}Một đối tượng Dataset là lớp bọc của một bảng Arrow, cho phép đọc nhanh từ các mảng trong bộ dữ liệu sang các tensor TensorFlow.
Điều này có thể hữu ích để chuyển đổi bộ dữ liệu của bạn thành một dict các đối tượng Tensor, hoặc để viết một generator để nạp các mẫu TF từ nó. Nếu bạn muốn chuyển đổi toàn bộ bộ dữ liệu thành Tensor, chỉ cần truy vấn toàn bộ bộ dữ liệu:
>>> ds[:]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}Mảng N chiều
Nếu tập dữ liệu của bạn bao gồm các mảng N chiều, bạn sẽ thấy rằng mặc định chúng được coi là cùng một tensor nếu hình dạng được cố định:
>>> from datasets import Dataset
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]] # fixed shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}Ngược lại, một tập dữ liệu định dạng TensorFlow sẽ xuất ra một RaggedTensor thay vì một tensor đơn lẻ:
>>> from datasets import Dataset
>>> data = [[[1, 2],[3]],[[4, 5, 6],[7, 8]]] # varying shape
>>> ds = Dataset.from_dict({"data": data})
>>> ds = ds.with_format("torch")
>>> ds[0]
{'data': <tf.RaggedTensor [[1, 2], [3]]>}Tuy nhiên, logic này thường đòi hỏi các phép so sánh shape và sao chép dữ liệu chậm.
Để tránh điều này, bạn phải sử dụng rõ ràng kiểu đặc trưng Array và chỉ định shape của các tensor của bạn:
>>> from datasets import Dataset, Features, Array2D
>>> data = [[[1, 2],[3, 4]],[[5, 6],[7, 8]]]
>>> features = Features({"data": Array2D(shape=(2, 2), dtype='int32')})
>>> ds = Dataset.from_dict({"data": data}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[0]
{'data': <tf.Tensor: shape=(2, 2), dtype=int64, numpy=
array([[1, 2],
[3, 4]])>}
>>> ds[:2]
{'data': <tf.Tensor: shape=(2, 2, 2), dtype=int64, numpy=
array([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])>}Các loại tính năng khác
Dữ liệu ClassLabel được chuyển đổi thành tensor một cách chính xác:
>>> from datasets import Dataset, Features, ClassLabel
>>> labels = [0, 0, 1]
>>> features = Features({"label": ClassLabel(names=["negative", "positive"])})
>>> ds = Dataset.from_dict({"label": labels}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[:3]
{'label': <tf.Tensor: shape=(3,), dtype=int64, numpy=array([0, 0, 1])>}Chuỗi và đối tượng nhị phân cũng được hỗ trợ:
>>> from datasets import Dataset, Features
>>> text = ["foo", "bar"]
>>> data = [0, 1]
>>> ds = Dataset.from_dict({"text": text, "data": data})
>>> ds = ds.with_format("tf")
>>> ds[:2]
{'text': <tf.Tensor: shape=(2,), dtype=string, numpy=array([b'foo', b'bar'], dtype=object)>,
'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 1])>}Bạn cũng có thể định dạng rõ ràng một số cột nhất định và để các cột khác không được định dạng:
>>> ds = ds.with_format("tf", columns=["data"], output_all_columns=True)
>>> ds[:2]
{'data': <tf.Tensor: shape=(2,), dtype=int64, numpy=array([0, 1])>,
'text': ['foo', 'bar']}Các đối tượng chuỗi và nhị phân không thay đổi, vì PyTorch chỉ hỗ trợ các số.
Các loại tính năng Image và Audio cũng được hỗ trợ.
Để sử dụng kiểu tính năng Image, bạn cần cài đặt phần mở rộng
visionbằng lệnhpip install datasets[vision].
>>> from datasets import Dataset, Features, Audio, Image
>>> images = ["path/to/image.png"] * 10
>>> features = Features({"image": Image()})
>>> ds = Dataset.from_dict({"image": images}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[0]
{'image': <tf.Tensor: shape=(512, 512, 4), dtype=uint8, numpy=
array([[[255, 215, 106, 255],
[255, 215, 106, 255],
...,
[255, 255, 255, 255],
[255, 255, 255, 255]]], dtype=uint8)>}
>>> ds[:2]
{'image': <tf.Tensor: shape=(2, 512, 512, 4), dtype=uint8, numpy=
array([[[[255, 215, 106, 255],
[255, 215, 106, 255],
...,
[255, 255, 255, 255],
[255, 255, 255, 255]]]], dtype=uint8)>}Để sử dụng kiểu tính năng Audio, bạn cần cài đặt phần mở rộng
audiobằng lệnhpip install datasets[audio].
>>> from datasets import Dataset, Features, Audio, Image
>>> audio = ["path/to/audio.wav"] * 10
>>> features = Features({"audio": Audio()})
>>> ds = Dataset.from_dict({"audio": audio}, features=features)
>>> ds = ds.with_format("tf")
>>> ds[0]["audio"]["array"]
<tf.Tensor: shape=(202311,), dtype=float32, numpy=
array([ 6.1035156e-05, 1.5258789e-05, 1.6784668e-04, ...,
-1.5258789e-05, -1.5258789e-05, 1.5258789e-05], dtype=float32)>
>>> ds[0]["audio"]["sampling_rate"]
<tf.Tensor: shape=(), dtype=int32, numpy=44100>Tải dữ liệu
Mặc dù bạn có thể tải các mẫu và lô riêng lẻ chỉ bằng cách chỉ mục vào tập dữ liệu của mình, cách này sẽ không hoạt động nếu bạn muốn sử dụng các phương thức Keras như fit() và predict(). Bạn có thể viết một hàm generator để xáo trộn và tải các lô từ tập dữ liệu của mình rồi fit() trên đó, nhưng điều đó nghe có vẻ là một khối lượng công việc không cần thiết. Thay vào đó, nếu bạn muốn truyền dữ liệu từ tập dữ liệu của mình theo thời gian thực, chúng tôi khuyến nghị chuyển đổi tập dữ liệu của bạn thành tf.data.Dataset bằng phương thức to_tf_dataset().
Lớp tf.data.Dataset bao phủ một phạm vi rộng các trường hợp sử dụng - nó thường được tạo từ các Tensor trong bộ nhớ, hoặc sử dụng một hàm tải để đọc các tệp trên đĩa
hoặc bộ nhớ lưu trữ bên ngoài. Tập dữ liệu có thể được biến đổi tùy ý bằng phương thức map(), hoặc các phương thức như batch() và shuffle() có thể được sử dụng để tạo một tập dữ liệu sẵn sàng cho việc huấn luyện. Các phương thức này không thay đổi dữ liệu được lưu trữ
theo bất kỳ cách nào - thay vào đó, các phương thức xây dựng một đồ thị đường ống dữ liệu sẽ được thực thi khi tập dữ liệu được lặp qua,
thường là trong quá trình huấn luyện hoặc suy luận mô hình. Điều này khác với phương thức map() của các đối tượng Dataset của Hugging Face,
nơi chạy hàm map ngay lập tức và lưu các cột mới hoặc đã thay đổi.
Vì toàn bộ pipeline tiền xử lý dữ liệu có thể được biên dịch trong một tf.data.Dataset, cách tiếp cận này cho phép nạp dữ liệu và huấn luyện song song, bất đồng bộ ở quy mô lớn. Tuy nhiên, yêu cầu về biên dịch đồ thị có thể là một hạn chế, đặc biệt đối với các tokenizer của Hugging Face, vốn thường không (chưa!) thể được biên dịch như một phần của đồ thị TF. Do đó, chúng tôi thường khuyên bạn nên tiền xử lý dataset dưới dạng một Hugging Face dataset, nơi có thể sử dụng các hàm Python tùy ý, sau đó chuyển đổi sang tf.data.Dataset bằng to_tf_dataset() để có được một dataset đã được phân batch sẵn sàng cho việc huấn luyện. Để xem các ví dụ về cách tiếp cận này, vui lòng xem các ví dụ hoặc các notebook cho transformers.
Sử dụng to_tf_dataset()
Việc sử dụng to_tf_dataset() rất đơn giản. Khi bộ dữ liệu của bạn đã được tiền xử lý và sẵn sàng, chỉ cần gọi nó như sau:
>>> from datasets import Dataset
>>> data = {"inputs": [[1, 2],[3, 4]], "labels": [0, 1]}
>>> ds = Dataset.from_dict(data)
>>> tf_ds = ds.to_tf_dataset(
columns=["inputs"],
label_cols=["labels"],
batch_size=2,
shuffle=True
)Đối tượng tf_ds được trả về ở đây giờ đã sẵn sàng để huấn luyện và có thể được truyền trực tiếp vào model.fit(). Lưu ý rằng bạn đã đặt kích thước lô (batch size) khi tạo tập dữ liệu, vì vậy bạn không cần chỉ định nó khi gọi fit():
>>> model.fit(tf_ds, epochs=2)Để có mô tả đầy đủ về các đối số, vui lòng xem tài liệu to_tf_dataset(). Trong nhiều trường hợp, bạn cũng sẽ cần thêm collate_fn vào lời gọi của mình. Đây là một hàm nhận nhiều phần tử của tập dữ liệu và kết hợp chúng thành một batch duy nhất. Khi tất cả các phần tử có cùng độ dài, bộ collator mặc định tích hợp sẵn sẽ đủ dùng, nhưng đối với các tác vụ phức tạp hơn, có thể cần đến một bộ collator tùy chỉnh. Cụ thể, nhiều tác vụ có các mẫu với độ dài chuỗi thay đổi, đòi hỏi một data collator có thể đệm (pad) các batch một cách chính xác. Bạn có thể xem các ví dụ về điều này trong các examples và notebooks NLP của transformers, nơi độ dài chuỗi thay đổi rất phổ biến.
Nếu bạn thấy việc nạp dữ liệu bằng to_tf_dataset chậm, bạn cũng có thể sử dụng đối số num_workers. Tham số này khởi tạo nhiều tiến trình con để nạp dữ liệu song song. Tính năng này mới được thêm vào và vẫn còn mang tính thử nghiệm - vui lòng báo cáo sự cố nếu bạn gặp bất kỳ lỗi nào khi sử dụng!
Khi nào nên sử dụng to_tf_dataset
Độc giả tinh ý có thể đã nhận ra rằng tại thời điểm này, chúng ta đã đề xuất hai cách tiếp cận để đạt được cùng một mục tiêu - nếu bạn muốn truyền dataset của mình vào một mô hình TensorFlow, bạn có thể chuyển đổi dataset thành một Tensor hoặc dict của các Tensors bằng cách sử dụng .with_format('tf'), hoặc bạn có thể chuyển đổi dataset thành một tf.data.Dataset bằng to_tf_dataset(). Cả hai cách này đều có thể được truyền vào model.fit(), vậy bạn nên chọn cách nào?
Điều quan trọng cần nhận ra là khi bạn chuyển đổi toàn bộ dataset thành các Tensor, nó sẽ ở trạng thái tĩnh và được nạp hoàn toàn vào RAM. Cách này đơn giản và tiện lợi, nhưng nếu bất kỳ điều nào sau đây áp dụng, bạn nên dùng to_tf_dataset() thay thế:
- Bộ dữ liệu của bạn quá lớn để chứa trong RAM.
to_tf_dataset()chỉ truyền trực tiếp một lô (batch) tại một thời điểm, do đó ngay cả các bộ dữ liệu rất lớn cũng có thể được xử lý bằng phương pháp này. - Bạn muốn áp dụng các phép biến đổi ngẫu nhiên bằng
dataset.with_transform()hoặccollate_fn. Điều này phổ biến trong một số phương thức (modality), chẳng hạn như tăng cường hình ảnh (image augmentation) khi huấn luyện mô hình thị giác, hoặc che ngẫu nhiên (random masking) khi huấn luyện mô hình ngôn ngữ che (masked language model). Việc sử dụngto_tf_dataset()sẽ áp dụng các phép biến đổi đó vào thời điểm một lô (batch) được nạp, nghĩa là cùng một mẫu sẽ nhận được các phép tăng cường khác nhau mỗi lần được nạp. Đây thường là điều bạn muốn. - Dữ liệu của bạn có một chiều kích thay đổi, chẳng hạn như văn bản đầu vào trong NLP bao gồm số lượng token khác nhau. Khi bạn tạo một batch với các mẫu có chiều kích thay đổi, giải pháp chuẩn là đệm (pad) các mẫu ngắn hơn đến độ dài của mẫu dài nhất. Khi bạn stream các mẫu từ một dataset bằng
to_tf_dataset, bạn có thể áp dụng việc đệm này cho mỗi batch thông quacollate_fncủa bạn. Tuy nhiên, nếu bạn muốn chuyển đổi một dataset như vậy thành cácTensordense, thì bạn sẽ phải đệm các mẫu đến độ dài của mẫu dài nhất trong toàn bộ dataset! Điều này có thể dẫn đến một lượng đệm rất lớn, gây lãng phí bộ nhớ và làm giảm tốc độ của mô hình.
Lưu ý và hạn chế
Hiện tại, to_tf_dataset() luôn trả về một dataset dạng batch - chúng tôi sẽ sớm thêm hỗ trợ cho các dataset không có batch!