image
This commit is contained in:
181
script/compare_factor.ts
Normal file
181
script/compare_factor.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import Bun from 'bun';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv,
|
||||
options: {
|
||||
workingDir: { type: "string" },
|
||||
dataDir: { type: "string" },
|
||||
script: { type: "string" },
|
||||
},
|
||||
strict: true,
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (!values.workingDir || !values.dataDir || !values.script) {
|
||||
console.error("Usage: bun run script/compare_factor.ts --workingDir <dir> --dataDir <dir> --script <python_script>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const workingDir = values.workingDir;
|
||||
const dataDir = values.dataDir;
|
||||
const predictScript = values.script;
|
||||
const tempFileName = "temp.json";
|
||||
const tempFilePath = path.join(workingDir, tempFileName);
|
||||
|
||||
// 1. 전처리 실행 (Factor 기반)
|
||||
console.log("Step 1: Running factor preprocessing to temp.json...");
|
||||
const preprocessResult = Bun.spawnSync([
|
||||
"bun", "run", "script/preprocess_factor.ts",
|
||||
"--workingDir", workingDir,
|
||||
"--dataDir", dataDir,
|
||||
"--fileName", tempFileName
|
||||
]);
|
||||
|
||||
if (!preprocessResult.success) {
|
||||
console.error("Preprocessing failed");
|
||||
console.error(preprocessResult.stderr.toString());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. measure.csv 로드
|
||||
console.log("Step 2: Loading measure.csv...");
|
||||
const measurePath = path.join(dataDir, "measure.csv");
|
||||
const measureContent = fs.readFileSync(measurePath, "utf-8");
|
||||
const measureMap = new Map<string, number>();
|
||||
|
||||
measureContent.split("\n").forEach((line, index) => {
|
||||
if (index === 0 || !line.trim()) return;
|
||||
const parts = line.split(",");
|
||||
if (parts.length >= 3) {
|
||||
const constant = parts[0];
|
||||
const songno = parts[1];
|
||||
const diff = parts[2];
|
||||
measureMap.set(`${songno.trim()}_${diff.trim()}`, parseFloat(constant));
|
||||
}
|
||||
});
|
||||
|
||||
// 3. temp.json 로드하여 대상 곡 목록 추출
|
||||
const factors = JSON.parse(fs.readFileSync(tempFilePath, "utf-8"));
|
||||
const uniqueSongnos = Array.from(new Set(factors.map((f: any) => f.songno)));
|
||||
|
||||
// 4. 예측 및 비교
|
||||
console.log(`Step 3: Predicting and comparing ${uniqueSongnos.length} songs (Factor-based)...`);
|
||||
const comparisonResults: any[] = [];
|
||||
let processedCount = 0;
|
||||
|
||||
for (const songno of uniqueSongnos) {
|
||||
try {
|
||||
const predictProcess = Bun.spawnSync([
|
||||
"python3", predictScript,
|
||||
"--workingDir", workingDir,
|
||||
"--songno", songno as string,
|
||||
"--factor", tempFilePath // 파이썬 스크립트 인자명이 --factor로 고정되어 있는 경우를 가정
|
||||
]);
|
||||
|
||||
if (!predictProcess.success) {
|
||||
console.error(`\n[ERROR] Failed to predict songno ${songno}`);
|
||||
processedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const output = predictProcess.stdout.toString().trim();
|
||||
const jsonStart = output.indexOf('[');
|
||||
const jsonEnd = output.lastIndexOf(']') + 1;
|
||||
|
||||
if (jsonStart !== -1 && jsonEnd !== 0) {
|
||||
const predictions = JSON.parse(output.substring(jsonStart, jsonEnd));
|
||||
predictions.forEach((pred: any) => {
|
||||
const key = `${pred.songno}_${pred.diff}`;
|
||||
const actual = measureMap.get(key);
|
||||
if (actual !== undefined) {
|
||||
comparisonResults.push({
|
||||
songno: pred.songno,
|
||||
diff: pred.diff,
|
||||
actual: actual,
|
||||
predicted: pred.predicted,
|
||||
error: Math.abs(actual - pred.predicted)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processedCount++;
|
||||
if (processedCount % 10 === 0 || processedCount === uniqueSongnos.length) {
|
||||
const percent = ((processedCount / uniqueSongnos.length) * 100).toFixed(1);
|
||||
process.stdout.write(`\rProgress: ${processedCount}/${uniqueSongnos.length} (${percent}%) `);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`\nError processing songno ${songno}:`, err);
|
||||
processedCount++;
|
||||
}
|
||||
}
|
||||
console.log("\nPrediction finished.");
|
||||
|
||||
const avgError = comparisonResults.reduce((acc, curr) => acc + curr.error, 0) / comparisonResults.length;
|
||||
const resultData = {
|
||||
summary: {
|
||||
total_compared: comparisonResults.length,
|
||||
average_absolute_error: avgError,
|
||||
timestamp: new Date().toISOString(),
|
||||
script_used: predictScript,
|
||||
type: "factor"
|
||||
},
|
||||
details: comparisonResults.sort((a, b) => b.error - a.error)
|
||||
};
|
||||
|
||||
const comparePath = path.join(workingDir, "compare.json");
|
||||
fs.writeFileSync(comparePath, JSON.stringify(resultData, null, 2), "utf-8");
|
||||
|
||||
console.log(`\nComparison complete! Results saved to: ${comparePath}`);
|
||||
|
||||
// 6. 결과 시각화 (compare.png 생성)
|
||||
if (comparisonResults.length > 0) {
|
||||
console.log("Step 4: Generating comparison plot (compare.png)...");
|
||||
const plotPythonCode = `
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
import json
|
||||
import os
|
||||
|
||||
with open('${comparePath}', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not data['details']:
|
||||
print("No details to plot.")
|
||||
exit(0)
|
||||
|
||||
df = pd.DataFrame(data['details'])
|
||||
df['abs_error'] = (df['actual'] - df['predicted']).abs()
|
||||
df = df.sort_values('abs_error', ascending=False).reset_index(drop=True)
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.switch_backend('Agg')
|
||||
sns.scatterplot(x=df.index, y=df['abs_error'], alpha=0.6, s=20, color='royalblue')
|
||||
|
||||
plt.axhline(0.2, color='green', linestyle='--', linewidth=0.8, alpha=0.5, label='Target (0.2)')
|
||||
plt.axhline(0.5, color='blue', linestyle='--', linewidth=0.8, alpha=0.5)
|
||||
plt.axhline(1.0, color='red', linestyle='--', linewidth=0.8, alpha=0.5)
|
||||
|
||||
plt.ylim(0, max(3.5, df['abs_error'].max() + 0.5) if not df.empty else 3.5)
|
||||
plt.title('Comparison Absolute Error Distribution (Feature-based)', fontsize=14)
|
||||
plt.xlabel('Samples (Sorted by Error Magnitude)', fontsize=12)
|
||||
plt.ylabel('Absolute Error', fontsize=12)
|
||||
plt.grid(True, axis='y', alpha=0.3)
|
||||
|
||||
plot_path = os.path.join('${workingDir}', 'compare.png')
|
||||
plt.savefig(plot_path)
|
||||
print(f"Plot saved to: {plot_path}")
|
||||
`;
|
||||
|
||||
const plotProc = Bun.spawnSync(["python3", "-c", plotPythonCode]);
|
||||
if (plotProc.success) {
|
||||
console.log(plotProc.stdout.toString().trim());
|
||||
} else {
|
||||
console.error("Failed to generate plot:");
|
||||
console.error(plotProc.stderr.toString());
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ const { values } = parseArgs({
|
||||
});
|
||||
|
||||
if (!values.workingDir || !values.dataDir || !values.script) {
|
||||
console.error("Usage: bun run script/compare.ts --workingDir <dir> --dataDir <dir> --script <python_script>");
|
||||
console.error("Usage: bun run script/compare_feature.ts --workingDir <dir> --dataDir <dir> --script <python_script>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ const predictScript = values.script;
|
||||
const tempFileName = "temp.json";
|
||||
const tempFilePath = path.join(workingDir, tempFileName);
|
||||
|
||||
// 1. 전처리 실행 (temp.json 생성하여 기존 features.json 보존)
|
||||
console.log("Step 1: Running preprocessing to temp.json...");
|
||||
// 1. 전처리 실행 (Feature 기반)
|
||||
console.log("Step 1: Running feature preprocessing to temp.json...");
|
||||
const preprocessResult = Bun.spawnSync([
|
||||
"bun", "run", "script/preprocess.ts",
|
||||
"bun", "run", "script/preprocess_feature.ts",
|
||||
"--workingDir", workingDir,
|
||||
"--dataDir", dataDir,
|
||||
"--fileName", tempFileName
|
||||
@@ -62,7 +62,7 @@ const features = JSON.parse(fs.readFileSync(tempFilePath, "utf-8"));
|
||||
const uniqueSongnos = Array.from(new Set(features.map((f: any) => f.songno)));
|
||||
|
||||
// 4. 예측 및 비교
|
||||
console.log(`Step 3: Predicting and comparing ${uniqueSongnos.length} songs...`);
|
||||
console.log(`Step 3: Predicting and comparing ${uniqueSongnos.length} songs (Feature-based)...`);
|
||||
const comparisonResults: any[] = [];
|
||||
let processedCount = 0;
|
||||
|
||||
@@ -77,39 +77,31 @@ for (const songno of uniqueSongnos) {
|
||||
|
||||
if (!predictProcess.success) {
|
||||
console.error(`\n[ERROR] Failed to predict songno ${songno}`);
|
||||
console.error(predictProcess.stderr.toString());
|
||||
processedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const output = predictProcess.stdout.toString().trim();
|
||||
// JSON 부분만 추출 (경고문 등이 섞여있을 경우 대비)
|
||||
const jsonStart = output.indexOf('[');
|
||||
const jsonEnd = output.lastIndexOf(']') + 1;
|
||||
|
||||
if (jsonStart === -1 || jsonEnd === 0) {
|
||||
console.error(`\n[ERROR] Invalid output format for songno ${songno}`);
|
||||
processedCount++;
|
||||
continue;
|
||||
|
||||
if (jsonStart !== -1 && jsonEnd !== 0) {
|
||||
const predictions = JSON.parse(output.substring(jsonStart, jsonEnd));
|
||||
predictions.forEach((pred: any) => {
|
||||
const key = `${pred.songno}_${pred.diff}`;
|
||||
const actual = measureMap.get(key);
|
||||
if (actual !== undefined) {
|
||||
comparisonResults.push({
|
||||
songno: pred.songno,
|
||||
diff: pred.diff,
|
||||
actual: actual,
|
||||
predicted: pred.predicted,
|
||||
error: Math.abs(actual - pred.predicted)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const predictions = JSON.parse(output.substring(jsonStart, jsonEnd));
|
||||
|
||||
predictions.forEach((pred: any) => {
|
||||
const key = `${pred.songno}_${pred.diff}`;
|
||||
const actual = measureMap.get(key);
|
||||
|
||||
if (actual !== undefined) {
|
||||
comparisonResults.push({
|
||||
songno: pred.songno,
|
||||
diff: pred.diff,
|
||||
actual: actual,
|
||||
predicted: pred.predicted,
|
||||
error: Math.abs(actual - pred.predicted)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
processedCount++;
|
||||
if (processedCount % 10 === 0 || processedCount === uniqueSongnos.length) {
|
||||
const percent = ((processedCount / uniqueSongnos.length) * 100).toFixed(1);
|
||||
@@ -122,27 +114,68 @@ for (const songno of uniqueSongnos) {
|
||||
}
|
||||
console.log("\nPrediction finished.");
|
||||
|
||||
// 5. 결과 분석 및 저장
|
||||
if (comparisonResults.length === 0) {
|
||||
console.error("No comparison results were generated.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const avgError = comparisonResults.reduce((acc, curr) => acc + curr.error, 0) / comparisonResults.length;
|
||||
const resultData = {
|
||||
summary: {
|
||||
total_compared: comparisonResults.length,
|
||||
average_absolute_error: avgError,
|
||||
timestamp: new Date().toISOString(),
|
||||
script_used: predictScript
|
||||
script_used: predictScript,
|
||||
type: "feature"
|
||||
},
|
||||
details: comparisonResults.sort((a, b) => b.error - a.error)
|
||||
};
|
||||
|
||||
const comparePath = path.join(workingDir, "compare.json");
|
||||
const comparePath = path.join(workingDir, "compare_feature.json");
|
||||
fs.writeFileSync(comparePath, JSON.stringify(resultData, null, 2), "utf-8");
|
||||
|
||||
console.log(`\nComparison complete!`);
|
||||
console.log(`Total compared: ${comparisonResults.length}`);
|
||||
console.log(`Average Error: ${avgError.toFixed(4)}`);
|
||||
console.log(`Results saved to: ${comparePath}`);
|
||||
console.log(`\nComparison complete! Results saved to: ${comparePath}`);
|
||||
|
||||
// 6. 결과 시각화 (compare.png 생성)
|
||||
if (comparisonResults.length > 0) {
|
||||
console.log("Step 4: Generating comparison plot (compare.png)...");
|
||||
const plotPythonCode = `
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
import json
|
||||
import os
|
||||
|
||||
with open('${comparePath}', 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
if not data['details']:
|
||||
print("No details to plot.")
|
||||
exit(0)
|
||||
|
||||
df = pd.DataFrame(data['details'])
|
||||
df['abs_error'] = (df['actual'] - df['predicted']).abs()
|
||||
df = df.sort_values('abs_error', ascending=False).reset_index(drop=True)
|
||||
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.switch_backend('Agg')
|
||||
sns.scatterplot(x=df.index, y=df['abs_error'], alpha=0.6, s=20, color='royalblue')
|
||||
|
||||
plt.axhline(0.2, color='green', linestyle='--', linewidth=0.8, alpha=0.5, label='Target (0.2)')
|
||||
plt.axhline(0.5, color='blue', linestyle='--', linewidth=0.8, alpha=0.5)
|
||||
plt.axhline(1.0, color='red', linestyle='--', linewidth=0.8, alpha=0.5)
|
||||
|
||||
plt.ylim(0, max(3.5, df['abs_error'].max() + 0.5) if not df.empty else 3.5)
|
||||
plt.title('Comparison Absolute Error Distribution (Feature-based)', fontsize=14)
|
||||
plt.xlabel('Samples (Sorted by Error Magnitude)', fontsize=12)
|
||||
plt.ylabel('Absolute Error', fontsize=12)
|
||||
plt.grid(True, axis='y', alpha=0.3)
|
||||
|
||||
plot_path = os.path.join('${workingDir}', 'compare.png')
|
||||
plt.savefig(plot_path)
|
||||
print(f"Plot saved to: {plot_path}")
|
||||
`;
|
||||
|
||||
const plotProc = Bun.spawnSync(["python3", "-c", plotPythonCode]);
|
||||
if (plotProc.success) {
|
||||
console.log(plotProc.stdout.toString().trim());
|
||||
} else {
|
||||
console.error("Failed to generate plot:");
|
||||
console.error(plotProc.stderr.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import Bun from 'bun';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
import fs from 'node:fs';
|
||||
import { featurize } from '../preprocess/featurize';
|
||||
import { parseTja } from '../preprocess/parse';
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv,
|
||||
options: {
|
||||
tja: {
|
||||
type: "string"
|
||||
},
|
||||
workingDir: {
|
||||
type: "string"
|
||||
},
|
||||
script: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (!values.tja || !values.workingDir || !values.script) {
|
||||
console.error("Usage: bun script/predict_tja.ts --tja <path_to_tja> [--workingDir <dir>] [--script <python_script>]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tjaPath = values.tja;
|
||||
if (!fs.existsSync(tjaPath)) {
|
||||
console.error(`File not found: ${tjaPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const workingDir = values.workingDir!;
|
||||
const pythonScript = values.script!;
|
||||
|
||||
if (!fs.existsSync(pythonScript)) {
|
||||
console.error(`Python script not found: ${pythonScript}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 1. TJA 파싱 및 피처 추출
|
||||
const tjaContent = fs.readFileSync(tjaPath, 'utf-8');
|
||||
const songno = path.basename(tjaPath, '.tja');
|
||||
const parsed = parseTja(tjaContent);
|
||||
|
||||
const features: any[] = [];
|
||||
if (parsed.oni) {
|
||||
features.push({
|
||||
songno,
|
||||
difficulty: 'oni',
|
||||
...featurize(parsed.oni)
|
||||
});
|
||||
}
|
||||
if (parsed.edit) {
|
||||
features.push({
|
||||
songno,
|
||||
difficulty: 'ura',
|
||||
...featurize(parsed.edit)
|
||||
});
|
||||
}
|
||||
|
||||
if (features.length === 0) {
|
||||
console.error("No Oni or Ura difficulty found in the TJA file.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. 임시 피처 파일 저장
|
||||
const tempFeaturePath = path.join(workingDir, `temp_predict_${Date.now()}.json`);
|
||||
fs.writeFileSync(tempFeaturePath, JSON.stringify(features, null, 2), 'utf-8');
|
||||
|
||||
try {
|
||||
// 3. Python 예측 스크립트 실행
|
||||
const proc = Bun.spawnSync([
|
||||
"python3",
|
||||
pythonScript,
|
||||
"--workingDir", workingDir,
|
||||
"--feature", tempFeaturePath,
|
||||
"--songno", songno
|
||||
]);
|
||||
|
||||
if (proc.success) {
|
||||
console.log(proc.stdout.toString());
|
||||
} else {
|
||||
console.error("Prediction failed:");
|
||||
console.error(proc.stderr.toString());
|
||||
}
|
||||
} finally {
|
||||
// 4. 임시 파일 삭제
|
||||
if (fs.existsSync(tempFeaturePath)) {
|
||||
fs.unlinkSync(tempFeaturePath);
|
||||
}
|
||||
}
|
||||
68
script/predict_tja_factor.ts
Normal file
68
script/predict_tja_factor.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import Bun from 'bun';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
import fs from 'node:fs';
|
||||
import { factorize } from '../preprocess/factorize';
|
||||
import { parseTja } from '../preprocess/parse';
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv,
|
||||
options: {
|
||||
tja: { type: "string" },
|
||||
workingDir: { type: "string" },
|
||||
script: { type: "string" }
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (!values.tja || !values.workingDir || !values.script) {
|
||||
console.error("Usage: bun script/predict_tja_factor.ts --tja <path_to_tja> --workingDir <dir> --script <python_script>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tjaPath = values.tja;
|
||||
const workingDir = values.workingDir!;
|
||||
const pythonScript = values.script!;
|
||||
|
||||
const tjaContent = fs.readFileSync(tjaPath, 'utf-8');
|
||||
const songno = path.basename(tjaPath, '.tja');
|
||||
const parsed = parseTja(tjaContent);
|
||||
|
||||
const factors: any[] = [];
|
||||
if (parsed.oni) {
|
||||
factors.push({
|
||||
songno,
|
||||
difficulty: 'oni',
|
||||
factors: factorize(parsed.oni)
|
||||
});
|
||||
}
|
||||
if (parsed.edit) {
|
||||
factors.push({
|
||||
songno,
|
||||
difficulty: 'ura',
|
||||
factors: factorize(parsed.edit)
|
||||
});
|
||||
}
|
||||
|
||||
if (factors.length === 0) {
|
||||
console.error("No Oni or Ura difficulty found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tempFactorPath = path.join(workingDir, `temp_predict_fact_${Date.now()}.json`);
|
||||
fs.writeFileSync(tempFactorPath, JSON.stringify(factors, null, 2), 'utf-8');
|
||||
|
||||
try {
|
||||
const proc = Bun.spawnSync([
|
||||
"python3",
|
||||
pythonScript,
|
||||
"--workingDir", workingDir,
|
||||
"--feature", tempFactorPath, // 파이썬 스크립트 인자명이 --feature로 고정되어 있다고 가정
|
||||
"--songno", songno
|
||||
]);
|
||||
|
||||
if (proc.success) console.log(proc.stdout.toString());
|
||||
else console.error("Prediction failed:", proc.stderr.toString());
|
||||
} finally {
|
||||
if (fs.existsSync(tempFactorPath)) fs.unlinkSync(tempFactorPath);
|
||||
}
|
||||
49
script/predict_tja_feature.ts
Normal file
49
script/predict_tja_feature.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import Bun from 'bun';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
import fs from 'node:fs';
|
||||
import { featurize } from '../preprocess/featurize';
|
||||
import { parseTja } from '../preprocess/parse';
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv,
|
||||
options: {
|
||||
tja: { type: "string" },
|
||||
workingDir: { type: "string" },
|
||||
script: { type: "string" }
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (!values.tja || !values.workingDir || !values.script) {
|
||||
console.error("Usage: bun script/predict_tja_feature.ts --tja <path_to_tja> --workingDir <dir> --script <python_script>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tjaPath = values.tja;
|
||||
const workingDir = values.workingDir!;
|
||||
const pythonScript = values.script!;
|
||||
|
||||
const tjaContent = fs.readFileSync(tjaPath, 'utf-8');
|
||||
const songno = path.basename(tjaPath, '.tja');
|
||||
const parsed = parseTja(tjaContent);
|
||||
|
||||
const features: any[] = [];
|
||||
if (parsed.oni) features.push({ songno, difficulty: 'oni', ...featurize(parsed.oni) });
|
||||
if (parsed.edit) features.push({ songno, difficulty: 'ura', ...featurize(parsed.edit) });
|
||||
|
||||
if (features.length === 0) {
|
||||
console.error("No Oni or Ura difficulty found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tempFeaturePath = path.join(workingDir, `temp_predict_feat_${Date.now()}.json`);
|
||||
fs.writeFileSync(tempFeaturePath, JSON.stringify(features, null, 2), 'utf-8');
|
||||
|
||||
try {
|
||||
const proc = Bun.spawnSync(["python3", pythonScript, "--workingDir", workingDir, "--feature", tempFeaturePath, "--songno", songno]);
|
||||
if (proc.success) console.log(proc.stdout.toString());
|
||||
else console.error("Prediction failed:", proc.stderr.toString());
|
||||
} finally {
|
||||
if (fs.existsSync(tempFeaturePath)) fs.unlinkSync(tempFeaturePath);
|
||||
}
|
||||
65
script/preprocess_factor.ts
Normal file
65
script/preprocess_factor.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import Bun from 'bun';
|
||||
import path from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
import fs, { mkdirSync } from 'node:fs';
|
||||
import { factorize } from '../preprocess/factorize';
|
||||
import { parseTja } from '../preprocess/parse'
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv,
|
||||
options: {
|
||||
workingDir: {
|
||||
type: "string"
|
||||
},
|
||||
dataDir: {
|
||||
type: "string"
|
||||
},
|
||||
fileName: {
|
||||
type: "string"
|
||||
}
|
||||
},
|
||||
allowPositionals: true,
|
||||
})
|
||||
|
||||
if (!values.dataDir || !values.workingDir) {
|
||||
console.error("--workingDir --dataDir --fileName");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const workingDir = values.workingDir ?? '';
|
||||
if (!fs.existsSync(workingDir)) mkdirSync(workingDir)
|
||||
const dataDir = values.dataDir ?? '';
|
||||
|
||||
const tjaDir = path.join(dataDir, 'tja');
|
||||
const files = fs.readdirSync(tjaDir);
|
||||
|
||||
const results: any[] = [];
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.tja')) continue;
|
||||
const tja = fs.readFileSync(path.join(tjaDir, file), 'utf-8');
|
||||
const songno = path.basename(file, '.tja');
|
||||
try {
|
||||
const parsed = parseTja(tja);
|
||||
const courses = [
|
||||
{ diff: 'oni', data: parsed?.oni },
|
||||
{ diff: 'ura', data: parsed?.edit }
|
||||
];
|
||||
|
||||
for (const course of courses) {
|
||||
if (course.data) {
|
||||
results.push({
|
||||
songno,
|
||||
difficulty: course.diff,
|
||||
factors: factorize(course.data)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`[Error] ${file}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
const outputPath = path.join(workingDir, values.fileName ?? 'factors.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(results, null, 2), 'utf-8');
|
||||
console.log(`Successfully saved factors to ${outputPath}`);
|
||||
106
script/train_factor.ts
Normal file
106
script/train_factor.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import Bun from 'bun';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { parseArgs } from 'node:util';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { factorize } from '../preprocess/factorize';
|
||||
import { parseTja } from '../preprocess/parse'
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Bun.argv,
|
||||
options: {
|
||||
workingDir: {
|
||||
type: "string"
|
||||
},
|
||||
dataDir: {
|
||||
type: "string"
|
||||
},
|
||||
script: {
|
||||
type: "string"
|
||||
},
|
||||
trainSize: {
|
||||
type: 'string'
|
||||
},
|
||||
validSize: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
allowPositionals: true,
|
||||
});
|
||||
|
||||
if (!values.dataDir || !values.workingDir || !values.script) {
|
||||
console.error("Usage: bun run script/train_factor.ts --workingDir <dir> --dataDir <dir> --script <python_script> --trainSize <num> --validSize <num>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 1. factors.json 생성 (없을 경우)
|
||||
generateFactors();
|
||||
|
||||
// 2. 파이썬 학습 스크립트 실행
|
||||
console.log(`Starting training with factor-based script: ${values.script}`);
|
||||
const child = spawn("python3", [
|
||||
values.script,
|
||||
"--workingDir", values.workingDir,
|
||||
"--dataDir", values.dataDir,
|
||||
"--trainSize", (Number(values.trainSize) || 1000).toString(),
|
||||
"--validSize", (Number(values.validSize) || 200).toString(),
|
||||
]);
|
||||
|
||||
child.stdout.pipe(process.stdout);
|
||||
child.stderr.pipe(process.stderr);
|
||||
|
||||
child.on("close", (code) => {
|
||||
console.log(`Training process exited with code ${code}`);
|
||||
process.exit(code || 0);
|
||||
});
|
||||
|
||||
// functions
|
||||
function generateFactors() {
|
||||
const workingDir = values.workingDir ?? '';
|
||||
if (!fs.existsSync(workingDir)) fs.mkdirSync(workingDir, { recursive: true });
|
||||
|
||||
const factorsPath = path.join(workingDir, 'factors.json');
|
||||
if (fs.existsSync(factorsPath)) {
|
||||
console.log('factors.json already exists, skipping generation.');
|
||||
return;
|
||||
}
|
||||
|
||||
const dataDir = values.dataDir ?? '';
|
||||
const tjaDir = path.join(dataDir, 'tja');
|
||||
if (!fs.existsSync(tjaDir)) {
|
||||
console.error(`TJA directory not found: ${tjaDir}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(tjaDir).filter(f => f.endsWith('.tja'));
|
||||
console.log(`Generating factors from ${files.length} TJA files...`);
|
||||
|
||||
const results: any[] = [];
|
||||
for (const file of files) {
|
||||
try {
|
||||
const tja = fs.readFileSync(path.join(tjaDir, file), 'utf-8');
|
||||
const songno = path.basename(file, '.tja');
|
||||
const parsed = parseTja(tja);
|
||||
|
||||
const courses = [
|
||||
{ diff: 'oni', data: parsed?.oni },
|
||||
{ diff: 'ura', data: parsed?.edit }
|
||||
];
|
||||
|
||||
for (const course of courses) {
|
||||
if (course.data) {
|
||||
results.push({
|
||||
songno,
|
||||
difficulty: course.diff,
|
||||
factors: factorize(course.data)
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error processing ${file}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(factorsPath, JSON.stringify(results, null, 2), 'utf-8');
|
||||
console.log(`factors.json generated at ${factorsPath}`);
|
||||
}
|
||||
Reference in New Issue
Block a user