| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- <template>
- <v-row align="center" justify="center" no-gutters>
- <v-col cols="12">
- <v-row>
- <v-col md="2" class="text-center">
- <!-- Player Button Group -->
- <v-btn class="mx-2" icon small color="black" @click.stop="reset">
- <v-icon>mdi-replay</v-icon>
- </v-btn>
- <v-btn
- v-show="!isPlaying"
- class="mx-2"
- fab
- large
- color="primary"
- :disabled="choosingPoint"
- @click.stop="isPlaying = true"
- >
- <v-icon>mdi-play</v-icon>
- </v-btn>
- <v-btn
- v-show="isPlaying"
- class="mx-2"
- fab
- large
- color="primary"
- @click.stop="isPlaying = false"
- >
- <v-icon>mdi-pause</v-icon>
- </v-btn>
- <v-btn class="mx-w" icon small color="black" @click.stop="oneStep">
- <v-icon>mdi-skip-next</v-icon>
- </v-btn>
- </v-col>
- <v-col md="10" class="text-left d-flex">
- <!-- Setting Board: learning rate, initial point, function -->
- <v-row>
- <!-- iteration number -->
- <v-col cols="1">
- <span class="d-block label">{{ $t('playground.gradientDescent.iterNum') }}</span>
- <span id="iter-number" class="d-block ma-0">000,000</span>
- </v-col>
- <!-- learning rate selects -->
- <v-col cols="3">
- <v-select
- v-model="learningRate"
- :class="[$vuetify.breakpoint.mdAndUp ? '' : 'mb-6', 'mx-2']"
- :items="learningRates"
- menu-props="auto"
- hide-details
- :label="$t('playground.gradientDescent.learningRate')"
- />
- </v-col>
- <!-- function selects -->
- <v-col cols="3">
- <v-select
- v-model="currentFunction"
- :class="[$vuetify.breakpoint.mdAndUp ? '' : 'mb-6', 'mx-2']"
- :items="functions"
- menu-props="auto"
- hide-details
- :label="$t('playground.gradientDescent.function')"
- />
- </v-col>
- </v-row>
- </v-col>
- </v-row>
- </v-col>
- <v-col cols="6">
- <!-- Function Chart -->
- <v-alert
- color="#2A3B4D"
- type="info"
- class="mt-5 mb-0 mx-auto"
- max-width="90%"
- :hidden="!choosingPoint"
- >
- {{ $t('playground.gradientDescent.choosingPoint') }}
- </v-alert>
- <div id="gd-function-chart" />
- </v-col>
- <v-col cols="6">
- <!-- Step Chart -->
- <div id="step-chart">
- <svg>
- <defs>
- <marker
- id="markerArrow"
- markerWidth="7"
- markerHeight="13"
- refX="1"
- refY="6"
- orient="auto"
- markerUnits="userSpaceOnUse"
- >
- <path d="M2,11 L7,6 L2,2" />
- </marker>
- </defs>
- </svg>
- </div>
- </v-col>
- </v-row>
- </template>
- <script lang="ts">
- import Vue from 'vue'
- import * as d3 from 'd3'
- import { FunctionChart } from './function-chart'
- import { singleVarfunctions } from './functions'
- import { Player } from './util'
- import { GradientDescent, OneStepInfo } from './gradient-descent'
- import { StepChart, StepNode } from './step-chart'
- const learningRates = [0.1, 0.05, 0.01, 0.005, 0.001]
- const functions = Object.keys(singleVarfunctions).map(key => ({
- text: key,
- value: singleVarfunctions[key]
- }))
- const initialStepResult: OneStepInfo = { gradient: 0, deltaX: 0, resultPoint: { x: 0, y: 0 } }
- const ALGORITHM_STEPS = 3
- export default Vue.extend({
- components: {},
- props: {},
- data() {
- return {
- functionChart: null as null | FunctionChart,
- stepChart: null as null | StepChart,
- player: null as null | Player,
- gradientDescent: null as null | GradientDescent,
- iter: 1, // steps number(refers to the step in StepChart)
- currentStepResult: initialStepResult,
- learningRates,
- functions,
- currentFunction: functions[0].value,
- learningRate: 0.01,
- choosingPoint: true,
- isPlaying: false
- }
- },
- watch: {
- currentFunction() {
- this.reset()
- },
- isPlaying(val, _) {
- if (val) {
- this.player!.play()
- } else {
- this.player!.pause()
- }
- }
- },
- mounted(): void {
- this.$nextTick(() => {
- this.reset()
- this.player = new Player(this.oneStep)
- })
- },
- methods: {
- oneStep() {
- this.updateStepChart()
- if (this.iter % ALGORITHM_STEPS === 0) {
- // becuase we have run one step when created the GradientDescent class
- // we have to show the result of the previous step first
- this.updateFunctionChart()
- this.currentStepResult = this.gradientDescent!.oneStep(this.learningRate)
- }
- function addCommas(s: string): string {
- return s.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
- }
- function zeroPad(n: number): string {
- const pad = '000000'
- return (pad + n).slice(-pad.length)
- }
- d3.select('#iter-number').text(addCommas(zeroPad(Math.floor(this.iter / ALGORITHM_STEPS))))
- this.iter++
- },
- updateFunctionChart() {
- this.functionChart!.setPoint(-1, this.currentStepResult.resultPoint)
- },
- buildStepChart() {
- const steps = [
- new StepNode('', this.$t('playground.gradientDescent.start').toString(), 0, 'start'),
- new StepNode(this.$t('playground.gradientDescent.step1').toString(), 'df/dx', 1),
- new StepNode(this.$t('playground.gradientDescent.step2').toString(), 'delta x', 2),
- new StepNode(this.$t('playground.gradientDescent.step3').toString(), '(x, y)', 3),
- new StepNode('', this.$t('playground.gradientDescent.end').toString(), 4, 'end')
- ]
- this.stepChart = new StepChart(d3.select('#step-chart'), steps)
- this.stepChart
- .linkNode(0, 1)
- .linkNode(1, 2)
- .linkNode(2, 3)
- .linkNode(3, 1)
- .linkNode(3, 4)
- this.stepChart.render()
- },
- updateStepChart() {
- function humanReadable(n: number) {
- return n.toFixed(4)
- }
- const currentStep = this.iter % 3
- if (currentStep === 0) {
- const resultPoint = this.currentStepResult.resultPoint
- const resultPointStr = `(${humanReadable(resultPoint.x)}, ${humanReadable(resultPoint.y)})`
- this.stepChart!.update(3, resultPointStr)
- } else if (currentStep === 1) {
- this.stepChart!.update(1, humanReadable(this.currentStepResult.gradient))
- } else {
- this.stepChart!.update(2, humanReadable(this.currentStepResult.deltaX))
- }
- },
- reset() {
- // Allowing select point on the chart
- const functionChart = new FunctionChart(this.currentFunction, d3.select('#gd-function-chart'))
- this.buildStepChart()
- this.functionChart = functionChart
- this.choosingPoint = true
- this.isPlaying = false
- this.iter = 1
- this.currentStepResult = initialStepResult
- d3.select('#iter-number').text('000,000')
- const addEventOnChartLine = () => {
- functionChart.performActionOnChart(({ chart, functionLine, points }) => {
- const eventFunctionLine = functionLine
- .clone()
- .attr('class', 'event-function-line')
- .style('fill', 'none')
- .style('stroke', '#348')
- .style('stroke-width', '8px')
- .style('opacity', 0)
- eventFunctionLine.on('click', () => {
- if (!this.choosingPoint) {
- return
- }
- const x = d3.mouse(chart.node() as SVGGElement)[0]
- functionChart.appendPointByRealPosition(x)
- this.choosingPoint = false
- this.gradientDescent = new GradientDescent(
- this.currentFunction,
- points[points.length - 1]
- )
- this.currentStepResult = this.gradientDescent.oneStep(this.learningRate)
- })
- })
- }
- addEventOnChartLine()
- // Re-render function chart when window changes
- if (window) {
- window.addEventListener('resize', () => {
- const prevChart = this.functionChart
- this.functionChart = new FunctionChart(
- this.currentFunction,
- d3.select('#gd-function-chart')
- )
- if (prevChart) {
- this.functionChart.appendPoint(prevChart.getPoints()[0].x)
- addEventOnChartLine()
- }
- })
- }
- }
- }
- })
- </script>
- <style scoped>
- #gd-function-chart {
- height: 600px;
- }
- #iter-number {
- font-size: 21.5px;
- font-weight: 300;
- }
- #step-chart {
- position: relative;
- height: 600px;
- }
- .label {
- font-size: 12px;
- opacity: 0.6;
- margin-top: 2px;
- }
- </style>
- <style>
- .step-card {
- position: absolute;
- left: 0;
- right: 0;
- }
- .step-card:not(.step-card--start, .step-card--end) {
- width: 300px;
- height: 100px;
- }
- .step-card--start,
- .step-card--end {
- width: 100px;
- height: 70px;
- text-align: center;
- }
- .link {
- fill: none;
- stroke: #aaa;
- stroke-width: 2;
- /* stroke-dasharray: 4 1;
- stroke-dashoffset: 1; */
- }
- </style>
|