「我把 seed 設成 17 了。」這句話仍然沒有告訴同事,你用哪一份資料、哪些訓練列、哪支程式與哪個環境。他拿到同名的 data.csv,跑出另一個分數,也許根本不是隨機性造成的。
可重現性的第一步,是讓一個結果有可以追溯的來路。資料、配方、切分與程式要能找回;輸出的數字與模型也要能對上那次執行。否則兩個看似相近的分數,可能來自不同測試集,連比較的前提都不相同。
本篇先用 24 列人工資料,把這些差別做成可執行的例子。你會看到同 seed 改資料、同資料改排列與改 split seed 分別留下什麼痕跡,再理解實驗追蹤與資料版本工具可以接住哪一部分。
你說的「同一個實驗」,到底要同到哪裡?
考慮一次簡單的線性回歸。你固定 seed,用資料的一部分估斜率與截距,再在剩下的資料算 MSE。下一次重做時,至少有六種東西可能變:
重現也有不同層次。檔案 bytes 一樣、同環境得到完全相同結果、數值在容許誤差內一致,和換一批資料仍支持同一個研究結論,是不同的要求。本文實際檢查前面幾個具體環節,不把一次 deterministic 小算例當成整個研究結論已可重現。
先看這些紀錄在工具裡會長什麼樣。MLflow 把一次執行稱作 run,讓使用者從名稱或 ID 找到該次結果。下面來源圖只有一筆 run,作用是辨識一份紀錄,不是已展示多次實驗比較。
接下來先不依賴工具,用一個能算清楚的小實驗,決定每個 run 究竟該留下什麼。
檔名、raw hash 與內容正規化,為什麼要分開?
檔名是方便找東西的位置。Hash 則可以比較拿到的 bytes 是否跟可信紀錄一致:本例用 SHA256,將原 CSV 的全部 bytes 算成一個指紋。
只要其中一列的 target 改了,即使檔名還叫 data.csv,raw 指紋也會不同。可是反過來說,raw 不同不一定代表統計內容不同。把同一份 CSV 的所有列倒序,檔案 bytes 已不同;若後續處理本來就先依 ID 排序,模型可能仍看到相同資料與相同切分。
因此本例另外保存「依明訂規則解讀後」的指紋:把 id 讀成整數、x/y 讀成有限浮點,拒絕重複 ID,再按唯一 ID 排序,最後以固定 key 順序與緊湊 JSON 編碼。這個 normalized rows hash 只回答這份 schema 和規則下的內容是否相同,不是普遍的語意相等判定。
例如換一種小數文字寫法,浮點解析可能得到同值;換成另一種單位,數字或許也很接近,卻已不是相同現象。規則必須先清楚,不能看兩個 hash 一樣就替資料語意背書。
同樣地,hash 的基準得可信。若原檔與紀錄都被一起換掉,單純比較兩者不會自動揭穿來源;它不是驗簽、資料品質證書或惡意程式安全證明。
一個 24 列實驗:把切分與計算配方一起留下來
本例的人工資料有 id=0 到 23:
x_i = i − 12
y_i = 2.5 x_i + 4 + 0.2 × (((7i) mod 5) − 2)
程式先按 ID 排序,再以 seed 17 shuffle ID。前六個作 test,剩下十八個作 train,並保存實際列 ID。只在 train 用含截距的一維最小平方法估參數:
slope = Σ_train (x−mean_x)(y−mean_y) / Σ_train (x−mean_x)²
intercept = mean_y − slope × mean_x
seed 只控制這份明確演算法的一個隨機步驟。保存 train/test IDs,才能直接檢查哪些列真正參與 fit,而不必靠回憶當時資料有沒有先排序。
本次實際 test IDs 是 1、2、10、12、19、22;其餘十八列 fit 得到:
這是人工配方的單次 holdout。它沒有調參,也不代表真實問題的品質。這裡刻意把模型算得很小,讓注意力放在結果與輸入如何對應。
程式最後產出三個檔案:data.csv 保留原始資料;result.json 保存模型參數、每筆 test 預測與 MSE;receipt.json 記資料與程式指紋、配方、切分列、部分環境,以及結果的指紋。
receipt 不是把所有資料塞進一個 JSON,而是一份尋找與比對的線索。程式指紋也只是此單檔原始碼的 bytes,不會自動涵蓋外部檔案、整個 Git repository 或它沒有記錄的依賴。
三個受控變動,找出「看起來一樣」藏著的差別
程式真的在同一次執行裡重做相同輸入,再測三個變動,沒有重新抽新資料來湊結論。
| 變動 | 指紋/結果發生什麼事 | 本例三項 checker 的反應 |
|---|---|---|
| 一列已知 train 的 target 加 1 | raw 與 normalized 內容改變,模型結果也改變 | 拒絕 raw 不一致 |
| 所有列倒序 | raw 改變;按 ID 排序後的內容、切分與結果相同 | 仍拒絕 raw 不一致 |
| 只把 split seed 17 改 18 | 這次檢查直接指出 seed 與舊紀錄不同 | 拒絕 seed 不一致 |
第一種變動改的是 ID 0,原本已在 train。相同 seed 並沒有把模型鎖住;資料改了,模型仍會改。
第二種最容易誤讀。Checker 的拒絕是「你沒有拿到完全相同的原 bytes」,不等於它證明統計結果一定不同。程式另外驗證,正規化後的 rows 與結果完全一致;這恰好說明 bytes 身分與本例處理後的內容身分是兩件事。
相同輸入與環境重做兩次,程式將 receipt 與 result 轉成 canonical JSON,兩次 bytes 相同。Canonical 在這裡明訂為 key 排序、無多餘空白、禁止 NaN;保存到檔案時再加一個換行。這項證據只限本次執行與這份流程,不能推成跨平台或任意演算法的 deterministic 保證。
存下來,不等於每一項都驗過
這個範例裡最重要的界線,藏在 check_inputs 函式。它實際只比三項:
目前 raw 資料的 SHA,是否等於 receipt 記錄?
目前這支程式的 SHA,是否等於 receipt 記錄?
傳入的 split seed,是否等於 receipt 記錄?
它沒有驗整份 receipt,也沒有讀 result.json 重新核對結果。train/test IDs、環境與 result hash 是保存供追溯與另行比較的紀錄,不是這個函式自動保證全部正確的欄位。
獨立原型審查實際測過反例:只把 receipt 裡的 train_ids 換成空陣列,或把 result_sha256 改成別的字串,這個三項 checker 仍接受。這符合它的程式範圍,也指出它不能被叫做完整的實驗紀錄驗證器。
如果你的任務要求驗證保存的結果,就需要真的重新讀結果檔、按同規則算 hash 再比較;若要求重建訓練,也要檢查完整配方、切分、環境與依賴。要求來源不可否認或避免惡意竄改,還會牽涉信任與驗簽。不要只多存幾個欄位,就把尚未實作的檢查算成已完成。
MLflow 與 DVC 接住哪一部分?
當實驗變多,手動收集結果與檔案會越來越難。MLflow Tracking 以 run 組織一次執行的 parameters、metrics 與 artifacts,experiment 再把多個 run 放在同一個任務脈絡裡。這讓你有地方找回某次設定與產物,但紀錄什麼仍然需要設計,分數對應哪份資料也不能省略。
指標不能只留一個 value,還要知道它算在哪份資料、哪個階段。畫面列名的 training 就提醒我們,訓練成績和保留測試的任務不同。本文自己的 .14 則來自六筆 test MSE,模型、資料與指標都不同,不能跟 .975 排成一個好壞比較。
模型產物與 run 紀錄需要能彼此對上。若只保留分數,拿不到當時的模型就無法交接;若只留下模型檔,卻不知哪份資料與配方產生它,也難以解釋這個產物。保存與登錄是可區分的步驟,不因為畫面上有模型名稱就推定它已經完成發布流程。
DVC 的 .dvc 文件 則提供追蹤資料檔案的 metadata,可跟 Git 一起保存,連到實際資料內容。這裡的用意是分清小型版本紀錄與資料本體,而不是把大型原始資料全塞進程式碼提交。
除了追蹤資料,DVC 也能在 dvc.yaml 宣告 stage 的命令、輸入依賴與輸出。當一個 stage 的輸出成為另一個 stage 的輸入,它們就有先後依賴;執行關係來自這些連結,不是 YAML 列出的上下順序。
例如資料處理結果變了,訓練與評估哪些部分需要重做,要看真實的檔案/參數依賴。若程式偷讀了沒有宣告的外部檔案,圖也不會神奇知道。因此這類工具需要正確的依賴定義,和「紀錄已存下來」一樣,都不能取代對流程本身的理解。
兩者都不能替你決定「這次比較是否公平」。即使每個 run 都完整保存,若測試集已被反覆看答案選設定,追蹤工具只會更清楚地保存這個過程,不會自動修正它。
這裡只介紹跟本篇任務有關的基本角色;引用的是目前官方概念頁,不將頁面上的較新產品功能當成 2023 年以前的歷史敘述。本例沒有安裝或執行 MLflow、DVC、remote 或 tracking server,也沒有把下面的 receipt 當作這些工具的等價實作。
完整可執行程式:先把一份紀錄做對
程式只使用 Python 標準函式庫。把完整內容存成 versioned_experiment.py,在同目錄執行:
python3 versioned_experiment.py > stdout.json
本次環境為 CPython 3.10.12、Linux、x86_64。成功時 exit 0、stdout 可解析成 JSON,三種預期拒絕都列在 rejected_inputs,並在本例 artifacts 目錄重建 data.csv、receipt.json、result.json。這不是保留歷次執行的完整 registry;再次執行會覆寫這三個本例檔案,需要保留舊 run 時應另存各次紀錄。
輸出的環境資訊只有 Python 版本、implementation、system 與 machine,沒有完整 OS、套件或 container lock。標準庫小例能把來路說清楚,不能代替更複雜模型需要的依賴鎖定與數值容許誤差設計。
"""Original stdlib-only local provenance exercise, not an MLflow/DVC implementation."""
import csv
import hashlib
import io
import json
import math
import platform
import random
from pathlib import Path
BASE = Path(__file__).resolve().parent
def digest(data):
return hashlib.sha256(data).hexdigest()
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
def csv_bytes(rows):
stream = io.StringIO(newline="")
writer = csv.writer(stream, lineterminator="\n")
writer.writerow(["id", "x", "y"])
writer.writerows(rows)
return stream.getvalue().encode()
def parse(raw):
records = list(csv.DictReader(io.StringIO(raw.decode())))
rows = [{"id": int(r["id"]), "x": float(r["x"]), "y": float(r["y"])} for r in records]
if len({r["id"] for r in rows}) != len(rows):
raise ValueError("Duplicate row ID")
if not all(math.isfinite(r[k]) for r in rows for k in ("x", "y")):
raise ValueError("Nonfinite data")
return sorted(rows, key=lambda r: r["id"])
def experiment(raw, seed=17):
rows = parse(raw)
shuffled_ids = [r["id"] for r in rows]
random.Random(seed).shuffle(shuffled_ids)
test_ids = sorted(shuffled_ids[:6])
train_ids = sorted(shuffled_ids[6:])
train = [r for r in rows if r["id"] in train_ids]
test = [r for r in rows if r["id"] in test_ids]
# Closed-form univariate OLS with intercept, fit on training rows only.
mean_x = math.fsum(r["x"] for r in train) / len(train)
mean_y = math.fsum(r["y"] for r in train) / len(train)
denominator = math.fsum((r["x"] - mean_x)**2 for r in train)
if denominator == 0:
raise ValueError("Training x has zero variance")
slope = math.fsum((r["x"] - mean_x) * (r["y"] - mean_y) for r in train) / denominator
intercept = mean_y - slope * mean_x
predictions = [{"id": r["id"], "prediction": slope * r["x"] + intercept} for r in test]
mse = math.fsum((p["prediction"] - r["y"])**2 for p, r in zip(predictions, test)) / len(test)
result = {"model": {"slope": slope, "intercept": intercept},
"test_predictions": predictions, "test_mse": mse}
receipt = {"raw_data_sha256": digest(raw), "normalized_rows_sha256": digest(canonical(rows)),
"code_sha256": digest(Path(__file__).read_bytes()),
"recipe": {"model": "univariate OLS with intercept; train-only closed form",
"split_seed": seed, "test_count": 6, "row_policy": "sort unique integer IDs before shuffle"},
"train_ids": train_ids, "test_ids": test_ids,
"environment": {"python": platform.python_version(), "implementation": platform.python_implementation(),
"platform": platform.system(), "machine": platform.machine()},
"result_sha256": digest(canonical(result))}
return receipt, result
def check_inputs(raw, receipt, seed):
checks = {"raw_data": digest(raw) == receipt["raw_data_sha256"],
"code": digest(Path(__file__).read_bytes()) == receipt["code_sha256"],
"split_seed": seed == receipt["recipe"]["split_seed"]}
# An integrity check against a trusted saved receipt, not proof of authorship/security.
if not all(checks.values()):
raise ValueError("Input mismatch: " + ",".join(k for k, same in checks.items() if not same))
def main():
rows = [(i, i - 12, 2.5 * (i - 12) + 4 + ((i * 7) % 5 - 2) * .2) for i in range(24)]
raw = csv_bytes(rows)
receipt, result = experiment(raw)
check_inputs(raw, receipt, 17)
replay_receipt, replay_result = experiment(raw)
assert canonical(receipt) == canonical(replay_receipt)
assert canonical(result) == canonical(replay_result)
reordered = csv_bytes(list(reversed(rows)))
reorder_receipt, reorder_result = experiment(reordered)
assert reorder_receipt["raw_data_sha256"] != receipt["raw_data_sha256"]
assert reorder_receipt["normalized_rows_sha256"] == receipt["normalized_rows_sha256"]
assert canonical(reorder_result) == canonical(result)
# Change one known training target; same filename and seed would hide this without a receipt.
changed_id = receipt["train_ids"][0]
changed_rows = [(i, x, y + 1 if i == changed_id else y) for i, x, y in rows]
changed = csv_bytes(changed_rows)
_, changed_result = experiment(changed)
assert canonical(changed_result) != canonical(result)
mismatches = []
for name, candidate, seed in [("changed_training_target", changed, 17),
("same_rows_reordered", reordered, 17),
("changed_split_seed", raw, 18)]:
try:
check_inputs(candidate, receipt, seed)
except ValueError as error:
mismatches.append({"case": name, "rejected": str(error)})
else:
raise AssertionError("Expected input mismatch")
out = BASE / "artifacts"
out.mkdir(exist_ok=True)
(out / "data.csv").write_bytes(raw)
(out / "receipt.json").write_bytes(canonical(receipt) + b"\n")
(out / "result.json").write_bytes(canonical(result) + b"\n")
print(json.dumps({"scope": "Original 24-row synthetic local OLS provenance exercise; one existing environment",
"receipt": receipt, "result": result,
"checks": {"same_environment_replay_receipt_and_result_byte_exact": True,
"reordered_raw_bytes_differ": True, "reordered_normalized_rows_and_result_same": True,
"changed_training_target_id": changed_id, "changed_target_changes_result": True},
"rejected_inputs": mismatches,
"limits": ["Hashes check bytes against a trusted receipt, not source trust or data quality.",
"Normalization is this explicit schema/ID policy, not a universal semantic equivalence test.",
"Environment metadata is partial, not an OS/library/container lock or cross-platform guarantee.",
"A seed alone does not identify data, code, split IDs, environment or numeric behavior.",
"Toy held-out MSE is not production quality, fairness or leakage assurance.",
"No MLflow server, DVC remote, cloud registry or external service was installed or tested."]},
indent=2, allow_nan=False))
if __name__ == "__main__":
main()
一份可用的實驗紀錄,應讓接手的人知道先找哪個資料版本、哪段程式、哪組切分,再對哪個結果。遇到差異時,也能指出是 bytes、處理規則、配方還是執行環境先變了。把這些問題拆開,才有辦法從「我記得有跑過」走到「我們能檢查這次究竟跑了什麼」。
來源與延伸閱讀:
-
MLflow Tracking 官方概念:本篇採 run、experiment、parameter/metric/artifact 的基本角色,未實作其服務或當前新增功能。
-
DVC .dvc 文件規格:資料追蹤 metadata 與 Git 的關係;本篇未執行 DVC 指令。
-
圖 1–3:MLflow Tracking Quickstart,固定來源 repository 與原 Apache-2.0 LICENSE.txt。來源的分類器與 autologging 只作讀圖脈絡,本文未安裝或執行該範例。
-
圖 4:DVC dag 官方文件、固定 dag 原文、Defining Pipelines;固定 README 授權聲明 說明文件/圖像採 CC BY 4.0。本文未執行 DVC 或把官方既有 ASCII 文字圖冒充新實測。
圖像來源、改作與完整授權副本:
DS70 image sources and modifications
Figures 1–3 and cover: MLflow project contributors. Source: MLflow Tracking Quickstart, https://mlflow.org/docs/latest/ml/getting-started/quickstart/ . Pinned repository: https://github.com/mlflow/mlflow/tree/c7027358d33437df5b9bea99058ea7b0ef884bde . Original files: quickstart-ui-run-list.png, quickstart-our-run.png, quickstart-ui-logged-models.png under docs/static/images/tutorials/introductory/quickstart-tracking/. Repository license: Apache-2.0; full original LICENSE.txt follows, including the Databricks copyright notice. The selected original screenshots are copied without changes; the three mobile views crop original pixel rectangles without retyping or rearranging labels/data. The cover fits the full run-list screenshot proportionally on a white canvas. These are the source tutorial's MLflow 3.5.0 UI examples and training records, not this project's OLS results or a historical 2023 UI. The source run detail shows training metrics, not this project's test scores. Product names/logos are used descriptively; no endorsement is implied.
Figure 4: DVC documentation project/contributors. Source: https://doc.dvc.org/command-reference/dag . Pinned source and grant: https://github.com/treeverse/dvc.org/blob/21fabba8d66bf81e3cfcbcb9e061d4a5363399ad/content/docs/command-reference/dag.md and https://github.com/treeverse/dvc.org/blob/21fabba8d66bf81e3cfcbcb9e061d4a5363399ad/README.md#copyright . Documentation and images are licensed CC BY 4.0, https://creativecommons.org/licenses/by/4.0/ , except where otherwise noted. This is a browser screenshot of the official documentation's existing dvc dag command/output block; no output, label, edge or value was retyped. It is not a separate upstream bitmap, a local DVC execution, or the graph of this project's original OLS script. No additional diagram content was drawn.
MLflow original Apache-2.0 license (verbatim)
Copyright 2018 Databricks, Inc. All rights reserved.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
留言0
登入後就能留言。
登入