🏠️Top Page

🖊️おもしろき こともなき世をおもしろく 住みなすものは 心なりけり.

⏩️Devin(でびん)とかいう生成AIが凄いらしいのは知っていたけど知らんけど

おはようございます.Devin(でびん)とかいう生成AIが凄いらしいのは知っていたけどついに自分が勤めている会社でもDevinを導入したみたいです.ひとつのリポジトリはDevinさんがほぼコードを書いていて何だか悲しい気持ちになりました?.

逆に言えばこういうDevinみたいな高性能な生成AIを使えば自分一人でも高機能なwebサービスが出来るだなって言う思いがあります、でも高機能なサービスが作れたとしてもそれを営業するノウハウ等がないので自分には結局意味がないのではないかと思います.

Devinさんに負けない高度なコードを書いても生産性では確実に劣っている気がします、ものの数秒で数千行のコードを書くことは人は出来そうにないので、正直なところDevinの性能が上がったら本当に必要なエンジニアは少なくなるでしょうね.

明日へ続く

⏩️自前のAPIでほぼ動かしている.利点はサービスの終了が無いところ

おはようございます.自前のAPIでほぼ動かしている.利点はサービスの終了が無いところですが、VPSサーバー等でしか動かない代物なのでそこが難でサーバー代がかさみます.

そこを何とか低予算で運用したいのだけどもなかなか難しいのが現状です.因みにいまはVPSサーバーとレンタルサーバーを合わして1300円ぐらいを毎月支払っています.

自分にとっては1300円は高いし未だに生成AIの有料版は高いので無料版でしのいでいます.もっと稼げるように慣れば有料版とかを使ってみたいなって思います.

お給与が今の倍以上に慣れば良いのだけども、なかなか難しいそうと言う想いもあります.何時までこのような状況が続くか分からないのであまりお金の掛かることは控えています.

最近読んだ記事で老後2000万円では足りないとか言う記事を読んで絶望しています、老後が心配でいまをあまり楽しんでいないような気もしています.今を楽しみたいなーって!

明日へ続く

⏩️AIでレコメンド記事が表示させるロジックを作りました.#機械学習って

おはようございます.AIでレコメンド記事が表示させるロジックを作りました、前からそのような事を考えていたのですが、あまり機械学習のことを分かっていなかったのですが、以前、地域の商品券で「実務で役立つPython機械学習入門」を購入していたのを先日の休みにパラパラと捲っていたら、これだったらなんか出来そうだなって思いたち.

昨日の休みに生成AIともに作りました.生成AIが出力したコードに対して再度指示を出したり自分で直したりして上手く処理される形に落ち着きました.

Pythonコードは昨日の記事がある程度役に立つと思います、あのコードを元にブラッシュアップ(改善)した形になります.

自分で機械学習のことに手を出すことになるとは思っていなかったけど、これからはPythonの事もたまに試していこうと思います.

因みに過去記事を参照してもらったら分かるかもですが何度か機械学習にトライして挫折した経緯がありますが、今回は生成AIという物があるので生成AIよって噛み砕いた言葉とコードで何とか自分でも初級か中級ぐらいのことは出来そうな気もします.

明日へ続く

⏩️AIレコメンド機能を乗せれるかもしれない.VPSだから微妙 #TFIDFベクトライザ

おはようございます.AIレコメンド機能を乗せれるかもしれないけど無理かもしれないということで、まだ試してはいないのだけど機械学習で学習済みのモデルをVPSに乗せ動かすことが出来れば、AIレコメンド機能が出来そうです.

いまある記事のデータのタグ付け部分をTF-IDFベクトライザの学習させれば案外簡単に学習させることが出来そうなので生成AIにコードを書いてもらいました.

尚、この方法はECサイトの商品のレコメンド機能にも同じような感じでデータを与えるとレコメンドしてくれたりします.

最後にPythonコードを貼っときます.VPSサーバで再学習できれば良いだけども難しいかもしれない、、、.

import pickle
import os

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import pandas as pd

# モデル保存ファイル名
MODEL_PATH = "tfidf_vectorizer.pkl"  # TfidfVectorizer
DATA_PATH = "article_tags.pkl"       # 記事IDとタグ

# 類似度モデル構築(再学習対応)
def build_similarity_model(article_tags_input, save_model=True, retrain=False):
    # 再学習時は既存データを読み込んで結合する
    if retrain and os.path.exists(DATA_PATH):
        with open(DATA_PATH, "rb") as f:
            existing_data = pickle.load(f)
        existing_data.update(article_tags_input)  # 新しいデータを追加
        article_tags_input = existing_data

    article_ids = list(article_tags_input.keys())
    processed_corpus = [" ".join(tags) for tags in article_tags_input.values()]

    # 再学習またはモデル未保存時に新たにモデルを学習・保存
    if retrain or not os.path.exists(MODEL_PATH):
        vectorizer = TfidfVectorizer()
        tfidf_matrix = vectorizer.fit_transform(processed_corpus)

        if save_model:
            with open(MODEL_PATH, "wb") as f:
                pickle.dump(vectorizer, f)  # ← ここでモデルを保存
            with open(DATA_PATH, "wb") as f:
                pickle.dump(article_tags_input, f)  # ← ここで元データを保存

    else:
        # 保存済みモデルを使って変換する
        with open(MODEL_PATH, "rb") as f:
            vectorizer = pickle.load(f)
        tfidf_matrix = vectorizer.transform(processed_corpus)

    cosine_sim_matrix = cosine_similarity(tfidf_matrix)
    cosine_sim_df = pd.DataFrame(cosine_sim_matrix, index=article_ids, columns=article_ids)

    return cosine_sim_df, article_ids

# 類似記事を取得する関数
def get_recommendations(article_title, similarity_matrix, articles_map, top_n=3):
    if article_title not in articles_map:
        print(f"エラー: 記事 '{article_title}' が見つかりません。")
        return []

    sim_scores = list(enumerate(similarity_matrix[article_title]))
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)

    recommended_articles = []
    for i, score in sim_scores:
        if articles_map[i] != article_title and len(recommended_articles) < top_n:
            recommended_articles.append((articles_map[i], score))
        if len(recommended_articles) >= top_n:
            break

    return recommended_articles

# 入力記事データ ( 例 )
article_tags_input = {
    "記事1": ["Python", "機械学習", "データサイエンス"],
    "記事2": ["Python", "Web開発", "Django"],
    "記事3": ["機械学習", "自然言語処理"],
    "記事4": ["データサイエンス", "統計学"],
    "記事5": ["Python", "データサイエンス", "可視化"]
}

# 類似度モデル構築 + モデル保存 ( 初回学習 )
cosine_sim_df, article_ids = build_similarity_model(article_tags_input)

# 使用例
target_article = "記事1"
recommendations = get_recommendations(target_article, cosine_sim_df, article_ids, top_n=2)
print(f"\n「{target_article}」へのおすすめ記事 ( 上位2件 ):")
for article, score in recommendations:
    print(f"- {article} (類似度: {score:.4f})")

# 新しい記事を追加して再学習
new_article_id = "記事6"
new_article_tags = ["Python", "統計学"]
article_tags_input = {new_article_id: new_article_tags}

# 再構築+再学習
cosine_sim_df, article_ids = build_similarity_model(article_tags_input, retrain=True)
target_article = new_article_id
recommendations = get_recommendations(target_article, cosine_sim_df, article_ids, top_n=2)
print(f"\n「{target_article}」へのおすすめ記事 ( 上位2件 ):")
for article, score in recommendations:
    print(f"- {article} (類似度: {score:.4f})")

明日へ続く

⏩️高知県の地方通貨ジモッペイに対応したら恐らく使う人増えると思うこと.

おはようございます、高知県の地方通貨ジモッペイに対応したら恐らく使う人増えると思うことを書いていきます.1.JR四国とタイアップ、2.とさでん交通とタイアップ、これらが出来れば間違いなく使う人が増えると思います.

特にとさでん交通とタイアップは出来そうな気もします.ジモッペイでとさでん交通を乗れるように慣れば結構便利だなって思います.そもそもとさでん交通のicカード「ですか」のチャージがとても不便です、チャージ出来る場所も限られていたり、スマホ板「ですか」も無いので高知県人でも限られた人しかicカードを持ってない気がしています.

なので、ジモッペイにシステム作ってもらったら、どちらもウィン・ウィンになるのではないかと思います.

JR四国とのタイアップが出来れば良いのだけども恐らく難しいと思うので、まずはとさでん交通さんとタイアップして頂きたいです.

ちなみにジモッペイ使ったことがないです、間違いなくとさでん交通さんとタイアップしたら自分のスマホにジモッペイを入れます!

明日へ続く

⏩️お知らせ.ブログの不具合をコンプリートしました.

おはようございます、ブログの不具合をコンプリートしました.今までほったらかしにしていた、検索結果のページの不具合をこの度修正しました.

どんな不具合が発生していたかと言えば、次頁へ遷移せずシングルページの次頁へ遷移するという不具合です.この不具合は自分のテンプレートを作ってからずっと今まで発生したのを知っていたけど対応を放置していた不具合になります.

ちなみに分かりにくいと思いますが、関数名のポストが複数形に置き換わっただけです...じゃ直ぐに対応出来たのではというご指摘があるかと思いますが案外気が付かないですよ.生成AIに投げてやっと「あっ」と思ったわけです.

人には先入観とかいう物があるけど今のところ、生成AIは機械的なのでそういうミスは指摘してくれます.いやー便利ですよね本当に.

明日へ続く

⏩️万博の来場者数と5月23日以降の予測をバイブコーディングで作ってみました

おはようございます.万博の来場者数と5月23日以降の予測をバイブコーディングで作ってみましたのでコードをシェアします.大阪万博の来場者数はYahooニュースの記事のデータを元にしています、其処から今後の予測を立ててもらいました.

https://zip358.com/tool/EXPO2025-OSAKA-KANSAI-JAPAN

因みに予測は簡易的なものなので、恐らく大きくハズレるとは思いますがそれなりの表が生成されるのが凄いなと思います.生成AIはOpenAIの無料枠を使用してデータと次のデータを元に10月13日までの予測を出力してという指示とJavaScriptやCSSを使用してグラフ化してという指示を出しただけで、コードを書いてくれます.

    const labels = [];
    const allVisitors = [];
    const generalVisitors = [];
    const staffVisitors = [];

    const rawData = [
      ["4/13", 146426, 22000], ["4/14", 70488, 17000], ["4/15", 63719, 16000], ["4/16", 73869, 15000],
      ["4/17", 82692, 15000], ["4/18", 93908, 15000], ["4/19", 108773, 15000], ["4/20", 95524, 16142],
      ["4/21", 99638, 17352], ["4/22", 103729, 17099], ["4/23", 99140, 17795], ["4/24", 108888, 17376],
      ["4/25", 110759, 17591], ["4/26", 122102, 17299], ["4/27", 102015, 16985], ["4/28", 121282, 17465],
      ["4/29", 97559, 17813], ["4/30", 101397, 17621], ["5/1", 105945, 17548], ["5/2", 104805, 17741],
      ["5/3", 120696, 17886], ["5/4", 136805, 16289], ["5/5", 126371, 16804], ["5/6", 76517, 16935],
      ["5/7", 91688, 16932], ["5/8", 105449, 18578], ["5/9", 105782, 19813], ["5/10", 128918, 21373],
      ["5/11", 121667, 18268], ["5/12", 117658, 17856], ["5/13", 123640, 18087], ["5/14", 129527, 17668],
      ["5/15", 129456, 17762], ["5/16", 132817, 17700], ["5/17", 123974, 16464], ["5/18", 117000, 16000],
      ["5/19", 129000, 17000], ["5/20", 125000, 18000], ["5/21", 129000, 19000], ["5/22", 134000, 18000],
      ["5/23", 157000, 18000]
    ];

    rawData.forEach(([date, total, staff]) => {
      labels.push(date);
      allVisitors.push(total);
      staffVisitors.push(staff);
      generalVisitors.push(total - staff);
    });

    const futureDays = 143;
    for (let i = 1; i <= futureDays; i++) {
      const futureDate = new Date(2025, 4, 13 + i); // 4月13日からスタート
      const label = `${futureDate.getMonth() + 1}/${futureDate.getDate()}`;
      labels.push(label);

      // 予測ロジック: 最近5日間の平均にランダムなゆらぎを加える
      const recentGeneral = generalVisitors.slice(-5);
      const avgGeneral = recentGeneral.reduce((a, b) => a + b, 0) / recentGeneral.length;
      const fluctuation = Math.sin(i / 5) * 5000 + (Math.random() - 0.5) * 8000;
      const predictedGeneral = Math.round(avgGeneral + fluctuation);

      const predictedStaff = 18000 + Math.round(Math.sin(i / 7) * 1000);
      const predictedTotal = predictedGeneral + predictedStaff;

      generalVisitors.push(predictedGeneral);
      staffVisitors.push(predictedStaff);
      allVisitors.push(predictedTotal);
    }

    const ctx = document.getElementById('visitorChart').getContext('2d');
    const chart = new Chart(ctx, {
      type: 'line',
      data: {
        labels: labels,
        datasets: [
          {
            label: '来場者数(合計)',
            data: allVisitors,
            borderColor: 'rgba(75, 192, 192, 1)',
            backgroundColor: 'rgba(75, 192, 192, 0.1)',
            borderWidth: 2,
            tension: 0.3,
            fill: true
          },
          {
            label: '一般来場者数',
            data: generalVisitors,
            borderColor: 'rgba(255, 159, 64, 1)',
            backgroundColor: 'rgba(255, 159, 64, 0.1)',
            borderWidth: 2,
            tension: 0.4,
            fill: true
          },
          {
            label: '関係者数',
            data: staffVisitors,
            borderColor: 'rgba(153, 102, 255, 1)',
            backgroundColor: 'rgba(153, 102, 255, 0.1)',
            borderWidth: 2,
            tension: 0.4,
            fill: true
          }
        ]
      },
      options: {
        animation: {
          duration: 1500,
          easing: 'easeInOutQuart'
        },
        responsive: true,
        scales: {
          y: {
            title: {
              display: true,
              text: '人数'
            }
          },
          x: {
            title: {
              display: true,
              text: '日付'
            },
            ticks: {
              maxRotation: 90,
              minRotation: 45,
              maxTicksLimit: 50
            }
          }
        },
        plugins: {
          legend: {
            position: 'top',
          },
          title: {
            display: false
          }
        }
      }
    });

これは脅威だなって思う人もいると思いますが、自分は便利だなって思う方です.今後、数年でエンジニア職を奪われかねないという懸念もありますが、コード書きは無くならないじゃないかなって思う方です.理由は修正しないといけない事が必ず起きる事.動作はするが予期しない動作が発生した場合、素人には対応できないため.

未来は人が描いている斜め上の未来になる事が多く、どう転ぶかは「神のみぞ知る」だと思います.なのであまり脅威に思わずそうなった時に対応するというスタンスで良いのかなと.

明日へ続く

⏩️デジタルアドレスAPIのコードを書いてみました.書いたのは

おはようございます.デジタルアドレスAPIのコードを書いてみました.書いたのは生成AIだけど一度で上手く正しいコードが生成出来たわけではなくて二、三回の指示出しを行って下記のコードが生成されました.

デジタルアドレスのAPIを使用するには企業もしくは個人事業者で屋号を登録されている方でないとAPIのアカウント登録は今のところ出来ないので、自分はリファレンスと生成AIが出力したコードを見て恐らく正しく処理されるだろうと思ったのでgist.githubに公開しました.

尚、引数にIPアドレスを渡さないといけない所があるけれど、これはサーバーのIPアドレスになります.


<?php

class JapanPostAPIClient
{
    private string $clientId;
    private string $secretKey;
    private string $clientIp;
    private ?string $accessToken = null;
    private ?array $lastResponse = null;

    public function __construct(string $clientId, string $secretKey, string $clientIp)
    {
        $this->clientId = $clientId;
        $this->secretKey = $secretKey;
        $this->clientIp = $clientIp;
    }

    public function authenticate(): self
    {
        $url = 'https://api.da.pf.japanpost.jp/api/v1/j/token';
        $data = json_encode([
            'grant_type' => 'client_credentials',
            'client_id' => $this->clientId,
            'secret_key' => $this->secretKey
        ]);

        $headers = [
            "Content-Type: application/json",
            "x-forwarded-for: {$this->clientIp}"
        ];

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $response = curl_exec($ch);
        $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpcode !== 200) {
            throw new Exception("Token request failed with status {$httpcode}: {$response}");
        }

        $responseData = json_decode($response, true);
        $this->accessToken = $responseData['token'] ?? null;
        $this->lastResponse = $responseData;

        return $this;
    }

    public function searchCode(string $searchCode, array $params = []): self
    {
        if (!$this->accessToken) {
            throw new Exception("Access token is not set. Please call authenticate() first.");
        }

        $defaultParams = [
            'page' => 1,
            'limit' => 10,
            'choikitype' => 1,
            'searchtype' => 1
        ];

        $queryParams = http_build_query(array_merge($defaultParams, $params));
        $url = "https://api.da.pf.japanpost.jp/api/v1/searchcode/{$searchCode}?{$queryParams}";

        $headers = [
            "Authorization: Bearer {$this->accessToken}",
            "Accept: application/json"
        ];

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode !== 200) {
            throw new Exception("Search request failed with status {$httpCode}: {$response}");
        }

        $this->lastResponse = json_decode($response, true);
        return $this;
    }

    public function getJson(): string
    {
        return json_encode($this->lastResponse, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    }

    public function getArray(): ?array
    {
        return $this->lastResponse;
    }
}

// 使い方の例:
// $client = new JapanPostAPIClient('YOUR_CLIENT_ID', 'YOUR_SECRET_KEY', 'IPアドレス.xxx.xxx.xxx');
// echo $client->authenticate()->searchCode('1000001')->getJson();

明日へ続く

⏩️いにしえのサイトの変数の命名を直しました.大部分を生成AIに直してもらった.

おはようございます.いにしえのサイトの変数の命名を直しました.大部分を生成AIに直してもらいましたがまだ残ってはいます.因みにいにしえのサイトというのは、このサイトのことを指しています.

生成AIに「変数と関数の命名を直して」と頼むとプログラミングのコードはそのままで変数の命名や関数の命名を直してくれます.

その方法でいにしえのサイトのコードを書き換えて「駆け出し感」は少し消えている感じがします.

命名もそうですがプログラミングには命名規則という物があります.自分はそんな事を知らずにコードを書いてきたのですが、いまやっと命名規則などをちゃんとした職場にいます.

なのですが、今までのなんか癖が抜けきれていない部分があり、ちゃんとしたコードを昔から書いていたらという後悔があります.

最後に命名規則というのは何なのかという人のために命名規則をわかりやすく書いているQiitaの記事を載せておきます.

明日へ続く

⏩️投稿時間を6時から7時へシフトしました.通勤時間帯に.

おはようございます.今までは6時投稿だったのだけど通勤時間帯にシフトしました、今まで6時にしていたのは、競合を避けて投稿していた感じになります.その昔まえは9時投稿にしていました.9時に投稿していた理由は仕事の関係者に記事を読まれたくなかったので、仕事が始まる9時に予約投稿していた形になります.

前置きはこれぐらいで本題、7時にシフトしたことを書いていきます.7時にシフトした事により微量にアクセス数が増えるのかも知れないという淡い期待もあります.

理由はSNSなどと連動しているので、通勤時間帯で電車やバスで通勤しているひとの目に止まりそうな気がしています.

今までより競争相手は多いもののユーザー傾向から言えば、その層が一番多いので期待もあります.もし上手く行かなったら「しれっ」と元の時間帯に戻しているかも知れません.

明日へ続く