1. 기본 구조 Skill은 폴더 하나와 그 안의 SKILL.md 로 구성됩니다. 프로젝트용은 .claude/skills/ 에, 개인 전역용은 ~/.claude/skills/ 에 둡니다. .claude/skills/ └── code-review/ # 폴더명 = name (kebab-case) ├── SKILL.md # 필수 (대문자) ├── reference.md # 선택: 상세 문서 └── scripts/ # 선택: 보조 스크립트 .claude/skills/<skill-name>/SKILL.md 를 만들고, name 은 디렉터리명과 일치해야 합니다. 폴더명은 소문자, 숫자, 하이픈만 쓰고, 파일명 SKILL.md 는 반드시 대문자입니다. 2. Frontmatter (공식 필수 필드) 파일 맨 위에 --- 로 감싼 YAML 블록을 두며, 필수 필드는 두 개입니다. --- name: code-review description: 코드 변경사항을 리뷰한다. 사용자가 "리뷰해줘", "PR 검토", "코드 점검"을 요청할 때 사용. --- 필드 역할 name Claude Code에서 /슬래시 명령어 가 됩니다. description Claude가 이 skill을 언제 로드할지 판단하는 기준입니다. description 이 가장 중요합니다. Claude는 이 문장을 보고 skill을 자동으로 호출할지 결정하기 때문입니다. 그래서 다음을 지키는 것이 좋습니다. 무엇을 하는지 + 언제 쓰는지 를 함께 적습니다. 구체적인 트리거 문구를 포함합니다. 사용자가 실제로 쓸 법한 표현을 넣으세요. 너무 모호한 설명(예: "코드 도움")은 오작동의 원인이 됩니다. allowed-tools 같은 선택 필드도 있는 것으로 보이지만, 이번 검색에서는 공식 문서 원문을 직접 확인하지 못했습니다. 정확한 목록은 공식 문서(docs.claude.com)를 확인해 주세요. 3. 본문(Body) 작성: Goal / Instructions / Constraints Goal, Instructions, Constraints는 공식 예약 키워드가 아니라 관례적인 섹션 이름입니다. 본문은 자유로운 Markdown이며, 아래 구조가 Claude가 따르기 쉬워서 많이 쓰입니다. 섹션 내용 Goal 이 skill이 달성할 최종 결과물 When to use 적용 상황, 입력 조건 (description 보완) Instructions 순서가 있는 단계별 작업 절차 Constraints 하지 말아야 할 것, 지켜야 할 규칙, 범위 제한 Output format 결과물 형식, 템플릿 Examples 입력/출력 예시 작성 팁은 다음과 같습니다. 로딩 방식을 이해하세요. frontmatter(name, description)는 항상 로드되고, 본문은 트리거될 때만 로드됩니다. 이를 progressive disclosure라고 부릅니다. 본문을 짧게 유지하세요. SKILL.md는 500줄 미만으로 유지하고, 상세 내용(DB 스키마, API 문서 등)은 별도 참조 파일로 분리하는 것이 권장됩니다. 본문에서 상대 경로로 링크하면 됩니다. 지시는 " 하지 마라"보다 " 해라"처럼 명확하고 검증 가능하게 쓰고, 금지 사항은 Constraints에 모읍니다. 4. 자주 쓰이는 Skill 예시 예시 1: 코드 리뷰 --- name: code-review description: 변경된 코드를 리뷰하고 개선점을 제안한다. "코드 리뷰", "PR 검토", "변경사항 점검" 요청 시 사용. --- # Code Review ## Goal 변경된 코드의 버그, 보안 이슈, 가독성 문제를 찾아 우선순위별로 보고한다. ## Instructions 1. `git diff`로 변경 범위를 확인한다. 2. 변경된 파일을 읽고 주변 코드 맥락을 파악한다. 3. 다음 관점으로 점검한다: 로직 오류 → 보안 → 성능 → 가독성 → 테스트 누락 4. 결과를 아래 출력 형식으로 정리한다. ## Constraints - 요청 없이 코드를 직접 수정하지 않는다. - 변경되지 않은 파일은 지적하지 않는다. - 취향 차이 수준의 스타일 지적은 최소화한다. ## Output format - 🔴 Critical / 🟡 Suggestion / 🟢 Good 로 분류 - 각 항목: `파일:라인` – 문제 – 제안 예시 2: 커밋 메시지 작성 --- name: commit-message description: 스테이징된 변경사항으로 Conventional Commits 형식의 커밋 메시지를 작성한다. "커밋 메시지", "commit" 요청 시 사용. --- ## Goal 프로젝트 컨벤션에 맞는 커밋 메시지를 생성한다. ## Instructions 1. `git diff --staged`로 변경 내용을 확인한다. 2. `type(scope): subject` 형식으로 작성한다. (feat, fix, docs, refactor, test, chore) 3. 필요하면 본문에 "왜 변경했는지"를 적는다. ## Constraints - subject는 50자 이내, 마침표 없이 작성한다. - 직접 `git commit`을 실행하지 말고 메시지만 제안한다. 예시 3: 테스트 작성 --- name: write-tests description: 지정한 함수나 모듈의 단위 테스트를 작성한다. "테스트 작성", "테스트 추가", "커버리지 보강" 요청 시 사용. --- ## Goal 기존 테스트 스타일에 맞춰 의미 있는 테스트를 추가한다. ## Instructions 1. 프로젝트의 기존 테스트 파일에서 프레임워크와 네이밍 규칙을 파악한다. 2. 정상 케이스, 경계값, 예외 케이스 순으로 작성한다. 3. 작성 후 테스트를 실행해 통과 여부를 확인한다. ## Constraints - 프로덕션 코드는 수정하지 않는다. - 외부 네트워크 호출은 mock 처리한다. 그 밖에 자주 만드는 유형으로 PR 설명 작성, 릴리스 노트 생성, DB 마이그레이션 점검, 프로젝트 코딩 컨벤션 가이드, 문서 감사(audit) 등이 있습니다. 5. 실무 체크리스트 name 이 폴더명과 일치하는가 (kebab-case) description 에 기능 + 트리거 문구 가 모두 들어 있는가 한 skill이 한 가지 일만 하는가 (큰 작업은 분리) 본문이 500줄 이하이고, 상세 내용은 별도 파일로 뺐는가 Constraints에 금지 사항과 범위를 명시했는가
Best 17 Sites to Buy,, Old GitHub Accounts (New & Aged) Buy GitHub Accounts: What You Should Know Before Purchasing ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️If You Are Interested Just Click Here 👉 ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️Telegram:@Usavcsmm ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️WhatsApp: +1(657) 462-1328 ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️E-mail: usavcsmm@gmail.com ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️Visit now: https://usavcsmm.com/product/buy-google-voice-accounts/ GitHub has become one of the world's most widely used platforms for software development, version control, collaboration, and open-source projects. Developers, agencies, startups, and businesses rely on GitHub to manage repositories, collaborate with teams, publish projects, and build professional portfolios. Because established GitHub profiles can appear more developed than newly created accounts, some users search for services using terms such as “Buy GitHub Accounts.” However, purchasing an existing account can involve significant security, ownership, authenticity, and policy concerns. GitHub's Terms of Service place responsibility for an account and activity performed through it on the account holder, while its Acceptable Use Policies prohibit various forms of fake accounts and inauthentic activity. For businesses and developers, the better approach is to understand what they actually need from a GitHub account and choose a legitimate setup that provides those capabilities without putting valuable projects or credentials at unnecessary risk. This guide explains what buyers should consider, the features that matter, common risks, and safer alternatives for establishing a professional GitHub presence. What Does “Buy GitHub Accounts” Mean? The phrase “Buy GitHub Accounts” generally refers to the search for pre-existing GitHub accounts offered by third parties rather than creating a new account directly through GitHub. Such accounts may be advertised with characteristics such as: An existing account history A pre-established profile Previous repository activity Existing followers or social connections An older account creation date Different profile configurations Claimed verification or reputation However, these characteristics do not automatically make an account trustworthy or suitable for business use. An account's history may be difficult to verify, and the original creator may retain information or recovery mechanisms that create security problems for a subsequent user. In addition, GitHub's policies specifically address fake accounts, inauthentic activity, impersonation, and secondary markets associated with inauthentic activity. Why Do People Search for GitHub Accounts? There are several legitimate business reasons someone may search for an established GitHub presence. Faster Project Setup A developer starting a new project may want to establish a professional profile quickly. Rather than focusing on account age, the practical goal should be setting up repositories, documentation, contribution workflows, and security controls efficiently. Professional Development GitHub can function as an important part of a developer's professional portfolio. A well-organized profile can showcase: Software projects Open-source contributions Programming experience Documentation skills Collaboration history Technical interests Building these assets organically provides a more reliable representation of a developer's work. Business Collaboration Companies often need GitHub access for development teams, repositories, organizations, and project management. Instead of purchasing individual accounts, businesses should use GitHub's organizational and access-management capabilities appropriate to their needs. Project Migration Sometimes the real requirement behind a search for an established account is actually the need to transfer an existing project. GitHub provides official repository-transfer functionality that allows eligible repositories to be transferred between personal accounts and organizations. Important Risks of Buying GitHub Accounts Before considering any third-party account marketplace, it is important to understand the potential disadvantages. Account Ownership Problems An account purchased from another person may not provide genuine long-term ownership. The original creator could potentially have recovery information, connected credentials, or historical access that creates complications. GitHub's Terms state that users are responsible for keeping their accounts secure and for activity performed while signed in to or using their accounts. Suspension Risk GitHub maintains policies against fake accounts and inauthentic activity. It also prohibits certain secondary markets associated with the proliferation of inauthentic activity. Enforcement can include account suspension, termination, or content removal. ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️If You Are Interested Just Click Here 👉 ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️Telegram:@Usavcsmm ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️WhatsApp: +1(657) 462-1328 ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️E-mail: usavcsmm@gmail.com ❤️💠♻️❇️🌐👉🏽✅🔷🏓🌍🔆💟✔️Visit now: https://usavcsmm.com/product/buy-google-voice-accounts/ This means an account's apparent age or history should never be treated as a guarantee of continued access. Security Concerns Third-party accounts may have unknown histories. You may not know: Who previously controlled the account Which devices accessed it Whether recovery information remains connected Whether credentials were reused elsewhere Whether previous activity violated platform rules For developers managing private repositories, source code, API credentials, or proprietary information, these concerns are particularly important. Reputation and Authenticity Issues An established profile may contain activity that does not accurately represent the current owner. Using another person's historical profile can also create questions about authorship and identity. GitHub's impersonation policy prohibits misleading others about a person's identity or association with another individual or organization. GGitHub Docs Features to Look for in a Legitimate GitHub Setup Instead of focusing primarily on account age, evaluate the features that actually support your work. Secure Account Access Security should be the first priority. Use a unique password, appropriate authentication methods, and current recovery information. Professional Profile A strong developer profile should clearly communicate: Your name or professional identity Areas of technical expertise Relevant projects Professional website or portfolio Appropriate contact information Contributions and achievements Organized Repositories High-quality repositories should have meaningful names, clear README files, documentation, licensing information where appropriate, and sensible project structures. Appropriate Team Permissions For companies and development teams, use organizational access controls instead of sharing personal credentials. This creates a clearer separation between individual identities and business projects. Repository Transfer Options If you already have a legitimate project and need to move it, GitHub provides an official repository-transfer process. Eligible repositories can be transferred to another personal account or organization when the applicable requirements are met. Benefits of Building a GitHub Account Properly Creating and developing your own GitHub presence offers several long-term benefits. Authentic Professional Reputation Your contributions represent your actual experience. This can be more valuable to employers, clients, collaborators, and open-source communities than an artificially established profile. Better Security You control your own credentials, recovery methods, authentication settings, repositories, and account history. Greater Transparency An organically developed profile gives other developers a clearer picture of your technical interests and contributions. Long-Term Stability You avoid relying on a third-party seller or former account owner for continued access. Better Compliance Using GitHub according to its Terms of Service and Acceptable Use Policies reduces the risk associated with prohibited or inauthentic activity. GitHub explicitly says that enforcement actions can include restricting or terminating access to an account. How to Build a Professional GitHub Presence If your objective is to establish a strong GitHub profile quickly, follow a structured process. Step 1: Create Your Own Account Start with an account you control and configure it using your real professional identity or an appropriate business identity. Step 2: Secure the Account Set up strong authentication and make sure recovery information is accurate and under your control. Step 3: Build Quality Repositories Publish useful projects rather than attempting to create artificial activity. Include clear documentation and explain what each project does. Step 4: Improve Your Profile Add a concise biography, relevant technologies, portfolio information, and links to appropriate professional resources. Step 5: Contribute Consistently Participate in projects where you can make meaningful contributions. Quality contributions are generally more valuable than artificially increasing activity. Step 6: Use Organizations for Teams Businesses should consider GitHub organizations and appropriate permissions when multiple people need access to company repositories. Is Buying GitHub Accounts Worth It? The answer depends on what someone actually needs. If the goal is simply to obtain an account quickly, purchasing a third-party account may appear convenient. However, convenience does not remove the potential security, ownership, authenticity, or policy issues associated with transferred credentials and established account histories. For many users, the underlying requirement is not an old account at all. It may be a professional developer profile, repository management, team access, project migration, or a stronger online presence.
26년 1학기 산업공학종합설계에서 진행한 Cross-corpus 환경에서의 병리 음성 판별 연구의 정리이다. 데이터 문제와 AIHUB 안심존 이슈 등 여러 장애물 때문에 성공적인 연구가 되진 못했지만, 이후 연구 주제로 발전시켜 진행할 예정이다. 1. 문제 정의 음성으로 정상·병리 여부를 구분하는 모델을 만들었다고 해보자. 익숙한 데이터의 테스트셋에서 좋은 점수를 얻었다면, 다른 병원이나 다른 나라에서 수집한 음성에도 그대로 적용할 수 있을까? 실제 임상 현장처럼 학습에 사용되지 않은 새로운 데이터 환경에 적용될 경우에는 데이터셋 간의 분포 불일치(domain shift) 로 인해 성능이 크게 저하되는 문제가 발생한다. 병리 음성 데이터는 녹음 장비의 주파수 특성이나 주변 소음과 같은 물리적 요인뿐만 아니라 화자의 언어적 배경, 발성 방식, 피실험자 집단의 인구통계학적 특성 등 다양한 환경적 변수에 민감하게 영향을 받기 때문이다. 본 연구는 이러한 문제의식에서 출발하여, 세 개의 병리 음성 코퍼스를 대상으로 교차 코퍼스 환경에서 발생하는 일반화 실패의 구조를 진단하고, 나아가 제한된 타겟 데이터만으로 이를 회복할 수 있는 전이학습 전략의 효과를 검증한다. 2. 데이터 설명 본 연구에서는 네 개의 병리 음성 데이터베이스를 사용하였다: Saarbrücken Voice Database(SVD), Advanced Voice Function Assessment Databases(AVFAD), Far Eastern Memorial Hospital database(FEMH), 그리고VOice ICar fEDerico II database(VOICED).(Pützer & Barry, 2008)(Jesus et al., 2017)(Wang, 2026)(Verde & Sannino, 2018) 코퍼스 간 언어적 변수의 영향을 최소화하기 위해 모든 실험은/a/지속발성 녹음만을 대상으로 수행하였으며, 이를 통해 언어적 정보에 따른 변이를 줄이고 데이터셋 간 비교 가능성을 확보하였다. 3. 활용 모델 본 연구에서는 데이터 규모(DB당 약2,000건)와 계산 효율을 고려하여ResNet18을 채택하였다. ResNet50 이상의 모델은 파라미터 수가25M을 초과하여 소규모 데이터에서 과적합 위험이 높은 반면, ResNet18은 약11M의 파라미터로 충분한 표현력을 확보하면서도 학습 안정성을 유지할 수 있다.(Geng et al., 2025) 본 연구에서는 그림 2와 같이 ImageNet으로 사전학습된 ResNet18을 기반 모델로 사용한다. 입력은Mel-spectrogram을 224×224×3 크기로 변환한 이미지이며, conv1과 bn1을 거쳐 네 개의 잔차 블록(layer1 layer4)을 순차적으로 통과한다. 이후Global Average Pooling을 통해 512차원 임베딩 벡터로 압축되며, Dropout(p=0.5)과 이진 분류를 위한 완전연결층을 거쳐 정상 또는 병리로 분류된다. 512차원 임베딩은 분류 외에도 코퍼스 간 표현 분포 분석에 활용된다. 그림에서 점선으로 표시된 초기 레이어(conv1, bn1, layer1)는 사전학습된 저수준 특징을 보존하기 위해 고정하고, 나머지 레이어(layer2 layer4)는 학습 가능하도록 설정한다. 본 연구에서는 레이어별로 학습률을 달리 적용하는 판별적 학습률(discriminative learning rate) 전략을 사용한다. (Ro & Choi., 2020) 일반적인 전이학습에서 단일 학습률을 전체 레이어에 동일하게 적용할 경우, 사전학습으로 획득한 표현이 손상될 위험이 있다. 이를 방지하기 위해 backbone에는 낮은 학습률(1e-5)을 적용하여 사전학습된 특징 표현을 유지하고, 새로운 분류 과제에 직접 관여하는 분류층에는 높은 학습률(3e-4)을 적용하여 빠른 적응을 유도한다. 4. 전처리 및 학습 각 데이터베이스는 샘플링 레이트와 녹음 길이가 상이하므로 모델 입력의 일관성을 확보하기 위한 전처리가 필요하다. 전처리된 신호는 병리 음성 분류에서 CNN 기반 모델의 입력으로 자주 사용되는 Mel-spectrogram으로 변환하며(Javanmardi et al., 2023; Farazi et al., 2024), 변환에 사용된 주요 파라미터는 <표 3>와 같다. 이후 최솟값–최댓값 정규화를 적용한 후 단일 채널을 3채널로 복제하여 224×224 크기로 조정한다. 본 연구에서는 cross-corpus 병리 음성 분류에서 발생하는 성능 저하의 구조를 진단하고 대응 전략의 효과를 비교하기 위해 zero-shot transfer, multi-source 학습, target fine-tuning, 소규모 외부 사이트 검증을 포함한 실험 전략을 구성한다. 과적합 방지와 일반화 성능 향상을 위해 SpecAugment를 포함한 데이터 증강을 적용하였으며, 세부 학습 설정은 <표 4>와 같다. 5. 결과 단일 데이터 베이스에서의 결과는 위와 같다. 세 corpus 모두 UAR 0.728~0.837 수준으로 내부 분류 성능이 충분히 확보되었으며, 이는 이후 cross-corpus 성능을 판단하는 상한 기준으로 활용된다. Zero shot 전이 실패 양상 그런데 다른 데이터셋으로 그대로 옮기자 여섯 방향의 UAR이 0.504~0.603 , MCC가 0.013~0.209 로 내려갔다. 더 중요한 것은 어떻게 틀렸는가였다. SVD → AVFAD 의 UAR은 0.515였고, 병리 음성을 병리로 맞힌 민감도는 0.962였다. 이 숫자만 보면 병리 음성을 잘 찾아낸 것 같지만, 정상 음성을 정상으로 맞힌 특이도는 0.068 에 불과했다. 실제로는 거의 모두를 병리로 예측하는 쪽으로 기운 것이다. FEMH → AVFAD 도 민감도 0.922, 특이도 0.085로 비슷한 양상이었다. 반대 방향의 쏠림도 있었다. AVFAD → FEMH 의 UAR은 0.588이고 특이도는 0.897이었지만, 민감도는 0.279 였다. 정상 음성은 비교적 잘 구분하는 대신 병리 음성을 많이 놓친 셈이다. 같은 UAR 근처의 점수라도 어느 클래스를 놓치는지에 따라 실패의 모습이 전혀 달랐다. 이 차이를 보기 위해 예측한 병리 비율 - 실제 병리 비율 을 prediction bias 로 정의했다. 양수이면 병리 과다 예측, 음수이면 병리 과소 예측이다. 분석에서는 이 값이 커질수록 민감도는 높고 특이도는 낮아지는 경향이 확인됐다. 핵심은 점수 한 개가 떨어진 사실보다, 데이터셋이 바뀌며 예측이 한쪽 클래스로 기울었다는 것 이다. 이 편향을 이해하기 위해 임계값, 학습 데이터의 클래스 비율, 모델 내부 표현의 차이를 차례로 확인했다. 전이 실패 분석 전이 실패의 원인을 분리하기 위해 몇가지 진단을 수행하였다. 첫째, 결정 임계값 조정 첫 번째 가설은 0.5 라는 분류 임계값이 새 데이터셋에 맞지 않는다는 것이었다. 평가 데이터의 정답을 알고 있다고 가정하고 UAR이 최대가 되는 임계값을 찾아보니, SVD → FEMH 는 0.574에서 0.706 으로 올랐다. 분명 임계값 불일치의 영향이 있었다. 하지만 다른 조합에서는 개선 폭이 작았고, 임계값을 바꿔도 무너진 분류가 충분히 회복되지는 않았다. 또한 정답을 보고 고른 이 값은 원인을 확인하기 위한 상한 실험 이지, 정답이 없는 새 환경에서 그대로 선택할 수 있는 운영 임계값은 아니다. *둘째, 클래스 불균형을 통제하기 위한 클래스 비율(prior) 조정 * 두 번째 가설은 학습 데이터의 정상·병리 비율 차이였다. SVD와 FEMH의 학습 데이터를 1:1 비율로 맞춰 다시 평가했지만, UAR 개선은 조합에 따라 0.002~0.023 에 머물렀다. 클래스 비율은 일부 영향을 줄 수 있으나, 데이터셋 간 성능 저하를 혼자 설명하기에는 부족했다 셋째, source–target 간 centroid distance(CD)와 MMD를 계산하여 6개 조합에서의 거리–성능 상관을 탐색 음성에서 추출한 모델 내부 표현의 분포 차이도 살펴봤다. 데이터셋 간 임베딩 거리와 성능의 관계를 탐색했지만, 방향별 비교가 여섯 개뿐이고 클래스별 거리와 민감도·특이도의 관계가 일관되지 않았다. 언어·녹음·질환 구성 중 어느 요인이 주원인인지는 이 실험만으로 분리할 수 없다. 원인을 하나로 특정하기 어렵다면, 실제 성능을 회복하는 방법은 무엇일까? 학습된 모델에 소량의 타겟 데이터 fine-tuning 대상 데이터의 일부 정답을 학습에 쓸 수 있을 때는 상황이 달라졌다. 예를 들어 SVD → AVFAD 의 대표 실험에서 zero-shot UAR 0.520 이 대상 데이터 미세조정 후 0.820 으로 올랐다. SVD → FEMH 도 0.599 → 0.822 로 회복됐다. 마지막 분류층만 조정하는 것보다 앞쪽 표현까지 조정하는 설정이 유리한 경우가 있었다. 이는 차이가 단순히 최종 임계값이나 마지막 층에만 있지 않을 가능성을 보여준다. 외부 데이터에서 실패한 모델을 현지 데이터로 상당 부분 회복할 수 있었다 는 것이다. 이후 지금까지 졸업논문으로 제출한 cross - corpus 관련된 연구 요약이다. 이 연구를 진행하면서 novelty 있는 결과를 보여주지 못했지만 그 안에서 알게된 것들이 많다. 다음 게시물에서는 한국 AIHUB 음성장애 데이터 활용에 있어 발생한 여러 문제점 IRB 승인 과정 느낀 점 에 대해 작성할 예정이다.
Linux 프로세스·사용자 관리 정리 — Day 13 Day 13 — Process와 사용자 관리 1. Process 구조 Linux에서는 Parent Process가 fork() 를 통해 Child Process를 만들고, Child Process에서 execve() 를 통해 새로운 Program을 실행할 수 있다. Parent Process │ │ fork() / \ / \ / \ Parent Process Child Process │ │ │ execve() │ │ │ 새로운 프로그램 실행 │ │ wait() │ │ exit(status) │ │ │◀──── 종료 상태 전달 │ wait() 반환 │ 부모 실행 계속 Process 확인과 제어에 사용한 명령어는 다음과 같다. ps ps -ef pstree pgrep kill jobs fg %1 bg %1 ps -e : System의 모든 Process 출력 ps -f : 상세 정보 출력 pstree : Parent-Child 관계 확인 jobs : Background 작업 확인 fg / bg : Foreground와 Background 전환 2. Archive와 Compression 여러 File을 하나로 묶을 때 tar 를 사용한다. -c : 새로운 tar 파일 생성 -t : tar 파일의 내부 내용들의 리스트 확인 -x : tar 파일 해제 -k : 덮어씌움 방지 -f : 아카이브 파일이나 테이프 장치를 지정 -v : tar 명령어 수행과정 자세히 출력 -h : 아카이브하려는 파일이 심볼릭 링크인 경우 원본을 아카이브 -z : gz로 압축 진행 Compression에는 gzip , bzip2 , zip 등을 사용할 수 있다. gzip gunzip bzip2 bzcat zip unzip Archive는 여러 File을 하나로 묶는 것이고 Compression은 Data Size를 줄이는 과정이다. 3. 사용자와 Group 정보 사용자와 Group 관련 주요 File은 다음과 같다. /etc/passwd /etc/shadow /etc/group /etc/gshadow /etc/passwd : 사용자 정보 /etc/shadow : Password 정보 /etc/group : Group 정보 /etc/gshadow : Group Password 정보 Linux에서는 사용자 이름보다 UID 를 기준으로 Permission을 판단한다. 실습에서는 두 사용자에게 같은 UID를 설정했을 때 Linux가 동일한 사용자처럼 판단하는 것을 확인했다. nobreak@rocky-node1:~$ sudo useradd test11 nobreak@rocky-node1:~$ sudo usermod -u 1000 -o test11 nobreak@rocky-node1:~$ id test11 uid=1000(nobreak) gid=1002(test11) groups=1000(nobreak) 4. usermod와 userdel 사용자 설정 변경: -a : 기존 보조 그룹 유지하면서 추가 -L : 계정 잠금 -U : 계정 잠금 해제 Group을 추가할 때는 기존 보조 Group을 유지하도록 -aG 를 함께 사용하는 것이 중요하다. sudo usermod -aG admins student6 사용자 삭제: sudo userdel testuser Home Directory까지 함께 삭제: sudo userdel -r testuser -r 없이 삭제하면 사용자 계정은 사라져도 Home Directory는 남을 수 있다. 5. Group 관리 Group 생성: nobreak@rocky-node1:~$ sudo groupadd -g 989 apache Group 이름과 GID 변경: nobreak@rocky-node1:~$ sudo groupmod -n apache2 apache nobreak@rocky-node1:~$ sudo groupmod -g 3000 apache2 nobreak@rocky-node1:~$ grep apache2 /etc/group apache2:x:3000: Group 삭제: nobreak@rocky-node1:~$ sudo groupdel apache2 기본 Group으로 사용되고 있다면 바로 삭제할 수 없다. 6. /etc/skel /etc/skel 은 새로운 사용자의 Home Directory에 기본적으로 들어갈 File을 관리한다. 실습에서는 welcome.txt 와 .bashrc 설정을 추가했다. nobreak@rocky-node1:~$ sudo touch /etc/skel/welcome.txt nobreak@rocky-node1:~$ echo 'echo "Welcome $USER"' | sudo tee -a /etc/skel/.bashrc echo "Welcome $USER" 이후 새로운 사용자를 생성하였다. nobreak@rocky-node1:~$ sudo useradd student11 nobreak@rocky-node1:~$ sudo su - student11 Welcome student11 새 사용자에게만 /etc/skel 의 내용이 복사되며 기존 사용자에게는 자동 적용되지 않는다. 7. su와 sudo su 는 현재 Session에서 다른 사용자로 전환한다. su su - su - 는 Login Shell 형태로 전환하여 해당 사용자의 환경까지 새로 적용한다. sudo 는 허가된 사용자가 root 등의 권한으로 Command를 실행한다. 대표적인 sudo 설정은 /etc/sudoers 에서 확인할 수 있다. root ALL=(ALL) ALL %wheel ALL=(ALL) ALL # %wheel ALL=(ALL) NOPASSWD: ALL % 는 Group을 의미한다. 8. tee Root Permission이 필요한 File에 Redirection할 때 tee 를 활용할 수 있다. nobreak@rocky-node1:~$ echo "hello" | tee hello.txt hello nobreak@rocky-node1:~$ cat hello.txt hello sudo와 함께 사용할 수도 있다. echo "내용" | sudo tee /root/file Day 13 한 줄 정리 Linux에서는 Process를 생성·제어하고, UID·GID를 기반으로 사용자와 Group을 관리하며 sudo 를 통해 높은 권한의 Command 실행을 통제한다. Linux 고급 권한·작업 스케줄링 정리 — Day 14 Day 14 — 특수 Permission과 Scheduling 1. /etc/skel과 sudo Group 실습 새로운 사용자에게 공통 File을 제공하도록 /etc/skel 을 설정하였다. nobreak@rocky-node2:~$ echo "welcome" | sudo tee /etc/skel/welcome.txt welcome nobreak@rocky-node2:~$ sudo useradd student4 nobreak@rocky-node2:~$ sudo ls -l /home/student4/ total 4 -rw-r--r--. 1 student4 student4 8 Sep 29 00:35 welcome.txt .bashrc 에도 Login Message를 추가했다. nobreak@rocky-node2:~$ echo "echo 'hello'" | sudo tee -a /etc/skel/.bashrc echo 'hello' nobreak@rocky-node2:~$ sudo useradd -m student5 nobreak@rocky-node2:~$ sudo su - student5 hello student5@rocky-node2:~$ exit logout sudo를 사용할 admins Group도 구성하였다. nobreak@rocky-node2:~$ sudo groupadd admins nobreak@rocky-node2:~$ sudo visudo -f /etc/sudoers.d/admins nobreak@rocky-node2:~$ sudo cat /etc/sudoers.d/admins %admins ALL=(ALL) ALL nobreak@rocky-node2:~$ sudo useradd student6 nobreak@rocky-node2:~$ sudo usermod -aG admins student6 sudo 사용 기록은 다음 Log에서 확인했다. nobreak@rocky-node2:~$ sudo cat /var/log/secure 2. setuid 실습 일반 사용자는 /etc/shadow 를 읽을 수 없다. nobreak@rocky-node2:~$ mkdir perm_prac nobreak@rocky-node2:~$ cp /bin/cat ~/perm_prac/secret_cat nobreak@rocky-node2:~$ perm_prac/secret_cat /etc/shadow perm_prac/secret_cat: /etc/shadow: Permission denied File Owner를 root로 변경하였다. nobreak@rocky-node2:~$ sudo chown root perm_prac/secret_cat setuid를 설정하였다. nobreak@rocky-node2:~$ sudo chmod u+s perm_prac/secret_cat Permission은 다음과 같이 변경된다. -rwsr-xr-x. 1 root nobreak 69456 Sep 30 00:06 perm_prac/secret_cat 이후 일반 사용자로 실행해도 File Owner인 root의 Effective Permission으로 Program이 실행되어 /etc/shadow 에 접근할 수 있었다. 3. 특수 Permission Permission 기호 역할 setuid s 실행 시 File Owner 권한 사용 setgid s File에서는 Group 권한 사용, Directory에서는 Group 상속 Sticky Bit t 공유 Directory에서 다른 사용자의 File 삭제 제한 Sticky Bit 실습에서는 먼저 모든 사용자가 Write 가능한 Directory를 만들었다. nobreak@rocky-node2:~$ mkdir /tmp/testdir nobreak@rocky-node2:~$ chmod 777 /tmp/testdir/ user01 이 만든 File을 user02 가 삭제할 수 있었다. Sticky Bit 설정: nobreak@rocky-node2:~$ chmod +t /tmp/testdir/ nobreak@rocky-node2:~$ ls -ld /tmp/testdir/ drwxrwxrwt. 2 nobreak nobreak 6 Sep 29 01:26 /tmp/testdir/ 이후 다른 사용자의 File 삭제가 차단되었다. rm: cannot remove '/tmp/testdir/fileA': Operation not permitted 4. ACL 기본 Owner / Group / Others 구조에 포함되지 않는 특정 사용자에게 Permission을 줄 때 ACL을 사용할 수 있다. root@rocky-node2:~# setfacl -m u:user01:rw acl/files/file1.txt root@rocky-node2:~# ls -l acl/files/file1.txt -rw-rw-r--+ 1 root root 10 Sep 29 02:08 acl/files/file1.txt root@rocky-node2:~# getfacl acl/files/file1.txt # file: acl/files/file1.txt # owner: root # group: root user::rw- user:user01:rw- group::r-- mask::rw- other::r-- ACL의 mask 는 실제 적용할 수 있는 최대 Permission을 제한한다. user:user1:rw- #effective:r- group::r-- mask::r-- 5. crontab 반복 작업 Scheduling에는 crontab 을 사용한다. -e : crontab 파일 편집 -l : crontab 파일 내용 출력 -r : crontab 파일 삭제 Cron 구조: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) # | | | | .---- day of week (0 - 6) # | | | | | # * * * * * command 수업에서 사용한 예시는 다음과 같다. # 매일 02:00에 실행 0 2 * * * # 평일 09:00에 실행 0 9 * * 1-5 # 10분마다 실행 */10 * * * * # 2시간마다 실행 0 */2 * * * # 분기별 첫날에 실행 0 0 1 1,4,7,10 * # 5분 마다 현재 시스템의 메모리 사용량 확인 및 결과를 ~/memory_log.txt 파일에 추가 */5 * * * * free -h >> ~/memory_log.txt # 매일 오후 2시 30분에 홈 디렉토리의 .tmp 파일을 모두 삭제하는 작업 30 14 * * * find ~ -name "*.tmp" -type -f -delete 6. Anacron Cron 실행 시점에 System이 꺼져 있으면 작업이 누락될 수 있다. Anacron은 System이 다시 실행되었을 때 작업 주기를 확인해 누락된 작업을 실행할 수 있다. 1 5 cron.daily 7 25 cron.weekly @monthly 45 cron.monthly Day 14 한 줄 정리 setuid·setgid·Sticky Bit와 ACL을 이용해 기본 Permission보다 세밀한 권한을 설정하고, Cron과 Anacron을 이용해 반복 작업을 자동 실행할 수 있다. Linux Disk·LVM 정리 — Day 14 ~ Day 15 Day 14 — Disk와 File System 1. MBR과 GPT Disk의 Partition Table은 대표적으로 MBR과 GPT 방식으로 나뉜다. 구분 MBR GPT Firmware BIOS UEFI 특징 기존 Partition 방식 GUID 기반 Partition 방식 대용량 Disk 약 2TB 제한 매우 큰 Disk 지원 2. fdisk Partition은 fdisk 를 이용해 관리했다. root@rocky-node2:~# fdisk /dev/sdb 주요 Command는 다음과 같다. d delete a partition l list known partition types n add a new partition p print the partition table t change a partition type w write table to disk and exit q quit without saving changes g create a new empty GPT partition table o create a new empty MBR (DOS) partition table Partition 생성 후 XFS 등의 File System을 만들고 Mount하여 사용한다. Day 14 한 줄 정리 Disk는 Partition Table → Partition → File System → Mount 과정을 거쳐 Linux의 Directory 구조에 연결된다. Day 15 — LVM 1. LVM 구조 LVM은 다음 구조로 Storage를 관리한다. Partition ↓ PV ↓ VG ↓ LV ↓ File System ↓ Mount 2. LVM 생성 실습 Partition Type을 Linux LVM으로 변경하였다. root@rocky-node2:~# fdisk /dev/sdb Command (m for help): t Selected partition 1 Hex code or alias (type L to list all): 8e Changed type of partition 'Linux' to 'Linux LVM'. PV 생성: root@rocky-node2:~# pvcreate /dev/sdb1 Physical volume "/dev/sdb1" successfully created. VG 생성: root@rocky-node2:~# vgcreate vg_test /dev/sdb1 Volume group "vg_test" successfully created LV 생성: root@rocky-node2:~# lvcreate -n lv_test -L 5G vg_test Logical volume "lv_test" created. XFS 생성: root@rocky-node2:~# mkfs.xfs /dev/vg_test/lv_test Mount: root@rocky-node2:~# mkdir /lvmtest root@rocky-node2:~# mount /dev/vg_test/lv_test /lvmtest root@rocky-node2:~# df -h /lvmtest Filesystem Size Used Avail Use% Mounted on /dev/mapper/vg_test-lv_test 5.0G 130M 4.9G 3% /lvmtest 3. Logical Volume 확장 1GB File 생성: root@rocky-node2:~# sudo dd if=/dev/zero of=/lvmtest/testfile bs=1M count=1000 1000+0 records in 1000+0 records out 1048576000 bytes (1.0 GB, 1000 MiB) copied, 0.222304 s, 4.7 GB/s Logical Volume을 5GB에서 8GB로 확장하였다. root@rocky-node2:~# lvextend -L 8G /dev/vg_test/lv_test Size of logical volume vg_test/lv_test changed from 5.00 GiB (1280 extents) to 8.00 GiB (2048 extents). Logical volume vg_test/lv_test successfully resized. 하지만 File System은 아직 5GB로 인식하고 있었다. /dev/mapper/vg_test-lv_test 5.0G 1.2G 3.9G 23% /lvmtest XFS File System까지 확장하였다. root@rocky-node2:~# xfs_growfs /lvmtest/ 확인: root@rocky-node2:~# df -h | grep vg /dev/mapper/vg_test-lv_test 8.0G 1.2G 6.8G 15% /lvmtest 4. Snapshot Snapshot 생성: root@rocky-node2:~# lvcreate -L 1G --snapshot --name lv_test_snap /dev/vg_test/lv_test Logical volume "lv_test_snap" created. Snapshot 생성 후 원본에 File을 추가했다. root@rocky-node2:~# touch /lvmtest/snapshot.txt XFS Snapshot은 원본과 같은 UUID를 사용하므로 그대로 Mount했을 때 Error가 발생했다. 이를 무시하고 Mount: root@rocky-node2:~# mount -o nouuid /dev/vg_test/lv_test_snap /snap 원본과 Snapshot을 비교하였다. root@rocky-node2:~# ls -al /snap total 1024000 drwxr-xr-x. 2 root root 22 Sep 30 08:29 . dr-xr-xr-x. 20 root root 262 Sep 30 08:38 .. -rw-r--r--. 1 root root 1048576000 Sep 30 08:29 testfile root@rocky-node2:~# ls -al /lvmtest total 1024000 drwxr-xr-x. 2 root root 42 Sep 30 08:37 . dr-xr-xr-x. 20 root root 262 Sep 30 08:38 .. -rw-r--r--. 1 root root 0 Sep 30 08:37 snapshot.txt -rw-r--r--. 1 root root 1048576000 Sep 30 08:29 testfile Day 15 한
2학년때, 처음 인공지능개론을 배우면서 각 알고리즘에 적합한 Loss Function이 다르다는 것을 배웠다. 사실 그때는 수식을 외우기에 바빠 이해보다 암기 위주로 학습을 진행했던 것 같다. 최근 들어 교수님 앞에서 논문을 리뷰하는 시간이 있는데, 교수님께서 기초적인 개념에 관한 질문을 많이 하신다. 거기에 피상적인 답만 하는 내 모습이 부끄러웠고, 아직 기초가 부족하다는 생각이 들었다. 그렇기에 이번 학기 위클리 블로그는 머릿속에 파편화되어 있는 개념들을, 하나씩 정리해보는 기회로 삼으려 한다. 이번 블로그 주제는 자주 등장하는 Loss Function에 관한 글이다. Regression Task와 Classification Task에서는 왜 서로 다른 Loss Function을 사용할까? 1. Regression & Classification Regression Task 연속적인 값을 예측하는 작업 → 출력은 실수 범위 내 대표적인 Loss Function: MSE(Mean Squared Error), MAE(Mean Absolute Error) $$ MSE = \frac{1}{n}\sum_{i=1}^{n}(y_i-\hat{y}_i)^2 $$ 실제값과 예측값 차를 제곱한 후 평균을 낸 것이다. 즉 MSE는 실제값과 예측값이 얼마나 멀리 떨어져 있는지 계산 할 수 있다. Classification Task 주어진 입력을 여러 클래스 중 하나로 분류하는 작업 → 이산적인 클래스 레이블 분류 작업의 출력은 각 클래스가 정답일 확률 이다. (ex. class = [a,b,c]면 모델의 출력은 [0.1, 0.7, 0.2] 이며, 최종적으론 b로 예측) 대표적인 Loss Function: Cross-Entropy 계열의 Loss (이진 분류)Binary Cross-Entropy (다중 분류) Categorical Cross-Entropy Binary Cross-Entropy $$ BCE = -\frac{1}{n} \sum_{i=1}^{n} \left[ y_i\log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i) \right] $$ 그런데 왜 Classification에서는 MSE가 아니라 Cross-Entropy를 사용하는 걸까? Entropy란 무엇일까? 2. Entropy란? 어떤 시스템이나 확률 분포의 불확실성 또는 정보의 불확실성을 나타내는 척도다. 쉽게 생각하면 어떤 사건의 결과를 얼마나 예측하기 어려운가 를 나타내는 값이다. 이산확률분포 $P$의 Entropy는 다음과 같다. $$ H(P) = -\sum_i P(i)\log P(i) $$ 예를 들어 동전의 앞면이 나올 확률이 100%면 결과가 이미 정해져 있으므로 Entropy는 0이다. 반대로 앞면과 뒷면의 확률이 각각 50%라면, 어떤 결과가 나올지 가장 예측하기 어렵기에 Entropy가 가장 크다. 그런데 식에는 왜 log 가 들어갈까? 정보이론에서는 어떤 사건 $x$가 발생했을 때 얻는 정보량을 다음과 같이 정의한다. $$ I(x) = -\log P(x) $$ 직관적으로 생각해보면, 확률이 높은 사건은 이미 예상하기 쉬운 사건이다. 따라서 실제로 발생해도 새롭게 얻는 정보가 많지 않다. 반대로 확률이 매우 낮은 사건이 실제로 발생하면 더 많은 정보를 얻게 된다. 예를 들어 $$ P(x)=1 $$ 이라면 $$ -\log 1 = 0 $$ 이다. 반드시 발생하는 사건이기 때문에 새로운 정보가 없는 것이다. 반대로 $P(x)$가 작아질수록 $-\log P(x)$는 커진다. Entropy는 이렇게 각 사건이 주는 정보량을 확률분포 전체에 대해 평균낸 값이라고 볼 수 있다. 3. Cross-Entropy 두 확률분포 $P$, $Q$에 대한 Cross-Entropy는 다음과 같다. $$ H(P,Q) = -\sum_i P(i)\log Q(i) $$ Classification에서는 $P$ = 실제 정답 분포 $Q$ = 모델이 예측한 분포 라고 생각할 수 있다. 예를 들어 실제 정답이 b 라면 $$ P = [0,1,0] $$ 이고, 모델의 예측이 $$ Q = [0.1,0.7,0.2] $$ 라고 해보자. Cross-Entropy를 계산하면 $$ -(0\log0.1 + 1\log0.7 + 0\log0.2) $$ 이므로 결국 $$ -\log0.7 $$ 만 남는다. 즉, 정답 클래스에 높은 확률을 줄수록 Loss는 작아지고, 낮은 확률을 줄수록 Loss는 커진다. 그렇다면 Cross-Entropy는 실제 분포와 예측 분포 사이의 차이를 측정하는 값이라고 볼 수 있을까? 여기서 조금 의문이 든다. 분포 간의 차이를 측정하는 방법으론 KL Divergence 나 Wasserstein Distance 같은 것도 있기 때문이다 4. Cross-Entropy와 KL Divergence KL Divergence는 두 확률 분포 간의 차이를 측정하는데 사용된다. $$ D_{KL}(P \parallel Q) = \sum_i P(i)\log\frac{P(i)}{Q(i)} $$ 식을 조금 풀어보면 $$ D_{KL}(P \parallel Q) = \sum_i P(i)\log P(i) - \sum_i P(i)\log Q(i) $$ 가 된다. 여기서 Entropy는 $$ H(P) = -\sum_i P(i)\log P(i) $$ 이고, Cross-Entropy는 $$ H(P,Q) = -\sum_i P(i)\log Q(i) $$ 이다. 따라서 $$ D_{KL}(P \parallel Q) = H(P,Q)-H(P) $$ 라는 관계를 얻을 수 있다. 다시 정리하면 $$ H(P,Q) = H(P)+D_{KL}(P \parallel Q) $$ 이다. 즉, Cross-Entropy = Entropy(실제 분포가 원래 가지고 있는 불확실성) + KL Divergence(두 분포의 차이) 라고 볼 수 있다. 그런데 모델을 학습할 때 실제 정답 분포 $P$는 이미 정해져 있으므로 $H(P)$는 고정된 값이다. 따라서 모델이 Cross-Entropy를 줄이는 것은, 결국 KL Divergence를 줄이는 것과 같다. 즉 Classification에서 Cross-Entropy를 최소화한다는 것은, 모델이 예측한 확률분포를 실제 정답 분포에 가깝게 만드는 과정이라고 이해할 수 있다. 정리하자면, 예측값이 실수인 회귀에서 사용하는 손실 함수 MSE 등은 실제값과 예측값 사이의 수치적인 차이 를 직접 계산할 수 있다. 반면 분류에서는 모델이 각 클래스에 대한 확률을 출력하고, Cross-Entropy를 최소화함으로써 모델이 예측한 확률분포를 실제 정답 분포에 가깝게 만든다! 처음에는 단순히 회귀면 MSE, 분류면 Cross-Entropy 정도로 암기했는데, 이 포스트를 작성면서 두 Task에서 서로 다른 Loss Function을 사용하는지 명확하게 이해할 수 있었다.
- 올것이 왔다. 복학하고 얼마 지나지 않아 코딩경진대회 개최 공지가 떴다. 이번에도 안보면 진짜 다음 시험은 4학년때나 볼 수 있어서... 한번 보기로 했다. PCCP 응시료를 공짜로 내주기도 하고. - 원래는 5만원이나 하는데 학과에서 대신 내준다. 산업기능요원 복무하는동안 회사에서 틈틈히 백준으로 코테 공부를 하긴 했는데, 잠시 휴학했던 사이에 백준은 역사속으로 사라져버렸고... PCCP를 주관하는 프로그래머스는 조금 다른 채점 방식을 사용한다. - solution이라는 메소드만 작성하면 된다. solution() 메소드만 작성해주면, input값을 그대로 가져와준다. 백준에서는 그 input시간도 줄여보겠다고 sys.stdin.readline을 활용했는데.. 그짓을 또 할 필요는 없어졌다. 솔직히 훨씬 편하다. 문제 읽으면서 코드 작성도 한 화면에서 할 수 있게 됐고. 여하튼, 오늘은 대회 준비하면서 풀었던 문제들 분석과 함께 대회 복기를 해보려 한다. PCCP 기출문제 1번 - 붕대 감기 일단 제일 먼저 문법에 익숙해지기 위해 풀었던 붕대감기 문제. 간단한 구현 문제고, 1번 문제인 만큼 난이도는 쉬웠다. def solution(bandage, health, attacks): answer=health now=0 for i,j in attacks: healtime=i-now-1 if answer==health: pass else: answer=min(health,(healtime//bandage[0])*bandage[2]+bandage[1]*healtime+answer) answer-=j now=i if answer<=0: return -1 return answer 대충 attacks 에 for문 돌려서, 각 공격마다 체력(answer)이 0 이하로 내려가면 바로 -1을 return하게 두고, 그렇지 않을 경우 최종적으로 남은 체력을 표시하게 하는 코드다. 문제를 제대로 읽어본다면 금방 풀 수 있는 문제. PCCP 기출문제 2번 - 석유 시추 2번 문제부터 바로 탐색을 시킨다... 머리로 알고 있어도 구현하기 진짜 귀찮은데... 늘 하던대로 ix[0,1,0,-1] iy[1,0,-1,0] 구현해주고 for문으로 상하좌우 탐색하게 시켰다. import sys sys.setrecursionlimit(1000000) def solution(land): m=len(land[0]) n=len(land) amountmap=[0] index=0 visited=[[-1]*m for _ in range(n)] where=[set() for i in range(m)] def searcher(x,y,code): ix=[1,0,-1,0] iy=[0,1,0,-1] visited[x][y]=0 for a in range(4): dx=x+ix[a] dy=y+iy[a] if 0<=dx<n and 0<=dy<m: if visited[dx][dy]==0: pass elif land[dx][dy]==0: visited[dx][dy]=0 pass elif land[dx][dy]!=0: visited[dx][dy]=0 where[dy].add(code) amountmap[code]+=1 searcher(dx,dy,code) for i in range(n): for j in range(m): if visited[i][j]==0: pass elif land[i][j]==0: visited[i][j]=0 pass elif land[i][j]!=0: amountmap[index]+=1 where[j].add(index) searcher(i,j,index) index+=1 amountmap.append(0) answer = 0 for i in where: num=0 for j in i: num+=amountmap[j] answer=max(num,answer) return answer 일단 연습문제는 다 맞았는데, 지도가 커지고 나면 탐색을 하면서 python 기본으로 내장된 재귀 제한 횟수를 넘기게 된다. 이 경우 재귀 제한을 더 늘려주는걸 잊지 말자. import sys sys.setrecursionlimit(1000000) 어쨋든, 각 석유를 발견할 때마다 발견한 순서대로 그 크기를 array에 따로 기록해두고, 석유에 code라는 이름의 시리얼 넘버를 기록한 뒤, set에 각 x 좌표별로 몇번 석유를 시추할 수 있는지 기록했다. 탐색하면서 동시에 솔루션까지 찾을 수 있으니, 이후에 한번 더 x 좌표별로 for문 돌리는것보다 시간 효율적이다. 마지막엔 X좌표별 Set를 하나씩 꺼내서, max값 비교만 해주면 되니까. PCCP 기출문제 3번 - 아날로그 시계 이번 문제는 친구가 풀다가 도저히 못풀었다고 해서 풀어봤다. 구현의 난이도는 그렇게까지 높진 않은데, 문제는 엣지 케이스가 너무 많다;;; def solution(h1, m1, s1, h2, m2, s2): Oclock=h2-h1 answer=0 if h1==h2 and m1==m2: if (h1==0 and m1==0 and s1==0) or (h1==12 and m1==0 and s1==0): return 1 if float((h1%12)*5)+float(m1)/12>=float(s1) and float((h2%12)*5)+m2/12<float(s2): answer+=1 if float(m1)+float(s1)/60>=float(s1) and float(m2)+float(s2)/60<float(s2): answer+=1 return answer totalmin=(h2*60+m2)-(h1*60+m1) if (h1==0 and m1==0 and s1==0): Oclock+=1 totalmin-=1 elif (h1==12 and m1==0 and s1==0): Oclock+=1 totalmin-=1 if s1>0: totalmin-=1 if float((h1%12)*5)+float(m1)/12>=float(s1): answer+=1 if float(m1)+float(s1)/60>=float(s1): answer+=1 elif s2>0: if float((h2%12)*5)+m2/12<float(s2): answer+=1 if float(m2)+float(s2)/60<float(s2): answer+=1 answer+=max(totalmin,0)*2 answer-=max(Oclock,0) return answer 그러니까, 1시간동안 초침은 분침과 시침을 60번 만나고, 자정과 정오의 경우 59번 만난다. 그래서 문제에서 주어진 시간을 풀타임 N시간+(m분 k초)로 구현했는데.. 생각보다 엣지케이스가 많았다. 엣지케이스 다 걸러내는 작업 하고 나서도 몇문제 틀리길래, GPT한테 도와달라고 했다. 자존심 상하긴 했지만, 엣지케이스를 구분할 수 있는것도 실력이라고 생각한다...
Hermes Agent나 OpenClaw에 GPU 작업을 맡길 때, 완료 메시지에 무엇이 있어야 할까? 실행 로그만으로는 부족하다. 내려받은 결과 파일의 경로, 파일을 검사한 결과, 이번에 빌린 인스턴스가 해제됐다는 조회 결과가 필요하다. Vast.ai를 사용하면 CPU 서버에서 실행되는 Agent도 별도의 GPU 머신을 빌려 SSH로 작업할 수 있다. Agent를 GPU 서버로 옮기는 방식이 아니라, 작업에 필요한 동안 외부 머신을 사용하는 방식이다. 여기서는 자체 호스팅 환경에서도 쓸 수 있는 공식 CLI를 기준으로 설명한다. 실행 요청을 파일 기준으로 적기 “렌더링해 줘”라는 요청만으로는 결과를 확인하기 어렵다. 입력 파일의 위치, Blender 버전, 프레임 범위, 해상도, 샘플 수, 결과 형식을 함께 지정한다. 학습 작업이라면 모델, 데이터셋, 설정과 회수할 체크포인트를 정한다. 처음에는 한 프레임이나 작은 입력으로 확인한다. 설치와 전송이 되는지, 애플리케이션이 GPU를 선택하는지 확인한 뒤 전체 작업으로 넘어간다. 작은 시험도 비용이 발생하므로 견적 승인은 필요하다. Agent에는 다음 조건도 전달한다. GPU를 생성하기 전에 오퍼 ID, GPU와 VRAM, 필요한 디스크, 예상 실행 시간과 비용을 보여 주고 승인을 기다린다. 정해진 시간 안에 작업과 결과 회수를 마친다. 결과 파일을 검사한 다음 이번 인스턴스만 해제한다. 실패하면 파일이 남아 있는 위치와 인스턴스 상태를 보고한다. 다른 인스턴스는 변경하지 않는다. 이런 요청이 Vast.ai 계정의 강제 결제 한도를 설정해 주지는 않는다. 시간과 예산을 지키려면 실행 상태를 확인하고, 제한에 도달했을 때 작업을 중단하거나 자원을 해제하는 처리가 필요하다. 읽기부터 연결 확인하기 Agent가 실행되는 머신에 CLI를 설치한다. 아래 키와 경로는 실제 값으로 바꿔야 한다. pip install vastai vastai --help vastai set api-key YOUR_VAST_API_KEY vastai show instances API 키는 신뢰할 수 있는 로컬 터미널이나 비밀정보 설정 화면에서 입력한다. 채팅에 붙여 넣지 않는다. 이 키는 해당 머신에 저장되므로 필요한 권한만 부여한다. 목록 조회와 인스턴스 생성·삭제는 권한을 구분해서 확인한다. 조회 결과에서 기존 작업과 이번 작업을 구별한다. 이후 생성 결과로 받은 인스턴스 ID를 기록하고, 변경 작업은 그 ID에만 적용한다. 작업용 SSH 키를 따로 준비하면 나중에 다른 연결에 영향을 주지 않고 제거할 수 있다. 등록할 것은 공개키다. vastai create ssh-key /path/to/task_key.pub vastai search offers 'gpu_ram>=12 num_gpus=1 rentable=true' 검색 조건은 VRAM 12GB 이상, GPU 한 장을 찾는 예시다. 실제 조건은 모델이나 장면에 맞춰 정한다. 시간당 GPU 요금 외에도 디스크와 전송 비용을 확인한다. 이미 견적에 포함된 항목을 다시 더하지 말고, 확인할 수 없는 비용은 따로 표시한다. 소프트웨어와 호스트 드라이버의 호환성도 확인해야 한다. 생성 응답과 실제 실행은 다르다 승인한 오퍼로만 생성한다. 다음 명령은 작업 흐름의 예시이며, 렌더링이나 학습을 실행하는 완성 스크립트는 아니다. vastai create instance OFFER_ID --image nvidia/cuda:12.8.0-runtime-ubuntu24.04 --disk 20 --ssh vastai show instances OFFER_ID 는 승인한 견적의 ID다. 이미지 역시 CUDA 환경의 예시이므로 작업 소프트웨어와 맞는지 확인한다. 오퍼가 사라지면 다른 머신을 임의로 빌리지 말고 견적을 다시 승인받는다. 생성 요청이 시간 초과됐다고 바로 재시도하면 인스턴스 두 개가 생길 수 있다. 먼저 목록을 조회해 실제 생성 여부를 확인한다. running 상태도 SSH 연결 가능 여부를 보장하지 않으므로 연결 대기 시간을 제한한다. 연결 후 nvidia-smi 로 GPU와 드라이버를 확인한다. GPU가 보이는 것과 프로그램이 GPU를 사용하는 것은 다르다. Blender라면 Cycles의 장치 설정과 실행 로그를 확인한다. 입력 파일을 올린 뒤에는 크기나 SHA-256을 비교해 전송이 온전히 끝났는지 검사한다. 해제 전에 검사할 것 작업이 끝나면 결과를 Agent의 작업 디렉터리로 회수한다. 공식 CLI의 전송 형식은 다음과 같다. vastai copy INSTANCE_ID:/workspace/results/ local:./results/ 이미지는 열리는지와 해상도를, 영상은 재생과 길이를, 모델 파일은 형식과 로딩 여부를 검사한다. 필요한 경우 원격 파일과 로컬 파일의 해시를 비교한다. “다운로드 완료”만으로 검사를 대신하지 않는다. 검사가 끝난 후 이번 인스턴스를 삭제하고 목록을 다시 조회한다. vastai destroy instance INSTANCE_ID vastai show instances destroy 는 인스턴스 안의 데이터를 없앤다. 필요한 파일을 먼저 내려받아야 한다. stop 은 디스크를 유지하므로 저장 비용이 계속 발생할 수 있다. 별도 볼륨 등은 따로 확인한다. 인스턴스 하나가 사라졌다고 계정의 모든 비용이 끝난 것은 아니다. 완료 보고에는 결과 파일의 실제 경로와 검사 내용, 작업 인스턴스 ID와 해제 후 조회 결과, 임시 SSH 공개키 처리 상태를 남긴다. 비용이 아직 확정되지 않았으면 그렇게 적는다. 청구 반영이 늦어질 수 있으므로 잠정 금액을 최종 비용으로 단정하지 않는다. 다운로드한 파일을 사용할 수 있고, 이번 렌탈의 종료 상태를 확인할 수 있어야 요청한 작업을 끝냈다고 말할 수 있다. 필자는 OpenClaw Launch의 Agent 호스팅 서비스를 운영한다. 이 글의 공식 CLI 흐름은 자체 호스팅한 Hermes Agent나 OpenClaw에서도 사용할 수 있다. 참고: Vast.ai 공식 CLI 시작하기 Vast.ai 인스턴스 관리
9월 5주차를 시작하며 지난주에는 RAG의 검색 품질을 높이는 방법을 배웠다면 이번 주에는 검색부터 답변 검토까지 전체 흐름을 연결하는 방법을 배웠다. 그래프와 벡터 검색을 함께 사용하는 방법부터 LangGraph의 조건 분기, 사람의 검토를 받는 HITL, 장기 기억, CRAG와 Self-RAG까지 다뤘다. 수업 내용을 TIL로 정리하고 과제를 풀면서 각 단계가 어떤 역할을 하는지 이해하려고 했다. 수업 외에는 정처기 실기 개념 회독과 문제 풀이도 함께 진행했다. FACTS 그래프와 벡터 검색 벡터 검색으로 시작점을 찾고, 그래프 관계로 범위를 넓히고, 전문 검색 결과와 비교한다. VectorRetriever : 의미가 비슷한 줄거리 찾기 VectorCypherRetriever : 그래프 관계까지 확장하여 같은 배우가 나온 다른 영화 찾기 복합 질문은 관계 조건과 줄거리 조건으로 먼저 나눈다. 관계와 연도는 그래프로 후보를 거르고, 줄거리는 벡터·전문 검색으로 순위를 매긴 뒤 합친다. LangGraph LLM에게 모든 일을 한 번에 시키는 대신, 작업을 작은 Node로 나누고 State로 데이터를 주고받으며 Edge로 실행 순서를 관리한다. State: 노드들이 공유하는 데이터 Node: 실제 작업을 하는 함수 Edge: 다음에 실행할 노드로 가는 연결 State 정의 → Node 함수 정의 → StateGraph 생성 → 노드 등록 → Edge 연결 → compile → invoke 워크플로우는 미리 정한 순서대로 실행하고, Agent는 모델이 상황에 따라 도구, 행동 등을 선택한다. 검토·수정 사이클 한 번에 생성하고 끝내지 않고 먼저 초안을 작성한 후 문제를 찾고, 문제를 고쳐 다시 확인한다. 초안 작성 → 검토 → 기준 미충족 시 수정 → 다시 검토 많이 반복한다고 결과가 좋아지는 것은 아니기 때문에 검토 횟수 상한 을 둔다. 체크포인터 현재 상태와 실행 기록을 저장하는 장치 InMemorySaver: 메모리에 저장 SQLite 파일 DB: 커널을 종료해도 남음 thread_id : 대화·작업을 구분하는 기준 체크포인터는 상태를 저장한다. 멀티턴 대화가 가능하려면 상태에 대화 목록이 저장되어 있어야 하고, LLM의 프롬프트에도 대화 목록을 넣어야 한다. HITL Human-in-the-loop, AI 작업 흐름 중간에 사람이 판단 하는 방식 interrupt() 로 그래프를 멈추고 사람에게 검토를 요청한다. approve: 이 문서로 확정 edit: 수정 후 다시 사람 검토 reject: 이 문서는 확정하지 않고 종료 사람이 선택한 값은 Command(resume=...) 로 그래프에 전달한다. 재개하면 검토 노드의 처음부터 다시 실행된다. 스트리밍 중간 진행 상황을 확인하거나, 최종 응답을 다 완성해서 받는 것이 아니라 생성할 때마다 받기 위한 용도 messages: 생성되는 글자의 조각별로 얻을 수 있음 updates: 어떤 노드가 어떤 State를 바꿨는지 보여줌 values: 초기 상태와 단계별 전체 State를 얻음 custom: 직접 만든 진행 상황, 상태 메시지를 전달 Store와 Mem0 Store는 내가 정리한 기억을 직접 저장하는 도구이고, Mem0는 대화에서 기억할 만한 사실을 추출하는 과정까지 도와주는 기억 관리 도구다. Store는 namespace로 기억 공간을 나누고, key로 기억 한 건을 구분한다. 체크포인터: 현재 대화의 상태 저장 Store: 여러 대화에서 재사용할 장기 기억 저장 Runtime context: 사용자 ID 등 실행 정보 전달 장기 기억에서 항상 참고할 프로필은 통째로 읽고, 과거 경험은 관련된 것만 찾는 게 효율적이다. 컨텍스트 엔지니어링 LLM에 주입할 컨텍스트를 결정하는 것 컨텍스트는 LLM이 답변을 만들 때 받는 모든 정보 로, 시스템 지시, 현재 질문, 이전 대화, 장기 기억, 출력 형식, 요약 등이 포함된다. 무엇을 넣을지 얼마나 넣을지 입력 토큰 예산 안에서 어떻게 구성할지 정보를 많이 넣으면 비용과 응답 지연이 늘어나고, 오래된 조건이 섞이거나 중요한 내용이 누락될 수 있다. 미들웨어 에이전트를 실행할 때 정해진 시점에 공통 처리를 붙이는 기능 “모델 호출 전에는 항상 입력 길이를 조절한다”와 같은 규칙을 노드마다 반복하지 않고 한 곳에 적용할 수 있다. before_model: 모델 호출 전 오래된 대화 요약 wrap_model_call: 입력 예산 계산, 대화 트리밍 after_agent: 요청 종료 후 장기 기억 갱신, 실행 결과 기록 CRAG 검색 결과를 평가하고 부족한 근거를 보완 하는 방법 내부 검색·평가 → 필요하면 검색어 수정·내부 재검색 → 그래도 부족하면 웹 검색·재평가 관련성은 문서 단위로, 충분성은 질문 전체 단위로 본다. 원질문은 바꾸지 않고 검색어만 교정한다. 일부 근거가 있으면 부분 답변으로 활용하고, 근거가 전혀 없을 때는 확인 불가로 끝낸다. Self-RAG 수업에서는 CRAG 프로세스에 답변 검토와 수정 노드를 추가했다. 답변 초안 생성 → 근거성·유용성 검토 → 미통과 시 수정 → 다시 검토 생성된 답변을 원질문과 근거 문서에 대조하고, 검토 의견은 반드시 다음 수정 노드에 전달해야 한다. 검토 한도까지 통과하지 못하면 보류하고 사람의 확인을 받는다. FEELINGS 이번 주도 개념을 이해하는 것과 직접 코드로 작성하는 것 사이에 차이가 느껴졌다. State, Node, Edge의 역할은 알겠는데, 조건 분기와 검토 단계가 늘어나면서 어떤 값이 어디로 전달되는지 따라가는 것이 어려웠다. 과제를 풀면서 막혔던 부분도 새로운 개념만은 아니었다. 모델 응답을 리스트로 반환해야 하는데 문자열의 첫 글자만 꺼내거나, 저장 함수의 반환값을 조회한 값처럼 사용하기도 했다. 기능을 이해했다고 생각했지만 실제로 어떤 데이터를 주고받는지는 놓치고 있었다. 그래도 TIL에 오류와 수정 이유를 함께 정리하면서 헷갈린 지점을 구체적으로 볼 수 있었다. 아직 혼자 전체 흐름을 작성하는 것은 어렵지만, 무엇을 다시 확인해야 하는지는 조금씩 명확해졌다. FINDINGS 가장 크게 배운 점은 함수를 작성하기 전에 그 함수가 어떤 역할을 하고, 어떤 값을 받아 무엇을 반환하는지 먼저 이해해야 한다는 것이다. LangGraph에서 State가 갱신되지 않았던 문제도 결과를 반환하는 것만 생각하고, 어느 필드에 넣어야 하는지 확인하지 않아 생겼다. 기억도 목적에 따라 구분해야 한다는 것을 알게 되었다. 현재 대화의 상태를 이어가는 체크포인터와 사용자 선호를 저장하는 장기 기억은 역할이 달랐다. 저장한 정보를 전부 넣기보다는 현재 질문에 필요한 내용을 골라 전달하는 과정도 중요했다. CRAG와 Self-RAG 실습에서는 관련 문서를 찾았다고 답변할 준비가 끝나는 것은 아니라는 점을 배웠다. 질문 전체에 답할 근거가 충분한지 확인하고, 생성한 답변도 그 근거와 맞는지 다시 검토해야 했다. 검토를 반복하는 것뿐 아니라 언제 멈추고 사람의 확인을 받을지도 정해야 한다는 점이 기억에 남았다. FUTURE 다음에는 코드를 보기 전에 질문이 들어와 답변이 나갈 때까지의 흐름을 먼저 그려보려고 한다. 그다음 각 노드가 읽는 값과 바꾸는 값을 적고, 내부 함수를 확인하는 순서로 공부하고 싶다. TIL에는 이번 주처럼 오류와 해결 이유를 함께 남기되, 자주 헷갈리는 입력·반환 형식을 중심으로 정리하려고 한다. 특히 수정한 코드는 다시 직접 작성해보면서 같은 실수를 줄이고 싶다. CRAG와 Self-RAG는 아직 코드만 보고 전체 흐름을 따라가기 어려워서, 수업에서 쓴 예제를 다시 보려고 한다. 어떤 경우에 재검색을 하고, 어떤 경우에 답변을 수정하는지부터 구분해보고 싶다.