Tài liệu này mô tả các phương pháp lập trình nhằm tối đa hoá cơ hội thành công cho các phép tính phức tạp hoặc tốn kém trên Earth Engine. Các phương thức được mô tả ở đây áp dụng cho cả phép tính tương tác (ví dụ: Trình soạn thảo mã) và phép tính theo lô (Export
), mặc dù thường thì các phép tính chạy trong thời gian dài nên được chạy trong hệ thống theo lô.
Tránh kết hợp các hàm và đối tượng của máy khách với các hàm và đối tượng của máy chủ
Đối tượng máy chủ Earth Engine là các đối tượng có hàm khởi tạo bắt đầu bằng ee
(ví dụ: ee.Image
, ee.Reducer
) và mọi phương thức trên các đối tượng đó đều là hàm máy chủ. Mọi đối tượng không được tạo theo cách này đều là đối tượng ứng dụng khách. Các đối tượng của ứng dụng có thể đến từ Trình soạn thảo mã (ví dụ: Map
, Chart
) hoặc ngôn ngữ JavaScript (ví dụ: Date
, Math
, []
, {}
).
Để tránh hành vi ngoài ý muốn, đừng kết hợp các hàm máy khách và máy chủ trong tập lệnh như đã thảo luận tại đây và tại đây và tại đây. Hãy xem trang này và/hoặc hướng dẫn này để biết nội dung giải thích chi tiết về máy khách so với máy chủ trong Earth Engine. Ví dụ sau đây minh hoạ những nguy hiểm khi kết hợp chức năng máy khách và máy chủ:
Lỗi – mã này không hoạt động!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Won't work.
for(var i=0; i<table.size(); i++) {
print('No!');
}
Bạn có phát hiện thấy lỗi không? Xin lưu ý rằng table.size()
là một phương thức máy chủ trên đối tượng máy chủ và không thể được sử dụng với chức năng phía máy khách, chẳng hạn như điều kiện <
.
Bạn có thể muốn sử dụng vòng lặp for khi thiết lập giao diện người dùng, vì các đối tượng và phương thức ui
của Trình chỉnh sửa mã nằm ở phía máy khách. (Tìm hiểu thêm về cách tạo giao diện người dùng trong Earth Engine). Ví dụ:
Sử dụng các hàm ứng dụng để thiết lập giao diện người dùng.
var panel = ui.Panel();
for(var i=1; i<8; i++) {
panel.widgets().set(i, ui.Button('button ' + i))
}
print(panel);
Ngược lại, map()
là một hàm máy chủ và chức năng máy khách sẽ không hoạt động bên trong hàm được truyền đến map()
. Ví dụ:
Lỗi – mã này không hoạt động!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Error:
var foobar = table.map(function(f) {
print(f); // Can't use a client function here.
// Can't Export, either.
});
Để thực hiện một thao tác nào đó cho mọi phần tử trong một bộ sưu tập, hãy map()
một hàm trên bộ sưu tập và set()
một thuộc tính:
Sử dụng map()
và set()
!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
print(table.first());
// Do something to every element of a collection.
var withMoreProperties = table.map(function(f) {
// Set a property.
return f.set('area_sq_meters', f.area())
});
print(withMoreProperties.first());
Bạn cũng có thể filter()
tập hợp dựa trên các thuộc tính đã tính toán hoặc hiện có và print()
kết quả. Xin lưu ý rằng bạn không thể in một bộ sưu tập có nhiều hơn 5000 phần tử. Nếu bạn gặp lỗi "Collection query aborted after accumulating over 5000 elements" (Truy vấn tập hợp bị huỷ sau khi tích luỹ hơn 5000 phần tử), hãy filter()
hoặc limit()
tập hợp trước khi in.
Tránh chuyển đổi sang danh sách khi không cần thiết
Các tập hợp trong Earth Engine được xử lý bằng cách sử dụng các hoạt động tối ưu hoá bị hỏng do chuyển đổi tập hợp thành loại List
hoặc Array
. Trừ phi bạn cần truy cập ngẫu nhiên vào các phần tử của bộ sưu tập (tức là bạn cần lấy phần tử thứ i của bộ sưu tập), hãy sử dụng bộ lọc trên bộ sưu tập để truy cập vào từng phần tử của bộ sưu tập. Ví dụ sau đây minh hoạ sự khác biệt giữa việc chuyển đổi loại (không nên dùng) và lọc (nên dùng) để truy cập vào một phần tử trong một tập hợp:
Đừng chuyển đổi thành danh sách khi không cần thiết!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Do NOT do this!!
var list = table.toList(table.size());
print(list.get(13)); // User memory limit exceeded.
Xin lưu ý rằng bạn có thể dễ dàng kích hoạt lỗi bằng cách chuyển đổi một bộ sưu tập thành danh sách một cách không cần thiết. Cách an toàn hơn là sử dụng filter()
:
Sử dụng filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Niger')).first());
Xin lưu ý rằng bạn nên sử dụng bộ lọc càng sớm càng tốt trong quá trình phân tích.
Tránh ee.Algorithms.If()
Không sử dụng ee.Algorithms.If()
để triển khai logic phân nhánh, đặc biệt là trong hàm được liên kết. Như ví dụ sau đây minh hoạ, ee.Algorithms.If()
có thể tiêu tốn nhiều bộ nhớ và không được khuyến khích:
Đừng sử dụng If()
!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
// Do NOT do this!
var veryBad = table.map(function(f) {
return ee.Algorithms.If({
condition: ee.String(f.get('country_na')).compareTo('Chad').gt(0),
trueCase: f, // Do something.
falseCase: null // Do something else.
});
}, true);
print(veryBad); // User memory limit exceeded.
// If() may evaluate both the true and false cases.
Lưu ý rằng đối số thứ hai cho map()
là true
. Điều này có nghĩa là hàm được ánh xạ có thể trả về giá trị rỗng và các giá trị này sẽ bị loại bỏ trong tập hợp kết quả.
Điều đó có thể hữu ích (không có If()
), nhưng giải pháp dễ nhất ở đây là sử dụng bộ lọc:
Sử dụng filter()
!
print(table.filter(ee.Filter.eq('country_na', 'Chad')));
Như đã trình bày trong hướng dẫn này, phương pháp lập trình hàm sử dụng bộ lọc là cách chính xác để áp dụng một logic cho một số phần tử của một tập hợp và một logic khác cho các phần tử khác của tập hợp.
Tránh reproject()
Không sử dụng tính năng lập lại bản đồ trừ khi thực sự cần thiết. Một lý do bạn có thể muốn sử dụng reproject()
là để buộc các phép tính của Trình soạn thảo mã diễn ra ở một tỷ lệ cụ thể để bạn có thể kiểm tra kết quả ở tỷ lệ phân tích mong muốn. Trong ví dụ tiếp theo, các mảng pixel nóng được tính toán và số lượng pixel trong mỗi mảng được tính toán. Chạy ví dụ và nhấp vào một trong các bản vá. Xin lưu ý rằng số lượng pixel khác nhau giữa dữ liệu được chiếu lại và dữ liệu chưa được chiếu lại.
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var sf = ee.Geometry.Point([-122.405, 37.786]);
Map.centerObject(sf, 13);
// A reason to reproject - counting pixels and exploring interactively.
var image = l8sr.filterBounds(sf)
.filterDate('2019-06-01', '2019-12-31')
.first();
image = image.multiply(0.00341802).add(149); // Apply scale factors.
Map.addLayer(image, {bands: ['ST_B10'], min: 280, max: 317}, 'image');
var hotspots = image.select('ST_B10').gt(317)
.selfMask()
.rename('hotspots');
var objectSize = hotspots.connectedPixelCount(256);
Map.addLayer(objectSize, {min: 1, max: 256}, 'Size No Reproject', false);
// Beware of reproject! Don't zoom out on reprojected data.
var reprojected = objectSize.reproject(hotspots.projection());
Map.addLayer(reprojected, {min: 1, max: 256}, 'Size Reproject', false);
Lý do của sự khác biệt là do tỷ lệ phân tích được đặt theo mức thu phóng của Trình soạn thảo mã. Bằng cách gọi reproject()
, bạn sẽ đặt tỷ lệ của phép tính thay vì Trình chỉnh sửa mã. Hãy hết sức thận trọng khi sử dụng reproject()
vì những lý do được mô tả trong tài liệu này.
Lọc và select()
trước
Nhìn chung, hãy lọc các bộ sưu tập đầu vào theo thời gian, vị trí và/hoặc siêu dữ liệu trước khi làm bất cứ việc gì khác với bộ sưu tập đó. Áp dụng các bộ lọc chọn lọc hơn trước khi áp dụng các bộ lọc chọn lọc ít hơn. Bộ lọc không gian và/hoặc bộ lọc thời gian thường có tính chọn lọc hơn. Ví dụ: lưu ý rằng select()
và filter()
được áp dụng trước
map()
:
var images = ee.ImageCollection('COPERNICUS/S2_SR_HARMONIZED');
var sf = ee.Geometry.Point([-122.463, 37.768]);
// Expensive function to reduce the neighborhood of an image.
var reduceFunction = function(image) {
return image.reduceNeighborhood({
reducer: ee.Reducer.mean(),
kernel: ee.Kernel.square(4)
});
};
var bands = ['B4', 'B3', 'B2'];
// Select and filter first!
var reasonableComputation = images
.select(bands)
.filterBounds(sf)
.filterDate('2018-01-01', '2019-02-01')
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 1))
.aside(print) // Useful for debugging.
.map(reduceFunction)
.reduce('mean')
.rename(bands);
var viz = {bands: bands, min: 0, max: 10000};
Map.addLayer(reasonableComputation, viz, 'reasonableComputation');
Sử dụng updateMask()
thay vì mask()
Sự khác biệt giữa updateMask()
và mask()
là hàm trước thực hiện một and()
logic của đối số (mặt nạ mới) và mặt nạ hình ảnh hiện có, trong khi mask()
chỉ thay thế mặt nạ hình ảnh bằng đối số. Nguy hiểm của trường hợp sau là bạn có thể vô tình làm lộ các pixel. Trong ví dụ này, mục tiêu là che các pixel có độ cao nhỏ hơn hoặc bằng 300 mét. Như bạn có thể thấy (thu nhỏ), việc sử dụng mask()
sẽ khiến nhiều pixel bị bỏ ngỏ, những pixel không thuộc hình ảnh mà bạn quan tâm:
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var sf = ee.Geometry.Point([-122.40554461769182, 37.786807309873716]);
var aw3d30 = ee.Image('JAXA/ALOS/AW3D30_V1_1');
Map.centerObject(sf, 7);
var image = l8sr.filterBounds(sf)
.filterDate('2019-06-01', '2019-12-31')
.first();
image = image.multiply(0.0000275).subtract(0.2); // Apply scale factors.
var vis = {bands: ['SR_B4', 'SR_B3', 'SR_B2'], min: 0, max: 0.3};
Map.addLayer(image, vis, 'image', false);
var mask = aw3d30.select('AVE').gt(300);
Map.addLayer(mask, {}, 'mask', false);
// NO! Don't do this!
var badMask = image.mask(mask);
Map.addLayer(badMask, vis, 'badMask');
var goodMask = image.updateMask(mask);
Map.addLayer(goodMask, vis, 'goodMask', false);
Kết hợp các reducer
Nếu bạn cần nhiều số liệu thống kê (ví dụ: trung bình và độ lệch chuẩn) từ một đầu vào duy nhất (ví dụ: một vùng hình ảnh), thì bạn nên kết hợp các bộ giảm. Ví dụ: để lấy số liệu thống kê về hình ảnh, hãy kết hợp các bộ giảm như sau:
var image = ee.Image(
'COPERNICUS/S2_HARMONIZED/20150821T111616_20160314T094808_T30UWU');
// Get mean and SD in every band by combining reducers.
var stats = image.reduceRegion({
reducer: ee.Reducer.mean().combine({
reducer2: ee.Reducer.stdDev(),
sharedInputs: true
}),
geometry: ee.Geometry.Rectangle([-2.15, 48.55, -1.83, 48.72]),
scale: 10,
bestEffort: true // Use maxPixels if you care about scale.
});
print(stats);
// Extract means and SDs to images.
var meansImage = stats.toImage().select('.*_mean');
var sdsImage = stats.toImage().select('.*_stdDev');
Trong ví dụ này, hãy lưu ý rằng bộ giảm trung bình được kết hợp với bộ giảm độ lệch chuẩn và sharedInputs
là true để cho phép một lượt truyền qua các pixel đầu vào. Trong từ điển đầu ra, tên của hàm giảm được thêm vào tên ban nhạc. Để lấy hình ảnh trung bình và SD (ví dụ: để chuẩn hoá hình ảnh đầu vào), bạn có thể biến các giá trị thành hình ảnh và sử dụng biểu thức chính quy để trích xuất trung bình và SD riêng lẻ như minh hoạ trong ví dụ.
Sử dụng Export
Đối với các phép tính dẫn đến lỗi "Quá hạn mức bộ nhớ người dùng" hoặc "Quá thời gian chờ tính toán" trong Trình soạn thảo mã, các phép tính tương tự có thể thành công bằng cách sử dụng Export
. Điều này là do thời gian chờ lâu hơn và mức sử dụng bộ nhớ được phép lớn hơn khi chạy trong hệ thống xử lý hàng loạt (nơi các hoạt động xuất chạy). (Bạn có thể muốn thử các phương pháp khác trước như được nêu chi tiết trong tài liệu gỡ lỗi). Tiếp tục ví dụ trước, giả sử từ điển trả về một lỗi. Bạn có thể lấy kết quả bằng cách làm như sau:
var link = '86836482971a35a5e735a17e93c23272';
Export.table.toDrive({
collection: ee.FeatureCollection([ee.Feature(null, stats)]),
description: 'exported_stats_demo_' + link,
fileFormat: 'CSV'
});
Xin lưu ý rằng đường liên kết được nhúng vào tên thành phần để có thể tái tạo. Ngoài ra, hãy lưu ý rằng nếu muốn xuất toAsset
, bạn cần cung cấp một hình học, có thể là bất kỳ hình học nào, chẳng hạn như tâm hình ảnh, có kích thước nhỏ và chi phí tính toán thấp. (tức là không sử dụng hình học phức tạp nếu không cần).
Hãy xem trang gỡ lỗi để biết ví dụ về cách sử dụng Export
nhằm giải quyết lỗi Hết thời gian tính toán và Quá nhiều hoạt động tổng hợp đồng thời. Hãy xem tài liệu này để biết thông tin chi tiết về việc xuất dữ liệu nói chung.
Nếu không cần cắt, đừng sử dụng clip()
Việc sử dụng clip()
không cần thiết sẽ tăng thời gian tính toán. Tránh sử dụng clip()
trừ phi cần thiết cho quá trình phân tích. Nếu bạn không chắc chắn, đừng cắt. Ví dụ về cách sử dụng đoạn video không phù hợp:
Đừng cắt bớt dữ liệu đầu vào một cách không cần thiết!
var table = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017');
var l8sr = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var belgium = table.filter(ee.Filter.eq('country_na', 'Belgium')).first();
// Do NOT clip unless you need to.
var unnecessaryClip = l8sr
.select('SR_B4') // Good.
.filterBounds(belgium.geometry()) // Good.
.filterDate('2019-01-01', '2019-04-01') // Good.
.map(function(image) {
return image.clip(belgium.geometry()); // NO! Bad! Not necessary.
})
.median()
.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: belgium.geometry(),
scale: 30,
maxPixels: 1e10,
});
print(unnecessaryClip);
Bạn có thể bỏ qua hoàn toàn việc cắt hình ảnh đầu vào vì vùng được chỉ định trong lệnh gọi reduceRegion()
:
Chỉ định khu vực cho đầu ra!
var noClipNeeded = l8sr
.select('SR_B4') // Good.
.filterBounds(belgium.geometry()) // Good.
.filterDate('2019-01-01', '2019-12-31') // Good.
.median()
.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: belgium.geometry(), // Geometry is specified here.
scale: 30,
maxPixels: 1e10,
});
print(noClipNeeded);
Nếu quá trình tính toán này hết thời gian chờ, hãy Export
như trong ví dụ này.
Nếu bạn cần cắt với một bộ sưu tập phức tạp, hãy sử dụng clipToCollection()
Nếu bạn thực sự cần cắt một nội dung nào đó và các hình học mà bạn muốn sử dụng để cắt nằm trong một bộ sưu tập, hãy sử dụng clipToCollection()
:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var image = ee.Image('JAXA/ALOS/AW3D30_V1_1');
var complexCollection = ecoregions
.filter(ee.Filter.eq('BIOME_NAME',
'Tropical & Subtropical Moist Broadleaf Forests'));
Map.addLayer(complexCollection, {}, 'complexCollection');
var clippedTheRightWay = image.select('AVE')
.clipToCollection(complexCollection);
Map.addLayer(clippedTheRightWay, {}, 'clippedTheRightWay', false);
ĐỪNG sử dụng featureCollection.geometry()
hoặc featureCollection.union()
trên các bộ sưu tập lớn và/hoặc phức tạp, vì các bộ sưu tập này có thể tốn nhiều bộ nhớ hơn.
Không sử dụng một tập hợp phức tạp làm vùng cho một hàm giảm
Nếu bạn cần giảm không gian để trình giảm dữ liệu gộp các đầu vào từ nhiều vùng trong FeatureCollection
, đừng cung cấp featureCollection.geometry()
làm đầu vào geometry
cho trình giảm dữ liệu. Thay vào đó, hãy sử dụng clipToCollection()
và một vùng đủ lớn để bao gồm các giới hạn của tập hợp. Ví dụ:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var image = ee.Image('JAXA/ALOS/AW3D30_V1_1');
var complexCollection = ecoregions
.filter(ee.Filter.eq('BIOME_NAME', 'Tropical & Subtropical Moist Broadleaf Forests'));
var clippedTheRightWay = image.select('AVE')
.clipToCollection(complexCollection);
Map.addLayer(clippedTheRightWay, {}, 'clippedTheRightWay');
var reduction = clippedTheRightWay.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: ee.Geometry.Rectangle({
coords: [-179.9, -50, 179.9, 50], // Almost global.
geodesic: false
}),
scale: 30,
maxPixels: 1e12
});
print(reduction); // If this times out, export it.
Sử dụng errorMargin
khác 0
Đối với các phép toán hình học có thể tốn kém, hãy sử dụng mức chênh lệch lỗi lớn nhất có thể dựa trên độ chính xác cần thiết của phép tính. Biên độ sai số chỉ định sai số tối đa được phép (tính bằng mét) trong các phép toán trên hình học (ví dụ: trong quá trình chiếu lại). Việc chỉ định một biên độ lỗi nhỏ có thể dẫn đến việc cần phải tăng mật độ hình học (có toạ độ), điều này có thể tốn nhiều bộ nhớ. Bạn nên chỉ định biên độ sai số càng lớn càng tốt cho phép tính của mình:
var ecoregions = ee.FeatureCollection('RESOLVE/ECOREGIONS/2017');
var complexCollection = ecoregions.limit(10);
Map.centerObject(complexCollection);
Map.addLayer(complexCollection);
var expensiveOps = complexCollection.map(function(f) {
return f.buffer(10000, 200).bounds(200);
});
Map.addLayer(expensiveOps, {}, 'expensiveOps');
Không sử dụng tỷ lệ quá nhỏ với reduceToVectors()
Nếu bạn muốn chuyển đổi một ảnh đường quét thành một vectơ, hãy sử dụng tỷ lệ thích hợp. Việc chỉ định một quy mô rất nhỏ có thể làm tăng đáng kể chi phí tính toán. Đặt tỷ lệ cao nhất có thể để đạt được độ chính xác cần thiết. Ví dụ: để lấy các đa giác đại diện cho các vùng đất trên toàn cầu:
var etopo = ee.Image('NOAA/NGDC/ETOPO1');
// Approximate land boundary.
var bounds = etopo.select(0).gt(-100);
// Non-geodesic polygon.
var almostGlobal = ee.Geometry.Polygon({
coords: [[-180, -80], [180, -80], [180, 80], [-180, 80], [-180, -80]],
geodesic: false
});
Map.addLayer(almostGlobal, {}, 'almostGlobal');
var vectors = bounds.selfMask().reduceToVectors({
reducer: ee.Reducer.countEvery(),
geometry: almostGlobal,
// Set the scale to the maximum possible given
// the required precision of the computation.
scale: 50000,
});
Map.addLayer(vectors, {}, 'vectors');
Trong ví dụ trước, hãy lưu ý việc sử dụng đa giác không geodesic để sử dụng trong việc giảm toàn cục.
Không sử dụng reduceToVectors()
với reduceRegions()
Không sử dụng FeatureCollection
do reduceToVectors()
trả về làm dữ liệu đầu vào cho reduceRegions()
. Thay vào đó, hãy thêm các dải tần mà bạn muốn giảm trước khi gọi reduceToVectors()
:
var etopo = ee.Image('NOAA/NGDC/ETOPO1');
var mod11a1 = ee.ImageCollection('MODIS/006/MOD11A1');
// Approximate land boundary.
var bounds = etopo.select(0).gt(-100);
// Non-geodesic polygon.
var almostGlobal = ee.Geometry.Polygon({
coords: [[-180, -80], [180, -80], [180, 80], [-180, 80], [-180, -80]],
geodesic: false
});
var lst = mod11a1.first().select(0);
var means = bounds.selfMask().addBands(lst).reduceToVectors({
reducer: ee.Reducer.mean(),
geometry: almostGlobal,
scale: 1000,
maxPixels: 1e10
});
print(means.limit(10));
Xin lưu ý rằng có các cách khác để giảm số pixel của một hình ảnh trong các vùng của hình ảnh khác, bao gồm cả reduceConnectedCommponents() và/hoặc các phương thức giảm nhóm.
Sử dụng fastDistanceTransform()
cho các thao tác trong vùng lân cận
Đối với một số phép tích chập, fastDistanceTransform()
có thể hiệu quả hơn reduceNeighborhood()
hoặc convolve()
. Ví dụ: để làm xói mòn và/hoặc mở rộng đầu vào nhị phân:
var aw3d30 = ee.Image('JAXA/ALOS/AW3D30_V1_1');
// Make a simple binary layer from a threshold on elevation.
var mask = aw3d30.select('AVE').gt(300);
Map.setCenter(-122.0703, 37.3872, 11);
Map.addLayer(mask, {}, 'mask');
// Distance in pixel units.
var distance = mask.fastDistanceTransform().sqrt();
// Threshold on distance (three pixels) for a dilation.
var dilation = distance.lt(3);
Map.addLayer(dilation, {}, 'dilation');
// Do the reverse for an erosion.
var notDistance = mask.not().fastDistanceTransform().sqrt();
var erosion = notDistance.gt(3);
Map.addLayer(erosion, {}, 'erosion');
Sử dụng các tính năng tối ưu hoá trong reduceNeighborhood()
Nếu bạn cần thực hiện phép tích chập và không thể sử dụng fastDistanceTransform()
, hãy sử dụng các tính năng tối ưu hoá trong reduceNeighborhood()
.
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var bands = ['B4', 'B3', 'B2'];
var optimizedConvolution = composite.select(bands).reduceNeighborhood({
reducer: ee.Reducer.mean(),
kernel: ee.Kernel.square(3),
optimization: 'boxcar' // Suitable optimization for mean.
}).rename(bands);
var viz = {bands: bands, min: 0, max: 72};
Map.setCenter(-122.0703, 37.3872, 11);
Map.addLayer(composite, viz, 'composite');
Map.addLayer(optimizedConvolution, viz, 'optimizedConvolution');
Không lấy mẫu nhiều dữ liệu hơn mức cần thiết
Cố gắng không tăng kích thước tập dữ liệu huấn luyện một cách không cần thiết. Mặc dù việc tăng lượng dữ liệu huấn luyện là một chiến lược học máy hiệu quả trong một số trường hợp, nhưng điều này cũng có thể làm tăng chi phí tính toán mà không làm tăng độ chính xác tương ứng. (Để hiểu thời điểm tăng kích thước tập dữ liệu huấn luyện, hãy xem tài liệu tham khảo này). Ví dụ sau đây minh hoạ cách việc yêu cầu quá nhiều dữ liệu huấn luyện có thể dẫn đến lỗi đáng sợ "Giá trị tính toán quá lớn":
Đừng lấy mẫu quá nhiều dữ liệu!
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var labels = ee.FeatureCollection('projects/google/demo_landcover_labels');
// No! Not necessary. Don't do this:
labels = labels.map(function(f) { return f.buffer(100000, 1000); });
var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var training = composite.select(bands).sampleRegions({
collection: labels,
properties: ['landcover'],
scale: 30
});
var classifier = ee.Classifier.smileCart().train({
features: training,
classProperty: 'landcover',
inputProperties: bands
});
print(classifier.explain()); // Computed value is too large
Cách tốt hơn là bắt đầu với một lượng dữ liệu vừa phải và điều chỉnh các tham số siêu dữ liệu của bộ phân loại để xác định xem bạn có thể đạt được độ chính xác mong muốn hay không:
Điều chỉnh siêu tham số!
var l8raw = ee.ImageCollection('LANDSAT/LC08/C02/T1_RT');
var composite = ee.Algorithms.Landsat.simpleComposite(l8raw);
var labels = ee.FeatureCollection('projects/google/demo_landcover_labels');
// Increase the data a little bit, possibly introducing noise.
labels = labels.map(function(f) { return f.buffer(100, 10); });
var bands = ['B2', 'B3', 'B4', 'B5', 'B6', 'B7'];
var data = composite.select(bands).sampleRegions({
collection: labels,
properties: ['landcover'],
scale: 30
});
// Add a column of uniform random numbers called 'random'.
data = data.randomColumn();
// Partition into training and testing.
var training = data.filter(ee.Filter.lt('random', 0.5));
var testing = data.filter(ee.Filter.gte('random', 0.5));
// Tune the minLeafPopulation parameter.
var minLeafPops = ee.List.sequence(1, 10);
var accuracies = minLeafPops.map(function(p) {
var classifier = ee.Classifier.smileCart({minLeafPopulation: p})
.train({
features: training,
classProperty: 'landcover',
inputProperties: bands
});
return testing
.classify(classifier)
.errorMatrix('landcover', 'classification')
.accuracy();
});
print(ui.Chart.array.values({
array: ee.Array(accuracies),
axis: 0,
xLabels: minLeafPops
}));
Trong ví dụ này, bộ phân loại đã rất chính xác, vì vậy, bạn không cần phải điều chỉnh nhiều. Bạn nên chọn cây nhỏ nhất có thể (tức là minLeafPopulation
lớn nhất) mà vẫn có độ chính xác cần thiết.
Kết quả trung gian Export
Giả sử mục tiêu của bạn là lấy mẫu từ một hình ảnh được tính toán tương đối phức tạp. Thường thì bạn nên Export
hình ảnh toAsset()
, tải hình ảnh đã xuất rồi lấy mẫu. Ví dụ:
var image = ee.Image('UMD/hansen/global_forest_change_2018_v1_6');
var geometry = ee.Geometry.Polygon(
[[[-76.64069800085349, 5.511777325802095],
[-76.64069800085349, -20.483938229362376],
[-35.15632300085349, -20.483938229362376],
[-35.15632300085349, 5.511777325802095]]], null, false);
var testRegion = ee.Geometry.Polygon(
[[[-48.86726050085349, -3.0475996402515717],
[-48.86726050085349, -3.9248707849303295],
[-47.46101050085349, -3.9248707849303295],
[-47.46101050085349, -3.0475996402515717]]], null, false);
// Forest loss in 2016, to stratify a sample.
var loss = image.select('lossyear');
var loss16 = loss.eq(16).rename('loss16');
// Scales and masks Landsat 8 surface reflectance images.
function prepSrL8(image) {
var qaMask = image.select('QA_PIXEL').bitwiseAnd(parseInt('11111', 2)).eq(0);
var opticalBands = image.select('SR_B.').multiply(0.0000275).add(-0.2);
var thermalBands = image.select('ST_B.*').multiply(0.00341802).add(149.0);
return image.addBands(opticalBands, null, true)
.addBands(thermalBands, null, true)
.updateMask(qaMask);
}
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2')
.map(prepSrL8);
// Create two annual cloud-free composites.
var composite1 = collection.filterDate('2015-01-01', '2015-12-31').median();
var composite2 = collection.filterDate('2017-01-01', '2017-12-31').median();
// We want a strtatified sample of this stack.
var stack = composite1.addBands(composite2)
.float(); // Export the smallest size possible.
// Export the image. This block is commented because the export is complete.
/*
var link = '0b8023b0af6c1b0ac7b5be649b54db06'
var desc = 'Logistic_regression_stack_' + link;
Export.image.toAsset({
image: stack,
description: desc,
assetId: desc,
region: geometry,
scale: 30,
maxPixels: 1e10
})
*/
// Load the exported image.
var exportedStack = ee.Image(
'projects/google/Logistic_regression_stack_0b8023b0af6c1b0ac7b5be649b54db06');
// Take a very small sample first, to debug.
var testSample = exportedStack.addBands(loss16).stratifiedSample({
numPoints: 1,
classBand: 'loss16',
region: testRegion,
scale: 30,
geometries: true
});
print(testSample); // Check this in the console.
// Take a large sample.
var sample = exportedStack.addBands(loss16).stratifiedSample({
numPoints: 10000,
classBand: 'loss16',
region: geometry,
scale: 30,
});
// Export the large sample...
Trong ví dụ này, hãy lưu ý rằng hình ảnh được xuất dưới dạng float. Không xuất ở độ chính xác kép trừ phi thực sự cần thiết. Khi thực hiện thao tác xuất này, hãy lưu ý rằng đường liên kết đến Trình soạn thảo mã (có được ngay trước khi xuất) được nhúng vào tên tệp để có thể tái tạo.
Sau khi quá trình xuất hoàn tất, hãy tải lại thành phần đó và tiếp tục lấy mẫu từ thành phần đó. Xin lưu ý rằng một mẫu rất nhỏ trên một khu vực kiểm thử rất nhỏ sẽ được chạy trước, để gỡ lỗi. Khi quá trình đó thành công, hãy lấy một mẫu lớn hơn và xuất mẫu đó.
Thông thường, bạn cần xuất các mẫu lớn như vậy. Đừng mong đợi các mẫu như vậy có sẵn để tương tác (ví dụ: thông qua print()
) hoặc có thể sử dụng (ví dụ: làm dữ liệu đầu vào cho một bộ phân loại) mà không cần xuất trước.
Kết hợp so với bộ lọc ánh xạ
Giả sử bạn muốn kết hợp các bộ sưu tập dựa trên thời gian, vị trí hoặc một số thuộc tính siêu dữ liệu. Nhìn chung, việc này được thực hiện hiệu quả nhất bằng một phép nối. Ví dụ sau đây thực hiện một phép nối không gian-thời gian giữa các bộ sưu tập Landsat 8 và Sentinel-2:
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var joined = ee.Join.saveAll('landsat').apply({
primary: s2,
secondary: l8,
condition: ee.Filter.and(
ee.Filter.maxDifference({
difference: 1000 * 60 * 60 * 24, // One day in milliseconds
leftField: 'system:time_start',
rightField: 'system:time_start',
}),
ee.Filter.intersects({
leftField: '.geo',
rightField: '.geo',
})
)
});
print(joined);
Mặc dù bạn nên thử nối trước (Export
nếu cần), nhưng đôi khi filter()
trong map()
cũng có thể hiệu quả, đặc biệt là đối với các bộ sưu tập rất lớn.
var s2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED')
.filterBounds(ee.Geometry.Point([-2.0205, 48.647]));
var l8 = ee.ImageCollection('LANDSAT/LC08/C02/T1_L2');
var mappedFilter = s2.map(function(image) {
var date = image.date();
var landsat = l8
.filterBounds(image.geometry())
.filterDate(date.advance(-1, 'day'), date.advance(1, 'day'));
// Return the input image with matching scenes in a property.
return image.set({
landsat: landsat,
size: landsat.size()
});
}).filter(ee.Filter.gt('size', 0));
print(mappedFilter);
reduceRegion()
so với reduceRegions()
so với vòng lặp for
Việc gọi reduceRegions()
với FeatureCollection
rất lớn hoặc phức tạp làm dữ liệu đầu vào có thể dẫn đến lỗi đáng sợ "Giá trị tính toán quá lớn". Một giải pháp tiềm năng là liên kết reduceRegion()
trên FeatureCollection
. Một giải pháp tiềm năng khác là sử dụng vòng lặp for. Mặc dù bạn không nên thực hiện việc này trong Earth Engine như mô tả tại đây, tại đây và tại đây, nhưng bạn có thể triển khai reduceRegion()
trong một vòng lặp for để thực hiện việc giảm số lượng lớn.
Giả sử mục tiêu của bạn là lấy giá trị trung bình của các pixel (hoặc bất kỳ số liệu thống kê nào) trong mỗi đặc điểm của FeatureCollection
cho mỗi hình ảnh trong ImageCollection
.
Ví dụ sau đây so sánh 3 phương pháp được mô tả trước đó:
// Table of countries.
var countriesTable = ee.FeatureCollection("USDOS/LSIB_SIMPLE/2017");
// Time series of images.
var mod13a1 = ee.ImageCollection("MODIS/006/MOD13A1");
// MODIS vegetation indices (always use the most recent version).
var band = 'NDVI';
var imagery = mod13a1.select(band);
// Option 1: reduceRegions()
var testTable = countriesTable.limit(1); // Do this outside map()s and loops.
var data = imagery.map(function(image) {
return image.reduceRegions({
collection: testTable,
reducer: ee.Reducer.mean(),
scale: 500
}).map(function(f) {
return f.set({
time: image.date().millis(),
date: image.date().format()
});
});
}).flatten();
print(data.first());
// Option 2: mapped reduceRegion()
var data = countriesTable.map(function(feature) {
return imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
});
}).flatten();
print(data.first());
// Option 3: for-loop (WATCH OUT!)
var size = countriesTable.size();
// print(size); // 312
var countriesList = countriesTable.toList(1); // Adjust size.
var data = ee.FeatureCollection([]); // Empty table.
for (var j=0; j<1; j++) { // Adjust size.
var feature = ee.Feature(countriesList.get(j));
// Convert ImageCollection > FeatureCollection
var fc = ee.FeatureCollection(imagery.map(function(image) {
return ee.Feature(feature.geometry().centroid(100),
image.reduceRegion({
reducer: ee.Reducer.mean(),
geometry: feature.geometry(),
scale: 500
})).set({
time: image.date().millis(),
date: image.date().format()
}).copyProperties(feature);
}));
data = data.merge(fc);
}
print(data.first());
Lưu ý rằng đối tượng first()
từ mỗi bộ sưu tập sẽ được in ra, phục vụ cho mục đích gỡ lỗi. Bạn không nên mong đợi kết quả đầy đủ sẽ có sẵn một cách tương tác: bạn sẽ cần Export
. Ngoài ra, hãy lưu ý rằng bạn chỉ nên sử dụng vòng lặp for một cách thận trọng và chỉ khi không còn lựa chọn nào khác. Cuối cùng, vòng lặp for yêu cầu lấy kích thước của tập hợp đầu vào theo cách thủ công và mã hoá cứng kích thước đó ở các vị trí thích hợp. Nếu bạn không hiểu rõ bất kỳ nội dung nào trong đó, đừng sử dụng vòng lặp for.
Sử dụng phép tính vi phân về phía trước cho các giá trị lân cận theo thời gian
Giả sử bạn có một ImageCollection
được sắp xếp theo thời gian (tức là một chuỗi thời gian) và bạn muốn so sánh từng hình ảnh với hình ảnh trước (hoặc tiếp theo). Thay vì sử dụng iterate()
cho mục đích này, bạn nên sử dụng phép trừ trước dựa trên mảng để đạt được hiệu quả cao hơn. Ví dụ sau đây sử dụng phương thức này để loại bỏ nội dung trùng lặp trong tập hợp Sentinel-2, trong đó nội dung trùng lặp được xác định là hình ảnh có cùng ngày trong năm:
var sentinel2 = ee.ImageCollection('COPERNICUS/S2_HARMONIZED');
var sf = ee.Geometry.Point([-122.47555371521855, 37.76884708376152]);
var s2 = sentinel2
.filterBounds(sf)
.filterDate('2018-01-01', '2019-12-31');
var withDoys = s2.map(function(image) {
var ndvi = image.normalizedDifference(['B4', 'B8']).rename('ndvi');
var date = image.date();
var doy = date.getRelative('day', 'year');
var time = image.metadata('system:time_start');
var doyImage = ee.Image(doy)
.rename('doy')
.int();
return ndvi.addBands(doyImage).addBands(time)
.clip(image.geometry()); // Appropriate use of clip.
});
var array = withDoys.toArray();
var timeAxis = 0;
var bandAxis = 1;
var dedupe = function(array) {
var time = array.arraySlice(bandAxis, -1);
var sorted = array.arraySort(time);
var doy = sorted.arraySlice(bandAxis, -2, -1);
var left = doy.arraySlice(timeAxis, 1);
var right = doy.arraySlice(timeAxis, 0, -1);
var mask = ee.Image(ee.Array([[1]]))
.arrayCat(left.neq(right), timeAxis);
return array.arrayMask(mask);
};
var deduped = dedupe(array);
// Inspect these outputs to confirm that duplicates have been removed.
print(array.reduceRegion('first', sf, 10));
print(deduped.reduceRegion('first', sf, 10));
Kiểm tra các bộ sưu tập đã in để xác minh rằng các bản sao đã bị xoá.