model-training-step.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. "use client"
  2. import { useState, useEffect, useRef } from "react"
  3. import { Button } from "@/components/ui/button"
  4. import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
  5. import { Badge } from "@/components/ui/badge"
  6. import { Progress } from "@/components/ui/progress"
  7. import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
  8. import { Slider } from "@/components/ui/slider"
  9. import { Label } from "@/components/ui/label"
  10. import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
  11. import { Play, Pause, Square, Zap, Info } from "lucide-react"
  12. interface ModelTrainingStepProps {
  13. onNext?: () => void
  14. }
  15. interface TrainingConfig {
  16. learningRate: number
  17. batchSize: number
  18. epochs: number
  19. optimizer: string
  20. architecture: string
  21. }
  22. interface TrainingMetrics {
  23. epoch: number
  24. loss: number
  25. accuracy: number
  26. valLoss: number
  27. valAccuracy: number
  28. learningRate: number
  29. }
  30. interface TrainingState {
  31. isTraining: boolean
  32. isPaused: boolean
  33. currentEpoch: number
  34. totalEpochs: number
  35. progress: number
  36. metrics: TrainingMetrics[]
  37. phase: "idle" | "initializing" | "training" | "validating" | "completed"
  38. }
  39. const architectures = [
  40. { value: "cnn", label: "CNN (卷积神经网络)", description: "适合图像特征提取" },
  41. { value: "resnet", label: "ResNet", description: "深度残差网络,防止梯度消失" },
  42. { value: "vgg", label: "VGG", description: "经典深度卷积网络" },
  43. { value: "gan", label: "GAN", description: "生成对抗网络,用于图像生成" },
  44. ]
  45. const optimizers = [
  46. { value: "adam", label: "Adam", description: "自适应学习率优化器" },
  47. { value: "sgd", label: "SGD", description: "随机梯度下降" },
  48. { value: "rmsprop", label: "RMSprop", description: "均方根传播算法" },
  49. ]
  50. export default function ModelTrainingStep({ onNext }: ModelTrainingStepProps) {
  51. const [config, setConfig] = useState<TrainingConfig>({
  52. learningRate: 0.001,
  53. batchSize: 32,
  54. epochs: 50,
  55. optimizer: "adam",
  56. architecture: "cnn",
  57. })
  58. const [trainingState, setTrainingState] = useState<TrainingState>({
  59. isTraining: false,
  60. isPaused: false,
  61. currentEpoch: 0,
  62. totalEpochs: 0,
  63. progress: 0,
  64. metrics: [],
  65. phase: "idle",
  66. })
  67. const intervalRef = useRef<NodeJS.Timeout | null>(null)
  68. const generateMetrics = (epoch: number): TrainingMetrics => {
  69. // 模拟真实的训练曲线
  70. const progress = epoch / config.epochs
  71. const baseLoss = 2.5 * Math.exp(-progress * 3) + 0.1
  72. const baseAccuracy = 0.95 * (1 - Math.exp(-progress * 4)) + 0.05
  73. // 添加一些随机波动
  74. const lossNoise = (Math.random() - 0.5) * 0.1
  75. const accNoise = (Math.random() - 0.5) * 0.05
  76. return {
  77. epoch,
  78. loss: Math.max(0.05, baseLoss + lossNoise),
  79. accuracy: Math.min(0.98, Math.max(0.05, baseAccuracy + accNoise)),
  80. valLoss: Math.max(0.08, baseLoss * 1.1 + lossNoise),
  81. valAccuracy: Math.min(0.95, Math.max(0.05, baseAccuracy * 0.95 + accNoise)),
  82. learningRate: config.learningRate * Math.pow(0.95, Math.floor(epoch / 10)),
  83. }
  84. }
  85. const startTraining = () => {
  86. setTrainingState((prev) => ({
  87. ...prev,
  88. isTraining: true,
  89. isPaused: false,
  90. currentEpoch: 0,
  91. totalEpochs: config.epochs,
  92. progress: 0,
  93. metrics: [],
  94. phase: "initializing",
  95. }))
  96. // 模拟训练过程
  97. let currentEpoch = 0
  98. intervalRef.current = setInterval(() => {
  99. if (currentEpoch >= config.epochs) {
  100. setTrainingState((prev) => ({
  101. ...prev,
  102. isTraining: false,
  103. phase: "completed",
  104. }))
  105. if (intervalRef.current) clearInterval(intervalRef.current)
  106. return
  107. }
  108. const metrics = generateMetrics(currentEpoch + 1)
  109. setTrainingState((prev) => ({
  110. ...prev,
  111. currentEpoch: currentEpoch + 1,
  112. progress: ((currentEpoch + 1) / config.epochs) * 100,
  113. metrics: [...prev.metrics, metrics],
  114. phase: currentEpoch % 5 === 4 ? "validating" : "training",
  115. }))
  116. currentEpoch++
  117. }, 200) // 每200ms一个epoch,加快演示速度
  118. }
  119. const pauseTraining = () => {
  120. setTrainingState((prev) => ({ ...prev, isPaused: true }))
  121. if (intervalRef.current) clearInterval(intervalRef.current)
  122. }
  123. const stopTraining = () => {
  124. setTrainingState({
  125. isTraining: false,
  126. isPaused: false,
  127. currentEpoch: 0,
  128. totalEpochs: 0,
  129. progress: 0,
  130. metrics: [],
  131. phase: "idle",
  132. })
  133. if (intervalRef.current) clearInterval(intervalRef.current)
  134. }
  135. const updateConfig = (key: keyof TrainingConfig, value: any) => {
  136. if (!trainingState.isTraining) {
  137. setConfig((prev) => ({ ...prev, [key]: value }))
  138. }
  139. }
  140. const currentMetrics = trainingState.metrics[trainingState.metrics.length - 1]
  141. const maxLoss = Math.max(...trainingState.metrics.map((m) => Math.max(m.loss, m.valLoss)), 2.5)
  142. const maxAccuracy = 1.0
  143. useEffect(() => {
  144. return () => {
  145. if (intervalRef.current) clearInterval(intervalRef.current)
  146. }
  147. }, [])
  148. const handleNext = () => {
  149. if (onNext && trainingState.phase === "completed") {
  150. onNext()
  151. }
  152. }
  153. return (
  154. <div className="space-y-6">
  155. {/* Introduction */}
  156. <Card className="bg-chart-3/5 border-chart-3/20">
  157. <CardHeader>
  158. <div className="flex items-center gap-2">
  159. <Info className="w-5 h-5 text-chart-3" />
  160. <CardTitle className="text-lg font-serif">什么是模型训练?</CardTitle>
  161. </div>
  162. </CardHeader>
  163. <CardContent>
  164. <p className="text-muted-foreground leading-relaxed">
  165. 模型训练是AI学习的核心过程。就像学生通过大量练习来掌握知识一样,AI模型通过反复处理训练数据来学习图像的特征和模式。
  166. 在这个过程中,模型会不断调整内部参数,逐渐提高对图像的理解能力,最终能够生成新的艺术作品。
  167. </p>
  168. </CardContent>
  169. </Card>
  170. <div className="grid lg:grid-cols-3 gap-6">
  171. {/* Training Configuration */}
  172. <Card className="lg:col-span-1">
  173. <CardHeader>
  174. <CardTitle className="font-serif">训练配置</CardTitle>
  175. <CardDescription>调整训练参数,观察对模型性能的影响</CardDescription>
  176. </CardHeader>
  177. <CardContent className="space-y-6">
  178. {/* Architecture Selection */}
  179. <div className="space-y-2">
  180. <Label className="font-medium">模型架构</Label>
  181. <Select
  182. value={config.architecture}
  183. onValueChange={(value) => updateConfig("architecture", value)}
  184. disabled={trainingState.isTraining}
  185. >
  186. <SelectTrigger>
  187. <SelectValue />
  188. </SelectTrigger>
  189. <SelectContent>
  190. {architectures.map((arch) => (
  191. <SelectItem key={arch.value} value={arch.value}>
  192. <div>
  193. <div className="font-medium">{arch.label}</div>
  194. <div className="text-xs text-muted-foreground">{arch.description}</div>
  195. </div>
  196. </SelectItem>
  197. ))}
  198. </SelectContent>
  199. </Select>
  200. </div>
  201. {/* Optimizer Selection */}
  202. <div className="space-y-2">
  203. <Label className="font-medium">优化器</Label>
  204. <Select
  205. value={config.optimizer}
  206. onValueChange={(value) => updateConfig("optimizer", value)}
  207. disabled={trainingState.isTraining}
  208. >
  209. <SelectTrigger>
  210. <SelectValue />
  211. </SelectTrigger>
  212. <SelectContent>
  213. {optimizers.map((opt) => (
  214. <SelectItem key={opt.value} value={opt.value}>
  215. <div>
  216. <div className="font-medium">{opt.label}</div>
  217. <div className="text-xs text-muted-foreground">{opt.description}</div>
  218. </div>
  219. </SelectItem>
  220. ))}
  221. </SelectContent>
  222. </Select>
  223. </div>
  224. {/* Learning Rate */}
  225. <div className="space-y-3">
  226. <Label className="font-medium">学习率: {config.learningRate}</Label>
  227. <Slider
  228. value={[config.learningRate]}
  229. onValueChange={([value]) => updateConfig("learningRate", value)}
  230. min={0.0001}
  231. max={0.01}
  232. step={0.0001}
  233. disabled={trainingState.isTraining}
  234. className="w-full"
  235. />
  236. <p className="text-xs text-muted-foreground">控制模型参数更新的步长</p>
  237. </div>
  238. {/* Batch Size */}
  239. <div className="space-y-3">
  240. <Label className="font-medium">批次大小: {config.batchSize}</Label>
  241. <Slider
  242. value={[config.batchSize]}
  243. onValueChange={([value]) => updateConfig("batchSize", value)}
  244. min={8}
  245. max={128}
  246. step={8}
  247. disabled={trainingState.isTraining}
  248. className="w-full"
  249. />
  250. <p className="text-xs text-muted-foreground">每次训练处理的图像数量</p>
  251. </div>
  252. {/* Epochs */}
  253. <div className="space-y-3">
  254. <Label className="font-medium">训练轮数: {config.epochs}</Label>
  255. <Slider
  256. value={[config.epochs]}
  257. onValueChange={([value]) => updateConfig("epochs", value)}
  258. min={10}
  259. max={200}
  260. step={10}
  261. disabled={trainingState.isTraining}
  262. className="w-full"
  263. />
  264. <p className="text-xs text-muted-foreground">完整遍历训练数据的次数</p>
  265. </div>
  266. {/* Training Controls */}
  267. <div className="flex gap-2 pt-4">
  268. {!trainingState.isTraining ? (
  269. <Button onClick={startTraining} className="flex-1">
  270. <Play className="w-4 h-4 mr-2" />
  271. 开始训练
  272. </Button>
  273. ) : (
  274. <>
  275. <Button onClick={pauseTraining} variant="outline" className="flex-1 bg-transparent">
  276. <Pause className="w-4 h-4 mr-2" />
  277. 暂停
  278. </Button>
  279. <Button onClick={stopTraining} variant="destructive" className="flex-1">
  280. <Square className="w-4 h-4 mr-2" />
  281. 停止
  282. </Button>
  283. </>
  284. )}
  285. </div>
  286. </CardContent>
  287. </Card>
  288. {/* Training Progress and Metrics */}
  289. <div className="lg:col-span-2 space-y-6">
  290. {/* Training Status */}
  291. <Card>
  292. <CardHeader>
  293. <div className="flex items-center justify-between">
  294. <CardTitle className="font-serif">训练状态</CardTitle>
  295. <Badge
  296. variant={
  297. trainingState.phase === "completed"
  298. ? "default"
  299. : trainingState.phase === "training"
  300. ? "secondary"
  301. : trainingState.phase === "validating"
  302. ? "outline"
  303. : "secondary"
  304. }
  305. >
  306. {trainingState.phase === "idle"
  307. ? "待机中"
  308. : trainingState.phase === "initializing"
  309. ? "初始化中"
  310. : trainingState.phase === "training"
  311. ? "训练中"
  312. : trainingState.phase === "validating"
  313. ? "验证中"
  314. : "训练完成"}
  315. </Badge>
  316. </div>
  317. </CardHeader>
  318. <CardContent>
  319. <div className="space-y-4">
  320. {/* Overall Progress */}
  321. <div className="space-y-2">
  322. <div className="flex justify-between text-sm">
  323. <span>总体进度</span>
  324. <span>
  325. {trainingState.currentEpoch}/{trainingState.totalEpochs} 轮
  326. </span>
  327. </div>
  328. <Progress value={trainingState.progress} className="h-2" />
  329. </div>
  330. {/* Current Metrics */}
  331. {currentMetrics && (
  332. <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
  333. <div className="text-center p-3 bg-muted rounded-lg">
  334. <div className="text-lg font-bold text-destructive">{currentMetrics.loss.toFixed(4)}</div>
  335. <div className="text-xs text-muted-foreground">训练损失</div>
  336. </div>
  337. <div className="text-center p-3 bg-muted rounded-lg">
  338. <div className="text-lg font-bold text-primary">
  339. {(currentMetrics.accuracy * 100).toFixed(1)}%
  340. </div>
  341. <div className="text-xs text-muted-foreground">训练准确率</div>
  342. </div>
  343. <div className="text-center p-3 bg-muted rounded-lg">
  344. <div className="text-lg font-bold text-destructive">{currentMetrics.valLoss.toFixed(4)}</div>
  345. <div className="text-xs text-muted-foreground">验证损失</div>
  346. </div>
  347. <div className="text-center p-3 bg-muted rounded-lg">
  348. <div className="text-lg font-bold text-primary">
  349. {(currentMetrics.valAccuracy * 100).toFixed(1)}%
  350. </div>
  351. <div className="text-xs text-muted-foreground">验证准确率</div>
  352. </div>
  353. </div>
  354. )}
  355. </div>
  356. </CardContent>
  357. </Card>
  358. {/* Training Charts */}
  359. <Card>
  360. <CardHeader>
  361. <CardTitle className="font-serif">训练曲线</CardTitle>
  362. <CardDescription>实时监控模型训练过程中的性能指标</CardDescription>
  363. </CardHeader>
  364. <CardContent>
  365. <Tabs defaultValue="loss" className="w-full">
  366. <TabsList className="grid w-full grid-cols-2">
  367. <TabsTrigger value="loss">损失函数</TabsTrigger>
  368. <TabsTrigger value="accuracy">准确率</TabsTrigger>
  369. </TabsList>
  370. <TabsContent value="loss" className="space-y-4">
  371. <div className="h-64 bg-muted/30 rounded-lg p-4 relative overflow-hidden">
  372. <div className="absolute inset-4">
  373. <svg className="w-full h-full" viewBox="0 0 400 200" preserveAspectRatio="none">
  374. {/* Grid lines */}
  375. <defs>
  376. <pattern id="grid" width="40" height="20" patternUnits="userSpaceOnUse">
  377. <path
  378. d="M 40 0 L 0 0 0 20"
  379. fill="none"
  380. stroke="currentColor"
  381. strokeWidth="0.5"
  382. opacity="0.1"
  383. />
  384. </pattern>
  385. </defs>
  386. <rect width="100%" height="100%" fill="url(#grid)" />
  387. {/* Loss curves */}
  388. {trainingState.metrics.length > 1 && (
  389. <>
  390. {/* Training loss */}
  391. <polyline
  392. fill="none"
  393. stroke="rgb(239 68 68)"
  394. strokeWidth="2"
  395. points={trainingState.metrics
  396. .map(
  397. (m, i) =>
  398. `${(i / Math.max(trainingState.metrics.length - 1, 1)) * 400},${200 - (m.loss / maxLoss) * 180}`,
  399. )
  400. .join(" ")}
  401. />
  402. {/* Validation loss */}
  403. <polyline
  404. fill="none"
  405. stroke="rgb(249 115 22)"
  406. strokeWidth="2"
  407. strokeDasharray="5,5"
  408. points={trainingState.metrics
  409. .map(
  410. (m, i) =>
  411. `${(i / Math.max(trainingState.metrics.length - 1, 1)) * 400},${200 - (m.valLoss / maxLoss) * 180}`,
  412. )
  413. .join(" ")}
  414. />
  415. </>
  416. )}
  417. </svg>
  418. </div>
  419. <div className="absolute bottom-2 left-4 flex gap-4 text-xs">
  420. <div className="flex items-center gap-1">
  421. <div className="w-3 h-0.5 bg-red-500"></div>
  422. <span>训练损失</span>
  423. </div>
  424. <div className="flex items-center gap-1">
  425. <div className="w-3 h-0.5 bg-orange-500 border-dashed"></div>
  426. <span>验证损失</span>
  427. </div>
  428. </div>
  429. </div>
  430. </TabsContent>
  431. <TabsContent value="accuracy" className="space-y-4">
  432. <div className="h-64 bg-muted/30 rounded-lg p-4 relative overflow-hidden">
  433. <div className="absolute inset-4">
  434. <svg className="w-full h-full" viewBox="0 0 400 200" preserveAspectRatio="none">
  435. <rect width="100%" height="100%" fill="url(#grid)" />
  436. {/* Accuracy curves */}
  437. {trainingState.metrics.length > 1 && (
  438. <>
  439. {/* Training accuracy */}
  440. <polyline
  441. fill="none"
  442. stroke="rgb(34 197 94)"
  443. strokeWidth="2"
  444. points={trainingState.metrics
  445. .map(
  446. (m, i) =>
  447. `${(i / Math.max(trainingState.metrics.length - 1, 1)) * 400},${200 - m.accuracy * 180}`,
  448. )
  449. .join(" ")}
  450. />
  451. {/* Validation accuracy */}
  452. <polyline
  453. fill="none"
  454. stroke="rgb(59 130 246)"
  455. strokeWidth="2"
  456. strokeDasharray="5,5"
  457. points={trainingState.metrics
  458. .map(
  459. (m, i) =>
  460. `${(i / Math.max(trainingState.metrics.length - 1, 1)) * 400},${200 - m.valAccuracy * 180}`,
  461. )
  462. .join(" ")}
  463. />
  464. </>
  465. )}
  466. </svg>
  467. </div>
  468. <div className="absolute bottom-2 left-4 flex gap-4 text-xs">
  469. <div className="flex items-center gap-1">
  470. <div className="w-3 h-0.5 bg-green-500"></div>
  471. <span>训练准确率</span>
  472. </div>
  473. <div className="flex items-center gap-1">
  474. <div className="w-3 h-0.5 bg-blue-500 border-dashed"></div>
  475. <span>验证准确率</span>
  476. </div>
  477. </div>
  478. </div>
  479. </TabsContent>
  480. </Tabs>
  481. </CardContent>
  482. </Card>
  483. </div>
  484. </div>
  485. {/* Next Step Button */}
  486. <div className="flex justify-end">
  487. <Button size="lg" disabled={trainingState.phase !== "completed"} className="font-semibold" onClick={handleNext}>
  488. 继续到图像生成
  489. <Zap className="w-4 h-4 ml-2" />
  490. </Button>
  491. </div>
  492. </div>
  493. )
  494. }