第82章 復現包
江臨把那張寫著【下一執行目標】的audit_log關掉,新建了一個空白的Python文件。
minimal_leak_demo.py
距離對方要求的24小時窗口期,還剩二十三個小時四十一分。
時間夠用,但他沒有任何懈怠的意思。
構造一個最小復現實驗,聽起來簡單,做起來卻是一樁挺講究分寸的活。
第一,不能用平台提供的那3.4GB原始數據集的任何一個字節。
否則對方可以反咬一口,說他是利用了數據集本身的某種特殊結構在斷章取義。
第二,不能太複雜。
如果腳本寫得花里胡哨,依賴了一堆奇形怪狀的庫,對方的工程師在覆核時光是配環境就要折騰半天,覆核流程一拖,事情就容易被冷處理。
第三,必須鎖死隨機種子。
任何一個浮點數的微小波動,都可能被對方技術組歸因於環境差異,進而拖慢覆核流程。
江臨盯著空白的代碼窗口,思考了兩分鐘,然後開始動手,用numpy生成了一段由兩個不同分布拼接而成的隨機時間序列。
前八十個數據點,服從均值為零、標準差為一的正態分布,用來模擬訓練窗口裡平穩的市場行情。
後二十個數據點,被他人為地注入了幾個三倍標準差以上的極端值,用來模擬測試窗口裡突然出現的異常事件。
整個數據構造過程,不超過十行代碼。
隨機種子被鎖定為42。
為了避免隨機數生成器版本差異,他乾脆刪除了隨機生成過程,直接把一組最小合成序列硬編碼進腳本。
數字沒有任何金融含義,只用於證明一個數學事實。
測試窗參與標準化,會系統性壓低測試窗內極端值的異常分數。
數據準備好後,他在下方寫了兩個對照函數。
第一個函數叫leaked_scaler,完全照搬Baseline第六十七行的邏輯。
把訓練窗和測試窗合併,計算全局均值和全局標準差,然後做標準化。
第二個函數叫causal_scaler,嚴格遵守時間因果律。
只用訓練窗的均值和標準差,去標準化測試窗。
然後,他讓兩個函數分別對同一份合成數據進行處理,把標準化之後的異常分數列印出來。
最後再加一行matplotlib畫圖代碼,把兩組結果疊在同一張圖上。
代碼總長不到四十行。
江臨點擊運行。
全網首發更新 TW 看書網體驗佳,𝔰𝔥𝔲.𝔱𝔴超讚
不到一秒鐘,屏幕上跳出了那張對照圖。
跟之前用真實數據跑出來的結果幾乎一模一樣。
leaked_scaler處理後的異常分數曲線,比causal_scaler矮了將近一截。
那些本該刺破天際的尖峰,被未來信息悄悄地按了下去。
乾淨,純粹,無可辯駁。
江臨把這張對照圖截下來,存進文件夾。
接著,他做了一件更狠的事情。
給這個最小復現實驗,寫了一份像一份實驗室設備校準記錄的README說明文檔。
文檔第一節,是腳本的目的陳述。
「本腳本旨在使用純合成數據,復現真實數據集中觀測到的預處理階段未來信息泄漏現象。本腳本完全不依賴主辦方提供的任何原始數據,所使用的所有隨機數均由鎖定種子42生成,理論上可在任意支持numpy1.24以上版本的Python環境中得到完全一致的輸出。「
文檔第二節,是逐行代碼注釋。
文檔第三節,是預期輸出的精確數值。
江臨把兩組異常分數序列、每一個時間點的具體數值,都以表格形式列印在了README里。
任何人只要運行一次腳本,就能用diff命令逐字節比對自己的輸出和他的輸出是否一致。
文檔第四節,是數學推導。
他用三頁紙的篇幅,從最基礎的標準化公式出發,一步一步推導了當測試集樣本參與計算時,為什麼會導致測試窗內的極端值在標準化後被系統性低估。
每一個推導步驟都有編號,每一個公式都有引用來源。
文檔第五節,是工程建議。
他冷靜地列出了三種正確的預處理方案,並標註了各自的適用場景和計算開銷。
最後一節,是免責聲明。
「本實驗的所有代碼、數據和推導過程,均不涉及主辦方提供的任何脫敏數據集的內部信息。本實驗旨在通過最小化的方式獨立驗證一個數學事實,與主辦方平台的具體實現細節無關。「
這份README一旦發出去,對方就徹底失去了所有可以模糊處理的空間。
他用的是合成數據,對方沒辦法說他在惡意攻擊平台。
他鎖了種子還附了精確數值,對方沒辦法說他不可復現。
他給了三頁的數學推導,對方沒辦法說他沒有理論依據。
他給出的是建議,不是指責,對方沒辦法說他在指手畫腳。
這就像在物理實驗裡搭出一個最小閉環的對照裝置。
輸入固定,處理流程固定,輸出差異固定。
你可以不喜歡結論,但不能說它沒有發生。
江臨掃了一眼右下角的時間,把整個文件夾打包成一個命名為reproduce_leakage_v1.0.zip的文件。
然後切回瀏覽器,打開那封站內信,點擊補充材料上傳按鈕。
頁面提示:補充材料已提交,等待評審組覆核。
江臨關掉瀏覽器,從椅子上站起來,走到窗邊把窗簾拉開了一道縫。
外面的陽光刺眼得讓他眯起了眼睛。
他這才意識到,自己已經連續在屋子裡待了將近五天。
轉身去洗了個澡。
熱水沖在後頸上的那一刻,他腦子裡那根緊繃了四天的弦,終於稍微鬆開了一點。
用毛巾擦頭髮的時候,他聽到客廳里母親在跟父親低聲說著什麼。
「都關在屋裡五天了,連飯都是我端進去的。」
「年輕人有事情忙是好事,別去打擾他。」
「我就是擔心。」
「孩子能吃能喝的,狀態好得很,你少瞎操心。」
江臨擦頭髮的動作停了一下。
他想了想,把毛巾搭在脖子上,推門走了出去。
「媽,下午我想出去走走。」
母親從沙發上回頭看他,臉上瞬間堆起笑容:「哎,去吧去吧,媽給你轉點零花錢。」
……
與此同時,上海,浦東某寫字樓的二十三層。
平台技術組的內部會議室里坐著六個人,沒有人大聲說話。
單越是這種聲音越低的時候,事情反而越重。
長條會議桌的主位坐著的是一個四十多歲,戴著金絲眼鏡的男人,是這次挑戰賽的技術負責人,姓沈。
他的筆記本電腦屏幕上,正顯示著一份PDF的第十八頁。
頁眉的位置印著兩行小字。
「《分鐘級行情數據異常檢測與回測前置審計報告》」
「提交者:1453號。」
沈工的右手食指無意識地敲著桌面,節奏越來越快。
「這個1453號,背景查到了嗎?」
坐在他左手邊的一個戴著耳機的年輕工程師立刻搖頭:「註冊信息只有一個手機號和一個郵箱,我們只能看到註冊時主動留的手機號歸屬地和郵箱域名,更深的身份核查不能做,用戶協議卡得很死。」
「你們說,對方會是什麼身份,某個高校的研究生?」沈工皺了皺眉。
「不像。」另一個年紀稍長的女工程師搖頭,「研究生寫報告習慣用LaTeX,遣詞造句也有學術八股的味道。但這份報告的寫法非常……」
她猶豫了一下,找了個合適的形容詞。
「非常工程化。」
「工程化?」
「就是那種在一線幹過活的人才寫得出來的語氣。你看他第二頁畫的那張污染鏈路拓撲圖,他根本不是從金融的角度在看這份數據,而是從測量系統的角度在看的。」
「他可能根本不關心市場,只關心從交易所到平台之間的每一層數據傳輸環節,哪一層最有可能掉包、漂移、錯位。」
女工程師翻到報告的第八頁,把屏幕轉向沈工。
「真正麻煩的不是他指出了橫向缺失。橫向缺失只要畫熱力圖,細心一點的人都能看見。麻煩的是,他把這些缺失帶、價格尺度跳變和source_flag的分組變化放在了一張圖里。」
沈工低頭看屏幕。
報告第八頁上有一張三層對齊圖。
最上面是按 minute_id展開的缺失熱力圖,中間是若干個脫敏標的在相鄰date_id邊界處的價格尺度變化。
最下面是一條被離散化後的source_flag分組序列。
三條圖之間,被江臨用細灰線拉出了幾組對應關係。
在圖下方,他只寫了一句很冷的判斷。
【source_flag的真實業務含義未知,本文不對其作確定解釋。但該欄位分組變化與若干橫向缺失帶、尺度突變邊界存在統計相關,建議主辦方按source_flag分層覆核底層數據源、清洗批次或供應商接口切換記錄。】
會議室里安靜了幾秒。
年輕工程師低聲說:「他沒有說source_flag是供應商編號。」
女工程師點頭:「對,他沒有越界,只是證明了這個欄位不是無害欄位,和異常分布有關係。」
沈工的臉色沉了下去。
因為他們自己知道,source_flag背後確實不是普通質量標籤。
它對應的是兩家底層行情供應商和一次內部清洗批次切換後的混合標記。
這個欄位本來不該暴露得這麼完整。
更糟的是,1453號並不是靠欄位名猜中的。
他是從數據斷層、尺度跳變和分組同步變化里,把這條鏈路反推了出來。
「也就是說,他不知道我們的底層供應商是誰,但他已經知道這裡發生過數據源切換。」沈工沉吟道。
女工程師說:「對,而且他很聰明,報告裡沒有把話說死,只建議我們分層覆核。」
這比直接指責更難處理。
對方沒有情緒,沒有誇張結論,甚至沒有一句越界的話。
只是把證據擺在那裡,然後把下一步該查什麼,清清楚楚地寫給了他們。
沈工思考了將近一分鐘,然後抬頭去看桌對面那個一直沒說話的法務。
「老周,這次公開榜單前十里,至少有幾份高分提交說明明顯沿用了 baseline的全局標準化流程。他這份E類章節,要是真發到外網上?」
那個被叫做老周的中年男人慢條斯理地推了推眼鏡。
「如果他沒有惡意公開的意圖,那就是一份非常嚴謹的內部審計建議,我們應該感謝他。但如果他選擇把這份報告掛到知乎、雪球、或者任何一個量化論壇,那就是一次嚴重的行業級事故。」
「問題不在於baseline本身。」女工程師說,「問題是我們前幾期給客戶演示的數據清洗模板,也是從這套notebook改出去的。參賽者可以不用,但客戶會看。只要他把這段對照實驗發出去,外界不會區分示例代碼和平台數據能力。」
老周說到這裡看了一眼沈工,解釋說:「所以,我們這一期挑戰賽的所有公開榜單成績,從法律和工程角度都會失去公信力。前幾期挑戰賽因為也用了類似的Baseline,會被一併質疑。已經簽了商業合作的幾個機構客戶,會立刻發函要求我們做技術核查。」
「最壞的情況是,我們整個平台技術組,未來三年內的口碑和業務都會受到不可逆的損害。」
就在這時,戴耳機的年輕工程師面前的電腦屏幕上突然彈出一個新的郵件提示。
他點開看了一眼,瞳孔猛地縮了一下。
「沈工,1453號剛剛提交了補充材料,叫reproduce_leakage_v1.0.zip。「
沈工的呼吸停頓了一瞬,立即說道:「打開,投到大屏上。「
年輕工程師將那個不到一百KB的壓縮包掃了一遍,然後下載解壓,把README的內容投到了會議室的大屏幕上。
六個人同時屏住了呼吸,目光在屏幕上由上至下掃過。
整整兩分鐘,沒有人說話。
最後是那位女工程師輕輕吐出一口氣:「他連我們想找藉口的餘地都沒留。」
沈工盯著屏幕上那張精確到小數點後十六位的預期輸出表格,緩緩地往後靠在了椅背上。
良久,他才開口:「通知評審組,最終評分先暫停自動出榜,等技術覆核結果。還有,聯繫1453號,我要跟他談談。」
……
江臨從外面回來的時候,已經是傍晚六點多。
晚飯桌上,母親難得沒念叨他這幾天悶在屋裡的事,反倒一個勁地往他碗裡夾菜。
吃完飯,他回到房間,習慣性地晃了晃滑鼠。
屏幕亮起。
賽事網頁右上角的通知小鈴鐺,又顯示了一條新的站內信。
「關於邀請您參與平台數據審計專項的初步溝通函。」
發件人依然帶著藍色V字認證:平台技術組管理員-DataOps_03。
minimal_leak_demo.py
距離對方要求的24小時窗口期,還剩二十三個小時四十一分。
時間夠用,但他沒有任何懈怠的意思。
構造一個最小復現實驗,聽起來簡單,做起來卻是一樁挺講究分寸的活。
第一,不能用平台提供的那3.4GB原始數據集的任何一個字節。
否則對方可以反咬一口,說他是利用了數據集本身的某種特殊結構在斷章取義。
第二,不能太複雜。
如果腳本寫得花里胡哨,依賴了一堆奇形怪狀的庫,對方的工程師在覆核時光是配環境就要折騰半天,覆核流程一拖,事情就容易被冷處理。
第三,必須鎖死隨機種子。
任何一個浮點數的微小波動,都可能被對方技術組歸因於環境差異,進而拖慢覆核流程。
江臨盯著空白的代碼窗口,思考了兩分鐘,然後開始動手,用numpy生成了一段由兩個不同分布拼接而成的隨機時間序列。
前八十個數據點,服從均值為零、標準差為一的正態分布,用來模擬訓練窗口裡平穩的市場行情。
後二十個數據點,被他人為地注入了幾個三倍標準差以上的極端值,用來模擬測試窗口裡突然出現的異常事件。
整個數據構造過程,不超過十行代碼。
隨機種子被鎖定為42。
為了避免隨機數生成器版本差異,他乾脆刪除了隨機生成過程,直接把一組最小合成序列硬編碼進腳本。
數字沒有任何金融含義,只用於證明一個數學事實。
測試窗參與標準化,會系統性壓低測試窗內極端值的異常分數。
數據準備好後,他在下方寫了兩個對照函數。
第一個函數叫leaked_scaler,完全照搬Baseline第六十七行的邏輯。
把訓練窗和測試窗合併,計算全局均值和全局標準差,然後做標準化。
第二個函數叫causal_scaler,嚴格遵守時間因果律。
只用訓練窗的均值和標準差,去標準化測試窗。
然後,他讓兩個函數分別對同一份合成數據進行處理,把標準化之後的異常分數列印出來。
最後再加一行matplotlib畫圖代碼,把兩組結果疊在同一張圖上。
代碼總長不到四十行。
江臨點擊運行。
全網首發更新 TW 看書網體驗佳,𝔰𝔥𝔲.𝔱𝔴超讚
不到一秒鐘,屏幕上跳出了那張對照圖。
跟之前用真實數據跑出來的結果幾乎一模一樣。
leaked_scaler處理後的異常分數曲線,比causal_scaler矮了將近一截。
那些本該刺破天際的尖峰,被未來信息悄悄地按了下去。
乾淨,純粹,無可辯駁。
江臨把這張對照圖截下來,存進文件夾。
接著,他做了一件更狠的事情。
給這個最小復現實驗,寫了一份像一份實驗室設備校準記錄的README說明文檔。
文檔第一節,是腳本的目的陳述。
「本腳本旨在使用純合成數據,復現真實數據集中觀測到的預處理階段未來信息泄漏現象。本腳本完全不依賴主辦方提供的任何原始數據,所使用的所有隨機數均由鎖定種子42生成,理論上可在任意支持numpy1.24以上版本的Python環境中得到完全一致的輸出。「
文檔第二節,是逐行代碼注釋。
文檔第三節,是預期輸出的精確數值。
江臨把兩組異常分數序列、每一個時間點的具體數值,都以表格形式列印在了README里。
任何人只要運行一次腳本,就能用diff命令逐字節比對自己的輸出和他的輸出是否一致。
文檔第四節,是數學推導。
他用三頁紙的篇幅,從最基礎的標準化公式出發,一步一步推導了當測試集樣本參與計算時,為什麼會導致測試窗內的極端值在標準化後被系統性低估。
每一個推導步驟都有編號,每一個公式都有引用來源。
文檔第五節,是工程建議。
他冷靜地列出了三種正確的預處理方案,並標註了各自的適用場景和計算開銷。
最後一節,是免責聲明。
「本實驗的所有代碼、數據和推導過程,均不涉及主辦方提供的任何脫敏數據集的內部信息。本實驗旨在通過最小化的方式獨立驗證一個數學事實,與主辦方平台的具體實現細節無關。「
這份README一旦發出去,對方就徹底失去了所有可以模糊處理的空間。
他用的是合成數據,對方沒辦法說他在惡意攻擊平台。
他鎖了種子還附了精確數值,對方沒辦法說他不可復現。
他給了三頁的數學推導,對方沒辦法說他沒有理論依據。
他給出的是建議,不是指責,對方沒辦法說他在指手畫腳。
這就像在物理實驗裡搭出一個最小閉環的對照裝置。
輸入固定,處理流程固定,輸出差異固定。
你可以不喜歡結論,但不能說它沒有發生。
江臨掃了一眼右下角的時間,把整個文件夾打包成一個命名為reproduce_leakage_v1.0.zip的文件。
然後切回瀏覽器,打開那封站內信,點擊補充材料上傳按鈕。
頁面提示:補充材料已提交,等待評審組覆核。
江臨關掉瀏覽器,從椅子上站起來,走到窗邊把窗簾拉開了一道縫。
外面的陽光刺眼得讓他眯起了眼睛。
他這才意識到,自己已經連續在屋子裡待了將近五天。
轉身去洗了個澡。
熱水沖在後頸上的那一刻,他腦子裡那根緊繃了四天的弦,終於稍微鬆開了一點。
用毛巾擦頭髮的時候,他聽到客廳里母親在跟父親低聲說著什麼。
「都關在屋裡五天了,連飯都是我端進去的。」
「年輕人有事情忙是好事,別去打擾他。」
「我就是擔心。」
「孩子能吃能喝的,狀態好得很,你少瞎操心。」
江臨擦頭髮的動作停了一下。
他想了想,把毛巾搭在脖子上,推門走了出去。
「媽,下午我想出去走走。」
母親從沙發上回頭看他,臉上瞬間堆起笑容:「哎,去吧去吧,媽給你轉點零花錢。」
……
與此同時,上海,浦東某寫字樓的二十三層。
平台技術組的內部會議室里坐著六個人,沒有人大聲說話。
單越是這種聲音越低的時候,事情反而越重。
長條會議桌的主位坐著的是一個四十多歲,戴著金絲眼鏡的男人,是這次挑戰賽的技術負責人,姓沈。
他的筆記本電腦屏幕上,正顯示著一份PDF的第十八頁。
頁眉的位置印著兩行小字。
「《分鐘級行情數據異常檢測與回測前置審計報告》」
「提交者:1453號。」
沈工的右手食指無意識地敲著桌面,節奏越來越快。
「這個1453號,背景查到了嗎?」
坐在他左手邊的一個戴著耳機的年輕工程師立刻搖頭:「註冊信息只有一個手機號和一個郵箱,我們只能看到註冊時主動留的手機號歸屬地和郵箱域名,更深的身份核查不能做,用戶協議卡得很死。」
「你們說,對方會是什麼身份,某個高校的研究生?」沈工皺了皺眉。
「不像。」另一個年紀稍長的女工程師搖頭,「研究生寫報告習慣用LaTeX,遣詞造句也有學術八股的味道。但這份報告的寫法非常……」
她猶豫了一下,找了個合適的形容詞。
「非常工程化。」
「工程化?」
「就是那種在一線幹過活的人才寫得出來的語氣。你看他第二頁畫的那張污染鏈路拓撲圖,他根本不是從金融的角度在看這份數據,而是從測量系統的角度在看的。」
「他可能根本不關心市場,只關心從交易所到平台之間的每一層數據傳輸環節,哪一層最有可能掉包、漂移、錯位。」
女工程師翻到報告的第八頁,把屏幕轉向沈工。
「真正麻煩的不是他指出了橫向缺失。橫向缺失只要畫熱力圖,細心一點的人都能看見。麻煩的是,他把這些缺失帶、價格尺度跳變和source_flag的分組變化放在了一張圖里。」
沈工低頭看屏幕。
報告第八頁上有一張三層對齊圖。
最上面是按 minute_id展開的缺失熱力圖,中間是若干個脫敏標的在相鄰date_id邊界處的價格尺度變化。
最下面是一條被離散化後的source_flag分組序列。
三條圖之間,被江臨用細灰線拉出了幾組對應關係。
在圖下方,他只寫了一句很冷的判斷。
【source_flag的真實業務含義未知,本文不對其作確定解釋。但該欄位分組變化與若干橫向缺失帶、尺度突變邊界存在統計相關,建議主辦方按source_flag分層覆核底層數據源、清洗批次或供應商接口切換記錄。】
會議室里安靜了幾秒。
年輕工程師低聲說:「他沒有說source_flag是供應商編號。」
女工程師點頭:「對,他沒有越界,只是證明了這個欄位不是無害欄位,和異常分布有關係。」
沈工的臉色沉了下去。
因為他們自己知道,source_flag背後確實不是普通質量標籤。
它對應的是兩家底層行情供應商和一次內部清洗批次切換後的混合標記。
這個欄位本來不該暴露得這麼完整。
更糟的是,1453號並不是靠欄位名猜中的。
他是從數據斷層、尺度跳變和分組同步變化里,把這條鏈路反推了出來。
「也就是說,他不知道我們的底層供應商是誰,但他已經知道這裡發生過數據源切換。」沈工沉吟道。
女工程師說:「對,而且他很聰明,報告裡沒有把話說死,只建議我們分層覆核。」
這比直接指責更難處理。
對方沒有情緒,沒有誇張結論,甚至沒有一句越界的話。
只是把證據擺在那裡,然後把下一步該查什麼,清清楚楚地寫給了他們。
沈工思考了將近一分鐘,然後抬頭去看桌對面那個一直沒說話的法務。
「老周,這次公開榜單前十里,至少有幾份高分提交說明明顯沿用了 baseline的全局標準化流程。他這份E類章節,要是真發到外網上?」
那個被叫做老周的中年男人慢條斯理地推了推眼鏡。
「如果他沒有惡意公開的意圖,那就是一份非常嚴謹的內部審計建議,我們應該感謝他。但如果他選擇把這份報告掛到知乎、雪球、或者任何一個量化論壇,那就是一次嚴重的行業級事故。」
「問題不在於baseline本身。」女工程師說,「問題是我們前幾期給客戶演示的數據清洗模板,也是從這套notebook改出去的。參賽者可以不用,但客戶會看。只要他把這段對照實驗發出去,外界不會區分示例代碼和平台數據能力。」
老周說到這裡看了一眼沈工,解釋說:「所以,我們這一期挑戰賽的所有公開榜單成績,從法律和工程角度都會失去公信力。前幾期挑戰賽因為也用了類似的Baseline,會被一併質疑。已經簽了商業合作的幾個機構客戶,會立刻發函要求我們做技術核查。」
「最壞的情況是,我們整個平台技術組,未來三年內的口碑和業務都會受到不可逆的損害。」
就在這時,戴耳機的年輕工程師面前的電腦屏幕上突然彈出一個新的郵件提示。
他點開看了一眼,瞳孔猛地縮了一下。
「沈工,1453號剛剛提交了補充材料,叫reproduce_leakage_v1.0.zip。「
沈工的呼吸停頓了一瞬,立即說道:「打開,投到大屏上。「
年輕工程師將那個不到一百KB的壓縮包掃了一遍,然後下載解壓,把README的內容投到了會議室的大屏幕上。
六個人同時屏住了呼吸,目光在屏幕上由上至下掃過。
整整兩分鐘,沒有人說話。
最後是那位女工程師輕輕吐出一口氣:「他連我們想找藉口的餘地都沒留。」
沈工盯著屏幕上那張精確到小數點後十六位的預期輸出表格,緩緩地往後靠在了椅背上。
良久,他才開口:「通知評審組,最終評分先暫停自動出榜,等技術覆核結果。還有,聯繫1453號,我要跟他談談。」
……
江臨從外面回來的時候,已經是傍晚六點多。
晚飯桌上,母親難得沒念叨他這幾天悶在屋裡的事,反倒一個勁地往他碗裡夾菜。
吃完飯,他回到房間,習慣性地晃了晃滑鼠。
屏幕亮起。
賽事網頁右上角的通知小鈴鐺,又顯示了一條新的站內信。
「關於邀請您參與平台數據審計專項的初步溝通函。」
發件人依然帶著藍色V字認證:平台技術組管理員-DataOps_03。