.
This commit is contained in:
108
script/train.ts
Normal file
108
script/train.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { parseTja } from './parse';
|
||||
import { factorize, Factors } from './factorize';
|
||||
import { join } from 'path';
|
||||
import { readFileSync, writeFileSync, readdirSync } from 'fs';
|
||||
import { parse } from 'csv-parse/sync';
|
||||
|
||||
async function train() {
|
||||
const args = process.argv.slice(2);
|
||||
const workingDir = args[0];
|
||||
const scriptDir = args[1];
|
||||
const dataDir = args[2];
|
||||
const trainCount = parseInt(args[3]) || 100;
|
||||
const validateCount = parseInt(args[4]) || 20;
|
||||
const margin = parseFloat(args[5]) || 0.1;
|
||||
|
||||
const factorJsonPath = join(scriptDir, 'factor.json');
|
||||
// 항상 script/ 위치 참조
|
||||
let weights: Factors = JSON.parse(readFileSync(factorJsonPath, 'utf-8'));
|
||||
|
||||
// 결과물(로그 등)을 저장할 경로 (필요 시 활용)
|
||||
const logPath = join(workingDir, 'training_log.txt');
|
||||
|
||||
const measureCsv = readFileSync(join(dataDir, 'measure.csv'), 'utf-8');
|
||||
const records = parse(measureCsv, { columns: true, skip_empty_lines: true });
|
||||
|
||||
const tjaFiles = readdirSync(join(dataDir, 'tja')).filter(f => f.endsWith('.tja'));
|
||||
const shuffled = tjaFiles.sort(() => 0.5 - Math.random());
|
||||
const trainFiles = shuffled.slice(0, trainCount);
|
||||
const validateFiles = shuffled.slice(trainCount, trainCount + validateCount);
|
||||
|
||||
console.log(`Training with ${trainFiles.length} files...`);
|
||||
|
||||
// 학습 로직 (단순화된 경사 하강법 또는 반복 최적화)
|
||||
let error = Infinity;
|
||||
let iterations = 0;
|
||||
while (error > margin && iterations < 100) {
|
||||
let totalError = 0;
|
||||
let count = 0;
|
||||
|
||||
for (const file of trainFiles) {
|
||||
const songno = file.replace(/\D/g, '');
|
||||
const tjaContent = readFileSync(join(dataDir, 'tja', file), 'utf-8');
|
||||
const parsed = parseTja(tjaContent);
|
||||
if (!parsed) continue;
|
||||
|
||||
for (const diff of ['oni', 'edit'] as const) {
|
||||
const course = parsed[diff];
|
||||
if (!course) continue;
|
||||
|
||||
const target = records.find((r: any) => r.songno === songno && (r.diff === (diff === 'oni' ? 'oni' : 'ura')));
|
||||
if (!target) {
|
||||
// console.log(`[!] No target for ${songno} diff=${diff}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const factors = factorize(course);
|
||||
const prediction = Object.keys(factors).reduce((sum, key) =>
|
||||
sum + (factors[key as keyof Factors] * weights[key as keyof Factors]), 0);
|
||||
|
||||
const targetValue = parseFloat(target.상수);
|
||||
const diff_val = targetValue - prediction;
|
||||
totalError += Math.abs(diff_val);
|
||||
count++;
|
||||
|
||||
// 가중치 업데이트
|
||||
for (const key in weights) {
|
||||
const k = key as keyof Factors;
|
||||
weights[k] += diff_val * factors[k] * 0.05; // 학습률 조정
|
||||
}
|
||||
}
|
||||
}
|
||||
error = totalError / (count || 1);
|
||||
console.log(`Iteration ${iterations}: Mean Error = ${error.toFixed(4)}`);
|
||||
iterations++;
|
||||
}
|
||||
|
||||
writeFileSync(factorJsonPath, JSON.stringify(weights, null, 2));
|
||||
writeFileSync(join(workingDir, 'training_result.json'), JSON.stringify({ finalError: error, weights }, null, 2));
|
||||
console.log(`Training complete. Weights saved to ${factorJsonPath}, result saved to ${workingDir}`);
|
||||
|
||||
// 검증 로직
|
||||
console.log('\nValidation Results:');
|
||||
for (const file of validateFiles) {
|
||||
const songno = file.replace(/\D/g, '');
|
||||
const tjaContent = readFileSync(join(dataDir, 'tja', file), 'utf-8');
|
||||
const parsed = parseTja(tjaContent);
|
||||
if (!parsed) continue;
|
||||
|
||||
for (const diff of ['oni', 'edit'] as const) {
|
||||
const course = parsed[diff];
|
||||
if (!course) continue;
|
||||
|
||||
const target = records.find((r: any) => r.songno === songno && (r.diff === (diff === 'oni' ? 'oni' : 'ura')));
|
||||
if (!target) {
|
||||
console.log(`[!] No match for ${songno} diff=${diff === 'oni' ? 'oni' : 'ura'}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const factors = factorize(course);
|
||||
const prediction = Object.keys(factors).reduce((sum, key) =>
|
||||
sum + (factors[key as keyof Factors] * weights[key as keyof Factors]), 0);
|
||||
|
||||
console.log(`[${songno}] Target: ${target.상수}, Predicted: ${prediction.toFixed(2)}, Diff: ${Math.abs(parseFloat(target.상수) - prediction).toFixed(2)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
train();
|
||||
Reference in New Issue
Block a user