All language subtitles for s24-advcc-p23-hints

af Afrikaans
ak Akan
sq Albanian
am Amharic
ar Arabic
hy Armenian
az Azerbaijani
eu Basque
be Belarusian
bem Bemba
bn Bengali
bh Bihari
bs Bosnian
br Breton
bg Bulgarian
km Cambodian
ca Catalan
ceb Cebuano
chr Cherokee
ny Chichewa
zh-CN Chinese (Simplified)
zh-TW Chinese (Traditional) Download
co Corsican
hr Croatian
cs Czech
da Danish
nl Dutch
en English
eo Esperanto
et Estonian
ee Ewe
fo Faroese
tl Filipino
fi Finnish
fr French
fy Frisian
gaa Ga
gl Galician
ka Georgian
de German
el Greek
gn Guarani
gu Gujarati
ht Haitian Creole
ha Hausa
haw Hawaiian
iw Hebrew
hi Hindi
hmn Hmong
hu Hungarian
is Icelandic
ig Igbo
id Indonesian
ia Interlingua
ga Irish
it Italian
ja Japanese
jw Javanese
kn Kannada
kk Kazakh
rw Kinyarwanda
rn Kirundi
kg Kongo
ko Korean
kri Krio (Sierra Leone)
ku Kurdish
ckb Kurdish (Soranรฎ)
ky Kyrgyz
lo Laothian
la Latin
lv Latvian
ln Lingala
lt Lithuanian
loz Lozi
lg Luganda
ach Luo
lb Luxembourgish
mk Macedonian
mg Malagasy
ms Malay
ml Malayalam
mt Maltese
mi Maori
mr Marathi
mfe Mauritian Creole
mo Moldavian
mn Mongolian
my Myanmar (Burmese)
sr-ME Montenegrin
ne Nepali
pcm Nigerian Pidgin
nso Northern Sotho
no Norwegian
nn Norwegian (Nynorsk)
oc Occitan
or Oriya
om Oromo
ps Pashto
fa Persian
pl Polish
pt-BR Portuguese (Brazil)
pt Portuguese (Portugal)
pa Punjabi
qu Quechua
ro Romanian
rm Romansh
nyn Runyakitara
ru Russian
sm Samoan
gd Scots Gaelic
sr Serbian
sh Serbo-Croatian
st Sesotho
tn Setswana
crs Seychellois Creole
sn Shona
sd Sindhi
si Sinhalese
sk Slovak
sl Slovenian
so Somali
es Spanish
es-419 Spanish (Latin American)
su Sundanese
sw Swahili
sv Swedish
tg Tajik
ta Tamil
tt Tatar
te Telugu
th Thai
ti Tigrinya
to Tonga
lua Tshiluba
tum Tumbuka
tr Turkish
tk Turkmen
tw Twi
ug Uighur
uk Ukrainian
ur Urdu
uz Uzbek
vi Vietnamese
cy Welsh
wo Wolof
xh Xhosa
yi Yiddish
yo Yoruba
zu Zulu

Original subtitles

Hi everyone, welcome to the HINTS video for Project 2.3, Iterative Machine Learning Training.

In this video, I will walk you through the following topics.

First, let's take a look at the starter code in DABS.

We'll figure out the format of the samples RDD and the rationale behind using sparse format.

Then we'll focus on the scalability problem of the starter code

and think about how we can store weights array in the worker nodes rather than purely in the driver node.

And I will give the first hint about how to use inverted index and join-based communication.

Then we will talk about the performance problem of the draft optimization

and how to utilize partitions to optimize join operators.

I will give an introduction for RDD join operators after that.

At last,

I will give some further tips that we want to share with you guys.

Yeah.

OK, so let's get started.

As you have watched in the overview video,

this is the core logic of our machine learning training.

We have data samples and model parameters as inputs.

For data samples, it is stored as an RDD and it's also partitioned across executors.

For model parameters, it is stored as a weight array in the driver.

Next is the training iteration.

Specifically, the driver will broadcast the weights to all executors.

Then each executor will compute the gradient updates with respect to the data partition that resides in the executors.

And the driver would collect updates from executors and then update the parameters locally.

Then the iteration will repeat until the model converges.

Here we focus on the most significant computation, namely the computation of gradient descent.

So for gradient descent, we have broadcasted weight array and the worker's local samples as input.

We need to compute prediction based on the result of the sigmoid function.

But before that, we need to do a dot product between the broadcasted weight array and the feature values for each sample.

Then we can use the prediction result to compute the gradient and of course the loss function.

As we can see here, since the dot product will combine values from both inputs,

therefore it's the most important computation that we will consider for optimization.

Let's go through an example.

Here we have five values, oh sorry, five features.

Therefore the weight array also has five elements and the sample also got five.

By dot producting them, we can get a result.

However, it's clear that since there are a lot of zeros, as you can see here in each sample,

the final result also contains a lot of zeros.

Which means if we use the whole sample array to do dot product,

then there will be a lot of unnecessary computation.

This is especially true in real-world training sets,

since there are typically a lot of zeros in those samples,

or null values in those samples.

So how can we solve this problem?

The answer is using sparse format instead of dense format.

Rather than storing the whole dense vector for each sample,

we store those feature values that are non-zero.

And here is a logic representation for this.

For sparse format, we have a key-value pair list for each sample,

and the key is the feature ID while the value is the feature at value.

For example, here 0 represents its feature 0,

and 1 means feature 0 has value equals to 1.

Note that for the starter code,

the physical representation is actually a tuple,

with three elements,

a label,

a feature ID list,

and a feature value list.

Then, in order to dot product,

we need to use a feature ID list

to retrieve all the corresponding weights

and do the dot product

between them and the feature value list.

Through this way,

those computations for zero,

for zero products,

can be saved.

As a quick summary,

the final samples RDD looks like this.

For each sample,

we will only store useful features

and their corresponding non-zero values.

Next,

we will take a look at the scalability problem in the starter code.

From a high level,

there are two limitations in the starter code.

First,

we need a single-node memory.

The training set might have too many features

to fit into a single-node memory.

Secondly,

for communication,

since each partition might only require

a subset of parameters to do the dot product,

then broadcasting the whole weight array across the nodes

might be a waste.

So,

how can we improve the implementation?

The first step is to partition the weight array,

like what we did for the sample data,

so that we can store them across the whole cluster

instead of only within one single node.

Then,

we need to figure out a way

to tag each weight with a feature ID.

After this step,

we can get the weights ready,

which the key is a feature ID

and the value is a feature weight.

The next step is that we need to bring the feature weights

to each sample

so that we can do the dot product

and further computation.

In general,

the goal is to combine samples RDD

and the weights RDD together.

A simple way to do this

is to use RDD lookup operation

for each sample.

Since we know its relevant feature ID,

we can use those feature IDs

to fetch each weight with RDD

and they can do the computation.

However,

since lookup can only bring weight

to the driver node,

we still need to either broadcast weight

to worker node

or fetch each sample to the driver,

then do the computation.

This is very slow.

So,

is there any better way to do the combination?

Actually,

what we want here is

a distributed shuffle operation

to combine the samples dataset

and the weights dataset.

And let's use the join operation.

One potential issue is that

the weights already

sample

is a KV pair,

but samples RDD is a list.

To solve this,

we will consider inverted index.

Here is an inverted index computation pipeline

for samples RDD.

As shown here,

we can first do a map operation

to construct multiple key-value pairs

for each sample

as shown in the operation here,

where key is a feature ID

and the value is a

single-sample sparse vector.

Note that

since different samples might have

the same relevant feature value,

for example,

sample 2 will generate a pair for

feature 3,

and sample 3 would also generate

a key-value pair

for the same feature,

that is, feature 3.

So,

we will

as a

we can use the

groupByKey operation

to group those

values

with the same key together,

so that we don't have like duplicate keys

specifically.

Here, for example,

for feature 3,

the values would be a list

that contains

sample 2 and sample 3.

And at last,

we'll have a key-value data set

mapping from feature ID

to a list of samples.

Now,

we can do joint operations

between the intermediate data set

with the weights RDD

because they own the

same keys.

So,

here is the draft data flow

for our training task.

As shown in this whole pipeline

after we do the joint operation,

we will get a

new mapping

that is from

feature ID

to its weight value

and the list of

samples

here.

Note that for each key-value pair,

we only have one feature.

So,

we're still not able

to do the dot product here.

Therefore,

in order to

get each sample

to get all its relevant parameter weights,

we can do an

inverted index again.

That is,

to construct a new data set

with samples as the key

while

a whole relevant weight list

as the value.

By this way,

each sample can update itself

based on the corresponding

weight list.

Unfortunately,

life is not easy.

You might have noticed

the warning sign here.

There are several problems

for this design.

For one thing,

the first inverted index

is very expensive.

It's possible that

we replicate the whole

sample data set

multiple times.

For example,

if we got a hot feature,

then every sample

that has a non-zero value for it,

then for the feature's

key-value pair,

we have to store the whole data set

in one list,

which is clearly not acceptable.

For another,

the join operation

is also very expensive,

since the number of features

might be huge,

in which case,

we are trying to join

a large number of records

while each pair of weights

has a very small size,

namely one single feature weight,

so it is also not desirable.

Before we give

our next thing,

after this part,

you should be able

to calculate

and know what is

the inverted index

using RDD operations

and know the basic ideas

of how to do

join-based communication

to combine two RDDs.

Make sure you comprehend those

and then we can proceed

to the next steps.

So now we focus on

the performance part.

As we stated before,

one of our

jacked implementation

drawbacks is that

it tries to join

a large number of records,

and each record

is small in size.

From the test case perspective,

for example,

for test case A,

it contains over

20 million features,

which makes the join cost

unacceptable

because it joins

on the feature side.

Let us now

take a look at

a test case

that has multiple times

of the feature dimensions

compared to test case A.

So our goal is that

we want to join

a smaller number of records

while each of them

has a larger size.

And we want to avoid

storing multiple copies

of each sample.

Okay,

so here is our second hint.

Instead of using

logical features

or sample IDs,

let's use partition ID.

And based on the partition ID

to do the join operation.

In this way,

we can adjust

the number of partitions

so that we can control

the size of the

to-be-joined record.

So we have a partition ID

based on

the join here.

As shown

in the lower half

of the pipeline,

instead of directly computing

the feature ID

to sample's data set,

we actually generate

two data sets here.

First is the data set

that maps partition ID

to sample list.

This can be done

using the map partitions

with index transformation

in Spark.

Another data set

is a mapping from feature ID

to a list of partition IDs.

Constructed through

the inverted index approach.

Notably,

rather than having

redundant data samples,

now we only have

redundant partition IDs,

which is much cheaper.

Next,

through the two cheaper joins

here,

we can achieve

the data set

that we want

to be able to calculate

gradient descent locally.

So,

for example,

we can map

partition IDs

in each partition

or locally,

we mean,

like locally

in the executors

when possible,

or when candidate

RDD

might look like.

It's like

a mapping

from partition ID

to a tuple

containing

a waitlist

and a sample list.

And it's your job

to think about

and figure out

how to use

join operations

to achieve

this task.

Think about

how you can

use

these two RDDs,

the samples partition RDD

and the feature partition RDD,

together with

other existing RDDs

like weights RDD,

to derive

the weight sample RDD.

Brainstorm yourself

and think

about

how you can use

the inverted index

join operations

to join keys

and eventually

get this step.

Furthermore,

as for

RDD partitioning

and join optimization,

we have

some suggestions here.

First,

we would like

to introduce

some RDD partition operations.

So,

for

partition level

map

and reduce

task,

you might want

to take a look

at map partitions

and partition with index

and also

glom operations.

And for

repartition operations,

you might want

to take a look

at map partitions

with partition

and partition by

is useful.

Second point

is that

before doing

join operation,

if you do

a partition

by key operation first,

then the

join operation

might be cheaper.

One interesting

thinking question

is that

can you partition

the data set once

and do not disturb

the data set

and you reuse

the data set

for multiple joins

so that it saves

you the

shuffle cost.

The third point

is that

you might need

to

think about

the number

of relations,

its relationship

with the number

of features

and number

of cores

so that

each record

can fit

in memory

and the

join operation

can have

a good

performance.

One trade-off

here is that

if we are

having higher

number

of partitions,

then the

join operation

would be slower

but it will cause

less memory.

However,

if we have

less partition

numbers,

the join operation

would be quicker

but it will cause

a lot of memory

and even

cause

out-of-memory

errors.

So,

explore

and evaluate

the impact

yourself

and figure out

some general

solution

to decide

the number

of features

and number

of cores.

And please pay

attention that

we

do not

allow hard

code

and make

separate

submissions

for each

test cases.

For more

information,

please refer

to the

write-up.

In addition,

you might

always

want

to

use

the same

number

of partitions

in your

test cases.

In this

example,

there are

at least

two possible

cases that

the output

size might

blow out.

First is

the output

of the partition

of the join

operation.

Another is

the output

of reduce

by key

operation.

In both

cases,

you might

want to

try using

repartition

or partition

by operation

to maintain

the

result.

In this

example,

you might

be able

to find

situations

where

RDDs

can be

used

across

iterations.

For example,

the samples

RDD

and the

features

indices

are

read-only

data

without

any

updates.

So you

can

consider

most of

the

important

hints

for P2-3.

I'd

like to

give a

brief

introduction

to RDD

join

operators.

From a

logical

view,

the use

of RDD

join

operation

is for

combining

two key

value

datasets

together

so that

pairs

with

RDDs

can

be

combined

locally.

On the

other hand,

from an

implementation

perspective,

the goal

of the

Spark RDD

join

operator

is to

bring

corresponding

keys

from

each

RDD

to

the

same

partition

so that

they

can

be

integrated

into

the

RDD

join

operation.

For example,

if RDD-A

and

RDD-B

don't

have a

partitioner,

we'll

have

two

shuffle

operations

here.

However,

if we

specify

the

partitioner

before

doing

join

operation,

we

can

use

the

hash

function,

which

will

use

the

default

hash

partition

algorithm

to

distribute

key

value

pairs.

Another way

to specify

the

partitioner

is

using

partition

bind

operations,

in

which

only

one

shuffle

operation

is

required.

In addition,

if both

RDDs

use

the

same

partitioner,

which

means

firstly,

they'll

have

the

same

number

of

partitions,

and

secondly,

they'll

use

the

same number

of

partition

bind

functions,

which

saves

you

a lot

of

shuffle

cost.

And

also,

some

other

general

optimizations

for

RDD

join

operator

are as

follows.

Note that

you

might

not

need

to

do

a

join

operation

to

reduce

the

result

size

for

the

join

operation.

Secondly,

pre-partitioning

with

non-partitioner

can

reduce

the

cost

from

shuffle

operations.

Besides,

a common

strategy

is

to

test

one

iteration

at

a

time

or

two

iterations

to

have

an

estimate

about

what's

your

runtime

for

the

whole

test

cases.

Subsequent

iterations

are

about

20%

cheaper.

It's

especially

useful

for

test

cases.

And always,

please

start

early.

Yeah.

That's

all for

the

hints

video.

Hope you

enjoyed

this project

and best

wishes.

Thank you.

Can't find what you're looking for?
Get subtitles in any language from opensubtitles.com, and translate them here.