Index.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. <template>
  2. <v-row align="center" justify="center" no-gutters>
  3. <v-col cols="12">
  4. <v-row>
  5. <v-col md="2" class="text-center">
  6. <!-- Player Button Group -->
  7. <v-btn class="mx-2" icon small color="black" @click.stop="reset">
  8. <v-icon>mdi-replay</v-icon>
  9. </v-btn>
  10. <v-btn
  11. v-show="!isPlaying"
  12. class="mx-2"
  13. fab
  14. large
  15. color="primary"
  16. :disabled="choosingPoint"
  17. @click.stop="isPlaying = true"
  18. >
  19. <v-icon>mdi-play</v-icon>
  20. </v-btn>
  21. <v-btn
  22. v-show="isPlaying"
  23. class="mx-2"
  24. fab
  25. large
  26. color="primary"
  27. @click.stop="isPlaying = false"
  28. >
  29. <v-icon>mdi-pause</v-icon>
  30. </v-btn>
  31. <v-btn class="mx-w" icon small color="black" @click.stop="oneStep">
  32. <v-icon>mdi-skip-next</v-icon>
  33. </v-btn>
  34. </v-col>
  35. <v-col md="10" class="text-left d-flex">
  36. <!-- Setting Board: learning rate, initial point, function -->
  37. <v-row>
  38. <!-- iteration number -->
  39. <v-col cols="1">
  40. <span class="d-block label">{{ $t('playground.gradientDescent.iterNum') }}</span>
  41. <span id="iter-number" class="d-block ma-0">000,000</span>
  42. </v-col>
  43. <!-- learning rate selects -->
  44. <v-col cols="3">
  45. <v-select
  46. v-model="learningRate"
  47. :class="[$vuetify.breakpoint.mdAndUp ? '' : 'mb-6', 'mx-2']"
  48. :items="learningRates"
  49. menu-props="auto"
  50. hide-details
  51. :label="$t('playground.gradientDescent.learningRate')"
  52. />
  53. </v-col>
  54. <!-- function selects -->
  55. <v-col cols="3">
  56. <v-select
  57. v-model="currentFunction"
  58. :class="[$vuetify.breakpoint.mdAndUp ? '' : 'mb-6', 'mx-2']"
  59. :items="functions"
  60. menu-props="auto"
  61. hide-details
  62. :label="$t('playground.gradientDescent.function')"
  63. />
  64. </v-col>
  65. </v-row>
  66. </v-col>
  67. </v-row>
  68. </v-col>
  69. <v-col cols="6">
  70. <!-- Function Chart -->
  71. <v-alert
  72. color="#2A3B4D"
  73. type="info"
  74. class="mt-5 mb-0 mx-auto"
  75. max-width="90%"
  76. :hidden="!choosingPoint"
  77. >
  78. {{ $t('playground.gradientDescent.choosingPoint') }}
  79. </v-alert>
  80. <div id="gd-function-chart" />
  81. </v-col>
  82. <v-col cols="6">
  83. <!-- Step Chart -->
  84. <div id="step-chart">
  85. <svg>
  86. <defs>
  87. <marker
  88. id="markerArrow"
  89. markerWidth="7"
  90. markerHeight="13"
  91. refX="1"
  92. refY="6"
  93. orient="auto"
  94. markerUnits="userSpaceOnUse"
  95. >
  96. <path d="M2,11 L7,6 L2,2" />
  97. </marker>
  98. </defs>
  99. </svg>
  100. </div>
  101. </v-col>
  102. </v-row>
  103. </template>
  104. <script lang="ts">
  105. import Vue from 'vue'
  106. import * as d3 from 'd3'
  107. import { FunctionChart } from './function-chart'
  108. import { singleVarfunctions } from './functions'
  109. import { Player } from './util'
  110. import { GradientDescent, OneStepInfo } from './gradient-descent'
  111. import { StepChart, StepNode } from './step-chart'
  112. const learningRates = [0.1, 0.05, 0.01, 0.005, 0.001]
  113. const functions = Object.keys(singleVarfunctions).map(key => ({
  114. text: key,
  115. value: singleVarfunctions[key]
  116. }))
  117. const initialStepResult: OneStepInfo = { gradient: 0, deltaX: 0, resultPoint: { x: 0, y: 0 } }
  118. const ALGORITHM_STEPS = 3
  119. export default Vue.extend({
  120. components: {},
  121. props: {},
  122. data() {
  123. return {
  124. functionChart: null as null | FunctionChart,
  125. stepChart: null as null | StepChart,
  126. player: null as null | Player,
  127. gradientDescent: null as null | GradientDescent,
  128. iter: 1, // steps number(refers to the step in StepChart)
  129. currentStepResult: initialStepResult,
  130. learningRates,
  131. functions,
  132. currentFunction: functions[0].value,
  133. learningRate: 0.01,
  134. choosingPoint: true,
  135. isPlaying: false
  136. }
  137. },
  138. watch: {
  139. currentFunction() {
  140. this.reset()
  141. },
  142. isPlaying(val, _) {
  143. if (val) {
  144. this.player!.play()
  145. } else {
  146. this.player!.pause()
  147. }
  148. }
  149. },
  150. mounted(): void {
  151. this.$nextTick(() => {
  152. this.reset()
  153. this.player = new Player(this.oneStep)
  154. })
  155. },
  156. methods: {
  157. oneStep() {
  158. this.updateStepChart()
  159. if (this.iter % ALGORITHM_STEPS === 0) {
  160. // becuase we have run one step when created the GradientDescent class
  161. // we have to show the result of the previous step first
  162. this.updateFunctionChart()
  163. this.currentStepResult = this.gradientDescent!.oneStep(this.learningRate)
  164. }
  165. function addCommas(s: string): string {
  166. return s.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
  167. }
  168. function zeroPad(n: number): string {
  169. const pad = '000000'
  170. return (pad + n).slice(-pad.length)
  171. }
  172. d3.select('#iter-number').text(addCommas(zeroPad(Math.floor(this.iter / ALGORITHM_STEPS))))
  173. this.iter++
  174. },
  175. updateFunctionChart() {
  176. this.functionChart!.setPoint(-1, this.currentStepResult.resultPoint)
  177. },
  178. buildStepChart() {
  179. const steps = [
  180. new StepNode('', this.$t('playground.gradientDescent.start').toString(), 0, 'start'),
  181. new StepNode(this.$t('playground.gradientDescent.step1').toString(), 'df/dx', 1),
  182. new StepNode(this.$t('playground.gradientDescent.step2').toString(), 'delta x', 2),
  183. new StepNode(this.$t('playground.gradientDescent.step3').toString(), '(x, y)', 3),
  184. new StepNode('', this.$t('playground.gradientDescent.end').toString(), 4, 'end')
  185. ]
  186. this.stepChart = new StepChart(d3.select('#step-chart'), steps)
  187. this.stepChart
  188. .linkNode(0, 1)
  189. .linkNode(1, 2)
  190. .linkNode(2, 3)
  191. .linkNode(3, 1)
  192. .linkNode(3, 4)
  193. this.stepChart.render()
  194. },
  195. updateStepChart() {
  196. function humanReadable(n: number) {
  197. return n.toFixed(4)
  198. }
  199. const currentStep = this.iter % 3
  200. if (currentStep === 0) {
  201. const resultPoint = this.currentStepResult.resultPoint
  202. const resultPointStr = `(${humanReadable(resultPoint.x)}, ${humanReadable(resultPoint.y)})`
  203. this.stepChart!.update(3, resultPointStr)
  204. } else if (currentStep === 1) {
  205. this.stepChart!.update(1, humanReadable(this.currentStepResult.gradient))
  206. } else {
  207. this.stepChart!.update(2, humanReadable(this.currentStepResult.deltaX))
  208. }
  209. },
  210. reset() {
  211. // Allowing select point on the chart
  212. const functionChart = new FunctionChart(this.currentFunction, d3.select('#gd-function-chart'))
  213. this.buildStepChart()
  214. this.functionChart = functionChart
  215. this.choosingPoint = true
  216. this.isPlaying = false
  217. this.iter = 1
  218. this.currentStepResult = initialStepResult
  219. d3.select('#iter-number').text('000,000')
  220. const addEventOnChartLine = () => {
  221. functionChart.performActionOnChart(({ chart, functionLine, points }) => {
  222. const eventFunctionLine = functionLine
  223. .clone()
  224. .attr('class', 'event-function-line')
  225. .style('fill', 'none')
  226. .style('stroke', '#348')
  227. .style('stroke-width', '8px')
  228. .style('opacity', 0)
  229. eventFunctionLine.on('click', () => {
  230. if (!this.choosingPoint) {
  231. return
  232. }
  233. const x = d3.mouse(chart.node() as SVGGElement)[0]
  234. functionChart.appendPointByRealPosition(x)
  235. this.choosingPoint = false
  236. this.gradientDescent = new GradientDescent(
  237. this.currentFunction,
  238. points[points.length - 1]
  239. )
  240. this.currentStepResult = this.gradientDescent.oneStep(this.learningRate)
  241. })
  242. })
  243. }
  244. addEventOnChartLine()
  245. // Re-render function chart when window changes
  246. if (window) {
  247. window.addEventListener('resize', () => {
  248. const prevChart = this.functionChart
  249. this.functionChart = new FunctionChart(
  250. this.currentFunction,
  251. d3.select('#gd-function-chart')
  252. )
  253. if (prevChart) {
  254. this.functionChart.appendPoint(prevChart.getPoints()[0].x)
  255. addEventOnChartLine()
  256. }
  257. })
  258. }
  259. }
  260. }
  261. })
  262. </script>
  263. <style scoped>
  264. #gd-function-chart {
  265. height: 600px;
  266. }
  267. #iter-number {
  268. font-size: 21.5px;
  269. font-weight: 300;
  270. }
  271. #step-chart {
  272. position: relative;
  273. height: 600px;
  274. }
  275. .label {
  276. font-size: 12px;
  277. opacity: 0.6;
  278. margin-top: 2px;
  279. }
  280. </style>
  281. <style>
  282. .step-card {
  283. position: absolute;
  284. left: 0;
  285. right: 0;
  286. }
  287. .step-card:not(.step-card--start, .step-card--end) {
  288. width: 300px;
  289. height: 100px;
  290. }
  291. .step-card--start,
  292. .step-card--end {
  293. width: 100px;
  294. height: 70px;
  295. text-align: center;
  296. }
  297. .link {
  298. fill: none;
  299. stroke: #aaa;
  300. stroke-width: 2;
  301. /* stroke-dasharray: 4 1;
  302. stroke-dashoffset: 1; */
  303. }
  304. </style>