오픈튜토리얼스 8.13 ~ 8.26 머신러닝야학에서 배운
레몬에이드 판매량 예측, 보스턴 집값 예측, 붓꽃 분류를 tensorflow.js로 만들었습니다.
오픈튜토리얼스 머신러닝야학 내용은 아래 링크를 참조해 주세요!
https://opentutorials.org/course/4570
nodejs version: 12.18.3
tensorflow.js version: 2.3.0
전체 코드
const bostonPriceCSV = tf.data.csv(
'file://' + path.join(__dirname, '../data/boston.csv'),
{ columnConfigs: { medv: { isLabel: true } } }
)
// tf.data.Dataset 으로 변환
const bostonPriceDataset = bostonPriceCSV.map(({ xs, ys }) => {
return { xs: Object.values(xs), ys: Object.values(ys) }
}).batch(32).shuffle(10)
// 모델 생성
const input = tf.input({ shape: [13] })
let hidden = tf.layers.dense({ units: 8 }).apply(input)
hidden = tf.layers.layerNormalization().apply(hidden)
hidden = tf.layers.activation({ activation: 'selu' }).apply(hidden)
hidden = tf.layers.dense({ units: 8 }).apply(hidden)
hidden = tf.layers.layerNormalization().apply(hidden)
hidden = tf.layers.activation({ activation: 'selu' }).apply(hidden)
hidden = tf.layers.dense({ units: 8 }).apply(hidden)
hidden = tf.layers.layerNormalization().apply(hidden)
hidden = tf.layers.activation({ activation: 'selu' }).apply(hidden)
const output = tf.layers.dense({ units: 1 }).apply(hidden)
const model = tf.model({
inputs: input,
outputs: output
})
model.compile({ optimizer: tf.train.adam(0.001), loss: tf.losses.meanSquaredError })
model.summary()
// 모델 학습
await model.fitDataset(bostonPriceDataset, {
epochs: 1000
})
await model.fitDataset(bostonPriceDataset, {
epochs: 10,
callbacks: {
onEpochEnd: async (epoch, logs) => {
console.log(epoch + ':' + logs.loss);
}
}
})
// 테스트 데이터 생성
const testDataset = await createTestDataSet(bostonPriceDataset, 0, 0, 5)
// 보스터 집값 예측
model.predict(testDataset.xs).print()
testDataset.ys.print()
model.getWeights()[0].print()
CSV 파일 읽어오기
레몬에이드 판매량 예측에서는 tf.data.csv 함수를 사용하지 않고 readCSV 함수를 만들어 사용했습니다.
tf.data.csv 함수는
'file://' + path.join(__dirname, '../data/boston.csv') 같이 'file://'를 꼭 붙여 주어야 합니다..
path.join 안에 "file://" 을 넣으면 에러가 발생합니다..
참고로 tensorflow.js API Reference 에서 tf.data.csv 함수는
https://storage.googleapis.com/tfjs-examples/multivariate-linear-regression/data/boston-housing-train.csv
와 같은 형태의 URI 절대 주소를 사용합니다.
columnConfigs 부분은 medv 열이 종속변수임을 알려줍니다.
const bostonPriceCSV = tf.data.csv(
'file://' + path.join(__dirname, '../data/boston.csv'),
{ columnConfigs: { medv: { isLabel: true } } }
)
// tf.data.Dataset 으로 변환
const bostonPriceDataset = bostonPriceCSV.map(({ xs, ys }) => {
return { xs: Object.values(xs), ys: Object.values(ys) }
}).batch(32).shuffle(10)
모델 학습을 위해 tf.data.Dataset으로 변환합니다. 배치 사이즈는 32이고 셔플 시드는 10으로 지정하였습니다.
batch와 shuffle을 한 이유는 학습 속도를 향상 시키기 위해서 입니다. 자세한 부분은 더 공부해야 합니다...ㅠㅠ
모델 만들기
한 히든 레이어를 3개로 나누었습니다. 활성화 함수는 swish를 사용하려 하였으나
tensorflow.js에 없어서 대신 selu를 사용하였습니다.
BatchNormalization을 처음에 사용하였는데 데이터 크기가 작아서인지 정확도가 많이 떨어졌습니다.
그래서 LayerNormalization을 사용했는데 정확도가 향상되었습니다.
이 두 정규화도 차이점이 무엇인지 공부해야 합니다.
// 모델 생성
const input = tf.input({ shape: [13] })
let hidden = tf.layers.dense({ units: 8 }).apply(input)
hidden = tf.layers.layerNormalization().apply(hidden)
hidden = tf.layers.activation({ activation: 'selu' }).apply(hidden)
hidden = tf.layers.dense({ units: 8 }).apply(hidden)
hidden = tf.layers.layerNormalization().apply(hidden)
hidden = tf.layers.activation({ activation: 'selu' }).apply(hidden)
hidden = tf.layers.dense({ units: 8 }).apply(hidden)
hidden = tf.layers.layerNormalization().apply(hidden)
hidden = tf.layers.activation({ activation: 'selu' }).apply(hidden)
const output = tf.layers.dense({ units: 1 }).apply(hidden)
const model = tf.model({
inputs: input,
outputs: output
})
model.compile({ optimizer: tf.train.adam(0.001), loss: tf.losses.meanSquaredError })
model.summary()
optimizer는 adam으로 학습률은 0.001로 하였습니다. 손실함수는 평균 제곱근 편차를 사용했습니다.
모델 학습
여기서 파이썬에서 model.fit()을 사용하면 epoch 를 몇 번 학습시켰는지 알 수 있습니다.
하지만 자바스크립트에서는 콜백함수 onEpochEnd를 설정해야
한 epoch가 끝나고 epoch 순서와 손실을 알 수 있습니다.
여기서 tfjs-vis API를 프론트엔드에서 사용하면 실시간으로 진행 상황을 확인할 수 있습니다.
// 모델 학습
await model.fitDataset(bostonPriceDataset, {
epochs: 1000
})
await model.fitDataset(bostonPriceDataset, {
epochs: 10,
callbacks: {
onEpochEnd: async (epoch, logs) => {
console.log(epoch + ':' + logs.loss);
}
}
})
테스트 데이터 생성
따로 테스트 데이터셋을 준비해야하지만 기존 데이터셋에서 테스트 데이터를 생성하여 사용합니다...
// 테스트 데이터 생성
const testDataset = await createTestDataSet(bostonPriceDataset, 0, 0, 5)
createTestDataSet은 기존 데이터셋에서 batchNumber와 범위를 정해 tensor들을 추출합니다.
tf.tidy 함수에서 생성된 tf는 함수 종료 후 없어져 메모리 활용도를 높입니다.
tf.gather 함수는 2차원 tensor에서 1차원 tensor들을 추출하는데 사용됩니다.
/**
* features와 label을 tensor2d로 반환합니다.
* @param {tf.data.Dataset} DataSet
* @param {Integer} batchNumber
* @param {Integer} start
* @param {Integer} end
*/
const createTestDataSet = async (DataSet, batchNumber, start, end) => {
if (start > end) {
throw new Error("Error: 'start' is bigger than 'end'")
}
if (start < 0 || end < 0) {
throw new Error("Error: 'start' and 'end' start from zero")
}
const dataArr = await DataSet.toArrayForTest()
if (dataArr.length < batchNumber) {
throw new Error("Error: Exceed batch number")
}
const batchSize = dataArr[batchNumber].xs.shape[0]
if (end > batchSize) {
throw new Error('Error: Exceed batch size')
}
let range = []
for (let i = start; i < end; i++) {
range.push(i)
}
const tensor1dRange = tf.tensor1d(range, 'int32')
const xs = tf.tidy(() => {
const tensor2dXs = dataArr[batchNumber].xs
const XsTestDataset = tf.gather(tensor2dXs, tensor1dRange)
return XsTestDataset
})
const ys = tf.tidy(() => {
const tensor2dYs = dataArr[batchNumber].ys
const YsTestDataset = tf.gather(tensor2dYs, tensor1dRange)
return YsTestDataset
})
return { xs, ys }
}
보스턴 집값 예측
getweights()[0] 은 첫번째 레이어가 가지는 weights 값들입니다.
// 보스터 집값 예측
model.predict(testDataset.xs).print()
testDataset.ys.print()
model.getWeights()[0].print()
참조
https://js.tensorflow.org/api/latest/
공부할 점
1. batchNormalization, layerNormalization 차이점 알기
2. tfjs.vis 활용법 생각하기
'머신러닝' 카테고리의 다른 글
tensorflow.js 3) 붓꽃 분류하기 (0) | 2020.08.28 |
---|---|
tensorflow.js 1) 레몬에이드 판매량 예측 (0) | 2020.08.27 |