Neural ODEs: Does Continuous Time Beat a Delta-Time Feature?
สมการเชิงอนุพันธ์สามัญแบบนิวรัล (Chen และคณะ, NeurIPS 2018) กำหนดพารามิเตอร์ให้กับ อนุพันธ์ ของสถานะซ่อนด้วยโครงข่ายประสาท แล้วให้ตัวแก้ ODE คำนวณผลลัพธ์ โมเดลจึงมีความลึกแบบต่อเนื่อง และที่สำคัญกว่าสำหรับตลาดคือมี เวลา แบบต่อเนื่อง: สามารถประเมินสถานะซ่อนได้ที่ทุกค่า ไม่ใช่เฉพาะจุดกริดที่ข้อมูลของคุณบังเอิญอยู่
คุณสมบัตินี้มีคำอธิบายที่น่าสนใจอย่างชัดเจนสำหรับข้อมูลการเงินซึ่งมาถึงในช่วงเวลาสุ่ม แต่คำอธิบายนั้นซ่อนข้ออ้างที่อ่อนกว่าที่ดูเหมือนจะสื่อ GRU ธรรมดาที่ได้รับฟีเจอร์อินพุตเพิ่ม — นับจากการสังเกตครั้งก่อน — ก็รู้เช่นกันว่าเวลาผ่านไปเท่าใด คำถามจริงไม่ใช่ว่าโมเดลเวลาแบบต่อเนื่องรับข้อมูลไม่สม่ำเสมอได้หรือไม่ แต่คือไดนามิกส์ต่อเนื่องที่เรียนรู้ระหว่างการสังเกตสกัดสิ่งใดที่ฟีเจอร์เดลตาเวลาไม่ได้ให้มาอยู่แล้วหรือไม่ โดยต้องแลกกับต้นทุนอนุมานที่สูงขึ้นสิบถึงร้อยเท่า
บทความนี้วางการทดลองดังกล่าว ให้ชุดเครื่องมือครบถ้วนสำหรับดำเนินการ และรายงานว่าการเปรียบเทียบต้องแสดงอะไรจึงจะรองรับความซับซ้อนนี้ ประเด็นการสร้างแท่งเทียน — ว่าเวลาของการซื้อขายมีสัญญาณในตัวเอง — ได้รับการยืนยันด้วยหลักฐานจากการวัดแท่ง 17 ประเภทใน แท่งเทียนนอกเหนือจากแท่งตามเวลา; ผลที่สำคัญที่นี่มีเพียงด้านการสร้างโมเดล: ตัวแก้ประเมินสถานะซ่อนได้ที่ทุกค่า จึงไม่ต้องใช้การอินเตอร์โพเลตหรือการเติมแพดดิ้ง
ข้ออ้างที่กำลังทดสอบ

สมมติฐานสามชั้น แต่ละข้อแข็งแรงกว่าข้อก่อนหน้าอย่างเคร่งครัด:
- H1 — ODE-RNN ที่ฟิตกับสตรีมการซื้อขาย BTC ดิบแบบไม่สม่ำเสมอเอาชนะ GRU ที่ฟิตกับสตรีมเดียวกันโดยไม่มีข้อมูลเวลาเลย ข้อนี้แทบแน่นอนและแทบไร้ประโยชน์: ODE-RNN เพียงมีข้อมูลที่เบสไลน์ไม่มี
- H2 — ODE-RNN เอาชนะ GRU เดิมเมื่อ GRU ได้รับ เป็นฟีเจอร์อินพุต นี่คือการทดสอบที่ซื่อตรง เป็นข้ออ้างที่งานวิจัย Neural ODE มักถูกตีความว่ากำลังเสนอ และเป็นข้ออ้างที่แทบไม่เคยสาธิตกับข้อมูลการเงิน
- H3 — ความได้เปรียบของ H2 อยู่รอดภายใต้งบเวลาแฝง กล่าวคือยังคงมีอยู่เมื่อใช้งาน ODE-RNN ด้วยตัวแก้แบบก้าวคงที่ที่เร็วพอสำหรับการอนุมานสด แทน
dopri5แบบปรับตัว
โพรโทคอลการทดลอง
สตรีมทิกเดียวกัน เป้าหมายเดียวกัน งบการปรับจูนเดียวกันสำหรับทั้งสองฝั่ง ไม่รีแซมเพิลเป็นแท่ง 1 นาที — การรีแซมเพิลทำลายโครงสร้างที่กำลังทดสอบโดยตรง
| การตั้งค่า | |
|---|---|
| ข้อมูล | รายการธุรกรรม BTC ช่วงห่างระหว่างการมาถึงแปรผัน ไม่รีแซมเพิล |
| ฝั่งการทดลอง | ODE-RNN; GRU + ; GRU ที่ไม่มีฟีเจอร์เวลา |
| เป้าหมาย | เหมือนกันทุกฝั่ง; ระบุพร้อมกับการฟิต |
| เมตริก | RMSE, log-likelihood ของชุดกันไว้, เวลานาฬิกาต่อ epoch, เวลาแฝงของการอนุมานต่อสเต็ป |
| การกวาดตัวแก้ | dopri5 (rtol 1e-5) เทียบกับ Euler แบบก้าวคงที่ โดยจับคู่ที่ความแม่นยำการฝึก |
| การแบ่งชุด | เดินหน้าไปตามเวลา ใช้เฉพาะนอกตัวอย่าง |
| ฝั่ง | RMSE | Log-lik | วินาที/epoch | เวลาแฝงการอนุมาน/สเต็ป |
|---|---|---|---|---|
| GRU (ไม่มีฟีเจอร์เวลา) | — | — | — | — |
| GRU + log(dt) | — | — | — | — |
| ODE-RNN (dopri5) | — | — | — | — |
| ODE-RNN (Euler แบบก้าวคงที่) | — | — | — | — |
ผลลัพธ์เชิงลบของ H2 เผยแพร่ได้อย่างเต็มที่ และอาจเป็นผลลัพธ์ที่มีค่ากว่า — ใช้มาตรฐานเดียวกับใน ผลลัพธ์เชิงลบอย่างซื่อตรง และ Sharpe ที่หักล้างเงินเฟ้อภายใต้การทดสอบหลายครั้ง
การทดลองรอง: CNF เทียบกับ Student-t
หากการเปรียบเทียบ ODE-RNN แพงเกินไป จุดยึดเชิงประจักษ์ที่ถูกกว่าคือด้านการแจกแจง: ฟิต continuous normalizing flow กับผลตอบแทน BTC รายวันจริง แล้วเปรียบเทียบความหนาแน่นที่ฟิตได้กับการฟิต Student-t และหางเชิงประจักษ์ พร้อมรายงานข้อผิดพลาดของควอนไทล์หางที่ระดับ 1% และ 5% เรื่องนี้เชื่อมโดยตรงกับงานด้านการแจกแจงที่วัดแล้วใน การพยากรณ์ความผันผวนด้วย GARCH และ GARCH แบบไม่สมมาตร
ส่วนที่เหลือของบทความนี้คือพื้นฐานและชุดเครื่องมือที่จำเป็นสำหรับรันงานข้างต้น
พื้นฐาน: จาก ResNet สู่ไดนามิกส์ต่อเนื่อง

โครงข่ายรีซิดวลคำนวณ . เมื่อย่อลดขนาดสเต็ปและเพิ่มจำนวนเลเยอร์ คุณจะเข้าใกล้ลิมิตแบบต่อเนื่อง:
แทนที่จะมี เลเยอร์แบบไม่ต่อเนื่องที่ใช้พารามิเตอร์แยกกัน โครงข่ายเดียว กำหนดอัตราการเปลี่ยนแปลงฉับพลัน ผลลัพธ์ที่เวลา แก้ปัญหาค่าเริ่มต้น:
ตัวแก้แบบกล่องดำ (Euler, Runge-Kutta, Dormand-Prince) คำนวณอินทิกรัลเชิงตัวเลข โดยเลือกขนาดสเต็ปแบบปรับตัวจากค่าประมาณข้อผิดพลาดเฉพาะที่
วิธีแอดจอยน์ต์
การแบ็กโพรพาเกตผ่านทุกสเต็ปของตัวแก้ต้องเก็บสถานะกลางทั้งหมด ใช้หน่วยความจำแปรผันตามจำนวนสเต็ป Chen และคณะจึงแก้ ODE ย้อนกลับด้วยหน่วยความจำ . กำหนดสถานะแอดจอยน์ต์ ซึ่งเป็นไปตาม
และสะสมเกรเดียนต์ของพารามิเตอร์ระหว่างการเดินย้อนกลับ:
การเดินย้อนกลับแก้ระบบเสริมที่คำนวณ , และ พร้อมกัน โดยรันตัวแก้ย้อนจาก ไปยัง
ข้อแลกเปลี่ยนคือการสร้าง ย้อนกลับสะสมข้อผิดพลาดเชิงตัวเลข โดยเฉพาะในไดนามิกส์แข็งหรือโกลาหล การทำ checkpoint เป็นทางสายกลาง — เก็บ ไว้ในเวลาตัวกลางไม่กี่จุด แล้วคำนวณใหม่ระหว่างจุดเหล่านั้น กระบวนการราคาคือเซมิ-มาร์ติงเกลต่อเนื่องและค่อนข้างราบรื่น ดังนั้นโดยทั่วไปวิธีแอดจอยน์ต์ยังใช้ได้; แต่ความแข็งรอบเหตุการณ์ไมโครสตรักเจอร์อาจบังคับให้ใช้ตัวแก้แบบปรับตัวหรือแบบไฮบริด
ODE-RNN: สถานะซ่อนต่อเนื่องระหว่างการสังเกต

ODE-RNN คือสถาปัตยกรรมที่การทดลองหลักมุ่งทดสอบ ระหว่างการสังเกต สถานะซ่อนวิวัฒน์ตาม ODE:
เมื่อการสังเกต มาถึงที่ จะเกิดการอัปเดตแบบไม่ต่อเนื่อง:
ตัวยกกำลังระบุสถานะก่อนและหลังการสังเกตทันที ระหว่างการสังเกตคือไดนามิกส์ต่อเนื่องที่เรียนรู้; ณ จุดสังเกตคือข้อมูลใหม่
กลไกที่การทดลองตัดออก H2 ตรวจสอบมีดังนี้: ช่องว่างยาวหมายความว่าสถานะซ่อนได้วิวัฒน์ไปไกลภายใต้ — สลายเข้าหาเบสไลน์หรือแยกออก — และรูปทรงของวิวัฒน์นี้ เรียนรู้ แทนที่จะป้อนเป็นสเกลาร์ การแสดงออกที่มากขึ้นนี้คุ้มต้นทุนบนข้อมูลการซื้อขายจริงหรือไม่ คือสิ่งที่ยังไม่ได้วัดอย่างแน่ชัด
การใช้งานที่เหมาะสมนอกเหนือจากทิก: พอร์ตหลายสินทรัพย์ที่แต่ละสินทรัพย์มีตารางการสังเกตของตนเอง ขณะที่สถานะแฝงของสินทรัพย์ที่สัมพันธ์กันยังวิวัฒน์ต่อไปแม้สังเกตเพียงสินทรัพย์เดียว; และสัญญาณขับเคลื่อนด้วยเหตุการณ์ (ข่าว ผลประกอบการ การประกาศเศรษฐกิจมหภาค) ที่มาถึงในเวลาไม่สม่ำเสมอ
Neural SDE: เพิ่มองค์ประกอบสุ่ม

Neural ODE เป็นแบบกำหนดแน่นอนและแทนสัญญาณรบกวนในเส้นทางราคาไม่ได้ การรักษาแบบคลาสสิก — geometric Brownian motion, drift ที่เป็นกลางต่อความเสี่ยง และวิธีที่สมมติฐานความผันผวนคงที่ล้มเหลวเมื่อเจอกับ smile — กล่าวถึงไว้ใน การกำหนดราคาตัวเลือก Black-Scholes Neural SDE แทนรูปแบบพารามิเตอร์ด้วยพจน์ diffusion ที่เรียนรู้:
คือ drift, คือ diffusion, คือกระบวนการ Wiener และทั้ง กับ เป็นโครงข่ายประสาท
สถาปัตยกรรม: โครงข่าย Drift และ Diffusion
- โครงข่าย Drift : วิถีที่คาดหมาย — แนวโน้ม การย้อนกลับสู่ค่าเฉลี่ย โมเมนตัม ฝึกเพื่อลดข้อผิดพลาดการพยากรณ์
- โครงข่าย Diffusion : ขนาดสัญญาณรบกวน เรียนรู้เป็นฟังก์ชันของสถานะ สูงในระบอบผันผวน ต่ำในระบอบสงบ
การแยกนี้สะท้อนการเงินเชิงปริมาณแบบคลาสสิก: drift คือไดนามิกส์ภายใต้การวัดที่เป็นกลางต่อความเสี่ยง (หรือการวัด P) ส่วน diffusion คือพื้นผิวความผันผวน
การฝึก Neural SDE
อินทิกรัลสุ่ม ไม่สามารถหาอนุพันธ์ได้แบบคลาสสิก มีสามแนวทาง:
- เกรเดียนต์ตามเส้นทาง: เทคนิค reparameterization — สุ่มเส้นทางบราวเนียน แล้วหาอนุพันธ์ผ่านตัวแก้โดยถือว่าสัญญาณรบกวนเป็นอินพุตคงที่
- การจับคู่สกอร์: ประมาณ และฝึกด้วยเป้าหมาย denoising — เครื่องมือเดียวกับที่ใช้เป็นโมเดลกำเนิดแบบเดี่ยวใน โมเดล diffusion สำหรับการพยากรณ์คริปโต แต่ที่นี่ลดบทบาทเหลือเพียงตัวเลือกการฝึกสำหรับ SDE
- การจับคู่การแจกแจงมิติจำกัด: จับคู่ marginal ณ เวลาสังเกต แทนที่จะจับคู่ measure ของเส้นทางเต็ม
วิธีตามเส้นทางเป็นค่าเริ่มต้นในทางปฏิบัติ torchsde รองรับ Euler-Maruyama, Milstein และ stochastic Runge-Kutta พร้อม autodiff
การเรียนรู้พื้นผิวความผันผวน
โมเดลคลาสสิก (Heston, SABR) บังคับรูปแบบพารามิเตอร์ให้กับค่าสัมประสิทธิ์ diffusion ส่วน Neural SDE เรียนรู้ เป็นฟังก์ชันทั่วไป:
นี่คือตัวประมาณสากลสำหรับกระบวนการ diffusion — กระบวนการ Ito ใด ๆ ก็ประมาณได้แม่นยำตามต้องการเมื่อมีความจุมากพอ
Latent ODE สำหรับข้อมูลหายและข้อมูลไม่พร้อมกัน

Latent ODE (Rubanova, Chen, Duvenaud, 2019) จับคู่ Neural ODE กับ VAE: อนุกรมการเงินคือการสังเกตที่มีสัญญาณรบกวนของกระบวนการพื้นฐานที่ราบรื่น ซึ่งเรียนรู้ในสเปซแฝงมิติต่ำ
- โครงข่ายรับรู้: ODE-RNN วิ่งย้อนผ่านการสังเกตเพื่อสร้าง
- ไดนามิกส์แฝง:
- ตัวถอดรหัส: ประเมินได้ ณ เวลาใด ๆ ที่ร้องขอ
ฟังก์ชันสูญเสียคือ ELBO มาตรฐาน:
การประมาณสถานะพอร์ต: จากการสังเกตแบบเบาบางและอะซิงโครนัสข้ามสินทรัพย์ อนุมานสถานะแฝงต่อเนื่องที่จับไดนามิกส์ร่วม — โดยพื้นฐานคือเวอร์ชันไม่เชิงเส้นที่เรียนรู้ของตัวกรอง Kalman
การเติมข้อมูลหาย: ช่วงหยุดซื้อขายและช่องว่างสุดสัปดาห์ได้วิถีแฝงที่อินเตอร์โพเลตอย่างราบรื่น; ตัวถอดรหัสสร้างเส้นทางที่เป็นไปได้ผ่านช่องว่าง พร้อมความไม่แน่นอนจาก posterior ของ VAE
การผสานหลายความถี่: ราคาปิดรายวัน VWAP ระหว่างวัน และข้อมูลทิกในโมเดลเดียวโดยไม่มีกริดเวลาร่วม
Continuous Normalizing Flow สำหรับการแจกแจงผลตอบแทน

CNF ใช้ Neural ODE แปลงการแจกแจงฐานอย่างง่ายเป็นเป้าหมายที่ซับซ้อน การแปลงคือ และ log-density เป็นไปตามการเปลี่ยนตัวแปรฉับพลัน:
อินทิเกรตสถานะและ log-density ไปข้างหน้าจาก เพื่อให้ได้ และ รอยเทรซของจาโคเบียนประมาณด้วยตัวประมาณเชิงสุ่มของ Hutchinson เพื่อการขยายขนาด
ผลตอบแทนคริปโตมี kurtosis สูงและเบ้ซ้าย — วัดได้จากข้อมูลจริงใน การพยากรณ์ความผันผวนด้วย GARCH และ GARCH แบบไม่สมมาตรกับผลของเลเวอเรจ — และ CNF เรียนรู้รูปทรงนั้นแทนการสมมติ การทำให้โฟลว์มีเงื่อนไขตามตัวแปรสถานะทำให้รูปทรงเปลี่ยนตาม regime ได้ เพราะความหนาแน่นเป็นแบบ exact ไม่ใช่ค่าประมาณ จึงส่งต่อไปยังเมตริกหางที่คำนวณใน แบ็กเทสต์มอนติคาร์โลและบูตสแตรป และการสร้าง CVaR ใน ไปป์ไลน์พอร์ต HRP/CVaR; โครงสร้างหางร่วมจัดการแยกต่างหากใน โมเดล copula สำหรับความเสี่ยงร่วม
การประมาณความหนาแน่นแบบมีเงื่อนไข
กรอบที่มีประโยชน์คือการเปรียบเทียบโดยตรงระหว่างสามแนวทางที่ตีพิมพ์สำหรับปัญหาเดียวกัน TFT ให้ควอนไทล์ชุดคงที่จากเลเยอร์เอาต์พุตควอนไทล์ Conformal prediction ให้ช่วงที่ปรับเทียบแล้วพร้อมหลักประกันด้าน coverage ส่วน CNF แบบมีเงื่อนไขให้ความหนาแน่นที่ exact และหาอนุพันธ์ได้:
ความสามารถในการหาอนุพันธ์คือคุณสมบัติที่แตกต่าง: ความหนาแน่นวางใน objective ปลายทางและแบ็กโพรพาเกตผ่านได้ ซึ่งทั้งควอนไทล์คงที่และช่วง conformal ทำไม่ได้ ความหนาแน่น exact คุ้มต้นทุนเมื่อเทียบกับควอนไทล์ TFT บนเป้าหมายเดียวกันหรือไม่ ยังไม่ได้ทดสอบที่นี่
เปรียบเทียบกับทางเลือกแบบไม่ต่อเนื่อง

| คุณสมบัติ | LSTM/GRU | Transformer | Neural ODE | Neural SDE |
|---|---|---|---|---|
| การจัดการเวลาไม่สม่ำเสมอ | แย่ (ต้องเติมแพดดิ้ง) | positional encoding | native | native |
| หน่วยความจำ (ฝึก) | adjoint | adjoint | ||
| การหาปริมาณความไม่แน่นอน | ไม่ (กำหนดแน่นอน) | ไม่ (กำหนดแน่นอน) | ผ่าน ensemble | native |
| อินเตอร์โพเลตระหว่างการสังเกต | ไม่ | ไม่ | ได้ | ได้ |
| ความหนาแน่นเวลาแบบต่อเนื่อง | ไม่ | ไม่ | ผ่าน CNF | ผ่าน path measure |
| ต้นทุนคำนวณ | ต่ำ | ปานกลาง | แปรผัน (ตัวแก้) | สูง (ตัวแก้ SDE) |
ส่วน LSTM เทียบกับ attention ของตารางนี้อธิบายโดยละเอียดพร้อมเบนช์มาร์กใน บทความ TFT — ดูส่วน TFT เทียบกับ LSTM เทียบกับ Vanilla Transformer และ เมื่อ LSTM ยังชนะ ส่วนที่ใหม่ที่นี่คือสองคอลัมน์ทางขวา และแถวที่ตัดสินคำถาม H2/H3 คือสองแถวสุดท้าย
การใช้งาน Python ด้วย torchdiffeq

torchdiffeq มีตัวแก้ ODE พร้อมแบ็กโพรพาเกชันแบบแอดจอยน์ต์
Neural ODE พื้นฐานสำหรับไดนามิกส์ราคา
import torch
import torch.nn as nn
from torchdiffeq import odeint_adjoint as odeint
class PriceDynamics(nn.Module):
"""Neural network defining dh/dt = f(h, t)."""
def __init__(self, hidden_dim: int = 64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(hidden_dim, 128),
nn.Tanh(),
nn.Linear(128, 128),
nn.Tanh(),
nn.Linear(128, hidden_dim),
)
def forward(self, t, h):
return self.net(h)
class NeuralODEPredictor(nn.Module):
"""
Encode observed features -> latent state,
evolve via Neural ODE,
decode to price prediction.
"""
def __init__(self, input_dim: int, hidden_dim: int = 64):
super().__init__()
self.encoder = nn.Linear(input_dim, hidden_dim)
self.dynamics = PriceDynamics(hidden_dim)
self.decoder = nn.Linear(hidden_dim, 1)
def forward(self, x0, eval_times):
"""
x0: (batch, input_dim) features at t=0
eval_times: (T,) times at which to evaluate the ODE
Returns: (T, batch, 1) predictions
"""
h0 = self.encoder(x0) # (batch, hidden_dim)
h_traj = odeint(self.dynamics, h0, eval_times,
method='dopri5', rtol=1e-5, atol=1e-7)
return self.decoder(h_traj)
ODE-RNN สำหรับข้อมูลทิกไม่สม่ำเสมอ
นี่คือฝั่งการทดลอง เบสไลน์ที่ต้องเอาชนะคือ nn.GRUCell บนสตรีมเดียวกัน โดยต่อ log(t_next - t_prev) เข้ากับ x
class ODERNNCell(nn.Module):
"""Single step: ODE-evolve, then RNN-update."""
def __init__(self, input_dim: int, hidden_dim: int = 64):
super().__init__()
self.dynamics = PriceDynamics(hidden_dim)
self.gru_cell = nn.GRUCell(input_dim, hidden_dim)
def forward(self, h, x, t_prev, t_next):
times = torch.tensor([t_prev, t_next], dtype=torch.float32)
h_evolved = odeint(self.dynamics, h, times,
method='dopri5')[-1] # state at t_next
h_updated = self.gru_cell(x, h_evolved)
return h_updated
class ODERNN(nn.Module):
"""Process irregularly-sampled sequence."""
def __init__(self, input_dim: int, hidden_dim: int = 64):
super().__init__()
self.cell = ODERNNCell(input_dim, hidden_dim)
self.decoder = nn.Linear(hidden_dim, 1)
self.hidden_dim = hidden_dim
def forward(self, observations, times):
"""
observations: list of (batch, input_dim) tensors
times: list of floats, observation timestamps
"""
batch_size = observations[0].shape[0]
h = torch.zeros(batch_size, self.hidden_dim)
outputs = []
for i in range(len(observations)):
t_prev = 0.0 if i == 0 else times[i - 1]
h = self.cell(h, observations[i], t_prev, times[i])
outputs.append(self.decoder(h))
return torch.stack(outputs) # (seq_len, batch, 1)
ลูปการฝึก
def train_neural_ode(model, train_loader, epochs=100, lr=1e-3):
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer, T_max=epochs
)
for epoch in range(epochs):
epoch_loss = 0.0
for batch in train_loader:
features, times, targets = batch
optimizer.zero_grad()
predictions = model(features, times)
loss = torch.nn.functional.mse_loss(predictions, targets)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
epoch_loss += loss.item()
scheduler.step()
if (epoch + 1) % 10 == 0:
avg_loss = epoch_loss / len(train_loader)
print(f"Epoch {epoch+1}/{epochs}, Loss: {avg_loss:.6f}")
Continuous Normalizing Flow สำหรับการแจกแจงผลตอบแทน
from torchdiffeq import odeint
class CNFDynamics(nn.Module):
"""Dynamics for continuous normalizing flow."""
def __init__(self, dim: int = 1, hidden_dim: int = 64):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim + 1, hidden_dim), # +1 for time
nn.Tanh(),
nn.Linear(hidden_dim, hidden_dim),
nn.Tanh(),
nn.Linear(hidden_dim, dim),
)
self.dim = dim
def forward(self, t, state):
z = state[..., :self.dim]
t_expand = t.expand(z.shape[0], 1)
zt = torch.cat([z, t_expand], dim=-1)
dz = self.net(zt)
e = torch.randn_like(z)
e_dz = torch.autograd.grad(
dz, z, e, create_graph=True
)[0]
trace_jac = (e_dz * e).sum(dim=-1, keepdim=True)
return torch.cat([dz, -trace_jac], dim=-1)
class ReturnDistributionCNF(nn.Module):
"""Model return distributions with continuous normalizing flows."""
def __init__(self, dim: int = 1):
super().__init__()
self.dynamics = CNFDynamics(dim)
self.dim = dim
def log_prob(self, x):
"""Compute log probability of observed returns."""
log_p0 = torch.zeros(x.shape[0], 1)
state0 = torch.cat([x, log_p0], dim=-1)
state0.requires_grad_(True)
times = torch.tensor([1.0, 0.0]) # backward
state_T = odeint(self.dynamics, state0, times,
method='dopri5')[-1]
z_T = state_T[..., :self.dim]
delta_log_p = state_T[..., self.dim:]
log_p_base = -0.5 * (z_T ** 2 + torch.log(
torch.tensor(2 * torch.pi)
)).sum(dim=-1, keepdim=True)
return log_p_base + delta_log_p
def sample(self, n_samples: int):
"""Generate samples from learned distribution."""
z0 = torch.randn(n_samples, self.dim)
log_p0 = torch.zeros(n_samples, 1)
state0 = torch.cat([z0, log_p0], dim=-1)
times = torch.tensor([0.0, 1.0]) # forward
state_T = odeint(self.dynamics, state0, times,
method='dopri5')[-1]
return state_T[..., :self.dim]
หมายเหตุเชิงปฏิบัติ

สิ่งเหล่านี้คือจุดที่จะทำให้คุณเจอปัญหาระหว่างรันการทดลองข้างต้น
การเลือกตัวแก้และความเร็ว
dopri5 ให้หลักประกันด้านความแม่นยำแต่มีต้นทุนคำนวณแปรผัน นี่คือเหตุผลที่ H3 เป็นสมมติฐานแยกจาก H2: ความได้เปรียบที่มีอยู่เฉพาะภายใต้ตัวแก้แบบปรับตัวอาจไม่รอดงบเวลาแฝงแบบสด ตัวแก้แบบก้าวคงที่ (Euler, RK4) ให้เวลาแฝงคาดเดาได้แลกกับความแม่นยำ ทางประนีประนอมเชิงปฏิบัติคือฝึกด้วย dopri5 แล้วนำไปใช้งานด้วยตัวแก้ก้าวคงที่ที่สอบเทียบให้เอาต์พุตตรงกับตัวแก้แบบปรับตัวบนข้อมูลตัวแทน — และวัดช่องว่างแทนการสมมติว่ามันเล็ก
odeint(f, h0, t, method='dopri5', rtol=1e-6, atol=1e-8)
odeint(f, h0, t, method='euler', options={'step_size': 0.1})
สำหรับปัญหาแข็งใกล้การกระโดดไม่ต่อเนื่อง ใช้ method='implicit_adams' หรือ method='scipy_solver'
เสถียรภาพเชิงตัวเลข
หาก ให้ค่าขนาดใหญ่ สถานะจะพุ่งออกจากระบบ วิธีบรรเทามีดังนี้:
- การทำ spectral normalization บนเลเยอร์ของ เพื่อควบคุมค่าคงที่ Lipschitz
- การตัดเกรเดียนต์ ระหว่างฝึก (แสดงในลูปการฝึกข้างต้น)
- การทำ normalization ของเวลา: ปรับสเกล timestamp ให้อยู่ใน
- การทำ regularization กับนอร์มไดนามิกส์: เพิ่ม ลงใน loss
ช่วงอินทิเกรต
สำหรับข้อมูลที่ครอบคลุมหลายเดือน อย่าอินทิเกรตจาก ถึง นาที:
t_normalized = (timestamps - timestamps[0]) / (timestamps[-1] - timestamps[0])
วิธีนี้ทำให้ตัวแก้อยู่ในช่วงที่เป็นมิตรต่อเชิงตัวเลข และทำให้ไดนามิกส์ที่เรียนรู้ไม่ขึ้นกับสเกล นอกจากนี้ยังสำคัญต่อการเปรียบเทียบ: ODE-RNN ที่ไม่ normalize อาจแพ้เบสไลน์ GRU ด้วยเหตุผลเชิงตัวเลขล้วน ๆ ซึ่งจะเป็นสิ่งประดิษฐ์จากการวัด ไม่ใช่ข้อค้นพบ
การจัดการหลายสเกลเวลา
ตลาดมีไดนามิกส์ในระดับไมโครวินาที วินาที นาที และวันพร้อมกัน และ Neural ODE เดียวอาจรักษาทั้งหมดไว้ได้ยาก ทางเลือกคือซ้อนบล็อก ODE หลายชุดในสเกลเวลาต่างกัน; ขยายมิติซ่อนเพื่อรองรับความจุทั้งเร็วและช้า; หรือรัน Neural ODE แยกตามแถบความถี่แล้วรวมเอาต์พุต (นี่เป็นคำถามเรื่องความจุโมเดล แยกจากคำถามเรื่องความเที่ยงตรงของแบ็กเทสต์ใน การเจาะลึกความละเอียดแบบปรับตัว)
ทิศทางวิจัยที่เปิดอยู่

Neural Jump SDE: เพิ่มองค์ประกอบ jump ที่เรียนรู้ได้สำหรับการกระจัดฉับพลัน (แฟลชแครช เซอร์ไพรส์จากผลประกอบการ):
โดยที่ คือ random measure แบบปัวซองชดเชย และ คือเคอร์เนล jump ที่เรียนรู้ได้
สมการเชิงอนุพันธ์ควบคุมแบบนิวรัล (Neural CDE): แทนที่กระบวนการ Wiener ด้วยสัญญาณขับทั่วไป ให้สตรีมข้อมูลที่สังเกตได้ขับไดนามิกส์ เหมาะกับ order flow ที่สตรีมธุรกรรมและราคาเสนอซื้อขายขับสถานะตลาดแฝง
การจำลองตลาดที่หาอนุพันธ์ได้: ใช้ Neural SDE เป็นโมเดลกำเนิดภายในตัวจำลองที่หาอนุพันธ์ได้ และฝึกกลยุทธ์แบบ end-to-end ด้วยการแบ็กโพรพาเกตผ่านมันด้วยวิธีแอดจอยน์ต์ กลยุทธ์และโมเดลตลาดวิวัฒน์ร่วมกัน
Neural ODE ที่คำนึงถึงฟิสิกส์: PINN ฝังรีซิดวลของสมการเชิงอนุพันธ์ไว้ใน loss — เทคนิคนี้แนะนำไว้ใน บทความ Navier-Stokes การประยุกต์ด้านการเงินคือการบังคับ no-arbitrage, put-call parity และเงื่อนไขมาร์ติงเกลให้เป็นพจน์ penalty หรือข้อจำกัดแข็งบนไดนามิกส์ที่เรียนรู้
บทสรุป

กรอบเวลาแบบต่อเนื่องถูกต้องในเชิงโครงสร้าง: ตลาดเป็นกระบวนการต่อเนื่องที่สังเกตในเวลาไม่ต่อเนื่องและไม่สม่ำเสมอ โมเดลควรเคารพสิ่งนี้ ชุดเครื่องมือมีความพร้อมแล้ว — torchdiffeq, torchsde, PyTorch ธรรมดาสำหรับส่วนที่เหลือ — และต้นทุนที่รู้จักคือความเร็วตัวแก้กับเสถียรภาพเชิงตัวเลขในช่วงอินทิเกรตยาว
สิ่งที่ยังไม่ยืนยันคือส่วนที่ตัดสินว่าเรื่องนี้ควรอยู่ใน production stack หรือไม่ ความถูกต้องเชิงโครงสร้างไม่ใช่หลักฐานของความได้เปรียบด้านการพยากรณ์ และความได้เปรียบเฉพาะที่อ้างว่าไดนามิกส์ต่อเนื่องเหนือกว่าฟีเจอร์ ยังไม่ได้วัดกับข้อมูลการซื้อขายจริงที่นี่ จนกว่าจะเติมตาราง H2 ด้านบน ให้ถือทุกอย่างหลังเส้นพับเป็นสมมติฐานที่ระบุไว้อย่างชัดเจนพร้อม implementation ที่ใช้งานได้ ไม่ใช่ผลลัพธ์
เอกสารอ้างอิง
- Chen, R.T.Q., Rubanova, Y., Bettencourt, J., Duvenaud, D. (2018). สมการเชิงอนุพันธ์สามัญแบบนิวรัล. NeurIPS 2018. arXiv:1806.07366
- Rubanova, Y., Chen, R.T.Q., Duvenaud, D. (2019). Latent ODE สำหรับอนุกรมเวลาที่มีการสุ่มตัวอย่างไม่สม่ำเสมอ. NeurIPS 2019. arXiv:1907.03907
- Jia, J., Benson, A.R. (2019). สมการเชิงอนุพันธ์สุ่มแบบนิวรัลที่มีการกระโดด. NeurIPS 2019.
- Kidger, P., Morrill, J., Foster, J., Lyons, T. (2020). สมการเชิงอนุพันธ์ควบคุมแบบนิวรัลสำหรับอนุกรมเวลาที่ไม่สม่ำเสมอ. NeurIPS 2020.
- Hasan, A., Pereira, J.M., Farsiu, S., Carin, L. (2021). โมเดลสมการเชิงอนุพันธ์สุ่มของโครงข่ายประสาทพร้อมการประยุกต์กับการพยากรณ์ข้อมูลการเงิน. arXiv:2111.13164
- torchdiffeq: github.com/rtqichen/torchdiffeq
- บทเรียนการเรียนรู้เชิงลึกของ UvA — Neural ODE: uvadlc-notebooks.readthedocs.io
ผู้เขียน
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.