rnn

#include "darknet.h"
#include <math.h>

typedef struct {
    float *x;
    float *y;
} float_pair;

load_files

unsigned char **load_files(char *filename, int *n)
{
    list *paths = get_paths(filename);
    *n = paths->size;
    unsigned char **contents = calloc(*n, sizeof(char *));
    int i;
    node *x = paths->front;
    for(i = 0; i < *n; ++i){
        contents[i] = read_file((char *)x->val);
        x = x->next;
    }
    return contents;
}

함수 이름: load_files

입력:

  • filename (char *) : 파일 경로를 나타내는 문자열

  • n (int *) : 파일 개수를 나타내는 포인터 변수

동작:

  • filename을 이용하여 파일들이 있는 디렉토리 경로를 찾아낸다.

  • 디렉토리 경로에 있는 파일들을 모두 읽어서 메모리에 저장한다.

  • 파일의 개수를 n에 저장한다.

  • 읽어들인 파일들의 내용을 포인터 배열에 저장하고, 해당 배열을 반환한다.

설명:

  • 이 함수는 입력받은 filename을 이용하여 해당 경로에 있는 파일들을 읽어들이는 함수이다.

  • 먼저 get_paths 함수를 통해 파일 경로를 리스트로 얻어낸 후, 리스트의 크기를 이용하여 메모리 할당을 한다.

  • 그리고 for 루프를 이용하여 리스트에 저장된 경로들의 파일 내용을 읽어들여 배열에 저장하게 된다.

  • 마지막으로 배열을 반환하면서 읽어들인 파일 개수를 n에 저장하게 된다. 이 함수는 unsigned char 형식의 이중 포인터를 반환하기 때문에, 반환된 값에 접근하기 위해서는 이중 포인터를 이용해야 한다.

read_tokenized_data

함수 이름: read_tokenized_data

입력:

  • filename: 파일 이름을 나타내는 문자열 포인터

  • read: 토큰 수를 저장할 size_t 포인터

동작:

  • 입력된 파일을 읽어들여 숫자 데이터를 배열에 저장한 뒤, 해당 배열을 반환함.

  • 파일에서 숫자를 읽어들일 때는 공백으로 구분되는 여러 개의 정수들을 각각 하나씩 순서대로 읽어들임.

  • 읽어들인 정수들은 배열에 저장됨.

  • 배열의 크기는 초기에 512로 설정되며, 배열이 꽉 차게 되면 크기를 두 배로 확장함.

  • 반환된 배열의 크기는 해당 파일에서 읽어들인 숫자의 개수와 같으며, 이 값은 read 포인터를 통해 출력됨.

설명:

  • 이 함수는 파일에서 숫자 데이터를 읽어들여 배열에 저장하는 기능을 수행함.

  • 파일에서 읽어들인 숫자들은 int 타입으로 처리됨.

  • 파일에서 숫자를 읽어들일 때, fscanf 함수를 사용하여 파일에서 읽은 문자열을 정수로 변환함.

  • 읽어들인 정수는 동적 할당된 배열에 저장됨.

  • 배열의 크기가 부족할 경우, realloc 함수를 사용하여 크기를 두 배로 늘림.

  • 읽어들인 숫자의 개수를 변수 count를 통해 추적함.

  • 최종적으로, 배열의 크기를 실제로 읽어들인 숫자의 개수와 동일하게 줄이기 위해 realloc 함수를 한번 더 사용함.

read_tokens

함수 이름: read_tokens

입력:

  • filename: 파일 이름을 나타내는 문자열 포인터

  • read: 토큰 수를 저장할 size_t 포인터

동작:

  • 주어진 파일에서 문자열 토큰을 읽어들입니다.

  • 각 토큰을 문자열 포인터의 배열에 저장합니다.

  • 저장된 토큰 수를 read 포인터에 저장합니다.

설명:

  • 주어진 파일에서 문자열 토큰을 읽어들여서 이를 문자열 포인터의 배열에 저장하고, 저장된 토큰 수를 반환하는 함수입니다.

  • 파일을 열고 각 라인을 읽어들인 후, 문자열이 있는 경우 이를 개행 문자로 바꾸고 배열에 저장합니다.

  • 저장 공간이 부족한 경우 배열 크기를 2배로 늘려줍니다. 마지막으로 배열 크기를 실제 저장된 토큰 수에 맞게 조절하고, 이를 read 포인터에 저장합니다.

get_rnn_token_data

함수 이름: get_rnn_token_data

입력:

  • tokens (int *): 정수형 배열로서 토큰들의 시퀀스를 나타냄

  • offsets (size_t *): 정수형 배열로서 각 배치(batch)의 시작 위치(offset)를 나타냄

  • characters (int): 정수형으로서 가능한 문자(character)의 총 개수를 나타냄

  • len (size_t): 정수형으로서 토큰 시퀀스의 전체 길이를 나타냄

  • batch (int): 정수형으로서 배치(batch)의 개수를 나타냄

  • steps (int): 정수형으로서 RNN에서 처리할 스텝(step)의 개수를 나타냄

동작:

  • 입력으로 받은 토큰 시퀀스를 RNN 학습을 위한 형태로 변환하여 반환함

  • 입력으로 받은 토큰 시퀀스를 이용해 one-hot 인코딩(one-hot encoding)된 입력 데이터(x)와 출력 데이터(y)를 생성함

  • x와 y는 각각 크기가 (batch * steps * characters)인 1차원 배열이며, 각 스텝(step)에서 현재 입력(input)의 one-hot 인코딩이 x에 저장되고, 다음 출력(output)의 one-hot 인코딩이 y에 저장됨

  • 생성된 x와 y를 구조체(struct)에 담아 반환함

설명:

  • 이 함수는 RNN 학습을 위한 토큰 데이터를 생성하는 함수이며, 이를 위해 토큰 시퀀스를 입력과 출력으로 변환함

  • 입력으로 받은 토큰 시퀀스는 각 배치(batch)의 시작 위치(offset)를 나타내는 offsets 배열과 함께 사용됨

  • 각 배치마다 steps 개수만큼의 스텝(step)을 처리하며, 현재 입력(input)과 다음 출력(output)을 one-hot 인코딩(one-hot encoding)하여 입력 데이터(x)와 출력 데이터(y)를 생성함

  • 생성된 입력과 출력 데이터는 float_pair 구조체에 담아 반환됨

get_seq2seq_data

함수 이름: get_seq2seq_data

입력:

  • char **source: 원문을 포함한 문자열 배열

  • char **dest: 번역문을 포함한 문자열 배열

  • int n: 입력 데이터의 총 개수

  • int characters: 문자 집합의 크기

  • size_t len: 데이터의 최대 길이

  • int batch: 배치 크기

  • int steps: 각 시퀀스에서의 타임 스텝 수

동작:

  • 주어진 소스와 대상 문자열에서 무작위로 배치 크기만큼의 시퀀스를 추출하여 입력 데이터를 생성합니다.

  • 각 시퀀스에서의 모든 타임 스텝에 대해 현재 문자와 다음 문자를 가져와서 x와 y 배열에 인코딩합니다.

  • 문자가 문자 집합의 범위를 벗어나는 경우 오류를 발생시킵니다.

설명:

  • 이 함수는 시퀀스-투-시퀀스 모델의 데이터를 생성하는 데 사용됩니다.

  • 소스 문자열은 모델의 인코더의 입력이고, 대상 문자열은 모델의 디코더의 입력 및 출력입니다.

  • 모델은 소스 문자열을 인코딩하여 고정 길이의 벡터로 변환한 다음, 이 벡터를 사용하여 대상 문자열을 디코딩합니다.

  • 이 함수는 무작위로 선택된 시퀀스를 생성하여 입력 데이터를 만듭니다.

get_rnn_data

함수 이름: get_rnn_data

입력:

  • unsigned char *text: RNN 모델에 입력될 텍스트 데이터 배열

  • size_t *offsets: 텍스트 데이터 배열에서 batch 크기만큼의 데이터를 선택하기 위한 offset 값들이 저장된 배열

  • int characters: 문자 집합의 크기

  • size_t len: 텍스트 데이터 배열의 길이

  • int batch: 한 번의 학습에서 처리할 batch 크기

  • int steps: RNN 모델의 time step 크기

동작:

  • RNN 모델 학습을 위한 데이터셋을 생성하는 함수로, 입력으로 받은 텍스트 데이터 배열에서 batch 크기만큼의 데이터를 선택하여 RNN 모델 학습에 필요한 x, y 데이터셋을 생성합니다.

  • 생성된 x, y 데이터셋은 각각 RNN 모델의 입력과 정답 데이터가 됩니다.

설명:

  • calloc 함수를 이용하여 x, y 데이터셋을 batch 크기와 time step 크기에 맞게 할당합니다.

  • for문을 이용하여 batch 크기만큼 데이터를 선택하고, steps 크기만큼 x, y 데이터셋에 값을 할당합니다.

  • x 데이터셋은 curr 위치에 해당하는 문자의 인덱스 값을 1로 설정하고, y 데이터셋은 next 위치에 해당하는 문자의 인덱스 값을 1로 설정합니다.

  • offsets 배열에는 각 batch의 시작 위치가 저장되어 있으며, 한 번 데이터를 선택하고 나면 offsets 값을 len으로 나눈 나머지로 갱신합니다.

  • 각 선택된 데이터가 문자 집합의 크기를 초과하거나 0 이하일 경우 에러 메시지를 출력합니다.

  • 생성된 x, y 데이터셋을 float_pair 구조체에 저장하여 반환합니다.

train_char_rnn

함수 이름: train_char_rnn

입력:

  • cfgfile: 문자열, 네트워크 설정 파일 경로

  • weightfile: 문자열, 네트워크 가중치 파일 경로

  • filename: 문자열, 학습 데이터 파일 경로

  • clear: 정수, 네트워크 초기화 여부 (0 또는 1)

  • tokenized: 정수, 토큰화된 데이터를 사용할지 여부 (0 또는 1)

동작:

  • 학습 데이터를 읽어들이고, 네트워크를 초기화한 후 학습을 수행하는 함수

  • 입력으로 주어진 학습 데이터를 사용하여 배치 크기(batch size)와 시간 스텝(time step)을 설정하고, 훈련 데이터에서 임의의 위치에서 시작하여 여러 시퀀스를 추출한다.

  • 추출된 시퀀스를 이용하여 배치 데이터를 생성하고, 이를 사용하여 네트워크를 훈련한다.

  • 네트워크의 학습이 완료될 때까지 반복적으로 학습 데이터를 읽어들이고, 배치 데이터를 생성하여 네트워크를 학습한다.

  • 네트워크의 학습 과정에서 정기적으로 가중치를 저장하고 학습 상태를 출력한다.

설명:

  • 네트워크 설정 파일(cfgfile)은 네트워크의 구조 및 하이퍼파라미터를 정의하는 파일이다.

  • 네트워크 가중치 파일(weightfile)은 미리 학습된 네트워크의 가중치 값을 저장하는 파일이다.

  • 학습 데이터 파일(filename)은 네트워크를 학습할 때 사용하는 데이터를 저장하는 파일이다.

  • clear 인수가 1로 설정되면, 네트워크의 가중치를 재설정한다.

  • tokenized 인수가 1로 설정되면, 학습 데이터가 이미 토큰화되어 있는 경우이다. 그렇지 않으면, 학습 데이터를 읽어들인 후 토큰화한다.

  • 배치 크기(batch size)는 네트워크가 한 번에 처리하는 입력 데이터의 크기이다. 이 값은 네트워크 설정 파일(cfgfile)에서 정의된다.

  • 시간 스텝(time step)은 시퀀스 데이터를 처리하는 데 필요한 네트워크 계산의 수를 나타내는 값이다. 이 값도 네트워크 설정 파일(cfgfile)에서 정의된다.

  • 추출된 시퀀스는 각각 배치 크기(batch size)와 시간 스텝(time step)에 맞게 조정된다.

  • 훈련 데이터에서 임의의 위치에서 시작하여 추출된 시퀀스는 배치 크기(batch size)와 시간 스텝(time step)에 맞게 잘

함수 이름: print_symbol

입력:

  • n (int 타입): 출력할 심볼의 인덱스

  • tokens (char ** 타입): 심볼을 저장한 문자열 배열

동작:

  • 입력으로 받은 인덱스 n에 해당하는 심볼을 출력한다.

  • tokens 배열이 주어진 경우, 해당 인덱스에 해당하는 문자열을 출력하고 뒤에 공백을 추가한다.

  • tokens 배열이 주어지지 않은 경우, 해당 인덱스에 해당하는 아스키 문자를 출력한다.

설명:

  • print_symbol 함수는 주어진 인덱스에 해당하는 심볼을 출력하는 함수이다.

  • tokens 배열이 주어지면, 해당 배열에서 인덱스 n에 해당하는 문자열을 출력하고, 배열이 주어지지 않으면, 해당 인덱스에 해당하는 아스키 문자를 출력한다.

  • 출력된 심볼 뒤에는 tokens 배열이 주어진 경우에는 공백이 추가된다.

test_char_rnn

함수 이름: test_char_rnn

입력:

  • cfgfile: 문자열 포인터. 신경망의 구성 파일 경로를 지정하는데 사용됨.

  • weightfile: 문자열 포인터. 신경망 가중치 파일 경로를 지정하는데 사용됨.

  • num: 정수. 생성할 문자 수를 지정하는데 사용됨.

  • seed: 문자열 포인터. 생성 시작을 위한 시드 텍스트를 지정하는데 사용됨.

  • temp: 부동 소수점. 예측된 다음 문자의 확률 분포를 제어하는데 사용됨.

  • rseed: 정수. 무작위 생성을 위한 시드를 지정하는데 사용됨.

  • token_file: 문자열 포인터. 토큰 파일 경로를 지정하는데 사용됨.

동작:

  • 텍스트 생성 신경망을 테스트하여 텍스트를 생성함.

설명:

  • 주어진 cfgfile 및 weightfile로부터 신경망을 로드함.

  • 지정된 시드 텍스트에서 시작하여 num 개의 새로운 문자를 생성함.

  • 생성된 각 문자는 출력되어 표시됨.

  • 예측된 다음 문자의 확률 분포를 제어하는 데 사용되는 온도를 설정할 수 있음.

  • 지정된 token_file이 있으면, 출력할 때 해당 토큰 파일의 토큰을 사용하여 출력함.

  • 시드 텍스트의 길이는 입력 신경망의 크기로 잘라짐.

  • 출력되는 문자 수(num)는 시드 텍스트 이후에 생성되는 문자의 수를 의미함.

test_tactic_rnn_multi

함수 이름: test_tactic_rnn_multi

입력:

  • cfgfile: char pointer. RNN 네트워크의 구성 파일 경로

  • weightfile: char pointer. 학습된 RNN 네트워크의 가중치 파일 경로

  • num: int. 생성할 텍스트의 길이 (문자 수)

  • temp: float. 생성할 때 사용할 softmax 온도 값

  • rseed: int. 난수 생성을 위한 시드 값

  • token_file: char pointer. 텍스트 생성에 사용할 토큰 파일 경로. (옵션)

동작:

  • RNN 네트워크를 불러오고, 입력으로부터 새로운 텍스트를 생성하는 함수

  • 입력으로 받은 cfgfile, weightfile로부터 네트워크를 로드하고, 입력 크기(inputs)를 설정

  • 생성된 텍스트를 출력하기 위한 출력용 버퍼(input)를 초기화하고, 출력용 버퍼에 저장된 값을 사용하여 다음 출력 문자를 결정

  • 출력용 버퍼에 현재 출력 문자를 저장하고, 출력 문자를 출력

  • 출력용 버퍼와 현재 출력 문자를 사용하여 네트워크에 입력을 전달하고, 출력값(out)을 얻음

  • 현재 출력 문자와 출력값(out)을 사용하여 다음 출력 문자를 결정

  • 출력 문자가 개행 문자일 경우, 생성을 중지하고 출력 종료

설명:

  • 이 함수는 주어진 RNN 모델을 사용하여 새로운 텍스트를 생성하고 출력하는 역할을 수행합니다.

  • 입력으로는 RNN 모델의 구성 파일 경로(cfgfile), 학습된 가중치 파일 경로(weightfile), 생성할 텍스트의 길이(num), softmax 온도 값(temp), 난수 생성 시드 값(rseed)을 받습니다.

  • 또한, 선택적으로 사용할 수 있는 입력으로는 텍스트 생성에 사용할 토큰 파일 경로(token_file)가 있습니다.

  • RNN 모델을 로드하고 입력 크기(inputs)를 설정한 후, 출력용 버퍼(input)를 초기화합니다.

  • 출력용 버퍼에 저장된 값을 사용하여 다음 출력 문자를 결정하고, 출력 문자를 출력합니다.

  • 출력용 버퍼와 현재 출력 문자를 사용하여 네트워크에 입력을 전달하고, 출력값(out)을 얻습니다.

  • 현재 출력 문자와 출력값(out)을 사용하여 다음 출력 문자를 결정합니다.

  • 출력 문자가 개행 문자일 경우, 생성을 중지하고 출력을 종료합니다.

test_tactic_rnn

함수 이름: test_tactic_rnn 입력:

  • cfgfile: char*: 모델 설정 파일 경로

  • weightfile: char*: 모델 가중치 파일 경로

  • num: int: 생성할 기호 수

  • temp: float: 생성에 사용할 softmax 온도

  • rseed: int: 난수 시드

  • token_file: char*: 토큰 파일 경로 (선택 사항)

동작:

  • 주어진 모델을 사용하여 입력 기호를 생성하고 출력하는 함수입니다.

  • 입력으로는 모델 설정 파일, 가중치 파일, 생성할 기호 수, softmax 온도, 난수 시드, 토큰 파일 경로 (선택 사항)을 받습니다.

  • 입력된 모델을 불러온 후에는 softmax 온도를 설정하고, 입력 기호를 받아들이고 예측 결과를 계산합니다.

  • 이후, 생성할 기호 수만큼 예측을 반복하면서 생성된 기호를 출력합니다. 출력 중 "."과 "\n"이 연속으로 등장하면 출력을 중지합니다.

설명:

  • tokens: char**: 토큰 배열

  • base: char*: 모델 설정 파일에서 불러온 베이스 설정

  • net: network*: 불러온 모델

  • inputs: int: 모델 입력의 크기

  • i, j: int: 반복문을 위한 변수

  • c: int: 현재 입력 기호

  • input: float*: 모델 입력

  • out: float*: 모델 예측 결과

  • next: int: 다음 생성 기호

  • print_symbol: 함수 포인터: 출력 기호를 토큰화하여 출력하는 함수

valid_tactic_rnn

함수 이름: valid_tactic_rnn

입력:

  • cfgfile: char 포인터 타입. Tactic 모델의 구성 파일 경로.

  • weightfile: char 포인터 타입. Tactic 모델의 가중치 파일 경로.

  • seed: char 포인터 타입. 모델을 검증하기 위한 시드 문자열.

동작:

  • 주어진 Tactic 모델을 사용하여 시드 문자열을 바탕으로 모델을 검증하고, 입력으로 주어지는 텍스트 파일의 perplexity와 단어 당 perplexity를 계산하여 출력한다.

설명:

  • basecfg 함수를 사용하여 cfgfile에서 모델의 기본 설정을 가져온다.

  • load_network 함수를 사용하여 cfgfile과 weightfile에서 모델을 로드한다.

  • seed 문자열을 모델에 입력으로 제공하여 모델을 초기화한다.

  • getc(stdin)을 사용하여 입력 파일에서 문자를 하나씩 읽어들인다.

  • 입력 파일에서 읽어들인 문자를 모델에 입력으로 제공하고, 다음 문자를 예측한다.

  • perplexity와 단어 당 perplexity를 계산하여 출력한다.

valid_char_rnn

함수 이름: valid_char_rnn

입력:

  • cfgfile: char 형 포인터. 네트워크 설정 파일 경로

  • weightfile: char 형 포인터. 학습된 모델 가중치 파일 경로

  • seed: char 형 포인터. 네트워크 시작 문자열

동작:

  • 주어진 네트워크 설정 파일(cfgfile)과 가중치 파일(weightfile)을 사용하여 문자 수준(character-level)의 언어 모델을 로드하고, 주어진 시작 문자열(seed)로 네트워크 상태를 초기화합니다.

  • 이후에는 표준 입력(stdin)으로부터 문자를 하나씩 읽어들이면서, 현재까지 읽어들인 문자열에 대한 예측값을 출력하고, 다음 문자에 대한 예측을 수행합니다.

  • 이 과정을 통해 모델의 성능을 측정합니다. 최종적으로, 현재까지 읽어들인 문자열에 대한 엔트로피(entropy), 퍼플렉서티(perplexity) 및 단어 당 퍼플렉서티를 출력합니다.

설명:

  • basecfg(cfgfile): cfgfile로부터 설정 파일의 기본 이름을 추출하는 함수입니다.

  • load_network(cfgfile, weightfile, clear): cfgfile 및 weightfile로부터 네트워크를 로드하는 함수입니다. clear 인자는 네트워크 상태를 초기화할지 여부를 결정합니다.

  • calloc(n, size): n개의 size 바이트 메모리 블록을 할당하고 이를 모두 0으로 초기화합니다.

  • network_predict(net, input): 네트워크(net)에 대한 입력(input)에 대한 예측값을 계산하는 함수입니다.

  • log(x): x의 자연로그 값을 계산하는 함수입니다.

  • pow(x, y): x의 y 제곱 값을 계산하는 함수입니다.

  • BPC(Bit Per Character): 문자 당 평균 비트 수로, 모델의 예측 성능을 나타내는 지표 중 하나입니다.

  • Perplexity: 언어 모델의 예측 성능을 나타내는 지표 중 하나로, 다음 문자의 예측 확률의 역수에 로그를 취한 값을 평균한 값입니다. 이 값이 작을수록 모델의 성능이 우수합니다.

  • 단어 당 퍼플렉서티: 퍼플렉서티를 단어 수로 나눈 값으로, 긴 문장에서의 모델 성능을 측정하는 지표입니다.

vec_char_rnn

함수 이름: vec_char_rnn

입력:

  • cfgfile: 학습된 모델의 설정 파일 경로 (문자열)

  • weightfile: 학습된 모델의 가중치 파일 경로 (문자열)

  • seed: 초기 시퀀스 (문자열)

동작:

  • 주어진 seed를 이용해 학습된 모델을 초기화한 뒤, stdin으로부터 입력된 문자열을 하나씩 받아들이면서 해당 문자열의 다음 문자 예측 결과를 출력한다.

  • 출력 결과는 입력된 문자열과 함께, 예측된 다음 문자의 출력 확률 값을 콤마로 구분하여 출력한다.

설명:

  • 주어진 학습된 모델(cfgfile, weightfile)과 초기 시퀀스(seed)를 이용해, stdin으로부터 입력된 문자열을 하나씩 받아들이면서 다음 문자의 예측 결과를 출력하는 함수이다.

  • 입력된 문자열의 마지막에는 공백문자를 추가해야 하며, 출력 결과는 입력된 문자열과 함께, 예측된 다음 문자의 출력 확률 값을 콤마로 구분하여 출력한다.

  • 이 때 출력 확률 값은 해당 문자의 원-핫 인코딩 벡터에 대해 모델의 예측 결과 값이다.

run_char_rnn

함수 이름: run_char_rnn

입력:

  • int argc: 명령줄 인수의 수

  • char **argv: 명령줄 인수 배열

동작:

  • 입력된 인수가 충분하지 않으면 사용 방법을 출력하고 함수를 종료한다.

  • filename, seed, len, temp, rseed, clear, tokenized, tokens을 각각의 옵션에 맞게 설정한다.

  • cfg와 weights를 설정한다.

  • argv[2]에 따라서 train_char_rnn, valid_char_rnn, valid_tactic_rnn, vec_char_rnn, test_char_rnn, test_tactic_rnn 함수를 호출한다.

설명:

  • 이 함수는 char_rnn 모델을 실행하기 위한 함수로, 명령줄에서 인수를 받아와서 모델을 학습하거나 생성하는 등의 작업을 수행한다.

  • filename은 학습할 데이터 파일의 경로를 지정한다.

  • seed는 모델의 초기 입력 시퀀스를 지정한다.

  • len은 생성할 시퀀스의 길이를 지정한다.

  • temp는 생성할 시퀀스의 품질을 조절하는 온도 매개변수이다.

  • rseed는 시드 값으로 사용할 난수 발생기의 시드를 지정한다.

  • clear는 학습 중 생성한 캐시 파일을 삭제할지 여부를 결정한다.

  • tokenized는 입력 파일이 토큰화된 텍스트인지 여부를 결정한다.

  • tokens는 토큰화된 텍스트 파일의 경로를 지정한다.

  • cfg와 weights는 각각 모델 구성 파일과 가중치 파일의 경로를 지정한다.

  • argv[2]에 따라서 학습, 검증, 생성 등의 작업을 수행하는 함수를 호출한다.

Last updated

Was this helpful?